[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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.