Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

power: supply: Add HWMON compatibility layer

Add code implementing HWMON adapter/compatibility layer to allow
expositing various sensors present on power supply devices via HWMON
subsystem. This is done in order to allow userspace to use single
ABI/library(libsensors) to access/manipulate all of the sensors of the
system.

Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Tested-by: Chris Healy <cphealy@gmail.com>
Cc: Chris Healy <cphealy@gmail.com>
Cc: Cory Tusar <cory.tusar@zii.aero>
Cc: Lucas Stach <l.stach@pengutronix.de>
Cc: Fabio Estevam <fabio.estevam@nxp.com>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Sebastian Reichel <sre@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: linux-pm@vger.kernel.org
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>

authored by

Andrey Smirnov and committed by
Sebastian Reichel
e67d4dfc 22ee8384

+390
+14
drivers/power/supply/Kconfig
··· 14 14 Say Y here to enable debugging messages for power supply class 15 15 and drivers. 16 16 17 + config POWER_SUPPLY_HWMON 18 + bool 19 + prompt "Expose power supply sensors as hwmon device" 20 + depends on HWMON=y || HWMON=POWER_SUPPLY 21 + default y 22 + help 23 + This options enables API that allows sensors found on a 24 + power supply device (current, voltage, temperature) to be 25 + exposed as a hwmon device. 26 + 27 + Say 'Y' here if you want power supplies to 28 + have hwmon sysfs interface too. 29 + 30 + 17 31 config PDA_POWER 18 32 tristate "Generic PDA/phone power driver" 19 33 depends on !S390
+1
drivers/power/supply/Makefile
··· 6 6 power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o 7 7 8 8 obj-$(CONFIG_POWER_SUPPLY) += power_supply.o 9 + obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o 9 10 obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o 10 11 11 12 obj-$(CONFIG_PDA_POWER) += pda_power.o
+7
drivers/power/supply/power_supply_core.c
··· 1072 1072 if (rc) 1073 1073 goto create_triggers_failed; 1074 1074 1075 + rc = power_supply_add_hwmon_sysfs(psy); 1076 + if (rc) 1077 + goto add_hwmon_sysfs_failed; 1078 + 1075 1079 /* 1076 1080 * Update use_cnt after any uevents (most notably from device_add()). 1077 1081 * We are here still during driver's probe but ··· 1094 1090 1095 1091 return psy; 1096 1092 1093 + add_hwmon_sysfs_failed: 1094 + power_supply_remove_triggers(psy); 1097 1095 create_triggers_failed: 1098 1096 psy_unregister_cooler(psy); 1099 1097 register_cooler_failed: ··· 1248 1242 cancel_work_sync(&psy->changed_work); 1249 1243 cancel_delayed_work_sync(&psy->deferred_register_work); 1250 1244 sysfs_remove_link(&psy->dev.kobj, "powers"); 1245 + power_supply_remove_hwmon_sysfs(psy); 1251 1246 power_supply_remove_triggers(psy); 1252 1247 psy_unregister_cooler(psy); 1253 1248 psy_unregister_thermal(psy);
+355
drivers/power/supply/power_supply_hwmon.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * power_supply_hwmon.c - power supply hwmon support. 4 + */ 5 + 6 + #include <linux/err.h> 7 + #include <linux/hwmon.h> 8 + #include <linux/power_supply.h> 9 + #include <linux/slab.h> 10 + 11 + struct power_supply_hwmon { 12 + struct power_supply *psy; 13 + unsigned long *props; 14 + }; 15 + 16 + static int power_supply_hwmon_in_to_property(u32 attr) 17 + { 18 + switch (attr) { 19 + case hwmon_in_average: 20 + return POWER_SUPPLY_PROP_VOLTAGE_AVG; 21 + case hwmon_in_min: 22 + return POWER_SUPPLY_PROP_VOLTAGE_MIN; 23 + case hwmon_in_max: 24 + return POWER_SUPPLY_PROP_VOLTAGE_MAX; 25 + case hwmon_in_input: 26 + return POWER_SUPPLY_PROP_VOLTAGE_NOW; 27 + default: 28 + return -EINVAL; 29 + } 30 + } 31 + 32 + static int power_supply_hwmon_curr_to_property(u32 attr) 33 + { 34 + switch (attr) { 35 + case hwmon_curr_average: 36 + return POWER_SUPPLY_PROP_CURRENT_AVG; 37 + case hwmon_curr_max: 38 + return POWER_SUPPLY_PROP_CURRENT_MAX; 39 + case hwmon_curr_input: 40 + return POWER_SUPPLY_PROP_CURRENT_NOW; 41 + default: 42 + return -EINVAL; 43 + } 44 + } 45 + 46 + static int power_supply_hwmon_temp_to_property(u32 attr, int channel) 47 + { 48 + if (channel) { 49 + switch (attr) { 50 + case hwmon_temp_input: 51 + return POWER_SUPPLY_PROP_TEMP_AMBIENT; 52 + case hwmon_temp_min_alarm: 53 + return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN; 54 + case hwmon_temp_max_alarm: 55 + return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX; 56 + default: 57 + break; 58 + } 59 + } else { 60 + switch (attr) { 61 + case hwmon_temp_input: 62 + return POWER_SUPPLY_PROP_TEMP; 63 + case hwmon_temp_max: 64 + return POWER_SUPPLY_PROP_TEMP_MAX; 65 + case hwmon_temp_min: 66 + return POWER_SUPPLY_PROP_TEMP_MIN; 67 + case hwmon_temp_min_alarm: 68 + return POWER_SUPPLY_PROP_TEMP_ALERT_MIN; 69 + case hwmon_temp_max_alarm: 70 + return POWER_SUPPLY_PROP_TEMP_ALERT_MAX; 71 + default: 72 + break; 73 + } 74 + } 75 + 76 + return -EINVAL; 77 + } 78 + 79 + static int 80 + power_supply_hwmon_to_property(enum hwmon_sensor_types type, 81 + u32 attr, int channel) 82 + { 83 + switch (type) { 84 + case hwmon_in: 85 + return power_supply_hwmon_in_to_property(attr); 86 + case hwmon_curr: 87 + return power_supply_hwmon_curr_to_property(attr); 88 + case hwmon_temp: 89 + return power_supply_hwmon_temp_to_property(attr, channel); 90 + default: 91 + return -EINVAL; 92 + } 93 + } 94 + 95 + static bool power_supply_hwmon_is_a_label(enum hwmon_sensor_types type, 96 + u32 attr) 97 + { 98 + return type == hwmon_temp && attr == hwmon_temp_label; 99 + } 100 + 101 + static bool power_supply_hwmon_is_writable(enum hwmon_sensor_types type, 102 + u32 attr) 103 + { 104 + switch (type) { 105 + case hwmon_in: 106 + return attr == hwmon_in_min || 107 + attr == hwmon_in_max; 108 + case hwmon_curr: 109 + return attr == hwmon_curr_max; 110 + case hwmon_temp: 111 + return attr == hwmon_temp_max || 112 + attr == hwmon_temp_min || 113 + attr == hwmon_temp_min_alarm || 114 + attr == hwmon_temp_max_alarm; 115 + default: 116 + return false; 117 + } 118 + } 119 + 120 + static umode_t power_supply_hwmon_is_visible(const void *data, 121 + enum hwmon_sensor_types type, 122 + u32 attr, int channel) 123 + { 124 + const struct power_supply_hwmon *psyhw = data; 125 + int prop; 126 + 127 + 128 + if (power_supply_hwmon_is_a_label(type, attr)) 129 + return 0444; 130 + 131 + prop = power_supply_hwmon_to_property(type, attr, channel); 132 + if (prop < 0 || !test_bit(prop, psyhw->props)) 133 + return 0; 134 + 135 + if (power_supply_property_is_writeable(psyhw->psy, prop) > 0 && 136 + power_supply_hwmon_is_writable(type, attr)) 137 + return 0644; 138 + 139 + return 0444; 140 + } 141 + 142 + static int power_supply_hwmon_read_string(struct device *dev, 143 + enum hwmon_sensor_types type, 144 + u32 attr, int channel, 145 + const char **str) 146 + { 147 + *str = channel ? "temp" : "temp ambient"; 148 + return 0; 149 + } 150 + 151 + static int 152 + power_supply_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 153 + u32 attr, int channel, long *val) 154 + { 155 + struct power_supply_hwmon *psyhw = dev_get_drvdata(dev); 156 + struct power_supply *psy = psyhw->psy; 157 + union power_supply_propval pspval; 158 + int ret, prop; 159 + 160 + prop = power_supply_hwmon_to_property(type, attr, channel); 161 + if (prop < 0) 162 + return prop; 163 + 164 + ret = power_supply_get_property(psy, prop, &pspval); 165 + if (ret) 166 + return ret; 167 + 168 + switch (type) { 169 + /* 170 + * Both voltage and current is reported in units of 171 + * microvolts/microamps, so we need to adjust it to 172 + * milliamps(volts) 173 + */ 174 + case hwmon_curr: 175 + case hwmon_in: 176 + pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 1000); 177 + break; 178 + /* 179 + * Temp needs to be converted from 1/10 C to milli-C 180 + */ 181 + case hwmon_temp: 182 + if (check_mul_overflow(pspval.intval, 100, 183 + &pspval.intval)) 184 + return -EOVERFLOW; 185 + break; 186 + default: 187 + return -EINVAL; 188 + } 189 + 190 + *val = pspval.intval; 191 + 192 + return 0; 193 + } 194 + 195 + static int 196 + power_supply_hwmon_write(struct device *dev, enum hwmon_sensor_types type, 197 + u32 attr, int channel, long val) 198 + { 199 + struct power_supply_hwmon *psyhw = dev_get_drvdata(dev); 200 + struct power_supply *psy = psyhw->psy; 201 + union power_supply_propval pspval; 202 + int prop; 203 + 204 + prop = power_supply_hwmon_to_property(type, attr, channel); 205 + if (prop < 0) 206 + return prop; 207 + 208 + pspval.intval = val; 209 + 210 + switch (type) { 211 + /* 212 + * Both voltage and current is reported in units of 213 + * microvolts/microamps, so we need to adjust it to 214 + * milliamps(volts) 215 + */ 216 + case hwmon_curr: 217 + case hwmon_in: 218 + if (check_mul_overflow(pspval.intval, 1000, 219 + &pspval.intval)) 220 + return -EOVERFLOW; 221 + break; 222 + /* 223 + * Temp needs to be converted from 1/10 C to milli-C 224 + */ 225 + case hwmon_temp: 226 + pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 100); 227 + break; 228 + default: 229 + return -EINVAL; 230 + } 231 + 232 + return power_supply_set_property(psy, prop, &pspval); 233 + } 234 + 235 + static const struct hwmon_ops power_supply_hwmon_ops = { 236 + .is_visible = power_supply_hwmon_is_visible, 237 + .read = power_supply_hwmon_read, 238 + .write = power_supply_hwmon_write, 239 + .read_string = power_supply_hwmon_read_string, 240 + }; 241 + 242 + static const struct hwmon_channel_info *power_supply_hwmon_info[] = { 243 + HWMON_CHANNEL_INFO(temp, 244 + HWMON_T_LABEL | 245 + HWMON_T_INPUT | 246 + HWMON_T_MAX | 247 + HWMON_T_MIN | 248 + HWMON_T_MIN_ALARM | 249 + HWMON_T_MIN_ALARM, 250 + 251 + HWMON_T_LABEL | 252 + HWMON_T_INPUT | 253 + HWMON_T_MIN_ALARM | 254 + HWMON_T_LABEL | 255 + HWMON_T_MAX_ALARM), 256 + 257 + HWMON_CHANNEL_INFO(curr, 258 + HWMON_C_AVERAGE | 259 + HWMON_C_MAX | 260 + HWMON_C_INPUT), 261 + 262 + HWMON_CHANNEL_INFO(in, 263 + HWMON_I_AVERAGE | 264 + HWMON_I_MIN | 265 + HWMON_I_MAX | 266 + HWMON_I_INPUT), 267 + NULL 268 + }; 269 + 270 + static const struct hwmon_chip_info power_supply_hwmon_chip_info = { 271 + .ops = &power_supply_hwmon_ops, 272 + .info = power_supply_hwmon_info, 273 + }; 274 + 275 + static void power_supply_hwmon_bitmap_free(void *data) 276 + { 277 + bitmap_free(data); 278 + } 279 + 280 + int power_supply_add_hwmon_sysfs(struct power_supply *psy) 281 + { 282 + const struct power_supply_desc *desc = psy->desc; 283 + struct power_supply_hwmon *psyhw; 284 + struct device *dev = &psy->dev; 285 + struct device *hwmon; 286 + int ret, i; 287 + 288 + if (!devres_open_group(dev, power_supply_add_hwmon_sysfs, 289 + GFP_KERNEL)) 290 + return -ENOMEM; 291 + 292 + psyhw = devm_kzalloc(dev, sizeof(*psyhw), GFP_KERNEL); 293 + if (!psyhw) { 294 + ret = -ENOMEM; 295 + goto error; 296 + } 297 + 298 + psyhw->psy = psy; 299 + psyhw->props = bitmap_zalloc(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1, 300 + GFP_KERNEL); 301 + if (!psyhw->props) { 302 + ret = -ENOMEM; 303 + goto error; 304 + } 305 + 306 + ret = devm_add_action(dev, power_supply_hwmon_bitmap_free, 307 + psyhw->props); 308 + if (ret) 309 + goto error; 310 + 311 + for (i = 0; i < desc->num_properties; i++) { 312 + const enum power_supply_property prop = desc->properties[i]; 313 + 314 + switch (prop) { 315 + case POWER_SUPPLY_PROP_CURRENT_AVG: 316 + case POWER_SUPPLY_PROP_CURRENT_MAX: 317 + case POWER_SUPPLY_PROP_CURRENT_NOW: 318 + case POWER_SUPPLY_PROP_TEMP: 319 + case POWER_SUPPLY_PROP_TEMP_MAX: 320 + case POWER_SUPPLY_PROP_TEMP_MIN: 321 + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: 322 + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: 323 + case POWER_SUPPLY_PROP_TEMP_AMBIENT: 324 + case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN: 325 + case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX: 326 + case POWER_SUPPLY_PROP_VOLTAGE_AVG: 327 + case POWER_SUPPLY_PROP_VOLTAGE_MIN: 328 + case POWER_SUPPLY_PROP_VOLTAGE_MAX: 329 + case POWER_SUPPLY_PROP_VOLTAGE_NOW: 330 + set_bit(prop, psyhw->props); 331 + break; 332 + default: 333 + break; 334 + } 335 + } 336 + 337 + hwmon = devm_hwmon_device_register_with_info(dev, psy->desc->name, 338 + psyhw, 339 + &power_supply_hwmon_chip_info, 340 + NULL); 341 + ret = PTR_ERR_OR_ZERO(hwmon); 342 + if (ret) 343 + goto error; 344 + 345 + devres_close_group(dev, power_supply_add_hwmon_sysfs); 346 + return 0; 347 + error: 348 + devres_release_group(dev, NULL); 349 + return ret; 350 + } 351 + 352 + void power_supply_remove_hwmon_sysfs(struct power_supply *psy) 353 + { 354 + devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs); 355 + }
+13
include/linux/power_supply.h
··· 481 481 return 0; 482 482 } 483 483 484 + #ifdef CONFIG_POWER_SUPPLY_HWMON 485 + int power_supply_add_hwmon_sysfs(struct power_supply *psy); 486 + void power_supply_remove_hwmon_sysfs(struct power_supply *psy); 487 + #else 488 + static inline int power_supply_add_hwmon_sysfs(struct power_supply *psy) 489 + { 490 + return 0; 491 + } 492 + 493 + static inline 494 + void power_supply_remove_hwmon_sysfs(struct power_supply *psy) {} 495 + #endif 496 + 484 497 #endif /* __LINUX_POWER_SUPPLY_H__ */