aboutsummaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/88pm860x_battery.c14
-rw-r--r--drivers/power/88pm860x_charger.c7
-rw-r--r--drivers/power/Kconfig47
-rw-r--r--drivers/power/Makefile8
-rw-r--r--drivers/power/ab8500_bmdata.c646
-rw-r--r--drivers/power/ab8500_btemp.c255
-rw-r--r--drivers/power/ab8500_charger.c1578
-rw-r--r--drivers/power/ab8500_fg.c951
-rw-r--r--drivers/power/abx500_chargalg.c612
-rw-r--r--drivers/power/avs/smartreflex.c154
-rw-r--r--drivers/power/bq2415x_charger.c190
-rw-r--r--drivers/power/bq24190_charger.c1549
-rw-r--r--drivers/power/bq24735-charger.c419
-rw-r--r--drivers/power/bq27x00_battery.c23
-rw-r--r--drivers/power/charger-manager.c793
-rw-r--r--drivers/power/collie_battery.c2
-rw-r--r--drivers/power/da9030_battery.c7
-rw-r--r--drivers/power/da9052-battery.c7
-rw-r--r--drivers/power/ds2760_battery.c4
-rw-r--r--drivers/power/ds2780_battery.c7
-rw-r--r--drivers/power/ds2782_battery.c79
-rw-r--r--drivers/power/generic-adc-battery.c24
-rw-r--r--drivers/power/goldfish_battery.c236
-rw-r--r--drivers/power/gpio-charger.c26
-rw-r--r--drivers/power/intel_mid_battery.c2
-rw-r--r--drivers/power/isp1704_charger.c148
-rw-r--r--drivers/power/jz4740-battery.c13
-rw-r--r--drivers/power/lp8727_charger.c76
-rw-r--r--drivers/power/lp8788-charger.c33
-rw-r--r--drivers/power/max14577_charger.c311
-rw-r--r--drivers/power/max17040_battery.c29
-rw-r--r--drivers/power/max17042_battery.c379
-rw-r--r--drivers/power/max8903_charger.c4
-rw-r--r--drivers/power/max8925_power.c6
-rw-r--r--drivers/power/max8997_charger.c9
-rw-r--r--drivers/power/max8998_charger.c5
-rw-r--r--drivers/power/pcf50633-charger.c15
-rw-r--r--drivers/power/pda_power.c14
-rw-r--r--drivers/power/pm2301_charger.c1269
-rw-r--r--drivers/power/pm2301_charger.h492
-rw-r--r--drivers/power/power_supply_core.c296
-rw-r--r--drivers/power/power_supply_sysfs.c5
-rw-r--r--drivers/power/reset/Kconfig67
-rw-r--r--drivers/power/reset/Makefile9
-rw-r--r--drivers/power/reset/as3722-poweroff.c96
-rw-r--r--drivers/power/reset/axxia-reset.c88
-rw-r--r--drivers/power/reset/keystone-reset.c166
-rw-r--r--drivers/power/reset/msm-poweroff.c73
-rw-r--r--drivers/power/reset/qnap-poweroff.c141
-rw-r--r--drivers/power/reset/restart-poweroff.c66
-rw-r--r--drivers/power/reset/sun6i-reboot.c85
-rw-r--r--drivers/power/reset/vexpress-poweroff.c147
-rw-r--r--drivers/power/reset/xgene-reboot.c103
-rw-r--r--drivers/power/rx51_battery.c28
-rw-r--r--drivers/power/s3c_adc_battery.c7
-rw-r--r--drivers/power/sbs-battery.c23
-rw-r--r--drivers/power/test_power.c31
-rw-r--r--drivers/power/tosa_battery.c2
-rw-r--r--drivers/power/tps65090-charger.c323
-rw-r--r--drivers/power/twl4030_charger.c68
-rw-r--r--drivers/power/twl4030_madc_battery.c245
-rw-r--r--drivers/power/wm831x_backup.c11
62 files changed, 10574 insertions, 1949 deletions
diff --git a/drivers/power/88pm860x_battery.c b/drivers/power/88pm860x_battery.c
index 8bc80b05c63..dfcda3a4940 100644
--- a/drivers/power/88pm860x_battery.c
+++ b/drivers/power/88pm860x_battery.c
@@ -915,15 +915,13 @@ static int pm860x_battery_probe(struct platform_device *pdev)
info->irq_cc = platform_get_irq(pdev, 0);
if (info->irq_cc <= 0) {
dev_err(&pdev->dev, "No IRQ resource!\n");
- ret = -EINVAL;
- goto out;
+ return -EINVAL;
}
info->irq_batt = platform_get_irq(pdev, 1);
if (info->irq_batt <= 0) {
dev_err(&pdev->dev, "No IRQ resource!\n");
- ret = -EINVAL;
- goto out;
+ return -EINVAL;
}
info->chip = chip;
@@ -957,7 +955,7 @@ static int pm860x_battery_probe(struct platform_device *pdev)
ret = power_supply_register(&pdev->dev, &info->battery);
if (ret)
- goto out;
+ return ret;
info->battery.dev->parent = &pdev->dev;
ret = request_threaded_irq(info->irq_cc, NULL,
@@ -984,8 +982,6 @@ out_coulomb:
free_irq(info->irq_cc, info);
out_reg:
power_supply_unregister(&info->battery);
-out:
- kfree(info);
return ret;
}
@@ -993,11 +989,9 @@ static int pm860x_battery_remove(struct platform_device *pdev)
{
struct pm860x_battery_info *info = platform_get_drvdata(pdev);
- power_supply_unregister(&info->battery);
free_irq(info->irq_batt, info);
free_irq(info->irq_cc, info);
- kfree(info);
- platform_set_drvdata(pdev, NULL);
+ power_supply_unregister(&info->battery);
return 0;
}
diff --git a/drivers/power/88pm860x_charger.c b/drivers/power/88pm860x_charger.c
index 4b37a5af8de..de029bbc1cc 100644
--- a/drivers/power/88pm860x_charger.c
+++ b/drivers/power/88pm860x_charger.c
@@ -554,7 +554,7 @@ static irqreturn_t pm860x_vchg_handler(int irq, void *data)
OVTEMP_AUTORECOVER,
OVTEMP_AUTORECOVER);
dev_dbg(info->dev,
- "%s, pm8606 over-temp occure\n", __func__);
+ "%s, pm8606 over-temp occurred\n", __func__);
}
}
@@ -562,7 +562,7 @@ static irqreturn_t pm860x_vchg_handler(int irq, void *data)
set_vchg_threshold(info, VCHG_OVP_LOW, 0);
info->allowed = 0;
dev_dbg(info->dev,
- "%s,pm8607 over-vchg occure,vchg = %dmv\n",
+ "%s,pm8607 over-vchg occurred,vchg = %dmv\n",
__func__, vchg);
} else if (vchg < VCHG_OVP_LOW) {
set_vchg_threshold(info, VCHG_NORMAL_LOW,
@@ -714,7 +714,6 @@ out_irq:
while (--i >= 0)
free_irq(info->irq[i], info);
out:
- kfree(info);
return ret;
}
@@ -723,12 +722,10 @@ static int pm860x_charger_remove(struct platform_device *pdev)
struct pm860x_charger_info *info = platform_get_drvdata(pdev);
int i;
- platform_set_drvdata(pdev, NULL);
power_supply_unregister(&info->usb);
free_irq(info->irq[0], info);
for (i = 0; i < info->irq_nums; i++)
free_irq(info->irq[i], info);
- kfree(info);
return 0;
}
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 9f45e2f77d5..ba697512307 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -152,6 +152,7 @@ config BATTERY_SBS
config BATTERY_BQ27x00
tristate "BQ27x00 battery driver"
+ depends on I2C || I2C=n
help
Say Y here to enable support for batteries with BQ27x00 (I2C/HDQ) chips.
@@ -195,6 +196,7 @@ config BATTERY_MAX17040
config BATTERY_MAX17042
tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge"
depends on I2C
+ select REGMAP_I2C
help
MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries
in handheld and portable equipment. The MAX17042 is configured
@@ -215,6 +217,13 @@ config BATTERY_S3C_ADC
help
Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery
+config BATTERY_TWL4030_MADC
+ tristate "TWL4030 MADC battery driver"
+ depends on TWL4030_MADC
+ help
+ Say Y here to enable this dumb driver for batteries managed
+ through the TWL4030 MADC.
+
config CHARGER_88PM860X
tristate "Marvell 88PM860x Charger driver"
depends on MFD_88PM860X && BATTERY_88PM860X
@@ -254,14 +263,13 @@ config BATTERY_RX51
config CHARGER_ISP1704
tristate "ISP1704 USB Charger Detection"
- depends on USB_OTG_UTILS
+ depends on USB_PHY
help
Say Y to enable support for USB Charger Detection with
ISP1707/ISP1704 USB transceivers.
config CHARGER_MAX8903
tristate "MAX8903 Battery DC-DC Charger for USB and Adapter Power"
- depends on GENERIC_HARDIRQS
help
Say Y to enable support for the MAX8903 DC-DC charger and sysfs.
The driver supports controlling charger-enable and current-limit
@@ -284,6 +292,7 @@ config CHARGER_LP8788
tristate "TI LP8788 charger driver"
depends on MFD_LP8788
depends on LP8788_ADC
+ depends on IIO
help
Say Y to enable support for the LP8788 linear charger.
@@ -308,6 +317,13 @@ config CHARGER_MANAGER
runtime and in suspend-to-RAM by waking up the system periodically
with help of suspend_again support.
+config CHARGER_MAX14577
+ tristate "Maxim MAX14577 MUIC battery charger driver"
+ depends on MFD_MAX14577
+ help
+ Say Y to enable support for the battery charger control sysfs and
+ platform data of MAX14577 MUICs.
+
config CHARGER_MAX8997
tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
depends on MFD_MAX8997 && REGULATOR_MAX8997
@@ -332,6 +348,18 @@ config CHARGER_BQ2415X
You'll need this driver to charge batteries on e.g. Nokia
RX-51/N900.
+config CHARGER_BQ24190
+ tristate "TI BQ24190 battery charger driver"
+ depends on I2C && GPIOLIB
+ help
+ Say Y to enable support for the TI BQ24190 battery charger.
+
+config CHARGER_BQ24735
+ tristate "TI BQ24735 battery charger support"
+ depends on I2C && GPIOLIB
+ help
+ Say Y to enable support for the TI BQ24735 battery charger.
+
config CHARGER_SMB347
tristate "Summit Microelectronics SMB347 Battery Charger"
depends on I2C
@@ -340,12 +368,27 @@ config CHARGER_SMB347
Say Y to include support for Summit Microelectronics SMB347
Battery Charger.
+config CHARGER_TPS65090
+ tristate "TPS65090 battery charger driver"
+ depends on MFD_TPS65090
+ help
+ Say Y here to enable support for battery charging with TPS65090
+ PMIC chips.
+
config AB8500_BM
bool "AB8500 Battery Management Driver"
depends on AB8500_CORE && AB8500_GPADC
help
Say Y to include support for AB8500 battery management.
+config BATTERY_GOLDFISH
+ tristate "Goldfish battery driver"
+ depends on GOLDFISH || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Say Y to enable support for the battery and AC power in the
+ Goldfish emulator.
+
source "drivers/power/reset/Kconfig"
endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 22c8913382c..ee54a3e4c90 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o
+obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
@@ -33,12 +34,13 @@ obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
obj-$(CONFIG_BATTERY_Z2) += z2_battery.o
obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o
+obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
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_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_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o
obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
@@ -46,9 +48,13 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
+obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
+obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
+obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o
obj-$(CONFIG_POWER_AVS) += avs/
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
+obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
obj-$(CONFIG_POWER_RESET) += reset/
diff --git a/drivers/power/ab8500_bmdata.c b/drivers/power/ab8500_bmdata.c
index f034ae43e04..d2986453309 100644
--- a/drivers/power/ab8500_bmdata.c
+++ b/drivers/power/ab8500_bmdata.c
@@ -11,7 +11,7 @@
* 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[] = {
+const struct abx500_res_to_temp ab8500_temp_tbl_a_thermistor[] = {
{-5, 53407},
{ 0, 48594},
{ 5, 43804},
@@ -28,8 +28,12 @@ static struct abx500_res_to_temp temp_tbl_A_thermistor[] = {
{60, 13437},
{65, 12500},
};
+EXPORT_SYMBOL(ab8500_temp_tbl_a_thermistor);
-static struct abx500_res_to_temp temp_tbl_B_thermistor[] = {
+const int ab8500_temp_tbl_a_size = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor);
+EXPORT_SYMBOL(ab8500_temp_tbl_a_size);
+
+const struct abx500_res_to_temp ab8500_temp_tbl_b_thermistor[] = {
{-5, 200000},
{ 0, 159024},
{ 5, 151921},
@@ -46,8 +50,12 @@ static struct abx500_res_to_temp temp_tbl_B_thermistor[] = {
{60, 85461},
{65, 82869},
};
+EXPORT_SYMBOL(ab8500_temp_tbl_b_thermistor);
+
+const int ab8500_temp_tbl_b_size = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor);
+EXPORT_SYMBOL(ab8500_temp_tbl_b_size);
-static struct abx500_v_to_cap cap_tbl_A_thermistor[] = {
+static const struct abx500_v_to_cap cap_tbl_a_thermistor[] = {
{4171, 100},
{4114, 95},
{4009, 83},
@@ -70,7 +78,7 @@ static struct abx500_v_to_cap cap_tbl_A_thermistor[] = {
{3247, 0},
};
-static struct abx500_v_to_cap cap_tbl_B_thermistor[] = {
+static const struct abx500_v_to_cap cap_tbl_b_thermistor[] = {
{4161, 100},
{4124, 98},
{4044, 90},
@@ -93,7 +101,7 @@ static struct abx500_v_to_cap cap_tbl_B_thermistor[] = {
{3250, 0},
};
-static struct abx500_v_to_cap cap_tbl[] = {
+static const struct abx500_v_to_cap cap_tbl[] = {
{4186, 100},
{4163, 99},
{4114, 95},
@@ -124,7 +132,7 @@ static struct abx500_v_to_cap cap_tbl[] = {
* 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[] = {
+static const struct abx500_res_to_temp temp_tbl[] = {
{-5, 214834},
{ 0, 162943},
{ 5, 124820},
@@ -146,7 +154,7 @@ static struct abx500_res_to_temp temp_tbl[] = {
* 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[] = {
+static const struct batres_vs_temp temp_to_batres_tbl_thermistor[] = {
{ 40, 120},
{ 30, 135},
{ 20, 165},
@@ -160,7 +168,7 @@ static struct batres_vs_temp temp_to_batres_tbl_thermistor[] = {
* 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[] = {
+static const struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = {
{ 60, 300},
{ 30, 300},
{ 20, 300},
@@ -171,7 +179,7 @@ static struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = {
};
/* battery resistance table for LI ION 9100 battery */
-static struct batres_vs_temp temp_to_batres_tbl_9100[] = {
+static const struct batres_vs_temp temp_to_batres_tbl_9100[] = {
{ 60, 180},
{ 30, 180},
{ 20, 180},
@@ -182,206 +190,206 @@ static struct batres_vs_temp temp_to_batres_tbl_9100[] = {
};
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,
-},
+ [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_cap = 95,
+ .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_cap = 95,
+ .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(ab8500_temp_tbl_a_thermistor),
+ .r_to_t_tbl = ab8500_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_cap = 95,
+ .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(ab8500_temp_tbl_b_thermistor),
+ .r_to_t_tbl = ab8500_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,
-},
+ [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_cap = 95,
+ .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,
-},
+ {
+ .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_cap = 95,
+ .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_cap = 95,
+ .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_cap = 95,
+ .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 = {
@@ -405,17 +413,29 @@ static const struct abx500_fg_parameters fg = {
.lowbat_threshold = 3100,
.battok_falling_th_sel0 = 2860,
.battok_raising_th_sel1 = 2860,
+ .maint_thres = 95,
.user_cap_limit = 15,
- .maint_thres = 97,
+ .pcut_enable = 1,
+ .pcut_max_time = 127,
+ .pcut_flag_time = 112,
+ .pcut_max_restart = 15,
+ .pcut_debounce_time = 2,
};
-static const struct abx500_maxim_parameters maxi_params = {
+static const struct abx500_maxim_parameters ab8500_maxi_params = {
.ena_maxi = true,
.chg_curr = 910,
.wait_cycles = 10,
.charger_curr_step = 100,
};
+static const struct abx500_maxim_parameters abx540_maxi_params = {
+ .ena_maxi = true,
+ .chg_curr = 3000,
+ .wait_cycles = 10,
+ .charger_curr_step = 200,
+};
+
static const struct abx500_bm_charger_parameters chg = {
.usb_volt_max = 5500,
.usb_curr_max = 1500,
@@ -423,97 +443,163 @@ static const struct abx500_bm_charger_parameters chg = {
.ac_curr_max = 1500,
};
+/*
+ * This array maps the raw hex value to charger output current used by the
+ * AB8500 values
+ */
+static int ab8500_charge_output_curr_map[] = {
+ 100, 200, 300, 400, 500, 600, 700, 800,
+ 900, 1000, 1100, 1200, 1300, 1400, 1500, 1500,
+};
+
+static int ab8540_charge_output_curr_map[] = {
+ 0, 0, 0, 75, 100, 125, 150, 175,
+ 200, 225, 250, 275, 300, 325, 350, 375,
+ 400, 425, 450, 475, 500, 525, 550, 575,
+ 600, 625, 650, 675, 700, 725, 750, 775,
+ 800, 825, 850, 875, 900, 925, 950, 975,
+ 1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175,
+ 1200, 1225, 1250, 1275, 1300, 1325, 1350, 1375,
+ 1400, 1425, 1450, 1500, 1600, 1700, 1900, 2000,
+};
+
+/*
+ * This array maps the raw hex value to charger input current used by the
+ * AB8500 values
+ */
+static int ab8500_charge_input_curr_map[] = {
+ 50, 98, 193, 290, 380, 450, 500, 600,
+ 700, 800, 900, 1000, 1100, 1300, 1400, 1500,
+};
+
+static int ab8540_charge_input_curr_map[] = {
+ 25, 50, 75, 100, 125, 150, 175, 200,
+ 225, 250, 275, 300, 325, 350, 375, 400,
+ 425, 450, 475, 500, 525, 550, 575, 600,
+ 625, 650, 675, 700, 725, 750, 775, 800,
+ 825, 850, 875, 900, 925, 950, 975, 1000,
+ 1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200,
+ 1225, 1250, 1275, 1300, 1325, 1350, 1375, 1400,
+ 1425, 1450, 1475, 1500, 1500, 1500, 1500, 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,
+ .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,
+ .capacity_scaling = 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 = ARRAY_SIZE(bat_type_thermistor),
+ .batt_id = 0,
+ .interval_charging = 5,
+ .interval_not_charging = 120,
+ .temp_hysteresis = 3,
+ .gnd_lift_resistance = 34,
+ .chg_output_curr = ab8500_charge_output_curr_map,
+ .n_chg_out_curr = ARRAY_SIZE(ab8500_charge_output_curr_map),
+ .maxi = &ab8500_maxi_params,
+ .chg_params = &chg,
+ .fg_params = &fg,
+ .chg_input_curr = ab8500_charge_input_curr_map,
+ .n_chg_in_curr = ARRAY_SIZE(ab8500_charge_input_curr_map),
};
-int bmdevs_of_probe(struct device *dev, struct device_node *np,
- struct abx500_bm_data **battery)
+struct abx500_bm_data ab8540_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,
+ .capacity_scaling = 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 = ARRAY_SIZE(bat_type_thermistor),
+ .batt_id = 0,
+ .interval_charging = 5,
+ .interval_not_charging = 120,
+ .temp_hysteresis = 3,
+ .gnd_lift_resistance = 0,
+ .maxi = &abx540_maxi_params,
+ .chg_params = &chg,
+ .fg_params = &fg,
+ .chg_output_curr = ab8540_charge_output_curr_map,
+ .n_chg_out_curr = ARRAY_SIZE(ab8540_charge_output_curr_map),
+ .chg_input_curr = ab8540_charge_input_curr_map,
+ .n_chg_in_curr = ARRAY_SIZE(ab8540_charge_input_curr_map),
+};
+
+int ab8500_bm_of_probe(struct device *dev,
+ struct device_node *np,
+ struct abx500_bm_data *bm)
{
- struct abx500_battery_type *btype;
- struct device_node *np_bat_supply;
- struct abx500_bm_data *bat;
+ const struct batres_vs_temp *tmp_batres_tbl;
+ struct device_node *battery_node;
const char *btech;
- char bat_tech[8];
- int i, thermistor;
-
- *battery = &ab8500_bm_data;
+ int i;
/* 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");
+ battery_node = of_parse_phandle(np, "battery", 0);
+ if (!battery_node) {
+ dev_err(dev, "battery node or reference missing\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);
+
+ btech = of_get_property(battery_node, "stericsson,battery-type", NULL);
if (!btech) {
dev_warn(dev, "missing property battery-name/type\n");
- strcpy(bat_tech, "UNKNOWN");
- } else {
- strcpy(bat_tech, btech);
+ return -EINVAL;
}
- 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;
+ if (strncmp(btech, "LION", 4) == 0) {
+ bm->no_maintenance = true;
+ bm->chg_unknown_bat = true;
+ bm->bat_type[BATTERY_UNKNOWN].charge_full_design = 2600;
+ bm->bat_type[BATTERY_UNKNOWN].termination_vol = 4150;
+ bm->bat_type[BATTERY_UNKNOWN].recharge_cap = 95;
+ bm->bat_type[BATTERY_UNKNOWN].normal_cur_lvl = 520;
+ bm->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;
- }
+
+ if (of_property_read_bool(battery_node, "thermistor-on-batctrl")) {
+ if (strncmp(btech, "LION", 4) == 0)
+ tmp_batres_tbl = temp_to_batres_tbl_9100;
+ else
+ tmp_batres_tbl = temp_to_batres_tbl_thermistor;
+ } else {
+ bm->n_btypes = 4;
+ bm->bat_type = bat_type_ext_thermistor;
+ bm->adc_therm = ABx500_ADC_THERM_BATTEMP;
+ tmp_batres_tbl = temp_to_batres_tbl_ext_thermistor;
}
- of_node_put(np_bat_supply);
+
+ /* select the battery resolution table */
+ for (i = 0; i < bm->n_btypes; ++i)
+ bm->bat_type[i].batres_tbl = tmp_batres_tbl;
+
+ of_node_put(battery_node);
+
return 0;
}
diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c
index 20e2a7d3ef4..7f9a4547dcc 100644
--- a/drivers/power/ab8500_btemp.c
+++ b/drivers/power/ab8500_btemp.c
@@ -39,6 +39,12 @@
#define BTEMP_BATCTRL_CURR_SRC_7UA 7
#define BTEMP_BATCTRL_CURR_SRC_20UA 20
+#define BTEMP_BATCTRL_CURR_SRC_16UA 16
+#define BTEMP_BATCTRL_CURR_SRC_18UA 18
+
+#define BTEMP_BATCTRL_CURR_SRC_60UA 60
+#define BTEMP_BATCTRL_CURR_SRC_120UA 120
+
#define to_ab8500_btemp_device_info(x) container_of((x), \
struct ab8500_btemp, btemp_psy);
@@ -73,17 +79,18 @@ struct ab8500_btemp_ranges {
* @dev: Pointer to the structure device
* @node: List of AB8500 BTEMPs, hence prepared for reentrance
* @curr_source: What current source we use, in uA
- * @bat_temp: Battery temperature in degree Celcius
- * @prev_bat_temp Last dispatched battery temperature
+ * @bat_temp: Dispatched battery temperature in degree Celcius
+ * @prev_bat_temp Last measured battery temperature in degree Celcius
* @parent: Pointer to the struct ab8500
* @gpadc: Pointer to the struct gpadc
* @fg: Pointer to the struct fg
- * @bat: Pointer to the abx500_bm platform data
+ * @bm: Platform specific battery management information
* @btemp_psy: Structure for BTEMP specific battery properties
* @events: Structure for information about events triggered
* @btemp_ranges: Battery temperature range structure
* @btemp_wq: Work queue for measuring the temperature periodically
* @btemp_periodic_work: Work for measuring the temperature periodically
+ * @initialized: True if battery id read.
*/
struct ab8500_btemp {
struct device *dev;
@@ -94,12 +101,13 @@ struct ab8500_btemp {
struct ab8500 *parent;
struct ab8500_gpadc *gpadc;
struct ab8500_fg *fg;
- struct abx500_bm_data *bat;
+ struct abx500_bm_data *bm;
struct power_supply btemp_psy;
struct ab8500_btemp_events events;
struct ab8500_btemp_ranges btemp_ranges;
struct workqueue_struct *btemp_wq;
struct delayed_work btemp_periodic_work;
+ bool initialized;
};
/* BTEMP power supply properties */
@@ -123,6 +131,7 @@ struct ab8500_btemp *ab8500_btemp_get(void)
return btemp;
}
+EXPORT_SYMBOL(ab8500_btemp_get);
/**
* ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance
@@ -147,13 +156,13 @@ static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di,
return (450000 * (v_batctrl)) / (1800 - v_batctrl);
}
- if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL) {
+ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL) {
/*
* If the battery has internal NTC, we use the current
- * source to calculate the resistance, 7uA or 20uA
+ * source to calculate the resistance.
*/
rbs = (v_batctrl * 1000
- - di->bat->gnd_lift_resistance * inst_curr)
+ - di->bm->gnd_lift_resistance * inst_curr)
/ di->curr_source;
} else {
/*
@@ -209,11 +218,24 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
return 0;
/* Only do this for batteries with internal NTC */
- if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) {
- if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA)
- curr = BAT_CTRL_7U_ENA;
- else
- curr = BAT_CTRL_20U_ENA;
+ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) {
+
+ if (is_ab8540(di->parent)) {
+ if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_60UA)
+ curr = BAT_CTRL_60U_ENA;
+ else
+ curr = BAT_CTRL_120U_ENA;
+ } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_16UA)
+ curr = BAT_CTRL_16U_ENA;
+ else
+ curr = BAT_CTRL_18U_ENA;
+ } else {
+ if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA)
+ curr = BAT_CTRL_7U_ENA;
+ else
+ curr = BAT_CTRL_20U_ENA;
+ }
dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source);
@@ -241,14 +263,32 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
__func__);
goto disable_curr_source;
}
- } else if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) {
+ } else if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) {
dev_dbg(di->dev, "Disable BATCTRL curr source\n");
- /* Write 0 to the curr bits */
- ret = abx500_mask_and_set_register_interruptible(di->dev,
- AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
- BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
- ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ if (is_ab8540(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(
+ di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA,
+ ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA));
+ } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(
+ di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA,
+ ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA));
+ } else {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(
+ di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
+ ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ }
+
if (ret) {
dev_err(di->dev, "%s failed disabling current source\n",
__func__);
@@ -290,11 +330,26 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
* if we got an error above
*/
disable_curr_source:
- /* Write 0 to the curr bits */
- ret = abx500_mask_and_set_register_interruptible(di->dev,
+ if (is_ab8540(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA,
+ ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA));
+ } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA,
+ ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA));
+ } else {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ }
+
if (ret) {
dev_err(di->dev, "%s failed disabling current source\n",
__func__);
@@ -372,13 +427,10 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di)
return ret;
}
- /*
- * Since there is no interrupt when current measurement is done,
- * loop for over 250ms (250ms is one sample conversion time
- * with 32.768 Khz RTC clock). Note that a stop time must be set
- * since the ab8500_btemp_read_batctrl_voltage call can block and
- * take an unknown amount of time to complete.
- */
+ do {
+ msleep(20);
+ } while (!ab8500_fg_inst_curr_started(di->fg));
+
i = 0;
do {
@@ -457,9 +509,9 @@ static int ab8500_btemp_measure_temp(struct ab8500_btemp *di)
int rbat, rntc, vntc;
u8 id;
- id = di->bat->batt_id;
+ id = di->bm->batt_id;
- if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL &&
+ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL &&
id != BATTERY_UNKNOWN) {
rbat = ab8500_btemp_get_batctrl_res(di);
@@ -474,8 +526,8 @@ static int ab8500_btemp_measure_temp(struct ab8500_btemp *di)
}
temp = ab8500_btemp_res_to_temp(di,
- di->bat->bat_type[id].r_to_t_tbl,
- di->bat->bat_type[id].n_temp_tbl_elements, rbat);
+ di->bm->bat_type[id].r_to_t_tbl,
+ di->bm->bat_type[id].n_temp_tbl_elements, rbat);
} else {
vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL);
if (vntc < 0) {
@@ -491,8 +543,8 @@ static int ab8500_btemp_measure_temp(struct ab8500_btemp *di)
rntc = 230000 * vntc / (VTVOUT_V - vntc);
temp = ab8500_btemp_res_to_temp(di,
- di->bat->bat_type[id].r_to_t_tbl,
- di->bat->bat_type[id].n_temp_tbl_elements, rntc);
+ di->bm->bat_type[id].r_to_t_tbl,
+ di->bm->bat_type[id].n_temp_tbl_elements, rntc);
prev = temp;
}
dev_dbg(di->dev, "Battery temperature is %d\n", temp);
@@ -511,9 +563,14 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
{
int res;
u8 i;
+ if (is_ab8540(di->parent))
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA;
+ else if (is_ab9540(di->parent) || is_ab8505(di->parent))
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA;
+ else
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
- di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
- di->bat->batt_id = BATTERY_UNKNOWN;
+ di->bm->batt_id = BATTERY_UNKNOWN;
res = ab8500_btemp_get_batctrl_res(di);
if (res < 0) {
@@ -522,23 +579,23 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
}
/* BATTERY_UNKNOWN is defined on position 0, skip it! */
- for (i = BATTERY_UNKNOWN + 1; i < di->bat->n_btypes; i++) {
- if ((res <= di->bat->bat_type[i].resis_high) &&
- (res >= di->bat->bat_type[i].resis_low)) {
+ for (i = BATTERY_UNKNOWN + 1; i < di->bm->n_btypes; i++) {
+ if ((res <= di->bm->bat_type[i].resis_high) &&
+ (res >= di->bm->bat_type[i].resis_low)) {
dev_dbg(di->dev, "Battery detected on %s"
" low %d < res %d < high: %d"
" index: %d\n",
- di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL ?
+ di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL ?
"BATCTRL" : "BATTEMP",
- di->bat->bat_type[i].resis_low, res,
- di->bat->bat_type[i].resis_high, i);
+ di->bm->bat_type[i].resis_low, res,
+ di->bm->bat_type[i].resis_high, i);
- di->bat->batt_id = i;
+ di->bm->batt_id = i;
break;
}
}
- if (di->bat->batt_id == BATTERY_UNKNOWN) {
+ if (di->bm->batt_id == BATTERY_UNKNOWN) {
dev_warn(di->dev, "Battery identified as unknown"
", resistance %d Ohm\n", res);
return -ENXIO;
@@ -546,15 +603,25 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
/*
* We only have to change current source if the
- * detected type is Type 1, else we use the 7uA source
+ * detected type is Type 1.
*/
- if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL &&
- di->bat->batt_id == 1) {
- dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
- di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA;
+ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL &&
+ di->bm->batt_id == 1) {
+ if (is_ab8540(di->parent)) {
+ dev_dbg(di->dev,
+ "Set BATCTRL current source to 60uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA;
+ } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ dev_dbg(di->dev,
+ "Set BATCTRL current source to 16uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA;
+ } else {
+ dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA;
+ }
}
- return di->bat->batt_id;
+ return di->bm->batt_id;
}
/**
@@ -566,20 +633,42 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
static void ab8500_btemp_periodic_work(struct work_struct *work)
{
int interval;
+ int bat_temp;
struct ab8500_btemp *di = container_of(work,
struct ab8500_btemp, btemp_periodic_work.work);
- di->bat_temp = ab8500_btemp_measure_temp(di);
+ if (!di->initialized) {
+ /* Identify the battery */
+ if (ab8500_btemp_id(di) < 0)
+ dev_warn(di->dev, "failed to identify the battery\n");
+ }
- if (di->bat_temp != di->prev_bat_temp) {
- di->prev_bat_temp = di->bat_temp;
+ bat_temp = ab8500_btemp_measure_temp(di);
+ /*
+ * Filter battery temperature.
+ * Allow direct updates on temperature only if two samples result in
+ * same temperature. Else only allow 1 degree change from previous
+ * reported value in the direction of the new measurement.
+ */
+ if ((bat_temp == di->prev_bat_temp) || !di->initialized) {
+ if ((di->bat_temp != di->prev_bat_temp) || !di->initialized) {
+ di->initialized = true;
+ di->bat_temp = bat_temp;
+ power_supply_changed(&di->btemp_psy);
+ }
+ } else if (bat_temp < di->prev_bat_temp) {
+ di->bat_temp--;
+ power_supply_changed(&di->btemp_psy);
+ } else if (bat_temp > di->prev_bat_temp) {
+ di->bat_temp++;
power_supply_changed(&di->btemp_psy);
}
+ di->prev_bat_temp = bat_temp;
if (di->events.ac_conn || di->events.usb_conn)
- interval = di->bat->temp_interval_chg;
+ interval = di->bm->temp_interval_chg;
else
- interval = di->bat->temp_interval_nochg;
+ interval = di->bm->temp_interval_nochg;
/* Schedule a new measurement */
queue_delayed_work(di->btemp_wq,
@@ -616,9 +705,9 @@ static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di)
{
struct ab8500_btemp *di = _di;
- if (is_ab8500_2p0_or_earlier(di->parent)) {
+ if (is_ab8500_3p3_or_earlier(di->parent)) {
dev_dbg(di->dev, "Ignore false btemp low irq"
- " for ABB cut 1.0, 1.1 and 2.0\n");
+ " for ABB cut 1.0, 1.1, 2.0 and 3.3\n");
} else {
dev_crit(di->dev, "Battery temperature lower than -10deg c\n");
@@ -727,35 +816,35 @@ static void ab8500_btemp_periodic(struct ab8500_btemp *di,
*
* Returns battery temperature
*/
-static int ab8500_btemp_get_temp(struct ab8500_btemp *di)
+int ab8500_btemp_get_temp(struct ab8500_btemp *di)
{
int temp = 0;
/*
- * The BTEMP events are not reliabe on AB8500 cut2.0
+ * The BTEMP events are not reliabe on AB8500 cut3.3
* and prior versions
*/
- if (is_ab8500_2p0_or_earlier(di->parent)) {
+ if (is_ab8500_3p3_or_earlier(di->parent)) {
temp = di->bat_temp * 10;
} else {
if (di->events.btemp_low) {
if (temp > di->btemp_ranges.btemp_low_limit)
- temp = di->btemp_ranges.btemp_low_limit;
+ temp = di->btemp_ranges.btemp_low_limit * 10;
else
temp = di->bat_temp * 10;
} else if (di->events.btemp_high) {
if (temp < di->btemp_ranges.btemp_high_limit)
- temp = di->btemp_ranges.btemp_high_limit;
+ temp = di->btemp_ranges.btemp_high_limit * 10;
else
temp = di->bat_temp * 10;
} else if (di->events.btemp_lowmed) {
if (temp > di->btemp_ranges.btemp_med_limit)
- temp = di->btemp_ranges.btemp_med_limit;
+ temp = di->btemp_ranges.btemp_med_limit * 10;
else
temp = di->bat_temp * 10;
} else if (di->events.btemp_medhigh) {
if (temp < di->btemp_ranges.btemp_med_limit)
- temp = di->btemp_ranges.btemp_med_limit;
+ temp = di->btemp_ranges.btemp_med_limit * 10;
else
temp = di->bat_temp * 10;
} else
@@ -763,6 +852,7 @@ static int ab8500_btemp_get_temp(struct ab8500_btemp *di)
}
return temp;
}
+EXPORT_SYMBOL(ab8500_btemp_get_temp);
/**
* ab8500_btemp_get_batctrl_temp() - get the temperature
@@ -774,6 +864,7 @@ int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp)
{
return btemp->bat_temp * 1000;
}
+EXPORT_SYMBOL(ab8500_btemp_get_batctrl_temp);
/**
* ab8500_btemp_get_property() - get the btemp properties
@@ -806,7 +897,7 @@ static int ab8500_btemp_get_property(struct power_supply *psy,
val->intval = 1;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
- val->intval = di->bat->bat_type[di->bat->batt_id].name;
+ val->intval = di->bm->bat_type[di->bm->batt_id].name;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = ab8500_btemp_get_temp(di);
@@ -954,7 +1045,6 @@ static int ab8500_btemp_remove(struct platform_device *pdev)
flush_scheduled_work();
power_supply_unregister(&di->btemp_psy);
- platform_set_drvdata(pdev, NULL);
return 0;
}
@@ -967,6 +1057,7 @@ static char *supply_interface[] = {
static int ab8500_btemp_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ struct abx500_bm_data *plat = pdev->dev.platform_data;
struct ab8500_btemp *di;
int irq, i, ret = 0;
u8 val;
@@ -976,21 +1067,19 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
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;
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no battery management data supplied\n");
+ return -EINVAL;
+ }
+ di->bm = plat;
+
+ if (np) {
+ ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get battery information\n");
+ return ret;
}
- } else {
- dev_info(&pdev->dev, "falling back to legacy platform data\n");
}
/* get parent data */
@@ -998,6 +1087,8 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
di->parent = dev_get_drvdata(pdev->dev.parent);
di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+ di->initialized = false;
+
/* BTEMP supply */
di->btemp_psy.name = "ab8500_btemp";
di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY;
@@ -1022,10 +1113,6 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
INIT_DEFERRABLE_WORK(&di->btemp_periodic_work,
ab8500_btemp_periodic_work);
- /* Identify the battery */
- if (ab8500_btemp_id(di) < 0)
- dev_warn(di->dev, "failed to identify the battery\n");
-
/* Set BTEMP thermal limits. Low and Med are fixed */
di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT;
di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT;
@@ -1123,7 +1210,7 @@ static void __exit ab8500_btemp_exit(void)
platform_driver_unregister(&ab8500_btemp_driver);
}
-subsys_initcall_sync(ab8500_btemp_init);
+device_initcall(ab8500_btemp_init);
module_exit(ab8500_btemp_exit);
MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c
index 3be9c0ee3fc..19110aa613a 100644
--- a/drivers/power/ab8500_charger.c
+++ b/drivers/power/ab8500_charger.c
@@ -15,6 +15,7 @@
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
+#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
@@ -31,6 +32,7 @@
#include <linux/mfd/abx500/ab8500-gpadc.h>
#include <linux/mfd/abx500/ux500_chargalg.h>
#include <linux/usb/otg.h>
+#include <linux/mutex.h>
/* Charger constants */
#define NO_PW_CONN 0
@@ -51,9 +53,15 @@
#define VBUS_DET_DBNC100 0x02
#define VBUS_DET_DBNC1 0x01
#define OTP_ENABLE_WD 0x01
+#define DROP_COUNT_RESET 0x01
+#define USB_CH_DET 0x01
#define MAIN_CH_INPUT_CURR_SHIFT 4
#define VBUS_IN_CURR_LIM_SHIFT 4
+#define AB8540_VBUS_IN_CURR_LIM_SHIFT 2
+#define AUTO_VBUS_IN_CURR_LIM_SHIFT 4
+#define AB8540_AUTO_VBUS_IN_CURR_MASK 0x3F
+#define VBUS_IN_CURR_LIM_RETRY_SET_TIME 30 /* seconds */
#define LED_INDICATOR_PWM_ENA 0x01
#define LED_INDICATOR_PWM_DIS 0x00
@@ -68,9 +76,16 @@
#define MAIN_CH_NOK 0x01
#define VBUS_DET 0x80
+#define MAIN_CH_STATUS2_MAINCHGDROP 0x80
+#define MAIN_CH_STATUS2_MAINCHARGERDETDBNC 0x40
+#define USB_CH_VBUSDROP 0x40
+#define USB_CH_VBUSDETDBNC 0x01
+
/* UsbLineStatus register bit masks */
#define AB8500_USB_LINK_STATUS 0x78
+#define AB8505_USB_LINK_STATUS 0xF8
#define AB8500_STD_HOST_SUSP 0x18
+#define USB_LINK_STATUS_SHIFT 3
/* Watchdog timeout constant */
#define WD_TIMER 0x30 /* 4min */
@@ -79,6 +94,21 @@
/* Lowest charger voltage is 3.39V -> 0x4E */
#define LOW_VOLT_REG 0x4E
+/* Step up/down delay in us */
+#define STEP_UDELAY 1000
+
+#define CHARGER_STATUS_POLL 10 /* in ms */
+
+#define CHG_WD_INTERVAL (60 * HZ)
+
+#define AB8500_SW_CONTROL_FALLBACK 0x03
+/* Wait for enumeration before charing in us */
+#define WAIT_ACA_RID_ENUMERATION (5 * 1000)
+/*External charger control*/
+#define AB8500_SYS_CHARGER_CONTROL_REG 0x52
+#define EXTERNAL_CHARGER_DISABLE_REG_VAL 0x03
+#define EXTERNAL_CHARGER_ENABLE_REG_VAL 0x07
+
/* UsbLineStatus register - usb types */
enum ab8500_charger_link_status {
USB_STAT_NOT_CONFIGURED,
@@ -97,6 +127,13 @@ enum ab8500_charger_link_status {
USB_STAT_HM_IDGND,
USB_STAT_RESERVED,
USB_STAT_NOT_VALID_LINK,
+ USB_STAT_PHY_EN,
+ USB_STAT_SUP_NO_IDGND_VBUS,
+ USB_STAT_SUP_IDGND_VBUS,
+ USB_STAT_CHARGER_LINE_1,
+ USB_STAT_CARKIT_1,
+ USB_STAT_CARKIT_2,
+ USB_STAT_ACA_DOCK_CHARGER,
};
enum ab8500_usb_state {
@@ -149,6 +186,7 @@ struct ab8500_charger_info {
int charger_voltage;
int cv_active;
bool wd_expired;
+ int charger_current;
};
struct ab8500_charger_event_flags {
@@ -159,19 +197,26 @@ struct ab8500_charger_event_flags {
bool usbchargernotok;
bool chgwdexp;
bool vbus_collapse;
+ bool vbus_drop_end;
};
struct ab8500_charger_usb_state {
- bool usb_changed;
int usb_current;
+ int usb_current_tmp;
enum ab8500_usb_state state;
+ enum ab8500_usb_state state_tmp;
spinlock_t usb_lock;
};
+struct ab8500_charger_max_usb_in_curr {
+ int usb_type_max;
+ int set_max;
+ int calculated_max;
+};
+
/**
* struct ab8500_charger - ab8500 Charger device information
* @dev: Pointer to the structure device
- * @max_usb_in_curr: Max USB charger input current
* @vbus_detected: VBUS detected
* @vbus_detected_start:
* VBUS detected during startup
@@ -182,36 +227,50 @@ struct ab8500_charger_usb_state {
* charger is enabled
* @vbat Battery voltage
* @old_vbat Previously measured battery voltage
+ * @usb_device_is_unrecognised USB device is unrecognised by the hardware
* @autopower Indicate if we should have automatic pwron after pwrloss
* @autopower_cfg platform specific power config support for "pwron after pwrloss"
+ * @invalid_charger_detect_state State when forcing AB to use invalid charger
+ * @is_aca_rid: Incicate if accessory is ACA type
+ * @current_stepping_sessions:
+ * Counter for current stepping sessions
* @parent: Pointer to the struct ab8500
* @gpadc: Pointer to the struct gpadc
- * @bat: Pointer to the abx500_bm platform data
+ * @bm: Platform specific battery management information
* @flags: Structure for information about events triggered
* @usb_state: Structure for usb stack information
+ * @max_usb_in_curr: Max USB charger input current
* @ac_chg: AC charger power supply
* @usb_chg: USB charger power supply
* @ac: Structure that holds the AC charger properties
* @usb: Structure that holds the USB charger properties
* @regu: Pointer to the struct regulator
* @charger_wq: Work queue for the IRQs and checking HW state
+ * @usb_ipt_crnt_lock: Lock to protect VBUS input current setting from mutuals
+ * @pm_lock: Lock to prevent system to suspend
* @check_vbat_work Work for checking vbat threshold to adjust vbus current
* @check_hw_failure_work: Work for checking HW state
* @check_usbchgnotok_work: Work for checking USB charger not ok status
* @kick_wd_work: Work for kicking the charger watchdog in case
* of ABB rev 1.* due to the watchog logic bug
+ * @ac_charger_attached_work: Work for checking if AC charger is still
+ * connected
+ * @usb_charger_attached_work: Work for checking if USB charger is still
+ * connected
* @ac_work: Work for checking AC charger connection
* @detect_usb_type_work: Work for detecting the USB type connected
* @usb_link_status_work: Work for checking the new USB link status
* @usb_state_changed_work: Work for checking USB state
+ * @attach_work: Work for detecting USB type
+ * @vbus_drop_end_work: Work for detecting VBUS drop end
* @check_main_thermal_prot_work:
* Work for checking Main thermal status
* @check_usb_thermal_prot_work:
* Work for checking USB thermal status
+ * @charger_attached_mutex: For controlling the wakelock
*/
struct ab8500_charger {
struct device *dev;
- int max_usb_in_curr;
bool vbus_detected;
bool vbus_detected_start;
bool ac_conn;
@@ -219,31 +278,42 @@ struct ab8500_charger {
bool vddadc_en_usb;
int vbat;
int old_vbat;
+ bool usb_device_is_unrecognised;
bool autopower;
bool autopower_cfg;
+ int invalid_charger_detect_state;
+ int is_aca_rid;
+ atomic_t current_stepping_sessions;
struct ab8500 *parent;
struct ab8500_gpadc *gpadc;
- struct abx500_bm_data *bat;
+ struct abx500_bm_data *bm;
struct ab8500_charger_event_flags flags;
struct ab8500_charger_usb_state usb_state;
+ struct ab8500_charger_max_usb_in_curr max_usb_in_curr;
struct ux500_charger ac_chg;
struct ux500_charger usb_chg;
struct ab8500_charger_info ac;
struct ab8500_charger_info usb;
struct regulator *regu;
struct workqueue_struct *charger_wq;
+ struct mutex usb_ipt_crnt_lock;
struct delayed_work check_vbat_work;
struct delayed_work check_hw_failure_work;
struct delayed_work check_usbchgnotok_work;
struct delayed_work kick_wd_work;
+ struct delayed_work usb_state_changed_work;
+ struct delayed_work attach_work;
+ struct delayed_work ac_charger_attached_work;
+ struct delayed_work usb_charger_attached_work;
+ struct delayed_work vbus_drop_end_work;
struct work_struct ac_work;
struct work_struct detect_usb_type_work;
struct work_struct usb_link_status_work;
- struct work_struct usb_state_changed_work;
struct work_struct check_main_thermal_prot_work;
struct work_struct check_usb_thermal_prot_work;
struct usb_phy *usb_phy;
struct notifier_block nb;
+ struct mutex charger_attached_mutex;
};
/* AC properties */
@@ -267,50 +337,65 @@ static enum power_supply_property ab8500_charger_usb_props[] = {
POWER_SUPPLY_PROP_CURRENT_NOW,
};
-/**
- * ab8500_power_loss_handling - set how we handle powerloss.
- * @di: pointer to the ab8500_charger structure
- *
- * Magic nummbers are from STE HW department.
+/*
+ * Function for enabling and disabling sw fallback mode
+ * should always be disabled when no charger is connected.
*/
-static void ab8500_power_loss_handling(struct ab8500_charger *di)
+static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di,
+ bool fallback)
{
+ u8 val;
u8 reg;
+ u8 bank;
+ u8 bit;
int ret;
- dev_dbg(di->dev, "Autopower : %d\n", di->autopower);
+ dev_dbg(di->dev, "SW Fallback: %d\n", fallback);
- /* read the autopower register */
- ret = abx500_get_register_interruptible(di->dev, 0x15, 0x00, &reg);
- if (ret) {
- dev_err(di->dev, "%d write failed\n", __LINE__);
- return;
+ if (is_ab8500(di->parent)) {
+ bank = 0x15;
+ reg = 0x0;
+ bit = 3;
+ } else {
+ bank = AB8500_SYS_CTRL1_BLOCK;
+ reg = AB8500_SW_CONTROL_FALLBACK;
+ bit = 0;
}
- /* enable the OPT emulation registers */
- ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2);
- if (ret) {
- dev_err(di->dev, "%d write failed\n", __LINE__);
+ /* read the register containing fallback bit */
+ ret = abx500_get_register_interruptible(di->dev, bank, reg, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%d read failed\n", __LINE__);
return;
}
- if (di->autopower)
- reg |= 0x8;
+ if (is_ab8500(di->parent)) {
+ /* enable the OPT emulation registers */
+ ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ goto disable_otp;
+ }
+ }
+
+ if (fallback)
+ val |= (1 << bit);
else
- reg &= ~0x8;
+ val &= ~(1 << bit);
- /* write back the changed value to autopower reg */
- ret = abx500_set_register_interruptible(di->dev, 0x15, 0x00, reg);
+ /* write back the changed fallback bit value to register */
+ ret = abx500_set_register_interruptible(di->dev, bank, reg, val);
if (ret) {
dev_err(di->dev, "%d write failed\n", __LINE__);
- return;
}
- /* disable the set OTP registers again */
- ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0);
- if (ret) {
- dev_err(di->dev, "%d write failed\n", __LINE__);
- return;
+disable_otp:
+ if (is_ab8500(di->parent)) {
+ /* disable the set OTP registers again */
+ ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ }
}
}
@@ -329,12 +414,12 @@ static void ab8500_power_supply_changed(struct ab8500_charger *di,
!di->ac.charger_connected &&
di->autopower) {
di->autopower = false;
- ab8500_power_loss_handling(di);
+ ab8500_enable_disable_sw_fallback(di, false);
} else if (!di->autopower &&
(di->ac.charger_connected ||
di->usb.charger_connected)) {
di->autopower = true;
- ab8500_power_loss_handling(di);
+ ab8500_enable_disable_sw_fallback(di, true);
}
}
power_supply_changed(psy);
@@ -346,7 +431,25 @@ static void ab8500_charger_set_usb_connected(struct ab8500_charger *di,
if (connected != di->usb.charger_connected) {
dev_dbg(di->dev, "USB connected:%i\n", connected);
di->usb.charger_connected = connected;
+
+ if (!connected)
+ di->flags.vbus_drop_end = false;
+
sysfs_notify(&di->usb_chg.psy.dev->kobj, NULL, "present");
+
+ if (connected) {
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
+ if (is_ab8500(di->parent))
+ queue_delayed_work(di->charger_wq,
+ &di->usb_charger_attached_work,
+ HZ);
+ } else {
+ cancel_delayed_work_sync(&di->usb_charger_attached_work);
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+ }
}
}
@@ -500,6 +603,7 @@ static int ab8500_charger_usb_cv(struct ab8500_charger *di)
/**
* ab8500_charger_detect_chargers() - Detect the connected chargers
* @di: pointer to the ab8500_charger structure
+ * @probe: if probe, don't delay and wait for HW
*
* Returns the type of charger connected.
* For USB it will not mean we can actually charge from it
@@ -513,7 +617,7 @@ static int ab8500_charger_usb_cv(struct ab8500_charger *di)
* USB_PW_CONN if the USB power supply is connected
* AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected
*/
-static int ab8500_charger_detect_chargers(struct ab8500_charger *di)
+static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe)
{
int result = NO_PW_CONN;
int ret;
@@ -531,13 +635,25 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di)
result = AC_PW_CONN;
/* Check for USB charger */
+
+ if (!probe) {
+ /*
+ * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC100
+ * when disconnecting ACA even though no
+ * charger was connected. Try waiting a little
+ * longer than the 100 ms of VBUS_DET_DBNC100...
+ */
+ msleep(110);
+ }
ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
AB8500_CH_USBCH_STAT1_REG, &val);
if (ret < 0) {
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
}
-
+ dev_dbg(di->dev,
+ "%s AB8500_CH_USBCH_STAT1_REG %x\n", __func__,
+ val);
if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100))
result |= USB_PW_CONN;
@@ -554,75 +670,125 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di)
* Returns error code in case of failure else 0 on success
*/
static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
- enum ab8500_charger_link_status link_status)
+ enum ab8500_charger_link_status link_status)
{
int ret = 0;
+ di->usb_device_is_unrecognised = false;
+
+ /*
+ * Platform only supports USB 2.0.
+ * This means that charging current from USB source
+ * is maximum 500 mA. Every occurence of USB_STAT_*_HOST_*
+ * should set USB_CH_IP_CUR_LVL_0P5.
+ */
+
switch (link_status) {
case USB_STAT_STD_HOST_NC:
case USB_STAT_STD_HOST_C_NS:
case USB_STAT_STD_HOST_C_S:
dev_dbg(di->dev, "USB Type - Standard host is "
"detected through USB driver\n");
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 0;
break;
case USB_STAT_HOST_CHG_HS_CHIRP:
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 0;
break;
case USB_STAT_HOST_CHG_HS:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 0;
+ break;
case USB_STAT_ACA_RID_C_HS:
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P9;
+ di->is_aca_rid = 0;
break;
case USB_STAT_ACA_RID_A:
/*
* Dedicated charger level minus maximum current accessory
- * can consume (300mA). Closest level is 1100mA
+ * can consume (900mA). Closest level is 500mA
*/
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1;
+ dev_dbg(di->dev, "USB_STAT_ACA_RID_A detected\n");
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 1;
break;
case USB_STAT_ACA_RID_B:
/*
* Dedicated charger level minus 120mA (20mA for ACA and
* 100mA for potential accessory). Closest level is 1300mA
*/
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P3;
+ dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
+ di->max_usb_in_curr.usb_type_max);
+ di->is_aca_rid = 1;
break;
- case USB_STAT_DEDICATED_CHG:
case USB_STAT_HOST_CHG_NM:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 0;
+ break;
+ case USB_STAT_DEDICATED_CHG:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5;
+ di->is_aca_rid = 0;
+ break;
case USB_STAT_ACA_RID_C_HS_CHIRP:
case USB_STAT_ACA_RID_C_NM:
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5;
+ di->is_aca_rid = 1;
break;
- case USB_STAT_RESERVED:
- /*
- * This state is used to indicate that VBUS has dropped below
- * the detection level 4 times in a row. This is due to the
- * charger output current is set to high making the charger
- * voltage collapse. This have to be propagated through to
- * chargalg. This is done using the property
- * POWER_SUPPLY_PROP_CURRENT_AVG = 1
- */
- di->flags.vbus_collapse = true;
- dev_dbg(di->dev, "USB Type - USB_STAT_RESERVED "
- "VBUS has collapsed\n");
- ret = -1;
- break;
- case USB_STAT_HM_IDGND:
case USB_STAT_NOT_CONFIGURED:
- case USB_STAT_NOT_VALID_LINK:
+ if (di->vbus_detected) {
+ di->usb_device_is_unrecognised = true;
+ dev_dbg(di->dev, "USB Type - Legacy charger.\n");
+ di->max_usb_in_curr.usb_type_max =
+ USB_CH_IP_CUR_LVL_1P5;
+ break;
+ }
+ case USB_STAT_HM_IDGND:
dev_err(di->dev, "USB Type - Charging not allowed\n");
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
ret = -ENXIO;
break;
+ case USB_STAT_RESERVED:
+ if (is_ab8500(di->parent)) {
+ di->flags.vbus_collapse = true;
+ dev_err(di->dev, "USB Type - USB_STAT_RESERVED "
+ "VBUS has collapsed\n");
+ ret = -ENXIO;
+ break;
+ } else {
+ dev_dbg(di->dev, "USB Type - Charging not allowed\n");
+ di->max_usb_in_curr.usb_type_max =
+ USB_CH_IP_CUR_LVL_0P05;
+ dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
+ link_status,
+ di->max_usb_in_curr.usb_type_max);
+ ret = -ENXIO;
+ break;
+ }
+ case USB_STAT_CARKIT_1:
+ case USB_STAT_CARKIT_2:
+ case USB_STAT_ACA_DOCK_CHARGER:
+ case USB_STAT_CHARGER_LINE_1:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
+ di->max_usb_in_curr.usb_type_max);
+ break;
+ case USB_STAT_NOT_VALID_LINK:
+ dev_err(di->dev, "USB Type invalid - try charging anyway\n");
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ break;
+
default:
dev_err(di->dev, "USB Type - Unknown\n");
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
ret = -ENXIO;
break;
};
+ di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max;
dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
- link_status, di->max_usb_in_curr);
+ link_status, di->max_usb_in_curr.set_max);
return ret;
}
@@ -645,15 +811,22 @@ static int ab8500_charger_read_usb_type(struct ab8500_charger *di)
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
}
- ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
- AB8500_USB_LINE_STAT_REG, &val);
+ if (is_ab8500(di->parent))
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINE_STAT_REG, &val);
+ else
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val);
if (ret < 0) {
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
}
/* get the USB type */
- val = (val & AB8500_USB_LINK_STATUS) >> 3;
+ if (is_ab8500(di->parent))
+ val = (val & AB8500_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT;
+ else
+ val = (val & AB8505_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT;
ret = ab8500_charger_max_usb_curr(di,
(enum ab8500_charger_link_status) val);
@@ -682,16 +855,25 @@ static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
ret = abx500_get_register_interruptible(di->dev,
AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG,
&val);
+ dev_dbg(di->dev, "%s AB8500_IT_SOURCE21_REG %x\n",
+ __func__, val);
if (ret < 0) {
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
}
- ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
- AB8500_USB_LINE_STAT_REG, &val);
+
+ if (is_ab8500(di->parent))
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINE_STAT_REG, &val);
+ else
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val);
if (ret < 0) {
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
}
+ dev_dbg(di->dev, "%s AB8500_USB_LINE_STAT_REG %x\n", __func__,
+ val);
/*
* Until the IT source register is read the UsbLineStatus
* register is not updated, hence doing the same
@@ -699,7 +881,12 @@ static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
*/
/* get the USB type */
- val = (val & AB8500_USB_LINK_STATUS) >> 3;
+ if (is_ab8500(di->parent))
+ val = (val & AB8500_USB_LINK_STATUS) >>
+ USB_LINK_STATUS_SHIFT;
+ else
+ val = (val & AB8505_USB_LINK_STATUS) >>
+ USB_LINK_STATUS_SHIFT;
if (val)
break;
}
@@ -794,51 +981,6 @@ static int ab8500_charger_voltage_map[] = {
4600 ,
};
-/*
- * This array maps the raw hex value to charger current used by the AB8500
- * Values taken from the UM0836
- */
-static int ab8500_charger_current_map[] = {
- 100 ,
- 200 ,
- 300 ,
- 400 ,
- 500 ,
- 600 ,
- 700 ,
- 800 ,
- 900 ,
- 1000 ,
- 1100 ,
- 1200 ,
- 1300 ,
- 1400 ,
- 1500 ,
-};
-
-/*
- * This array maps the raw hex value to VBUS input current used by the AB8500
- * Values taken from the UM0836
- */
-static int ab8500_charger_vbus_in_curr_map[] = {
- USB_CH_IP_CUR_LVL_0P05,
- USB_CH_IP_CUR_LVL_0P09,
- USB_CH_IP_CUR_LVL_0P19,
- USB_CH_IP_CUR_LVL_0P29,
- USB_CH_IP_CUR_LVL_0P38,
- USB_CH_IP_CUR_LVL_0P45,
- USB_CH_IP_CUR_LVL_0P5,
- USB_CH_IP_CUR_LVL_0P6,
- USB_CH_IP_CUR_LVL_0P7,
- USB_CH_IP_CUR_LVL_0P8,
- USB_CH_IP_CUR_LVL_0P9,
- USB_CH_IP_CUR_LVL_1P0,
- USB_CH_IP_CUR_LVL_1P1,
- USB_CH_IP_CUR_LVL_1P3,
- USB_CH_IP_CUR_LVL_1P4,
- USB_CH_IP_CUR_LVL_1P5,
-};
-
static int ab8500_voltage_to_regval(int voltage)
{
int i;
@@ -860,41 +1002,41 @@ static int ab8500_voltage_to_regval(int voltage)
return -1;
}
-static int ab8500_current_to_regval(int curr)
+static int ab8500_current_to_regval(struct ab8500_charger *di, int curr)
{
int i;
- if (curr < ab8500_charger_current_map[0])
+ if (curr < di->bm->chg_output_curr[0])
return 0;
- for (i = 0; i < ARRAY_SIZE(ab8500_charger_current_map); i++) {
- if (curr < ab8500_charger_current_map[i])
+ for (i = 0; i < di->bm->n_chg_out_curr; i++) {
+ if (curr < di->bm->chg_output_curr[i])
return i - 1;
}
/* If not last element, return error */
- i = ARRAY_SIZE(ab8500_charger_current_map) - 1;
- if (curr == ab8500_charger_current_map[i])
+ i = di->bm->n_chg_out_curr - 1;
+ if (curr == di->bm->chg_output_curr[i])
return i;
else
return -1;
}
-static int ab8500_vbus_in_curr_to_regval(int curr)
+static int ab8500_vbus_in_curr_to_regval(struct ab8500_charger *di, int curr)
{
int i;
- if (curr < ab8500_charger_vbus_in_curr_map[0])
+ if (curr < di->bm->chg_input_curr[0])
return 0;
- for (i = 0; i < ARRAY_SIZE(ab8500_charger_vbus_in_curr_map); i++) {
- if (curr < ab8500_charger_vbus_in_curr_map[i])
+ for (i = 0; i < di->bm->n_chg_in_curr; i++) {
+ if (curr < di->bm->chg_input_curr[i])
return i - 1;
}
/* If not last element, return error */
- i = ARRAY_SIZE(ab8500_charger_vbus_in_curr_map) - 1;
- if (curr == ab8500_charger_vbus_in_curr_map[i])
+ i = di->bm->n_chg_in_curr - 1;
+ if (curr == di->bm->chg_input_curr[i])
return i;
else
return -1;
@@ -911,28 +1053,177 @@ static int ab8500_vbus_in_curr_to_regval(int curr)
*/
static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
{
+ int ret = 0;
switch (di->usb_state.usb_current) {
case 100:
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P09;
break;
case 200:
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P19;
break;
case 300:
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P29;
break;
case 400:
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P38;
break;
case 500:
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
break;
default:
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
- return -1;
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
+ ret = -EPERM;
break;
};
- return 0;
+ di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max;
+ return ret;
+}
+
+/**
+ * ab8500_charger_check_continue_stepping() - Check to allow stepping
+ * @di: pointer to the ab8500_charger structure
+ * @reg: select what charger register to check
+ *
+ * Check if current stepping should be allowed to continue.
+ * Checks if charger source has not collapsed. If it has, further stepping
+ * is not allowed.
+ */
+static bool ab8500_charger_check_continue_stepping(struct ab8500_charger *di,
+ int reg)
+{
+ if (reg == AB8500_USBCH_IPT_CRNTLVL_REG)
+ return !di->flags.vbus_drop_end;
+ else
+ return true;
+}
+
+/**
+ * ab8500_charger_set_current() - set charger current
+ * @di: pointer to the ab8500_charger structure
+ * @ich: charger current, in mA
+ * @reg: select what charger register to set
+ *
+ * Set charger current.
+ * There is no state machine in the AB to step up/down the charger
+ * current to avoid dips and spikes on MAIN, VBUS and VBAT when
+ * charging is started. Instead we need to implement
+ * this charger current step-up/down here.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_current(struct ab8500_charger *di,
+ int ich, int reg)
+{
+ int ret = 0;
+ int curr_index, prev_curr_index, shift_value, i;
+ u8 reg_value;
+ u32 step_udelay;
+ bool no_stepping = false;
+
+ atomic_inc(&di->current_stepping_sessions);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ reg, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s read failed\n", __func__);
+ goto exit_set_current;
+ }
+
+ switch (reg) {
+ case AB8500_MCH_IPT_CURLVL_REG:
+ shift_value = MAIN_CH_INPUT_CURR_SHIFT;
+ prev_curr_index = (reg_value >> shift_value);
+ curr_index = ab8500_current_to_regval(di, ich);
+ step_udelay = STEP_UDELAY;
+ if (!di->ac.charger_connected)
+ no_stepping = true;
+ break;
+ case AB8500_USBCH_IPT_CRNTLVL_REG:
+ if (is_ab8540(di->parent))
+ shift_value = AB8540_VBUS_IN_CURR_LIM_SHIFT;
+ else
+ shift_value = VBUS_IN_CURR_LIM_SHIFT;
+ prev_curr_index = (reg_value >> shift_value);
+ curr_index = ab8500_vbus_in_curr_to_regval(di, ich);
+ step_udelay = STEP_UDELAY * 100;
+
+ if (!di->usb.charger_connected)
+ no_stepping = true;
+ break;
+ case AB8500_CH_OPT_CRNTLVL_REG:
+ shift_value = 0;
+ prev_curr_index = (reg_value >> shift_value);
+ curr_index = ab8500_current_to_regval(di, ich);
+ step_udelay = STEP_UDELAY;
+ if (curr_index && (curr_index - prev_curr_index) > 1)
+ step_udelay *= 100;
+
+ if (!di->usb.charger_connected && !di->ac.charger_connected)
+ no_stepping = true;
+
+ break;
+ default:
+ dev_err(di->dev, "%s current register not valid\n", __func__);
+ ret = -ENXIO;
+ goto exit_set_current;
+ }
+
+ if (curr_index < 0) {
+ dev_err(di->dev, "requested current limit out-of-range\n");
+ ret = -ENXIO;
+ goto exit_set_current;
+ }
+
+ /* only update current if it's been changed */
+ if (prev_curr_index == curr_index) {
+ dev_dbg(di->dev, "%s current not changed for reg: 0x%02x\n",
+ __func__, reg);
+ ret = 0;
+ goto exit_set_current;
+ }
+
+ dev_dbg(di->dev, "%s set charger current: %d mA for reg: 0x%02x\n",
+ __func__, ich, reg);
+
+ if (no_stepping) {
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ reg, (u8)curr_index << shift_value);
+ if (ret)
+ dev_err(di->dev, "%s write failed\n", __func__);
+ } else if (prev_curr_index > curr_index) {
+ for (i = prev_curr_index - 1; i >= curr_index; i--) {
+ dev_dbg(di->dev, "curr change_1 to: %x for 0x%02x\n",
+ (u8) i << shift_value, reg);
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, reg, (u8)i << shift_value);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ goto exit_set_current;
+ }
+ if (i != curr_index)
+ usleep_range(step_udelay, step_udelay * 2);
+ }
+ } else {
+ bool allow = true;
+ for (i = prev_curr_index + 1; i <= curr_index && allow; i++) {
+ dev_dbg(di->dev, "curr change_2 to: %x for 0x%02x\n",
+ (u8)i << shift_value, reg);
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, reg, (u8)i << shift_value);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ goto exit_set_current;
+ }
+ if (i != curr_index)
+ usleep_range(step_udelay, step_udelay * 2);
+
+ allow = ab8500_charger_check_continue_stepping(di, reg);
+ }
+ }
+
+exit_set_current:
+ atomic_dec(&di->current_stepping_sessions);
+
+ return ret;
}
/**
@@ -946,12 +1237,16 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
int ich_in)
{
- int ret;
- int input_curr_index;
int min_value;
+ int ret;
/* We should always use to lowest current limit */
- min_value = min(di->bat->chg_params->usb_curr_max, ich_in);
+ min_value = min(di->bm->chg_params->usb_curr_max, ich_in);
+ if (di->max_usb_in_curr.set_max > 0)
+ min_value = min(di->max_usb_in_curr.set_max, min_value);
+
+ if (di->usb_state.usb_current >= 0)
+ min_value = min(di->usb_state.usb_current, min_value);
switch (min_value) {
case 100:
@@ -966,22 +1261,47 @@ static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
break;
}
- input_curr_index = ab8500_vbus_in_curr_to_regval(min_value);
- if (input_curr_index < 0) {
- dev_err(di->dev, "VBUS input current limit too high\n");
- return -ENXIO;
- }
+ dev_info(di->dev, "VBUS input current limit set to %d mA\n", min_value);
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_USBCH_IPT_CRNTLVL_REG,
- input_curr_index << VBUS_IN_CURR_LIM_SHIFT);
- if (ret)
- dev_err(di->dev, "%s write failed\n", __func__);
+ mutex_lock(&di->usb_ipt_crnt_lock);
+ ret = ab8500_charger_set_current(di, min_value,
+ AB8500_USBCH_IPT_CRNTLVL_REG);
+ mutex_unlock(&di->usb_ipt_crnt_lock);
return ret;
}
/**
+ * ab8500_charger_set_main_in_curr() - set main charger input current
+ * @di: pointer to the ab8500_charger structure
+ * @ich_in: input charger current, in mA
+ *
+ * Set main charger input current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_main_in_curr(struct ab8500_charger *di,
+ int ich_in)
+{
+ return ab8500_charger_set_current(di, ich_in,
+ AB8500_MCH_IPT_CURLVL_REG);
+}
+
+/**
+ * ab8500_charger_set_output_curr() - set charger output current
+ * @di: pointer to the ab8500_charger structure
+ * @ich_out: output charger current, in mA
+ *
+ * Set charger output current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_output_curr(struct ab8500_charger *di,
+ int ich_out)
+{
+ return ab8500_charger_set_current(di, ich_out,
+ AB8500_CH_OPT_CRNTLVL_REG);
+}
+
+/**
* ab8500_charger_led_en() - turn on/off chargign led
* @di: pointer to the ab8500_charger structure
* @on: flag to turn on/off the chargign led
@@ -1066,15 +1386,19 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
* the GPADC module independant of the AB8500 chargers
*/
if (!di->vddadc_en_ac) {
- regulator_enable(di->regu);
- di->vddadc_en_ac = true;
+ ret = regulator_enable(di->regu);
+ if (ret)
+ dev_warn(di->dev,
+ "Failed to enable regulator\n");
+ else
+ di->vddadc_en_ac = true;
}
/* Check if the requested voltage or current is valid */
volt_index = ab8500_voltage_to_regval(vset);
- curr_index = ab8500_current_to_regval(iset);
- input_curr_index = ab8500_current_to_regval(
- di->bat->chg_params->ac_curr_max);
+ curr_index = ab8500_current_to_regval(di, iset);
+ input_curr_index = ab8500_current_to_regval(di,
+ di->bm->chg_params->ac_curr_max);
if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) {
dev_err(di->dev,
"Charger voltage or current too high, "
@@ -1090,23 +1414,24 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
return ret;
}
/* MainChInputCurr: current that can be drawn from the charger*/
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_MCH_IPT_CURLVL_REG,
- input_curr_index << MAIN_CH_INPUT_CURR_SHIFT);
+ ret = ab8500_charger_set_main_in_curr(di,
+ di->bm->chg_params->ac_curr_max);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s Failed to set MainChInputCurr\n",
+ __func__);
return ret;
}
/* ChOutputCurentLevel: protected output current */
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+ ret = ab8500_charger_set_output_curr(di, iset);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
/* Check if VBAT overshoot control should be enabled */
- if (!di->bat->enable_overshoot)
+ if (!di->bm->enable_overshoot)
overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N;
/* Enable Main Charger */
@@ -1158,12 +1483,11 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
return ret;
}
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1);
+ ret = ab8500_charger_set_output_curr(di, 0);
if (ret) {
- dev_err(di->dev,
- "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
} else {
@@ -1235,8 +1559,12 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
* the GPADC module independant of the AB8500 chargers
*/
if (!di->vddadc_en_usb) {
- regulator_enable(di->regu);
- di->vddadc_en_usb = true;
+ ret = regulator_enable(di->regu);
+ if (ret)
+ dev_warn(di->dev,
+ "Failed to enable regulator\n");
+ else
+ di->vddadc_en_usb = true;
}
/* Enable USB charging */
@@ -1244,7 +1572,7 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
/* Check if the requested voltage or current is valid */
volt_index = ab8500_voltage_to_regval(vset);
- curr_index = ab8500_current_to_regval(ich_out);
+ curr_index = ab8500_current_to_regval(di, ich_out);
if (volt_index < 0 || curr_index < 0) {
dev_err(di->dev,
"Charger voltage or current too high, "
@@ -1259,24 +1587,13 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
dev_err(di->dev, "%s write failed\n", __func__);
return ret;
}
- /* USBChInputCurr: current that can be drawn from the usb */
- ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
- if (ret) {
- dev_err(di->dev, "setting USBChInputCurr failed\n");
- return ret;
- }
- /* ChOutputCurentLevel: protected output current */
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
- if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
- return ret;
- }
/* Check if VBAT overshoot control should be enabled */
- if (!di->bat->enable_overshoot)
+ if (!di->bm->enable_overshoot)
overshoot = USB_CHG_NO_OVERSHOOT_ENA_N;
/* Enable USB Charger */
+ dev_dbg(di->dev,
+ "Enabling USB with write to AB8500_USBCH_CTRL1_REG\n");
ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot);
if (ret) {
@@ -1289,11 +1606,30 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
if (ret < 0)
dev_err(di->dev, "failed to enable LED\n");
+ di->usb.charger_online = 1;
+
+ /* USBChInputCurr: current that can be drawn from the usb */
+ ret = ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr.usb_type_max);
+ if (ret) {
+ dev_err(di->dev, "setting USBChInputCurr failed\n");
+ return ret;
+ }
+
+ /* ChOutputCurentLevel: protected output current */
+ ret = ab8500_charger_set_output_curr(di, ich_out);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
+ return ret;
+ }
+
queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ);
- di->usb.charger_online = 1;
} else {
/* Disable USB charging */
+ dev_dbg(di->dev, "%s Disabled USB charging\n", __func__);
ret = abx500_set_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_USBCH_CTRL1_REG, 0);
@@ -1306,7 +1642,21 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
ret = ab8500_charger_led_en(di, false);
if (ret < 0)
dev_err(di->dev, "failed to disable LED\n");
+ /* USBChInputCurr: current that can be drawn from the usb */
+ ret = ab8500_charger_set_vbus_in_curr(di, 0);
+ if (ret) {
+ dev_err(di->dev, "setting USBChInputCurr failed\n");
+ return ret;
+ }
+ /* ChOutputCurentLevel: protected output current */
+ ret = ab8500_charger_set_output_curr(di, 0);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to reset ChOutputCurentLevel\n",
+ __func__);
+ return ret;
+ }
di->usb.charger_online = 0;
di->usb.wd_expired = false;
@@ -1319,8 +1669,7 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
dev_dbg(di->dev, "%s Disabled USB charging\n", __func__);
/* Cancel any pending Vbat check work */
- if (delayed_work_pending(&di->check_vbat_work))
- cancel_delayed_work(&di->check_vbat_work);
+ cancel_delayed_work(&di->check_vbat_work);
}
ab8500_power_supply_changed(di, &di->usb_chg.psy);
@@ -1328,6 +1677,128 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
return ret;
}
+static int ab8500_external_charger_prepare(struct notifier_block *charger_nb,
+ unsigned long event, void *data)
+{
+ int ret;
+ struct device *dev = data;
+ /*Toggle External charger control pin*/
+ ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK,
+ AB8500_SYS_CHARGER_CONTROL_REG,
+ EXTERNAL_CHARGER_DISABLE_REG_VAL);
+ if (ret < 0) {
+ dev_err(dev, "write reg failed %d\n", ret);
+ goto out;
+ }
+ ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK,
+ AB8500_SYS_CHARGER_CONTROL_REG,
+ EXTERNAL_CHARGER_ENABLE_REG_VAL);
+ if (ret < 0)
+ dev_err(dev, "Write reg failed %d\n", ret);
+
+out:
+ return ret;
+}
+
+/**
+ * ab8500_charger_usb_check_enable() - enable usb charging
+ * @charger: pointer to the ux500_charger structure
+ * @vset: charging voltage
+ * @iset: charger output current
+ *
+ * Check if the VBUS charger has been disconnected and reconnected without
+ * AB8500 rising an interrupt. Returns 0 on success.
+ */
+static int ab8500_charger_usb_check_enable(struct ux500_charger *charger,
+ int vset, int iset)
+{
+ u8 usbch_ctrl1 = 0;
+ int ret = 0;
+
+ struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger);
+
+ if (!di->usb.charger_connected)
+ return ret;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_USBCH_CTRL1_REG, &usbch_ctrl1);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+ return ret;
+ }
+ dev_dbg(di->dev, "USB charger ctrl: 0x%02x\n", usbch_ctrl1);
+
+ if (!(usbch_ctrl1 & USB_CH_ENA)) {
+ dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n");
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CHARGER_CTRL,
+ DROP_COUNT_RESET, DROP_COUNT_RESET);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 write failed %d\n", __LINE__);
+ return ret;
+ }
+
+ ret = ab8500_charger_usb_en(&di->usb_chg, true, vset, iset);
+ if (ret < 0) {
+ dev_err(di->dev, "Failed to enable VBUS charger %d\n",
+ __LINE__);
+ return ret;
+ }
+ }
+ return ret;
+}
+
+/**
+ * ab8500_charger_ac_check_enable() - enable usb charging
+ * @charger: pointer to the ux500_charger structure
+ * @vset: charging voltage
+ * @iset: charger output current
+ *
+ * Check if the AC charger has been disconnected and reconnected without
+ * AB8500 rising an interrupt. Returns 0 on success.
+ */
+static int ab8500_charger_ac_check_enable(struct ux500_charger *charger,
+ int vset, int iset)
+{
+ u8 mainch_ctrl1 = 0;
+ int ret = 0;
+
+ struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger);
+
+ if (!di->ac.charger_connected)
+ return ret;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_MCH_CTRL1, &mainch_ctrl1);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+ return ret;
+ }
+ dev_dbg(di->dev, "AC charger ctrl: 0x%02x\n", mainch_ctrl1);
+
+ if (!(mainch_ctrl1 & MAIN_CH_ENA)) {
+ dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n");
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CHARGER_CTRL,
+ DROP_COUNT_RESET, DROP_COUNT_RESET);
+
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 write failed %d\n", __LINE__);
+ return ret;
+ }
+
+ ret = ab8500_charger_ac_en(&di->usb_chg, true, vset, iset);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to enable AC charger %d\n",
+ __LINE__);
+ return ret;
+ }
+ }
+ return ret;
+}
+
/**
* ab8500_charger_watchdog_kick() - kick charger watchdog
* @di: pointer to the ab8500_charger structure
@@ -1366,7 +1837,6 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger,
int ich_out)
{
int ret;
- int curr_index;
struct ab8500_charger *di;
if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
@@ -1376,25 +1846,78 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger,
else
return -ENXIO;
- curr_index = ab8500_current_to_regval(ich_out);
- if (curr_index < 0) {
- dev_err(di->dev,
- "Charger current too high, "
- "charging not started\n");
- return -ENXIO;
+ ret = ab8500_charger_set_output_curr(di, ich_out);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
+ return ret;
}
+ /* Reset the main and usb drop input current measurement counter */
ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+ AB8500_CHARGER_CTRL, DROP_COUNT_RESET);
if (ret) {
dev_err(di->dev, "%s write failed\n", __func__);
return ret;
}
- /* Reset the main and usb drop input current measurement counter */
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_CHARGER_CTRL,
- 0x1);
+ return ret;
+}
+
+/**
+ * ab8540_charger_power_path_enable() - enable usb power path mode
+ * @charger: pointer to the ux500_charger structure
+ * @enable: enable/disable flag
+ *
+ * Enable or disable the power path for usb mode
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8540_charger_power_path_enable(struct ux500_charger *charger,
+ bool enable)
+{
+ int ret;
+ struct ab8500_charger *di;
+
+ if (charger->psy.type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab8500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8540_USB_PP_MODE_REG,
+ BUS_POWER_PATH_MODE_ENA, enable);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+
+/**
+ * ab8540_charger_usb_pre_chg_enable() - enable usb pre change
+ * @charger: pointer to the ux500_charger structure
+ * @enable: enable/disable flag
+ *
+ * Enable or disable the pre-chage for usb mode
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8540_charger_usb_pre_chg_enable(struct ux500_charger *charger,
+ bool enable)
+{
+ int ret;
+ struct ab8500_charger *di;
+
+ if (charger->psy.type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab8500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8540_USB_PP_CHR_REG,
+ BUS_POWER_PATH_PRECHG_ENA, enable);
if (ret) {
dev_err(di->dev, "%s write failed\n", __func__);
return ret;
@@ -1482,9 +2005,10 @@ static void ab8500_charger_check_vbat_work(struct work_struct *work)
di->vbat > VBAT_TRESH_IP_CUR_RED))) {
dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d,"
- " old: %d\n", di->max_usb_in_curr, di->vbat,
- di->old_vbat);
- ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
+ " old: %d\n", di->max_usb_in_curr.usb_type_max,
+ di->vbat, di->old_vbat);
+ ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr.usb_type_max);
power_supply_changed(&di->usb_chg.psy);
}
@@ -1597,7 +2121,7 @@ static void ab8500_charger_ac_work(struct work_struct *work)
* synchronously, we have the check if the main charger is
* connected by reading the status register
*/
- ret = ab8500_charger_detect_chargers(di);
+ ret = ab8500_charger_detect_chargers(di, false);
if (ret < 0)
return;
@@ -1612,6 +2136,84 @@ static void ab8500_charger_ac_work(struct work_struct *work)
sysfs_notify(&di->ac_chg.psy.dev->kobj, NULL, "present");
}
+static void ab8500_charger_usb_attached_work(struct work_struct *work)
+{
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger,
+ usb_charger_attached_work.work);
+ int usbch = (USB_CH_VBUSDROP | USB_CH_VBUSDETDBNC);
+ int ret, i;
+ u8 statval;
+
+ for (i = 0; i < 10; i++) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_USBCH_STAT1_REG,
+ &statval);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+ goto reschedule;
+ }
+ if ((statval & usbch) != usbch)
+ goto reschedule;
+
+ msleep(CHARGER_STATUS_POLL);
+ }
+
+ ab8500_charger_usb_en(&di->usb_chg, 0, 0, 0);
+
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
+ return;
+
+reschedule:
+ queue_delayed_work(di->charger_wq,
+ &di->usb_charger_attached_work,
+ HZ);
+}
+
+static void ab8500_charger_ac_attached_work(struct work_struct *work)
+{
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger,
+ ac_charger_attached_work.work);
+ int mainch = (MAIN_CH_STATUS2_MAINCHGDROP |
+ MAIN_CH_STATUS2_MAINCHARGERDETDBNC);
+ int ret, i;
+ u8 statval;
+
+ for (i = 0; i < 10; i++) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_STATUS2_REG,
+ &statval);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+ goto reschedule;
+ }
+
+ if ((statval & mainch) != mainch)
+ goto reschedule;
+
+ msleep(CHARGER_STATUS_POLL);
+ }
+
+ ab8500_charger_ac_en(&di->ac_chg, 0, 0, 0);
+ queue_work(di->charger_wq, &di->ac_work);
+
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
+ return;
+
+reschedule:
+ queue_delayed_work(di->charger_wq,
+ &di->ac_charger_attached_work,
+ HZ);
+}
+
/**
* ab8500_charger_detect_usb_type_work() - work to detect USB type
* @work: Pointer to the work_struct structure
@@ -1630,16 +2232,18 @@ static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
* synchronously, we have the check if is
* connected by reading the status register
*/
- ret = ab8500_charger_detect_chargers(di);
+ ret = ab8500_charger_detect_chargers(di, false);
if (ret < 0)
return;
if (!(ret & USB_PW_CONN)) {
- di->vbus_detected = 0;
+ dev_dbg(di->dev, "%s di->vbus_detected = false\n", __func__);
+ di->vbus_detected = false;
ab8500_charger_set_usb_connected(di, false);
ab8500_power_supply_changed(di, &di->usb_chg.psy);
} else {
- di->vbus_detected = 1;
+ dev_dbg(di->dev, "%s di->vbus_detected = true\n", __func__);
+ di->vbus_detected = true;
if (is_ab8500_1p1_or_earlier(di->parent)) {
ret = ab8500_charger_detect_usb_type(di);
@@ -1649,7 +2253,8 @@ static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
&di->usb_chg.psy);
}
} else {
- /* For ABB cut2.0 and onwards we have an IRQ,
+ /*
+ * For ABB cut2.0 and onwards we have an IRQ,
* USB_LINK_STATUS that will be triggered when the USB
* link status changes. The exception is USB connected
* during startup. Then we don't get a
@@ -1670,6 +2275,30 @@ static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
}
/**
+ * ab8500_charger_usb_link_attach_work() - work to detect USB type
+ * @work: pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+static void ab8500_charger_usb_link_attach_work(struct work_struct *work)
+{
+ struct ab8500_charger *di =
+ container_of(work, struct ab8500_charger, attach_work.work);
+ int ret;
+
+ /* Update maximum input current if USB enumeration is not detected */
+ if (!di->usb.charger_online) {
+ ret = ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr.usb_type_max);
+ if (ret)
+ return;
+ }
+
+ ab8500_charger_set_usb_connected(di, true);
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+}
+
+/**
* ab8500_charger_usb_link_status_work() - work to detect USB type
* @work: pointer to the work_struct structure
*
@@ -1677,7 +2306,10 @@ static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
*/
static void ab8500_charger_usb_link_status_work(struct work_struct *work)
{
+ int detected_chargers;
int ret;
+ u8 val;
+ u8 link_status;
struct ab8500_charger *di = container_of(work,
struct ab8500_charger, usb_link_status_work);
@@ -1687,31 +2319,118 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work)
* synchronously, we have the check if is
* connected by reading the status register
*/
- ret = ab8500_charger_detect_chargers(di);
- if (ret < 0)
+ detected_chargers = ab8500_charger_detect_chargers(di, false);
+ if (detected_chargers < 0)
return;
- if (!(ret & USB_PW_CONN)) {
- di->vbus_detected = 0;
+ /*
+ * Some chargers that breaks the USB spec is
+ * identified as invalid by AB8500 and it refuse
+ * to start the charging process. but by jumping
+ * thru a few hoops it can be forced to start.
+ */
+ if (is_ab8500(di->parent))
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINE_STAT_REG, &val);
+ else
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINK1_STAT_REG, &val);
+
+ if (ret >= 0)
+ dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val);
+ else
+ dev_dbg(di->dev, "Error reading USB link status\n");
+
+ if (is_ab8500(di->parent))
+ link_status = AB8500_USB_LINK_STATUS;
+ else
+ link_status = AB8505_USB_LINK_STATUS;
+
+ if (detected_chargers & USB_PW_CONN) {
+ if (((val & link_status) >> USB_LINK_STATUS_SHIFT) ==
+ USB_STAT_NOT_VALID_LINK &&
+ di->invalid_charger_detect_state == 0) {
+ dev_dbg(di->dev,
+ "Invalid charger detected, state= 0\n");
+ /*Enable charger*/
+ abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_USBCH_CTRL1_REG,
+ USB_CH_ENA, USB_CH_ENA);
+ /*Enable charger detection*/
+ abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINE_CTRL2_REG,
+ USB_CH_DET, USB_CH_DET);
+ di->invalid_charger_detect_state = 1;
+ /*exit and wait for new link status interrupt.*/
+ return;
+
+ }
+ if (di->invalid_charger_detect_state == 1) {
+ dev_dbg(di->dev,
+ "Invalid charger detected, state= 1\n");
+ /*Stop charger detection*/
+ abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINE_CTRL2_REG,
+ USB_CH_DET, 0x00);
+ /*Check link status*/
+ if (is_ab8500(di->parent))
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINE_STAT_REG,
+ &val);
+ else
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINK1_STAT_REG,
+ &val);
+
+ dev_dbg(di->dev, "USB link status= 0x%02x\n",
+ (val & link_status) >> USB_LINK_STATUS_SHIFT);
+ di->invalid_charger_detect_state = 2;
+ }
+ } else {
+ di->invalid_charger_detect_state = 0;
+ }
+
+ if (!(detected_chargers & USB_PW_CONN)) {
+ di->vbus_detected = false;
ab8500_charger_set_usb_connected(di, false);
ab8500_power_supply_changed(di, &di->usb_chg.psy);
- } else {
- di->vbus_detected = 1;
- ret = ab8500_charger_read_usb_type(di);
- if (!ret) {
- /* Update maximum input current */
- ret = ab8500_charger_set_vbus_in_curr(di,
- di->max_usb_in_curr);
- if (ret)
- return;
+ return;
+ }
- ab8500_charger_set_usb_connected(di, true);
- ab8500_power_supply_changed(di, &di->usb_chg.psy);
- } else if (ret == -ENXIO) {
+ dev_dbg(di->dev,"%s di->vbus_detected = true\n",__func__);
+ di->vbus_detected = true;
+ ret = ab8500_charger_read_usb_type(di);
+ if (ret) {
+ if (ret == -ENXIO) {
/* No valid charger type detected */
ab8500_charger_set_usb_connected(di, false);
ab8500_power_supply_changed(di, &di->usb_chg.psy);
}
+ return;
+ }
+
+ if (di->usb_device_is_unrecognised) {
+ dev_dbg(di->dev,
+ "Potential Legacy Charger device. "
+ "Delay work for %d msec for USB enum "
+ "to finish",
+ WAIT_ACA_RID_ENUMERATION);
+ queue_delayed_work(di->charger_wq,
+ &di->attach_work,
+ msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION));
+ } else if (di->is_aca_rid == 1) {
+ /* Only wait once */
+ di->is_aca_rid++;
+ dev_dbg(di->dev,
+ "%s Wait %d msec for USB enum to finish",
+ __func__, WAIT_ACA_RID_ENUMERATION);
+ queue_delayed_work(di->charger_wq,
+ &di->attach_work,
+ msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION));
+ } else {
+ queue_delayed_work(di->charger_wq,
+ &di->attach_work,
+ 0);
}
}
@@ -1721,24 +2440,20 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
unsigned long flags;
struct ab8500_charger *di = container_of(work,
- struct ab8500_charger, usb_state_changed_work);
+ struct ab8500_charger, usb_state_changed_work.work);
- if (!di->vbus_detected)
+ if (!di->vbus_detected) {
+ dev_dbg(di->dev,
+ "%s !di->vbus_detected\n",
+ __func__);
return;
+ }
spin_lock_irqsave(&di->usb_state.usb_lock, flags);
- di->usb_state.usb_changed = false;
+ di->usb_state.state = di->usb_state.state_tmp;
+ di->usb_state.usb_current = di->usb_state.usb_current_tmp;
spin_unlock_irqrestore(&di->usb_state.usb_lock, flags);
- /*
- * wait for some time until you get updates from the usb stack
- * and negotiations are completed
- */
- msleep(250);
-
- if (di->usb_state.usb_changed)
- return;
-
dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n",
__func__, di->usb_state.state, di->usb_state.usb_current);
@@ -1766,7 +2481,7 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
if (!ab8500_charger_get_usb_cur(di)) {
/* Update maximum input current */
ret = ab8500_charger_set_vbus_in_curr(di,
- di->max_usb_in_curr);
+ di->max_usb_in_curr.usb_type_max);
if (ret)
return;
@@ -1892,6 +2607,10 @@ static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di)
dev_dbg(di->dev, "Main charger unplugged\n");
queue_work(di->charger_wq, &di->ac_work);
+ cancel_delayed_work_sync(&di->ac_charger_attached_work);
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
return IRQ_HANDLED;
}
@@ -1909,6 +2628,13 @@ static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di)
dev_dbg(di->dev, "Main charger plugged\n");
queue_work(di->charger_wq, &di->ac_work);
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
+ if (is_ab8500(di->parent))
+ queue_delayed_work(di->charger_wq,
+ &di->ac_charger_attached_work,
+ HZ);
return IRQ_HANDLED;
}
@@ -1971,6 +2697,60 @@ static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di)
return IRQ_HANDLED;
}
+static void ab8500_charger_vbus_drop_end_work(struct work_struct *work)
+{
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, vbus_drop_end_work.work);
+ int ret, curr;
+ u8 reg_value;
+
+ di->flags.vbus_drop_end = false;
+
+ /* Reset the drop counter */
+ abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CHARGER_CTRL, 0x01);
+
+ if (is_ab8540(di->parent))
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8540_CH_USBCH_STAT3_REG, &reg_value);
+ else
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_USBCH_STAT2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s read failed\n", __func__);
+ return;
+ }
+
+ if (is_ab8540(di->parent))
+ curr = di->bm->chg_input_curr[
+ reg_value & AB8540_AUTO_VBUS_IN_CURR_MASK];
+ else
+ curr = di->bm->chg_input_curr[
+ reg_value >> AUTO_VBUS_IN_CURR_LIM_SHIFT];
+
+ if (di->max_usb_in_curr.calculated_max != curr) {
+ /* USB source is collapsing */
+ di->max_usb_in_curr.calculated_max = curr;
+ dev_dbg(di->dev,
+ "VBUS input current limiting to %d mA\n",
+ di->max_usb_in_curr.calculated_max);
+ } else {
+ /*
+ * USB source can not give more than this amount.
+ * Taking more will collapse the source.
+ */
+ di->max_usb_in_curr.set_max =
+ di->max_usb_in_curr.calculated_max;
+ dev_dbg(di->dev,
+ "VBUS input current limited to %d mA\n",
+ di->max_usb_in_curr.set_max);
+ }
+
+ if (di->usb.charger_connected)
+ ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr.usb_type_max);
+}
+
/**
* ab8500_charger_vbusdetf_handler() - VBUS falling detected
* @irq: interrupt number
@@ -1982,6 +2762,7 @@ static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di)
{
struct ab8500_charger *di = _di;
+ di->vbus_detected = false;
dev_dbg(di->dev, "VBUS falling detected\n");
queue_work(di->charger_wq, &di->detect_usb_type_work);
@@ -2001,6 +2782,7 @@ static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di)
di->vbus_detected = true;
dev_dbg(di->dev, "VBUS rising detected\n");
+
queue_work(di->charger_wq, &di->detect_usb_type_work);
return IRQ_HANDLED;
@@ -2109,6 +2891,30 @@ static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di)
}
/**
+ * ab8500_charger_vbuschdropend_handler() - VBUS drop removed
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbuschdropend_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "VBUS charger drop ended\n");
+ di->flags.vbus_drop_end = true;
+
+ /*
+ * VBUS might have dropped due to bad connection.
+ * Schedule a new input limit set to the value SW requests.
+ */
+ queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work,
+ round_jiffies(VBUS_IN_CURR_LIM_RETRY_SET_TIME * HZ));
+
+ return IRQ_HANDLED;
+}
+
+/**
* ab8500_charger_vbusovv_handler() - VBUS overvoltage detected
* @irq: interrupt number
* @_di: pointer to the ab8500_charger structure
@@ -2148,6 +2954,7 @@ static int ab8500_charger_ac_get_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct ab8500_charger *di;
+ int ret;
di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy));
@@ -2169,7 +2976,10 @@ static int ab8500_charger_ac_get_property(struct power_supply *psy,
val->intval = di->ac.charger_connected;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- di->ac.charger_voltage = ab8500_charger_get_ac_voltage(di);
+ ret = ab8500_charger_get_ac_voltage(di);
+ if (ret >= 0)
+ di->ac.charger_voltage = ret;
+ /* On error, use previous value */
val->intval = di->ac.charger_voltage * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
@@ -2181,7 +2991,10 @@ static int ab8500_charger_ac_get_property(struct power_supply *psy,
val->intval = di->ac.cv_active;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
- val->intval = ab8500_charger_get_ac_current(di) * 1000;
+ ret = ab8500_charger_get_ac_current(di);
+ if (ret >= 0)
+ di->ac.charger_current = ret;
+ val->intval = di->ac.charger_current * 1000;
break;
default:
return -EINVAL;
@@ -2208,6 +3021,7 @@ static int ab8500_charger_usb_get_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct ab8500_charger *di;
+ int ret;
di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy));
@@ -2231,7 +3045,9 @@ static int ab8500_charger_usb_get_property(struct power_supply *psy,
val->intval = di->usb.charger_connected;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- di->usb.charger_voltage = ab8500_charger_get_vbus_voltage(di);
+ ret = ab8500_charger_get_vbus_voltage(di);
+ if (ret >= 0)
+ di->usb.charger_voltage = ret;
val->intval = di->usb.charger_voltage * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
@@ -2243,7 +3059,10 @@ static int ab8500_charger_usb_get_property(struct power_supply *psy,
val->intval = di->usb.cv_active;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
- val->intval = ab8500_charger_get_usb_current(di) * 1000;
+ ret = ab8500_charger_get_usb_current(di);
+ if (ret >= 0)
+ di->usb.charger_current = ret;
+ val->intval = di->usb.charger_current * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
/*
@@ -2271,6 +3090,7 @@ static int ab8500_charger_usb_get_property(struct power_supply *psy,
static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
{
int ret = 0;
+ u8 bup_vch_range = 0, vbup33_vrtcn = 0;
/* Setup maximum charger current and voltage for ABB cut2.0 */
if (!is_ab8500_1p1_or_earlier(di->parent)) {
@@ -2283,9 +3103,14 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
goto out;
}
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_MAX_REG, CH_OP_CUR_LVL_1P6);
+ if (is_ab8540(di->parent))
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG,
+ CH_OP_CUR_LVL_2P);
+ else
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG,
+ CH_OP_CUR_LVL_1P6);
if (ret) {
dev_err(di->dev,
"failed to set CH_OPT_CRNTLVL_MAX_REG\n");
@@ -2293,13 +3118,24 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
}
}
- /* VBUS OVV set to 6.3V and enable automatic current limitiation */
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER,
- AB8500_USBCH_CTRL2_REG,
- VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA);
+ if (is_ab9540_2p0(di->parent) || is_ab9540_3p0(di->parent)
+ || is_ab8505_2p0(di->parent) || is_ab8540(di->parent))
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL2_REG,
+ VBUS_AUTO_IN_CURR_LIM_ENA,
+ VBUS_AUTO_IN_CURR_LIM_ENA);
+ else
+ /*
+ * VBUS OVV set to 6.3V and enable automatic current limitation
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL2_REG,
+ VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA);
if (ret) {
- dev_err(di->dev, "failed to set VBUS OVV\n");
+ dev_err(di->dev,
+ "failed to set automatic current limitation\n");
goto out;
}
@@ -2355,16 +3191,37 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
goto out;
}
+ ret = ab8500_charger_led_en(di, false);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to disable LED\n");
+ goto out;
+ }
+
/* Backup battery voltage and current */
+ if (di->bm->bkup_bat_v > BUP_VCH_SEL_3P1V)
+ bup_vch_range = BUP_VCH_RANGE;
+ if (di->bm->bkup_bat_v == BUP_VCH_SEL_3P3V)
+ vbup33_vrtcn = VBUP33_VRTCN;
+
ret = abx500_set_register_interruptible(di->dev,
AB8500_RTC,
AB8500_RTC_BACKUP_CHG_REG,
- di->bat->bkup_bat_v |
- di->bat->bkup_bat_i);
+ (di->bm->bkup_bat_v & 0x3) | di->bm->bkup_bat_i);
if (ret) {
dev_err(di->dev, "failed to setup backup battery charging\n");
goto out;
}
+ if (is_ab8540(di->parent)) {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_CTRL1_REG,
+ bup_vch_range | vbup33_vrtcn);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to setup backup battery charging\n");
+ goto out;
+ }
+ }
/* Enable backup battery charging */
abx500_mask_and_set_register_interruptible(di->dev,
@@ -2373,6 +3230,25 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
if (ret < 0)
dev_err(di->dev, "%s mask and set failed\n", __func__);
+ if (is_ab8540(di->parent)) {
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8540_USB_PP_MODE_REG,
+ BUS_VSYS_VOL_SELECT_MASK, BUS_VSYS_VOL_SELECT_3P6V);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to setup usb power path vsys voltage\n");
+ goto out;
+ }
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8540_USB_PP_CHR_REG,
+ BUS_PP_PRECHG_CURRENT_MASK, 0);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to setup usb power path prechage current\n");
+ goto out;
+ }
+ }
+
out:
return ret;
}
@@ -2394,6 +3270,7 @@ static struct ab8500_charger_interrupts ab8500_charger_irq[] = {
{"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler},
{"VBUS_OVV", ab8500_charger_vbusovv_handler},
{"CH_WD_EXP", ab8500_charger_chwdexp_handler},
+ {"VBUS_CH_DROP_END", ab8500_charger_vbuschdropend_handler},
};
static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
@@ -2404,6 +3281,9 @@ static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
enum ab8500_usb_state bm_usb_state;
unsigned mA = *((unsigned *)power);
+ if (!di)
+ return NOTIFY_DONE;
+
if (event != USB_EVENT_VBUS) {
dev_dbg(di->dev, "not a standard host, returning\n");
return NOTIFY_DONE;
@@ -2427,13 +3307,15 @@ static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
__func__, bm_usb_state, mA);
spin_lock(&di->usb_state.usb_lock);
- di->usb_state.usb_changed = true;
+ di->usb_state.state_tmp = bm_usb_state;
+ di->usb_state.usb_current_tmp = mA;
spin_unlock(&di->usb_state.usb_lock);
- di->usb_state.state = bm_usb_state;
- di->usb_state.usb_current = mA;
-
- queue_work(di->charger_wq, &di->usb_state_changed_work);
+ /*
+ * wait for some time until you get updates from the usb stack
+ * and negotiations are completed
+ */
+ queue_delayed_work(di->charger_wq, &di->usb_state_changed_work, HZ/2);
return NOTIFY_OK;
}
@@ -2460,11 +3342,8 @@ static int ab8500_charger_resume(struct platform_device *pdev)
dev_err(di->dev, "Failed to kick WD!\n");
/* If not already pending start a new timer */
- if (!delayed_work_pending(
- &di->kick_wd_work)) {
- queue_delayed_work(di->charger_wq, &di->kick_wd_work,
- round_jiffies(WD_KICK_INTERVAL));
- }
+ queue_delayed_work(di->charger_wq, &di->kick_wd_work,
+ round_jiffies(WD_KICK_INTERVAL));
}
/* If we still have a HW failure, schedule a new check */
@@ -2473,6 +3352,9 @@ static int ab8500_charger_resume(struct platform_device *pdev)
&di->check_hw_failure_work, 0);
}
+ if (di->flags.vbus_drop_end)
+ queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work, 0);
+
return 0;
}
@@ -2481,9 +3363,23 @@ static int ab8500_charger_suspend(struct platform_device *pdev,
{
struct ab8500_charger *di = platform_get_drvdata(pdev);
- /* Cancel any pending HW failure check */
- if (delayed_work_pending(&di->check_hw_failure_work))
- cancel_delayed_work(&di->check_hw_failure_work);
+ /* Cancel any pending jobs */
+ cancel_delayed_work(&di->check_hw_failure_work);
+ cancel_delayed_work(&di->vbus_drop_end_work);
+
+ flush_delayed_work(&di->attach_work);
+ flush_delayed_work(&di->usb_charger_attached_work);
+ flush_delayed_work(&di->ac_charger_attached_work);
+ flush_delayed_work(&di->check_usbchgnotok_work);
+ flush_delayed_work(&di->check_vbat_work);
+ flush_delayed_work(&di->kick_wd_work);
+
+ flush_work(&di->usb_link_status_work);
+ flush_work(&di->ac_work);
+ flush_work(&di->detect_usb_type_work);
+
+ if (atomic_read(&di->current_stepping_sessions))
+ return -EAGAIN;
return 0;
}
@@ -2492,6 +3388,10 @@ static int ab8500_charger_suspend(struct platform_device *pdev,
#define ab8500_charger_resume NULL
#endif
+static struct notifier_block charger_nb = {
+ .notifier_call = ab8500_external_charger_prepare,
+};
+
static int ab8500_charger_remove(struct platform_device *pdev)
{
struct ab8500_charger *di = platform_get_drvdata(pdev);
@@ -2509,9 +3409,6 @@ static int ab8500_charger_remove(struct platform_device *pdev)
free_irq(irq, di);
}
- /* disable the regulator */
- regulator_put(di->regu);
-
/* Backup battery voltage and current disable */
ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0);
@@ -2524,10 +3421,17 @@ static int ab8500_charger_remove(struct platform_device *pdev)
/* Delete the work queue */
destroy_workqueue(di->charger_wq);
+ /* Unregister external charger enable notifier */
+ if (!di->ac_chg.enabled)
+ blocking_notifier_chain_unregister(
+ &charger_notifier_list, &charger_nb);
+
flush_scheduled_work();
- power_supply_unregister(&di->usb_chg.psy);
- power_supply_unregister(&di->ac_chg.psy);
- platform_set_drvdata(pdev, NULL);
+ if (di->usb_chg.enabled)
+ power_supply_unregister(&di->usb_chg.psy);
+
+ if (di->ac_chg.enabled && !di->ac_chg.external)
+ power_supply_unregister(&di->ac_chg.psy);
return 0;
}
@@ -2541,32 +3445,31 @@ static char *supply_interface[] = {
static int ab8500_charger_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ struct abx500_bm_data *plat = pdev->dev.platform_data;
struct ab8500_charger *di;
- int irq, i, charger_status, ret = 0;
+ int irq, i, charger_status, ret = 0, ch_stat;
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;
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no battery management data supplied\n");
+ return -EINVAL;
+ }
+ di->bm = plat;
+
+ if (np) {
+ ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get battery information\n");
+ return ret;
}
- } else {
- dev_info(&pdev->dev, "falling back to legacy platform data\n");
+ di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
+ } else
di->autopower_cfg = false;
- }
/* get parent data */
di->dev = &pdev->dev;
@@ -2575,8 +3478,10 @@ static int ab8500_charger_probe(struct platform_device *pdev)
/* initialize lock */
spin_lock_init(&di->usb_state.usb_lock);
+ mutex_init(&di->usb_ipt_crnt_lock);
di->autopower = false;
+ di->invalid_charger_detect_state = 0;
/* AC supply */
/* power_supply base class */
@@ -2589,12 +3494,21 @@ static int ab8500_charger_probe(struct platform_device *pdev)
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.check_enable = &ab8500_charger_ac_check_enable;
di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current;
di->ac_chg.max_out_volt = ab8500_charger_voltage_map[
ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
- di->ac_chg.max_out_curr = ab8500_charger_current_map[
- ARRAY_SIZE(ab8500_charger_current_map) - 1];
+ di->ac_chg.max_out_curr =
+ di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1];
+ di->ac_chg.wdt_refresh = CHG_WD_INTERVAL;
+ di->ac_chg.enabled = di->bm->ac_enabled;
+ di->ac_chg.external = false;
+
+ /*notifier for external charger enabling*/
+ if (!di->ac_chg.enabled)
+ blocking_notifier_chain_register(
+ &charger_notifier_list, &charger_nb);
/* USB supply */
/* power_supply base class */
@@ -2607,13 +3521,20 @@ static int ab8500_charger_probe(struct platform_device *pdev)
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.check_enable = &ab8500_charger_usb_check_enable;
di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current;
+ di->usb_chg.ops.pp_enable = &ab8540_charger_power_path_enable;
+ di->usb_chg.ops.pre_chg_enable = &ab8540_charger_usb_pre_chg_enable;
di->usb_chg.max_out_volt = ab8500_charger_voltage_map[
ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
- di->usb_chg.max_out_curr = ab8500_charger_current_map[
- ARRAY_SIZE(ab8500_charger_current_map) - 1];
-
+ di->usb_chg.max_out_curr =
+ di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1];
+ di->usb_chg.wdt_refresh = CHG_WD_INTERVAL;
+ di->usb_chg.enabled = di->bm->usb_enabled;
+ di->usb_chg.external = false;
+ di->usb_chg.power_path = di->bm->usb_power_path;
+ di->usb_state.usb_current = -1;
/* Create a work queue for the charger */
di->charger_wq =
@@ -2623,12 +3544,19 @@ static int ab8500_charger_probe(struct platform_device *pdev)
return -ENOMEM;
}
+ mutex_init(&di->charger_attached_mutex);
+
/* Init work for HW failure check */
INIT_DEFERRABLE_WORK(&di->check_hw_failure_work,
ab8500_charger_check_hw_failure_work);
INIT_DEFERRABLE_WORK(&di->check_usbchgnotok_work,
ab8500_charger_check_usbchargernotok_work);
+ INIT_DELAYED_WORK(&di->ac_charger_attached_work,
+ ab8500_charger_ac_attached_work);
+ INIT_DELAYED_WORK(&di->usb_charger_attached_work,
+ ab8500_charger_usb_attached_work);
+
/*
* For ABB revision 1.0 and 1.1 there is a bug in the watchdog
* logic. That means we have to continously kick the charger
@@ -2644,6 +3572,15 @@ static int ab8500_charger_probe(struct platform_device *pdev)
INIT_DEFERRABLE_WORK(&di->check_vbat_work,
ab8500_charger_check_vbat_work);
+ INIT_DELAYED_WORK(&di->attach_work,
+ ab8500_charger_usb_link_attach_work);
+
+ INIT_DELAYED_WORK(&di->usb_state_changed_work,
+ ab8500_charger_usb_state_changed_work);
+
+ INIT_DELAYED_WORK(&di->vbus_drop_end_work,
+ ab8500_charger_vbus_drop_end_work);
+
/* Init work for charger detection */
INIT_WORK(&di->usb_link_status_work,
ab8500_charger_usb_link_status_work);
@@ -2651,9 +3588,6 @@ static int ab8500_charger_probe(struct platform_device *pdev)
INIT_WORK(&di->detect_usb_type_work,
ab8500_charger_detect_usb_type_work);
- INIT_WORK(&di->usb_state_changed_work,
- ab8500_charger_usb_state_changed_work);
-
/* Init work for checking HW status */
INIT_WORK(&di->check_main_thermal_prot_work,
ab8500_charger_check_main_thermal_prot_work);
@@ -2665,7 +3599,7 @@ static int ab8500_charger_probe(struct platform_device *pdev)
* is a charger connected to avoid erroneous BTEMP_HIGH/LOW
* interrupts during charging
*/
- di->regu = regulator_get(di->dev, "vddadc");
+ di->regu = devm_regulator_get(di->dev, "vddadc");
if (IS_ERR(di->regu)) {
ret = PTR_ERR(di->regu);
dev_err(di->dev, "failed to get vddadc regulator\n");
@@ -2677,21 +3611,25 @@ static int ab8500_charger_probe(struct platform_device *pdev)
ret = ab8500_charger_init_hw_registers(di);
if (ret) {
dev_err(di->dev, "failed to initialize ABB registers\n");
- goto free_regulator;
+ goto free_charger_wq;
}
/* Register AC charger class */
- ret = power_supply_register(di->dev, &di->ac_chg.psy);
- if (ret) {
- dev_err(di->dev, "failed to register AC charger\n");
- goto free_regulator;
+ if (di->ac_chg.enabled) {
+ ret = power_supply_register(di->dev, &di->ac_chg.psy);
+ if (ret) {
+ dev_err(di->dev, "failed to register AC charger\n");
+ goto free_charger_wq;
+ }
}
/* Register USB charger class */
- ret = power_supply_register(di->dev, &di->usb_chg.psy);
- if (ret) {
- dev_err(di->dev, "failed to register USB charger\n");
- goto free_ac;
+ if (di->usb_chg.enabled) {
+ ret = power_supply_register(di->dev, &di->usb_chg.psy);
+ if (ret) {
+ dev_err(di->dev, "failed to register USB charger\n");
+ goto free_ac;
+ }
}
di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2);
@@ -2708,7 +3646,7 @@ static int ab8500_charger_probe(struct platform_device *pdev)
}
/* Identify the connected charger types during startup */
- charger_status = ab8500_charger_detect_chargers(di);
+ charger_status = ab8500_charger_detect_chargers(di, true);
if (charger_status & AC_PW_CONN) {
di->ac.charger_connected = 1;
di->ac_conn = true;
@@ -2717,7 +3655,6 @@ static int ab8500_charger_probe(struct platform_device *pdev)
}
if (charger_status & USB_PW_CONN) {
- dev_dbg(di->dev, "VBUS Detect during startup\n");
di->vbus_detected = true;
di->vbus_detected_start = true;
queue_work(di->charger_wq,
@@ -2742,6 +3679,25 @@ static int ab8500_charger_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, di);
+ mutex_lock(&di->charger_attached_mutex);
+
+ ch_stat = ab8500_charger_detect_chargers(di, false);
+
+ if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) {
+ if (is_ab8500(di->parent))
+ queue_delayed_work(di->charger_wq,
+ &di->ac_charger_attached_work,
+ HZ);
+ }
+ if ((ch_stat & USB_PW_CONN) == USB_PW_CONN) {
+ if (is_ab8500(di->parent))
+ queue_delayed_work(di->charger_wq,
+ &di->usb_charger_attached_work,
+ HZ);
+ }
+
+ mutex_unlock(&di->charger_attached_mutex);
+
return ret;
free_irq:
@@ -2755,11 +3711,11 @@ free_irq:
put_usb_phy:
usb_put_phy(di->usb_phy);
free_usb:
- power_supply_unregister(&di->usb_chg.psy);
+ if (di->usb_chg.enabled)
+ power_supply_unregister(&di->usb_chg.psy);
free_ac:
- power_supply_unregister(&di->ac_chg.psy);
-free_regulator:
- regulator_put(di->regu);
+ if (di->ac_chg.enabled)
+ power_supply_unregister(&di->ac_chg.psy);
free_charger_wq:
destroy_workqueue(di->charger_wq);
return ret;
diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c
index b3bf178c346..3cb4178e397 100644
--- a/drivers/power/ab8500_fg.c
+++ b/drivers/power/ab8500_fg.c
@@ -32,17 +32,18 @@
#include <linux/mfd/abx500/ab8500.h>
#include <linux/mfd/abx500/ab8500-bm.h>
#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/kernel.h>
#define MILLI_TO_MICRO 1000
#define FG_LSB_IN_MA 1627
-#define QLSB_NANO_AMP_HOURS_X10 1129
+#define QLSB_NANO_AMP_HOURS_X10 1071
#define INS_CURR_TIMEOUT (3 * HZ)
#define SEC_TO_SAMPLE(S) (S * 4)
#define NBR_AVG_SAMPLES 20
-#define LOW_BAT_CHECK_INTERVAL (2 * HZ)
+#define LOW_BAT_CHECK_INTERVAL (HZ / 16) /* 62.5 ms */
#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */
#define BATT_OK_MIN 2360 /* mV */
@@ -113,6 +114,13 @@ struct ab8500_fg_avg_cap {
int sum;
};
+struct ab8500_fg_cap_scaling {
+ bool enable;
+ int cap_to_scale[2];
+ int disable_cap_level;
+ int scaled_cap;
+};
+
struct ab8500_fg_battery_capacity {
int max_mah_design;
int max_mah;
@@ -123,6 +131,7 @@ struct ab8500_fg_battery_capacity {
int prev_percent;
int prev_level;
int user_mah;
+ struct ab8500_fg_cap_scaling cap_scale;
};
struct ab8500_fg_flags {
@@ -160,6 +169,8 @@ struct inst_curr_result_list {
* @recovery_cnt: Counter for recovery mode
* @high_curr_cnt: Counter for high current mode
* @init_cnt: Counter for init mode
+ * @low_bat_cnt Counter for number of consecutive low battery measures
+ * @nbr_cceoc_irq_cnt Counter for number of CCEOC irqs received since enabled
* @recovery_needed: Indicate if recovery is needed
* @high_curr_mode: Indicate if we're in high current mode
* @init_capacity: Indicate if initial capacity measuring should be done
@@ -167,13 +178,14 @@ struct inst_curr_result_list {
* @calib_state State during offset calibration
* @discharge_state: Current discharge state
* @charge_state: Current charge state
+ * @ab8500_fg_started Completion struct used for the instant current start
* @ab8500_fg_complete Completion struct used for the instant current reading
* @flags: Structure for information about events triggered
* @bat_cap: Structure for battery capacity specific parameters
* @avg_cap: Average capacity filter
* @parent: Pointer to the struct ab8500
* @gpadc: Pointer to the struct gpadc
- * @bat: Pointer to the abx500_bm platform data
+ * @bm: Platform specific battery management information
* @fg_psy: Structure that holds the FG specific battery properties
* @fg_wq: Work queue for running the FG algorithm
* @fg_periodic_work: Work to run the FG algorithm periodically
@@ -199,6 +211,8 @@ struct ab8500_fg {
int recovery_cnt;
int high_curr_cnt;
int init_cnt;
+ int low_bat_cnt;
+ int nbr_cceoc_irq_cnt;
bool recovery_needed;
bool high_curr_mode;
bool init_capacity;
@@ -206,13 +220,14 @@ struct ab8500_fg {
enum ab8500_fg_calibration_state calib_state;
enum ab8500_fg_discharge_state discharge_state;
enum ab8500_fg_charge_state charge_state;
+ struct completion ab8500_fg_started;
struct completion ab8500_fg_complete;
struct ab8500_fg_flags flags;
struct ab8500_fg_battery_capacity bat_cap;
struct ab8500_fg_avg_cap avg_cap;
struct ab8500 *parent;
struct ab8500_gpadc *gpadc;
- struct abx500_bm_data *bat;
+ struct abx500_bm_data *bm;
struct power_supply fg_psy;
struct workqueue_struct *fg_wq;
struct delayed_work fg_periodic_work;
@@ -355,7 +370,7 @@ static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr)
/*
* We want to know if we're in low current mode
*/
- if (curr > -di->bat->fg_params->high_curr_threshold)
+ if (curr > -di->bm->fg_params->high_curr_threshold)
return true;
else
return false;
@@ -484,8 +499,9 @@ static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable)
di->flags.fg_enabled = true;
} else {
/* Clear any pending read requests */
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0);
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ (RESET_ACCU | READ_REQ), 0);
if (ret)
goto cc_err;
@@ -523,13 +539,14 @@ cc_err:
* Note: This is part "one" and has to be called before
* ab8500_fg_inst_curr_finalize()
*/
- int ab8500_fg_inst_curr_start(struct ab8500_fg *di)
+int ab8500_fg_inst_curr_start(struct ab8500_fg *di)
{
u8 reg_val;
int ret;
mutex_lock(&di->cc_lock);
+ di->nbr_cceoc_irq_cnt = 0;
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8500_RTC_CC_CONF_REG, &reg_val);
if (ret < 0)
@@ -557,7 +574,8 @@ cc_err:
}
/* Return and WFI */
- INIT_COMPLETION(di->ab8500_fg_complete);
+ reinit_completion(&di->ab8500_fg_started);
+ reinit_completion(&di->ab8500_fg_complete);
enable_irq(di->irq);
/* Note: cc_lock is still locked */
@@ -568,6 +586,17 @@ fail:
}
/**
+ * ab8500_fg_inst_curr_started() - check if fg conversion has started
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns 1 if conversion started, 0 if still waiting
+ */
+int ab8500_fg_inst_curr_started(struct ab8500_fg *di)
+{
+ return completion_done(&di->ab8500_fg_started);
+}
+
+/**
* ab8500_fg_inst_curr_done() - check if fg conversion is done
* @di: pointer to the ab8500_fg structure
*
@@ -595,13 +624,15 @@ int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res)
int timeout;
if (!completion_done(&di->ab8500_fg_complete)) {
- timeout = wait_for_completion_timeout(&di->ab8500_fg_complete,
+ timeout = wait_for_completion_timeout(
+ &di->ab8500_fg_complete,
INS_CURR_TIMEOUT);
dev_dbg(di->dev, "Finalize time: %d ms\n",
((INS_CURR_TIMEOUT - timeout) * 1000) / HZ);
if (!timeout) {
ret = -ETIME;
disable_irq(di->irq);
+ di->nbr_cceoc_irq_cnt = 0;
dev_err(di->dev, "completion timed out [%d]\n",
__LINE__);
goto fail;
@@ -609,6 +640,7 @@ int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res)
}
disable_irq(di->irq);
+ di->nbr_cceoc_irq_cnt = 0;
ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
@@ -640,14 +672,14 @@ int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res)
/*
* Convert to unit value in mA
* Full scale input voltage is
- * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA
+ * 63.160mV => LSB = 63.160mV/(4096*res) = 1.542mA
* Given a 250ms conversion cycle time the LSB corresponds
- * to 112.9 nAh. Convert to current by dividing by the conversion
+ * to 107.1 nAh. Convert to current by dividing by the conversion
* time in hours (250ms = 1 / (3600 * 4)h)
- * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+ * 107.1nAh assumes 10mOhm, but fg_res is in 0.1mOhm
*/
val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) /
- (1000 * di->bat->fg_res);
+ (1000 * di->bm->fg_res);
if (di->turn_off_fg) {
dev_dbg(di->dev, "%s Disable FG\n", __func__);
@@ -683,6 +715,7 @@ fail:
int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di)
{
int ret;
+ int timeout;
int res = 0;
ret = ab8500_fg_inst_curr_start(di);
@@ -691,13 +724,33 @@ int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di)
return 0;
}
+ /* Wait for CC to actually start */
+ if (!completion_done(&di->ab8500_fg_started)) {
+ timeout = wait_for_completion_timeout(
+ &di->ab8500_fg_started,
+ INS_CURR_TIMEOUT);
+ dev_dbg(di->dev, "Start time: %d ms\n",
+ ((INS_CURR_TIMEOUT - timeout) * 1000) / HZ);
+ if (!timeout) {
+ ret = -ETIME;
+ dev_err(di->dev, "completion timed out [%d]\n",
+ __LINE__);
+ goto fail;
+ }
+ }
+
ret = ab8500_fg_inst_curr_finalize(di, &res);
if (ret) {
dev_err(di->dev, "Failed to finalize fg_inst\n");
return 0;
}
+ dev_dbg(di->dev, "%s instant current: %d", __func__, res);
return res;
+fail:
+ disable_irq(di->irq);
+ mutex_unlock(&di->cc_lock);
+ return ret;
}
/**
@@ -750,19 +803,16 @@ static void ab8500_fg_acc_cur_work(struct work_struct *work)
* 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
*/
di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10) /
- (100 * di->bat->fg_res);
+ (100 * di->bm->fg_res);
/*
* Convert to unit value in mA
- * Full scale input voltage is
- * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA
- * Given a 250ms conversion cycle time the LSB corresponds
- * to 112.9 nAh. Convert to current by dividing by the conversion
+ * by dividing by the conversion
* time in hours (= samples / (3600 * 4)h)
- * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+ * and multiply with 1000
*/
di->avg_curr = (val * QLSB_NANO_AMP_HOURS_X10 * 36) /
- (1000 * di->bat->fg_res * (di->fg_samples / 4));
+ (1000 * di->bm->fg_res * (di->fg_samples / 4));
di->flags.conv_done = true;
@@ -770,6 +820,8 @@ static void ab8500_fg_acc_cur_work(struct work_struct *work)
queue_work(di->fg_wq, &di->fg_work);
+ dev_dbg(di->dev, "fg_res: %d, fg_samples: %d, gasg: %d, accu_charge: %d \n",
+ di->bm->fg_res, di->fg_samples, val, di->accu_charge);
return;
exit:
dev_err(di->dev,
@@ -811,11 +863,11 @@ static int ab8500_fg_bat_voltage(struct ab8500_fg *di)
static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage)
{
int i, tbl_size;
- struct abx500_v_to_cap *tbl;
+ const struct abx500_v_to_cap *tbl;
int cap = 0;
- tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl,
- tbl_size = di->bat->bat_type[di->bat->batt_id].n_v_cap_tbl_elements;
+ tbl = di->bm->bat_type[di->bm->batt_id].v_to_cap_tbl,
+ tbl_size = di->bm->bat_type[di->bm->batt_id].n_v_cap_tbl_elements;
for (i = 0; i < tbl_size; ++i) {
if (voltage > tbl[i].voltage)
@@ -863,11 +915,11 @@ static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di)
static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
{
int i, tbl_size;
- struct batres_vs_temp *tbl;
+ const struct batres_vs_temp *tbl;
int resist = 0;
- tbl = di->bat->bat_type[di->bat->batt_id].batres_tbl;
- tbl_size = di->bat->bat_type[di->bat->batt_id].n_batres_tbl_elements;
+ tbl = di->bm->bat_type[di->bm->batt_id].batres_tbl;
+ tbl_size = di->bm->bat_type[di->bm->batt_id].n_batres_tbl_elements;
for (i = 0; i < tbl_size; ++i) {
if (di->bat_temp / 10 > tbl[i].temp)
@@ -888,11 +940,11 @@ static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d"
" fg resistance %d, total: %d (mOhm)\n",
- __func__, di->bat_temp, resist, di->bat->fg_res / 10,
- (di->bat->fg_res / 10) + resist);
+ __func__, di->bat_temp, resist, di->bm->fg_res / 10,
+ (di->bm->fg_res / 10) + resist);
/* fg_res variable is in 0.1mOhm */
- resist += di->bat->fg_res / 10;
+ resist += di->bm->fg_res / 10;
return resist;
}
@@ -915,7 +967,7 @@ static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di)
do {
vbat += ab8500_fg_bat_voltage(di);
i++;
- msleep(5);
+ usleep_range(5000, 6000);
} while (!ab8500_fg_inst_curr_done(di));
ab8500_fg_inst_curr_finalize(di, &di->inst_curr);
@@ -1108,16 +1160,16 @@ static int ab8500_fg_capacity_level(struct ab8500_fg *di)
{
int ret, percent;
- percent = di->bat_cap.permille / 10;
+ percent = DIV_ROUND_CLOSEST(di->bat_cap.permille, 10);
- if (percent <= di->bat->cap_levels->critical ||
+ if (percent <= di->bm->cap_levels->critical ||
di->flags.low_bat)
ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
- else if (percent <= di->bat->cap_levels->low)
+ else if (percent <= di->bm->cap_levels->low)
ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
- else if (percent <= di->bat->cap_levels->normal)
+ else if (percent <= di->bm->cap_levels->normal)
ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
- else if (percent <= di->bat->cap_levels->high)
+ else if (percent <= di->bm->cap_levels->high)
ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
else
ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
@@ -1126,6 +1178,99 @@ static int ab8500_fg_capacity_level(struct ab8500_fg *di)
}
/**
+ * ab8500_fg_calculate_scaled_capacity() - Capacity scaling
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Calculates the capacity to be shown to upper layers. Scales the capacity
+ * to have 100% as a reference from the actual capacity upon removal of charger
+ * when charging is in maintenance mode.
+ */
+static int ab8500_fg_calculate_scaled_capacity(struct ab8500_fg *di)
+{
+ struct ab8500_fg_cap_scaling *cs = &di->bat_cap.cap_scale;
+ int capacity = di->bat_cap.prev_percent;
+
+ if (!cs->enable)
+ return capacity;
+
+ /*
+ * As long as we are in fully charge mode scale the capacity
+ * to show 100%.
+ */
+ if (di->flags.fully_charged) {
+ cs->cap_to_scale[0] = 100;
+ cs->cap_to_scale[1] =
+ max(capacity, di->bm->fg_params->maint_thres);
+ dev_dbg(di->dev, "Scale cap with %d/%d\n",
+ cs->cap_to_scale[0], cs->cap_to_scale[1]);
+ }
+
+ /* Calculates the scaled capacity. */
+ if ((cs->cap_to_scale[0] != cs->cap_to_scale[1])
+ && (cs->cap_to_scale[1] > 0))
+ capacity = min(100,
+ DIV_ROUND_CLOSEST(di->bat_cap.prev_percent *
+ cs->cap_to_scale[0],
+ cs->cap_to_scale[1]));
+
+ if (di->flags.charging) {
+ if (capacity < cs->disable_cap_level) {
+ cs->disable_cap_level = capacity;
+ dev_dbg(di->dev, "Cap to stop scale lowered %d%%\n",
+ cs->disable_cap_level);
+ } else if (!di->flags.fully_charged) {
+ if (di->bat_cap.prev_percent >=
+ cs->disable_cap_level) {
+ dev_dbg(di->dev, "Disabling scaled capacity\n");
+ cs->enable = false;
+ capacity = di->bat_cap.prev_percent;
+ } else {
+ dev_dbg(di->dev,
+ "Waiting in cap to level %d%%\n",
+ cs->disable_cap_level);
+ capacity = cs->disable_cap_level;
+ }
+ }
+ }
+
+ return capacity;
+}
+
+/**
+ * ab8500_fg_update_cap_scalers() - Capacity scaling
+ * @di: pointer to the ab8500_fg structure
+ *
+ * To be called when state change from charge<->discharge to update
+ * the capacity scalers.
+ */
+static void ab8500_fg_update_cap_scalers(struct ab8500_fg *di)
+{
+ struct ab8500_fg_cap_scaling *cs = &di->bat_cap.cap_scale;
+
+ if (!cs->enable)
+ return;
+ if (di->flags.charging) {
+ di->bat_cap.cap_scale.disable_cap_level =
+ di->bat_cap.cap_scale.scaled_cap;
+ dev_dbg(di->dev, "Cap to stop scale at charge %d%%\n",
+ di->bat_cap.cap_scale.disable_cap_level);
+ } else {
+ if (cs->scaled_cap != 100) {
+ cs->cap_to_scale[0] = cs->scaled_cap;
+ cs->cap_to_scale[1] = di->bat_cap.prev_percent;
+ } else {
+ cs->cap_to_scale[0] = 100;
+ cs->cap_to_scale[1] =
+ max(di->bat_cap.prev_percent,
+ di->bm->fg_params->maint_thres);
+ }
+
+ dev_dbg(di->dev, "Cap to scale at discharge %d/%d\n",
+ cs->cap_to_scale[0], cs->cap_to_scale[1]);
+ }
+}
+
+/**
* ab8500_fg_check_capacity_limits() - Check if capacity has changed
* @di: pointer to the ab8500_fg structure
* @init: capacity is allowed to go up in init mode
@@ -1136,6 +1281,7 @@ static int ab8500_fg_capacity_level(struct ab8500_fg *di)
static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
{
bool changed = false;
+ int percent = DIV_ROUND_CLOSEST(di->bat_cap.permille, 10);
di->bat_cap.level = ab8500_fg_capacity_level(di);
@@ -1167,47 +1313,52 @@ static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
dev_dbg(di->dev, "Battery low, set capacity to 0\n");
di->bat_cap.prev_percent = 0;
di->bat_cap.permille = 0;
+ percent = 0;
di->bat_cap.prev_mah = 0;
di->bat_cap.mah = 0;
changed = true;
} else if (di->flags.fully_charged) {
/*
* We report 100% if algorithm reported fully charged
- * unless capacity drops too much
+ * and show 100% during maintenance charging (scaling).
*/
if (di->flags.force_full) {
- di->bat_cap.prev_percent = di->bat_cap.permille / 10;
+ di->bat_cap.prev_percent = percent;
di->bat_cap.prev_mah = di->bat_cap.mah;
- } else if (!di->flags.force_full &&
- di->bat_cap.prev_percent !=
- (di->bat_cap.permille) / 10 &&
- (di->bat_cap.permille / 10) <
- di->bat->fg_params->maint_thres) {
+
+ changed = true;
+
+ if (!di->bat_cap.cap_scale.enable &&
+ di->bm->capacity_scaling) {
+ di->bat_cap.cap_scale.enable = true;
+ di->bat_cap.cap_scale.cap_to_scale[0] = 100;
+ di->bat_cap.cap_scale.cap_to_scale[1] =
+ di->bat_cap.prev_percent;
+ di->bat_cap.cap_scale.disable_cap_level = 100;
+ }
+ } else if (di->bat_cap.prev_percent != percent) {
dev_dbg(di->dev,
"battery reported full "
"but capacity dropping: %d\n",
- di->bat_cap.permille / 10);
- di->bat_cap.prev_percent = di->bat_cap.permille / 10;
+ percent);
+ di->bat_cap.prev_percent = percent;
di->bat_cap.prev_mah = di->bat_cap.mah;
changed = true;
}
- } else if (di->bat_cap.prev_percent != di->bat_cap.permille / 10) {
- if (di->bat_cap.permille / 10 == 0) {
+ } else if (di->bat_cap.prev_percent != percent) {
+ if (percent == 0) {
/*
* We will not report 0% unless we've got
* the LOW_BAT IRQ, no matter what the FG
* algorithm says.
*/
di->bat_cap.prev_percent = 1;
- di->bat_cap.permille = 1;
- di->bat_cap.prev_mah = 1;
- di->bat_cap.mah = 1;
+ percent = 1;
changed = true;
} else if (!(!di->flags.charging &&
- (di->bat_cap.permille / 10) >
- di->bat_cap.prev_percent) || init) {
+ percent > di->bat_cap.prev_percent) || init) {
/*
* We do not allow reported capacity to go up
* unless we're charging or if we're in init
@@ -1215,9 +1366,9 @@ static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
dev_dbg(di->dev,
"capacity changed from %d to %d (%d)\n",
di->bat_cap.prev_percent,
- di->bat_cap.permille / 10,
+ percent,
di->bat_cap.permille);
- di->bat_cap.prev_percent = di->bat_cap.permille / 10;
+ di->bat_cap.prev_percent = percent;
di->bat_cap.prev_mah = di->bat_cap.mah;
changed = true;
@@ -1225,12 +1376,20 @@ static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
dev_dbg(di->dev, "capacity not allowed to go up since "
"no charger is connected: %d to %d (%d)\n",
di->bat_cap.prev_percent,
- di->bat_cap.permille / 10,
+ percent,
di->bat_cap.permille);
}
}
if (changed) {
+ if (di->bm->capacity_scaling) {
+ di->bat_cap.cap_scale.scaled_cap =
+ ab8500_fg_calculate_scaled_capacity(di);
+
+ dev_info(di->dev, "capacity=%d (%d)\n",
+ di->bat_cap.prev_percent,
+ di->bat_cap.cap_scale.scaled_cap);
+ }
power_supply_changed(&di->fg_psy);
if (di->flags.fully_charged && di->flags.force_full) {
dev_dbg(di->dev, "Battery full, notifying.\n");
@@ -1284,7 +1443,7 @@ static void ab8500_fg_algorithm_charging(struct ab8500_fg *di)
switch (di->charge_state) {
case AB8500_FG_CHARGE_INIT:
di->fg_samples = SEC_TO_SAMPLE(
- di->bat->fg_params->accu_charging);
+ di->bm->fg_params->accu_charging);
ab8500_fg_coulomb_counter(di, true);
ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT);
@@ -1296,7 +1455,7 @@ static void ab8500_fg_algorithm_charging(struct ab8500_fg *di)
* Read the FG and calculate the new capacity
*/
mutex_lock(&di->cc_lock);
- if (!di->flags.conv_done) {
+ if (!di->flags.conv_done && !di->flags.force_full) {
/* Wasn't the CC IRQ that got us here */
mutex_unlock(&di->cc_lock);
dev_dbg(di->dev, "%s CC conv not done\n",
@@ -1346,8 +1505,8 @@ static bool check_sysfs_capacity(struct ab8500_fg *di)
cap_permille = ab8500_fg_convert_mah_to_permille(di,
di->bat_cap.user_mah);
- lower = di->bat_cap.permille - di->bat->fg_params->user_cap_limit * 10;
- upper = di->bat_cap.permille + di->bat->fg_params->user_cap_limit * 10;
+ lower = di->bat_cap.permille - di->bm->fg_params->user_cap_limit * 10;
+ upper = di->bat_cap.permille + di->bm->fg_params->user_cap_limit * 10;
if (lower < 0)
lower = 0;
@@ -1387,7 +1546,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
case AB8500_FG_DISCHARGE_INIT:
/* We use the FG IRQ to work on */
di->init_cnt = 0;
- di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer);
+ di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer);
ab8500_fg_coulomb_counter(di, true);
ab8500_fg_discharge_state_to(di,
AB8500_FG_DISCHARGE_INITMEASURING);
@@ -1400,18 +1559,17 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
* samples to get an initial capacity.
* Then go to READOUT
*/
- sleep_time = di->bat->fg_params->init_timer;
+ sleep_time = di->bm->fg_params->init_timer;
/* Discard the first [x] seconds */
- if (di->init_cnt >
- di->bat->fg_params->init_discard_time) {
+ if (di->init_cnt > di->bm->fg_params->init_discard_time) {
ab8500_fg_calc_cap_discharge_voltage(di, true);
ab8500_fg_check_capacity_limits(di, true);
}
di->init_cnt += sleep_time;
- if (di->init_cnt > di->bat->fg_params->init_total_time)
+ if (di->init_cnt > di->bm->fg_params->init_total_time)
ab8500_fg_discharge_state_to(di,
AB8500_FG_DISCHARGE_READOUT_INIT);
@@ -1426,7 +1584,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
/* Intentional fallthrough */
case AB8500_FG_DISCHARGE_RECOVERY:
- sleep_time = di->bat->fg_params->recovery_sleep_timer;
+ sleep_time = di->bm->fg_params->recovery_sleep_timer;
/*
* We should check the power consumption
@@ -1438,9 +1596,9 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
if (ab8500_fg_is_low_curr(di, di->inst_curr)) {
if (di->recovery_cnt >
- di->bat->fg_params->recovery_total_time) {
+ di->bm->fg_params->recovery_total_time) {
di->fg_samples = SEC_TO_SAMPLE(
- di->bat->fg_params->accu_high_curr);
+ di->bm->fg_params->accu_high_curr);
ab8500_fg_coulomb_counter(di, true);
ab8500_fg_discharge_state_to(di,
AB8500_FG_DISCHARGE_READOUT);
@@ -1453,7 +1611,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
di->recovery_cnt += sleep_time;
} else {
di->fg_samples = SEC_TO_SAMPLE(
- di->bat->fg_params->accu_high_curr);
+ di->bm->fg_params->accu_high_curr);
ab8500_fg_coulomb_counter(di, true);
ab8500_fg_discharge_state_to(di,
AB8500_FG_DISCHARGE_READOUT);
@@ -1462,7 +1620,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
case AB8500_FG_DISCHARGE_READOUT_INIT:
di->fg_samples = SEC_TO_SAMPLE(
- di->bat->fg_params->accu_high_curr);
+ di->bm->fg_params->accu_high_curr);
ab8500_fg_coulomb_counter(di, true);
ab8500_fg_discharge_state_to(di,
AB8500_FG_DISCHARGE_READOUT);
@@ -1480,7 +1638,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
if (di->recovery_needed) {
ab8500_fg_discharge_state_to(di,
- AB8500_FG_DISCHARGE_RECOVERY);
+ AB8500_FG_DISCHARGE_INIT_RECOVERY);
queue_delayed_work(di->fg_wq,
&di->fg_periodic_work, 0);
@@ -1509,9 +1667,9 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
}
di->high_curr_cnt +=
- di->bat->fg_params->accu_high_curr;
+ di->bm->fg_params->accu_high_curr;
if (di->high_curr_cnt >
- di->bat->fg_params->high_curr_time)
+ di->bm->fg_params->high_curr_time)
di->recovery_needed = true;
ab8500_fg_calc_cap_discharge_fg(di);
@@ -1522,13 +1680,10 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
break;
case AB8500_FG_DISCHARGE_WAKEUP:
- ab8500_fg_coulomb_counter(di, true);
- di->inst_curr = ab8500_fg_inst_curr_blocking(di);
-
ab8500_fg_calc_cap_discharge_voltage(di, true);
di->fg_samples = SEC_TO_SAMPLE(
- di->bat->fg_params->accu_high_curr);
+ di->bm->fg_params->accu_high_curr);
ab8500_fg_coulomb_counter(di, true);
ab8500_fg_discharge_state_to(di,
AB8500_FG_DISCHARGE_READOUT);
@@ -1609,9 +1764,10 @@ static void ab8500_fg_algorithm(struct ab8500_fg *di)
ab8500_fg_algorithm_discharging(di);
}
- dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d "
+ dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d %d "
"%d %d %d %d %d %d %d\n",
di->bat_cap.max_mah_design,
+ di->bat_cap.max_mah,
di->bat_cap.mah,
di->bat_cap.permille,
di->bat_cap.level,
@@ -1641,8 +1797,6 @@ static void ab8500_fg_periodic_work(struct work_struct *work)
fg_periodic_work.work);
if (di->init_capacity) {
- /* A dummy read that will return 0 */
- di->inst_curr = ab8500_fg_inst_curr_blocking(di);
/* Get an initial capacity calculation */
ab8500_fg_calc_cap_discharge_voltage(di, true);
ab8500_fg_check_capacity_limits(di, true);
@@ -1684,24 +1838,26 @@ static void ab8500_fg_check_hw_failure_work(struct work_struct *work)
* If we have had a battery over-voltage situation,
* check ovv-bit to see if it should be reset.
*/
- if (di->flags.bat_ovv) {
- ret = abx500_get_register_interruptible(di->dev,
- AB8500_CHARGER, AB8500_CH_STAT_REG,
- &reg_value);
- if (ret < 0) {
- dev_err(di->dev, "%s ab8500 read failed\n", __func__);
- return;
- }
- if ((reg_value & BATT_OVV) != BATT_OVV) {
- dev_dbg(di->dev, "Battery recovered from OVV\n");
- di->flags.bat_ovv = false;
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_STAT_REG,
+ &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if ((reg_value & BATT_OVV) == BATT_OVV) {
+ if (!di->flags.bat_ovv) {
+ dev_dbg(di->dev, "Battery OVV\n");
+ di->flags.bat_ovv = true;
power_supply_changed(&di->fg_psy);
- return;
}
-
/* Not yet recovered from ovv, reschedule this test */
queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work,
- round_jiffies(HZ));
+ HZ);
+ } else {
+ dev_dbg(di->dev, "Battery recovered from OVV\n");
+ di->flags.bat_ovv = false;
+ power_supply_changed(&di->fg_psy);
}
}
@@ -1721,26 +1877,30 @@ static void ab8500_fg_low_bat_work(struct work_struct *work)
vbat = ab8500_fg_bat_voltage(di);
/* Check if LOW_BAT still fulfilled */
- if (vbat < di->bat->fg_params->lowbat_threshold) {
- di->flags.low_bat = true;
- dev_warn(di->dev, "Battery voltage still LOW\n");
-
- /*
- * We need to re-schedule this check to be able to detect
- * if the voltage increases again during charging
- */
- queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
- round_jiffies(LOW_BAT_CHECK_INTERVAL));
+ if (vbat < di->bm->fg_params->lowbat_threshold) {
+ /* Is it time to shut down? */
+ if (di->low_bat_cnt < 1) {
+ di->flags.low_bat = true;
+ dev_warn(di->dev, "Shut down pending...\n");
+ } else {
+ /*
+ * Else we need to re-schedule this check to be able to detect
+ * if the voltage increases again during charging or
+ * due to decreasing load.
+ */
+ di->low_bat_cnt--;
+ dev_warn(di->dev, "Battery voltage still LOW\n");
+ queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
+ round_jiffies(LOW_BAT_CHECK_INTERVAL));
+ }
} else {
- di->flags.low_bat = false;
+ di->flags.low_bat_delay = false;
+ di->low_bat_cnt = 10;
dev_warn(di->dev, "Battery voltage OK again\n");
}
/* This is needed to dispatch LOW_BAT */
ab8500_fg_check_capacity_limits(di, false);
-
- /* Set this flag to check if LOW_BAT IRQ still occurs */
- di->flags.low_bat_delay = false;
}
/**
@@ -1779,8 +1939,8 @@ static int ab8500_fg_battok_init_hw_register(struct ab8500_fg *di)
int ret;
int new_val;
- sel0 = di->bat->fg_params->battok_falling_th_sel0;
- sel1 = di->bat->fg_params->battok_raising_th_sel1;
+ sel0 = di->bm->fg_params->battok_falling_th_sel0;
+ sel1 = di->bm->fg_params->battok_raising_th_sel1;
cbp_sel0 = ab8500_fg_battok_calc(di, sel0);
cbp_sel1 = ab8500_fg_battok_calc(di, sel1);
@@ -1819,7 +1979,7 @@ static void ab8500_fg_instant_work(struct work_struct *work)
}
/**
- * ab8500_fg_cc_data_end_handler() - isr to get battery avg current.
+ * ab8500_fg_cc_data_end_handler() - end of data conversion isr.
* @irq: interrupt number
* @_di: pointer to the ab8500_fg structure
*
@@ -1828,12 +1988,18 @@ static void ab8500_fg_instant_work(struct work_struct *work)
static irqreturn_t ab8500_fg_cc_data_end_handler(int irq, void *_di)
{
struct ab8500_fg *di = _di;
- complete(&di->ab8500_fg_complete);
+ if (!di->nbr_cceoc_irq_cnt) {
+ di->nbr_cceoc_irq_cnt++;
+ complete(&di->ab8500_fg_started);
+ } else {
+ di->nbr_cceoc_irq_cnt = 0;
+ complete(&di->ab8500_fg_complete);
+ }
return IRQ_HANDLED;
}
/**
- * ab8500_fg_cc_convend_handler() - isr to get battery avg current.
+ * ab8500_fg_cc_int_calib_handler () - end of calibration isr.
* @irq: interrupt number
* @_di: pointer to the ab8500_fg structure
*
@@ -1875,8 +2041,6 @@ static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di)
struct ab8500_fg *di = _di;
dev_dbg(di->dev, "Battery OVV\n");
- di->flags.bat_ovv = true;
- power_supply_changed(&di->fg_psy);
/* Schedule a new HW failure check */
queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, 0);
@@ -1895,6 +2059,7 @@ static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di)
{
struct ab8500_fg *di = _di;
+ /* Initiate handling in ab8500_fg_low_bat_work() if not already initiated. */
if (!di->flags.low_bat_delay) {
dev_warn(di->dev, "Battery voltage is below LOW threshold\n");
di->flags.low_bat_delay = true;
@@ -1963,7 +2128,7 @@ static int ab8500_fg_get_property(struct power_supply *psy,
di->bat_cap.max_mah);
break;
case POWER_SUPPLY_PROP_ENERGY_NOW:
- if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+ if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
di->flags.batt_id_received)
val->intval = ab8500_fg_convert_mah_to_uwh(di,
di->bat_cap.max_mah);
@@ -1978,21 +2143,21 @@ static int ab8500_fg_get_property(struct power_supply *psy,
val->intval = di->bat_cap.max_mah;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
- if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+ if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
di->flags.batt_id_received)
val->intval = di->bat_cap.max_mah;
else
val->intval = di->bat_cap.prev_mah;
break;
case POWER_SUPPLY_PROP_CAPACITY:
- if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+ if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
di->flags.batt_id_received)
val->intval = 100;
else
val->intval = di->bat_cap.prev_percent;
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
- if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+ if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
di->flags.batt_id_received)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
else
@@ -2049,6 +2214,8 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
break;
di->flags.charging = false;
di->flags.fully_charged = false;
+ if (di->bm->capacity_scaling)
+ ab8500_fg_update_cap_scalers(di);
queue_work(di->fg_wq, &di->fg_work);
break;
case POWER_SUPPLY_STATUS_FULL:
@@ -2061,10 +2228,13 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
queue_work(di->fg_wq, &di->fg_work);
break;
case POWER_SUPPLY_STATUS_CHARGING:
- if (di->flags.charging)
+ if (di->flags.charging &&
+ !di->flags.fully_charged)
break;
di->flags.charging = true;
di->flags.fully_charged = false;
+ if (di->bm->capacity_scaling)
+ ab8500_fg_update_cap_scalers(di);
queue_work(di->fg_wq, &di->fg_work);
break;
};
@@ -2075,10 +2245,11 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
case POWER_SUPPLY_PROP_TECHNOLOGY:
switch (ext->type) {
case POWER_SUPPLY_TYPE_BATTERY:
- if (!di->flags.batt_id_received) {
+ if (!di->flags.batt_id_received &&
+ di->bm->batt_id != BATTERY_UNKNOWN) {
const struct abx500_battery_type *b;
- b = &(di->bat->bat_type[di->bat->batt_id]);
+ b = &(di->bm->bat_type[di->bm->batt_id]);
di->flags.batt_id_received = true;
@@ -2104,8 +2275,8 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
case POWER_SUPPLY_PROP_TEMP:
switch (ext->type) {
case POWER_SUPPLY_TYPE_BATTERY:
- if (di->flags.batt_id_received)
- di->bat_temp = ret.intval;
+ if (di->flags.batt_id_received)
+ di->bat_temp = ret.intval;
break;
default:
break;
@@ -2155,7 +2326,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
AB8500_SYS_CTRL2_BLOCK,
AB8500_LOW_BAT_REG,
ab8500_volt_to_regval(
- di->bat->fg_params->lowbat_threshold) << 1 |
+ di->bm->fg_params->lowbat_threshold) << 1 |
LOW_BAT_ENABLE);
if (ret) {
dev_err(di->dev, "%s write failed\n", __func__);
@@ -2168,6 +2339,50 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
dev_err(di->dev, "BattOk init write failed.\n");
goto out;
}
+
+ if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
+ abx500_get_chip_id(di->dev) >= AB8500_CUT2P0)
+ || is_ab8540(di->parent)) {
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_MAX_TIME_REG, di->bm->fg_params->pcut_max_time);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", __func__);
+ goto out;
+ };
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__);
+ goto out;
+ };
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", __func__);
+ goto out;
+ };
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_DEBOUNCE_REG, di->bm->fg_params->pcut_debounce_time);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", __func__);
+ goto out;
+ };
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, di->bm->fg_params->pcut_enable);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", __func__);
+ goto out;
+ };
+ }
out:
return ret;
}
@@ -2250,9 +2465,9 @@ static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf,
size_t count)
{
unsigned long charge_full;
- ssize_t ret = -EINVAL;
+ ssize_t ret;
- ret = strict_strtoul(buf, 10, &charge_full);
+ ret = kstrtoul(buf, 10, &charge_full);
dev_dbg(di->dev, "Ret %zd charge_full %lu", ret, charge_full);
@@ -2274,7 +2489,7 @@ static ssize_t charge_now_store(struct ab8500_fg *di, const char *buf,
unsigned long charge_now;
ssize_t ret;
- ret = strict_strtoul(buf, 10, &charge_now);
+ ret = kstrtoul(buf, 10, &charge_now);
dev_dbg(di->dev, "Ret %zd charge_now %lu was %d",
ret, charge_now, di->bat_cap.prev_mah);
@@ -2370,6 +2585,428 @@ static int ab8500_fg_sysfs_init(struct ab8500_fg *di)
return ret;
}
+
+static ssize_t ab8505_powercut_flagtime_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_FLAG_TIME_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_flagtime_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ long unsigned reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+
+ if (reg_value > 0x7F) {
+ dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n");
+
+fail:
+ return count;
+}
+
+static ssize_t ab8505_powercut_maxtime_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_MAX_TIME_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_MAX_TIME_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F));
+
+fail:
+ return ret;
+
+}
+
+static ssize_t ab8505_powercut_maxtime_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+ if (reg_value > 0x7F) {
+ dev_err(dev, "Incorrect parameter, echo 0 (0.0s) - 127 (1.98s) for maxtime\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_MAX_TIME_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_MAX_TIME_REG\n");
+
+fail:
+ return count;
+}
+
+static ssize_t ab8505_powercut_restart_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_RESTART_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_restart_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+ if (reg_value > 0xF) {
+ dev_err(dev, "Incorrect parameter, echo 0 - 15 for number of restart\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_RESTART_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_RESTART_REG\n");
+
+fail:
+ return count;
+
+}
+
+static ssize_t ab8505_powercut_timer_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_TIME_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_TIME_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_restart_counter_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_RESTART_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF0) >> 4);
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
+
+ if (ret < 0)
+ goto fail;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x1));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+ if (reg_value > 0x1) {
+ dev_err(dev, "Incorrect parameter, echo 0/1 to disable/enable Pcut feature\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_CTL_STATUS_REG\n");
+
+fail:
+ return count;
+}
+
+static ssize_t ab8505_powercut_flag_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x10) >> 4));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_debounce_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_DEBOUNCE_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_DEBOUNCE_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_debounce_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+ if (reg_value > 0x7) {
+ dev_err(dev, "Incorrect parameter, echo 0 to 7 for debounce setting\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_DEBOUNCE_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_DEBOUNCE_REG\n");
+
+fail:
+ return count;
+}
+
+static ssize_t ab8505_powercut_enable_status_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x20) >> 5));
+
+fail:
+ return ret;
+}
+
+static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = {
+ __ATTR(powercut_flagtime, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_flagtime_read, ab8505_powercut_flagtime_write),
+ __ATTR(powercut_maxtime, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_maxtime_read, ab8505_powercut_maxtime_write),
+ __ATTR(powercut_restart_max, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_restart_read, ab8505_powercut_restart_write),
+ __ATTR(powercut_timer, S_IRUGO, ab8505_powercut_timer_read, NULL),
+ __ATTR(powercut_restart_counter, S_IRUGO,
+ ab8505_powercut_restart_counter_read, NULL),
+ __ATTR(powercut_enable, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_read, ab8505_powercut_write),
+ __ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL),
+ __ATTR(powercut_debounce_time, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_debounce_read, ab8505_powercut_debounce_write),
+ __ATTR(powercut_enable_status, S_IRUGO,
+ ab8505_powercut_enable_status_read, NULL),
+};
+
+static int ab8500_fg_sysfs_psy_create_attrs(struct device *dev)
+{
+ unsigned int i, j;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
+ abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0)
+ || is_ab8540(di->parent)) {
+ for (j = 0; j < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); j++)
+ if (device_create_file(dev, &ab8505_fg_sysfs_psy_attrs[j]))
+ goto sysfs_psy_create_attrs_failed_ab8505;
+ }
+ return 0;
+sysfs_psy_create_attrs_failed_ab8505:
+ dev_err(dev, "Failed creating sysfs psy attrs for ab8505.\n");
+ while (j--)
+ device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]);
+
+ return -EIO;
+}
+
+static void ab8500_fg_sysfs_psy_remove_attrs(struct device *dev)
+{
+ unsigned int i;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
+ abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0)
+ || is_ab8540(di->parent)) {
+ for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++)
+ (void)device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]);
+ }
+}
+
/* Exposure to the sysfs interface <<END>> */
#if defined(CONFIG_PM)
@@ -2395,6 +3032,11 @@ static int ab8500_fg_suspend(struct platform_device *pdev,
struct ab8500_fg *di = platform_get_drvdata(pdev);
flush_delayed_work(&di->fg_periodic_work);
+ flush_work(&di->fg_work);
+ flush_work(&di->fg_acc_cur_work);
+ flush_delayed_work(&di->fg_reinit_work);
+ flush_delayed_work(&di->fg_low_bat_work);
+ flush_delayed_work(&di->fg_check_hw_failure_work);
/*
* If the FG is enabled we will disable it before going to suspend
@@ -2426,8 +3068,8 @@ static int ab8500_fg_remove(struct platform_device *pdev)
ab8500_fg_sysfs_exit(di);
flush_scheduled_work();
+ ab8500_fg_sysfs_psy_remove_attrs(di->fg_psy.dev);
power_supply_unregister(&di->fg_psy);
- platform_set_drvdata(pdev, NULL);
return ret;
}
@@ -2448,6 +3090,7 @@ static char *supply_interface[] = {
static int ab8500_fg_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ struct abx500_bm_data *plat = pdev->dev.platform_data;
struct ab8500_fg *di;
int i, irq;
int ret = 0;
@@ -2457,21 +3100,19 @@ static int ab8500_fg_probe(struct platform_device *pdev)
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;
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no battery management data supplied\n");
+ return -EINVAL;
+ }
+ di->bm = plat;
+
+ if (np) {
+ ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get battery information\n");
+ return ret;
}
- } else {
- dev_info(&pdev->dev, "falling back to legacy platform data\n");
}
mutex_init(&di->cc_lock);
@@ -2491,11 +3132,11 @@ static int ab8500_fg_probe(struct platform_device *pdev)
di->fg_psy.external_power_changed = ab8500_fg_external_power_changed;
di->bat_cap.max_mah_design = MILLI_TO_MICRO *
- di->bat->bat_type[di->bat->batt_id].charge_full_design;
+ di->bm->bat_type[di->bm->batt_id].charge_full_design;
di->bat_cap.max_mah = di->bat_cap.max_mah_design;
- di->vbat_nom = di->bat->bat_type[di->bat->batt_id].nominal_voltage;
+ di->vbat_nom = di->bm->bat_type[di->bm->batt_id].nominal_voltage;
di->init_capacity = true;
@@ -2531,6 +3172,12 @@ static int ab8500_fg_probe(struct platform_device *pdev)
INIT_DEFERRABLE_WORK(&di->fg_check_hw_failure_work,
ab8500_fg_check_hw_failure_work);
+ /* Reset battery low voltage flag */
+ di->flags.low_bat = false;
+
+ /* Initialize low battery counter */
+ di->low_bat_cnt = 10;
+
/* Initialize OVV, and other registers */
ret = ab8500_fg_init_hw_registers(di);
if (ret) {
@@ -2549,10 +3196,14 @@ static int ab8500_fg_probe(struct platform_device *pdev)
goto free_inst_curr_wq;
}
- di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer);
+ di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer);
ab8500_fg_coulomb_counter(di, true);
- /* Initialize completion used to notify completion of inst current */
+ /*
+ * Initialize completion used to notify completion and start
+ * of inst current
+ */
+ init_completion(&di->ab8500_fg_started);
init_completion(&di->ab8500_fg_complete);
/* Register interrupts */
@@ -2572,6 +3223,7 @@ static int ab8500_fg_probe(struct platform_device *pdev)
}
di->irq = platform_get_irq_byname(pdev, "CCEOC");
disable_irq(di->irq);
+ di->nbr_cceoc_irq_cnt = 0;
platform_set_drvdata(pdev, di);
@@ -2581,6 +3233,13 @@ static int ab8500_fg_probe(struct platform_device *pdev)
goto free_irq;
}
+ ret = ab8500_fg_sysfs_psy_create_attrs(di->fg_psy.dev);
+ if (ret) {
+ dev_err(di->dev, "failed to create FG psy\n");
+ ab8500_fg_sysfs_exit(di);
+ goto free_irq;
+ }
+
/* Calibrate the fg first time */
di->flags.calibrate = true;
di->calib_state = AB8500_FG_CALIB_INIT;
diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c
index 29708914606..6d2723664a0 100644
--- a/drivers/power/abx500_chargalg.c
+++ b/drivers/power/abx500_chargalg.c
@@ -1,5 +1,6 @@
/*
* Copyright (C) ST-Ericsson SA 2012
+ * Copyright (c) 2012 Sony Mobile Communications AB
*
* Charging algorithm driver for abx500 variants
*
@@ -8,11 +9,13 @@
* Johan Palsson <johan.palsson@stericsson.com>
* Karl Komierowski <karl.komierowski@stericsson.com>
* Arun R Murthy <arun.murthy@stericsson.com>
+ * Author: Imre Sunyi <imre.sunyi@sonymobile.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
+#include <linux/hrtimer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/slab.h>
@@ -24,8 +27,10 @@
#include <linux/of.h>
#include <linux/mfd/core.h>
#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500.h>
#include <linux/mfd/abx500/ux500_chargalg.h>
#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/notifier.h>
/* Watchdog kick interval */
#define CHG_WD_INTERVAL (6 * HZ)
@@ -33,8 +38,17 @@
/* End-of-charge criteria counter */
#define EOC_COND_CNT 10
-/* Recharge criteria counter */
-#define RCH_COND_CNT 3
+/* One hour expressed in seconds */
+#define ONE_HOUR_IN_SECONDS 3600
+
+/* Five minutes expressed in seconds */
+#define FIVE_MINUTES_IN_SECONDS 300
+
+/* Plus margin for the low battery threshold */
+#define BAT_PLUS_MARGIN (100)
+
+#define CHARGALG_CURR_STEP_LOW 0
+#define CHARGALG_CURR_STEP_HIGH 100
#define to_abx500_chargalg_device_info(x) container_of((x), \
struct abx500_chargalg, chargalg_psy);
@@ -69,6 +83,11 @@ struct abx500_chargalg_suspension_status {
bool usb_suspended;
};
+struct abx500_chargalg_current_step_status {
+ bool curr_step_change;
+ int curr_step;
+};
+
struct abx500_chargalg_battery_data {
int temp;
int volt;
@@ -85,6 +104,7 @@ enum abx500_chargalg_states {
STATE_HW_TEMP_PROTECT_INIT,
STATE_HW_TEMP_PROTECT,
STATE_NORMAL_INIT,
+ STATE_USB_PP_PRE_CHARGE,
STATE_NORMAL,
STATE_WAIT_FOR_RECHARGE_INIT,
STATE_WAIT_FOR_RECHARGE,
@@ -116,6 +136,7 @@ static const char *states[] = {
"HW_TEMP_PROTECT_INIT",
"HW_TEMP_PROTECT",
"NORMAL_INIT",
+ "USB_PP_PRE_CHARGE",
"NORMAL",
"WAIT_FOR_RECHARGE_INIT",
"WAIT_FOR_RECHARGE",
@@ -196,7 +217,6 @@ enum maxim_ret {
* @dev: pointer to the structure device
* @charge_status: battery operating status
* @eoc_cnt: counter used to determine end-of_charge
- * @rch_cnt: counter used to determine start of recharge
* @maintenance_chg: indicate if maintenance charge is active
* @t_hyst_norm temperature hysteresis when the temperature has been
* over or under normal limits
@@ -207,7 +227,9 @@ enum maxim_ret {
* @chg_info: information about connected charger types
* @batt_data: data of the battery
* @susp_status: current charger suspension status
- * @bat: pointer to the abx500_bm platform data
+ * @bm: Platform specific battery management information
+ * @curr_status: Current step status for over-current protection
+ * @parent: pointer to the struct abx500
* @chargalg_psy: structure that holds the battery properties exposed by
* the charging algorithm
* @events: structure for information about events triggered
@@ -223,7 +245,6 @@ struct abx500_chargalg {
struct device *dev;
int charge_status;
int eoc_cnt;
- int rch_cnt;
bool maintenance_chg;
int t_hyst_norm;
int t_hyst_lowhigh;
@@ -232,7 +253,9 @@ 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_bm_data *bat;
+ struct ab8500 *parent;
+ struct abx500_chargalg_current_step_status curr_status;
+ struct abx500_bm_data *bm;
struct power_supply chargalg_psy;
struct ux500_charger *ac_chg;
struct ux500_charger *usb_chg;
@@ -241,51 +264,69 @@ struct abx500_chargalg {
struct delayed_work chargalg_periodic_work;
struct delayed_work chargalg_wd_work;
struct work_struct chargalg_work;
- struct timer_list safety_timer;
- struct timer_list maintenance_timer;
+ struct hrtimer safety_timer;
+ struct hrtimer maintenance_timer;
struct kobject chargalg_kobject;
};
+/*External charger prepare notifier*/
+BLOCKING_NOTIFIER_HEAD(charger_notifier_list);
+
/* Main battery properties */
static enum power_supply_property abx500_chargalg_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
};
+struct abx500_chargalg_sysfs_entry {
+ struct attribute attr;
+ ssize_t (*show)(struct abx500_chargalg *, char *);
+ ssize_t (*store)(struct abx500_chargalg *, const char *, size_t);
+};
+
/**
* abx500_chargalg_safety_timer_expired() - Expiration of the safety timer
- * @data: pointer to the abx500_chargalg structure
+ * @timer: pointer to the hrtimer structure
*
* This function gets called when the safety timer for the charger
* expires
*/
-static void abx500_chargalg_safety_timer_expired(unsigned long data)
+static enum hrtimer_restart
+abx500_chargalg_safety_timer_expired(struct hrtimer *timer)
{
- struct abx500_chargalg *di = (struct abx500_chargalg *) data;
+ struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg,
+ safety_timer);
dev_err(di->dev, "Safety timer expired\n");
di->events.safety_timer_expired = true;
/* Trigger execution of the algorithm instantly */
queue_work(di->chargalg_wq, &di->chargalg_work);
+
+ return HRTIMER_NORESTART;
}
/**
* abx500_chargalg_maintenance_timer_expired() - Expiration of
* the maintenance timer
- * @i: pointer to the abx500_chargalg structure
+ * @timer: pointer to the timer structure
*
* This function gets called when the maintenence timer
* expires
*/
-static void abx500_chargalg_maintenance_timer_expired(unsigned long data)
+static enum hrtimer_restart
+abx500_chargalg_maintenance_timer_expired(struct hrtimer *timer)
{
- struct abx500_chargalg *di = (struct abx500_chargalg *) data;
+ struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg,
+ maintenance_timer);
+
dev_dbg(di->dev, "Maintenance timer expired\n");
di->events.maintenance_timer_expired = true;
/* Trigger execution of the algorithm instantly */
queue_work(di->chargalg_wq, &di->chargalg_work);
+
+ return HRTIMER_NORESTART;
}
/**
@@ -308,6 +349,30 @@ static void abx500_chargalg_state_to(struct abx500_chargalg *di,
di->charge_state = state;
}
+static int abx500_chargalg_check_charger_enable(struct abx500_chargalg *di)
+{
+ switch (di->charge_state) {
+ case STATE_NORMAL:
+ case STATE_MAINTENANCE_A:
+ case STATE_MAINTENANCE_B:
+ break;
+ default:
+ return 0;
+ }
+
+ if (di->chg_info.charger_type & USB_CHG) {
+ return di->usb_chg->ops.check_enable(di->usb_chg,
+ di->bm->bat_type[di->bm->batt_id].normal_vol_lvl,
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl);
+ } else if ((di->chg_info.charger_type & AC_CHG) &&
+ !(di->ac_chg->external)) {
+ return di->ac_chg->ops.check_enable(di->ac_chg,
+ di->bm->bat_type[di->bm->batt_id].normal_vol_lvl,
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl);
+ }
+ return 0;
+}
+
/**
* abx500_chargalg_check_charger_connection() - Check charger connection change
* @di: pointer to the abx500_chargalg structure
@@ -353,6 +418,22 @@ static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di)
}
/**
+ * abx500_chargalg_check_current_step_status() - Check charging current
+ * step status.
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function will check if there is a change in the charging current step
+ * and change charge state accordingly.
+ */
+static void abx500_chargalg_check_current_step_status
+ (struct abx500_chargalg *di)
+{
+ if (di->curr_status.curr_step_change)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ di->curr_status.curr_step_change = false;
+}
+
+/**
* abx500_chargalg_start_safety_timer() - Start charging safety timer
* @di: pointer to the abx500_chargalg structure
*
@@ -361,19 +442,16 @@ static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di)
*/
static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)
{
- unsigned long timer_expiration = 0;
+ /* Charger-dependent expiration time in hours*/
+ int timer_expiration = 0;
switch (di->chg_info.charger_type) {
case AC_CHG:
- timer_expiration =
- round_jiffies(jiffies +
- (di->bat->main_safety_tmr_h * 3600 * HZ));
+ timer_expiration = di->bm->main_safety_tmr_h;
break;
case USB_CHG:
- timer_expiration =
- round_jiffies(jiffies +
- (di->bat->usb_safety_tmr_h * 3600 * HZ));
+ timer_expiration = di->bm->usb_safety_tmr_h;
break;
default:
@@ -382,11 +460,10 @@ static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)
}
di->events.safety_timer_expired = false;
- di->safety_timer.expires = timer_expiration;
- if (!timer_pending(&di->safety_timer))
- add_timer(&di->safety_timer);
- else
- mod_timer(&di->safety_timer, timer_expiration);
+ hrtimer_set_expires_range(&di->safety_timer,
+ ktime_set(timer_expiration * ONE_HOUR_IN_SECONDS, 0),
+ ktime_set(FIVE_MINUTES_IN_SECONDS, 0));
+ hrtimer_start_expires(&di->safety_timer, HRTIMER_MODE_REL);
}
/**
@@ -397,8 +474,8 @@ static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)
*/
static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di)
{
- di->events.safety_timer_expired = false;
- del_timer(&di->safety_timer);
+ if (hrtimer_try_to_cancel(&di->safety_timer) >= 0)
+ di->events.safety_timer_expired = false;
}
/**
@@ -413,17 +490,11 @@ static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di)
static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di,
int duration)
{
- unsigned long timer_expiration;
-
- /* Convert from hours to jiffies */
- timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ));
-
+ hrtimer_set_expires_range(&di->maintenance_timer,
+ ktime_set(duration * ONE_HOUR_IN_SECONDS, 0),
+ ktime_set(FIVE_MINUTES_IN_SECONDS, 0));
di->events.maintenance_timer_expired = false;
- di->maintenance_timer.expires = timer_expiration;
- if (!timer_pending(&di->maintenance_timer))
- add_timer(&di->maintenance_timer);
- else
- mod_timer(&di->maintenance_timer, timer_expiration);
+ hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL);
}
/**
@@ -435,8 +506,8 @@ static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di,
*/
static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di)
{
- di->events.maintenance_timer_expired = false;
- del_timer(&di->maintenance_timer);
+ if (hrtimer_try_to_cancel(&di->maintenance_timer) >= 0)
+ di->events.maintenance_timer_expired = false;
}
/**
@@ -450,8 +521,18 @@ static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di)
{
/* Check if charger exists and kick watchdog if charging */
if (di->ac_chg && di->ac_chg->ops.kick_wd &&
- di->chg_info.online_chg & AC_CHG)
+ di->chg_info.online_chg & AC_CHG) {
+ /*
+ * If AB charger watchdog expired, pm2xxx charging
+ * gets disabled. To be safe, kick both AB charger watchdog
+ * and pm2xxx watchdog.
+ */
+ if (di->ac_chg->external &&
+ di->usb_chg && di->usb_chg->ops.kick_wd)
+ di->usb_chg->ops.kick_wd(di->usb_chg);
+
return di->ac_chg->ops.kick_wd(di->ac_chg);
+ }
else if (di->usb_chg && di->usb_chg->ops.kick_wd &&
di->chg_info.online_chg & USB_CHG)
return di->usb_chg->ops.kick_wd(di->usb_chg);
@@ -472,6 +553,8 @@ static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di)
static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable,
int vset, int iset)
{
+ static int abx500_chargalg_ex_ac_enable_toggle;
+
if (!di->ac_chg || !di->ac_chg->ops.enable)
return -ENXIO;
@@ -484,6 +567,14 @@ static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable,
di->chg_info.ac_iset = iset;
di->chg_info.ac_vset = vset;
+ /* Enable external charger */
+ if (enable && di->ac_chg->external &&
+ !abx500_chargalg_ex_ac_enable_toggle) {
+ blocking_notifier_call_chain(&charger_notifier_list,
+ 0, di->dev);
+ abx500_chargalg_ex_ac_enable_toggle++;
+ }
+
return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset);
}
@@ -515,6 +606,37 @@ static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable,
return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset);
}
+ /**
+ * ab8540_chargalg_usb_pp_en() - Enable/ disable USB power path
+ * @di: pointer to the abx500_chargalg structure
+ * @enable: power path enable/disable
+ *
+ * The USB power path will be enable/ disable
+ */
+static int ab8540_chargalg_usb_pp_en(struct abx500_chargalg *di, bool enable)
+{
+ if (!di->usb_chg || !di->usb_chg->ops.pp_enable)
+ return -ENXIO;
+
+ return di->usb_chg->ops.pp_enable(di->usb_chg, enable);
+}
+
+/**
+ * ab8540_chargalg_usb_pre_chg_en() - Enable/ disable USB pre-charge
+ * @di: pointer to the abx500_chargalg structure
+ * @enable: USB pre-charge enable/disable
+ *
+ * The USB USB pre-charge will be enable/ disable
+ */
+static int ab8540_chargalg_usb_pre_chg_en(struct abx500_chargalg *di,
+ bool enable)
+{
+ if (!di->usb_chg || !di->usb_chg->ops.pre_chg_enable)
+ return -ENXIO;
+
+ return di->usb_chg->ops.pre_chg_enable(di->usb_chg, enable);
+}
+
/**
* abx500_chargalg_update_chg_curr() - Update charger current
* @di: pointer to the abx500_chargalg structure
@@ -638,32 +760,32 @@ static void abx500_chargalg_start_charging(struct abx500_chargalg *di,
*/
static void abx500_chargalg_check_temp(struct abx500_chargalg *di)
{
- if (di->batt_data.temp > (di->bat->temp_low + di->t_hyst_norm) &&
- di->batt_data.temp < (di->bat->temp_high - di->t_hyst_norm)) {
+ if (di->batt_data.temp > (di->bm->temp_low + di->t_hyst_norm) &&
+ di->batt_data.temp < (di->bm->temp_high - di->t_hyst_norm)) {
/* Temp OK! */
di->events.btemp_underover = false;
di->events.btemp_lowhigh = false;
di->t_hyst_norm = 0;
di->t_hyst_lowhigh = 0;
} else {
- if (((di->batt_data.temp >= di->bat->temp_high) &&
+ if (((di->batt_data.temp >= di->bm->temp_high) &&
(di->batt_data.temp <
- (di->bat->temp_over - di->t_hyst_lowhigh))) ||
+ (di->bm->temp_over - di->t_hyst_lowhigh))) ||
((di->batt_data.temp >
- (di->bat->temp_under + di->t_hyst_lowhigh)) &&
- (di->batt_data.temp <= di->bat->temp_low))) {
+ (di->bm->temp_under + di->t_hyst_lowhigh)) &&
+ (di->batt_data.temp <= di->bm->temp_low))) {
/* TEMP minor!!!!! */
di->events.btemp_underover = false;
di->events.btemp_lowhigh = true;
- di->t_hyst_norm = di->bat->temp_hysteresis;
+ di->t_hyst_norm = di->bm->temp_hysteresis;
di->t_hyst_lowhigh = 0;
- } else if (di->batt_data.temp <= di->bat->temp_under ||
- di->batt_data.temp >= di->bat->temp_over) {
+ } else if (di->batt_data.temp <= di->bm->temp_under ||
+ di->batt_data.temp >= di->bm->temp_over) {
/* TEMP major!!!!! */
di->events.btemp_underover = true;
di->events.btemp_lowhigh = false;
di->t_hyst_norm = 0;
- di->t_hyst_lowhigh = di->bat->temp_hysteresis;
+ di->t_hyst_lowhigh = di->bm->temp_hysteresis;
} else {
/* Within hysteresis */
dev_dbg(di->dev, "Within hysteresis limit temp: %d "
@@ -682,12 +804,12 @@ static void abx500_chargalg_check_temp(struct abx500_chargalg *di)
*/
static void abx500_chargalg_check_charger_voltage(struct abx500_chargalg *di)
{
- if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max)
+ if (di->chg_info.usb_volt > di->bm->chg_params->usb_volt_max)
di->chg_info.usb_chg_ok = false;
else
di->chg_info.usb_chg_ok = true;
- if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max)
+ if (di->chg_info.ac_volt > di->bm->chg_params->ac_volt_max)
di->chg_info.ac_chg_ok = false;
else
di->chg_info.ac_chg_ok = true;
@@ -707,13 +829,16 @@ static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di)
if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING &&
di->charge_state == STATE_NORMAL &&
!di->maintenance_chg && (di->batt_data.volt >=
- di->bat->bat_type[di->bat->batt_id].termination_vol ||
+ di->bm->bat_type[di->bm->batt_id].termination_vol ||
di->events.usb_cv_active || di->events.ac_cv_active) &&
di->batt_data.avg_curr <
- di->bat->bat_type[di->bat->batt_id].termination_curr &&
+ di->bm->bat_type[di->bm->batt_id].termination_curr &&
di->batt_data.avg_curr > 0) {
if (++di->eoc_cnt >= EOC_COND_CNT) {
di->eoc_cnt = 0;
+ if ((di->chg_info.charger_type & USB_CHG) &&
+ (di->usb_chg->power_path))
+ ab8540_chargalg_usb_pp_en(di, true);
di->charge_status = POWER_SUPPLY_STATUS_FULL;
di->maintenance_chg = true;
dev_dbg(di->dev, "EOC reached!\n");
@@ -733,12 +858,12 @@ static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di)
static void init_maxim_chg_curr(struct abx500_chargalg *di)
{
di->ccm.original_iset =
- di->bat->bat_type[di->bat->batt_id].normal_cur_lvl;
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl;
di->ccm.current_iset =
- di->bat->bat_type[di->bat->batt_id].normal_cur_lvl;
- di->ccm.test_delta_i = di->bat->maxi->charger_curr_step;
- di->ccm.max_current = di->bat->maxi->chg_curr;
- di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl;
+ di->ccm.test_delta_i = di->bm->maxi->charger_curr_step;
+ di->ccm.max_current = di->bm->maxi->chg_curr;
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
di->ccm.level = 0;
}
@@ -755,7 +880,7 @@ static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di)
{
int delta_i;
- if (!di->bat->maxi->ena_maxi)
+ if (!di->bm->maxi->ena_maxi)
return MAXIM_RET_NOACTION;
delta_i = di->ccm.original_iset - di->batt_data.inst_curr;
@@ -766,7 +891,7 @@ static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di)
if (di->ccm.wait_cnt == 0) {
dev_dbg(di->dev, "lowering current\n");
di->ccm.wait_cnt++;
- di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
di->ccm.max_current =
di->ccm.current_iset - di->ccm.test_delta_i;
di->ccm.current_iset = di->ccm.max_current;
@@ -791,7 +916,7 @@ static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di)
if (di->ccm.current_iset == di->ccm.original_iset)
return MAXIM_RET_NOACTION;
- di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
di->ccm.current_iset = di->ccm.original_iset;
di->ccm.level = 0;
@@ -803,7 +928,7 @@ static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di)
di->ccm.max_current) {
if (di->ccm.condition_cnt-- == 0) {
/* Increse the iset with cco.test_delta_i */
- di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
di->ccm.current_iset += di->ccm.test_delta_i;
di->ccm.level++;
dev_dbg(di->dev, " Maximization needed, increase"
@@ -818,7 +943,7 @@ static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di)
return MAXIM_RET_NOACTION;
}
} else {
- di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
return MAXIM_RET_NOACTION;
}
}
@@ -838,7 +963,7 @@ static void handle_maxim_chg_curr(struct abx500_chargalg *di)
break;
case MAXIM_RET_IBAT_TOO_HIGH:
result = abx500_chargalg_update_chg_curr(di,
- di->bat->bat_type[di->bat->batt_id].normal_cur_lvl);
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl);
if (result)
dev_err(di->dev, "failed to set chg curr\n");
break;
@@ -858,6 +983,7 @@ static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data)
union power_supply_propval ret;
int i, j;
bool psy_found = false;
+ bool capacity_updated = false;
psy = (struct power_supply *)data;
ext = dev_get_drvdata(dev);
@@ -870,6 +996,16 @@ static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data)
if (!psy_found)
return 0;
+ /*
+ * If external is not registering 'POWER_SUPPLY_PROP_CAPACITY' to its
+ * property because of handling that sysfs entry on its own, this is
+ * the place to get the battery capacity.
+ */
+ if (!ext->get_property(ext, POWER_SUPPLY_PROP_CAPACITY, &ret)) {
+ di->batt_data.percent = ret.intval;
+ capacity_updated = true;
+ }
+
/* Go through all properties for the psy */
for (j = 0; j < ext->num_properties; j++) {
enum power_supply_property prop;
@@ -1154,7 +1290,8 @@ static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data)
}
break;
case POWER_SUPPLY_PROP_CAPACITY:
- di->batt_data.percent = ret.intval;
+ if (!capacity_updated)
+ di->batt_data.percent = ret.intval;
break;
default:
break;
@@ -1194,6 +1331,8 @@ static void abx500_chargalg_external_power_changed(struct power_supply *psy)
static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
{
int charger_status;
+ int ret;
+ int curr_step_lvl;
/* Collect data from all power_supply class devices */
class_for_each_device(power_supply_class, NULL,
@@ -1204,13 +1343,22 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
abx500_chargalg_check_charger_voltage(di);
charger_status = abx500_chargalg_check_charger_connection(di);
+ abx500_chargalg_check_current_step_status(di);
+
+ if (is_ab8500(di->parent)) {
+ ret = abx500_chargalg_check_charger_enable(di);
+ if (ret < 0)
+ dev_err(di->dev, "Checking charger is enabled error"
+ ": Returned Value %d\n", ret);
+ }
+
/*
* First check if we have a charger connected.
* Also we don't allow charging of unknown batteries if configured
* this way
*/
if (!charger_status ||
- (di->events.batt_unknown && !di->bat->chg_unknown_bat)) {
+ (di->events.batt_unknown && !di->bm->chg_unknown_bat)) {
if (di->charge_state != STATE_HANDHELD) {
di->events.safety_timer_expired = false;
abx500_chargalg_state_to(di, STATE_HANDHELD_INIT);
@@ -1393,9 +1541,34 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
break;
case STATE_NORMAL_INIT:
- abx500_chargalg_start_charging(di,
- di->bat->bat_type[di->bat->batt_id].normal_vol_lvl,
- di->bat->bat_type[di->bat->batt_id].normal_cur_lvl);
+ if ((di->chg_info.charger_type & USB_CHG) &&
+ di->usb_chg->power_path) {
+ if (di->batt_data.volt >
+ (di->bm->fg_params->lowbat_threshold +
+ BAT_PLUS_MARGIN)) {
+ ab8540_chargalg_usb_pre_chg_en(di, false);
+ ab8540_chargalg_usb_pp_en(di, false);
+ } else {
+ ab8540_chargalg_usb_pp_en(di, true);
+ ab8540_chargalg_usb_pre_chg_en(di, true);
+ abx500_chargalg_state_to(di,
+ STATE_USB_PP_PRE_CHARGE);
+ break;
+ }
+ }
+
+ if (di->curr_status.curr_step == CHARGALG_CURR_STEP_LOW)
+ abx500_chargalg_stop_charging(di);
+ else {
+ curr_step_lvl = di->bm->bat_type[
+ di->bm->batt_id].normal_cur_lvl
+ * di->curr_status.curr_step
+ / CHARGALG_CURR_STEP_HIGH;
+ abx500_chargalg_start_charging(di,
+ di->bm->bat_type[di->bm->batt_id]
+ .normal_vol_lvl, curr_step_lvl);
+ }
+
abx500_chargalg_state_to(di, STATE_NORMAL);
abx500_chargalg_start_safety_timer(di);
abx500_chargalg_stop_maintenance_timer(di);
@@ -1407,11 +1580,18 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
break;
+ case STATE_USB_PP_PRE_CHARGE:
+ if (di->batt_data.volt >
+ (di->bm->fg_params->lowbat_threshold +
+ BAT_PLUS_MARGIN))
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
case STATE_NORMAL:
handle_maxim_chg_curr(di);
if (di->charge_status == POWER_SUPPLY_STATUS_FULL &&
di->maintenance_chg) {
- if (di->bat->no_maintenance)
+ if (di->bm->no_maintenance)
abx500_chargalg_state_to(di,
STATE_WAIT_FOR_RECHARGE_INIT);
else
@@ -1424,28 +1604,25 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
case STATE_WAIT_FOR_RECHARGE_INIT:
abx500_chargalg_hold_charging(di);
abx500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE);
- di->rch_cnt = RCH_COND_CNT;
/* Intentional fallthrough */
case STATE_WAIT_FOR_RECHARGE:
- if (di->batt_data.volt <=
- di->bat->bat_type[di->bat->batt_id].recharge_vol) {
- if (di->rch_cnt-- == 0)
- abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
- } else
- di->rch_cnt = RCH_COND_CNT;
+ if (di->batt_data.percent <=
+ di->bm->bat_type[di->bm->batt_id].
+ recharge_cap)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
break;
case STATE_MAINTENANCE_A_INIT:
abx500_chargalg_stop_safety_timer(di);
abx500_chargalg_start_maintenance_timer(di,
- di->bat->bat_type[
- di->bat->batt_id].maint_a_chg_timer_h);
+ di->bm->bat_type[
+ di->bm->batt_id].maint_a_chg_timer_h);
abx500_chargalg_start_charging(di,
- di->bat->bat_type[
- di->bat->batt_id].maint_a_vol_lvl,
- di->bat->bat_type[
- di->bat->batt_id].maint_a_cur_lvl);
+ di->bm->bat_type[
+ di->bm->batt_id].maint_a_vol_lvl,
+ di->bm->bat_type[
+ di->bm->batt_id].maint_a_cur_lvl);
abx500_chargalg_state_to(di, STATE_MAINTENANCE_A);
power_supply_changed(&di->chargalg_psy);
/* Intentional fallthrough*/
@@ -1459,13 +1636,13 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
case STATE_MAINTENANCE_B_INIT:
abx500_chargalg_start_maintenance_timer(di,
- di->bat->bat_type[
- di->bat->batt_id].maint_b_chg_timer_h);
+ di->bm->bat_type[
+ di->bm->batt_id].maint_b_chg_timer_h);
abx500_chargalg_start_charging(di,
- di->bat->bat_type[
- di->bat->batt_id].maint_b_vol_lvl,
- di->bat->bat_type[
- di->bat->batt_id].maint_b_cur_lvl);
+ di->bm->bat_type[
+ di->bm->batt_id].maint_b_vol_lvl,
+ di->bm->bat_type[
+ di->bm->batt_id].maint_b_cur_lvl);
abx500_chargalg_state_to(di, STATE_MAINTENANCE_B);
power_supply_changed(&di->chargalg_psy);
/* Intentional fallthrough*/
@@ -1479,10 +1656,10 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
case STATE_TEMP_LOWHIGH_INIT:
abx500_chargalg_start_charging(di,
- di->bat->bat_type[
- di->bat->batt_id].low_high_vol_lvl,
- di->bat->bat_type[
- di->bat->batt_id].low_high_cur_lvl);
+ di->bm->bat_type[
+ di->bm->batt_id].low_high_vol_lvl,
+ di->bm->bat_type[
+ di->bm->batt_id].low_high_cur_lvl);
abx500_chargalg_stop_maintenance_timer(di);
di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH);
@@ -1543,11 +1720,11 @@ static void abx500_chargalg_periodic_work(struct work_struct *work)
if (di->chg_info.conn_chg)
queue_delayed_work(di->chargalg_wq,
&di->chargalg_periodic_work,
- di->bat->interval_charging * HZ);
+ di->bm->interval_charging * HZ);
else
queue_delayed_work(di->chargalg_wq,
&di->chargalg_periodic_work,
- di->bat->interval_not_charging * HZ);
+ di->bm->interval_not_charging * HZ);
}
/**
@@ -1614,10 +1791,13 @@ static int abx500_chargalg_get_property(struct power_supply *psy,
if (di->events.batt_ovv) {
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
} else if (di->events.btemp_underover) {
- if (di->batt_data.temp <= di->bat->temp_under)
+ if (di->batt_data.temp <= di->bm->temp_under)
val->intval = POWER_SUPPLY_HEALTH_COLD;
else
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ } else if (di->charge_state == STATE_SAFETY_TIMER_EXPIRED ||
+ di->charge_state == STATE_SAFETY_TIMER_EXPIRED_INIT) {
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
} else {
val->intval = POWER_SUPPLY_HEALTH_GOOD;
}
@@ -1630,83 +1810,138 @@ static int abx500_chargalg_get_property(struct power_supply *psy,
/* Exposure to the sysfs interface */
-/**
- * abx500_chargalg_sysfs_charger() - sysfs store operations
- * @kobj: pointer to the struct kobject
- * @attr: pointer to the struct attribute
- * @buf: buffer that holds the parameter passed from userspace
- * @length: length of the parameter passed
- *
- * Returns length of the buffer(input taken from user space) on success
- * else error code on failure
- * The operation to be performed on passing the parameters from the user space.
- */
-static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj,
- struct attribute *attr, const char *buf, size_t length)
+static ssize_t abx500_chargalg_curr_step_show(struct abx500_chargalg *di,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", di->curr_status.curr_step);
+}
+
+static ssize_t abx500_chargalg_curr_step_store(struct abx500_chargalg *di,
+ const char *buf, size_t length)
{
- struct abx500_chargalg *di = container_of(kobj,
- struct abx500_chargalg, chargalg_kobject);
long int param;
- int ac_usb;
int ret;
- char entry = *attr->name;
- switch (entry) {
- case 'c':
- ret = strict_strtol(buf, 10, &param);
- if (ret < 0)
- return ret;
+ ret = kstrtol(buf, 10, &param);
+ if (ret < 0)
+ return ret;
- ac_usb = param;
- switch (ac_usb) {
- case 0:
- /* Disable charging */
- di->susp_status.ac_suspended = true;
- di->susp_status.usb_suspended = true;
- di->susp_status.suspended_change = true;
- /* Trigger a state change */
- queue_work(di->chargalg_wq,
- &di->chargalg_work);
- break;
- case 1:
- /* Enable AC Charging */
- di->susp_status.ac_suspended = false;
- di->susp_status.suspended_change = true;
- /* Trigger a state change */
- queue_work(di->chargalg_wq,
- &di->chargalg_work);
- break;
- case 2:
- /* Enable USB charging */
- di->susp_status.usb_suspended = false;
- di->susp_status.suspended_change = true;
- /* Trigger a state change */
- queue_work(di->chargalg_wq,
- &di->chargalg_work);
- break;
- default:
- dev_info(di->dev, "Wrong input\n"
- "Enter 0. Disable AC/USB Charging\n"
- "1. Enable AC charging\n"
- "2. Enable USB Charging\n");
- };
+ di->curr_status.curr_step = param;
+ if (di->curr_status.curr_step >= CHARGALG_CURR_STEP_LOW &&
+ di->curr_status.curr_step <= CHARGALG_CURR_STEP_HIGH) {
+ di->curr_status.curr_step_change = true;
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+ } else
+ dev_info(di->dev, "Wrong current step\n"
+ "Enter 0. Disable AC/USB Charging\n"
+ "1--100. Set AC/USB charging current step\n"
+ "100. Enable AC/USB Charging\n");
+
+ return strlen(buf);
+}
+
+
+static ssize_t abx500_chargalg_en_show(struct abx500_chargalg *di,
+ char *buf)
+{
+ return sprintf(buf, "%d\n",
+ di->susp_status.ac_suspended &&
+ di->susp_status.usb_suspended);
+}
+
+static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di,
+ const char *buf, size_t length)
+{
+ long int param;
+ int ac_usb;
+ int ret;
+
+ ret = kstrtol(buf, 10, &param);
+ if (ret < 0)
+ return ret;
+
+ ac_usb = param;
+ switch (ac_usb) {
+ case 0:
+ /* Disable charging */
+ di->susp_status.ac_suspended = true;
+ di->susp_status.usb_suspended = true;
+ di->susp_status.suspended_change = true;
+ /* Trigger a state change */
+ queue_work(di->chargalg_wq,
+ &di->chargalg_work);
+ break;
+ case 1:
+ /* Enable AC Charging */
+ di->susp_status.ac_suspended = false;
+ di->susp_status.suspended_change = true;
+ /* Trigger a state change */
+ queue_work(di->chargalg_wq,
+ &di->chargalg_work);
break;
+ case 2:
+ /* Enable USB charging */
+ di->susp_status.usb_suspended = false;
+ di->susp_status.suspended_change = true;
+ /* Trigger a state change */
+ queue_work(di->chargalg_wq,
+ &di->chargalg_work);
+ break;
+ default:
+ dev_info(di->dev, "Wrong input\n"
+ "Enter 0. Disable AC/USB Charging\n"
+ "1. Enable AC charging\n"
+ "2. Enable USB Charging\n");
};
return strlen(buf);
}
-static struct attribute abx500_chargalg_en_charger = \
+static struct abx500_chargalg_sysfs_entry abx500_chargalg_en_charger =
+ __ATTR(chargalg, 0644, abx500_chargalg_en_show,
+ abx500_chargalg_en_store);
+
+static struct abx500_chargalg_sysfs_entry abx500_chargalg_curr_step =
+ __ATTR(chargalg_curr_step, 0644, abx500_chargalg_curr_step_show,
+ abx500_chargalg_curr_step_store);
+
+static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
{
- .name = "chargalg",
- .mode = S_IWUGO,
-};
+ struct abx500_chargalg_sysfs_entry *entry = container_of(attr,
+ struct abx500_chargalg_sysfs_entry, attr);
+
+ struct abx500_chargalg *di = container_of(kobj,
+ struct abx500_chargalg, chargalg_kobject);
+
+ if (!entry->show)
+ return -EIO;
+
+ return entry->show(di, buf);
+}
+
+static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj,
+ struct attribute *attr, const char *buf, size_t length)
+{
+ struct abx500_chargalg_sysfs_entry *entry = container_of(attr,
+ struct abx500_chargalg_sysfs_entry, attr);
+
+ struct abx500_chargalg *di = container_of(kobj,
+ struct abx500_chargalg, chargalg_kobject);
+
+ if (!entry->store)
+ return -EIO;
+
+ return entry->store(di, buf, length);
+}
static struct attribute *abx500_chargalg_chg[] = {
- &abx500_chargalg_en_charger,
- NULL
+ &abx500_chargalg_en_charger.attr,
+ &abx500_chargalg_curr_step.attr,
+ NULL,
};
static const struct sysfs_ops abx500_chargalg_sysfs_ops = {
+ .show = abx500_chargalg_sysfs_show,
.store = abx500_chargalg_sysfs_charger,
};
@@ -1789,12 +2024,17 @@ static int abx500_chargalg_remove(struct platform_device *pdev)
/* sysfs interface to enable/disbale charging from user space */
abx500_chargalg_sysfs_exit(di);
+ hrtimer_cancel(&di->safety_timer);
+ hrtimer_cancel(&di->maintenance_timer);
+
+ cancel_delayed_work_sync(&di->chargalg_periodic_work);
+ cancel_delayed_work_sync(&di->chargalg_wd_work);
+ cancel_work_sync(&di->chargalg_work);
+
/* Delete the work queue */
destroy_workqueue(di->chargalg_wq);
- flush_scheduled_work();
power_supply_unregister(&di->chargalg_psy);
- platform_set_drvdata(pdev, NULL);
return 0;
}
@@ -1806,6 +2046,7 @@ static char *supply_interface[] = {
static int abx500_chargalg_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ struct abx500_bm_data *plat = pdev->dev.platform_data;
struct abx500_chargalg *di;
int ret = 0;
@@ -1814,25 +2055,24 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
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;
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no battery management data supplied\n");
+ return -EINVAL;
+ }
+ di->bm = plat;
+
+ if (np) {
+ ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get battery information\n");
+ return ret;
}
- } else {
- dev_info(&pdev->dev, "falling back to legacy platform data\n");
}
- /* get device struct */
+ /* get device struct and parent */
di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
/* chargalg supply */
di->chargalg_psy.name = "abx500_chargalg";
@@ -1846,15 +2086,13 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
abx500_chargalg_external_power_changed;
/* Initilialize safety timer */
- init_timer(&di->safety_timer);
+ hrtimer_init(&di->safety_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
di->safety_timer.function = abx500_chargalg_safety_timer_expired;
- di->safety_timer.data = (unsigned long) di;
/* Initilialize maintenance timer */
- init_timer(&di->maintenance_timer);
+ hrtimer_init(&di->maintenance_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
di->maintenance_timer.function =
abx500_chargalg_maintenance_timer_expired;
- di->maintenance_timer.data = (unsigned long) di;
/* Create a work queue for the chargalg */
di->chargalg_wq =
@@ -1891,6 +2129,7 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
dev_err(di->dev, "failed to create sysfs entry\n");
goto free_psy;
}
+ di->curr_status.curr_step = CHARGALG_CURR_STEP_HIGH;
/* Run the charging algorithm */
queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
@@ -1922,18 +2161,7 @@ static struct platform_driver abx500_chargalg_driver = {
},
};
-static int __init abx500_chargalg_init(void)
-{
- return platform_driver_register(&abx500_chargalg_driver);
-}
-
-static void __exit abx500_chargalg_exit(void)
-{
- platform_driver_unregister(&abx500_chargalg_driver);
-}
-
-module_init(abx500_chargalg_init);
-module_exit(abx500_chargalg_exit);
+module_platform_driver(abx500_chargalg_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
diff --git a/drivers/power/avs/smartreflex.c b/drivers/power/avs/smartreflex.c
index 6b2238bb6a8..db9973bb53f 100644
--- a/drivers/power/avs/smartreflex.c
+++ b/drivers/power/avs/smartreflex.c
@@ -27,7 +27,8 @@
#include <linux/pm_runtime.h>
#include <linux/power/smartreflex.h>
-#define SMARTREFLEX_NAME_LEN 16
+#define DRIVER_NAME "smartreflex"
+#define SMARTREFLEX_NAME_LEN 32
#define NVALUE_NAME_LEN 40
#define SR_DISABLE_TIMEOUT 200
@@ -207,12 +208,11 @@ static void sr_stop_vddautocomp(struct omap_sr *sr)
static int sr_late_init(struct omap_sr *sr_info)
{
struct omap_sr_data *pdata = sr_info->pdev->dev.platform_data;
- struct resource *mem;
int ret = 0;
if (sr_class->notify && sr_class->notify_flags && sr_info->irq) {
- ret = request_irq(sr_info->irq, sr_interrupt,
- 0, sr_info->name, sr_info);
+ ret = devm_request_irq(&sr_info->pdev->dev, sr_info->irq,
+ sr_interrupt, 0, sr_info->name, sr_info);
if (ret)
goto error;
disable_irq(sr_info->irq);
@@ -224,14 +224,10 @@ static int sr_late_init(struct omap_sr *sr_info)
return ret;
error:
- iounmap(sr_info->base);
- mem = platform_get_resource(sr_info->pdev, IORESOURCE_MEM, 0);
- release_mem_region(mem->start, resource_size(mem));
list_del(&sr_info->node);
dev_err(&sr_info->pdev->dev, "%s: ERROR in registering"
"interrupt handler. Smartreflex will"
"not function as desired\n", __func__);
- kfree(sr_info);
return ret;
}
@@ -341,9 +337,9 @@ static struct omap_sr_nvalue_table *sr_retrieve_nvalue_row(
/* Public Functions */
/**
- * sr_configure_errgen() - Configures the smrtreflex to perform AVS using the
+ * sr_configure_errgen() - Configures the SmartReflex to perform AVS using the
* error generator module.
- * @voltdm: VDD pointer to which the SR module to be configured belongs to.
+ * @sr: SR module to be configured.
*
* This API is to be called from the smartreflex class driver to
* configure the error generator module inside the smartreflex module.
@@ -352,17 +348,17 @@ static struct omap_sr_nvalue_table *sr_retrieve_nvalue_row(
* SR CLASS 2 can choose between ERROR module and MINMAXAVG
* module. Returns 0 on success and error value in case of failure.
*/
-int sr_configure_errgen(struct voltagedomain *voltdm)
+int sr_configure_errgen(struct omap_sr *sr)
{
u32 sr_config, sr_errconfig, errconfig_offs;
u32 vpboundint_en, vpboundint_st;
u32 senp_en = 0, senn_en = 0;
u8 senp_shift, senn_shift;
- struct omap_sr *sr = _sr_lookup(voltdm);
- if (IS_ERR(sr)) {
- pr_warning("%s: omap_sr struct for voltdm not found\n", __func__);
- return PTR_ERR(sr);
+ if (!sr) {
+ pr_warn("%s: NULL omap_sr from %pF\n", __func__,
+ (void *)_RET_IP_);
+ return -EINVAL;
}
if (!sr->clk_length)
@@ -414,22 +410,22 @@ int sr_configure_errgen(struct voltagedomain *voltdm)
/**
* sr_disable_errgen() - Disables SmartReflex AVS module's errgen component
- * @voltdm: VDD pointer to which the SR module to be configured belongs to.
+ * @sr: SR module to be configured.
*
* This API is to be called from the smartreflex class driver to
* disable the error generator module inside the smartreflex module.
*
* Returns 0 on success and error value in case of failure.
*/
-int sr_disable_errgen(struct voltagedomain *voltdm)
+int sr_disable_errgen(struct omap_sr *sr)
{
u32 errconfig_offs;
u32 vpboundint_en, vpboundint_st;
- struct omap_sr *sr = _sr_lookup(voltdm);
- if (IS_ERR(sr)) {
- pr_warning("%s: omap_sr struct for voltdm not found\n", __func__);
- return PTR_ERR(sr);
+ if (!sr) {
+ pr_warn("%s: NULL omap_sr from %pF\n", __func__,
+ (void *)_RET_IP_);
+ return -EINVAL;
}
switch (sr->ip_type) {
@@ -449,19 +445,24 @@ int sr_disable_errgen(struct voltagedomain *voltdm)
return -EINVAL;
}
- /* Disable the interrupts of ERROR module */
- sr_modify_reg(sr, errconfig_offs, vpboundint_en | vpboundint_st, 0);
-
/* Disable the Sensor and errorgen */
sr_modify_reg(sr, SRCONFIG, SRCONFIG_SENENABLE | SRCONFIG_ERRGEN_EN, 0);
+ /*
+ * Disable the interrupts of ERROR module
+ * NOTE: modify is a read, modify,write - an implicit OCP barrier
+ * which is required is present here - sequencing is critical
+ * at this point (after errgen is disabled, vpboundint disable)
+ */
+ sr_modify_reg(sr, errconfig_offs, vpboundint_en | vpboundint_st, 0);
+
return 0;
}
/**
- * sr_configure_minmax() - Configures the smrtreflex to perform AVS using the
+ * sr_configure_minmax() - Configures the SmartReflex to perform AVS using the
* minmaxavg module.
- * @voltdm: VDD pointer to which the SR module to be configured belongs to.
+ * @sr: SR module to be configured.
*
* This API is to be called from the smartreflex class driver to
* configure the minmaxavg module inside the smartreflex module.
@@ -470,16 +471,16 @@ int sr_disable_errgen(struct voltagedomain *voltdm)
* SR CLASS 2 can choose between ERROR module and MINMAXAVG
* module. Returns 0 on success and error value in case of failure.
*/
-int sr_configure_minmax(struct voltagedomain *voltdm)
+int sr_configure_minmax(struct omap_sr *sr)
{
u32 sr_config, sr_avgwt;
u32 senp_en = 0, senn_en = 0;
u8 senp_shift, senn_shift;
- struct omap_sr *sr = _sr_lookup(voltdm);
- if (IS_ERR(sr)) {
- pr_warning("%s: omap_sr struct for voltdm not found\n", __func__);
- return PTR_ERR(sr);
+ if (!sr) {
+ pr_warn("%s: NULL omap_sr from %pF\n", __func__,
+ (void *)_RET_IP_);
+ return -EINVAL;
}
if (!sr->clk_length)
@@ -546,7 +547,7 @@ int sr_configure_minmax(struct voltagedomain *voltdm)
/**
* sr_enable() - Enables the smartreflex module.
- * @voltdm: VDD pointer to which the SR module to be configured belongs to.
+ * @sr: pointer to which the SR module to be configured belongs to.
* @volt: The voltage at which the Voltage domain associated with
* the smartreflex module is operating at.
* This is required only to program the correct Ntarget value.
@@ -555,16 +556,16 @@ int sr_configure_minmax(struct voltagedomain *voltdm)
* enable a smartreflex module. Returns 0 on success. Returns error
* value if the voltage passed is wrong or if ntarget value is wrong.
*/
-int sr_enable(struct voltagedomain *voltdm, unsigned long volt)
+int sr_enable(struct omap_sr *sr, unsigned long volt)
{
struct omap_volt_data *volt_data;
- struct omap_sr *sr = _sr_lookup(voltdm);
struct omap_sr_nvalue_table *nvalue_row;
int ret;
- if (IS_ERR(sr)) {
- pr_warning("%s: omap_sr struct for voltdm not found\n", __func__);
- return PTR_ERR(sr);
+ if (!sr) {
+ pr_warn("%s: NULL omap_sr from %pF\n", __func__,
+ (void *)_RET_IP_);
+ return -EINVAL;
}
volt_data = omap_voltage_get_voltdata(sr->voltdm, volt);
@@ -606,17 +607,16 @@ int sr_enable(struct voltagedomain *voltdm, unsigned long volt)
/**
* sr_disable() - Disables the smartreflex module.
- * @voltdm: VDD pointer to which the SR module to be configured belongs to.
+ * @sr: pointer to which the SR module to be configured belongs to.
*
* This API is to be called from the smartreflex class driver to
* disable a smartreflex module.
*/
-void sr_disable(struct voltagedomain *voltdm)
+void sr_disable(struct omap_sr *sr)
{
- struct omap_sr *sr = _sr_lookup(voltdm);
-
- if (IS_ERR(sr)) {
- pr_warning("%s: omap_sr struct for voltdm not found\n", __func__);
+ if (!sr) {
+ pr_warn("%s: NULL omap_sr from %pF\n", __func__,
+ (void *)_RET_IP_);
return;
}
@@ -847,34 +847,33 @@ static int __init omap_sr_probe(struct platform_device *pdev)
struct dentry *nvalue_dir;
int i, ret = 0;
- sr_info = kzalloc(sizeof(struct omap_sr), GFP_KERNEL);
+ sr_info = devm_kzalloc(&pdev->dev, sizeof(struct omap_sr), GFP_KERNEL);
if (!sr_info) {
dev_err(&pdev->dev, "%s: unable to allocate sr_info\n",
__func__);
return -ENOMEM;
}
+ sr_info->name = devm_kzalloc(&pdev->dev,
+ SMARTREFLEX_NAME_LEN, GFP_KERNEL);
+ if (!sr_info->name) {
+ dev_err(&pdev->dev, "%s: unable to allocate SR instance name\n",
+ __func__);
+ return -ENOMEM;
+ }
+
platform_set_drvdata(pdev, sr_info);
if (!pdata) {
dev_err(&pdev->dev, "%s: platform data missing\n", __func__);
- ret = -EINVAL;
- goto err_free_devinfo;
+ return -EINVAL;
}
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!mem) {
- dev_err(&pdev->dev, "%s: no mem resource\n", __func__);
- ret = -ENODEV;
- goto err_free_devinfo;
- }
-
- mem = request_mem_region(mem->start, resource_size(mem),
- dev_name(&pdev->dev));
- if (!mem) {
- dev_err(&pdev->dev, "%s: no mem region\n", __func__);
- ret = -EBUSY;
- goto err_free_devinfo;
+ sr_info->base = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(sr_info->base)) {
+ dev_err(&pdev->dev, "%s: ioremap fail\n", __func__);
+ return PTR_ERR(sr_info->base);
}
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
@@ -882,13 +881,7 @@ static int __init omap_sr_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
pm_runtime_irq_safe(&pdev->dev);
- sr_info->name = kasprintf(GFP_KERNEL, "%s", pdata->name);
- if (!sr_info->name) {
- dev_err(&pdev->dev, "%s: Unable to alloc SR instance name\n",
- __func__);
- ret = -ENOMEM;
- goto err_release_region;
- }
+ snprintf(sr_info->name, SMARTREFLEX_NAME_LEN, "%s", pdata->name);
sr_info->pdev = pdev;
sr_info->srid = pdev->id;
@@ -905,13 +898,6 @@ static int __init omap_sr_probe(struct platform_device *pdev)
sr_info->autocomp_active = false;
sr_info->ip_type = pdata->ip_type;
- sr_info->base = ioremap(mem->start, resource_size(mem));
- if (!sr_info->base) {
- dev_err(&pdev->dev, "%s: ioremap fail\n", __func__);
- ret = -ENOMEM;
- goto err_free_name;
- }
-
if (irq)
sr_info->irq = irq->start;
@@ -927,7 +913,7 @@ static int __init omap_sr_probe(struct platform_device *pdev)
ret = sr_late_init(sr_info);
if (ret) {
pr_warning("%s: Error in SR late init\n", __func__);
- goto err_iounmap;
+ goto err_list_del;
}
}
@@ -938,7 +924,7 @@ static int __init omap_sr_probe(struct platform_device *pdev)
ret = PTR_ERR(sr_dbg_dir);
pr_err("%s:sr debugfs dir creation failed(%d)\n",
__func__, ret);
- goto err_iounmap;
+ goto err_list_del;
}
}
@@ -991,16 +977,8 @@ static int __init omap_sr_probe(struct platform_device *pdev)
err_debugfs:
debugfs_remove_recursive(sr_info->dbg_dir);
-err_iounmap:
+err_list_del:
list_del(&sr_info->node);
- iounmap(sr_info->base);
-err_free_name:
- kfree(sr_info->name);
-err_release_region:
- release_mem_region(mem->start, resource_size(mem));
-err_free_devinfo:
- kfree(sr_info);
-
return ret;
}
@@ -1008,7 +986,6 @@ static int omap_sr_remove(struct platform_device *pdev)
{
struct omap_sr_data *pdata = pdev->dev.platform_data;
struct omap_sr *sr_info;
- struct resource *mem;
if (!pdata) {
dev_err(&pdev->dev, "%s: platform data missing\n", __func__);
@@ -1027,13 +1004,8 @@ static int omap_sr_remove(struct platform_device *pdev)
if (sr_info->dbg_dir)
debugfs_remove_recursive(sr_info->dbg_dir);
+ pm_runtime_disable(&pdev->dev);
list_del(&sr_info->node);
- iounmap(sr_info->base);
- kfree(sr_info->name);
- kfree(sr_info);
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- release_mem_region(mem->start, resource_size(mem));
-
return 0;
}
@@ -1064,7 +1036,7 @@ static struct platform_driver smartreflex_driver = {
.remove = omap_sr_remove,
.shutdown = omap_sr_shutdown,
.driver = {
- .name = "smartreflex",
+ .name = DRIVER_NAME,
},
};
diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c
index ee842b37f46..79a37f6d330 100644
--- a/drivers/power/bq2415x_charger.c
+++ b/drivers/power/bq2415x_charger.c
@@ -1,7 +1,7 @@
/*
* bq2415x charger driver
*
- * Copyright (C) 2011-2012 Pali Rohár <pali.rohar@gmail.com>
+ * Copyright (C) 2011-2013 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
@@ -28,7 +28,6 @@
* http://www.ti.com/product/bq24155
*/
-#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/param.h>
@@ -171,6 +170,8 @@ struct bq2415x_device {
struct bq2415x_platform_data init_data;
struct power_supply charger;
struct delayed_work work;
+ struct power_supply *notify_psy;
+ struct notifier_block nb;
enum bq2415x_mode reported_mode;/* mode reported by hook function */
enum bq2415x_mode mode; /* current configured mode */
enum bq2415x_chip chip;
@@ -606,9 +607,13 @@ static int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq,
{
int val = (mV/10 - 350) / 2;
+ /*
+ * According to datasheet, maximum battery regulation voltage is
+ * 4440mV which is b101111 = 47.
+ */
if (val < 0)
val = 0;
- else if (val > 94) /* FIXME: Max is 94 or 122 ? Set max value ? */
+ else if (val > 47)
return -EINVAL;
return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val,
@@ -734,12 +739,10 @@ static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
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;
+ else if (mode != BQ2415X_MODE_OFF)
+ charger = 1;
if (!charger)
ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
@@ -751,6 +754,10 @@ static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
return ret;
switch (mode) {
+ case BQ2415X_MODE_OFF:
+ dev_dbg(bq->dev, "changing mode to: Offline\n");
+ ret = bq2415x_set_current_limit(bq, 100);
+ break;
case BQ2415X_MODE_NONE:
dev_dbg(bq->dev, "changing mode to: N/A\n");
ret = bq2415x_set_current_limit(bq, 100);
@@ -790,24 +797,53 @@ static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
}
-/* hook function called by other driver which set reported mode */
-static void bq2415x_hook_function(enum bq2415x_mode mode, void *data)
+static int bq2415x_notifier_call(struct notifier_block *nb,
+ unsigned long val, void *v)
{
- struct bq2415x_device *bq = data;
+ struct bq2415x_device *bq =
+ container_of(nb, struct bq2415x_device, nb);
+ struct power_supply *psy = v;
+ enum bq2415x_mode mode;
+ union power_supply_propval prop;
+ int ret;
+ int mA;
- if (!bq)
- return;
+ if (val != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_OK;
+
+ if (psy != bq->notify_psy)
+ return NOTIFY_OK;
+
+ dev_dbg(bq->dev, "notifier call was called\n");
+
+ ret = psy->get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
+ if (ret != 0)
+ return NOTIFY_OK;
+
+ mA = prop.intval;
+
+ if (mA == 0)
+ mode = BQ2415X_MODE_OFF;
+ else if (mA < 500)
+ mode = BQ2415X_MODE_NONE;
+ else if (mA < 1800)
+ mode = BQ2415X_MODE_HOST_CHARGER;
+ else
+ mode = BQ2415X_MODE_DEDICATED_CHARGER;
+
+ if (bq->reported_mode == mode)
+ return NOTIFY_OK;
- 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;
+ return NOTIFY_OK;
sysfs_notify(&bq->charger.dev->kobj, NULL, "reported_mode");
bq2415x_set_mode(bq, bq->reported_mode);
+ return NOTIFY_OK;
}
/**** timer functions ****/
@@ -843,7 +879,7 @@ static void bq2415x_timer_error(struct bq2415x_device *bq, const char *msg)
dev_err(bq->dev, "%s\n", msg);
if (bq->automode > 0)
bq->automode = 0;
- bq2415x_set_mode(bq, BQ2415X_MODE_NONE);
+ bq2415x_set_mode(bq, BQ2415X_MODE_OFF);
bq2415x_set_autotimer(bq, 0);
}
@@ -1136,6 +1172,10 @@ static ssize_t bq2415x_sysfs_set_mode(struct device *dev,
return -ENOSYS;
bq->automode = 1;
mode = bq->reported_mode;
+ } else if (strncmp(buf, "off", 3) == 0) {
+ if (bq->automode > 0)
+ bq->automode = 0;
+ mode = BQ2415X_MODE_OFF;
} else if (strncmp(buf, "none", 4) == 0) {
if (bq->automode > 0)
bq->automode = 0;
@@ -1183,6 +1223,9 @@ static ssize_t bq2415x_sysfs_show_mode(struct device *dev,
ret += sprintf(buf+ret, "auto (");
switch (bq->mode) {
+ case BQ2415X_MODE_OFF:
+ ret += sprintf(buf+ret, "off");
+ break;
case BQ2415X_MODE_NONE:
ret += sprintf(buf+ret, "none");
break;
@@ -1217,6 +1260,8 @@ static ssize_t bq2415x_sysfs_show_reported_mode(struct device *dev,
return -EINVAL;
switch (bq->reported_mode) {
+ case BQ2415X_MODE_OFF:
+ return sprintf(buf, "off\n");
case BQ2415X_MODE_NONE:
return sprintf(buf, "none\n");
case BQ2415X_MODE_HOST_CHARGER:
@@ -1498,23 +1543,20 @@ static int bq2415x_probe(struct i2c_client *client,
int num;
char *name;
struct bq2415x_device *bq;
+ struct device_node *np = client->dev.of_node;
+ struct bq2415x_platform_data *pdata = client->dev.platform_data;
- if (!client->dev.platform_data) {
- dev_err(&client->dev, "platform data not set\n");
+ if (!np && !pdata) {
+ dev_err(&client->dev, "platform data missing\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);
+ num = idr_alloc(&bq2415x_id, client, 0, 0, GFP_KERNEL);
mutex_unlock(&bq2415x_id_mutex);
-
- if (ret < 0)
- return ret;
+ if (num < 0)
+ return num;
name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
if (!name) {
@@ -1523,57 +1565,98 @@ static int bq2415x_probe(struct i2c_client *client,
goto error_1;
}
- bq = kzalloc(sizeof(*bq), GFP_KERNEL);
+ bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL);
if (!bq) {
dev_err(&client->dev, "failed to allocate device data\n");
ret = -ENOMEM;
goto error_2;
}
+ if (np) {
+ bq->notify_psy = power_supply_get_by_phandle(np, "ti,usb-charger-detection");
+
+ if (!bq->notify_psy)
+ return -EPROBE_DEFER;
+ }
+ else if (pdata->notify_device)
+ bq->notify_psy = power_supply_get_by_name(pdata->notify_device);
+ else
+ bq->notify_psy = NULL;
+
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->mode = BQ2415X_MODE_OFF;
+ bq->reported_mode = BQ2415X_MODE_OFF;
bq->autotimer = 0;
bq->automode = 0;
- memcpy(&bq->init_data, client->dev.platform_data,
- sizeof(bq->init_data));
+ if (np) {
+ ret = of_property_read_u32(np, "ti,current-limit",
+ &bq->init_data.current_limit);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,weak-battery-voltage",
+ &bq->init_data.weak_battery_voltage);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,battery-regulation-voltage",
+ &bq->init_data.battery_regulation_voltage);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,charge-current",
+ &bq->init_data.charge_current);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,termination-current",
+ &bq->init_data.termination_current);
+ if (ret)
+ return ret;
+ ret = of_property_read_u32(np, "ti,resistor-sense",
+ &bq->init_data.resistor_sense);
+ if (ret)
+ return ret;
+ } else {
+ memcpy(&bq->init_data, pdata, 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;
+ goto error_2;
}
ret = bq2415x_sysfs_init(bq);
if (ret) {
dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret);
- goto error_4;
+ goto error_3;
}
ret = bq2415x_set_defaults(bq);
if (ret) {
dev_err(bq->dev, "failed to set default values: %d\n", ret);
- goto error_5;
+ goto error_4;
}
- 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");
+ if (bq->notify_psy) {
+ bq->nb.notifier_call = bq2415x_notifier_call;
+ ret = power_supply_reg_notifier(&bq->nb);
+ if (ret) {
+ dev_err(bq->dev, "failed to reg notifier: %d\n", ret);
+ goto error_5;
}
+
+ /* Query for initial reported_mode and set it */
+ bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, bq->notify_psy);
+ bq2415x_set_mode(bq, bq->reported_mode);
+
+ bq->automode = 1;
+ dev_info(bq->dev, "automode enabled\n");
} else {
bq->automode = -1;
dev_info(bq->dev, "automode not supported\n");
@@ -1586,11 +1669,10 @@ static int bq2415x_probe(struct i2c_client *client,
return 0;
error_5:
- bq2415x_sysfs_exit(bq);
error_4:
- bq2415x_power_supply_exit(bq);
+ bq2415x_sysfs_exit(bq);
error_3:
- kfree(bq);
+ bq2415x_power_supply_exit(bq);
error_2:
kfree(name);
error_1:
@@ -1607,8 +1689,8 @@ 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);
+ if (bq->notify_psy)
+ power_supply_unreg_notifier(&bq->nb);
bq2415x_sysfs_exit(bq);
bq2415x_power_supply_exit(bq);
@@ -1622,7 +1704,6 @@ static int bq2415x_remove(struct i2c_client *client)
dev_info(bq->dev, "driver unregistered\n");
kfree(bq->name);
- kfree(bq);
return 0;
}
@@ -1652,18 +1733,7 @@ static struct i2c_driver bq2415x_driver = {
.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_i2c_driver(bq2415x_driver);
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
MODULE_DESCRIPTION("bq2415x charger driver");
diff --git a/drivers/power/bq24190_charger.c b/drivers/power/bq24190_charger.c
new file mode 100644
index 00000000000..ad3ff8fbfbb
--- /dev/null
+++ b/drivers/power/bq24190_charger.c
@@ -0,0 +1,1549 @@
+/*
+ * Driver for the TI bq24190 battery charger.
+ *
+ * Author: Mark A. Greer <mgreer@animalcreek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/of_irq.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/power_supply.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+
+#include <linux/power/bq24190_charger.h>
+
+
+#define BQ24190_MANUFACTURER "Texas Instruments"
+
+#define BQ24190_REG_ISC 0x00 /* Input Source Control */
+#define BQ24190_REG_ISC_EN_HIZ_MASK BIT(7)
+#define BQ24190_REG_ISC_EN_HIZ_SHIFT 7
+#define BQ24190_REG_ISC_VINDPM_MASK (BIT(6) | BIT(5) | BIT(4) | \
+ BIT(3))
+#define BQ24190_REG_ISC_VINDPM_SHIFT 3
+#define BQ24190_REG_ISC_IINLIM_MASK (BIT(2) | BIT(1) | BIT(0))
+#define BQ24190_REG_ISC_IINLIM_SHIFT 0
+
+#define BQ24190_REG_POC 0x01 /* Power-On Configuration */
+#define BQ24190_REG_POC_RESET_MASK BIT(7)
+#define BQ24190_REG_POC_RESET_SHIFT 7
+#define BQ24190_REG_POC_WDT_RESET_MASK BIT(6)
+#define BQ24190_REG_POC_WDT_RESET_SHIFT 6
+#define BQ24190_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4))
+#define BQ24190_REG_POC_CHG_CONFIG_SHIFT 4
+#define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1))
+#define BQ24190_REG_POC_SYS_MIN_SHIFT 1
+#define BQ24190_REG_POC_BOOST_LIM_MASK BIT(0)
+#define BQ24190_REG_POC_BOOST_LIM_SHIFT 0
+
+#define BQ24190_REG_CCC 0x02 /* Charge Current Control */
+#define BQ24190_REG_CCC_ICHG_MASK (BIT(7) | BIT(6) | BIT(5) | \
+ BIT(4) | BIT(3) | BIT(2))
+#define BQ24190_REG_CCC_ICHG_SHIFT 2
+#define BQ24190_REG_CCC_FORCE_20PCT_MASK BIT(0)
+#define BQ24190_REG_CCC_FORCE_20PCT_SHIFT 0
+
+#define BQ24190_REG_PCTCC 0x03 /* Pre-charge/Termination Current Cntl */
+#define BQ24190_REG_PCTCC_IPRECHG_MASK (BIT(7) | BIT(6) | BIT(5) | \
+ BIT(4))
+#define BQ24190_REG_PCTCC_IPRECHG_SHIFT 4
+#define BQ24190_REG_PCTCC_ITERM_MASK (BIT(3) | BIT(2) | BIT(1) | \
+ BIT(0))
+#define BQ24190_REG_PCTCC_ITERM_SHIFT 0
+
+#define BQ24190_REG_CVC 0x04 /* Charge Voltage Control */
+#define BQ24190_REG_CVC_VREG_MASK (BIT(7) | BIT(6) | BIT(5) | \
+ BIT(4) | BIT(3) | BIT(2))
+#define BQ24190_REG_CVC_VREG_SHIFT 2
+#define BQ24190_REG_CVC_BATLOWV_MASK BIT(1)
+#define BQ24190_REG_CVC_BATLOWV_SHIFT 1
+#define BQ24190_REG_CVC_VRECHG_MASK BIT(0)
+#define BQ24190_REG_CVC_VRECHG_SHIFT 0
+
+#define BQ24190_REG_CTTC 0x05 /* Charge Term/Timer Control */
+#define BQ24190_REG_CTTC_EN_TERM_MASK BIT(7)
+#define BQ24190_REG_CTTC_EN_TERM_SHIFT 7
+#define BQ24190_REG_CTTC_TERM_STAT_MASK BIT(6)
+#define BQ24190_REG_CTTC_TERM_STAT_SHIFT 6
+#define BQ24190_REG_CTTC_WATCHDOG_MASK (BIT(5) | BIT(4))
+#define BQ24190_REG_CTTC_WATCHDOG_SHIFT 4
+#define BQ24190_REG_CTTC_EN_TIMER_MASK BIT(3)
+#define BQ24190_REG_CTTC_EN_TIMER_SHIFT 3
+#define BQ24190_REG_CTTC_CHG_TIMER_MASK (BIT(2) | BIT(1))
+#define BQ24190_REG_CTTC_CHG_TIMER_SHIFT 1
+#define BQ24190_REG_CTTC_JEITA_ISET_MASK BIT(0)
+#define BQ24190_REG_CTTC_JEITA_ISET_SHIFT 0
+
+#define BQ24190_REG_ICTRC 0x06 /* IR Comp/Thermal Regulation Control */
+#define BQ24190_REG_ICTRC_BAT_COMP_MASK (BIT(7) | BIT(6) | BIT(5))
+#define BQ24190_REG_ICTRC_BAT_COMP_SHIFT 5
+#define BQ24190_REG_ICTRC_VCLAMP_MASK (BIT(4) | BIT(3) | BIT(2))
+#define BQ24190_REG_ICTRC_VCLAMP_SHIFT 2
+#define BQ24190_REG_ICTRC_TREG_MASK (BIT(1) | BIT(0))
+#define BQ24190_REG_ICTRC_TREG_SHIFT 0
+
+#define BQ24190_REG_MOC 0x07 /* Misc. Operation Control */
+#define BQ24190_REG_MOC_DPDM_EN_MASK BIT(7)
+#define BQ24190_REG_MOC_DPDM_EN_SHIFT 7
+#define BQ24190_REG_MOC_TMR2X_EN_MASK BIT(6)
+#define BQ24190_REG_MOC_TMR2X_EN_SHIFT 6
+#define BQ24190_REG_MOC_BATFET_DISABLE_MASK BIT(5)
+#define BQ24190_REG_MOC_BATFET_DISABLE_SHIFT 5
+#define BQ24190_REG_MOC_JEITA_VSET_MASK BIT(4)
+#define BQ24190_REG_MOC_JEITA_VSET_SHIFT 4
+#define BQ24190_REG_MOC_INT_MASK_MASK (BIT(1) | BIT(0))
+#define BQ24190_REG_MOC_INT_MASK_SHIFT 0
+
+#define BQ24190_REG_SS 0x08 /* System Status */
+#define BQ24190_REG_SS_VBUS_STAT_MASK (BIT(7) | BIT(6))
+#define BQ24190_REG_SS_VBUS_STAT_SHIFT 6
+#define BQ24190_REG_SS_CHRG_STAT_MASK (BIT(5) | BIT(4))
+#define BQ24190_REG_SS_CHRG_STAT_SHIFT 4
+#define BQ24190_REG_SS_DPM_STAT_MASK BIT(3)
+#define BQ24190_REG_SS_DPM_STAT_SHIFT 3
+#define BQ24190_REG_SS_PG_STAT_MASK BIT(2)
+#define BQ24190_REG_SS_PG_STAT_SHIFT 2
+#define BQ24190_REG_SS_THERM_STAT_MASK BIT(1)
+#define BQ24190_REG_SS_THERM_STAT_SHIFT 1
+#define BQ24190_REG_SS_VSYS_STAT_MASK BIT(0)
+#define BQ24190_REG_SS_VSYS_STAT_SHIFT 0
+
+#define BQ24190_REG_F 0x09 /* Fault */
+#define BQ24190_REG_F_WATCHDOG_FAULT_MASK BIT(7)
+#define BQ24190_REG_F_WATCHDOG_FAULT_SHIFT 7
+#define BQ24190_REG_F_BOOST_FAULT_MASK BIT(6)
+#define BQ24190_REG_F_BOOST_FAULT_SHIFT 6
+#define BQ24190_REG_F_CHRG_FAULT_MASK (BIT(5) | BIT(4))
+#define BQ24190_REG_F_CHRG_FAULT_SHIFT 4
+#define BQ24190_REG_F_BAT_FAULT_MASK BIT(3)
+#define BQ24190_REG_F_BAT_FAULT_SHIFT 3
+#define BQ24190_REG_F_NTC_FAULT_MASK (BIT(2) | BIT(1) | BIT(0))
+#define BQ24190_REG_F_NTC_FAULT_SHIFT 0
+
+#define BQ24190_REG_VPRS 0x0A /* Vendor/Part/Revision Status */
+#define BQ24190_REG_VPRS_PN_MASK (BIT(5) | BIT(4) | BIT(3))
+#define BQ24190_REG_VPRS_PN_SHIFT 3
+#define BQ24190_REG_VPRS_PN_24190 0x4
+#define BQ24190_REG_VPRS_PN_24192 0x5 /* Also 24193 */
+#define BQ24190_REG_VPRS_PN_24192I 0x3
+#define BQ24190_REG_VPRS_TS_PROFILE_MASK BIT(2)
+#define BQ24190_REG_VPRS_TS_PROFILE_SHIFT 2
+#define BQ24190_REG_VPRS_DEV_REG_MASK (BIT(1) | BIT(0))
+#define BQ24190_REG_VPRS_DEV_REG_SHIFT 0
+
+/*
+ * The FAULT register is latched by the bq24190 (except for NTC_FAULT)
+ * so the first read after a fault returns the latched value and subsequent
+ * reads return the current value. In order to return the fault status
+ * to the user, have the interrupt handler save the reg's value and retrieve
+ * it in the appropriate health/status routine. Each routine has its own
+ * flag indicating whether it should use the value stored by the last run
+ * of the interrupt handler or do an actual reg read. That way each routine
+ * can report back whatever fault may have occured.
+ */
+struct bq24190_dev_info {
+ struct i2c_client *client;
+ struct device *dev;
+ struct power_supply charger;
+ struct power_supply battery;
+ char model_name[I2C_NAME_SIZE];
+ kernel_ulong_t model;
+ unsigned int gpio_int;
+ unsigned int irq;
+ struct mutex f_reg_lock;
+ bool first_time;
+ bool charger_health_valid;
+ bool battery_health_valid;
+ bool battery_status_valid;
+ u8 f_reg;
+ u8 ss_reg;
+ u8 watchdog;
+};
+
+/*
+ * The tables below provide a 2-way mapping for the value that goes in
+ * the register field and the real-world value that it represents.
+ * The index of the array is the value that goes in the register; the
+ * number at that index in the array is the real-world value that it
+ * represents.
+ */
+/* REG02[7:2] (ICHG) in uAh */
+static const int bq24190_ccc_ichg_values[] = {
+ 512000, 576000, 640000, 704000, 768000, 832000, 896000, 960000,
+ 1024000, 1088000, 1152000, 1216000, 1280000, 1344000, 1408000, 1472000,
+ 1536000, 1600000, 1664000, 1728000, 1792000, 1856000, 1920000, 1984000,
+ 2048000, 2112000, 2176000, 2240000, 2304000, 2368000, 2432000, 2496000,
+ 2560000, 2624000, 2688000, 2752000, 2816000, 2880000, 2944000, 3008000,
+ 3072000, 3136000, 3200000, 3264000, 3328000, 3392000, 3456000, 3520000,
+ 3584000, 3648000, 3712000, 3776000, 3840000, 3904000, 3968000, 4032000,
+ 4096000, 4160000, 4224000, 4288000, 4352000, 4416000, 4480000, 4544000
+};
+
+/* REG04[7:2] (VREG) in uV */
+static const int bq24190_cvc_vreg_values[] = {
+ 3504000, 3520000, 3536000, 3552000, 3568000, 3584000, 3600000, 3616000,
+ 3632000, 3648000, 3664000, 3680000, 3696000, 3712000, 3728000, 3744000,
+ 3760000, 3776000, 3792000, 3808000, 3824000, 3840000, 3856000, 3872000,
+ 3888000, 3904000, 3920000, 3936000, 3952000, 3968000, 3984000, 4000000,
+ 4016000, 4032000, 4048000, 4064000, 4080000, 4096000, 4112000, 4128000,
+ 4144000, 4160000, 4176000, 4192000, 4208000, 4224000, 4240000, 4256000,
+ 4272000, 4288000, 4304000, 4320000, 4336000, 4352000, 4368000, 4384000,
+ 4400000
+};
+
+/* REG06[1:0] (TREG) in tenths of degrees Celcius */
+static const int bq24190_ictrc_treg_values[] = {
+ 600, 800, 1000, 1200
+};
+
+/*
+ * Return the index in 'tbl' of greatest value that is less than or equal to
+ * 'val'. The index range returned is 0 to 'tbl_size' - 1. Assumes that
+ * the values in 'tbl' are sorted from smallest to largest and 'tbl_size'
+ * is less than 2^8.
+ */
+static u8 bq24190_find_idx(const int tbl[], int tbl_size, int v)
+{
+ int i;
+
+ for (i = 1; i < tbl_size; i++)
+ if (v < tbl[i])
+ break;
+
+ return i - 1;
+}
+
+/* Basic driver I/O routines */
+
+static int bq24190_read(struct bq24190_dev_info *bdi, u8 reg, u8 *data)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(bdi->client, reg);
+ if (ret < 0)
+ return ret;
+
+ *data = ret;
+ return 0;
+}
+
+static int bq24190_write(struct bq24190_dev_info *bdi, u8 reg, u8 data)
+{
+ return i2c_smbus_write_byte_data(bdi->client, reg, data);
+}
+
+static int bq24190_read_mask(struct bq24190_dev_info *bdi, u8 reg,
+ u8 mask, u8 shift, u8 *data)
+{
+ u8 v;
+ int ret;
+
+ ret = bq24190_read(bdi, reg, &v);
+ if (ret < 0)
+ return ret;
+
+ v &= mask;
+ v >>= shift;
+ *data = v;
+
+ return 0;
+}
+
+static int bq24190_write_mask(struct bq24190_dev_info *bdi, u8 reg,
+ u8 mask, u8 shift, u8 data)
+{
+ u8 v;
+ int ret;
+
+ ret = bq24190_read(bdi, reg, &v);
+ if (ret < 0)
+ return ret;
+
+ v &= ~mask;
+ v |= ((data << shift) & mask);
+
+ return bq24190_write(bdi, reg, v);
+}
+
+static int bq24190_get_field_val(struct bq24190_dev_info *bdi,
+ u8 reg, u8 mask, u8 shift,
+ const int tbl[], int tbl_size,
+ int *val)
+{
+ u8 v;
+ int ret;
+
+ ret = bq24190_read_mask(bdi, reg, mask, shift, &v);
+ if (ret < 0)
+ return ret;
+
+ v = (v >= tbl_size) ? (tbl_size - 1) : v;
+ *val = tbl[v];
+
+ return 0;
+}
+
+static int bq24190_set_field_val(struct bq24190_dev_info *bdi,
+ u8 reg, u8 mask, u8 shift,
+ const int tbl[], int tbl_size,
+ int val)
+{
+ u8 idx;
+
+ idx = bq24190_find_idx(tbl, tbl_size, val);
+
+ return bq24190_write_mask(bdi, reg, mask, shift, idx);
+}
+
+#ifdef CONFIG_SYSFS
+/*
+ * There are a numerous options that are configurable on the bq24190
+ * that go well beyond what the power_supply properties provide access to.
+ * Provide sysfs access to them so they can be examined and possibly modified
+ * on the fly. They will be provided for the charger power_supply object only
+ * and will be prefixed by 'f_' to make them easier to recognize.
+ */
+
+#define BQ24190_SYSFS_FIELD(_name, r, f, m, store) \
+{ \
+ .attr = __ATTR(f_##_name, m, bq24190_sysfs_show, store), \
+ .reg = BQ24190_REG_##r, \
+ .mask = BQ24190_REG_##r##_##f##_MASK, \
+ .shift = BQ24190_REG_##r##_##f##_SHIFT, \
+}
+
+#define BQ24190_SYSFS_FIELD_RW(_name, r, f) \
+ BQ24190_SYSFS_FIELD(_name, r, f, S_IWUSR | S_IRUGO, \
+ bq24190_sysfs_store)
+
+#define BQ24190_SYSFS_FIELD_RO(_name, r, f) \
+ BQ24190_SYSFS_FIELD(_name, r, f, S_IRUGO, NULL)
+
+static ssize_t bq24190_sysfs_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t bq24190_sysfs_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count);
+
+struct bq24190_sysfs_field_info {
+ struct device_attribute attr;
+ u8 reg;
+ u8 mask;
+ u8 shift;
+};
+
+/* On i386 ptrace-abi.h defines SS that breaks the macro calls below. */
+#undef SS
+
+static struct bq24190_sysfs_field_info bq24190_sysfs_field_tbl[] = {
+ /* sysfs name reg field in reg */
+ BQ24190_SYSFS_FIELD_RW(en_hiz, ISC, EN_HIZ),
+ BQ24190_SYSFS_FIELD_RW(vindpm, ISC, VINDPM),
+ BQ24190_SYSFS_FIELD_RW(iinlim, ISC, IINLIM),
+ BQ24190_SYSFS_FIELD_RW(chg_config, POC, CHG_CONFIG),
+ BQ24190_SYSFS_FIELD_RW(sys_min, POC, SYS_MIN),
+ BQ24190_SYSFS_FIELD_RW(boost_lim, POC, BOOST_LIM),
+ BQ24190_SYSFS_FIELD_RW(ichg, CCC, ICHG),
+ BQ24190_SYSFS_FIELD_RW(force_20_pct, CCC, FORCE_20PCT),
+ BQ24190_SYSFS_FIELD_RW(iprechg, PCTCC, IPRECHG),
+ BQ24190_SYSFS_FIELD_RW(iterm, PCTCC, ITERM),
+ BQ24190_SYSFS_FIELD_RW(vreg, CVC, VREG),
+ BQ24190_SYSFS_FIELD_RW(batlowv, CVC, BATLOWV),
+ BQ24190_SYSFS_FIELD_RW(vrechg, CVC, VRECHG),
+ BQ24190_SYSFS_FIELD_RW(en_term, CTTC, EN_TERM),
+ BQ24190_SYSFS_FIELD_RW(term_stat, CTTC, TERM_STAT),
+ BQ24190_SYSFS_FIELD_RO(watchdog, CTTC, WATCHDOG),
+ BQ24190_SYSFS_FIELD_RW(en_timer, CTTC, EN_TIMER),
+ BQ24190_SYSFS_FIELD_RW(chg_timer, CTTC, CHG_TIMER),
+ BQ24190_SYSFS_FIELD_RW(jeta_iset, CTTC, JEITA_ISET),
+ BQ24190_SYSFS_FIELD_RW(bat_comp, ICTRC, BAT_COMP),
+ BQ24190_SYSFS_FIELD_RW(vclamp, ICTRC, VCLAMP),
+ BQ24190_SYSFS_FIELD_RW(treg, ICTRC, TREG),
+ BQ24190_SYSFS_FIELD_RW(dpdm_en, MOC, DPDM_EN),
+ BQ24190_SYSFS_FIELD_RW(tmr2x_en, MOC, TMR2X_EN),
+ BQ24190_SYSFS_FIELD_RW(batfet_disable, MOC, BATFET_DISABLE),
+ BQ24190_SYSFS_FIELD_RW(jeita_vset, MOC, JEITA_VSET),
+ BQ24190_SYSFS_FIELD_RO(int_mask, MOC, INT_MASK),
+ BQ24190_SYSFS_FIELD_RO(vbus_stat, SS, VBUS_STAT),
+ BQ24190_SYSFS_FIELD_RO(chrg_stat, SS, CHRG_STAT),
+ BQ24190_SYSFS_FIELD_RO(dpm_stat, SS, DPM_STAT),
+ BQ24190_SYSFS_FIELD_RO(pg_stat, SS, PG_STAT),
+ BQ24190_SYSFS_FIELD_RO(therm_stat, SS, THERM_STAT),
+ BQ24190_SYSFS_FIELD_RO(vsys_stat, SS, VSYS_STAT),
+ BQ24190_SYSFS_FIELD_RO(watchdog_fault, F, WATCHDOG_FAULT),
+ BQ24190_SYSFS_FIELD_RO(boost_fault, F, BOOST_FAULT),
+ BQ24190_SYSFS_FIELD_RO(chrg_fault, F, CHRG_FAULT),
+ BQ24190_SYSFS_FIELD_RO(bat_fault, F, BAT_FAULT),
+ BQ24190_SYSFS_FIELD_RO(ntc_fault, F, NTC_FAULT),
+ BQ24190_SYSFS_FIELD_RO(pn, VPRS, PN),
+ BQ24190_SYSFS_FIELD_RO(ts_profile, VPRS, TS_PROFILE),
+ BQ24190_SYSFS_FIELD_RO(dev_reg, VPRS, DEV_REG),
+};
+
+static struct attribute *
+ bq24190_sysfs_attrs[ARRAY_SIZE(bq24190_sysfs_field_tbl) + 1];
+
+static const struct attribute_group bq24190_sysfs_attr_group = {
+ .attrs = bq24190_sysfs_attrs,
+};
+
+static void bq24190_sysfs_init_attrs(void)
+{
+ int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl);
+
+ for (i = 0; i < limit; i++)
+ bq24190_sysfs_attrs[i] = &bq24190_sysfs_field_tbl[i].attr.attr;
+
+ bq24190_sysfs_attrs[limit] = NULL; /* Has additional entry for this */
+}
+
+static struct bq24190_sysfs_field_info *bq24190_sysfs_field_lookup(
+ const char *name)
+{
+ int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl);
+
+ for (i = 0; i < limit; i++)
+ if (!strcmp(name, bq24190_sysfs_field_tbl[i].attr.attr.name))
+ break;
+
+ if (i >= limit)
+ return NULL;
+
+ return &bq24190_sysfs_field_tbl[i];
+}
+
+static ssize_t bq24190_sysfs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq24190_dev_info *bdi =
+ container_of(psy, struct bq24190_dev_info, charger);
+ struct bq24190_sysfs_field_info *info;
+ int ret;
+ u8 v;
+
+ info = bq24190_sysfs_field_lookup(attr->attr.name);
+ if (!info)
+ return -EINVAL;
+
+ ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%hhx\n", v);
+}
+
+static ssize_t bq24190_sysfs_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq24190_dev_info *bdi =
+ container_of(psy, struct bq24190_dev_info, charger);
+ struct bq24190_sysfs_field_info *info;
+ int ret;
+ u8 v;
+
+ info = bq24190_sysfs_field_lookup(attr->attr.name);
+ if (!info)
+ return -EINVAL;
+
+ ret = kstrtou8(buf, 0, &v);
+ if (ret < 0)
+ return ret;
+
+ ret = bq24190_write_mask(bdi, info->reg, info->mask, info->shift, v);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi)
+{
+ bq24190_sysfs_init_attrs();
+
+ return sysfs_create_group(&bdi->charger.dev->kobj,
+ &bq24190_sysfs_attr_group);
+}
+
+static void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi)
+{
+ sysfs_remove_group(&bdi->charger.dev->kobj, &bq24190_sysfs_attr_group);
+}
+#else
+static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi)
+{
+ return 0;
+}
+
+static inline void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) {}
+#endif
+
+/*
+ * According to the "Host Mode and default Mode" section of the
+ * manual, a write to any register causes the bq24190 to switch
+ * from default mode to host mode. It will switch back to default
+ * mode after a WDT timeout unless the WDT is turned off as well.
+ * So, by simply turning off the WDT, we accomplish both with the
+ * same write.
+ */
+static int bq24190_set_mode_host(struct bq24190_dev_info *bdi)
+{
+ int ret;
+ u8 v;
+
+ ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v);
+ if (ret < 0)
+ return ret;
+
+ bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >>
+ BQ24190_REG_CTTC_WATCHDOG_SHIFT);
+ v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK;
+
+ return bq24190_write(bdi, BQ24190_REG_CTTC, v);
+}
+
+static int bq24190_register_reset(struct bq24190_dev_info *bdi)
+{
+ int ret, limit = 100;
+ u8 v;
+
+ /* Reset the registers */
+ ret = bq24190_write_mask(bdi, BQ24190_REG_POC,
+ BQ24190_REG_POC_RESET_MASK,
+ BQ24190_REG_POC_RESET_SHIFT,
+ 0x1);
+ if (ret < 0)
+ return ret;
+
+ /* Reset bit will be cleared by hardware so poll until it is */
+ do {
+ ret = bq24190_read_mask(bdi, BQ24190_REG_POC,
+ BQ24190_REG_POC_RESET_MASK,
+ BQ24190_REG_POC_RESET_SHIFT,
+ &v);
+ if (ret < 0)
+ return ret;
+
+ if (!v)
+ break;
+
+ udelay(10);
+ } while (--limit);
+
+ if (!limit)
+ return -EIO;
+
+ return 0;
+}
+
+/* Charger power supply property routines */
+
+static int bq24190_charger_get_charge_type(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ u8 v;
+ int type, ret;
+
+ ret = bq24190_read_mask(bdi, BQ24190_REG_POC,
+ BQ24190_REG_POC_CHG_CONFIG_MASK,
+ BQ24190_REG_POC_CHG_CONFIG_SHIFT,
+ &v);
+ if (ret < 0)
+ return ret;
+
+ /* If POC[CHG_CONFIG] (REG01[5:4]) == 0, charge is disabled */
+ if (!v) {
+ type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ } else {
+ ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
+ BQ24190_REG_CCC_FORCE_20PCT_MASK,
+ BQ24190_REG_CCC_FORCE_20PCT_SHIFT,
+ &v);
+ if (ret < 0)
+ return ret;
+
+ type = (v) ? POWER_SUPPLY_CHARGE_TYPE_TRICKLE :
+ POWER_SUPPLY_CHARGE_TYPE_FAST;
+ }
+
+ val->intval = type;
+
+ return 0;
+}
+
+static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi,
+ const union power_supply_propval *val)
+{
+ u8 chg_config, force_20pct, en_term;
+ int ret;
+
+ /*
+ * According to the "Termination when REG02[0] = 1" section of
+ * the bq24190 manual, the trickle charge could be less than the
+ * termination current so it recommends turning off the termination
+ * function.
+ *
+ * Note: AFAICT from the datasheet, the user will have to manually
+ * turn off the charging when in 20% mode. If its not turned off,
+ * there could be battery damage. So, use this mode at your own risk.
+ */
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_TYPE_NONE:
+ chg_config = 0x0;
+ break;
+ case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
+ chg_config = 0x1;
+ force_20pct = 0x1;
+ en_term = 0x0;
+ break;
+ case POWER_SUPPLY_CHARGE_TYPE_FAST:
+ chg_config = 0x1;
+ force_20pct = 0x0;
+ en_term = 0x1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (chg_config) { /* Enabling the charger */
+ ret = bq24190_write_mask(bdi, BQ24190_REG_CCC,
+ BQ24190_REG_CCC_FORCE_20PCT_MASK,
+ BQ24190_REG_CCC_FORCE_20PCT_SHIFT,
+ force_20pct);
+ if (ret < 0)
+ return ret;
+
+ ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC,
+ BQ24190_REG_CTTC_EN_TERM_MASK,
+ BQ24190_REG_CTTC_EN_TERM_SHIFT,
+ en_term);
+ if (ret < 0)
+ return ret;
+ }
+
+ return bq24190_write_mask(bdi, BQ24190_REG_POC,
+ BQ24190_REG_POC_CHG_CONFIG_MASK,
+ BQ24190_REG_POC_CHG_CONFIG_SHIFT, chg_config);
+}
+
+static int bq24190_charger_get_health(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ u8 v;
+ int health, ret;
+
+ mutex_lock(&bdi->f_reg_lock);
+
+ if (bdi->charger_health_valid) {
+ v = bdi->f_reg;
+ bdi->charger_health_valid = false;
+ mutex_unlock(&bdi->f_reg_lock);
+ } else {
+ mutex_unlock(&bdi->f_reg_lock);
+
+ ret = bq24190_read(bdi, BQ24190_REG_F, &v);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (v & BQ24190_REG_F_BOOST_FAULT_MASK) {
+ /*
+ * This could be over-current or over-voltage but there's
+ * no way to tell which. Return 'OVERVOLTAGE' since there
+ * isn't an 'OVERCURRENT' value defined that we can return
+ * even if it was over-current.
+ */
+ health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ } else {
+ v &= BQ24190_REG_F_CHRG_FAULT_MASK;
+ v >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
+
+ switch (v) {
+ case 0x0: /* Normal */
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x1: /* Input Fault (VBUS OVP or VBAT<VBUS<3.8V) */
+ /*
+ * This could be over-voltage or under-voltage
+ * and there's no way to tell which. Instead
+ * of looking foolish and returning 'OVERVOLTAGE'
+ * when its really under-voltage, just return
+ * 'UNSPEC_FAILURE'.
+ */
+ health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ case 0x2: /* Thermal Shutdown */
+ health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+ case 0x3: /* Charge Safety Timer Expiration */
+ health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ break;
+ default:
+ health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+ }
+
+ val->intval = health;
+
+ return 0;
+}
+
+static int bq24190_charger_get_online(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ u8 v;
+ int ret;
+
+ ret = bq24190_read_mask(bdi, BQ24190_REG_SS,
+ BQ24190_REG_SS_PG_STAT_MASK,
+ BQ24190_REG_SS_PG_STAT_SHIFT, &v);
+ if (ret < 0)
+ return ret;
+
+ val->intval = v;
+ return 0;
+}
+
+static int bq24190_charger_get_current(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ u8 v;
+ int curr, ret;
+
+ ret = bq24190_get_field_val(bdi, BQ24190_REG_CCC,
+ BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT,
+ bq24190_ccc_ichg_values,
+ ARRAY_SIZE(bq24190_ccc_ichg_values), &curr);
+ if (ret < 0)
+ return ret;
+
+ ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
+ BQ24190_REG_CCC_FORCE_20PCT_MASK,
+ BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v);
+ if (ret < 0)
+ return ret;
+
+ /* If FORCE_20PCT is enabled, then current is 20% of ICHG value */
+ if (v)
+ curr /= 5;
+
+ val->intval = curr;
+ return 0;
+}
+
+static int bq24190_charger_get_current_max(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ int idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1;
+
+ val->intval = bq24190_ccc_ichg_values[idx];
+ return 0;
+}
+
+static int bq24190_charger_set_current(struct bq24190_dev_info *bdi,
+ const union power_supply_propval *val)
+{
+ u8 v;
+ int ret, curr = val->intval;
+
+ ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
+ BQ24190_REG_CCC_FORCE_20PCT_MASK,
+ BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v);
+ if (ret < 0)
+ return ret;
+
+ /* If FORCE_20PCT is enabled, have to multiply value passed in by 5 */
+ if (v)
+ curr *= 5;
+
+ return bq24190_set_field_val(bdi, BQ24190_REG_CCC,
+ BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT,
+ bq24190_ccc_ichg_values,
+ ARRAY_SIZE(bq24190_ccc_ichg_values), curr);
+}
+
+static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ int voltage, ret;
+
+ ret = bq24190_get_field_val(bdi, BQ24190_REG_CVC,
+ BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT,
+ bq24190_cvc_vreg_values,
+ ARRAY_SIZE(bq24190_cvc_vreg_values), &voltage);
+ if (ret < 0)
+ return ret;
+
+ val->intval = voltage;
+ return 0;
+}
+
+static int bq24190_charger_get_voltage_max(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ int idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1;
+
+ val->intval = bq24190_cvc_vreg_values[idx];
+ return 0;
+}
+
+static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi,
+ const union power_supply_propval *val)
+{
+ return bq24190_set_field_val(bdi, BQ24190_REG_CVC,
+ BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT,
+ bq24190_cvc_vreg_values,
+ ARRAY_SIZE(bq24190_cvc_vreg_values), val->intval);
+}
+
+static int bq24190_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct bq24190_dev_info *bdi =
+ container_of(psy, struct bq24190_dev_info, charger);
+ int ret;
+
+ dev_dbg(bdi->dev, "prop: %d\n", psp);
+
+ pm_runtime_get_sync(bdi->dev);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = bq24190_charger_get_charge_type(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = bq24190_charger_get_health(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = bq24190_charger_get_online(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = bq24190_charger_get_current(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = bq24190_charger_get_current_max(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = bq24190_charger_get_voltage(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ ret = bq24190_charger_get_voltage_max(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = bdi->model_name;
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = BQ24190_MANUFACTURER;
+ ret = 0;
+ break;
+ default:
+ ret = -ENODATA;
+ }
+
+ pm_runtime_put_sync(bdi->dev);
+ return ret;
+}
+
+static int bq24190_charger_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bq24190_dev_info *bdi =
+ container_of(psy, struct bq24190_dev_info, charger);
+ int ret;
+
+ dev_dbg(bdi->dev, "prop: %d\n", psp);
+
+ pm_runtime_get_sync(bdi->dev);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = bq24190_charger_set_charge_type(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = bq24190_charger_set_current(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = bq24190_charger_set_voltage(bdi, val);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ pm_runtime_put_sync(bdi->dev);
+ return ret;
+}
+
+static int bq24190_charger_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property bq24190_charger_properties[] = {
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static char *bq24190_charger_supplied_to[] = {
+ "main-battery",
+};
+
+static void bq24190_charger_init(struct power_supply *charger)
+{
+ charger->name = "bq24190-charger";
+ charger->type = POWER_SUPPLY_TYPE_USB;
+ charger->properties = bq24190_charger_properties;
+ charger->num_properties = ARRAY_SIZE(bq24190_charger_properties);
+ charger->supplied_to = bq24190_charger_supplied_to;
+ charger->num_supplies = ARRAY_SIZE(bq24190_charger_supplied_to);
+ charger->get_property = bq24190_charger_get_property;
+ charger->set_property = bq24190_charger_set_property;
+ charger->property_is_writeable = bq24190_charger_property_is_writeable;
+}
+
+/* Battery power supply property routines */
+
+static int bq24190_battery_get_status(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ u8 ss_reg, chrg_fault;
+ int status, ret;
+
+ mutex_lock(&bdi->f_reg_lock);
+
+ if (bdi->battery_status_valid) {
+ chrg_fault = bdi->f_reg;
+ bdi->battery_status_valid = false;
+ mutex_unlock(&bdi->f_reg_lock);
+ } else {
+ mutex_unlock(&bdi->f_reg_lock);
+
+ ret = bq24190_read(bdi, BQ24190_REG_F, &chrg_fault);
+ if (ret < 0)
+ return ret;
+ }
+
+ chrg_fault &= BQ24190_REG_F_CHRG_FAULT_MASK;
+ chrg_fault >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
+
+ ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The battery must be discharging when any of these are true:
+ * - there is no good power source;
+ * - there is a charge fault.
+ * Could also be discharging when in "supplement mode" but
+ * there is no way to tell when its in that mode.
+ */
+ if (!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK) || chrg_fault) {
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else {
+ ss_reg &= BQ24190_REG_SS_CHRG_STAT_MASK;
+ ss_reg >>= BQ24190_REG_SS_CHRG_STAT_SHIFT;
+
+ switch (ss_reg) {
+ case 0x0: /* Not Charging */
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case 0x1: /* Pre-charge */
+ case 0x2: /* Fast Charging */
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case 0x3: /* Charge Termination Done */
+ status = POWER_SUPPLY_STATUS_FULL;
+ break;
+ default:
+ ret = -EIO;
+ }
+ }
+
+ if (!ret)
+ val->intval = status;
+
+ return ret;
+}
+
+static int bq24190_battery_get_health(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ u8 v;
+ int health, ret;
+
+ mutex_lock(&bdi->f_reg_lock);
+
+ if (bdi->battery_health_valid) {
+ v = bdi->f_reg;
+ bdi->battery_health_valid = false;
+ mutex_unlock(&bdi->f_reg_lock);
+ } else {
+ mutex_unlock(&bdi->f_reg_lock);
+
+ ret = bq24190_read(bdi, BQ24190_REG_F, &v);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (v & BQ24190_REG_F_BAT_FAULT_MASK) {
+ health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ } else {
+ v &= BQ24190_REG_F_NTC_FAULT_MASK;
+ v >>= BQ24190_REG_F_NTC_FAULT_SHIFT;
+
+ switch (v) {
+ case 0x0: /* Normal */
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x1: /* TS1 Cold */
+ case 0x3: /* TS2 Cold */
+ case 0x5: /* Both Cold */
+ health = POWER_SUPPLY_HEALTH_COLD;
+ break;
+ case 0x2: /* TS1 Hot */
+ case 0x4: /* TS2 Hot */
+ case 0x6: /* Both Hot */
+ health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+ default:
+ health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+ }
+
+ val->intval = health;
+ return 0;
+}
+
+static int bq24190_battery_get_online(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ u8 batfet_disable;
+ int ret;
+
+ ret = bq24190_read_mask(bdi, BQ24190_REG_MOC,
+ BQ24190_REG_MOC_BATFET_DISABLE_MASK,
+ BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, &batfet_disable);
+ if (ret < 0)
+ return ret;
+
+ val->intval = !batfet_disable;
+ return 0;
+}
+
+static int bq24190_battery_set_online(struct bq24190_dev_info *bdi,
+ const union power_supply_propval *val)
+{
+ return bq24190_write_mask(bdi, BQ24190_REG_MOC,
+ BQ24190_REG_MOC_BATFET_DISABLE_MASK,
+ BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, !val->intval);
+}
+
+static int bq24190_battery_get_temp_alert_max(struct bq24190_dev_info *bdi,
+ union power_supply_propval *val)
+{
+ int temp, ret;
+
+ ret = bq24190_get_field_val(bdi, BQ24190_REG_ICTRC,
+ BQ24190_REG_ICTRC_TREG_MASK,
+ BQ24190_REG_ICTRC_TREG_SHIFT,
+ bq24190_ictrc_treg_values,
+ ARRAY_SIZE(bq24190_ictrc_treg_values), &temp);
+ if (ret < 0)
+ return ret;
+
+ val->intval = temp;
+ return 0;
+}
+
+static int bq24190_battery_set_temp_alert_max(struct bq24190_dev_info *bdi,
+ const union power_supply_propval *val)
+{
+ return bq24190_set_field_val(bdi, BQ24190_REG_ICTRC,
+ BQ24190_REG_ICTRC_TREG_MASK,
+ BQ24190_REG_ICTRC_TREG_SHIFT,
+ bq24190_ictrc_treg_values,
+ ARRAY_SIZE(bq24190_ictrc_treg_values), val->intval);
+}
+
+static int bq24190_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct bq24190_dev_info *bdi =
+ container_of(psy, struct bq24190_dev_info, battery);
+ int ret;
+
+ dev_dbg(bdi->dev, "prop: %d\n", psp);
+
+ pm_runtime_get_sync(bdi->dev);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = bq24190_battery_get_status(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = bq24190_battery_get_health(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = bq24190_battery_get_online(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ /* Could be Li-on or Li-polymer but no way to tell which */
+ val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ ret = bq24190_battery_get_temp_alert_max(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ ret = 0;
+ break;
+ default:
+ ret = -ENODATA;
+ }
+
+ pm_runtime_put_sync(bdi->dev);
+ return ret;
+}
+
+static int bq24190_battery_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bq24190_dev_info *bdi =
+ container_of(psy, struct bq24190_dev_info, battery);
+ int ret;
+
+ dev_dbg(bdi->dev, "prop: %d\n", psp);
+
+ pm_runtime_put_sync(bdi->dev);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = bq24190_battery_set_online(bdi, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ ret = bq24190_battery_set_temp_alert_max(bdi, val);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ pm_runtime_put_sync(bdi->dev);
+ return ret;
+}
+
+static int bq24190_battery_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property bq24190_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
+ POWER_SUPPLY_PROP_SCOPE,
+};
+
+static void bq24190_battery_init(struct power_supply *battery)
+{
+ battery->name = "bq24190-battery";
+ battery->type = POWER_SUPPLY_TYPE_BATTERY;
+ battery->properties = bq24190_battery_properties;
+ battery->num_properties = ARRAY_SIZE(bq24190_battery_properties);
+ battery->get_property = bq24190_battery_get_property;
+ battery->set_property = bq24190_battery_set_property;
+ battery->property_is_writeable = bq24190_battery_property_is_writeable;
+}
+
+static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
+{
+ struct bq24190_dev_info *bdi = data;
+ bool alert_userspace = false;
+ u8 ss_reg, f_reg;
+ int ret;
+
+ pm_runtime_get_sync(bdi->dev);
+
+ ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg);
+ if (ret < 0) {
+ dev_err(bdi->dev, "Can't read SS reg: %d\n", ret);
+ goto out;
+ }
+
+ if (ss_reg != bdi->ss_reg) {
+ /*
+ * The device is in host mode so when PG_STAT goes from 1->0
+ * (i.e., power removed) HIZ needs to be disabled.
+ */
+ if ((bdi->ss_reg & BQ24190_REG_SS_PG_STAT_MASK) &&
+ !(ss_reg & BQ24190_REG_SS_PG_STAT_MASK)) {
+ ret = bq24190_write_mask(bdi, BQ24190_REG_ISC,
+ BQ24190_REG_ISC_EN_HIZ_MASK,
+ BQ24190_REG_ISC_EN_HIZ_SHIFT,
+ 0);
+ if (ret < 0)
+ dev_err(bdi->dev, "Can't access ISC reg: %d\n",
+ ret);
+ }
+
+ bdi->ss_reg = ss_reg;
+ alert_userspace = true;
+ }
+
+ mutex_lock(&bdi->f_reg_lock);
+
+ ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg);
+ if (ret < 0) {
+ mutex_unlock(&bdi->f_reg_lock);
+ dev_err(bdi->dev, "Can't read F reg: %d\n", ret);
+ goto out;
+ }
+
+ if (f_reg != bdi->f_reg) {
+ bdi->f_reg = f_reg;
+ bdi->charger_health_valid = true;
+ bdi->battery_health_valid = true;
+ bdi->battery_status_valid = true;
+
+ alert_userspace = true;
+ }
+
+ mutex_unlock(&bdi->f_reg_lock);
+
+ /*
+ * Sometimes bq24190 gives a steady trickle of interrupts even
+ * though the watchdog timer is turned off and neither the STATUS
+ * nor FAULT registers have changed. Weed out these sprurious
+ * interrupts so userspace isn't alerted for no reason.
+ * In addition, the chip always generates an interrupt after
+ * register reset so we should ignore that one (the very first
+ * interrupt received).
+ */
+ if (alert_userspace && !bdi->first_time) {
+ power_supply_changed(&bdi->charger);
+ power_supply_changed(&bdi->battery);
+ bdi->first_time = false;
+ }
+
+out:
+ pm_runtime_put_sync(bdi->dev);
+
+ dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg);
+
+ return IRQ_HANDLED;
+}
+
+static int bq24190_hw_init(struct bq24190_dev_info *bdi)
+{
+ u8 v;
+ int ret;
+
+ pm_runtime_get_sync(bdi->dev);
+
+ /* First check that the device really is what its supposed to be */
+ ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS,
+ BQ24190_REG_VPRS_PN_MASK,
+ BQ24190_REG_VPRS_PN_SHIFT,
+ &v);
+ if (ret < 0)
+ goto out;
+
+ if (v != bdi->model) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ ret = bq24190_register_reset(bdi);
+ if (ret < 0)
+ goto out;
+
+ ret = bq24190_set_mode_host(bdi);
+out:
+ pm_runtime_put_sync(bdi->dev);
+ return ret;
+}
+
+#ifdef CONFIG_OF
+static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
+{
+ bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0);
+ if (bdi->irq <= 0)
+ return -1;
+
+ return 0;
+}
+#else
+static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
+{
+ return -1;
+}
+#endif
+
+static int bq24190_setup_pdata(struct bq24190_dev_info *bdi,
+ struct bq24190_platform_data *pdata)
+{
+ int ret;
+
+ if (!gpio_is_valid(pdata->gpio_int))
+ return -1;
+
+ ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev));
+ if (ret < 0)
+ return -1;
+
+ ret = gpio_direction_input(pdata->gpio_int);
+ if (ret < 0)
+ goto out;
+
+ bdi->irq = gpio_to_irq(pdata->gpio_int);
+ if (!bdi->irq)
+ goto out;
+
+ bdi->gpio_int = pdata->gpio_int;
+ return 0;
+
+out:
+ gpio_free(pdata->gpio_int);
+ return -1;
+}
+
+static int bq24190_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct device *dev = &client->dev;
+ struct bq24190_platform_data *pdata = client->dev.platform_data;
+ struct bq24190_dev_info *bdi;
+ int ret;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+ dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
+ return -ENODEV;
+ }
+
+ bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL);
+ if (!bdi) {
+ dev_err(dev, "Can't alloc bdi struct\n");
+ return -ENOMEM;
+ }
+
+ bdi->client = client;
+ bdi->dev = dev;
+ bdi->model = id->driver_data;
+ strncpy(bdi->model_name, id->name, I2C_NAME_SIZE);
+ mutex_init(&bdi->f_reg_lock);
+ bdi->first_time = true;
+ bdi->charger_health_valid = false;
+ bdi->battery_health_valid = false;
+ bdi->battery_status_valid = false;
+
+ i2c_set_clientdata(client, bdi);
+
+ if (dev->of_node)
+ ret = bq24190_setup_dt(bdi);
+ else
+ ret = bq24190_setup_pdata(bdi, pdata);
+
+ if (ret) {
+ dev_err(dev, "Can't get irq info\n");
+ return -EINVAL;
+ }
+
+ ret = devm_request_threaded_irq(dev, bdi->irq, NULL,
+ bq24190_irq_handler_thread,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "bq24190-charger", bdi);
+ if (ret < 0) {
+ dev_err(dev, "Can't set up irq handler\n");
+ goto out1;
+ }
+
+ pm_runtime_enable(dev);
+ pm_runtime_resume(dev);
+
+ ret = bq24190_hw_init(bdi);
+ if (ret < 0) {
+ dev_err(dev, "Hardware init failed\n");
+ goto out2;
+ }
+
+ bq24190_charger_init(&bdi->charger);
+
+ ret = power_supply_register(dev, &bdi->charger);
+ if (ret) {
+ dev_err(dev, "Can't register charger\n");
+ goto out2;
+ }
+
+ bq24190_battery_init(&bdi->battery);
+
+ ret = power_supply_register(dev, &bdi->battery);
+ if (ret) {
+ dev_err(dev, "Can't register battery\n");
+ goto out3;
+ }
+
+ ret = bq24190_sysfs_create_group(bdi);
+ if (ret) {
+ dev_err(dev, "Can't create sysfs entries\n");
+ goto out4;
+ }
+
+ return 0;
+
+out4:
+ power_supply_unregister(&bdi->battery);
+out3:
+ power_supply_unregister(&bdi->charger);
+out2:
+ pm_runtime_disable(dev);
+out1:
+ if (bdi->gpio_int)
+ gpio_free(bdi->gpio_int);
+
+ return ret;
+}
+
+static int bq24190_remove(struct i2c_client *client)
+{
+ struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
+
+ pm_runtime_get_sync(bdi->dev);
+ bq24190_register_reset(bdi);
+ pm_runtime_put_sync(bdi->dev);
+
+ bq24190_sysfs_remove_group(bdi);
+ power_supply_unregister(&bdi->battery);
+ power_supply_unregister(&bdi->charger);
+ pm_runtime_disable(bdi->dev);
+
+ if (bdi->gpio_int)
+ gpio_free(bdi->gpio_int);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int bq24190_pm_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
+
+ pm_runtime_get_sync(bdi->dev);
+ bq24190_register_reset(bdi);
+ pm_runtime_put_sync(bdi->dev);
+
+ return 0;
+}
+
+static int bq24190_pm_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
+
+ bdi->charger_health_valid = false;
+ bdi->battery_health_valid = false;
+ bdi->battery_status_valid = false;
+
+ pm_runtime_get_sync(bdi->dev);
+ bq24190_register_reset(bdi);
+ pm_runtime_put_sync(bdi->dev);
+
+ /* Things may have changed while suspended so alert upper layer */
+ power_supply_changed(&bdi->charger);
+ power_supply_changed(&bdi->battery);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(bq24190_pm_ops, bq24190_pm_suspend, bq24190_pm_resume);
+
+/*
+ * Only support the bq24190 right now. The bq24192, bq24192i, and bq24193
+ * are similar but not identical so the driver needs to be extended to
+ * support them.
+ */
+static const struct i2c_device_id bq24190_i2c_ids[] = {
+ { "bq24190", BQ24190_REG_VPRS_PN_24190 },
+ { },
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id bq24190_of_match[] = {
+ { .compatible = "ti,bq24190", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bq24190_of_match);
+#else
+static const struct of_device_id bq24190_of_match[] = {
+ { },
+};
+#endif
+
+static struct i2c_driver bq24190_driver = {
+ .probe = bq24190_probe,
+ .remove = bq24190_remove,
+ .id_table = bq24190_i2c_ids,
+ .driver = {
+ .name = "bq24190-charger",
+ .owner = THIS_MODULE,
+ .pm = &bq24190_pm_ops,
+ .of_match_table = of_match_ptr(bq24190_of_match),
+ },
+};
+module_i2c_driver(bq24190_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark A. Greer <mgreer@animalcreek.com>");
+MODULE_ALIAS("i2c:bq24190-charger");
+MODULE_DESCRIPTION("TI BQ24190 Charger Driver");
diff --git a/drivers/power/bq24735-charger.c b/drivers/power/bq24735-charger.c
new file mode 100644
index 00000000000..d022b823305
--- /dev/null
+++ b/drivers/power/bq24735-charger.c
@@ -0,0 +1,419 @@
+/*
+ * Battery charger driver for TI BQ24735
+ *
+ * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
+ *
+ * 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;
+ *
+ * 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/err.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include <linux/power/bq24735-charger.h>
+
+#define BQ24735_CHG_OPT 0x12
+#define BQ24735_CHG_OPT_CHARGE_DISABLE (1 << 0)
+#define BQ24735_CHG_OPT_AC_PRESENT (1 << 4)
+#define BQ24735_CHARGE_CURRENT 0x14
+#define BQ24735_CHARGE_CURRENT_MASK 0x1fc0
+#define BQ24735_CHARGE_VOLTAGE 0x15
+#define BQ24735_CHARGE_VOLTAGE_MASK 0x7ff0
+#define BQ24735_INPUT_CURRENT 0x3f
+#define BQ24735_INPUT_CURRENT_MASK 0x1f80
+#define BQ24735_MANUFACTURER_ID 0xfe
+#define BQ24735_DEVICE_ID 0xff
+
+struct bq24735 {
+ struct power_supply charger;
+ struct i2c_client *client;
+ struct bq24735_platform *pdata;
+};
+
+static inline struct bq24735 *to_bq24735(struct power_supply *psy)
+{
+ return container_of(psy, struct bq24735, charger);
+}
+
+static enum power_supply_property bq24735_charger_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static inline int bq24735_write_word(struct i2c_client *client, u8 reg,
+ u16 value)
+{
+ return i2c_smbus_write_word_data(client, reg, le16_to_cpu(value));
+}
+
+static inline int bq24735_read_word(struct i2c_client *client, u8 reg)
+{
+ s32 ret = i2c_smbus_read_word_data(client, reg);
+
+ return ret < 0 ? ret : le16_to_cpu(ret);
+}
+
+static int bq24735_update_word(struct i2c_client *client, u8 reg,
+ u16 mask, u16 value)
+{
+ unsigned int tmp;
+ int ret;
+
+ ret = bq24735_read_word(client, reg);
+ if (ret < 0)
+ return ret;
+
+ tmp = ret & ~mask;
+ tmp |= value & mask;
+
+ return bq24735_write_word(client, reg, tmp);
+}
+
+static inline int bq24735_enable_charging(struct bq24735 *charger)
+{
+ return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
+ BQ24735_CHG_OPT_CHARGE_DISABLE,
+ ~BQ24735_CHG_OPT_CHARGE_DISABLE);
+}
+
+static inline int bq24735_disable_charging(struct bq24735 *charger)
+{
+ return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
+ BQ24735_CHG_OPT_CHARGE_DISABLE,
+ BQ24735_CHG_OPT_CHARGE_DISABLE);
+}
+
+static int bq24735_config_charger(struct bq24735 *charger)
+{
+ struct bq24735_platform *pdata = charger->pdata;
+ int ret;
+ u16 value;
+
+ if (pdata->charge_current) {
+ value = pdata->charge_current & BQ24735_CHARGE_CURRENT_MASK;
+
+ ret = bq24735_write_word(charger->client,
+ BQ24735_CHARGE_CURRENT, value);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to write charger current : %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (pdata->charge_voltage) {
+ value = pdata->charge_voltage & BQ24735_CHARGE_VOLTAGE_MASK;
+
+ ret = bq24735_write_word(charger->client,
+ BQ24735_CHARGE_VOLTAGE, value);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to write charger voltage : %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (pdata->input_current) {
+ value = pdata->input_current & BQ24735_INPUT_CURRENT_MASK;
+
+ ret = bq24735_write_word(charger->client,
+ BQ24735_INPUT_CURRENT, value);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to write input current : %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static bool bq24735_charger_is_present(struct bq24735 *charger)
+{
+ struct bq24735_platform *pdata = charger->pdata;
+ int ret;
+
+ if (pdata->status_gpio_valid) {
+ ret = gpio_get_value_cansleep(pdata->status_gpio);
+ return ret ^= pdata->status_gpio_active_low == 0;
+ } else {
+ int ac = 0;
+
+ ac = bq24735_read_word(charger->client, BQ24735_CHG_OPT);
+ if (ac < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to read charger options : %d\n",
+ ac);
+ return false;
+ }
+ return (ac & BQ24735_CHG_OPT_AC_PRESENT) ? true : false;
+ }
+
+ return false;
+}
+
+static irqreturn_t bq24735_charger_isr(int irq, void *devid)
+{
+ struct power_supply *psy = devid;
+ struct bq24735 *charger = to_bq24735(psy);
+
+ if (bq24735_charger_is_present(charger))
+ bq24735_enable_charging(charger);
+ else
+ bq24735_disable_charging(charger);
+
+ power_supply_changed(psy);
+
+ return IRQ_HANDLED;
+}
+
+static int bq24735_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq24735 *charger;
+
+ charger = container_of(psy, struct bq24735, charger);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = bq24735_charger_is_present(charger) ? 1 : 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct bq24735_platform *bq24735_parse_dt_data(struct i2c_client *client)
+{
+ struct bq24735_platform *pdata;
+ struct device_node *np = client->dev.of_node;
+ u32 val;
+ int ret;
+ enum of_gpio_flags flags;
+
+ pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(&client->dev,
+ "Memory alloc for bq24735 pdata failed\n");
+ return NULL;
+ }
+
+ pdata->status_gpio = of_get_named_gpio_flags(np, "ti,ac-detect-gpios",
+ 0, &flags);
+
+ if (flags & OF_GPIO_ACTIVE_LOW)
+ pdata->status_gpio_active_low = 1;
+
+ ret = of_property_read_u32(np, "ti,charge-current", &val);
+ if (!ret)
+ pdata->charge_current = val;
+
+ ret = of_property_read_u32(np, "ti,charge-voltage", &val);
+ if (!ret)
+ pdata->charge_voltage = val;
+
+ ret = of_property_read_u32(np, "ti,input-current", &val);
+ if (!ret)
+ pdata->input_current = val;
+
+ return pdata;
+}
+
+static int bq24735_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct bq24735 *charger;
+ struct power_supply *supply;
+ char *name;
+
+ charger = devm_kzalloc(&client->dev, sizeof(*charger), GFP_KERNEL);
+ if (!charger)
+ return -ENOMEM;
+
+ charger->pdata = client->dev.platform_data;
+
+ if (IS_ENABLED(CONFIG_OF) && !charger->pdata && client->dev.of_node)
+ charger->pdata = bq24735_parse_dt_data(client);
+
+ if (!charger->pdata) {
+ dev_err(&client->dev, "no platform data provided\n");
+ return -EINVAL;
+ }
+
+ name = (char *)charger->pdata->name;
+ if (!name) {
+ name = kasprintf(GFP_KERNEL, "bq24735@%s",
+ dev_name(&client->dev));
+ if (!name) {
+ dev_err(&client->dev, "Failed to alloc device name\n");
+ return -ENOMEM;
+ }
+ }
+
+ charger->client = client;
+
+ supply = &charger->charger;
+
+ supply->name = name;
+ supply->type = POWER_SUPPLY_TYPE_MAINS;
+ supply->properties = bq24735_charger_properties;
+ supply->num_properties = ARRAY_SIZE(bq24735_charger_properties);
+ supply->get_property = bq24735_charger_get_property;
+ supply->supplied_to = charger->pdata->supplied_to;
+ supply->num_supplicants = charger->pdata->num_supplicants;
+ supply->of_node = client->dev.of_node;
+
+ i2c_set_clientdata(client, charger);
+
+ ret = bq24735_read_word(client, BQ24735_MANUFACTURER_ID);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to read manufacturer id : %d\n",
+ ret);
+ goto err_free_name;
+ } else if (ret != 0x0040) {
+ dev_err(&client->dev,
+ "manufacturer id mismatch. 0x0040 != 0x%04x\n", ret);
+ ret = -ENODEV;
+ goto err_free_name;
+ }
+
+ ret = bq24735_read_word(client, BQ24735_DEVICE_ID);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to read device id : %d\n", ret);
+ goto err_free_name;
+ } else if (ret != 0x000B) {
+ dev_err(&client->dev,
+ "device id mismatch. 0x000b != 0x%04x\n", ret);
+ ret = -ENODEV;
+ goto err_free_name;
+ }
+
+ if (gpio_is_valid(charger->pdata->status_gpio)) {
+ ret = devm_gpio_request(&client->dev,
+ charger->pdata->status_gpio,
+ name);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed GPIO request for GPIO %d: %d\n",
+ charger->pdata->status_gpio, ret);
+ }
+
+ charger->pdata->status_gpio_valid = !ret;
+ }
+
+ ret = bq24735_config_charger(charger);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed in configuring charger");
+ goto err_free_name;
+ }
+
+ /* check for AC adapter presence */
+ if (bq24735_charger_is_present(charger)) {
+ ret = bq24735_enable_charging(charger);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to enable charging\n");
+ goto err_free_name;
+ }
+ }
+
+ ret = power_supply_register(&client->dev, supply);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to register power supply: %d\n",
+ ret);
+ goto err_free_name;
+ }
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, bq24735_charger_isr,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ supply->name, supply);
+ if (ret) {
+ dev_err(&client->dev,
+ "Unable to register IRQ %d err %d\n",
+ client->irq, ret);
+ goto err_unregister_supply;
+ }
+ }
+
+ return 0;
+err_unregister_supply:
+ power_supply_unregister(supply);
+err_free_name:
+ if (name != charger->pdata->name)
+ kfree(name);
+
+ return ret;
+}
+
+static int bq24735_charger_remove(struct i2c_client *client)
+{
+ struct bq24735 *charger = i2c_get_clientdata(client);
+
+ if (charger->client->irq)
+ devm_free_irq(&charger->client->dev, charger->client->irq,
+ &charger->charger);
+
+ power_supply_unregister(&charger->charger);
+
+ if (charger->charger.name != charger->pdata->name)
+ kfree(charger->charger.name);
+
+ return 0;
+}
+
+static const struct i2c_device_id bq24735_charger_id[] = {
+ { "bq24735-charger", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, bq24735_charger_id);
+
+static const struct of_device_id bq24735_match_ids[] = {
+ { .compatible = "ti,bq24735", },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, bq24735_match_ids);
+
+static struct i2c_driver bq24735_charger_driver = {
+ .driver = {
+ .name = "bq24735-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = bq24735_match_ids,
+ },
+ .probe = bq24735_charger_probe,
+ .remove = bq24735_charger_remove,
+ .id_table = bq24735_charger_id,
+};
+
+module_i2c_driver(bq24735_charger_driver);
+
+MODULE_DESCRIPTION("bq24735 battery charging driver");
+MODULE_AUTHOR("Darbha Sriharsha <dsriharsha@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c
index 36b34efdafc..b309713b63b 100644
--- a/drivers/power/bq27x00_battery.c
+++ b/drivers/power/bq27x00_battery.c
@@ -299,7 +299,7 @@ static int bq27x00_battery_read_energy(struct bq27x00_device_info *di)
}
/*
- * Return the battery temperature in tenths of degree Celsius
+ * Return the battery temperature in tenths of degree Kelvin
* Or < 0 if something fails.
*/
static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di)
@@ -312,10 +312,8 @@ static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di)
return temp;
}
- if (bq27xxx_is_chip_version_higher(di))
- temp -= 2731;
- else
- temp = ((temp * 5) - 5463) / 2;
+ if (!bq27xxx_is_chip_version_higher(di))
+ temp = 5 * temp / 2;
return temp;
}
@@ -448,7 +446,6 @@ static void bq27x00_update(struct bq27x00_device_info *di)
cache.temperature = bq27x00_battery_read_temperature(di);
if (!is_bq27425)
cache.cycle_count = bq27x00_battery_read_cyct(di);
- cache.cycle_count = bq27x00_battery_read_cyct(di);
cache.power_avg =
bq27x00_battery_read_pwr_avg(di, BQ27x00_POWER_AVG);
@@ -642,6 +639,8 @@ static int bq27x00_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_TEMP:
ret = bq27x00_simple_value(di->cache.temperature, val);
+ if (ret == 0)
+ val->intval -= 2731;
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
ret = bq27x00_simple_value(di->cache.time_to_empty, val);
@@ -696,7 +695,6 @@ static int bq27x00_powersupply_init(struct bq27x00_device_info *di)
int ret;
di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
- di->chip = BQ27425;
if (di->chip == BQ27425) {
di->bat.properties = bq27425_battery_props;
di->bat.num_properties = ARRAY_SIZE(bq27425_battery_props);
@@ -793,14 +791,11 @@ static int bq27x00_battery_probe(struct i2c_client *client,
int retval = 0;
/* Get new ID for the new battery device */
- retval = idr_pre_get(&battery_id, GFP_KERNEL);
- if (retval == 0)
- return -ENOMEM;
mutex_lock(&battery_mutex);
- retval = idr_get_new(&battery_id, client, &num);
+ num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL);
mutex_unlock(&battery_mutex);
- if (retval < 0)
- return retval;
+ if (num < 0)
+ return num;
name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
if (!name) {
@@ -971,7 +966,6 @@ static int bq27000_battery_probe(struct platform_device *pdev)
return 0;
err_free:
- platform_set_drvdata(pdev, NULL);
kfree(di);
return ret;
@@ -983,7 +977,6 @@ static int bq27000_battery_remove(struct platform_device *pdev)
bq27x00_powersupply_unregister(di);
- platform_set_drvdata(pdev, NULL);
kfree(di);
return 0;
diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c
index 6ba047f5ac2..9e4dab46eef 100644
--- a/drivers/power/charger-manager.c
+++ b/drivers/power/charger-manager.c
@@ -12,6 +12,8 @@
* published by the Free Software Foundation.
**/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/io.h>
#include <linux/module.h>
#include <linux/irq.h>
@@ -23,12 +25,23 @@
#include <linux/power/charger-manager.h>
#include <linux/regulator/consumer.h>
#include <linux/sysfs.h>
+#include <linux/of.h>
+#include <linux/thermal.h>
+
+/*
+ * Default termperature threshold for charging.
+ * Every temperature units are in tenth of centigrade.
+ */
+#define CM_DEFAULT_RECHARGE_TEMP_DIFF 50
+#define CM_DEFAULT_CHARGE_TEMP_MAX 500
static const char * const default_event_names[] = {
[CM_EVENT_UNKNOWN] = "Unknown",
[CM_EVENT_BATT_FULL] = "Battery Full",
[CM_EVENT_BATT_IN] = "Battery Inserted",
[CM_EVENT_BATT_OUT] = "Battery Pulled Out",
+ [CM_EVENT_BATT_OVERHEAT] = "Battery Overheat",
+ [CM_EVENT_BATT_COLD] = "Battery Cold",
[CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach",
[CM_EVENT_CHG_START_STOP] = "Charging Start/Stop",
[CM_EVENT_OTHERS] = "Other battery events"
@@ -195,8 +208,8 @@ static bool is_charging(struct charger_manager *cm)
cm->charger_stat[i],
POWER_SUPPLY_PROP_ONLINE, &val);
if (ret) {
- dev_warn(cm->dev, "Cannot read ONLINE value from %s.\n",
- cm->desc->psy_charger_stat[i]);
+ dev_warn(cm->dev, "Cannot read ONLINE value from %s\n",
+ cm->desc->psy_charger_stat[i]);
continue;
}
if (val.intval == 0)
@@ -210,8 +223,8 @@ static bool is_charging(struct charger_manager *cm)
cm->charger_stat[i],
POWER_SUPPLY_PROP_STATUS, &val);
if (ret) {
- dev_warn(cm->dev, "Cannot read STATUS value from %s.\n",
- cm->desc->psy_charger_stat[i]);
+ dev_warn(cm->dev, "Cannot read STATUS value from %s\n",
+ cm->desc->psy_charger_stat[i]);
continue;
}
if (val.intval == POWER_SUPPLY_STATUS_FULL ||
@@ -289,7 +302,7 @@ static bool is_polling_required(struct charger_manager *cm)
return is_charging(cm);
default:
dev_warn(cm->dev, "Incorrect polling_mode (%d)\n",
- cm->desc->polling_mode);
+ cm->desc->polling_mode);
}
return false;
@@ -331,9 +344,8 @@ static int try_charger_enable(struct charger_manager *cm, bool enable)
err = regulator_enable(desc->charger_regulators[i].consumer);
if (err < 0) {
- dev_warn(cm->dev,
- "Cannot enable %s regulator\n",
- desc->charger_regulators[i].regulator_name);
+ dev_warn(cm->dev, "Cannot enable %s regulator\n",
+ desc->charger_regulators[i].regulator_name);
}
}
} else {
@@ -350,9 +362,8 @@ static int try_charger_enable(struct charger_manager *cm, bool enable)
err = regulator_disable(desc->charger_regulators[i].consumer);
if (err < 0) {
- dev_warn(cm->dev,
- "Cannot disable %s regulator\n",
- desc->charger_regulators[i].regulator_name);
+ dev_warn(cm->dev, "Cannot disable %s regulator\n",
+ desc->charger_regulators[i].regulator_name);
}
}
@@ -365,9 +376,8 @@ static int try_charger_enable(struct charger_manager *cm, bool enable)
desc->charger_regulators[i].consumer)) {
regulator_force_disable(
desc->charger_regulators[i].consumer);
- dev_warn(cm->dev,
- "Disable regulator(%s) forcibly.\n",
- desc->charger_regulators[i].regulator_name);
+ dev_warn(cm->dev, "Disable regulator(%s) forcibly\n",
+ desc->charger_regulators[i].regulator_name);
}
}
}
@@ -450,7 +460,7 @@ static void uevent_notify(struct charger_manager *cm, const char *event)
strncpy(env_str, event, UEVENT_BUF_SIZE);
kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
- dev_info(cm->dev, event);
+ dev_info(cm->dev, "%s\n", event);
}
/**
@@ -478,7 +488,7 @@ static void fullbatt_vchk(struct work_struct *work)
err = get_batt_uV(cm, &batt_uV);
if (err) {
- dev_err(cm->dev, "%s: get_batt_uV error(%d).\n", __func__, err);
+ dev_err(cm->dev, "%s: get_batt_uV error(%d)\n", __func__, err);
return;
}
@@ -486,7 +496,7 @@ static void fullbatt_vchk(struct work_struct *work)
if (diff < 0)
return;
- dev_info(cm->dev, "VBATT dropped %duV after full-batt.\n", diff);
+ dev_info(cm->dev, "VBATT dropped %duV after full-batt\n", diff);
if (diff > desc->fullbatt_vchkdrop_uV) {
try_charger_restart(cm);
@@ -519,7 +529,7 @@ static int check_charging_duration(struct charger_manager *cm)
duration = curr - cm->charging_start_time;
if (duration > desc->charging_max_duration_ms) {
- dev_info(cm->dev, "Charging duration exceed %lldms",
+ dev_info(cm->dev, "Charging duration exceed %ums\n",
desc->charging_max_duration_ms);
uevent_notify(cm, "Discharging");
try_charger_enable(cm, false);
@@ -530,9 +540,9 @@ static int check_charging_duration(struct charger_manager *cm)
if (duration > desc->charging_max_duration_ms &&
is_ext_pwr_online(cm)) {
- dev_info(cm->dev, "DisCharging duration exceed %lldms",
+ dev_info(cm->dev, "Discharging duration exceed %ums\n",
desc->discharging_max_duration_ms);
- uevent_notify(cm, "Recharing");
+ uevent_notify(cm, "Recharging");
try_charger_enable(cm, true);
ret = true;
}
@@ -541,6 +551,60 @@ static int check_charging_duration(struct charger_manager *cm)
return ret;
}
+static int cm_get_battery_temperature(struct charger_manager *cm,
+ int *temp)
+{
+ int ret;
+
+ if (!cm->desc->measure_battery_temp)
+ return -ENODEV;
+
+#ifdef CONFIG_THERMAL
+ ret = thermal_zone_get_temp(cm->tzd_batt, (unsigned long *)temp);
+ if (!ret)
+ /* Calibrate temperature unit */
+ *temp /= 100;
+#else
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_TEMP,
+ (union power_supply_propval *)temp);
+#endif
+ return ret;
+}
+
+static int cm_check_thermal_status(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ int temp, upper_limit, lower_limit;
+ int ret = 0;
+
+ ret = cm_get_battery_temperature(cm, &temp);
+ if (ret) {
+ /* FIXME:
+ * No information of battery temperature might
+ * occur hazadous result. We have to handle it
+ * depending on battery type.
+ */
+ dev_err(cm->dev, "Failed to get battery temperature\n");
+ return 0;
+ }
+
+ upper_limit = desc->temp_max;
+ lower_limit = desc->temp_min;
+
+ if (cm->emergency_stop) {
+ upper_limit -= desc->temp_diff;
+ lower_limit += desc->temp_diff;
+ }
+
+ if (temp > upper_limit)
+ ret = CM_EVENT_BATT_OVERHEAT;
+ else if (temp < lower_limit)
+ ret = CM_EVENT_BATT_COLD;
+
+ return ret;
+}
+
/**
* _cm_monitor - Monitor the temperature and return true for exceptions.
* @cm: the Charger Manager representing the battery.
@@ -550,28 +614,22 @@ static int check_charging_duration(struct charger_manager *cm)
*/
static bool _cm_monitor(struct charger_manager *cm)
{
- struct charger_desc *desc = cm->desc;
- int temp = desc->temperature_out_of_range(&cm->last_temp_mC);
+ int temp_alrt;
- dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n",
- cm->last_temp_mC / 1000, cm->last_temp_mC % 1000);
+ temp_alrt = cm_check_thermal_status(cm);
/* It has been stopped already */
- if (temp && cm->emergency_stop)
+ if (temp_alrt && cm->emergency_stop)
return false;
/*
* Check temperature whether overheat or cold.
* If temperature is out of range normal state, stop charging.
*/
- if (temp) {
- cm->emergency_stop = temp;
- if (!try_charger_enable(cm, false)) {
- if (temp > 0)
- uevent_notify(cm, "OVERHEAT");
- else
- uevent_notify(cm, "COLD");
- }
+ if (temp_alrt) {
+ cm->emergency_stop = temp_alrt;
+ if (!try_charger_enable(cm, false))
+ uevent_notify(cm, default_event_names[temp_alrt]);
/*
* Check whole charging duration and discharing duration
@@ -579,7 +637,7 @@ static bool _cm_monitor(struct charger_manager *cm)
*/
} else if (!cm->emergency_stop && check_charging_duration(cm)) {
dev_dbg(cm->dev,
- "Charging/Discharging duration is out of range");
+ "Charging/Discharging duration is out of range\n");
/*
* Check dropped voltage of battery. If battery voltage is more
* dropped than fullbatt_vchkdrop_uV after fully charged state,
@@ -595,7 +653,7 @@ static bool _cm_monitor(struct charger_manager *cm)
*/
} else if (!cm->emergency_stop && is_full_charged(cm) &&
cm->charger_enabled) {
- dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged.\n");
+ dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n");
uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]);
try_charger_enable(cm, false);
@@ -669,15 +727,21 @@ static void _setup_polling(struct work_struct *work)
WARN(cm_wq == NULL, "charger-manager: workqueue not initialized"
". try it later. %s\n", __func__);
+ /*
+ * Use mod_delayed_work() iff the next polling interval should
+ * occur before the currently scheduled one. If @cm_monitor_work
+ * isn't active, the end result is the same, so no need to worry
+ * about stale @next_polling.
+ */
_next_polling = jiffies + polling_jiffy;
- if (!delayed_work_pending(&cm_monitor_work) ||
- (delayed_work_pending(&cm_monitor_work) &&
- time_after(next_polling, _next_polling))) {
- next_polling = jiffies + polling_jiffy;
+ if (time_before(_next_polling, next_polling)) {
mod_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy);
+ next_polling = _next_polling;
+ } else {
+ if (queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy))
+ next_polling = _next_polling;
}
-
out:
mutex_unlock(&cm_list_mtx);
}
@@ -719,7 +783,7 @@ static void fullbatt_handler(struct charger_manager *cm)
cm->fullbatt_vchk_jiffies_at = 1;
out:
- dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged.\n");
+ dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n");
uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]);
}
@@ -751,8 +815,7 @@ static void misc_event_handler(struct charger_manager *cm,
if (cm_suspended)
device_set_wakeup_capable(cm->dev, true);
- if (!delayed_work_pending(&cm_monitor_work) &&
- is_polling_required(cm) && cm->desc->polling_interval_ms)
+ if (is_polling_required(cm) && cm->desc->polling_interval_ms)
schedule_work(&setup_polling);
uevent_notify(cm, default_event_names[type]);
}
@@ -798,21 +861,8 @@ static int charger_get_property(struct power_supply *psy,
POWER_SUPPLY_PROP_CURRENT_NOW, val);
break;
case POWER_SUPPLY_PROP_TEMP:
- /* in thenth of centigrade */
- if (cm->last_temp_mC == INT_MIN)
- desc->temperature_out_of_range(&cm->last_temp_mC);
- val->intval = cm->last_temp_mC / 100;
- if (!desc->measure_battery_temp)
- ret = -ENODEV;
- break;
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
- /* in thenth of centigrade */
- if (cm->last_temp_mC == INT_MIN)
- desc->temperature_out_of_range(&cm->last_temp_mC);
- val->intval = cm->last_temp_mC / 100;
- if (desc->measure_battery_temp)
- ret = -ENODEV;
- break;
+ return cm_get_battery_temperature(cm, &val->intval);
case POWER_SUPPLY_PROP_CAPACITY:
if (!cm->fuel_gauge) {
ret = -ENODEV;
@@ -967,7 +1017,7 @@ static bool cm_setup_timer(void)
mutex_unlock(&cm_list_mtx);
if (wakeup_ms < UINT_MAX && wakeup_ms > 0) {
- pr_info("Charger Manager wakeup timer: %u ms.\n", wakeup_ms);
+ pr_info("Charger Manager wakeup timer: %u ms\n", wakeup_ms);
if (rtc_dev) {
struct rtc_wkalrm tmp;
unsigned long time, now;
@@ -1000,8 +1050,7 @@ static bool cm_setup_timer(void)
ret = false;
}
- pr_info("Waking up after %lu secs.\n",
- time - now);
+ pr_info("Waking up after %lu secs\n", time - now);
rtc_time_to_tm(time, &tmp.time);
rtc_set_alarm(rtc_dev, &tmp);
@@ -1096,7 +1145,7 @@ int setup_charger_manager(struct charger_global_desc *gd)
g_desc = NULL;
if (!gd->rtc_only_wakeup) {
- pr_err("The callback rtc_only_wakeup is not given.\n");
+ pr_err("The callback rtc_only_wakeup is not given\n");
return -EINVAL;
}
@@ -1107,7 +1156,7 @@ int setup_charger_manager(struct charger_global_desc *gd)
/* Retry at probe. RTC may be not registered yet */
}
} else {
- pr_warn("No wakeup timer is given for charger manager."
+ pr_warn("No wakeup timer is given for charger manager. "
"In-suspend monitoring won't work.\n");
}
@@ -1133,13 +1182,13 @@ static void charger_extcon_work(struct work_struct *work)
cable->min_uA, cable->max_uA);
if (ret < 0) {
pr_err("Cannot set current limit of %s (%s)\n",
- cable->charger->regulator_name, cable->name);
+ cable->charger->regulator_name, cable->name);
return;
}
pr_info("Set current limit of %s : %duA ~ %duA\n",
- cable->charger->regulator_name,
- cable->min_uA, cable->max_uA);
+ cable->charger->regulator_name,
+ cable->min_uA, cable->max_uA);
}
try_charger_enable(cable->cm, cable->attached);
@@ -1170,8 +1219,7 @@ static int charger_extcon_notifier(struct notifier_block *self,
* when charger cable is attached.
*/
if (cable->attached && is_polling_required(cable->cm)) {
- if (work_pending(&setup_polling))
- cancel_work_sync(&setup_polling);
+ cancel_work_sync(&setup_polling);
schedule_work(&setup_polling);
}
@@ -1206,15 +1254,62 @@ static int charger_extcon_init(struct charger_manager *cm,
ret = extcon_register_interest(&cable->extcon_dev,
cable->extcon_name, cable->name, &cable->nb);
if (ret < 0) {
- pr_info("Cannot register extcon_dev for %s(cable: %s).\n",
- cable->extcon_name,
- cable->name);
+ pr_info("Cannot register extcon_dev for %s(cable: %s)\n",
+ cable->extcon_name, cable->name);
ret = -EINVAL;
}
return ret;
}
+/**
+ * charger_manager_register_extcon - Register extcon device to recevie state
+ * of charger cable.
+ * @cm: the Charger Manager representing the battery.
+ *
+ * This function support EXTCON(External Connector) subsystem to detect the
+ * state of charger cables for enabling or disabling charger(regulator) and
+ * select the charger cable for charging among a number of external cable
+ * according to policy of H/W board.
+ */
+static int charger_manager_register_extcon(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ struct charger_regulator *charger;
+ int ret = 0;
+ int i;
+ int j;
+
+ for (i = 0; i < desc->num_charger_regulators; i++) {
+ charger = &desc->charger_regulators[i];
+
+ charger->consumer = regulator_get(cm->dev,
+ charger->regulator_name);
+ if (IS_ERR(charger->consumer)) {
+ dev_err(cm->dev, "Cannot find charger(%s)\n",
+ charger->regulator_name);
+ return PTR_ERR(charger->consumer);
+ }
+ charger->cm = cm;
+
+ for (j = 0; j < charger->num_cables; j++) {
+ struct charger_cable *cable = &charger->cables[j];
+
+ ret = charger_extcon_init(cm, cable);
+ if (ret < 0) {
+ dev_err(cm->dev, "Cannot initialize charger(%s)\n",
+ charger->regulator_name);
+ goto err;
+ }
+ cable->charger = charger;
+ cable->cm = cm;
+ }
+ }
+
+err:
+ return ret;
+}
+
/* help function of sysfs node to control charger(regulator) */
static ssize_t charger_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -1274,7 +1369,7 @@ static ssize_t charger_externally_control_store(struct device *dev,
for (i = 0; i < desc->num_charger_regulators; i++) {
if (&desc->charger_regulators[i] != charger &&
- !desc->charger_regulators[i].externally_control) {
+ !desc->charger_regulators[i].externally_control) {
/*
* At least, one charger is controlled by
* charger-manager
@@ -1294,144 +1389,372 @@ static ssize_t charger_externally_control_store(struct device *dev,
}
} else {
dev_warn(cm->dev,
- "'%s' regulator should be controlled "
- "in charger-manager because charger-manager "
- "must need at least one charger for charging\n",
- charger->regulator_name);
+ "'%s' regulator should be controlled in charger-manager because charger-manager must need at least one charger for charging\n",
+ charger->regulator_name);
}
return count;
}
+/**
+ * charger_manager_register_sysfs - Register sysfs entry for each charger
+ * @cm: the Charger Manager representing the battery.
+ *
+ * This function add sysfs entry for charger(regulator) to control charger from
+ * user-space. If some development board use one more chargers for charging
+ * but only need one charger on specific case which is dependent on user
+ * scenario or hardware restrictions, the user enter 1 or 0(zero) to '/sys/
+ * class/power_supply/battery/charger.[index]/externally_control'. For example,
+ * if user enter 1 to 'sys/class/power_supply/battery/charger.[index]/
+ * externally_control, this charger isn't controlled from charger-manager and
+ * always stay off state of regulator.
+ */
+static int charger_manager_register_sysfs(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ struct charger_regulator *charger;
+ int chargers_externally_control = 1;
+ char buf[11];
+ char *str;
+ int ret = 0;
+ int i;
+
+ /* Create sysfs entry to control charger(regulator) */
+ for (i = 0; i < desc->num_charger_regulators; i++) {
+ charger = &desc->charger_regulators[i];
+
+ snprintf(buf, 10, "charger.%d", i);
+ str = devm_kzalloc(cm->dev,
+ sizeof(char) * (strlen(buf) + 1), GFP_KERNEL);
+ if (!str) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ strcpy(str, buf);
+
+ charger->attrs[0] = &charger->attr_name.attr;
+ charger->attrs[1] = &charger->attr_state.attr;
+ charger->attrs[2] = &charger->attr_externally_control.attr;
+ charger->attrs[3] = NULL;
+ charger->attr_g.name = str;
+ charger->attr_g.attrs = charger->attrs;
+
+ sysfs_attr_init(&charger->attr_name.attr);
+ charger->attr_name.attr.name = "name";
+ charger->attr_name.attr.mode = 0444;
+ charger->attr_name.show = charger_name_show;
+
+ sysfs_attr_init(&charger->attr_state.attr);
+ charger->attr_state.attr.name = "state";
+ charger->attr_state.attr.mode = 0444;
+ charger->attr_state.show = charger_state_show;
+
+ sysfs_attr_init(&charger->attr_externally_control.attr);
+ charger->attr_externally_control.attr.name
+ = "externally_control";
+ charger->attr_externally_control.attr.mode = 0644;
+ charger->attr_externally_control.show
+ = charger_externally_control_show;
+ charger->attr_externally_control.store
+ = charger_externally_control_store;
+
+ if (!desc->charger_regulators[i].externally_control ||
+ !chargers_externally_control)
+ chargers_externally_control = 0;
+
+ dev_info(cm->dev, "'%s' regulator's externally_control is %d\n",
+ charger->regulator_name, charger->externally_control);
+
+ ret = sysfs_create_group(&cm->charger_psy.dev->kobj,
+ &charger->attr_g);
+ if (ret < 0) {
+ dev_err(cm->dev, "Cannot create sysfs entry of %s regulator\n",
+ charger->regulator_name);
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ if (chargers_externally_control) {
+ dev_err(cm->dev, "Cannot register regulator because charger-manager must need at least one charger for charging battery\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
+static int cm_init_thermal_data(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ union power_supply_propval val;
+ int ret;
+
+ /* Verify whether fuel gauge provides battery temperature */
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_TEMP, &val);
+
+ if (!ret) {
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_TEMP;
+ cm->charger_psy.num_properties++;
+ cm->desc->measure_battery_temp = true;
+ }
+#ifdef CONFIG_THERMAL
+ cm->tzd_batt = cm->fuel_gauge->tzd;
+
+ if (ret && desc->thermal_zone) {
+ cm->tzd_batt =
+ thermal_zone_get_zone_by_name(desc->thermal_zone);
+ if (IS_ERR(cm->tzd_batt))
+ return PTR_ERR(cm->tzd_batt);
+
+ /* Use external thermometer */
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_TEMP_AMBIENT;
+ cm->charger_psy.num_properties++;
+ cm->desc->measure_battery_temp = true;
+ ret = 0;
+ }
+#endif
+ if (cm->desc->measure_battery_temp) {
+ /* NOTICE : Default allowable minimum charge temperature is 0 */
+ if (!desc->temp_max)
+ desc->temp_max = CM_DEFAULT_CHARGE_TEMP_MAX;
+ if (!desc->temp_diff)
+ desc->temp_diff = CM_DEFAULT_RECHARGE_TEMP_DIFF;
+ }
+
+ return ret;
+}
+
+static struct of_device_id charger_manager_match[] = {
+ {
+ .compatible = "charger-manager",
+ },
+ {},
+};
+
+static struct charger_desc *of_cm_parse_desc(struct device *dev)
+{
+ struct charger_desc *desc;
+ struct device_node *np = dev->of_node;
+ u32 poll_mode = CM_POLL_DISABLE;
+ u32 battery_stat = CM_NO_BATTERY;
+ int num_chgs = 0;
+
+ desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ return ERR_PTR(-ENOMEM);
+
+ of_property_read_string(np, "cm-name", &desc->psy_name);
+
+ of_property_read_u32(np, "cm-poll-mode", &poll_mode);
+ desc->polling_mode = poll_mode;
+
+ of_property_read_u32(np, "cm-poll-interval",
+ &desc->polling_interval_ms);
+
+ of_property_read_u32(np, "cm-fullbatt-vchkdrop-ms",
+ &desc->fullbatt_vchkdrop_ms);
+ of_property_read_u32(np, "cm-fullbatt-vchkdrop-volt",
+ &desc->fullbatt_vchkdrop_uV);
+ of_property_read_u32(np, "cm-fullbatt-voltage", &desc->fullbatt_uV);
+ of_property_read_u32(np, "cm-fullbatt-soc", &desc->fullbatt_soc);
+ of_property_read_u32(np, "cm-fullbatt-capacity",
+ &desc->fullbatt_full_capacity);
+
+ of_property_read_u32(np, "cm-battery-stat", &battery_stat);
+ desc->battery_present = battery_stat;
+
+ /* chargers */
+ of_property_read_u32(np, "cm-num-chargers", &num_chgs);
+ if (num_chgs) {
+ /* Allocate empty bin at the tail of array */
+ desc->psy_charger_stat = devm_kzalloc(dev, sizeof(char *)
+ * (num_chgs + 1), GFP_KERNEL);
+ if (desc->psy_charger_stat) {
+ int i;
+ for (i = 0; i < num_chgs; i++)
+ of_property_read_string_index(np, "cm-chargers",
+ i, &desc->psy_charger_stat[i]);
+ } else {
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+
+ of_property_read_string(np, "cm-fuel-gauge", &desc->psy_fuel_gauge);
+
+ of_property_read_string(np, "cm-thermal-zone", &desc->thermal_zone);
+
+ of_property_read_u32(np, "cm-battery-cold", &desc->temp_min);
+ if (of_get_property(np, "cm-battery-cold-in-minus", NULL))
+ desc->temp_min *= -1;
+ of_property_read_u32(np, "cm-battery-hot", &desc->temp_max);
+ of_property_read_u32(np, "cm-battery-temp-diff", &desc->temp_diff);
+
+ of_property_read_u32(np, "cm-charging-max",
+ &desc->charging_max_duration_ms);
+ of_property_read_u32(np, "cm-discharging-max",
+ &desc->discharging_max_duration_ms);
+
+ /* battery charger regualtors */
+ desc->num_charger_regulators = of_get_child_count(np);
+ if (desc->num_charger_regulators) {
+ struct charger_regulator *chg_regs;
+ struct device_node *child;
+
+ chg_regs = devm_kzalloc(dev, sizeof(*chg_regs)
+ * desc->num_charger_regulators,
+ GFP_KERNEL);
+ if (!chg_regs)
+ return ERR_PTR(-ENOMEM);
+
+ desc->charger_regulators = chg_regs;
+
+ for_each_child_of_node(np, child) {
+ struct charger_cable *cables;
+ struct device_node *_child;
+
+ of_property_read_string(child, "cm-regulator-name",
+ &chg_regs->regulator_name);
+
+ /* charger cables */
+ chg_regs->num_cables = of_get_child_count(child);
+ if (chg_regs->num_cables) {
+ cables = devm_kzalloc(dev, sizeof(*cables)
+ * chg_regs->num_cables,
+ GFP_KERNEL);
+ if (!cables)
+ return ERR_PTR(-ENOMEM);
+
+ chg_regs->cables = cables;
+
+ for_each_child_of_node(child, _child) {
+ of_property_read_string(_child,
+ "cm-cable-name", &cables->name);
+ of_property_read_string(_child,
+ "cm-cable-extcon",
+ &cables->extcon_name);
+ of_property_read_u32(_child,
+ "cm-cable-min",
+ &cables->min_uA);
+ of_property_read_u32(_child,
+ "cm-cable-max",
+ &cables->max_uA);
+ cables++;
+ }
+ }
+ chg_regs++;
+ }
+ }
+ return desc;
+}
+
+static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev)
+{
+ if (pdev->dev.of_node)
+ return of_cm_parse_desc(&pdev->dev);
+ return (struct charger_desc *)dev_get_platdata(&pdev->dev);
+}
+
static int charger_manager_probe(struct platform_device *pdev)
{
- struct charger_desc *desc = dev_get_platdata(&pdev->dev);
+ struct charger_desc *desc = cm_get_drv_data(pdev);
struct charger_manager *cm;
int ret = 0, i = 0;
int j = 0;
- int chargers_externally_control = 1;
union power_supply_propval val;
if (g_desc && !rtc_dev && g_desc->rtc_name) {
rtc_dev = rtc_class_open(g_desc->rtc_name);
if (IS_ERR_OR_NULL(rtc_dev)) {
rtc_dev = NULL;
- dev_err(&pdev->dev, "Cannot get RTC %s.\n",
+ dev_err(&pdev->dev, "Cannot get RTC %s\n",
g_desc->rtc_name);
- ret = -ENODEV;
- goto err_alloc;
+ return -ENODEV;
}
}
if (!desc) {
- dev_err(&pdev->dev, "No platform data (desc) found.\n");
- ret = -ENODEV;
- goto err_alloc;
+ dev_err(&pdev->dev, "No platform data (desc) found\n");
+ return -ENODEV;
}
- cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL);
- if (!cm) {
- dev_err(&pdev->dev, "Cannot allocate memory.\n");
- ret = -ENOMEM;
- goto err_alloc;
- }
+ cm = devm_kzalloc(&pdev->dev,
+ sizeof(struct charger_manager), GFP_KERNEL);
+ if (!cm)
+ return -ENOMEM;
/* Basic Values. Unspecified are Null or 0 */
cm->dev = &pdev->dev;
- cm->desc = kzalloc(sizeof(struct charger_desc), GFP_KERNEL);
- if (!cm->desc) {
- dev_err(&pdev->dev, "Cannot allocate memory.\n");
- ret = -ENOMEM;
- goto err_alloc_desc;
- }
- memcpy(cm->desc, desc, sizeof(struct charger_desc));
- cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */
+ cm->desc = desc;
/*
* The following two do not need to be errors.
* Users may intentionally ignore those two features.
*/
if (desc->fullbatt_uV == 0) {
- dev_info(&pdev->dev, "Ignoring full-battery voltage threshold"
- " as it is not supplied.");
+ dev_info(&pdev->dev, "Ignoring full-battery voltage threshold as it is not supplied\n");
}
if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) {
- dev_info(&pdev->dev, "Disabling full-battery voltage drop "
- "checking mechanism as it is not supplied.");
+ dev_info(&pdev->dev, "Disabling full-battery voltage drop checking mechanism as it is not supplied\n");
desc->fullbatt_vchkdrop_ms = 0;
desc->fullbatt_vchkdrop_uV = 0;
}
if (desc->fullbatt_soc == 0) {
- dev_info(&pdev->dev, "Ignoring full-battery soc(state of"
- " charge) threshold as it is not"
- " supplied.");
+ dev_info(&pdev->dev, "Ignoring full-battery soc(state of charge) threshold as it is not supplied\n");
}
if (desc->fullbatt_full_capacity == 0) {
- dev_info(&pdev->dev, "Ignoring full-battery full capacity"
- " threshold as it is not supplied.");
+ dev_info(&pdev->dev, "Ignoring full-battery full capacity threshold as it is not supplied\n");
}
if (!desc->charger_regulators || desc->num_charger_regulators < 1) {
- ret = -EINVAL;
- dev_err(&pdev->dev, "charger_regulators undefined.\n");
- goto err_no_charger;
+ dev_err(&pdev->dev, "charger_regulators undefined\n");
+ return -EINVAL;
}
if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) {
- dev_err(&pdev->dev, "No power supply defined.\n");
- ret = -EINVAL;
- goto err_no_charger_stat;
+ dev_err(&pdev->dev, "No power supply defined\n");
+ return -EINVAL;
}
/* Counting index only */
while (desc->psy_charger_stat[i])
i++;
- cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1),
- GFP_KERNEL);
- if (!cm->charger_stat) {
- ret = -ENOMEM;
- goto err_no_charger_stat;
- }
+ cm->charger_stat = devm_kzalloc(&pdev->dev,
+ sizeof(struct power_supply *) * i, GFP_KERNEL);
+ if (!cm->charger_stat)
+ return -ENOMEM;
for (i = 0; desc->psy_charger_stat[i]; i++) {
cm->charger_stat[i] = power_supply_get_by_name(
desc->psy_charger_stat[i]);
if (!cm->charger_stat[i]) {
- dev_err(&pdev->dev, "Cannot find power supply "
- "\"%s\"\n",
- desc->psy_charger_stat[i]);
- ret = -ENODEV;
- goto err_chg_stat;
+ dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
+ desc->psy_charger_stat[i]);
+ return -ENODEV;
}
}
cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
if (!cm->fuel_gauge) {
dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
- desc->psy_fuel_gauge);
- ret = -ENODEV;
- goto err_chg_stat;
+ desc->psy_fuel_gauge);
+ return -ENODEV;
}
if (desc->polling_interval_ms == 0 ||
msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) {
dev_err(&pdev->dev, "polling_interval_ms is too small\n");
- ret = -EINVAL;
- goto err_chg_stat;
- }
-
- if (!desc->temperature_out_of_range) {
- dev_err(&pdev->dev, "there is no temperature_out_of_range\n");
- ret = -EINVAL;
- goto err_chg_stat;
+ return -EINVAL;
}
if (!desc->charging_max_duration_ms ||
!desc->discharging_max_duration_ms) {
- dev_info(&pdev->dev, "Cannot limit charging duration "
- "checking mechanism to prevent overcharge/overheat "
- "and control discharging duration");
+ dev_info(&pdev->dev, "Cannot limit charging duration checking mechanism to prevent overcharge/overheat and control discharging duration\n");
desc->charging_max_duration_ms = 0;
desc->discharging_max_duration_ms = 0;
}
@@ -1440,23 +1763,20 @@ static int charger_manager_probe(struct platform_device *pdev)
memcpy(&cm->charger_psy, &psy_default, sizeof(psy_default));
- if (!desc->psy_name) {
+ if (!desc->psy_name)
strncpy(cm->psy_name_buf, psy_default.name, PSY_NAME_MAX);
- } else {
+ else
strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX);
- }
cm->charger_psy.name = cm->psy_name_buf;
/* Allocate for psy properties because they may vary */
- cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property)
+ cm->charger_psy.properties = devm_kzalloc(&pdev->dev,
+ sizeof(enum power_supply_property)
* (ARRAY_SIZE(default_charger_props) +
- NUM_CHARGER_PSY_OPTIONAL),
- GFP_KERNEL);
- if (!cm->charger_psy.properties) {
- dev_err(&pdev->dev, "Cannot allocate for psy properties.\n");
- ret = -ENOMEM;
- goto err_chg_stat;
- }
+ NUM_CHARGER_PSY_OPTIONAL), GFP_KERNEL);
+ if (!cm->charger_psy.properties)
+ return -ENOMEM;
+
memcpy(cm->charger_psy.properties, default_charger_props,
sizeof(enum power_supply_property) *
ARRAY_SIZE(default_charger_props));
@@ -1477,124 +1797,34 @@ static int charger_manager_probe(struct platform_device *pdev)
cm->charger_psy.num_properties++;
}
- if (desc->measure_battery_temp) {
- cm->charger_psy.properties[cm->charger_psy.num_properties] =
- POWER_SUPPLY_PROP_TEMP;
- cm->charger_psy.num_properties++;
- } else {
- cm->charger_psy.properties[cm->charger_psy.num_properties] =
- POWER_SUPPLY_PROP_TEMP_AMBIENT;
- cm->charger_psy.num_properties++;
+ ret = cm_init_thermal_data(cm);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to initialize thermal data\n");
+ cm->desc->measure_battery_temp = false;
}
INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
ret = power_supply_register(NULL, &cm->charger_psy);
if (ret) {
- dev_err(&pdev->dev, "Cannot register charger-manager with"
- " name \"%s\".\n", cm->charger_psy.name);
- goto err_register;
- }
-
- for (i = 0 ; i < desc->num_charger_regulators ; i++) {
- struct charger_regulator *charger
- = &desc->charger_regulators[i];
- char buf[11];
- char *str;
-
- charger->consumer = regulator_get(&pdev->dev,
- charger->regulator_name);
- if (charger->consumer == NULL) {
- dev_err(&pdev->dev, "Cannot find charger(%s)n",
- charger->regulator_name);
- ret = -EINVAL;
- goto err_chg_get;
- }
- charger->cm = cm;
-
- for (j = 0 ; j < charger->num_cables ; j++) {
- struct charger_cable *cable = &charger->cables[j];
-
- ret = charger_extcon_init(cm, cable);
- if (ret < 0) {
- dev_err(&pdev->dev, "Cannot find charger(%s)n",
- charger->regulator_name);
- goto err_extcon;
- }
- cable->charger = charger;
- cable->cm = cm;
- }
-
- /* Create sysfs entry to control charger(regulator) */
- snprintf(buf, 10, "charger.%d", i);
- str = kzalloc(sizeof(char) * (strlen(buf) + 1), GFP_KERNEL);
- if (!str) {
- for (i--; i >= 0; i--) {
- charger = &desc->charger_regulators[i];
- kfree(charger->attr_g.name);
- }
- ret = -ENOMEM;
-
- goto err_extcon;
- }
- strcpy(str, buf);
-
- charger->attrs[0] = &charger->attr_name.attr;
- charger->attrs[1] = &charger->attr_state.attr;
- charger->attrs[2] = &charger->attr_externally_control.attr;
- charger->attrs[3] = NULL;
- charger->attr_g.name = str;
- charger->attr_g.attrs = charger->attrs;
-
- sysfs_attr_init(&charger->attr_name.attr);
- charger->attr_name.attr.name = "name";
- charger->attr_name.attr.mode = 0444;
- charger->attr_name.show = charger_name_show;
-
- sysfs_attr_init(&charger->attr_state.attr);
- charger->attr_state.attr.name = "state";
- charger->attr_state.attr.mode = 0444;
- charger->attr_state.show = charger_state_show;
-
- sysfs_attr_init(&charger->attr_externally_control.attr);
- charger->attr_externally_control.attr.name
- = "externally_control";
- charger->attr_externally_control.attr.mode = 0644;
- charger->attr_externally_control.show
- = charger_externally_control_show;
- charger->attr_externally_control.store
- = charger_externally_control_store;
-
- if (!desc->charger_regulators[i].externally_control ||
- !chargers_externally_control) {
- chargers_externally_control = 0;
- }
- dev_info(&pdev->dev, "'%s' regulator's externally_control"
- "is %d\n", charger->regulator_name,
- charger->externally_control);
-
- ret = sysfs_create_group(&cm->charger_psy.dev->kobj,
- &charger->attr_g);
- if (ret < 0) {
- dev_info(&pdev->dev, "Cannot create sysfs entry"
- "of %s regulator\n",
- charger->regulator_name);
- }
+ dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n",
+ cm->charger_psy.name);
+ return ret;
}
- if (chargers_externally_control) {
- dev_err(&pdev->dev, "Cannot register regulator because "
- "charger-manager must need at least "
- "one charger for charging battery\n");
-
- ret = -EINVAL;
- goto err_chg_enable;
+ /* Register extcon device for charger cable */
+ ret = charger_manager_register_extcon(cm);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot initialize extcon device\n");
+ goto err_reg_extcon;
}
- ret = try_charger_enable(cm, true);
- if (ret) {
- dev_err(&pdev->dev, "Cannot enable charger regulators\n");
- goto err_chg_enable;
+ /* Register sysfs entry for charger(regulator) */
+ ret = charger_manager_register_sysfs(cm);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Cannot initialize sysfs entry of regulator\n");
+ goto err_reg_sysfs;
}
/* Add to the list */
@@ -1613,39 +1843,31 @@ static int charger_manager_probe(struct platform_device *pdev)
return 0;
-err_chg_enable:
+err_reg_sysfs:
for (i = 0; i < desc->num_charger_regulators; i++) {
struct charger_regulator *charger;
charger = &desc->charger_regulators[i];
sysfs_remove_group(&cm->charger_psy.dev->kobj,
&charger->attr_g);
- kfree(charger->attr_g.name);
}
-err_extcon:
- for (i = 0 ; i < desc->num_charger_regulators ; i++) {
- struct charger_regulator *charger
- = &desc->charger_regulators[i];
- for (j = 0 ; j < charger->num_cables ; j++) {
+err_reg_extcon:
+ for (i = 0; i < desc->num_charger_regulators; i++) {
+ struct charger_regulator *charger;
+
+ charger = &desc->charger_regulators[i];
+ for (j = 0; j < charger->num_cables; j++) {
struct charger_cable *cable = &charger->cables[j];
- extcon_unregister_interest(&cable->extcon_dev);
+ /* Remove notifier block if only edev exists */
+ if (cable->extcon_dev.edev)
+ extcon_unregister_interest(&cable->extcon_dev);
}
- }
-err_chg_get:
- for (i = 0 ; i < desc->num_charger_regulators ; i++)
+
regulator_put(desc->charger_regulators[i].consumer);
+ }
power_supply_unregister(&cm->charger_psy);
-err_register:
- kfree(cm->charger_psy.properties);
-err_chg_stat:
- kfree(cm->charger_stat);
-err_no_charger_stat:
-err_no_charger:
- kfree(cm->desc);
-err_alloc_desc:
- kfree(cm);
-err_alloc:
+
return ret;
}
@@ -1661,10 +1883,8 @@ static int charger_manager_remove(struct platform_device *pdev)
list_del(&cm->entry);
mutex_unlock(&cm_list_mtx);
- if (work_pending(&setup_polling))
- cancel_work_sync(&setup_polling);
- if (delayed_work_pending(&cm_monitor_work))
- cancel_delayed_work_sync(&cm_monitor_work);
+ cancel_work_sync(&setup_polling);
+ cancel_delayed_work_sync(&cm_monitor_work);
for (i = 0 ; i < desc->num_charger_regulators ; i++) {
struct charger_regulator *charger
@@ -1682,11 +1902,6 @@ static int charger_manager_remove(struct platform_device *pdev)
try_charger_enable(cm, false);
- kfree(cm->charger_psy.properties);
- kfree(cm->charger_stat);
- kfree(cm->desc);
- kfree(cm);
-
return 0;
}
@@ -1733,8 +1948,7 @@ static int cm_suspend_prepare(struct device *dev)
cm_suspended = true;
}
- if (delayed_work_pending(&cm->fullbatt_vchk_work))
- cancel_delayed_work(&cm->fullbatt_vchk_work);
+ cancel_delayed_work(&cm->fullbatt_vchk_work);
cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm);
cm->status_save_batt = is_batt_present(cm);
@@ -1804,6 +2018,7 @@ static struct platform_driver charger_manager_driver = {
.name = "charger-manager",
.owner = THIS_MODULE,
.pm = &charger_manager_pm,
+ .of_match_table = charger_manager_match,
},
.probe = charger_manager_probe,
.remove = charger_manager_remove,
@@ -1891,7 +2106,7 @@ void cm_notify_event(struct power_supply *psy, enum cm_event_types type,
uevent_notify(cm, msg ? msg : default_event_names[type]);
break;
default:
- dev_err(cm->dev, "%s type not specified.\n", __func__);
+ dev_err(cm->dev, "%s: type not specified\n", __func__);
break;
}
}
diff --git a/drivers/power/collie_battery.c b/drivers/power/collie_battery.c
index c58d0e31bde..d02ae02a759 100644
--- a/drivers/power/collie_battery.c
+++ b/drivers/power/collie_battery.c
@@ -287,7 +287,7 @@ static struct gpio collie_batt_gpios[] = {
};
#ifdef CONFIG_PM
-static int collie_bat_suspend(struct ucb1x00_dev *dev, pm_message_t state)
+static int collie_bat_suspend(struct ucb1x00_dev *dev)
{
/* flush all pending status updates */
flush_work(&bat_work);
diff --git a/drivers/power/da9030_battery.c b/drivers/power/da9030_battery.c
index 94762e67e22..ae6c41835ee 100644
--- a/drivers/power/da9030_battery.c
+++ b/drivers/power/da9030_battery.c
@@ -22,6 +22,7 @@
#include <linux/debugfs.h>
#include <linux/seq_file.h>
+#include <linux/notifier.h>
#define DA9030_FAULT_LOG 0x0a
#define DA9030_FAULT_LOG_OVER_TEMP (1 << 7)
@@ -504,7 +505,7 @@ static int da9030_battery_probe(struct platform_device *pdev)
pdata->charge_millivolt > 4350)
return -EINVAL;
- charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+ charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
if (charger == NULL)
return -ENOMEM;
@@ -556,8 +557,6 @@ err_notifier:
cancel_delayed_work(&charger->work);
err_charger_init:
- kfree(charger);
-
return ret;
}
@@ -574,8 +573,6 @@ static int da9030_battery_remove(struct platform_device *dev)
da9030_set_charge(charger, 0);
power_supply_unregister(&charger->psy);
- kfree(charger);
-
return 0;
}
diff --git a/drivers/power/da9052-battery.c b/drivers/power/da9052-battery.c
index 3c5c2e459d7..f8f4c0f7c17 100644
--- a/drivers/power/da9052-battery.c
+++ b/drivers/power/da9052-battery.c
@@ -337,7 +337,7 @@ static unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp)
if (adc_temp > vc_tbl_ref[DA9052_VC_TBL_REF_SZ - 1])
return DA9052_VC_TBL_REF_SZ - 1;
- for (i = 0; i < DA9052_VC_TBL_REF_SZ; i++) {
+ for (i = 0; i < DA9052_VC_TBL_REF_SZ - 1; i++) {
if ((adc_temp > vc_tbl_ref[i]) &&
(adc_temp <= DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1])))
return i;
@@ -594,7 +594,8 @@ static s32 da9052_bat_probe(struct platform_device *pdev)
int ret;
int i;
- bat = kzalloc(sizeof(struct da9052_battery), GFP_KERNEL);
+ bat = devm_kzalloc(&pdev->dev, sizeof(struct da9052_battery),
+ GFP_KERNEL);
if (!bat)
return -ENOMEM;
@@ -635,7 +636,6 @@ err:
while (--i >= 0)
da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat);
- kfree(bat);
return ret;
}
static int da9052_bat_remove(struct platform_device *pdev)
@@ -647,7 +647,6 @@ static int da9052_bat_remove(struct platform_device *pdev)
da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat);
power_supply_unregister(&bat->psy);
- kfree(bat);
return 0;
}
diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c
index 704e652072b..85b4e6eca0b 100644
--- a/drivers/power/ds2760_battery.c
+++ b/drivers/power/ds2760_battery.c
@@ -512,7 +512,7 @@ static int ds2760_battery_probe(struct platform_device *pdev)
int retval = 0;
struct ds2760_device_info *di;
- di = kzalloc(sizeof(*di), GFP_KERNEL);
+ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
if (!di) {
retval = -ENOMEM;
goto di_alloc_failed;
@@ -576,7 +576,6 @@ static int ds2760_battery_probe(struct platform_device *pdev)
workqueue_failed:
power_supply_unregister(&di->bat);
batt_failed:
- kfree(di);
di_alloc_failed:
success:
return retval;
@@ -590,7 +589,6 @@ static int ds2760_battery_remove(struct platform_device *pdev)
cancel_delayed_work_sync(&di->set_charged_work);
destroy_workqueue(di->monitor_wqueue);
power_supply_unregister(&di->bat);
- kfree(di);
return 0;
}
diff --git a/drivers/power/ds2780_battery.c b/drivers/power/ds2780_battery.c
index 8b6c4539e7f..9f418fa879e 100644
--- a/drivers/power/ds2780_battery.c
+++ b/drivers/power/ds2780_battery.c
@@ -760,7 +760,7 @@ static int ds2780_battery_probe(struct platform_device *pdev)
int ret = 0;
struct ds2780_device_info *dev_info;
- dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
+ dev_info = devm_kzalloc(&pdev->dev, sizeof(*dev_info), GFP_KERNEL);
if (!dev_info) {
ret = -ENOMEM;
goto fail;
@@ -779,7 +779,7 @@ static int ds2780_battery_probe(struct platform_device *pdev)
ret = power_supply_register(&pdev->dev, &dev_info->bat);
if (ret) {
dev_err(dev_info->dev, "failed to register battery\n");
- goto fail_free_info;
+ goto fail;
}
ret = sysfs_create_group(&dev_info->bat.dev->kobj, &ds2780_attr_group);
@@ -813,8 +813,6 @@ fail_remove_group:
sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group);
fail_unregister:
power_supply_unregister(&dev_info->bat);
-fail_free_info:
- kfree(dev_info);
fail:
return ret;
}
@@ -828,7 +826,6 @@ static int ds2780_battery_remove(struct platform_device *pdev)
power_supply_unregister(&dev_info->bat);
- kfree(dev_info);
return 0;
}
diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c
index 2fa9b6bf1f3..041f9b638d2 100644
--- a/drivers/power/ds2782_battery.c
+++ b/drivers/power/ds2782_battery.c
@@ -7,6 +7,8 @@
*
* DS2786 added by Yulia Vilensky <vilensky@compulab.co.il>
*
+ * UEvent sending added by Evgeny Romanov <romanov@neurosoft.ru>
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
@@ -19,6 +21,7 @@
#include <linux/errno.h>
#include <linux/swab.h>
#include <linux/i2c.h>
+#include <linux/delay.h>
#include <linux/idr.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
@@ -40,6 +43,8 @@
#define DS2786_CURRENT_UNITS 25
+#define DS278x_DELAY 1000
+
struct ds278x_info;
struct ds278x_battery_ops {
@@ -54,8 +59,11 @@ struct ds278x_info {
struct i2c_client *client;
struct power_supply battery;
struct ds278x_battery_ops *ops;
+ struct delayed_work bat_work;
int id;
int rsns;
+ int capacity;
+ int status; /* State Of Charge */
};
static DEFINE_IDR(battery_id);
@@ -184,7 +192,7 @@ static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV)
/*
* Voltage is measured in units of 1.22mV. The voltage is stored as
- * a 10-bit number plus sign, in the upper bits of a 16-bit register
+ * a 12-bit number plus sign, in the upper bits of a 16-bit register
*/
err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw);
if (err)
@@ -220,6 +228,8 @@ static int ds278x_get_status(struct ds278x_info *info, int *status)
if (err)
return err;
+ info->capacity = capacity;
+
if (capacity == 100)
*status = POWER_SUPPLY_STATUS_FULL;
else if (current_uA == 0)
@@ -267,6 +277,27 @@ static int ds278x_battery_get_property(struct power_supply *psy,
return ret;
}
+static void ds278x_bat_update(struct ds278x_info *info)
+{
+ int old_status = info->status;
+ int old_capacity = info->capacity;
+
+ ds278x_get_status(info, &info->status);
+
+ if ((old_status != info->status) || (old_capacity != info->capacity))
+ power_supply_changed(&info->battery);
+}
+
+static void ds278x_bat_work(struct work_struct *work)
+{
+ struct ds278x_info *info;
+
+ info = container_of(work, struct ds278x_info, bat_work.work);
+ ds278x_bat_update(info);
+
+ schedule_delayed_work(&info->bat_work, DS278x_DELAY);
+}
+
static enum power_supply_property ds278x_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CAPACITY,
@@ -295,10 +326,39 @@ static int ds278x_battery_remove(struct i2c_client *client)
idr_remove(&battery_id, info->id);
mutex_unlock(&battery_lock);
+ cancel_delayed_work(&info->bat_work);
+
kfree(info);
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+
+static int ds278x_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ds278x_info *info = i2c_get_clientdata(client);
+
+ cancel_delayed_work(&info->bat_work);
+ return 0;
+}
+
+static int ds278x_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ds278x_info *info = i2c_get_clientdata(client);
+
+ schedule_delayed_work(&info->bat_work, DS278x_DELAY);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ds278x_battery_pm_ops, ds278x_suspend, ds278x_resume);
+#define DS278X_BATTERY_PM_OPS (&ds278x_battery_pm_ops)
+
+#else
+#define DS278X_BATTERY_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
enum ds278x_num_id {
DS2782 = 0,
DS2786,
@@ -335,17 +395,12 @@ static int ds278x_battery_probe(struct i2c_client *client,
}
/* Get an ID for this battery */
- ret = idr_pre_get(&battery_id, GFP_KERNEL);
- if (ret == 0) {
- ret = -ENOMEM;
- goto fail_id;
- }
-
mutex_lock(&battery_lock);
- ret = idr_get_new(&battery_id, client, &num);
+ ret = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL);
mutex_unlock(&battery_lock);
if (ret < 0)
goto fail_id;
+ num = ret;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info) {
@@ -368,10 +423,17 @@ static int ds278x_battery_probe(struct i2c_client *client,
info->ops = &ds278x_ops[id->driver_data];
ds278x_power_supply_init(&info->battery);
+ info->capacity = 100;
+ info->status = POWER_SUPPLY_STATUS_FULL;
+
+ INIT_DELAYED_WORK(&info->bat_work, ds278x_bat_work);
+
ret = power_supply_register(&client->dev, &info->battery);
if (ret) {
dev_err(&client->dev, "failed to register battery\n");
goto fail_register;
+ } else {
+ schedule_delayed_work(&info->bat_work, DS278x_DELAY);
}
return 0;
@@ -398,6 +460,7 @@ MODULE_DEVICE_TABLE(i2c, ds278x_id);
static struct i2c_driver ds278x_battery_driver = {
.driver = {
.name = "ds2782-battery",
+ .pm = DS278X_BATTERY_PM_OPS,
},
.probe = ds278x_battery_probe,
.remove = ds278x_battery_remove,
diff --git a/drivers/power/generic-adc-battery.c b/drivers/power/generic-adc-battery.c
index 32ce17e235c..59a1421f928 100644
--- a/drivers/power/generic-adc-battery.c
+++ b/drivers/power/generic-adc-battery.c
@@ -263,9 +263,6 @@ static int gab_probe(struct platform_device *pdev)
psy->external_power_changed = gab_ext_power_changed;
adc_bat->pdata = pdata;
- /* calculate the total number of channels */
- chan = ARRAY_SIZE(gab_chan_name);
-
/*
* copying the static properties and allocating extra memory for holding
* the extra configurable properties received from platform data.
@@ -287,10 +284,11 @@ static int gab_probe(struct platform_device *pdev)
* based on the channel supported by consumer device.
*/
for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) {
- adc_bat->channel[chan] = iio_channel_get(dev_name(&pdev->dev),
- gab_chan_name[chan]);
+ adc_bat->channel[chan] = iio_channel_get(&pdev->dev,
+ gab_chan_name[chan]);
if (IS_ERR(adc_bat->channel[chan])) {
ret = PTR_ERR(adc_bat->channel[chan]);
+ adc_bat->channel[chan] = NULL;
} else {
/* copying properties for supported channels only */
memcpy(properties + sizeof(*(psy->properties)) * index,
@@ -301,8 +299,10 @@ static int gab_probe(struct platform_device *pdev)
}
/* none of the channels are supported so let's bail out */
- if (index == ARRAY_SIZE(gab_chan_name))
+ if (index == 0) {
+ ret = -ENODEV;
goto second_mem_fail;
+ }
/*
* Total number of properties is equal to static properties
@@ -344,8 +344,10 @@ err_gpio:
gpio_req_fail:
power_supply_unregister(psy);
err_reg_fail:
- for (chan = 0; ARRAY_SIZE(gab_chan_name); chan++)
- iio_channel_release(adc_bat->channel[chan]);
+ for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) {
+ if (adc_bat->channel[chan])
+ iio_channel_release(adc_bat->channel[chan]);
+ }
second_mem_fail:
kfree(psy->properties);
first_mem_fail:
@@ -365,8 +367,10 @@ static int gab_remove(struct platform_device *pdev)
gpio_free(pdata->gpio_charge_finished);
}
- for (chan = 0; ARRAY_SIZE(gab_chan_name); chan++)
- iio_channel_release(adc_bat->channel[chan]);
+ for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) {
+ if (adc_bat->channel[chan])
+ iio_channel_release(adc_bat->channel[chan]);
+ }
kfree(adc_bat->psy.properties);
cancel_delayed_work(&adc_bat->bat_work);
diff --git a/drivers/power/goldfish_battery.c b/drivers/power/goldfish_battery.c
new file mode 100644
index 00000000000..29eba88a296
--- /dev/null
+++ b/drivers/power/goldfish_battery.c
@@ -0,0 +1,236 @@
+/*
+ * Power supply driver for the goldfish emulator
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Copyright (C) 2012 Intel, Inc.
+ * Copyright (C) 2013 Intel, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+struct goldfish_battery_data {
+ void __iomem *reg_base;
+ int irq;
+ spinlock_t lock;
+
+ struct power_supply battery;
+ struct power_supply ac;
+};
+
+#define GOLDFISH_BATTERY_READ(data, addr) \
+ (readl(data->reg_base + addr))
+#define GOLDFISH_BATTERY_WRITE(data, addr, x) \
+ (writel(x, data->reg_base + addr))
+
+/*
+ * Temporary variable used between goldfish_battery_probe() and
+ * goldfish_battery_open().
+ */
+static struct goldfish_battery_data *battery_data;
+
+enum {
+ /* status register */
+ BATTERY_INT_STATUS = 0x00,
+ /* set this to enable IRQ */
+ BATTERY_INT_ENABLE = 0x04,
+
+ BATTERY_AC_ONLINE = 0x08,
+ BATTERY_STATUS = 0x0C,
+ BATTERY_HEALTH = 0x10,
+ BATTERY_PRESENT = 0x14,
+ BATTERY_CAPACITY = 0x18,
+
+ BATTERY_STATUS_CHANGED = 1U << 0,
+ AC_STATUS_CHANGED = 1U << 1,
+ BATTERY_INT_MASK = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED,
+};
+
+
+static int goldfish_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct goldfish_battery_data *data = container_of(psy,
+ struct goldfish_battery_data, ac);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_AC_ONLINE);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int goldfish_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct goldfish_battery_data *data = container_of(psy,
+ struct goldfish_battery_data, battery);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_STATUS);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_HEALTH);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_PRESENT);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CAPACITY);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property goldfish_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static enum power_supply_property goldfish_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static irqreturn_t goldfish_battery_interrupt(int irq, void *dev_id)
+{
+ unsigned long irq_flags;
+ struct goldfish_battery_data *data = dev_id;
+ uint32_t status;
+
+ spin_lock_irqsave(&data->lock, irq_flags);
+
+ /* read status flags, which will clear the interrupt */
+ status = GOLDFISH_BATTERY_READ(data, BATTERY_INT_STATUS);
+ status &= BATTERY_INT_MASK;
+
+ if (status & BATTERY_STATUS_CHANGED)
+ power_supply_changed(&data->battery);
+ if (status & AC_STATUS_CHANGED)
+ power_supply_changed(&data->ac);
+
+ spin_unlock_irqrestore(&data->lock, irq_flags);
+ return status ? IRQ_HANDLED : IRQ_NONE;
+}
+
+
+static int goldfish_battery_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *r;
+ struct goldfish_battery_data *data;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&data->lock);
+
+ data->battery.properties = goldfish_battery_props;
+ data->battery.num_properties = ARRAY_SIZE(goldfish_battery_props);
+ data->battery.get_property = goldfish_battery_get_property;
+ data->battery.name = "battery";
+ data->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+
+ data->ac.properties = goldfish_ac_props;
+ data->ac.num_properties = ARRAY_SIZE(goldfish_ac_props);
+ data->ac.get_property = goldfish_ac_get_property;
+ data->ac.name = "ac";
+ data->ac.type = POWER_SUPPLY_TYPE_MAINS;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r == NULL) {
+ dev_err(&pdev->dev, "platform_get_resource failed\n");
+ return -ENODEV;
+ }
+
+ data->reg_base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+ if (data->reg_base == NULL) {
+ dev_err(&pdev->dev, "unable to remap MMIO\n");
+ return -ENOMEM;
+ }
+
+ data->irq = platform_get_irq(pdev, 0);
+ if (data->irq < 0) {
+ dev_err(&pdev->dev, "platform_get_irq failed\n");
+ return -ENODEV;
+ }
+
+ ret = devm_request_irq(&pdev->dev, data->irq, goldfish_battery_interrupt,
+ IRQF_SHARED, pdev->name, data);
+ if (ret)
+ return ret;
+
+ ret = power_supply_register(&pdev->dev, &data->ac);
+ if (ret)
+ return ret;
+
+ ret = power_supply_register(&pdev->dev, &data->battery);
+ if (ret) {
+ power_supply_unregister(&data->ac);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, data);
+ battery_data = data;
+
+ GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK);
+ return 0;
+}
+
+static int goldfish_battery_remove(struct platform_device *pdev)
+{
+ struct goldfish_battery_data *data = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&data->battery);
+ power_supply_unregister(&data->ac);
+ battery_data = NULL;
+ return 0;
+}
+
+static struct platform_driver goldfish_battery_device = {
+ .probe = goldfish_battery_probe,
+ .remove = goldfish_battery_remove,
+ .driver = {
+ .name = "goldfish-battery"
+ }
+};
+module_platform_driver(goldfish_battery_device);
+
+MODULE_AUTHOR("Mike Lockwood lockwood@android.com");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Battery driver for the Goldfish emulator");
diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c
index e3e40a9f3af..a0024b25219 100644
--- a/drivers/power/gpio-charger.c
+++ b/drivers/power/gpio-charger.c
@@ -28,6 +28,7 @@
struct gpio_charger {
const struct gpio_charger_platform_data *pdata;
unsigned int irq;
+ bool wakeup_enabled;
struct power_supply charger;
};
@@ -86,7 +87,8 @@ static int gpio_charger_probe(struct platform_device *pdev)
return -EINVAL;
}
- gpio_charger = kzalloc(sizeof(*gpio_charger), GFP_KERNEL);
+ gpio_charger = devm_kzalloc(&pdev->dev, sizeof(*gpio_charger),
+ GFP_KERNEL);
if (!gpio_charger) {
dev_err(&pdev->dev, "Failed to alloc driver structure\n");
return -ENOMEM;
@@ -135,12 +137,13 @@ static int gpio_charger_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, gpio_charger);
+ device_init_wakeup(&pdev->dev, 1);
+
return 0;
err_gpio_free:
gpio_free(pdata->gpio);
err_free:
- kfree(gpio_charger);
return ret;
}
@@ -155,25 +158,36 @@ static int gpio_charger_remove(struct platform_device *pdev)
gpio_free(gpio_charger->pdata->gpio);
- platform_set_drvdata(pdev, NULL);
- kfree(gpio_charger);
-
return 0;
}
#ifdef CONFIG_PM_SLEEP
+static int gpio_charger_suspend(struct device *dev)
+{
+ struct gpio_charger *gpio_charger = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ gpio_charger->wakeup_enabled =
+ enable_irq_wake(gpio_charger->irq);
+
+ return 0;
+}
+
static int gpio_charger_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct gpio_charger *gpio_charger = platform_get_drvdata(pdev);
+ if (gpio_charger->wakeup_enabled)
+ disable_irq_wake(gpio_charger->irq);
power_supply_changed(&gpio_charger->charger);
return 0;
}
#endif
-static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, NULL, gpio_charger_resume);
+static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops,
+ gpio_charger_suspend, gpio_charger_resume);
static struct platform_driver gpio_charger_driver = {
.probe = gpio_charger_probe,
diff --git a/drivers/power/intel_mid_battery.c b/drivers/power/intel_mid_battery.c
index 18d136b443e..4520811168a 100644
--- a/drivers/power/intel_mid_battery.c
+++ b/drivers/power/intel_mid_battery.c
@@ -756,7 +756,7 @@ static int platform_pmic_battery_probe(struct platform_device *pdev)
static int platform_pmic_battery_remove(struct platform_device *pdev)
{
- struct pmic_power_module_info *pbi = dev_get_drvdata(&pdev->dev);
+ struct pmic_power_module_info *pbi = platform_get_drvdata(pdev);
free_irq(pbi->irq, pbi);
cancel_delayed_work_sync(&pbi->monitor_battery);
diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c
index 176ad59d99f..0b4cf9d6329 100644
--- a/drivers/power/isp1704_charger.c
+++ b/drivers/power/isp1704_charger.c
@@ -2,6 +2,7 @@
* ISP1704 USB Charger Detection driver
*
* Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2012 - 2013 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
@@ -28,6 +29,8 @@
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
#include <linux/usb/otg.h>
#include <linux/usb/ulpi.h>
@@ -65,10 +68,6 @@ struct isp1704_charger {
unsigned present:1;
unsigned online:1;
unsigned current_max;
-
- /* temp storage variables */
- unsigned long event;
- unsigned max_power;
};
static inline int isp1704_read(struct isp1704_charger *isp, u32 reg)
@@ -91,6 +90,8 @@ static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on)
if (board && board->set_power)
board->set_power(on);
+ else if (board)
+ gpio_set_value(board->enable_gpio, on);
}
/*
@@ -231,56 +232,59 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp)
return ret;
}
+static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp)
+{
+ if (isp1704_charger_detect(isp) &&
+ isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP)
+ return true;
+ else
+ return false;
+}
+
static void isp1704_charger_work(struct work_struct *data)
{
- int detect;
- unsigned long event;
- unsigned power;
struct isp1704_charger *isp =
container_of(data, struct isp1704_charger, work);
static DEFINE_MUTEX(lock);
- event = isp->event;
- power = isp->max_power;
-
mutex_lock(&lock);
- if (event != USB_EVENT_NONE)
- isp1704_charger_set_power(isp, 1);
-
- switch (event) {
+ switch (isp->phy->last_event) {
case USB_EVENT_VBUS:
- isp->online = true;
-
- /* detect charger */
- detect = isp1704_charger_detect(isp);
+ /* do not call wall charger detection more times */
+ if (!isp->present) {
+ isp->online = true;
+ isp->present = 1;
+ isp1704_charger_set_power(isp, 1);
+
+ /* detect wall charger */
+ if (isp1704_charger_detect_dcp(isp)) {
+ isp->psy.type = POWER_SUPPLY_TYPE_USB_DCP;
+ isp->current_max = 1800;
+ } else {
+ isp->psy.type = POWER_SUPPLY_TYPE_USB;
+ isp->current_max = 500;
+ }
- if (detect) {
- isp->present = detect;
- isp->psy.type = isp1704_charger_type(isp);
+ /* enable data pullups */
+ if (isp->phy->otg->gadget)
+ usb_gadget_connect(isp->phy->otg->gadget);
}
- switch (isp->psy.type) {
- case POWER_SUPPLY_TYPE_USB_DCP:
- isp->current_max = 1800;
- break;
- case POWER_SUPPLY_TYPE_USB_CDP:
+ if (isp->psy.type != POWER_SUPPLY_TYPE_USB_DCP) {
/*
* Only 500mA here or high speed chirp
* handshaking may break
*/
- isp->current_max = 500;
- /* FALLTHROUGH */
- case POWER_SUPPLY_TYPE_USB:
- default:
- /* enable data pullups */
- if (isp->phy->otg->gadget)
- usb_gadget_connect(isp->phy->otg->gadget);
+ if (isp->current_max > 500)
+ isp->current_max = 500;
+
+ if (isp->current_max > 100)
+ isp->psy.type = POWER_SUPPLY_TYPE_USB_CDP;
}
break;
case USB_EVENT_NONE:
isp->online = false;
- isp->current_max = 0;
isp->present = 0;
isp->current_max = 0;
isp->psy.type = POWER_SUPPLY_TYPE_USB;
@@ -298,12 +302,6 @@ static void isp1704_charger_work(struct work_struct *data)
isp1704_charger_set_power(isp, 0);
break;
- case USB_EVENT_ENUMERATED:
- if (isp->present)
- isp->current_max = 1800;
- else
- isp->current_max = power;
- break;
default:
goto out;
}
@@ -314,16 +312,11 @@ out:
}
static int isp1704_notifier_call(struct notifier_block *nb,
- unsigned long event, void *power)
+ unsigned long val, void *v)
{
struct isp1704_charger *isp =
container_of(nb, struct isp1704_charger, nb);
- isp->event = event;
-
- if (power)
- isp->max_power = *((unsigned *)power);
-
schedule_work(&isp->work);
return NOTIFY_OK;
@@ -411,13 +404,46 @@ static int isp1704_charger_probe(struct platform_device *pdev)
struct isp1704_charger *isp;
int ret = -ENODEV;
- isp = kzalloc(sizeof *isp, GFP_KERNEL);
+ struct isp1704_charger_data *pdata = dev_get_platdata(&pdev->dev);
+ struct device_node *np = pdev->dev.of_node;
+
+ if (np) {
+ int gpio = of_get_named_gpio(np, "nxp,enable-gpio", 0);
+
+ if (gpio < 0)
+ return gpio;
+
+ pdata = devm_kzalloc(&pdev->dev,
+ sizeof(struct isp1704_charger_data), GFP_KERNEL);
+ pdata->enable_gpio = gpio;
+
+ dev_info(&pdev->dev, "init gpio %d\n", pdata->enable_gpio);
+
+ ret = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio,
+ GPIOF_OUT_INIT_HIGH, "isp1704_reset");
+ if (ret)
+ goto fail0;
+ }
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "missing platform data!\n");
+ return -ENODEV;
+ }
+
+
+ isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL);
if (!isp)
return -ENOMEM;
- isp->phy = usb_get_phy(USB_PHY_TYPE_USB2);
- if (IS_ERR_OR_NULL(isp->phy))
+ if (np)
+ isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);
+ else
+ isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
+
+ if (IS_ERR(isp->phy)) {
+ ret = PTR_ERR(isp->phy);
goto fail0;
+ }
isp->dev = &pdev->dev;
platform_set_drvdata(pdev, isp);
@@ -462,23 +488,20 @@ static int isp1704_charger_probe(struct platform_device *pdev)
if (isp->phy->otg->gadget)
usb_gadget_disconnect(isp->phy->otg->gadget);
+ if (isp->phy->last_event == USB_EVENT_NONE)
+ isp1704_charger_set_power(isp, 0);
+
/* Detect charger if VBUS is valid (the cable was already plugged). */
- ret = isp1704_read(isp, ULPI_USB_INT_STS);
- isp1704_charger_set_power(isp, 0);
- if ((ret & ULPI_INT_VBUS_VALID) && !isp->phy->otg->default_a) {
- isp->event = USB_EVENT_VBUS;
+ if (isp->phy->last_event == USB_EVENT_VBUS &&
+ !isp->phy->otg->default_a)
schedule_work(&isp->work);
- }
return 0;
fail2:
power_supply_unregister(&isp->psy);
fail1:
isp1704_charger_set_power(isp, 0);
- usb_put_phy(isp->phy);
fail0:
- kfree(isp);
-
dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
return ret;
@@ -490,16 +513,23 @@ static int isp1704_charger_remove(struct platform_device *pdev)
usb_unregister_notifier(isp->phy, &isp->nb);
power_supply_unregister(&isp->psy);
- usb_put_phy(isp->phy);
isp1704_charger_set_power(isp, 0);
- kfree(isp);
return 0;
}
+#ifdef CONFIG_OF
+static const struct of_device_id omap_isp1704_of_match[] = {
+ { .compatible = "nxp,isp1704", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, omap_isp1704_of_match);
+#endif
+
static struct platform_driver isp1704_charger_driver = {
.driver = {
.name = "isp1704_charger",
+ .of_match_table = of_match_ptr(omap_isp1704_of_match),
},
.probe = isp1704_charger_probe,
.remove = isp1704_charger_remove,
diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c
index bf914893c6f..6c8931d4ad6 100644
--- a/drivers/power/jz4740-battery.c
+++ b/drivers/power/jz4740-battery.c
@@ -22,6 +22,7 @@
#include <linux/io.h>
#include <linux/delay.h>
+#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/mfd/core.h>
#include <linux/power_supply.h>
@@ -72,7 +73,7 @@ static long jz_battery_read_voltage(struct jz_battery *battery)
mutex_lock(&battery->lock);
- INIT_COMPLETION(battery->read_completion);
+ reinit_completion(&battery->read_completion);
enable_irq(battery->irq);
battery->cell->enable(battery->pdev);
@@ -266,9 +267,9 @@ static int jz_battery_probe(struct platform_device *pdev)
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- jz_battery->base = devm_request_and_ioremap(&pdev->dev, mem);
- if (!jz_battery->base)
- return -EBUSY;
+ jz_battery->base = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(jz_battery->base))
+ return PTR_ERR(jz_battery->base);
battery = &jz_battery->battery;
battery->name = pdata->info.name;
@@ -291,7 +292,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;
+ return ret;
}
disable_irq(jz_battery->irq);
@@ -348,8 +349,6 @@ err_free_gpio:
gpio_free(jz_battery->pdata->gpio_charge);
err_free_irq:
free_irq(jz_battery->irq, jz_battery);
-err:
- platform_set_drvdata(pdev, NULL);
return ret;
}
diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c
index 4ee71a90e24..32de636dcd7 100644
--- a/drivers/power/lp8727_charger.c
+++ b/drivers/power/lp8727_charger.c
@@ -16,6 +16,7 @@
#include <linux/i2c.h>
#include <linux/power_supply.h>
#include <linux/platform_data/lp8727.h>
+#include <linux/of.h>
#define LP8788_NUM_INTREGS 2
#define DEFAULT_DEBOUNCE_MSEC 270
@@ -367,28 +368,28 @@ static int lp8727_battery_get_property(struct power_supply *psy,
return -EINVAL;
if (pdata->get_batt_present)
- val->intval = pchg->pdata->get_batt_present();
+ val->intval = pdata->get_batt_present();
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
if (!pdata)
return -EINVAL;
if (pdata->get_batt_level)
- val->intval = pchg->pdata->get_batt_level();
+ val->intval = pdata->get_batt_level();
break;
case POWER_SUPPLY_PROP_CAPACITY:
if (!pdata)
return -EINVAL;
if (pdata->get_batt_capacity)
- val->intval = pchg->pdata->get_batt_capacity();
+ val->intval = pdata->get_batt_capacity();
break;
case POWER_SUPPLY_PROP_TEMP:
if (!pdata)
return -EINVAL;
if (pdata->get_batt_temp)
- val->intval = pchg->pdata->get_batt_temp();
+ val->intval = pdata->get_batt_temp();
break;
default:
break;
@@ -481,6 +482,60 @@ static void lp8727_unregister_psy(struct lp8727_chg *pchg)
power_supply_unregister(&psy->batt);
}
+#ifdef CONFIG_OF
+static struct lp8727_chg_param
+*lp8727_parse_charge_pdata(struct device *dev, struct device_node *np)
+{
+ struct lp8727_chg_param *param;
+
+ param = devm_kzalloc(dev, sizeof(*param), GFP_KERNEL);
+ if (!param)
+ goto out;
+
+ of_property_read_u8(np, "eoc-level", (u8 *)&param->eoc_level);
+ of_property_read_u8(np, "charging-current", (u8 *)&param->ichg);
+out:
+ return param;
+}
+
+static int lp8727_parse_dt(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ struct lp8727_platform_data *pdata;
+ const char *type;
+
+ /* If charging parameter is not defined, just skip parsing the dt */
+ if (of_get_child_count(np) == 0)
+ goto out;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ of_property_read_u32(np, "debounce-ms", &pdata->debounce_msec);
+
+ for_each_child_of_node(np, child) {
+ of_property_read_string(child, "charger-type", &type);
+
+ if (!strcmp(type, "ac"))
+ pdata->ac = lp8727_parse_charge_pdata(dev, child);
+
+ if (!strcmp(type, "usb"))
+ pdata->usb = lp8727_parse_charge_pdata(dev, child);
+ }
+
+ dev->platform_data = pdata;
+out:
+ return 0;
+}
+#else
+static int lp8727_parse_dt(struct device *dev)
+{
+ return 0;
+}
+#endif
+
static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id)
{
struct lp8727_chg *pchg;
@@ -489,6 +544,12 @@ static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id)
if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
return -EIO;
+ if (cl->dev.of_node) {
+ ret = lp8727_parse_dt(&cl->dev);
+ if (ret)
+ return ret;
+ }
+
pchg = devm_kzalloc(&cl->dev, sizeof(*pchg), GFP_KERNEL);
if (!pchg)
return -ENOMEM;
@@ -531,6 +592,12 @@ static int lp8727_remove(struct i2c_client *cl)
return 0;
}
+static const struct of_device_id lp8727_dt_ids[] = {
+ { .compatible = "ti,lp8727", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lp8727_dt_ids);
+
static const struct i2c_device_id lp8727_ids[] = {
{"lp8727", 0},
{ }
@@ -540,6 +607,7 @@ MODULE_DEVICE_TABLE(i2c, lp8727_ids);
static struct i2c_driver lp8727_driver = {
.driver = {
.name = "lp8727",
+ .of_match_table = of_match_ptr(lp8727_dt_ids),
},
.probe = lp8727_probe,
.remove = lp8727_remove,
diff --git a/drivers/power/lp8788-charger.c b/drivers/power/lp8788-charger.c
index 22b6407c9ca..ed49b50b220 100644
--- a/drivers/power/lp8788-charger.c
+++ b/drivers/power/lp8788-charger.c
@@ -49,7 +49,6 @@
#define LP8788_CHG_START 0x11
#define LP8788_CHG_END 0x1C
-#define LP8788_BUF_SIZE 40
#define LP8788_ISEL_MAX 23
#define LP8788_ISEL_STEP 50
#define LP8788_VTERM_MIN 4100
@@ -367,7 +366,8 @@ static inline bool lp8788_is_valid_charger_register(u8 addr)
return addr >= LP8788_CHG_START && addr <= LP8788_CHG_END;
}
-static int lp8788_update_charger_params(struct lp8788_charger *pchg)
+static int lp8788_update_charger_params(struct platform_device *pdev,
+ struct lp8788_charger *pchg)
{
struct lp8788 *lp = pchg->lp;
struct lp8788_charger_platform_data *pdata = pchg->pdata;
@@ -376,7 +376,7 @@ static int lp8788_update_charger_params(struct lp8788_charger *pchg)
int ret;
if (!pdata || !pdata->chg_params) {
- dev_info(lp->dev, "skip updating charger parameters\n");
+ dev_info(&pdev->dev, "skip updating charger parameters\n");
return 0;
}
@@ -537,7 +537,6 @@ err_free_irq:
static int lp8788_irq_register(struct platform_device *pdev,
struct lp8788_charger *pchg)
{
- struct lp8788 *lp = pchg->lp;
const char *name[] = {
LP8788_CHG_IRQ, LP8788_PRSW_IRQ, LP8788_BATT_IRQ
};
@@ -550,13 +549,13 @@ static int lp8788_irq_register(struct platform_device *pdev,
for (i = 0; i < ARRAY_SIZE(name); i++) {
ret = lp8788_set_irqs(pdev, pchg, name[i]);
if (ret) {
- dev_warn(lp->dev, "irq setup failed: %s\n", name[i]);
+ dev_warn(&pdev->dev, "irq setup failed: %s\n", name[i]);
return ret;
}
}
if (pchg->num_irqs > LP8788_MAX_CHG_IRQS) {
- dev_err(lp->dev, "invalid total number of irqs: %d\n",
+ dev_err(&pdev->dev, "invalid total number of irqs: %d\n",
pchg->num_irqs);
return -EINVAL;
}
@@ -580,7 +579,7 @@ static void lp8788_irq_unregister(struct platform_device *pdev,
}
}
-static void lp8788_setup_adc_channel(const char *consumer_name,
+static void lp8788_setup_adc_channel(struct device *dev,
struct lp8788_charger *pchg)
{
struct lp8788_charger_platform_data *pdata = pchg->pdata;
@@ -590,11 +589,11 @@ static void lp8788_setup_adc_channel(const char *consumer_name,
return;
/* ADC channel for battery voltage */
- chan = iio_channel_get(consumer_name, pdata->adc_vbatt);
+ chan = iio_channel_get(dev, pdata->adc_vbatt);
pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan;
/* ADC channel for battery temperature */
- chan = iio_channel_get(consumer_name, pdata->adc_batt_temp);
+ chan = iio_channel_get(dev, pdata->adc_batt_temp);
pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan;
}
@@ -633,7 +632,7 @@ static ssize_t lp8788_show_charger_status(struct device *dev,
lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data);
state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S;
- return scnprintf(buf, LP8788_BUF_SIZE, "%s\n", desc[state]);
+ return scnprintf(buf, PAGE_SIZE, "%s\n", desc[state]);
}
static ssize_t lp8788_show_eoc_time(struct device *dev,
@@ -647,7 +646,7 @@ static ssize_t lp8788_show_eoc_time(struct device *dev,
lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val);
val = (val & LP8788_CHG_EOC_TIME_M) >> LP8788_CHG_EOC_TIME_S;
- return scnprintf(buf, LP8788_BUF_SIZE, "End Of Charge Time: %s\n",
+ return scnprintf(buf, PAGE_SIZE, "End Of Charge Time: %s\n",
stime[val]);
}
@@ -667,8 +666,7 @@ static ssize_t lp8788_show_eoc_level(struct device *dev,
val = (val & LP8788_CHG_EOC_LEVEL_M) >> LP8788_CHG_EOC_LEVEL_S;
level = mode ? abs_level[val] : relative_level[val];
- return scnprintf(buf, LP8788_BUF_SIZE, "End Of Charge Level: %s\n",
- level);
+ return scnprintf(buf, PAGE_SIZE, "End Of Charge Level: %s\n", level);
}
static DEVICE_ATTR(charger_status, S_IRUSR, lp8788_show_charger_status, NULL);
@@ -690,9 +688,10 @@ static int lp8788_charger_probe(struct platform_device *pdev)
{
struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent);
struct lp8788_charger *pchg;
+ struct device *dev = &pdev->dev;
int ret;
- pchg = devm_kzalloc(lp->dev, sizeof(struct lp8788_charger), GFP_KERNEL);
+ pchg = devm_kzalloc(dev, sizeof(struct lp8788_charger), GFP_KERNEL);
if (!pchg)
return -ENOMEM;
@@ -700,11 +699,11 @@ static int lp8788_charger_probe(struct platform_device *pdev)
pchg->pdata = lp->pdata ? lp->pdata->chg_pdata : NULL;
platform_set_drvdata(pdev, pchg);
- ret = lp8788_update_charger_params(pchg);
+ ret = lp8788_update_charger_params(pdev, pchg);
if (ret)
return ret;
- lp8788_setup_adc_channel(pdev->name, pchg);
+ lp8788_setup_adc_channel(&pdev->dev, pchg);
ret = lp8788_psy_register(pdev, pchg);
if (ret)
@@ -718,7 +717,7 @@ static int lp8788_charger_probe(struct platform_device *pdev)
ret = lp8788_irq_register(pdev, pchg);
if (ret)
- dev_warn(lp->dev, "failed to register charger irq: %d\n", ret);
+ dev_warn(dev, "failed to register charger irq: %d\n", ret);
return 0;
}
diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c
new file mode 100644
index 00000000000..fad2a75b360
--- /dev/null
+++ b/drivers/power/max14577_charger.c
@@ -0,0 +1,311 @@
+/*
+ * Battery charger driver for the Maxim 14577
+ *
+ * Copyright (C) 2013 Samsung Electronics
+ * Krzysztof Kozlowski <k.kozlowski@samsung.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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max14577-private.h>
+
+struct max14577_charger {
+ struct device *dev;
+ struct max14577 *max14577;
+ struct power_supply charger;
+
+ unsigned int charging_state;
+ unsigned int battery_state;
+};
+
+static int max14577_get_charger_state(struct max14577_charger *chg)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ int state = POWER_SUPPLY_STATUS_DISCHARGING;
+ u8 reg_data;
+
+ /*
+ * Charging occurs only if:
+ * - CHGCTRL2/MBCHOSTEN == 1
+ * - STATUS2/CGMBC == 1
+ *
+ * TODO:
+ * - handle FULL after Top-off timer (EOC register may be off
+ * and the charger won't be charging although MBCHOSTEN is on)
+ * - handle properly dead-battery charging (respect timer)
+ * - handle timers (fast-charge and prequal) /MBCCHGERR/
+ */
+ max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, &reg_data);
+ if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0)
+ goto state_set;
+
+ max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, &reg_data);
+ if (reg_data & STATUS3_CGMBC_MASK) {
+ /* Charger or USB-cable is connected */
+ if (reg_data & STATUS3_EOC_MASK)
+ state = POWER_SUPPLY_STATUS_FULL;
+ else
+ state = POWER_SUPPLY_STATUS_CHARGING;
+ goto state_set;
+ }
+
+state_set:
+ chg->charging_state = state;
+ return state;
+}
+
+/*
+ * Supported charge types:
+ * - POWER_SUPPLY_CHARGE_TYPE_NONE
+ * - POWER_SUPPLY_CHARGE_TYPE_FAST
+ */
+static int max14577_get_charge_type(struct max14577_charger *chg)
+{
+ /*
+ * TODO: CHARGE_TYPE_TRICKLE (VCHGR_RC or EOC)?
+ * As spec says:
+ * [after reaching EOC interrupt]
+ * "When the battery is fully charged, the 30-minute (typ)
+ * top-off timer starts. The device continues to trickle
+ * charge the battery until the top-off timer runs out."
+ */
+ if (max14577_get_charger_state(chg) == POWER_SUPPLY_STATUS_CHARGING)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+static int max14577_get_online(struct max14577_charger *chg)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ u8 reg_data;
+
+ max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, &reg_data);
+ reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT);
+ switch (reg_data) {
+ case MAX14577_CHARGER_TYPE_USB:
+ case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
+ case MAX14577_CHARGER_TYPE_SPECIAL_500MA:
+ case MAX14577_CHARGER_TYPE_SPECIAL_1A:
+ case MAX14577_CHARGER_TYPE_DEAD_BATTERY:
+ return 1;
+ case MAX14577_CHARGER_TYPE_NONE:
+ case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT:
+ case MAX14577_CHARGER_TYPE_RESERVED:
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Supported health statuses:
+ * - POWER_SUPPLY_HEALTH_DEAD
+ * - POWER_SUPPLY_HEALTH_OVERVOLTAGE
+ * - POWER_SUPPLY_HEALTH_GOOD
+ */
+static int max14577_get_battery_health(struct max14577_charger *chg)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ int state = POWER_SUPPLY_HEALTH_GOOD;
+ u8 reg_data;
+
+ max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, &reg_data);
+ reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT);
+ if (reg_data == MAX14577_CHARGER_TYPE_DEAD_BATTERY) {
+ state = POWER_SUPPLY_HEALTH_DEAD;
+ goto state_set;
+ }
+
+ max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, &reg_data);
+ if (reg_data & STATUS3_OVP_MASK) {
+ state = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ goto state_set;
+ }
+
+state_set:
+ chg->battery_state = state;
+ return state;
+}
+
+/*
+ * Always returns 1.
+ * The max14577 chip doesn't report any status of battery presence.
+ * Lets assume that it will always be used with some battery.
+ */
+static int max14577_get_present(struct max14577_charger *chg)
+{
+ return 1;
+}
+
+/*
+ * Sets charger registers to proper and safe default values.
+ * Some of these values are equal to defaults in MAX14577E
+ * data sheet but there are minor differences.
+ */
+static void max14577_charger_reg_init(struct max14577_charger *chg)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ u8 reg_data;
+
+ /*
+ * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0)
+ * Charger-Detection Enable, default on (set CHGDETEN to 1)
+ * Combined mask of CHGDETEN and CHGTYPMAN will zero the CHGTYPMAN bit
+ */
+ reg_data = 0x1 << CDETCTRL1_CHGDETEN_SHIFT;
+ max14577_update_reg(rmap, MAX14577_REG_CDETCTRL1,
+ CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK,
+ reg_data);
+
+ /* Battery Fast-Charge Timer, from SM-V700: 6hrs */
+ reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, reg_data);
+
+ /*
+ * Wall-Adapter Rapid Charge, default on
+ * Battery-Charger, default on
+ */
+ reg_data = 0x1 << CHGCTRL2_VCHGR_RC_SHIFT;
+ reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data);
+
+ /* Battery-Charger Constant Voltage (CV) Mode, from SM-V700: 4.35V */
+ reg_data = 0xf << CHGCTRL3_MBCCVWRC_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL3, reg_data);
+
+ /*
+ * Fast Battery-Charge Current Low, default 200-950mA
+ * Fast Battery-Charge Current High, from SM-V700: 450mA
+ */
+ reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT;
+ reg_data |= 0x5 << CHGCTRL4_MBCICHWRCH_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL4, reg_data);
+
+ /* End-of-Charge Current, from SM-V700: 50mA */
+ reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data);
+
+ /* Auto Charging Stop, default off */
+ reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data);
+
+ /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */
+ reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data);
+}
+
+/* Support property from charger */
+static enum power_supply_property max14577_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static const char *model_name = "MAX14577";
+static const char *manufacturer = "Maxim Integrated";
+
+static int max14577_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max14577_charger *chg = container_of(psy,
+ struct max14577_charger,
+ charger);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = max14577_get_charger_state(chg);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = max14577_get_charge_type(chg);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = max14577_get_battery_health(chg);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = max14577_get_present(chg);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = max14577_get_online(chg);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = model_name;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = manufacturer;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int max14577_charger_probe(struct platform_device *pdev)
+{
+ struct max14577_charger *chg;
+ struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
+ int ret;
+
+ chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, chg);
+ chg->dev = &pdev->dev;
+ chg->max14577 = max14577;
+
+ max14577_charger_reg_init(chg);
+
+ chg->charger.name = "max14577-charger",
+ chg->charger.type = POWER_SUPPLY_TYPE_BATTERY,
+ chg->charger.properties = max14577_charger_props,
+ chg->charger.num_properties = ARRAY_SIZE(max14577_charger_props),
+ chg->charger.get_property = max14577_charger_get_property,
+
+ ret = power_supply_register(&pdev->dev, &chg->charger);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max14577_charger_remove(struct platform_device *pdev)
+{
+ struct max14577_charger *chg = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&chg->charger);
+
+ return 0;
+}
+
+static struct platform_driver max14577_charger_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "max14577-charger",
+ },
+ .probe = max14577_charger_probe,
+ .remove = max14577_charger_remove,
+};
+module_platform_driver(max14577_charger_driver);
+
+MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>");
+MODULE_DESCRIPTION("MAXIM 14577 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c
index 22cfe9cc472..0fbac861080 100644
--- a/drivers/power/max17040_battery.c
+++ b/drivers/power/max17040_battery.c
@@ -148,7 +148,7 @@ static void max17040_get_online(struct i2c_client *client)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
- if (chip->pdata->battery_online)
+ if (chip->pdata && chip->pdata->battery_online)
chip->online = chip->pdata->battery_online();
else
chip->online = 1;
@@ -158,7 +158,8 @@ static void max17040_get_status(struct i2c_client *client)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
- if (!chip->pdata->charger_online || !chip->pdata->charger_enable) {
+ if (!chip->pdata || !chip->pdata->charger_online
+ || !chip->pdata->charger_enable) {
chip->status = POWER_SUPPLY_STATUS_UNKNOWN;
return;
}
@@ -207,7 +208,7 @@ static int max17040_probe(struct i2c_client *client,
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
return -EIO;
- chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
@@ -225,7 +226,6 @@ static int max17040_probe(struct i2c_client *client,
ret = power_supply_register(&client->dev, &chip->battery);
if (ret) {
dev_err(&client->dev, "failed: power supply register\n");
- kfree(chip);
return ret;
}
@@ -244,35 +244,37 @@ static int max17040_remove(struct i2c_client *client)
power_supply_unregister(&chip->battery);
cancel_delayed_work(&chip->work);
- kfree(chip);
return 0;
}
-#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
-static int max17040_suspend(struct i2c_client *client,
- pm_message_t state)
+static int max17040_suspend(struct device *dev)
{
+ struct i2c_client *client = to_i2c_client(dev);
struct max17040_chip *chip = i2c_get_clientdata(client);
cancel_delayed_work(&chip->work);
return 0;
}
-static int max17040_resume(struct i2c_client *client)
+static int max17040_resume(struct device *dev)
{
+ struct i2c_client *client = to_i2c_client(dev);
struct max17040_chip *chip = i2c_get_clientdata(client);
schedule_delayed_work(&chip->work, MAX17040_DELAY);
return 0;
}
+static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume);
+#define MAX17040_PM_OPS (&max17040_pm_ops)
+
#else
-#define max17040_suspend NULL
-#define max17040_resume NULL
+#define MAX17040_PM_OPS NULL
-#endif /* CONFIG_PM */
+#endif /* CONFIG_PM_SLEEP */
static const struct i2c_device_id max17040_id[] = {
{ "max17040", 0 },
@@ -283,11 +285,10 @@ MODULE_DEVICE_TABLE(i2c, max17040_id);
static struct i2c_driver max17040_i2c_driver = {
.driver = {
.name = "max17040",
+ .pm = MAX17040_PM_OPS,
},
.probe = max17040_probe,
.remove = max17040_remove,
- .suspend = max17040_suspend,
- .resume = max17040_resume,
.id_table = max17040_id,
};
module_i2c_driver(max17040_i2c_driver);
diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c
index d664ef58afa..66da691c41c 100644
--- a/drivers/power/max17042_battery.c
+++ b/drivers/power/max17042_battery.c
@@ -33,6 +33,7 @@
#include <linux/power_supply.h>
#include <linux/power/max17042_battery.h>
#include <linux/of.h>
+#include <linux/regmap.h>
/* Status register bits */
#define STATUS_POR_BIT (1 << 1)
@@ -67,6 +68,7 @@
struct max17042_chip {
struct i2c_client *client;
+ struct regmap *regmap;
struct power_supply battery;
enum max170xx_chip_type chip_type;
struct max17042_platform_data *pdata;
@@ -74,35 +76,6 @@ struct max17042_chip {
int init_complete;
};
-static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value)
-{
- int ret = i2c_smbus_write_word_data(client, reg, value);
-
- if (ret < 0)
- dev_err(&client->dev, "%s: err %d\n", __func__, ret);
-
- return ret;
-}
-
-static int max17042_read_reg(struct i2c_client *client, u8 reg)
-{
- int ret = i2c_smbus_read_word_data(client, reg);
-
- if (ret < 0)
- dev_err(&client->dev, "%s: err %d\n", __func__, ret);
-
- return ret;
-}
-
-static void max17042_set_reg(struct i2c_client *client,
- struct max17042_reg_data *data, int size)
-{
- int i;
-
- for (i = 0; i < size; i++)
- max17042_write_reg(client, data[i].addr, data[i].data);
-}
-
static enum power_supply_property max17042_battery_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CYCLE_COUNT,
@@ -125,96 +98,98 @@ static int max17042_get_property(struct power_supply *psy,
{
struct max17042_chip *chip = container_of(psy,
struct max17042_chip, battery);
+ struct regmap *map = chip->regmap;
int ret;
+ u32 data;
if (!chip->init_complete)
return -EAGAIN;
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
- ret = max17042_read_reg(chip->client, MAX17042_STATUS);
+ ret = regmap_read(map, MAX17042_STATUS, &data);
if (ret < 0)
return ret;
- if (ret & MAX17042_STATUS_BattAbsent)
+ if (data & MAX17042_STATUS_BattAbsent)
val->intval = 0;
else
val->intval = 1;
break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
- ret = max17042_read_reg(chip->client, MAX17042_Cycles);
+ ret = regmap_read(map, MAX17042_Cycles, &data);
if (ret < 0)
return ret;
- val->intval = ret;
+ val->intval = data;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
- ret = max17042_read_reg(chip->client, MAX17042_MinMaxVolt);
+ ret = regmap_read(map, MAX17042_MinMaxVolt, &data);
if (ret < 0)
return ret;
- val->intval = ret >> 8;
+ val->intval = data >> 8;
val->intval *= 20000; /* Units of LSB = 20mV */
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
if (chip->chip_type == MAX17042)
- ret = max17042_read_reg(chip->client, MAX17042_V_empty);
+ ret = regmap_read(map, MAX17042_V_empty, &data);
else
- ret = max17042_read_reg(chip->client, MAX17047_V_empty);
+ ret = regmap_read(map, MAX17047_V_empty, &data);
if (ret < 0)
return ret;
- val->intval = ret >> 7;
+ val->intval = data >> 7;
val->intval *= 10000; /* Units of LSB = 10mV */
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- ret = max17042_read_reg(chip->client, MAX17042_VCELL);
+ ret = regmap_read(map, MAX17042_VCELL, &data);
if (ret < 0)
return ret;
- val->intval = ret * 625 / 8;
+ val->intval = data * 625 / 8;
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
- ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL);
+ ret = regmap_read(map, MAX17042_AvgVCELL, &data);
if (ret < 0)
return ret;
- val->intval = ret * 625 / 8;
+ val->intval = data * 625 / 8;
break;
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
- ret = max17042_read_reg(chip->client, MAX17042_OCVInternal);
+ ret = regmap_read(map, MAX17042_OCVInternal, &data);
if (ret < 0)
return ret;
- val->intval = ret * 625 / 8;
+ val->intval = data * 625 / 8;
break;
case POWER_SUPPLY_PROP_CAPACITY:
- ret = max17042_read_reg(chip->client, MAX17042_RepSOC);
+ ret = regmap_read(map, MAX17042_RepSOC, &data);
if (ret < 0)
return ret;
- val->intval = ret >> 8;
+ val->intval = data >> 8;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
- ret = max17042_read_reg(chip->client, MAX17042_FullCAP);
+ ret = regmap_read(map, MAX17042_FullCAP, &data);
if (ret < 0)
return ret;
- val->intval = ret * 1000 / 2;
+ val->intval = data * 1000 / 2;
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
- ret = max17042_read_reg(chip->client, MAX17042_QH);
+ ret = regmap_read(map, MAX17042_QH, &data);
if (ret < 0)
return ret;
- val->intval = ret * 1000 / 2;
+ val->intval = data * 1000 / 2;
break;
case POWER_SUPPLY_PROP_TEMP:
- ret = max17042_read_reg(chip->client, MAX17042_TEMP);
+ ret = regmap_read(map, MAX17042_TEMP, &data);
if (ret < 0)
return ret;
- val->intval = ret;
+ val->intval = data;
/* The value is signed. */
if (val->intval & 0x8000) {
val->intval = (0x7fff & ~val->intval) + 1;
@@ -226,11 +201,11 @@ static int max17042_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (chip->pdata->enable_current_sense) {
- ret = max17042_read_reg(chip->client, MAX17042_Current);
+ ret = regmap_read(map, MAX17042_Current, &data);
if (ret < 0)
return ret;
- val->intval = ret;
+ val->intval = data;
if (val->intval & 0x8000) {
/* Negative */
val->intval = ~val->intval & 0x7fff;
@@ -244,12 +219,11 @@ static int max17042_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
if (chip->pdata->enable_current_sense) {
- ret = max17042_read_reg(chip->client,
- MAX17042_AvgCurrent);
+ ret = regmap_read(map, MAX17042_AvgCurrent, &data);
if (ret < 0)
return ret;
- val->intval = ret;
+ val->intval = data;
if (val->intval & 0x8000) {
/* Negative */
val->intval = ~val->intval & 0x7fff;
@@ -267,16 +241,15 @@ static int max17042_get_property(struct power_supply *psy,
return 0;
}
-static int max17042_write_verify_reg(struct i2c_client *client,
- u8 reg, u16 value)
+static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
{
int retries = 8;
int ret;
- u16 read_value;
+ u32 read_value;
do {
- ret = i2c_smbus_write_word_data(client, reg, value);
- read_value = max17042_read_reg(client, reg);
+ ret = regmap_write(map, reg, value);
+ regmap_read(map, reg, &read_value);
if (read_value != value) {
ret = -EIO;
retries--;
@@ -284,50 +257,51 @@ static int max17042_write_verify_reg(struct i2c_client *client,
} while (retries && read_value != value);
if (ret < 0)
- dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+ pr_err("%s: err %d\n", __func__, ret);
return ret;
}
-static inline void max17042_override_por(
- struct i2c_client *client, u8 reg, u16 value)
+static inline void max17042_override_por(struct regmap *map,
+ u8 reg, u16 value)
{
if (value)
- max17042_write_reg(client, reg, value);
+ regmap_write(map, reg, value);
}
static inline void max10742_unlock_model(struct max17042_chip *chip)
{
- struct i2c_client *client = chip->client;
- max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_UNLOCK1);
- max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_UNLOCK2);
+ struct regmap *map = chip->regmap;
+ regmap_write(map, MAX17042_MLOCKReg1, MODEL_UNLOCK1);
+ regmap_write(map, MAX17042_MLOCKReg2, MODEL_UNLOCK2);
}
static inline void max10742_lock_model(struct max17042_chip *chip)
{
- struct i2c_client *client = chip->client;
- max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_LOCK1);
- max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_LOCK2);
+ struct regmap *map = chip->regmap;
+
+ regmap_write(map, MAX17042_MLOCKReg1, MODEL_LOCK1);
+ regmap_write(map, MAX17042_MLOCKReg2, MODEL_LOCK2);
}
static inline void max17042_write_model_data(struct max17042_chip *chip,
u8 addr, int size)
{
- struct i2c_client *client = chip->client;
+ struct regmap *map = chip->regmap;
int i;
for (i = 0; i < size; i++)
- max17042_write_reg(client, addr + i,
- chip->pdata->config_data->cell_char_tbl[i]);
+ regmap_write(map, addr + i,
+ chip->pdata->config_data->cell_char_tbl[i]);
}
static inline void max17042_read_model_data(struct max17042_chip *chip,
- u8 addr, u16 *data, int size)
+ u8 addr, u32 *data, int size)
{
- struct i2c_client *client = chip->client;
+ struct regmap *map = chip->regmap;
int i;
for (i = 0; i < size; i++)
- data[i] = max17042_read_reg(client, addr + i);
+ regmap_read(map, addr + i, &data[i]);
}
static inline int max17042_model_data_compare(struct max17042_chip *chip,
@@ -350,7 +324,7 @@ static int max17042_init_model(struct max17042_chip *chip)
{
int ret;
int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
- u16 *temp_data;
+ u32 *temp_data;
temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL);
if (!temp_data)
@@ -365,7 +339,7 @@ static int max17042_init_model(struct max17042_chip *chip)
ret = max17042_model_data_compare(
chip,
chip->pdata->config_data->cell_char_tbl,
- temp_data,
+ (u16 *)temp_data,
table_size);
max10742_lock_model(chip);
@@ -378,7 +352,7 @@ static int max17042_verify_model_lock(struct max17042_chip *chip)
{
int i;
int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
- u16 *temp_data;
+ u32 *temp_data;
int ret = 0;
temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL);
@@ -398,40 +372,38 @@ static int max17042_verify_model_lock(struct max17042_chip *chip)
static void max17042_write_config_regs(struct max17042_chip *chip)
{
struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
- max17042_write_reg(chip->client, MAX17042_CONFIG, config->config);
- max17042_write_reg(chip->client, MAX17042_LearnCFG, config->learn_cfg);
- max17042_write_reg(chip->client, MAX17042_FilterCFG,
+ regmap_write(map, MAX17042_CONFIG, config->config);
+ regmap_write(map, MAX17042_LearnCFG, config->learn_cfg);
+ regmap_write(map, MAX17042_FilterCFG,
config->filter_cfg);
- max17042_write_reg(chip->client, MAX17042_RelaxCFG, config->relax_cfg);
+ regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg);
if (chip->chip_type == MAX17047)
- max17042_write_reg(chip->client, MAX17047_FullSOCThr,
+ regmap_write(map, MAX17047_FullSOCThr,
config->full_soc_thresh);
}
static void max17042_write_custom_regs(struct max17042_chip *chip)
{
struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
- max17042_write_verify_reg(chip->client, MAX17042_RCOMP0,
- config->rcomp0);
- max17042_write_verify_reg(chip->client, MAX17042_TempCo,
- config->tcompc0);
- max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm,
- config->ichgt_term);
+ max17042_write_verify_reg(map, MAX17042_RCOMP0, config->rcomp0);
+ max17042_write_verify_reg(map, MAX17042_TempCo, config->tcompc0);
+ max17042_write_verify_reg(map, MAX17042_ICHGTerm, config->ichgt_term);
if (chip->chip_type == MAX17042) {
- max17042_write_reg(chip->client, MAX17042_EmptyTempCo,
- config->empty_tempco);
- max17042_write_verify_reg(chip->client, MAX17042_K_empty0,
+ regmap_write(map, MAX17042_EmptyTempCo, config->empty_tempco);
+ max17042_write_verify_reg(map, MAX17042_K_empty0,
config->kempty0);
} else {
- max17042_write_verify_reg(chip->client, MAX17047_QRTbl00,
+ max17042_write_verify_reg(map, MAX17047_QRTbl00,
config->qrtbl00);
- max17042_write_verify_reg(chip->client, MAX17047_QRTbl10,
+ max17042_write_verify_reg(map, MAX17047_QRTbl10,
config->qrtbl10);
- max17042_write_verify_reg(chip->client, MAX17047_QRTbl20,
+ max17042_write_verify_reg(map, MAX17047_QRTbl20,
config->qrtbl20);
- max17042_write_verify_reg(chip->client, MAX17047_QRTbl30,
+ max17042_write_verify_reg(map, MAX17047_QRTbl30,
config->qrtbl30);
}
}
@@ -439,58 +411,60 @@ static void max17042_write_custom_regs(struct max17042_chip *chip)
static void max17042_update_capacity_regs(struct max17042_chip *chip)
{
struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
- max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+ max17042_write_verify_reg(map, MAX17042_FullCAP,
config->fullcap);
- max17042_write_reg(chip->client, MAX17042_DesignCap,
- config->design_cap);
- max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+ regmap_write(map, MAX17042_DesignCap, config->design_cap);
+ max17042_write_verify_reg(map, MAX17042_FullCAPNom,
config->fullcapnom);
}
static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip)
{
- u16 vfSoc;
+ unsigned int vfSoc;
+ struct regmap *map = chip->regmap;
- vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC);
- max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK);
- max17042_write_verify_reg(chip->client, MAX17042_VFSOC0, vfSoc);
- max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK);
+ regmap_read(map, MAX17042_VFSOC, &vfSoc);
+ regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK);
+ max17042_write_verify_reg(map, MAX17042_VFSOC0, vfSoc);
+ regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_LOCK);
}
static void max17042_load_new_capacity_params(struct max17042_chip *chip)
{
- u16 full_cap0, rep_cap, dq_acc, vfSoc;
+ u32 full_cap0, rep_cap, dq_acc, vfSoc;
u32 rem_cap;
struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
- full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0);
- vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC);
+ regmap_read(map, MAX17042_FullCAP0, &full_cap0);
+ regmap_read(map, MAX17042_VFSOC, &vfSoc);
/* fg_vfSoc needs to shifted by 8 bits to get the
* perc in 1% accuracy, to get the right rem_cap multiply
* full_cap0, fg_vfSoc and devide by 100
*/
rem_cap = ((vfSoc >> 8) * full_cap0) / 100;
- max17042_write_verify_reg(chip->client, MAX17042_RemCap, (u16)rem_cap);
+ max17042_write_verify_reg(map, MAX17042_RemCap, rem_cap);
- rep_cap = (u16)rem_cap;
- max17042_write_verify_reg(chip->client, MAX17042_RepCap, rep_cap);
+ rep_cap = rem_cap;
+ max17042_write_verify_reg(map, MAX17042_RepCap, rep_cap);
/* Write dQ_acc to 200% of Capacity and dP_acc to 200% */
dq_acc = config->fullcap / dQ_ACC_DIV;
- max17042_write_verify_reg(chip->client, MAX17042_dQacc, dq_acc);
- max17042_write_verify_reg(chip->client, MAX17042_dPacc, dP_ACC_200);
+ max17042_write_verify_reg(map, MAX17042_dQacc, dq_acc);
+ max17042_write_verify_reg(map, MAX17042_dPacc, dP_ACC_200);
- max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+ max17042_write_verify_reg(map, MAX17042_FullCAP,
config->fullcap);
- max17042_write_reg(chip->client, MAX17042_DesignCap,
+ regmap_write(map, MAX17042_DesignCap,
config->design_cap);
- max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+ max17042_write_verify_reg(map, MAX17042_FullCAPNom,
config->fullcapnom);
/* Update SOC register with new SOC */
- max17042_write_reg(chip->client, MAX17042_RepSOC, vfSoc);
+ regmap_write(map, MAX17042_RepSOC, vfSoc);
}
/*
@@ -500,59 +474,60 @@ static void max17042_load_new_capacity_params(struct max17042_chip *chip)
*/
static inline void max17042_override_por_values(struct max17042_chip *chip)
{
- struct i2c_client *client = chip->client;
+ struct regmap *map = chip->regmap;
struct max17042_config_data *config = chip->pdata->config_data;
- max17042_override_por(client, MAX17042_TGAIN, config->tgain);
- max17042_override_por(client, MAx17042_TOFF, config->toff);
- max17042_override_por(client, MAX17042_CGAIN, config->cgain);
- max17042_override_por(client, MAX17042_COFF, config->coff);
-
- max17042_override_por(client, MAX17042_VALRT_Th, config->valrt_thresh);
- max17042_override_por(client, MAX17042_TALRT_Th, config->talrt_thresh);
- max17042_override_por(client, MAX17042_SALRT_Th,
- config->soc_alrt_thresh);
- max17042_override_por(client, MAX17042_CONFIG, config->config);
- max17042_override_por(client, MAX17042_SHDNTIMER, config->shdntimer);
-
- max17042_override_por(client, MAX17042_DesignCap, config->design_cap);
- max17042_override_por(client, MAX17042_ICHGTerm, config->ichgt_term);
-
- max17042_override_por(client, MAX17042_AtRate, config->at_rate);
- max17042_override_por(client, MAX17042_LearnCFG, config->learn_cfg);
- max17042_override_por(client, MAX17042_FilterCFG, config->filter_cfg);
- max17042_override_por(client, MAX17042_RelaxCFG, config->relax_cfg);
- max17042_override_por(client, MAX17042_MiscCFG, config->misc_cfg);
- max17042_override_por(client, MAX17042_MaskSOC, config->masksoc);
-
- max17042_override_por(client, MAX17042_FullCAP, config->fullcap);
- max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom);
+ max17042_override_por(map, MAX17042_TGAIN, config->tgain);
+ max17042_override_por(map, MAx17042_TOFF, config->toff);
+ max17042_override_por(map, MAX17042_CGAIN, config->cgain);
+ max17042_override_por(map, MAX17042_COFF, config->coff);
+
+ max17042_override_por(map, MAX17042_VALRT_Th, config->valrt_thresh);
+ max17042_override_por(map, MAX17042_TALRT_Th, config->talrt_thresh);
+ max17042_override_por(map, MAX17042_SALRT_Th,
+ config->soc_alrt_thresh);
+ max17042_override_por(map, MAX17042_CONFIG, config->config);
+ max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer);
+
+ max17042_override_por(map, MAX17042_DesignCap, config->design_cap);
+ max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term);
+
+ max17042_override_por(map, MAX17042_AtRate, config->at_rate);
+ max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg);
+ max17042_override_por(map, MAX17042_FilterCFG, config->filter_cfg);
+ max17042_override_por(map, MAX17042_RelaxCFG, config->relax_cfg);
+ max17042_override_por(map, MAX17042_MiscCFG, config->misc_cfg);
+ max17042_override_por(map, MAX17042_MaskSOC, config->masksoc);
+
+ max17042_override_por(map, MAX17042_FullCAP, config->fullcap);
+ max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom);
if (chip->chip_type == MAX17042)
- max17042_override_por(client, MAX17042_SOC_empty,
+ max17042_override_por(map, MAX17042_SOC_empty,
config->socempty);
- max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty);
- max17042_override_por(client, MAX17042_dQacc, config->dqacc);
- max17042_override_por(client, MAX17042_dPacc, config->dpacc);
+ max17042_override_por(map, MAX17042_LAvg_empty, config->lavg_empty);
+ max17042_override_por(map, MAX17042_dQacc, config->dqacc);
+ max17042_override_por(map, MAX17042_dPacc, config->dpacc);
if (chip->chip_type == MAX17042)
- max17042_override_por(client, MAX17042_V_empty, config->vempty);
+ max17042_override_por(map, MAX17042_V_empty, config->vempty);
else
- max17042_override_por(client, MAX17047_V_empty, config->vempty);
- max17042_override_por(client, MAX17042_TempNom, config->temp_nom);
- max17042_override_por(client, MAX17042_TempLim, config->temp_lim);
- max17042_override_por(client, MAX17042_FCTC, config->fctc);
- max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0);
- max17042_override_por(client, MAX17042_TempCo, config->tcompc0);
+ max17042_override_por(map, MAX17047_V_empty, config->vempty);
+ max17042_override_por(map, MAX17042_TempNom, config->temp_nom);
+ max17042_override_por(map, MAX17042_TempLim, config->temp_lim);
+ max17042_override_por(map, MAX17042_FCTC, config->fctc);
+ max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
+ max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
if (chip->chip_type) {
- max17042_override_por(client, MAX17042_EmptyTempCo,
- config->empty_tempco);
- max17042_override_por(client, MAX17042_K_empty0,
- config->kempty0);
+ max17042_override_por(map, MAX17042_EmptyTempCo,
+ config->empty_tempco);
+ max17042_override_por(map, MAX17042_K_empty0,
+ config->kempty0);
}
}
static int max17042_init_chip(struct max17042_chip *chip)
{
+ struct regmap *map = chip->regmap;
int ret;
int val;
@@ -597,31 +572,32 @@ static int max17042_init_chip(struct max17042_chip *chip)
max17042_load_new_capacity_params(chip);
/* Init complete, Clear the POR bit */
- val = max17042_read_reg(chip->client, MAX17042_STATUS);
- max17042_write_reg(chip->client, MAX17042_STATUS,
- val & (~STATUS_POR_BIT));
+ regmap_read(map, MAX17042_STATUS, &val);
+ regmap_write(map, MAX17042_STATUS, val & (~STATUS_POR_BIT));
return 0;
}
static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
{
- u16 soc, soc_tr;
+ struct regmap *map = chip->regmap;
+ u32 soc, soc_tr;
/* program interrupt thesholds such that we should
* get interrupt for every 'off' perc change in the soc
*/
- soc = max17042_read_reg(chip->client, MAX17042_RepSOC) >> 8;
+ regmap_read(map, MAX17042_RepSOC, &soc);
+ soc >>= 8;
soc_tr = (soc + off) << 8;
soc_tr |= (soc - off);
- max17042_write_reg(chip->client, MAX17042_SALRT_Th, soc_tr);
+ regmap_write(map, MAX17042_SALRT_Th, soc_tr);
}
static irqreturn_t max17042_thread_handler(int id, void *dev)
{
struct max17042_chip *chip = dev;
- u16 val;
+ u32 val;
- val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ regmap_read(chip->regmap, MAX17042_STATUS, &val);
if ((val & STATUS_INTR_SOCMIN_BIT) ||
(val & STATUS_INTR_SOCMAX_BIT)) {
dev_info(&chip->client->dev, "SOC threshold INTR\n");
@@ -682,13 +658,20 @@ max17042_get_pdata(struct device *dev)
}
#endif
+static struct regmap_config max17042_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+};
+
static int max17042_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct max17042_chip *chip;
int ret;
- int reg;
+ int i;
+ u32 val;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
return -EIO;
@@ -698,6 +681,12 @@ static int max17042_probe(struct i2c_client *client,
return -ENOMEM;
chip->client = client;
+ chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ dev_err(&client->dev, "Failed to initialize regmap\n");
+ return -EINVAL;
+ }
+
chip->pdata = max17042_get_pdata(&client->dev);
if (!chip->pdata) {
dev_err(&client->dev, "no platform data provided\n");
@@ -706,15 +695,15 @@ static int max17042_probe(struct i2c_client *client,
i2c_set_clientdata(client, chip);
- ret = max17042_read_reg(chip->client, MAX17042_DevName);
- if (ret == MAX17042_IC_VERSION) {
+ regmap_read(chip->regmap, MAX17042_DevName, &val);
+ if (val == MAX17042_IC_VERSION) {
dev_dbg(&client->dev, "chip type max17042 detected\n");
chip->chip_type = MAX17042;
- } else if (ret == MAX17047_IC_VERSION) {
+ } else if (val == MAX17047_IC_VERSION) {
dev_dbg(&client->dev, "chip type max17047/50 detected\n");
chip->chip_type = MAX17047;
} else {
- dev_err(&client->dev, "device version mismatch: %x\n", ret);
+ dev_err(&client->dev, "device version mismatch: %x\n", val);
return -EIO;
}
@@ -733,13 +722,15 @@ static int max17042_probe(struct i2c_client *client,
chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
if (chip->pdata->init_data)
- max17042_set_reg(client, chip->pdata->init_data,
- chip->pdata->num_init_data);
+ for (i = 0; i < chip->pdata->num_init_data; i++)
+ regmap_write(chip->regmap,
+ chip->pdata->init_data[i].addr,
+ chip->pdata->init_data[i].data);
if (!chip->pdata->enable_current_sense) {
- max17042_write_reg(client, MAX17042_CGAIN, 0x0000);
- max17042_write_reg(client, MAX17042_MiscCFG, 0x0003);
- max17042_write_reg(client, MAX17042_LearnCFG, 0x0007);
+ regmap_write(chip->regmap, MAX17042_CGAIN, 0x0000);
+ regmap_write(chip->regmap, MAX17042_MiscCFG, 0x0003);
+ regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007);
}
ret = power_supply_register(&client->dev, &chip->battery);
@@ -750,13 +741,13 @@ static int max17042_probe(struct i2c_client *client,
if (client->irq) {
ret = request_threaded_irq(client->irq, NULL,
- max17042_thread_handler,
- IRQF_TRIGGER_FALLING,
- chip->battery.name, chip);
+ max17042_thread_handler,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ chip->battery.name, chip);
if (!ret) {
- reg = max17042_read_reg(client, MAX17042_CONFIG);
- reg |= CONFIG_ALRT_BIT_ENBL;
- max17042_write_reg(client, MAX17042_CONFIG, reg);
+ regmap_read(chip->regmap, MAX17042_CONFIG, &val);
+ val |= CONFIG_ALRT_BIT_ENBL;
+ regmap_write(chip->regmap, MAX17042_CONFIG, val);
max17042_set_soc_threshold(chip, 1);
} else {
client->irq = 0;
@@ -765,8 +756,8 @@ static int max17042_probe(struct i2c_client *client,
}
}
- reg = max17042_read_reg(chip->client, MAX17042_STATUS);
- if (reg & STATUS_POR_BIT) {
+ regmap_read(chip->regmap, MAX17042_STATUS, &val);
+ if (val & STATUS_POR_BIT) {
INIT_WORK(&chip->work, max17042_init_worker);
schedule_work(&chip->work);
} else {
@@ -786,7 +777,7 @@ static int max17042_remove(struct i2c_client *client)
return 0;
}
-#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
static int max17042_suspend(struct device *dev)
{
struct max17042_chip *chip = dev_get_drvdata(dev);
@@ -816,17 +807,11 @@ static int max17042_resume(struct device *dev)
return 0;
}
-
-static const struct dev_pm_ops max17042_pm_ops = {
- .suspend = max17042_suspend,
- .resume = max17042_resume,
-};
-
-#define MAX17042_PM_OPS (&max17042_pm_ops)
-#else
-#define MAX17042_PM_OPS NULL
#endif
+static SIMPLE_DEV_PM_OPS(max17042_pm_ops, max17042_suspend,
+ max17042_resume);
+
#ifdef CONFIG_OF
static const struct of_device_id max17042_dt_match[] = {
{ .compatible = "maxim,max17042" },
@@ -849,7 +834,7 @@ static struct i2c_driver max17042_i2c_driver = {
.driver = {
.name = "max17042",
.of_match_table = of_match_ptr(max17042_dt_match),
- .pm = MAX17042_PM_OPS,
+ .pm = &max17042_pm_ops,
},
.probe = max17042_probe,
.remove = max17042_remove,
diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c
index 14e2b96d93b..08f0d7909b6 100644
--- a/drivers/power/max8903_charger.c
+++ b/drivers/power/max8903_charger.c
@@ -189,7 +189,7 @@ static int max8903_probe(struct platform_device *pdev)
int ta_in = 0;
int usb_in = 0;
- data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL);
+ data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL);
if (data == NULL) {
dev_err(dev, "Cannot allocate memory.\n");
return -ENOMEM;
@@ -341,7 +341,6 @@ err_dc_irq:
err_psy:
power_supply_unregister(&data->psy);
err:
- kfree(data);
return ret;
}
@@ -359,7 +358,6 @@ static int max8903_remove(struct platform_device *pdev)
if (pdata->dc_valid)
free_irq(gpio_to_irq(pdata->dok), data);
power_supply_unregister(&data->psy);
- kfree(data);
}
return 0;
diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c
index 665cdc76c26..b4513f284bb 100644
--- a/drivers/power/max8925_power.c
+++ b/drivers/power/max8925_power.c
@@ -458,6 +458,7 @@ max8925_power_dt_init(struct platform_device *pdev)
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);
+ of_node_put(np);
pdata->batt_detect = batt_detect;
pdata->fast_charge = fast_charge;
@@ -489,7 +490,8 @@ static int max8925_power_probe(struct platform_device *pdev)
return -EINVAL;
}
- info = kzalloc(sizeof(struct max8925_power_info), GFP_KERNEL);
+ info = devm_kzalloc(&pdev->dev, sizeof(struct max8925_power_info),
+ GFP_KERNEL);
if (!info)
return -ENOMEM;
info->chip = chip;
@@ -546,7 +548,6 @@ out_battery:
out_usb:
power_supply_unregister(&info->ac);
out:
- kfree(info);
return ret;
}
@@ -559,7 +560,6 @@ static int max8925_power_remove(struct platform_device *pdev)
power_supply_unregister(&info->usb);
power_supply_unregister(&info->battery);
max8925_deinit_charger(info);
- kfree(info);
}
return 0;
}
diff --git a/drivers/power/max8997_charger.c b/drivers/power/max8997_charger.c
index e757885b620..4bdedfed936 100644
--- a/drivers/power/max8997_charger.c
+++ b/drivers/power/max8997_charger.c
@@ -138,7 +138,8 @@ static int max8997_battery_probe(struct platform_device *pdev)
return ret;
}
- charger = kzalloc(sizeof(struct charger_data), GFP_KERNEL);
+ charger = devm_kzalloc(&pdev->dev, sizeof(struct charger_data),
+ GFP_KERNEL);
if (charger == NULL) {
dev_err(&pdev->dev, "Cannot allocate memory.\n");
return -ENOMEM;
@@ -158,13 +159,10 @@ static int max8997_battery_probe(struct platform_device *pdev)
ret = power_supply_register(&pdev->dev, &charger->battery);
if (ret) {
dev_err(&pdev->dev, "failed: power supply register\n");
- goto err;
+ return ret;
}
return 0;
-err:
- kfree(charger);
- return ret;
}
static int max8997_battery_remove(struct platform_device *pdev)
@@ -172,7 +170,6 @@ static int max8997_battery_remove(struct platform_device *pdev)
struct charger_data *charger = platform_get_drvdata(pdev);
power_supply_unregister(&charger->battery);
- kfree(charger);
return 0;
}
diff --git a/drivers/power/max8998_charger.c b/drivers/power/max8998_charger.c
index bf677e3daec..5017470c2fc 100644
--- a/drivers/power/max8998_charger.c
+++ b/drivers/power/max8998_charger.c
@@ -88,7 +88,8 @@ static int max8998_battery_probe(struct platform_device *pdev)
return -ENODEV;
}
- max8998 = kzalloc(sizeof(struct max8998_battery_data), GFP_KERNEL);
+ max8998 = devm_kzalloc(&pdev->dev, sizeof(struct max8998_battery_data),
+ GFP_KERNEL);
if (!max8998)
return -ENOMEM;
@@ -174,7 +175,6 @@ static int max8998_battery_probe(struct platform_device *pdev)
return 0;
err:
- kfree(max8998);
return ret;
}
@@ -183,7 +183,6 @@ static int max8998_battery_remove(struct platform_device *pdev)
struct max8998_battery_data *max8998 = platform_get_drvdata(pdev);
power_supply_unregister(&max8998->battery);
- kfree(max8998);
return 0;
}
diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c
index c2122a7ad06..771c4f0fb8a 100644
--- a/drivers/power/pcf50633-charger.c
+++ b/drivers/power/pcf50633-charger.c
@@ -191,9 +191,9 @@ static ssize_t set_usblim(struct device *dev,
unsigned long ma;
int ret;
- ret = strict_strtoul(buf, 10, &ma);
+ ret = kstrtoul(buf, 10, &ma);
if (ret)
- return -EINVAL;
+ return ret;
pcf50633_mbc_usb_curlim_set(mbc->pcf, ma);
@@ -228,9 +228,9 @@ static ssize_t set_chglim(struct device *dev,
if (!mbc->pcf->pdata->charger_reference_current_ma)
return -ENODEV;
- ret = strict_strtoul(buf, 10, &ma);
+ ret = kstrtoul(buf, 10, &ma);
if (ret)
- return -EINVAL;
+ return ret;
mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma;
if (mbcc5 > 255)
@@ -373,7 +373,7 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
int i;
u8 mbcs1;
- mbc = kzalloc(sizeof(*mbc), GFP_KERNEL);
+ mbc = devm_kzalloc(&pdev->dev, sizeof(*mbc), GFP_KERNEL);
if (!mbc)
return -ENOMEM;
@@ -413,7 +413,6 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
ret = power_supply_register(&pdev->dev, &mbc->adapter);
if (ret) {
dev_err(mbc->pcf->dev, "failed to register adapter\n");
- kfree(mbc);
return ret;
}
@@ -421,7 +420,6 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
if (ret) {
dev_err(mbc->pcf->dev, "failed to register usb\n");
power_supply_unregister(&mbc->adapter);
- kfree(mbc);
return ret;
}
@@ -430,7 +428,6 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
dev_err(mbc->pcf->dev, "failed to register ac\n");
power_supply_unregister(&mbc->adapter);
power_supply_unregister(&mbc->usb);
- kfree(mbc);
return ret;
}
@@ -461,8 +458,6 @@ static int pcf50633_mbc_remove(struct platform_device *pdev)
power_supply_unregister(&mbc->adapter);
power_supply_unregister(&mbc->ac);
- kfree(mbc);
-
return 0;
}
diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c
index 7df7c5facc1..0c52e2a0d90 100644
--- a/drivers/power/pda_power.c
+++ b/drivers/power/pda_power.c
@@ -35,7 +35,7 @@ static struct timer_list supply_timer;
static struct timer_list polling_timer;
static int polling;
-#ifdef CONFIG_USB_OTG_UTILS
+#if IS_ENABLED(CONFIG_USB_PHY)
static struct usb_phy *transceiver;
static struct notifier_block otg_nb;
#endif
@@ -218,7 +218,7 @@ static void polling_timer_func(unsigned long unused)
jiffies + msecs_to_jiffies(pdata->polling_interval));
}
-#ifdef CONFIG_USB_OTG_UTILS
+#if IS_ENABLED(CONFIG_USB_PHY)
static int otg_is_usb_online(void)
{
return (transceiver->last_event == USB_EVENT_VBUS ||
@@ -315,7 +315,7 @@ static int pda_power_probe(struct platform_device *pdev)
pda_psy_usb.num_supplicants = pdata->num_supplicants;
}
-#ifdef CONFIG_USB_OTG_UTILS
+#if IS_ENABLED(CONFIG_USB_PHY)
transceiver = usb_get_phy(USB_PHY_TYPE_USB2);
if (!IS_ERR_OR_NULL(transceiver)) {
if (!pdata->is_usb_online)
@@ -367,7 +367,7 @@ static int pda_power_probe(struct platform_device *pdev)
}
}
-#ifdef CONFIG_USB_OTG_UTILS
+#if IS_ENABLED(CONFIG_USB_PHY)
if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier) {
otg_nb.notifier_call = otg_handle_notification;
ret = usb_register_notifier(transceiver, &otg_nb);
@@ -391,7 +391,7 @@ static int pda_power_probe(struct platform_device *pdev)
return 0;
-#ifdef CONFIG_USB_OTG_UTILS
+#if IS_ENABLED(CONFIG_USB_PHY)
otg_reg_notifier_failed:
if (pdata->is_usb_online && usb_irq)
free_irq(usb_irq->start, &pda_psy_usb);
@@ -402,7 +402,7 @@ usb_irq_failed:
usb_supply_failed:
if (pdata->is_ac_online && ac_irq)
free_irq(ac_irq->start, &pda_psy_ac);
-#ifdef CONFIG_USB_OTG_UTILS
+#if IS_ENABLED(CONFIG_USB_PHY)
if (!IS_ERR_OR_NULL(transceiver))
usb_put_phy(transceiver);
#endif
@@ -437,7 +437,7 @@ static int pda_power_remove(struct platform_device *pdev)
power_supply_unregister(&pda_psy_usb);
if (pdata->is_ac_online)
power_supply_unregister(&pda_psy_ac);
-#ifdef CONFIG_USB_OTG_UTILS
+#if IS_ENABLED(CONFIG_USB_PHY)
if (!IS_ERR_OR_NULL(transceiver))
usb_put_phy(transceiver);
#endif
diff --git a/drivers/power/pm2301_charger.c b/drivers/power/pm2301_charger.c
new file mode 100644
index 00000000000..62c15af58c9
--- /dev/null
+++ b/drivers/power/pm2301_charger.c
@@ -0,0 +1,1269 @@
+/*
+ * Copyright 2012 ST Ericsson.
+ *
+ * Power supply driver for ST Ericsson pm2xxx_charger charger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/pm2301_charger.h>
+#include <linux/gpio.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm.h>
+
+#include "pm2301_charger.h"
+
+#define to_pm2xxx_charger_ac_device_info(x) container_of((x), \
+ struct pm2xxx_charger, ac_chg)
+#define SLEEP_MIN 50
+#define SLEEP_MAX 100
+#define PM2XXX_AUTOSUSPEND_DELAY 500
+
+static int pm2xxx_interrupt_registers[] = {
+ PM2XXX_REG_INT1,
+ PM2XXX_REG_INT2,
+ PM2XXX_REG_INT3,
+ PM2XXX_REG_INT4,
+ PM2XXX_REG_INT5,
+ PM2XXX_REG_INT6,
+};
+
+static enum power_supply_property pm2xxx_charger_ac_props[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+};
+
+static int pm2xxx_charger_voltage_map[] = {
+ 3500,
+ 3525,
+ 3550,
+ 3575,
+ 3600,
+ 3625,
+ 3650,
+ 3675,
+ 3700,
+ 3725,
+ 3750,
+ 3775,
+ 3800,
+ 3825,
+ 3850,
+ 3875,
+ 3900,
+ 3925,
+ 3950,
+ 3975,
+ 4000,
+ 4025,
+ 4050,
+ 4075,
+ 4100,
+ 4125,
+ 4150,
+ 4175,
+ 4200,
+ 4225,
+ 4250,
+ 4275,
+ 4300,
+};
+
+static int pm2xxx_charger_current_map[] = {
+ 200,
+ 200,
+ 400,
+ 600,
+ 800,
+ 1000,
+ 1200,
+ 1400,
+ 1600,
+ 1800,
+ 2000,
+ 2200,
+ 2400,
+ 2600,
+ 2800,
+ 3000,
+};
+
+static const struct i2c_device_id pm2xxx_ident[] = {
+ { "pm2301", 0 },
+ { }
+};
+
+static void set_lpn_pin(struct pm2xxx_charger *pm2)
+{
+ if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) {
+ gpio_set_value(pm2->lpn_pin, 1);
+ usleep_range(SLEEP_MIN, SLEEP_MAX);
+ }
+}
+
+static void clear_lpn_pin(struct pm2xxx_charger *pm2)
+{
+ if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin))
+ gpio_set_value(pm2->lpn_pin, 0);
+}
+
+static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val)
+{
+ int ret;
+
+ /* wake up the device */
+ pm_runtime_get_sync(pm2->dev);
+
+ ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg,
+ 1, val);
+ if (ret < 0)
+ dev_err(pm2->dev, "Error reading register at 0x%x\n", reg);
+ else
+ ret = 0;
+
+ pm_runtime_put_sync(pm2->dev);
+
+ return ret;
+}
+
+static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val)
+{
+ int ret;
+
+ /* wake up the device */
+ pm_runtime_get_sync(pm2->dev);
+
+ ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg,
+ 1, &val);
+ if (ret < 0)
+ dev_err(pm2->dev, "Error writing register at 0x%x\n", reg);
+ else
+ ret = 0;
+
+ pm_runtime_put_sync(pm2->dev);
+
+ return ret;
+}
+
+static int pm2xxx_charging_enable_mngt(struct pm2xxx_charger *pm2)
+{
+ int ret;
+
+ /* Enable charging */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2,
+ (PM2XXX_CH_AUTO_RESUME_EN | PM2XXX_CHARGER_ENA));
+
+ return ret;
+}
+
+static int pm2xxx_charging_disable_mngt(struct pm2xxx_charger *pm2)
+{
+ int ret;
+
+ /* Disable SW EOC ctrl */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, PM2XXX_SWCTRL_HW);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ return ret;
+ }
+
+ /* Disable charging */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2,
+ (PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS));
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work);
+
+ return 0;
+}
+
+
+static int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work);
+
+ return 0;
+}
+
+static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ dev_err(pm2->dev, "Overvoltage detected\n");
+ pm2->flags.ovv = true;
+ power_supply_changed(&pm2->ac_chg.psy);
+
+ /* Schedule a new HW failure check */
+ queue_delayed_work(pm2->charger_wq, &pm2->check_hw_failure_work, 0);
+
+ return 0;
+}
+
+static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ dev_dbg(pm2->dev , "20 minutes watchdog expired\n");
+
+ pm2->ac.wd_expired = true;
+ power_supply_changed(&pm2->ac_chg.psy);
+
+ return 0;
+}
+
+static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ int ret;
+
+ switch (val) {
+ case PM2XXX_INT1_ITVBATLOWR:
+ dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n");
+ /* Enable SW EOC ctrl */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG,
+ PM2XXX_SWCTRL_SW);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ return ret;
+ }
+ break;
+
+ case PM2XXX_INT1_ITVBATLOWF:
+ dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n");
+ /* Disable SW EOC ctrl */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG,
+ PM2XXX_SWCTRL_HW);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ return ret;
+ }
+ break;
+
+ default:
+ dev_err(pm2->dev, "Unknown VBAT level\n");
+ }
+
+ return 0;
+}
+
+static int pm2xxx_charger_bat_disc_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ dev_dbg(pm2->dev, "battery disconnected\n");
+
+ return 0;
+}
+
+static int pm2xxx_charger_detection(struct pm2xxx_charger *pm2, u8 *val)
+{
+ int ret;
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT2, val);
+
+ if (ret < 0) {
+ dev_err(pm2->dev, "Charger detection failed\n");
+ goto out;
+ }
+
+ *val &= (PM2XXX_INT2_S_ITVPWR1PLUG | PM2XXX_INT2_S_ITVPWR2PLUG);
+
+out:
+ return ret;
+}
+
+static int pm2xxx_charger_itv_pwr_plug_mngt(struct pm2xxx_charger *pm2, int val)
+{
+
+ int ret;
+ u8 read_val;
+
+ /*
+ * Since we can't be sure that the events are received
+ * synchronously, we have the check if the main charger is
+ * connected by reading the interrupt source register.
+ */
+ ret = pm2xxx_charger_detection(pm2, &read_val);
+
+ if ((ret == 0) && read_val) {
+ pm2->ac.charger_connected = 1;
+ pm2->ac_conn = true;
+ queue_work(pm2->charger_wq, &pm2->ac_work);
+ }
+
+
+ return ret;
+}
+
+static int pm2xxx_charger_itv_pwr_unplug_mngt(struct pm2xxx_charger *pm2,
+ int val)
+{
+ pm2->ac.charger_connected = 0;
+ queue_work(pm2->charger_wq, &pm2->ac_work);
+
+ return 0;
+}
+
+static int pm2_int_reg0(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & PM2XXX_INT1_ITVBATLOWR) {
+ ret = pm2xxx_charger_vbat_lsig_mngt(pm2,
+ PM2XXX_INT1_ITVBATLOWR);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (val & PM2XXX_INT1_ITVBATLOWF) {
+ ret = pm2xxx_charger_vbat_lsig_mngt(pm2,
+ PM2XXX_INT1_ITVBATLOWF);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (val & PM2XXX_INT1_ITVBATDISCONNECT) {
+ ret = pm2xxx_charger_bat_disc_mngt(pm2,
+ PM2XXX_INT1_ITVBATDISCONNECT);
+ if (ret < 0)
+ goto out;
+ }
+out:
+ return ret;
+}
+
+static int pm2_int_reg1(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)) {
+ dev_dbg(pm2->dev , "Main charger plugged\n");
+ ret = pm2xxx_charger_itv_pwr_plug_mngt(pm2, val &
+ (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG));
+ }
+
+ if (val &
+ (PM2XXX_INT2_ITVPWR1UNPLUG | PM2XXX_INT2_ITVPWR2UNPLUG)) {
+ dev_dbg(pm2->dev , "Main charger unplugged\n");
+ ret = pm2xxx_charger_itv_pwr_unplug_mngt(pm2, val &
+ (PM2XXX_INT2_ITVPWR1UNPLUG |
+ PM2XXX_INT2_ITVPWR2UNPLUG));
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg2(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & PM2XXX_INT3_ITAUTOTIMEOUTWD)
+ ret = pm2xxx_charger_wd_exp_mngt(pm2, val);
+
+ if (val & (PM2XXX_INT3_ITCHPRECHARGEWD |
+ PM2XXX_INT3_ITCHCCWD | PM2XXX_INT3_ITCHCVWD)) {
+ dev_dbg(pm2->dev,
+ "Watchdog occurred for precharge, CC and CV charge\n");
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg3(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & (PM2XXX_INT4_ITCHARGINGON)) {
+ dev_dbg(pm2->dev ,
+ "chargind operation has started\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITVRESUME)) {
+ dev_dbg(pm2->dev,
+ "battery discharged down to VResume threshold\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITBATTFULL)) {
+ dev_dbg(pm2->dev , "battery fully detected\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITCVPHASE)) {
+ dev_dbg(pm2->dev, "CV phase enter with 0.5C charging\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)) {
+ pm2->failure_case = VPWR_OVV;
+ ret = pm2xxx_charger_ovv_mngt(pm2, val &
+ (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV));
+ dev_dbg(pm2->dev, "VPWR/VSYSTEM overvoltage detected\n");
+ }
+
+ if (val & (PM2XXX_INT4_S_ITBATTEMPCOLD |
+ PM2XXX_INT4_S_ITBATTEMPHOT)) {
+ ret = pm2xxx_charger_batt_therm_mngt(pm2, val &
+ (PM2XXX_INT4_S_ITBATTEMPCOLD |
+ PM2XXX_INT4_S_ITBATTEMPHOT));
+ dev_dbg(pm2->dev, "BTEMP is too Low/High\n");
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg4(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & PM2XXX_INT5_ITVSYSTEMOVV) {
+ pm2->failure_case = VSYSTEM_OVV;
+ ret = pm2xxx_charger_ovv_mngt(pm2, val &
+ PM2XXX_INT5_ITVSYSTEMOVV);
+ dev_dbg(pm2->dev, "VSYSTEM overvoltage detected\n");
+ }
+
+ if (val & (PM2XXX_INT5_ITTHERMALWARNINGFALL |
+ PM2XXX_INT5_ITTHERMALWARNINGRISE |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNFALL |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)) {
+ dev_dbg(pm2->dev, "BTEMP die temperature is too Low/High\n");
+ ret = pm2xxx_charger_die_therm_mngt(pm2, val &
+ (PM2XXX_INT5_ITTHERMALWARNINGFALL |
+ PM2XXX_INT5_ITTHERMALWARNINGRISE |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNFALL |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNRISE));
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg5(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) {
+ dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n");
+ }
+
+ if (val & (PM2XXX_INT6_ITVPWR2VALIDRISE |
+ PM2XXX_INT6_ITVPWR1VALIDRISE |
+ PM2XXX_INT6_ITVPWR2VALIDFALL |
+ PM2XXX_INT6_ITVPWR1VALIDFALL)) {
+ dev_dbg(pm2->dev, "Falling/Rising edge on WPWR1/2\n");
+ }
+
+ return ret;
+}
+
+static irqreturn_t pm2xxx_irq_int(int irq, void *data)
+{
+ struct pm2xxx_charger *pm2 = data;
+ struct pm2xxx_interrupts *interrupt = pm2->pm2_int;
+ int i;
+
+ /* wake up the device */
+ pm_runtime_get_sync(pm2->dev);
+
+ do {
+ for (i = 0; i < PM2XXX_NUM_INT_REG; i++) {
+ pm2xxx_reg_read(pm2,
+ pm2xxx_interrupt_registers[i],
+ &(interrupt->reg[i]));
+
+ if (interrupt->reg[i] > 0)
+ interrupt->handler[i](pm2, interrupt->reg[i]);
+ }
+ } while (gpio_get_value(pm2->pdata->gpio_irq_number) == 0);
+
+ pm_runtime_mark_last_busy(pm2->dev);
+ pm_runtime_put_autosuspend(pm2->dev);
+
+ return IRQ_HANDLED;
+}
+
+static int pm2xxx_charger_get_ac_cv(struct pm2xxx_charger *pm2)
+{
+ int ret = 0;
+ u8 val;
+
+ if (pm2->ac.charger_connected && pm2->ac.charger_online) {
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ goto out;
+ }
+
+ if (val & PM2XXX_INT4_S_ITCVPHASE)
+ ret = PM2XXX_CONST_VOLT;
+ else
+ ret = PM2XXX_CONST_CURR;
+ }
+out:
+ return ret;
+}
+
+static int pm2xxx_current_to_regval(int curr)
+{
+ int i;
+
+ if (curr < pm2xxx_charger_current_map[0])
+ return 0;
+
+ for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_current_map); i++) {
+ if (curr < pm2xxx_charger_current_map[i])
+ return (i - 1);
+ }
+
+ i = ARRAY_SIZE(pm2xxx_charger_current_map) - 1;
+ if (curr == pm2xxx_charger_current_map[i])
+ return i;
+ else
+ return -EINVAL;
+}
+
+static int pm2xxx_voltage_to_regval(int curr)
+{
+ int i;
+
+ if (curr < pm2xxx_charger_voltage_map[0])
+ return 0;
+
+ for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_voltage_map); i++) {
+ if (curr < pm2xxx_charger_voltage_map[i])
+ return i - 1;
+ }
+
+ i = ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1;
+ if (curr == pm2xxx_charger_voltage_map[i])
+ return i;
+ else
+ return -EINVAL;
+}
+
+static int pm2xxx_charger_update_charger_current(struct ux500_charger *charger,
+ int ich_out)
+{
+ int ret;
+ int curr_index;
+ struct pm2xxx_charger *pm2;
+ u8 val;
+
+ if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
+ pm2 = to_pm2xxx_charger_ac_device_info(charger);
+ else
+ return -ENXIO;
+
+ curr_index = pm2xxx_current_to_regval(ich_out);
+ if (curr_index < 0) {
+ dev_err(pm2->dev,
+ "Charger current too high, charging not started\n");
+ return -ENXIO;
+ }
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val);
+ if (ret >= 0) {
+ val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK;
+ val |= curr_index;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val);
+ if (ret < 0) {
+ dev_err(pm2->dev,
+ "%s write failed\n", __func__);
+ }
+ }
+ else
+ dev_err(pm2->dev, "%s read failed\n", __func__);
+
+ return ret;
+}
+
+static int pm2xxx_charger_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pm2xxx_charger *pm2;
+
+ pm2 = to_pm2xxx_charger_ac_device_info(psy_to_ux500_charger(psy));
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (pm2->flags.mainextchnotok)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (pm2->ac.wd_expired)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else if (pm2->flags.main_thermal_prot)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (pm2->flags.ovv)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = pm2->ac.charger_online;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = pm2->ac.charger_connected;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ pm2->ac.cv_active = pm2xxx_charger_get_ac_cv(pm2);
+ val->intval = pm2->ac.cv_active;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int pm2xxx_charging_init(struct pm2xxx_charger *pm2)
+{
+ int ret = 0;
+
+ /* enable CC and CV watchdog */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG3,
+ (PM2XXX_CH_WD_CV_PHASE_60MIN | PM2XXX_CH_WD_CC_PHASE_60MIN));
+ if( ret < 0)
+ return ret;
+
+ /* enable precharge watchdog */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG4,
+ PM2XXX_CH_WD_PRECH_PHASE_60MIN);
+
+ /* Disable auto timeout */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG5,
+ PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN);
+
+ /*
+ * EOC current level = 100mA
+ * Precharge current level = 100mA
+ * CC current level = 1000mA
+ */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6,
+ (PM2XXX_DIR_CH_CC_CURRENT_1000MA |
+ PM2XXX_CH_PRECH_CURRENT_100MA |
+ PM2XXX_CH_EOC_CURRENT_100MA));
+
+ /*
+ * recharge threshold = 3.8V
+ * Precharge to CC threshold = 2.9V
+ */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG7,
+ (PM2XXX_CH_PRECH_VOL_2_9 | PM2XXX_CH_VRESUME_VOL_3_8));
+
+ /* float voltage charger level = 4.2V */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8,
+ PM2XXX_CH_VOLT_4_2);
+
+ /* Voltage drop between VBAT and VSYS in HW charging = 300mV */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG9,
+ (PM2XXX_CH_150MV_DROP_300MV | PM2XXX_CHARCHING_INFO_DIS |
+ PM2XXX_CH_CC_REDUCED_CURRENT_IDENT |
+ PM2XXX_CH_CC_MODEDROP_DIS));
+
+ /* Input charger level of over voltage = 10V */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR2,
+ PM2XXX_VPWR2_OVV_10);
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR1,
+ PM2XXX_VPWR1_OVV_10);
+
+ /* Input charger drop */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR2,
+ (PM2XXX_VPWR2_HW_OPT_DIS | PM2XXX_VPWR2_VALID_DIS |
+ PM2XXX_VPWR2_DROP_DIS));
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR1,
+ (PM2XXX_VPWR1_HW_OPT_DIS | PM2XXX_VPWR1_VALID_DIS |
+ PM2XXX_VPWR1_DROP_DIS));
+
+ /* Disable battery low monitoring */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_LOW_LEV_COMP_REG,
+ PM2XXX_VBAT_LOW_MONITORING_ENA);
+
+ return ret;
+}
+
+static int pm2xxx_charger_ac_en(struct ux500_charger *charger,
+ int enable, int vset, int iset)
+{
+ int ret;
+ int volt_index;
+ int curr_index;
+ u8 val;
+
+ struct pm2xxx_charger *pm2 = to_pm2xxx_charger_ac_device_info(charger);
+
+ if (enable) {
+ if (!pm2->ac.charger_connected) {
+ dev_dbg(pm2->dev, "AC charger not connected\n");
+ return -ENXIO;
+ }
+
+ dev_dbg(pm2->dev, "Enable AC: %dmV %dmA\n", vset, iset);
+ if (!pm2->vddadc_en_ac) {
+ ret = regulator_enable(pm2->regu);
+ if (ret)
+ dev_warn(pm2->dev,
+ "Failed to enable vddadc regulator\n");
+ else
+ pm2->vddadc_en_ac = true;
+ }
+
+ ret = pm2xxx_charging_init(pm2);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s charging init failed\n",
+ __func__);
+ goto error_occured;
+ }
+
+ volt_index = pm2xxx_voltage_to_regval(vset);
+ curr_index = pm2xxx_current_to_regval(iset);
+
+ if (volt_index < 0 || curr_index < 0) {
+ dev_err(pm2->dev,
+ "Charger voltage or current too high, "
+ "charging not started\n");
+ return -ENXIO;
+ }
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG8, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ goto error_occured;
+ }
+ val &= ~PM2XXX_CH_VOLT_MASK;
+ val |= volt_index;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8, val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ goto error_occured;
+ }
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ goto error_occured;
+ }
+ val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK;
+ val |= curr_index;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ goto error_occured;
+ }
+
+ if (!pm2->bat->enable_overshoot) {
+ ret = pm2xxx_reg_read(pm2, PM2XXX_LED_CTRL_REG, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n",
+ __func__);
+ goto error_occured;
+ }
+ val |= PM2XXX_ANTI_OVERSHOOT_EN;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_LED_CTRL_REG, val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n",
+ __func__);
+ goto error_occured;
+ }
+ }
+
+ ret = pm2xxx_charging_enable_mngt(pm2);
+ if (ret < 0) {
+ dev_err(pm2->dev, "Failed to enable"
+ "pm2xxx ac charger\n");
+ goto error_occured;
+ }
+
+ pm2->ac.charger_online = 1;
+ } else {
+ pm2->ac.charger_online = 0;
+ pm2->ac.wd_expired = false;
+
+ /* Disable regulator if enabled */
+ if (pm2->vddadc_en_ac) {
+ regulator_disable(pm2->regu);
+ pm2->vddadc_en_ac = false;
+ }
+
+ ret = pm2xxx_charging_disable_mngt(pm2);
+ if (ret < 0) {
+ dev_err(pm2->dev, "failed to disable"
+ "pm2xxx ac charger\n");
+ goto error_occured;
+ }
+
+ dev_dbg(pm2->dev, "PM2301: " "Disabled AC charging\n");
+ }
+ power_supply_changed(&pm2->ac_chg.psy);
+
+error_occured:
+ return ret;
+}
+
+static int pm2xxx_charger_watchdog_kick(struct ux500_charger *charger)
+{
+ int ret;
+ struct pm2xxx_charger *pm2;
+
+ if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
+ pm2 = to_pm2xxx_charger_ac_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_WD_KICK, WD_TIMER);
+ if (ret)
+ dev_err(pm2->dev, "Failed to kick WD!\n");
+
+ return ret;
+}
+
+static void pm2xxx_charger_ac_work(struct work_struct *work)
+{
+ struct pm2xxx_charger *pm2 = container_of(work,
+ struct pm2xxx_charger, ac_work);
+
+
+ power_supply_changed(&pm2->ac_chg.psy);
+ sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present");
+};
+
+static void pm2xxx_charger_check_hw_failure_work(struct work_struct *work)
+{
+ u8 reg_value;
+
+ struct pm2xxx_charger *pm2 = container_of(work,
+ struct pm2xxx_charger, check_hw_failure_work.work);
+
+ if (pm2->flags.ovv) {
+ pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &reg_value);
+
+ if (!(reg_value & (PM2XXX_INT4_S_ITVPWR1OVV |
+ PM2XXX_INT4_S_ITVPWR2OVV))) {
+ pm2->flags.ovv = false;
+ power_supply_changed(&pm2->ac_chg.psy);
+ }
+ }
+
+ /* If we still have a failure, schedule a new check */
+ if (pm2->flags.ovv) {
+ queue_delayed_work(pm2->charger_wq,
+ &pm2->check_hw_failure_work, round_jiffies(HZ));
+ }
+}
+
+static void pm2xxx_charger_check_main_thermal_prot_work(
+ struct work_struct *work)
+{
+ int ret;
+ u8 val;
+
+ struct pm2xxx_charger *pm2 = container_of(work, struct pm2xxx_charger,
+ check_main_thermal_prot_work);
+
+ /* Check if die temp warning is still active */
+ ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT5, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ return;
+ }
+ if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGRISE
+ | PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE))
+ pm2->flags.main_thermal_prot = true;
+ else if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGFALL
+ | PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL))
+ pm2->flags.main_thermal_prot = false;
+
+ power_supply_changed(&pm2->ac_chg.psy);
+}
+
+static struct pm2xxx_interrupts pm2xxx_int = {
+ .handler[0] = pm2_int_reg0,
+ .handler[1] = pm2_int_reg1,
+ .handler[2] = pm2_int_reg2,
+ .handler[3] = pm2_int_reg3,
+ .handler[4] = pm2_int_reg4,
+ .handler[5] = pm2_int_reg5,
+};
+
+static struct pm2xxx_irq pm2xxx_charger_irq[] = {
+ {"PM2XXX_IRQ_INT", pm2xxx_irq_int},
+};
+
+#ifdef CONFIG_PM
+
+#ifdef CONFIG_PM_SLEEP
+
+static int pm2xxx_wall_charger_resume(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+ struct pm2xxx_charger *pm2;
+
+ pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client);
+ set_lpn_pin(pm2);
+
+ /* If we still have a HW failure, schedule a new check */
+ if (pm2->flags.ovv)
+ queue_delayed_work(pm2->charger_wq,
+ &pm2->check_hw_failure_work, 0);
+
+ return 0;
+}
+
+static int pm2xxx_wall_charger_suspend(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+ struct pm2xxx_charger *pm2;
+
+ pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client);
+ clear_lpn_pin(pm2);
+
+ /* Cancel any pending HW failure check */
+ if (delayed_work_pending(&pm2->check_hw_failure_work))
+ cancel_delayed_work(&pm2->check_hw_failure_work);
+
+ flush_work(&pm2->ac_work);
+ flush_work(&pm2->check_main_thermal_prot_work);
+
+ return 0;
+}
+
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+
+static int pm2xxx_runtime_suspend(struct device *dev)
+{
+ struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev);
+ struct pm2xxx_charger *pm2;
+
+ pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client);
+ clear_lpn_pin(pm2);
+
+ return 0;
+}
+
+static int pm2xxx_runtime_resume(struct device *dev)
+{
+ struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev);
+ struct pm2xxx_charger *pm2;
+
+ pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client);
+
+ if (gpio_is_valid(pm2->lpn_pin) && gpio_get_value(pm2->lpn_pin) == 0)
+ set_lpn_pin(pm2);
+
+ return 0;
+}
+
+#endif
+
+static const struct dev_pm_ops pm2xxx_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm2xxx_wall_charger_suspend,
+ pm2xxx_wall_charger_resume)
+ SET_RUNTIME_PM_OPS(pm2xxx_runtime_suspend, pm2xxx_runtime_resume, NULL)
+};
+#define PM2XXX_PM_OPS (&pm2xxx_pm_ops)
+#else
+#define PM2XXX_PM_OPS NULL
+#endif
+
+static int pm2xxx_wall_charger_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct pm2xxx_platform_data *pl_data = i2c_client->dev.platform_data;
+ struct pm2xxx_charger *pm2;
+ int ret = 0;
+ u8 val;
+ int i;
+
+ if (!pl_data) {
+ dev_err(&i2c_client->dev, "No platform data supplied\n");
+ return -EINVAL;
+ }
+
+ pm2 = kzalloc(sizeof(struct pm2xxx_charger), GFP_KERNEL);
+ if (!pm2) {
+ dev_err(&i2c_client->dev, "pm2xxx_charger allocation failed\n");
+ return -ENOMEM;
+ }
+
+ /* get parent data */
+ pm2->dev = &i2c_client->dev;
+
+ pm2->pm2_int = &pm2xxx_int;
+
+ /* get charger spcific platform data */
+ if (!pl_data->wall_charger) {
+ dev_err(pm2->dev, "no charger platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ pm2->pdata = pl_data->wall_charger;
+
+ /* get battery specific platform data */
+ if (!pl_data->battery) {
+ dev_err(pm2->dev, "no battery platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ pm2->bat = pl_data->battery;
+
+ if (!i2c_check_functionality(i2c_client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_WORD_DATA)) {
+ ret = -ENODEV;
+ dev_info(pm2->dev, "pm2301 i2c_check_functionality failed\n");
+ goto free_device_info;
+ }
+
+ pm2->config.pm2xxx_i2c = i2c_client;
+ pm2->config.pm2xxx_id = (struct i2c_device_id *) id;
+ i2c_set_clientdata(i2c_client, pm2);
+
+ /* AC supply */
+ /* power_supply base class */
+ pm2->ac_chg.psy.name = pm2->pdata->label;
+ pm2->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS;
+ pm2->ac_chg.psy.properties = pm2xxx_charger_ac_props;
+ pm2->ac_chg.psy.num_properties = ARRAY_SIZE(pm2xxx_charger_ac_props);
+ pm2->ac_chg.psy.get_property = pm2xxx_charger_ac_get_property;
+ pm2->ac_chg.psy.supplied_to = pm2->pdata->supplied_to;
+ pm2->ac_chg.psy.num_supplicants = pm2->pdata->num_supplicants;
+ /* pm2xxx_charger sub-class */
+ pm2->ac_chg.ops.enable = &pm2xxx_charger_ac_en;
+ pm2->ac_chg.ops.kick_wd = &pm2xxx_charger_watchdog_kick;
+ pm2->ac_chg.ops.update_curr = &pm2xxx_charger_update_charger_current;
+ pm2->ac_chg.max_out_volt = pm2xxx_charger_voltage_map[
+ ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1];
+ pm2->ac_chg.max_out_curr = pm2xxx_charger_current_map[
+ ARRAY_SIZE(pm2xxx_charger_current_map) - 1];
+ pm2->ac_chg.wdt_refresh = WD_KICK_INTERVAL;
+ pm2->ac_chg.enabled = true;
+ pm2->ac_chg.external = true;
+
+ /* Create a work queue for the charger */
+ pm2->charger_wq = create_singlethread_workqueue("pm2xxx_charger_wq");
+ if (pm2->charger_wq == NULL) {
+ ret = -ENOMEM;
+ dev_err(pm2->dev, "failed to create work queue\n");
+ goto free_device_info;
+ }
+
+ /* Init work for charger detection */
+ INIT_WORK(&pm2->ac_work, pm2xxx_charger_ac_work);
+
+ /* Init work for checking HW status */
+ INIT_WORK(&pm2->check_main_thermal_prot_work,
+ pm2xxx_charger_check_main_thermal_prot_work);
+
+ /* Init work for HW failure check */
+ INIT_DEFERRABLE_WORK(&pm2->check_hw_failure_work,
+ pm2xxx_charger_check_hw_failure_work);
+
+ /*
+ * VDD ADC supply needs to be enabled from this driver when there
+ * is a charger connected to avoid erroneous BTEMP_HIGH/LOW
+ * interrupts during charging
+ */
+ pm2->regu = regulator_get(pm2->dev, "vddadc");
+ if (IS_ERR(pm2->regu)) {
+ ret = PTR_ERR(pm2->regu);
+ dev_err(pm2->dev, "failed to get vddadc regulator\n");
+ goto free_charger_wq;
+ }
+
+ /* Register AC charger class */
+ ret = power_supply_register(pm2->dev, &pm2->ac_chg.psy);
+ if (ret) {
+ dev_err(pm2->dev, "failed to register AC charger\n");
+ goto free_regulator;
+ }
+
+ /* Register interrupts */
+ ret = request_threaded_irq(gpio_to_irq(pm2->pdata->gpio_irq_number),
+ NULL,
+ pm2xxx_charger_irq[0].isr,
+ pm2->pdata->irq_type,
+ pm2xxx_charger_irq[0].name, pm2);
+
+ if (ret != 0) {
+ dev_err(pm2->dev, "failed to request %s IRQ %d: %d\n",
+ pm2xxx_charger_irq[0].name,
+ gpio_to_irq(pm2->pdata->gpio_irq_number), ret);
+ goto unregister_pm2xxx_charger;
+ }
+
+ ret = pm_runtime_set_active(pm2->dev);
+ if (ret)
+ dev_err(pm2->dev, "set active Error\n");
+
+ pm_runtime_enable(pm2->dev);
+ pm_runtime_set_autosuspend_delay(pm2->dev, PM2XXX_AUTOSUSPEND_DELAY);
+ pm_runtime_use_autosuspend(pm2->dev);
+ pm_runtime_resume(pm2->dev);
+
+ /* pm interrupt can wake up system */
+ ret = enable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number));
+ if (ret) {
+ dev_err(pm2->dev, "failed to set irq wake\n");
+ goto unregister_pm2xxx_interrupt;
+ }
+
+ mutex_init(&pm2->lock);
+
+ if (gpio_is_valid(pm2->pdata->lpn_gpio)) {
+ /* get lpn GPIO from platform data */
+ pm2->lpn_pin = pm2->pdata->lpn_gpio;
+
+ /*
+ * Charger detection mechanism requires pulling up the LPN pin
+ * while i2c communication if Charger is not connected
+ * LPN pin of PM2301 is GPIO60 of AB9540
+ */
+ ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio");
+
+ if (ret < 0) {
+ dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n");
+ goto disable_pm2_irq_wake;
+ }
+ ret = gpio_direction_output(pm2->lpn_pin, 0);
+ if (ret < 0) {
+ dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n");
+ goto free_gpio;
+ }
+ set_lpn_pin(pm2);
+ }
+
+ /* read interrupt registers */
+ for (i = 0; i < PM2XXX_NUM_INT_REG; i++)
+ pm2xxx_reg_read(pm2,
+ pm2xxx_interrupt_registers[i],
+ &val);
+
+ ret = pm2xxx_charger_detection(pm2, &val);
+
+ if ((ret == 0) && val) {
+ pm2->ac.charger_connected = 1;
+ ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON,
+ AB8500_MAIN_CH_DET);
+ pm2->ac_conn = true;
+ power_supply_changed(&pm2->ac_chg.psy);
+ sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present");
+ }
+
+ return 0;
+
+free_gpio:
+ if (gpio_is_valid(pm2->lpn_pin))
+ gpio_free(pm2->lpn_pin);
+disable_pm2_irq_wake:
+ disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number));
+unregister_pm2xxx_interrupt:
+ /* disable interrupt */
+ free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2);
+unregister_pm2xxx_charger:
+ /* unregister power supply */
+ power_supply_unregister(&pm2->ac_chg.psy);
+free_regulator:
+ /* disable the regulator */
+ regulator_put(pm2->regu);
+free_charger_wq:
+ destroy_workqueue(pm2->charger_wq);
+free_device_info:
+ kfree(pm2);
+
+ return ret;
+}
+
+static int pm2xxx_wall_charger_remove(struct i2c_client *i2c_client)
+{
+ struct pm2xxx_charger *pm2 = i2c_get_clientdata(i2c_client);
+
+ /* Disable pm_runtime */
+ pm_runtime_disable(pm2->dev);
+ /* Disable AC charging */
+ pm2xxx_charger_ac_en(&pm2->ac_chg, false, 0, 0);
+
+ /* Disable wake by pm interrupt */
+ disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number));
+
+ /* Disable interrupts */
+ free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2);
+
+ /* Delete the work queue */
+ destroy_workqueue(pm2->charger_wq);
+
+ flush_scheduled_work();
+
+ /* disable the regulator */
+ regulator_put(pm2->regu);
+
+ power_supply_unregister(&pm2->ac_chg.psy);
+
+ if (gpio_is_valid(pm2->lpn_pin))
+ gpio_free(pm2->lpn_pin);
+
+ kfree(pm2);
+
+ return 0;
+}
+
+static const struct i2c_device_id pm2xxx_id[] = {
+ { "pm2301", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, pm2xxx_id);
+
+static struct i2c_driver pm2xxx_charger_driver = {
+ .probe = pm2xxx_wall_charger_probe,
+ .remove = pm2xxx_wall_charger_remove,
+ .driver = {
+ .name = "pm2xxx-wall_charger",
+ .owner = THIS_MODULE,
+ .pm = PM2XXX_PM_OPS,
+ },
+ .id_table = pm2xxx_id,
+};
+
+static int __init pm2xxx_charger_init(void)
+{
+ return i2c_add_driver(&pm2xxx_charger_driver);
+}
+
+static void __exit pm2xxx_charger_exit(void)
+{
+ i2c_del_driver(&pm2xxx_charger_driver);
+}
+
+device_initcall_sync(pm2xxx_charger_init);
+module_exit(pm2xxx_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay");
+MODULE_ALIAS("i2c:pm2xxx-charger");
+MODULE_DESCRIPTION("PM2xxx charger management driver");
diff --git a/drivers/power/pm2301_charger.h b/drivers/power/pm2301_charger.h
new file mode 100644
index 00000000000..8ce3cc0195d
--- /dev/null
+++ b/drivers/power/pm2301_charger.h
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * PM2301 power supply interface
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef PM2301_CHARGER_H
+#define PM2301_CHARGER_H
+
+/* Watchdog timeout constant */
+#define WD_TIMER 0x30 /* 4min */
+#define WD_KICK_INTERVAL (30 * HZ)
+
+#define PM2XXX_NUM_INT_REG 0x6
+
+/* Constant voltage/current */
+#define PM2XXX_CONST_CURR 0x0
+#define PM2XXX_CONST_VOLT 0x1
+
+/* Lowest charger voltage is 3.39V -> 0x4E */
+#define LOW_VOLT_REG 0x4E
+
+#define PM2XXX_BATT_CTRL_REG1 0x00
+#define PM2XXX_BATT_CTRL_REG2 0x01
+#define PM2XXX_BATT_CTRL_REG3 0x02
+#define PM2XXX_BATT_CTRL_REG4 0x03
+#define PM2XXX_BATT_CTRL_REG5 0x04
+#define PM2XXX_BATT_CTRL_REG6 0x05
+#define PM2XXX_BATT_CTRL_REG7 0x06
+#define PM2XXX_BATT_CTRL_REG8 0x07
+#define PM2XXX_NTC_CTRL_REG1 0x08
+#define PM2XXX_NTC_CTRL_REG2 0x09
+#define PM2XXX_BATT_CTRL_REG9 0x0A
+#define PM2XXX_BATT_STAT_REG1 0x0B
+#define PM2XXX_INP_VOLT_VPWR2 0x11
+#define PM2XXX_INP_DROP_VPWR2 0x13
+#define PM2XXX_INP_VOLT_VPWR1 0x15
+#define PM2XXX_INP_DROP_VPWR1 0x17
+#define PM2XXX_INP_MODE_VPWR 0x18
+#define PM2XXX_BATT_WD_KICK 0x70
+#define PM2XXX_DEV_VER_STAT 0x0C
+#define PM2XXX_THERM_WARN_CTRL_REG 0x20
+#define PM2XXX_BATT_DISC_REG 0x21
+#define PM2XXX_BATT_LOW_LEV_COMP_REG 0x22
+#define PM2XXX_BATT_LOW_LEV_VAL_REG 0x23
+#define PM2XXX_I2C_PAD_CTRL_REG 0x24
+#define PM2XXX_SW_CTRL_REG 0x26
+#define PM2XXX_LED_CTRL_REG 0x28
+
+#define PM2XXX_REG_INT1 0x40
+#define PM2XXX_MASK_REG_INT1 0x50
+#define PM2XXX_SRCE_REG_INT1 0x60
+#define PM2XXX_REG_INT2 0x41
+#define PM2XXX_MASK_REG_INT2 0x51
+#define PM2XXX_SRCE_REG_INT2 0x61
+#define PM2XXX_REG_INT3 0x42
+#define PM2XXX_MASK_REG_INT3 0x52
+#define PM2XXX_SRCE_REG_INT3 0x62
+#define PM2XXX_REG_INT4 0x43
+#define PM2XXX_MASK_REG_INT4 0x53
+#define PM2XXX_SRCE_REG_INT4 0x63
+#define PM2XXX_REG_INT5 0x44
+#define PM2XXX_MASK_REG_INT5 0x54
+#define PM2XXX_SRCE_REG_INT5 0x64
+#define PM2XXX_REG_INT6 0x45
+#define PM2XXX_MASK_REG_INT6 0x55
+#define PM2XXX_SRCE_REG_INT6 0x65
+
+#define VPWR_OVV 0x0
+#define VSYSTEM_OVV 0x1
+
+/* control Reg 1 */
+#define PM2XXX_CH_RESUME_EN 0x1
+#define PM2XXX_CH_RESUME_DIS 0x0
+
+/* control Reg 2 */
+#define PM2XXX_CH_AUTO_RESUME_EN 0X2
+#define PM2XXX_CH_AUTO_RESUME_DIS 0X0
+#define PM2XXX_CHARGER_ENA 0x4
+#define PM2XXX_CHARGER_DIS 0x0
+
+/* control Reg 3 */
+#define PM2XXX_CH_WD_CC_PHASE_OFF 0x0
+#define PM2XXX_CH_WD_CC_PHASE_5MIN 0x1
+#define PM2XXX_CH_WD_CC_PHASE_10MIN 0x2
+#define PM2XXX_CH_WD_CC_PHASE_30MIN 0x3
+#define PM2XXX_CH_WD_CC_PHASE_60MIN 0x4
+#define PM2XXX_CH_WD_CC_PHASE_120MIN 0x5
+#define PM2XXX_CH_WD_CC_PHASE_240MIN 0x6
+#define PM2XXX_CH_WD_CC_PHASE_360MIN 0x7
+
+#define PM2XXX_CH_WD_CV_PHASE_OFF (0x0<<3)
+#define PM2XXX_CH_WD_CV_PHASE_5MIN (0x1<<3)
+#define PM2XXX_CH_WD_CV_PHASE_10MIN (0x2<<3)
+#define PM2XXX_CH_WD_CV_PHASE_30MIN (0x3<<3)
+#define PM2XXX_CH_WD_CV_PHASE_60MIN (0x4<<3)
+#define PM2XXX_CH_WD_CV_PHASE_120MIN (0x5<<3)
+#define PM2XXX_CH_WD_CV_PHASE_240MIN (0x6<<3)
+#define PM2XXX_CH_WD_CV_PHASE_360MIN (0x7<<3)
+
+/* control Reg 4 */
+#define PM2XXX_CH_WD_PRECH_PHASE_OFF 0x0
+#define PM2XXX_CH_WD_PRECH_PHASE_1MIN 0x1
+#define PM2XXX_CH_WD_PRECH_PHASE_5MIN 0x2
+#define PM2XXX_CH_WD_PRECH_PHASE_10MIN 0x3
+#define PM2XXX_CH_WD_PRECH_PHASE_30MIN 0x4
+#define PM2XXX_CH_WD_PRECH_PHASE_60MIN 0x5
+#define PM2XXX_CH_WD_PRECH_PHASE_120MIN 0x6
+#define PM2XXX_CH_WD_PRECH_PHASE_240MIN 0x7
+
+/* control Reg 5 */
+#define PM2XXX_CH_WD_AUTO_TIMEOUT_NONE 0x0
+#define PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN 0x1
+
+/* control Reg 6 */
+#define PM2XXX_DIR_CH_CC_CURRENT_MASK 0x0F
+#define PM2XXX_DIR_CH_CC_CURRENT_200MA 0x0
+#define PM2XXX_DIR_CH_CC_CURRENT_400MA 0x2
+#define PM2XXX_DIR_CH_CC_CURRENT_600MA 0x3
+#define PM2XXX_DIR_CH_CC_CURRENT_800MA 0x4
+#define PM2XXX_DIR_CH_CC_CURRENT_1000MA 0x5
+#define PM2XXX_DIR_CH_CC_CURRENT_1200MA 0x6
+#define PM2XXX_DIR_CH_CC_CURRENT_1400MA 0x7
+#define PM2XXX_DIR_CH_CC_CURRENT_1600MA 0x8
+#define PM2XXX_DIR_CH_CC_CURRENT_1800MA 0x9
+#define PM2XXX_DIR_CH_CC_CURRENT_2000MA 0xA
+#define PM2XXX_DIR_CH_CC_CURRENT_2200MA 0xB
+#define PM2XXX_DIR_CH_CC_CURRENT_2400MA 0xC
+#define PM2XXX_DIR_CH_CC_CURRENT_2600MA 0xD
+#define PM2XXX_DIR_CH_CC_CURRENT_2800MA 0xE
+#define PM2XXX_DIR_CH_CC_CURRENT_3000MA 0xF
+
+#define PM2XXX_CH_PRECH_CURRENT_MASK 0x30
+#define PM2XXX_CH_PRECH_CURRENT_25MA (0x0<<4)
+#define PM2XXX_CH_PRECH_CURRENT_50MA (0x1<<4)
+#define PM2XXX_CH_PRECH_CURRENT_75MA (0x2<<4)
+#define PM2XXX_CH_PRECH_CURRENT_100MA (0x3<<4)
+
+#define PM2XXX_CH_EOC_CURRENT_MASK 0xC0
+#define PM2XXX_CH_EOC_CURRENT_100MA (0x0<<6)
+#define PM2XXX_CH_EOC_CURRENT_150MA (0x1<<6)
+#define PM2XXX_CH_EOC_CURRENT_300MA (0x2<<6)
+#define PM2XXX_CH_EOC_CURRENT_400MA (0x3<<6)
+
+/* control Reg 7 */
+#define PM2XXX_CH_PRECH_VOL_2_5 0x0
+#define PM2XXX_CH_PRECH_VOL_2_7 0x1
+#define PM2XXX_CH_PRECH_VOL_2_9 0x2
+#define PM2XXX_CH_PRECH_VOL_3_1 0x3
+
+#define PM2XXX_CH_VRESUME_VOL_3_2 (0x0<<2)
+#define PM2XXX_CH_VRESUME_VOL_3_4 (0x1<<2)
+#define PM2XXX_CH_VRESUME_VOL_3_6 (0x2<<2)
+#define PM2XXX_CH_VRESUME_VOL_3_8 (0x3<<2)
+
+/* control Reg 8 */
+#define PM2XXX_CH_VOLT_MASK 0x3F
+#define PM2XXX_CH_VOLT_3_5 0x0
+#define PM2XXX_CH_VOLT_3_5225 0x1
+#define PM2XXX_CH_VOLT_3_6 0x4
+#define PM2XXX_CH_VOLT_3_7 0x8
+#define PM2XXX_CH_VOLT_4_0 0x14
+#define PM2XXX_CH_VOLT_4_175 0x1B
+#define PM2XXX_CH_VOLT_4_2 0x1C
+#define PM2XXX_CH_VOLT_4_275 0x1F
+#define PM2XXX_CH_VOLT_4_3 0x20
+
+/*NTC control register 1*/
+#define PM2XXX_BTEMP_HIGH_TH_45 0x0
+#define PM2XXX_BTEMP_HIGH_TH_50 0x1
+#define PM2XXX_BTEMP_HIGH_TH_55 0x2
+#define PM2XXX_BTEMP_HIGH_TH_60 0x3
+#define PM2XXX_BTEMP_HIGH_TH_65 0x4
+
+#define PM2XXX_BTEMP_LOW_TH_N5 (0x0<<3)
+#define PM2XXX_BTEMP_LOW_TH_0 (0x1<<3)
+#define PM2XXX_BTEMP_LOW_TH_5 (0x2<<3)
+#define PM2XXX_BTEMP_LOW_TH_10 (0x3<<3)
+
+/*NTC control register 2*/
+#define PM2XXX_NTC_BETA_COEFF_3477 0x0
+#define PM2XXX_NTC_BETA_COEFF_3964 0x1
+
+#define PM2XXX_NTC_RES_10K (0x0<<2)
+#define PM2XXX_NTC_RES_47K (0x1<<2)
+#define PM2XXX_NTC_RES_100K (0x2<<2)
+#define PM2XXX_NTC_RES_NO_NTC (0x3<<2)
+
+/* control Reg 9 */
+#define PM2XXX_CH_CC_MODEDROP_EN 1
+#define PM2XXX_CH_CC_MODEDROP_DIS 0
+
+#define PM2XXX_CH_CC_REDUCED_CURRENT_100MA (0x0<<1)
+#define PM2XXX_CH_CC_REDUCED_CURRENT_200MA (0x1<<1)
+#define PM2XXX_CH_CC_REDUCED_CURRENT_400MA (0x2<<1)
+#define PM2XXX_CH_CC_REDUCED_CURRENT_IDENT (0x3<<1)
+
+#define PM2XXX_CHARCHING_INFO_DIS (0<<3)
+#define PM2XXX_CHARCHING_INFO_EN (1<<3)
+
+#define PM2XXX_CH_150MV_DROP_300MV (0<<4)
+#define PM2XXX_CH_150MV_DROP_150MV (1<<4)
+
+
+/* charger status register */
+#define PM2XXX_CHG_STATUS_OFF 0x0
+#define PM2XXX_CHG_STATUS_ON 0x1
+#define PM2XXX_CHG_STATUS_FULL 0x2
+#define PM2XXX_CHG_STATUS_ERR 0x3
+#define PM2XXX_CHG_STATUS_WAIT 0x4
+#define PM2XXX_CHG_STATUS_NOBAT 0x5
+
+/* Input charger voltage VPWR2 */
+#define PM2XXX_VPWR2_OVV_6_0 0x0
+#define PM2XXX_VPWR2_OVV_6_3 0x1
+#define PM2XXX_VPWR2_OVV_10 0x2
+#define PM2XXX_VPWR2_OVV_NONE 0x3
+
+/* Input charger drop VPWR2 */
+#define PM2XXX_VPWR2_HW_OPT_EN (0x1<<4)
+#define PM2XXX_VPWR2_HW_OPT_DIS (0x0<<4)
+
+#define PM2XXX_VPWR2_VALID_EN (0x1<<3)
+#define PM2XXX_VPWR2_VALID_DIS (0x0<<3)
+
+#define PM2XXX_VPWR2_DROP_EN (0x1<<2)
+#define PM2XXX_VPWR2_DROP_DIS (0x0<<2)
+
+/* Input charger voltage VPWR1 */
+#define PM2XXX_VPWR1_OVV_6_0 0x0
+#define PM2XXX_VPWR1_OVV_6_3 0x1
+#define PM2XXX_VPWR1_OVV_10 0x2
+#define PM2XXX_VPWR1_OVV_NONE 0x3
+
+/* Input charger drop VPWR1 */
+#define PM2XXX_VPWR1_HW_OPT_EN (0x1<<4)
+#define PM2XXX_VPWR1_HW_OPT_DIS (0x0<<4)
+
+#define PM2XXX_VPWR1_VALID_EN (0x1<<3)
+#define PM2XXX_VPWR1_VALID_DIS (0x0<<3)
+
+#define PM2XXX_VPWR1_DROP_EN (0x1<<2)
+#define PM2XXX_VPWR1_DROP_DIS (0x0<<2)
+
+/* Battery low level comparator control register */
+#define PM2XXX_VBAT_LOW_MONITORING_DIS 0x0
+#define PM2XXX_VBAT_LOW_MONITORING_ENA 0x1
+
+/* Battery low level value control register */
+#define PM2XXX_VBAT_LOW_LEVEL_2_3 0x0
+#define PM2XXX_VBAT_LOW_LEVEL_2_4 0x1
+#define PM2XXX_VBAT_LOW_LEVEL_2_5 0x2
+#define PM2XXX_VBAT_LOW_LEVEL_2_6 0x3
+#define PM2XXX_VBAT_LOW_LEVEL_2_7 0x4
+#define PM2XXX_VBAT_LOW_LEVEL_2_8 0x5
+#define PM2XXX_VBAT_LOW_LEVEL_2_9 0x6
+#define PM2XXX_VBAT_LOW_LEVEL_3_0 0x7
+#define PM2XXX_VBAT_LOW_LEVEL_3_1 0x8
+#define PM2XXX_VBAT_LOW_LEVEL_3_2 0x9
+#define PM2XXX_VBAT_LOW_LEVEL_3_3 0xA
+#define PM2XXX_VBAT_LOW_LEVEL_3_4 0xB
+#define PM2XXX_VBAT_LOW_LEVEL_3_5 0xC
+#define PM2XXX_VBAT_LOW_LEVEL_3_6 0xD
+#define PM2XXX_VBAT_LOW_LEVEL_3_7 0xE
+#define PM2XXX_VBAT_LOW_LEVEL_3_8 0xF
+#define PM2XXX_VBAT_LOW_LEVEL_3_9 0x10
+#define PM2XXX_VBAT_LOW_LEVEL_4_0 0x11
+#define PM2XXX_VBAT_LOW_LEVEL_4_1 0x12
+#define PM2XXX_VBAT_LOW_LEVEL_4_2 0x13
+
+/* SW CTRL */
+#define PM2XXX_SWCTRL_HW 0x0
+#define PM2XXX_SWCTRL_SW 0x1
+
+
+/* LED Driver Control */
+#define PM2XXX_LED_CURRENT_MASK 0x0C
+#define PM2XXX_LED_CURRENT_2_5MA (0X0<<2)
+#define PM2XXX_LED_CURRENT_1MA (0X1<<2)
+#define PM2XXX_LED_CURRENT_5MA (0X2<<2)
+#define PM2XXX_LED_CURRENT_10MA (0X3<<2)
+
+#define PM2XXX_LED_SELECT_MASK 0x02
+#define PM2XXX_LED_SELECT_EN (0X0<<1)
+#define PM2XXX_LED_SELECT_DIS (0X1<<1)
+
+#define PM2XXX_ANTI_OVERSHOOT_MASK 0x01
+#define PM2XXX_ANTI_OVERSHOOT_DIS 0X0
+#define PM2XXX_ANTI_OVERSHOOT_EN 0X1
+
+enum pm2xxx_reg_int1 {
+ PM2XXX_INT1_ITVBATDISCONNECT = 0x02,
+ PM2XXX_INT1_ITVBATLOWR = 0x04,
+ PM2XXX_INT1_ITVBATLOWF = 0x08,
+};
+
+enum pm2xxx_mask_reg_int1 {
+ PM2XXX_INT1_M_ITVBATDISCONNECT = 0x02,
+ PM2XXX_INT1_M_ITVBATLOWR = 0x04,
+ PM2XXX_INT1_M_ITVBATLOWF = 0x08,
+};
+
+enum pm2xxx_source_reg_int1 {
+ PM2XXX_INT1_S_ITVBATDISCONNECT = 0x02,
+ PM2XXX_INT1_S_ITVBATLOWR = 0x04,
+ PM2XXX_INT1_S_ITVBATLOWF = 0x08,
+};
+
+enum pm2xxx_reg_int2 {
+ PM2XXX_INT2_ITVPWR2PLUG = 0x01,
+ PM2XXX_INT2_ITVPWR2UNPLUG = 0x02,
+ PM2XXX_INT2_ITVPWR1PLUG = 0x04,
+ PM2XXX_INT2_ITVPWR1UNPLUG = 0x08,
+};
+
+enum pm2xxx_mask_reg_int2 {
+ PM2XXX_INT2_M_ITVPWR2PLUG = 0x01,
+ PM2XXX_INT2_M_ITVPWR2UNPLUG = 0x02,
+ PM2XXX_INT2_M_ITVPWR1PLUG = 0x04,
+ PM2XXX_INT2_M_ITVPWR1UNPLUG = 0x08,
+};
+
+enum pm2xxx_source_reg_int2 {
+ PM2XXX_INT2_S_ITVPWR2PLUG = 0x03,
+ PM2XXX_INT2_S_ITVPWR1PLUG = 0x0c,
+};
+
+enum pm2xxx_reg_int3 {
+ PM2XXX_INT3_ITCHPRECHARGEWD = 0x01,
+ PM2XXX_INT3_ITCHCCWD = 0x02,
+ PM2XXX_INT3_ITCHCVWD = 0x04,
+ PM2XXX_INT3_ITAUTOTIMEOUTWD = 0x08,
+};
+
+enum pm2xxx_mask_reg_int3 {
+ PM2XXX_INT3_M_ITCHPRECHARGEWD = 0x01,
+ PM2XXX_INT3_M_ITCHCCWD = 0x02,
+ PM2XXX_INT3_M_ITCHCVWD = 0x04,
+ PM2XXX_INT3_M_ITAUTOTIMEOUTWD = 0x08,
+};
+
+enum pm2xxx_source_reg_int3 {
+ PM2XXX_INT3_S_ITCHPRECHARGEWD = 0x01,
+ PM2XXX_INT3_S_ITCHCCWD = 0x02,
+ PM2XXX_INT3_S_ITCHCVWD = 0x04,
+ PM2XXX_INT3_S_ITAUTOTIMEOUTWD = 0x08,
+};
+
+enum pm2xxx_reg_int4 {
+ PM2XXX_INT4_ITBATTEMPCOLD = 0x01,
+ PM2XXX_INT4_ITBATTEMPHOT = 0x02,
+ PM2XXX_INT4_ITVPWR2OVV = 0x04,
+ PM2XXX_INT4_ITVPWR1OVV = 0x08,
+ PM2XXX_INT4_ITCHARGINGON = 0x10,
+ PM2XXX_INT4_ITVRESUME = 0x20,
+ PM2XXX_INT4_ITBATTFULL = 0x40,
+ PM2XXX_INT4_ITCVPHASE = 0x80,
+};
+
+enum pm2xxx_mask_reg_int4 {
+ PM2XXX_INT4_M_ITBATTEMPCOLD = 0x01,
+ PM2XXX_INT4_M_ITBATTEMPHOT = 0x02,
+ PM2XXX_INT4_M_ITVPWR2OVV = 0x04,
+ PM2XXX_INT4_M_ITVPWR1OVV = 0x08,
+ PM2XXX_INT4_M_ITCHARGINGON = 0x10,
+ PM2XXX_INT4_M_ITVRESUME = 0x20,
+ PM2XXX_INT4_M_ITBATTFULL = 0x40,
+ PM2XXX_INT4_M_ITCVPHASE = 0x80,
+};
+
+enum pm2xxx_source_reg_int4 {
+ PM2XXX_INT4_S_ITBATTEMPCOLD = 0x01,
+ PM2XXX_INT4_S_ITBATTEMPHOT = 0x02,
+ PM2XXX_INT4_S_ITVPWR2OVV = 0x04,
+ PM2XXX_INT4_S_ITVPWR1OVV = 0x08,
+ PM2XXX_INT4_S_ITCHARGINGON = 0x10,
+ PM2XXX_INT4_S_ITVRESUME = 0x20,
+ PM2XXX_INT4_S_ITBATTFULL = 0x40,
+ PM2XXX_INT4_S_ITCVPHASE = 0x80,
+};
+
+enum pm2xxx_reg_int5 {
+ PM2XXX_INT5_ITTHERMALSHUTDOWNRISE = 0x01,
+ PM2XXX_INT5_ITTHERMALSHUTDOWNFALL = 0x02,
+ PM2XXX_INT5_ITTHERMALWARNINGRISE = 0x04,
+ PM2XXX_INT5_ITTHERMALWARNINGFALL = 0x08,
+ PM2XXX_INT5_ITVSYSTEMOVV = 0x10,
+};
+
+enum pm2xxx_mask_reg_int5 {
+ PM2XXX_INT5_M_ITTHERMALSHUTDOWNRISE = 0x01,
+ PM2XXX_INT5_M_ITTHERMALSHUTDOWNFALL = 0x02,
+ PM2XXX_INT5_M_ITTHERMALWARNINGRISE = 0x04,
+ PM2XXX_INT5_M_ITTHERMALWARNINGFALL = 0x08,
+ PM2XXX_INT5_M_ITVSYSTEMOVV = 0x10,
+};
+
+enum pm2xxx_source_reg_int5 {
+ PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE = 0x01,
+ PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL = 0x02,
+ PM2XXX_INT5_S_ITTHERMALWARNINGRISE = 0x04,
+ PM2XXX_INT5_S_ITTHERMALWARNINGFALL = 0x08,
+ PM2XXX_INT5_S_ITVSYSTEMOVV = 0x10,
+};
+
+enum pm2xxx_reg_int6 {
+ PM2XXX_INT6_ITVPWR2DROP = 0x01,
+ PM2XXX_INT6_ITVPWR1DROP = 0x02,
+ PM2XXX_INT6_ITVPWR2VALIDRISE = 0x04,
+ PM2XXX_INT6_ITVPWR2VALIDFALL = 0x08,
+ PM2XXX_INT6_ITVPWR1VALIDRISE = 0x10,
+ PM2XXX_INT6_ITVPWR1VALIDFALL = 0x20,
+};
+
+enum pm2xxx_mask_reg_int6 {
+ PM2XXX_INT6_M_ITVPWR2DROP = 0x01,
+ PM2XXX_INT6_M_ITVPWR1DROP = 0x02,
+ PM2XXX_INT6_M_ITVPWR2VALIDRISE = 0x04,
+ PM2XXX_INT6_M_ITVPWR2VALIDFALL = 0x08,
+ PM2XXX_INT6_M_ITVPWR1VALIDRISE = 0x10,
+ PM2XXX_INT6_M_ITVPWR1VALIDFALL = 0x20,
+};
+
+enum pm2xxx_source_reg_int6 {
+ PM2XXX_INT6_S_ITVPWR2DROP = 0x01,
+ PM2XXX_INT6_S_ITVPWR1DROP = 0x02,
+ PM2XXX_INT6_S_ITVPWR2VALIDRISE = 0x04,
+ PM2XXX_INT6_S_ITVPWR2VALIDFALL = 0x08,
+ PM2XXX_INT6_S_ITVPWR1VALIDRISE = 0x10,
+ PM2XXX_INT6_S_ITVPWR1VALIDFALL = 0x20,
+};
+
+struct pm2xxx_charger_info {
+ int charger_connected;
+ int charger_online;
+ int cv_active;
+ bool wd_expired;
+};
+
+struct pm2xxx_charger_event_flags {
+ bool mainextchnotok;
+ bool main_thermal_prot;
+ bool ovv;
+ bool chgwdexp;
+};
+
+struct pm2xxx_interrupts {
+ u8 reg[PM2XXX_NUM_INT_REG];
+ int (*handler[PM2XXX_NUM_INT_REG])(void *, int);
+};
+
+struct pm2xxx_config {
+ struct i2c_client *pm2xxx_i2c;
+ struct i2c_device_id *pm2xxx_id;
+};
+
+struct pm2xxx_irq {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct pm2xxx_charger {
+ struct device *dev;
+ u8 chip_id;
+ bool vddadc_en_ac;
+ struct pm2xxx_config config;
+ bool ac_conn;
+ unsigned int gpio_irq;
+ int vbat;
+ int old_vbat;
+ int failure_case;
+ int failure_input_ovv;
+ unsigned int lpn_pin;
+ struct pm2xxx_interrupts *pm2_int;
+ struct regulator *regu;
+ struct pm2xxx_bm_data *bat;
+ struct mutex lock;
+ struct ab8500 *parent;
+ struct pm2xxx_charger_info ac;
+ struct pm2xxx_charger_platform_data *pdata;
+ struct workqueue_struct *charger_wq;
+ struct delayed_work check_vbat_work;
+ struct work_struct ac_work;
+ struct work_struct check_main_thermal_prot_work;
+ struct delayed_work check_hw_failure_work;
+ struct ux500_charger ac_chg;
+ struct pm2xxx_charger_event_flags flags;
+};
+
+#endif /* PM2301_CHARGER_H */
diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c
index 8a7cfb3cc16..5a5a24e7d43 100644
--- a/drivers/power/power_supply_core.c
+++ b/drivers/power/power_supply_core.c
@@ -15,6 +15,7 @@
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/device.h>
+#include <linux/notifier.h>
#include <linux/err.h>
#include <linux/power_supply.h>
#include <linux/thermal.h>
@@ -24,61 +25,243 @@
struct class *power_supply_class;
EXPORT_SYMBOL_GPL(power_supply_class);
+ATOMIC_NOTIFIER_HEAD(power_supply_notifier);
+EXPORT_SYMBOL_GPL(power_supply_notifier);
+
static struct device_type power_supply_dev_type;
+static bool __power_supply_is_supplied_by(struct power_supply *supplier,
+ struct power_supply *supply)
+{
+ int i;
+
+ if (!supply->supplied_from && !supplier->supplied_to)
+ return false;
+
+ /* Support both supplied_to and supplied_from modes */
+ if (supply->supplied_from) {
+ if (!supplier->name)
+ return false;
+ for (i = 0; i < supply->num_supplies; i++)
+ if (!strcmp(supplier->name, supply->supplied_from[i]))
+ return true;
+ } else {
+ if (!supply->name)
+ return false;
+ for (i = 0; i < supplier->num_supplicants; i++)
+ if (!strcmp(supplier->supplied_to[i], supply->name))
+ return true;
+ }
+
+ return false;
+}
+
static int __power_supply_changed_work(struct device *dev, void *data)
{
struct power_supply *psy = (struct power_supply *)data;
struct power_supply *pst = dev_get_drvdata(dev);
- int i;
- for (i = 0; i < psy->num_supplicants; i++)
- if (!strcmp(psy->supplied_to[i], pst->name)) {
- if (pst->external_power_changed)
- pst->external_power_changed(pst);
- }
+ if (__power_supply_is_supplied_by(psy, pst)) {
+ if (pst->external_power_changed)
+ pst->external_power_changed(pst);
+ }
+
return 0;
}
static void power_supply_changed_work(struct work_struct *work)
{
+ unsigned long flags;
struct power_supply *psy = container_of(work, struct power_supply,
changed_work);
dev_dbg(psy->dev, "%s\n", __func__);
- class_for_each_device(power_supply_class, NULL, psy,
- __power_supply_changed_work);
-
- power_supply_update_leds(psy);
-
- kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ if (psy->changed) {
+ psy->changed = false;
+ spin_unlock_irqrestore(&psy->changed_lock, flags);
+ class_for_each_device(power_supply_class, NULL, psy,
+ __power_supply_changed_work);
+ power_supply_update_leds(psy);
+ atomic_notifier_call_chain(&power_supply_notifier,
+ PSY_EVENT_PROP_CHANGED, psy);
+ kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ }
+ /*
+ * Dependent power supplies (e.g. battery) may have changed state
+ * as a result of this event, so poll again and hold the
+ * wakeup_source until all events are processed.
+ */
+ if (!psy->changed)
+ pm_relax(psy->dev);
+ spin_unlock_irqrestore(&psy->changed_lock, flags);
}
void power_supply_changed(struct power_supply *psy)
{
+ unsigned long flags;
+
dev_dbg(psy->dev, "%s\n", __func__);
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ psy->changed = true;
+ pm_stay_awake(psy->dev);
+ spin_unlock_irqrestore(&psy->changed_lock, flags);
schedule_work(&psy->changed_work);
}
EXPORT_SYMBOL_GPL(power_supply_changed);
+#ifdef CONFIG_OF
+#include <linux/of.h>
+
+static int __power_supply_populate_supplied_from(struct device *dev,
+ void *data)
+{
+ struct power_supply *psy = (struct power_supply *)data;
+ struct power_supply *epsy = dev_get_drvdata(dev);
+ struct device_node *np;
+ int i = 0;
+
+ do {
+ np = of_parse_phandle(psy->of_node, "power-supplies", i++);
+ if (!np)
+ continue;
+
+ if (np == epsy->of_node) {
+ dev_info(psy->dev, "%s: Found supply : %s\n",
+ psy->name, epsy->name);
+ psy->supplied_from[i-1] = (char *)epsy->name;
+ psy->num_supplies++;
+ of_node_put(np);
+ break;
+ }
+ of_node_put(np);
+ } while (np);
+
+ return 0;
+}
+
+static int power_supply_populate_supplied_from(struct power_supply *psy)
+{
+ int error;
+
+ error = class_for_each_device(power_supply_class, NULL, psy,
+ __power_supply_populate_supplied_from);
+
+ dev_dbg(psy->dev, "%s %d\n", __func__, error);
+
+ return error;
+}
+
+static int __power_supply_find_supply_from_node(struct device *dev,
+ void *data)
+{
+ struct device_node *np = (struct device_node *)data;
+ struct power_supply *epsy = dev_get_drvdata(dev);
+
+ /* return error breaks out of class_for_each_device loop */
+ if (epsy->of_node == np)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int power_supply_find_supply_from_node(struct device_node *supply_node)
+{
+ int error;
+ struct device *dev;
+ struct class_dev_iter iter;
+
+ /*
+ * Use iterator to see if any other device is registered.
+ * This is required since class_for_each_device returns 0
+ * if there are no devices registered.
+ */
+ class_dev_iter_init(&iter, power_supply_class, NULL, NULL);
+ dev = class_dev_iter_next(&iter);
+
+ if (!dev)
+ return -EPROBE_DEFER;
+
+ /*
+ * We have to treat the return value as inverted, because if
+ * we return error on not found, then it won't continue looking.
+ * So we trick it by returning error on success to stop looking
+ * once the matching device is found.
+ */
+ error = class_for_each_device(power_supply_class, NULL, supply_node,
+ __power_supply_find_supply_from_node);
+
+ return error ? 0 : -EPROBE_DEFER;
+}
+
+static int power_supply_check_supplies(struct power_supply *psy)
+{
+ struct device_node *np;
+ int cnt = 0;
+
+ /* If there is already a list honor it */
+ if (psy->supplied_from && psy->num_supplies > 0)
+ return 0;
+
+ /* No device node found, nothing to do */
+ if (!psy->of_node)
+ return 0;
+
+ do {
+ int ret;
+
+ np = of_parse_phandle(psy->of_node, "power-supplies", cnt++);
+ if (!np)
+ continue;
+
+ ret = power_supply_find_supply_from_node(np);
+ if (ret) {
+ dev_dbg(psy->dev, "Failed to find supply, defer!\n");
+ of_node_put(np);
+ return -EPROBE_DEFER;
+ }
+ of_node_put(np);
+ } while (np);
+
+ /* All supplies found, allocate char ** array for filling */
+ psy->supplied_from = devm_kzalloc(psy->dev, sizeof(psy->supplied_from),
+ GFP_KERNEL);
+ if (!psy->supplied_from) {
+ dev_err(psy->dev, "Couldn't allocate memory for supply list\n");
+ return -ENOMEM;
+ }
+
+ *psy->supplied_from = devm_kzalloc(psy->dev, sizeof(char *) * cnt,
+ GFP_KERNEL);
+ if (!*psy->supplied_from) {
+ dev_err(psy->dev, "Couldn't allocate memory for supply list\n");
+ return -ENOMEM;
+ }
+
+ return power_supply_populate_supplied_from(psy);
+}
+#else
+static inline int power_supply_check_supplies(struct power_supply *psy)
+{
+ return 0;
+}
+#endif
+
static int __power_supply_am_i_supplied(struct device *dev, void *data)
{
union power_supply_propval ret = {0,};
struct power_supply *psy = (struct power_supply *)data;
struct power_supply *epsy = dev_get_drvdata(dev);
- int i;
- for (i = 0; i < epsy->num_supplicants; i++) {
- if (!strcmp(epsy->supplied_to[i], psy->name)) {
- if (epsy->get_property(epsy,
- POWER_SUPPLY_PROP_ONLINE, &ret))
- continue;
+ if (__power_supply_is_supplied_by(epsy, psy))
+ if (!epsy->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, &ret)) {
if (ret.intval)
return ret.intval;
}
- }
+
return 0;
}
@@ -141,7 +324,7 @@ int power_supply_set_battery_charged(struct power_supply *psy)
}
EXPORT_SYMBOL_GPL(power_supply_set_battery_charged);
-static int power_supply_match_device_by_name(struct device *dev, void *data)
+static int power_supply_match_device_by_name(struct device *dev, const void *data)
{
const char *name = data;
struct power_supply *psy = dev_get_drvdata(dev);
@@ -149,7 +332,7 @@ static int power_supply_match_device_by_name(struct device *dev, void *data)
return strcmp(psy->name, name) == 0;
}
-struct power_supply *power_supply_get_by_name(char *name)
+struct power_supply *power_supply_get_by_name(const char *name)
{
struct device *dev = class_find_device(power_supply_class, NULL, name,
power_supply_match_device_by_name);
@@ -158,6 +341,32 @@ struct power_supply *power_supply_get_by_name(char *name)
}
EXPORT_SYMBOL_GPL(power_supply_get_by_name);
+#ifdef CONFIG_OF
+static int power_supply_match_device_node(struct device *dev, const void *data)
+{
+ return dev->parent && dev->parent->of_node == data;
+}
+
+struct power_supply *power_supply_get_by_phandle(struct device_node *np,
+ const char *property)
+{
+ struct device_node *power_supply_np;
+ struct device *dev;
+
+ power_supply_np = of_parse_phandle(np, property, 0);
+ if (!power_supply_np)
+ return ERR_PTR(-ENODEV);
+
+ dev = class_find_device(power_supply_class, NULL, power_supply_np,
+ power_supply_match_device_node);
+
+ of_node_put(power_supply_np);
+
+ return dev ? dev_get_drvdata(dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(power_supply_get_by_phandle);
+#endif /* CONFIG_OF */
+
int power_supply_powers(struct power_supply *psy, struct device *dev)
{
return sysfs_create_link(&psy->dev->kobj, &dev->kobj, "powers");
@@ -170,6 +379,18 @@ static void power_supply_dev_release(struct device *dev)
kfree(dev);
}
+int power_supply_reg_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_register(&power_supply_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(power_supply_reg_notifier);
+
+void power_supply_unreg_notifier(struct notifier_block *nb)
+{
+ atomic_notifier_chain_unregister(&power_supply_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(power_supply_unreg_notifier);
+
#ifdef CONFIG_THERMAL
static int power_supply_read_temp(struct thermal_zone_device *tzd,
unsigned long *temp)
@@ -316,7 +537,7 @@ static void psy_unregister_cooler(struct power_supply *psy)
}
#endif
-int power_supply_register(struct device *parent, struct power_supply *psy)
+int __power_supply_register(struct device *parent, struct power_supply *psy, bool ws)
{
struct device *dev;
int rc;
@@ -334,11 +555,22 @@ int power_supply_register(struct device *parent, struct power_supply *psy)
dev_set_drvdata(dev, psy);
psy->dev = dev;
+ rc = dev_set_name(dev, "%s", psy->name);
+ if (rc)
+ goto dev_set_name_failed;
+
INIT_WORK(&psy->changed_work, power_supply_changed_work);
- rc = kobject_set_name(&dev->kobj, "%s", psy->name);
+ rc = power_supply_check_supplies(psy);
+ if (rc) {
+ dev_info(dev, "Not all required supplies found, defer probe\n");
+ goto check_supplies_failed;
+ }
+
+ spin_lock_init(&psy->changed_lock);
+ rc = device_init_wakeup(dev, ws);
if (rc)
- goto kobject_set_name_failed;
+ goto wakeup_init_failed;
rc = device_add(dev);
if (rc)
@@ -366,14 +598,27 @@ register_cooler_failed:
psy_unregister_thermal(psy);
register_thermal_failed:
device_del(dev);
-kobject_set_name_failed:
device_add_failed:
+wakeup_init_failed:
+check_supplies_failed:
+dev_set_name_failed:
put_device(dev);
success:
return rc;
}
+
+int power_supply_register(struct device *parent, struct power_supply *psy)
+{
+ return __power_supply_register(parent, psy, true);
+}
EXPORT_SYMBOL_GPL(power_supply_register);
+int power_supply_register_no_ws(struct device *parent, struct power_supply *psy)
+{
+ return __power_supply_register(parent, psy, false);
+}
+EXPORT_SYMBOL_GPL(power_supply_register_no_ws);
+
void power_supply_unregister(struct power_supply *psy)
{
cancel_work_sync(&psy->changed_work);
@@ -381,6 +626,7 @@ void power_supply_unregister(struct power_supply *psy)
power_supply_remove_triggers(psy);
psy_unregister_cooler(psy);
psy_unregister_thermal(psy);
+ device_init_wakeup(psy->dev, false);
device_unregister(psy->dev);
}
EXPORT_SYMBOL_GPL(power_supply_unregister);
diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c
index 40fa3b7cae5..44420d1e909 100644
--- a/drivers/power/power_supply_sysfs.c
+++ b/drivers/power/power_supply_sysfs.c
@@ -55,7 +55,8 @@ static ssize_t power_supply_show_property(struct device *dev,
};
static char *health_text[] = {
"Unknown", "Good", "Overheat", "Dead", "Over voltage",
- "Unspecified failure", "Cold",
+ "Unspecified failure", "Cold", "Watchdog timer expire",
+ "Safety timer expire"
};
static char *technology_text[] = {
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
@@ -117,7 +118,7 @@ static ssize_t power_supply_store_property(struct device *dev,
long long_val;
/* TODO: support other types than int */
- ret = strict_strtol(buf, 10, &long_val);
+ ret = kstrtol(buf, 10, &long_val);
if (ret < 0)
return ret;
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 6461b489fb0..bdcf5173e37 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -6,6 +6,20 @@ menuconfig POWER_RESET
Say Y here to enable board reset and power off
+config POWER_RESET_AS3722
+ bool "ams AS3722 power-off driver"
+ depends on MFD_AS3722 && POWER_RESET
+ help
+ This driver supports turning off board via a ams AS3722 power-off.
+
+config POWER_RESET_AXXIA
+ bool "LSI Axxia reset driver"
+ depends on POWER_RESET && ARCH_AXXIA
+ help
+ This driver supports restart for Axxia SoC.
+
+ Say Y if you have an Axxia family SoC.
+
config POWER_RESET_GPIO
bool "GPIO power-off driver"
depends on OF_GPIO && POWER_RESET
@@ -13,3 +27,56 @@ config POWER_RESET_GPIO
This driver supports turning off your board via a GPIO line.
If your board needs a GPIO high/low to power down, say Y and
create a binding in your devicetree.
+
+config POWER_RESET_MSM
+ bool "Qualcomm MSM power-off driver"
+ depends on POWER_RESET && ARCH_QCOM
+ help
+ Power off and restart support for Qualcomm boards.
+
+config POWER_RESET_QNAP
+ bool "QNAP power-off driver"
+ depends on OF_GPIO && POWER_RESET && PLAT_ORION
+ help
+ This driver supports turning off QNAP NAS devices by sending
+ commands to the microcontroller which controls the main power.
+
+ Say Y if you have a QNAP NAS.
+
+config POWER_RESET_RESTART
+ bool "Restart power-off driver"
+ depends on ARM
+ help
+ Some boards don't actually have the ability to power off.
+ Instead they restart, and u-boot holds the SoC until the
+ user presses a key. u-boot then boots into Linux.
+
+config POWER_RESET_SUN6I
+ bool "Allwinner A31 SoC reset driver"
+ depends on ARCH_SUNXI
+ depends on POWER_RESET
+ help
+ Reboot support for the Allwinner A31 SoCs.
+
+config POWER_RESET_VEXPRESS
+ bool "ARM Versatile Express power-off and reset driver"
+ depends on ARM || ARM64
+ depends on POWER_RESET && VEXPRESS_CONFIG
+ help
+ Power off and reset support for the ARM Ltd. Versatile
+ Express boards.
+
+config POWER_RESET_XGENE
+ bool "APM SoC X-Gene reset driver"
+ depends on ARM64
+ depends on POWER_RESET
+ help
+ Reboot support for the APM SoC X-Gene Eval boards.
+
+config POWER_RESET_KEYSTONE
+ bool "Keystone reset driver"
+ depends on ARCH_KEYSTONE
+ select MFD_SYSCON
+ help
+ Reboot support for the KEYSTONE SoCs.
+
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 751488a4a0c..dde2e8bbac5 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -1 +1,10 @@
+obj-$(CONFIG_POWER_RESET_AS3722) += as3722-poweroff.o
+obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o
obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
+obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
+obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
+obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
+obj-$(CONFIG_POWER_RESET_SUN6I) += sun6i-reboot.o
+obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o
+obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o
+obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o
diff --git a/drivers/power/reset/as3722-poweroff.c b/drivers/power/reset/as3722-poweroff.c
new file mode 100644
index 00000000000..684971199bd
--- /dev/null
+++ b/drivers/power/reset/as3722-poweroff.c
@@ -0,0 +1,96 @@
+/*
+ * Power off driver for ams AS3722 device.
+ *
+ * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+
+#include <linux/mfd/as3722.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct as3722_poweroff {
+ struct device *dev;
+ struct as3722 *as3722;
+};
+
+static struct as3722_poweroff *as3722_pm_poweroff;
+
+static void as3722_pm_power_off(void)
+{
+ int ret;
+
+ if (!as3722_pm_poweroff) {
+ pr_err("AS3722 poweroff is not initialised\n");
+ return;
+ }
+
+ ret = as3722_update_bits(as3722_pm_poweroff->as3722,
+ AS3722_RESET_CONTROL_REG, AS3722_POWER_OFF, AS3722_POWER_OFF);
+ if (ret < 0)
+ dev_err(as3722_pm_poweroff->dev,
+ "RESET_CONTROL_REG update failed, %d\n", ret);
+}
+
+static int as3722_poweroff_probe(struct platform_device *pdev)
+{
+ struct as3722_poweroff *as3722_poweroff;
+ struct device_node *np = pdev->dev.parent->of_node;
+
+ if (!np)
+ return -EINVAL;
+
+ if (!of_property_read_bool(np, "ams,system-power-controller"))
+ return 0;
+
+ as3722_poweroff = devm_kzalloc(&pdev->dev, sizeof(*as3722_poweroff),
+ GFP_KERNEL);
+ if (!as3722_poweroff)
+ return -ENOMEM;
+
+ as3722_poweroff->as3722 = dev_get_drvdata(pdev->dev.parent);
+ as3722_poweroff->dev = &pdev->dev;
+ as3722_pm_poweroff = as3722_poweroff;
+ if (!pm_power_off)
+ pm_power_off = as3722_pm_power_off;
+
+ return 0;
+}
+
+static int as3722_poweroff_remove(struct platform_device *pdev)
+{
+ if (pm_power_off == as3722_pm_power_off)
+ pm_power_off = NULL;
+ as3722_pm_poweroff = NULL;
+
+ return 0;
+}
+
+static struct platform_driver as3722_poweroff_driver = {
+ .driver = {
+ .name = "as3722-power-off",
+ .owner = THIS_MODULE,
+ },
+ .probe = as3722_poweroff_probe,
+ .remove = as3722_poweroff_remove,
+};
+
+module_platform_driver(as3722_poweroff_driver);
+
+MODULE_DESCRIPTION("Power off driver for ams AS3722 PMIC Device");
+MODULE_ALIAS("platform:as3722-power-off");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/axxia-reset.c b/drivers/power/reset/axxia-reset.c
new file mode 100644
index 00000000000..3b1f8d60178
--- /dev/null
+++ b/drivers/power/reset/axxia-reset.c
@@ -0,0 +1,88 @@
+/*
+ * Reset driver for Axxia devices
+ *
+ * Copyright (C) 2014 LSI
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+#include <asm/system_misc.h>
+
+
+#define SC_CRIT_WRITE_KEY 0x1000
+#define SC_LATCH_ON_RESET 0x1004
+#define SC_RESET_CONTROL 0x1008
+#define RSTCTL_RST_ZERO (1<<3)
+#define RSTCTL_RST_FAB (1<<2)
+#define RSTCTL_RST_CHIP (1<<1)
+#define RSTCTL_RST_SYS (1<<0)
+#define SC_EFUSE_INT_STATUS 0x180c
+#define EFUSE_READ_DONE (1<<31)
+
+static struct regmap *syscon;
+
+static void do_axxia_restart(enum reboot_mode reboot_mode, const char *cmd)
+{
+ /* Access Key (0xab) */
+ regmap_write(syscon, SC_CRIT_WRITE_KEY, 0xab);
+ /* Select internal boot from 0xffff0000 */
+ regmap_write(syscon, SC_LATCH_ON_RESET, 0x00000040);
+ /* Assert ResetReadDone (to avoid hanging in boot ROM) */
+ regmap_write(syscon, SC_EFUSE_INT_STATUS, EFUSE_READ_DONE);
+ /* Assert chip reset */
+ regmap_update_bits(syscon, SC_RESET_CONTROL,
+ RSTCTL_RST_CHIP, RSTCTL_RST_CHIP);
+}
+
+static int axxia_reset_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
+ if (IS_ERR(syscon)) {
+ pr_err("%s: syscon lookup failed\n", dev->of_node->name);
+ return PTR_ERR(syscon);
+ }
+
+ arm_pm_restart = do_axxia_restart;
+
+ return 0;
+}
+
+static const struct of_device_id of_axxia_reset_match[] = {
+ { .compatible = "lsi,axm55xx-reset", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_axxia_reset_match);
+
+static struct platform_driver axxia_reset_driver = {
+ .probe = axxia_reset_probe,
+ .driver = {
+ .name = "axxia-reset",
+ .of_match_table = of_match_ptr(of_axxia_reset_match),
+ },
+};
+
+static int __init axxia_reset_init(void)
+{
+ return platform_driver_register(&axxia_reset_driver);
+}
+device_initcall(axxia_reset_init);
diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c
new file mode 100644
index 00000000000..408a18fd91c
--- /dev/null
+++ b/drivers/power/reset/keystone-reset.c
@@ -0,0 +1,166 @@
+/*
+ * TI keystone reboot driver
+ *
+ * Copyright (C) 2014 Texas Instruments Incorporated. http://www.ti.com/
+ *
+ * Author: Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <asm/system_misc.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_platform.h>
+
+#define RSTYPE_RG 0x0
+#define RSCTRL_RG 0x4
+#define RSCFG_RG 0x8
+#define RSISO_RG 0xc
+
+#define RSCTRL_KEY_MASK 0x0000ffff
+#define RSCTRL_RESET_MASK BIT(16)
+#define RSCTRL_KEY 0x5a69
+
+#define RSMUX_OMODE_MASK 0xe
+#define RSMUX_OMODE_RESET_ON 0xa
+#define RSMUX_OMODE_RESET_OFF 0x0
+#define RSMUX_LOCK_MASK 0x1
+#define RSMUX_LOCK_SET 0x1
+
+#define RSCFG_RSTYPE_SOFT 0x300f
+#define RSCFG_RSTYPE_HARD 0x0
+
+#define WDT_MUX_NUMBER 0x4
+
+static int rspll_offset;
+static struct regmap *pllctrl_regs;
+
+/**
+ * rsctrl_enable_rspll_write - enable access to RSCTRL, RSCFG
+ * To be able to access to RSCTRL, RSCFG registers
+ * we have to write a key before
+ */
+static inline int rsctrl_enable_rspll_write(void)
+{
+ return regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG,
+ RSCTRL_KEY_MASK, RSCTRL_KEY);
+}
+
+static void rsctrl_restart(enum reboot_mode mode, const char *cmd)
+{
+ /* enable write access to RSTCTRL */
+ rsctrl_enable_rspll_write();
+
+ /* reset the SOC */
+ regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG,
+ RSCTRL_RESET_MASK, 0);
+}
+
+static struct of_device_id rsctrl_of_match[] = {
+ {.compatible = "ti,keystone-reset", },
+ {},
+};
+
+static int rsctrl_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret;
+ u32 val;
+ unsigned int rg;
+ u32 rsmux_offset;
+ struct regmap *devctrl_regs;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+
+ if (!np)
+ return -ENODEV;
+
+ /* get regmaps */
+ pllctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-pll");
+ if (IS_ERR(pllctrl_regs))
+ return PTR_ERR(pllctrl_regs);
+
+ devctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev");
+ if (IS_ERR(devctrl_regs))
+ return PTR_ERR(devctrl_regs);
+
+ ret = of_property_read_u32_index(np, "ti,syscon-pll", 1, &rspll_offset);
+ if (ret) {
+ dev_err(dev, "couldn't read the reset pll offset!\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_index(np, "ti,syscon-dev", 1, &rsmux_offset);
+ if (ret) {
+ dev_err(dev, "couldn't read the rsmux offset!\n");
+ return -EINVAL;
+ }
+
+ /* set soft/hard reset */
+ val = of_property_read_bool(np, "ti,soft-reset");
+ val = val ? RSCFG_RSTYPE_SOFT : RSCFG_RSTYPE_HARD;
+
+ ret = rsctrl_enable_rspll_write();
+ if (ret)
+ return ret;
+
+ ret = regmap_write(pllctrl_regs, rspll_offset + RSCFG_RG, val);
+ if (ret)
+ return ret;
+
+ arm_pm_restart = rsctrl_restart;
+
+ /* disable a reset isolation for all module clocks */
+ ret = regmap_write(pllctrl_regs, rspll_offset + RSISO_RG, 0);
+ if (ret)
+ return ret;
+
+ /* enable a reset for watchdogs from wdt-list */
+ for (i = 0; i < WDT_MUX_NUMBER; i++) {
+ ret = of_property_read_u32_index(np, "ti,wdt-list", i, &val);
+ if (ret == -EOVERFLOW && !i) {
+ dev_err(dev, "ti,wdt-list property has to contain at"
+ "least one entry\n");
+ return -EINVAL;
+ } else if (ret) {
+ break;
+ }
+
+ if (val >= WDT_MUX_NUMBER) {
+ dev_err(dev, "ti,wdt-list property can contain"
+ "only numbers < 4\n");
+ return -EINVAL;
+ }
+
+ rg = rsmux_offset + val * 4;
+
+ ret = regmap_update_bits(devctrl_regs, rg, RSMUX_OMODE_MASK,
+ RSMUX_OMODE_RESET_ON |
+ RSMUX_LOCK_SET);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver rsctrl_driver = {
+ .probe = rsctrl_probe,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = KBUILD_MODNAME,
+ .of_match_table = rsctrl_of_match,
+ },
+};
+module_platform_driver(rsctrl_driver);
+
+MODULE_AUTHOR("Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>");
+MODULE_DESCRIPTION("Texas Instruments keystone reset driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c
new file mode 100644
index 00000000000..774f9a3b310
--- /dev/null
+++ b/drivers/power/reset/msm-poweroff.c
@@ -0,0 +1,73 @@
+/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+
+#include <asm/system_misc.h>
+
+static void __iomem *msm_ps_hold;
+
+static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd)
+{
+ writel(0, msm_ps_hold);
+ mdelay(10000);
+}
+
+static void do_msm_poweroff(void)
+{
+ /* TODO: Add poweroff capability */
+ do_msm_restart(REBOOT_HARD, NULL);
+}
+
+static int msm_restart_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *mem;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ msm_ps_hold = devm_ioremap_resource(dev, mem);
+ if (IS_ERR(msm_ps_hold))
+ return PTR_ERR(msm_ps_hold);
+
+ pm_power_off = do_msm_poweroff;
+ arm_pm_restart = do_msm_restart;
+ return 0;
+}
+
+static const struct of_device_id of_msm_restart_match[] = {
+ { .compatible = "qcom,pshold", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_msm_restart_match);
+
+static struct platform_driver msm_restart_driver = {
+ .probe = msm_restart_probe,
+ .driver = {
+ .name = "msm-restart",
+ .of_match_table = of_match_ptr(of_msm_restart_match),
+ },
+};
+
+static int __init msm_restart_init(void)
+{
+ return platform_driver_register(&msm_restart_driver);
+}
+device_initcall(msm_restart_init);
diff --git a/drivers/power/reset/qnap-poweroff.c b/drivers/power/reset/qnap-poweroff.c
new file mode 100644
index 00000000000..a75db7f8a92
--- /dev/null
+++ b/drivers/power/reset/qnap-poweroff.c
@@ -0,0 +1,141 @@
+/*
+ * QNAP Turbo NAS Board power off. Can also be used on Synology devices.
+ *
+ * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch>
+ *
+ * Based on the code from:
+ *
+ * Copyright (C) 2009 Martin Michlmayr <tbm@cyrius.com>
+ * Copyright (C) 2008 Byron Bradley <byron.bbradley@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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/serial_reg.h>
+#include <linux/kallsyms.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+
+#define UART1_REG(x) (base + ((UART_##x) << 2))
+
+struct power_off_cfg {
+ u32 baud;
+ char cmd;
+};
+
+static const struct power_off_cfg qnap_power_off_cfg = {
+ .baud = 19200,
+ .cmd = 'A',
+};
+
+static const struct power_off_cfg synology_power_off_cfg = {
+ .baud = 9600,
+ .cmd = '1',
+};
+
+static const struct of_device_id qnap_power_off_of_match_table[] = {
+ { .compatible = "qnap,power-off",
+ .data = &qnap_power_off_cfg,
+ },
+ { .compatible = "synology,power-off",
+ .data = &synology_power_off_cfg,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table);
+
+static void __iomem *base;
+static unsigned long tclk;
+static const struct power_off_cfg *cfg;
+
+static void qnap_power_off(void)
+{
+ const unsigned divisor = ((tclk + (8 * cfg->baud)) / (16 * cfg->baud));
+
+ pr_err("%s: triggering power-off...\n", __func__);
+
+ /* hijack UART1 and reset into sane state */
+ writel(0x83, UART1_REG(LCR));
+ writel(divisor & 0xff, UART1_REG(DLL));
+ writel((divisor >> 8) & 0xff, UART1_REG(DLM));
+ writel(0x03, UART1_REG(LCR));
+ writel(0x00, UART1_REG(IER));
+ writel(0x00, UART1_REG(FCR));
+ writel(0x00, UART1_REG(MCR));
+
+ /* send the power-off command to PIC */
+ writel(cfg->cmd, UART1_REG(TX));
+}
+
+static int qnap_power_off_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *res;
+ struct clk *clk;
+ char symname[KSYM_NAME_LEN];
+
+ const struct of_device_id *match =
+ of_match_node(qnap_power_off_of_match_table, np);
+ cfg = match->data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Missing resource");
+ return -EINVAL;
+ }
+
+ base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (!base) {
+ dev_err(&pdev->dev, "Unable to map resource");
+ return -EINVAL;
+ }
+
+ /* We need to know tclk in order to calculate the UART divisor */
+ clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(&pdev->dev, "Clk missing");
+ return PTR_ERR(clk);
+ }
+
+ tclk = clk_get_rate(clk);
+
+ /* Check that nothing else has already setup a handler */
+ if (pm_power_off) {
+ lookup_symbol_name((ulong)pm_power_off, symname);
+ dev_err(&pdev->dev,
+ "pm_power_off already claimed %p %s",
+ pm_power_off, symname);
+ return -EBUSY;
+ }
+ pm_power_off = qnap_power_off;
+
+ return 0;
+}
+
+static int qnap_power_off_remove(struct platform_device *pdev)
+{
+ pm_power_off = NULL;
+ return 0;
+}
+
+static struct platform_driver qnap_power_off_driver = {
+ .probe = qnap_power_off_probe,
+ .remove = qnap_power_off_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "qnap_power_off",
+ .of_match_table = of_match_ptr(qnap_power_off_of_match_table),
+ },
+};
+module_platform_driver(qnap_power_off_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_DESCRIPTION("QNAP Power off driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/restart-poweroff.c b/drivers/power/reset/restart-poweroff.c
new file mode 100644
index 00000000000..5758033e0c1
--- /dev/null
+++ b/drivers/power/reset/restart-poweroff.c
@@ -0,0 +1,66 @@
+/*
+ * Power off by restarting and let u-boot keep hold of the machine
+ * until the user presses a button for example.
+ *
+ * Andrew Lunn <andrew@lunn.ch>
+ *
+ * Copyright (C) 2012 Andrew Lunn
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <asm/system_misc.h>
+
+static void restart_poweroff_do_poweroff(void)
+{
+ arm_pm_restart(REBOOT_HARD, NULL);
+}
+
+static int restart_poweroff_probe(struct platform_device *pdev)
+{
+ /* If a pm_power_off function has already been added, leave it alone */
+ if (pm_power_off != NULL) {
+ dev_err(&pdev->dev,
+ "pm_power_off function already registered");
+ return -EBUSY;
+ }
+
+ pm_power_off = &restart_poweroff_do_poweroff;
+ return 0;
+}
+
+static int restart_poweroff_remove(struct platform_device *pdev)
+{
+ if (pm_power_off == &restart_poweroff_do_poweroff)
+ pm_power_off = NULL;
+
+ return 0;
+}
+
+static const struct of_device_id of_restart_poweroff_match[] = {
+ { .compatible = "restart-poweroff", },
+ {},
+};
+
+static struct platform_driver restart_poweroff_driver = {
+ .probe = restart_poweroff_probe,
+ .remove = restart_poweroff_remove,
+ .driver = {
+ .name = "poweroff-restart",
+ .owner = THIS_MODULE,
+ .of_match_table = of_restart_poweroff_match,
+ },
+};
+module_platform_driver(restart_poweroff_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch");
+MODULE_DESCRIPTION("restart poweroff driver");
+MODULE_LICENSE("GPLv2");
+MODULE_ALIAS("platform:poweroff-restart");
diff --git a/drivers/power/reset/sun6i-reboot.c b/drivers/power/reset/sun6i-reboot.c
new file mode 100644
index 00000000000..af2cd7ff2fe
--- /dev/null
+++ b/drivers/power/reset/sun6i-reboot.c
@@ -0,0 +1,85 @@
+/*
+ * Allwinner A31 SoCs reset code
+ *
+ * Copyright (C) 2012-2014 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+
+#include <asm/system_misc.h>
+
+#define SUN6I_WATCHDOG1_IRQ_REG 0x00
+#define SUN6I_WATCHDOG1_CTRL_REG 0x10
+#define SUN6I_WATCHDOG1_CTRL_RESTART BIT(0)
+#define SUN6I_WATCHDOG1_CONFIG_REG 0x14
+#define SUN6I_WATCHDOG1_CONFIG_RESTART BIT(0)
+#define SUN6I_WATCHDOG1_CONFIG_IRQ BIT(1)
+#define SUN6I_WATCHDOG1_MODE_REG 0x18
+#define SUN6I_WATCHDOG1_MODE_ENABLE BIT(0)
+
+static void __iomem *wdt_base;
+
+static void sun6i_wdt_restart(enum reboot_mode mode, const char *cmd)
+{
+ if (!wdt_base)
+ return;
+
+ /* Disable interrupts */
+ writel(0, wdt_base + SUN6I_WATCHDOG1_IRQ_REG);
+
+ /* We want to disable the IRQ and just reset the whole system */
+ writel(SUN6I_WATCHDOG1_CONFIG_RESTART,
+ wdt_base + SUN6I_WATCHDOG1_CONFIG_REG);
+
+ /* Enable timer. The default and lowest interval value is 0.5s */
+ writel(SUN6I_WATCHDOG1_MODE_ENABLE,
+ wdt_base + SUN6I_WATCHDOG1_MODE_REG);
+
+ /* Restart the watchdog. */
+ writel(SUN6I_WATCHDOG1_CTRL_RESTART,
+ wdt_base + SUN6I_WATCHDOG1_CTRL_REG);
+
+ while (1) {
+ mdelay(5);
+ writel(SUN6I_WATCHDOG1_MODE_ENABLE,
+ wdt_base + SUN6I_WATCHDOG1_MODE_REG);
+ }
+}
+
+static int sun6i_reboot_probe(struct platform_device *pdev)
+{
+ wdt_base = of_iomap(pdev->dev.of_node, 0);
+ if (!wdt_base) {
+ WARN(1, "failed to map watchdog base address");
+ return -ENODEV;
+ }
+
+ arm_pm_restart = sun6i_wdt_restart;
+
+ return 0;
+}
+
+static struct of_device_id sun6i_reboot_of_match[] = {
+ { .compatible = "allwinner,sun6i-a31-wdt" },
+ {}
+};
+
+static struct platform_driver sun6i_reboot_driver = {
+ .probe = sun6i_reboot_probe,
+ .driver = {
+ .name = "sun6i-reboot",
+ .of_match_table = sun6i_reboot_of_match,
+ },
+};
+module_platform_driver(sun6i_reboot_driver);
diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c
new file mode 100644
index 00000000000..4dc102e2b23
--- /dev/null
+++ b/drivers/power/reset/vexpress-poweroff.c
@@ -0,0 +1,147 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Copyright (C) 2012 ARM Limited
+ */
+
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/stat.h>
+#include <linux/vexpress.h>
+
+#include <asm/system_misc.h>
+
+static void vexpress_reset_do(struct device *dev, const char *what)
+{
+ int err = -ENOENT;
+ struct regmap *reg = dev_get_drvdata(dev);
+
+ if (reg) {
+ err = regmap_write(reg, 0, 0);
+ if (!err)
+ mdelay(1000);
+ }
+
+ dev_emerg(dev, "Unable to %s (%d)\n", what, err);
+}
+
+static struct device *vexpress_power_off_device;
+
+static void vexpress_power_off(void)
+{
+ vexpress_reset_do(vexpress_power_off_device, "power off");
+}
+
+static struct device *vexpress_restart_device;
+
+static void vexpress_restart(enum reboot_mode reboot_mode, const char *cmd)
+{
+ vexpress_reset_do(vexpress_restart_device, "restart");
+}
+
+static ssize_t vexpress_reset_active_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", vexpress_restart_device == dev);
+}
+
+static ssize_t vexpress_reset_active_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ long value;
+ int err = kstrtol(buf, 0, &value);
+
+ if (!err && value)
+ vexpress_restart_device = dev;
+
+ return err ? err : count;
+}
+
+DEVICE_ATTR(active, S_IRUGO | S_IWUSR, vexpress_reset_active_show,
+ vexpress_reset_active_store);
+
+
+enum vexpress_reset_func { FUNC_RESET, FUNC_SHUTDOWN, FUNC_REBOOT };
+
+static struct of_device_id vexpress_reset_of_match[] = {
+ {
+ .compatible = "arm,vexpress-reset",
+ .data = (void *)FUNC_RESET,
+ }, {
+ .compatible = "arm,vexpress-shutdown",
+ .data = (void *)FUNC_SHUTDOWN
+ }, {
+ .compatible = "arm,vexpress-reboot",
+ .data = (void *)FUNC_REBOOT
+ },
+ {}
+};
+
+static int vexpress_reset_probe(struct platform_device *pdev)
+{
+ enum vexpress_reset_func func;
+ const struct of_device_id *match =
+ of_match_device(vexpress_reset_of_match, &pdev->dev);
+ struct regmap *regmap;
+
+ if (match)
+ func = (enum vexpress_reset_func)match->data;
+ else
+ func = pdev->id_entry->driver_data;
+
+ regmap = devm_regmap_init_vexpress_config(&pdev->dev);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+ dev_set_drvdata(&pdev->dev, regmap);
+
+ switch (func) {
+ case FUNC_SHUTDOWN:
+ vexpress_power_off_device = &pdev->dev;
+ pm_power_off = vexpress_power_off;
+ break;
+ case FUNC_RESET:
+ if (!vexpress_restart_device)
+ vexpress_restart_device = &pdev->dev;
+ arm_pm_restart = vexpress_restart;
+ device_create_file(&pdev->dev, &dev_attr_active);
+ break;
+ case FUNC_REBOOT:
+ vexpress_restart_device = &pdev->dev;
+ arm_pm_restart = vexpress_restart;
+ device_create_file(&pdev->dev, &dev_attr_active);
+ break;
+ };
+
+ return 0;
+}
+
+static const struct platform_device_id vexpress_reset_id_table[] = {
+ { .name = "vexpress-reset", .driver_data = FUNC_RESET, },
+ { .name = "vexpress-shutdown", .driver_data = FUNC_SHUTDOWN, },
+ { .name = "vexpress-reboot", .driver_data = FUNC_REBOOT, },
+ {}
+};
+
+static struct platform_driver vexpress_reset_driver = {
+ .probe = vexpress_reset_probe,
+ .driver = {
+ .name = "vexpress-reset",
+ .of_match_table = vexpress_reset_of_match,
+ },
+ .id_table = vexpress_reset_id_table,
+};
+
+static int __init vexpress_reset_init(void)
+{
+ return platform_driver_register(&vexpress_reset_driver);
+}
+device_initcall(vexpress_reset_init);
diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c
new file mode 100644
index 00000000000..ecd55f81b9d
--- /dev/null
+++ b/drivers/power/reset/xgene-reboot.c
@@ -0,0 +1,103 @@
+/*
+ * AppliedMicro X-Gene SoC Reboot Driver
+ *
+ * Copyright (c) 2013, Applied Micro Circuits Corporation
+ * Author: Feng Kan <fkan@apm.com>
+ * Author: Loc Ho <lho@apm.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., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * This driver provides system reboot functionality for APM X-Gene SoC.
+ * For system shutdown, this is board specify. If a board designer
+ * implements GPIO shutdown, use the gpio-poweroff.c driver.
+ */
+#include <linux/io.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/stat.h>
+#include <linux/slab.h>
+#include <asm/system_misc.h>
+
+struct xgene_reboot_context {
+ struct platform_device *pdev;
+ void *csr;
+ u32 mask;
+};
+
+static struct xgene_reboot_context *xgene_restart_ctx;
+
+static void xgene_restart(char str, const char *cmd)
+{
+ struct xgene_reboot_context *ctx = xgene_restart_ctx;
+ unsigned long timeout;
+
+ /* Issue the reboot */
+ if (ctx)
+ writel(ctx->mask, ctx->csr);
+
+ timeout = jiffies + HZ;
+ while (time_before(jiffies, timeout))
+ cpu_relax();
+
+ dev_emerg(&ctx->pdev->dev, "Unable to restart system\n");
+}
+
+static int xgene_reboot_probe(struct platform_device *pdev)
+{
+ struct xgene_reboot_context *ctx;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx) {
+ dev_err(&pdev->dev, "out of memory for context\n");
+ return -ENODEV;
+ }
+
+ ctx->csr = of_iomap(pdev->dev.of_node, 0);
+ if (!ctx->csr) {
+ devm_kfree(&pdev->dev, ctx);
+ dev_err(&pdev->dev, "can not map resource\n");
+ return -ENODEV;
+ }
+
+ if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask))
+ ctx->mask = 0xFFFFFFFF;
+
+ ctx->pdev = pdev;
+ arm_pm_restart = xgene_restart;
+ xgene_restart_ctx = ctx;
+
+ return 0;
+}
+
+static struct of_device_id xgene_reboot_of_match[] = {
+ { .compatible = "apm,xgene-reboot" },
+ {}
+};
+
+static struct platform_driver xgene_reboot_driver = {
+ .probe = xgene_reboot_probe,
+ .driver = {
+ .name = "xgene-reboot",
+ .of_match_table = xgene_reboot_of_match,
+ },
+};
+
+static int __init xgene_reboot_init(void)
+{
+ return platform_driver_register(&xgene_reboot_driver);
+}
+device_initcall(xgene_reboot_init);
diff --git a/drivers/power/rx51_battery.c b/drivers/power/rx51_battery.c
index 8208888b844..1bc5857b8bd 100644
--- a/drivers/power/rx51_battery.c
+++ b/drivers/power/rx51_battery.c
@@ -25,6 +25,10 @@
#include <linux/slab.h>
#include <linux/i2c/twl4030-madc.h>
+/* RX51 specific channels */
+#define TWL4030_MADC_BTEMP_RX51 TWL4030_MADC_ADCIN0
+#define TWL4030_MADC_BCI_RX51 TWL4030_MADC_ADCIN4
+
struct rx51_device_info {
struct device *dev;
struct power_supply bat;
@@ -37,16 +41,17 @@ static int rx51_battery_read_adc(int channel)
{
struct twl4030_madc_request req;
- req.channels = 1 << channel;
+ req.channels = channel;
req.do_avg = 1;
req.method = TWL4030_MADC_SW1;
req.func_cb = NULL;
req.type = TWL4030_MADC_WAIT;
+ req.raw = true;
if (twl4030_madc_conversion(&req) <= 0)
return -ENODATA;
- return req.rbuf[channel];
+ return req.rbuf[ffs(channel) - 1];
}
/*
@@ -55,7 +60,7 @@ static int rx51_battery_read_adc(int channel)
*/
static int rx51_battery_read_voltage(struct rx51_device_info *di)
{
- int voltage = rx51_battery_read_adc(12);
+ int voltage = rx51_battery_read_adc(TWL4030_MADC_VBAT);
if (voltage < 0)
return voltage;
@@ -107,7 +112,7 @@ 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);
+ int raw = rx51_battery_read_adc(TWL4030_MADC_BTEMP_RX51);
/* Zero and negative values are undefined */
if (raw <= 0)
@@ -119,7 +124,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di)
/* First check for temperature in first direct table */
if (raw < ARRAY_SIZE(rx51_temp_table1))
- return rx51_temp_table1[raw] * 100;
+ return rx51_temp_table1[raw] * 10;
/* Binary search RAW value in second inverse table */
while (max - min > 1) {
@@ -132,7 +137,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di)
break;
}
- return (rx51_temp_table2_first - min) * 100;
+ return (rx51_temp_table2_first - min) * 10;
}
/*
@@ -141,7 +146,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di)
*/
static int rx51_battery_read_capacity(struct rx51_device_info *di)
{
- int capacity = rx51_battery_read_adc(4);
+ int capacity = rx51_battery_read_adc(TWL4030_MADC_BCI_RX51);
if (capacity < 0)
return capacity;
@@ -202,7 +207,7 @@ static int rx51_battery_probe(struct platform_device *pdev)
struct rx51_device_info *di;
int ret;
- di = kzalloc(sizeof(*di), GFP_KERNEL);
+ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
if (!di)
return -ENOMEM;
@@ -215,11 +220,8 @@ static int rx51_battery_probe(struct platform_device *pdev)
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);
+ if (ret)
return ret;
- }
return 0;
}
@@ -229,8 +231,6 @@ static int 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;
}
diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c
index d2ca989dcbd..5948ce058bd 100644
--- a/drivers/power/s3c_adc_battery.c
+++ b/drivers/power/s3c_adc_battery.c
@@ -145,14 +145,17 @@ static int s3c_adc_bat_get_property(struct power_supply *psy,
int new_level;
int full_volt;
- const struct s3c_adc_bat_thresh *lut = bat->pdata->lut_noac;
- unsigned int lut_size = bat->pdata->lut_noac_cnt;
+ const struct s3c_adc_bat_thresh *lut;
+ unsigned int lut_size;
if (!bat) {
dev_err(psy->dev, "no battery infos ?!\n");
return -EINVAL;
}
+ lut = bat->pdata->lut_noac;
+ lut_size = bat->pdata->lut_noac_cnt;
+
if (bat->volt_value < 0 || bat->cur_value < 0 ||
jiffies_to_msecs(jiffies - bat->timestamp) >
BAT_POLL_INTERVAL) {
diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c
index 3960f0b2afe..b5f2a76b6cd 100644
--- a/drivers/power/sbs-battery.c
+++ b/drivers/power/sbs-battery.c
@@ -27,6 +27,7 @@
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
+#include <linux/of.h>
#include <linux/power/sbs-battery.h>
@@ -667,7 +668,6 @@ of_out:
return pdata;
}
#else
-#define sbs_dt_ids NULL
static struct sbs_platform_data *sbs_of_populate_pdata(
struct i2c_client *client)
{
@@ -704,6 +704,7 @@ static int sbs_probe(struct i2c_client *client,
chip->power_supply.properties = sbs_properties;
chip->power_supply.num_properties = ARRAY_SIZE(sbs_properties);
chip->power_supply.get_property = sbs_get_property;
+ chip->power_supply.of_node = client->dev.of_node;
/* ignore first notification of external change, it is generated
* from the power_supply_register call back
*/
@@ -820,10 +821,11 @@ static int sbs_remove(struct i2c_client *client)
return 0;
}
-#if defined CONFIG_PM
-static int sbs_suspend(struct i2c_client *client,
- pm_message_t state)
+#if defined CONFIG_PM_SLEEP
+
+static int sbs_suspend(struct device *dev)
{
+ struct i2c_client *client = to_i2c_client(dev);
struct sbs_info *chip = i2c_get_clientdata(client);
s32 ret;
@@ -838,11 +840,13 @@ static int sbs_suspend(struct i2c_client *client,
return 0;
}
+
+static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL);
+#define SBS_PM_OPS (&sbs_pm_ops)
+
#else
-#define sbs_suspend NULL
+#define SBS_PM_OPS NULL
#endif
-/* any smbus transaction will wake up sbs */
-#define sbs_resume NULL
static const struct i2c_device_id sbs_id[] = {
{ "bq20z75", 0 },
@@ -854,12 +858,11 @@ MODULE_DEVICE_TABLE(i2c, sbs_id);
static struct i2c_driver sbs_battery_driver = {
.probe = sbs_probe,
.remove = sbs_remove,
- .suspend = sbs_suspend,
- .resume = sbs_resume,
.id_table = sbs_id,
.driver = {
.name = "sbs-battery",
- .of_match_table = sbs_dt_ids,
+ .of_match_table = of_match_ptr(sbs_dt_ids),
+ .pm = SBS_PM_OPS,
},
};
module_i2c_driver(sbs_battery_driver);
diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c
index b99a452a4fd..0152f35dca5 100644
--- a/drivers/power/test_power.c
+++ b/drivers/power/test_power.c
@@ -30,6 +30,8 @@ static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION;
static int battery_capacity = 50;
static int battery_voltage = 3300;
+static bool module_initialized;
+
static int test_power_get_ac_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -185,6 +187,7 @@ static int __init test_power_init(void)
}
}
+ module_initialized = true;
return 0;
failed:
while (--i >= 0)
@@ -209,6 +212,8 @@ static void __exit test_power_exit(void)
for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++)
power_supply_unregister(&test_power_supplies[i]);
+
+ module_initialized = false;
}
module_exit(test_power_exit);
@@ -221,8 +226,8 @@ struct battery_property_map {
};
static struct battery_property_map map_ac_online[] = {
- { 0, "on" },
- { 1, "off" },
+ { 0, "off" },
+ { 1, "on" },
{ -1, NULL },
};
@@ -295,10 +300,16 @@ static const char *map_get_key(struct battery_property_map *map, int value,
return def_key;
}
+static inline void signal_power_supply_changed(struct power_supply *psy)
+{
+ if (module_initialized)
+ power_supply_changed(psy);
+}
+
static int param_set_ac_online(const char *key, const struct kernel_param *kp)
{
ac_online = map_get_value(map_ac_online, key, ac_online);
- power_supply_changed(&test_power_supplies[0]);
+ signal_power_supply_changed(&test_power_supplies[0]);
return 0;
}
@@ -311,7 +322,7 @@ static int param_get_ac_online(char *buffer, const struct kernel_param *kp)
static int param_set_usb_online(const char *key, const struct kernel_param *kp)
{
usb_online = map_get_value(map_ac_online, key, usb_online);
- power_supply_changed(&test_power_supplies[2]);
+ signal_power_supply_changed(&test_power_supplies[2]);
return 0;
}
@@ -325,7 +336,7 @@ static int param_set_battery_status(const char *key,
const struct kernel_param *kp)
{
battery_status = map_get_value(map_status, key, battery_status);
- power_supply_changed(&test_power_supplies[1]);
+ signal_power_supply_changed(&test_power_supplies[1]);
return 0;
}
@@ -339,7 +350,7 @@ static int param_set_battery_health(const char *key,
const struct kernel_param *kp)
{
battery_health = map_get_value(map_health, key, battery_health);
- power_supply_changed(&test_power_supplies[1]);
+ signal_power_supply_changed(&test_power_supplies[1]);
return 0;
}
@@ -353,7 +364,7 @@ static int param_set_battery_present(const char *key,
const struct kernel_param *kp)
{
battery_present = map_get_value(map_present, key, battery_present);
- power_supply_changed(&test_power_supplies[0]);
+ signal_power_supply_changed(&test_power_supplies[0]);
return 0;
}
@@ -369,7 +380,7 @@ static int param_set_battery_technology(const char *key,
{
battery_technology = map_get_value(map_technology, key,
battery_technology);
- power_supply_changed(&test_power_supplies[1]);
+ signal_power_supply_changed(&test_power_supplies[1]);
return 0;
}
@@ -390,7 +401,7 @@ static int param_set_battery_capacity(const char *key,
return -EINVAL;
battery_capacity = tmp;
- power_supply_changed(&test_power_supplies[1]);
+ signal_power_supply_changed(&test_power_supplies[1]);
return 0;
}
@@ -405,7 +416,7 @@ static int param_set_battery_voltage(const char *key,
return -EINVAL;
battery_voltage = tmp;
- power_supply_changed(&test_power_supplies[1]);
+ signal_power_supply_changed(&test_power_supplies[1]);
return 0;
}
diff --git a/drivers/power/tosa_battery.c b/drivers/power/tosa_battery.c
index 0224de50c54..f4d80df627c 100644
--- a/drivers/power/tosa_battery.c
+++ b/drivers/power/tosa_battery.c
@@ -150,7 +150,7 @@ static void tosa_bat_external_power_changed(struct power_supply *psy)
static irqreturn_t tosa_bat_gpio_isr(int irq, void *data)
{
- pr_info("tosa_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq)));
+ pr_info("tosa_bat_gpio irq\n");
schedule_work(&bat_work);
return IRQ_HANDLED;
}
diff --git a/drivers/power/tps65090-charger.c b/drivers/power/tps65090-charger.c
new file mode 100644
index 00000000000..1685f63b9e5
--- /dev/null
+++ b/drivers/power/tps65090-charger.c
@@ -0,0 +1,323 @@
+/*
+ * Battery charger driver for TI's tps65090
+ *
+ * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/tps65090.h>
+
+#define TPS65090_CHARGER_ENABLE BIT(0)
+#define TPS65090_VACG BIT(1)
+#define TPS65090_NOITERM BIT(5)
+
+struct tps65090_charger {
+ struct device *dev;
+ int ac_online;
+ int prev_ac_online;
+ int irq;
+ struct power_supply ac;
+ struct tps65090_platform_data *pdata;
+};
+
+static enum power_supply_property tps65090_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int tps65090_low_chrg_current(struct tps65090_charger *charger)
+{
+ int ret;
+
+ ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL5,
+ TPS65090_NOITERM);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error reading in register 0x%x\n",
+ __func__, TPS65090_REG_CG_CTRL5);
+ return ret;
+ }
+ return 0;
+}
+
+static int tps65090_enable_charging(struct tps65090_charger *charger)
+{
+ int ret;
+ uint8_t ctrl0 = 0;
+
+ ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_CTRL0,
+ &ctrl0);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error reading in register 0x%x\n",
+ __func__, TPS65090_REG_CG_CTRL0);
+ return ret;
+ }
+
+ ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL0,
+ (ctrl0 | TPS65090_CHARGER_ENABLE));
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error writing in register 0x%x\n",
+ __func__, TPS65090_REG_CG_CTRL0);
+ return ret;
+ }
+ return 0;
+}
+
+static int tps65090_config_charger(struct tps65090_charger *charger)
+{
+ uint8_t intrmask = 0;
+ int ret;
+
+ if (charger->pdata->enable_low_current_chrg) {
+ ret = tps65090_low_chrg_current(charger);
+ if (ret < 0) {
+ dev_err(charger->dev,
+ "error configuring low charge current\n");
+ return ret;
+ }
+ }
+
+ /* Enable the VACG interrupt for AC power detect */
+ ret = tps65090_read(charger->dev->parent, TPS65090_REG_INTR_MASK,
+ &intrmask);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error reading in register 0x%x\n",
+ __func__, TPS65090_REG_INTR_MASK);
+ return ret;
+ }
+
+ ret = tps65090_write(charger->dev->parent, TPS65090_REG_INTR_MASK,
+ (intrmask | TPS65090_VACG));
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error writing in register 0x%x\n",
+ __func__, TPS65090_REG_CG_CTRL0);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tps65090_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct tps65090_charger *charger = container_of(psy,
+ struct tps65090_charger, ac);
+
+ if (psp == POWER_SUPPLY_PROP_ONLINE) {
+ val->intval = charger->ac_online;
+ charger->prev_ac_online = charger->ac_online;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static irqreturn_t tps65090_charger_isr(int irq, void *dev_id)
+{
+ struct tps65090_charger *charger = dev_id;
+ int ret;
+ uint8_t status1 = 0;
+ uint8_t intrsts = 0;
+
+ ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_STATUS1,
+ &status1);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n",
+ __func__, TPS65090_REG_CG_STATUS1);
+ return IRQ_HANDLED;
+ }
+ msleep(75);
+ ret = tps65090_read(charger->dev->parent, TPS65090_REG_INTR_STS,
+ &intrsts);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n",
+ __func__, TPS65090_REG_INTR_STS);
+ return IRQ_HANDLED;
+ }
+
+ if (intrsts & TPS65090_VACG) {
+ ret = tps65090_enable_charging(charger);
+ if (ret < 0)
+ return IRQ_HANDLED;
+ charger->ac_online = 1;
+ } else {
+ charger->ac_online = 0;
+ }
+
+ /* Clear interrupts. */
+ ret = tps65090_write(charger->dev->parent, TPS65090_REG_INTR_STS, 0x00);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Error in writing reg 0x%x\n",
+ __func__, TPS65090_REG_INTR_STS);
+ }
+
+ if (charger->prev_ac_online != charger->ac_online)
+ power_supply_changed(&charger->ac);
+
+ return IRQ_HANDLED;
+}
+
+static struct tps65090_platform_data *
+ tps65090_parse_dt_charger_data(struct platform_device *pdev)
+{
+ struct tps65090_platform_data *pdata;
+ struct device_node *np = pdev->dev.of_node;
+ unsigned int prop;
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(&pdev->dev, "Memory alloc for tps65090_pdata failed\n");
+ return NULL;
+ }
+
+ prop = of_property_read_bool(np, "ti,enable-low-current-chrg");
+ pdata->enable_low_current_chrg = prop;
+
+ pdata->irq_base = -1;
+
+ return pdata;
+
+}
+
+static int tps65090_charger_probe(struct platform_device *pdev)
+{
+ struct tps65090_charger *cdata;
+ struct tps65090_platform_data *pdata;
+ uint8_t status1 = 0;
+ int ret;
+ int irq;
+
+ pdata = dev_get_platdata(pdev->dev.parent);
+
+ if (IS_ENABLED(CONFIG_OF) && !pdata && pdev->dev.of_node)
+ pdata = tps65090_parse_dt_charger_data(pdev);
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "%s():no platform data available\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ cdata = devm_kzalloc(&pdev->dev, sizeof(*cdata), GFP_KERNEL);
+ if (!cdata) {
+ dev_err(&pdev->dev, "failed to allocate memory status\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, cdata);
+
+ cdata->dev = &pdev->dev;
+ cdata->pdata = pdata;
+
+ cdata->ac.name = "tps65090-ac";
+ cdata->ac.type = POWER_SUPPLY_TYPE_MAINS;
+ cdata->ac.get_property = tps65090_ac_get_property;
+ cdata->ac.properties = tps65090_ac_props;
+ cdata->ac.num_properties = ARRAY_SIZE(tps65090_ac_props);
+ cdata->ac.supplied_to = pdata->supplied_to;
+ cdata->ac.num_supplicants = pdata->num_supplicants;
+ cdata->ac.of_node = pdev->dev.of_node;
+
+ ret = power_supply_register(&pdev->dev, &cdata->ac);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ return ret;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ dev_warn(&pdev->dev, "Unable to get charger irq = %d\n", irq);
+ ret = irq;
+ goto fail_unregister_supply;
+ }
+
+ cdata->irq = irq;
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ tps65090_charger_isr, 0, "tps65090-charger", cdata);
+ if (ret) {
+ dev_err(cdata->dev, "Unable to register irq %d err %d\n", irq,
+ ret);
+ goto fail_unregister_supply;
+ }
+
+ ret = tps65090_config_charger(cdata);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "charger config failed, err %d\n", ret);
+ goto fail_unregister_supply;
+ }
+
+ /* Check for charger presence */
+ ret = tps65090_read(cdata->dev->parent, TPS65090_REG_CG_STATUS1,
+ &status1);
+ if (ret < 0) {
+ dev_err(cdata->dev, "%s(): Error in reading reg 0x%x", __func__,
+ TPS65090_REG_CG_STATUS1);
+ goto fail_unregister_supply;
+ }
+
+ if (status1 != 0) {
+ ret = tps65090_enable_charging(cdata);
+ if (ret < 0) {
+ dev_err(cdata->dev, "error enabling charger\n");
+ goto fail_unregister_supply;
+ }
+ cdata->ac_online = 1;
+ power_supply_changed(&cdata->ac);
+ }
+
+ return 0;
+
+fail_unregister_supply:
+ power_supply_unregister(&cdata->ac);
+
+ return ret;
+}
+
+static int tps65090_charger_remove(struct platform_device *pdev)
+{
+ struct tps65090_charger *cdata = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&cdata->ac);
+
+ return 0;
+}
+
+static struct of_device_id of_tps65090_charger_match[] = {
+ { .compatible = "ti,tps65090-charger", },
+ { /* end */ }
+};
+
+static struct platform_driver tps65090_charger_driver = {
+ .driver = {
+ .name = "tps65090-charger",
+ .of_match_table = of_tps65090_charger_match,
+ .owner = THIS_MODULE,
+ },
+ .probe = tps65090_charger_probe,
+ .remove = tps65090_charger_remove,
+};
+module_platform_driver(tps65090_charger_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com>");
+MODULE_DESCRIPTION("tps65090 battery charger driver");
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
index a69d0d11b54..f14108844e1 100644
--- a/drivers/power/twl4030_charger.c
+++ b/drivers/power/twl4030_charger.c
@@ -189,7 +189,12 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
/* Need to keep regulator on */
if (!bci->usb_enabled) {
- regulator_enable(bci->usb_reg);
+ ret = regulator_enable(bci->usb_reg);
+ if (ret) {
+ dev_err(bci->dev,
+ "Failed to enable regulator\n");
+ return ret;
+ }
bci->usb_enabled = 1;
}
@@ -490,10 +495,38 @@ static enum power_supply_property twl4030_charger_props[] = {
POWER_SUPPLY_PROP_CURRENT_NOW,
};
+#ifdef CONFIG_OF
+static const struct twl4030_bci_platform_data *
+twl4030_bci_parse_dt(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct twl4030_bci_platform_data *pdata;
+ u32 num;
+
+ if (!np)
+ return NULL;
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return pdata;
+
+ if (of_property_read_u32(np, "ti,bb-uvolt", &num) == 0)
+ pdata->bb_uvolt = num;
+ if (of_property_read_u32(np, "ti,bb-uamp", &num) == 0)
+ pdata->bb_uamp = num;
+ return pdata;
+}
+#else
+static inline const struct twl4030_bci_platform_data *
+twl4030_bci_parse_dt(struct device *dev)
+{
+ return NULL;
+}
+#endif
+
static int __init twl4030_bci_probe(struct platform_device *pdev)
{
struct twl4030_bci *bci;
- struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data;
+ const struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data;
int ret;
u32 reg;
@@ -501,6 +534,9 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
if (bci == NULL)
return -ENOMEM;
+ if (!pdata)
+ pdata = twl4030_bci_parse_dt(&pdev->dev);
+
bci->dev = &pdev->dev;
bci->irq_chg = platform_get_irq(pdev, 0);
bci->irq_bci = platform_get_irq(pdev, 1);
@@ -576,8 +612,11 @@ static int __init twl4030_bci_probe(struct platform_device *pdev)
twl4030_charger_enable_ac(true);
twl4030_charger_enable_usb(bci, true);
- twl4030_charger_enable_backup(pdata->bb_uvolt,
- pdata->bb_uamp);
+ if (pdata)
+ twl4030_charger_enable_backup(pdata->bb_uvolt,
+ pdata->bb_uamp);
+ else
+ twl4030_charger_enable_backup(0, 0);
return 0;
@@ -594,7 +633,6 @@ fail_chg_irq:
fail_register_usb:
power_supply_unregister(&bci->ac);
fail_register_ac:
- platform_set_drvdata(pdev, NULL);
kfree(bci);
return ret;
@@ -622,31 +660,27 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
free_irq(bci->irq_chg, bci);
power_supply_unregister(&bci->usb);
power_supply_unregister(&bci->ac);
- platform_set_drvdata(pdev, NULL);
kfree(bci);
return 0;
}
+static const struct of_device_id twl_bci_of_match[] = {
+ {.compatible = "ti,twl4030-bci", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, twl_bci_of_match);
+
static struct platform_driver twl4030_bci_driver = {
.driver = {
.name = "twl4030_bci",
.owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(twl_bci_of_match),
},
.remove = __exit_p(twl4030_bci_remove),
};
-static int __init twl4030_bci_init(void)
-{
- return platform_driver_probe(&twl4030_bci_driver, twl4030_bci_probe);
-}
-module_init(twl4030_bci_init);
-
-static void __exit twl4030_bci_exit(void)
-{
- platform_driver_unregister(&twl4030_bci_driver);
-}
-module_exit(twl4030_bci_exit);
+module_platform_driver_probe(twl4030_bci_driver, twl4030_bci_probe);
MODULE_AUTHOR("Gražvydas Ignotas");
MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver");
diff --git a/drivers/power/twl4030_madc_battery.c b/drivers/power/twl4030_madc_battery.c
new file mode 100644
index 00000000000..7ef445a6cfa
--- /dev/null
+++ b/drivers/power/twl4030_madc_battery.c
@@ -0,0 +1,245 @@
+/*
+ * Dumb driver for LiIon batteries using TWL4030 madc.
+ *
+ * Copyright 2013 Golden Delicious Computers
+ * Lukas Märdian <lukas@goldelico.com>
+ *
+ * Based on dumb driver for gta01 battery
+ * Copyright 2009 Openmoko, Inc
+ * Balaji Rao <balajirrao@openmoko.org>
+ */
+
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/i2c/twl4030-madc.h>
+#include <linux/power/twl4030_madc_battery.h>
+
+struct twl4030_madc_battery {
+ struct power_supply psy;
+ struct twl4030_madc_bat_platform_data *pdata;
+};
+
+static enum power_supply_property twl4030_madc_bat_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+};
+
+static int madc_read(int index)
+{
+ struct twl4030_madc_request req;
+ int val;
+
+ req.channels = index;
+ req.method = TWL4030_MADC_SW2;
+ req.type = TWL4030_MADC_WAIT;
+ req.do_avg = 0;
+ req.raw = false;
+ req.func_cb = NULL;
+
+ val = twl4030_madc_conversion(&req);
+ if (val < 0)
+ return val;
+
+ return req.rbuf[ffs(index) - 1];
+}
+
+static int twl4030_madc_bat_get_charging_status(void)
+{
+ return (madc_read(TWL4030_MADC_ICHG) > 0) ? 1 : 0;
+}
+
+static int twl4030_madc_bat_get_voltage(void)
+{
+ return madc_read(TWL4030_MADC_VBAT);
+}
+
+static int twl4030_madc_bat_get_current(void)
+{
+ return madc_read(TWL4030_MADC_ICHG) * 1000;
+}
+
+static int twl4030_madc_bat_get_temp(void)
+{
+ return madc_read(TWL4030_MADC_BTEMP) * 10;
+}
+
+static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat,
+ int volt)
+{
+ struct twl4030_madc_bat_calibration *calibration;
+ int i, res = 0;
+
+ /* choose charging curve */
+ if (twl4030_madc_bat_get_charging_status())
+ calibration = bat->pdata->charging;
+ else
+ calibration = bat->pdata->discharging;
+
+ if (volt > calibration[0].voltage) {
+ res = calibration[0].level;
+ } else {
+ for (i = 0; calibration[i+1].voltage >= 0; i++) {
+ if (volt <= calibration[i].voltage &&
+ volt >= calibration[i+1].voltage) {
+ /* interval found - interpolate within range */
+ res = calibration[i].level -
+ ((calibration[i].voltage - volt) *
+ (calibration[i].level -
+ calibration[i+1].level)) /
+ (calibration[i].voltage -
+ calibration[i+1].voltage);
+ break;
+ }
+ }
+ }
+ return res;
+}
+
+static int twl4030_madc_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct twl4030_madc_battery *bat = container_of(psy,
+ struct twl4030_madc_battery, psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (twl4030_madc_bat_voltscale(bat,
+ twl4030_madc_bat_get_voltage()) > 95)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else {
+ if (twl4030_madc_bat_get_charging_status())
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = twl4030_madc_bat_get_voltage() * 1000;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = twl4030_madc_bat_get_current();
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ /* assume battery is always present */
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW: {
+ int percent = twl4030_madc_bat_voltscale(bat,
+ twl4030_madc_bat_get_voltage());
+ val->intval = (percent * bat->pdata->capacity) / 100;
+ break;
+ }
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = twl4030_madc_bat_voltscale(bat,
+ twl4030_madc_bat_get_voltage());
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = bat->pdata->capacity;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = twl4030_madc_bat_get_temp();
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {
+ int percent = twl4030_madc_bat_voltscale(bat,
+ twl4030_madc_bat_get_voltage());
+ /* in mAh */
+ int chg = (percent * (bat->pdata->capacity/1000))/100;
+
+ /* assume discharge with 400 mA (ca. 1.5W) */
+ val->intval = (3600l * chg) / 400;
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void twl4030_madc_bat_ext_changed(struct power_supply *psy)
+{
+ struct twl4030_madc_battery *bat = container_of(psy,
+ struct twl4030_madc_battery, psy);
+
+ power_supply_changed(&bat->psy);
+}
+
+static int twl4030_cmp(const void *a, const void *b)
+{
+ return ((struct twl4030_madc_bat_calibration *)b)->voltage -
+ ((struct twl4030_madc_bat_calibration *)a)->voltage;
+}
+
+static int twl4030_madc_battery_probe(struct platform_device *pdev)
+{
+ struct twl4030_madc_battery *twl4030_madc_bat;
+ struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data;
+
+ twl4030_madc_bat = kzalloc(sizeof(*twl4030_madc_bat), GFP_KERNEL);
+ if (!twl4030_madc_bat)
+ return -ENOMEM;
+
+ twl4030_madc_bat->psy.name = "twl4030_battery";
+ twl4030_madc_bat->psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ twl4030_madc_bat->psy.properties = twl4030_madc_bat_props;
+ twl4030_madc_bat->psy.num_properties =
+ ARRAY_SIZE(twl4030_madc_bat_props);
+ twl4030_madc_bat->psy.get_property = twl4030_madc_bat_get_property;
+ twl4030_madc_bat->psy.external_power_changed =
+ twl4030_madc_bat_ext_changed;
+
+ /* sort charging and discharging calibration data */
+ sort(pdata->charging, pdata->charging_size,
+ sizeof(struct twl4030_madc_bat_calibration),
+ twl4030_cmp, NULL);
+ sort(pdata->discharging, pdata->discharging_size,
+ sizeof(struct twl4030_madc_bat_calibration),
+ twl4030_cmp, NULL);
+
+ twl4030_madc_bat->pdata = pdata;
+ platform_set_drvdata(pdev, twl4030_madc_bat);
+ power_supply_register(&pdev->dev, &twl4030_madc_bat->psy);
+
+ return 0;
+}
+
+static int twl4030_madc_battery_remove(struct platform_device *pdev)
+{
+ struct twl4030_madc_battery *bat = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&bat->psy);
+ kfree(bat);
+
+ return 0;
+}
+
+static struct platform_driver twl4030_madc_battery_driver = {
+ .driver = {
+ .name = "twl4030_madc_battery",
+ },
+ .probe = twl4030_madc_battery_probe,
+ .remove = twl4030_madc_battery_remove,
+};
+module_platform_driver(twl4030_madc_battery_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
+MODULE_DESCRIPTION("twl4030_madc battery driver");
diff --git a/drivers/power/wm831x_backup.c b/drivers/power/wm831x_backup.c
index d9cc169f142..56fb509f4be 100644
--- a/drivers/power/wm831x_backup.c
+++ b/drivers/power/wm831x_backup.c
@@ -169,7 +169,8 @@ static int wm831x_backup_probe(struct platform_device *pdev)
struct power_supply *backup;
int ret;
- devdata = kzalloc(sizeof(struct wm831x_backup), GFP_KERNEL);
+ devdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_backup),
+ GFP_KERNEL);
if (devdata == NULL)
return -ENOMEM;
@@ -197,14 +198,8 @@ static int wm831x_backup_probe(struct platform_device *pdev)
backup->num_properties = ARRAY_SIZE(wm831x_backup_props);
backup->get_property = wm831x_backup_get_prop;
ret = power_supply_register(&pdev->dev, backup);
- if (ret)
- goto err_kmalloc;
return ret;
-
-err_kmalloc:
- kfree(devdata);
- return ret;
}
static int wm831x_backup_remove(struct platform_device *pdev)
@@ -212,8 +207,6 @@ static int wm831x_backup_remove(struct platform_device *pdev)
struct wm831x_backup *devdata = platform_get_drvdata(pdev);
power_supply_unregister(&devdata->backup);
- kfree(devdata->backup.name);
- kfree(devdata);
return 0;
}