Skip to content

Commit bcb7dd9

Browse files
lategoodbyeEduardo Valentin
authored andcommitted
thermal: bcm2835: add thermal driver for bcm2835 SoC
Add basic thermal driver for bcm2835 SoC. This driver currently make sure that tsense HW block is set up correctly. Tested-by: Rafał Miłecki <[email protected]> Signed-off-by: Martin Sperl <[email protected]> Signed-off-by: Stefan Wahren <[email protected]> Acked-by: Eric Anholt <[email protected]> Acked-by: Eduardo Valentin <[email protected]> Signed-off-by: Eduardo Valentin <[email protected]>
1 parent 1e2ac98 commit bcb7dd9

File tree

3 files changed

+323
-0
lines changed

3 files changed

+323
-0
lines changed

drivers/thermal/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,4 +453,12 @@ config ZX2967_THERMAL
453453
the primitive temperature sensor embedded in zx2967 SoCs.
454454
This sensor generates the real time die temperature.
455455

456+
config BCM2835_THERMAL
457+
tristate "Thermal sensors on bcm2835 SoC"
458+
depends on ARCH_BCM2835 || COMPILE_TEST
459+
depends on HAS_IOMEM
460+
depends on THERMAL_OF
461+
help
462+
Support for thermal sensors on Broadcom bcm2835 SoCs.
463+
456464
endif

drivers/thermal/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,4 @@ obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
5858
obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
5959
obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
6060
obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o
61+
obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o

