lochnagar-hwmon.c (10325B)
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");