aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/power/twl4030_charger.c67
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) {