| /* | |
| * intel_mid_thermal.c - Intel MID platform thermal driver | |
| * | |
| * Copyright (C) 2011 Intel Corporation | |
| * | |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; version 2 of the License. | |
| * | |
| * This program is distributed in the hope that it will be useful, but | |
| * WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License along | |
| * with this program; if not, write to the Free Software Foundation, Inc., | |
| * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
| * | |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| * Author: Durgadoss R <durgadoss.r@intel.com> | |
| */ | |
| #define pr_fmt(fmt) "intel_mid_thermal: " fmt | |
| #include <linux/module.h> | |
| #include <linux/init.h> | |
| #include <linux/err.h> | |
| #include <linux/param.h> | |
| #include <linux/device.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/slab.h> | |
| #include <linux/pm.h> | |
| #include <linux/thermal.h> | |
| #include <linux/mfd/intel_msic.h> | |
| /* Number of thermal sensors */ | |
| #define MSIC_THERMAL_SENSORS 4 | |
| /* ADC1 - thermal registers */ | |
| #define MSIC_ADC_ENBL 0x10 | |
| #define MSIC_ADC_START 0x08 | |
| #define MSIC_ADCTHERM_ENBL 0x04 | |
| #define MSIC_ADCRRDATA_ENBL 0x05 | |
| #define MSIC_CHANL_MASK_VAL 0x0F | |
| #define MSIC_STOPBIT_MASK 16 | |
| #define MSIC_ADCTHERM_MASK 4 | |
| /* Number of ADC channels */ | |
| #define ADC_CHANLS_MAX 15 | |
| #define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS) | |
| /* ADC channel code values */ | |
| #define SKIN_SENSOR0_CODE 0x08 | |
| #define SKIN_SENSOR1_CODE 0x09 | |
| #define SYS_SENSOR_CODE 0x0A | |
| #define MSIC_DIE_SENSOR_CODE 0x03 | |
| #define SKIN_THERM_SENSOR0 0 | |
| #define SKIN_THERM_SENSOR1 1 | |
| #define SYS_THERM_SENSOR2 2 | |
| #define MSIC_DIE_THERM_SENSOR3 3 | |
| /* ADC code range */ | |
| #define ADC_MAX 977 | |
| #define ADC_MIN 162 | |
| #define ADC_VAL0C 887 | |
| #define ADC_VAL20C 720 | |
| #define ADC_VAL40C 508 | |
| #define ADC_VAL60C 315 | |
| /* ADC base addresses */ | |
| #define ADC_CHNL_START_ADDR INTEL_MSIC_ADC1ADDR0 /* increments by 1 */ | |
| #define ADC_DATA_START_ADDR INTEL_MSIC_ADC1SNS0H /* increments by 2 */ | |
| /* MSIC die attributes */ | |
| #define MSIC_DIE_ADC_MIN 488 | |
| #define MSIC_DIE_ADC_MAX 1004 | |
| /* This holds the address of the first free ADC channel, | |
| * among the 15 channels | |
| */ | |
| static int channel_index; | |
| struct platform_info { | |
| struct platform_device *pdev; | |
| struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS]; | |
| }; | |
| struct thermal_device_info { | |
| unsigned int chnl_addr; | |
| int direct; | |
| /* This holds the current temperature in millidegree celsius */ | |
| long curr_temp; | |
| }; | |
| /** | |
| * to_msic_die_temp - converts adc_val to msic_die temperature | |
| * @adc_val: ADC value to be converted | |
| * | |
| * Can sleep | |
| */ | |
| static int to_msic_die_temp(uint16_t adc_val) | |
| { | |
| return (368 * (adc_val) / 1000) - 220; | |
| } | |
| /** | |
| * is_valid_adc - checks whether the adc code is within the defined range | |
| * @min: minimum value for the sensor | |
| * @max: maximum value for the sensor | |
| * | |
| * Can sleep | |
| */ | |
| static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max) | |
| { | |
| return (adc_val >= min) && (adc_val <= max); | |
| } | |
| /** | |
| * adc_to_temp - converts the ADC code to temperature in C | |
| * @direct: true if ths channel is direct index | |
| * @adc_val: the adc_val that needs to be converted | |
| * @tp: temperature return value | |
| * | |
| * Linear approximation is used to covert the skin adc value into temperature. | |
| * This technique is used to avoid very long look-up table to get | |
| * the appropriate temp value from ADC value. | |
| * The adc code vs sensor temp curve is split into five parts | |
| * to achieve very close approximate temp value with less than | |
| * 0.5C error | |
| */ | |
| static int adc_to_temp(int direct, uint16_t adc_val, int *tp) | |
| { | |
| int temp; | |
| /* Direct conversion for die temperature */ | |
| if (direct) { | |
| if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) { | |
| *tp = to_msic_die_temp(adc_val) * 1000; | |
| return 0; | |
| } | |
| return -ERANGE; | |
| } | |
| if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX)) | |
| return -ERANGE; | |
| /* Linear approximation for skin temperature */ | |
| if (adc_val > ADC_VAL0C) | |
| temp = 177 - (adc_val/5); | |
| else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C)) | |
| temp = 111 - (adc_val/8); | |
| else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C)) | |
| temp = 92 - (adc_val/10); | |
| else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C)) | |
| temp = 91 - (adc_val/10); | |
| else | |
| temp = 112 - (adc_val/6); | |
| /* Convert temperature in celsius to milli degree celsius */ | |
| *tp = temp * 1000; | |
| return 0; | |
| } | |
| /** | |
| * mid_read_temp - read sensors for temperature | |
| * @temp: holds the current temperature for the sensor after reading | |
| * | |
| * reads the adc_code from the channel and converts it to real | |
| * temperature. The converted value is stored in temp. | |
| * | |
| * Can sleep | |
| */ | |
| static int mid_read_temp(struct thermal_zone_device *tzd, int *temp) | |
| { | |
| struct thermal_device_info *td_info = tzd->devdata; | |
| uint16_t adc_val, addr; | |
| uint8_t data = 0; | |
| int ret; | |
| int curr_temp; | |
| addr = td_info->chnl_addr; | |
| /* Enable the msic for conversion before reading */ | |
| ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCRRDATA_ENBL); | |
| if (ret) | |
| return ret; | |
| /* Re-toggle the RRDATARD bit (temporary workaround) */ | |
| ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCTHERM_ENBL); | |
| if (ret) | |
| return ret; | |
| /* Read the higher bits of data */ | |
| ret = intel_msic_reg_read(addr, &data); | |
| if (ret) | |
| return ret; | |
| /* Shift bits to accommodate the lower two data bits */ | |
| adc_val = (data << 2); | |
| addr++; | |
| ret = intel_msic_reg_read(addr, &data);/* Read lower bits */ | |
| if (ret) | |
| return ret; | |
| /* Adding lower two bits to the higher bits */ | |
| data &= 03; | |
| adc_val += data; | |
| /* Convert ADC value to temperature */ | |
| ret = adc_to_temp(td_info->direct, adc_val, &curr_temp); | |
| if (ret == 0) | |
| *temp = td_info->curr_temp = curr_temp; | |
| return ret; | |
| } | |
| /** | |
| * configure_adc - enables/disables the ADC for conversion | |
| * @val: zero: disables the ADC non-zero:enables the ADC | |
| * | |
| * Enable/Disable the ADC depending on the argument | |
| * | |
| * Can sleep | |
| */ | |
| static int configure_adc(int val) | |
| { | |
| int ret; | |
| uint8_t data; | |
| ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); | |
| if (ret) | |
| return ret; | |
| if (val) { | |
| /* Enable and start the ADC */ | |
| data |= (MSIC_ADC_ENBL | MSIC_ADC_START); | |
| } else { | |
| /* Just stop the ADC */ | |
| data &= (~MSIC_ADC_START); | |
| } | |
| return intel_msic_reg_write(INTEL_MSIC_ADC1CNTL1, data); | |
| } | |
| /** | |
| * set_up_therm_channel - enable thermal channel for conversion | |
| * @base_addr: index of free msic ADC channel | |
| * | |
| * Enable all the three channels for conversion | |
| * | |
| * Can sleep | |
| */ | |
| static int set_up_therm_channel(u16 base_addr) | |
| { | |
| int ret; | |
| /* Enable all the sensor channels */ | |
| ret = intel_msic_reg_write(base_addr, SKIN_SENSOR0_CODE); | |
| if (ret) | |
| return ret; | |
| ret = intel_msic_reg_write(base_addr + 1, SKIN_SENSOR1_CODE); | |
| if (ret) | |
| return ret; | |
| ret = intel_msic_reg_write(base_addr + 2, SYS_SENSOR_CODE); | |
| if (ret) | |
| return ret; | |
| /* Since this is the last channel, set the stop bit | |
| * to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ | |
| ret = intel_msic_reg_write(base_addr + 3, | |
| (MSIC_DIE_SENSOR_CODE | 0x10)); | |
| if (ret) | |
| return ret; | |
| /* Enable ADC and start it */ | |
| return configure_adc(1); | |
| } | |
| /** | |
| * reset_stopbit - sets the stop bit to 0 on the given channel | |
| * @addr: address of the channel | |
| * | |
| * Can sleep | |
| */ | |
| static int reset_stopbit(uint16_t addr) | |
| { | |
| int ret; | |
| uint8_t data; | |
| ret = intel_msic_reg_read(addr, &data); | |
| if (ret) | |
| return ret; | |
| /* Set the stop bit to zero */ | |
| return intel_msic_reg_write(addr, (data & 0xEF)); | |
| } | |
| /** | |
| * find_free_channel - finds an empty channel for conversion | |
| * | |
| * If the ADC is not enabled then start using 0th channel | |
| * itself. Otherwise find an empty channel by looking for a | |
| * channel in which the stopbit is set to 1. returns the index | |
| * of the first free channel if succeeds or an error code. | |
| * | |
| * Context: can sleep | |
| * | |
| * FIXME: Ultimately the channel allocator will move into the intel_scu_ipc | |
| * code. | |
| */ | |
| static int find_free_channel(void) | |
| { | |
| int ret; | |
| int i; | |
| uint8_t data; | |
| /* check whether ADC is enabled */ | |
| ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); | |
| if (ret) | |
| return ret; | |
| if ((data & MSIC_ADC_ENBL) == 0) | |
| return 0; | |
| /* ADC is already enabled; Looking for an empty channel */ | |
| for (i = 0; i < ADC_CHANLS_MAX; i++) { | |
| ret = intel_msic_reg_read(ADC_CHNL_START_ADDR + i, &data); | |
| if (ret) | |
| return ret; | |
| if (data & MSIC_STOPBIT_MASK) { | |
| ret = i; | |
| break; | |
| } | |
| } | |
| return (ret > ADC_LOOP_MAX) ? (-EINVAL) : ret; | |
| } | |
| /** | |
| * mid_initialize_adc - initializing the ADC | |
| * @dev: our device structure | |
| * | |
| * Initialize the ADC for reading thermistor values. Can sleep. | |
| */ | |
| static int mid_initialize_adc(struct device *dev) | |
| { | |
| u8 data; | |
| u16 base_addr; | |
| int ret; | |
| /* | |
| * Ensure that adctherm is disabled before we | |
| * initialize the ADC | |
| */ | |
| ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL3, &data); | |
| if (ret) | |
| return ret; | |
| data &= ~MSIC_ADCTHERM_MASK; | |
| ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, data); | |
| if (ret) | |
| return ret; | |
| /* Index of the first channel in which the stop bit is set */ | |
| channel_index = find_free_channel(); | |
| if (channel_index < 0) { | |
| dev_err(dev, "No free ADC channels"); | |
| return channel_index; | |
| } | |
| base_addr = ADC_CHNL_START_ADDR + channel_index; | |
| if (!(channel_index == 0 || channel_index == ADC_LOOP_MAX)) { | |
| /* Reset stop bit for channels other than 0 and 12 */ | |
| ret = reset_stopbit(base_addr); | |
| if (ret) | |
| return ret; | |
| /* Index of the first free channel */ | |
| base_addr++; | |
| channel_index++; | |
| } | |
| ret = set_up_therm_channel(base_addr); | |
| if (ret) { | |
| dev_err(dev, "unable to enable ADC"); | |
| return ret; | |
| } | |
| dev_dbg(dev, "ADC initialization successful"); | |
| return ret; | |
| } | |
| /** | |
| * initialize_sensor - sets default temp and timer ranges | |
| * @index: index of the sensor | |
| * | |
| * Context: can sleep | |
| */ | |
| static struct thermal_device_info *initialize_sensor(int index) | |
| { | |
| struct thermal_device_info *td_info = | |
| kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL); | |
| if (!td_info) | |
| return NULL; | |
| /* Set the base addr of the channel for this sensor */ | |
| td_info->chnl_addr = ADC_DATA_START_ADDR + 2 * (channel_index + index); | |
| /* Sensor 3 is direct conversion */ | |
| if (index == 3) | |
| td_info->direct = 1; | |
| return td_info; | |
| } | |
| #ifdef CONFIG_PM_SLEEP | |
| /** | |
| * mid_thermal_resume - resume routine | |
| * @dev: device structure | |
| * | |
| * mid thermal resume: re-initializes the adc. Can sleep. | |
| */ | |
| static int mid_thermal_resume(struct device *dev) | |
| { | |
| return mid_initialize_adc(dev); | |
| } | |
| /** | |
| * mid_thermal_suspend - suspend routine | |
| * @dev: device structure | |
| * | |
| * mid thermal suspend implements the suspend functionality | |
| * by stopping the ADC. Can sleep. | |
| */ | |
| static int mid_thermal_suspend(struct device *dev) | |
| { | |
| /* | |
| * This just stops the ADC and does not disable it. | |
| * temporary workaround until we have a generic ADC driver. | |
| * If 0 is passed, it disables the ADC. | |
| */ | |
| return configure_adc(0); | |
| } | |
| #endif | |
| static SIMPLE_DEV_PM_OPS(mid_thermal_pm, | |
| mid_thermal_suspend, mid_thermal_resume); | |
| /** | |
| * read_curr_temp - reads the current temperature and stores in temp | |
| * @temp: holds the current temperature value after reading | |
| * | |
| * Can sleep | |
| */ | |
| static int read_curr_temp(struct thermal_zone_device *tzd, int *temp) | |
| { | |
| WARN_ON(tzd == NULL); | |
| return mid_read_temp(tzd, temp); | |
| } | |
| /* Can't be const */ | |
| static struct thermal_zone_device_ops tzd_ops = { | |
| .get_temp = read_curr_temp, | |
| }; | |
| /** | |
| * mid_thermal_probe - mfld thermal initialize | |
| * @pdev: platform device structure | |
| * | |
| * mid thermal probe initializes the hardware and registers | |
| * all the sensors with the generic thermal framework. Can sleep. | |
| */ | |
| static int mid_thermal_probe(struct platform_device *pdev) | |
| { | |
| static char *name[MSIC_THERMAL_SENSORS] = { | |
| "skin0", "skin1", "sys", "msicdie" | |
| }; | |
| int ret; | |
| int i; | |
| struct platform_info *pinfo; | |
| pinfo = devm_kzalloc(&pdev->dev, sizeof(struct platform_info), | |
| GFP_KERNEL); | |
| if (!pinfo) | |
| return -ENOMEM; | |
| /* Initializing the hardware */ | |
| ret = mid_initialize_adc(&pdev->dev); | |
| if (ret) { | |
| dev_err(&pdev->dev, "ADC init failed"); | |
| return ret; | |
| } | |
| /* Register each sensor with the generic thermal framework*/ | |
| for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { | |
| struct thermal_device_info *td_info = initialize_sensor(i); | |
| if (!td_info) { | |
| ret = -ENOMEM; | |
| goto err; | |
| } | |
| pinfo->tzd[i] = thermal_zone_device_register(name[i], | |
| 0, 0, td_info, &tzd_ops, NULL, 0, 0); | |
| if (IS_ERR(pinfo->tzd[i])) { | |
| kfree(td_info); | |
| ret = PTR_ERR(pinfo->tzd[i]); | |
| goto err; | |
| } | |
| } | |
| pinfo->pdev = pdev; | |
| platform_set_drvdata(pdev, pinfo); | |
| return 0; | |
| err: | |
| while (--i >= 0) { | |
| kfree(pinfo->tzd[i]->devdata); | |
| thermal_zone_device_unregister(pinfo->tzd[i]); | |
| } | |
| configure_adc(0); | |
| return ret; | |
| } | |
| /** | |
| * mid_thermal_remove - mfld thermal finalize | |
| * @dev: platform device structure | |
| * | |
| * MLFD thermal remove unregisters all the sensors from the generic | |
| * thermal framework. Can sleep. | |
| */ | |
| static int mid_thermal_remove(struct platform_device *pdev) | |
| { | |
| int i; | |
| struct platform_info *pinfo = platform_get_drvdata(pdev); | |
| for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { | |
| kfree(pinfo->tzd[i]->devdata); | |
| thermal_zone_device_unregister(pinfo->tzd[i]); | |
| } | |
| /* Stop the ADC */ | |
| return configure_adc(0); | |
| } | |
| #define DRIVER_NAME "msic_thermal" | |
| static const struct platform_device_id therm_id_table[] = { | |
| { DRIVER_NAME, 1 }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(platform, therm_id_table); | |
| static struct platform_driver mid_thermal_driver = { | |
| .driver = { | |
| .name = DRIVER_NAME, | |
| .pm = &mid_thermal_pm, | |
| }, | |
| .probe = mid_thermal_probe, | |
| .remove = mid_thermal_remove, | |
| .id_table = therm_id_table, | |
| }; | |
| module_platform_driver(mid_thermal_driver); | |
| MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>"); | |
| MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver"); | |
| MODULE_LICENSE("GPL"); |