Guenter, Please check this v4 patches, thanks for all your reviews/comments for this patch set.
Anton Vorontsov, I have add your Acked-by: into patch [1/3]; but for this v4 [2/3], we have to change it much according to Guenter's feedback and our internal discussions, so please have a look at this new [2/3] again, thank you.
v3 -> v4 changes: for patch [3/3] - define delays in HZ - update ab8500_read_sensor function, returning temp by parameter - remove ab8500_is_visible function - use clamp_val in set_min and set_max callback - remove unnecessary locks in remove and suspend functions - let abx500 and ab8500 use its own data structure for patch [2/3] - move the data tables from driver/power/ab8500_bmdata.c to include/linux/power/ab8500.h - rename driver/power/ab8500_bmdata.c to driver/power/ab8500_bm.c - rename these variable names to eliminate CamelCase warnings - add const attribute to these data
v2 -> v3 changes: - Add interface for converting voltage to temperature - Remove temp5 sensor since we cannot offer temperature read interface of it - Update hyst to use absolute temperature instead of a difference - Add the 3/3 patch
v1 -> v2 changes: - Add Documentation/hwmon/abx500 and Documentation/hwmon/abx500 - Make devices which cannot report milli-Celsius invisible - Add temp5_crit interface - Re-work the old find_active_thresholds() to threshold_updated() - Reset updated_min_alarm and updated_max_alarm at the end of each loop - Update the hyst mechamisn to make it works as real hyst - Remove non-stand attributes - Re-order the operations sequence inside probe and remove functions - Update all the lock usages to eliminate race conditions - Make attibutes index starts from 0 also changes: - Since the old [1/2] "ARM: ux500: rename ab8500 to abx500 for hwmon driver" has been merged by Samuel, so won't send it again. - Add another new patch "ab8500_btemp: export two symblols" as [2/2] of this patch set.
Hongbo Zhang (3): ab8500_btemp: make ab8500_btemp_get* interfaces public ab8500: re-arrange ab8500 power and temperature data tables hwmon: add ST-Ericsson ABX500 hwmon driver
Documentation/hwmon/ab8500 | 22 ++ Documentation/hwmon/abx500 | 28 ++ drivers/hwmon/Kconfig | 13 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ab8500.c | 208 ++++++++++++++ drivers/hwmon/abx500.c | 494 +++++++++++++++++++++++++++++++++ drivers/hwmon/abx500.h | 69 +++++ drivers/power/Makefile | 2 +- drivers/power/ab8500_bm.c | 341 +++++++++++++++++++++++ drivers/power/ab8500_bmdata.c | 519 ----------------------------------- drivers/power/ab8500_btemp.c | 5 +- drivers/power/ab8500_fg.c | 4 +- include/linux/mfd/abx500.h | 6 +- include/linux/mfd/abx500/ab8500-bm.h | 5 + include/linux/power/ab8500.h | 189 +++++++++++++ 15 files changed, 1380 insertions(+), 526 deletions(-) create mode 100644 Documentation/hwmon/ab8500 create mode 100644 Documentation/hwmon/abx500 create mode 100644 drivers/hwmon/ab8500.c create mode 100644 drivers/hwmon/abx500.c create mode 100644 drivers/hwmon/abx500.h create mode 100644 drivers/power/ab8500_bm.c delete mode 100644 drivers/power/ab8500_bmdata.c create mode 100644 include/linux/power/ab8500.h
Make ab8500_btemp_get_temp interface public, export it and also export the ab8500_btemp_get, ab8500_btemp_get_batctrl_temp interfaces, so that the ab8500 hwmon driver can use them.
Signed-off-by: Hongbo Zhang hongbo.zhang@linaro.org Acked-by: Anton Vorontsov anton@enomsg.org --- drivers/power/ab8500_btemp.c | 5 ++++- include/linux/mfd/abx500/ab8500-bm.h | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c index 20e2a7d..3359075 100644 --- a/drivers/power/ab8500_btemp.c +++ b/drivers/power/ab8500_btemp.c @@ -123,6 +123,7 @@ struct ab8500_btemp *ab8500_btemp_get(void)
return btemp; } +EXPORT_SYMBOL(ab8500_btemp_get);
/** * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance @@ -727,7 +728,7 @@ static void ab8500_btemp_periodic(struct ab8500_btemp *di, * * Returns battery temperature */ -static int ab8500_btemp_get_temp(struct ab8500_btemp *di) +int ab8500_btemp_get_temp(struct ab8500_btemp *di) { int temp = 0;
@@ -763,6 +764,7 @@ static int ab8500_btemp_get_temp(struct ab8500_btemp *di) } return temp; } +EXPORT_SYMBOL(ab8500_btemp_get_temp);
/** * ab8500_btemp_get_batctrl_temp() - get the temperature @@ -774,6 +776,7 @@ int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) { return btemp->bat_temp * 1000; } +EXPORT_SYMBOL(ab8500_btemp_get_batctrl_temp);
/** * ab8500_btemp_get_property() - get the btemp properties diff --git a/include/linux/mfd/abx500/ab8500-bm.h b/include/linux/mfd/abx500/ab8500-bm.h index 44310c9..4dd79f6 100644 --- a/include/linux/mfd/abx500/ab8500-bm.h +++ b/include/linux/mfd/abx500/ab8500-bm.h @@ -427,6 +427,7 @@ void ab8500_fg_reinit(void); void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); struct ab8500_btemp *ab8500_btemp_get(void); int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp); +int ab8500_btemp_get_temp(struct ab8500_btemp *btemp); struct ab8500_fg *ab8500_fg_get(void); int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev); int ab8500_fg_inst_curr_start(struct ab8500_fg *di); @@ -451,6 +452,10 @@ static int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) { return 0; } +static int ab8500_btemp_get_temp(struct ab8500_btemp *btemp) +{ + return 0; +} struct ab8500_fg *ab8500_fg_get(void) { return NULL;
This patch moves the data tables from driver/power/ab8500_bmdata.c to a common header file include/linux/power/ab8500.h, so that other modules such as ab8500 hwmon can use these data. This patch also renames these variable names to eliminate CamelCase warnings from checkpatch.pl, and adds const attribute to these data.
Signed-off-by: Hongbo Zhang hongbo.zhang@linaro.org --- drivers/power/Makefile | 2 +- drivers/power/ab8500_bm.c | 341 +++++++++++++++++++++++++++ drivers/power/ab8500_bmdata.c | 519 ------------------------------------------ drivers/power/ab8500_fg.c | 4 +- include/linux/mfd/abx500.h | 6 +- include/linux/power/ab8500.h | 189 +++++++++++++++ 6 files changed, 536 insertions(+), 525 deletions(-) create mode 100644 drivers/power/ab8500_bm.c delete mode 100644 drivers/power/ab8500_bmdata.c create mode 100644 include/linux/power/ab8500.h
diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 22c8913..8875c03 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -38,7 +38,7 @@ obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o -obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o +obj-$(CONFIG_AB8500_BM) += ab8500_bm.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 diff --git a/drivers/power/ab8500_bm.c b/drivers/power/ab8500_bm.c new file mode 100644 index 0000000..7e803be --- /dev/null +++ b/drivers/power/ab8500_bm.c @@ -0,0 +1,341 @@ +#include <linux/export.h> +#include <linux/power_supply.h> +#include <linux/of.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/power/ab8500.h> + +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(ab8500_temp_tbl), + .r_to_t_tbl = ab8500_temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(ab8500_cap_tbl), + .v_to_cap_tbl = ab8500_cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(ab8500_temp_to_batres_tbl), + .batres_tbl = ab8500_temp_to_batres_tbl, +}, +{ + .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(ab8500_temp_tbl_a), + .r_to_t_tbl = ab8500_temp_tbl_a, + .n_v_cap_tbl_elements = ARRAY_SIZE(ab8500_cap_tbl_a), + .v_to_cap_tbl = ab8500_cap_tbl_a, + .n_batres_tbl_elements = ARRAY_SIZE(ab8500_temp_to_batres_tbl), + .batres_tbl = ab8500_temp_to_batres_tbl, +}, +{ + .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(ab8500_temp_tbl_b), + .r_to_t_tbl = ab8500_temp_tbl_b, + .n_v_cap_tbl_elements = ARRAY_SIZE(ab8500_cap_tbl_b), + .v_to_cap_tbl = ab8500_cap_tbl_b, + .n_batres_tbl_elements = ARRAY_SIZE(ab8500_temp_to_batres_tbl), + .batres_tbl = ab8500_temp_to_batres_tbl, +}, +}; + +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(ab8500_temp_tbl), + .r_to_t_tbl = ab8500_temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(ab8500_cap_tbl), + .v_to_cap_tbl = ab8500_cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(ab8500_temp_to_batres_tbl), + .batres_tbl = ab8500_temp_to_batres_tbl, +}, +/* + * 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(ab8500_temp_tbl), + .r_to_t_tbl = ab8500_temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(ab8500_cap_tbl), + .v_to_cap_tbl = ab8500_cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(ab8500_temp_to_batres_tbl), + .batres_tbl = ab8500_temp_to_batres_tbl, +}, +{ + .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(ab8500_temp_tbl), + .r_to_t_tbl = ab8500_temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(ab8500_cap_tbl), + .v_to_cap_tbl = ab8500_cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(ab8500_temp_to_batres_tbl), + .batres_tbl = ab8500_temp_to_batres_tbl, +}, +{ + .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(ab8500_temp_tbl), + .r_to_t_tbl = ab8500_temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(ab8500_cap_tbl), + .v_to_cap_tbl = ab8500_cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(ab8500_temp_to_batres_tbl), + .batres_tbl = ab8500_temp_to_batres_tbl, +}, +}; + +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 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 = + ab8500_temp_to_batres_tbl_ext; + } else if (strncmp(bat_tech, "LION", 4) == 0) { + btype->batres_tbl = + ab8500_temp_to_batres_tbl_9100; + } else { + btype->batres_tbl = + ab8500_temp_to_batres_tbl; + } + } + of_node_put(np_bat_supply); + return 0; +} diff --git a/drivers/power/ab8500_bmdata.c b/drivers/power/ab8500_bmdata.c deleted file mode 100644 index f034ae4..0000000 --- a/drivers/power/ab8500_bmdata.c +++ /dev/null @@ -1,519 +0,0 @@ -#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 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_fg.c b/drivers/power/ab8500_fg.c index b3bf178..d21456d 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -811,7 +811,7 @@ static int ab8500_fg_bat_voltage(struct ab8500_fg *di) static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage) { int i, tbl_size; - struct abx500_v_to_cap *tbl; + const struct abx500_v_to_cap *tbl; int cap = 0;
tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, @@ -863,7 +863,7 @@ static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di) static int ab8500_fg_battery_resistance(struct ab8500_fg *di) { int i, tbl_size; - struct batres_vs_temp *tbl; + const struct batres_vs_temp *tbl; int resist = 0;
tbl = di->bat->bat_type[di->bat->batt_id].batres_tbl; diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 2138bd3..03fc0f1 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -173,11 +173,11 @@ struct abx500_battery_type { int low_high_vol_lvl; int battery_resistance; int n_temp_tbl_elements; - struct abx500_res_to_temp *r_to_t_tbl; + const struct abx500_res_to_temp *r_to_t_tbl; int n_v_cap_tbl_elements; - struct abx500_v_to_cap *v_to_cap_tbl; + const struct abx500_v_to_cap *v_to_cap_tbl; int n_batres_tbl_elements; - struct batres_vs_temp *batres_tbl; + const struct batres_vs_temp *batres_tbl; };
/** diff --git a/include/linux/power/ab8500.h b/include/linux/power/ab8500.h new file mode 100644 index 0000000..3916b12 --- /dev/null +++ b/include/linux/power/ab8500.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) ST-Ericsson 2013 + * Author: Hongbo Zhang hongbo.zhang@linaro.com + * License terms: GNU General Public License v2 + */ + +#ifndef PWR_AB8500_H +#define PWR_AB8500_H + +#include <linux/mfd/abx500.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 const struct abx500_res_to_temp ab8500_temp_tbl_a[] = { + {-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 const struct abx500_res_to_temp ab8500_temp_tbl_b[] = { + {-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 const struct abx500_v_to_cap ab8500_cap_tbl_a[] = { + {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 const struct abx500_v_to_cap ab8500_cap_tbl_b[] = { + {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 const struct abx500_v_to_cap ab8500_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 const struct abx500_res_to_temp ab8500_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 const struct batres_vs_temp ab8500_temp_to_batres_tbl[] = { + { 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 const struct batres_vs_temp ab8500_temp_to_batres_tbl_ext[] = { + { 60, 300}, + { 30, 300}, + { 20, 300}, + { 10, 300}, + { 00, 300}, + {-10, 300}, + {-20, 300}, +}; + +/* battery resistance table for LI ION 9100 battery */ +static const struct batres_vs_temp ab8500_temp_to_batres_tbl_9100[] = { + { 60, 180}, + { 30, 180}, + { 20, 180}, + { 10, 180}, + { 00, 180}, + {-10, 180}, + {-20, 180}, +}; + +#endif /* PWR_AB8500_H */
On Fri, Mar 08, 2013 at 04:13:30PM +0800, Hongbo Zhang wrote:
This patch moves the data tables from driver/power/ab8500_bmdata.c to a common header file include/linux/power/ab8500.h, so that other modules such as ab8500 hwmon can use these data. This patch also renames these variable names to eliminate CamelCase warnings from checkpatch.pl, and adds const attribute to these data.
Signed-off-by: Hongbo Zhang hongbo.zhang@linaro.org
[ ... ]
/** diff --git a/include/linux/power/ab8500.h b/include/linux/power/ab8500.h new file mode 100644 index 0000000..3916b12 --- /dev/null +++ b/include/linux/power/ab8500.h @@ -0,0 +1,189 @@ +/*
- Copyright (C) ST-Ericsson 2013
- Author: Hongbo Zhang hongbo.zhang@linaro.com
- License terms: GNU General Public License v2
- */
+#ifndef PWR_AB8500_H +#define PWR_AB8500_H
+#include <linux/mfd/abx500.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 const struct abx500_res_to_temp ab8500_temp_tbl_a[] = {
- {-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 const struct abx500_res_to_temp ab8500_temp_tbl_b[] = {
- {-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 const struct abx500_v_to_cap ab8500_cap_tbl_a[] = {
- {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 const struct abx500_v_to_cap ab8500_cap_tbl_b[] = {
- {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 const struct abx500_v_to_cap ab8500_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 const struct abx500_res_to_temp ab8500_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 const struct batres_vs_temp ab8500_temp_to_batres_tbl[] = {
- { 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 const struct batres_vs_temp ab8500_temp_to_batres_tbl_ext[] = {
- { 60, 300},
- { 30, 300},
- { 20, 300},
- { 10, 300},
- { 00, 300},
- {-10, 300},
- {-20, 300},
+};
+/* battery resistance table for LI ION 9100 battery */ +static const struct batres_vs_temp ab8500_temp_to_batres_tbl_9100[] = {
- { 60, 180},
- { 30, 180},
- { 20, 180},
- { 10, 180},
- { 00, 180},
- {-10, 180},
- {-20, 180},
+};
I don't think it is a good idea to define static variables in an include file.
Guenter
On 12 March 2013 13:38, Guenter Roeck linux@roeck-us.net wrote:
On Fri, Mar 08, 2013 at 04:13:30PM +0800, Hongbo Zhang wrote:
This patch moves the data tables from driver/power/ab8500_bmdata.c to a common header file include/linux/power/ab8500.h, so that other modules such as ab8500 hwmon can use these data. This patch also renames these variable names to eliminate CamelCase warnings from checkpatch.pl, and adds const attribute to these data.
Signed-off-by: Hongbo Zhang hongbo.zhang@linaro.org
[ ... ]
/** diff --git a/include/linux/power/ab8500.h b/include/linux/power/ab8500.h new file mode 100644 index 0000000..3916b12 --- /dev/null +++ b/include/linux/power/ab8500.h @@ -0,0 +1,189 @@ +/*
- Copyright (C) ST-Ericsson 2013
- Author: Hongbo Zhang hongbo.zhang@linaro.com
- License terms: GNU General Public License v2
- */
+#ifndef PWR_AB8500_H +#define PWR_AB8500_H
+#include <linux/mfd/abx500.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 const struct abx500_res_to_temp ab8500_temp_tbl_a[] = {
{-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 const struct abx500_res_to_temp ab8500_temp_tbl_b[] = {
{-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 const struct abx500_v_to_cap ab8500_cap_tbl_a[] = {
{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 const struct abx500_v_to_cap ab8500_cap_tbl_b[] = {
{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 const struct abx500_v_to_cap ab8500_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 const struct abx500_res_to_temp ab8500_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 const struct batres_vs_temp ab8500_temp_to_batres_tbl[] = {
{ 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 const struct batres_vs_temp ab8500_temp_to_batres_tbl_ext[] = {
{ 60, 300},
{ 30, 300},
{ 20, 300},
{ 10, 300},
{ 00, 300},
{-10, 300},
{-20, 300},
+};
+/* battery resistance table for LI ION 9100 battery */ +static const struct batres_vs_temp ab8500_temp_to_batres_tbl_9100[] = {
{ 60, 180},
{ 30, 180},
{ 20, 180},
{ 10, 180},
{ 00, 180},
{-10, 180},
{-20, 180},
+};
I don't think it is a good idea to define static variables in an include file.
Hmm.. this part is more difficult than the hwmon itself from my point of view,
Lee Jones, we still need discussion about where to place these data.
Guenter
Hi,
On 03/12/2013 10:44 AM, Hongbo Zhang wrote:
On 12 March 2013 13:38, Guenter Roeck linux@roeck-us.net wrote:
On Fri, Mar 08, 2013 at 04:13:30PM +0800, Hongbo Zhang wrote:
This patch moves the data tables from driver/power/ab8500_bmdata.c to a common header file include/linux/power/ab8500.h, so that other modules such as ab8500 hwmon can use these data. This patch also renames these variable names to eliminate CamelCase warnings from checkpatch.pl, and adds const attribute to these data.
[...]
I don't think it is a good idea to define static variables in an include file.
Hmm.. this part is more difficult than the hwmon itself from my point of view,
I think Guenter meant leaving the data tables inside a .c file in drivers/power/, but declaring them as extern variables in a public header file in include/linux/power/, instead of having extern declarations in the importing driver (hwmon). This is the standard practice when variables need to be shared between drivers.
Regards, Francesco
On Sat, Mar 16, 2013 at 11:28:39AM +0100, Francesco Lavra wrote:
Hi,
On 03/12/2013 10:44 AM, Hongbo Zhang wrote:
On 12 March 2013 13:38, Guenter Roeck linux@roeck-us.net wrote:
On Fri, Mar 08, 2013 at 04:13:30PM +0800, Hongbo Zhang wrote:
This patch moves the data tables from driver/power/ab8500_bmdata.c to a common header file include/linux/power/ab8500.h, so that other modules such as ab8500 hwmon can use these data. This patch also renames these variable names to eliminate CamelCase warnings from checkpatch.pl, and adds const attribute to these data.
[...]
I don't think it is a good idea to define static variables in an include file.
Hmm.. this part is more difficult than the hwmon itself from my point of view,
I think Guenter meant leaving the data tables inside a .c file in drivers/power/, but declaring them as extern variables in a public header file in include/linux/power/, instead of having extern declarations in the importing driver (hwmon). This is the standard practice when variables need to be shared between drivers.
Correct. Could be exported either as extern variables or with access functions.
Thanks, Guenter
On 16 March 2013 18:28, Francesco Lavra francescolavra.fl@gmail.com wrote:
Hi,
On 03/12/2013 10:44 AM, Hongbo Zhang wrote:
On 12 March 2013 13:38, Guenter Roeck linux@roeck-us.net wrote:
On Fri, Mar 08, 2013 at 04:13:30PM +0800, Hongbo Zhang wrote:
This patch moves the data tables from driver/power/ab8500_bmdata.c to a common header file include/linux/power/ab8500.h, so that other modules such as ab8500 hwmon can use these data. This patch also renames these variable names to eliminate CamelCase warnings from checkpatch.pl, and adds const attribute to these data.
[...]
I don't think it is a good idea to define static variables in an include file.
Hmm.. this part is more difficult than the hwmon itself from my point of view,
I think Guenter meant leaving the data tables inside a .c file in drivers/power/, but declaring them as extern variables in a public header file in include/linux/power/, instead of having extern declarations in the importing driver (hwmon). This is the standard practice when variables need to be shared between drivers.
Get it, thank you Francesco.
Regards, Francesco
Each of ST-Ericsson X500 chip set series consists of both ABX500 and DBX500 chips. This is ABX500 hwmon driver, where the abx500.c is a common layer for all ABX500s, and the ab8500.c is specific for AB8500 chip. Under this designed structure, other chip specific files can be added simply using the same common layer abx500.c.
Signed-off-by: Hongbo Zhang hongbo.zhang@linaro.org --- Documentation/hwmon/ab8500 | 22 ++ Documentation/hwmon/abx500 | 28 +++ drivers/hwmon/Kconfig | 13 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/ab8500.c | 208 +++++++++++++++++++ drivers/hwmon/abx500.c | 494 +++++++++++++++++++++++++++++++++++++++++++++ drivers/hwmon/abx500.h | 69 +++++++ 7 files changed, 835 insertions(+) create mode 100644 Documentation/hwmon/ab8500 create mode 100644 Documentation/hwmon/abx500 create mode 100644 drivers/hwmon/ab8500.c create mode 100644 drivers/hwmon/abx500.c create mode 100644 drivers/hwmon/abx500.h
diff --git a/Documentation/hwmon/ab8500 b/Documentation/hwmon/ab8500 new file mode 100644 index 0000000..cf169c8 --- /dev/null +++ b/Documentation/hwmon/ab8500 @@ -0,0 +1,22 @@ +Kernel driver ab8500 +==================== + +Supported chips: + * ST-Ericsson AB8500 + Prefix: 'ab8500' + Addresses scanned: - + Datasheet: http://www.stericsson.com/developers/documentation.jsp + +Authors: + Martin Persson martin.persson@stericsson.com + Hongbo Zhang hongbo.zhang@linaro.org + +Description +----------- + +See also Documentation/hwmon/abx500. This is the ST-Ericsson AB8500 specific +driver. + +Currently only the AB8500 internal sensor and one external sensor for battery +temperature are monitored. Other GPADC channels can also be monitored if needed +in future. diff --git a/Documentation/hwmon/abx500 b/Documentation/hwmon/abx500 new file mode 100644 index 0000000..319a058 --- /dev/null +++ b/Documentation/hwmon/abx500 @@ -0,0 +1,28 @@ +Kernel driver abx500 +==================== + +Supported chips: + * ST-Ericsson ABx500 series + Prefix: 'abx500' + Addresses scanned: - + Datasheet: http://www.stericsson.com/developers/documentation.jsp + +Authors: + Martin Persson martin.persson@stericsson.com + Hongbo Zhang hongbo.zhang@linaro.org + +Description +----------- + +Every ST-Ericsson Ux500 SOC consists of both ABx500 and DBx500 physically, +this is kernel hwmon driver for ABx500. + +There are some GPADCs inside ABx500 which are designed for connecting to +thermal sensors, and there is also a thermal sensor inside ABx500 too, which +raises interrupt when critical temperature reached. + +This abx500 is a common layer which can monitor all of the sensors, every +specific abx500 chip has its special configurations in its own file, e.g. some +sensors can be configured invisible if they are not available on that chip, and +the corresponding gpadc_addr should be set to 0, thus this sensor won't be +polled. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 32f238f..0a6fd21 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -39,6 +39,19 @@ config HWMON_DEBUG_CHIP
comment "Native drivers"
+config SENSORS_AB8500 + tristate "AB8500 thermal monitoring" + depends on AB8500_GPADC + default n + help + If you say yes here you get support for the thermal sensor part + of the AB8500 chip. The driver includes thermal management for + AB8500 die and two GPADC channels. The GPADC channel are preferably + used to access sensors outside the AB8500 chip. + + This driver can also be built as a module. If so, the module + will be called abx500-temp. + config SENSORS_ABITUGURU tristate "Abit uGuru (rev 1 & 2)" depends on X86 && DMI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5da2874..06dfe85 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
+obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7314) += ad7314.o diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c new file mode 100644 index 0000000..da165fa --- /dev/null +++ b/drivers/hwmon/ab8500.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) ST-Ericsson 2010 - 2013 + * Author: Martin Persson martin.persson@stericsson.com + * Hongbo Zhang hongbo.zhang@linaro.org + * License Terms: GNU General Public License v2 + * + * When the AB8500 thermal warning temperature is reached (threshold cannot + * be changed by SW), an interrupt is set, and if no further action is taken + * within a certain time frame, pm_power off will be called. + * + * When AB8500 thermal shutdown temperature is reached a hardware shutdown of + * the AB8500 will occur. + */ + +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power/ab8500.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include "abx500.h" + +#define DEFAULT_POWER_OFF_DELAY (HZ * 10) +#define THERMAL_VCC 1800 +#define PULL_UP_RESISTOR 47000 +/* Number of monitored sensors should not greater than NUM_SENSORS */ +#define NUM_MONITORED_SENSORS 4 + +struct ab8500_gpadc_cfg { + const struct abx500_res_to_temp *temp_tbl; + int tbl_sz; + int vcc; + int r_up; +}; + +struct ab8500_temp { + struct ab8500_gpadc *gpadc; + struct ab8500_btemp *btemp; + struct delayed_work power_off_work; + struct ab8500_gpadc_cfg cfg; + struct abx500_temp *abx500_data; +}; + +/* + * The hardware connection is like this: + * VCC----[ R_up ]-----[ NTC ]----GND + * where R_up is pull-up resistance, and GPADC measures voltage on NTC. + * and res_to_temp table is strictly sorted by falling resistance values. + */ +static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg, + int v_ntc, int *temp) +{ + int r_ntc, i = 0, tbl_sz = cfg->tbl_sz; + const struct abx500_res_to_temp *tbl = cfg->temp_tbl; + + if (cfg->vcc < 0 || v_ntc >= cfg->vcc) + return -EINVAL; + + r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc); + if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist) + return -EINVAL; + + while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist) + && i < tbl_sz - 2) + i++; + + /* return milli-Celsius */ + *temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 * + (r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + + return 0; +} + +static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp) +{ + int voltage, ret; + struct ab8500_temp *ab8500_data = data->plat_data; + + if (sensor == BAT_CTRL) + *temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp); + + else if (sensor == BTEMP_BALL) + *temp = ab8500_btemp_get_temp(ab8500_data->btemp); + + else { + voltage = ab8500_gpadc_convert(ab8500_data->gpadc, sensor); + if (voltage < 0) + return voltage; + + ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp); + if (ret < 0) + return ret; + } + + return 0; +} + +static void ab8500_thermal_power_off(struct work_struct *work) +{ + struct ab8500_temp *ab8500_data = container_of(work, + struct ab8500_temp, power_off_work.work); + struct abx500_temp *abx500_data = ab8500_data->abx500_data; + + dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n"); + + pm_power_off(); +} + +static ssize_t ab8500_show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "ab8500\n"); +} + +static ssize_t ab8500_show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + char *label; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int index = attr->index; + + switch (index) { + case 1: + label = "ext_adc1"; + break; + case 2: + label = "ext_adc2"; + break; + case 3: + label = "bat_temp"; + break; + case 4: + label = "bat_ctrl"; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%s\n", label); +} + +static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) +{ + struct ab8500_temp *ab8500_data = data->plat_data; + + dev_warn(&data->pdev->dev, "Power off in %d s\n", + DEFAULT_POWER_OFF_DELAY / HZ); + + schedule_delayed_work(&ab8500_data->power_off_work, + DEFAULT_POWER_OFF_DELAY); + return 0; +} + +int abx500_hwmon_init(struct abx500_temp *data) +{ + struct ab8500_temp *ab8500_data; + + ab8500_data = devm_kzalloc(&data->pdev->dev, sizeof(*ab8500_data), + GFP_KERNEL); + if (!ab8500_data) + return -ENOMEM; + + ab8500_data->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + if (IS_ERR(ab8500_data->gpadc)) + return PTR_ERR(ab8500_data->gpadc); + + ab8500_data->btemp = ab8500_btemp_get(); + if (IS_ERR(ab8500_data->btemp)) + return PTR_ERR(ab8500_data->btemp); + + INIT_DELAYED_WORK(&ab8500_data->power_off_work, + ab8500_thermal_power_off); + + ab8500_data->cfg.vcc = THERMAL_VCC; + ab8500_data->cfg.r_up = PULL_UP_RESISTOR; + ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a; + ab8500_data->cfg.tbl_sz = ARRAY_SIZE(ab8500_temp_tbl_a); + + data->plat_data = ab8500_data; + + /* + * ADC_AUX1 and ADC_AUX2, connected to external NTC + * BTEMP_BALL and BAT_CTRL, fixed usage + */ + data->gpadc_addr[0] = ADC_AUX1; + data->gpadc_addr[1] = ADC_AUX2; + data->gpadc_addr[2] = BTEMP_BALL; + data->gpadc_addr[3] = BAT_CTRL; + data->monitored_sensors = NUM_MONITORED_SENSORS; + + data->ops.read_sensor = ab8500_read_sensor; + data->ops.irq_handler = ab8500_temp_irq_handler; + data->ops.show_name = ab8500_show_name; + data->ops.show_label = ab8500_show_label; + data->ops.is_visible = NULL; + + return 0; +} +EXPORT_SYMBOL(abx500_hwmon_init); + +MODULE_AUTHOR("Hongbo Zhang hongbo.zhang@linaro.org"); +MODULE_DESCRIPTION("AB8500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c new file mode 100644 index 0000000..1da5910 --- /dev/null +++ b/drivers/hwmon/abx500.c @@ -0,0 +1,494 @@ +/* + * Copyright (C) ST-Ericsson 2010 - 2013 + * Author: Martin Persson martin.persson@stericsson.com + * Hongbo Zhang hongbo.zhang@linaro.org + * License Terms: GNU General Public License v2 + * + * ABX500 does not provide auto ADC, so to monitor the required temperatures, + * a periodic work is used. It is more important to not wake up the CPU than + * to perform this job, hence the use of a deferred delay. + * + * A deferred delay for thermal monitor is considered safe because: + * If the chip gets too hot during a sleep state it's most likely due to + * external factors, such as the surrounding temperature. I.e. no SW decisions + * will make any difference. + */ + +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/workqueue.h> +#include "abx500.h" + +#define DEFAULT_MONITOR_DELAY HZ +#define DEFAULT_MAX_TEMP 130 + +static inline void schedule_monitor(struct abx500_temp *data) +{ + data->work_active = true; + schedule_delayed_work(&data->work, DEFAULT_MONITOR_DELAY); +} + +static void threshold_updated(struct abx500_temp *data) +{ + int i; + for (i = 0; i < data->monitored_sensors; i++) + if (data->max[i] != 0 || data->min[i] != 0) { + schedule_monitor(data); + return; + } + + dev_dbg(&data->pdev->dev, "No active thresholds.\n"); + cancel_delayed_work_sync(&data->work); + data->work_active = false; +} + +static void gpadc_monitor(struct work_struct *work) +{ + int temp, i, ret; + char alarm_node[30]; + bool updated_min_alarm = false; + bool updated_max_alarm = false; + struct abx500_temp *data; + + data = container_of(work, struct abx500_temp, work.work); + mutex_lock(&data->lock); + + for (i = 0; i < data->monitored_sensors; i++) { + /* Thresholds are considered inactive if set to 0 */ + if (data->max[i] == 0 && data->min[i] == 0) + continue; + + if (data->max[i] < data->min[i]) + continue; + + ret = data->ops.read_sensor(data, data->gpadc_addr[i], &temp); + if (ret < 0) { + dev_err(&data->pdev->dev, "GPADC read failed\n"); + continue; + } + + if (data->min[i] != 0) { + if (temp < data->min[i]) { + if (data->min_alarm[i] == false) { + data->min_alarm[i] = true; + updated_min_alarm = true; + } + } else { + if (data->min_alarm[i] == true) { + data->min_alarm[i] = false; + updated_min_alarm = true; + } + } + } + if (data->max[i] != 0) { + if (temp > data->max[i]) { + if (data->max_alarm[i] == false) { + data->max_alarm[i] = true; + updated_max_alarm = true; + } + } else if (temp < data->max[i] - data->max_hyst[i]) { + if (data->max_alarm[i] == true) { + data->max_alarm[i] = false; + updated_max_alarm = true; + } + } + } + + if (updated_min_alarm) { + ret = sprintf(alarm_node, "temp%d_min_alarm", i); + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_alarm) { + ret = sprintf(alarm_node, "temp%d_max_alarm", i); + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + + updated_min_alarm = false; + updated_max_alarm = false; + } + + schedule_monitor(data); + mutex_unlock(&data->lock); +} + +/* HWMON sysfs interfaces */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* Show chip name */ + return data->ops.show_name(dev, devattr, buf); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* Show each sensor label */ + return data->ops.show_label(dev, devattr, buf); +} + +static ssize_t show_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int ret, temp; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + u8 gpadc_addr = data->gpadc_addr[attr->index]; + + ret = data->ops.read_sensor(data, gpadc_addr, &temp); + if (ret < 0) + dev_err(&data->pdev->dev, "GPADC read failed\n"); + + return sprintf(buf, "%d\n", temp); +} + +/* Set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = kstrtol(buf, 10, &val); + if (res < 0) + return res; + + clamp_val(val, 0, data->max[attr->index]); + + mutex_lock(&data->lock); + data->min[attr->index] = val; + threshold_updated(data); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = kstrtol(buf, 10, &val); + if (res < 0) + return res; + + clamp_val(val, data->min[attr->index], DEFAULT_MAX_TEMP); + + mutex_lock(&data->lock); + data->max[attr->index] = val; + threshold_updated(data); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = kstrtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->max_hyst[attr->index] = val; + threshold_updated(data); + mutex_unlock(&data->lock); + + return count; +} + +/* Show functions (RO nodes) */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%ld\n", data->min[attr->index]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%ld\n", data->max[attr->index]); +} + +static ssize_t show_max_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%ld\n", data->max_hyst[attr->index]); +} + +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%d\n", data->min_alarm[attr->index]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%d\n", data->max_alarm[attr->index]); +} + +static mode_t abx500_attrs_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct abx500_temp *data = dev_get_drvdata(dev); + + if (data->ops.is_visible) + return data->ops.is_visible(attr, n); + else + return attr->mode; +} + +/* Chip name, required by hwmon */ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +/* GPADC - SENSOR1 */ +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 0); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 0); + +/* GPADC - SENSOR2 */ +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 1); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); + +/* GPADC - SENSOR3 */ +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 2); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 2); + +/* GPADC - SENSOR4 */ +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 3); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 3); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 3); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 3); + +struct attribute *abx500_temp_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group abx500_temp_group = { + .attrs = abx500_temp_attributes, + .is_visible = abx500_attrs_visible, +}; + +static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct abx500_temp *data = platform_get_drvdata(pdev); + + data->ops.irq_handler(irq, data); + return IRQ_HANDLED; +} + +static int setup_irqs(struct platform_device *pdev) +{ + int ret; + int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM"); + + if (irq < 0) { + dev_err(&pdev->dev, "Get irq by name failed\n"); + return irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + abx500_temp_irq_handler, IRQF_NO_SUSPEND, "abx500-temp", pdev); + if (ret < 0) + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + + return ret; +} + +static int abx500_temp_probe(struct platform_device *pdev) +{ + struct abx500_temp *data; + int err; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->pdev = pdev; + mutex_init(&data->lock); + + /* Chip specific initialization */ + err = abx500_hwmon_init(data); + if (err < 0 || !data->ops.read_sensor || !data->ops.show_name + || !data->ops.show_label) { + dev_err(&pdev->dev, "ABx500 hwmon init failed"); + return -EINVAL; + } + + INIT_DEFERRABLE_WORK(&data->work, gpadc_monitor); + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + return err; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit_sysfs_group; + } + + if (data->ops.irq_handler) { + err = setup_irqs(pdev); + if (err < 0) { + dev_err(&pdev->dev, "irq setup failed (%d)\n", err); + goto exit_hwmon_reg; + } + } + return 0; + +exit_hwmon_reg: + hwmon_device_unregister(data->hwmon_dev); +exit_sysfs_group: + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); + return err; +} + +static int abx500_temp_remove(struct platform_device *pdev) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&data->work); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); + + return 0; +} + +static int abx500_temp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + if (data->work_active) + cancel_delayed_work_sync(&data->work); + + return 0; +} + +static int abx500_temp_resume(struct platform_device *pdev) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + if (data->work_active) + schedule_monitor(data); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id abx500_temp_match[] = { + { .compatible = "stericsson,abx500-temp" }, + {}, +}; +#endif + +static struct platform_driver abx500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "abx500-temp", + .of_match_table = of_match_ptr(abx500_temp_match), + }, + .suspend = abx500_temp_suspend, + .resume = abx500_temp_resume, + .probe = abx500_temp_probe, + .remove = abx500_temp_remove, +}; + +module_platform_driver(abx500_temp_driver); + +MODULE_AUTHOR("Martin Persson martin.persson@stericsson.com"); +MODULE_DESCRIPTION("ABX500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h new file mode 100644 index 0000000..9b295e6 --- /dev/null +++ b/drivers/hwmon/abx500.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) ST-Ericsson 2010 - 2013 + * License terms: GNU General Public License v2 + * Author: Martin Persson martin.persson@stericsson.com + * Hongbo Zhang hongbo.zhang@linaro.com + */ + +#ifndef _ABX500_H +#define _ABX500_H + +#define NUM_SENSORS 5 + +struct abx500_temp; + +/* + * struct abx500_temp_ops - abx500 chip specific ops + * @read_sensor: reads gpadc output + * @irq_handler: irq handler + * @show_name: hwmon device name + * @show_label: hwmon attribute label + * @is_visible: is attribute visible + */ +struct abx500_temp_ops { + int (*read_sensor)(struct abx500_temp *, u8, int *); + int (*irq_handler)(int, struct abx500_temp *); + ssize_t (*show_name)(struct device *, + struct device_attribute *, char *); + ssize_t (*show_label) (struct device *, + struct device_attribute *, char *); + int (*is_visible)(struct attribute *, int); +}; + +/* + * struct abx500_temp - representation of temp mon device + * @pdev: platform device + * @hwmon_dev: hwmon device + * @ops: abx500 chip specific ops + * @gpadc_addr: gpadc channel address + * @min: sensor temperature min value + * @max: sensor temperature max value + * @max_hyst: sensor temperature hysteresis value for max limit + * @min_alarm: sensor temperature min alarm + * @max_alarm: sensor temperature max alarm + * @work: delayed work scheduled to monitor temperature periodically + * @work_active: True if work is active + * @lock: mutex + * @monitored_sensors: number of monitored sensors + * @plat_data: private usage, usually points to platform specific data + */ +struct abx500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + struct abx500_temp_ops ops; + u8 gpadc_addr[NUM_SENSORS]; + unsigned long min[NUM_SENSORS]; + unsigned long max[NUM_SENSORS]; + unsigned long max_hyst[NUM_SENSORS]; + bool min_alarm[NUM_SENSORS]; + bool max_alarm[NUM_SENSORS]; + struct delayed_work work; + bool work_active; + struct mutex lock; + int monitored_sensors; + void *plat_data; +}; + +int abx500_hwmon_init(struct abx500_temp *data); + +#endif /* _ABX500_H */
On Fri, Mar 08, 2013 at 04:13:31PM +0800, Hongbo Zhang wrote:
Each of ST-Ericsson X500 chip set series consists of both ABX500 and DBX500 chips. This is ABX500 hwmon driver, where the abx500.c is a common layer for all ABX500s, and the ab8500.c is specific for AB8500 chip. Under this designed structure, other chip specific files can be added simply using the same common layer abx500.c.
Signed-off-by: Hongbo Zhang hongbo.zhang@linaro.org
Documentation/hwmon/ab8500 | 22 ++ Documentation/hwmon/abx500 | 28 +++ drivers/hwmon/Kconfig | 13 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/ab8500.c | 208 +++++++++++++++++++ drivers/hwmon/abx500.c | 494 +++++++++++++++++++++++++++++++++++++++++++++ drivers/hwmon/abx500.h | 69 +++++++ 7 files changed, 835 insertions(+) create mode 100644 Documentation/hwmon/ab8500 create mode 100644 Documentation/hwmon/abx500 create mode 100644 drivers/hwmon/ab8500.c create mode 100644 drivers/hwmon/abx500.c create mode 100644 drivers/hwmon/abx500.h
diff --git a/Documentation/hwmon/ab8500 b/Documentation/hwmon/ab8500 new file mode 100644 index 0000000..cf169c8 --- /dev/null +++ b/Documentation/hwmon/ab8500 @@ -0,0 +1,22 @@ +Kernel driver ab8500 +====================
+Supported chips:
- ST-Ericsson AB8500
- Prefix: 'ab8500'
- Addresses scanned: -
- Datasheet: http://www.stericsson.com/developers/documentation.jsp
+Authors:
Martin Persson <martin.persson@stericsson.com>
Hongbo Zhang <hongbo.zhang@linaro.org>
+Description +-----------
+See also Documentation/hwmon/abx500. This is the ST-Ericsson AB8500 specific +driver.
+Currently only the AB8500 internal sensor and one external sensor for battery +temperature are monitored. Other GPADC channels can also be monitored if needed +in future. diff --git a/Documentation/hwmon/abx500 b/Documentation/hwmon/abx500 new file mode 100644 index 0000000..319a058 --- /dev/null +++ b/Documentation/hwmon/abx500 @@ -0,0 +1,28 @@ +Kernel driver abx500 +====================
+Supported chips:
- ST-Ericsson ABx500 series
- Prefix: 'abx500'
- Addresses scanned: -
- Datasheet: http://www.stericsson.com/developers/documentation.jsp
+Authors:
Martin Persson <martin.persson@stericsson.com>
Hongbo Zhang <hongbo.zhang@linaro.org>
+Description +-----------
+Every ST-Ericsson Ux500 SOC consists of both ABx500 and DBx500 physically, +this is kernel hwmon driver for ABx500.
+There are some GPADCs inside ABx500 which are designed for connecting to +thermal sensors, and there is also a thermal sensor inside ABx500 too, which +raises interrupt when critical temperature reached.
+This abx500 is a common layer which can monitor all of the sensors, every +specific abx500 chip has its special configurations in its own file, e.g. some +sensors can be configured invisible if they are not available on that chip, and +the corresponding gpadc_addr should be set to 0, thus this sensor won't be +polled. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 32f238f..0a6fd21 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -39,6 +39,19 @@ config HWMON_DEBUG_CHIP comment "Native drivers" +config SENSORS_AB8500
- tristate "AB8500 thermal monitoring"
- depends on AB8500_GPADC
- default n
- help
If you say yes here you get support for the thermal sensor part
of the AB8500 chip. The driver includes thermal management for
AB8500 die and two GPADC channels. The GPADC channel are preferably
used to access sensors outside the AB8500 chip.
This driver can also be built as a module. If so, the module
will be called abx500-temp.
config SENSORS_ABITUGURU tristate "Abit uGuru (rev 1 & 2)" depends on X86 && DMI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5da2874..06dfe85 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o +obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7314) += ad7314.o diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c new file mode 100644 index 0000000..da165fa --- /dev/null +++ b/drivers/hwmon/ab8500.c @@ -0,0 +1,208 @@ +/*
- Copyright (C) ST-Ericsson 2010 - 2013
- Author: Martin Persson martin.persson@stericsson.com
Hongbo Zhang <hongbo.zhang@linaro.org>
- License Terms: GNU General Public License v2
- When the AB8500 thermal warning temperature is reached (threshold cannot
- be changed by SW), an interrupt is set, and if no further action is taken
- within a certain time frame, pm_power off will be called.
- When AB8500 thermal shutdown temperature is reached a hardware shutdown of
- the AB8500 will occur.
- */
+#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power/ab8500.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include "abx500.h"
+#define DEFAULT_POWER_OFF_DELAY (HZ * 10) +#define THERMAL_VCC 1800 +#define PULL_UP_RESISTOR 47000 +/* Number of monitored sensors should not greater than NUM_SENSORS */ +#define NUM_MONITORED_SENSORS 4
+struct ab8500_gpadc_cfg {
- const struct abx500_res_to_temp *temp_tbl;
- int tbl_sz;
- int vcc;
- int r_up;
+};
+struct ab8500_temp {
- struct ab8500_gpadc *gpadc;
- struct ab8500_btemp *btemp;
- struct delayed_work power_off_work;
- struct ab8500_gpadc_cfg cfg;
- struct abx500_temp *abx500_data;
+};
+/*
- The hardware connection is like this:
- VCC----[ R_up ]-----[ NTC ]----GND
- where R_up is pull-up resistance, and GPADC measures voltage on NTC.
- and res_to_temp table is strictly sorted by falling resistance values.
- */
+static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg,
int v_ntc, int *temp)
+{
- int r_ntc, i = 0, tbl_sz = cfg->tbl_sz;
- const struct abx500_res_to_temp *tbl = cfg->temp_tbl;
- if (cfg->vcc < 0 || v_ntc >= cfg->vcc)
return -EINVAL;
- r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc);
- if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist)
return -EINVAL;
- while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist)
&& i < tbl_sz - 2)
i++;
- /* return milli-Celsius */
- *temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 *
(r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);
- return 0;
+}
+static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp) +{
- int voltage, ret;
- struct ab8500_temp *ab8500_data = data->plat_data;
- if (sensor == BAT_CTRL)
*temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp);
- else if (sensor == BTEMP_BALL)
*temp = ab8500_btemp_get_temp(ab8500_data->btemp);
- else {
voltage = ab8500_gpadc_convert(ab8500_data->gpadc, sensor);
if (voltage < 0)
return voltage;
ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp);
if (ret < 0)
return ret;
- }
Please follow coding style: if you use { } anywhere in an if statement, use it everywhere.
- return 0;
+}
+static void ab8500_thermal_power_off(struct work_struct *work) +{
- struct ab8500_temp *ab8500_data = container_of(work,
struct ab8500_temp, power_off_work.work);
- struct abx500_temp *abx500_data = ab8500_data->abx500_data;
- dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n");
- pm_power_off();
+}
+static ssize_t ab8500_show_name(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
- return sprintf(buf, "ab8500\n");
+}
+static ssize_t ab8500_show_label(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
- char *label;
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- int index = attr->index;
- switch (index) {
- case 1:
label = "ext_adc1";
break;
- case 2:
label = "ext_adc2";
break;
- case 3:
label = "bat_temp";
break;
- case 4:
label = "bat_ctrl";
break;
- default:
return -EINVAL;
- }
- return sprintf(buf, "%s\n", label);
+}
+static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) +{
- struct ab8500_temp *ab8500_data = data->plat_data;
- dev_warn(&data->pdev->dev, "Power off in %d s\n",
DEFAULT_POWER_OFF_DELAY / HZ);
- schedule_delayed_work(&ab8500_data->power_off_work,
DEFAULT_POWER_OFF_DELAY);
- return 0;
+}
+int abx500_hwmon_init(struct abx500_temp *data) +{
- struct ab8500_temp *ab8500_data;
- ab8500_data = devm_kzalloc(&data->pdev->dev, sizeof(*ab8500_data),
GFP_KERNEL);
- if (!ab8500_data)
return -ENOMEM;
- ab8500_data->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
- if (IS_ERR(ab8500_data->gpadc))
return PTR_ERR(ab8500_data->gpadc);
- ab8500_data->btemp = ab8500_btemp_get();
- if (IS_ERR(ab8500_data->btemp))
return PTR_ERR(ab8500_data->btemp);
- INIT_DELAYED_WORK(&ab8500_data->power_off_work,
ab8500_thermal_power_off);
- ab8500_data->cfg.vcc = THERMAL_VCC;
- ab8500_data->cfg.r_up = PULL_UP_RESISTOR;
- ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a;
- ab8500_data->cfg.tbl_sz = ARRAY_SIZE(ab8500_temp_tbl_a);
- data->plat_data = ab8500_data;
- /*
* ADC_AUX1 and ADC_AUX2, connected to external NTC
* BTEMP_BALL and BAT_CTRL, fixed usage
*/
- data->gpadc_addr[0] = ADC_AUX1;
- data->gpadc_addr[1] = ADC_AUX2;
- data->gpadc_addr[2] = BTEMP_BALL;
- data->gpadc_addr[3] = BAT_CTRL;
- data->monitored_sensors = NUM_MONITORED_SENSORS;
- data->ops.read_sensor = ab8500_read_sensor;
- data->ops.irq_handler = ab8500_temp_irq_handler;
- data->ops.show_name = ab8500_show_name;
- data->ops.show_label = ab8500_show_label;
- data->ops.is_visible = NULL;
- return 0;
+} +EXPORT_SYMBOL(abx500_hwmon_init);
+MODULE_AUTHOR("Hongbo Zhang hongbo.zhang@linaro.org"); +MODULE_DESCRIPTION("AB8500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c new file mode 100644 index 0000000..1da5910 --- /dev/null +++ b/drivers/hwmon/abx500.c @@ -0,0 +1,494 @@ +/*
- Copyright (C) ST-Ericsson 2010 - 2013
- Author: Martin Persson martin.persson@stericsson.com
Hongbo Zhang <hongbo.zhang@linaro.org>
- License Terms: GNU General Public License v2
- ABX500 does not provide auto ADC, so to monitor the required temperatures,
- a periodic work is used. It is more important to not wake up the CPU than
- to perform this job, hence the use of a deferred delay.
- A deferred delay for thermal monitor is considered safe because:
- If the chip gets too hot during a sleep state it's most likely due to
- external factors, such as the surrounding temperature. I.e. no SW decisions
- will make any difference.
- */
+#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/workqueue.h> +#include "abx500.h"
+#define DEFAULT_MONITOR_DELAY HZ +#define DEFAULT_MAX_TEMP 130
+static inline void schedule_monitor(struct abx500_temp *data) +{
- data->work_active = true;
- schedule_delayed_work(&data->work, DEFAULT_MONITOR_DELAY);
+}
+static void threshold_updated(struct abx500_temp *data) +{
- int i;
- for (i = 0; i < data->monitored_sensors; i++)
if (data->max[i] != 0 || data->min[i] != 0) {
schedule_monitor(data);
return;
}
- dev_dbg(&data->pdev->dev, "No active thresholds.\n");
- cancel_delayed_work_sync(&data->work);
- data->work_active = false;
+}
+static void gpadc_monitor(struct work_struct *work) +{
- int temp, i, ret;
- char alarm_node[30];
- bool updated_min_alarm = false;
- bool updated_max_alarm = false;
- struct abx500_temp *data;
- data = container_of(work, struct abx500_temp, work.work);
- mutex_lock(&data->lock);
- for (i = 0; i < data->monitored_sensors; i++) {
/* Thresholds are considered inactive if set to 0 */
if (data->max[i] == 0 && data->min[i] == 0)
continue;
if (data->max[i] < data->min[i])
continue;
ret = data->ops.read_sensor(data, data->gpadc_addr[i], &temp);
if (ret < 0) {
dev_err(&data->pdev->dev, "GPADC read failed\n");
continue;
}
if (data->min[i] != 0) {
if (temp < data->min[i]) {
if (data->min_alarm[i] == false) {
data->min_alarm[i] = true;
updated_min_alarm = true;
}
} else {
if (data->min_alarm[i] == true) {
data->min_alarm[i] = false;
updated_min_alarm = true;
}
}
}
if (data->max[i] != 0) {
if (temp > data->max[i]) {
if (data->max_alarm[i] == false) {
data->max_alarm[i] = true;
updated_max_alarm = true;
}
} else if (temp < data->max[i] - data->max_hyst[i]) {
if (data->max_alarm[i] == true) {
data->max_alarm[i] = false;
updated_max_alarm = true;
}
}
}
if (updated_min_alarm) {
ret = sprintf(alarm_node, "temp%d_min_alarm", i);
sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
}
if (updated_max_alarm) {
ret = sprintf(alarm_node, "temp%d_max_alarm", i);
I am almost sure this is wrong. Loop index starts with 0, tempX index starts with 1.
sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
}
updated_min_alarm = false;
updated_max_alarm = false;
We had this before: Move this to the beginning of the loop, and you safe one set of initializations.
- }
- schedule_monitor(data);
- mutex_unlock(&data->lock);
+}
+/* HWMON sysfs interfaces */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
char *buf)
+{
- struct abx500_temp *data = dev_get_drvdata(dev);
- /* Show chip name */
- return data->ops.show_name(dev, devattr, buf);
+}
+static ssize_t show_label(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
- struct abx500_temp *data = dev_get_drvdata(dev);
- /* Show each sensor label */
- return data->ops.show_label(dev, devattr, buf);
+}
+static ssize_t show_input(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
- int ret, temp;
- struct abx500_temp *data = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- u8 gpadc_addr = data->gpadc_addr[attr->index];
- ret = data->ops.read_sensor(data, gpadc_addr, &temp);
- if (ret < 0)
dev_err(&data->pdev->dev, "GPADC read failed\n");
... and temp is uninitialized. Needs return err; Not sure if the error message is really helpful; the caller will notice.
- return sprintf(buf, "%d\n", temp);
+}
+/* Set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
+{
- unsigned long val;
- struct abx500_temp *data = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- int res = kstrtol(buf, 10, &val);
- if (res < 0)
return res;
- clamp_val(val, 0, data->max[attr->index]);
You should clamp against fixed limits, ie [0, DEFAULT_MAX_TEMP]. Otherwise updating limits gets tricky and has to be done in a fixed order, which would be unexpected behavior. The code using the values should be able to handle situations where min > max.
- mutex_lock(&data->lock);
- data->min[attr->index] = val;
- threshold_updated(data);
- mutex_unlock(&data->lock);
- return count;
+}
+static ssize_t set_max(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
+{
- unsigned long val;
- struct abx500_temp *data = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- int res = kstrtol(buf, 10, &val);
- if (res < 0)
return res;
- clamp_val(val, data->min[attr->index], DEFAULT_MAX_TEMP);
Same here.
- mutex_lock(&data->lock);
- data->max[attr->index] = val;
- threshold_updated(data);
- mutex_unlock(&data->lock);
- return count;
+}
+static ssize_t set_max_hyst(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
+{
- unsigned long val;
- struct abx500_temp *data = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- int res = kstrtoul(buf, 10, &val);
- if (res < 0)
return res;
- mutex_lock(&data->lock);
- data->max_hyst[attr->index] = val;
No clamp here ?
- threshold_updated(data);
- mutex_unlock(&data->lock);
- return count;
+}
+/* Show functions (RO nodes) */ +static ssize_t show_min(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
- struct abx500_temp *data = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- return sprintf(buf, "%ld\n", data->min[attr->index]);
+}
+static ssize_t show_max(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
- struct abx500_temp *data = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- return sprintf(buf, "%ld\n", data->max[attr->index]);
+}
+static ssize_t show_max_hyst(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
- struct abx500_temp *data = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- return sprintf(buf, "%ld\n", data->max_hyst[attr->index]);
+}
+static ssize_t show_min_alarm(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
- struct abx500_temp *data = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- return sprintf(buf, "%d\n", data->min_alarm[attr->index]);
+}
+static ssize_t show_max_alarm(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
- struct abx500_temp *data = dev_get_drvdata(dev);
- struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
- return sprintf(buf, "%d\n", data->max_alarm[attr->index]);
+}
+static mode_t abx500_attrs_visible(struct kobject *kobj,
struct attribute *attr, int n)
+{
- struct device *dev = container_of(kobj, struct device, kobj);
- struct abx500_temp *data = dev_get_drvdata(dev);
- if (data->ops.is_visible)
return data->ops.is_visible(attr, n);
- else
Unnecessary else
return attr->mode;
+}
+/* Chip name, required by hwmon */ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+/* GPADC - SENSOR1 */ +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
show_max_hyst, set_max_hyst, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 0);
+/* GPADC - SENSOR2 */ +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO,
show_max_hyst, set_max_hyst, 1);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 1);
+/* GPADC - SENSOR3 */ +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO,
show_max_hyst, set_max_hyst, 2);
+static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 2);
+/* GPADC - SENSOR4 */ +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 3); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 3); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO,
show_max_hyst, set_max_hyst, 3);
+static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 3);
+struct attribute *abx500_temp_attributes[] = {
- &sensor_dev_attr_name.dev_attr.attr,
- &sensor_dev_attr_temp1_label.dev_attr.attr,
- &sensor_dev_attr_temp1_input.dev_attr.attr,
- &sensor_dev_attr_temp1_min.dev_attr.attr,
- &sensor_dev_attr_temp1_max.dev_attr.attr,
- &sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_label.dev_attr.attr,
- &sensor_dev_attr_temp2_input.dev_attr.attr,
- &sensor_dev_attr_temp2_min.dev_attr.attr,
- &sensor_dev_attr_temp2_max.dev_attr.attr,
- &sensor_dev_attr_temp2_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_label.dev_attr.attr,
- &sensor_dev_attr_temp3_input.dev_attr.attr,
- &sensor_dev_attr_temp3_min.dev_attr.attr,
- &sensor_dev_attr_temp3_max.dev_attr.attr,
- &sensor_dev_attr_temp3_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp3_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
- &sensor_dev_attr_temp4_label.dev_attr.attr,
- &sensor_dev_attr_temp4_input.dev_attr.attr,
- &sensor_dev_attr_temp4_min.dev_attr.attr,
- &sensor_dev_attr_temp4_max.dev_attr.attr,
- &sensor_dev_attr_temp4_max_hyst.dev_attr.attr,
- &sensor_dev_attr_temp4_min_alarm.dev_attr.attr,
- &sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
- NULL
+};
+static const struct attribute_group abx500_temp_group = {
- .attrs = abx500_temp_attributes,
- .is_visible = abx500_attrs_visible,
+};
+static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data) +{
- struct platform_device *pdev = irq_data;
- struct abx500_temp *data = platform_get_drvdata(pdev);
- data->ops.irq_handler(irq, data);
- return IRQ_HANDLED;
+}
+static int setup_irqs(struct platform_device *pdev) +{
- int ret;
- int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM");
- if (irq < 0) {
dev_err(&pdev->dev, "Get irq by name failed\n");
return irq;
- }
- ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
abx500_temp_irq_handler, IRQF_NO_SUSPEND, "abx500-temp", pdev);
- if (ret < 0)
dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret);
You display error messages here and in the calling code ... once should be enough.
- return ret;
+}
+static int abx500_temp_probe(struct platform_device *pdev) +{
- struct abx500_temp *data;
- int err;
- data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
- if (!data)
return -ENOMEM;
- data->pdev = pdev;
- mutex_init(&data->lock);
- /* Chip specific initialization */
- err = abx500_hwmon_init(data);
- if (err < 0 || !data->ops.read_sensor || !data->ops.show_name
|| !data->ops.show_label) {
dev_err(&pdev->dev, "ABx500 hwmon init failed");
"ABx500 hwmon" is redundant here.
return -EINVAL;
You'll want to return the original error code, which for example can be ENOMEM where an error message is not needed. Also, I suspect abx500_hwmon_init might at some point return -EPROBE_DEFER. If that ever happens, you definitely want to fail silently and return the correct error code.
- }
- INIT_DEFERRABLE_WORK(&data->work, gpadc_monitor);
- platform_set_drvdata(pdev, data);
- err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group);
- if (err < 0) {
dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err);
return err;
- }
- data->hwmon_dev = hwmon_device_register(&pdev->dev);
- if (IS_ERR(data->hwmon_dev)) {
err = PTR_ERR(data->hwmon_dev);
dev_err(&pdev->dev, "Class registration failed (%d)\n", err);
goto exit_sysfs_group;
- }
- if (data->ops.irq_handler) {
err = setup_irqs(pdev);
if (err < 0) {
dev_err(&pdev->dev, "irq setup failed (%d)\n", err);
goto exit_hwmon_reg;
}
- }
- return 0;
+exit_hwmon_reg:
- hwmon_device_unregister(data->hwmon_dev);
+exit_sysfs_group:
- sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group);
- return err;
+}
+static int abx500_temp_remove(struct platform_device *pdev) +{
- struct abx500_temp *data = platform_get_drvdata(pdev);
- cancel_delayed_work_sync(&data->work);
- hwmon_device_unregister(data->hwmon_dev);
- sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group);
- return 0;
+}
+static int abx500_temp_suspend(struct platform_device *pdev,
pm_message_t state)
+{
- struct abx500_temp *data = platform_get_drvdata(pdev);
- if (data->work_active)
cancel_delayed_work_sync(&data->work);
- return 0;
+}
+static int abx500_temp_resume(struct platform_device *pdev) +{
- struct abx500_temp *data = platform_get_drvdata(pdev);
- if (data->work_active)
schedule_monitor(data);
- return 0;
+}
+#ifdef CONFIG_OF +static const struct of_device_id abx500_temp_match[] = {
- { .compatible = "stericsson,abx500-temp" },
- {},
+}; +#endif
+static struct platform_driver abx500_temp_driver = {
- .driver = {
.owner = THIS_MODULE,
.name = "abx500-temp",
.of_match_table = of_match_ptr(abx500_temp_match),
- },
- .suspend = abx500_temp_suspend,
- .resume = abx500_temp_resume,
- .probe = abx500_temp_probe,
- .remove = abx500_temp_remove,
+};
+module_platform_driver(abx500_temp_driver);
+MODULE_AUTHOR("Martin Persson martin.persson@stericsson.com"); +MODULE_DESCRIPTION("ABX500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h new file mode 100644 index 0000000..9b295e6 --- /dev/null +++ b/drivers/hwmon/abx500.h @@ -0,0 +1,69 @@ +/*
- Copyright (C) ST-Ericsson 2010 - 2013
- License terms: GNU General Public License v2
- Author: Martin Persson martin.persson@stericsson.com
Hongbo Zhang <hongbo.zhang@linaro.com>
- */
+#ifndef _ABX500_H +#define _ABX500_H
+#define NUM_SENSORS 5
+struct abx500_temp;
+/*
- struct abx500_temp_ops - abx500 chip specific ops
- @read_sensor: reads gpadc output
- @irq_handler: irq handler
- @show_name: hwmon device name
- @show_label: hwmon attribute label
- @is_visible: is attribute visible
- */
+struct abx500_temp_ops {
- int (*read_sensor)(struct abx500_temp *, u8, int *);
- int (*irq_handler)(int, struct abx500_temp *);
- ssize_t (*show_name)(struct device *,
struct device_attribute *, char *);
- ssize_t (*show_label) (struct device *,
struct device_attribute *, char *);
- int (*is_visible)(struct attribute *, int);
+};
+/*
- struct abx500_temp - representation of temp mon device
- @pdev: platform device
- @hwmon_dev: hwmon device
- @ops: abx500 chip specific ops
- @gpadc_addr: gpadc channel address
- @min: sensor temperature min value
- @max: sensor temperature max value
- @max_hyst: sensor temperature hysteresis value for max limit
- @min_alarm: sensor temperature min alarm
- @max_alarm: sensor temperature max alarm
- @work: delayed work scheduled to monitor temperature periodically
- @work_active: True if work is active
- @lock: mutex
- @monitored_sensors: number of monitored sensors
- @plat_data: private usage, usually points to platform specific data
- */
+struct abx500_temp {
- struct platform_device *pdev;
- struct device *hwmon_dev;
- struct abx500_temp_ops ops;
- u8 gpadc_addr[NUM_SENSORS];
- unsigned long min[NUM_SENSORS];
- unsigned long max[NUM_SENSORS];
- unsigned long max_hyst[NUM_SENSORS];
- bool min_alarm[NUM_SENSORS];
- bool max_alarm[NUM_SENSORS];
- struct delayed_work work;
- bool work_active;
- struct mutex lock;
- int monitored_sensors;
- void *plat_data;
+};
+int abx500_hwmon_init(struct abx500_temp *data);
+#endif /* _ABX500_H */
1.8.0
On 12 March 2013 13:30, Guenter Roeck linux@roeck-us.net wrote:
On Fri, Mar 08, 2013 at 04:13:31PM +0800, Hongbo Zhang wrote:
Each of ST-Ericsson X500 chip set series consists of both ABX500 and DBX500 chips. This is ABX500 hwmon driver, where the abx500.c is a common layer for all ABX500s, and the ab8500.c is specific for AB8500 chip. Under this designed structure, other chip specific files can be added simply using the same common layer abx500.c.
Signed-off-by: Hongbo Zhang hongbo.zhang@linaro.org
Documentation/hwmon/ab8500 | 22 ++ Documentation/hwmon/abx500 | 28 +++ drivers/hwmon/Kconfig | 13 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/ab8500.c | 208 +++++++++++++++++++ drivers/hwmon/abx500.c | 494 +++++++++++++++++++++++++++++++++++++++++++++ drivers/hwmon/abx500.h | 69 +++++++ 7 files changed, 835 insertions(+) create mode 100644 Documentation/hwmon/ab8500 create mode 100644 Documentation/hwmon/abx500 create mode 100644 drivers/hwmon/ab8500.c create mode 100644 drivers/hwmon/abx500.c create mode 100644 drivers/hwmon/abx500.h
diff --git a/Documentation/hwmon/ab8500 b/Documentation/hwmon/ab8500 new file mode 100644 index 0000000..cf169c8 --- /dev/null +++ b/Documentation/hwmon/ab8500 @@ -0,0 +1,22 @@ +Kernel driver ab8500 +====================
+Supported chips:
- ST-Ericsson AB8500
- Prefix: 'ab8500'
- Addresses scanned: -
- Datasheet: http://www.stericsson.com/developers/documentation.jsp
+Authors:
Martin Persson <martin.persson@stericsson.com>
Hongbo Zhang <hongbo.zhang@linaro.org>
+Description +-----------
+See also Documentation/hwmon/abx500. This is the ST-Ericsson AB8500 specific +driver.
+Currently only the AB8500 internal sensor and one external sensor for battery +temperature are monitored. Other GPADC channels can also be monitored if needed +in future. diff --git a/Documentation/hwmon/abx500 b/Documentation/hwmon/abx500 new file mode 100644 index 0000000..319a058 --- /dev/null +++ b/Documentation/hwmon/abx500 @@ -0,0 +1,28 @@ +Kernel driver abx500 +====================
+Supported chips:
- ST-Ericsson ABx500 series
- Prefix: 'abx500'
- Addresses scanned: -
- Datasheet: http://www.stericsson.com/developers/documentation.jsp
+Authors:
Martin Persson <martin.persson@stericsson.com>
Hongbo Zhang <hongbo.zhang@linaro.org>
+Description +-----------
+Every ST-Ericsson Ux500 SOC consists of both ABx500 and DBx500 physically, +this is kernel hwmon driver for ABx500.
+There are some GPADCs inside ABx500 which are designed for connecting to +thermal sensors, and there is also a thermal sensor inside ABx500 too, which +raises interrupt when critical temperature reached.
+This abx500 is a common layer which can monitor all of the sensors, every +specific abx500 chip has its special configurations in its own file, e.g. some +sensors can be configured invisible if they are not available on that chip, and +the corresponding gpadc_addr should be set to 0, thus this sensor won't be +polled. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 32f238f..0a6fd21 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -39,6 +39,19 @@ config HWMON_DEBUG_CHIP
comment "Native drivers"
+config SENSORS_AB8500
tristate "AB8500 thermal monitoring"
depends on AB8500_GPADC
default n
help
If you say yes here you get support for the thermal sensor part
of the AB8500 chip. The driver includes thermal management for
AB8500 die and two GPADC channels. The GPADC channel are preferably
used to access sensors outside the AB8500 chip.
This driver can also be built as a module. If so, the module
will be called abx500-temp.
config SENSORS_ABITUGURU tristate "Abit uGuru (rev 1 & 2)" depends on X86 && DMI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5da2874..06dfe85 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
+obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7314) += ad7314.o diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c new file mode 100644 index 0000000..da165fa --- /dev/null +++ b/drivers/hwmon/ab8500.c @@ -0,0 +1,208 @@ +/*
- Copyright (C) ST-Ericsson 2010 - 2013
- Author: Martin Persson martin.persson@stericsson.com
Hongbo Zhang <hongbo.zhang@linaro.org>
- License Terms: GNU General Public License v2
- When the AB8500 thermal warning temperature is reached (threshold cannot
- be changed by SW), an interrupt is set, and if no further action is taken
- within a certain time frame, pm_power off will be called.
- When AB8500 thermal shutdown temperature is reached a hardware shutdown of
- the AB8500 will occur.
- */
+#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power/ab8500.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include "abx500.h"
+#define DEFAULT_POWER_OFF_DELAY (HZ * 10) +#define THERMAL_VCC 1800 +#define PULL_UP_RESISTOR 47000 +/* Number of monitored sensors should not greater than NUM_SENSORS */ +#define NUM_MONITORED_SENSORS 4
+struct ab8500_gpadc_cfg {
const struct abx500_res_to_temp *temp_tbl;
int tbl_sz;
int vcc;
int r_up;
+};
+struct ab8500_temp {
struct ab8500_gpadc *gpadc;
struct ab8500_btemp *btemp;
struct delayed_work power_off_work;
struct ab8500_gpadc_cfg cfg;
struct abx500_temp *abx500_data;
+};
+/*
- The hardware connection is like this:
- VCC----[ R_up ]-----[ NTC ]----GND
- where R_up is pull-up resistance, and GPADC measures voltage on NTC.
- and res_to_temp table is strictly sorted by falling resistance values.
- */
+static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg,
int v_ntc, int *temp)
+{
int r_ntc, i = 0, tbl_sz = cfg->tbl_sz;
const struct abx500_res_to_temp *tbl = cfg->temp_tbl;
if (cfg->vcc < 0 || v_ntc >= cfg->vcc)
return -EINVAL;
r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc);
if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist)
return -EINVAL;
while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist)
&& i < tbl_sz - 2)
i++;
/* return milli-Celsius */
*temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 *
(r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);
return 0;
+}
+static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp) +{
int voltage, ret;
struct ab8500_temp *ab8500_data = data->plat_data;
if (sensor == BAT_CTRL)
*temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp);
else if (sensor == BTEMP_BALL)
*temp = ab8500_btemp_get_temp(ab8500_data->btemp);
else {
voltage = ab8500_gpadc_convert(ab8500_data->gpadc, sensor);
if (voltage < 0)
return voltage;
ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp);
if (ret < 0)
return ret;
}
Please follow coding style: if you use { } anywhere in an if statement, use it everywhere.
return 0;
+}
+static void ab8500_thermal_power_off(struct work_struct *work) +{
struct ab8500_temp *ab8500_data = container_of(work,
struct ab8500_temp, power_off_work.work);
struct abx500_temp *abx500_data = ab8500_data->abx500_data;
dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n");
pm_power_off();
+}
+static ssize_t ab8500_show_name(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
return sprintf(buf, "ab8500\n");
+}
+static ssize_t ab8500_show_label(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
char *label;
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int index = attr->index;
switch (index) {
case 1:
label = "ext_adc1";
break;
case 2:
label = "ext_adc2";
break;
case 3:
label = "bat_temp";
break;
case 4:
label = "bat_ctrl";
break;
default:
return -EINVAL;
}
return sprintf(buf, "%s\n", label);
+}
+static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) +{
struct ab8500_temp *ab8500_data = data->plat_data;
dev_warn(&data->pdev->dev, "Power off in %d s\n",
DEFAULT_POWER_OFF_DELAY / HZ);
schedule_delayed_work(&ab8500_data->power_off_work,
DEFAULT_POWER_OFF_DELAY);
return 0;
+}
+int abx500_hwmon_init(struct abx500_temp *data) +{
struct ab8500_temp *ab8500_data;
ab8500_data = devm_kzalloc(&data->pdev->dev, sizeof(*ab8500_data),
GFP_KERNEL);
if (!ab8500_data)
return -ENOMEM;
ab8500_data->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
if (IS_ERR(ab8500_data->gpadc))
return PTR_ERR(ab8500_data->gpadc);
ab8500_data->btemp = ab8500_btemp_get();
if (IS_ERR(ab8500_data->btemp))
return PTR_ERR(ab8500_data->btemp);
INIT_DELAYED_WORK(&ab8500_data->power_off_work,
ab8500_thermal_power_off);
ab8500_data->cfg.vcc = THERMAL_VCC;
ab8500_data->cfg.r_up = PULL_UP_RESISTOR;
ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a;
ab8500_data->cfg.tbl_sz = ARRAY_SIZE(ab8500_temp_tbl_a);
data->plat_data = ab8500_data;
/*
* ADC_AUX1 and ADC_AUX2, connected to external NTC
* BTEMP_BALL and BAT_CTRL, fixed usage
*/
data->gpadc_addr[0] = ADC_AUX1;
data->gpadc_addr[1] = ADC_AUX2;
data->gpadc_addr[2] = BTEMP_BALL;
data->gpadc_addr[3] = BAT_CTRL;
data->monitored_sensors = NUM_MONITORED_SENSORS;
data->ops.read_sensor = ab8500_read_sensor;
data->ops.irq_handler = ab8500_temp_irq_handler;
data->ops.show_name = ab8500_show_name;
data->ops.show_label = ab8500_show_label;
data->ops.is_visible = NULL;
return 0;
+} +EXPORT_SYMBOL(abx500_hwmon_init);
+MODULE_AUTHOR("Hongbo Zhang hongbo.zhang@linaro.org"); +MODULE_DESCRIPTION("AB8500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c new file mode 100644 index 0000000..1da5910 --- /dev/null +++ b/drivers/hwmon/abx500.c @@ -0,0 +1,494 @@ +/*
- Copyright (C) ST-Ericsson 2010 - 2013
- Author: Martin Persson martin.persson@stericsson.com
Hongbo Zhang <hongbo.zhang@linaro.org>
- License Terms: GNU General Public License v2
- ABX500 does not provide auto ADC, so to monitor the required temperatures,
- a periodic work is used. It is more important to not wake up the CPU than
- to perform this job, hence the use of a deferred delay.
- A deferred delay for thermal monitor is considered safe because:
- If the chip gets too hot during a sleep state it's most likely due to
- external factors, such as the surrounding temperature. I.e. no SW decisions
- will make any difference.
- */
+#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/workqueue.h> +#include "abx500.h"
+#define DEFAULT_MONITOR_DELAY HZ +#define DEFAULT_MAX_TEMP 130
+static inline void schedule_monitor(struct abx500_temp *data) +{
data->work_active = true;
schedule_delayed_work(&data->work, DEFAULT_MONITOR_DELAY);
+}
+static void threshold_updated(struct abx500_temp *data) +{
int i;
for (i = 0; i < data->monitored_sensors; i++)
if (data->max[i] != 0 || data->min[i] != 0) {
schedule_monitor(data);
return;
}
dev_dbg(&data->pdev->dev, "No active thresholds.\n");
cancel_delayed_work_sync(&data->work);
data->work_active = false;
+}
+static void gpadc_monitor(struct work_struct *work) +{
int temp, i, ret;
char alarm_node[30];
bool updated_min_alarm = false;
bool updated_max_alarm = false;
struct abx500_temp *data;
data = container_of(work, struct abx500_temp, work.work);
mutex_lock(&data->lock);
for (i = 0; i < data->monitored_sensors; i++) {
/* Thresholds are considered inactive if set to 0 */
if (data->max[i] == 0 && data->min[i] == 0)
continue;
if (data->max[i] < data->min[i])
continue;
ret = data->ops.read_sensor(data, data->gpadc_addr[i], &temp);
if (ret < 0) {
dev_err(&data->pdev->dev, "GPADC read failed\n");
continue;
}
if (data->min[i] != 0) {
if (temp < data->min[i]) {
if (data->min_alarm[i] == false) {
data->min_alarm[i] = true;
updated_min_alarm = true;
}
} else {
if (data->min_alarm[i] == true) {
data->min_alarm[i] = false;
updated_min_alarm = true;
}
}
}
if (data->max[i] != 0) {
if (temp > data->max[i]) {
if (data->max_alarm[i] == false) {
data->max_alarm[i] = true;
updated_max_alarm = true;
}
} else if (temp < data->max[i] - data->max_hyst[i]) {
if (data->max_alarm[i] == true) {
data->max_alarm[i] = false;
updated_max_alarm = true;
}
}
}
if (updated_min_alarm) {
ret = sprintf(alarm_node, "temp%d_min_alarm", i);
sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
}
if (updated_max_alarm) {
ret = sprintf(alarm_node, "temp%d_max_alarm", i);
I am almost sure this is wrong. Loop index starts with 0, tempX index starts with 1.
sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
}
updated_min_alarm = false;
updated_max_alarm = false;
We had this before: Move this to the beginning of the loop, and you safe one set of initializations.
Will remember.
}
schedule_monitor(data);
mutex_unlock(&data->lock);
+}
+/* HWMON sysfs interfaces */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
char *buf)
+{
struct abx500_temp *data = dev_get_drvdata(dev);
/* Show chip name */
return data->ops.show_name(dev, devattr, buf);
+}
+static ssize_t show_label(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
struct abx500_temp *data = dev_get_drvdata(dev);
/* Show each sensor label */
return data->ops.show_label(dev, devattr, buf);
+}
+static ssize_t show_input(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
int ret, temp;
struct abx500_temp *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
u8 gpadc_addr = data->gpadc_addr[attr->index];
ret = data->ops.read_sensor(data, gpadc_addr, &temp);
if (ret < 0)
dev_err(&data->pdev->dev, "GPADC read failed\n");
... and temp is uninitialized. Needs return err; Not sure if the error message is really helpful; the caller will notice.
Yes will return err and remove this error message.
return sprintf(buf, "%d\n", temp);
+}
+/* Set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
+{
unsigned long val;
struct abx500_temp *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int res = kstrtol(buf, 10, &val);
if (res < 0)
return res;
clamp_val(val, 0, data->max[attr->index]);
You should clamp against fixed limits, ie [0, DEFAULT_MAX_TEMP]. Otherwise updating limits gets tricky and has to be done in a fixed order, which would be unexpected behavior. The code using the values should be able to handle situations where min > max.
I had also realized this will cause tricky order issue, and was hesitating which max value should be used, now get confirmation and will use the macro.
mutex_lock(&data->lock);
data->min[attr->index] = val;
threshold_updated(data);
mutex_unlock(&data->lock);
return count;
+}
+static ssize_t set_max(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
+{
unsigned long val;
struct abx500_temp *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int res = kstrtol(buf, 10, &val);
if (res < 0)
return res;
clamp_val(val, data->min[attr->index], DEFAULT_MAX_TEMP);
Same here.
mutex_lock(&data->lock);
data->max[attr->index] = val;
threshold_updated(data);
mutex_unlock(&data->lock);
return count;
+}
+static ssize_t set_max_hyst(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
+{
unsigned long val;
struct abx500_temp *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int res = kstrtoul(buf, 10, &val);
if (res < 0)
return res;
mutex_lock(&data->lock);
data->max_hyst[attr->index] = val;
No clamp here ?
Will add.
threshold_updated(data);
mutex_unlock(&data->lock);
return count;
+}
+/* Show functions (RO nodes) */ +static ssize_t show_min(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
struct abx500_temp *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
return sprintf(buf, "%ld\n", data->min[attr->index]);
+}
+static ssize_t show_max(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
struct abx500_temp *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
return sprintf(buf, "%ld\n", data->max[attr->index]);
+}
+static ssize_t show_max_hyst(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
struct abx500_temp *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
return sprintf(buf, "%ld\n", data->max_hyst[attr->index]);
+}
+static ssize_t show_min_alarm(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
struct abx500_temp *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
return sprintf(buf, "%d\n", data->min_alarm[attr->index]);
+}
+static ssize_t show_max_alarm(struct device *dev,
struct device_attribute *devattr, char *buf)
+{
struct abx500_temp *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
return sprintf(buf, "%d\n", data->max_alarm[attr->index]);
+}
+static mode_t abx500_attrs_visible(struct kobject *kobj,
struct attribute *attr, int n)
+{
struct device *dev = container_of(kobj, struct device, kobj);
struct abx500_temp *data = dev_get_drvdata(dev);
if (data->ops.is_visible)
return data->ops.is_visible(attr, n);
else
Unnecessary else
return attr->mode;
+}
+/* Chip name, required by hwmon */ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+/* GPADC - SENSOR1 */ +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
show_max_hyst, set_max_hyst, 0);
+static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 0);
+/* GPADC - SENSOR2 */ +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO,
show_max_hyst, set_max_hyst, 1);
+static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 1);
+/* GPADC - SENSOR3 */ +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO,
show_max_hyst, set_max_hyst, 2);
+static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 2);
+/* GPADC - SENSOR4 */ +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 3); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 3); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO,
show_max_hyst, set_max_hyst, 3);
+static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 3);
+struct attribute *abx500_temp_attributes[] = {
&sensor_dev_attr_name.dev_attr.attr,
&sensor_dev_attr_temp1_label.dev_attr.attr,
&sensor_dev_attr_temp1_input.dev_attr.attr,
&sensor_dev_attr_temp1_min.dev_attr.attr,
&sensor_dev_attr_temp1_max.dev_attr.attr,
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
&sensor_dev_attr_temp2_label.dev_attr.attr,
&sensor_dev_attr_temp2_input.dev_attr.attr,
&sensor_dev_attr_temp2_min.dev_attr.attr,
&sensor_dev_attr_temp2_max.dev_attr.attr,
&sensor_dev_attr_temp2_max_hyst.dev_attr.attr,
&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
&sensor_dev_attr_temp3_label.dev_attr.attr,
&sensor_dev_attr_temp3_input.dev_attr.attr,
&sensor_dev_attr_temp3_min.dev_attr.attr,
&sensor_dev_attr_temp3_max.dev_attr.attr,
&sensor_dev_attr_temp3_max_hyst.dev_attr.attr,
&sensor_dev_attr_temp3_min_alarm.dev_attr.attr,
&sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
&sensor_dev_attr_temp4_label.dev_attr.attr,
&sensor_dev_attr_temp4_input.dev_attr.attr,
&sensor_dev_attr_temp4_min.dev_attr.attr,
&sensor_dev_attr_temp4_max.dev_attr.attr,
&sensor_dev_attr_temp4_max_hyst.dev_attr.attr,
&sensor_dev_attr_temp4_min_alarm.dev_attr.attr,
&sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
NULL
+};
+static const struct attribute_group abx500_temp_group = {
.attrs = abx500_temp_attributes,
.is_visible = abx500_attrs_visible,
+};
+static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data) +{
struct platform_device *pdev = irq_data;
struct abx500_temp *data = platform_get_drvdata(pdev);
data->ops.irq_handler(irq, data);
return IRQ_HANDLED;
+}
+static int setup_irqs(struct platform_device *pdev) +{
int ret;
int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM");
if (irq < 0) {
dev_err(&pdev->dev, "Get irq by name failed\n");
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
abx500_temp_irq_handler, IRQF_NO_SUSPEND, "abx500-temp", pdev);
if (ret < 0)
dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret);
You display error messages here and in the calling code ... once should be enough.
Yes.
return ret;
+}
+static int abx500_temp_probe(struct platform_device *pdev) +{
struct abx500_temp *data;
int err;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->pdev = pdev;
mutex_init(&data->lock);
/* Chip specific initialization */
err = abx500_hwmon_init(data);
if (err < 0 || !data->ops.read_sensor || !data->ops.show_name
|| !data->ops.show_label) {
dev_err(&pdev->dev, "ABx500 hwmon init failed");
"ABx500 hwmon" is redundant here.
Yes.
return -EINVAL;
You'll want to return the original error code, which for example can be ENOMEM where an error message is not needed. Also, I suspect abx500_hwmon_init might at some point return -EPROBE_DEFER. If that ever happens, you definitely want to fail silently and return the correct error code.
Yes, should return err in this probe function. Where to return -EPROBE_DEFER? when ab8500_gpadc_get() or ab8500_btemp_get() fails? In fact these two interfaces aren't so good, because we cannot tell the reason when they return fail. A solution I can imagine now is to send another separate patch for these two interfaces, letting them return -EPROBE_DEFER if they are not initialized when being called.
}
INIT_DEFERRABLE_WORK(&data->work, gpadc_monitor);
platform_set_drvdata(pdev, data);
err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group);
if (err < 0) {
dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err);
return err;
}
data->hwmon_dev = hwmon_device_register(&pdev->dev);
if (IS_ERR(data->hwmon_dev)) {
err = PTR_ERR(data->hwmon_dev);
dev_err(&pdev->dev, "Class registration failed (%d)\n", err);
goto exit_sysfs_group;
}
if (data->ops.irq_handler) {
err = setup_irqs(pdev);
if (err < 0) {
dev_err(&pdev->dev, "irq setup failed (%d)\n", err);
goto exit_hwmon_reg;
}
}
return 0;
+exit_hwmon_reg:
hwmon_device_unregister(data->hwmon_dev);
+exit_sysfs_group:
sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group);
return err;
+}
+static int abx500_temp_remove(struct platform_device *pdev) +{
struct abx500_temp *data = platform_get_drvdata(pdev);
cancel_delayed_work_sync(&data->work);
hwmon_device_unregister(data->hwmon_dev);
sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group);
return 0;
+}
+static int abx500_temp_suspend(struct platform_device *pdev,
pm_message_t state)
+{
struct abx500_temp *data = platform_get_drvdata(pdev);
if (data->work_active)
cancel_delayed_work_sync(&data->work);
return 0;
+}
+static int abx500_temp_resume(struct platform_device *pdev) +{
struct abx500_temp *data = platform_get_drvdata(pdev);
if (data->work_active)
schedule_monitor(data);
return 0;
+}
+#ifdef CONFIG_OF +static const struct of_device_id abx500_temp_match[] = {
{ .compatible = "stericsson,abx500-temp" },
{},
+}; +#endif
+static struct platform_driver abx500_temp_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "abx500-temp",
.of_match_table = of_match_ptr(abx500_temp_match),
},
.suspend = abx500_temp_suspend,
.resume = abx500_temp_resume,
.probe = abx500_temp_probe,
.remove = abx500_temp_remove,
+};
+module_platform_driver(abx500_temp_driver);
+MODULE_AUTHOR("Martin Persson martin.persson@stericsson.com"); +MODULE_DESCRIPTION("ABX500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h new file mode 100644 index 0000000..9b295e6 --- /dev/null +++ b/drivers/hwmon/abx500.h @@ -0,0 +1,69 @@ +/*
- Copyright (C) ST-Ericsson 2010 - 2013
- License terms: GNU General Public License v2
- Author: Martin Persson martin.persson@stericsson.com
Hongbo Zhang <hongbo.zhang@linaro.com>
- */
+#ifndef _ABX500_H +#define _ABX500_H
+#define NUM_SENSORS 5
+struct abx500_temp;
+/*
- struct abx500_temp_ops - abx500 chip specific ops
- @read_sensor: reads gpadc output
- @irq_handler: irq handler
- @show_name: hwmon device name
- @show_label: hwmon attribute label
- @is_visible: is attribute visible
- */
+struct abx500_temp_ops {
int (*read_sensor)(struct abx500_temp *, u8, int *);
int (*irq_handler)(int, struct abx500_temp *);
ssize_t (*show_name)(struct device *,
struct device_attribute *, char *);
ssize_t (*show_label) (struct device *,
struct device_attribute *, char *);
int (*is_visible)(struct attribute *, int);
+};
+/*
- struct abx500_temp - representation of temp mon device
- @pdev: platform device
- @hwmon_dev: hwmon device
- @ops: abx500 chip specific ops
- @gpadc_addr: gpadc channel address
- @min: sensor temperature min value
- @max: sensor temperature max value
- @max_hyst: sensor temperature hysteresis value for max limit
- @min_alarm: sensor temperature min alarm
- @max_alarm: sensor temperature max alarm
- @work: delayed work scheduled to monitor temperature periodically
- @work_active: True if work is active
- @lock: mutex
- @monitored_sensors: number of monitored sensors
- @plat_data: private usage, usually points to platform specific data
- */
+struct abx500_temp {
struct platform_device *pdev;
struct device *hwmon_dev;
struct abx500_temp_ops ops;
u8 gpadc_addr[NUM_SENSORS];
unsigned long min[NUM_SENSORS];
unsigned long max[NUM_SENSORS];
unsigned long max_hyst[NUM_SENSORS];
bool min_alarm[NUM_SENSORS];
bool max_alarm[NUM_SENSORS];
struct delayed_work work;
bool work_active;
struct mutex lock;
int monitored_sensors;
void *plat_data;
+};
+int abx500_hwmon_init(struct abx500_temp *data);
+#endif /* _ABX500_H */
1.8.0
On Tue, Mar 12, 2013 at 07:16:03PM +0800, Hongbo Zhang wrote:
[ .. ]
return -EINVAL;
You'll want to return the original error code, which for example can be ENOMEM where an error message is not needed. Also, I suspect abx500_hwmon_init might at some point return -EPROBE_DEFER. If that ever happens, you definitely want to fail silently and return the correct error code.
Yes, should return err in this probe function. Where to return -EPROBE_DEFER? when ab8500_gpadc_get() or ab8500_btemp_get() fails? In fact these two interfaces aren't so good, because we cannot tell the reason when they return fail. A solution I can imagine now is to send another separate patch for these two interfaces, letting them return -EPROBE_DEFER if they are not initialized when being called.
Guess the point I am trying to make is that this is an external API (ie it calls code in another module), and you don't know if and when -EPROBE_DEFER may be returned. So you should always pass errors up the chain, unless there is a well defined reason to not do so.
Thanks, Guenter
linaro-kernel@lists.linaro.org