Merge tag 'for-v3.8-merged' of git://git.infradead.org/battery-2.6
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 14 Dec 2012 03:26:04 +0000 (19:26 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 14 Dec 2012 03:26:04 +0000 (19:26 -0800)
Pull battery subsystem updates from Anton Vorontsov:
 "Highlights:

   - Two new drivers from Pali Rohár and N900 hackers: rx51_battery and
     bq2415x_charger.  The drivers are a part of a solution to replace
     the proprietary Nokia BME stack

   - Power supply core now registers devices with a thermal cooling
     subsystem, so we can now automatically throttle charging.  Thanks
     to Ramakrishna Pallala!

   - Device tree support for ab8500 and max8925_power drivers

   - Random fixups and enhancements for a bunch of drivers."

* tag 'for-v3.8-merged' of git://git.infradead.org/battery-2.6: (22 commits)
  max8925_power: Add support for device-tree initialization
  ab8500: Add devicetree support for chargalg
  ab8500: Add devicetree support for charger
  ab8500: Add devicetree support for btemp
  ab8500: Add devicetree support for fuelgauge
  twl4030_charger: Change TWL4030_MODULE_* ids to TWL_MODULE_*
  jz4740-battery: Use devm_request_and_ioremap
  jz4740-battery: Use devm_kzalloc
  bq27x00_battery: Fixup nominal available capacity reporting
  bq2415x_charger: Fix style issues
  bq2415x_charger: Add Kconfig/Makefile entries
  power_supply: Add bq2415x charger driver
  power_supply: Add new Nokia RX-51 (N900) power supply battery driver
  max17042_battery: Fix missing verify_model_lock() return value check
  ds2782_battery: Fix signedness bug in ds278x_read_reg16()
  lp8788-charger: Fix ADC channel names
  lp8788-charger: Fix wrong ADC conversion
  lp8788-charger: Use consumer device name on setting IIO channels
  power_supply: Register power supply for thermal cooling device
  power_supply: Add support for CHARGE_CONTROL_* attributes
  ...

31 files changed:
Documentation/devicetree/bindings/mfd/ab8500.txt
Documentation/devicetree/bindings/power_supply/ab8500/btemp.txt [new file with mode: 0644]
Documentation/devicetree/bindings/power_supply/ab8500/chargalg.txt [new file with mode: 0644]
Documentation/devicetree/bindings/power_supply/ab8500/charger.txt [new file with mode: 0644]
Documentation/devicetree/bindings/power_supply/ab8500/fg.txt [new file with mode: 0644]
Documentation/power/power_supply_class.txt
arch/arm/boot/dts/dbx5x0.dtsi
drivers/mfd/ab8500-core.c
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/ab8500_bmdata.c [new file with mode: 0644]
drivers/power/ab8500_btemp.c
drivers/power/ab8500_charger.c
drivers/power/ab8500_fg.c
drivers/power/abx500_chargalg.c
drivers/power/bq2415x_charger.c [new file with mode: 0644]
drivers/power/bq27x00_battery.c
drivers/power/ds2782_battery.c
drivers/power/generic-adc-battery.c
drivers/power/jz4740-battery.c
drivers/power/lp8788-charger.c
drivers/power/max17042_battery.c
drivers/power/max8925_power.c
drivers/power/power_supply_core.c
drivers/power/power_supply_sysfs.c
drivers/power/rx51_battery.c [new file with mode: 0644]
drivers/power/twl4030_charger.c
include/linux/mfd/abx500.h
include/linux/mfd/lp8788.h
include/linux/power/bq2415x_charger.h [new file with mode: 0644]
include/linux/power_supply.h

index ce83c8d3c00e262953b543717c00105ac92edf64..13b707b7355c005e9c877e860c93d4e9ca6baf12 100644 (file)
@@ -24,7 +24,32 @@ ab8500-bm                :                      :              : Battery Manager
 ab8500-btemp             :                      :              : Battery Temperature
 ab8500-charger           :                      :              : Battery Charger
 ab8500-codec             :                      :              : Audio Codec
-ab8500-fg                :                      :              : Fuel Gauge
+ab8500-fg                :                     : vddadc       : Fuel Gauge
+                        : NCONV_ACCU           :              : Accumulate N Sample Conversion
+                        : BATT_OVV             :              : Battery Over Voltage
+                        : LOW_BAT_F            :              : LOW threshold battery voltage
+                        : CC_INT_CALIB         :              : Coulomb Counter Internal Calibration
+                        : CCEOC                :              : Coulomb Counter End of Conversion
+ab8500-btemp            :                      : vtvout       : Battery Temperature
+                        : BAT_CTRL_INDB        :              : Battery Removal Indicator
+                        : BTEMP_LOW            :              : Btemp < BtempLow, if battery temperature is lower than -10°C
+                        : BTEMP_LOW_MEDIUM     :              : BtempLow < Btemp < BtempMedium,if battery temperature is between -10 and 0°C
+                        : BTEMP_MEDIUM_HIGH    :              : BtempMedium < Btemp < BtempHigh,if battery temperature is between 0°C and“MaxTemp
+                        : BTEMP_HIGH           :              : Btemp > BtempHigh, if battery temperature is higher than “MaxTemp
+ab8500-charger          :                      : vddadc       : Charger interface
+                        : MAIN_CH_UNPLUG_DET   :              : main charger unplug detection management (not in 8505)
+                        : MAIN_CHARGE_PLUG_DET :              : main charger plug detection management (not in 8505)
+                        : MAIN_EXT_CH_NOT_OK   :              : main charger not OK
+                        : MAIN_CH_TH_PROT_R    :              : Die temp is above main charger
+                        : MAIN_CH_TH_PROT_F    :              : Die temp is below main charger
+                        : VBUS_DET_F           :              : VBUS falling detected
+                        : VBUS_DET_R           :              : VBUS rising detected
+                        : USB_LINK_STATUS      :              : USB link status has changed
+                        : USB_CH_TH_PROT_R     :              : Die temp is above usb charger
+                        : USB_CH_TH_PROT_F     :              : Die temp is below usb charger
+                        : USB_CHARGER_NOT_OKR  :              : allowed USB charger not ok detection
+                        : VBUS_OVV             :              : Overvoltage on Vbus ball detected (USB charge is stopped)
+                        : CH_WD_EXP            :              : Charger watchdog detected
 ab8500-gpadc             : HW_CONV_END          : vddadc       : Analogue to Digital Converter
                            SW_CONV_END          :              :
 ab8500-gpio              :                      :              : GPIO Controller
diff --git a/Documentation/devicetree/bindings/power_supply/ab8500/btemp.txt b/Documentation/devicetree/bindings/power_supply/ab8500/btemp.txt
new file mode 100644 (file)
index 0000000..0ba1bcc
--- /dev/null
@@ -0,0 +1,16 @@
+=== AB8500 Battery Temperature Monitor Driver ===
+
+The properties below describes the node for btemp driver.
+
+Required Properties:
+- compatible = Shall be: "stericsson,ab8500-btemp"
+- battery = Shall be battery specific information
+
+       Example:
+       ab8500_btemp {
+               compatible = "stericsson,ab8500-btemp";
+               battery    = <&ab8500_battery>;
+       };
+
+For information on battery specific node, Ref:
+Documentation/devicetree/bindings/power_supply/ab8500/fg.txt
diff --git a/Documentation/devicetree/bindings/power_supply/ab8500/chargalg.txt b/Documentation/devicetree/bindings/power_supply/ab8500/chargalg.txt
new file mode 100644 (file)
index 0000000..ef53283
--- /dev/null
@@ -0,0 +1,16 @@
+=== AB8500 Charging Algorithm Driver ===
+
+The properties below describes the node for chargalg driver.
+
+Required Properties:
+- compatible = Shall be: "stericsson,ab8500-chargalg"
+- battery = Shall be battery specific information
+
+Example:
+ab8500_chargalg {
+       compatible = "stericsson,ab8500-chargalg";
+       battery    = <&ab8500_battery>;
+};
+
+For information on battery specific node, Ref:
+Documentation/devicetree/bindings/power_supply/ab8500/fg.txt
diff --git a/Documentation/devicetree/bindings/power_supply/ab8500/charger.txt b/Documentation/devicetree/bindings/power_supply/ab8500/charger.txt
new file mode 100644 (file)
index 0000000..6bdbb08
--- /dev/null
@@ -0,0 +1,25 @@
+=== AB8500 Charger Driver ===
+
+Required Properties:
+- compatible = Shall be "stericsson,ab8500-charger"
+- battery = Shall be battery specific information
+       Example:
+       ab8500_charger {
+               compatible = "stericsson,ab8500-charger";
+               battery    = <&ab8500_battery>;
+       };
+
+- vddadc-supply: Supply for USB and Main charger
+       Example:
+       ab8500-charger {
+               vddadc-supply   = <&ab8500_ldo_tvout_reg>;
+       }
+- autopower_cfg:
+       Boolean value depicting the presence of 'automatic poweron after powerloss'
+       Example:
+       ab8500-charger {
+               autopower_cfg;
+       };
+
+For information on battery specific node, Ref:
+Documentation/devicetree/bindings/power_supply/ab8500/fg.txt
diff --git a/Documentation/devicetree/bindings/power_supply/ab8500/fg.txt b/Documentation/devicetree/bindings/power_supply/ab8500/fg.txt
new file mode 100644 (file)
index 0000000..ccafcb9
--- /dev/null
@@ -0,0 +1,58 @@
+=== AB8500 Fuel Gauge Driver ===
+
+AB8500 is a mixed signal multimedia and power management
+device comprising: power and energy-management-module,
+wall-charger, usb-charger, audio codec, general purpose adc,
+tvout, clock management and sim card interface.
+
+Fuelgauge support is part of energy-management-modules, other
+components of this module are:
+main-charger, usb-combo-charger and battery-temperature-monitoring.
+
+The properties below describes the node for fuelgauge driver.
+
+Required Properties:
+- compatible = This shall be: "stericsson,ab8500-fg"
+- battery = Shall be battery specific information
+       Example:
+       ab8500_fg {
+               compatible = "stericsson,ab8500-fg";
+               battery    = <&ab8500_battery>;
+       };
+
+dependent node:
+       ab8500_battery: ab8500_battery {
+       };
+       This node will provide information on 'thermistor interface' and
+       'battery technology type' used.
+
+Properties of this node are:
+thermistor-on-batctrl:
+       A boolean value indicating thermistor interface to battery
+
+       Note:
+       'btemp' and 'batctrl' are the pins interfaced for battery temperature
+       measurement, 'btemp' signal is used when NTC(negative temperature
+       coefficient) resister is interfaced external to battery whereas
+       'batctrl' pin is used when NTC resister is internal to battery.
+
+       Example:
+       ab8500_battery: ab8500_battery {
+               thermistor-on-batctrl;
+       };
+       indicates: NTC resister is internal to battery, 'batctrl' is used
+               for thermal measurement.
+
+       The absence of property 'thermal-on-batctrl' indicates
+       NTC resister is external to battery and  'btemp' signal is used
+       for thermal measurement.
+
+battery-type:
+       This shall be the battery manufacturing technology type,
+       allowed types are:
+               "UNKNOWN" "NiMH" "LION" "LIPO" "LiFe" "NiCd" "LiMn"
+       Example:
+       ab8500_battery: ab8500_battery {
+               stericsson,battery-type = "LIPO";
+       }
+
index 9c647bd7c5a9cf108746647b8f97ec4b70afe1ab..3f10b39b034639cd558e080d289ed361c7ea63f7 100644 (file)
@@ -123,6 +123,9 @@ CONSTANT_CHARGE_VOLTAGE - constant charge voltage programmed by charger.
 CONSTANT_CHARGE_VOLTAGE_MAX - maximum charge voltage supported by the
 power supply object.
 
+CHARGE_CONTROL_LIMIT - current charge control limit setting
+CHARGE_CONTROL_LIMIT_MAX - maximum charge control limit setting
+
 ENERGY_FULL, ENERGY_EMPTY - same as above but for energy.
 
 CAPACITY - capacity in percents.
index 0d69322f689f03bbe16b1d061d33cd847ced989f..2efd9c891bc91f85522a6158854fd14e89b7f65f 100644 (file)
                                        vddadc-supply = <&ab8500_ldo_tvout_reg>;
                                };
 
