]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/power/ab8500_charger.c
pm2301: Provide u9540 support for the pm2301 charger
[mirror_ubuntu-bionic-kernel.git] / drivers / power / ab8500_charger.c
index 3be9c0ee3fc58a2c016cb9509ceb316c54312b5f..43ec82ba427589236a46beb3f379eee514fd3b6c 100644 (file)
@@ -31,6 +31,7 @@
 #include <linux/mfd/abx500/ab8500-gpadc.h>
 #include <linux/mfd/abx500/ux500_chargalg.h>
 #include <linux/usb/otg.h>
+#include <linux/mutex.h>
 
 /* Charger constants */
 #define NO_PW_CONN                     0
 #define MAIN_CH_NOK                    0x01
 #define VBUS_DET                       0x80
 
+#define MAIN_CH_STATUS2_MAINCHGDROP            0x80
+#define MAIN_CH_STATUS2_MAINCHARGERDETDBNC     0x40
+#define USB_CH_VBUSDROP                                0x40
+#define USB_CH_VBUSDETDBNC                     0x01
+
 /* UsbLineStatus register bit masks */
 #define AB8500_USB_LINK_STATUS         0x78
 #define AB8500_STD_HOST_SUSP           0x18
 /* Lowest charger voltage is 3.39V -> 0x4E */
 #define LOW_VOLT_REG                   0x4E
 
