From 1bad919577b82154c637ddeca694cdb2880ea038 Mon Sep 17 00:00:00 2001 From: Grond Date: Tue, 20 Dec 2016 10:59:42 -0800 Subject: [PATCH] twl4030_charger: allow changing end-of-charge currnet. Allows the user to change the TWL4030's end-of-charge current parameter via a sysfs file. If set to 400mA, it nicely solves the overcharging problem on the pandora. Note that this commit also changes the format of the charge_current sysfs file to be in micro-amperes, to make it consistent with all the other sysfs parameters. Since it looks like this file was only being used for debugging purposes, this shouldn't break anybody's tools. (fingers crossed.) --- drivers/power/twl4030_charger.c | 264 +++++++++++++++++++++++++------- 1 file changed, 208 insertions(+), 56 deletions(-) diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index 575407a03024..643727ff9703 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c @@ -32,6 +32,7 @@ #define TWL4030_BCIMFSTS3 0x0f #define TWL4030_BCIMFSTS4 0x10 #define TWL4030_BCIMFKEY 0x11 +#define TWL4030_BCIMFTH8 0x1d #define TWL4030_BCICTL1 0x23 #define TWL4030_BCIIREF1 0x27 #define TWL4030_BCIIREF2 0x28 @@ -71,6 +72,7 @@ #define TWL4030_MSTATEC_COMPLETE4 0x0e #define TWL4030_KEY_IIREF 0xe7 +#define TWL4030_KEY_MFTH8 0xf4 #define TWL4030_BATSTSMCHG BIT(6) #define IRQ_CHECK_PERIOD (3 * HZ) @@ -98,6 +100,9 @@ struct twl4030_bci { int usb_enabled; int irq_had_charger; + unsigned charge_current; + unsigned eoc_current; + unsigned long irq_check_count_time; int irq_check_count; int irq_check_ac_disabled; @@ -164,6 +169,20 @@ static int twl4030bci_read_adc_val(u8 reg) return temp | val; } +static int twl4030bci_get_cgain(struct twl4030_bci *bci, int *cgain) { + int ret; + u8 reg; + + ret = twl4030_bci_read(TWL4030_BCICTL1, ®); + if (ret < 0) { + dev_warn(bci->dev, "error fetching CGAIN value from BCICTL1 register"); + return ret; + } + *cgain = (reg & TWL4030_CGAIN) ? 1 : 0; + + return 0; +} + /* * Check if VBUS power is present */ @@ -249,26 +268,62 @@ static int twl4030_charger_enable_ac(bool enable) return ret; } -static int set_charge_current(struct twl4030_bci *bci, int new_current) +static int update_charge_parameters(struct twl4030_bci *bci, + int charge_current, int eoc_current) { - u8 val, boot_bci_prev, cgain_set, cgain_clear; - int ret, ret2; + int old_charge_current = bci->charge_current; + int old_eoc_current = bci->eoc_current; + int ret = 0; + int ret2; + u16 hw_charge_current; + u8 hw_eoc_current, boot_bci_prev, reg, cgain_set, cgain_clear; + bool need_cgain = false; + bool restore_boot_reg = false; + + if (charge_current < 0 && eoc_current < 0) + return -EINVAL; - ret = twl4030_bci_read(TWL4030_BCIMFSTS3, &val); + ret = twl4030_bci_read(TWL4030_BCIMFSTS3, ®); if (ret) - goto out_norestore; + goto fail_norestore; + if (!(reg & TWL4030_BATSTSMCHG)) { + dev_err(bci->dev, "missing battery, can't change charging parameters\n"); + goto fail_norestore; + } + + if (charge_current >= 0) + bci->charge_current = charge_current; + if (eoc_current >= 0) + bci->eoc_current = eoc_current; + + hw_eoc_current = bci->eoc_current / 13310; + if (hw_eoc_current > 15) + need_cgain = true; - if (!(val & TWL4030_BATSTSMCHG)) { - dev_err(bci->dev, "missing battery, can't change charge_current\n"); - goto out_norestore; + hw_charge_current = (bci->charge_current * 1000) / 1666666; + if (hw_charge_current > 511) + need_cgain = true; + + if (need_cgain) { + hw_eoc_current /= 2; + hw_charge_current /= 2; + } + + if (hw_eoc_current > 15) { + ret = -EINVAL; + goto fail; + } + if (hw_charge_current > 511) { + ret = -EINVAL; + goto fail; } ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &boot_bci_prev, TWL4030_PM_MASTER_BOOT_BCI); if (ret) - goto out_norestore; + goto fail; - /* + /* * Stop automatic charging here, because charge current change * requires multiple register writes and CGAIN change requires * automatic charge to be stopped (and CV mode disabled too). @@ -276,26 +331,32 @@ static int set_charge_current(struct twl4030_bci *bci, int new_current) ret = twl4030_clear_set_boot_bci( TWL4030_CVENAC | TWL4030_BCIAUTOAC | TWL4030_BCIAUTOUSB, 0); if (ret) - goto out; + goto fail; + else + restore_boot_reg = true; - ret = twl4030_bci_write(TWL4030_BCIMFKEY, TWL4030_KEY_IIREF); + ret = twl4030_bci_write(TWL4030_BCIMFKEY, TWL4030_KEY_MFTH8); if (ret) - goto out; - - ret = twl4030_bci_write(TWL4030_BCIIREF1, new_current & 0xff); + goto fail; + ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, 0xf0, + hw_eoc_current << 4, TWL4030_BCIMFTH8); if (ret) - goto out; + goto fail; ret = twl4030_bci_write(TWL4030_BCIMFKEY, TWL4030_KEY_IIREF); if (ret) - goto out; - - ret = twl4030_bci_write(TWL4030_BCIIREF2, (new_current >> 8) & 0x1); + goto fail; + ret = twl4030_bci_write(TWL4030_BCIIREF1, hw_charge_current & 0xff); if (ret) - goto out; + goto fail; + ret = twl4030_bci_write(TWL4030_BCIMFKEY, TWL4030_KEY_IIREF); + if (ret) + goto fail; + ret = twl4030_bci_write(TWL4030_BCIIREF2, (hw_charge_current >> 8) & 0x1); + if (ret) + goto fail; - /* Set CGAIN = 0 or 1 */ - if (new_current > 511) { + if (need_cgain) { cgain_set = TWL4030_CGAIN; cgain_clear = 0; } else { @@ -306,23 +367,32 @@ static int set_charge_current(struct twl4030_bci *bci, int new_current) ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, cgain_clear, cgain_set, TWL4030_BCICTL1); if (ret) - goto out; + goto fail; - ret = twl4030_bci_read(TWL4030_BCICTL1, &val); - if (ret != 0 || (val & TWL4030_CGAIN) != cgain_set) { + ret = twl4030_bci_read(TWL4030_BCICTL1, ®); + if (ret != 0 || (reg & TWL4030_CGAIN) != cgain_set) { dev_err(bci->dev, "CGAIN change failed\n"); - goto out; + goto fail; } -out: - ret2 = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, boot_bci_prev, - TWL4030_PM_MASTER_BOOT_BCI); - if (ret2 != 0) +fail: + if (restore_boot_reg && (ret2 = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, + boot_bci_prev, TWL4030_PM_MASTER_BOOT_BCI))) { dev_err(bci->dev, "failed boot_bci restore: %d\n", ret2); + if (!ret) + ret = ret2; + } + + if (ret) { + if (charge_current >= 0) + bci->charge_current = old_charge_current; + if (eoc_current >= 0) + bci->eoc_current = old_eoc_current; + } -out_norestore: - if (ret != 0) - dev_err(bci->dev, "charge current change failed: %d\n", ret); +fail_norestore: + if (ret) + dev_err(bci->dev, "failed to change charging parameters\n"); return ret; } @@ -470,27 +540,66 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val, * CGAIN == 0: val * 1.6618 - 0.85 * CGAIN == 1: (val * 1.6618 - 0.85) * 2 */ -static int twl4030_charger_get_current(void) +static int twl4030_charger_get_current(struct twl4030_bci *bci) { int curr; int ret; - u8 bcictl1; + int cgain; curr = twl4030bci_read_adc_val(TWL4030_BCIICHG); if (curr < 0) return curr; - ret = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); - if (ret) + if ((ret = twl4030bci_get_cgain(bci, &cgain))) return ret; ret = (curr * 16618 - 850 * 10000) / 10; - if (bcictl1 & TWL4030_CGAIN) + if (cgain) ret *= 2; return ret; } +static int twl4030bci_get_charging_current(struct twl4030_bci *bci, int *curr) +{ + int ret, val, cgain; + + val = twl4030bci_read_adc_val(TWL4030_BCIIREF1); + if (val < 0) + return val; + if ((ret = twl4030bci_get_cgain(bci, &cgain))) + return ret; + + val &= 0x1ff; + val *= 1666; + if (cgain) + val *= 2; + + *curr = val; + + return 0; +} + +static int twl4030bci_get_eoc_current(struct twl4030_bci *bci, int *curr) +{ + int ret, cgain, ichgeocth; + u8 reg; + + if ((ret = twl4030bci_get_cgain(bci, &cgain))) + return ret; + + ret = twl4030_bci_read(TWL4030_BCIMFTH8, ®); + if (ret < 0) { + dev_warn(bci->dev, "error fetching BCIMFTH8 register"); + return ret; + } + ichgeocth = reg >> 4; + + *curr = (ichgeocth * 13310) * ((cgain) ? 2 : 1); + + return 0; +} + static ssize_t twl4030_bci_ac_show_enable(struct device *dev, struct device_attribute *attr, char *buf) @@ -568,19 +677,12 @@ static struct device_attribute dev_attr_enable_usb = static ssize_t show_charge_current(struct device *dev, struct device_attribute *attr, char *buf) { + struct power_supply *psy = dev_get_drvdata(dev); + struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent); int ret, val; - u8 ctl; - - val = twl4030bci_read_adc_val(TWL4030_BCIIREF1); - if (val < 0) - return val; - ret = twl4030_bci_read(TWL4030_BCICTL1, &ctl); - if (ret < 0) - return ret; - val &= 0x1ff; - if (ctl & TWL4030_CGAIN) - val |= 0x200; + if ((ret = twl4030bci_get_charging_current(bci, &val))) + return ret; return sprintf(buf, "%d\n", val); } @@ -598,7 +700,7 @@ static ssize_t store_charge_current(struct device *dev, if (ret) return -EINVAL; - ret = set_charge_current(bci, new_current); + ret = update_charge_parameters(bci, new_current, -1); if (ret) return ret; @@ -612,18 +714,60 @@ static ssize_t store_charge_current(struct device *dev, static DEVICE_ATTR(charge_current, S_IRUGO | S_IWUSR, show_charge_current, store_charge_current); +static ssize_t show_end_of_charge_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent); + int ret, eoc_current; + + if ((ret = twl4030bci_get_eoc_current(bci, &eoc_current))) + return ret; + + return sprintf(buf, "%d\n", eoc_current); +} + +static ssize_t store_end_of_charge_current(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent); + unsigned long new_eoc_current; + int eoc_current, ret; + + ret = strict_strtoul(buf, 10, &new_eoc_current); + if (ret) + return -EINVAL; + + eoc_current = new_eoc_current; + /* check for overflow */ + if (eoc_current != new_eoc_current) + return -EINVAL; + + if ((ret = update_charge_parameters(bci, -1, eoc_current))) + return ret; + + return count; +} + +static DEVICE_ATTR(end_of_charge_current, S_IRUGO | S_IWUSR, + show_end_of_charge_current, store_end_of_charge_current); + static struct attribute *bci_ac_attrs[] = { &dev_attr_enable_ac.attr, &dev_attr_charge_current.attr, + &dev_attr_end_of_charge_current.attr, NULL, }; static struct attribute *bci_usb_attrs[] = { &dev_attr_enable_usb.attr, &dev_attr_charge_current.attr, + &dev_attr_end_of_charge_current.attr, NULL, }; - + static const struct attribute_group bci_ac_attr_group = { .attrs = bci_ac_attrs, }; @@ -696,9 +840,9 @@ static int twl4030_bci_get_property(struct power_supply *psy, if (is_charging && psy->type != bci->current_supply) { if (psy->type == POWER_SUPPLY_TYPE_USB) - set_charge_current(bci, bci->usb_current); + update_charge_parameters(bci, bci->usb_current, -1); else - set_charge_current(bci, bci->ac_current); + update_charge_parameters(bci, bci->ac_current, -1); bci->current_supply = psy->type; } @@ -731,7 +875,7 @@ static int twl4030_bci_get_property(struct power_supply *psy, if (!is_charging) return -ENODATA; /* current measurement is shared between AC and USB */ - ret = twl4030_charger_get_current(); + ret = twl4030_charger_get_current(bci); if (ret < 0) return ret; val->intval = ret; @@ -774,8 +918,14 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) bci->dev = &pdev->dev; bci->irq_chg = platform_get_irq(pdev, 0); bci->irq_bci = platform_get_irq(pdev, 1); - bci->ac_current = 860; /* ~1.2A */ - bci->usb_current = 330; /* ~560mA */ + bci->ac_current = 1200000; /* ~1.2A */ + bci->usb_current = 560000; /* ~560mA */ + + if ((ret = twl4030bci_get_charging_current(bci, &bci->charge_current))) + goto fail_read_charge_params; + if ((ret = twl4030bci_get_eoc_current(bci, &bci->eoc_current))) + goto fail_read_charge_params; + bci->irq_had_charger = -1; bci->irq_check_count_time = jiffies; @@ -905,6 +1055,8 @@ fail_register_usb: fail_register_ac: led_trigger_unregister_simple(bci->charging_any_trig); platform_set_drvdata(pdev, NULL); + +fail_read_charge_params: kfree(bci); return ret; -- 2.39.2