[PATCH 2/3] power: supply: qcom_smbx: add SMB5 support
From: Robin Snyders via B4 Relay
Date: Tue Aug 11 2026 - 16:38:31 EST
From: Casey Connolly <casey.connolly@xxxxxxxxxx>
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@xxxxxxxxxx>
Co-developed-by: Joel Selvaraj <foss@xxxxxxxxxxxxxxxx>
Signed-off-by: Joel Selvaraj <foss@xxxxxxxxxxxxxxxx>
Co-developed-by: Robin Snyders <robin@xxxxxxxxxxx>
Signed-off-by: Robin Snyders <robin@xxxxxxxxxxx>
---
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