From 441d38c60fbe5b416e562853aa3041c7dcaf3af7 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 25 Jan 2022 11:52:45 -0600 Subject: [PATCH 01/94] power: supply: cros_usbpd: Use struct_size() helper in kzalloc() Make use of the struct_size() helper instead of an open-coded version, in order to avoid any potential type mistakes or integer overflows that, in the worst scenario, could lead to heap overflows. Also, address the following sparse warnings: drivers/power/supply/cros_usbpd-charger.c:107:23: warning: using sizeof on a flexible structure Link: https://github.com/KSPP/linux/issues/174 Signed-off-by: Gustavo A. R. Silva Signed-off-by: Sebastian Reichel --- drivers/power/supply/cros_usbpd-charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c index d89e08efd2ad..cadb6a0c2cc7 100644 --- a/drivers/power/supply/cros_usbpd-charger.c +++ b/drivers/power/supply/cros_usbpd-charger.c @@ -104,7 +104,7 @@ static int cros_usbpd_charger_ec_command(struct charger_data *charger, struct cros_ec_command *msg; int ret; - msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL); + msg = kzalloc(struct_size(msg, data, max(outsize, insize)), GFP_KERNEL); if (!msg) return -ENOMEM; From 2b7950c7ac9117a18f1790fcea819cb69380e5bd Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Thu, 27 Jan 2022 22:44:25 +0300 Subject: [PATCH 02/94] power: supply: mp2629_charger: use platform_get_irq() Calling platform_get_irq_optional() doesn't make sense if you then bail out on any error it returns. Switch to calling platform_get_irq() instead and remove dev_err() call as platform_get_irq() already curses loudly on error. Signed-off-by: Sergey Shtylyov Signed-off-by: Sebastian Reichel --- drivers/power/supply/mp2629_charger.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/mp2629_charger.c b/drivers/power/supply/mp2629_charger.c index bdf924b73e47..bf9c27b463a8 100644 --- a/drivers/power/supply/mp2629_charger.c +++ b/drivers/power/supply/mp2629_charger.c @@ -580,11 +580,9 @@ static int mp2629_charger_probe(struct platform_device *pdev) charger->dev = dev; platform_set_drvdata(pdev, charger); - irq = platform_get_irq_optional(to_platform_device(dev->parent), 0); - if (irq < 0) { - dev_err(dev, "get irq fail: %d\n", irq); + irq = platform_get_irq(to_platform_device(dev->parent), 0); + if (irq < 0) return irq; - } for (i = 0; i < MP2629_MAX_FIELD; i++) { charger->regmap_fields[i] = devm_regmap_field_alloc(dev, From ba18dad0fb880cd29aa97b6b75560ef14d1061ba Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Thu, 20 Jan 2022 10:46:54 +0000 Subject: [PATCH 03/94] power: reset: gemini-poweroff: Fix IRQ check in gemini_poweroff_probe platform_get_irq() returns negative error number instead 0 on failure. And the doc of platform_get_irq() provides a usage example: int irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; Fix the check of return value to catch errors correctly. Fixes: f7a388d6cd1c ("power: reset: Add a driver for the Gemini poweroff") Signed-off-by: Miaoqian Lin Reviewed-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/reset/gemini-poweroff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/reset/gemini-poweroff.c b/drivers/power/reset/gemini-poweroff.c index 90e35c07240a..b7f7a8225f22 100644 --- a/drivers/power/reset/gemini-poweroff.c +++ b/drivers/power/reset/gemini-poweroff.c @@ -107,8 +107,8 @@ static int gemini_poweroff_probe(struct platform_device *pdev) return PTR_ERR(gpw->base); irq = platform_get_irq(pdev, 0); - if (!irq) - return -EINVAL; + if (irq < 0) + return irq; gpw->dev = dev; From d4f408cdcd26921c1268cb8dcbe8ffb6faf837f3 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Wed, 12 Jan 2022 11:47:27 +0300 Subject: [PATCH 04/94] power: supply: axp20x_battery: properly report current when discharging As stated in [1], negative current values are used for discharging batteries. AXP PMICs internally have two different ADC channels for shunt current measurement: one used during charging and one during discharging. The values reported by these ADCs are unsigned. While the driver properly selects ADC channel to get the data from, it doesn't apply negative sign when reporting discharging current. [1] Documentation/ABI/testing/sysfs-class-power Signed-off-by: Evgeny Boger Acked-by: Chen-Yu Tsai Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_battery.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index 5d197141f476..9106077c0dbb 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -186,7 +186,6 @@ static int axp20x_battery_get_prop(struct power_supply *psy, union power_supply_propval *val) { struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); - struct iio_channel *chan; int ret = 0, reg, val1; switch (psp) { @@ -266,12 +265,12 @@ static int axp20x_battery_get_prop(struct power_supply *psy, if (ret) return ret; - if (reg & AXP20X_PWR_STATUS_BAT_CHARGING) - chan = axp20x_batt->batt_chrg_i; - else - chan = axp20x_batt->batt_dischrg_i; - - ret = iio_read_channel_processed(chan, &val->intval); + if (reg & AXP20X_PWR_STATUS_BAT_CHARGING) { + ret = iio_read_channel_processed(axp20x_batt->batt_chrg_i, &val->intval); + } else { + ret = iio_read_channel_processed(axp20x_batt->batt_dischrg_i, &val1); + val->intval = -val1; + } if (ret) return ret; From fd46821e85de90fb5b7356251164af4815b6e3f6 Mon Sep 17 00:00:00 2001 From: Carl Philipp Klemm Date: Wed, 29 Dec 2021 01:08:53 +0100 Subject: [PATCH 05/94] power: supply: cpcap-battery: Add battery type auto detection for mapphone devices This allows cpcap-battery to detect whitch battery is inserted, HW4X or BW8X for xt875 and EB41 for xt894 by examining the battery nvmem. If no known battery is detected sane defaults are used. Signed-off-by: Carl Philipp Klemm Signed-off-by: Sebastian Reichel --- drivers/power/supply/cpcap-battery.c | 118 +++++++++++++++++++++------ 1 file changed, 92 insertions(+), 26 deletions(-) diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c index 18e3ff0e15d5..ae284bdd6cc3 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -73,6 +74,9 @@ #define CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS 250 +#define CPCAP_BATTERY_EB41_HW4X_ID 0x9E +#define CPCAP_BATTERY_BW8X_ID 0x98 + enum { CPCAP_BATTERY_IIO_BATTDET, CPCAP_BATTERY_IIO_VOLTAGE, @@ -138,6 +142,7 @@ struct cpcap_battery_ddata { int charge_full; int status; u16 vendor; + bool check_nvmem; unsigned int is_full:1; }; @@ -354,6 +359,88 @@ cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata, ccd->offset); } + +/* + * Based on the values from Motorola mapphone Linux kernel for the + * stock Droid 4 battery eb41. In the Motorola mapphone Linux + * kernel tree the value for pm_cd_factor is passed to the kernel + * via device tree. If it turns out to be something device specific + * we can consider that too later. These values are also fine for + * Bionic's hw4x. + * + * And looking at the battery full and shutdown values for the stock + * kernel on droid 4, full is 4351000 and software initiates shutdown + * at 3078000. The device will die around 2743000. + */ +static const struct cpcap_battery_config cpcap_battery_eb41_data = { + .cd_factor = 0x3cc, + .info.technology = POWER_SUPPLY_TECHNOLOGY_LION, + .info.voltage_max_design = 4351000, + .info.voltage_min_design = 3100000, + .info.charge_full_design = 1740000, + .bat.constant_charge_voltage_max_uv = 4200000, +}; + +/* Values for the extended Droid Bionic battery bw8x. */ +static const struct cpcap_battery_config cpcap_battery_bw8x_data = { + .cd_factor = 0x3cc, + .info.technology = POWER_SUPPLY_TECHNOLOGY_LION, + .info.voltage_max_design = 4200000, + .info.voltage_min_design = 3200000, + .info.charge_full_design = 2760000, + .bat.constant_charge_voltage_max_uv = 4200000, +}; + +/* + * Safe values for any lipo battery likely to fit into a mapphone + * battery bay. + */ +static const struct cpcap_battery_config cpcap_battery_unkown_data = { + .cd_factor = 0x3cc, + .info.technology = POWER_SUPPLY_TECHNOLOGY_LION, + .info.voltage_max_design = 4200000, + .info.voltage_min_design = 3200000, + .info.charge_full_design = 3000000, + .bat.constant_charge_voltage_max_uv = 4200000, +}; + +static int cpcap_battery_match_nvmem(struct device *dev, const void *data) +{ + if (strcmp(dev_name(dev), "89-500029ba0f73") == 0) + return 1; + else + return 0; +} + +static void cpcap_battery_detect_battery_type(struct cpcap_battery_ddata *ddata) +{ + struct nvmem_device *nvmem; + u8 battery_id = 0; + + ddata->check_nvmem = false; + + nvmem = nvmem_device_find(NULL, &cpcap_battery_match_nvmem); + if (IS_ERR_OR_NULL(nvmem)) { + ddata->check_nvmem = true; + dev_info_once(ddata->dev, "Can not find battery nvmem device. Assuming generic lipo battery\n"); + } else if (nvmem_device_read(nvmem, 2, 1, &battery_id) < 0) { + battery_id = 0; + ddata->check_nvmem = true; + dev_warn(ddata->dev, "Can not read battery nvmem device. Assuming generic lipo battery\n"); + } + + switch (battery_id) { + case CPCAP_BATTERY_EB41_HW4X_ID: + ddata->config = cpcap_battery_eb41_data; + break; + case CPCAP_BATTERY_BW8X_ID: + ddata->config = cpcap_battery_bw8x_data; + break; + default: + ddata->config = cpcap_battery_unkown_data; + } +} + /** * cpcap_battery_cc_get_avg_current - read cpcap coulumb counter * @ddata: cpcap battery driver device data @@ -571,6 +658,9 @@ static int cpcap_battery_get_property(struct power_supply *psy, latest = cpcap_battery_latest(ddata); previous = cpcap_battery_previous(ddata); + if (ddata->check_nvmem) + cpcap_battery_detect_battery_type(ddata); + switch (psp) { case POWER_SUPPLY_PROP_PRESENT: if (latest->temperature > CPCAP_NO_BATTERY || ignore_temperature_probe) @@ -982,30 +1072,10 @@ restore: return error; } -/* - * Based on the values from Motorola mapphone Linux kernel. In the - * the Motorola mapphone Linux kernel tree the value for pm_cd_factor - * is passed to the kernel via device tree. If it turns out to be - * something device specific we can consider that too later. - * - * And looking at the battery full and shutdown values for the stock - * kernel on droid 4, full is 4351000 and software initiates shutdown - * at 3078000. The device will die around 2743000. - */ -static const struct cpcap_battery_config cpcap_battery_default_data = { - .cd_factor = 0x3cc, - .info.technology = POWER_SUPPLY_TECHNOLOGY_LION, - .info.voltage_max_design = 4351000, - .info.voltage_min_design = 3100000, - .info.charge_full_design = 1740000, - .bat.constant_charge_voltage_max_uv = 4200000, -}; - #ifdef CONFIG_OF static const struct of_device_id cpcap_battery_id_table[] = { { .compatible = "motorola,cpcap-battery", - .data = &cpcap_battery_default_data, }, {}, }; @@ -1028,19 +1098,15 @@ static int cpcap_battery_probe(struct platform_device *pdev) struct cpcap_battery_ddata *ddata; struct power_supply_config psy_cfg = {}; int error; - const struct cpcap_battery_config *cfg; - - cfg = device_get_match_data(&pdev->dev); - if (!cfg) - return -ENODEV; ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); if (!ddata) return -ENOMEM; + cpcap_battery_detect_battery_type(ddata); + INIT_LIST_HEAD(&ddata->irq_list); ddata->dev = &pdev->dev; - memcpy(&ddata->config, cfg, sizeof(ddata->config)); ddata->reg = dev_get_regmap(ddata->dev->parent, NULL); if (!ddata->reg) From b2657167447a059990827809e5ef3e6a56c700d8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 6 Jan 2022 12:06:02 +0100 Subject: [PATCH 06/94] power: supply: axp288_fuel_gauge: Add dev helper var to probe() Add a dev local variable to probe() as shortcut for &pdev->dev, this is a preparation change for making more use of devm managed resources. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index c1da217fdb0e..1495402f440c 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -622,16 +622,17 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) [BAT_D_CURR] = "axp288-chrg-d-curr", [BAT_VOLT] = "axp288-batt-volt", }; + struct device *dev = &pdev->dev; unsigned int val; if (dmi_check_system(axp288_no_battery_list)) return -ENODEV; - info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; - info->dev = &pdev->dev; + info->dev = dev; info->regmap = axp20x->regmap; info->regmap_irqc = axp20x->regmap_irqc; info->status = POWER_SUPPLY_STATUS_UNKNOWN; @@ -651,8 +652,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) iio_channel_get(NULL, iio_chan_name[i]); if (IS_ERR(info->iio_channel[i])) { ret = PTR_ERR(info->iio_channel[i]); - dev_dbg(&pdev->dev, "error getting iiochan %s: %d\n", - iio_chan_name[i], ret); + dev_dbg(dev, "error getting iiochan %s: %d\n", iio_chan_name[i], ret); /* Wait for axp288_adc to load */ if (ret == -ENODEV) ret = -EPROBE_DEFER; @@ -722,10 +722,10 @@ unblock_punit_i2c_access: goto out_free_iio_chan; psy_cfg.drv_data = info; - info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg); + info->bat = power_supply_register(dev, &fuel_gauge_desc, &psy_cfg); if (IS_ERR(info->bat)) { ret = PTR_ERR(info->bat); - dev_err(&pdev->dev, "failed to register battery: %d\n", ret); + dev_err(dev, "failed to register battery: %d\n", ret); goto out_free_iio_chan; } From 0b80eb6c3832447a28634dc2611cd2a8f53aa189 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 6 Jan 2022 12:06:03 +0100 Subject: [PATCH 07/94] power: supply: axp288_fuel_gauge: Add axp288_fuel_gauge_read_initial_regs() Refactor probe a bit, introducing a new axp288_fuel_gauge_read_initial_regs() helper. This replaces a whole bunch of gotos and removes the unblock_punit_i2c_access label. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 109 ++++++++++++----------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 1495402f440c..35f9edf3da09 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -611,6 +611,61 @@ static const struct dmi_system_id axp288_no_battery_list[] = { {} }; +static int axp288_fuel_gauge_read_initial_regs(struct axp288_fg_info *info) +{ + unsigned int val; + int ret; + + /* + * On some devices the fuelgauge and charger parts of the axp288 are + * not used, check that the fuelgauge is enabled (CC_CTRL != 0). + */ + ret = regmap_read(info->regmap, AXP20X_CC_CTRL, &val); + if (ret < 0) + return ret; + if (val == 0) + return -ENODEV; + + ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG); + if (ret < 0) + return ret; + + if (!(ret & FG_DES_CAP1_VALID)) { + dev_err(info->dev, "axp288 not configured by firmware\n"); + return -ENODEV; + } + + ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1); + if (ret < 0) + return ret; + switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) { + case CHRG_CCCV_CV_4100MV: + info->max_volt = 4100; + break; + case CHRG_CCCV_CV_4150MV: + info->max_volt = 4150; + break; + case CHRG_CCCV_CV_4200MV: + info->max_volt = 4200; + break; + case CHRG_CCCV_CV_4350MV: + info->max_volt = 4350; + break; + } + + ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE); + if (ret < 0) + return ret; + info->pwr_op = ret; + + ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); + if (ret < 0) + return ret; + info->low_cap = ret; + + return 0; +} + static int axp288_fuel_gauge_probe(struct platform_device *pdev) { int i, ret = 0; @@ -623,7 +678,6 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) [BAT_VOLT] = "axp288-batt-volt", }; struct device *dev = &pdev->dev; - unsigned int val; if (dmi_check_system(axp288_no_battery_list)) return -ENODEV; @@ -665,59 +719,8 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) if (ret < 0) goto out_free_iio_chan; - /* - * On some devices the fuelgauge and charger parts of the axp288 are - * not used, check that the fuelgauge is enabled (CC_CTRL != 0). - */ - ret = regmap_read(axp20x->regmap, AXP20X_CC_CTRL, &val); - if (ret < 0) - goto unblock_punit_i2c_access; - if (val == 0) { - ret = -ENODEV; - goto unblock_punit_i2c_access; - } - - ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG); - if (ret < 0) - goto unblock_punit_i2c_access; - - if (!(ret & FG_DES_CAP1_VALID)) { - dev_err(&pdev->dev, "axp288 not configured by firmware\n"); - ret = -ENODEV; - goto unblock_punit_i2c_access; - } - - ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1); - if (ret < 0) - goto unblock_punit_i2c_access; - switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) { - case CHRG_CCCV_CV_4100MV: - info->max_volt = 4100; - break; - case CHRG_CCCV_CV_4150MV: - info->max_volt = 4150; - break; - case CHRG_CCCV_CV_4200MV: - info->max_volt = 4200; - break; - case CHRG_CCCV_CV_4350MV: - info->max_volt = 4350; - break; - } - - ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE); - if (ret < 0) - goto unblock_punit_i2c_access; - info->pwr_op = ret; - - ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); - if (ret < 0) - goto unblock_punit_i2c_access; - info->low_cap = ret; - -unblock_punit_i2c_access: + ret = axp288_fuel_gauge_read_initial_regs(info); iosf_mbi_unblock_punit_i2c_access(); - /* In case we arrive here by goto because of a register access error */ if (ret < 0) goto out_free_iio_chan; From 360108661277a784aacb22913b853e9b0fc4de1c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 6 Jan 2022 12:06:04 +0100 Subject: [PATCH 08/94] power: supply: axp288_fuel_gauge: Use devm_add_action_or_reset() for iio chan release An existing comment already mentions we: "cannot use devm_iio_channel_get because x86 systems lack the device<->channel maps which iio_channel_get will try to use when passed a non NULL device pointer". Work around this by registering a devm action to free the iio-channels. This is a step on the way to fully converting the probe() function to only use devm managed resources. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 33 ++++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 35f9edf3da09..aaf2d5542316 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -666,6 +666,16 @@ static int axp288_fuel_gauge_read_initial_regs(struct axp288_fg_info *info) return 0; } +static void axp288_fuel_gauge_release_iio_chans(void *data) +{ + struct axp288_fg_info *info = data; + int i; + + for (i = 0; i < IIO_CHANNEL_NUM; i++) + if (!IS_ERR_OR_NULL(info->iio_channel[i])) + iio_channel_release(info->iio_channel[i]); +} + static int axp288_fuel_gauge_probe(struct platform_device *pdev) { int i, ret = 0; @@ -711,37 +721,35 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) if (ret == -ENODEV) ret = -EPROBE_DEFER; - goto out_free_iio_chan; + axp288_fuel_gauge_release_iio_chans(info); + return ret; } } + ret = devm_add_action_or_reset(dev, axp288_fuel_gauge_release_iio_chans, info); + if (ret) + return ret; + ret = iosf_mbi_block_punit_i2c_access(); if (ret < 0) - goto out_free_iio_chan; + return ret; ret = axp288_fuel_gauge_read_initial_regs(info); iosf_mbi_unblock_punit_i2c_access(); if (ret < 0) - goto out_free_iio_chan; + return ret; psy_cfg.drv_data = info; info->bat = power_supply_register(dev, &fuel_gauge_desc, &psy_cfg); if (IS_ERR(info->bat)) { ret = PTR_ERR(info->bat); dev_err(dev, "failed to register battery: %d\n", ret); - goto out_free_iio_chan; + return ret; } fuel_gauge_init_irq(info, pdev); return 0; - -out_free_iio_chan: - for (i = 0; i < IIO_CHANNEL_NUM; i++) - if (!IS_ERR_OR_NULL(info->iio_channel[i])) - iio_channel_release(info->iio_channel[i]); - - return ret; } static const struct platform_device_id axp288_fg_id_table[] = { @@ -761,9 +769,6 @@ static int axp288_fuel_gauge_remove(struct platform_device *pdev) if (info->irq[i] >= 0) free_irq(info->irq[i], info); - for (i = 0; i < IIO_CHANNEL_NUM; i++) - iio_channel_release(info->iio_channel[i]); - return 0; } From f1b7e0881fe12ebd2d95f55ebf4fcf57566a53b6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 6 Jan 2022 12:06:05 +0100 Subject: [PATCH 09/94] power: supply: axp288_fuel_gauge: Use devm_power_supply_register() Use devm_power_supply_register() instead of power_supply_register(). Note as a side-effect this changes the release order so that now first the IRQs get free-ed and then the psy gets unregistered. This is actually a bug-fix since this fixes the IRQ possibly trying to reference the unregistered psy. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index aaf2d5542316..cefde85e3309 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -740,7 +740,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) return ret; psy_cfg.drv_data = info; - info->bat = power_supply_register(dev, &fuel_gauge_desc, &psy_cfg); + info->bat = devm_power_supply_register(dev, &fuel_gauge_desc, &psy_cfg); if (IS_ERR(info->bat)) { ret = PTR_ERR(info->bat); dev_err(dev, "failed to register battery: %d\n", ret); @@ -763,8 +763,6 @@ static int axp288_fuel_gauge_remove(struct platform_device *pdev) struct axp288_fg_info *info = platform_get_drvdata(pdev); int i; - power_supply_unregister(info->bat); - for (i = 0; i < AXP288_FG_INTR_NUM; i++) if (info->irq[i] >= 0) free_irq(info->irq[i], info); From 05972f53a12fe97ac2bb4d56dfed730e93040d89 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 6 Jan 2022 12:06:06 +0100 Subject: [PATCH 10/94] power: supply: axp288_fuel_gauge: Refactor IRQ initialization Refactor the IRQ initialization code: * Move the looking up of the vIRQs to the beginning of probe(), failing probe early if this fails * Do the actual requesting of IRQs inline in probe() and properly abort probe() on errors * Use devm_request_threaded_irq(), completing the conversion of probe() to only use devm managed resources and remove the remove() driver function. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 66 ++++++------------------ 1 file changed, 17 insertions(+), 49 deletions(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index cefde85e3309..f7dce029266a 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -107,7 +107,6 @@ enum { struct axp288_fg_info { struct device *dev; struct regmap *regmap; - struct regmap_irq_chip_data *regmap_irqc; int irq[AXP288_FG_INTR_NUM]; struct iio_channel *iio_channel[IIO_CHANNEL_NUM]; struct power_supply *bat; @@ -502,38 +501,6 @@ static const struct power_supply_desc fuel_gauge_desc = { .external_power_changed = fuel_gauge_external_power_changed, }; -static void fuel_gauge_init_irq(struct axp288_fg_info *info, struct platform_device *pdev) -{ - int ret, i, pirq; - - for (i = 0; i < AXP288_FG_INTR_NUM; i++) { - pirq = platform_get_irq(pdev, i); - info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); - if (info->irq[i] < 0) { - dev_warn(info->dev, "regmap_irq get virq failed for IRQ %d: %d\n", - pirq, info->irq[i]); - info->irq[i] = -1; - goto intr_failed; - } - ret = request_threaded_irq(info->irq[i], - NULL, fuel_gauge_thread_handler, - IRQF_ONESHOT, DEV_NAME, info); - if (ret) { - dev_warn(info->dev, "request irq failed for IRQ %d: %d\n", - pirq, info->irq[i]); - info->irq[i] = -1; - goto intr_failed; - } - } - return; - -intr_failed: - for (; i > 0; i--) { - free_irq(info->irq[i - 1], info); - info->irq[i - 1] = -1; - } -} - /* * Some devices have no battery (HDMI sticks) and the axp288 battery's * detection reports one despite it not being there. @@ -678,7 +645,6 @@ static void axp288_fuel_gauge_release_iio_chans(void *data) static int axp288_fuel_gauge_probe(struct platform_device *pdev) { - int i, ret = 0; struct axp288_fg_info *info; struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); struct power_supply_config psy_cfg = {}; @@ -688,6 +654,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) [BAT_VOLT] = "axp288-batt-volt", }; struct device *dev = &pdev->dev; + int i, pirq, ret; if (dmi_check_system(axp288_no_battery_list)) return -ENODEV; @@ -698,7 +665,6 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) info->dev = dev; info->regmap = axp20x->regmap; - info->regmap_irqc = axp20x->regmap_irqc; info->status = POWER_SUPPLY_STATUS_UNKNOWN; info->valid = 0; @@ -706,6 +672,15 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) mutex_init(&info->lock); + for (i = 0; i < AXP288_FG_INTR_NUM; i++) { + pirq = platform_get_irq(pdev, i); + ret = regmap_irq_get_virq(axp20x->regmap_irqc, pirq); + if (ret < 0) + return dev_err_probe(dev, ret, "getting vIRQ %d\n", pirq); + + info->irq[i] = ret; + } + for (i = 0; i < IIO_CHANNEL_NUM; i++) { /* * Note cannot use devm_iio_channel_get because x86 systems @@ -747,7 +722,13 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) return ret; } - fuel_gauge_init_irq(info, pdev); + for (i = 0; i < AXP288_FG_INTR_NUM; i++) { + ret = devm_request_threaded_irq(dev, info->irq[i], NULL, + fuel_gauge_thread_handler, + IRQF_ONESHOT, DEV_NAME, info); + if (ret) + return dev_err_probe(dev, ret, "requesting IRQ %d\n", info->irq[i]); + } return 0; } @@ -758,21 +739,8 @@ static const struct platform_device_id axp288_fg_id_table[] = { }; MODULE_DEVICE_TABLE(platform, axp288_fg_id_table); -static int axp288_fuel_gauge_remove(struct platform_device *pdev) -{ - struct axp288_fg_info *info = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < AXP288_FG_INTR_NUM; i++) - if (info->irq[i] >= 0) - free_irq(info->irq[i], info); - - return 0; -} - static struct platform_driver axp288_fuel_gauge_driver = { .probe = axp288_fuel_gauge_probe, - .remove = axp288_fuel_gauge_remove, .id_table = axp288_fg_id_table, .driver = { .name = DEV_NAME, From 30abb3d07929137bf72327560e1595508a692c4e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 6 Jan 2022 12:06:07 +0100 Subject: [PATCH 11/94] power: supply: axp288_fuel_gauge: Take lock before updating the valid flag The valid flag is protected by the mutex, so code clearing it should take the mutex before cleating the valid flag. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index f7dce029266a..53d0e82bbb3e 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -476,7 +476,9 @@ static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev) dev_warn(info->dev, "Spurious Interrupt!!!\n"); } + mutex_lock(&info->lock); info->valid = 0; /* Force updating of the cached registers */ + mutex_unlock(&info->lock); power_supply_changed(info->bat); return IRQ_HANDLED; @@ -486,7 +488,9 @@ static void fuel_gauge_external_power_changed(struct power_supply *psy) { struct axp288_fg_info *info = power_supply_get_drvdata(psy); + mutex_lock(&info->lock); info->valid = 0; /* Force updating of the cached registers */ + mutex_unlock(&info->lock); power_supply_changed(info->bat); } From 210bc22c5d3d894cd032e0957035933945266d71 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 6 Jan 2022 12:06:08 +0100 Subject: [PATCH 12/94] power: supply: axp288_fuel_gauge: Add a no_current_sense_res module_param Some boards with an AXP288 fuel-gauge appear to have a broken (approx. 2 milli-ohm instead of 10) current sense resistor. This makes the coulomb-counter part of the fuel-gauge useless, but the OCV based capacity reporting is still working. Add a no_current_sense_res module_param to disable use of the coulomb-counter using parts of the fuel-gauge to allow users to work around this. Note this is a module parameter and not done through DMI quirks, since this seems to be a defect on some boards, not something which all boards of the same model share. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 33 +++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 53d0e82bbb3e..dcedbc59732d 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -88,6 +88,11 @@ #define AXP288_REG_UPDATE_INTERVAL (60 * HZ) #define AXP288_FG_INTR_NUM 6 + +static bool no_current_sense_res; +module_param(no_current_sense_res, bool, 0444); +MODULE_PARM_DESC(no_current_sense_res, "No (or broken) current sense resisitor"); + enum { QWBTU_IRQ = 0, WBTU_IRQ, @@ -137,12 +142,13 @@ static enum power_supply_property fuel_gauge_props[] = { POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_OCV, - POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, POWER_SUPPLY_PROP_TECHNOLOGY, + /* The 3 props below are not used when no_current_sense_res is set */ POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, }; static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg) @@ -224,7 +230,10 @@ static int fuel_gauge_update_registers(struct axp288_fg_info *info) goto out; info->pwr_stat = ret; - ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); + if (no_current_sense_res) + ret = fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG); + else + ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); if (ret < 0) goto out; info->fg_res = ret; @@ -233,6 +242,14 @@ static int fuel_gauge_update_registers(struct axp288_fg_info *info) if (ret < 0) goto out; + ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG); + if (ret < 0) + goto out; + info->ocv = ret; + + if (no_current_sense_res) + goto out_no_current_sense_res; + if (info->pwr_stat & PS_STAT_BAT_CHRG_DIR) { info->d_curr = 0; ret = iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], &info->c_curr); @@ -245,11 +262,6 @@ static int fuel_gauge_update_registers(struct axp288_fg_info *info) goto out; } - ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG); - if (ret < 0) - goto out; - info->ocv = ret; - ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG); if (ret < 0) goto out; @@ -260,6 +272,7 @@ static int fuel_gauge_update_registers(struct axp288_fg_info *info) goto out; info->fg_des_cap1 = ret; +out_no_current_sense_res: info->last_updated = jiffies; info->valid = 1; ret = 0; @@ -292,7 +305,7 @@ static void fuel_gauge_get_status(struct axp288_fg_info *info) * When this happens the AXP288 reports a not-charging status and * 0 mA discharge current. */ - if (fg_res < 90 || (pwr_stat & PS_STAT_BAT_CHRG_DIR)) + if (fg_res < 90 || (pwr_stat & PS_STAT_BAT_CHRG_DIR) || no_current_sense_res) goto not_full; if (curr == 0) { @@ -494,7 +507,7 @@ static void fuel_gauge_external_power_changed(struct power_supply *psy) power_supply_changed(info->bat); } -static const struct power_supply_desc fuel_gauge_desc = { +static struct power_supply_desc fuel_gauge_desc = { .name = DEV_NAME, .type = POWER_SUPPLY_TYPE_BATTERY, .properties = fuel_gauge_props, @@ -719,6 +732,8 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) return ret; psy_cfg.drv_data = info; + if (no_current_sense_res) + fuel_gauge_desc.num_properties = ARRAY_SIZE(fuel_gauge_props) - 3; info->bat = devm_power_supply_register(dev, &fuel_gauge_desc, &psy_cfg); if (IS_ERR(info->bat)) { ret = PTR_ERR(info->bat); From 2220af8ca61ae67de4ec3deec1c6395a2f65b9fd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:47 +0100 Subject: [PATCH 13/94] power: supply: core: Refactor power_supply_set_input_current_limit_from_supplier() Some (USB) charger ICs have variants with USB D+ and D- pins to do their own builtin charger-type detection, like e.g. the bq24190 and bq25890 and also variants which lack this functionality, e.g. the bq24192 and bq25892. In case the charger-type; and thus the input-current-limit detection is done outside the charger IC then we need some way to communicate this to the charger IC. In the past extcon was used for this, but if the external detection does e.g. full USB PD negotiation then the extcon cable-types do not convey enough information. For these setups it was decided to model the external charging "brick" and the parameters negotiated with it as a power_supply class-device itself; and power_supply_set_input_current_limit_from_supplier() was introduced to allow drivers to get the input-current-limit this way. But in some cases psy drivers may want to know other properties, e.g. the bq25892 can do "quick-charge" negotiation by pulsing its current draw, but this should only be done if the usb_type psy-property of its supplier is set to DCP (and device-properties indicate the board allows higher voltages). Instead of adding extra helper functions for each property which a psy-driver wants to query from its supplier, refactor power_supply_set_input_current_limit_from_supplier() into a more generic power_supply_get_property_from_supplier() function. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 12 ++++- drivers/power/supply/power_supply_core.c | 57 +++++++++++++----------- include/linux/power_supply.h | 5 ++- 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 06c34b09349c..a1c957a26f07 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -1206,8 +1206,18 @@ static void bq24190_input_current_limit_work(struct work_struct *work) struct bq24190_dev_info *bdi = container_of(work, struct bq24190_dev_info, input_current_limit_work.work); + union power_supply_propval val; + int ret; - power_supply_set_input_current_limit_from_supplier(bdi->charger); + ret = power_supply_get_property_from_supplier(bdi->charger, + POWER_SUPPLY_PROP_CURRENT_MAX, + &val); + if (ret) + return; + + bq24190_charger_set_property(bdi->charger, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + &val); } /* Sync the input-current-limit with our parent supply (if we have one) */ diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index ec838c9bcc0a..df4471e50d33 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -376,46 +376,49 @@ int power_supply_is_system_supplied(void) } EXPORT_SYMBOL_GPL(power_supply_is_system_supplied); -static int __power_supply_get_supplier_max_current(struct device *dev, - void *data) +struct psy_get_supplier_prop_data { + struct power_supply *psy; + enum power_supply_property psp; + union power_supply_propval *val; +}; + +static int __power_supply_get_supplier_property(struct device *dev, void *_data) { - union power_supply_propval ret = {0,}; struct power_supply *epsy = dev_get_drvdata(dev); - struct power_supply *psy = data; + struct psy_get_supplier_prop_data *data = _data; - if (__power_supply_is_supplied_by(epsy, psy)) - if (!epsy->desc->get_property(epsy, - POWER_SUPPLY_PROP_CURRENT_MAX, - &ret)) - return ret.intval; + if (__power_supply_is_supplied_by(epsy, data->psy)) + if (!epsy->desc->get_property(epsy, data->psp, data->val)) + return 1; /* Success */ - return 0; + return 0; /* Continue iterating */ } -int power_supply_set_input_current_limit_from_supplier(struct power_supply *psy) +int power_supply_get_property_from_supplier(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) { - union power_supply_propval val = {0,}; - int curr; - - if (!psy->desc->set_property) - return -EINVAL; + struct psy_get_supplier_prop_data data = { + .psy = psy, + .psp = psp, + .val = val, + }; + int ret; /* * This function is not intended for use with a supply with multiple - * suppliers, we simply pick the first supply to report a non 0 - * max-current. + * suppliers, we simply pick the first supply to report the psp. */ - curr = class_for_each_device(power_supply_class, NULL, psy, - __power_supply_get_supplier_max_current); - if (curr <= 0) - return (curr == 0) ? -ENODEV : curr; + ret = class_for_each_device(power_supply_class, NULL, &data, + __power_supply_get_supplier_property); + if (ret < 0) + return ret; + if (ret == 0) + return -ENODEV; - val.intval = curr; - - return psy->desc->set_property(psy, - POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); + return 0; } -EXPORT_SYMBOL_GPL(power_supply_set_input_current_limit_from_supplier); +EXPORT_SYMBOL_GPL(power_supply_get_property_from_supplier); int power_supply_set_battery_charged(struct power_supply *psy) { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index e218041cc000..006111917d1a 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -597,8 +597,9 @@ power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table int table_len, int temp); extern void power_supply_changed(struct power_supply *psy); extern int power_supply_am_i_supplied(struct power_supply *psy); -extern int power_supply_set_input_current_limit_from_supplier( - struct power_supply *psy); +int power_supply_get_property_from_supplier(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); extern int power_supply_set_battery_charged(struct power_supply *psy); #ifdef CONFIG_POWER_SUPPLY From 766873c139a9185f2ee33c434df85140d67356d1 Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Tue, 1 Feb 2022 14:06:48 +0100 Subject: [PATCH 14/94] power: supply: bq25890: Rename IILIM field to IINLIM Rename the Input Current Limit field in the REG00 from IILIM to IINLIM accordingly with the bq2589x datasheet. This is just cosmetical change to reduce confusion. Signed-off-by: Yauhen Kharuzhy Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index e62da9dc4f35..fb987579d05a 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -40,7 +40,7 @@ static const char *const bq25890_chip_name[] = { }; enum bq25890_fields { - F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */ + F_EN_HIZ, F_EN_ILIM, F_IINLIM, /* Reg00 */ F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */ F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN, F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN, /* Reg02 */ @@ -153,7 +153,7 @@ static const struct reg_field bq25890_reg_fields[] = { /* REG00 */ [F_EN_HIZ] = REG_FIELD(0x00, 7, 7), [F_EN_ILIM] = REG_FIELD(0x00, 6, 6), - [F_IILIM] = REG_FIELD(0x00, 0, 5), + [F_IINLIM] = REG_FIELD(0x00, 0, 5), /* REG01 */ [F_BHOT] = REG_FIELD(0x01, 6, 7), [F_BCOLD] = REG_FIELD(0x01, 5, 5), @@ -256,7 +256,7 @@ enum bq25890_table_ids { /* range tables */ TBL_ICHG, TBL_ITERM, - TBL_IILIM, + TBL_IINLIM, TBL_VREG, TBL_BOOSTV, TBL_SYSVMIN, @@ -322,7 +322,7 @@ static const union { /* TODO: BQ25896 has max ICHG 3008 mA */ [TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */ [TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */ - [TBL_IILIM] = { .rt = {100000, 3250000, 50000} }, /* uA */ + [TBL_IINLIM] = { .rt = {100000, 3250000, 50000} }, /* uA */ [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */ [TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */ [TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */ @@ -528,11 +528,11 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - ret = bq25890_field_read(bq, F_IILIM); + ret = bq25890_field_read(bq, F_IINLIM); if (ret < 0) return ret; - val->intval = bq25890_find_val(ret, TBL_IILIM); + val->intval = bq25890_find_val(ret, TBL_IINLIM); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: From c562a43a460fbd582fba0e7e4d3f09cef48d65b1 Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Tue, 1 Feb 2022 14:06:49 +0100 Subject: [PATCH 15/94] power: supply: bq25890: Reduce reported CONSTANT_CHARGE_CURRENT_MAX for low temperatures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Take into account possible current reduction due to low-temperature when reading POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX. As described in the datasheet in cool (0-20° Celcius) conditions the current limit is decreased to 20% or 50% of ICHG field value depended on JEITA_ISET field. Also add NTC_FAULT field value to the debug message in bq25890_get_chip_state(). Changed by Hans de Goede: - Fix reading F_CHG_FAULT instead of F_NTC_FIELD for state->ntc_fault - Only read JEITA_ISET field if necessary - Tweak commit message a bit Signed-off-by: Yauhen Kharuzhy Reviewed-by: Andy Shevchenko Co-developed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 29 +++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index fb987579d05a..7a3269c06b38 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -94,6 +94,7 @@ struct bq25890_state { u8 vsys_status; u8 boost_fault; u8 bat_fault; + u8 ntc_fault; }; struct bq25890_device { @@ -407,6 +408,14 @@ enum bq25890_chrg_fault { CHRG_FAULT_TIMER_EXPIRED, }; +enum bq25890_ntc_fault { + NTC_FAULT_NORMAL = 0, + NTC_FAULT_WARM = 2, + NTC_FAULT_COOL = 3, + NTC_FAULT_COLD = 5, + NTC_FAULT_HOT = 6, +}; + static bool bq25890_is_adc_property(enum power_supply_property psp) { switch (psp) { @@ -499,6 +508,18 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: val->intval = bq25890_find_val(bq->init_data.ichg, TBL_ICHG); + + /* When temperature is too low, charge current is decreased */ + if (bq->state.ntc_fault == NTC_FAULT_COOL) { + ret = bq25890_field_read(bq, F_JEITA_ISET); + if (ret < 0) + return ret; + + if (ret) + val->intval /= 5; + else + val->intval /= 2; + } break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: @@ -583,7 +604,8 @@ static int bq25890_get_chip_state(struct bq25890_device *bq, {F_VSYS_STAT, &state->vsys_status}, {F_BOOST_FAULT, &state->boost_fault}, {F_BAT_FAULT, &state->bat_fault}, - {F_CHG_FAULT, &state->chrg_fault} + {F_CHG_FAULT, &state->chrg_fault}, + {F_NTC_FAULT, &state->ntc_fault} }; for (i = 0; i < ARRAY_SIZE(state_fields); i++) { @@ -594,9 +616,10 @@ static int bq25890_get_chip_state(struct bq25890_device *bq, *state_fields[i].data = ret; } - dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT=%d/%d/%d\n", + dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT/NTC=%d/%d/%d/%d\n", state->chrg_status, state->online, state->vsys_status, - state->chrg_fault, state->boost_fault, state->bat_fault); + state->chrg_fault, state->boost_fault, state->bat_fault, + state->ntc_fault); return 0; } From 7b22a97464b4fd49b27a5eda72229854d0f454fa Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:50 +0100 Subject: [PATCH 16/94] power: supply: bq25890: Add a bq25890_rw_init_data() helper On most x86/ACPI devices there is no devicetree to supply the necessary init-data. Instead the firmware already fully initializes the bq25890 charger at boot. Factor out the current code to write all the init_data from devicetree into a new bq25890_rw_init_data() helper which can both write the data to the charger (the current behavior) as well as read it back from the charger into the init_data struct. This is a preparation patch for adding support for x86/ACPI device's where the init_data must be read back from the bq25890 charger. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 62 +++++++++++++++++--------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 7a3269c06b38..eaf0400b632f 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -693,29 +693,52 @@ static int bq25890_chip_reset(struct bq25890_device *bq) return 0; } -static int bq25890_hw_init(struct bq25890_device *bq) +static int bq25890_rw_init_data(struct bq25890_device *bq) { + bool write = true; int ret; int i; const struct { enum bq25890_fields id; - u32 value; + u8 *value; } init_data[] = { - {F_ICHG, bq->init_data.ichg}, - {F_VREG, bq->init_data.vreg}, - {F_ITERM, bq->init_data.iterm}, - {F_IPRECHG, bq->init_data.iprechg}, - {F_SYSVMIN, bq->init_data.sysvmin}, - {F_BOOSTV, bq->init_data.boostv}, - {F_BOOSTI, bq->init_data.boosti}, - {F_BOOSTF, bq->init_data.boostf}, - {F_EN_ILIM, bq->init_data.ilim_en}, - {F_TREG, bq->init_data.treg}, - {F_BATCMP, bq->init_data.rbatcomp}, - {F_VCLAMP, bq->init_data.vclamp}, + {F_ICHG, &bq->init_data.ichg}, + {F_VREG, &bq->init_data.vreg}, + {F_ITERM, &bq->init_data.iterm}, + {F_IPRECHG, &bq->init_data.iprechg}, + {F_SYSVMIN, &bq->init_data.sysvmin}, + {F_BOOSTV, &bq->init_data.boostv}, + {F_BOOSTI, &bq->init_data.boosti}, + {F_BOOSTF, &bq->init_data.boostf}, + {F_EN_ILIM, &bq->init_data.ilim_en}, + {F_TREG, &bq->init_data.treg}, + {F_BATCMP, &bq->init_data.rbatcomp}, + {F_VCLAMP, &bq->init_data.vclamp}, }; + for (i = 0; i < ARRAY_SIZE(init_data); i++) { + if (write) { + ret = bq25890_field_write(bq, init_data[i].id, + *init_data[i].value); + } else { + ret = bq25890_field_read(bq, init_data[i].id); + if (ret >= 0) + *init_data[i].value = ret; + } + if (ret < 0) { + dev_dbg(bq->dev, "Accessing init data failed %d\n", ret); + return ret; + } + } + + return 0; +} + +static int bq25890_hw_init(struct bq25890_device *bq) +{ + int ret; + ret = bq25890_chip_reset(bq); if (ret < 0) { dev_dbg(bq->dev, "Reset failed %d\n", ret); @@ -730,14 +753,9 @@ static int bq25890_hw_init(struct bq25890_device *bq) } /* initialize currents/voltages and other parameters */ - for (i = 0; i < ARRAY_SIZE(init_data); i++) { - ret = bq25890_field_write(bq, init_data[i].id, - init_data[i].value); - if (ret < 0) { - dev_dbg(bq->dev, "Writing init data failed %d\n", ret); - return ret; - } - } + ret = bq25890_rw_init_data(bq); + if (ret) + return ret; ret = bq25890_get_chip_state(bq, &bq->state); if (ret < 0) { From 7e3b8e357f230f96473a84581cfa18899f860338 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:51 +0100 Subject: [PATCH 17/94] power: supply: bq25890: Add support to skip reset at probe() / remove() On most x86/ACPI devices the firmware already fully initializes the bq25890 charger at boot, in this case it is best to not reset it at probe() time. At support for a new "linux,skip-reset" boolean property to support this. So far this new property is only used on x86/ACPI (non devicetree) devs, IOW it is not used in actual devicetree files. The devicetree-bindings maintainers have requested properties like these to not be added to the devicetree-bindings, so the new property is deliberately not added to the existing devicetree-bindings. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index eaf0400b632f..cd80d748df92 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -110,6 +110,7 @@ struct bq25890_device { struct regmap *rmap; struct regmap_field *rmap_fields[F_MAX_FIELDS]; + bool skip_reset; enum bq25890_chip_version chip_version; struct bq25890_init_data init_data; struct bq25890_state state; @@ -739,10 +740,12 @@ static int bq25890_hw_init(struct bq25890_device *bq) { int ret; - ret = bq25890_chip_reset(bq); - if (ret < 0) { - dev_dbg(bq->dev, "Reset failed %d\n", ret); - return ret; + if (!bq->skip_reset) { + ret = bq25890_chip_reset(bq); + if (ret < 0) { + dev_dbg(bq->dev, "Reset failed %d\n", ret); + return ret; + } } /* disable watchdog */ @@ -977,6 +980,8 @@ static int bq25890_fw_probe(struct bq25890_device *bq) int ret; struct bq25890_init_data *init = &bq->init_data; + bq->skip_reset = device_property_read_bool(bq->dev, "linux,skip-reset"); + ret = bq25890_fw_read_u32_props(bq); if (ret < 0) return ret; @@ -1089,8 +1094,10 @@ static int bq25890_remove(struct i2c_client *client) if (!IS_ERR_OR_NULL(bq->usb_phy)) usb_unregister_notifier(bq->usb_phy, &bq->usb_nb); - /* reset all registers to default values */ - bq25890_chip_reset(bq); + if (!bq->skip_reset) { + /* reset all registers to default values */ + bq25890_chip_reset(bq); + } return 0; } From 40428bd4675762f2da5e81b5b31c0425f9bd0de2 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:52 +0100 Subject: [PATCH 18/94] power: supply: bq25890: Add support to read back the settings from the chip On most x86/ACPI devices there is no devicetree to supply the necessary init-data. Instead the firmware already fully initializes the bq25890 charger at boot. To support this, add support for reading back the settings from the chip through a new "linux,read-back-settings" boolean. So far this new property is only used on x86/ACPI (non devicetree) devs, IOW it is not used in actual devicetree files. The devicetree-bindings maintainers have requested properties like these to not be added to the devicetree-bindings, so the new property is deliberately not added to the existing devicetree-bindings. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index cd80d748df92..f758e28046e5 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -111,6 +111,7 @@ struct bq25890_device { struct regmap_field *rmap_fields[F_MAX_FIELDS]; bool skip_reset; + bool read_back_init_data; enum bq25890_chip_version chip_version; struct bq25890_init_data init_data; struct bq25890_state state; @@ -696,7 +697,7 @@ static int bq25890_chip_reset(struct bq25890_device *bq) static int bq25890_rw_init_data(struct bq25890_device *bq) { - bool write = true; + bool write = !bq->read_back_init_data; int ret; int i; @@ -981,6 +982,10 @@ static int bq25890_fw_probe(struct bq25890_device *bq) struct bq25890_init_data *init = &bq->init_data; bq->skip_reset = device_property_read_bool(bq->dev, "linux,skip-reset"); + bq->read_back_init_data = device_property_read_bool(bq->dev, + "linux,read-back-settings"); + if (bq->read_back_init_data) + return 0; ret = bq25890_fw_read_u32_props(bq); if (ret < 0) From 06c75095e8147770626f7ca0b6352a718e62ba38 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:53 +0100 Subject: [PATCH 19/94] power: supply: bq25890: Enable charging on boards where we skip reset On boards where the "linux,skip-reset" boolean property is set we don't reset the charger; and on some boards where the fw takes care of initalizition F_CHG_CFG is set to 0 before handing control over to the OS. Explicitly set F_CHG_CFG to 1 on boards where we don't reset the charger, so that charging is always enabled on these boards, like it is always enabled on boards where we do reset the charger. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index f758e28046e5..d185299db9c3 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -747,6 +747,17 @@ static int bq25890_hw_init(struct bq25890_device *bq) dev_dbg(bq->dev, "Reset failed %d\n", ret); return ret; } + } else { + /* + * Ensure charging is enabled, on some boards where the fw + * takes care of initalizition F_CHG_CFG is set to 0 before + * handing control over to the OS. + */ + ret = bq25890_field_write(bq, F_CHG_CFG, 1); + if (ret < 0) { + dev_dbg(bq->dev, "Enabling charging failed %d\n", ret); + return ret; + } } /* disable watchdog */ From f481d5b80bd86ad39229c430d26513c096474578 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:54 +0100 Subject: [PATCH 20/94] power: supply: bq25890: Drop dev->platform_data == NULL check Drop the "if (!dev->platform_data)" check, this seems to be an attempt for allowing loading the driver on devices without devicetree stemming from the initial commit of the driver (with the presumed intention being the "return -ENODEV" else branch getting replaced with something else). With the new "linux,skip-init" and "linux,read-back-settings" properties the driver can actually supports devices without devicetree and this check no longer makes sense. While at it, also switch to dev_err_probe(), which is already used in various other places in the driver. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index d185299db9c3..548d1a793e31 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -1048,16 +1048,9 @@ static int bq25890_probe(struct i2c_client *client, return ret; } - if (!dev->platform_data) { - ret = bq25890_fw_probe(bq); - if (ret < 0) { - dev_err(dev, "Cannot read device properties: %d\n", - ret); - return ret; - } - } else { - return -ENODEV; - } + ret = bq25890_fw_probe(bq); + if (ret < 0) + return dev_err_probe(dev, ret, "reading device properties\n"); ret = bq25890_hw_init(bq); if (ret < 0) { From 5575802d78b8c6130aaecb33f700b379f1fe5ecd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:55 +0100 Subject: [PATCH 21/94] power: supply: bq25890: Add bq25890_set_otg_cfg() helper Add a bq25890_set_otg_cfg() helper function, this is a preparation patch for adding regulator support. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 28 ++++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 548d1a793e31..162bffb02410 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -832,6 +832,17 @@ static int bq25890_power_supply_init(struct bq25890_device *bq) return PTR_ERR_OR_ZERO(bq->charger); } +static int bq25890_set_otg_cfg(struct bq25890_device *bq, u8 val) +{ + int ret; + + ret = bq25890_field_write(bq, F_OTG_CFG, val); + if (ret < 0) + dev_err(bq->dev, "Error switching to boost/charger mode: %d\n", ret); + + return ret; +} + static void bq25890_usb_work(struct work_struct *data) { int ret; @@ -841,25 +852,16 @@ static void bq25890_usb_work(struct work_struct *data) switch (bq->usb_event) { case USB_EVENT_ID: /* Enable boost mode */ - ret = bq25890_field_write(bq, F_OTG_CFG, 1); - if (ret < 0) - goto error; + bq25890_set_otg_cfg(bq, 1); break; case USB_EVENT_NONE: /* Disable boost mode */ - ret = bq25890_field_write(bq, F_OTG_CFG, 0); - if (ret < 0) - goto error; - - power_supply_changed(bq->charger); + ret = bq25890_set_otg_cfg(bq, 0); + if (ret == 0) + power_supply_changed(bq->charger); break; } - - return; - -error: - dev_err(bq->dev, "Error switching to boost/charger mode.\n"); } static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val, From 79d35365a5858466ff7b37aaf1fcf11b683b9442 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:56 +0100 Subject: [PATCH 22/94] power: supply: bq25890: Add support for registering the Vbus boost converter as a regulator The bq25890_charger code supports enabling/disabling the boost converter based on usb-phy notifications. But the usb-phy framework is not used on all boards/platforms. At support for registering the Vbus boost converter as a standard regulator when there is no usb-phy on the board. Also add support for providing regulator_init_data through platform_data for use on boards where device-tree is not used and the platform code must thus provide the regulator_init_data. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 80 ++++++++++++++++++++++++++ include/linux/power/bq25890_charger.h | 15 +++++ 2 files changed, 95 insertions(+) create mode 100644 include/linux/power/bq25890_charger.h diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 162bffb02410..637cdd3b6b11 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -876,6 +878,45 @@ static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val, return NOTIFY_OK; } +#ifdef CONFIG_REGULATOR +static int bq25890_vbus_enable(struct regulator_dev *rdev) +{ + struct bq25890_device *bq = rdev_get_drvdata(rdev); + + return bq25890_set_otg_cfg(bq, 1); +} + +static int bq25890_vbus_disable(struct regulator_dev *rdev) +{ + struct bq25890_device *bq = rdev_get_drvdata(rdev); + + return bq25890_set_otg_cfg(bq, 0); +} + +static int bq25890_vbus_is_enabled(struct regulator_dev *rdev) +{ + struct bq25890_device *bq = rdev_get_drvdata(rdev); + + return bq25890_field_read(bq, F_OTG_CFG); +} + +static const struct regulator_ops bq25890_vbus_ops = { + .enable = bq25890_vbus_enable, + .disable = bq25890_vbus_disable, + .is_enabled = bq25890_vbus_is_enabled, +}; + +static const struct regulator_desc bq25890_vbus_desc = { + .name = "usb_otg_vbus", + .of_match = "usb-otg-vbus", + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &bq25890_vbus_ops, + .fixed_uV = 5000000, + .n_voltages = 1, +}; +#endif + static int bq25890_get_chip_version(struct bq25890_device *bq) { int id, rev; @@ -1075,6 +1116,22 @@ static int bq25890_probe(struct i2c_client *client, bq->usb_nb.notifier_call = bq25890_usb_notifier; usb_register_notifier(bq->usb_phy, &bq->usb_nb); } +#ifdef CONFIG_REGULATOR + else { + struct bq25890_platform_data *pdata = dev_get_platdata(dev); + struct regulator_config cfg = { }; + struct regulator_dev *reg; + + cfg.dev = dev; + cfg.driver_data = bq; + if (pdata) + cfg.init_data = pdata->regulator_init_data; + + reg = devm_regulator_register(dev, &bq25890_vbus_desc, &cfg); + if (IS_ERR(reg)) + return dev_err_probe(dev, PTR_ERR(reg), "registering regulator"); + } +#endif ret = bq25890_power_supply_init(bq); if (ret < 0) { @@ -1113,6 +1170,28 @@ static int bq25890_remove(struct i2c_client *client) return 0; } +static void bq25890_shutdown(struct i2c_client *client) +{ + struct bq25890_device *bq = i2c_get_clientdata(client); + + /* + * TODO this if + return should probably be removed, but that would + * introduce a function change for boards using the usb-phy framework. + * This needs to be tested on such a board before making this change. + */ + if (!IS_ERR_OR_NULL(bq->usb_phy)) + return; + + /* + * Turn off the 5v Boost regulator which outputs Vbus to the device's + * Micro-USB or Type-C USB port. Leaving this on drains power and + * this avoids the PMIC on some device-models seeing this as Vbus + * getting inserted after shutdown, causing the device to immediately + * power-up again. + */ + bq25890_set_otg_cfg(bq, 0); +} + #ifdef CONFIG_PM_SLEEP static int bq25890_suspend(struct device *dev) { @@ -1192,6 +1271,7 @@ static struct i2c_driver bq25890_driver = { }, .probe = bq25890_probe, .remove = bq25890_remove, + .shutdown = bq25890_shutdown, .id_table = bq25890_i2c_ids, }; module_i2c_driver(bq25890_driver); diff --git a/include/linux/power/bq25890_charger.h b/include/linux/power/bq25890_charger.h new file mode 100644 index 000000000000..c706ddb77a08 --- /dev/null +++ b/include/linux/power/bq25890_charger.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Platform data for the TI bq25890 battery charger driver. + */ + +#ifndef _BQ25890_CHARGER_H_ +#define _BQ25890_CHARGER_H_ + +struct regulator_init_data; + +struct bq25890_platform_data { + const struct regulator_init_data *regulator_init_data; +}; + +#endif From eab25b4f93aa771728127705eb4b235a3b5aad94 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:57 +0100 Subject: [PATCH 23/94] power: supply: bq25890: On the bq25892 set the IINLIM based on external charger detection The bq25892 does not have builtin charger-type detection like the bq25980, there might be some external charger detection capability, which will be modelled as a power_supply class-device supplying the bq25892. Use the usb_type property value from the supplier psy-device to set the input-current-limit (when available). Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 637cdd3b6b11..3de72f0fbf3e 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -594,6 +594,38 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, return 0; } +/* On the BQ25892 try to get charger-type info from our supplier */ +static void bq25890_charger_external_power_changed(struct power_supply *psy) +{ + struct bq25890_device *bq = power_supply_get_drvdata(psy); + union power_supply_propval val; + int input_current_limit, ret; + + if (bq->chip_version != BQ25892) + return; + + ret = power_supply_get_property_from_supplier(bq->charger, + POWER_SUPPLY_PROP_USB_TYPE, + &val); + if (ret) + return; + + switch (val.intval) { + case POWER_SUPPLY_USB_TYPE_DCP: + input_current_limit = bq25890_find_idx(2000000, TBL_IINLIM); + break; + case POWER_SUPPLY_USB_TYPE_CDP: + case POWER_SUPPLY_USB_TYPE_ACA: + input_current_limit = bq25890_find_idx(1500000, TBL_IINLIM); + break; + case POWER_SUPPLY_USB_TYPE_SDP: + default: + input_current_limit = bq25890_find_idx(500000, TBL_IINLIM); + } + + bq25890_field_write(bq, F_IINLIM, input_current_limit); +} + static int bq25890_get_chip_state(struct bq25890_device *bq, struct bq25890_state *state) { @@ -818,6 +850,7 @@ static const struct power_supply_desc bq25890_power_supply_desc = { .properties = bq25890_power_supply_props, .num_properties = ARRAY_SIZE(bq25890_power_supply_props), .get_property = bq25890_power_supply_get_property, + .external_power_changed = bq25890_charger_external_power_changed, }; static int bq25890_power_supply_init(struct bq25890_device *bq) From 48f45b094dbbf23620929b68afd32e865f712272 Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Tue, 1 Feb 2022 14:06:58 +0100 Subject: [PATCH 24/94] power: supply: bq25890: Support higher charging voltages through Pump Express+ protocol Add a "linux,pump-express-vbus-max" property which indicates if the Pump Express+ protocol should be used to increase the charging protocol. If this new property is set and a DCP charger is detected then request a higher charging voltage through the Pump Express+ protocol. So far this new property is only used on x86/ACPI (non devicetree) devs, IOW it is not used in actual devicetree files. The devicetree-bindings maintainers have requested properties like these to not be added to the devicetree-bindings, so the new property is deliberately not added to the existing devicetree-bindings. Changes by Hans de Goede: - Port to my bq25890 patch-series + various cleanups - Make behavior configurable through a new "linux,pump-express-vbus-max" device-property - Sleep 1 second before re-checking the Vbus voltage after requesting it to be raised, to ensure that the ADC has time to sampled the new Vbus - Add VBUSV bq25890_tables[] entry and use it in bq25890_get_vbus_voltage() - Tweak commit message Signed-off-by: Yauhen Kharuzhy Reviewed-by: Andy Shevchenko Co-developed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 92 +++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 3de72f0fbf3e..179abed92f9b 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -27,6 +27,10 @@ #define BQ25895_ID 7 #define BQ25896_ID 0 +#define PUMP_EXPRESS_START_DELAY (5 * HZ) +#define PUMP_EXPRESS_MAX_TRIES 6 +#define PUMP_EXPRESS_VBUS_MARGIN_uV 1000000 + enum bq25890_chip_version { BQ25890, BQ25892, @@ -107,6 +111,7 @@ struct bq25890_device { struct usb_phy *usb_phy; struct notifier_block usb_nb; struct work_struct usb_work; + struct delayed_work pump_express_work; unsigned long usb_event; struct regmap *rmap; @@ -114,6 +119,7 @@ struct bq25890_device { bool skip_reset; bool read_back_init_data; + u32 pump_express_vbus_max; enum bq25890_chip_version chip_version; struct bq25890_init_data init_data; struct bq25890_state state; @@ -265,6 +271,7 @@ enum bq25890_table_ids { TBL_VREG, TBL_BOOSTV, TBL_SYSVMIN, + TBL_VBUSV, TBL_VBATCOMP, TBL_RBATCOMP, @@ -325,14 +332,15 @@ static const union { } bq25890_tables[] = { /* range tables */ /* TODO: BQ25896 has max ICHG 3008 mA */ - [TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */ - [TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */ - [TBL_IINLIM] = { .rt = {100000, 3250000, 50000} }, /* uA */ - [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */ - [TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */ - [TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */ - [TBL_VBATCOMP] ={ .rt = {0, 224000, 32000} }, /* uV */ - [TBL_RBATCOMP] ={ .rt = {0, 140000, 20000} }, /* uOhm */ + [TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */ + [TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */ + [TBL_IINLIM] = { .rt = {100000, 3250000, 50000} }, /* uA */ + [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */ + [TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */ + [TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */ + [TBL_VBUSV] = { .rt = {2600000, 15300000, 100000} }, /* uV */ + [TBL_VBATCOMP] = { .rt = {0, 224000, 32000} }, /* uV */ + [TBL_RBATCOMP] = { .rt = {0, 140000, 20000} }, /* uOhm */ /* lookup tables */ [TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} }, @@ -435,6 +443,17 @@ static bool bq25890_is_adc_property(enum power_supply_property psp) static irqreturn_t __bq25890_handle_irq(struct bq25890_device *bq); +static int bq25890_get_vbus_voltage(struct bq25890_device *bq) +{ + int ret; + + ret = bq25890_field_read(bq, F_VBUSV); + if (ret < 0) + return ret; + + return bq25890_find_val(ret, TBL_VBUSV); +} + static int bq25890_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -613,6 +632,11 @@ static void bq25890_charger_external_power_changed(struct power_supply *psy) switch (val.intval) { case POWER_SUPPLY_USB_TYPE_DCP: input_current_limit = bq25890_find_idx(2000000, TBL_IINLIM); + if (bq->pump_express_vbus_max) { + queue_delayed_work(system_power_efficient_wq, + &bq->pump_express_work, + PUMP_EXPRESS_START_DELAY); + } break; case POWER_SUPPLY_USB_TYPE_CDP: case POWER_SUPPLY_USB_TYPE_ACA: @@ -878,6 +902,53 @@ static int bq25890_set_otg_cfg(struct bq25890_device *bq, u8 val) return ret; } +static void bq25890_pump_express_work(struct work_struct *data) +{ + struct bq25890_device *bq = + container_of(data, struct bq25890_device, pump_express_work.work); + int voltage, i, ret; + + dev_dbg(bq->dev, "Start to request input voltage increasing\n"); + + /* Enable current pulse voltage control protocol */ + ret = bq25890_field_write(bq, F_PUMPX_EN, 1); + if (ret < 0) + goto error_print; + + for (i = 0; i < PUMP_EXPRESS_MAX_TRIES; i++) { + voltage = bq25890_get_vbus_voltage(bq); + if (voltage < 0) + goto error_print; + dev_dbg(bq->dev, "input voltage = %d uV\n", voltage); + + if ((voltage + PUMP_EXPRESS_VBUS_MARGIN_uV) > + bq->pump_express_vbus_max) + break; + + ret = bq25890_field_write(bq, F_PUMPX_UP, 1); + if (ret < 0) + goto error_print; + + /* Note a single PUMPX up pulse-sequence takes 2.1s */ + ret = regmap_field_read_poll_timeout(bq->rmap_fields[F_PUMPX_UP], + ret, !ret, 100000, 3000000); + if (ret < 0) + goto error_print; + + /* Make sure ADC has sampled Vbus before checking again */ + msleep(1000); + } + + bq25890_field_write(bq, F_PUMPX_EN, 0); + + dev_info(bq->dev, "Hi-voltage charging requested, input voltage is %d mV\n", + voltage); + + return; +error_print: + dev_err(bq->dev, "Failed to request hi-voltage charging\n"); +} + static void bq25890_usb_work(struct work_struct *data) { int ret; @@ -1068,6 +1139,10 @@ static int bq25890_fw_probe(struct bq25890_device *bq) int ret; struct bq25890_init_data *init = &bq->init_data; + /* Optional, left at 0 if property is not present */ + device_property_read_u32(bq->dev, "linux,pump-express-vbus-max", + &bq->pump_express_vbus_max); + bq->skip_reset = device_property_read_bool(bq->dev, "linux,skip-reset"); bq->read_back_init_data = device_property_read_bool(bq->dev, "linux,read-back-settings"); @@ -1100,6 +1175,7 @@ static int bq25890_probe(struct i2c_client *client, bq->dev = dev; mutex_init(&bq->lock); + INIT_DELAYED_WORK(&bq->pump_express_work, bq25890_pump_express_work); bq->rmap = devm_regmap_init_i2c(client, &bq25890_regmap_config); if (IS_ERR(bq->rmap)) From c1ae3a4efbf53335a6422646759f43d682ebdaf8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:06:59 +0100 Subject: [PATCH 25/94] power: supply: bq25890: Use the devm_regmap_field_bulk_alloc() helper Use the devm_regmap_field_bulk_alloc() helper function instead of open-coding this ourselves. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 179abed92f9b..852a6fec4339 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -1165,7 +1165,6 @@ static int bq25890_probe(struct i2c_client *client, struct device *dev = &client->dev; struct bq25890_device *bq; int ret; - int i; bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL); if (!bq) @@ -1182,15 +1181,10 @@ static int bq25890_probe(struct i2c_client *client, return dev_err_probe(dev, PTR_ERR(bq->rmap), "failed to allocate register map\n"); - for (i = 0; i < ARRAY_SIZE(bq25890_reg_fields); i++) { - const struct reg_field *reg_fields = bq25890_reg_fields; - - bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap, - reg_fields[i]); - if (IS_ERR(bq->rmap_fields[i])) - return dev_err_probe(dev, PTR_ERR(bq->rmap_fields[i]), - "cannot allocate regmap field\n"); - } + ret = devm_regmap_field_bulk_alloc(dev, bq->rmap, bq->rmap_fields, + bq25890_reg_fields, F_MAX_FIELDS); + if (ret) + return ret; i2c_set_clientdata(client, bq); From 3afcbe09470091ca8a8048ef7c96701839a70961 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:07:00 +0100 Subject: [PATCH 26/94] mfd: intel_soc_pmic_chtwc: Add cht_wc_model data to struct intel_soc_pmic Tablet / laptop designs using an Intel Cherry Trail x86 main SoC with an Intel Whiskey Cove PMIC do not use a single standard setup for the charger, fuel-gauge and other chips surrounding the PMIC / charging+data USB port. Unlike what is normal on x86 this diversity in designs is not handled by the ACPI tables. On 2 of the 3 known designs there are no standard (PNP0C0A) ACPI battery devices and on the 3th design the ACPI battery device does not work under Linux due to it requiring non-standard and undocumented ACPI behavior. So to make things work under Linux we use native charger and fuel-gauge drivers on these devices, re-using the native drivers used on ARM boards with the same charger / fuel-gauge ICs. This requires various MFD-cell drivers for the CHT-WC PMIC cells to know which model they are exactly running on so that they can e.g. instantiate an I2C-client for the right model charger-IC (the charger is connected to an I2C-controller which is part of the PMIC). Rather then duplicating DMI-id matching to check which model we are running on in each MFD-cell driver, add a check for this to the shared drivers/mfd/intel_soc_pmic_chtwc.c code by using a DMI table for all 3 known models: 1. The GPD Win and GPD Pocket mini-laptops, these are really 2 models but the Pocket re-uses the GPD Win's design in a different housing: The WC PMIC is connected to a TI BQ24292i charger, paired with a Maxim MAX17047 fuelgauge + a FUSB302 USB Type-C Controller + a PI3USB30532 USB switch, for a fully functional Type-C port. 2. The Xiaomi Mi Pad 2: The WC PMIC is connected to a TI BQ25890 charger, paired with a TI BQ27520 fuelgauge, using the TI BQ25890 for BC1.2 charger type detection, for a USB-2 only Type-C port without PD. 3. The Lenovo Yoga Book YB1-X90 / Lenovo Yoga Book YB1-X91 series: The WC PMIC is connected to a TI BQ25892 charger, paired with a TI BQ27542 fuelgauge, using the WC PMIC for BC1.2 charger type detection and using the BQ25892's Mediatek Pump Express+ (1.0) support to enable charging with up to 12V through a micro-USB port. Reviewed-by: Andy Shevchenko Acked-by: Lee Jones Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/mfd/intel_soc_pmic_chtwc.c | 40 ++++++++++++++++++++++++++++++ include/linux/mfd/intel_soc_pmic.h | 8 ++++++ 2 files changed, 48 insertions(+) diff --git a/drivers/mfd/intel_soc_pmic_chtwc.c b/drivers/mfd/intel_soc_pmic_chtwc.c index 49c5f71664bc..4eab191e053a 100644 --- a/drivers/mfd/intel_soc_pmic_chtwc.c +++ b/drivers/mfd/intel_soc_pmic_chtwc.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -134,9 +135,44 @@ static const struct regmap_irq_chip cht_wc_regmap_irq_chip = { .num_regs = 1, }; +static const struct dmi_system_id cht_wc_model_dmi_ids[] = { + { + /* GPD win / GPD pocket mini laptops */ + .driver_data = (void *)(long)INTEL_CHT_WC_GPD_WIN_POCKET, + /* + * This DMI match may not seem unique, but it is. In the 67000+ + * DMI decode dumps from linux-hardware.org only 116 have + * board_vendor set to "AMI Corporation" and of those 116 only + * the GPD win's and pocket's board_name is "Default string". + */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), + DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), + }, + }, { + /* Xiaomi Mi Pad 2 */ + .driver_data = (void *)(long)INTEL_CHT_WC_XIAOMI_MIPAD2, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Xiaomi Inc"), + DMI_MATCH(DMI_PRODUCT_NAME, "Mipad2"), + }, + }, { + /* Lenovo Yoga Book X90F / X91F / X91L */ + .driver_data = (void *)(long)INTEL_CHT_WC_LENOVO_YOGABOOK1, + .matches = { + /* Non exact match to match all versions */ + DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"), + }, + }, + { } +}; + static int cht_wc_probe(struct i2c_client *client) { struct device *dev = &client->dev; + const struct dmi_system_id *id; struct intel_soc_pmic *pmic; acpi_status status; unsigned long long hrv; @@ -160,6 +196,10 @@ static int cht_wc_probe(struct i2c_client *client) if (!pmic) return -ENOMEM; + id = dmi_first_match(cht_wc_model_dmi_ids); + if (id) + pmic->cht_wc_model = (long)id->driver_data; + pmic->irq = client->irq; pmic->dev = dev; i2c_set_clientdata(client, pmic); diff --git a/include/linux/mfd/intel_soc_pmic.h b/include/linux/mfd/intel_soc_pmic.h index 6a88e34cb955..945bde1fe55c 100644 --- a/include/linux/mfd/intel_soc_pmic.h +++ b/include/linux/mfd/intel_soc_pmic.h @@ -13,6 +13,13 @@ #include +enum intel_cht_wc_models { + INTEL_CHT_WC_UNKNOWN, + INTEL_CHT_WC_GPD_WIN_POCKET, + INTEL_CHT_WC_XIAOMI_MIPAD2, + INTEL_CHT_WC_LENOVO_YOGABOOK1, +}; + /** * struct intel_soc_pmic - Intel SoC PMIC data * @irq: Master interrupt number of the parent PMIC device @@ -39,6 +46,7 @@ struct intel_soc_pmic { struct regmap_irq_chip_data *irq_chip_data_crit; struct device *dev; struct intel_scu_ipc_dev *scu; + enum intel_cht_wc_models cht_wc_model; }; int intel_soc_pmic_exec_mipi_pmic_seq_element(u16 i2c_address, u32 reg_address, From 4b32649140a175fbda3cb8683faff16c3f390533 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:07:01 +0100 Subject: [PATCH 27/94] i2c: cht-wc: Make charger i2c-client instantiation board/device-model specific The i2c-controller on the Cherry Trail - Whiskey Cove PMIC is special in that it is always connected to the I2C charger IC of the board on which the PMIC is used; and the charger IC is not described in ACPI, so the i2c-cht-wc code needs to instantiate an i2c-client for it itself. So far this was hardcoded to instantiate an i2c-client for the bq24292i, with all properties, etc. set to match how this charger is used on the GPD win and GPD pocket devices. There is a rudimentary check to make sure the ACPI tables are at least somewhat as expected, but this is far from accurate, leading to a wrong i2c-client being instantiated for the charger on some boards. Switch to the new DMI based intel_cht_wc_get_model() helper which is exported by the MFD driver for the CHT Whiskey Cove PMIC to help PMIC cell drivers like the i2c-cht-wc code reliably detect which board they are running on. And add board_info for the charger ICs as found on the other 2 known boards with a Whisky Cove PMIC. This has been tested on all 3 known boards. Acked-by: Wolfram Sang Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/i2c/busses/i2c-cht-wc.c | 120 +++++++++++++++++++++++++++----- 1 file changed, 102 insertions(+), 18 deletions(-) diff --git a/drivers/i2c/busses/i2c-cht-wc.c b/drivers/i2c/busses/i2c-cht-wc.c index 1cf68f85b2e1..54e909f9eab6 100644 --- a/drivers/i2c/busses/i2c-cht-wc.c +++ b/drivers/i2c/busses/i2c-cht-wc.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #define CHT_WC_I2C_CTRL 0x5e24 @@ -270,6 +271,7 @@ static const struct irq_chip cht_wc_i2c_irq_chip = { .name = "cht_wc_ext_chrg_irq_chip", }; +/********** GPD Win / Pocket charger IC settings **********/ static const char * const bq24190_suppliers[] = { "tcpm-source-psy-i2c-fusb302" }; @@ -304,17 +306,92 @@ static struct bq24190_platform_data bq24190_pdata = { .regulator_init_data = &bq24190_vbus_init_data, }; +static struct i2c_board_info gpd_win_board_info = { + .type = "bq24190", + .addr = 0x6b, + .dev_name = "bq24190", + .swnode = &bq24190_node, + .platform_data = &bq24190_pdata, +}; + +/********** Xiaomi Mi Pad 2 charger IC settings **********/ +static struct regulator_consumer_supply bq2589x_vbus_consumer = { + .supply = "vbus", + .dev_name = "cht_wcove_pwrsrc", +}; + +static const struct regulator_init_data bq2589x_vbus_init_data = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = &bq2589x_vbus_consumer, + .num_consumer_supplies = 1, +}; + +static struct bq25890_platform_data bq2589x_pdata = { + .regulator_init_data = &bq2589x_vbus_init_data, +}; + +static const struct property_entry xiaomi_mipad2_props[] = { + PROPERTY_ENTRY_BOOL("linux,skip-reset"), + PROPERTY_ENTRY_BOOL("linux,read-back-settings"), + { } +}; + +static const struct software_node xiaomi_mipad2_node = { + .properties = xiaomi_mipad2_props, +}; + +static struct i2c_board_info xiaomi_mipad2_board_info = { + .type = "bq25890", + .addr = 0x6a, + .dev_name = "bq25890", + .swnode = &xiaomi_mipad2_node, + .platform_data = &bq2589x_pdata, +}; + +/********** Lenovo Yogabook YB1-X90F/-X91F/-X91L charger settings **********/ +static const char * const lenovo_yb1_bq25892_suppliers[] = { "cht_wcove_pwrsrc" }; + +static const struct property_entry lenovo_yb1_bq25892_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", + lenovo_yb1_bq25892_suppliers), + PROPERTY_ENTRY_U32("linux,pump-express-vbus-max", 12000000), + PROPERTY_ENTRY_BOOL("linux,skip-reset"), + /* + * The firmware sets everything to the defaults, which leads to a + * somewhat low charge-current of 2048mA and worse to a battery-voltage + * of 4.2V instead of 4.35V (when booted without a charger connected). + * Use our own values instead of "linux,read-back-settings" to fix this. + */ + PROPERTY_ENTRY_U32("ti,charge-current", 4224000), + PROPERTY_ENTRY_U32("ti,battery-regulation-voltage", 4352000), + PROPERTY_ENTRY_U32("ti,termination-current", 256000), + PROPERTY_ENTRY_U32("ti,precharge-current", 128000), + PROPERTY_ENTRY_U32("ti,minimum-sys-voltage", 3500000), + PROPERTY_ENTRY_U32("ti,boost-voltage", 4998000), + PROPERTY_ENTRY_U32("ti,boost-max-current", 1400000), + PROPERTY_ENTRY_BOOL("ti,use-ilim-pin"), + { } +}; + +static const struct software_node lenovo_yb1_bq25892_node = { + .properties = lenovo_yb1_bq25892_props, +}; + +static struct i2c_board_info lenovo_yogabook1_board_info = { + .type = "bq25892", + .addr = 0x6b, + .dev_name = "bq25892", + .swnode = &lenovo_yb1_bq25892_node, + .platform_data = &bq2589x_pdata, +}; + static int cht_wc_i2c_adap_i2c_probe(struct platform_device *pdev) { struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + struct i2c_board_info *board_info = NULL; struct cht_wc_i2c_adap *adap; - struct i2c_board_info board_info = { - .type = "bq24190", - .addr = 0x6b, - .dev_name = "bq24190", - .swnode = &bq24190_node, - .platform_data = &bq24190_pdata, - }; int ret, reg, irq; irq = platform_get_irq(pdev, 0); @@ -379,17 +456,24 @@ static int cht_wc_i2c_adap_i2c_probe(struct platform_device *pdev) if (ret) goto remove_irq_domain; - /* - * Normally the Whiskey Cove PMIC is paired with a TI bq24292i charger, - * connected to this i2c bus, and a max17047 fuel-gauge and a fusb302 - * USB Type-C controller connected to another i2c bus. In this setup - * the max17047 and fusb302 devices are enumerated through an INT33FE - * ACPI device. If this device is present register an i2c-client for - * the TI bq24292i charger. - */ - if (acpi_dev_present("INT33FE", NULL, -1)) { - board_info.irq = adap->client_irq; - adap->client = i2c_new_client_device(&adap->adapter, &board_info); + switch (pmic->cht_wc_model) { + case INTEL_CHT_WC_GPD_WIN_POCKET: + board_info = &gpd_win_board_info; + break; + case INTEL_CHT_WC_XIAOMI_MIPAD2: + board_info = &xiaomi_mipad2_board_info; + break; + case INTEL_CHT_WC_LENOVO_YOGABOOK1: + board_info = &lenovo_yogabook1_board_info; + break; + default: + dev_warn(&pdev->dev, "Unknown model, not instantiating charger device\n"); + break; + } + + if (board_info) { + board_info->irq = adap->client_irq; + adap->client = i2c_new_client_device(&adap->adapter, board_info); if (IS_ERR(adap->client)) { ret = PTR_ERR(adap->client); goto del_adapter; From b910a9cea8aa54d39275ebee8d0f04ff9656ab80 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:07:02 +0100 Subject: [PATCH 28/94] extcon: intel-cht-wc: Use new cht_wc_model intel_soc_pmic field The CHT_WC_VBUS_GPIO_CTLO GPIO actually driving an external 5V Vboost converter for Vbus depends on the board on which the Cherry Trail - Whiskey Cove PMIC is actually used. Since the information about the exact PMIC setup is necessary in other places too, struct intel_soc_pmic now has a new cht_wc_model field indicating the board model. Only poke the CHT_WC_VBUS_GPIO_CTLO GPIO if this new field is set to INTEL_CHT_WC_GPD_WIN_POCKET, which indicates the Type-C (with PD and DP-altmode) setup used on the GPD pocket and GPD win; and on which this GPIO actually controls an external 5V Vboost converter. Acked-by: Chanwoo Choi Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/extcon/extcon-intel-cht-wc.c | 35 +++++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index 771f6f4cf92e..81cae8c75850 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -358,20 +359,26 @@ static int cht_wc_extcon_probe(struct platform_device *pdev) if (IS_ERR(ext->edev)) return PTR_ERR(ext->edev); - /* - * When a host-cable is detected the BIOS enables an external 5v boost - * converter to power connected devices there are 2 problems with this: - * 1) This gets seen by the external battery charger as a valid Vbus - * supply and it then tries to feed Vsys from this creating a - * feedback loop which causes aprox. 300 mA extra battery drain - * (and unless we drive the external-charger-disable pin high it - * also tries to charge the battery causing even more feedback). - * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply - * Since the external battery charger has its own 5v boost converter - * which does not have these issues, we simply turn the separate - * external 5v boost converter off and leave it off entirely. - */ - cht_wc_extcon_set_5v_boost(ext, false); + switch (pmic->cht_wc_model) { + case INTEL_CHT_WC_GPD_WIN_POCKET: + /* + * When a host-cable is detected the BIOS enables an external 5v boost + * converter to power connected devices there are 2 problems with this: + * 1) This gets seen by the external battery charger as a valid Vbus + * supply and it then tries to feed Vsys from this creating a + * feedback loop which causes aprox. 300 mA extra battery drain + * (and unless we drive the external-charger-disable pin high it + * also tries to charge the battery causing even more feedback). + * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply + * Since the external battery charger has its own 5v boost converter + * which does not have these issues, we simply turn the separate + * external 5v boost converter off and leave it off entirely. + */ + cht_wc_extcon_set_5v_boost(ext, false); + break; + default: + break; + } /* Enable sw control */ ret = cht_wc_extcon_sw_control(ext, true); From b7fa2cd357eded72159a6112e23839c47f4b50c1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:07:03 +0100 Subject: [PATCH 29/94] extcon: intel-cht-wc: Support devs with Micro-B / USB-2 only Type-C connectors So far the extcon-intel-cht-wc code has only been tested on devices with a Type-C connector with USB-PD, USB3 (superspeed) and DP-altmode support through a FUSB302 Type-C controller. Some devices with the intel-cht-wc PMIC however come with an USB-micro-B connector, or an USB-2 only Type-C connector without USB-PD. Which device-model we are running on can be identified with the new cht_wc_model intel_soc_pmic field. On models without a Type-C controller the extcon code must control the Vbus 5V boost converter and the USB role switch depending on the detected cable-type. Acked-by: Chanwoo Choi Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/extcon/Kconfig | 2 + drivers/extcon/extcon-intel-cht-wc.c | 91 ++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index aab87c9b35c8..0d42e49105dd 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -61,6 +61,8 @@ config EXTCON_INTEL_INT3496 config EXTCON_INTEL_CHT_WC tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver" depends on INTEL_SOC_PMIC_CHTWC + depends on USB_SUPPORT + select USB_ROLE_SWITCH help Say Y here to enable extcon support for charger detection / control on the Intel Cherrytrail Whiskey Cove PMIC. diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index 81cae8c75850..3fc5dd06849a 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include #include "extcon-intel.h" @@ -102,8 +104,11 @@ struct cht_wc_extcon_data { struct device *dev; struct regmap *regmap; struct extcon_dev *edev; + struct usb_role_switch *role_sw; + struct regulator *vbus_boost; unsigned int previous_cable; bool usb_host; + bool vbus_boost_enabled; }; static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) @@ -217,6 +222,18 @@ static void cht_wc_extcon_set_otgmode(struct cht_wc_extcon_data *ext, CHT_WC_CHGRCTRL1_OTGMODE, val); if (ret) dev_err(ext->dev, "Error updating CHGRCTRL1 reg: %d\n", ret); + + if (ext->vbus_boost && ext->vbus_boost_enabled != enable) { + if (enable) + ret = regulator_enable(ext->vbus_boost); + else + ret = regulator_disable(ext->vbus_boost); + + if (ret) + dev_err(ext->dev, "Error updating Vbus boost regulator: %d\n", ret); + else + ext->vbus_boost_enabled = enable; + } } static void cht_wc_extcon_enable_charging(struct cht_wc_extcon_data *ext, @@ -246,6 +263,7 @@ static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) unsigned int cable = EXTCON_NONE; /* Ignore errors in host mode, as the 5v boost converter is on then */ bool ignore_get_charger_errors = ext->usb_host; + enum usb_role role; ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); if (ret) { @@ -289,6 +307,18 @@ set_state: ext->usb_host = ((id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A)); extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host); + + if (ext->usb_host) + role = USB_ROLE_HOST; + else if (pwrsrc_sts & CHT_WC_PWRSRC_VBUS) + role = USB_ROLE_DEVICE; + else + role = USB_ROLE_NONE; + + /* Note: this is a no-op when ext->role_sw is NULL */ + ret = usb_role_switch_set_role(ext->role_sw, role); + if (ret) + dev_err(ext->dev, "Error setting USB-role: %d\n", ret); } static irqreturn_t cht_wc_extcon_isr(int irq, void *data) @@ -334,6 +364,61 @@ static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable) return ret; } +static int cht_wc_extcon_find_role_sw(struct cht_wc_extcon_data *ext) +{ + const struct software_node *swnode; + struct fwnode_handle *fwnode; + + swnode = software_node_find_by_name(NULL, "intel-xhci-usb-sw"); + if (!swnode) + return -EPROBE_DEFER; + + fwnode = software_node_fwnode(swnode); + ext->role_sw = usb_role_switch_find_by_fwnode(fwnode); + fwnode_handle_put(fwnode); + + return ext->role_sw ? 0 : -EPROBE_DEFER; +} + +static void cht_wc_extcon_put_role_sw(void *data) +{ + struct cht_wc_extcon_data *ext = data; + + usb_role_switch_put(ext->role_sw); +} + +/* Some boards require controlling the role-sw and Vbus based on the id-pin */ +static int cht_wc_extcon_get_role_sw_and_regulator(struct cht_wc_extcon_data *ext) +{ + int ret; + + ret = cht_wc_extcon_find_role_sw(ext); + if (ret) + return ret; + + ret = devm_add_action_or_reset(ext->dev, cht_wc_extcon_put_role_sw, ext); + if (ret) + return ret; + + /* + * On x86/ACPI platforms the regulator <-> consumer link is provided + * by platform_data passed to the regulator driver. This means that + * this info is not available before the regulator driver has bound. + * Use devm_regulator_get_optional() to avoid getting a dummy + * regulator and wait for the regulator to show up if necessary. + */ + ext->vbus_boost = devm_regulator_get_optional(ext->dev, "vbus"); + if (IS_ERR(ext->vbus_boost)) { + ret = PTR_ERR(ext->vbus_boost); + if (ret == -ENODEV) + ret = -EPROBE_DEFER; + + return dev_err_probe(ext->dev, ret, "getting Vbus regulator"); + } + + return 0; +} + static int cht_wc_extcon_probe(struct platform_device *pdev) { struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); @@ -376,6 +461,12 @@ static int cht_wc_extcon_probe(struct platform_device *pdev) */ cht_wc_extcon_set_5v_boost(ext, false); break; + case INTEL_CHT_WC_LENOVO_YOGABOOK1: + case INTEL_CHT_WC_XIAOMI_MIPAD2: + ret = cht_wc_extcon_get_role_sw_and_regulator(ext); + if (ret) + return ret; + break; default: break; } From 73b5ae341588ddb6480dff5471e66e4016be26a0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:07:04 +0100 Subject: [PATCH 30/94] extcon: intel-cht-wc: Refactor cht_wc_extcon_get_charger() This is a preparation patch for adding support for registering a power_supply class device. Setting usbsrc to "CHT_WC_USBSRC_TYPE_SDP << CHT_WC_USBSRC_TYPE_SHIFT" will make the following switch-case return EXTCON_CHG_USB_SDP just as before, so there is no functional change. Acked-by: Chanwoo Choi Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/extcon/extcon-intel-cht-wc.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index 3fc5dd06849a..a828ca89ca7f 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -153,14 +153,15 @@ static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext, } while (time_before(jiffies, timeout)); if (status != CHT_WC_USBSRC_STS_SUCCESS) { - if (ignore_errors) - return EXTCON_CHG_USB_SDP; /* Save fallback */ + if (!ignore_errors) { + if (status == CHT_WC_USBSRC_STS_FAIL) + dev_warn(ext->dev, "Could not detect charger type\n"); + else + dev_warn(ext->dev, "Timeout detecting charger type\n"); + } - if (status == CHT_WC_USBSRC_STS_FAIL) - dev_warn(ext->dev, "Could not detect charger type\n"); - else - dev_warn(ext->dev, "Timeout detecting charger type\n"); - return EXTCON_CHG_USB_SDP; /* Save fallback */ + /* Safe fallback */ + usbsrc = CHT_WC_USBSRC_TYPE_SDP << CHT_WC_USBSRC_TYPE_SHIFT; } usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT; From 66e31186cd2aa7ee325b64555321e2ba06f141ab Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:07:05 +0100 Subject: [PATCH 31/94] extcon: intel-cht-wc: Add support for registering a power_supply class-device The bq25890 used on the Yogabook YB1-X90 / -X91 models relies on the extcon-driver's BC-1.2 charger detection, and the bq25890 driver expect this info to be available through a parent power_supply class-device which models the detected charger (idem to how the Type-C TCPM code registers a power_supply classdev for the connected charger). Add support for registering the power_supply class-device expected by this setup. Acked-by: Chanwoo Choi Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/extcon/extcon-intel-cht-wc.c | 81 ++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index a828ca89ca7f..cf74acb92fe3 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,8 @@ struct cht_wc_extcon_data { struct extcon_dev *edev; struct usb_role_switch *role_sw; struct regulator *vbus_boost; + struct power_supply *psy; + enum power_supply_usb_type usb_type; unsigned int previous_cable; bool usb_host; bool vbus_boost_enabled; @@ -170,18 +173,23 @@ static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext, dev_warn(ext->dev, "Unhandled charger type %d, defaulting to SDP\n", ret); + ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP; return EXTCON_CHG_USB_SDP; case CHT_WC_USBSRC_TYPE_SDP: case CHT_WC_USBSRC_TYPE_FLOATING: case CHT_WC_USBSRC_TYPE_OTHER: + ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP; return EXTCON_CHG_USB_SDP; case CHT_WC_USBSRC_TYPE_CDP: + ext->usb_type = POWER_SUPPLY_USB_TYPE_CDP; return EXTCON_CHG_USB_CDP; case CHT_WC_USBSRC_TYPE_DCP: case CHT_WC_USBSRC_TYPE_DCP_EXTPHY: case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */ + ext->usb_type = POWER_SUPPLY_USB_TYPE_DCP; return EXTCON_CHG_USB_DCP; case CHT_WC_USBSRC_TYPE_ACA: + ext->usb_type = POWER_SUPPLY_USB_TYPE_ACA; return EXTCON_CHG_USB_ACA; } } @@ -266,6 +274,8 @@ static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) bool ignore_get_charger_errors = ext->usb_host; enum usb_role role; + ext->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; + ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); if (ret) { dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); @@ -320,6 +330,9 @@ set_state: ret = usb_role_switch_set_role(ext->role_sw, role); if (ret) dev_err(ext->dev, "Error setting USB-role: %d\n", ret); + + if (ext->psy) + power_supply_changed(ext->psy); } static irqreturn_t cht_wc_extcon_isr(int irq, void *data) @@ -420,6 +433,59 @@ static int cht_wc_extcon_get_role_sw_and_regulator(struct cht_wc_extcon_data *ex return 0; } +static int cht_wc_extcon_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct cht_wc_extcon_data *ext = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = ext->usb_type; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ext->usb_type ? 1 : 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const enum power_supply_usb_type cht_wc_extcon_psy_usb_types[] = { + POWER_SUPPLY_USB_TYPE_SDP, + POWER_SUPPLY_USB_TYPE_CDP, + POWER_SUPPLY_USB_TYPE_DCP, + POWER_SUPPLY_USB_TYPE_ACA, + POWER_SUPPLY_USB_TYPE_UNKNOWN, +}; + +static const enum power_supply_property cht_wc_extcon_psy_props[] = { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc cht_wc_extcon_psy_desc = { + .name = "cht_wcove_pwrsrc", + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = cht_wc_extcon_psy_usb_types, + .num_usb_types = ARRAY_SIZE(cht_wc_extcon_psy_usb_types), + .properties = cht_wc_extcon_psy_props, + .num_properties = ARRAY_SIZE(cht_wc_extcon_psy_props), + .get_property = cht_wc_extcon_psy_get_prop, +}; + +static int cht_wc_extcon_register_psy(struct cht_wc_extcon_data *ext) +{ + struct power_supply_config psy_cfg = { .drv_data = ext }; + + ext->psy = devm_power_supply_register(ext->dev, + &cht_wc_extcon_psy_desc, + &psy_cfg); + return PTR_ERR_OR_ZERO(ext->psy); +} + static int cht_wc_extcon_probe(struct platform_device *pdev) { struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); @@ -463,6 +529,21 @@ static int cht_wc_extcon_probe(struct platform_device *pdev) cht_wc_extcon_set_5v_boost(ext, false); break; case INTEL_CHT_WC_LENOVO_YOGABOOK1: + /* Do this first, as it may very well return -EPROBE_DEFER. */ + ret = cht_wc_extcon_get_role_sw_and_regulator(ext); + if (ret) + return ret; + /* + * The bq25890 used here relies on this driver's BC-1.2 charger + * detection, and the bq25890 driver expect this info to be + * available through a parent power_supply class device which + * models the detected charger (idem to how the Type-C TCPM code + * registers a power_supply classdev for the connected charger). + */ + ret = cht_wc_extcon_register_psy(ext); + if (ret) + return ret; + break; case INTEL_CHT_WC_XIAOMI_MIPAD2: ret = cht_wc_extcon_get_role_sw_and_regulator(ext); if (ret) From 21356ac14e054ee86a34008e2c59bd3f1b42d7bf Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 1 Feb 2022 14:07:06 +0100 Subject: [PATCH 32/94] extcon: intel-cht-wc: Report RID_A for ACA adapters Make cht_wc_extcon_get_id() report RID_A for ACA adapters, instead of reporting ID_FLOAT. According to the spec. we should read the USB-ID pin ADC value to determine the resistance of the used pull-down resister and then return RID_A / RID_B / RID_C based on this. But all "Accessory Charger Adapter"s (ACAs) which users can actually buy always use a combination of a charging port with one or more USB-A ports, so they should always use a resistor indicating RID_A. But the spec is hard to read / badly-worded so some of them actually indicate they are a RID_B ACA even though they clearly are a RID_A ACA. To workaround this simply always return INTEL_USB_RID_A, which matches all the ACAs which users can actually buy. Acked-by: Chanwoo Choi Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/extcon/extcon-intel-cht-wc.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index cf74acb92fe3..89a6449e3f4a 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -121,13 +121,21 @@ static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) return INTEL_USB_ID_GND; case CHT_WC_PWRSRC_RID_FLOAT: return INTEL_USB_ID_FLOAT; + /* + * According to the spec. we should read the USB-ID pin ADC value here + * to determine the resistance of the used pull-down resister and then + * return RID_A / RID_B / RID_C based on this. But all "Accessory + * Charger Adapter"s (ACAs) which users can actually buy always use + * a combination of a charging port with one or more USB-A ports, so + * they should always use a resistor indicating RID_A. But the spec + * is hard to read / badly-worded so some of them actually indicate + * they are a RID_B ACA evnen though they clearly are a RID_A ACA. + * To workaround this simply always return INTEL_USB_RID_A, which + * matches all the ACAs which users can actually buy. + */ case CHT_WC_PWRSRC_RID_ACA: + return INTEL_USB_RID_A; default: - /* - * Once we have IIO support for the GPADC we should read - * the USBID GPADC channel here and determine ACA role - * based on that. - */ return INTEL_USB_ID_FLOAT; } } From 784056d36f2779bce7e345285cfa1e097a9136bb Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 2 Feb 2022 09:12:46 +0000 Subject: [PATCH 33/94] power: supply: axp288_fuel_gauge: Fix spelling mistake "resisitor" -> "resistor" There is a spelling mistake in a MODULE_PARM_DESC description. Fix it. Signed-off-by: Colin Ian King Acked-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index dcedbc59732d..13be2c1d6528 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -91,7 +91,7 @@ static bool no_current_sense_res; module_param(no_current_sense_res, bool, 0444); -MODULE_PARM_DESC(no_current_sense_res, "No (or broken) current sense resisitor"); +MODULE_PARM_DESC(no_current_sense_res, "No (or broken) current sense resistor"); enum { QWBTU_IRQ = 0, From 7562ccd85ffb3fac15d731df1ea5c8974485626e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 19:35:36 +0100 Subject: [PATCH 34/94] power: supply: core: Use fwnode_property_*() in power_supply_get_battery_info() Switch power_supply_get_battery_info() over to use the generic fwnode_property_*() property read functions. This is a preparation patch for adding support for reading properties from other fwnode types such as swnode properties added by platform code on x86 devices. Note the parsing of the 2d matrix "ocv-capacity-table-%d" and "resistance-temp-table" properties is not converted since this depends on the raw of_get_property() accessor function of which there is no fwnode_property_*() equivalent AFAICT. This means that these properties will not be supported in swnodes for now. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 69 ++++++++++++++---------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index df4471e50d33..fd08c018c18e 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -572,9 +572,11 @@ int power_supply_get_battery_info(struct power_supply *psy, struct power_supply_resistance_temp_table *resist_table; struct power_supply_battery_info *info; struct device_node *battery_np; + struct fwnode_handle *fwnode; const char *value; int err, len, index; const __be32 *list; + u32 min_max[2]; info = devm_kmalloc(&psy->dev, sizeof(*info), GFP_KERNEL); if (!info) @@ -618,7 +620,9 @@ int power_supply_get_battery_info(struct power_supply *psy, if (!battery_np) return -ENODEV; - err = of_property_read_string(battery_np, "compatible", &value); + fwnode = of_fwnode_handle(battery_np); + + err = fwnode_property_read_string(fwnode, "compatible", &value); if (err) goto out_put_node; @@ -632,7 +636,7 @@ int power_supply_get_battery_info(struct power_supply *psy, * Documentation/power/power_supply_class.rst. */ - if (!of_property_read_string(battery_np, "device-chemistry", &value)) { + if (!fwnode_property_read_string(fwnode, "device-chemistry", &value)) { if (!strcmp("nickel-cadmium", value)) info->technology = POWER_SUPPLY_TECHNOLOGY_NiCd; else if (!strcmp("nickel-metal-hydride", value)) @@ -650,45 +654,56 @@ int power_supply_get_battery_info(struct power_supply *psy, dev_warn(&psy->dev, "%s unknown battery type\n", value); } - of_property_read_u32(battery_np, "energy-full-design-microwatt-hours", + fwnode_property_read_u32(fwnode, "energy-full-design-microwatt-hours", &info->energy_full_design_uwh); - of_property_read_u32(battery_np, "charge-full-design-microamp-hours", + fwnode_property_read_u32(fwnode, "charge-full-design-microamp-hours", &info->charge_full_design_uah); - of_property_read_u32(battery_np, "voltage-min-design-microvolt", + fwnode_property_read_u32(fwnode, "voltage-min-design-microvolt", &info->voltage_min_design_uv); - of_property_read_u32(battery_np, "voltage-max-design-microvolt", + fwnode_property_read_u32(fwnode, "voltage-max-design-microvolt", &info->voltage_max_design_uv); - of_property_read_u32(battery_np, "trickle-charge-current-microamp", + fwnode_property_read_u32(fwnode, "trickle-charge-current-microamp", &info->tricklecharge_current_ua); - of_property_read_u32(battery_np, "precharge-current-microamp", + fwnode_property_read_u32(fwnode, "precharge-current-microamp", &info->precharge_current_ua); - of_property_read_u32(battery_np, "precharge-upper-limit-microvolt", + fwnode_property_read_u32(fwnode, "precharge-upper-limit-microvolt", &info->precharge_voltage_max_uv); - of_property_read_u32(battery_np, "charge-term-current-microamp", + fwnode_property_read_u32(fwnode, "charge-term-current-microamp", &info->charge_term_current_ua); - of_property_read_u32(battery_np, "re-charge-voltage-microvolt", + fwnode_property_read_u32(fwnode, "re-charge-voltage-microvolt", &info->charge_restart_voltage_uv); - of_property_read_u32(battery_np, "over-voltage-threshold-microvolt", + fwnode_property_read_u32(fwnode, "over-voltage-threshold-microvolt", &info->overvoltage_limit_uv); - of_property_read_u32(battery_np, "constant-charge-current-max-microamp", + fwnode_property_read_u32(fwnode, "constant-charge-current-max-microamp", &info->constant_charge_current_max_ua); - of_property_read_u32(battery_np, "constant-charge-voltage-max-microvolt", + fwnode_property_read_u32(fwnode, "constant-charge-voltage-max-microvolt", &info->constant_charge_voltage_max_uv); - of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms", + fwnode_property_read_u32(fwnode, "factory-internal-resistance-micro-ohms", &info->factory_internal_resistance_uohm); - of_property_read_u32_index(battery_np, "ambient-celsius", - 0, &info->temp_ambient_alert_min); - of_property_read_u32_index(battery_np, "ambient-celsius", - 1, &info->temp_ambient_alert_max); - of_property_read_u32_index(battery_np, "alert-celsius", - 0, &info->temp_alert_min); - of_property_read_u32_index(battery_np, "alert-celsius", - 1, &info->temp_alert_max); - of_property_read_u32_index(battery_np, "operating-range-celsius", - 0, &info->temp_min); - of_property_read_u32_index(battery_np, "operating-range-celsius", - 1, &info->temp_max); + if (!fwnode_property_read_u32_array(fwnode, "ambient-celsius", + min_max, ARRAY_SIZE(min_max))) { + info->temp_ambient_alert_min = min_max[0]; + info->temp_ambient_alert_max = min_max[1]; + } + if (!fwnode_property_read_u32_array(fwnode, "alert-celsius", + min_max, ARRAY_SIZE(min_max))) { + info->temp_alert_min = min_max[0]; + info->temp_alert_max = min_max[1]; + } + if (!fwnode_property_read_u32_array(fwnode, "operating-range-celsius", + min_max, ARRAY_SIZE(min_max))) { + info->temp_min = min_max[0]; + info->temp_max = min_max[1]; + } + + /* + * The below code uses raw of-data parsing to parse + * /schemas/types.yaml#/definitions/uint32-matrix + * data, so for now this is only support with of. + */ + if (!battery_np) + goto out_ret_pointer; len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius"); if (len < 0 && len != -EINVAL) { From c76787b0d8d2b8660e880d17fbe6fac22e8d35f5 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 19:35:37 +0100 Subject: [PATCH 35/94] power: supply: core: Add support for generic fwnodes to power_supply_get_battery_info() Add support to power_supply_get_battery_info() to read the properties from other fwnode types such as swnodes added by platform code on x86 devices. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 28 ++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index fd08c018c18e..1a2440e212a7 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -571,7 +571,8 @@ int power_supply_get_battery_info(struct power_supply *psy, { struct power_supply_resistance_temp_table *resist_table; struct power_supply_battery_info *info; - struct device_node *battery_np; + struct device_node *battery_np = NULL; + struct fwnode_reference_args args; struct fwnode_handle *fwnode; const char *value; int err, len, index; @@ -610,18 +611,22 @@ int power_supply_get_battery_info(struct power_supply *psy, info->ocv_table_size[index] = -EINVAL; } - if (!psy->of_node) { - dev_warn(&psy->dev, "%s currently only supports devicetree\n", - __func__); - return -ENXIO; + if (psy->of_node) { + battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0); + if (!battery_np) + return -ENODEV; + + fwnode = fwnode_handle_get(of_fwnode_handle(battery_np)); + } else { + err = fwnode_property_get_reference_args( + dev_fwnode(psy->dev.parent), + "monitored-battery", NULL, 0, 0, &args); + if (err) + return err; + + fwnode = args.fwnode; } - battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0); - if (!battery_np) - return -ENODEV; - - fwnode = of_fwnode_handle(battery_np); - err = fwnode_property_read_string(fwnode, "compatible", &value); if (err) goto out_put_node; @@ -778,6 +783,7 @@ out_ret_pointer: *info_out = info; out_put_node: + fwnode_handle_put(fwnode); of_node_put(battery_np); return err; } From 5e8c1dc78ccf37fc804df5415a8426ca9d19d51a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 19:35:38 +0100 Subject: [PATCH 36/94] power: supply: bq24190_charger: Turn off 5V boost regulator on shutdown Turn off the 5V boost regulator on shutdown, there are 3 reasons for doing this: 1. It drains he battery if left on 2. If left on the device will not charge when plugged into a charger 3. If left on and the powered peripheral attached to a Type-C port is removed before the next boot, then the Type-C port-controller will see VBus being present while nothing is attached confusing the TCPM state-machine. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index a1c957a26f07..7414830a70e4 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -497,10 +497,8 @@ static ssize_t bq24190_sysfs_store(struct device *dev, } #endif -#ifdef CONFIG_REGULATOR -static int bq24190_set_charge_mode(struct regulator_dev *dev, u8 val) +static int bq24190_set_charge_mode(struct bq24190_dev_info *bdi, u8 val) { - struct bq24190_dev_info *bdi = rdev_get_drvdata(dev); int ret; ret = pm_runtime_get_sync(bdi->dev); @@ -520,14 +518,17 @@ static int bq24190_set_charge_mode(struct regulator_dev *dev, u8 val) return ret; } +#ifdef CONFIG_REGULATOR static int bq24190_vbus_enable(struct regulator_dev *dev) { - return bq24190_set_charge_mode(dev, BQ24190_REG_POC_CHG_CONFIG_OTG); + return bq24190_set_charge_mode(rdev_get_drvdata(dev), + BQ24190_REG_POC_CHG_CONFIG_OTG); } static int bq24190_vbus_disable(struct regulator_dev *dev) { - return bq24190_set_charge_mode(dev, BQ24190_REG_POC_CHG_CONFIG_CHARGE); + return bq24190_set_charge_mode(rdev_get_drvdata(dev), + BQ24190_REG_POC_CHG_CONFIG_CHARGE); } static int bq24190_vbus_is_enabled(struct regulator_dev *dev) @@ -1870,6 +1871,14 @@ static int bq24190_remove(struct i2c_client *client) return 0; } +static void bq24190_shutdown(struct i2c_client *client) +{ + struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + + /* Turn off 5V boost regulator on shutdown */ + bq24190_set_charge_mode(bdi, BQ24190_REG_POC_CHG_CONFIG_CHARGE); +} + static __maybe_unused int bq24190_runtime_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -1980,6 +1989,7 @@ MODULE_DEVICE_TABLE(of, bq24190_of_match); static struct i2c_driver bq24190_driver = { .probe = bq24190_probe, .remove = bq24190_remove, + .shutdown = bq24190_shutdown, .id_table = bq24190_i2c_ids, .driver = { .name = "bq24190-charger", From a314fae312639876ff00788c6b8a5dca55be3190 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 19:35:39 +0100 Subject: [PATCH 37/94] power: supply: bq24190_charger: Always call power_supply_get_battery_info() power_supply_get_battery_info() now also supports getting battery_info on boards not using dt/of. Remove the of_node check. If neither of nor other battery-info is present the function will fail making this change a no-op in that case. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 7414830a70e4..83873119ba24 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -1693,8 +1693,7 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi) dev_warn(bdi->dev, "invalid value for %s: %u\n", s, v); } - if (bdi->dev->of_node && - !power_supply_get_battery_info(bdi->charger, &info)) { + if (!power_supply_get_battery_info(bdi->charger, &info)) { v = info->precharge_current_ua / 1000; if (v >= BQ24190_REG_PCTCC_IPRECHG_MIN && v <= BQ24190_REG_PCTCC_IPRECHG_MAX) From 73633ccbba38b9e7bc1ac0527f52cb421beb68f1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 19:35:40 +0100 Subject: [PATCH 38/94] power: supply: bq24190_charger: Store ichg-max and vreg-max in bq24190_dev_info Store ichg-max and vreg-max in bq24190_dev_info once from bq24190_get_config() and drop the bq24190_charger_get_current_max() and bq24190_charger_get_voltage_max() helpers. This is a preparation patch for honoring the constant_charge_current_max_ua and constant_charge_voltage_max_uv values from power_supply_get_battery_info(). Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 34 ++++++++++---------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 83873119ba24..cb36ccbb731a 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -165,6 +165,8 @@ struct bq24190_dev_info { u16 sys_min; u16 iprechg; u16 iterm; + u32 ichg_max; + u32 vreg_max; struct mutex f_reg_lock; u8 f_reg; u8 ss_reg; @@ -977,15 +979,6 @@ static int bq24190_charger_get_current(struct bq24190_dev_info *bdi, return 0; } -static int bq24190_charger_get_current_max(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - int idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1; - - val->intval = bq24190_ccc_ichg_values[idx]; - return 0; -} - static int bq24190_charger_set_current(struct bq24190_dev_info *bdi, const union power_supply_propval *val) { @@ -1024,15 +1017,6 @@ static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi, return 0; } -static int bq24190_charger_get_voltage_max(struct bq24190_dev_info *bdi, - union power_supply_propval *val) -{ - int idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1; - - val->intval = bq24190_cvc_vreg_values[idx]; - return 0; -} - static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi, const union power_supply_propval *val) { @@ -1109,13 +1093,15 @@ static int bq24190_charger_get_property(struct power_supply *psy, ret = bq24190_charger_get_current(bdi, val); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - ret = bq24190_charger_get_current_max(bdi, val); + val->intval = bdi->ichg_max; + ret = 0; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ret = bq24190_charger_get_voltage(bdi, val); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: - ret = bq24190_charger_get_voltage_max(bdi, val); + val->intval = bdi->vreg_max; + ret = 0; break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: ret = bq24190_charger_get_iinlimit(bdi, val); @@ -1682,7 +1668,13 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi) { const char * const s = "ti,system-minimum-microvolt"; struct power_supply_battery_info *info; - int v; + int v, idx; + + idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1; + bdi->ichg_max = bq24190_ccc_ichg_values[idx]; + + idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1; + bdi->vreg_max = bq24190_cvc_vreg_values[idx]; if (device_property_read_u32(bdi->dev, s, &v) == 0) { v /= 1000; From 445c21d2080f6ffaad3482409fe90e8ed22db6a2 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 19:35:41 +0100 Subject: [PATCH 39/94] power: supply: bq24190_charger: Program charger with fwnode supplied ccc_ireg and cvc_vreg So far the bq24190_charger driver has been relying on either the chips default constant_charge_current_max_ua and constant_charge_voltage_max_uv values, or on the BIOS or bootloader to program these for us. This does not happen on all boards, causing e.g. the wrong (too low) values to be used on Lenovo Yoga Tablet 2 830F/L and 1050F/L tablets. If power_supply_get_battery_info() provides us with values for these settings, then program the charger accordingly. And if the user later overrides these values then save the user-values so that these will be restored after a suspend/resume. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 51 +++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index cb36ccbb731a..974e4b6b8d4d 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -165,7 +165,9 @@ struct bq24190_dev_info { u16 sys_min; u16 iprechg; u16 iterm; + u32 ichg; u32 ichg_max; + u32 vreg; u32 vreg_max; struct mutex f_reg_lock; u8 f_reg; @@ -662,6 +664,28 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi) return ret; } + if (bdi->ichg) { + ret = bq24190_set_field_val(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_ICHG_MASK, + BQ24190_REG_CCC_ICHG_SHIFT, + bq24190_ccc_ichg_values, + ARRAY_SIZE(bq24190_ccc_ichg_values), + bdi->ichg); + if (ret < 0) + return ret; + } + + if (bdi->vreg) { + ret = bq24190_set_field_val(bdi, BQ24190_REG_CVC, + BQ24190_REG_CVC_VREG_MASK, + BQ24190_REG_CVC_VREG_SHIFT, + bq24190_cvc_vreg_values, + ARRAY_SIZE(bq24190_cvc_vreg_values), + bdi->vreg); + if (ret < 0) + return ret; + } + return 0; } @@ -995,10 +1019,16 @@ static int bq24190_charger_set_current(struct bq24190_dev_info *bdi, if (v) curr *= 5; - return bq24190_set_field_val(bdi, BQ24190_REG_CCC, + ret = bq24190_set_field_val(bdi, BQ24190_REG_CCC, BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, bq24190_ccc_ichg_values, ARRAY_SIZE(bq24190_ccc_ichg_values), curr); + if (ret < 0) + return ret; + + bdi->ichg = curr; + + return 0; } static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi, @@ -1020,10 +1050,18 @@ static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi, static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi, const union power_supply_propval *val) { - return bq24190_set_field_val(bdi, BQ24190_REG_CVC, + int ret; + + ret = bq24190_set_field_val(bdi, BQ24190_REG_CVC, BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT, bq24190_cvc_vreg_values, ARRAY_SIZE(bq24190_cvc_vreg_values), val->intval); + if (ret < 0) + return ret; + + bdi->vreg = val->intval; + + return 0; } static int bq24190_charger_get_iinlimit(struct bq24190_dev_info *bdi, @@ -1701,6 +1739,15 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi) else dev_warn(bdi->dev, "invalid value for battery:charge-term-current-microamp: %d\n", v); + + /* These are optional, so no warning when not set */ + v = info->constant_charge_current_max_ua; + if (v >= bq24190_ccc_ichg_values[0] && v <= bdi->ichg_max) + bdi->ichg = v; + + v = info->constant_charge_voltage_max_uv; + if (v >= bq24190_cvc_vreg_values[0] && v <= bdi->vreg_max) + bdi->vreg = v; } return 0; From 4e456230f1ba27deed5a2148bc58c85281d214b7 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 19:35:42 +0100 Subject: [PATCH 40/94] power: supply: bq24190_charger: Disallow ccc_ireg and cvc_vreg to be higher then the fwnode values If the fwnode data as parsed by power_supply_get_battery_info() provides max values for ccc_ireg and cvc_vreg then do not allow the user to later set these to higher values then those specified by the firmware, otherwise the battery might get damaged. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 974e4b6b8d4d..04aa25f2d033 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -1019,6 +1019,9 @@ static int bq24190_charger_set_current(struct bq24190_dev_info *bdi, if (v) curr *= 5; + if (curr > bdi->ichg_max) + return -EINVAL; + ret = bq24190_set_field_val(bdi, BQ24190_REG_CCC, BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, bq24190_ccc_ichg_values, @@ -1052,6 +1055,9 @@ static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi, { int ret; + if (val->intval > bdi->vreg_max) + return -EINVAL; + ret = bq24190_set_field_val(bdi, BQ24190_REG_CVC, BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT, bq24190_cvc_vreg_values, @@ -1743,11 +1749,11 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi) /* These are optional, so no warning when not set */ v = info->constant_charge_current_max_ua; if (v >= bq24190_ccc_ichg_values[0] && v <= bdi->ichg_max) - bdi->ichg = v; + bdi->ichg = bdi->ichg_max = v; v = info->constant_charge_voltage_max_uv; if (v >= bq24190_cvc_vreg_values[0] && v <= bdi->vreg_max) - bdi->vreg = v; + bdi->vreg = bdi->vreg_max = v; } return 0; From f059b46e6fc3eb07e54e2a94912740da49f1d652 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 19:35:43 +0100 Subject: [PATCH 41/94] power: supply: ug3105_battery: Add driver for uPI uG3105 battery monitor Add a new battery driver for the uPI uG3105 battery monitor. Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead it is expected to be use in combination with some always on microcontroller reading its coulomb-counter before it can wrap (must be read every 400 seconds!). Since Linux does not monitor coulomb-counter changes while the device is off or suspended, the coulomb counter is not used atm. So far this driver is only used on x86/ACPI (non devicetree) devs (also note there is no of_match table). Therefor there is no devicetree bindings documentation for this driver's "upisemi,rsns-microohm" property since this is not used in actual devicetree files and the dt bindings maintainers have requested properties with no actual dt users to _not_ be added to the dt bindings. The property's name has been chosen so that it should not need to be changed if/when devicetree enumeration support gets added later, as it mirrors "maxim,rsns-microohm" from the "maxim,max17042" bindings. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 15 + drivers/power/supply/Makefile | 1 + drivers/power/supply/ug3105_battery.c | 486 ++++++++++++++++++++++++++ 3 files changed, 502 insertions(+) create mode 100644 drivers/power/supply/ug3105_battery.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index b366e2fd8e97..6b15eb184072 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -866,4 +866,19 @@ config CHARGER_SURFACE Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, Surface Book 3, and Surface Laptop Go. +config BATTERY_UG3105 + tristate "uPI uG3105 battery monitor driver" + depends on I2C + help + Battery monitor driver for the uPI uG3105 battery monitor. + + Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead + it is expected to be use in combination with some always on + microcontroller reading its coulomb-counter before it can wrap + (it must be read every 400 seconds!). + + Since Linux does not monitor coulomb-counter changes while the + device is off or suspended, the functionality of this driver is + limited to reporting capacity only. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 2c1b264b2046..edf983676799 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -105,3 +105,4 @@ obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o +obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o diff --git a/drivers/power/supply/ug3105_battery.c b/drivers/power/supply/ug3105_battery.c new file mode 100644 index 000000000000..fbc966842509 --- /dev/null +++ b/drivers/power/supply/ug3105_battery.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Battery monitor driver for the uPI uG3105 battery monitor + * + * Note the uG3105 is not a full-featured autonomous fuel-gauge. Instead it is + * expected to be use in combination with some always on microcontroller reading + * its coulomb-counter before it can wrap (must be read every 400 seconds!). + * + * Since Linux does not monitor coulomb-counter changes while the device + * is off or suspended, the coulomb counter is not used atm. + * + * Possible improvements: + * 1. Activate commented out total_coulomb_count code + * 2. Reset total_coulomb_count val to 0 when the battery is as good as empty + * and remember that we did this (and clear the flag for this on susp/resume) + * 3. When the battery is full check if the flag that we set total_coulomb_count + * to when the battery was empty is set. If so we now know the capacity, + * not the design, but actual capacity, of the battery + * 4. Add some mechanism (needs userspace help, or maybe use efivar?) to remember + * the actual capacity of the battery over reboots + * 5. When we know the actual capacity at probe time, add energy_now and + * energy_full attributes. Guess boot + resume energy_now value based on ocv + * and then use total_coulomb_count to report energy_now over time, resetting + * things to adjust for drift when empty/full. This should give more accurate + * readings, esp. in the 30-70% range and allow userspace to estimate time + * remaining till empty/full + * 6. Maybe unregister + reregister the psy device when we learn the actual + * capacity during run-time ? + * + * The above will also require some sort of mwh_per_unit calculation. Testing + * has shown that an estimated 7404mWh increase of the battery's energy results + * in a total_coulomb_count increase of 3277 units with a 5 milli-ohm sense R. + * + * Copyright (C) 2021 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define UG3105_MOV_AVG_WINDOW 8 +#define UG3105_INIT_POLL_TIME (5 * HZ) +#define UG3105_POLL_TIME (30 * HZ) +#define UG3105_SETTLE_TIME (1 * HZ) + +#define UG3105_INIT_POLL_COUNT 30 + +#define UG3105_REG_MODE 0x00 +#define UG3105_REG_CTRL1 0x01 +#define UG3105_REG_COULOMB_CNT 0x02 +#define UG3105_REG_BAT_VOLT 0x08 +#define UG3105_REG_BAT_CURR 0x0c + +#define UG3105_MODE_STANDBY 0x00 +#define UG3105_MODE_RUN 0x10 + +#define UG3105_CTRL1_RESET_COULOMB_CNT 0x03 + +#define UG3105_CURR_HYST_UA 65000 + +#define UG3105_LOW_BAT_UV 3700000 +#define UG3105_FULL_BAT_HYST_UV 38000 + +struct ug3105_chip { + struct i2c_client *client; + struct power_supply *psy; + struct power_supply_battery_info *info; + struct delayed_work work; + struct mutex lock; + int ocv[UG3105_MOV_AVG_WINDOW]; /* micro-volt */ + int intern_res[UG3105_MOV_AVG_WINDOW]; /* milli-ohm */ + int poll_count; + int ocv_avg_index; + int ocv_avg; /* micro-volt */ + int intern_res_poll_count; + int intern_res_avg_index; + int intern_res_avg; /* milli-ohm */ + int volt; /* micro-volt */ + int curr; /* micro-ampere */ + int total_coulomb_count; + int uv_per_unit; + int ua_per_unit; + int status; + int capacity; + bool supplied; +}; + +static int ug3105_read_word(struct i2c_client *client, u8 reg) +{ + int val; + + val = i2c_smbus_read_word_data(client, reg); + if (val < 0) + dev_err(&client->dev, "Error reading reg 0x%02x\n", reg); + + return val; +} + +static int ug3105_get_status(struct ug3105_chip *chip) +{ + int full = chip->info->constant_charge_voltage_max_uv - UG3105_FULL_BAT_HYST_UV; + + if (chip->curr > UG3105_CURR_HYST_UA) + return POWER_SUPPLY_STATUS_CHARGING; + + if (chip->curr < -UG3105_CURR_HYST_UA) + return POWER_SUPPLY_STATUS_DISCHARGING; + + if (chip->supplied && chip->ocv_avg > full) + return POWER_SUPPLY_STATUS_FULL; + + return POWER_SUPPLY_STATUS_NOT_CHARGING; +} + +static int ug3105_get_capacity(struct ug3105_chip *chip) +{ + /* + * OCV voltages in uV for 0-110% in 5% increments, the 100-110% is + * for LiPo HV (High-Voltage) bateries which can go up to 4.35V + * instead of the usual 4.2V. + */ + static const int ocv_capacity_tbl[23] = { + 3350000, + 3610000, + 3690000, + 3710000, + 3730000, + 3750000, + 3770000, + 3786667, + 3803333, + 3820000, + 3836667, + 3853333, + 3870000, + 3907500, + 3945000, + 3982500, + 4020000, + 4075000, + 4110000, + 4150000, + 4200000, + 4250000, + 4300000, + }; + int i, ocv_diff, ocv_step; + + if (chip->ocv_avg < ocv_capacity_tbl[0]) + return 0; + + if (chip->status == POWER_SUPPLY_STATUS_FULL) + return 100; + + for (i = 1; i < ARRAY_SIZE(ocv_capacity_tbl); i++) { + if (chip->ocv_avg > ocv_capacity_tbl[i]) + continue; + + ocv_diff = ocv_capacity_tbl[i] - chip->ocv_avg; + ocv_step = ocv_capacity_tbl[i] - ocv_capacity_tbl[i - 1]; + /* scale 0-110% down to 0-100% for LiPo HV */ + if (chip->info->constant_charge_voltage_max_uv >= 4300000) + return (i * 500 - ocv_diff * 500 / ocv_step) / 110; + else + return i * 5 - ocv_diff * 5 / ocv_step; + } + + return 100; +} + +static void ug3105_work(struct work_struct *work) +{ + struct ug3105_chip *chip = container_of(work, struct ug3105_chip, + work.work); + int i, val, curr_diff, volt_diff, res, win_size; + bool prev_supplied = chip->supplied; + int prev_status = chip->status; + int prev_volt = chip->volt; + int prev_curr = chip->curr; + struct power_supply *psy; + + mutex_lock(&chip->lock); + + psy = chip->psy; + if (!psy) + goto out; + + val = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); + if (val < 0) + goto out; + chip->volt = val * chip->uv_per_unit; + + val = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); + if (val < 0) + goto out; + chip->curr = (s16)val * chip->ua_per_unit; + + chip->ocv[chip->ocv_avg_index] = + chip->volt - chip->curr * chip->intern_res_avg / 1000; + chip->ocv_avg_index = (chip->ocv_avg_index + 1) % UG3105_MOV_AVG_WINDOW; + chip->poll_count++; + + /* + * See possible improvements comment above. + * + * Read + reset coulomb counter every 10 polls (every 300 seconds) + * if ((chip->poll_count % 10) == 0) { + * val = ug3105_read_word(chip->client, UG3105_REG_COULOMB_CNT); + * if (val < 0) + * goto out; + * + * i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, + * UG3105_CTRL1_RESET_COULOMB_CNT); + * + * chip->total_coulomb_count += (s16)val; + * dev_dbg(&chip->client->dev, "coulomb count %d total %d\n", + * (s16)val, chip->total_coulomb_count); + * } + */ + + chip->ocv_avg = 0; + win_size = min(chip->poll_count, UG3105_MOV_AVG_WINDOW); + for (i = 0; i < win_size; i++) + chip->ocv_avg += chip->ocv[i]; + chip->ocv_avg /= win_size; + + chip->supplied = power_supply_am_i_supplied(psy); + chip->status = ug3105_get_status(chip); + chip->capacity = ug3105_get_capacity(chip); + + /* + * Skip internal resistance calc on charger [un]plug and + * when the battery is almost empty (voltage low). + */ + if (chip->supplied != prev_supplied || + chip->volt < UG3105_LOW_BAT_UV || + chip->poll_count < 2) + goto out; + + /* + * Assuming that the OCV voltage does not change significantly + * between 2 polls, then we can calculate the internal resistance + * on a significant current change by attributing all voltage + * change between the 2 readings to the internal resistance. + */ + curr_diff = abs(chip->curr - prev_curr); + if (curr_diff < UG3105_CURR_HYST_UA) + goto out; + + volt_diff = abs(chip->volt - prev_volt); + res = volt_diff * 1000 / curr_diff; + + if ((res < (chip->intern_res_avg * 2 / 3)) || + (res > (chip->intern_res_avg * 4 / 3))) { + dev_dbg(&chip->client->dev, "Ignoring outlier internal resistance %d mOhm\n", res); + goto out; + } + + dev_dbg(&chip->client->dev, "Internal resistance %d mOhm\n", res); + + chip->intern_res[chip->intern_res_avg_index] = res; + chip->intern_res_avg_index = (chip->intern_res_avg_index + 1) % UG3105_MOV_AVG_WINDOW; + chip->intern_res_poll_count++; + + chip->intern_res_avg = 0; + win_size = min(chip->intern_res_poll_count, UG3105_MOV_AVG_WINDOW); + for (i = 0; i < win_size; i++) + chip->intern_res_avg += chip->intern_res[i]; + chip->intern_res_avg /= win_size; + +out: + mutex_unlock(&chip->lock); + + queue_delayed_work(system_wq, &chip->work, + (chip->poll_count <= UG3105_INIT_POLL_COUNT) ? + UG3105_INIT_POLL_TIME : UG3105_POLL_TIME); + + if (chip->status != prev_status && psy) + power_supply_changed(psy); +} + +static enum power_supply_property ug3105_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int ug3105_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ug3105_chip *chip = power_supply_get_drvdata(psy); + int ret = 0; + + mutex_lock(&chip->lock); + + if (!chip->psy) { + ret = -EAGAIN; + goto out; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = chip->status; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = chip->info->technology; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = ug3105_read_word(chip->client, UG3105_REG_BAT_VOLT); + if (ret < 0) + break; + val->intval = ret * chip->uv_per_unit; + ret = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = chip->ocv_avg; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = ug3105_read_word(chip->client, UG3105_REG_BAT_CURR); + if (ret < 0) + break; + val->intval = (s16)ret * chip->ua_per_unit; + ret = 0; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = chip->capacity; + break; + default: + ret = -EINVAL; + } + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static void ug3105_external_power_changed(struct power_supply *psy) +{ + struct ug3105_chip *chip = power_supply_get_drvdata(psy); + + dev_dbg(&chip->client->dev, "external power changed\n"); + mod_delayed_work(system_wq, &chip->work, UG3105_SETTLE_TIME); +} + +static const struct power_supply_desc ug3105_psy_desc = { + .name = "ug3105_battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = ug3105_get_property, + .external_power_changed = ug3105_external_power_changed, + .properties = ug3105_battery_props, + .num_properties = ARRAY_SIZE(ug3105_battery_props), +}; + +static void ug3105_init(struct ug3105_chip *chip) +{ + chip->poll_count = 0; + chip->ocv_avg_index = 0; + chip->total_coulomb_count = 0; + i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, + UG3105_MODE_RUN); + i2c_smbus_write_byte_data(chip->client, UG3105_REG_CTRL1, + UG3105_CTRL1_RESET_COULOMB_CNT); + queue_delayed_work(system_wq, &chip->work, 0); + flush_delayed_work(&chip->work); +} + +static int ug3105_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = {}; + struct device *dev = &client->dev; + u32 curr_sense_res_uohm = 10000; + struct power_supply *psy; + struct ug3105_chip *chip; + int ret; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + mutex_init(&chip->lock); + ret = devm_delayed_work_autocancel(dev, &chip->work, ug3105_work); + if (ret) + return ret; + + psy_cfg.drv_data = chip; + psy = devm_power_supply_register(dev, &ug3105_psy_desc, &psy_cfg); + if (IS_ERR(psy)) + return PTR_ERR(psy); + + ret = power_supply_get_battery_info(psy, &chip->info); + if (ret) + return ret; + + if (chip->info->factory_internal_resistance_uohm == -EINVAL || + chip->info->constant_charge_voltage_max_uv == -EINVAL) { + dev_err(dev, "error required properties are missing\n"); + return -ENODEV; + } + + device_property_read_u32(dev, "upisemi,rsns-microohm", &curr_sense_res_uohm); + + /* + * DAC maximum is 4.5V divided by 65536 steps + an unknown factor of 10 + * coming from somewhere for some reason (verified with a volt-meter). + */ + chip->uv_per_unit = 45000000/65536; + /* Datasheet says 8.1 uV per unit for the current ADC */ + chip->ua_per_unit = 8100000 / curr_sense_res_uohm; + + /* Use provided internal resistance as start point (in milli-ohm) */ + chip->intern_res_avg = chip->info->factory_internal_resistance_uohm / 1000; + /* Also add it to the internal resistance moving average window */ + chip->intern_res[0] = chip->intern_res_avg; + chip->intern_res_avg_index = 1; + chip->intern_res_poll_count = 1; + + mutex_lock(&chip->lock); + chip->psy = psy; + mutex_unlock(&chip->lock); + + ug3105_init(chip); + + i2c_set_clientdata(client, chip); + return 0; +} + +static int __maybe_unused ug3105_suspend(struct device *dev) +{ + struct ug3105_chip *chip = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&chip->work); + i2c_smbus_write_byte_data(chip->client, UG3105_REG_MODE, + UG3105_MODE_STANDBY); + + return 0; +} + +static int __maybe_unused ug3105_resume(struct device *dev) +{ + struct ug3105_chip *chip = dev_get_drvdata(dev); + + ug3105_init(chip); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(ug3105_pm_ops, ug3105_suspend, + ug3105_resume); + +static const struct i2c_device_id ug3105_id[] = { + { "ug3105" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ug3105_id); + +static struct i2c_driver ug3105_i2c_driver = { + .driver = { + .name = "ug3105", + .pm = &ug3105_pm_ops, + }, + .probe_new = ug3105_probe, + .id_table = ug3105_id, +}; +module_i2c_driver(ug3105_i2c_driver); + +MODULE_AUTHOR("Hans de Goede Date: Sat, 5 Feb 2022 17:40:57 +0100 Subject: [PATCH 42/94] power: supply: core: Simplify hwmon memory allocation Use devm_bitmap_zalloc() instead of hand writing it. Signed-off-by: Christophe JAILLET Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_hwmon.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/power/supply/power_supply_hwmon.c b/drivers/power/supply/power_supply_hwmon.c index bffe6d84c429..a48aa4afb828 100644 --- a/drivers/power/supply/power_supply_hwmon.c +++ b/drivers/power/supply/power_supply_hwmon.c @@ -324,11 +324,6 @@ static const struct hwmon_chip_info power_supply_hwmon_chip_info = { .info = power_supply_hwmon_info, }; -static void power_supply_hwmon_bitmap_free(void *data) -{ - bitmap_free(data); -} - int power_supply_add_hwmon_sysfs(struct power_supply *psy) { const struct power_supply_desc *desc = psy->desc; @@ -349,18 +344,14 @@ int power_supply_add_hwmon_sysfs(struct power_supply *psy) } psyhw->psy = psy; - psyhw->props = bitmap_zalloc(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1, - GFP_KERNEL); + psyhw->props = devm_bitmap_zalloc(dev, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1, + GFP_KERNEL); if (!psyhw->props) { ret = -ENOMEM; goto error; } - ret = devm_add_action_or_reset(dev, power_supply_hwmon_bitmap_free, - psyhw->props); - if (ret) - goto error; - for (i = 0; i < desc->num_properties; i++) { const enum power_supply_property prop = desc->properties[i]; From e83c7204f185c598b5b933ea5511b062b26afb67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sat, 5 Feb 2022 19:35:12 +0100 Subject: [PATCH 43/94] power: supply: rt9455: Don't pass an error code in remove callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ret is not zero there were already one or two error messages emitted about a problem (because rt9455_register_reset() emits a message in most cases then). Passing on that error code to the i2c core only results in another error message. Suppress that by returning zero unconditionally. Signed-off-by: Uwe Kleine-König Signed-off-by: Sebastian Reichel --- drivers/power/supply/rt9455_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index 594bb3b8a4d1..74ee54320e6a 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -1716,7 +1716,7 @@ static int rt9455_remove(struct i2c_client *client) cancel_delayed_work_sync(&info->max_charging_time_work); cancel_delayed_work_sync(&info->batt_presence_work); - return ret; + return 0; } static const struct i2c_device_id rt9455_i2c_id_table[] = { From 1c97db1747b756611f0d26ea7fb2ed2d93f90fcc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 2 Feb 2022 20:23:25 +0200 Subject: [PATCH 44/94] power: supply: core: Use device_property_string_array_count() Use device_property_string_array_count() to get number of strings in a string array property. Signed-off-by: Andy Shevchenko Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 1a2440e212a7..8dbd1197cc62 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -283,8 +283,7 @@ static int power_supply_check_supplies(struct power_supply *psy) if (!psy->dev.parent) return 0; - nval = device_property_read_string_array(psy->dev.parent, - "supplied-from", NULL, 0); + nval = device_property_string_array_count(psy->dev.parent, "supplied-from"); if (nval <= 0) return 0; From d58964be64f3bfc21fe8054dadbf4592cdd4e9c1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:15 +0100 Subject: [PATCH 45/94] power: supply: ab8500: Drop BATCTRL thermal mode The BATCTRL mode reads the temperature of the battery by enabling a certain probing current (7-20 mA) and then measure the voltage of the NTC mounted inside the battery. None of the AB8500 product or the reference designs use this mode. What we use is the so-called BATTEMP mode which enables an internal 230 kOhm pull-up to 1.8 V to the external NTC on pin BatTemp (N16) and then measures the voltage over the NTC using the ADC: 1.8V (VTVOUT) | [ ] 230 kOhm | BatTemp +---------------- ADC Pin N16 | _ |/ [/] NTC _/| | GND Cut out the BATCTRL code to clear the forest and stop maintaining code we can never test. The current inducing method is still used to probe for the battery type using the internal BTI (battery type indicator) on the BatCtrl (C3) pin in a separate code path. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-bm.h | 16 -- drivers/power/supply/ab8500_bmdata.c | 2 - drivers/power/supply/ab8500_btemp.c | 263 +++------------------------ 3 files changed, 24 insertions(+), 257 deletions(-) diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 56a5aaf9a27a..3bc6fd9337d2 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -260,18 +260,6 @@ enum bup_vch_sel { #define BUS_PP_PRECHG_CURRENT_MASK 0x0E #define BUS_POWER_PATH_PRECHG_ENA 0x01 -/* - * ADC for the battery thermistor. - * When using the AB8500_ADC_THERM_BATCTRL the battery ID resistor is combined - * with a NTC resistor to both identify the battery and to measure its - * temperature. Different phone manufactures uses different techniques to both - * identify the battery and to read its temperature. - */ -enum ab8500_adc_therm { - AB8500_ADC_THERM_BATCTRL, - AB8500_ADC_THERM_BATTEMP, -}; - /** * struct ab8500_res_to_temp - defines one point in a temp to res curve. To * be used in battery packs that combines the identification resistor with a @@ -423,7 +411,6 @@ struct ab8500_bm_charger_parameters { * @bkup_bat_i current which we charge the backup battery with * @no_maintenance indicates that maintenance charging is disabled * @capacity_scaling indicates whether capacity scaling is to be used - * @ab8500_adc_therm placement of thermistor, batctrl or battemp adc * @chg_unknown_bat flag to enable charging of unknown batteries * @enable_overshoot flag to enable VBAT overshoot control * @auto_trig flag to enable auto adc trigger @@ -431,7 +418,6 @@ struct ab8500_bm_charger_parameters { * @interval_charging charge alg cycle period time when charging (sec) * @interval_not_charging charge alg cycle period time when not charging (sec) * @temp_hysteresis temperature hysteresis - * @gnd_lift_resistance Battery ground to phone ground resistance (mOhm) * @maxi maximization parameters * @cap_levels capacity in percent for the different capacity levels * @bat_type table of supported battery types @@ -452,12 +438,10 @@ struct ab8500_bm_data { bool chg_unknown_bat; bool enable_overshoot; bool auto_trig; - enum ab8500_adc_therm adc_therm; int fg_res; int interval_charging; int interval_not_charging; int temp_hysteresis; - int gnd_lift_resistance; const struct ab8500_maxim_parameters *maxi; const struct ab8500_bm_capacity_levels *cap_levels; struct ab8500_battery_type *bat_type; diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index 7ae95f537580..7133cce6a25a 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -150,7 +150,6 @@ struct ab8500_bm_data ab8500_bm_data = { .bkup_bat_i = BUP_ICH_SEL_150UA, .no_maintenance = false, .capacity_scaling = false, - .adc_therm = AB8500_ADC_THERM_BATCTRL, .chg_unknown_bat = false, .enable_overshoot = false, .fg_res = 100, @@ -158,7 +157,6 @@ struct ab8500_bm_data ab8500_bm_data = { .bat_type = &bat_type_thermistor_unknown, .interval_charging = 5, .interval_not_charging = 120, - .gnd_lift_resistance = 34, .maxi = &ab8500_maxi_params, .chg_params = &chg, .fg_params = &fg, diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index cc33c5187fbb..a5ca09124c93 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -135,8 +135,6 @@ static LIST_HEAD(ab8500_btemp_list); static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, int v_batctrl, int inst_curr) { - int rbs; - if (is_ab8500_1p1_or_earlier(di->parent)) { /* * For ABB cut1.0 and 1.1 BAT_CTRL is internally @@ -145,23 +143,11 @@ static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, return (450000 * (v_batctrl)) / (1800 - v_batctrl); } - if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL) { - /* - * If the battery has internal NTC, we use the current - * source to calculate the resistance. - */ - rbs = (v_batctrl * 1000 - - di->bm->gnd_lift_resistance * inst_curr) - / di->curr_source; - } else { - /* - * BAT_CTRL is internally - * connected to 1.8V through a 80k resistor - */ - rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl); - } - - return rbs; + /* + * BAT_CTRL is internally + * connected to 1.8V through a 80k resistor + */ + return (80000 * (v_batctrl)) / (1800 - v_batctrl); } /** @@ -186,155 +172,6 @@ static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di) return vbtemp; } -/** - * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source - * @di: pointer to the ab8500_btemp structure - * @enable: enable or disable the current source - * - * Enable or disable the current sources for the BatCtrl AD channel - */ -static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, - bool enable) -{ - int curr; - int ret = 0; - - /* - * BATCTRL current sources are included on AB8500 cut2.0 - * and future versions - */ - if (is_ab8500_1p1_or_earlier(di->parent)) - return 0; - - /* Only do this for batteries with internal NTC */ - if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL && enable) { - - if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA) - curr = BAT_CTRL_7U_ENA; - else - curr = BAT_CTRL_20U_ENA; - - dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source); - - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH); - if (ret) { - dev_err(di->dev, "%s failed setting cmp_force\n", - __func__); - return ret; - } - - /* - * We have to wait one 32kHz cycle before enabling - * the current source, since ForceBatCtrlCmpHigh needs - * to be written in a separate cycle - */ - udelay(32); - - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - FORCE_BAT_CTRL_CMP_HIGH | curr); - if (ret) { - dev_err(di->dev, "%s failed enabling current source\n", - __func__); - goto disable_curr_source; - } - } else if (di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL && !enable) { - dev_dbg(di->dev, "Disable BATCTRL curr source\n"); - - /* Write 0 to the curr bits */ - ret = abx500_mask_and_set_register_interruptible( - di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, - ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); - - if (ret) { - dev_err(di->dev, "%s failed disabling current source\n", - __func__); - goto disable_curr_source; - } - - /* Enable Pull-Up and comparator */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, - BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); - if (ret) { - dev_err(di->dev, "%s failed enabling PU and comp\n", - __func__); - goto enable_pu_comp; - } - - /* - * We have to wait one 32kHz cycle before disabling - * ForceBatCtrlCmpHigh since this needs to be written - * in a separate cycle - */ - udelay(32); - - /* Disable 'force comparator' */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); - if (ret) { - dev_err(di->dev, "%s failed disabling force comp\n", - __func__); - goto disable_force_comp; - } - } - return ret; - - /* - * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time - * if we got an error above - */ -disable_curr_source: - /* Write 0 to the curr bits */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, - ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); - - if (ret) { - dev_err(di->dev, "%s failed disabling current source\n", - __func__); - return ret; - } -enable_pu_comp: - /* Enable Pull-Up and comparator */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, - BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); - if (ret) { - dev_err(di->dev, "%s failed enabling PU and comp\n", - __func__); - return ret; - } - -disable_force_comp: - /* - * We have to wait one 32kHz cycle before disabling - * ForceBatCtrlCmpHigh since this needs to be written - * in a separate cycle - */ - udelay(32); - - /* Disable 'force comparator' */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, - FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); - if (ret) { - dev_err(di->dev, "%s failed disabling force comp\n", - __func__); - return ret; - } - - return ret; -} - /** * ab8500_btemp_get_batctrl_res() - get battery resistance * @di: pointer to the ab8500_btemp structure @@ -350,16 +187,6 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) int inst_curr; int i; - /* - * BATCTRL current sources are included on AB8500 cut2.0 - * and future versions - */ - ret = ab8500_btemp_curr_source_enable(di, true); - if (ret) { - dev_err(di->dev, "%s curr source enabled failed\n", __func__); - return ret; - } - if (!di->fg) di->fg = ab8500_fg_get(); if (!di->fg) { @@ -395,12 +222,6 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr); - ret = ab8500_btemp_curr_source_enable(di, false); - if (ret) { - dev_err(di->dev, "%s curr source disable failed\n", __func__); - return ret; - } - dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d samples: %d\n", __func__, batctrl, res, inst_curr, i); @@ -451,47 +272,28 @@ static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di, */ static int ab8500_btemp_measure_temp(struct ab8500_btemp *di) { - struct power_supply_battery_info *bi = di->bm->bi; int temp, ret; static int prev; - int rbat, rntc, vntc; + int rntc, vntc; - if ((di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL) && - (bi && (bi->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN))) { - - rbat = ab8500_btemp_get_batctrl_res(di); - if (rbat < 0) { - dev_err(di->dev, "%s get batctrl res failed\n", - __func__); - /* - * Return out-of-range temperature so that - * charging is stopped - */ - return BTEMP_THERMAL_LOW_LIMIT; - } - - temp = ab8500_btemp_res_to_temp(di, - di->bm->bat_type->r_to_t_tbl, - di->bm->bat_type->n_temp_tbl_elements, rbat); - } else { - ret = iio_read_channel_processed(di->btemp_ball, &vntc); - if (ret < 0) { - dev_err(di->dev, - "%s ADC conversion failed," - " using previous value\n", __func__); - return prev; - } - /* - * The PCB NTC is sourced from VTVOUT via a 230kOhm - * resistor. - */ - rntc = 230000 * vntc / (VTVOUT_V - vntc); - - temp = ab8500_btemp_res_to_temp(di, - di->bm->bat_type->r_to_t_tbl, - di->bm->bat_type->n_temp_tbl_elements, rntc); - prev = temp; + ret = iio_read_channel_processed(di->btemp_ball, &vntc); + if (ret < 0) { + dev_err(di->dev, + "%s ADC conversion failed," + " using previous value\n", __func__); + return prev; } + /* + * The PCB NTC is sourced from VTVOUT via a 230kOhm + * resistor. + */ + rntc = 230000 * vntc / (VTVOUT_V - vntc); + + temp = ab8500_btemp_res_to_temp(di, + di->bm->bat_type->r_to_t_tbl, + di->bm->bat_type->n_temp_tbl_elements, rntc); + prev = temp; + dev_dbg(di->dev, "Battery temperature is %d\n", temp); return temp; } @@ -519,11 +321,9 @@ static int ab8500_btemp_id(struct ab8500_btemp *di) if ((res <= di->bm->bat_type->resis_high) && (res >= di->bm->bat_type->resis_low)) { - dev_info(di->dev, "Battery detected on %s" + dev_info(di->dev, "Battery detected on BATTEMP" " low %d < res %d < high: %d" " index: %d\n", - di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL ? - "BATCTRL" : "BATTEMP", di->bm->bat_type->resis_low, res, di->bm->bat_type->resis_high, i); } else { @@ -532,21 +332,6 @@ static int ab8500_btemp_id(struct ab8500_btemp *di) return -ENXIO; } - /* - * We only have to change current source if the - * detected type is Type 1 (LIPO) resis_high = 53407, resis_low = 12500 - * if someone hacks this in. - * - * FIXME: make sure this is done automatically for the batteries - * that need it. - */ - if ((di->bm->adc_therm == AB8500_ADC_THERM_BATCTRL) && - (di->bm->bi && (di->bm->bi->technology == POWER_SUPPLY_TECHNOLOGY_LIPO)) && - (res <= 53407) && (res >= 12500)) { - dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n"); - di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA; - } - return 0; } From d662a7df36e1edc65eaf166ec1c8527ce9d088ea Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:16 +0100 Subject: [PATCH 46/94] power: supply: ab8500: Swap max and overvoltage We should terminate charging when we reach the voltage_max_design_uv not overvoltage_limit_uv, this is an abuse of that struct member. The overvoltage limit is actually not configurable on the AB8500, it is fixed to 4.75 V so drop a comment about that in the code. Fixes: 2a5f41830aad ("power: supply: ab8500: Standardize voltages") Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_bmdata.c | 8 +++----- drivers/power/supply/ab8500_chargalg.c | 2 +- drivers/power/supply/ab8500_fg.c | 8 +++++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index 7133cce6a25a..62b63f0437dd 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -186,13 +186,11 @@ int ab8500_bm_of_probe(struct power_supply *psy, * fall back to safe defaults. */ if ((bi->voltage_min_design_uv < 0) || - (bi->voltage_max_design_uv < 0) || - (bi->overvoltage_limit_uv < 0)) { + (bi->voltage_max_design_uv < 0)) { /* Nominal voltage is 3.7V for unknown batteries */ bi->voltage_min_design_uv = 3700000; - bi->voltage_max_design_uv = 3700000; - /* Termination voltage (overcharge limit) 4.05V */ - bi->overvoltage_limit_uv = 4050000; + /* Termination voltage 4.05V */ + bi->voltage_max_design_uv = 4050000; } if (bi->constant_charge_current_max_ua < 0) diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index c4a2fe07126c..bcf85ae6828e 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -802,7 +802,7 @@ static void ab8500_chargalg_end_of_charge(struct ab8500_chargalg *di) if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING && di->charge_state == STATE_NORMAL && !di->maintenance_chg && (di->batt_data.volt_uv >= - di->bm->bi->overvoltage_limit_uv || + di->bm->bi->voltage_max_design_uv || di->events.usb_cv_active || di->events.ac_cv_active) && di->batt_data.avg_curr_ua < di->bm->bi->charge_term_current_ua && diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index b0919a6a6587..236fd9f9d6f1 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -2263,7 +2263,13 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) { int ret; - /* Set VBAT OVV threshold */ + /* + * Set VBAT OVV (overvoltage) threshold to 4.75V (typ) this is what + * the hardware supports, nothing else can be configured in hardware. + * See this as an "outer limit" where the charger will certainly + * shut down. Other (lower) overvoltage levels need to be implemented + * in software. + */ ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_CHARGER, AB8500_BATT_OVV, From 2b0e7ac0841b3906aeecf432567b02af683a596c Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:17 +0100 Subject: [PATCH 47/94] power: supply: ab8500: Integrate thermal zone Instead of providing our own homebrewn thermal measurement code for an NTC and passing tables, we put the NTC thermistor into the device tree, create a passive thermal zone, and poll this thermal zone for the temperature. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 2 + drivers/power/supply/ab8500-bm.h | 33 --------- drivers/power/supply/ab8500_bmdata.c | 24 ------- drivers/power/supply/ab8500_btemp.c | 103 ++++++--------------------- 4 files changed, 24 insertions(+), 138 deletions(-) diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 6b15eb184072..3520da74b8a7 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -728,6 +728,8 @@ config BATTERY_GAUGE_LTC2941 config AB8500_BM bool "AB8500 Battery Management Driver" depends on AB8500_CORE && AB8500_GPADC && (IIO = y) && OF + select THERMAL + select THERMAL_OF help Say Y to include support for AB8500 battery management. diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 3bc6fd9337d2..6efd5174dbce 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -260,18 +260,6 @@ enum bup_vch_sel { #define BUS_PP_PRECHG_CURRENT_MASK 0x0E #define BUS_POWER_PATH_PRECHG_ENA 0x01 -/** - * struct ab8500_res_to_temp - defines one point in a temp to res curve. To - * be used in battery packs that combines the identification resistor with a - * NTC resistor. - * @temp: battery pack temperature in Celsius - * @resist: NTC resistor net total resistance - */ -struct ab8500_res_to_temp { - int temp; - int resist; -}; - /* Forward declaration */ struct ab8500_fg; @@ -351,8 +339,6 @@ struct ab8500_maxim_parameters { * @maint_b_chg_timer_h: charge time in maintenance B state * @low_high_cur_lvl: charger current in temp low/high state in mA * @low_high_vol_lvl: charger voltage in temp low/high state in mV' - * @n_r_t_tbl_elements: number of elements in r_to_t_tbl - * @r_to_t_tbl: table containing resistance to temp points */ struct ab8500_battery_type { int resis_high; @@ -365,8 +351,6 @@ struct ab8500_battery_type { int maint_b_chg_timer_h; int low_high_cur_lvl; int low_high_vol_lvl; - int n_temp_tbl_elements; - const struct ab8500_res_to_temp *r_to_t_tbl; }; /** @@ -449,23 +433,6 @@ struct ab8500_bm_data { const struct ab8500_fg_parameters *fg_params; }; -enum { - NTC_EXTERNAL = 0, - NTC_INTERNAL, -}; - -/** - * struct res_to_temp - defines one point in a temp to res curve. To - * be used in battery packs that combines the identification resistor with a - * NTC resistor. - * @temp: battery pack temperature in Celsius - * @resist: NTC resistor net total resistance - */ -struct res_to_temp { - int temp; - int resist; -}; - /* Forward declaration */ struct ab8500_fg; diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index 62b63f0437dd..d8fc72be0f0e 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -43,28 +43,6 @@ static struct power_supply_battery_ocv_table ocv_cap_tbl[] = { { .ocv = 3094000, .capacity = 0}, }; -/* - * Note that the res_to_temp table must be strictly sorted by falling - * resistance values to work. - */ -static const struct ab8500_res_to_temp temp_tbl[] = { - {-5, 214834}, - { 0, 162943}, - { 5, 124820}, - {10, 96520}, - {15, 75306}, - {20, 59254}, - {25, 47000}, - {30, 37566}, - {35, 30245}, - {40, 24520}, - {45, 20010}, - {50, 16432}, - {55, 13576}, - {60, 11280}, - {65, 9425}, -}; - /* * Note that the batres_vs_temp table must be strictly sorted by falling * temperature values to work. Factory resistance is 300 mOhm and the @@ -92,8 +70,6 @@ static struct ab8500_battery_type bat_type_thermistor_unknown = { .maint_b_chg_timer_h = 200, .low_high_cur_lvl = 300, .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), - .r_to_t_tbl = temp_tbl, }; static const struct ab8500_bm_capacity_levels cap_levels = { diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index a5ca09124c93..2a6fc151210c 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -26,13 +26,12 @@ #include #include #include +#include #include #include #include "ab8500-bm.h" -#define VTVOUT_V 1800 - #define BTEMP_THERMAL_LOW_LIMIT -10 #define BTEMP_THERMAL_MED_LIMIT 0 #define BTEMP_THERMAL_HIGH_LIMIT_52 52 @@ -82,7 +81,7 @@ struct ab8500_btemp_ranges { * @bat_temp: Dispatched battery temperature in degree Celsius * @prev_bat_temp Last measured battery temperature in degree Celsius * @parent: Pointer to the struct ab8500 - * @adc_btemp_ball: ADC channel for the battery ball temperature + * @tz: Thermal zone for the battery * @adc_bat_ctrl: ADC channel for the battery control * @fg: Pointer to the struct fg * @bm: Platform specific battery management information @@ -100,7 +99,7 @@ struct ab8500_btemp { int bat_temp; int prev_bat_temp; struct ab8500 *parent; - struct iio_channel *btemp_ball; + struct thermal_zone_device *tz; struct iio_channel *bat_ctrl; struct ab8500_fg *fg; struct ab8500_bm_data *bm; @@ -228,76 +227,6 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) return res; } -/** - * ab8500_btemp_res_to_temp() - resistance to temperature - * @di: pointer to the ab8500_btemp structure - * @tbl: pointer to the resiatance to temperature table - * @tbl_size: size of the resistance to temperature table - * @res: resistance to calculate the temperature from - * - * This function returns the battery temperature in degrees Celsius - * based on the NTC resistance. - */ -static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di, - const struct ab8500_res_to_temp *tbl, int tbl_size, int res) -{ - int i; - /* - * Calculate the formula for the straight line - * Simple interpolation if we are within - * the resistance table limits, extrapolate - * if resistance is outside the limits. - */ - if (res > tbl[0].resist) - i = 0; - else if (res <= tbl[tbl_size - 1].resist) - i = tbl_size - 2; - else { - i = 0; - while (!(res <= tbl[i].resist && - res > tbl[i + 1].resist)) - i++; - } - - return fixp_linear_interpolate(tbl[i].resist, tbl[i].temp, - tbl[i + 1].resist, tbl[i + 1].temp, - res); -} - -/** - * ab8500_btemp_measure_temp() - measure battery temperature - * @di: pointer to the ab8500_btemp structure - * - * Returns battery temperature (on success) else the previous temperature - */ -static int ab8500_btemp_measure_temp(struct ab8500_btemp *di) -{ - int temp, ret; - static int prev; - int rntc, vntc; - - ret = iio_read_channel_processed(di->btemp_ball, &vntc); - if (ret < 0) { - dev_err(di->dev, - "%s ADC conversion failed," - " using previous value\n", __func__); - return prev; - } - /* - * The PCB NTC is sourced from VTVOUT via a 230kOhm - * resistor. - */ - rntc = 230000 * vntc / (VTVOUT_V - vntc); - - temp = ab8500_btemp_res_to_temp(di, - di->bm->bat_type->r_to_t_tbl, - di->bm->bat_type->n_temp_tbl_elements, rntc); - prev = temp; - - dev_dbg(di->dev, "Battery temperature is %d\n", temp); - return temp; -} - /** * ab8500_btemp_id() - Identify the connected battery * @di: pointer to the ab8500_btemp structure @@ -347,6 +276,9 @@ static void ab8500_btemp_periodic_work(struct work_struct *work) int bat_temp; struct ab8500_btemp *di = container_of(work, struct ab8500_btemp, btemp_periodic_work.work); + /* Assume 25 degrees celsius as start temperature */ + static int prev = 25; + int ret; if (!di->initialized) { /* Identify the battery */ @@ -354,7 +286,17 @@ static void ab8500_btemp_periodic_work(struct work_struct *work) dev_warn(di->dev, "failed to identify the battery\n"); } - bat_temp = ab8500_btemp_measure_temp(di); + /* Failover if a reading is erroneous, use last meausurement */ + ret = thermal_zone_get_temp(di->tz, &bat_temp); + if (ret) { + dev_err(di->dev, "error reading temperature\n"); + bat_temp = prev; + } else { + /* Convert from millicentigrades to centigrades */ + bat_temp /= 1000; + prev = bat_temp; + } + /* * Filter battery temperature. * Allow direct updates on temperature only if two samples result in @@ -783,12 +725,11 @@ static int ab8500_btemp_probe(struct platform_device *pdev) di->dev = dev; di->parent = dev_get_drvdata(pdev->dev.parent); - /* Get ADC channels */ - di->btemp_ball = devm_iio_channel_get(dev, "btemp_ball"); - if (IS_ERR(di->btemp_ball)) { - ret = dev_err_probe(dev, PTR_ERR(di->btemp_ball), - "failed to get BTEMP BALL ADC channel\n"); - return ret; + /* Get thermal zone and ADC */ + di->tz = thermal_zone_get_zone_by_name("battery-thermal"); + if (IS_ERR(di->tz)) { + return dev_err_probe(dev, PTR_ERR(di->tz), + "failed to get battery thermal zone\n"); } di->bat_ctrl = devm_iio_channel_get(dev, "bat_ctrl"); if (IS_ERR(di->bat_ctrl)) { From edc400e1632fc65dd4dc92075db12b6986ebe5fd Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:18 +0100 Subject: [PATCH 48/94] power: supply: ab8500_fg: Break loop for measurement In the Samsung code tree we find that it can happen that this measurement loop goes on for a long time, and it seems like a good idea to break it after 70 iterations if it goes on for too long. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 236fd9f9d6f1..29896f09fd17 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -45,6 +45,7 @@ #define SEC_TO_SAMPLE(S) (S * 4) #define NBR_AVG_SAMPLES 20 +#define WAIT_FOR_INST_CURRENT_MAX 70 #define LOW_BAT_CHECK_INTERVAL (HZ / 16) /* 62.5 ms */ @@ -926,10 +927,18 @@ static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) vbat_uv += ab8500_fg_bat_voltage(di); i++; usleep_range(5000, 6000); - } while (!ab8500_fg_inst_curr_done(di)); + } while (!ab8500_fg_inst_curr_done(di) && + i <= WAIT_FOR_INST_CURRENT_MAX); + + if (i > WAIT_FOR_INST_CURRENT_MAX) { + dev_err(di->dev, + "TIMEOUT: return capacity based on uncompensated measurement of VBAT\n"); + goto calc_cap; + } ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua); +calc_cap: di->vbat_uv = vbat_uv / i; res = ab8500_fg_battery_resistance(di); From 673b50322bb6e31f0e917eeb321648092528635a Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:19 +0100 Subject: [PATCH 49/94] power: supply: ab8500_fg: Break out load compensated voltage Break out the part of the function providing the load compensated capacity that provides the load compensated voltage and use that to get the load compensated capacity. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 50 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 29896f09fd17..1797518c4b0e 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -909,18 +909,20 @@ static int ab8500_fg_battery_resistance(struct ab8500_fg *di) } /** - * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity + * ab8500_load_comp_fg_bat_voltage() - get load compensated battery voltage * @di: pointer to the ab8500_fg structure * - * Returns battery capacity based on battery voltage that is load compensated - * for the voltage drop + * Returns compensated battery voltage (on success) else error code. + * If always is specified, we always return a voltage but it may be + * uncompensated. */ -static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) +static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di) { - int vbat_comp_uv, res; int i = 0; int vbat_uv = 0; + int rcomp; + /* Average the instant current to get a stable current measurement */ ab8500_fg_inst_curr_start(di); do { @@ -932,25 +934,37 @@ static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) if (i > WAIT_FOR_INST_CURRENT_MAX) { dev_err(di->dev, - "TIMEOUT: return capacity based on uncompensated measurement of VBAT\n"); - goto calc_cap; + "TIMEOUT: return uncompensated measurement of VBAT\n"); + di->vbat_uv = vbat_uv / i; + return di->vbat_uv; } ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua); -calc_cap: - di->vbat_uv = vbat_uv / i; - res = ab8500_fg_battery_resistance(di); + vbat_uv = vbat_uv / i; - /* - * Use Ohms law to get the load compensated voltage. - * Divide by 1000 to get from milliohms to ohms. - */ - vbat_comp_uv = di->vbat_uv - (di->inst_curr_ua * res) / 1000; + /* Next we apply voltage compensation from internal resistance */ + rcomp = ab8500_fg_battery_resistance(di); + vbat_uv = vbat_uv - (di->inst_curr_ua * rcomp) / 1000; - dev_dbg(di->dev, "%s Measured Vbat: %d uV,Compensated Vbat %d uV, " - "R: %d mOhm, Current: %d uA Vbat Samples: %d\n", - __func__, di->vbat_uv, vbat_comp_uv, res, di->inst_curr_ua, i); + /* Always keep this state at latest measurement */ + di->vbat_uv = vbat_uv; + + return vbat_uv; +} + +/** + * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is load compensated + * for the voltage drop + */ +static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) +{ + int vbat_comp_uv; + + vbat_comp_uv = ab8500_load_comp_fg_bat_voltage(di); return ab8500_fg_volt_to_capacity(di, vbat_comp_uv); } From 965a990427b313a118865df1d33b82e30500c79a Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:20 +0100 Subject: [PATCH 50/94] power: supply: ab8500_fg: Safeguard compensated voltage In some cases when the platform is dissapating more than 500mA the voltage measurements and compensation will become instable. Add a parameter to bail out of the voltage measurement if this happens. This code was found in a Samsung vendor tree. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 1797518c4b0e..c659fdc8babd 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -46,6 +46,8 @@ #define NBR_AVG_SAMPLES 20 #define WAIT_FOR_INST_CURRENT_MAX 70 +/* Currents higher than -500mA (dissipating) will make compensation unstable */ +#define IGNORE_VBAT_HIGHCUR -500000 #define LOW_BAT_CHECK_INTERVAL (HZ / 16) /* 62.5 ms */ @@ -911,12 +913,13 @@ static int ab8500_fg_battery_resistance(struct ab8500_fg *di) /** * ab8500_load_comp_fg_bat_voltage() - get load compensated battery voltage * @di: pointer to the ab8500_fg structure + * @always: always return a voltage, also uncompensated * * Returns compensated battery voltage (on success) else error code. * If always is specified, we always return a voltage but it may be * uncompensated. */ -static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di) +static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always) { int i = 0; int vbat_uv = 0; @@ -941,6 +944,14 @@ static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di) ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua); + /* + * If there is too high current dissipation, the compensation cannot be + * trusted so return an error unless we must return something here, as + * enforced by the "always" parameter. + */ + if (!always && di->inst_curr_ua < IGNORE_VBAT_HIGHCUR) + return -EINVAL; + vbat_uv = vbat_uv / i; /* Next we apply voltage compensation from internal resistance */ @@ -964,7 +975,7 @@ static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) { int vbat_comp_uv; - vbat_comp_uv = ab8500_load_comp_fg_bat_voltage(di); + vbat_comp_uv = ab8500_load_comp_fg_bat_voltage(di, true); return ab8500_fg_volt_to_capacity(di, vbat_comp_uv); } From 3bab736363cf4a6d78c5169fc9ccbf5e0d9968e8 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:21 +0100 Subject: [PATCH 51/94] power: supply: ab8500_fg: Drop useless parameter All calls to ab8500_fg_calc_cap_discharge_voltage() require compensation and pass true as the second argument so just drop this argument. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index c659fdc8babd..6436861db016 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -1073,20 +1073,16 @@ static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di) /** * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage * @di: pointer to the ab8500_fg structure - * @comp: if voltage should be load compensated before capacity calc * - * Return the capacity in mAh based on the battery voltage. The voltage can - * either be load compensated or not. This value is added to the filter and a - * new mean value is calculated and returned. + * Return the capacity in mAh based on the load compensated battery voltage. + * This value is added to the filter and a new mean value is calculated and + * returned. */ -static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp) +static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di) { int permille, mah; - if (comp) - permille = ab8500_fg_load_comp_volt_to_capacity(di); - else - permille = ab8500_fg_uncomp_volt_to_capacity(di); + permille = ab8500_fg_load_comp_volt_to_capacity(di); mah = ab8500_fg_convert_permille_to_mah(di, permille); @@ -1563,7 +1559,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) /* Discard the first [x] seconds */ if (di->init_cnt > di->bm->fg_params->init_discard_time) { - ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_calc_cap_discharge_voltage(di); ab8500_fg_check_capacity_limits(di, true); } @@ -1646,7 +1642,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) break; } - ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_calc_cap_discharge_voltage(di); } else { mutex_lock(&di->cc_lock); if (!di->flags.conv_done) { @@ -1680,7 +1676,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) break; case AB8500_FG_DISCHARGE_WAKEUP: - ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_calc_cap_discharge_voltage(di); di->fg_samples = SEC_TO_SAMPLE( di->bm->fg_params->accu_high_curr); @@ -1799,7 +1795,7 @@ static void ab8500_fg_periodic_work(struct work_struct *work) if (di->init_capacity) { /* Get an initial capacity calculation */ - ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_calc_cap_discharge_voltage(di); ab8500_fg_check_capacity_limits(di, true); di->init_capacity = false; @@ -2422,7 +2418,7 @@ static void ab8500_fg_reinit_work(struct work_struct *work) if (!di->flags.calibrate) { dev_dbg(di->dev, "Resetting FG state machine to init.\n"); ab8500_fg_clear_cap_samples(di); - ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_calc_cap_discharge_voltage(di); ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); From 05906f58c82259e42d053409abe7e23b4f88650e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:22 +0100 Subject: [PATCH 52/94] power: supply: ab8500_chargalg: Drop charging step There is a sysfs ABI to change the "charging step" of the charger i.e. limit how much we charge from userspace. Since we don't have any userspace for this code, this sits unused and it is not used on production products either. Drop this code. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_chargalg.c | 105 ++----------------------- 1 file changed, 6 insertions(+), 99 deletions(-) diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index bcf85ae6828e..9f9a84ad2da2 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -46,9 +46,6 @@ /* Five minutes expressed in seconds */ #define FIVE_MINUTES_IN_SECONDS 300 -#define CHARGALG_CURR_STEP_LOW_UA 0 -#define CHARGALG_CURR_STEP_HIGH_UA 100000 - /* * This is the battery capacity limit that will trigger a new * full charging cycle in the case where maintenance charging @@ -86,11 +83,6 @@ struct ab8500_chargalg_suspension_status { bool usb_suspended; }; -struct ab8500_chargalg_current_step_status { - bool curr_step_change; - int curr_step_ua; -}; - struct ab8500_chargalg_battery_data { int temp; int volt_uv; @@ -186,8 +178,6 @@ struct ab8500_chargalg_events { * struct ab8500_charge_curr_maximization - Charger maximization parameters * @original_iset_ua: the non optimized/maximised charger current * @current_iset_ua: the charging current used at this moment - * @test_delta_i_ua: the delta between the current we want to charge and the - current that is really going into the battery * @condition_cnt: number of iterations needed before a new charger current is set * @max_current_ua: maximum charger current @@ -200,7 +190,6 @@ struct ab8500_chargalg_events { struct ab8500_charge_curr_maximization { int original_iset_ua; int current_iset_ua; - int test_delta_i_ua; int condition_cnt; int max_current_ua; int wait_cnt; @@ -229,7 +218,6 @@ enum maxim_ret { * @batt_data: data of the battery * @susp_status: current charger suspension status * @bm: Platform specific battery management information - * @curr_status: Current step status for over-current protection * @parent: pointer to the struct ab8500 * @chargalg_psy: structure that holds the battery properties exposed by * the charging algorithm @@ -255,7 +243,6 @@ struct ab8500_chargalg { struct ab8500_chargalg_battery_data batt_data; struct ab8500_chargalg_suspension_status susp_status; struct ab8500 *parent; - struct ab8500_chargalg_current_step_status curr_status; struct ab8500_bm_data *bm; struct power_supply *chargalg_psy; struct ux500_charger *ac_chg; @@ -420,22 +407,6 @@ static int ab8500_chargalg_check_charger_connection(struct ab8500_chargalg *di) return di->chg_info.conn_chg; } -/** - * ab8500_chargalg_check_current_step_status() - Check charging current - * step status. - * @di: pointer to the ab8500_chargalg structure - * - * This function will check if there is a change in the charging current step - * and change charge state accordingly. - */ -static void ab8500_chargalg_check_current_step_status - (struct ab8500_chargalg *di) -{ - if (di->curr_status.curr_step_change) - ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); - di->curr_status.curr_step_change = false; -} - /** * ab8500_chargalg_start_safety_timer() - Start charging safety timer * @di: pointer to the ab8500_chargalg structure @@ -831,7 +802,6 @@ static void init_maxim_chg_curr(struct ab8500_chargalg *di) di->ccm.original_iset_ua = bi->constant_charge_current_max_ua; di->ccm.current_iset_ua = bi->constant_charge_current_max_ua; - di->ccm.test_delta_i_ua = di->bm->maxi->charger_curr_step_ua; di->ccm.max_current_ua = di->bm->maxi->chg_curr_ua; di->ccm.condition_cnt = di->bm->maxi->wait_cycles; di->ccm.level = 0; @@ -862,8 +832,7 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) dev_dbg(di->dev, "lowering current\n"); di->ccm.wait_cnt++; di->ccm.condition_cnt = di->bm->maxi->wait_cycles; - di->ccm.max_current_ua = - di->ccm.current_iset_ua - di->ccm.test_delta_i_ua; + di->ccm.max_current_ua = di->ccm.current_iset_ua; di->ccm.current_iset_ua = di->ccm.max_current_ua; di->ccm.level--; return MAXIM_RET_CHANGE; @@ -893,29 +862,8 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) return MAXIM_RET_IBAT_TOO_HIGH; } - if (delta_i_ua > di->ccm.test_delta_i_ua && - (di->ccm.current_iset_ua + di->ccm.test_delta_i_ua) < - di->ccm.max_current_ua) { - if (di->ccm.condition_cnt-- == 0) { - /* Increse the iset with cco.test_delta_i */ - di->ccm.condition_cnt = di->bm->maxi->wait_cycles; - di->ccm.current_iset_ua += di->ccm.test_delta_i_ua; - di->ccm.level++; - dev_dbg(di->dev, " Maximization needed, increase" - " with %d uA to %duA (Optimal ibat: %d uA)" - " Level %d\n", - di->ccm.test_delta_i_ua, - di->ccm.current_iset_ua, - di->ccm.original_iset_ua, - di->ccm.level); - return MAXIM_RET_CHANGE; - } else { - return MAXIM_RET_NOACTION; - } - } else { - di->ccm.condition_cnt = di->bm->maxi->wait_cycles; - return MAXIM_RET_NOACTION; - } + di->ccm.condition_cnt = di->bm->maxi->wait_cycles; + return MAXIM_RET_NOACTION; } static void handle_maxim_chg_curr(struct ab8500_chargalg *di) @@ -1302,7 +1250,6 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) struct power_supply_battery_info *bi = di->bm->bi; int charger_status; int ret; - int curr_step_lvl_ua; /* Collect data from all power_supply class devices */ class_for_each_device(power_supply_class, NULL, @@ -1313,7 +1260,6 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) ab8500_chargalg_check_charger_voltage(di); charger_status = ab8500_chargalg_check_charger_connection(di); - ab8500_chargalg_check_current_step_status(di); if (is_ab8500(di->parent)) { ret = ab8500_chargalg_check_charger_enable(di); @@ -1511,15 +1457,13 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) break; case STATE_NORMAL_INIT: - if (di->curr_status.curr_step_ua == CHARGALG_CURR_STEP_LOW_UA) + if (bi->constant_charge_current_max_ua == 0) + /* "charging" with 0 uA */ ab8500_chargalg_stop_charging(di); else { - curr_step_lvl_ua = bi->constant_charge_current_max_ua - * di->curr_status.curr_step_ua - / CHARGALG_CURR_STEP_HIGH_UA; ab8500_chargalg_start_charging(di, bi->constant_charge_voltage_max_uv, - curr_step_lvl_ua); + bi->constant_charge_current_max_ua); } ab8500_chargalg_state_to(di, STATE_NORMAL); @@ -1742,37 +1686,6 @@ static int ab8500_chargalg_get_property(struct power_supply *psy, /* Exposure to the sysfs interface */ -static ssize_t ab8500_chargalg_curr_step_show(struct ab8500_chargalg *di, - char *buf) -{ - return sprintf(buf, "%d\n", di->curr_status.curr_step_ua); -} - -static ssize_t ab8500_chargalg_curr_step_store(struct ab8500_chargalg *di, - const char *buf, size_t length) -{ - long param; - int ret; - - ret = kstrtol(buf, 10, ¶m); - if (ret < 0) - return ret; - - di->curr_status.curr_step_ua = param; - if (di->curr_status.curr_step_ua >= CHARGALG_CURR_STEP_LOW_UA && - di->curr_status.curr_step_ua <= CHARGALG_CURR_STEP_HIGH_UA) { - di->curr_status.curr_step_change = true; - queue_work(di->chargalg_wq, &di->chargalg_work); - } else - dev_info(di->dev, "Wrong current step\n" - "Enter 0. Disable AC/USB Charging\n" - "1--100. Set AC/USB charging current step\n" - "100. Enable AC/USB Charging\n"); - - return strlen(buf); -} - - static ssize_t ab8500_chargalg_en_show(struct ab8500_chargalg *di, char *buf) { @@ -1832,10 +1745,6 @@ static struct ab8500_chargalg_sysfs_entry ab8500_chargalg_en_charger = __ATTR(chargalg, 0644, ab8500_chargalg_en_show, ab8500_chargalg_en_store); -static struct ab8500_chargalg_sysfs_entry ab8500_chargalg_curr_step = - __ATTR(chargalg_curr_step, 0644, ab8500_chargalg_curr_step_show, - ab8500_chargalg_curr_step_store); - static ssize_t ab8500_chargalg_sysfs_show(struct kobject *kobj, struct attribute *attr, char *buf) { @@ -1868,7 +1777,6 @@ static ssize_t ab8500_chargalg_sysfs_charger(struct kobject *kobj, static struct attribute *ab8500_chargalg_chg[] = { &ab8500_chargalg_en_charger.attr, - &ab8500_chargalg_curr_step.attr, NULL, }; @@ -2057,7 +1965,6 @@ static int ab8500_chargalg_probe(struct platform_device *pdev) dev_err(di->dev, "failed to create sysfs entry\n"); return ret; } - di->curr_status.curr_step_ua = CHARGALG_CURR_STEP_HIGH_UA; dev_info(di->dev, "probe success\n"); return component_add(dev, &ab8500_chargalg_component_ops); From 75ee3f6f0c1a0b70b9afc2b758566aa49c3cf978 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:23 +0100 Subject: [PATCH 53/94] power: supply: ab8500_chargalg: Drop enable/disable sysfs There is a sysfs ABI to enable/disable charging of different types (AC/USB). Since we don't have any userspace for this code, this sits unused and it is not used on production products either. Drop this code. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_chargalg.c | 211 +------------------------ 1 file changed, 6 insertions(+), 205 deletions(-) diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index 9f9a84ad2da2..b5a3096e78a1 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -77,12 +77,6 @@ struct ab8500_chargalg_charger_info { int ac_iset_ua; }; -struct ab8500_chargalg_suspension_status { - bool suspended_change; - bool ac_suspended; - bool usb_suspended; -}; - struct ab8500_chargalg_battery_data { int temp; int volt_uv; @@ -110,8 +104,6 @@ enum ab8500_chargalg_states { STATE_TEMP_UNDEROVER, STATE_TEMP_LOWHIGH_INIT, STATE_TEMP_LOWHIGH, - STATE_SUSPENDED_INIT, - STATE_SUSPENDED, STATE_OVV_PROTECT_INIT, STATE_OVV_PROTECT, STATE_SAFETY_TIMER_EXPIRED_INIT, @@ -141,8 +133,6 @@ static const char * const states[] = { "TEMP_UNDEROVER", "TEMP_LOWHIGH_INIT", "TEMP_LOWHIGH", - "SUSPENDED_INIT", - "SUSPENDED", "OVV_PROTECT_INIT", "OVV_PROTECT", "SAFETY_TIMER_EXPIRED_INIT", @@ -216,7 +206,6 @@ enum maxim_ret { * @ccm charging current maximization parameters * @chg_info: information about connected charger types * @batt_data: data of the battery - * @susp_status: current charger suspension status * @bm: Platform specific battery management information * @parent: pointer to the struct ab8500 * @chargalg_psy: structure that holds the battery properties exposed by @@ -241,7 +230,6 @@ struct ab8500_chargalg { struct ab8500_charge_curr_maximization ccm; struct ab8500_chargalg_charger_info chg_info; struct ab8500_chargalg_battery_data batt_data; - struct ab8500_chargalg_suspension_status susp_status; struct ab8500 *parent; struct ab8500_bm_data *bm; struct power_supply *chargalg_psy; @@ -372,37 +360,24 @@ static int ab8500_chargalg_check_charger_enable(struct ab8500_chargalg *di) */ static int ab8500_chargalg_check_charger_connection(struct ab8500_chargalg *di) { - if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg || - di->susp_status.suspended_change) { - /* - * Charger state changed or suspension - * has changed since last update - */ - if ((di->chg_info.conn_chg & AC_CHG) && - !di->susp_status.ac_suspended) { - dev_dbg(di->dev, "Charging source is AC\n"); + if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg) { + /* Charger state changed since last update */ + if (di->chg_info.conn_chg & AC_CHG) { + dev_info(di->dev, "Charging source is AC\n"); if (di->chg_info.charger_type != AC_CHG) { di->chg_info.charger_type = AC_CHG; ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); } - } else if ((di->chg_info.conn_chg & USB_CHG) && - !di->susp_status.usb_suspended) { - dev_dbg(di->dev, "Charging source is USB\n"); + } else if (di->chg_info.conn_chg & USB_CHG) { + dev_info(di->dev, "Charging source is USB\n"); di->chg_info.charger_type = USB_CHG; ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); - } else if (di->chg_info.conn_chg && - (di->susp_status.ac_suspended || - di->susp_status.usb_suspended)) { - dev_dbg(di->dev, "Charging is suspended\n"); - di->chg_info.charger_type = NO_CHG; - ab8500_chargalg_state_to(di, STATE_SUSPENDED_INIT); } else { dev_dbg(di->dev, "Charging source is OFF\n"); di->chg_info.charger_type = NO_CHG; ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT); } di->chg_info.prev_conn_chg = di->chg_info.conn_chg; - di->susp_status.suspended_change = false; } return di->chg_info.conn_chg; } @@ -1281,12 +1256,6 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) } } - /* If suspended, we should not continue checking the flags */ - else if (di->charge_state == STATE_SUSPENDED_INIT || - di->charge_state == STATE_SUSPENDED) { - /* We don't do anything here, just don,t continue */ - } - /* Safety timer expiration */ else if (di->events.safety_timer_expired) { if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED) @@ -1384,23 +1353,6 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) case STATE_HANDHELD: break; - case STATE_SUSPENDED_INIT: - if (di->susp_status.ac_suspended) - ab8500_chargalg_ac_en(di, false, 0, 0); - if (di->susp_status.usb_suspended) - ab8500_chargalg_usb_en(di, false, 0, 0); - ab8500_chargalg_stop_safety_timer(di); - ab8500_chargalg_stop_maintenance_timer(di); - di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; - di->maintenance_chg = false; - ab8500_chargalg_state_to(di, STATE_SUSPENDED); - power_supply_changed(di->chargalg_psy); - fallthrough; - - case STATE_SUSPENDED: - /* CHARGING is suspended */ - break; - case STATE_BATT_REMOVED_INIT: ab8500_chargalg_stop_charging(di); ab8500_chargalg_state_to(di, STATE_BATT_REMOVED); @@ -1684,144 +1636,6 @@ static int ab8500_chargalg_get_property(struct power_supply *psy, return 0; } -/* Exposure to the sysfs interface */ - -static ssize_t ab8500_chargalg_en_show(struct ab8500_chargalg *di, - char *buf) -{ - return sprintf(buf, "%d\n", - di->susp_status.ac_suspended && - di->susp_status.usb_suspended); -} - -static ssize_t ab8500_chargalg_en_store(struct ab8500_chargalg *di, - const char *buf, size_t length) -{ - long param; - int ac_usb; - int ret; - - ret = kstrtol(buf, 10, ¶m); - if (ret < 0) - return ret; - - ac_usb = param; - switch (ac_usb) { - case 0: - /* Disable charging */ - di->susp_status.ac_suspended = true; - di->susp_status.usb_suspended = true; - di->susp_status.suspended_change = true; - /* Trigger a state change */ - queue_work(di->chargalg_wq, - &di->chargalg_work); - break; - case 1: - /* Enable AC Charging */ - di->susp_status.ac_suspended = false; - di->susp_status.suspended_change = true; - /* Trigger a state change */ - queue_work(di->chargalg_wq, - &di->chargalg_work); - break; - case 2: - /* Enable USB charging */ - di->susp_status.usb_suspended = false; - di->susp_status.suspended_change = true; - /* Trigger a state change */ - queue_work(di->chargalg_wq, - &di->chargalg_work); - break; - default: - dev_info(di->dev, "Wrong input\n" - "Enter 0. Disable AC/USB Charging\n" - "1. Enable AC charging\n" - "2. Enable USB Charging\n"); - } - return strlen(buf); -} - -static struct ab8500_chargalg_sysfs_entry ab8500_chargalg_en_charger = - __ATTR(chargalg, 0644, ab8500_chargalg_en_show, - ab8500_chargalg_en_store); - -static ssize_t ab8500_chargalg_sysfs_show(struct kobject *kobj, - struct attribute *attr, char *buf) -{ - struct ab8500_chargalg_sysfs_entry *entry = container_of(attr, - struct ab8500_chargalg_sysfs_entry, attr); - - struct ab8500_chargalg *di = container_of(kobj, - struct ab8500_chargalg, chargalg_kobject); - - if (!entry->show) - return -EIO; - - return entry->show(di, buf); -} - -static ssize_t ab8500_chargalg_sysfs_charger(struct kobject *kobj, - struct attribute *attr, const char *buf, size_t length) -{ - struct ab8500_chargalg_sysfs_entry *entry = container_of(attr, - struct ab8500_chargalg_sysfs_entry, attr); - - struct ab8500_chargalg *di = container_of(kobj, - struct ab8500_chargalg, chargalg_kobject); - - if (!entry->store) - return -EIO; - - return entry->store(di, buf, length); -} - -static struct attribute *ab8500_chargalg_chg[] = { - &ab8500_chargalg_en_charger.attr, - NULL, -}; - -static const struct sysfs_ops ab8500_chargalg_sysfs_ops = { - .show = ab8500_chargalg_sysfs_show, - .store = ab8500_chargalg_sysfs_charger, -}; - -static struct kobj_type ab8500_chargalg_ktype = { - .sysfs_ops = &ab8500_chargalg_sysfs_ops, - .default_attrs = ab8500_chargalg_chg, -}; - -/** - * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry - * @di: pointer to the struct ab8500_chargalg - * - * This function removes the entry in sysfs. - */ -static void ab8500_chargalg_sysfs_exit(struct ab8500_chargalg *di) -{ - kobject_del(&di->chargalg_kobject); -} - -/** - * ab8500_chargalg_sysfs_init() - init of sysfs entry - * @di: pointer to the struct ab8500_chargalg - * - * This function adds an entry in sysfs. - * Returns error code in case of failure else 0(on success) - */ -static int ab8500_chargalg_sysfs_init(struct ab8500_chargalg *di) -{ - int ret = 0; - - ret = kobject_init_and_add(&di->chargalg_kobject, - &ab8500_chargalg_ktype, - NULL, "ab8500_chargalg"); - if (ret < 0) - dev_err(di->dev, "failed to create sysfs entry\n"); - - return ret; -} -/* Exposure to the sysfs interface <> */ - static int __maybe_unused ab8500_chargalg_resume(struct device *dev) { struct ab8500_chargalg *di = dev_get_drvdata(dev); @@ -1911,7 +1725,6 @@ static int ab8500_chargalg_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct power_supply_config psy_cfg = {}; struct ab8500_chargalg *di; - int ret = 0; di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL); if (!di) @@ -1959,26 +1772,14 @@ static int ab8500_chargalg_probe(struct platform_device *pdev) platform_set_drvdata(pdev, di); - /* sysfs interface to enable/disable charging from user space */ - ret = ab8500_chargalg_sysfs_init(di); - if (ret) { - dev_err(di->dev, "failed to create sysfs entry\n"); - return ret; - } - dev_info(di->dev, "probe success\n"); return component_add(dev, &ab8500_chargalg_component_ops); } static int ab8500_chargalg_remove(struct platform_device *pdev) { - struct ab8500_chargalg *di = platform_get_drvdata(pdev); - component_del(&pdev->dev, &ab8500_chargalg_component_ops); - /* sysfs interface to enable/disable charging from user space */ - ab8500_chargalg_sysfs_exit(di); - return 0; } From 0f6dad11fdf765a2fc89ef86961efcfcf94b5d0a Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:24 +0100 Subject: [PATCH 54/94] power: supply: ab8500_charger: Restrict ADC retrieveal The AB8505 only has two ADC channels: the voltage and current provided from VBUS (USB). It does not support AC charging at all. Make sure we don't try to retrieve the non-existing channels. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index ce074c018dcb..681b53bb0df0 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3443,17 +3443,19 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->parent = dev_get_drvdata(pdev->dev.parent); /* Get ADC channels */ - di->adc_main_charger_v = devm_iio_channel_get(dev, "main_charger_v"); - if (IS_ERR(di->adc_main_charger_v)) { - ret = dev_err_probe(dev, PTR_ERR(di->adc_main_charger_v), - "failed to get ADC main charger voltage\n"); - return ret; - } - di->adc_main_charger_c = devm_iio_channel_get(dev, "main_charger_c"); - if (IS_ERR(di->adc_main_charger_c)) { - ret = dev_err_probe(dev, PTR_ERR(di->adc_main_charger_c), - "failed to get ADC main charger current\n"); - return ret; + if (!is_ab8505(di->parent)) { + di->adc_main_charger_v = devm_iio_channel_get(dev, "main_charger_v"); + if (IS_ERR(di->adc_main_charger_v)) { + ret = dev_err_probe(dev, PTR_ERR(di->adc_main_charger_v), + "failed to get ADC main charger voltage\n"); + return ret; + } + di->adc_main_charger_c = devm_iio_channel_get(dev, "main_charger_c"); + if (IS_ERR(di->adc_main_charger_c)) { + ret = dev_err_probe(dev, PTR_ERR(di->adc_main_charger_c), + "failed to get ADC main charger current\n"); + return ret; + } } di->adc_vbus_v = devm_iio_channel_get(dev, "vbus_v"); if (IS_ERR(di->adc_vbus_v)) { From 21ad180d0fdfb2ab6df0f19c7dedce829a9ed1ed Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 29 Jan 2022 01:49:25 +0100 Subject: [PATCH 55/94] power: supply: ab8500_charger: Fix VBAT interval check When using USB charging, the AB8500 charger is periodically checking VBAT for a threshold at 3.8V. This crashes badly, as the class_for_each_device() was passed the wrong argument. I think this has maybe worked by chance in the past because of how the structs were arranged but it is leading to crashes now. Fix this up and also switch to using microvolts for the voltages like the rest of the code. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 681b53bb0df0..88099cdba8a7 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -163,7 +163,7 @@ enum ab8500_usb_state { #define USB_CH_IP_CUR_LVL_1P4 1400000 #define USB_CH_IP_CUR_LVL_1P5 1500000 -#define VBAT_TRESH_IP_CUR_RED 3800 +#define VBAT_TRESH_IP_CUR_RED 3800000 #define to_ab8500_charger_usb_device_info(x) container_of((x), \ struct ab8500_charger, usb_chg) @@ -1920,7 +1920,11 @@ static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data) di = to_ab8500_charger_usb_device_info(usb_chg); - /* For all psy where the driver name appears in any supplied_to */ + /* + * For all psy where the driver name appears in any supplied_to + * in practice what we will find will always be "ab8500_fg" as + * the fuel gauge is responsible of keeping track of VBAT. + */ j = match_string(supplicants, ext->num_supplicants, psy->desc->name); if (j < 0) return 0; @@ -1937,7 +1941,10 @@ static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data) case POWER_SUPPLY_PROP_VOLTAGE_NOW: switch (ext->desc->type) { case POWER_SUPPLY_TYPE_BATTERY: - di->vbat = ret.intval / 1000; + /* This will always be "ab8500_fg" */ + dev_dbg(di->dev, "get VBAT from %s\n", + dev_name(&ext->dev)); + di->vbat = ret.intval; break; default: break; @@ -1966,7 +1973,7 @@ static void ab8500_charger_check_vbat_work(struct work_struct *work) struct ab8500_charger, check_vbat_work.work); class_for_each_device(power_supply_class, NULL, - di->usb_chg.psy, ab8500_charger_get_ext_psy_data); + &di->usb_chg, ab8500_charger_get_ext_psy_data); /* First run old_vbat is 0. */ if (di->old_vbat == 0) @@ -1991,8 +1998,8 @@ static void ab8500_charger_check_vbat_work(struct work_struct *work) * No need to check the battery voltage every second when not close to * the threshold. */ - if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100) && - (di->vbat > (VBAT_TRESH_IP_CUR_RED - 100))) + if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100000) && + (di->vbat > (VBAT_TRESH_IP_CUR_RED - 100000))) t = 1; queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ); From 845301001308aab8fb7902548f6c3256d28b8c48 Mon Sep 17 00:00:00 2001 From: Daisuke Nojiri Date: Wed, 26 Jan 2022 10:04:10 -0800 Subject: [PATCH 56/94] power: supply: PCHG: Use MKBP for device event handling This change makes the PCHG driver receive device events through MKBP protocol since CrOS EC switched to deliver all peripheral charge events to the MKBP protocol. This will unify PCHG event handling on X86 and ARM. Signed-off-by: Daisuke Nojiri Signed-off-by: Sebastian Reichel --- .../power/supply/cros_peripheral_charger.c | 37 ++--------- .../linux/platform_data/cros_ec_commands.h | 64 +++++++++++++++++++ 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/drivers/power/supply/cros_peripheral_charger.c b/drivers/power/supply/cros_peripheral_charger.c index 305f10dfc06d..9fe6d826148d 100644 --- a/drivers/power/supply/cros_peripheral_charger.c +++ b/drivers/power/supply/cros_peripheral_charger.c @@ -14,6 +14,7 @@ #include #include #include +#include #define DRV_NAME "cros-ec-pchg" #define PCHG_DIR_PREFIX "peripheral" @@ -237,46 +238,22 @@ static int cros_pchg_event(const struct charger_data *charger, return NOTIFY_OK; } -static u32 cros_get_device_event(const struct charger_data *charger) -{ - struct ec_params_device_event req; - struct ec_response_device_event rsp; - struct device *dev = charger->dev; - int ret; - - req.param = EC_DEVICE_EVENT_PARAM_GET_CURRENT_EVENTS; - ret = cros_pchg_ec_command(charger, 0, EC_CMD_DEVICE_EVENT, - &req, sizeof(req), &rsp, sizeof(rsp)); - if (ret < 0) { - dev_warn(dev, "Unable to get device events (err:%d)\n", ret); - return 0; - } - - return rsp.event_mask; -} - static int cros_ec_notify(struct notifier_block *nb, unsigned long queued_during_suspend, void *data) { - struct cros_ec_device *ec_dev = (struct cros_ec_device *)data; - u32 host_event = cros_ec_get_host_event(ec_dev); + struct cros_ec_device *ec_dev = data; struct charger_data *charger = container_of(nb, struct charger_data, notifier); - u32 device_event_mask; + u32 host_event; - if (!host_event) + if (ec_dev->event_data.event_type != EC_MKBP_EVENT_PCHG || + ec_dev->event_size != sizeof(host_event)) return NOTIFY_DONE; - if (!(host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_DEVICE))) - return NOTIFY_DONE; + host_event = get_unaligned_le32(&ec_dev->event_data.data.host_event); - /* - * todo: Retrieve device event mask in common place - * (e.g. cros_ec_proto.c). - */ - device_event_mask = cros_get_device_event(charger); - if (!(device_event_mask & EC_DEVICE_EVENT_MASK(EC_DEVICE_EVENT_WLC))) + if (!(host_event & EC_MKBP_PCHG_DEVICE_EVENT)) return NOTIFY_DONE; return cros_pchg_event(charger, host_event); diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 271bd87bff0a..95e7e5667291 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -3386,6 +3386,9 @@ enum ec_mkbp_event { /* Send an incoming CEC message to the AP */ EC_MKBP_EVENT_CEC_MESSAGE = 9, + /* Peripheral device charger event */ + EC_MKBP_EVENT_PCHG = 12, + /* Number of MKBP events */ EC_MKBP_EVENT_COUNT, }; @@ -5527,6 +5530,67 @@ enum pchg_state { [PCHG_STATE_CONNECTED] = "CONNECTED", \ } +/* + * Update firmware of peripheral chip + */ +#define EC_CMD_PCHG_UPDATE 0x0136 + +/* Port number is encoded in bit[28:31]. */ +#define EC_MKBP_PCHG_PORT_SHIFT 28 +/* Utility macro for converting MKBP event to port number. */ +#define EC_MKBP_PCHG_EVENT_TO_PORT(e) (((e) >> EC_MKBP_PCHG_PORT_SHIFT) & 0xf) +/* Utility macro for extracting event bits. */ +#define EC_MKBP_PCHG_EVENT_MASK(e) ((e) \ + & GENMASK(EC_MKBP_PCHG_PORT_SHIFT-1, 0)) + +#define EC_MKBP_PCHG_UPDATE_OPENED BIT(0) +#define EC_MKBP_PCHG_WRITE_COMPLETE BIT(1) +#define EC_MKBP_PCHG_UPDATE_CLOSED BIT(2) +#define EC_MKBP_PCHG_UPDATE_ERROR BIT(3) +#define EC_MKBP_PCHG_DEVICE_EVENT BIT(4) + +enum ec_pchg_update_cmd { + /* Reset chip to normal mode. */ + EC_PCHG_UPDATE_CMD_RESET_TO_NORMAL = 0, + /* Reset and put a chip in update (a.k.a. download) mode. */ + EC_PCHG_UPDATE_CMD_OPEN, + /* Write a block of data containing FW image. */ + EC_PCHG_UPDATE_CMD_WRITE, + /* Close update session. */ + EC_PCHG_UPDATE_CMD_CLOSE, + /* End of commands */ + EC_PCHG_UPDATE_CMD_COUNT, +}; + +struct ec_params_pchg_update { + /* PCHG port number */ + uint8_t port; + /* enum ec_pchg_update_cmd */ + uint8_t cmd; + /* Padding */ + uint8_t reserved0; + uint8_t reserved1; + /* Version of new firmware */ + uint32_t version; + /* CRC32 of new firmware */ + uint32_t crc32; + /* Address in chip memory where is written to */ + uint32_t addr; + /* Size of */ + uint32_t size; + /* Partial data of new firmware */ + uint8_t data[]; +} __ec_align4; + +BUILD_ASSERT(EC_PCHG_UPDATE_CMD_COUNT + < BIT(sizeof(((struct ec_params_pchg_update *)0)->cmd)*8)); + +struct ec_response_pchg_update { + /* Block size */ + uint32_t block_size; +} __ec_align4; + + /*****************************************************************************/ /* Voltage regulator controls */ From 6a4760463dbc6b603690938c468839985189ce0a Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Mon, 24 Jan 2022 13:13:46 +0000 Subject: [PATCH 57/94] power: supply: ab8500: Fix memory leak in ab8500_fg_sysfs_init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kobject_init_and_add() takes reference even when it fails. According to the doc of kobject_init_and_add(): If this function returns an error, kobject_put() must be called to properly clean up the memory associated with the object. Fix memory leak by calling kobject_put(). Fixes: 8c0984e5a753 ("power: move power supply drivers to power/supply") Signed-off-by: Miaoqian Lin Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 6436861db016..e60db2f869a7 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -2557,8 +2557,10 @@ static int ab8500_fg_sysfs_init(struct ab8500_fg *di) ret = kobject_init_and_add(&di->fg_kobject, &ab8500_fg_ktype, NULL, "battery"); - if (ret < 0) + if (ret < 0) { + kobject_put(&di->fg_kobject); dev_err(di->dev, "failed to create sysfs entry\n"); + } return ret; } From 8f5b373960f976cf80e54636a60637cbe9718f66 Mon Sep 17 00:00:00 2001 From: Hong Peng Date: Wed, 19 Jan 2022 11:53:01 +0800 Subject: [PATCH 58/94] power: supply: ab8500_charger: Fix spelling typo fix the comment typo: "interrupts", "structcure" Signed-off-by: Hong Peng Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 88099cdba8a7..b17d4649210a 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -171,7 +171,7 @@ enum ab8500_usb_state { struct ab8500_charger, ac_chg) /** - * struct ab8500_charger_interrupts - ab8500 interupts + * struct ab8500_charger_interrupts - ab8500 interrupts * @name: name of the interrupt * @isr function pointer to the isr */ @@ -1083,7 +1083,7 @@ static int ab8500_vbus_in_curr_to_regval(struct ab8500_charger *di, int curr_ua) /** * ab8500_charger_get_usb_cur() - get usb current - * @di: pointer to the ab8500_charger structre + * @di: pointer to the ab8500_charger structure * * The usb stack provides the maximum current that can be drawn from * the standard usb host. This will be in uA. From cf215c37f581c4afd6a4880eeb9f4ac70d000c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Date: Tue, 11 Jan 2022 03:32:40 +0100 Subject: [PATCH 59/94] power: supply: ltc2941: simplify Qlsb calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace two divisions with a subtraction+shift for a small code size improvement and less brackets. Signed-off-by: Michał Mirosław Signed-off-by: Sebastian Reichel --- drivers/power/supply/ltc2941-battery-gauge.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index 09f3e78af4e0..c0cbf4cd59ee 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -490,13 +490,13 @@ static int ltc294x_i2c_probe(struct i2c_client *client, if (info->id == LTC2943_ID) { if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP) prescaler_exp = LTC2943_MAX_PRESCALER_EXP; - info->Qlsb = ((340 * 50000) / r_sense) / - (4096 / (1 << (2*prescaler_exp))); + info->Qlsb = ((340 * 50000) / r_sense) >> + (12 - 2*prescaler_exp); } else { if (prescaler_exp > LTC2941_MAX_PRESCALER_EXP) prescaler_exp = LTC2941_MAX_PRESCALER_EXP; - info->Qlsb = ((85 * 50000) / r_sense) / - (128 / (1 << prescaler_exp)); + info->Qlsb = ((85 * 50000) / r_sense) >> + (7 - prescaler_exp); } /* Read status register to check for LTC2942 */ From 513e3b53c1d535631ad206105ecb278ca56a5ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Date: Tue, 11 Jan 2022 03:32:41 +0100 Subject: [PATCH 60/94] power: supply: ltc2941: clean up error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace dev_err() with dev_err_probe() in probe() and extend register access failure messages. dev_err()s in _reset() are removed as they are redundant: register access wrappers already log the error. Signed-off-by: Michał Mirosław Signed-off-by: Sebastian Reichel --- drivers/power/supply/ltc2941-battery-gauge.c | 53 ++++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index c0cbf4cd59ee..657305214d68 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -112,7 +112,8 @@ static int ltc294x_read_regs(struct i2c_client *client, ret = i2c_transfer(client->adapter, &msgs[0], 2); if (ret < 0) { - dev_err(&client->dev, "ltc2941 read_reg failed!\n"); + dev_err(&client->dev, "ltc2941 read_reg(0x%x[%d]) failed: %pe\n", + reg, num_regs, ERR_PTR(ret)); return ret; } @@ -130,7 +131,8 @@ static int ltc294x_write_regs(struct i2c_client *client, ret = i2c_smbus_write_i2c_block_data(client, reg_start, num_regs, buf); if (ret < 0) { - dev_err(&client->dev, "ltc2941 write_reg failed!\n"); + dev_err(&client->dev, "ltc2941 write_reg(0x%x[%d]) failed: %pe\n", + reg, num_regs, ERR_PTR(ret)); return ret; } @@ -148,11 +150,8 @@ static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp) /* Read status and control registers */ ret = ltc294x_read_regs(info->client, LTC294X_REG_CONTROL, &value, 1); - if (ret < 0) { - dev_err(&info->client->dev, - "Could not read registers from device\n"); - goto error_exit; - } + if (ret < 0) + return ret; control = LTC294X_REG_CONTROL_PRESCALER_SET(prescaler_exp) | LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED; @@ -172,17 +171,11 @@ static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp) if (value != control) { ret = ltc294x_write_regs(info->client, LTC294X_REG_CONTROL, &control, 1); - if (ret < 0) { - dev_err(&info->client->dev, - "Could not write register\n"); - goto error_exit; - } + if (ret < 0) + return ret; } return 0; - -error_exit: - return ret; } static int ltc294x_read_charge_register(const struct ltc294x_info *info, @@ -472,11 +465,9 @@ static int ltc294x_i2c_probe(struct i2c_client *client, /* r_sense can be negative, when sense+ is connected to the battery * instead of the sense-. This results in reversed measurements. */ ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense); - if (ret < 0) { - dev_err(&client->dev, + if (ret < 0) + return dev_err_probe(&client->dev, ret, "Could not find lltc,resistor-sense in devicetree\n"); - return ret; - } info->r_sense = r_sense; ret = of_property_read_u32(np, "lltc,prescaler-exponent", @@ -502,11 +493,9 @@ static int ltc294x_i2c_probe(struct i2c_client *client, /* Read status register to check for LTC2942 */ if (info->id == LTC2941_ID || info->id == LTC2942_ID) { ret = ltc294x_read_regs(client, LTC294X_REG_STATUS, &status, 1); - if (ret < 0) { - dev_err(&client->dev, + if (ret < 0) + return dev_err_probe(&client->dev, ret, "Could not read status register\n"); - return ret; - } if (status & LTC2941_REG_STATUS_CHIP_ID) info->id = LTC2941_ID; else @@ -545,19 +534,17 @@ static int ltc294x_i2c_probe(struct i2c_client *client, return ret; ret = ltc294x_reset(info, prescaler_exp); - if (ret < 0) { - dev_err(&client->dev, "Communication with chip failed\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Communication with chip failed\n"); info->supply = devm_power_supply_register(&client->dev, &info->supply_desc, &psy_cfg); - if (IS_ERR(info->supply)) { - dev_err(&client->dev, "failed to register ltc2941\n"); - return PTR_ERR(info->supply); - } else { - schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); - } + if (IS_ERR(info->supply)) + return dev_err_probe(&client->dev, PTR_ERR(info->supply), + "failed to register ltc2941\n"); + + schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ); return 0; } From cd4c7b2725ccb776e931ceaeaa6333580c10f263 Mon Sep 17 00:00:00 2001 From: Changcheng Deng Date: Fri, 7 Jan 2022 02:26:02 +0000 Subject: [PATCH 61/94] power: supply: ab8500: Remove unneeded variable Remove unneeded variable used to store return value. Reported-by: Zeal Robot Signed-off-by: Changcheng Deng Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index e60db2f869a7..0227e800c58d 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -3208,7 +3208,6 @@ static int ab8500_fg_probe(struct platform_device *pdev) static int ab8500_fg_remove(struct platform_device *pdev) { - int ret = 0; struct ab8500_fg *di = platform_get_drvdata(pdev); component_del(&pdev->dev, &ab8500_fg_component_ops); @@ -3216,7 +3215,7 @@ static int ab8500_fg_remove(struct platform_device *pdev) ab8500_fg_sysfs_exit(di); ab8500_fg_sysfs_psy_remove_attrs(di); - return ret; + return 0; } static SIMPLE_DEV_PM_OPS(ab8500_fg_pm_ops, ab8500_fg_suspend, ab8500_fg_resume); From 5ac121b81b4051e7fc83d5b3456a5e499d5bd147 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 8 Feb 2022 13:51:47 +0100 Subject: [PATCH 62/94] power: supply: axp288-charger: Set Vhold to 4.4V The AXP288's recommended and factory default Vhold value (minimum input voltage below which the input current draw will be reduced) is 4.4V. This lines up with other charger IC's such as the TI bq2419x/bq2429x series which use 4.36V or 4.44V. For some reason some BIOS-es initialize Vhold to 4.6V or even 4.7V which combined with the typical voltage drop over typically low wire gauge micro-USB cables leads to the input-current getting capped below 1A (with a 2A capable dedicated charger) based on Vhold. This leads to slow charging, or even to the device slowly discharging if the device is in heavy use. As the Linux AXP288 drivers use the builtin BC1.2 charger detection and send the input-current-limit according to the detected charger there really is no reason not to use the recommended 4.4V Vhold. Set Vhold to 4.4V to fix the slow charging issue on various devices. There is one exception, the special-case of the HP X2 2-in-1s which combine this BC1.2 capable PMIC with a Type-C port and a 5V/3A factory provided charger with a Type-C plug which does not do BC1.2. These have their input-current-limit hardcoded to 3A (like under Windows) and use a higher Vhold on purpose to limit the current when used with other chargers. To avoid touching Vhold on these HP X2 laptops the code setting Vhold is added to an else branch of the if checking for these models. Note this also fixes the sofar unused VBUS_ISPOUT_VHOLD_SET_MASK define, which was wrong. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_charger.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c index ec41f6cd3f93..c498e62ab4e2 100644 --- a/drivers/power/supply/axp288_charger.c +++ b/drivers/power/supply/axp288_charger.c @@ -42,11 +42,11 @@ #define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */ #define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */ #define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */ -#define VBUS_ISPOUT_VHOLD_SET_MASK 0x31 +#define VBUS_ISPOUT_VHOLD_SET_MASK 0x38 #define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3 #define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */ #define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */ -#define VBUS_ISPOUT_VHOLD_SET_4300MV 0x3 /* 4300mV */ +#define VBUS_ISPOUT_VHOLD_SET_4400MV 0x4 /* 4400mV */ #define VBUS_ISPOUT_VBUS_PATH_DIS BIT(7) #define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ @@ -769,6 +769,16 @@ static int charger_init_hw_regs(struct axp288_chrg_info *info) ret = axp288_charger_vbus_path_select(info, true); if (ret < 0) return ret; + } else { + /* Set Vhold to the factory default / recommended 4.4V */ + val = VBUS_ISPOUT_VHOLD_SET_4400MV << VBUS_ISPOUT_VHOLD_SET_BIT_POS; + ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, + VBUS_ISPOUT_VHOLD_SET_MASK, val); + if (ret < 0) { + dev_err(&info->pdev->dev, "register(%x) write error(%d)\n", + AXP20X_VBUS_IPSOUT_MGMT, ret); + return ret; + } } /* Read current charge voltage and current limit */ From 4c678b7a6442c1d2d137639b2b6730f0587a956d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 22 Feb 2022 21:43:31 +0000 Subject: [PATCH 63/94] power: supply: Use an rbtree rather than flat register cache The smb347 has a very sparse register map (the maximum register is 0x3f but less than 10% of the possible registers appear to be defined) and doesn't have any hardware defaults specified so the sparser data structure of an rbtree is a better fit for it's needs than a flat cache. Since it uses I2C for the control interface there is no performance concern with the slightly more involved code so let's convert it. This will mean we avoid any issues created by assuming that any previously unaccessed registers hold a value that doesn't match what's in the hardware (eg, an _update_bits() suppressing a write). Signed-off-by: Mark Brown Signed-off-by: Sebastian Reichel --- drivers/power/supply/smb347-charger.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c index d56e469043bb..1511f71f937c 100644 --- a/drivers/power/supply/smb347-charger.c +++ b/drivers/power/supply/smb347-charger.c @@ -1488,8 +1488,7 @@ static const struct regmap_config smb347_regmap = { .max_register = SMB347_MAX_REGISTER, .volatile_reg = smb347_volatile_reg, .readable_reg = smb347_readable_reg, - .cache_type = REGCACHE_FLAT, - .num_reg_defaults_raw = SMB347_MAX_REGISTER, + .cache_type = REGCACHE_RBTREE, }; static const struct regulator_ops smb347_usb_vbus_regulator_ops = { From e5372503d82b780ebc3fea3b893f04ca6f52fe42 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Fri, 18 Feb 2022 18:37:37 +0100 Subject: [PATCH 64/94] power: supply: max17042_battery: Use devm_work_autocancel() Use devm_work_autocancel() instead of hand-writing it. This saves a few lines of code. Signed-off-by: Christophe JAILLET Reviewed-by: Hans de Goede Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 87128cf0d577..ab031bbfbe78 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -9,6 +9,7 @@ // This driver is based on max17040_battery.c #include +#include #include #include #include @@ -1030,13 +1031,6 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = { .num_properties = ARRAY_SIZE(max17042_battery_props) - 2, }; -static void max17042_stop_work(void *data) -{ - struct max17042_chip *chip = data; - - cancel_work_sync(&chip->work); -} - static int max17042_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -1142,8 +1136,8 @@ static int max17042_probe(struct i2c_client *client, regmap_read(chip->regmap, MAX17042_STATUS, &val); if (val & STATUS_POR_BIT) { - INIT_WORK(&chip->work, max17042_init_worker); - ret = devm_add_action(&client->dev, max17042_stop_work, chip); + ret = devm_work_autocancel(&client->dev, &chip->work, + max17042_init_worker); if (ret) return ret; schedule_work(&chip->work); From 05f2281b4192320a20d746df6146b3dd82f96e39 Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Mon, 14 Feb 2022 18:07:56 -0600 Subject: [PATCH 65/94] power: supply: Introduces bypass charging property Adds a POWER_SUPPLY_CHARGE_TYPE_BYPASS option to the POWER_SUPPLY_PROP_CHARGE_TYPE property to facilitate bypass charging operation. In bypass charging operation, the charger bypasses the charging path around the integrated converter allowing for a "smart" wall adaptor to perform the power conversion externally. This operational mode is critical for the USB PPS standard of power adaptors and is becoming a common feature in modern charging ICs such as: - BQ25980 - BQ25975 - BQ25960 - LN8000 - LN8410 Signed-off-by: Ricardo Rivera-Matos Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 7 +++++-- drivers/power/supply/power_supply_sysfs.c | 1 + include/linux/power_supply.h | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index fde21d900420..738dcb0cf180 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -380,13 +380,16 @@ Description: algorithm to adjust the charge rate dynamically, without any user configuration required. "Custom" means that the charger uses the charge_control_* properties as configuration for some - different algorithm. + different algorithm. "Bypass" means the charger bypasses the + charging path around the integrated converter allowing for a + "smart" wall adaptor to perform the power conversion + externally. Access: Read, Write Valid values: "Unknown", "N/A", "Trickle", "Fast", "Standard", - "Adaptive", "Custom" + "Adaptive", "Custom", "Bypass" What: /sys/class/power_supply//charge_term_current Date: July 2014 diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index c0dfcfa33206..4239591e1522 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -89,6 +89,7 @@ static const char * const POWER_SUPPLY_CHARGE_TYPE_TEXT[] = { [POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE] = "Adaptive", [POWER_SUPPLY_CHARGE_TYPE_CUSTOM] = "Custom", [POWER_SUPPLY_CHARGE_TYPE_LONGLIFE] = "Long Life", + [POWER_SUPPLY_CHARGE_TYPE_BYPASS] = "Bypass", }; static const char * const POWER_SUPPLY_HEALTH_TEXT[] = { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 006111917d1a..c135196aa9d1 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -49,6 +49,7 @@ enum { POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE, /* dynamically adjusted speed */ POWER_SUPPLY_CHARGE_TYPE_CUSTOM, /* use CHARGE_CONTROL_* props */ POWER_SUPPLY_CHARGE_TYPE_LONGLIFE, /* slow speed, longer life */ + POWER_SUPPLY_CHARGE_TYPE_BYPASS, /* bypassing the charger */ }; enum { From be5f08f066f6530c414429335b58b42ca291c484 Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Mon, 14 Feb 2022 18:07:57 -0600 Subject: [PATCH 66/94] power: supply: bq25980: Implements POWER_SUPPLY_CHARGE_TYPE_BYPASS This patch remaps the bypass operation from POWER_SUPPLY_CHARGE_TYPE_FAST to POWER_SUPPLY_CHARGE_TYPE_BYPASS. Signed-off-by: Ricardo Rivera-Matos Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25980_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/bq25980_charger.c b/drivers/power/supply/bq25980_charger.c index 9daa6d14db4d..9339f5649282 100644 --- a/drivers/power/supply/bq25980_charger.c +++ b/drivers/power/supply/bq25980_charger.c @@ -764,7 +764,7 @@ static int bq25980_get_charger_property(struct power_supply *psy, if (!state.ce) val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; else if (state.bypass) - val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + val->intval = POWER_SUPPLY_CHARGE_TYPE_BYPASS; else if (!state.bypass) val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; break; From 1ff8cc2ca84d695da3fc30a2ead1054e4c99e2fa Mon Sep 17 00:00:00 2001 From: Ricardo Rivera-Matos Date: Mon, 14 Feb 2022 18:07:58 -0600 Subject: [PATCH 67/94] ABI: testing: sysfs-class-power: Adds "Long Life" entry Adds a brief desciption of the "Long Life" charge type and adds "Long Life" to the list of valid values. Signed-off-by: Ricardo Rivera-Matos Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 738dcb0cf180..1a04e729cc77 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -380,16 +380,17 @@ Description: algorithm to adjust the charge rate dynamically, without any user configuration required. "Custom" means that the charger uses the charge_control_* properties as configuration for some - different algorithm. "Bypass" means the charger bypasses the - charging path around the integrated converter allowing for a - "smart" wall adaptor to perform the power conversion - externally. + different algorithm. "Long Life" means the charger reduces its + charging rate in order to prolong the battery health. "Bypass" + means the charger bypasses the charging path around the + integrated converter allowing for a "smart" wall adaptor to + perform the power conversion externally. Access: Read, Write Valid values: "Unknown", "N/A", "Trickle", "Fast", "Standard", - "Adaptive", "Custom", "Bypass" + "Adaptive", "Custom", "Long Life", "Bypass" What: /sys/class/power_supply//charge_term_current Date: July 2014 From de85193cff0d94d030a53656d8fcc41794807bef Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 13 Feb 2022 18:07:03 +0100 Subject: [PATCH 68/94] power: supply: sbs-charger: Don't cancel work that is not initialized This driver can use an interrupt or polling in order get the charger's status. When using polling, a delayed work is used. However, the remove() function unconditionally call cancel_delayed_work_sync(), even if the delayed work is not used and is not initialized. In order to fix it, use devm_delayed_work_autocancel() and remove the now useless remove() function. Fixes: feb583e37f8a ("power: supply: add sbs-charger driver") Signed-off-by: Christophe JAILLET Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-charger.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index 6fa65d118ec1..b08f7d0c4181 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -18,6 +18,7 @@ #include #include #include +#include #define SBS_CHARGER_REG_SPEC_INFO 0x11 #define SBS_CHARGER_REG_STATUS 0x13 @@ -209,7 +210,12 @@ static int sbs_probe(struct i2c_client *client, if (ret) return dev_err_probe(&client->dev, ret, "Failed to request irq\n"); } else { - INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); + ret = devm_delayed_work_autocancel(&client->dev, &chip->work, + sbs_delayed_work); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to init work for polling\n"); + schedule_delayed_work(&chip->work, msecs_to_jiffies(SBS_CHARGER_POLL_TIME)); } @@ -220,15 +226,6 @@ static int sbs_probe(struct i2c_client *client, return 0; } -static int sbs_remove(struct i2c_client *client) -{ - struct sbs_info *chip = i2c_get_clientdata(client); - - cancel_delayed_work_sync(&chip->work); - - return 0; -} - #ifdef CONFIG_OF static const struct of_device_id sbs_dt_ids[] = { { .compatible = "sbs,sbs-charger" }, @@ -245,7 +242,6 @@ MODULE_DEVICE_TABLE(i2c, sbs_id); static struct i2c_driver sbs_driver = { .probe = sbs_probe, - .remove = sbs_remove, .id_table = sbs_id, .driver = { .name = "sbs-charger", From 419c0e9d25ac083877eb37143b54eb57af1401dd Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 13 Feb 2022 18:55:05 +0100 Subject: [PATCH 69/94] power: supply: da9150-fg: Use devm_delayed_work_autocancel() This driver only uses managed resources, except for the delayed work, if it is used. Use devm_delayed_work_autocancel() to also manage the delayed work. The error handling path of the probe and the remove function can both be removed. This saves a few lines of code. Signed-off-by: Christophe JAILLET Reviewed-by: Adam Thomson Signed-off-by: Sebastian Reichel --- drivers/power/supply/da9150-fg.c | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/drivers/power/supply/da9150-fg.c b/drivers/power/supply/da9150-fg.c index 6e367826aae9..e63fa62d1943 100644 --- a/drivers/power/supply/da9150-fg.c +++ b/drivers/power/supply/da9150-fg.c @@ -20,6 +20,7 @@ #include #include #include +#include /* Core2Wire */ #define DA9150_QIF_READ (0x0 << 7) @@ -506,7 +507,13 @@ static int da9150_fg_probe(struct platform_device *pdev) * work for reporting data updates. */ if (fg->interval) { - INIT_DELAYED_WORK(&fg->work, da9150_fg_work); + ret = devm_delayed_work_autocancel(dev, &fg->work, + da9150_fg_work); + if (ret) { + dev_err(dev, "Failed to init work\n"); + return ret; + } + schedule_delayed_work(&fg->work, msecs_to_jiffies(fg->interval)); } @@ -515,34 +522,17 @@ static int da9150_fg_probe(struct platform_device *pdev) irq = platform_get_irq_byname(pdev, "FG"); if (irq < 0) { dev_err(dev, "Failed to get IRQ FG: %d\n", irq); - ret = irq; - goto irq_fail; + return irq; } ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq, IRQF_ONESHOT, "FG", fg); if (ret) { dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); - goto irq_fail; + return ret; } return 0; - -irq_fail: - if (fg->interval) - cancel_delayed_work(&fg->work); - - return ret; -} - -static int da9150_fg_remove(struct platform_device *pdev) -{ - struct da9150_fg *fg = platform_get_drvdata(pdev); - - if (fg->interval) - cancel_delayed_work(&fg->work); - - return 0; } static int da9150_fg_resume(struct platform_device *pdev) @@ -564,7 +554,6 @@ static struct platform_driver da9150_fg_driver = { .name = "da9150-fuel-gauge", }, .probe = da9150_fg_probe, - .remove = da9150_fg_remove, .resume = da9150_fg_resume, }; From d016fc7ab52ab378d8bc11ac6f7e326da7b7a625 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 13 Feb 2022 12:54:56 +0100 Subject: [PATCH 70/94] power: supply: max14656: Use devm_work_autocancel() Use devm_delayed_work_autocancel() instead of hand writing it. It saves a few lines of code. Signed-off-by: Christophe JAILLET Signed-off-by: Sebastian Reichel --- drivers/power/supply/max14656_charger_detector.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c index 3f49b29f3c88..fc36828895bf 100644 --- a/drivers/power/supply/max14656_charger_detector.c +++ b/drivers/power/supply/max14656_charger_detector.c @@ -18,6 +18,7 @@ #include #include #include +#include #define MAX14656_MANUFACTURER "Maxim Integrated" #define MAX14656_NAME "max14656" @@ -233,14 +234,6 @@ static enum power_supply_property max14656_battery_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; -static void stop_irq_work(void *data) -{ - struct max14656_chip *chip = data; - - cancel_delayed_work_sync(&chip->irq_work); -} - - static int max14656_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -286,10 +279,10 @@ static int max14656_probe(struct i2c_client *client, return -EINVAL; } - INIT_DELAYED_WORK(&chip->irq_work, max14656_irq_worker); - ret = devm_add_action(dev, stop_irq_work, chip); + ret = devm_delayed_work_autocancel(dev, &chip->irq_work, + max14656_irq_worker); if (ret) { - dev_err(dev, "devm_add_action %d failed\n", ret); + dev_err(dev, "devm_delayed_work_autocancel %d failed\n", ret); return ret; } From fdc9ce72cffea59c564dc890086b0d0b714d05b0 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 13 Feb 2022 12:19:31 +0100 Subject: [PATCH 71/94] power: supply: max8997_charger: Use devm_work_autocancel() Use devm_work_autocancel() instead of hand writing it. It saves a few lines of code. Signed-off-by: Christophe JAILLET Signed-off-by: Sebastian Reichel --- drivers/power/supply/max8997_charger.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/power/supply/max8997_charger.c b/drivers/power/supply/max8997_charger.c index 25207fe2aa68..127c73b0b3bd 100644 --- a/drivers/power/supply/max8997_charger.c +++ b/drivers/power/supply/max8997_charger.c @@ -14,6 +14,7 @@ #include #include #include +#include /* MAX8997_REG_STATUS4 */ #define DCINOK_SHIFT 1 @@ -94,13 +95,6 @@ static int max8997_battery_get_property(struct power_supply *psy, return 0; } -static void max8997_battery_extcon_evt_stop_work(void *data) -{ - struct charger_data *charger = data; - - cancel_work_sync(&charger->extcon_work); -} - static void max8997_battery_extcon_evt_worker(struct work_struct *work) { struct charger_data *charger = @@ -255,8 +249,8 @@ static int max8997_battery_probe(struct platform_device *pdev) } if (!IS_ERR(charger->reg) && !IS_ERR_OR_NULL(charger->edev)) { - INIT_WORK(&charger->extcon_work, max8997_battery_extcon_evt_worker); - ret = devm_add_action(&pdev->dev, max8997_battery_extcon_evt_stop_work, charger); + ret = devm_work_autocancel(&pdev->dev, &charger->extcon_work, + max8997_battery_extcon_evt_worker); if (ret) { dev_err(&pdev->dev, "failed to add extcon evt stop action: %d\n", ret); return ret; From 00d0566614b7bb7b226cb5a6895b0180ffe6915a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 24 Feb 2022 23:28:04 +0100 Subject: [PATCH 72/94] power: supply: axp288_charger: Use acpi_quirk_skip_acpi_ac_and_battery() Normally the native AXP288 fg/charger drivers are preferred but one some devices the ACPI drivers should be used instead. The ACPI battery/ac drivers use the acpi_quirk_skip_acpi_ac_and_battery() helper to determine if they should skip loading because native fuel-gauge/ charger drivers like the AXP288 drivers will be used. The new acpi_quirk_skip_acpi_ac_and_battery() helper includes a list of exceptions for boards where the ACPI drivers should be used instead. Use this new helper to avoid loading on such boards. Note this requires adding a Kconfig dependency on ACPI, this is not a problem because ACPI should be enabled on all boards with an AXP288 PMIC anyways. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 2 +- drivers/power/supply/axp288_charger.c | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 3520da74b8a7..ce7ecf2c821e 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -351,7 +351,7 @@ config AXP20X_POWER config AXP288_CHARGER tristate "X-Powers AXP288 Charger" - depends on MFD_AXP20X && EXTCON_AXP288 && IOSF_MBI + depends on MFD_AXP20X && EXTCON_AXP288 && IOSF_MBI && ACPI help Say yes here to have support X-Power AXP288 power management IC (PMIC) integrated charger. diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c index c498e62ab4e2..19746e658a6a 100644 --- a/drivers/power/supply/axp288_charger.c +++ b/drivers/power/supply/axp288_charger.c @@ -838,6 +838,13 @@ static int axp288_charger_probe(struct platform_device *pdev) struct power_supply_config charger_cfg = {}; unsigned int val; + /* + * Normally the native AXP288 fg/charger drivers are preferred but + * on some devices the ACPI drivers should be used instead. + */ + if (!acpi_quirk_skip_acpi_ac_and_battery()) + return -ENODEV; + /* * On some devices the fuelgauge and charger parts of the axp288 are * not used, check that the fuelgauge is enabled (CC_CTRL != 0). From da365db704d290fb4dc4cdbd41f60b0ecec1cc03 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 24 Feb 2022 23:28:05 +0100 Subject: [PATCH 73/94] power: supply: axp288_fuel_gauge: Use acpi_quirk_skip_acpi_ac_and_battery() Normally the native AXP288 fg/charger drivers are preferred but one some devices the ACPI drivers should be used instead. The ACPI battery/ac drivers use the acpi_quirk_skip_acpi_ac_and_battery() helper to determine if they should skip loading because native fuel-gauge/ charger drivers like the AXP288 drivers will be used. The new acpi_quirk_skip_acpi_ac_and_battery() helper includes a list of exceptions for boards where the ACPI drivers should be used instead. Use this new helper to avoid loading on such boards. Note this requires adding a Kconfig dependency on ACPI, this is not a problem because ACPI should be enabled on all boards with an AXP288 PMIC anyways. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 2 +- drivers/power/supply/axp288_fuel_gauge.c | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index ce7ecf2c821e..8f9033679f49 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -358,7 +358,7 @@ config AXP288_CHARGER config AXP288_FUEL_GAUGE tristate "X-Powers AXP288 Fuel Gauge" - depends on MFD_AXP20X && IIO && IOSF_MBI + depends on MFD_AXP20X && IIO && IOSF_MBI && ACPI help Say yes here to have support for X-Power power management IC (PMIC) Fuel Gauge. The device provides battery statistics and status diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 13be2c1d6528..e9f285dae489 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -9,6 +9,7 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#include #include #include #include @@ -544,12 +545,6 @@ static const struct dmi_system_id axp288_no_battery_list[] = { DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"), }, }, - { - /* ECS EF20EA */ - .matches = { - DMI_MATCH(DMI_PRODUCT_NAME, "EF20EA"), - }, - }, { /* Intel Cherry Trail Compute Stick, Windows version */ .matches = { @@ -673,6 +668,13 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; int i, pirq, ret; + /* + * Normally the native AXP288 fg/charger drivers are preferred but + * on some devices the ACPI drivers should be used instead. + */ + if (!acpi_quirk_skip_acpi_ac_and_battery()) + return -ENODEV; + if (dmi_check_system(axp288_no_battery_list)) return -ENODEV; From f7731754fdce33dad19be746f647d6ac47c5d695 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 12 Feb 2022 17:48:16 +0100 Subject: [PATCH 74/94] power: supply: bq24190_charger: Fix bq24190_vbus_is_enabled() wrong false return The datasheet says that the BQ24190_REG_POC_CHG_CONFIG bits can have a value of either 10(0x2) or 11(0x3) for OTG (5V boost regulator) mode. Sofar bq24190_vbus_is_enabled() was only checking for 10 but some BIOS-es uses 11 when enabling the regulator at boot. Make bq24190_vbus_is_enabled() also check for 11 so that it does not wrongly returns false when the bits are set to 11. Fixes: 66b6bef2c4e0 ("power: supply: bq24190_charger: Export 5V boost converter as regulator") Cc: Bastien Nocera Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 04aa25f2d033..dcbfd97a55be 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -39,6 +39,7 @@ #define BQ24190_REG_POC_CHG_CONFIG_DISABLE 0x0 #define BQ24190_REG_POC_CHG_CONFIG_CHARGE 0x1 #define BQ24190_REG_POC_CHG_CONFIG_OTG 0x2 +#define BQ24190_REG_POC_CHG_CONFIG_OTG_ALT 0x3 #define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1)) #define BQ24190_REG_POC_SYS_MIN_SHIFT 1 #define BQ24190_REG_POC_SYS_MIN_MIN 3000 @@ -555,7 +556,11 @@ static int bq24190_vbus_is_enabled(struct regulator_dev *dev) pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); - return ret ? ret : val == BQ24190_REG_POC_CHG_CONFIG_OTG; + if (ret) + return ret; + + return (val == BQ24190_REG_POC_CHG_CONFIG_OTG || + val == BQ24190_REG_POC_CHG_CONFIG_OTG_ALT); } static const struct regulator_ops bq24190_vbus_ops = { From d69fc86aca7eda7bd7405c16f35f260c154e14e3 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 12 Feb 2022 17:48:17 +0100 Subject: [PATCH 75/94] power: supply: bq24190_charger: Delay applying charge_type changes when OTG 5V Vbus boost is on Recently userspace has started switching power_supply class devices with a charge_type psy-property between fast and trickle charge mode, see: https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/issues/85 Before this patch bq24190_charger_set_charge_type() would unconditionally write charging or none to the BQ24190_REG_POC_CHG_CONFIG bits, replacing the otg setting of those bits when the OTG 5V Vbus boost converter was on, turning the 5V Vbus off, removing the power from any attached peripherals. This fixes this by keeping track of otg_vbus_enabled and the requested charger_type settings and when otg_vbus_enabled is true, delay applying the charger_type until the 5V boost converter is turned off. Cc: Bastien Nocera Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 41 +++++++++++++++++++------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index dcbfd97a55be..aa1a589eb9f2 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -163,6 +163,8 @@ struct bq24190_dev_info { char model_name[I2C_NAME_SIZE]; bool initialized; bool irq_event; + bool otg_vbus_enabled; + int charge_type; u16 sys_min; u16 iprechg; u16 iterm; @@ -176,6 +178,9 @@ struct bq24190_dev_info { u8 watchdog; }; +static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi, + const union power_supply_propval *val); + static const unsigned int bq24190_usb_extcon_cable[] = { EXTCON_USB, EXTCON_NONE, @@ -502,8 +507,9 @@ static ssize_t bq24190_sysfs_store(struct device *dev, } #endif -static int bq24190_set_charge_mode(struct bq24190_dev_info *bdi, u8 val) +static int bq24190_set_otg_vbus(struct bq24190_dev_info *bdi, bool enable) { + union power_supply_propval val = { .intval = bdi->charge_type }; int ret; ret = pm_runtime_get_sync(bdi->dev); @@ -513,9 +519,14 @@ static int bq24190_set_charge_mode(struct bq24190_dev_info *bdi, u8 val) return ret; } - ret = bq24190_write_mask(bdi, BQ24190_REG_POC, - BQ24190_REG_POC_CHG_CONFIG_MASK, - BQ24190_REG_POC_CHG_CONFIG_SHIFT, val); + bdi->otg_vbus_enabled = enable; + if (enable) + ret = bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_CHG_CONFIG_MASK, + BQ24190_REG_POC_CHG_CONFIG_SHIFT, + BQ24190_REG_POC_CHG_CONFIG_OTG); + else + ret = bq24190_charger_set_charge_type(bdi, &val); pm_runtime_mark_last_busy(bdi->dev); pm_runtime_put_autosuspend(bdi->dev); @@ -526,14 +537,12 @@ static int bq24190_set_charge_mode(struct bq24190_dev_info *bdi, u8 val) #ifdef CONFIG_REGULATOR static int bq24190_vbus_enable(struct regulator_dev *dev) { - return bq24190_set_charge_mode(rdev_get_drvdata(dev), - BQ24190_REG_POC_CHG_CONFIG_OTG); + return bq24190_set_otg_vbus(rdev_get_drvdata(dev), true); } static int bq24190_vbus_disable(struct regulator_dev *dev) { - return bq24190_set_charge_mode(rdev_get_drvdata(dev), - BQ24190_REG_POC_CHG_CONFIG_CHARGE); + return bq24190_set_otg_vbus(rdev_get_drvdata(dev), false); } static int bq24190_vbus_is_enabled(struct regulator_dev *dev) @@ -559,8 +568,9 @@ static int bq24190_vbus_is_enabled(struct regulator_dev *dev) if (ret) return ret; - return (val == BQ24190_REG_POC_CHG_CONFIG_OTG || - val == BQ24190_REG_POC_CHG_CONFIG_OTG_ALT); + bdi->otg_vbus_enabled = (val == BQ24190_REG_POC_CHG_CONFIG_OTG || + val == BQ24190_REG_POC_CHG_CONFIG_OTG_ALT); + return bdi->otg_vbus_enabled; } static const struct regulator_ops bq24190_vbus_ops = { @@ -807,6 +817,14 @@ static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi, return -EINVAL; } + bdi->charge_type = val->intval; + /* + * If the 5V Vbus boost regulator is enabled delay setting + * the charge-type until its gets disabled. + */ + if (bdi->otg_vbus_enabled) + return 0; + if (chg_config) { /* Enabling the charger */ ret = bq24190_write_mask(bdi, BQ24190_REG_CCC, BQ24190_REG_CCC_FORCE_20PCT_MASK, @@ -1788,6 +1806,7 @@ static int bq24190_probe(struct i2c_client *client, bdi->dev = dev; strncpy(bdi->model_name, id->name, I2C_NAME_SIZE); mutex_init(&bdi->f_reg_lock); + bdi->charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST; bdi->f_reg = 0; bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */ INIT_DELAYED_WORK(&bdi->input_current_limit_work, @@ -1925,7 +1944,7 @@ static void bq24190_shutdown(struct i2c_client *client) struct bq24190_dev_info *bdi = i2c_get_clientdata(client); /* Turn off 5V boost regulator on shutdown */ - bq24190_set_charge_mode(bdi, BQ24190_REG_POC_CHG_CONFIG_CHARGE); + bq24190_set_otg_vbus(bdi, false); } static __maybe_unused int bq24190_runtime_suspend(struct device *dev) From d72ce7d324786257410bec6b36e3756647dd76fd Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 26 Feb 2022 00:27:55 +0100 Subject: [PATCH 76/94] power: supply: ab8500: Standardize maintenance charging Maintenance charging is the phase of keeping up the charge after the battery has charged fully using CC/CV charging. This can be done in many successive phases and is usually done with a slightly lower constant voltage than CV, and a slightly lower allowed current. Add an array of maintenance charging points each with a current, voltage and safety timer, and add helper functions to use these. Migrate the AB8500 code over. This is used in several Samsung products using the AB8500 and these batteries and their complete parameters will be added later as full examples, but the default battery in the AB8500 code serves as a reasonable example so far. Reviewed-by: Matti Vaittinen Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-bm.h | 14 ------ drivers/power/supply/ab8500_bmdata.c | 27 +++++++--- drivers/power/supply/ab8500_chargalg.c | 41 +++++++++++---- drivers/power/supply/power_supply_core.c | 11 ++++ include/linux/power_supply.h | 64 ++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 31 deletions(-) diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 6efd5174dbce..4d74d21cf1eb 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -331,24 +331,12 @@ struct ab8500_maxim_parameters { * struct ab8500_battery_type - different batteries supported * @resis_high: battery upper resistance limit * @resis_low: battery lower resistance limit - * @maint_a_cur_lvl: charger current in maintenance A state in mA - * @maint_a_vol_lvl: charger voltage in maintenance A state in mV - * @maint_a_chg_timer_h: charge time in maintenance A state - * @maint_b_cur_lvl: charger current in maintenance B state in mA - * @maint_b_vol_lvl: charger voltage in maintenance B state in mV - * @maint_b_chg_timer_h: charge time in maintenance B state * @low_high_cur_lvl: charger current in temp low/high state in mA * @low_high_vol_lvl: charger voltage in temp low/high state in mV' */ struct ab8500_battery_type { int resis_high; int resis_low; - int maint_a_cur_lvl; - int maint_a_vol_lvl; - int maint_a_chg_timer_h; - int maint_b_cur_lvl; - int maint_b_vol_lvl; - int maint_b_chg_timer_h; int low_high_cur_lvl; int low_high_vol_lvl; }; @@ -393,7 +381,6 @@ struct ab8500_bm_charger_parameters { * @usb_safety_tmr_h safety timer for usb charger * @bkup_bat_v voltage which we charge the backup battery with * @bkup_bat_i current which we charge the backup battery with - * @no_maintenance indicates that maintenance charging is disabled * @capacity_scaling indicates whether capacity scaling is to be used * @chg_unknown_bat flag to enable charging of unknown batteries * @enable_overshoot flag to enable VBAT overshoot control @@ -417,7 +404,6 @@ struct ab8500_bm_data { int usb_safety_tmr_h; int bkup_bat_v; int bkup_bat_i; - bool no_maintenance; bool capacity_scaling; bool chg_unknown_bat; bool enable_overshoot; diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index d8fc72be0f0e..66a454942c7c 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -58,16 +58,25 @@ static struct power_supply_resistance_temp_table temp_to_batres_tbl_thermistor[] { .temp = -20, .resistance = 198 /* 595 mOhm */ }, }; +static struct power_supply_maintenance_charge_table ab8500_maint_charg_table[] = { + { + /* Maintenance charging phase A, 60 hours */ + .charge_current_max_ua = 400000, + .charge_voltage_max_uv = 4050000, + .charge_safety_timer_minutes = 60*60, + }, + { + /* Maintenance charging phase B, 200 hours */ + .charge_current_max_ua = 400000, + .charge_voltage_max_uv = 4000000, + .charge_safety_timer_minutes = 200*60, + } +}; + /* Default battery type for reference designs is the unknown type */ static struct ab8500_battery_type bat_type_thermistor_unknown = { .resis_high = 0, .resis_low = 0, - .maint_a_cur_lvl = 400, - .maint_a_vol_lvl = 4050, - .maint_a_chg_timer_h = 60, - .maint_b_cur_lvl = 400, - .maint_b_vol_lvl = 4000, - .maint_b_chg_timer_h = 200, .low_high_cur_lvl = 300, .low_high_vol_lvl = 4000, }; @@ -124,7 +133,6 @@ struct ab8500_bm_data ab8500_bm_data = { .usb_safety_tmr_h = 4, .bkup_bat_v = BUP_VCH_SEL_2P6V, .bkup_bat_i = BUP_ICH_SEL_150UA, - .no_maintenance = false, .capacity_scaling = false, .chg_unknown_bat = false, .enable_overshoot = false, @@ -179,6 +187,11 @@ int ab8500_bm_of_probe(struct power_supply *psy, /* Charging stops when we drop below this current */ bi->charge_term_current_ua = 200000; + if (!bi->maintenance_charge || !bi->maintenance_charge_size) { + bi->maintenance_charge = ab8500_maint_charg_table; + bi->maintenance_charge_size = ARRAY_SIZE(ab8500_maint_charg_table); + } + /* * Internal resistance and factory resistance are tightly coupled * so both MUST be defined or we fall back to defaults. diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index b5a3096e78a1..6054996b6260 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -430,7 +430,7 @@ static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di) /** * ab8500_chargalg_start_maintenance_timer() - Start charging maintenance timer * @di: pointer to the ab8500_chargalg structure - * @duration: duration of ther maintenance timer in hours + * @duration: duration of ther maintenance timer in minutes * * The maintenance timer is used to maintain the charge in the battery once * the battery is considered full. These timers are chosen to match the @@ -439,9 +439,10 @@ static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di) static void ab8500_chargalg_start_maintenance_timer(struct ab8500_chargalg *di, int duration) { + /* Set a timer in minutes with a 30 second range */ hrtimer_set_expires_range(&di->maintenance_timer, - ktime_set(duration * ONE_HOUR_IN_SECONDS, 0), - ktime_set(FIVE_MINUTES_IN_SECONDS, 0)); + ktime_set(duration * 60, 0), + ktime_set(30, 0)); di->events.maintenance_timer_expired = false; hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL); } @@ -1223,6 +1224,7 @@ static void ab8500_chargalg_external_power_changed(struct power_supply *psy) static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) { struct power_supply_battery_info *bi = di->bm->bi; + struct power_supply_maintenance_charge_table *mt; int charger_status; int ret; @@ -1433,7 +1435,12 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) handle_maxim_chg_curr(di); if (di->charge_status == POWER_SUPPLY_STATUS_FULL && di->maintenance_chg) { - if (di->bm->no_maintenance) + /* + * The battery is fully charged, check if we support + * maintenance charging else go back to waiting for + * the recharge voltage limit. + */ + if (!power_supply_supports_maintenance_charging(bi)) ab8500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE_INIT); else @@ -1454,12 +1461,19 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) break; case STATE_MAINTENANCE_A_INIT: + mt = power_supply_get_maintenance_charging_setting(bi, 0); + if (!mt) { + /* No maintenance A state, go back to normal */ + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + power_supply_changed(di->chargalg_psy); + break; + } ab8500_chargalg_stop_safety_timer(di); ab8500_chargalg_start_maintenance_timer(di, - di->bm->bat_type->maint_a_chg_timer_h); + mt->charge_safety_timer_minutes); ab8500_chargalg_start_charging(di, - di->bm->bat_type->maint_a_vol_lvl, - di->bm->bat_type->maint_a_cur_lvl); + mt->charge_voltage_max_uv, + mt->charge_current_max_ua); ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A); power_supply_changed(di->chargalg_psy); fallthrough; @@ -1472,11 +1486,18 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) break; case STATE_MAINTENANCE_B_INIT: + mt = power_supply_get_maintenance_charging_setting(bi, 1); + if (!mt) { + /* No maintenance B state, go back to normal */ + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + power_supply_changed(di->chargalg_psy); + break; + } ab8500_chargalg_start_maintenance_timer(di, - di->bm->bat_type->maint_b_chg_timer_h); + mt->charge_safety_timer_minutes); ab8500_chargalg_start_charging(di, - di->bm->bat_type->maint_b_vol_lvl, - di->bm->bat_type->maint_b_cur_lvl); + mt->charge_voltage_max_uv, + mt->charge_current_max_ua); ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B); power_supply_changed(di->chargalg_psy); fallthrough; diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 8dbd1197cc62..accbbd36bfe7 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -595,6 +595,7 @@ int power_supply_get_battery_info(struct power_supply *psy, info->precharge_voltage_max_uv = -EINVAL; info->charge_restart_voltage_uv = -EINVAL; info->overvoltage_limit_uv = -EINVAL; + info->maintenance_charge = NULL; info->temp_ambient_alert_min = INT_MIN; info->temp_ambient_alert_max = INT_MAX; info->temp_alert_min = INT_MIN; @@ -844,6 +845,16 @@ int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *t } EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple); +struct power_supply_maintenance_charge_table * +power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, + int index) +{ + if (index >= info->maintenance_charge_size) + return NULL; + return &info->maintenance_charge[index]; +} +EXPORT_SYMBOL_GPL(power_supply_get_maintenance_charging_setting); + /** * power_supply_ocv2cap_simple() - find the battery capacity * @table: Pointer to battery OCV lookup table diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index c135196aa9d1..8ced6550caa7 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -349,6 +349,52 @@ struct power_supply_resistance_temp_table { int resistance; /* internal resistance percent */ }; +/** + * struct power_supply_maintenance_charge_table - setting for maintenace charging + * @charge_current_max_ua: maintenance charging current that is used to keep + * the charge of the battery full as current is consumed after full charging. + * The corresponding charge_voltage_max_uv is used as a safeguard: when we + * reach this voltage the maintenance charging current is turned off. It is + * turned back on if we fall below this voltage. + * @charge_voltage_max_uv: maintenance charging voltage that is usually a bit + * lower than the constant_charge_voltage_max_uv. We can apply this settings + * charge_current_max_ua until we get back up to this voltage. + * @safety_timer_minutes: maintenance charging safety timer, with an expiry + * time in minutes. We will only use maintenance charging in this setting + * for a certain amount of time, then we will first move to the next + * maintenance charge current and voltage pair in respective array and wait + * for the next safety timer timeout, or, if we reached the last maintencance + * charging setting, disable charging until we reach + * charge_restart_voltage_uv and restart ordinary CC/CV charging from there. + * These timers should be chosen to align with the typical discharge curve + * for the battery. + * + * When the main CC/CV charging is complete the battery can optionally be + * maintenance charged at the voltages from this table: a table of settings is + * traversed using a slightly lower current and voltage than what is used for + * CC/CV charging. The maintenance charging will for safety reasons not go on + * indefinately: we lower the current and voltage with successive maintenance + * settings, then disable charging completely after we reach the last one, + * and after that we do not restart charging until we reach + * charge_restart_voltage_uv (see struct power_supply_battery_info) and restart + * ordinary CC/CV charging from there. + * + * As an example, a Samsung EB425161LA Lithium-Ion battery is CC/CV charged + * at 900mA to 4340mV, then maintenance charged at 600mA and 4150mV for + * 60 hours, then maintenance charged at 600mA and 4100mV for 200 hours. + * After this the charge cycle is restarted waiting for + * charge_restart_voltage_uv. + * + * For most mobile electronics this type of maintenance charging is enough for + * the user to disconnect the device and make use of it before both maintenance + * charging cycles are complete. + */ +struct power_supply_maintenance_charge_table { + int charge_current_max_ua; + int charge_voltage_max_uv; + int charge_safety_timer_minutes; +}; + #define POWER_SUPPLY_OCV_TEMP_MAX 20 /** @@ -394,6 +440,10 @@ struct power_supply_resistance_temp_table { * @constant_charge_voltage_max_uv: voltage in microvolts signifying the end of * the CC (constant current) charging phase and the beginning of the CV * (constant voltage) charging phase. + * @maintenance_charge: an array of maintenance charging settings to be used + * after the main CC/CV charging phase is complete. + * @maintenance_charge_size: the number of maintenance charging settings in + * maintenance_charge. * @factory_internal_resistance_uohm: the internal resistance of the battery * at fabrication time, expressed in microohms. This resistance will vary * depending on the lifetime and charge of the battery, so this is just a @@ -543,6 +593,8 @@ struct power_supply_battery_info { int overvoltage_limit_uv; int constant_charge_current_max_ua; int constant_charge_voltage_max_uv; + struct power_supply_maintenance_charge_table *maintenance_charge; + int maintenance_charge_size; int factory_internal_resistance_uohm; int ocv_temp[POWER_SUPPLY_OCV_TEMP_MAX]; int temp_ambient_alert_min; @@ -596,6 +648,8 @@ extern int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info, extern int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table, int table_len, int temp); +extern struct power_supply_maintenance_charge_table * +power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index); extern void power_supply_changed(struct power_supply *psy); extern int power_supply_am_i_supplied(struct power_supply *psy); int power_supply_get_property_from_supplier(struct power_supply *psy, @@ -603,6 +657,16 @@ int power_supply_get_property_from_supplier(struct power_supply *psy, union power_supply_propval *val); extern int power_supply_set_battery_charged(struct power_supply *psy); +static inline bool +power_supply_supports_maintenance_charging(struct power_supply_battery_info *info) +{ + struct power_supply_maintenance_charge_table *mt; + + mt = power_supply_get_maintenance_charging_setting(info, 0); + + return (mt != NULL); +} + #ifdef CONFIG_POWER_SUPPLY extern int power_supply_is_system_supplied(void); #else From 0e8b903b522b5a3cb473035cea085d396dd7150a Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 26 Feb 2022 00:27:56 +0100 Subject: [PATCH 77/94] power: supply: ab8500: Standardize alert mode charging The AB8500 code is using a special current and voltage setting when the battery is in "alert mode", i.e. when it is starting to go outside normal operating conditions so it is too cold or too hot. This makes sense as a way for the charging algorithm to deal with hostile environments. Add the needed members to the struct power_supply_battery_info, and switch the AB8500 charging code over to using this. Reviewed-by: Matti Vaittineen Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-bm.h | 4 -- drivers/power/supply/ab8500_bmdata.c | 15 +++++++- drivers/power/supply/ab8500_chargalg.c | 48 ++++++++++++++++-------- drivers/power/supply/power_supply_core.c | 4 ++ include/linux/power_supply.h | 17 +++++++++ 5 files changed, 66 insertions(+), 22 deletions(-) diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 4d74d21cf1eb..91ef9d4a5222 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -331,14 +331,10 @@ struct ab8500_maxim_parameters { * struct ab8500_battery_type - different batteries supported * @resis_high: battery upper resistance limit * @resis_low: battery lower resistance limit - * @low_high_cur_lvl: charger current in temp low/high state in mA - * @low_high_vol_lvl: charger voltage in temp low/high state in mV' */ struct ab8500_battery_type { int resis_high; int resis_low; - int low_high_cur_lvl; - int low_high_vol_lvl; }; /** diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index 66a454942c7c..bf0b74773eee 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -77,8 +77,6 @@ static struct power_supply_maintenance_charge_table ab8500_maint_charg_table[] = static struct ab8500_battery_type bat_type_thermistor_unknown = { .resis_high = 0, .resis_low = 0, - .low_high_cur_lvl = 300, - .low_high_vol_lvl = 4000, }; static const struct ab8500_bm_capacity_levels cap_levels = { @@ -192,6 +190,19 @@ int ab8500_bm_of_probe(struct power_supply *psy, bi->maintenance_charge_size = ARRAY_SIZE(ab8500_maint_charg_table); } + if (bi->alert_low_temp_charge_current_ua < 0 || + bi->alert_low_temp_charge_voltage_uv < 0) + { + bi->alert_low_temp_charge_current_ua = 300000; + bi->alert_low_temp_charge_voltage_uv = 4000000; + } + if (bi->alert_high_temp_charge_current_ua < 0 || + bi->alert_high_temp_charge_voltage_uv < 0) + { + bi->alert_high_temp_charge_current_ua = 300000; + bi->alert_high_temp_charge_voltage_uv = 4000000; + } + /* * Internal resistance and factory resistance are tightly coupled * so both MUST be defined or we fall back to defaults. diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index 6054996b6260..c9c7f7028af6 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -149,7 +149,8 @@ struct ab8500_chargalg_events { bool batt_ovv; bool batt_rem; bool btemp_underover; - bool btemp_lowhigh; + bool btemp_low; + bool btemp_high; bool main_thermal_prot; bool usb_thermal_prot; bool main_ovv; @@ -684,26 +685,31 @@ static void ab8500_chargalg_check_temp(struct ab8500_chargalg *di) di->batt_data.temp < (bi->temp_alert_max - di->t_hyst_norm)) { /* Temp OK! */ di->events.btemp_underover = false; - di->events.btemp_lowhigh = false; + di->events.btemp_low = false; + di->events.btemp_high = false; di->t_hyst_norm = 0; di->t_hyst_lowhigh = 0; } else { - if (((di->batt_data.temp >= bi->temp_alert_max) && - (di->batt_data.temp < - (bi->temp_max - di->t_hyst_lowhigh))) || - ((di->batt_data.temp > - (bi->temp_min + di->t_hyst_lowhigh)) && - (di->batt_data.temp <= bi->temp_alert_min))) { - /* TEMP minor!!!!! */ + if ((di->batt_data.temp >= bi->temp_alert_max) && + (di->batt_data.temp < (bi->temp_max - di->t_hyst_lowhigh))) { + /* Alert zone for high temperature */ di->events.btemp_underover = false; - di->events.btemp_lowhigh = true; + di->events.btemp_high = true; + di->t_hyst_norm = di->bm->temp_hysteresis; + di->t_hyst_lowhigh = 0; + } else if ((di->batt_data.temp > (bi->temp_min + di->t_hyst_lowhigh)) && + (di->batt_data.temp <= bi->temp_alert_min)) { + /* Alert zone for low temperature */ + di->events.btemp_underover = false; + di->events.btemp_low = true; di->t_hyst_norm = di->bm->temp_hysteresis; di->t_hyst_lowhigh = 0; } else if (di->batt_data.temp <= bi->temp_min || di->batt_data.temp >= bi->temp_max) { /* TEMP major!!!!! */ di->events.btemp_underover = true; - di->events.btemp_lowhigh = false; + di->events.btemp_low = false; + di->events.btemp_high = false; di->t_hyst_norm = 0; di->t_hyst_lowhigh = di->bm->temp_hysteresis; } else { @@ -1313,7 +1319,7 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) ab8500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT); } /* Battery temp high/low */ - else if (di->events.btemp_lowhigh) { + else if (di->events.btemp_low || di->events.btemp_high) { if (di->charge_state != STATE_TEMP_LOWHIGH) ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT); } @@ -1510,9 +1516,19 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) break; case STATE_TEMP_LOWHIGH_INIT: - ab8500_chargalg_start_charging(di, - di->bm->bat_type->low_high_vol_lvl, - di->bm->bat_type->low_high_cur_lvl); + if (di->events.btemp_low) { + ab8500_chargalg_start_charging(di, + bi->alert_low_temp_charge_voltage_uv, + bi->alert_low_temp_charge_current_ua); + } else if (di->events.btemp_high) { + ab8500_chargalg_start_charging(di, + bi->alert_high_temp_charge_voltage_uv, + bi->alert_high_temp_charge_current_ua); + } else { + dev_err(di->dev, "neither low or high temp event occured\n"); + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + } ab8500_chargalg_stop_maintenance_timer(di); di->charge_status = POWER_SUPPLY_STATUS_CHARGING; ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH); @@ -1520,7 +1536,7 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) fallthrough; case STATE_TEMP_LOWHIGH: - if (!di->events.btemp_lowhigh) + if (!di->events.btemp_low && !di->events.btemp_high) ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break; diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index accbbd36bfe7..e3d6d3ff492a 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -596,6 +596,10 @@ int power_supply_get_battery_info(struct power_supply *psy, info->charge_restart_voltage_uv = -EINVAL; info->overvoltage_limit_uv = -EINVAL; info->maintenance_charge = NULL; + info->alert_low_temp_charge_current_ua = -EINVAL; + info->alert_low_temp_charge_voltage_uv = -EINVAL; + info->alert_high_temp_charge_current_ua = -EINVAL; + info->alert_high_temp_charge_voltage_uv = -EINVAL; info->temp_ambient_alert_min = INT_MIN; info->temp_ambient_alert_max = INT_MAX; info->temp_alert_min = INT_MIN; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 8ced6550caa7..f8601598d3d3 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -444,6 +444,19 @@ struct power_supply_maintenance_charge_table { * after the main CC/CV charging phase is complete. * @maintenance_charge_size: the number of maintenance charging settings in * maintenance_charge. + * @alert_low_temp_charge_current_ua: The charging current to use if the battery + * enters low alert temperature, i.e. if the internal temperature is between + * temp_alert_min and temp_min. No matter the charging phase, this + * and alert_high_temp_charge_voltage_uv will be applied. + * @alert_low_temp_charge_voltage_uv: Same as alert_low_temp_charge_current_ua, + * but for the charging voltage. + * @alert_high_temp_charge_current_ua: The charging current to use if the + * battery enters high alert temperature, i.e. if the internal temperature is + * between temp_alert_max and temp_max. No matter the charging phase, this + * and alert_high_temp_charge_voltage_uv will be applied, usually lowering + * the charging current as an evasive manouver. + * @alert_high_temp_charge_voltage_uv: Same as + * alert_high_temp_charge_current_ua, but for the charging voltage. * @factory_internal_resistance_uohm: the internal resistance of the battery * at fabrication time, expressed in microohms. This resistance will vary * depending on the lifetime and charge of the battery, so this is just a @@ -595,6 +608,10 @@ struct power_supply_battery_info { int constant_charge_voltage_max_uv; struct power_supply_maintenance_charge_table *maintenance_charge; int maintenance_charge_size; + int alert_low_temp_charge_current_ua; + int alert_low_temp_charge_voltage_uv; + int alert_high_temp_charge_current_ua; + int alert_high_temp_charge_voltage_uv; int factory_internal_resistance_uohm; int ocv_temp[POWER_SUPPLY_OCV_TEMP_MAX]; int temp_ambient_alert_min; From 1f918e0fe43ec41d906e2cf96b80b15451fed7ba Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 26 Feb 2022 00:27:57 +0100 Subject: [PATCH 78/94] power: supply: ab8500: Standardize BTI resistance The Battery Type Indicator (BTI) resistor is a resistor mounted between a special terminal on the battery and ground. By sending a fixed current (such as 7mA) through this resistor and measuring the voltage over it, the resistance can be determined, and this verifies the battery type. Typical side view of the battery: o o o GND BTI +3.8V Typical example of the electrical layout: +3.8 V BTI | | | + | _______ [ ] 7kOhm ___ | | | | | GND GND By verifying this resistance before attempting to charge the battery we add an additional level of security. In some systems this is used for plug-and-play of batteries with different capacity. In other cases, this is merely used to verify that the right type of battery is connected, if several batteries have the same physical shape and can be plugged into the same slot. Sometimes this is just a surplus security mechanism. Nokia and Samsung among many other vendors are known to use these BTI resistors. Add the BTI properties to struct power_supply_battery_info and switch the AB8500 charger code over to using it. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-bm.h | 12 ----------- drivers/power/supply/ab8500_bmdata.c | 14 ++++++------- drivers/power/supply/ab8500_btemp.c | 14 ++++++------- drivers/power/supply/ab8500_fg.c | 4 ---- drivers/power/supply/power_supply_core.c | 26 +++++++++++++++++++++++- include/linux/power_supply.h | 13 ++++++++++++ 6 files changed, 51 insertions(+), 32 deletions(-) diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 91ef9d4a5222..180a016b3662 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -327,16 +327,6 @@ struct ab8500_maxim_parameters { int charger_curr_step_ua; }; -/** - * struct ab8500_battery_type - different batteries supported - * @resis_high: battery upper resistance limit - * @resis_low: battery lower resistance limit - */ -struct ab8500_battery_type { - int resis_high; - int resis_low; -}; - /** * struct ab8500_bm_capacity_levels - ab8500 capacity level data * @critical: critical capacity level in percent @@ -387,7 +377,6 @@ struct ab8500_bm_charger_parameters { * @temp_hysteresis temperature hysteresis * @maxi maximization parameters * @cap_levels capacity in percent for the different capacity levels - * @bat_type table of supported battery types * @chg_params charger parameters * @fg_params fuel gauge parameters */ @@ -410,7 +399,6 @@ struct ab8500_bm_data { int temp_hysteresis; const struct ab8500_maxim_parameters *maxi; const struct ab8500_bm_capacity_levels *cap_levels; - struct ab8500_battery_type *bat_type; const struct ab8500_bm_charger_parameters *chg_params; const struct ab8500_fg_parameters *fg_params; }; diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index bf0b74773eee..3e6ea22372b2 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -73,12 +73,6 @@ static struct power_supply_maintenance_charge_table ab8500_maint_charg_table[] = } }; -/* Default battery type for reference designs is the unknown type */ -static struct ab8500_battery_type bat_type_thermistor_unknown = { - .resis_high = 0, - .resis_low = 0, -}; - static const struct ab8500_bm_capacity_levels cap_levels = { .critical = 2, .low = 10, @@ -136,7 +130,6 @@ struct ab8500_bm_data ab8500_bm_data = { .enable_overshoot = false, .fg_res = 100, .cap_levels = &cap_levels, - .bat_type = &bat_type_thermistor_unknown, .interval_charging = 5, .interval_not_charging = 120, .maxi = &ab8500_maxi_params, @@ -214,6 +207,13 @@ int ab8500_bm_of_probe(struct power_supply *psy, bi->resist_table_size = ARRAY_SIZE(temp_to_batres_tbl_thermistor); } + /* The default battery is emulated by a resistor at 7K */ + if (bi->bti_resistance_ohm < 0 || + bi->bti_resistance_tolerance < 0) { + bi->bti_resistance_ohm = 7000; + bi->bti_resistance_tolerance = 20; + } + if (!bi->ocv_table[0]) { /* Default capacity table at say 25 degrees Celsius */ bi->ocv_temp[0] = 25; diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index 2a6fc151210c..b7e842dff567 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -237,8 +237,8 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) */ static int ab8500_btemp_id(struct ab8500_btemp *di) { + struct power_supply_battery_info *bi = di->bm->bi; int res; - u8 i; di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; @@ -248,13 +248,11 @@ static int ab8500_btemp_id(struct ab8500_btemp *di) return -ENXIO; } - if ((res <= di->bm->bat_type->resis_high) && - (res >= di->bm->bat_type->resis_low)) { - dev_info(di->dev, "Battery detected on BATTEMP" - " low %d < res %d < high: %d" - " index: %d\n", - di->bm->bat_type->resis_low, res, - di->bm->bat_type->resis_high, i); + if (power_supply_battery_bti_in_range(bi, res)) { + dev_info(di->dev, "Battery detected on BATCTRL (pin C3)" + " resistance %d Ohm = %d Ohm +/- %d%%\n", + res, bi->bti_resistance_ohm, + bi->bti_resistance_tolerance); } else { dev_warn(di->dev, "Battery identified as unknown" ", resistance %d Ohm\n", res); diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 0227e800c58d..f2ff3103e0d0 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -2241,10 +2241,6 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) if (!di->flags.batt_id_received && (bi && (bi->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN))) { - const struct ab8500_battery_type *b; - - b = di->bm->bat_type; - di->flags.batt_id_received = true; di->bat_cap.max_mah_design = diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index e3d6d3ff492a..3d5047d3fe99 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -607,7 +607,9 @@ int power_supply_get_battery_info(struct power_supply *psy, info->temp_min = INT_MIN; info->temp_max = INT_MAX; info->factory_internal_resistance_uohm = -EINVAL; - info->resist_table = NULL; + info->resist_table = NULL; + info->bti_resistance_ohm = -EINVAL; + info->bti_resistance_tolerance = -EINVAL; for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) { info->ocv_table[index] = NULL; @@ -938,6 +940,28 @@ int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info, } EXPORT_SYMBOL_GPL(power_supply_batinfo_ocv2cap); +bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info, + int resistance) +{ + int low, high; + + /* Nothing like this can be checked */ + if (info->bti_resistance_ohm <= 0) + return false; + + /* This will be extremely strict and unlikely to work */ + if (info->bti_resistance_tolerance <= 0) + return (info->bti_resistance_ohm == resistance); + + low = info->bti_resistance_ohm - + (info->bti_resistance_ohm * info->bti_resistance_tolerance) / 100; + high = info->bti_resistance_ohm + + (info->bti_resistance_ohm * info->bti_resistance_tolerance) / 100; + + return ((resistance >= low) && (resistance <= high)); +} +EXPORT_SYMBOL_GPL(power_supply_battery_bti_in_range); + int power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index f8601598d3d3..7fdc03cf2285 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -498,6 +498,14 @@ struct power_supply_maintenance_charge_table { * by temperature: highest temperature with lowest resistance first, lowest * temperature with highest resistance last. * @resist_table_size: the number of items in the resist_table. + * @bti_resistance_ohm: The Battery Type Indicator (BIT) nominal resistance + * in ohms for this battery, if an identification resistor is mounted + * between a third battery terminal and ground. This scheme is used by a lot + * of mobile device batteries. + * @bti_resistance_tolerance: The tolerance in percent of the BTI resistance, + * for example 10 for +/- 10%, if the bti_resistance is set to 7000 and the + * tolerance is 10% we will detect a proper battery if the BTI resistance + * is between 6300 and 7700 Ohm. * * This is the recommended struct to manage static battery parameters, * populated by power_supply_get_battery_info(). Most platform drivers should @@ -624,6 +632,8 @@ struct power_supply_battery_info { int ocv_table_size[POWER_SUPPLY_OCV_TEMP_MAX]; struct power_supply_resistance_temp_table *resist_table; int resist_table_size; + int bti_resistance_ohm; + int bti_resistance_tolerance; }; extern struct atomic_notifier_head power_supply_notifier; @@ -667,6 +677,8 @@ power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table int table_len, int temp); extern struct power_supply_maintenance_charge_table * power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index); +extern bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info, + int resistance); extern void power_supply_changed(struct power_supply *psy); extern int power_supply_am_i_supplied(struct power_supply *psy); int power_supply_get_property_from_supplier(struct power_supply *psy, @@ -684,6 +696,7 @@ power_supply_supports_maintenance_charging(struct power_supply_battery_info *inf return (mt != NULL); } + #ifdef CONFIG_POWER_SUPPLY extern int power_supply_is_system_supplied(void); #else From e9e7d165b4b0413c5b7db74515e4981a226b78a0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 26 Feb 2022 00:27:58 +0100 Subject: [PATCH 79/94] power: supply: Support VBAT-to-Ri lookup tables In Samsung devices, the method used to compensate for temperature, age, load etc is by way of VBAT to Ri tables, which correlates the battery voltage under load (VBAT) to an internal resistance (Ri). Using this Ri and a measurement of the current out of the battery (IBAT) the open circuit voltage (OCV) can be calculated as: OCV = VBAT - (Ri * IBAT) The details are described in comments to struct power_supply_battery_info in the commit. Since not all batteries supply this VBAT-to-Ri data, the fallback method to use the temperature-to-Ri lookup table can also be used as a fallback. Add two helper functions to check if we have the tables needed for using power_supply_vbat2ri() or power_supply_temp2resist_simple() respectively, so capacity estimation code can choose which one to employ. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 67 +++++++++++++- include/linux/power_supply.h | 113 ++++++++++++++++++++++- 2 files changed, 177 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 3d5047d3fe99..fb0b3870566e 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -814,7 +814,7 @@ EXPORT_SYMBOL_GPL(power_supply_put_battery_info); /** * power_supply_temp2resist_simple() - find the battery internal resistance - * percent + * percent from temperature * @table: Pointer to battery resistance temperature table * @table_len: The table length * @temp: Current temperature @@ -851,6 +851,71 @@ int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *t } EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple); +/** + * power_supply_vbat2ri() - find the battery internal resistance + * from the battery voltage + * @info: The battery information container + * @table: Pointer to battery resistance temperature table + * @vbat_uv: The battery voltage in microvolt + * @charging: If we are charging (true) or not (false) + * + * This helper function is used to look up battery internal resistance + * according to current battery voltage. Depending on whether the battery + * is currently charging or not, different resistance will be returned. + * + * Returns the internal resistance in microohm or negative error code. + */ +int power_supply_vbat2ri(struct power_supply_battery_info *info, + int vbat_uv, bool charging) +{ + struct power_supply_vbat_ri_table *vbat2ri; + int table_len; + int i, high, low; + + /* + * If we are charging, and the battery supplies a separate table + * for this state, we use that in order to compensate for the + * charging voltage. Otherwise we use the main table. + */ + if (charging && info->vbat2ri_charging) { + vbat2ri = info->vbat2ri_charging; + table_len = info->vbat2ri_charging_size; + } else { + vbat2ri = info->vbat2ri_discharging; + table_len = info->vbat2ri_discharging_size; + } + + /* + * If no tables are specified, or if we are above the highest voltage in + * the voltage table, just return the factory specified internal resistance. + */ + if (!vbat2ri || (table_len <= 0) || (vbat_uv > vbat2ri[0].vbat_uv)) { + if (charging && (info->factory_internal_resistance_charging_uohm > 0)) + return info->factory_internal_resistance_charging_uohm; + else + return info->factory_internal_resistance_uohm; + } + + /* Break loop at table_len - 1 because that is the highest index */ + for (i = 0; i < table_len - 1; i++) + if (vbat_uv > vbat2ri[i].vbat_uv) + break; + + /* The library function will deal with high == low */ + if ((i == 0) || (i == (table_len - 1))) + high = i; + else + high = i - 1; + low = i; + + return fixp_linear_interpolate(vbat2ri[low].vbat_uv, + vbat2ri[low].ri_uohm, + vbat2ri[high].vbat_uv, + vbat2ri[high].ri_uohm, + vbat_uv); +} +EXPORT_SYMBOL_GPL(power_supply_vbat2ri); + struct power_supply_maintenance_charge_table * power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index) diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 7fdc03cf2285..cb380c1d9459 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -349,6 +349,11 @@ struct power_supply_resistance_temp_table { int resistance; /* internal resistance percent */ }; +struct power_supply_vbat_ri_table { + int vbat_uv; /* Battery voltage in microvolt */ + int ri_uohm; /* Internal resistance in microohm */ +}; + /** * struct power_supply_maintenance_charge_table - setting for maintenace charging * @charge_current_max_ua: maintenance charging current that is used to keep @@ -460,7 +465,14 @@ struct power_supply_maintenance_charge_table { * @factory_internal_resistance_uohm: the internal resistance of the battery * at fabrication time, expressed in microohms. This resistance will vary * depending on the lifetime and charge of the battery, so this is just a - * nominal ballpark figure. + * nominal ballpark figure. This internal resistance is given for the state + * when the battery is discharging. + * @factory_internal_resistance_charging_uohm: the internal resistance of the + * battery at fabrication time while charging, expressed in microohms. + * The charging process will affect the internal resistance of the battery + * so this value provides a better resistance under these circumstances. + * This resistance will vary depending on the lifetime and charge of the + * battery, so this is just a nominal ballpark figure. * @ocv_temp: array indicating the open circuit voltage (OCV) capacity * temperature indices. This is an array of temperatures in degrees Celsius * indicating which capacity table to use for a certain temperature, since @@ -498,6 +510,21 @@ struct power_supply_maintenance_charge_table { * by temperature: highest temperature with lowest resistance first, lowest * temperature with highest resistance last. * @resist_table_size: the number of items in the resist_table. + * @vbat2ri_discharging: this is a table that correlates Battery voltage (VBAT) + * to internal resistance (Ri). The resistance is given in microohm for the + * corresponding voltage in microvolts. The internal resistance is used to + * determine the open circuit voltage so that we can determine the capacity + * of the battery. These voltages to resistance tables apply when the battery + * is discharging. The table must be ordered descending by voltage: highest + * voltage first. + * @vbat2ri_discharging_size: the number of items in the vbat2ri_discharging + * table. + * @vbat2ri_charging: same function as vbat2ri_discharging but for the state + * when the battery is charging. Being under charge changes the battery's + * internal resistance characteristics so a separate table is needed.* + * The table must be ordered descending by voltage: highest voltage first. + * @vbat2ri_charging_size: the number of items in the vbat2ri_charging + * table. * @bti_resistance_ohm: The Battery Type Indicator (BIT) nominal resistance * in ohms for this battery, if an identification resistor is mounted * between a third battery terminal and ground. This scheme is used by a lot @@ -512,7 +539,9 @@ struct power_supply_maintenance_charge_table { * use these for consistency. * * Its field names must correspond to elements in enum power_supply_property. - * The default field value is -EINVAL. + * The default field value is -EINVAL or NULL for pointers. + * + * CC/CV CHARGING: * * The charging parameters here assume a CC/CV charging scheme. This method * is most common with Lithium Ion batteries (other methods are possible) and @@ -597,6 +626,66 @@ struct power_supply_maintenance_charge_table { * Overcharging Lithium Ion cells can be DANGEROUS and lead to fire or * explosions. * + * DETERMINING BATTERY CAPACITY: + * + * Several members of the struct deal with trying to determine the remaining + * capacity in the battery, usually as a percentage of charge. In practice + * many chargers uses a so-called fuel gauge or coloumb counter that measure + * how much charge goes into the battery and how much goes out (+/- leak + * consumption). This does not help if we do not know how much capacity the + * battery has to begin with, such as when it is first used or was taken out + * and charged in a separate charger. Therefore many capacity algorithms use + * the open circuit voltage with a look-up table to determine the rough + * capacity of the battery. The open circuit voltage can be conceptualized + * with an ideal voltage source (V) in series with an internal resistance (Ri) + * like this: + * + * +-------> IBAT >----------------+ + * | ^ | + * [ ] Ri | | + * | | VBAT | + * o <---------- | | + * +| ^ | [ ] Rload + * .---. | | | + * | V | | OCV | | + * '---' | | | + * | | | | + * GND +-------------------------------+ + * + * If we disconnect the load (here simplified as a fixed resistance Rload) + * and measure VBAT with a infinite impedance voltage meter we will get + * VBAT = OCV and this assumption is sometimes made even under load, assuming + * Rload is insignificant. However this will be of dubious quality because the + * load is rarely that small and Ri is strongly nonlinear depending on + * temperature and how much capacity is left in the battery due to the + * chemistry involved. + * + * In many practical applications we cannot just disconnect the battery from + * the load, so instead we often try to measure the instantaneous IBAT (the + * current out from the battery), estimate the Ri and thus calculate the + * voltage drop over Ri and compensate like this: + * + * OCV = VBAT - (IBAT * Ri) + * + * The tables vbat2ri_discharging and vbat2ri_charging are used to determine + * (by interpolation) the Ri from the VBAT under load. These curves are highly + * nonlinear and may need many datapoints but can be found in datasheets for + * some batteries. This gives the compensated open circuit voltage (OCV) for + * the battery even under load. Using this method will also compensate for + * temperature changes in the environment: this will also make the internal + * resistance change, and it will affect the VBAT under load, so correlating + * VBAT to Ri takes both remaining capacity and temperature into consideration. + * + * Alternatively a manufacturer can specify how the capacity of the battery + * is dependent on the battery temperature which is the main factor affecting + * Ri. As we know all checmical reactions are faster when it is warm and slower + * when it is cold. You can put in 1500mAh and only get 800mAh out before the + * voltage drops too low for example. This effect is also highly nonlinear and + * the purpose of the table resist_table: this will take a temperature and + * tell us how big percentage of Ri the specified temperature correlates to. + * Usually we have 100% of the factory_internal_resistance_uohm at 25 degrees + * Celsius. + * * The power supply class itself doesn't use this struct as of now. */ @@ -621,6 +710,7 @@ struct power_supply_battery_info { int alert_high_temp_charge_current_ua; int alert_high_temp_charge_voltage_uv; int factory_internal_resistance_uohm; + int factory_internal_resistance_charging_uohm; int ocv_temp[POWER_SUPPLY_OCV_TEMP_MAX]; int temp_ambient_alert_min; int temp_ambient_alert_max; @@ -632,6 +722,10 @@ struct power_supply_battery_info { int ocv_table_size[POWER_SUPPLY_OCV_TEMP_MAX]; struct power_supply_resistance_temp_table *resist_table; int resist_table_size; + struct power_supply_vbat_ri_table *vbat2ri_discharging; + int vbat2ri_discharging_size; + struct power_supply_vbat_ri_table *vbat2ri_charging; + int vbat2ri_charging_size; int bti_resistance_ohm; int bti_resistance_tolerance; }; @@ -675,6 +769,8 @@ extern int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info, extern int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table, int table_len, int temp); +extern int power_supply_vbat2ri(struct power_supply_battery_info *info, + int vbat_uv, bool charging); extern struct power_supply_maintenance_charge_table * power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index); extern bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info, @@ -696,6 +792,19 @@ power_supply_supports_maintenance_charging(struct power_supply_battery_info *inf return (mt != NULL); } +static inline bool +power_supply_supports_vbat2ri(struct power_supply_battery_info *info) +{ + return ((info->vbat2ri_discharging != NULL) && + info->vbat2ri_discharging_size > 0); +} + +static inline bool +power_supply_supports_temp2ri(struct power_supply_battery_info *info) +{ + return ((info->resist_table != NULL) && + info->resist_table_size > 0); +} #ifdef CONFIG_POWER_SUPPLY extern int power_supply_is_system_supplied(void); From bc5d4a24eca3bc0c6ab5dd81026265a20cabcd48 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 26 Feb 2022 00:27:59 +0100 Subject: [PATCH 80/94] power: supply: ab8500_fg: Use VBAT-to-Ri if possible Augment the AB8500 fuel gauge to use the VBAT-to-Ri method of estimating the internal resistance if possible. Else fall back to using the temperature-to-Ri or just the default Ri. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index f2ff3103e0d0..1abe10c7ff2a 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -877,27 +877,38 @@ static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di) /** * ab8500_fg_battery_resistance() - Returns the battery inner resistance * @di: pointer to the ab8500_fg structure + * @vbat_uncomp_uv: Uncompensated VBAT voltage * * Returns battery inner resistance added with the fuel gauge resistor value * to get the total resistance in the whole link from gnd to bat+ node * in milliohm. */ -static int ab8500_fg_battery_resistance(struct ab8500_fg *di) +static int ab8500_fg_battery_resistance(struct ab8500_fg *di, int vbat_uncomp_uv) { struct power_supply_battery_info *bi = di->bm->bi; int resistance_percent = 0; int resistance; - resistance_percent = power_supply_temp2resist_simple(bi->resist_table, - bi->resist_table_size, - di->bat_temp / 10); /* - * We get a percentage of factory resistance here so first get - * the factory resistance in milliohms then calculate how much - * resistance we have at this temperature. + * Determine the resistance at this voltage. First try VBAT-to-Ri else + * just infer it from the surrounding temperature, if nothing works just + * use the internal resistance. */ - resistance = (bi->factory_internal_resistance_uohm / 1000); - resistance = resistance * resistance_percent / 100; + if (power_supply_supports_vbat2ri(bi)) { + resistance = power_supply_vbat2ri(bi, vbat_uncomp_uv, di->flags.charging); + /* Convert to milliohm */ + resistance = resistance / 1000; + } else if (power_supply_supports_temp2ri(bi)) { + resistance_percent = power_supply_temp2resist_simple(bi->resist_table, + bi->resist_table_size, + di->bat_temp / 10); + /* Convert to milliohm */ + resistance = bi->factory_internal_resistance_uohm / 1000; + resistance = resistance * resistance_percent / 100; + } else { + /* Last fallback */ + resistance = bi->factory_internal_resistance_uohm / 1000; + } dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d" " fg resistance %d, total: %d (mOhm)\n", @@ -955,7 +966,7 @@ static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always) vbat_uv = vbat_uv / i; /* Next we apply voltage compensation from internal resistance */ - rcomp = ab8500_fg_battery_resistance(di); + rcomp = ab8500_fg_battery_resistance(di, vbat_uv); vbat_uv = vbat_uv - (di->inst_curr_ua * rcomp) / 1000; /* Always keep this state at latest measurement */ From c8aee3f41cb84a9ac3b6b5ba2e92cda6cf50d0a0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 26 Feb 2022 00:28:00 +0100 Subject: [PATCH 81/94] power: supply: Static data for Samsung batteries If we detect a Samsung SDI battery, we return a static struct power_supply_battery_info and avoid looking further. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 6 + drivers/power/supply/Makefile | 1 + drivers/power/supply/power_supply_core.c | 63 +- drivers/power/supply/samsung-sdi-battery.c | 918 +++++++++++++++++++++ drivers/power/supply/samsung-sdi-battery.h | 13 + 5 files changed, 974 insertions(+), 27 deletions(-) create mode 100644 drivers/power/supply/samsung-sdi-battery.c create mode 100644 drivers/power/supply/samsung-sdi-battery.h diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 8f9033679f49..fa128b9f6e63 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -181,6 +181,12 @@ config BATTERY_OLPC help Say Y to enable support for the battery on the OLPC laptop. +config BATTERY_SAMSUNG_SDI + bool "Samsung SDI batteries" + help + Say Y to enable support for Samsung SDI battery data. + These batteries are used in Samsung mobile phones. + config BATTERY_TOSA tristate "Sharp SL-6000 (tosa) battery" depends on MACH_TOSA && MFD_TC6393XB && TOUCHSCREEN_WM97XX diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index edf983676799..b87eb8d87bac 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o +obj-$(CONFIG_BATTERY_SAMSUNG_SDI) += samsung-sdi-battery.o obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o obj-$(CONFIG_BATTERY_INGENIC) += ingenic-battery.o diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index fb0b3870566e..ea02c8dcd748 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -23,6 +23,7 @@ #include #include #include "power_supply.h" +#include "samsung-sdi-battery.h" /* exported for the APM Power driver, APM emulation */ struct class *power_supply_class; @@ -578,9 +579,42 @@ int power_supply_get_battery_info(struct power_supply *psy, const __be32 *list; u32 min_max[2]; + if (psy->of_node) { + battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0); + if (!battery_np) + return -ENODEV; + + fwnode = fwnode_handle_get(of_fwnode_handle(battery_np)); + } else { + err = fwnode_property_get_reference_args( + dev_fwnode(psy->dev.parent), + "monitored-battery", NULL, 0, 0, &args); + if (err) + return err; + + fwnode = args.fwnode; + } + + err = fwnode_property_read_string(fwnode, "compatible", &value); + if (err) + goto out_put_node; + + + /* Try static batteries first */ + err = samsung_sdi_battery_get_info(&psy->dev, value, &info); + if (!err) + goto out_ret_pointer; + + if (strcmp("simple-battery", value)) { + err = -ENODEV; + goto out_put_node; + } + info = devm_kmalloc(&psy->dev, sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; + if (!info) { + err = -ENOMEM; + goto out_put_node; + } info->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; info->energy_full_design_uwh = -EINVAL; @@ -617,31 +651,6 @@ int power_supply_get_battery_info(struct power_supply *psy, info->ocv_table_size[index] = -EINVAL; } - if (psy->of_node) { - battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0); - if (!battery_np) - return -ENODEV; - - fwnode = fwnode_handle_get(of_fwnode_handle(battery_np)); - } else { - err = fwnode_property_get_reference_args( - dev_fwnode(psy->dev.parent), - "monitored-battery", NULL, 0, 0, &args); - if (err) - return err; - - fwnode = args.fwnode; - } - - err = fwnode_property_read_string(fwnode, "compatible", &value); - if (err) - goto out_put_node; - - if (strcmp("simple-battery", value)) { - err = -ENODEV; - goto out_put_node; - } - /* The property and field names below must correspond to elements * in enum power_supply_property. For reasoning, see * Documentation/power/power_supply_class.rst. diff --git a/drivers/power/supply/samsung-sdi-battery.c b/drivers/power/supply/samsung-sdi-battery.c new file mode 100644 index 000000000000..9d59f277f519 --- /dev/null +++ b/drivers/power/supply/samsung-sdi-battery.c @@ -0,0 +1,918 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Battery data and characteristics for Samsung SDI (Samsung Digital Interface) + * batteries. The data is retrieved automatically into drivers using + * the power_supply_get_battery_info() call. + * + * The BTI (battery type indicator) resistance in the code drops was very + * unreliable. The resistance listed here was obtained by simply measuring + * the BTI resistance with a multimeter on the battery. + */ +#include +#include +#include "samsung-sdi-battery.h" + +struct samsung_sdi_battery { + char *compatible; + char *name; + struct power_supply_battery_info info; +}; + +/* + * Voltage to internal resistance tables. The internal resistance varies + * depending on the VBAT voltage, so look this up from a table. Different + * tables apply depending on whether we are charging or not. + */ + +static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb_l1m7flu[] = { + { .vbat_uv = 4240000, .ri_uohm = 160000 }, + { .vbat_uv = 4210000, .ri_uohm = 179000 }, + { .vbat_uv = 4180000, .ri_uohm = 183000 }, + { .vbat_uv = 4160000, .ri_uohm = 184000 }, + { .vbat_uv = 4140000, .ri_uohm = 191000 }, + { .vbat_uv = 4120000, .ri_uohm = 204000 }, + { .vbat_uv = 4076000, .ri_uohm = 220000 }, + { .vbat_uv = 4030000, .ri_uohm = 227000 }, + { .vbat_uv = 3986000, .ri_uohm = 215000 }, + { .vbat_uv = 3916000, .ri_uohm = 221000 }, + { .vbat_uv = 3842000, .ri_uohm = 259000 }, + { .vbat_uv = 3773000, .ri_uohm = 287000 }, + { .vbat_uv = 3742000, .ri_uohm = 283000 }, + { .vbat_uv = 3709000, .ri_uohm = 277000 }, + { .vbat_uv = 3685000, .ri_uohm = 297000 }, + { .vbat_uv = 3646000, .ri_uohm = 310000 }, + { .vbat_uv = 3616000, .ri_uohm = 331000 }, + { .vbat_uv = 3602000, .ri_uohm = 370000 }, + { .vbat_uv = 3578000, .ri_uohm = 350000 }, + { .vbat_uv = 3553000, .ri_uohm = 321000 }, + { .vbat_uv = 3503000, .ri_uohm = 322000 }, + { .vbat_uv = 3400000, .ri_uohm = 269000 }, + { .vbat_uv = 3360000, .ri_uohm = 328000 }, + { .vbat_uv = 3330000, .ri_uohm = 305000 }, + { .vbat_uv = 3300000, .ri_uohm = 339000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb_l1m7flu[] = { + { .vbat_uv = 4302000, .ri_uohm = 230000 }, + { .vbat_uv = 4276000, .ri_uohm = 345000 }, + { .vbat_uv = 4227000, .ri_uohm = 345000 }, + { .vbat_uv = 4171000, .ri_uohm = 346000 }, + { .vbat_uv = 4134000, .ri_uohm = 311000 }, + { .vbat_uv = 4084000, .ri_uohm = 299000 }, + { .vbat_uv = 4052000, .ri_uohm = 316000 }, + { .vbat_uv = 4012000, .ri_uohm = 309000 }, + { .vbat_uv = 3961000, .ri_uohm = 303000 }, + { .vbat_uv = 3939000, .ri_uohm = 280000 }, + { .vbat_uv = 3904000, .ri_uohm = 261000 }, + { .vbat_uv = 3850000, .ri_uohm = 212000 }, + { .vbat_uv = 3800000, .ri_uohm = 232000 }, + { .vbat_uv = 3750000, .ri_uohm = 177000 }, + { .vbat_uv = 3712000, .ri_uohm = 164000 }, + { .vbat_uv = 3674000, .ri_uohm = 161000 }, + { .vbat_uv = 3590000, .ri_uohm = 164000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161la[] = { + { .vbat_uv = 4240000, .ri_uohm = 160000 }, + { .vbat_uv = 4210000, .ri_uohm = 179000 }, + { .vbat_uv = 4180000, .ri_uohm = 183000 }, + { .vbat_uv = 4160000, .ri_uohm = 184000 }, + { .vbat_uv = 4140000, .ri_uohm = 191000 }, + { .vbat_uv = 4120000, .ri_uohm = 204000 }, + { .vbat_uv = 4080000, .ri_uohm = 200000 }, + { .vbat_uv = 4027000, .ri_uohm = 202000 }, + { .vbat_uv = 3916000, .ri_uohm = 221000 }, + { .vbat_uv = 3842000, .ri_uohm = 259000 }, + { .vbat_uv = 3800000, .ri_uohm = 262000 }, + { .vbat_uv = 3742000, .ri_uohm = 263000 }, + { .vbat_uv = 3709000, .ri_uohm = 277000 }, + { .vbat_uv = 3685000, .ri_uohm = 312000 }, + { .vbat_uv = 3668000, .ri_uohm = 258000 }, + { .vbat_uv = 3660000, .ri_uohm = 247000 }, + { .vbat_uv = 3636000, .ri_uohm = 293000 }, + { .vbat_uv = 3616000, .ri_uohm = 331000 }, + { .vbat_uv = 3600000, .ri_uohm = 349000 }, + { .vbat_uv = 3593000, .ri_uohm = 345000 }, + { .vbat_uv = 3585000, .ri_uohm = 344000 }, + { .vbat_uv = 3572000, .ri_uohm = 336000 }, + { .vbat_uv = 3553000, .ri_uohm = 321000 }, + { .vbat_uv = 3517000, .ri_uohm = 336000 }, + { .vbat_uv = 3503000, .ri_uohm = 322000 }, + { .vbat_uv = 3400000, .ri_uohm = 269000 }, + { .vbat_uv = 3360000, .ri_uohm = 328000 }, + { .vbat_uv = 3330000, .ri_uohm = 305000 }, + { .vbat_uv = 3300000, .ri_uohm = 339000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161la[] = { + { .vbat_uv = 4345000, .ri_uohm = 230000 }, + { .vbat_uv = 4329000, .ri_uohm = 238000 }, + { .vbat_uv = 4314000, .ri_uohm = 225000 }, + { .vbat_uv = 4311000, .ri_uohm = 239000 }, + { .vbat_uv = 4294000, .ri_uohm = 235000 }, + { .vbat_uv = 4264000, .ri_uohm = 229000 }, + { .vbat_uv = 4262000, .ri_uohm = 228000 }, + { .vbat_uv = 4252000, .ri_uohm = 236000 }, + { .vbat_uv = 4244000, .ri_uohm = 234000 }, + { .vbat_uv = 4235000, .ri_uohm = 234000 }, + { .vbat_uv = 4227000, .ri_uohm = 238000 }, + { .vbat_uv = 4219000, .ri_uohm = 242000 }, + { .vbat_uv = 4212000, .ri_uohm = 239000 }, + { .vbat_uv = 4206000, .ri_uohm = 231000 }, + { .vbat_uv = 4201000, .ri_uohm = 231000 }, + { .vbat_uv = 4192000, .ri_uohm = 224000 }, + { .vbat_uv = 4184000, .ri_uohm = 238000 }, + { .vbat_uv = 4173000, .ri_uohm = 245000 }, + { .vbat_uv = 4161000, .ri_uohm = 244000 }, + { .vbat_uv = 4146000, .ri_uohm = 244000 }, + { .vbat_uv = 4127000, .ri_uohm = 228000 }, + { .vbat_uv = 4119000, .ri_uohm = 218000 }, + { .vbat_uv = 4112000, .ri_uohm = 215000 }, + { .vbat_uv = 4108000, .ri_uohm = 209000 }, + { .vbat_uv = 4102000, .ri_uohm = 214000 }, + { .vbat_uv = 4096000, .ri_uohm = 215000 }, + { .vbat_uv = 4090000, .ri_uohm = 215000 }, + { .vbat_uv = 4083000, .ri_uohm = 219000 }, + { .vbat_uv = 4078000, .ri_uohm = 208000 }, + { .vbat_uv = 4071000, .ri_uohm = 205000 }, + { .vbat_uv = 4066000, .ri_uohm = 208000 }, + { .vbat_uv = 4061000, .ri_uohm = 210000 }, + { .vbat_uv = 4055000, .ri_uohm = 212000 }, + { .vbat_uv = 4049000, .ri_uohm = 215000 }, + { .vbat_uv = 4042000, .ri_uohm = 212000 }, + { .vbat_uv = 4032000, .ri_uohm = 217000 }, + { .vbat_uv = 4027000, .ri_uohm = 220000 }, + { .vbat_uv = 4020000, .ri_uohm = 210000 }, + { .vbat_uv = 4013000, .ri_uohm = 214000 }, + { .vbat_uv = 4007000, .ri_uohm = 219000 }, + { .vbat_uv = 4003000, .ri_uohm = 229000 }, + { .vbat_uv = 3996000, .ri_uohm = 246000 }, + { .vbat_uv = 3990000, .ri_uohm = 245000 }, + { .vbat_uv = 3984000, .ri_uohm = 242000 }, + { .vbat_uv = 3977000, .ri_uohm = 236000 }, + { .vbat_uv = 3971000, .ri_uohm = 231000 }, + { .vbat_uv = 3966000, .ri_uohm = 229000 }, + { .vbat_uv = 3952000, .ri_uohm = 226000 }, + { .vbat_uv = 3946000, .ri_uohm = 222000 }, + { .vbat_uv = 3941000, .ri_uohm = 222000 }, + { .vbat_uv = 3936000, .ri_uohm = 217000 }, + { .vbat_uv = 3932000, .ri_uohm = 217000 }, + { .vbat_uv = 3928000, .ri_uohm = 212000 }, + { .vbat_uv = 3926000, .ri_uohm = 214000 }, + { .vbat_uv = 3922000, .ri_uohm = 209000 }, + { .vbat_uv = 3917000, .ri_uohm = 215000 }, + { .vbat_uv = 3914000, .ri_uohm = 212000 }, + { .vbat_uv = 3912000, .ri_uohm = 220000 }, + { .vbat_uv = 3910000, .ri_uohm = 226000 }, + { .vbat_uv = 3903000, .ri_uohm = 226000 }, + { .vbat_uv = 3891000, .ri_uohm = 222000 }, + { .vbat_uv = 3871000, .ri_uohm = 221000 }, + { .vbat_uv = 3857000, .ri_uohm = 219000 }, + { .vbat_uv = 3850000, .ri_uohm = 216000 }, + { .vbat_uv = 3843000, .ri_uohm = 212000 }, + { .vbat_uv = 3835000, .ri_uohm = 206000 }, + { .vbat_uv = 3825000, .ri_uohm = 217000 }, + { .vbat_uv = 3824000, .ri_uohm = 220000 }, + { .vbat_uv = 3820000, .ri_uohm = 237000 }, + { .vbat_uv = 3800000, .ri_uohm = 232000 }, + { .vbat_uv = 3750000, .ri_uohm = 177000 }, + { .vbat_uv = 3712000, .ri_uohm = 164000 }, + { .vbat_uv = 3674000, .ri_uohm = 161000 }, + { .vbat_uv = 3590000, .ri_uohm = 164000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161lu[] = { + { .vbat_uv = 4240000, .ri_uohm = 160000 }, + { .vbat_uv = 4210000, .ri_uohm = 179000 }, + { .vbat_uv = 4180000, .ri_uohm = 183000 }, + { .vbat_uv = 4160000, .ri_uohm = 184000 }, + { .vbat_uv = 4140000, .ri_uohm = 191000 }, + { .vbat_uv = 4120000, .ri_uohm = 204000 }, + { .vbat_uv = 4080000, .ri_uohm = 200000 }, + { .vbat_uv = 4027000, .ri_uohm = 202000 }, + { .vbat_uv = 3916000, .ri_uohm = 221000 }, + { .vbat_uv = 3842000, .ri_uohm = 259000 }, + { .vbat_uv = 3800000, .ri_uohm = 262000 }, + { .vbat_uv = 3742000, .ri_uohm = 263000 }, + { .vbat_uv = 3708000, .ri_uohm = 277000 }, + { .vbat_uv = 3684000, .ri_uohm = 272000 }, + { .vbat_uv = 3664000, .ri_uohm = 278000 }, + { .vbat_uv = 3655000, .ri_uohm = 285000 }, + { .vbat_uv = 3638000, .ri_uohm = 261000 }, + { .vbat_uv = 3624000, .ri_uohm = 259000 }, + { .vbat_uv = 3616000, .ri_uohm = 266000 }, + { .vbat_uv = 3597000, .ri_uohm = 278000 }, + { .vbat_uv = 3581000, .ri_uohm = 281000 }, + { .vbat_uv = 3560000, .ri_uohm = 287000 }, + { .vbat_uv = 3527000, .ri_uohm = 289000 }, + { .vbat_uv = 3512000, .ri_uohm = 286000 }, + { .vbat_uv = 3494000, .ri_uohm = 282000 }, + { .vbat_uv = 3400000, .ri_uohm = 269000 }, + { .vbat_uv = 3360000, .ri_uohm = 328000 }, + { .vbat_uv = 3330000, .ri_uohm = 305000 }, + { .vbat_uv = 3300000, .ri_uohm = 339000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161lu[] = { + { .vbat_uv = 4346000, .ri_uohm = 293000 }, + { .vbat_uv = 4336000, .ri_uohm = 290000 }, + { .vbat_uv = 4315000, .ri_uohm = 274000 }, + { .vbat_uv = 4310000, .ri_uohm = 264000 }, + { .vbat_uv = 4275000, .ri_uohm = 275000 }, + { .vbat_uv = 4267000, .ri_uohm = 274000 }, + { .vbat_uv = 4227000, .ri_uohm = 262000 }, + { .vbat_uv = 4186000, .ri_uohm = 282000 }, + { .vbat_uv = 4136000, .ri_uohm = 246000 }, + { .vbat_uv = 4110000, .ri_uohm = 242000 }, + { .vbat_uv = 4077000, .ri_uohm = 249000 }, + { .vbat_uv = 4049000, .ri_uohm = 238000 }, + { .vbat_uv = 4017000, .ri_uohm = 268000 }, + { .vbat_uv = 3986000, .ri_uohm = 261000 }, + { .vbat_uv = 3962000, .ri_uohm = 252000 }, + { .vbat_uv = 3940000, .ri_uohm = 235000 }, + { .vbat_uv = 3930000, .ri_uohm = 237000 }, + { .vbat_uv = 3924000, .ri_uohm = 255000 }, + { .vbat_uv = 3910000, .ri_uohm = 244000 }, + { .vbat_uv = 3889000, .ri_uohm = 231000 }, + { .vbat_uv = 3875000, .ri_uohm = 249000 }, + { .vbat_uv = 3850000, .ri_uohm = 212000 }, + { .vbat_uv = 3800000, .ri_uohm = 232000 }, + { .vbat_uv = 3750000, .ri_uohm = 177000 }, + { .vbat_uv = 3712000, .ri_uohm = 164000 }, + { .vbat_uv = 3674000, .ri_uohm = 161000 }, + { .vbat_uv = 3590000, .ri_uohm = 164000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb485159lu[] = { + { .vbat_uv = 4240000, .ri_uohm = 160000 }, + { .vbat_uv = 4210000, .ri_uohm = 179000 }, + { .vbat_uv = 4180000, .ri_uohm = 183000 }, + { .vbat_uv = 4160000, .ri_uohm = 184000 }, + { .vbat_uv = 4140000, .ri_uohm = 191000 }, + { .vbat_uv = 4120000, .ri_uohm = 204000 }, + { .vbat_uv = 4080000, .ri_uohm = 200000 }, + { .vbat_uv = 4027000, .ri_uohm = 202000 }, + { .vbat_uv = 3916000, .ri_uohm = 221000 }, + { .vbat_uv = 3842000, .ri_uohm = 259000 }, + { .vbat_uv = 3800000, .ri_uohm = 262000 }, + { .vbat_uv = 3715000, .ri_uohm = 340000 }, + { .vbat_uv = 3700000, .ri_uohm = 300000 }, + { .vbat_uv = 3682000, .ri_uohm = 233000 }, + { .vbat_uv = 3655000, .ri_uohm = 246000 }, + { .vbat_uv = 3639000, .ri_uohm = 260000 }, + { .vbat_uv = 3621000, .ri_uohm = 254000 }, + { .vbat_uv = 3583000, .ri_uohm = 266000 }, + { .vbat_uv = 3536000, .ri_uohm = 274000 }, + { .vbat_uv = 3502000, .ri_uohm = 300000 }, + { .vbat_uv = 3465000, .ri_uohm = 245000 }, + { .vbat_uv = 3438000, .ri_uohm = 225000 }, + { .vbat_uv = 3330000, .ri_uohm = 305000 }, + { .vbat_uv = 3300000, .ri_uohm = 339000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb485159lu[] = { + { .vbat_uv = 4302000, .ri_uohm = 200000 }, + { .vbat_uv = 4258000, .ri_uohm = 206000 }, + { .vbat_uv = 4200000, .ri_uohm = 231000 }, + { .vbat_uv = 4150000, .ri_uohm = 198000 }, + { .vbat_uv = 4134000, .ri_uohm = 268000 }, + { .vbat_uv = 4058000, .ri_uohm = 172000 }, + { .vbat_uv = 4003000, .ri_uohm = 227000 }, + { .vbat_uv = 3972000, .ri_uohm = 241000 }, + { .vbat_uv = 3953000, .ri_uohm = 244000 }, + { .vbat_uv = 3950000, .ri_uohm = 213000 }, + { .vbat_uv = 3900000, .ri_uohm = 225000 }, + { .vbat_uv = 3850000, .ri_uohm = 212000 }, + { .vbat_uv = 3800000, .ri_uohm = 232000 }, + { .vbat_uv = 3750000, .ri_uohm = 177000 }, + { .vbat_uv = 3712000, .ri_uohm = 164000 }, + { .vbat_uv = 3674000, .ri_uohm = 161000 }, + { .vbat_uv = 3590000, .ri_uohm = 164000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb535151vu[] = { + { .vbat_uv = 4071000, .ri_uohm = 158000 }, + { .vbat_uv = 4019000, .ri_uohm = 187000 }, + { .vbat_uv = 3951000, .ri_uohm = 191000 }, + { .vbat_uv = 3901000, .ri_uohm = 193000 }, + { .vbat_uv = 3850000, .ri_uohm = 273000 }, + { .vbat_uv = 3800000, .ri_uohm = 305000 }, + { .vbat_uv = 3750000, .ri_uohm = 205000 }, + { .vbat_uv = 3700000, .ri_uohm = 290000 }, + { .vbat_uv = 3650000, .ri_uohm = 262000 }, + { .vbat_uv = 3618000, .ri_uohm = 290000 }, + { .vbat_uv = 3505000, .ri_uohm = 235000 }, + { .vbat_uv = 3484000, .ri_uohm = 253000 }, + { .vbat_uv = 3413000, .ri_uohm = 243000 }, + { .vbat_uv = 3393000, .ri_uohm = 285000 }, + { .vbat_uv = 3361000, .ri_uohm = 281000 }, + { .vbat_uv = 3302000, .ri_uohm = 286000 }, + { .vbat_uv = 3280000, .ri_uohm = 250000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb535151vu[] = { + { .vbat_uv = 4190000, .ri_uohm = 214000 }, + { .vbat_uv = 4159000, .ri_uohm = 252000 }, + { .vbat_uv = 4121000, .ri_uohm = 245000 }, + { .vbat_uv = 4069000, .ri_uohm = 228000 }, + { .vbat_uv = 4046000, .ri_uohm = 229000 }, + { .vbat_uv = 4026000, .ri_uohm = 233000 }, + { .vbat_uv = 4007000, .ri_uohm = 240000 }, + { .vbat_uv = 3982000, .ri_uohm = 291000 }, + { .vbat_uv = 3945000, .ri_uohm = 276000 }, + { .vbat_uv = 3924000, .ri_uohm = 266000 }, + { .vbat_uv = 3910000, .ri_uohm = 258000 }, + { .vbat_uv = 3900000, .ri_uohm = 271000 }, + { .vbat_uv = 3844000, .ri_uohm = 279000 }, + { .vbat_uv = 3772000, .ri_uohm = 217000 }, + { .vbat_uv = 3673000, .ri_uohm = 208000 }, + { .vbat_uv = 3571000, .ri_uohm = 208000 }, + { .vbat_uv = 3510000, .ri_uohm = 228000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb585157lu[] = { + { .vbat_uv = 4194000, .ri_uohm = 121000 }, + { .vbat_uv = 4169000, .ri_uohm = 188000 }, + { .vbat_uv = 4136000, .ri_uohm = 173000 }, + { .vbat_uv = 4108000, .ri_uohm = 158000 }, + { .vbat_uv = 4064000, .ri_uohm = 143000 }, + { .vbat_uv = 3956000, .ri_uohm = 160000 }, + { .vbat_uv = 3847000, .ri_uohm = 262000 }, + { .vbat_uv = 3806000, .ri_uohm = 280000 }, + { .vbat_uv = 3801000, .ri_uohm = 266000 }, + { .vbat_uv = 3794000, .ri_uohm = 259000 }, + { .vbat_uv = 3785000, .ri_uohm = 234000 }, + { .vbat_uv = 3779000, .ri_uohm = 227000 }, + { .vbat_uv = 3772000, .ri_uohm = 222000 }, + { .vbat_uv = 3765000, .ri_uohm = 221000 }, + { .vbat_uv = 3759000, .ri_uohm = 216000 }, + { .vbat_uv = 3754000, .ri_uohm = 206000 }, + { .vbat_uv = 3747000, .ri_uohm = 212000 }, + { .vbat_uv = 3743000, .ri_uohm = 208000 }, + { .vbat_uv = 3737000, .ri_uohm = 212000 }, + { .vbat_uv = 3733000, .ri_uohm = 200000 }, + { .vbat_uv = 3728000, .ri_uohm = 203000 }, + { .vbat_uv = 3722000, .ri_uohm = 207000 }, + { .vbat_uv = 3719000, .ri_uohm = 208000 }, + { .vbat_uv = 3715000, .ri_uohm = 209000 }, + { .vbat_uv = 3712000, .ri_uohm = 211000 }, + { .vbat_uv = 3709000, .ri_uohm = 210000 }, + { .vbat_uv = 3704000, .ri_uohm = 216000 }, + { .vbat_uv = 3701000, .ri_uohm = 218000 }, + { .vbat_uv = 3698000, .ri_uohm = 222000 }, + { .vbat_uv = 3694000, .ri_uohm = 218000 }, + { .vbat_uv = 3692000, .ri_uohm = 215000 }, + { .vbat_uv = 3688000, .ri_uohm = 224000 }, + { .vbat_uv = 3686000, .ri_uohm = 224000 }, + { .vbat_uv = 3683000, .ri_uohm = 228000 }, + { .vbat_uv = 3681000, .ri_uohm = 228000 }, + { .vbat_uv = 3679000, .ri_uohm = 229000 }, + { .vbat_uv = 3676000, .ri_uohm = 232000 }, + { .vbat_uv = 3675000, .ri_uohm = 229000 }, + { .vbat_uv = 3673000, .ri_uohm = 229000 }, + { .vbat_uv = 3672000, .ri_uohm = 223000 }, + { .vbat_uv = 3669000, .ri_uohm = 224000 }, + { .vbat_uv = 3666000, .ri_uohm = 224000 }, + { .vbat_uv = 3663000, .ri_uohm = 221000 }, + { .vbat_uv = 3660000, .ri_uohm = 218000 }, + { .vbat_uv = 3657000, .ri_uohm = 215000 }, + { .vbat_uv = 3654000, .ri_uohm = 212000 }, + { .vbat_uv = 3649000, .ri_uohm = 215000 }, + { .vbat_uv = 3644000, .ri_uohm = 215000 }, + { .vbat_uv = 3636000, .ri_uohm = 215000 }, + { .vbat_uv = 3631000, .ri_uohm = 206000 }, + { .vbat_uv = 3623000, .ri_uohm = 205000 }, + { .vbat_uv = 3616000, .ri_uohm = 193000 }, + { .vbat_uv = 3605000, .ri_uohm = 193000 }, + { .vbat_uv = 3600000, .ri_uohm = 198000 }, + { .vbat_uv = 3597000, .ri_uohm = 198000 }, + { .vbat_uv = 3592000, .ri_uohm = 203000 }, + { .vbat_uv = 3591000, .ri_uohm = 188000 }, + { .vbat_uv = 3587000, .ri_uohm = 188000 }, + { .vbat_uv = 3583000, .ri_uohm = 177000 }, + { .vbat_uv = 3577000, .ri_uohm = 170000 }, + { .vbat_uv = 3568000, .ri_uohm = 135000 }, + { .vbat_uv = 3552000, .ri_uohm = 54000 }, + { .vbat_uv = 3526000, .ri_uohm = 130000 }, + { .vbat_uv = 3501000, .ri_uohm = 48000 }, + { .vbat_uv = 3442000, .ri_uohm = 183000 }, + { .vbat_uv = 3326000, .ri_uohm = 372000 }, + { .vbat_uv = 3161000, .ri_uohm = 452000 }, +}; + +static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb585157lu[] = { + { .vbat_uv = 4360000, .ri_uohm = 128000 }, + { .vbat_uv = 4325000, .ri_uohm = 130000 }, + { .vbat_uv = 4316000, .ri_uohm = 148000 }, + { .vbat_uv = 4308000, .ri_uohm = 162000 }, + { .vbat_uv = 4301000, .ri_uohm = 162000 }, + { .vbat_uv = 4250000, .ri_uohm = 162000 }, + { .vbat_uv = 4230000, .ri_uohm = 164000 }, + { .vbat_uv = 4030000, .ri_uohm = 164000 }, + { .vbat_uv = 4000000, .ri_uohm = 193000 }, + { .vbat_uv = 3950000, .ri_uohm = 204000 }, + { .vbat_uv = 3850000, .ri_uohm = 210000 }, + { .vbat_uv = 3800000, .ri_uohm = 230000 }, + { .vbat_uv = 3790000, .ri_uohm = 240000 }, + { .vbat_uv = 3780000, .ri_uohm = 311000 }, + { .vbat_uv = 3760000, .ri_uohm = 420000 }, + { .vbat_uv = 3700000, .ri_uohm = 504000 }, + { .vbat_uv = 3600000, .ri_uohm = 565000 }, +}; + +/* + * Temperature to internal resistance scaling tables. + * + * "resistance" is the percentage of the resistance determined from the voltage + * so this represents the capacity ratio at different temperatures. + * + * FIXME: the proper table is missing: Samsung does not provide the necessary + * temperature compensation tables so we just state 100% for every temperature. + * If you have the datasheets, please provide these tables. + */ +static struct power_supply_resistance_temp_table samsung_temp2res[] = { + { .temp = 50, .resistance = 100 }, + { .temp = 40, .resistance = 100 }, + { .temp = 30, .resistance = 100 }, + { .temp = 20, .resistance = 100 }, + { .temp = 10, .resistance = 100 }, + { .temp = 00, .resistance = 100 }, + { .temp = -10, .resistance = 100 }, + { .temp = -20, .resistance = 100 }, +}; + +/* + * Capacity tables for different Open Circuit Voltages (OCV). + * These must be sorted by falling OCV value. + */ + +static struct power_supply_battery_ocv_table samsung_ocv_cap_eb485159lu[] = { + { .ocv = 4330000, .capacity = 100}, + { .ocv = 4320000, .capacity = 99}, + { .ocv = 4283000, .capacity = 95}, + { .ocv = 4246000, .capacity = 92}, + { .ocv = 4211000, .capacity = 89}, + { .ocv = 4167000, .capacity = 85}, + { .ocv = 4146000, .capacity = 83}, + { .ocv = 4124000, .capacity = 81}, + { .ocv = 4062000, .capacity = 75}, + { .ocv = 4013000, .capacity = 70}, + { .ocv = 3977000, .capacity = 66}, + { .ocv = 3931000, .capacity = 60}, + { .ocv = 3914000, .capacity = 58}, + { .ocv = 3901000, .capacity = 57}, + { .ocv = 3884000, .capacity = 56}, + { .ocv = 3870000, .capacity = 55}, + { .ocv = 3862000, .capacity = 54}, + { .ocv = 3854000, .capacity = 53}, + { .ocv = 3838000, .capacity = 50}, + { .ocv = 3823000, .capacity = 47}, + { .ocv = 3813000, .capacity = 45}, + { .ocv = 3807000, .capacity = 43}, + { .ocv = 3800000, .capacity = 41}, + { .ocv = 3795000, .capacity = 40}, + { .ocv = 3786000, .capacity = 37}, + { .ocv = 3783000, .capacity = 35}, + { .ocv = 3773000, .capacity = 30}, + { .ocv = 3758000, .capacity = 25}, + { .ocv = 3745000, .capacity = 22}, + { .ocv = 3738000, .capacity = 20}, + { .ocv = 3733000, .capacity = 19}, + { .ocv = 3716000, .capacity = 17}, + { .ocv = 3709000, .capacity = 16}, + { .ocv = 3698000, .capacity = 15}, + { .ocv = 3687000, .capacity = 14}, + { .ocv = 3684000, .capacity = 13}, + { .ocv = 3684000, .capacity = 12}, + { .ocv = 3678000, .capacity = 10}, + { .ocv = 3671000, .capacity = 9}, + { .ocv = 3665000, .capacity = 8}, + { .ocv = 3651000, .capacity = 7}, + { .ocv = 3634000, .capacity = 6}, + { .ocv = 3601000, .capacity = 5}, + { .ocv = 3564000, .capacity = 4}, + { .ocv = 3516000, .capacity = 3}, + { .ocv = 3456000, .capacity = 2}, + { .ocv = 3381000, .capacity = 1}, + { .ocv = 3300000, .capacity = 0}, +}; + +/* Same capacity table is used by eb-l1m7flu, eb425161la, eb425161lu */ +static struct power_supply_battery_ocv_table samsung_ocv_cap_1500mah[] = { + { .ocv = 4328000, .capacity = 100}, + { .ocv = 4299000, .capacity = 99}, + { .ocv = 4281000, .capacity = 98}, + { .ocv = 4241000, .capacity = 95}, + { .ocv = 4183000, .capacity = 90}, + { .ocv = 4150000, .capacity = 87}, + { .ocv = 4116000, .capacity = 84}, + { .ocv = 4077000, .capacity = 80}, + { .ocv = 4068000, .capacity = 79}, + { .ocv = 4058000, .capacity = 77}, + { .ocv = 4026000, .capacity = 75}, + { .ocv = 3987000, .capacity = 72}, + { .ocv = 3974000, .capacity = 69}, + { .ocv = 3953000, .capacity = 66}, + { .ocv = 3933000, .capacity = 63}, + { .ocv = 3911000, .capacity = 60}, + { .ocv = 3900000, .capacity = 58}, + { .ocv = 3873000, .capacity = 55}, + { .ocv = 3842000, .capacity = 52}, + { .ocv = 3829000, .capacity = 50}, + { .ocv = 3810000, .capacity = 45}, + { .ocv = 3793000, .capacity = 40}, + { .ocv = 3783000, .capacity = 35}, + { .ocv = 3776000, .capacity = 30}, + { .ocv = 3762000, .capacity = 25}, + { .ocv = 3746000, .capacity = 20}, + { .ocv = 3739000, .capacity = 18}, + { .ocv = 3715000, .capacity = 15}, + { .ocv = 3700000, .capacity = 12}, + { .ocv = 3690000, .capacity = 10}, + { .ocv = 3680000, .capacity = 9}, + { .ocv = 3670000, .capacity = 7}, + { .ocv = 3656000, .capacity = 5}, + { .ocv = 3634000, .capacity = 4}, + { .ocv = 3614000, .capacity = 3}, + { .ocv = 3551000, .capacity = 2}, + { .ocv = 3458000, .capacity = 1}, + { .ocv = 3300000, .capacity = 0}, +}; + +static struct power_supply_battery_ocv_table samsung_ocv_cap_eb535151vu[] = { + { .ocv = 4178000, .capacity = 100}, + { .ocv = 4148000, .capacity = 99}, + { .ocv = 4105000, .capacity = 95}, + { .ocv = 4078000, .capacity = 92}, + { .ocv = 4057000, .capacity = 89}, + { .ocv = 4013000, .capacity = 85}, + { .ocv = 3988000, .capacity = 82}, + { .ocv = 3962000, .capacity = 77}, + { .ocv = 3920000, .capacity = 70}, + { .ocv = 3891000, .capacity = 65}, + { .ocv = 3874000, .capacity = 62}, + { .ocv = 3839000, .capacity = 59}, + { .ocv = 3816000, .capacity = 55}, + { .ocv = 3798000, .capacity = 50}, + { .ocv = 3778000, .capacity = 40}, + { .ocv = 3764000, .capacity = 30}, + { .ocv = 3743000, .capacity = 25}, + { .ocv = 3711000, .capacity = 20}, + { .ocv = 3691000, .capacity = 18}, + { .ocv = 3685000, .capacity = 15}, + { .ocv = 3680000, .capacity = 12}, + { .ocv = 3662000, .capacity = 10}, + { .ocv = 3638000, .capacity = 9}, + { .ocv = 3593000, .capacity = 7}, + { .ocv = 3566000, .capacity = 6}, + { .ocv = 3497000, .capacity = 4}, + { .ocv = 3405000, .capacity = 2}, + { .ocv = 3352000, .capacity = 1}, + { .ocv = 3300000, .capacity = 0}, +}; + +static struct power_supply_battery_ocv_table samsung_ocv_cap_eb585157lu[] = { + { .ocv = 4320000, .capacity = 100}, + { .ocv = 4296000, .capacity = 99}, + { .ocv = 4283000, .capacity = 98}, + { .ocv = 4245000, .capacity = 95}, + { .ocv = 4185000, .capacity = 90}, + { .ocv = 4152000, .capacity = 87}, + { .ocv = 4119000, .capacity = 84}, + { .ocv = 4077000, .capacity = 80}, + { .ocv = 4057000, .capacity = 78}, + { .ocv = 4048000, .capacity = 77}, + { .ocv = 4020000, .capacity = 74}, + { .ocv = 4003000, .capacity = 72}, + { .ocv = 3978000, .capacity = 69}, + { .ocv = 3955000, .capacity = 66}, + { .ocv = 3934000, .capacity = 63}, + { .ocv = 3912000, .capacity = 60}, + { .ocv = 3894000, .capacity = 58}, + { .ocv = 3860000, .capacity = 55}, + { .ocv = 3837000, .capacity = 52}, + { .ocv = 3827000, .capacity = 50}, + { .ocv = 3806000, .capacity = 45}, + { .ocv = 3791000, .capacity = 40}, + { .ocv = 3779000, .capacity = 35}, + { .ocv = 3770000, .capacity = 30}, + { .ocv = 3758000, .capacity = 25}, + { .ocv = 3739000, .capacity = 20}, + { .ocv = 3730000, .capacity = 18}, + { .ocv = 3706000, .capacity = 15}, + { .ocv = 3684000, .capacity = 13}, + { .ocv = 3675000, .capacity = 10}, + { .ocv = 3673000, .capacity = 9}, + { .ocv = 3665000, .capacity = 7}, + { .ocv = 3649000, .capacity = 5}, + { .ocv = 3628000, .capacity = 4}, + { .ocv = 3585000, .capacity = 3}, + { .ocv = 3525000, .capacity = 2}, + { .ocv = 3441000, .capacity = 1}, + { .ocv = 3300000, .capacity = 0}, +}; + +static struct power_supply_maintenance_charge_table samsung_maint_charge_table[] = { + { + /* Maintenance charging phase A, 60 hours */ + .charge_current_max_ua = 600000, + .charge_voltage_max_uv = 4150000, + .charge_safety_timer_minutes = 60*60, + }, + { + /* Maintenance charging phase B, 200 hours */ + .charge_current_max_ua = 600000, + .charge_voltage_max_uv = 4100000, + .charge_safety_timer_minutes = 200*60, + } +}; + +static struct samsung_sdi_battery samsung_sdi_batteries[] = { + { + /* + * Used in Samsung GT-I8190 "Golden" + * Data from vendor boardfile board-golden-[bm|battery].c + */ + .compatible = "samsung,eb-l1m7flu", + .name = "EB-L1M7FLU", + .info = { + .charge_full_design_uah = 1500000, + .technology = POWER_SUPPLY_TECHNOLOGY_LION, + .factory_internal_resistance_uohm = 100000, + .factory_internal_resistance_charging_uohm = 200000, + /* If you have data on this fix the min_design_uv */ + .voltage_min_design_uv = 3320000, + .voltage_max_design_uv = 4340000, + .overvoltage_limit_uv = 4500000, + .constant_charge_current_max_ua = 900000, + .constant_charge_voltage_max_uv = 4320000, + .charge_term_current_ua = 200000, + .charge_restart_voltage_uv = 4300000, + .maintenance_charge = samsung_maint_charge_table, + .maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table), + .alert_low_temp_charge_current_ua = 300000, + .alert_low_temp_charge_voltage_uv = 4000000, + .alert_high_temp_charge_current_ua = 300000, + .alert_high_temp_charge_voltage_uv = 4000000, + .temp_min = -50, + .temp_alert_min = 0, + .temp_alert_max = 40, + .temp_max = 60, + .resist_table = samsung_temp2res, + .resist_table_size = ARRAY_SIZE(samsung_temp2res), + /* If you have tables for more temperatures, add them */ + .ocv_temp[0] = 25, + .ocv_table[0] = samsung_ocv_cap_1500mah, + .ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1500mah), + .vbat2ri_discharging = samsung_vbat2res_discharging_eb_l1m7flu, + .vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb_l1m7flu), + .vbat2ri_charging = samsung_vbat2res_charging_eb_l1m7flu, + .vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb_l1m7flu), + .bti_resistance_ohm = 2400, + .bti_resistance_tolerance = 40, + }, + }, + { + /* + * Used in Samsung SGH-T599 "Codina TMO" and SGH-I407 "Kyle" + * Data from vendor boardfile board-kyle-[bm|battery].c + */ + .compatible = "samsung,eb425161la", + .name = "EB425161LA", + .info = { + .charge_full_design_uah = 1500000, + .technology = POWER_SUPPLY_TECHNOLOGY_LION, + .factory_internal_resistance_uohm = 136000, + .factory_internal_resistance_charging_uohm = 200000, + /* If you have data on this fix the min_design_uv */ + .voltage_min_design_uv = 3320000, + .voltage_max_design_uv = 4340000, + .overvoltage_limit_uv = 4500000, + .constant_charge_current_max_ua = 900000, + .constant_charge_voltage_max_uv = 4320000, + .charge_term_current_ua = 200000, + .charge_restart_voltage_uv = 4270000, + .maintenance_charge = samsung_maint_charge_table, + .maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table), + .alert_low_temp_charge_current_ua = 300000, + .alert_low_temp_charge_voltage_uv = 4000000, + .alert_high_temp_charge_current_ua = 300000, + .alert_high_temp_charge_voltage_uv = 4000000, + .temp_min = -30, + .temp_alert_min = 0, + .temp_alert_max = 40, + .temp_max = 47, + .resist_table = samsung_temp2res, + .resist_table_size = ARRAY_SIZE(samsung_temp2res), + /* If you have tables for more temperatures, add them */ + .ocv_temp[0] = 25, + .ocv_table[0] = samsung_ocv_cap_1500mah, + .ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1500mah), + .vbat2ri_discharging = samsung_vbat2res_discharging_eb425161la, + .vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb425161la), + .vbat2ri_charging = samsung_vbat2res_charging_eb425161la, + .vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb425161la), + .bti_resistance_ohm = 2400, + .bti_resistance_tolerance = 40, + }, + }, + { + /* + * Used in Samsung GT-I8160 "Codina" + * Data from vendor boardfile board-codina-[bm|battery].c + */ + .compatible = "samsung,eb425161lu", + .name = "EB425161LU", + .info = { + .charge_full_design_uah = 1500000, + .technology = POWER_SUPPLY_TECHNOLOGY_LION, + .factory_internal_resistance_uohm = 100000, + .factory_internal_resistance_charging_uohm = 200000, + /* If you have data on this fix the min_design_uv */ + .voltage_min_design_uv = 3320000, + .voltage_max_design_uv = 4350000, + .overvoltage_limit_uv = 4500000, + .constant_charge_current_max_ua = 900000, + .constant_charge_voltage_max_uv = 4340000, + .charge_term_current_ua = 200000, + .charge_restart_voltage_uv = 4280000, + .maintenance_charge = samsung_maint_charge_table, + .maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table), + .alert_low_temp_charge_current_ua = 300000, + .alert_low_temp_charge_voltage_uv = 4000000, + .alert_high_temp_charge_current_ua = 300000, + .alert_high_temp_charge_voltage_uv = 4000000, + .temp_min = -50, + .temp_alert_min = 0, + .temp_alert_max = 43, + .temp_max = 49, + .resist_table = samsung_temp2res, + .resist_table_size = ARRAY_SIZE(samsung_temp2res), + /* If you have tables for more temperatures, add them */ + .ocv_temp[0] = 25, + .ocv_table[0] = samsung_ocv_cap_1500mah, + .ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_1500mah), + .vbat2ri_discharging = samsung_vbat2res_discharging_eb425161lu, + .vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb425161lu), + .vbat2ri_charging = samsung_vbat2res_charging_eb425161lu, + .vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb425161lu), + .bti_resistance_ohm = 2400, + .bti_resistance_tolerance = 40, + }, + }, + { + /* + * Used in Samsung GT-S7710 "Skomer" + * Data from vendor boardfile board-skomer-[bm|battery].c + */ + .compatible = "samsung,eb485159lu", + .name = "EB485159LU", + .info = { + .charge_full_design_uah = 1700000, + .technology = POWER_SUPPLY_TECHNOLOGY_LION, + .factory_internal_resistance_uohm = 100000, + .factory_internal_resistance_charging_uohm = 200000, + .voltage_min_design_uv = 3320000, + .voltage_max_design_uv = 4350000, + .overvoltage_limit_uv = 4500000, + .constant_charge_current_max_ua = 900000, + .constant_charge_voltage_max_uv = 4340000, + .charge_term_current_ua = 200000, + .charge_restart_voltage_uv = 4300000, + .maintenance_charge = samsung_maint_charge_table, + .maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table), + .alert_low_temp_charge_current_ua = 300000, + .alert_low_temp_charge_voltage_uv = 4000000, + .alert_high_temp_charge_current_ua = 300000, + .alert_high_temp_charge_voltage_uv = 4000000, + .temp_min = -50, + .temp_alert_min = 0, + .temp_alert_max = 40, + .temp_max = 60, + .resist_table = samsung_temp2res, + .resist_table_size = ARRAY_SIZE(samsung_temp2res), + /* If you have tables for more temperatures, add them */ + .ocv_temp[0] = 25, + .ocv_table[0] = samsung_ocv_cap_eb485159lu, + .ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_eb485159lu), + /* CHECKME: vendor uses the 1500 mAh table, check against datasheet */ + .vbat2ri_discharging = samsung_vbat2res_discharging_eb485159lu, + .vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb485159lu), + .vbat2ri_charging = samsung_vbat2res_charging_eb485159lu, + .vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb485159lu), + .bti_resistance_ohm = 2400, + .bti_resistance_tolerance = 40, + }, + }, + { + /* + * Used in Samsung GT-I9070 "Janice" + * Data from vendor boardfile board-janice-bm.c + */ + .compatible = "samsung,eb535151vu", + .name = "EB535151VU", + .info = { + .charge_full_design_uah = 1500000, + .technology = POWER_SUPPLY_TECHNOLOGY_LION, + .factory_internal_resistance_uohm = 100000, + .factory_internal_resistance_charging_uohm = 200000, + /* If you have data on this fix the min_design_uv */ + .voltage_min_design_uv = 3300000, + .voltage_max_design_uv = 4180000, + .overvoltage_limit_uv = 4500000, + .constant_charge_current_max_ua = 900000, + .constant_charge_voltage_max_uv = 4200000, + .charge_term_current_ua = 200000, + .maintenance_charge = samsung_maint_charge_table, + .maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table), + .alert_low_temp_charge_current_ua = 300000, + .alert_low_temp_charge_voltage_uv = 4000000, + .alert_high_temp_charge_current_ua = 300000, + .alert_high_temp_charge_voltage_uv = 4000000, + .temp_min = -5, + .temp_alert_min = 0, + .temp_alert_max = 40, + .temp_max = 60, + .resist_table = samsung_temp2res, + .resist_table_size = ARRAY_SIZE(samsung_temp2res), + /* If you have tables for more temperatures, add them */ + .ocv_temp[0] = 25, + .ocv_table[0] = samsung_ocv_cap_eb535151vu, + .ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_eb535151vu), + .vbat2ri_discharging = samsung_vbat2res_discharging_eb535151vu, + .vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb535151vu), + .vbat2ri_charging = samsung_vbat2res_charging_eb535151vu, + .vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb535151vu), + .bti_resistance_ohm = 1500, + .bti_resistance_tolerance = 40, + }, + }, + { + /* + * Used in Samsung GT-I8530 "Gavini" + * Data from vendor boardfile board-gavini-bm.c + */ + .compatible = "samsung,eb585157lu", + .name = "EB585157LU", + .info = { + .charge_full_design_uah = 2000000, + .technology = POWER_SUPPLY_TECHNOLOGY_LION, + .factory_internal_resistance_uohm = 105000, + .factory_internal_resistance_charging_uohm = 160000, + /* If you have data on this fix the min_design_uv */ + .voltage_min_design_uv = 3300000, + .voltage_max_design_uv = 4320000, + .overvoltage_limit_uv = 4500000, + .constant_charge_current_max_ua = 1500000, + .constant_charge_voltage_max_uv = 4350000, + .charge_term_current_ua = 120000, + .maintenance_charge = samsung_maint_charge_table, + .maintenance_charge_size = ARRAY_SIZE(samsung_maint_charge_table), + .alert_low_temp_charge_current_ua = 300000, + .alert_low_temp_charge_voltage_uv = 4000000, + .alert_high_temp_charge_current_ua = 300000, + .alert_high_temp_charge_voltage_uv = 4000000, + .temp_min = -5, + .temp_alert_min = 0, + .temp_alert_max = 40, + .temp_max = 60, + .resist_table = samsung_temp2res, + .resist_table_size = ARRAY_SIZE(samsung_temp2res), + /* If you have tables for more temperatures, add them */ + .ocv_temp[0] = 25, + .ocv_table[0] = samsung_ocv_cap_eb585157lu, + .ocv_table_size[0] = ARRAY_SIZE(samsung_ocv_cap_eb585157lu), + .vbat2ri_discharging = samsung_vbat2res_discharging_eb585157lu, + .vbat2ri_discharging_size = ARRAY_SIZE(samsung_vbat2res_discharging_eb585157lu), + .vbat2ri_charging = samsung_vbat2res_charging_eb585157lu, + .vbat2ri_charging_size = ARRAY_SIZE(samsung_vbat2res_charging_eb585157lu), + .bti_resistance_ohm = 2400, + .bti_resistance_tolerance = 40, + }, + }, +}; + +int samsung_sdi_battery_get_info(struct device *dev, + const char *compatible, + struct power_supply_battery_info **info) +{ + struct samsung_sdi_battery *batt; + int i; + + for (i = 0; i < ARRAY_SIZE(samsung_sdi_batteries); i++) { + batt = &samsung_sdi_batteries[i]; + if (!strcmp(compatible, batt->compatible)) + break; + } + + if (i == ARRAY_SIZE(samsung_sdi_batteries)) + return -ENODEV; + + *info = &batt->info; + dev_info(dev, "Samsung SDI %s battery %d mAh\n", + batt->name, batt->info.charge_full_design_uah / 1000); + + return 0; +} +EXPORT_SYMBOL_GPL(samsung_sdi_battery_get_info); diff --git a/drivers/power/supply/samsung-sdi-battery.h b/drivers/power/supply/samsung-sdi-battery.h new file mode 100644 index 000000000000..365ab6e85b26 --- /dev/null +++ b/drivers/power/supply/samsung-sdi-battery.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG_SDI) +extern int samsung_sdi_battery_get_info(struct device *dev, + const char *compatible, + struct power_supply_battery_info **info); +#else +static inline int samsung_sdi_battery_get_info(struct device *dev, + const char *compatible, + struct power_supply_battery_info **info) +{ + return -ENODEV; +} +#endif From b0b14b5ba11bec56fad344a4a0b2e16449cc8b94 Mon Sep 17 00:00:00 2001 From: Jiasheng Jiang Date: Fri, 4 Mar 2022 09:57:51 +0800 Subject: [PATCH 82/94] power: supply: wm8350-power: Handle error for wm8350_register_irq As the potential failure of the wm8350_register_irq(), it should be better to check it and return error if fails. Also, use 'free_' in order to avoid same code. Fixes: 14431aa0c5a4 ("power_supply: Add support for WM8350 PMU") Signed-off-by: Jiasheng Jiang Acked-by: Charles Keepax Signed-off-by: Sebastian Reichel --- drivers/power/supply/wm8350_power.c | 96 ++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 14 deletions(-) diff --git a/drivers/power/supply/wm8350_power.c b/drivers/power/supply/wm8350_power.c index e05cee457471..9c46c48dccb1 100644 --- a/drivers/power/supply/wm8350_power.c +++ b/drivers/power/supply/wm8350_power.c @@ -408,44 +408,112 @@ static const struct power_supply_desc wm8350_usb_desc = { * Initialisation *********************************************************************/ -static void wm8350_init_charger(struct wm8350 *wm8350) +static int wm8350_init_charger(struct wm8350 *wm8350) { + int ret; + /* register our interest in charger events */ - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350_charger_handler, 0, "Battery hot", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, + if (ret) + goto err; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350_charger_handler, 0, "Battery cold", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, + if (ret) + goto free_chg_bat_hot; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350_charger_handler, 0, "Battery fail", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO, + if (ret) + goto free_chg_bat_cold; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350_charger_handler, 0, "Charger timeout", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END, + if (ret) + goto free_chg_bat_fail; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END, wm8350_charger_handler, 0, "Charge end", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START, + if (ret) + goto free_chg_to; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START, wm8350_charger_handler, 0, "Charge start", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, + if (ret) + goto free_chg_end; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, wm8350_charger_handler, 0, "Fast charge ready", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, + if (ret) + goto free_chg_start; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350_charger_handler, 0, "Battery <3.9V", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, + if (ret) + goto free_chg_fast_rdy; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350_charger_handler, 0, "Battery <3.1V", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, + if (ret) + goto free_chg_vbatt_lt_3p9; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350_charger_handler, 0, "Battery <2.85V", wm8350); + if (ret) + goto free_chg_vbatt_lt_3p1; /* and supply change events */ - wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB, + ret = wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350_charger_handler, 0, "USB", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, + if (ret) + goto free_chg_vbatt_lt_2p85; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350_charger_handler, 0, "Wall", wm8350); - wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, + if (ret) + goto free_ext_usb_fb; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350_charger_handler, 0, "Battery", wm8350); + if (ret) + goto free_ext_wall_fb; + + return 0; + +free_ext_wall_fb: + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350); +free_ext_usb_fb: + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350); +free_chg_vbatt_lt_2p85: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350); +free_chg_vbatt_lt_3p1: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350); +free_chg_vbatt_lt_3p9: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350); +free_chg_fast_rdy: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, wm8350); +free_chg_start: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350); +free_chg_end: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350); +free_chg_to: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350); +free_chg_bat_fail: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350); +free_chg_bat_cold: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350); +free_chg_bat_hot: + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350); +err: + return ret; } static void free_charger_irq(struct wm8350 *wm8350) From 6dee930f6f6776d1e5a7edf542c6863b47d9f078 Mon Sep 17 00:00:00 2001 From: Jiasheng Jiang Date: Thu, 3 Mar 2022 17:43:22 +0800 Subject: [PATCH 83/94] power: supply: wm8350-power: Add missing free in free_charger_irq In free_charger_irq(), there is no free for 'WM8350_IRQ_CHG_FAST_RDY'. Therefore, it should be better to add it in order to avoid the memory leak. Fixes: 14431aa0c5a4 ("power_supply: Add support for WM8350 PMU") Signed-off-by: Jiasheng Jiang Acked-by: Charles Keepax Signed-off-by: Sebastian Reichel --- drivers/power/supply/wm8350_power.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/wm8350_power.c b/drivers/power/supply/wm8350_power.c index 9c46c48dccb1..908cfd45d262 100644 --- a/drivers/power/supply/wm8350_power.c +++ b/drivers/power/supply/wm8350_power.c @@ -524,6 +524,7 @@ static void free_charger_irq(struct wm8350 *wm8350) wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350); wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350); wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, wm8350); wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350); wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350); wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350); From 99dcda8d1f6afa824cdb8ffc3ffa15ef530f6d83 Mon Sep 17 00:00:00 2001 From: Yihao Han Date: Thu, 3 Mar 2022 04:27:19 -0800 Subject: [PATCH 84/94] power: supply: axp20x_ac_power: fix platform_get_irq.cocci warning Remove dev_err() messages after platform_get_irq*() failures. platform_get_irq() already prints an error. Generated by: scripts/coccinelle/api/platform_get_irq.cocci Signed-off-by: Yihao Han Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_ac_power.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c index ac360016b08a..57e50208d537 100644 --- a/drivers/power/supply/axp20x_ac_power.c +++ b/drivers/power/supply/axp20x_ac_power.c @@ -377,11 +377,9 @@ static int axp20x_ac_power_probe(struct platform_device *pdev) /* Request irqs after registering, as irqs may trigger immediately */ for (i = 0; i < axp_data->num_irq_names; i++) { irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]); - if (irq < 0) { - dev_err(&pdev->dev, "No IRQ for %s: %d\n", - axp_data->irq_names[i], irq); + if (irq < 0) return irq; - } + power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq); ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], axp20x_ac_power_irq, 0, From 4f084810ec26f3b7f58294cfa4e6028b1827f5fe Mon Sep 17 00:00:00 2001 From: Yihao Han Date: Tue, 1 Mar 2022 18:34:51 -0800 Subject: [PATCH 85/94] power: supply: axp20x_usb_power: fix platform_get_irq.cocci warnings Remove dev_err() messages after platform_get_irq*() failures. platform_get_irq() already prints an error. Generated by: scripts/coccinelle/api/platform_get_irq.cocci Signed-off-by: Yihao Han Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_usb_power.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index a1d110f7ddce..a1e6d1d44808 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -637,11 +637,9 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) /* Request irqs after registering, as irqs may trigger immediately */ for (i = 0; i < axp_data->num_irq_names; i++) { irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]); - if (irq < 0) { - dev_err(&pdev->dev, "No IRQ for %s: %d\n", - axp_data->irq_names[i], irq); + if (irq < 0) return irq; - } + power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq); ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], axp20x_usb_power_irq, 0, From 8dc355748a7cb4b374d1296bdd68f66c18aced9e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 1 Mar 2022 13:42:53 +0100 Subject: [PATCH 86/94] dt-bindings: power: supply: ab8500_fg: Add line impedance To improve the inner resistance measurement of the battery we need to account for the line impedance of the connector to the battery. Cc: devicetree@vger.kernel.org Signed-off-by: Linus Walleij Acked-by: Rob Herring Signed-off-by: Sebastian Reichel --- .../bindings/power/supply/stericsson,ab8500-fg.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-fg.yaml b/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-fg.yaml index 54ac42a9d354..2ce408a7c0ae 100644 --- a/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-fg.yaml +++ b/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-fg.yaml @@ -25,6 +25,11 @@ properties: $ref: /schemas/types.yaml#/definitions/phandle deprecated: true + line-impedance-micro-ohms: + description: The line impedance between the battery and the + AB8500 inputs, to compensate for this when determining internal + resistance. + interrupts: maxItems: 5 From 1ae4a91c923220b96c81e4a116595f4060f65c7f Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 1 Mar 2022 13:42:54 +0100 Subject: [PATCH 87/94] power: supply: ab8500_fg: Account for line impedance We add the line impedance to the inner resistance determined from the battery voltage or other means to improve the capacity estimations. Signed-off-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 1abe10c7ff2a..3ae8086907de 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -213,6 +213,7 @@ struct ab8500_fg { int init_cnt; int low_bat_cnt; int nbr_cceoc_irq_cnt; + u32 line_impedance_uohm; bool recovery_needed; bool high_curr_mode; bool init_capacity; @@ -910,6 +911,9 @@ static int ab8500_fg_battery_resistance(struct ab8500_fg *di, int vbat_uncomp_uv resistance = bi->factory_internal_resistance_uohm / 1000; } + /* Compensate for line impedance */ + resistance += (di->line_impedance_uohm / 1000); + dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d" " fg resistance %d, total: %d (mOhm)\n", __func__, di->bat_temp, resistance, di->bm->fg_res / 10, @@ -3098,6 +3102,11 @@ static int ab8500_fg_probe(struct platform_device *pdev) return ret; } + if (!of_property_read_u32(dev->of_node, "line-impedance-micro-ohms", + &di->line_impedance_uohm)) + dev_info(dev, "line impedance: %u uOhm\n", + di->line_impedance_uohm); + psy_cfg.supplied_to = supply_interface; psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface); psy_cfg.drv_data = di; From 8652b62ee6f157ceba66dc3fe3a4afbac3b0a0f5 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 2 Mar 2022 13:08:32 +0000 Subject: [PATCH 88/94] power: supply: ab8500: fix a handful of spelling mistakes There are a few spelling mistakes in comments and in a dev_err error message. Fix them. Signed-off-by: Colin Ian King Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_chargalg.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index c9c7f7028af6..8842e51b8b1c 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -287,7 +287,7 @@ ab8500_chargalg_safety_timer_expired(struct hrtimer *timer) * the maintenance timer * @timer: pointer to the timer structure * - * This function gets called when the maintenence timer + * This function gets called when the maintenance timer * expires */ static enum hrtimer_restart @@ -431,7 +431,7 @@ static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di) /** * ab8500_chargalg_start_maintenance_timer() - Start charging maintenance timer * @di: pointer to the ab8500_chargalg structure - * @duration: duration of ther maintenance timer in minutes + * @duration: duration of the maintenance timer in minutes * * The maintenance timer is used to maintain the charge in the battery once * the battery is considered full. These timers are chosen to match the @@ -1271,7 +1271,7 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) STATE_SAFETY_TIMER_EXPIRED_INIT); } /* - * Check if any interrupts has occured + * Check if any interrupts has occurred * that will prevent us from charging */ @@ -1525,7 +1525,7 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) bi->alert_high_temp_charge_voltage_uv, bi->alert_high_temp_charge_current_ua); } else { - dev_err(di->dev, "neither low or high temp event occured\n"); + dev_err(di->dev, "neither low or high temp event occurred\n"); ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); break; } From e6824196f81f9a159a9473aaa5b0ed782d905745 Mon Sep 17 00:00:00 2001 From: Yang Li Date: Tue, 1 Mar 2022 16:16:19 +0800 Subject: [PATCH 89/94] power: supply: da9150-fg: Remove unnecessary print function dev_err() The print function dev_err() is redundant because platform_get_irq_byname() already prints an error. Eliminate the follow coccicheck warning: ./drivers/power/supply/da9150-fg.c:524:2-9: line 524 is redundant because platform_get_irq() already prints an error Reported-by: Abaci Robot Signed-off-by: Yang Li Reviewed-by: Adam Thomson Signed-off-by: Sebastian Reichel --- drivers/power/supply/da9150-fg.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/da9150-fg.c b/drivers/power/supply/da9150-fg.c index e63fa62d1943..8c5e2c49d6c1 100644 --- a/drivers/power/supply/da9150-fg.c +++ b/drivers/power/supply/da9150-fg.c @@ -520,10 +520,8 @@ static int da9150_fg_probe(struct platform_device *pdev) /* Register IRQ */ irq = platform_get_irq_byname(pdev, "FG"); - if (irq < 0) { - dev_err(dev, "Failed to get IRQ FG: %d\n", irq); + if (irq < 0) return irq; - } ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq, IRQF_ONESHOT, "FG", fg); From c17f2a53c3f4936d46db0e811366ef030783e9f3 Mon Sep 17 00:00:00 2001 From: "Souptick Joarder (HPE)" Date: Tue, 1 Mar 2022 10:58:50 +0530 Subject: [PATCH 90/94] power: supply: ab8500: Remove unused variable Kernel test robot reported below warning -> drivers/power/supply/ab8500_chargalg.c:790:13: warning: variable 'delta_i_ua' set but not used [-Wunused-but-set-variable] Remove unused variable delta_i_ua. Reported-by: kernel test robot Signed-off-by: Souptick Joarder (HPE) Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_chargalg.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index 8842e51b8b1c..cd7de1127a1e 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -800,13 +800,10 @@ static void init_maxim_chg_curr(struct ab8500_chargalg *di) */ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) { - int delta_i_ua; if (!di->bm->maxi->ena_maxi) return MAXIM_RET_NOACTION; - delta_i_ua = di->ccm.original_iset_ua - di->batt_data.inst_curr_ua; - if (di->events.vbus_collapsed) { dev_dbg(di->dev, "Charger voltage has collapsed %d\n", di->ccm.wait_cnt); From 14ea3e701c9599374bab4dc78002bd4a2757be23 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Sun, 13 Feb 2022 22:22:57 -0600 Subject: [PATCH 91/94] dt-bindings: vendor-prefixes: Add Injoinic Add prefix for Injoinic Technology Corp. (http://www.injoinic.com/) Acked-by: Rob Herring Signed-off-by: Samuel Holland Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 294093d45a23..3a1a7b2fbe52 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -561,6 +561,8 @@ patternProperties: description: InfoVision Optoelectronics Kunshan Co. Ltd. "^ingenic,.*": description: Ingenic Semiconductor + "^injoinic,.*": + description: Injoinic Technology Corp. "^innolux,.*": description: Innolux Corporation "^inside-secure,.*": From 78eb753ea1d76356245ef8c008be70fbbac3f19d Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Sun, 13 Feb 2022 22:22:58 -0600 Subject: [PATCH 92/94] dt-bindings: trivial-devices: Add Injoinic power bank ICs Some Injoinic power bank ICs feature an I2C interface which allows monitoring and controlling the battery charger and boost converter. Acked-by: Rob Herring Signed-off-by: Samuel Holland Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/trivial-devices.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 091792ba993e..a045d627a297 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -141,6 +141,14 @@ properties: - infineon,xdpe12254 # Infineon Multi-phase Digital VR Controller xdpe12284 - infineon,xdpe12284 + # Injoinic IP5108 2.0A Power Bank IC with I2C + - injoinic,ip5108 + # Injoinic IP5109 2.1A Power Bank IC with I2C + - injoinic,ip5109 + # Injoinic IP5207 1.2A Power Bank IC with I2C + - injoinic,ip5207 + # Injoinic IP5209 2.4A Power Bank IC with I2C + - injoinic,ip5209 # Inspur Power System power supply unit version 1 - inspur,ipsps1 # Intersil ISL29028 Ambient Light and Proximity Sensor From 75853406fa27961044e7dc03c7dc8544477e81f5 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Sun, 13 Feb 2022 22:22:59 -0600 Subject: [PATCH 93/94] power: supply: Add a driver for Injoinic power bank ICs This driver supports several chip variants which all share the same I2C register interface. Since the chip will turn off and become inaccessible under conditions outside of software control (e.g. upon button press or input voltage removal), some special handling is needed to delay the initialization of the IC until it is accessible. Signed-off-by: Samuel Holland Signed-off-by: Sebastian Reichel --- MAINTAINERS | 5 + drivers/power/supply/Kconfig | 8 + drivers/power/supply/Makefile | 1 + drivers/power/supply/ip5xxx_power.c | 638 ++++++++++++++++++++++++++++ 4 files changed, 652 insertions(+) create mode 100644 drivers/power/supply/ip5xxx_power.c diff --git a/MAINTAINERS b/MAINTAINERS index ea3e6c914384..beaf6ac78524 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9461,6 +9461,11 @@ F: include/linux/mfd/ingenic-tcu.h F: sound/soc/codecs/jz47* F: sound/soc/jz4740/ +INJOINIC IP5xxx POWER BANK IC DRIVER +M: Samuel Holland +S: Maintained +F: drivers/power/supply/ip5xxx_power.c + INOTIFY M: Jan Kara R: Amir Goldstein diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index fa128b9f6e63..1aa8323ad9f6 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -51,6 +51,14 @@ config GENERIC_ADC_BATTERY Say Y here to enable support for the generic battery driver which uses IIO framework to read adc. +config IP5XXX_POWER + tristate "Injoinic IP5xxx power bank IC driver" + depends on I2C + select REGMAP_I2C + help + Say Y to include support for Injoinic IP5xxx power bank ICs, + which include a battery charger and a boost converter. + config MAX8925_POWER tristate "MAX8925 battery charger support" depends on MFD_MAX8925 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index b87eb8d87bac..7f02f36aea55 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o obj-$(CONFIG_PDA_POWER) += pda_power.o obj-$(CONFIG_APM_POWER) += apm_power.o obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o +obj-$(CONFIG_IP5XXX_POWER) += ip5xxx_power.o obj-$(CONFIG_MAX8925_POWER) += max8925_power.o obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o obj-$(CONFIG_WM831X_POWER) += wm831x_power.o diff --git a/drivers/power/supply/ip5xxx_power.c b/drivers/power/supply/ip5xxx_power.c new file mode 100644 index 000000000000..218e8e689a3f --- /dev/null +++ b/drivers/power/supply/ip5xxx_power.c @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2021 Samuel Holland + +#include +#include +#include +#include + +#define IP5XXX_SYS_CTL0 0x01 +#define IP5XXX_SYS_CTL0_WLED_DET_EN BIT(4) +#define IP5XXX_SYS_CTL0_WLED_EN BIT(3) +#define IP5XXX_SYS_CTL0_BOOST_EN BIT(2) +#define IP5XXX_SYS_CTL0_CHARGER_EN BIT(1) +#define IP5XXX_SYS_CTL1 0x02 +#define IP5XXX_SYS_CTL1_LIGHT_SHDN_EN BIT(1) +#define IP5XXX_SYS_CTL1_LOAD_PWRUP_EN BIT(0) +#define IP5XXX_SYS_CTL2 0x0c +#define IP5XXX_SYS_CTL2_LIGHT_SHDN_TH GENMASK(7, 3) +#define IP5XXX_SYS_CTL3 0x03 +#define IP5XXX_SYS_CTL3_LONG_PRESS_TIME_SEL GENMASK(7, 6) +#define IP5XXX_SYS_CTL3_BTN_SHDN_EN BIT(5) +#define IP5XXX_SYS_CTL4 0x04 +#define IP5XXX_SYS_CTL4_SHDN_TIME_SEL GENMASK(7, 6) +#define IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN BIT(5) +#define IP5XXX_SYS_CTL5 0x07 +#define IP5XXX_SYS_CTL5_NTC_DIS BIT(6) +#define IP5XXX_SYS_CTL5_WLED_MODE_SEL BIT(1) +#define IP5XXX_SYS_CTL5_BTN_SHDN_SEL BIT(0) +#define IP5XXX_CHG_CTL1 0x22 +#define IP5XXX_CHG_CTL1_BOOST_UVP_SEL GENMASK(3, 2) +#define IP5XXX_CHG_CTL2 0x24 +#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL GENMASK(6, 5) +#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V (0x0 << 5) +#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V (0x1 << 5) +#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V (0x2 << 5) +#define IP5XXX_CHG_CTL2_CONST_VOLT_SEL GENMASK(2, 1) +#define IP5XXX_CHG_CTL4 0x26 +#define IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN BIT(6) +#define IP5XXX_CHG_CTL4A 0x25 +#define IP5XXX_CHG_CTL4A_CONST_CUR_SEL GENMASK(4, 0) +#define IP5XXX_MFP_CTL0 0x51 +#define IP5XXX_MFP_CTL1 0x52 +#define IP5XXX_GPIO_CTL2 0x53 +#define IP5XXX_GPIO_CTL2A 0x54 +#define IP5XXX_GPIO_CTL3 0x55 +#define IP5XXX_READ0 0x71 +#define IP5XXX_READ0_CHG_STAT GENMASK(7, 5) +#define IP5XXX_READ0_CHG_STAT_IDLE (0x0 << 5) +#define IP5XXX_READ0_CHG_STAT_TRICKLE (0x1 << 5) +#define IP5XXX_READ0_CHG_STAT_CONST_VOLT (0x2 << 5) +#define IP5XXX_READ0_CHG_STAT_CONST_CUR (0x3 << 5) +#define IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP (0x4 << 5) +#define IP5XXX_READ0_CHG_STAT_FULL (0x5 << 5) +#define IP5XXX_READ0_CHG_STAT_TIMEOUT (0x6 << 5) +#define IP5XXX_READ0_CHG_OP BIT(4) +#define IP5XXX_READ0_CHG_END BIT(3) +#define IP5XXX_READ0_CONST_VOLT_TIMEOUT BIT(2) +#define IP5XXX_READ0_CHG_TIMEOUT BIT(1) +#define IP5XXX_READ0_TRICKLE_TIMEOUT BIT(0) +#define IP5XXX_READ0_TIMEOUT GENMASK(2, 0) +#define IP5XXX_READ1 0x72 +#define IP5XXX_READ1_WLED_PRESENT BIT(7) +#define IP5XXX_READ1_LIGHT_LOAD BIT(6) +#define IP5XXX_READ1_VIN_OVERVOLT BIT(5) +#define IP5XXX_READ2 0x77 +#define IP5XXX_READ2_BTN_PRESS BIT(3) +#define IP5XXX_READ2_BTN_LONG_PRESS BIT(1) +#define IP5XXX_READ2_BTN_SHORT_PRESS BIT(0) +#define IP5XXX_BATVADC_DAT0 0xa2 +#define IP5XXX_BATVADC_DAT1 0xa3 +#define IP5XXX_BATIADC_DAT0 0xa4 +#define IP5XXX_BATIADC_DAT1 0xa5 +#define IP5XXX_BATOCV_DAT0 0xa8 +#define IP5XXX_BATOCV_DAT1 0xa9 + +struct ip5xxx { + struct regmap *regmap; + bool initialized; +}; + +/* + * The IP5xxx charger only responds on I2C when it is "awake". The charger is + * generally only awake when VIN is powered or when its boost converter is + * enabled. Going into shutdown resets all register values. To handle this: + * 1) When any bus error occurs, assume the charger has gone into shutdown. + * 2) Attempt the initialization sequence on each subsequent register access + * until it succeeds. + */ +static int ip5xxx_read(struct ip5xxx *ip5xxx, unsigned int reg, + unsigned int *val) +{ + int ret; + + ret = regmap_read(ip5xxx->regmap, reg, val); + if (ret) + ip5xxx->initialized = false; + + return ret; +} + +static int ip5xxx_update_bits(struct ip5xxx *ip5xxx, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int ret; + + ret = regmap_update_bits(ip5xxx->regmap, reg, mask, val); + if (ret) + ip5xxx->initialized = false; + + return ret; +} + +static int ip5xxx_initialize(struct power_supply *psy) +{ + struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); + int ret; + + if (ip5xxx->initialized) + return 0; + + /* + * Disable shutdown under light load. + * Enable power on when under load. + */ + ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL1, + IP5XXX_SYS_CTL1_LIGHT_SHDN_EN | + IP5XXX_SYS_CTL1_LOAD_PWRUP_EN, + IP5XXX_SYS_CTL1_LOAD_PWRUP_EN); + if (ret) + return ret; + + /* + * Enable shutdown after a long button press (as configured below). + */ + ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL3, + IP5XXX_SYS_CTL3_BTN_SHDN_EN, + IP5XXX_SYS_CTL3_BTN_SHDN_EN); + if (ret) + return ret; + + /* + * Power on automatically when VIN is removed. + */ + ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL4, + IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN, + IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN); + if (ret) + return ret; + + /* + * Enable the NTC. + * Configure the button for two presses => LED, long press => shutdown. + */ + ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL5, + IP5XXX_SYS_CTL5_NTC_DIS | + IP5XXX_SYS_CTL5_WLED_MODE_SEL | + IP5XXX_SYS_CTL5_BTN_SHDN_SEL, + IP5XXX_SYS_CTL5_WLED_MODE_SEL | + IP5XXX_SYS_CTL5_BTN_SHDN_SEL); + if (ret) + return ret; + + ip5xxx->initialized = true; + dev_dbg(psy->dev.parent, "Initialized after power on\n"); + + return 0; +} + +static const enum power_supply_property ip5xxx_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, +}; + +static int ip5xxx_battery_get_status(struct ip5xxx *ip5xxx, int *val) +{ + unsigned int rval; + int ret; + + ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval); + if (ret) + return ret; + + switch (rval & IP5XXX_READ0_CHG_STAT) { + case IP5XXX_READ0_CHG_STAT_IDLE: + *val = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case IP5XXX_READ0_CHG_STAT_TRICKLE: + case IP5XXX_READ0_CHG_STAT_CONST_CUR: + case IP5XXX_READ0_CHG_STAT_CONST_VOLT: + *val = POWER_SUPPLY_STATUS_CHARGING; + break; + case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP: + case IP5XXX_READ0_CHG_STAT_FULL: + *val = POWER_SUPPLY_STATUS_FULL; + break; + case IP5XXX_READ0_CHG_STAT_TIMEOUT: + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ip5xxx_battery_get_charge_type(struct ip5xxx *ip5xxx, int *val) +{ + unsigned int rval; + int ret; + + ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval); + if (ret) + return ret; + + switch (rval & IP5XXX_READ0_CHG_STAT) { + case IP5XXX_READ0_CHG_STAT_IDLE: + case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP: + case IP5XXX_READ0_CHG_STAT_FULL: + case IP5XXX_READ0_CHG_STAT_TIMEOUT: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case IP5XXX_READ0_CHG_STAT_TRICKLE: + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case IP5XXX_READ0_CHG_STAT_CONST_CUR: + case IP5XXX_READ0_CHG_STAT_CONST_VOLT: + *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ip5xxx_battery_get_health(struct ip5xxx *ip5xxx, int *val) +{ + unsigned int rval; + int ret; + + ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval); + if (ret) + return ret; + + if (rval & IP5XXX_READ0_TIMEOUT) + *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + else + *val = POWER_SUPPLY_HEALTH_GOOD; + + return 0; +} + +static int ip5xxx_battery_get_voltage_max(struct ip5xxx *ip5xxx, int *val) +{ + unsigned int rval; + int ret; + + ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval); + if (ret) + return ret; + + /* + * It is not clear what this will return if + * IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN is not set... + */ + switch (rval & IP5XXX_CHG_CTL2_BAT_TYPE_SEL) { + case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V: + *val = 4200000; + break; + case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V: + *val = 4300000; + break; + case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V: + *val = 4350000; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ip5xxx_battery_read_adc(struct ip5xxx *ip5xxx, + u8 lo_reg, u8 hi_reg, int *val) +{ + unsigned int hi, lo; + int ret; + + ret = ip5xxx_read(ip5xxx, lo_reg, &lo); + if (ret) + return ret; + + ret = ip5xxx_read(ip5xxx, hi_reg, &hi); + if (ret) + return ret; + + *val = sign_extend32(hi << 8 | lo, 13); + + return 0; +} + +static int ip5xxx_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); + int raw, ret, vmax; + unsigned int rval; + + ret = ip5xxx_initialize(psy); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return ip5xxx_battery_get_status(ip5xxx, &val->intval); + + case POWER_SUPPLY_PROP_CHARGE_TYPE: + return ip5xxx_battery_get_charge_type(ip5xxx, &val->intval); + + case POWER_SUPPLY_PROP_HEALTH: + return ip5xxx_battery_get_health(ip5xxx, &val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + return ip5xxx_battery_get_voltage_max(ip5xxx, &val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATVADC_DAT0, + IP5XXX_BATVADC_DAT1, &raw); + + val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100); + return 0; + + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATOCV_DAT0, + IP5XXX_BATOCV_DAT1, &raw); + + val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100); + return 0; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATIADC_DAT0, + IP5XXX_BATIADC_DAT1, &raw); + + val->intval = DIV_ROUND_CLOSEST(raw * 745985, 1000); + return 0; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL4A, &rval); + if (ret) + return ret; + + rval &= IP5XXX_CHG_CTL4A_CONST_CUR_SEL; + val->intval = 100000 * rval; + return 0; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = 100000 * 0x1f; + return 0; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax); + if (ret) + return ret; + + ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval); + if (ret) + return ret; + + rval &= IP5XXX_CHG_CTL2_CONST_VOLT_SEL; + val->intval = vmax + 14000 * (rval >> 1); + return 0; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax); + if (ret) + return ret; + + val->intval = vmax + 14000 * 3; + return 0; + + default: + return -EINVAL; + } +} + +static int ip5xxx_battery_set_voltage_max(struct ip5xxx *ip5xxx, int val) +{ + unsigned int rval; + int ret; + + switch (val) { + case 4200000: + rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V; + break; + case 4300000: + rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V; + break; + case 4350000: + rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V; + break; + default: + return -EINVAL; + } + + ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2, + IP5XXX_CHG_CTL2_BAT_TYPE_SEL, rval); + if (ret) + return ret; + + ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4, + IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN, + IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN); + if (ret) + return ret; + + return 0; +} + +static int ip5xxx_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); + unsigned int rval; + int ret, vmax; + + ret = ip5xxx_initialize(psy); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + switch (val->intval) { + case POWER_SUPPLY_STATUS_CHARGING: + rval = IP5XXX_SYS_CTL0_CHARGER_EN; + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + rval = 0; + break; + default: + return -EINVAL; + } + return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0, + IP5XXX_SYS_CTL0_CHARGER_EN, rval); + + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + return ip5xxx_battery_set_voltage_max(ip5xxx, val->intval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + rval = val->intval / 100000; + return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4A, + IP5XXX_CHG_CTL4A_CONST_CUR_SEL, rval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax); + if (ret) + return ret; + + rval = ((val->intval - vmax) / 14000) << 1; + return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2, + IP5XXX_CHG_CTL2_CONST_VOLT_SEL, rval); + + default: + return -EINVAL; + } +} + +static int ip5xxx_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_STATUS || + psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN || + psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT || + psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE; +} + +static const struct power_supply_desc ip5xxx_battery_desc = { + .name = "ip5xxx-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = ip5xxx_battery_properties, + .num_properties = ARRAY_SIZE(ip5xxx_battery_properties), + .get_property = ip5xxx_battery_get_property, + .set_property = ip5xxx_battery_set_property, + .property_is_writeable = ip5xxx_battery_property_is_writeable, +}; + +static const enum power_supply_property ip5xxx_boost_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, +}; + +static int ip5xxx_boost_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); + unsigned int rval; + int ret; + + ret = ip5xxx_initialize(psy); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = ip5xxx_read(ip5xxx, IP5XXX_SYS_CTL0, &rval); + if (ret) + return ret; + + val->intval = !!(rval & IP5XXX_SYS_CTL0_BOOST_EN); + return 0; + + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL1, &rval); + if (ret) + return ret; + + rval &= IP5XXX_CHG_CTL1_BOOST_UVP_SEL; + val->intval = 4530000 + 100000 * (rval >> 2); + return 0; + + default: + return -EINVAL; + } +} + +static int ip5xxx_boost_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy); + unsigned int rval; + int ret; + + ret = ip5xxx_initialize(psy); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + rval = val->intval ? IP5XXX_SYS_CTL0_BOOST_EN : 0; + return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0, + IP5XXX_SYS_CTL0_BOOST_EN, rval); + + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + rval = ((val->intval - 4530000) / 100000) << 2; + return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL1, + IP5XXX_CHG_CTL1_BOOST_UVP_SEL, rval); + + default: + return -EINVAL; + } +} + +static int ip5xxx_boost_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return true; +} + +static const struct power_supply_desc ip5xxx_boost_desc = { + .name = "ip5xxx-boost", + .type = POWER_SUPPLY_TYPE_USB, + .properties = ip5xxx_boost_properties, + .num_properties = ARRAY_SIZE(ip5xxx_boost_properties), + .get_property = ip5xxx_boost_get_property, + .set_property = ip5xxx_boost_set_property, + .property_is_writeable = ip5xxx_boost_property_is_writeable, +}; + +static const struct regmap_config ip5xxx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = IP5XXX_BATOCV_DAT1, +}; + +static int ip5xxx_power_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = {}; + struct device *dev = &client->dev; + struct power_supply *psy; + struct ip5xxx *ip5xxx; + + ip5xxx = devm_kzalloc(dev, sizeof(*ip5xxx), GFP_KERNEL); + if (!ip5xxx) + return -ENOMEM; + + ip5xxx->regmap = devm_regmap_init_i2c(client, &ip5xxx_regmap_config); + if (IS_ERR(ip5xxx->regmap)) + return PTR_ERR(ip5xxx->regmap); + + psy_cfg.of_node = dev->of_node; + psy_cfg.drv_data = ip5xxx; + + psy = devm_power_supply_register(dev, &ip5xxx_battery_desc, &psy_cfg); + if (IS_ERR(psy)) + return PTR_ERR(psy); + + psy = devm_power_supply_register(dev, &ip5xxx_boost_desc, &psy_cfg); + if (IS_ERR(psy)) + return PTR_ERR(psy); + + return 0; +} + +static const struct of_device_id ip5xxx_power_of_match[] = { + { .compatible = "injoinic,ip5108" }, + { .compatible = "injoinic,ip5109" }, + { .compatible = "injoinic,ip5207" }, + { .compatible = "injoinic,ip5209" }, + { } +}; +MODULE_DEVICE_TABLE(of, ip5xxx_power_of_match); + +static struct i2c_driver ip5xxx_power_driver = { + .probe_new = ip5xxx_power_probe, + .driver = { + .name = "ip5xxx-power", + .of_match_table = ip5xxx_power_of_match, + } +}; +module_i2c_driver(ip5xxx_power_driver); + +MODULE_AUTHOR("Samuel Holland "); +MODULE_DESCRIPTION("Injoinic IP5xxx power bank IC driver"); +MODULE_LICENSE("GPL"); From c22fca40522e2be8af168f3087d87d85e404ea72 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 8 Mar 2022 16:44:25 +0100 Subject: [PATCH 94/94] power: ab8500_chargalg: Use CLOCK_MONOTONIC The HRTimer in the AB8500 charging code is using CLOCK_REALTIME to set an alarm some hours forward in time +/- 5 min for a safety timer. I have observed that this will sometimes fire sporadically early when charging a battery with the result that charging stops. As CLOCK_REALTIME can be subject to adjustments of time from sources such as NTP, this cannot be trusted and will likely for example fire events if the clock is set forward some hours by say NTP. Use CLOCK_MONOTONIC as indicated in other instances and the problem goes away. Also initialize the timer to REL mode as this is what will be used later. Fixes: 257107ae6b9b ("ab8500-chargalg: Use hrtimer") Cc: Lee Jones Suggested-by: Matti Vaittinen Signed-off-by: Linus Walleij Reviewed-by: Matti Vaittinen Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_chargalg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index cd7de1127a1e..431bbc352d1b 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -1775,11 +1775,11 @@ static int ab8500_chargalg_probe(struct platform_device *pdev) psy_cfg.drv_data = di; /* Initilialize safety timer */ - hrtimer_init(&di->safety_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); + hrtimer_init(&di->safety_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); di->safety_timer.function = ab8500_chargalg_safety_timer_expired; /* Initilialize maintenance timer */ - hrtimer_init(&di->maintenance_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); + hrtimer_init(&di->maintenance_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); di->maintenance_timer.function = ab8500_chargalg_maintenance_timer_expired;