diff options
author | Yin Kangkai <kangkai.yin@linux.intel.com> | 2013-12-26 22:32:00 +0800 |
---|---|---|
committer | Yin Kangkai <kangkai.yin@linux.intel.com> | 2013-12-26 22:32:00 +0800 |
commit | 47279d7332b3fabb314d305d9a797569f49b85ff (patch) | |
tree | b0d867a7f1013ce879d756bcfa2f027a93bfd2ff /drivers/thermal | |
download | kernel-clovertrail-47279d7332b3fabb314d305d9a797569f49b85ff.tar.gz kernel-clovertrail-47279d7332b3fabb314d305d9a797569f49b85ff.tar.bz2 kernel-clovertrail-47279d7332b3fabb314d305d9a797569f49b85ff.zip |
initial import
Change-Id: I10a809ebfe35facab3592c6bdd87f6ccca8e2e68
Diffstat (limited to 'drivers/thermal')
-rw-r--r-- | drivers/thermal/Kconfig | 58 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 8 | ||||
-rw-r--r-- | drivers/thermal/intel_mrfl_thermal.c | 818 | ||||
-rw-r--r-- | drivers/thermal/intel_soc_thermal.c | 482 | ||||
-rw-r--r-- | drivers/thermal/spear_thermal.c | 206 | ||||
-rw-r--r-- | drivers/thermal/thermal_sys.c | 1793 |
6 files changed, 3365 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig new file mode 100644 index 00000000000..9ef31f9785b --- /dev/null +++ b/drivers/thermal/Kconfig @@ -0,0 +1,58 @@ +# +# Generic thermal sysfs drivers configuration +# + +menuconfig THERMAL + tristate "Generic Thermal sysfs driver" + help + Generic Thermal Sysfs driver offers a generic mechanism for + thermal management. Usually it's made up of one or more thermal + zone and cooling device. + Each thermal zone contains its own temperature, trip points, + cooling devices. + All platforms with ACPI thermal support can use this driver. + If you want this support, you should say Y or M here. + +config THERMAL_HWMON + bool + depends on THERMAL + depends on HWMON=y || HWMON=THERMAL + default y + +config SPEAR_THERMAL + bool "SPEAr thermal sensor driver" + depends on THERMAL + depends on PLAT_SPEAR + help + Enable this to plug the SPEAr thermal sensor driver into the Linux + thermal framework + +config DEBUG_THERMAL + bool "Thermal debug information support" + depends on THERMAL + help + This enables debug sysfs interfaces/information for Thermal + subsystem. + + Saying Y here will expose extra sysfs nodes under + /sys/class/thermal/thermal_zoneX/ + +config SENSORS_THERMAL_MRFLD + tristate "Thermal driver for Intel Merrifield platform" + depends on THERMAL && IIO + help + Say Y here to enable thermal driver on Intel Merrifield + platform. + + To load this driver as a module, select M here. The module + will be called "mrfl_thermal" + +config SOC_THERMAL + tristate "SoC Thermal driver" + depends on THERMAL + help + SoC Thermal driver registers to Generic Thermal Framework. + Exposes SoC DTS and aux trip point values through the framework. + + Say Y here to enable thermal driver on Intel Merrifield + platform. To load this driver as a module, select M here. diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile new file mode 100644 index 00000000000..ece76fa656c --- /dev/null +++ b/drivers/thermal/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for sensor chip drivers. +# + +obj-$(CONFIG_THERMAL) += thermal_sys.o +obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o +obj-$(CONFIG_SENSORS_THERMAL_MRFLD) += intel_mrfl_thermal.o +obj-$(CONFIG_SOC_THERMAL) += intel_soc_thermal.o diff --git a/drivers/thermal/intel_mrfl_thermal.c b/drivers/thermal/intel_mrfl_thermal.c new file mode 100644 index 00000000000..13bc35569fb --- /dev/null +++ b/drivers/thermal/intel_mrfl_thermal.c @@ -0,0 +1,818 @@ +/* + * intel_mrfl_thermal.c - Intel Merrifield 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> + * + * DEVICE_NAME: Intel Merrifield platform - PMIC: Thermal Monitor + */ + +#define pr_fmt(fmt) "intel_mrfl_thermal: " fmt + +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/rpmsg.h> +#include <linux/module.h> +#include <linux/thermal.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <asm/intel_scu_pmic.h> +#include <asm/intel_mid_rpmsg.h> +#include <asm/intel_basincove_gpadc.h> + +#include "../staging/iio/consumer.h" + +#define DRIVER_NAME "bcove_thrm" +#define DEVICE_NAME "mrfl_pmic_thermal" + +/* Number of Thermal sensors on the PMIC */ +#define PMIC_THERMAL_SENSORS 4 + +/* Registers that govern Thermal Monitoring */ +#define THRMMONCFG 0xB3 +#define THRMMONCTL 0xB4 +#define THRMIRQ 0x04 +#define MTHRMIRQ 0x0F +#define STHRMIRQ 0xB2 +#define IRQLVL1 0x01 +#define MIRQLVL1 0x0C +#define IRQ_MASK_ALL 0x0F + +/* PMIC SRAM base address and offset for Thermal register */ +#define PMIC_SRAM_BASE_ADDR 0xFFFFF610 +#define PMIC_SRAM_THRM_OFFSET 0x03 +#define IOMAP_SIZE 0x04 + +#define PMICALRT (1 << 3) +#define SYS2ALRT (1 << 2) +#define SYS1ALRT (1 << 1) +#define SYS0ALRT (1 << 0) +#define THERM_EN (1 << 0) +#define THERM_ALRT (1 << 2) + +/* ADC to Temperature conversion table length */ +#define TABLE_LENGTH 34 +#define TEMP_INTERVAL 5 + +/* Default _max 85 C */ +#define DEFAULT_MAX_TEMP 85 + +/* Constants defined in BasinCove PMIC spec */ +#define PMIC_DIE_SENSOR 0 +#define PMIC_DIE_ADC_MIN 395 +#define PMIC_DIE_ADC_MAX 661 +#define PMIC_DIE_TEMP_MIN -40 +#define PMIC_DIE_TEMP_MAX 125 +#define ADC_VAL_27C 503 +#define ADC_COEFFICIENT 619 +#define TEMP_OFFSET 27000 + +/* 'enum' of Thermal sensors */ +enum thermal_sensors { SYS0, SYS1, SYS2, PMIC_DIE, _COUNT }; + +/* ADC channels for the sensors. Order: SYS0 SYS1 SYS2 PMIC_DIE */ +static const int adc_channels[] = { GPADC_SYSTEMP0, GPADC_SYSTEMP1, + GPADC_SYSTEMP2, GPADC_PMICTEMP }; +/* + * Alert registers store the 'alert' temperature for each sensor, + * as 10 bit ADC code. The higher two bits are stored in bits[0:1] of + * alert_regs_h. The lower eight bits are stored in alert_regs_l. + * The hysteresis value is stored in bits[2:6] of alert_regs_h. + * Order: SYS0 SYS1 SYS2 PMIC_DIE + * + * static const int alert_regs_l[] = { 0xB7, 0xB9, 0xBB, 0xC1 }; + */ +static const int alert_regs_h[] = { 0xB6, 0xB8, 0xBA, 0xC0 }; + +/* + * ADC code vs Temperature table + * This table will be different for different thermistors + * Row 0: ADC code + * Row 1: Temperature (in degree celsius) + */ +static const int adc_code[2][TABLE_LENGTH] = { + {952, 932, 906, 877, 843, 804, 761, 714, 665, 614, + 563, 512, 462, 415, 370, 329, 291, 257, 226, 199, + 174, 153, 135, 119, 104, 92, 81, 72, 64, 56, + 50, 45, 40, 36}, + {-40, -35, -30, -25, -20, -15, -10, -5, 0, 5, + 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, + 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, + 110, 115, 120, 125}, + }; + +static DEFINE_MUTEX(thrm_update_lock); + +struct thermal_device_info { + int sensor_index; + /* If 'direct', no table lookup is required */ + bool is_direct; +}; + +struct thermal_data { + struct platform_device *pdev; + struct iio_channel *iio_chan; + struct thermal_zone_device *tzd[PMIC_THERMAL_SENSORS]; + void *thrm_addr; + unsigned int irq; + /* Caching information */ + bool is_initialized; + unsigned long last_updated; + int cached_vals[PMIC_THERMAL_SENSORS]; +}; +static struct thermal_data *tdata; + +static inline int adc_to_pmic_die_temp(unsigned int val) +{ + /* return temperature in mC */ + return (val - ADC_VAL_27C) * ADC_COEFFICIENT + TEMP_OFFSET; +} + +static inline int pmic_die_temp_to_adc(int temp) +{ + /* 'temp' is in C, convert to mC and then do calculations */ + return ((temp * 1000) - TEMP_OFFSET) / ADC_COEFFICIENT + ADC_VAL_27C; +} + +/** + * find_adc_code - searches the ADC code using binary search + * @val: value to find in the array + * + * This function does binary search on an array sorted in 'descending' order + * Can sleep + */ +static int find_adc_code(uint16_t val) +{ + int left = 0; + int right = TABLE_LENGTH - 1; + int mid; + while (left <= right) { + mid = (left + right)/2; + if (val == adc_code[0][mid] || + (mid > 0 && + val > adc_code[0][mid] && val < adc_code[0][mid-1])) + return mid; + else if (val > adc_code[0][mid]) + right = mid - 1; + else if (val < adc_code[0][mid]) + left = mid + 1; + } + return -EINVAL; +} + +/** + * adc_to_temp - converts the ADC code to temperature in mC + * @direct: true if the sensor uses direct conversion + * @adc_val: the ADC code to be converted + * @tp: temperature return value + * + * Can sleep + */ +static int adc_to_temp(int direct, uint16_t adc_val, unsigned long *tp) +{ + int x0, x1, y0, y1; + int nr, dr; /* Numerator & Denominator */ + int indx; + int x = adc_val; + + /* Direct conversion for pmic die temperature */ + if (direct) { + if (adc_val < PMIC_DIE_ADC_MIN || adc_val > PMIC_DIE_ADC_MAX) + return -EINVAL; + + *tp = adc_to_pmic_die_temp(adc_val); + return 0; + } + + indx = find_adc_code(adc_val); + if (indx < 0) + return -EINVAL; + + if (adc_code[0][indx] == adc_val) { + *tp = adc_code[1][indx] * 1000; + return 0; + } + + /* + * The ADC code is in between two values directly defined in the + * table. So, do linear interpolation to calculate the temperature. + */ + x0 = adc_code[0][indx]; + x1 = adc_code[0][indx - 1]; + y0 = adc_code[1][indx]; + y1 = adc_code[1][indx - 1]; + + /* + * Find y: + * Of course, we can avoid these variables, but keep them + * for readability and maintainability. + */ + nr = (x-x0)*y1 + (x1-x)*y0; + dr = x1-x0; + + if (!dr) + return -EINVAL; + /* + * We have to report the temperature in milli degree celsius. + * So, to reduce the loss of precision, do (Nr*1000)/Dr, instead + * of (Nr/Dr)*1000. + */ + *tp = (nr * 1000)/dr; + + return 0; +} + +/** + * temp_to_adc - converts the temperature(in C) to ADC code + * @direct: true if the sensor uses direct conversion + * @temp: the temperature to be converted + * @adc_val: ADC code return value + * + * Can sleep + */ +static int temp_to_adc(int direct, int temp, int *adc_val) +{ + int indx; + int x0, x1, y0, y1; + int nr, dr; /* Numerator & Denominator */ + int x = temp; + + /* Direct conversion for pmic die temperature */ + if (direct) { + if (temp < PMIC_DIE_TEMP_MIN || temp > PMIC_DIE_TEMP_MAX) + return -EINVAL; + + *adc_val = pmic_die_temp_to_adc(temp); + return 0; + } + + if (temp < adc_code[1][0] || temp > adc_code[1][TABLE_LENGTH - 1]) + return -EINVAL; + + + /* Find the 'indx' of this 'temp' in the table */ + indx = (temp - adc_code[1][0]) / TEMP_INTERVAL; + + if (temp == adc_code[1][indx]) { + *adc_val = adc_code[0][indx]; + return 0; + } + + /* + * Temperature is not a multiple of 'TEMP_INTERVAL'. So, + * do linear interpolation to obtain a better ADC code. + */ + x0 = adc_code[1][indx]; + x1 = adc_code[1][indx + 1]; + y0 = adc_code[0][indx]; + y1 = adc_code[0][indx + 1]; + + nr = (x-x0)*y1 + (x1-x)*y0; + dr = x1-x0; + + if (!dr) + return -EINVAL; + + *adc_val = nr/dr; + + return 0; +} + +/** + * set_tmax - sets the given 'adc_val' to the 'alert_reg' + * @alert_reg: register address + * @adc_val: ADC value to be programmed + * + * Not protected. Calling function should handle synchronization. + * Can sleep + */ +static int set_tmax(int alert_reg, int adc_val) +{ + int ret; + + /* Set bits[0:1] of alert_reg_h to bits[8:9] of 'adc_val' */ + ret = intel_scu_ipc_update_register(alert_reg, (adc_val >> 8), 0x03); + if (ret) + return ret; + + /* Extract bits[0:7] of 'adc_val' and write them into alert_reg_l */ + return intel_scu_ipc_iowrite8(alert_reg + 1, adc_val & 0xFF); +} + +/** + * program_tmax - programs a default _max value for each sensor + * @dev: device pointer + * + * Can sleep + */ +static int program_tmax(struct device *dev) +{ + int i, ret; + int pmic_die_val, adc_val; + + ret = temp_to_adc(0, DEFAULT_MAX_TEMP, &adc_val); + if (ret) + return ret; + + ret = temp_to_adc(1, DEFAULT_MAX_TEMP, &pmic_die_val); + if (ret) + return ret; + + for (i = 0; i < PMIC_THERMAL_SENSORS - 1; i++) { + ret = set_tmax(alert_regs_h[i], adc_val); + if (ret) + goto exit_err; + } + + /* Set _max for pmic die sensor */ + ret = set_tmax(alert_regs_h[i], pmic_die_val); + if (ret) + goto exit_err; + + return ret; + +exit_err: + dev_err(dev, "set_tmax for channel %d failed:%d\n", i, ret); + return ret; +} + +static ssize_t store_trip_hyst(struct thermal_zone_device *tzd, + int trip, unsigned long hyst) +{ + int ret; + uint8_t data; + struct thermal_device_info *td_info = tzd->devdata; + int alert_reg = alert_regs_h[td_info->sensor_index]; + + /* Hysteresis value is 5 bits wide */ + if (hyst > 31) + return -EINVAL; + + mutex_lock(&thrm_update_lock); + + ret = intel_scu_ipc_ioread8(alert_reg, &data); + if (ret) + goto ipc_fail; + + /* Set bits [2:6] to value of hyst */ + data = (data & 0x83) | (hyst << 2); + + ret = intel_scu_ipc_iowrite8(alert_reg, data); + +ipc_fail: + mutex_unlock(&thrm_update_lock); + return ret; +} + +static ssize_t show_trip_hyst(struct thermal_zone_device *tzd, + int trip, unsigned long *hyst) +{ + int ret; + uint8_t data; + struct thermal_device_info *td_info = tzd->devdata; + int alert_reg = alert_regs_h[td_info->sensor_index]; + + mutex_lock(&thrm_update_lock); + + ret = intel_scu_ipc_ioread8(alert_reg, &data); + if (!ret) + *hyst = (data >> 2) & 0x1F; /* Extract bits[2:6] of data */ + + mutex_unlock(&thrm_update_lock); + + return ret; +} + +static ssize_t store_trip_temp(struct thermal_zone_device *tzd, + int trip, unsigned long trip_temp) +{ + int ret, adc_val; + struct thermal_device_info *td_info = tzd->devdata; + int alert_reg = alert_regs_h[td_info->sensor_index]; + + if (trip_temp < 1000) { + dev_err(&tzd->device, "Temperature should be in mC\n"); + return -EINVAL; + } + + mutex_lock(&thrm_update_lock); + + /* Convert from mC to C */ + trip_temp /= 1000; + + ret = temp_to_adc(td_info->is_direct, (int)trip_temp, &adc_val); + if (ret) + goto exit; + + ret = set_tmax(alert_reg, adc_val); +exit: + mutex_unlock(&thrm_update_lock); + return ret; +} + +static ssize_t show_trip_temp(struct thermal_zone_device *tzd, + int trip, unsigned long *trip_temp) +{ + int ret, adc_val; + uint8_t l, h; + struct thermal_device_info *td_info = tzd->devdata; + int alert_reg = alert_regs_h[td_info->sensor_index]; + + mutex_lock(&thrm_update_lock); + + ret = intel_scu_ipc_ioread8(alert_reg, &h); + if (ret) + goto exit; + + ret = intel_scu_ipc_ioread8(alert_reg + 1, &l); + if (ret) + goto exit; + + /* Concatenate 'h' and 'l' to get 10-bit ADC code */ + adc_val = ((h & 0x03) << 8) | l; + + ret = adc_to_temp(td_info->is_direct, adc_val, trip_temp); +exit: + mutex_unlock(&thrm_update_lock); + return ret; +} + +static ssize_t show_trip_type(struct thermal_zone_device *tzd, + int trip, enum thermal_trip_type *trip_type) +{ + /* All are passive trip points */ + *trip_type = THERMAL_TRIP_PASSIVE; + + return 0; +} + +static ssize_t show_temp(struct thermal_zone_device *tzd, unsigned long *temp) +{ + int ret; + struct thermal_device_info *td_info = tzd->devdata; + int indx = td_info->sensor_index; + + if (!tdata->iio_chan) + return -EINVAL; + + mutex_lock(&thrm_update_lock); + + if (!tdata->is_initialized || + time_after(jiffies, tdata->last_updated + HZ)) { + ret = iio_st_read_channel_all_raw(tdata->iio_chan, + tdata->cached_vals); + if (ret) { + dev_err(&tzd->device, "ADC sampling failed:%d\n", ret); + goto exit; + } + tdata->last_updated = jiffies; + tdata->is_initialized = true; + } + + ret = adc_to_temp(td_info->is_direct, tdata->cached_vals[indx], temp); +exit: + mutex_unlock(&thrm_update_lock); + return ret; +} + +static int enable_tm(void) +{ + int ret; + uint8_t data; + + mutex_lock(&thrm_update_lock); + + ret = intel_scu_ipc_ioread8(THRMMONCTL, &data); + if (ret) + goto ipc_fail; + + ret = intel_scu_ipc_iowrite8(THRMMONCTL, data | THERM_EN); + +ipc_fail: + mutex_unlock(&thrm_update_lock); + return ret; +} + +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; + + td_info->sensor_index = index; + + if (index == PMIC_DIE_SENSOR) + td_info->is_direct = true; + + return td_info; +} + +static irqreturn_t thermal_intrpt(int irq, void *dev_data) +{ + int ret, sensor, event_type; + uint8_t irq_status; + unsigned int irq_data; + struct thermal_data *tdata = (struct thermal_data *)dev_data; + + if (!tdata) + return IRQ_NONE; + + mutex_lock(&thrm_update_lock); + + irq_data = ioread8(tdata->thrm_addr + PMIC_SRAM_THRM_OFFSET); + + ret = intel_scu_ipc_ioread8(STHRMIRQ, &irq_status); + if (ret) + goto ipc_fail; + + dev_dbg(&tdata->pdev->dev, "STHRMIRQ: %.2x\n", irq_status); + + /* + * -1 for invalid interrupt + * 1 for LOW to HIGH temperature alert + * 0 for HIGH to LOW temperature alert + */ + event_type = -1; + + /* Check which interrupt occured and for what event */ + if (irq_data & PMICALRT) { + event_type = !!(irq_status & PMICALRT); + sensor = PMIC_DIE; + } else if (irq_data & SYS2ALRT) { + event_type = !!(irq_status & SYS2ALRT); + sensor = SYS2; + } else if (irq_data & SYS1ALRT) { + event_type = !!(irq_status & SYS1ALRT); + sensor = SYS1; + } else if (irq_data & SYS0ALRT) { + event_type = !!(irq_status & SYS0ALRT); + sensor = SYS0; + } else { + dev_err(&tdata->pdev->dev, "Invalid Interrupt\n"); + ret = IRQ_HANDLED; + goto ipc_fail; + } + + if (event_type != -1) { + dev_info(&tdata->pdev->dev, + "%s interrupt for thermal sensor %d\n", + event_type ? "HIGH" : "LOW", sensor); + } + + /* Notify using UEvent */ + kobject_uevent(&tdata->pdev->dev.kobj, KOBJ_CHANGE); + + /* Unmask Thermal Interrupt in the mask register */ + ret = intel_scu_ipc_update_register(MIRQLVL1, 0xFF, THERM_ALRT); + if (ret) + goto ipc_fail; + + ret = IRQ_HANDLED; + +ipc_fail: + mutex_unlock(&thrm_update_lock); + return ret; +} + +static struct thermal_zone_device_ops tzd_ops = { + .get_temp = show_temp, + .get_trip_type = show_trip_type, + .get_trip_temp = show_trip_temp, + .set_trip_temp = store_trip_temp, + .get_trip_hyst = show_trip_hyst, + .set_trip_hyst = store_trip_hyst, +}; + +static int mrfl_thermal_probe(struct platform_device *pdev) +{ + int ret, i; + static char *name[PMIC_THERMAL_SENSORS] = { + "PMICDIE", "SYSTHERM0", "SYSTHERM1", "SYSTHERM2" }; + + tdata = kzalloc(sizeof(struct thermal_data), GFP_KERNEL); + if (!tdata) { + dev_err(&pdev->dev, "kzalloc failed\n"); + return -ENOMEM; + } + + tdata->pdev = pdev; + tdata->irq = platform_get_irq(pdev, 0); + platform_set_drvdata(pdev, tdata); + + /* Program a default _max value for each sensor */ + ret = program_tmax(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Programming _max failed:%d\n", ret); + goto exit_free; + } + + /* Register with IIO to sample temperature values */ + tdata->iio_chan = iio_st_channel_get_all("THERMAL"); + if (tdata->iio_chan == NULL) { + dev_err(&pdev->dev, "tdata->iio_chan is null\n"); + ret = -EINVAL; + goto exit_free; + } + + /* Check whether we got all the four channels */ + ret = iio_st_channel_get_num(tdata->iio_chan); + if (ret != PMIC_THERMAL_SENSORS) { + dev_err(&pdev->dev, "incorrect number of channels:%d\n", ret); + ret = -EFAULT; + goto exit_iio; + } + + /* Register each sensor with the generic thermal framework */ + for (i = 0; i < PMIC_THERMAL_SENSORS; i++) { + tdata->tzd[i] = thermal_zone_device_register(name[i], + 1, 1, initialize_sensor(i), &tzd_ops, + 0, 0, 0, 0); + if (IS_ERR(tdata->tzd[i])) { + ret = PTR_ERR(tdata->tzd[i]); + dev_err(&pdev->dev, + "registering thermal sensor %s failed: %d\n", + name[i], ret); + goto exit_reg; + } + } + + tdata->thrm_addr = ioremap_nocache(PMIC_SRAM_BASE_ADDR, IOMAP_SIZE); + if (!tdata->thrm_addr) { + ret = -ENOMEM; + dev_err(&pdev->dev, "ioremap_nocache failed\n"); + goto exit_reg; + } + + /* Register for Interrupt Handler */ + ret = request_threaded_irq(tdata->irq, NULL, thermal_intrpt, + IRQF_TRIGGER_RISING, + DRIVER_NAME, tdata); + if (ret) { + dev_err(&pdev->dev, "request_threaded_irq failed:%d\n", ret); + goto exit_ioremap; + } + + /* Enable Thermal Monitoring */ + ret = enable_tm(); + if (ret) { + dev_err(&pdev->dev, "Enabling TM failed:%d\n", ret); + goto exit_irq; + } + + return 0; + +exit_irq: + free_irq(tdata->irq, tdata); +exit_ioremap: + iounmap(tdata->thrm_addr); +exit_reg: + while (--i >= 0) + thermal_zone_device_unregister(tdata->tzd[i]); +exit_iio: + iio_st_channel_release_all(tdata->iio_chan); +exit_free: + kfree(tdata); + return ret; +} + +static int mrfl_thermal_resume(struct device *dev) +{ + dev_info(dev, "resume called.\n"); + return 0; +} + +static int mrfl_thermal_suspend(struct device *dev) +{ + dev_info(dev, "suspend called.\n"); + return 0; +} + +static int mrfl_thermal_remove(struct platform_device *pdev) +{ + int i; + struct thermal_data *tdata = platform_get_drvdata(pdev); + + if (!tdata) + return 0; + + for (i = 0; i < PMIC_THERMAL_SENSORS; i++) + thermal_zone_device_unregister(tdata->tzd[i]); + + free_irq(tdata->irq, tdata); + iounmap(tdata->thrm_addr); + iio_st_channel_release_all(tdata->iio_chan); + kfree(tdata); + return 0; +} + +/********************************************************************* + * Driver initialization and finalization + *********************************************************************/ + +static const struct dev_pm_ops thermal_pm_ops = { + .suspend = mrfl_thermal_suspend, + .resume = mrfl_thermal_resume, +}; + +static struct platform_driver mrfl_thermal_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &thermal_pm_ops, + }, + .probe = mrfl_thermal_probe, + .remove = mrfl_thermal_remove, +}; + +static int mrfl_thermal_module_init(void) +{ + return platform_driver_register(&mrfl_thermal_driver); +} + +static void mrfl_thermal_module_exit(void) +{ + platform_driver_unregister(&mrfl_thermal_driver); +} + +/* RPMSG related functionality */ +static int mrfl_thermal_rpmsg_probe(struct rpmsg_channel *rpdev) +{ + if (!rpdev) { + pr_err("rpmsg channel not created\n"); + return -ENODEV; + } + + dev_info(&rpdev->dev, "Probed mrfl_thermal rpmsg device\n"); + + return mrfl_thermal_module_init(); +} + +static void mrfl_thermal_rpmsg_remove(struct rpmsg_channel *rpdev) +{ + mrfl_thermal_module_exit(); + dev_info(&rpdev->dev, "Removed mrfl_thermal rpmsg device\n"); +} + +static void mrfl_thermal_rpmsg_cb(struct rpmsg_channel *rpdev, void *data, + int len, void *priv, u32 src) +{ + dev_warn(&rpdev->dev, "unexpected, message\n"); + + print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1, + data, len, true); +} + +static struct rpmsg_device_id mrfl_thermal_id_table[] = { + { .name = "rpmsg_mrfl_thermal" }, + { }, +}; + +MODULE_DEVICE_TABLE(rpmsg, mrfl_thermal_id_table); + +static struct rpmsg_driver mrfl_thermal_rpmsg = { + .drv.name = DRIVER_NAME, + .drv.owner = THIS_MODULE, + .probe = mrfl_thermal_rpmsg_probe, + .callback = mrfl_thermal_rpmsg_cb, + .remove = mrfl_thermal_rpmsg_remove, + .id_table = mrfl_thermal_id_table, +}; + +static int __init mrfl_thermal_rpmsg_init(void) +{ + return register_rpmsg_driver(&mrfl_thermal_rpmsg); +} + +static void __exit mrfl_thermal_rpmsg_exit(void) +{ + return unregister_rpmsg_driver(&mrfl_thermal_rpmsg); +} + +module_init(mrfl_thermal_rpmsg_init); +module_exit(mrfl_thermal_rpmsg_exit); + +MODULE_AUTHOR("Durgadoss R <durgadoss.r@intel.com>"); +MODULE_DESCRIPTION("Intel Merrifield Platform Thermal Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/intel_soc_thermal.c b/drivers/thermal/intel_soc_thermal.c new file mode 100644 index 00000000000..c6809bdc913 --- /dev/null +++ b/drivers/thermal/intel_soc_thermal.c @@ -0,0 +1,482 @@ +/* + * intel_soc_thermal.c - Intel SoC Platform Thermal Driver + * + * Copyright (C) 2012 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: Shravan B M <shravan.k.b.m@intel.com> + * + * This driver registers to Thermal framework as SoC zone. It exposes + * two SoC DTS temperature with aux trip points. Only aux0, aux1 are + * writable. + * + */ + +#define pr_fmt(fmt) "intel_soc_thermal: " fmt + +#include <linux/thermal.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/interrupt.h> + +#include <asm/intel-mid.h> + +#define DRIVER_NAME "soc_thrm" + +/* SOC DTS Registers */ +#define SOC_THERMAL_SENSORS 2 +#define PUNIT_PORT 0x04 +#define DTS_ENABLE_REG 0xB0 +#define PUNIT_TEMP_REG 0xB1 +#define PUNIT_AUX_REG 0xB2 +#define DTS_ENABLE 0x02 +/* There are 4 Aux trips. Only Aux0, Aux1 are writeable */ +#define DTS_TRIP_RW 0x03 +#define SOC_THERMAL_TRIPS 4 + +#define TJMAX_TEMP 90 +#define TJMAX_CODE 0x7F + +/* IRQ details */ +#define SOC_DTS_CONTROL 0x80 +#define TRIP_STATUS_RO 0xB3 +#define TRIP_STATUS_RW 0xB4 +/* TE stands for THERMAL_EVENT */ +#define TE_AUX0 0xB5 +#define TE_AUX1 0xB6 +#define TE_AUX2 0xB7 +#define TE_AUX3 0xB8 +#define ENABLE_AUX_INTRPT 0x0F +#define ENABLE_CPU0 (1 << 16) +#define RTE_ENABLE (1 << 9) +#define AUX0_EVENT (1 << 0) +#define AUX1_EVENT (1 << 1) +#define AUX2_EVENT (1 << 2) +#define AUX3_EVENT (1 << 3) + +struct platform_soc_data { + struct thermal_zone_device *tzd[SOC_THERMAL_SENSORS]; + int irq; +}; + +struct thermal_device_info { + int sensor_index; + struct mutex lock_aux; +}; + +static inline u32 read_soc_reg(unsigned int addr) +{ + return intel_mid_msgbus_read32(PUNIT_PORT, addr); +} + +static inline void write_soc_reg(unsigned int addr, u32 val) +{ + intel_mid_msgbus_write32(PUNIT_PORT, addr, val); +} + +#ifdef CONFIG_DEBUG_FS +struct dts_regs { + char *name; + u32 addr; +} dts_regs[] = { + /* Thermal Management Registers */ + {"PTMC", 0x80}, + {"TRR0", 0x81}, + {"TRR1", 0x82}, + {"TTS", 0x83}, + {"TELB", 0x84}, + {"TELT", 0x85}, + {"GFXT", 0x88}, + {"VEDT", 0x89}, + {"VECT", 0x8A}, + {"VSPT", 0x8B}, + {"ISPT", 0x8C}, + {"SWT", 0x8D}, + /* Trip Event Registers */ + {"DTSC", 0xB0}, + {"TRR", 0xB1}, + {"PTPS", 0xB2}, + {"PTTS", 0xB3}, + {"PTTSS", 0xB4}, + {"TE_AUX0", 0xB5}, + {"TE_AUX1", 0xB6}, + {"TE_AUX2", 0xB7}, + {"TE_AUX3", 0xB8}, + {"TTE_VRIcc", 0xB9}, + {"TTE_VRHOT", 0xBA}, + {"TTE_PROCHOT", 0xBB}, + {"TTE_SLM0", 0xBC}, + {"TTE_SLM1", 0xBD}, + {"BWTE", 0xBE}, + {"TTE_SWT", 0xBF}, + /* MSI Message Registers */ + {"TMA", 0xC0}, + {"TMD", 0xC1}, +}; + +/* /sys/kernel/debug/tng_soc_dts */ +static struct dentry *soc_dts_dent; +static struct dentry *tng_thermal_dir; + +static int soc_dts_debugfs_show(struct seq_file *s, void *unused) +{ + int i; + u32 val; + + for (i = 0; i < ARRAY_SIZE(dts_regs); i++) { + val = read_soc_reg(dts_regs[i].addr); + seq_printf(s, + "%s[0x%X] Val: 0x%X\n", + dts_regs[i].name, dts_regs[i].addr, val); + } + return 0; +} + +static int debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, soc_dts_debugfs_show, NULL); +} + +static const struct file_operations soc_dts_debugfs_fops = { + .open = debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void create_soc_dts_debugfs(void) +{ + int err; + + /* /sys/kernel/debug/tng_thermal/ */ + tng_thermal_dir = debugfs_create_dir("tng_thermal", NULL); + if (IS_ERR(tng_thermal_dir)) { + err = PTR_ERR(tng_thermal_dir); + pr_err("debugfs_create_dir failed:%d\n", err); + return; + } + + /* /sys/kernel/debug/tng_thermal/soc_dts */ + soc_dts_dent = debugfs_create_file("soc_dts", S_IFREG | S_IRUGO, + tng_thermal_dir, NULL, + &soc_dts_debugfs_fops); + if (IS_ERR(soc_dts_dent)) { + err = PTR_ERR(soc_dts_dent); + debugfs_remove_recursive(tng_thermal_dir); + pr_err("debugfs_create_file failed:%d\n", err); + } +} + +static void remove_soc_dts_debugfs(void) +{ + debugfs_remove_recursive(tng_thermal_dir); +} +#else +static inline void create_soc_dts_debugfs(void) { } +static inline void remove_soc_dts_debugfs(void) { } +#endif + +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; + td_info->sensor_index = index; + mutex_init(&td_info->lock_aux); + return td_info; +} + +static void enable_soc_dts(void) +{ + int i; + u32 val; + + /* Enable the DTS */ + write_soc_reg(DTS_ENABLE_REG, DTS_ENABLE); + + val = read_soc_reg(SOC_DTS_CONTROL); + write_soc_reg(SOC_DTS_CONTROL, val | ENABLE_AUX_INTRPT | ENABLE_CPU0); + + /* Enable Interrupts for all the AUX trips for the DTS */ + for (i = 0; i < SOC_THERMAL_TRIPS; i++) { + val = read_soc_reg(TE_AUX0 + i); + write_soc_reg(TE_AUX0 + i, (val | RTE_ENABLE)); + } +} + +static ssize_t show_temp(struct thermal_zone_device *tzd, long *temp) +{ + struct thermal_device_info *td_info = tzd->devdata; + u32 val = read_soc_reg(PUNIT_TEMP_REG); + + /* Extract bits[0:7] or [8:15] using sensor_index */ + *temp = (val >> (8 * td_info->sensor_index)) & 0xFF; + + /* Calibrate the temperature */ + *temp = TJMAX_CODE - *temp + TJMAX_TEMP; + + /* Convert to mC */ + *temp *= 1000; + + return 0; +} + +static ssize_t show_trip_type(struct thermal_zone_device *tzd, + int trip, enum thermal_trip_type *trip_type) +{ + /* All are passive trip points */ + *trip_type = THERMAL_TRIP_PASSIVE; + + return 0; +} + +static ssize_t show_trip_temp(struct thermal_zone_device *tzd, + int trip, long *trip_temp) +{ + u32 aux_value = read_soc_reg(PUNIT_AUX_REG); + + /* aux0 b[0:7], aux1 b[8:15], aux2 b[16:23], aux3 b[24:31] */ + *trip_temp = (aux_value >> (8 * trip)) & 0xFF; + + /* Calibrate the trip point temperature */ + *trip_temp = TJMAX_TEMP - *trip_temp; + + /* Convert to mC and report */ + *trip_temp *= 1000; + + return 0; +} + +static ssize_t store_trip_temp(struct thermal_zone_device *tzd, + int trip, long trip_temp) +{ + u32 aux_trip, aux = 0; + struct thermal_device_info *td_info = tzd->devdata; + + /* Convert from mC to C */ + trip_temp /= 1000; + + /* The trip temp is 8 bits wide (unsigned) */ + if (trip_temp > 255) + return -EINVAL; + + /* Assign last byte to unsigned 32 */ + aux_trip = trip_temp & 0xFF; + + /* Calibrate w.r.t TJMAX_TEMP */ + aux_trip = TJMAX_TEMP - aux_trip; + + mutex_lock(&td_info->lock_aux); + aux = read_soc_reg(PUNIT_AUX_REG); + switch (trip) { + case 0: + /* aux0 bits 0:7 */ + aux = (aux & 0xFFFFFF00) | (aux_trip << (8 * trip)); + break; + case 1: + /* aux1 bits 8:15 */ + aux = (aux & 0xFFFF00FF) | (aux_trip << (8 * trip)); + break; + case 2: + /* aux2 bits 16:23 */ + aux = (aux & 0xFF00FFFF) | (aux_trip << (8 * trip)); + break; + case 3: + /* aux3 bits 24:31 */ + aux = (aux & 0x00FFFFFF) | (aux_trip << (8 * trip)); + break; + } + write_soc_reg(PUNIT_AUX_REG, aux); + + mutex_unlock(&td_info->lock_aux); + + return 0; +} + +static irqreturn_t soc_dts_intrpt(int irq, void *dev_data) +{ + u32 irq_sts; + struct thermal_zone_device *tzd; + char *event; + bool valid = true; + struct platform_soc_data *pdata = (struct platform_soc_data *)dev_data; + + if (!pdata || !pdata->tzd[0]) + return IRQ_NONE; + + tzd = pdata->tzd[0]; + + irq_sts = read_soc_reg(TRIP_STATUS_RW); + + /* The status bit is cleared by writing 1 to the bit */ + if (irq_sts & AUX0_EVENT) { + event = "aux0_event"; + irq_sts |= AUX0_EVENT; + } else if (irq_sts & AUX1_EVENT) { + event = "aux1_event"; + irq_sts |= AUX1_EVENT; + } else if (irq_sts & AUX2_EVENT) { + event = "aux2_event"; + irq_sts |= AUX2_EVENT; + } else if (irq_sts & AUX3_EVENT) { + event = "aux3_event"; + irq_sts |= AUX3_EVENT; + } else { + event = "invalid_event"; + valid = false; + } + + dev_info(&tzd->device, "SoC DTS %s occurred\n", event); + + /* Notify using UEvent, if it is a valid event */ + if (valid) { + kobject_uevent(&tzd->device.kobj, KOBJ_CHANGE); + /* Clear the status bits */ + write_soc_reg(TRIP_STATUS_RW, irq_sts); + } + + return IRQ_HANDLED; +} + +static struct thermal_zone_device_ops tzd_ops = { + .get_temp = show_temp, + .get_trip_type = show_trip_type, + .get_trip_temp = show_trip_temp, + .set_trip_temp = store_trip_temp, +}; + +/********************************************************************* + * Driver initialization and finalization + *********************************************************************/ + +static int soc_thermal_probe(struct platform_device *pdev) +{ + struct platform_soc_data *pdata; + int i, ret; + static char *name[SOC_THERMAL_SENSORS] = {"SoC_DTS0", "SoC_DTS1"}; + + pdata = kzalloc(sizeof(struct platform_soc_data), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + /* Register each sensor with the generic thermal framework */ + for (i = 0; i < SOC_THERMAL_SENSORS; i++) { + pdata->tzd[i] = thermal_zone_device_register(name[i], + 4, DTS_TRIP_RW, initialize_sensor(i), + &tzd_ops, 0, 0, 0, 0); + if (IS_ERR(pdata->tzd[i])) { + ret = PTR_ERR(pdata->tzd[i]); + dev_err(&pdev->dev, "tzd register failed: %d\n", ret); + goto exit_reg; + } + } + + platform_set_drvdata(pdev, pdata); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(&pdev->dev, "platform_get_irq failed:%d\n", ret); + goto exit_reg; + } + + pdata->irq = ret; + + /* Register for Interrupt Handler */ + ret = request_threaded_irq(pdata->irq, NULL, soc_dts_intrpt, + IRQF_TRIGGER_RISING, + DRIVER_NAME, pdata); + if (ret) { + dev_err(&pdev->dev, "request_threaded_irq failed:%d\n", ret); + goto exit_reg; + } + + /* Enable DTS0 and DTS1 */ + enable_soc_dts(); + + create_soc_dts_debugfs(); + + return 0; + +exit_reg: + while (--i >= 0) { + struct thermal_device_info *td_info = pdata->tzd[i]->devdata; + kfree(td_info); + thermal_zone_device_unregister(pdata->tzd[i]); + } + platform_set_drvdata(pdev, NULL); + kfree(pdata); + return ret; +} + +static int soc_thermal_remove(struct platform_device *pdev) +{ + int i; + struct platform_soc_data *pdata = platform_get_drvdata(pdev); + + /* Unregister each sensor with the generic thermal framework */ + for (i = 0; i < SOC_THERMAL_SENSORS; i++) { + struct thermal_device_info *td_info = pdata->tzd[i]->devdata; + kfree(td_info); + thermal_zone_device_unregister(pdata->tzd[i]); + } + platform_set_drvdata(pdev, NULL); + free_irq(pdata->irq, pdata); + kfree(pdata); + + remove_soc_dts_debugfs(); + + return 0; +} + +static const struct platform_device_id therm_id_table[] = { + { DRIVER_NAME, 1}, +}; + +static struct platform_driver soc_thermal_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, + .probe = soc_thermal_probe, + .remove = soc_thermal_remove, + .id_table = therm_id_table, +}; + +static int __init soc_thermal_module_init(void) +{ + return platform_driver_register(&soc_thermal_driver); +} + +static void __exit soc_thermal_module_exit(void) +{ + platform_driver_unregister(&soc_thermal_driver); +} + +module_init(soc_thermal_module_init); +module_exit(soc_thermal_module_exit); + +MODULE_AUTHOR("Shravan B M <shravan.k.b.m@intel.com>"); +MODULE_DESCRIPTION("Intel SoC Thermal Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c new file mode 100644 index 00000000000..c2e32df3b16 --- /dev/null +++ b/drivers/thermal/spear_thermal.c @@ -0,0 +1,206 @@ +/* + * SPEAr thermal driver. + * + * Copyright (C) 2011-2012 ST Microelectronics + * Author: Vincenzo Frascino <vincenzo.frascino@st.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/platform_data/spear_thermal.h> +#include <linux/thermal.h> + +#define MD_FACTOR 1000 + +/* SPEAr Thermal Sensor Dev Structure */ +struct spear_thermal_dev { + /* pointer to base address of the thermal sensor */ + void __iomem *thermal_base; + /* clk structure */ + struct clk *clk; + /* pointer to thermal flags */ + unsigned int flags; +}; + +static inline int thermal_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct spear_thermal_dev *stdev = thermal->devdata; + + /* + * Data are ready to be read after 628 usec from POWERDOWN signal + * (PDN) = 1 + */ + *temp = (readl_relaxed(stdev->thermal_base) & 0x7F) * MD_FACTOR; + return 0; +} + +static struct thermal_zone_device_ops ops = { + .get_temp = thermal_get_temp, +}; + +#ifdef CONFIG_PM +static int spear_thermal_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev); + struct spear_thermal_dev *stdev = spear_thermal->devdata; + unsigned int actual_mask = 0; + + /* Disable SPEAr Thermal Sensor */ + actual_mask = readl_relaxed(stdev->thermal_base); + writel_relaxed(actual_mask & ~stdev->flags, stdev->thermal_base); + + clk_disable(stdev->clk); + dev_info(dev, "Suspended.\n"); + + return 0; +} + +static int spear_thermal_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev); + struct spear_thermal_dev *stdev = spear_thermal->devdata; + unsigned int actual_mask = 0; + int ret = 0; + + ret = clk_enable(stdev->clk); + if (ret) { + dev_err(&pdev->dev, "Can't enable clock\n"); + return ret; + } + + /* Enable SPEAr Thermal Sensor */ + actual_mask = readl_relaxed(stdev->thermal_base); + writel_relaxed(actual_mask | stdev->flags, stdev->thermal_base); + + dev_info(dev, "Resumed.\n"); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(spear_thermal_pm_ops, spear_thermal_suspend, + spear_thermal_resume); + +static int spear_thermal_probe(struct platform_device *pdev) +{ + struct thermal_zone_device *spear_thermal = NULL; + struct spear_thermal_dev *stdev; + struct spear_thermal_pdata *pdata; + int ret = 0; + struct resource *stres = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!stres) { + dev_err(&pdev->dev, "memory resource missing\n"); + return -ENODEV; + } + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "platform data is NULL\n"); + return -EINVAL; + } + + stdev = devm_kzalloc(&pdev->dev, sizeof(*stdev), GFP_KERNEL); + if (!stdev) { + dev_err(&pdev->dev, "kzalloc fail\n"); + return -ENOMEM; + } + + /* Enable thermal sensor */ + stdev->thermal_base = devm_ioremap(&pdev->dev, stres->start, + resource_size(stres)); + if (!stdev->thermal_base) { + dev_err(&pdev->dev, "ioremap failed\n"); + return -ENOMEM; + } + + stdev->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(stdev->clk)) { + dev_err(&pdev->dev, "Can't get clock\n"); + return PTR_ERR(stdev->clk); + } + + ret = clk_enable(stdev->clk); + if (ret) { + dev_err(&pdev->dev, "Can't enable clock\n"); + goto put_clk; + } + + stdev->flags = pdata->thermal_flags; + writel_relaxed(stdev->flags, stdev->thermal_base); + + spear_thermal = thermal_zone_device_register("spear_thermal", 0, + stdev, &ops, 0, 0, 0, 0); + if (IS_ERR(spear_thermal)) { + dev_err(&pdev->dev, "thermal zone device is NULL\n"); + ret = PTR_ERR(spear_thermal); + goto disable_clk; + } + + platform_set_drvdata(pdev, spear_thermal); + + dev_info(&spear_thermal->device, "Thermal Sensor Loaded at: 0x%p.\n", + stdev->thermal_base); + + return 0; + +disable_clk: + clk_disable(stdev->clk); +put_clk: + clk_put(stdev->clk); + + return ret; +} + +static int spear_thermal_exit(struct platform_device *pdev) +{ + unsigned int actual_mask = 0; + struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev); + struct spear_thermal_dev *stdev = spear_thermal->devdata; + + thermal_zone_device_unregister(spear_thermal); + platform_set_drvdata(pdev, NULL); + + /* Disable SPEAr Thermal Sensor */ + actual_mask = readl_relaxed(stdev->thermal_base); + writel_relaxed(actual_mask & ~stdev->flags, stdev->thermal_base); + + clk_disable(stdev->clk); + clk_put(stdev->clk); + + return 0; +} + +static struct platform_driver spear_thermal_driver = { + .probe = spear_thermal_probe, + .remove = spear_thermal_exit, + .driver = { + .name = "spear_thermal", + .owner = THIS_MODULE, + .pm = &spear_thermal_pm_ops, + }, +}; + +module_platform_driver(spear_thermal_driver); + +MODULE_AUTHOR("Vincenzo Frascino <vincenzo.frascino@st.com>"); +MODULE_DESCRIPTION("SPEAr thermal driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/thermal_sys.c b/drivers/thermal/thermal_sys.c new file mode 100644 index 00000000000..db2329e2793 --- /dev/null +++ b/drivers/thermal/thermal_sys.c @@ -0,0 +1,1793 @@ +/* + * thermal.c - Generic Thermal Management Sysfs support. + * + * Copyright (C) 2008 Intel Corp + * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> + * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/kdev_t.h> +#include <linux/idr.h> +#include <linux/thermal.h> +#include <linux/spinlock.h> +#include <linux/reboot.h> +#include <net/netlink.h> +#include <net/genetlink.h> + +MODULE_AUTHOR("Zhang Rui"); +MODULE_DESCRIPTION("Generic thermal management sysfs support"); +MODULE_LICENSE("GPL"); + +struct thermal_cooling_device_instance { + int id; + char name[THERMAL_NAME_LENGTH]; + struct thermal_zone_device *tz; + struct thermal_cooling_device *cdev; + int trip; + char attr_name[THERMAL_NAME_LENGTH]; + struct device_attribute attr; + struct list_head node; +}; + +static DEFINE_IDR(thermal_tz_idr); +static DEFINE_IDR(thermal_cdev_idr); +static DEFINE_MUTEX(thermal_idr_lock); + +static LIST_HEAD(thermal_tz_list); +static LIST_HEAD(thermal_cdev_list); +static DEFINE_MUTEX(thermal_list_lock); + +static int get_idr(struct idr *idr, struct mutex *lock, int *id) +{ + int err; + +again: + if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0)) + return -ENOMEM; + + if (lock) + mutex_lock(lock); + err = idr_get_new(idr, NULL, id); + if (lock) + mutex_unlock(lock); + if (unlikely(err == -EAGAIN)) + goto again; + else if (unlikely(err)) + return err; + + *id = *id & MAX_ID_MASK; + return 0; +} + +static void release_idr(struct idr *idr, struct mutex *lock, int id) +{ + if (lock) + mutex_lock(lock); + idr_remove(idr, id); + if (lock) + mutex_unlock(lock); +} + +/* sys I/F for thermal zone */ + +#define to_thermal_zone(_dev) \ + container_of(_dev, struct thermal_zone_device, device) + +static ssize_t +type_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + + return sprintf(buf, "%s\n", tz->type); +} + +static ssize_t +temp_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + long temperature; + int ret; + + if (!tz->ops->get_temp) + return -EPERM; + + ret = tz->ops->get_temp(tz, &temperature); + + if (ret) + return ret; + + return sprintf(buf, "%ld\n", temperature); +} + +static ssize_t +mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + enum thermal_device_mode mode; + int result; + + if (!tz->ops->get_mode) + return -EPERM; + + result = tz->ops->get_mode(tz, &mode); + if (result) + return result; + + return sprintf(buf, "%s\n", mode == THERMAL_DEVICE_ENABLED ? "enabled" + : "disabled"); +} + +static ssize_t +mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + int result; + + if (!tz->ops->set_mode) + return -EPERM; + + if (!strncmp(buf, "enabled", sizeof("enabled") - 1)) + result = tz->ops->set_mode(tz, THERMAL_DEVICE_ENABLED); + else if (!strncmp(buf, "disabled", sizeof("disabled") - 1)) + result = tz->ops->set_mode(tz, THERMAL_DEVICE_DISABLED); + else + result = -EINVAL; + + if (result) + return result; + + return count; +} + +static ssize_t +trip_point_type_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + enum thermal_trip_type type; + int trip, result; + + if (!tz->ops->get_trip_type) + return -EPERM; + + if (!sscanf(attr->attr.name, "trip_point_%d_type", &trip)) + return -EINVAL; + + result = tz->ops->get_trip_type(tz, trip, &type); + if (result) + return result; + + switch (type) { + case THERMAL_TRIP_CRITICAL: + return sprintf(buf, "critical\n"); + case THERMAL_TRIP_HOT: + return sprintf(buf, "hot\n"); + case THERMAL_TRIP_PASSIVE: + return sprintf(buf, "passive\n"); + case THERMAL_TRIP_ACTIVE: + return sprintf(buf, "active\n"); + default: + return sprintf(buf, "unknown\n"); + } +} + +static ssize_t +trip_point_temp_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + int trip, ret; + long temperature; + + if (!tz->ops->set_trip_temp) + return -EPERM; + + if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip)) + return -EINVAL; + + if (kstrtoul(buf, 10, &temperature)) + return -EINVAL; + + ret = tz->ops->set_trip_temp(tz, trip, temperature); + + return ret ? ret : count; +} + +static ssize_t +trip_point_temp_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + int trip, ret; + long temperature; + + if (!tz->ops->get_trip_temp) + return -EPERM; + + if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip)) + return -EINVAL; + + ret = tz->ops->get_trip_temp(tz, trip, &temperature); + + if (ret) + return ret; + + return sprintf(buf, "%ld\n", temperature); +} + +static ssize_t +trip_point_hyst_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + int trip, ret; + long temperature; + + if (!tz->ops->set_trip_hyst) + return -EPERM; + + if (!sscanf(attr->attr.name, "trip_point_%d_hyst", &trip)) + return -EINVAL; + + if (kstrtoul(buf, 10, &temperature)) + return -EINVAL; + + /* + * We are not doing any check on the 'temperature' value + * here. The driver implementing 'set_trip_hyst' has to + * take care of this. + */ + ret = tz->ops->set_trip_hyst(tz, trip, temperature); + + return ret ? ret : count; +} + +static ssize_t +trip_point_hyst_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + int trip, ret; + long temperature; + + if (!tz->ops->get_trip_hyst) + return -EPERM; + + if (!sscanf(attr->attr.name, "trip_point_%d_hyst", &trip)) + return -EINVAL; + + ret = tz->ops->get_trip_hyst(tz, trip, &temperature); + + return ret ? ret : sprintf(buf, "%ld\n", temperature); +} + +static ssize_t +passive_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + struct thermal_cooling_device *cdev = NULL; + int state; + + if (!sscanf(buf, "%d\n", &state)) + return -EINVAL; + + /* sanity check: values below 1000 millicelcius don't make sense + * and can cause the system to go into a thermal heart attack + */ + if (state && state < 1000) + return -EINVAL; + + if (state && !tz->forced_passive) { + mutex_lock(&thermal_list_lock); + list_for_each_entry(cdev, &thermal_cdev_list, node) { + if (!strncmp("Processor", cdev->type, + sizeof("Processor"))) + thermal_zone_bind_cooling_device(tz, + THERMAL_TRIPS_NONE, + cdev); + } + mutex_unlock(&thermal_list_lock); + if (!tz->passive_delay) + tz->passive_delay = 1000; + } else if (!state && tz->forced_passive) { + mutex_lock(&thermal_list_lock); + list_for_each_entry(cdev, &thermal_cdev_list, node) { + if (!strncmp("Processor", cdev->type, + sizeof("Processor"))) + thermal_zone_unbind_cooling_device(tz, + THERMAL_TRIPS_NONE, + cdev); + } + mutex_unlock(&thermal_list_lock); + tz->passive_delay = 0; + } + + tz->tc1 = 1; + tz->tc2 = 1; + + tz->forced_passive = state; + + thermal_zone_device_update(tz); + + return count; +} + +static ssize_t +passive_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + + return sprintf(buf, "%d\n", tz->forced_passive); +} + +static ssize_t +slope_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long slope; + struct thermal_zone_device *tz = to_thermal_zone(dev); + + if (!tz->ops->set_slope) + return -EPERM; + + if (kstrtol(buf, 10, &slope)) + return -EINVAL; + + ret = tz->ops->set_slope(tz, slope); + if (ret) + return ret; + + return count; +} + +static ssize_t +slope_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + int ret; + long slope; + struct thermal_zone_device *tz = to_thermal_zone(dev); + + if (!tz->ops->get_slope) + return -EINVAL; + + ret = tz->ops->get_slope(tz, &slope); + if (ret) + return ret; + + return sprintf(buf, "%ld\n", slope); +} + +static ssize_t +intercept_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long intercept; + struct thermal_zone_device *tz = to_thermal_zone(dev); + + if (!tz->ops->set_intercept) + return -EPERM; + + if (kstrtol(buf, 10, &intercept)) + return -EINVAL; + + ret = tz->ops->set_intercept(tz, intercept); + if (ret) + return ret; + + return count; +} + +static ssize_t +intercept_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + int ret; + long intercept; + struct thermal_zone_device *tz = to_thermal_zone(dev); + + if (!tz->ops->get_intercept) + return -EINVAL; + + ret = tz->ops->get_intercept(tz, &intercept); + if (ret) + return ret; + + return sprintf(buf, "%ld\n", intercept); +} + +static DEVICE_ATTR(type, 0444, type_show, NULL); +static DEVICE_ATTR(temp, 0444, temp_show, NULL); +static DEVICE_ATTR(mode, 0644, mode_show, mode_store); +static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store); +static DEVICE_ATTR(slope, S_IRUGO | S_IWUSR, slope_show, slope_store); +static DEVICE_ATTR(intercept, S_IRUGO | S_IWUSR, intercept_show, \ + intercept_store); + +/* sys I/F for cooling device */ +#define to_cooling_device(_dev) \ + container_of(_dev, struct thermal_cooling_device, device) + +static ssize_t +thermal_cooling_device_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct thermal_cooling_device *cdev = to_cooling_device(dev); + + return sprintf(buf, "%s\n", cdev->type); +} + +static ssize_t +thermal_cooling_device_max_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct thermal_cooling_device *cdev = to_cooling_device(dev); + unsigned long state; + int ret; + + ret = cdev->ops->get_max_state(cdev, &state); + if (ret) + return ret; + return sprintf(buf, "%ld\n", state); +} + +static ssize_t +thermal_cooling_device_cur_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct thermal_cooling_device *cdev = to_cooling_device(dev); + unsigned long state; + int ret; + + ret = cdev->ops->get_cur_state(cdev, &state); + if (ret) + return ret; + return sprintf(buf, "%ld\n", state); +} + +static ssize_t +thermal_cooling_device_cur_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct thermal_cooling_device *cdev = to_cooling_device(dev); + unsigned long state; + int result; + + if (!sscanf(buf, "%ld\n", &state)) + return -EINVAL; + + if ((long)state < 0) + return -EINVAL; + + result = cdev->ops->set_cur_state(cdev, state); + if (result) + return result; + return count; +} + +static struct device_attribute dev_attr_cdev_type = +__ATTR(type, 0444, thermal_cooling_device_type_show, NULL); +static DEVICE_ATTR(max_state, 0444, + thermal_cooling_device_max_state_show, NULL); +static DEVICE_ATTR(cur_state, 0644, + thermal_cooling_device_cur_state_show, + thermal_cooling_device_cur_state_store); + +static ssize_t +thermal_cooling_device_trip_point_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct thermal_cooling_device_instance *instance; + + instance = + container_of(attr, struct thermal_cooling_device_instance, attr); + + if (instance->trip == THERMAL_TRIPS_NONE) + return sprintf(buf, "-1\n"); + else + return sprintf(buf, "%d\n", instance->trip); +} + +/* Device management */ + +#if defined(CONFIG_THERMAL_HWMON) + +/* hwmon sys I/F */ +#include <linux/hwmon.h> + +/* thermal zone devices with the same type share one hwmon device */ +struct thermal_hwmon_device { + char type[THERMAL_NAME_LENGTH]; + struct device *device; + int count; + struct list_head tz_list; + struct list_head node; +}; + +struct thermal_hwmon_attr { + struct device_attribute attr; + char name[16]; +}; + +/* one temperature input for each thermal zone */ +struct thermal_hwmon_temp { + struct list_head hwmon_node; + struct thermal_zone_device *tz; + struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ + struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ +}; + +static LIST_HEAD(thermal_hwmon_list); + +static ssize_t +name_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", hwmon->type); +} +static DEVICE_ATTR(name, 0444, name_show, NULL); + +static ssize_t +temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + long temperature; + int ret; + struct thermal_hwmon_attr *hwmon_attr + = container_of(attr, struct thermal_hwmon_attr, attr); + struct thermal_hwmon_temp *temp + = container_of(hwmon_attr, struct thermal_hwmon_temp, + temp_input); + struct thermal_zone_device *tz = temp->tz; + + ret = tz->ops->get_temp(tz, &temperature); + + if (ret) + return ret; + + return sprintf(buf, "%ld\n", temperature); +} + +static ssize_t +temp_crit_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct thermal_hwmon_attr *hwmon_attr + = container_of(attr, struct thermal_hwmon_attr, attr); + struct thermal_hwmon_temp *temp + = container_of(hwmon_attr, struct thermal_hwmon_temp, + temp_crit); + struct thermal_zone_device *tz = temp->tz; + long temperature; + int ret; + + ret = tz->ops->get_trip_temp(tz, 0, &temperature); + if (ret) + return ret; + + return sprintf(buf, "%ld\n", temperature); +} + + +static struct thermal_hwmon_device * +thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) +{ + struct thermal_hwmon_device *hwmon; + + mutex_lock(&thermal_list_lock); + list_for_each_entry(hwmon, &thermal_hwmon_list, node) + if (!strcmp(hwmon->type, tz->type)) { + mutex_unlock(&thermal_list_lock); + return hwmon; + } + mutex_unlock(&thermal_list_lock); + + return NULL; +} + +/* Find the temperature input matching a given thermal zone */ +static struct thermal_hwmon_temp * +thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, + const struct thermal_zone_device *tz) +{ + struct thermal_hwmon_temp *temp; + + mutex_lock(&thermal_list_lock); + list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) + if (temp->tz == tz) { + mutex_unlock(&thermal_list_lock); + return temp; + } + mutex_unlock(&thermal_list_lock); + + return NULL; +} + +static int +thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) +{ + struct thermal_hwmon_device *hwmon; + struct thermal_hwmon_temp *temp; + int new_hwmon_device = 1; + int result; + + hwmon = thermal_hwmon_lookup_by_type(tz); + if (hwmon) { + new_hwmon_device = 0; + goto register_sys_interface; + } + + hwmon = kzalloc(sizeof(struct thermal_hwmon_device), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + INIT_LIST_HEAD(&hwmon->tz_list); + strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); + hwmon->device = hwmon_device_register(NULL); + if (IS_ERR(hwmon->device)) { + result = PTR_ERR(hwmon->device); + goto free_mem; + } + dev_set_drvdata(hwmon->device, hwmon); + result = device_create_file(hwmon->device, &dev_attr_name); + if (result) + goto free_mem; + + register_sys_interface: + temp = kzalloc(sizeof(struct thermal_hwmon_temp), GFP_KERNEL); + if (!temp) { + result = -ENOMEM; + goto unregister_name; + } + + temp->tz = tz; + hwmon->count++; + + snprintf(temp->temp_input.name, THERMAL_NAME_LENGTH, + "temp%d_input", hwmon->count); + temp->temp_input.attr.attr.name = temp->temp_input.name; + temp->temp_input.attr.attr.mode = 0444; + temp->temp_input.attr.show = temp_input_show; + sysfs_attr_init(&temp->temp_input.attr.attr); + result = device_create_file(hwmon->device, &temp->temp_input.attr); + if (result) + goto free_temp_mem; + + if (tz->ops->get_crit_temp) { + long temperature; + if (!tz->ops->get_crit_temp(tz, &temperature)) { + snprintf(temp->temp_crit.name, THERMAL_NAME_LENGTH, + "temp%d_crit", hwmon->count); + temp->temp_crit.attr.attr.name = temp->temp_crit.name; + temp->temp_crit.attr.attr.mode = 0444; + temp->temp_crit.attr.show = temp_crit_show; + sysfs_attr_init(&temp->temp_crit.attr.attr); + result = device_create_file(hwmon->device, + &temp->temp_crit.attr); + if (result) + goto unregister_input; + } + } + + mutex_lock(&thermal_list_lock); + if (new_hwmon_device) + list_add_tail(&hwmon->node, &thermal_hwmon_list); + list_add_tail(&temp->hwmon_node, &hwmon->tz_list); + mutex_unlock(&thermal_list_lock); + + return 0; + + unregister_input: + device_remove_file(hwmon->device, &temp->temp_input.attr); + free_temp_mem: + kfree(temp); + unregister_name: + if (new_hwmon_device) { + device_remove_file(hwmon->device, &dev_attr_name); + hwmon_device_unregister(hwmon->device); + } + free_mem: + if (new_hwmon_device) + kfree(hwmon); + + return result; +} + +static void +thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) +{ + struct thermal_hwmon_device *hwmon; + struct thermal_hwmon_temp *temp; + + hwmon = thermal_hwmon_lookup_by_type(tz); + if (unlikely(!hwmon)) { + /* Should never happen... */ + dev_dbg(&tz->device, "hwmon device lookup failed!\n"); + return; + } + + temp = thermal_hwmon_lookup_temp(hwmon, tz); + if (unlikely(!temp)) { + /* Should never happen... */ + dev_dbg(&tz->device, "temperature input lookup failed!\n"); + return; + } + + device_remove_file(hwmon->device, &temp->temp_input.attr); + if (tz->ops->get_crit_temp) + device_remove_file(hwmon->device, &temp->temp_crit.attr); + + mutex_lock(&thermal_list_lock); + list_del(&temp->hwmon_node); + kfree(temp); + if (!list_empty(&hwmon->tz_list)) { + mutex_unlock(&thermal_list_lock); + return; + } + list_del(&hwmon->node); + mutex_unlock(&thermal_list_lock); + + device_remove_file(hwmon->device, &dev_attr_name); + hwmon_device_unregister(hwmon->device); + kfree(hwmon); +} +#else +static int +thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) +{ + return 0; +} + +static void +thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) +{ +} +#endif + +static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, + int delay) +{ + cancel_delayed_work(&(tz->poll_queue)); + + if (!delay) + return; + + if (delay > 1000) + queue_delayed_work(system_freezable_wq, &(tz->poll_queue), + round_jiffies(msecs_to_jiffies(delay))); + else + queue_delayed_work(system_freezable_wq, &(tz->poll_queue), + msecs_to_jiffies(delay)); +} + +static void thermal_zone_device_passive(struct thermal_zone_device *tz, + int temp, int trip_temp, int trip) +{ + int trend = 0; + struct thermal_cooling_device_instance *instance; + struct thermal_cooling_device *cdev; + long state, max_state; + + /* + * Above Trip? + * ----------- + * Calculate the thermal trend (using the passive cooling equation) + * and modify the performance limit for all passive cooling devices + * accordingly. Note that we assume symmetry. + */ + if (temp >= trip_temp) { + tz->passive = true; + + trend = (tz->tc1 * (temp - tz->last_temperature)) + + (tz->tc2 * (temp - trip_temp)); + + /* Heating up? */ + if (trend > 0) { + list_for_each_entry(instance, &tz->cooling_devices, + node) { + if (instance->trip != trip) + continue; + cdev = instance->cdev; + cdev->ops->get_cur_state(cdev, &state); + cdev->ops->get_max_state(cdev, &max_state); + if (state++ < max_state) + cdev->ops->set_cur_state(cdev, state); + } + } else if (trend < 0) { /* Cooling off? */ + list_for_each_entry(instance, &tz->cooling_devices, + node) { + if (instance->trip != trip) + continue; + cdev = instance->cdev; + cdev->ops->get_cur_state(cdev, &state); + cdev->ops->get_max_state(cdev, &max_state); + if (state > 0) + cdev->ops->set_cur_state(cdev, --state); + } + } + return; + } + + /* + * Below Trip? + * ----------- + * Implement passive cooling hysteresis to slowly increase performance + * and avoid thrashing around the passive trip point. Note that we + * assume symmetry. + */ + list_for_each_entry(instance, &tz->cooling_devices, node) { + if (instance->trip != trip) + continue; + cdev = instance->cdev; + cdev->ops->get_cur_state(cdev, &state); + cdev->ops->get_max_state(cdev, &max_state); + if (state > 0) + cdev->ops->set_cur_state(cdev, --state); + if (state == 0) + tz->passive = false; + } +} + +static void thermal_zone_device_check(struct work_struct *work) +{ + struct thermal_zone_device *tz = container_of(work, struct + thermal_zone_device, + poll_queue.work); + thermal_zone_device_update(tz); +} + +/** + * thermal_zone_bind_cooling_device - bind a cooling device to a thermal zone + * @tz: thermal zone device + * @trip: indicates which trip point the cooling devices is + * associated with in this thermal zone. + * @cdev: thermal cooling device + * + * This function is usually called in the thermal zone device .bind callback. + */ +int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, + int trip, + struct thermal_cooling_device *cdev) +{ + struct thermal_cooling_device_instance *dev; + struct thermal_cooling_device_instance *pos; + struct thermal_zone_device *pos1; + struct thermal_cooling_device *pos2; + int result; + + if (trip >= tz->trips || (trip < 0 && trip != THERMAL_TRIPS_NONE)) + return -EINVAL; + + list_for_each_entry(pos1, &thermal_tz_list, node) { + if (pos1 == tz) + break; + } + list_for_each_entry(pos2, &thermal_cdev_list, node) { + if (pos2 == cdev) + break; + } + + if (tz != pos1 || cdev != pos2) + return -EINVAL; + + dev = + kzalloc(sizeof(struct thermal_cooling_device_instance), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->tz = tz; + dev->cdev = cdev; + dev->trip = trip; + result = get_idr(&tz->idr, &tz->lock, &dev->id); + if (result) + goto free_mem; + + sprintf(dev->name, "cdev%d", dev->id); + result = + sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name); + if (result) + goto release_idr; + + snprintf(dev->attr_name, sizeof(dev->attr_name), + "cdev%d_trip_point", dev->id); + sysfs_attr_init(&dev->attr.attr); + dev->attr.attr.name = dev->attr_name; + dev->attr.attr.mode = 0444; + dev->attr.show = thermal_cooling_device_trip_point_show; + result = device_create_file(&tz->device, &dev->attr); + if (result) + goto remove_symbol_link; + + mutex_lock(&tz->lock); + list_for_each_entry(pos, &tz->cooling_devices, node) + if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { + result = -EEXIST; + break; + } + if (!result) + list_add_tail(&dev->node, &tz->cooling_devices); + mutex_unlock(&tz->lock); + + if (!result) + return 0; + + device_remove_file(&tz->device, &dev->attr); +remove_symbol_link: + sysfs_remove_link(&tz->device.kobj, dev->name); +release_idr: + release_idr(&tz->idr, &tz->lock, dev->id); +free_mem: + kfree(dev); + return result; +} +EXPORT_SYMBOL(thermal_zone_bind_cooling_device); + +/** + * thermal_zone_unbind_cooling_device - unbind a cooling device from a thermal zone + * @tz: thermal zone device + * @trip: indicates which trip point the cooling devices is + * associated with in this thermal zone. + * @cdev: thermal cooling device + * + * This function is usually called in the thermal zone device .unbind callback. + */ +int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz, + int trip, + struct thermal_cooling_device *cdev) +{ + struct thermal_cooling_device_instance *pos, *next; + + mutex_lock(&tz->lock); + list_for_each_entry_safe(pos, next, &tz->cooling_devices, node) { + if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { + list_del(&pos->node); + mutex_unlock(&tz->lock); + goto unbind; + } + } + mutex_unlock(&tz->lock); + + return -ENODEV; + +unbind: + device_remove_file(&tz->device, &pos->attr); + sysfs_remove_link(&tz->device.kobj, pos->name); + release_idr(&tz->idr, &tz->lock, pos->id); + kfree(pos); + return 0; +} +EXPORT_SYMBOL(thermal_zone_unbind_cooling_device); + +static void thermal_release(struct device *dev) +{ + struct thermal_zone_device *tz; + struct thermal_cooling_device *cdev; + + if (!strncmp(dev_name(dev), "thermal_zone", + sizeof("thermal_zone") - 1)) { + tz = to_thermal_zone(dev); + kfree(tz); + } else { + cdev = to_cooling_device(dev); + kfree(cdev); + } +} + +static struct class thermal_class = { + .name = "thermal", + .dev_release = thermal_release, +}; +#define THERMAL_WAKE_TIMER +#ifdef THERMAL_WAKE_TIMER +/* default expiry for deferrable timer */ +#define DEFERRABLE_TIMEOUT 29 +static DEFINE_MUTEX(def_timer_lock); +struct deferrable_timer { + struct device dev; + struct work_struct uevent_work; + struct timer_list timer; + struct timer_list wdtimer; + unsigned int timeout; + unsigned int enabled; + int updated_once; +}; + +static struct deferrable_timer *dt; +static void dt_timer(unsigned long data); +/** + * this function is just a no-op. the deferable + * timer should fire when we are here! + */ +static void wd_timer(unsigned long data) +{ + return; +}; +static void dt_uevent_helper(struct work_struct *work); +#define to_dt(_dev) container_of(_dev, struct deferrable_timer, dev) +static int dt_activate_once(struct deferrable_timer *dt) +{ + INIT_WORK((&dt->uevent_work), dt_uevent_helper); + init_timer_deferrable(&dt->timer); + dt->timer.data = (unsigned long) dt; + dt->timer.function = dt_timer; + dt->timer.expires = jiffies + (dt->timeout * HZ); + add_timer(&dt->timer); + + /** + * another normal timer acts as a hard limit backup, + * but this timer func does nothing! the wake was enough + */ + init_timer(&dt->wdtimer); + dt->wdtimer.data = (unsigned long) dt; + dt->wdtimer.function = wd_timer; + dt->wdtimer.expires = jiffies + (2 * dt->timeout * HZ); + add_timer(&dt->wdtimer); + return 0; +} + +static ssize_t show_dt_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct deferrable_timer *dt = to_dt(dev); + return sprintf(buf, "%u\n", dt->enabled); +} +static ssize_t show_dt_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct deferrable_timer *dt = to_dt(dev); + return sprintf(buf, "%u\n", dt->timeout); +} + +static ssize_t set_dt_enable_once(struct device *dev, + struct device_attribute *attr, + char *buf, size_t count) +{ + unsigned int enable; + struct deferrable_timer *dt = to_dt(dev); + + if (kstrtouint(buf, 10, &enable)) + return -EINVAL; + + mutex_lock(&def_timer_lock); + /* one time operation. disallow subsequent writes */ + if (!dt->updated_once) { + dt->enabled = enable; + dt->updated_once = 1; + if (dt->enabled) + dt_activate_once(dt); + } + mutex_unlock(&def_timer_lock); + return count; + +} +static ssize_t set_dt_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct deferrable_timer *dt = to_dt(dev); + int timeout = 0; + + if (kstrtoint(buf, 10, &timeout)) + return -EINVAL; + + if (timeout <= 0) + return count; + + mutex_lock(&def_timer_lock); + dt->timeout = timeout; + mutex_unlock(&def_timer_lock); + return count; + +} +static struct device_attribute dev_attrs[] = { + __ATTR(enable, S_IRUGO | S_IWUSR, show_dt_enabled, set_dt_enable_once), + __ATTR(timeout, S_IRUGO | S_IWUSR, show_dt_timeout, set_dt_timeout) +}; + +static void dt_uevent_helper(struct work_struct *work) +{ + struct deferrable_timer *dt = + container_of(work, struct deferrable_timer, uevent_work); + kobject_uevent(&dt->dev.kobj, KOBJ_CHANGE); +} +static void dt_timer(unsigned long data) +{ + struct deferrable_timer *dt = (struct deferrable_timer *)data; + if (dt->enabled) { + schedule_work(&dt->uevent_work); + mod_timer(&dt->timer, jiffies + (dt->timeout * HZ)); + mod_timer(&dt->wdtimer, jiffies + 2 * dt->timeout * HZ); + } else { + cancel_work_sync(&dt->uevent_work); + } +} +static int dt_init(void) +{ + int ret, ret0, ret1; + dt = kzalloc(sizeof(struct deferrable_timer), GFP_KERNEL); + if (!dt) + return ERR_PTR(-ENOMEM); + dt->dev.class = &thermal_class; + dev_set_name(&dt->dev, "deferrable_timer"); + ret = device_register(&dt->dev); + if (ret) { + kfree(dt); + return ret; + } + ret0 = device_create_file(&dt->dev, &dev_attrs[0]); + if (ret0) + goto exit0; + + ret1 = device_create_file(&dt->dev, &dev_attrs[1]); + if (ret1) + goto exit1; + dt->timeout = DEFERRABLE_TIMEOUT; + return 0; +exit1: + device_remove_file(&dt->dev, &dev_attrs[0]); +exit0: + device_unregister(&dt->dev); + kfree(dt); + return ERR_PTR(ret); +} +#endif + +/** + * thermal_cooling_device_register - register a new thermal cooling device + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + */ +struct thermal_cooling_device * +thermal_cooling_device_register(char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + struct thermal_zone_device *pos; + int result; + + if (!type || strlen(type) >= THERMAL_NAME_LENGTH) + return ERR_PTR(-EINVAL); + + if (!ops || !ops->get_max_state || !ops->get_cur_state || + !ops->set_cur_state) + return ERR_PTR(-EINVAL); + + cdev = kzalloc(sizeof(struct thermal_cooling_device), GFP_KERNEL); + if (!cdev) + return ERR_PTR(-ENOMEM); + + result = get_idr(&thermal_cdev_idr, &thermal_idr_lock, &cdev->id); + if (result) { + kfree(cdev); + return ERR_PTR(result); + } + + strcpy(cdev->type, type); + cdev->ops = ops; + cdev->device.class = &thermal_class; + cdev->devdata = devdata; + dev_set_name(&cdev->device, "cooling_device%d", cdev->id); + result = device_register(&cdev->device); + if (result) { + release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); + kfree(cdev); + return ERR_PTR(result); + } + + /* sys I/F */ + result = device_create_file(&cdev->device, &dev_attr_cdev_type); + if (result) + goto unregister; + + result = device_create_file(&cdev->device, &dev_attr_max_state); + if (result) + goto unregister; + + result = device_create_file(&cdev->device, &dev_attr_cur_state); + if (result) + goto unregister; + + mutex_lock(&thermal_list_lock); + list_add(&cdev->node, &thermal_cdev_list); + list_for_each_entry(pos, &thermal_tz_list, node) { + if (!pos->ops->bind) + continue; + result = pos->ops->bind(pos, cdev); + if (result) + break; + + } + mutex_unlock(&thermal_list_lock); + + if (!result) + return cdev; + +unregister: + release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); + device_unregister(&cdev->device); + return ERR_PTR(result); +} +EXPORT_SYMBOL(thermal_cooling_device_register); + +/** + * thermal_cooling_device_unregister - removes the registered thermal cooling device + * @cdev: the thermal cooling device to remove. + * + * thermal_cooling_device_unregister() must be called when the device is no + * longer needed. + */ +void thermal_cooling_device_unregister(struct + thermal_cooling_device + *cdev) +{ + struct thermal_zone_device *tz; + struct thermal_cooling_device *pos = NULL; + + if (!cdev) + return; + + mutex_lock(&thermal_list_lock); + list_for_each_entry(pos, &thermal_cdev_list, node) + if (pos == cdev) + break; + if (pos != cdev) { + /* thermal cooling device not found */ + mutex_unlock(&thermal_list_lock); + return; + } + list_del(&cdev->node); + list_for_each_entry(tz, &thermal_tz_list, node) { + if (!tz->ops->unbind) + continue; + tz->ops->unbind(tz, cdev); + } + mutex_unlock(&thermal_list_lock); + device_remove_file(&cdev->device, &dev_attr_cdev_type); + device_remove_file(&cdev->device, &dev_attr_max_state); + device_remove_file(&cdev->device, &dev_attr_cur_state); + + release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); + device_unregister(&cdev->device); + return; +} +EXPORT_SYMBOL(thermal_cooling_device_unregister); + +/** + * thermal_zone_device_update - force an update of a thermal zone's state + * @ttz: the thermal zone to update + */ + +void thermal_zone_device_update(struct thermal_zone_device *tz) +{ + int count, ret = 0; + long temp, trip_temp; + enum thermal_trip_type trip_type; + struct thermal_cooling_device_instance *instance; + struct thermal_cooling_device *cdev; + + mutex_lock(&tz->lock); + + if (tz->ops->get_temp(tz, &temp)) { + /* get_temp failed - retry it later */ + pr_warn("failed to read out thermal zone %d\n", tz->id); + goto leave; + } + + for (count = 0; count < tz->trips; count++) { + tz->ops->get_trip_type(tz, count, &trip_type); + tz->ops->get_trip_temp(tz, count, &trip_temp); + + switch (trip_type) { + case THERMAL_TRIP_CRITICAL: + if (temp >= trip_temp) { + if (tz->ops->notify) + ret = tz->ops->notify(tz, count, + trip_type); + if (!ret) { + pr_emerg("Critical temperature reached (%ld C), shutting down\n", + temp/1000); + orderly_poweroff(true); + } + } + break; + case THERMAL_TRIP_HOT: + if (temp >= trip_temp) + if (tz->ops->notify) + tz->ops->notify(tz, count, trip_type); + break; + case THERMAL_TRIP_ACTIVE: + list_for_each_entry(instance, &tz->cooling_devices, + node) { + if (instance->trip != count) + continue; + + cdev = instance->cdev; + + if (temp >= trip_temp) + cdev->ops->set_cur_state(cdev, 1); + else + cdev->ops->set_cur_state(cdev, 0); + } + break; + case THERMAL_TRIP_PASSIVE: + if (temp >= trip_temp || tz->passive) + thermal_zone_device_passive(tz, temp, + trip_temp, count); + break; + } + } + + if (tz->forced_passive) + thermal_zone_device_passive(tz, temp, tz->forced_passive, + THERMAL_TRIPS_NONE); + + tz->last_temperature = temp; + +leave: + if (tz->passive) + thermal_zone_device_set_polling(tz, tz->passive_delay); + else if (tz->polling_delay) + thermal_zone_device_set_polling(tz, tz->polling_delay); + else + thermal_zone_device_set_polling(tz, 0); + mutex_unlock(&tz->lock); +} +EXPORT_SYMBOL(thermal_zone_device_update); + +/** + * create_trip_type_attr - creates a trip point type attribute + * @tz: the thermal zone device + * @indx: index into the trip_type_attrs array + */ +static int create_trip_type_attr(struct thermal_zone_device *tz, int indx) +{ + char *attr_name = kzalloc(THERMAL_NAME_LENGTH, GFP_KERNEL); + if (!attr_name) + return -ENOMEM; + + snprintf(attr_name, THERMAL_NAME_LENGTH, "trip_point_%d_type", indx); + + sysfs_attr_init(&tz->trip_type_attrs[indx].attr); + tz->trip_type_attrs[indx].attr.name = attr_name; + tz->trip_type_attrs[indx].attr.mode = S_IRUGO; + tz->trip_type_attrs[indx].show = trip_point_type_show; + + return device_create_file(&tz->device, &tz->trip_type_attrs[indx]); +} + +/** + * create_trip_temp_attr - creates a trip point temp attribute + * @tz: the thermal zone device + * @indx: index into the trip_type_attrs array + * @writeable: A flag: If 1, 'this' trip point is writeable; otherwise not + */ +static int create_trip_temp_attr(struct thermal_zone_device *tz, + int indx, int writeable) +{ + char *attr_name = kzalloc(THERMAL_NAME_LENGTH, GFP_KERNEL); + if (!attr_name) + return -ENOMEM; + + snprintf(attr_name, THERMAL_NAME_LENGTH, "trip_point_%d_temp", indx); + + sysfs_attr_init(&tz->trip_temp_attrs[indx].attr); + tz->trip_temp_attrs[indx].attr.name = attr_name; + tz->trip_temp_attrs[indx].attr.mode = S_IRUGO; + tz->trip_temp_attrs[indx].show = trip_point_temp_show; + if (writeable) { + tz->trip_temp_attrs[indx].attr.mode |= S_IWUSR; + tz->trip_temp_attrs[indx].store = trip_point_temp_store; + } + + return device_create_file(&tz->device, &tz->trip_temp_attrs[indx]); +} + +/** + * create_trip_hyst_attr - creates hysteresis attribute for a trip point + * @tz: the thermal zone device + * @indx: index into the trip_hyst_attrs array + */ +static int create_trip_hyst_attr(struct thermal_zone_device *tz, int indx) +{ + char *attr_name = kzalloc(THERMAL_NAME_LENGTH, GFP_KERNEL); + if (!attr_name) + return -ENOMEM; + + snprintf(attr_name, THERMAL_NAME_LENGTH, "trip_point_%d_hyst", indx); + + sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr); + tz->trip_hyst_attrs[indx].attr.name = attr_name; + tz->trip_hyst_attrs[indx].attr.mode = S_IRUGO; + tz->trip_hyst_attrs[indx].show = trip_point_hyst_show; + if (tz->ops->set_trip_hyst) { + tz->trip_hyst_attrs[indx].attr.mode |= S_IWUSR; + tz->trip_hyst_attrs[indx].store = trip_point_hyst_store; + } + + return device_create_file(&tz->device, &tz->trip_hyst_attrs[indx]); +} + +/** + * thermal_zone_device_register - register a new thermal zone device + * @type: the thermal zone device type + * @trips: the number of trip points the thermal zone support + * @flag: a bit string indicating the writeablility of trip points + * @devdata: private device data + * @ops: standard thermal zone device callbacks + * @tc1: thermal coefficient 1 for passive calculations + * @tc2: thermal coefficient 2 for passive calculations + * @passive_delay: number of milliseconds to wait between polls when + * performing passive cooling + * @polling_delay: number of milliseconds to wait between polls when checking + * whether trip points have been crossed (0 for interrupt + * driven systems) + * + * thermal_zone_device_unregister() must be called when the device is no + * longer needed. The passive cooling formula uses tc1 and tc2 as described in + * section 11.1.5.1 of the ACPI specification 3.0. + */ +struct thermal_zone_device *thermal_zone_device_register(char *type, + int trips, int flag, void *devdata, + const struct thermal_zone_device_ops *ops, + int tc1, int tc2, int passive_delay, int polling_delay) +{ + struct thermal_zone_device *tz; + struct thermal_cooling_device *pos; + enum thermal_trip_type trip_type; + int result, ret; + int count; + int passive = 0; + + if (!type || strlen(type) >= THERMAL_NAME_LENGTH) + return ERR_PTR(-EINVAL); + + if (trips > THERMAL_MAX_TRIPS || trips < 0) + return ERR_PTR(-EINVAL); + + if (flag >> trips) + return ERR_PTR(-EINVAL); + + if (!ops || !ops->get_temp) + return ERR_PTR(-EINVAL); + + tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL); + if (!tz) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&tz->cooling_devices); + idr_init(&tz->idr); + mutex_init(&tz->lock); + result = get_idr(&thermal_tz_idr, &thermal_idr_lock, &tz->id); + if (result) { + kfree(tz); + return ERR_PTR(result); + } + + strcpy(tz->type, type); + tz->ops = ops; + tz->device.class = &thermal_class; + tz->devdata = devdata; + tz->trips = trips; + tz->tc1 = tc1; + tz->tc2 = tc2; + tz->passive_delay = passive_delay; + tz->polling_delay = polling_delay; + + dev_set_name(&tz->device, "thermal_zone%d", tz->id); + result = device_register(&tz->device); + if (result) { + release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); + kfree(tz); + return ERR_PTR(result); + } + + /* sys I/F */ + result = device_create_file(&tz->device, &dev_attr_type); + if (result) + goto unregister; + + result = device_create_file(&tz->device, &dev_attr_temp); + if (result) + goto unregister; + + if (ops->get_mode) { + result = device_create_file(&tz->device, &dev_attr_mode); + if (result) + goto unregister; + } + + for (count = 0; count < trips; count++) { + result = create_trip_type_attr(tz, count); + if (result) + goto unregister; + result = create_trip_temp_attr(tz, count, + !!(flag & (1 << count))); + if (result) + goto unregister; + + if (tz->ops->get_trip_hyst) { + result = create_trip_hyst_attr(tz, count); + if (result) + goto unregister; + } + + tz->ops->get_trip_type(tz, count, &trip_type); + if (trip_type == THERMAL_TRIP_PASSIVE) + passive = 1; + } + + if (!passive) + result = device_create_file(&tz->device, + &dev_attr_passive); + + if (result) + goto unregister; + + /* Create Sysfs for slope/intercept values */ + if (tz->ops->get_slope) { + result = device_create_file(&tz->device, &dev_attr_slope); + if (result) + goto unregister; + } + + if (tz->ops->get_intercept) { + result = device_create_file(&tz->device, &dev_attr_intercept); + if (result) + goto unregister; + } + + result = thermal_add_hwmon_sysfs(tz); + if (result) + goto unregister; + + mutex_lock(&thermal_list_lock); + list_add_tail(&tz->node, &thermal_tz_list); + if (ops->bind) + list_for_each_entry(pos, &thermal_cdev_list, node) { + result = ops->bind(tz, pos); + if (result) + break; + } + mutex_unlock(&thermal_list_lock); + + INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check); + + thermal_zone_device_update(tz); + + /* start deferrable timer once, if there is one or more tz register*/ +#ifdef THERMAL_WAKE_TIMER + if (!dt) { + ret = dt_init(); + if (ret) + printk(KERN_DEBUG pr_fmt( + "Cant init Thermal deferrable timer: err %d\n"), ret); + } +#endif + if (!result) + return tz; + +unregister: + release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); + device_unregister(&tz->device); + return ERR_PTR(result); +} +EXPORT_SYMBOL(thermal_zone_device_register); + +/** + * thermal_device_unregister - removes the registered thermal zone device + * @tz: the thermal zone device to remove + */ +void thermal_zone_device_unregister(struct thermal_zone_device *tz) +{ + struct thermal_cooling_device *cdev; + struct thermal_zone_device *pos = NULL; + int count; + + if (!tz) + return; + + mutex_lock(&thermal_list_lock); + list_for_each_entry(pos, &thermal_tz_list, node) + if (pos == tz) + break; + if (pos != tz) { + /* thermal zone device not found */ + mutex_unlock(&thermal_list_lock); + return; + } + list_del(&tz->node); + if (tz->ops->unbind) + list_for_each_entry(cdev, &thermal_cdev_list, node) + tz->ops->unbind(tz, cdev); + mutex_unlock(&thermal_list_lock); + + thermal_zone_device_set_polling(tz, 0); + + device_remove_file(&tz->device, &dev_attr_type); + device_remove_file(&tz->device, &dev_attr_temp); + if (tz->ops->get_mode) + device_remove_file(&tz->device, &dev_attr_mode); + + if (tz->ops->get_slope) + device_remove_file(&tz->device, &dev_attr_slope); + + if (tz->ops->get_intercept) + device_remove_file(&tz->device, &dev_attr_intercept); + + for (count = 0; count < tz->trips; count++) { + device_remove_file(&tz->device, &tz->trip_type_attrs[count]); + device_remove_file(&tz->device, &tz->trip_temp_attrs[count]); + if (tz->ops->get_trip_hyst) + device_remove_file(&tz->device, + &tz->trip_hyst_attrs[count]); + } + + thermal_remove_hwmon_sysfs(tz); + release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); + idr_destroy(&tz->idr); + mutex_destroy(&tz->lock); + device_unregister(&tz->device); + return; +} +EXPORT_SYMBOL(thermal_zone_device_unregister); + +#ifdef CONFIG_NET +static struct genl_family thermal_event_genl_family = { + .id = GENL_ID_GENERATE, + .name = THERMAL_GENL_FAMILY_NAME, + .version = THERMAL_GENL_VERSION, + .maxattr = THERMAL_GENL_ATTR_MAX, +}; + +static struct genl_multicast_group thermal_event_mcgrp = { + .name = THERMAL_GENL_MCAST_GROUP_NAME, +}; + +int thermal_generate_netlink_event(u32 orig, enum events event) +{ + struct sk_buff *skb; + struct nlattr *attr; + struct thermal_genl_event *thermal_event; + void *msg_header; + int size; + int result; + static unsigned int thermal_event_seqnum; + + /* allocate memory */ + size = nla_total_size(sizeof(struct thermal_genl_event)) + + nla_total_size(0); + + skb = genlmsg_new(size, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + /* add the genetlink message header */ + msg_header = genlmsg_put(skb, 0, thermal_event_seqnum++, + &thermal_event_genl_family, 0, + THERMAL_GENL_CMD_EVENT); + if (!msg_header) { + nlmsg_free(skb); + return -ENOMEM; + } + + /* fill the data */ + attr = nla_reserve(skb, THERMAL_GENL_ATTR_EVENT, + sizeof(struct thermal_genl_event)); + + if (!attr) { + nlmsg_free(skb); + return -EINVAL; + } + + thermal_event = nla_data(attr); + if (!thermal_event) { + nlmsg_free(skb); + return -EINVAL; + } + + memset(thermal_event, 0, sizeof(struct thermal_genl_event)); + + thermal_event->orig = orig; + thermal_event->event = event; + + /* send multicast genetlink message */ + result = genlmsg_end(skb, msg_header); + if (result < 0) { + nlmsg_free(skb); + return result; + } + + result = genlmsg_multicast(skb, 0, thermal_event_mcgrp.id, GFP_ATOMIC); + if (result) + pr_info("failed to send netlink event:%d\n", result); + + return result; +} +EXPORT_SYMBOL(thermal_generate_netlink_event); + +static int genetlink_init(void) +{ + int result; + + result = genl_register_family(&thermal_event_genl_family); + if (result) + return result; + + result = genl_register_mc_group(&thermal_event_genl_family, + &thermal_event_mcgrp); + if (result) + genl_unregister_family(&thermal_event_genl_family); + return result; +} + +static void genetlink_exit(void) +{ + genl_unregister_family(&thermal_event_genl_family); +} +#else /* !CONFIG_NET */ +static inline int genetlink_init(void) { return 0; } +static inline void genetlink_exit(void) {} +#endif /* !CONFIG_NET */ + +static int __init thermal_init(void) +{ + int result = 0; + + result = class_register(&thermal_class); + if (result) { + idr_destroy(&thermal_tz_idr); + idr_destroy(&thermal_cdev_idr); + mutex_destroy(&thermal_idr_lock); + mutex_destroy(&thermal_list_lock); + } + result = genetlink_init(); + return result; +} + +static void __exit thermal_exit(void) +{ + class_unregister(&thermal_class); + idr_destroy(&thermal_tz_idr); + idr_destroy(&thermal_cdev_idr); + mutex_destroy(&thermal_idr_lock); + mutex_destroy(&thermal_list_lock); + genetlink_exit(); +#ifdef THERMAL_WAKE_TIMER + if (dt) { + del_timer_sync(&dt->timer); + del_timer_sync(&dt->wdtimer); + kfree(dt); + } +#endif +} + +fs_initcall(thermal_init); +module_exit(thermal_exit); |