]> Pileus Git - ~andy/linux/commitdiff
Merge branch 'power-supply-scope' of git://git.kernel.org/pub/scm/linux/kernel/git...
authorAnton Vorontsov <cbouatmailru@gmail.com>
Wed, 4 Jan 2012 05:09:35 +0000 (09:09 +0400)
committerAnton Vorontsov <cbouatmailru@gmail.com>
Wed, 4 Jan 2012 05:09:35 +0000 (09:09 +0400)
30 files changed:
Documentation/devicetree/bindings/power_supply/olpc_battery.txt [new file with mode: 0644]
Documentation/devicetree/bindings/power_supply/ti_bq20z75.txt [new file with mode: 0644]
Documentation/power/charger-manager.txt [new file with mode: 0644]
drivers/mfd/max8925-core.c
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/bq20z75.c
drivers/power/bq27x00_battery.c
drivers/power/charger-manager.c [new file with mode: 0644]
drivers/power/collie_battery.c
drivers/power/ds2760_battery.c
drivers/power/ds2780_battery.c
drivers/power/intel_mid_battery.c
drivers/power/lp8727_charger.c [new file with mode: 0644]
drivers/power/max17042_battery.c
drivers/power/max8903_charger.c
drivers/power/max8925_power.c
drivers/power/max8997_charger.c
drivers/power/max8998_charger.c
drivers/power/olpc_battery.c
drivers/power/pda_power.c
drivers/power/power_supply_sysfs.c
drivers/power/tosa_battery.c
drivers/power/wm831x_power.c
drivers/power/wm97xx_battery.c
drivers/power/z2_battery.c
include/linux/mfd/max8925.h
include/linux/pda_power.h
include/linux/power/charger-manager.h [new file with mode: 0644]
include/linux/power_supply.h

diff --git a/Documentation/devicetree/bindings/power_supply/olpc_battery.txt b/Documentation/devicetree/bindings/power_supply/olpc_battery.txt
new file mode 100644 (file)
index 0000000..c8901b3
--- /dev/null
@@ -0,0 +1,5 @@
+OLPC battery
+~~~~~~~~~~~~
+
+Required properties:
+  - compatible : "olpc,xo1-battery"
diff --git a/Documentation/devicetree/bindings/power_supply/ti_bq20z75.txt b/Documentation/devicetree/bindings/power_supply/ti_bq20z75.txt
new file mode 100644 (file)
index 0000000..7571294
--- /dev/null
@@ -0,0 +1,23 @@
+TI bq20z75
+~~~~~~~~~~
+
+Required properties :
+ - compatible : "ti,bq20z75"
+
+Optional properties :
+ - ti,i2c-retry-count : The number of times to retry i2c transactions on i2c
+   IO failure.
+ - ti,poll-retry-count : The number of times to try looking for new status
+   after an external change notification.
+ - ti,battery-detect-gpios : The gpio which signals battery detection and
+   a flag specifying its polarity.
+
+Example:
+
+       bq20z75@b {
+               compatible = "ti,bq20z75";
+               reg = < 0xb >;
+               ti,i2c-retry-count = <2>;
+               ti,poll-retry-count = <10>;
+               ti,battery-detect-gpios = <&gpio-controller 122 1>;
+       }
diff --git a/Documentation/power/charger-manager.txt b/Documentation/power/charger-manager.txt
new file mode 100644 (file)
index 0000000..fdcca99
--- /dev/null
@@ -0,0 +1,163 @@
+Charger Manager
+       (C) 2011 MyungJoo Ham <myungjoo.ham@samsung.com>, GPL
+
+Charger Manager provides in-kernel battery charger management that
+requires temperature monitoring during suspend-to-RAM state
+and where each battery may have multiple chargers attached and the userland
+wants to look at the aggregated information of the multiple chargers.
+
+Charger Manager is a platform_driver with power-supply-class entries.
+An instance of Charger Manager (a platform-device created with Charger-Manager)
+represents an independent battery with chargers. If there are multiple
+batteries with their own chargers acting independently in a system,
+the system may need multiple instances of Charger Manager.
+
+1. Introduction
+===============
+
+Charger Manager supports the following:
+
+* Support for multiple chargers (e.g., a device with USB, AC, and solar panels)
+       A system may have multiple chargers (or power sources) and some of
+       they may be activated at the same time. Each charger may have its
+       own power-supply-class and each power-supply-class can provide
+       different information about the battery status. This framework
+       aggregates charger-related information from multiple sources and
+       shows combined information as a single power-supply-class.
+
+* Support for in suspend-to-RAM polling (with suspend_again callback)
+       While the battery is being charged and the system is in suspend-to-RAM,
+       we may need to monitor the battery health by looking at the ambient or
+       battery temperature. We can accomplish this by waking up the system
+       periodically. However, such a method wakes up devices unncessary for
+       monitoring the battery health and tasks, and user processes that are
+       supposed to be kept suspended. That, in turn, incurs unnecessary power
+       consumption and slow down charging process. Or even, such peak power
+       consumption can stop chargers in the middle of charging
+       (external power input < device power consumption), which not
+       only affects the charging time, but the lifespan of the battery.
+
+       Charger Manager provides a function "cm_suspend_again" that can be
+       used as suspend_again callback of platform_suspend_ops. If the platform
+       requires tasks other than cm_suspend_again, it may implement its own
+       suspend_again callback that calls cm_suspend_again in the middle.
+       Normally, the platform will need to resume and suspend some devices
+       that are used by Charger Manager.
+
+2. Global Charger-Manager Data related with suspend_again
+========================================================
+In order to setup Charger Manager with suspend-again feature
+(in-suspend monitoring), the user should provide charger_global_desc
+with setup_charger_manager(struct charger_global_desc *).
+This charger_global_desc data for in-suspend monitoring is global
+as the name suggests. Thus, the user needs to provide only once even
+if there are multiple batteries. If there are multiple batteries, the
+multiple instances of Charger Manager share the same charger_global_desc
+and it will manage in-suspend monitoring for all instances of Charger Manager.
+
+The user needs to provide all the two entries properly in order to activate
+in-suspend monitoring:
+
+struct charger_global_desc {
+
+char *rtc_name;
+       : The name of rtc (e.g., "rtc0") used to wakeup the system from
+       suspend for Charger Manager. The alarm interrupt (AIE) of the rtc
+       should be able to wake up the system from suspend. Charger Manager
+       saves and restores the alarm value and use the previously-defined
+       alarm if it is going to go off earlier than Charger Manager so that
+       Charger Manager does not interfere with previously-defined alarms.
+
+bool (*rtc_only_wakeup)(void);
+       : This callback should let CM know whether
+       the wakeup-from-suspend is caused only by the alarm of "rtc" in the
+       same struct. If there is any other wakeup source triggered the
+       wakeup, it should return false. If the "rtc" is the only wakeup
+       reason, it should return true.
+};
+
+3. How to setup suspend_again
+=============================
+Charger Manager provides a function "extern bool cm_suspend_again(void)".
+When cm_suspend_again is called, it monitors every battery. The suspend_ops
+callback of the system's platform_suspend_ops can call cm_suspend_again
+function to know whether Charger Manager wants to suspend again or not.
+If there are no other devices or tasks that want to use suspend_again
+feature, the platform_suspend_ops may directly refer to cm_suspend_again
+for its suspend_again callback.
+
+The cm_suspend_again() returns true (meaning "I want to suspend again")
+if the system was woken up by Charger Manager and the polling
+(in-suspend monitoring) results in "normal".
+
+4. Charger-Manager Data (struct charger_desc)
+=============================================
+For each battery charged independently from other batteries (if a series of
+batteries are charged by a single charger, they are counted as one independent
+battery), an instance of Charger Manager is attached to it.
+
+struct charger_desc {
+
+char *psy_name;
+       : The power-supply-class name of the battery. Default is
+       "battery" if psy_name is NULL. Users can access the psy entries
+       at "/sys/class/power_supply/[psy_name]/".
+
+enum polling_modes polling_mode;
+       : CM_POLL_DISABLE: do not poll this battery.
+         CM_POLL_ALWAYS: always poll this battery.
+         CM_POLL_EXTERNAL_POWER_ONLY: poll this battery if and only if
+                                      an external power source is attached.
+         CM_POLL_CHARGING_ONLY: poll this battery if and only if the
+                                battery is being charged.
+
+unsigned int fullbatt_uV;
+       : If specified with a non-zero value, Charger Manager assumes
+       that the battery is full (capacity = 100) if the battery is not being
+       charged and the battery voltage is equal to or greater than
+       fullbatt_uV.
+
+unsigned int polling_interval_ms;
+       : Required polling interval in ms. Charger Manager will poll
+       this battery every polling_interval_ms or more frequently.
+
+enum data_source battery_present;
+       CM_FUEL_GAUGE: get battery presence information from fuel gauge.
+       CM_CHARGER_STAT: get battery presence from chargers.
+
+char **psy_charger_stat;
+       : An array ending with NULL that has power-supply-class names of
+       chargers. Each power-supply-class should provide "PRESENT" (if
+       battery_present is "CM_CHARGER_STAT"), "ONLINE" (shows whether an
+       external power source is attached or not), and "STATUS" (shows whether
+       the battery is {"FULL" or not FULL} or {"FULL", "Charging",
+       "Discharging", "NotCharging"}).
+
+int num_charger_regulators;
+struct regulator_bulk_data *charger_regulators;
+       : Regulators representing the chargers in the form for
+       regulator framework's bulk functions.
+
+char *psy_fuel_gauge;
+       : Power-supply-class name of the fuel gauge.
+
+int (*temperature_out_of_range)(int *mC);
+bool measure_battery_temp;
+       : This callback returns 0 if the temperature is safe for charging,
+       a positive number if it is too hot to charge, and a negative number
+       if it is too cold to charge. With the variable mC, the callback returns
+       the temperature in 1/1000 of centigrade.
+       The source of temperature can be battery or ambient one according to
+       the value of measure_battery_temp.
+};
+
+5. Other Considerations
+=======================
+
+At the charger/battery-related events such as battery-pulled-out,
+charger-pulled-out, charger-inserted, DCIN-over/under-voltage, charger-stopped,
+and others critical to chargers, the system should be configured to wake up.
+At least the following should wake up the system from a suspend:
+a) charger-on/off b) external-power-in/out c) battery-in/out (while charging)
+
+It is usually accomplished by configuring the PMIC as a wakeup source.
index e1e59c92f7588592e141839f2af5c0c4298d53d5..ca881efedf75a43f7543e19d506702ab1518e0ec 100644 (file)
@@ -210,21 +210,6 @@ static struct max8925_irq_data max8925_irqs[] = {
                .mask_reg       = MAX8925_CHG_IRQ1_MASK,
                .offs           = 1 << 2,
        },
-       [MAX8925_IRQ_VCHG_USB_OVP] = {
-               .reg            = MAX8925_CHG_IRQ1,
-               .mask_reg       = MAX8925_CHG_IRQ1_MASK,
-               .offs           = 1 << 3,
-       },
-       [MAX8925_IRQ_VCHG_USB_F] =  {
-               .reg            = MAX8925_CHG_IRQ1,
-               .mask_reg       = MAX8925_CHG_IRQ1_MASK,
-               .offs           = 1 << 4,
-       },
-       [MAX8925_IRQ_VCHG_USB_R] = {
-               .reg            = MAX8925_CHG_IRQ1,
-               .mask_reg       = MAX8925_CHG_IRQ1_MASK,
-               .offs           = 1 << 5,
-       },
        [MAX8925_IRQ_VCHG_THM_OK_R] = {
                .reg            = MAX8925_CHG_IRQ2,
                .mask_reg       = MAX8925_CHG_IRQ2_MASK,
index 9f88641e67f9255e0219071eb5a702a1289bb066..3bd2ed86fea2e71b5fadacf451a82dade4b68fe6 100644 (file)
@@ -226,6 +226,12 @@ config CHARGER_TWL4030
        help
          Say Y here to enable support for TWL4030 Battery Charge Interface.
 
+config CHARGER_LP8727
+       tristate "National Semiconductor LP8727 charger driver"
+       depends on I2C
+       help
+         Say Y here to enable support for LP8727 Charger Driver.
+
 config CHARGER_GPIO
        tristate "GPIO charger"
        depends on GPIOLIB
@@ -236,6 +242,16 @@ config CHARGER_GPIO
          This driver can be build as a module. If so, the module will be
          called gpio-charger.
 
+config CHARGER_MANAGER
+       bool "Battery charger manager for multiple chargers"
+       depends on REGULATOR && RTC_CLASS
+       help
+          Say Y to enable charger-manager support, which allows multiple
+          chargers attached to a battery and multiple batteries attached to a
+          system. The charger-manager also can monitor charging status in
+          runtime and in suspend-to-RAM by waking up the system periodically
+          with help of suspend_again support.
+
 config CHARGER_MAX8997
        tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
        depends on MFD_MAX8997 && REGULATOR_MAX8997
index b4af13dd8b66a1b45a3ed1307c1d0c4b1b10e169..9a78b1dd570bf1ddada97fd14400522e5cdf2008 100644 (file)
@@ -35,6 +35,8 @@ obj-$(CONFIG_BATTERY_INTEL_MID)       += intel_mid_battery.o
 obj-$(CONFIG_CHARGER_ISP1704)  += isp1704_charger.o
 obj-$(CONFIG_CHARGER_MAX8903)  += max8903_charger.o
 obj-$(CONFIG_CHARGER_TWL4030)  += twl4030_charger.o
+obj-$(CONFIG_CHARGER_LP8727)   += lp8727_charger.o
 obj-$(CONFIG_CHARGER_GPIO)     += gpio-charger.o
+obj-$(CONFIG_CHARGER_MANAGER)  += charger-manager.o
 obj-$(CONFIG_CHARGER_MAX8997)  += max8997_charger.o
 obj-$(CONFIG_CHARGER_MAX8998)  += max8998_charger.o
index 9c5e5beda3a8e34a50dcdc6f960f2e830ca099f7..ce95ff79101663adf2cbe13105f61e6ffb02175e 100644 (file)
@@ -613,6 +613,80 @@ static void bq20z75_delayed_work(struct work_struct *work)
        }
 }
 
