Merge branch 'for-anton' of git://git.linaro.org/people/ljones/linux-3.0-ux500
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / drivers / power / ab8500_charger.c
index 3be9c0ee3fc58a2c016cb9509ceb316c54312b5f..e5755f0ba831a998f8d126c5683e5b41756af335 100644 (file)
@@ -79,6 +79,9 @@
 /* Lowest charger voltage is 3.39V -> 0x4E */
 #define LOW_VOLT_REG                   0x4E
 
+/* Step up/down delay in us */
+#define STEP_UDELAY                    1000
+
 /* UsbLineStatus register - usb types */
 enum ab8500_charger_link_status {
        USB_STAT_NOT_CONFIGURED,
@@ -186,7 +189,7 @@ struct ab8500_charger_usb_state {
  * @autopower_cfg      platform specific power config support for "pwron after pwrloss"
  * @parent:            Pointer to the struct ab8500
  * @gpadc:             Pointer to the struct gpadc
- * @bat:               Pointer to the abx500_bm platform data
+ * @bm:                Platform specific battery management information
  * @flags:             Structure for information about events triggered
  * @usb_state:         Structure for usb stack information
  * @ac_chg:            AC charger power supply
@@ -223,7 +226,7 @@ struct ab8500_charger {
        bool autopower_cfg;
        struct ab8500 *parent;
        struct ab8500_gpadc *gpadc;
-       struct abx500_bm_data *bat;
+       struct abx500_bm_data *bm;
        struct ab8500_charger_event_flags flags;
        struct ab8500_charger_usb_state usb_state;
        struct ux500_charger ac_chg;
@@ -935,6 +938,88 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
        return 0;
 }
 
+/**
+ * ab8500_charger_set_current() - set charger current
+ * @di:                pointer to the ab8500_charger structure
+ * @ich:       charger current, in mA
+ * @reg:       select what charger register to set
+ *
+ * Set charger current.
+ * There is no state machine in the AB to step up/down the charger
+ * current to avoid dips and spikes on MAIN, VBUS and VBAT when
+ * charging is started. Instead we need to implement
+ * this charger current step-up/down here.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_current(struct ab8500_charger *di,
+       int ich, int reg)
+{
+       int ret, i;
+       int curr_index, prev_curr_index, shift_value;
+       u8 reg_value;
+
+       switch (reg) {
+       case AB8500_MCH_IPT_CURLVL_REG:
+               shift_value = MAIN_CH_INPUT_CURR_SHIFT;
+               curr_index = ab8500_current_to_regval(ich);
+               break;
+       case AB8500_USBCH_IPT_CRNTLVL_REG:
+               shift_value = VBUS_IN_CURR_LIM_SHIFT;
+               curr_index = ab8500_vbus_in_curr_to_regval(ich);
+               break;
+       case AB8500_CH_OPT_CRNTLVL_REG:
+               shift_value = 0;
+               curr_index = ab8500_current_to_regval(ich);
+               break;
+       default:
+               dev_err(di->dev, "%s current register not valid\n", __func__);
+               return -ENXIO;
+       }
+
+       if (curr_index < 0) {
+               dev_err(di->dev, "requested current limit out-of-range\n");
+               return -ENXIO;
+       }
+
+       ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+               reg, &reg_value);
+       if (ret < 0) {
+               dev_err(di->dev, "%s read failed\n", __func__);
+               return ret;
+       }
+       prev_curr_index = (reg_value >> shift_value);
+
+       /* only update current if it's been changed */
+       if (prev_curr_index == curr_index)
+               return 0;
+
+       dev_dbg(di->dev, "%s set charger current: %d mA for reg: 0x%02x\n",
+               __func__, ich, reg);
+
+       if (prev_curr_index > curr_index) {
+               for (i = prev_curr_index - 1; i >= curr_index; i--) {
+                       ret = abx500_set_register_interruptible(di->dev,
+                               AB8500_CHARGER, reg, (u8) i << shift_value);
+                       if (ret) {
+                               dev_err(di->dev, "%s write failed\n", __func__);
+                               return ret;
+                       }
+                       usleep_range(STEP_UDELAY, STEP_UDELAY * 2);
+               }
+       } else {
+               for (i = prev_curr_index + 1; i <= curr_index; i++) {
+                       ret = abx500_set_register_interruptible(di->dev,
+                               AB8500_CHARGER, reg, (u8) i << shift_value);
+                       if (ret) {
+                               dev_err(di->dev, "%s write failed\n", __func__);
+                               return ret;
+                       }
+                       usleep_range(STEP_UDELAY, STEP_UDELAY * 2);
+               }
+       }
+       return ret;
+}
+
 /**
  * ab8500_charger_set_vbus_in_curr() - set VBUS input current limit
  * @di:                pointer to the ab8500_charger structure
@@ -946,12 +1031,10 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
 static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
                int ich_in)
 {
-       int ret;
-       int input_curr_index;
        int min_value;
 
        /* We should always use to lowest current limit */
-       min_value = min(di->bat->chg_params->usb_curr_max, ich_in);
+       min_value = min(di->bm->chg_params->usb_curr_max, ich_in);
 
        switch (min_value) {
        case 100:
@@ -966,19 +1049,38 @@ static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
                break;
        }
 
-       input_curr_index = ab8500_vbus_in_curr_to_regval(min_value);
-       if (input_curr_index < 0) {
-               dev_err(di->dev, "VBUS input current limit too high\n");
-               return -ENXIO;
-       }
+       return ab8500_charger_set_current(di, min_value,
+               AB8500_USBCH_IPT_CRNTLVL_REG);
+}
 
