ab8500_charger: Detect charger removal
authorLee Jones <lee.jones@linaro.org>
Fri, 11 Jan 2013 13:12:51 +0000 (13:12 +0000)
committerAnton Vorontsov <anton@enomsg.org>
Wed, 16 Jan 2013 01:35:12 +0000 (17:35 -0800)
Add two new work queues to provide USB and AC charger disconnect
detection.

Signed-off-by: Lee Jones <lee.jones@linaro.org>
Signed-off-by: Anton Vorontsov <anton@enomsg.org>
drivers/power/ab8500_charger.c

index a941e6a66996b3eb005bc8100df97c7f0d553097..24c4ae58dab519e4e7315d8c03908b649afbcbe4 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
@@ -82,6 +88,8 @@
 /* Step up/down delay in us */
 #define STEP_UDELAY                    1000
 
+#define CHARGER_STATUS_POLL 10 /* in ms */
+
 /* UsbLineStatus register - usb types */
 enum ab8500_charger_link_status {
        USB_STAT_NOT_CONFIGURED,
@@ -203,6 +211,10 @@ 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
+ * @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
@@ -211,6 +223,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;
@@ -239,6 +252,8 @@ 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 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;
@@ -247,6 +262,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 */
@@ -349,6 +365,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);
+               }
        }
 }
 
@@ -1706,6 +1735,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
@@ -1986,6 +2093,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;
 }
 
@@ -2003,6 +2114,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;
 }
 
@@ -2634,7 +2750,7 @@ 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) {
@@ -2713,12 +2829,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
@@ -2832,6 +2955,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: