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