diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/thermal/Kconfig | 33 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 6 | ||||
-rw-r--r-- | drivers/thermal/cpu_cooling.c | 8 | ||||
-rw-r--r-- | drivers/thermal/exynos_thermal.c | 1059 | ||||
-rw-r--r-- | drivers/thermal/imx_thermal.c | 541 | ||||
-rw-r--r-- | drivers/thermal/samsung/Kconfig | 18 | ||||
-rw-r--r-- | drivers/thermal/samsung/Makefile | 7 | ||||
-rw-r--r-- | drivers/thermal/samsung/exynos_thermal_common.c | 432 | ||||
-rw-r--r-- | drivers/thermal/samsung/exynos_thermal_common.h | 107 | ||||
-rw-r--r-- | drivers/thermal/samsung/exynos_tmu.c | 762 | ||||
-rw-r--r-- | drivers/thermal/samsung/exynos_tmu.h | 311 | ||||
-rw-r--r-- | drivers/thermal/samsung/exynos_tmu_data.c | 250 | ||||
-rw-r--r-- | drivers/thermal/samsung/exynos_tmu_data.h | 155 | ||||
-rw-r--r-- | drivers/thermal/step_wise.c | 32 | ||||
-rw-r--r-- | drivers/thermal/thermal_core.c | 282 | ||||
-rw-r--r-- | drivers/thermal/thermal_hwmon.c | 269 | ||||
-rw-r--r-- | drivers/thermal/thermal_hwmon.h | 49 | ||||
-rw-r--r-- | drivers/thermal/ti-soc-thermal/dra752-thermal-data.c | 5 | ||||
-rw-r--r-- | drivers/thermal/ti-soc-thermal/ti-bandgap.c | 6 | ||||
-rw-r--r-- | drivers/thermal/ti-soc-thermal/ti-thermal-common.c | 7 |
20 files changed, 2997 insertions, 1342 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e988c81d763c..dbfc390330ac 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -17,8 +17,17 @@ if THERMAL config THERMAL_HWMON bool + prompt "Expose thermal sensors as hwmon device" depends on HWMON=y || HWMON=THERMAL default y + help + In case a sensor is registered with the thermal + framework, this option will also register it + as a hwmon. The sensor will then have the common + hwmon sysfs interface. + + Say 'Y' here if you want all thermal sensors to + have hwmon sysfs interface too. choice prompt "Default Thermal governor" @@ -91,6 +100,17 @@ config THERMAL_EMULATION because userland can easily disable the thermal policy by simply flooding this sysfs node with low temperature values. +config IMX_THERMAL + tristate "Temperature sensor driver for Freescale i.MX SoCs" + depends on CPU_THERMAL + depends on MFD_SYSCON + depends on OF + help + Support for Temperature Monitor (TEMPMON) found on Freescale i.MX SoCs. + It supports one critical trip point and one passive trip point. The + cpufreq is used as the cooling device to throttle CPUs when the + passive trip is crossed. + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" depends on PLAT_SPEAR @@ -114,14 +134,6 @@ config KIRKWOOD_THERMAL Support for the Kirkwood thermal sensor driver into the Linux thermal framework. Only kirkwood 88F6282 and 88F6283 have this sensor. -config EXYNOS_THERMAL - tristate "Temperature sensor on Samsung EXYNOS" - depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) - depends on CPU_THERMAL - help - If you say yes here you get support for TMU (Thermal Management - Unit) on SAMSUNG EXYNOS series of SoC. - config DOVE_THERMAL tristate "Temperature sensor on Marvell Dove SoCs" depends on ARCH_DOVE @@ -184,4 +196,9 @@ menu "Texas Instruments thermal drivers" source "drivers/thermal/ti-soc-thermal/Kconfig" endmenu +menu "Samsung thermal drivers" +depends on PLAT_SAMSUNG +source "drivers/thermal/samsung/Kconfig" +endmenu + endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 67184a293e3f..584b36319d51 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -5,6 +5,9 @@ obj-$(CONFIG_THERMAL) += thermal_sys.o thermal_sys-y += thermal_core.o +# interface to/from other layers providing sensors +thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o + # governors thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o @@ -17,10 +20,11 @@ thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o -obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-y += samsung/ obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o +obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 82e15dbb3ac7..d17902886c3f 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -322,6 +322,8 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, if (cpumask_test_cpu(policy->cpu, ¬ify_device->allowed_cpus)) max_freq = notify_device->cpufreq_val; + else + return 0; /* Never exceed user_policy.max */ if (max_freq > policy->user_policy.max) @@ -496,8 +498,12 @@ EXPORT_SYMBOL_GPL(cpufreq_cooling_register); */ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) { - struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata; + struct cpufreq_cooling_device *cpufreq_dev; + + if (!cdev) + return; + cpufreq_dev = cdev->devdata; mutex_lock(&cooling_cpufreq_lock); cpufreq_dev_count--; diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c deleted file mode 100644 index 9af4b93c9f86..000000000000 --- a/drivers/thermal/exynos_thermal.c +++ /dev/null @@ -1,1059 +0,0 @@ -/* - * 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> - -/* 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 << 12) -#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 MAX_THRESHOLD_LEVS 4 - -#define ACTIVE_INTERVAL 500 -#define IDLE_INTERVAL 10000 -#define MCELSIUS 1000 - -#ifdef CONFIG_THERMAL_EMULATION -#define EXYNOS_EMUL_TIME 0x57F0 -#define EXYNOS_EMUL_TIME_SHIFT 16 -#define EXYNOS_EMUL_DATA_SHIFT 8 -#define EXYNOS_EMUL_DATA_MASK 0xFF -#define EXYNOS_EMUL_ENABLE 0x1 -#endif /* CONFIG_THERMAL_EMULATION */ - -/* 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; - u8 trigger_falling; -}; - -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); - int (*write_emul_temp)(void *drv_data, unsigned long temp); - 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->sensor_conf->trip_data.trigger_falling) - 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; -} - -/* 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 = cpufreq_cooling_get_level(0, clip_data->freq_clip_max); - if (level == THERMAL_CSTATE_INVALID) - return 0; - switch (GET_ZONE(i)) { - case MONITOR_ZONE: - case WARN_ZONE: - if (thermal_zone_bind_cooling_device(thermal, i, cdev, - level, 0)) { - 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 temperature callback functions for thermal zone */ -static int exynos_set_emul_temp(struct thermal_zone_device *thermal, - unsigned long temp) -{ - void *data; - int ret = -EINVAL; - - if (!th_zone->sensor_conf) { - pr_info("Temperature sensor not initialised\n"); - return -EINVAL; - } - data = th_zone->sensor_conf->private_data; - if (th_zone->sensor_conf->write_emul_temp) - ret = th_zone->sensor_conf->write_emul_temp(data, temp); - return ret; -} - -/* Get the temperature trend */ -static int exynos_get_trend(struct thermal_zone_device *thermal, - int trip, enum thermal_trend *trend) -{ - int ret; - unsigned long trip_temp; - - ret = exynos_get_trip_temp(thermal, trip, &trip_temp); - if (ret < 0) - return ret; - - if (thermal->temperature >= trip_temp) - *trend = THERMAL_TREND_RAISE_FULL; - else - *trend = THERMAL_TREND_DROP_FULL; - - 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, - .set_emul_temp = exynos_set_emul_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 && - !th_zone->sensor_conf->trip_data.trigger_falling) { - 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, NULL, 0, - sensor_conf->trip_data.trigger_falling ? - 0 : IDLE_INTERVAL); - - if (IS_ERR(th_zone->therm_dev)) { - pr_err("Failed to register thermal zone device\n"); - ret = PTR_ERR(th_zone->therm_dev); - 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; - unsigned int rising_threshold = 0, falling_threshold = 0; - int ret = 0, threshold_code, i, trigger_levs = 0; - - 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; - - /* Count trigger levels to be enabled */ - for (i = 0; i < MAX_THRESHOLD_LEVS; i++) - if (pdata->trigger_levels[i]) - trigger_levs++; - - 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); - for (i = 0; i < trigger_levs; i++) - writeb(pdata->trigger_levels[i], - data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4); - - writel(EXYNOS4210_TMU_INTCLEAR_VAL, - data->base + EXYNOS_TMU_REG_INTCLEAR); - } else if (data->soc == SOC_ARCH_EXYNOS) { - /* Write temperature code for rising and falling threshold */ - for (i = 0; i < trigger_levs; i++) { - threshold_code = temp_to_code(data, - pdata->trigger_levels[i]); - if (threshold_code < 0) { - ret = threshold_code; - goto out; - } - rising_threshold |= threshold_code << 8 * i; - if (pdata->threshold_falling) { - threshold_code = temp_to_code(data, - pdata->trigger_levels[i] - - pdata->threshold_falling); - if (threshold_code > 0) - falling_threshold |= - threshold_code << 8 * i; - } - } - - writel(rising_threshold, - data->base + EXYNOS_THD_TEMP_RISE); - writel(falling_threshold, - 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; - if (pdata->threshold_falling) - interrupt_en |= interrupt_en << 16; - } 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; -} - -#ifdef CONFIG_THERMAL_EMULATION -static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) -{ - struct exynos_tmu_data *data = drv_data; - unsigned int reg; - int ret = -EINVAL; - - if (data->soc == SOC_ARCH_EXYNOS4210) - goto out; - - if (temp && temp < MCELSIUS) - goto out; - - mutex_lock(&data->lock); - clk_enable(data->clk); - - reg = readl(data->base + EXYNOS_EMUL_CON); - - if (temp) { - temp /= MCELSIUS; - - reg = (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT) | - (temp_to_code(data, temp) - << EXYNOS_EMUL_DATA_SHIFT) | EXYNOS_EMUL_ENABLE; - } else { - reg &= ~EXYNOS_EMUL_ENABLE; - } - - writel(reg, data->base + EXYNOS_EMUL_CON); - - clk_disable(data->clk); - mutex_unlock(&data->lock); - return 0; -out: - return ret; -} -#else -static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) - { return -EINVAL; } -#endif/*CONFIG_THERMAL_EMULATION*/ - -static void exynos_tmu_work(struct work_struct *work) -{ - struct exynos_tmu_data *data = container_of(work, - struct exynos_tmu_data, irq_work); - - exynos_report_trigger(); - mutex_lock(&data->lock); - clk_enable(data->clk); - if (data->soc == SOC_ARCH_EXYNOS) - writel(EXYNOS_TMU_CLEAR_RISE_INT | - EXYNOS_TMU_CLEAR_FALL_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); - - 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, - .write_emul_temp = exynos_tmu_set_emulation, -}; - -#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) || \ - defined(CONFIG_SOC_EXYNOS4212) -static struct exynos_tmu_platform_data const exynos_default_tmu_data = { - .threshold_falling = 10, - .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,exynos4412-tmu", - .data = (void *)EXYNOS_TMU_DRV_DATA, - }, - { - .compatible = "samsung,exynos5250-tmu", - .data = (void *)EXYNOS_TMU_DRV_DATA, - }, - {}, -}; -MODULE_DEVICE_TABLE(of, exynos_tmu_match); -#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, exynos_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 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); - data->base = devm_ioremap_resource(&pdev->dev, data->mem); - if (IS_ERR(data->base)) - return PTR_ERR(data->base); - - 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 = devm_clk_get(&pdev->dev, "tmu_apbif"); - if (IS_ERR(data->clk)) { - dev_err(&pdev->dev, "Failed to get clock\n"); - return PTR_ERR(data->clk); - } - - ret = clk_prepare(data->clk); - if (ret) - return ret; - - 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.trip_data.trigger_falling = pdata->threshold_falling; - - 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: - clk_unprepare(data->clk); - return ret; -} - -static int 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_unprepare(data->clk); - - 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 = of_match_ptr(exynos_tmu_match), - }, - .probe = exynos_tmu_probe, - .remove = 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/imx_thermal.c b/drivers/thermal/imx_thermal.c new file mode 100644 index 000000000000..1d6c801c1eb9 --- /dev/null +++ b/drivers/thermal/imx_thermal.c @@ -0,0 +1,541 @@ +/* + * Copyright 2013 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/thermal.h> +#include <linux/types.h> + +#define REG_SET 0x4 +#define REG_CLR 0x8 +#define REG_TOG 0xc + +#define MISC0 0x0150 +#define MISC0_REFTOP_SELBIASOFF (1 << 3) + +#define TEMPSENSE0 0x0180 +#define TEMPSENSE0_ALARM_VALUE_SHIFT 20 +#define TEMPSENSE0_ALARM_VALUE_MASK (0xfff << TEMPSENSE0_ALARM_VALUE_SHIFT) +#define TEMPSENSE0_TEMP_CNT_SHIFT 8 +#define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT) +#define TEMPSENSE0_FINISHED (1 << 2) +#define TEMPSENSE0_MEASURE_TEMP (1 << 1) +#define TEMPSENSE0_POWER_DOWN (1 << 0) + +#define TEMPSENSE1 0x0190 +#define TEMPSENSE1_MEASURE_FREQ 0xffff + +#define OCOTP_ANA1 0x04e0 + +/* The driver supports 1 passive trip point and 1 critical trip point */ +enum imx_thermal_trip { + IMX_TRIP_PASSIVE, + IMX_TRIP_CRITICAL, + IMX_TRIP_NUM, +}; + +/* + * It defines the temperature in millicelsius for passive trip point + * that will trigger cooling action when crossed. + */ +#define IMX_TEMP_PASSIVE 85000 + +#define IMX_POLLING_DELAY 2000 /* millisecond */ +#define IMX_PASSIVE_DELAY 1000 + +struct imx_thermal_data { + struct thermal_zone_device *tz; + struct thermal_cooling_device *cdev; + enum thermal_device_mode mode; + struct regmap *tempmon; + int c1, c2; /* See formula in imx_get_sensor_data() */ + unsigned long temp_passive; + unsigned long temp_critical; + unsigned long alarm_temp; + unsigned long last_temp; + bool irq_enabled; + int irq; +}; + +static void imx_set_alarm_temp(struct imx_thermal_data *data, + signed long alarm_temp) +{ + struct regmap *map = data->tempmon; + int alarm_value; + + data->alarm_temp = alarm_temp; + alarm_value = (alarm_temp - data->c2) / data->c1; + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_ALARM_VALUE_MASK); + regmap_write(map, TEMPSENSE0 + REG_SET, alarm_value << + TEMPSENSE0_ALARM_VALUE_SHIFT); +} + +static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp) +{ + struct imx_thermal_data *data = tz->devdata; + struct regmap *map = data->tempmon; + unsigned int n_meas; + bool wait; + u32 val; + + if (data->mode == THERMAL_DEVICE_ENABLED) { + /* Check if a measurement is currently in progress */ + regmap_read(map, TEMPSENSE0, &val); + wait = !(val & TEMPSENSE0_FINISHED); + } else { + /* + * Every time we measure the temperature, we will power on the + * temperature sensor, enable measurements, take a reading, + * disable measurements, power off the temperature sensor. + */ + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + + wait = true; + } + + /* + * According to the temp sensor designers, it may require up to ~17us + * to complete a measurement. + */ + if (wait) + usleep_range(20, 50); + + regmap_read(map, TEMPSENSE0, &val); + + if (data->mode != THERMAL_DEVICE_ENABLED) { + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + } + + if ((val & TEMPSENSE0_FINISHED) == 0) { + dev_dbg(&tz->device, "temp measurement never finished\n"); + return -EAGAIN; + } + + n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT; + + /* See imx_get_sensor_data() for formula derivation */ + *temp = data->c2 + data->c1 * n_meas; + + /* Update alarm value to next higher trip point */ + if (data->alarm_temp == data->temp_passive && *temp >= data->temp_passive) + imx_set_alarm_temp(data, data->temp_critical); + if (data->alarm_temp == data->temp_critical && *temp < data->temp_passive) { + imx_set_alarm_temp(data, data->temp_passive); + dev_dbg(&tz->device, "thermal alarm off: T < %lu\n", + data->alarm_temp / 1000); + } + + if (*temp != data->last_temp) { + dev_dbg(&tz->device, "millicelsius: %ld\n", *temp); + data->last_temp = *temp; + } + + /* Reenable alarm IRQ if temperature below alarm temperature */ + if (!data->irq_enabled && *temp < data->alarm_temp) { + data->irq_enabled = true; + enable_irq(data->irq); + } + + return 0; +} + +static int imx_get_mode(struct thermal_zone_device *tz, + enum thermal_device_mode *mode) +{ + struct imx_thermal_data *data = tz->devdata; + + *mode = data->mode; + + return 0; +} + +static int imx_set_mode(struct thermal_zone_device *tz, + enum thermal_device_mode mode) +{ + struct imx_thermal_data *data = tz->devdata; + struct regmap *map = data->tempmon; + + if (mode == THERMAL_DEVICE_ENABLED) { + tz->polling_delay = IMX_POLLING_DELAY; + tz->passive_delay = IMX_PASSIVE_DELAY; + + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + + if (!data->irq_enabled) { + data->irq_enabled = true; + enable_irq(data->irq); + } + } else { + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + + tz->polling_delay = 0; + tz->passive_delay = 0; + + if (data->irq_enabled) { + disable_irq(data->irq); + data->irq_enabled = false; + } + } + + data->mode = mode; + thermal_zone_device_update(tz); + + return 0; +} + +static int imx_get_trip_type(struct thermal_zone_device *tz, int trip, + enum thermal_trip_type *type) +{ + *type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE : + THERMAL_TRIP_CRITICAL; + return 0; +} + +static int imx_get_crit_temp(struct thermal_zone_device *tz, + unsigned long *temp) +{ + struct imx_thermal_data *data = tz->devdata; + + *temp = data->temp_critical; + return 0; +} + +static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip, + unsigned long *temp) +{ + struct imx_thermal_data *data = tz->devdata; + + *temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive : + data->temp_critical; + return 0; +} + +static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip, + unsigned long temp) +{ + struct imx_thermal_data *data = tz->devdata; + + if (trip == IMX_TRIP_CRITICAL) + return -EPERM; + + if (temp > IMX_TEMP_PASSIVE) + return -EINVAL; + + data->temp_passive = temp; + + imx_set_alarm_temp(data, temp); + + return 0; +} + +static int imx_bind(struct thermal_zone_device *tz, + struct thermal_cooling_device *cdev) +{ + int ret; + + ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev, + THERMAL_NO_LIMIT, + THERMAL_NO_LIMIT); + if (ret) { + dev_err(&tz->device, + "binding zone %s with cdev %s failed:%d\n", + tz->type, cdev->type, ret); + return ret; + } + + return 0; +} + +static int imx_unbind(struct thermal_zone_device *tz, + struct thermal_cooling_device *cdev) +{ + int ret; + + ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev); + if (ret) { + dev_err(&tz->device, + "unbinding zone %s with cdev %s failed:%d\n", + tz->type, cdev->type, ret); + return ret; + } + + return 0; +} + +static const struct thermal_zone_device_ops imx_tz_ops = { + .bind = imx_bind, + .unbind = imx_unbind, + .get_temp = imx_get_temp, + .get_mode = imx_get_mode, + .set_mode = imx_set_mode, + .get_trip_type = imx_get_trip_type, + .get_trip_temp = imx_get_trip_temp, + .get_crit_temp = imx_get_crit_temp, + .set_trip_temp = imx_set_trip_temp, +}; + +static int imx_get_sensor_data(struct platform_device *pdev) +{ + struct imx_thermal_data *data = platform_get_drvdata(pdev); + struct regmap *map; + int t1, t2, n1, n2; + int ret; + u32 val; + + map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "fsl,tempmon-data"); + if (IS_ERR(map)) { + ret = PTR_ERR(map); + dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); + return ret; + } + + ret = regmap_read(map, OCOTP_ANA1, &val); + if (ret) { + dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); + return ret; + } + + if (val == 0 || val == ~0) { + dev_err(&pdev->dev, "invalid sensor calibration data\n"); + return -EINVAL; + } + + /* + * Sensor data layout: + * [31:20] - sensor value @ 25C + * [19:8] - sensor value of hot + * [7:0] - hot temperature value + */ + n1 = val >> 20; + n2 = (val & 0xfff00) >> 8; + t2 = val & 0xff; + t1 = 25; /* t1 always 25C */ + + /* + * Derived from linear interpolation, + * Tmeas = T2 + (Nmeas - N2) * (T1 - T2) / (N1 - N2) + * We want to reduce this down to the minimum computation necessary + * for each temperature read. Also, we want Tmeas in millicelsius + * and we don't want to lose precision from integer division. So... + * milli_Tmeas = 1000 * T2 + 1000 * (Nmeas - N2) * (T1 - T2) / (N1 - N2) + * Let constant c1 = 1000 * (T1 - T2) / (N1 - N2) + * milli_Tmeas = (1000 * T2) + c1 * (Nmeas - N2) + * milli_Tmeas = (1000 * T2) + (c1 * Nmeas) - (c1 * N2) + * Let constant c2 = (1000 * T2) - (c1 * N2) + * milli_Tmeas = c2 + (c1 * Nmeas) + */ + data->c1 = 1000 * (t1 - t2) / (n1 - n2); + data->c2 = 1000 * t2 - data->c1 * n2; + + /* + * Set the default passive cooling trip point to 20 °C below the + * maximum die temperature. Can be changed from userspace. + */ + data->temp_passive = 1000 * (t2 - 20); + + /* + * The maximum die temperature is t2, let's give 5 °C cushion + * for noise and possible temperature rise between measurements. + */ + data->temp_critical = 1000 * (t2 - 5); + + return 0; +} + +static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev) +{ + struct imx_thermal_data *data = dev; + + disable_irq_nosync(irq); + data->irq_enabled = false; + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) +{ + struct imx_thermal_data *data = dev; + + dev_dbg(&data->tz->device, "THERMAL ALARM: T > %lu\n", + data->alarm_temp / 1000); + + thermal_zone_device_update(data->tz); + + return IRQ_HANDLED; +} + +static int imx_thermal_probe(struct platform_device *pdev) +{ + struct imx_thermal_data *data; + struct cpumask clip_cpus; + struct regmap *map; + int measure_freq; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon"); + if (IS_ERR(map)) { + ret = PTR_ERR(map); + dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret); + return ret; + } + data->tempmon = map; + + data->irq = platform_get_irq(pdev, 0); + if (data->irq < 0) + return data->irq; + + ret = devm_request_threaded_irq(&pdev->dev, data->irq, + imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread, + 0, "imx_thermal", data); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, data); + + ret = imx_get_sensor_data(pdev); + if (ret) { + dev_err(&pdev->dev, "failed to get sensor data\n"); + return ret; + } + + /* Make sure sensor is in known good state for measurements */ + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); + regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); + regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF); + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + + cpumask_set_cpu(0, &clip_cpus); + data->cdev = cpufreq_cooling_register(&clip_cpus); + if (IS_ERR(data->cdev)) { + ret = PTR_ERR(data->cdev); + dev_err(&pdev->dev, + "failed to register cpufreq cooling device: %d\n", ret); + return ret; + } + + data->tz = thermal_zone_device_register("imx_thermal_zone", + IMX_TRIP_NUM, + BIT(IMX_TRIP_PASSIVE), data, + &imx_tz_ops, NULL, + IMX_PASSIVE_DELAY, + IMX_POLLING_DELAY); + if (IS_ERR(data->tz)) { + ret = PTR_ERR(data->tz); + dev_err(&pdev->dev, + "failed to register thermal zone device %d\n", ret); + cpufreq_cooling_unregister(data->cdev); + return ret; + } + + /* Enable measurements at ~ 10 Hz */ + regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); + measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */ + regmap_write(map, TEMPSENSE1 + REG_SET, measure_freq); + imx_set_alarm_temp(data, data->temp_passive); + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + + data->irq_enabled = true; + data->mode = THERMAL_DEVICE_ENABLED; + + return 0; +} + +static int imx_thermal_remove(struct platform_device *pdev) +{ + struct imx_thermal_data *data = platform_get_drvdata(pdev); + struct regmap *map = data->tempmon; + + /* Disable measurements */ + regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + + thermal_zone_device_unregister(data->tz); + cpufreq_cooling_unregister(data->cdev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx_thermal_suspend(struct device *dev) +{ + struct imx_thermal_data *data = dev_get_drvdata(dev); + struct regmap *map = data->tempmon; + u32 val; + + regmap_read(map, TEMPSENSE0, &val); + if ((val & TEMPSENSE0_POWER_DOWN) == 0) { + /* + * If a measurement is taking place, wait for a long enough + * time for it to finish, and then check again. If it still + * does not finish, something must go wrong. + */ + udelay(50); + regmap_read(map, TEMPSENSE0, &val); + if ((val & TEMPSENSE0_POWER_DOWN) == 0) + return -ETIMEDOUT; + } + + return 0; +} + +static int imx_thermal_resume(struct device *dev) +{ + /* Nothing to do for now */ + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops, + imx_thermal_suspend, imx_thermal_resume); + +static const struct of_device_id of_imx_thermal_match[] = { + { .compatible = "fsl,imx6q-tempmon", }, + { /* end */ } +}; + +static struct platform_driver imx_thermal = { + .driver = { + .name = "imx_thermal", + .owner = THIS_MODULE, + .pm = &imx_thermal_pm_ops, + .of_match_table = of_imx_thermal_match, + }, + .probe = imx_thermal_probe, + .remove = imx_thermal_remove, +}; +module_platform_driver(imx_thermal); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-thermal"); diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig new file mode 100644 index 000000000000..f760389a204c --- /dev/null +++ b/drivers/thermal/samsung/Kconfig @@ -0,0 +1,18 @@ +config EXYNOS_THERMAL + tristate "Exynos thermal management unit driver" + depends on ARCH_HAS_BANDGAP && OF + help + If you say yes here you get support for the TMU (Thermal Management + Unit) driver for SAMSUNG EXYNOS series of SoCs. This driver initialises + the TMU, reports temperature and handles cooling action if defined. + This driver uses the Exynos core thermal APIs and TMU configuration + data from the supported SoCs. + +config EXYNOS_THERMAL_CORE + bool "Core thermal framework support for EXYNOS SOCs" + depends on EXYNOS_THERMAL + help + If you say yes here you get support for EXYNOS TMU + (Thermal Management Unit) common registration/unregistration + functions to the core thermal layer and also to use the generic + CPU cooling APIs. diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile new file mode 100644 index 000000000000..c09d83095dc2 --- /dev/null +++ b/drivers/thermal/samsung/Makefile @@ -0,0 +1,7 @@ +# +# Samsung thermal specific Makefile +# +obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +exynos_thermal-y := exynos_tmu.o +exynos_thermal-y += exynos_tmu_data.o +exynos_thermal-$(CONFIG_EXYNOS_THERMAL_CORE) += exynos_thermal_common.o diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c new file mode 100644 index 000000000000..f10a6ad37c06 --- /dev/null +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -0,0 +1,432 @@ +/* + * exynos_thermal_common.c - Samsung EXYNOS common thermal file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap <amit.daniel@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/cpu_cooling.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/thermal.h> + +#include "exynos_thermal_common.h" + +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; +}; + +/* Get mode callback functions for thermal zone */ +static int exynos_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct exynos_thermal_zone *th_zone = thermal->devdata; + 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) +{ + struct exynos_thermal_zone *th_zone = thermal->devdata; + if (!th_zone) { + dev_err(&thermal->device, + "thermal zone not registered\n"); + return 0; + } + + mutex_lock(&thermal->lock); + + if (mode == THERMAL_DEVICE_ENABLED && + !th_zone->sensor_conf->trip_data.trigger_falling) + thermal->polling_delay = IDLE_INTERVAL; + else + thermal->polling_delay = 0; + + mutex_unlock(&thermal->lock); + + th_zone->mode = mode; + thermal_zone_device_update(thermal); + dev_dbg(th_zone->sensor_conf->dev, + "thermal polling set for duration=%d msec\n", + thermal->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) +{ + struct exynos_thermal_zone *th_zone = thermal->devdata; + int max_trip = th_zone->sensor_conf->trip_data.trip_count; + int trip_type; + + if (trip < 0 || trip >= max_trip) + return -EINVAL; + + trip_type = th_zone->sensor_conf->trip_data.trip_type[trip]; + + if (trip_type == SW_TRIP) + *type = THERMAL_TRIP_CRITICAL; + else if (trip_type == THROTTLE_ACTIVE) + *type = THERMAL_TRIP_ACTIVE; + else if (trip_type == THROTTLE_PASSIVE) + *type = THERMAL_TRIP_PASSIVE; + else + 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) +{ + struct exynos_thermal_zone *th_zone = thermal->devdata; + int max_trip = th_zone->sensor_conf->trip_data.trip_count; + + if (trip < 0 || trip >= max_trip) + 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) +{ + struct exynos_thermal_zone *th_zone = thermal->devdata; + int max_trip = th_zone->sensor_conf->trip_data.trip_count; + /* Get the temp of highest trip*/ + return exynos_get_trip_temp(thermal, max_trip - 1, temp); +} + +/* 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 exynos_thermal_zone *th_zone = thermal->devdata; + 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 0; + + /* 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 = cpufreq_cooling_get_level(0, clip_data->freq_clip_max); + if (level == THERMAL_CSTATE_INVALID) + return 0; + switch (GET_ZONE(i)) { + case MONITOR_ZONE: + case WARN_ZONE: + if (thermal_zone_bind_cooling_device(thermal, i, cdev, + level, 0)) { + dev_err(data->dev, + "error unbinding 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 exynos_thermal_zone *th_zone = thermal->devdata; + 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 0; + + /* 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)) { + dev_err(data->dev, + "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) +{ + struct exynos_thermal_zone *th_zone = thermal->devdata; + void *data; + + if (!th_zone->sensor_conf) { + dev_err(&thermal->device, + "Temperature sensor not initialised\n"); + return -EINVAL; + } + data = th_zone->sensor_conf->driver_data; + *temp = th_zone->sensor_conf->read_temperature(data); + /* convert the temperature into millicelsius */ + *temp = *temp * MCELSIUS; + return 0; +} + +/* Get temperature callback functions for thermal zone */ +static int exynos_set_emul_temp(struct thermal_zone_device *thermal, + unsigned long temp) +{ + void *data; + int ret = -EINVAL; + struct exynos_thermal_zone *th_zone = thermal->devdata; + + if (!th_zone->sensor_conf) { + dev_err(&thermal->device, + "Temperature sensor not initialised\n"); + return -EINVAL; + } + data = th_zone->sensor_conf->driver_data; + if (th_zone->sensor_conf->write_emul_temp) + ret = th_zone->sensor_conf->write_emul_temp(data, temp); + return ret; +} + +/* Get the temperature trend */ +static int exynos_get_trend(struct thermal_zone_device *thermal, + int trip, enum thermal_trend *trend) +{ + int ret; + unsigned long trip_temp; + + ret = exynos_get_trip_temp(thermal, trip, &trip_temp); + if (ret < 0) + return ret; + + if (thermal->temperature >= trip_temp) + *trend = THERMAL_TREND_RAISE_FULL; + else + *trend = THERMAL_TREND_DROP_FULL; + + 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, + .set_emul_temp = exynos_set_emul_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. + */ +void exynos_report_trigger(struct thermal_sensor_conf *conf) +{ + unsigned int i; + char data[10]; + char *envp[] = { data, NULL }; + struct exynos_thermal_zone *th_zone; + + if (!conf || !conf->pzone_data) { + pr_err("Invalid temperature sensor configuration data\n"); + return; + } + + th_zone = conf->pzone_data; + if (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 && + !th_zone->sensor_conf->trip_data.trigger_falling) { + 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 */ +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) +{ + int ret; + struct cpumask mask_val; + struct exynos_thermal_zone *th_zone; + + if (!sensor_conf || !sensor_conf->read_temperature) { + pr_err("Temperature sensor not initialised\n"); + return -EINVAL; + } + + th_zone = devm_kzalloc(sensor_conf->dev, + sizeof(struct exynos_thermal_zone), GFP_KERNEL); + if (!th_zone) + return -ENOMEM; + + th_zone->sensor_conf = sensor_conf; + /* + * TODO: 1) Handle multiple cooling devices in a thermal zone + * 2) Add a flag/name in cooling info to map to specific + * sensor + */ + if (sensor_conf->cooling_data.freq_clip_count > 0) { + cpumask_set_cpu(0, &mask_val); + th_zone->cool_dev[th_zone->cool_dev_size] = + cpufreq_cooling_register(&mask_val); + if (IS_ERR(th_zone->cool_dev[th_zone->cool_dev_size])) { + dev_err(sensor_conf->dev, + "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, sensor_conf->trip_data.trip_count, + 0, th_zone, &exynos_dev_ops, NULL, 0, + sensor_conf->trip_data.trigger_falling ? 0 : + IDLE_INTERVAL); + + if (IS_ERR(th_zone->therm_dev)) { + dev_err(sensor_conf->dev, + "Failed to register thermal zone device\n"); + ret = PTR_ERR(th_zone->therm_dev); + goto err_unregister; + } + th_zone->mode = THERMAL_DEVICE_ENABLED; + sensor_conf->pzone_data = th_zone; + + dev_info(sensor_conf->dev, + "Exynos: Thermal zone(%s) registered\n", sensor_conf->name); + + return 0; + +err_unregister: + exynos_unregister_thermal(sensor_conf); + return ret; +} + +/* Un-Register with the in-kernel thermal management */ +void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) +{ + int i; + struct exynos_thermal_zone *th_zone; + + if (!sensor_conf || !sensor_conf->pzone_data) { + pr_err("Invalid temperature sensor configuration data\n"); + return; + } + + th_zone = sensor_conf->pzone_data; + + 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]); + } + + dev_info(sensor_conf->dev, + "Exynos: Kernel Thermal management unregistered\n"); +} diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h new file mode 100644 index 000000000000..3eb2ed9ea3a4 --- /dev/null +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -0,0 +1,107 @@ +/* + * exynos_thermal_common.h - Samsung EXYNOS common header file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap <amit.daniel@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 + * + */ + +#ifndef _EXYNOS_THERMAL_COMMON_H +#define _EXYNOS_THERMAL_COMMON_H + +/* In-kernel thermal framework related macros & definations */ +#define SENSOR_NAME_LEN 16 +#define MAX_TRIP_COUNT 8 +#define MAX_COOLING_DEVICE 4 +#define MAX_THRESHOLD_LEVS 5 + +#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) + +enum trigger_type { + THROTTLE_ACTIVE = 1, + THROTTLE_PASSIVE, + SW_TRIP, + HW_TRIP, +}; + +/** + * 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 thermal_trip_point_conf { + int trip_val[MAX_TRIP_COUNT]; + int trip_type[MAX_TRIP_COUNT]; + int trip_count; + unsigned char trigger_falling; +}; + +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); + int (*write_emul_temp)(void *drv_data, unsigned long temp); + struct thermal_trip_point_conf trip_data; + struct thermal_cooling_conf cooling_data; + void *driver_data; + void *pzone_data; + struct device *dev; +}; + +/*Functions used exynos based thermal sensor driver*/ +#ifdef CONFIG_EXYNOS_THERMAL_CORE +void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf); +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); +void exynos_report_trigger(struct thermal_sensor_conf *sensor_conf); +#else +static inline void +exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) { return; } + +static inline int +exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) { return 0; } + +static inline void +exynos_report_trigger(struct thermal_sensor_conf *sensor_conf) { return; } + +#endif /* CONFIG_EXYNOS_THERMAL_CORE */ +#endif /* _EXYNOS_THERMAL_COMMON_H */ diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c new file mode 100644 index 000000000000..b43afda8acd1 --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -0,0 +1,762 @@ +/* + * exynos_tmu.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/clk.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include "exynos_thermal_common.h" +#include "exynos_tmu.h" +#include "exynos_tmu_data.h" + +/** + * struct exynos_tmu_data : A structure to hold the private data of the TMU + driver + * @id: identifier of the one instance of the TMU controller. + * @pdata: pointer to the tmu platform/configuration data + * @base: base address of the single instance of the TMU controller. + * @base_common: base address of the common registers of the TMU controller. + * @irq: irq number of the TMU controller. + * @soc: id of the SOC type. + * @irq_work: pointer to the irq work structure. + * @lock: lock to implement synchronization. + * @clk: pointer to the clock structure. + * @temp_error1: fused value of the first point trim. + * @temp_error2: fused value of the second point trim. + * @regulator: pointer to the TMU regulator structure. + * @reg_conf: pointer to structure to register with core thermal. + */ +struct exynos_tmu_data { + int id; + struct exynos_tmu_platform_data *pdata; + void __iomem *base; + void __iomem *base_common; + int irq; + enum soc_type soc; + struct work_struct irq_work; + struct mutex lock; + struct clk *clk; + u8 temp_error1, temp_error2; + struct regulator *regulator; + struct thermal_sensor_conf *reg_conf; +}; + +/* + * 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 (pdata->cal_mode == HW_MODE) + return temp; + + 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 - pdata->first_point_trim) * + (data->temp_error2 - data->temp_error1) / + (pdata->second_point_trim - pdata->first_point_trim) + + data->temp_error1; + break; + case TYPE_ONE_POINT_TRIMMING: + temp_code = temp + data->temp_error1 - pdata->first_point_trim; + break; + default: + temp_code = temp + pdata->default_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 (pdata->cal_mode == HW_MODE) + return temp_code; + + 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) * + (pdata->second_point_trim - pdata->first_point_trim) / + (data->temp_error2 - data->temp_error1) + + pdata->first_point_trim; + break; + case TYPE_ONE_POINT_TRIMMING: + temp = temp_code - data->temp_error1 + pdata->first_point_trim; + break; + default: + temp = temp_code - pdata->default_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; + const struct exynos_tmu_registers *reg = pdata->registers; + unsigned int status, trim_info = 0, con; + unsigned int rising_threshold = 0, falling_threshold = 0; + int ret = 0, threshold_code, i, trigger_levs = 0; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + if (TMU_SUPPORTS(pdata, READY_STATUS)) { + status = readb(data->base + reg->tmu_status); + if (!status) { + ret = -EBUSY; + goto out; + } + } + + if (TMU_SUPPORTS(pdata, TRIM_RELOAD)) + __raw_writel(1, data->base + reg->triminfo_ctrl); + + if (pdata->cal_mode == HW_MODE) + goto skip_calib_data; + + /* Save trimming info in order to perform calibration */ + if (data->soc == SOC_ARCH_EXYNOS5440) { + /* + * For exynos5440 soc triminfo value is swapped between TMU0 and + * TMU2, so the below logic is needed. + */ + switch (data->id) { + case 0: + trim_info = readl(data->base + + EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data); + break; + case 1: + trim_info = readl(data->base + reg->triminfo_data); + break; + case 2: + trim_info = readl(data->base - + EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data); + } + } else { + trim_info = readl(data->base + reg->triminfo_data); + } + data->temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; + data->temp_error2 = ((trim_info >> reg->triminfo_85_shift) & + EXYNOS_TMU_TEMP_MASK); + + if (!data->temp_error1 || + (pdata->min_efuse_value > data->temp_error1) || + (data->temp_error1 > pdata->max_efuse_value)) + data->temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK; + + if (!data->temp_error2) + data->temp_error2 = + (pdata->efuse_value >> reg->triminfo_85_shift) & + EXYNOS_TMU_TEMP_MASK; + +skip_calib_data: + if (pdata->max_trigger_level > MAX_THRESHOLD_LEVS) { + dev_err(&pdev->dev, "Invalid max trigger level\n"); + goto out; + } + + for (i = 0; i < pdata->max_trigger_level; i++) { + if (!pdata->trigger_levels[i]) + continue; + + if ((pdata->trigger_type[i] == HW_TRIP) && + (!pdata->trigger_levels[pdata->max_trigger_level - 1])) { + dev_err(&pdev->dev, "Invalid hw trigger level\n"); + ret = -EINVAL; + goto out; + } + + /* Count trigger levels except the HW trip*/ + if (!(pdata->trigger_type[i] == HW_TRIP)) + trigger_levs++; + } + + 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 + reg->threshold_temp); + for (i = 0; i < trigger_levs; i++) + writeb(pdata->trigger_levels[i], data->base + + reg->threshold_th0 + i * sizeof(reg->threshold_th0)); + + writel(reg->inten_rise_mask, data->base + reg->tmu_intclear); + } else { + /* Write temperature code for rising and falling threshold */ + for (i = 0; + i < trigger_levs && i < EXYNOS_MAX_TRIGGER_PER_REG; i++) { + threshold_code = temp_to_code(data, + pdata->trigger_levels[i]); + if (threshold_code < 0) { + ret = threshold_code; + goto out; + } + rising_threshold |= threshold_code << 8 * i; + if (pdata->threshold_falling) { + threshold_code = temp_to_code(data, + pdata->trigger_levels[i] - + pdata->threshold_falling); + if (threshold_code > 0) + falling_threshold |= + threshold_code << 8 * i; + } + } + + writel(rising_threshold, + data->base + reg->threshold_th0); + writel(falling_threshold, + data->base + reg->threshold_th1); + + writel((reg->inten_rise_mask << reg->inten_rise_shift) | + (reg->inten_fall_mask << reg->inten_fall_shift), + data->base + reg->tmu_intclear); + + /* if last threshold limit is also present */ + i = pdata->max_trigger_level - 1; + if (pdata->trigger_levels[i] && + (pdata->trigger_type[i] == HW_TRIP)) { + threshold_code = temp_to_code(data, + pdata->trigger_levels[i]); + if (threshold_code < 0) { + ret = threshold_code; + goto out; + } + if (i == EXYNOS_MAX_TRIGGER_PER_REG - 1) { + /* 1-4 level to be assigned in th0 reg */ + rising_threshold |= threshold_code << 8 * i; + writel(rising_threshold, + data->base + reg->threshold_th0); + } else if (i == EXYNOS_MAX_TRIGGER_PER_REG) { + /* 5th level to be assigned in th2 reg */ + rising_threshold = + threshold_code << reg->threshold_th3_l0_shift; + writel(rising_threshold, + data->base + reg->threshold_th2); + } + con = readl(data->base + reg->tmu_ctrl); + con |= (1 << reg->therm_trip_en_shift); + writel(con, data->base + reg->tmu_ctrl); + } + } + /*Clear the PMIN in the common TMU register*/ + if (reg->tmu_pmin && !data->id) + writel(0, data->base_common + reg->tmu_pmin); +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; + const struct exynos_tmu_registers *reg = pdata->registers; + unsigned int con, interrupt_en, cal_val; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + con = readl(data->base + reg->tmu_ctrl); + + if (pdata->reference_voltage) { + con &= ~(reg->buf_vref_sel_mask << reg->buf_vref_sel_shift); + con |= pdata->reference_voltage << reg->buf_vref_sel_shift; + } + + if (pdata->gain) { + con &= ~(reg->buf_slope_sel_mask << reg->buf_slope_sel_shift); + con |= (pdata->gain << reg->buf_slope_sel_shift); + } + + if (pdata->noise_cancel_mode) { + con &= ~(reg->therm_trip_mode_mask << + reg->therm_trip_mode_shift); + con |= (pdata->noise_cancel_mode << reg->therm_trip_mode_shift); + } + + if (pdata->cal_mode == HW_MODE) { + con &= ~(reg->calib_mode_mask << reg->calib_mode_shift); + cal_val = 0; + switch (pdata->cal_type) { + case TYPE_TWO_POINT_TRIMMING: + cal_val = 3; + break; + case TYPE_ONE_POINT_TRIMMING_85: + cal_val = 2; + break; + case TYPE_ONE_POINT_TRIMMING_25: + cal_val = 1; + break; + case TYPE_NONE: + break; + default: + dev_err(&pdev->dev, "Invalid calibration type, using none\n"); + } + con |= cal_val << reg->calib_mode_shift; + } + + if (on) { + con |= (1 << reg->core_en_shift); + interrupt_en = + pdata->trigger_enable[3] << reg->inten_rise3_shift | + pdata->trigger_enable[2] << reg->inten_rise2_shift | + pdata->trigger_enable[1] << reg->inten_rise1_shift | + pdata->trigger_enable[0] << reg->inten_rise0_shift; + if (TMU_SUPPORTS(pdata, FALLING_TRIP)) + interrupt_en |= + interrupt_en << reg->inten_fall0_shift; + } else { + con &= ~(1 << reg->core_en_shift); + interrupt_en = 0; /* Disable all interrupts */ + } + writel(interrupt_en, data->base + reg->tmu_inten); + writel(con, data->base + reg->tmu_ctrl); + + clk_disable(data->clk); + mutex_unlock(&data->lock); +} + +static int exynos_tmu_read(struct exynos_tmu_data *data) +{ + struct exynos_tmu_platform_data *pdata = data->pdata; + const struct exynos_tmu_registers *reg = pdata->registers; + u8 temp_code; + int temp; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + temp_code = readb(data->base + reg->tmu_cur_temp); + temp = code_to_temp(data, temp_code); + + clk_disable(data->clk); + mutex_unlock(&data->lock); + + return temp; +} + +#ifdef CONFIG_THERMAL_EMULATION +static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) +{ + struct exynos_tmu_data *data = drv_data; + struct exynos_tmu_platform_data *pdata = data->pdata; + const struct exynos_tmu_registers *reg = pdata->registers; + unsigned int val; + int ret = -EINVAL; + + if (!TMU_SUPPORTS(pdata, EMULATION)) + goto out; + + if (temp && temp < MCELSIUS) + goto out; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + val = readl(data->base + reg->emul_con); + + if (temp) { + temp /= MCELSIUS; + + if (TMU_SUPPORTS(pdata, EMUL_TIME)) { + val &= ~(EXYNOS_EMUL_TIME_MASK << reg->emul_time_shift); + val |= (EXYNOS_EMUL_TIME << reg->emul_time_shift); + } + val &= ~(EXYNOS_EMUL_DATA_MASK << reg->emul_temp_shift); + val |= (temp_to_code(data, temp) << reg->emul_temp_shift) | + EXYNOS_EMUL_ENABLE; + } else { + val &= ~EXYNOS_EMUL_ENABLE; + } + + writel(val, data->base + reg->emul_con); + + clk_disable(data->clk); + mutex_unlock(&data->lock); + return 0; +out: + return ret; +} +#else +static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) + { return -EINVAL; } +#endif/*CONFIG_THERMAL_EMULATION*/ + +static void exynos_tmu_work(struct work_struct *work) +{ + struct exynos_tmu_data *data = container_of(work, + struct exynos_tmu_data, irq_work); + struct exynos_tmu_platform_data *pdata = data->pdata; + const struct exynos_tmu_registers *reg = pdata->registers; + unsigned int val_irq, val_type; + + /* Find which sensor generated this interrupt */ + if (reg->tmu_irqstatus) { + val_type = readl(data->base_common + reg->tmu_irqstatus); + if (!((val_type >> data->id) & 0x1)) + goto out; + } + + exynos_report_trigger(data->reg_conf); + mutex_lock(&data->lock); + clk_enable(data->clk); + + /* TODO: take action based on particular interrupt */ + val_irq = readl(data->base + reg->tmu_intstat); + /* clear the interrupts */ + writel(val_irq, data->base + reg->tmu_intclear); + + clk_disable(data->clk); + mutex_unlock(&data->lock); +out: + 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 const struct of_device_id exynos_tmu_match[] = { + { + .compatible = "samsung,exynos4210-tmu", + .data = (void *)EXYNOS4210_TMU_DRV_DATA, + }, + { + .compatible = "samsung,exynos4412-tmu", + .data = (void *)EXYNOS5250_TMU_DRV_DATA, + }, + { + .compatible = "samsung,exynos5250-tmu", + .data = (void *)EXYNOS5250_TMU_DRV_DATA, + }, + { + .compatible = "samsung,exynos5440-tmu", + .data = (void *)EXYNOS5440_TMU_DRV_DATA, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_tmu_match); + +static inline struct exynos_tmu_platform_data *exynos_get_driver_data( + struct platform_device *pdev, int id) +{ + struct exynos_tmu_init_data *data_table; + struct exynos_tmu_platform_data *tmu_data; + const struct of_device_id *match; + + match = of_match_node(exynos_tmu_match, pdev->dev.of_node); + if (!match) + return NULL; + data_table = (struct exynos_tmu_init_data *) match->data; + if (!data_table || id >= data_table->tmu_count) + return NULL; + tmu_data = data_table->tmu_data; + return (struct exynos_tmu_platform_data *) (tmu_data + id); +} + +static int exynos_map_dt_data(struct platform_device *pdev) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + struct exynos_tmu_platform_data *pdata; + struct resource res; + int ret; + + if (!data || !pdev->dev.of_node) + return -ENODEV; + + /* + * Try enabling the regulator if found + * TODO: Add regulator as an SOC feature, so that regulator enable + * is a compulsory call. + */ + data->regulator = devm_regulator_get(&pdev->dev, "vtmu"); + if (!IS_ERR(data->regulator)) { + ret = regulator_enable(data->regulator); + if (ret) { + dev_err(&pdev->dev, "failed to enable vtmu\n"); + return ret; + } + } else { + dev_info(&pdev->dev, "Regulator node (vtmu) not found\n"); + } + + data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl"); + if (data->id < 0) + data->id = 0; + + data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + if (data->irq <= 0) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + return -ENODEV; + } + + if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { + dev_err(&pdev->dev, "failed to get Resource 0\n"); + return -ENODEV; + } + + data->base = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); + if (!data->base) { + dev_err(&pdev->dev, "Failed to ioremap memory\n"); + return -EADDRNOTAVAIL; + } + + pdata = exynos_get_driver_data(pdev, data->id); + if (!pdata) { + dev_err(&pdev->dev, "No platform init data supplied.\n"); + return -ENODEV; + } + data->pdata = pdata; + /* + * Check if the TMU shares some registers and then try to map the + * memory of common registers. + */ + if (!TMU_SUPPORTS(pdata, SHARED_MEMORY)) + return 0; + + if (of_address_to_resource(pdev->dev.of_node, 1, &res)) { + dev_err(&pdev->dev, "failed to get Resource 1\n"); + return -ENODEV; + } + + data->base_common = devm_ioremap(&pdev->dev, res.start, + resource_size(&res)); + if (!data->base_common) { + dev_err(&pdev->dev, "Failed to ioremap memory\n"); + return -ENOMEM; + } + + return 0; +} + +static int exynos_tmu_probe(struct platform_device *pdev) +{ + struct exynos_tmu_data *data; + struct exynos_tmu_platform_data *pdata; + struct thermal_sensor_conf *sensor_conf; + int ret, i; + + 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; + } + + platform_set_drvdata(pdev, data); + mutex_init(&data->lock); + + ret = exynos_map_dt_data(pdev); + if (ret) + return ret; + + pdata = data->pdata; + + INIT_WORK(&data->irq_work, exynos_tmu_work); + + data->clk = devm_clk_get(&pdev->dev, "tmu_apbif"); + if (IS_ERR(data->clk)) { + dev_err(&pdev->dev, "Failed to get clock\n"); + return PTR_ERR(data->clk); + } + + ret = clk_prepare(data->clk); + if (ret) + return ret; + + if (pdata->type == SOC_ARCH_EXYNOS || + pdata->type == SOC_ARCH_EXYNOS4210 || + pdata->type == SOC_ARCH_EXYNOS5440) + data->soc = pdata->type; + else { + ret = -EINVAL; + dev_err(&pdev->dev, "Platform not supported\n"); + goto err_clk; + } + + ret = exynos_tmu_initialize(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize TMU\n"); + goto err_clk; + } + + exynos_tmu_control(pdev, true); + + /* Allocate a structure to register with the exynos core thermal */ + sensor_conf = devm_kzalloc(&pdev->dev, + sizeof(struct thermal_sensor_conf), GFP_KERNEL); + if (!sensor_conf) { + dev_err(&pdev->dev, "Failed to allocate registration struct\n"); + ret = -ENOMEM; + goto err_clk; + } + sprintf(sensor_conf->name, "therm_zone%d", data->id); + sensor_conf->read_temperature = (int (*)(void *))exynos_tmu_read; + sensor_conf->write_emul_temp = + (int (*)(void *, unsigned long))exynos_tmu_set_emulation; + sensor_conf->driver_data = data; + sensor_conf->trip_data.trip_count = pdata->trigger_enable[0] + + pdata->trigger_enable[1] + pdata->trigger_enable[2]+ + pdata->trigger_enable[3]; + + for (i = 0; i < sensor_conf->trip_data.trip_count; i++) { + sensor_conf->trip_data.trip_val[i] = + pdata->threshold + pdata->trigger_levels[i]; + sensor_conf->trip_data.trip_type[i] = + pdata->trigger_type[i]; + } + + sensor_conf->trip_data.trigger_falling = pdata->threshold_falling; + + sensor_conf->cooling_data.freq_clip_count = pdata->freq_tab_count; + for (i = 0; i < pdata->freq_tab_count; i++) { + sensor_conf->cooling_data.freq_data[i].freq_clip_max = + pdata->freq_tab[i].freq_clip_max; + sensor_conf->cooling_data.freq_data[i].temp_level = + pdata->freq_tab[i].temp_level; + } + sensor_conf->dev = &pdev->dev; + /* Register the sensor with thermal management interface */ + ret = exynos_register_thermal(sensor_conf); + if (ret) { + dev_err(&pdev->dev, "Failed to register thermal interface\n"); + goto err_clk; + } + data->reg_conf = sensor_conf; + + ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, + IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); + goto err_clk; + } + + return 0; +err_clk: + clk_unprepare(data->clk); + return ret; +} + +static int exynos_tmu_remove(struct platform_device *pdev) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + + exynos_tmu_control(pdev, false); + + exynos_unregister_thermal(data->reg_conf); + + clk_unprepare(data->clk); + + if (!IS_ERR(data->regulator)) + regulator_disable(data->regulator); + + 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 = exynos_tmu_remove, +}; + +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/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h new file mode 100644 index 000000000000..b364c9eee701 --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -0,0 +1,311 @@ +/* + * exynos_tmu.h - Samsung EXYNOS TMU (Thermal Management Unit) + * + * Copyright (C) 2011 Samsung Electronics + * Donggeun Kim <dg77.kim@samsung.com> + * Amit Daniel Kachhap <amit.daniel@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 + */ + +#ifndef _EXYNOS_TMU_H +#define _EXYNOS_TMU_H +#include <linux/cpu_cooling.h> + +#include "exynos_thermal_common.h" + +enum calibration_type { + TYPE_ONE_POINT_TRIMMING, + TYPE_ONE_POINT_TRIMMING_25, + TYPE_ONE_POINT_TRIMMING_85, + TYPE_TWO_POINT_TRIMMING, + TYPE_NONE, +}; + +enum calibration_mode { + SW_MODE, + HW_MODE, +}; + +enum soc_type { + SOC_ARCH_EXYNOS4210 = 1, + SOC_ARCH_EXYNOS, + SOC_ARCH_EXYNOS5440, +}; + +/** + * EXYNOS TMU supported features. + * TMU_SUPPORT_EMULATION - This features is used to set user defined + * temperature to the TMU controller. + * TMU_SUPPORT_MULTI_INST - This features denotes that the soc + * has many instances of TMU. + * TMU_SUPPORT_TRIM_RELOAD - This features shows that trimming can + * be reloaded. + * TMU_SUPPORT_FALLING_TRIP - This features shows that interrupt can + * be registered for falling trips also. + * TMU_SUPPORT_READY_STATUS - This feature tells that the TMU current + * state(active/idle) can be checked. + * TMU_SUPPORT_EMUL_TIME - This features allows to set next temp emulation + * sample time. + * TMU_SUPPORT_SHARED_MEMORY - This feature tells that the different TMU + * sensors shares some common registers. + * TMU_SUPPORT - macro to compare the above features with the supplied. + */ +#define TMU_SUPPORT_EMULATION BIT(0) +#define TMU_SUPPORT_MULTI_INST BIT(1) +#define TMU_SUPPORT_TRIM_RELOAD BIT(2) +#define TMU_SUPPORT_FALLING_TRIP BIT(3) +#define TMU_SUPPORT_READY_STATUS BIT(4) +#define TMU_SUPPORT_EMUL_TIME BIT(5) +#define TMU_SUPPORT_SHARED_MEMORY BIT(6) + +#define TMU_SUPPORTS(a, b) (a->features & TMU_SUPPORT_ ## b) + +/** + * struct exynos_tmu_register - register descriptors to access registers and + * bitfields. The register validity, offsets and bitfield values may vary + * slightly across different exynos SOC's. + * @triminfo_data: register containing 2 pont trimming data + * @triminfo_25_shift: shift bit of the 25 C trim value in triminfo_data reg. + * @triminfo_85_shift: shift bit of the 85 C trim value in triminfo_data reg. + * @triminfo_ctrl: trim info controller register. + * @triminfo_reload_shift: shift of triminfo reload enable bit in triminfo_ctrl + reg. + * @tmu_ctrl: TMU main controller register. + * @buf_vref_sel_shift: shift bits of reference voltage in tmu_ctrl register. + * @buf_vref_sel_mask: mask bits of reference voltage in tmu_ctrl register. + * @therm_trip_mode_shift: shift bits of tripping mode in tmu_ctrl register. + * @therm_trip_mode_mask: mask bits of tripping mode in tmu_ctrl register. + * @therm_trip_en_shift: shift bits of tripping enable in tmu_ctrl register. + * @buf_slope_sel_shift: shift bits of amplifier gain value in tmu_ctrl + register. + * @buf_slope_sel_mask: mask bits of amplifier gain value in tmu_ctrl register. + * @calib_mode_shift: shift bits of calibration mode value in tmu_ctrl + register. + * @calib_mode_mask: mask bits of calibration mode value in tmu_ctrl + register. + * @therm_trip_tq_en_shift: shift bits of thermal trip enable by TQ pin in + tmu_ctrl register. + * @core_en_shift: shift bits of TMU core enable bit in tmu_ctrl register. + * @tmu_status: register drescribing the TMU status. + * @tmu_cur_temp: register containing the current temperature of the TMU. + * @tmu_cur_temp_shift: shift bits of current temp value in tmu_cur_temp + register. + * @threshold_temp: register containing the base threshold level. + * @threshold_th0: Register containing first set of rising levels. + * @threshold_th0_l0_shift: shift bits of level0 threshold temperature. + * @threshold_th0_l1_shift: shift bits of level1 threshold temperature. + * @threshold_th0_l2_shift: shift bits of level2 threshold temperature. + * @threshold_th0_l3_shift: shift bits of level3 threshold temperature. + * @threshold_th1: Register containing second set of rising levels. + * @threshold_th1_l0_shift: shift bits of level0 threshold temperature. + * @threshold_th1_l1_shift: shift bits of level1 threshold temperature. + * @threshold_th1_l2_shift: shift bits of level2 threshold temperature. + * @threshold_th1_l3_shift: shift bits of level3 threshold temperature. + * @threshold_th2: Register containing third set of rising levels. + * @threshold_th2_l0_shift: shift bits of level0 threshold temperature. + * @threshold_th3: Register containing fourth set of rising levels. + * @threshold_th3_l0_shift: shift bits of level0 threshold temperature. + * @tmu_inten: register containing the different threshold interrupt + enable bits. + * @inten_rise_shift: shift bits of all rising interrupt bits. + * @inten_rise_mask: mask bits of all rising interrupt bits. + * @inten_fall_shift: shift bits of all rising interrupt bits. + * @inten_fall_mask: mask bits of all rising interrupt bits. + * @inten_rise0_shift: shift bits of rising 0 interrupt bits. + * @inten_rise1_shift: shift bits of rising 1 interrupt bits. + * @inten_rise2_shift: shift bits of rising 2 interrupt bits. + * @inten_rise3_shift: shift bits of rising 3 interrupt bits. + * @inten_fall0_shift: shift bits of falling 0 interrupt bits. + * @inten_fall1_shift: shift bits of falling 1 interrupt bits. + * @inten_fall2_shift: shift bits of falling 2 interrupt bits. + * @inten_fall3_shift: shift bits of falling 3 interrupt bits. + * @tmu_intstat: Register containing the interrupt status values. + * @tmu_intclear: Register for clearing the raised interrupt status. + * @emul_con: TMU emulation controller register. + * @emul_temp_shift: shift bits of emulation temperature. + * @emul_time_shift: shift bits of emulation time. + * @emul_time_mask: mask bits of emulation time. + * @tmu_irqstatus: register to find which TMU generated interrupts. + * @tmu_pmin: register to get/set the Pmin value. + */ +struct exynos_tmu_registers { + u32 triminfo_data; + u32 triminfo_25_shift; + u32 triminfo_85_shift; + + u32 triminfo_ctrl; + u32 triminfo_reload_shift; + + u32 tmu_ctrl; + u32 buf_vref_sel_shift; + u32 buf_vref_sel_mask; + u32 therm_trip_mode_shift; + u32 therm_trip_mode_mask; + u32 therm_trip_en_shift; + u32 buf_slope_sel_shift; + u32 buf_slope_sel_mask; + u32 calib_mode_shift; + u32 calib_mode_mask; + u32 therm_trip_tq_en_shift; + u32 core_en_shift; + + u32 tmu_status; + + u32 tmu_cur_temp; + u32 tmu_cur_temp_shift; + + u32 threshold_temp; + + u32 threshold_th0; + u32 threshold_th0_l0_shift; + u32 threshold_th0_l1_shift; + u32 threshold_th0_l2_shift; + u32 threshold_th0_l3_shift; + + u32 threshold_th1; + u32 threshold_th1_l0_shift; + u32 threshold_th1_l1_shift; + u32 threshold_th1_l2_shift; + u32 threshold_th1_l3_shift; + + u32 threshold_th2; + u32 threshold_th2_l0_shift; + + u32 threshold_th3; + u32 threshold_th3_l0_shift; + + u32 tmu_inten; + u32 inten_rise_shift; + u32 inten_rise_mask; + u32 inten_fall_shift; + u32 inten_fall_mask; + u32 inten_rise0_shift; + u32 inten_rise1_shift; + u32 inten_rise2_shift; + u32 inten_rise3_shift; + u32 inten_fall0_shift; + u32 inten_fall1_shift; + u32 inten_fall2_shift; + u32 inten_fall3_shift; + + u32 tmu_intstat; + + u32 tmu_intclear; + + u32 emul_con; + u32 emul_temp_shift; + u32 emul_time_shift; + u32 emul_time_mask; + + u32 tmu_irqstatus; + u32 tmu_pmin; +}; + +/** + * struct exynos_tmu_platform_data + * @threshold: basic temperature for generating interrupt + * 25 <= threshold <= 125 [unit: degree Celsius] + * @threshold_falling: differntial value for setting threshold + * of temperature falling interrupt. + * @trigger_levels: array for each interrupt levels + * [unit: degree Celsius] + * 0: temperature for trigger_level0 interrupt + * condition for trigger_level0 interrupt: + * current temperature > threshold + trigger_levels[0] + * 1: temperature for trigger_level1 interrupt + * condition for trigger_level1 interrupt: + * current temperature > threshold + trigger_levels[1] + * 2: temperature for trigger_level2 interrupt + * condition for trigger_level2 interrupt: + * current temperature > threshold + trigger_levels[2] + * 3: temperature for trigger_level3 interrupt + * condition for trigger_level3 interrupt: + * current temperature > threshold + trigger_levels[3] + * @trigger_type: defines the type of trigger. Possible values are, + * THROTTLE_ACTIVE trigger type + * THROTTLE_PASSIVE trigger type + * SW_TRIP trigger type + * HW_TRIP + * @trigger_enable[]: array to denote which trigger levels are enabled. + * 1 = enable trigger_level[] interrupt, + * 0 = disable trigger_level[] interrupt + * @max_trigger_level: max trigger level supported by the TMU + * @gain: gain of amplifier in the positive-TC generator block + * 0 <= gain <= 15 + * @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 + * @min_efuse_value: minimum valid trimming data + * @max_efuse_value: maximum valid trimming data + * @first_point_trim: temp value of the first point trimming + * @second_point_trim: temp value of the second point trimming + * @default_temp_offset: default temperature offset in case of no trimming + * @cal_type: calibration type for temperature + * @cal_mode: calibration mode 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. + * @registers: Pointer to structure containing all the TMU controller registers + * and bitfields shifts and masks. + * @features: a bitfield value indicating the features supported in SOC like + * emulation, multi instance etc + * + * This structure is required for configuration of exynos_tmu driver. + */ +struct exynos_tmu_platform_data { + u8 threshold; + u8 threshold_falling; + u8 trigger_levels[MAX_TRIP_COUNT]; + enum trigger_type trigger_type[MAX_TRIP_COUNT]; + bool trigger_enable[MAX_TRIP_COUNT]; + u8 max_trigger_level; + u8 gain; + u8 reference_voltage; + u8 noise_cancel_mode; + + u32 efuse_value; + u32 min_efuse_value; + u32 max_efuse_value; + u8 first_point_trim; + u8 second_point_trim; + u8 default_temp_offset; + + enum calibration_type cal_type; + enum calibration_mode cal_mode; + enum soc_type type; + struct freq_clip_table freq_tab[4]; + unsigned int freq_tab_count; + const struct exynos_tmu_registers *registers; + unsigned int features; +}; + +/** + * struct exynos_tmu_init_data + * @tmu_count: number of TMU instances. + * @tmu_data: platform data of all TMU instances. + * This structure is required to store data for multi-instance exynos tmu + * driver. + */ +struct exynos_tmu_init_data { + int tmu_count; + struct exynos_tmu_platform_data tmu_data[]; +}; + +#endif /* _EXYNOS_TMU_H */ diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c new file mode 100644 index 000000000000..9002499c1f69 --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -0,0 +1,250 @@ +/* + * exynos_tmu_data.c - Samsung EXYNOS tmu data file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap <amit.daniel@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 "exynos_thermal_common.h" +#include "exynos_tmu.h" +#include "exynos_tmu_data.h" + +#if defined(CONFIG_CPU_EXYNOS4210) +static const struct exynos_tmu_registers exynos4210_tmu_registers = { + .triminfo_data = EXYNOS_TMU_REG_TRIMINFO, + .triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, + .triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, + .tmu_ctrl = EXYNOS_TMU_REG_CONTROL, + .buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, + .buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, + .buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, + .buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, + .core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, + .tmu_status = EXYNOS_TMU_REG_STATUS, + .tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP, + .threshold_temp = EXYNOS4210_TMU_REG_THRESHOLD_TEMP, + .threshold_th0 = EXYNOS4210_TMU_REG_TRIG_LEVEL0, + .tmu_inten = EXYNOS_TMU_REG_INTEN, + .inten_rise_mask = EXYNOS4210_TMU_TRIG_LEVEL_MASK, + .inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, + .inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, + .inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, + .inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, + .tmu_intstat = EXYNOS_TMU_REG_INTSTAT, + .tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, +}; + +struct exynos_tmu_init_data const exynos4210_default_tmu_data = { + .tmu_data = { + { + .threshold = 80, + .trigger_levels[0] = 5, + .trigger_levels[1] = 20, + .trigger_levels[2] = 30, + .trigger_enable[0] = true, + .trigger_enable[1] = true, + .trigger_enable[2] = true, + .trigger_enable[3] = false, + .trigger_type[0] = THROTTLE_ACTIVE, + .trigger_type[1] = THROTTLE_ACTIVE, + .trigger_type[2] = SW_TRIP, + .max_trigger_level = 4, + .gain = 15, + .reference_voltage = 7, + .cal_type = TYPE_ONE_POINT_TRIMMING, + .min_efuse_value = 40, + .max_efuse_value = 100, + .first_point_trim = 25, + .second_point_trim = 85, + .default_temp_offset = 50, + .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, + .registers = &exynos4210_tmu_registers, + .features = TMU_SUPPORT_READY_STATUS, + }, + }, + .tmu_count = 1, +}; +#endif + +#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412) +static const struct exynos_tmu_registers exynos5250_tmu_registers = { + .triminfo_data = EXYNOS_TMU_REG_TRIMINFO, + .triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, + .triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, + .triminfo_ctrl = EXYNOS_TMU_TRIMINFO_CON, + .triminfo_reload_shift = EXYNOS_TRIMINFO_RELOAD_SHIFT, + .tmu_ctrl = EXYNOS_TMU_REG_CONTROL, + .buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, + .buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, + .therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT, + .therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK, + .therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT, + .buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, + .buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, + .core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, + .tmu_status = EXYNOS_TMU_REG_STATUS, + .tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP, + .threshold_th0 = EXYNOS_THD_TEMP_RISE, + .threshold_th1 = EXYNOS_THD_TEMP_FALL, + .tmu_inten = EXYNOS_TMU_REG_INTEN, + .inten_rise_mask = EXYNOS_TMU_RISE_INT_MASK, + .inten_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, + .inten_fall_mask = EXYNOS_TMU_FALL_INT_MASK, + .inten_fall_shift = EXYNOS_TMU_FALL_INT_SHIFT, + .inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, + .inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, + .inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, + .inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, + .inten_fall0_shift = EXYNOS_TMU_INTEN_FALL0_SHIFT, + .tmu_intstat = EXYNOS_TMU_REG_INTSTAT, + .tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, + .emul_con = EXYNOS_EMUL_CON, + .emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, + .emul_time_shift = EXYNOS_EMUL_TIME_SHIFT, + .emul_time_mask = EXYNOS_EMUL_TIME_MASK, +}; + +#define EXYNOS5250_TMU_DATA \ + .threshold_falling = 10, \ + .trigger_levels[0] = 85, \ + .trigger_levels[1] = 103, \ + .trigger_levels[2] = 110, \ + .trigger_levels[3] = 120, \ + .trigger_enable[0] = true, \ + .trigger_enable[1] = true, \ + .trigger_enable[2] = true, \ + .trigger_enable[3] = false, \ + .trigger_type[0] = THROTTLE_ACTIVE, \ + .trigger_type[1] = THROTTLE_ACTIVE, \ + .trigger_type[2] = SW_TRIP, \ + .trigger_type[3] = HW_TRIP, \ + .max_trigger_level = 4, \ + .gain = 8, \ + .reference_voltage = 16, \ + .noise_cancel_mode = 4, \ + .cal_type = TYPE_ONE_POINT_TRIMMING, \ + .efuse_value = 55, \ + .min_efuse_value = 40, \ + .max_efuse_value = 100, \ + .first_point_trim = 25, \ + .second_point_trim = 85, \ + .default_temp_offset = 50, \ + .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, \ + .registers = &exynos5250_tmu_registers, \ + .features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ + TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ + TMU_SUPPORT_EMUL_TIME) + +struct exynos_tmu_init_data const exynos5250_default_tmu_data = { + .tmu_data = { + { EXYNOS5250_TMU_DATA }, + }, + .tmu_count = 1, +}; +#endif + +#if defined(CONFIG_SOC_EXYNOS5440) +static const struct exynos_tmu_registers exynos5440_tmu_registers = { + .triminfo_data = EXYNOS5440_TMU_S0_7_TRIM, + .triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, + .triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, + .tmu_ctrl = EXYNOS5440_TMU_S0_7_CTRL, + .buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, + .buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, + .therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT, + .therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK, + .therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT, + .buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, + .buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, + .calib_mode_shift = EXYNOS_TMU_CALIB_MODE_SHIFT, + .calib_mode_mask = EXYNOS_TMU_CALIB_MODE_MASK, + .core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, + .tmu_status = EXYNOS5440_TMU_S0_7_STATUS, + .tmu_cur_temp = EXYNOS5440_TMU_S0_7_TEMP, + .threshold_th0 = EXYNOS5440_TMU_S0_7_TH0, + .threshold_th1 = EXYNOS5440_TMU_S0_7_TH1, + .threshold_th2 = EXYNOS5440_TMU_S0_7_TH2, + .threshold_th3_l0_shift = EXYNOS5440_TMU_TH_RISE4_SHIFT, + .tmu_inten = EXYNOS5440_TMU_S0_7_IRQEN, + .inten_rise_mask = EXYNOS5440_TMU_RISE_INT_MASK, + .inten_rise_shift = EXYNOS5440_TMU_RISE_INT_SHIFT, + .inten_fall_mask = EXYNOS5440_TMU_FALL_INT_MASK, + .inten_fall_shift = EXYNOS5440_TMU_FALL_INT_SHIFT, + .inten_rise0_shift = EXYNOS5440_TMU_INTEN_RISE0_SHIFT, + .inten_rise1_shift = EXYNOS5440_TMU_INTEN_RISE1_SHIFT, + .inten_rise2_shift = EXYNOS5440_TMU_INTEN_RISE2_SHIFT, + .inten_rise3_shift = EXYNOS5440_TMU_INTEN_RISE3_SHIFT, + .inten_fall0_shift = EXYNOS5440_TMU_INTEN_FALL0_SHIFT, + .tmu_intstat = EXYNOS5440_TMU_S0_7_IRQ, + .tmu_intclear = EXYNOS5440_TMU_S0_7_IRQ, + .tmu_irqstatus = EXYNOS5440_TMU_IRQ_STATUS, + .emul_con = EXYNOS5440_TMU_S0_7_DEBUG, + .emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, + .tmu_pmin = EXYNOS5440_TMU_PMIN, +}; + +#define EXYNOS5440_TMU_DATA \ + .trigger_levels[0] = 100, \ + .trigger_levels[4] = 105, \ + .trigger_enable[0] = 1, \ + .trigger_type[0] = SW_TRIP, \ + .trigger_type[4] = HW_TRIP, \ + .max_trigger_level = 5, \ + .gain = 5, \ + .reference_voltage = 16, \ + .noise_cancel_mode = 4, \ + .cal_type = TYPE_ONE_POINT_TRIMMING, \ + .cal_mode = 0, \ + .efuse_value = 0x5b2d, \ + .min_efuse_value = 16, \ + .max_efuse_value = 76, \ + .first_point_trim = 25, \ + .second_point_trim = 70, \ + .default_temp_offset = 25, \ + .type = SOC_ARCH_EXYNOS5440, \ + .registers = &exynos5440_tmu_registers, \ + .features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_FALLING_TRIP | \ + TMU_SUPPORT_MULTI_INST | TMU_SUPPORT_SHARED_MEMORY), + +struct exynos_tmu_init_data const exynos5440_default_tmu_data = { + .tmu_data = { + { EXYNOS5440_TMU_DATA } , + { EXYNOS5440_TMU_DATA } , + { EXYNOS5440_TMU_DATA } , + }, + .tmu_count = 3, +}; +#endif diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h new file mode 100644 index 000000000000..dc7feb51099b --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -0,0 +1,155 @@ +/* + * exynos_tmu_data.h - Samsung EXYNOS tmu data header file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap <amit.daniel@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 + * + */ + +#ifndef _EXYNOS_TMU_DATA_H +#define _EXYNOS_TMU_DATA_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_TEMP_MASK 0xff +#define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24 +#define EXYNOS_TMU_REF_VOLTAGE_MASK 0x1f +#define EXYNOS_TMU_BUF_SLOPE_SEL_MASK 0xf +#define EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT 8 +#define EXYNOS_TMU_CORE_EN_SHIFT 0 + +/* 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_TRIG_LEVEL_MASK 0x1111 +#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_SHIFT 1 +#define EXYNOS_TRIMINFO_25_SHIFT 0 +#define EXYNOS_TRIMINFO_85_SHIFT 8 +#define EXYNOS_TMU_RISE_INT_MASK 0x111 +#define EXYNOS_TMU_RISE_INT_SHIFT 0 +#define EXYNOS_TMU_FALL_INT_MASK 0x111 +#define EXYNOS_TMU_FALL_INT_SHIFT 12 +#define EXYNOS_TMU_CLEAR_RISE_INT 0x111 +#define EXYNOS_TMU_CLEAR_FALL_INT (0x111 << 12) +#define EXYNOS_TMU_TRIP_MODE_SHIFT 13 +#define EXYNOS_TMU_TRIP_MODE_MASK 0x7 +#define EXYNOS_TMU_THERM_TRIP_EN_SHIFT 12 +#define EXYNOS_TMU_CALIB_MODE_SHIFT 4 +#define EXYNOS_TMU_CALIB_MODE_MASK 0x3 + +#define EXYNOS_TMU_INTEN_RISE0_SHIFT 0 +#define EXYNOS_TMU_INTEN_RISE1_SHIFT 4 +#define EXYNOS_TMU_INTEN_RISE2_SHIFT 8 +#define EXYNOS_TMU_INTEN_RISE3_SHIFT 12 +#define EXYNOS_TMU_INTEN_FALL0_SHIFT 16 +#define EXYNOS_TMU_INTEN_FALL1_SHIFT 20 +#define EXYNOS_TMU_INTEN_FALL2_SHIFT 24 + +#define EXYNOS_EMUL_TIME 0x57F0 +#define EXYNOS_EMUL_TIME_MASK 0xffff +#define EXYNOS_EMUL_TIME_SHIFT 16 +#define EXYNOS_EMUL_DATA_SHIFT 8 +#define EXYNOS_EMUL_DATA_MASK 0xFF +#define EXYNOS_EMUL_ENABLE 0x1 + +#define EXYNOS_MAX_TRIGGER_PER_REG 4 + +/*exynos5440 specific registers*/ +#define EXYNOS5440_TMU_S0_7_TRIM 0x000 +#define EXYNOS5440_TMU_S0_7_CTRL 0x020 +#define EXYNOS5440_TMU_S0_7_DEBUG 0x040 +#define EXYNOS5440_TMU_S0_7_STATUS 0x060 +#define EXYNOS5440_TMU_S0_7_TEMP 0x0f0 +#define EXYNOS5440_TMU_S0_7_TH0 0x110 +#define EXYNOS5440_TMU_S0_7_TH1 0x130 +#define EXYNOS5440_TMU_S0_7_TH2 0x150 +#define EXYNOS5440_TMU_S0_7_EVTEN 0x1F0 +#define EXYNOS5440_TMU_S0_7_IRQEN 0x210 +#define EXYNOS5440_TMU_S0_7_IRQ 0x230 +/* exynos5440 common registers */ +#define EXYNOS5440_TMU_IRQ_STATUS 0x000 +#define EXYNOS5440_TMU_PMIN 0x004 +#define EXYNOS5440_TMU_TEMP 0x008 + +#define EXYNOS5440_TMU_RISE_INT_MASK 0xf +#define EXYNOS5440_TMU_RISE_INT_SHIFT 0 +#define EXYNOS5440_TMU_FALL_INT_MASK 0xf +#define EXYNOS5440_TMU_FALL_INT_SHIFT 4 +#define EXYNOS5440_TMU_INTEN_RISE0_SHIFT 0 +#define EXYNOS5440_TMU_INTEN_RISE1_SHIFT 1 +#define EXYNOS5440_TMU_INTEN_RISE2_SHIFT 2 +#define EXYNOS5440_TMU_INTEN_RISE3_SHIFT 3 +#define EXYNOS5440_TMU_INTEN_FALL0_SHIFT 4 +#define EXYNOS5440_TMU_INTEN_FALL1_SHIFT 5 +#define EXYNOS5440_TMU_INTEN_FALL2_SHIFT 6 +#define EXYNOS5440_TMU_INTEN_FALL3_SHIFT 7 +#define EXYNOS5440_TMU_TH_RISE0_SHIFT 0 +#define EXYNOS5440_TMU_TH_RISE1_SHIFT 8 +#define EXYNOS5440_TMU_TH_RISE2_SHIFT 16 +#define EXYNOS5440_TMU_TH_RISE3_SHIFT 24 +#define EXYNOS5440_TMU_TH_RISE4_SHIFT 24 +#define EXYNOS5440_EFUSE_SWAP_OFFSET 8 + +#if defined(CONFIG_CPU_EXYNOS4210) +extern struct exynos_tmu_init_data const exynos4210_default_tmu_data; +#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)) +extern struct exynos_tmu_init_data const exynos5250_default_tmu_data; +#define EXYNOS5250_TMU_DRV_DATA (&exynos5250_default_tmu_data) +#else +#define EXYNOS5250_TMU_DRV_DATA (NULL) +#endif + +#if defined(CONFIG_SOC_EXYNOS5440) +extern struct exynos_tmu_init_data const exynos5440_default_tmu_data; +#define EXYNOS5440_TMU_DRV_DATA (&exynos5440_default_tmu_data) +#else +#define EXYNOS5440_TMU_DRV_DATA (NULL) +#endif + +#endif /*_EXYNOS_TMU_DATA_H*/ diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/step_wise.c index 4d4ddae1a991..d89e781b0a18 100644 --- a/drivers/thermal/step_wise.c +++ b/drivers/thermal/step_wise.c @@ -51,44 +51,51 @@ static unsigned long get_target_state(struct thermal_instance *instance, { struct thermal_cooling_device *cdev = instance->cdev; unsigned long cur_state; + unsigned long next_target; + /* + * We keep this instance the way it is by default. + * Otherwise, we use the current state of the + * cdev in use to determine the next_target. + */ cdev->ops->get_cur_state(cdev, &cur_state); + next_target = instance->target; switch (trend) { case THERMAL_TREND_RAISING: if (throttle) { - cur_state = cur_state < instance->upper ? + next_target = cur_state < instance->upper ? (cur_state + 1) : instance->upper; - if (cur_state < instance->lower) - cur_state = instance->lower; + if (next_target < instance->lower) + next_target = instance->lower; } break; case THERMAL_TREND_RAISE_FULL: if (throttle) - cur_state = instance->upper; + next_target = instance->upper; break; case THERMAL_TREND_DROPPING: if (cur_state == instance->lower) { if (!throttle) - cur_state = -1; + next_target = THERMAL_NO_TARGET; } else { - cur_state -= 1; - if (cur_state > instance->upper) - cur_state = instance->upper; + next_target = cur_state - 1; + if (next_target > instance->upper) + next_target = instance->upper; } break; case THERMAL_TREND_DROP_FULL: if (cur_state == instance->lower) { if (!throttle) - cur_state = -1; + next_target = THERMAL_NO_TARGET; } else - cur_state = instance->lower; + next_target = instance->lower; break; default: break; } - return cur_state; + return next_target; } static void update_passive_instance(struct thermal_zone_device *tz, @@ -133,6 +140,9 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip) old_target = instance->target; instance->target = get_target_state(instance, trend, throttle); + if (old_target == instance->target) + continue; + /* Activate a passive thermal instance */ if (old_target == THERMAL_NO_TARGET && instance->target != THERMAL_NO_TARGET) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 1f02e8edb45c..4962a6aaf295 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -38,6 +38,7 @@ #include <net/genetlink.h> #include "thermal_core.h" +#include "thermal_hwmon.h" MODULE_AUTHOR("Zhang Rui"); MODULE_DESCRIPTION("Generic thermal management sysfs support"); @@ -201,14 +202,23 @@ static void print_bind_err_msg(struct thermal_zone_device *tz, } static void __bind(struct thermal_zone_device *tz, int mask, - struct thermal_cooling_device *cdev) + struct thermal_cooling_device *cdev, + unsigned long *limits) { int i, ret; for (i = 0; i < tz->trips; i++) { if (mask & (1 << i)) { + unsigned long upper, lower; + + upper = THERMAL_NO_LIMIT; + lower = THERMAL_NO_LIMIT; + if (limits) { + lower = limits[i * 2]; + upper = limits[i * 2 + 1]; + } ret = thermal_zone_bind_cooling_device(tz, i, cdev, - THERMAL_NO_LIMIT, THERMAL_NO_LIMIT); + upper, lower); if (ret) print_bind_err_msg(tz, cdev, ret); } @@ -253,7 +263,8 @@ static void bind_cdev(struct thermal_cooling_device *cdev) if (tzp->tbp[i].match(pos, cdev)) continue; tzp->tbp[i].cdev = cdev; - __bind(pos, tzp->tbp[i].trip_mask, cdev); + __bind(pos, tzp->tbp[i].trip_mask, cdev, + tzp->tbp[i].binding_limits); } } @@ -291,7 +302,8 @@ static void bind_tz(struct thermal_zone_device *tz) if (tzp->tbp[i].match(tz, pos)) continue; tzp->tbp[i].cdev = pos; - __bind(tz, tzp->tbp[i].trip_mask, pos); + __bind(tz, tzp->tbp[i].trip_mask, pos, + tzp->tbp[i].binding_limits); } } exit: @@ -859,260 +871,6 @@ thermal_cooling_device_trip_point_show(struct device *dev, /* Device management */ -#if defined(CONFIG_THERMAL_HWMON) - -/* hwmon sys I/F */ -#include <linux/hwmon.h> - -/* thermal zone devices with the same type share one hwmon device */ -struct thermal_hwmon_device { - char type[THERMAL_NAME_LENGTH]; - struct device *device; - int count; - struct list_head tz_list; - struct list_head node; -}; - -struct thermal_hwmon_attr { - struct device_attribute attr; - char name[16]; -}; - -/* one temperature input for each thermal zone */ -struct thermal_hwmon_temp { - struct list_head hwmon_node; - struct thermal_zone_device *tz; - struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ - struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ -}; - -static LIST_HEAD(thermal_hwmon_list); - -static ssize_t -name_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); - return sprintf(buf, "%s\n", hwmon->type); -} -static DEVICE_ATTR(name, 0444, name_show, NULL); - -static ssize_t -temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - long temperature; - int ret; - struct thermal_hwmon_attr *hwmon_attr - = container_of(attr, struct thermal_hwmon_attr, attr); - struct thermal_hwmon_temp *temp - = container_of(hwmon_attr, struct thermal_hwmon_temp, - temp_input); - struct thermal_zone_device *tz = temp->tz; - - ret = thermal_zone_get_temp(tz, &temperature); - - if (ret) - return ret; - - return sprintf(buf, "%ld\n", temperature); -} - -static ssize_t -temp_crit_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct thermal_hwmon_attr *hwmon_attr - = container_of(attr, struct thermal_hwmon_attr, attr); - struct thermal_hwmon_temp *temp - = container_of(hwmon_attr, struct thermal_hwmon_temp, - temp_crit); - struct thermal_zone_device *tz = temp->tz; - long temperature; - int ret; - - ret = tz->ops->get_trip_temp(tz, 0, &temperature); - if (ret) - return ret; - - return sprintf(buf, "%ld\n", temperature); -} - - -static struct thermal_hwmon_device * -thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) -{ - struct thermal_hwmon_device *hwmon; - - mutex_lock(&thermal_list_lock); - list_for_each_entry(hwmon, &thermal_hwmon_list, node) - if (!strcmp(hwmon->type, tz->type)) { - mutex_unlock(&thermal_list_lock); - return hwmon; - } - mutex_unlock(&thermal_list_lock); - - return NULL; -} - -/* Find the temperature input matching a given thermal zone */ -static struct thermal_hwmon_temp * -thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, - const struct thermal_zone_device *tz) -{ - struct thermal_hwmon_temp *temp; - - mutex_lock(&thermal_list_lock); - list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) - if (temp->tz == tz) { - mutex_unlock(&thermal_list_lock); - return temp; - } - mutex_unlock(&thermal_list_lock); - - return NULL; -} - -static int -thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) -{ - struct thermal_hwmon_device *hwmon; - struct thermal_hwmon_temp *temp; - int new_hwmon_device = 1; - int result; - - hwmon = thermal_hwmon_lookup_by_type(tz); - if (hwmon) { - new_hwmon_device = 0; - goto register_sys_interface; - } - - hwmon = kzalloc(sizeof(struct thermal_hwmon_device), GFP_KERNEL); - if (!hwmon) - return -ENOMEM; - - INIT_LIST_HEAD(&hwmon->tz_list); - strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); - hwmon->device = hwmon_device_register(NULL); - if (IS_ERR(hwmon->device)) { - result = PTR_ERR(hwmon->device); - goto free_mem; - } - dev_set_drvdata(hwmon->device, hwmon); - result = device_create_file(hwmon->device, &dev_attr_name); - if (result) - goto free_mem; - - register_sys_interface: - temp = kzalloc(sizeof(struct thermal_hwmon_temp), GFP_KERNEL); - if (!temp) { - result = -ENOMEM; - goto unregister_name; - } - - temp->tz = tz; - hwmon->count++; - - snprintf(temp->temp_input.name, 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; - temp->temp_input.attr.show = temp_input_show; - sysfs_attr_init(&temp->temp_input.attr.attr); - result = device_create_file(hwmon->device, &temp->temp_input.attr); - if (result) - goto free_temp_mem; - - if (tz->ops->get_crit_temp) { - unsigned long temperature; - if (!tz->ops->get_crit_temp(tz, &temperature)) { - 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; - temp->temp_crit.attr.show = temp_crit_show; - sysfs_attr_init(&temp->temp_crit.attr.attr); - result = device_create_file(hwmon->device, - &temp->temp_crit.attr); - if (result) - goto unregister_input; - } - } - - mutex_lock(&thermal_list_lock); - if (new_hwmon_device) - list_add_tail(&hwmon->node, &thermal_hwmon_list); - list_add_tail(&temp->hwmon_node, &hwmon->tz_list); - mutex_unlock(&thermal_list_lock); - - return 0; - - unregister_input: - device_remove_file(hwmon->device, &temp->temp_input.attr); - free_temp_mem: - kfree(temp); - unregister_name: - if (new_hwmon_device) { - device_remove_file(hwmon->device, &dev_attr_name); - hwmon_device_unregister(hwmon->device); - } - free_mem: - if (new_hwmon_device) - kfree(hwmon); - - return result; -} - -static void -thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) -{ - struct thermal_hwmon_device *hwmon; - struct thermal_hwmon_temp *temp; - - hwmon = thermal_hwmon_lookup_by_type(tz); - if (unlikely(!hwmon)) { - /* Should never happen... */ - dev_dbg(&tz->device, "hwmon device lookup failed!\n"); - return; - } - - temp = thermal_hwmon_lookup_temp(hwmon, tz); - if (unlikely(!temp)) { - /* Should never happen... */ - dev_dbg(&tz->device, "temperature input lookup failed!\n"); - return; - } - - device_remove_file(hwmon->device, &temp->temp_input.attr); - if (tz->ops->get_crit_temp) - device_remove_file(hwmon->device, &temp->temp_crit.attr); - - mutex_lock(&thermal_list_lock); - list_del(&temp->hwmon_node); - kfree(temp); - if (!list_empty(&hwmon->tz_list)) { - mutex_unlock(&thermal_list_lock); - return; - } - list_del(&hwmon->node); - mutex_unlock(&thermal_list_lock); - - device_remove_file(hwmon->device, &dev_attr_name); - hwmon_device_unregister(hwmon->device); - kfree(hwmon); -} -#else -static int -thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) -{ - return 0; -} - -static void -thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) -{ -} -#endif - /** * thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone * @tz: pointer to struct thermal_zone_device @@ -1715,9 +1473,11 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, mutex_unlock(&thermal_governor_lock); - result = thermal_add_hwmon_sysfs(tz); - if (result) - goto unregister; + if (!tz->tzp || !tz->tzp->no_hwmon) { + result = thermal_add_hwmon_sysfs(tz); + if (result) + goto unregister; + } mutex_lock(&thermal_list_lock); list_add_tail(&tz->node, &thermal_tz_list); diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c new file mode 100644 index 000000000000..eeef0e2498ca --- /dev/null +++ b/drivers/thermal/thermal_hwmon.c @@ -0,0 +1,269 @@ +/* + * thermal_hwmon.c - Generic Thermal Management hwmon support. + * + * Code based on Intel thermal_core.c. Copyrights of the original code: + * Copyright (C) 2008 Intel Corp + * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> + * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> + * + * Copyright (C) 2013 Texas Instruments + * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.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/hwmon.h> +#include <linux/thermal.h> +#include <linux/slab.h> +#include <linux/err.h> +#include "thermal_hwmon.h" + +/* hwmon sys I/F */ +/* thermal zone devices with the same type share one hwmon device */ +struct thermal_hwmon_device { + char type[THERMAL_NAME_LENGTH]; + struct device *device; + int count; + struct list_head tz_list; + struct list_head node; +}; + +struct thermal_hwmon_attr { + struct device_attribute attr; + char name[16]; +}; + +/* one temperature input for each thermal zone */ +struct thermal_hwmon_temp { + struct list_head hwmon_node; + struct thermal_zone_device *tz; + struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ + struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ +}; + +static LIST_HEAD(thermal_hwmon_list); + +static DEFINE_MUTEX(thermal_hwmon_list_lock); + +static ssize_t +name_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", hwmon->type); +} +static DEVICE_ATTR(name, 0444, name_show, NULL); + +static ssize_t +temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + long temperature; + int ret; + struct thermal_hwmon_attr *hwmon_attr + = container_of(attr, struct thermal_hwmon_attr, attr); + struct thermal_hwmon_temp *temp + = container_of(hwmon_attr, struct thermal_hwmon_temp, + temp_input); + struct thermal_zone_device *tz = temp->tz; + + ret = thermal_zone_get_temp(tz, &temperature); + + if (ret) + return ret; + + return sprintf(buf, "%ld\n", temperature); +} + +static ssize_t +temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct thermal_hwmon_attr *hwmon_attr + = container_of(attr, struct thermal_hwmon_attr, attr); + struct thermal_hwmon_temp *temp + = container_of(hwmon_attr, struct thermal_hwmon_temp, + temp_crit); + struct thermal_zone_device *tz = temp->tz; + long temperature; + int ret; + + ret = tz->ops->get_trip_temp(tz, 0, &temperature); + if (ret) + return ret; + + return sprintf(buf, "%ld\n", temperature); +} + + +static struct thermal_hwmon_device * +thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) +{ + struct thermal_hwmon_device *hwmon; + + mutex_lock(&thermal_hwmon_list_lock); + list_for_each_entry(hwmon, &thermal_hwmon_list, node) + if (!strcmp(hwmon->type, tz->type)) { + mutex_unlock(&thermal_hwmon_list_lock); + return hwmon; + } + mutex_unlock(&thermal_hwmon_list_lock); + + return NULL; +} + +/* Find the temperature input matching a given thermal zone */ +static struct thermal_hwmon_temp * +thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, + const struct thermal_zone_device *tz) +{ + struct thermal_hwmon_temp *temp; + + mutex_lock(&thermal_hwmon_list_lock); + list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) + if (temp->tz == tz) { + mutex_unlock(&thermal_hwmon_list_lock); + return temp; + } + mutex_unlock(&thermal_hwmon_list_lock); + + return NULL; +} + +int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) +{ + struct thermal_hwmon_device *hwmon; + struct thermal_hwmon_temp *temp; + int new_hwmon_device = 1; + int result; + + hwmon = thermal_hwmon_lookup_by_type(tz); + if (hwmon) { + new_hwmon_device = 0; + goto register_sys_interface; + } + + hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + INIT_LIST_HEAD(&hwmon->tz_list); + strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); + hwmon->device = hwmon_device_register(&tz->device); + if (IS_ERR(hwmon->device)) { + result = PTR_ERR(hwmon->device); + goto free_mem; + } + dev_set_drvdata(hwmon->device, hwmon); + result = device_create_file(hwmon->device, &dev_attr_name); + if (result) + goto free_mem; + + register_sys_interface: + temp = kzalloc(sizeof(*temp), GFP_KERNEL); + if (!temp) { + result = -ENOMEM; + goto unregister_name; + } + + temp->tz = tz; + hwmon->count++; + + 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; + temp->temp_input.attr.show = temp_input_show; + sysfs_attr_init(&temp->temp_input.attr.attr); + result = device_create_file(hwmon->device, &temp->temp_input.attr); + if (result) + goto free_temp_mem; + + if (tz->ops->get_crit_temp) { + unsigned long temperature; + if (!tz->ops->get_crit_temp(tz, &temperature)) { + 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; + temp->temp_crit.attr.show = temp_crit_show; + sysfs_attr_init(&temp->temp_crit.attr.attr); + result = device_create_file(hwmon->device, + &temp->temp_crit.attr); + if (result) + goto unregister_input; + } + } + + mutex_lock(&thermal_hwmon_list_lock); + if (new_hwmon_device) + list_add_tail(&hwmon->node, &thermal_hwmon_list); + list_add_tail(&temp->hwmon_node, &hwmon->tz_list); + mutex_unlock(&thermal_hwmon_list_lock); + + return 0; + + unregister_input: + device_remove_file(hwmon->device, &temp->temp_input.attr); + free_temp_mem: + kfree(temp); + unregister_name: + if (new_hwmon_device) { + device_remove_file(hwmon->device, &dev_attr_name); + hwmon_device_unregister(hwmon->device); + } + free_mem: + if (new_hwmon_device) + kfree(hwmon); + + return result; +} + +void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) +{ + struct thermal_hwmon_device *hwmon; + struct thermal_hwmon_temp *temp; + + hwmon = thermal_hwmon_lookup_by_type(tz); + if (unlikely(!hwmon)) { + /* Should never happen... */ + dev_dbg(&tz->device, "hwmon device lookup failed!\n"); + return; + } + + temp = thermal_hwmon_lookup_temp(hwmon, tz); + if (unlikely(!temp)) { + /* Should never happen... */ + dev_dbg(&tz->device, "temperature input lookup failed!\n"); + return; + } + + device_remove_file(hwmon->device, &temp->temp_input.attr); + if (tz->ops->get_crit_temp) + device_remove_file(hwmon->device, &temp->temp_crit.attr); + + mutex_lock(&thermal_hwmon_list_lock); + list_del(&temp->hwmon_node); + kfree(temp); + if (!list_empty(&hwmon->tz_list)) { + mutex_unlock(&thermal_hwmon_list_lock); + return; + } + list_del(&hwmon->node); + mutex_unlock(&thermal_hwmon_list_lock); + + device_remove_file(hwmon->device, &dev_attr_name); + hwmon_device_unregister(hwmon->device); + kfree(hwmon); +} diff --git a/drivers/thermal/thermal_hwmon.h b/drivers/thermal/thermal_hwmon.h new file mode 100644 index 000000000000..c798fdb2ae43 --- /dev/null +++ b/drivers/thermal/thermal_hwmon.h @@ -0,0 +1,49 @@ +/* + * thermal_hwmon.h - Generic Thermal Management hwmon support. + * + * Code based on Intel thermal_core.c. Copyrights of the original code: + * Copyright (C) 2008 Intel Corp + * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> + * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> + * + * Copyright (C) 2013 Texas Instruments + * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#ifndef __THERMAL_HWMON_H__ +#define __THERMAL_HWMON_H__ + +#include <linux/thermal.h> + +#ifdef CONFIG_THERMAL_HWMON +int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz); +void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz); +#else +static int +thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) +{ + return 0; +} + +static void +thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) +{ +} +#endif + +#endif /* __THERMAL_HWMON_H__ */ diff --git a/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c index e5d8326a54d6..a4929272074f 100644 --- a/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c +++ b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c @@ -42,6 +42,7 @@ dra752_core_temp_sensor_registers = { .mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_CORE_MASK, .mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_CORE_MASK, .mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, + .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, .mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_CORE_MASK, .mask_clear_mask = DRA752_BANDGAP_CTRL_1_CLEAR_CORE_MASK, .mask_clear_accum_mask = DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_CORE_MASK, @@ -77,6 +78,7 @@ dra752_iva_temp_sensor_registers = { .mask_hot_mask = DRA752_BANDGAP_CTRL_2_MASK_HOT_IVA_MASK, .mask_cold_mask = DRA752_BANDGAP_CTRL_2_MASK_COLD_IVA_MASK, .mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, + .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, .mask_freeze_mask = DRA752_BANDGAP_CTRL_2_FREEZE_IVA_MASK, .mask_clear_mask = DRA752_BANDGAP_CTRL_2_CLEAR_IVA_MASK, .mask_clear_accum_mask = DRA752_BANDGAP_CTRL_2_CLEAR_ACCUM_IVA_MASK, @@ -112,6 +114,7 @@ dra752_mpu_temp_sensor_registers = { .mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_MPU_MASK, .mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_MPU_MASK, .mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, + .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, .mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_MPU_MASK, .mask_clear_mask = DRA752_BANDGAP_CTRL_1_CLEAR_MPU_MASK, .mask_clear_accum_mask = DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_MPU_MASK, @@ -147,6 +150,7 @@ dra752_dspeve_temp_sensor_registers = { .mask_hot_mask = DRA752_BANDGAP_CTRL_2_MASK_HOT_DSPEVE_MASK, .mask_cold_mask = DRA752_BANDGAP_CTRL_2_MASK_COLD_DSPEVE_MASK, .mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, + .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, .mask_freeze_mask = DRA752_BANDGAP_CTRL_2_FREEZE_DSPEVE_MASK, .mask_clear_mask = DRA752_BANDGAP_CTRL_2_CLEAR_DSPEVE_MASK, .mask_clear_accum_mask = DRA752_BANDGAP_CTRL_2_CLEAR_ACCUM_DSPEVE_MASK, @@ -182,6 +186,7 @@ dra752_gpu_temp_sensor_registers = { .mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_GPU_MASK, .mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_GPU_MASK, .mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, + .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, .mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_GPU_MASK, .mask_clear_mask = DRA752_BANDGAP_CTRL_1_CLEAR_GPU_MASK, .mask_clear_accum_mask = DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_GPU_MASK, diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index 9dfd47196e63..74c0e3474d6e 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -1020,9 +1020,13 @@ int ti_bandgap_get_trend(struct ti_bandgap *bgp, int id, int *trend) /* Fetch the update interval */ ret = ti_bandgap_read_update_interval(bgp, id, &interval); - if (ret || !interval) + if (ret) goto unfreeze; + /* Set the interval to 1 ms if bandgap counter delay is not set */ + if (interval == 0) + interval = 1; + *trend = (t1 - t2) / interval; dev_dbg(bgp->dev, "The temperatures are t1 = %d and t2 = %d and trend =%d\n", diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 4c5f55c37349..4f8b9af54a5a 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -174,6 +174,9 @@ static int ti_thermal_set_mode(struct thermal_zone_device *thermal, enum thermal_device_mode mode) { struct ti_thermal_data *data = thermal->devdata; + struct ti_bandgap *bgp; + + bgp = data->bgp; if (!data->ti_thermal) { dev_notice(&thermal->device, "thermal zone not registered\n"); @@ -190,6 +193,8 @@ static int ti_thermal_set_mode(struct thermal_zone_device *thermal, mutex_unlock(&data->ti_thermal->lock); data->mode = mode; + ti_bandgap_write_update_interval(bgp, data->sensor_id, + data->ti_thermal->polling_delay); thermal_zone_device_update(data->ti_thermal); dev_dbg(&thermal->device, "thermal polling set for duration=%d msec\n", data->ti_thermal->polling_delay); @@ -313,6 +318,8 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, } data->ti_thermal->polling_delay = FAST_TEMP_MONITORING_RATE; ti_bandgap_set_sensor_data(bgp, id, data); + ti_bandgap_write_update_interval(bgp, data->sensor_id, + data->ti_thermal->polling_delay); return 0; } |