drivers/thermal/bcm2835_thermal.c

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
/*
2+
* Driver for Broadcom BCM2835 SoC temperature sensor
3+
*
4+
* Copyright (C) 2016 Martin Sperl
5+
*
6+
* This program is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 2 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*/
16+
17+
#include <linux/clk.h>
18+
#include <linux/debugfs.h>
19+
#include <linux/device.h>
20+
#include <linux/err.h>
21+
#include <linux/io.h>
22+
#include <linux/kernel.h>
23+
#include <linux/module.h>
24+
#include <linux/of.h>
25+
#include <linux/of_address.h>
26+
#include <linux/of_device.h>
27+
#include <linux/platform_device.h>
28+
#include <linux/thermal.h>
29+
30+
#define BCM2835_TS_TSENSCTL 0x00
31+
#define BCM2835_TS_TSENSSTAT 0x04
32+
33+
#define BCM2835_TS_TSENSCTL_PRWDW BIT(0)
34+
#define BCM2835_TS_TSENSCTL_RSTB BIT(1)
35+
36+
/*
37+
* bandgap reference voltage in 6 mV increments
38+
* 000b = 1178 mV, 001b = 1184 mV, ... 111b = 1220 mV
39+
*/
40+
#define BCM2835_TS_TSENSCTL_CTRL_BITS 3
41+
#define BCM2835_TS_TSENSCTL_CTRL_SHIFT 2
42+
#define BCM2835_TS_TSENSCTL_CTRL_MASK \
43+
GENMASK(BCM2835_TS_TSENSCTL_CTRL_BITS + \
44+
BCM2835_TS_TSENSCTL_CTRL_SHIFT - 1, \
45+
BCM2835_TS_TSENSCTL_CTRL_SHIFT)
46+
#define BCM2835_TS_TSENSCTL_CTRL_DEFAULT 1
47+
#define BCM2835_TS_TSENSCTL_EN_INT BIT(5)
48+
#define BCM2835_TS_TSENSCTL_DIRECT BIT(6)
49+
#define BCM2835_TS_TSENSCTL_CLR_INT BIT(7)
50+
#define BCM2835_TS_TSENSCTL_THOLD_SHIFT 8
51+
#define BCM2835_TS_TSENSCTL_THOLD_BITS 10
52+
#define BCM2835_TS_TSENSCTL_THOLD_MASK \
53+
GENMASK(BCM2835_TS_TSENSCTL_THOLD_BITS + \
54+
BCM2835_TS_TSENSCTL_THOLD_SHIFT - 1, \
55+
BCM2835_TS_TSENSCTL_THOLD_SHIFT)
56+
/*
57+
* time how long the block to be asserted in reset
58+
* which based on a clock counter (TSENS clock assumed)
59+
*/
60+
#define BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT 18
61+
#define BCM2835_TS_TSENSCTL_RSTDELAY_BITS 8
62+
#define BCM2835_TS_TSENSCTL_REGULEN BIT(26)
63+
64+
#define BCM2835_TS_TSENSSTAT_DATA_BITS 10
65+
#define BCM2835_TS_TSENSSTAT_DATA_SHIFT 0
66+
#define BCM2835_TS_TSENSSTAT_DATA_MASK \
67+
GENMASK(BCM2835_TS_TSENSSTAT_DATA_BITS + \
68+
BCM2835_TS_TSENSSTAT_DATA_SHIFT - 1, \
69+
BCM2835_TS_TSENSSTAT_DATA_SHIFT)
70+
#define BCM2835_TS_TSENSSTAT_VALID BIT(10)
71+
#define BCM2835_TS_TSENSSTAT_INTERRUPT BIT(11)
72+
73+
struct bcm2835_thermal_data {
74+
struct thermal_zone_device *tz;
75+
void __iomem *regs;
76+
struct clk *clk;
77+
struct dentry *debugfsdir;
78+
};
79+
80+
static int bcm2835_thermal_adc2temp(u32 adc, int offset, int slope)
81+
{
82+
return offset + slope * adc;
83+
}
84+
85+
static int bcm2835_thermal_temp2adc(int temp, int offset, int slope)
86+
{
87+
temp -= offset;
88+
temp /= slope;
89+
90+
if (temp < 0)
91+
temp = 0;
92+
if (temp >= BIT(BCM2835_TS_TSENSSTAT_DATA_BITS))
93+
temp = BIT(BCM2835_TS_TSENSSTAT_DATA_BITS) - 1;
94+
95+
return temp;
96+
}
97+
98+
static int bcm2835_thermal_get_temp(void *d, int *temp)
99+
{
100+
struct bcm2835_thermal_data *data = d;
101+
u32 val = readl(data->regs + BCM2835_TS_TSENSSTAT);
102+
103+
if (!(val & BCM2835_TS_TSENSSTAT_VALID))
104+
return -EIO;
105+
106+
val &= BCM2835_TS_TSENSSTAT_DATA_MASK;
107+
108+
*temp = bcm2835_thermal_adc2temp(
109+
val,
110+
thermal_zone_get_offset(data->tz),
111+
thermal_zone_get_slope(data->tz));
112+
113+
return 0;
114+
}
115+
116+
static const struct debugfs_reg32 bcm2835_thermal_regs[] = {
117+
{
118+
.name = "ctl",
119+
.offset = 0
120+
},
121+
{
122+
.name = "stat",
123+
.offset = 4
124+
}
125+
};
126+
127+
static void bcm2835_thermal_debugfs(struct platform_device *pdev)
128+
{
129+
struct thermal_zone_device *tz = platform_get_drvdata(pdev);
130+
struct bcm2835_thermal_data *data = tz->devdata;
131+
struct debugfs_regset32 *regset;
132+
133+
data->debugfsdir = debugfs_create_dir("bcm2835_thermal", NULL);
134+
if (!data->debugfsdir)
135+
return;
136+
137+
regset = devm_kzalloc(&pdev->dev, sizeof(*regset), GFP_KERNEL);
138+
if (!regset)
139+
return;
140+
141+
regset->regs = bcm2835_thermal_regs;
142+
regset->nregs = ARRAY_SIZE(bcm2835_thermal_regs);
143+
regset->base = data->regs;
144+
145+
debugfs_create_regset32("regset", 0444, data->debugfsdir, regset);
146+
}
147+
148+
static struct thermal_zone_of_device_ops bcm2835_thermal_ops = {
149+
.get_temp = bcm2835_thermal_get_temp,
150+
};
151+
152+
/*
153+
* Note: as per Raspberry Foundation FAQ
154+
* (https://www.raspberrypi.org/help/faqs/#performanceOperatingTemperature)
155+
* the recommended temperature range for the SoC -40C to +85C
156+
* so the trip limit is set to 80C.
157+
* this applies to all the BCM283X SoC
158+
*/
159+
160+
static const struct of_device_id bcm2835_thermal_of_match_table[] = {
161+
{
162+
.compatible = "brcm,bcm2835-thermal",
163+
},
164+
{
165+
.compatible = "brcm,bcm2836-thermal",
166+
},
167+
{
168+
.compatible = "brcm,bcm2837-thermal",
169+
},
170+
{},
171+
};
172+
MODULE_DEVICE_TABLE(of, bcm2835_thermal_of_match_table);
173+
174+
static int bcm2835_thermal_probe(struct platform_device *pdev)
175+
{
176+
const struct of_device_id *match;
177+
struct thermal_zone_device *tz;
178+
struct bcm2835_thermal_data *data;
179+
struct resource *res;
180+
int err = 0;
181+
u32 val;
182+
unsigned long rate;
183+
184+
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
185+
if (!data)
186+
return -ENOMEM;
187+
188+
match = of_match_device(bcm2835_thermal_of_match_table,
189+
&pdev->dev);
190+
if (!match)
191+
return -EINVAL;
192+
193+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
194+
data->regs = devm_ioremap_resource(&pdev->dev, res);
195+
if (IS_ERR(data->regs)) {
196+
err = PTR_ERR(data->regs);
197+
dev_err(&pdev->dev, "Could not get registers: %d\n", err);
198+
return err;
199+
}
200+
201+
data->clk = devm_clk_get(&pdev->dev, NULL);
202+
if (IS_ERR(data->clk)) {
203+
err = PTR_ERR(data->clk);
204+
if (err != -EPROBE_DEFER)
205+
dev_err(&pdev->dev, "Could not get clk: %d\n", err);
206+
return err;
207+
}
208+
209+
err = clk_prepare_enable(data->clk);
210+
if (err)
211+
return err;
212+
213+
rate = clk_get_rate(data->clk);
214+
if ((rate < 1920000) || (rate > 5000000))
215+
dev_warn(&pdev->dev,
216+
"Clock %pCn running at %pCr Hz is outside of the recommended range: 1.92 to 5MHz\n",
217+
data->clk, data->clk);
218+
219+
/* register of thermal sensor and get info from DT */
220+
tz = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
221+
&bcm2835_thermal_ops);
222+
if (IS_ERR(tz)) {
223+
err = PTR_ERR(tz);
224+
dev_err(&pdev->dev,
225+
"Failed to register the thermal device: %d\n",
226+
err);
227+
goto err_clk;
228+
}
229+
230+
/*
231+
* right now the FW does set up the HW-block, so we are not
232+
* touching the configuration registers.
233+
* But if the HW is not enabled, then set it up
234+
* using "sane" values used by the firmware right now.
235+
*/
236+
val = readl(data->regs + BCM2835_TS_TSENSCTL);
237+
if (!(val & BCM2835_TS_TSENSCTL_RSTB)) {
238+
int trip_temp, offset, slope;
239+
240+
slope = thermal_zone_get_slope(tz);
241+
offset = thermal_zone_get_offset(tz);
242+
/*
243+
* For now we deal only with critical, otherwise
244+
* would need to iterate
245+
*/
246+
err = tz->ops->get_trip_temp(tz, 0, &trip_temp);
247+
if (err < 0) {
248+
err = PTR_ERR(tz);
249+
dev_err(&pdev->dev,
250+
"Not able to read trip_temp: %d\n",
251+
err);
252+
goto err_tz;
253+
}
254+
255+
/* set bandgap reference voltage and enable voltage regulator */
256+
val = (BCM2835_TS_TSENSCTL_CTRL_DEFAULT <<
257+
BCM2835_TS_TSENSCTL_CTRL_SHIFT) |
258+
BCM2835_TS_TSENSCTL_REGULEN;
259+
260+
/* use the recommended reset duration */
261+
val |= (0xFE << BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT);
262+
263+
/* trip_adc value from info */
264+
val |= bcm2835_thermal_temp2adc(trip_temp,
265+
offset,
266+
slope)
267+
<< BCM2835_TS_TSENSCTL_THOLD_SHIFT;
268+
269+
/* write the value back to the register as 2 steps */
270+
writel(val, data->regs + BCM2835_TS_TSENSCTL);
271+
val |= BCM2835_TS_TSENSCTL_RSTB;
272+
writel(val, data->regs + BCM2835_TS_TSENSCTL);
273+
}
274+
275+
data->tz = tz;
276+
277+
platform_set_drvdata(pdev, tz);
278+
279+
bcm2835_thermal_debugfs(pdev);
280+
281+
return 0;
282+
err_tz:
283+
thermal_zone_of_sensor_unregister(&pdev->dev, tz);
284+
err_clk:
285+
clk_disable_unprepare(data->clk);
286+
287+
return err;
288+
}
289+
290+
static int bcm2835_thermal_remove(struct platform_device *pdev)
291+
{
292+
struct thermal_zone_device *tz = platform_get_drvdata(pdev);
293+
struct bcm2835_thermal_data *data = tz->devdata;
294+
295+
debugfs_remove_recursive(data->debugfsdir);
296+
thermal_zone_of_sensor_unregister(&pdev->dev, tz);
297+
clk_disable_unprepare(data->clk);
298+
299+
return 0;
300+
}
301+
302+
static struct platform_driver bcm2835_thermal_driver = {
303+
.probe = bcm2835_thermal_probe,
304+
.remove = bcm2835_thermal_remove,
305+
.driver = {
306+
.name = "bcm2835_thermal",
307+
.of_match_table = bcm2835_thermal_of_match_table,
308+
},
309+
};
310+
module_platform_driver(bcm2835_thermal_driver);
311+
312+
MODULE_AUTHOR("Martin Sperl");
313+
MODULE_DESCRIPTION("Thermal driver for bcm2835 chip");
314+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)