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 const char *name; 288 289 if (!devres_open_group(dev, power_supply_add_hwmon_sysfs, 290 GFP_KERNEL)) 291 return -ENOMEM; 292 293 psyhw = devm_kzalloc(dev, sizeof(*psyhw), GFP_KERNEL); 294 if (!psyhw) { 295 ret = -ENOMEM; 296 goto error; 297 } 298 299 psyhw->psy = psy; 300 psyhw->props = bitmap_zalloc(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1, 301 GFP_KERNEL); 302 if (!psyhw->props) { 303 ret = -ENOMEM; 304 goto error; 305 } 306 307 ret = devm_add_action(dev, power_supply_hwmon_bitmap_free, 308 psyhw->props); 309 if (ret) 310 goto error; 311 312 for (i = 0; i < desc->num_properties; i++) { 313 const enum power_supply_property prop = desc->properties[i]; 314 315 switch (prop) { 316 case POWER_SUPPLY_PROP_CURRENT_AVG: 317 case POWER_SUPPLY_PROP_CURRENT_MAX: 318 case POWER_SUPPLY_PROP_CURRENT_NOW: 319 case POWER_SUPPLY_PROP_TEMP: 320 case POWER_SUPPLY_PROP_TEMP_MAX: 321 case POWER_SUPPLY_PROP_TEMP_MIN: 322 case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: 323 case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: 324 case POWER_SUPPLY_PROP_TEMP_AMBIENT: 325 case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN: 326 case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX: 327 case POWER_SUPPLY_PROP_VOLTAGE_AVG: 328 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 329 case POWER_SUPPLY_PROP_VOLTAGE_MAX: 330 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 331 set_bit(prop, psyhw->props); 332 break; 333 default: 334 break; 335 } 336 } 337 338 name = psy->desc->name; 339 if (strchr(name, '-')) { 340 char *new_name; 341 342 new_name = devm_kstrdup(dev, name, GFP_KERNEL); 343 if (!new_name) { 344 ret = -ENOMEM; 345 goto error; 346 } 347 strreplace(new_name, '-', '_'); 348 name = new_name; 349 } 350 hwmon = devm_hwmon_device_register_with_info(dev, name, 351 psyhw, 352 &power_supply_hwmon_chip_info, 353 NULL); 354 ret = PTR_ERR_OR_ZERO(hwmon); 355 if (ret) 356 goto error; 357 358 devres_close_group(dev, power_supply_add_hwmon_sysfs); 359 return 0; 360 error: 361 devres_release_group(dev, NULL); 362 return ret; 363 } 364 365 void power_supply_remove_hwmon_sysfs(struct power_supply *psy) 366 { 367 devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs); 368 } 369