diff options
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/twl4030_charger.c | 67 |
1 files changed, 61 insertions, 6 deletions
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index 68117ad23564..2c537ee11bbe 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c @@ -119,6 +119,18 @@ struct twl4030_bci { #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; }; @@ -257,6 +269,12 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci) } 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 */ @@ -391,6 +409,41 @@ static int twl4030_charger_update_current(struct twl4030_bci *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. */ @@ -451,6 +504,7 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) pm_runtime_put_autosuspend(bci->transceiver->dev); bci->usb_enabled = 0; } + bci->usb_cur = 0; } return ret; @@ -599,7 +653,7 @@ twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr, if (dev == &bci->ac->dev) bci->ac_cur = cur; else - bci->usb_cur = cur; + bci->usb_cur_target = cur; twl4030_charger_update_current(bci); return n; @@ -621,7 +675,7 @@ static ssize_t twl4030_bci_max_current_show(struct device *dev, cur = bci->ac_cur; } else { if (bci->ac_is_active) - cur = bci->usb_cur; + cur = bci->usb_cur_target; } if (cur < 0) { cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1); @@ -662,9 +716,9 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val, /* reset current on each 'plug' event */ if (allow_usb) - bci->usb_cur = 500000; + bci->usb_cur_target = 500000; else - bci->usb_cur = 100000; + bci->usb_cur_target = 100000; bci->event = val; schedule_work(&bci->work); @@ -927,9 +981,9 @@ static int twl4030_bci_probe(struct platform_device *pdev) bci->ichg_hi = 500000; /* High threshold */ bci->ac_cur = 500000; /* 500mA */ if (allow_usb) - bci->usb_cur = 500000; /* 500mA */ + bci->usb_cur_target = 500000; /* 500mA */ else - bci->usb_cur = 100000; /* 100mA */ + bci->usb_cur_target = 100000; /* 100mA */ bci->usb_mode = CHARGE_AUTO; bci->ac_mode = CHARGE_AUTO; @@ -980,6 +1034,7 @@ static int twl4030_bci_probe(struct platform_device *pdev) } 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) { |