aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-12-13 19:26:04 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2012-12-13 19:26:04 -0800
commit7313264b899bbf3988841296265a6e0e8a7b6521 (patch)
tree59b5069980434945394152e94eeaef2b32cf4e72
parentd8c532c40721f7507896d202b8cae3b3642d2b0d (diff)
parent76d8a23b127020472207b281427d3e9f4f1227e4 (diff)
Merge tag 'for-v3.8-merged' of git://git.infradead.org/battery-2.6
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 ...
-rw-r--r--Documentation/devicetree/bindings/mfd/ab8500.txt27
-rw-r--r--Documentation/devicetree/bindings/power_supply/ab8500/btemp.txt16
-rw-r--r--Documentation/devicetree/bindings/power_supply/ab8500/chargalg.txt16
-rw-r--r--Documentation/devicetree/bindings/power_supply/ab8500/charger.txt25
-rw-r--r--Documentation/devicetree/bindings/power_supply/ab8500/fg.txt58
-rw-r--r--Documentation/power/power_supply_class.txt3
-rw-r--r--arch/arm/boot/dts/dbx5x0.dtsi28
-rw-r--r--drivers/mfd/ab8500-core.c20
-rw-r--r--drivers/power/Kconfig24
-rw-r--r--drivers/power/Makefile4
-rw-r--r--drivers/power/ab8500_bmdata.c521
-rw-r--r--drivers/power/ab8500_btemp.c77
-rw-r--r--drivers/power/ab8500_charger.c84
-rw-r--r--drivers/power/ab8500_fg.c82
-rw-r--r--drivers/power/abx500_chargalg.c56
-rw-r--r--drivers/power/bq2415x_charger.c1670
-rw-r--r--drivers/power/bq27x00_battery.c8
-rw-r--r--drivers/power/ds2782_battery.c4
-rw-r--r--drivers/power/generic-adc-battery.c5
-rw-r--r--drivers/power/jz4740-battery.c45
-rw-r--r--drivers/power/lp8788-charger.c75
-rw-r--r--drivers/power/max17042_battery.c3
-rw-r--r--drivers/power/max8925_power.c51
-rw-r--r--drivers/power/power_supply_core.c96
-rw-r--r--drivers/power/power_supply_sysfs.c2
-rw-r--r--drivers/power/rx51_battery.c251
-rw-r--r--drivers/power/twl4030_charger.c12
-rw-r--r--include/linux/mfd/abx500.h34
-rw-r--r--include/linux/mfd/lp8788.h8
-rw-r--r--include/linux/power/bq2415x_charger.h95
-rw-r--r--include/linux/power_supply.h3
31 files changed, 3123 insertions, 280 deletions
diff --git a/Documentation/devicetree/bindings/mfd/ab8500.txt b/Documentation/devicetree/bindings/mfd/ab8500.txt
index ce83c8d3c00..13b707b7355 100644
--- a/Documentation/devicetree/bindings/mfd/ab8500.txt
+++ b/Documentation/devicetree/bindings/mfd/ab8500.txt
@@ -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
index 00000000000..0ba1bcc7f33
--- /dev/null
+++ b/Documentation/devicetree/bindings/power_supply/ab8500/btemp.txt
@@ -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
index 00000000000..ef532837112
--- /dev/null
+++ b/Documentation/devicetree/bindings/power_supply/ab8500/chargalg.txt
@@ -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
index 00000000000..6bdbb08ea9e
--- /dev/null
+++ b/Documentation/devicetree/bindings/power_supply/ab8500/charger.txt
@@ -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
index 00000000000..ccafcb9112f
--- /dev/null
+++ b/Documentation/devicetree/bindings/power_supply/ab8500/fg.txt
@@ -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";
+ }
+
diff --git a/Documentation/power/power_supply_class.txt b/Documentation/power/power_supply_class.txt
index 9c647bd7c5a..3f10b39b034 100644
--- a/Documentation/power/power_supply_class.txt
+++ b/Documentation/power/power_supply_class.txt
@@ -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.
diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi
index 0d69322f689..2efd9c891bc 100644
--- a/arch/arm/boot/dts/dbx5x0.dtsi
+++ b/arch/arm/boot/dts/dbx5x0.dtsi
@@ -340,7 +340,33 @@
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
diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c
index 3e27c031aea..59da1650fb8 100644
--- a/drivers/mfd/ab8500-core.c
+++ b/drivers/mfd/ab8500-core.c
@@ -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
},
};
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index b1d956d81f0..9f45e2f77d5 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -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
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index f1d99f4a0bc..22c8913382c 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -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
index 00000000000..03cc528425c
--- /dev/null
+++ b/drivers/power/ab8500_bmdata.c
@@ -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;
+}
diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c
index 989b09950af..20e2a7d3ef4 100644
--- a/drivers/power/ab8500_btemp.c
+++ b/drivers/power/ab8500_btemp.c
@@ -20,11 +20,13 @@
#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,
},
};
diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c
index 7ecb8abe20b..3be9c0ee3fc 100644
--- a/drivers/power/ab8500_charger.c
+++ b/drivers/power/ab8500_charger.c
@@ -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,
},
};
diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c
index 331dc43ded4..b3bf178c346 100644
--- a/drivers/power/ab8500_fg.c
+++ b/drivers/power/ab8500_fg.c
@@ -22,15 +22,16 @@
#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,
},
};
diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c
index 19f25419079..29708914606 100644
--- a/drivers/power/abx500_chargalg.c
+++ b/drivers/power/abx500_chargalg.c
@@ -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
index 00000000000..ee842b37f46
--- /dev/null
+++ b/drivers/power/bq2415x_charger.c
@@ -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");
diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c
index e0edaf7de54..36b34efdafc 100644
--- a/drivers/power/bq27x00_battery.c
+++ b/drivers/power/bq27x00_battery.c
@@ -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);
}
diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c
index 6bb6e2f5ea8..2fa9b6bf1f3 100644
--- a/drivers/power/ds2782_battery.c
+++ b/drivers/power/ds2782_battery.c
@@ -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;
}
diff --git a/drivers/power/generic-adc-battery.c b/drivers/power/generic-adc-battery.c
index e902b088d52..32ce17e235c 100644
--- a/drivers/power/generic-adc-battery.c
+++ b/drivers/power/generic-adc-battery.c
@@ -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;
}
diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c
index 74ac69e0687..bf914893c6f 100644
--- a/drivers/power/jz4740-battery.c
+++ b/drivers/power/jz4740-battery.c
@@ -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;
}
diff --git a/drivers/power/lp8788-charger.c b/drivers/power/lp8788-charger.c
index a1c51ac117f..22b6407c9ca 100644
--- a/drivers/power/lp8788-charger.c
+++ b/drivers/power/lp8788-charger.c
@@ -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)
diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c
index 5ffe46916f0..d664ef58afa 100644
--- a/drivers/power/max17042_battery.c
+++ b/drivers/power/max17042_battery.c
@@ -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__);
diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c
index 1a075f1f1b6..665cdc76c26 100644
--- a/drivers/power/max8925_power.c
+++ b/drivers/power/max8925_power.c
@@ -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");
diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c
index f77a41272e5..8a7cfb3cc16 100644
--- a/drivers/power/power_supply_core.c
+++ b/drivers/power/power_supply_core.c
@@ -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);
}
diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c
index 395c2cfa16c..40fa3b7cae5 100644
--- a/drivers/power/power_supply_sysfs.c
+++ b/drivers/power/power_supply_sysfs.c
@@ -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
index 00000000000..ca49d6c0ee9
--- /dev/null
+++ b/drivers/power/rx51_battery.c
@@ -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");
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index f9e70cf0819..a69d0d11b54 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -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);
diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h
index 5d5298d5602..2138bd33021 100644
--- a/include/linux/mfd/abx500.h
+++ b/include/linux/mfd/abx500.h
@@ -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);
diff --git a/include/linux/mfd/lp8788.h b/include/linux/mfd/lp8788.h
index cec364bdccf..2a32b16f79c 100644
--- a/include/linux/mfd/lp8788.h
+++ b/include/linux/mfd/lp8788.h
@@ -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
index 00000000000..97a1665eaea
--- /dev/null
+++ b/include/linux/power/bq2415x_charger.h
@@ -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
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index e5ef45834c3..1f0ab90aff0 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -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