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