linux/drivers/power/supply/power_supply_hwmon.c
<<
>>
Prefs
   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
  11struct power_supply_hwmon {
  12        struct power_supply *psy;
  13        unsigned long *props;
  14};
  15
  16static 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
  32static 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
  46static 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
  79static int
  80power_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
  95static 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
 101static 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
 120static 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
 142static 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
 151static int
 152power_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
 195static int
 196power_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
 235static 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
 242static 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
 270static const struct hwmon_chip_info power_supply_hwmon_chip_info = {
 271        .ops = &power_supply_hwmon_ops,
 272        .info = power_supply_hwmon_info,
 273};
 274
 275static void power_supply_hwmon_bitmap_free(void *data)
 276{
 277        bitmap_free(data);
 278}
 279
 280int 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
 288        if (!devres_open_group(dev, power_supply_add_hwmon_sysfs,
 289                               GFP_KERNEL))
 290                return -ENOMEM;
 291
 292        psyhw = devm_kzalloc(dev, sizeof(*psyhw), GFP_KERNEL);
 293        if (!psyhw) {
 294                ret = -ENOMEM;
 295                goto error;
 296        }
 297
 298        psyhw->psy = psy;
 299        psyhw->props = bitmap_zalloc(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1,
 300                                     GFP_KERNEL);
 301        if (!psyhw->props) {
 302                ret = -ENOMEM;
 303                goto error;
 304        }
 305
 306        ret = devm_add_action(dev, power_supply_hwmon_bitmap_free,
 307                              psyhw->props);
 308        if (ret)
 309                goto error;
 310
 311        for (i = 0; i < desc->num_properties; i++) {
 312                const enum power_supply_property prop = desc->properties[i];
 313
 314                switch (prop) {
 315                case POWER_SUPPLY_PROP_CURRENT_AVG:
 316                case POWER_SUPPLY_PROP_CURRENT_MAX:
 317                case POWER_SUPPLY_PROP_CURRENT_NOW:
 318                case POWER_SUPPLY_PROP_TEMP:
 319                case POWER_SUPPLY_PROP_TEMP_MAX:
 320                case POWER_SUPPLY_PROP_TEMP_MIN:
 321                case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
 322                case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
 323                case POWER_SUPPLY_PROP_TEMP_AMBIENT:
 324                case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN:
 325                case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX:
 326                case POWER_SUPPLY_PROP_VOLTAGE_AVG:
 327                case POWER_SUPPLY_PROP_VOLTAGE_MIN:
 328                case POWER_SUPPLY_PROP_VOLTAGE_MAX:
 329                case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 330                        set_bit(prop, psyhw->props);
 331                        break;
 332                default:
 333                        break;
 334                }
 335        }
 336
 337        hwmon = devm_hwmon_device_register_with_info(dev, psy->desc->name,
 338                                                psyhw,
 339                                                &power_supply_hwmon_chip_info,
 340                                                NULL);
 341        ret = PTR_ERR_OR_ZERO(hwmon);
 342        if (ret)
 343                goto error;
 344
 345        devres_close_group(dev, power_supply_add_hwmon_sysfs);
 346        return 0;
 347error:
 348        devres_release_group(dev, NULL);
 349        return ret;
 350}
 351
 352void power_supply_remove_hwmon_sysfs(struct power_supply *psy)
 353{
 354        devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs);
 355}
 356