+#if defined(CONFIG_OF)
+
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+
+static const struct of_device_id bq20z75_dt_ids[] = {
+       { .compatible = "ti,bq20z75" },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, bq20z75_dt_ids);
+
+static struct bq20z75_platform_data *bq20z75_of_populate_pdata(
+               struct i2c_client *client)
+{
+       struct device_node *of_node = client->dev.of_node;
+       struct bq20z75_platform_data *pdata = client->dev.platform_data;
+       enum of_gpio_flags gpio_flags;
+       int rc;
+       u32 prop;
+
+       /* verify this driver matches this device */
+       if (!of_node)
+               return NULL;
+
+       /* if platform data is set, honor it */
+       if (pdata)
+               return pdata;
+
+       /* first make sure at least one property is set, otherwise
+        * it won't change behavior from running without pdata.
+        */
+       if (!of_get_property(of_node, "ti,i2c-retry-count", NULL) &&
+               !of_get_property(of_node, "ti,poll-retry-count", NULL) &&
+               !of_get_property(of_node, "ti,battery-detect-gpios", NULL))
+               goto of_out;
+
+       pdata = devm_kzalloc(&client->dev, sizeof(struct bq20z75_platform_data),
+                               GFP_KERNEL);
+       if (!pdata)
+               goto of_out;
+
+       rc = of_property_read_u32(of_node, "ti,i2c-retry-count", &prop);
+       if (!rc)
+               pdata->i2c_retry_count = prop;
+
+       rc = of_property_read_u32(of_node, "ti,poll-retry-count", &prop);
+       if (!rc)
+               pdata->poll_retry_count = prop;
+
+       if (!of_get_property(of_node, "ti,battery-detect-gpios", NULL)) {
+               pdata->battery_detect = -1;
+               goto of_out;
+       }
+
+       pdata->battery_detect = of_get_named_gpio_flags(of_node,
+                       "ti,battery-detect-gpios", 0, &gpio_flags);
+
+       if (gpio_flags & OF_GPIO_ACTIVE_LOW)
+               pdata->battery_detect_present = 0;
+       else
+               pdata->battery_detect_present = 1;
+
+of_out:
+       return pdata;
+}
+#else
+#define bq20z75_dt_ids NULL
+static struct bq20z75_platform_data *bq20z75_of_populate_pdata(
+       struct i2c_client *client)
+{
+       return client->dev.platform_data;
+}
+#endif
+
 static int __devinit bq20z75_probe(struct i2c_client *client,
        const struct i2c_device_id *id)
 {
@@ -642,6 +716,8 @@ static int __devinit bq20z75_probe(struct i2c_client *client,
        bq20z75_device->power_supply.external_power_changed =
                bq20z75_external_power_changed;
 
+       pdata = bq20z75_of_populate_pdata(client);
+
        if (pdata) {
                bq20z75_device->gpio_detect =
                        gpio_is_valid(pdata->battery_detect);
@@ -775,6 +851,7 @@ static struct i2c_driver bq20z75_battery_driver = {
        .id_table       = bq20z75_id,
        .driver = {
                .name   = "bq20z75-battery",
+               .of_match_table = bq20z75_dt_ids,
        },
 };
 
index bb16f5b7e167490519d2793cbaa1546192e447f5..98bf5676318d4b7664870558b8f26eb35222fbe8 100644 (file)
 
 #define BQ27000_REG_RSOC               0x0B /* Relative State-of-Charge */
 #define BQ27000_REG_ILMD               0x76 /* Initial last measured discharge */
-#define BQ27000_FLAG_CHGS              BIT(7)
+#define BQ27000_FLAG_EDVF              BIT(0) /* Final End-of-Discharge-Voltage flag */
+#define BQ27000_FLAG_EDV1              BIT(1) /* First End-of-Discharge-Voltage flag */
+#define BQ27000_FLAG_CI                        BIT(4) /* Capacity Inaccurate flag */
 #define BQ27000_FLAG_FC                        BIT(5)
+#define BQ27000_FLAG_CHGS              BIT(7) /* Charge state flag */
 
 #define BQ27500_REG_SOC                        0x2C
 #define BQ27500_REG_DCAP               0x3C /* Design capacity */
-#define BQ27500_FLAG_DSC               BIT(0)
-#define BQ27500_FLAG_FC                        BIT(9)
+#define BQ27500_FLAG_DSG               BIT(0) /* Discharging */
+#define BQ27500_FLAG_SOCF              BIT(1) /* State-of-Charge threshold final */
+#define BQ27500_FLAG_SOC1              BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27500_FLAG_CHG               BIT(8) /* Charging */
+#define BQ27500_FLAG_FC                        BIT(9) /* Fully charged */
 
 #define BQ27000_RS                     20 /* Resistor sense */
 
@@ -79,9 +85,8 @@ struct bq27x00_reg_cache {
        int charge_full;
        int cycle_count;
        int capacity;
+       int energy;
        int flags;
-
-       int current_now;
 };
 
 struct bq27x00_device_info {
@@ -108,6 +113,7 @@ static enum power_supply_property bq27x00_battery_props[] = {
        POWER_SUPPLY_PROP_VOLTAGE_NOW,
        POWER_SUPPLY_PROP_CURRENT_NOW,
        POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_CAPACITY_LEVEL,
        POWER_SUPPLY_PROP_TEMP,
        POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
        POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
@@ -149,7 +155,7 @@ static int bq27x00_battery_read_rsoc(struct bq27x00_device_info *di)
                rsoc = bq27x00_read(di, BQ27000_REG_RSOC, true);
 
        if (rsoc < 0)
-               dev_err(di->dev, "error reading relative State-of-Charge\n");
+               dev_dbg(di->dev, "error reading relative State-of-Charge\n");
 
        return rsoc;
 }
@@ -164,7 +170,8 @@ static int bq27x00_battery_read_charge(struct bq27x00_device_info *di, u8 reg)
 
        charge = bq27x00_read(di, reg, false);
        if (charge < 0) {
-               dev_err(di->dev, "error reading nominal available capacity\n");
+               dev_dbg(di->dev, "error reading charge register %02x: %d\n",
+                       reg, charge);
                return charge;
        }
 
@@ -208,7 +215,7 @@ static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di)
                ilmd = bq27x00_read(di, BQ27000_REG_ILMD, true);
 
        if (ilmd < 0) {
-               dev_err(di->dev, "error reading initial last measured discharge\n");
+               dev_dbg(di->dev, "error reading initial last measured discharge\n");
                return ilmd;
        }
 
@@ -220,6 +227,50 @@ static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di)
        return ilmd;
 }
 
+/*
+ * Return the battery Available energy in ÂµWh
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_energy(struct bq27x00_device_info *di)
+{
+       int ae;
+
+       ae = bq27x00_read(di, BQ27x00_REG_AE, false);
+       if (ae < 0) {
+               dev_dbg(di->dev, "error reading available energy\n");
+               return ae;
+       }
+
+       if (di->chip == BQ27500)
+               ae *= 1000;
+       else
+               ae = ae * 29200 / BQ27000_RS;
+
+       return ae;
+}
+
+/*
+ * Return the battery temperature in tenths of degree Celsius
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di)
+{
+       int temp;
+
+       temp = bq27x00_read(di, BQ27x00_REG_TEMP, false);
+       if (temp < 0) {
+               dev_err(di->dev, "error reading temperature\n");
+               return temp;
+       }
+
+       if (di->chip == BQ27500)
+               temp -= 2731;
+       else
+               temp = ((temp * 5) - 5463) / 2;
+
+       return temp;
+}
+
 /*
  * Return the battery Cycle count total
  * Or < 0 if something fails.
@@ -245,7 +296,8 @@ static int bq27x00_battery_read_time(struct bq27x00_device_info *di, u8 reg)
 
        tval = bq27x00_read(di, reg, false);
        if (tval < 0) {
-               dev_err(di->dev, "error reading register %02x: %d\n", reg, tval);
+               dev_dbg(di->dev, "error reading time register %02x: %d\n",
+                       reg, tval);
                return tval;
        }
 
@@ -262,25 +314,31 @@ static void bq27x00_update(struct bq27x00_device_info *di)
 
        cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, is_bq27500);
        if (cache.flags >= 0) {
-               cache.capacity = bq27x00_battery_read_rsoc(di);
-               cache.temperature = bq27x00_read(di, BQ27x00_REG_TEMP, false);
-               cache.time_to_empty = bq27x00_battery_read_time(di, BQ27x00_REG_TTE);
-               cache.time_to_empty_avg = bq27x00_battery_read_time(di, BQ27x00_REG_TTECP);
-               cache.time_to_full = bq27x00_battery_read_time(di, BQ27x00_REG_TTF);
-               cache.charge_full = bq27x00_battery_read_lmd(di);
+               if (!is_bq27500 && (cache.flags & BQ27000_FLAG_CI)) {
+                       dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n");
+                       cache.capacity = -ENODATA;
+                       cache.energy = -ENODATA;
+                       cache.time_to_empty = -ENODATA;
+                       cache.time_to_empty_avg = -ENODATA;
+                       cache.time_to_full = -ENODATA;
+                       cache.charge_full = -ENODATA;
+               } else {
+                       cache.capacity = bq27x00_battery_read_rsoc(di);
+                       cache.energy = bq27x00_battery_read_energy(di);
+                       cache.time_to_empty = bq27x00_battery_read_time(di, BQ27x00_REG_TTE);
+                       cache.time_to_empty_avg = bq27x00_battery_read_time(di, BQ27x00_REG_TTECP);
+                       cache.time_to_full = bq27x00_battery_read_time(di, BQ27x00_REG_TTF);
+                       cache.charge_full = bq27x00_battery_read_lmd(di);
+               }
+               cache.temperature = bq27x00_battery_read_temperature(di);
                cache.cycle_count = bq27x00_battery_read_cyct(di);
 
-               if (!is_bq27500)
-                       cache.current_now = bq27x00_read(di, BQ27x00_REG_AI, false);
-
                /* We only have to read charge design full once */
                if (di->charge_design_full <= 0)
                        di->charge_design_full = bq27x00_battery_read_ilmd(di);
        }
 
-       /* Ignore current_now which is a snapshot of the current battery state
-        * and is likely to be different even between two consecutive reads */
-       if (memcmp(&di->cache, &cache, sizeof(cache) - sizeof(int)) != 0) {
+       if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) {
                di->cache = cache;
                power_supply_changed(&di->bat);
        }
@@ -302,25 +360,6 @@ static void bq27x00_battery_poll(struct work_struct *work)
        }
 }
 
