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 const char *const ps_temp_label[] = {
  17        "temp",
  18        "ambient temp",
  19};
  20
  21static 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
  37static 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
  51static 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
  84static int
  85power_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
 100static 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
 106struct hwmon_type_attr_list {
 107        const u32 *attrs;
 108        size_t n_attrs;
 109};
 110
 111static 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
 117static const struct hwmon_type_attr_list ps_type_attrs[hwmon_max] = {
 118        [hwmon_temp] = { ps_temp_attrs, ARRAY_SIZE(ps_temp_attrs) },
 119};
 120
 121static 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
 139static 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
 158static 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
 183static 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
 205static int
 206power_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
 249static int
 250power_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
 289static 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
 296static const struct hwmon_channel_info *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_MIN_ALARM,
 304
 305                           HWMON_T_LABEL     |
 306                           HWMON_T_INPUT     |
 307                           HWMON_T_MIN_ALARM |
 308                           HWMON_T_LABEL     |
 309                           HWMON_T_MAX_ALARM),
 310
 311        HWMON_CHANNEL_INFO(curr,
 312                           HWMON_C_AVERAGE |
 313                           HWMON_C_MAX     |
 314                           HWMON_C_INPUT),
 315
 316        HWMON_CHANNEL_INFO(in,
 317                           HWMON_I_AVERAGE |
 318                           HWMON_I_MIN     |
 319                           HWMON_I_MAX     |
 320                           HWMON_I_INPUT),
 321        NULL
 322};
 323
 324static const struct hwmon_chip_info power_supply_hwmon_chip_info = {
 325        .ops = &power_supply_hwmon_ops,
 326        .info = power_supply_hwmon_info,
 327};
 328
 329static void power_supply_hwmon_bitmap_free(void *data)
 330{
 331        bitmap_free(data);
 332}
 333
 334int power_supply_add_hwmon_sysfs(struct power_supply *psy)
 335{
 336        const struct power_supply_desc *desc = psy->desc;
 337        struct power_supply_hwmon *psyhw;
 338        struct device *dev = &psy->dev;
 339        struct device *hwmon;
 340        int ret, i;
 341        const char *name;
 342
 343        if (!devres_open_group(dev, power_supply_add_hwmon_sysfs,
 344                               GFP_KERNEL))
 345                return -ENOMEM;
 346
 347        psyhw = devm_kzalloc(dev, sizeof(*psyhw), GFP_KERNEL);
 348        if (!psyhw) {
 349                ret = -ENOMEM;
 350                goto error;
 351        }
 352
 353        psyhw->psy = psy;
 354        psyhw->props = bitmap_zalloc(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1,
 355                                     GFP_KERNEL);
 356        if (!psyhw->props) {
 357                ret = -ENOMEM;
 358                goto error;
 359        }
 360
 361        ret = devm_add_action_or_reset(dev, power_supply_hwmon_bitmap_free,
 362                              psyhw->props);
 363        if (ret)
 364                goto error;
 365
 366        for (i = 0; i < desc->num_properties; i++) {
 367                const enum power_supply_property prop = desc->properties[i];
 368
 369                switch (prop) {
 370                case POWER_SUPPLY_PROP_CURRENT_AVG:
 371                case POWER_SUPPLY_PROP_CURRENT_MAX:
 372                case POWER_SUPPLY_PROP_CURRENT_NOW:
 373                case POWER_SUPPLY_PROP_TEMP:
 374                case POWER_SUPPLY_PROP_TEMP_MAX:
 375                case POWER_SUPPLY_PROP_TEMP_MIN:
 376                case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
 377                case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
 378                case POWER_SUPPLY_PROP_TEMP_AMBIENT:
 379                case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN:
 380                case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX:
 381                case POWER_SUPPLY_PROP_VOLTAGE_AVG:
 382                case POWER_SUPPLY_PROP_VOLTAGE_MIN:
 383                case POWER_SUPPLY_PROP_VOLTAGE_MAX:
 384                case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 385                        set_bit(prop, psyhw->props);
 386                        break;
 387                default:
 388                        break;
 389                }
 390        }
 391
 392        name = psy->desc->name;
 393        if (strchr(name, '-')) {
 394                char *new_name;
 395
 396                new_name = devm_kstrdup(dev, name, GFP_KERNEL);
 397                if (!new_name) {
 398                        ret = -ENOMEM;
 399                        goto error;
 400                }
 401                strreplace(new_name, '-', '_');
 402                name = new_name;
 403        }
 404        hwmon = devm_hwmon_device_register_with_info(dev, name,
 405                                                psyhw,
 406                                                &power_supply_hwmon_chip_info,
 407                                                NULL);
 408        ret = PTR_ERR_OR_ZERO(hwmon);
 409        if (ret)
 410                goto error;
 411
 412        devres_close_group(dev, power_supply_add_hwmon_sysfs);
 413        return 0;
 414error:
 415        devres_release_group(dev, NULL);
 416        return ret;
 417}
 418
 419void power_supply_remove_hwmon_sysfs(struct power_supply *psy)
 420{
 421        devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs);
 422}
 423