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