+/* Step up/down delay in us */
+#define STEP_UDELAY                    1000
+
+/* Wait for enumeration before charging in ms */
+#define WAIT_FOR_USB_ENUMERATION       5 * 1000
+
+#define CHARGER_STATUS_POLL 10 /* in ms */
+
 /* UsbLineStatus register - usb types */
 enum ab8500_charger_link_status {
        USB_STAT_NOT_CONFIGURED,
@@ -97,6 +111,13 @@ enum ab8500_charger_link_status {
        USB_STAT_HM_IDGND,
        USB_STAT_RESERVED,
        USB_STAT_NOT_VALID_LINK,
+       USB_STAT_PHY_EN,
+       USB_STAT_SUP_NO_IDGND_VBUS,
+       USB_STAT_SUP_IDGND_VBUS,
+       USB_STAT_CHARGER_LINE_1,
+       USB_STAT_CARKIT_1,
+       USB_STAT_CARKIT_2,
+       USB_STAT_ACA_DOCK_CHARGER,
 };
 
 enum ab8500_usb_state {
@@ -149,6 +170,7 @@ struct ab8500_charger_info {
        int charger_voltage;
        int cv_active;
        bool wd_expired;
+       int charger_current;
 };
 
 struct ab8500_charger_event_flags {
@@ -182,11 +204,12 @@ struct ab8500_charger_usb_state {
  *                     charger is enabled
  * @vbat               Battery voltage
  * @old_vbat           Previously measured battery voltage
+ * @usb_device_is_unrecognised USB device is unrecognised by the hardware
  * @autopower          Indicate if we should have automatic pwron after pwrloss
  * @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
@@ -200,6 +223,11 @@ struct ab8500_charger_usb_state {
  * @check_usbchgnotok_work:    Work for checking USB charger not ok status
  * @kick_wd_work:              Work for kicking the charger watchdog in case
  *                             of ABB rev 1.* due to the watchog logic bug
+ * @attach_work:               Work for checking the usb enumeration
+ * @ac_charger_attached_work:  Work for checking if AC charger is still
+ *                             connected
+ * @usb_charger_attached_work: Work for checking if USB charger is still
+ *                             connected
  * @ac_work:                   Work for checking AC charger connection
  * @detect_usb_type_work:      Work for detecting the USB type connected
  * @usb_link_status_work:      Work for checking the new USB link status
@@ -208,6 +236,7 @@ struct ab8500_charger_usb_state {
  *                             Work for checking Main thermal status
  * @check_usb_thermal_prot_work:
  *                             Work for checking USB thermal status
+ * @charger_attached_mutex:    For controlling the wakelock
  */
 struct ab8500_charger {
        struct device *dev;
@@ -219,11 +248,12 @@ struct ab8500_charger {
        bool vddadc_en_usb;
        int vbat;
        int old_vbat;
+       bool usb_device_is_unrecognised;
        bool autopower;
        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;
@@ -236,6 +266,9 @@ struct ab8500_charger {
        struct delayed_work check_hw_failure_work;
        struct delayed_work check_usbchgnotok_work;
        struct delayed_work kick_wd_work;
+       struct delayed_work attach_work;
+       struct delayed_work ac_charger_attached_work;
+       struct delayed_work usb_charger_attached_work;
        struct work_struct ac_work;
        struct work_struct detect_usb_type_work;
        struct work_struct usb_link_status_work;
@@ -244,6 +277,7 @@ struct ab8500_charger {
        struct work_struct check_usb_thermal_prot_work;
        struct usb_phy *usb_phy;
        struct notifier_block nb;
+       struct mutex charger_attached_mutex;
 };
 
 /* AC properties */
@@ -267,20 +301,19 @@ static enum power_supply_property ab8500_charger_usb_props[] = {
        POWER_SUPPLY_PROP_CURRENT_NOW,
 };
 
-/**
- * ab8500_power_loss_handling - set how we handle powerloss.
- * @di:                pointer to the ab8500_charger structure
- *
- * Magic nummbers are from STE HW department.
+/*
+ * Function for enabling and disabling sw fallback mode
+ * should always be disabled when no charger is connected.
  */
-static void ab8500_power_loss_handling(struct ab8500_charger *di)
+static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di,
+               bool fallback)
 {
        u8 reg;
        int ret;
 
-       dev_dbg(di->dev, "Autopower : %d\n", di->autopower);
+       dev_dbg(di->dev, "SW Fallback: %d\n", fallback);
 
-       /* read the autopower register */
+       /* read the register containing fallback bit */
        ret = abx500_get_register_interruptible(di->dev, 0x15, 0x00, &reg);
        if (ret) {
                dev_err(di->dev, "%d write failed\n", __LINE__);
@@ -294,12 +327,12 @@ static void ab8500_power_loss_handling(struct ab8500_charger *di)
                return;
        }
 
-       if (di->autopower)
+       if (fallback)
                reg |= 0x8;
        else
                reg &= ~0x8;
 
-       /* write back the changed value to autopower reg */
+       /* write back the changed fallback bit value to register */
        ret = abx500_set_register_interruptible(di->dev, 0x15, 0x00, reg);
        if (ret) {
                dev_err(di->dev, "%d write failed\n", __LINE__);
@@ -329,12 +362,12 @@ static void ab8500_power_supply_changed(struct ab8500_charger *di,
                    !di->ac.charger_connected &&
                    di->autopower) {
                        di->autopower = false;
-                       ab8500_power_loss_handling(di);
+                       ab8500_enable_disable_sw_fallback(di, false);
                } else if (!di->autopower &&
                           (di->ac.charger_connected ||
                            di->usb.charger_connected)) {
                        di->autopower = true;
-                       ab8500_power_loss_handling(di);
+                       ab8500_enable_disable_sw_fallback(di, true);
                }
        }
        power_supply_changed(psy);
@@ -347,6 +380,19 @@ static void ab8500_charger_set_usb_connected(struct ab8500_charger *di,
                dev_dbg(di->dev, "USB connected:%i\n", connected);
                di->usb.charger_connected = connected;
                sysfs_notify(&di->usb_chg.psy.dev->kobj, NULL, "present");
+
+               if (connected) {
+                       mutex_lock(&di->charger_attached_mutex);
+                       mutex_unlock(&di->charger_attached_mutex);
+
+                       queue_delayed_work(di->charger_wq,
+                                          &di->usb_charger_attached_work,
+                                          HZ);
+               } else {
+                       cancel_delayed_work_sync(&di->usb_charger_attached_work);
+                       mutex_lock(&di->charger_attached_mutex);
+                       mutex_unlock(&di->charger_attached_mutex);
+               }
        }
 }
 
@@ -554,24 +600,30 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di)
  * Returns error code in case of failure else 0 on success
  */
 static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
-       enum ab8500_charger_link_status link_status)
+               enum ab8500_charger_link_status link_status)
 {
        int ret = 0;
 
+       di->usb_device_is_unrecognised = false;
+
        switch (link_status) {
        case USB_STAT_STD_HOST_NC:
        case USB_STAT_STD_HOST_C_NS:
        case USB_STAT_STD_HOST_C_S:
                dev_dbg(di->dev, "USB Type - Standard host is "
-                       "detected through USB driver\n");
+                               "detected through USB driver\n");
                di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09;
                break;
        case USB_STAT_HOST_CHG_HS_CHIRP:
                di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+               dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
+                               di->max_usb_in_curr);
                break;
        case USB_STAT_HOST_CHG_HS:
        case USB_STAT_ACA_RID_C_HS:
                di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9;
+               dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
+                               di->max_usb_in_curr);
                break;
        case USB_STAT_ACA_RID_A:
                /*
@@ -579,6 +631,8 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
                 * can consume (300mA). Closest level is 1100mA
                 */
                di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1;
+               dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
+                               di->max_usb_in_curr);
                break;
        case USB_STAT_ACA_RID_B:
                /*
@@ -586,34 +640,56 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
                 * 100mA for potential accessory). Closest level is 1300mA
                 */
                di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3;
+               dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
+                               di->max_usb_in_curr);
                break;
-       case USB_STAT_DEDICATED_CHG:
        case USB_STAT_HOST_CHG_NM:
-       case USB_STAT_ACA_RID_C_HS_CHIRP:
+       case USB_STAT_DEDICATED_CHG:
        case USB_STAT_ACA_RID_C_NM:
+       case USB_STAT_ACA_RID_C_HS_CHIRP:
                di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5;
+               dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
+                               di->max_usb_in_curr);
                break;
-       case USB_STAT_RESERVED:
-               /*
-                * This state is used to indicate that VBUS has dropped below
-                * the detection level 4 times in a row. This is due to the
-                * charger output current is set to high making the charger
-                * voltage collapse. This have to be propagated through to
-                * chargalg. This is done using the property
-                * POWER_SUPPLY_PROP_CURRENT_AVG = 1
-                */
-               di->flags.vbus_collapse = true;
-               dev_dbg(di->dev, "USB Type - USB_STAT_RESERVED "
-                       "VBUS has collapsed\n");
-               ret = -1;
-               break;
-       case USB_STAT_HM_IDGND:
        case USB_STAT_NOT_CONFIGURED:
+               if (di->vbus_detected) {
+                       di->usb_device_is_unrecognised = true;
+                       dev_dbg(di->dev, "USB Type - Legacy charger.\n");
+                       di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5;
+                       break;
+               }
+       case USB_STAT_HM_IDGND:
        case USB_STAT_NOT_VALID_LINK:
                dev_err(di->dev, "USB Type - Charging not allowed\n");
                di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
                ret = -ENXIO;
                break;
+       case USB_STAT_RESERVED:
+               if (is_ab8500(di->parent)) {
+                       di->flags.vbus_collapse = true;
+                       dev_err(di->dev, "USB Type - USB_STAT_RESERVED "
+                                               "VBUS has collapsed\n");
+                       ret = -ENXIO;
+                       break;
+               }
+               if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+                       dev_dbg(di->dev, "USB Type - Charging not allowed\n");
+                       di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+                       dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
+                                       link_status, di->max_usb_in_curr);
+                       ret = -ENXIO;
+                       break;
+               }
+               break;
+       case USB_STAT_CARKIT_1:
+       case USB_STAT_CARKIT_2:
+       case USB_STAT_ACA_DOCK_CHARGER:
+       case USB_STAT_CHARGER_LINE_1:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+               dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
+                               di->max_usb_in_curr);
+               break;
+
        default:
                dev_err(di->dev, "USB Type - Unknown\n");
                di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
