Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hwmon: lochnagar: Add Lochnagar 2 hardware monitoring driver

Lochnagar is an evaluation and development board for Cirrus
Logic Smart CODEC and Amp devices. It allows the connection of
most Cirrus Logic devices on mini-cards, as well as allowing
connection of various application processor systems to provide a
full evaluation platform.

This driver adds support for the hardware monitoring features of
the Lochnagar 2 to the hwmon API. Monitoring is provided for
the board voltages, currents and temperature supported by the
board controller chip.

Signed-off-by: Lucas Tanure <tanureal@opensource.cirrus.com>
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>

authored by

Lucas Tanure and committed by
Guenter Roeck
4cdb5621 5ad4d7ca

+506
+80
Documentation/hwmon/lochnagar
··· 1 + Kernel Driver Lochnagar 2 + ======================== 3 + 4 + Supported systems: 5 + * Cirrus Logic : Lochnagar 2 6 + 7 + Author: Lucas A. Tanure Alves 8 + 9 + Description 10 + ----------- 11 + 12 + Lochnagar 2 features built-in Current Monitor circuitry that allows for the 13 + measurement of both voltage and current on up to eight of the supply voltage 14 + rails provided to the minicards. The Current Monitor does not require any 15 + hardware modifications or external circuitry to operate. 16 + 17 + The current and voltage measurements are obtained through the standard register 18 + map interface to the Lochnagar board controller, and can therefore be monitored 19 + by software. 20 + 21 + Sysfs attributes 22 + ---------------- 23 + 24 + temp1_input The Lochnagar board temperature (milliCelsius) 25 + in0_input Measured voltage for DBVDD1 (milliVolts) 26 + in0_label "DBVDD1" 27 + curr1_input Measured current for DBVDD1 (milliAmps) 28 + curr1_label "DBVDD1" 29 + power1_average Measured average power for DBVDD1 (microWatts) 30 + power1_average_interval Power averaging time input valid from 1 to 1708mS 31 + power1_label "DBVDD1" 32 + in1_input Measured voltage for 1V8 DSP (milliVolts) 33 + in1_label "1V8 DSP" 34 + curr2_input Measured current for 1V8 DSP (milliAmps) 35 + curr2_label "1V8 DSP" 36 + power2_average Measured average power for 1V8 DSP (microWatts) 37 + power2_average_interval Power averaging time input valid from 1 to 1708mS 38 + power2_label "1V8 DSP" 39 + in2_input Measured voltage for 1V8 CDC (milliVolts) 40 + in2_label "1V8 CDC" 41 + curr3_input Measured current for 1V8 CDC (milliAmps) 42 + curr3_label "1V8 CDC" 43 + power3_average Measured average power for 1V8 CDC (microWatts) 44 + power3_average_interval Power averaging time input valid from 1 to 1708mS 45 + power3_label "1V8 CDC" 46 + in3_input Measured voltage for VDDCORE DSP (milliVolts) 47 + in3_label "VDDCORE DSP" 48 + curr4_input Measured current for VDDCORE DSP (milliAmps) 49 + curr4_label "VDDCORE DSP" 50 + power4_average Measured average power for VDDCORE DSP (microWatts) 51 + power4_average_interval Power averaging time input valid from 1 to 1708mS 52 + power4_label "VDDCORE DSP" 53 + in4_input Measured voltage for AVDD 1V8 (milliVolts) 54 + in4_label "AVDD 1V8" 55 + curr5_input Measured current for AVDD 1V8 (milliAmps) 56 + curr5_label "AVDD 1V8" 57 + power5_average Measured average power for AVDD 1V8 (microWatts) 58 + power5_average_interval Power averaging time input valid from 1 to 1708mS 59 + power5_label "AVDD 1V8" 60 + curr6_input Measured current for SYSVDD (milliAmps) 61 + curr6_label "SYSVDD" 62 + power6_average Measured average power for SYSVDD (microWatts) 63 + power6_average_interval Power averaging time input valid from 1 to 1708mS 64 + power6_label "SYSVDD" 65 + in6_input Measured voltage for VDDCORE CDC (milliVolts) 66 + in6_label "VDDCORE CDC" 67 + curr7_input Measured current for VDDCORE CDC (milliAmps) 68 + curr7_label "VDDCORE CDC" 69 + power7_average Measured average power for VDDCORE CDC (microWatts) 70 + power7_average_interval Power averaging time input valid from 1 to 1708mS 71 + power7_label "VDDCORE CDC" 72 + in7_input Measured voltage for MICVDD (milliVolts) 73 + in7_label "MICVDD" 74 + curr8_input Measured current for MICVDD (milliAmps) 75 + curr8_label "MICVDD" 76 + power8_average Measured average power for MICVDD (microWatts) 77 + power8_average_interval Power averaging time input valid from 1 to 1708mS 78 + power8_label "MICVDD" 79 + 80 + Note: It is not possible to measure voltage on the SYSVDD rail.
+3
MAINTAINERS
··· 3797 3797 L: patches@opensource.cirrus.com 3798 3798 S: Supported 3799 3799 F: drivers/clk/clk-lochnagar.c 3800 + F: drivers/hwmon/lochnagar-hwmon.c 3800 3801 F: drivers/mfd/lochnagar-i2c.c 3801 3802 F: drivers/pinctrl/cirrus/pinctrl-lochnagar.c 3802 3803 F: drivers/regulator/lochnagar-regulator.c ··· 3806 3805 F: include/linux/mfd/lochnagar* 3807 3806 F: Documentation/devicetree/bindings/mfd/cirrus,lochnagar.txt 3808 3807 F: Documentation/devicetree/bindings/clock/cirrus,lochnagar.txt 3808 + F: Documentation/devicetree/bindings/hwmon/cirrus,lochnagar.txt 3809 3809 F: Documentation/devicetree/bindings/pinctrl/cirrus,lochnagar.txt 3810 3810 F: Documentation/devicetree/bindings/regulator/cirrus,lochnagar.txt 3811 + F: Documentation/hwmon/lochnagar 3811 3812 3812 3813 CISCO FCOE HBA DRIVER 3813 3814 M: Satish Kharat <satishkh@cisco.com>
+10
drivers/hwmon/Kconfig
··· 705 705 This driver can also be built as a module. If so, the module 706 706 will be called lineage-pem. 707 707 708 + config SENSORS_LOCHNAGAR 709 + tristate "Lochnagar Hardware Monitor" 710 + depends on MFD_LOCHNAGAR 711 + help 712 + If you say yes here you get support for Lochnagar 2 temperature, 713 + voltage and current sensors abilities. 714 + 715 + This driver can also be built as a module. If so, the module 716 + will be called lochnagar-hwmon. 717 + 708 718 config SENSORS_LTC2945 709 719 tristate "Linear Technology LTC2945" 710 720 depends on I2C
+1
drivers/hwmon/Makefile
··· 89 89 obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o 90 90 obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o 91 91 obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o 92 + obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o 92 93 obj-$(CONFIG_SENSORS_LM63) += lm63.o 93 94 obj-$(CONFIG_SENSORS_LM70) += lm70.o 94 95 obj-$(CONFIG_SENSORS_LM73) += lm73.o
+412
drivers/hwmon/lochnagar-hwmon.c
··· 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");