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 */ | |
40 | #define MSIC_THERMAL_SENSORS 4 | |
41 | ||
42 | /* ADC1 - thermal registers */ | |
43 | #define MSIC_THERM_ADC1CNTL1 0x1C0 | |
44 | #define MSIC_ADC_ENBL 0x10 | |
45 | #define MSIC_ADC_START 0x08 | |
46 | ||
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 | |
51 | ||
52 | #define MSIC_STOPBIT_MASK 16 | |
53 | #define MSIC_ADCTHERM_MASK 4 | |
54 | #define ADC_CHANLS_MAX 15 /* Number of ADC channels */ | |
55 | #define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS) | |
56 | ||
57 | /* ADC channel code values */ | |
58 | #define SKIN_SENSOR0_CODE 0x08 | |
59 | #define SKIN_SENSOR1_CODE 0x09 | |
60 | #define SYS_SENSOR_CODE 0x0A | |
61 | #define MSIC_DIE_SENSOR_CODE 0x03 | |
62 | ||
63 | #define SKIN_THERM_SENSOR0 0 | |
64 | #define SKIN_THERM_SENSOR1 1 | |
65 | #define SYS_THERM_SENSOR2 2 | |
66 | #define MSIC_DIE_THERM_SENSOR3 3 | |
67 | ||
68 | /* ADC code range */ | |
69 | #define ADC_MAX 977 | |
70 | #define ADC_MIN 162 | |
71 | #define ADC_VAL0C 887 | |
72 | #define ADC_VAL20C 720 | |
73 | #define ADC_VAL40C 508 | |
74 | #define ADC_VAL60C 315 | |
75 | ||
76 | /* ADC base addresses */ | |
77 | #define ADC_CHNL_START_ADDR 0x1C5 /* increments by 1 */ | |
78 | #define ADC_DATA_START_ADDR 0x1D4 /* increments by 2 */ | |
79 | ||
80 | /* MSIC die attributes */ | |
81 | #define MSIC_DIE_ADC_MIN 488 | |
82 | #define MSIC_DIE_ADC_MAX 1004 | |
83 | ||
84 | /* This holds the address of the first free ADC channel, | |
85 | * among the 15 channels | |
86 | */ | |
87 | static int channel_index; | |
88 | ||
89 | struct platform_info { | |
90 | struct platform_device *pdev; | |
91 | struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS]; | |
92 | }; | |
93 | ||
94 | struct thermal_device_info { | |
95 | unsigned int chnl_addr; | |
96 | int direct; | |
97 | /* This holds the current temperature in millidegree celsius */ | |
98 | long curr_temp; | |
99 | }; | |
100 | ||
101 | /** | |
102 | * to_msic_die_temp - converts adc_val to msic_die temperature | |
103 | * @adc_val: ADC value to be converted | |
104 | * | |
105 | * Can sleep | |
106 | */ | |
107 | static int to_msic_die_temp(uint16_t adc_val) | |
108 | { | |
109 | return (368 * (adc_val) / 1000) - 220; | |
110 | } | |
111 | ||
112 | /** | |
113 | * is_valid_adc - checks whether the adc code is within the defined range | |
114 | * @min: minimum value for the sensor | |
115 | * @max: maximum value for the sensor | |
116 | * | |
117 | * Can sleep | |
118 | */ | |
119 | static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max) | |
120 | { | |
121 | return (adc_val >= min) && (adc_val <= max); | |
122 | } | |
123 | ||
124 | /** | |
125 | * adc_to_temp - converts the ADC code to temperature in C | |
126 | * @direct: true if ths channel is direct index | |
127 | * @adc_val: the adc_val that needs to be converted | |
128 | * @tp: temperature return value | |
129 | * | |
130 | * Linear approximation is used to covert the skin adc value into temperature. | |
131 | * This technique is used to avoid very long look-up table to get | |
132 | * the appropriate temp value from ADC value. | |
133 | * The adc code vs sensor temp curve is split into five parts | |
134 | * to achieve very close approximate temp value with less than | |
135 | * 0.5C error | |
136 | */ | |
137 | static int adc_to_temp(int direct, uint16_t adc_val, unsigned long *tp) | |
138 | { | |
139 | int temp; | |
140 | ||
141 | /* Direct conversion for die temperature */ | |
142 | if (direct) { | |
143 | if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) { | |
144 | *tp = to_msic_die_temp(adc_val) * 1000; | |
145 | return 0; | |
146 | } | |
147 | return -ERANGE; | |
148 | } | |
149 | ||
150 | if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX)) | |
151 | return -ERANGE; | |
152 | ||
153 | /* Linear approximation for skin temperature */ | |
154 | if (adc_val > ADC_VAL0C) | |
155 | temp = 177 - (adc_val/5); | |
156 | else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C)) | |
157 | temp = 111 - (adc_val/8); | |
158 | else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C)) | |
159 | temp = 92 - (adc_val/10); | |
160 | else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C)) | |
161 | temp = 91 - (adc_val/10); | |
162 | else | |
163 | temp = 112 - (adc_val/6); | |
164 | ||
165 | /* Convert temperature in celsius to milli degree celsius */ | |
166 | *tp = temp * 1000; | |
167 | return 0; | |
168 | } | |
169 | ||
170 | /** | |
171 | * mid_read_temp - read sensors for temperature | |
172 | * @temp: holds the current temperature for the sensor after reading | |
173 | * | |
174 | * reads the adc_code from the channel and converts it to real | |
175 | * temperature. The converted value is stored in temp. | |
176 | * | |
177 | * Can sleep | |
178 | */ | |
179 | static int mid_read_temp(struct thermal_zone_device *tzd, unsigned long *temp) | |
180 | { | |
181 | struct thermal_device_info *td_info = tzd->devdata; | |
182 | uint16_t adc_val, addr; | |
183 | uint8_t data = 0; | |
184 | int ret; | |
185 | unsigned long curr_temp; | |
186 | ||
187 | ||
188 | addr = td_info->chnl_addr; | |
189 | ||
190 | /* Enable the msic for conversion before reading */ | |
191 | ret = intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL3, MSIC_ADCRRDATA_ENBL); | |
192 | if (ret) | |
193 | return ret; | |
194 | ||
195 | /* Re-toggle the RRDATARD bit (temporary workaround) */ | |
196 | ret = intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL3, MSIC_ADCTHERM_ENBL); | |
197 | if (ret) | |
198 | return ret; | |
199 | ||
200 | /* Read the higher bits of data */ | |
201 | ret = intel_scu_ipc_ioread8(addr, &data); | |
202 | if (ret) | |
203 | return ret; | |
204 | ||
25985edc | 205 | /* Shift bits to accommodate the lower two data bits */ |
f017fbe7 D |
206 | adc_val = (data << 2); |
207 | addr++; | |
208 | ||
209 | ret = intel_scu_ipc_ioread8(addr, &data);/* Read lower bits */ | |
210 | if (ret) | |
211 | return ret; | |
212 | ||
213 | /* Adding lower two bits to the higher bits */ | |
214 | data &= 03; | |
215 | adc_val += data; | |
216 | ||
217 | /* Convert ADC value to temperature */ | |
218 | ret = adc_to_temp(td_info->direct, adc_val, &curr_temp); | |
219 | if (ret == 0) | |
220 | *temp = td_info->curr_temp = curr_temp; | |
221 | return ret; | |
222 | } | |
223 | ||
224 | /** | |
225 | * configure_adc - enables/disables the ADC for conversion | |
226 | * @val: zero: disables the ADC non-zero:enables the ADC | |
227 | * | |
228 | * Enable/Disable the ADC depending on the argument | |
229 | * | |
230 | * Can sleep | |
231 | */ | |
232 | static int configure_adc(int val) | |
233 | { | |
234 | int ret; | |
235 | uint8_t data; | |
236 | ||
237 | ret = intel_scu_ipc_ioread8(MSIC_THERM_ADC1CNTL1, &data); | |
238 | if (ret) | |
239 | return ret; | |
240 | ||
241 | if (val) { | |
242 | /* Enable and start the ADC */ | |
243 | data |= (MSIC_ADC_ENBL | MSIC_ADC_START); | |
244 | } else { | |
245 | /* Just stop the ADC */ | |
246 | data &= (~MSIC_ADC_START); | |
247 | } | |
248 | ||
249 | return intel_scu_ipc_iowrite8(MSIC_THERM_ADC1CNTL1, data); | |
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 | { | |
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); | |
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 | { | |
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)); | |
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 | { | |
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; | |
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 | { | |
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; | |
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 | { | |
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; | |
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 | { | |
428 | return mid_initialize_adc(&pdev->dev); | |
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 | { | |
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); | |
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 | { | |
456 | WARN_ON(tzd == NULL); | |
457 | return mid_read_temp(tzd, temp); | |
458 | } | |
459 | ||
460 | /* Can't be const */ | |
461 | static struct thermal_zone_device_ops tzd_ops = { | |
462 | .get_temp = read_curr_temp, | |
463 | }; | |
464 | ||
465 | ||
466 | /** | |
467 | * mid_thermal_probe - mfld thermal initialize | |
468 | * @pdev: platform device structure | |
469 | * | |
470 | * mid thermal probe initializes the hardware and registers | |
471 | * all the sensors with the generic thermal framework. Can sleep. | |
472 | */ | |
473 | static int mid_thermal_probe(struct platform_device *pdev) | |
474 | { | |
475 | static char *name[MSIC_THERMAL_SENSORS] = { | |
476 | "skin0", "skin1", "sys", "msicdie" | |
477 | }; | |
478 | ||
479 | int ret; | |
480 | int i; | |
481 | struct platform_info *pinfo; | |
482 | ||
483 | pinfo = kzalloc(sizeof(struct platform_info), GFP_KERNEL); | |
484 | if (!pinfo) | |
485 | return -ENOMEM; | |
486 | ||
487 | /* Initializing the hardware */ | |
488 | ret = mid_initialize_adc(&pdev->dev); | |
489 | if (ret) { | |
490 | dev_err(&pdev->dev, "ADC init failed"); | |
491 | kfree(pinfo); | |
492 | return ret; | |
493 | } | |
494 | ||
495 | /* Register each sensor with the generic thermal framework*/ | |
496 | for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { | |
497 | pinfo->tzd[i] = thermal_zone_device_register(name[i], | |
498 | 0, initialize_sensor(i), | |
499 | &tzd_ops, 0, 0, 0, 0); | |
500 | if (IS_ERR(pinfo->tzd[i])) | |
501 | goto reg_fail; | |
502 | } | |
503 | ||
504 | pinfo->pdev = pdev; | |
505 | platform_set_drvdata(pdev, pinfo); | |
506 | return 0; | |
507 | ||
508 | reg_fail: | |
509 | ret = PTR_ERR(pinfo->tzd[i]); | |
510 | while (--i >= 0) | |
511 | thermal_zone_device_unregister(pinfo->tzd[i]); | |
512 | configure_adc(0); | |
513 | kfree(pinfo); | |
514 | return ret; | |
515 | } | |
516 | ||
517 | /** | |
518 | * mid_thermal_remove - mfld thermal finalize | |
519 | * @dev: platform device structure | |
520 | * | |
521 | * MLFD thermal remove unregisters all the sensors from the generic | |
522 | * thermal framework. Can sleep. | |
523 | */ | |
524 | static int mid_thermal_remove(struct platform_device *pdev) | |
525 | { | |
526 | int i; | |
527 | struct platform_info *pinfo = platform_get_drvdata(pdev); | |
528 | ||
529 | for (i = 0; i < MSIC_THERMAL_SENSORS; i++) | |
530 | thermal_zone_device_unregister(pinfo->tzd[i]); | |
531 | ||
532 | platform_set_drvdata(pdev, NULL); | |
533 | ||
534 | /* Stop the ADC */ | |
535 | return configure_adc(0); | |
536 | } | |
537 | ||
538 | /********************************************************************* | |
539 | * Driver initialisation and finalization | |
540 | *********************************************************************/ | |
541 | ||
542 | #define DRIVER_NAME "msic_sensor" | |
543 | ||
544 | static const struct platform_device_id therm_id_table[] = { | |
545 | { DRIVER_NAME, 1 }, | |
47ae4352 | 546 | { } |
f017fbe7 D |
547 | }; |
548 | ||
549 | static struct platform_driver mid_thermal_driver = { | |
550 | .driver = { | |
551 | .name = DRIVER_NAME, | |
552 | .owner = THIS_MODULE, | |
553 | }, | |
554 | .probe = mid_thermal_probe, | |
555 | .suspend = mid_thermal_suspend, | |
556 | .resume = mid_thermal_resume, | |
557 | .remove = __devexit_p(mid_thermal_remove), | |
558 | .id_table = therm_id_table, | |
559 | }; | |
560 | ||
561 | static int __init mid_thermal_module_init(void) | |
562 | { | |
563 | return platform_driver_register(&mid_thermal_driver); | |
564 | } | |
565 | ||
566 | static void __exit mid_thermal_module_exit(void) | |
567 | { | |
568 | platform_driver_unregister(&mid_thermal_driver); | |
569 | } | |
570 | ||
571 | module_init(mid_thermal_module_init); | |
572 | module_exit(mid_thermal_module_exit); | |
573 | ||
574 | MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>"); | |
575 | MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver"); | |
576 | MODULE_LICENSE("GPL"); |