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 #include "power_supply.h" 11 12 struct power_supply_hwmon { 13 struct power_supply *psy; 14 unsigned long *props; 15 }; 16 17 static const char *const ps_temp_label[] = { 18 "temp", 19 "ambient temp", 20 }; 21 22 static int power_supply_hwmon_in_to_property(u32 attr) 23 { 24 switch (attr) { 25 case hwmon_in_average: 26 return POWER_SUPPLY_PROP_VOLTAGE_AVG; 27 case hwmon_in_min: 28 return POWER_SUPPLY_PROP_VOLTAGE_MIN; 29 case hwmon_in_max: 30 return POWER_SUPPLY_PROP_VOLTAGE_MAX; 31 case hwmon_in_input: 32 return POWER_SUPPLY_PROP_VOLTAGE_NOW; 33 default: 34 return -EINVAL; 35 } 36 } 37 38 static int power_supply_hwmon_curr_to_property(u32 attr) 39 { 40 switch (attr) { 41 case hwmon_curr_average: 42 return POWER_SUPPLY_PROP_CURRENT_AVG; 43 case hwmon_curr_max: 44 return POWER_SUPPLY_PROP_CURRENT_MAX; 45 case hwmon_curr_input: 46 return POWER_SUPPLY_PROP_CURRENT_NOW; 47 default: 48 return -EINVAL; 49 } 50 } 51 52 static int power_supply_hwmon_power_to_property(u32 attr) 53 { 54 switch (attr) { 55 case hwmon_power_input: 56 return POWER_SUPPLY_PROP_POWER_NOW; 57 case hwmon_power_average: 58 return POWER_SUPPLY_PROP_POWER_AVG; 59 default: 60 return -EINVAL; 61 } 62 } 63 64 static int power_supply_hwmon_temp_to_property(u32 attr, int channel) 65 { 66 if (channel) { 67 switch (attr) { 68 case hwmon_temp_input: 69 return POWER_SUPPLY_PROP_TEMP_AMBIENT; 70 case hwmon_temp_min_alarm: 71 return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN; 72 case hwmon_temp_max_alarm: 73 return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX; 74 default: 75 break; 76 } 77 } else { 78 switch (attr) { 79 case hwmon_temp_input: 80 return POWER_SUPPLY_PROP_TEMP; 81 case hwmon_temp_max: 82 return POWER_SUPPLY_PROP_TEMP_MAX; 83 case hwmon_temp_min: 84 return POWER_SUPPLY_PROP_TEMP_MIN; 85 case hwmon_temp_min_alarm: 86 return POWER_SUPPLY_PROP_TEMP_ALERT_MIN; 87 case hwmon_temp_max_alarm: 88 return POWER_SUPPLY_PROP_TEMP_ALERT_MAX; 89 default: 90 break; 91 } 92 } 93 94 return -EINVAL; 95 } 96 97 static int 98 power_supply_hwmon_to_property(enum hwmon_sensor_types type, 99 u32 attr, int channel) 100 { 101 switch (type) { 102 case hwmon_in: 103 return power_supply_hwmon_in_to_property(attr); 104 case hwmon_curr: 105 return power_supply_hwmon_curr_to_property(attr); 106 case hwmon_power: 107 return power_supply_hwmon_power_to_property(attr); 108 case hwmon_temp: 109 return power_supply_hwmon_temp_to_property(attr, channel); 110 default: 111 return -EINVAL; 112 } 113 } 114 115 static bool power_supply_hwmon_is_a_label(enum hwmon_sensor_types type, 116 u32 attr) 117 { 118 return type == hwmon_temp && attr == hwmon_temp_label; 119 } 120 121 struct hwmon_type_attr_list { 122 const u32 *attrs; 123 size_t n_attrs; 124 }; 125 126 static const u32 ps_temp_attrs[] = { 127 hwmon_temp_input, 128 hwmon_temp_min, hwmon_temp_max, 129 hwmon_temp_min_alarm, hwmon_temp_max_alarm, 130 }; 131 132 static const struct hwmon_type_attr_list ps_type_attrs[hwmon_max] = { 133 [hwmon_temp] = { ps_temp_attrs, ARRAY_SIZE(ps_temp_attrs) }, 134 }; 135 136 static bool power_supply_hwmon_has_input( 137 const struct power_supply_hwmon *psyhw, 138 enum hwmon_sensor_types type, int channel) 139 { 140 const struct hwmon_type_attr_list *attr_list = &ps_type_attrs[type]; 141 size_t i; 142 143 for (i = 0; i < attr_list->n_attrs; ++i) { 144 int prop = power_supply_hwmon_to_property(type, 145 attr_list->attrs[i], channel); 146 147 if (prop >= 0 && test_bit(prop, psyhw->props)) 148 return true; 149 } 150 151 return false; 152 } 153 154 static bool power_supply_hwmon_is_writable(enum hwmon_sensor_types type, 155 u32 attr) 156 { 157 switch (type) { 158 case hwmon_in: 159 return attr == hwmon_in_min || 160 attr == hwmon_in_max; 161 case hwmon_curr: 162 return attr == hwmon_curr_max; 163 case hwmon_temp: 164 return attr == hwmon_temp_max || 165 attr == hwmon_temp_min || 166 attr == hwmon_temp_min_alarm || 167 attr == hwmon_temp_max_alarm; 168 default: 169 return false; 170 } 171 } 172 173 static umode_t power_supply_hwmon_is_visible(const void *data, 174 enum hwmon_sensor_types type, 175 u32 attr, int channel) 176 { 177 const struct power_supply_hwmon *psyhw = data; 178 int prop; 179 180 if (power_supply_hwmon_is_a_label(type, attr)) { 181 if (power_supply_hwmon_has_input(psyhw, type, channel)) 182 return 0444; 183 else 184 return 0; 185 } 186 187 prop = power_supply_hwmon_to_property(type, attr, channel); 188 if (prop < 0 || !test_bit(prop, psyhw->props)) 189 return 0; 190 191 if (power_supply_property_is_writeable(psyhw->psy, prop) > 0 && 192 power_supply_hwmon_is_writable(type, attr)) 193 return 0644; 194 195 return 0444; 196 } 197 198 static int power_supply_hwmon_read_string(struct device *dev, 199 enum hwmon_sensor_types type, 200 u32 attr, int channel, 201 const char **str) 202 { 203 switch (type) { 204 case hwmon_temp: 205 *str = ps_temp_label[channel]; 206 break; 207 default: 208 /* unreachable, but see: 209 * gcc bug #51513 [1] and clang bug #978 [2] 210 * 211 * [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51513 212 * [2] https://github.com/ClangBuiltLinux/linux/issues/978 213 */ 214 break; 215 } 216 217 return 0; 218 } 219 220 static int 221 power_supply_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 222 u32 attr, int channel, long *val) 223 { 224 struct power_supply_hwmon *psyhw = dev_get_drvdata(dev); 225 struct power_supply *psy = psyhw->psy; 226 union power_supply_propval pspval; 227 int ret, prop; 228 229 prop = power_supply_hwmon_to_property(type, attr, channel); 230 if (prop < 0) 231 return prop; 232 233 ret = power_supply_get_property(psy, prop, &pspval); 234 if (ret) 235 return ret; 236 237 switch (type) { 238 /* 239 * Both voltage and current is reported in units of 240 * microvolts/microamps, so we need to adjust it to 241 * milliamps(volts) 242 */ 243 case hwmon_curr: 244 case hwmon_in: 245 pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 1000); 246 break; 247 case hwmon_power: 248 /* 249 * Power properties are already in microwatts. 250 */ 251 break; 252 /* 253 * Temp needs to be converted from 1/10 C to milli-C 254 */ 255 case hwmon_temp: 256 if (check_mul_overflow(pspval.intval, 100, 257 &pspval.intval)) 258 return -EOVERFLOW; 259 break; 260 default: 261 return -EINVAL; 262 } 263 264 *val = pspval.intval; 265 266 return 0; 267 } 268 269 static int 270 power_supply_hwmon_write(struct device *dev, enum hwmon_sensor_types type, 271 u32 attr, int channel, long val) 272 { 273 struct power_supply_hwmon *psyhw = dev_get_drvdata(dev); 274 struct power_supply *psy = psyhw->psy; 275 union power_supply_propval pspval; 276 int prop; 277 278 prop = power_supply_hwmon_to_property(type, attr, channel); 279 if (prop < 0) 280 return prop; 281 282 pspval.intval = val; 283 284 switch (type) { 285 /* 286 * Both voltage and current is reported in units of 287 * microvolts/microamps, so we need to adjust it to 288 * milliamps(volts) 289 */ 290 case hwmon_curr: 291 case hwmon_in: 292 if (check_mul_overflow(pspval.intval, 1000, 293 &pspval.intval)) 294 return -EOVERFLOW; 295 break; 296 /* 297 * Temp needs to be converted from 1/10 C to milli-C 298 */ 299 case hwmon_temp: 300 pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 100); 301 break; 302 default: 303 return -EINVAL; 304 } 305 306 return power_supply_set_property(psy, prop, &pspval); 307 } 308 309 static const struct hwmon_ops power_supply_hwmon_ops = { 310 .is_visible = power_supply_hwmon_is_visible, 311 .read = power_supply_hwmon_read, 312 .write = power_supply_hwmon_write, 313 .read_string = power_supply_hwmon_read_string, 314 }; 315 316 static const struct hwmon_channel_info * const power_supply_hwmon_info[] = { 317 HWMON_CHANNEL_INFO(temp, 318 HWMON_T_LABEL | 319 HWMON_T_INPUT | 320 HWMON_T_MAX | 321 HWMON_T_MIN | 322 HWMON_T_MIN_ALARM | 323 HWMON_T_MAX_ALARM, 324 325 HWMON_T_LABEL | 326 HWMON_T_INPUT | 327 HWMON_T_MIN_ALARM | 328 HWMON_T_MAX_ALARM), 329 330 HWMON_CHANNEL_INFO(curr, 331 HWMON_C_AVERAGE | 332 HWMON_C_MAX | 333 HWMON_C_INPUT), 334 335 HWMON_CHANNEL_INFO(power, 336 HWMON_P_INPUT | 337 HWMON_P_AVERAGE), 338 339 HWMON_CHANNEL_INFO(in, 340 HWMON_I_AVERAGE | 341 HWMON_I_MIN | 342 HWMON_I_MAX | 343 HWMON_I_INPUT), 344 NULL 345 }; 346 347 static const struct hwmon_chip_info power_supply_hwmon_chip_info = { 348 .ops = &power_supply_hwmon_ops, 349 .info = power_supply_hwmon_info, 350 }; 351 352 int power_supply_add_hwmon_sysfs(struct power_supply *psy) 353 { 354 const struct power_supply_desc *desc = psy->desc; 355 struct power_supply_hwmon *psyhw; 356 struct device *dev = &psy->dev; 357 struct device *hwmon; 358 int ret, i; 359 const char *name; 360 361 if (!devres_open_group(dev, power_supply_add_hwmon_sysfs, 362 GFP_KERNEL)) 363 return -ENOMEM; 364 365 psyhw = devm_kzalloc(dev, sizeof(*psyhw), GFP_KERNEL); 366 if (!psyhw) { 367 ret = -ENOMEM; 368 goto error; 369 } 370 371 psyhw->psy = psy; 372 psyhw->props = devm_bitmap_zalloc(dev, 373 POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1, 374 GFP_KERNEL); 375 if (!psyhw->props) { 376 ret = -ENOMEM; 377 goto error; 378 } 379 380 for (i = 0; i < desc->num_properties; i++) { 381 const enum power_supply_property prop = desc->properties[i]; 382 383 switch (prop) { 384 case POWER_SUPPLY_PROP_CURRENT_AVG: 385 case POWER_SUPPLY_PROP_CURRENT_MAX: 386 case POWER_SUPPLY_PROP_CURRENT_NOW: 387 case POWER_SUPPLY_PROP_POWER_AVG: 388 case POWER_SUPPLY_PROP_POWER_NOW: 389 case POWER_SUPPLY_PROP_TEMP: 390 case POWER_SUPPLY_PROP_TEMP_MAX: 391 case POWER_SUPPLY_PROP_TEMP_MIN: 392 case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: 393 case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: 394 case POWER_SUPPLY_PROP_TEMP_AMBIENT: 395 case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN: 396 case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX: 397 case POWER_SUPPLY_PROP_VOLTAGE_AVG: 398 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 399 case POWER_SUPPLY_PROP_VOLTAGE_MAX: 400 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 401 set_bit(prop, psyhw->props); 402 break; 403 default: 404 break; 405 } 406 } 407 408 name = psy->desc->name; 409 if (strchr(name, '-')) { 410 char *new_name; 411 412 new_name = devm_kstrdup(dev, name, GFP_KERNEL); 413 if (!new_name) { 414 ret = -ENOMEM; 415 goto error; 416 } 417 strreplace(new_name, '-', '_'); 418 name = new_name; 419 } 420 hwmon = devm_hwmon_device_register_with_info(dev, name, 421 psyhw, 422 &power_supply_hwmon_chip_info, 423 NULL); 424 ret = PTR_ERR_OR_ZERO(hwmon); 425 if (ret) 426 goto error; 427 428 devres_close_group(dev, power_supply_add_hwmon_sysfs); 429 return 0; 430 error: 431 devres_release_group(dev, NULL); 432 return ret; 433 } 434 435 void power_supply_remove_hwmon_sysfs(struct power_supply *psy) 436 { 437 devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs); 438 } 439