]>
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 | 54 | * @reg_conf: pointer to structure to register with core thermal. |
72d1100b | 55 | * @tmu_initialize: SoC specific TMU initialization method |
cebe7373 | 56 | */ |
f22d9c03 | 57 | struct exynos_tmu_data { |
cebe7373 | 58 | int id; |
f22d9c03 | 59 | struct exynos_tmu_platform_data *pdata; |
9d97e5c8 | 60 | void __iomem *base; |
9025d563 | 61 | void __iomem *base_second; |
9d97e5c8 | 62 | int irq; |
f22d9c03 | 63 | enum soc_type soc; |
9d97e5c8 DK |
64 | struct work_struct irq_work; |
65 | struct mutex lock; | |
14a11dc7 | 66 | struct clk *clk, *clk_sec; |
9d97e5c8 | 67 | u8 temp_error1, temp_error2; |
498d22f6 | 68 | struct regulator *regulator; |
cebe7373 | 69 | struct thermal_sensor_conf *reg_conf; |
72d1100b | 70 | int (*tmu_initialize)(struct platform_device *pdev); |
9d97e5c8 DK |
71 | }; |
72 | ||
73 | /* | |
74 | * TMU treats temperature as a mapped temperature code. | |
75 | * The temperature is converted differently depending on the calibration type. | |
76 | */ | |
f22d9c03 | 77 | static int temp_to_code(struct exynos_tmu_data *data, u8 temp) |
9d97e5c8 | 78 | { |
f22d9c03 | 79 | struct exynos_tmu_platform_data *pdata = data->pdata; |
9d97e5c8 DK |
80 | int temp_code; |
81 | ||
9d97e5c8 DK |
82 | switch (pdata->cal_type) { |
83 | case TYPE_TWO_POINT_TRIMMING: | |
bb34b4c8 ADK |
84 | temp_code = (temp - pdata->first_point_trim) * |
85 | (data->temp_error2 - data->temp_error1) / | |
86 | (pdata->second_point_trim - pdata->first_point_trim) + | |
87 | data->temp_error1; | |
9d97e5c8 DK |
88 | break; |
89 | case TYPE_ONE_POINT_TRIMMING: | |
bb34b4c8 | 90 | temp_code = temp + data->temp_error1 - pdata->first_point_trim; |
9d97e5c8 DK |
91 | break; |
92 | default: | |
bb34b4c8 | 93 | temp_code = temp + pdata->default_temp_offset; |
9d97e5c8 DK |
94 | break; |
95 | } | |
ddb31d43 | 96 | |
9d97e5c8 DK |
97 | return temp_code; |
98 | } | |
99 | ||
100 | /* | |
101 | * Calculate a temperature value from a temperature code. | |
102 | * The unit of the temperature is degree Celsius. | |
103 | */ | |
f22d9c03 | 104 | static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code) |
9d97e5c8 | 105 | { |
f22d9c03 | 106 | struct exynos_tmu_platform_data *pdata = data->pdata; |
9d97e5c8 DK |
107 | int temp; |
108 | ||
9d97e5c8 DK |
109 | switch (pdata->cal_type) { |
110 | case TYPE_TWO_POINT_TRIMMING: | |
bb34b4c8 ADK |
111 | temp = (temp_code - data->temp_error1) * |
112 | (pdata->second_point_trim - pdata->first_point_trim) / | |
113 | (data->temp_error2 - data->temp_error1) + | |
114 | pdata->first_point_trim; | |
9d97e5c8 DK |
115 | break; |
116 | case TYPE_ONE_POINT_TRIMMING: | |
bb34b4c8 | 117 | temp = temp_code - data->temp_error1 + pdata->first_point_trim; |
9d97e5c8 DK |
118 | break; |
119 | default: | |
bb34b4c8 | 120 | temp = temp_code - pdata->default_temp_offset; |
9d97e5c8 DK |
121 | break; |
122 | } | |
ddb31d43 | 123 | |
9d97e5c8 DK |
124 | return temp; |
125 | } | |
126 | ||
b835ced1 BZ |
127 | static void exynos_tmu_clear_irqs(struct exynos_tmu_data *data) |
128 | { | |
129 | const struct exynos_tmu_registers *reg = data->pdata->registers; | |
130 | unsigned int val_irq; | |
131 | ||
132 | val_irq = readl(data->base + reg->tmu_intstat); | |
133 | /* | |
134 | * Clear the interrupts. Please note that the documentation for | |
135 | * Exynos3250, Exynos4412, Exynos5250 and Exynos5260 incorrectly | |
136 | * states that INTCLEAR register has a different placing of bits | |
137 | * responsible for FALL IRQs than INTSTAT register. Exynos5420 | |
138 | * and Exynos5440 documentation is correct (Exynos4210 doesn't | |
139 | * support FALL IRQs at all). | |
140 | */ | |
141 | writel(val_irq, data->base + reg->tmu_intclear); | |
142 | } | |
143 | ||
8328a4b1 BZ |
144 | static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info) |
145 | { | |
146 | struct exynos_tmu_platform_data *pdata = data->pdata; | |
147 | ||
148 | data->temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; | |
149 | data->temp_error2 = ((trim_info >> EXYNOS_TRIMINFO_85_SHIFT) & | |
150 | EXYNOS_TMU_TEMP_MASK); | |
151 | ||
152 | if (!data->temp_error1 || | |
153 | (pdata->min_efuse_value > data->temp_error1) || | |
154 | (data->temp_error1 > pdata->max_efuse_value)) | |
155 | data->temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK; | |
156 | ||
157 | if (!data->temp_error2) | |
158 | data->temp_error2 = | |
159 | (pdata->efuse_value >> EXYNOS_TRIMINFO_85_SHIFT) & | |
160 | EXYNOS_TMU_TEMP_MASK; | |
161 | } | |
162 | ||
fe87789c BZ |
163 | static u32 get_th_reg(struct exynos_tmu_data *data, u32 threshold, bool falling) |
164 | { | |
165 | struct exynos_tmu_platform_data *pdata = data->pdata; | |
166 | int i; | |
167 | ||
168 | for (i = 0; i < pdata->non_hw_trigger_levels; i++) { | |
169 | u8 temp = pdata->trigger_levels[i]; | |
170 | ||
171 | if (falling) | |
172 | temp -= pdata->threshold_falling; | |
173 | else | |
174 | threshold &= ~(0xff << 8 * i); | |
175 | ||
176 | threshold |= temp_to_code(data, temp) << 8 * i; | |
177 | } | |
178 | ||
179 | return threshold; | |
180 | } | |
181 | ||
f22d9c03 | 182 | static int exynos_tmu_initialize(struct platform_device *pdev) |
9d97e5c8 | 183 | { |
f22d9c03 | 184 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
72d1100b | 185 | int ret; |
9d97e5c8 DK |
186 | |
187 | mutex_lock(&data->lock); | |
188 | clk_enable(data->clk); | |
14a11dc7 NKC |
189 | if (!IS_ERR(data->clk_sec)) |
190 | clk_enable(data->clk_sec); | |
72d1100b | 191 | ret = data->tmu_initialize(pdev); |
9d97e5c8 DK |
192 | clk_disable(data->clk); |
193 | mutex_unlock(&data->lock); | |
14a11dc7 NKC |
194 | if (!IS_ERR(data->clk_sec)) |
195 | clk_disable(data->clk_sec); | |
9d97e5c8 DK |
196 | |
197 | return ret; | |
198 | } | |
199 | ||
f22d9c03 | 200 | static void exynos_tmu_control(struct platform_device *pdev, bool on) |
9d97e5c8 | 201 | { |
f22d9c03 ADK |
202 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
203 | struct exynos_tmu_platform_data *pdata = data->pdata; | |
b8d582b9 | 204 | const struct exynos_tmu_registers *reg = pdata->registers; |
d37761ec | 205 | unsigned int con, interrupt_en; |
9d97e5c8 DK |
206 | |
207 | mutex_lock(&data->lock); | |
208 | clk_enable(data->clk); | |
209 | ||
b8d582b9 | 210 | con = readl(data->base + reg->tmu_ctrl); |
f22d9c03 | 211 | |
86f5362e | 212 | if (pdata->test_mux) |
bfb2b88c | 213 | con |= (pdata->test_mux << EXYNOS4412_MUX_ADDR_SHIFT); |
86f5362e | 214 | |
99d67fb9 BZ |
215 | con &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << EXYNOS_TMU_REF_VOLTAGE_SHIFT); |
216 | con |= pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT; | |
d0a0ce3e | 217 | |
99d67fb9 BZ |
218 | con &= ~(EXYNOS_TMU_BUF_SLOPE_SEL_MASK << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT); |
219 | con |= (pdata->gain << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT); | |
d0a0ce3e ADK |
220 | |
221 | if (pdata->noise_cancel_mode) { | |
b9504a6a BZ |
222 | con &= ~(EXYNOS_TMU_TRIP_MODE_MASK << EXYNOS_TMU_TRIP_MODE_SHIFT); |
223 | con |= (pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT); | |
f22d9c03 ADK |
224 | } |
225 | ||
9d97e5c8 | 226 | if (on) { |
99d67fb9 | 227 | con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); |
d0a0ce3e | 228 | interrupt_en = |
b8d582b9 ADK |
229 | pdata->trigger_enable[3] << reg->inten_rise3_shift | |
230 | pdata->trigger_enable[2] << reg->inten_rise2_shift | | |
231 | pdata->trigger_enable[1] << reg->inten_rise1_shift | | |
232 | pdata->trigger_enable[0] << reg->inten_rise0_shift; | |
f4dae753 | 233 | if (TMU_SUPPORTS(pdata, FALLING_TRIP)) |
d0a0ce3e | 234 | interrupt_en |= |
b8d582b9 | 235 | interrupt_en << reg->inten_fall0_shift; |
9d97e5c8 | 236 | } else { |
99d67fb9 | 237 | con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); |
9d97e5c8 DK |
238 | interrupt_en = 0; /* Disable all interrupts */ |
239 | } | |
b8d582b9 ADK |
240 | writel(interrupt_en, data->base + reg->tmu_inten); |
241 | writel(con, data->base + reg->tmu_ctrl); | |
9d97e5c8 DK |
242 | |
243 | clk_disable(data->clk); | |
244 | mutex_unlock(&data->lock); | |
245 | } | |
246 | ||
72d1100b BZ |
247 | static int exynos4210_tmu_initialize(struct platform_device *pdev) |
248 | { | |
249 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); | |
250 | struct exynos_tmu_platform_data *pdata = data->pdata; | |
251 | unsigned int status; | |
252 | int ret = 0, threshold_code, i; | |
253 | ||
254 | status = readb(data->base + EXYNOS_TMU_REG_STATUS); | |
255 | if (!status) { | |
256 | ret = -EBUSY; | |
257 | goto out; | |
258 | } | |
259 | ||
260 | sanitize_temp_error(data, readl(data->base + EXYNOS_TMU_REG_TRIMINFO)); | |
261 | ||
262 | /* Write temperature code for threshold */ | |
263 | threshold_code = temp_to_code(data, pdata->threshold); | |
264 | writeb(threshold_code, data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP); | |
265 | ||
266 | for (i = 0; i < pdata->non_hw_trigger_levels; i++) | |
267 | writeb(pdata->trigger_levels[i], data->base + | |
268 | EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4); | |
269 | ||
270 | exynos_tmu_clear_irqs(data); | |
271 | out: | |
272 | return ret; | |
273 | } | |
274 | ||
275 | static int exynos4412_tmu_initialize(struct platform_device *pdev) | |
276 | { | |
277 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); | |
278 | struct exynos_tmu_platform_data *pdata = data->pdata; | |
279 | unsigned int status, trim_info, con, ctrl, rising_threshold; | |
280 | int ret = 0, threshold_code, i; | |
281 | ||
282 | status = readb(data->base + EXYNOS_TMU_REG_STATUS); | |
283 | if (!status) { | |
284 | ret = -EBUSY; | |
285 | goto out; | |
286 | } | |
287 | ||
288 | if (data->soc == SOC_ARCH_EXYNOS3250 || | |
289 | data->soc == SOC_ARCH_EXYNOS4412 || | |
290 | data->soc == SOC_ARCH_EXYNOS5250) { | |
291 | if (data->soc == SOC_ARCH_EXYNOS3250) { | |
292 | ctrl = readl(data->base + EXYNOS_TMU_TRIMINFO_CON1); | |
293 | ctrl |= EXYNOS_TRIMINFO_RELOAD_ENABLE; | |
294 | writel(ctrl, data->base + EXYNOS_TMU_TRIMINFO_CON1); | |
295 | } | |
296 | ctrl = readl(data->base + EXYNOS_TMU_TRIMINFO_CON2); | |
297 | ctrl |= EXYNOS_TRIMINFO_RELOAD_ENABLE; | |
298 | writel(ctrl, data->base + EXYNOS_TMU_TRIMINFO_CON2); | |
299 | } | |
300 | ||
301 | /* On exynos5420 the triminfo register is in the shared space */ | |
302 | if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) | |
303 | trim_info = readl(data->base_second + EXYNOS_TMU_REG_TRIMINFO); | |
304 | else | |
305 | trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); | |
306 | ||
307 | sanitize_temp_error(data, trim_info); | |
308 | ||
309 | /* Write temperature code for rising and falling threshold */ | |
310 | rising_threshold = readl(data->base + EXYNOS_THD_TEMP_RISE); | |
311 | rising_threshold = get_th_reg(data, rising_threshold, false); | |
312 | writel(rising_threshold, data->base + EXYNOS_THD_TEMP_RISE); | |
313 | writel(get_th_reg(data, 0, true), data->base + EXYNOS_THD_TEMP_FALL); | |
314 | ||
315 | exynos_tmu_clear_irqs(data); | |
316 | ||
317 | /* if last threshold limit is also present */ | |
318 | i = pdata->max_trigger_level - 1; | |
319 | if (pdata->trigger_levels[i] && pdata->trigger_type[i] == HW_TRIP) { | |
320 | threshold_code = temp_to_code(data, pdata->trigger_levels[i]); | |
321 | /* 1-4 level to be assigned in th0 reg */ | |
322 | rising_threshold &= ~(0xff << 8 * i); | |
323 | rising_threshold |= threshold_code << 8 * i; | |
324 | writel(rising_threshold, data->base + EXYNOS_THD_TEMP_RISE); | |
325 | con = readl(data->base + EXYNOS_TMU_REG_CONTROL); | |
326 | con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); | |
327 | writel(con, data->base + EXYNOS_TMU_REG_CONTROL); | |
328 | } | |
329 | out: | |
330 | return ret; | |
331 | } | |
332 | ||
333 | static int exynos5440_tmu_initialize(struct platform_device *pdev) | |
334 | { | |
335 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); | |
336 | struct exynos_tmu_platform_data *pdata = data->pdata; | |
337 | unsigned int trim_info = 0, con, rising_threshold; | |
338 | int ret = 0, threshold_code, i; | |
339 | ||
340 | /* | |
341 | * For exynos5440 soc triminfo value is swapped between TMU0 and | |
342 | * TMU2, so the below logic is needed. | |
343 | */ | |
344 | switch (data->id) { | |
345 | case 0: | |
346 | trim_info = readl(data->base + EXYNOS5440_EFUSE_SWAP_OFFSET + | |
347 | EXYNOS5440_TMU_S0_7_TRIM); | |
348 | break; | |
349 | case 1: | |
350 | trim_info = readl(data->base + EXYNOS5440_TMU_S0_7_TRIM); | |
351 | break; | |
352 | case 2: | |
353 | trim_info = readl(data->base - EXYNOS5440_EFUSE_SWAP_OFFSET + | |
354 | EXYNOS5440_TMU_S0_7_TRIM); | |
355 | } | |
356 | sanitize_temp_error(data, trim_info); | |
357 | ||
358 | /* Write temperature code for rising and falling threshold */ | |
359 | rising_threshold = readl(data->base + EXYNOS5440_TMU_S0_7_TH0); | |
360 | rising_threshold = get_th_reg(data, rising_threshold, false); | |
361 | writel(rising_threshold, data->base + EXYNOS5440_TMU_S0_7_TH0); | |
362 | writel(0, data->base + EXYNOS5440_TMU_S0_7_TH1); | |
363 | ||
364 | exynos_tmu_clear_irqs(data); | |
365 | ||
366 | /* if last threshold limit is also present */ | |
367 | i = pdata->max_trigger_level - 1; | |
368 | if (pdata->trigger_levels[i] && pdata->trigger_type[i] == HW_TRIP) { | |
369 | threshold_code = temp_to_code(data, pdata->trigger_levels[i]); | |
370 | /* 5th level to be assigned in th2 reg */ | |
371 | rising_threshold = | |
372 | threshold_code << EXYNOS5440_TMU_TH_RISE4_SHIFT; | |
373 | writel(rising_threshold, data->base + EXYNOS5440_TMU_S0_7_TH2); | |
374 | con = readl(data->base + EXYNOS5440_TMU_S0_7_CTRL); | |
375 | con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); | |
376 | writel(con, data->base + EXYNOS5440_TMU_S0_7_CTRL); | |
377 | } | |
378 | /* Clear the PMIN in the common TMU register */ | |
379 | if (!data->id) | |
380 | writel(0, data->base_second + EXYNOS5440_TMU_PMIN); | |
381 | return ret; | |
382 | } | |
383 | ||
f22d9c03 | 384 | static int exynos_tmu_read(struct exynos_tmu_data *data) |
9d97e5c8 | 385 | { |
b8d582b9 ADK |
386 | struct exynos_tmu_platform_data *pdata = data->pdata; |
387 | const struct exynos_tmu_registers *reg = pdata->registers; | |
9d97e5c8 DK |
388 | u8 temp_code; |
389 | int temp; | |
390 | ||
391 | mutex_lock(&data->lock); | |
392 | clk_enable(data->clk); | |
393 | ||
b8d582b9 | 394 | temp_code = readb(data->base + reg->tmu_cur_temp); |
9d97e5c8 | 395 | |
ddb31d43 BZ |
396 | if (data->soc == SOC_ARCH_EXYNOS4210) |
397 | /* temp_code should range between 75 and 175 */ | |
398 | if (temp_code < 75 || temp_code > 175) { | |
399 | temp = -ENODATA; | |
400 | goto out; | |
401 | } | |
402 | ||
403 | temp = code_to_temp(data, temp_code); | |
404 | out: | |
9d97e5c8 DK |
405 | clk_disable(data->clk); |
406 | mutex_unlock(&data->lock); | |
407 | ||
408 | return temp; | |
409 | } | |
410 | ||
bffd1f8a ADK |
411 | #ifdef CONFIG_THERMAL_EMULATION |
412 | static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) | |
413 | { | |
414 | struct exynos_tmu_data *data = drv_data; | |
b8d582b9 ADK |
415 | struct exynos_tmu_platform_data *pdata = data->pdata; |
416 | const struct exynos_tmu_registers *reg = pdata->registers; | |
417 | unsigned int val; | |
bffd1f8a ADK |
418 | int ret = -EINVAL; |
419 | ||
f4dae753 | 420 | if (!TMU_SUPPORTS(pdata, EMULATION)) |
bffd1f8a ADK |
421 | goto out; |
422 | ||
423 | if (temp && temp < MCELSIUS) | |
424 | goto out; | |
425 | ||
426 | mutex_lock(&data->lock); | |
427 | clk_enable(data->clk); | |
428 | ||
b8d582b9 | 429 | val = readl(data->base + reg->emul_con); |
bffd1f8a ADK |
430 | |
431 | if (temp) { | |
432 | temp /= MCELSIUS; | |
433 | ||
f4dae753 | 434 | if (TMU_SUPPORTS(pdata, EMUL_TIME)) { |
6070c2ca BZ |
435 | val &= ~(EXYNOS_EMUL_TIME_MASK << EXYNOS_EMUL_TIME_SHIFT); |
436 | val |= (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT); | |
f4dae753 | 437 | } |
9e288d64 BZ |
438 | val &= ~(EXYNOS_EMUL_DATA_MASK << EXYNOS_EMUL_DATA_SHIFT); |
439 | val |= (temp_to_code(data, temp) << EXYNOS_EMUL_DATA_SHIFT) | | |
f4dae753 | 440 | EXYNOS_EMUL_ENABLE; |
bffd1f8a | 441 | } else { |
b8d582b9 | 442 | val &= ~EXYNOS_EMUL_ENABLE; |
bffd1f8a ADK |
443 | } |
444 | ||
b8d582b9 | 445 | writel(val, data->base + reg->emul_con); |
bffd1f8a ADK |
446 | |
447 | clk_disable(data->clk); | |
448 | mutex_unlock(&data->lock); | |
449 | return 0; | |
450 | out: | |
451 | return ret; | |
452 | } | |
453 | #else | |
454 | static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) | |
455 | { return -EINVAL; } | |
456 | #endif/*CONFIG_THERMAL_EMULATION*/ | |
457 | ||
f22d9c03 | 458 | static void exynos_tmu_work(struct work_struct *work) |
9d97e5c8 | 459 | { |
f22d9c03 ADK |
460 | struct exynos_tmu_data *data = container_of(work, |
461 | struct exynos_tmu_data, irq_work); | |
b835ced1 | 462 | unsigned int val_type; |
a0395eee | 463 | |
14a11dc7 NKC |
464 | if (!IS_ERR(data->clk_sec)) |
465 | clk_enable(data->clk_sec); | |
a0395eee | 466 | /* Find which sensor generated this interrupt */ |
421d5d12 BZ |
467 | if (data->soc == SOC_ARCH_EXYNOS5440) { |
468 | val_type = readl(data->base_second + EXYNOS5440_TMU_IRQ_STATUS); | |
a0395eee ADK |
469 | if (!((val_type >> data->id) & 0x1)) |
470 | goto out; | |
471 | } | |
14a11dc7 NKC |
472 | if (!IS_ERR(data->clk_sec)) |
473 | clk_disable(data->clk_sec); | |
9d97e5c8 | 474 | |
cebe7373 | 475 | exynos_report_trigger(data->reg_conf); |
9d97e5c8 DK |
476 | mutex_lock(&data->lock); |
477 | clk_enable(data->clk); | |
b8d582b9 | 478 | |
a4463c4f | 479 | /* TODO: take action based on particular interrupt */ |
b835ced1 | 480 | exynos_tmu_clear_irqs(data); |
b8d582b9 | 481 | |
9d97e5c8 DK |
482 | clk_disable(data->clk); |
483 | mutex_unlock(&data->lock); | |
a0395eee | 484 | out: |
f22d9c03 | 485 | enable_irq(data->irq); |
9d97e5c8 DK |
486 | } |
487 | ||
f22d9c03 | 488 | static irqreturn_t exynos_tmu_irq(int irq, void *id) |
9d97e5c8 | 489 | { |
f22d9c03 | 490 | struct exynos_tmu_data *data = id; |
9d97e5c8 DK |
491 | |
492 | disable_irq_nosync(irq); | |
493 | schedule_work(&data->irq_work); | |
494 | ||
495 | return IRQ_HANDLED; | |
496 | } | |
17be868e | 497 | |
17be868e | 498 | static const struct of_device_id exynos_tmu_match[] = { |
1fe56dc1 CC |
499 | { |
500 | .compatible = "samsung,exynos3250-tmu", | |
501 | .data = (void *)EXYNOS3250_TMU_DRV_DATA, | |
502 | }, | |
17be868e ADK |
503 | { |
504 | .compatible = "samsung,exynos4210-tmu", | |
505 | .data = (void *)EXYNOS4210_TMU_DRV_DATA, | |
506 | }, | |
b6cee53c SK |
507 | { |
508 | .compatible = "samsung,exynos4412-tmu", | |
14ddfaec | 509 | .data = (void *)EXYNOS4412_TMU_DRV_DATA, |
b6cee53c | 510 | }, |
17be868e ADK |
511 | { |
512 | .compatible = "samsung,exynos5250-tmu", | |
e6b7991e | 513 | .data = (void *)EXYNOS5250_TMU_DRV_DATA, |
17be868e | 514 | }, |
923488a5 NKC |
515 | { |
516 | .compatible = "samsung,exynos5260-tmu", | |
517 | .data = (void *)EXYNOS5260_TMU_DRV_DATA, | |
518 | }, | |
14a11dc7 NKC |
519 | { |
520 | .compatible = "samsung,exynos5420-tmu", | |
521 | .data = (void *)EXYNOS5420_TMU_DRV_DATA, | |
522 | }, | |
523 | { | |
524 | .compatible = "samsung,exynos5420-tmu-ext-triminfo", | |
525 | .data = (void *)EXYNOS5420_TMU_DRV_DATA, | |
526 | }, | |
90542546 ADK |
527 | { |
528 | .compatible = "samsung,exynos5440-tmu", | |
529 | .data = (void *)EXYNOS5440_TMU_DRV_DATA, | |
530 | }, | |
17be868e ADK |
531 | {}, |
532 | }; | |
533 | MODULE_DEVICE_TABLE(of, exynos_tmu_match); | |
17be868e | 534 | |
17be868e | 535 | static inline struct exynos_tmu_platform_data *exynos_get_driver_data( |
cebe7373 | 536 | struct platform_device *pdev, int id) |
17be868e | 537 | { |
cebe7373 ADK |
538 | struct exynos_tmu_init_data *data_table; |
539 | struct exynos_tmu_platform_data *tmu_data; | |
73b5b1d7 SK |
540 | const struct of_device_id *match; |
541 | ||
542 | match = of_match_node(exynos_tmu_match, pdev->dev.of_node); | |
543 | if (!match) | |
544 | return NULL; | |
545 | data_table = (struct exynos_tmu_init_data *) match->data; | |
546 | if (!data_table || id >= data_table->tmu_count) | |
547 | return NULL; | |
548 | tmu_data = data_table->tmu_data; | |
549 | return (struct exynos_tmu_platform_data *) (tmu_data + id); | |
7e0b55e6 | 550 | } |
bbf63be4 | 551 | |
cebe7373 | 552 | static int exynos_map_dt_data(struct platform_device *pdev) |
9d97e5c8 | 553 | { |
cebe7373 ADK |
554 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
555 | struct exynos_tmu_platform_data *pdata; | |
556 | struct resource res; | |
498d22f6 | 557 | int ret; |
cebe7373 | 558 | |
73b5b1d7 | 559 | if (!data || !pdev->dev.of_node) |
cebe7373 | 560 | return -ENODEV; |
9d97e5c8 | 561 | |
498d22f6 ADK |
562 | /* |
563 | * Try enabling the regulator if found | |
564 | * TODO: Add regulator as an SOC feature, so that regulator enable | |
565 | * is a compulsory call. | |
566 | */ | |
567 | data->regulator = devm_regulator_get(&pdev->dev, "vtmu"); | |
568 | if (!IS_ERR(data->regulator)) { | |
569 | ret = regulator_enable(data->regulator); | |
570 | if (ret) { | |
571 | dev_err(&pdev->dev, "failed to enable vtmu\n"); | |
572 | return ret; | |
573 | } | |
574 | } else { | |
575 | dev_info(&pdev->dev, "Regulator node (vtmu) not found\n"); | |
576 | } | |
577 | ||
cebe7373 ADK |
578 | data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl"); |
579 | if (data->id < 0) | |
580 | data->id = 0; | |
17be868e | 581 | |
cebe7373 ADK |
582 | data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); |
583 | if (data->irq <= 0) { | |
584 | dev_err(&pdev->dev, "failed to get IRQ\n"); | |
585 | return -ENODEV; | |
586 | } | |
587 | ||
588 | if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { | |
589 | dev_err(&pdev->dev, "failed to get Resource 0\n"); | |
590 | return -ENODEV; | |
591 | } | |
592 | ||
593 | data->base = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); | |
594 | if (!data->base) { | |
595 | dev_err(&pdev->dev, "Failed to ioremap memory\n"); | |
596 | return -EADDRNOTAVAIL; | |
597 | } | |
598 | ||
599 | pdata = exynos_get_driver_data(pdev, data->id); | |
9d97e5c8 DK |
600 | if (!pdata) { |
601 | dev_err(&pdev->dev, "No platform init data supplied.\n"); | |
602 | return -ENODEV; | |
603 | } | |
cebe7373 | 604 | data->pdata = pdata; |
d9b6ee14 ADK |
605 | /* |
606 | * Check if the TMU shares some registers and then try to map the | |
607 | * memory of common registers. | |
608 | */ | |
9025d563 | 609 | if (!TMU_SUPPORTS(pdata, ADDRESS_MULTIPLE)) |
d9b6ee14 ADK |
610 | return 0; |
611 | ||
612 | if (of_address_to_resource(pdev->dev.of_node, 1, &res)) { | |
613 | dev_err(&pdev->dev, "failed to get Resource 1\n"); | |
614 | return -ENODEV; | |
615 | } | |
616 | ||
9025d563 | 617 | data->base_second = devm_ioremap(&pdev->dev, res.start, |
d9b6ee14 | 618 | resource_size(&res)); |
9025d563 | 619 | if (!data->base_second) { |
d9b6ee14 ADK |
620 | dev_err(&pdev->dev, "Failed to ioremap memory\n"); |
621 | return -ENOMEM; | |
622 | } | |
cebe7373 ADK |
623 | |
624 | return 0; | |
625 | } | |
626 | ||
627 | static int exynos_tmu_probe(struct platform_device *pdev) | |
628 | { | |
629 | struct exynos_tmu_data *data; | |
630 | struct exynos_tmu_platform_data *pdata; | |
631 | struct thermal_sensor_conf *sensor_conf; | |
632 | int ret, i; | |
633 | ||
79e093c3 ADK |
634 | data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data), |
635 | GFP_KERNEL); | |
2a9675b3 | 636 | if (!data) |
9d97e5c8 | 637 | return -ENOMEM; |
9d97e5c8 | 638 | |
cebe7373 ADK |
639 | platform_set_drvdata(pdev, data); |
640 | mutex_init(&data->lock); | |
9d97e5c8 | 641 | |
cebe7373 ADK |
642 | ret = exynos_map_dt_data(pdev); |
643 | if (ret) | |
644 | return ret; | |
9d97e5c8 | 645 | |
cebe7373 | 646 | pdata = data->pdata; |
9d97e5c8 | 647 | |
cebe7373 | 648 | INIT_WORK(&data->irq_work, exynos_tmu_work); |
9d97e5c8 | 649 | |
2a16279c | 650 | data->clk = devm_clk_get(&pdev->dev, "tmu_apbif"); |
9d97e5c8 | 651 | if (IS_ERR(data->clk)) { |
9d97e5c8 | 652 | dev_err(&pdev->dev, "Failed to get clock\n"); |
79e093c3 | 653 | return PTR_ERR(data->clk); |
9d97e5c8 DK |
654 | } |
655 | ||
14a11dc7 NKC |
656 | data->clk_sec = devm_clk_get(&pdev->dev, "tmu_triminfo_apbif"); |
657 | if (IS_ERR(data->clk_sec)) { | |
658 | if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) { | |
659 | dev_err(&pdev->dev, "Failed to get triminfo clock\n"); | |
660 | return PTR_ERR(data->clk_sec); | |
661 | } | |
662 | } else { | |
663 | ret = clk_prepare(data->clk_sec); | |
664 | if (ret) { | |
665 | dev_err(&pdev->dev, "Failed to get clock\n"); | |
666 | return ret; | |
667 | } | |
668 | } | |
669 | ||
2a16279c | 670 | ret = clk_prepare(data->clk); |
14a11dc7 NKC |
671 | if (ret) { |
672 | dev_err(&pdev->dev, "Failed to get clock\n"); | |
673 | goto err_clk_sec; | |
674 | } | |
2a16279c | 675 | |
72d1100b BZ |
676 | data->soc = pdata->type; |
677 | ||
678 | switch (data->soc) { | |
679 | case SOC_ARCH_EXYNOS4210: | |
680 | data->tmu_initialize = exynos4210_tmu_initialize; | |
681 | break; | |
682 | case SOC_ARCH_EXYNOS3250: | |
683 | case SOC_ARCH_EXYNOS4412: | |
684 | case SOC_ARCH_EXYNOS5250: | |
685 | case SOC_ARCH_EXYNOS5260: | |
686 | case SOC_ARCH_EXYNOS5420: | |
687 | case SOC_ARCH_EXYNOS5420_TRIMINFO: | |
688 | data->tmu_initialize = exynos4412_tmu_initialize; | |
689 | break; | |
690 | case SOC_ARCH_EXYNOS5440: | |
691 | data->tmu_initialize = exynos5440_tmu_initialize; | |
692 | break; | |
693 | default: | |
f22d9c03 ADK |
694 | ret = -EINVAL; |
695 | dev_err(&pdev->dev, "Platform not supported\n"); | |
696 | goto err_clk; | |
697 | } | |
698 | ||
f22d9c03 | 699 | ret = exynos_tmu_initialize(pdev); |
9d97e5c8 DK |
700 | if (ret) { |
701 | dev_err(&pdev->dev, "Failed to initialize TMU\n"); | |
702 | goto err_clk; | |
703 | } | |
704 | ||
f22d9c03 | 705 | exynos_tmu_control(pdev, true); |
9d97e5c8 | 706 | |
cebe7373 ADK |
707 | /* Allocate a structure to register with the exynos core thermal */ |
708 | sensor_conf = devm_kzalloc(&pdev->dev, | |
709 | sizeof(struct thermal_sensor_conf), GFP_KERNEL); | |
710 | if (!sensor_conf) { | |
cebe7373 ADK |
711 | ret = -ENOMEM; |
712 | goto err_clk; | |
713 | } | |
714 | sprintf(sensor_conf->name, "therm_zone%d", data->id); | |
715 | sensor_conf->read_temperature = (int (*)(void *))exynos_tmu_read; | |
716 | sensor_conf->write_emul_temp = | |
717 | (int (*)(void *, unsigned long))exynos_tmu_set_emulation; | |
718 | sensor_conf->driver_data = data; | |
719 | sensor_conf->trip_data.trip_count = pdata->trigger_enable[0] + | |
bb34b4c8 ADK |
720 | pdata->trigger_enable[1] + pdata->trigger_enable[2]+ |
721 | pdata->trigger_enable[3]; | |
7e0b55e6 | 722 | |
cebe7373 ADK |
723 | for (i = 0; i < sensor_conf->trip_data.trip_count; i++) { |
724 | sensor_conf->trip_data.trip_val[i] = | |
7e0b55e6 | 725 | pdata->threshold + pdata->trigger_levels[i]; |
cebe7373 | 726 | sensor_conf->trip_data.trip_type[i] = |
5c3cf552 ADK |
727 | pdata->trigger_type[i]; |
728 | } | |
7e0b55e6 | 729 | |
cebe7373 | 730 | sensor_conf->trip_data.trigger_falling = pdata->threshold_falling; |
4f0a6847 | 731 | |
cebe7373 | 732 | sensor_conf->cooling_data.freq_clip_count = pdata->freq_tab_count; |
7e0b55e6 | 733 | for (i = 0; i < pdata->freq_tab_count; i++) { |
cebe7373 | 734 | sensor_conf->cooling_data.freq_data[i].freq_clip_max = |
7e0b55e6 | 735 | pdata->freq_tab[i].freq_clip_max; |
cebe7373 | 736 | sensor_conf->cooling_data.freq_data[i].temp_level = |
7e0b55e6 ADK |
737 | pdata->freq_tab[i].temp_level; |
738 | } | |
cebe7373 ADK |
739 | sensor_conf->dev = &pdev->dev; |
740 | /* Register the sensor with thermal management interface */ | |
741 | ret = exynos_register_thermal(sensor_conf); | |
7e0b55e6 ADK |
742 | if (ret) { |
743 | dev_err(&pdev->dev, "Failed to register thermal interface\n"); | |
744 | goto err_clk; | |
745 | } | |
cebe7373 ADK |
746 | data->reg_conf = sensor_conf; |
747 | ||
748 | ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, | |
749 | IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data); | |
750 | if (ret) { | |
751 | dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); | |
752 | goto err_clk; | |
753 | } | |
bbf63be4 | 754 | |
9d97e5c8 | 755 | return 0; |
9d97e5c8 | 756 | err_clk: |
2a16279c | 757 | clk_unprepare(data->clk); |
14a11dc7 NKC |
758 | err_clk_sec: |
759 | if (!IS_ERR(data->clk_sec)) | |
760 | clk_unprepare(data->clk_sec); | |
9d97e5c8 DK |
761 | return ret; |
762 | } | |
763 | ||
4eab7a9e | 764 | static int exynos_tmu_remove(struct platform_device *pdev) |
9d97e5c8 | 765 | { |
f22d9c03 | 766 | struct exynos_tmu_data *data = platform_get_drvdata(pdev); |
9d97e5c8 | 767 | |
cebe7373 | 768 | exynos_unregister_thermal(data->reg_conf); |
7e0b55e6 | 769 | |
4215688e BZ |
770 | exynos_tmu_control(pdev, false); |
771 | ||
2a16279c | 772 | clk_unprepare(data->clk); |
14a11dc7 NKC |
773 | if (!IS_ERR(data->clk_sec)) |
774 | clk_unprepare(data->clk_sec); | |
9d97e5c8 | 775 | |
498d22f6 ADK |
776 | if (!IS_ERR(data->regulator)) |
777 | regulator_disable(data->regulator); | |
778 | ||
9d97e5c8 DK |
779 | return 0; |
780 | } | |
781 | ||
08cd6753 | 782 | #ifdef CONFIG_PM_SLEEP |
f22d9c03 | 783 | static int exynos_tmu_suspend(struct device *dev) |
9d97e5c8 | 784 | { |
f22d9c03 | 785 | exynos_tmu_control(to_platform_device(dev), false); |
9d97e5c8 DK |
786 | |
787 | return 0; | |
788 | } | |
789 | ||
f22d9c03 | 790 | static int exynos_tmu_resume(struct device *dev) |
9d97e5c8 | 791 | { |
08cd6753 RW |
792 | struct platform_device *pdev = to_platform_device(dev); |
793 | ||
f22d9c03 ADK |
794 | exynos_tmu_initialize(pdev); |
795 | exynos_tmu_control(pdev, true); | |
9d97e5c8 DK |
796 | |
797 | return 0; | |
798 | } | |
08cd6753 | 799 | |
f22d9c03 ADK |
800 | static SIMPLE_DEV_PM_OPS(exynos_tmu_pm, |
801 | exynos_tmu_suspend, exynos_tmu_resume); | |
802 | #define EXYNOS_TMU_PM (&exynos_tmu_pm) | |
9d97e5c8 | 803 | #else |
f22d9c03 | 804 | #define EXYNOS_TMU_PM NULL |
9d97e5c8 DK |
805 | #endif |
806 | ||
f22d9c03 | 807 | static struct platform_driver exynos_tmu_driver = { |
9d97e5c8 | 808 | .driver = { |
f22d9c03 | 809 | .name = "exynos-tmu", |
9d97e5c8 | 810 | .owner = THIS_MODULE, |
f22d9c03 | 811 | .pm = EXYNOS_TMU_PM, |
73b5b1d7 | 812 | .of_match_table = exynos_tmu_match, |
9d97e5c8 | 813 | }, |
f22d9c03 | 814 | .probe = exynos_tmu_probe, |
4eab7a9e | 815 | .remove = exynos_tmu_remove, |
9d97e5c8 DK |
816 | }; |
817 | ||
f22d9c03 | 818 | module_platform_driver(exynos_tmu_driver); |
9d97e5c8 | 819 | |
f22d9c03 | 820 | MODULE_DESCRIPTION("EXYNOS TMU Driver"); |
9d97e5c8 DK |
821 | MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); |
822 | MODULE_LICENSE("GPL"); | |
f22d9c03 | 823 | MODULE_ALIAS("platform:exynos-tmu"); |