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