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