* [PATCH 0/3] power: supply: qcom_smbx: add SMB5 support
@ 2026-08-11 20:35 ` Robin Snyders
0 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-11 20:35 UTC (permalink / raw)
To: Casey Connolly, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Robin Snyders,
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, selects register layouts and electrical
limits from per-PMIC match data, initializes SMB5 with charging and USB
input suspended, and programs a stable AICL rerun interval.
This 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
without crossing the voltage guard.
The driver was built with LLVM and the binding passed dt_binding_check.
All three patches pass checkpatch --strict.
Based on linux-next 20260810 (3d08ff75a47a).
Signed-off-by: Robin Snyders <robin@snyders.xyz>
---
Casey Connolly (3):
dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs
power: supply: qcom_smbx: add SMB5 support
power: supply: qcom_smbx: program AICL rerun time
.../power/supply/qcom,pmi8998-charger.yaml | 2 +
drivers/power/supply/qcom_smbx.c | 521 ++++++++++++++++++---
2 files changed, 457 insertions(+), 66 deletions(-)
---
base-commit: 3d08ff75a47a3e7e2ab45a3bcab6723b4d906422
change-id: 20260811-submit-qcom-smbx-send-v1-69acefa61977
Best regards,
--
Robin Snyders <robin@snyders.xyz>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 0/3] power: supply: qcom_smbx: add SMB5 support
@ 2026-08-11 20:35 ` Robin Snyders
0 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders @ 2026-08-11 20:35 UTC (permalink / raw)
To: Casey Connolly, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Robin Snyders,
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, selects register layouts and electrical
limits from per-PMIC match data, initializes SMB5 with charging and USB
input suspended, and programs a stable AICL rerun interval.
This 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
without crossing the voltage guard.
The driver was built with LLVM and the binding passed dt_binding_check.
All three patches pass checkpatch --strict.
Based on linux-next 20260810 (3d08ff75a47a).
Signed-off-by: Robin Snyders <robin@snyders.xyz>
---
Casey Connolly (3):
dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs
power: supply: qcom_smbx: add SMB5 support
power: supply: qcom_smbx: program AICL rerun time
.../power/supply/qcom,pmi8998-charger.yaml | 2 +
drivers/power/supply/qcom_smbx.c | 521 ++++++++++++++++++---
2 files changed, 457 insertions(+), 66 deletions(-)
---
base-commit: 3d08ff75a47a3e7e2ab45a3bcab6723b4d906422
change-id: 20260811-submit-qcom-smbx-send-v1-69acefa61977
Best regards,
--
Robin Snyders <robin@snyders.xyz>
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 1/3] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs
2026-08-11 20:35 ` Robin Snyders
@ 2026-08-11 20:35 ` Robin Snyders
-1 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-11 20:35 UTC (permalink / raw)
To: Casey Connolly, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Robin Snyders
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.
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Signed-off-by: Robin Snyders <robin@snyders.xyz>
---
.../devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml b/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
index 70f5cd6eaeab..9e200ba3f3a8 100644
--- a/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
+++ b/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
@@ -12,6 +12,8 @@ maintainers:
properties:
compatible:
enum:
+ - qcom,pm7250b-charger
+ - qcom,pm8150b-charger
- qcom,pmi8998-charger
- qcom,pm660-charger
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH 1/3] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs
@ 2026-08-11 20:35 ` Robin Snyders
0 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders @ 2026-08-11 20:35 UTC (permalink / raw)
To: Casey Connolly, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Robin Snyders
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.
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Signed-off-by: Robin Snyders <robin@snyders.xyz>
---
.../devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml b/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
index 70f5cd6eaeab..9e200ba3f3a8 100644
--- a/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
+++ b/Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
@@ -12,6 +12,8 @@ maintainers:
properties:
compatible:
enum:
+ - qcom,pm7250b-charger
+ - qcom,pm8150b-charger
- qcom,pmi8998-charger
- qcom,pm660-charger
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH 2/3] power: supply: qcom_smbx: add SMB5 support
2026-08-11 20:35 ` Robin Snyders
@ 2026-08-11 20:35 ` Robin Snyders
-1 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-11 20:35 UTC (permalink / raw)
To: Casey Connolly, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Robin Snyders,
Joel Selvaraj
From: Casey Connolly <casey.connolly@linaro.org>
Introduce support for the SMB5 charger found on PM8150B and other
modern Qualcomm PMICs.
SMB5 is largely similar to SMB2, with differences in the register
layout, Type-C block and charger status fields. Select the register
layout and the voltage, fast-charge-current and input-current ranges
from per-PMIC match data.
Suspend USB input and charging before initialization, program the
battery limits and interrupts, and only then enable charging and input.
Start PM8150B with conservative 4.40 V, 1.50 A and 500 mA limits.
Also read overvoltage state from the returned register value rather than
the register address.
On a OnePlus 7T Pro, register reads confirmed the programmed 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/qcom_smbx.c | 512 ++++++++++++++++++++++++++++++++++-----
1 file changed, 446 insertions(+), 66 deletions(-)
diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
index 67fdb4335338..09d9e0f95cb5 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,12 +91,15 @@
#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 SMB5_CHARGE_RCHG_SOC_THRESHOLD_CFG_REG 0x7D
+#define SMB5_CHARGE_RCHG_SOC_THRESHOLD_CFG_MASK GENMASK(7, 0)
+
#define JEITA_EN_CFG 0x90
#define JEITA_EN_HARDLIMIT_BIT BIT(4)
#define JEITA_EN_HOT_SL_FCV_BIT BIT(3)
@@ -189,9 +205,11 @@
#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 CMD_ICL_OVERRIDE 0x342
+#define ICL_OVERRIDE_BIT BIT(0)
+
#define TYPE_C_CFG 0x358
#define APSD_START_ON_CC_BIT BIT(7)
#define WAIT_FOR_APSD_BIT BIT(6)
@@ -260,7 +278,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 +292,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 +321,10 @@
#define AICL_SWITCH_ENABLE_BIT BIT(1)
#define ZIN_ICL_ENABLE_BIT BIT(0)
-#define ICL_STATUS 0x607
+#define ICL_STATUS(smb) (SMB_REG_OFFSET(smb) + 0x07)
#define INPUT_CURRENT_LIMIT_MASK GENMASK(7, 0)
-#define POWER_PATH_STATUS 0x60B
+#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)
@@ -315,6 +333,27 @@
#define P_PATH_POWER_PATH_MASK GENMASK(2, 1)
#define P_PATH_VALID_INPUT_POWER_SOURCE_STS_BIT BIT(0)
+/* 0x5xx region is available on the SMB5 Type-C block. */
+#define SMB5_TYPE_C_MODE_CFG 0x544
+#define SMB5_EN_TRY_SNK_BIT BIT(4)
+#define SMB5_EN_SNK_ONLY_BIT BIT(1)
+
+#define SMB5_TYPEC_TYPE_C_VCONN_CONTROL 0x546
+#define SMB5_VCONN_EN_ORIENTATION_BIT BIT(2)
+#define SMB5_VCONN_EN_VALUE_BIT BIT(1)
+#define SMB5_VCONN_EN_SRC_BIT BIT(0)
+
+#define SMB5_TYPE_C_DEBUG_ACCESS_SINK 0x54a
+#define SMB5_TYPEC_DEBUG_ACCESS_SINK_MASK GENMASK(4, 0)
+
+#define SMB5_DEBUG_ACCESS_SRC_CFG 0x54c
+#define SMB5_EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT BIT(0)
+
+#define SMB5_TYPE_C_EXIT_STATE_CFG 0x550
+#define SMB5_BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT BIT(3)
+#define SMB5_SEL_SRC_UPPER_REF_BIT BIT(2)
+#define SMB5_EXIT_SNK_BASED_ON_CC_BIT BIT(0)
+
#define BARK_BITE_WDOG_PET 0x643
#define BARK_BITE_WDOG_PET_BIT BIT(0)
@@ -350,8 +389,8 @@
#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
+/* PMI8998 registers represent current in increments of 1/40th of an amp. */
+#define SMB2_CURRENT_STEP_UA 25000
/* clang-format on */
enum charger_status {
@@ -378,6 +417,9 @@ struct smb_init_register {
* @base: Base address for smb registers
* @regmap: Register map
* @batt_info: Battery data from DT
+ * @gen: Charger hardware generation
+ * @icl_max_ua: Maximum programmable USB input current
+ * @icl_step_ua: USB input current register step
* @status_change_work: Worker to handle plug/unplug events
* @cable_irq: USB plugin IRQ
* @wakeup_enabled: If the cable IRQ will cause a wakeup
@@ -391,6 +433,9 @@ struct smb_chip {
unsigned int base;
struct regmap *regmap;
struct power_supply_battery_info *batt_info;
+ enum smb_generation gen;
+ unsigned int icl_max_ua;
+ unsigned int icl_step_ua;
struct delayed_work status_change_work;
int cable_irq;
@@ -402,6 +447,20 @@ 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;
+ const struct smb_init_register *init_seq;
+};
+
static enum power_supply_property smb_properties[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
@@ -419,7 +478,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;
@@ -474,9 +533,35 @@ 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)
+{
+ u16 reg;
+ u8 mask;
+ int rc;
+ u32 val;
+
+ switch (chip->gen) {
+ case SMB2:
+ reg = BATTERY_CHARGER_STATUS_2;
+ mask = SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT;
+ break;
+ case SMB5:
+ reg = BATTERY_CHARGER_STATUS_7;
+ mask = SMB5_CHARGER_ERROR_STATUS_BAT_OV_BIT;
+ break;
+ }
+
+ rc = regmap_read(chip->regmap, chip->base + reg, &val);
+ if (rc)
+ return rc;
+
+ return !!(val & mask);
+}
+
static int smb_get_prop_status(struct smb_chip *chip, int *val)
{
- unsigned char stat[2];
+ u32 stat;
int usb_online = 0;
int rc;
@@ -486,22 +571,27 @@ 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 = stat & BATTERY_CHARGER_STATUS_MASK;
- switch (stat[0]) {
+ switch (stat) {
case TRICKLE_CHARGE:
case PRE_CHARGE:
case FAST_CHARGE:
@@ -525,10 +615,10 @@ static int smb_get_prop_status(struct smb_chip *chip, int *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 + ICL_STATUS(chip), val);
if (rc >= 0)
- *val *= CURRENT_SCALE_FACTOR;
+ *val *= chip->icl_step_ua;
return rc;
}
@@ -536,12 +626,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);
@@ -620,7 +711,44 @@ 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 smb5_get_prop_health(struct smb_chip *chip, int *val)
+{
+ int rc;
+ unsigned int stat;
+
+ rc = smbx_ov_status(chip);
+
+ /* Treat any error as if we are in the overvoltage state */
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't determine overvoltage status!");
+ if (rc) {
+ dev_err(chip->dev, "battery over-voltage");
+ *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;
@@ -633,19 +761,19 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
}
switch (stat) {
- case CHARGER_ERROR_STATUS_BAT_OV_BIT:
+ case SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT:
*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
return 0;
- case BAT_TEMP_STATUS_TOO_COLD_BIT:
+ case SMB2_BAT_TEMP_STATUS_TOO_COLD_BIT:
*val = POWER_SUPPLY_HEALTH_COLD;
return 0;
- case BAT_TEMP_STATUS_TOO_HOT_BIT:
+ case SMB2_BAT_TEMP_STATUS_TOO_HOT_BIT:
*val = POWER_SUPPLY_HEALTH_OVERHEAT;
return 0;
- case BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT:
+ case SMB2_BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT:
*val = POWER_SUPPLY_HEALTH_COOL;
return 0;
- case BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT:
+ case SMB2_BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT:
*val = POWER_SUPPLY_HEALTH_WARM;
return 0;
default:
@@ -654,11 +782,25 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
}
}
+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)
{
struct smb_chip *chip = power_supply_get_drvdata(psy);
+ int ret;
switch (psp) {
case POWER_SUPPLY_PROP_MANUFACTURER:
@@ -673,8 +815,13 @@ static int smb_get_property(struct power_supply *psy,
return smb_get_iio_chan(chip, chip->usb_in_i_chan,
&val->intval);
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- return smb_get_iio_chan(chip, chip->usb_in_v_chan,
- &val->intval);
+ ret = smb_get_iio_chan(chip, chip->usb_in_v_chan,
+ &val->intval);
+ if (!ret) {
+ if (chip->gen == SMB5)
+ val->intval *= 16;
+ }
+ return ret;
case POWER_SUPPLY_PROP_ONLINE:
return smb_get_prop_usb_online(chip, &val->intval);
case POWER_SUPPLY_PROP_STATUS:
@@ -722,12 +869,8 @@ 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;
-
- regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_2,
- &status);
- if (status & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
+ if (smbx_ov_status(chip) == 1) {
/* The hardware stops charging automatically */
dev_err(chip->dev, "battery overvoltage detected\n");
power_supply_changed(chip->chg_psy);
@@ -773,7 +916,7 @@ static irqreturn_t smb_handle_wdog_bark(int irq, void *data)
}
static const struct power_supply_desc smb_psy_desc = {
- .name = "pmi8998_charger",
+ .name = "SMB2_charger",
.type = POWER_SUPPLY_TYPE_USB,
.usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) |
BIT(POWER_SUPPLY_USB_TYPE_CDP) |
@@ -787,16 +930,84 @@ static const struct power_supply_desc smb_psy_desc = {
};
/* 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 smb5_init_seq[] = {
+ /*
+ * By default configure us as an upstream facing port
+ * FIXME: This will be handled by the type-c driver
+ */
+ { .addr = SMB5_TYPE_C_MODE_CFG,
+ .mask = SMB5_EN_TRY_SNK_BIT | SMB5_EN_SNK_ONLY_BIT,
+ .val = SMB5_EN_TRY_SNK_BIT },
+ { .addr = SMB5_TYPEC_TYPE_C_VCONN_CONTROL,
+ .mask = SMB5_VCONN_EN_ORIENTATION_BIT | SMB5_VCONN_EN_SRC_BIT |
+ SMB5_VCONN_EN_VALUE_BIT,
+ .val = SMB5_VCONN_EN_SRC_BIT },
+ { .addr = SMB5_DEBUG_ACCESS_SRC_CFG,
+ .mask = SMB5_EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT,
+ .val = SMB5_EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT },
+ { .addr = SMB5_TYPE_C_EXIT_STATE_CFG,
+ .mask = SMB5_SEL_SRC_UPPER_REF_BIT,
+ .val = SMB5_SEL_SRC_UPPER_REF_BIT },
+ /*
+ * Disable Type-C factory mode and stay in Attached.SRC state when VCONN
+ * over-current happens
+ */
+ { .addr = TYPE_C_CFG,
+ .mask = APSD_START_ON_CC_BIT,
+ .val = 0 },
+ { .addr = SMB5_TYPE_C_DEBUG_ACCESS_SINK,
+ .mask = SMB5_TYPEC_DEBUG_ACCESS_SINK_MASK,
+ .val = 0x17 },
+ /* Configure VBUS for software control */
+ { .addr = OTG_CFG, .mask = OTG_EN_SRC_CFG_BIT, .val = 0 },
+ /*
+ * Recharge when State Of Charge drops below 98%.
+ */
+ { .addr = SMB5_CHARGE_RCHG_SOC_THRESHOLD_CFG_REG,
+ .mask = SMB5_CHARGE_RCHG_SOC_THRESHOLD_CFG_MASK,
+ .val = 250 },
+ /* Enable BC1P2 auto Src detect */
+ { .addr = USBIN_OPTIONS_1_CFG,
+ .mask = AUTO_SRC_DETECT_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_ADC_EN_BIT
+ | USBIN_AICL_EN_BIT | SUSPEND_ON_COLLAPSE_USBIN_BIT },
+};
+
+/* Init sequence derived from vendor downstream driver */
+static const struct smb_init_register smb2_init_seq[] = {
/*
* 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
@@ -810,10 +1021,10 @@ static const struct smb_init_register smb_init_seq[] = {
* 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,
@@ -878,7 +1089,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
@@ -886,20 +1097,76 @@ 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,
+};
+
+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,
+};
+
+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,
};
-static int smb_init_hw(struct smb_chip *chip)
+static const struct smb_match_data pm7250b_match_data = {
+ .init_seq = smb5_init_seq,
+ .init_seq_len = ARRAY_SIZE(smb5_init_seq),
+ .name = "pm7250b",
+ .gen = SMB5,
+ .fv_min_uv = 3600000,
+ .fv_max_uv = 4800000,
+ .fv_step_uv = 10000,
+ .fcc_max_ua = 3000000,
+ .fcc_step_ua = 50000,
+ .icl_max_ua = 3000000,
+ .icl_step_ua = 50000,
+};
+
+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",
@@ -935,6 +1202,11 @@ static int smb_probe(struct platform_device *pdev)
struct power_supply_config supply_config = {};
struct power_supply_desc *desc;
struct smb_chip *chip;
+ const struct smb_match_data *match_data;
+ unsigned int fast_charge_current_sel;
+ unsigned int float_voltage_sel;
+ int fast_charge_current_ua;
+ int float_voltage_uv;
int rc, irq;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
@@ -965,7 +1237,34 @@ static int smb_probe(struct platform_device *pdev)
"Couldn't get usbin_i IIO channel\n");
}
- rc = smb_init_hw(chip);
+ 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->icl_max_ua = match_data->icl_max_ua;
+ chip->icl_step_ua = match_data->icl_step_ua;
+
+ dev_info(chip->dev, "Generation %s\n", chip->gen == SMB2 ? "SMB2" : "SMB5");
+ 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 = 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;
@@ -978,7 +1277,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;
@@ -999,11 +1298,66 @@ static int smb_probe(struct platform_device *pdev)
return dev_err_probe(chip->dev, rc,
"Failed to init status change work\n");
- rc = (chip->batt_info->voltage_max_design_uv - 3487500) / 7500 + 1;
- rc = regmap_update_bits(chip->regmap, chip->base + FLOAT_VOLTAGE_CFG,
- FLOAT_VOLTAGE_SETTING_MASK, rc);
- if (rc < 0)
- return dev_err_probe(chip->dev, rc, "Couldn't set vbat max\n");
+ if (chip->gen == SMB5) {
+ float_voltage_uv =
+ chip->batt_info->constant_charge_voltage_max_uv;
+ if (float_voltage_uv <= 0)
+ float_voltage_uv = chip->batt_info->voltage_max_design_uv;
+
+ if (float_voltage_uv < match_data->fv_min_uv ||
+ float_voltage_uv > match_data->fv_max_uv)
+ return 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);
+
+ 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)
+ return dev_err_probe(chip->dev, rc,
+ "could not set float voltage\n");
+
+ fast_charge_current_ua =
+ chip->batt_info->constant_charge_current_max_ua;
+ if (fast_charge_current_ua <= 0)
+ fast_charge_current_ua = DCP_CURRENT_UA;
+
+ if (fast_charge_current_ua > match_data->fcc_max_ua)
+ return dev_err_probe(chip->dev, -EINVAL,
+ "fast charge current %d uA exceeds %u uA\n",
+ fast_charge_current_ua,
+ match_data->fcc_max_ua);
+
+ 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)
+ return dev_err_probe(chip->dev, rc,
+ "could not set fast charge current\n");
+
+ rc = smb_set_current_limit(chip, SDP_CURRENT_UA);
+ if (rc < 0)
+ return dev_err_probe(chip->dev, rc,
+ "could not set USB input current\n");
+ } else {
+ rc = (chip->batt_info->voltage_max_design_uv -
+ match_data->fv_min_uv) / match_data->fv_step_uv + 1;
+ rc = regmap_update_bits(chip->regmap,
+ chip->base + FLOAT_VOLTAGE_CFG,
+ FLOAT_VOLTAGE_SETTING_MASK, rc);
+ if (rc < 0)
+ return dev_err_probe(chip->dev, rc,
+ "could not set vbat max\n");
+ }
rc = smb_init_irq(chip, &irq, "bat-ov", smb_handle_batt_overvoltage);
if (rc < 0)
@@ -1030,6 +1384,30 @@ static int smb_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, chip);
+ if (chip->gen == SMB5) {
+ 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");
+
+ 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);
+ }
+
/* Initialise charger state */
schedule_delayed_work(&chip->status_change_work, 0);
@@ -1037,8 +1415,10 @@ 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,pm7250b-charger", .data = &pm7250b_match_data },
+ { .compatible = "qcom,pm8150b-charger", .data = &pm8150b_match_data },
{ /* sentinal */ }
};
MODULE_DEVICE_TABLE(of, smb_match_id_table);
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH 2/3] power: supply: qcom_smbx: add SMB5 support
@ 2026-08-11 20:35 ` Robin Snyders
0 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders @ 2026-08-11 20:35 UTC (permalink / raw)
To: Casey Connolly, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Robin Snyders,
Joel Selvaraj
From: Casey Connolly <casey.connolly@linaro.org>
Introduce support for the SMB5 charger found on PM8150B and other
modern Qualcomm PMICs.
SMB5 is largely similar to SMB2, with differences in the register
layout, Type-C block and charger status fields. Select the register
layout and the voltage, fast-charge-current and input-current ranges
from per-PMIC match data.
Suspend USB input and charging before initialization, program the
battery limits and interrupts, and only then enable charging and input.
Start PM8150B with conservative 4.40 V, 1.50 A and 500 mA limits.
Also read overvoltage state from the returned register value rather than
the register address.
On a OnePlus 7T Pro, register reads confirmed the programmed 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/qcom_smbx.c | 512 ++++++++++++++++++++++++++++++++++-----
1 file changed, 446 insertions(+), 66 deletions(-)
diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
index 67fdb4335338..09d9e0f95cb5 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,12 +91,15 @@
#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 SMB5_CHARGE_RCHG_SOC_THRESHOLD_CFG_REG 0x7D
+#define SMB5_CHARGE_RCHG_SOC_THRESHOLD_CFG_MASK GENMASK(7, 0)
+
#define JEITA_EN_CFG 0x90
#define JEITA_EN_HARDLIMIT_BIT BIT(4)
#define JEITA_EN_HOT_SL_FCV_BIT BIT(3)
@@ -189,9 +205,11 @@
#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 CMD_ICL_OVERRIDE 0x342
+#define ICL_OVERRIDE_BIT BIT(0)
+
#define TYPE_C_CFG 0x358
#define APSD_START_ON_CC_BIT BIT(7)
#define WAIT_FOR_APSD_BIT BIT(6)
@@ -260,7 +278,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 +292,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 +321,10 @@
#define AICL_SWITCH_ENABLE_BIT BIT(1)
#define ZIN_ICL_ENABLE_BIT BIT(0)
-#define ICL_STATUS 0x607
+#define ICL_STATUS(smb) (SMB_REG_OFFSET(smb) + 0x07)
#define INPUT_CURRENT_LIMIT_MASK GENMASK(7, 0)
-#define POWER_PATH_STATUS 0x60B
+#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)
@@ -315,6 +333,27 @@
#define P_PATH_POWER_PATH_MASK GENMASK(2, 1)
#define P_PATH_VALID_INPUT_POWER_SOURCE_STS_BIT BIT(0)
+/* 0x5xx region is available on the SMB5 Type-C block. */
+#define SMB5_TYPE_C_MODE_CFG 0x544
+#define SMB5_EN_TRY_SNK_BIT BIT(4)
+#define SMB5_EN_SNK_ONLY_BIT BIT(1)
+
+#define SMB5_TYPEC_TYPE_C_VCONN_CONTROL 0x546
+#define SMB5_VCONN_EN_ORIENTATION_BIT BIT(2)
+#define SMB5_VCONN_EN_VALUE_BIT BIT(1)
+#define SMB5_VCONN_EN_SRC_BIT BIT(0)
+
+#define SMB5_TYPE_C_DEBUG_ACCESS_SINK 0x54a
+#define SMB5_TYPEC_DEBUG_ACCESS_SINK_MASK GENMASK(4, 0)
+
+#define SMB5_DEBUG_ACCESS_SRC_CFG 0x54c
+#define SMB5_EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT BIT(0)
+
+#define SMB5_TYPE_C_EXIT_STATE_CFG 0x550
+#define SMB5_BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT BIT(3)
+#define SMB5_SEL_SRC_UPPER_REF_BIT BIT(2)
+#define SMB5_EXIT_SNK_BASED_ON_CC_BIT BIT(0)
+
#define BARK_BITE_WDOG_PET 0x643
#define BARK_BITE_WDOG_PET_BIT BIT(0)
@@ -350,8 +389,8 @@
#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
+/* PMI8998 registers represent current in increments of 1/40th of an amp. */
+#define SMB2_CURRENT_STEP_UA 25000
/* clang-format on */
enum charger_status {
@@ -378,6 +417,9 @@ struct smb_init_register {
* @base: Base address for smb registers
* @regmap: Register map
* @batt_info: Battery data from DT
+ * @gen: Charger hardware generation
+ * @icl_max_ua: Maximum programmable USB input current
+ * @icl_step_ua: USB input current register step
* @status_change_work: Worker to handle plug/unplug events
* @cable_irq: USB plugin IRQ
* @wakeup_enabled: If the cable IRQ will cause a wakeup
@@ -391,6 +433,9 @@ struct smb_chip {
unsigned int base;
struct regmap *regmap;
struct power_supply_battery_info *batt_info;
+ enum smb_generation gen;
+ unsigned int icl_max_ua;
+ unsigned int icl_step_ua;
struct delayed_work status_change_work;
int cable_irq;
@@ -402,6 +447,20 @@ 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;
+ const struct smb_init_register *init_seq;
+};
+
static enum power_supply_property smb_properties[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
@@ -419,7 +478,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;
@@ -474,9 +533,35 @@ 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)
+{
+ u16 reg;
+ u8 mask;
+ int rc;
+ u32 val;
+
+ switch (chip->gen) {
+ case SMB2:
+ reg = BATTERY_CHARGER_STATUS_2;
+ mask = SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT;
+ break;
+ case SMB5:
+ reg = BATTERY_CHARGER_STATUS_7;
+ mask = SMB5_CHARGER_ERROR_STATUS_BAT_OV_BIT;
+ break;
+ }
+
+ rc = regmap_read(chip->regmap, chip->base + reg, &val);
+ if (rc)
+ return rc;
+
+ return !!(val & mask);
+}
+
static int smb_get_prop_status(struct smb_chip *chip, int *val)
{
- unsigned char stat[2];
+ u32 stat;
int usb_online = 0;
int rc;
@@ -486,22 +571,27 @@ 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 = stat & BATTERY_CHARGER_STATUS_MASK;
- switch (stat[0]) {
+ switch (stat) {
case TRICKLE_CHARGE:
case PRE_CHARGE:
case FAST_CHARGE:
@@ -525,10 +615,10 @@ static int smb_get_prop_status(struct smb_chip *chip, int *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 + ICL_STATUS(chip), val);
if (rc >= 0)
- *val *= CURRENT_SCALE_FACTOR;
+ *val *= chip->icl_step_ua;
return rc;
}
@@ -536,12 +626,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);
@@ -620,7 +711,44 @@ 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 smb5_get_prop_health(struct smb_chip *chip, int *val)
+{
+ int rc;
+ unsigned int stat;
+
+ rc = smbx_ov_status(chip);
+
+ /* Treat any error as if we are in the overvoltage state */
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't determine overvoltage status!");
+ if (rc) {
+ dev_err(chip->dev, "battery over-voltage");
+ *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;
@@ -633,19 +761,19 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
}
switch (stat) {
- case CHARGER_ERROR_STATUS_BAT_OV_BIT:
+ case SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT:
*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
return 0;
- case BAT_TEMP_STATUS_TOO_COLD_BIT:
+ case SMB2_BAT_TEMP_STATUS_TOO_COLD_BIT:
*val = POWER_SUPPLY_HEALTH_COLD;
return 0;
- case BAT_TEMP_STATUS_TOO_HOT_BIT:
+ case SMB2_BAT_TEMP_STATUS_TOO_HOT_BIT:
*val = POWER_SUPPLY_HEALTH_OVERHEAT;
return 0;
- case BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT:
+ case SMB2_BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT:
*val = POWER_SUPPLY_HEALTH_COOL;
return 0;
- case BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT:
+ case SMB2_BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT:
*val = POWER_SUPPLY_HEALTH_WARM;
return 0;
default:
@@ -654,11 +782,25 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
}
}
+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)
{
struct smb_chip *chip = power_supply_get_drvdata(psy);
+ int ret;
switch (psp) {
case POWER_SUPPLY_PROP_MANUFACTURER:
@@ -673,8 +815,13 @@ static int smb_get_property(struct power_supply *psy,
return smb_get_iio_chan(chip, chip->usb_in_i_chan,
&val->intval);
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- return smb_get_iio_chan(chip, chip->usb_in_v_chan,
- &val->intval);
+ ret = smb_get_iio_chan(chip, chip->usb_in_v_chan,
+ &val->intval);
+ if (!ret) {
+ if (chip->gen == SMB5)
+ val->intval *= 16;
+ }
+ return ret;
case POWER_SUPPLY_PROP_ONLINE:
return smb_get_prop_usb_online(chip, &val->intval);
case POWER_SUPPLY_PROP_STATUS:
@@ -722,12 +869,8 @@ 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;
-
- regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_2,
- &status);
- if (status & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
+ if (smbx_ov_status(chip) == 1) {
/* The hardware stops charging automatically */
dev_err(chip->dev, "battery overvoltage detected\n");
power_supply_changed(chip->chg_psy);
@@ -773,7 +916,7 @@ static irqreturn_t smb_handle_wdog_bark(int irq, void *data)
}
static const struct power_supply_desc smb_psy_desc = {
- .name = "pmi8998_charger",
+ .name = "SMB2_charger",
.type = POWER_SUPPLY_TYPE_USB,
.usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) |
BIT(POWER_SUPPLY_USB_TYPE_CDP) |
@@ -787,16 +930,84 @@ static const struct power_supply_desc smb_psy_desc = {
};
/* 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 smb5_init_seq[] = {
+ /*
+ * By default configure us as an upstream facing port
+ * FIXME: This will be handled by the type-c driver
+ */
+ { .addr = SMB5_TYPE_C_MODE_CFG,
+ .mask = SMB5_EN_TRY_SNK_BIT | SMB5_EN_SNK_ONLY_BIT,
+ .val = SMB5_EN_TRY_SNK_BIT },
+ { .addr = SMB5_TYPEC_TYPE_C_VCONN_CONTROL,
+ .mask = SMB5_VCONN_EN_ORIENTATION_BIT | SMB5_VCONN_EN_SRC_BIT |
+ SMB5_VCONN_EN_VALUE_BIT,
+ .val = SMB5_VCONN_EN_SRC_BIT },
+ { .addr = SMB5_DEBUG_ACCESS_SRC_CFG,
+ .mask = SMB5_EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT,
+ .val = SMB5_EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT },
+ { .addr = SMB5_TYPE_C_EXIT_STATE_CFG,
+ .mask = SMB5_SEL_SRC_UPPER_REF_BIT,
+ .val = SMB5_SEL_SRC_UPPER_REF_BIT },
+ /*
+ * Disable Type-C factory mode and stay in Attached.SRC state when VCONN
+ * over-current happens
+ */
+ { .addr = TYPE_C_CFG,
+ .mask = APSD_START_ON_CC_BIT,
+ .val = 0 },
+ { .addr = SMB5_TYPE_C_DEBUG_ACCESS_SINK,
+ .mask = SMB5_TYPEC_DEBUG_ACCESS_SINK_MASK,
+ .val = 0x17 },
+ /* Configure VBUS for software control */
+ { .addr = OTG_CFG, .mask = OTG_EN_SRC_CFG_BIT, .val = 0 },
+ /*
+ * Recharge when State Of Charge drops below 98%.
+ */
+ { .addr = SMB5_CHARGE_RCHG_SOC_THRESHOLD_CFG_REG,
+ .mask = SMB5_CHARGE_RCHG_SOC_THRESHOLD_CFG_MASK,
+ .val = 250 },
+ /* Enable BC1P2 auto Src detect */
+ { .addr = USBIN_OPTIONS_1_CFG,
+ .mask = AUTO_SRC_DETECT_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_ADC_EN_BIT
+ | USBIN_AICL_EN_BIT | SUSPEND_ON_COLLAPSE_USBIN_BIT },
+};
+
+/* Init sequence derived from vendor downstream driver */
+static const struct smb_init_register smb2_init_seq[] = {
/*
* 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
@@ -810,10 +1021,10 @@ static const struct smb_init_register smb_init_seq[] = {
* 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,
@@ -878,7 +1089,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
@@ -886,20 +1097,76 @@ 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,
+};
+
+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,
+};
+
+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,
};
-static int smb_init_hw(struct smb_chip *chip)
+static const struct smb_match_data pm7250b_match_data = {
+ .init_seq = smb5_init_seq,
+ .init_seq_len = ARRAY_SIZE(smb5_init_seq),
+ .name = "pm7250b",
+ .gen = SMB5,
+ .fv_min_uv = 3600000,
+ .fv_max_uv = 4800000,
+ .fv_step_uv = 10000,
+ .fcc_max_ua = 3000000,
+ .fcc_step_ua = 50000,
+ .icl_max_ua = 3000000,
+ .icl_step_ua = 50000,
+};
+
+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",
@@ -935,6 +1202,11 @@ static int smb_probe(struct platform_device *pdev)
struct power_supply_config supply_config = {};
struct power_supply_desc *desc;
struct smb_chip *chip;
+ const struct smb_match_data *match_data;
+ unsigned int fast_charge_current_sel;
+ unsigned int float_voltage_sel;
+ int fast_charge_current_ua;
+ int float_voltage_uv;
int rc, irq;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
@@ -965,7 +1237,34 @@ static int smb_probe(struct platform_device *pdev)
"Couldn't get usbin_i IIO channel\n");
}
- rc = smb_init_hw(chip);
+ 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->icl_max_ua = match_data->icl_max_ua;
+ chip->icl_step_ua = match_data->icl_step_ua;
+
+ dev_info(chip->dev, "Generation %s\n", chip->gen == SMB2 ? "SMB2" : "SMB5");
+ 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 = 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;
@@ -978,7 +1277,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;
@@ -999,11 +1298,66 @@ static int smb_probe(struct platform_device *pdev)
return dev_err_probe(chip->dev, rc,
"Failed to init status change work\n");
- rc = (chip->batt_info->voltage_max_design_uv - 3487500) / 7500 + 1;
- rc = regmap_update_bits(chip->regmap, chip->base + FLOAT_VOLTAGE_CFG,
- FLOAT_VOLTAGE_SETTING_MASK, rc);
- if (rc < 0)
- return dev_err_probe(chip->dev, rc, "Couldn't set vbat max\n");
+ if (chip->gen == SMB5) {
+ float_voltage_uv =
+ chip->batt_info->constant_charge_voltage_max_uv;
+ if (float_voltage_uv <= 0)
+ float_voltage_uv = chip->batt_info->voltage_max_design_uv;
+
+ if (float_voltage_uv < match_data->fv_min_uv ||
+ float_voltage_uv > match_data->fv_max_uv)
+ return 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);
+
+ 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)
+ return dev_err_probe(chip->dev, rc,
+ "could not set float voltage\n");
+
+ fast_charge_current_ua =
+ chip->batt_info->constant_charge_current_max_ua;
+ if (fast_charge_current_ua <= 0)
+ fast_charge_current_ua = DCP_CURRENT_UA;
+
+ if (fast_charge_current_ua > match_data->fcc_max_ua)
+ return dev_err_probe(chip->dev, -EINVAL,
+ "fast charge current %d uA exceeds %u uA\n",
+ fast_charge_current_ua,
+ match_data->fcc_max_ua);
+
+ 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)
+ return dev_err_probe(chip->dev, rc,
+ "could not set fast charge current\n");
+
+ rc = smb_set_current_limit(chip, SDP_CURRENT_UA);
+ if (rc < 0)
+ return dev_err_probe(chip->dev, rc,
+ "could not set USB input current\n");
+ } else {
+ rc = (chip->batt_info->voltage_max_design_uv -
+ match_data->fv_min_uv) / match_data->fv_step_uv + 1;
+ rc = regmap_update_bits(chip->regmap,
+ chip->base + FLOAT_VOLTAGE_CFG,
+ FLOAT_VOLTAGE_SETTING_MASK, rc);
+ if (rc < 0)
+ return dev_err_probe(chip->dev, rc,
+ "could not set vbat max\n");
+ }
rc = smb_init_irq(chip, &irq, "bat-ov", smb_handle_batt_overvoltage);
if (rc < 0)
@@ -1030,6 +1384,30 @@ static int smb_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, chip);
+ if (chip->gen == SMB5) {
+ 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");
+
+ 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);
+ }
+
/* Initialise charger state */
schedule_delayed_work(&chip->status_change_work, 0);
@@ -1037,8 +1415,10 @@ 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,pm7250b-charger", .data = &pm7250b_match_data },
+ { .compatible = "qcom,pm8150b-charger", .data = &pm8150b_match_data },
{ /* sentinal */ }
};
MODULE_DEVICE_TABLE(of, smb_match_id_table);
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH 3/3] power: supply: qcom_smbx: program AICL rerun time
2026-08-11 20:35 ` Robin Snyders
@ 2026-08-11 20:35 ` Robin Snyders
-1 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-11 20:35 UTC (permalink / raw)
To: Casey Connolly, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Robin Snyders
From: Casey Connolly <casey.connolly@linaro.org>
The bootloader-provided automatic input current limit rerun interval is
not a stable software interface. Program a consistent three-second
interval during probe. This is required at least on SMB5 hardware.
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Signed-off-by: Robin Snyders <robin@snyders.xyz>
---
drivers/power/supply/qcom_smbx.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
index 09d9e0f95cb5..9e3e240b6813 100644
--- a/drivers/power/supply/qcom_smbx.c
+++ b/drivers/power/supply/qcom_smbx.c
@@ -374,6 +374,7 @@ enum smb_generation {
#define AICL_RERUN_TIME_CFG 0x661
#define AICL_RERUN_TIME_MASK GENMASK(1, 0)
+#define AICL_RERUN_TIME_3_SECS 0
#define STAT_CFG 0x690
#define STAT_SW_OVERRIDE_VALUE_BIT BIT(7)
@@ -1384,6 +1385,14 @@ static int smb_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, chip);
+ rc = regmap_write_bits(chip->regmap,
+ chip->base + AICL_RERUN_TIME_CFG,
+ AICL_RERUN_TIME_MASK,
+ AICL_RERUN_TIME_3_SECS);
+ if (rc < 0)
+ return dev_err_probe(chip->dev, rc,
+ "could not set AICL rerun time\n");
+
if (chip->gen == SMB5) {
rc = regmap_update_bits(chip->regmap,
chip->base + CHARGING_ENABLE_CMD,
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH 3/3] power: supply: qcom_smbx: program AICL rerun time
@ 2026-08-11 20:35 ` Robin Snyders
0 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders @ 2026-08-11 20:35 UTC (permalink / raw)
To: Casey Connolly, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel, Robin Snyders
From: Casey Connolly <casey.connolly@linaro.org>
The bootloader-provided automatic input current limit rerun interval is
not a stable software interface. Program a consistent three-second
interval during probe. This is required at least on SMB5 hardware.
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Signed-off-by: Robin Snyders <robin@snyders.xyz>
---
drivers/power/supply/qcom_smbx.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
index 09d9e0f95cb5..9e3e240b6813 100644
--- a/drivers/power/supply/qcom_smbx.c
+++ b/drivers/power/supply/qcom_smbx.c
@@ -374,6 +374,7 @@ enum smb_generation {
#define AICL_RERUN_TIME_CFG 0x661
#define AICL_RERUN_TIME_MASK GENMASK(1, 0)
+#define AICL_RERUN_TIME_3_SECS 0
#define STAT_CFG 0x690
#define STAT_SW_OVERRIDE_VALUE_BIT BIT(7)
@@ -1384,6 +1385,14 @@ static int smb_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, chip);
+ rc = regmap_write_bits(chip->regmap,
+ chip->base + AICL_RERUN_TIME_CFG,
+ AICL_RERUN_TIME_MASK,
+ AICL_RERUN_TIME_3_SECS);
+ if (rc < 0)
+ return dev_err_probe(chip->dev, rc,
+ "could not set AICL rerun time\n");
+
if (chip->gen == SMB5) {
rc = regmap_update_bits(chip->regmap,
chip->base + CHARGING_ENABLE_CMD,
--
2.54.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH 3/3] power: supply: qcom_smbx: program AICL rerun time
2026-08-11 20:35 ` Robin Snyders
(?)
@ 2026-08-11 20:46 ` sashiko-bot
-1 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-08-11 20:46 UTC (permalink / raw)
To: Robin Snyders; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The watchdog interrupt handler writes to a register without adding the peripheral base address.
--
--- Patch [3]: [PATCH 3/3] power: supply: qcom_smbx: program AICL rerun time ---
commit 7ed13fc0edf8d350c9eda801c5d3ae1f45ee12b6
Author: Casey Connolly <casey.connolly@linaro.org>
power: supply: qcom_smbx: program AICL rerun time
The bootloader-provided automatic input current limit rerun interval is
not a stable software interface. Program a consistent three-second
interval during probe. This is required at least on SMB5 hardware.
> diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
> index 09d9e0f95cb56..9e3e240b68130 100644
> --- a/drivers/power/supply/qcom_smbx.c
> +++ b/drivers/power/supply/qcom_smbx.c
[ ... ]
> @@ -1384,6 +1385,14 @@ static int smb_probe(struct platform_device *pdev)
>
> platform_set_drvdata(pdev, chip);
>
> + rc = regmap_write_bits(chip->regmap,
> + chip->base + AICL_RERUN_TIME_CFG,
[Severity: High]
This is a pre-existing issue, but seeing the correct usage of chip->base
here, is the regmap write in smb_handle_wdog_bark() writing to an arbitrary
PMIC peripheral?
drivers/power/supply/qcom_smbx.c:smb_handle_wdog_bark() {
...
rc = regmap_write(chip->regmap, BARK_BITE_WDOG_PET,
BARK_BITE_WDOG_PET_BIT);
...
}
It appears to be missing chip->base, which could fail to pet the charger
watchdog and corrupt the state of another peripheral when the watchdog bark
interrupt fires.
> + AICL_RERUN_TIME_MASK,
> + AICL_RERUN_TIME_3_SECS);
> + if (rc < 0)
> + return dev_err_probe(chip->dev, rc,
> + "could not set AICL rerun time\n");
> +
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260811-submit-qcom-smbx-send-v1-v1-0-feec6cfa123a@snyders.xyz?part=3
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH 2/3] power: supply: qcom_smbx: add SMB5 support
2026-08-11 20:35 ` Robin Snyders
(?)
@ 2026-08-11 20:51 ` sashiko-bot
-1 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-08-11 20:51 UTC (permalink / raw)
To: Robin Snyders; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [High] Probe failure leaves SMB5 charging and USB input permanently disabled.
- [High] Watchdog configuration in `smb5_init_seq` uses hardcoded SMB2 offsets, corrupting unrelated hardware blocks.
Pre-existing issues:
- [High] Missing battery voltage properties cause unsigned integer underflow and write garbage hardware configurations on SMB2.
- [High] Logic bug in `smb2_get_prop_health` masks critical hardware faults like overvoltage and overheating.
- [High] The watchdog interrupt handler writes to a fixed PMIC address instead of the relative charger base, corrupting unrelated hardware.
--
--- Patch [2]: [PATCH 2/3] power: supply: qcom_smbx: add SMB5 support ---
commit 6307e65683cce1f0d817fcc2cf3983ee19ae24c0
Author: Casey Connolly <casey.connolly@linaro.org>
power: supply: qcom_smbx: add SMB5 support
Introduce support for the SMB5 charger found on PM8150B and other
modern Qualcomm PMICs. It selects register layout and limits from
match data, and suspends USB input/charging during init.
> diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
> index 67fdb43353388..09d9e0f95cb56 100644
> --- a/drivers/power/supply/qcom_smbx.c
> +++ b/drivers/power/supply/qcom_smbx.c
[ ... ]
> @@ -633,19 +761,19 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val)
> }
>
> switch (stat) {
> - case CHARGER_ERROR_STATUS_BAT_OV_BIT:
> + case SMB2_CHARGER_ERROR_STATUS_BAT_OV_BIT:
> *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
> return 0;
[ ... ]
> - case BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT:
> + case SMB2_BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT:
> *val = POWER_SUPPLY_HEALTH_WARM;
> return 0;
> default:
> *val = POWER_SUPPLY_HEALTH_GOOD;
> return 0;
> }
> }
[Severity: High]
This is a pre-existing issue, but does this switch statement correctly handle
cases where multiple status bits are set?
Looking at smb2_get_prop_health(), the switch evaluates the unmasked register
value. If a normal status bit is active alongside a fault bit, it seems the
exact match fails and it falls through to the default case, incorrectly
returning POWER_SUPPLY_HEALTH_GOOD during hardware faults.
[ ... ]
> @@ -773,7 +916,7 @@ static irqreturn_t smb_handle_wdog_bark(int irq, void *data)
> }
>
> static const struct power_supply_desc smb_psy_desc = {
> - .name = "pmi8998_charger",
> + .name = "SMB2_charger",
[Severity: High]
This is a pre-existing issue, but does smb_handle_wdog_bark() write to the
correct PMIC hardware block?
In smb_handle_wdog_bark(), the code performs a register write to the absolute
address BARK_BITE_WDOG_PET. It omits the chip->base offset which is used by
other register accesses to target the correct peripheral block. Could this
corrupt unrelated PMIC hardware blocks when the watchdog interrupt fires?
[ ... ]
> @@ -787,16 +930,84 @@ static const struct power_supply_desc smb_psy_desc = {
> };
>
> /* 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 smb5_init_seq[] = {
[ ... ]
> + /* 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 },
[Severity: High]
Are these register offsets correct for SMB5 devices?
In smb5_init_seq(), SNARL_BARK_BITE_WD_CFG and WD_CFG appear to use hardcoded
SMB2 absolute offsets (0x653 and 0x651). When these are added to chip->base
in smb_init_hw(), will this write the watchdog configuration to the wrong
hardware block on SMB5 PMICs?
[ ... ]
> @@ -965,7 +1237,34 @@ static int smb_probe(struct platform_device *pdev)
[ ... ]
> + 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 = 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");
> + }
> +
[Severity: High]
If any subsequent initialization step fails and probe returns early, does
this leave SMB5 charging and USB input permanently disabled?
In smb_probe(), the USBIN_SUSPEND_BIT is set and CHARGING_ENABLE_CMD_BIT is
cleared. If probe subsequently fails (for example, due to a missing device
tree property or IIO channel), the function returns without restoring these
bits. Could this prevent the device from charging or receiving USB power
until it is rebooted?
[ ... ]
> @@ -999,11 +1298,66 @@ static int smb_probe(struct platform_device *pdev)
[ ... ]
> + } else {
> + rc = (chip->batt_info->voltage_max_design_uv -
> + match_data->fv_min_uv) / match_data->fv_step_uv + 1;
> + rc = regmap_update_bits(chip->regmap,
> + chip->base + FLOAT_VOLTAGE_CFG,
> + FLOAT_VOLTAGE_SETTING_MASK, rc);
> + if (rc < 0)
> + return dev_err_probe(chip->dev, rc,
> + "could not set vbat max\n");
> + }
[Severity: High]
This is a pre-existing issue, but does this calculation handle missing
battery device tree properties safely?
If the battery node lacks a maximum voltage, power_supply_get_battery_info()
populates voltage_max_design_uv with a negative error code (like -EINVAL).
In smb_probe(), subtracting the unsigned fv_min_uv from this negative value
could cause an unsigned integer underflow, writing a garbage configuration
value to the FLOAT_VOLTAGE_CFG hardware register.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260811-submit-qcom-smbx-send-v1-v1-0-feec6cfa123a@snyders.xyz?part=2
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 0/2] power: supply: qcom_smbx: add SMB5 support
2026-08-11 20:35 ` Robin Snyders
@ 2026-08-11 23:56 ` Robin Snyders
-1 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-11 23:56 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,
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 use 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 before
device_add publishes properties, and writable properties remain gated
until IRQ, wake and final-enable setup finishes. 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.
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 initial 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 without crossing the voltage guard. The v2 corrections
were validated offline and have not been re-tested on hardware.
The driver builds with LLVM W=1 and passes sparse without driver warnings.
The binding passes dt_binding_check, and both patches pass checkpatch
--strict.
Signed-off-by: Robin Snyders <robin@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 | 766 ++++++++++++++++-----
3 files changed, 623 insertions(+), 168 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] 16+ messages in thread
* [PATCH v2 0/2] power: supply: qcom_smbx: add SMB5 support
@ 2026-08-11 23:56 ` Robin Snyders
0 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders @ 2026-08-11 23:56 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,
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 use 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 before
device_add publishes properties, and writable properties remain gated
until IRQ, wake and final-enable setup finishes. 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.
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 initial 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 without crossing the voltage guard. The v2 corrections
were validated offline and have not been re-tested on hardware.
The driver builds with LLVM W=1 and passes sparse without driver warnings.
The binding passes dt_binding_check, and both patches pass checkpatch
--strict.
Signed-off-by: Robin Snyders <robin@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 | 766 ++++++++++++++++-----
3 files changed, 623 insertions(+), 168 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] 16+ messages in thread
* [PATCH v2 1/2] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs
2026-08-11 23:56 ` Robin Snyders
@ 2026-08-11 23:56 ` Robin Snyders
-1 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-11 23:56 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
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>
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 70f5cd6eaeabe..25511e2e33691 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] 16+ messages in thread
* [PATCH v2 1/2] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs
@ 2026-08-11 23:56 ` Robin Snyders
0 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders @ 2026-08-11 23:56 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
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>
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 70f5cd6eaeabe..25511e2e33691 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] 16+ messages in thread
* [PATCH v2 2/2] power: supply: qcom_smbx: add SMB5 support
2026-08-11 23:56 ` Robin Snyders
@ 2026-08-11 23:57 ` Robin Snyders
-1 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders via B4 Relay @ 2026-08-11 23:57 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,
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, including the
PM8150B-compatible parameter block used by PM7250B. 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.
Program the battery limits from the power-supply registration init
callback before device_add publishes the properties. Gate writable
properties until the IRQ and wake setup and final input and charging
enables are complete, preventing userspace from racing the
suspend-configure-enable sequence.
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 | 766 +++++++++++++++++++++++++++++++--------
2 files changed, 612 insertions(+), 162 deletions(-)
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index b89ef40df7f82..1159a7711919d 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 71f5a48594129..f99982ebb964f 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
@@ -189,9 +202,11 @@
#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 CMD_ICL_OVERRIDE 0x342
+#define ICL_OVERRIDE_BIT BIT(0)
+
#define TYPE_C_CFG 0x358
#define APSD_START_ON_CC_BIT BIT(7)
#define WAIT_FOR_APSD_BIT BIT(6)
@@ -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,14 +401,23 @@ 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
+ * @probe_complete: Whether runtime property writes are safe
* @status_change_work: Worker to handle plug/unplug events
* @cable_irq: USB plugin IRQ
* @wakeup_enabled: If the cable IRQ will cause a wakeup
@@ -394,8 +430,15 @@ 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;
+ bool probe_complete;
struct delayed_work status_change_work;
int cable_irq;
@@ -407,6 +450,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 +483,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 +538,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 +621,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 +658,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 +725,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 +741,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 +807,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 +823,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 +852,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);
@@ -694,6 +876,9 @@ static int smb_set_property(struct power_supply *psy,
{
struct smb_chip *chip = power_supply_get_drvdata(psy);
+ if (!READ_ONCE(chip->probe_complete))
+ return -EAGAIN;
+
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
return regmap_update_bits(chip->regmap, chip->base + USBIN_CMD_IL,
@@ -721,17 +906,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 +960,123 @@ 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);
+
+ return rc;
+}
+
static const struct power_supply_desc smb_psy_desc = {
.name = "pmi8998_charger",
.type = POWER_SUPPLY_TYPE_USB,
@@ -789,19 +1089,65 @@ 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 Type-C; prevent BC1.2 detection from starting on CC. */
+ { .addr = 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
@@ -815,10 +1161,10 @@ static const struct smb_init_register smb_init_seq[] = {
* 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 +1229,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 +1237,85 @@ 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 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 const struct smb_match_data pm7250b_match_data = {
+ .init_seq = smb5_init_seq,
+ .init_seq_len = ARRAY_SIZE(smb5_init_seq),
+ .name = "pm7250b",
+ .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)
+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 +1325,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 +1346,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 +1408,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, ®);
+ 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, ®);
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;
- rc = smb_init_hw(chip);
+ 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 = 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 +1471,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 +1481,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,9 +1511,30 @@ 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);
+ if (chip->gen == SMB2)
+ devm_remove_action(chip->dev, smb_restore_charge_state, chip);
+
+ if (chip->gen == SMB5) {
+ 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");
+
+ devm_remove_action(chip->dev, smb_restore_charge_state, chip);
+ }
platform_set_drvdata(pdev, chip);
+ WRITE_ONCE(chip->probe_complete, true);
/* Initialise charger state */
schedule_delayed_work(&chip->status_change_work, 0);
@@ -1095,8 +1543,10 @@ 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,pm7250b-charger", .data = &pm7250b_match_data },
+ { .compatible = "qcom,pm8150b-charger", .data = &pm8150b_match_data },
{ /* sentinal */ }
};
MODULE_DEVICE_TABLE(of, smb_match_id_table);
@@ -1112,5 +1562,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] 16+ messages in thread
* [PATCH v2 2/2] power: supply: qcom_smbx: add SMB5 support
@ 2026-08-11 23:57 ` Robin Snyders
0 siblings, 0 replies; 16+ messages in thread
From: Robin Snyders @ 2026-08-11 23:57 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,
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, including the
PM8150B-compatible parameter block used by PM7250B. 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.
Program the battery limits from the power-supply registration init
callback before device_add publishes the properties. Gate writable
properties until the IRQ and wake setup and final input and charging
enables are complete, preventing userspace from racing the
suspend-configure-enable sequence.
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 | 766 +++++++++++++++++++++++++++++++--------
2 files changed, 612 insertions(+), 162 deletions(-)
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index b89ef40df7f82..1159a7711919d 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 71f5a48594129..f99982ebb964f 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
@@ -189,9 +202,11 @@
#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 CMD_ICL_OVERRIDE 0x342
+#define ICL_OVERRIDE_BIT BIT(0)
+
#define TYPE_C_CFG 0x358
#define APSD_START_ON_CC_BIT BIT(7)
#define WAIT_FOR_APSD_BIT BIT(6)
@@ -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,14 +401,23 @@ 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
+ * @probe_complete: Whether runtime property writes are safe
* @status_change_work: Worker to handle plug/unplug events
* @cable_irq: USB plugin IRQ
* @wakeup_enabled: If the cable IRQ will cause a wakeup
@@ -394,8 +430,15 @@ 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;
+ bool probe_complete;
struct delayed_work status_change_work;
int cable_irq;
@@ -407,6 +450,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 +483,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 +538,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 +621,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 +658,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 +725,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 +741,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 +807,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 +823,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 +852,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);
@@ -694,6 +876,9 @@ static int smb_set_property(struct power_supply *psy,
{
struct smb_chip *chip = power_supply_get_drvdata(psy);
+ if (!READ_ONCE(chip->probe_complete))
+ return -EAGAIN;
+
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
return regmap_update_bits(chip->regmap, chip->base + USBIN_CMD_IL,
@@ -721,17 +906,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 +960,123 @@ 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);
+
+ return rc;
+}
+
static const struct power_supply_desc smb_psy_desc = {
.name = "pmi8998_charger",
.type = POWER_SUPPLY_TYPE_USB,
@@ -789,19 +1089,65 @@ 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 Type-C; prevent BC1.2 detection from starting on CC. */
+ { .addr = 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
@@ -815,10 +1161,10 @@ static const struct smb_init_register smb_init_seq[] = {
* 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 +1229,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 +1237,85 @@ 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 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 const struct smb_match_data pm7250b_match_data = {
+ .init_seq = smb5_init_seq,
+ .init_seq_len = ARRAY_SIZE(smb5_init_seq),
+ .name = "pm7250b",
+ .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)
+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 +1325,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 +1346,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 +1408,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, ®);
+ 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, ®);
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;
- rc = smb_init_hw(chip);
+ 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 = 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 +1471,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 +1481,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,9 +1511,30 @@ 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);
+ if (chip->gen == SMB2)
+ devm_remove_action(chip->dev, smb_restore_charge_state, chip);
+
+ if (chip->gen == SMB5) {
+ 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");
+
+ devm_remove_action(chip->dev, smb_restore_charge_state, chip);
+ }
platform_set_drvdata(pdev, chip);
+ WRITE_ONCE(chip->probe_complete, true);
/* Initialise charger state */
schedule_delayed_work(&chip->status_change_work, 0);
@@ -1095,8 +1543,10 @@ 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,pm7250b-charger", .data = &pm7250b_match_data },
+ { .compatible = "qcom,pm8150b-charger", .data = &pm8150b_match_data },
{ /* sentinal */ }
};
MODULE_DEVICE_TABLE(of, smb_match_id_table);
@@ -1112,5 +1562,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] 16+ messages in thread
end of thread, other threads:[~2026-08-11 23:57 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-11 20:35 [PATCH 0/3] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
2026-08-11 20:35 ` Robin Snyders
2026-08-11 20:35 ` [PATCH 1/3] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs Robin Snyders via B4 Relay
2026-08-11 20:35 ` Robin Snyders
2026-08-11 20:35 ` [PATCH 2/3] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
2026-08-11 20:35 ` Robin Snyders
2026-08-11 20:51 ` sashiko-bot
2026-08-11 20:35 ` [PATCH 3/3] power: supply: qcom_smbx: program AICL rerun time Robin Snyders via B4 Relay
2026-08-11 20:35 ` Robin Snyders
2026-08-11 20:46 ` sashiko-bot
2026-08-11 23:56 ` [PATCH v2 0/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
2026-08-11 23:56 ` Robin Snyders
2026-08-11 23:56 ` [PATCH v2 1/2] dt-bindings: power: supply: qcom,pmi8998-charger: add SMB5 PMICs Robin Snyders via B4 Relay
2026-08-11 23:56 ` Robin Snyders
2026-08-11 23:57 ` [PATCH v2 2/2] power: supply: qcom_smbx: add SMB5 support Robin Snyders via B4 Relay
2026-08-11 23:57 ` Robin Snyders
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.