@@ -645,8 +721,14 @@ static int ab8500_charger_read_usb_type(struct ab8500_charger *di)
                dev_err(di->dev, "%s ab8500 read failed\n", __func__);
                return ret;
        }
-       ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
-               AB8500_USB_LINE_STAT_REG, &val);
+       if (is_ab8500(di->parent)) {
+               ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+                               AB8500_USB_LINE_STAT_REG, &val);
+       } else {
+               if (is_ab9540(di->parent) || is_ab8505(di->parent))
+                       ret = abx500_get_register_interruptible(di->dev,
+                               AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val);
+       }
        if (ret < 0) {
                dev_err(di->dev, "%s ab8500 read failed\n", __func__);
                return ret;
@@ -686,8 +768,13 @@ static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
                        dev_err(di->dev, "%s ab8500 read failed\n", __func__);
                        return ret;
                }
-               ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
-                       AB8500_USB_LINE_STAT_REG, &val);
+
+               if (is_ab8500(di->parent))
+                       ret = abx500_get_register_interruptible(di->dev,
+                               AB8500_USB, AB8500_USB_LINE_STAT_REG, &val);
+               else
+                       ret = abx500_get_register_interruptible(di->dev,
+                               AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val);
                if (ret < 0) {
                        dev_err(di->dev, "%s ab8500 read failed\n", __func__);
                        return ret;
@@ -935,6 +1022,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 +1115,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 +1133,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 +1260,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 +1276,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 +1345,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 +1452,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 +1553,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 +1562,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;
        }
 
