linux/drivers/hwmon/scmi-hwmon.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * System Control and Management Interface(SCMI) based hwmon sensor driver
   4 *
   5 * Copyright (C) 2018 ARM Ltd.
   6 * Sudeep Holla <sudeep.holla@arm.com>
   7 */
   8
   9#include <linux/hwmon.h>
  10#include <linux/module.h>
  11#include <linux/scmi_protocol.h>
  12#include <linux/slab.h>
  13#include <linux/sysfs.h>
  14#include <linux/thermal.h>
  15
  16struct scmi_sensors {
  17        const struct scmi_handle *handle;
  18        const struct scmi_sensor_info **info[hwmon_max];
  19};
  20
  21static int scmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
  22                           u32 attr, int channel, long *val)
  23{
  24        int ret;
  25        u64 value;
  26        const struct scmi_sensor_info *sensor;
  27        struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev);
  28        const struct scmi_handle *h = scmi_sensors->handle;
  29
  30        sensor = *(scmi_sensors->info[type] + channel);
  31        ret = h->sensor_ops->reading_get(h, sensor->id, false, &value);
  32        if (!ret)
  33                *val = value;
  34
  35        return ret;
  36}
  37
  38static int
  39scmi_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
  40                       u32 attr, int channel, const char **str)
  41{
  42        const struct scmi_sensor_info *sensor;
  43        struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev);
  44
  45        sensor = *(scmi_sensors->info[type] + channel);
  46        *str = sensor->name;
  47
  48        return 0;
  49}
  50
  51static umode_t
  52scmi_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
  53                      u32 attr, int channel)
  54{
  55        const struct scmi_sensor_info *sensor;
  56        const struct scmi_sensors *scmi_sensors = drvdata;
  57
  58        sensor = *(scmi_sensors->info[type] + channel);
  59        if (sensor && sensor->name)
  60                return S_IRUGO;
  61
  62        return 0;
  63}
  64
  65static const struct hwmon_ops scmi_hwmon_ops = {
  66        .is_visible = scmi_hwmon_is_visible,
  67        .read = scmi_hwmon_read,
  68        .read_string = scmi_hwmon_read_string,
  69};
  70
  71static struct hwmon_chip_info scmi_chip_info = {
  72        .ops = &scmi_hwmon_ops,
  73        .info = NULL,
  74};
  75
  76static int scmi_hwmon_add_chan_info(struct hwmon_channel_info *scmi_hwmon_chan,
  77                                    struct device *dev, int num,
  78                                    enum hwmon_sensor_types type, u32 config)
  79{
  80        int i;
  81        u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL);
  82
  83        if (!cfg)
  84                return -ENOMEM;
  85
  86        scmi_hwmon_chan->type = type;
  87        scmi_hwmon_chan->config = cfg;
  88        for (i = 0; i < num; i++, cfg++)
  89                *cfg = config;
  90
  91        return 0;
  92}
  93
  94static enum hwmon_sensor_types scmi_types[] = {
  95        [TEMPERATURE_C] = hwmon_temp,
  96        [VOLTAGE] = hwmon_in,
  97        [CURRENT] = hwmon_curr,
  98        [POWER] = hwmon_power,
  99        [ENERGY] = hwmon_energy,
 100};
 101
 102static u32 hwmon_attributes[] = {
 103        [hwmon_chip] = HWMON_C_REGISTER_TZ,
 104        [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL,
 105        [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL,
 106        [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL,
 107        [hwmon_power] = HWMON_P_INPUT | HWMON_P_LABEL,
 108        [hwmon_energy] = HWMON_E_INPUT | HWMON_E_LABEL,
 109};
 110
 111static int scmi_hwmon_probe(struct scmi_device *sdev)
 112{
 113        int i, idx;
 114        u16 nr_sensors;
 115        enum hwmon_sensor_types type;
 116        struct scmi_sensors *scmi_sensors;
 117        const struct scmi_sensor_info *sensor;
 118        int nr_count[hwmon_max] = {0}, nr_types = 0;
 119        const struct hwmon_chip_info *chip_info;
 120        struct device *hwdev, *dev = &sdev->dev;
 121        struct hwmon_channel_info *scmi_hwmon_chan;
 122        const struct hwmon_channel_info **ptr_scmi_ci;
 123        const struct scmi_handle *handle = sdev->handle;
 124
 125        if (!handle || !handle->sensor_ops)
 126                return -ENODEV;
 127
 128        nr_sensors = handle->sensor_ops->count_get(handle);
 129        if (!nr_sensors)
 130                return -EIO;
 131
 132        scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL);
 133        if (!scmi_sensors)
 134                return -ENOMEM;
 135
 136        scmi_sensors->handle = handle;
 137
 138        for (i = 0; i < nr_sensors; i++) {
 139                sensor = handle->sensor_ops->info_get(handle, i);
 140                if (!sensor)
 141                        return -EINVAL;
 142
 143                switch (sensor->type) {
 144                case TEMPERATURE_C:
 145                case VOLTAGE:
 146                case CURRENT:
 147                case POWER:
 148                case ENERGY:
 149                        type = scmi_types[sensor->type];
 150                        if (!nr_count[type])
 151                                nr_types++;
 152                        nr_count[type]++;
 153                        break;
 154                }
 155        }
 156
 157        if (nr_count[hwmon_temp])
 158                nr_count[hwmon_chip]++, nr_types++;
 159
 160        scmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*scmi_hwmon_chan),
 161                                       GFP_KERNEL);
 162        if (!scmi_hwmon_chan)
 163                return -ENOMEM;
 164
 165        ptr_scmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*ptr_scmi_ci),
 166                                   GFP_KERNEL);
 167        if (!ptr_scmi_ci)
 168                return -ENOMEM;
 169
 170        scmi_chip_info.info = ptr_scmi_ci;
 171        chip_info = &scmi_chip_info;
 172
 173        for (type = 0; type < hwmon_max; type++) {
 174                if (!nr_count[type])
 175                        continue;
 176
 177                scmi_hwmon_add_chan_info(scmi_hwmon_chan, dev, nr_count[type],
 178                                         type, hwmon_attributes[type]);
 179                *ptr_scmi_ci++ = scmi_hwmon_chan++;
 180
 181                scmi_sensors->info[type] =
 182                        devm_kcalloc(dev, nr_count[type],
 183                                     sizeof(*scmi_sensors->info), GFP_KERNEL);
 184                if (!scmi_sensors->info[type])
 185                        return -ENOMEM;
 186        }
 187
 188        for (i = nr_sensors - 1; i >= 0 ; i--) {
 189                sensor = handle->sensor_ops->info_get(handle, i);
 190                if (!sensor)
 191                        continue;
 192
 193                switch (sensor->type) {
 194                case TEMPERATURE_C:
 195                case VOLTAGE:
 196                case CURRENT:
 197                case POWER:
 198                case ENERGY:
 199                        type = scmi_types[sensor->type];
 200                        idx = --nr_count[type];
 201                        *(scmi_sensors->info[type] + idx) = sensor;
 202                        break;
 203                }
 204        }
 205
 206        hwdev = devm_hwmon_device_register_with_info(dev, "scmi_sensors",
 207                                                     scmi_sensors, chip_info,
 208                                                     NULL);
 209
 210        return PTR_ERR_OR_ZERO(hwdev);
 211}
 212
 213static const struct scmi_device_id scmi_id_table[] = {
 214        { SCMI_PROTOCOL_SENSOR },
 215        { },
 216};
 217MODULE_DEVICE_TABLE(scmi, scmi_id_table);
 218
 219static struct scmi_driver scmi_hwmon_drv = {
 220        .name           = "scmi-hwmon",
 221        .probe          = scmi_hwmon_probe,
 222        .id_table       = scmi_id_table,
 223};
 224module_scmi_driver(scmi_hwmon_drv);
 225
 226MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
 227MODULE_DESCRIPTION("ARM SCMI HWMON interface driver");
 228MODULE_LICENSE("GPL v2");
 229