-       ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
-               AB8500_USBCH_IPT_CRNTLVL_REG,
-               input_curr_index << VBUS_IN_CURR_LIM_SHIFT);
-       if (ret)
-               dev_err(di->dev, "%s write failed\n", __func__);
+/**
+ * ab8500_charger_set_main_in_curr() - set main charger input current
+ * @di:                pointer to the ab8500_charger structure
+ * @ich_in:    input charger current, in mA
+ *
+ * Set main charger input current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_main_in_curr(struct ab8500_charger *di,
+       int ich_in)
+{
+       return ab8500_charger_set_current(di, ich_in,
+               AB8500_MCH_IPT_CURLVL_REG);
+}
 
-       return ret;
+/**
+ * ab8500_charger_set_output_curr() - set charger output current
+ * @di:                pointer to the ab8500_charger structure
+ * @ich_out:   output charger current, in mA
+ *
+ * Set charger output current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_output_curr(struct ab8500_charger *di,
+       int ich_out)
+{
+       return ab8500_charger_set_current(di, ich_out,
+               AB8500_CH_OPT_CRNTLVL_REG);
 }
 
 /**
@@ -1074,7 +1176,7 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
                volt_index = ab8500_voltage_to_regval(vset);
                curr_index = ab8500_current_to_regval(iset);
                input_curr_index = ab8500_current_to_regval(
-                       di->bat->chg_params->ac_curr_max);
+                       di->bm->chg_params->ac_curr_max);
                if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) {
                        dev_err(di->dev,
                                "Charger voltage or current too high, "
@@ -1090,23 +1192,24 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
                        return ret;
                }
                /* MainChInputCurr: current that can be drawn from the charger*/
-               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
-                       AB8500_MCH_IPT_CURLVL_REG,
-                       input_curr_index << MAIN_CH_INPUT_CURR_SHIFT);
+               ret = ab8500_charger_set_main_in_curr(di,
+                       di->bm->chg_params->ac_curr_max);
                if (ret) {
-                       dev_err(di->dev, "%s write failed\n", __func__);
+                       dev_err(di->dev, "%s Failed to set MainChInputCurr\n",
+                               __func__);
                        return ret;
                }
                /* ChOutputCurentLevel: protected output current */
-               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
-                       AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+               ret = ab8500_charger_set_output_curr(di, iset);
                if (ret) {
-                       dev_err(di->dev, "%s write failed\n", __func__);
+                       dev_err(di->dev, "%s "
+                               "Failed to set ChOutputCurentLevel\n",
+                               __func__);
                        return ret;
                }
 
                /* Check if VBAT overshoot control should be enabled */
-               if (!di->bat->enable_overshoot)
+               if (!di->bm->enable_overshoot)
                        overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N;
 
                /* Enable Main Charger */
