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