aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRajendra Nayak <rnayak@codeaurora.org>2016-05-05 04:51:39 -0400
committerZhang Rui <rui.zhang@intel.com>2016-09-27 02:02:16 -0400
commit9066073c6c27994a30187abf3b674770b4088348 (patch)
tree56b92622d6b3386810b2a4a655b8b9ad9a9b41d9
parentc6935931c1894ff857616ff8549b61236a19148f (diff)
thermal: qcom: tsens: Add a skeletal TSENS drivers
TSENS is Qualcomms' thermal temperature sensor device. It supports reading temperatures from multiple thermal sensors present on various QCOM SoCs. Calibration data is generally read from a non-volatile memory (eeprom) device. Add a skeleton driver with all the necessary abstractions so a variety of qcom device families which support TSENS can add driver extensions. Also add the required device tree bindings which can be used to describe the TSENS device in DT. Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org> Reviewed-by: Lina Iyer <lina.iyer@linaro.org> Signed-off-by: Eduardo Valentin <edubezval@gmail.com> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
-rw-r--r--Documentation/devicetree/bindings/thermal/qcom-tsens.txt21
-rw-r--r--drivers/thermal/Kconfig5
-rw-r--r--drivers/thermal/Makefile1
-rw-r--r--drivers/thermal/qcom/Kconfig11
-rw-r--r--drivers/thermal/qcom/Makefile2
-rw-r--r--drivers/thermal/qcom/tsens-common.c141
-rw-r--r--drivers/thermal/qcom/tsens.c195
-rw-r--r--drivers/thermal/qcom/tsens.h90
8 files changed, 466 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.txt b/Documentation/devicetree/bindings/thermal/qcom-tsens.txt
new file mode 100644
index 000000000000..292ed89d900b
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.txt
@@ -0,0 +1,21 @@
1* QCOM SoC Temperature Sensor (TSENS)
2
3Required properties:
4- compatible :
5 - "qcom,msm8916-tsens" : For 8916 Family of SoCs
6 - "qcom,msm8974-tsens" : For 8974 Family of SoCs
7 - "qcom,msm8996-tsens" : For 8996 Family of SoCs
8
9- reg: Address range of the thermal registers
10- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description.
11- Refer to Documentation/devicetree/bindings/nvmem/nvmem.txt to know how to specify
12nvmem cells
13
14Example:
15tsens: thermal-sensor@900000 {
16 compatible = "qcom,msm8916-tsens";
17 reg = <0x4a8000 0x2000>;
18 nvmem-cells = <&tsens_caldata>, <&tsens_calsel>;
19 nvmem-cell-names = "caldata", "calsel";
20 #thermal-sensor-cells = <1>;
21 };
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 2d702ca6556f..8303823d7f07 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -399,4 +399,9 @@ config GENERIC_ADC_THERMAL
399 to this driver. This driver reports the temperature by reading ADC 399 to this driver. This driver reports the temperature by reading ADC
400 channel and converts it to temperature based on lookup table. 400 channel and converts it to temperature based on lookup table.
401 401
402menu "Qualcomm thermal drivers"
403depends on (ARCH_QCOM && OF) || COMPILE_TEST
404source "drivers/thermal/qcom/Kconfig"
405endmenu
406
402endif 407endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 10b07c14f8a9..431771e260e5 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
47obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ 47obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
48obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o 48obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o
49obj-$(CONFIG_ST_THERMAL) += st/ 49obj-$(CONFIG_ST_THERMAL) += st/
50obj-$(CONFIG_QCOM_TSENS) += qcom/
50obj-$(CONFIG_TEGRA_SOCTHERM) += tegra/ 51obj-$(CONFIG_TEGRA_SOCTHERM) += tegra/
51obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o 52obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
52obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o 53obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
new file mode 100644
index 000000000000..be32e5abce3c
--- /dev/null
+++ b/drivers/thermal/qcom/Kconfig
@@ -0,0 +1,11 @@
1config QCOM_TSENS
2 tristate "Qualcomm TSENS Temperature Alarm"
3 depends on THERMAL
4 depends on QCOM_QFPROM
5 depends on ARCH_QCOM || COMPILE_TEST
6 help
7 This enables the thermal sysfs driver for the TSENS device. It shows
8 up in Sysfs as a thermal zone with multiple trip points. Disabling the
9 thermal zone device via the mode file results in disabling the sensor.
10 Also able to set threshold temperature for both hot and cold and update
11 when a threshold is reached.
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
new file mode 100644
index 000000000000..2e800c5ea120
--- /dev/null
+++ b/drivers/thermal/qcom/Makefile
@@ -0,0 +1,2 @@
1obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
2qcom_tsens-y += tsens.o tsens-common.o
diff --git a/drivers/thermal/qcom/tsens-common.c b/drivers/thermal/qcom/tsens-common.c
new file mode 100644
index 000000000000..4a1af151bd53
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-common.c
@@ -0,0 +1,141 @@
1/*
2 * Copyright (c) 2015, The Linux Foundation. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 */
14
15#include <linux/err.h>
16#include <linux/io.h>
17#include <linux/nvmem-consumer.h>
18#include <linux/of_address.h>
19#include <linux/platform_device.h>
20#include <linux/regmap.h>
21#include "tsens.h"
22
23#define S0_ST_ADDR 0x1030
24#define SN_ADDR_OFFSET 0x4
25#define SN_ST_TEMP_MASK 0x3ff
26#define CAL_DEGC_PT1 30
27#define CAL_DEGC_PT2 120
28#define SLOPE_FACTOR 1000
29#define SLOPE_DEFAULT 3200
30
31char *qfprom_read(struct device *dev, const char *cname)
32{
33 struct nvmem_cell *cell;
34 ssize_t data;
35 char *ret;
36
37 cell = nvmem_cell_get(dev, cname);
38 if (IS_ERR(cell))
39 return ERR_CAST(cell);
40
41 ret = nvmem_cell_read(cell, &data);
42 nvmem_cell_put(cell);
43
44 return ret;
45}
46
47/*
48 * Use this function on devices where slope and offset calculations
49 * depend on calibration data read from qfprom. On others the slope
50 * and offset values are derived from tz->tzp->slope and tz->tzp->offset
51 * resp.
52 */
53void compute_intercept_slope(struct tsens_device *tmdev, u32 *p1,
54 u32 *p2, u32 mode)
55{
56 int i;
57 int num, den;
58
59 for (i = 0; i < tmdev->num_sensors; i++) {
60 dev_dbg(tmdev->dev,
61 "sensor%d - data_point1:%#x data_point2:%#x\n",
62 i, p1[i], p2[i]);
63
64 tmdev->sensor[i].slope = SLOPE_DEFAULT;
65 if (mode == TWO_PT_CALIB) {
66 /*
67 * slope (m) = adc_code2 - adc_code1 (y2 - y1)/
68 * temp_120_degc - temp_30_degc (x2 - x1)
69 */
70 num = p2[i] - p1[i];
71 num *= SLOPE_FACTOR;
72 den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
73 tmdev->sensor[i].slope = num / den;
74 }
75
76 tmdev->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
77 (CAL_DEGC_PT1 *
78 tmdev->sensor[i].slope);
79 dev_dbg(tmdev->dev, "offset:%d\n", tmdev->sensor[i].offset);
80 }
81}
82
83static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
84{
85 int degc, num, den;
86
87 num = (adc_code * SLOPE_FACTOR) - s->offset;
88 den = s->slope;
89
90 if (num > 0)
91 degc = num + (den / 2);
92 else if (num < 0)
93 degc = num - (den / 2);
94 else
95 degc = num;
96
97 degc /= den;
98
99 return degc;
100}
101
102int get_temp_common(struct tsens_device *tmdev, int id, int *temp)
103{
104 struct tsens_sensor *s = &tmdev->sensor[id];
105 u32 code;
106 unsigned int sensor_addr;
107 int last_temp = 0, ret;
108
109 sensor_addr = S0_ST_ADDR + s->hw_id * SN_ADDR_OFFSET;
110 ret = regmap_read(tmdev->map, sensor_addr, &code);
111 if (ret)
112 return ret;
113 last_temp = code & SN_ST_TEMP_MASK;
114
115 *temp = code_to_degc(last_temp, s) * 1000;
116
117 return 0;
118}
119
120static const struct regmap_config tsens_config = {
121 .reg_bits = 32,
122 .val_bits = 32,
123 .reg_stride = 4,
124};
125
126int __init init_common(struct tsens_device *tmdev)
127{
128 void __iomem *base;
129
130 base = of_iomap(tmdev->dev->of_node, 0);
131 if (IS_ERR(base))
132 return -EINVAL;
133
134 tmdev->map = devm_regmap_init_mmio(tmdev->dev, base, &tsens_config);
135 if (!tmdev->map) {
136 iounmap(base);
137 return -ENODEV;
138 }
139
140 return 0;
141}
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
new file mode 100644
index 000000000000..17d7b5538f3a
--- /dev/null
+++ b/drivers/thermal/qcom/tsens.c
@@ -0,0 +1,195 @@
1/*
2 * Copyright (c) 2015, The Linux Foundation. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 */
14
15#include <linux/err.h>
16#include <linux/module.h>
17#include <linux/of.h>
18#include <linux/platform_device.h>
19#include <linux/pm.h>
20#include <linux/slab.h>
21#include <linux/thermal.h>
22#include "tsens.h"
23
24static int tsens_get_temp(void *data, int *temp)
25{
26 const struct tsens_sensor *s = data;
27 struct tsens_device *tmdev = s->tmdev;
28
29 return tmdev->ops->get_temp(tmdev, s->id, temp);
30}
31
32static int tsens_get_trend(void *data, long *temp)
33{
34 const struct tsens_sensor *s = data;
35 struct tsens_device *tmdev = s->tmdev;
36
37 if (tmdev->ops->get_trend)
38 return tmdev->ops->get_trend(tmdev, s->id, temp);
39
40 return -ENOTSUPP;
41}
42
43static int tsens_suspend(struct device *dev)
44{
45 struct tsens_device *tmdev = dev_get_drvdata(dev);
46
47 if (tmdev->ops && tmdev->ops->suspend)
48 return tmdev->ops->suspend(tmdev);
49
50 return 0;
51}
52
53static int tsens_resume(struct device *dev)
54{
55 struct tsens_device *tmdev = dev_get_drvdata(dev);
56
57 if (tmdev->ops && tmdev->ops->resume)
58 return tmdev->ops->resume(tmdev);
59
60 return 0;
61}
62
63static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
64
65static const struct of_device_id tsens_table[] = {
66 {
67 .compatible = "qcom,msm8916-tsens",
68 }, {
69 .compatible = "qcom,msm8974-tsens",
70 },
71 {}
72};
73MODULE_DEVICE_TABLE(of, tsens_table);
74
75static const struct thermal_zone_of_device_ops tsens_of_ops = {
76 .get_temp = tsens_get_temp,
77 .get_trend = tsens_get_trend,
78};
79
80static int tsens_register(struct tsens_device *tmdev)
81{
82 int i;
83 struct thermal_zone_device *tzd;
84 u32 *hw_id, n = tmdev->num_sensors;
85
86 hw_id = devm_kcalloc(tmdev->dev, n, sizeof(u32), GFP_KERNEL);
87 if (!hw_id)
88 return -ENOMEM;
89
90 for (i = 0; i < tmdev->num_sensors; i++) {
91 tmdev->sensor[i].tmdev = tmdev;
92 tmdev->sensor[i].id = i;
93 tzd = devm_thermal_zone_of_sensor_register(tmdev->dev, i,
94 &tmdev->sensor[i],
95 &tsens_of_ops);
96 if (IS_ERR(tzd))
97 continue;
98 tmdev->sensor[i].tzd = tzd;
99 if (tmdev->ops->enable)
100 tmdev->ops->enable(tmdev, i);
101 }
102 return 0;
103}
104
105static int tsens_probe(struct platform_device *pdev)
106{
107 int ret, i;
108 struct device *dev;
109 struct device_node *np;
110 struct tsens_sensor *s;
111 struct tsens_device *tmdev;
112 const struct tsens_data *data;
113 const struct of_device_id *id;
114
115 if (pdev->dev.of_node)
116 dev = &pdev->dev;
117 else
118 dev = pdev->dev.parent;
119
120 np = dev->of_node;
121
122 id = of_match_node(tsens_table, np);
123 if (!id)
124 return -EINVAL;
125
126 data = id->data;
127
128 if (data->num_sensors <= 0) {
129 dev_err(dev, "invalid number of sensors\n");
130 return -EINVAL;
131 }
132
133 tmdev = devm_kzalloc(dev, sizeof(*tmdev) +
134 data->num_sensors * sizeof(*s), GFP_KERNEL);
135 if (!tmdev)
136 return -ENOMEM;
137
138 tmdev->dev = dev;
139 tmdev->num_sensors = data->num_sensors;
140 tmdev->ops = data->ops;
141 for (i = 0; i < tmdev->num_sensors; i++) {
142 if (data->hw_ids)
143 tmdev->sensor[i].hw_id = data->hw_ids[i];
144 else
145 tmdev->sensor[i].hw_id = i;
146 }
147
148 if (!tmdev->ops || !tmdev->ops->init || !tmdev->ops->get_temp)
149 return -EINVAL;
150
151 ret = tmdev->ops->init(tmdev);
152 if (ret < 0) {
153 dev_err(dev, "tsens init failed\n");
154 return ret;
155 }
156
157 if (tmdev->ops->calibrate) {
158 ret = tmdev->ops->calibrate(tmdev);
159 if (ret < 0) {
160 dev_err(dev, "tsens calibration failed\n");
161 return ret;
162 }
163 }
164
165 ret = tsens_register(tmdev);
166
167 platform_set_drvdata(pdev, tmdev);
168
169 return ret;
170}
171
172static int tsens_remove(struct platform_device *pdev)
173{
174 struct tsens_device *tmdev = platform_get_drvdata(pdev);
175
176 if (tmdev->ops->disable)
177 tmdev->ops->disable(tmdev);
178
179 return 0;
180}
181
182static struct platform_driver tsens_driver = {
183 .probe = tsens_probe,
184 .remove = tsens_remove,
185 .driver = {
186 .name = "qcom-tsens",
187 .pm = &tsens_pm_ops,
188 .of_match_table = tsens_table,
189 },
190};
191module_platform_driver(tsens_driver);
192
193MODULE_LICENSE("GPL v2");
194MODULE_DESCRIPTION("QCOM Temperature Sensor driver");
195MODULE_ALIAS("platform:qcom-tsens");
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
new file mode 100644
index 000000000000..3b1b6ae82a47
--- /dev/null
+++ b/drivers/thermal/qcom/tsens.h
@@ -0,0 +1,90 @@
1/*
2 * Copyright (c) 2015, The Linux Foundation. All rights reserved.
3 *
4 * This software is licensed under the terms of the GNU General Public
5 * License version 2, as published by the Free Software Foundation, and
6 * may be copied, distributed, and modified under those terms.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13#ifndef __QCOM_TSENS_H__
14#define __QCOM_TSENS_H__
15
16#define ONE_PT_CALIB 0x1
17#define ONE_PT_CALIB2 0x2
18#define TWO_PT_CALIB 0x3
19
20struct tsens_device;
21
22struct tsens_sensor {
23 struct tsens_device *tmdev;
24 struct thermal_zone_device *tzd;
25 int offset;
26 int id;
27 int hw_id;
28 int slope;
29 u32 status;
30};
31
32/**
33 * struct tsens_ops - operations as supported by the tsens device
34 * @init: Function to initialize the tsens device
35 * @calibrate: Function to calibrate the tsens device
36 * @get_temp: Function which returns the temp in millidegC
37 * @enable: Function to enable (clocks/power) tsens device
38 * @disable: Function to disable the tsens device
39 * @suspend: Function to suspend the tsens device
40 * @resume: Function to resume the tsens device
41 * @get_trend: Function to get the thermal/temp trend
42 */
43struct tsens_ops {
44 /* mandatory callbacks */
45 int (*init)(struct tsens_device *);
46 int (*calibrate)(struct tsens_device *);
47 int (*get_temp)(struct tsens_device *, int, int *);
48 /* optional callbacks */
49 int (*enable)(struct tsens_device *, int);
50 void (*disable)(struct tsens_device *);
51 int (*suspend)(struct tsens_device *);
52 int (*resume)(struct tsens_device *);
53 int (*get_trend)(struct tsens_device *, int, long *);
54};
55
56/**
57 * struct tsens_data - tsens instance specific data
58 * @num_sensors: Max number of sensors supported by platform
59 * @ops: operations the tsens instance supports
60 * @hw_ids: Subset of sensors ids supported by platform, if not the first n
61 */
62struct tsens_data {
63 const u32 num_sensors;
64 const struct tsens_ops *ops;
65 unsigned int *hw_ids;
66};
67
68/* Registers to be saved/restored across a context loss */
69struct tsens_context {
70 int threshold;
71 int control;
72};
73
74struct tsens_device {
75 struct device *dev;
76 u32 num_sensors;
77 struct regmap *map;
78 struct regmap_field *status_field;
79 struct tsens_context ctx;
80 bool trdy;
81 const struct tsens_ops *ops;
82 struct tsens_sensor sensor[0];
83};
84
85char *qfprom_read(struct device *, const char *);
86void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32);
87int init_common(struct tsens_device *);
88int get_temp_common(struct tsens_device *, int, int *);
89
90#endif /* __QCOM_TSENS_H__ */