1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (C) 2020 Spreadtrum Communications Inc. |
3 | |
4 | #include <linux/clk.h> |
5 | #include <linux/io.h> |
6 | #include <linux/iopoll.h> |
7 | #include <linux/module.h> |
8 | #include <linux/nvmem-consumer.h> |
9 | #include <linux/of.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/thermal.h> |
13 | |
14 | #define SPRD_THM_CTL 0x0 |
15 | #define SPRD_THM_INT_EN 0x4 |
16 | #define SPRD_THM_INT_STS 0x8 |
17 | #define SPRD_THM_INT_RAW_STS 0xc |
18 | #define SPRD_THM_DET_PERIOD 0x10 |
19 | #define SPRD_THM_INT_CLR 0x14 |
20 | #define SPRD_THM_INT_CLR_ST 0x18 |
21 | #define SPRD_THM_MON_PERIOD 0x4c |
22 | #define SPRD_THM_MON_CTL 0x50 |
23 | #define SPRD_THM_INTERNAL_STS1 0x54 |
24 | #define SPRD_THM_RAW_READ_MSK 0x3ff |
25 | |
26 | #define SPRD_THM_OFFSET(id) ((id) * 0x4) |
27 | #define SPRD_THM_TEMP(id) (SPRD_THM_OFFSET(id) + 0x5c) |
28 | #define SPRD_THM_THRES(id) (SPRD_THM_OFFSET(id) + 0x2c) |
29 | |
30 | #define SPRD_THM_SEN(id) BIT((id) + 2) |
31 | #define SPRD_THM_SEN_OVERHEAT_EN(id) BIT((id) + 8) |
32 | #define SPRD_THM_SEN_OVERHEAT_ALARM_EN(id) BIT((id) + 0) |
33 | |
34 | /* bits definitions for register THM_CTL */ |
35 | #define SPRD_THM_SET_RDY_ST BIT(13) |
36 | #define SPRD_THM_SET_RDY BIT(12) |
37 | #define SPRD_THM_MON_EN BIT(1) |
38 | #define SPRD_THM_EN BIT(0) |
39 | |
40 | /* bits definitions for register THM_INT_CTL */ |
41 | #define SPRD_THM_BIT_INT_EN BIT(26) |
42 | #define SPRD_THM_OVERHEAT_EN BIT(25) |
43 | #define SPRD_THM_OTP_TRIP_SHIFT 10 |
44 | |
45 | /* bits definitions for register SPRD_THM_INTERNAL_STS1 */ |
46 | #define SPRD_THM_TEMPER_RDY BIT(0) |
47 | |
48 | #define SPRD_THM_DET_PERIOD_DATA 0x800 |
49 | #define SPRD_THM_DET_PERIOD_MASK GENMASK(19, 0) |
50 | #define SPRD_THM_MON_MODE 0x7 |
51 | #define SPRD_THM_MON_MODE_MASK GENMASK(3, 0) |
52 | #define SPRD_THM_MON_PERIOD_DATA 0x10 |
53 | #define SPRD_THM_MON_PERIOD_MASK GENMASK(15, 0) |
54 | #define SPRD_THM_THRES_MASK GENMASK(19, 0) |
55 | #define SPRD_THM_INT_CLR_MASK GENMASK(24, 0) |
56 | |
57 | /* thermal sensor calibration parameters */ |
58 | #define SPRD_THM_TEMP_LOW -40000 |
59 | #define SPRD_THM_TEMP_HIGH 120000 |
60 | #define SPRD_THM_OTP_TEMP 120000 |
61 | #define SPRD_THM_HOT_TEMP 75000 |
62 | #define SPRD_THM_RAW_DATA_LOW 0 |
63 | #define SPRD_THM_RAW_DATA_HIGH 1000 |
64 | #define SPRD_THM_SEN_NUM 8 |
65 | #define SPRD_THM_DT_OFFSET 24 |
66 | #define SPRD_THM_RATION_OFFSET 17 |
67 | #define SPRD_THM_RATION_SIGN 16 |
68 | |
69 | #define SPRD_THM_RDYST_POLLING_TIME 10 |
70 | #define SPRD_THM_RDYST_TIMEOUT 700 |
71 | #define SPRD_THM_TEMP_READY_POLL_TIME 10000 |
72 | #define SPRD_THM_TEMP_READY_TIMEOUT 600000 |
73 | #define SPRD_THM_MAX_SENSOR 8 |
74 | |
75 | struct sprd_thermal_sensor { |
76 | struct thermal_zone_device *tzd; |
77 | struct sprd_thermal_data *data; |
78 | struct device *dev; |
79 | int cal_slope; |
80 | int cal_offset; |
81 | int id; |
82 | }; |
83 | |
84 | struct sprd_thermal_data { |
85 | const struct sprd_thm_variant_data *var_data; |
86 | struct sprd_thermal_sensor *sensor[SPRD_THM_MAX_SENSOR]; |
87 | struct clk *clk; |
88 | void __iomem *base; |
89 | u32 ratio_off; |
90 | int ratio_sign; |
91 | int nr_sensors; |
92 | }; |
93 | |
94 | /* |
95 | * The conversion between ADC and temperature is based on linear relationship, |
96 | * and use idea_k to specify the slope and ideal_b to specify the offset. |
97 | * |
98 | * Since different Spreadtrum SoCs have different ideal_k and ideal_b, |
99 | * we should save ideal_k and ideal_b in the device data structure. |
100 | */ |
101 | struct sprd_thm_variant_data { |
102 | u32 ideal_k; |
103 | u32 ideal_b; |
104 | }; |
105 | |
106 | static const struct sprd_thm_variant_data ums512_data = { |
107 | .ideal_k = 262, |
108 | .ideal_b = 66400, |
109 | }; |
110 | |
111 | static inline void sprd_thm_update_bits(void __iomem *reg, u32 mask, u32 val) |
112 | { |
113 | u32 tmp, orig; |
114 | |
115 | orig = readl(addr: reg); |
116 | tmp = orig & ~mask; |
117 | tmp |= val & mask; |
118 | writel(val: tmp, addr: reg); |
119 | } |
120 | |
121 | static int sprd_thm_cal_read(struct device_node *np, const char *cell_id, |
122 | u32 *val) |
123 | { |
124 | struct nvmem_cell *cell; |
125 | void *buf; |
126 | size_t len; |
127 | |
128 | cell = of_nvmem_cell_get(np, id: cell_id); |
129 | if (IS_ERR(ptr: cell)) |
130 | return PTR_ERR(ptr: cell); |
131 | |
132 | buf = nvmem_cell_read(cell, len: &len); |
133 | nvmem_cell_put(cell); |
134 | if (IS_ERR(ptr: buf)) |
135 | return PTR_ERR(ptr: buf); |
136 | |
137 | if (len > sizeof(u32)) { |
138 | kfree(objp: buf); |
139 | return -EINVAL; |
140 | } |
141 | |
142 | memcpy(val, buf, len); |
143 | |
144 | kfree(objp: buf); |
145 | return 0; |
146 | } |
147 | |
148 | static int sprd_thm_sensor_calibration(struct device_node *np, |
149 | struct sprd_thermal_data *thm, |
150 | struct sprd_thermal_sensor *sen) |
151 | { |
152 | int ret; |
153 | /* |
154 | * According to thermal datasheet, the default calibration offset is 64, |
155 | * and the default ratio is 1000. |
156 | */ |
157 | int dt_offset = 64, ratio = 1000; |
158 | |
159 | ret = sprd_thm_cal_read(np, cell_id: "sen_delta_cal" , val: &dt_offset); |
160 | if (ret) |
161 | return ret; |
162 | |
163 | ratio += thm->ratio_sign * thm->ratio_off; |
164 | |
165 | /* |
166 | * According to the ideal slope K and ideal offset B, combined with |
167 | * calibration value of thermal from efuse, then calibrate the real |
168 | * slope k and offset b: |
169 | * k_cal = (k * ratio) / 1000. |
170 | * b_cal = b + (dt_offset - 64) * 500. |
171 | */ |
172 | sen->cal_slope = (thm->var_data->ideal_k * ratio) / 1000; |
173 | sen->cal_offset = thm->var_data->ideal_b + (dt_offset - 128) * 250; |
174 | |
175 | return 0; |
176 | } |
177 | |
178 | static int sprd_thm_rawdata_to_temp(struct sprd_thermal_sensor *sen, |
179 | u32 rawdata) |
180 | { |
181 | clamp(rawdata, (u32)SPRD_THM_RAW_DATA_LOW, (u32)SPRD_THM_RAW_DATA_HIGH); |
182 | |
183 | /* |
184 | * According to the thermal datasheet, the formula of converting |
185 | * adc value to the temperature value should be: |
186 | * T_final = k_cal * x - b_cal. |
187 | */ |
188 | return sen->cal_slope * rawdata - sen->cal_offset; |
189 | } |
190 | |
191 | static int sprd_thm_temp_to_rawdata(int temp, struct sprd_thermal_sensor *sen) |
192 | { |
193 | u32 val; |
194 | |
195 | clamp(temp, (int)SPRD_THM_TEMP_LOW, (int)SPRD_THM_TEMP_HIGH); |
196 | |
197 | /* |
198 | * According to the thermal datasheet, the formula of converting |
199 | * adc value to the temperature value should be: |
200 | * T_final = k_cal * x - b_cal. |
201 | */ |
202 | val = (temp + sen->cal_offset) / sen->cal_slope; |
203 | |
204 | return clamp(val, val, (u32)(SPRD_THM_RAW_DATA_HIGH - 1)); |
205 | } |
206 | |
207 | static int sprd_thm_read_temp(struct thermal_zone_device *tz, int *temp) |
208 | { |
209 | struct sprd_thermal_sensor *sen = thermal_zone_device_priv(tzd: tz); |
210 | u32 data; |
211 | |
212 | data = readl(addr: sen->data->base + SPRD_THM_TEMP(sen->id)) & |
213 | SPRD_THM_RAW_READ_MSK; |
214 | |
215 | *temp = sprd_thm_rawdata_to_temp(sen, rawdata: data); |
216 | |
217 | return 0; |
218 | } |
219 | |
220 | static const struct thermal_zone_device_ops sprd_thm_ops = { |
221 | .get_temp = sprd_thm_read_temp, |
222 | }; |
223 | |
224 | static int sprd_thm_poll_ready_status(struct sprd_thermal_data *thm) |
225 | { |
226 | u32 val; |
227 | int ret; |
228 | |
229 | /* |
230 | * Wait for thermal ready status before configuring thermal parameters. |
231 | */ |
232 | ret = readl_poll_timeout(thm->base + SPRD_THM_CTL, val, |
233 | !(val & SPRD_THM_SET_RDY_ST), |
234 | SPRD_THM_RDYST_POLLING_TIME, |
235 | SPRD_THM_RDYST_TIMEOUT); |
236 | if (ret) |
237 | return ret; |
238 | |
239 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_CTL, SPRD_THM_MON_EN, |
240 | SPRD_THM_MON_EN); |
241 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_CTL, SPRD_THM_SET_RDY, |
242 | SPRD_THM_SET_RDY); |
243 | return 0; |
244 | } |
245 | |
246 | static int sprd_thm_wait_temp_ready(struct sprd_thermal_data *thm) |
247 | { |
248 | u32 val; |
249 | |
250 | /* Wait for first temperature data ready before reading temperature */ |
251 | return readl_poll_timeout(thm->base + SPRD_THM_INTERNAL_STS1, val, |
252 | !(val & SPRD_THM_TEMPER_RDY), |
253 | SPRD_THM_TEMP_READY_POLL_TIME, |
254 | SPRD_THM_TEMP_READY_TIMEOUT); |
255 | } |
256 | |
257 | static int sprd_thm_set_ready(struct sprd_thermal_data *thm) |
258 | { |
259 | int ret; |
260 | |
261 | ret = sprd_thm_poll_ready_status(thm); |
262 | if (ret) |
263 | return ret; |
264 | |
265 | /* |
266 | * Clear interrupt status, enable thermal interrupt and enable thermal. |
267 | * |
268 | * The SPRD thermal controller integrates a hardware interrupt signal, |
269 | * which means if the temperature is overheat, it will generate an |
270 | * interrupt and notify the event to PMIC automatically to shutdown the |
271 | * system. So here we should enable the interrupt bits, though we have |
272 | * not registered an irq handler. |
273 | */ |
274 | writel(SPRD_THM_INT_CLR_MASK, addr: thm->base + SPRD_THM_INT_CLR); |
275 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_INT_EN, |
276 | SPRD_THM_BIT_INT_EN, SPRD_THM_BIT_INT_EN); |
277 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_CTL, |
278 | SPRD_THM_EN, SPRD_THM_EN); |
279 | return 0; |
280 | } |
281 | |
282 | static void sprd_thm_sensor_init(struct sprd_thermal_data *thm, |
283 | struct sprd_thermal_sensor *sen) |
284 | { |
285 | u32 otp_rawdata, hot_rawdata; |
286 | |
287 | otp_rawdata = sprd_thm_temp_to_rawdata(SPRD_THM_OTP_TEMP, sen); |
288 | hot_rawdata = sprd_thm_temp_to_rawdata(SPRD_THM_HOT_TEMP, sen); |
289 | |
290 | /* Enable the sensor' overheat temperature protection interrupt */ |
291 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_INT_EN, |
292 | SPRD_THM_SEN_OVERHEAT_ALARM_EN(sen->id), |
293 | SPRD_THM_SEN_OVERHEAT_ALARM_EN(sen->id)); |
294 | |
295 | /* Set the sensor' overheat and hot threshold temperature */ |
296 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_THRES(sen->id), |
297 | SPRD_THM_THRES_MASK, |
298 | val: (otp_rawdata << SPRD_THM_OTP_TRIP_SHIFT) | |
299 | hot_rawdata); |
300 | |
301 | /* Enable the corresponding sensor */ |
302 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_CTL, SPRD_THM_SEN(sen->id), |
303 | SPRD_THM_SEN(sen->id)); |
304 | } |
305 | |
306 | static void sprd_thm_para_config(struct sprd_thermal_data *thm) |
307 | { |
308 | /* Set the period of two valid temperature detection action */ |
309 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_DET_PERIOD, |
310 | SPRD_THM_DET_PERIOD_MASK, SPRD_THM_DET_PERIOD); |
311 | |
312 | /* Set the sensors' monitor mode */ |
313 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_MON_CTL, |
314 | SPRD_THM_MON_MODE_MASK, SPRD_THM_MON_MODE); |
315 | |
316 | /* Set the sensors' monitor period */ |
317 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_MON_PERIOD, |
318 | SPRD_THM_MON_PERIOD_MASK, SPRD_THM_MON_PERIOD); |
319 | } |
320 | |
321 | static void sprd_thm_toggle_sensor(struct sprd_thermal_sensor *sen, bool on) |
322 | { |
323 | struct thermal_zone_device *tzd = sen->tzd; |
324 | |
325 | if (on) |
326 | thermal_zone_device_enable(tz: tzd); |
327 | else |
328 | thermal_zone_device_disable(tz: tzd); |
329 | } |
330 | |
331 | static int sprd_thm_probe(struct platform_device *pdev) |
332 | { |
333 | struct device_node *np = pdev->dev.of_node; |
334 | struct device_node *sen_child; |
335 | struct sprd_thermal_data *thm; |
336 | struct sprd_thermal_sensor *sen; |
337 | const struct sprd_thm_variant_data *pdata; |
338 | int ret, i; |
339 | u32 val; |
340 | |
341 | pdata = of_device_get_match_data(dev: &pdev->dev); |
342 | if (!pdata) { |
343 | dev_err(&pdev->dev, "No matching driver data found\n" ); |
344 | return -EINVAL; |
345 | } |
346 | |
347 | thm = devm_kzalloc(dev: &pdev->dev, size: sizeof(*thm), GFP_KERNEL); |
348 | if (!thm) |
349 | return -ENOMEM; |
350 | |
351 | thm->var_data = pdata; |
352 | thm->base = devm_platform_ioremap_resource(pdev, index: 0); |
353 | if (IS_ERR(ptr: thm->base)) |
354 | return PTR_ERR(ptr: thm->base); |
355 | |
356 | thm->nr_sensors = of_get_child_count(np); |
357 | if (thm->nr_sensors == 0 || thm->nr_sensors > SPRD_THM_MAX_SENSOR) { |
358 | dev_err(&pdev->dev, "incorrect sensor count\n" ); |
359 | return -EINVAL; |
360 | } |
361 | |
362 | thm->clk = devm_clk_get(dev: &pdev->dev, id: "enable" ); |
363 | if (IS_ERR(ptr: thm->clk)) { |
364 | dev_err(&pdev->dev, "failed to get enable clock\n" ); |
365 | return PTR_ERR(ptr: thm->clk); |
366 | } |
367 | |
368 | ret = clk_prepare_enable(clk: thm->clk); |
369 | if (ret) |
370 | return ret; |
371 | |
372 | sprd_thm_para_config(thm); |
373 | |
374 | ret = sprd_thm_cal_read(np, cell_id: "thm_sign_cal" , val: &val); |
375 | if (ret) |
376 | goto disable_clk; |
377 | |
378 | if (val > 0) |
379 | thm->ratio_sign = -1; |
380 | else |
381 | thm->ratio_sign = 1; |
382 | |
383 | ret = sprd_thm_cal_read(np, cell_id: "thm_ratio_cal" , val: &thm->ratio_off); |
384 | if (ret) |
385 | goto disable_clk; |
386 | |
387 | for_each_child_of_node(np, sen_child) { |
388 | sen = devm_kzalloc(dev: &pdev->dev, size: sizeof(*sen), GFP_KERNEL); |
389 | if (!sen) { |
390 | ret = -ENOMEM; |
391 | goto of_put; |
392 | } |
393 | |
394 | sen->data = thm; |
395 | sen->dev = &pdev->dev; |
396 | |
397 | ret = of_property_read_u32(np: sen_child, propname: "reg" , out_value: &sen->id); |
398 | if (ret) { |
399 | dev_err(&pdev->dev, "get sensor reg failed" ); |
400 | goto of_put; |
401 | } |
402 | |
403 | ret = sprd_thm_sensor_calibration(np: sen_child, thm, sen); |
404 | if (ret) { |
405 | dev_err(&pdev->dev, "efuse cal analysis failed" ); |
406 | goto of_put; |
407 | } |
408 | |
409 | sprd_thm_sensor_init(thm, sen); |
410 | |
411 | sen->tzd = devm_thermal_of_zone_register(dev: sen->dev, |
412 | id: sen->id, |
413 | data: sen, |
414 | ops: &sprd_thm_ops); |
415 | if (IS_ERR(ptr: sen->tzd)) { |
416 | dev_err(&pdev->dev, "register thermal zone failed %d\n" , |
417 | sen->id); |
418 | ret = PTR_ERR(ptr: sen->tzd); |
419 | goto of_put; |
420 | } |
421 | |
422 | thm->sensor[sen->id] = sen; |
423 | } |
424 | /* sen_child set to NULL at this point */ |
425 | |
426 | ret = sprd_thm_set_ready(thm); |
427 | if (ret) |
428 | goto of_put; |
429 | |
430 | ret = sprd_thm_wait_temp_ready(thm); |
431 | if (ret) |
432 | goto of_put; |
433 | |
434 | for (i = 0; i < thm->nr_sensors; i++) |
435 | sprd_thm_toggle_sensor(sen: thm->sensor[i], on: true); |
436 | |
437 | platform_set_drvdata(pdev, data: thm); |
438 | return 0; |
439 | |
440 | of_put: |
441 | of_node_put(node: sen_child); |
442 | disable_clk: |
443 | clk_disable_unprepare(clk: thm->clk); |
444 | return ret; |
445 | } |
446 | |
447 | #ifdef CONFIG_PM_SLEEP |
448 | static void sprd_thm_hw_suspend(struct sprd_thermal_data *thm) |
449 | { |
450 | int i; |
451 | |
452 | for (i = 0; i < thm->nr_sensors; i++) { |
453 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_CTL, |
454 | SPRD_THM_SEN(thm->sensor[i]->id), val: 0); |
455 | } |
456 | |
457 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_CTL, |
458 | SPRD_THM_EN, val: 0x0); |
459 | } |
460 | |
461 | static int sprd_thm_suspend(struct device *dev) |
462 | { |
463 | struct sprd_thermal_data *thm = dev_get_drvdata(dev); |
464 | int i; |
465 | |
466 | for (i = 0; i < thm->nr_sensors; i++) |
467 | sprd_thm_toggle_sensor(sen: thm->sensor[i], on: false); |
468 | |
469 | sprd_thm_hw_suspend(thm); |
470 | clk_disable_unprepare(clk: thm->clk); |
471 | |
472 | return 0; |
473 | } |
474 | |
475 | static int sprd_thm_hw_resume(struct sprd_thermal_data *thm) |
476 | { |
477 | int ret, i; |
478 | |
479 | for (i = 0; i < thm->nr_sensors; i++) { |
480 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_CTL, |
481 | SPRD_THM_SEN(thm->sensor[i]->id), |
482 | SPRD_THM_SEN(thm->sensor[i]->id)); |
483 | } |
484 | |
485 | ret = sprd_thm_poll_ready_status(thm); |
486 | if (ret) |
487 | return ret; |
488 | |
489 | writel(SPRD_THM_INT_CLR_MASK, addr: thm->base + SPRD_THM_INT_CLR); |
490 | sprd_thm_update_bits(reg: thm->base + SPRD_THM_CTL, |
491 | SPRD_THM_EN, SPRD_THM_EN); |
492 | return sprd_thm_wait_temp_ready(thm); |
493 | } |
494 | |
495 | static int sprd_thm_resume(struct device *dev) |
496 | { |
497 | struct sprd_thermal_data *thm = dev_get_drvdata(dev); |
498 | int ret, i; |
499 | |
500 | ret = clk_prepare_enable(clk: thm->clk); |
501 | if (ret) |
502 | return ret; |
503 | |
504 | ret = sprd_thm_hw_resume(thm); |
505 | if (ret) |
506 | goto disable_clk; |
507 | |
508 | for (i = 0; i < thm->nr_sensors; i++) |
509 | sprd_thm_toggle_sensor(sen: thm->sensor[i], on: true); |
510 | |
511 | return 0; |
512 | |
513 | disable_clk: |
514 | clk_disable_unprepare(clk: thm->clk); |
515 | return ret; |
516 | } |
517 | #endif |
518 | |
519 | static void sprd_thm_remove(struct platform_device *pdev) |
520 | { |
521 | struct sprd_thermal_data *thm = platform_get_drvdata(pdev); |
522 | int i; |
523 | |
524 | for (i = 0; i < thm->nr_sensors; i++) { |
525 | sprd_thm_toggle_sensor(sen: thm->sensor[i], on: false); |
526 | devm_thermal_of_zone_unregister(dev: &pdev->dev, |
527 | tz: thm->sensor[i]->tzd); |
528 | } |
529 | |
530 | clk_disable_unprepare(clk: thm->clk); |
531 | } |
532 | |
533 | static const struct of_device_id sprd_thermal_of_match[] = { |
534 | { .compatible = "sprd,ums512-thermal" , .data = &ums512_data }, |
535 | { }, |
536 | }; |
537 | MODULE_DEVICE_TABLE(of, sprd_thermal_of_match); |
538 | |
539 | static const struct dev_pm_ops sprd_thermal_pm_ops = { |
540 | SET_SYSTEM_SLEEP_PM_OPS(sprd_thm_suspend, sprd_thm_resume) |
541 | }; |
542 | |
543 | static struct platform_driver sprd_thermal_driver = { |
544 | .probe = sprd_thm_probe, |
545 | .remove_new = sprd_thm_remove, |
546 | .driver = { |
547 | .name = "sprd-thermal" , |
548 | .pm = &sprd_thermal_pm_ops, |
549 | .of_match_table = sprd_thermal_of_match, |
550 | }, |
551 | }; |
552 | |
553 | module_platform_driver(sprd_thermal_driver); |
554 | |
555 | MODULE_AUTHOR("Freeman Liu <freeman.liu@unisoc.com>" ); |
556 | MODULE_DESCRIPTION("Spreadtrum thermal driver" ); |
557 | MODULE_LICENSE("GPL v2" ); |
558 | |