Branch data Line data Source code
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 : 0 : static int power_supply_hwmon_temp_to_property(u32 attr, int channel) 47 : : { 48 : 0 : if (channel) { 49 : 0 : switch (attr) { 50 : : case hwmon_temp_input: 51 : : return POWER_SUPPLY_PROP_TEMP_AMBIENT; 52 : : case hwmon_temp_min_alarm: 53 : 0 : return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN; 54 : : case hwmon_temp_max_alarm: 55 : 0 : return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX; 56 : : default: 57 : : break; 58 : : } 59 : : } else { 60 : 0 : switch (attr) { 61 : : case hwmon_temp_input: 62 : : return POWER_SUPPLY_PROP_TEMP; 63 : : case hwmon_temp_max: 64 : 0 : return POWER_SUPPLY_PROP_TEMP_MAX; 65 : : case hwmon_temp_min: 66 : 0 : return POWER_SUPPLY_PROP_TEMP_MIN; 67 : : case hwmon_temp_min_alarm: 68 : 0 : return POWER_SUPPLY_PROP_TEMP_ALERT_MIN; 69 : : case hwmon_temp_max_alarm: 70 : 0 : return POWER_SUPPLY_PROP_TEMP_ALERT_MAX; 71 : : default: 72 : : break; 73 : : } 74 : : } 75 : : 76 : 0 : return -EINVAL; 77 : : } 78 : : 79 : : static int 80 : 0 : power_supply_hwmon_to_property(enum hwmon_sensor_types type, 81 : : u32 attr, int channel) 82 : : { 83 : 0 : switch (type) { 84 : : case hwmon_in: 85 : 0 : return power_supply_hwmon_in_to_property(attr); 86 : : case hwmon_curr: 87 : 0 : return power_supply_hwmon_curr_to_property(attr); 88 : : case hwmon_temp: 89 : 0 : 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 : 0 : return type == hwmon_temp && attr == hwmon_temp_label; 99 : : } 100 : : 101 : 0 : static bool power_supply_hwmon_is_writable(enum hwmon_sensor_types type, 102 : : u32 attr) 103 : : { 104 : 0 : switch (type) { 105 : : case hwmon_in: 106 : 0 : return attr == hwmon_in_min || 107 : : attr == hwmon_in_max; 108 : : case hwmon_curr: 109 : 0 : return attr == hwmon_curr_max; 110 : : case hwmon_temp: 111 : 0 : return attr == hwmon_temp_max || 112 : 0 : attr == hwmon_temp_min || 113 : 0 : attr == hwmon_temp_min_alarm || 114 : : attr == hwmon_temp_max_alarm; 115 : : default: 116 : : return false; 117 : : } 118 : : } 119 : : 120 : 0 : 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 : 0 : if (power_supply_hwmon_is_a_label(type, attr)) 129 : : return 0444; 130 : : 131 : 0 : prop = power_supply_hwmon_to_property(type, attr, channel); 132 : 0 : if (prop < 0 || !test_bit(prop, psyhw->props)) 133 : : return 0; 134 : : 135 : 0 : if (power_supply_property_is_writeable(psyhw->psy, prop) > 0 && 136 : 0 : power_supply_hwmon_is_writable(type, attr)) 137 : : return 0644; 138 : : 139 : 0 : return 0444; 140 : : } 141 : : 142 : 0 : 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 : 0 : *str = channel ? "temp ambient" : "temp"; 148 : 0 : return 0; 149 : : } 150 : : 151 : : static int 152 : 0 : 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 : 0 : struct power_supply *psy = psyhw->psy; 157 : : union power_supply_propval pspval; 158 : : int ret, prop; 159 : : 160 : 0 : prop = power_supply_hwmon_to_property(type, attr, channel); 161 : 0 : if (prop < 0) 162 : : return prop; 163 : : 164 : 0 : ret = power_supply_get_property(psy, prop, &pspval); 165 : 0 : if (ret) 166 : : return ret; 167 : : 168 : 0 : 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 : 0 : pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 1000); 177 : 0 : break; 178 : : /* 179 : : * Temp needs to be converted from 1/10 C to milli-C 180 : : */ 181 : : case hwmon_temp: 182 : 0 : if (check_mul_overflow(pspval.intval, 100, 183 : : &pspval.intval)) 184 : : return -EOVERFLOW; 185 : : break; 186 : : default: 187 : : return -EINVAL; 188 : : } 189 : : 190 : 0 : *val = pspval.intval; 191 : : 192 : 0 : return 0; 193 : : } 194 : : 195 : : static int 196 : 0 : 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 : 0 : struct power_supply *psy = psyhw->psy; 201 : : union power_supply_propval pspval; 202 : : int prop; 203 : : 204 : 0 : prop = power_supply_hwmon_to_property(type, attr, channel); 205 : 0 : if (prop < 0) 206 : : return prop; 207 : : 208 : 0 : pspval.intval = val; 209 : : 210 : 0 : 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 : 0 : 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 : 0 : pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 100); 227 : 0 : break; 228 : : default: 229 : : return -EINVAL; 230 : : } 231 : : 232 : 0 : 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 : 0 : static void power_supply_hwmon_bitmap_free(void *data) 276 : : { 277 : 0 : bitmap_free(data); 278 : 0 : } 279 : : 280 : 0 : int power_supply_add_hwmon_sysfs(struct power_supply *psy) 281 : : { 282 : 0 : const struct power_supply_desc *desc = psy->desc; 283 : : struct power_supply_hwmon *psyhw; 284 : 0 : struct device *dev = &psy->dev; 285 : : struct device *hwmon; 286 : : int ret, i; 287 : : const char *name; 288 : : 289 : 0 : 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 : 0 : if (!psyhw) { 295 : : ret = -ENOMEM; 296 : : goto error; 297 : : } 298 : : 299 : 0 : psyhw->psy = psy; 300 : 0 : psyhw->props = bitmap_zalloc(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1, 301 : : GFP_KERNEL); 302 : 0 : if (!psyhw->props) { 303 : : ret = -ENOMEM; 304 : : goto error; 305 : : } 306 : : 307 : 0 : ret = devm_add_action_or_reset(dev, power_supply_hwmon_bitmap_free, 308 : : psyhw->props); 309 : 0 : if (ret) 310 : : goto error; 311 : : 312 : 0 : for (i = 0; i < desc->num_properties; i++) { 313 : 0 : const enum power_supply_property prop = desc->properties[i]; 314 : : 315 : 0 : 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 : 0 : set_bit(prop, psyhw->props); 332 : 0 : break; 333 : : default: 334 : : break; 335 : : } 336 : : } 337 : : 338 : 0 : name = psy->desc->name; 339 : 0 : if (strchr(name, '-')) { 340 : : char *new_name; 341 : : 342 : 0 : new_name = devm_kstrdup(dev, name, GFP_KERNEL); 343 : 0 : if (!new_name) { 344 : : ret = -ENOMEM; 345 : : goto error; 346 : : } 347 : 0 : strreplace(new_name, '-', '_'); 348 : : name = new_name; 349 : : } 350 : 0 : 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 : 0 : if (ret) 356 : : goto error; 357 : : 358 : 0 : devres_close_group(dev, power_supply_add_hwmon_sysfs); 359 : 0 : return 0; 360 : : error: 361 : 0 : devres_release_group(dev, NULL); 362 : 0 : return ret; 363 : : } 364 : : 365 : 0 : void power_supply_remove_hwmon_sysfs(struct power_supply *psy) 366 : : { 367 : 0 : devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs); 368 : 0 : }