1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Marvell EBU Armada SoCs thermal sensor driver |
4 | * |
5 | * Copyright (C) 2013 Marvell |
6 | */ |
7 | #include <linux/device.h> |
8 | #include <linux/err.h> |
9 | #include <linux/io.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/of.h> |
12 | #include <linux/module.h> |
13 | #include <linux/delay.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/of_device.h> |
16 | #include <linux/thermal.h> |
17 | #include <linux/iopoll.h> |
18 | #include <linux/mfd/syscon.h> |
19 | #include <linux/regmap.h> |
20 | #include <linux/interrupt.h> |
21 | |
22 | /* Thermal Manager Control and Status Register */ |
23 | #define PMU_TDC0_SW_RST_MASK (0x1 << 1) |
24 | #define PMU_TM_DISABLE_OFFS 0 |
25 | #define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS) |
26 | #define PMU_TDC0_REF_CAL_CNT_OFFS 11 |
27 | #define PMU_TDC0_REF_CAL_CNT_MASK (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS) |
28 | #define PMU_TDC0_OTF_CAL_MASK (0x1 << 30) |
29 | #define PMU_TDC0_START_CAL_MASK (0x1 << 25) |
30 | |
31 | #define A375_UNIT_CONTROL_SHIFT 27 |
32 | #define A375_UNIT_CONTROL_MASK 0x7 |
33 | #define A375_READOUT_INVERT BIT(15) |
34 | #define A375_HW_RESETn BIT(8) |
35 | |
36 | /* Errata fields */ |
37 | #define CONTROL0_TSEN_TC_TRIM_MASK 0x7 |
38 | #define CONTROL0_TSEN_TC_TRIM_VAL 0x3 |
39 | |
40 | #define CONTROL0_TSEN_START BIT(0) |
41 | #define CONTROL0_TSEN_RESET BIT(1) |
42 | #define CONTROL0_TSEN_ENABLE BIT(2) |
43 | #define CONTROL0_TSEN_AVG_BYPASS BIT(6) |
44 | #define CONTROL0_TSEN_CHAN_SHIFT 13 |
45 | #define CONTROL0_TSEN_CHAN_MASK 0xF |
46 | #define CONTROL0_TSEN_OSR_SHIFT 24 |
47 | #define CONTROL0_TSEN_OSR_MAX 0x3 |
48 | #define CONTROL0_TSEN_MODE_SHIFT 30 |
49 | #define CONTROL0_TSEN_MODE_EXTERNAL 0x2 |
50 | #define CONTROL0_TSEN_MODE_MASK 0x3 |
51 | |
52 | #define CONTROL1_TSEN_AVG_MASK 0x7 |
53 | #define CONTROL1_EXT_TSEN_SW_RESET BIT(7) |
54 | #define CONTROL1_EXT_TSEN_HW_RESETn BIT(8) |
55 | #define CONTROL1_TSEN_INT_EN BIT(25) |
56 | #define CONTROL1_TSEN_SELECT_OFF 21 |
57 | #define CONTROL1_TSEN_SELECT_MASK 0x3 |
58 | |
59 | #define STATUS_POLL_PERIOD_US 1000 |
60 | #define STATUS_POLL_TIMEOUT_US 100000 |
61 | #define OVERHEAT_INT_POLL_DELAY_MS 1000 |
62 | |
63 | struct armada_thermal_data; |
64 | |
65 | /* Marvell EBU Thermal Sensor Dev Structure */ |
66 | struct armada_thermal_priv { |
67 | struct device *dev; |
68 | struct regmap *syscon; |
69 | char zone_name[THERMAL_NAME_LENGTH]; |
70 | /* serialize temperature reads/updates */ |
71 | struct mutex update_lock; |
72 | struct armada_thermal_data *data; |
73 | struct thermal_zone_device *overheat_sensor; |
74 | int interrupt_source; |
75 | int current_channel; |
76 | long current_threshold; |
77 | long current_hysteresis; |
78 | }; |
79 | |
80 | struct armada_thermal_data { |
81 | /* Initialize the thermal IC */ |
82 | void (*init)(struct platform_device *pdev, |
83 | struct armada_thermal_priv *priv); |
84 | |
85 | /* Formula coeficients: temp = (b - m * reg) / div */ |
86 | s64 coef_b; |
87 | s64 coef_m; |
88 | u32 coef_div; |
89 | bool inverted; |
90 | bool signed_sample; |
91 | |
92 | /* Register shift and mask to access the sensor temperature */ |
93 | unsigned int temp_shift; |
94 | unsigned int temp_mask; |
95 | unsigned int thresh_shift; |
96 | unsigned int hyst_shift; |
97 | unsigned int hyst_mask; |
98 | u32 is_valid_bit; |
99 | |
100 | /* Syscon access */ |
101 | unsigned int syscon_control0_off; |
102 | unsigned int syscon_control1_off; |
103 | unsigned int syscon_status_off; |
104 | unsigned int dfx_irq_cause_off; |
105 | unsigned int dfx_irq_mask_off; |
106 | unsigned int dfx_overheat_irq; |
107 | unsigned int dfx_server_irq_mask_off; |
108 | unsigned int dfx_server_irq_en; |
109 | |
110 | /* One sensor is in the thermal IC, the others are in the CPUs if any */ |
111 | unsigned int cpu_nr; |
112 | }; |
113 | |
114 | struct armada_drvdata { |
115 | enum drvtype { |
116 | LEGACY, |
117 | SYSCON |
118 | } type; |
119 | union { |
120 | struct armada_thermal_priv *priv; |
121 | struct thermal_zone_device *tz; |
122 | } data; |
123 | }; |
124 | |
125 | /* |
126 | * struct armada_thermal_sensor - hold the information of one thermal sensor |
127 | * @thermal: pointer to the local private structure |
128 | * @tzd: pointer to the thermal zone device |
129 | * @id: identifier of the thermal sensor |
130 | */ |
131 | struct armada_thermal_sensor { |
132 | struct armada_thermal_priv *priv; |
133 | int id; |
134 | }; |
135 | |
136 | static void armadaxp_init(struct platform_device *pdev, |
137 | struct armada_thermal_priv *priv) |
138 | { |
139 | struct armada_thermal_data *data = priv->data; |
140 | u32 reg; |
141 | |
142 | regmap_read(map: priv->syscon, reg: data->syscon_control1_off, val: ®); |
143 | reg |= PMU_TDC0_OTF_CAL_MASK; |
144 | |
145 | /* Reference calibration value */ |
146 | reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; |
147 | reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); |
148 | |
149 | /* Reset the sensor */ |
150 | reg |= PMU_TDC0_SW_RST_MASK; |
151 | |
152 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: reg); |
153 | |
154 | reg &= ~PMU_TDC0_SW_RST_MASK; |
155 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: reg); |
156 | |
157 | /* Enable the sensor */ |
158 | regmap_read(map: priv->syscon, reg: data->syscon_status_off, val: ®); |
159 | reg &= ~PMU_TM_DISABLE_MASK; |
160 | regmap_write(map: priv->syscon, reg: data->syscon_status_off, val: reg); |
161 | } |
162 | |
163 | static void armada370_init(struct platform_device *pdev, |
164 | struct armada_thermal_priv *priv) |
165 | { |
166 | struct armada_thermal_data *data = priv->data; |
167 | u32 reg; |
168 | |
169 | regmap_read(map: priv->syscon, reg: data->syscon_control1_off, val: ®); |
170 | reg |= PMU_TDC0_OTF_CAL_MASK; |
171 | |
172 | /* Reference calibration value */ |
173 | reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; |
174 | reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); |
175 | |
176 | /* Reset the sensor */ |
177 | reg &= ~PMU_TDC0_START_CAL_MASK; |
178 | |
179 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: reg); |
180 | |
181 | msleep(msecs: 10); |
182 | } |
183 | |
184 | static void armada375_init(struct platform_device *pdev, |
185 | struct armada_thermal_priv *priv) |
186 | { |
187 | struct armada_thermal_data *data = priv->data; |
188 | u32 reg; |
189 | |
190 | regmap_read(map: priv->syscon, reg: data->syscon_control1_off, val: ®); |
191 | reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT); |
192 | reg &= ~A375_READOUT_INVERT; |
193 | reg &= ~A375_HW_RESETn; |
194 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: reg); |
195 | |
196 | msleep(msecs: 20); |
197 | |
198 | reg |= A375_HW_RESETn; |
199 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: reg); |
200 | |
201 | msleep(msecs: 50); |
202 | } |
203 | |
204 | static int armada_wait_sensor_validity(struct armada_thermal_priv *priv) |
205 | { |
206 | u32 reg; |
207 | |
208 | return regmap_read_poll_timeout(priv->syscon, |
209 | priv->data->syscon_status_off, reg, |
210 | reg & priv->data->is_valid_bit, |
211 | STATUS_POLL_PERIOD_US, |
212 | STATUS_POLL_TIMEOUT_US); |
213 | } |
214 | |
215 | static void armada380_init(struct platform_device *pdev, |
216 | struct armada_thermal_priv *priv) |
217 | { |
218 | struct armada_thermal_data *data = priv->data; |
219 | u32 reg; |
220 | |
221 | /* Disable the HW/SW reset */ |
222 | regmap_read(map: priv->syscon, reg: data->syscon_control1_off, val: ®); |
223 | reg |= CONTROL1_EXT_TSEN_HW_RESETn; |
224 | reg &= ~CONTROL1_EXT_TSEN_SW_RESET; |
225 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: reg); |
226 | |
227 | /* Set Tsen Tc Trim to correct default value (errata #132698) */ |
228 | regmap_read(map: priv->syscon, reg: data->syscon_control0_off, val: ®); |
229 | reg &= ~CONTROL0_TSEN_TC_TRIM_MASK; |
230 | reg |= CONTROL0_TSEN_TC_TRIM_VAL; |
231 | regmap_write(map: priv->syscon, reg: data->syscon_control0_off, val: reg); |
232 | } |
233 | |
234 | static void armada_ap80x_init(struct platform_device *pdev, |
235 | struct armada_thermal_priv *priv) |
236 | { |
237 | struct armada_thermal_data *data = priv->data; |
238 | u32 reg; |
239 | |
240 | regmap_read(map: priv->syscon, reg: data->syscon_control0_off, val: ®); |
241 | reg &= ~CONTROL0_TSEN_RESET; |
242 | reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_ENABLE; |
243 | |
244 | /* Sample every ~2ms */ |
245 | reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; |
246 | |
247 | /* Enable average (2 samples by default) */ |
248 | reg &= ~CONTROL0_TSEN_AVG_BYPASS; |
249 | |
250 | regmap_write(map: priv->syscon, reg: data->syscon_control0_off, val: reg); |
251 | } |
252 | |
253 | static void armada_cp110_init(struct platform_device *pdev, |
254 | struct armada_thermal_priv *priv) |
255 | { |
256 | struct armada_thermal_data *data = priv->data; |
257 | u32 reg; |
258 | |
259 | armada380_init(pdev, priv); |
260 | |
261 | /* Sample every ~2ms */ |
262 | regmap_read(map: priv->syscon, reg: data->syscon_control0_off, val: ®); |
263 | reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; |
264 | regmap_write(map: priv->syscon, reg: data->syscon_control0_off, val: reg); |
265 | |
266 | /* Average the output value over 2^1 = 2 samples */ |
267 | regmap_read(map: priv->syscon, reg: data->syscon_control1_off, val: ®); |
268 | reg &= ~CONTROL1_TSEN_AVG_MASK; |
269 | reg |= 1; |
270 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: reg); |
271 | } |
272 | |
273 | static bool armada_is_valid(struct armada_thermal_priv *priv) |
274 | { |
275 | u32 reg; |
276 | |
277 | if (!priv->data->is_valid_bit) |
278 | return true; |
279 | |
280 | regmap_read(map: priv->syscon, reg: priv->data->syscon_status_off, val: ®); |
281 | |
282 | return reg & priv->data->is_valid_bit; |
283 | } |
284 | |
285 | static void armada_enable_overheat_interrupt(struct armada_thermal_priv *priv) |
286 | { |
287 | struct armada_thermal_data *data = priv->data; |
288 | u32 reg; |
289 | |
290 | /* Clear DFX temperature IRQ cause */ |
291 | regmap_read(map: priv->syscon, reg: data->dfx_irq_cause_off, val: ®); |
292 | |
293 | /* Enable DFX Temperature IRQ */ |
294 | regmap_read(map: priv->syscon, reg: data->dfx_irq_mask_off, val: ®); |
295 | reg |= data->dfx_overheat_irq; |
296 | regmap_write(map: priv->syscon, reg: data->dfx_irq_mask_off, val: reg); |
297 | |
298 | /* Enable DFX server IRQ */ |
299 | regmap_read(map: priv->syscon, reg: data->dfx_server_irq_mask_off, val: ®); |
300 | reg |= data->dfx_server_irq_en; |
301 | regmap_write(map: priv->syscon, reg: data->dfx_server_irq_mask_off, val: reg); |
302 | |
303 | /* Enable overheat interrupt */ |
304 | regmap_read(map: priv->syscon, reg: data->syscon_control1_off, val: ®); |
305 | reg |= CONTROL1_TSEN_INT_EN; |
306 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: reg); |
307 | } |
308 | |
309 | static void __maybe_unused |
310 | armada_disable_overheat_interrupt(struct armada_thermal_priv *priv) |
311 | { |
312 | struct armada_thermal_data *data = priv->data; |
313 | u32 reg; |
314 | |
315 | regmap_read(map: priv->syscon, reg: data->syscon_control1_off, val: ®); |
316 | reg &= ~CONTROL1_TSEN_INT_EN; |
317 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: reg); |
318 | } |
319 | |
320 | /* There is currently no board with more than one sensor per channel */ |
321 | static int armada_select_channel(struct armada_thermal_priv *priv, int channel) |
322 | { |
323 | struct armada_thermal_data *data = priv->data; |
324 | u32 ctrl0; |
325 | |
326 | if (channel < 0 || channel > priv->data->cpu_nr) |
327 | return -EINVAL; |
328 | |
329 | if (priv->current_channel == channel) |
330 | return 0; |
331 | |
332 | /* Stop the measurements */ |
333 | regmap_read(map: priv->syscon, reg: data->syscon_control0_off, val: &ctrl0); |
334 | ctrl0 &= ~CONTROL0_TSEN_START; |
335 | regmap_write(map: priv->syscon, reg: data->syscon_control0_off, val: ctrl0); |
336 | |
337 | /* Reset the mode, internal sensor will be automatically selected */ |
338 | ctrl0 &= ~(CONTROL0_TSEN_MODE_MASK << CONTROL0_TSEN_MODE_SHIFT); |
339 | |
340 | /* Other channels are external and should be selected accordingly */ |
341 | if (channel) { |
342 | /* Change the mode to external */ |
343 | ctrl0 |= CONTROL0_TSEN_MODE_EXTERNAL << |
344 | CONTROL0_TSEN_MODE_SHIFT; |
345 | /* Select the sensor */ |
346 | ctrl0 &= ~(CONTROL0_TSEN_CHAN_MASK << CONTROL0_TSEN_CHAN_SHIFT); |
347 | ctrl0 |= (channel - 1) << CONTROL0_TSEN_CHAN_SHIFT; |
348 | } |
349 | |
350 | /* Actually set the mode/channel */ |
351 | regmap_write(map: priv->syscon, reg: data->syscon_control0_off, val: ctrl0); |
352 | priv->current_channel = channel; |
353 | |
354 | /* Re-start the measurements */ |
355 | ctrl0 |= CONTROL0_TSEN_START; |
356 | regmap_write(map: priv->syscon, reg: data->syscon_control0_off, val: ctrl0); |
357 | |
358 | /* |
359 | * The IP has a latency of ~15ms, so after updating the selected source, |
360 | * we must absolutely wait for the sensor validity bit to ensure we read |
361 | * actual data. |
362 | */ |
363 | if (armada_wait_sensor_validity(priv)) |
364 | return -EIO; |
365 | |
366 | return 0; |
367 | } |
368 | |
369 | static int armada_read_sensor(struct armada_thermal_priv *priv, int *temp) |
370 | { |
371 | u32 reg, div; |
372 | s64 sample, b, m; |
373 | |
374 | regmap_read(map: priv->syscon, reg: priv->data->syscon_status_off, val: ®); |
375 | reg = (reg >> priv->data->temp_shift) & priv->data->temp_mask; |
376 | if (priv->data->signed_sample) |
377 | /* The most significant bit is the sign bit */ |
378 | sample = sign_extend32(value: reg, index: fls(x: priv->data->temp_mask) - 1); |
379 | else |
380 | sample = reg; |
381 | |
382 | /* Get formula coeficients */ |
383 | b = priv->data->coef_b; |
384 | m = priv->data->coef_m; |
385 | div = priv->data->coef_div; |
386 | |
387 | if (priv->data->inverted) |
388 | *temp = div_s64(dividend: (m * sample) - b, divisor: div); |
389 | else |
390 | *temp = div_s64(dividend: b - (m * sample), divisor: div); |
391 | |
392 | return 0; |
393 | } |
394 | |
395 | static int armada_get_temp_legacy(struct thermal_zone_device *thermal, |
396 | int *temp) |
397 | { |
398 | struct armada_thermal_priv *priv = thermal_zone_device_priv(tzd: thermal); |
399 | int ret; |
400 | |
401 | /* Valid check */ |
402 | if (!armada_is_valid(priv)) |
403 | return -EIO; |
404 | |
405 | /* Do the actual reading */ |
406 | ret = armada_read_sensor(priv, temp); |
407 | |
408 | return ret; |
409 | } |
410 | |
411 | static struct thermal_zone_device_ops legacy_ops = { |
412 | .get_temp = armada_get_temp_legacy, |
413 | }; |
414 | |
415 | static int armada_get_temp(struct thermal_zone_device *tz, int *temp) |
416 | { |
417 | struct armada_thermal_sensor *sensor = thermal_zone_device_priv(tzd: tz); |
418 | struct armada_thermal_priv *priv = sensor->priv; |
419 | int ret; |
420 | |
421 | mutex_lock(&priv->update_lock); |
422 | |
423 | /* Select the desired channel */ |
424 | ret = armada_select_channel(priv, channel: sensor->id); |
425 | if (ret) |
426 | goto unlock_mutex; |
427 | |
428 | /* Do the actual reading */ |
429 | ret = armada_read_sensor(priv, temp); |
430 | if (ret) |
431 | goto unlock_mutex; |
432 | |
433 | /* |
434 | * Select back the interrupt source channel from which a potential |
435 | * critical trip point has been set. |
436 | */ |
437 | ret = armada_select_channel(priv, channel: priv->interrupt_source); |
438 | |
439 | unlock_mutex: |
440 | mutex_unlock(lock: &priv->update_lock); |
441 | |
442 | return ret; |
443 | } |
444 | |
445 | static const struct thermal_zone_device_ops of_ops = { |
446 | .get_temp = armada_get_temp, |
447 | }; |
448 | |
449 | static unsigned int armada_mc_to_reg_temp(struct armada_thermal_data *data, |
450 | unsigned int temp_mc) |
451 | { |
452 | s64 b = data->coef_b; |
453 | s64 m = data->coef_m; |
454 | s64 div = data->coef_div; |
455 | unsigned int sample; |
456 | |
457 | if (data->inverted) |
458 | sample = div_s64(dividend: ((temp_mc * div) + b), divisor: m); |
459 | else |
460 | sample = div_s64(dividend: (b - (temp_mc * div)), divisor: m); |
461 | |
462 | return sample & data->temp_mask; |
463 | } |
464 | |
465 | /* |
466 | * The documentation states: |
467 | * high/low watermark = threshold +/- 0.4761 * 2^(hysteresis + 2) |
468 | * which is the mathematical derivation for: |
469 | * 0x0 <=> 1.9°C, 0x1 <=> 3.8°C, 0x2 <=> 7.6°C, 0x3 <=> 15.2°C |
470 | */ |
471 | static unsigned int hyst_levels_mc[] = {1900, 3800, 7600, 15200}; |
472 | |
473 | static unsigned int armada_mc_to_reg_hyst(struct armada_thermal_data *data, |
474 | unsigned int hyst_mc) |
475 | { |
476 | int i; |
477 | |
478 | /* |
479 | * We will always take the smallest possible hysteresis to avoid risking |
480 | * the hardware integrity by enlarging the threshold by +8°C in the |
481 | * worst case. |
482 | */ |
483 | for (i = ARRAY_SIZE(hyst_levels_mc) - 1; i > 0; i--) |
484 | if (hyst_mc >= hyst_levels_mc[i]) |
485 | break; |
486 | |
487 | return i & data->hyst_mask; |
488 | } |
489 | |
490 | static void armada_set_overheat_thresholds(struct armada_thermal_priv *priv, |
491 | int thresh_mc, int hyst_mc) |
492 | { |
493 | struct armada_thermal_data *data = priv->data; |
494 | unsigned int threshold = armada_mc_to_reg_temp(data, temp_mc: thresh_mc); |
495 | unsigned int hysteresis = armada_mc_to_reg_hyst(data, hyst_mc); |
496 | u32 ctrl1; |
497 | |
498 | regmap_read(map: priv->syscon, reg: data->syscon_control1_off, val: &ctrl1); |
499 | |
500 | /* Set Threshold */ |
501 | if (thresh_mc >= 0) { |
502 | ctrl1 &= ~(data->temp_mask << data->thresh_shift); |
503 | ctrl1 |= threshold << data->thresh_shift; |
504 | priv->current_threshold = thresh_mc; |
505 | } |
506 | |
507 | /* Set Hysteresis */ |
508 | if (hyst_mc >= 0) { |
509 | ctrl1 &= ~(data->hyst_mask << data->hyst_shift); |
510 | ctrl1 |= hysteresis << data->hyst_shift; |
511 | priv->current_hysteresis = hyst_mc; |
512 | } |
513 | |
514 | regmap_write(map: priv->syscon, reg: data->syscon_control1_off, val: ctrl1); |
515 | } |
516 | |
517 | static irqreturn_t armada_overheat_isr(int irq, void *blob) |
518 | { |
519 | /* |
520 | * Disable the IRQ and continue in thread context (thermal core |
521 | * notification and temperature monitoring). |
522 | */ |
523 | disable_irq_nosync(irq); |
524 | |
525 | return IRQ_WAKE_THREAD; |
526 | } |
527 | |
528 | static irqreturn_t armada_overheat_isr_thread(int irq, void *blob) |
529 | { |
530 | struct armada_thermal_priv *priv = blob; |
531 | int low_threshold = priv->current_threshold - priv->current_hysteresis; |
532 | int temperature; |
533 | u32 dummy; |
534 | int ret; |
535 | |
536 | /* Notify the core in thread context */ |
537 | thermal_zone_device_update(priv->overheat_sensor, |
538 | THERMAL_EVENT_UNSPECIFIED); |
539 | |
540 | /* |
541 | * The overheat interrupt must be cleared by reading the DFX interrupt |
542 | * cause _after_ the temperature has fallen down to the low threshold. |
543 | * Otherwise future interrupts might not be served. |
544 | */ |
545 | do { |
546 | msleep(OVERHEAT_INT_POLL_DELAY_MS); |
547 | mutex_lock(&priv->update_lock); |
548 | ret = armada_read_sensor(priv, temp: &temperature); |
549 | mutex_unlock(lock: &priv->update_lock); |
550 | if (ret) |
551 | goto enable_irq; |
552 | } while (temperature >= low_threshold); |
553 | |
554 | regmap_read(map: priv->syscon, reg: priv->data->dfx_irq_cause_off, val: &dummy); |
555 | |
556 | /* Notify the thermal core that the temperature is acceptable again */ |
557 | thermal_zone_device_update(priv->overheat_sensor, |
558 | THERMAL_EVENT_UNSPECIFIED); |
559 | |
560 | enable_irq: |
561 | enable_irq(irq); |
562 | |
563 | return IRQ_HANDLED; |
564 | } |
565 | |
566 | static const struct armada_thermal_data armadaxp_data = { |
567 | .init = armadaxp_init, |
568 | .temp_shift = 10, |
569 | .temp_mask = 0x1ff, |
570 | .coef_b = 3153000000ULL, |
571 | .coef_m = 10000000ULL, |
572 | .coef_div = 13825, |
573 | .syscon_status_off = 0xb0, |
574 | .syscon_control1_off = 0x2d0, |
575 | }; |
576 | |
577 | static const struct armada_thermal_data armada370_data = { |
578 | .init = armada370_init, |
579 | .is_valid_bit = BIT(9), |
580 | .temp_shift = 10, |
581 | .temp_mask = 0x1ff, |
582 | .coef_b = 3153000000ULL, |
583 | .coef_m = 10000000ULL, |
584 | .coef_div = 13825, |
585 | .syscon_status_off = 0x0, |
586 | .syscon_control1_off = 0x4, |
587 | }; |
588 | |
589 | static const struct armada_thermal_data armada375_data = { |
590 | .init = armada375_init, |
591 | .is_valid_bit = BIT(10), |
592 | .temp_shift = 0, |
593 | .temp_mask = 0x1ff, |
594 | .coef_b = 3171900000ULL, |
595 | .coef_m = 10000000ULL, |
596 | .coef_div = 13616, |
597 | .syscon_status_off = 0x78, |
598 | .syscon_control0_off = 0x7c, |
599 | .syscon_control1_off = 0x80, |
600 | }; |
601 | |
602 | static const struct armada_thermal_data armada380_data = { |
603 | .init = armada380_init, |
604 | .is_valid_bit = BIT(10), |
605 | .temp_shift = 0, |
606 | .temp_mask = 0x3ff, |
607 | .coef_b = 1172499100ULL, |
608 | .coef_m = 2000096ULL, |
609 | .coef_div = 4201, |
610 | .inverted = true, |
611 | .syscon_control0_off = 0x70, |
612 | .syscon_control1_off = 0x74, |
613 | .syscon_status_off = 0x78, |
614 | }; |
615 | |
616 | static const struct armada_thermal_data armada_ap806_data = { |
617 | .init = armada_ap80x_init, |
618 | .is_valid_bit = BIT(16), |
619 | .temp_shift = 0, |
620 | .temp_mask = 0x3ff, |
621 | .thresh_shift = 3, |
622 | .hyst_shift = 19, |
623 | .hyst_mask = 0x3, |
624 | .coef_b = -150000LL, |
625 | .coef_m = 423ULL, |
626 | .coef_div = 1, |
627 | .inverted = true, |
628 | .signed_sample = true, |
629 | .syscon_control0_off = 0x84, |
630 | .syscon_control1_off = 0x88, |
631 | .syscon_status_off = 0x8C, |
632 | .dfx_irq_cause_off = 0x108, |
633 | .dfx_irq_mask_off = 0x10C, |
634 | .dfx_overheat_irq = BIT(22), |
635 | .dfx_server_irq_mask_off = 0x104, |
636 | .dfx_server_irq_en = BIT(1), |
637 | .cpu_nr = 4, |
638 | }; |
639 | |
640 | static const struct armada_thermal_data armada_ap807_data = { |
641 | .init = armada_ap80x_init, |
642 | .is_valid_bit = BIT(16), |
643 | .temp_shift = 0, |
644 | .temp_mask = 0x3ff, |
645 | .thresh_shift = 3, |
646 | .hyst_shift = 19, |
647 | .hyst_mask = 0x3, |
648 | .coef_b = -128900LL, |
649 | .coef_m = 394ULL, |
650 | .coef_div = 1, |
651 | .inverted = true, |
652 | .signed_sample = true, |
653 | .syscon_control0_off = 0x84, |
654 | .syscon_control1_off = 0x88, |
655 | .syscon_status_off = 0x8C, |
656 | .dfx_irq_cause_off = 0x108, |
657 | .dfx_irq_mask_off = 0x10C, |
658 | .dfx_overheat_irq = BIT(22), |
659 | .dfx_server_irq_mask_off = 0x104, |
660 | .dfx_server_irq_en = BIT(1), |
661 | .cpu_nr = 4, |
662 | }; |
663 | |
664 | static const struct armada_thermal_data armada_cp110_data = { |
665 | .init = armada_cp110_init, |
666 | .is_valid_bit = BIT(10), |
667 | .temp_shift = 0, |
668 | .temp_mask = 0x3ff, |
669 | .thresh_shift = 16, |
670 | .hyst_shift = 26, |
671 | .hyst_mask = 0x3, |
672 | .coef_b = 1172499100ULL, |
673 | .coef_m = 2000096ULL, |
674 | .coef_div = 4201, |
675 | .inverted = true, |
676 | .syscon_control0_off = 0x70, |
677 | .syscon_control1_off = 0x74, |
678 | .syscon_status_off = 0x78, |
679 | .dfx_irq_cause_off = 0x108, |
680 | .dfx_irq_mask_off = 0x10C, |
681 | .dfx_overheat_irq = BIT(20), |
682 | .dfx_server_irq_mask_off = 0x104, |
683 | .dfx_server_irq_en = BIT(1), |
684 | }; |
685 | |
686 | static const struct of_device_id armada_thermal_id_table[] = { |
687 | { |
688 | .compatible = "marvell,armadaxp-thermal" , |
689 | .data = &armadaxp_data, |
690 | }, |
691 | { |
692 | .compatible = "marvell,armada370-thermal" , |
693 | .data = &armada370_data, |
694 | }, |
695 | { |
696 | .compatible = "marvell,armada375-thermal" , |
697 | .data = &armada375_data, |
698 | }, |
699 | { |
700 | .compatible = "marvell,armada380-thermal" , |
701 | .data = &armada380_data, |
702 | }, |
703 | { |
704 | .compatible = "marvell,armada-ap806-thermal" , |
705 | .data = &armada_ap806_data, |
706 | }, |
707 | { |
708 | .compatible = "marvell,armada-ap807-thermal" , |
709 | .data = &armada_ap807_data, |
710 | }, |
711 | { |
712 | .compatible = "marvell,armada-cp110-thermal" , |
713 | .data = &armada_cp110_data, |
714 | }, |
715 | { |
716 | /* sentinel */ |
717 | }, |
718 | }; |
719 | MODULE_DEVICE_TABLE(of, armada_thermal_id_table); |
720 | |
721 | static const struct regmap_config armada_thermal_regmap_config = { |
722 | .reg_bits = 32, |
723 | .reg_stride = 4, |
724 | .val_bits = 32, |
725 | .fast_io = true, |
726 | }; |
727 | |
728 | static int armada_thermal_probe_legacy(struct platform_device *pdev, |
729 | struct armada_thermal_priv *priv) |
730 | { |
731 | struct armada_thermal_data *data = priv->data; |
732 | void __iomem *base; |
733 | |
734 | /* First memory region points towards the status register */ |
735 | base = devm_platform_get_and_ioremap_resource(pdev, index: 0, NULL); |
736 | if (IS_ERR(ptr: base)) |
737 | return PTR_ERR(ptr: base); |
738 | |
739 | /* |
740 | * Fix up from the old individual DT register specification to |
741 | * cover all the registers. We do this by adjusting the ioremap() |
742 | * result, which should be fine as ioremap() deals with pages. |
743 | * However, validate that we do not cross a page boundary while |
744 | * making this adjustment. |
745 | */ |
746 | if (((unsigned long)base & ~PAGE_MASK) < data->syscon_status_off) |
747 | return -EINVAL; |
748 | base -= data->syscon_status_off; |
749 | |
750 | priv->syscon = devm_regmap_init_mmio(&pdev->dev, base, |
751 | &armada_thermal_regmap_config); |
752 | return PTR_ERR_OR_ZERO(ptr: priv->syscon); |
753 | } |
754 | |
755 | static int armada_thermal_probe_syscon(struct platform_device *pdev, |
756 | struct armada_thermal_priv *priv) |
757 | { |
758 | priv->syscon = syscon_node_to_regmap(np: pdev->dev.parent->of_node); |
759 | return PTR_ERR_OR_ZERO(ptr: priv->syscon); |
760 | } |
761 | |
762 | static void armada_set_sane_name(struct platform_device *pdev, |
763 | struct armada_thermal_priv *priv) |
764 | { |
765 | const char *name = dev_name(dev: &pdev->dev); |
766 | char *insane_char; |
767 | |
768 | if (strlen(name) > THERMAL_NAME_LENGTH) { |
769 | /* |
770 | * When inside a system controller, the device name has the |
771 | * form: f06f8000.system-controller:ap-thermal so stripping |
772 | * after the ':' should give us a shorter but meaningful name. |
773 | */ |
774 | name = strrchr(name, ':'); |
775 | if (!name) |
776 | name = "armada_thermal" ; |
777 | else |
778 | name++; |
779 | } |
780 | |
781 | /* Save the name locally */ |
782 | strscpy(p: priv->zone_name, q: name, THERMAL_NAME_LENGTH); |
783 | |
784 | /* Then check there are no '-' or hwmon core will complain */ |
785 | do { |
786 | insane_char = strpbrk(priv->zone_name, "-" ); |
787 | if (insane_char) |
788 | *insane_char = '_'; |
789 | } while (insane_char); |
790 | } |
791 | |
792 | /* |
793 | * The IP can manage to trigger interrupts on overheat situation from all the |
794 | * sensors. However, the interrupt source changes along with the last selected |
795 | * source (ie. the last read sensor), which is an inconsistent behavior. Avoid |
796 | * possible glitches by always selecting back only one channel (arbitrarily: the |
797 | * first in the DT which has a critical trip point). We also disable sensor |
798 | * switch during overheat situations. |
799 | */ |
800 | static int armada_configure_overheat_int(struct armada_thermal_priv *priv, |
801 | struct thermal_zone_device *tz, |
802 | int sensor_id) |
803 | { |
804 | /* Retrieve the critical trip point to enable the overheat interrupt */ |
805 | int temperature; |
806 | int ret; |
807 | |
808 | ret = thermal_zone_get_crit_temp(tz, temp: &temperature); |
809 | if (ret) |
810 | return ret; |
811 | |
812 | ret = armada_select_channel(priv, channel: sensor_id); |
813 | if (ret) |
814 | return ret; |
815 | |
816 | /* |
817 | * A critical temperature does not have a hysteresis |
818 | */ |
819 | armada_set_overheat_thresholds(priv, thresh_mc: temperature, hyst_mc: 0); |
820 | priv->overheat_sensor = tz; |
821 | priv->interrupt_source = sensor_id; |
822 | armada_enable_overheat_interrupt(priv); |
823 | |
824 | return 0; |
825 | } |
826 | |
827 | static int armada_thermal_probe(struct platform_device *pdev) |
828 | { |
829 | struct thermal_zone_device *tz; |
830 | struct armada_thermal_sensor *sensor; |
831 | struct armada_drvdata *drvdata; |
832 | const struct of_device_id *match; |
833 | struct armada_thermal_priv *priv; |
834 | int sensor_id, irq; |
835 | int ret; |
836 | |
837 | match = of_match_device(matches: armada_thermal_id_table, dev: &pdev->dev); |
838 | if (!match) |
839 | return -ENODEV; |
840 | |
841 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
842 | if (!priv) |
843 | return -ENOMEM; |
844 | |
845 | drvdata = devm_kzalloc(dev: &pdev->dev, size: sizeof(*drvdata), GFP_KERNEL); |
846 | if (!drvdata) |
847 | return -ENOMEM; |
848 | |
849 | priv->dev = &pdev->dev; |
850 | priv->data = (struct armada_thermal_data *)match->data; |
851 | |
852 | mutex_init(&priv->update_lock); |
853 | |
854 | /* |
855 | * Legacy DT bindings only described "control1" register (also referred |
856 | * as "control MSB" on old documentation). Then, bindings moved to cover |
857 | * "control0/control LSB" and "control1/control MSB" registers within |
858 | * the same resource, which was then of size 8 instead of 4. |
859 | * |
860 | * The logic of defining sporadic registers is broken. For instance, it |
861 | * blocked the addition of the overheat interrupt feature that needed |
862 | * another resource somewhere else in the same memory area. One solution |
863 | * is to define an overall system controller and put the thermal node |
864 | * into it, which requires the use of regmaps across all the driver. |
865 | */ |
866 | if (IS_ERR(ptr: syscon_node_to_regmap(np: pdev->dev.parent->of_node))) { |
867 | /* Ensure device name is correct for the thermal core */ |
868 | armada_set_sane_name(pdev, priv); |
869 | |
870 | ret = armada_thermal_probe_legacy(pdev, priv); |
871 | if (ret) |
872 | return ret; |
873 | |
874 | priv->data->init(pdev, priv); |
875 | |
876 | /* Wait the sensors to be valid */ |
877 | armada_wait_sensor_validity(priv); |
878 | |
879 | tz = thermal_tripless_zone_device_register(type: priv->zone_name, |
880 | devdata: priv, ops: &legacy_ops, |
881 | NULL); |
882 | if (IS_ERR(ptr: tz)) { |
883 | dev_err(&pdev->dev, |
884 | "Failed to register thermal zone device\n" ); |
885 | return PTR_ERR(ptr: tz); |
886 | } |
887 | |
888 | ret = thermal_zone_device_enable(tz); |
889 | if (ret) { |
890 | thermal_zone_device_unregister(tz); |
891 | return ret; |
892 | } |
893 | |
894 | drvdata->type = LEGACY; |
895 | drvdata->data.tz = tz; |
896 | platform_set_drvdata(pdev, data: drvdata); |
897 | |
898 | return 0; |
899 | } |
900 | |
901 | ret = armada_thermal_probe_syscon(pdev, priv); |
902 | if (ret) |
903 | return ret; |
904 | |
905 | priv->current_channel = -1; |
906 | priv->data->init(pdev, priv); |
907 | drvdata->type = SYSCON; |
908 | drvdata->data.priv = priv; |
909 | platform_set_drvdata(pdev, data: drvdata); |
910 | |
911 | irq = platform_get_irq(pdev, 0); |
912 | if (irq == -EPROBE_DEFER) |
913 | return irq; |
914 | |
915 | /* The overheat interrupt feature is not mandatory */ |
916 | if (irq > 0) { |
917 | ret = devm_request_threaded_irq(dev: &pdev->dev, irq, |
918 | handler: armada_overheat_isr, |
919 | thread_fn: armada_overheat_isr_thread, |
920 | irqflags: 0, NULL, dev_id: priv); |
921 | if (ret) { |
922 | dev_err(&pdev->dev, "Cannot request threaded IRQ %d\n" , |
923 | irq); |
924 | return ret; |
925 | } |
926 | } |
927 | |
928 | /* |
929 | * There is one channel for the IC and one per CPU (if any), each |
930 | * channel has one sensor. |
931 | */ |
932 | for (sensor_id = 0; sensor_id <= priv->data->cpu_nr; sensor_id++) { |
933 | sensor = devm_kzalloc(dev: &pdev->dev, |
934 | size: sizeof(struct armada_thermal_sensor), |
935 | GFP_KERNEL); |
936 | if (!sensor) |
937 | return -ENOMEM; |
938 | |
939 | /* Register the sensor */ |
940 | sensor->priv = priv; |
941 | sensor->id = sensor_id; |
942 | tz = devm_thermal_of_zone_register(dev: &pdev->dev, |
943 | id: sensor->id, data: sensor, |
944 | ops: &of_ops); |
945 | if (IS_ERR(ptr: tz)) { |
946 | dev_info(&pdev->dev, "Thermal sensor %d unavailable\n" , |
947 | sensor_id); |
948 | devm_kfree(dev: &pdev->dev, p: sensor); |
949 | continue; |
950 | } |
951 | |
952 | /* |
953 | * The first channel that has a critical trip point registered |
954 | * in the DT will serve as interrupt source. Others possible |
955 | * critical trip points will simply be ignored by the driver. |
956 | */ |
957 | if (irq > 0 && !priv->overheat_sensor) |
958 | armada_configure_overheat_int(priv, tz, sensor_id: sensor->id); |
959 | } |
960 | |
961 | /* Just complain if no overheat interrupt was set up */ |
962 | if (!priv->overheat_sensor) |
963 | dev_warn(&pdev->dev, "Overheat interrupt not available\n" ); |
964 | |
965 | return 0; |
966 | } |
967 | |
968 | static void armada_thermal_exit(struct platform_device *pdev) |
969 | { |
970 | struct armada_drvdata *drvdata = platform_get_drvdata(pdev); |
971 | |
972 | if (drvdata->type == LEGACY) |
973 | thermal_zone_device_unregister(tz: drvdata->data.tz); |
974 | } |
975 | |
976 | static struct platform_driver armada_thermal_driver = { |
977 | .probe = armada_thermal_probe, |
978 | .remove_new = armada_thermal_exit, |
979 | .driver = { |
980 | .name = "armada_thermal" , |
981 | .of_match_table = armada_thermal_id_table, |
982 | }, |
983 | }; |
984 | |
985 | module_platform_driver(armada_thermal_driver); |
986 | |
987 | MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>" ); |
988 | MODULE_DESCRIPTION("Marvell EBU Armada SoCs thermal driver" ); |
989 | MODULE_LICENSE("GPL v2" ); |
990 | |