diff options
21 files changed, 2177 insertions, 728 deletions
diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt new file mode 100644 index 00000000000..fca24c931ec --- /dev/null +++ b/Documentation/thermal/cpu-cooling-api.txt @@ -0,0 +1,32 @@ +CPU cooling APIs How To +=================================== + +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org> + +Updated: 12 May 2012 + +Copyright (c) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com) + +0. Introduction + +The generic cpu cooling(freq clipping) provides registration/unregistration APIs +to the caller. The binding of the cooling devices to the trip point is left for +the user. The registration APIs returns the cooling device pointer. + +1. cpu cooling APIs + +1.1 cpufreq registration/unregistration APIs +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register( + struct cpumask *clip_cpus) + + This interface function registers the cpufreq cooling device with the name + "thermal-cpufreq-%x". This api can support multiple instances of cpufreq + cooling devices. + + clip_cpus: cpumask of cpus where the frequency constraints will happen. + +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) + + This interface function unregisters the "thermal-cpufreq-%x" cooling device. + + cdev: Cooling device pointer which has to be unregistered. diff --git a/Documentation/hwmon/exynos4_tmu b/Documentation/thermal/exynos_thermal index c3c6b41db60..2b46f67b1cc 100644 --- a/Documentation/hwmon/exynos4_tmu +++ b/Documentation/thermal/exynos_thermal @@ -46,36 +46,7 @@ The threshold levels are defined as follows: The threshold and each trigger_level are set through the corresponding registers. -When an interrupt occurs, this driver notify user space of -one of four threshold levels for the interrupt -through kobject_uevent_env and sysfs_notify functions. +When an interrupt occurs, this driver notify kernel thermal framework +with the function exynos4_report_trigger. Although an interrupt condition for level_0 can be set, -it is not notified to user space through sysfs_notify function. - -Sysfs Interface ---------------- -name name of the temperature sensor - RO - -temp1_input temperature - RO - -temp1_max temperature for level_1 interrupt - RO - -temp1_crit temperature for level_2 interrupt - RO - -temp1_emergency temperature for level_3 interrupt - RO - -temp1_max_alarm alarm for level_1 interrupt - RO - -temp1_crit_alarm - alarm for level_2 interrupt - RO - -temp1_emergency_alarm - alarm for level_3 interrupt - RO +it can be used to synchronize the cooling action. diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt index c087dbcf353..ca1a1a34970 100644 --- a/Documentation/thermal/sysfs-api.txt +++ b/Documentation/thermal/sysfs-api.txt @@ -84,7 +84,8 @@ temperature) and throttle appropriate devices. 1.3 interface for binding a thermal zone device with a thermal cooling device 1.3.1 int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, - int trip, struct thermal_cooling_device *cdev); + int trip, struct thermal_cooling_device *cdev, + unsigned long upper, unsigned long lower); This interface function bind a thermal cooling device to the certain trip point of a thermal zone device. @@ -93,6 +94,12 @@ temperature) and throttle appropriate devices. cdev: thermal cooling device trip: indicates which trip point the cooling devices is associated with in this thermal zone. + upper:the Maximum cooling state for this trip point. + THERMAL_NO_LIMIT means no upper limit, + and the cooling device can be in max_state. + lower:the Minimum cooling state can be used for this trip point. + THERMAL_NO_LIMIT means no lower limit, + and the cooling device can be in cooling state 0. 1.3.2 int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz, int trip, struct thermal_cooling_device *cdev); diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index edda74a4340..804204d4199 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -708,6 +708,40 @@ static int thermal_get_crit_temp(struct thermal_zone_device *thermal, return -EINVAL; } +static int thermal_get_trend(struct thermal_zone_device *thermal, + int trip, enum thermal_trend *trend) +{ + struct acpi_thermal *tz = thermal->devdata; + enum thermal_trip_type type; + int i; + + if (thermal_get_trip_type(thermal, trip, &type)) + return -EINVAL; + + if (type == THERMAL_TRIP_ACTIVE) { + /* aggressive active cooling */ + *trend = THERMAL_TREND_RAISING; + return 0; + } + + /* + * tz->temperature has already been updated by generic thermal layer, + * before this callback being invoked + */ + i = (tz->trips.passive.tc1 * (tz->temperature - tz->last_temperature)) + + (tz->trips.passive.tc2 + * (tz->temperature - tz->trips.passive.temperature)); + + if (i > 0) + *trend = THERMAL_TREND_RAISING; + else if (i < 0) + *trend = THERMAL_TREND_DROPPING; + else + *trend = THERMAL_TREND_STABLE; + return 0; +} + + static int thermal_notify(struct thermal_zone_device *thermal, int trip, enum thermal_trip_type trip_type) { @@ -731,11 +765,9 @@ static int thermal_notify(struct thermal_zone_device *thermal, int trip, return 0; } -typedef int (*cb)(struct thermal_zone_device *, int, - struct thermal_cooling_device *); static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal, struct thermal_cooling_device *cdev, - cb action) + bool bind) { struct acpi_device *device = cdev->devdata; struct acpi_thermal *tz = thermal->devdata; @@ -759,11 +791,19 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal, i++) { handle = tz->trips.passive.devices.handles[i]; status = acpi_bus_get_device(handle, &dev); - if (ACPI_SUCCESS(status) && (dev == device)) { - result = action(thermal, trip, cdev); - if (result) - goto failed; - } + if (ACPI_FAILURE(status) || dev != device) + continue; + if (bind) + result = + thermal_zone_bind_cooling_device + (thermal, trip, cdev, + THERMAL_NO_LIMIT, THERMAL_NO_LIMIT); + else + result = + thermal_zone_unbind_cooling_device + (thermal, trip, cdev); + if (result) + goto failed; } } @@ -776,11 +816,17 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal, j++) { handle = tz->trips.active[i].devices.handles[j]; status = acpi_bus_get_device(handle, &dev); - if (ACPI_SUCCESS(status) && (dev == device)) { - result = action(thermal, trip, cdev); - if (result) - goto failed; - } + if (ACPI_FAILURE(status) || dev != device) + continue; + if (bind) + result = thermal_zone_bind_cooling_device + (thermal, trip, cdev, + THERMAL_NO_LIMIT, THERMAL_NO_LIMIT); + else + result = thermal_zone_unbind_cooling_device + (thermal, trip, cdev); + if (result) + goto failed; } } @@ -788,7 +834,14 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal, handle = tz->devices.handles[i]; status = acpi_bus_get_device(handle, &dev); if (ACPI_SUCCESS(status) && (dev == device)) { - result = action(thermal, -1, cdev); + if (bind) + result = thermal_zone_bind_cooling_device + (thermal, -1, cdev, + THERMAL_NO_LIMIT, + THERMAL_NO_LIMIT); + else + result = thermal_zone_unbind_cooling_device + (thermal, -1, cdev); if (result) goto failed; } @@ -802,16 +855,14 @@ static int acpi_thermal_bind_cooling_device(struct thermal_zone_device *thermal, struct thermal_cooling_device *cdev) { - return acpi_thermal_cooling_device_cb(thermal, cdev, - thermal_zone_bind_cooling_device); + return acpi_thermal_cooling_device_cb(thermal, cdev, true); } static int acpi_thermal_unbind_cooling_device(struct thermal_zone_device *thermal, struct thermal_cooling_device *cdev) { - return acpi_thermal_cooling_device_cb(thermal, cdev, - thermal_zone_unbind_cooling_device); + return acpi_thermal_cooling_device_cb(thermal, cdev, false); } static const struct thermal_zone_device_ops acpi_thermal_zone_ops = { @@ -823,6 +874,7 @@ static const struct thermal_zone_device_ops acpi_thermal_zone_ops = { .get_trip_type = thermal_get_trip_type, .get_trip_temp = thermal_get_trip_temp, .get_crit_temp = thermal_get_crit_temp, + .get_trend = thermal_get_trend, .notify = thermal_notify, }; @@ -849,15 +901,12 @@ static int acpi_thermal_register_thermal_zone(struct acpi_thermal *tz) tz->thermal_zone = thermal_zone_device_register("acpitz", trips, 0, tz, &acpi_thermal_zone_ops, - tz->trips.passive.tc1, - tz->trips.passive.tc2, tz->trips.passive.tsp*100, tz->polling_frequency*100); else tz->thermal_zone = thermal_zone_device_register("acpitz", trips, 0, tz, - &acpi_thermal_zone_ops, - 0, 0, 0, + &acpi_thermal_zone_ops, 0, tz->polling_frequency*100); if (IS_ERR(tz->thermal_zone)) return -ENODEV; diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c74e73b2069..c4633de6446 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -334,16 +334,6 @@ config SENSORS_DA9052_ADC This driver can also be built as module. If so, the module will be called da9052-hwmon. -config SENSORS_EXYNOS4_TMU - tristate "Temperature sensor on Samsung EXYNOS4" - depends on ARCH_EXYNOS4 - help - If you say yes here you get support for TMU (Thermal Management - Unit) on SAMSUNG EXYNOS4 series of SoC. - - This driver can also be built as a module. If so, the module - will be called exynos4-tmu. - config SENSORS_I5K_AMB tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets" depends on PCI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index a62ce17ddbf..8d5fcb5e8e9 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -50,7 +50,6 @@ obj-$(CONFIG_SENSORS_DS1621) += ds1621.o obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o -obj-$(CONFIG_SENSORS_EXYNOS4_TMU) += exynos4_tmu.o obj-$(CONFIG_SENSORS_F71805F) += f71805f.o obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o obj-$(CONFIG_SENSORS_F75375S) += f75375s.o diff --git a/drivers/hwmon/exynos4_tmu.c b/drivers/hwmon/exynos4_tmu.c deleted file mode 100644 index e912059140c..00000000000 --- a/drivers/hwmon/exynos4_tmu.c +++ /dev/null @@ -1,518 +0,0 @@ -/* - * exynos4_tmu.c - Samsung EXYNOS4 TMU (Thermal Management Unit) - * - * Copyright (C) 2011 Samsung Electronics - * Donggeun Kim <dg77.kim@samsung.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; either version 2 of the License, or - * (at your option) any later version. - * - * 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 - * - */ - -#include <linux/module.h> -#include <linux/err.h> -#include <linux/kernel.h> -#include <linux/slab.h> -#include <linux/platform_device.h> -#include <linux/interrupt.h> -#include <linux/clk.h> -#include <linux/workqueue.h> -#include <linux/sysfs.h> -#include <linux/kobject.h> -#include <linux/io.h> -#include <linux/mutex.h> - -#include <linux/hwmon.h> -#include <linux/hwmon-sysfs.h> - -#include <linux/platform_data/exynos4_tmu.h> - -#define EXYNOS4_TMU_REG_TRIMINFO 0x0 -#define EXYNOS4_TMU_REG_CONTROL 0x20 -#define EXYNOS4_TMU_REG_STATUS 0x28 -#define EXYNOS4_TMU_REG_CURRENT_TEMP 0x40 -#define EXYNOS4_TMU_REG_THRESHOLD_TEMP 0x44 -#define EXYNOS4_TMU_REG_TRIG_LEVEL0 0x50 -#define EXYNOS4_TMU_REG_TRIG_LEVEL1 0x54 -#define EXYNOS4_TMU_REG_TRIG_LEVEL2 0x58 -#define EXYNOS4_TMU_REG_TRIG_LEVEL3 0x5C -#define EXYNOS4_TMU_REG_PAST_TEMP0 0x60 -#define EXYNOS4_TMU_REG_PAST_TEMP1 0x64 -#define EXYNOS4_TMU_REG_PAST_TEMP2 0x68 -#define EXYNOS4_TMU_REG_PAST_TEMP3 0x6C -#define EXYNOS4_TMU_REG_INTEN 0x70 -#define EXYNOS4_TMU_REG_INTSTAT 0x74 -#define EXYNOS4_TMU_REG_INTCLEAR 0x78 - -#define EXYNOS4_TMU_GAIN_SHIFT 8 -#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT 24 - -#define EXYNOS4_TMU_TRIM_TEMP_MASK 0xff -#define EXYNOS4_TMU_CORE_ON 3 -#define EXYNOS4_TMU_CORE_OFF 2 -#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET 50 -#define EXYNOS4_TMU_TRIG_LEVEL0_MASK 0x1 -#define EXYNOS4_TMU_TRIG_LEVEL1_MASK 0x10 -#define EXYNOS4_TMU_TRIG_LEVEL2_MASK 0x100 -#define EXYNOS4_TMU_TRIG_LEVEL3_MASK 0x1000 -#define EXYNOS4_TMU_INTCLEAR_VAL 0x1111 - -struct exynos4_tmu_data { - struct exynos4_tmu_platform_data *pdata; - struct device *hwmon_dev; - struct resource *mem; - void __iomem *base; - int irq; - struct work_struct irq_work; - struct mutex lock; - struct clk *clk; - u8 temp_error1, temp_error2; -}; - -/* - * TMU treats temperature as a mapped temperature code. - * The temperature is converted differently depending on the calibration type. - */ -static int temp_to_code(struct exynos4_tmu_data *data, u8 temp) -{ - struct exynos4_tmu_platform_data *pdata = data->pdata; - int temp_code; - - /* temp should range between 25 and 125 */ - if (temp < 25 || temp > 125) { - temp_code = -EINVAL; - goto out; - } - - switch (pdata->cal_type) { - case TYPE_TWO_POINT_TRIMMING: - temp_code = (temp - 25) * - (data->temp_error2 - data->temp_error1) / - (85 - 25) + data->temp_error1; - break; - case TYPE_ONE_POINT_TRIMMING: - temp_code = temp + data->temp_error1 - 25; - break; - default: - temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET; - break; - } -out: - return temp_code; -} - -/* - * Calculate a temperature value from a temperature code. - * The unit of the temperature is degree Celsius. - */ -static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code) -{ - struct exynos4_tmu_platform_data *pdata = data->pdata; - int temp; - - /* temp_code should range between 75 and 175 */ - if (temp_code < 75 || temp_code > 175) { - temp = -ENODATA; - goto out; - } - - switch (pdata->cal_type) { - case TYPE_TWO_POINT_TRIMMING: - temp = (temp_code - data->temp_error1) * (85 - 25) / - (data->temp_error2 - data->temp_error1) + 25; - break; - case TYPE_ONE_POINT_TRIMMING: - temp = temp_code - data->temp_error1 + 25; - break; - default: - temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET; - break; - } -out: - return temp; -} - -static int exynos4_tmu_initialize(struct platform_device *pdev) -{ - struct exynos4_tmu_data *data = platform_get_drvdata(pdev); - struct exynos4_tmu_platform_data *pdata = data->pdata; - unsigned int status, trim_info; - int ret = 0, threshold_code; - - mutex_lock(&data->lock); - clk_enable(data->clk); - - status = readb(data->base + EXYNOS4_TMU_REG_STATUS); - if (!status) { - ret = -EBUSY; - goto out; - } - - /* Save trimming info in order to perform calibration */ - trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO); - data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK; - data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK); - - /* Write temperature code for threshold */ - threshold_code = temp_to_code(data, pdata->threshold); - if (threshold_code < 0) { - ret = threshold_code; - goto out; - } - writeb(threshold_code, - data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP); - - writeb(pdata->trigger_levels[0], - data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0); - writeb(pdata->trigger_levels[1], - data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1); - writeb(pdata->trigger_levels[2], - data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2); - writeb(pdata->trigger_levels[3], - data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3); - - writel(EXYNOS4_TMU_INTCLEAR_VAL, - data->base + EXYNOS4_TMU_REG_INTCLEAR); -out: - clk_disable(data->clk); - mutex_unlock(&data->lock); - - return ret; -} - -static void exynos4_tmu_control(struct platform_device *pdev, bool on) -{ - struct exynos4_tmu_data *data = platform_get_drvdata(pdev); - struct exynos4_tmu_platform_data *pdata = data->pdata; - unsigned int con, interrupt_en; - - mutex_lock(&data->lock); - clk_enable(data->clk); - - con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT | - pdata->gain << EXYNOS4_TMU_GAIN_SHIFT; - if (on) { - con |= EXYNOS4_TMU_CORE_ON; - interrupt_en = pdata->trigger_level3_en << 12 | - pdata->trigger_level2_en << 8 | - pdata->trigger_level1_en << 4 | - pdata->trigger_level0_en; - } else { - con |= EXYNOS4_TMU_CORE_OFF; - interrupt_en = 0; /* Disable all interrupts */ - } - writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN); - writel(con, data->base + EXYNOS4_TMU_REG_CONTROL); - - clk_disable(data->clk); - mutex_unlock(&data->lock); -} - -static int exynos4_tmu_read(struct exynos4_tmu_data *data) -{ - u8 temp_code; - int temp; - - mutex_lock(&data->lock); - clk_enable(data->clk); - - temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP); - temp = code_to_temp(data, temp_code); - - clk_disable(data->clk); - mutex_unlock(&data->lock); - - return temp; -} - -static void exynos4_tmu_work(struct work_struct *work) -{ - struct exynos4_tmu_data *data = container_of(work, - struct exynos4_tmu_data, irq_work); - - mutex_lock(&data->lock); - clk_enable(data->clk); - - writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR); - - kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE); - - enable_irq(data->irq); - - clk_disable(data->clk); - mutex_unlock(&data->lock); -} - -static irqreturn_t exynos4_tmu_irq(int irq, void *id) -{ - struct exynos4_tmu_data *data = id; - - disable_irq_nosync(irq); - schedule_work(&data->irq_work); - - return IRQ_HANDLED; -} - -static ssize_t exynos4_tmu_show_name(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return sprintf(buf, "exynos4-tmu\n"); -} - -static ssize_t exynos4_tmu_show_temp(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct exynos4_tmu_data *data = dev_get_drvdata(dev); - int ret; - - ret = exynos4_tmu_read(data); - if (ret < 0) - return ret; - - /* convert from degree Celsius to millidegree Celsius */ - return sprintf(buf, "%d\n", ret * 1000); -} - -static ssize_t exynos4_tmu_show_alarm(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct exynos4_tmu_data *data = dev_get_drvdata(dev); - struct exynos4_tmu_platform_data *pdata = data->pdata; - int temp; - unsigned int trigger_level; - - temp = exynos4_tmu_read(data); - if (temp < 0) - return temp; - - trigger_level = pdata->threshold + pdata->trigger_levels[attr->index]; - - return sprintf(buf, "%d\n", !!(temp > trigger_level)); -} - -static ssize_t exynos4_tmu_show_level(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct exynos4_tmu_data *data = dev_get_drvdata(dev); - struct exynos4_tmu_platform_data *pdata = data->pdata; - unsigned int temp = pdata->threshold + - pdata->trigger_levels[attr->index]; - - return sprintf(buf, "%u\n", temp * 1000); -} - -static DEVICE_ATTR(name, S_IRUGO, exynos4_tmu_show_name, NULL); -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, exynos4_tmu_show_temp, NULL, 0); - -static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, - exynos4_tmu_show_alarm, NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, - exynos4_tmu_show_alarm, NULL, 2); -static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO, - exynos4_tmu_show_alarm, NULL, 3); - -static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, exynos4_tmu_show_level, NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, exynos4_tmu_show_level, NULL, 2); -static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO, - exynos4_tmu_show_level, NULL, 3); - -static struct attribute *exynos4_tmu_attributes[] = { - &dev_attr_name.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp1_emergency.dev_attr.attr, - NULL, -}; - -static const struct attribute_group exynos4_tmu_attr_group = { - .attrs = exynos4_tmu_attributes, -}; - -static int __devinit exynos4_tmu_probe(struct platform_device *pdev) -{ - struct exynos4_tmu_data *data; - struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data; - int ret; - - if (!pdata) { - dev_err(&pdev->dev, "No platform init data supplied.\n"); - return -ENODEV; - } - - data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL); - if (!data) { - dev_err(&pdev->dev, "Failed to allocate driver structure\n"); - return -ENOMEM; - } - - data->irq = platform_get_irq(pdev, 0); - if (data->irq < 0) { - ret = data->irq; - dev_err(&pdev->dev, "Failed to get platform irq\n"); - goto err_free; - } - - INIT_WORK(&data->irq_work, exynos4_tmu_work); - - data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!data->mem) { - ret = -ENOENT; - dev_err(&pdev->dev, "Failed to get platform resource\n"); - goto err_free; - } - - data->mem = request_mem_region(data->mem->start, - resource_size(data->mem), pdev->name); - if (!data->mem) { - ret = -ENODEV; - dev_err(&pdev->dev, "Failed to request memory region\n"); - goto err_free; - } - - data->base = ioremap(data->mem->start, resource_size(data->mem)); - if (!data->base) { - ret = -ENODEV; - dev_err(&pdev->dev, "Failed to ioremap memory\n"); - goto err_mem_region; - } - - ret = request_irq(data->irq, exynos4_tmu_irq, - IRQF_TRIGGER_RISING, - "exynos4-tmu", data); - if (ret) { - dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); - goto err_io_remap; - } - - data->clk = clk_get(NULL, "tmu_apbif"); - if (IS_ERR(data->clk)) { - ret = PTR_ERR(data->clk); - dev_err(&pdev->dev, "Failed to get clock\n"); - goto err_irq; - } - - data->pdata = pdata; - platform_set_drvdata(pdev, data); - mutex_init(&data->lock); - - ret = exynos4_tmu_initialize(pdev); - if (ret) { - dev_err(&pdev->dev, "Failed to initialize TMU\n"); - goto err_clk; - } - - ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group); - if (ret) { - dev_err(&pdev->dev, "Failed to create sysfs group\n"); - goto err_clk; - } - - data->hwmon_dev = hwmon_device_register(&pdev->dev); - if (IS_ERR(data->hwmon_dev)) { - ret = PTR_ERR(data->hwmon_dev); - dev_err(&pdev->dev, "Failed to register hwmon device\n"); - goto err_create_group; - } - - exynos4_tmu_control(pdev, true); - - return 0; - -err_create_group: - sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group); -err_clk: - platform_set_drvdata(pdev, NULL); - clk_put(data->clk); -err_irq: - free_irq(data->irq, data); -err_io_remap: - iounmap(data->base); -err_mem_region: - release_mem_region(data->mem->start, resource_size(data->mem)); -err_free: - kfree(data); - - return ret; -} - -static int __devexit exynos4_tmu_remove(struct platform_device *pdev) -{ - struct exynos4_tmu_data *data = platform_get_drvdata(pdev); - - exynos4_tmu_control(pdev, false); - - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group); - - clk_put(data->clk); - - free_irq(data->irq, data); - - iounmap(data->base); - release_mem_region(data->mem->start, resource_size(data->mem)); - - platform_set_drvdata(pdev, NULL); - - kfree(data); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int exynos4_tmu_suspend(struct device *dev) -{ - exynos4_tmu_control(to_platform_device(dev), false); - - return 0; -} - -static int exynos4_tmu_resume(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - - exynos4_tmu_initialize(pdev); - exynos4_tmu_control(pdev, true); - - return 0; -} - -static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm, - exynos4_tmu_suspend, exynos4_tmu_resume); -#define EXYNOS4_TMU_PM &exynos4_tmu_pm -#else -#define EXYNOS4_TMU_PM NULL -#endif - -static struct platform_driver exynos4_tmu_driver = { - .driver = { - .name = "exynos4-tmu", - .owner = THIS_MODULE, - .pm = EXYNOS4_TMU_PM, - }, - .probe = exynos4_tmu_probe, - .remove = __devexit_p(exynos4_tmu_remove), -}; - -module_platform_driver(exynos4_tmu_driver); - -MODULE_DESCRIPTION("EXYNOS4 TMU Driver"); -MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:exynos4-tmu"); diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index 39abb150bdd..84c56881ba8 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -329,7 +329,8 @@ static int acerhdf_bind(struct thermal_zone_device *thermal, if (cdev != cl_dev) return 0; - if (thermal_zone_bind_cooling_device(thermal, 0, cdev)) { + if (thermal_zone_bind_cooling_device(thermal, 0, cdev, + THERMAL_NO_LIMIT, THERMAL_NO_LIMIT)) { pr_err("error binding cooling dev\n"); return -EINVAL; } @@ -661,7 +662,7 @@ static int acerhdf_register_thermal(void) return -EINVAL; thz_dev = thermal_zone_device_register("acerhdf", 1, 0, NULL, - &acerhdf_dev_ops, 0, 0, 0, + &acerhdf_dev_ops, 0, (kernelmode) ? interval*1000 : 0); if (IS_ERR(thz_dev)) return -EINVAL; diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c index 3a27113deda..c8097616dd6 100644 --- a/drivers/platform/x86/intel_mid_thermal.c +++ b/drivers/platform/x86/intel_mid_thermal.c @@ -502,7 +502,7 @@ static int mid_thermal_probe(struct platform_device *pdev) goto err; } pinfo->tzd[i] = thermal_zone_device_register(name[i], - 0, 0, td_info, &tzd_ops, 0, 0, 0, 0); + 0, 0, td_info, &tzd_ops, 0, 0); if (IS_ERR(pinfo->tzd[i])) { kfree(td_info); ret = PTR_ERR(pinfo->tzd[i]); diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 08cc8a3c15a..2436f135001 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -201,7 +201,7 @@ static int psy_register_thermal(struct power_supply *psy) for (i = 0; i < psy->num_properties; i++) { if (psy->properties[i] == POWER_SUPPLY_PROP_TEMP) { psy->tzd = thermal_zone_device_register(psy->name, 0, 0, - psy, &psy_tzd_ops, 0, 0, 0, 0); + psy, &psy_tzd_ops, 0, 0); if (IS_ERR(psy->tzd)) return PTR_ERR(psy->tzd); break; diff --git a/drivers/staging/omap-thermal/omap-thermal-common.c b/drivers/staging/omap-thermal/omap-thermal-common.c index 46ee0a9f49d..5c0c203b887 100644 --- a/drivers/staging/omap-thermal/omap-thermal-common.c +++ b/drivers/staging/omap-thermal/omap-thermal-common.c @@ -126,7 +126,9 @@ static int omap_thermal_bind(struct thermal_zone_device *thermal, /* TODO: bind with min and max states */ /* Simple thing, two trips, one passive another critical */ - return thermal_zone_bind_cooling_device(thermal, 0, cdev); + return thermal_zone_bind_cooling_device(thermal, 0, cdev, + THERMAL_NO_LIMIT, + THERMAL_NO_LIMIT); } /* Unbind callback functions for thermal zone */ @@ -268,7 +270,6 @@ int omap_thermal_expose_sensor(struct omap_bandgap *bg_ptr, int id, /* Create thermal zone */ data->omap_thermal = thermal_zone_device_register(domain, OMAP_TRIP_NUMBER, 0, data, &omap_thermal_ops, - 1, 2, /*TODO: remove this when FW allows */ FAST_TEMP_MONITORING_RATE, FAST_TEMP_MONITORING_RATE); if (IS_ERR_OR_NULL(data->omap_thermal)) { diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 3ab2bd540b5..edfd67d2501 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -19,6 +19,17 @@ config THERMAL_HWMON depends on HWMON=y || HWMON=THERMAL default y +config CPU_THERMAL + bool "generic cpu cooling support" + depends on THERMAL && CPU_FREQ + help + This implements the generic cpu cooling mechanism through frequency + reduction, cpu hotplug and any other ways of reducing temperature. An + ACPI version of this already exists(drivers/acpi/processor_thermal.c). + This will be useful for platforms using the generic thermal interface + and not the ACPI interface. + If you want this support, you should say Y here. + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on THERMAL @@ -27,3 +38,18 @@ config SPEAR_THERMAL help Enable this to plug the SPEAr thermal sensor driver into the Linux thermal framework + +config RCAR_THERMAL + tristate "Renesas R-Car thermal driver" + depends on THERMAL + depends on ARCH_SHMOBILE + help + Enable this to plug the R-Car thermal sensor driver into the Linux + thermal framework + +config EXYNOS_THERMAL + tristate "Temperature sensor on Samsung EXYNOS" + depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) && THERMAL + help + If you say yes here you get support for TMU (Thermal Managment + Unit) on SAMSUNG EXYNOS series of SoC. diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index a9fff0bf4b1..885550dc64b 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -3,4 +3,7 @@ # obj-$(CONFIG_THERMAL) += thermal_sys.o -obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
\ No newline at end of file +obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o +obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o +obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o +obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c new file mode 100644 index 00000000000..cc1c930a90e --- /dev/null +++ b/drivers/thermal/cpu_cooling.c @@ -0,0 +1,449 @@ +/* + * linux/drivers/thermal/cpu_cooling.c + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com) + * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/thermal.h> +#include <linux/platform_device.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/cpu.h> +#include <linux/cpu_cooling.h> + +/** + * struct cpufreq_cooling_device + * @id: unique integer value corresponding to each cpufreq_cooling_device + * registered. + * @cool_dev: thermal_cooling_device pointer to keep track of the the + * egistered cooling device. + * @cpufreq_state: integer value representing the current state of cpufreq + * cooling devices. + * @cpufreq_val: integer value representing the absolute value of the clipped + * frequency. + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device. + * @node: list_head to link all cpufreq_cooling_device together. + * + * This structure is required for keeping information of each + * cpufreq_cooling_device registered as a list whose head is represented by + * cooling_cpufreq_list. In order to prevent corruption of this list a + * mutex lock cooling_cpufreq_lock is used. + */ +struct cpufreq_cooling_device { + int id; + struct thermal_cooling_device *cool_dev; + unsigned int cpufreq_state; + unsigned int cpufreq_val; + struct cpumask allowed_cpus; + struct list_head node; +}; +static LIST_HEAD(cooling_cpufreq_list); +static DEFINE_IDR(cpufreq_idr); + +static struct mutex cooling_cpufreq_lock; + +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */ +#define NOTIFY_INVALID NULL +struct cpufreq_cooling_device *notify_device; + +/** + * get_idr - function to get a unique id. + * @idr: struct idr * handle used to create a id. + * @id: int * value generated by this function. + */ +static int get_idr(struct idr *idr, int *id) +{ + int err; +again: + if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0)) + return -ENOMEM; + + mutex_lock(&cooling_cpufreq_lock); + err = idr_get_new(idr, NULL, id); + mutex_unlock(&cooling_cpufreq_lock); + + if (unlikely(err == -EAGAIN)) + goto again; + else if (unlikely(err)) + return err; + + *id = *id & MAX_IDR_MASK; + return 0; +} + +/** + * release_idr - function to free the unique id. + * @idr: struct idr * handle used for creating the id. + * @id: int value representing the unique id. + */ +static void release_idr(struct idr *idr, int id) +{ + mutex_lock(&cooling_cpufreq_lock); + idr_remove(idr, id); + mutex_unlock(&cooling_cpufreq_lock); +} + +/* Below code defines functions to be used for cpufreq as cooling device */ + +/** + * is_cpufreq_valid - function to check if a cpu has frequency transition policy. + * @cpu: cpu for which check is needed. + */ +static int is_cpufreq_valid(int cpu) +{ + struct cpufreq_policy policy; + return !cpufreq_get_policy(&policy, cpu); +} + +/** + * get_cpu_frequency - get the absolute value of frequency from level. + * @cpu: cpu for which frequency is fetched. + * @level: level of frequency of the CPU + * e.g level=1 --> 1st MAX FREQ, LEVEL=2 ---> 2nd MAX FREQ, .... etc + */ +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level) +{ + int ret = 0, i = 0; + unsigned long level_index; + bool descend = false; + struct cpufreq_frequency_table *table = + cpufreq_frequency_get_table(cpu); + if (!table) + return ret; + + while (table[i].frequency != CPUFREQ_TABLE_END) { + if (table[i].frequency == CPUFREQ_ENTRY_INVALID) + continue; + + /*check if table in ascending or descending order*/ + if ((table[i + 1].frequency != CPUFREQ_TABLE_END) && + (table[i + 1].frequency < table[i].frequency) + && !descend) { + descend = true; + } + + /*return if level matched and table in descending order*/ + if (descend && i == level) + return table[i].frequency; + i++; + } + i--; + + if (level > i || descend) + return ret; + level_index = i - level; + + /*Scan the table in reverse order and match the level*/ + while (i >= 0) { + if (table[i].frequency == CPUFREQ_ENTRY_INVALID) + continue; + /*return if level matched*/ + if (i == level_index) + return table[i].frequency; + i--; + } + return ret; +} + +/** + * cpufreq_apply_cooling - function to apply frequency clipping. + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency + * clipping data. + * @cooling_state: value of the cooling state. + */ +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device, + unsigned long cooling_state) +{ + unsigned int cpuid, clip_freq; + struct cpumask *maskPtr = &cpufreq_device->allowed_cpus; + unsigned int cpu = cpumask_any(maskPtr); + + + /* Check if the old cooling action is same as new cooling action */ + if (cpufreq_device->cpufreq_state == cooling_state) + return 0; + + clip_freq = get_cpu_frequency(cpu, cooling_state); + if (!clip_freq) + return -EINVAL; + + cpufreq_device->cpufreq_state = cooling_state; + cpufreq_device->cpufreq_val = clip_freq; + notify_device = cpufreq_device; + + for_each_cpu(cpuid, maskPtr) { + if (is_cpufreq_valid(cpuid)) + cpufreq_update_policy(cpuid); + } + + notify_device = NOTIFY_INVALID; + + return 0; +} + +/** + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change. + * @nb: struct notifier_block * with callback info. + * @event: value showing cpufreq event for which this function invoked. + * @data: callback-specific data + */ +static int cpufreq_thermal_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_policy *policy = data; + unsigned long max_freq = 0; + + if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID) + return 0; + + if (cpumask_test_cpu(policy->cpu, ¬ify_device->allowed_cpus)) + max_freq = notify_device->cpufreq_val; + + /* Never exceed user_policy.max*/ + if (max_freq > policy->user_policy.max) + max_freq = policy->user_policy.max; + + if (policy->max != max_freq) + cpufreq_verify_within_limits(policy, 0, max_freq); + + return 0; +} + +/* + * cpufreq cooling device callback functions are defined below + */ + +/** + * cpufreq_get_max_state - callback function to get the max cooling state. + * @cdev: thermal cooling device pointer. + * @state: fill this variable with the max cooling state. + */ +static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + int ret = -EINVAL, i = 0; + struct cpufreq_cooling_device *cpufreq_device; + struct cpumask *maskPtr; + unsigned int cpu; + struct cpufreq_frequency_table *table; + + mutex_lock(&cooling_cpufreq_lock); + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { + if (cpufreq_device && cpufreq_device->cool_dev == cdev) + break; + } + if (cpufreq_device == NULL) + goto return_get_max_state; + + maskPtr = &cpufreq_device->allowed_cpus; + cpu = cpumask_any(maskPtr); + table = cpufreq_frequency_get_table(cpu); + if (!table) { + *state = 0; + ret = 0; + goto return_get_max_state; + } + + while (table[i].frequency != CPUFREQ_TABLE_END) { + if (table[i].frequency == CPUFREQ_ENTRY_INVALID) + continue; + i++; + } + if (i > 0) { + *state = --i; + ret = 0; + } + +return_get_max_state: + mutex_unlock(&cooling_cpufreq_lock); + return ret; +} + +/** + * cpufreq_get_cur_state - callback function to get the current cooling state. + * @cdev: thermal cooling device pointer. + * @state: fill this variable with the current cooling state. + */ +static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + int ret = -EINVAL; + struct cpufreq_cooling_device *cpufreq_device; + + mutex_lock(&cooling_cpufreq_lock); + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { + if (cpufreq_device && cpufreq_device->cool_dev == cdev) { + *state = cpufreq_device->cpufreq_state; + ret = 0; + break; + } + } + mutex_unlock(&cooling_cpufreq_lock); + + return ret; +} + +/** + * cpufreq_set_cur_state - callback function to set the current cooling state. + * @cdev: thermal cooling device pointer. + * @state: set this variable to the current cooling state. + */ +static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + int ret = -EINVAL; + struct cpufreq_cooling_device *cpufreq_device; + + mutex_lock(&cooling_cpufreq_lock); + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { + if (cpufreq_device && cpufreq_device->cool_dev == cdev) { + ret = 0; + break; + } + } + if (!ret) + ret = cpufreq_apply_cooling(cpufreq_device, state); + + mutex_unlock(&cooling_cpufreq_lock); + + return ret; +} + +/* Bind cpufreq callbacks to thermal cooling device ops */ +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = { + .get_max_state = cpufreq_get_max_state, + .get_cur_state = cpufreq_get_cur_state, + .set_cur_state = cpufreq_set_cur_state, +}; + +/* Notifier for cpufreq policy change */ +static struct notifier_block thermal_cpufreq_notifier_block = { + .notifier_call = cpufreq_thermal_notifier, +}; + +/** + * cpufreq_cooling_register - function to create cpufreq cooling device. + * @clip_cpus: cpumask of cpus where the frequency constraints will happen. + */ +struct thermal_cooling_device *cpufreq_cooling_register( + struct cpumask *clip_cpus) +{ + struct thermal_cooling_device *cool_dev; + struct cpufreq_cooling_device *cpufreq_dev = NULL; + unsigned int cpufreq_dev_count = 0, min = 0, max = 0; + char dev_name[THERMAL_NAME_LENGTH]; + int ret = 0, i; + struct cpufreq_policy policy; + + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) + cpufreq_dev_count++; + + /*Verify that all the clip cpus have same freq_min, freq_max limit*/ + for_each_cpu(i, clip_cpus) { + /*continue if cpufreq policy not found and not return error*/ + if (!cpufreq_get_policy(&policy, i)) + continue; + if (min == 0 && max == 0) { + min = policy.cpuinfo.min_freq; + max = policy.cpuinfo.max_freq; + } else { + if (min != policy.cpuinfo.min_freq || + max != policy.cpuinfo.max_freq) + return ERR_PTR(-EINVAL); +} + } + cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device), + GFP_KERNEL); + if (!cpufreq_dev) + return ERR_PTR(-ENOMEM); + + cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus); + + if (cpufreq_dev_count == 0) + mutex_init(&cooling_cpufreq_lock); + + ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); + if (ret) { + kfree(cpufreq_dev); + return ERR_PTR(-EINVAL); + } + + sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id); + + cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev, + &cpufreq_cooling_ops); + if (!cool_dev) { + release_idr(&cpufreq_idr, cpufreq_dev->id); + kfree(cpufreq_dev); + return ERR_PTR(-EINVAL); + } + cpufreq_dev->cool_dev = cool_dev; + cpufreq_dev->cpufreq_state = 0; + mutex_lock(&cooling_cpufreq_lock); + list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list); + + /* Register the notifier for first cpufreq cooling device */ + if (cpufreq_dev_count == 0) + cpufreq_register_notifier(&thermal_cpufreq_notifier_block, + CPUFREQ_POLICY_NOTIFIER); + + mutex_unlock(&cooling_cpufreq_lock); + return cool_dev; +} +EXPORT_SYMBOL(cpufreq_cooling_register); + +/** + * cpufreq_cooling_unregister - function to remove cpufreq cooling device. + * @cdev: thermal cooling device pointer. + */ +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) +{ + struct cpufreq_cooling_device *cpufreq_dev = NULL; + unsigned int cpufreq_dev_count = 0; + + mutex_lock(&cooling_cpufreq_lock); + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) { + if (cpufreq_dev && cpufreq_dev->cool_dev == cdev) + break; + cpufreq_dev_count++; + } + + if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) { + mutex_unlock(&cooling_cpufreq_lock); + return; + } + + list_del(&cpufreq_dev->node); + + /* Unregister the notifier for the last cpufreq cooling device */ + if (cpufreq_dev_count == 1) { + cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, + CPUFREQ_POLICY_NOTIFIER); + } + mutex_unlock(&cooling_cpufreq_lock); + thermal_cooling_device_unregister(cpufreq_dev->cool_dev); + release_idr(&cpufreq_idr, cpufreq_dev->id); + if (cpufreq_dev_count == 1) + mutex_destroy(&cooling_cpufreq_lock); + kfree(cpufreq_dev); +} +EXPORT_SYMBOL(cpufreq_cooling_unregister); diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c new file mode 100644 index 00000000000..fd03e8581af --- /dev/null +++ b/drivers/thermal/exynos_thermal.c @@ -0,0 +1,997 @@ +/* + * exynos_thermal.c - Samsung EXYNOS TMU (Thermal Management Unit) + * + * Copyright (C) 2011 Samsung Electronics + * Donggeun Kim <dg77.kim@samsung.com> + * Amit Daniel Kachhap <amit.kachhap@linaro.org> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/workqueue.h> +#include <linux/sysfs.h> +#include <linux/kobject.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/platform_data/exynos_thermal.h> +#include <linux/thermal.h> +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h> +#include <linux/of.h> + +#include <plat/cpu.h> + +/* Exynos generic registers */ +#define EXYNOS_TMU_REG_TRIMINFO 0x0 +#define EXYNOS_TMU_REG_CONTROL 0x20 +#define EXYNOS_TMU_REG_STATUS 0x28 +#define EXYNOS_TMU_REG_CURRENT_TEMP 0x40 +#define EXYNOS_TMU_REG_INTEN 0x70 +#define EXYNOS_TMU_REG_INTSTAT 0x74 +#define EXYNOS_TMU_REG_INTCLEAR 0x78 + +#define EXYNOS_TMU_TRIM_TEMP_MASK 0xff +#define EXYNOS_TMU_GAIN_SHIFT 8 +#define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24 +#define EXYNOS_TMU_CORE_ON 3 +#define EXYNOS_TMU_CORE_OFF 2 +#define EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET 50 + +/* Exynos4210 specific registers */ +#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44 +#define EXYNOS4210_TMU_REG_TRIG_LEVEL0 0x50 +#define EXYNOS4210_TMU_REG_TRIG_LEVEL1 0x54 +#define EXYNOS4210_TMU_REG_TRIG_LEVEL2 0x58 +#define EXYNOS4210_TMU_REG_TRIG_LEVEL3 0x5C +#define EXYNOS4210_TMU_REG_PAST_TEMP0 0x60 +#define EXYNOS4210_TMU_REG_PAST_TEMP1 0x64 +#define EXYNOS4210_TMU_REG_PAST_TEMP2 0x68 +#define EXYNOS4210_TMU_REG_PAST_TEMP3 0x6C + +#define EXYNOS4210_TMU_TRIG_LEVEL0_MASK 0x1 +#define EXYNOS4210_TMU_TRIG_LEVEL1_MASK 0x10 +#define EXYNOS4210_TMU_TRIG_LEVEL2_MASK 0x100 +#define EXYNOS4210_TMU_TRIG_LEVEL3_MASK 0x1000 +#define EXYNOS4210_TMU_INTCLEAR_VAL 0x1111 + +/* Exynos5250 and Exynos4412 specific registers */ +#define EXYNOS_TMU_TRIMINFO_CON 0x14 +#define EXYNOS_THD_TEMP_RISE 0x50 +#define EXYNOS_THD_TEMP_FALL 0x54 +#define EXYNOS_EMUL_CON 0x80 + +#define EXYNOS_TRIMINFO_RELOAD 0x1 +#define EXYNOS_TMU_CLEAR_RISE_INT 0x111 +#define EXYNOS_TMU_CLEAR_FALL_INT (0x111 << 16) +#define EXYNOS_MUX_ADDR_VALUE 6 +#define EXYNOS_MUX_ADDR_SHIFT 20 +#define EXYNOS_TMU_TRIP_MODE_SHIFT 13 + +#define EFUSE_MIN_VALUE 40 +#define EFUSE_MAX_VALUE 100 + +/* In-kernel thermal framework related macros & definations */ +#define SENSOR_NAME_LEN 16 +#define MAX_TRIP_COUNT 8 +#define MAX_COOLING_DEVICE 4 + +#define ACTIVE_INTERVAL 500 +#define IDLE_INTERVAL 10000 +#define MCELSIUS 1000 + +/* CPU Zone information */ +#define PANIC_ZONE 4 +#define WARN_ZONE 3 +#define MONITOR_ZONE 2 +#define SAFE_ZONE 1 + +#define GET_ZONE(trip) (trip + 2) +#define GET_TRIP(zone) (zone - 2) + +#define EXYNOS_ZONE_COUNT 3 + +struct exynos_tmu_data { + struct exynos_tmu_platform_data *pdata; + struct resource *mem; + void __iomem *base; + int irq; + enum soc_type soc; + struct work_struct irq_work; + struct mutex lock; + struct clk *clk; + u8 temp_error1, temp_error2; +}; + +struct thermal_trip_point_conf { + int trip_val[MAX_TRIP_COUNT]; + int trip_count; +}; + +struct thermal_cooling_conf { + struct freq_clip_table freq_data[MAX_TRIP_COUNT]; + int freq_clip_count; +}; + +struct thermal_sensor_conf { + char name[SENSOR_NAME_LEN]; + int (*read_temperature)(void *data); + struct thermal_trip_point_conf trip_data; + struct thermal_cooling_conf cooling_data; + void *private_data; +}; + +struct exynos_thermal_zone { + enum thermal_device_mode mode; + struct thermal_zone_device *therm_dev; + struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE]; + unsigned int cool_dev_size; + struct platform_device *exynos4_dev; + struct thermal_sensor_conf *sensor_conf; + bool bind; +}; + +static struct exynos_thermal_zone *th_zone; +static void exynos_unregister_thermal(void); +static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); + +/* Get mode callback functions for thermal zone */ +static int exynos_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + if (th_zone) + *mode = th_zone->mode; + return 0; +} + +/* Set mode callback functions for thermal zone */ +static int exynos_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + if (!th_zone->therm_dev) { + pr_notice("thermal zone not registered\n"); + return 0; + } + + mutex_lock(&th_zone->therm_dev->lock); + + if (mode == THERMAL_DEVICE_ENABLED) + th_zone->therm_dev->polling_delay = IDLE_INTERVAL; + else + th_zone->therm_dev->polling_delay = 0; + + mutex_unlock(&th_zone->therm_dev->lock); + + th_zone->mode = mode; + thermal_zone_device_update(th_zone->therm_dev); + pr_info("thermal polling set for duration=%d msec\n", + th_zone->therm_dev->polling_delay); + return 0; +} + + +/* Get trip type callback functions for thermal zone */ +static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip, + enum thermal_trip_type *type) +{ + switch (GET_ZONE(trip)) { + case MONITOR_ZONE: + case WARN_ZONE: + *type = THERMAL_TRIP_ACTIVE; + break; + case PANIC_ZONE: + *type = THERMAL_TRIP_CRITICAL; + break; + default: + return -EINVAL; + } + return 0; +} + +/* Get trip temperature callback functions for thermal zone */ +static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip, + unsigned long *temp) +{ + if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE)) + return -EINVAL; + + *temp = th_zone->sensor_conf->trip_data.trip_val[trip]; + /* convert the temperature into millicelsius */ + *temp = *temp * MCELSIUS; + + return 0; +} + +/* Get critical temperature callback functions for thermal zone */ +static int exynos_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + int ret; + /* Panic zone */ + ret = exynos_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp); + return ret; +} + +static int exynos_get_frequency_level(unsigned int cpu, unsigned int freq) +{ + int i = 0, ret = -EINVAL; + struct cpufreq_frequency_table *table = NULL; +#ifdef CONFIG_CPU_FREQ + table = cpufreq_frequency_get_table(cpu); +#endif + if (!table) + return ret; + + while (table[i].frequency != CPUFREQ_TABLE_END) { + if (table[i].frequency == CPUFREQ_ENTRY_INVALID) + continue; + if (table[i].frequency == freq) + return i; + i++; + } + return ret; +} + +/* Bind callback functions for thermal zone */ +static int exynos_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + int ret = 0, i, tab_size, level; + struct freq_clip_table *tab_ptr, *clip_data; + struct thermal_sensor_conf *data = th_zone->sensor_conf; + + tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data; + tab_size = data->cooling_data.freq_clip_count; + + if (tab_ptr == NULL || tab_size == 0) + return -EINVAL; + + /* find the cooling device registered*/ + for (i = 0; i < th_zone->cool_dev_size; i++) + if (cdev == th_zone->cool_dev[i]) + break; + + /* No matching cooling device */ + if (i == th_zone->cool_dev_size) + return 0; + + /* Bind the thermal zone to the cpufreq cooling device */ + for (i = 0; i < tab_size; i++) { + clip_data = (struct freq_clip_table *)&(tab_ptr[i]); + level = exynos_get_frequency_level(0, clip_data->freq_clip_max); + if (level < 0) + return 0; + switch (GET_ZONE(i)) { + case MONITOR_ZONE: + case WARN_ZONE: + if (thermal_zone_bind_cooling_device(thermal, i, cdev, + level, level)) { + pr_err("error binding cdev inst %d\n", i); + ret = -EINVAL; + } + th_zone->bind = true; + break; + default: + ret = -EINVAL; + } + } + + return ret; +} + +/* Unbind callback functions for thermal zone */ +static int exynos_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + int ret = 0, i, tab_size; + struct thermal_sensor_conf *data = th_zone->sensor_conf; + + if (th_zone->bind == false) + return 0; + + tab_size = data->cooling_data.freq_clip_count; + + if (tab_size == 0) + return -EINVAL; + + /* find the cooling device registered*/ + for (i = 0; i < th_zone->cool_dev_size; i++) + if (cdev == th_zone->cool_dev[i]) + break; + + /* No matching cooling device */ + if (i == th_zone->cool_dev_size) + return 0; + + /* Bind the thermal zone to the cpufreq cooling device */ + for (i = 0; i < tab_size; i++) { + switch (GET_ZONE(i)) { + case MONITOR_ZONE: + case WARN_ZONE: + if (thermal_zone_unbind_cooling_device(thermal, i, + cdev)) { + pr_err("error unbinding cdev inst=%d\n", i); + ret = -EINVAL; + } + th_zone->bind = false; + break; + default: + ret = -EINVAL; + } + } + return ret; +} + +/* Get temperature callback functions for thermal zone */ +static int exynos_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + void *data; + + if (!th_zone->sensor_conf) { + pr_info("Temperature sensor not initialised\n"); + return -EINVAL; + } + data = th_zone->sensor_conf->private_data; + *temp = th_zone->sensor_conf->read_temperature(data); + /* convert the temperature into millicelsius */ + *temp = *temp * MCELSIUS; + return 0; +} + +/* Get the temperature trend */ +static int exynos_get_trend(struct thermal_zone_device *thermal, + int trip, enum thermal_trend *trend) +{ + if (thermal->temperature >= trip) + *trend = THERMAL_TREND_RAISING; + else + *trend = THERMAL_TREND_DROPPING; + + return 0; +} +/* Operation callback functions for thermal zone */ +static struct thermal_zone_device_ops const exynos_dev_ops = { + .bind = exynos_bind, + .unbind = exynos_unbind, + .get_temp = exynos_get_temp, + .get_trend = exynos_get_trend, + .get_mode = exynos_get_mode, + .set_mode = exynos_set_mode, + .get_trip_type = exynos_get_trip_type, + .get_trip_temp = exynos_get_trip_temp, + .get_crit_temp = exynos_get_crit_temp, +}; + +/* + * This function may be called from interrupt based temperature sensor + * when threshold is changed. + */ +static void exynos_report_trigger(void) +{ + unsigned int i; + char data[10]; + char *envp[] = { data, NULL }; + + if (!th_zone || !th_zone->therm_dev) + return; + if (th_zone->bind == false) { + for (i = 0; i < th_zone->cool_dev_size; i++) { + if (!th_zone->cool_dev[i]) + continue; + exynos_bind(th_zone->therm_dev, + th_zone->cool_dev[i]); + } + } + + thermal_zone_device_update(th_zone->therm_dev); + + mutex_lock(&th_zone->therm_dev->lock); + /* Find the level for which trip happened */ + for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) { + if (th_zone->therm_dev->last_temperature < + th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS) + break; + } + + if (th_zone->mode == THERMAL_DEVICE_ENABLED) { + if (i > 0) + th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL; + else + th_zone->therm_dev->polling_delay = IDLE_INTERVAL; + } + + snprintf(data, sizeof(data), "%u", i); + kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp); + mutex_unlock(&th_zone->therm_dev->lock); +} + +/* Register with the in-kernel thermal management */ +static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) +{ + int ret; + struct cpumask mask_val; + + if (!sensor_conf || !sensor_conf->read_temperature) { + pr_err("Temperature sensor not initialised\n"); + return -EINVAL; + } + + th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL); + if (!th_zone) + return -ENOMEM; + + th_zone->sensor_conf = sensor_conf; + cpumask_set_cpu(0, &mask_val); + th_zone->cool_dev[0] = cpufreq_cooling_register(&mask_val); + if (IS_ERR(th_zone->cool_dev[0])) { + pr_err("Failed to register cpufreq cooling device\n"); + ret = -EINVAL; + goto err_unregister; + } + th_zone->cool_dev_size++; + + th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name, + EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, 0, + IDLE_INTERVAL); + + if (IS_ERR(th_zone->therm_dev)) { + pr_err("Failed to register thermal zone device\n"); + ret = -EINVAL; + goto err_unregister; + } + th_zone->mode = THERMAL_DEVICE_ENABLED; + + pr_info("Exynos: Kernel Thermal management registered\n"); + + return 0; + +err_unregister: + exynos_unregister_thermal(); + return ret; +} + +/* Un-Register with the in-kernel thermal management */ +static void exynos_unregister_thermal(void) +{ + int i; + + if (!th_zone) + return; + + if (th_zone->therm_dev) + thermal_zone_device_unregister(th_zone->therm_dev); + + for (i = 0; i < th_zone->cool_dev_size; i++) { + if (th_zone->cool_dev[i]) + cpufreq_cooling_unregister(th_zone->cool_dev[i]); + } + + kfree(th_zone); + pr_info("Exynos: Kernel Thermal management unregistered\n"); +} + +/* + * TMU treats temperature as a mapped temperature code. + * The temperature is converted differently depending on the calibration type. + */ +static int temp_to_code(struct exynos_tmu_data *data, u8 temp) +{ + struct exynos_tmu_platform_data *pdata = data->pdata; + int temp_code; + + if (data->soc == SOC_ARCH_EXYNOS4210) + /* temp should range between 25 and 125 */ + if (temp < 25 || temp > 125) { + temp_code = -EINVAL; + goto out; + } + + switch (pdata->cal_type) { + case TYPE_TWO_POINT_TRIMMING: + temp_code = (temp - 25) * + (data->temp_error2 - data->temp_error1) / + (85 - 25) + data->temp_error1; + break; + case TYPE_ONE_POINT_TRIMMING: + temp_code = temp + data->temp_error1 - 25; + break; + default: + temp_code = temp + EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET; + break; + } +out: + return temp_code; +} + +/* + * Calculate a temperature value from a temperature code. + * The unit of the temperature is degree Celsius. + */ +static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code) +{ + struct exynos_tmu_platform_data *pdata = data->pdata; + int temp; + + if (data->soc == SOC_ARCH_EXYNOS4210) + /* temp_code should range between 75 and 175 */ + if (temp_code < 75 || temp_code > 175) { + temp = -ENODATA; + goto out; + } + + switch (pdata->cal_type) { + case TYPE_TWO_POINT_TRIMMING: + temp = (temp_code - data->temp_error1) * (85 - 25) / + (data->temp_error2 - data->temp_error1) + 25; + break; + case TYPE_ONE_POINT_TRIMMING: + temp = temp_code - data->temp_error1 + 25; + break; + default: + temp = temp_code - EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET; + break; + } +out: + return temp; +} + +static int exynos_tmu_initialize(struct platform_device *pdev) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + struct exynos_tmu_platform_data *pdata = data->pdata; + unsigned int status, trim_info, rising_threshold; + int ret = 0, threshold_code; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + status = readb(data->base + EXYNOS_TMU_REG_STATUS); + if (!status) { + ret = -EBUSY; + goto out; + } + + if (data->soc == SOC_ARCH_EXYNOS) { + __raw_writel(EXYNOS_TRIMINFO_RELOAD, + data->base + EXYNOS_TMU_TRIMINFO_CON); + } + /* Save trimming info in order to perform calibration */ + trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); + data->temp_error1 = trim_info & EXYNOS_TMU_TRIM_TEMP_MASK; + data->temp_error2 = ((trim_info >> 8) & EXYNOS_TMU_TRIM_TEMP_MASK); + + if ((EFUSE_MIN_VALUE > data->temp_error1) || + (data->temp_error1 > EFUSE_MAX_VALUE) || + (data->temp_error2 != 0)) + data->temp_error1 = pdata->efuse_value; + + if (data->soc == SOC_ARCH_EXYNOS4210) { + /* Write temperature code for threshold */ + threshold_code = temp_to_code(data, pdata->threshold); + if (threshold_code < 0) { + ret = threshold_code; + goto out; + } + writeb(threshold_code, + data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP); + + writeb(pdata->trigger_levels[0], + data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0); + writeb(pdata->trigger_levels[1], + data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL1); + writeb(pdata->trigger_levels[2], + data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL2); + writeb(pdata->trigger_levels[3], + data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL3); + + writel(EXYNOS4210_TMU_INTCLEAR_VAL, + data->base + EXYNOS_TMU_REG_INTCLEAR); + } else if (data->soc == SOC_ARCH_EXYNOS) { + /* Write temperature code for threshold */ + threshold_code = temp_to_code(data, pdata->trigger_levels[0]); + if (threshold_code < 0) { + ret = threshold_code; + goto out; + } + rising_threshold = threshold_code; + threshold_code = temp_to_code(data, pdata->trigger_levels[1]); + if (threshold_code < 0) { + ret = threshold_code; + goto out; + } + rising_threshold |= (threshold_code << 8); + threshold_code = temp_to_code(data, pdata->trigger_levels[2]); + if (threshold_code < 0) { + ret = threshold_code; + goto out; + } + rising_threshold |= (threshold_code << 16); + + writel(rising_threshold, + data->base + EXYNOS_THD_TEMP_RISE); + writel(0, data->base + EXYNOS_THD_TEMP_FALL); + + writel(EXYNOS_TMU_CLEAR_RISE_INT|EXYNOS_TMU_CLEAR_FALL_INT, + data->base + EXYNOS_TMU_REG_INTCLEAR); + } +out: + clk_disable(data->clk); + mutex_unlock(&data->lock); + + return ret; +} + +static void exynos_tmu_control(struct platform_device *pdev, bool on) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + struct exynos_tmu_platform_data *pdata = data->pdata; + unsigned int con, interrupt_en; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + con = pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT | + pdata->gain << EXYNOS_TMU_GAIN_SHIFT; + + if (data->soc == SOC_ARCH_EXYNOS) { + con |= pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT; + con |= (EXYNOS_MUX_ADDR_VALUE << EXYNOS_MUX_ADDR_SHIFT); + } + + if (on) { + con |= EXYNOS_TMU_CORE_ON; + interrupt_en = pdata->trigger_level3_en << 12 | + pdata->trigger_level2_en << 8 | + pdata->trigger_level1_en << 4 | + pdata->trigger_level0_en; + } else { + con |= EXYNOS_TMU_CORE_OFF; + interrupt_en = 0; /* Disable all interrupts */ + } + writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN); + writel(con, data->base + EXYNOS_TMU_REG_CONTROL); + + clk_disable(data->clk); + mutex_unlock(&data->lock); +} + +static int exynos_tmu_read(struct exynos_tmu_data *data) +{ + u8 temp_code; + int temp; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + temp_code = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP); + temp = code_to_temp(data, temp_code); + + clk_disable(data->clk); + mutex_unlock(&data->lock); + + return temp; +} + +static void exynos_tmu_work(struct work_struct *work) +{ + struct exynos_tmu_data *data = container_of(work, + struct exynos_tmu_data, irq_work); + + mutex_lock(&data->lock); + clk_enable(data->clk); + + + if (data->soc == SOC_ARCH_EXYNOS) + writel(EXYNOS_TMU_CLEAR_RISE_INT, + data->base + EXYNOS_TMU_REG_INTCLEAR); + else + writel(EXYNOS4210_TMU_INTCLEAR_VAL, + data->base + EXYNOS_TMU_REG_INTCLEAR); + + clk_disable(data->clk); + mutex_unlock(&data->lock); + exynos_report_trigger(); + enable_irq(data->irq); +} + +static irqreturn_t exynos_tmu_irq(int irq, void *id) +{ + struct exynos_tmu_data *data = id; + + disable_irq_nosync(irq); + schedule_work(&data->irq_work); + + return IRQ_HANDLED; +} +static struct thermal_sensor_conf exynos_sensor_conf = { + .name = "exynos-therm", + .read_temperature = (int (*)(void *))exynos_tmu_read, +}; + +#if defined(CONFIG_CPU_EXYNOS4210) +static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = { + .threshold = 80, + .trigger_levels[0] = 5, + .trigger_levels[1] = 20, + .trigger_levels[2] = 30, + .trigger_level0_en = 1, + .trigger_level1_en = 1, + .trigger_level2_en = 1, + .trigger_level3_en = 0, + .gain = 15, + .reference_voltage = 7, + .cal_type = TYPE_ONE_POINT_TRIMMING, + .freq_tab[0] = { + .freq_clip_max = 800 * 1000, + .temp_level = 85, + }, + .freq_tab[1] = { + .freq_clip_max = 200 * 1000, + .temp_level = 100, + }, + .freq_tab_count = 2, + .type = SOC_ARCH_EXYNOS4210, +}; +#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data) +#else +#define EXYNOS4210_TMU_DRV_DATA (NULL) +#endif + +#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412) +static struct exynos_tmu_platform_data const exynos_default_tmu_data = { + .trigger_levels[0] = 85, + .trigger_levels[1] = 103, + .trigger_levels[2] = 110, + .trigger_level0_en = 1, + .trigger_level1_en = 1, + .trigger_level2_en = 1, + .trigger_level3_en = 0, + .gain = 8, + .reference_voltage = 16, + .noise_cancel_mode = 4, + .cal_type = TYPE_ONE_POINT_TRIMMING, + .efuse_value = 55, + .freq_tab[0] = { + .freq_clip_max = 800 * 1000, + .temp_level = 85, + }, + .freq_tab[1] = { + .freq_clip_max = 200 * 1000, + .temp_level = 103, + }, + .freq_tab_count = 2, + .type = SOC_ARCH_EXYNOS, +}; +#define EXYNOS_TMU_DRV_DATA (&exynos_default_tmu_data) +#else +#define EXYNOS_TMU_DRV_DATA (NULL) +#endif + +#ifdef CONFIG_OF +static const struct of_device_id exynos_tmu_match[] = { + { + .compatible = "samsung,exynos4210-tmu", + .data = (void *)EXYNOS4210_TMU_DRV_DATA, + }, + { + .compatible = "samsung,exynos5250-tmu", + .data = (void *)EXYNOS_TMU_DRV_DATA, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_tmu_match); +#else +#define exynos_tmu_match NULL +#endif + +static struct platform_device_id exynos_tmu_driver_ids[] = { + { + .name = "exynos4210-tmu", + .driver_data = (kernel_ulong_t)EXYNOS4210_TMU_DRV_DATA, + }, + { + .name = "exynos5250-tmu", + .driver_data = (kernel_ulong_t)EXYNOS_TMU_DRV_DATA, + }, + { }, +}; +MODULE_DEVICE_TABLE(platform, exynos4_tmu_driver_ids); + +static inline struct exynos_tmu_platform_data *exynos_get_driver_data( + struct platform_device *pdev) +{ +#ifdef CONFIG_OF + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(exynos_tmu_match, pdev->dev.of_node); + if (!match) + return NULL; + return (struct exynos_tmu_platform_data *) match->data; + } +#endif + return (struct exynos_tmu_platform_data *) + platform_get_device_id(pdev)->driver_data; +} +static int __devinit exynos_tmu_probe(struct platform_device *pdev) +{ + struct exynos_tmu_data *data; + struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data; + int ret, i; + + if (!pdata) + pdata = exynos_get_driver_data(pdev); + + if (!pdata) { + dev_err(&pdev->dev, "No platform init data supplied.\n"); + return -ENODEV; + } + data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data), + GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "Failed to allocate driver structure\n"); + return -ENOMEM; + } + + data->irq = platform_get_irq(pdev, 0); + if (data->irq < 0) { + dev_err(&pdev->dev, "Failed to get platform irq\n"); + return data->irq; + } + + INIT_WORK(&data->irq_work, exynos_tmu_work); + + data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!data->mem) { + dev_err(&pdev->dev, "Failed to get platform resource\n"); + return -ENOENT; + } + + data->base = devm_request_and_ioremap(&pdev->dev, data->mem); + if (!data->base) { + dev_err(&pdev->dev, "Failed to ioremap memory\n"); + return -ENODEV; + } + + ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, + IRQF_TRIGGER_RISING, "exynos-tmu", data); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); + return ret; + } + + data->clk = clk_get(NULL, "tmu_apbif"); + if (IS_ERR(data->clk)) { + dev_err(&pdev->dev, "Failed to get clock\n"); + return PTR_ERR(data->clk); + } + + if (pdata->type == SOC_ARCH_EXYNOS || + pdata->type == SOC_ARCH_EXYNOS4210) + data->soc = pdata->type; + else { + ret = -EINVAL; + dev_err(&pdev->dev, "Platform not supported\n"); + goto err_clk; + } + + data->pdata = pdata; + platform_set_drvdata(pdev, data); + mutex_init(&data->lock); + + ret = exynos_tmu_initialize(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize TMU\n"); + goto err_clk; + } + + exynos_tmu_control(pdev, true); + + /* Register the sensor with thermal management interface */ + (&exynos_sensor_conf)->private_data = data; + exynos_sensor_conf.trip_data.trip_count = pdata->trigger_level0_en + + pdata->trigger_level1_en + pdata->trigger_level2_en + + pdata->trigger_level3_en; + + for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++) + exynos_sensor_conf.trip_data.trip_val[i] = + pdata->threshold + pdata->trigger_levels[i]; + + exynos_sensor_conf.cooling_data.freq_clip_count = + pdata->freq_tab_count; + for (i = 0; i < pdata->freq_tab_count; i++) { + exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max = + pdata->freq_tab[i].freq_clip_max; + exynos_sensor_conf.cooling_data.freq_data[i].temp_level = + pdata->freq_tab[i].temp_level; + } + + ret = exynos_register_thermal(&exynos_sensor_conf); + if (ret) { + dev_err(&pdev->dev, "Failed to register thermal interface\n"); + goto err_clk; + } + return 0; +err_clk: + platform_set_drvdata(pdev, NULL); + clk_put(data->clk); + return ret; +} + +static int __devexit exynos_tmu_remove(struct platform_device *pdev) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + + exynos_tmu_control(pdev, false); + + exynos_unregister_thermal(); + + clk_put(data->clk); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int exynos_tmu_suspend(struct device *dev) +{ + exynos_tmu_control(to_platform_device(dev), false); + + return 0; +} + +static int exynos_tmu_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + exynos_tmu_initialize(pdev); + exynos_tmu_control(pdev, true); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(exynos_tmu_pm, + exynos_tmu_suspend, exynos_tmu_resume); +#define EXYNOS_TMU_PM (&exynos_tmu_pm) +#else +#define EXYNOS_TMU_PM NULL +#endif + +static struct platform_driver exynos_tmu_driver = { + .driver = { + .name = "exynos-tmu", + .owner = THIS_MODULE, + .pm = EXYNOS_TMU_PM, + .of_match_table = exynos_tmu_match, + }, + .probe = exynos_tmu_probe, + .remove = __devexit_p(exynos_tmu_remove), + .id_table = exynos_tmu_driver_ids, +}; + +module_platform_driver(exynos_tmu_driver); + +MODULE_DESCRIPTION("EXYNOS TMU Driver"); +MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:exynos-tmu"); diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c new file mode 100644 index 00000000000..d4452716aaa --- /dev/null +++ b/drivers/thermal/rcar_thermal.c @@ -0,0 +1,260 @@ +/* + * R-Car THS/TSC thermal sensor driver + * + * Copyright (C) 2012 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.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. + */ +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/thermal.h> + +#define THSCR 0x2c +#define THSSR 0x30 + +/* THSCR */ +#define CPTAP 0xf + +/* THSSR */ +#define CTEMP 0x3f + + +struct rcar_thermal_priv { + void __iomem *base; + struct device *dev; + spinlock_t lock; + u32 comp; +}; + +/* + * basic functions + */ +static u32 rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg) +{ + unsigned long flags; + u32 ret; + + spin_lock_irqsave(&priv->lock, flags); + + ret = ioread32(priv->base + reg); + + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; +} + +#if 0 /* no user at this point */ +static void rcar_thermal_write(struct rcar_thermal_priv *priv, + u32 reg, u32 data) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + iowrite32(data, priv->base + reg); + + spin_unlock_irqrestore(&priv->lock, flags); +} +#endif + +static void rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg, + u32 mask, u32 data) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&priv->lock, flags); + + val = ioread32(priv->base + reg); + val &= ~mask; + val |= (data & mask); + iowrite32(val, priv->base + reg); + + spin_unlock_irqrestore(&priv->lock, flags); +} + +/* + * zone device functions + */ +static int rcar_thermal_get_temp(struct thermal_zone_device *zone, + unsigned long *temp) +{ + struct rcar_thermal_priv *priv = zone->devdata; + int val, min, max, tmp; + + tmp = -200; /* default */ + while (1) { + if (priv->comp < 1 || priv->comp > 12) { + dev_err(priv->dev, + "THSSR invalid data (%d)\n", priv->comp); + priv->comp = 4; /* for next thermal */ + return -EINVAL; + } + + /* + * THS comparator offset and the reference temperature + * + * Comparator | reference | Temperature field + * offset | temperature | measurement + * | (degrees C) | (degrees C) + * -------------+---------------+------------------- + * 1 | -45 | -45 to -30 + * 2 | -30 | -30 to -15 + * 3 | -15 | -15 to 0 + * 4 | 0 | 0 to +15 + * 5 | +15 | +15 to +30 + * 6 | +30 | +30 to +45 + * 7 | +45 | +45 to +60 + * 8 | +60 | +60 to +75 + * 9 | +75 | +75 to +90 + * 10 | +90 | +90 to +105 + * 11 | +105 | +105 to +120 + * 12 | +120 | +120 to +135 + */ + + /* calculate thermal limitation */ + min = (priv->comp * 15) - 60; + max = min + 15; + + /* + * we need to wait 300us after changing comparator offset + * to get stable temperature. + * see "Usage Notes" on datasheet + */ + rcar_thermal_bset(priv, THSCR, CPTAP, priv->comp); + udelay(300); + + /* calculate current temperature */ + val = rcar_thermal_read(priv, THSSR) & CTEMP; + val = (val * 5) - 65; + + dev_dbg(priv->dev, "comp/min/max/val = %d/%d/%d/%d\n", + priv->comp, min, max, val); + + /* + * If val is same as min/max, then, + * it should try again on next comparator. + * But the val might be correct temperature. + * Keep it on "tmp" and compare with next val. + */ + if (tmp == val) + break; + + if (val <= min) { + tmp = min; + priv->comp--; /* try again */ + } else if (val >= max) { + tmp = max; + priv->comp++; /* try again */ + } else { + tmp = val; + break; + } + } + + *temp = tmp; + return 0; +} + +static struct thermal_zone_device_ops rcar_thermal_zone_ops = { + .get_temp = rcar_thermal_get_temp, +}; + +/* + * platform functions + */ +static int rcar_thermal_probe(struct platform_device *pdev) +{ + struct thermal_zone_device *zone; + struct rcar_thermal_priv *priv; + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Could not get platform resource\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "Could not allocate priv\n"); + return -ENOMEM; + } + + priv->comp = 4; /* basic setup */ + priv->dev = &pdev->dev; + spin_lock_init(&priv->lock); + priv->base = devm_ioremap_nocache(&pdev->dev, + res->start, resource_size(res)); + if (!priv->base) { + dev_err(&pdev->dev, "Unable to ioremap thermal register\n"); + ret = -ENOMEM; + goto error_free_priv; + } + + zone = thermal_zone_device_register("rcar_thermal", 0, priv, + &rcar_thermal_zone_ops, 0, 0); + if (IS_ERR(zone)) { + dev_err(&pdev->dev, "thermal zone device is NULL\n"); + ret = PTR_ERR(zone); + goto error_iounmap; + } + + platform_set_drvdata(pdev, zone); + + dev_info(&pdev->dev, "proved\n"); + + return 0; + +error_iounmap: + devm_iounmap(&pdev->dev, priv->base); +error_free_priv: + devm_kfree(&pdev->dev, priv); + + return ret; +} + +static int rcar_thermal_remove(struct platform_device *pdev) +{ + struct thermal_zone_device *zone = platform_get_drvdata(pdev); + struct rcar_thermal_priv *priv = zone->devdata; + + thermal_zone_device_unregister(zone); + platform_set_drvdata(pdev, NULL); + + devm_iounmap(&pdev->dev, priv->base); + devm_kfree(&pdev->dev, priv); + + return 0; +} + +static struct platform_driver rcar_thermal_driver = { + .driver = { + .name = "rcar_thermal", + }, + .probe = rcar_thermal_probe, + .remove = rcar_thermal_remove, +}; +module_platform_driver(rcar_thermal_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("R-Car THS/TSC thermal sensor driver"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c index 5f8ee39f200..9bc969261d0 100644 --- a/drivers/thermal/spear_thermal.c +++ b/drivers/thermal/spear_thermal.c @@ -147,7 +147,7 @@ static int spear_thermal_probe(struct platform_device *pdev) writel_relaxed(stdev->flags, stdev->thermal_base); spear_thermal = thermal_zone_device_register("spear_thermal", 0, 0, - stdev, &ops, 0, 0, 0, 0); + stdev, &ops, 0, 0); if (IS_ERR(spear_thermal)) { dev_err(&pdev->dev, "thermal zone device is NULL\n"); ret = PTR_ERR(spear_thermal); diff --git a/drivers/thermal/thermal_sys.c b/drivers/thermal/thermal_sys.c index efd81bb25e0..9ee42ca4d28 100644 --- a/drivers/thermal/thermal_sys.c +++ b/drivers/thermal/thermal_sys.c @@ -41,15 +41,25 @@ MODULE_AUTHOR("Zhang Rui"); MODULE_DESCRIPTION("Generic thermal management sysfs support"); MODULE_LICENSE("GPL"); -struct thermal_cooling_device_instance { +#define THERMAL_NO_TARGET -1UL +/* + * This structure is used to describe the behavior of + * a certain cooling device on a certain trip point + * in a certain thermal zone + */ +struct thermal_instance { int id; char name[THERMAL_NAME_LENGTH]; struct thermal_zone_device *tz; struct thermal_cooling_device *cdev; int trip; + unsigned long upper; /* Highest cooling state for this trip point */ + unsigned long lower; /* Lowest cooling state for this trip point */ + unsigned long target; /* expected cooling state */ char attr_name[THERMAL_NAME_LENGTH]; struct device_attribute attr; - struct list_head node; + struct list_head tz_node; /* node in tz->thermal_instances */ + struct list_head cdev_node; /* node in cdev->thermal_instances */ }; static DEFINE_IDR(thermal_tz_idr); @@ -308,8 +318,9 @@ passive_store(struct device *dev, struct device_attribute *attr, if (!strncmp("Processor", cdev->type, sizeof("Processor"))) thermal_zone_bind_cooling_device(tz, - THERMAL_TRIPS_NONE, - cdev); + THERMAL_TRIPS_NONE, cdev, + THERMAL_NO_LIMIT, + THERMAL_NO_LIMIT); } mutex_unlock(&thermal_list_lock); if (!tz->passive_delay) @@ -327,9 +338,6 @@ passive_store(struct device *dev, struct device_attribute *attr, tz->passive_delay = 0; } - tz->tc1 = 1; - tz->tc2 = 1; - tz->forced_passive = state; thermal_zone_device_update(tz); @@ -425,10 +433,10 @@ static ssize_t thermal_cooling_device_trip_point_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct thermal_cooling_device_instance *instance; + struct thermal_instance *instance; instance = - container_of(attr, struct thermal_cooling_device_instance, attr); + container_of(attr, struct thermal_instance, attr); if (instance->trip == THERMAL_TRIPS_NONE) return sprintf(buf, "-1\n"); @@ -590,7 +598,7 @@ thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) temp->tz = tz; hwmon->count++; - snprintf(temp->temp_input.name, THERMAL_NAME_LENGTH, + snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), "temp%d_input", hwmon->count); temp->temp_input.attr.attr.name = temp->temp_input.name; temp->temp_input.attr.attr.mode = 0444; @@ -603,7 +611,8 @@ thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) if (tz->ops->get_crit_temp) { unsigned long temperature; if (!tz->ops->get_crit_temp(tz, &temperature)) { - snprintf(temp->temp_crit.name, THERMAL_NAME_LENGTH, + snprintf(temp->temp_crit.name, + sizeof(temp->temp_crit.name), "temp%d_crit", hwmon->count); temp->temp_crit.attr.attr.name = temp->temp_crit.name; temp->temp_crit.attr.attr.mode = 0444; @@ -704,74 +713,6 @@ static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, cancel_delayed_work(&tz->poll_queue); } -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 @@ -791,12 +732,14 @@ static void thermal_zone_device_check(struct work_struct *work) */ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, int trip, - struct thermal_cooling_device *cdev) + struct thermal_cooling_device *cdev, + unsigned long upper, unsigned long lower) { - struct thermal_cooling_device_instance *dev; - struct thermal_cooling_device_instance *pos; + struct thermal_instance *dev; + struct thermal_instance *pos; struct thermal_zone_device *pos1; struct thermal_cooling_device *pos2; + unsigned long max_state; int result; if (trip >= tz->trips || (trip < 0 && trip != THERMAL_TRIPS_NONE)) @@ -814,13 +757,26 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, if (tz != pos1 || cdev != pos2) return -EINVAL; + cdev->ops->get_max_state(cdev, &max_state); + + /* lower default 0, upper default max_state */ + lower = lower == THERMAL_NO_LIMIT ? 0 : lower; + upper = upper == THERMAL_NO_LIMIT ? max_state : upper; + + if (lower > upper || upper > max_state) + return -EINVAL; + dev = - kzalloc(sizeof(struct thermal_cooling_device_instance), GFP_KERNEL); + kzalloc(sizeof(struct thermal_instance), GFP_KERNEL); if (!dev) return -ENOMEM; dev->tz = tz; dev->cdev = cdev; dev->trip = trip; + dev->upper = upper; + dev->lower = lower; + dev->target = THERMAL_NO_TARGET; + result = get_idr(&tz->idr, &tz->lock, &dev->id); if (result) goto free_mem; @@ -841,13 +797,17 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, goto remove_symbol_link; mutex_lock(&tz->lock); - list_for_each_entry(pos, &tz->cooling_devices, node) + mutex_lock(&cdev->lock); + list_for_each_entry(pos, &tz->thermal_instances, tz_node) if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { result = -EEXIST; break; } - if (!result) - list_add_tail(&dev->node, &tz->cooling_devices); + if (!result) { + list_add_tail(&dev->tz_node, &tz->thermal_instances); + list_add_tail(&dev->cdev_node, &cdev->thermal_instances); + } + mutex_unlock(&cdev->lock); mutex_unlock(&tz->lock); if (!result) @@ -877,16 +837,20 @@ 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; + struct thermal_instance *pos, *next; mutex_lock(&tz->lock); - list_for_each_entry_safe(pos, next, &tz->cooling_devices, node) { + mutex_lock(&cdev->lock); + list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) { if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { - list_del(&pos->node); + list_del(&pos->tz_node); + list_del(&pos->cdev_node); + mutex_unlock(&cdev->lock); mutex_unlock(&tz->lock); goto unbind; } } + mutex_unlock(&cdev->lock); mutex_unlock(&tz->lock); return -ENODEV; @@ -934,7 +898,7 @@ thermal_cooling_device_register(char *type, void *devdata, struct thermal_zone_device *pos; int result; - if (strlen(type) >= THERMAL_NAME_LENGTH) + if (type && strlen(type) >= THERMAL_NAME_LENGTH) return ERR_PTR(-EINVAL); if (!ops || !ops->get_max_state || !ops->get_cur_state || @@ -951,8 +915,11 @@ thermal_cooling_device_register(char *type, void *devdata, return ERR_PTR(result); } - strcpy(cdev->type, type); + strcpy(cdev->type, type ? : ""); + mutex_init(&cdev->lock); + INIT_LIST_HEAD(&cdev->thermal_instances); cdev->ops = ops; + cdev->updated = true; cdev->device.class = &thermal_class; cdev->devdata = devdata; dev_set_name(&cdev->device, "cooling_device%d", cdev->id); @@ -1044,6 +1011,136 @@ void thermal_cooling_device_unregister(struct } EXPORT_SYMBOL(thermal_cooling_device_unregister); +static void thermal_cdev_do_update(struct thermal_cooling_device *cdev) +{ + struct thermal_instance *instance; + unsigned long target = 0; + + /* cooling device is updated*/ + if (cdev->updated) + return; + + mutex_lock(&cdev->lock); + /* Make sure cdev enters the deepest cooling state */ + list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) { + if (instance->target == THERMAL_NO_TARGET) + continue; + if (instance->target > target) + target = instance->target; + } + mutex_unlock(&cdev->lock); + cdev->ops->set_cur_state(cdev, target); + cdev->updated = true; +} + +static void thermal_zone_do_update(struct thermal_zone_device *tz) +{ + struct thermal_instance *instance; + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) + thermal_cdev_do_update(instance->cdev); +} + +/* + * Cooling algorithm for both active and passive cooling + * + * 1. if the temperature is higher than a trip point, + * a. if the trend is THERMAL_TREND_RAISING, use higher cooling + * state for this trip point + * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling + * state for this trip point + * + * 2. if the temperature is lower than a trip point, use lower + * cooling state for this trip point + * + * Note that this behaves the same as the previous passive cooling + * algorithm. + */ + +static void thermal_zone_trip_update(struct thermal_zone_device *tz, + int trip, long temp) +{ + struct thermal_instance *instance; + struct thermal_cooling_device *cdev = NULL; + unsigned long cur_state, max_state; + long trip_temp; + enum thermal_trip_type trip_type; + enum thermal_trend trend; + + if (trip == THERMAL_TRIPS_NONE) { + trip_temp = tz->forced_passive; + trip_type = THERMAL_TRIPS_NONE; + } else { + tz->ops->get_trip_temp(tz, trip, &trip_temp); + tz->ops->get_trip_type(tz, trip, &trip_type); + } + + if (!tz->ops->get_trend || tz->ops->get_trend(tz, trip, &trend)) { + /* + * compare the current temperature and previous temperature + * to get the thermal trend, if no special requirement + */ + if (tz->temperature > tz->last_temperature) + trend = THERMAL_TREND_RAISING; + else if (tz->temperature < tz->last_temperature) + trend = THERMAL_TREND_DROPPING; + else + trend = THERMAL_TREND_STABLE; + } + + if (temp >= trip_temp) { + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if (instance->trip != trip) + continue; + + cdev = instance->cdev; + + cdev->ops->get_cur_state(cdev, &cur_state); + cdev->ops->get_max_state(cdev, &max_state); + + if (trend == THERMAL_TREND_RAISING) { + cur_state = cur_state < instance->upper ? + (cur_state + 1) : instance->upper; + } else if (trend == THERMAL_TREND_DROPPING) { + cur_state = cur_state > instance->lower ? + (cur_state - 1) : instance->lower; + } + + /* activate a passive thermal instance */ + if ((trip_type == THERMAL_TRIP_PASSIVE || + trip_type == THERMAL_TRIPS_NONE) && + instance->target == THERMAL_NO_TARGET) + tz->passive++; + + instance->target = cur_state; + cdev->updated = false; /* cooling device needs update */ + } + } else { /* below trip */ + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if (instance->trip != trip) + continue; + + /* Do not use the inactive thermal instance */ + if (instance->target == THERMAL_NO_TARGET) + continue; + cdev = instance->cdev; + cdev->ops->get_cur_state(cdev, &cur_state); + + cur_state = cur_state > instance->lower ? + (cur_state - 1) : THERMAL_NO_TARGET; + + /* deactivate a passive thermal instance */ + if ((trip_type == THERMAL_TRIP_PASSIVE || + trip_type == THERMAL_TRIPS_NONE) && + cur_state == THERMAL_NO_TARGET) + tz->passive--; + instance->target = cur_state; + cdev->updated = false; /* cooling device needs update */ + } + } + + return; +} /** * thermal_zone_device_update - force an update of a thermal zone's state * @ttz: the thermal zone to update @@ -1054,8 +1151,6 @@ 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); @@ -1065,6 +1160,9 @@ void thermal_zone_device_update(struct thermal_zone_device *tz) goto leave; } + tz->last_temperature = tz->temperature; + tz->temperature = temp; + 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); @@ -1088,32 +1186,18 @@ void thermal_zone_device_update(struct thermal_zone_device *tz) 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); - } + thermal_zone_trip_update(tz, count, temp); break; case THERMAL_TRIP_PASSIVE: if (temp >= trip_temp || tz->passive) - thermal_zone_device_passive(tz, temp, - trip_temp, count); + thermal_zone_trip_update(tz, count, temp); break; } } if (tz->forced_passive) - thermal_zone_device_passive(tz, temp, tz->forced_passive, - THERMAL_TRIPS_NONE); - - tz->last_temperature = temp; + thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE, temp); + thermal_zone_do_update(tz); leave: if (tz->passive) @@ -1236,8 +1320,6 @@ static void remove_trip_attrs(struct thermal_zone_device *tz) * @mask: 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 @@ -1245,13 +1327,12 @@ static void remove_trip_attrs(struct thermal_zone_device *tz) * 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. + * longer needed. The passive cooling depends on the .get_trend() return value. */ struct thermal_zone_device *thermal_zone_device_register(const char *type, int trips, int mask, void *devdata, const struct thermal_zone_device_ops *ops, - int tc1, int tc2, int passive_delay, int polling_delay) + int passive_delay, int polling_delay) { struct thermal_zone_device *tz; struct thermal_cooling_device *pos; @@ -1260,7 +1341,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, int count; int passive = 0; - if (strlen(type) >= THERMAL_NAME_LENGTH) + if (type && strlen(type) >= THERMAL_NAME_LENGTH) return ERR_PTR(-EINVAL); if (trips > THERMAL_MAX_TRIPS || trips < 0 || mask >> trips) @@ -1273,7 +1354,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, if (!tz) return ERR_PTR(-ENOMEM); - INIT_LIST_HEAD(&tz->cooling_devices); + INIT_LIST_HEAD(&tz->thermal_instances); idr_init(&tz->idr); mutex_init(&tz->lock); result = get_idr(&thermal_tz_idr, &thermal_idr_lock, &tz->id); @@ -1282,13 +1363,11 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, return ERR_PTR(result); } - strcpy(tz->type, type); + 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; diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h new file mode 100644 index 00000000000..851530128e6 --- /dev/null +++ b/include/linux/cpu_cooling.h @@ -0,0 +1,58 @@ +/* + * linux/include/linux/cpu_cooling.h + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com) + * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#ifndef __CPU_COOLING_H__ +#define __CPU_COOLING_H__ + +#include <linux/thermal.h> + +#define CPUFREQ_COOLING_START 0 +#define CPUFREQ_COOLING_STOP 1 + +#ifdef CONFIG_CPU_THERMAL +/** + * cpufreq_cooling_register - function to create cpufreq cooling device. + * @clip_cpus: cpumask of cpus where the frequency constraints will happen + */ +struct thermal_cooling_device *cpufreq_cooling_register( + struct cpumask *clip_cpus); + +/** + * cpufreq_cooling_unregister - function to remove cpufreq cooling device. + * @cdev: thermal cooling device pointer. + */ +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev); +#else /* !CONFIG_CPU_THERMAL */ +static inline struct thermal_cooling_device *cpufreq_cooling_register( + struct cpumask *clip_cpus) +{ + return NULL; +} +static inline void cpufreq_cooling_unregister( + struct thermal_cooling_device *cdev) +{ + return; +} +#endif /* CONFIG_CPU_THERMAL */ + +#endif /* __CPU_COOLING_H__ */ diff --git a/include/linux/platform_data/exynos4_tmu.h b/include/linux/platform_data/exynos_thermal.h index 39e038cca59..a7bdb2f63b7 100644 --- a/include/linux/platform_data/exynos4_tmu.h +++ b/include/linux/platform_data/exynos_thermal.h @@ -1,5 +1,5 @@ /* - * exynos4_tmu.h - Samsung EXYNOS4 TMU (Thermal Management Unit) + * exynos_thermal.h - Samsung EXYNOS TMU (Thermal Management Unit) * * Copyright (C) 2011 Samsung Electronics * Donggeun Kim <dg77.kim@samsung.com> @@ -19,8 +19,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef _LINUX_EXYNOS4_TMU_H -#define _LINUX_EXYNOS4_TMU_H +#ifndef _LINUX_EXYNOS_THERMAL_H +#define _LINUX_EXYNOS_THERMAL_H +#include <linux/cpu_cooling.h> enum calibration_type { TYPE_ONE_POINT_TRIMMING, @@ -28,8 +29,28 @@ enum calibration_type { TYPE_NONE, }; +enum soc_type { + SOC_ARCH_EXYNOS4210 = 1, + SOC_ARCH_EXYNOS, +}; +/** + * struct freq_clip_table + * @freq_clip_max: maximum frequency allowed for this cooling state. + * @temp_level: Temperature level at which the temperature clipping will + * happen. + * @mask_val: cpumask of the allowed cpu's where the clipping will take place. + * + * This structure is required to be filled and passed to the + * cpufreq_cooling_unregister function. + */ +struct freq_clip_table { + unsigned int freq_clip_max; + unsigned int temp_level; + const struct cpumask *mask_val; +}; + /** - * struct exynos4_tmu_platform_data + * struct exynos_tmu_platform_data * @threshold: basic temperature for generating interrupt * 25 <= threshold <= 125 [unit: degree Celsius] * @trigger_levels: array for each interrupt levels @@ -63,11 +84,18 @@ enum calibration_type { * @reference_voltage: reference voltage of amplifier * in the positive-TC generator block * 0 <= reference_voltage <= 31 + * @noise_cancel_mode: noise cancellation mode + * 000, 100, 101, 110 and 111 can be different modes + * @type: determines the type of SOC + * @efuse_value: platform defined fuse value * @cal_type: calibration type for temperature + * @freq_clip_table: Table representing frequency reduction percentage. + * @freq_tab_count: Count of the above table as frequency reduction may + * applicable to only some of the trigger levels. * - * This structure is required for configuration of exynos4_tmu driver. + * This structure is required for configuration of exynos_tmu driver. */ -struct exynos4_tmu_platform_data { +struct exynos_tmu_platform_data { u8 threshold; u8 trigger_levels[4]; bool trigger_level0_en; @@ -77,7 +105,12 @@ struct exynos4_tmu_platform_data { u8 gain; u8 reference_voltage; + u8 noise_cancel_mode; + u32 efuse_value; enum calibration_type cal_type; + enum soc_type type; + struct freq_clip_table freq_tab[4]; + unsigned int freq_tab_count; }; -#endif /* _LINUX_EXYNOS4_TMU_H */ +#endif /* _LINUX_EXYNOS_THERMAL_H */ diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 4b94a61955d..91b34812cd8 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -44,6 +44,12 @@ enum thermal_trip_type { THERMAL_TRIP_CRITICAL, }; +enum thermal_trend { + THERMAL_TREND_STABLE, /* temperature is stable */ + THERMAL_TREND_RAISING, /* temperature is raising */ + THERMAL_TREND_DROPPING, /* temperature is dropping */ +}; + struct thermal_zone_device_ops { int (*bind) (struct thermal_zone_device *, struct thermal_cooling_device *); @@ -65,6 +71,8 @@ struct thermal_zone_device_ops { int (*set_trip_hyst) (struct thermal_zone_device *, int, unsigned long); int (*get_crit_temp) (struct thermal_zone_device *, unsigned long *); + int (*get_trend) (struct thermal_zone_device *, int, + enum thermal_trend *); int (*notify) (struct thermal_zone_device *, int, enum thermal_trip_type); }; @@ -75,6 +83,8 @@ struct thermal_cooling_device_ops { int (*set_cur_state) (struct thermal_cooling_device *, unsigned long); }; +#define THERMAL_NO_LIMIT -1UL /* no upper/lower limit requirement */ + #define THERMAL_TRIPS_NONE -1 #define THERMAL_MAX_TRIPS 12 #define THERMAL_NAME_LENGTH 20 @@ -84,6 +94,9 @@ struct thermal_cooling_device { struct device device; void *devdata; const struct thermal_cooling_device_ops *ops; + bool updated; /* true if the cooling device does not need update */ + struct mutex lock; /* protect thermal_instances list */ + struct list_head thermal_instances; struct list_head node; }; @@ -105,17 +118,16 @@ struct thermal_zone_device { struct thermal_attr *trip_hyst_attrs; void *devdata; int trips; - int tc1; - int tc2; int passive_delay; int polling_delay; + int temperature; int last_temperature; - bool passive; + int passive; unsigned int forced_passive; const struct thermal_zone_device_ops *ops; - struct list_head cooling_devices; + struct list_head thermal_instances; struct idr idr; - struct mutex lock; /* protect cooling devices list */ + struct mutex lock; /* protect thermal_instances list */ struct list_head node; struct delayed_work poll_queue; }; @@ -152,12 +164,12 @@ enum { #define THERMAL_GENL_CMD_MAX (__THERMAL_GENL_CMD_MAX - 1) struct thermal_zone_device *thermal_zone_device_register(const char *, int, int, - void *, const struct thermal_zone_device_ops *, int tc1, - int tc2, int passive_freq, int polling_freq); + void *, const struct thermal_zone_device_ops *, int, int); void thermal_zone_device_unregister(struct thermal_zone_device *); int thermal_zone_bind_cooling_device(struct thermal_zone_device *, int, - struct thermal_cooling_device *); + struct thermal_cooling_device *, + unsigned long, unsigned long); int thermal_zone_unbind_cooling_device(struct thermal_zone_device *, int, struct thermal_cooling_device *); void thermal_zone_device_update(struct thermal_zone_device *); |