twl4030_charger: Increase current carefully while watching voltage.
[firefly-linux-kernel-4.4.55.git] / drivers / power / twl4030_charger.c
index 68117ad23564828ae8fd97b5349caaf06fe6a10a..2c537ee11bbe74602a62394b8de8f4e9951b0718 100644 (file)
@@ -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) {