]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ENGR00275004-4: power: sabresd_battery: add sabresd_battery driver
authorRobin Gong <b38343@freescale.com>
Tue, 13 Aug 2013 09:49:54 +0000 (17:49 +0800)
committerLothar Waßmann <LW@KARO-electronics.de>
Wed, 20 Aug 2014 08:06:16 +0000 (10:06 +0200)
Add sabresd battery driver which based on Max8903 charger IC.

Signed-off-by: Robin Gong <b38343@freescale.com>
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/sabresd_battery.c [new file with mode: 0644]
include/linux/power/sabresd_battery.h [new file with mode: 0644]

index ba6975123071984b88195d3acbce7a6d5e79d8f7..653f394eb5dbe8dfca0379c62c936efb493843b9 100644 (file)
@@ -276,6 +276,14 @@ config CHARGER_MAX8903
          pins based on the status of charger connections with interrupt
          handlers.
 
+config SABRESD_MAX8903
+       tristate "Sabresd Board Battery DC-DC Charger for USB and Adapter Power"
+       depends on GENERIC_HARDIRQS && TOUCHSCREEN_MAX11801
+       help
+         Say Y to enable support for the MAX8903 DC-DC charger and sysfs on
+         sabresd board.The driver supports controlling charger and battery
+         based on the status of charger connections with interrupt handlers.
+
 config CHARGER_TWL4030
        tristate "OMAP TWL4030 BCI charger driver"
        depends on TWL4030_CORE
index ee54a3e4c90ac63970715c9bf8491d3041041f69..f98b37350fa94b0db544f854b37e5f1d92a0ec77 100644 (file)
@@ -43,6 +43,7 @@ obj-$(CONFIG_BATTERY_RX51)    += rx51_battery.o
 obj-$(CONFIG_AB8500_BM)                += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o
 obj-$(CONFIG_CHARGER_ISP1704)  += isp1704_charger.o
 obj-$(CONFIG_CHARGER_MAX8903)  += max8903_charger.o
+obj-$(CONFIG_SABRESD_MAX8903)   += sabresd_battery.o
 obj-$(CONFIG_CHARGER_TWL4030)  += twl4030_charger.o
 obj-$(CONFIG_CHARGER_LP8727)   += lp8727_charger.o
 obj-$(CONFIG_CHARGER_LP8788)   += lp8788-charger.o
