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