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 * const 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 HWMON_T_MAX_ALARM, 304 305 HWMON_T_LABEL | 306 HWMON_T_INPUT | 307 HWMON_T_MIN_ALARM | 308 HWMON_T_MAX_ALARM), 309 310 HWMON_CHANNEL_INFO(curr, 311 HWMON_C_AVERAGE | 312 HWMON_C_MAX | 313 HWMON_C_INPUT), 314 315 HWMON_CHANNEL_INFO(in, 316 HWMON_I_AVERAGE | 317 HWMON_I_MIN | 318 HWMON_I_MAX | 319 HWMON_I_INPUT), 320 NULL 321 }; 322 323 static const struct hwmon_chip_info power_supply_hwmon_chip_info = { 324 .ops = &power_supply_hwmon_ops, 325 .info = power_supply_hwmon_info, 326 }; 327 328 int power_supply_add_hwmon_sysfs(struct power_supply *psy) 329 { 330 const struct power_supply_desc *desc = psy->desc; 331 struct power_supply_hwmon *psyhw; 332 struct device *dev = &psy->dev; 333 struct device *hwmon; 334 int ret, i; 335 const char *name; 336 337 if (!devres_open_group(dev, power_supply_add_hwmon_sysfs, 338 GFP_KERNEL)) 339 return -ENOMEM; 340 341 psyhw = devm_kzalloc(dev, sizeof(*psyhw), GFP_KERNEL); 342 if (!psyhw) { 343 ret = -ENOMEM; 344 goto error; 345 } 346 347 psyhw->psy = psy; 348 psyhw->props = devm_bitmap_zalloc(dev, 349 POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1, 350 GFP_KERNEL); 351 if (!psyhw->props) { 352 ret = -ENOMEM; 353 goto error; 354 } 355 356 for (i = 0; i < desc->num_properties; i++) { 357 const enum power_supply_property prop = desc->properties[i]; 358 359 switch (prop) { 360 case POWER_SUPPLY_PROP_CURRENT_AVG: 361 case POWER_SUPPLY_PROP_CURRENT_MAX: 362 case POWER_SUPPLY_PROP_CURRENT_NOW: 363 case POWER_SUPPLY_PROP_TEMP: 364 case POWER_SUPPLY_PROP_TEMP_MAX: 365 case POWER_SUPPLY_PROP_TEMP_MIN: 366 case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: 367 case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: 368 case POWER_SUPPLY_PROP_TEMP_AMBIENT: 369 case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN: 370 case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX: 371 case POWER_SUPPLY_PROP_VOLTAGE_AVG: 372 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 373 case POWER_SUPPLY_PROP_VOLTAGE_MAX: 374 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 375 set_bit(prop, psyhw->props); 376 break; 377 default: 378 break; 379 } 380 } 381 382 name = psy->desc->name; 383 if (strchr(name, '-')) { 384 char *new_name; 385 386 new_name = devm_kstrdup(dev, name, GFP_KERNEL); 387 if (!new_name) { 388 ret = -ENOMEM; 389 goto error; 390 } 391 strreplace(new_name, '-', '_'); 392 name = new_name; 393 } 394 hwmon = devm_hwmon_device_register_with_info(dev, name, 395 psyhw, 396 &power_supply_hwmon_chip_info, 397 NULL); 398 ret = PTR_ERR_OR_ZERO(hwmon); 399 if (ret) 400 goto error; 401 402 devres_close_group(dev, power_supply_add_hwmon_sysfs); 403 return 0; 404 error: 405 devres_release_group(dev, NULL); 406 return ret; 407 } 408 409 void power_supply_remove_hwmon_sysfs(struct power_supply *psy) 410 { 411 devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs); 412 } 413