TWL4030/TPS65950 is a multi-function device with integrated charger,
which allows charging from AC or USB. This driver enables the
charger and provides several monitoring functions.

Signed-off-by: Grazvydas Ignotas <[email protected]>
---
For this driver to work, TWL4030-core needs to be patched to use
correct macros so that it registers twl4030_bci platform_device.
I'll send patches for this later.

 drivers/power/Kconfig           |    7 +
 drivers/power/Makefile          |    1 +
 drivers/power/twl4030_charger.c |  499 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 507 insertions(+), 0 deletions(-)
 create mode 100644 drivers/power/twl4030_charger.c

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index cea6cef..95d7e60 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -110,4 +110,11 @@ config CHARGER_PCF50633
        help
         Say Y to include support for NXP PCF50633 Main Battery Charger.
 
+config CHARGER_TWL4030
+       tristate "OMAP TWL4030 BCI charger driver"
+       depends on TWL4030_CORE
+       default y
+       help
+         Say Y here to enable support for TWL4030 Battery Charge Interface.
+
 endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index b96f29d..9cea9b5 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -29,3 +29,4 @@ obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o
 obj-$(CONFIG_BATTERY_DA9030)   += da9030_battery.o
 obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
 obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
+obj-$(CONFIG_CHARGER_TWL4030)  += twl4030_charger.o
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
new file mode 100644
index 0000000..604dd56
--- /dev/null
+++ b/drivers/power/twl4030_charger.c
@@ -0,0 +1,499 @@
+/*
+ * TWL4030/TPS65950 BCI (Battery Charger Interface) driver
+ *
+ * Copyright (C) 2009 Gražvydas Ignotas <[email protected]>
+ *
+ * based on twl4030_bci_battery.c by TI
+ * Copyright (C) 2008 Texas Instruments, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/i2c/twl4030.h>
+#include <linux/power_supply.h>
+
+#define REG_BCIMSTATEC         0x02
+#define REG_BCIICHG            0x08
+#define REG_BCIVAC             0x0a
+#define REG_BCIVBUS            0x0c
+#define REG_BCIMFSTS4          0x10
+#define REG_BCICTL1            0x23
+
+#define REG_BOOT_BCI           0x07
+#define REG_STS_HW_CONDITIONS  0x0f
+
+#define BCIAUTOWEN             0x20
+#define CONFIG_DONE            0x10
+#define CVENAC                 0x04
+#define BCIAUTOUSB             0x02
+#define BCIAUTOAC              0x01
+#define BCIMSTAT_MASK          0x3F
+#define STS_VBUS               0x80
+#define STS_CHG                        0x02
+#define STS_USB_ID             0x04
+#define CGAIN                  0x20
+#define USBFASTMCHG            0x04
+
+#define STATEC_USB             0x10
+#define STATEC_AC              0x20
+#define STATEC_STATUS_MASK     0x0f
+#define STATEC_QUICK1          0x02
+#define STATEC_QUICK7          0x07
+#define STATEC_COMPLETE1       0x0b
+#define STATEC_COMPLETE4       0x0e
+
+#define BCI_DELAY              100
+
+struct twl4030_bci_device_info {
+       struct power_supply     ac;
+       struct power_supply     usb;
+       struct delayed_work     bat_work;
+       bool                    started;
+};
+
+/*
+ * clear and set bits on an given register on a given module
+ */
+static int twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg)
+{
+       u8 val = 0;
+       int ret;
+
+       ret = twl4030_i2c_read_u8(mod_no, &val, reg);
+       if (ret)
+               return ret;
+
+       val &= ~clear;
+       val |= set;
+
+       return twl4030_i2c_write_u8(mod_no, val, reg);
+}
+
+static int twl4030bci_read_adc_val(u8 reg)
+{
+       int ret, temp;
+       u8 val;
+
+       /* read MSB */
+       ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, reg + 1);
+       if (ret)
+               return ret;
+
+       temp = (int)(val & 0x03) << 8;
+
+       /* read LSB */
+       ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val, reg);
+       if (ret)
+               return ret;
+
+       return temp | val;
+}
+
+static void twl4030bci_power_work(struct work_struct *work)
+{
+       struct twl4030_bci_device_info *di = container_of(work,
+               struct twl4030_bci_device_info, bat_work.work);
+
+       power_supply_changed(&di->ac);
+       power_supply_changed(&di->usb);
+}
+
+/*
+ * Attend to TWL4030 CHG_PRES (AC charger presence) events
+ */
+static irqreturn_t twl4030_charger_interrupt(int irq, void *_di)
+{
+       struct twl4030_bci_device_info *di = _di;
+
+#ifdef CONFIG_LOCKDEP
+       /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
+        * we don't want and can't tolerate.  Although it might be
+        * friendlier not to borrow this thread context...
+        */
+       local_irq_enable();
+#endif
+
+       schedule_delayed_work(&di->bat_work, msecs_to_jiffies(BCI_DELAY));
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * Enable/Disable AC Charge funtionality.
+ */
+static int twl4030_charger_enable_ac(bool enable)
+{
+       int ret;
+
+       if (enable) {
+               /* forcing the field BCIAUTOAC (BOOT_BCI[0) to 1 */
+               ret = twl4030_clear_set(TWL4030_MODULE_PM_MASTER, 0,
+                       CONFIG_DONE | BCIAUTOWEN | BCIAUTOAC,
+                       REG_BOOT_BCI);
+       } else {
+               /* forcing the field BCIAUTOAC (BOOT_BCI[0) to 0*/
+               ret = twl4030_clear_set(TWL4030_MODULE_PM_MASTER, BCIAUTOAC,
+                       CONFIG_DONE | BCIAUTOWEN,
+                       REG_BOOT_BCI);
+       }
+
+       return ret;
+}
+
+/*
+ * Check if VBUS power is present
+ */
+static int twl4030_charger_check_vbus(void)
+{
+       int ret;
+       u8 hwsts;
+
+       ret = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts,
+               REG_STS_HW_CONDITIONS);
+       if (ret) {
+               pr_err("twl4030_bci: error reading STS_HW_CONDITIONS\n");
+               return ret;
+       }
+
+       pr_debug("check_vbus: HW_CONDITIONS %02x\n", hwsts);
+
+       /* in case we also have STS_USB_ID, VBUS is driven by TWL itself */
+       if ((hwsts & STS_VBUS) && !(hwsts & STS_USB_ID))
+               return 1;
+
+       return 0;
+}
+
+/*
+ * Enable/Disable USB Charge funtionality.
+ */
+static int twl4030_charger_enable_usb(bool enable)
+{
+       int ret;
+
+       if (enable) {
+               /* Check for USB charger conneted */
+               ret = twl4030_charger_check_vbus();
+               if (ret < 0)
+                       return ret;
+
+               if (!ret)
+                       return -ENXIO;
+
+               /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */
+               ret = twl4030_clear_set(TWL4030_MODULE_PM_MASTER, 0,
+                       CONFIG_DONE | BCIAUTOWEN | BCIAUTOUSB,
+                       REG_BOOT_BCI);
+               if (ret)
+                       return ret;
+
+               /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */
+               ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, 0,
+                       USBFASTMCHG, REG_BCIMFSTS4);
+       } else {
+               ret = twl4030_clear_set(TWL4030_MODULE_PM_MASTER, BCIAUTOUSB,
+                       CONFIG_DONE | BCIAUTOWEN, REG_BOOT_BCI);
+       }
+
+       return ret;
+}
+
+/*
+ * Return voltage (valid while charging only)
+ * 10 bit ADC (0...0x3ff) scales to 0...6V
+ */
+static int twl4030_get_voltage(int reg)
+{
+       int ret = twl4030bci_read_adc_val(reg);
+       if (ret < 0)
+               return ret;
+
+       return 6000 * ret / 1023;
+}
+
+/*
+ * TI provided formulas:
+ * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 – 1) - 0.85
+ * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 – 1) - 1.7
+ * Here we use integer approximation of:
+ * CGAIN == 0: val * 1.6618 - 0.85
+ * CGAIN == 1: (val * 1.6618 - 0.85) * 2
+ */
+static int twl4030_charger_get_current(void)
+{
+       int curr;
+       int ret;
+       u8 bcictl1;
+
+       curr = twl4030bci_read_adc_val(REG_BCIICHG);
+       if (curr < 0)
+               return curr;
+
+       ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &bcictl1,
+               REG_BCICTL1);
+       if (ret)
+               return ret;
+
+       ret = (curr * 16618 - 850 * 10000) / 10000;
+       if (bcictl1 & CGAIN)
+               ret *= 2;
+
+       return ret;
+}
+
+/*
+ * Returns the main charge FSM state
+ * Or < 0 on failure.
+ */
+static int twl4030bci_state(void)
+{
+       int ret;
+       u8 state;
+
+       ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE,
+               &state, REG_BCIMSTATEC);
+       if (ret) {
+               pr_err("twl4030_bci: error reading BCIMSTATEC\n");
+               return ret;
+       }
+
+       pr_debug("state: %02x\n", state);
+
+       return state & BCIMSTAT_MASK;
+}
+
+static int twl4030_bci_state_to_status(int state)
+{
+       state &= STATEC_STATUS_MASK;
+       if (STATEC_QUICK1 <= state && state <= STATEC_QUICK7)
+               return POWER_SUPPLY_STATUS_CHARGING;
+       else if (STATEC_COMPLETE1 <= state && state <= STATEC_COMPLETE4)
+               return POWER_SUPPLY_STATUS_FULL;
+       else
+               return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static int twl4030_charger_get_property(struct power_supply *psy,
+                                       enum power_supply_property psp,
+                                       union power_supply_propval *val)
+{
+       int is_charging;
+       int voltage_reg;
+       int state;
+       int ret;
+
+       state = twl4030bci_state();
+       if (state < 0)
+               return state;
+
+       if (psy->type == POWER_SUPPLY_TYPE_USB) {
+               is_charging = state & STATEC_USB;
+               voltage_reg = REG_BCIVBUS;
+       } else {
+               is_charging = state & STATEC_AC;
+               voltage_reg = REG_BCIVAC;
+       }
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               if (is_charging)
+                       val->intval = twl4030_bci_state_to_status(state);
+               else
+                       val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               /* charging must be active for meaningful result */
+               if (!is_charging) {
+                       val->intval = 0;
+                       break;
+               }
+               ret = twl4030_get_voltage(voltage_reg);
+               if (ret < 0)
+                       return ret;
+               val->intval = ret;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               if (!is_charging) {
+                       val->intval = 0;
+                       break;
+               }
+               /* current measurement is shared between AC and USB */
+               ret = twl4030_charger_get_current();
+               if (ret < 0)
+                       return ret;
+               val->intval = ret;
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = is_charging &&
+                       twl4030_bci_state_to_status(state) !=
+                               POWER_SUPPLY_STATUS_NOT_CHARGING;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static enum power_supply_property twl4030_charger_props[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static struct twl4030_bci_device_info twl4030_bci = {
+       .ac = {
+               .name           = "twl4030_ac",
+               .type           = POWER_SUPPLY_TYPE_MAINS,
+               .properties     = twl4030_charger_props,
+               .num_properties = ARRAY_SIZE(twl4030_charger_props),
+               .get_property   = twl4030_charger_get_property,
+       },
+       .usb = {
+               .name           = "twl4030_usb",
+               .type           = POWER_SUPPLY_TYPE_USB,
+               .properties     = twl4030_charger_props,
+               .num_properties = ARRAY_SIZE(twl4030_charger_props),
+               .get_property   = twl4030_charger_get_property,
+       },
+};
+
+/*
+ * called by TWL4030 USB transceiver driver on USB_PRES interrupt.
+ */
+int twl4030charger_usb_en(int enable)
+{
+       if (twl4030_bci.started)
+               schedule_delayed_work(&twl4030_bci.bat_work,
+                       msecs_to_jiffies(BCI_DELAY));
+
+       return twl4030_charger_enable_usb(enable);
+}
+
+static int __devinit twl4030_bci_probe(struct platform_device *pdev)
+{
+       int irq;
+       int ret;
+
+       twl4030_charger_enable_ac(true);
+       twl4030_charger_enable_usb(true);
+
+       irq = platform_get_irq(pdev, 0);
+
+       /* CHG_PRES irq */
+       ret = request_irq(irq, twl4030_charger_interrupt,
+               0, pdev->name, &twl4030_bci);
+       if (ret) {
+               dev_err(&pdev->dev, "could not request irq %d, status %d\n",
+                       irq, ret);
+               goto fail_chg_irq;
+       }
+
+       ret = power_supply_register(&pdev->dev, &twl4030_bci.ac);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register ac: %d\n", ret);
+               goto fail_register_ac;
+       }
+
+       ret = power_supply_register(&pdev->dev, &twl4030_bci.usb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to register usb: %d\n", ret);
+               goto fail_register_usb;
+       }
+
+       platform_set_drvdata(pdev, &twl4030_bci);
+
+       INIT_DELAYED_WORK_DEFERRABLE(&twl4030_bci.bat_work,
+                       twl4030bci_power_work);
+       schedule_delayed_work(&twl4030_bci.bat_work,
+                       msecs_to_jiffies(BCI_DELAY));
+       twl4030_bci.started = true;
+
+       return 0;
+
+fail_register_usb:
+       power_supply_unregister(&twl4030_bci.ac);
+fail_register_ac:
+       free_irq(irq, &twl4030_bci);
+fail_chg_irq:
+       twl4030_charger_enable_ac(false);
+       twl4030_charger_enable_usb(false);
+
+       return ret;
+}
+
+static int __devexit twl4030_bci_remove(struct platform_device *pdev)
+{
+       struct twl4030_bci_device_info *di = platform_get_drvdata(pdev);
+       int irq = platform_get_irq(pdev, 0);
+
+       di->started = false;
+       twl4030_charger_enable_ac(false);
+       twl4030_charger_enable_usb(false);
+
+       free_irq(irq, di);
+
+       flush_scheduled_work();
+       power_supply_unregister(&di->ac);
+       power_supply_unregister(&di->usb);
+       platform_set_drvdata(pdev, NULL);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int twl4030_bci_suspend(struct platform_device *pdev,
+       pm_message_t state)
+{
+       /* flush all pending status updates */
+       flush_scheduled_work();
+       return 0;
+}
+
+static int twl4030_bci_resume(struct platform_device *pdev)
+{
+       struct twl4030_bci_device_info *di = platform_get_drvdata(pdev);
+
+       /* things may have changed while we were away */
+       schedule_delayed_work(&di->bat_work, msecs_to_jiffies(BCI_DELAY));
+       return 0;
+}
+#else
+#define twl4030_bci_suspend    NULL
+#define twl4030_bci_resume     NULL
+#endif /* CONFIG_PM */
+
+static struct platform_driver twl4030_bci_driver = {
+       .probe          = twl4030_bci_probe,
+       .remove         = __devexit_p(twl4030_bci_remove),
+       .suspend        = twl4030_bci_suspend,
+       .resume         = twl4030_bci_resume,
+       .driver         = {
+               .name   = "twl4030_bci",
+               .owner  = THIS_MODULE,
+       },
+};
+
+static int __init twl4030_bci_init(void)
+{
+       return platform_driver_register(&twl4030_bci_driver);
+}
+module_init(twl4030_bci_init);
+
+static void __exit twl4030_bci_exit(void)
+{
+       platform_driver_unregister(&twl4030_bci_driver);
+}
+module_exit(twl4030_bci_exit);
+
+MODULE_AUTHOR("Gražvydas Ignotas");
+MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:twl4030_bci");
-- 
1.6.3.3

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to