Commit | Line | Data |
---|---|---|
9d97e5c8 | 1 | /* |
59dfa54c | 2 | * exynos_tmu.c - Samsung EXYNOS TMU (Thermal Management Unit) |
9d97e5c8 DK |
3 | * |
4 | * Copyright (C) 2011 Samsung Electronics | |
5 | * Donggeun Kim <dg77.kim@samsung.com> | |
c48cbba6 | 6 | * Amit Daniel Kachhap <amit.kachhap@linaro.org> |
9d97e5c8 DK |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 | * | |
22 | */ | |
23 | ||
9d97e5c8 | 24 | #include <linux/clk.h> |
9d97e5c8 | 25 | #include <linux/io.h> |
1b678641 ADK |
26 | #include <linux/interrupt.h> |
27 | #include <linux/module.h> | |
f22d9c03 | 28 | #include <linux/of.h> |
cebe7373 ADK |
29 | #include <linux/of_address.h> |
30 | #include <linux/of_irq.h> | |
1b678641 | 31 | #include <linux/platform_device.h> |
498d22f6 | 32 | #include <linux/regulator/consumer.h> |
1b678641 ADK |
33 | |
34 | #include "exynos_thermal_common.h" | |
0c1836a6 | 35 | #include "exynos_tmu.h" |
e6b7991e | 36 | #include "exynos_tmu_data.h" |
f22d9c03 | 37 | |
cebe7373 ADK |
38 | /** |
39 | * struct exynos_tmu_data : A structure to hold the private data of the TMU | |
40 | driver | |
41 | * @id: identifier of the one instance of the TMU controller. | |
42 | * @pdata: pointer to the tmu platform/configuration data | |
43 | * @base: base address of the single instance of the TMU controller. | |
9025d563 | 44 | * @base_second: base address of the common registers of the TMU controller. |
cebe7373 ADK |
45 | * @irq: irq number of the TMU controller. |
46 | * @soc: id of the SOC type. | |
47 | * @irq_work: pointer to the irq work structure. | |
48 | * @lock: lock to implement synchronization. | |
49 | * @clk: pointer to the clock structure. | |
14a11dc7 | 50 | * @clk_sec: pointer to the clock structure for accessing the base_second. |
cebe7373 ADK |
51 | * @temp_error1: fused value of the first point trim. |
52 | * @temp_error2: fused value of the second point trim. | |
498d22f6 | 53 | * @regulator: pointer to the TMU regulator structure. |
cebe7373 ADK |
54 | * @reg_conf: pointer to structure to register with core thermal. |
55 | */ | |
f22d9c03 | 56 | struct exynos_tmu_data { |
cebe7373 | 57 | int id; |
f22d9c03 | 58 | struct exynos_tmu_platform_data *pdata; |
9d97e5c8 | 59 | void __iomem *base; |
9025d563 | 60 | void __iomem *base_second; |
9d97e5c8 | 61 | int irq; |
f22d9c03 | 62 | enum soc_type soc; |
9d97e5c8 DK |
63 | struct work_struct irq_work; |
64 | struct mutex lock; | |
14a11dc7 | 65 | struct clk *clk, *clk_sec; |
9d97e5c8 | 66 | u8 temp_error1, temp_error2; |
498d22f6 | 67 | struct regulator *regulator; |
cebe7373 | 68 | struct thermal_sensor_conf *reg_conf; |
9d97e5c8 DK |
69 | }; |
70 | ||
71 | /* | |
72 | * TMU treats temperature as a mapped temperature code. | |
73 | * The temperature is converted differently depending on the calibration type. | |
74 | */ | |
f22d9c03 | 75 | static int temp_to_code(struct exynos_tmu_data *data, u8 temp) |
9d97e5c8 | 76 | { |
f22d9c03 | 77 | struct exynos_tmu_platform_data *pdata = data->pdata; |
9d97e5c8 DK |
78 | int temp_code; |
79 | ||
1928457e ADK |
80 | if (pdata->cal_mode == HW_MODE) |
81 | return temp; | |
82 | ||
f22d9c03 ADK |
83 | if (data->soc == SOC_ARCH_EXYNOS4210) |
84 | /* temp should range between 25 and 125 */ | |
85 | if (temp < 25 || temp > 125) { | |
86 | temp_code = -EINVAL; | |
87 | goto out; | |
88 | } | |
9d97e5c8 DK |
89 | |
90 | switch (pdata->cal_type) { | |
91 | case TYPE_TWO_POINT_TRIMMING: | |
bb34b4c8 ADK |
92 | temp_code = (temp - pdata->first_point_trim) * |
93 | (data->temp_error2 - data->temp_error1) / | |
94 | (pdata->second_point_trim - pdata->first_point_trim) + | |
95 | data->temp_error1; | |
9d97e5c8 DK |
96 | break; |
97 | case TYPE_ONE_POINT_TRIMMING: | |
bb34b4c8 | 98 | temp_code = temp + data->temp_error1 - pdata->first_point_trim; |
9d97e5c8 DK |
99 | break; |
100 | default: | |
bb34b4c8 | 101 | temp_code = temp + pdata->default_temp_offset; |
9d97e5c8 DK |
102 | break; |
103 | } | |
104 | out: | |
105 | return temp_code; | |
106 | } | |
107 | ||
108 | /* | |
109 | * Calculate a temperature value from a temperature code. | |
110 | * The unit of the temperature is degree Celsius. | |
111 | */ | |
f22d9c03 | 112 | static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code) |
9d97e5c8 | 113 | { |
f22d9c03 | 114 | struct exynos_tmu_platform_data *pdata = data->pdata; |
9d97e5c8 DK |
115 | int temp; |
116 | ||
1928457e ADK |
117 | if (pdata->cal_mode == HW_MODE) |
118 | return temp_code; | |
119 | ||
f22d9c03 ADK |
120 | if (data->soc == SOC_ARCH_EXYNOS4210) |
121 | /* temp_code should range between 75 and 175 */ | |
122 | if (temp_code < 75 || temp_code > 175) { | |
123 | temp = -ENODATA; | |
124 | goto out; | |
125 | } | |
9d97e5c8 DK |
126 | |
127 | switch (pdata->cal_type) { | |
128 | case TYPE_TWO_POINT_TRIMMING: | |
bb34b4c8 ADK |
129 | temp = (temp_code - data->temp_error1) * |
130 | (pdata->second_point_trim - pdata->first_point_trim) / | |
131 | (data->temp_error2 - data->temp_error1) + | |
132 | pdata->first_point_trim; | |
9d97e5c8 DK |
133 | break; |
134 | case TYPE_ONE_POINT_TRIMMING: | |
bb34b4c8 | 135 | temp = temp_code - data->temp_error1 + pdata->first_point_trim; |
9d97e5c8 DK |
136 | break; |
137 | default: | |
bb34b4c8 | 138 | temp = temp_code - pdata->default_temp_offset; |
9d97e5c8 DK |
139 | break; |
140 | } | |
141 | out: | |
142 | return temp; | |
143 | } | |
144 | ||
f22d9c03 | 145 | static int exynos_tmu_initialize(struct platform_device *pdev) |
9d97e5c8 | 146 | { |
f22d9c03 ADK |
147 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
148 | struct exynos_tmu_platform_data *pdata = data->pdata; | |
b8d582b9 | 149 | const struct exynos_tmu_registers *reg = pdata->registers; |
7ca04e58 | 150 | unsigned int status, trim_info = 0, con; |
4f0a6847 JL |
151 | unsigned int rising_threshold = 0, falling_threshold = 0; |
152 | int ret = 0, threshold_code, i, trigger_levs = 0; | |
9d97e5c8 DK |
153 | |
154 | mutex_lock(&data->lock); | |
155 | clk_enable(data->clk); | |
14a11dc7 NKC |
156 | if (!IS_ERR(data->clk_sec)) |
157 | clk_enable(data->clk_sec); | |
9d97e5c8 | 158 | |
f4dae753 ADK |
159 | if (TMU_SUPPORTS(pdata, READY_STATUS)) { |
160 | status = readb(data->base + reg->tmu_status); | |
161 | if (!status) { | |
162 | ret = -EBUSY; | |
163 | goto out; | |
164 | } | |
9d97e5c8 DK |
165 | } |
166 | ||
f4dae753 | 167 | if (TMU_SUPPORTS(pdata, TRIM_RELOAD)) |
b8d582b9 ADK |
168 | __raw_writel(1, data->base + reg->triminfo_ctrl); |
169 | ||
1928457e ADK |
170 | if (pdata->cal_mode == HW_MODE) |
171 | goto skip_calib_data; | |
172 | ||
9d97e5c8 | 173 | /* Save trimming info in order to perform calibration */ |
a0395eee ADK |
174 | if (data->soc == SOC_ARCH_EXYNOS5440) { |
175 | /* | |
176 | * For exynos5440 soc triminfo value is swapped between TMU0 and | |
177 | * TMU2, so the below logic is needed. | |
178 | */ | |
179 | switch (data->id) { | |
180 | case 0: | |
181 | trim_info = readl(data->base + | |
182 | EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data); | |
183 | break; | |
184 | case 1: | |
185 | trim_info = readl(data->base + reg->triminfo_data); | |
186 | break; | |
187 | case 2: | |
188 | trim_info = readl(data->base - | |
189 | EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data); | |
190 | } | |
191 | } else { | |
14a11dc7 NKC |
192 | /* On exynos5420 the triminfo register is in the shared space */ |
193 | if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) | |
194 | trim_info = readl(data->base_second + | |
195 | reg->triminfo_data); | |
196 | else | |
197 | trim_info = readl(data->base + reg->triminfo_data); | |
a0395eee | 198 | } |
b8d582b9 ADK |
199 | data->temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; |
200 | data->temp_error2 = ((trim_info >> reg->triminfo_85_shift) & | |
201 | EXYNOS_TMU_TEMP_MASK); | |
f22d9c03 | 202 | |
5000806c ADK |
203 | if (!data->temp_error1 || |
204 | (pdata->min_efuse_value > data->temp_error1) || | |
205 | (data->temp_error1 > pdata->max_efuse_value)) | |
206 | data->temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK; | |
207 | ||
208 | if (!data->temp_error2) | |
209 | data->temp_error2 = | |
210 | (pdata->efuse_value >> reg->triminfo_85_shift) & | |
211 | EXYNOS_TMU_TEMP_MASK; | |
f22d9c03 | 212 | |
1928457e | 213 | skip_calib_data: |
7ca04e58 ADK |
214 | if (pdata->max_trigger_level > MAX_THRESHOLD_LEVS) { |
215 | dev_err(&pdev->dev, "Invalid max trigger level\n"); | |
60acb389 | 216 | ret = -EINVAL; |
7ca04e58 ADK |
217 | goto out; |
218 | } | |
219 | ||
220 | for (i = 0; i < pdata->max_trigger_level; i++) { | |
221 | if (!pdata->trigger_levels[i]) | |
222 | continue; | |
223 | ||
224 | if ((pdata->trigger_type[i] == HW_TRIP) && | |
225 | (!pdata->trigger_levels[pdata->max_trigger_level - 1])) { | |
226 | dev_err(&pdev->dev, "Invalid hw trigger level\n"); | |
227 | ret = -EINVAL; | |
228 | goto out; | |
229 | } | |
230 | ||
231 | /* Count trigger levels except the HW trip*/ | |
232 | if (!(pdata->trigger_type[i] == HW_TRIP)) | |
4f0a6847 | 233 | trigger_levs++; |
7ca04e58 | 234 | } |
4f0a6847 | 235 | |
c65d3473 TB |
236 | rising_threshold = readl(data->base + reg->threshold_th0); |
237 | ||
f22d9c03 ADK |
238 | if (data->soc == SOC_ARCH_EXYNOS4210) { |
239 | /* Write temperature code for threshold */ | |
240 | threshold_code = temp_to_code(data, pdata->threshold); | |
241 | if (threshold_code < 0) { | |
242 | ret = threshold_code; | |
243 | goto out; | |
244 | } | |
245 | writeb(threshold_code, | |
b8d582b9 | 246 | data->base + reg->threshold_temp); |
4f0a6847 | 247 | for (i = 0; i < trigger_levs; i++) |
b8d582b9 ADK |
248 | writeb(pdata->trigger_levels[i], data->base + |
249 | reg->threshold_th0 + i * sizeof(reg->threshold_th0)); | |
f22d9c03 | 250 | |
74429c2f | 251 | writel(reg->intclr_rise_mask, data->base + reg->tmu_intclear); |
a0395eee | 252 | } else { |
4f0a6847 | 253 | /* Write temperature code for rising and falling threshold */ |
7ca04e58 ADK |
254 | for (i = 0; |
255 | i < trigger_levs && i < EXYNOS_MAX_TRIGGER_PER_REG; i++) { | |
4f0a6847 JL |
256 | threshold_code = temp_to_code(data, |
257 | pdata->trigger_levels[i]); | |
258 | if (threshold_code < 0) { | |
259 | ret = threshold_code; | |
260 | goto out; | |
261 | } | |
c65d3473 | 262 | rising_threshold &= ~(0xff << 8 * i); |
4f0a6847 JL |
263 | rising_threshold |= threshold_code << 8 * i; |
264 | if (pdata->threshold_falling) { | |
265 | threshold_code = temp_to_code(data, | |
266 | pdata->trigger_levels[i] - | |
267 | pdata->threshold_falling); | |
268 | if (threshold_code > 0) | |
269 | falling_threshold |= | |
270 | threshold_code << 8 * i; | |
271 | } | |
f22d9c03 | 272 | } |
f22d9c03 ADK |
273 | |
274 | writel(rising_threshold, | |
b8d582b9 | 275 | data->base + reg->threshold_th0); |
4f0a6847 | 276 | writel(falling_threshold, |
b8d582b9 | 277 | data->base + reg->threshold_th1); |
f22d9c03 | 278 | |
74429c2f NKC |
279 | writel((reg->intclr_rise_mask << reg->intclr_rise_shift) | |
280 | (reg->intclr_fall_mask << reg->intclr_fall_shift), | |
b8d582b9 | 281 | data->base + reg->tmu_intclear); |
7ca04e58 ADK |
282 | |
283 | /* if last threshold limit is also present */ | |
284 | i = pdata->max_trigger_level - 1; | |
285 | if (pdata->trigger_levels[i] && | |
286 | (pdata->trigger_type[i] == HW_TRIP)) { | |
287 | threshold_code = temp_to_code(data, | |
288 | pdata->trigger_levels[i]); | |
289 | if (threshold_code < 0) { | |
290 | ret = threshold_code; | |
291 | goto out; | |
292 | } | |
a0395eee ADK |
293 | if (i == EXYNOS_MAX_TRIGGER_PER_REG - 1) { |
294 | /* 1-4 level to be assigned in th0 reg */ | |
c65d3473 | 295 | rising_threshold &= ~(0xff << 8 * i); |
a0395eee ADK |
296 | rising_threshold |= threshold_code << 8 * i; |
297 | writel(rising_threshold, | |
298 | data->base + reg->threshold_th0); | |
299 | } else if (i == EXYNOS_MAX_TRIGGER_PER_REG) { | |
300 | /* 5th level to be assigned in th2 reg */ | |
301 | rising_threshold = | |
302 | threshold_code << reg->threshold_th3_l0_shift; | |
303 | writel(rising_threshold, | |
304 | data->base + reg->threshold_th2); | |
305 | } | |
7ca04e58 ADK |
306 | con = readl(data->base + reg->tmu_ctrl); |
307 | con |= (1 << reg->therm_trip_en_shift); | |
308 | writel(con, data->base + reg->tmu_ctrl); | |
309 | } | |
9d97e5c8 | 310 | } |
a0395eee ADK |
311 | /*Clear the PMIN in the common TMU register*/ |
312 | if (reg->tmu_pmin && !data->id) | |
9025d563 | 313 | writel(0, data->base_second + reg->tmu_pmin); |
9d97e5c8 DK |
314 | out: |
315 | clk_disable(data->clk); | |
316 | mutex_unlock(&data->lock); | |
14a11dc7 NKC |
317 | if (!IS_ERR(data->clk_sec)) |
318 | clk_disable(data->clk_sec); | |
9d97e5c8 DK |
319 | |
320 | return ret; | |
321 | } | |
322 | ||
f22d9c03 | 323 | static void exynos_tmu_control(struct platform_device *pdev, bool on) |
9d97e5c8 | 324 | { |
f22d9c03 ADK |
325 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
326 | struct exynos_tmu_platform_data *pdata = data->pdata; | |
b8d582b9 | 327 | const struct exynos_tmu_registers *reg = pdata->registers; |
1928457e | 328 | unsigned int con, interrupt_en, cal_val; |
9d97e5c8 DK |
329 | |
330 | mutex_lock(&data->lock); | |
331 | clk_enable(data->clk); | |
332 | ||
b8d582b9 | 333 | con = readl(data->base + reg->tmu_ctrl); |
f22d9c03 | 334 | |
86f5362e LM |
335 | if (pdata->test_mux) |
336 | con |= (pdata->test_mux << reg->test_mux_addr_shift); | |
337 | ||
d0a0ce3e | 338 | if (pdata->reference_voltage) { |
b8d582b9 ADK |
339 | con &= ~(reg->buf_vref_sel_mask << reg->buf_vref_sel_shift); |
340 | con |= pdata->reference_voltage << reg->buf_vref_sel_shift; | |
d0a0ce3e ADK |
341 | } |
342 | ||
343 | if (pdata->gain) { | |
b8d582b9 ADK |
344 | con &= ~(reg->buf_slope_sel_mask << reg->buf_slope_sel_shift); |
345 | con |= (pdata->gain << reg->buf_slope_sel_shift); | |
d0a0ce3e ADK |
346 | } |
347 | ||
348 | if (pdata->noise_cancel_mode) { | |
b8d582b9 ADK |
349 | con &= ~(reg->therm_trip_mode_mask << |
350 | reg->therm_trip_mode_shift); | |
351 | con |= (pdata->noise_cancel_mode << reg->therm_trip_mode_shift); | |
f22d9c03 ADK |
352 | } |
353 | ||
1928457e ADK |
354 | if (pdata->cal_mode == HW_MODE) { |
355 | con &= ~(reg->calib_mode_mask << reg->calib_mode_shift); | |
356 | cal_val = 0; | |
357 | switch (pdata->cal_type) { | |
358 | case TYPE_TWO_POINT_TRIMMING: | |
359 | cal_val = 3; | |
360 | break; | |
361 | case TYPE_ONE_POINT_TRIMMING_85: | |
362 | cal_val = 2; | |
363 | break; | |
364 | case TYPE_ONE_POINT_TRIMMING_25: | |
365 | cal_val = 1; | |
366 | break; | |
367 | case TYPE_NONE: | |
368 | break; | |
369 | default: | |
370 | dev_err(&pdev->dev, "Invalid calibration type, using none\n"); | |
371 | } | |
372 | con |= cal_val << reg->calib_mode_shift; | |
373 | } | |
374 | ||
9d97e5c8 | 375 | if (on) { |
b8d582b9 | 376 | con |= (1 << reg->core_en_shift); |
d0a0ce3e | 377 | interrupt_en = |
b8d582b9 ADK |
378 | pdata->trigger_enable[3] << reg->inten_rise3_shift | |
379 | pdata->trigger_enable[2] << reg->inten_rise2_shift | | |
380 | pdata->trigger_enable[1] << reg->inten_rise1_shift | | |
381 | pdata->trigger_enable[0] << reg->inten_rise0_shift; | |
f4dae753 | 382 | if (TMU_SUPPORTS(pdata, FALLING_TRIP)) |
d0a0ce3e | 383 | interrupt_en |= |
b8d582b9 | 384 | interrupt_en << reg->inten_fall0_shift; |
9d97e5c8 | 385 | } else { |
b8d582b9 | 386 | con &= ~(1 << reg->core_en_shift); |
9d97e5c8 DK |
387 | interrupt_en = 0; /* Disable all interrupts */ |
388 | } | |
b8d582b9 ADK |
389 | writel(interrupt_en, data->base + reg->tmu_inten); |
390 | writel(con, data->base + reg->tmu_ctrl); | |
9d97e5c8 DK |
391 | |
392 | clk_disable(data->clk); | |
393 | mutex_unlock(&data->lock); | |
394 | } | |
395 | ||
f22d9c03 | 396 | static int exynos_tmu_read(struct exynos_tmu_data *data) |
9d97e5c8 | 397 | { |
b8d582b9 ADK |
398 | struct exynos_tmu_platform_data *pdata = data->pdata; |
399 | const struct exynos_tmu_registers *reg = pdata->registers; | |
9d97e5c8 DK |
400 | u8 temp_code; |
401 | int temp; | |
402 | ||
403 | mutex_lock(&data->lock); | |
404 | clk_enable(data->clk); | |
405 | ||
b8d582b9 | 406 | temp_code = readb(data->base + reg->tmu_cur_temp); |
9d97e5c8 DK |
407 | temp = code_to_temp(data, temp_code); |
408 | ||
409 | clk_disable(data->clk); | |
410 | mutex_unlock(&data->lock); | |
411 | ||
412 | return temp; | |
413 | } | |
414 | ||
bffd1f8a ADK |
415 | #ifdef CONFIG_THERMAL_EMULATION |
416 | static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) | |
417 | { | |
418 | struct exynos_tmu_data *data = drv_data; | |
b8d582b9 ADK |
419 | struct exynos_tmu_platform_data *pdata = data->pdata; |
420 | const struct exynos_tmu_registers *reg = pdata->registers; | |
421 | unsigned int val; | |
bffd1f8a ADK |
422 | int ret = -EINVAL; |
423 | ||
f4dae753 | 424 | if (!TMU_SUPPORTS(pdata, EMULATION)) |
bffd1f8a ADK |
425 | goto out; |
426 | ||
427 | if (temp && temp < MCELSIUS) | |
428 | goto out; | |
429 | ||
430 | mutex_lock(&data->lock); | |
431 | clk_enable(data->clk); | |
432 | ||
b8d582b9 | 433 | val = readl(data->base + reg->emul_con); |
bffd1f8a ADK |
434 | |
435 | if (temp) { | |
436 | temp /= MCELSIUS; | |
437 | ||
f4dae753 ADK |
438 | if (TMU_SUPPORTS(pdata, EMUL_TIME)) { |
439 | val &= ~(EXYNOS_EMUL_TIME_MASK << reg->emul_time_shift); | |
440 | val |= (EXYNOS_EMUL_TIME << reg->emul_time_shift); | |
441 | } | |
442 | val &= ~(EXYNOS_EMUL_DATA_MASK << reg->emul_temp_shift); | |
443 | val |= (temp_to_code(data, temp) << reg->emul_temp_shift) | | |
444 | EXYNOS_EMUL_ENABLE; | |
bffd1f8a | 445 | } else { |
b8d582b9 | 446 | val &= ~EXYNOS_EMUL_ENABLE; |
bffd1f8a ADK |
447 | } |
448 | ||
b8d582b9 | 449 | writel(val, data->base + reg->emul_con); |
bffd1f8a ADK |
450 | |
451 | clk_disable(data->clk); | |
452 | mutex_unlock(&data->lock); | |
453 | return 0; | |
454 | out: | |
455 | return ret; | |
456 | } | |
457 | #else | |
458 | static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) | |
459 | { return -EINVAL; } | |
460 | #endif/*CONFIG_THERMAL_EMULATION*/ | |
461 | ||
f22d9c03 | 462 | static void exynos_tmu_work(struct work_struct *work) |
9d97e5c8 | 463 | { |
f22d9c03 ADK |
464 | struct exynos_tmu_data *data = container_of(work, |
465 | struct exynos_tmu_data, irq_work); | |
b8d582b9 ADK |
466 | struct exynos_tmu_platform_data *pdata = data->pdata; |
467 | const struct exynos_tmu_registers *reg = pdata->registers; | |
a0395eee ADK |
468 | unsigned int val_irq, val_type; |
469 | ||
14a11dc7 NKC |
470 | if (!IS_ERR(data->clk_sec)) |
471 | clk_enable(data->clk_sec); | |
a0395eee ADK |
472 | /* Find which sensor generated this interrupt */ |
473 | if (reg->tmu_irqstatus) { | |
9025d563 | 474 | val_type = readl(data->base_second + reg->tmu_irqstatus); |
a0395eee ADK |
475 | if (!((val_type >> data->id) & 0x1)) |
476 | goto out; | |
477 | } | |
14a11dc7 NKC |
478 | if (!IS_ERR(data->clk_sec)) |
479 | clk_disable(data->clk_sec); | |
9d97e5c8 | 480 | |
cebe7373 | 481 | exynos_report_trigger(data->reg_conf); |
9d97e5c8 DK |
482 | mutex_lock(&data->lock); |
483 | clk_enable(data->clk); | |
b8d582b9 | 484 | |
a4463c4f ADK |
485 | /* TODO: take action based on particular interrupt */ |
486 | val_irq = readl(data->base + reg->tmu_intstat); | |
487 | /* clear the interrupts */ | |
488 | writel(val_irq, data->base + reg->tmu_intclear); | |
b8d582b9 | 489 | |
9d97e5c8 DK |
490 | clk_disable(data->clk); |
491 | mutex_unlock(&data->lock); | |
a0395eee | 492 | out: |
f22d9c03 | 493 | enable_irq(data->irq); |
9d97e5c8 DK |
494 | } |
495 | ||
f22d9c03 | 496 | static irqreturn_t exynos_tmu_irq(int irq, void *id) |
9d97e5c8 | 497 | { |
f22d9c03 | 498 | struct exynos_tmu_data *data = id; |
9d97e5c8 DK |
499 | |
500 | disable_irq_nosync(irq); | |
501 | schedule_work(&data->irq_work); | |
502 | ||
503 | return IRQ_HANDLED; | |
504 | } | |
17be868e | 505 | |
17be868e ADK |
506 | static const struct of_device_id exynos_tmu_match[] = { |
507 | { | |
508 | .compatible = "samsung,exynos4210-tmu", | |
509 | .data = (void *)EXYNOS4210_TMU_DRV_DATA, | |
510 | }, | |
b6cee53c SK |
511 | { |
512 | .compatible = "samsung,exynos4412-tmu", | |
14ddfaec | 513 | .data = (void *)EXYNOS4412_TMU_DRV_DATA, |
b6cee53c | 514 | }, |
17be868e ADK |
515 | { |
516 | .compatible = "samsung,exynos5250-tmu", | |
e6b7991e | 517 | .data = (void *)EXYNOS5250_TMU_DRV_DATA, |
17be868e | 518 | }, |
923488a5 NKC |
519 | { |
520 | .compatible = "samsung,exynos5260-tmu", | |
521 | .data = (void *)EXYNOS5260_TMU_DRV_DATA, | |
522 | }, | |
14a11dc7 NKC |
523 | { |
524 | .compatible = "samsung,exynos5420-tmu", | |
525 | .data = (void *)EXYNOS5420_TMU_DRV_DATA, | |
526 | }, | |
527 | { | |
528 | .compatible = "samsung,exynos5420-tmu-ext-triminfo", | |
529 | .data = (void *)EXYNOS5420_TMU_DRV_DATA, | |
530 | }, | |
90542546 ADK |
531 | { |
532 | .compatible = "samsung,exynos5440-tmu", | |
533 | .data = (void *)EXYNOS5440_TMU_DRV_DATA, | |
534 | }, | |
17be868e ADK |
535 | {}, |
536 | }; | |
537 | MODULE_DEVICE_TABLE(of, exynos_tmu_match); | |
17be868e | 538 | |
17be868e | 539 | static inline struct exynos_tmu_platform_data *exynos_get_driver_data( |
cebe7373 | 540 | struct platform_device *pdev, int id) |
17be868e | 541 | { |
cebe7373 ADK |
542 | struct exynos_tmu_init_data *data_table; |
543 | struct exynos_tmu_platform_data *tmu_data; | |
73b5b1d7 SK |
544 | const struct of_device_id *match; |
545 | ||
546 | match = of_match_node(exynos_tmu_match, pdev->dev.of_node); | |
547 | if (!match) | |
548 | return NULL; | |
549 | data_table = (struct exynos_tmu_init_data *) match->data; | |
550 | if (!data_table || id >= data_table->tmu_count) | |
551 | return NULL; | |
552 | tmu_data = data_table->tmu_data; | |
553 | return (struct exynos_tmu_platform_data *) (tmu_data + id); | |
7e0b55e6 | 554 | } |
bbf63be4 | 555 | |
cebe7373 | 556 | static int exynos_map_dt_data(struct platform_device *pdev) |
9d97e5c8 | 557 | { |
cebe7373 ADK |
558 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
559 | struct exynos_tmu_platform_data *pdata; | |
560 | struct resource res; | |
498d22f6 | 561 | int ret; |
cebe7373 | 562 | |
73b5b1d7 | 563 | if (!data || !pdev->dev.of_node) |
cebe7373 | 564 | return -ENODEV; |
9d97e5c8 | 565 | |
498d22f6 ADK |
566 | /* |
567 | * Try enabling the regulator if found | |
568 | * TODO: Add regulator as an SOC feature, so that regulator enable | |
569 | * is a compulsory call. | |
570 | */ | |
571 | data->regulator = devm_regulator_get(&pdev->dev, "vtmu"); | |
572 | if (!IS_ERR(data->regulator)) { | |
573 | ret = regulator_enable(data->regulator); | |
574 | if (ret) { | |
575 | dev_err(&pdev->dev, "failed to enable vtmu\n"); | |
576 | return ret; | |
577 | } | |
578 | } else { | |
579 | dev_info(&pdev->dev, "Regulator node (vtmu) not found\n"); | |
580 | } | |
581 | ||
cebe7373 ADK |
582 | data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl"); |
583 | if (data->id < 0) | |
584 | data->id = 0; | |
17be868e | 585 | |
cebe7373 ADK |
586 | data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); |
587 | if (data->irq <= 0) { | |
588 | dev_err(&pdev->dev, "failed to get IRQ\n"); | |
589 | return -ENODEV; | |
590 | } | |
591 | ||
592 | if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { | |
593 | dev_err(&pdev->dev, "failed to get Resource 0\n"); | |
594 | return -ENODEV; | |
595 | } | |
596 | ||
597 | data->base = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); | |
598 | if (!data->base) { | |
599 | dev_err(&pdev->dev, "Failed to ioremap memory\n"); | |
600 | return -EADDRNOTAVAIL; | |
601 | } | |
602 | ||
603 | pdata = exynos_get_driver_data(pdev, data->id); | |
9d97e5c8 DK |
604 | if (!pdata) { |
605 | dev_err(&pdev->dev, "No platform init data supplied.\n"); | |
606 | return -ENODEV; | |
607 | } | |
cebe7373 | 608 | data->pdata = pdata; |
d9b6ee14 ADK |
609 | /* |
610 | * Check if the TMU shares some registers and then try to map the | |
611 | * memory of common registers. | |
612 | */ | |
9025d563 | 613 | if (!TMU_SUPPORTS(pdata, ADDRESS_MULTIPLE)) |
d9b6ee14 ADK |
614 | return 0; |
615 | ||
616 | if (of_address_to_resource(pdev->dev.of_node, 1, &res)) { | |
617 | dev_err(&pdev->dev, "failed to get Resource 1\n"); | |
618 | return -ENODEV; | |
619 | } | |
620 | ||
9025d563 | 621 | data->base_second = devm_ioremap(&pdev->dev, res.start, |
d9b6ee14 | 622 | resource_size(&res)); |
9025d563 | 623 | if (!data->base_second) { |
d9b6ee14 ADK |
624 | dev_err(&pdev->dev, "Failed to ioremap memory\n"); |
625 | return -ENOMEM; | |
626 | } | |
cebe7373 ADK |
627 | |
628 | return 0; | |
629 | } | |
630 | ||
631 | static int exynos_tmu_probe(struct platform_device *pdev) | |
632 | { | |
633 | struct exynos_tmu_data *data; | |
634 | struct exynos_tmu_platform_data *pdata; | |
635 | struct thermal_sensor_conf *sensor_conf; | |
636 | int ret, i; | |
637 | ||
79e093c3 ADK |
638 | data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data), |
639 | GFP_KERNEL); | |
2a9675b3 | 640 | if (!data) |
9d97e5c8 | 641 | return -ENOMEM; |
9d97e5c8 | 642 | |
cebe7373 ADK |
643 | platform_set_drvdata(pdev, data); |
644 | mutex_init(&data->lock); | |
9d97e5c8 | 645 | |
cebe7373 ADK |
646 | ret = exynos_map_dt_data(pdev); |
647 | if (ret) | |
648 | return ret; | |
9d97e5c8 | 649 | |
cebe7373 | 650 | pdata = data->pdata; |
9d97e5c8 | 651 | |
cebe7373 | 652 | INIT_WORK(&data->irq_work, exynos_tmu_work); |
9d97e5c8 | 653 | |
2a16279c | 654 | data->clk = devm_clk_get(&pdev->dev, "tmu_apbif"); |
9d97e5c8 | 655 | if (IS_ERR(data->clk)) { |
9d97e5c8 | 656 | dev_err(&pdev->dev, "Failed to get clock\n"); |
79e093c3 | 657 | return PTR_ERR(data->clk); |
9d97e5c8 DK |
658 | } |
659 | ||
14a11dc7 NKC |
660 | data->clk_sec = devm_clk_get(&pdev->dev, "tmu_triminfo_apbif"); |
661 | if (IS_ERR(data->clk_sec)) { | |
662 | if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) { | |
663 | dev_err(&pdev->dev, "Failed to get triminfo clock\n"); | |
664 | return PTR_ERR(data->clk_sec); | |
665 | } | |
666 | } else { | |
667 | ret = clk_prepare(data->clk_sec); | |
668 | if (ret) { | |
669 | dev_err(&pdev->dev, "Failed to get clock\n"); | |
670 | return ret; | |
671 | } | |
672 | } | |
673 | ||
2a16279c | 674 | ret = clk_prepare(data->clk); |
14a11dc7 NKC |
675 | if (ret) { |
676 | dev_err(&pdev->dev, "Failed to get clock\n"); | |
677 | goto err_clk_sec; | |
678 | } | |
2a16279c | 679 | |
14ddfaec LM |
680 | if (pdata->type == SOC_ARCH_EXYNOS4210 || |
681 | pdata->type == SOC_ARCH_EXYNOS4412 || | |
682 | pdata->type == SOC_ARCH_EXYNOS5250 || | |
923488a5 | 683 | pdata->type == SOC_ARCH_EXYNOS5260 || |
14a11dc7 | 684 | pdata->type == SOC_ARCH_EXYNOS5420_TRIMINFO || |
14ddfaec | 685 | pdata->type == SOC_ARCH_EXYNOS5440) |
f22d9c03 ADK |
686 | data->soc = pdata->type; |
687 | else { | |
688 | ret = -EINVAL; | |
689 | dev_err(&pdev->dev, "Platform not supported\n"); | |
690 | goto err_clk; | |
691 | } | |
692 | ||
f22d9c03 | 693 | ret = exynos_tmu_initialize(pdev); |
9d97e5c8 DK |
694 | if (ret) { |
695 | dev_err(&pdev->dev, "Failed to initialize TMU\n"); | |
696 | goto err_clk; | |
697 | } | |
698 | ||
f22d9c03 | 699 | exynos_tmu_control(pdev, true); |
9d97e5c8 | 700 | |
cebe7373 ADK |
701 | /* Allocate a structure to register with the exynos core thermal */ |
702 | sensor_conf = devm_kzalloc(&pdev->dev, | |
703 | sizeof(struct thermal_sensor_conf), GFP_KERNEL); | |
704 | if (!sensor_conf) { | |
cebe7373 ADK |
705 | ret = -ENOMEM; |
706 | goto err_clk; | |
707 | } | |
708 | sprintf(sensor_conf->name, "therm_zone%d", data->id); | |
709 | sensor_conf->read_temperature = (int (*)(void *))exynos_tmu_read; | |
710 | sensor_conf->write_emul_temp = | |
711 | (int (*)(void *, unsigned long))exynos_tmu_set_emulation; | |
712 | sensor_conf->driver_data = data; | |
713 | sensor_conf->trip_data.trip_count = pdata->trigger_enable[0] + | |
bb34b4c8 ADK |
714 | pdata->trigger_enable[1] + pdata->trigger_enable[2]+ |
715 | pdata->trigger_enable[3]; | |
7e0b55e6 | 716 | |
cebe7373 ADK |
717 | for (i = 0; i < sensor_conf->trip_data.trip_count; i++) { |
718 | sensor_conf->trip_data.trip_val[i] = | |
7e0b55e6 | 719 | pdata->threshold + pdata->trigger_levels[i]; |
cebe7373 | 720 | sensor_conf->trip_data.trip_type[i] = |
5c3cf552 ADK |
721 | pdata->trigger_type[i]; |
722 | } | |
7e0b55e6 | 723 | |
cebe7373 | 724 | sensor_conf->trip_data.trigger_falling = pdata->threshold_falling; |
4f0a6847 | 725 | |
cebe7373 | 726 | sensor_conf->cooling_data.freq_clip_count = pdata->freq_tab_count; |
7e0b55e6 | 727 | for (i = 0; i < pdata->freq_tab_count; i++) { |
cebe7373 | 728 | sensor_conf->cooling_data.freq_data[i].freq_clip_max = |
7e0b55e6 | 729 | pdata->freq_tab[i].freq_clip_max; |
cebe7373 | 730 | sensor_conf->cooling_data.freq_data[i].temp_level = |
7e0b55e6 ADK |
731 | pdata->freq_tab[i].temp_level; |
732 | } | |
cebe7373 ADK |
733 | sensor_conf->dev = &pdev->dev; |
734 | /* Register the sensor with thermal management interface */ | |
735 | ret = exynos_register_thermal(sensor_conf); | |
7e0b55e6 ADK |
736 | if (ret) { |
737 | dev_err(&pdev->dev, "Failed to register thermal interface\n"); | |
738 | goto err_clk; | |
739 | } | |
cebe7373 ADK |
740 | data->reg_conf = sensor_conf; |
741 | ||
742 | ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, | |
743 | IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data); | |
744 | if (ret) { | |
745 | dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); | |
746 | goto err_clk; | |
747 | } | |
bbf63be4 | 748 | |
9d97e5c8 | 749 | return 0; |
9d97e5c8 | 750 | err_clk: |
2a16279c | 751 | clk_unprepare(data->clk); |
14a11dc7 NKC |
752 | err_clk_sec: |
753 | if (!IS_ERR(data->clk_sec)) | |
754 | clk_unprepare(data->clk_sec); | |
9d97e5c8 DK |
755 | return ret; |
756 | } | |
757 | ||
4eab7a9e | 758 | static int exynos_tmu_remove(struct platform_device *pdev) |
9d97e5c8 | 759 | { |
f22d9c03 | 760 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
9d97e5c8 | 761 | |
cebe7373 | 762 | exynos_unregister_thermal(data->reg_conf); |
7e0b55e6 | 763 | |
4215688e BZ |
764 | exynos_tmu_control(pdev, false); |
765 | ||
2a16279c | 766 | clk_unprepare(data->clk); |
14a11dc7 NKC |
767 | if (!IS_ERR(data->clk_sec)) |
768 | clk_unprepare(data->clk_sec); | |
9d97e5c8 | 769 | |
498d22f6 ADK |
770 | if (!IS_ERR(data->regulator)) |
771 | regulator_disable(data->regulator); | |
772 | ||
9d97e5c8 DK |
773 | return 0; |
774 | } | |
775 | ||
08cd6753 | 776 | #ifdef CONFIG_PM_SLEEP |
f22d9c03 | 777 | static int exynos_tmu_suspend(struct device *dev) |
9d97e5c8 | 778 | { |
f22d9c03 | 779 | exynos_tmu_control(to_platform_device(dev), false); |
9d97e5c8 DK |
780 | |
781 | return 0; | |
782 | } | |
783 | ||
f22d9c03 | 784 | static int exynos_tmu_resume(struct device *dev) |
9d97e5c8 | 785 | { |
08cd6753 RW |
786 | struct platform_device *pdev = to_platform_device(dev); |
787 | ||
f22d9c03 ADK |
788 | exynos_tmu_initialize(pdev); |
789 | exynos_tmu_control(pdev, true); | |
9d97e5c8 DK |
790 | |
791 | return 0; | |
792 | } | |
08cd6753 | 793 | |
f22d9c03 ADK |
794 | static SIMPLE_DEV_PM_OPS(exynos_tmu_pm, |
795 | exynos_tmu_suspend, exynos_tmu_resume); | |
796 | #define EXYNOS_TMU_PM (&exynos_tmu_pm) | |
9d97e5c8 | 797 | #else |
f22d9c03 | 798 | #define EXYNOS_TMU_PM NULL |
9d97e5c8 DK |
799 | #endif |
800 | ||
f22d9c03 | 801 | static struct platform_driver exynos_tmu_driver = { |
9d97e5c8 | 802 | .driver = { |
f22d9c03 | 803 | .name = "exynos-tmu", |
9d97e5c8 | 804 | .owner = THIS_MODULE, |
f22d9c03 | 805 | .pm = EXYNOS_TMU_PM, |
73b5b1d7 | 806 | .of_match_table = exynos_tmu_match, |
9d97e5c8 | 807 | }, |
f22d9c03 | 808 | .probe = exynos_tmu_probe, |
4eab7a9e | 809 | .remove = exynos_tmu_remove, |
9d97e5c8 DK |
810 | }; |
811 | ||
f22d9c03 | 812 | module_platform_driver(exynos_tmu_driver); |
9d97e5c8 | 813 | |
f22d9c03 | 814 | MODULE_DESCRIPTION("EXYNOS TMU Driver"); |
9d97e5c8 DK |
815 | MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); |
816 | MODULE_LICENSE("GPL"); | |
f22d9c03 | 817 | MODULE_ALIAS("platform:exynos-tmu"); |