@@ -1612,6 +1791,84 @@ static void ab8500_charger_ac_work(struct work_struct *work)
        sysfs_notify(&di->ac_chg.psy.dev->kobj, NULL, "present");
 }
 
+static void ab8500_charger_usb_attached_work(struct work_struct *work)
+{
+       struct ab8500_charger *di = container_of(work,
+                                                struct ab8500_charger,
+                                                usb_charger_attached_work.work);
+       int usbch = (USB_CH_VBUSDROP | USB_CH_VBUSDETDBNC);
+       int ret, i;
+       u8 statval;
+
+       for (i = 0; i < 10; i++) {
+               ret = abx500_get_register_interruptible(di->dev,
+                                                       AB8500_CHARGER,
+                                                       AB8500_CH_USBCH_STAT1_REG,
+                                                       &statval);
+               if (ret < 0) {
+                       dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+                       goto reschedule;
+               }
+               if ((statval & usbch) != usbch)
+                       goto reschedule;
+
+               msleep(CHARGER_STATUS_POLL);
+       }
+
+       ab8500_charger_usb_en(&di->usb_chg, 0, 0, 0);
+
+       mutex_lock(&di->charger_attached_mutex);
+       mutex_unlock(&di->charger_attached_mutex);
+
+       return;
+
+reschedule:
+       queue_delayed_work(di->charger_wq,
+                          &di->usb_charger_attached_work,
+                          HZ);
+}
+
+static void ab8500_charger_ac_attached_work(struct work_struct *work)
+{
+
+       struct ab8500_charger *di = container_of(work,
+                                                struct ab8500_charger,
+                                                ac_charger_attached_work.work);
+       int mainch = (MAIN_CH_STATUS2_MAINCHGDROP |
+                     MAIN_CH_STATUS2_MAINCHARGERDETDBNC);
+       int ret, i;
+       u8 statval;
+
+       for (i = 0; i < 10; i++) {
+               ret = abx500_get_register_interruptible(di->dev,
+                                                       AB8500_CHARGER,
+                                                       AB8500_CH_STATUS2_REG,
+                                                       &statval);
+               if (ret < 0) {
+                       dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+                       goto reschedule;
+               }
+
+               if ((statval & mainch) != mainch)
+                       goto reschedule;
+
+               msleep(CHARGER_STATUS_POLL);
+       }
+
+       ab8500_charger_ac_en(&di->ac_chg, 0, 0, 0);
+       queue_work(di->charger_wq, &di->ac_work);
+
+       mutex_lock(&di->charger_attached_mutex);
+       mutex_unlock(&di->charger_attached_mutex);
+
+       return;
+
+reschedule:
+       queue_delayed_work(di->charger_wq,
+                          &di->ac_charger_attached_work,
+                          HZ);
+}
+
 /**
  * ab8500_charger_detect_usb_type_work() - work to detect USB type
  * @work:      Pointer to the work_struct structure
@@ -1669,6 +1926,29 @@ static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
        }
 }
 
+/**
+ * ab8500_charger_usb_link_attach_work() - delayd work to detect USB type
+ * @work:      pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+static void ab8500_charger_usb_link_attach_work(struct work_struct *work)
+{
+       struct ab8500_charger *di =
+               container_of(work, struct ab8500_charger, attach_work.work);
+       int ret;
+
+       /* Update maximum input current if USB enumeration is not detected */
+       if (!di->usb.charger_online) {
+               ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
+               if (ret)
+                       return;
+       }
+
+       ab8500_charger_set_usb_connected(di, true);
+       ab8500_power_supply_changed(di, &di->usb_chg.psy);
+}
+
 /**
  * ab8500_charger_usb_link_status_work() - work to detect USB type
  * @work:      pointer to the work_struct structure
@@ -1695,23 +1975,29 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work)
                di->vbus_detected = 0;
                ab8500_charger_set_usb_connected(di, false);
                ab8500_power_supply_changed(di, &di->usb_chg.psy);
-       } else {
-               di->vbus_detected = 1;
-               ret = ab8500_charger_read_usb_type(di);
-               if (!ret) {
-                       /* Update maximum input current */
-                       ret = ab8500_charger_set_vbus_in_curr(di,
-                                       di->max_usb_in_curr);
-                       if (ret)
-                               return;
+               return;
+       }
 
