Devicetree
 help / color / mirror / Atom feed
* [PATCH v4 0/2] power: supply: qcom_smbx: add SMB5 support
@ 2026-08-20 10:03 Robin Snyders via B4 Relay
  2026-08-20 10:03 ` [PATCH v4 1/2] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs Robin Snyders via B4 Relay
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-20 10:03 UTC (permalink / raw)
  To: Casey Connolly, Sebastian Reichel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Robin Snyders, linux-arm-msm, linux-pm, devicetree, linux-kernel,
	Dmitry Baryshkov, Konrad Dybcio, Krzysztof Kozlowski,
	Joel Selvaraj

Add support for the SMB5 charger generation found in PM7250B and
PM8150B to the existing qcom_smbx driver.

The series extends the binding and selects register layouts, charger
status encodings and electrical limits from per-PMIC match data. Encode
PM7250B as PM8150B-compatible in the binding and let the compatible
fallback select the shared charge-parameter block, including the +0x08
effective input-current status register. Convert the SMB5 0.2 V/A USB
current-sense voltage measurement to microamps while leaving the SMB2
current reading unchanged.

SMB5 setup suspends USB input and disables charging before configuration.
The power-supply registration init callback programs battery limits and
completes final input and charging setup before device_add publishes the
properties, so public callbacks cannot observe partially initialized
hardware. Managed rollback restores the charging state before the USB input
state; if charging cannot be restored, USB input remains suspended. Managed
teardown cancels status work before unregistering the power supply.

Enable periodic hardware AICL with the downstream twelve-second rerun
interval while leaving ADC-based AICL disabled by default. Clear the
unsupported HVDCP modes so firmware state cannot raise VBUS, and do not
override the firmware recharge policy without a binding input. The
dedicated TCPM and VBUS regulator drivers retain ownership of Type-C
power-role and VBUS control.

On PM8150B, the SMB5 charger and VBUS regulator use the same DCDC
peripheral but do not write the same registers. qcom_smbx only reads
0x1108 and 0x110b there; qcom_usb_vbus-regulator writes 0x1140, 0x1152
and 0x1153. The qcom_smbx write to 0x1153 belongs only to the SMB2
initialization sequence and is not executed for SMB5. The TCPM port and PD
PHY use the separate 0x15xx and 0x17xx peripherals. Patch 2 names the
USBIN BC1.2 integration register and SMB2-only OTG definitions explicitly
to document this boundary.

Apply the five-patch qcom_smbx Fixes series before this one. This series is
based on power-supply for-next (99b38cda3f4c). The b4 dependency metadata
records all five prerequisite patch IDs. This ordering preserves the SMB2
watchdog, health, overvoltage and float-voltage fixes across the SMB5
driver refactoring. The prerequisite series is public at:
https://lore.kernel.org/r/20260812-qcom-smbx-fixes-v1-0-eb48246be599@snyders.xyz/

The v3 implementation was tested on a OnePlus 7T Pro (HD1913) with
PM8150B. Register reads confirmed 4.40 V, 1.50 A and 500 mA limits. A
180-second guarded charging trace and a subsequent 600-second runtime
trace completed with health Good and the input current below its 500 mA
limit. After a physical USB-C disconnect and reconnect, USB networking and
the charger recovered automatically; a further 60-second trace remained
Good and below the input-current limit. The final kernel log had no SMB5
warning, error, oops or lockdep report.

The v4 driver was rebuilt and retested on the same device. A 180-second
trace and a 600-second, 121-sample guarded trace completed with the charger
online, health Good and no guard failure. With a powered USB-C dock, the
phone remained the data host and PD power sink; with the dock unpowered, it
remained the data host and enabled the VBUS regulator as the power source.
The USB 2 and USB 3 hubs, storage and RTL8153 interface enumerated in both
connector orientations, and 64 MiB read-only storage reads produced the
same hash in all four dock configurations. Reconnecting the phone directly
to the PC restored the power-sink/device roles, charging, NCM and SSH
without a reboot. Ethernet traffic and visual DisplayPort output were not
tested in this run because no Ethernet carrier or display was available.
The final kernel log had no SMB5, Type-C or VBUS warning, error, oops or
lockdep report.

The driver builds with LLVM W=1 and passes Sparse. The binding passes
dt_binding_check, and both patches pass checkpatch --strict. A full LLVM
Image, modules and DTBs integration build completed and booted on the test
device.

Signed-off-by: Robin Snyders <robin@snyders.xyz>
---
Changes in v4:
- Make the PM8150B charger, VBUS regulator and TCPM register ownership
  explicit, including the shared DCDC peripheral's disjoint accesses.
- Rename the USBIN BC1.2 integration register and SMB2-only OTG definitions
  to prevent them from being mistaken for SMB5 TCPM/VBUS controls.
- Test powered sink/host and unpowered source/host USB-C dock operation in
  both connector orientations, including storage I/O and gadget recovery.
- Link to v3: https://lore.kernel.org/r/20260813-submit-qcom-smbx-send-v1-v3-0-27be0091d7c7@snyders.xyz

Changes in v3:
- Complete SMB5 input and charging setup in the pre-device_add power-supply
  registration init callback instead of gating setters on a driver-specific
  probe flag.
- Register the power supply and initialize managed status work before
  requesting IRQs so handlers cannot observe an unassigned power supply.
- Let PM7250B match the required PM8150B compatible fallback and remove its
  duplicate match-data entry.
- Collect the binding Reviewed-by tag.
- Link to v2: https://lore.kernel.org/r/20260812-submit-qcom-smbx-send-v1-v2-0-f504b8f9bfad@snyders.xyz

Changes in v2:
- Rebase onto power-supply for-next 99b38cda3f4c.
- Encode PM7250B with the required PM8150B compatible fallback.
- Leave SMB5 Type-C power-role and VBUS control to the TCPM and regulator
  drivers.
- Preserve the SMB2 3-second AICL interval and use the SMB5 downstream
  12-second interval; leave ADC-based AICL disabled by default.
- Disable unsupported HVDCP negotiation deterministically.
- Do not override the firmware recharge policy without a binding input.
- Model PM7250B with PM8150B electrical limits and the +0x08 AICL ICL
  status register.
- Convert the SMB5 0.2 V/A USB current-sense reading to microamps while
  leaving the SMB2 current reading unscaled.
- Program battery limits in the pre-device_add registration init callback
  and gate setters until probe completes.
- Order managed teardown so status work is cancelled before the power
  supply is unregistered.
- Restore charging before USB input on probe failure, and leave input
  suspended if charging restoration fails.
- Correct the SMB5 overvoltage register and charger-state decoding.
- Avoid repeated error logs when polling a persistent overvoltage state.
- Use battery-info property presence when selecting charge targets.
- Remove duplicate scaling from the already-prescaled USB voltage reading
  and clarify the binding IIO channel descriptions.
- Update the binding title, Kconfig prompt and module description for both
  charger generations.
- Move the five pre-existing SMB2 fixes to a prerequisite Fixes series.
- Link to v1: https://lore.kernel.org/r/20260811-submit-qcom-smbx-send-v1-v1-0-feec6cfa123a@snyders.xyz

---
Casey Connolly (2):
      dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs
      power: supply: qcom_smbx: add SMB5 support

 .../power/supply/qcom,pmi8998-charger.yaml         |  17 +-
 drivers/power/supply/Kconfig                       |   8 +-
 drivers/power/supply/qcom_smbx.c                   | 757 ++++++++++++++++-----
 3 files changed, 609 insertions(+), 173 deletions(-)
---
base-commit: 99b38cda3f4c486cfbc40f3c8ede1703594e9f13
change-id: 20260811-submit-qcom-smbx-send-v1-69acefa61977
prerequisite-patch-id: 3d8a9cf43b5e5eac64a60aeec5e9c90880bcf792
prerequisite-patch-id: de3e3b494f9644596cfa4cafc362ca0b48288c45
prerequisite-patch-id: d5e22a0fe7cd55374f60d7c2cb14ed3df8f94e0b
prerequisite-patch-id: 9eafa41402e97d6a3e5369bec46df4e9db3d2af6
prerequisite-patch-id: 5638b901cd10773fa9485605f84b25a1dd826a84

Best regards,
-- 
Robin Snyders <robin@snyders.xyz>



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v4 1/2] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs
  2026-08-20 10:03 [PATCH v4 0/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
@ 2026-08-20 10:03 ` Robin Snyders via B4 Relay
  2026-08-20 10:03 ` [PATCH v4 2/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
  2026-09-02 20:09 ` [PATCH v4 0/2] " David Heidelberg
  2 siblings, 0 replies; 6+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-20 10:03 UTC (permalink / raw)
  To: Casey Connolly, Sebastian Reichel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Robin Snyders, linux-arm-msm, linux-pm, devicetree, linux-kernel,
	Dmitry Baryshkov, Konrad Dybcio, Krzysztof Kozlowski

From: Casey Connolly <casey.connolly@linaro.org>

Add compatible strings for PM7250B and PM8150B, which contain the SMB5
generation of the Qualcomm switch-mode battery charger. PM7250B uses the
same charger interface and parameters as PM8150B, so require the PM8150B
compatible as its fallback.

Clarify the IIO channel descriptions because SMB5 exposes a USB current-
sense voltage while SMB2 exposes an already-converted current.

Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Robin Snyders <robin@snyders.xyz>
---
 .../bindings/power/supply/qcom,pmi8998-charger.yaml     | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml b/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
index 70f5cd6eaeab..25511e2e3369 100644
--- a/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
+++ b/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
@@ -4,16 +4,21 @@
 $id: http://devicetree.org/schemas/power/supply/qcom,pmi8998-charger.yaml#
 $schema: http://devicetree.org/meta-schemas/core.yaml#
 
-title: Qualcomm PMI8998/PM660 Switch-Mode Battery Charger "2"
+title: Qualcomm SMB2 and SMB5 Switch-Mode Battery Charger
 
 maintainers:
   - Casey Connolly <casey.connolly@linaro.org>
 
 properties:
   compatible:
-    enum:
-      - qcom,pmi8998-charger
-      - qcom,pm660-charger
+    oneOf:
+      - enum:
+          - qcom,pmi8998-charger
+          - qcom,pm660-charger
+          - qcom,pm8150b-charger
+      - items:
+          - const: qcom,pm7250b-charger
+          - const: qcom,pm8150b-charger
 
   reg:
     maxItems: 1
@@ -30,8 +35,8 @@ properties:
 
   io-channels:
     items:
-      - description: USB in current in uA
-      - description: USB in voltage in uV
+      - description: USB input current or current-sense voltage measurement
+      - description: USB input voltage measurement
 
   io-channel-names:
     items:

