aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKunihiko Hayashi <hayashi.kunihiko@socionext.com>2017-08-01 04:04:51 -0400
committerZhang Rui <rui.zhang@intel.com>2017-08-10 22:49:52 -0400
commit86da4391752c09f5f2b4a0d825466ec23e9da61f (patch)
treed9d81b6f93d26e33f21ca4500ca0edf424db109c
parent0fad424465021aeeb11a816f3a749167f8c776eb (diff)
thermal: uniphier: add UniPhier thermal driver
Add a thermal driver for on-chip PVT (Process, Voltage and Temperature) monitoring unit implemented on UniPhier SoCs. This driver supports temperature monitoring and alert function. Signed-off-by: Kunihiko Hayashi <hayashi.kunihiko@socionext.com> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
-rw-r--r--drivers/thermal/Kconfig8
-rw-r--r--drivers/thermal/Makefile1
-rw-r--r--drivers/thermal/uniphier_thermal.c384
3 files changed, 393 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index b5b5facb8747..b9f23653e0ea 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -473,4 +473,12 @@ config ZX2967_THERMAL
473 the primitive temperature sensor embedded in zx2967 SoCs. 473 the primitive temperature sensor embedded in zx2967 SoCs.
474 This sensor generates the real time die temperature. 474 This sensor generates the real time die temperature.
475 475
476config UNIPHIER_THERMAL
477 tristate "Socionext UniPhier thermal driver"
478 depends on ARCH_UNIPHIER || COMPILE_TEST
479 depends on THERMAL_OF && MFD_SYSCON
480 help
481 Enable this to plug in UniPhier on-chip PVT thermal driver into the
482 thermal framework. The driver supports CPU thermal zone temperature
483 reporting and a couple of trip points.
476endif 484endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 094d7039981c..8b79bca23536 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -59,3 +59,4 @@ obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
59obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o 59obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
60obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o 60obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
61obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o 61obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o
62obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o
diff --git a/drivers/thermal/uniphier_thermal.c b/drivers/thermal/uniphier_thermal.c
new file mode 100644
index 000000000000..95704732f760
--- /dev/null
+++ b/drivers/thermal/uniphier_thermal.c
@@ -0,0 +1,384 @@
1/**
2 * uniphier_thermal.c - Socionext UniPhier thermal driver
3 *
4 * Copyright 2014 Panasonic Corporation
5 * Copyright 2016-2017 Socionext Inc.
6 * All rights reserved.
7 *
8 * Author:
9 * Kunihiko Hayashi <hayashi.kunihiko@socionext.com>
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 of
13 * the License as published by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 */
20
21#include <linux/bitops.h>
22#include <linux/interrupt.h>
23#include <linux/mfd/syscon.h>
24#include <linux/module.h>
25#include <linux/of.h>
26#include <linux/of_device.h>
27#include <linux/platform_device.h>
28#include <linux/regmap.h>
29#include <linux/thermal.h>
30
31#include "thermal_core.h"
32
33/*
34 * block registers
35 * addresses are the offset from .block_base
36 */
37#define PVTCTLEN 0x0000
38#define PVTCTLEN_EN BIT(0)
39
40#define PVTCTLMODE 0x0004
41#define PVTCTLMODE_MASK 0xf
42#define PVTCTLMODE_TEMPMON 0x5
43
44#define EMONREPEAT 0x0040
45#define EMONREPEAT_ENDLESS BIT(24)
46#define EMONREPEAT_PERIOD GENMASK(3, 0)
47#define EMONREPEAT_PERIOD_1000000 0x9
48
49/*
50 * common registers
51 * addresses are the offset from .map_base
52 */
53#define PVTCTLSEL 0x0900
54#define PVTCTLSEL_MASK GENMASK(2, 0)
55#define PVTCTLSEL_MONITOR 0
56
57#define SETALERT0 0x0910
58#define SETALERT1 0x0914
59#define SETALERT2 0x0918
60#define SETALERT_TEMP_OVF (GENMASK(7, 0) << 16)
61#define SETALERT_TEMP_OVF_VALUE(val) (((val) & GENMASK(7, 0)) << 16)
62#define SETALERT_EN BIT(0)
63
64#define PMALERTINTCTL 0x0920
65#define PMALERTINTCTL_CLR(ch) BIT(4 * (ch) + 2)
66#define PMALERTINTCTL_SET(ch) BIT(4 * (ch) + 1)
67#define PMALERTINTCTL_EN(ch) BIT(4 * (ch) + 0)
68#define PMALERTINTCTL_MASK (GENMASK(10, 8) | GENMASK(6, 4) | \
69 GENMASK(2, 0))
70
71#define TMOD 0x0928
72#define TMOD_WIDTH 9
73
74#define TMODCOEF 0x0e5c
75
76#define TMODSETUP0_EN BIT(30)
77#define TMODSETUP0_VAL(val) (((val) & GENMASK(13, 0)) << 16)
78#define TMODSETUP1_EN BIT(15)
79#define TMODSETUP1_VAL(val) ((val) & GENMASK(14, 0))
80
81/* SoC critical temperature */
82#define CRITICAL_TEMP_LIMIT (120 * 1000)
83
84/* Max # of alert channels */
85#define ALERT_CH_NUM 3
86
87/* SoC specific thermal sensor data */
88struct uniphier_tm_soc_data {
89 u32 map_base;
90 u32 block_base;
91 u32 tmod_setup_addr;
92};
93
94struct uniphier_tm_dev {
95 struct regmap *regmap;
96 struct device *dev;
97 bool alert_en[ALERT_CH_NUM];
98 struct thermal_zone_device *tz_dev;
99 const struct uniphier_tm_soc_data *data;
100};
101
102static int uniphier_tm_initialize_sensor(struct uniphier_tm_dev *tdev)
103{
104 struct regmap *map = tdev->regmap;
105 u32 val;
106 u32 tmod_calib[2];
107 int ret;
108
109 /* stop PVT */
110 regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,
111 PVTCTLEN_EN, 0);
112
113 /*
114 * Since SoC has a calibrated value that was set in advance,
115 * TMODCOEF shows non-zero and PVT refers the value internally.
116 *
117 * If TMODCOEF shows zero, the boards don't have the calibrated
118 * value, and the driver has to set default value from DT.
119 */
120 ret = regmap_read(map, tdev->data->map_base + TMODCOEF, &val);
121 if (ret)
122 return ret;
123 if (!val) {
124 /* look for the default values in DT */
125 ret = of_property_read_u32_array(tdev->dev->of_node,
126 "socionext,tmod-calibration",
127 tmod_calib,
128 ARRAY_SIZE(tmod_calib));
129 if (ret)
130 return ret;
131
132 regmap_write(map, tdev->data->tmod_setup_addr,
133 TMODSETUP0_EN | TMODSETUP0_VAL(tmod_calib[0]) |
134 TMODSETUP1_EN | TMODSETUP1_VAL(tmod_calib[1]));
135 }
136
137 /* select temperature mode */
138 regmap_write_bits(map, tdev->data->block_base + PVTCTLMODE,
139 PVTCTLMODE_MASK, PVTCTLMODE_TEMPMON);
140
141 /* set monitoring period */
142 regmap_write_bits(map, tdev->data->block_base + EMONREPEAT,
143 EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD,
144 EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD_1000000);
145
146 /* set monitor mode */
147 regmap_write_bits(map, tdev->data->map_base + PVTCTLSEL,
148 PVTCTLSEL_MASK, PVTCTLSEL_MONITOR);
149
150 return 0;
151}
152
153static void uniphier_tm_set_alert(struct uniphier_tm_dev *tdev, u32 ch,
154 u32 temp)
155{
156 struct regmap *map = tdev->regmap;
157
158 /* set alert temperature */
159 regmap_write_bits(map, tdev->data->map_base + SETALERT0 + (ch << 2),
160 SETALERT_EN | SETALERT_TEMP_OVF,
161 SETALERT_EN |
162 SETALERT_TEMP_OVF_VALUE(temp / 1000));
163}
164
165static void uniphier_tm_enable_sensor(struct uniphier_tm_dev *tdev)
166{
167 struct regmap *map = tdev->regmap;
168 int i;
169 u32 bits = 0;
170
171 for (i = 0; i < ALERT_CH_NUM; i++)
172 if (tdev->alert_en[i])
173 bits |= PMALERTINTCTL_EN(i);
174
175 /* enable alert interrupt */
176 regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL,
177 PMALERTINTCTL_MASK, bits);
178
179 /* start PVT */
180 regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,
181 PVTCTLEN_EN, PVTCTLEN_EN);
182
183 usleep_range(700, 1500); /* The spec note says at least 700us */
184}
185
186static void uniphier_tm_disable_sensor(struct uniphier_tm_dev *tdev)
187{
188 struct regmap *map = tdev->regmap;
189
190 /* disable alert interrupt */
191 regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL,
192 PMALERTINTCTL_MASK, 0);
193
194 /* stop PVT */
195 regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,
196 PVTCTLEN_EN, 0);
197
198 usleep_range(1000, 2000); /* The spec note says at least 1ms */
199}
200
201static int uniphier_tm_get_temp(void *data, int *out_temp)
202{
203 struct uniphier_tm_dev *tdev = data;
204 struct regmap *map = tdev->regmap;
205 int ret;
206 u32 temp;
207
208 ret = regmap_read(map, tdev->data->map_base + TMOD, &temp);
209 if (ret)
210 return ret;
211
212 /* MSB of the TMOD field is a sign bit */
213 *out_temp = sign_extend32(temp, TMOD_WIDTH - 1) * 1000;
214
215 return 0;
216}
217
218static const struct thermal_zone_of_device_ops uniphier_of_thermal_ops = {
219 .get_temp = uniphier_tm_get_temp,
220};
221
222static void uniphier_tm_irq_clear(struct uniphier_tm_dev *tdev)
223{
224 u32 mask = 0, bits = 0;
225 int i;
226
227 for (i = 0; i < ALERT_CH_NUM; i++) {
228 mask |= (PMALERTINTCTL_CLR(i) | PMALERTINTCTL_SET(i));
229 bits |= PMALERTINTCTL_CLR(i);
230 }
231
232 /* clear alert interrupt */
233 regmap_write_bits(tdev->regmap,
234 tdev->data->map_base + PMALERTINTCTL, mask, bits);
235}
236
237static irqreturn_t uniphier_tm_alarm_irq(int irq, void *_tdev)
238{
239 struct uniphier_tm_dev *tdev = _tdev;
240
241 disable_irq_nosync(irq);
242 uniphier_tm_irq_clear(tdev);
243
244 return IRQ_WAKE_THREAD;
245}
246
247static irqreturn_t uniphier_tm_alarm_irq_thread(int irq, void *_tdev)
248{
249 struct uniphier_tm_dev *tdev = _tdev;
250
251 thermal_zone_device_update(tdev->tz_dev, THERMAL_EVENT_UNSPECIFIED);
252
253 return IRQ_HANDLED;
254}
255
256static int uniphier_tm_probe(struct platform_device *pdev)
257{
258 struct device *dev = &pdev->dev;
259 struct regmap *regmap;
260 struct device_node *parent;
261 struct uniphier_tm_dev *tdev;
262 const struct thermal_trip *trips;
263 int i, ret, irq, ntrips, crit_temp = INT_MAX;
264
265 tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
266 if (!tdev)
267 return -ENOMEM;
268 tdev->dev = dev;
269
270 tdev->data = of_device_get_match_data(dev);
271 if (WARN_ON(!tdev->data))
272 return -EINVAL;
273
274 irq = platform_get_irq(pdev, 0);
275 if (irq < 0)
276 return irq;
277
278 /* get regmap from syscon node */
279 parent = of_get_parent(dev->of_node); /* parent should be syscon node */
280 regmap = syscon_node_to_regmap(parent);
281 of_node_put(parent);
282 if (IS_ERR(regmap)) {
283 dev_err(dev, "failed to get regmap (error %ld)\n",
284 PTR_ERR(regmap));
285 return PTR_ERR(regmap);
286 }
287 tdev->regmap = regmap;
288
289 ret = uniphier_tm_initialize_sensor(tdev);
290 if (ret) {
291 dev_err(dev, "failed to initialize sensor\n");
292 return ret;
293 }
294
295 ret = devm_request_threaded_irq(dev, irq, uniphier_tm_alarm_irq,
296 uniphier_tm_alarm_irq_thread,
297 0, "thermal", tdev);
298 if (ret)
299 return ret;
300
301 platform_set_drvdata(pdev, tdev);
302
303 tdev->tz_dev = devm_thermal_zone_of_sensor_register(dev, 0, tdev,
304 &uniphier_of_thermal_ops);
305 if (IS_ERR(tdev->tz_dev)) {
306 dev_err(dev, "failed to register sensor device\n");
307 return PTR_ERR(tdev->tz_dev);
308 }
309
310 /* get trip points */
311 trips = of_thermal_get_trip_points(tdev->tz_dev);
312 ntrips = of_thermal_get_ntrips(tdev->tz_dev);
313 if (ntrips > ALERT_CH_NUM) {
314 dev_err(dev, "thermal zone has too many trips\n");
315 return -E2BIG;
316 }
317
318 /* set alert temperatures */
319 for (i = 0; i < ntrips; i++) {
320 if (trips[i].type == THERMAL_TRIP_CRITICAL &&
321 trips[i].temperature < crit_temp)
322 crit_temp = trips[i].temperature;
323 uniphier_tm_set_alert(tdev, i, trips[i].temperature);
324 tdev->alert_en[i] = true;
325 }
326 if (crit_temp > CRITICAL_TEMP_LIMIT) {
327 dev_err(dev, "critical trip is over limit(>%d), or not set\n",
328 CRITICAL_TEMP_LIMIT);
329 return -EINVAL;
330 }
331
332 uniphier_tm_enable_sensor(tdev);
333
334 return 0;
335}
336
337static int uniphier_tm_remove(struct platform_device *pdev)
338{
339 struct uniphier_tm_dev *tdev = platform_get_drvdata(pdev);
340
341 /* disable sensor */
342 uniphier_tm_disable_sensor(tdev);
343
344 return 0;
345}
346
347static const struct uniphier_tm_soc_data uniphier_pxs2_tm_data = {
348 .map_base = 0xe000,
349 .block_base = 0xe000,
350 .tmod_setup_addr = 0xe904,
351};
352
353static const struct uniphier_tm_soc_data uniphier_ld20_tm_data = {
354 .map_base = 0xe000,
355 .block_base = 0xe800,
356 .tmod_setup_addr = 0xe938,
357};
358
359static const struct of_device_id uniphier_tm_dt_ids[] = {
360 {
361 .compatible = "socionext,uniphier-pxs2-thermal",
362 .data = &uniphier_pxs2_tm_data,
363 },
364 {
365 .compatible = "socionext,uniphier-ld20-thermal",
366 .data = &uniphier_ld20_tm_data,
367 },
368 { /* sentinel */ }
369};
370MODULE_DEVICE_TABLE(of, uniphier_tm_dt_ids);
371
372static struct platform_driver uniphier_tm_driver = {
373 .probe = uniphier_tm_probe,
374 .remove = uniphier_tm_remove,
375 .driver = {
376 .name = "uniphier-thermal",
377 .of_match_table = uniphier_tm_dt_ids,
378 },
379};
380module_platform_driver(uniphier_tm_driver);
381
382MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>");
383MODULE_DESCRIPTION("UniPhier thermal driver");
384MODULE_LICENSE("GPL v2");