[PATCH 2/3] power: supply: qcom_smbx: add SMB5 support
Robin Snyders via B4 Relay <[email protected]>
| Newsgroups | org.kernel.vger.linux-pm,org.kernel.feeds.b4-sent,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Casey Connolly <[email protected]> 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 <[email protected]> Co-developed-by: Joel Selvaraj <[email protected]> Signed-off-by: Joel Selvaraj <[email protected]> Co-developed-by: Robin Snyders <[email protected]> Signed-off-by: Robin Snyders <[email protected]> --- 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