-                               ab8500-usb {
+                               ab8500_battery: ab8500_battery {
+                                       stericsson,battery-type = "LIPO";
+                                       thermistor-on-batctrl;
+                               };
+
+                               ab8500_fg {
+                                       compatible = "stericsson,ab8500-fg";
+                                       battery    = <&ab8500_battery>;
+                               };
+
+                               ab8500_btemp {
+                                       compatible = "stericsson,ab8500-btemp";
+                                       battery    = <&ab8500_battery>;
+                               };
+
+                               ab8500_charger {
+                                       compatible      = "stericsson,ab8500-charger";
+                                       battery         = <&ab8500_battery>;
+                                       vddadc-supply   = <&ab8500_ldo_tvout_reg>;
+                               };
+
+                               ab8500_chargalg {
+                                       compatible      = "stericsson,ab8500-chargalg";
+                                       battery         = <&ab8500_battery>;
+                               };
+
+                               ab8500_usb {
                                        compatible = "stericsson,ab8500-usb";
                                        interrupts = < 90 0x4
                                                       96 0x4
index 3e27c031aeaa26fad637fadb86641851470595e0..59da1650fb814ea5c688f2c83051734d6bc2e8a7 100644 (file)
@@ -1036,23 +1036,43 @@ static struct mfd_cell abx500_common_devs[] = {
 static struct mfd_cell ab8500_bm_devs[] = {
        {
                .name = "ab8500-charger",
+               .of_compatible = "stericsson,ab8500-charger",
                .num_resources = ARRAY_SIZE(ab8500_charger_resources),
                .resources = ab8500_charger_resources,
+#ifndef CONFIG_OF
+               .platform_data = &ab8500_bm_data,
+               .pdata_size = sizeof(ab8500_bm_data),
+#endif
        },
        {
                .name = "ab8500-btemp",
+               .of_compatible = "stericsson,ab8500-btemp",
                .num_resources = ARRAY_SIZE(ab8500_btemp_resources),
                .resources = ab8500_btemp_resources,
+#ifndef CONFIG_OF
+               .platform_data = &ab8500_bm_data,
+               .pdata_size = sizeof(ab8500_bm_data),
+#endif
        },
        {
                .name = "ab8500-fg",
+               .of_compatible = "stericsson,ab8500-fg",
                .num_resources = ARRAY_SIZE(ab8500_fg_resources),
                .resources = ab8500_fg_resources,
+#ifndef CONFIG_OF
+               .platform_data = &ab8500_bm_data,
+               .pdata_size = sizeof(ab8500_bm_data),
+#endif
        },
        {
                .name = "ab8500-chargalg",
+               .of_compatible = "stericsson,ab8500-chargalg",
                .num_resources = ARRAY_SIZE(ab8500_chargalg_resources),
                .resources = ab8500_chargalg_resources,
+#ifndef CONFIG_OF
+               .platform_data = &ab8500_bm_data,
+               .pdata_size = sizeof(ab8500_bm_data),
+#endif
        },
 };
 
index b1d956d81f0c341fc8f24a8c013e3ddc1d016079..9f45e2f77d531748016db6799a39fcd7465b6ab9 100644 (file)
@@ -245,6 +245,13 @@ config BATTERY_INTEL_MID
          Say Y here to enable the battery driver on Intel MID
          platforms.
 
+config BATTERY_RX51
+       tristate "Nokia RX-51 (N900) battery driver"
+       depends on TWL4030_MADC
+       help
+         Say Y here to enable support for battery information on Nokia
+         RX-51, also known as N900 tablet.
+
 config CHARGER_ISP1704
        tristate "ISP1704 USB Charger Detection"
        depends on USB_OTG_UTILS
@@ -315,6 +322,16 @@ config CHARGER_MAX8998
          Say Y to enable support for the battery charger control sysfs and
          platform data of MAX8998/LP3974 PMICs.
 
+config CHARGER_BQ2415X
+       tristate "TI BQ2415x battery charger driver"
+       depends on I2C
+       help
+         Say Y to enable support for the TI BQ2415x battery charger
+         PMICs.
+
+         You'll need this driver to charge batteries on e.g. Nokia
+         RX-51/N900.
+
 config CHARGER_SMB347
        tristate "Summit Microelectronics SMB347 Battery Charger"
        depends on I2C
@@ -329,13 +346,6 @@ config AB8500_BM
        help
          Say Y to include support for AB8500 battery management.
 
-config AB8500_BATTERY_THERM_ON_BATCTRL
-       bool "Thermistor connected on BATCTRL ADC"
-       depends on AB8500_BM
-       help
-         Say Y to enable battery temperature measurements using
-         thermistor connected on BATCTRL ADC.
-
 source "drivers/power/reset/Kconfig"
 
 endif # POWER_SUPPLY
index f1d99f4a0bc35b8b64c8e494a03c2f4049688985..22c8913382c0839bda10690268fb740e5ea42999 100644 (file)
@@ -37,7 +37,8 @@ obj-$(CONFIG_CHARGER_88PM860X)        += 88pm860x_charger.o
 obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
 obj-$(CONFIG_BATTERY_JZ4740)   += jz4740-battery.o
 obj-$(CONFIG_BATTERY_INTEL_MID)        += intel_mid_battery.o
-obj-$(CONFIG_AB8500_BM)                += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o
+obj-$(CONFIG_BATTERY_RX51)     += rx51_battery.o
+obj-$(CONFIG_AB8500_BM)                += ab8500_bmdata.o ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o
 obj-$(CONFIG_CHARGER_ISP1704)  += isp1704_charger.o
 obj-$(CONFIG_CHARGER_MAX8903)  += max8903_charger.o
 obj-$(CONFIG_CHARGER_TWL4030)  += twl4030_charger.o
@@ -47,6 +48,7 @@ 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
+obj-$(CONFIG_CHARGER_BQ2415X)  += bq2415x_charger.o
 obj-$(CONFIG_POWER_AVS)                += avs/
 obj-$(CONFIG_CHARGER_SMB347)   += smb347-charger.o
 obj-$(CONFIG_POWER_RESET)      += reset/
diff --git a/drivers/power/ab8500_bmdata.c b/drivers/power/ab8500_bmdata.c
new file mode 100644 (file)
index 0000000..03cc528
--- /dev/null
@@ -0,0 +1,521 @@
+#include <linux/export.h>
+#include <linux/power_supply.h>
+#include <linux/of.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+
+/*
+ * These are the defined batteries that uses a NTC and ID resistor placed
+ * inside of the battery pack.
+ * Note that the res_to_temp table must be strictly sorted by falling resistance
+ * values to work.
+ */
+static struct abx500_res_to_temp temp_tbl_A_thermistor[] = {
+       {-5, 53407},
+       { 0, 48594},
+       { 5, 43804},
+       {10, 39188},
+       {15, 34870},
+       {20, 30933},
+       {25, 27422},
+       {30, 24347},
+       {35, 21694},
+       {40, 19431},
+       {45, 17517},
+       {50, 15908},
+       {55, 14561},
+       {60, 13437},
+       {65, 12500},
+};
+
+static struct abx500_res_to_temp temp_tbl_B_thermistor[] = {
+       {-5, 200000},
+       { 0, 159024},
+       { 5, 151921},
+       {10, 144300},
+       {15, 136424},
+       {20, 128565},
+       {25, 120978},
+       {30, 113875},
+       {35, 107397},
+       {40, 101629},
+       {45,  96592},
+       {50,  92253},
+       {55,  88569},
+       {60,  85461},
+       {65,  82869},
+};
+
+static struct abx500_v_to_cap cap_tbl_A_thermistor[] = {
+       {4171,  100},
+       {4114,   95},
+       {4009,   83},
+       {3947,   74},
+       {3907,   67},
+       {3863,   59},
+       {3830,   56},
+       {3813,   53},
+       {3791,   46},
+       {3771,   33},
+       {3754,   25},
+       {3735,   20},
+       {3717,   17},
+       {3681,   13},
+       {3664,    8},
+       {3651,    6},
+       {3635,    5},
+       {3560,    3},
+       {3408,    1},
+       {3247,    0},
+};
+
+static struct abx500_v_to_cap cap_tbl_B_thermistor[] = {
+       {4161,  100},
+       {4124,   98},
+       {4044,   90},
+       {4003,   85},
+       {3966,   80},
+       {3933,   75},
+       {3888,   67},
+       {3849,   60},
+       {3813,   55},
+       {3787,   47},
+       {3772,   30},
+       {3751,   25},
+       {3718,   20},
+       {3681,   16},
+       {3660,   14},
+       {3589,   10},
+       {3546,    7},
+       {3495,    4},
+       {3404,    2},
+       {3250,    0},
+};
+
+static struct abx500_v_to_cap cap_tbl[] = {
+       {4186,  100},
+       {4163,   99},
+       {4114,   95},
+       {4068,   90},
+       {3990,   80},
+       {3926,   70},
+       {3898,   65},
+       {3866,   60},
+       {3833,   55},
+       {3812,   50},
+       {3787,   40},
+       {3768,   30},
+       {3747,   25},
+       {3730,   20},
+       {3705,   15},
+       {3699,   14},
+       {3684,   12},
+       {3672,    9},
+       {3657,    7},
+       {3638,    6},
+       {3556,    4},
+       {3424,    2},
+       {3317,    1},
+       {3094,    0},
+};
+
+/*
+ * Note that the res_to_temp table must be strictly sorted by falling
+ * resistance values to work.
+ */
+static struct abx500_res_to_temp temp_tbl[] = {
+       {-5, 214834},
+       { 0, 162943},
+       { 5, 124820},
+       {10,  96520},
+       {15,  75306},
+       {20,  59254},
+       {25,  47000},
+       {30,  37566},
+       {35,  30245},
+       {40,  24520},
+       {45,  20010},
+       {50,  16432},
+       {55,  13576},
+       {60,  11280},
+       {65,   9425},
+};
+
+/*
+ * Note that the batres_vs_temp table must be strictly sorted by falling
+ * temperature values to work.
+ */
+static struct batres_vs_temp temp_to_batres_tbl_thermistor[] = {
+       { 40, 120},
+       { 30, 135},
+       { 20, 165},
+       { 10, 230},
+       { 00, 325},
+       {-10, 445},
+       {-20, 595},
+};
+
+/*
+ * Note that the batres_vs_temp table must be strictly sorted by falling
+ * temperature values to work.
+ */
+static struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = {
+       { 60, 300},
+       { 30, 300},
+       { 20, 300},
+       { 10, 300},
+       { 00, 300},
+       {-10, 300},
+       {-20, 300},
+};
+
+/* battery resistance table for LI ION 9100 battery */
+static struct batres_vs_temp temp_to_batres_tbl_9100[] = {
+       { 60, 180},
+       { 30, 180},
+       { 20, 180},
+       { 10, 180},
+       { 00, 180},
+       {-10, 180},
+       {-20, 180},
+};
+
+static struct abx500_battery_type bat_type_thermistor[] = {
+[BATTERY_UNKNOWN] = {
+       /* First element always represent the UNKNOWN battery */
+       .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN,
+       .resis_high = 0,
+       .resis_low = 0,
+       .battery_resistance = 300,
+       .charge_full_design = 612,
+       .nominal_voltage = 3700,
+       .termination_vol = 4050,
+       .termination_curr = 200,
+       .recharge_vol = 3990,
+       .normal_cur_lvl = 400,
+       .normal_vol_lvl = 4100,
+       .maint_a_cur_lvl = 400,
+       .maint_a_vol_lvl = 4050,
+       .maint_a_chg_timer_h = 60,
+       .maint_b_cur_lvl = 400,
+       .maint_b_vol_lvl = 4000,
+       .maint_b_chg_timer_h = 200,
+       .low_high_cur_lvl = 300,
+       .low_high_vol_lvl = 4000,
+       .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+       .r_to_t_tbl = temp_tbl,
+       .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+       .v_to_cap_tbl = cap_tbl,
+       .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+       .batres_tbl = temp_to_batres_tbl_thermistor,
+},
+{
+       .name = POWER_SUPPLY_TECHNOLOGY_LIPO,
+       .resis_high = 53407,
+       .resis_low = 12500,
+       .battery_resistance = 300,
+       .charge_full_design = 900,
+       .nominal_voltage = 3600,
+       .termination_vol = 4150,
+       .termination_curr = 80,
+       .recharge_vol = 4130,
+       .normal_cur_lvl = 700,
+       .normal_vol_lvl = 4200,
+       .maint_a_cur_lvl = 600,
+       .maint_a_vol_lvl = 4150,
+       .maint_a_chg_timer_h = 60,
+       .maint_b_cur_lvl = 600,
+       .maint_b_vol_lvl = 4100,
+       .maint_b_chg_timer_h = 200,
+       .low_high_cur_lvl = 300,
+       .low_high_vol_lvl = 4000,
+       .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_A_thermistor),
+       .r_to_t_tbl = temp_tbl_A_thermistor,
+       .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_A_thermistor),
+       .v_to_cap_tbl = cap_tbl_A_thermistor,
+       .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+       .batres_tbl = temp_to_batres_tbl_thermistor,
+
+},
+{
+       .name = POWER_SUPPLY_TECHNOLOGY_LIPO,
+       .resis_high = 200000,
+       .resis_low = 82869,
+       .battery_resistance = 300,
+       .charge_full_design = 900,
+       .nominal_voltage = 3600,
+       .termination_vol = 4150,
+       .termination_curr = 80,
+       .recharge_vol = 4130,
+       .normal_cur_lvl = 700,
+       .normal_vol_lvl = 4200,
+       .maint_a_cur_lvl = 600,
+       .maint_a_vol_lvl = 4150,
+       .maint_a_chg_timer_h = 60,
+       .maint_b_cur_lvl = 600,
+       .maint_b_vol_lvl = 4100,
+       .maint_b_chg_timer_h = 200,
+       .low_high_cur_lvl = 300,
+       .low_high_vol_lvl = 4000,
+       .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_B_thermistor),
+       .r_to_t_tbl = temp_tbl_B_thermistor,
+       .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_B_thermistor),
+       .v_to_cap_tbl = cap_tbl_B_thermistor,
+       .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+       .batres_tbl = temp_to_batres_tbl_thermistor,
+},
+};
+
+static struct abx500_battery_type bat_type_ext_thermistor[] = {
+[BATTERY_UNKNOWN] = {
+       /* First element always represent the UNKNOWN battery */
+       .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN,
+       .resis_high = 0,
+       .resis_low = 0,
+       .battery_resistance = 300,
+       .charge_full_design = 612,
+       .nominal_voltage = 3700,
+       .termination_vol = 4050,
+       .termination_curr = 200,
+       .recharge_vol = 3990,
+       .normal_cur_lvl = 400,
+       .normal_vol_lvl = 4100,
+       .maint_a_cur_lvl = 400,
+       .maint_a_vol_lvl = 4050,
+       .maint_a_chg_timer_h = 60,
+       .maint_b_cur_lvl = 400,
+       .maint_b_vol_lvl = 4000,
+       .maint_b_chg_timer_h = 200,
+       .low_high_cur_lvl = 300,
+       .low_high_vol_lvl = 4000,
+       .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+       .r_to_t_tbl = temp_tbl,
+       .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+       .v_to_cap_tbl = cap_tbl,
+       .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+       .batres_tbl = temp_to_batres_tbl_thermistor,
+},
+/*
+ * These are the batteries that doesn't have an internal NTC resistor to measure
+ * its temperature. The temperature in this case is measure with a NTC placed
+ * near the battery but on the PCB.
+ */
+{
+       .name = POWER_SUPPLY_TECHNOLOGY_LIPO,
+       .resis_high = 76000,
+       .resis_low = 53000,
+       .battery_resistance = 300,
+       .charge_full_design = 900,
+       .nominal_voltage = 3700,
+       .termination_vol = 4150,
+       .termination_curr = 100,
+       .recharge_vol = 4130,
+       .normal_cur_lvl = 700,
+       .normal_vol_lvl = 4200,
+       .maint_a_cur_lvl = 600,
+       .maint_a_vol_lvl = 4150,
+       .maint_a_chg_timer_h = 60,
+       .maint_b_cur_lvl = 600,
+       .maint_b_vol_lvl = 4100,
+       .maint_b_chg_timer_h = 200,
+       .low_high_cur_lvl = 300,
+       .low_high_vol_lvl = 4000,
+       .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+       .r_to_t_tbl = temp_tbl,
+       .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+       .v_to_cap_tbl = cap_tbl,
+       .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+       .batres_tbl = temp_to_batres_tbl_thermistor,
+},
+{
+       .name = POWER_SUPPLY_TECHNOLOGY_LION,
+       .resis_high = 30000,
+       .resis_low = 10000,
+       .battery_resistance = 300,
+       .charge_full_design = 950,
+       .nominal_voltage = 3700,
+       .termination_vol = 4150,
+       .termination_curr = 100,
+       .recharge_vol = 4130,
+       .normal_cur_lvl = 700,
+       .normal_vol_lvl = 4200,
+       .maint_a_cur_lvl = 600,
+       .maint_a_vol_lvl = 4150,
+       .maint_a_chg_timer_h = 60,
+       .maint_b_cur_lvl = 600,
+       .maint_b_vol_lvl = 4100,
+       .maint_b_chg_timer_h = 200,
+       .low_high_cur_lvl = 300,
+       .low_high_vol_lvl = 4000,
+       .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+       .r_to_t_tbl = temp_tbl,
+       .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+       .v_to_cap_tbl = cap_tbl,
+       .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+       .batres_tbl = temp_to_batres_tbl_thermistor,
+},
+{
+       .name = POWER_SUPPLY_TECHNOLOGY_LION,
+       .resis_high = 95000,
+       .resis_low = 76001,
+       .battery_resistance = 300,
+       .charge_full_design = 950,
+       .nominal_voltage = 3700,
+       .termination_vol = 4150,
+       .termination_curr = 100,
+       .recharge_vol = 4130,
+       .normal_cur_lvl = 700,
+       .normal_vol_lvl = 4200,
+       .maint_a_cur_lvl = 600,
+       .maint_a_vol_lvl = 4150,
+       .maint_a_chg_timer_h = 60,
+       .maint_b_cur_lvl = 600,
+       .maint_b_vol_lvl = 4100,
+       .maint_b_chg_timer_h = 200,
+       .low_high_cur_lvl = 300,
+       .low_high_vol_lvl = 4000,
+       .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+       .r_to_t_tbl = temp_tbl,
+       .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+       .v_to_cap_tbl = cap_tbl,
+       .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+       .batres_tbl = temp_to_batres_tbl_thermistor,
+},
+};
+
+static const struct abx500_bm_capacity_levels cap_levels = {
+       .critical       = 2,
+       .low            = 10,
+       .normal         = 70,
+       .high           = 95,
+       .full           = 100,
+};
+
+static const struct abx500_fg_parameters fg = {
+       .recovery_sleep_timer = 10,
+       .recovery_total_time = 100,
+       .init_timer = 1,
+       .init_discard_time = 5,
+       .init_total_time = 40,
+       .high_curr_time = 60,
+       .accu_charging = 30,
+       .accu_high_curr = 30,
+       .high_curr_threshold = 50,
+       .lowbat_threshold = 3100,
+       .battok_falling_th_sel0 = 2860,
+       .battok_raising_th_sel1 = 2860,
+       .user_cap_limit = 15,
+       .maint_thres = 97,
+};
+
+static const struct abx500_maxim_parameters maxi_params = {
+       .ena_maxi = true,
+       .chg_curr = 910,
+       .wait_cycles = 10,
+       .charger_curr_step = 100,
+};
+
+static const struct abx500_bm_charger_parameters chg = {
+       .usb_volt_max           = 5500,
+       .usb_curr_max           = 1500,
+       .ac_volt_max            = 7500,
+       .ac_curr_max            = 1500,
+};
+
+struct abx500_bm_data ab8500_bm_data = {
+       .temp_under             = 3,
+       .temp_low               = 8,
+       .temp_high              = 43,
+       .temp_over              = 48,
+       .main_safety_tmr_h      = 4,
+       .temp_interval_chg      = 20,
+       .temp_interval_nochg    = 120,
+       .usb_safety_tmr_h       = 4,
+       .bkup_bat_v             = BUP_VCH_SEL_2P6V,
+       .bkup_bat_i             = BUP_ICH_SEL_150UA,
+       .no_maintenance         = false,
+       .adc_therm              = ABx500_ADC_THERM_BATCTRL,
+       .chg_unknown_bat        = false,
+       .enable_overshoot       = false,
+       .fg_res                 = 100,
+       .cap_levels             = &cap_levels,
+       .bat_type               = bat_type_thermistor,
+       .n_btypes               = 3,
+       .batt_id                = 0,
+       .interval_charging      = 5,
+       .interval_not_charging  = 120,
+       .temp_hysteresis        = 3,
+       .gnd_lift_resistance    = 34,
+       .maxi                   = &maxi_params,
+       .chg_params             = &chg,
+       .fg_params              = &fg,
+};
+
+int __devinit
+bmdevs_of_probe(struct device *dev,
+               struct device_node *np,
+               struct abx500_bm_data **battery)
+{
+       struct  abx500_battery_type *btype;
+       struct  device_node *np_bat_supply;
+       struct  abx500_bm_data *bat;
+       const char *btech;
+       char bat_tech[8];
+       int i, thermistor;
+
+       *battery = &ab8500_bm_data;
+
+       /* get phandle to 'battery-info' node */
+       np_bat_supply = of_parse_phandle(np, "battery", 0);
+       if (!np_bat_supply) {
+               dev_err(dev, "missing property battery\n");
+               return -EINVAL;
+       }
+       if (of_property_read_bool(np_bat_supply,
+                       "thermistor-on-batctrl"))
+               thermistor = NTC_INTERNAL;
+       else
+               thermistor = NTC_EXTERNAL;
+
+       bat = *battery;
+       if (thermistor == NTC_EXTERNAL) {
+               bat->n_btypes  = 4;
+               bat->bat_type  = bat_type_ext_thermistor;
+               bat->adc_therm = ABx500_ADC_THERM_BATTEMP;
+       }
+       btech = of_get_property(np_bat_supply,
+               "stericsson,battery-type", NULL);
+       if (!btech) {
+               dev_warn(dev, "missing property battery-name/type\n");
+               strcpy(bat_tech, "UNKNOWN");
+       } else {
+               strcpy(bat_tech, btech);
+       }
+
+       if (strncmp(bat_tech, "LION", 4) == 0) {
+               bat->no_maintenance  = true;
+               bat->chg_unknown_bat = true;
+               bat->bat_type[BATTERY_UNKNOWN].charge_full_design = 2600;
+               bat->bat_type[BATTERY_UNKNOWN].termination_vol    = 4150;
+               bat->bat_type[BATTERY_UNKNOWN].recharge_vol       = 4130;
+               bat->bat_type[BATTERY_UNKNOWN].normal_cur_lvl     = 520;
+               bat->bat_type[BATTERY_UNKNOWN].normal_vol_lvl     = 4200;
+       }
+       /* select the battery resolution table */
+       for (i = 0; i < bat->n_btypes; ++i) {
+               btype = (bat->bat_type + i);
+               if (thermistor == NTC_EXTERNAL) {
+                       btype->batres_tbl =
+                               temp_to_batres_tbl_ext_thermistor;
+               } else if (strncmp(bat_tech, "LION", 4) == 0) {
+                       btype->batres_tbl =
+                               temp_to_batres_tbl_9100;
+               } else {
+                       btype->batres_tbl =
+                               temp_to_batres_tbl_thermistor;
+               }
+       }
+       of_node_put(np_bat_supply);
+       return 0;
+}
index 989b09950affb71fefb663b5aafa5e7564f9ee2d..20e2a7d3ef43c0f6877aae8e5b3cb9c08fc6a9ae 100644 (file)
 #include <linux/power_supply.h>
 #include <linux/completion.h>
 #include <linux/workqueue.h>
