aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Norris <computersforpeace@gmail.com>2017-09-26 17:27:59 -0400
committerEduardo Valentin <edubezval@gmail.com>2017-10-31 22:32:19 -0400
commit9e03cf1b2dd54733d0d2c5811b78257d78562c03 (patch)
tree1b0518e9c25c66e6e18593937a072ffbfdeaff75
parentb590c51c9b956f465acc73a2864d3a1444c76c3b (diff)
thermal: add brcmstb AVS TMON driver
The AVS TMON core provides temperature readings, a pair of configurable high- and low-temperature threshold interrupts, and an emergency over-temperature chip reset. The driver utilizes the first two to provide temperature readings and high-temperature notifications to applications. The over-temperature reset is not exposed to applications; this reset threshold is critical to the system and should be set with care within the bootloader. Applications may choose to utilize the notification mechanism, the temperature reading mechanism (e.g., through polling), or both. Signed-off-by: Brian Norris <computersforpeace@gmail.com> Signed-off-by: Doug Berger <opendmb@gmail.com> Signed-off-by: Markus Mayer <mmayer@broadcom.com> Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
-rw-r--r--drivers/thermal/Kconfig2
-rw-r--r--drivers/thermal/broadcom/Kconfig7
-rw-r--r--drivers/thermal/broadcom/Makefile1
-rw-r--r--drivers/thermal/broadcom/brcmstb_thermal.c387
4 files changed, 396 insertions, 1 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index e3f0d1fd1720..c70f6bfd9e85 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -408,7 +408,7 @@ config MTK_THERMAL
408 controller present in Mediatek SoCs 408 controller present in Mediatek SoCs
409 409
410menu "Broadcom thermal drivers" 410menu "Broadcom thermal drivers"
411depends on ARCH_BCM || ARCH_BCM2835 || COMPILE_TEST 411depends on ARCH_BCM || ARCH_BRCMSTB || ARCH_BCM2835 || COMPILE_TEST
412source "drivers/thermal/broadcom/Kconfig" 412source "drivers/thermal/broadcom/Kconfig"
413endmenu 413endmenu
414 414
diff --git a/drivers/thermal/broadcom/Kconfig b/drivers/thermal/broadcom/Kconfig
index 42c098e86f84..c106a15bf7f9 100644
--- a/drivers/thermal/broadcom/Kconfig
+++ b/drivers/thermal/broadcom/Kconfig
@@ -6,6 +6,13 @@ config BCM2835_THERMAL
6 help 6 help
7 Support for thermal sensors on Broadcom bcm2835 SoCs. 7 Support for thermal sensors on Broadcom bcm2835 SoCs.
8 8
9config BRCMSTB_THERMAL
10 tristate "Broadcom STB AVS TMON thermal driver"
11 depends on ARCH_BRCMSTB || COMPILE_TEST
12 help
13 Enable this driver if you have a Broadcom STB SoC and would like
14 thermal framework support.
15
9config BCM_NS_THERMAL 16config BCM_NS_THERMAL
10 tristate "Northstar thermal driver" 17 tristate "Northstar thermal driver"
11 depends on ARCH_BCM_IPROC || COMPILE_TEST 18 depends on ARCH_BCM_IPROC || COMPILE_TEST
diff --git a/drivers/thermal/broadcom/Makefile b/drivers/thermal/broadcom/Makefile
index c6f62e4fd0ee..fae10ecafaef 100644
--- a/drivers/thermal/broadcom/Makefile
+++ b/drivers/thermal/broadcom/Makefile
@@ -1,2 +1,3 @@
1obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o 1obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o
2obj-$(CONFIG_BRCMSTB_THERMAL) += brcmstb_thermal.o
2obj-$(CONFIG_BCM_NS_THERMAL) += ns-thermal.o 3obj-$(CONFIG_BCM_NS_THERMAL) += ns-thermal.o
diff --git a/drivers/thermal/broadcom/brcmstb_thermal.c b/drivers/thermal/broadcom/brcmstb_thermal.c
new file mode 100644
index 000000000000..1919f91fa756
--- /dev/null
+++ b/drivers/thermal/broadcom/brcmstb_thermal.c
@@ -0,0 +1,387 @@
1/*
2 * Broadcom STB AVS TMON thermal sensor driver
3 *
4 * Copyright (c) 2015-2017 Broadcom
5 *
6 * This software is licensed under the terms of the GNU General Public
7 * License version 2, as published by the Free Software Foundation, and
8 * may be copied, distributed, and modified under those terms.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 */
16
17#define DRV_NAME "brcmstb_thermal"
18
19#define pr_fmt(fmt) DRV_NAME ": " fmt
20
21#include <linux/bitops.h>
22#include <linux/device.h>
23#include <linux/err.h>
24#include <linux/io.h>
25#include <linux/irqreturn.h>
26#include <linux/interrupt.h>
27#include <linux/kernel.h>
28#include <linux/module.h>
29#include <linux/platform_device.h>
30#include <linux/of_device.h>
31#include <linux/thermal.h>
32
33#define AVS_TMON_STATUS 0x00
34 #define AVS_TMON_STATUS_valid_msk BIT(11)
35 #define AVS_TMON_STATUS_data_msk GENMASK(10, 1)
36 #define AVS_TMON_STATUS_data_shift 1
37
38#define AVS_TMON_EN_OVERTEMP_RESET 0x04
39 #define AVS_TMON_EN_OVERTEMP_RESET_msk BIT(0)
40
41#define AVS_TMON_RESET_THRESH 0x08
42 #define AVS_TMON_RESET_THRESH_msk GENMASK(10, 1)
43 #define AVS_TMON_RESET_THRESH_shift 1
44
45#define AVS_TMON_INT_IDLE_TIME 0x10
46
47#define AVS_TMON_EN_TEMP_INT_SRCS 0x14
48 #define AVS_TMON_EN_TEMP_INT_SRCS_high BIT(1)
49 #define AVS_TMON_EN_TEMP_INT_SRCS_low BIT(0)
50
51#define AVS_TMON_INT_THRESH 0x18
52 #define AVS_TMON_INT_THRESH_high_msk GENMASK(26, 17)
53 #define AVS_TMON_INT_THRESH_high_shift 17
54 #define AVS_TMON_INT_THRESH_low_msk GENMASK(10, 1)
55 #define AVS_TMON_INT_THRESH_low_shift 1
56
57#define AVS_TMON_TEMP_INT_CODE 0x1c
58#define AVS_TMON_TP_TEST_ENABLE 0x20
59
60/* Default coefficients */
61#define AVS_TMON_TEMP_SLOPE -487
62#define AVS_TMON_TEMP_OFFSET 410040
63
64/* HW related temperature constants */
65#define AVS_TMON_TEMP_MAX 0x3ff
66#define AVS_TMON_TEMP_MIN -88161
67#define AVS_TMON_TEMP_MASK AVS_TMON_TEMP_MAX
68
69enum avs_tmon_trip_type {
70 TMON_TRIP_TYPE_LOW = 0,
71 TMON_TRIP_TYPE_HIGH,
72 TMON_TRIP_TYPE_RESET,
73 TMON_TRIP_TYPE_MAX,
74};
75
76struct avs_tmon_trip {
77 /* HW bit to enable the trip */
78 u32 enable_offs;
79 u32 enable_mask;
80
81 /* HW field to read the trip temperature */
82 u32 reg_offs;
83 u32 reg_msk;
84 int reg_shift;
85};
86
87static struct avs_tmon_trip avs_tmon_trips[] = {
88 /* Trips when temperature is below threshold */
89 [TMON_TRIP_TYPE_LOW] = {
90 .enable_offs = AVS_TMON_EN_TEMP_INT_SRCS,
91 .enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_low,
92 .reg_offs = AVS_TMON_INT_THRESH,
93 .reg_msk = AVS_TMON_INT_THRESH_low_msk,
94 .reg_shift = AVS_TMON_INT_THRESH_low_shift,
95 },
96 /* Trips when temperature is above threshold */
97 [TMON_TRIP_TYPE_HIGH] = {
98 .enable_offs = AVS_TMON_EN_TEMP_INT_SRCS,
99 .enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_high,
100 .reg_offs = AVS_TMON_INT_THRESH,
101 .reg_msk = AVS_TMON_INT_THRESH_high_msk,
102 .reg_shift = AVS_TMON_INT_THRESH_high_shift,
103 },
104 /* Automatically resets chip when above threshold */
105 [TMON_TRIP_TYPE_RESET] = {
106 .enable_offs = AVS_TMON_EN_OVERTEMP_RESET,
107 .enable_mask = AVS_TMON_EN_OVERTEMP_RESET_msk,
108 .reg_offs = AVS_TMON_RESET_THRESH,
109 .reg_msk = AVS_TMON_RESET_THRESH_msk,
110 .reg_shift = AVS_TMON_RESET_THRESH_shift,
111 },
112};
113
114struct brcmstb_thermal_priv {
115 void __iomem *tmon_base;
116 struct device *dev;
117 struct thermal_zone_device *thermal;
118};
119
120static void avs_tmon_get_coeffs(struct thermal_zone_device *tz, int *slope,
121 int *offset)
122{
123 *slope = thermal_zone_get_slope(tz);
124 *offset = thermal_zone_get_offset(tz);
125}
126
127/* Convert a HW code to a temperature reading (millidegree celsius) */
128static inline int avs_tmon_code_to_temp(struct thermal_zone_device *tz,
129 u32 code)
130{
131 const int val = code & AVS_TMON_TEMP_MASK;
132 int slope, offset;
133
134 avs_tmon_get_coeffs(tz, &slope, &offset);
135
136 return slope * val + offset;
137}
138
139/*
140 * Convert a temperature value (millidegree celsius) to a HW code
141 *
142 * @temp: temperature to convert
143 * @low: if true, round toward the low side
144 */
145static inline u32 avs_tmon_temp_to_code(struct thermal_zone_device *tz,
146 int temp, bool low)
147{
148 int slope, offset;
149
150 if (temp < AVS_TMON_TEMP_MIN)
151 return AVS_TMON_TEMP_MAX; /* Maximum code value */
152
153 avs_tmon_get_coeffs(tz, &slope, &offset);
154
155 if (temp >= offset)
156 return 0; /* Minimum code value */
157
158 if (low)
159 return (u32)(DIV_ROUND_UP(offset - temp, abs(slope)));
160 else
161 return (u32)((offset - temp) / abs(slope));
162}
163
164static int brcmstb_get_temp(void *data, int *temp)
165{
166 struct brcmstb_thermal_priv *priv = data;
167 u32 val;
168 long t;
169
170 val = __raw_readl(priv->tmon_base + AVS_TMON_STATUS);
171
172 if (!(val & AVS_TMON_STATUS_valid_msk)) {
173 dev_err(priv->dev, "reading not valid\n");
174 return -EIO;
175 }
176
177 val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift;
178
179 t = avs_tmon_code_to_temp(priv->thermal, val);
180 if (t < 0)
181 *temp = 0;
182 else
183 *temp = t;
184
185 return 0;
186}
187
188static void avs_tmon_trip_enable(struct brcmstb_thermal_priv *priv,
189 enum avs_tmon_trip_type type, int en)
190{
191 struct avs_tmon_trip *trip = &avs_tmon_trips[type];
192 u32 val = __raw_readl(priv->tmon_base + trip->enable_offs);
193
194 dev_dbg(priv->dev, "%sable trip, type %d\n", en ? "en" : "dis", type);
195
196 if (en)
197 val |= trip->enable_mask;
198 else
199 val &= ~trip->enable_mask;
200
201 __raw_writel(val, priv->tmon_base + trip->enable_offs);
202}
203
204static int avs_tmon_get_trip_temp(struct brcmstb_thermal_priv *priv,
205 enum avs_tmon_trip_type type)
206{
207 struct avs_tmon_trip *trip = &avs_tmon_trips[type];
208 u32 val = __raw_readl(priv->tmon_base + trip->reg_offs);
209
210 val &= trip->reg_msk;
211 val >>= trip->reg_shift;
212
213 return avs_tmon_code_to_temp(priv->thermal, val);
214}
215
216static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv *priv,
217 enum avs_tmon_trip_type type,
218 int temp)
219{
220 struct avs_tmon_trip *trip = &avs_tmon_trips[type];
221 u32 val, orig;
222
223 dev_dbg(priv->dev, "set temp %d to %d\n", type, temp);
224
225 /* round toward low temp for the low interrupt */
226 val = avs_tmon_temp_to_code(priv->thermal, temp,
227 type == TMON_TRIP_TYPE_LOW);
228
229 val <<= trip->reg_shift;
230 val &= trip->reg_msk;
231
232 orig = __raw_readl(priv->tmon_base + trip->reg_offs);
233 orig &= ~trip->reg_msk;
234 orig |= val;
235 __raw_writel(orig, priv->tmon_base + trip->reg_offs);
236}
237
238static int avs_tmon_get_intr_temp(struct brcmstb_thermal_priv *priv)
239{
240 u32 val;
241
242 val = __raw_readl(priv->tmon_base + AVS_TMON_TEMP_INT_CODE);
243 return avs_tmon_code_to_temp(priv->thermal, val);
244}
245
246static irqreturn_t brcmstb_tmon_irq_thread(int irq, void *data)
247{
248 struct brcmstb_thermal_priv *priv = data;
249 int low, high, intr;
250
251 low = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_LOW);
252 high = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_HIGH);
253 intr = avs_tmon_get_intr_temp(priv);
254
255 dev_dbg(priv->dev, "low/intr/high: %d/%d/%d\n",
256 low, intr, high);
257
258 /* Disable high-temp until next threshold shift */
259 if (intr >= high)
260 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);
261 /* Disable low-temp until next threshold shift */
262 if (intr <= low)
263 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);
264
265 /*
266 * Notify using the interrupt temperature, in case the temperature
267 * changes before it can next be read out
268 */
269 thermal_zone_device_update(priv->thermal, intr);
270
271 return IRQ_HANDLED;
272}
273
274static int brcmstb_set_trips(void *data, int low, int high)
275{
276 struct brcmstb_thermal_priv *priv = data;
277
278 dev_dbg(priv->dev, "set trips %d <--> %d\n", low, high);
279
280 /*
281 * Disable low-temp if "low" is too small. As per thermal framework
282 * API, we use -INT_MAX rather than INT_MIN.
283 */
284 if (low <= -INT_MAX) {
285 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);
286 } else {
287 avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_LOW, low);
288 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 1);
289 }
290
291 /* Disable high-temp if "high" is too big. */
292 if (high == INT_MAX) {
293 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);
294 } else {
295 avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_HIGH, high);
296 avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 1);
297 }
298
299 return 0;
300}
301
302static struct thermal_zone_of_device_ops of_ops = {
303 .get_temp = brcmstb_get_temp,
304 .set_trips = brcmstb_set_trips,
305};
306
307static const struct of_device_id brcmstb_thermal_id_table[] = {
308 { .compatible = "brcm,avs-tmon" },
309 {},
310};
311MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table);
312
313static int brcmstb_thermal_probe(struct platform_device *pdev)
314{
315 struct thermal_zone_device *thermal;
316 struct brcmstb_thermal_priv *priv;
317 struct resource *res;
318 int irq, ret;
319
320 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
321 if (!priv)
322 return -ENOMEM;
323
324 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
325 priv->tmon_base = devm_ioremap_resource(&pdev->dev, res);
326 if (IS_ERR(priv->tmon_base))
327 return PTR_ERR(priv->tmon_base);
328
329 priv->dev = &pdev->dev;
330 platform_set_drvdata(pdev, priv);
331
332 thermal = thermal_zone_of_sensor_register(&pdev->dev, 0, priv, &of_ops);
333 if (IS_ERR(thermal)) {
334 ret = PTR_ERR(thermal);
335 dev_err(&pdev->dev, "could not register sensor: %d\n", ret);
336 return ret;
337 }
338
339 priv->thermal = thermal;
340
341 irq = platform_get_irq(pdev, 0);
342 if (irq < 0) {
343 dev_err(&pdev->dev, "could not get IRQ\n");
344 ret = irq;
345 goto err;
346 }
347 ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
348 brcmstb_tmon_irq_thread, IRQF_ONESHOT,
349 DRV_NAME, priv);
350 if (ret < 0) {
351 dev_err(&pdev->dev, "could not request IRQ: %d\n", ret);
352 goto err;
353 }
354
355 dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n");
356
357 return 0;
358
359err:
360 thermal_zone_of_sensor_unregister(&pdev->dev, thermal);
361 return ret;
362}
363
364static int brcmstb_thermal_exit(struct platform_device *pdev)
365{
366 struct brcmstb_thermal_priv *priv = platform_get_drvdata(pdev);
367 struct thermal_zone_device *thermal = priv->thermal;
368
369 if (thermal)
370 thermal_zone_of_sensor_unregister(&pdev->dev, priv->thermal);
371
372 return 0;
373}
374
375static struct platform_driver brcmstb_thermal_driver = {
376 .probe = brcmstb_thermal_probe,
377 .remove = brcmstb_thermal_exit,
378 .driver = {
379 .name = DRV_NAME,
380 .of_match_table = brcmstb_thermal_id_table,
381 },
382};
383module_platform_driver(brcmstb_thermal_driver);
384
385MODULE_LICENSE("GPL v2");
386MODULE_AUTHOR("Brian Norris");
387MODULE_DESCRIPTION("Broadcom STB AVS TMON thermal driver");