linux/drivers/hwmon/lochnagar-hwmon.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Lochnagar hardware monitoring features
   4 *
   5 * Copyright (c) 2016-2019 Cirrus Logic, Inc. and
   6 *                         Cirrus Logic International Semiconductor Ltd.
   7 *
   8 * Author: Lucas Tanure <tanureal@opensource.cirrus.com>
   9 */
  10
  11#include <linux/delay.h>
  12#include <linux/hwmon.h>
  13#include <linux/hwmon-sysfs.h>
  14#include <linux/i2c.h>
  15#include <linux/math64.h>
  16#include <linux/mfd/lochnagar.h>
  17#include <linux/mfd/lochnagar2_regs.h>
  18#include <linux/module.h>
  19#include <linux/of.h>
  20#include <linux/of_device.h>
  21#include <linux/platform_device.h>
  22#include <linux/regmap.h>
  23
  24#define LN2_MAX_NSAMPLE 1023
  25#define LN2_SAMPLE_US   1670
  26
  27#define LN2_CURR_UNITS  1000
  28#define LN2_VOLT_UNITS  1000
  29#define LN2_TEMP_UNITS  1000
  30#define LN2_PWR_UNITS   1000000
  31
  32static const char * const lochnagar_chan_names[] = {
  33        "DBVDD1",
  34        "1V8 DSP",
  35        "1V8 CDC",
  36        "VDDCORE DSP",
  37        "AVDD 1V8",
  38        "SYSVDD",
  39        "VDDCORE CDC",
  40        "MICVDD",
  41};
  42
  43struct lochnagar_hwmon {
  44        struct regmap *regmap;
  45
  46        long power_nsamples[ARRAY_SIZE(lochnagar_chan_names)];
  47
  48        /* Lock to ensure only a single sensor is read at a time */
  49        struct mutex sensor_lock;
  50};
  51
  52enum lochnagar_measure_mode {
  53        LN2_CURR = 0,
  54        LN2_VOLT,
  55        LN2_TEMP,
  56};
  57
  58/**
  59 * float_to_long - Convert ieee754 reading from hardware to an integer
  60 *
  61 * @data: Value read from the hardware
  62 * @precision: Units to multiply up to eg. 1000 = milli, 1000000 = micro
  63 *
  64 * Return: Converted integer reading
  65 *
  66 * Depending on the measurement type the hardware returns an ieee754
  67 * floating point value in either volts, amps or celsius. This function
  68 * will convert that into an integer in a smaller unit such as micro-amps
  69 * or milli-celsius. The hardware does not return NaN, so consideration of
  70 * that is not required.
  71 */
  72static long float_to_long(u32 data, u32 precision)
  73{
  74        u64 man = data & 0x007FFFFF;
  75        int exp = ((data & 0x7F800000) >> 23) - 127 - 23;
  76        bool negative = data & 0x80000000;
  77        long result;
  78
  79        man = (man + (1 << 23)) * precision;
  80
  81        if (fls64(man) + exp > (int)sizeof(long) * 8 - 1)
  82                result = LONG_MAX;
  83        else if (exp < 0)
  84                result = (man + (1ull << (-exp - 1))) >> -exp;
  85        else
  86                result = man << exp;
  87
  88        return negative ? -result : result;
  89}
  90
  91static int do_measurement(struct regmap *regmap, int chan,
  92                          enum lochnagar_measure_mode mode, int nsamples)
  93{
  94        unsigned int val;
  95        int ret;
  96
  97        chan = 1 << (chan + LOCHNAGAR2_IMON_MEASURED_CHANNELS_SHIFT);
  98
  99        ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL1,
 100                           LOCHNAGAR2_IMON_ENA_MASK | chan | mode);
 101        if (ret < 0)
 102                return ret;
 103
 104        ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL2, nsamples);
 105        if (ret < 0)
 106                return ret;
 107
 108        ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL3,
 109                           LOCHNAGAR2_IMON_CONFIGURE_MASK);
 110        if (ret < 0)
 111                return ret;
 112
 113        ret =  regmap_read_poll_timeout(regmap, LOCHNAGAR2_IMON_CTRL3, val,
 114                                        val & LOCHNAGAR2_IMON_DONE_MASK,
 115                                        1000, 10000);
 116        if (ret < 0)
 117                return ret;
 118
 119        ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL3,
 120                           LOCHNAGAR2_IMON_MEASURE_MASK);
 121        if (ret < 0)
 122                return ret;
 123
 124        /*
 125         * Actual measurement time is ~1.67mS per sample, approximate this
 126         * with a 1.5mS per sample msleep and then poll for success up to
 127         * ~0.17mS * 1023 (LN2_MAX_NSAMPLES). Normally for smaller values
 128         * of nsamples the poll will complete on the first loop due to
 129         * other latency in the system.
 130         */
 131        msleep((nsamples * 3) / 2);
 132
 133        ret =  regmap_read_poll_timeout(regmap, LOCHNAGAR2_IMON_CTRL3, val,
 134                                        val & LOCHNAGAR2_IMON_DONE_MASK,
 135                                        5000, 200000);
 136        if (ret < 0)
 137                return ret;
 138
 139        return regmap_write(regmap, LOCHNAGAR2_IMON_CTRL3, 0);
 140}
 141
 142static int request_data(struct regmap *regmap, int chan, u32 *data)
 143{
 144        unsigned int val;
 145        int ret;
 146
 147        ret = regmap_write(regmap, LOCHNAGAR2_IMON_CTRL4,
 148                           LOCHNAGAR2_IMON_DATA_REQ_MASK |
 149                           chan << LOCHNAGAR2_IMON_CH_SEL_SHIFT);
 150        if (ret < 0)
 151                return ret;
 152
 153        ret =  regmap_read_poll_timeout(regmap, LOCHNAGAR2_IMON_CTRL4, val,
 154                                        val & LOCHNAGAR2_IMON_DATA_RDY_MASK,
 155                                        1000, 10000);
 156        if (ret < 0)
 157                return ret;
 158
 159        ret = regmap_read(regmap, LOCHNAGAR2_IMON_DATA1, &val);
 160        if (ret < 0)
 161                return ret;
 162
 163        *data = val << 16;
 164
 165        ret = regmap_read(regmap, LOCHNAGAR2_IMON_DATA2, &val);
 166        if (ret < 0)
 167                return ret;
 168
 169        *data |= val;
 170
 171        return regmap_write(regmap, LOCHNAGAR2_IMON_CTRL4, 0);
 172}
 173
 174static int read_sensor(struct device *dev, int chan,
 175                       enum lochnagar_measure_mode mode, int nsamples,
 176                       unsigned int precision, long *val)
 177{
 178        struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
 179        struct regmap *regmap = priv->regmap;
 180        u32 data;
 181        int ret;
 182
 183        mutex_lock(&priv->sensor_lock);
 184
 185        ret = do_measurement(regmap, chan, mode, nsamples);
 186        if (ret < 0) {
 187                dev_err(dev, "Failed to perform measurement: %d\n", ret);
 188                goto error;
 189        }
 190
 191        ret = request_data(regmap, chan, &data);
 192        if (ret < 0) {
 193                dev_err(dev, "Failed to read measurement: %d\n", ret);
 194                goto error;
 195        }
 196
 197        *val = float_to_long(data, precision);
 198
 199error:
 200        mutex_unlock(&priv->sensor_lock);
 201
 202        return ret;
 203}
 204
 205static int read_power(struct device *dev, int chan, long *val)
 206{
 207        struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
 208        int nsamples = priv->power_nsamples[chan];
 209        u64 power;
 210        int ret;
 211
 212        if (!strcmp("SYSVDD", lochnagar_chan_names[chan])) {
 213                power = 5 * LN2_PWR_UNITS;
 214        } else {
 215                ret = read_sensor(dev, chan, LN2_VOLT, 1, LN2_PWR_UNITS, val);
 216                if (ret < 0)
 217                        return ret;
 218
 219                power = abs(*val);
 220        }
 221
 222        ret = read_sensor(dev, chan, LN2_CURR, nsamples, LN2_PWR_UNITS, val);
 223        if (ret < 0)
 224                return ret;
 225
 226        power *= abs(*val);
 227        power = DIV_ROUND_CLOSEST_ULL(power, LN2_PWR_UNITS);
 228
 229        if (power > LONG_MAX)
 230                *val = LONG_MAX;
 231        else
 232                *val = power;
 233
 234        return 0;
 235}
 236
 237static umode_t lochnagar_is_visible(const void *drvdata,
 238                                    enum hwmon_sensor_types type,
 239                                    u32 attr, int chan)
 240{
 241        switch (type) {
 242        case hwmon_in:
 243                if (!strcmp("SYSVDD", lochnagar_chan_names[chan]))
 244                        return 0;
 245                break;
 246        case hwmon_power:
 247                if (attr == hwmon_power_average_interval)
 248                        return 0644;
 249                break;
 250        default:
 251                break;
 252        }
 253
 254        return 0444;
 255}
 256
 257static int lochnagar_read(struct device *dev, enum hwmon_sensor_types type,
 258                          u32 attr, int chan, long *val)
 259{
 260        struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
 261        int interval;
 262
 263        switch (type) {
 264        case hwmon_in:
 265                return read_sensor(dev, chan, LN2_VOLT, 1, LN2_VOLT_UNITS, val);
 266        case hwmon_curr:
 267                return read_sensor(dev, chan, LN2_CURR, 1, LN2_CURR_UNITS, val);
 268        case hwmon_temp:
 269                return read_sensor(dev, chan, LN2_TEMP, 1, LN2_TEMP_UNITS, val);
 270        case hwmon_power:
 271                switch (attr) {
 272                case hwmon_power_average:
 273                        return read_power(dev, chan, val);
 274                case hwmon_power_average_interval:
 275                        interval = priv->power_nsamples[chan] * LN2_SAMPLE_US;
 276                        *val = DIV_ROUND_CLOSEST(interval, 1000);
 277                        return 0;
 278                default:
 279                        return -EOPNOTSUPP;
 280                }
 281        default:
 282                return -EOPNOTSUPP;
 283        }
 284}
 285
 286static int lochnagar_read_string(struct device *dev,
 287                                 enum hwmon_sensor_types type, u32 attr,
 288                                 int chan, const char **str)
 289{
 290        switch (type) {
 291        case hwmon_in:
 292        case hwmon_curr:
 293        case hwmon_power:
 294                *str = lochnagar_chan_names[chan];
 295                return 0;
 296        default:
 297                return -EOPNOTSUPP;
 298        }
 299}
 300
 301static int lochnagar_write(struct device *dev, enum hwmon_sensor_types type,
 302                           u32 attr, int chan, long val)
 303{
 304        struct lochnagar_hwmon *priv = dev_get_drvdata(dev);
 305
 306        if (type != hwmon_power || attr != hwmon_power_average_interval)
 307                return -EOPNOTSUPP;
 308
 309        val = clamp_t(long, val, 1, (LN2_MAX_NSAMPLE * LN2_SAMPLE_US) / 1000);
 310        val = DIV_ROUND_CLOSEST(val * 1000, LN2_SAMPLE_US);
 311
 312        priv->power_nsamples[chan] = val;
 313
 314        return 0;
 315}
 316
 317static const struct hwmon_ops lochnagar_ops = {
 318        .is_visible = lochnagar_is_visible,
 319        .read = lochnagar_read,
 320        .read_string = lochnagar_read_string,
 321        .write = lochnagar_write,
 322};
 323
 324static const struct hwmon_channel_info *lochnagar_info[] = {
 325        HWMON_CHANNEL_INFO(temp,  HWMON_T_INPUT),
 326        HWMON_CHANNEL_INFO(in,    HWMON_I_INPUT | HWMON_I_LABEL,
 327                                  HWMON_I_INPUT | HWMON_I_LABEL,
 328                                  HWMON_I_INPUT | HWMON_I_LABEL,
 329                                  HWMON_I_INPUT | HWMON_I_LABEL,
 330                                  HWMON_I_INPUT | HWMON_I_LABEL,
 331                                  HWMON_I_INPUT | HWMON_I_LABEL,
 332                                  HWMON_I_INPUT | HWMON_I_LABEL,
 333                                  HWMON_I_INPUT | HWMON_I_LABEL),
 334        HWMON_CHANNEL_INFO(curr,  HWMON_C_INPUT | HWMON_C_LABEL,
 335                                  HWMON_C_INPUT | HWMON_C_LABEL,
 336                                  HWMON_C_INPUT | HWMON_C_LABEL,
 337                                  HWMON_C_INPUT | HWMON_C_LABEL,
 338                                  HWMON_C_INPUT | HWMON_C_LABEL,
 339                                  HWMON_C_INPUT | HWMON_C_LABEL,
 340                                  HWMON_C_INPUT | HWMON_C_LABEL,
 341                                  HWMON_C_INPUT | HWMON_C_LABEL),
 342        HWMON_CHANNEL_INFO(power, HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
 343                                  HWMON_P_LABEL,
 344                                  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
 345                                  HWMON_P_LABEL,
 346                                  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
 347                                  HWMON_P_LABEL,
 348                                  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
 349                                  HWMON_P_LABEL,
 350                                  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
 351                                  HWMON_P_LABEL,
 352                                  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
 353                                  HWMON_P_LABEL,
 354                                  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
 355                                  HWMON_P_LABEL,
 356                                  HWMON_P_AVERAGE | HWMON_P_AVERAGE_INTERVAL |
 357                                  HWMON_P_LABEL),
 358        NULL
 359};
 360
 361static const struct hwmon_chip_info lochnagar_chip_info = {
 362        .ops = &lochnagar_ops,
 363        .info = lochnagar_info,
 364};
 365
 366static const struct of_device_id lochnagar_of_match[] = {
 367        { .compatible = "cirrus,lochnagar2-hwmon" },
 368        {}
 369};
 370MODULE_DEVICE_TABLE(of, lochnagar_of_match);
 371
 372static int lochnagar_hwmon_probe(struct platform_device *pdev)
 373{
 374        struct device *dev = &pdev->dev;
 375        struct device *hwmon_dev;
 376        struct lochnagar_hwmon *priv;
 377        int i;
 378
 379        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 380        if (!priv)
 381                return -ENOMEM;
 382
 383        mutex_init(&priv->sensor_lock);
 384
 385        priv->regmap = dev_get_regmap(dev->parent, NULL);
 386        if (!priv->regmap) {
 387                dev_err(dev, "No register map found\n");
 388                return -EINVAL;
 389        }
 390
 391        for (i = 0; i < ARRAY_SIZE(priv->power_nsamples); i++)
 392                priv->power_nsamples[i] = 96;
 393
 394        hwmon_dev = devm_hwmon_device_register_with_info(dev, "Lochnagar", priv,
 395                                                         &lochnagar_chip_info,
 396                                                         NULL);
 397
 398        return PTR_ERR_OR_ZERO(hwmon_dev);
 399}
 400
 401static struct platform_driver lochnagar_hwmon_driver = {
 402        .driver = {
 403                .name = "lochnagar-hwmon",
 404                .of_match_table = lochnagar_of_match,
 405        },
 406        .probe = lochnagar_hwmon_probe,
 407};
 408module_platform_driver(lochnagar_hwmon_driver);
 409
 410MODULE_AUTHOR("Lucas Tanure <tanureal@opensource.cirrus.com>");
 411MODULE_DESCRIPTION("Lochnagar hardware monitoring features");
 412MODULE_LICENSE("GPL");
 413