-#include <linux/mfd/abx500/ab8500.h>
+#include <linux/jiffies.h>
+#include <linux/of.h>
+#include <linux/mfd/core.h>
 #include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500.h>
 #include <linux/mfd/abx500/ab8500-bm.h>
 #include <linux/mfd/abx500/ab8500-gpadc.h>
-#include <linux/jiffies.h>
 
 #define VTVOUT_V                       1800
 
@@ -76,7 +78,6 @@ struct ab8500_btemp_ranges {
  * @parent:            Pointer to the struct ab8500
  * @gpadc:             Pointer to the struct gpadc
  * @fg:                        Pointer to the struct fg
- * @pdata:             Pointer to the abx500_btemp platform data
  * @bat:               Pointer to the abx500_bm platform data
  * @btemp_psy:         Structure for BTEMP specific battery properties
  * @events:            Structure for information about events triggered
@@ -93,7 +94,6 @@ struct ab8500_btemp {
        struct ab8500 *parent;
        struct ab8500_gpadc *gpadc;
        struct ab8500_fg *fg;
-       struct abx500_btemp_platform_data *pdata;
        struct abx500_bm_data *bat;
        struct power_supply btemp_psy;
        struct ab8500_btemp_events events;
@@ -955,56 +955,57 @@ static int ab8500_btemp_remove(struct platform_device *pdev)
        flush_scheduled_work();
        power_supply_unregister(&di->btemp_psy);
        platform_set_drvdata(pdev, NULL);
-       kfree(di);
 
        return 0;
 }
 
+static char *supply_interface[] = {
+       "ab8500_chargalg",
+       "ab8500_fg",
+};
+
 static int ab8500_btemp_probe(struct platform_device *pdev)
 {
+       struct device_node *np = pdev->dev.of_node;
+       struct ab8500_btemp *di;
        int irq, i, ret = 0;
        u8 val;
-       struct abx500_bm_plat_data *plat_data = pdev->dev.platform_data;
-       struct ab8500_btemp *di;
-
-       if (!plat_data) {
-               dev_err(&pdev->dev, "No platform data\n");
-               return -EINVAL;
-       }
 
-       di = kzalloc(sizeof(*di), GFP_KERNEL);
-       if (!di)
+       di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+       if (!di) {
+               dev_err(&pdev->dev, "%s no mem for ab8500_btemp\n", __func__);
                return -ENOMEM;
+       }
+       di->bat = pdev->mfd_cell->platform_data;
+       if (!di->bat) {
+               if (np) {
+                       ret = bmdevs_of_probe(&pdev->dev, np, &di->bat);
+                       if (ret) {
+                               dev_err(&pdev->dev,
+                                       "failed to get battery information\n");
+                               return ret;
+                       }
+               } else {
+                       dev_err(&pdev->dev, "missing dt node for ab8500_btemp\n");
+                       return -EINVAL;
+               }
+       } else {
+               dev_info(&pdev->dev, "falling back to legacy platform data\n");
+       }
 
        /* get parent data */
        di->dev = &pdev->dev;
        di->parent = dev_get_drvdata(pdev->dev.parent);
        di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
 
-       /* get btemp specific platform data */
-       di->pdata = plat_data->btemp;
-       if (!di->pdata) {
-               dev_err(di->dev, "no btemp platform data supplied\n");
-               ret = -EINVAL;
-               goto free_device_info;
-       }
-
-       /* get battery specific platform data */
-       di->bat = plat_data->battery;
-       if (!di->bat) {
-               dev_err(di->dev, "no battery platform data supplied\n");
-               ret = -EINVAL;
-               goto free_device_info;
-       }
-
        /* BTEMP supply */
        di->btemp_psy.name = "ab8500_btemp";
        di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY;
        di->btemp_psy.properties = ab8500_btemp_props;
        di->btemp_psy.num_properties = ARRAY_SIZE(ab8500_btemp_props);
        di->btemp_psy.get_property = ab8500_btemp_get_property;
-       di->btemp_psy.supplied_to = di->pdata->supplied_to;
-       di->btemp_psy.num_supplicants = di->pdata->num_supplicants;
+       di->btemp_psy.supplied_to = supply_interface;
+       di->btemp_psy.num_supplicants = ARRAY_SIZE(supply_interface);
        di->btemp_psy.external_power_changed =
                ab8500_btemp_external_power_changed;
 
@@ -1014,8 +1015,7 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
                create_singlethread_workqueue("ab8500_btemp_wq");
        if (di->btemp_wq == NULL) {
                dev_err(di->dev, "failed to create work queue\n");
-               ret = -ENOMEM;
-               goto free_device_info;
+               return -ENOMEM;
        }
 
        /* Init work for measuring temperature periodically */
@@ -1093,12 +1093,14 @@ free_irq:
        }
 free_btemp_wq:
        destroy_workqueue(di->btemp_wq);
-free_device_info:
-       kfree(di);
-
        return ret;
 }
 
