diff options
Diffstat (limited to 'drivers/power')
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, ®); - 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, ®_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, ®_value); + else + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT2_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, ®_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, - ®_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, + ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ¶m); - if (ret < 0) - return ret; + ret = kstrtol(buf, 10, ¶m); + 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, ¶m); + 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 *)¶m->eoc_level); + of_property_read_u8(np, "charging-current", (u8 *)¶m->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, ®_data); + if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0) + goto state_set; + + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_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, ®_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, ®_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, ®_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, ®_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; } |