-
-/*
- * Return the battery temperature in tenths of degree Celsius
- * Or < 0 if something fails.
- */
-static int bq27x00_battery_temperature(struct bq27x00_device_info *di,
-       union power_supply_propval *val)
-{
-       if (di->cache.temperature < 0)
-               return di->cache.temperature;
-
-       if (di->chip == BQ27500)
-               val->intval = di->cache.temperature - 2731;
-       else
-               val->intval = ((di->cache.temperature * 5) - 5463) / 2;
-
-       return 0;
-}
-
 /*
  * Return the battery average current in ÂµA
  * Note that current can be negative signed as well
@@ -330,20 +369,20 @@ static int bq27x00_battery_current(struct bq27x00_device_info *di,
        union power_supply_propval *val)
 {
        int curr;
+       int flags;
 
-       if (di->chip == BQ27500)
-           curr = bq27x00_read(di, BQ27x00_REG_AI, false);
-       else
-           curr = di->cache.current_now;
-
-       if (curr < 0)
+       curr = bq27x00_read(di, BQ27x00_REG_AI, false);
+       if (curr < 0) {
+               dev_err(di->dev, "error reading current\n");
                return curr;
+       }
 
        if (di->chip == BQ27500) {
                /* bq27500 returns signed value */
                val->intval = (int)((s16)curr) * 1000;
        } else {
-               if (di->cache.flags & BQ27000_FLAG_CHGS) {
+               flags = bq27x00_read(di, BQ27x00_REG_FLAGS, false);
+               if (flags & BQ27000_FLAG_CHGS) {
                        dev_dbg(di->dev, "negative current!\n");
                        curr = -curr;
                }
@@ -362,10 +401,14 @@ static int bq27x00_battery_status(struct bq27x00_device_info *di,
        if (di->chip == BQ27500) {
                if (di->cache.flags & BQ27500_FLAG_FC)
                        status = POWER_SUPPLY_STATUS_FULL;
-               else if (di->cache.flags & BQ27500_FLAG_DSC)
+               else if (di->cache.flags & BQ27500_FLAG_DSG)
                        status = POWER_SUPPLY_STATUS_DISCHARGING;
-               else
+               else if (di->cache.flags & BQ27500_FLAG_CHG)
                        status = POWER_SUPPLY_STATUS_CHARGING;
+               else if (power_supply_am_i_supplied(&di->bat))
+                       status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               else
+                       status = POWER_SUPPLY_STATUS_UNKNOWN;
        } else {
                if (di->cache.flags & BQ27000_FLAG_FC)
                        status = POWER_SUPPLY_STATUS_FULL;
@@ -382,50 +425,56 @@ static int bq27x00_battery_status(struct bq27x00_device_info *di,
        return 0;
 }
 
-/*
- * Return the battery Voltage in milivolts
- * Or < 0 if something fails.
- */
-static int bq27x00_battery_voltage(struct bq27x00_device_info *di,
+static int bq27x00_battery_capacity_level(struct bq27x00_device_info *di,
        union power_supply_propval *val)
 {
-       int volt;
+       int level;
 
-       volt = bq27x00_read(di, BQ27x00_REG_VOLT, false);
-       if (volt < 0)
-               return volt;
+       if (di->chip == BQ27500) {
+               if (di->cache.flags & BQ27500_FLAG_FC)
+                       level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+               else if (di->cache.flags & BQ27500_FLAG_SOC1)
+                       level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+               else if (di->cache.flags & BQ27500_FLAG_SOCF)
+                       level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+               else
+                       level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+       } else {
+               if (di->cache.flags & BQ27000_FLAG_FC)
+                       level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+               else if (di->cache.flags & BQ27000_FLAG_EDV1)
+                       level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+               else if (di->cache.flags & BQ27000_FLAG_EDVF)
+                       level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+               else
+                       level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+       }
 
-       val->intval = volt * 1000;
+       val->intval = level;
 
        return 0;
 }
 
 /*
- * Return the battery Available energy in ÂµWh
+ * Return the battery Voltage in milivolts
  * Or < 0 if something fails.
  */
-static int bq27x00_battery_energy(struct bq27x00_device_info *di,
+static int bq27x00_battery_voltage(struct bq27x00_device_info *di,
        union power_supply_propval *val)
 {
-       int ae;
+       int volt;
 
-       ae = bq27x00_read(di, BQ27x00_REG_AE, false);
-       if (ae < 0) {
-               dev_err(di->dev, "error reading available energy\n");
-               return ae;
+       volt = bq27x00_read(di, BQ27x00_REG_VOLT, false);
+       if (volt < 0) {
+               dev_err(di->dev, "error reading voltage\n");
+               return volt;
        }
 
-       if (di->chip == BQ27500)
-               ae *= 1000;
-       else
-               ae = ae * 29200 / BQ27000_RS;
-
-       val->intval = ae;
+       val->intval = volt * 1000;
 
        return 0;
 }
 
-
 static int bq27x00_simple_value(int value,
        union power_supply_propval *val)
 {
@@ -473,8 +522,11 @@ static int bq27x00_battery_get_property(struct power_supply *psy,
        case POWER_SUPPLY_PROP_CAPACITY:
                ret = bq27x00_simple_value(di->cache.capacity, val);
                break;
+       case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+               ret = bq27x00_battery_capacity_level(di, val);
+               break;
        case POWER_SUPPLY_PROP_TEMP:
-               ret = bq27x00_battery_temperature(di, val);
+               ret = bq27x00_simple_value(di->cache.temperature, val);
                break;
        case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
                ret = bq27x00_simple_value(di->cache.time_to_empty, val);
@@ -501,7 +553,7 @@ static int bq27x00_battery_get_property(struct power_supply *psy,
                ret = bq27x00_simple_value(di->cache.cycle_count, val);
                break;
        case POWER_SUPPLY_PROP_ENERGY_NOW:
-               ret = bq27x00_battery_energy(di, val);
+               ret = bq27x00_simple_value(di->cache.energy, val);
                break;
        default:
                return -EINVAL;
@@ -546,6 +598,14 @@ static int bq27x00_powersupply_init(struct bq27x00_device_info *di)
 
 static void bq27x00_powersupply_unregister(struct bq27x00_device_info *di)
 {
+       /*
+        * power_supply_unregister call bq27x00_battery_get_property which
+        * call bq27x00_battery_poll.
+        * Make sure that bq27x00_battery_poll will not call
+        * schedule_delayed_work again after unregister (which cause OOPS).
+        */
+       poll_interval = 0;
+
        cancel_delayed_work_sync(&di->work);
 
        power_supply_unregister(&di->bat);
diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c
new file mode 100644 (file)
index 0000000..0378d01
--- /dev/null
@@ -0,0 +1,1072 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * This driver enables to monitor battery health and control charger
+ * during suspend-to-mem.
+ * Charger manager depends on other devices. register this later than
+ * the depending devices.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+**/
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/power/charger-manager.h>
+#include <linux/regulator/consumer.h>
+
+/*
+ * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
+ * delayed works so that we can run delayed works with CM_JIFFIES_SMALL
+ * without any delays.
+ */
+#define        CM_JIFFIES_SMALL        (2)
+
+/* If y is valid (> 0) and smaller than x, do x = y */
+#define CM_MIN_VALID(x, y)     x = (((y > 0) && ((x) > (y))) ? (y) : (x))
+
+/*
+ * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking
+ * rtc alarm. It should be 2 or larger
+ */
+#define CM_RTC_SMALL           (2)
+
+#define UEVENT_BUF_SIZE                32
+
+static LIST_HEAD(cm_list);
+static DEFINE_MUTEX(cm_list_mtx);
+
+/* About in-suspend (suspend-again) monitoring */
+static struct rtc_device *rtc_dev;
+/*
+ * Backup RTC alarm
+ * Save the wakeup alarm before entering suspend-to-RAM
+ */
+static struct rtc_wkalrm rtc_wkalarm_save;
+/* Backup RTC alarm time in terms of seconds since 01-01-1970 00:00:00 */
+static unsigned long rtc_wkalarm_save_time;
+static bool cm_suspended;
+static bool cm_rtc_set;
+static unsigned long cm_suspend_duration_ms;
+
+/* Global charger-manager description */
+static struct charger_global_desc *g_desc; /* init with setup_charger_manager */
+
+/**
+ * is_batt_present - See if the battery presents in place.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_batt_present(struct charger_manager *cm)
+{
+       union power_supply_propval val;
+       bool present = false;
+       int i, ret;
+
+       switch (cm->desc->battery_present) {
+       case CM_FUEL_GAUGE:
+               ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+                               POWER_SUPPLY_PROP_PRESENT, &val);
+               if (ret == 0 && val.intval)
+                       present = true;
+               break;
+       case CM_CHARGER_STAT:
+               for (i = 0; cm->charger_stat[i]; i++) {
+                       ret = cm->charger_stat[i]->get_property(
+                                       cm->charger_stat[i],
+                                       POWER_SUPPLY_PROP_PRESENT, &val);
+                       if (ret == 0 && val.intval) {
+                               present = true;
+                               break;
+                       }
+               }
+               break;
+       }
+
+       return present;
+}
+
+/**
+ * is_ext_pwr_online - See if an external power source is attached to charge
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Returns true if at least one of the chargers of the battery has an external
+ * power source attached to charge the battery regardless of whether it is
+ * actually charging or not.
+ */
+static bool is_ext_pwr_online(struct charger_manager *cm)
+{
+       union power_supply_propval val;
+       bool online = false;
+       int i, ret;
+
+       /* If at least one of them has one, it's yes. */
+       for (i = 0; cm->charger_stat[i]; i++) {
+               ret = cm->charger_stat[i]->get_property(
+                               cm->charger_stat[i],
+                               POWER_SUPPLY_PROP_ONLINE, &val);
+               if (ret == 0 && val.intval) {
+                       online = true;
+                       break;
+               }
+       }
+
+       return online;
+}
+
+/**
+ * get_batt_uV - Get the voltage level of the battery
+ * @cm: the Charger Manager representing the battery.
+ * @uV: the voltage level returned.
+ *
+ * Returns 0 if there is no error.
+ * Returns a negative value on error.
+ */
+static int get_batt_uV(struct charger_manager *cm, int *uV)
+{
+       union power_supply_propval val;
+       int ret;
+
+       if (cm->fuel_gauge)
+               ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+                               POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
+       else
+               return -ENODEV;
+
+       if (ret)
+               return ret;
+
+       *uV = val.intval;
+       return 0;
+}
+
+/**
+ * is_charging - Returns true if the battery is being charged.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_charging(struct charger_manager *cm)
+{
+       int i, ret;
+       bool charging = false;
+       union power_supply_propval val;
+
+       /* If there is no battery, it cannot be charged */
+       if (!is_batt_present(cm))
+               return false;
+
+       /* If at least one of the charger is charging, return yes */
+       for (i = 0; cm->charger_stat[i]; i++) {
+               /* 1. The charger sholuld not be DISABLED */
+               if (cm->emergency_stop)
+                       continue;
+               if (!cm->charger_enabled)
+                       continue;
+
+               /* 2. The charger should be online (ext-power) */
+               ret = cm->charger_stat[i]->get_property(
+                               cm->charger_stat[i],
+                               POWER_SUPPLY_PROP_ONLINE, &val);
+               if (ret) {
+                       dev_warn(cm->dev, "Cannot read ONLINE value from %s.\n",
+                                       cm->desc->psy_charger_stat[i]);
+                       continue;
+               }
+               if (val.intval == 0)
+                       continue;
+
+               /*
+                * 3. The charger should not be FULL, DISCHARGING,
+                * or NOT_CHARGING.
+                */
+               ret = cm->charger_stat[i]->get_property(
+                               cm->charger_stat[i],
+                               POWER_SUPPLY_PROP_STATUS, &val);
+               if (ret) {
+                       dev_warn(cm->dev, "Cannot read STATUS value from %s.\n",
+                                       cm->desc->psy_charger_stat[i]);
+                       continue;
+               }
+               if (val.intval == POWER_SUPPLY_STATUS_FULL ||
+                               val.intval == POWER_SUPPLY_STATUS_DISCHARGING ||
+                               val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING)
+                       continue;
+
+               /* Then, this is charging. */
+               charging = true;
+               break;
+       }
+
+       return charging;
+}
+
+/**
+ * is_polling_required - Return true if need to continue polling for this CM.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_polling_required(struct charger_manager *cm)
+{
+       switch (cm->desc->polling_mode) {
+       case CM_POLL_DISABLE:
+               return false;
+       case CM_POLL_ALWAYS:
+               return true;
+       case CM_POLL_EXTERNAL_POWER_ONLY:
+               return is_ext_pwr_online(cm);
+       case CM_POLL_CHARGING_ONLY:
+               return is_charging(cm);
+       default:
+               dev_warn(cm->dev, "Incorrect polling_mode (%d)\n",
+                       cm->desc->polling_mode);
+       }
+
+       return false;
+}
+
+/**
+ * try_charger_enable - Enable/Disable chargers altogether
+ * @cm: the Charger Manager representing the battery.
+ * @enable: true: enable / false: disable
+ *
+ * Note that Charger Manager keeps the charger enabled regardless whether
+ * the charger is charging or not (because battery is full or no external
+ * power source exists) except when CM needs to disable chargers forcibly
+ * bacause of emergency causes; when the battery is overheated or too cold.
+ */
+static int try_charger_enable(struct charger_manager *cm, bool enable)
+{
+       int err = 0, i;
+       struct charger_desc *desc = cm->desc;
+
+       /* Ignore if it's redundent command */
+       if (enable && cm->charger_enabled)
+               return 0;
+       if (!enable && !cm->charger_enabled)
+               return 0;
+
+       if (enable) {
+               if (cm->emergency_stop)
+                       return -EAGAIN;
+               err = regulator_bulk_enable(desc->num_charger_regulators,
+                                       desc->charger_regulators);
+       } else {
+               /*
+                * Abnormal battery state - Stop charging forcibly,
+                * even if charger was enabled at the other places
+                */
+               err = regulator_bulk_disable(desc->num_charger_regulators,
+                                       desc->charger_regulators);
+
+               for (i = 0; i < desc->num_charger_regulators; i++) {
+                       if (regulator_is_enabled(
+                                   desc->charger_regulators[i].consumer)) {
+                               regulator_force_disable(
+                                       desc->charger_regulators[i].consumer);
+                               dev_warn(cm->dev,
+                                       "Disable regulator(%s) forcibly.\n",
+                                       desc->charger_regulators[i].supply);
+                       }
+               }
+       }
+
+       if (!err)
+               cm->charger_enabled = enable;
+
+       return err;
+}
+
+/**
+ * uevent_notify - Let users know something has changed.
+ * @cm: the Charger Manager representing the battery.
+ * @event: the event string.
+ *
+ * If @event is null, it implies that uevent_notify is called
+ * by resume function. When called in the resume function, cm_suspended
+ * should be already reset to false in order to let uevent_notify
+ * notify the recent event during the suspend to users. While
+ * suspended, uevent_notify does not notify users, but tracks
+ * events so that uevent_notify can notify users later after resumed.
+ */
+static void uevent_notify(struct charger_manager *cm, const char *event)
+{
+       static char env_str[UEVENT_BUF_SIZE + 1] = "";
+       static char env_str_save[UEVENT_BUF_SIZE + 1] = "";
+
+       if (cm_suspended) {
+               /* Nothing in suspended-event buffer */
+               if (env_str_save[0] == 0) {
+                       if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
+                               return; /* status not changed */
+                       strncpy(env_str_save, event, UEVENT_BUF_SIZE);
+                       return;
+               }
+
+               if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE))
+                       return; /* Duplicated. */
+               else
+                       strncpy(env_str_save, event, UEVENT_BUF_SIZE);
+
+               return;
+       }
+
+       if (event == NULL) {
+               /* No messages pending */
+               if (!env_str_save[0])
+                       return;
+
+               strncpy(env_str, env_str_save, UEVENT_BUF_SIZE);
+               kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
+               env_str_save[0] = 0;
+
+               return;
+       }
+
+       /* status not changed */
+       if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
+               return;
+
+       /* save the status and notify the update */
+       strncpy(env_str, event, UEVENT_BUF_SIZE);
+       kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
+
+       dev_info(cm->dev, event);
+}
+
+/**
+ * _cm_monitor - Monitor the temperature and return true for exceptions.
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Returns true if there is an event to notify for the battery.
+ * (True if the status of "emergency_stop" changes)
+ */
+static bool _cm_monitor(struct charger_manager *cm)
+{
+       struct charger_desc *desc = cm->desc;
+       int temp = desc->temperature_out_of_range(&cm->last_temp_mC);
+
+       dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n",
+               cm->last_temp_mC / 1000, cm->last_temp_mC % 1000);
+
+       /* It has been stopped or charging already */
+       if (!!temp == !!cm->emergency_stop)
+               return false;
+
+       if (temp) {
+               cm->emergency_stop = temp;
+               if (!try_charger_enable(cm, false)) {
+                       if (temp > 0)
+                               uevent_notify(cm, "OVERHEAT");
+                       else
+                               uevent_notify(cm, "COLD");
+               }
+       } else {
+               cm->emergency_stop = 0;
+               if (!try_charger_enable(cm, true))
+                       uevent_notify(cm, "CHARGING");
+       }
+
+       return true;
+}
+
+/**
+ * cm_monitor - Monitor every battery.
+ *
+ * Returns true if there is an event to notify from any of the batteries.
+ * (True if the status of "emergency_stop" changes)
+ */
+static bool cm_monitor(void)
+{
+       bool stop = false;
+       struct charger_manager *cm;
+
+       mutex_lock(&cm_list_mtx);
+
+       list_for_each_entry(cm, &cm_list, entry)
+               stop = stop || _cm_monitor(cm);
+
+       mutex_unlock(&cm_list_mtx);
+
+       return stop;
+}
+
+static int charger_get_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               union power_supply_propval *val)
+{
+       struct charger_manager *cm = container_of(psy,
+                       struct charger_manager, charger_psy);
+       struct charger_desc *desc = cm->desc;
+       int i, ret = 0, uV;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               if (is_charging(cm))
+                       val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               else if (is_ext_pwr_online(cm))
+                       val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               else
+                       val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               if (cm->emergency_stop > 0)
+                       val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+               else if (cm->emergency_stop < 0)
+                       val->intval = POWER_SUPPLY_HEALTH_COLD;
+               else
+                       val->intval = POWER_SUPPLY_HEALTH_GOOD;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               if (is_batt_present(cm))
+                       val->intval = 1;
+               else
+                       val->intval = 0;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               ret = get_batt_uV(cm, &i);
+               val->intval = i;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+                               POWER_SUPPLY_PROP_CURRENT_NOW, val);
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+               /* in thenth of centigrade */
+               if (cm->last_temp_mC == INT_MIN)
+                       desc->temperature_out_of_range(&cm->last_temp_mC);
+               val->intval = cm->last_temp_mC / 100;
+               if (!desc->measure_battery_temp)
+                       ret = -ENODEV;
+               break;
+       case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+               /* in thenth of centigrade */
+               if (cm->last_temp_mC == INT_MIN)
+                       desc->temperature_out_of_range(&cm->last_temp_mC);
+               val->intval = cm->last_temp_mC / 100;
+               if (desc->measure_battery_temp)
+                       ret = -ENODEV;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               if (!cm->fuel_gauge) {
+                       ret = -ENODEV;
+                       break;
+               }
+
+               if (!is_batt_present(cm)) {
+                       /* There is no battery. Assume 100% */
+                       val->intval = 100;
+                       break;
+               }
+
+               ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+                                       POWER_SUPPLY_PROP_CAPACITY, val);
+               if (ret)
+                       break;
+
+               if (val->intval > 100) {
+                       val->intval = 100;
+                       break;
+               }
+               if (val->intval < 0)
+                       val->intval = 0;
+
+               /* Do not adjust SOC when charging: voltage is overrated */
+               if (is_charging(cm))
+                       break;
+
+               /*
+                * If the capacity value is inconsistent, calibrate it base on
+                * the battery voltage values and the thresholds given as desc
+                */
+               ret = get_batt_uV(cm, &uV);
+               if (ret) {
+                       /* Voltage information not available. No calibration */
+                       ret = 0;
+                       break;
+               }
+
+               if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV &&
+                   !is_charging(cm)) {
+                       val->intval = 100;
+                       break;
+               }
+
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               if (is_ext_pwr_online(cm))
+                       val->intval = 1;
+               else
+                       val->intval = 0;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               if (cm->fuel_gauge) {
+                       if (cm->fuel_gauge->get_property(cm->fuel_gauge,
+                           POWER_SUPPLY_PROP_CHARGE_FULL, val) == 0)
+                               break;
+               }
+
+               if (is_ext_pwr_online(cm)) {
+                       /* Not full if it's charging. */
+                       if (is_charging(cm)) {
+                               val->intval = 0;
+                               break;
+                       }
+                       /*
+                        * Full if it's powered but not charging andi
+                        * not forced stop by emergency
+                        */
+                       if (!cm->emergency_stop) {
+                               val->intval = 1;
+                               break;
+                       }
+               }
+
+               /* Full if it's over the fullbatt voltage */
+               ret = get_batt_uV(cm, &uV);
+               if (!ret && desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV &&
+                   !is_charging(cm)) {
+                       val->intval = 1;
+                       break;
+               }
+
+               /* Full if the cap is 100 */
+               if (cm->fuel_gauge) {
+                       ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+                                       POWER_SUPPLY_PROP_CAPACITY, val);
+                       if (!ret && val->intval >= 100 && !is_charging(cm)) {
+                               val->intval = 1;
+                               break;
+                       }
+               }
+
+               val->intval = 0;
+               ret = 0;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_NOW:
+               if (is_charging(cm)) {
+                       ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+                                               POWER_SUPPLY_PROP_CHARGE_NOW,
+                                               val);
+                       if (ret) {
+                               val->intval = 1;
+                               ret = 0;
+                       } else {
+                               /* If CHARGE_NOW is supplied, use it */
+                               val->intval = (val->intval > 0) ?
+                                               val->intval : 1;
+                       }
+               } else {
+                       val->intval = 0;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+       return ret;
+}
+
+#define NUM_CHARGER_PSY_OPTIONAL       (4)
+static enum power_supply_property default_charger_props[] = {
+       /* Guaranteed to provide */
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_CHARGE_FULL,
+       /*
+        * Optional properties are:
+        * POWER_SUPPLY_PROP_CHARGE_NOW,
+        * POWER_SUPPLY_PROP_CURRENT_NOW,
+        * POWER_SUPPLY_PROP_TEMP, and
+        * POWER_SUPPLY_PROP_TEMP_AMBIENT,
+        */
+};
+
+static struct power_supply psy_default = {
+       .name = "battery",
+       .type = POWER_SUPPLY_TYPE_BATTERY,
+       .properties = default_charger_props,
+       .num_properties = ARRAY_SIZE(default_charger_props),
+       .get_property = charger_get_property,
+};
+
+/**
+ * cm_setup_timer - For in-suspend monitoring setup wakeup alarm
+ *                 for suspend_again.
+ *
+ * Returns true if the alarm is set for Charger Manager to use.
+ * Returns false if
+ *     cm_setup_timer fails to set an alarm,
+ *     cm_setup_timer does not need to set an alarm for Charger Manager,
+ *     or an alarm previously configured is to be used.
+ */
+static bool cm_setup_timer(void)
+{
+       struct charger_manager *cm;
+       unsigned int wakeup_ms = UINT_MAX;
+       bool ret = false;
+
+       mutex_lock(&cm_list_mtx);
+
+       list_for_each_entry(cm, &cm_list, entry) {
+               /* Skip if polling is not required for this CM */
+               if (!is_polling_required(cm) && !cm->emergency_stop)
+                       continue;
+               if (cm->desc->polling_interval_ms == 0)
+                       continue;
+               CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms);
+       }
+
+       mutex_unlock(&cm_list_mtx);
+
+       if (wakeup_ms < UINT_MAX && wakeup_ms > 0) {
+               pr_info("Charger Manager wakeup timer: %u ms.\n", wakeup_ms);
+               if (rtc_dev) {
+                       struct rtc_wkalrm tmp;
+                       unsigned long time, now;
+                       unsigned long add = DIV_ROUND_UP(wakeup_ms, 1000);
+
+                       /*
+                        * Set alarm with the polling interval (wakeup_ms)
+                        * except when rtc_wkalarm_save comes first.
+                        * However, the alarm time should be NOW +
+                        * CM_RTC_SMALL or later.
+                        */
+                       tmp.enabled = 1;
+                       rtc_read_time(rtc_dev, &tmp.time);
+                       rtc_tm_to_time(&tmp.time, &now);
+                       if (add < CM_RTC_SMALL)
+                               add = CM_RTC_SMALL;
+                       time = now + add;
+
+                       ret = true;
+
+                       if (rtc_wkalarm_save.enabled &&
+                           rtc_wkalarm_save_time &&
+                           rtc_wkalarm_save_time < time) {
+                               if (rtc_wkalarm_save_time < now + CM_RTC_SMALL)
+                                       time = now + CM_RTC_SMALL;
+                               else
+                                       time = rtc_wkalarm_save_time;
+
+                               /* The timer is not appointed by CM */
+                               ret = false;
+                       }
+
+                       pr_info("Waking up after %lu secs.\n",
+                                       time - now);
+
+                       rtc_time_to_tm(time, &tmp.time);
+                       rtc_set_alarm(rtc_dev, &tmp);
+                       cm_suspend_duration_ms += wakeup_ms;
+                       return ret;
+               }
+       }
+
+       if (rtc_dev)
+               rtc_set_alarm(rtc_dev, &rtc_wkalarm_save);
+       return false;
+}
+
+/**
+ * cm_suspend_again - Determine whether suspend again or not
+ *
+ * Returns true if the system should be suspended again
+ * Returns false if the system should be woken up
+ */
+bool cm_suspend_again(void)
+{
+       struct charger_manager *cm;
+       bool ret = false;
+
+       if (!g_desc || !g_desc->rtc_only_wakeup || !g_desc->rtc_only_wakeup() ||
+           !cm_rtc_set)
+               return false;
+
+       if (cm_monitor())
+               goto out;
+
+       ret = true;
+       mutex_lock(&cm_list_mtx);
+       list_for_each_entry(cm, &cm_list, entry) {
+               if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) ||
+                   cm->status_save_batt != is_batt_present(cm))
+                       ret = false;
+       }
+       mutex_unlock(&cm_list_mtx);
+
+       cm_rtc_set = cm_setup_timer();
+out:
+       /* It's about the time when the non-CM appointed timer goes off */
+       if (rtc_wkalarm_save.enabled) {
+               unsigned long now;
+               struct rtc_time tmp;
+
+               rtc_read_time(rtc_dev, &tmp);
+               rtc_tm_to_time(&tmp, &now);
+
+               if (rtc_wkalarm_save_time &&
+                   now + CM_RTC_SMALL >= rtc_wkalarm_save_time)
+                       return false;
+       }
+       return ret;
+}
+EXPORT_SYMBOL_GPL(cm_suspend_again);
+
+/**
+ * setup_charger_manager - initialize charger_global_desc data
+ * @gd: pointer to instance of charger_global_desc
+ */
+int setup_charger_manager(struct charger_global_desc *gd)
+{
+       if (!gd)
+               return -EINVAL;
+
+       if (rtc_dev)
+               rtc_class_close(rtc_dev);
+       rtc_dev = NULL;
+       g_desc = NULL;
+
+       if (!gd->rtc_only_wakeup) {
+               pr_err("The callback rtc_only_wakeup is not given.\n");
+               return -EINVAL;
+       }
+
+       if (gd->rtc_name) {
+               rtc_dev = rtc_class_open(gd->rtc_name);
+               if (IS_ERR_OR_NULL(rtc_dev)) {
+                       rtc_dev = NULL;
+                       /* Retry at probe. RTC may be not registered yet */
+               }
+       } else {
+               pr_warn("No wakeup timer is given for charger manager."
+                       "In-suspend monitoring won't work.\n");
+       }
+
+       g_desc = gd;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(setup_charger_manager);
+
+static int charger_manager_probe(struct platform_device *pdev)
+{
+       struct charger_desc *desc = dev_get_platdata(&pdev->dev);
+       struct charger_manager *cm;
+       int ret = 0, i = 0;
+       union power_supply_propval val;
+
+       if (g_desc && !rtc_dev && g_desc->rtc_name) {
+               rtc_dev = rtc_class_open(g_desc->rtc_name);
+               if (IS_ERR_OR_NULL(rtc_dev)) {
+                       rtc_dev = NULL;
+                       dev_err(&pdev->dev, "Cannot get RTC %s.\n",
+                               g_desc->rtc_name);
+                       ret = -ENODEV;
+                       goto err_alloc;
+               }
+       }
+
+       if (!desc) {
+               dev_err(&pdev->dev, "No platform data (desc) found.\n");
+               ret = -ENODEV;
+               goto err_alloc;
+       }
+
+       cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL);
+       if (!cm) {
+               dev_err(&pdev->dev, "Cannot allocate memory.\n");
+               ret = -ENOMEM;
+               goto err_alloc;
+       }
+
+       /* Basic Values. Unspecified are Null or 0 */
+       cm->dev = &pdev->dev;
+       cm->desc = kzalloc(sizeof(struct charger_desc), GFP_KERNEL);
+       if (!cm->desc) {
+               dev_err(&pdev->dev, "Cannot allocate memory.\n");
+               ret = -ENOMEM;
+               goto err_alloc_desc;
+       }
+       memcpy(cm->desc, desc, sizeof(struct charger_desc));
+       cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */
+
+       if (!desc->charger_regulators || desc->num_charger_regulators < 1) {
+               ret = -EINVAL;
+               dev_err(&pdev->dev, "charger_regulators undefined.\n");
+               goto err_no_charger;
+       }
+
+       if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) {
+               dev_err(&pdev->dev, "No power supply defined.\n");
+               ret = -EINVAL;
+               goto err_no_charger_stat;
+       }
+
+       /* Counting index only */
+       while (desc->psy_charger_stat[i])
+               i++;
+
+       cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1),
+                                  GFP_KERNEL);
+       if (!cm->charger_stat) {
+               ret = -ENOMEM;
+               goto err_no_charger_stat;
+       }
+
+       for (i = 0; desc->psy_charger_stat[i]; i++) {
+               cm->charger_stat[i] = power_supply_get_by_name(
+                                       desc->psy_charger_stat[i]);
+               if (!cm->charger_stat[i]) {
+                       dev_err(&pdev->dev, "Cannot find power supply "
+                                       "\"%s\"\n",
+                                       desc->psy_charger_stat[i]);
+                       ret = -ENODEV;
+                       goto err_chg_stat;
+               }
+       }
+
+       cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
+       if (!cm->fuel_gauge) {
+               dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
+                               desc->psy_fuel_gauge);
+               ret = -ENODEV;
+               goto err_chg_stat;
+       }
+
+       if (desc->polling_interval_ms == 0 ||
+           msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) {
+               dev_err(&pdev->dev, "polling_interval_ms is too small\n");
+               ret = -EINVAL;
+               goto err_chg_stat;
+       }
+
+       if (!desc->temperature_out_of_range) {
+               dev_err(&pdev->dev, "there is no temperature_out_of_range\n");
+               ret = -EINVAL;
+               goto err_chg_stat;
+       }
+
+       platform_set_drvdata(pdev, cm);
+
+       memcpy(&cm->charger_psy, &psy_default,
+                               sizeof(psy_default));
+       if (!desc->psy_name) {
+               strncpy(cm->psy_name_buf, psy_default.name,
+                               PSY_NAME_MAX);
+       } else {
+               strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX);
+       }
+       cm->charger_psy.name = cm->psy_name_buf;
+
+       /* Allocate for psy properties because they may vary */
+       cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property)
+                               * (ARRAY_SIZE(default_charger_props) +
+                               NUM_CHARGER_PSY_OPTIONAL),
+                               GFP_KERNEL);
+       if (!cm->charger_psy.properties) {
+               dev_err(&pdev->dev, "Cannot allocate for psy properties.\n");
+               ret = -ENOMEM;
+               goto err_chg_stat;
+       }
+       memcpy(cm->charger_psy.properties, default_charger_props,
+               sizeof(enum power_supply_property) *
+               ARRAY_SIZE(default_charger_props));
+       cm->charger_psy.num_properties = psy_default.num_properties;
+
+       /* Find which optional psy-properties are available */
+       if (!cm->fuel_gauge->get_property(cm->fuel_gauge,
+                                         POWER_SUPPLY_PROP_CHARGE_NOW, &val)) {
+               cm->charger_psy.properties[cm->charger_psy.num_properties] =
+                               POWER_SUPPLY_PROP_CHARGE_NOW;
+               cm->charger_psy.num_properties++;
+       }
+       if (!cm->fuel_gauge->get_property(cm->fuel_gauge,
+                                         POWER_SUPPLY_PROP_CURRENT_NOW,
+                                         &val)) {
+               cm->charger_psy.properties[cm->charger_psy.num_properties] =
+                               POWER_SUPPLY_PROP_CURRENT_NOW;
+               cm->charger_psy.num_properties++;
+       }
+       if (!desc->measure_battery_temp) {
+               cm->charger_psy.properties[cm->charger_psy.num_properties] =
+                               POWER_SUPPLY_PROP_TEMP_AMBIENT;
+               cm->charger_psy.num_properties++;
+       }
+       if (desc->measure_battery_temp) {
+               cm->charger_psy.properties[cm->charger_psy.num_properties] =
+                               POWER_SUPPLY_PROP_TEMP;
+               cm->charger_psy.num_properties++;
+       }
+
+       ret = power_supply_register(NULL, &cm->charger_psy);
+       if (ret) {
+               dev_err(&pdev->dev, "Cannot register charger-manager with"
+                               " name \"%s\".\n", cm->charger_psy.name);
+               goto err_register;
+       }
+
+       ret = regulator_bulk_get(&pdev->dev, desc->num_charger_regulators,
+                                desc->charger_regulators);
+       if (ret) {
+               dev_err(&pdev->dev, "Cannot get charger regulators.\n");
+               goto err_bulk_get;
+       }
+
+       ret = try_charger_enable(cm, true);
+       if (ret) {
+               dev_err(&pdev->dev, "Cannot enable charger regulators\n");
+               goto err_chg_enable;
+       }
+
+       /* Add to the list */
+       mutex_lock(&cm_list_mtx);
+       list_add(&cm->entry, &cm_list);
+       mutex_unlock(&cm_list_mtx);
+
+       return 0;
+
+err_chg_enable:
+       if (desc->charger_regulators)
+               regulator_bulk_free(desc->num_charger_regulators,
+                                       desc->charger_regulators);
+err_bulk_get:
+       power_supply_unregister(&cm->charger_psy);
+err_register:
+       kfree(cm->charger_psy.properties);
+err_chg_stat:
+       kfree(cm->charger_stat);
+err_no_charger_stat:
+err_no_charger:
+       kfree(cm->desc);
+err_alloc_desc:
+       kfree(cm);
+err_alloc:
+       return ret;
+}
+
+static int __devexit charger_manager_remove(struct platform_device *pdev)
+{
+       struct charger_manager *cm = platform_get_drvdata(pdev);
+       struct charger_desc *desc = cm->desc;
+
+       /* Remove from the list */
+       mutex_lock(&cm_list_mtx);
+       list_del(&cm->entry);
+       mutex_unlock(&cm_list_mtx);
+
+       if (desc->charger_regulators)
+               regulator_bulk_free(desc->num_charger_regulators,
+                                       desc->charger_regulators);
+
+       power_supply_unregister(&cm->charger_psy);
+       kfree(cm->charger_psy.properties);
+       kfree(cm->charger_stat);
+       kfree(cm->desc);
+       kfree(cm);
+
+       return 0;
+}
+
+const struct platform_device_id charger_manager_id[] = {
+       { "charger-manager", 0 },
+       { },
+};
+
+static int cm_suspend_prepare(struct device *dev)
+{
+       struct platform_device *pdev = container_of(dev, struct platform_device,
+                                                   dev);
+       struct charger_manager *cm = platform_get_drvdata(pdev);
+
+       if (!cm_suspended) {
+               if (rtc_dev) {
+                       struct rtc_time tmp;
+                       unsigned long now;
+
+                       rtc_read_alarm(rtc_dev, &rtc_wkalarm_save);
+                       rtc_read_time(rtc_dev, &tmp);
+
+                       if (rtc_wkalarm_save.enabled) {
+                               rtc_tm_to_time(&rtc_wkalarm_save.time,
+                                              &rtc_wkalarm_save_time);
+                               rtc_tm_to_time(&tmp, &now);
+                               if (now > rtc_wkalarm_save_time)
+                                       rtc_wkalarm_save_time = 0;
+                       } else {
+                               rtc_wkalarm_save_time = 0;
+                       }
+               }
+               cm_suspended = true;
+       }
+
+       cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm);
+       cm->status_save_batt = is_batt_present(cm);
+
+       if (!cm_rtc_set) {
+               cm_suspend_duration_ms = 0;
+               cm_rtc_set = cm_setup_timer();
+       }
+
+       return 0;
+}
+
+static void cm_suspend_complete(struct device *dev)
+{
+       struct platform_device *pdev = container_of(dev, struct platform_device,
+                                                   dev);
+       struct charger_manager *cm = platform_get_drvdata(pdev);
+
+       if (cm_suspended) {
+               if (rtc_dev) {
+                       struct rtc_wkalrm tmp;
+
+                       rtc_read_alarm(rtc_dev, &tmp);
+                       rtc_wkalarm_save.pending = tmp.pending;
+                       rtc_set_alarm(rtc_dev, &rtc_wkalarm_save);
+               }
+               cm_suspended = false;
+               cm_rtc_set = false;
+       }
+
+       uevent_notify(cm, NULL);
+}
+
+static const struct dev_pm_ops charger_manager_pm = {
+       .prepare        = cm_suspend_prepare,
+       .complete       = cm_suspend_complete,
+};
+
+static struct platform_driver charger_manager_driver = {
+       .driver = {
+               .name = "charger-manager",
+               .owner = THIS_MODULE,
+               .pm = &charger_manager_pm,
+       },
+       .probe = charger_manager_probe,
+       .remove = __devexit_p(charger_manager_remove),
+       .id_table = charger_manager_id,
+};
+
+static int __init charger_manager_init(void)
+{
+       return platform_driver_register(&charger_manager_driver);
+}
+late_initcall(charger_manager_init);
+
+static void __exit charger_manager_cleanup(void)
+{
+       platform_driver_unregister(&charger_manager_driver);
+}
+module_exit(charger_manager_cleanup);
+
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_DESCRIPTION("Charger Manager");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("charger-manager");
index 548d263b1ad0b1444cf65198c112e7c237b9f1bc..1b4c17b527c171c3527a4ce29e7703c205bc2945 100644 (file)
@@ -277,18 +277,13 @@ static struct collie_bat collie_bat_bu = {
        .adc_temp_divider = -1,
 };
 