+static const struct of_device_id ab8500_btemp_match[] = {
+       { .compatible = "stericsson,ab8500-btemp", },
+       { },
+};
+
 static struct platform_driver ab8500_btemp_driver = {
        .probe = ab8500_btemp_probe,
        .remove = ab8500_btemp_remove,
@@ -1107,6 +1109,7 @@ static struct platform_driver ab8500_btemp_driver = {
        .driver = {
                .name = "ab8500-btemp",
                .owner = THIS_MODULE,
+               .of_match_table = ab8500_btemp_match,
        },
 };
 
index 7ecb8abe20b54a69189772c5ed913ede3475cf29..3be9c0ee3fc58a2c016cb9509ceb316c54312b5f 100644 (file)
@@ -23,6 +23,8 @@
 #include <linux/err.h>
 #include <linux/workqueue.h>
 #include <linux/kobject.h>
+#include <linux/of.h>
+#include <linux/mfd/core.h>
 #include <linux/mfd/abx500/ab8500.h>
 #include <linux/mfd/abx500.h>
 #include <linux/mfd/abx500/ab8500-bm.h>
@@ -181,9 +183,9 @@ struct ab8500_charger_usb_state {
  * @vbat               Battery voltage
  * @old_vbat           Previously measured battery voltage
  * @autopower          Indicate if we should have automatic pwron after pwrloss
+ * @autopower_cfg      platform specific power config support for "pwron after pwrloss"
  * @parent:            Pointer to the struct ab8500
  * @gpadc:             Pointer to the struct gpadc
- * @pdata:             Pointer to the abx500_charger platform data
  * @bat:               Pointer to the abx500_bm platform data
  * @flags:             Structure for information about events triggered
  * @usb_state:         Structure for usb stack information
@@ -218,9 +220,9 @@ struct ab8500_charger {
        int vbat;
        int old_vbat;
        bool autopower;
+       bool autopower_cfg;
        struct ab8500 *parent;
        struct ab8500_gpadc *gpadc;
-       struct abx500_charger_platform_data *pdata;
        struct abx500_bm_data *bat;
        struct ab8500_charger_event_flags flags;
        struct ab8500_charger_usb_state usb_state;
@@ -322,7 +324,7 @@ static void ab8500_power_loss_handling(struct ab8500_charger *di)
 static void ab8500_power_supply_changed(struct ab8500_charger *di,
                                        struct power_supply *psy)
 {
-       if (di->pdata->autopower_cfg) {
+       if (di->autopower_cfg) {
                if (!di->usb.charger_connected &&
                    !di->ac.charger_connected &&
                    di->autopower) {
@@ -2526,25 +2528,45 @@ static int ab8500_charger_remove(struct platform_device *pdev)
        power_supply_unregister(&di->usb_chg.psy);
        power_supply_unregister(&di->ac_chg.psy);
        platform_set_drvdata(pdev, NULL);
-       kfree(di);
 
        return 0;
 }
 
+static char *supply_interface[] = {
+       "ab8500_chargalg",
+       "ab8500_fg",
+       "ab8500_btemp",
+};
+
 static int ab8500_charger_probe(struct platform_device *pdev)
 {
-       int irq, i, charger_status, ret = 0;
-       struct abx500_bm_plat_data *plat_data = pdev->dev.platform_data;
+       struct device_node *np = pdev->dev.of_node;
        struct ab8500_charger *di;
+       int irq, i, charger_status, ret = 0;
 
-       if (!plat_data) {
-               dev_err(&pdev->dev, "No platform data\n");
-               return -EINVAL;
-       }
-
-       di = kzalloc(sizeof(*di), GFP_KERNEL);
-       if (!di)
+       di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+       if (!di) {
+               dev_err(&pdev->dev, "%s no mem for ab8500_charger\n", __func__);
                return -ENOMEM;
+       }
+       di->bat = pdev->mfd_cell->platform_data;
+       if (!di->bat) {
+               if (np) {
+                       ret = bmdevs_of_probe(&pdev->dev, np, &di->bat);
+                       if (ret) {
+                               dev_err(&pdev->dev,
+                                       "failed to get battery information\n");
+                               return ret;
+                       }
+                       di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
+               } else {
+                       dev_err(&pdev->dev, "missing dt node for ab8500_charger\n");
+                       return -EINVAL;
+               }
+       } else {
+               dev_info(&pdev->dev, "falling back to legacy platform data\n");
+               di->autopower_cfg = false;
+       }
 
        /* get parent data */
        di->dev = &pdev->dev;
@@ -2554,22 +2576,6 @@ static int ab8500_charger_probe(struct platform_device *pdev)
        /* initialize lock */
        spin_lock_init(&di->usb_state.usb_lock);
 
-       /* get charger specific platform data */
-       di->pdata = plat_data->charger;
-       if (!di->pdata) {
-               dev_err(di->dev, "no charger platform data supplied\n");
-               ret = -EINVAL;
-               goto free_device_info;
-       }
-
-       /* get battery specific platform data */
-       di->bat = plat_data->battery;
-       if (!di->bat) {
-               dev_err(di->dev, "no battery platform data supplied\n");
-               ret = -EINVAL;
-               goto free_device_info;
-       }
-
        di->autopower = false;
 
        /* AC supply */
@@ -2579,8 +2585,8 @@ static int ab8500_charger_probe(struct platform_device *pdev)
        di->ac_chg.psy.properties = ab8500_charger_ac_props;
        di->ac_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_ac_props);
        di->ac_chg.psy.get_property = ab8500_charger_ac_get_property;
-       di->ac_chg.psy.supplied_to = di->pdata->supplied_to;
-       di->ac_chg.psy.num_supplicants = di->pdata->num_supplicants;
+       di->ac_chg.psy.supplied_to = supply_interface;
+       di->ac_chg.psy.num_supplicants = ARRAY_SIZE(supply_interface),
        /* ux500_charger sub-class */
        di->ac_chg.ops.enable = &ab8500_charger_ac_en;
        di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
@@ -2597,8 +2603,8 @@ static int ab8500_charger_probe(struct platform_device *pdev)
        di->usb_chg.psy.properties = ab8500_charger_usb_props;
        di->usb_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_usb_props);
        di->usb_chg.psy.get_property = ab8500_charger_usb_get_property;
-       di->usb_chg.psy.supplied_to = di->pdata->supplied_to;
-       di->usb_chg.psy.num_supplicants = di->pdata->num_supplicants;
+       di->usb_chg.psy.supplied_to = supply_interface;
+       di->usb_chg.psy.num_supplicants = ARRAY_SIZE(supply_interface),
        /* ux500_charger sub-class */
        di->usb_chg.ops.enable = &ab8500_charger_usb_en;
        di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
@@ -2614,8 +2620,7 @@ static int ab8500_charger_probe(struct platform_device *pdev)
                create_singlethread_workqueue("ab8500_charger_wq");
        if (di->charger_wq == NULL) {
                dev_err(di->dev, "failed to create work queue\n");
-               ret = -ENOMEM;
-               goto free_device_info;
+               return -ENOMEM;
        }
 
        /* Init work for HW failure check */
@@ -2757,12 +2762,14 @@ free_regulator:
        regulator_put(di->regu);
 free_charger_wq:
        destroy_workqueue(di->charger_wq);
-free_device_info:
-       kfree(di);
-
        return ret;
 }
 
+static const struct of_device_id ab8500_charger_match[] = {
+       { .compatible = "stericsson,ab8500-charger", },
+       { },
+};
+
 static struct platform_driver ab8500_charger_driver = {
        .probe = ab8500_charger_probe,
        .remove = ab8500_charger_remove,
@@ -2771,6 +2778,7 @@ static struct platform_driver ab8500_charger_driver = {
        .driver = {
                .name = "ab8500-charger",
                .owner = THIS_MODULE,
+               .of_match_table = ab8500_charger_match,
        },
 };
 
index 331dc43ded4e6fe319d336a5725431317e94b701..b3bf178c346270fdb22800c53ed21b574a25b956 100644 (file)
 #include <linux/platform_device.h>
 #include <linux/power_supply.h>
 #include <linux/kobject.h>
-#include <linux/mfd/abx500/ab8500.h>
-#include <linux/mfd/abx500.h>
 #include <linux/slab.h>
-#include <linux/mfd/abx500/ab8500-bm.h>
 #include <linux/delay.h>
-#include <linux/mfd/abx500/ab8500-gpadc.h>
-#include <linux/mfd/abx500.h>
 #include <linux/time.h>
+#include <linux/of.h>
 #include <linux/completion.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
 
 #define MILLI_TO_MICRO                 1000
 #define FG_LSB_IN_MA                   1627
@@ -172,7 +173,6 @@ struct inst_curr_result_list {
  * @avg_cap:           Average capacity filter
  * @parent:            Pointer to the struct ab8500
  * @gpadc:             Pointer to the struct gpadc
- * @pdata:             Pointer to the abx500_fg platform data
  * @bat:               Pointer to the abx500_bm platform data
  * @fg_psy:            Structure that holds the FG specific battery properties
  * @fg_wq:             Work queue for running the FG algorithm
@@ -212,7 +212,6 @@ struct ab8500_fg {
        struct ab8500_fg_avg_cap avg_cap;
        struct ab8500 *parent;
        struct ab8500_gpadc *gpadc;
-       struct abx500_fg_platform_data *pdata;
        struct abx500_bm_data *bat;
        struct power_supply fg_psy;
        struct workqueue_struct *fg_wq;
@@ -2429,7 +2428,6 @@ static int ab8500_fg_remove(struct platform_device *pdev)
        flush_scheduled_work();
        power_supply_unregister(&di->fg_psy);
        platform_set_drvdata(pdev, NULL);
-       kfree(di);
        return ret;
 }
 
@@ -2442,21 +2440,39 @@ static struct ab8500_fg_interrupts ab8500_fg_irq[] = {
        {"CCEOC", ab8500_fg_cc_data_end_handler},
 };
 
+static char *supply_interface[] = {
+       "ab8500_chargalg",
+       "ab8500_usb",
+};
+
 static int ab8500_fg_probe(struct platform_device *pdev)
 {
+       struct device_node *np = pdev->dev.of_node;
+       struct ab8500_fg *di;
        int i, irq;
        int ret = 0;
-       struct abx500_bm_plat_data *plat_data = pdev->dev.platform_data;
-       struct ab8500_fg *di;
-
-       if (!plat_data) {
-               dev_err(&pdev->dev, "No platform data\n");
-               return -EINVAL;
-       }
 
-       di = kzalloc(sizeof(*di), GFP_KERNEL);
-       if (!di)
+       di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+       if (!di) {
+               dev_err(&pdev->dev, "%s no mem for ab8500_fg\n", __func__);
                return -ENOMEM;
+       }
+       di->bat = pdev->mfd_cell->platform_data;
+       if (!di->bat) {
+               if (np) {
+                       ret = bmdevs_of_probe(&pdev->dev, np, &di->bat);
+                       if (ret) {
+                               dev_err(&pdev->dev,
+                                       "failed to get battery information\n");
+                               return ret;
+                       }
+               } else {
+                       dev_err(&pdev->dev, "missing dt node for ab8500_fg\n");
+                       return -EINVAL;
+               }
+       } else {
+               dev_info(&pdev->dev, "falling back to legacy platform data\n");
+       }
 
        mutex_init(&di->cc_lock);
 
@@ -2465,29 +2481,13 @@ static int ab8500_fg_probe(struct platform_device *pdev)
        di->parent = dev_get_drvdata(pdev->dev.parent);
        di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
 
-       /* get fg specific platform data */
-       di->pdata = plat_data->fg;
-       if (!di->pdata) {
-               dev_err(di->dev, "no fg platform data supplied\n");
-               ret = -EINVAL;
-               goto free_device_info;
-       }
-
-       /* get battery specific platform data */
-       di->bat = plat_data->battery;
-       if (!di->bat) {
-               dev_err(di->dev, "no battery platform data supplied\n");
-               ret = -EINVAL;
-               goto free_device_info;
-       }
-
        di->fg_psy.name = "ab8500_fg";
        di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY;
        di->fg_psy.properties = ab8500_fg_props;
        di->fg_psy.num_properties = ARRAY_SIZE(ab8500_fg_props);
        di->fg_psy.get_property = ab8500_fg_get_property;
-       di->fg_psy.supplied_to = di->pdata->supplied_to;
-       di->fg_psy.num_supplicants = di->pdata->num_supplicants;
+       di->fg_psy.supplied_to = supply_interface;
+       di->fg_psy.num_supplicants = ARRAY_SIZE(supply_interface),
        di->fg_psy.external_power_changed = ab8500_fg_external_power_changed;
 
        di->bat_cap.max_mah_design = MILLI_TO_MICRO *
@@ -2506,8 +2506,7 @@ static int ab8500_fg_probe(struct platform_device *pdev)
        di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq");
        if (di->fg_wq == NULL) {
                dev_err(di->dev, "failed to create work queue\n");
-               ret = -ENOMEM;
-               goto free_device_info;
+               return -ENOMEM;
        }
 
        /* Init work for running the fg algorithm instantly */
@@ -2606,12 +2605,14 @@ free_irq:
        }
 free_inst_curr_wq:
        destroy_workqueue(di->fg_wq);
-free_device_info:
-       kfree(di);
-
        return ret;
 }
 
+static const struct of_device_id ab8500_fg_match[] = {
+       { .compatible = "stericsson,ab8500-fg", },
+       { },
+};
+
 static struct platform_driver ab8500_fg_driver = {
        .probe = ab8500_fg_probe,
        .remove = ab8500_fg_remove,
@@ -2620,6 +2621,7 @@ static struct platform_driver ab8500_fg_driver = {
        .driver = {
                .name = "ab8500-fg",
                .owner = THIS_MODULE,
+               .of_match_table = ab8500_fg_match,
        },
 };
 
index 19f25419079056b5a27260e53ce70639b0eeabdd..2970891460641177f53449dc1ff32b33ed26a375 100644 (file)
@@ -21,6 +21,8 @@
 #include <linux/completion.h>
 #include <linux/workqueue.h>
 #include <linux/kobject.h>
+#include <linux/of.h>
+#include <linux/mfd/core.h>
 #include <linux/mfd/abx500.h>
 #include <linux/mfd/abx500/ux500_chargalg.h>
 #include <linux/mfd/abx500/ab8500-bm.h>
@@ -205,7 +207,6 @@ enum maxim_ret {
  * @chg_info:          information about connected charger types
  * @batt_data:         data of the battery
  * @susp_status:       current charger suspension status
- * @pdata:             pointer to the abx500_chargalg platform data
  * @bat:               pointer to the abx500_bm platform data
  * @chargalg_psy:      structure that holds the battery properties exposed by
  *                     the charging algorithm
@@ -231,7 +232,6 @@ struct abx500_chargalg {
        struct abx500_chargalg_charger_info chg_info;
        struct abx500_chargalg_battery_data batt_data;
        struct abx500_chargalg_suspension_status susp_status;
-       struct abx500_chargalg_platform_data *pdata;
        struct abx500_bm_data *bat;
        struct power_supply chargalg_psy;
        struct ux500_charger *ac_chg;
@@ -1795,36 +1795,53 @@ static int abx500_chargalg_remove(struct platform_device *pdev)
        flush_scheduled_work();
        power_supply_unregister(&di->chargalg_psy);
        platform_set_drvdata(pdev, NULL);
-       kfree(di);
 
        return 0;
 }
 
+static char *supply_interface[] = {
+       "ab8500_fg",
+};
+
 static int abx500_chargalg_probe(struct platform_device *pdev)
 {
-       struct abx500_bm_plat_data *plat_data;
+       struct device_node *np = pdev->dev.of_node;
+       struct abx500_chargalg *di;
        int ret = 0;
 
-       struct abx500_chargalg *di =
-               kzalloc(sizeof(struct abx500_chargalg), GFP_KERNEL);
-       if (!di)
+       di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+       if (!di) {
+               dev_err(&pdev->dev, "%s no mem for ab8500_chargalg\n", __func__);
                return -ENOMEM;
+       }
+       di->bat = pdev->mfd_cell->platform_data;
+       if (!di->bat) {
+               if (np) {
+                       ret = bmdevs_of_probe(&pdev->dev, np, &di->bat);
+                       if (ret) {
+                               dev_err(&pdev->dev,
+                                       "failed to get battery information\n");
+                               return ret;
+                       }
+               } else {
+                       dev_err(&pdev->dev, "missing dt node for ab8500_chargalg\n");
+                       return -EINVAL;
+               }
+       } else {
+               dev_info(&pdev->dev, "falling back to legacy platform data\n");
+       }
 
        /* get device struct */
        di->dev = &pdev->dev;
 
-       plat_data = pdev->dev.platform_data;
-       di->pdata = plat_data->chargalg;
-       di->bat = plat_data->battery;
-
        /* chargalg supply */
        di->chargalg_psy.name = "abx500_chargalg";
        di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY;
        di->chargalg_psy.properties = abx500_chargalg_props;
        di->chargalg_psy.num_properties = ARRAY_SIZE(abx500_chargalg_props);
        di->chargalg_psy.get_property = abx500_chargalg_get_property;
-       di->chargalg_psy.supplied_to = di->pdata->supplied_to;
-       di->chargalg_psy.num_supplicants = di->pdata->num_supplicants;
+       di->chargalg_psy.supplied_to = supply_interface;
+       di->chargalg_psy.num_supplicants = ARRAY_SIZE(supply_interface),
        di->chargalg_psy.external_power_changed =
                abx500_chargalg_external_power_changed;
 
@@ -1844,7 +1861,7 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
                create_singlethread_workqueue("abx500_chargalg_wq");
        if (di->chargalg_wq == NULL) {
                dev_err(di->dev, "failed to create work queue\n");
-               goto free_device_info;
+               return -ENOMEM;
        }
 
        /* Init work for chargalg */
@@ -1885,20 +1902,23 @@ free_psy:
        power_supply_unregister(&di->chargalg_psy);
 free_chargalg_wq:
        destroy_workqueue(di->chargalg_wq);
-free_device_info:
-       kfree(di);
-
        return ret;
 }
 
+static const struct of_device_id ab8500_chargalg_match[] = {
+       { .compatible = "stericsson,ab8500-chargalg", },
+       { },
+};
+
 static struct platform_driver abx500_chargalg_driver = {
        .probe = abx500_chargalg_probe,
        .remove = abx500_chargalg_remove,
        .suspend = abx500_chargalg_suspend,
        .resume = abx500_chargalg_resume,
        .driver = {
-               .name = "abx500-chargalg",
+               .name = "ab8500-chargalg",
                .owner = THIS_MODULE,
+               .of_match_table = ab8500_chargalg_match,
        },
 };
 
diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c
new file mode 100644 (file)
index 0000000..ee842b3
--- /dev/null
@@ -0,0 +1,1670 @@
+/*
+ * bq2415x charger driver
+ *
+ * Copyright (C) 2011-2012  Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Datasheets:
+ * http://www.ti.com/product/bq24150
+ * http://www.ti.com/product/bq24150a
+ * http://www.ti.com/product/bq24152
+ * http://www.ti.com/product/bq24153
+ * http://www.ti.com/product/bq24153a
+ * http://www.ti.com/product/bq24155
+ */
+
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+
+#include <linux/power/bq2415x_charger.h>
+
+/* timeout for resetting chip timer */
+#define BQ2415X_TIMER_TIMEOUT          10
+
+#define BQ2415X_REG_STATUS             0x00
+#define BQ2415X_REG_CONTROL            0x01
+#define BQ2415X_REG_VOLTAGE            0x02
+#define BQ2415X_REG_VENDER             0x03
+#define BQ2415X_REG_CURRENT            0x04
+
+/* reset state for all registers */
+#define BQ2415X_RESET_STATUS           BIT(6)
+#define BQ2415X_RESET_CONTROL          (BIT(4)|BIT(5))
+#define BQ2415X_RESET_VOLTAGE          (BIT(1)|BIT(3))
+#define BQ2415X_RESET_CURRENT          (BIT(0)|BIT(3)|BIT(7))
+
+/* status register */
+#define BQ2415X_BIT_TMR_RST            7
+#define BQ2415X_BIT_OTG                        7
+#define BQ2415X_BIT_EN_STAT            6
+#define BQ2415X_MASK_STAT              (BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_STAT             4
+#define BQ2415X_BIT_BOOST              3
+#define BQ2415X_MASK_FAULT             (BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_FAULT            0
+
+/* control register */
+#define BQ2415X_MASK_LIMIT             (BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_LIMIT            6
+#define BQ2415X_MASK_VLOWV             (BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_VLOWV            4
+#define BQ2415X_BIT_TE                 3
+#define BQ2415X_BIT_CE                 2
+#define BQ2415X_BIT_HZ_MODE            1
+#define BQ2415X_BIT_OPA_MODE           0
+
+/* voltage register */
+#define BQ2415X_MASK_VO                (BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VO               2
+#define BQ2415X_BIT_OTG_PL             1
+#define BQ2415X_BIT_OTG_EN             0
+
+/* vender register */
+#define BQ2415X_MASK_VENDER            (BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VENDER           5
+#define BQ2415X_MASK_PN                        (BIT(3)|BIT(4))
+#define BQ2415X_SHIFT_PN               3
+#define BQ2415X_MASK_REVISION          (BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_REVISION         0
+
+/* current register */
+#define BQ2415X_MASK_RESET             BIT(7)
+#define BQ2415X_MASK_VI_CHRG           (BIT(4)|BIT(5)|BIT(6))
+#define BQ2415X_SHIFT_VI_CHRG          4
+/* N/A                                 BIT(3) */
+#define BQ2415X_MASK_VI_TERM           (BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_VI_TERM          0
+
+
+enum bq2415x_command {
+       BQ2415X_TIMER_RESET,
+       BQ2415X_OTG_STATUS,
+       BQ2415X_STAT_PIN_STATUS,
+       BQ2415X_STAT_PIN_ENABLE,
+       BQ2415X_STAT_PIN_DISABLE,
+       BQ2415X_CHARGE_STATUS,
+       BQ2415X_BOOST_STATUS,
+       BQ2415X_FAULT_STATUS,
+
+       BQ2415X_CHARGE_TERMINATION_STATUS,
+       BQ2415X_CHARGE_TERMINATION_ENABLE,
+       BQ2415X_CHARGE_TERMINATION_DISABLE,
+       BQ2415X_CHARGER_STATUS,
+       BQ2415X_CHARGER_ENABLE,
+       BQ2415X_CHARGER_DISABLE,
+       BQ2415X_HIGH_IMPEDANCE_STATUS,
+       BQ2415X_HIGH_IMPEDANCE_ENABLE,
+       BQ2415X_HIGH_IMPEDANCE_DISABLE,
+       BQ2415X_BOOST_MODE_STATUS,
+       BQ2415X_BOOST_MODE_ENABLE,
+       BQ2415X_BOOST_MODE_DISABLE,
+
+       BQ2415X_OTG_LEVEL,
+       BQ2415X_OTG_ACTIVATE_HIGH,
+       BQ2415X_OTG_ACTIVATE_LOW,
+       BQ2415X_OTG_PIN_STATUS,
+       BQ2415X_OTG_PIN_ENABLE,
+       BQ2415X_OTG_PIN_DISABLE,
+
+       BQ2415X_VENDER_CODE,
+       BQ2415X_PART_NUMBER,
+       BQ2415X_REVISION,
+};
+
+enum bq2415x_chip {
+       BQUNKNOWN,
+       BQ24150,
+       BQ24150A,
+       BQ24151,
+       BQ24151A,
+       BQ24152,
+       BQ24153,
+       BQ24153A,
+       BQ24155,
+       BQ24156,
+       BQ24156A,
+       BQ24158,
+};
+
+static char *bq2415x_chip_name[] = {
+       "unknown",
+       "bq24150",
+       "bq24150a",
+       "bq24151",
+       "bq24151a",
+       "bq24152",
+       "bq24153",
+       "bq24153a",
+       "bq24155",
+       "bq24156",
+       "bq24156a",
+       "bq24158",
+};
+
+struct bq2415x_device {
+       struct device *dev;
+       struct bq2415x_platform_data init_data;
+       struct power_supply charger;
+       struct delayed_work work;
+       enum bq2415x_mode reported_mode;/* mode reported by hook function */
+       enum bq2415x_mode mode;         /* current configured mode */
+       enum bq2415x_chip chip;
+       const char *timer_error;
+       char *model;
+       char *name;
+       int autotimer;  /* 1 - if driver automatically reset timer, 0 - not */
+       int automode;   /* 1 - enabled, 0 - disabled; -1 - not supported */
+       int id;
+};
+
+/* each registered chip must have unique id */
+static DEFINE_IDR(bq2415x_id);
+
+static DEFINE_MUTEX(bq2415x_id_mutex);
+static DEFINE_MUTEX(bq2415x_timer_mutex);
+static DEFINE_MUTEX(bq2415x_i2c_mutex);
+
+/**** i2c read functions ****/
+
+/* read value from register */
+static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg)
+{
+       struct i2c_client *client = to_i2c_client(bq->dev);
+       struct i2c_msg msg[2];
+       u8 val;
+       int ret;
+
+       if (!client->adapter)
+               return -ENODEV;
+
+       msg[0].addr = client->addr;
+       msg[0].flags = 0;
+       msg[0].buf = &reg;
+       msg[0].len = sizeof(reg);
+       msg[1].addr = client->addr;
+       msg[1].flags = I2C_M_RD;
+       msg[1].buf = &val;
+       msg[1].len = sizeof(val);
+
+       mutex_lock(&bq2415x_i2c_mutex);
+       ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+       mutex_unlock(&bq2415x_i2c_mutex);
+
+       if (ret < 0)
+               return ret;
+
+       return val;
+}
+
+/* read value from register, apply mask and right shift it */
+static int bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg,
+                                u8 mask, u8 shift)
+{
+       int ret;
+
+       if (shift > 8)
+               return -EINVAL;
+
+       ret = bq2415x_i2c_read(bq, reg);
+       if (ret < 0)
+               return ret;
+       return (ret & mask) >> shift;
+}
+
+/* read value from register and return one specified bit */
+static int bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit)
+{
+       if (bit > 8)
+               return -EINVAL;
+       return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit);
+}
+
+/**** i2c write functions ****/
+
+/* write value to register */
+static int bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val)
+{
+       struct i2c_client *client = to_i2c_client(bq->dev);
+       struct i2c_msg msg[1];
+       u8 data[2];
+       int ret;
+
+       data[0] = reg;
+       data[1] = val;
+
+       msg[0].addr = client->addr;
+       msg[0].flags = 0;
+       msg[0].buf = data;
+       msg[0].len = ARRAY_SIZE(data);
+
+       mutex_lock(&bq2415x_i2c_mutex);
+       ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+       mutex_unlock(&bq2415x_i2c_mutex);
+
+       /* i2c_transfer returns number of messages transferred */
+       if (ret < 0)
+               return ret;
+       else if (ret != 1)
+               return -EIO;
+
+       return 0;
+}
+
+/* read value from register, change it with mask left shifted and write back */
+static int bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val,
+                                 u8 mask, u8 shift)
+{
+       int ret;
+
+       if (shift > 8)
+               return -EINVAL;
+
+       ret = bq2415x_i2c_read(bq, reg);
+       if (ret < 0)
+               return ret;
+
+       ret &= ~mask;
+       ret |= val << shift;
+
+       return bq2415x_i2c_write(bq, reg, ret);
+}
+
+/* change only one bit in register */
+static int bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg,
+                                bool val, u8 bit)
+{
+       if (bit > 8)
+               return -EINVAL;
+       return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit);
+}
+
+/**** global functions ****/
+
+/* exec command function */
+static int bq2415x_exec_command(struct bq2415x_device *bq,
+                               enum bq2415x_command command)
+{
+       int ret;
+
+       switch (command) {
+       case BQ2415X_TIMER_RESET:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS,
+                               1, BQ2415X_BIT_TMR_RST);
+       case BQ2415X_OTG_STATUS:
+               return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
+                               BQ2415X_BIT_OTG);
+       case BQ2415X_STAT_PIN_STATUS:
+               return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
+                               BQ2415X_BIT_EN_STAT);
+       case BQ2415X_STAT_PIN_ENABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1,
+                               BQ2415X_BIT_EN_STAT);
+       case BQ2415X_STAT_PIN_DISABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0,
+                               BQ2415X_BIT_EN_STAT);
+       case BQ2415X_CHARGE_STATUS:
+               return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS,
+                               BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT);
+       case BQ2415X_BOOST_STATUS:
+               return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
+                               BQ2415X_BIT_BOOST);
+       case BQ2415X_FAULT_STATUS:
+               return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS,
+                       BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT);
+
+       case BQ2415X_CHARGE_TERMINATION_STATUS:
+               return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+                               BQ2415X_BIT_TE);
+       case BQ2415X_CHARGE_TERMINATION_ENABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+                               1, BQ2415X_BIT_TE);
+       case BQ2415X_CHARGE_TERMINATION_DISABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+                               0, BQ2415X_BIT_TE);
+       case BQ2415X_CHARGER_STATUS:
+               ret = bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+                       BQ2415X_BIT_CE);
+               if (ret < 0)
+                       return ret;
+               else
+                       return ret > 0 ? 0 : 1;
+       case BQ2415X_CHARGER_ENABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+                               0, BQ2415X_BIT_CE);
+       case BQ2415X_CHARGER_DISABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+                               1, BQ2415X_BIT_CE);
+       case BQ2415X_HIGH_IMPEDANCE_STATUS:
+               return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+                               BQ2415X_BIT_HZ_MODE);
+       case BQ2415X_HIGH_IMPEDANCE_ENABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+                               1, BQ2415X_BIT_HZ_MODE);
+       case BQ2415X_HIGH_IMPEDANCE_DISABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+                               0, BQ2415X_BIT_HZ_MODE);
+       case BQ2415X_BOOST_MODE_STATUS:
+               return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+                               BQ2415X_BIT_OPA_MODE);
+       case BQ2415X_BOOST_MODE_ENABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+                               1, BQ2415X_BIT_OPA_MODE);
+       case BQ2415X_BOOST_MODE_DISABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+                               0, BQ2415X_BIT_OPA_MODE);
+
+       case BQ2415X_OTG_LEVEL:
+               return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE,
+                               BQ2415X_BIT_OTG_PL);
+       case BQ2415X_OTG_ACTIVATE_HIGH:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+                               1, BQ2415X_BIT_OTG_PL);
+       case BQ2415X_OTG_ACTIVATE_LOW:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+                               0, BQ2415X_BIT_OTG_PL);
+       case BQ2415X_OTG_PIN_STATUS:
+               return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE,
+                               BQ2415X_BIT_OTG_EN);
+       case BQ2415X_OTG_PIN_ENABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+                               1, BQ2415X_BIT_OTG_EN);
+       case BQ2415X_OTG_PIN_DISABLE:
+               return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+                               0, BQ2415X_BIT_OTG_EN);
+
+       case BQ2415X_VENDER_CODE:
+               return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
+                       BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER);
+       case BQ2415X_PART_NUMBER:
+               return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
+                               BQ2415X_MASK_PN, BQ2415X_SHIFT_PN);
+       case BQ2415X_REVISION:
+               return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
+                       BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION);
+       }
+       return -EINVAL;
+}
+
+/* detect chip type */
+static enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq)
+{
+       struct i2c_client *client = to_i2c_client(bq->dev);
+       int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER);
+
+       if (ret < 0)
+               return ret;
+
+       switch (client->addr) {
+       case 0x6b:
+               switch (ret) {
+               case 0:
+                       if (bq->chip == BQ24151A)
+                               return bq->chip;
+                       else
+                               return BQ24151;
+               case 1:
+                       if (bq->chip == BQ24150A ||
+                               bq->chip == BQ24152 ||
+                               bq->chip == BQ24155)
+                               return bq->chip;
+                       else
+                               return BQ24150;
+               case 2:
+                       if (bq->chip == BQ24153A)
+                               return bq->chip;
+                       else
+                               return BQ24153;
+               default:
+                       return BQUNKNOWN;
+               }
+               break;
+
+       case 0x6a:
+               switch (ret) {
+               case 0:
+                       if (bq->chip == BQ24156A)
+                               return bq->chip;
+                       else
+                               return BQ24156;
+               case 2:
+                       return BQ24158;
+               default:
+                       return BQUNKNOWN;
+               }
+               break;
+       }
+
+       return BQUNKNOWN;
+}
+
+/* detect chip revision */
+static int bq2415x_detect_revision(struct bq2415x_device *bq)
+{
+       int ret = bq2415x_exec_command(bq, BQ2415X_REVISION);
+       int chip = bq2415x_detect_chip(bq);
+
+       if (ret < 0 || chip < 0)
+               return -1;
+
+       switch (chip) {
+       case BQ24150:
+       case BQ24150A:
+       case BQ24151:
+       case BQ24151A:
+       case BQ24152:
+               if (ret >= 0 && ret <= 3)
+                       return ret;
+               else
+                       return -1;
+       case BQ24153:
+       case BQ24153A:
+       case BQ24156:
+       case BQ24156A:
+       case BQ24158:
+               if (ret == 3)
+                       return 0;
+               else if (ret == 1)
+                       return 1;
+               else
+                       return -1;
+       case BQ24155:
+               if (ret == 3)
+                       return 3;
+               else
+                       return -1;
+       case BQUNKNOWN:
+               return -1;
+       }
+
+       return -1;
+}
+
+/* return chip vender code */
+static int bq2415x_get_vender_code(struct bq2415x_device *bq)
+{
+       int ret;
+
+       ret = bq2415x_exec_command(bq, BQ2415X_VENDER_CODE);
+       if (ret < 0)
+               return 0;
+
+       /* convert to binary */
+       return (ret & 0x1) +
+              ((ret >> 1) & 0x1) * 10 +
+              ((ret >> 2) & 0x1) * 100;
+}
+
+/* reset all chip registers to default state */
+static void bq2415x_reset_chip(struct bq2415x_device *bq)
+{
+       bq2415x_i2c_write(bq, BQ2415X_REG_CURRENT, BQ2415X_RESET_CURRENT);
+       bq2415x_i2c_write(bq, BQ2415X_REG_VOLTAGE, BQ2415X_RESET_VOLTAGE);
+       bq2415x_i2c_write(bq, BQ2415X_REG_CONTROL, BQ2415X_RESET_CONTROL);
+       bq2415x_i2c_write(bq, BQ2415X_REG_STATUS, BQ2415X_RESET_STATUS);
+       bq->timer_error = NULL;
+}
+
+/**** properties functions ****/
+
+/* set current limit in mA */
+static int bq2415x_set_current_limit(struct bq2415x_device *bq, int mA)
+{
+       int val;
+
+       if (mA <= 100)
+               val = 0;
+       else if (mA <= 500)
+               val = 1;
+       else if (mA <= 800)
+               val = 2;
+       else
+               val = 3;
+
+       return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val,
+                       BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+}
+
+/* get current limit in mA */
+static int bq2415x_get_current_limit(struct bq2415x_device *bq)
+{
+       int ret;
+
+       ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL,
+                       BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+       if (ret < 0)
+               return ret;
+       else if (ret == 0)
+               return 100;
+       else if (ret == 1)
+               return 500;
+       else if (ret == 2)
+               return 800;
+       else if (ret == 3)
+               return 1800;
+       return -EINVAL;
+}
+
+/* set weak battery voltage in mV */
+static int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV)
+{
+       int val;
+
+       /* round to 100mV */
+       if (mV <= 3400 + 50)
+               val = 0;
+       else if (mV <= 3500 + 50)
+               val = 1;
+       else if (mV <= 3600 + 50)
+               val = 2;
+       else
+               val = 3;
+
+       return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val,
+                       BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+}
+
+/* get weak battery voltage in mV */
+static int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq)
+{
+       int ret;
+
+       ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL,
+                       BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+       if (ret < 0)
+               return ret;
+       return 100 * (34 + ret);
+}
+
+/* set battery regulation voltage in mV */
+static int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq,
+                                                 int mV)
+{
+       int val = (mV/10 - 350) / 2;
+
+       if (val < 0)
+               val = 0;
+       else if (val > 94) /* FIXME: Max is 94 or 122 ? Set max value ? */
+               return -EINVAL;
+
+       return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val,
+                       BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+}
+
+/* get battery regulation voltage in mV */
+static int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq)
+{
+       int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE,
+                       BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+
+       if (ret < 0)
+               return ret;
+       return 10 * (350 + 2*ret);
+}
+
+/* set charge current in mA (platform data must provide resistor sense) */
+static int bq2415x_set_charge_current(struct bq2415x_device *bq, int mA)
+{
+       int val;
+
+       if (bq->init_data.resistor_sense <= 0)
+               return -ENOSYS;
+
+       val = (mA * bq->init_data.resistor_sense - 37400) / 6800;
+       if (val < 0)
+               val = 0;
+       else if (val > 7)
+               val = 7;
+
+       return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val,
+                       BQ2415X_MASK_VI_CHRG | BQ2415X_MASK_RESET,
+                       BQ2415X_SHIFT_VI_CHRG);
+}
+
+/* get charge current in mA (platform data must provide resistor sense) */
+static int bq2415x_get_charge_current(struct bq2415x_device *bq)
+{
+       int ret;
+
+       if (bq->init_data.resistor_sense <= 0)
+               return -ENOSYS;
+
+       ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
+                       BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG);
+       if (ret < 0)
+               return ret;
+       return (37400 + 6800*ret) / bq->init_data.resistor_sense;
+}
+
+/* set termination current in mA (platform data must provide resistor sense) */
+static int bq2415x_set_termination_current(struct bq2415x_device *bq, int mA)
+{
+       int val;
+
+       if (bq->init_data.resistor_sense <= 0)
+               return -ENOSYS;
+
+       val = (mA * bq->init_data.resistor_sense - 3400) / 3400;
+       if (val < 0)
+               val = 0;
+       else if (val > 7)
+               val = 7;
+
+       return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val,
+                       BQ2415X_MASK_VI_TERM | BQ2415X_MASK_RESET,
+                       BQ2415X_SHIFT_VI_TERM);
+}
+
+/* get termination current in mA (platform data must provide resistor sense) */
+static int bq2415x_get_termination_current(struct bq2415x_device *bq)
+{
+       int ret;
+
+       if (bq->init_data.resistor_sense <= 0)
+               return -ENOSYS;
+
+       ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
+                       BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM);
+       if (ret < 0)
+               return ret;
+       return (3400 + 3400*ret) / bq->init_data.resistor_sense;
+}
+
+/* set default value of property */
+#define bq2415x_set_default_value(bq, prop) \
+       do { \
+               int ret = 0; \
+               if (bq->init_data.prop != -1) \
+                       ret = bq2415x_set_##prop(bq, bq->init_data.prop); \
+               if (ret < 0) \
+                       return ret; \
+       } while (0)
+
+/* set default values of all properties */
+static int bq2415x_set_defaults(struct bq2415x_device *bq)
+{
+       bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
+       bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
+       bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_DISABLE);
+
+       bq2415x_set_default_value(bq, current_limit);
+       bq2415x_set_default_value(bq, weak_battery_voltage);
+       bq2415x_set_default_value(bq, battery_regulation_voltage);
+
+       if (bq->init_data.resistor_sense > 0) {
+               bq2415x_set_default_value(bq, charge_current);
+               bq2415x_set_default_value(bq, termination_current);
+               bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_ENABLE);
+       }
+
+       bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
+       return 0;
+}
+
+/**** charger mode functions ****/
+
+/* set charger mode */
+static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
+{
+       int ret = 0;
+       int charger = 0;
+       int boost = 0;
+
+       if (mode == BQ2415X_MODE_HOST_CHARGER ||
+               mode == BQ2415X_MODE_DEDICATED_CHARGER)
+                       charger = 1;
+
+       if (mode == BQ2415X_MODE_BOOST)
+               boost = 1;
+
+       if (!charger)
+               ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
+
+       if (!boost)
+               ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
+
+       if (ret < 0)
+               return ret;
+
+       switch (mode) {
+       case BQ2415X_MODE_NONE:
+               dev_dbg(bq->dev, "changing mode to: N/A\n");
+               ret = bq2415x_set_current_limit(bq, 100);
+               break;
+       case BQ2415X_MODE_HOST_CHARGER:
+               dev_dbg(bq->dev, "changing mode to: Host/HUB charger\n");
+               ret = bq2415x_set_current_limit(bq, 500);
+               break;
+       case BQ2415X_MODE_DEDICATED_CHARGER:
+               dev_dbg(bq->dev, "changing mode to: Dedicated charger\n");
+               ret = bq2415x_set_current_limit(bq, 1800);
+               break;
+       case BQ2415X_MODE_BOOST: /* Boost mode */
+               dev_dbg(bq->dev, "changing mode to: Boost\n");
+               ret = bq2415x_set_current_limit(bq, 100);
+               break;
+       }
+
+       if (ret < 0)
+               return ret;
+
+       if (charger)
+               ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
+       else if (boost)
+               ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_ENABLE);
+
+       if (ret < 0)
+               return ret;
+
+       bq2415x_set_default_value(bq, weak_battery_voltage);
+       bq2415x_set_default_value(bq, battery_regulation_voltage);
+
+       bq->mode = mode;
+       sysfs_notify(&bq->charger.dev->kobj, NULL, "mode");
+
+       return 0;
+
+}
+
+/* hook function called by other driver which set reported mode */
+static void bq2415x_hook_function(enum bq2415x_mode mode, void *data)
+{
+       struct bq2415x_device *bq = data;
+
+       if (!bq)
+               return;
+
+       dev_dbg(bq->dev, "hook function was called\n");
+       bq->reported_mode = mode;
+
+       /* if automode is not enabled do not tell about reported_mode */
+       if (bq->automode < 1)
+               return;
+
+       sysfs_notify(&bq->charger.dev->kobj, NULL, "reported_mode");
+       bq2415x_set_mode(bq, bq->reported_mode);
+
+}
+
+/**** timer functions ****/
+
+/* enable/disable auto resetting chip timer */
+static void bq2415x_set_autotimer(struct bq2415x_device *bq, int state)
+{
+       mutex_lock(&bq2415x_timer_mutex);
+
+       if (bq->autotimer == state) {
+               mutex_unlock(&bq2415x_timer_mutex);
+               return;
+       }
+
+       bq->autotimer = state;
+
+       if (state) {
+               schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ);
+               bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+               bq->timer_error = NULL;
+       } else {
+               cancel_delayed_work_sync(&bq->work);
+       }
+
+       mutex_unlock(&bq2415x_timer_mutex);
+}
+
+/* called by bq2415x_timer_work on timer error */
+static void bq2415x_timer_error(struct bq2415x_device *bq, const char *msg)
+{
+       bq->timer_error = msg;
+       sysfs_notify(&bq->charger.dev->kobj, NULL, "timer");
+       dev_err(bq->dev, "%s\n", msg);
+       if (bq->automode > 0)
+               bq->automode = 0;
+       bq2415x_set_mode(bq, BQ2415X_MODE_NONE);
+       bq2415x_set_autotimer(bq, 0);
+}
+
+/* delayed work function for auto resetting chip timer */
+static void bq2415x_timer_work(struct work_struct *work)
+{
+       struct bq2415x_device *bq = container_of(work, struct bq2415x_device,
+                                                work.work);
+       int ret;
+       int error;
+       int boost;
+
+       if (!bq->autotimer)
+               return;
+
+       ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+       if (ret < 0) {
+               bq2415x_timer_error(bq, "Resetting timer failed");
+               return;
+       }
+
+       boost = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_STATUS);
+       if (boost < 0) {
+               bq2415x_timer_error(bq, "Unknown error");
+               return;
+       }
+
+       error = bq2415x_exec_command(bq, BQ2415X_FAULT_STATUS);
+       if (error < 0) {
+               bq2415x_timer_error(bq, "Unknown error");
+               return;
+       }
+
+       if (boost) {
+               switch (error) {
+               /* Non fatal errors, chip is OK */
+               case 0: /* No error */
+                       break;
+               case 6: /* Timer expired */
+                       dev_err(bq->dev, "Timer expired\n");
+                       break;
+               case 3: /* Battery voltage too low */
+                       dev_err(bq->dev, "Battery voltage to low\n");
+                       break;
+
+               /* Fatal errors, disable and reset chip */
+               case 1: /* Overvoltage protection (chip fried) */
+                       bq2415x_timer_error(bq,
+                               "Overvoltage protection (chip fried)");
+                       return;
+               case 2: /* Overload */
+                       bq2415x_timer_error(bq, "Overload");
+                       return;
+               case 4: /* Battery overvoltage protection */
+                       bq2415x_timer_error(bq,
+                               "Battery overvoltage protection");
+                       return;
+               case 5: /* Thermal shutdown (too hot) */
+                       bq2415x_timer_error(bq,
+                                       "Thermal shutdown (too hot)");
+                       return;
+               case 7: /* N/A */
+                       bq2415x_timer_error(bq, "Unknown error");
+                       return;
+               }
+       } else {
+               switch (error) {
+               /* Non fatal errors, chip is OK */
+               case 0: /* No error */
+                       break;
+               case 2: /* Sleep mode */
+                       dev_err(bq->dev, "Sleep mode\n");
+                       break;
+               case 3: /* Poor input source */
+                       dev_err(bq->dev, "Poor input source\n");
+                       break;
+               case 6: /* Timer expired */
+                       dev_err(bq->dev, "Timer expired\n");
+                       break;
+               case 7: /* No battery */
+                       dev_err(bq->dev, "No battery\n");
+                       break;
+
+               /* Fatal errors, disable and reset chip */
+               case 1: /* Overvoltage protection (chip fried) */
+                       bq2415x_timer_error(bq,
+                               "Overvoltage protection (chip fried)");
+                       return;
+               case 4: /* Battery overvoltage protection */
+                       bq2415x_timer_error(bq,
+                               "Battery overvoltage protection");
+                       return;
+               case 5: /* Thermal shutdown (too hot) */
+                       bq2415x_timer_error(bq,
+                               "Thermal shutdown (too hot)");
+                       return;
+               }
+       }
+
+       schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ);
+}
+
+/**** power supply interface code ****/
+
+static enum power_supply_property bq2415x_power_supply_props[] = {
+       /* TODO: maybe add more power supply properties */
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int bq2415x_power_supply_get_property(struct power_supply *psy,
+                                            enum power_supply_property psp,
+                                            union power_supply_propval *val)
+{
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+       int ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               ret = bq2415x_exec_command(bq, BQ2415X_CHARGE_STATUS);
+               if (ret < 0)
+                       return ret;
+               else if (ret == 0) /* Ready */
+                       val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               else if (ret == 1) /* Charge in progress */
+                       val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               else if (ret == 2) /* Charge done */
+                       val->intval = POWER_SUPPLY_STATUS_FULL;
+               else
+                       val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+               break;
+       case POWER_SUPPLY_PROP_MODEL_NAME:
+               val->strval = bq->model;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int bq2415x_power_supply_init(struct bq2415x_device *bq)
+{
+       int ret;
+       int chip;
+       char revstr[8];
+
+       bq->charger.name = bq->name;
+       bq->charger.type = POWER_SUPPLY_TYPE_USB;
+       bq->charger.properties = bq2415x_power_supply_props;
+       bq->charger.num_properties = ARRAY_SIZE(bq2415x_power_supply_props);
+       bq->charger.get_property = bq2415x_power_supply_get_property;
+
+       ret = bq2415x_detect_chip(bq);
+       if (ret < 0)
+               chip = BQUNKNOWN;
+       else
+               chip = ret;
+
+       ret = bq2415x_detect_revision(bq);
+       if (ret < 0)
+               strcpy(revstr, "unknown");
+       else
+               sprintf(revstr, "1.%d", ret);
+
+       bq->model = kasprintf(GFP_KERNEL,
+                               "chip %s, revision %s, vender code %.3d",
+                               bq2415x_chip_name[chip], revstr,
+                               bq2415x_get_vender_code(bq));
+       if (!bq->model) {
+               dev_err(bq->dev, "failed to allocate model name\n");
+               return -ENOMEM;
+       }
+
+       ret = power_supply_register(bq->dev, &bq->charger);
+       if (ret) {
+               kfree(bq->model);
+               return ret;
+       }
+
+       return 0;
+}
+
+static void bq2415x_power_supply_exit(struct bq2415x_device *bq)
+{
+       bq->autotimer = 0;
+       if (bq->automode > 0)
+               bq->automode = 0;
+       cancel_delayed_work_sync(&bq->work);
+       power_supply_unregister(&bq->charger);
+       kfree(bq->model);
+}
+
+/**** additional sysfs entries for power supply interface ****/
+
+/* show *_status entries */
+static ssize_t bq2415x_sysfs_show_status(struct device *dev,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                               charger);
+       enum bq2415x_command command;
+       int ret;
+
+       if (strcmp(attr->attr.name, "otg_status") == 0)
+               command = BQ2415X_OTG_STATUS;
+       else if (strcmp(attr->attr.name, "charge_status") == 0)
+               command = BQ2415X_CHARGE_STATUS;
+       else if (strcmp(attr->attr.name, "boost_status") == 0)
+               command = BQ2415X_BOOST_STATUS;
+       else if (strcmp(attr->attr.name, "fault_status") == 0)
+               command = BQ2415X_FAULT_STATUS;
+       else
+               return -EINVAL;
+
+       ret = bq2415x_exec_command(bq, command);
+       if (ret < 0)
+               return ret;
+       return sprintf(buf, "%d\n", ret);
+}
+
+/*
+ * set timer entry:
+ *    auto - enable auto mode
+ *    off - disable auto mode
+ *    (other values) - reset chip timer
+ */
+static ssize_t bq2415x_sysfs_set_timer(struct device *dev,
+                                      struct device_attribute *attr,
+                                      const char *buf,
+                                      size_t count)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                               charger);
+       int ret = 0;
+
+       if (strncmp(buf, "auto", 4) == 0)
+               bq2415x_set_autotimer(bq, 1);
+       else if (strncmp(buf, "off", 3) == 0)
+               bq2415x_set_autotimer(bq, 0);
+       else
+               ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+
+       if (ret < 0)
+               return ret;
+       return count;
+}
+
+/* show timer entry (auto or off) */
+static ssize_t bq2415x_sysfs_show_timer(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+
+       if (bq->timer_error)
+               return sprintf(buf, "%s\n", bq->timer_error);
+
+       if (bq->autotimer)
+               return sprintf(buf, "auto\n");
+       return sprintf(buf, "off\n");
+}
+
+/*
+ * set mode entry:
+ *    auto - if automode is supported, enable it and set mode to reported
+ *    none - disable charger and boost mode
+ *    host - charging mode for host/hub chargers (current limit 500mA)
+ *    dedicated - charging mode for dedicated chargers (unlimited current limit)
+ *    boost - disable charger and enable boost mode
+ */
+static ssize_t bq2415x_sysfs_set_mode(struct device *dev,
+                                     struct device_attribute *attr,
+                                     const char *buf,
+                                     size_t count)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+       enum bq2415x_mode mode;
+       int ret = 0;
+
+       if (strncmp(buf, "auto", 4) == 0) {
+               if (bq->automode < 0)
+                       return -ENOSYS;
+               bq->automode = 1;
+               mode = bq->reported_mode;
+       } else if (strncmp(buf, "none", 4) == 0) {
+               if (bq->automode > 0)
+                       bq->automode = 0;
+               mode = BQ2415X_MODE_NONE;
+       } else if (strncmp(buf, "host", 4) == 0) {
+               if (bq->automode > 0)
+                       bq->automode = 0;
+               mode = BQ2415X_MODE_HOST_CHARGER;
+       } else if (strncmp(buf, "dedicated", 9) == 0) {
+               if (bq->automode > 0)
+                       bq->automode = 0;
+               mode = BQ2415X_MODE_DEDICATED_CHARGER;
+       } else if (strncmp(buf, "boost", 5) == 0) {
+               if (bq->automode > 0)
+                       bq->automode = 0;
+               mode = BQ2415X_MODE_BOOST;
+       } else if (strncmp(buf, "reset", 5) == 0) {
+               bq2415x_reset_chip(bq);
+               bq2415x_set_defaults(bq);
+               if (bq->automode <= 0)
+                       return count;
+               bq->automode = 1;
+               mode = bq->reported_mode;
+       } else {
+               return -EINVAL;
+       }
+
+       ret = bq2415x_set_mode(bq, mode);
+       if (ret < 0)
+               return ret;
+       return count;
+}
+
+/* show mode entry (auto, none, host, dedicated or boost) */
+static ssize_t bq2415x_sysfs_show_mode(struct device *dev,
+                                      struct device_attribute *attr,
+                                      char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                               charger);
+       ssize_t ret = 0;
+
+       if (bq->automode > 0)
+               ret += sprintf(buf+ret, "auto (");
+
+       switch (bq->mode) {
+       case BQ2415X_MODE_NONE:
+               ret += sprintf(buf+ret, "none");
+               break;
+       case BQ2415X_MODE_HOST_CHARGER:
+               ret += sprintf(buf+ret, "host");
+               break;
+       case BQ2415X_MODE_DEDICATED_CHARGER:
+               ret += sprintf(buf+ret, "dedicated");
+               break;
+       case BQ2415X_MODE_BOOST:
+               ret += sprintf(buf+ret, "boost");
+               break;
+       }
+
+       if (bq->automode > 0)
+               ret += sprintf(buf+ret, ")");
+
+       ret += sprintf(buf+ret, "\n");
+       return ret;
+}
+
+/* show reported_mode entry (none, host, dedicated or boost) */
+static ssize_t bq2415x_sysfs_show_reported_mode(struct device *dev,
+                                               struct device_attribute *attr,
+                                               char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+
+       if (bq->automode < 0)
+               return -EINVAL;
+
+       switch (bq->reported_mode) {
+       case BQ2415X_MODE_NONE:
+               return sprintf(buf, "none\n");
+       case BQ2415X_MODE_HOST_CHARGER:
+               return sprintf(buf, "host\n");
+       case BQ2415X_MODE_DEDICATED_CHARGER:
+               return sprintf(buf, "dedicated\n");
+       case BQ2415X_MODE_BOOST:
+               return sprintf(buf, "boost\n");
+       }
+
+       return -EINVAL;
+}
+
+/* directly set raw value to chip register, format: 'register value' */
+static ssize_t bq2415x_sysfs_set_registers(struct device *dev,
+                                          struct device_attribute *attr,
+                                          const char *buf,
+                                          size_t count)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+       ssize_t ret = 0;
+       unsigned int reg;
+       unsigned int val;
+
+       if (sscanf(buf, "%x %x", &reg, &val) != 2)
+               return -EINVAL;
+
+       if (reg > 4 || val > 255)
+               return -EINVAL;
+
+       ret = bq2415x_i2c_write(bq, reg, val);
+       if (ret < 0)
+               return ret;
+       return count;
+}
+
+/* print value of chip register, format: 'register=value' */
+static ssize_t bq2415x_sysfs_print_reg(struct bq2415x_device *bq,
+                                      u8 reg,
+                                      char *buf)
+{
+       int ret = bq2415x_i2c_read(bq, reg);
+
+       if (ret < 0)
+               return sprintf(buf, "%#.2x=error %d\n", reg, ret);
+       return sprintf(buf, "%#.2x=%#.2x\n", reg, ret);
+}
+
+/* show all raw values of chip register, format per line: 'register=value' */
+static ssize_t bq2415x_sysfs_show_registers(struct device *dev,
+                                           struct device_attribute *attr,
+                                           char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+       ssize_t ret = 0;
+
+       ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_STATUS, buf+ret);
+       ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CONTROL, buf+ret);
+       ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VOLTAGE, buf+ret);
+       ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VENDER, buf+ret);
+       ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CURRENT, buf+ret);
+       return ret;
+}
+
+/* set current and voltage limit entries (in mA or mV) */
+static ssize_t bq2415x_sysfs_set_limit(struct device *dev,
+                                      struct device_attribute *attr,
+                                      const char *buf,
+                                      size_t count)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+       long val;
+       int ret;
+
+       if (kstrtol(buf, 10, &val) < 0)
+               return -EINVAL;
+
+       if (strcmp(attr->attr.name, "current_limit") == 0)
+               ret = bq2415x_set_current_limit(bq, val);
+       else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
+               ret = bq2415x_set_weak_battery_voltage(bq, val);
+       else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
+               ret = bq2415x_set_battery_regulation_voltage(bq, val);
+       else if (strcmp(attr->attr.name, "charge_current") == 0)
+               ret = bq2415x_set_charge_current(bq, val);
+       else if (strcmp(attr->attr.name, "termination_current") == 0)
+               ret = bq2415x_set_termination_current(bq, val);
+       else
+               return -EINVAL;
+
+       if (ret < 0)
+               return ret;
+       return count;
+}
+
+/* show current and voltage limit entries (in mA or mV) */
+static ssize_t bq2415x_sysfs_show_limit(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+       int ret;
+
+       if (strcmp(attr->attr.name, "current_limit") == 0)
+               ret = bq2415x_get_current_limit(bq);
+       else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
+               ret = bq2415x_get_weak_battery_voltage(bq);
+       else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
+               ret = bq2415x_get_battery_regulation_voltage(bq);
+       else if (strcmp(attr->attr.name, "charge_current") == 0)
+               ret = bq2415x_get_charge_current(bq);
+       else if (strcmp(attr->attr.name, "termination_current") == 0)
+               ret = bq2415x_get_termination_current(bq);
+       else
+               return -EINVAL;
+
+       if (ret < 0)
+               return ret;
+       return sprintf(buf, "%d\n", ret);
+}
+
+/* set *_enable entries */
+static ssize_t bq2415x_sysfs_set_enable(struct device *dev,
+                                       struct device_attribute *attr,
+                                       const char *buf,
+                                       size_t count)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+       enum bq2415x_command command;
+       long val;
+       int ret;
+
+       if (kstrtol(buf, 10, &val) < 0)
+               return -EINVAL;
+
+       if (strcmp(attr->attr.name, "charge_termination_enable") == 0)
+               command = val ? BQ2415X_CHARGE_TERMINATION_ENABLE :
+                       BQ2415X_CHARGE_TERMINATION_DISABLE;
+       else if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+               command = val ? BQ2415X_HIGH_IMPEDANCE_ENABLE :
+                       BQ2415X_HIGH_IMPEDANCE_DISABLE;
+       else if (strcmp(attr->attr.name, "otg_pin_enable") == 0)
+               command = val ? BQ2415X_OTG_PIN_ENABLE :
+                       BQ2415X_OTG_PIN_DISABLE;
+       else if (strcmp(attr->attr.name, "stat_pin_enable") == 0)
+               command = val ? BQ2415X_STAT_PIN_ENABLE :
+                       BQ2415X_STAT_PIN_DISABLE;
+       else
+               return -EINVAL;
+
+       ret = bq2415x_exec_command(bq, command);
+       if (ret < 0)
+               return ret;
+       return count;
+}
+
+/* show *_enable entries */
+static ssize_t bq2415x_sysfs_show_enable(struct device *dev,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq2415x_device *bq = container_of(psy, struct bq2415x_device,
+                                                charger);
+       enum bq2415x_command command;
+       int ret;
+
+       if (strcmp(attr->attr.name, "charge_termination_enable") == 0)
+               command = BQ2415X_CHARGE_TERMINATION_STATUS;
+       else if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+               command = BQ2415X_HIGH_IMPEDANCE_STATUS;
+       else if (strcmp(attr->attr.name, "otg_pin_enable") == 0)
+               command = BQ2415X_OTG_PIN_STATUS;
+       else if (strcmp(attr->attr.name, "stat_pin_enable") == 0)
+               command = BQ2415X_STAT_PIN_STATUS;
+       else
+               return -EINVAL;
+
+       ret = bq2415x_exec_command(bq, command);
+       if (ret < 0)
+               return ret;
+       return sprintf(buf, "%d\n", ret);
+}
+
+static DEVICE_ATTR(current_limit, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(weak_battery_voltage, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(battery_regulation_voltage, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(charge_current, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(termination_current, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+
+static DEVICE_ATTR(charge_termination_enable, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(otg_pin_enable, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(stat_pin_enable, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+
+static DEVICE_ATTR(reported_mode, S_IRUGO,
+               bq2415x_sysfs_show_reported_mode, NULL);
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_mode, bq2415x_sysfs_set_mode);
+static DEVICE_ATTR(timer, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_timer, bq2415x_sysfs_set_timer);
+
+static DEVICE_ATTR(registers, S_IWUSR | S_IRUGO,
+               bq2415x_sysfs_show_registers, bq2415x_sysfs_set_registers);
+
+static DEVICE_ATTR(otg_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(charge_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(boost_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+static DEVICE_ATTR(fault_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+
+static struct attribute *bq2415x_sysfs_attributes[] = {
+       /*
+        * TODO: some (appropriate) of these attrs should be switched to
+        * use power supply class props.
+        */
+       &dev_attr_current_limit.attr,
+       &dev_attr_weak_battery_voltage.attr,
+       &dev_attr_battery_regulation_voltage.attr,
+       &dev_attr_charge_current.attr,
+       &dev_attr_termination_current.attr,
+
+       &dev_attr_charge_termination_enable.attr,
+       &dev_attr_high_impedance_enable.attr,
+       &dev_attr_otg_pin_enable.attr,
+       &dev_attr_stat_pin_enable.attr,
+
+       &dev_attr_reported_mode.attr,
+       &dev_attr_mode.attr,
+       &dev_attr_timer.attr,
+
+       &dev_attr_registers.attr,
+
+       &dev_attr_otg_status.attr,
+       &dev_attr_charge_status.attr,
+       &dev_attr_boost_status.attr,
+       &dev_attr_fault_status.attr,
+       NULL,
+};
+
+static const struct attribute_group bq2415x_sysfs_attr_group = {
+       .attrs = bq2415x_sysfs_attributes,
+};
+
+static int bq2415x_sysfs_init(struct bq2415x_device *bq)
+{
+       return sysfs_create_group(&bq->charger.dev->kobj,
+                       &bq2415x_sysfs_attr_group);
+}
+
+static void bq2415x_sysfs_exit(struct bq2415x_device *bq)
+{
+       sysfs_remove_group(&bq->charger.dev->kobj, &bq2415x_sysfs_attr_group);
+}
+
+/* main bq2415x probe function */
+static int bq2415x_probe(struct i2c_client *client,
+                        const struct i2c_device_id *id)
+{
+       int ret;
+       int num;
+       char *name;
+       struct bq2415x_device *bq;
+
+       if (!client->dev.platform_data) {
+               dev_err(&client->dev, "platform data not set\n");
+               return -ENODEV;
+       }
+
+       /* Get new ID for the new device */
+       ret = idr_pre_get(&bq2415x_id, GFP_KERNEL);
+       if (ret == 0)
+               return -ENOMEM;
+
+       mutex_lock(&bq2415x_id_mutex);
+       ret = idr_get_new(&bq2415x_id, client, &num);
+       mutex_unlock(&bq2415x_id_mutex);
+
+       if (ret < 0)
+               return ret;
+
+       name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
+       if (!name) {
+               dev_err(&client->dev, "failed to allocate device name\n");
+               ret = -ENOMEM;
+               goto error_1;
+       }
+
+       bq = kzalloc(sizeof(*bq), GFP_KERNEL);
+       if (!bq) {
+               dev_err(&client->dev, "failed to allocate device data\n");
+               ret = -ENOMEM;
+               goto error_2;
+       }
+
+       i2c_set_clientdata(client, bq);
+
+       bq->id = num;
+       bq->dev = &client->dev;
+       bq->chip = id->driver_data;
+       bq->name = name;
+       bq->mode = BQ2415X_MODE_NONE;
+       bq->reported_mode = BQ2415X_MODE_NONE;
+       bq->autotimer = 0;
+       bq->automode = 0;
+
+       memcpy(&bq->init_data, client->dev.platform_data,
+                       sizeof(bq->init_data));
+
+       bq2415x_reset_chip(bq);
+
+       ret = bq2415x_power_supply_init(bq);
+       if (ret) {
+               dev_err(bq->dev, "failed to register power supply: %d\n", ret);
+               goto error_3;
+       }
+
+       ret = bq2415x_sysfs_init(bq);
+       if (ret) {
+               dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret);
+               goto error_4;
+       }
+
+       ret = bq2415x_set_defaults(bq);
+       if (ret) {
+               dev_err(bq->dev, "failed to set default values: %d\n", ret);
+               goto error_5;
+       }
+
+       if (bq->init_data.set_mode_hook) {
+               if (bq->init_data.set_mode_hook(
+                               bq2415x_hook_function, bq)) {
+                       bq->automode = 1;
+                       bq2415x_set_mode(bq, bq->reported_mode);
+                       dev_info(bq->dev, "automode enabled\n");
+               } else {
+                       bq->automode = -1;
+                       dev_info(bq->dev, "automode failed\n");
+               }
+       } else {
+               bq->automode = -1;
+               dev_info(bq->dev, "automode not supported\n");
+       }
+
+       INIT_DELAYED_WORK(&bq->work, bq2415x_timer_work);
+       bq2415x_set_autotimer(bq, 1);
+
+       dev_info(bq->dev, "driver registered\n");
+       return 0;
+
+error_5:
+       bq2415x_sysfs_exit(bq);
+error_4:
+       bq2415x_power_supply_exit(bq);
+error_3:
+       kfree(bq);
+error_2:
+       kfree(name);
+error_1:
+       mutex_lock(&bq2415x_id_mutex);
+       idr_remove(&bq2415x_id, num);
+       mutex_unlock(&bq2415x_id_mutex);
+
+       return ret;
+}
+
+/* main bq2415x remove function */
+
+static int bq2415x_remove(struct i2c_client *client)
+{
+       struct bq2415x_device *bq = i2c_get_clientdata(client);
+
+       if (bq->init_data.set_mode_hook)
+               bq->init_data.set_mode_hook(NULL, NULL);
+
+       bq2415x_sysfs_exit(bq);
+       bq2415x_power_supply_exit(bq);
+
+       bq2415x_reset_chip(bq);
+
+       mutex_lock(&bq2415x_id_mutex);
+       idr_remove(&bq2415x_id, bq->id);
+       mutex_unlock(&bq2415x_id_mutex);
+
+       dev_info(bq->dev, "driver unregistered\n");
+
+       kfree(bq->name);
+       kfree(bq);
+
+       return 0;
+}
+
+static const struct i2c_device_id bq2415x_i2c_id_table[] = {
+       { "bq2415x", BQUNKNOWN },
+       { "bq24150", BQ24150 },
+       { "bq24150a", BQ24150A },
+       { "bq24151", BQ24151 },
+       { "bq24151a", BQ24151A },
+       { "bq24152", BQ24152 },
+       { "bq24153", BQ24153 },
+       { "bq24153a", BQ24153A },
+       { "bq24155", BQ24155 },
+       { "bq24156", BQ24156 },
+       { "bq24156a", BQ24156A },
+       { "bq24158", BQ24158 },
+       {},
+};
+MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table);
+
+static struct i2c_driver bq2415x_driver = {
+       .driver = {
+               .name = "bq2415x-charger",
+       },
+       .probe = bq2415x_probe,
+       .remove = bq2415x_remove,
+       .id_table = bq2415x_i2c_id_table,
+};
+
+static int __init bq2415x_init(void)
+{
+       return i2c_add_driver(&bq2415x_driver);
+}
+module_init(bq2415x_init);
+
+static void __exit bq2415x_exit(void)
+{
+       i2c_del_driver(&bq2415x_driver);
+}
+module_exit(bq2415x_exit);
+
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("bq2415x charger driver");
+MODULE_LICENSE("GPL");
index e0edaf7de54bcb8b8823f899a74b23dccfd038f6..36b34efdafc9fda1d8cc96773031f5b106f005f9 100644 (file)
@@ -230,6 +230,14 @@ static int bq27x00_battery_read_charge(struct bq27x00_device_info *di, u8 reg)
  */
 static inline int bq27x00_battery_read_nac(struct bq27x00_device_info *di)
 {
+       int flags;
+       bool is_bq27500 = di->chip == BQ27500;
+       bool is_higher = bq27xxx_is_chip_version_higher(di);
+
+       flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500);
+       if (flags >= 0 && !is_higher && (flags & BQ27000_FLAG_CI))
+               return -ENODATA;
+
        return bq27x00_battery_read_charge(di, BQ27x00_REG_NAC);
 }
 
index 6bb6e2f5ea814e408c52f23ca78d35f8a22e7444..2fa9b6bf1f3f9e08d9d5512e875ec377902898f9 100644 (file)
@@ -80,13 +80,13 @@ static inline int ds278x_read_reg16(struct ds278x_info *info, int reg_msb,
 {
        int ret;
 
-       ret = swab16(i2c_smbus_read_word_data(info->client, reg_msb));
+       ret = i2c_smbus_read_word_data(info->client, reg_msb);
        if (ret < 0) {
                dev_err(&info->client->dev, "register read failed\n");
                return ret;
        }
 
-       *val = ret;
+       *val = swab16(ret);
        return 0;
 }
 
index e902b088d52cf334429b287ad1b26640ffbd051a..32ce17e235c08ef3644c81573a22ad37417e50b2 100644 (file)
@@ -279,7 +279,8 @@ static int gab_probe(struct platform_device *pdev)
        }
 
        memcpy(psy->properties, gab_props, sizeof(gab_props));
-       properties = psy->properties + sizeof(gab_props);
+       properties = (enum power_supply_property *)
+                               ((char *)psy->properties + sizeof(gab_props));
 
        /*
         * getting channel from iio and copying the battery properties
@@ -327,7 +328,7 @@ static int gab_probe(struct platform_device *pdev)
                ret = request_any_context_irq(irq, gab_charged,
                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                                "battery charged", adc_bat);
-               if (ret)
+               if (ret < 0)
                        goto err_gpio;
        }
 
index 74ac69e0687fdc262791cc006e27b4d4895c3b0d..bf914893c6fd40e2a54423fd3352d6522f039288 100644 (file)
@@ -33,7 +33,6 @@ struct jz_battery {
        struct jz_battery_platform_data *pdata;
        struct platform_device *pdev;
 
-       struct resource *mem;
        void __iomem *base;
 
        int irq;
@@ -244,13 +243,14 @@ static int jz_battery_probe(struct platform_device *pdev)
        struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data;
        struct jz_battery *jz_battery;
        struct power_supply *battery;
+       struct resource *mem;
 
        if (!pdata) {
                dev_err(&pdev->dev, "No platform_data supplied\n");
                return -ENXIO;
        }
 
-       jz_battery = kzalloc(sizeof(*jz_battery), GFP_KERNEL);
+       jz_battery = devm_kzalloc(&pdev->dev, sizeof(*jz_battery), GFP_KERNEL);
        if (!jz_battery) {
                dev_err(&pdev->dev, "Failed to allocate driver structure\n");
                return -ENOMEM;
@@ -260,33 +260,15 @@ static int jz_battery_probe(struct platform_device *pdev)
 
        jz_battery->irq = platform_get_irq(pdev, 0);
        if (jz_battery->irq < 0) {
-               ret = jz_battery->irq;
                dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
-               goto err_free;
+               return jz_battery->irq;
        }
 
-       jz_battery->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       if (!jz_battery->mem) {
-               ret = -ENOENT;
-               dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
-               goto err_free;
-       }
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 
-       jz_battery->mem = request_mem_region(jz_battery->mem->start,
-                               resource_size(jz_battery->mem), pdev->name);
-       if (!jz_battery->mem) {
-               ret = -EBUSY;
-               dev_err(&pdev->dev, "Failed to request mmio memory region\n");
-               goto err_free;
-       }
-
-       jz_battery->base = ioremap_nocache(jz_battery->mem->start,
-                               resource_size(jz_battery->mem));
-       if (!jz_battery->base) {
-               ret = -EBUSY;
-               dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
-               goto err_release_mem_region;
-       }
+       jz_battery->base = devm_request_and_ioremap(&pdev->dev, mem);
+       if (!jz_battery->base)
+               return -EBUSY;
 
        battery = &jz_battery->battery;
        battery->name = pdata->info.name;
@@ -309,7 +291,7 @@ static int jz_battery_probe(struct platform_device *pdev)
                        jz_battery);
        if (ret) {
                dev_err(&pdev->dev, "Failed to request irq %d\n", ret);
-               goto err_iounmap;
+               goto err;
        }
        disable_irq(jz_battery->irq);
 
@@ -366,13 +348,8 @@ err_free_gpio:
                gpio_free(jz_battery->pdata->gpio_charge);
 err_free_irq:
        free_irq(jz_battery->irq, jz_battery);
-err_iounmap:
+err:
        platform_set_drvdata(pdev, NULL);
-       iounmap(jz_battery->base);
-err_release_mem_region:
-       release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem));
-err_free:
-       kfree(jz_battery);
        return ret;
 }
 
@@ -392,10 +369,6 @@ static int jz_battery_remove(struct platform_device *pdev)
 
        free_irq(jz_battery->irq, jz_battery);
 
-       iounmap(jz_battery->base);
-       release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem));
-       kfree(jz_battery);
-
        return 0;
 }
 
index a1c51ac117fd2a7cf319449296ff7d6eb9b1428c..22b6407c9ca9949d65b0c719e895549e8bb3e902 100644 (file)
@@ -235,25 +235,14 @@ static int lp8788_get_battery_present(struct lp8788_charger *pchg,
        return 0;
 }
 
-static int lp8788_get_vbatt_adc(struct lp8788_charger *pchg,
-                               unsigned int *result)
+static int lp8788_get_vbatt_adc(struct lp8788_charger *pchg, int *result)
 {
        struct iio_channel *channel = pchg->chan[LP8788_VBATT];
-       int scaleint;
-       int scalepart;
-       int ret;
 
        if (!channel)
                return -EINVAL;
 
-       ret = iio_read_channel_scale(channel, &scaleint, &scalepart);
-       if (ret != IIO_VAL_INT_PLUS_MICRO)
-               return -EINVAL;
-
-       /* unit: mV */
-       *result = (scaleint + scalepart * 1000000) / 1000;
-
-       return 0;
+       return iio_read_channel_processed(channel, result);
 }
 
 static int lp8788_get_battery_voltage(struct lp8788_charger *pchg,
@@ -268,7 +257,7 @@ static int lp8788_get_battery_capacity(struct lp8788_charger *pchg,
        struct lp8788 *lp = pchg->lp;
        struct lp8788_charger_platform_data *pdata = pchg->pdata;
        unsigned int max_vbatt;
-       unsigned int vbatt;
+       int vbatt;
        enum lp8788_charging_state state;
        u8 data;
        int ret;
@@ -304,19 +293,18 @@ static int lp8788_get_battery_temperature(struct lp8788_charger *pchg,
                                union power_supply_propval *val)
 {
        struct iio_channel *channel = pchg->chan[LP8788_BATT_TEMP];
-       int scaleint;
-       int scalepart;
+       int result;
        int ret;
 
        if (!channel)
                return -EINVAL;
 
-       ret = iio_read_channel_scale(channel, &scaleint, &scalepart);
-       if (ret != IIO_VAL_INT_PLUS_MICRO)
+       ret = iio_read_channel_processed(channel, &result);
+       if (ret < 0)
                return -EINVAL;
 
        /* unit: 0.1 'C */
-       val->intval = (scaleint + scalepart * 1000000) / 100;
+       val->intval = result * 10;
 
        return 0;
 }
@@ -592,53 +580,22 @@ static void lp8788_irq_unregister(struct platform_device *pdev,
        }
 }
 
-static void lp8788_setup_adc_channel(struct lp8788_charger *pchg)
+static void lp8788_setup_adc_channel(const char *consumer_name,
+                               struct lp8788_charger *pchg)
 {
        struct lp8788_charger_platform_data *pdata = pchg->pdata;
-       struct device *dev = pchg->lp->dev;
        struct iio_channel *chan;
-       enum lp8788_adc_id id;
-       const char *chan_name[LPADC_MAX] = {
-               [LPADC_VBATT_5P5] = "vbatt-5p5",
-               [LPADC_VBATT_6P0] = "vbatt-6p0",
-               [LPADC_VBATT_5P0] = "vbatt-5p0",
-               [LPADC_ADC1]      = "adc1",
-               [LPADC_ADC2]      = "adc2",
-               [LPADC_ADC3]      = "adc3",
-               [LPADC_ADC4]      = "adc4",
-       };
 
        if (!pdata)
                return;
 
-       id = pdata->vbatt_adc;
-       switch (id) {
-       case LPADC_VBATT_5P5:
-       case LPADC_VBATT_6P0:
-       case LPADC_VBATT_5P0:
-               chan = iio_channel_get(NULL, chan_name[id]);
-               pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan;
-               break;
-       default:
-               dev_err(dev, "invalid ADC id for VBATT: %d\n", id);
-               pchg->chan[LP8788_VBATT] = NULL;
-               break;
-       }
+       /* ADC channel for battery voltage */
+       chan = iio_channel_get(consumer_name, pdata->adc_vbatt);
+       pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan;
 
-       id = pdata->batt_temp_adc;
-       switch (id) {
-       case LPADC_ADC1:
-       case LPADC_ADC2:
-       case LPADC_ADC3:
-       case LPADC_ADC4:
-               chan = iio_channel_get(NULL, chan_name[id]);
-               pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan;
-               break;
-       default:
-               dev_err(dev, "invalid ADC id for BATT_TEMP : %d\n", id);
-               pchg->chan[LP8788_BATT_TEMP] = NULL;
-               break;
-       }
+       /* ADC channel for battery temperature */
+       chan = iio_channel_get(consumer_name, pdata->adc_batt_temp);
+       pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan;
 }
 
 static void lp8788_release_adc_channel(struct lp8788_charger *pchg)
@@ -747,7 +704,7 @@ static int lp8788_charger_probe(struct platform_device *pdev)
        if (ret)
                return ret;
 
-       lp8788_setup_adc_channel(pchg);
+       lp8788_setup_adc_channel(pdev->name, pchg);
 
        ret = lp8788_psy_register(pdev, pchg);
        if (ret)
index 5ffe46916f0be4a445f63a257dc0b86383177c32..d664ef58afa7d0ffd34d03e89444cd015f31ab7d 100644 (file)
@@ -572,7 +572,8 @@ static int max17042_init_chip(struct max17042_chip *chip)
                        __func__);
                return -EIO;
        }
-       max17042_verify_model_lock(chip);
+
+       ret = max17042_verify_model_lock(chip);
        if (ret) {
                dev_err(&chip->client->dev, "%s lock verify failed\n",
                        __func__);
index 1a075f1f1b676c9cd7c2f16666852a46cf4629f1..665cdc76c26502e7a207ac8cbfc1dbfb90be5864 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/module.h>
 #include <linux/err.h>
 #include <linux/slab.h>
+#include <linux/of.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
 #include <linux/platform_device.h>
@@ -426,6 +427,54 @@ static int max8925_deinit_charger(struct max8925_power_info *info)
        return 0;
 }
 
+#ifdef CONFIG_OF
+static struct max8925_power_pdata *
+max8925_power_dt_init(struct platform_device *pdev)
+{
+       struct device_node *nproot = pdev->dev.parent->of_node;
+       struct device_node *np;
+       int batt_detect;
+       int topoff_threshold;
+       int fast_charge;
+       int no_temp_support;
+       int no_insert_detect;
+       struct max8925_power_pdata *pdata;
+
+       if (!nproot)
+               return pdev->dev.platform_data;
+
+       np = of_find_node_by_name(nproot, "charger");
+       if (!np) {
+               dev_err(&pdev->dev, "failed to find charger node\n");
+               return NULL;
+       }
+
+       pdata = devm_kzalloc(&pdev->dev,
+                       sizeof(struct max8925_power_pdata),
+                       GFP_KERNEL);
+
+       of_property_read_u32(np, "topoff-threshold", &topoff_threshold);
+       of_property_read_u32(np, "batt-detect", &batt_detect);
+       of_property_read_u32(np, "fast-charge", &fast_charge);
+       of_property_read_u32(np, "no-insert-detect", &no_insert_detect);
+       of_property_read_u32(np, "no-temp-support", &no_temp_support);
+
+       pdata->batt_detect = batt_detect;
+       pdata->fast_charge = fast_charge;
+       pdata->topoff_threshold = topoff_threshold;
+       pdata->no_insert_detect = no_insert_detect;
+       pdata->no_temp_support = no_temp_support;
+
+       return pdata;
+}
+#else
+static struct max8925_power_pdata *
+max8925_power_dt_init(struct platform_device *pdev)
+{
+       return pdev->dev.platform_data;
+}
+#endif
+
 static int max8925_power_probe(struct platform_device *pdev)
 {
        struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
@@ -433,7 +482,7 @@ static int max8925_power_probe(struct platform_device *pdev)
        struct max8925_power_info *info;
        int ret;
 
-       pdata = pdev->dev.platform_data;
+       pdata = max8925_power_dt_init(pdev);
        if (!pdata) {
                dev_err(&pdev->dev, "platform data isn't assigned to "
                        "power supply\n");
index f77a41272e5d6f3e5f9fecf1b40b7a4a5e1bb459..8a7cfb3cc16694e7fdd4881d9bede20b7aaf4099 100644 (file)
@@ -216,6 +216,86 @@ static void psy_unregister_thermal(struct power_supply *psy)
                return;
        thermal_zone_device_unregister(psy->tzd);
 }
+
+/* thermal cooling device callbacks */
+static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd,
+                                       unsigned long *state)
+{
+       struct power_supply *psy;
+       union power_supply_propval val;
+       int ret;
+
+       psy = tcd->devdata;
+       ret = psy->get_property(psy,
+               POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, &val);
+       if (!ret)
+               *state = val.intval;
+
+       return ret;
+}
+
+static int ps_get_cur_chrage_cntl_limit(struct thermal_cooling_device *tcd,
+                                       unsigned long *state)
+{
+       struct power_supply *psy;
+       union power_supply_propval val;
+       int ret;
+
+       psy = tcd->devdata;
+       ret = psy->get_property(psy,
+               POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val);
+       if (!ret)
+               *state = val.intval;
+
+       return ret;
+}
+
+static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd,
+                                       unsigned long state)
+{
+       struct power_supply *psy;
+       union power_supply_propval val;
+       int ret;
+
+       psy = tcd->devdata;
+       val.intval = state;
+       ret = psy->set_property(psy,
+               POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val);
+
+       return ret;
+}
+
+static struct thermal_cooling_device_ops psy_tcd_ops = {
+       .get_max_state = ps_get_max_charge_cntl_limit,
+       .get_cur_state = ps_get_cur_chrage_cntl_limit,
+       .set_cur_state = ps_set_cur_charge_cntl_limit,
+};
+
+static int psy_register_cooler(struct power_supply *psy)
+{
+       int i;
+
+       /* Register for cooling device if psy can control charging */
+       for (i = 0; i < psy->num_properties; i++) {
+               if (psy->properties[i] ==
+                               POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT) {
+                       psy->tcd = thermal_cooling_device_register(
+                                                       (char *)psy->name,
+                                                       psy, &psy_tcd_ops);
+                       if (IS_ERR(psy->tcd))
+                               return PTR_ERR(psy->tcd);
+                       break;
+               }
+       }
+       return 0;
+}
+
+static void psy_unregister_cooler(struct power_supply *psy)
+{
+       if (IS_ERR_OR_NULL(psy->tcd))
+               return;
+       thermal_cooling_device_unregister(psy->tcd);
+}
 #else
 static int psy_register_thermal(struct power_supply *psy)
 {
@@ -225,6 +305,15 @@ static int psy_register_thermal(struct power_supply *psy)
 static void psy_unregister_thermal(struct power_supply *psy)
 {
 }
+
+static int psy_register_cooler(struct power_supply *psy)
+{
+       return 0;
+}
+
+static void psy_unregister_cooler(struct power_supply *psy)
+{
+}
 #endif
 
 int power_supply_register(struct device *parent, struct power_supply *psy)
@@ -259,6 +348,10 @@ int power_supply_register(struct device *parent, struct power_supply *psy)
        if (rc)
                goto register_thermal_failed;
 
+       rc = psy_register_cooler(psy);
+       if (rc)
+               goto register_cooler_failed;
+
        rc = power_supply_create_triggers(psy);
        if (rc)
                goto create_triggers_failed;
@@ -268,6 +361,8 @@ int power_supply_register(struct device *parent, struct power_supply *psy)
        goto success;
 
 create_triggers_failed:
+       psy_unregister_cooler(psy);
+register_cooler_failed:
        psy_unregister_thermal(psy);
 register_thermal_failed:
        device_del(dev);
@@ -284,6 +379,7 @@ void power_supply_unregister(struct power_supply *psy)
        cancel_work_sync(&psy->changed_work);
        sysfs_remove_link(&psy->dev->kobj, "powers");
        power_supply_remove_triggers(psy);
+       psy_unregister_cooler(psy);
        psy_unregister_thermal(psy);
        device_unregister(psy->dev);
 }
index 395c2cfa16c0bbd3bb200b31f5d2de343e2cd7e3..40fa3b7cae548033d0d69e358574326e11a8f22c 100644 (file)
@@ -164,6 +164,8 @@ static struct device_attribute power_supply_attrs[] = {
        POWER_SUPPLY_ATTR(constant_charge_current_max),
        POWER_SUPPLY_ATTR(constant_charge_voltage),
        POWER_SUPPLY_ATTR(constant_charge_voltage_max),
+       POWER_SUPPLY_ATTR(charge_control_limit),
+       POWER_SUPPLY_ATTR(charge_control_limit_max),
        POWER_SUPPLY_ATTR(energy_full_design),
        POWER_SUPPLY_ATTR(energy_empty_design),
        POWER_SUPPLY_ATTR(energy_full),
diff --git a/drivers/power/rx51_battery.c b/drivers/power/rx51_battery.c
new file mode 100644 (file)
index 0000000..ca49d6c
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * Nokia RX-51 battery driver
+ *
+ * Copyright (C) 2012  Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/i2c/twl4030-madc.h>
+
+struct rx51_device_info {
+       struct device *dev;
+       struct power_supply bat;
+};
+
+/*
+ * Read ADCIN channel value, code copied from maemo kernel
+ */
+static int rx51_battery_read_adc(int channel)
+{
+       struct twl4030_madc_request req;
+
+       req.channels = 1 << channel;
+       req.do_avg = 1;
+       req.method = TWL4030_MADC_SW1;
+       req.func_cb = NULL;
+       req.type = TWL4030_MADC_WAIT;
+
+       if (twl4030_madc_conversion(&req) <= 0)
+               return -ENODATA;
+
+       return req.rbuf[channel];
+}
+
+/*
+ * Read ADCIN channel 12 (voltage) and convert RAW value to micro voltage
+ * This conversion formula was extracted from maemo program bsi-read
+ */
+static int rx51_battery_read_voltage(struct rx51_device_info *di)
+{
+       int voltage = rx51_battery_read_adc(12);
+
+       if (voltage < 0)
+               return voltage;
+
+       return 1000 * (10000 * voltage / 1705);
+}
+
+/*
+ * Temperature look-up tables
+ * TEMP = (1/(t1 + 1/298) - 273.15)
+ * Where t1 = (1/B) * ln((RAW_ADC_U * 2.5)/(R * I * 255))
+ * Formula is based on experimental data, RX-51 CAL data, maemo program bme
+ * and formula from da9052 driver with values R = 100, B = 3380, I = 0.00671
+ */
+
+/*
+ * Table1 (temperature for first 25 RAW values)
+ * Usage: TEMP = rx51_temp_table1[RAW]
+ *   RAW is between 1 and 24
+ *   TEMP is between 201 C and 55 C
+ */
+static u8 rx51_temp_table1[] = {
+       255, 201, 159, 138, 124, 114, 106,  99,  94,  89,  85,  82,  78,  75,
+        73,  70,  68,  66,  64,  62,  61,  59,  57,  56,  55
+};
+
+/*
+ * Table2 (lowest RAW value for temperature)
+ * Usage: RAW = rx51_temp_table2[TEMP-rx51_temp_table2_first]
+ *   TEMP is between 53 C and -32 C
+ *   RAW is between 25 and 993
+ */
+#define rx51_temp_table2_first 53
+static u16 rx51_temp_table2[] = {
+        25,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  39,
+        40,  41,  43,  44,  46,  48,  49,  51,  53,  55,  57,  59,  61,  64,
+        66,  69,  71,  74,  77,  80,  83,  86,  90,  94,  97, 101, 106, 110,
+       115, 119, 125, 130, 136, 141, 148, 154, 161, 168, 176, 184, 202, 211,
+       221, 231, 242, 254, 266, 279, 293, 308, 323, 340, 357, 375, 395, 415,
+       437, 460, 485, 511, 539, 568, 600, 633, 669, 706, 747, 790, 836, 885,
+       937, 993, 1024
+};
+
+/*
+ * Read ADCIN channel 0 (battery temp) and convert value to tenths of Celsius
+ * Use Temperature look-up tables for conversation
+ */
+static int rx51_battery_read_temperature(struct rx51_device_info *di)
+{
+       int min = 0;
+       int max = ARRAY_SIZE(rx51_temp_table2) - 1;
+       int raw = rx51_battery_read_adc(0);
+
+       /* Zero and negative values are undefined */
+       if (raw <= 0)
+               return INT_MAX;
+
+       /* ADC channels are 10 bit, higher value are undefined */
+       if (raw >= (1 << 10))
+               return INT_MIN;
+
+       /* First check for temperature in first direct table */
+       if (raw < ARRAY_SIZE(rx51_temp_table1))
+               return rx51_temp_table1[raw] * 100;
+
+       /* Binary search RAW value in second inverse table */
+       while (max - min > 1) {
+               int mid = (max + min) / 2;
+               if (rx51_temp_table2[mid] <= raw)
+                       min = mid;
+               else if (rx51_temp_table2[mid] > raw)
+                       max = mid;
+               if (rx51_temp_table2[mid] == raw)
+                       break;
+       }
+
+       return (rx51_temp_table2_first - min) * 100;
+}
+
+/*
+ * Read ADCIN channel 4 (BSI) and convert RAW value to micro Ah
+ * This conversion formula was extracted from maemo program bsi-read
+ */
+static int rx51_battery_read_capacity(struct rx51_device_info *di)
+{
+       int capacity = rx51_battery_read_adc(4);
+
+       if (capacity < 0)
+               return capacity;
+
+       return 1280 * (1200 * capacity)/(1024 - capacity);
+}
+
+/*
+ * Return power_supply property
+ */
+static int rx51_battery_get_property(struct power_supply *psy,
+                                       enum power_supply_property psp,
+                                       union power_supply_propval *val)
+{
+       struct rx51_device_info *di = container_of((psy),
+                               struct rx51_device_info, bat);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_TECHNOLOGY:
+               val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+               val->intval = 4200000;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = rx51_battery_read_voltage(di) ? 1 : 0;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               val->intval = rx51_battery_read_voltage(di);
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+               val->intval = rx51_battery_read_temperature(di);
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+               val->intval = rx51_battery_read_capacity(di);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (val->intval == INT_MAX || val->intval == INT_MIN)
+               return -EINVAL;
+
+       return 0;
+}
+
+static enum power_supply_property rx51_battery_props[] = {
+       POWER_SUPPLY_PROP_TECHNOLOGY,
+       POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_TEMP,
+       POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+};
+
+static int __devinit rx51_battery_probe(struct platform_device *pdev)
+{
+       struct rx51_device_info *di;
+       int ret;
+
+       di = kzalloc(sizeof(*di), GFP_KERNEL);
+       if (!di)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, di);
+
+       di->bat.name = dev_name(&pdev->dev);
+       di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+       di->bat.properties = rx51_battery_props;
+       di->bat.num_properties = ARRAY_SIZE(rx51_battery_props);
+       di->bat.get_property = rx51_battery_get_property;
+
+       ret = power_supply_register(di->dev, &di->bat);
+       if (ret) {
+               platform_set_drvdata(pdev, NULL);
+               kfree(di);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int __devexit rx51_battery_remove(struct platform_device *pdev)
+{
+       struct rx51_device_info *di = platform_get_drvdata(pdev);
+
+       power_supply_unregister(&di->bat);
+       platform_set_drvdata(pdev, NULL);
+       kfree(di);
+
+       return 0;
+}
+
+static struct platform_driver rx51_battery_driver = {
+       .probe = rx51_battery_probe,
+       .remove = __devexit_p(rx51_battery_remove),
+       .driver = {
+               .name = "rx51-battery",
+               .owner = THIS_MODULE,
+       },
+};
+module_platform_driver(rx51_battery_driver);
+
+MODULE_ALIAS("platform:rx51-battery");
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("Nokia RX-51 battery driver");
+MODULE_LICENSE("GPL");
index f9e70cf08199121b0a9e8b5d4297cf1951106b93..a69d0d11b54006b35d3ad3c04a9c53f3456d842f 100644 (file)
@@ -114,12 +114,12 @@ static int twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg)
 
 static int twl4030_bci_read(u8 reg, u8 *val)
 {
-       return twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, val, reg);
+       return twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, val, reg);
 }
 
 static int twl4030_clear_set_boot_bci(u8 clear, u8 set)
 {
-       return twl4030_clear_set(TWL4030_MODULE_PM_MASTER, clear,
+       return twl4030_clear_set(TWL_MODULE_PM_MASTER, clear,
                        TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set,
                        TWL4030_PM_MASTER_BOOT_BCI);
 }
@@ -152,7 +152,7 @@ static int twl4030_bci_have_vbus(struct twl4030_bci *bci)
        int ret;
        u8 hwsts;
 
-       ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts,
+       ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &hwsts,
                              TWL4030_PM_MASTER_STS_HW_CONDITIONS);
        if (ret < 0)
                return 0;
@@ -199,7 +199,7 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
                        return ret;
 
                /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */
-               ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, 0,
+               ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0,
                        TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4);
        } else {
                ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0);
@@ -238,7 +238,7 @@ static int twl4030_charger_enable_backup(int uvolt, int uamp)
        if (uvolt < 2500000 ||
            uamp < 25) {
                /* disable charging of backup battery */
-               ret = twl4030_clear_set(TWL4030_MODULE_PM_RECEIVER,
+               ret = twl4030_clear_set(TWL_MODULE_PM_RECEIVER,
                                        TWL4030_BBCHEN, 0, TWL4030_BB_CFG);
                return ret;
        }
@@ -262,7 +262,7 @@ static int twl4030_charger_enable_backup(int uvolt, int uamp)
        else
                flags |= TWL4030_BBISEL_25uA;
 
-       ret = twl4030_clear_set(TWL4030_MODULE_PM_RECEIVER,
+       ret = twl4030_clear_set(TWL_MODULE_PM_RECEIVER,
                                TWL4030_BBSEL_MASK | TWL4030_BBISEL_MASK,
                                flags,
                                TWL4030_BB_CFG);
index 5d5298d56026e6a8681769c68b0f4927b2f60930..2138bd33021a629f59b868d5a3938aefa4f87449 100644 (file)
@@ -267,39 +267,21 @@ struct abx500_bm_data {
        int gnd_lift_resistance;
        const struct abx500_maxim_parameters *maxi;
        const struct abx500_bm_capacity_levels *cap_levels;
-       const struct abx500_battery_type *bat_type;
+       struct abx500_battery_type *bat_type;
        const struct abx500_bm_charger_parameters *chg_params;
        const struct abx500_fg_parameters *fg_params;
 };
 
-struct abx500_chargalg_platform_data {
-       char **supplied_to;
-       size_t num_supplicants;
-};
-
-struct abx500_charger_platform_data {
-       char **supplied_to;
-       size_t num_supplicants;
-       bool autopower_cfg;
-};
+extern struct abx500_bm_data ab8500_bm_data;
 
-struct abx500_btemp_platform_data {
-       char **supplied_to;
-       size_t num_supplicants;
+enum {
+       NTC_EXTERNAL = 0,
+       NTC_INTERNAL,
 };
 
-struct abx500_fg_platform_data {
-       char **supplied_to;
-       size_t num_supplicants;
-};
-
-struct abx500_bm_plat_data {
-       struct abx500_bm_data *battery;
-       struct abx500_charger_platform_data *charger;
-       struct abx500_btemp_platform_data *btemp;
-       struct abx500_fg_platform_data *fg;
-       struct abx500_chargalg_platform_data *chargalg;
-};
+int bmdevs_of_probe(struct device *dev,
+               struct device_node *np,
+               struct abx500_bm_data **battery);
 
 int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg,
        u8 value);
index cec364bdccfaa4aae6cee96b086aea857bc8227d..2a32b16f79cb88c2e3649f190508b7abb980818d 100644 (file)
@@ -211,16 +211,16 @@ struct lp8788_chg_param {
 
 /*
  * struct lp8788_charger_platform_data
- * @vbatt_adc         : adc selection id for battery voltage
- * @batt_temp_adc     : adc selection id for battery temperature
+ * @adc_vbatt         : adc channel name for battery voltage
+ * @adc_batt_temp     : adc channel name for battery temperature
  * @max_vbatt_mv      : used for calculating battery capacity
  * @chg_params        : initial charging parameters
  * @num_chg_params    : numbers of charging parameters
  * @charger_event     : the charger event can be reported to the platform side
  */
 struct lp8788_charger_platform_data {
-       enum lp8788_adc_id vbatt_adc;
-       enum lp8788_adc_id batt_temp_adc;
+       const char *adc_vbatt;
+       const char *adc_batt_temp;
        unsigned int max_vbatt_mv;
        struct lp8788_chg_param *chg_params;
        int num_chg_params;
diff --git a/include/linux/power/bq2415x_charger.h b/include/linux/power/bq2415x_charger.h
new file mode 100644 (file)
index 0000000..97a1665
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * bq2415x charger driver
+ *
+ * Copyright (C) 2011-2012  Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BQ2415X_CHARGER_H
+#define BQ2415X_CHARGER_H
+
+/*
+ * This is platform data for bq2415x chip. It contains default board
+ * voltages and currents which can be also later configured via sysfs. If
+ * value is -1 then default chip value (specified in datasheet) will be
+ * used.
+ *
+ * Value resistor_sense is needed for for configuring charge and
+ * termination current. It it is less or equal to zero, configuring charge
+ * and termination current will not be possible.
+ *
+ * Function set_mode_hook is needed for automode (setting correct current
+ * limit when charger is connected/disconnected or setting boost mode).
+ * When is NULL, automode function is disabled. When is not NULL, it must
+ * have this prototype:
+ *
+ *    int (*set_mode_hook)(
+ *      void (*hook)(enum bq2415x_mode mode, void *data),
+ *      void *data)
+ *
+ * hook is hook function (see below) and data is pointer to driver private
+ * data
+ *
+ * bq2415x driver will call it as:
+ *
+ *    platform_data->set_mode_hook(bq2415x_hook_function, bq2415x_device);
+ *
+ * Board/platform function set_mode_hook return non zero value when hook
+ * function was successful registered. Platform code should call that hook
+ * function (which get from pointer, with data) every time when charger
+ * was connected/disconnected or require to enable boost mode. bq2415x
+ * driver then will set correct current limit, enable/disable charger or
+ * boost mode.
+ *
+ * Hook function has this prototype:
+ *
+ *    void hook(enum bq2415x_mode mode, void *data);
+ *
+ * mode is bq2415x mode (charger or boost)
+ * data is pointer to driver private data (which get from
+ * set_charger_type_hook)
+ *
+ * When bq driver is being unloaded, it call function:
+ *
+ *    platform_data->set_mode_hook(NULL, NULL);
+ *
+ * (hook function and driver private data are NULL)
+ *
+ * After that board/platform code must not call driver hook function! It
+ * is possible that pointer to hook function will not be valid and calling
+ * will cause undefined result.
+ */
+
+/* Supported modes with maximal current limit */
+enum bq2415x_mode {
+       BQ2415X_MODE_NONE,              /* unknown or no charger (100mA) */
+       BQ2415X_MODE_HOST_CHARGER,      /* usb host/hub charger (500mA) */
+       BQ2415X_MODE_DEDICATED_CHARGER, /* dedicated charger (unlimited) */
+       BQ2415X_MODE_BOOST,             /* boost mode (charging disabled) */
+};
+
+struct bq2415x_platform_data {
+       int current_limit;              /* mA */
+       int weak_battery_voltage;       /* mV */
+       int battery_regulation_voltage; /* mV */
+       int charge_current;             /* mA */
+       int termination_current;        /* mA */
+       int resistor_sense;             /* m ohm */
+       int (*set_mode_hook)(void (*hook)(enum bq2415x_mode mode, void *data),
+                            void *data);
+};
+
+#endif
index e5ef45834c3c415e05af46d66b0d38599c12ec85..1f0ab90aff00ccbfc5075d49225d9d56ef73e4c8 100644 (file)
@@ -114,6 +114,8 @@ enum power_supply_property {
        POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
        POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
        POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+       POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
+       POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
        POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
        POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN,
        POWER_SUPPLY_PROP_ENERGY_FULL,
@@ -186,6 +188,7 @@ struct power_supply {
        struct work_struct changed_work;
 #ifdef CONFIG_THERMAL
        struct thermal_zone_device *tzd;
+       struct thermal_cooling_device *tcd;
 #endif
 
 #ifdef CONFIG_LEDS_TRIGGERS