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