-- 
2.54.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v4 2/2] power: supply: qcom_smbx: add SMB5 support
  2026-08-20 10:03 [PATCH v4 0/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
  2026-08-20 10:03 ` [PATCH v4 1/2] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs Robin Snyders via B4 Relay
@ 2026-08-20 10:03 ` Robin Snyders via B4 Relay
  2026-09-04 20:37   ` David Heidelberg
  2026-09-05  0:17   ` David Heidelberg
  2026-09-02 20:09 ` [PATCH v4 0/2] " David Heidelberg
  2 siblings, 2 replies; 6+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-20 10:03 UTC (permalink / raw)
  To: Casey Connolly, Sebastian Reichel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Robin Snyders, linux-arm-msm, linux-pm, devicetree, linux-kernel,
	Dmitry Baryshkov, Konrad Dybcio, Joel Selvaraj

From: Casey Connolly <casey.connolly@linaro.org>

Introduce support for the SMB5 charger found on PM7250B, PM8150B and
related Qualcomm PMICs.

SMB5 uses different DCDC status offsets, charger-state encodings and
electrical ranges. Select these from per-PMIC match data, with PM7250B
using the PM8150B compatible fallback and parameter block. Read
overvoltage from the SMB5 status bit, use the already-prescaled IIO
voltage reading, and convert the SMB5 current-sense voltage to microamps.
Use battery-info property presence when selecting voltage and current
targets.

Keep Type-C power-role and VBUS control with the dedicated TCPM and
regulator drivers. Clear the unsupported HVDCP negotiation modes so stale
firmware settings cannot raise VBUS. Leave the firmware recharge policy
unchanged and match the downstream default of ADC-based AICL disabled,
while enabling periodic hardware AICL with its twelve-second SMB5 rerun
interval. Preserve the existing three-second SMB2 interval.

PM8150B places the charger and VBUS regulator in the same DCDC peripheral,
but the SMB5 path does not write the regulator registers. qcom_smbx reads
0x1108 and 0x110b, while qcom_usb_vbus-regulator writes 0x1140, 0x1152
and 0x1153. The SMB2-only OTG configuration write to 0x1153 is not part
of the SMB5 initialization sequence. The TCPM port and PD PHY use the
separate 0x15xx and 0x17xx peripherals. Name the USBIN BC1.2 integration
register and SMB2-only OTG definitions accordingly to make this ownership
boundary explicit.

Program the battery limits and complete SMB5 input and charging setup
from the power-supply registration init callback before device_add
publishes the properties. This makes all public callbacks safe without
driver-specific probe synchronization.

Suspend USB input and charging before SMB5 initialization. On a later
failure, restore the original charging-enable state before the
input-suspend state; leave the input suspended if charging cannot be
restored. Cancel status work before unregistering the power supply during
managed teardown. Update the Kconfig description to cover both charger
generations.

On a OnePlus 7T Pro, register reads from the initial implementation
confirmed the programmed 4.40 V, 1.50 A and 500 mA limits. A 180-second
guarded charging trace and a subsequent 600-second runtime trace
completed without crossing the voltage guard.

Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Co-developed-by: Joel Selvaraj <foss@joelselvaraj.com>
Signed-off-by: Joel Selvaraj <foss@joelselvaraj.com>
Co-developed-by: Robin Snyders <robin@snyders.xyz>
Signed-off-by: Robin Snyders <robin@snyders.xyz>
---
 drivers/power/supply/Kconfig     |   8 +-
 drivers/power/supply/qcom_smbx.c | 757 ++++++++++++++++++++++++++++++---------
 2 files changed, 598 insertions(+), 167 deletions(-)

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index b89ef40df7f8..1159a7711919 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -1173,13 +1173,13 @@ config BATTERY_CHARGER_SURFACE_RT
 	  will be called surface-rt-ec.
 
 config CHARGER_QCOM_SMB2
-	tristate "Qualcomm PMI8998 PMIC charger driver"
+	tristate "Qualcomm SMB2 and SMB5 charger driver"
 	depends on MFD_SPMI_PMIC
 	depends on IIO
 	help
-	  Say Y here to enable the Qualcomm PMIC Charger driver. This
-	  adds support for the SMB2 switch mode battery charger found
-	  in PMI8998 and related PMICs.
+	  Say Y here to enable the Qualcomm PMIC charger driver. This adds
+	  support for the SMB2 and SMB5 switch-mode battery chargers found
+	  in PMI8998, PM660, PM7250B and PM8150B PMICs.
 
 config FUEL_GAUGE_MM8013
 	tristate "Mitsumi MM8013 fuel gauge driver"
diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
index 71f5a4859412..bc2b1cd3ed68 100644
--- a/drivers/power/supply/qcom_smbx.c
+++ b/drivers/power/supply/qcom_smbx.c
@@ -23,6 +23,13 @@
 #include <linux/types.h>
 #include <linux/workqueue.h>
 
+enum smb_generation {
+	SMB2,
+	SMB5,
+};
+
+#define SMB_REG_OFFSET(smb) ((smb)->gen == SMB2 ? 0x600 : 0x100)
+
 /* clang-format off */
 #define BATTERY_CHARGER_STATUS_1			0x06
 #define BVR_INITIAL_RAMP_BIT				BIT(7)
@@ -34,14 +41,20 @@
 #define BATTERY_CHARGER_STATUS_2			0x07
 #define INPUT_CURRENT_LIMITED_BIT			BIT(7)
 #define CHARGER_ERROR_STATUS_SFT_EXPIRE_BIT		BIT(6)
-#define CHARGER_ERROR_STATUS_BAT_OV_BIT			BIT(5)
+#define SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT		BIT(5)
 #define CHARGER_ERROR_STATUS_BAT_TERM_MISSING_BIT	BIT(4)
 #define BAT_TEMP_STATUS_MASK				GENMASK(3, 0)
 #define BAT_TEMP_STATUS_SOFT_LIMIT_MASK			GENMASK(3, 2)
-#define BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT		BIT(3)
-#define BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT		BIT(2)
-#define BAT_TEMP_STATUS_TOO_HOT_BIT			BIT(1)
-#define BAT_TEMP_STATUS_TOO_COLD_BIT			BIT(0)
+#define SMB2_BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT		BIT(3)
+#define SMB2_BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT	BIT(2)
+#define SMB2_BAT_TEMP_STATUS_TOO_HOT_BIT			BIT(1)
+#define SMB2_BAT_TEMP_STATUS_TOO_COLD_BIT		BIT(0)
+
+#define SMB5_CHARGER_ERROR_STATUS_BAT_OV_BIT		BIT(1)
+#define SMB5_BAT_TEMP_STATUS_HOT_SOFT_BIT		BIT(5)
+#define SMB5_BAT_TEMP_STATUS_COLD_SOFT_BIT		BIT(4)
+#define SMB5_BAT_TEMP_STATUS_TOO_HOT_BIT			BIT(3)
+#define SMB5_BAT_TEMP_STATUS_TOO_COLD_BIT		BIT(2)
 
 #define BATTERY_CHARGER_STATUS_4			0x0A
 #define CHARGE_CURRENT_POST_JEITA_MASK			GENMASK(7, 0)
@@ -78,10 +91,10 @@
 #define FLOAT_VOLTAGE_CFG				0x70
 #define FLOAT_VOLTAGE_SETTING_MASK			GENMASK(7, 0)
 
-#define FG_UPDATE_CFG_2_SEL				0x7D
+#define SMB2_FG_UPDATE_CFG_2_SEL			0x7D
 #define SOC_LT_OTG_THRESH_SEL_BIT			BIT(3)
-#define SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT		BIT(2)
-#define VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT		BIT(1)
+#define SMB2_SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT		BIT(2)
+#define SMB2_VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT		BIT(1)
 #define IBT_LT_CHG_TERM_THRESH_SEL_BIT			BIT(0)
 
 #define JEITA_EN_CFG					0x90
@@ -101,13 +114,13 @@
 #define USBIN_LT_3P6V_RT_STS_BIT			BIT(1)
 #define USBIN_COLLAPSE_RT_STS_BIT			BIT(0)
 
-#define OTG_CFG						0x153
+#define SMB2_OTG_CFG					0x153
 #define OTG_RESERVED_MASK				GENMASK(7, 6)
 #define DIS_OTG_ON_TLIM_BIT				BIT(5)
 #define QUICKSTART_OTG_FASTROLESWAP_BIT			BIT(4)
 #define INCREASE_DFP_TIME_BIT				BIT(3)
 #define ENABLE_OTG_IN_DEBUG_MODE_BIT			BIT(2)
-#define OTG_EN_SRC_CFG_BIT				BIT(1)
+#define SMB2_OTG_EN_SRC_CFG_BIT				BIT(1)
 #define CONCURRENT_MODE_CFG_BIT				BIT(0)
 
 #define OTG_ENG_OTG_CFG					0x1C0
@@ -189,10 +202,12 @@
 #define TYPEC_TRYSINK_DETECT_STATUS_BIT			BIT(0)
 
 #define CMD_APSD					0x341
-#define ICL_OVERRIDE_BIT				BIT(1)
 #define APSD_RERUN_BIT					BIT(0)
 
-#define TYPE_C_CFG					0x358
+#define CMD_ICL_OVERRIDE				0x342
+#define ICL_OVERRIDE_BIT				BIT(0)
+
+#define USBIN_TYPE_C_CFG				0x358
 #define APSD_START_ON_CC_BIT				BIT(7)
 #define WAIT_FOR_APSD_BIT				BIT(6)
 #define FACTORY_MODE_DETECTION_EN_BIT			BIT(5)
@@ -260,7 +275,7 @@
 #define EXIT_SNK_BASED_ON_CC_BIT			BIT(7)
 #define VCONN_EN_ORIENTATION_BIT			BIT(6)
 #define TYPEC_VCONN_OVERCURR_INT_EN_BIT			BIT(5)
-#define VCONN_EN_SRC_BIT				BIT(4)
+#define SMB2_VCONN_EN_SRC_BIT				BIT(4)
 #define VCONN_EN_VALUE_BIT				BIT(3)
 #define TYPEC_POWER_ROLE_CMD_MASK			GENMASK(2, 0)
 #define UFP_EN_CMD_BIT					BIT(2)
@@ -274,7 +289,7 @@
 #define SUSPEND_ON_COLLAPSE_USBIN_BIT			BIT(7)
 #define USBIN_AICL_HDC_EN_BIT				BIT(6)
 #define USBIN_AICL_START_AT_MAX_BIT			BIT(5)
-#define USBIN_AICL_RERUN_EN_BIT				BIT(4)
+#define USBIN_AICL_PERIODIC_RERUN_EN_BIT		BIT(4)
 #define USBIN_AICL_ADC_EN_BIT				BIT(3)
 #define USBIN_AICL_EN_BIT				BIT(2)
 #define USBIN_HV_COLLAPSE_RESPONSE_BIT			BIT(1)
@@ -303,10 +318,12 @@
 #define AICL_SWITCH_ENABLE_BIT				BIT(1)
 #define ZIN_ICL_ENABLE_BIT				BIT(0)
 
-#define ICL_STATUS					0x607
 #define INPUT_CURRENT_LIMIT_MASK			GENMASK(7, 0)
 
-#define POWER_PATH_STATUS				0x60B
+#define SMB2_ICL_STATUS					0x607
+#define SMB5_AICL_ICL_STATUS				0x108
+
+#define POWER_PATH_STATUS(smb)				(SMB_REG_OFFSET(smb) + 0x0B)
 #define P_PATH_INPUT_SS_DONE_BIT			BIT(7)
 #define P_PATH_USBIN_SUSPEND_STS_BIT			BIT(6)
 #define P_PATH_DCIN_SUSPEND_STS_BIT			BIT(5)
@@ -335,6 +352,8 @@
 
 #define AICL_RERUN_TIME_CFG				0x661
 #define AICL_RERUN_TIME_MASK				GENMASK(1, 0)
+#define AICL_RERUN_TIME_3_SECS				0
+#define AICL_RERUN_TIME_12_SECS				1
 
 #define STAT_CFG					0x690
 #define STAT_SW_OVERRIDE_VALUE_BIT			BIT(7)
@@ -350,22 +369,30 @@
 #define DCP_CURRENT_UA					1500000
 #define CURRENT_MAX_UA					DCP_CURRENT_UA
 
-/* pmi8998 registers represent current in increments of 1/40th of an amp */
-#define CURRENT_SCALE_FACTOR				25000
-#define SMB2_FLOAT_VOLTAGE_MIN_UV			3487500
-#define SMB2_FLOAT_VOLTAGE_MAX_UV			4920000
-#define SMB2_FLOAT_VOLTAGE_STEP_UV			7500
+/* PMI8998 registers represent current in increments of 1/40th of an amp. */
+#define SMB2_CURRENT_STEP_UA				25000
 /* clang-format on */
 
-enum charger_status {
-	TRICKLE_CHARGE = 0,
-	PRE_CHARGE,
-	FAST_CHARGE,
-	FULLON_CHARGE,
-	TAPER_CHARGE,
-	TERMINATE_CHARGE,
-	INHIBIT_CHARGE,
-	DISABLE_CHARGE,
+enum smb2_charger_status {
+	SMB2_TRICKLE_CHARGE = 0,
+	SMB2_PRE_CHARGE,
+	SMB2_FAST_CHARGE,
+	SMB2_FULLON_CHARGE,
+	SMB2_TAPER_CHARGE,
+	SMB2_TERMINATE_CHARGE,
+	SMB2_INHIBIT_CHARGE,
+	SMB2_DISABLE_CHARGE,
+};
+
+enum smb5_charger_status {
+	SMB5_INHIBIT_CHARGE = 0,
+	SMB5_TRICKLE_CHARGE,
+	SMB5_PRE_CHARGE,
+	SMB5_FULLON_CHARGE,
+	SMB5_TAPER_CHARGE,
+	SMB5_TERMINATE_CHARGE,
+	SMB5_PAUSE_CHARGE,
+	SMB5_DISABLE_CHARGE,
 };
 
 struct smb_init_register {
@@ -374,13 +401,21 @@ struct smb_init_register {
 	u8 val;
 };
 
+struct smb_match_data;
+
 /**
  * struct smb_chip - smb chip structure
  * @dev:		Device reference for power_supply
  * @name:		The platform device name
  * @base:		Base address for smb registers
  * @regmap:		Register map
- * @batt_info:		Battery data from DT
+ * @match_data:		Per-PMIC hardware parameters
+ * @gen:		Charger hardware generation
+ * @icl_max_ua:		Maximum programmable USB input current
+ * @icl_step_ua:	USB input current register step
+ * @icl_status:		Effective input current status register offset
+ * @usbin_current_scale: USB input current-sense scale
+ * @initial_usb_suspend:	USB input suspend state before SMB5 setup
  * @initial_charge_enable: Charging enable state before hardware setup
  * @status_change_work: Worker to handle plug/unplug events
  * @cable_irq:		USB plugin IRQ
@@ -394,7 +429,13 @@ struct smb_chip {
 	const char *name;
 	unsigned int base;
 	struct regmap *regmap;
-	struct power_supply_battery_info *batt_info;
+	const struct smb_match_data *match_data;
+	enum smb_generation gen;
+	unsigned int icl_max_ua;
+	unsigned int icl_step_ua;
+	u16 icl_status;
+	unsigned int usbin_current_scale;
+	u8 initial_usb_suspend;
 	u8 initial_charge_enable;
 
 	struct delayed_work status_change_work;
@@ -407,6 +448,22 @@ struct smb_chip {
 	struct power_supply *chg_psy;
 };
 
+struct smb_match_data {
+	const char *name;
+	enum smb_generation gen;
+	size_t init_seq_len;
+	unsigned int fv_min_uv;
+	unsigned int fv_max_uv;
+	unsigned int fv_step_uv;
+	unsigned int fcc_max_ua;
+	unsigned int fcc_step_ua;
+	unsigned int icl_max_ua;
+	unsigned int icl_step_ua;
+	u16 icl_status;
+	unsigned int usbin_current_scale;
+	const struct smb_init_register *init_seq;
+};
+
 static enum power_supply_property smb_properties[] = {
 	POWER_SUPPLY_PROP_MANUFACTURER,
 	POWER_SUPPLY_PROP_MODEL_NAME,
@@ -424,7 +481,7 @@ static int smb_get_prop_usb_online(struct smb_chip *chip, int *val)
 	unsigned int stat;
 	int rc;
 
-	rc = regmap_read(chip->regmap, chip->base + POWER_PATH_STATUS, &stat);
+	rc = regmap_read(chip->regmap, chip->base + POWER_PATH_STATUS(chip), &stat);
 	if (rc < 0) {
 		dev_err(chip->dev, "Couldn't read power path status: %d\n", rc);
 		return rc;
@@ -479,9 +536,80 @@ static int smb_apsd_get_charger_type(struct smb_chip *chip, int *val)
 	return 0;
 }
 
+/* Return 1 when in overvoltage state, else 0 or -errno */
+static int smbx_ov_status(struct smb_chip *chip)
+{
+	u8 mask;
+	int rc;
+	u32 val;
+
+	switch (chip->gen) {
+	case SMB2:
+		mask = SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT;
+		break;
+	case SMB5:
+		mask = SMB5_CHARGER_ERROR_STATUS_BAT_OV_BIT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	rc = regmap_read(chip->regmap,
+			 chip->base + BATTERY_CHARGER_STATUS_2, &val);
+	if (rc)
+		return rc;
+
+	return !!(val & mask);
+}
+
+static int smb_map_charge_status(struct smb_chip *chip, u32 stat, int *val)
+{
+	switch (chip->gen) {
+	case SMB2:
+		switch (stat) {
+		case SMB2_TRICKLE_CHARGE:
+		case SMB2_PRE_CHARGE:
+		case SMB2_FAST_CHARGE:
+		case SMB2_FULLON_CHARGE:
+		case SMB2_TAPER_CHARGE:
+			*val = POWER_SUPPLY_STATUS_CHARGING;
+			return 0;
+		case SMB2_TERMINATE_CHARGE:
+		case SMB2_INHIBIT_CHARGE:
+			*val = POWER_SUPPLY_STATUS_FULL;
+			return 0;
+		case SMB2_DISABLE_CHARGE:
+			*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+			return 0;
+		}
+		break;
+	case SMB5:
+		switch (stat) {
+		case SMB5_TRICKLE_CHARGE:
+		case SMB5_PRE_CHARGE:
+		case SMB5_FULLON_CHARGE:
+		case SMB5_TAPER_CHARGE:
+			*val = POWER_SUPPLY_STATUS_CHARGING;
+			return 0;
+		case SMB5_TERMINATE_CHARGE:
+		case SMB5_INHIBIT_CHARGE:
+			*val = POWER_SUPPLY_STATUS_FULL;
+			return 0;
+		case SMB5_PAUSE_CHARGE:
+		case SMB5_DISABLE_CHARGE:
+			*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+			return 0;
+		}
+		break;
+	}
+
+	*val = POWER_SUPPLY_STATUS_UNKNOWN;
+	return 0;
+}
+
 static int smb_get_prop_status(struct smb_chip *chip, int *val)
 {
-	unsigned char stat[2];
+	u32 stat;
 	int usb_online = 0;
 	int rc;
 
@@ -491,49 +619,36 @@ static int smb_get_prop_status(struct smb_chip *chip, int *val)
 		return rc;
 	}
 
-	rc = regmap_bulk_read(chip->regmap,
-			      chip->base + BATTERY_CHARGER_STATUS_1, &stat, 2);
+	rc = regmap_read(chip->regmap,
+			 chip->base + BATTERY_CHARGER_STATUS_1, &stat);
 	if (rc < 0) {
 		dev_err(chip->dev, "Failed to read charging status ret=%d\n",
 			rc);
 		return rc;
 	}
 
-	if (stat[1] & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
+	rc = smbx_ov_status(chip);
+	if (rc < 0)
+		return rc;
+
+	/* In overvoltage state */
+	if (rc == 1) {
 		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
 		return 0;
 	}
 
-	stat[0] = stat[0] & BATTERY_CHARGER_STATUS_MASK;
+	stat &= BATTERY_CHARGER_STATUS_MASK;
 
-	switch (stat[0]) {
-	case TRICKLE_CHARGE:
-	case PRE_CHARGE:
-	case FAST_CHARGE:
-	case FULLON_CHARGE:
-	case TAPER_CHARGE:
-		*val = POWER_SUPPLY_STATUS_CHARGING;
-		return rc;
-	case DISABLE_CHARGE:
-		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
-		return rc;
-	case TERMINATE_CHARGE:
-	case INHIBIT_CHARGE:
-		*val = POWER_SUPPLY_STATUS_FULL;
-		return rc;
-	default:
-		*val = POWER_SUPPLY_STATUS_UNKNOWN;
-		return rc;
-	}
+	return smb_map_charge_status(chip, stat, val);
 }
 
 static inline int smb_get_current_limit(struct smb_chip *chip,
 					 unsigned int *val)
 {
-	int rc = regmap_read(chip->regmap, chip->base + ICL_STATUS, val);
+	int rc = regmap_read(chip->regmap, chip->base + chip->icl_status, val);
 
 	if (rc >= 0)
-		*val *= CURRENT_SCALE_FACTOR;
+		*val *= chip->icl_step_ua;
 	return rc;
 }
 
@@ -541,12 +656,13 @@ static int smb_set_current_limit(struct smb_chip *chip, unsigned int val)
 {
 	unsigned char val_raw;
 
-	if (val > 4800000) {
+	if (val > chip->icl_max_ua) {
 		dev_err(chip->dev,
-			"Can't set current limit higher than 4800000uA");
+			"Can't set current limit higher than %uuA",
+			chip->icl_max_ua);
 		return -EINVAL;
 	}
-	val_raw = val / CURRENT_SCALE_FACTOR;
+	val_raw = val / chip->icl_step_ua;
 
 	return regmap_write(chip->regmap, chip->base + USBIN_CURRENT_LIMIT_CFG,
 			    val_raw);
@@ -607,12 +723,10 @@ static void smb_status_change_work(struct work_struct *work)
 static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan,
 			     int *val)
 {
-	int rc;
-	union power_supply_propval status;
+	int rc, status;
 
-	rc = power_supply_get_property(chip->chg_psy, POWER_SUPPLY_PROP_STATUS,
-				       &status);
-	if (rc < 0 || status.intval != POWER_SUPPLY_STATUS_CHARGING) {
+	rc = smb_get_prop_status(chip, &status);
+	if (rc < 0 || status != POWER_SUPPLY_STATUS_CHARGING) {
 		*val = 0;
 		return 0;
 	}
@@ -625,7 +739,61 @@ static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan,
 	return iio_read_channel_processed(chan, val);
 }
 
-static int smb_get_prop_health(struct smb_chip *chip, int *val)
+static int smb_get_prop_current_now(struct smb_chip *chip, int *val)
+{
+	s64 current_ua;
+	int rc;
+
+	rc = smb_get_iio_chan(chip, chip->usb_in_i_chan, val);
+	if (rc < 0)
+		return rc;
+
+	current_ua = (s64)*val * chip->usbin_current_scale;
+	if (current_ua < INT_MIN || current_ua > INT_MAX)
+		return -ERANGE;
+
+	*val = (int)current_ua;
+	return 0;
+}
+
+static int smb5_get_prop_health(struct smb_chip *chip, int *val)
+{
+	int rc;
+	unsigned int stat;
+
+	rc = smbx_ov_status(chip);
+	if (rc < 0) {
+		dev_err(chip->dev,
+			"Couldn't determine overvoltage status: %d\n", rc);
+		return rc;
+	}
+	if (rc) {
+		*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+		return 0;
+	}
+
+	rc = regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_7,
+			 &stat);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read charger status 7 rc=%d\n", rc);
+		return rc;
+	}
+
+	if (stat & SMB5_BAT_TEMP_STATUS_TOO_COLD_BIT)
+		*val = POWER_SUPPLY_HEALTH_COLD;
+	else if (stat & SMB5_BAT_TEMP_STATUS_TOO_HOT_BIT)
+		*val = POWER_SUPPLY_HEALTH_OVERHEAT;
+	else if (stat & SMB5_BAT_TEMP_STATUS_COLD_SOFT_BIT)
+		*val = POWER_SUPPLY_HEALTH_COOL;
+	else if (stat & SMB5_BAT_TEMP_STATUS_HOT_SOFT_BIT)
+		*val = POWER_SUPPLY_HEALTH_WARM;
+	else
+		*val = POWER_SUPPLY_HEALTH_GOOD;
+
+	return 0;
+}
+
+static int smb2_get_prop_health(struct smb_chip *chip, int *val)
 {
 	int rc;
 	unsigned int stat;
@@ -637,15 +805,15 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
 		return rc;
 	}
 
-	if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT)
+	if (stat & SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT)
 		*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
-	else if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT)
+	else if (stat & SMB2_BAT_TEMP_STATUS_TOO_COLD_BIT)
 		*val = POWER_SUPPLY_HEALTH_COLD;
-	else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT)
+	else if (stat & SMB2_BAT_TEMP_STATUS_TOO_HOT_BIT)
 		*val = POWER_SUPPLY_HEALTH_OVERHEAT;
-	else if (stat & BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT)
+	else if (stat & SMB2_BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT)
 		*val = POWER_SUPPLY_HEALTH_COOL;
-	else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
+	else if (stat & SMB2_BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
 		*val = POWER_SUPPLY_HEALTH_WARM;
 	else
 		*val = POWER_SUPPLY_HEALTH_GOOD;
@@ -653,6 +821,19 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
 	return 0;
 }
 
+static int smb_get_prop_health(struct smb_chip *chip, int *val)
+{
+	switch (chip->gen) {
+	case SMB2:
+		return smb2_get_prop_health(chip, val);
+	case SMB5:
+		return smb5_get_prop_health(chip, val);
+	default:
+		dev_err(chip->dev, "unsupported SMB chip generation\n");
+		return -EINVAL;
+	}
+}
+
 static int smb_get_property(struct power_supply *psy,
 			     enum power_supply_property psp,
 			     union power_supply_propval *val)
@@ -669,8 +850,7 @@ static int smb_get_property(struct power_supply *psy,
 	case POWER_SUPPLY_PROP_CURRENT_MAX:
 		return smb_get_current_limit(chip, &val->intval);
 	case POWER_SUPPLY_PROP_CURRENT_NOW:
-		return smb_get_iio_chan(chip, chip->usb_in_i_chan,
-					 &val->intval);
+		return smb_get_prop_current_now(chip, &val->intval);
 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 		return smb_get_iio_chan(chip, chip->usb_in_v_chan,
 					 &val->intval);
@@ -721,17 +901,15 @@ static int smb_property_is_writable(struct power_supply *psy,
 static irqreturn_t smb_handle_batt_overvoltage(int irq, void *data)
 {
 	struct smb_chip *chip = data;
-	unsigned int status;
 	int rc;
 
-	rc = regmap_read(chip->regmap,
-			 chip->base + BATTERY_CHARGER_STATUS_2, &status);
+	rc = smbx_ov_status(chip);
 	if (rc < 0) {
-		dev_err(chip->dev, "Couldn't read charger status: %d\n", rc);
+		dev_err(chip->dev, "could not read overvoltage status: %d\n", rc);
 		return IRQ_HANDLED;
 	}
 
-	if (status & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
+	if (rc) {
 		/* The hardware stops charging automatically */
 		dev_err(chip->dev, "battery overvoltage detected\n");
 	}
@@ -777,6 +955,144 @@ static irqreturn_t smb_handle_wdog_bark(int irq, void *data)
 	return IRQ_HANDLED;
 }
 
+static bool smb_batt_info_has_prop(struct power_supply_battery_info *batt_info,
+				   enum power_supply_property prop)
+{
+	return power_supply_battery_info_has_prop(batt_info, prop);
+}
+
+static int smb_power_supply_init(struct power_supply *psy)
+{
+	struct smb_chip *chip = power_supply_get_drvdata(psy);
+	const struct smb_match_data *match_data = chip->match_data;
+	struct power_supply_battery_info *batt_info;
+	unsigned int fast_charge_current_sel;
+	unsigned int float_voltage_sel;
+	bool program_float_voltage = true;
+	int fast_charge_current_ua;
+	int float_voltage_uv;
+	int rc;
+
+	rc = power_supply_get_battery_info(psy, &batt_info);
+	if (rc)
+		return dev_err_probe(chip->dev, rc,
+				     "Failed to get battery info\n");
+
+	if (smb_batt_info_has_prop(batt_info,
+				   POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX)) {
+		float_voltage_uv = batt_info->constant_charge_voltage_max_uv;
+	} else if (smb_batt_info_has_prop(batt_info,
+					  POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN)) {
+		float_voltage_uv = batt_info->voltage_max_design_uv;
+	} else {
+		dev_warn(chip->dev,
+			 "No battery float voltage; preserving hardware setting\n");
+		program_float_voltage = false;
+	}
+
+	if (program_float_voltage) {
+		if (float_voltage_uv < match_data->fv_min_uv ||
+		    float_voltage_uv > match_data->fv_max_uv) {
+			rc = dev_err_probe(chip->dev, -EINVAL,
+					   "float voltage %d uV outside %u-%u uV\n",
+					   float_voltage_uv,
+					   match_data->fv_min_uv,
+					   match_data->fv_max_uv);
+			goto out_put_batt_info;
+		}
+
+		float_voltage_sel =
+			(float_voltage_uv - match_data->fv_min_uv) /
+			match_data->fv_step_uv;
+		rc = regmap_update_bits(chip->regmap,
+					chip->base + FLOAT_VOLTAGE_CFG,
+					FLOAT_VOLTAGE_SETTING_MASK,
+					float_voltage_sel);
+		if (rc < 0) {
+			rc = dev_err_probe(chip->dev, rc,
+					   "could not set float voltage\n");
+			goto out_put_batt_info;
+		}
+	}
+
+	if (chip->gen == SMB5) {
+		if (smb_batt_info_has_prop(batt_info,
+					   POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX))
+			fast_charge_current_ua =
+				batt_info->constant_charge_current_max_ua;
+		else
+			fast_charge_current_ua = DCP_CURRENT_UA;
+
+		if (fast_charge_current_ua > match_data->fcc_max_ua) {
+			rc = dev_err_probe(chip->dev, -EINVAL,
+					   "fast charge current %d uA exceeds %u uA\n",
+					   fast_charge_current_ua,
+					   match_data->fcc_max_ua);
+			goto out_put_batt_info;
+		}
+
+		fast_charge_current_sel = fast_charge_current_ua /
+			match_data->fcc_step_ua;
+		rc = regmap_update_bits(chip->regmap,
+					chip->base + FAST_CHARGE_CURRENT_CFG,
+					FAST_CHARGE_CURRENT_SETTING_MASK,
+					fast_charge_current_sel);
+		if (rc < 0) {
+			rc = dev_err_probe(chip->dev, rc,
+					   "could not set fast charge current\n");
+			goto out_put_batt_info;
+		}
+
+		rc = smb_set_current_limit(chip, SDP_CURRENT_UA);
+		if (rc < 0) {
+			rc = dev_err_probe(chip->dev, rc,
+					   "could not set USB input current\n");
+			goto out_put_batt_info;
+		}
+
+		if (program_float_voltage)
+			dev_info(chip->dev,
+				 "charge limits: float=%u uV fast=%u uA input=%u uA\n",
+				 match_data->fv_min_uv +
+				 float_voltage_sel * match_data->fv_step_uv,
+				 fast_charge_current_sel * match_data->fcc_step_ua,
+				 SDP_CURRENT_UA);
+		else
+			dev_info(chip->dev,
+				 "charge limits: float=firmware fast=%u uA input=%u uA\n",
+				 fast_charge_current_sel * match_data->fcc_step_ua,
+				 SDP_CURRENT_UA);
+	}
+
+	rc = 0;
+
+out_put_batt_info:
+	power_supply_put_battery_info(psy, batt_info);
+
+	if (rc)
+		return rc;
+
+	if (chip->gen != SMB5)
+		return 0;
+
+	rc = regmap_update_bits(chip->regmap,
+				chip->base + CHARGING_ENABLE_CMD,
+				CHARGING_ENABLE_CMD_BIT,
+				CHARGING_ENABLE_CMD_BIT);
+	if (rc < 0)
+		return dev_err_probe(chip->dev, rc,
+				     "could not enable charging\n");
+
+	rc = regmap_update_bits(chip->regmap,
+				chip->base + USBIN_CMD_IL,
+				USBIN_SUSPEND_BIT, 0);
+	if (rc < 0)
+		return dev_err_probe(chip->dev, rc,
+				     "could not enable USB input\n");
+
+	return 0;
+}
+
 static const struct power_supply_desc smb_psy_desc = {
 	.name = "pmi8998_charger",
 	.type = POWER_SUPPLY_TYPE_USB,
@@ -789,36 +1105,85 @@ static const struct power_supply_desc smb_psy_desc = {
 	.get_property = smb_get_property,
 	.set_property = smb_set_property,
 	.property_is_writeable = smb_property_is_writable,
+	.init = smb_power_supply_init,
+};
+
+/* Init sequence derived from vendor downstream driver */
+static const struct smb_init_register smb5_init_seq[] = {
+	/*
+	 * TCPM owns the separate Type-C peripheral. This register only
+	 * controls how the charger's USBIN peripheral starts BC1.2 detection.
+	 */
+	{ .addr = USBIN_TYPE_C_CFG,
+	  .mask = APSD_START_ON_CC_BIT,
+	  .val = 0 },
+	/* Enable BC1.2 source detection without unsupported HVDCP negotiation. */
+	{ .addr = USBIN_OPTIONS_1_CFG,
+	  .mask = HVDCP_AUTH_ALG_EN_CFG_BIT |
+		  HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT | AUTO_SRC_DETECT_BIT |
+		  HVDCP_EN_BIT,
+	  .val = AUTO_SRC_DETECT_BIT },
+	/* Set the default SDP charger type to a 500ma USB 2.0 port */
+	{ .addr = USBIN_ICL_OPTIONS,
+	  .mask = USBIN_MODE_CHG_BIT,
+	  .val = USBIN_MODE_CHG_BIT },
+	{ .addr = CMD_ICL_OVERRIDE,
+	  .mask = ICL_OVERRIDE_BIT,
+	  .val = 0 },
+	{ .addr = USBIN_LOAD_CFG,
+	  .mask = ICL_OVERRIDE_AFTER_APSD_BIT,
+	  .val = 0 },
+	/* Disable watchdog */
+	{ .addr = SNARL_BARK_BITE_WD_CFG, .mask = 0xff, .val = 0 },
+	{ .addr = WD_CFG,
+	  .mask = WATCHDOG_TRIGGER_AFP_EN_BIT | WDOG_TIMER_EN_ON_PLUGIN_BIT |
+		  BARK_WDOG_INT_EN_BIT,
+	  .val = 0 },
+	/*
+	 * Enable Automatic Input Current Limit, this will slowly ramp up the current
+	 * When connected to a wall charger, and automatically stop when it detects
+	 * the charger current limit (voltage drop?) or it reaches the programmed limit.
+	 */
+	{ .addr = USBIN_AICL_OPTIONS_CFG,
+	  .mask = USBIN_AICL_PERIODIC_RERUN_EN_BIT | USBIN_AICL_ADC_EN_BIT
+			| USBIN_AICL_EN_BIT | SUSPEND_ON_COLLAPSE_USBIN_BIT,
+	  .val = USBIN_AICL_PERIODIC_RERUN_EN_BIT | USBIN_AICL_EN_BIT |
+		 SUSPEND_ON_COLLAPSE_USBIN_BIT },
+	{ .addr = AICL_RERUN_TIME_CFG,
+	  .mask = AICL_RERUN_TIME_MASK,
+	  .val = AICL_RERUN_TIME_12_SECS },
 };
 
 /* Init sequence derived from vendor downstream driver */
-static const struct smb_init_register smb_init_seq[] = {
-	{ .addr = AICL_RERUN_TIME_CFG, .mask = AICL_RERUN_TIME_MASK, .val = 0 },
+static const struct smb_init_register smb2_init_seq[] = {
+	{ .addr = AICL_RERUN_TIME_CFG,
+	  .mask = AICL_RERUN_TIME_MASK,
+	  .val = AICL_RERUN_TIME_3_SECS },
 	/*
 	 * By default configure us as an upstream facing port
 	 * FIXME: This will be handled by the type-c driver
 	 */
 	{ .addr = TYPE_C_INTRPT_ENB_SOFTWARE_CTRL,
-	  .mask = TYPEC_POWER_ROLE_CMD_MASK | VCONN_EN_SRC_BIT |
+	  .mask = TYPEC_POWER_ROLE_CMD_MASK | SMB2_VCONN_EN_SRC_BIT |
 		  VCONN_EN_VALUE_BIT,
-	  .val = VCONN_EN_SRC_BIT },
+	  .val = SMB2_VCONN_EN_SRC_BIT },
 	/*
 	 * Disable Type-C factory mode and stay in Attached.SRC state when VCONN
 	 * over-current happens
 	 */
-	{ .addr = TYPE_C_CFG,
+	{ .addr = USBIN_TYPE_C_CFG,
 	  .mask = FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT,
 	  .val = 0 },
 	/* Configure VBUS for software control */
-	{ .addr = OTG_CFG, .mask = OTG_EN_SRC_CFG_BIT, .val = 0 },
+	{ .addr = SMB2_OTG_CFG, .mask = SMB2_OTG_EN_SRC_CFG_BIT, .val = 0 },
 	/*
 	 * Use VBAT to determine the recharge threshold when battery is full
 	 * rather than the state of charge.
 	 */
-	{ .addr = FG_UPDATE_CFG_2_SEL,
-	  .mask = SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
-		  VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
-	  .val = VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT },
+	{ .addr = SMB2_FG_UPDATE_CFG_2_SEL,
+	  .mask = SMB2_SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
+		  SMB2_VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
+	  .val = SMB2_VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT },
 	/* Enable charging */
 	{ .addr = USBIN_OPTIONS_1_CFG, .mask = HVDCP_EN_BIT, .val = 0 },
 	{ .addr = CHARGING_ENABLE_CMD,
@@ -883,7 +1248,7 @@ static const struct smb_init_register smb_init_seq[] = {
 	 */
 	{ .addr = PRE_CHARGE_CURRENT_CFG,
 	  .mask = PRE_CHARGE_CURRENT_SETTING_MASK,
-	  .val = 500000 / CURRENT_SCALE_FACTOR },
+	  .val = 500000 / SMB2_CURRENT_STEP_UA },
 	/*
 	 * This overrides all of the current limit options exposed to userspace
 	 * and prevents the device from pulling more than ~1A. This is done
@@ -891,20 +1256,69 @@ static const struct smb_init_register smb_init_seq[] = {
 	 */
 	{ .addr = FAST_CHARGE_CURRENT_CFG,
 	  .mask = FAST_CHARGE_CURRENT_SETTING_MASK,
-	  .val = 1000000 / CURRENT_SCALE_FACTOR },
+	  .val = 1000000 / SMB2_CURRENT_STEP_UA },
+};
+
+static const struct smb_match_data pmi8998_match_data = {
+	.init_seq = smb2_init_seq,
+	.init_seq_len = ARRAY_SIZE(smb2_init_seq),
+	.name = "pmi8998",
+	.gen = SMB2,
+	.fv_min_uv = 3487500,
+	.fv_max_uv = 4920000,
+	.fv_step_uv = 7500,
+	.fcc_max_ua = 4500000,
+	.fcc_step_ua = 25000,
+	.icl_max_ua = 4800000,
+	.icl_step_ua = 25000,
+	.icl_status = SMB2_ICL_STATUS,
+	.usbin_current_scale = 1,
+};
+
+static const struct smb_match_data pm660_match_data = {
+	.init_seq = smb2_init_seq,
+	.init_seq_len = ARRAY_SIZE(smb2_init_seq),
+	.name = "pm660",
+	.gen = SMB2,
+	.fv_min_uv = 3487500,
+	.fv_max_uv = 4920000,
+	.fv_step_uv = 7500,
+	.fcc_max_ua = 4500000,
+	.fcc_step_ua = 25000,
+	.icl_max_ua = 4800000,
+	.icl_step_ua = 25000,
+	.icl_status = SMB2_ICL_STATUS,
+	.usbin_current_scale = 1,
 };
 
-static int smb_init_hw(struct smb_chip *chip)
+static const struct smb_match_data pm8150b_match_data = {
+	.init_seq = smb5_init_seq,
+	.init_seq_len = ARRAY_SIZE(smb5_init_seq),
+	.name = "pm8150b",
+	.gen = SMB5,
+	.fv_min_uv = 3600000,
+	.fv_max_uv = 4790000,
+	.fv_step_uv = 10000,
+	.fcc_max_ua = 8000000,
+	.fcc_step_ua = 50000,
+	.icl_max_ua = 5000000,
+	.icl_step_ua = 50000,
+	.icl_status = SMB5_AICL_ICL_STATUS,
+	.usbin_current_scale = 5,
+};
+
+static int smb_init_hw(struct smb_chip *chip,
+		       const struct smb_init_register *init_seq, size_t len)
 {
 	int rc, i;
 
-	for (i = 0; i < ARRAY_SIZE(smb_init_seq); i++) {
+	for (i = 0; i < len; i++) {
 		dev_dbg(chip->dev, "%d: Writing 0x%02x to 0x%02x\n", i,
-			smb_init_seq[i].val, smb_init_seq[i].addr);
+			init_seq[i].val, init_seq[i].addr);
 		rc = regmap_update_bits(chip->regmap,
-					chip->base + smb_init_seq[i].addr,
-					smb_init_seq[i].mask,
-					smb_init_seq[i].val);
+					chip->base + init_seq[i].addr,
+					init_seq[i].mask,
+					init_seq[i].val);
 		if (rc < 0)
 			return dev_err_probe(chip->dev, rc,
 					     "%s: init command %d failed\n",
@@ -914,19 +1328,6 @@ static int smb_init_hw(struct smb_chip *chip)
 	return 0;
 }
 
-static void smb_restore_charge_enable(void *data)
-{
-	struct smb_chip *chip = data;
-	int rc;
-
-	rc = regmap_update_bits(chip->regmap,
-				chip->base + CHARGING_ENABLE_CMD,
-				CHARGING_ENABLE_CMD_BIT,
-				chip->initial_charge_enable);
-	if (rc < 0)
-		dev_err(chip->dev, "Couldn't restore charging state: %d\n", rc);
-}
-
 static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name,
 			 irqreturn_t (*handler)(int irq, void *data))
 {
@@ -948,14 +1349,38 @@ static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name,
 	return 0;
 }
 
+static void smb_restore_charge_state(void *data)
+{
+	struct smb_chip *chip = data;
+	int rc;
+
+	rc = regmap_update_bits(chip->regmap,
+				chip->base + CHARGING_ENABLE_CMD,
+				CHARGING_ENABLE_CMD_BIT,
+				chip->initial_charge_enable);
+	if (rc < 0) {
+		dev_err(chip->dev, "could not restore charging state: %d\n", rc);
+		return;
+	}
+
+	if (chip->gen == SMB5) {
+		rc = regmap_update_bits(chip->regmap,
+					chip->base + USBIN_CMD_IL,
+					USBIN_SUSPEND_BIT,
+					chip->initial_usb_suspend);
+		if (rc < 0)
+			dev_err(chip->dev,
+				"could not restore USB input state: %d\n", rc);
+	}
+}
+
 static int smb_probe(struct platform_device *pdev)
 {
 	struct power_supply_config supply_config = {};
 	struct power_supply_desc *desc;
 	struct smb_chip *chip;
-	unsigned int charge_enable;
-	unsigned int float_voltage_sel;
-	int float_voltage_uv;
+	const struct smb_match_data *match_data;
+	unsigned int reg;
 	int rc, irq;
 
 	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
@@ -986,19 +1411,57 @@ static int smb_probe(struct platform_device *pdev)
 				     "Couldn't get usbin_i IIO channel\n");
 	}
 
-	rc = regmap_read(chip->regmap, chip->base + CHARGING_ENABLE_CMD,
-			 &charge_enable);
+	match_data = device_get_match_data(chip->dev);
+	if (!match_data)
+		return dev_err_probe(chip->dev, -ENODEV,
+				     "missing match data\n");
+
+	chip->gen = match_data->gen;
+	chip->match_data = match_data;
+	chip->icl_max_ua = match_data->icl_max_ua;
+	chip->icl_step_ua = match_data->icl_step_ua;
+	chip->icl_status = match_data->icl_status;
+	chip->usbin_current_scale = match_data->usbin_current_scale;
+
+	dev_info(chip->dev, "Generation %s\n", chip->gen == SMB2 ? "SMB2" : "SMB5");
+	if (chip->gen == SMB5) {
+		rc = regmap_read(chip->regmap, chip->base + USBIN_CMD_IL, &reg);
+		if (rc < 0)
+			return dev_err_probe(chip->dev, rc,
+					     "could not read USB input state\n");
+		chip->initial_usb_suspend = reg & USBIN_SUSPEND_BIT;
+	}
+
+	rc = regmap_read(chip->regmap,
+			 chip->base + CHARGING_ENABLE_CMD, &reg);
 	if (rc < 0)
 		return dev_err_probe(chip->dev, rc,
-				     "Couldn't read charging state\n");
+				     "could not read charging state\n");
+	chip->initial_charge_enable = reg & CHARGING_ENABLE_CMD_BIT;
 
-	chip->initial_charge_enable = charge_enable & CHARGING_ENABLE_CMD_BIT;
-	rc = devm_add_action_or_reset(chip->dev, smb_restore_charge_enable, chip);
-	if (rc)
-		return dev_err_probe(chip->dev, rc,
-				     "Couldn't register charging state rollback\n");
+	rc = devm_add_action_or_reset(chip->dev,
+				      smb_restore_charge_state, chip);
+	if (rc < 0)
+		return rc;
+
+	if (chip->gen == SMB5) {
+		/* Do not use bootloader charge limits while configuring SMB5. */
+		rc = regmap_update_bits(chip->regmap,
+					chip->base + USBIN_CMD_IL,
+					USBIN_SUSPEND_BIT, USBIN_SUSPEND_BIT);
+		if (rc < 0)
+			return dev_err_probe(chip->dev, rc,
+					     "could not suspend USB input\n");
 
-	rc = smb_init_hw(chip);
+		rc = regmap_update_bits(chip->regmap,
+					chip->base + CHARGING_ENABLE_CMD,
+					CHARGING_ENABLE_CMD_BIT, 0);
+		if (rc < 0)
+			return dev_err_probe(chip->dev, rc,
+					     "could not disable charging\n");
+	}
+
+	rc = smb_init_hw(chip, match_data->init_seq, match_data->init_seq_len);
 	if (rc < 0)
 		return rc;
 
@@ -1011,7 +1474,7 @@ static int smb_probe(struct platform_device *pdev)
 	memcpy(desc, &smb_psy_desc, sizeof(smb_psy_desc));
 	desc->name =
 		devm_kasprintf(chip->dev, GFP_KERNEL, "%s-charger",
-			       (const char *)device_get_match_data(chip->dev));
+			       match_data->name);
 	if (!desc->name)
 		return -ENOMEM;
 
@@ -1021,46 +1484,13 @@ static int smb_probe(struct platform_device *pdev)
 		return dev_err_probe(chip->dev, PTR_ERR(chip->chg_psy),
 				     "failed to register power supply\n");
 
-	rc = power_supply_get_battery_info(chip->chg_psy, &chip->batt_info);
-	if (rc)
-		return dev_err_probe(chip->dev, rc,
-				     "Failed to get battery info\n");
-
-	rc = devm_delayed_work_autocancel(chip->dev, &chip->status_change_work,
+	rc = devm_delayed_work_autocancel(chip->dev,
+					  &chip->status_change_work,
 					  smb_status_change_work);
 	if (rc)
 		return dev_err_probe(chip->dev, rc,
 				     "Failed to init status change work\n");
 
-	if (power_supply_battery_info_has_prop(chip->batt_info,
-					       POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX)) {
-		float_voltage_uv =
-			chip->batt_info->constant_charge_voltage_max_uv;
-	} else if (power_supply_battery_info_has_prop(chip->batt_info,
-					      POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN)) {
-		float_voltage_uv = chip->batt_info->voltage_max_design_uv;
-	} else {
-		dev_warn(chip->dev, "No battery float voltage; preserving hardware setting\n");
-		goto skip_float_voltage;
-	}
-
-	if (float_voltage_uv < SMB2_FLOAT_VOLTAGE_MIN_UV ||
-	    float_voltage_uv > SMB2_FLOAT_VOLTAGE_MAX_UV)
-		return dev_err_probe(chip->dev, -EINVAL,
-				     "float voltage %d uV outside %d-%d uV\n",
-				     float_voltage_uv,
-				     SMB2_FLOAT_VOLTAGE_MIN_UV,
-				     SMB2_FLOAT_VOLTAGE_MAX_UV);
-
-	float_voltage_sel =
-		(float_voltage_uv - SMB2_FLOAT_VOLTAGE_MIN_UV) /
-		SMB2_FLOAT_VOLTAGE_STEP_UV;
-	rc = regmap_update_bits(chip->regmap, chip->base + FLOAT_VOLTAGE_CFG,
-				FLOAT_VOLTAGE_SETTING_MASK, float_voltage_sel);
-	if (rc < 0)
-		return dev_err_probe(chip->dev, rc, "Couldn't set vbat max\n");
-
-skip_float_voltage:
 	rc = smb_init_irq(chip, &irq, "bat-ov", smb_handle_batt_overvoltage);
 	if (rc < 0)
 		return rc;
@@ -1084,7 +1514,7 @@ static int smb_probe(struct platform_device *pdev)
 	if (rc < 0)
 		return dev_err_probe(chip->dev, rc, "Couldn't set wake irq\n");
 
-	devm_remove_action(chip->dev, smb_restore_charge_enable, chip);
+	devm_remove_action(chip->dev, smb_restore_charge_state, chip);
 
 	platform_set_drvdata(pdev, chip);
 
@@ -1095,8 +1525,9 @@ static int smb_probe(struct platform_device *pdev)
 }
 
 static const struct of_device_id smb_match_id_table[] = {
-	{ .compatible = "qcom,pmi8998-charger", .data = "pmi8998" },
-	{ .compatible = "qcom,pm660-charger", .data = "pm660" },
+	{ .compatible = "qcom,pmi8998-charger", .data = &pmi8998_match_data },
+	{ .compatible = "qcom,pm660-charger", .data = &pm660_match_data },
+	{ .compatible = "qcom,pm8150b-charger", .data = &pm8150b_match_data },
 	{ /* sentinal */ }
 };
 MODULE_DEVICE_TABLE(of, smb_match_id_table);
@@ -1112,5 +1543,5 @@ static struct platform_driver qcom_spmi_smb = {
 module_platform_driver(qcom_spmi_smb);
 
 MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>");
-MODULE_DESCRIPTION("Qualcomm SMB2 Charger Driver");
+MODULE_DESCRIPTION("Qualcomm SMB2 and SMB5 Charger Driver");
 MODULE_LICENSE("GPL");

-- 
2.54.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v4 0/2] power: supply: qcom_smbx: add SMB5 support
  2026-08-20 10:03 [PATCH v4 0/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
  2026-08-20 10:03 ` [PATCH v4 1/2] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs Robin Snyders via B4 Relay
  2026-08-20 10:03 ` [PATCH v4 2/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
@ 2026-09-02 20:09 ` David Heidelberg
  2 siblings, 0 replies; 6+ messages in thread
From: David Heidelberg @ 2026-09-02 20:09 UTC (permalink / raw)
  To: robin, Casey Connolly, Sebastian Reichel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel,
	Dmitry Baryshkov, Konrad Dybcio, Krzysztof Kozlowski,
	Joel Selvaraj

On 20/08/2026 12:03, Robin Snyders via B4 Relay wrote:
> Add support for the SMB5 charger generation found in PM7250B and
> PM8150B to the existing qcom_smbx driver.
> 
> The series extends the binding and selects register layouts, charger
> status encodings and electrical limits from per-PMIC match data. Encode
> PM7250B as PM8150B-compatible in the binding and let the compatible
> fallback select the shared charge-parameter block, including the +0x08
> effective input-current status register. Convert the SMB5 0.2 V/A USB
> current-sense voltage measurement to microamps while leaving the SMB2
> current reading unchanged.
> 
> SMB5 setup suspends USB input and disables charging before configuration.
> The power-supply registration init callback programs battery limits and
> completes final input and charging setup before device_add publishes the
> properties, so public callbacks cannot observe partially initialized
> hardware. Managed rollback restores the charging state before the USB input
> state; if charging cannot be restored, USB input remains suspended. Managed
> teardown cancels status work before unregistering the power supply.
> 
> Enable periodic hardware AICL with the downstream twelve-second rerun
> interval while leaving ADC-based AICL disabled by default. Clear the
> unsupported HVDCP modes so firmware state cannot raise VBUS, and do not
> override the firmware recharge policy without a binding input. The
> dedicated TCPM and VBUS regulator drivers retain ownership of Type-C
> power-role and VBUS control.
> 
> On PM8150B, the SMB5 charger and VBUS regulator use the same DCDC
> peripheral but do not write the same registers. qcom_smbx only reads
> 0x1108 and 0x110b there; qcom_usb_vbus-regulator writes 0x1140, 0x1152
> and 0x1153. The qcom_smbx write to 0x1153 belongs only to the SMB2
> initialization sequence and is not executed for SMB5. The TCPM port and PD
> PHY use the separate 0x15xx and 0x17xx peripherals. Patch 2 names the
> USBIN BC1.2 integration register and SMB2-only OTG definitions explicitly
> to document this boundary.
> 
> Apply the five-patch qcom_smbx Fixes series before this one. This series is
> based on power-supply for-next (99b38cda3f4c). The b4 dependency metadata
> records all five prerequisite patch IDs. This ordering preserves the SMB2
> watchdog, health, overvoltage and float-voltage fixes across the SMB5
> driver refactoring. The prerequisite series is public at:
> https://lore.kernel.org/r/20260812-qcom-smbx-fixes-v1-0-eb48246be599@snyders.xyz/
> 
> The v3 implementation was tested on a OnePlus 7T Pro (HD1913) with
> PM8150B. Register reads confirmed 4.40 V, 1.50 A and 500 mA limits. A
> 180-second guarded charging trace and a subsequent 600-second runtime
> trace completed with health Good and the input current below its 500 mA
> limit. After a physical USB-C disconnect and reconnect, USB networking and
> the charger recovered automatically; a further 60-second trace remained
> Good and below the input-current limit. The final kernel log had no SMB5
> warning, error, oops or lockdep report.
> 
> The v4 driver was rebuilt and retested on the same device. A 180-second
> trace and a 600-second, 121-sample guarded trace completed with the charger
> online, health Good and no guard failure. With a powered USB-C dock, the
> phone remained the data host and PD power sink; with the dock unpowered, it
> remained the data host and enabled the VBUS regulator as the power source.
> The USB 2 and USB 3 hubs, storage and RTL8153 interface enumerated in both
> connector orientations, and 64 MiB read-only storage reads produced the
> same hash in all four dock configurations. Reconnecting the phone directly
> to the PC restored the power-sink/device roles, charging, NCM and SSH
> without a reboot. Ethernet traffic and visual DisplayPort output were not
> tested in this run because no Ethernet carrier or display was available.
> The final kernel log had no SMB5, Type-C or VBUS warning, error, oops or
> lockdep report.
> 
> The driver builds with LLVM W=1 and passes Sparse. The binding passes
> dt_binding_check, and both patches pass checkpatch --strict. A full LLVM
> Image, modules and DTBs integration build completed and booted on the test
> device.
> 
> Signed-off-by: Robin Snyders <robin@snyders.xyz>
> ---
> Changes in v4:
> - Make the PM8150B charger, VBUS regulator and TCPM register ownership
>    explicit, including the shared DCDC peripheral's disjoint accesses.
> - Rename the USBIN BC1.2 integration register and SMB2-only OTG definitions
>    to prevent them from being mistaken for SMB5 TCPM/VBUS controls.
> - Test powered sink/host and unpowered source/host USB-C dock operation in
>    both connector orientations, including storage I/O and gadget recovery.
> - Link to v3: https://lore.kernel.org/r/20260813-submit-qcom-smbx-send-v1-v3-0-27be0091d7c7@snyders.xyz
> 
> Changes in v3:
> - Complete SMB5 input and charging setup in the pre-device_add power-supply
>    registration init callback instead of gating setters on a driver-specific
>    probe flag.
> - Register the power supply and initialize managed status work before
>    requesting IRQs so handlers cannot observe an unassigned power supply.
> - Let PM7250B match the required PM8150B compatible fallback and remove its
>    duplicate match-data entry.
> - Collect the binding Reviewed-by tag.
> - Link to v2: https://lore.kernel.org/r/20260812-submit-qcom-smbx-send-v1-v2-0-f504b8f9bfad@snyders.xyz
> 
> Changes in v2:
> - Rebase onto power-supply for-next 99b38cda3f4c.
> - Encode PM7250B with the required PM8150B compatible fallback.
> - Leave SMB5 Type-C power-role and VBUS control to the TCPM and regulator
>    drivers.
> - Preserve the SMB2 3-second AICL interval and use the SMB5 downstream
>    12-second interval; leave ADC-based AICL disabled by default.
> - Disable unsupported HVDCP negotiation deterministically.
> - Do not override the firmware recharge policy without a binding input.
> - Model PM7250B with PM8150B electrical limits and the +0x08 AICL ICL
>    status register.
> - Convert the SMB5 0.2 V/A USB current-sense reading to microamps while
>    leaving the SMB2 current reading unscaled.
> - Program battery limits in the pre-device_add registration init callback
>    and gate setters until probe completes.
> - Order managed teardown so status work is cancelled before the power
>    supply is unregistered.
> - Restore charging before USB input on probe failure, and leave input
>    suspended if charging restoration fails.
> - Correct the SMB5 overvoltage register and charger-state decoding.
> - Avoid repeated error logs when polling a persistent overvoltage state.
> - Use battery-info property presence when selecting charge targets.
> - Remove duplicate scaling from the already-prescaled USB voltage reading
>    and clarify the binding IIO channel descriptions.
> - Update the binding title, Kconfig prompt and module description for both
>    charger generations.
> - Move the five pre-existing SMB2 fixes to a prerequisite Fixes series.
> - Link to v1: https://lore.kernel.org/r/20260811-submit-qcom-smbx-send-v1-v1-0-feec6cfa123a@snyders.xyz
> 
> ---
> Casey Connolly (2):
>        dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs
>        power: supply: qcom_smbx: add SMB5 support
> 
>   .../power/supply/qcom,pmi8998-charger.yaml         |  17 +-
>   drivers/power/supply/Kconfig                       |   8 +-
>   drivers/power/supply/qcom_smbx.c                   | 757 ++++++++++++++++-----
>   3 files changed, 609 insertions(+), 173 deletions(-)
> ---
> base-commit: 99b38cda3f4c486cfbc40f3c8ede1703594e9f13
> change-id: 20260811-submit-qcom-smbx-send-v1-69acefa61977
> prerequisite-patch-id: 3d8a9cf43b5e5eac64a60aeec5e9c90880bcf792
> prerequisite-patch-id: de3e3b494f9644596cfa4cafc362ca0b48288c45
> prerequisite-patch-id: d5e22a0fe7cd55374f60d7c2cb14ed3df8f94e0b
> prerequisite-patch-id: 9eafa41402e97d6a3e5369bec46df4e9db3d2af6
> prerequisite-patch-id: 5638b901cd10773fa9485605f84b25a1dd826a84
> 
> Best regards,

Hello Robin,

happy to see this series got picked up and also for additional fixes in the 
prerequisite series.

I'll add it into our integration sdm845-next branch and I would happy if you 
could Cc me and phone-devel@vger.kernel.org in follow up (in case this won't get 
merged right away! :) ).

