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
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
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.
16 Also add the required device tree bindings which can be used
17 to describe the TSENS device in DT.
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>
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
41 +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.txt
43 +* QCOM SoC Temperature Sensor (TSENS)
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
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
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>;
64 --- a/drivers/thermal/Kconfig
65 +++ b/drivers/thermal/Kconfig
66 @@ -404,4 +404,9 @@ config BCM2835_THERMAL
68 Support for thermal sensors on Broadcom bcm2835 SoCs.
70 +menu "Qualcomm thermal drivers"
71 +depends on (ARCH_QCOM && OF) || COMPILE_TEST
72 +source "drivers/thermal/qcom/Kconfig"
76 --- a/drivers/thermal/Makefile
77 +++ b/drivers/thermal/Makefile
78 @@ -50,3 +50,4 @@ obj-$(CONFIG_ST_THERMAL) += st/
79 obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o
80 obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
81 obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o
82 +obj-$(CONFIG_QCOM_TSENS) += qcom/
84 +++ b/drivers/thermal/qcom/Kconfig
87 + tristate "Qualcomm TSENS Temperature Alarm"
89 + depends on QCOM_QFPROM
90 + depends on ARCH_QCOM || COMPILE_TEST
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.
98 +++ b/drivers/thermal/qcom/Makefile
100 +obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
101 +qcom_tsens-y += tsens.o tsens-common.o
103 +++ b/drivers/thermal/qcom/tsens-common.c
106 + * Copyright (c) 2015, The Linux Foundation. All rights reserved.
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.
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.
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>
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
135 +char *qfprom_read(struct device *dev, const char *cname)
137 + struct nvmem_cell *cell;
141 + cell = nvmem_cell_get(dev, cname);
143 + return ERR_CAST(cell);
145 + ret = nvmem_cell_read(cell, &data);
146 + nvmem_cell_put(cell);
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
157 +void compute_intercept_slope(struct tsens_device *tmdev, u32 *p1,
163 + for (i = 0; i < tmdev->num_sensors; i++) {
164 + dev_dbg(tmdev->dev,
165 + "sensor%d - data_point1:%#x data_point2:%#x\n",
168 + tmdev->sensor[i].slope = SLOPE_DEFAULT;
169 + if (mode == TWO_PT_CALIB) {
171 + * slope (m) = adc_code2 - adc_code1 (y2 - y1)/
172 + * temp_120_degc - temp_30_degc (x2 - x1)
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;
180 + tmdev->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
182 + tmdev->sensor[i].slope);
183 + dev_dbg(tmdev->dev, "offset:%d\n", tmdev->sensor[i].offset);
187 +static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
189 + int degc, num, den;
191 + num = (adc_code * SLOPE_FACTOR) - s->offset;
195 + degc = num + (den / 2);
197 + degc = num - (den / 2);
206 +int get_temp_common(struct tsens_device *tmdev, int id, int *temp)
208 + struct tsens_sensor *s = &tmdev->sensor[id];
210 + unsigned int sensor_addr;
211 + int last_temp = 0, ret;
213 + sensor_addr = S0_ST_ADDR + s->hw_id * SN_ADDR_OFFSET;
214 + ret = regmap_read(tmdev->map, sensor_addr, &code);
217 + last_temp = code & SN_ST_TEMP_MASK;
219 + *temp = code_to_degc(last_temp, s) * 1000;
224 +static const struct regmap_config tsens_config = {
230 +int __init init_common(struct tsens_device *tmdev)
232 + void __iomem *base;
234 + base = of_iomap(tmdev->dev->of_node, 0);
238 + tmdev->map = devm_regmap_init_mmio(tmdev->dev, base, &tsens_config);
247 +++ b/drivers/thermal/qcom/tsens.c
250 + * Copyright (c) 2015, The Linux Foundation. All rights reserved.
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.
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.
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>
272 +static int tsens_get_temp(void *data, int *temp)
274 + const struct tsens_sensor *s = data;
275 + struct tsens_device *tmdev = s->tmdev;
277 + return tmdev->ops->get_temp(tmdev, s->id, temp);
280 +static int tsens_get_trend(void *data, long *temp)
282 + const struct tsens_sensor *s = data;
283 + struct tsens_device *tmdev = s->tmdev;
285 + if (tmdev->ops->get_trend)
286 + return tmdev->ops->get_trend(tmdev, s->id, temp);
291 +static int tsens_suspend(struct device *dev)
293 + struct tsens_device *tmdev = dev_get_drvdata(dev);
295 + if (tmdev->ops && tmdev->ops->suspend)
296 + return tmdev->ops->suspend(tmdev);
301 +static int tsens_resume(struct device *dev)
303 + struct tsens_device *tmdev = dev_get_drvdata(dev);
305 + if (tmdev->ops && tmdev->ops->resume)
306 + return tmdev->ops->resume(tmdev);
311 +static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
313 +static const struct of_device_id tsens_table[] = {
315 + .compatible = "qcom,msm8916-tsens",
317 + .compatible = "qcom,msm8974-tsens",
321 +MODULE_DEVICE_TABLE(of, tsens_table);
323 +static const struct thermal_zone_of_device_ops tsens_of_ops = {
324 + .get_temp = tsens_get_temp,
325 + .get_trend = tsens_get_trend,
328 +static int tsens_register(struct tsens_device *tmdev)
331 + struct thermal_zone_device *tzd;
332 + u32 *hw_id, n = tmdev->num_sensors;
334 + hw_id = devm_kcalloc(tmdev->dev, n, sizeof(u32), GFP_KERNEL);
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,
346 + tmdev->sensor[i].tzd = tzd;
347 + if (tmdev->ops->enable)
348 + tmdev->ops->enable(tmdev, i);
353 +static int tsens_probe(struct platform_device *pdev)
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;
363 + if (pdev->dev.of_node)
366 + dev = pdev->dev.parent;
370 + id = of_match_node(tsens_table, np);
376 + if (data->num_sensors <= 0) {
377 + dev_err(dev, "invalid number of sensors\n");
381 + tmdev = devm_kzalloc(dev, sizeof(*tmdev) +
382 + data->num_sensors * sizeof(*s), GFP_KERNEL);
387 + tmdev->num_sensors = data->num_sensors;
388 + tmdev->ops = data->ops;
389 + for (i = 0; i < tmdev->num_sensors; i++) {
391 + tmdev->sensor[i].hw_id = data->hw_ids[i];
393 + tmdev->sensor[i].hw_id = i;
396 + if (!tmdev->ops || !tmdev->ops->init || !tmdev->ops->get_temp)
399 + ret = tmdev->ops->init(tmdev);
401 + dev_err(dev, "tsens init failed\n");
405 + if (tmdev->ops->calibrate) {
406 + ret = tmdev->ops->calibrate(tmdev);
408 + dev_err(dev, "tsens calibration failed\n");
413 + ret = tsens_register(tmdev);
415 + platform_set_drvdata(pdev, tmdev);
420 +static int tsens_remove(struct platform_device *pdev)
422 + struct tsens_device *tmdev = platform_get_drvdata(pdev);
424 + if (tmdev->ops->disable)
425 + tmdev->ops->disable(tmdev);
430 +static struct platform_driver tsens_driver = {
431 + .probe = tsens_probe,
432 + .remove = tsens_remove,
434 + .name = "qcom-tsens",
435 + .pm = &tsens_pm_ops,
436 + .of_match_table = tsens_table,
439 +module_platform_driver(tsens_driver);
441 +MODULE_LICENSE("GPL v2");
442 +MODULE_DESCRIPTION("QCOM Temperature Sensor driver");
443 +MODULE_ALIAS("platform:qcom-tsens");
445 +++ b/drivers/thermal/qcom/tsens.h
448 + * Copyright (c) 2015, The Linux Foundation. All rights reserved.
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.
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.
459 +#ifndef __QCOM_TSENS_H__
460 +#define __QCOM_TSENS_H__
462 +#define ONE_PT_CALIB 0x1
463 +#define ONE_PT_CALIB2 0x2
464 +#define TWO_PT_CALIB 0x3
466 +struct tsens_device;
468 +struct tsens_sensor {
469 + struct tsens_device *tmdev;
470 + struct thermal_zone_device *tzd;
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
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 *);
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
509 + const u32 num_sensors;
510 + const struct tsens_ops *ops;
511 + unsigned int *hw_ids;
514 +/* Registers to be saved/restored across a context loss */
515 +struct tsens_context {
520 +struct tsens_device {
521 + struct device *dev;
523 + struct regmap *map;
524 + struct regmap_field *status_field;
525 + struct tsens_context ctx;
527 + const struct tsens_ops *ops;
528 + struct tsens_sensor sensor[0];
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 *);
536 +#endif /* __QCOM_TSENS_H__ */