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