]>
Commit | Line | Data |
---|---|---|
fa0d654c | 1 | /* |
a9d58a1a | 2 | * Marvell EBU Armada SoCs thermal sensor driver |
fa0d654c EG |
3 | * |
4 | * Copyright (C) 2013 Marvell | |
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 | #include <linux/device.h> | |
17 | #include <linux/err.h> | |
18 | #include <linux/io.h> | |
19 | #include <linux/kernel.h> | |
20 | #include <linux/of.h> | |
21 | #include <linux/module.h> | |
22 | #include <linux/delay.h> | |
23 | #include <linux/platform_device.h> | |
24 | #include <linux/of_device.h> | |
25 | #include <linux/thermal.h> | |
64163681 | 26 | #include <linux/iopoll.h> |
3d4e5184 MR |
27 | #include <linux/mfd/syscon.h> |
28 | #include <linux/regmap.h> | |
fa0d654c | 29 | |
fa0d654c EG |
30 | /* Thermal Manager Control and Status Register */ |
31 | #define PMU_TDC0_SW_RST_MASK (0x1 << 1) | |
32 | #define PMU_TM_DISABLE_OFFS 0 | |
33 | #define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS) | |
34 | #define PMU_TDC0_REF_CAL_CNT_OFFS 11 | |
35 | #define PMU_TDC0_REF_CAL_CNT_MASK (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS) | |
36 | #define PMU_TDC0_OTF_CAL_MASK (0x1 << 30) | |
37 | #define PMU_TDC0_START_CAL_MASK (0x1 << 25) | |
38 | ||
e2d5f05b EG |
39 | #define A375_UNIT_CONTROL_SHIFT 27 |
40 | #define A375_UNIT_CONTROL_MASK 0x7 | |
41 | #define A375_READOUT_INVERT BIT(15) | |
42 | #define A375_HW_RESETn BIT(8) | |
43 | ||
8c0b888f MR |
44 | /* Errata fields */ |
45 | #define CONTROL0_TSEN_TC_TRIM_MASK 0x7 | |
46 | #define CONTROL0_TSEN_TC_TRIM_VAL 0x3 | |
47 | ||
2ff12799 BS |
48 | #define CONTROL0_TSEN_START BIT(0) |
49 | #define CONTROL0_TSEN_RESET BIT(1) | |
50 | #define CONTROL0_TSEN_ENABLE BIT(2) | |
a9fae794 | 51 | #define CONTROL0_TSEN_AVG_BYPASS BIT(6) |
f7c2068a MR |
52 | #define CONTROL0_TSEN_CHAN_SHIFT 13 |
53 | #define CONTROL0_TSEN_CHAN_MASK 0xF | |
a9fae794 MR |
54 | #define CONTROL0_TSEN_OSR_SHIFT 24 |
55 | #define CONTROL0_TSEN_OSR_MAX 0x3 | |
f7c2068a MR |
56 | #define CONTROL0_TSEN_MODE_SHIFT 30 |
57 | #define CONTROL0_TSEN_MODE_EXTERNAL 0x2 | |
58 | #define CONTROL0_TSEN_MODE_MASK 0x3 | |
2ff12799 | 59 | |
a9fae794 MR |
60 | #define CONTROL1_TSEN_AVG_SHIFT 0 |
61 | #define CONTROL1_TSEN_AVG_MASK 0x7 | |
ccf8f522 BS |
62 | #define CONTROL1_EXT_TSEN_SW_RESET BIT(7) |
63 | #define CONTROL1_EXT_TSEN_HW_RESETn BIT(8) | |
64 | ||
64163681 MR |
65 | #define STATUS_POLL_PERIOD_US 1000 |
66 | #define STATUS_POLL_TIMEOUT_US 100000 | |
67 | ||
66fdb7b6 | 68 | struct armada_thermal_data; |
fa0d654c EG |
69 | |
70 | /* Marvell EBU Thermal Sensor Dev Structure */ | |
71 | struct armada_thermal_priv { | |
c9899c18 | 72 | struct device *dev; |
3d4e5184 | 73 | struct regmap *syscon; |
8d98761a | 74 | char zone_name[THERMAL_NAME_LENGTH]; |
f7c2068a MR |
75 | /* serialize temperature reads/updates */ |
76 | struct mutex update_lock; | |
66fdb7b6 | 77 | struct armada_thermal_data *data; |
f7c2068a | 78 | int current_channel; |
fa0d654c EG |
79 | }; |
80 | ||
66fdb7b6 | 81 | struct armada_thermal_data { |
8b4c2712 MR |
82 | /* Initialize the thermal IC */ |
83 | void (*init)(struct platform_device *pdev, | |
84 | struct armada_thermal_priv *priv); | |
fa0d654c | 85 | |
0cf3a1ac | 86 | /* Formula coeficients: temp = (b - m * reg) / div */ |
2ff12799 BS |
87 | s64 coef_b; |
88 | s64 coef_m; | |
89 | u32 coef_div; | |
fd2c94d5 | 90 | bool inverted; |
2ff12799 | 91 | bool signed_sample; |
1fcacca4 EG |
92 | |
93 | /* Register shift and mask to access the sensor temperature */ | |
94 | unsigned int temp_shift; | |
95 | unsigned int temp_mask; | |
27d92f27 | 96 | u32 is_valid_bit; |
3d4e5184 MR |
97 | |
98 | /* Syscon access */ | |
99 | unsigned int syscon_control0_off; | |
100 | unsigned int syscon_control1_off; | |
101 | unsigned int syscon_status_off; | |
f7c2068a MR |
102 | |
103 | /* One sensor is in the thermal IC, the others are in the CPUs if any */ | |
104 | unsigned int cpu_nr; | |
fa0d654c EG |
105 | }; |
106 | ||
c9899c18 MR |
107 | struct armada_drvdata { |
108 | enum drvtype { | |
109 | LEGACY, | |
110 | SYSCON | |
111 | } type; | |
112 | union { | |
113 | struct armada_thermal_priv *priv; | |
114 | struct thermal_zone_device *tz; | |
115 | } data; | |
116 | }; | |
117 | ||
118 | /* | |
119 | * struct armada_thermal_sensor - hold the information of one thermal sensor | |
120 | * @thermal: pointer to the local private structure | |
121 | * @tzd: pointer to the thermal zone device | |
f7c2068a | 122 | * @id: identifier of the thermal sensor |
c9899c18 MR |
123 | */ |
124 | struct armada_thermal_sensor { | |
125 | struct armada_thermal_priv *priv; | |
f7c2068a | 126 | int id; |
c9899c18 MR |
127 | }; |
128 | ||
8b4c2712 MR |
129 | static void armadaxp_init(struct platform_device *pdev, |
130 | struct armada_thermal_priv *priv) | |
fa0d654c | 131 | { |
3d4e5184 | 132 | struct armada_thermal_data *data = priv->data; |
2f28e4c2 | 133 | u32 reg; |
fa0d654c | 134 | |
3d4e5184 | 135 | regmap_read(priv->syscon, data->syscon_control1_off, ®); |
fa0d654c | 136 | reg |= PMU_TDC0_OTF_CAL_MASK; |
fa0d654c EG |
137 | |
138 | /* Reference calibration value */ | |
139 | reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; | |
140 | reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); | |
fa0d654c EG |
141 | |
142 | /* Reset the sensor */ | |
931d3c5d | 143 | reg |= PMU_TDC0_SW_RST_MASK; |
fa0d654c | 144 | |
3d4e5184 | 145 | regmap_write(priv->syscon, data->syscon_control1_off, reg); |
fa0d654c EG |
146 | |
147 | /* Enable the sensor */ | |
3d4e5184 | 148 | regmap_read(priv->syscon, data->syscon_status_off, ®); |
fa0d654c | 149 | reg &= ~PMU_TM_DISABLE_MASK; |
3d4e5184 | 150 | regmap_write(priv->syscon, data->syscon_status_off, reg); |
fa0d654c EG |
151 | } |
152 | ||
8b4c2712 MR |
153 | static void armada370_init(struct platform_device *pdev, |
154 | struct armada_thermal_priv *priv) | |
fa0d654c | 155 | { |
3d4e5184 | 156 | struct armada_thermal_data *data = priv->data; |
2f28e4c2 | 157 | u32 reg; |
fa0d654c | 158 | |
3d4e5184 | 159 | regmap_read(priv->syscon, data->syscon_control1_off, ®); |
fa0d654c | 160 | reg |= PMU_TDC0_OTF_CAL_MASK; |
fa0d654c EG |
161 | |
162 | /* Reference calibration value */ | |
163 | reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; | |
164 | reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); | |
fa0d654c | 165 | |
3d4e5184 | 166 | /* Reset the sensor */ |
fa0d654c | 167 | reg &= ~PMU_TDC0_START_CAL_MASK; |
931d3c5d | 168 | |
3d4e5184 | 169 | regmap_write(priv->syscon, data->syscon_control1_off, reg); |
fa0d654c | 170 | |
7f3be017 | 171 | msleep(10); |
fa0d654c EG |
172 | } |
173 | ||
8b4c2712 MR |
174 | static void armada375_init(struct platform_device *pdev, |
175 | struct armada_thermal_priv *priv) | |
e2d5f05b | 176 | { |
3d4e5184 | 177 | struct armada_thermal_data *data = priv->data; |
2f28e4c2 | 178 | u32 reg; |
e2d5f05b | 179 | |
3d4e5184 | 180 | regmap_read(priv->syscon, data->syscon_control1_off, ®); |
e2d5f05b EG |
181 | reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT); |
182 | reg &= ~A375_READOUT_INVERT; | |
183 | reg &= ~A375_HW_RESETn; | |
3d4e5184 | 184 | regmap_write(priv->syscon, data->syscon_control1_off, reg); |
e2d5f05b | 185 | |
7f3be017 | 186 | msleep(20); |
e2d5f05b EG |
187 | |
188 | reg |= A375_HW_RESETn; | |
3d4e5184 MR |
189 | regmap_write(priv->syscon, data->syscon_control1_off, reg); |
190 | ||
7f3be017 | 191 | msleep(50); |
e2d5f05b EG |
192 | } |
193 | ||
f7c2068a | 194 | static int armada_wait_sensor_validity(struct armada_thermal_priv *priv) |
64163681 MR |
195 | { |
196 | u32 reg; | |
197 | ||
f7c2068a MR |
198 | return regmap_read_poll_timeout(priv->syscon, |
199 | priv->data->syscon_status_off, reg, | |
200 | reg & priv->data->is_valid_bit, | |
201 | STATUS_POLL_PERIOD_US, | |
202 | STATUS_POLL_TIMEOUT_US); | |
64163681 MR |
203 | } |
204 | ||
8b4c2712 MR |
205 | static void armada380_init(struct platform_device *pdev, |
206 | struct armada_thermal_priv *priv) | |
e6e0a68c | 207 | { |
3d4e5184 MR |
208 | struct armada_thermal_data *data = priv->data; |
209 | u32 reg; | |
e6e0a68c | 210 | |
ccf8f522 | 211 | /* Disable the HW/SW reset */ |
3d4e5184 | 212 | regmap_read(priv->syscon, data->syscon_control1_off, ®); |
ccf8f522 BS |
213 | reg |= CONTROL1_EXT_TSEN_HW_RESETn; |
214 | reg &= ~CONTROL1_EXT_TSEN_SW_RESET; | |
3d4e5184 | 215 | regmap_write(priv->syscon, data->syscon_control1_off, reg); |
8c0b888f MR |
216 | |
217 | /* Set Tsen Tc Trim to correct default value (errata #132698) */ | |
3d4e5184 MR |
218 | regmap_read(priv->syscon, data->syscon_control0_off, ®); |
219 | reg &= ~CONTROL0_TSEN_TC_TRIM_MASK; | |
220 | reg |= CONTROL0_TSEN_TC_TRIM_VAL; | |
221 | regmap_write(priv->syscon, data->syscon_control0_off, reg); | |
e6e0a68c EG |
222 | } |
223 | ||
8b4c2712 MR |
224 | static void armada_ap806_init(struct platform_device *pdev, |
225 | struct armada_thermal_priv *priv) | |
2ff12799 | 226 | { |
3d4e5184 | 227 | struct armada_thermal_data *data = priv->data; |
2ff12799 BS |
228 | u32 reg; |
229 | ||
3d4e5184 | 230 | regmap_read(priv->syscon, data->syscon_control0_off, ®); |
2ff12799 BS |
231 | reg &= ~CONTROL0_TSEN_RESET; |
232 | reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_ENABLE; | |
a9fae794 MR |
233 | |
234 | /* Sample every ~2ms */ | |
235 | reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; | |
236 | ||
237 | /* Enable average (2 samples by default) */ | |
238 | reg &= ~CONTROL0_TSEN_AVG_BYPASS; | |
239 | ||
3d4e5184 | 240 | regmap_write(priv->syscon, data->syscon_control0_off, reg); |
2ff12799 BS |
241 | } |
242 | ||
5b5e17a1 MR |
243 | static void armada_cp110_init(struct platform_device *pdev, |
244 | struct armada_thermal_priv *priv) | |
245 | { | |
3d4e5184 | 246 | struct armada_thermal_data *data = priv->data; |
a9fae794 MR |
247 | u32 reg; |
248 | ||
5b5e17a1 | 249 | armada380_init(pdev, priv); |
a9fae794 MR |
250 | |
251 | /* Sample every ~2ms */ | |
3d4e5184 | 252 | regmap_read(priv->syscon, data->syscon_control0_off, ®); |
a9fae794 | 253 | reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; |
3d4e5184 | 254 | regmap_write(priv->syscon, data->syscon_control0_off, reg); |
a9fae794 MR |
255 | |
256 | /* Average the output value over 2^1 = 2 samples */ | |
3d4e5184 | 257 | regmap_read(priv->syscon, data->syscon_control1_off, ®); |
a9fae794 MR |
258 | reg &= ~CONTROL1_TSEN_AVG_MASK << CONTROL1_TSEN_AVG_SHIFT; |
259 | reg |= 1 << CONTROL1_TSEN_AVG_SHIFT; | |
3d4e5184 | 260 | regmap_write(priv->syscon, data->syscon_control1_off, reg); |
5b5e17a1 MR |
261 | } |
262 | ||
fa0d654c EG |
263 | static bool armada_is_valid(struct armada_thermal_priv *priv) |
264 | { | |
3d4e5184 MR |
265 | u32 reg; |
266 | ||
8c0e64ac MR |
267 | if (!priv->data->is_valid_bit) |
268 | return true; | |
269 | ||
3d4e5184 | 270 | regmap_read(priv->syscon, priv->data->syscon_status_off, ®); |
fa0d654c | 271 | |
27d92f27 | 272 | return reg & priv->data->is_valid_bit; |
fa0d654c EG |
273 | } |
274 | ||
f7c2068a MR |
275 | /* There is currently no board with more than one sensor per channel */ |
276 | static int armada_select_channel(struct armada_thermal_priv *priv, int channel) | |
277 | { | |
278 | struct armada_thermal_data *data = priv->data; | |
279 | u32 ctrl0; | |
280 | ||
281 | if (channel < 0 || channel > priv->data->cpu_nr) | |
282 | return -EINVAL; | |
283 | ||
284 | if (priv->current_channel == channel) | |
285 | return 0; | |
286 | ||
287 | /* Stop the measurements */ | |
288 | regmap_read(priv->syscon, data->syscon_control0_off, &ctrl0); | |
289 | ctrl0 &= ~CONTROL0_TSEN_START; | |
290 | regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); | |
291 | ||
292 | /* Reset the mode, internal sensor will be automatically selected */ | |
293 | ctrl0 &= ~(CONTROL0_TSEN_MODE_MASK << CONTROL0_TSEN_MODE_SHIFT); | |
294 | ||
295 | /* Other channels are external and should be selected accordingly */ | |
296 | if (channel) { | |
297 | /* Change the mode to external */ | |
298 | ctrl0 |= CONTROL0_TSEN_MODE_EXTERNAL << | |
299 | CONTROL0_TSEN_MODE_SHIFT; | |
300 | /* Select the sensor */ | |
301 | ctrl0 &= ~(CONTROL0_TSEN_CHAN_MASK << CONTROL0_TSEN_CHAN_SHIFT); | |
302 | ctrl0 |= (channel - 1) << CONTROL0_TSEN_CHAN_SHIFT; | |
303 | } | |
304 | ||
305 | /* Actually set the mode/channel */ | |
306 | regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); | |
307 | priv->current_channel = channel; | |
308 | ||
309 | /* Re-start the measurements */ | |
310 | ctrl0 |= CONTROL0_TSEN_START; | |
311 | regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); | |
312 | ||
313 | /* | |
314 | * The IP has a latency of ~15ms, so after updating the selected source, | |
315 | * we must absolutely wait for the sensor validity bit to ensure we read | |
316 | * actual data. | |
317 | */ | |
318 | if (armada_wait_sensor_validity(priv)) { | |
319 | dev_err(priv->dev, | |
320 | "Temperature sensor reading not valid\n"); | |
321 | return -EIO; | |
322 | } | |
323 | ||
324 | return 0; | |
325 | } | |
326 | ||
c9899c18 | 327 | static int armada_read_sensor(struct armada_thermal_priv *priv, int *temp) |
fa0d654c | 328 | { |
2ff12799 BS |
329 | u32 reg, div; |
330 | s64 sample, b, m; | |
fa0d654c | 331 | |
3d4e5184 | 332 | regmap_read(priv->syscon, priv->data->syscon_status_off, ®); |
1fcacca4 | 333 | reg = (reg >> priv->data->temp_shift) & priv->data->temp_mask; |
2ff12799 BS |
334 | if (priv->data->signed_sample) |
335 | /* The most significant bit is the sign bit */ | |
336 | sample = sign_extend32(reg, fls(priv->data->temp_mask) - 1); | |
337 | else | |
338 | sample = reg; | |
9484bc62 EG |
339 | |
340 | /* Get formula coeficients */ | |
341 | b = priv->data->coef_b; | |
342 | m = priv->data->coef_m; | |
343 | div = priv->data->coef_div; | |
344 | ||
fd2c94d5 | 345 | if (priv->data->inverted) |
2ff12799 | 346 | *temp = div_s64((m * sample) - b, div); |
fd2c94d5 | 347 | else |
2ff12799 BS |
348 | *temp = div_s64(b - (m * sample), div); |
349 | ||
fa0d654c EG |
350 | return 0; |
351 | } | |
352 | ||
c9899c18 MR |
353 | static int armada_get_temp_legacy(struct thermal_zone_device *thermal, |
354 | int *temp) | |
355 | { | |
356 | struct armada_thermal_priv *priv = thermal->devdata; | |
357 | int ret; | |
358 | ||
68b14828 | 359 | /* Valid check */ |
70bb27b7 | 360 | if (!armada_is_valid(priv)) { |
68b14828 MR |
361 | dev_err(priv->dev, |
362 | "Temperature sensor reading not valid\n"); | |
363 | return -EIO; | |
364 | } | |
365 | ||
c9899c18 MR |
366 | /* Do the actual reading */ |
367 | ret = armada_read_sensor(priv, temp); | |
368 | ||
369 | return ret; | |
370 | } | |
371 | ||
372 | static struct thermal_zone_device_ops legacy_ops = { | |
373 | .get_temp = armada_get_temp_legacy, | |
374 | }; | |
375 | ||
376 | static int armada_get_temp(void *_sensor, int *temp) | |
377 | { | |
378 | struct armada_thermal_sensor *sensor = _sensor; | |
379 | struct armada_thermal_priv *priv = sensor->priv; | |
f7c2068a MR |
380 | int ret; |
381 | ||
382 | mutex_lock(&priv->update_lock); | |
383 | ||
384 | /* Select the desired channel */ | |
385 | ret = armada_select_channel(priv, sensor->id); | |
386 | if (ret) | |
387 | goto unlock_mutex; | |
c9899c18 MR |
388 | |
389 | /* Do the actual reading */ | |
f7c2068a MR |
390 | ret = armada_read_sensor(priv, temp); |
391 | ||
392 | unlock_mutex: | |
393 | mutex_unlock(&priv->update_lock); | |
394 | ||
395 | return ret; | |
c9899c18 MR |
396 | } |
397 | ||
13cfb713 | 398 | static const struct thermal_zone_of_device_ops of_ops = { |
fa0d654c EG |
399 | .get_temp = armada_get_temp, |
400 | }; | |
401 | ||
66fdb7b6 | 402 | static const struct armada_thermal_data armadaxp_data = { |
8b4c2712 | 403 | .init = armadaxp_init, |
1fcacca4 EG |
404 | .temp_shift = 10, |
405 | .temp_mask = 0x1ff, | |
2ff12799 BS |
406 | .coef_b = 3153000000ULL, |
407 | .coef_m = 10000000ULL, | |
9484bc62 | 408 | .coef_div = 13825, |
3d4e5184 MR |
409 | .syscon_status_off = 0xb0, |
410 | .syscon_control1_off = 0xd0, | |
fa0d654c EG |
411 | }; |
412 | ||
66fdb7b6 | 413 | static const struct armada_thermal_data armada370_data = { |
8b4c2712 | 414 | .init = armada370_init, |
27d92f27 | 415 | .is_valid_bit = BIT(9), |
1fcacca4 EG |
416 | .temp_shift = 10, |
417 | .temp_mask = 0x1ff, | |
2ff12799 BS |
418 | .coef_b = 3153000000ULL, |
419 | .coef_m = 10000000ULL, | |
9484bc62 | 420 | .coef_div = 13825, |
3d4e5184 MR |
421 | .syscon_status_off = 0x0, |
422 | .syscon_control1_off = 0x4, | |
fa0d654c EG |
423 | }; |
424 | ||
e2d5f05b | 425 | static const struct armada_thermal_data armada375_data = { |
8b4c2712 | 426 | .init = armada375_init, |
27d92f27 | 427 | .is_valid_bit = BIT(10), |
e2d5f05b EG |
428 | .temp_shift = 0, |
429 | .temp_mask = 0x1ff, | |
2ff12799 BS |
430 | .coef_b = 3171900000ULL, |
431 | .coef_m = 10000000ULL, | |
e2d5f05b | 432 | .coef_div = 13616, |
3d4e5184 MR |
433 | .syscon_status_off = 0x78, |
434 | .syscon_control0_off = 0x7c, | |
435 | .syscon_control1_off = 0x80, | |
e2d5f05b EG |
436 | }; |
437 | ||
e6e0a68c | 438 | static const struct armada_thermal_data armada380_data = { |
8b4c2712 | 439 | .init = armada380_init, |
27d92f27 | 440 | .is_valid_bit = BIT(10), |
e6e0a68c EG |
441 | .temp_shift = 0, |
442 | .temp_mask = 0x3ff, | |
2ff12799 BS |
443 | .coef_b = 1172499100ULL, |
444 | .coef_m = 2000096ULL, | |
b56100db | 445 | .coef_div = 4201, |
e6e0a68c | 446 | .inverted = true, |
3d4e5184 MR |
447 | .syscon_control0_off = 0x70, |
448 | .syscon_control1_off = 0x74, | |
449 | .syscon_status_off = 0x78, | |
e6e0a68c EG |
450 | }; |
451 | ||
2ff12799 | 452 | static const struct armada_thermal_data armada_ap806_data = { |
8b4c2712 | 453 | .init = armada_ap806_init, |
2ff12799 BS |
454 | .is_valid_bit = BIT(16), |
455 | .temp_shift = 0, | |
456 | .temp_mask = 0x3ff, | |
457 | .coef_b = -150000LL, | |
458 | .coef_m = 423ULL, | |
459 | .coef_div = 1, | |
460 | .inverted = true, | |
461 | .signed_sample = true, | |
3d4e5184 MR |
462 | .syscon_control0_off = 0x84, |
463 | .syscon_control1_off = 0x88, | |
464 | .syscon_status_off = 0x8C, | |
f7c2068a | 465 | .cpu_nr = 4, |
2ff12799 BS |
466 | }; |
467 | ||
ccf8f522 | 468 | static const struct armada_thermal_data armada_cp110_data = { |
5b5e17a1 | 469 | .init = armada_cp110_init, |
ccf8f522 BS |
470 | .is_valid_bit = BIT(10), |
471 | .temp_shift = 0, | |
472 | .temp_mask = 0x3ff, | |
473 | .coef_b = 1172499100ULL, | |
474 | .coef_m = 2000096ULL, | |
475 | .coef_div = 4201, | |
476 | .inverted = true, | |
3d4e5184 MR |
477 | .syscon_control0_off = 0x70, |
478 | .syscon_control1_off = 0x74, | |
479 | .syscon_status_off = 0x78, | |
ccf8f522 BS |
480 | }; |
481 | ||
fa0d654c EG |
482 | static const struct of_device_id armada_thermal_id_table[] = { |
483 | { | |
484 | .compatible = "marvell,armadaxp-thermal", | |
66fdb7b6 | 485 | .data = &armadaxp_data, |
fa0d654c EG |
486 | }, |
487 | { | |
488 | .compatible = "marvell,armada370-thermal", | |
66fdb7b6 | 489 | .data = &armada370_data, |
fa0d654c | 490 | }, |
e2d5f05b EG |
491 | { |
492 | .compatible = "marvell,armada375-thermal", | |
493 | .data = &armada375_data, | |
494 | }, | |
e6e0a68c EG |
495 | { |
496 | .compatible = "marvell,armada380-thermal", | |
497 | .data = &armada380_data, | |
498 | }, | |
2ff12799 BS |
499 | { |
500 | .compatible = "marvell,armada-ap806-thermal", | |
501 | .data = &armada_ap806_data, | |
502 | }, | |
ccf8f522 BS |
503 | { |
504 | .compatible = "marvell,armada-cp110-thermal", | |
505 | .data = &armada_cp110_data, | |
506 | }, | |
fa0d654c EG |
507 | { |
508 | /* sentinel */ | |
509 | }, | |
510 | }; | |
511 | MODULE_DEVICE_TABLE(of, armada_thermal_id_table); | |
512 | ||
3d4e5184 MR |
513 | static const struct regmap_config armada_thermal_regmap_config = { |
514 | .reg_bits = 32, | |
515 | .reg_stride = 4, | |
516 | .val_bits = 32, | |
517 | .fast_io = true, | |
518 | }; | |
519 | ||
520 | static int armada_thermal_probe_legacy(struct platform_device *pdev, | |
521 | struct armada_thermal_priv *priv) | |
522 | { | |
523 | struct armada_thermal_data *data = priv->data; | |
524 | struct resource *res; | |
525 | void __iomem *base; | |
526 | ||
527 | /* First memory region points towards the status register */ | |
528 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
3d4e5184 MR |
529 | base = devm_ioremap_resource(&pdev->dev, res); |
530 | if (IS_ERR(base)) | |
531 | return PTR_ERR(base); | |
532 | ||
dc6946cb RK |
533 | /* |
534 | * Fix up from the old individual DT register specification to | |
535 | * cover all the registers. We do this by adjusting the ioremap() | |
536 | * result, which should be fine as ioremap() deals with pages. | |
537 | * However, validate that we do not cross a page boundary while | |
538 | * making this adjustment. | |
539 | */ | |
540 | if (((unsigned long)base & ~PAGE_MASK) < data->syscon_status_off) | |
541 | return -EINVAL; | |
542 | base -= data->syscon_status_off; | |
543 | ||
3d4e5184 MR |
544 | priv->syscon = devm_regmap_init_mmio(&pdev->dev, base, |
545 | &armada_thermal_regmap_config); | |
546 | if (IS_ERR(priv->syscon)) | |
547 | return PTR_ERR(priv->syscon); | |
548 | ||
549 | return 0; | |
550 | } | |
551 | ||
552 | static int armada_thermal_probe_syscon(struct platform_device *pdev, | |
553 | struct armada_thermal_priv *priv) | |
554 | { | |
555 | priv->syscon = syscon_node_to_regmap(pdev->dev.parent->of_node); | |
556 | if (IS_ERR(priv->syscon)) | |
557 | return PTR_ERR(priv->syscon); | |
558 | ||
559 | return 0; | |
560 | } | |
561 | ||
8d98761a MR |
562 | static void armada_set_sane_name(struct platform_device *pdev, |
563 | struct armada_thermal_priv *priv) | |
564 | { | |
565 | const char *name = dev_name(&pdev->dev); | |
566 | char *insane_char; | |
567 | ||
568 | if (strlen(name) > THERMAL_NAME_LENGTH) { | |
569 | /* | |
570 | * When inside a system controller, the device name has the | |
571 | * form: f06f8000.system-controller:ap-thermal so stripping | |
572 | * after the ':' should give us a shorter but meaningful name. | |
573 | */ | |
574 | name = strrchr(name, ':'); | |
575 | if (!name) | |
576 | name = "armada_thermal"; | |
577 | else | |
578 | name++; | |
579 | } | |
580 | ||
581 | /* Save the name locally */ | |
582 | strncpy(priv->zone_name, name, THERMAL_NAME_LENGTH - 1); | |
583 | priv->zone_name[THERMAL_NAME_LENGTH - 1] = '\0'; | |
584 | ||
585 | /* Then check there are no '-' or hwmon core will complain */ | |
586 | do { | |
587 | insane_char = strpbrk(priv->zone_name, "-"); | |
588 | if (insane_char) | |
589 | *insane_char = '_'; | |
590 | } while (insane_char); | |
591 | } | |
592 | ||
fa0d654c EG |
593 | static int armada_thermal_probe(struct platform_device *pdev) |
594 | { | |
c9899c18 | 595 | struct thermal_zone_device *tz; |
f7c2068a | 596 | struct armada_thermal_sensor *sensor; |
c9899c18 | 597 | struct armada_drvdata *drvdata; |
fa0d654c EG |
598 | const struct of_device_id *match; |
599 | struct armada_thermal_priv *priv; | |
f7c2068a | 600 | int sensor_id; |
3d4e5184 | 601 | int ret; |
fa0d654c EG |
602 | |
603 | match = of_match_device(armada_thermal_id_table, &pdev->dev); | |
604 | if (!match) | |
605 | return -ENODEV; | |
606 | ||
607 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | |
608 | if (!priv) | |
609 | return -ENOMEM; | |
610 | ||
c9899c18 | 611 | drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); |
84b64de5 | 612 | if (!drvdata) |
c9899c18 | 613 | return -ENOMEM; |
2f28e4c2 | 614 | |
c9899c18 MR |
615 | priv->dev = &pdev->dev; |
616 | priv->data = (struct armada_thermal_data *)match->data; | |
8d98761a | 617 | |
f7c2068a MR |
618 | mutex_init(&priv->update_lock); |
619 | ||
2f28e4c2 MR |
620 | /* |
621 | * Legacy DT bindings only described "control1" register (also referred | |
3d4e5184 | 622 | * as "control MSB" on old documentation). Then, bindings moved to cover |
2f28e4c2 | 623 | * "control0/control LSB" and "control1/control MSB" registers within |
3d4e5184 MR |
624 | * the same resource, which was then of size 8 instead of 4. |
625 | * | |
626 | * The logic of defining sporadic registers is broken. For instance, it | |
627 | * blocked the addition of the overheat interrupt feature that needed | |
628 | * another resource somewhere else in the same memory area. One solution | |
629 | * is to define an overall system controller and put the thermal node | |
630 | * into it, which requires the use of regmaps across all the driver. | |
2f28e4c2 | 631 | */ |
c9899c18 MR |
632 | if (IS_ERR(syscon_node_to_regmap(pdev->dev.parent->of_node))) { |
633 | /* Ensure device name is correct for the thermal core */ | |
634 | armada_set_sane_name(pdev, priv); | |
635 | ||
3d4e5184 | 636 | ret = armada_thermal_probe_legacy(pdev, priv); |
c9899c18 MR |
637 | if (ret) |
638 | return ret; | |
3d4e5184 | 639 | |
c9899c18 MR |
640 | priv->data->init(pdev, priv); |
641 | ||
00707e4c MR |
642 | /* Wait the sensors to be valid */ |
643 | armada_wait_sensor_validity(priv); | |
644 | ||
c9899c18 MR |
645 | tz = thermal_zone_device_register(priv->zone_name, 0, 0, priv, |
646 | &legacy_ops, NULL, 0, 0); | |
647 | if (IS_ERR(tz)) { | |
648 | dev_err(&pdev->dev, | |
649 | "Failed to register thermal zone device\n"); | |
650 | return PTR_ERR(tz); | |
651 | } | |
652 | ||
653 | drvdata->type = LEGACY; | |
654 | drvdata->data.tz = tz; | |
655 | platform_set_drvdata(pdev, drvdata); | |
656 | ||
657 | return 0; | |
658 | } | |
659 | ||
660 | ret = armada_thermal_probe_syscon(pdev, priv); | |
3d4e5184 MR |
661 | if (ret) |
662 | return ret; | |
2f28e4c2 | 663 | |
f7c2068a | 664 | priv->current_channel = -1; |
8b4c2712 | 665 | priv->data->init(pdev, priv); |
c9899c18 MR |
666 | drvdata->type = SYSCON; |
667 | drvdata->data.priv = priv; | |
668 | platform_set_drvdata(pdev, drvdata); | |
fa0d654c | 669 | |
f7c2068a MR |
670 | /* |
671 | * There is one channel for the IC and one per CPU (if any), each | |
672 | * channel has one sensor. | |
673 | */ | |
674 | for (sensor_id = 0; sensor_id <= priv->data->cpu_nr; sensor_id++) { | |
675 | sensor = devm_kzalloc(&pdev->dev, | |
676 | sizeof(struct armada_thermal_sensor), | |
677 | GFP_KERNEL); | |
678 | if (!sensor) | |
679 | return -ENOMEM; | |
680 | ||
681 | /* Register the sensor */ | |
682 | sensor->priv = priv; | |
683 | sensor->id = sensor_id; | |
684 | tz = devm_thermal_zone_of_sensor_register(&pdev->dev, | |
685 | sensor->id, sensor, | |
686 | &of_ops); | |
687 | if (IS_ERR(tz)) { | |
688 | dev_info(&pdev->dev, "Thermal sensor %d unavailable\n", | |
689 | sensor_id); | |
690 | devm_kfree(&pdev->dev, sensor); | |
691 | continue; | |
692 | } | |
fa0d654c EG |
693 | } |
694 | ||
fa0d654c EG |
695 | return 0; |
696 | } | |
697 | ||
698 | static int armada_thermal_exit(struct platform_device *pdev) | |
699 | { | |
c9899c18 | 700 | struct armada_drvdata *drvdata = platform_get_drvdata(pdev); |
fa0d654c | 701 | |
c9899c18 MR |
702 | if (drvdata->type == LEGACY) |
703 | thermal_zone_device_unregister(drvdata->data.tz); | |
fa0d654c EG |
704 | |
705 | return 0; | |
706 | } | |
707 | ||
708 | static struct platform_driver armada_thermal_driver = { | |
709 | .probe = armada_thermal_probe, | |
710 | .remove = armada_thermal_exit, | |
711 | .driver = { | |
712 | .name = "armada_thermal", | |
1d089e09 | 713 | .of_match_table = armada_thermal_id_table, |
fa0d654c EG |
714 | }, |
715 | }; | |
716 | ||
717 | module_platform_driver(armada_thermal_driver); | |
718 | ||
719 | MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>"); | |
a9d58a1a | 720 | MODULE_DESCRIPTION("Marvell EBU Armada SoCs thermal driver"); |
fa0d654c | 721 | MODULE_LICENSE("GPL v2"); |