@@ -1158,12 +1261,11 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
                                return ret;
                        }
 
-                       ret = abx500_set_register_interruptible(di->dev,
-                               AB8500_CHARGER,
-                               AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1);
+                       ret = ab8500_charger_set_output_curr(di, 0);
                        if (ret) {
-                               dev_err(di->dev,
-                                       "%s write failed\n", __func__);
+                               dev_err(di->dev, "%s "
+                                       "Failed to set ChOutputCurentLevel\n",
+                                       __func__);
                                return ret;
                        }
                } else {
@@ -1266,14 +1368,15 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
                        return ret;
                }
                /* ChOutputCurentLevel: protected output current */
-               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
-                       AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+               ret = ab8500_charger_set_output_curr(di, ich_out);
                if (ret) {
-                       dev_err(di->dev, "%s write failed\n", __func__);
+                       dev_err(di->dev, "%s "
+                               "Failed to set ChOutputCurentLevel\n",
+                               __func__);
                        return ret;
                }
                /* Check if VBAT overshoot control should be enabled */
-               if (!di->bat->enable_overshoot)
+               if (!di->bm->enable_overshoot)
                        overshoot = USB_CHG_NO_OVERSHOOT_ENA_N;
 
                /* Enable USB Charger */
@@ -1366,7 +1469,6 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger,
                int ich_out)
 {
        int ret;
-       int curr_index;
        struct ab8500_charger *di;
 
        if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
@@ -1376,18 +1478,11 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger,
        else
                return -ENXIO;
 
-       curr_index = ab8500_current_to_regval(ich_out);
-       if (curr_index < 0) {
-               dev_err(di->dev,
-                       "Charger current too high, "
-                       "charging not started\n");
-               return -ENXIO;
-       }
-
-       ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
-               AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+       ret = ab8500_charger_set_output_curr(di, ich_out);
        if (ret) {
-               dev_err(di->dev, "%s write failed\n", __func__);
+               dev_err(di->dev, "%s "
+                       "Failed to set ChOutputCurentLevel\n",
+                       __func__);
                return ret;
        }
 
@@ -2359,8 +2454,8 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
        ret = abx500_set_register_interruptible(di->dev,
                AB8500_RTC,
                AB8500_RTC_BACKUP_CHG_REG,
-               di->bat->bkup_bat_v |
-               di->bat->bkup_bat_i);
+               di->bm->bkup_bat_v |
+               di->bm->bkup_bat_i);
        if (ret) {
                dev_err(di->dev, "failed to setup backup battery charging\n");
                goto out;
@@ -2541,6 +2636,7 @@ static char *supply_interface[] = {
 static int ab8500_charger_probe(struct platform_device *pdev)
 {
        struct device_node *np = pdev->dev.of_node;
+       struct abx500_bm_data *plat = pdev->dev.platform_data;
        struct ab8500_charger *di;
        int irq, i, charger_status, ret = 0;
 
@@ -2549,24 +2645,22 @@ static int ab8500_charger_probe(struct platform_device *pdev)
                dev_err(&pdev->dev, "%s no mem for ab8500_charger\n", __func__);
                return -ENOMEM;
        }
-       di->bat = pdev->mfd_cell->platform_data;
-       if (!di->bat) {
-               if (np) {
-                       ret = bmdevs_of_probe(&pdev->dev, np, &di->bat);
-                       if (ret) {
-                               dev_err(&pdev->dev,
-                                       "failed to get battery information\n");
-                               return ret;
-                       }
-                       di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
-               } else {
-                       dev_err(&pdev->dev, "missing dt node for ab8500_charger\n");
-                       return -EINVAL;
+
+       if (!plat) {
+               dev_err(&pdev->dev, "no battery management data supplied\n");
+               return -EINVAL;
+       }
+       di->bm = plat;
+
+       if (np) {
+               ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+               if (ret) {
+                       dev_err(&pdev->dev, "failed to get battery information\n");
+                       return ret;
                }
-       } else {
-               dev_info(&pdev->dev, "falling back to legacy platform data\n");
+               di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
+       } else
                di->autopower_cfg = false;
-       }
 
        /* get parent data */
        di->dev = &pdev->dev;