-                       ab8500_charger_set_usb_connected(di, true);
-                       ab8500_power_supply_changed(di, &di->usb_chg.psy);
-               } else if (ret == -ENXIO) {
-                       /* No valid charger type detected */
-                       ab8500_charger_set_usb_connected(di, false);
-                       ab8500_power_supply_changed(di, &di->usb_chg.psy);
+       di->vbus_detected = 1;
+       ret = ab8500_charger_read_usb_type(di);
+       if (!ret) {
+               if (di->usb_device_is_unrecognised) {
+                       dev_dbg(di->dev,
+                               "Potential Legacy Charger device. "
+                               "Delay work for %d msec for USB enum "
+                               "to finish",
+                               WAIT_FOR_USB_ENUMERATION);
+                       queue_delayed_work(di->charger_wq,
+                                          &di->attach_work,
+                                          msecs_to_jiffies(WAIT_FOR_USB_ENUMERATION));
+               } else {
+                       queue_delayed_work(di->charger_wq,
+                                          &di->attach_work, 0);
                }
+       } else if (ret == -ENXIO) {
+               /* No valid charger type detected */
+               ab8500_charger_set_usb_connected(di, false);
+               ab8500_power_supply_changed(di, &di->usb_chg.psy);
        }
 }
 
@@ -1892,6 +2178,10 @@ static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di)
        dev_dbg(di->dev, "Main charger unplugged\n");
        queue_work(di->charger_wq, &di->ac_work);
 
+       cancel_delayed_work_sync(&di->ac_charger_attached_work);
+       mutex_lock(&di->charger_attached_mutex);
+       mutex_unlock(&di->charger_attached_mutex);
+
        return IRQ_HANDLED;
 }
 
@@ -1909,6 +2199,11 @@ static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di)
        dev_dbg(di->dev, "Main charger plugged\n");
        queue_work(di->charger_wq, &di->ac_work);
 
+       mutex_lock(&di->charger_attached_mutex);
+       mutex_unlock(&di->charger_attached_mutex);
+       queue_delayed_work(di->charger_wq,
+                          &di->ac_charger_attached_work,
+                          HZ);
        return IRQ_HANDLED;
 }
 
@@ -2148,6 +2443,7 @@ static int ab8500_charger_ac_get_property(struct power_supply *psy,
        union power_supply_propval *val)
 {
        struct ab8500_charger *di;
+       int ret;
 
        di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy));
 
