diff options
author | Wolfram Sang <wsa+renesas@sang-engineering.com> | 2016-12-22 05:38:21 -0500 |
---|---|---|
committer | Eduardo Valentin <edubezval@gmail.com> | 2017-01-19 23:15:28 -0500 |
commit | 564e73d283af9d4c1d642079d5c7ac601876162f (patch) | |
tree | 5e0a452ddfbd097f8daa59bb8b21595bfbd1d3cc /drivers/thermal | |
parent | b022e9b9d0e67f4cba62bc790bd387e23c29dc6c (diff) |
thermal: rcar_gen3_thermal: Add R-Car Gen3 thermal driver
Add support for R-Car Gen3 thermal sensors. Polling only for now,
interrupts will be added incrementally. Same goes for reading fuses.
This is documented already, but no hardware available for now.
Signed-off-by: Hien Dang <hien.dang.eb@renesas.com>
Signed-off-by: Thao Nguyen <thao.nguyen.yb@rvc.renesas.com>
Signed-off-by: Khiem Nguyen <khiem.nguyen.xt@renesas.com>
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
[Niklas: document and rework temperature calculation]
Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
Diffstat (limited to 'drivers/thermal')
-rw-r--r-- | drivers/thermal/Kconfig | 9 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/rcar_gen3_thermal.c | 335 |
3 files changed, 345 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index c2c056cc7ea5..3912d24a07b1 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig | |||
@@ -245,6 +245,15 @@ config RCAR_THERMAL | |||
245 | Enable this to plug the R-Car thermal sensor driver into the Linux | 245 | Enable this to plug the R-Car thermal sensor driver into the Linux |
246 | thermal framework. | 246 | thermal framework. |
247 | 247 | ||
248 | config RCAR_GEN3_THERMAL | ||
249 | tristate "Renesas R-Car Gen3 thermal driver" | ||
250 | depends on ARCH_RENESAS || COMPILE_TEST | ||
251 | depends on HAS_IOMEM | ||
252 | depends on OF | ||
253 | help | ||
254 | Enable this to plug the R-Car Gen3 thermal sensor driver into the Linux | ||
255 | thermal framework. | ||
256 | |||
248 | config KIRKWOOD_THERMAL | 257 | config KIRKWOOD_THERMAL |
249 | tristate "Temperature sensor on Marvell Kirkwood SoCs" | 258 | tristate "Temperature sensor on Marvell Kirkwood SoCs" |
250 | depends on MACH_KIRKWOOD || COMPILE_TEST | 259 | depends on MACH_KIRKWOOD || COMPILE_TEST |
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 6a3d7b573036..adcf3cc90859 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile | |||
@@ -31,6 +31,7 @@ obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o | |||
31 | obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o | 31 | obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o |
32 | obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o | 32 | obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o |
33 | obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o | 33 | obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o |
34 | obj-$(CONFIG_RCAR_GEN3_THERMAL) += rcar_gen3_thermal.o | ||
34 | obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o | 35 | obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o |
35 | obj-y += samsung/ | 36 | obj-y += samsung/ |
36 | obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o | 37 | obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o |
diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c new file mode 100644 index 000000000000..d33c845244b1 --- /dev/null +++ b/drivers/thermal/rcar_gen3_thermal.c | |||
@@ -0,0 +1,335 @@ | |||
1 | /* | ||
2 | * R-Car Gen3 THS thermal sensor driver | ||
3 | * Based on rcar_thermal.c and work from Hien Dang and Khiem Nguyen. | ||
4 | * | ||
5 | * Copyright (C) 2016 Renesas Electronics Corporation. | ||
6 | * Copyright (C) 2016 Sang Engineering | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; version 2 of the License. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * General Public License for more details. | ||
16 | * | ||
17 | */ | ||
18 | #include <linux/delay.h> | ||
19 | #include <linux/err.h> | ||
20 | #include <linux/interrupt.h> | ||
21 | #include <linux/io.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/mutex.h> | ||
24 | #include <linux/of_device.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/pm_runtime.h> | ||
27 | #include <linux/thermal.h> | ||
28 | |||
29 | /* Register offsets */ | ||
30 | #define REG_GEN3_IRQSTR 0x04 | ||
31 | #define REG_GEN3_IRQMSK 0x08 | ||
32 | #define REG_GEN3_IRQCTL 0x0C | ||
33 | #define REG_GEN3_IRQEN 0x10 | ||
34 | #define REG_GEN3_IRQTEMP1 0x14 | ||
35 | #define REG_GEN3_IRQTEMP2 0x18 | ||
36 | #define REG_GEN3_IRQTEMP3 0x1C | ||
37 | #define REG_GEN3_CTSR 0x20 | ||
38 | #define REG_GEN3_THCTR 0x20 | ||
39 | #define REG_GEN3_TEMP 0x28 | ||
40 | #define REG_GEN3_THCODE1 0x50 | ||
41 | #define REG_GEN3_THCODE2 0x54 | ||
42 | #define REG_GEN3_THCODE3 0x58 | ||
43 | |||
44 | /* CTSR bits */ | ||
45 | #define CTSR_PONM BIT(8) | ||
46 | #define CTSR_AOUT BIT(7) | ||
47 | #define CTSR_THBGR BIT(5) | ||
48 | #define CTSR_VMEN BIT(4) | ||
49 | #define CTSR_VMST BIT(1) | ||
50 | #define CTSR_THSST BIT(0) | ||
51 | |||
52 | /* THCTR bits */ | ||
53 | #define THCTR_PONM BIT(6) | ||
54 | #define THCTR_THSST BIT(0) | ||
55 | |||
56 | #define CTEMP_MASK 0xFFF | ||
57 | |||
58 | #define MCELSIUS(temp) ((temp) * 1000) | ||
59 | #define GEN3_FUSE_MASK 0xFFF | ||
60 | |||
61 | #define TSC_MAX_NUM 3 | ||
62 | |||
63 | /* Structure for thermal temperature calculation */ | ||
64 | struct equation_coefs { | ||
65 | int a1; | ||
66 | int b1; | ||
67 | int a2; | ||
68 | int b2; | ||
69 | }; | ||
70 | |||
71 | struct rcar_gen3_thermal_tsc { | ||
72 | void __iomem *base; | ||
73 | struct thermal_zone_device *zone; | ||
74 | struct equation_coefs coef; | ||
75 | struct mutex lock; | ||
76 | }; | ||
77 | |||
78 | struct rcar_gen3_thermal_priv { | ||
79 | struct rcar_gen3_thermal_tsc *tscs[TSC_MAX_NUM]; | ||
80 | }; | ||
81 | |||
82 | struct rcar_gen3_thermal_data { | ||
83 | void (*thermal_init)(struct rcar_gen3_thermal_tsc *tsc); | ||
84 | }; | ||
85 | |||
86 | static inline u32 rcar_gen3_thermal_read(struct rcar_gen3_thermal_tsc *tsc, | ||
87 | u32 reg) | ||
88 | { | ||
89 | return ioread32(tsc->base + reg); | ||
90 | } | ||
91 | |||
92 | static inline void rcar_gen3_thermal_write(struct rcar_gen3_thermal_tsc *tsc, | ||
93 | u32 reg, u32 data) | ||
94 | { | ||
95 | iowrite32(data, tsc->base + reg); | ||
96 | } | ||
97 | |||
98 | /* | ||
99 | * Linear approximation for temperature | ||
100 | * | ||
101 | * [reg] = [temp] * a + b => [temp] = ([reg] - b) / a | ||
102 | * | ||
103 | * The constants a and b are calculated using two triplets of int values PTAT | ||
104 | * and THCODE. PTAT and THCODE can either be read from hardware or use hard | ||
105 | * coded values from driver. The formula to calculate a and b are taken from | ||
106 | * BSP and sparsely documented and understood. | ||
107 | * | ||
108 | * Examining the linear formula and the formula used to calculate constants a | ||
109 | * and b while knowing that the span for PTAT and THCODE values are between | ||
110 | * 0x000 and 0xfff the largest integer possible is 0xfff * 0xfff == 0xffe001. | ||
111 | * Integer also needs to be signed so that leaves 7 bits for binary | ||
112 | * fixed point scaling. | ||
113 | */ | ||
114 | |||
115 | #define FIXPT_SHIFT 7 | ||
116 | #define FIXPT_INT(_x) ((_x) << FIXPT_SHIFT) | ||
117 | #define FIXPT_DIV(_a, _b) DIV_ROUND_CLOSEST(((_a) << FIXPT_SHIFT), (_b)) | ||
118 | #define FIXPT_TO_MCELSIUS(_x) ((_x) * 1000 >> FIXPT_SHIFT) | ||
119 | |||
120 | #define RCAR3_THERMAL_GRAN 500 /* mili Celsius */ | ||
121 | |||
122 | /* no idea where these constants come from */ | ||
123 | #define TJ_1 96 | ||
124 | #define TJ_3 -41 | ||
125 | |||
126 | static void rcar_gen3_thermal_calc_coefs(struct equation_coefs *coef, | ||
127 | int *ptat, int *thcode) | ||
128 | { | ||
129 | int tj_2; | ||
130 | |||
131 | /* TODO: Find documentation and document constant calculation formula */ | ||
132 | |||
133 | /* | ||
134 | * Division is not scaled in BSP and if scaled it might overflow | ||
135 | * the dividend (4095 * 4095 << 14 > INT_MAX) so keep it unscaled | ||
136 | */ | ||
137 | tj_2 = (FIXPT_INT((ptat[1] - ptat[2]) * 137) | ||
138 | / (ptat[0] - ptat[2])) - FIXPT_INT(41); | ||
139 | |||
140 | coef->a1 = FIXPT_DIV(FIXPT_INT(thcode[1] - thcode[2]), | ||
141 | tj_2 - FIXPT_INT(TJ_3)); | ||
142 | coef->b1 = FIXPT_INT(thcode[2]) - coef->a1 * TJ_3; | ||
143 | |||
144 | coef->a2 = FIXPT_DIV(FIXPT_INT(thcode[1] - thcode[0]), | ||
145 | tj_2 - FIXPT_INT(TJ_1)); | ||
146 | coef->b2 = FIXPT_INT(thcode[0]) - coef->a2 * TJ_1; | ||
147 | } | ||
148 | |||
149 | static int rcar_gen3_thermal_round(int temp) | ||
150 | { | ||
151 | int result, round_offs; | ||
152 | |||
153 | round_offs = temp >= 0 ? RCAR3_THERMAL_GRAN / 2 : | ||
154 | -RCAR3_THERMAL_GRAN / 2; | ||
155 | result = (temp + round_offs) / RCAR3_THERMAL_GRAN; | ||
156 | return result * RCAR3_THERMAL_GRAN; | ||
157 | } | ||
158 | |||
159 | static int rcar_gen3_thermal_get_temp(void *devdata, int *temp) | ||
160 | { | ||
161 | struct rcar_gen3_thermal_tsc *tsc = devdata; | ||
162 | int mcelsius, val1, val2; | ||
163 | u32 reg; | ||
164 | |||
165 | /* Read register and convert to mili Celsius */ | ||
166 | mutex_lock(&tsc->lock); | ||
167 | |||
168 | reg = rcar_gen3_thermal_read(tsc, REG_GEN3_TEMP) & CTEMP_MASK; | ||
169 | |||
170 | val1 = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b1, tsc->coef.a1); | ||
171 | val2 = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b2, tsc->coef.a2); | ||
172 | mcelsius = FIXPT_TO_MCELSIUS((val1 + val2) / 2); | ||
173 | |||
174 | mutex_unlock(&tsc->lock); | ||
175 | |||
176 | /* Make sure we are inside specifications */ | ||
177 | if ((mcelsius < MCELSIUS(-40)) || (mcelsius > MCELSIUS(125))) | ||
178 | return -EIO; | ||
179 | |||
180 | /* Round value to device granularity setting */ | ||
181 | *temp = rcar_gen3_thermal_round(mcelsius); | ||
182 | |||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | static struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = { | ||
187 | .get_temp = rcar_gen3_thermal_get_temp, | ||
188 | }; | ||
189 | |||
190 | static void r8a7795_thermal_init(struct rcar_gen3_thermal_tsc *tsc) | ||
191 | { | ||
192 | rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, CTSR_THBGR); | ||
193 | rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, 0x0); | ||
194 | |||
195 | usleep_range(1000, 2000); | ||
196 | |||
197 | rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, CTSR_PONM); | ||
198 | rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F); | ||
199 | rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, | ||
200 | CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN); | ||
201 | |||
202 | usleep_range(100, 200); | ||
203 | |||
204 | rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, | ||
205 | CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN | | ||
206 | CTSR_VMST | CTSR_THSST); | ||
207 | |||
208 | usleep_range(1000, 2000); | ||
209 | } | ||
210 | |||
211 | static void r8a7796_thermal_init(struct rcar_gen3_thermal_tsc *tsc) | ||
212 | { | ||
213 | u32 reg_val; | ||
214 | |||
215 | reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR); | ||
216 | reg_val &= ~THCTR_PONM; | ||
217 | rcar_gen3_thermal_write(tsc, REG_GEN3_THCTR, reg_val); | ||
218 | |||
219 | usleep_range(1000, 2000); | ||
220 | |||
221 | rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F); | ||
222 | reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR); | ||
223 | reg_val |= THCTR_THSST; | ||
224 | rcar_gen3_thermal_write(tsc, REG_GEN3_THCTR, reg_val); | ||
225 | } | ||
226 | |||
227 | static const struct rcar_gen3_thermal_data r8a7795_data = { | ||
228 | .thermal_init = r8a7795_thermal_init, | ||
229 | }; | ||
230 | |||
231 | static const struct rcar_gen3_thermal_data r8a7796_data = { | ||
232 | .thermal_init = r8a7796_thermal_init, | ||
233 | }; | ||
234 | |||
235 | static const struct of_device_id rcar_gen3_thermal_dt_ids[] = { | ||
236 | { .compatible = "renesas,r8a7795-thermal", .data = &r8a7795_data}, | ||
237 | { .compatible = "renesas,r8a7796-thermal", .data = &r8a7796_data}, | ||
238 | {}, | ||
239 | }; | ||
240 | MODULE_DEVICE_TABLE(of, rcar_gen3_thermal_dt_ids); | ||
241 | |||
242 | static int rcar_gen3_thermal_remove(struct platform_device *pdev) | ||
243 | { | ||
244 | struct device *dev = &pdev->dev; | ||
245 | |||
246 | pm_runtime_put(dev); | ||
247 | pm_runtime_disable(dev); | ||
248 | |||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | static int rcar_gen3_thermal_probe(struct platform_device *pdev) | ||
253 | { | ||
254 | struct rcar_gen3_thermal_priv *priv; | ||
255 | struct device *dev = &pdev->dev; | ||
256 | struct resource *res; | ||
257 | struct thermal_zone_device *zone; | ||
258 | int ret, i; | ||
259 | const struct rcar_gen3_thermal_data *match_data = | ||
260 | of_device_get_match_data(dev); | ||
261 | |||
262 | /* default values if FUSEs are missing */ | ||
263 | /* TODO: Read values from hardware on supported platforms */ | ||
264 | int ptat[3] = { 2351, 1509, 435 }; | ||
265 | int thcode[TSC_MAX_NUM][3] = { | ||
266 | { 3248, 2800, 2221 }, | ||
267 | { 3245, 2795, 2216 }, | ||
268 | { 3250, 2805, 2237 }, | ||
269 | }; | ||
270 | |||
271 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | ||
272 | if (!priv) | ||
273 | return -ENOMEM; | ||
274 | |||
275 | platform_set_drvdata(pdev, priv); | ||
276 | |||
277 | pm_runtime_enable(dev); | ||
278 | pm_runtime_get_sync(dev); | ||
279 | |||
280 | for (i = 0; i < TSC_MAX_NUM; i++) { | ||
281 | struct rcar_gen3_thermal_tsc *tsc; | ||
282 | |||
283 | tsc = devm_kzalloc(dev, sizeof(*tsc), GFP_KERNEL); | ||
284 | if (!tsc) { | ||
285 | ret = -ENOMEM; | ||
286 | goto error_unregister; | ||
287 | } | ||
288 | |||
289 | res = platform_get_resource(pdev, IORESOURCE_MEM, i); | ||
290 | if (!res) | ||
291 | break; | ||
292 | |||
293 | tsc->base = devm_ioremap_resource(dev, res); | ||
294 | if (IS_ERR(tsc->base)) { | ||
295 | ret = PTR_ERR(tsc->base); | ||
296 | goto error_unregister; | ||
297 | } | ||
298 | |||
299 | priv->tscs[i] = tsc; | ||
300 | mutex_init(&tsc->lock); | ||
301 | |||
302 | match_data->thermal_init(tsc); | ||
303 | rcar_gen3_thermal_calc_coefs(&tsc->coef, ptat, thcode[i]); | ||
304 | |||
305 | zone = devm_thermal_zone_of_sensor_register(dev, i, tsc, | ||
306 | &rcar_gen3_tz_of_ops); | ||
307 | if (IS_ERR(zone)) { | ||
308 | dev_err(dev, "Can't register thermal zone\n"); | ||
309 | ret = PTR_ERR(zone); | ||
310 | goto error_unregister; | ||
311 | } | ||
312 | tsc->zone = zone; | ||
313 | } | ||
314 | |||
315 | return 0; | ||
316 | |||
317 | error_unregister: | ||
318 | rcar_gen3_thermal_remove(pdev); | ||
319 | |||
320 | return ret; | ||
321 | } | ||
322 | |||
323 | static struct platform_driver rcar_gen3_thermal_driver = { | ||
324 | .driver = { | ||
325 | .name = "rcar_gen3_thermal", | ||
326 | .of_match_table = rcar_gen3_thermal_dt_ids, | ||
327 | }, | ||
328 | .probe = rcar_gen3_thermal_probe, | ||
329 | .remove = rcar_gen3_thermal_remove, | ||
330 | }; | ||
331 | module_platform_driver(rcar_gen3_thermal_driver); | ||
332 | |||
333 | MODULE_LICENSE("GPL v2"); | ||
334 | MODULE_DESCRIPTION("R-Car Gen3 THS thermal sensor driver"); | ||
335 | MODULE_AUTHOR("Wolfram Sang <wsa+renesas@sang-engineering.com>"); | ||