diff options
Diffstat (limited to 'drivers/power/twl4030_charger.c')
-rw-r--r-- | drivers/power/twl4030_charger.c | 598 |
1 files changed, 507 insertions, 91 deletions
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index 022b8910e443..f4f2c1f76c32 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c @@ -22,8 +22,10 @@ #include <linux/power_supply.h> #include <linux/notifier.h> #include <linux/usb/otg.h> -#include <linux/regulator/machine.h> +#include <linux/i2c/twl4030-madc.h> +#define TWL4030_BCIMDEN 0x00 +#define TWL4030_BCIMDKEY 0x01 #define TWL4030_BCIMSTATEC 0x02 #define TWL4030_BCIICHG 0x08 #define TWL4030_BCIVAC 0x0a @@ -32,11 +34,19 @@ #define TWL4030_BCIMFSTS4 0x10 #define TWL4030_BCICTL1 0x23 #define TWL4030_BB_CFG 0x12 +#define TWL4030_BCIIREF1 0x27 +#define TWL4030_BCIIREF2 0x28 +#define TWL4030_BCIMFKEY 0x11 +#define TWL4030_BCIMFEN3 0x14 +#define TWL4030_BCIMFTH8 0x1d +#define TWL4030_BCIMFTH9 0x1e +#define TWL4030_BCIWDKEY 0x21 #define TWL4030_BCIMFSTS1 0x01 #define TWL4030_BCIAUTOWEN BIT(5) #define TWL4030_CONFIG_DONE BIT(4) +#define TWL4030_CVENAC BIT(2) #define TWL4030_BCIAUTOUSB BIT(1) #define TWL4030_BCIAUTOAC BIT(0) #define TWL4030_CGAIN BIT(5) @@ -81,6 +91,21 @@ #define TWL4030_MSTATEC_COMPLETE1 0x0b #define TWL4030_MSTATEC_COMPLETE4 0x0e +#if IS_ENABLED(CONFIG_TWL4030_MADC) +/* + * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) + * then AC is available. + */ +static inline int ac_available(void) +{ + return twl4030_get_madc_conversion(11) > 4500; +} +#else +static inline int ac_available(void) +{ + return 0; +} +#endif static bool allow_usb; module_param(allow_usb, bool, 0644); MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current"); @@ -94,12 +119,39 @@ struct twl4030_bci { struct work_struct work; int irq_chg; int irq_bci; - struct regulator *usb_reg; int usb_enabled; + /* + * ichg_* and *_cur values in uA. If any are 'large', we set + * CGAIN to '1' which doubles the range for half the + * precision. + */ + unsigned int ichg_eoc, ichg_lo, ichg_hi; + unsigned int usb_cur, ac_cur; + bool ac_is_active; + int usb_mode, ac_mode; /* charging mode requested */ +#define CHARGE_OFF 0 +#define CHARGE_AUTO 1 +#define CHARGE_LINEAR 2 + + /* When setting the USB current we slowly increase the + * requested current until target is reached or the voltage + * drops below 4.75V. In the latter case we step back one + * step. + */ + unsigned int usb_cur_target; + struct delayed_work current_worker; +#define USB_CUR_STEP 20000 /* 20mA at a time */ +#define USB_MIN_VOLT 4750000 /* 4.75V */ +#define USB_CUR_DELAY msecs_to_jiffies(100) +#define USB_MAX_CURRENT 1700000 /* TWL4030 caps at 1.7A */ + unsigned long event; }; +/* strings for 'usb_mode' values */ +static char *modes[] = { "off", "auto", "continuous" }; + /* * clear and set bits on an given register on a given module */ @@ -180,27 +232,233 @@ static int twl4030_is_battery_present(struct twl4030_bci *bci) } /* - * Check if VBUS power is present + * TI provided formulas: + * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85 + * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7 + * Here we use integer approximation of: + * CGAIN == 0: val * 1.6618 - 0.85 * 1000 + * CGAIN == 1: (val * 1.6618 - 0.85 * 1000) * 2 + */ +/* + * convert twl register value for currents into uA + */ +static int regval2ua(int regval, bool cgain) +{ + if (cgain) + return (regval * 16618 - 8500 * 1000) / 5; + else + return (regval * 16618 - 8500 * 1000) / 10; +} + +/* + * convert uA currents into twl register value */ -static int twl4030_bci_have_vbus(struct twl4030_bci *bci) +static int ua2regval(int ua, bool cgain) { int ret; - u8 hwsts; + if (cgain) + ua /= 2; + ret = (ua * 10 + 8500 * 1000) / 16618; + /* rounding problems */ + if (ret < 512) + ret = 512; + return ret; +} - ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &hwsts, - TWL4030_PM_MASTER_STS_HW_CONDITIONS); - if (ret < 0) - return 0; +static int twl4030_charger_update_current(struct twl4030_bci *bci) +{ + int status; + int cur; + unsigned reg, cur_reg; + u8 bcictl1, oldreg, fullreg; + bool cgain = false; + u8 boot_bci; - dev_dbg(bci->dev, "check_vbus: HW_CONDITIONS %02x\n", hwsts); + /* + * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) + * and AC is enabled, set current for 'ac' + */ + if (ac_available()) { + cur = bci->ac_cur; + bci->ac_is_active = true; + } else { + cur = bci->usb_cur; + bci->ac_is_active = false; + if (cur > bci->usb_cur_target) { + cur = bci->usb_cur_target; + bci->usb_cur = cur; + } + if (cur < bci->usb_cur_target) + schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY); + } + + /* First, check thresholds and see if cgain is needed */ + if (bci->ichg_eoc >= 200000) + cgain = true; + if (bci->ichg_lo >= 400000) + cgain = true; + if (bci->ichg_hi >= 820000) + cgain = true; + if (cur > 852000) + cgain = true; + + status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); + if (status < 0) + return status; + if (twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &boot_bci, + TWL4030_PM_MASTER_BOOT_BCI) < 0) + boot_bci = 0; + boot_bci &= 7; + + if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) + /* Need to turn for charging while we change the + * CGAIN bit. Leave it off while everything is + * updated. + */ + twl4030_clear_set_boot_bci(boot_bci, 0); + + /* + * For ichg_eoc, the hardware only supports reg values matching + * 100XXXX000, and requires the XXXX be stored in the high nibble + * of TWL4030_BCIMFTH8. + */ + reg = ua2regval(bci->ichg_eoc, cgain); + if (reg > 0x278) + reg = 0x278; + if (reg < 0x200) + reg = 0x200; + reg = (reg >> 3) & 0xf; + fullreg = reg << 4; + + /* + * For ichg_lo, reg value must match 10XXXX0000. + * XXXX is stored in low nibble of TWL4030_BCIMFTH8. + */ + reg = ua2regval(bci->ichg_lo, cgain); + if (reg > 0x2F0) + reg = 0x2F0; + if (reg < 0x200) + reg = 0x200; + reg = (reg >> 4) & 0xf; + fullreg |= reg; + + /* ichg_eoc and ichg_lo live in same register */ + status = twl4030_bci_read(TWL4030_BCIMFTH8, &oldreg); + if (status < 0) + return status; + if (oldreg != fullreg) { + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xF4, + TWL4030_BCIMFKEY); + if (status < 0) + return status; + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + fullreg, TWL4030_BCIMFTH8); + } - /* in case we also have STS_USB_ID, VBUS is driven by TWL itself */ - if ((hwsts & TWL4030_STS_VBUS) && !(hwsts & TWL4030_STS_USB_ID)) - return 1; + /* ichg_hi threshold must be 1XXXX01100 (I think) */ + reg = ua2regval(bci->ichg_hi, cgain); + if (reg > 0x3E0) + reg = 0x3E0; + if (reg < 0x200) + reg = 0x200; + fullreg = (reg >> 5) & 0xF; + fullreg <<= 4; + status = twl4030_bci_read(TWL4030_BCIMFTH9, &oldreg); + if (status < 0) + return status; + if ((oldreg & 0xF0) != fullreg) { + fullreg |= (oldreg & 0x0F); + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, + TWL4030_BCIMFKEY); + if (status < 0) + return status; + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + fullreg, TWL4030_BCIMFTH9); + } + /* + * And finally, set the current. This is stored in + * two registers. + */ + reg = ua2regval(cur, cgain); + /* we have only 10 bits */ + if (reg > 0x3ff) + reg = 0x3ff; + status = twl4030_bci_read(TWL4030_BCIIREF1, &oldreg); + if (status < 0) + return status; + cur_reg = oldreg; + status = twl4030_bci_read(TWL4030_BCIIREF2, &oldreg); + if (status < 0) + return status; + cur_reg |= oldreg << 8; + if (reg != oldreg) { + /* disable write protection for one write access for + * BCIIREF */ + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, + TWL4030_BCIMFKEY); + if (status < 0) + return status; + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + (reg & 0x100) ? 3 : 2, + TWL4030_BCIIREF2); + if (status < 0) + return status; + /* disable write protection for one write access for + * BCIIREF */ + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, + TWL4030_BCIMFKEY); + if (status < 0) + return status; + status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + reg & 0xff, + TWL4030_BCIIREF1); + } + if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) { + /* Flip CGAIN and re-enable charging */ + bcictl1 ^= TWL4030_CGAIN; + twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + bcictl1, TWL4030_BCICTL1); + twl4030_clear_set_boot_bci(0, boot_bci); + } return 0; } +static int twl4030_charger_get_current(void); + +static void twl4030_current_worker(struct work_struct *data) +{ + int v, curr; + int res; + struct twl4030_bci *bci = container_of(data, struct twl4030_bci, + current_worker.work); + + res = twl4030bci_read_adc_val(TWL4030_BCIVBUS); + if (res < 0) + v = 0; + else + /* BCIVBUS uses ADCIN8, 7/1023 V/step */ + v = res * 6843; + curr = twl4030_charger_get_current(); + + dev_dbg(bci->dev, "v=%d cur=%d limit=%d target=%d\n", v, curr, + bci->usb_cur, bci->usb_cur_target); + + if (v < USB_MIN_VOLT) { + /* Back up and stop adjusting. */ + bci->usb_cur -= USB_CUR_STEP; + bci->usb_cur_target = bci->usb_cur; + } else if (bci->usb_cur >= bci->usb_cur_target || + bci->usb_cur + USB_CUR_STEP > USB_MAX_CURRENT) { + /* Reached target and voltage is OK - stop */ + return; + } else { + bci->usb_cur += USB_CUR_STEP; + schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY); + } + twl4030_charger_update_current(bci); +} + /* * Enable/Disable USB Charge functionality. */ @@ -208,45 +466,60 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) { int ret; - if (enable) { - /* Check for USB charger connected */ - if (!twl4030_bci_have_vbus(bci)) - return -ENODEV; + if (bci->usb_mode == CHARGE_OFF) + enable = false; + if (enable && !IS_ERR_OR_NULL(bci->transceiver)) { - /* - * Until we can find out what current the device can provide, - * require a module param to enable USB charging. - */ - if (!allow_usb) { - dev_warn(bci->dev, "USB charging is disabled.\n"); - return -EACCES; - } + twl4030_charger_update_current(bci); - /* Need to keep regulator on */ + /* Need to keep phy powered */ if (!bci->usb_enabled) { - ret = regulator_enable(bci->usb_reg); - if (ret) { - dev_err(bci->dev, - "Failed to enable regulator\n"); - return ret; - } + pm_runtime_get_sync(bci->transceiver->dev); bci->usb_enabled = 1; } - /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ - ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); - if (ret < 0) - return ret; + if (bci->usb_mode == CHARGE_AUTO) + /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ + ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0, TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4); + if (bci->usb_mode == CHARGE_LINEAR) { + twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC|TWL4030_CVENAC, 0); + /* Watch dog key: WOVF acknowledge */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x33, + TWL4030_BCIWDKEY); + /* 0x24 + EKEY6: off mode */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a, + TWL4030_BCIMDKEY); + /* EKEY2: Linear charge: USB path */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x26, + TWL4030_BCIMDKEY); + /* WDKEY5: stop watchdog count */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf3, + TWL4030_BCIWDKEY); + /* enable MFEN3 access */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x9c, + TWL4030_BCIMFKEY); + /* ICHGEOCEN - end-of-charge monitor (current < 80mA) + * (charging continues) + * ICHGLOWEN - current level monitor (charge continues) + * don't monitor over-current or heat save + */ + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf0, + TWL4030_BCIMFEN3); + } } else { ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0); + ret |= twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a, + TWL4030_BCIMDKEY); if (bci->usb_enabled) { - regulator_disable(bci->usb_reg); + pm_runtime_mark_last_busy(bci->transceiver->dev); + pm_runtime_put_autosuspend(bci->transceiver->dev); bci->usb_enabled = 0; } + bci->usb_cur = 0; } return ret; @@ -255,10 +528,13 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) /* * Enable/Disable AC Charge funtionality. */ -static int twl4030_charger_enable_ac(bool enable) +static int twl4030_charger_enable_ac(struct twl4030_bci *bci, bool enable) { int ret; + if (bci->ac_mode == CHARGE_OFF) + enable = false; + if (enable) ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC); else @@ -318,6 +594,9 @@ static irqreturn_t twl4030_charger_interrupt(int irq, void *arg) struct twl4030_bci *bci = arg; dev_dbg(bci->dev, "CHG_PRES irq\n"); + /* reset current on each 'plug' event */ + bci->ac_cur = 500000; + twl4030_charger_update_current(bci); power_supply_changed(bci->ac); power_supply_changed(bci->usb); @@ -350,6 +629,7 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg) power_supply_changed(bci->ac); power_supply_changed(bci->usb); } + twl4030_charger_update_current(bci); /* various monitoring events, for now we just log them here */ if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1)) @@ -370,6 +650,63 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg) return IRQ_HANDLED; } +/* + * Provide "max_current" attribute in sysfs. + */ +static ssize_t +twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct twl4030_bci *bci = dev_get_drvdata(dev->parent); + int cur = 0; + int status = 0; + status = kstrtoint(buf, 10, &cur); + if (status) + return status; + if (cur < 0) + return -EINVAL; + if (dev == &bci->ac->dev) + bci->ac_cur = cur; + else + bci->usb_cur_target = cur; + + twl4030_charger_update_current(bci); + return n; +} + +/* + * sysfs max_current show + */ +static ssize_t twl4030_bci_max_current_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = 0; + int cur = -1; + u8 bcictl1; + struct twl4030_bci *bci = dev_get_drvdata(dev->parent); + + if (dev == &bci->ac->dev) { + if (!bci->ac_is_active) + cur = bci->ac_cur; + } else { + if (bci->ac_is_active) + cur = bci->usb_cur_target; + } + if (cur < 0) { + cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1); + if (cur < 0) + return cur; + status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); + if (status < 0) + return status; + cur = regval2ua(cur, bcictl1 & TWL4030_CGAIN); + } + return scnprintf(buf, PAGE_SIZE, "%u\n", cur); +} + +static DEVICE_ATTR(max_current, 0644, twl4030_bci_max_current_show, + twl4030_bci_max_current_store); + static void twl4030_bci_usb_work(struct work_struct *data) { struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work); @@ -392,6 +729,12 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val, dev_dbg(bci->dev, "OTG notify %lu\n", val); + /* reset current on each 'plug' event */ + if (allow_usb) + bci->usb_cur_target = 500000; + else + bci->usb_cur_target = 100000; + bci->event = val; schedule_work(&bci->work); @@ -399,13 +742,66 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val, } /* - * TI provided formulas: - * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85 - * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7 - * Here we use integer approximation of: - * CGAIN == 0: val * 1.6618 - 0.85 - * CGAIN == 1: (val * 1.6618 - 0.85) * 2 + * sysfs charger enabled store + */ +static ssize_t +twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct twl4030_bci *bci = dev_get_drvdata(dev->parent); + int mode; + int status; + + if (sysfs_streq(buf, modes[0])) + mode = 0; + else if (sysfs_streq(buf, modes[1])) + mode = 1; + else if (sysfs_streq(buf, modes[2])) + mode = 2; + else + return -EINVAL; + if (dev == &bci->ac->dev) { + if (mode == 2) + return -EINVAL; + twl4030_charger_enable_ac(bci, false); + bci->ac_mode = mode; + status = twl4030_charger_enable_ac(bci, true); + } else { + twl4030_charger_enable_usb(bci, false); + bci->usb_mode = mode; + status = twl4030_charger_enable_usb(bci, true); + } + return (status == 0) ? n : status; +} + +/* + * sysfs charger enabled show */ +static ssize_t +twl4030_bci_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct twl4030_bci *bci = dev_get_drvdata(dev->parent); + int len = 0; + int i; + int mode = bci->usb_mode; + + if (dev == &bci->ac->dev) + mode = bci->ac_mode; + + for (i = 0; i < ARRAY_SIZE(modes); i++) + if (mode == i) + len += snprintf(buf+len, PAGE_SIZE-len, + "[%s] ", modes[i]); + else + len += snprintf(buf+len, PAGE_SIZE-len, + "%s ", modes[i]); + buf[len-1] = '\n'; + return len; +} +static DEVICE_ATTR(mode, 0644, twl4030_bci_mode_show, + twl4030_bci_mode_store); + static int twl4030_charger_get_current(void) { int curr; @@ -420,11 +816,7 @@ static int twl4030_charger_get_current(void) if (ret) return ret; - ret = (curr * 16618 - 850 * 10000) / 10; - if (bcictl1 & TWL4030_CGAIN) - ret *= 2; - - return ret; + return regval2ua(curr, bcictl1 & TWL4030_CGAIN); } /* @@ -476,6 +868,17 @@ static int twl4030_bci_get_property(struct power_supply *psy, is_charging = state & TWL4030_MSTATEC_USB; else is_charging = state & TWL4030_MSTATEC_AC; + if (!is_charging) { + u8 s; + twl4030_bci_read(TWL4030_BCIMDEN, &s); + if (psy->desc->type == POWER_SUPPLY_TYPE_USB) + is_charging = s & 1; + else + is_charging = s & 2; + if (is_charging) + /* A little white lie */ + state = TWL4030_MSTATEC_QUICK1; + } switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -574,20 +977,31 @@ static const struct power_supply_desc twl4030_bci_usb_desc = { .get_property = twl4030_bci_get_property, }; -static int __init twl4030_bci_probe(struct platform_device *pdev) +static int twl4030_bci_probe(struct platform_device *pdev) { struct twl4030_bci *bci; const struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; int ret; u32 reg; - bci = kzalloc(sizeof(*bci), GFP_KERNEL); + bci = devm_kzalloc(&pdev->dev, sizeof(*bci), GFP_KERNEL); if (bci == NULL) return -ENOMEM; if (!pdata) pdata = twl4030_bci_parse_dt(&pdev->dev); + bci->ichg_eoc = 80100; /* Stop charging when current drops to here */ + bci->ichg_lo = 241000; /* Low threshold */ + bci->ichg_hi = 500000; /* High threshold */ + bci->ac_cur = 500000; /* 500mA */ + if (allow_usb) + bci->usb_cur_target = 500000; /* 500mA */ + else + bci->usb_cur_target = 100000; /* 100mA */ + bci->usb_mode = CHARGE_AUTO; + bci->ac_mode = CHARGE_AUTO; + bci->dev = &pdev->dev; bci->irq_chg = platform_get_irq(pdev, 0); bci->irq_bci = platform_get_irq(pdev, 1); @@ -596,47 +1010,46 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) ret = twl4030_is_battery_present(bci); if (ret) { dev_crit(&pdev->dev, "Battery was not detected:%d\n", ret); - goto fail_no_battery; + return ret; } platform_set_drvdata(pdev, bci); - bci->ac = power_supply_register(&pdev->dev, &twl4030_bci_ac_desc, - NULL); + bci->ac = devm_power_supply_register(&pdev->dev, &twl4030_bci_ac_desc, + NULL); if (IS_ERR(bci->ac)) { ret = PTR_ERR(bci->ac); dev_err(&pdev->dev, "failed to register ac: %d\n", ret); - goto fail_register_ac; + return ret; } - bci->usb_reg = regulator_get(bci->dev, "bci3v1"); - - bci->usb = power_supply_register(&pdev->dev, &twl4030_bci_usb_desc, - NULL); + bci->usb = devm_power_supply_register(&pdev->dev, &twl4030_bci_usb_desc, + NULL); if (IS_ERR(bci->usb)) { ret = PTR_ERR(bci->usb); dev_err(&pdev->dev, "failed to register usb: %d\n", ret); - goto fail_register_usb; + return ret; } - ret = request_threaded_irq(bci->irq_chg, NULL, + ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL, twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name, bci); if (ret < 0) { dev_err(&pdev->dev, "could not request irq %d, status %d\n", bci->irq_chg, ret); - goto fail_chg_irq; + return ret; } - ret = request_threaded_irq(bci->irq_bci, NULL, + ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL, twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci); if (ret < 0) { dev_err(&pdev->dev, "could not request irq %d, status %d\n", bci->irq_bci, ret); - goto fail_bci_irq; + return ret; } INIT_WORK(&bci->work, twl4030_bci_usb_work); + INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker); bci->usb_nb.notifier_call = twl4030_bci_usb_ncb; if (bci->dev->of_node) { @@ -644,9 +1057,13 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) phynode = of_find_compatible_node(bci->dev->of_node->parent, NULL, "ti,twl4030-usb"); - if (phynode) + if (phynode) { bci->transceiver = devm_usb_get_phy_by_node( bci->dev, phynode, &bci->usb_nb); + if (IS_ERR(bci->transceiver) && + PTR_ERR(bci->transceiver) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } } /* Enable interrupts now. */ @@ -656,7 +1073,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) TWL4030_INTERRUPTS_BCIIMR1A); if (ret < 0) { dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret); - goto fail_unmask_interrupts; + return ret; } reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV); @@ -665,8 +1082,23 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) if (ret < 0) dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret); - twl4030_charger_enable_ac(true); - twl4030_charger_enable_usb(bci, true); + twl4030_charger_update_current(bci); + if (device_create_file(&bci->usb->dev, &dev_attr_max_current)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + if (device_create_file(&bci->usb->dev, &dev_attr_mode)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + if (device_create_file(&bci->ac->dev, &dev_attr_mode)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + if (device_create_file(&bci->ac->dev, &dev_attr_max_current)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + + twl4030_charger_enable_ac(bci, true); + if (!IS_ERR_OR_NULL(bci->transceiver)) + twl4030_bci_usb_ncb(&bci->usb_nb, + bci->transceiver->last_event, + NULL); + else + twl4030_charger_enable_usb(bci, false); if (pdata) twl4030_charger_enable_backup(pdata->bb_uvolt, pdata->bb_uamp); @@ -674,42 +1106,26 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) twl4030_charger_enable_backup(0, 0); return 0; - -fail_unmask_interrupts: - free_irq(bci->irq_bci, bci); -fail_bci_irq: - free_irq(bci->irq_chg, bci); -fail_chg_irq: - power_supply_unregister(bci->usb); -fail_register_usb: - power_supply_unregister(bci->ac); -fail_register_ac: -fail_no_battery: - kfree(bci); - - return ret; } static int __exit twl4030_bci_remove(struct platform_device *pdev) { struct twl4030_bci *bci = platform_get_drvdata(pdev); - twl4030_charger_enable_ac(false); + twl4030_charger_enable_ac(bci, false); twl4030_charger_enable_usb(bci, false); twl4030_charger_enable_backup(0, 0); + device_remove_file(&bci->usb->dev, &dev_attr_max_current); + device_remove_file(&bci->usb->dev, &dev_attr_mode); + device_remove_file(&bci->ac->dev, &dev_attr_max_current); + device_remove_file(&bci->ac->dev, &dev_attr_mode); /* mask interrupts */ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, TWL4030_INTERRUPTS_BCIIMR1A); twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, TWL4030_INTERRUPTS_BCIIMR2A); - free_irq(bci->irq_bci, bci); - free_irq(bci->irq_chg, bci); - power_supply_unregister(bci->usb); - power_supply_unregister(bci->ac); - kfree(bci); - return 0; } @@ -720,14 +1136,14 @@ static const struct of_device_id twl_bci_of_match[] = { MODULE_DEVICE_TABLE(of, twl_bci_of_match); static struct platform_driver twl4030_bci_driver = { + .probe = twl4030_bci_probe, .driver = { .name = "twl4030_bci", .of_match_table = of_match_ptr(twl_bci_of_match), }, .remove = __exit_p(twl4030_bci_remove), }; - -module_platform_driver_probe(twl4030_bci_driver, twl4030_bci_probe); +module_platform_driver(twl4030_bci_driver); MODULE_AUTHOR("GraÅžvydas Ignotas"); MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver"); |