summaryrefslogtreecommitdiff
path: root/drivers/thermal
diff options
context:
space:
mode:
authorYin Kangkai <kangkai.yin@linux.intel.com>2013-12-26 22:32:00 +0800
committerYin Kangkai <kangkai.yin@linux.intel.com>2013-12-26 22:32:00 +0800
commit47279d7332b3fabb314d305d9a797569f49b85ff (patch)
treeb0d867a7f1013ce879d756bcfa2f027a93bfd2ff /drivers/thermal
downloadkernel-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/Kconfig58
-rw-r--r--drivers/thermal/Makefile8
-rw-r--r--drivers/thermal/intel_mrfl_thermal.c818
-rw-r--r--drivers/thermal/intel_soc_thermal.c482
-rw-r--r--drivers/thermal/spear_thermal.c206
-rw-r--r--drivers/thermal/thermal_sys.c1793
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);