Huge thanks!
David

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v4 2/2] power: supply: qcom_smbx: add SMB5 support
  2026-08-20 10:03 ` [PATCH v4 2/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
@ 2026-09-04 20:37   ` David Heidelberg
  2026-09-05  0:17   ` David Heidelberg
  1 sibling, 0 replies; 6+ messages in thread
From: David Heidelberg @ 2026-09-04 20:37 UTC (permalink / raw)
  To: robin, Casey Connolly, Sebastian Reichel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel,
	Dmitry Baryshkov, Konrad Dybcio, Joel Selvaraj

On 20/08/2026 12:03, Robin Snyders via B4 Relay wrote:
> From: Casey Connolly <casey.connolly@linaro.org>
> 
> Introduce support for the SMB5 charger found on PM7250B, PM8150B and
> related Qualcomm PMICs.
> 
> SMB5 uses different DCDC status offsets, charger-state encodings and
> electrical ranges. Select these from per-PMIC match data, with PM7250B
> using the PM8150B compatible fallback and parameter block. Read
> overvoltage from the SMB5 status bit, use the already-prescaled IIO
> voltage reading, and convert the SMB5 current-sense voltage to microamps.
> Use battery-info property presence when selecting voltage and current
> targets.
> 
> Keep Type-C power-role and VBUS control with the dedicated TCPM and
> regulator drivers. Clear the unsupported HVDCP negotiation modes so stale
> firmware settings cannot raise VBUS. Leave the firmware recharge policy
> unchanged and match the downstream default of ADC-based AICL disabled,
> while enabling periodic hardware AICL with its twelve-second SMB5 rerun
> interval. Preserve the existing three-second SMB2 interval.
> 
> PM8150B places the charger and VBUS regulator in the same DCDC peripheral,
> but the SMB5 path does not write the regulator registers. qcom_smbx reads
> 0x1108 and 0x110b, while qcom_usb_vbus-regulator writes 0x1140, 0x1152
> and 0x1153. The SMB2-only OTG configuration write to 0x1153 is not part
> of the SMB5 initialization sequence. The TCPM port and PD PHY use the
> separate 0x15xx and 0x17xx peripherals. Name the USBIN BC1.2 integration
> register and SMB2-only OTG definitions accordingly to make this ownership
> boundary explicit.
> 
> Program the battery limits and complete SMB5 input and charging setup
> from the power-supply registration init callback before device_add
> publishes the properties. This makes all public callbacks safe without
> driver-specific probe synchronization.
> 
> Suspend USB input and charging before SMB5 initialization. On a later
> failure, restore the original charging-enable state before the
> input-suspend state; leave the input suspended if charging cannot be
> restored. Cancel status work before unregistering the power supply during
> managed teardown. Update the Kconfig description to cover both charger
> generations.
> 
> On a OnePlus 7T Pro, register reads from the initial implementation
> confirmed the programmed 4.40 V, 1.50 A and 500 mA limits. A 180-second
> guarded charging trace and a subsequent 600-second runtime trace
> completed without crossing the voltage guard.
> 
> Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
> Co-developed-by: Joel Selvaraj <foss@joelselvaraj.com>
> Signed-off-by: Joel Selvaraj <foss@joelselvaraj.com>
> Co-developed-by: Robin Snyders <robin@snyders.xyz>
> Signed-off-by: Robin Snyders <robin@snyders.xyz>
> ---
>   drivers/power/supply/Kconfig     |   8 +-
>   drivers/power/supply/qcom_smbx.c | 757 ++++++++++++++++++++++++++++++---------
>   2 files changed, 598 insertions(+), 167 deletions(-)
> 

[...]

>   
> +/* Return 1 when in overvoltage state, else 0 or -errno */
> +static int smbx_ov_status(struct smb_chip *chip)
> +{
> +	u8 mask;
> +	int rc;
> +	u32 val;
> +
> +	switch (chip->gen) {
> +	case SMB2:
> +		mask = SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT;
> +		break;
> +	case SMB5:
> +		mask = SMB5_CHARGER_ERROR_STATUS_BAT_OV_BIT;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	rc = regmap_read(chip->regmap,
> +			 chip->base + BATTERY_CHARGER_STATUS_2, &val);
> +	if (rc)
> +		return rc;
> +
> +	return !!(val & mask);
> +}
> +
> +static int smb_map_charge_status(struct smb_chip *chip, u32 stat, int *val)

would it make sense to split into smb{2,5}_map_charge_status and pass as .data 
and call match_data->map_charge_status(chip, stat, val)?

> +{
> +	switch (chip->gen) {
> +	case SMB2:
> +		switch (stat) {
> +		case SMB2_TRICKLE_CHARGE:
> +		case SMB2_PRE_CHARGE:
> +		case SMB2_FAST_CHARGE:
> +		case SMB2_FULLON_CHARGE:
> +		case SMB2_TAPER_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_CHARGING;
> +			return 0;
> +		case SMB2_TERMINATE_CHARGE:
> +		case SMB2_INHIBIT_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_FULL;
> +			return 0;
> +		case SMB2_DISABLE_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
> +			return 0;
> +		}
> +		break;
> +	case SMB5:
> +		switch (stat) {
> +		case SMB5_TRICKLE_CHARGE:
> +		case SMB5_PRE_CHARGE:
> +		case SMB5_FULLON_CHARGE:
> +		case SMB5_TAPER_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_CHARGING;
> +			return 0;
> +		case SMB5_TERMINATE_CHARGE:
> +		case SMB5_INHIBIT_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_FULL;
> +			return 0;
> +		case SMB5_PAUSE_CHARGE:
> +		case SMB5_DISABLE_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
> +			return 0;
> +		}
> +		break;
> +	}
> +
> +	*val = POWER_SUPPLY_STATUS_UNKNOWN;
> +	return 0;
> +}
> +
>   static int smb_get_prop_status(struct smb_chip *chip, int *val)
>   {
> -	unsigned char stat[2];
> +	u32 stat;
>   	int usb_online = 0;
>   	int rc;
>   
> @@ -491,49 +619,36 @@ static int smb_get_prop_status(struct smb_chip *chip, int *val)
>   		return rc;
>   	}
>   
> -	rc = regmap_bulk_read(chip->regmap,
> -			      chip->base + BATTERY_CHARGER_STATUS_1, &stat, 2);
> +	rc = regmap_read(chip->regmap,
> +			 chip->base + BATTERY_CHARGER_STATUS_1, &stat);
>   	if (rc < 0) {
>   		dev_err(chip->dev, "Failed to read charging status ret=%d\n",
>   			rc);
>   		return rc;
>   	}
>   
> -	if (stat[1] & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
> +	rc = smbx_ov_status(chip);
> +	if (rc < 0)
> +		return rc;
> +
> +	/* In overvoltage state */
> +	if (rc == 1) {
>   		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
>   		return 0;
>   	}
>   
> -	stat[0] = stat[0] & BATTERY_CHARGER_STATUS_MASK;
> +	stat &= BATTERY_CHARGER_STATUS_MASK;
>   
> -	switch (stat[0]) {
> -	case TRICKLE_CHARGE:
> -	case PRE_CHARGE:
> -	case FAST_CHARGE:
> -	case FULLON_CHARGE:
> -	case TAPER_CHARGE:
> -		*val = POWER_SUPPLY_STATUS_CHARGING;
> -		return rc;
> -	case DISABLE_CHARGE:
> -		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
> -		return rc;
> -	case TERMINATE_CHARGE:
> -	case INHIBIT_CHARGE:
> -		*val = POWER_SUPPLY_STATUS_FULL;
> -		return rc;
> -	default:
> -		*val = POWER_SUPPLY_STATUS_UNKNOWN;
> -		return rc;
> -	}
> +	return smb_map_charge_status(chip, stat, val);
>   }
>   
>   static inline int smb_get_current_limit(struct smb_chip *chip,
>   					 unsigned int *val)
>   {
> -	int rc = regmap_read(chip->regmap, chip->base + ICL_STATUS, val);
> +	int rc = regmap_read(chip->regmap, chip->base + chip->icl_status, val);
>   
>   	if (rc >= 0)
> -		*val *= CURRENT_SCALE_FACTOR;
> +		*val *= chip->icl_step_ua;
>   	return rc;
>   }
>   
> @@ -541,12 +656,13 @@ static int smb_set_current_limit(struct smb_chip *chip, unsigned int val)
>   {
>   	unsigned char val_raw;
>   
> -	if (val > 4800000) {
> +	if (val > chip->icl_max_ua) {
>   		dev_err(chip->dev,
> -			"Can't set current limit higher than 4800000uA");
> +			"Can't set current limit higher than %uuA",
> +			chip->icl_max_ua);
>   		return -EINVAL;
>   	}
> -	val_raw = val / CURRENT_SCALE_FACTOR;
> +	val_raw = val / chip->icl_step_ua;
>   
>   	return regmap_write(chip->regmap, chip->base + USBIN_CURRENT_LIMIT_CFG,
>   			    val_raw);
> @@ -607,12 +723,10 @@ static void smb_status_change_work(struct work_struct *work)
>   static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan,
>   			     int *val)
>   {
> -	int rc;
> -	union power_supply_propval status;
> +	int rc, status;
>   
> -	rc = power_supply_get_property(chip->chg_psy, POWER_SUPPLY_PROP_STATUS,
> -				       &status);
> -	if (rc < 0 || status.intval != POWER_SUPPLY_STATUS_CHARGING) {
> +	rc = smb_get_prop_status(chip, &status);
> +	if (rc < 0 || status != POWER_SUPPLY_STATUS_CHARGING) {
>   		*val = 0;
>   		return 0;
>   	}
> @@ -625,7 +739,61 @@ static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan,
>   	return iio_read_channel_processed(chan, val);
>   }
>   
> -static int smb_get_prop_health(struct smb_chip *chip, int *val)
> +static int smb_get_prop_current_now(struct smb_chip *chip, int *val)
> +{
> +	s64 current_ua;
> +	int rc;
> +
> +	rc = smb_get_iio_chan(chip, chip->usb_in_i_chan, val);
> +	if (rc < 0)
> +		return rc;
> +
> +	current_ua = (s64)*val * chip->usbin_current_scale;
> +	if (current_ua < INT_MIN || current_ua > INT_MAX)
> +		return -ERANGE;
> +
> +	*val = (int)current_ua;
> +	return 0;
> +}
> +
> +static int smb5_get_prop_health(struct smb_chip *chip, int *val)
> +{
> +	int rc;
> +	unsigned int stat;
> +
> +	rc = smbx_ov_status(chip);
> +	if (rc < 0) {
> +		dev_err(chip->dev,
> +			"Couldn't determine overvoltage status: %d\n", rc);
> +		return rc;
> +	}
> +	if (rc) {
> +		*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
> +		return 0;
> +	}
> +
> +	rc = regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_7,
> +			 &stat);
> +	if (rc < 0) {
> +		dev_err(chip->dev, "Couldn't read charger status 7 rc=%d\n", rc);
> +		return rc;
> +	}
> +
> +	if (stat & SMB5_BAT_TEMP_STATUS_TOO_COLD_BIT)
> +		*val = POWER_SUPPLY_HEALTH_COLD;
> +	else if (stat & SMB5_BAT_TEMP_STATUS_TOO_HOT_BIT)
> +		*val = POWER_SUPPLY_HEALTH_OVERHEAT;
> +	else if (stat & SMB5_BAT_TEMP_STATUS_COLD_SOFT_BIT)
> +		*val = POWER_SUPPLY_HEALTH_COOL;
> +	else if (stat & SMB5_BAT_TEMP_STATUS_HOT_SOFT_BIT)
> +		*val = POWER_SUPPLY_HEALTH_WARM;
> +	else
> +		*val = POWER_SUPPLY_HEALTH_GOOD;
> +
> +	return 0;
> +}
> +
> +static int smb2_get_prop_health(struct smb_chip *chip, int *val)
>   {
>   	int rc;
>   	unsigned int stat;
> @@ -637,15 +805,15 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
>   		return rc;
>   	}
>   
> -	if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT)
> +	if (stat & SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT)
>   		*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
> -	else if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT)
> +	else if (stat & SMB2_BAT_TEMP_STATUS_TOO_COLD_BIT)
>   		*val = POWER_SUPPLY_HEALTH_COLD;
> -	else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT)
> +	else if (stat & SMB2_BAT_TEMP_STATUS_TOO_HOT_BIT)
>   		*val = POWER_SUPPLY_HEALTH_OVERHEAT;
> -	else if (stat & BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT)
> +	else if (stat & SMB2_BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT)
>   		*val = POWER_SUPPLY_HEALTH_COOL;
> -	else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
> +	else if (stat & SMB2_BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
>   		*val = POWER_SUPPLY_HEALTH_WARM;
>   	else
>   		*val = POWER_SUPPLY_HEALTH_GOOD;
> @@ -653,6 +821,19 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
>   	return 0;
>   }
>   
> +static int smb_get_prop_health(struct smb_chip *chip, int *val)

I would drop this function and just called
smb{2,5}_get_prop_health directly with

match_data->get_prop_health(chip, val)

> +{
> +	switch (chip->gen) {
> +	case SMB2:
> +		return smb2_get_prop_health(chip, val);
> +	case SMB5:
> +		return smb5_get_prop_health(chip, val);
> +	default:
> +		dev_err(chip->dev, "unsupported SMB chip generation\n");
> +		return -EINVAL;
> +	}
> +}
> +
>   static int smb_get_property(struct power_supply *psy,
>   			     enum power_supply_property psp,
>   			     union power_supply_propval *val)
> @@ -669,8 +850,7 @@ static int smb_get_property(struct power_supply *psy,
>   	case POWER_SUPPLY_PROP_CURRENT_MAX:
>   		return smb_get_current_limit(chip, &val->intval);
>   	case POWER_SUPPLY_PROP_CURRENT_NOW:
> -		return smb_get_iio_chan(chip, chip->usb_in_i_chan,
> -					 &val->intval);
> +		return smb_get_prop_current_now(chip, &val->intval);
>   	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
>   		return smb_get_iio_chan(chip, chip->usb_in_v_chan,
>   					 &val->intval);

[...]

> +static const struct smb_match_data pmi8998_match_data = {
> +	.init_seq = smb2_init_seq,
> +	.init_seq_len = ARRAY_SIZE(smb2_init_seq),
> +	.name = "pmi8998",
> +	.gen = SMB2,
> +	.fv_min_uv = 3487500,
> +	.fv_max_uv = 4920000,
> +	.fv_step_uv = 7500,
> +	.fcc_max_ua = 4500000,
> +	.fcc_step_ua = 25000,
> +	.icl_max_ua = 4800000,
> +	.icl_step_ua = 25000,
> +	.icl_status = SMB2_ICL_STATUS,
> +	.usbin_current_scale = 1,
> +};
> +
> +static const struct smb_match_data pm660_match_data = {
> +	.init_seq = smb2_init_seq,
> +	.init_seq_len = ARRAY_SIZE(smb2_init_seq),
> +	.name = "pm660",
> +	.gen = SMB2,
> +	.fv_min_uv = 3487500,
> +	.fv_max_uv = 4920000,
> +	.fv_step_uv = 7500,
> +	.fcc_max_ua = 4500000,
> +	.fcc_step_ua = 25000,
> +	.icl_max_ua = 4800000,
> +	.icl_step_ua = 25000,
> +	.icl_status = SMB2_ICL_STATUS,
> +	.usbin_current_scale = 1,
>   };

I don't see any difference between pmi8998 and pm660 (which applies for many 
parts between these chips usually). Maybe would be worth it just to ruse the 
pmi8998 for the pm660 and only set the name?

>   
> -static int smb_init_hw(struct smb_chip *chip)
> +static const struct smb_match_data pm8150b_match_data = {
> +	.init_seq = smb5_init_seq,
> +	.init_seq_len = ARRAY_SIZE(smb5_init_seq),
> +	.name = "pm8150b",
> +	.gen = SMB5,
> +	.fv_min_uv = 3600000,
> +	.fv_max_uv = 4790000,
> +	.fv_step_uv = 10000,
> +	.fcc_max_ua = 8000000,
> +	.fcc_step_ua = 50000,
> +	.icl_max_ua = 5000000,
> +	.icl_step_ua = 50000,
> +	.icl_status = SMB5_AICL_ICL_STATUS,
> +	.usbin_current_scale = 5,
> +};
> +
> +static int smb_init_hw(struct smb_chip *chip,
> +		       const struct smb_init_register *init_seq, size_t len)
>   {
>   	int rc, i;
>   
> -	for (i = 0; i < ARRAY_SIZE(smb_init_seq); i++) {
> +	for (i = 0; i < len; i++) {

since you touching this - C99 would be nice:
for (int i = ...

So far running these patches on Pixel 3 since yesterday, so far looks ok. I 
still need debug some crashes, but these usually happen regardless to charging, 
so likely not issue of this driver :))

David

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v4 2/2] power: supply: qcom_smbx: add SMB5 support
  2026-08-20 10:03 ` [PATCH v4 2/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
  2026-09-04 20:37   ` David Heidelberg
@ 2026-09-05  0:17   ` David Heidelberg
  1 sibling, 0 replies; 6+ messages in thread
From: David Heidelberg @ 2026-09-05  0:17 UTC (permalink / raw)
  To: robin, Casey Connolly, Sebastian Reichel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel,
	Dmitry Baryshkov, Konrad Dybcio, Joel Selvaraj

On 20/08/2026 12:03, Robin Snyders via B4 Relay wrote:
> From: Casey Connolly <casey.connolly@linaro.org>
> 
> Introduce support for the SMB5 charger found on PM7250B, PM8150B and
> related Qualcomm PMICs.
> 
> SMB5 uses different DCDC status offsets, charger-state encodings and
> electrical ranges. Select these from per-PMIC match data, with PM7250B
> using the PM8150B compatible fallback and parameter block. Read
> overvoltage from the SMB5 status bit, use the already-prescaled IIO
> voltage reading, and convert the SMB5 current-sense voltage to microamps.
> Use battery-info property presence when selecting voltage and current
> targets.
> 
> Keep Type-C power-role and VBUS control with the dedicated TCPM and
> regulator drivers. Clear the unsupported HVDCP negotiation modes so stale
> firmware settings cannot raise VBUS. Leave the firmware recharge policy
> unchanged and match the downstream default of ADC-based AICL disabled,
> while enabling periodic hardware AICL with its twelve-second SMB5 rerun
> interval. Preserve the existing three-second SMB2 interval.
> 
> PM8150B places the charger and VBUS regulator in the same DCDC peripheral,
> but the SMB5 path does not write the regulator registers. qcom_smbx reads
> 0x1108 and 0x110b, while qcom_usb_vbus-regulator writes 0x1140, 0x1152
> and 0x1153. The SMB2-only OTG configuration write to 0x1153 is not part
> of the SMB5 initialization sequence. The TCPM port and PD PHY use the
> separate 0x15xx and 0x17xx peripherals. Name the USBIN BC1.2 integration
> register and SMB2-only OTG definitions accordingly to make this ownership
> boundary explicit.
> 
> Program the battery limits and complete SMB5 input and charging setup
> from the power-supply registration init callback before device_add
> publishes the properties. This makes all public callbacks safe without
> driver-specific probe synchronization.
> 
> Suspend USB input and charging before SMB5 initialization. On a later
> failure, restore the original charging-enable state before the
> input-suspend state; leave the input suspended if charging cannot be
> restored. Cancel status work before unregistering the power supply during
> managed teardown. Update the Kconfig description to cover both charger
> generations.
> 
> On a OnePlus 7T Pro, register reads from the initial implementation
> confirmed the programmed 4.40 V, 1.50 A and 500 mA limits. A 180-second
> guarded charging trace and a subsequent 600-second runtime trace
> completed without crossing the voltage guard.
> 
> Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
> Co-developed-by: Joel Selvaraj <foss@joelselvaraj.com>
> Signed-off-by: Joel Selvaraj <foss@joelselvaraj.com>
> Co-developed-by: Robin Snyders <robin@snyders.xyz>
> Signed-off-by: Robin Snyders <robin@snyders.xyz>
> ---
>   drivers/power/supply/Kconfig     |   8 +-
>   drivers/power/supply/qcom_smbx.c | 757 ++++++++++++++++++++++++++++++---------
>   2 files changed, 598 insertions(+), 167 deletions(-)
> 
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index b89ef40df7f8..1159a7711919 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -1173,13 +1173,13 @@ config BATTERY_CHARGER_SURFACE_RT
>   	  will be called surface-rt-ec.
>   
>   config CHARGER_QCOM_SMB2
> -	tristate "Qualcomm PMI8998 PMIC charger driver"
> +	tristate "Qualcomm SMB2 and SMB5 charger driver"
>   	depends on MFD_SPMI_PMIC
>   	depends on IIO
>   	help
> -	  Say Y here to enable the Qualcomm PMIC Charger driver. This
> -	  adds support for the SMB2 switch mode battery charger found
> -	  in PMI8998 and related PMICs.
> +	  Say Y here to enable the Qualcomm PMIC charger driver. This adds
> +	  support for the SMB2 and SMB5 switch-mode battery chargers found
> +	  in PMI8998, PM660, PM7250B and PM8150B PMICs.
>   
>   config FUEL_GAUGE_MM8013
>   	tristate "Mitsumi MM8013 fuel gauge driver"
> diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
> index 71f5a4859412..bc2b1cd3ed68 100644
> --- a/drivers/power/supply/qcom_smbx.c
> +++ b/drivers/power/supply/qcom_smbx.c
> @@ -23,6 +23,13 @@
>   #include <linux/types.h>
>   #include <linux/workqueue.h>
>   
> +enum smb_generation {
> +	SMB2,
> +	SMB5,
> +};
> +
> +#define SMB_REG_OFFSET(smb) ((smb)->gen == SMB2 ? 0x600 : 0x100)
> +
>   /* clang-format off */
>   #define BATTERY_CHARGER_STATUS_1			0x06
>   #define BVR_INITIAL_RAMP_BIT				BIT(7)
> @@ -34,14 +41,20 @@
>   #define BATTERY_CHARGER_STATUS_2			0x07
>   #define INPUT_CURRENT_LIMITED_BIT			BIT(7)
>   #define CHARGER_ERROR_STATUS_SFT_EXPIRE_BIT		BIT(6)
> -#define CHARGER_ERROR_STATUS_BAT_OV_BIT			BIT(5)
> +#define SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT		BIT(5)
>   #define CHARGER_ERROR_STATUS_BAT_TERM_MISSING_BIT	BIT(4)
>   #define BAT_TEMP_STATUS_MASK				GENMASK(3, 0)
>   #define BAT_TEMP_STATUS_SOFT_LIMIT_MASK			GENMASK(3, 2)
> -#define BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT		BIT(3)
> -#define BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT		BIT(2)
> -#define BAT_TEMP_STATUS_TOO_HOT_BIT			BIT(1)
> -#define BAT_TEMP_STATUS_TOO_COLD_BIT			BIT(0)
> +#define SMB2_BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT		BIT(3)
> +#define SMB2_BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT	BIT(2)
> +#define SMB2_BAT_TEMP_STATUS_TOO_HOT_BIT			BIT(1)
> +#define SMB2_BAT_TEMP_STATUS_TOO_COLD_BIT		BIT(0)
> +
> +#define SMB5_CHARGER_ERROR_STATUS_BAT_OV_BIT		BIT(1)
> +#define SMB5_BAT_TEMP_STATUS_HOT_SOFT_BIT		BIT(5)
> +#define SMB5_BAT_TEMP_STATUS_COLD_SOFT_BIT		BIT(4)
> +#define SMB5_BAT_TEMP_STATUS_TOO_HOT_BIT			BIT(3)
> +#define SMB5_BAT_TEMP_STATUS_TOO_COLD_BIT		BIT(2)
>   
>   #define BATTERY_CHARGER_STATUS_4			0x0A
>   #define CHARGE_CURRENT_POST_JEITA_MASK			GENMASK(7, 0)
> @@ -78,10 +91,10 @@
>   #define FLOAT_VOLTAGE_CFG				0x70
>   #define FLOAT_VOLTAGE_SETTING_MASK			GENMASK(7, 0)
>   
> -#define FG_UPDATE_CFG_2_SEL				0x7D
> +#define SMB2_FG_UPDATE_CFG_2_SEL			0x7D
>   #define SOC_LT_OTG_THRESH_SEL_BIT			BIT(3)
> -#define SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT		BIT(2)
> -#define VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT		BIT(1)
> +#define SMB2_SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT		BIT(2)
> +#define SMB2_VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT		BIT(1)
>   #define IBT_LT_CHG_TERM_THRESH_SEL_BIT			BIT(0)
>   
>   #define JEITA_EN_CFG					0x90
> @@ -101,13 +114,13 @@
>   #define USBIN_LT_3P6V_RT_STS_BIT			BIT(1)
>   #define USBIN_COLLAPSE_RT_STS_BIT			BIT(0)
>   
> -#define OTG_CFG						0x153
> +#define SMB2_OTG_CFG					0x153
>   #define OTG_RESERVED_MASK				GENMASK(7, 6)
>   #define DIS_OTG_ON_TLIM_BIT				BIT(5)
>   #define QUICKSTART_OTG_FASTROLESWAP_BIT			BIT(4)
>   #define INCREASE_DFP_TIME_BIT				BIT(3)
>   #define ENABLE_OTG_IN_DEBUG_MODE_BIT			BIT(2)
> -#define OTG_EN_SRC_CFG_BIT				BIT(1)
> +#define SMB2_OTG_EN_SRC_CFG_BIT				BIT(1)
>   #define CONCURRENT_MODE_CFG_BIT				BIT(0)
>   
>   #define OTG_ENG_OTG_CFG					0x1C0
> @@ -189,10 +202,12 @@
>   #define TYPEC_TRYSINK_DETECT_STATUS_BIT			BIT(0)
>   
>   #define CMD_APSD					0x341
> -#define ICL_OVERRIDE_BIT				BIT(1)
>   #define APSD_RERUN_BIT					BIT(0)
>   
> -#define TYPE_C_CFG					0x358
> +#define CMD_ICL_OVERRIDE				0x342
> +#define ICL_OVERRIDE_BIT				BIT(0)
> +
> +#define USBIN_TYPE_C_CFG				0x358
>   #define APSD_START_ON_CC_BIT				BIT(7)
>   #define WAIT_FOR_APSD_BIT				BIT(6)
>   #define FACTORY_MODE_DETECTION_EN_BIT			BIT(5)
> @@ -260,7 +275,7 @@
>   #define EXIT_SNK_BASED_ON_CC_BIT			BIT(7)
>   #define VCONN_EN_ORIENTATION_BIT			BIT(6)
>   #define TYPEC_VCONN_OVERCURR_INT_EN_BIT			BIT(5)
> -#define VCONN_EN_SRC_BIT				BIT(4)
> +#define SMB2_VCONN_EN_SRC_BIT				BIT(4)
>   #define VCONN_EN_VALUE_BIT				BIT(3)
>   #define TYPEC_POWER_ROLE_CMD_MASK			GENMASK(2, 0)
>   #define UFP_EN_CMD_BIT					BIT(2)
> @@ -274,7 +289,7 @@
>   #define SUSPEND_ON_COLLAPSE_USBIN_BIT			BIT(7)
>   #define USBIN_AICL_HDC_EN_BIT				BIT(6)
>   #define USBIN_AICL_START_AT_MAX_BIT			BIT(5)
> -#define USBIN_AICL_RERUN_EN_BIT				BIT(4)
> +#define USBIN_AICL_PERIODIC_RERUN_EN_BIT		BIT(4)
>   #define USBIN_AICL_ADC_EN_BIT				BIT(3)
>   #define USBIN_AICL_EN_BIT				BIT(2)
>   #define USBIN_HV_COLLAPSE_RESPONSE_BIT			BIT(1)
> @@ -303,10 +318,12 @@
>   #define AICL_SWITCH_ENABLE_BIT				BIT(1)
>   #define ZIN_ICL_ENABLE_BIT				BIT(0)
>   
> -#define ICL_STATUS					0x607
>   #define INPUT_CURRENT_LIMIT_MASK			GENMASK(7, 0)
>   
> -#define POWER_PATH_STATUS				0x60B
> +#define SMB2_ICL_STATUS					0x607
> +#define SMB5_AICL_ICL_STATUS				0x108
> +
> +#define POWER_PATH_STATUS(smb)				(SMB_REG_OFFSET(smb) + 0x0B)
>   #define P_PATH_INPUT_SS_DONE_BIT			BIT(7)
>   #define P_PATH_USBIN_SUSPEND_STS_BIT			BIT(6)
>   #define P_PATH_DCIN_SUSPEND_STS_BIT			BIT(5)
> @@ -335,6 +352,8 @@
>   
>   #define AICL_RERUN_TIME_CFG				0x661
>   #define AICL_RERUN_TIME_MASK				GENMASK(1, 0)
> +#define AICL_RERUN_TIME_3_SECS				0
> +#define AICL_RERUN_TIME_12_SECS				1
>   
>   #define STAT_CFG					0x690
>   #define STAT_SW_OVERRIDE_VALUE_BIT			BIT(7)
> @@ -350,22 +369,30 @@
>   #define DCP_CURRENT_UA					1500000
>   #define CURRENT_MAX_UA					DCP_CURRENT_UA
>   
> -/* pmi8998 registers represent current in increments of 1/40th of an amp */
> -#define CURRENT_SCALE_FACTOR				25000
> -#define SMB2_FLOAT_VOLTAGE_MIN_UV			3487500
> -#define SMB2_FLOAT_VOLTAGE_MAX_UV			4920000
> -#define SMB2_FLOAT_VOLTAGE_STEP_UV			7500
> +/* PMI8998 registers represent current in increments of 1/40th of an amp. */
> +#define SMB2_CURRENT_STEP_UA				25000
>   /* clang-format on */
>   
> -enum charger_status {
> -	TRICKLE_CHARGE = 0,
> -	PRE_CHARGE,
> -	FAST_CHARGE,
> -	FULLON_CHARGE,
> -	TAPER_CHARGE,
> -	TERMINATE_CHARGE,
> -	INHIBIT_CHARGE,
> -	DISABLE_CHARGE,
> +enum smb2_charger_status {
> +	SMB2_TRICKLE_CHARGE = 0,
> +	SMB2_PRE_CHARGE,
> +	SMB2_FAST_CHARGE,
> +	SMB2_FULLON_CHARGE,
> +	SMB2_TAPER_CHARGE,
> +	SMB2_TERMINATE_CHARGE,
> +	SMB2_INHIBIT_CHARGE,
> +	SMB2_DISABLE_CHARGE,
> +};
> +
> +enum smb5_charger_status {
> +	SMB5_INHIBIT_CHARGE = 0,
> +	SMB5_TRICKLE_CHARGE,
> +	SMB5_PRE_CHARGE,
> +	SMB5_FULLON_CHARGE,
> +	SMB5_TAPER_CHARGE,
> +	SMB5_TERMINATE_CHARGE,
> +	SMB5_PAUSE_CHARGE,
> +	SMB5_DISABLE_CHARGE,
>   };
>   
>   struct smb_init_register {
> @@ -374,13 +401,21 @@ struct smb_init_register {
>   	u8 val;
>   };
>   
> +struct smb_match_data;
> +
>   /**
>    * struct smb_chip - smb chip structure
>    * @dev:		Device reference for power_supply
>    * @name:		The platform device name
>    * @base:		Base address for smb registers
>    * @regmap:		Register map
> - * @batt_info:		Battery data from DT
> + * @match_data:		Per-PMIC hardware parameters
> + * @gen:		Charger hardware generation
> + * @icl_max_ua:		Maximum programmable USB input current
> + * @icl_step_ua:	USB input current register step
> + * @icl_status:		Effective input current status register offset
> + * @usbin_current_scale: USB input current-sense scale
> + * @initial_usb_suspend:	USB input suspend state before SMB5 setup
>    * @initial_charge_enable: Charging enable state before hardware setup
>    * @status_change_work: Worker to handle plug/unplug events
>    * @cable_irq:		USB plugin IRQ
> @@ -394,7 +429,13 @@ struct smb_chip {
>   	const char *name;
>   	unsigned int base;
>   	struct regmap *regmap;
> -	struct power_supply_battery_info *batt_info;
> +	const struct smb_match_data *match_data;
> +	enum smb_generation gen;
> +	unsigned int icl_max_ua;
> +	unsigned int icl_step_ua;
> +	u16 icl_status;
> +	unsigned int usbin_current_scale;
> +	u8 initial_usb_suspend;
>   	u8 initial_charge_enable;
>   
>   	struct delayed_work status_change_work;
> @@ -407,6 +448,22 @@ struct smb_chip {
>   	struct power_supply *chg_psy;
>   };
>   
> +struct smb_match_data {
> +	const char *name;
> +	enum smb_generation gen;
> +	size_t init_seq_len;
> +	unsigned int fv_min_uv;
> +	unsigned int fv_max_uv;
> +	unsigned int fv_step_uv;
> +	unsigned int fcc_max_ua;
> +	unsigned int fcc_step_ua;
> +	unsigned int icl_max_ua;
> +	unsigned int icl_step_ua;
> +	u16 icl_status;
> +	unsigned int usbin_current_scale;
> +	const struct smb_init_register *init_seq;
> +};
> +
>   static enum power_supply_property smb_properties[] = {
>   	POWER_SUPPLY_PROP_MANUFACTURER,
>   	POWER_SUPPLY_PROP_MODEL_NAME,
> @@ -424,7 +481,7 @@ static int smb_get_prop_usb_online(struct smb_chip *chip, int *val)
>   	unsigned int stat;
>   	int rc;
>   
> -	rc = regmap_read(chip->regmap, chip->base + POWER_PATH_STATUS, &stat);
> +	rc = regmap_read(chip->regmap, chip->base + POWER_PATH_STATUS(chip), &stat);
>   	if (rc < 0) {
>   		dev_err(chip->dev, "Couldn't read power path status: %d\n", rc);
>   		return rc;
> @@ -479,9 +536,80 @@ static int smb_apsd_get_charger_type(struct smb_chip *chip, int *val)
>   	return 0;
>   }
>   
> +/* Return 1 when in overvoltage state, else 0 or -errno */
> +static int smbx_ov_status(struct smb_chip *chip)
> +{
> +	u8 mask;
> +	int rc;
> +	u32 val;
> +
> +	switch (chip->gen) {
> +	case SMB2:
> +		mask = SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT;
> +		break;
> +	case SMB5:
> +		mask = SMB5_CHARGER_ERROR_STATUS_BAT_OV_BIT;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	rc = regmap_read(chip->regmap,
> +			 chip->base + BATTERY_CHARGER_STATUS_2, &val);
> +	if (rc)
> +		return rc;
> +
> +	return !!(val & mask);
> +}
> +
> +static int smb_map_charge_status(struct smb_chip *chip, u32 stat, int *val)
> +{
> +	switch (chip->gen) {
> +	case SMB2:
> +		switch (stat) {
> +		case SMB2_TRICKLE_CHARGE:
> +		case SMB2_PRE_CHARGE:
> +		case SMB2_FAST_CHARGE:
> +		case SMB2_FULLON_CHARGE:
> +		case SMB2_TAPER_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_CHARGING;
> +			return 0;
> +		case SMB2_TERMINATE_CHARGE:
> +		case SMB2_INHIBIT_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_FULL;
> +			return 0;
> +		case SMB2_DISABLE_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
> +			return 0;
> +		}
> +		break;
> +	case SMB5:
> +		switch (stat) {
> +		case SMB5_TRICKLE_CHARGE:
> +		case SMB5_PRE_CHARGE:
> +		case SMB5_FULLON_CHARGE:
> +		case SMB5_TAPER_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_CHARGING;
> +			return 0;
> +		case SMB5_TERMINATE_CHARGE:
> +		case SMB5_INHIBIT_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_FULL;
> +			return 0;
> +		case SMB5_PAUSE_CHARGE:
> +		case SMB5_DISABLE_CHARGE:
> +			*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
> +			return 0;
> +		}
> +		break;
> +	}
> +
> +	*val = POWER_SUPPLY_STATUS_UNKNOWN;
> +	return 0;
> +}
> +
>   static int smb_get_prop_status(struct smb_chip *chip, int *val)
>   {
> -	unsigned char stat[2];
> +	u32 stat;
>   	int usb_online = 0;
>   	int rc;
>   
> @@ -491,49 +619,36 @@ static int smb_get_prop_status(struct smb_chip *chip, int *val)
>   		return rc;
>   	}
>   
> -	rc = regmap_bulk_read(chip->regmap,
> -			      chip->base + BATTERY_CHARGER_STATUS_1, &stat, 2);
> +	rc = regmap_read(chip->regmap,
> +			 chip->base + BATTERY_CHARGER_STATUS_1, &stat);
>   	if (rc < 0) {
>   		dev_err(chip->dev, "Failed to read charging status ret=%d\n",
>   			rc);
>   		return rc;
>   	}
>   
> -	if (stat[1] & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
> +	rc = smbx_ov_status(chip);
> +	if (rc < 0)
> +		return rc;
> +
> +	/* In overvoltage state */
> +	if (rc == 1) {
>   		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
>   		return 0;
>   	}
>   
> -	stat[0] = stat[0] & BATTERY_CHARGER_STATUS_MASK;
> +	stat &= BATTERY_CHARGER_STATUS_MASK;
>   
> -	switch (stat[0]) {
> -	case TRICKLE_CHARGE:
> -	case PRE_CHARGE:
> -	case FAST_CHARGE:
> -	case FULLON_CHARGE:
> -	case TAPER_CHARGE:
> -		*val = POWER_SUPPLY_STATUS_CHARGING;
> -		return rc;
> -	case DISABLE_CHARGE:
> -		*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
> -		return rc;
> -	case TERMINATE_CHARGE:
> -	case INHIBIT_CHARGE:
> -		*val = POWER_SUPPLY_STATUS_FULL;
> -		return rc;
> -	default:
> -		*val = POWER_SUPPLY_STATUS_UNKNOWN;
> -		return rc;
> -	}
> +	return smb_map_charge_status(chip, stat, val);
>   }
>   
>   static inline int smb_get_current_limit(struct smb_chip *chip,
>   					 unsigned int *val)
>   {
> -	int rc = regmap_read(chip->regmap, chip->base + ICL_STATUS, val);
> +	int rc = regmap_read(chip->regmap, chip->base + chip->icl_status, val);
>   
>   	if (rc >= 0)
> -		*val *= CURRENT_SCALE_FACTOR;
> +		*val *= chip->icl_step_ua;
>   	return rc;
>   }
>   
> @@ -541,12 +656,13 @@ static int smb_set_current_limit(struct smb_chip *chip, unsigned int val)
>   {
>   	unsigned char val_raw;
>   
> -	if (val > 4800000) {
> +	if (val > chip->icl_max_ua) {
>   		dev_err(chip->dev,
> -			"Can't set current limit higher than 4800000uA");
> +			"Can't set current limit higher than %uuA",
> +			chip->icl_max_ua);
>   		return -EINVAL;
>   	}
> -	val_raw = val / CURRENT_SCALE_FACTOR;
> +	val_raw = val / chip->icl_step_ua;
>   
>   	return regmap_write(chip->regmap, chip->base + USBIN_CURRENT_LIMIT_CFG,
>   			    val_raw);
> @@ -607,12 +723,10 @@ static void smb_status_change_work(struct work_struct *work)
>   static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan,
>   			     int *val)
>   {
> -	int rc;
> -	union power_supply_propval status;
> +	int rc, status;
>   
> -	rc = power_supply_get_property(chip->chg_psy, POWER_SUPPLY_PROP_STATUS,
> -				       &status);
> -	if (rc < 0 || status.intval != POWER_SUPPLY_STATUS_CHARGING) {
> +	rc = smb_get_prop_status(chip, &status);
> +	if (rc < 0 || status != POWER_SUPPLY_STATUS_CHARGING) {
>   		*val = 0;
>   		return 0;
>   	}
> @@ -625,7 +739,61 @@ static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan,
>   	return iio_read_channel_processed(chan, val);
>   }
>   
> -static int smb_get_prop_health(struct smb_chip *chip, int *val)
> +static int smb_get_prop_current_now(struct smb_chip *chip, int *val)
> +{
> +	s64 current_ua;
> +	int rc;
> +
> +	rc = smb_get_iio_chan(chip, chip->usb_in_i_chan, val);
> +	if (rc < 0)
> +		return rc;
> +
> +	current_ua = (s64)*val * chip->usbin_current_scale;
> +	if (current_ua < INT_MIN || current_ua > INT_MAX)
> +		return -ERANGE;
> +
> +	*val = (int)current_ua;
> +	return 0;
> +}
> +
> +static int smb5_get_prop_health(struct smb_chip *chip, int *val)
> +{
> +	int rc;
> +	unsigned int stat;
> +
> +	rc = smbx_ov_status(chip);
> +	if (rc < 0) {
> +		dev_err(chip->dev,
> +			"Couldn't determine overvoltage status: %d\n", rc);
> +		return rc;
> +	}
> +	if (rc) {
> +		*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
> +		return 0;
> +	}
> +
> +	rc = regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_7,
> +			 &stat);
> +	if (rc < 0) {
> +		dev_err(chip->dev, "Couldn't read charger status 7 rc=%d\n", rc);
> +		return rc;
> +	}
> +
> +	if (stat & SMB5_BAT_TEMP_STATUS_TOO_COLD_BIT)
> +		*val = POWER_SUPPLY_HEALTH_COLD;
> +	else if (stat & SMB5_BAT_TEMP_STATUS_TOO_HOT_BIT)
> +		*val = POWER_SUPPLY_HEALTH_OVERHEAT;
> +	else if (stat & SMB5_BAT_TEMP_STATUS_COLD_SOFT_BIT)
> +		*val = POWER_SUPPLY_HEALTH_COOL;
> +	else if (stat & SMB5_BAT_TEMP_STATUS_HOT_SOFT_BIT)
> +		*val = POWER_SUPPLY_HEALTH_WARM;
> +	else
> +		*val = POWER_SUPPLY_HEALTH_GOOD;
> +
> +	return 0;
> +}
> +
> +static int smb2_get_prop_health(struct smb_chip *chip, int *val)
>   {
>   	int rc;
>   	unsigned int stat;
> @@ -637,15 +805,15 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
>   		return rc;
>   	}
>   
> -	if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT)
> +	if (stat & SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT)
>   		*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
> -	else if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT)
> +	else if (stat & SMB2_BAT_TEMP_STATUS_TOO_COLD_BIT)
>   		*val = POWER_SUPPLY_HEALTH_COLD;
> -	else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT)
> +	else if (stat & SMB2_BAT_TEMP_STATUS_TOO_HOT_BIT)
>   		*val = POWER_SUPPLY_HEALTH_OVERHEAT;
> -	else if (stat & BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT)
> +	else if (stat & SMB2_BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT)
>   		*val = POWER_SUPPLY_HEALTH_COOL;
> -	else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
> +	else if (stat & SMB2_BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
>   		*val = POWER_SUPPLY_HEALTH_WARM;
>   	else
>   		*val = POWER_SUPPLY_HEALTH_GOOD;
> @@ -653,6 +821,19 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
>   	return 0;
>   }
>   
> +static int smb_get_prop_health(struct smb_chip *chip, int *val)
> +{
> +	switch (chip->gen) {
> +	case SMB2:
> +		return smb2_get_prop_health(chip, val);
> +	case SMB5:
> +		return smb5_get_prop_health(chip, val);
> +	default:
> +		dev_err(chip->dev, "unsupported SMB chip generation\n");
> +		return -EINVAL;
> +	}
> +}
> +
>   static int smb_get_property(struct power_supply *psy,
>   			     enum power_supply_property psp,
>   			     union power_supply_propval *val)
> @@ -669,8 +850,7 @@ static int smb_get_property(struct power_supply *psy,
>   	case POWER_SUPPLY_PROP_CURRENT_MAX:
>   		return smb_get_current_limit(chip, &val->intval);
>   	case POWER_SUPPLY_PROP_CURRENT_NOW:
> -		return smb_get_iio_chan(chip, chip->usb_in_i_chan,
> -					 &val->intval);
> +		return smb_get_prop_current_now(chip, &val->intval);
>   	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
>   		return smb_get_iio_chan(chip, chip->usb_in_v_chan,
>   					 &val->intval);
> @@ -721,17 +901,15 @@ static int smb_property_is_writable(struct power_supply *psy,
>   static irqreturn_t smb_handle_batt_overvoltage(int irq, void *data)
>   {
>   	struct smb_chip *chip = data;
> -	unsigned int status;
>   	int rc;
>   
> -	rc = regmap_read(chip->regmap,
> -			 chip->base + BATTERY_CHARGER_STATUS_2, &status);
> +	rc = smbx_ov_status(chip);
>   	if (rc < 0) {
> -		dev_err(chip->dev, "Couldn't read charger status: %d\n", rc);
> +		dev_err(chip->dev, "could not read overvoltage status: %d\n", rc);
>   		return IRQ_HANDLED;
>   	}
>   
> -	if (status & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
> +	if (rc) {
>   		/* The hardware stops charging automatically */
>   		dev_err(chip->dev, "battery overvoltage detected\n");
>   	}
> @@ -777,6 +955,144 @@ static irqreturn_t smb_handle_wdog_bark(int irq, void *data)
>   	return IRQ_HANDLED;
>   }
>   
> +static bool smb_batt_info_has_prop(struct power_supply_battery_info *batt_info,
> +				   enum power_supply_property prop)
> +{
> +	return power_supply_battery_info_has_prop(batt_info, prop);
> +}
> +
> +static int smb_power_supply_init(struct power_supply *psy)
> +{
> +	struct smb_chip *chip = power_supply_get_drvdata(psy);
> +	const struct smb_match_data *match_data = chip->match_data;
> +	struct power_supply_battery_info *batt_info;
> +	unsigned int fast_charge_current_sel;
> +	unsigned int float_voltage_sel;
> +	bool program_float_voltage = true;
> +	int fast_charge_current_ua;
> +	int float_voltage_uv;
> +	int rc;
> +
> +	rc = power_supply_get_battery_info(psy, &batt_info);
> +	if (rc)
> +		return dev_err_probe(chip->dev, rc,
> +				     "Failed to get battery info\n");
> +
> +	if (smb_batt_info_has_prop(batt_info,
> +				   POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX)) {
> +		float_voltage_uv = batt_info->constant_charge_voltage_max_uv;
> +	} else if (smb_batt_info_has_prop(batt_info,
> +					  POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN)) {
> +		float_voltage_uv = batt_info->voltage_max_design_uv;
> +	} else {
> +		dev_warn(chip->dev,
> +			 "No battery float voltage; preserving hardware setting\n");
> +		program_float_voltage = false;
> +	}
> +
> +	if (program_float_voltage) {
> +		if (float_voltage_uv < match_data->fv_min_uv ||
> +		    float_voltage_uv > match_data->fv_max_uv) {
> +			rc = dev_err_probe(chip->dev, -EINVAL,
> +					   "float voltage %d uV outside %u-%u uV\n",
> +					   float_voltage_uv,
> +					   match_data->fv_min_uv,
> +					   match_data->fv_max_uv);
> +			goto out_put_batt_info;
> +		}
> +
> +		float_voltage_sel =
> +			(float_voltage_uv - match_data->fv_min_uv) /
> +			match_data->fv_step_uv;
> +		rc = regmap_update_bits(chip->regmap,
> +					chip->base + FLOAT_VOLTAGE_CFG,
> +					FLOAT_VOLTAGE_SETTING_MASK,
> +					float_voltage_sel);
> +		if (rc < 0) {
> +			rc = dev_err_probe(chip->dev, rc,
> +					   "could not set float voltage\n");
> +			goto out_put_batt_info;
> +		}
> +	}
> +
> +	if (chip->gen == SMB5) {
> +		if (smb_batt_info_has_prop(batt_info,
> +					   POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX))
> +			fast_charge_current_ua =
> +				batt_info->constant_charge_current_max_ua;
> +		else
> +			fast_charge_current_ua = DCP_CURRENT_UA;
> +
> +		if (fast_charge_current_ua > match_data->fcc_max_ua) {
> +			rc = dev_err_probe(chip->dev, -EINVAL,
> +					   "fast charge current %d uA exceeds %u uA\n",
> +					   fast_charge_current_ua,
> +					   match_data->fcc_max_ua);
> +			goto out_put_batt_info;
> +		}
> +
> +		fast_charge_current_sel = fast_charge_current_ua /
> +			match_data->fcc_step_ua;
> +		rc = regmap_update_bits(chip->regmap,
> +					chip->base + FAST_CHARGE_CURRENT_CFG,
> +					FAST_CHARGE_CURRENT_SETTING_MASK,
> +					fast_charge_current_sel);
> +		if (rc < 0) {
> +			rc = dev_err_probe(chip->dev, rc,
> +					   "could not set fast charge current\n");
> +			goto out_put_batt_info;
> +		}
> +
> +		rc = smb_set_current_limit(chip, SDP_CURRENT_UA);
> +		if (rc < 0) {
> +			rc = dev_err_probe(chip->dev, rc,
> +					   "could not set USB input current\n");
> +			goto out_put_batt_info;
> +		}
> +
> +		if (program_float_voltage)
> +			dev_info(chip->dev,
> +				 "charge limits: float=%u uV fast=%u uA input=%u uA\n",
> +				 match_data->fv_min_uv +
> +				 float_voltage_sel * match_data->fv_step_uv,
> +				 fast_charge_current_sel * match_data->fcc_step_ua,
> +				 SDP_CURRENT_UA);
> +		else
> +			dev_info(chip->dev,
> +				 "charge limits: float=firmware fast=%u uA input=%u uA\n",
> +				 fast_charge_current_sel * match_data->fcc_step_ua,
> +				 SDP_CURRENT_UA);
> +	}
> +
> +	rc = 0;
> +
> +out_put_batt_info:
> +	power_supply_put_battery_info(psy, batt_info);
> +
> +	if (rc)
> +		return rc;
> +
> +	if (chip->gen != SMB5)
> +		return 0;
> +
> +	rc = regmap_update_bits(chip->regmap,
> +				chip->base + CHARGING_ENABLE_CMD,
> +				CHARGING_ENABLE_CMD_BIT,
> +				CHARGING_ENABLE_CMD_BIT);
> +	if (rc < 0)
> +		return dev_err_probe(chip->dev, rc,
> +				     "could not enable charging\n");
> +
> +	rc = regmap_update_bits(chip->regmap,
> +				chip->base + USBIN_CMD_IL,
> +				USBIN_SUSPEND_BIT, 0);
> +	if (rc < 0)
> +		return dev_err_probe(chip->dev, rc,
> +				     "could not enable USB input\n");
> +
> +	return 0;
> +}
> +
>   static const struct power_supply_desc smb_psy_desc = {
>   	.name = "pmi8998_charger",
>   	.type = POWER_SUPPLY_TYPE_USB,
> @@ -789,36 +1105,85 @@ static const struct power_supply_desc smb_psy_desc = {
>   	.get_property = smb_get_property,
>   	.set_property = smb_set_property,
>   	.property_is_writeable = smb_property_is_writable,
> +	.init = smb_power_supply_init,
> +};
> +
> +/* Init sequence derived from vendor downstream driver */
> +static const struct smb_init_register smb5_init_seq[] = {
> +	/*
> +	 * TCPM owns the separate Type-C peripheral. This register only
> +	 * controls how the charger's USBIN peripheral starts BC1.2 detection.
> +	 */
> +	{ .addr = USBIN_TYPE_C_CFG,
> +	  .mask = APSD_START_ON_CC_BIT,
> +	  .val = 0 },
> +	/* Enable BC1.2 source detection without unsupported HVDCP negotiation. */
> +	{ .addr = USBIN_OPTIONS_1_CFG,
> +	  .mask = HVDCP_AUTH_ALG_EN_CFG_BIT |
> +		  HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT | AUTO_SRC_DETECT_BIT |
> +		  HVDCP_EN_BIT,
> +	  .val = AUTO_SRC_DETECT_BIT },
> +	/* Set the default SDP charger type to a 500ma USB 2.0 port */
> +	{ .addr = USBIN_ICL_OPTIONS,
> +	  .mask = USBIN_MODE_CHG_BIT,
> +	  .val = USBIN_MODE_CHG_BIT },
> +	{ .addr = CMD_ICL_OVERRIDE,
> +	  .mask = ICL_OVERRIDE_BIT,
> +	  .val = 0 },
> +	{ .addr = USBIN_LOAD_CFG,
> +	  .mask = ICL_OVERRIDE_AFTER_APSD_BIT,
> +	  .val = 0 },
> +	/* Disable watchdog */
> +	{ .addr = SNARL_BARK_BITE_WD_CFG, .mask = 0xff, .val = 0 },
> +	{ .addr = WD_CFG,
> +	  .mask = WATCHDOG_TRIGGER_AFP_EN_BIT | WDOG_TIMER_EN_ON_PLUGIN_BIT |
> +		  BARK_WDOG_INT_EN_BIT,
> +	  .val = 0 },
> +	/*
> +	 * Enable Automatic Input Current Limit, this will slowly ramp up the current
> +	 * When connected to a wall charger, and automatically stop when it detects
> +	 * the charger current limit (voltage drop?) or it reaches the programmed limit.
> +	 */
> +	{ .addr = USBIN_AICL_OPTIONS_CFG,
> +	  .mask = USBIN_AICL_PERIODIC_RERUN_EN_BIT | USBIN_AICL_ADC_EN_BIT
> +			| USBIN_AICL_EN_BIT | SUSPEND_ON_COLLAPSE_USBIN_BIT,
> +	  .val = USBIN_AICL_PERIODIC_RERUN_EN_BIT | USBIN_AICL_EN_BIT |
> +		 SUSPEND_ON_COLLAPSE_USBIN_BIT },
> +	{ .addr = AICL_RERUN_TIME_CFG,
> +	  .mask = AICL_RERUN_TIME_MASK,
> +	  .val = AICL_RERUN_TIME_12_SECS },
>   };
>   
>   /* Init sequence derived from vendor downstream driver */
> -static const struct smb_init_register smb_init_seq[] = {
> -	{ .addr = AICL_RERUN_TIME_CFG, .mask = AICL_RERUN_TIME_MASK, .val = 0 },
> +static const struct smb_init_register smb2_init_seq[] = {
> +	{ .addr = AICL_RERUN_TIME_CFG,
> +	  .mask = AICL_RERUN_TIME_MASK,
> +	  .val = AICL_RERUN_TIME_3_SECS },
>   	/*
>   	 * By default configure us as an upstream facing port
>   	 * FIXME: This will be handled by the type-c driver
>   	 */
>   	{ .addr = TYPE_C_INTRPT_ENB_SOFTWARE_CTRL,
> -	  .mask = TYPEC_POWER_ROLE_CMD_MASK | VCONN_EN_SRC_BIT |
> +	  .mask = TYPEC_POWER_ROLE_CMD_MASK | SMB2_VCONN_EN_SRC_BIT |
>   		  VCONN_EN_VALUE_BIT,
> -	  .val = VCONN_EN_SRC_BIT },
> +	  .val = SMB2_VCONN_EN_SRC_BIT },
>   	/*
>   	 * Disable Type-C factory mode and stay in Attached.SRC state when VCONN
>   	 * over-current happens
>   	 */
> -	{ .addr = TYPE_C_CFG,
> +	{ .addr = USBIN_TYPE_C_CFG,
>   	  .mask = FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT,
>   	  .val = 0 },
>   	/* Configure VBUS for software control */
> -	{ .addr = OTG_CFG, .mask = OTG_EN_SRC_CFG_BIT, .val = 0 },
> +	{ .addr = SMB2_OTG_CFG, .mask = SMB2_OTG_EN_SRC_CFG_BIT, .val = 0 },
>   	/*
>   	 * Use VBAT to determine the recharge threshold when battery is full
>   	 * rather than the state of charge.
>   	 */
> -	{ .addr = FG_UPDATE_CFG_2_SEL,
> -	  .mask = SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
> -		  VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
> -	  .val = VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT },
> +	{ .addr = SMB2_FG_UPDATE_CFG_2_SEL,
> +	  .mask = SMB2_SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
> +		  SMB2_VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
> +	  .val = SMB2_VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT },
>   	/* Enable charging */
>   	{ .addr = USBIN_OPTIONS_1_CFG, .mask = HVDCP_EN_BIT, .val = 0 },
>   	{ .addr = CHARGING_ENABLE_CMD,
> @@ -883,7 +1248,7 @@ static const struct smb_init_register smb_init_seq[] = {
>   	 */
>   	{ .addr = PRE_CHARGE_CURRENT_CFG,
>   	  .mask = PRE_CHARGE_CURRENT_SETTING_MASK,
> -	  .val = 500000 / CURRENT_SCALE_FACTOR },
> +	  .val = 500000 / SMB2_CURRENT_STEP_UA },
>   	/*
>   	 * This overrides all of the current limit options exposed to userspace
>   	 * and prevents the device from pulling more than ~1A. This is done
> @@ -891,20 +1256,69 @@ static const struct smb_init_register smb_init_seq[] = {
>   	 */
>   	{ .addr = FAST_CHARGE_CURRENT_CFG,
>   	  .mask = FAST_CHARGE_CURRENT_SETTING_MASK,
> -	  .val = 1000000 / CURRENT_SCALE_FACTOR },
> +	  .val = 1000000 / SMB2_CURRENT_STEP_UA },
> +};
> +
> +static const struct smb_match_data pmi8998_match_data = {
> +	.init_seq = smb2_init_seq,
> +	.init_seq_len = ARRAY_SIZE(smb2_init_seq),
> +	.name = "pmi8998",
> +	.gen = SMB2,
> +	.fv_min_uv = 3487500,
> +	.fv_max_uv = 4920000,
> +	.fv_step_uv = 7500,
> +	.fcc_max_ua = 4500000,
> +	.fcc_step_ua = 25000,
> +	.icl_max_ua = 4800000,
> +	.icl_step_ua = 25000,
> +	.icl_status = SMB2_ICL_STATUS,
> +	.usbin_current_scale = 1,
> +};
> +
> +static const struct smb_match_data pm660_match_data = {
> +	.init_seq = smb2_init_seq,
> +	.init_seq_len = ARRAY_SIZE(smb2_init_seq),
> +	.name = "pm660",
> +	.gen = SMB2,
> +	.fv_min_uv = 3487500,
> +	.fv_max_uv = 4920000,
> +	.fv_step_uv = 7500,
> +	.fcc_max_ua = 4500000,
> +	.fcc_step_ua = 25000,
> +	.icl_max_ua = 4800000,
> +	.icl_step_ua = 25000,
> +	.icl_status = SMB2_ICL_STATUS,
> +	.usbin_current_scale = 1,
>   };
>   
> -static int smb_init_hw(struct smb_chip *chip)
> +static const struct smb_match_data pm8150b_match_data = {
> +	.init_seq = smb5_init_seq,
> +	.init_seq_len = ARRAY_SIZE(smb5_init_seq),
> +	.name = "pm8150b",
> +	.gen = SMB5,
> +	.fv_min_uv = 3600000,
> +	.fv_max_uv = 4790000,
> +	.fv_step_uv = 10000,
> +	.fcc_max_ua = 8000000,
> +	.fcc_step_ua = 50000,
> +	.icl_max_ua = 5000000,
> +	.icl_step_ua = 50000,
> +	.icl_status = SMB5_AICL_ICL_STATUS,
> +	.usbin_current_scale = 5,
> +};
> +
> +static int smb_init_hw(struct smb_chip *chip,
> +		       const struct smb_init_register *init_seq, size_t len)
>   {
>   	int rc, i;
>   
> -	for (i = 0; i < ARRAY_SIZE(smb_init_seq); i++) {
> +	for (i = 0; i < len; i++) {
>   		dev_dbg(chip->dev, "%d: Writing 0x%02x to 0x%02x\n", i,
> -			smb_init_seq[i].val, smb_init_seq[i].addr);
> +			init_seq[i].val, init_seq[i].addr);
>   		rc = regmap_update_bits(chip->regmap,
> -					chip->base + smb_init_seq[i].addr,
> -					smb_init_seq[i].mask,
> -					smb_init_seq[i].val);
> +					chip->base + init_seq[i].addr,
> +					init_seq[i].mask,
> +					init_seq[i].val);
>   		if (rc < 0)
>   			return dev_err_probe(chip->dev, rc,
>   					     "%s: init command %d failed\n",
> @@ -914,19 +1328,6 @@ static int smb_init_hw(struct smb_chip *chip)
>   	return 0;
>   }
>   
> -static void smb_restore_charge_enable(void *data)
> -{
> -	struct smb_chip *chip = data;
> -	int rc;
> -
> -	rc = regmap_update_bits(chip->regmap,
> -				chip->base + CHARGING_ENABLE_CMD,
> -				CHARGING_ENABLE_CMD_BIT,
> -				chip->initial_charge_enable);
> -	if (rc < 0)
> -		dev_err(chip->dev, "Couldn't restore charging state: %d\n", rc);
> -}
> -
>   static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name,
>   			 irqreturn_t (*handler)(int irq, void *data))
>   {
> @@ -948,14 +1349,38 @@ static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name,
>   	return 0;
>   }
>   
> +static void smb_restore_charge_state(void *data)
> +{
> +	struct smb_chip *chip = data;
> +	int rc;
> +
> +	rc = regmap_update_bits(chip->regmap,
> +				chip->base + CHARGING_ENABLE_CMD,
> +				CHARGING_ENABLE_CMD_BIT,
> +				chip->initial_charge_enable);
> +	if (rc < 0) {
> +		dev_err(chip->dev, "could not restore charging state: %d\n", rc);
> +		return;
> +	}
> +
> +	if (chip->gen == SMB5) {
> +		rc = regmap_update_bits(chip->regmap,
> +					chip->base + USBIN_CMD_IL,
> +					USBIN_SUSPEND_BIT,
> +					chip->initial_usb_suspend);
> +		if (rc < 0)
> +			dev_err(chip->dev,
> +				"could not restore USB input state: %d\n", rc);
> +	}
> +}
> +
>   static int smb_probe(struct platform_device *pdev)
>   {
>   	struct power_supply_config supply_config = {};
>   	struct power_supply_desc *desc;
>   	struct smb_chip *chip;
> -	unsigned int charge_enable;
> -	unsigned int float_voltage_sel;
> -	int float_voltage_uv;
> +	const struct smb_match_data *match_data;
> +	unsigned int reg;
>   	int rc, irq;
>   
>   	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
> @@ -986,19 +1411,57 @@ static int smb_probe(struct platform_device *pdev)
>   				     "Couldn't get usbin_i IIO channel\n");
>   	}
>   
> -	rc = regmap_read(chip->regmap, chip->base + CHARGING_ENABLE_CMD,
> -			 &charge_enable);
> +	match_data = device_get_match_data(chip->dev);
> +	if (!match_data)
> +		return dev_err_probe(chip->dev, -ENODEV,
> +				     "missing match data\n");
> +
> +	chip->gen = match_data->gen;
> +	chip->match_data = match_data;
> +	chip->icl_max_ua = match_data->icl_max_ua;
> +	chip->icl_step_ua = match_data->icl_step_ua;
> +	chip->icl_status = match_data->icl_status;

except mapping the match_data to the chip->match_data, this seems to be a bit 
duplication of existing structure.

I would just do chip->data->gen (I would likely call `match_data` just `data`) 
to access the values.

David

> +	chip->usbin_current_scale = match_data->usbin_current_scale;
> +
> +	dev_info(chip->dev, "Generation %s\n", chip->gen == SMB2 ? "SMB2" : "SMB5");
> +	if (chip->gen == SMB5) {
> +		rc = regmap_read(chip->regmap, chip->base + USBIN_CMD_IL, &reg);
> +		if (rc < 0)
> +			return dev_err_probe(chip->dev, rc,
> +					     "could not read USB input state\n");
> +		chip->initial_usb_suspend = reg & USBIN_SUSPEND_BIT;
> +	}
> +
> +	rc = regmap_read(chip->regmap,
> +			 chip->base + CHARGING_ENABLE_CMD, &reg);
>   	if (rc < 0)
>   		return dev_err_probe(chip->dev, rc,
> -				     "Couldn't read charging state\n");
> +				     "could not read charging state\n");
> +	chip->initial_charge_enable = reg & CHARGING_ENABLE_CMD_BIT;
>   
> -	chip->initial_charge_enable = charge_enable & CHARGING_ENABLE_CMD_BIT;
> -	rc = devm_add_action_or_reset(chip->dev, smb_restore_charge_enable, chip);
> -	if (rc)
> -		return dev_err_probe(chip->dev, rc,
> -				     "Couldn't register charging state rollback\n");
> +	rc = devm_add_action_or_reset(chip->dev,
> +				      smb_restore_charge_state, chip);
> +	if (rc < 0)
> +		return rc;
> +
> +	if (chip->gen == SMB5) {
> +		/* Do not use bootloader charge limits while configuring SMB5. */
> +		rc = regmap_update_bits(chip->regmap,
> +					chip->base + USBIN_CMD_IL,
> +					USBIN_SUSPEND_BIT, USBIN_SUSPEND_BIT);
> +		if (rc < 0)
> +			return dev_err_probe(chip->dev, rc,
> +					     "could not suspend USB input\n");
>   
> -	rc = smb_init_hw(chip);
> +		rc = regmap_update_bits(chip->regmap,
> +					chip->base + CHARGING_ENABLE_CMD,
> +					CHARGING_ENABLE_CMD_BIT, 0);
> +		if (rc < 0)
> +			return dev_err_probe(chip->dev, rc,
> +					     "could not disable charging\n");
> +	}
> +
> +	rc = smb_init_hw(chip, match_data->init_seq, match_data->init_seq_len);
>   	if (rc < 0)
>   		return rc;
>   
> @@ -1011,7 +1474,7 @@ static int smb_probe(struct platform_device *pdev)
>   	memcpy(desc, &smb_psy_desc, sizeof(smb_psy_desc));
>   	desc->name =
>   		devm_kasprintf(chip->dev, GFP_KERNEL, "%s-charger",
> -			       (const char *)device_get_match_data(chip->dev));
> +			       match_data->name);
>   	if (!desc->name)
>   		return -ENOMEM;
>   
> @@ -1021,46 +1484,13 @@ static int smb_probe(struct platform_device *pdev)
>   		return dev_err_probe(chip->dev, PTR_ERR(chip->chg_psy),
>   				     "failed to register power supply\n");
>   
> -	rc = power_supply_get_battery_info(chip->chg_psy, &chip->batt_info);
> -	if (rc)
> -		return dev_err_probe(chip->dev, rc,
> -				     "Failed to get battery info\n");
> -
> -	rc = devm_delayed_work_autocancel(chip->dev, &chip->status_change_work,
> +	rc = devm_delayed_work_autocancel(chip->dev,
> +					  &chip->status_change_work,
>   					  smb_status_change_work);
>   	if (rc)
>   		return dev_err_probe(chip->dev, rc,
>   				     "Failed to init status change work\n");
>   
> -	if (power_supply_battery_info_has_prop(chip->batt_info,
> -					       POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX)) {
> -		float_voltage_uv =
> -			chip->batt_info->constant_charge_voltage_max_uv;
> -	} else if (power_supply_battery_info_has_prop(chip->batt_info,
> -					      POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN)) {
> -		float_voltage_uv = chip->batt_info->voltage_max_design_uv;
> -	} else {
> -		dev_warn(chip->dev, "No battery float voltage; preserving hardware setting\n");
> -		goto skip_float_voltage;
> -	}
> -
> -	if (float_voltage_uv < SMB2_FLOAT_VOLTAGE_MIN_UV ||
> -	    float_voltage_uv > SMB2_FLOAT_VOLTAGE_MAX_UV)
> -		return dev_err_probe(chip->dev, -EINVAL,
> -				     "float voltage %d uV outside %d-%d uV\n",
> -				     float_voltage_uv,
> -				     SMB2_FLOAT_VOLTAGE_MIN_UV,
> -				     SMB2_FLOAT_VOLTAGE_MAX_UV);
> -
> -	float_voltage_sel =
> -		(float_voltage_uv - SMB2_FLOAT_VOLTAGE_MIN_UV) /
> -		SMB2_FLOAT_VOLTAGE_STEP_UV;
> -	rc = regmap_update_bits(chip->regmap, chip->base + FLOAT_VOLTAGE_CFG,
> -				FLOAT_VOLTAGE_SETTING_MASK, float_voltage_sel);
> -	if (rc < 0)
> -		return dev_err_probe(chip->dev, rc, "Couldn't set vbat max\n");
> -
> -skip_float_voltage:
>   	rc = smb_init_irq(chip, &irq, "bat-ov", smb_handle_batt_overvoltage);
>   	if (rc < 0)
>   		return rc;
> @@ -1084,7 +1514,7 @@ static int smb_probe(struct platform_device *pdev)
>   	if (rc < 0)
>   		return dev_err_probe(chip->dev, rc, "Couldn't set wake irq\n");
>   
> -	devm_remove_action(chip->dev, smb_restore_charge_enable, chip);
> +	devm_remove_action(chip->dev, smb_restore_charge_state, chip);
>   
>   	platform_set_drvdata(pdev, chip);
>   
> @@ -1095,8 +1525,9 @@ static int smb_probe(struct platform_device *pdev)
>   }
>   
>   static const struct of_device_id smb_match_id_table[] = {
> -	{ .compatible = "qcom,pmi8998-charger", .data = "pmi8998" },
> -	{ .compatible = "qcom,pm660-charger", .data = "pm660" },
> +	{ .compatible = "qcom,pmi8998-charger", .data = &pmi8998_match_data },
> +	{ .compatible = "qcom,pm660-charger", .data = &pm660_match_data },
> +	{ .compatible = "qcom,pm8150b-charger", .data = &pm8150b_match_data },
>   	{ /* sentinal */ }
>   };
>   MODULE_DEVICE_TABLE(of, smb_match_id_table);
> @@ -1112,5 +1543,5 @@ static struct platform_driver qcom_spmi_smb = {
>   module_platform_driver(qcom_spmi_smb);
>   
>   MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>");
> -MODULE_DESCRIPTION("Qualcomm SMB2 Charger Driver");
> +MODULE_DESCRIPTION("Qualcomm SMB2 and SMB5 Charger Driver");
>   MODULE_LICENSE("GPL");
> 


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-09-05  0:17 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-20 10:03 [PATCH v4 0/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
2026-08-20 10:03 ` [PATCH v4 1/2] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs Robin Snyders via B4 Relay
2026-08-20 10:03 ` [PATCH v4 2/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
2026-09-04 20:37   ` David Heidelberg
2026-09-05  0:17   ` David Heidelberg
2026-09-02 20:09 ` [PATCH v4 0/2] " David Heidelberg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox