]>
Commit | Line | Data |
---|---|---|
f017fbe7 D |
1 | /* |
2 | * intel_mid_thermal.c - Intel MID platform thermal driver | |
3 | * | |
4 | * Copyright (C) 2011 Intel Corporation | |
5 | * | |
6 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
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; version 2 of the License. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License along | |
18 | * with this program; if not, write to the Free Software Foundation, Inc., | |
19 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
20 | * | |
21 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
22 | * Author: Durgadoss R <durgadoss.r@intel.com> | |
23 | */ | |
24 | ||
25 | #define pr_fmt(fmt) "intel_mid_thermal: " fmt | |
26 | ||
27 | #include <linux/module.h> | |
28 | #include <linux/init.h> | |
29 | #include <linux/err.h> | |
30 | #include <linux/param.h> | |
31 | #include <linux/device.h> | |
32 | #include <linux/platform_device.h> | |
33 | #include <linux/slab.h> | |
34 | #include <linux/pm.h> | |
35 | #include <linux/thermal.h> | |
36 | ||
37 | #include <asm/intel_scu_ipc.h> | |
38 | ||
39 | /* Number of thermal sensors */ | |
253a0069 | 40 | #define MSIC_THERMAL_SENSORS 4 |
f017fbe7 D |
41 | |
42 | /* ADC1 - thermal registers */ | |
253a0069 AP |
43 | #define MSIC_THERM_ADC1CNTL1 0x1C0 |
44 | #define MSIC_ADC_ENBL 0x10 | |
45 | #define MSIC_ADC_START 0x08 | |
f017fbe7 | 46 | |
253a0069 AP |
47 | #define MSIC_THERM_ADC1CNTL3 0x1C2 |
48 | #define MSIC_ADCTHERM_ENBL 0x04 | |
49 | #define MSIC_ADCRRDATA_ENBL 0x05 | |
50 | #define MSIC_CHANL_MASK_VAL 0x0F | |
f017fbe7 | 51 | |
253a0069 AP |
52 | #define MSIC_STOPBIT_MASK 16 |
53 | #define MSIC_ADCTHERM_MASK 4 | |
54 | /* Number of ADC channels */ | |
55 | #define ADC_CHANLS_MAX 15 | |
56 | #define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS) | |
f017fbe7 D |
57 | |
58 | /* ADC channel code values */ | |
253a0069 AP |
59 | #define SKIN_SENSOR0_CODE 0x08 |
60 | #define SKIN_SENSOR1_CODE 0x09 | |
61 | #define SYS_SENSOR_CODE 0x0A | |
62 | #define MSIC_DIE_SENSOR_CODE 0x03 | |
f017fbe7 | 63 | |
253a0069 AP |
64 | #define SKIN_THERM_SENSOR0 0 |
65 | #define SKIN_THERM_SENSOR1 1 | |
66 | #define SYS_THERM_SENSOR2 2 | |
67 | #define MSIC_DIE_THERM_SENSOR3 3 | |
f017fbe7 D |
68 | |
69 | /* ADC code range */ | |
253a0069 AP |
70 | #define ADC_MAX 977 |
71 | #define ADC_MIN 162 | |
72 | #define ADC_VAL0C 887 | |
73 | #define ADC_VAL20C 720 | |
74 | #define ADC_VAL40C 508 | |
75 | #define ADC_VAL60C 315 | |
f017fbe7 D |
76 | |
77 | /* ADC base addresses */ | |
253a0069 AP |
78 | #define ADC_CHNL_START_ADDR 0x1C5 /* increments by 1 */ |
79 | #define ADC_DATA_START_ADDR 0x1D4 /* increments by 2 */ | |
f017fbe7 D |
80 | |
81 | /* MSIC die attributes */ | |
253a0069 AP |
82 | #define MSIC_DIE_ADC_MIN 488 |
83 | #define MSIC_DIE_ADC_MAX 1004 | |
f017fbe7 D |
84 | |
85 | /* This holds the address of the first free ADC channel, | |
86 | * among the 15 channels | |
87 | */ | |
88 | static int channel_index; | |
89 | ||
90 | struct platform_info { | |
253a0069 AP |
91 | struct platform_device *pdev; |
92 | struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS]; | |
f017fbe7 D |
93 | }; |
94 | ||
95 | struct thermal_device_info { | |
253a0069 AP |
96 | unsigned int chnl_addr; |
97 | int direct; | |
98 | /* This holds the current temperature in millidegree celsius */ | |
99 | long curr_temp; | |
f017fbe7 D |
100 | }; |
101 | ||
102 | /** | |
103 | * to_msic_die_temp - converts adc_val to msic_die temperature | |
104 | * @adc_val: ADC value to be converted | |
105 | * | |
106 | * Can sleep | |
107 | */ | |
108 | static int to_msic_die_temp(uint16_t adc_val) | |
109 | { | |
253a0069 | 110 | return (368 * (adc_val) / 1000) - 220; |
f017fbe7 D |
111 | } |
112 | ||
113 | /** | |
114 | * is_valid_adc - checks whether the adc code is within the defined range | |
115 | * @min: minimum value for the sensor | |
116 | * @max: maximum value for the sensor | |
117 | * | |
118 | * Can sleep | |
119 | */ | |
120 | static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max) | |
121 | { | |
253a0069 | 122 | return (adc_val >= min) && (adc_val <= max); |
f017fbe7 D |
123 | } |
124 | ||
125 | /** | |
126 | * adc_to_temp - converts the ADC code to temperature in C | |
127 | * @direct: true if ths channel is direct index | |
128 | * @adc_val: the adc_val that needs to be converted | |
129 | * @tp: temperature return value | |
130 | * | |
131 | * Linear approximation is used to covert the skin adc value into temperature. | |
132 | * This technique is used to avoid very long look-up table to get | |
133 | * the appropriate temp value from ADC value. | |
134 | * The adc code vs sensor temp curve is split into five parts | |
135 | * to achieve very close approximate temp value with less than | |
136 | * 0.5C error | |
137 | */ | |
138 | static int adc_to_temp(int direct, uint16_t adc_val, unsigned long *tp) | |
139 | { | |
253a0069 AP |
140 | int temp; |
141 | ||
142 | /* Direct conversion for die temperature */ | |
143 | if (direct) { | |
144 | if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) { | |
145 | *tp = to_msic_die_temp(adc_val) * 1000; | |
146 | return 0; | |
147 | } | |
148 | return -ERANGE; | |
149 | } | |
150 | ||
151 | if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX)) | |
152 | return -ERANGE; | |
153 | ||
154 | /* Linear approximation for skin temperature */ | |
155 | if (adc_val > ADC_VAL0C) | |
156 | temp = 177 - (adc_val/5); | |
157 | else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C)) | |
158 | temp = 111 - (adc_val/8); | |
159 | else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C)) | |
160 | temp = 92 - (adc_val/10); | |
161 | else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C)) | |
162 | temp = 91 - (adc_val/10); | |
163 | else | |
164 | temp = 112 - (adc_val/6); | |
165 | ||
166 | /* Convert temperature in celsius to milli degree celsius */ | |
167 | *tp = temp * 1000; | |
168 | return 0; | |
f017fbe7 D |
169 | } |
170 | ||
171 | /** | |
172 | * mid_read_temp - read sensors for temperature | |
173 | * @temp: holds the current temperature for the sensor after reading | |
174 | * | |
175 | * reads the adc_code from the channel and converts it to real | |
176 | * temperature. The converted value is stored in temp. | |
177 | * | |
178 | * Can sleep | |
179 | */ | |
180 | static int mid_read_temp(struct thermal_zone_device *tzd, unsigned long *temp) | |
181 | { | |
253a0069 AP |
182 | struct thermal_device_info *td_info = tzd->devdata; |
183 | uint16_t adc_val, addr; | |
184 | uint8_t data = 0; | |
185 | int ret; | |
186 | unsigned long curr_temp; | |
187 | ||
188 | ||
189 | addr = td_info->chnl_addr; | |
190 | ||
191 | /* Enable the msic for conversion before reading */ | |
192 | ret = intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL3, MSIC_ADCRRDATA_ENBL); | |
193 | if (ret) | |
194 | return ret; | |
195 | ||
196 | /* Re-toggle the RRDATARD bit (temporary workaround) */ | |
197 | ret = intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL3, MSIC_ADCTHERM_ENBL); | |
198 | if (ret) | |
199 | return ret; | |
200 | ||
201 | /* Read the higher bits of data */ | |
202 | ret = intel_scu_ipc_ioread8(addr, &data); | |
203 | if (ret) | |
204 | return ret; | |
205 | ||
206 | /* Shift bits to accommodate the lower two data bits */ | |
207 | adc_val = (data << 2); | |
208 | addr++; | |
209 | ||
210 | ret = intel_scu_ipc_ioread8(addr, &data);/* Read lower bits */ | |
211 | if (ret) | |
212 | return ret; | |
213 | ||
214 | /* Adding lower two bits to the higher bits */ | |
215 | data &= 03; | |
216 | adc_val += data; | |
217 | ||
218 | /* Convert ADC value to temperature */ | |
219 | ret = adc_to_temp(td_info->direct, adc_val, &curr_temp); | |
220 | if (ret == 0) | |
221 | *temp = td_info->curr_temp = curr_temp; | |
222 | return ret; | |
f017fbe7 D |
223 | } |
224 | ||
225 | /** | |
226 | * configure_adc - enables/disables the ADC for conversion | |
227 | * @val: zero: disables the ADC non-zero:enables the ADC | |
228 | * | |
229 | * Enable/Disable the ADC depending on the argument | |
230 | * | |
231 | * Can sleep | |
232 | */ | |
233 | static int configure_adc(int val) | |
234 | { | |
253a0069 AP |
235 | int ret; |
236 | uint8_t data; | |
237 | ||
238 | ret = intel_scu_ipc_ioread8(MSIC_THERM_ADC1CNTL1, &data); | |
239 | if (ret) | |
240 | return ret; | |
241 | ||
242 | if (val) { | |
243 | /* Enable and start the ADC */ | |
244 | data |= (MSIC_ADC_ENBL | MSIC_ADC_START); | |
245 | } else { | |
246 | /* Just stop the ADC */ | |
247 | data &= (~MSIC_ADC_START); | |
248 | } | |
249 | return intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL1, data); | |
f017fbe7 D |
250 | } |
251 | ||
252 | /** | |
253 | * set_up_therm_channel - enable thermal channel for conversion | |
254 | * @base_addr: index of free msic ADC channel | |
255 | * | |
256 | * Enable all the three channels for conversion | |
257 | * | |
258 | * Can sleep | |
259 | */ | |
260 | static int set_up_therm_channel(u16 base_addr) | |
261 | { | |
253a0069 AP |
262 | int ret; |
263 | ||
264 | /* Enable all the sensor channels */ | |
265 | ret = intel_scu_ipc_iowrite8(base_addr, SKIN_SENSOR0_CODE); | |
266 | if (ret) | |
267 | return ret; | |
268 | ||
269 | ret = intel_scu_ipc_iowrite8(base_addr + 1, SKIN_SENSOR1_CODE); | |
270 | if (ret) | |
271 | return ret; | |
272 | ||
273 | ret = intel_scu_ipc_iowrite8(base_addr + 2, SYS_SENSOR_CODE); | |
274 | if (ret) | |
275 | return ret; | |
276 | ||
277 | /* Since this is the last channel, set the stop bit | |
278 | * to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ | |
279 | ret = intel_scu_ipc_iowrite8(base_addr + 3, | |
280 | (MSIC_DIE_SENSOR_CODE | 0x10)); | |
281 | if (ret) | |
282 | return ret; | |
283 | ||
284 | /* Enable ADC and start it */ | |
285 | return configure_adc(1); | |
f017fbe7 D |
286 | } |
287 | ||
288 | /** | |
289 | * reset_stopbit - sets the stop bit to 0 on the given channel | |
290 | * @addr: address of the channel | |
291 | * | |
292 | * Can sleep | |
293 | */ | |
294 | static int reset_stopbit(uint16_t addr) | |
295 | { | |
253a0069 AP |
296 | int ret; |
297 | uint8_t data; | |
298 | ret = intel_scu_ipc_ioread8(addr, &data); | |
299 | if (ret) | |
300 | return ret; | |
301 | /* Set the stop bit to zero */ | |
302 | return intel_scu_ipc_iowrite8(addr, (data & 0xEF)); | |
f017fbe7 D |
303 | } |
304 | ||
305 | /** | |
306 | * find_free_channel - finds an empty channel for conversion | |
307 | * | |
308 | * If the ADC is not enabled then start using 0th channel | |
309 | * itself. Otherwise find an empty channel by looking for a | |
310 | * channel in which the stopbit is set to 1. returns the index | |
311 | * of the first free channel if succeeds or an error code. | |
312 | * | |
313 | * Context: can sleep | |
314 | * | |
315 | * FIXME: Ultimately the channel allocator will move into the intel_scu_ipc | |
316 | * code. | |
317 | */ | |
318 | static int find_free_channel(void) | |
319 | { | |
253a0069 AP |
320 | int ret; |
321 | int i; | |
322 | uint8_t data; | |
323 | ||
324 | /* check whether ADC is enabled */ | |
325 | ret = intel_scu_ipc_ioread8(MSIC_THERM_ADC1CNTL1, &data); | |
326 | if (ret) | |
327 | return ret; | |
328 | ||
329 | if ((data & MSIC_ADC_ENBL) == 0) | |
330 | return 0; | |
331 | ||
332 | /* ADC is already enabled; Looking for an empty channel */ | |
333 | for (i = 0; i < ADC_CHANLS_MAX; i++) { | |
334 | ret = intel_scu_ipc_ioread8(ADC_CHNL_START_ADDR + i, &data); | |
335 | if (ret) | |
336 | return ret; | |
337 | ||
338 | if (data & MSIC_STOPBIT_MASK) { | |
339 | ret = i; | |
340 | break; | |
341 | } | |
342 | } | |
343 | return (ret > ADC_LOOP_MAX) ? (-EINVAL) : ret; | |
f017fbe7 D |
344 | } |
345 | ||
346 | /** | |
347 | * mid_initialize_adc - initializing the ADC | |
348 | * @dev: our device structure | |
349 | * | |
350 | * Initialize the ADC for reading thermistor values. Can sleep. | |
351 | */ | |
352 | static int mid_initialize_adc(struct device *dev) | |
353 | { | |
253a0069 AP |
354 | u8 data; |
355 | u16 base_addr; | |
356 | int ret; | |
357 | ||
358 | /* | |
359 | * Ensure that adctherm is disabled before we | |
360 | * initialize the ADC | |
361 | */ | |
362 | ret = intel_scu_ipc_ioread8(MSIC_THERM_ADC1CNTL3, &data); | |
363 | if (ret) | |
364 | return ret; | |
365 | ||
366 | if (data & MSIC_ADCTHERM_MASK) | |
367 | dev_warn(dev, "ADCTHERM already set"); | |
368 | ||
369 | /* Index of the first channel in which the stop bit is set */ | |
370 | channel_index = find_free_channel(); | |
371 | if (channel_index < 0) { | |
372 | dev_err(dev, "No free ADC channels"); | |
373 | return channel_index; | |
374 | } | |
375 | ||
376 | base_addr = ADC_CHNL_START_ADDR + channel_index; | |
377 | ||
378 | if (!(channel_index == 0 || channel_index == ADC_LOOP_MAX)) { | |
379 | /* Reset stop bit for channels other than 0 and 12 */ | |
380 | ret = reset_stopbit(base_addr); | |
381 | if (ret) | |
382 | return ret; | |
383 | ||
384 | /* Index of the first free channel */ | |
385 | base_addr++; | |
386 | channel_index++; | |
387 | } | |
388 | ||
389 | ret = set_up_therm_channel(base_addr); | |
390 | if (ret) { | |
391 | dev_err(dev, "unable to enable ADC"); | |
392 | return ret; | |
393 | } | |
394 | dev_dbg(dev, "ADC initialization successful"); | |
395 | return ret; | |
f017fbe7 D |
396 | } |
397 | ||
398 | /** | |
399 | * initialize_sensor - sets default temp and timer ranges | |
400 | * @index: index of the sensor | |
401 | * | |
402 | * Context: can sleep | |
403 | */ | |
404 | static struct thermal_device_info *initialize_sensor(int index) | |
405 | { | |
253a0069 AP |
406 | struct thermal_device_info *td_info = |
407 | kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL); | |
408 | ||
409 | if (!td_info) | |
410 | return NULL; | |
411 | ||
412 | /* Set the base addr of the channel for this sensor */ | |
413 | td_info->chnl_addr = ADC_DATA_START_ADDR + 2 * (channel_index + index); | |
414 | /* Sensor 3 is direct conversion */ | |
415 | if (index == 3) | |
416 | td_info->direct = 1; | |
417 | return td_info; | |
f017fbe7 D |
418 | } |
419 | ||
420 | /** | |
421 | * mid_thermal_resume - resume routine | |
422 | * @pdev: platform device structure | |
423 | * | |
424 | * mid thermal resume: re-initializes the adc. Can sleep. | |
425 | */ | |
426 | static int mid_thermal_resume(struct platform_device *pdev) | |
427 | { | |
253a0069 | 428 | return mid_initialize_adc(&pdev->dev); |
f017fbe7 D |
429 | } |
430 | ||
431 | /** | |
432 | * mid_thermal_suspend - suspend routine | |
433 | * @pdev: platform device structure | |
434 | * | |
435 | * mid thermal suspend implements the suspend functionality | |
436 | * by stopping the ADC. Can sleep. | |
437 | */ | |
438 | static int mid_thermal_suspend(struct platform_device *pdev, pm_message_t mesg) | |
439 | { | |
253a0069 AP |
440 | /* |
441 | * This just stops the ADC and does not disable it. | |
442 | * temporary workaround until we have a generic ADC driver. | |
443 | * If 0 is passed, it disables the ADC. | |
444 | */ | |
445 | return configure_adc(0); | |
f017fbe7 D |
446 | } |
447 | ||
448 | /** | |
449 | * read_curr_temp - reads the current temperature and stores in temp | |
450 | * @temp: holds the current temperature value after reading | |
451 | * | |
452 | * Can sleep | |
453 | */ | |
454 | static int read_curr_temp(struct thermal_zone_device *tzd, unsigned long *temp) | |
455 | { | |
253a0069 AP |
456 | WARN_ON(tzd == NULL); |
457 | return mid_read_temp(tzd, temp); | |
f017fbe7 D |
458 | } |
459 | ||
460 | /* Can't be const */ | |
461 | static struct thermal_zone_device_ops tzd_ops = { | |
253a0069 | 462 | .get_temp = read_curr_temp, |
f017fbe7 D |
463 | }; |
464 | ||
f017fbe7 D |
465 | /** |
466 | * mid_thermal_probe - mfld thermal initialize | |
467 | * @pdev: platform device structure | |
468 | * | |
469 | * mid thermal probe initializes the hardware and registers | |
470 | * all the sensors with the generic thermal framework. Can sleep. | |
471 | */ | |
472 | static int mid_thermal_probe(struct platform_device *pdev) | |
473 | { | |
253a0069 AP |
474 | static char *name[MSIC_THERMAL_SENSORS] = { |
475 | "skin0", "skin1", "sys", "msicdie" | |
476 | }; | |
477 | ||
478 | int ret; | |
479 | int i; | |
480 | struct platform_info *pinfo; | |
481 | ||
482 | pinfo = kzalloc(sizeof(struct platform_info), GFP_KERNEL); | |
483 | if (!pinfo) | |
484 | return -ENOMEM; | |
485 | ||
486 | /* Initializing the hardware */ | |
487 | ret = mid_initialize_adc(&pdev->dev); | |
488 | if (ret) { | |
489 | dev_err(&pdev->dev, "ADC init failed"); | |
490 | kfree(pinfo); | |
491 | return ret; | |
492 | } | |
493 | ||
494 | /* Register each sensor with the generic thermal framework*/ | |
495 | for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { | |
496 | pinfo->tzd[i] = thermal_zone_device_register(name[i], | |
497 | 0, initialize_sensor(i), &tzd_ops, 0, 0, 0, 0); | |
498 | if (IS_ERR(pinfo->tzd[i])) | |
499 | goto reg_fail; | |
500 | } | |
501 | ||
502 | pinfo->pdev = pdev; | |
503 | platform_set_drvdata(pdev, pinfo); | |
504 | return 0; | |
f017fbe7 D |
505 | |
506 | reg_fail: | |
253a0069 AP |
507 | ret = PTR_ERR(pinfo->tzd[i]); |
508 | while (--i >= 0) | |
509 | thermal_zone_device_unregister(pinfo->tzd[i]); | |
510 | configure_adc(0); | |
511 | kfree(pinfo); | |
512 | return ret; | |
f017fbe7 D |
513 | } |
514 | ||
515 | /** | |
516 | * mid_thermal_remove - mfld thermal finalize | |
517 | * @dev: platform device structure | |
518 | * | |
519 | * MLFD thermal remove unregisters all the sensors from the generic | |
520 | * thermal framework. Can sleep. | |
521 | */ | |
522 | static int mid_thermal_remove(struct platform_device *pdev) | |
523 | { | |
253a0069 AP |
524 | int i; |
525 | struct platform_info *pinfo = platform_get_drvdata(pdev); | |
f017fbe7 | 526 | |
253a0069 AP |
527 | for (i = 0; i < MSIC_THERMAL_SENSORS; i++) |
528 | thermal_zone_device_unregister(pinfo->tzd[i]); | |
f017fbe7 | 529 | |
239dca9e | 530 | kfree(pinfo); |
253a0069 | 531 | platform_set_drvdata(pdev, NULL); |
f017fbe7 | 532 | |
253a0069 AP |
533 | /* Stop the ADC */ |
534 | return configure_adc(0); | |
f017fbe7 D |
535 | } |
536 | ||
f017fbe7 D |
537 | #define DRIVER_NAME "msic_sensor" |
538 | ||
539 | static const struct platform_device_id therm_id_table[] = { | |
253a0069 AP |
540 | { DRIVER_NAME, 1 }, |
541 | { } | |
f017fbe7 D |
542 | }; |
543 | ||
544 | static struct platform_driver mid_thermal_driver = { | |
253a0069 AP |
545 | .driver = { |
546 | .name = DRIVER_NAME, | |
547 | .owner = THIS_MODULE, | |
548 | }, | |
549 | .probe = mid_thermal_probe, | |
550 | .suspend = mid_thermal_suspend, | |
551 | .resume = mid_thermal_resume, | |
552 | .remove = __devexit_p(mid_thermal_remove), | |
553 | .id_table = therm_id_table, | |
f017fbe7 D |
554 | }; |
555 | ||
556 | static int __init mid_thermal_module_init(void) | |
557 | { | |
253a0069 | 558 | return platform_driver_register(&mid_thermal_driver); |
f017fbe7 D |
559 | } |
560 | ||
561 | static void __exit mid_thermal_module_exit(void) | |
562 | { | |
253a0069 | 563 | platform_driver_unregister(&mid_thermal_driver); |
f017fbe7 D |
564 | } |
565 | ||
566 | module_init(mid_thermal_module_init); | |
567 | module_exit(mid_thermal_module_exit); | |
568 | ||
569 | MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>"); | |
570 | MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver"); | |
571 | MODULE_LICENSE("GPL"); |