@@ -2169,7 +2465,10 @@ static int ab8500_charger_ac_get_property(struct power_supply *psy,
                val->intval = di->ac.charger_connected;
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
-               di->ac.charger_voltage = ab8500_charger_get_ac_voltage(di);
+               ret = ab8500_charger_get_ac_voltage(di);
+               if (ret >= 0)
+                       di->ac.charger_voltage = ret;
+               /* On error, use previous value */
                val->intval = di->ac.charger_voltage * 1000;
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_AVG:
@@ -2181,7 +2480,10 @@ static int ab8500_charger_ac_get_property(struct power_supply *psy,
                val->intval = di->ac.cv_active;
                break;
        case POWER_SUPPLY_PROP_CURRENT_NOW:
-               val->intval = ab8500_charger_get_ac_current(di) * 1000;
+               ret = ab8500_charger_get_ac_current(di);
+               if (ret >= 0)
+                       di->ac.charger_current = ret;
+               val->intval = di->ac.charger_current * 1000;
                break;
        default:
                return -EINVAL;
@@ -2208,6 +2510,7 @@ static int ab8500_charger_usb_get_property(struct power_supply *psy,
        union power_supply_propval *val)
 {
        struct ab8500_charger *di;
+       int ret;
 
        di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy));
 
@@ -2231,7 +2534,9 @@ static int ab8500_charger_usb_get_property(struct power_supply *psy,
                val->intval = di->usb.charger_connected;
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
-               di->usb.charger_voltage = ab8500_charger_get_vbus_voltage(di);
+               ret = ab8500_charger_get_vbus_voltage(di);
+               if (ret >= 0)
+                       di->usb.charger_voltage = ret;
                val->intval = di->usb.charger_voltage * 1000;
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_AVG:
@@ -2243,7 +2548,10 @@ static int ab8500_charger_usb_get_property(struct power_supply *psy,
                val->intval = di->usb.cv_active;
                break;
        case POWER_SUPPLY_PROP_CURRENT_NOW:
-               val->intval = ab8500_charger_get_usb_current(di) * 1000;
+               ret = ab8500_charger_get_usb_current(di);
+               if (ret >= 0)
+                       di->usb.charger_current = ret;
+               val->intval = di->usb.charger_current * 1000;
                break;
        case POWER_SUPPLY_PROP_CURRENT_AVG:
                /*
@@ -2359,8 +2667,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;
@@ -2509,9 +2817,6 @@ static int ab8500_charger_remove(struct platform_device *pdev)
                free_irq(irq, di);
        }
 
-       /* disable the regulator */
-       regulator_put(di->regu);
-
        /* Backup battery voltage and current disable */
        ret = abx500_mask_and_set_register_interruptible(di->dev,
                AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0);
@@ -2525,8 +2830,11 @@ static int ab8500_charger_remove(struct platform_device *pdev)
        destroy_workqueue(di->charger_wq);
 
        flush_scheduled_work();
-       power_supply_unregister(&di->usb_chg.psy);
-       power_supply_unregister(&di->ac_chg.psy);
+       if(di->usb_chg.enabled)
+               power_supply_unregister(&di->usb_chg.psy);
+       if(di->ac_chg.enabled)
+               power_supply_unregister(&di->ac_chg.psy);
+
        platform_set_drvdata(pdev, NULL);
 
        return 0;
@@ -2541,32 +2849,31 @@ 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;
+       int irq, i, charger_status, ret = 0, ch_stat;
 
        di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
        if (!di) {
                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;
@@ -2595,6 +2902,7 @@ static int ab8500_charger_probe(struct platform_device *pdev)
                ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
        di->ac_chg.max_out_curr = ab8500_charger_current_map[
                ARRAY_SIZE(ab8500_charger_current_map) - 1];
+       di->ac_chg.enabled = di->pdata->ac_enabled;
 
        /* USB supply */
        /* power_supply base class */
@@ -2613,7 +2921,7 @@ static int ab8500_charger_probe(struct platform_device *pdev)
                ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
        di->usb_chg.max_out_curr = ab8500_charger_current_map[
                ARRAY_SIZE(ab8500_charger_current_map) - 1];
-
+       di->usb_chg.enabled = di->pdata->usb_enabled;
 
        /* Create a work queue for the charger */
        di->charger_wq =
@@ -2623,12 +2931,19 @@ static int ab8500_charger_probe(struct platform_device *pdev)
                return -ENOMEM;
        }
 
+       mutex_init(&di->charger_attached_mutex);
+
        /* Init work for HW failure check */
        INIT_DEFERRABLE_WORK(&di->check_hw_failure_work,
                ab8500_charger_check_hw_failure_work);
        INIT_DEFERRABLE_WORK(&di->check_usbchgnotok_work,
                ab8500_charger_check_usbchargernotok_work);
 
+       INIT_DELAYED_WORK(&di->ac_charger_attached_work,
+                         ab8500_charger_ac_attached_work);
+       INIT_DELAYED_WORK(&di->usb_charger_attached_work,
+                         ab8500_charger_usb_attached_work);
+
        /*
         * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
         * logic. That means we have to continously kick the charger
@@ -2644,6 +2959,9 @@ static int ab8500_charger_probe(struct platform_device *pdev)
        INIT_DEFERRABLE_WORK(&di->check_vbat_work,
                ab8500_charger_check_vbat_work);
 
+       INIT_DELAYED_WORK(&di->attach_work,
+               ab8500_charger_usb_link_attach_work);
+
        /* Init work for charger detection */
        INIT_WORK(&di->usb_link_status_work,
                ab8500_charger_usb_link_status_work);
@@ -2665,7 +2983,7 @@ static int ab8500_charger_probe(struct platform_device *pdev)
         * is a charger connected to avoid erroneous BTEMP_HIGH/LOW
         * interrupts during charging
         */
-       di->regu = regulator_get(di->dev, "vddadc");
+       di->regu = devm_regulator_get(di->dev, "vddadc");
        if (IS_ERR(di->regu)) {
                ret = PTR_ERR(di->regu);
                dev_err(di->dev, "failed to get vddadc regulator\n");
@@ -2677,21 +2995,25 @@ static int ab8500_charger_probe(struct platform_device *pdev)
        ret = ab8500_charger_init_hw_registers(di);
        if (ret) {
                dev_err(di->dev, "failed to initialize ABB registers\n");
-               goto free_regulator;
+               goto free_charger_wq;
        }
 
        /* Register AC charger class */
-       ret = power_supply_register(di->dev, &di->ac_chg.psy);
-       if (ret) {
-               dev_err(di->dev, "failed to register AC charger\n");
-               goto free_regulator;
+       if(di->ac_chg.enabled) {
+               ret = power_supply_register(di->dev, &di->ac_chg.psy);
+               if (ret) {
+                       dev_err(di->dev, "failed to register AC charger\n");
+                       goto free_charger_wq;
+               }
        }
 
        /* Register USB charger class */
-       ret = power_supply_register(di->dev, &di->usb_chg.psy);
-       if (ret) {
-               dev_err(di->dev, "failed to register USB charger\n");
-               goto free_ac;
+       if(di->usb_chg.enabled) {
+               ret = power_supply_register(di->dev, &di->usb_chg.psy);
+               if (ret) {
+                       dev_err(di->dev, "failed to register USB charger\n");
+                       goto free_ac;
+               }
        }
 
        di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2);
@@ -2717,7 +3039,6 @@ static int ab8500_charger_probe(struct platform_device *pdev)
        }
 
        if (charger_status & USB_PW_CONN) {
-               dev_dbg(di->dev, "VBUS Detect during startup\n");
                di->vbus_detected = true;
                di->vbus_detected_start = true;
                queue_work(di->charger_wq,
@@ -2742,6 +3063,23 @@ static int ab8500_charger_probe(struct platform_device *pdev)
 
        platform_set_drvdata(pdev, di);
 
+       mutex_lock(&di->charger_attached_mutex);
+
+       ch_stat = ab8500_charger_detect_chargers(di);
+
+       if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) {
+               queue_delayed_work(di->charger_wq,
+                                  &di->ac_charger_attached_work,
+                                  HZ);
+       }
+       if ((ch_stat & USB_PW_CONN) == USB_PW_CONN) {
+               queue_delayed_work(di->charger_wq,
+                                  &di->usb_charger_attached_work,
+                                  HZ);
+       }
+
+       mutex_unlock(&di->charger_attached_mutex);
+
        return ret;
 
 free_irq:
@@ -2755,11 +3093,11 @@ free_irq:
 put_usb_phy:
        usb_put_phy(di->usb_phy);
 free_usb:
-       power_supply_unregister(&di->usb_chg.psy);
+       if(di->usb_chg.enabled)
+               power_supply_unregister(&di->usb_chg.psy);
 free_ac:
-       power_supply_unregister(&di->ac_chg.psy);
-free_regulator:
-       regulator_put(di->regu);
+       if(di->ac_chg.enabled)
+               power_supply_unregister(&di->ac_chg.psy);
 free_charger_wq:
        destroy_workqueue(di->charger_wq);
        return ret;