-static struct {
-       int gpio;
-       char *name;
-       bool output;
-       int value;
-} gpios[] = {
-       { COLLIE_GPIO_CO,               "main battery full",    0, 0 },
-       { COLLIE_GPIO_MAIN_BAT_LOW,     "main battery low",     0, 0 },
-       { COLLIE_GPIO_CHARGE_ON,        "main charge on",       1, 0 },
-       { COLLIE_GPIO_MBAT_ON,          "main battery",         1, 0 },
-       { COLLIE_GPIO_TMP_ON,           "main battery temp",    1, 0 },
-       { COLLIE_GPIO_BBAT_ON,          "backup battery",       1, 0 },
+static struct gpio collie_batt_gpios[] = {
+       { COLLIE_GPIO_CO,           GPIOF_IN,           "main battery full" },
+       { COLLIE_GPIO_MAIN_BAT_LOW, GPIOF_IN,           "main battery low" },
+       { COLLIE_GPIO_CHARGE_ON,    GPIOF_OUT_INIT_LOW, "main charge on" },
+       { COLLIE_GPIO_MBAT_ON,      GPIOF_OUT_INIT_LOW, "main battery" },
+       { COLLIE_GPIO_TMP_ON,       GPIOF_OUT_INIT_LOW, "main battery temp" },
+       { COLLIE_GPIO_BBAT_ON,      GPIOF_OUT_INIT_LOW, "backup battery" },
 };
 
 #ifdef CONFIG_PM
@@ -313,29 +308,16 @@ static int collie_bat_resume(struct ucb1x00_dev *dev)
 static int __devinit collie_bat_probe(struct ucb1x00_dev *dev)
 {
        int ret;
-       int i;
 
        if (!machine_is_collie())
                return -ENODEV;
 
        ucb = dev->ucb;
 
-       for (i = 0; i < ARRAY_SIZE(gpios); i++) {
-               ret = gpio_request(gpios[i].gpio, gpios[i].name);
-               if (ret) {
-                       i--;
-                       goto err_gpio;
-               }
-
-               if (gpios[i].output)
-                       ret = gpio_direction_output(gpios[i].gpio,
-                                       gpios[i].value);
-               else
-                       ret = gpio_direction_input(gpios[i].gpio);
-
-               if (ret)
-                       goto err_gpio;
-       }
+       ret = gpio_request_array(collie_batt_gpios,
+                                ARRAY_SIZE(collie_batt_gpios));
+       if (ret)
+               return ret;
 
        mutex_init(&collie_bat_main.work_lock);
 
@@ -363,19 +345,12 @@ err_psy_reg_main:
 
        /* see comment in collie_bat_remove */
        cancel_work_sync(&bat_work);
-
-       i--;
-err_gpio:
-       for (; i >= 0; i--)
-               gpio_free(gpios[i].gpio);
-
+       gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
        return ret;
 }
 
 static void __devexit collie_bat_remove(struct ucb1x00_dev *dev)
 {
-       int i;
-
        free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main);
 
        power_supply_unregister(&collie_bat_bu.psy);
@@ -387,9 +362,7 @@ static void __devexit collie_bat_remove(struct ucb1x00_dev *dev)
         * unregistered now.
         */
        cancel_work_sync(&bat_work);
-
-       for (i = ARRAY_SIZE(gpios) - 1; i >= 0; i--)
-               gpio_free(gpios[i].gpio);
+       gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
 }
 
 static struct ucb1x00_driver collie_bat_driver = {
index f2c9cc33c0f9f795a81125368c1dc758c2dd81c8..cda03dae75f406009136610ea666a7ca20446198 100644 (file)
@@ -95,7 +95,11 @@ static int rated_capacities[] = {
        2880,   /* Samsung */
        2880,   /* BYD */
        2880,   /* Lishen */
-       2880    /* NEC */
+       2880,   /* NEC */
+#ifdef CONFIG_MACH_H4700
+       0,
+       3600,   /* HP iPAQ hx4700 3.7V 3600mAh (359114-001) */
+#endif
 };
 
 /* array is level at temps 0°C, 10°C, 20°C, 30°C, 40°C
index 91a783d72360b314094a134d5284a89e751b3ca9..887ec98d8c22063ff85754e6d749ac16f6460652 100644 (file)
@@ -848,7 +848,7 @@ static struct platform_driver ds2780_battery_driver = {
                .name = "ds2780-battery",
        },
        .probe    = ds2780_battery_probe,
-       .remove   = ds2780_battery_remove,
+       .remove   = __devexit_p(ds2780_battery_remove),
 };
 
 static int __init ds2780_battery_init(void)
index cffcb7c00b0068da7b32a467358b7e71a0388e13..01fa671ec97f6af9a31bf1a8c39ceef86691617b 100644 (file)
@@ -61,7 +61,8 @@ MODULE_PARM_DESC(debug, "Flag to enable PMIC Battery debug messages.");
 #define PMIC_BATT_CHR_SBATDET_MASK     (1 << 5)
 #define PMIC_BATT_CHR_SDCLMT_MASK      (1 << 6)
 #define PMIC_BATT_CHR_SUSBOVP_MASK     (1 << 7)
-#define PMIC_BATT_CHR_EXCPT_MASK       0xC6
+#define PMIC_BATT_CHR_EXCPT_MASK       0x86
+
 #define PMIC_BATT_ADC_ACCCHRG_MASK     (1 << 31)
 #define PMIC_BATT_ADC_ACCCHRGVAL_MASK  0x7FFFFFFF
 
@@ -304,11 +305,6 @@ static void pmic_battery_read_status(struct pmic_power_module_info *pbi)
                        pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
                        pmic_battery_log_event(BATT_EVENT_BATOVP_EXCPT);
                        batt_exception = 1;
-               } else if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) {
-                       pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
-                       pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
-                       pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT);
-                       batt_exception = 1;
                } else if (r8 & PMIC_BATT_CHR_STEMP_MASK) {
                        pbi->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
                        pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
@@ -316,6 +312,10 @@ static void pmic_battery_read_status(struct pmic_power_module_info *pbi)
                        batt_exception = 1;
                } else {
                        pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+                       if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) {
+                               /* PMIC will change charging current automatically */
+                               pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT);
+                       }
                }
        }
 
diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c
new file mode 100644 (file)
index 0000000..b15b575
--- /dev/null
@@ -0,0 +1,494 @@
+/*
+ * Driver for LP8727 Micro/Mini USB IC with intergrated charger
+ *
+ *                     Copyright (C) 2011 National Semiconductor
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/power_supply.h>
+#include <linux/lp8727.h>
+
+#define DEBOUNCE_MSEC  270
+
+/* Registers */
+#define CTRL1          0x1
+#define CTRL2          0x2
+#define        SWCTRL          0x3
+#define INT1           0x4
+#define INT2           0x5
+#define STATUS1                0x6
+#define STATUS2        0x7
+#define CHGCTRL2       0x9
+
+/* CTRL1 register */
+#define CP_EN          (1 << 0)
+#define ADC_EN         (1 << 1)
+#define ID200_EN       (1 << 4)
+
+/* CTRL2 register */
+#define CHGDET_EN      (1 << 1)
+#define INT_EN         (1 << 6)
+
+/* SWCTRL register */
+#define SW_DM1_DM      (0x0 << 0)
+#define SW_DM1_U1      (0x1 << 0)
+#define SW_DM1_HiZ     (0x7 << 0)
+#define SW_DP2_DP      (0x0 << 3)
+#define SW_DP2_U2      (0x1 << 3)
+#define SW_DP2_HiZ     (0x7 << 3)
+
+/* INT1 register */
+#define IDNO           (0xF << 0)
+#define VBUS           (1 << 4)
+
+/* STATUS1 register */
+#define CHGSTAT                (3 << 4)
+#define CHPORT         (1 << 6)
+#define DCPORT         (1 << 7)
+
+/* STATUS2 register */
+#define TEMP_STAT      (3 << 5)
+
+enum lp8727_dev_id {
+       ID_NONE,
+       ID_TA,
+       ID_DEDICATED_CHG,
+       ID_USB_CHG,
+       ID_USB_DS,
+       ID_MAX,
+};
+
+enum lp8727_chg_stat {
+       PRECHG,
+       CC,
+       CV,
+       EOC,
+};
+
+struct lp8727_psy {
+       struct power_supply ac;
+       struct power_supply usb;
+       struct power_supply batt;
+};
+
+struct lp8727_chg {
+       struct device *dev;
+       struct i2c_client *client;
+       struct mutex xfer_lock;
+       struct delayed_work work;
+       struct workqueue_struct *irqthread;
+       struct lp8727_platform_data *pdata;
+       struct lp8727_psy *psy;
+       struct lp8727_chg_param *chg_parm;
+       enum lp8727_dev_id devid;
+};
+
+static int lp8727_i2c_read(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len)
+{
+       s32 ret;
+
+       mutex_lock(&pchg->xfer_lock);
+       ret = i2c_smbus_read_i2c_block_data(pchg->client, reg, len, data);
+       mutex_unlock(&pchg->xfer_lock);
+
+       return (ret != len) ? -EIO : 0;
+}
+
+static int lp8727_i2c_write(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len)
+{
+       s32 ret;
+
+       mutex_lock(&pchg->xfer_lock);
+       ret = i2c_smbus_write_i2c_block_data(pchg->client, reg, len, data);
+       mutex_unlock(&pchg->xfer_lock);
+
+       return ret;
+}
+
+static inline int lp8727_i2c_read_byte(struct lp8727_chg *pchg, u8 reg,
+                                      u8 *data)
+{
+       return lp8727_i2c_read(pchg, reg, data, 1);
+}
+
+static inline int lp8727_i2c_write_byte(struct lp8727_chg *pchg, u8 reg,
+                                       u8 *data)
+{
+       return lp8727_i2c_write(pchg, reg, data, 1);
+}
+
+static int lp8727_is_charger_attached(const char *name, int id)
+{
+       if (name) {
+               if (!strcmp(name, "ac"))
+                       return (id == ID_TA || id == ID_DEDICATED_CHG) ? 1 : 0;
+               else if (!strcmp(name, "usb"))
+                       return (id == ID_USB_CHG) ? 1 : 0;
+       }
+
+       return (id >= ID_TA && id <= ID_USB_CHG) ? 1 : 0;
+}
+
+static void lp8727_init_device(struct lp8727_chg *pchg)
+{
+       u8 val;
+
+       val = ID200_EN | ADC_EN | CP_EN;
+       if (lp8727_i2c_write_byte(pchg, CTRL1, &val))
+               dev_err(pchg->dev, "i2c write err : addr=0x%.2x\n", CTRL1);
+
+       val = INT_EN | CHGDET_EN;
+       if (lp8727_i2c_write_byte(pchg, CTRL2, &val))
+               dev_err(pchg->dev, "i2c write err : addr=0x%.2x\n", CTRL2);
+}
+
+static int lp8727_is_dedicated_charger(struct lp8727_chg *pchg)
+{
+       u8 val;
+       lp8727_i2c_read_byte(pchg, STATUS1, &val);
+       return (val & DCPORT);
+}
+
+static int lp8727_is_usb_charger(struct lp8727_chg *pchg)
+{
+       u8 val;
+       lp8727_i2c_read_byte(pchg, STATUS1, &val);
+       return (val & CHPORT);
+}
+
+static void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw)
+{
+       u8 val = sw;
+       lp8727_i2c_write_byte(pchg, SWCTRL, &val);
+}
+
+static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin)
+{
+       u8 devid = ID_NONE;
+       u8 swctrl = SW_DM1_HiZ | SW_DP2_HiZ;
+
+       switch (id) {
+       case 0x5:
+               devid = ID_TA;
+               pchg->chg_parm = &pchg->pdata->ac;
+               break;
+       case 0xB:
+               if (lp8727_is_dedicated_charger(pchg)) {
+                       pchg->chg_parm = &pchg->pdata->ac;
+                       devid = ID_DEDICATED_CHG;
+               } else if (lp8727_is_usb_charger(pchg)) {
+                       pchg->chg_parm = &pchg->pdata->usb;
+                       devid = ID_USB_CHG;
+                       swctrl = SW_DM1_DM | SW_DP2_DP;
+               } else if (vbusin) {
+                       devid = ID_USB_DS;
+                       swctrl = SW_DM1_DM | SW_DP2_DP;
+               }
+               break;
+       default:
+               devid = ID_NONE;
+               pchg->chg_parm = NULL;
+               break;
+       }
+
+       pchg->devid = devid;
+       lp8727_ctrl_switch(pchg, swctrl);
+}
+
+static void lp8727_enable_chgdet(struct lp8727_chg *pchg)
+{
+       u8 val;
+
+       lp8727_i2c_read_byte(pchg, CTRL2, &val);
+       val |= CHGDET_EN;
+       lp8727_i2c_write_byte(pchg, CTRL2, &val);
+}
+
+static void lp8727_delayed_func(struct work_struct *_work)
+{
+       u8 intstat[2], idno, vbus;
+       struct lp8727_chg *pchg =
+           container_of(_work, struct lp8727_chg, work.work);
+
+       if (lp8727_i2c_read(pchg, INT1, intstat, 2)) {
+               dev_err(pchg->dev, "can not read INT registers\n");
+               return;
+       }
+
+       idno = intstat[0] & IDNO;
+       vbus = intstat[0] & VBUS;
+
+       lp8727_id_detection(pchg, idno, vbus);
+       lp8727_enable_chgdet(pchg);
+
+       power_supply_changed(&pchg->psy->ac);
+       power_supply_changed(&pchg->psy->usb);
+       power_supply_changed(&pchg->psy->batt);
+}
+
+static irqreturn_t lp8727_isr_func(int irq, void *ptr)
+{
+       struct lp8727_chg *pchg = ptr;
+       unsigned long delay = msecs_to_jiffies(DEBOUNCE_MSEC);
+
+       queue_delayed_work(pchg->irqthread, &pchg->work, delay);
+
+       return IRQ_HANDLED;
+}
+
+static void lp8727_intr_config(struct lp8727_chg *pchg)
+{
+       INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func);
+
+       pchg->irqthread = create_singlethread_workqueue("lp8727-irqthd");
+       if (!pchg->irqthread)
+               dev_err(pchg->dev, "can not create thread for lp8727\n");
+
+       if (request_threaded_irq(pchg->client->irq,
+                                NULL,
+                                lp8727_isr_func,
+                                IRQF_TRIGGER_FALLING, "lp8727_irq", pchg)) {
+               dev_err(pchg->dev, "lp8727 irq can not be registered\n");
+       }
+}
+
+static enum power_supply_property lp8727_charger_prop[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property lp8727_battery_prop[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_TEMP,
+};
+
+static char *battery_supplied_to[] = {
+       "main_batt",
+};
+
+static int lp8727_charger_get_property(struct power_supply *psy,
+                                      enum power_supply_property psp,
+                                      union power_supply_propval *val)
+{
+       struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent);
+
+       if (psp == POWER_SUPPLY_PROP_ONLINE)
+               val->intval = lp8727_is_charger_attached(psy->name,
+                                                        pchg->devid);
+
+       return 0;
+}
+
+static int lp8727_battery_get_property(struct power_supply *psy,
+                                      enum power_supply_property psp,
+                                      union power_supply_propval *val)
+{
+       struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent);
+       u8 read;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               if (lp8727_is_charger_attached(psy->name, pchg->devid)) {
+                       lp8727_i2c_read_byte(pchg, STATUS1, &read);
+                       if (((read & CHGSTAT) >> 4) == EOC)
+                               val->intval = POWER_SUPPLY_STATUS_FULL;
+                       else
+                               val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               } else {
+                       val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+               }
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               lp8727_i2c_read_byte(pchg, STATUS2, &read);
+               read = (read & TEMP_STAT) >> 5;
+               if (read >= 0x1 && read <= 0x3)
+                       val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+               else
+                       val->intval = POWER_SUPPLY_HEALTH_GOOD;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               if (pchg->pdata->get_batt_present)
+                       val->intval = pchg->pdata->get_batt_present();
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               if (pchg->pdata->get_batt_level)
+                       val->intval = pchg->pdata->get_batt_level();
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               if (pchg->pdata->get_batt_capacity)
+                       val->intval = pchg->pdata->get_batt_capacity();
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+               if (pchg->pdata->get_batt_temp)
+                       val->intval = pchg->pdata->get_batt_temp();
+               break;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static void lp8727_charger_changed(struct power_supply *psy)
+{
+       struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent);
+       u8 val;
+       u8 eoc_level, ichg;
+
+       if (lp8727_is_charger_attached(psy->name, pchg->devid)) {
+               if (pchg->chg_parm) {
+                       eoc_level = pchg->chg_parm->eoc_level;
+                       ichg = pchg->chg_parm->ichg;
+                       val = (ichg << 4) | eoc_level;
+                       lp8727_i2c_write_byte(pchg, CHGCTRL2, &val);
+               }
+       }
+}
+
+static int lp8727_register_psy(struct lp8727_chg *pchg)
+{
+       struct lp8727_psy *psy;
+
+       psy = kzalloc(sizeof(*psy), GFP_KERNEL);
+       if (!psy)
+               goto err_mem;
+
+       pchg->psy = psy;
+
+       psy->ac.name = "ac";
+       psy->ac.type = POWER_SUPPLY_TYPE_MAINS;
+       psy->ac.properties = lp8727_charger_prop;
+       psy->ac.num_properties = ARRAY_SIZE(lp8727_charger_prop);
+       psy->ac.get_property = lp8727_charger_get_property;
+       psy->ac.supplied_to = battery_supplied_to;
+       psy->ac.num_supplicants = ARRAY_SIZE(battery_supplied_to);
+
+       if (power_supply_register(pchg->dev, &psy->ac))
+               goto err_psy;
+
+       psy->usb.name = "usb";
+       psy->usb.type = POWER_SUPPLY_TYPE_USB;
+       psy->usb.properties = lp8727_charger_prop;
+       psy->usb.num_properties = ARRAY_SIZE(lp8727_charger_prop);
+       psy->usb.get_property = lp8727_charger_get_property;
+       psy->usb.supplied_to = battery_supplied_to;
+       psy->usb.num_supplicants = ARRAY_SIZE(battery_supplied_to);
+
+       if (power_supply_register(pchg->dev, &psy->usb))
+               goto err_psy;
+
+       psy->batt.name = "main_batt";
+       psy->batt.type = POWER_SUPPLY_TYPE_BATTERY;
+       psy->batt.properties = lp8727_battery_prop;
+       psy->batt.num_properties = ARRAY_SIZE(lp8727_battery_prop);
+       psy->batt.get_property = lp8727_battery_get_property;
+       psy->batt.external_power_changed = lp8727_charger_changed;
+
+       if (power_supply_register(pchg->dev, &psy->batt))
+               goto err_psy;
+
+       return 0;
+
+err_mem:
+       return -ENOMEM;
+err_psy:
+       kfree(psy);
+       return -EPERM;
+}
+
+static void lp8727_unregister_psy(struct lp8727_chg *pchg)
+{
+       struct lp8727_psy *psy = pchg->psy;
+
+       if (!psy)
+               return;
+
+       power_supply_unregister(&psy->ac);
+       power_supply_unregister(&psy->usb);
+       power_supply_unregister(&psy->batt);
+       kfree(psy);
+}
+
+static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id)
+{
+       struct lp8727_chg *pchg;
+       int ret;
+
+       if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
+               return -EIO;
+
+       pchg = kzalloc(sizeof(*pchg), GFP_KERNEL);
+       if (!pchg)
+               return -ENOMEM;
+
+       pchg->client = cl;
+       pchg->dev = &cl->dev;
+       pchg->pdata = cl->dev.platform_data;
+       i2c_set_clientdata(cl, pchg);
+
+       mutex_init(&pchg->xfer_lock);
+
+       lp8727_init_device(pchg);
+       lp8727_intr_config(pchg);
+
+       ret = lp8727_register_psy(pchg);
+       if (ret)
+               dev_err(pchg->dev,
+                       "can not register power supplies. err=%d", ret);
+
+       return 0;
+}
+
+static int __devexit lp8727_remove(struct i2c_client *cl)
+{
+       struct lp8727_chg *pchg = i2c_get_clientdata(cl);
+
+       lp8727_unregister_psy(pchg);
+       free_irq(pchg->client->irq, pchg);
+       flush_workqueue(pchg->irqthread);
+       destroy_workqueue(pchg->irqthread);
+       kfree(pchg);
+       return 0;
+}
+
+static const struct i2c_device_id lp8727_ids[] = {
+       {"lp8727", 0},
+};
+
+static struct i2c_driver lp8727_driver = {
+       .driver = {
+                  .name = "lp8727",
+                  },
+       .probe = lp8727_probe,
+       .remove = __devexit_p(lp8727_remove),
+       .id_table = lp8727_ids,
+};
+
+static int __init lp8727_init(void)
+{
+       return i2c_add_driver(&lp8727_driver);
+}
+
+static void __exit lp8727_exit(void)
+{
+       i2c_del_driver(&lp8727_driver);
+}
+
+module_init(lp8727_init);
+module_exit(lp8727_exit);
+
+MODULE_DESCRIPTION("National Semiconductor LP8727 charger driver");
+MODULE_AUTHOR
+    ("Woogyom Kim <milo.kim@ti.com>, Daniel Jeong <daniel.jeong@ti.com>");
+MODULE_LICENSE("GPL");
index 9f0183c73076852b213b10ea2832bc7e9d74cd3e..86acee2f988917912919e710d4b62f1592b98397 100644 (file)
@@ -85,55 +85,79 @@ static int max17042_get_property(struct power_supply *psy,
 {
        struct max17042_chip *chip = container_of(psy,
                                struct max17042_chip, battery);
+       int ret;
 
        switch (psp) {
        case POWER_SUPPLY_PROP_PRESENT:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_STATUS);
-               if (val->intval & MAX17042_STATUS_BattAbsent)
+               ret = max17042_read_reg(chip->client, MAX17042_STATUS);
+               if (ret < 0)
+                       return ret;
+
+               if (ret & MAX17042_STATUS_BattAbsent)
                        val->intval = 0;
                else
                        val->intval = 1;
                break;
        case POWER_SUPPLY_PROP_CYCLE_COUNT:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_Cycles);
+               ret = max17042_read_reg(chip->client, MAX17042_Cycles);
+               if (ret < 0)
+                       return ret;
+
+               val->intval = ret;
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_MAX:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_MinMaxVolt);
-               val->intval >>= 8;
+               ret = max17042_read_reg(chip->client, MAX17042_MinMaxVolt);
+               if (ret < 0)
+                       return ret;
+
+               val->intval = ret >> 8;
                val->intval *= 20000; /* Units of LSB = 20mV */
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_V_empty);
-               val->intval >>= 7;
+               ret = max17042_read_reg(chip->client, MAX17042_V_empty);
+               if (ret < 0)
+                       return ret;
+
+               val->intval = ret >> 7;
                val->intval *= 10000; /* Units of LSB = 10mV */
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_VCELL) * 83; /* 1000 / 12 = 83 */
+               ret = max17042_read_reg(chip->client, MAX17042_VCELL);
+               if (ret < 0)
+                       return ret;
+
+               val->intval = ret * 625 / 8;
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_AVG:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_AvgVCELL) * 83;
+               ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL);
+               if (ret < 0)
+                       return ret;
+
+               val->intval = ret * 625 / 8;
                break;
        case POWER_SUPPLY_PROP_CAPACITY:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_SOC) / 256;
