twl4030_charger: remember to clean up USB regulator
[pandora-kernel.git] / drivers / power / twl4030_charger.c
index c832d32..575407a 100644 (file)
 #include <linux/usb/otg.h>
 #include <linux/ratelimit.h>
 #include <linux/regulator/machine.h>
+#include <linux/leds.h>
 
 #define TWL4030_BCIMSTATEC     0x02
 #define TWL4030_BCIICHG                0x08
 #define TWL4030_BCIVAC         0x0a
 #define TWL4030_BCIVBUS                0x0c
+#define TWL4030_BCIMFSTS3      0x0f
 #define TWL4030_BCIMFSTS4      0x10
 #define TWL4030_BCIMFKEY       0x11
 #define TWL4030_BCICTL1                0x23
 
 #define TWL4030_BCIAUTOWEN     BIT(5)
 #define TWL4030_CONFIG_DONE    BIT(4)
+#define TWL4030_CVENAC         BIT(2)
 #define TWL4030_BCIAUTOUSB     BIT(1)
 #define TWL4030_BCIAUTOAC      BIT(0)
 #define TWL4030_CGAIN          BIT(5)
 #define TWL4030_USBFASTMCHG    BIT(2)
 #define TWL4030_STS_VBUS       BIT(7)
 #define TWL4030_STS_USB_ID     BIT(2)
+#define TWL4030_STS_CHG                BIT(1)
 
 /* BCI interrupts */
 #define TWL4030_WOVF           BIT(0) /* Watchdog overflow */
 #define TWL4030_MSTATEC_COMPLETE4      0x0e
 
 #define TWL4030_KEY_IIREF              0xe7
+#define TWL4030_BATSTSMCHG             BIT(6)
+
+#define IRQ_CHECK_PERIOD       (3 * HZ)
+#define IRQ_CHECK_THRESHOLD    4
 
 static bool allow_usb = 1;
 module_param(allow_usb, bool, 0644);
@@ -88,6 +96,14 @@ struct twl4030_bci {
        enum power_supply_type  current_supply;
        struct regulator        *usb_reg;
        int                     usb_enabled;
+       int                     irq_had_charger;
+
+       unsigned long           irq_check_count_time;
+       int                     irq_check_count;
+       int                     irq_check_ac_disabled;
+
+       struct led_trigger      *charging_any_trig;
+       int                     was_charging_any;
 
        unsigned long           event;
        struct ratelimit_state  ratelimit;
@@ -235,21 +251,30 @@ static int twl4030_charger_enable_ac(bool enable)
 
 static int set_charge_current(struct twl4030_bci *bci, int new_current)
 {
-       u8 tmp, boot_bci_prev, cgain_set, cgain_clear;
-       int ret;
+       u8 val, boot_bci_prev, cgain_set, cgain_clear;
+       int ret, ret2;
+
+       ret = twl4030_bci_read(TWL4030_BCIMFSTS3, &val);
+       if (ret)
+               goto out_norestore;
+
+       if (!(val & TWL4030_BATSTSMCHG)) {
+               dev_err(bci->dev, "missing battery, can't change charge_current\n");
+               goto out_norestore;
+       }
 
        ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &boot_bci_prev,
                TWL4030_PM_MASTER_BOOT_BCI);
        if (ret)
-               goto out;
+               goto out_norestore;
 
        /* 
         * Stop automatic charging here, because charge current change
         * requires multiple register writes and CGAIN change requires
-        * automatic charge to be stopped.
+        * automatic charge to be stopped (and CV mode disabled too).
         */
        ret = twl4030_clear_set_boot_bci(
-               TWL4030_BCIAUTOAC | TWL4030_BCIAUTOUSB | 4, 0);
+               TWL4030_CVENAC | TWL4030_BCIAUTOAC | TWL4030_BCIAUTOUSB, 0);
        if (ret)
                goto out;
 
@@ -283,17 +308,21 @@ static int set_charge_current(struct twl4030_bci *bci, int new_current)
        if (ret)
                goto out;
 
-       ret = twl4030_bci_read(TWL4030_BCICTL1, &tmp);
-       if (ret != 0 || (tmp & TWL4030_CGAIN) != cgain_set) {
+       ret = twl4030_bci_read(TWL4030_BCICTL1, &val);
+       if (ret != 0 || (val & TWL4030_CGAIN) != cgain_set) {
                dev_err(bci->dev, "CGAIN change failed\n");
                goto out;
        }
 
-       ret = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, boot_bci_prev,
-               TWL4030_PM_MASTER_BOOT_BCI);
 out:
+       ret2 = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, boot_bci_prev,
+               TWL4030_PM_MASTER_BOOT_BCI);
+       if (ret2 != 0)
+               dev_err(bci->dev, "failed boot_bci restore: %d\n", ret2);
+
+out_norestore:
        if (ret != 0)
-               dev_err(bci->dev, "charge current change failed\n");
+               dev_err(bci->dev, "charge current change failed: %d\n", ret);
 
        return ret;
 }
