Re: [PATCH v4 1/2] power: supply: add Qualcomm PMI8998 SMB2 Charger driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 





On 06/07/2022 20:41, Caleb Connolly wrote:
Add a driver for the SMB2 charger block found in the Qualcomm PMI8998
and PM660.

I noticed an issue in this revision which was the root-cause of the OnePlus 6 charging incredibly slowly. Other SDM845 phones feature a secondary charger IC, usually the smb1135 (iirc) which was able to work normally, hiding the issue, however the OnePlus 6 does not. See below.

Signed-off-by: Caleb Connolly <caleb.connolly@xxxxxxxxxx>
---
Changes since v1:
  * Renamed from qcom_smb2 to qcom_pmi8998_charger
---
  drivers/power/supply/Kconfig                |    9 +
  drivers/power/supply/Makefile               |    1 +
  drivers/power/supply/qcom_pmi8998_charger.c | 1044 +++++++++++++++++++
  3 files changed, 1054 insertions(+)
  create mode 100644 drivers/power/supply/qcom_pmi8998_charger.c

diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 7f02f36aea55..a39e3eda6dbd 100644

[snip]

+
+/* Init sequence derived from vendor downstream driver */
+static const struct smb2_register smb2_init_seq[] = {
+	{ .addr = AICL_RERUN_TIME_CFG, .mask = AICL_RERUN_TIME_MASK, .val = 0 },
+	/*
+	 * By default configure us as an upstream facing port
+	 * FIXME: for OTG we should set UFP_EN_CMD_BIT and DFP_EN_CMD_BIT both to 0
+	 */
+	{ .addr = TYPE_C_INTRPT_ENB_SOFTWARE_CTRL,
+	  .mask = TYPEC_POWER_ROLE_CMD_MASK | VCONN_EN_SRC_BIT |
+		  VCONN_EN_VALUE_BIT,
+	  .val = VCONN_EN_SRC_BIT | UFP_EN_CMD_BIT },
+	/*
+	 * disable Type-C factory mode and stay in Attached.SRC state when VCONN
+	 * over-current happens
+	 */
+	{ .addr = TYPE_C_CFG,
+	  .mask = FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT,
+	  .val = 0 },
+	/* Configure VBUS for software control */
+	{ .addr = OTG_CFG, .mask = OTG_EN_SRC_CFG_BIT, .val = 0 },
+	{ .addr = 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 },
+	/* Enable charging */
+	{ .addr = USBIN_OPTIONS_1_CFG, .mask = HVDCP_EN_BIT, .val = 0 },
+	{ .addr = CHARGING_ENABLE_CMD,
+	  .mask = CHARGING_ENABLE_CMD_BIT,
+	  .val = CHARGING_ENABLE_CMD_BIT },
+	/* Allow overriding the current limit */
+	// { .addr = USBIN_LOAD_CFG,
+	//   .mask = ICL_OVERRIDE_AFTER_APSD_BIT,
+	//   .val = ICL_OVERRIDE_AFTER_APSD_BIT },
+	{ .addr = CHGR_CFG2,
+	  .mask = CHG_EN_SRC_BIT | CHG_EN_POLARITY_BIT |
+		  PRETOFAST_TRANSITION_CFG_BIT | BAT_OV_ECC_BIT | I_TERM_BIT |
+		  AUTO_RECHG_BIT | EN_ANALOG_DROP_IN_VBATT_BIT |
+		  CHARGER_INHIBIT_BIT,
+	  .val = 0 },
+	/*
+	 * No clue what this does
+	 */
+	{ .addr = STAT_CFG,
+	  .mask = STAT_SW_OVERRIDE_CFG_BIT,
+	  .val = STAT_SW_OVERRIDE_CFG_BIT },
+	/*
+	 * Set the default SDP charger type to a 500ma USB 2.0 port
+	 */
+	{ .addr = USBIN_ICL_OPTIONS,
+	  .mask = USB51_MODE_BIT | USBIN_MODE_CHG_BIT,
+	  .val = USB51_MODE_BIT },
+	/*
+	 * 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 },
+	/* OnePlus init stuff from "op_set_collapse_fet" */
+	{ .addr = USBIN_5V_AICL_THRESHOLD_CFG,
+	  .mask = USBIN_5V_AICL_THRESHOLD_CFG_MASK,
+	  .val = 0x3 },
+	{ .addr = USBIN_CONT_AICL_THRESHOLD_CFG,
+	  .mask = USBIN_CONT_AICL_THRESHOLD_CFG_MASK,
+	  .val = 0x3 },
+	/* Yay undocumented register values! */
+	{ .addr = USBIN_LOAD_CFG, .mask = BIT(0) | BIT(1), .val = 0x3 },
+	/* 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_START_AT_MAX_BIT | USBIN_AICL_ADC_EN_BIT |
+		  USBIN_AICL_EN_BIT | SUSPEND_ON_COLLAPSE_USBIN_BIT |
+		  USBIN_HV_COLLAPSE_RESPONSE_BIT |
+		  USBIN_LV_COLLAPSE_RESPONSE_BIT,
+	  .val = USBIN_HV_COLLAPSE_RESPONSE_BIT |
+		 USBIN_LV_COLLAPSE_RESPONSE_BIT | USBIN_AICL_EN_BIT },
+	/*
+	 * Set pre charge current to default, the OnePlus 6 bootloader
+	 * sets this very conservatively.
+	 * NOTE: seems to be reset to zero again anyway after boot
+	 */
+	{ .addr = PRE_CHARGE_CURRENT_CFG,
+	  .mask = PRE_CHARGE_CURRENT_SETTING_MASK,
+	  .val = 500000 / 25000 },
+	/*
+	 * Set "fast charge current" to the default 2A, the OnePlus 6 also
+	 * sets this very conservatively.
+	 * NOTE: seems to be reset to zero again anyway after boot
+	 */
+	{ .addr = FAST_CHARGE_CURRENT_CFG,
+	  .mask = FAST_CHARGE_CURRENT_SETTING_MASK,
+	  .addr = 1950000 / 25000 },

The .addr is set here twice, meaning the fast charge current register is not configured properly and thus left at conservative bootloader values - or set to zero, I'm not sure how the compiler handles this.

Will be fixed in the next revision.
+};
+
+static int smb2_init_hw(struct smb2_chip *chip)
+{
+	int rc, i;
+
+	for (i = 0; i < ARRAY_SIZE(smb2_init_seq); i++) {
+		dev_dbg(chip->dev, "%d: Writing 0x%02x to 0x%02x\n", i,
+			smb2_init_seq[i].val, smb2_init_seq[i].addr);
+		rc = regmap_update_bits(chip->regmap,
+					chip->base + smb2_init_seq[i].addr,
+					smb2_init_seq[i].mask,
+					smb2_init_seq[i].val);
+		if (rc < 0)
+			return dev_err_probe(
+				chip->dev, rc,
+				"%s: Failed to write 0x%02x to 0x%02x\n",
+				__func__, smb2_init_seq[i].val,
+				smb2_init_seq[i].addr);
+	}
+
+	return 0;
+}
+
+struct smb2_irqs {
+	const char *name;
+	irqreturn_t (*handler)(int irq, void *data);
+};
+
+static const struct smb2_irqs irqs[] = {
+	{ .name = "bat-ov", .handler = smb2_handle_batt_overvoltage },
+	{ .name = "usb-plugin", .handler = smb2_handle_usb_plugin },
+	{ .name = "usbin-icl-change", .handler = smb2_handle_usb_icl_change },
+	{ .name = "wdog-bark", .handler = smb2_handle_wdog_bark },
+};
+
+static int smb2_probe(struct platform_device *pdev)
+{
+	struct power_supply_config supply_config = {};
+	struct smb2_chip *chip;
+	int rc, i, irq;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->dev = &pdev->dev;
+	chip->name = pdev->name;
+
+	chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!chip->regmap) {
+		return dev_err_probe(chip->dev, -ENODEV,
+				     "failed to locate the regmap\n");
+	}
+
+	rc = device_property_read_u32(chip->dev, "reg", &chip->base);
+	if (rc < 0) {
+		return dev_err_probe(chip->dev, rc,
+				     "Couldn't read base address\n");
+	}
+
+	chip->usb_in_v_chan = devm_iio_channel_get(chip->dev, "usbin_v");
+	if (IS_ERR(chip->usb_in_v_chan))
+		return dev_err_probe(
+			chip->dev, PTR_ERR(chip->usb_in_v_chan),
+			"Couldn't get usbin_v IIO channel from RRADC\n");
+
+	chip->usb_in_i_chan = devm_iio_channel_get(chip->dev, "usbin_i");
+	if (IS_ERR(chip->usb_in_i_chan)) {
+		return dev_err_probe(
+			chip->dev, PTR_ERR(chip->usb_in_i_chan),
+			"Couldn't get usbin_i IIO channel from RRADC\n");
+	}
+
+	rc = smb2_init_hw(chip);
+	if (rc < 0)
+		return rc;
+
+	supply_config.drv_data = chip;
+	supply_config.of_node = pdev->dev.of_node;
+
+	chip->chg_psy = devm_power_supply_register(chip->dev, &smb2_psy_desc,
+						   &supply_config);
+	if (IS_ERR(chip->chg_psy))
+		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,
+					  smb2_status_change_work);
+	if (rc)
+		return dev_err_probe(
+			chip->dev, rc,
+			"Failed to initialise 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");
+
+	for (i = 0; i < ARRAY_SIZE(irqs); i++) {
+		irq = of_irq_get_byname(pdev->dev.of_node, irqs[i].name);
+		if (irq < 0)
+			return dev_err_probe(chip->dev, irq,
+					     "Couldn't get irq %s byname\n",
+					     irqs[i].name);
+		rc = devm_request_threaded_irq(chip->dev, irq, NULL,
+					       irqs[i].handler, IRQF_ONESHOT,
+					       irqs[i].name, chip);
+		if (rc < 0)
+			return dev_err_probe(chip->dev, irq,
+					     "Couldn't request irq %s\n",
+					     irqs[i].name);
+	}
+
+	platform_set_drvdata(pdev, chip);
+
+	/* Initialise charger state */
+	schedule_delayed_work(&chip->status_change_work, 0);
+
+	return 0;
+}
+
+static const struct of_device_id smb2_match_id_table[] = {
+	{ .compatible = "qcom,pmi8998-charger" },
+	{ .compatible = "qcom,pm660-charger" },
+	{ /* sentinal */ }
+};
+MODULE_DEVICE_TABLE(of, smb2_match_id_table);
+
+static struct platform_driver qcom_spmi_smb2 = {
+	.probe = smb2_probe,
+	.driver = {
+			.name = "qcom-pmi8998-charger",
+			.of_match_table = smb2_match_id_table,
+		},
+};
+
+module_platform_driver(qcom_spmi_smb2);
+
+MODULE_AUTHOR("Caleb Connolly <caleb.connolly@xxxxxxxxxx>");
+MODULE_DESCRIPTION("Qualcomm SMB2 Charger Driver");
+MODULE_LICENSE("GPL");

--
Kind Regards,
Caleb (they/he)



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux