root/drivers/hwmon/lochnagar-hwmon.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. float_to_long
  2. do_measurement
  3. request_data
  4. read_sensor
  5. read_power
  6. lochnagar_is_visible
  7. lochnagar_read
  8. lochnagar_read_string
  9. lochnagar_write
  10. lochnagar_hwmon_probe

   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 
  32 static 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 
  43 struct 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 
  52 enum 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  */
  72 static 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 
  91 static 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 
 142 static 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 
 174 static 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 
 199 error:
 200         mutex_unlock(&priv->sensor_lock);
 201 
 202         return ret;
 203 }
 204 
 205 static 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 
 237 static 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 
 257 static 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 
 286 static 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 
 301 static 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 
 317 static 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 
 324 static 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 
 361 static const struct hwmon_chip_info lochnagar_chip_info = {
 362         .ops = &lochnagar_ops,
 363         .info = lochnagar_info,
 364 };
 365 
 366 static const struct of_device_id lochnagar_of_match[] = {
 367         { .compatible = "cirrus,lochnagar2-hwmon" },
 368         {}
 369 };
 370 MODULE_DEVICE_TABLE(of, lochnagar_of_match);
 371 
 372 static 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 
 401 static 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 };
 408 module_platform_driver(lochnagar_hwmon_driver);
 409 
 410 MODULE_AUTHOR("Lucas Tanure <tanureal@opensource.cirrus.com>");
 411 MODULE_DESCRIPTION("Lochnagar hardware monitoring features");
 412 MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */