ef7e2f4152a195003ba007f056960b99b2dc944a
[openwrt/staging/dedeckeh.git] / target / linux / ipq806x / patches-4.4 / 015-1-thermal-qcom-tsens-Add-a-skeletal-TSENS-drivers.patch
1 From 9066073c6c27994a30187abf3b674770b4088348 Mon Sep 17 00:00:00 2001
2 From: Rajendra Nayak <rnayak@codeaurora.org>
3 Date: Thu, 5 May 2016 14:21:39 +0530
4 Subject: thermal: qcom: tsens: Add a skeletal TSENS drivers
5
6 TSENS is Qualcomms' thermal temperature sensor device. It
7 supports reading temperatures from multiple thermal sensors
8 present on various QCOM SoCs.
9 Calibration data is generally read from a non-volatile memory
10 (eeprom) device.
11
12 Add a skeleton driver with all the necessary abstractions so
13 a variety of qcom device families which support TSENS can
14 add driver extensions.
15
16 Also add the required device tree bindings which can be used
17 to describe the TSENS device in DT.
18
19 Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
20 Reviewed-by: Lina Iyer <lina.iyer@linaro.org>
21 Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
22 Signed-off-by: Zhang Rui <rui.zhang@intel.com>
23 ---
24 .../devicetree/bindings/thermal/qcom-tsens.txt | 21 +++
25 drivers/thermal/Kconfig | 5 +
26 drivers/thermal/Makefile | 1 +
27 drivers/thermal/qcom/Kconfig | 11 ++
28 drivers/thermal/qcom/Makefile | 2 +
29 drivers/thermal/qcom/tsens-common.c | 141 +++++++++++++++
30 drivers/thermal/qcom/tsens.c | 195 +++++++++++++++++++++
31 drivers/thermal/qcom/tsens.h | 90 ++++++++++
32 8 files changed, 466 insertions(+)
33 create mode 100644 Documentation/devicetree/bindings/thermal/qcom-tsens.txt
34 create mode 100644 drivers/thermal/qcom/Kconfig
35 create mode 100644 drivers/thermal/qcom/Makefile
36 create mode 100644 drivers/thermal/qcom/tsens-common.c
37 create mode 100644 drivers/thermal/qcom/tsens.c
38 create mode 100644 drivers/thermal/qcom/tsens.h
39
40 --- /dev/null
41 +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.txt
42 @@ -0,0 +1,21 @@
43 +* QCOM SoC Temperature Sensor (TSENS)
44 +
45 +Required properties:
46 +- compatible :
47 + - "qcom,msm8916-tsens" : For 8916 Family of SoCs
48 + - "qcom,msm8974-tsens" : For 8974 Family of SoCs
49 + - "qcom,msm8996-tsens" : For 8996 Family of SoCs
50 +
51 +- reg: Address range of the thermal registers
52 +- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description.
53 +- Refer to Documentation/devicetree/bindings/nvmem/nvmem.txt to know how to specify
54 +nvmem cells
55 +
56 +Example:
57 +tsens: thermal-sensor@900000 {
58 + compatible = "qcom,msm8916-tsens";
59 + reg = <0x4a8000 0x2000>;
60 + nvmem-cells = <&tsens_caldata>, <&tsens_calsel>;
61 + nvmem-cell-names = "caldata", "calsel";
62 + #thermal-sensor-cells = <1>;
63 + };
64 --- a/drivers/thermal/Kconfig
65 +++ b/drivers/thermal/Kconfig
66 @@ -391,4 +391,9 @@ config QCOM_SPMI_TEMP_ALARM
67 real time die temperature if an ADC is present or an estimate of the
68 temperature based upon the over temperature stage value.
69
70 +menu "Qualcomm thermal drivers"
71 +depends on (ARCH_QCOM && OF) || COMPILE_TEST
72 +source "drivers/thermal/qcom/Kconfig"
73 +endmenu
74 +
75 endif
76 --- a/drivers/thermal/Makefile
77 +++ b/drivers/thermal/Makefile
78 @@ -48,3 +48,4 @@ obj-$(CONFIG_INTEL_PCH_THERMAL) += intel
79 obj-$(CONFIG_ST_THERMAL) += st/
80 obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o
81 obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
82 +obj-$(CONFIG_QCOM_TSENS) += qcom/
83 --- /dev/null
84 +++ b/drivers/thermal/qcom/Kconfig
85 @@ -0,0 +1,11 @@
86 +config QCOM_TSENS
87 + tristate "Qualcomm TSENS Temperature Alarm"
88 + depends on THERMAL
89 + depends on QCOM_QFPROM
90 + depends on ARCH_QCOM || COMPILE_TEST
91 + help
92 + This enables the thermal sysfs driver for the TSENS device. It shows
93 + up in Sysfs as a thermal zone with multiple trip points. Disabling the
94 + thermal zone device via the mode file results in disabling the sensor.
95 + Also able to set threshold temperature for both hot and cold and update
96 + when a threshold is reached.
97 --- /dev/null
98 +++ b/drivers/thermal/qcom/Makefile
99 @@ -0,0 +1,2 @@
100 +obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
101 +qcom_tsens-y += tsens.o tsens-common.o
102 --- /dev/null
103 +++ b/drivers/thermal/qcom/tsens-common.c
104 @@ -0,0 +1,141 @@
105 +/*
106 + * Copyright (c) 2015, The Linux Foundation. All rights reserved.
107 + *
108 + * This program is free software; you can redistribute it and/or modify
109 + * it under the terms of the GNU General Public License version 2 and
110 + * only version 2 as published by the Free Software Foundation.
111 + *
112 + * This program is distributed in the hope that it will be useful,
113 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
114 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
115 + * GNU General Public License for more details.
116 + *
117 + */
118 +
119 +#include <linux/err.h>
120 +#include <linux/io.h>
121 +#include <linux/nvmem-consumer.h>
122 +#include <linux/of_address.h>
123 +#include <linux/platform_device.h>
124 +#include <linux/regmap.h>
125 +#include "tsens.h"
126 +
127 +#define S0_ST_ADDR 0x1030
128 +#define SN_ADDR_OFFSET 0x4
129 +#define SN_ST_TEMP_MASK 0x3ff
130 +#define CAL_DEGC_PT1 30
131 +#define CAL_DEGC_PT2 120
132 +#define SLOPE_FACTOR 1000
133 +#define SLOPE_DEFAULT 3200
134 +
135 +char *qfprom_read(struct device *dev, const char *cname)
136 +{
137 + struct nvmem_cell *cell;
138 + ssize_t data;
139 + char *ret;
140 +
141 + cell = nvmem_cell_get(dev, cname);
142 + if (IS_ERR(cell))
143 + return ERR_CAST(cell);
144 +
145 + ret = nvmem_cell_read(cell, &data);
146 + nvmem_cell_put(cell);
147 +
148 + return ret;
149 +}
150 +
151 +/*
152 + * Use this function on devices where slope and offset calculations
153 + * depend on calibration data read from qfprom. On others the slope
154 + * and offset values are derived from tz->tzp->slope and tz->tzp->offset
155 + * resp.
156 + */
157 +void compute_intercept_slope(struct tsens_device *tmdev, u32 *p1,
158 + u32 *p2, u32 mode)
159 +{
160 + int i;
161 + int num, den;
162 +
163 + for (i = 0; i < tmdev->num_sensors; i++) {
164 + dev_dbg(tmdev->dev,
165 + "sensor%d - data_point1:%#x data_point2:%#x\n",
166 + i, p1[i], p2[i]);
167 +
168 + tmdev->sensor[i].slope = SLOPE_DEFAULT;
169 + if (mode == TWO_PT_CALIB) {
170 + /*
171 + * slope (m) = adc_code2 - adc_code1 (y2 - y1)/
172 + * temp_120_degc - temp_30_degc (x2 - x1)
173 + */
174 + num = p2[i] - p1[i];
175 + num *= SLOPE_FACTOR;
176 + den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
177 + tmdev->sensor[i].slope = num / den;
178 + }
179 +
180 + tmdev->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
181 + (CAL_DEGC_PT1 *
182 + tmdev->sensor[i].slope);
183 + dev_dbg(tmdev->dev, "offset:%d\n", tmdev->sensor[i].offset);
184 + }
185 +}
186 +
187 +static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
188 +{
189 + int degc, num, den;
190 +
191 + num = (adc_code * SLOPE_FACTOR) - s->offset;
192 + den = s->slope;
193 +
194 + if (num > 0)
195 + degc = num + (den / 2);
196 + else if (num < 0)
197 + degc = num - (den / 2);
198 + else
199 + degc = num;
200 +
201 + degc /= den;
202 +
203 + return degc;
204 +}
205 +
206 +int get_temp_common(struct tsens_device *tmdev, int id, int *temp)
207 +{
208 + struct tsens_sensor *s = &tmdev->sensor[id];
209 + u32 code;
210 + unsigned int sensor_addr;
211 + int last_temp = 0, ret;
212 +
213 + sensor_addr = S0_ST_ADDR + s->hw_id * SN_ADDR_OFFSET;
214 + ret = regmap_read(tmdev->map, sensor_addr, &code);
215 + if (ret)
216 + return ret;
217 + last_temp = code & SN_ST_TEMP_MASK;
218 +
219 + *temp = code_to_degc(last_temp, s) * 1000;
220 +
221 + return 0;
222 +}
223 +
224 +static const struct regmap_config tsens_config = {
225 + .reg_bits = 32,
226 + .val_bits = 32,
227 + .reg_stride = 4,
228 +};
229 +
230 +int __init init_common(struct tsens_device *tmdev)
231 +{
232 + void __iomem *base;
233 +
234 + base = of_iomap(tmdev->dev->of_node, 0);
235 + if (IS_ERR(base))
236 + return -EINVAL;
237 +
238 + tmdev->map = devm_regmap_init_mmio(tmdev->dev, base, &tsens_config);
239 + if (!tmdev->map) {
240 + iounmap(base);
241 + return -ENODEV;
242 + }
243 +
244 + return 0;
245 +}
246 --- /dev/null
247 +++ b/drivers/thermal/qcom/tsens.c
248 @@ -0,0 +1,195 @@
249 +/*
250 + * Copyright (c) 2015, The Linux Foundation. All rights reserved.
251 + *
252 + * This program is free software; you can redistribute it and/or modify
253 + * it under the terms of the GNU General Public License version 2 and
254 + * only version 2 as published by the Free Software Foundation.
255 + *
256 + * This program is distributed in the hope that it will be useful,
257 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
258 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
259 + * GNU General Public License for more details.
260 + *
261 + */
262 +
263 +#include <linux/err.h>
264 +#include <linux/module.h>
265 +#include <linux/of.h>
266 +#include <linux/platform_device.h>
267 +#include <linux/pm.h>
268 +#include <linux/slab.h>
269 +#include <linux/thermal.h>
270 +#include "tsens.h"
271 +
272 +static int tsens_get_temp(void *data, int *temp)
273 +{
274 + const struct tsens_sensor *s = data;
275 + struct tsens_device *tmdev = s->tmdev;
276 +
277 + return tmdev->ops->get_temp(tmdev, s->id, temp);
278 +}
279 +
280 +static int tsens_get_trend(void *data, long *temp)
281 +{
282 + const struct tsens_sensor *s = data;
283 + struct tsens_device *tmdev = s->tmdev;
284 +
285 + if (tmdev->ops->get_trend)
286 + return tmdev->ops->get_trend(tmdev, s->id, temp);
287 +
288 + return -ENOTSUPP;
289 +}
290 +
291 +static int tsens_suspend(struct device *dev)
292 +{
293 + struct tsens_device *tmdev = dev_get_drvdata(dev);
294 +
295 + if (tmdev->ops && tmdev->ops->suspend)
296 + return tmdev->ops->suspend(tmdev);
297 +
298 + return 0;
299 +}
300 +
301 +static int tsens_resume(struct device *dev)
302 +{
303 + struct tsens_device *tmdev = dev_get_drvdata(dev);
304 +
305 + if (tmdev->ops && tmdev->ops->resume)
306 + return tmdev->ops->resume(tmdev);
307 +
308 + return 0;
309 +}
310 +
311 +static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
312 +
313 +static const struct of_device_id tsens_table[] = {
314 + {
315 + .compatible = "qcom,msm8916-tsens",
316 + }, {
317 + .compatible = "qcom,msm8974-tsens",
318 + },
319 + {}
320 +};
321 +MODULE_DEVICE_TABLE(of, tsens_table);
322 +
323 +static const struct thermal_zone_of_device_ops tsens_of_ops = {
324 + .get_temp = tsens_get_temp,
325 + .get_trend = tsens_get_trend,
326 +};
327 +
328 +static int tsens_register(struct tsens_device *tmdev)
329 +{
330 + int i;
331 + struct thermal_zone_device *tzd;
332 + u32 *hw_id, n = tmdev->num_sensors;
333 +
334 + hw_id = devm_kcalloc(tmdev->dev, n, sizeof(u32), GFP_KERNEL);
335 + if (!hw_id)
336 + return -ENOMEM;
337 +
338 + for (i = 0; i < tmdev->num_sensors; i++) {
339 + tmdev->sensor[i].tmdev = tmdev;
340 + tmdev->sensor[i].id = i;
341 + tzd = devm_thermal_zone_of_sensor_register(tmdev->dev, i,
342 + &tmdev->sensor[i],
343 + &tsens_of_ops);
344 + if (IS_ERR(tzd))
345 + continue;
346 + tmdev->sensor[i].tzd = tzd;
347 + if (tmdev->ops->enable)
348 + tmdev->ops->enable(tmdev, i);
349 + }
350 + return 0;
351 +}
352 +
353 +static int tsens_probe(struct platform_device *pdev)
354 +{
355 + int ret, i;
356 + struct device *dev;
357 + struct device_node *np;
358 + struct tsens_sensor *s;
359 + struct tsens_device *tmdev;
360 + const struct tsens_data *data;
361 + const struct of_device_id *id;
362 +
363 + if (pdev->dev.of_node)
364 + dev = &pdev->dev;
365 + else
366 + dev = pdev->dev.parent;
367 +
368 + np = dev->of_node;
369 +
370 + id = of_match_node(tsens_table, np);
371 + if (!id)
372 + return -EINVAL;
373 +
374 + data = id->data;
375 +
376 + if (data->num_sensors <= 0) {
377 + dev_err(dev, "invalid number of sensors\n");
378 + return -EINVAL;
379 + }
380 +
381 + tmdev = devm_kzalloc(dev, sizeof(*tmdev) +
382 + data->num_sensors * sizeof(*s), GFP_KERNEL);
383 + if (!tmdev)
384 + return -ENOMEM;
385 +
386 + tmdev->dev = dev;
387 + tmdev->num_sensors = data->num_sensors;
388 + tmdev->ops = data->ops;
389 + for (i = 0; i < tmdev->num_sensors; i++) {
390 + if (data->hw_ids)
391 + tmdev->sensor[i].hw_id = data->hw_ids[i];
392 + else
393 + tmdev->sensor[i].hw_id = i;
394 + }
395 +
396 + if (!tmdev->ops || !tmdev->ops->init || !tmdev->ops->get_temp)
397 + return -EINVAL;
398 +
399 + ret = tmdev->ops->init(tmdev);
400 + if (ret < 0) {
401 + dev_err(dev, "tsens init failed\n");
402 + return ret;
403 + }
404 +
405 + if (tmdev->ops->calibrate) {
406 + ret = tmdev->ops->calibrate(tmdev);
407 + if (ret < 0) {
408 + dev_err(dev, "tsens calibration failed\n");
409 + return ret;
410 + }
411 + }
412 +
413 + ret = tsens_register(tmdev);
414 +
415 + platform_set_drvdata(pdev, tmdev);
416 +
417 + return ret;
418 +}
419 +
420 +static int tsens_remove(struct platform_device *pdev)
421 +{
422 + struct tsens_device *tmdev = platform_get_drvdata(pdev);
423 +
424 + if (tmdev->ops->disable)
425 + tmdev->ops->disable(tmdev);
426 +
427 + return 0;
428 +}
429 +
430 +static struct platform_driver tsens_driver = {
431 + .probe = tsens_probe,
432 + .remove = tsens_remove,
433 + .driver = {
434 + .name = "qcom-tsens",
435 + .pm = &tsens_pm_ops,
436 + .of_match_table = tsens_table,
437 + },
438 +};
439 +module_platform_driver(tsens_driver);
440 +
441 +MODULE_LICENSE("GPL v2");
442 +MODULE_DESCRIPTION("QCOM Temperature Sensor driver");
443 +MODULE_ALIAS("platform:qcom-tsens");
444 --- /dev/null
445 +++ b/drivers/thermal/qcom/tsens.h
446 @@ -0,0 +1,90 @@
447 +/*
448 + * Copyright (c) 2015, The Linux Foundation. All rights reserved.
449 + *
450 + * This software is licensed under the terms of the GNU General Public
451 + * License version 2, as published by the Free Software Foundation, and
452 + * may be copied, distributed, and modified under those terms.
453 + *
454 + * This program is distributed in the hope that it will be useful,
455 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
456 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
457 + * GNU General Public License for more details.
458 + */
459 +#ifndef __QCOM_TSENS_H__
460 +#define __QCOM_TSENS_H__
461 +
462 +#define ONE_PT_CALIB 0x1
463 +#define ONE_PT_CALIB2 0x2
464 +#define TWO_PT_CALIB 0x3
465 +
466 +struct tsens_device;
467 +
468 +struct tsens_sensor {
469 + struct tsens_device *tmdev;
470 + struct thermal_zone_device *tzd;
471 + int offset;
472 + int id;
473 + int hw_id;
474 + int slope;
475 + u32 status;
476 +};
477 +
478 +/**
479 + * struct tsens_ops - operations as supported by the tsens device
480 + * @init: Function to initialize the tsens device
481 + * @calibrate: Function to calibrate the tsens device
482 + * @get_temp: Function which returns the temp in millidegC
483 + * @enable: Function to enable (clocks/power) tsens device
484 + * @disable: Function to disable the tsens device
485 + * @suspend: Function to suspend the tsens device
486 + * @resume: Function to resume the tsens device
487 + * @get_trend: Function to get the thermal/temp trend
488 + */
489 +struct tsens_ops {
490 + /* mandatory callbacks */
491 + int (*init)(struct tsens_device *);
492 + int (*calibrate)(struct tsens_device *);
493 + int (*get_temp)(struct tsens_device *, int, int *);
494 + /* optional callbacks */
495 + int (*enable)(struct tsens_device *, int);
496 + void (*disable)(struct tsens_device *);
497 + int (*suspend)(struct tsens_device *);
498 + int (*resume)(struct tsens_device *);
499 + int (*get_trend)(struct tsens_device *, int, long *);
500 +};
501 +
502 +/**
503 + * struct tsens_data - tsens instance specific data
504 + * @num_sensors: Max number of sensors supported by platform
505 + * @ops: operations the tsens instance supports
506 + * @hw_ids: Subset of sensors ids supported by platform, if not the first n
507 + */
508 +struct tsens_data {
509 + const u32 num_sensors;
510 + const struct tsens_ops *ops;
511 + unsigned int *hw_ids;
512 +};
513 +
514 +/* Registers to be saved/restored across a context loss */
515 +struct tsens_context {
516 + int threshold;
517 + int control;
518 +};
519 +
520 +struct tsens_device {
521 + struct device *dev;
522 + u32 num_sensors;
523 + struct regmap *map;
524 + struct regmap_field *status_field;
525 + struct tsens_context ctx;
526 + bool trdy;
527 + const struct tsens_ops *ops;
528 + struct tsens_sensor sensor[0];
529 +};
530 +
531 +char *qfprom_read(struct device *, const char *);
532 +void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32);
533 +int init_common(struct tsens_device *);
534 +int get_temp_common(struct tsens_device *, int, int *);
535 +
536 +#endif /* __QCOM_TSENS_H__ */