diff --git a/drivers/power/sabresd_battery.c b/drivers/power/sabresd_battery.c
new file mode 100644 (file)
index 0000000..4fe5e78
--- /dev/null
@@ -0,0 +1,989 @@
+/*
+ * sabresd_battery.c - Maxim 8903 USB/Adapter Charger Driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
+ * Based on max8903_charger.c
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/power/sabresd_battery.h>
+#include <linux/slab.h>
+
+#define        BATTERY_UPDATE_INTERVAL 5 /*seconds*/
+#define LOW_VOLT_THRESHOLD     2800000
+#define HIGH_VOLT_THRESHOLD    4200000
+#define ADC_SAMPLE_COUNT       6
+
+struct max8903_data {
+       struct max8903_pdata *pdata;
+       struct device *dev;
+       struct power_supply psy;
+       struct power_supply usb;
+       bool fault;
+       bool usb_in;
+       bool ta_in;
+       bool chg_state;
+       struct delayed_work work;
+       unsigned int interval;
+       unsigned short thermal_raw;
+       int voltage_uV;
+       int current_uA;
+       int battery_status;
+       int charger_online;
+       int charger_voltage_uV;
+       int real_capacity;
+       int percent;
+       int old_percent;
+       int usb_charger_online;
+       int first_delay_count;
+       struct power_supply bat;
+       struct power_supply     detect_usb;
+};
+
+typedef struct {
+       u32 voltage;
+       u32 percent;
+} battery_capacity , *pbattery_capacity;
+
+static int offset_discharger;
+static int offset_charger;
+static int offset_usb_charger;
+
+static battery_capacity chargingTable[] = {
+       {4050,  99},
+       {4040,  98},
+       {4020,  97},
+       {4010,  96},
+       {3990,  95},
+       {3980,  94},
+       {3970,  93},
+       {3960,  92},
+       {3950,  91},
+       {3940,  90},
+       {3930,  85},
+       {3920,  81},
+       {3910,  77},
+       {3900,  73},
+       {3890,  70},
+       {3860,  65},
+       {3830,  60},
+       {3780,  55},
+       {3760,  50},
+       {3740,  45},
+       {3720,  40},
+       {3700,  35},
+       {3680,  30},
+       {3660,  25},
+       {3640,  20},
+       {3620,  17},
+       {3600,  14},
+       {3580,  13},
+       {3560,  12},
+       {3540,  11},
+       {3520,  10},
+       {3500,  9},
+       {3480,  8},
+       {3460,  7},
+       {3440,  6},
+       {3430,  5},
+       {3420,  4},
+       {3020,  0},
+};
+
+static battery_capacity dischargingTable[] = {
+       {4050, 100},
+       {4035,  99},
+       {4020,  98},
+       {4010,  97},
+       {4000,  96},
+       {3990,  96},
+       {3980,  95},
+       {3970,  92},
+       {3960,  91},
+       {3950,  90},
+       {3940,  88},
+       {3930,  86},
+       {3920,  84},
+       {3910,  82},
+       {3900,  80},
+       {3890,  74},
+       {3860,  69},
+       {3830,  64},
+       {3780,  59},
+       {3760,  54},
+       {3740,  49},
+       {3720,  44},
+       {3700,  39},
+       {3680,  34},
+       {3660,  29},
+       {3640,  24},
+       {3620,  19},
+       {3600,  14},
+       {3580,  13},
+       {3560,  12},
+       {3540,  11},
+       {3520,  10},
+       {3500,  9},
+       {3480,  8},
+       {3460,  7},
+       {3440,  6},
+       {3430,  5},
+       {3420,  4},
+       {3020,  0},
+};
+
+u32 calibrate_battery_capability_percent(struct max8903_data *data)
+{
+       u8 i;
+       pbattery_capacity pTable;
+       u32 tableSize;
+
+       if (data->battery_status  == POWER_SUPPLY_STATUS_DISCHARGING) {
+               pTable = dischargingTable;
+               tableSize = sizeof(dischargingTable)/
+                       sizeof(dischargingTable[0]);
+       } else {
+               pTable = chargingTable;
+               tableSize = sizeof(chargingTable)/
+                       sizeof(chargingTable[0]);
+       }
+       for (i = 0; i < tableSize; i++) {
+               if (data->voltage_uV >= pTable[i].voltage)
+                       return  pTable[i].percent;
+       }
+
+       return 0;
+}
+
+static enum power_supply_property max8903_charger_props[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property max8903_battery_props[] = {
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+       POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+};
+
+extern u32 max11801_read_adc(void);
+
+static void max8903_charger_update_status(struct max8903_data *data)
+{
+       if (data->ta_in) {
+               data->charger_online = 1;
+       } else if (data->usb_in) {
+               data->usb_charger_online = 1;
+       } else {
+               data->charger_online = 0;
+               data->usb_charger_online = 0;
+       }
+
+       if (!data->charger_online && !data->usb_charger_online) {
+               data->battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+       } else if (gpio_get_value(data->pdata->chg) == 0) {
+               data->battery_status = POWER_SUPPLY_STATUS_CHARGING;
+       } else if ((data->ta_in || data->usb_in) &&
+               gpio_get_value(data->pdata->chg) > 0) {
+               if (!data->pdata->feature_flag) {
+                       if (data->percent >= 99)
+                               data->battery_status = POWER_SUPPLY_STATUS_FULL;
+                       else
+                               data->battery_status =
+                               POWER_SUPPLY_STATUS_NOT_CHARGING;
+               } else {
+                       data->battery_status = POWER_SUPPLY_STATUS_FULL;
+               }
+       }
+}
+
+u32 calibration_voltage(struct max8903_data *data)
+{
+       u32 voltage_data = 0;
+       int i;
+       int offset;
+
+       if (!data->charger_online && !data->usb_charger_online)
+               offset = offset_discharger;
+       else if (data->usb_charger_online)
+               offset = offset_usb_charger;
+       else if (data->charger_online)
+               offset = offset_charger;
+
+       /* simple average */
+       for (i = 0; i < ADC_SAMPLE_COUNT; i++)
+               voltage_data += max11801_read_adc()-offset;
+       voltage_data = voltage_data / ADC_SAMPLE_COUNT;
+       dev_dbg(data->dev, "volt: %d\n", voltage_data);
+
+       return voltage_data;
+}
+
+static void max8903_battery_update_status(struct max8903_data *data)
+{
+       if (!data->pdata->feature_flag) {
+               data->voltage_uV = calibration_voltage(data);
+               data->percent = calibrate_battery_capability_percent(data);
+               if (data->percent != data->old_percent) {
+                       data->old_percent = data->percent;
+                       power_supply_changed(&data->bat);
+               }
+                /*
+                 * because boot time gap between led framwork and charger
+                 * framwork,when system boots with charger attatched,
+                 * charger led framwork loses the first charger online event,
+                 * add once extra power_supply_changed can fix this issure
+                 */
+               if (data->first_delay_count < 200) {
+                       data->first_delay_count = data->first_delay_count + 1;
+                       power_supply_changed(&data->bat);
+               }
+       }
+}
+
+static int max8903_battery_get_property(struct power_supply *bat,
+                                      enum power_supply_property psp,
+                                      union power_supply_propval *val)
+{
+       struct max8903_data *di = container_of(bat, struct max8903_data, bat);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+               if (gpio_get_value(di->pdata->chg) == 0) {
+                       di->battery_status = POWER_SUPPLY_STATUS_CHARGING;
+               } else if ((di->ta_in || di->usb_in) &&
+                       gpio_get_value(di->pdata->chg) > 0) {
+                       if (!di->pdata->feature_flag) {
+                               if (di->percent >= 99)
+                                       di->battery_status =
+                                       POWER_SUPPLY_STATUS_FULL;
+                               else
+                                       di->battery_status =
+                                       POWER_SUPPLY_STATUS_NOT_CHARGING;
+                       } else {
+                               di->battery_status = POWER_SUPPLY_STATUS_FULL;
+                       }
+               }
+               val->intval = di->battery_status;
+               return 0;
+       default:
+               break;
+       }
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               val->intval = di->voltage_uV;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_NOW:
+               val->intval = 0;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+               val->intval = HIGH_VOLT_THRESHOLD;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+               val->intval = LOW_VOLT_THRESHOLD;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = 1;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               val->intval = di->percent < 0 ? 0 :
+                               (di->percent > 100 ? 100 : di->percent);
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               val->intval = POWER_SUPPLY_HEALTH_GOOD;
+               if (di->fault)
+                       val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+               if (di->battery_status == POWER_SUPPLY_STATUS_FULL)
+                       val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+               else if (di->percent <= 15)
+                       val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+               else
+                       val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int max8903_get_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               union power_supply_propval *val)
+{
+       struct max8903_data *data = container_of(psy,
+                       struct max8903_data, psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = 0;
+               if (data->ta_in)
+                       val->intval = 1;
+               data->charger_online = val->intval;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int max8903_get_usb_property(struct power_supply *usb,
+               enum power_supply_property psp,
+               union power_supply_propval *val)
+{
+       struct max8903_data *data = container_of(usb,
+                       struct max8903_data, usb);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = 0;
+               if (data->usb_in)
+                       val->intval = 1;
+               data->usb_charger_online = val->intval;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static irqreturn_t max8903_dcin(int irq, void *_data)
+{
+       struct max8903_data *data = _data;
+       struct max8903_pdata *pdata = data->pdata;
+       bool ta_in;
+
+       ta_in = gpio_get_value(pdata->dok) ? false : true;
+
+       if (ta_in == data->ta_in)
+               return IRQ_HANDLED;
+
+       data->ta_in = ta_in;
+       dev_info(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?
+                       "Connected" : "Disconnected");
+       max8903_charger_update_status(data);
+       power_supply_changed(&data->psy);
+       power_supply_changed(&data->bat);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_usbin(int irq, void *_data)
+{
+       struct max8903_data *data = _data;
+       struct max8903_pdata *pdata = data->pdata;
+       bool usb_in;
+
+       usb_in = gpio_get_value(pdata->uok) ? false : true;
+       if (usb_in == data->usb_in)
+               return IRQ_HANDLED;
+       data->usb_in = usb_in;
+       dev_info(data->dev, "USB Charger %s.\n", usb_in ?
+                       "Connected" : "Disconnected");
+       max8903_charger_update_status(data);
+       power_supply_changed(&data->bat);
+       power_supply_changed(&data->usb);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_fault(int irq, void *_data)
+{
+       struct max8903_data *data = _data;
+       struct max8903_pdata *pdata = data->pdata;
+       bool fault;
+
+       fault = gpio_get_value(pdata->flt) ? false : true;
+
+       if (fault == data->fault)
+               return IRQ_HANDLED;
+       data->fault = fault;
+
+       if (fault)
+               dev_err(data->dev, "Charger suffers a fault and stops.\n");
+       else
+               dev_err(data->dev, "Charger recovered from a fault.\n");
+       max8903_charger_update_status(data);
+       power_supply_changed(&data->psy);
+       power_supply_changed(&data->bat);
+       power_supply_changed(&data->usb);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_chg(int irq, void *_data)
+{
+       struct max8903_data *data = _data;
+       struct max8903_pdata *pdata = data->pdata;
+       int chg_state;
+
+       chg_state = gpio_get_value(pdata->chg) ? false : true;
+
+       if (chg_state == data->chg_state)
+               return IRQ_HANDLED;
+       data->chg_state = chg_state;
+       max8903_charger_update_status(data);
+       power_supply_changed(&data->psy);
+       power_supply_changed(&data->bat);
+       power_supply_changed(&data->usb);
+
+       return IRQ_HANDLED;
+}
+
+static void max8903_battery_work(struct work_struct *work)
+{
+       struct max8903_data *data;
+
+       data = container_of(work, struct max8903_data, work.work);
+       data->interval = HZ * BATTERY_UPDATE_INTERVAL;
+
+       max8903_charger_update_status(data);
+       max8903_battery_update_status(data);
+       dev_dbg(data->dev, "battery voltage: %4d mV\n", data->voltage_uV);
+       dev_dbg(data->dev, "charger online status: %d\n",
+               data->charger_online);
+       dev_dbg(data->dev, "battery status : %d\n" , data->battery_status);
+       dev_dbg(data->dev, "battery capacity percent: %3d\n", data->percent);
+       dev_dbg(data->dev, "data->usb_in: %x , data->ta_in: %x\n",
+               data->usb_in, data->ta_in);
+       /* reschedule for the next time */
+       schedule_delayed_work(&data->work, data->interval);
+}
+
+static ssize_t max8903_voltage_offset_discharger_show(struct device *dev,
+                           struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "read offset_discharger:%04d\n",
+               offset_discharger);
+}
+
+static ssize_t max8903_voltage_offset_discharger_store(struct device *dev,
+                            struct device_attribute *attr, const char *buf,
+                            size_t count)
+{
+       int ret;
+       unsigned long data;
+
+       ret = strict_strtoul(buf, 10, &data);
+       offset_discharger = (int)data;
+       pr_info("read offset_discharger:%04d\n", offset_discharger);
+
+       return count;
+}
+
+static ssize_t max8903_voltage_offset_charger_show(struct device *dev,
+                           struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "read offset_charger:%04d\n",
+               offset_charger);
+}
+
+static ssize_t max8903_voltage_offset_charger_store(struct device *dev,
+                            struct device_attribute *attr, const char *buf,
+                            size_t count)
+{
+       int ret;
+       unsigned long data;
+
+       ret = strict_strtoul(buf, 10, &data);
+       offset_charger = (int)data;
+       pr_info("read offset_charger:%04d\n", offset_charger);
+       return count;
+}
+
+static ssize_t max8903_voltage_offset_usb_charger_show(struct device *dev,
+                           struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "read offset_usb_charger:%04d\n",
+               offset_usb_charger);
+}
+
+static ssize_t max8903_voltage_offset_usb_charger_store(struct device *dev,
+                            struct device_attribute *attr, const char *buf,
+                            size_t count)
+{
+       int ret;
+       unsigned long data;
+
+       ret = strict_strtoul(buf, 10, &data);
+       offset_usb_charger = (int)data;
+       pr_info("read offset_charger:%04d\n", offset_usb_charger);
+
+       return count;
+}
+
+static struct device_attribute max8903_discharger_dev_attr = {
+       .attr = {
+                .name = "max8903_ctl_offset_discharger",
+                .mode = S_IRUSR | S_IWUSR,
+                },
+       .show = max8903_voltage_offset_discharger_show,
+       .store = max8903_voltage_offset_discharger_store,
+};
+
+static struct device_attribute max8903_charger_dev_attr = {
+       .attr = {
+                .name = "max8903_ctl_offset_charger",
+                .mode = S_IRUSR | S_IWUSR,
+                },
+       .show = max8903_voltage_offset_charger_show,
+       .store = max8903_voltage_offset_charger_store,
+};
+
+static struct device_attribute max8903_usb_charger_dev_attr = {
+       .attr = {
+                .name = "max8903_ctl_offset_usb_charger",
+                .mode = S_IRUSR | S_IWUSR,
+                },
+       .show = max8903_voltage_offset_usb_charger_show,
+       .store = max8903_voltage_offset_usb_charger_store,
+};
+
+#if defined(CONFIG_OF)
+static const struct of_device_id max8903_dt_ids[] = {
+       { .compatible = "fsl,max8903-charger", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, max8903_dt_ids);
+
+static struct max8903_pdata *max8903_of_populate_pdata(
+               struct device *dev)
+{
+       struct device_node *of_node = dev->of_node;
+       struct max8903_pdata *pdata = dev->platform_data;
+
+       if (!of_node || pdata)
+               return pdata;
+
+       pdata = devm_kzalloc(dev, sizeof(struct max8903_pdata),
+                               GFP_KERNEL);
+       if (!pdata)
+               return pdata;
+
+       if (of_get_property(of_node, "fsl,dcm_always_high", NULL))
+               pdata->dcm_always_high = true;
+       if (of_get_property(of_node, "fsl,dc_valid", NULL))
+               pdata->dc_valid = true;
+       if (of_get_property(of_node, "fsl,usb_valid", NULL))
+               pdata->usb_valid = true;
+       if (of_get_property(of_node, "fsl,adc_disable", NULL))
+               pdata->feature_flag = true;
+
+       pdata->dok = of_get_named_gpio(of_node, "dok_input", 0);
+       if (!gpio_is_valid(pdata->dok)) {
+               dev_err(dev, "pin pdata->dok: invalid gpio %d\n", pdata->dok);
+               return NULL;
+       }
+
+       pdata->uok = of_get_named_gpio(of_node, "uok_input", 0);
+       if (!gpio_is_valid(pdata->uok)) {
+               dev_err(dev, "pin pdata->uok: invalid gpio %d\n", pdata->uok);
+               return NULL;
+       }
+       pdata->chg = of_get_named_gpio(of_node, "chg_input", 0);
+       if (!gpio_is_valid(pdata->chg)) {
+               dev_err(dev, "pin pdata->chg: invalid gpio %d\n", pdata->chg);
+               return NULL;
+       }
+       pdata->flt = of_get_named_gpio(of_node, "flt_input", 0);
+       if (!gpio_is_valid(pdata->flt)) {
+               dev_err(dev, "pin pdata->flt: invalid gpio %d\n", pdata->flt);
+               return NULL;
+       }
+
+       if (of_property_read_u32(of_node, "offset-charger",
+               &offset_charger))
+               dev_err(dev, "Not setting offset-charger in dts!\n");
+
+       if (of_property_read_u32(of_node, "offset-discharger",
+               &offset_discharger))
+               dev_err(dev, "Not setting offset-discharger in dts!\n");
+
+       if (of_property_read_u32(of_node, "offset-usb-charger",
+               &offset_usb_charger))
+               dev_err(dev, "Not setting offset-usb-charger in dts!\n");
+
+       return pdata;
+}
+#endif
+
+static int max8903_probe(struct platform_device *pdev)
+{
+       struct max8903_data *data;
+       struct device *dev = &pdev->dev;
+       struct max8903_pdata *pdata = pdev->dev.platform_data;
+       int ret = 0;
+       int gpio = 0;
+       int ta_in = 0;
+       int usb_in = 0;
+       int retval;
+
+       data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       pdata = pdev->dev.platform_data;
+       if (!pdata) {
+               pdata = max8903_of_populate_pdata(&pdev->dev);
+               if (!pdata)
+                       return -EINVAL;
+       }
+
+       data->first_delay_count = 0;
+       data->pdata = pdata;
+       data->dev = dev;
+       data->usb_in = 0;
+       data->ta_in = 0;
+       platform_set_drvdata(pdev, data);
+
+       if (pdata->dc_valid == false && pdata->usb_valid == false) {
+               dev_err(dev, "No valid power sources.\n");
+               ret = -EINVAL;
+               goto err;
+       }
+       if (pdata->dc_valid) {
+               if (pdata->dok && pdata->dcm_always_high) {
+                       gpio = pdata->dok;
+                       ret = gpio_request_one(gpio, GPIOF_IN, "max8903-DOK");
+                       if (ret) {
+                               dev_err(dev, "request max8903-DOK error!!\n");
+                               goto err;
+                       }
+                       ta_in = gpio_get_value(gpio) ? 0 : 1;
+               } else {
+                       dev_err(dev, "When DC is wired, DOK and DCM should be"
+                               " wired as well or set dcm always high!\n");
+                       ret = -EINVAL;
+                       goto err;
+               }
+       }
+
+       if (pdata->usb_valid) {
+               if (pdata->uok) {
+                       gpio = pdata->uok;
+                       ret = gpio_request_one(gpio, GPIOF_IN, "max8903-UOK");
+                       if (ret) {
+                               dev_err(dev, "request max8903-UOK error!!\n");
+                               goto err;
+                       }
+                       usb_in = gpio_get_value(gpio) ? 0 : 1;
+               } else {
+                       dev_err(dev, "When USB is wired, UOK should be wired"
+                               " as well.\n");
+                       ret = -EINVAL;
+                       goto err;
+               }
+       }
+
+       if (pdata->chg) {
+               ret = gpio_request_one(pdata->chg, GPIOF_IN, "max8903-CHG");
+               if (ret) {
+                       dev_err(dev, "request max8903-CHG error!!\n");
+                       goto err;
+               }
+       }
+
+       if (pdata->flt) {
+               ret = gpio_request_one(pdata->flt, GPIOF_IN, "max8903-FLT");
+               if (ret) {
+                       dev_err(dev, "request max8903-FLT error!!\n");
+                       goto err;
+               }
+       }
+
+       data->fault = false;
+       data->ta_in = ta_in;
+       data->usb_in = usb_in;
+       data->psy.name = "max8903-ac";
+       data->psy.type = POWER_SUPPLY_TYPE_MAINS;
+       data->psy.get_property = max8903_get_property;
+       data->psy.properties = max8903_charger_props;
+       data->psy.num_properties = ARRAY_SIZE(max8903_charger_props);
+       ret = power_supply_register(dev, &data->psy);
+       if (ret) {
+               dev_err(dev, "failed: power supply register.\n");
+               goto err_psy;
+       }
+
+       data->usb.name = "max8903-usb";
+       data->usb.type = POWER_SUPPLY_TYPE_USB;
+       data->usb.get_property = max8903_get_usb_property;
+       data->usb.properties = max8903_charger_props;
+       data->usb.num_properties = ARRAY_SIZE(max8903_charger_props);
+       ret = power_supply_register(dev, &data->usb);
+       if (ret) {
+               dev_err(dev, "failed: power supply register.\n");
+               goto err_psy;
+       }
+
+       data->bat.name = "max8903-charger";
+       data->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+       data->bat.properties = max8903_battery_props;
+       data->bat.num_properties = ARRAY_SIZE(max8903_battery_props);
+       data->bat.get_property = max8903_battery_get_property;
+       data->bat.use_for_apm = 1;
+       retval = power_supply_register(&pdev->dev, &data->bat);
+       if (retval) {
+               dev_err(data->dev, "failed to register battery\n");
+               goto battery_failed;
+       }
+
+       INIT_DELAYED_WORK(&data->work, max8903_battery_work);
+       schedule_delayed_work(&data->work, data->interval);
+
+       if (pdata->dc_valid) {
+               ret = request_threaded_irq(gpio_to_irq(pdata->dok), NULL,
+                       max8903_dcin, IRQF_TRIGGER_FALLING |
+                       IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 DC IN",
+                       data);
+               if (ret) {
+                       dev_err(dev, "Cannot request irq %d for DC (%d)\n",
+                                       gpio_to_irq(pdata->dok), ret);
+                       goto err_dc_irq;
+               }
+       }
+
+       if (pdata->usb_valid) {
+               ret = request_threaded_irq(gpio_to_irq(pdata->uok), NULL,
+                       max8903_usbin, IRQF_TRIGGER_FALLING |
+                       IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 USB IN",
+                       data);
+               if (ret) {
+                       dev_err(dev, "Cannot request irq %d for USB (%d)\n",
+                                       gpio_to_irq(pdata->uok), ret);
+                       goto err_usb_irq;
+               }
+       }
+
+       if (pdata->flt) {
+               ret = request_threaded_irq(gpio_to_irq(pdata->flt), NULL,
+                       max8903_fault, IRQF_TRIGGER_FALLING |
+                       IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 Fault",
+                       data);
+               if (ret) {
+                       dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
+                                       gpio_to_irq(pdata->flt), ret);
+                       goto err_flt_irq;
+               }
+       }
+
+       if (pdata->chg) {
+               ret = request_threaded_irq(gpio_to_irq(pdata->chg), NULL,
+                       max8903_chg, IRQF_TRIGGER_FALLING |
+                       IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 Status",
+                       data);
+               if (ret) {
+                       dev_err(dev, "Cannot request irq %d for Status (%d)\n",
+                                       gpio_to_irq(pdata->flt), ret);
+                       goto err_chg_irq;
+               }
+       }
+
+       ret = device_create_file(&pdev->dev, &max8903_discharger_dev_attr);
+       if (ret)
+               dev_err(&pdev->dev, "create device file failed!\n");
+       ret = device_create_file(&pdev->dev, &max8903_charger_dev_attr);
+       if (ret)
+               dev_err(&pdev->dev, "create device file failed!\n");
+       ret = device_create_file(&pdev->dev, &max8903_usb_charger_dev_attr);
+       if (ret)
+               dev_err(&pdev->dev, "create device file failed!\n");
+
+       device_set_wakeup_capable(&pdev->dev, true);
+
+       max8903_charger_update_status(data);
+       max8903_battery_update_status(data);
+
+       return 0;
+err_psy:
+       power_supply_unregister(&data->psy);
+battery_failed:
+       power_supply_unregister(&data->bat);
+err_usb_irq:
+       if (pdata->usb_valid)
+               free_irq(gpio_to_irq(pdata->uok), data);
+       cancel_delayed_work(&data->work);
+err_dc_irq:
+       if (pdata->dc_valid)
+               free_irq(gpio_to_irq(pdata->dok), data);
+       cancel_delayed_work(&data->work);
+err_flt_irq:
+       if (pdata->usb_valid)
+               free_irq(gpio_to_irq(pdata->uok), data);
+       cancel_delayed_work(&data->work);
+err_chg_irq:
+       if (pdata->dc_valid)
+               free_irq(gpio_to_irq(pdata->dok), data);
+       cancel_delayed_work(&data->work);
+err:
+       if (pdata->uok)
+               gpio_free(pdata->uok);
+       if (pdata->dok)
+               gpio_free(pdata->dok);
+       if (pdata->flt)
+               gpio_free(pdata->flt);
+       if (pdata->chg)
+               gpio_free(pdata->chg);
+       return ret;
+}
+
+static int max8903_remove(struct platform_device *pdev)
+{
+       struct max8903_data *data = platform_get_drvdata(pdev);
+       if (data) {
+               struct max8903_pdata *pdata = data->pdata;
+
+               cancel_delayed_work_sync(&data->work);
+               power_supply_unregister(&data->psy);
+               power_supply_unregister(&data->usb);
+               power_supply_unregister(&data->bat);
+
+               if (pdata->flt) {
+                       free_irq(gpio_to_irq(pdata->flt), data);
+                       gpio_free(pdata->flt);
+               }
+               if (pdata->usb_valid && pdata->uok) {
+                       free_irq(gpio_to_irq(pdata->uok), data);
+                       gpio_free(pdata->uok);
+               }
+               if (pdata->dc_valid) {
+                       if (pdata->dok) {
+                               free_irq(gpio_to_irq(pdata->dok), data);
+                               gpio_free(pdata->dok);
+                       } else if (pdata->chg) {
+                               free_irq(gpio_to_irq(pdata->chg), data);
+                               gpio_free(pdata->chg);
+                       }
+               }
+
+               device_remove_file(&pdev->dev, &max8903_discharger_dev_attr);
+               device_remove_file(&pdev->dev, &max8903_charger_dev_attr);
+               device_remove_file(&pdev->dev, &max8903_usb_charger_dev_attr);
+
+               platform_set_drvdata(pdev, NULL);
+               kfree(data);
+       }
+
+       return 0;
+}
+
+static int max8903_suspend(struct platform_device *pdev,
+                                 pm_message_t state)
+{
+       struct max8903_data *data = platform_get_drvdata(pdev);
+       int irq;
+       if (data) {
+               struct max8903_pdata *pdata = data->pdata;
+               if (pdata) {
+                       if (pdata->dc_valid && device_may_wakeup(&pdev->dev)) {
+                               irq = gpio_to_irq(pdata->dok);
+                               enable_irq_wake(irq);
+                       }
+
+                       if (pdata->usb_valid && device_may_wakeup(&pdev->dev)) {
+                               irq = gpio_to_irq(pdata->uok);
+                               enable_irq_wake(irq);
+                       }
+                       cancel_delayed_work(&data->work);
+               }
+       }
+       return 0;
+}
+
+static int max8903_resume(struct platform_device *pdev)
+{
+       struct max8903_data *data = platform_get_drvdata(pdev);
+       bool ta_in;
+       bool usb_in;
+       int irq;
+
+       if (data) {
+               struct max8903_pdata *pdata = data->pdata;
+
+               if (pdata) {
+                       ta_in = gpio_get_value(pdata->dok) ? false : true;
+                       usb_in = gpio_get_value(pdata->uok) ? false : true;
+
+                       if (ta_in != data->ta_in) {
+                               data->ta_in = ta_in;
+                               dev_info(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?
+                               "Connected" : "Disconnected");
+                               max8903_charger_update_status(data);
+                               power_supply_changed(&data->psy);
+                       }
+
+                       if (usb_in != data->usb_in) {
+                               data->usb_in = usb_in;
+                               dev_info(data->dev, "USB Charger %s.\n", usb_in ?
+                               "Connected" : "Disconnected");
+                               max8903_charger_update_status(data);
+                               power_supply_changed(&data->usb);
+                       }
+
+                       if (pdata->dc_valid && device_may_wakeup(&pdev->dev)) {
+                               irq = gpio_to_irq(pdata->dok);
+                               disable_irq_wake(irq);
+                       }
+                       if (pdata->usb_valid && device_may_wakeup(&pdev->dev)) {
+                               irq = gpio_to_irq(pdata->uok);
+                               disable_irq_wake(irq);
+                       }
+
+                       schedule_delayed_work(&data->work,
+                       BATTERY_UPDATE_INTERVAL);
+               }
+       }
+
+       return 0;
+}
+
+static struct platform_driver max8903_driver = {
+       .probe  = max8903_probe,
+       .remove = max8903_remove,
+       .suspend = max8903_suspend,
+       .resume = max8903_resume,
+       .driver = {
+               .name   = "max8903-charger",
+               .owner  = THIS_MODULE,
+               .of_match_table = max8903_dt_ids,
+       },
+};
+module_platform_driver(max8903_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Sabresd Battery Driver");
+MODULE_ALIAS("sabresd_battery");
diff --git a/include/linux/power/sabresd_battery.h b/include/linux/power/sabresd_battery.h
new file mode 100644 (file)
index 0000000..f157b16
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * sabresd_battery.h - Maxim 8903 USB/Adapter Charger Driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
+ * Based on max8903_charger.h
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef __MAX8903_SABRESD_H__
+#define __MAX8903_SABRESD_H__
+
+struct max8903_pdata {
+       /*
+        * GPIOs
+        * cen, chg, flt, and usus are optional.
+        * dok, dcm, and uok are not optional depending on the status of
+        * dc_valid and usb_valid.
+        */
+       int cen;        /* Charger Enable input */
+       int dok;        /* DC(Adapter) Power OK output */
+       int uok;        /* USB Power OK output */
+       int chg;        /* Charger status output */
+       int flt;        /* Fault output */
+       int dcm;        /* Current-Limit Mode input (1: DC, 2: USB) */
+       int usus;       /* USB Suspend Input (1: suspended) */
+       int feature_flag;/* battery capacity feature(0:enable, 1:disable) */
+
+       /*
+        * DCM wired to Logic High Set this true when DCM pin connect to
+        * Logic high.
+        */
+       bool dcm_always_high;
+
+       /*
+        * DC(Adapter/TA) is wired
+        * When dc_valid is true,
+        *      dok and dcm should be valid.
+        *
+        * At least one of dc_valid or usb_valid should be true.
+        */
+       bool dc_valid;
+       /*
+        * USB is wired
+        * When usb_valid is true,
+        *      uok should be valid.
+        */
+       bool usb_valid;
+};
+
+#endif /* __SABRESD_BATTERY_H__ */