+               ret = max17042_read_reg(chip->client, MAX17042_SOC);
+               if (ret < 0)
+                       return ret;
+
+               val->intval = ret >> 8;
                break;
        case POWER_SUPPLY_PROP_CHARGE_FULL:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_RepSOC);
-               if ((val->intval / 256) >= MAX17042_BATTERY_FULL)
+               ret = max17042_read_reg(chip->client, MAX17042_RepSOC);
+               if (ret < 0)
+                       return ret;
+
+               if ((ret >> 8) >= MAX17042_BATTERY_FULL)
                        val->intval = 1;
-               else if (val->intval >= 0)
+               else if (ret >= 0)
                        val->intval = 0;
                break;
        case POWER_SUPPLY_PROP_TEMP:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_TEMP);
+               ret = max17042_read_reg(chip->client, MAX17042_TEMP);
+               if (ret < 0)
+                       return ret;
+
+               val->intval = ret;
                /* The value is signed. */
                if (val->intval & 0x8000) {
                        val->intval = (0x7fff & ~val->intval) + 1;
@@ -145,24 +169,30 @@ static int max17042_get_property(struct power_supply *psy,
                break;
        case POWER_SUPPLY_PROP_CURRENT_NOW:
                if (chip->pdata->enable_current_sense) {
-                       val->intval = max17042_read_reg(chip->client,
-                                       MAX17042_Current);
+                       ret = max17042_read_reg(chip->client, MAX17042_Current);
+                       if (ret < 0)
+                               return ret;
+
+                       val->intval = ret;
                        if (val->intval & 0x8000) {
                                /* Negative */
                                val->intval = ~val->intval & 0x7fff;
                                val->intval++;
                                val->intval *= -1;
                        }
-                       val->intval >>= 4;
-                       val->intval *= 1000000 * 25 / chip->pdata->r_sns;
+                       val->intval *= 1562500 / chip->pdata->r_sns;
                } else {
                        return -EINVAL;
                }
                break;
        case POWER_SUPPLY_PROP_CURRENT_AVG:
                if (chip->pdata->enable_current_sense) {
-                       val->intval = max17042_read_reg(chip->client,
-                                       MAX17042_AvgCurrent);
+                       ret = max17042_read_reg(chip->client,
+                                               MAX17042_AvgCurrent);
+                       if (ret < 0)
+                               return ret;
+
+                       val->intval = ret;
                        if (val->intval & 0x8000) {
                                /* Negative */
                                val->intval = ~val->intval & 0x7fff;
@@ -210,6 +240,9 @@ static int __devinit max17042_probe(struct i2c_client *client,
        if (!chip->pdata->enable_current_sense)
                chip->battery.num_properties -= 2;
 
+       if (chip->pdata->r_sns == 0)
+               chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
+
        ret = power_supply_register(&client->dev, &chip->battery);
        if (ret) {
                dev_err(&client->dev, "failed: power supply register\n");
@@ -226,9 +259,6 @@ static int __devinit max17042_probe(struct i2c_client *client,
                max17042_write_reg(client, MAX17042_CGAIN, 0x0000);
                max17042_write_reg(client, MAX17042_MiscCFG, 0x0003);
                max17042_write_reg(client, MAX17042_LearnCFG, 0x0007);
-       } else {
-               if (chip->pdata->r_sns == 0)
-                       chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
        }
 
        return 0;
index 2595145f3bffad80d9cdfba4d50b83df3f41ffa6..f204ad16361adf8daaa8cfd65289e1c6fab2eb9a 100644 (file)
@@ -389,4 +389,4 @@ module_exit(max8903_exit);
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("MAX8903 Charger Driver");
 MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
-MODULE_ALIAS("max8903-charger");
+MODULE_ALIAS("platform:max8903-charger");
index a70e16d3a3dc1d31942ccf6cf788d06b1c0ea3b1..815525094ff9c5e33fd7133096deb9ebea5cff51 100644 (file)
@@ -78,6 +78,8 @@ struct max8925_power_info {
        unsigned                batt_detect:1;  /* detecing MB by ID pin */
        unsigned                topoff_threshold:2;
        unsigned                fast_charge:3;
+       unsigned                no_temp_support:1;
+       unsigned                no_insert_detect:1;
 
        int (*set_charger) (int);
 };
@@ -116,17 +118,7 @@ static irqreturn_t max8925_charger_handler(int irq, void *data)
        case MAX8925_IRQ_VCHG_DC_F:
                info->ac_online = 0;
                __set_charger(info, 0);
-               dev_dbg(chip->dev, "Adapter is removal\n");
-               break;
-       case MAX8925_IRQ_VCHG_USB_R:
-               info->usb_online = 1;
-               __set_charger(info, 1);
-               dev_dbg(chip->dev, "USB inserted\n");
-               break;
-       case MAX8925_IRQ_VCHG_USB_F:
-               info->usb_online = 0;
-               __set_charger(info, 0);
-               dev_dbg(chip->dev, "USB is removal\n");
+               dev_dbg(chip->dev, "Adapter removed\n");
                break;
        case MAX8925_IRQ_VCHG_THM_OK_F:
                /* Battery is not ready yet */
@@ -168,27 +160,33 @@ static irqreturn_t max8925_charger_handler(int irq, void *data)
 static int start_measure(struct max8925_power_info *info, int type)
 {
        unsigned char buf[2] = {0, 0};
+       int meas_cmd;
        int meas_reg = 0, ret;
 
        switch (type) {
        case MEASURE_VCHG:
+               meas_cmd = MAX8925_CMD_VCHG;
                meas_reg = MAX8925_ADC_VCHG;
                break;
        case MEASURE_VBBATT:
+               meas_cmd = MAX8925_CMD_VBBATT;
                meas_reg = MAX8925_ADC_VBBATT;
                break;
        case MEASURE_VMBATT:
+               meas_cmd = MAX8925_CMD_VMBATT;
                meas_reg = MAX8925_ADC_VMBATT;
                break;
        case MEASURE_ISNS:
+               meas_cmd = MAX8925_CMD_ISNS;
                meas_reg = MAX8925_ADC_ISNS;
                break;
        default:
                return -EINVAL;
        }
 
+       max8925_reg_write(info->adc, meas_cmd, 0);
        max8925_bulk_read(info->adc, meas_reg, 2, buf);
-       ret = (buf[0] << 4) | (buf[1] >> 4);
+       ret = ((buf[0]<<8) | buf[1]) >> 4;
 
        return ret;
 }
@@ -208,7 +206,7 @@ static int max8925_ac_get_prop(struct power_supply *psy,
                if (info->ac_online) {
                        ret = start_measure(info, MEASURE_VCHG);
                        if (ret >= 0) {
-                               val->intval = ret << 1; /* unit is mV */
+                               val->intval = ret * 2000;       /* unit is uV */
                                goto out;
                        }
                }
@@ -242,7 +240,7 @@ static int max8925_usb_get_prop(struct power_supply *psy,
                if (info->usb_online) {
                        ret = start_measure(info, MEASURE_VCHG);
                        if (ret >= 0) {
-                               val->intval = ret << 1; /* unit is mV */
+                               val->intval = ret * 2000;       /* unit is uV */
                                goto out;
                        }
                }
@@ -266,7 +264,6 @@ static int max8925_bat_get_prop(struct power_supply *psy,
                                union power_supply_propval *val)
 {
        struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent);
-       long long int tmp = 0;
        int ret = 0;
 
        switch (psp) {
@@ -277,7 +274,7 @@ static int max8925_bat_get_prop(struct power_supply *psy,
                if (info->bat_online) {
                        ret = start_measure(info, MEASURE_VMBATT);
                        if (ret >= 0) {
-                               val->intval = ret << 1; /* unit is mV */
+                               val->intval = ret * 2000;       /* unit is uV */
                                ret = 0;
                                break;
                        }
@@ -288,8 +285,8 @@ static int max8925_bat_get_prop(struct power_supply *psy,
                if (info->bat_online) {
                        ret = start_measure(info, MEASURE_ISNS);
                        if (ret >= 0) {
-                               tmp = (long long int)ret * 6250 / 4096 - 3125;
-                               ret = (int)tmp;
+                               /* assume r_sns is 0.02 */
+                               ret = ((ret * 6250) - 3125) /* uA */;
                                val->intval = 0;
                                if (ret > 0)
                                        val->intval = ret; /* unit is mA */
@@ -365,13 +362,14 @@ static __devinit int max8925_init_charger(struct max8925_chip *chip,
        int ret;
 
        REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp");
-       REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove");
-       REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert");
-       REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_OVP, "usb-ovp");
-       REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_F, "usb-remove");
-       REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_R, "usb-insert");
-       REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range");
-       REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range");
+       if (!info->no_insert_detect) {
+               REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove");
+               REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert");
+       }
+       if (!info->no_temp_support) {
+               REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range");
+               REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range");
+       }
        REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high");
        REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low");
        REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset");
@@ -379,9 +377,15 @@ static __devinit int max8925_init_charger(struct max8925_chip *chip,
        REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff");
        REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire");
 
-       info->ac_online = 0;
        info->usb_online = 0;
        info->bat_online = 0;
+
+       /* check for power - can miss interrupt at boot time */
+       if (start_measure(info, MEASURE_VCHG) * 2000 > 500000)
+               info->ac_online = 1;
+       else
+               info->ac_online = 0;
+
        ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
        if (ret >= 0) {
                /*
@@ -449,6 +453,8 @@ static __devinit int max8925_power_probe(struct platform_device *pdev)
        info->ac.properties = max8925_ac_props;
        info->ac.num_properties = ARRAY_SIZE(max8925_ac_props);
        info->ac.get_property = max8925_ac_get_prop;
+       info->ac.supplied_to = pdata->supplied_to;
+       info->ac.num_supplicants = pdata->num_supplicants;
        ret = power_supply_register(&pdev->dev, &info->ac);
        if (ret)
                goto out;
@@ -459,6 +465,9 @@ static __devinit int max8925_power_probe(struct platform_device *pdev)
        info->usb.properties = max8925_usb_props;
        info->usb.num_properties = ARRAY_SIZE(max8925_usb_props);
        info->usb.get_property = max8925_usb_get_prop;
+       info->usb.supplied_to = pdata->supplied_to;
+       info->usb.num_supplicants = pdata->num_supplicants;
+
        ret = power_supply_register(&pdev->dev, &info->usb);
        if (ret)
                goto out_usb;
@@ -478,6 +487,8 @@ static __devinit int max8925_power_probe(struct platform_device *pdev)
        info->topoff_threshold = pdata->topoff_threshold;
        info->fast_charge = pdata->fast_charge;
        info->set_charger = pdata->set_charger;
+       info->no_temp_support = pdata->no_temp_support;
+       info->no_insert_detect = pdata->no_insert_detect;
 
        max8925_init_charger(chip, info);
        return 0;
index a23317d75c5a80e65bec82fdf89bdb1f6499f0a1..23ca65d177059481687e2c9705d5954716154ca2 100644 (file)
@@ -98,7 +98,7 @@ static __devinit int max8997_battery_probe(struct platform_device *pdev)
                return -EINVAL;
 
        if (pdata->eoc_mA) {
-               u8 val = (pdata->eoc_mA - 50) / 10;
+               int val = (pdata->eoc_mA - 50) / 10;
                if (val < 0)
                        val = 0;
                if (val > 0xf)
@@ -179,6 +179,7 @@ static int __devexit max8997_battery_remove(struct platform_device *pdev)
 
 static const struct platform_device_id max8997_battery_id[] = {
        { "max8997-battery", 0 },
+       { }
 };
 
 static struct platform_driver max8997_battery_driver = {
index 93e3bb47a3a817ac7d1fbf0aa712bb77b0c181fd..0737302af1d202f6803ba633f341340696a3a56e 100644 (file)
@@ -154,6 +154,7 @@ static __devinit int max8998_battery_probe(struct platform_device *pdev)
        case 0:
                dev_dbg(max8998->dev,
                        "Full Timeout not set: leave it unchanged.\n");
+               break;
        default:
                dev_err(max8998->dev, "Invalid Full Timeout value\n");
                ret = -EINVAL;
@@ -190,6 +191,7 @@ static int __devexit max8998_battery_remove(struct platform_device *pdev)
 
 static const struct platform_device_id max8998_battery_id[] = {
        { "max8998-battery", TYPE_MAX8998 },
+       { }
 };
 
 static struct platform_driver max8998_battery_driver = {
index 0b0ff3a936a61722be4fbb9a48d0a9de44626f1f..21e7c06724bfcddd956bb8b3adbdbd5d0959fe8d 100644 (file)
@@ -519,29 +519,35 @@ static struct device_attribute olpc_bat_error = {
  *             Initialisation
  *********************************************************************/
 
-static struct platform_device *bat_pdev;
-
 static struct power_supply olpc_bat = {
+       .name = "olpc-battery",
        .get_property = olpc_bat_get_property,
        .use_for_apm = 1,
 };
 
-void olpc_battery_trigger_uevent(unsigned long cause)
+static int olpc_battery_suspend(struct platform_device *pdev,
+                               pm_message_t state)
 {
-       if (cause & EC_SCI_SRC_ACPWR)
-               kobject_uevent(&olpc_ac.dev->kobj, KOBJ_CHANGE);
-       if (cause & (EC_SCI_SRC_BATERR|EC_SCI_SRC_BATSOC|EC_SCI_SRC_BATTERY))
-               kobject_uevent(&olpc_bat.dev->kobj, KOBJ_CHANGE);
+       if (device_may_wakeup(olpc_ac.dev))
+               olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR);
+       else
+               olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR);
+
+       if (device_may_wakeup(olpc_bat.dev))
+               olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
+                                  | EC_SCI_SRC_BATERR);
+       else
+               olpc_ec_wakeup_clear(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
+                                    | EC_SCI_SRC_BATERR);
+
+       return 0;
 }
 
-static int __init olpc_bat_init(void)
+static int __devinit olpc_battery_probe(struct platform_device *pdev)
 {
-       int ret = 0;
+       int ret;
        uint8_t status;
 
-       if (!olpc_platform_info.ecver)
-               return -ENXIO;
-
        /*
         * We've seen a number of EC protocol changes; this driver requires
         * the latest EC protocol, supported by 0x44 and above.
@@ -558,15 +564,10 @@ static int __init olpc_bat_init(void)
 
        /* Ignore the status. It doesn't actually matter */
 
-       bat_pdev = platform_device_register_simple("olpc-battery", 0, NULL, 0);
-       if (IS_ERR(bat_pdev))
-               return PTR_ERR(bat_pdev);
-
-       ret = power_supply_register(&bat_pdev->dev, &olpc_ac);
+       ret = power_supply_register(&pdev->dev, &olpc_ac);
        if (ret)
-               goto ac_failed;
+               return ret;
 
-       olpc_bat.name = bat_pdev->name;
        if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */
                olpc_bat.properties = olpc_xo15_bat_props;
                olpc_bat.num_properties = ARRAY_SIZE(olpc_xo15_bat_props);
@@ -575,7 +576,7 @@ static int __init olpc_bat_init(void)
                olpc_bat.num_properties = ARRAY_SIZE(olpc_xo1_bat_props);
        }
 
-       ret = power_supply_register(&bat_pdev->dev, &olpc_bat);
+       ret = power_supply_register(&pdev->dev, &olpc_bat);
        if (ret)
                goto battery_failed;
 
@@ -587,7 +588,12 @@ static int __init olpc_bat_init(void)
        if (ret)
                goto error_failed;
 
-       goto success;
+       if (olpc_ec_wakeup_available()) {
+               device_set_wakeup_capable(olpc_ac.dev, true);
+               device_set_wakeup_capable(olpc_bat.dev, true);
+       }
+
+       return 0;
 
 error_failed:
        device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom);
@@ -595,22 +601,45 @@ eeprom_failed:
        power_supply_unregister(&olpc_bat);
 battery_failed:
        power_supply_unregister(&olpc_ac);
-ac_failed:
-       platform_device_unregister(bat_pdev);
-success:
        return ret;
 }
 
-static void __exit olpc_bat_exit(void)
+static int __devexit olpc_battery_remove(struct platform_device *pdev)
 {
        device_remove_file(olpc_bat.dev, &olpc_bat_error);
        device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom);
        power_supply_unregister(&olpc_bat);
        power_supply_unregister(&olpc_ac);
-       platform_device_unregister(bat_pdev);
+       return 0;
 }
 
+static const struct of_device_id olpc_battery_ids[] __devinitconst = {
+       { .compatible = "olpc,xo1-battery" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, olpc_battery_ids);
+
+static struct platform_driver olpc_battery_driver = {
+       .driver = {
+               .name = "olpc-battery",
+               .owner = THIS_MODULE,
+               .of_match_table = olpc_battery_ids,
+       },
+       .probe = olpc_battery_probe,
+       .remove = __devexit_p(olpc_battery_remove),
+       .suspend = olpc_battery_suspend,
+};
+
+static int __init olpc_bat_init(void)
+{
+       return platform_driver_register(&olpc_battery_driver);
+}
 module_init(olpc_bat_init);
+
+static void __exit olpc_bat_exit(void)
+{
+       platform_driver_unregister(&olpc_battery_driver);
+}
 module_exit(olpc_bat_exit);
 
 MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
index 69f8aa3a6a4bda06b34ed6e7d147b09d723eaedc..87be6795605af9a5b82c8343c6b7dd007f4a4f01 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/platform_device.h>
 #include <linux/err.h>
 #include <linux/interrupt.h>
+#include <linux/notifier.h>
 #include <linux/power_supply.h>
 #include <linux/pda_power.h>
 #include <linux/regulator/consumer.h>
@@ -40,7 +41,9 @@ static int polling;
 
 #ifdef CONFIG_USB_OTG_UTILS
 static struct otg_transceiver *transceiver;
+static struct notifier_block otg_nb;
 #endif
+
 static struct regulator *ac_draw;
 
 enum {
@@ -222,7 +225,42 @@ static void polling_timer_func(unsigned long unused)
 #ifdef CONFIG_USB_OTG_UTILS
 static int otg_is_usb_online(void)
 {
-       return (transceiver->state == OTG_STATE_B_PERIPHERAL);
+       return (transceiver->last_event == USB_EVENT_VBUS ||
+               transceiver->last_event == USB_EVENT_ENUMERATED);
+}
+
+static int otg_is_ac_online(void)
+{
+       return (transceiver->last_event == USB_EVENT_CHARGER);
+}
+
+static int otg_handle_notification(struct notifier_block *nb,
+               unsigned long event, void *unused)
+{
+       switch (event) {
+       case USB_EVENT_CHARGER:
+               ac_status = PDA_PSY_TO_CHANGE;
+               break;
+       case USB_EVENT_VBUS:
+       case USB_EVENT_ENUMERATED:
+               usb_status = PDA_PSY_TO_CHANGE;
+               break;
+       case USB_EVENT_NONE:
+               ac_status = PDA_PSY_TO_CHANGE;
+               usb_status = PDA_PSY_TO_CHANGE;
+               break;
+       default:
+               return NOTIFY_OK;
+       }
+
+       /*
+        * Wait a bit before reading ac/usb line status and setting charger,
+        * because ac/usb status readings may lag from irq.
+        */
+       mod_timer(&charger_timer,
+                 jiffies + msecs_to_jiffies(pdata->wait_for_status));
+
+       return NOTIFY_OK;
 }
 #endif
 
@@ -282,6 +320,16 @@ static int pda_power_probe(struct platform_device *pdev)
                ret = PTR_ERR(ac_draw);
        }
 
+#ifdef CONFIG_USB_OTG_UTILS
+       transceiver = otg_get_transceiver();
+       if (transceiver && !pdata->is_usb_online) {
+               pdata->is_usb_online = otg_is_usb_online;
+       }
+       if (transceiver && !pdata->is_ac_online) {
+               pdata->is_ac_online = otg_is_ac_online;
+       }
+#endif
+
        if (pdata->is_ac_online) {
                ret = power_supply_register(&pdev->dev, &pda_psy_ac);
                if (ret) {
@@ -303,13 +351,6 @@ static int pda_power_probe(struct platform_device *pdev)
                }
        }
 
-#ifdef CONFIG_USB_OTG_UTILS
-       transceiver = otg_get_transceiver();
-       if (transceiver && !pdata->is_usb_online) {
-               pdata->is_usb_online = otg_is_usb_online;
-       }
-#endif
-
        if (pdata->is_usb_online) {
                ret = power_supply_register(&pdev->dev, &pda_psy_usb);
                if (ret) {
@@ -331,6 +372,18 @@ static int pda_power_probe(struct platform_device *pdev)
                }
        }
 
+#ifdef CONFIG_USB_OTG_UTILS
+       if (transceiver && pdata->use_otg_notifier) {
+               otg_nb.notifier_call = otg_handle_notification;
+               ret = otg_register_notifier(transceiver, &otg_nb);
+               if (ret) {
+                       dev_err(dev, "failure to register otg notifier\n");
+                       goto otg_reg_notifier_failed;
+               }
+               polling = 0;
+       }
+#endif
+
        if (polling) {
                dev_dbg(dev, "will poll for status\n");
                setup_timer(&polling_timer, polling_timer_func, 0);
@@ -343,6 +396,11 @@ static int pda_power_probe(struct platform_device *pdev)
 
        return 0;
 
+#ifdef CONFIG_USB_OTG_UTILS
+otg_reg_notifier_failed:
+       if (pdata->is_usb_online && usb_irq)
+               free_irq(usb_irq->start, &pda_psy_usb);
+#endif
 usb_irq_failed:
        if (pdata->is_usb_online)
                power_supply_unregister(&pda_psy_usb);
index 21178ebfe51aacb9d7996dcd6d15299aaa1547fb..5faf7ae9b81f224bb72684d44e1f81be62ac1cad 100644 (file)
@@ -43,7 +43,7 @@ static ssize_t power_supply_show_property(struct device *dev,
                                          struct device_attribute *attr,
                                          char *buf) {
        static char *type_text[] = {
-               "Battery", "UPS", "Mains", "USB",
+               "Unknown", "Battery", "UPS", "Mains", "USB",
                "USB_DCP", "USB_CDP", "USB_ACA"
        };
        static char *status_text[] = {
@@ -81,8 +81,8 @@ static ssize_t power_supply_show_property(struct device *dev,
                        dev_dbg(dev, "driver has no data for `%s' property\n",
                                attr->attr.name);
                else if (ret != -ENODEV)
-                       dev_err(dev, "driver failed to report `%s' property\n",
-                               attr->attr.name);
+                       dev_err(dev, "driver failed to report `%s' property: %zd\n",
+                               attr->attr.name, ret);
                return ret;
        }
 
index 53f0d3524fcd58f483a0ebcda42ccc0a353726e8..ada86a4f50e2f8e34a9ad6c2039a56be599a6b92 100644 (file)
@@ -307,25 +307,20 @@ static struct tosa_bat tosa_bat_bu = {
        .adc_temp_divider = -1,
 };
 
-static struct {
-       int gpio;
-       char *name;
-       bool output;
-       int value;
-} gpios[] = {
-       { TOSA_GPIO_CHARGE_OFF,         "main charge off",      1, 1 },
-       { TOSA_GPIO_CHARGE_OFF_JC,      "jacket charge off",    1, 1 },
-       { TOSA_GPIO_BAT_SW_ON,          "battery switch",       1, 0 },
-       { TOSA_GPIO_BAT0_V_ON,          "main battery",         1, 0 },
-       { TOSA_GPIO_BAT1_V_ON,          "jacket battery",       1, 0 },
-       { TOSA_GPIO_BAT1_TH_ON,         "main battery temp",    1, 0 },
-       { TOSA_GPIO_BAT0_TH_ON,         "jacket battery temp",  1, 0 },
-       { TOSA_GPIO_BU_CHRG_ON,         "backup battery",       1, 0 },
-       { TOSA_GPIO_BAT0_CRG,           "main battery full",    0, 0 },
-       { TOSA_GPIO_BAT1_CRG,           "jacket battery full",  0, 0 },
-       { TOSA_GPIO_BAT0_LOW,           "main battery low",     0, 0 },
-       { TOSA_GPIO_BAT1_LOW,           "jacket battery low",   0, 0 },
-       { TOSA_GPIO_JACKET_DETECT,      "jacket detect",        0, 0 },
+static struct gpio tosa_bat_gpios[] = {
+       { TOSA_GPIO_CHARGE_OFF,    GPIOF_OUT_INIT_HIGH, "main charge off" },
+       { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" },
+       { TOSA_GPIO_BAT_SW_ON,     GPIOF_OUT_INIT_LOW,  "battery switch" },
+       { TOSA_GPIO_BAT0_V_ON,     GPIOF_OUT_INIT_LOW,  "main battery" },
+       { TOSA_GPIO_BAT1_V_ON,     GPIOF_OUT_INIT_LOW,  "jacket battery" },
+       { TOSA_GPIO_BAT1_TH_ON,    GPIOF_OUT_INIT_LOW,  "main battery temp" },
+       { TOSA_GPIO_BAT0_TH_ON,    GPIOF_OUT_INIT_LOW,  "jacket battery temp" },
+       { TOSA_GPIO_BU_CHRG_ON,    GPIOF_OUT_INIT_LOW,  "backup battery" },
+       { TOSA_GPIO_BAT0_CRG,      GPIOF_IN,            "main battery full" },
+       { TOSA_GPIO_BAT1_CRG,      GPIOF_IN,            "jacket battery full" },
+       { TOSA_GPIO_BAT0_LOW,      GPIOF_IN,            "main battery low" },
+       { TOSA_GPIO_BAT1_LOW,      GPIOF_IN,            "jacket battery low" },
+       { TOSA_GPIO_JACKET_DETECT, GPIOF_IN,            "jacket detect" },
 };
 
 #ifdef CONFIG_PM
@@ -350,27 +345,13 @@ static int tosa_bat_resume(struct platform_device *dev)
 static int __devinit tosa_bat_probe(struct platform_device *dev)
 {
        int ret;
-       int i;
 
        if (!machine_is_tosa())
                return -ENODEV;
 
-       for (i = 0; i < ARRAY_SIZE(gpios); i++) {
-               ret = gpio_request(gpios[i].gpio, gpios[i].name);
-               if (ret) {
-                       i--;
-                       goto err_gpio;
-               }
-
-               if (gpios[i].output)
-                       ret = gpio_direction_output(gpios[i].gpio,
-                                       gpios[i].value);
-               else
-                       ret = gpio_direction_input(gpios[i].gpio);
-
-               if (ret)
-                       goto err_gpio;
-       }
+       ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
+       if (ret)
+               return ret;
 
        mutex_init(&tosa_bat_main.work_lock);
        mutex_init(&tosa_bat_jacket.work_lock);
@@ -424,18 +405,12 @@ err_psy_reg_main:
        /* see comment in tosa_bat_remove */
        cancel_work_sync(&bat_work);
 
-       i--;
-err_gpio:
-       for (; i >= 0; i--)
-               gpio_free(gpios[i].gpio);
-
+       gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
        return ret;
 }
 
 static int __devexit tosa_bat_remove(struct platform_device *dev)
 {
-       int i;
-
        free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket);
        free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket);
        free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main);
@@ -450,10 +425,7 @@ static int __devexit tosa_bat_remove(struct platform_device *dev)
         * unregistered now.
         */
        cancel_work_sync(&bat_work);
-
-       for (i = ARRAY_SIZE(gpios) - 1; i >= 0; i--)
-               gpio_free(gpios[i].gpio);
-
+       gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
        return 0;
 }
 
index 6cc2ca6427f337460152ca9ab257d3575396e465..c32e6f83c7a85417c3d1ce6870da3ea204195a70 100644 (file)
@@ -27,6 +27,7 @@ struct wm831x_power {
        char wall_name[20];
        char usb_name[20];
        char battery_name[20];
+       bool have_battery;
 };
 
 static int wm831x_power_check_online(struct wm831x *wm831x, int supply,
@@ -449,7 +450,8 @@ static irqreturn_t wm831x_bat_irq(int irq, void *data)
 
        /* The battery charger is autonomous so we don't need to do
         * anything except kick user space */
-       power_supply_changed(&wm831x_power->battery);
+       if (wm831x_power->have_battery)
+               power_supply_changed(&wm831x_power->battery);
 
        return IRQ_HANDLED;
 }
@@ -479,7 +481,8 @@ static irqreturn_t wm831x_pwr_src_irq(int irq, void *data)
        dev_dbg(wm831x->dev, "Power source changed\n");
 
        /* Just notify for everything - little harm in overnotifying. */
-       power_supply_changed(&wm831x_power->battery);
+       if (wm831x_power->have_battery)
+               power_supply_changed(&wm831x_power->battery);
        power_supply_changed(&wm831x_power->usb);
        power_supply_changed(&wm831x_power->wall);
 
@@ -537,15 +540,6 @@ static __devinit int wm831x_power_probe(struct platform_device *pdev)
        if (ret)
                goto err_kmalloc;
 
-       battery->name = power->battery_name;
-       battery->properties = wm831x_bat_props;
-       battery->num_properties = ARRAY_SIZE(wm831x_bat_props);
-       battery->get_property = wm831x_bat_get_prop;
-       battery->use_for_apm = 1;
-       ret = power_supply_register(&pdev->dev, battery);
-       if (ret)
-               goto err_wall;
-
        usb->name = power->usb_name,
        usb->type = POWER_SUPPLY_TYPE_USB;
        usb->properties = wm831x_usb_props;
@@ -553,7 +547,23 @@ static __devinit int wm831x_power_probe(struct platform_device *pdev)
        usb->get_property = wm831x_usb_get_prop;
        ret = power_supply_register(&pdev->dev, usb);
        if (ret)
-               goto err_battery;
+               goto err_wall;
+
+       ret = wm831x_reg_read(wm831x, WM831X_CHARGER_CONTROL_1);
+       if (ret < 0)
+               goto err_wall;
+       power->have_battery = ret & WM831X_CHG_ENA;
+
+       if (power->have_battery) {
+                   battery->name = power->battery_name;
+                   battery->properties = wm831x_bat_props;
+                   battery->num_properties = ARRAY_SIZE(wm831x_bat_props);
+                   battery->get_property = wm831x_bat_get_prop;
+                   battery->use_for_apm = 1;
+                   ret = power_supply_register(&pdev->dev, battery);
+                   if (ret)
+                           goto err_usb;
+       }
 
        irq = platform_get_irq_byname(pdev, "SYSLO");
        ret = request_threaded_irq(irq, NULL, wm831x_syslo_irq,
@@ -562,7 +572,7 @@ static __devinit int wm831x_power_probe(struct platform_device *pdev)
        if (ret != 0) {
                dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n",
                        irq, ret);
-               goto err_usb;
+               goto err_battery;
        }
 
        irq = platform_get_irq_byname(pdev, "PWR SRC");
@@ -601,10 +611,11 @@ err_bat_irq:
 err_syslo:
        irq = platform_get_irq_byname(pdev, "SYSLO");
        free_irq(irq, power);
+err_battery:
+       if (power->have_battery)
+               power_supply_unregister(battery);
 err_usb:
        power_supply_unregister(usb);
-err_battery:
-       power_supply_unregister(battery);
 err_wall:
        power_supply_unregister(wall);
 err_kmalloc:
@@ -628,7 +639,8 @@ static __devexit int wm831x_power_remove(struct platform_device *pdev)
        irq = platform_get_irq_byname(pdev, "SYSLO");
        free_irq(irq, wm831x_power);
 
-       power_supply_unregister(&wm831x_power->battery);
+       if (wm831x_power->have_battery)
+               power_supply_unregister(&wm831x_power->battery);
        power_supply_unregister(&wm831x_power->wall);
        power_supply_unregister(&wm831x_power->usb);
        kfree(wm831x_power);
index 156559e56fa5e808e48ce095efde1b3178767e91..cf8681c1f8eb74c4c731c3d0fb2d0966bb9222ed 100644 (file)
@@ -196,7 +196,7 @@ static int __devinit wm97xx_bat_probe(struct platform_device *dev)
                if (ret)
                        goto err2;
                ret = request_irq(gpio_to_irq(pdata->charge_gpio),
-                               wm97xx_chrg_irq, IRQF_DISABLED,
+                               wm97xx_chrg_irq, 0,
                                "AC Detect", dev);
                if (ret)
                        goto err2;
index d119c38b3ff632ef158f66a971799c522202c6a3..636ebb2a0e807bd44e28f902200cabdf16e399b9 100644 (file)
@@ -218,7 +218,7 @@ static int __devinit z2_batt_probe(struct i2c_client *client,
                irq_set_irq_type(gpio_to_irq(info->charge_gpio),
                                 IRQ_TYPE_EDGE_BOTH);
                ret = request_irq(gpio_to_irq(info->charge_gpio),
-                               z2_charge_switch_irq, IRQF_DISABLED,
+                               z2_charge_switch_irq, 0,
                                "AC Detect", charger);
                if (ret)
                        goto err3;
@@ -313,7 +313,7 @@ static struct i2c_driver z2_batt_driver = {
                .pm     = Z2_BATTERY_PM_OPS
        },
        .probe          = z2_batt_probe,
-       .remove         = z2_batt_remove,
+       .remove         = __devexit_p(z2_batt_remove),
        .id_table       = z2_batt_id,
 };
 
index 5259dfe8c58567937ef5f7e96ebbf86850c28428..b8e6d9449086e3bcc126e8191611e77bc6303d72 100644 (file)
@@ -167,9 +167,6 @@ enum {
        MAX8925_IRQ_VCHG_DC_OVP,
        MAX8925_IRQ_VCHG_DC_F,
        MAX8925_IRQ_VCHG_DC_R,
-       MAX8925_IRQ_VCHG_USB_OVP,
-       MAX8925_IRQ_VCHG_USB_F,
-       MAX8925_IRQ_VCHG_USB_R,
        MAX8925_IRQ_VCHG_THM_OK_R,
        MAX8925_IRQ_VCHG_THM_OK_F,
        MAX8925_IRQ_VCHG_SYSLOW_F,
@@ -223,6 +220,10 @@ struct max8925_power_pdata {
        unsigned        batt_detect:1;
        unsigned        topoff_threshold:2;
        unsigned        fast_charge:3;  /* charge current */
+       unsigned        no_temp_support:1; /* set if no temperature detect */
+       unsigned        no_insert_detect:1; /* set if no ac insert detect */
+       char            **supplied_to;
+       int             num_supplicants;
 };
 
 /*
index c9e4d814ff7795414b028ee7ebcd7ed46fcdddef..2bb62bf296ac2078d08005e6b3b728c6927bb440 100644 (file)
@@ -35,6 +35,8 @@ struct pda_power_pdata {
        unsigned int polling_interval; /* msecs, default is 2000 */
 
        unsigned long ac_max_uA; /* current to draw when on AC */
+
+       bool use_otg_notifier;
 };
 
 #endif /* __PDA_POWER_H__ */
diff --git a/include/linux/power/charger-manager.h b/include/linux/power/charger-manager.h
new file mode 100644 (file)
index 0000000..4f75e53
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * MyungJoo.Ham <myungjoo.ham@samsung.com>
+ *
+ * Charger Manager.
+ * This framework enables to control and multiple chargers and to
+ * monitor charging even in the context of suspend-to-RAM with
+ * an interface combining the chargers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+**/
+
+#ifndef _CHARGER_MANAGER_H
+#define _CHARGER_MANAGER_H
+
+#include <linux/power_supply.h>
+
+enum data_source {
+       CM_FUEL_GAUGE,
+       CM_CHARGER_STAT,
+};
+
+enum polling_modes {
+       CM_POLL_DISABLE = 0,
+       CM_POLL_ALWAYS,
+       CM_POLL_EXTERNAL_POWER_ONLY,
+       CM_POLL_CHARGING_ONLY,
+};
+
+/**
+ * struct charger_global_desc
+ * @rtc_name: the name of RTC used to wake up the system from suspend.
+ * @rtc_only_wakeup:
+ *     If the system is woken up by waekup-sources other than the RTC or
+ *     callbacks, Charger Manager should recognize with
+ *     rtc_only_wakeup() returning false.
+ *     If the RTC given to CM is the only wakeup reason,
+ *     rtc_only_wakeup should return true.
+ */
+struct charger_global_desc {
+       char *rtc_name;
+
+       bool (*rtc_only_wakeup)(void);
+};
+
+/**
+ * struct charger_desc
+ * @psy_name: the name of power-supply-class for charger manager
+ * @polling_mode:
+ *     Determine which polling mode will be used
+ * @fullbatt_uV: voltage in microvolt
+ *     If it is not being charged and VBATT >= fullbatt_uV,
+ *     it is assumed to be full.
+ * @polling_interval_ms: interval in millisecond at which
+ *     charger manager will monitor battery health
+ * @battery_present:
+ *     Specify where information for existance of battery can be obtained
+ * @psy_charger_stat: the names of power-supply for chargers
+ * @num_charger_regulator: the number of entries in charger_regulators
+ * @charger_regulators: array of regulator_bulk_data for chargers
+ * @psy_fuel_gauge: the name of power-supply for fuel gauge
+ * @temperature_out_of_range:
+ *     Determine whether the status is overheat or cold or normal.
+ *     return_value > 0: overheat
+ *     return_value == 0: normal
+ *     return_value < 0: cold
+ * @measure_battery_temp:
+ *     true: measure battery temperature
+ *     false: measure ambient temperature
+ */
+struct charger_desc {
+       char *psy_name;
+
+       enum polling_modes polling_mode;
+       unsigned int polling_interval_ms;
+
+       unsigned int fullbatt_uV;
+
+       enum data_source battery_present;
+
+       char **psy_charger_stat;
+
+       int num_charger_regulators;
+       struct regulator_bulk_data *charger_regulators;
+
+       char *psy_fuel_gauge;
+
+       int (*temperature_out_of_range)(int *mC);
+       bool measure_battery_temp;
+};
+
+#define PSY_NAME_MAX   30
+
+/**
+ * struct charger_manager
+ * @entry: entry for list
+ * @dev: device pointer
+ * @desc: instance of charger_desc
+ * @fuel_gauge: power_supply for fuel gauge
+ * @charger_stat: array of power_supply for chargers
+ * @charger_enabled: the state of charger
+ * @emergency_stop:
+ *     When setting true, stop charging
+ * @last_temp_mC: the measured temperature in milli-Celsius
+ * @psy_name_buf: the name of power-supply-class for charger manager
+ * @charger_psy: power_supply for charger manager
+ * @status_save_ext_pwr_inserted:
+ *     saved status of external power before entering suspend-to-RAM
+ * @status_save_batt:
+ *     saved status of battery before entering suspend-to-RAM
+ */
+struct charger_manager {
+       struct list_head entry;
+       struct device *dev;
+       struct charger_desc *desc;
+
+       struct power_supply *fuel_gauge;
+       struct power_supply **charger_stat;
+
+       bool charger_enabled;
+
+       int emergency_stop;
+       int last_temp_mC;
+
+       char psy_name_buf[PSY_NAME_MAX + 1];
+       struct power_supply charger_psy;
+
+       bool status_save_ext_pwr_inserted;
+       bool status_save_batt;
+};
+
+#ifdef CONFIG_CHARGER_MANAGER
+extern int setup_charger_manager(struct charger_global_desc *gd);
+extern bool cm_suspend_again(void);
+#else
+static void __maybe_unused setup_charger_manager(struct charger_global_desc *gd)
+{ }
+
+static bool __maybe_unused cm_suspend_again(void)
+{
+       return false;
+}
+#endif
+
+#endif /* _CHARGER_MANAGER_H */
index 2e3c8279b3b0e91d288a9437e4b0db3d05e7d547..fa9b962aec124ad0e126d7400d8a92325525d9ff 100644 (file)
@@ -130,7 +130,8 @@ enum power_supply_property {
 };
 
 enum power_supply_type {
-       POWER_SUPPLY_TYPE_BATTERY = 0,
+       POWER_SUPPLY_TYPE_UNKNOWN = 0,
+       POWER_SUPPLY_TYPE_BATTERY,
        POWER_SUPPLY_TYPE_UPS,
        POWER_SUPPLY_TYPE_MAINS,
        POWER_SUPPLY_TYPE_USB,          /* Standard Downstream Port */