@@ -304,11 +333,49 @@ out:
 static irqreturn_t twl4030_charger_interrupt(int irq, void *arg)
 {
        struct twl4030_bci *bci = arg;
+       int have_charger;
+       u8 hw_cond;
+       int ret;
+
+       ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hw_cond,
+                             TWL4030_PM_MASTER_STS_HW_CONDITIONS);
+       if (ret < 0) {
+               dev_err(bci->dev, "HW_CONDITIONS read failed: %d\n", ret);
+               goto out;
+       }
+
+       have_charger = (hw_cond & TWL4030_STS_CHG) ? 1 : 0;
+       if (have_charger == bci->irq_had_charger)
+               goto out;
+       bci->irq_had_charger = have_charger;
+
+       dev_dbg(bci->dev, "CHG_PRES irq, hw_cond %02x\n", hw_cond);
+
+       /*
+        * deal with rare mysterious issue of CHG_PRES changing states at ~4Hz
+        * without any charger connected or anything
+        */
+       if (time_before(jiffies, bci->irq_check_count_time + IRQ_CHECK_PERIOD)) {
+               bci->irq_check_count++;
+               if (have_charger && bci->irq_check_count > IRQ_CHECK_THRESHOLD) {
+                       dev_err(bci->dev, "spurious CHG_PRES irqs detected (%d), disabling charger\n",
+                               bci->irq_check_count);
+                       twl4030_charger_enable_ac(false);
+                       bci->irq_check_ac_disabled = true;
+               }
+       } else {
+               bci->irq_check_count_time = jiffies;
+               bci->irq_check_count = 1;
+               if (have_charger && bci->irq_check_ac_disabled) {
+                       twl4030_charger_enable_ac(true);
+                       bci->irq_check_ac_disabled = false;
+               }
+       }
 
-       dev_dbg(bci->dev, "CHG_PRES irq\n");
        power_supply_changed(&bci->ac);
        power_supply_changed(&bci->usb);
 
+out:
        return IRQ_HANDLED;
 }
 
@@ -356,11 +423,13 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg)
        if (irqs2 & TWL4030_ACCHGOV && __ratelimit(&bci->ratelimit))
                dev_crit(bci->dev, "Ac charger overvoltage\n");
 
+#if 0
        /* ack the interrupts */
        twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, irqs1,
                         TWL4030_INTERRUPTS_BCIISR1A);
        twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, irqs2,
                         TWL4030_INTERRUPTS_BCIISR2A);
+#endif
 
        return IRQ_HANDLED;
 }
@@ -533,7 +602,7 @@ static ssize_t store_charge_current(struct device *dev,
        if (ret)
                return ret;
 
-       if (bci->current_supply == POWER_SUPPLY_TYPE_MAINS)
+       if (psy->type == POWER_SUPPLY_TYPE_MAINS)
                bci->ac_current = new_current;
        else
                bci->usb_current = new_current;
@@ -600,7 +669,8 @@ static int twl4030_bci_get_property(struct power_supply *psy,
                                    union power_supply_propval *val)
 {
        struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent);
-       int is_charging;
+       int is_charging_any = 0;
+       int is_charging = 0;
        int state;
        int ret;
 
@@ -608,10 +678,22 @@ static int twl4030_bci_get_property(struct power_supply *psy,
        if (state < 0)
                return state;
 
-       if (psy->type == POWER_SUPPLY_TYPE_USB)
-               is_charging = state & TWL4030_MSTATEC_USB;
-       else
-               is_charging = state & TWL4030_MSTATEC_AC;
+       if (twl4030_bci_state_to_status(state) ==
+           POWER_SUPPLY_STATUS_CHARGING) {
+               is_charging_any =
+                       state & (TWL4030_MSTATEC_USB | TWL4030_MSTATEC_AC);
+               if (psy->type == POWER_SUPPLY_TYPE_USB)
+                       is_charging = state & TWL4030_MSTATEC_USB;
+               else
+                       is_charging = state & TWL4030_MSTATEC_AC;
+       }
+
+       if (is_charging_any != bci->was_charging_any) {
+               led_trigger_event(bci->charging_any_trig,
+                       is_charging_any ? LED_FULL : LED_OFF);
+               bci->was_charging_any = is_charging_any;
+       }
+
        if (is_charging && psy->type != bci->current_supply) {
                if (psy->type == POWER_SUPPLY_TYPE_USB)
                        set_charge_current(bci, bci->usb_current);
@@ -693,12 +775,17 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
        bci->irq_chg = platform_get_irq(pdev, 0);
        bci->irq_bci = platform_get_irq(pdev, 1);
        bci->ac_current = 860; /* ~1.2A */
-       bci->usb_current = 360; /* ~600mA */
+       bci->usb_current = 330; /* ~560mA */
+       bci->irq_had_charger = -1;
+       bci->irq_check_count_time = jiffies;
 
        platform_set_drvdata(pdev, bci);
 
        ratelimit_state_init(&bci->ratelimit, HZ, 2);
 
+       led_trigger_register_simple("twl4030_bci-charging",
+               &bci->charging_any_trig);
+
        bci->ac.name = "twl4030_ac";
        bci->ac.type = POWER_SUPPLY_TYPE_MAINS;
        bci->ac.properties = twl4030_charger_props;
@@ -808,7 +895,15 @@ fail_chg_irq:
        power_supply_unregister(&bci->usb);
 fail_register_usb:
        power_supply_unregister(&bci->ac);
+
+       if (bci->usb_reg) {
+               if (bci->usb_enabled)
+                       regulator_disable(bci->usb_reg);
+               regulator_put(bci->usb_reg);
+       }
+
 fail_register_ac:
+       led_trigger_unregister_simple(bci->charging_any_trig);
        platform_set_drvdata(pdev, NULL);
        kfree(bci);
 
@@ -839,6 +934,14 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
        free_irq(bci->irq_chg, bci);
        power_supply_unregister(&bci->usb);
        power_supply_unregister(&bci->ac);
+
+       if (bci->usb_reg) {
+               if (bci->usb_enabled)
+                       regulator_disable(bci->usb_reg);
+               regulator_put(bci->usb_reg);
+       }
+
+       led_trigger_unregister_simple(bci->charging_any_trig);
        platform_set_drvdata(pdev, NULL);
        kfree(bci);