From ca3de46b50809000b5ba708634e26ad979a4a63a Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Mon, 24 Jun 2013 14:30:44 +0800 Subject: thermal: add imx thermal driver support This is based on the initial imx thermal work done by Rob Lee (Not sure if the email address is still valid). Since he is no longer interested in the work and I have rewritten a significant amount of the code, I just took the authorship over from him. It adds the imx thermal support using Temperature Monitor (TEMPMON) block found on some Freescale i.MX SoCs. The driver uses syscon regmap interface to access TEMPMON control registers and calibration data, and supports cpufreq as the cooling device. Signed-off-by: Shawn Guo Signed-off-by: Eduardo Valentin diff --git a/Documentation/devicetree/bindings/thermal/imx-thermal.txt b/Documentation/devicetree/bindings/thermal/imx-thermal.txt new file mode 100644 index 0000000..541c25e --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt @@ -0,0 +1,17 @@ +* Temperature Monitor (TEMPMON) on Freescale i.MX SoCs + +Required properties: +- compatible : "fsl,imx6q-thermal" +- fsl,tempmon : phandle pointer to system controller that contains TEMPMON + control registers, e.g. ANATOP on imx6q. +- fsl,tempmon-data : phandle pointer to fuse controller that contains TEMPMON + calibration data, e.g. OCOTP on imx6q. The details about calibration data + can be found in SoC Reference Manual. + +Example: + +tempmon { + compatible = "fsl,imx6q-tempmon"; + fsl,tempmon = <&anatop>; + fsl,tempmon-data = <&ocotp>; +}; diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e988c81..69eed55 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -91,6 +91,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 diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 67184a2..dff19c6 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o 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/imx_thermal.c b/drivers/thermal/imx_thermal.c new file mode 100644 index 0000000..d16c33c --- /dev/null +++ b/drivers/thermal/imx_thermal.c @@ -0,0 +1,397 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_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 + +/* + * The maximum die temperature on imx parts is 105C, let's give some cushion + * for noise and possible temperature rise between measurements. + */ +#define IMX_TEMP_CRITICAL 100000 + +#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() */ +}; + +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; + static unsigned long last_temp; + unsigned int n_meas; + u32 val; + + /* + * 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); + + /* + * According to the temp sensor designers, it may require up to ~17us + * to complete a measurement. + */ + usleep_range(20, 50); + + regmap_read(map, TEMPSENSE0, &val); + 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; + + if (*temp != last_temp) { + dev_dbg(&tz->device, "millicelsius: %ld\n", *temp); + last_temp = *temp; + } + + 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; + + if (mode == THERMAL_DEVICE_ENABLED) { + tz->polling_delay = IMX_POLLING_DELAY; + tz->passive_delay = IMX_PASSIVE_DELAY; + } else { + tz->polling_delay = 0; + tz->passive_delay = 0; + } + + 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) +{ + *temp = IMX_TEMP_CRITICAL; + return 0; +} + +static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip, + unsigned long *temp) +{ + *temp = (trip == IMX_TRIP_PASSIVE) ? IMX_TEMP_PASSIVE : + IMX_TEMP_CRITICAL; + 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, +}; + +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; + + return 0; +} + +static int imx_thermal_probe(struct platform_device *pdev) +{ + struct imx_thermal_data *data; + struct cpumask clip_cpus; + struct regmap *map; + 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; + + 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, 0, 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; + } + + 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); + + 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"); -- cgit v0.10.2 From c68213780e5a443d50d9f7291203591b0c5f9b60 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:22 +0530 Subject: thermal: exynos: Moving exynos thermal files into samsung directory This movement of files is done for easy maintenance and adding more new sensor's support for exynos platform easily . This will also help in bifurcating exynos common, sensor driver and sensor data related parts. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e988c81..34fa688 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -114,14 +114,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 +176,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 67184a2..1f27ada 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -17,7 +17,7 @@ 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 diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c deleted file mode 100644 index 9af4b93..0000000 --- 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 - * Amit Daniel Kachhap - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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 "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:exynos-tmu"); diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig new file mode 100644 index 0000000..2d3d9dc --- /dev/null +++ b/drivers/thermal/samsung/Kconfig @@ -0,0 +1,9 @@ +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. This helps in registering + the exynos thermal driver with the core thermal layer and cpu + cooling API's. diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile new file mode 100644 index 0000000..1fe6d93 --- /dev/null +++ b/drivers/thermal/samsung/Makefile @@ -0,0 +1,4 @@ +# +# Samsung thermal specific Makefile +# +obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o diff --git a/drivers/thermal/samsung/exynos_thermal.c b/drivers/thermal/samsung/exynos_thermal.c new file mode 100644 index 0000000..9af4b93 --- /dev/null +++ b/drivers/thermal/samsung/exynos_thermal.c @@ -0,0 +1,1059 @@ +/* + * exynos_thermal.c - Samsung EXYNOS TMU (Thermal Management Unit) + * + * Copyright (C) 2011 Samsung Electronics + * Donggeun Kim + * Amit Daniel Kachhap + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:exynos-tmu"); -- cgit v0.10.2 From c657dcd1988e561b587a07baa3978a98780ae62a Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:23 +0530 Subject: thermal: exynos: Use ARCH_HAS_BANDGAP config to know the supported soc's This patch uses the recently added config sybmol ARCH_HAS_BANDGAP to enable the TMU driver. This will allow adding support for new soc easily as now it is the platform responsibility to enable this config symbol for a particular soc. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index 2d3d9dc..883a8a8 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -1,6 +1,6 @@ config EXYNOS_THERMAL tristate "Temperature sensor on Samsung EXYNOS" - depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) + depends on ARCH_HAS_BANDGAP depends on CPU_THERMAL help If you say yes here you get support for TMU (Thermal Management -- cgit v0.10.2 From 44328fcc4964d04a8e70e14fd443a8389e98a1de Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:24 +0530 Subject: thermal: exynos: Remove un-necessary CPU_THERMAL dependency This patch removes the dependency on CPU_THERMAL for compiling TMU driver. This is useful for cases when only TMU controller needs to be initialised without cpu cooling action. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index 883a8a8..2cf31ad 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -1,7 +1,6 @@ config EXYNOS_THERMAL tristate "Temperature sensor on Samsung EXYNOS" depends on ARCH_HAS_BANDGAP - depends on CPU_THERMAL help If you say yes here you get support for TMU (Thermal Management Unit) on SAMSUNG EXYNOS series of SoC. This helps in registering -- cgit v0.10.2 From 1b678641c24f035f020bcecf8a92cde145a49724 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:25 +0530 Subject: thermal: exynos: Bifurcate exynos thermal common and tmu controller code This code bifurcates exynos thermal implementation into common and sensor specific parts. The common thermal code interacts with core thermal layer and core cpufreq cooling parts and is independent of SOC specific driver. This change is needed to cleanly add support for new TMU sensors. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index 2cf31ad..f8100b1 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -1,8 +1,17 @@ config EXYNOS_THERMAL - tristate "Temperature sensor on Samsung EXYNOS" + tristate "Exynos thermal management unit driver" depends on ARCH_HAS_BANDGAP help - If you say yes here you get support for TMU (Thermal Management - Unit) on SAMSUNG EXYNOS series of SoC. This helps in registering - the exynos thermal driver with the core thermal layer and cpu - cooling API's. + If you say yes here you get support for the TMU (Thermal Management + Unit) driver for SAMSUNG EXYNOS series of soc. This driver initialises + the TMU, reports temperature and handles cooling action if defined. + This driver uses the exynos core thermal API's. + +config EXYNOS_THERMAL_CORE + bool "Core thermal framework support for EXYNOS SOC's" + 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 API's. diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile index 1fe6d93..6227d4f 100644 --- a/drivers/thermal/samsung/Makefile +++ b/drivers/thermal/samsung/Makefile @@ -1,4 +1,6 @@ # # Samsung thermal specific Makefile # -obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_EXYNOS_THERMAL) += exynos_soc_thermal.o +exynos_soc_thermal-y := exynos_thermal.o +exynos_soc_thermal-$(CONFIG_EXYNOS_THERMAL_CORE) += exynos_thermal_common.o diff --git a/drivers/thermal/samsung/exynos_thermal.c b/drivers/thermal/samsung/exynos_thermal.c index 9af4b93..f3500ab 100644 --- a/drivers/thermal/samsung/exynos_thermal.c +++ b/drivers/thermal/samsung/exynos_thermal.c @@ -21,23 +21,15 @@ * */ -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include +#include +#include #include +#include +#include + +#include "exynos_thermal_common.h" /* Exynos generic registers */ #define EXYNOS_TMU_REG_TRIMINFO 0x0 @@ -88,16 +80,6 @@ #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 @@ -106,17 +88,6 @@ #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; @@ -129,384 +100,6 @@ struct exynos_tmu_data { 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. diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c new file mode 100644 index 0000000..f20f458 --- /dev/null +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -0,0 +1,385 @@ +/* + * exynos_thermal_common.c - Samsung EXYNOS common thermal file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap + * + * 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 +#include +#include +#include +#include + +#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; +}; + +static struct exynos_thermal_zone *th_zone; + +/* 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. + */ +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 */ +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 */ +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"); +} diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h new file mode 100644 index 0000000..8df1848 --- /dev/null +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -0,0 +1,83 @@ +/* + * exynos_thermal_common.h - Samsung EXYNOS common header file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap + * + * 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 4 + +#define ACTIVE_INTERVAL 500 +#define IDLE_INTERVAL 10000 +#define MCELSIUS 1000 + +/* CPU Zone information */ +#define PANIC_ZONE 4 +#define WARN_ZONE 3 +#define MONITOR_ZONE 2 +#define SAFE_ZONE 1 + +#define GET_ZONE(trip) (trip + 2) +#define GET_TRIP(zone) (zone - 2) + +#define EXYNOS_ZONE_COUNT 3 + +struct thermal_trip_point_conf { + int trip_val[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 *private_data; +}; + +/*Functions used exynos based thermal sensor driver*/ +#ifdef CONFIG_EXYNOS_THERMAL_CORE +void exynos_unregister_thermal(void); +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); +void exynos_report_trigger(void); +#else +static inline void +exynos_unregister_thermal(void) { return; } + +static inline int +exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) { return 0; } + +static inline void +exynos_report_trigger(void) { return; } + +#endif /* CONFIG_EXYNOS_THERMAL_CORE */ +#endif /* _EXYNOS_THERMAL_COMMON_H */ -- cgit v0.10.2 From 59dfa54c5edf4955e6e5469fd7cb8c0a5a813818 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:26 +0530 Subject: thermal: exynos: Rename exynos_thermal.c to exynos_tmu.c This patch renames exynos_thermal.c to exynos_tmu.c. This change is needed as this file now just contains exynos tmu driver related codes and thermal zone or cpufreq cooling registration related changes are not there anymore. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile index 6227d4f..22528d6 100644 --- a/drivers/thermal/samsung/Makefile +++ b/drivers/thermal/samsung/Makefile @@ -1,6 +1,6 @@ # # Samsung thermal specific Makefile # -obj-$(CONFIG_EXYNOS_THERMAL) += exynos_soc_thermal.o -exynos_soc_thermal-y := exynos_thermal.o -exynos_soc_thermal-$(CONFIG_EXYNOS_THERMAL_CORE) += exynos_thermal_common.o +obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +exynos_thermal-y := exynos_tmu.o +exynos_thermal-$(CONFIG_EXYNOS_THERMAL_CORE) += exynos_thermal_common.o diff --git a/drivers/thermal/samsung/exynos_thermal.c b/drivers/thermal/samsung/exynos_thermal.c deleted file mode 100644 index f3500ab..0000000 --- a/drivers/thermal/samsung/exynos_thermal.c +++ /dev/null @@ -1,652 +0,0 @@ -/* - * exynos_thermal.c - Samsung EXYNOS TMU (Thermal Management Unit) - * - * Copyright (C) 2011 Samsung Electronics - * Donggeun Kim - * Amit Daniel Kachhap - * - * 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 -#include -#include -#include -#include -#include -#include - -#include "exynos_thermal_common.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 - -#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 */ - -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; -}; - -/* - * 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 "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:exynos-tmu"); diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c new file mode 100644 index 0000000..2b2cc33 --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -0,0 +1,652 @@ +/* + * exynos_tmu.c - Samsung EXYNOS TMU (Thermal Management Unit) + * + * Copyright (C) 2011 Samsung Electronics + * Donggeun Kim + * Amit Daniel Kachhap + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "exynos_thermal_common.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 + +#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 */ + +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; +}; + +/* + * 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 "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:exynos-tmu"); -- cgit v0.10.2 From 0c1836a6563decc6de8622b2ed71523b3bdceb65 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:27 +0530 Subject: thermal: exynos: Move exynos_thermal.h from include/* to driver/* folder This patch renames and moves include/linux/platform_data/exynos_thermal.h to drivers/thermal/samsung/exynos_tmu.h. This file movement is needed as exynos SOC's are not supporting non-DT based platforms and this file now just contains exynos tmu driver related definations. Also struct freq_clip_table is now moved to exynos_thermal_common.c as it fixes the compilation issue occuring because now this new tmu header file is included in tmu driver c file and not in the common thermal header file. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c index f20f458..c9edc30 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.c +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -22,7 +22,6 @@ #include #include -#include #include #include diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h index 8df1848..57ce33b 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.h +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -44,6 +44,22 @@ #define EXYNOS_ZONE_COUNT 3 +/** + * 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_count; diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 2b2cc33..568c31d 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -27,9 +27,9 @@ #include #include #include -#include #include "exynos_thermal_common.h" +#include "exynos_tmu.h" /* Exynos generic registers */ #define EXYNOS_TMU_REG_TRIMINFO 0x0 diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h new file mode 100644 index 0000000..1857609 --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -0,0 +1,107 @@ +/* + * exynos_tmu.h - Samsung EXYNOS TMU (Thermal Management Unit) + * + * Copyright (C) 2011 Samsung Electronics + * Donggeun Kim + * Amit Daniel Kachhap + * + * 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 + +#include "exynos_thermal_common.h" + +enum calibration_type { + TYPE_ONE_POINT_TRIMMING, + TYPE_TWO_POINT_TRIMMING, + TYPE_NONE, +}; + +enum soc_type { + SOC_ARCH_EXYNOS4210 = 1, + SOC_ARCH_EXYNOS, +}; + +/** + * 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_level0_en: + * 1 = enable trigger_level0 interrupt, + * 0 = disable trigger_level0 interrupt + * @trigger_level1_en: + * 1 = enable trigger_level1 interrupt, + * 0 = disable trigger_level1 interrupt + * @trigger_level2_en: + * 1 = enable trigger_level2 interrupt, + * 0 = disable trigger_level2 interrupt + * @trigger_level3_en: + * 1 = enable trigger_level3 interrupt, + * 0 = disable trigger_level3 interrupt + * @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 + * @cal_type: calibration type for temperature + * @freq_clip_table: Table representing frequency reduction percentage. + * @freq_tab_count: Count of the above table as frequency reduction may + * applicable to only some of the trigger levels. + * + * This structure is required for configuration of exynos_tmu driver. + */ +struct exynos_tmu_platform_data { + u8 threshold; + u8 threshold_falling; + u8 trigger_levels[4]; + bool trigger_level0_en; + bool trigger_level1_en; + bool trigger_level2_en; + bool trigger_level3_en; + + u8 gain; + u8 reference_voltage; + u8 noise_cancel_mode; + u32 efuse_value; + + enum calibration_type cal_type; + enum soc_type type; + struct freq_clip_table freq_tab[4]; + unsigned int freq_tab_count; +}; +#endif /* _EXYNOS_TMU_H */ diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h deleted file mode 100644 index da7e627..0000000 --- a/include/linux/platform_data/exynos_thermal.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - * exynos_thermal.h - Samsung EXYNOS TMU (Thermal Management Unit) - * - * Copyright (C) 2011 Samsung Electronics - * Donggeun Kim - * - * 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 _LINUX_EXYNOS_THERMAL_H -#define _LINUX_EXYNOS_THERMAL_H -#include - -enum calibration_type { - TYPE_ONE_POINT_TRIMMING, - TYPE_TWO_POINT_TRIMMING, - TYPE_NONE, -}; - -enum soc_type { - SOC_ARCH_EXYNOS4210 = 1, - SOC_ARCH_EXYNOS, -}; -/** - * struct freq_clip_table - * @freq_clip_max: maximum frequency allowed for this cooling state. - * @temp_level: Temperature level at which the temperature clipping will - * happen. - * @mask_val: cpumask of the allowed cpu's where the clipping will take place. - * - * This structure is required to be filled and passed to the - * cpufreq_cooling_unregister function. - */ -struct freq_clip_table { - unsigned int freq_clip_max; - unsigned int temp_level; - const struct cpumask *mask_val; -}; - -/** - * struct 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_level0_en: - * 1 = enable trigger_level0 interrupt, - * 0 = disable trigger_level0 interrupt - * @trigger_level1_en: - * 1 = enable trigger_level1 interrupt, - * 0 = disable trigger_level1 interrupt - * @trigger_level2_en: - * 1 = enable trigger_level2 interrupt, - * 0 = disable trigger_level2 interrupt - * @trigger_level3_en: - * 1 = enable trigger_level3 interrupt, - * 0 = disable trigger_level3 interrupt - * @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 - * @cal_type: calibration type for temperature - * @freq_clip_table: Table representing frequency reduction percentage. - * @freq_tab_count: Count of the above table as frequency reduction may - * applicable to only some of the trigger levels. - * - * This structure is required for configuration of exynos_tmu driver. - */ -struct exynos_tmu_platform_data { - u8 threshold; - u8 threshold_falling; - u8 trigger_levels[4]; - bool trigger_level0_en; - bool trigger_level1_en; - bool trigger_level2_en; - bool trigger_level3_en; - - u8 gain; - u8 reference_voltage; - u8 noise_cancel_mode; - u32 efuse_value; - - enum calibration_type cal_type; - enum soc_type type; - struct freq_clip_table freq_tab[4]; - unsigned int freq_tab_count; -}; -#endif /* _LINUX_EXYNOS_THERMAL_H */ -- cgit v0.10.2 From e6b7991ed50fea9bf8b36e8a4794ee36d35e1651 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:28 +0530 Subject: thermal: exynos: Bifurcate exynos tmu driver and configuration data This code splits the exynos tmu driver code into SOC specific data parts. This will simplify adding new SOC specific data to the same TMU controller. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index f8100b1..b653f15 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -5,7 +5,8 @@ config EXYNOS_THERMAL If you say yes here you get support for the TMU (Thermal Management Unit) driver for SAMSUNG EXYNOS series of soc. This driver initialises the TMU, reports temperature and handles cooling action if defined. - This driver uses the exynos core thermal API's. + This driver uses the exynos core thermal API's and TMU configuration + data from the supported soc's. config EXYNOS_THERMAL_CORE bool "Core thermal framework support for EXYNOS SOC's" diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile index 22528d6..c09d830 100644 --- a/drivers/thermal/samsung/Makefile +++ b/drivers/thermal/samsung/Makefile @@ -3,4 +3,5 @@ # 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_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 568c31d..5df04a1 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -30,6 +30,7 @@ #include "exynos_thermal_common.h" #include "exynos_tmu.h" +#include "exynos_tmu_data.h" /* Exynos generic registers */ #define EXYNOS_TMU_REG_TRIMINFO 0x0 @@ -381,67 +382,6 @@ static struct thermal_sensor_conf exynos_sensor_conf = { .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[] = { { @@ -450,11 +390,11 @@ static const struct of_device_id exynos_tmu_match[] = { }, { .compatible = "samsung,exynos4412-tmu", - .data = (void *)EXYNOS_TMU_DRV_DATA, + .data = (void *)EXYNOS5250_TMU_DRV_DATA, }, { .compatible = "samsung,exynos5250-tmu", - .data = (void *)EXYNOS_TMU_DRV_DATA, + .data = (void *)EXYNOS5250_TMU_DRV_DATA, }, {}, }; @@ -468,7 +408,7 @@ static struct platform_device_id exynos_tmu_driver_ids[] = { }, { .name = "exynos5250-tmu", - .driver_data = (kernel_ulong_t)EXYNOS_TMU_DRV_DATA, + .driver_data = (kernel_ulong_t)EXYNOS5250_TMU_DRV_DATA, }, { }, }; diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c new file mode 100644 index 0000000..144ba44 --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -0,0 +1,79 @@ +/* + * exynos_tmu_data.c - Samsung EXYNOS tmu data file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap + * + * 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) +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, +}; +#endif + +#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412) +struct exynos_tmu_platform_data const exynos5250_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, +}; +#endif diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h new file mode 100644 index 0000000..b7835fe --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -0,0 +1,40 @@ +/* + * exynos_tmu_data.h - Samsung EXYNOS tmu data header file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap + * + * 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 + +#if defined(CONFIG_CPU_EXYNOS4210) +extern struct exynos_tmu_platform_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_platform_data const exynos5250_default_tmu_data; +#define EXYNOS5250_TMU_DRV_DATA (&exynos5250_default_tmu_data) +#else +#define EXYNOS5250_TMU_DRV_DATA (NULL) +#endif + +#endif /*_EXYNOS_TMU_DATA_H*/ -- cgit v0.10.2 From d0a0ce3e77c795258d47f9163e92d5031d0c5221 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:29 +0530 Subject: thermal: exynos: Add missing definations and code cleanup This patch adds some extra register bitfield definations and cleans up the code to prepare for moving register macros and definations inside the TMU data section. In this code cleanup the TMU enable bit is correctly used as bit0 and bit1 is taken care which is reserve bit. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Signed-off-by: Amit Daniel Kachhap Acked-by: Eduardo Valentin Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 5df04a1..fa33a48 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -43,9 +43,12 @@ #define EXYNOS_TMU_TRIM_TEMP_MASK 0xff #define EXYNOS_TMU_GAIN_SHIFT 8 +#define EXYNOS_TMU_GAIN_MASK 0xf #define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24 -#define EXYNOS_TMU_CORE_ON 3 -#define EXYNOS_TMU_CORE_OFF 2 +#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 #define EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET 50 /* Exynos4210 specific registers */ @@ -63,6 +66,7 @@ #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 */ @@ -72,17 +76,30 @@ #define EXYNOS_EMUL_CON 0x80 #define EXYNOS_TRIMINFO_RELOAD 0x1 +#define EXYNOS_TRIMINFO_SHIFT 0x0 +#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_MUX_ADDR_VALUE 6 -#define EXYNOS_MUX_ADDR_SHIFT 20 #define EXYNOS_TMU_TRIP_MODE_SHIFT 13 +#define EXYNOS_TMU_TRIP_MODE_MASK 0x7 + +#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 EFUSE_MIN_VALUE 40 #define EFUSE_MAX_VALUE 100 #ifdef CONFIG_THERMAL_EMULATION #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 @@ -261,24 +278,37 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on) mutex_lock(&data->lock); clk_enable(data->clk); - con = pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT | - pdata->gain << EXYNOS_TMU_GAIN_SHIFT; + con = readl(data->base + EXYNOS_TMU_REG_CONTROL); - 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 (pdata->reference_voltage) { + con &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << + EXYNOS_TMU_REF_VOLTAGE_SHIFT); + con |= pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT; + } + + if (pdata->gain) { + con &= ~(EXYNOS_TMU_GAIN_MASK << EXYNOS_TMU_GAIN_SHIFT); + con |= (pdata->gain << EXYNOS_TMU_GAIN_SHIFT); + } + + if (pdata->noise_cancel_mode) { + con &= ~(EXYNOS_TMU_TRIP_MODE_MASK << + EXYNOS_TMU_TRIP_MODE_SHIFT); + con |= (pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_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; + con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); + interrupt_en = + pdata->trigger_level3_en << EXYNOS_TMU_INTEN_RISE3_SHIFT | + pdata->trigger_level2_en << EXYNOS_TMU_INTEN_RISE2_SHIFT | + pdata->trigger_level1_en << EXYNOS_TMU_INTEN_RISE1_SHIFT | + pdata->trigger_level0_en << EXYNOS_TMU_INTEN_RISE0_SHIFT; if (pdata->threshold_falling) - interrupt_en |= interrupt_en << 16; + interrupt_en |= + interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT; } else { - con |= EXYNOS_TMU_CORE_OFF; + con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); interrupt_en = 0; /* Disable all interrupts */ } writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN); -- cgit v0.10.2 From bb34b4c8cfd8538376666f574ed90b03ea2b0446 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:30 +0530 Subject: thermal: exynos: Add extra entries in the tmu platform data This patch adds entries min_efuse_value, max_efuse_value, default_temp_offset, trigger_type, cal_type, trim_first_point, trim_second_point, max_trigger_level trigger_enable in the TMU platform data structure. Also the driver is modified to use the data passed by these new platform memebers instead of the constant macros. All these changes helps in separating the SOC specific data part from the TMU driver. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Signed-off-by: Amit Daniel Kachhap Acked-by: Eduardo Valentin Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h index 57ce33b..fee1588 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.h +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -44,6 +44,13 @@ #define EXYNOS_ZONE_COUNT 3 +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. diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index fa33a48..401ec98 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -49,7 +49,6 @@ #define EXYNOS_TMU_BUF_SLOPE_SEL_MASK 0xf #define EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT 8 #define EXYNOS_TMU_CORE_EN_SHIFT 0 -#define EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET 50 /* Exynos4210 specific registers */ #define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44 @@ -94,9 +93,6 @@ #define EXYNOS_TMU_INTEN_FALL1_SHIFT 20 #define EXYNOS_TMU_INTEN_FALL2_SHIFT 24 -#define EFUSE_MIN_VALUE 40 -#define EFUSE_MAX_VALUE 100 - #ifdef CONFIG_THERMAL_EMULATION #define EXYNOS_EMUL_TIME 0x57F0 #define EXYNOS_EMUL_TIME_MASK 0xffff @@ -136,15 +132,16 @@ static int temp_to_code(struct exynos_tmu_data *data, u8 temp) switch (pdata->cal_type) { case TYPE_TWO_POINT_TRIMMING: - temp_code = (temp - 25) * - (data->temp_error2 - data->temp_error1) / - (85 - 25) + data->temp_error1; + 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 - 25; + temp_code = temp + data->temp_error1 - pdata->first_point_trim; break; default: - temp_code = temp + EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET; + temp_code = temp + pdata->default_temp_offset; break; } out: @@ -169,14 +166,16 @@ static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code) switch (pdata->cal_type) { case TYPE_TWO_POINT_TRIMMING: - temp = (temp_code - data->temp_error1) * (85 - 25) / - (data->temp_error2 - data->temp_error1) + 25; + 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 + 25; + temp = temp_code - data->temp_error1 + pdata->first_point_trim; break; default: - temp = temp_code - EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET; + temp = temp_code - pdata->default_temp_offset; break; } out: @@ -209,8 +208,8 @@ static int exynos_tmu_initialize(struct platform_device *pdev) 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) || + if ((pdata->min_efuse_value > data->temp_error1) || + (data->temp_error1 > pdata->max_efuse_value) || (data->temp_error2 != 0)) data->temp_error1 = pdata->efuse_value; @@ -300,10 +299,10 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on) if (on) { con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); interrupt_en = - pdata->trigger_level3_en << EXYNOS_TMU_INTEN_RISE3_SHIFT | - pdata->trigger_level2_en << EXYNOS_TMU_INTEN_RISE2_SHIFT | - pdata->trigger_level1_en << EXYNOS_TMU_INTEN_RISE1_SHIFT | - pdata->trigger_level0_en << EXYNOS_TMU_INTEN_RISE0_SHIFT; + pdata->trigger_enable[3] << EXYNOS_TMU_INTEN_RISE3_SHIFT | + pdata->trigger_enable[2] << EXYNOS_TMU_INTEN_RISE2_SHIFT | + pdata->trigger_enable[1] << EXYNOS_TMU_INTEN_RISE1_SHIFT | + pdata->trigger_enable[0] << EXYNOS_TMU_INTEN_RISE0_SHIFT; if (pdata->threshold_falling) interrupt_en |= interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT; @@ -533,9 +532,9 @@ static int exynos_tmu_probe(struct platform_device *pdev) /* 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; + exynos_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 < exynos_sensor_conf.trip_data.trip_count; i++) exynos_sensor_conf.trip_data.trip_val[i] = diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h index 1857609..473acae 100644 --- a/drivers/thermal/samsung/exynos_tmu.h +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -32,6 +32,11 @@ enum calibration_type { TYPE_NONE, }; +enum calibration_mode { + SW_MODE, + HW_MODE, +}; + enum soc_type { SOC_ARCH_EXYNOS4210 = 1, SOC_ARCH_EXYNOS, @@ -57,18 +62,15 @@ enum soc_type { * 3: temperature for trigger_level3 interrupt * condition for trigger_level3 interrupt: * current temperature > threshold + trigger_levels[3] - * @trigger_level0_en: - * 1 = enable trigger_level0 interrupt, - * 0 = disable trigger_level0 interrupt - * @trigger_level1_en: - * 1 = enable trigger_level1 interrupt, - * 0 = disable trigger_level1 interrupt - * @trigger_level2_en: - * 1 = enable trigger_level2 interrupt, - * 0 = disable trigger_level2 interrupt - * @trigger_level3_en: - * 1 = enable trigger_level3 interrupt, - * 0 = disable trigger_level3 interrupt + * @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 @@ -78,7 +80,13 @@ enum soc_type { * 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. @@ -88,18 +96,23 @@ enum soc_type { struct exynos_tmu_platform_data { u8 threshold; u8 threshold_falling; - u8 trigger_levels[4]; - bool trigger_level0_en; - bool trigger_level1_en; - bool trigger_level2_en; - bool trigger_level3_en; - + 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; diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index 144ba44..24934d7 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -30,13 +30,22 @@ struct exynos_tmu_platform_data const exynos4210_default_tmu_data = { .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, + .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, @@ -56,15 +65,24 @@ struct exynos_tmu_platform_data const exynos5250_default_tmu_data = { .trigger_levels[0] = 85, .trigger_levels[1] = 103, .trigger_levels[2] = 110, - .trigger_level0_en = 1, - .trigger_level1_en = 1, - .trigger_level2_en = 1, - .trigger_level3_en = 0, + .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 = 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, -- cgit v0.10.2 From b8d582b9405c75506f4adff9abd4afbe7b698b61 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:31 +0530 Subject: thermal: exynos: Move register definitions from driver to data file This patch migrates the TMU register definition/bitfields to data file. This is needed to support SoC's which use the same TMU controller but register validity, offsets or bitfield may slightly vary across SOC's. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 401ec98..6fd776f 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -32,76 +32,6 @@ #include "exynos_tmu.h" #include "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_TRIM_TEMP_MASK 0xff -#define EXYNOS_TMU_GAIN_SHIFT 8 -#define EXYNOS_TMU_GAIN_MASK 0xf -#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 0x1 -#define EXYNOS_TRIMINFO_SHIFT 0x0 -#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_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 - -#ifdef CONFIG_THERMAL_EMULATION -#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 -#endif /* CONFIG_THERMAL_EMULATION */ - struct exynos_tmu_data { struct exynos_tmu_platform_data *pdata; struct resource *mem; @@ -186,6 +116,7 @@ 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; unsigned int rising_threshold = 0, falling_threshold = 0; int ret = 0, threshold_code, i, trigger_levs = 0; @@ -193,20 +124,20 @@ static int exynos_tmu_initialize(struct platform_device *pdev) mutex_lock(&data->lock); clk_enable(data->clk); - status = readb(data->base + EXYNOS_TMU_REG_STATUS); + status = readb(data->base + reg->tmu_status); if (!status) { ret = -EBUSY; goto out; } - if (data->soc == SOC_ARCH_EXYNOS) { - __raw_writel(EXYNOS_TRIMINFO_RELOAD, - data->base + EXYNOS_TMU_TRIMINFO_CON); - } + if (data->soc == SOC_ARCH_EXYNOS) + __raw_writel(1, data->base + reg->triminfo_ctrl); + /* 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); + 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 ((pdata->min_efuse_value > data->temp_error1) || (data->temp_error1 > pdata->max_efuse_value) || @@ -226,13 +157,12 @@ static int exynos_tmu_initialize(struct platform_device *pdev) goto out; } writeb(threshold_code, - data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP); + data->base + reg->threshold_temp); for (i = 0; i < trigger_levs; i++) - writeb(pdata->trigger_levels[i], - data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4); + writeb(pdata->trigger_levels[i], data->base + + reg->threshold_th0 + i * sizeof(reg->threshold_th0)); - writel(EXYNOS4210_TMU_INTCLEAR_VAL, - data->base + EXYNOS_TMU_REG_INTCLEAR); + writel(reg->inten_rise_mask, data->base + reg->tmu_intclear); } else if (data->soc == SOC_ARCH_EXYNOS) { /* Write temperature code for rising and falling threshold */ for (i = 0; i < trigger_levs; i++) { @@ -254,12 +184,13 @@ static int exynos_tmu_initialize(struct platform_device *pdev) } writel(rising_threshold, - data->base + EXYNOS_THD_TEMP_RISE); + data->base + reg->threshold_th0); writel(falling_threshold, - data->base + EXYNOS_THD_TEMP_FALL); + data->base + reg->threshold_th1); - writel(EXYNOS_TMU_CLEAR_RISE_INT | EXYNOS_TMU_CLEAR_FALL_INT, - data->base + EXYNOS_TMU_REG_INTCLEAR); + writel((reg->inten_rise_mask << reg->inten_rise_shift) | + (reg->inten_fall_mask << reg->inten_fall_shift), + data->base + reg->tmu_intclear); } out: clk_disable(data->clk); @@ -272,46 +203,46 @@ 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; mutex_lock(&data->lock); clk_enable(data->clk); - con = readl(data->base + EXYNOS_TMU_REG_CONTROL); + con = readl(data->base + reg->tmu_ctrl); if (pdata->reference_voltage) { - con &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << - EXYNOS_TMU_REF_VOLTAGE_SHIFT); - con |= pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT; + con &= ~(reg->buf_vref_sel_mask << reg->buf_vref_sel_shift); + con |= pdata->reference_voltage << reg->buf_vref_sel_shift; } if (pdata->gain) { - con &= ~(EXYNOS_TMU_GAIN_MASK << EXYNOS_TMU_GAIN_SHIFT); - con |= (pdata->gain << EXYNOS_TMU_GAIN_SHIFT); + 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 &= ~(EXYNOS_TMU_TRIP_MODE_MASK << - EXYNOS_TMU_TRIP_MODE_SHIFT); - con |= (pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT); + con &= ~(reg->therm_trip_mode_mask << + reg->therm_trip_mode_shift); + con |= (pdata->noise_cancel_mode << reg->therm_trip_mode_shift); } if (on) { - con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); + con |= (1 << reg->core_en_shift); interrupt_en = - pdata->trigger_enable[3] << EXYNOS_TMU_INTEN_RISE3_SHIFT | - pdata->trigger_enable[2] << EXYNOS_TMU_INTEN_RISE2_SHIFT | - pdata->trigger_enable[1] << EXYNOS_TMU_INTEN_RISE1_SHIFT | - pdata->trigger_enable[0] << EXYNOS_TMU_INTEN_RISE0_SHIFT; + 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 (pdata->threshold_falling) interrupt_en |= - interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT; + interrupt_en << reg->inten_fall0_shift; } else { - con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); + con &= ~(1 << reg->core_en_shift); interrupt_en = 0; /* Disable all interrupts */ } - writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN); - writel(con, data->base + EXYNOS_TMU_REG_CONTROL); + writel(interrupt_en, data->base + reg->tmu_inten); + writel(con, data->base + reg->tmu_ctrl); clk_disable(data->clk); mutex_unlock(&data->lock); @@ -319,13 +250,15 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on) 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 + EXYNOS_TMU_REG_CURRENT_TEMP); + temp_code = readb(data->base + reg->tmu_cur_temp); temp = code_to_temp(data, temp_code); clk_disable(data->clk); @@ -338,7 +271,9 @@ static int exynos_tmu_read(struct exynos_tmu_data *data) static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) { struct exynos_tmu_data *data = drv_data; - unsigned int reg; + struct exynos_tmu_platform_data *pdata = data->pdata; + const struct exynos_tmu_registers *reg = pdata->registers; + unsigned int val; int ret = -EINVAL; if (data->soc == SOC_ARCH_EXYNOS4210) @@ -350,19 +285,19 @@ static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) mutex_lock(&data->lock); clk_enable(data->clk); - reg = readl(data->base + EXYNOS_EMUL_CON); + val = readl(data->base + reg->emul_con); if (temp) { temp /= MCELSIUS; - reg = (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT) | + val = (EXYNOS_EMUL_TIME << reg->emul_time_shift) | (temp_to_code(data, temp) - << EXYNOS_EMUL_DATA_SHIFT) | EXYNOS_EMUL_ENABLE; + << reg->emul_temp_shift) | EXYNOS_EMUL_ENABLE; } else { - reg &= ~EXYNOS_EMUL_ENABLE; + val &= ~EXYNOS_EMUL_ENABLE; } - writel(reg, data->base + EXYNOS_EMUL_CON); + writel(val, data->base + reg->emul_con); clk_disable(data->clk); mutex_unlock(&data->lock); @@ -379,17 +314,20 @@ 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; 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); + writel((reg->inten_rise_mask << reg->inten_rise_shift) | + (reg->inten_fall_mask << reg->inten_fall_shift), + data->base + reg->tmu_intclear); else - writel(EXYNOS4210_TMU_INTCLEAR_VAL, - data->base + EXYNOS_TMU_REG_INTCLEAR); + writel(reg->inten_rise_mask, data->base + reg->tmu_intclear); + clk_disable(data->clk); mutex_unlock(&data->lock); diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h index 473acae..ef5dc0e 100644 --- a/drivers/thermal/samsung/exynos_tmu.h +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -43,6 +43,136 @@ enum soc_type { }; /** + * 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. + * @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. + */ +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 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; +}; + +/** * struct exynos_tmu_platform_data * @threshold: basic temperature for generating interrupt * 25 <= threshold <= 125 [unit: degree Celsius] @@ -90,6 +220,8 @@ enum soc_type { * @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. * * This structure is required for configuration of exynos_tmu driver. */ @@ -116,5 +248,6 @@ struct exynos_tmu_platform_data { enum soc_type type; struct freq_clip_table freq_tab[4]; unsigned int freq_tab_count; + const struct exynos_tmu_registers *registers; }; #endif /* _EXYNOS_TMU_H */ diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index 24934d7..896aa2a 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -25,6 +25,28 @@ #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_intclear = EXYNOS_TMU_REG_INTCLEAR, +}; struct exynos_tmu_platform_data const exynos4210_default_tmu_data = { .threshold = 80, .trigger_levels[0] = 5, @@ -56,10 +78,46 @@ struct exynos_tmu_platform_data const exynos4210_default_tmu_data = { }, .freq_tab_count = 2, .type = SOC_ARCH_EXYNOS4210, + .registers = &exynos4210_tmu_registers, }; #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_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, +}; struct exynos_tmu_platform_data const exynos5250_default_tmu_data = { .threshold_falling = 10, .trigger_levels[0] = 85, @@ -93,5 +151,6 @@ struct exynos_tmu_platform_data const exynos5250_default_tmu_data = { }, .freq_tab_count = 2, .type = SOC_ARCH_EXYNOS, + .registers = &exynos5250_tmu_registers, }; #endif diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h index b7835fe..0e2244f 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.h +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -23,6 +23,74 @@ #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_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 + #if defined(CONFIG_CPU_EXYNOS4210) extern struct exynos_tmu_platform_data const exynos4210_default_tmu_data; #define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data) -- cgit v0.10.2 From 7ca04e587ee5d391fecd477f9a3dd725c3e21f23 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:32 +0530 Subject: thermal: exynos: Support thermal tripping TMU urgently sends active-high signal (thermal trip) to PMU, and thermal tripping by hardware logic. Thermal tripping means that PMU cuts off the whole power of SoC by controlling external voltage regulator. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Jonghwan Choi Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 6fd776f..33f494e 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -117,7 +117,7 @@ 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; + unsigned int status, trim_info = 0, con; unsigned int rising_threshold = 0, falling_threshold = 0; int ret = 0, threshold_code, i, trigger_levs = 0; @@ -144,10 +144,26 @@ static int exynos_tmu_initialize(struct platform_device *pdev) (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]) + 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 */ @@ -165,7 +181,8 @@ static int exynos_tmu_initialize(struct platform_device *pdev) writel(reg->inten_rise_mask, data->base + reg->tmu_intclear); } else if (data->soc == SOC_ARCH_EXYNOS) { /* Write temperature code for rising and falling threshold */ - for (i = 0; i < trigger_levs; i++) { + 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) { @@ -191,6 +208,24 @@ static int exynos_tmu_initialize(struct platform_device *pdev) 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; + } + rising_threshold |= threshold_code << 8 * i; + writel(rising_threshold, + data->base + reg->threshold_th0); + con = readl(data->base + reg->tmu_ctrl); + con |= (1 << reg->therm_trip_en_shift); + writel(con, data->base + reg->tmu_ctrl); + } } out: clk_disable(data->clk); diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index 896aa2a..6cac393 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -123,6 +123,7 @@ struct exynos_tmu_platform_data const exynos5250_default_tmu_data = { .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, @@ -130,6 +131,7 @@ struct exynos_tmu_platform_data const exynos5250_default_tmu_data = { .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, diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h index 0e2244f..4acf070 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.h +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -91,6 +91,8 @@ #define EXYNOS_EMUL_DATA_MASK 0xFF #define EXYNOS_EMUL_ENABLE 0x1 +#define EXYNOS_MAX_TRIGGER_PER_REG 4 + #if defined(CONFIG_CPU_EXYNOS4210) extern struct exynos_tmu_platform_data const exynos4210_default_tmu_data; #define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data) -- cgit v0.10.2 From a4463c4f669ac776a1f456fb51fd3c8c99ef904f Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:33 +0530 Subject: thermal: exynos: Fix to clear only the generated interrupts This patch uses the TMU status register to know the generated interrupts and only clear them in the interrupt handler. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 33f494e..f6f63ca 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -351,17 +351,16 @@ static void exynos_tmu_work(struct work_struct *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; exynos_report_trigger(); mutex_lock(&data->lock); clk_enable(data->clk); - if (data->soc == SOC_ARCH_EXYNOS) - writel((reg->inten_rise_mask << reg->inten_rise_shift) | - (reg->inten_fall_mask << reg->inten_fall_shift), - data->base + reg->tmu_intclear); - else - writel(reg->inten_rise_mask, data->base + reg->tmu_intclear); + /* 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); diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index 6cac393..217b8f5 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -45,6 +45,7 @@ static const struct exynos_tmu_registers exynos4210_tmu_registers = { .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_platform_data const exynos4210_default_tmu_data = { @@ -112,6 +113,7 @@ static const struct exynos_tmu_registers exynos5250_tmu_registers = { .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, -- cgit v0.10.2 From 4de0bdaa9677d11406c9becb70c60887c957e1f0 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:34 +0530 Subject: thermal: exynos: Add support for instance based register/unregister This code modifies the thermal driver to have multiple thermal zone support by replacing the global thermal zone variable with device data member of thermal_zone_device. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c index c9edc30..0b014e8 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.c +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -37,12 +37,11 @@ struct exynos_thermal_zone { bool bind; }; -static struct exynos_thermal_zone *th_zone; - /* 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; @@ -52,25 +51,26 @@ static int exynos_get_mode(struct thermal_zone_device *thermal, static int exynos_set_mode(struct thermal_zone_device *thermal, enum thermal_device_mode mode) { - if (!th_zone->therm_dev) { + struct exynos_thermal_zone *th_zone = thermal->devdata; + if (!th_zone) { pr_notice("thermal zone not registered\n"); return 0; } - mutex_lock(&th_zone->therm_dev->lock); + mutex_lock(&thermal->lock); if (mode == THERMAL_DEVICE_ENABLED && !th_zone->sensor_conf->trip_data.trigger_falling) - th_zone->therm_dev->polling_delay = IDLE_INTERVAL; + thermal->polling_delay = IDLE_INTERVAL; else - th_zone->therm_dev->polling_delay = 0; + thermal->polling_delay = 0; - mutex_unlock(&th_zone->therm_dev->lock); + mutex_unlock(&thermal->lock); th_zone->mode = mode; - thermal_zone_device_update(th_zone->therm_dev); + thermal_zone_device_update(thermal); pr_info("thermal polling set for duration=%d msec\n", - th_zone->therm_dev->polling_delay); + thermal->polling_delay); return 0; } @@ -97,6 +97,8 @@ static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip, static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip, unsigned long *temp) { + struct exynos_thermal_zone *th_zone = thermal->devdata; + if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE)) return -EINVAL; @@ -123,6 +125,7 @@ static int exynos_bind(struct thermal_zone_device *thermal, { 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; @@ -169,6 +172,7 @@ 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) @@ -211,6 +215,7 @@ static int exynos_unbind(struct thermal_zone_device *thermal, 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) { @@ -230,6 +235,7 @@ static int exynos_set_emul_temp(struct thermal_zone_device *thermal, { void *data; int ret = -EINVAL; + struct exynos_thermal_zone *th_zone = thermal->devdata; if (!th_zone->sensor_conf) { pr_info("Temperature sensor not initialised\n"); @@ -277,14 +283,22 @@ static struct thermal_zone_device_ops const exynos_dev_ops = { * This function may be called from interrupt based temperature sensor * when threshold is changed. */ -void exynos_report_trigger(void) +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 (!th_zone || !th_zone->therm_dev) + 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]) @@ -322,6 +336,7 @@ 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"); @@ -343,7 +358,7 @@ int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) 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, + EXYNOS_ZONE_COUNT, 0, th_zone, &exynos_dev_ops, NULL, 0, sensor_conf->trip_data.trigger_falling ? 0 : IDLE_INTERVAL); @@ -353,23 +368,29 @@ int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) goto err_unregister; } th_zone->mode = THERMAL_DEVICE_ENABLED; + sensor_conf->pzone_data = th_zone; pr_info("Exynos: Kernel Thermal management registered\n"); return 0; err_unregister: - exynos_unregister_thermal(); + exynos_unregister_thermal(sensor_conf); return ret; } /* Un-Register with the in-kernel thermal management */ -void exynos_unregister_thermal(void) +void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) { int i; + struct exynos_thermal_zone *th_zone; - if (!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); diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h index fee1588..86c1f84 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.h +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -85,22 +85,23 @@ struct thermal_sensor_conf { struct thermal_trip_point_conf trip_data; struct thermal_cooling_conf cooling_data; void *private_data; + void *pzone_data; }; /*Functions used exynos based thermal sensor driver*/ #ifdef CONFIG_EXYNOS_THERMAL_CORE -void exynos_unregister_thermal(void); +void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf); int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); -void exynos_report_trigger(void); +void exynos_report_trigger(struct thermal_sensor_conf *sensor_conf); #else static inline void -exynos_unregister_thermal(void) { return; } +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(void) { return; } +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 index f6f63ca..a7bba69 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -345,6 +345,12 @@ static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) { return -EINVAL; } #endif/*CONFIG_THERMAL_EMULATION*/ +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, +}; + static void exynos_tmu_work(struct work_struct *work) { struct exynos_tmu_data *data = container_of(work, @@ -353,7 +359,7 @@ static void exynos_tmu_work(struct work_struct *work) const struct exynos_tmu_registers *reg = pdata->registers; unsigned int val_irq; - exynos_report_trigger(); + exynos_report_trigger(&exynos_sensor_conf); mutex_lock(&data->lock); clk_enable(data->clk); @@ -377,11 +383,6 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id) 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, -}; #ifdef CONFIG_OF static const struct of_device_id exynos_tmu_match[] = { @@ -541,7 +542,7 @@ static int exynos_tmu_remove(struct platform_device *pdev) exynos_tmu_control(pdev, false); - exynos_unregister_thermal(); + exynos_unregister_thermal(&exynos_sensor_conf); clk_unprepare(data->clk); -- cgit v0.10.2 From d58f0a6d8dd6af9f6b4fc4cb5f3f3c1379efc2ef Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:35 +0530 Subject: thermal: exynos: Modify private_data to appropriate name driver_data This patch renames member private_data to driver_data of the thermal zone registration structure as this item stores the driver related data and uses it to call the driver related callbacks. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c index 0b014e8..c6eab8c 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.c +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -222,7 +222,7 @@ static int exynos_get_temp(struct thermal_zone_device *thermal, pr_info("Temperature sensor not initialised\n"); return -EINVAL; } - data = th_zone->sensor_conf->private_data; + data = th_zone->sensor_conf->driver_data; *temp = th_zone->sensor_conf->read_temperature(data); /* convert the temperature into millicelsius */ *temp = *temp * MCELSIUS; @@ -241,7 +241,7 @@ static int exynos_set_emul_temp(struct thermal_zone_device *thermal, pr_info("Temperature sensor not initialised\n"); return -EINVAL; } - data = th_zone->sensor_conf->private_data; + 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; diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h index 86c1f84..6ca3096 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.h +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -84,7 +84,7 @@ struct thermal_sensor_conf { 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; + void *driver_data; void *pzone_data; }; diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index a7bba69..40e0cfd 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -504,7 +504,7 @@ static int exynos_tmu_probe(struct platform_device *pdev) exynos_tmu_control(pdev, true); /* Register the sensor with thermal management interface */ - (&exynos_sensor_conf)->private_data = data; + (&exynos_sensor_conf)->driver_data = data; exynos_sensor_conf.trip_data.trip_count = pdata->trigger_enable[0] + pdata->trigger_enable[1] + pdata->trigger_enable[2]+ pdata->trigger_enable[3]; -- cgit v0.10.2 From 23a3eb1062d18001f895bd73aae8554870875cb6 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:36 +0530 Subject: thermal: exynos: Return success even if no cooling data supplied This patch removes the error return in the bind/unbind routine as the platform may not register any cpufreq cooling data. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c index c6eab8c..99318e5 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.c +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -132,7 +132,7 @@ static int exynos_bind(struct thermal_zone_device *thermal, tab_size = data->cooling_data.freq_clip_count; if (tab_ptr == NULL || tab_size == 0) - return -EINVAL; + return 0; /* find the cooling device registered*/ for (i = 0; i < th_zone->cool_dev_size; i++) @@ -181,7 +181,7 @@ static int exynos_unbind(struct thermal_zone_device *thermal, tab_size = data->cooling_data.freq_clip_count; if (tab_size == 0) - return -EINVAL; + return 0; /* find the cooling device registered*/ for (i = 0; i < th_zone->cool_dev_size; i++) -- cgit v0.10.2 From 5c3cf5527e316973d7140a1258e156f3d4e4a8ac Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:37 +0530 Subject: thermal: exynos: Make the zone handling use trip information This code simplifies the zone handling to use the trip information passed by the TMU driver and not the hardcoded macros. This also helps in adding more zone support. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c index 99318e5..af0ae77 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.c +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -79,17 +79,24 @@ static int exynos_set_mode(struct thermal_zone_device *thermal, 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: + 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; - break; - default: + 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; } @@ -98,8 +105,9 @@ 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 < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE)) + if (trip < 0 || trip >= max_trip) return -EINVAL; *temp = th_zone->sensor_conf->trip_data.trip_val[trip]; @@ -113,10 +121,10 @@ static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip, 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; + 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 */ @@ -348,19 +356,28 @@ int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) 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; + /* + * 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])) { + pr_err("Failed to register cpufreq cooling device\n"); + ret = -EINVAL; + goto err_unregister; + } + th_zone->cool_dev_size++; } - th_zone->cool_dev_size++; - th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name, - EXYNOS_ZONE_COUNT, 0, th_zone, &exynos_dev_ops, NULL, 0, - sensor_conf->trip_data.trigger_falling ? - 0 : IDLE_INTERVAL); + 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)) { pr_err("Failed to register thermal zone device\n"); diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h index 6ca3096..bfcf69e 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.h +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -42,8 +42,6 @@ #define GET_ZONE(trip) (trip + 2) #define GET_TRIP(zone) (zone - 2) -#define EXYNOS_ZONE_COUNT 3 - enum trigger_type { THROTTLE_ACTIVE = 1, THROTTLE_PASSIVE, @@ -69,6 +67,7 @@ struct freq_clip_table { struct thermal_trip_point_conf { int trip_val[MAX_TRIP_COUNT]; + int trip_type[MAX_TRIP_COUNT]; int trip_count; unsigned char trigger_falling; }; diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 40e0cfd..acbd295 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -509,9 +509,12 @@ static int exynos_tmu_probe(struct platform_device *pdev) pdata->trigger_enable[1] + pdata->trigger_enable[2]+ pdata->trigger_enable[3]; - for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++) + 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.trip_type[i] = + pdata->trigger_type[i]; + } exynos_sensor_conf.trip_data.trigger_falling = pdata->threshold_falling; -- cgit v0.10.2 From 1cd1ecb611d8be48df571a46950411ecee1fe3a1 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:38 +0530 Subject: thermal: exynos: Remove non DT based support Recently non DT support from Exynos platform is removed and hence removing non DT support from the driver also. This will help in easy maintainence. Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index acbd295..4356118 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -403,19 +403,6 @@ static const struct of_device_id exynos_tmu_match[] = { 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)EXYNOS5250_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) { @@ -428,8 +415,7 @@ static inline struct exynos_tmu_platform_data *exynos_get_driver_data( return (struct exynos_tmu_platform_data *) match->data; } #endif - return (struct exynos_tmu_platform_data *) - platform_get_device_id(pdev)->driver_data; + return NULL; } static int exynos_tmu_probe(struct platform_device *pdev) @@ -586,7 +572,6 @@ static struct platform_driver exynos_tmu_driver = { }, .probe = exynos_tmu_probe, .remove = exynos_tmu_remove, - .id_table = exynos_tmu_driver_ids, }; module_platform_driver(exynos_tmu_driver); -- cgit v0.10.2 From cebe7373a7e659d29e939ed2ce379b478684793c Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:39 +0530 Subject: thermal: exynos: Add support to handle many instances of TMU This patch adds support to handle multiple instances of the TMU controllers. This is done by removing the static structure to register with the core thermal and creating it dynamically for each instance of the TMU controller. The interrupt is made shared type to handle shared interrupts. Now since the ISR needs the core thermal framework to be registered so request_irq is moved after the core registration is done. Also the identifier of the TMU controller is extracted from device tree alias. This will be used for TMU specific initialisation. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Signed-off-by: Amit Daniel Kachhap Acked-by: Eduardo Valentin Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h index bfcf69e..bc3016e 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.h +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -85,6 +85,7 @@ struct thermal_sensor_conf { struct thermal_cooling_conf cooling_data; void *driver_data; void *pzone_data; + struct device *dev; }; /*Functions used exynos based thermal sensor driver*/ diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 4356118..7f65fe0 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -26,15 +26,32 @@ #include #include #include +#include +#include #include #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. + * @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. + * @reg_conf: pointer to structure to register with core thermal. + */ struct exynos_tmu_data { + int id; struct exynos_tmu_platform_data *pdata; - struct resource *mem; void __iomem *base; int irq; enum soc_type soc; @@ -42,6 +59,7 @@ struct exynos_tmu_data { struct mutex lock; struct clk *clk; u8 temp_error1, temp_error2; + struct thermal_sensor_conf *reg_conf; }; /* @@ -345,12 +363,6 @@ static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) { return -EINVAL; } #endif/*CONFIG_THERMAL_EMULATION*/ -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, -}; - static void exynos_tmu_work(struct work_struct *work) { struct exynos_tmu_data *data = container_of(work, @@ -359,7 +371,7 @@ static void exynos_tmu_work(struct work_struct *work) const struct exynos_tmu_registers *reg = pdata->registers; unsigned int val_irq; - exynos_report_trigger(&exynos_sensor_conf); + exynos_report_trigger(data->reg_conf); mutex_lock(&data->lock); clk_enable(data->clk); @@ -404,33 +416,73 @@ MODULE_DEVICE_TABLE(of, exynos_tmu_match); #endif static inline struct exynos_tmu_platform_data *exynos_get_driver_data( - struct platform_device *pdev) + struct platform_device *pdev, int id) { #ifdef CONFIG_OF + struct exynos_tmu_init_data *data_table; + struct exynos_tmu_platform_data *tmu_data; 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; + 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); } #endif return NULL; } -static int exynos_tmu_probe(struct platform_device *pdev) +static int exynos_map_dt_data(struct platform_device *pdev) { - struct exynos_tmu_data *data; - struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data; - int ret, i; + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + struct exynos_tmu_platform_data *pdata; + struct resource res; + + if (!data) + return -ENODEV; - if (!pdata) - pdata = exynos_get_driver_data(pdev); + 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; + + 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) { @@ -438,25 +490,16 @@ static int exynos_tmu_probe(struct platform_device *pdev) 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; - } + platform_set_drvdata(pdev, data); + mutex_init(&data->lock); - INIT_WORK(&data->irq_work, exynos_tmu_work); + ret = exynos_map_dt_data(pdev); + if (ret) + return ret; - 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); + pdata = data->pdata; - 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; - } + INIT_WORK(&data->irq_work, exynos_tmu_work); data->clk = devm_clk_get(&pdev->dev, "tmu_apbif"); if (IS_ERR(data->clk)) { @@ -477,10 +520,6 @@ static int exynos_tmu_probe(struct platform_device *pdev) 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"); @@ -489,35 +528,54 @@ static int exynos_tmu_probe(struct platform_device *pdev) exynos_tmu_control(pdev, true); - /* Register the sensor with thermal management interface */ - (&exynos_sensor_conf)->driver_data = data; - exynos_sensor_conf.trip_data.trip_count = pdata->trigger_enable[0] + + /* 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 < exynos_sensor_conf.trip_data.trip_count; i++) { - exynos_sensor_conf.trip_data.trip_val[i] = + for (i = 0; i < sensor_conf->trip_data.trip_count; i++) { + sensor_conf->trip_data.trip_val[i] = pdata->threshold + pdata->trigger_levels[i]; - exynos_sensor_conf.trip_data.trip_type[i] = + sensor_conf->trip_data.trip_type[i] = pdata->trigger_type[i]; } - exynos_sensor_conf.trip_data.trigger_falling = pdata->threshold_falling; + sensor_conf->trip_data.trigger_falling = pdata->threshold_falling; - exynos_sensor_conf.cooling_data.freq_clip_count = - pdata->freq_tab_count; + 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 = + 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 = + sensor_conf->cooling_data.freq_data[i].temp_level = pdata->freq_tab[i].temp_level; } - - ret = exynos_register_thermal(&exynos_sensor_conf); + 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: @@ -531,7 +589,7 @@ static int exynos_tmu_remove(struct platform_device *pdev) exynos_tmu_control(pdev, false); - exynos_unregister_thermal(&exynos_sensor_conf); + exynos_unregister_thermal(data->reg_conf); clk_unprepare(data->clk); diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h index ef5dc0e..53fa702 100644 --- a/drivers/thermal/samsung/exynos_tmu.h +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -250,4 +250,17 @@ struct exynos_tmu_platform_data { unsigned int freq_tab_count; const struct exynos_tmu_registers *registers; }; + +/** + * 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 index 217b8f5..a5c25b4 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -48,38 +48,44 @@ static const struct exynos_tmu_registers exynos4210_tmu_registers = { .tmu_intstat = EXYNOS_TMU_REG_INTSTAT, .tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, }; -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_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, + +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, + }, }, - .freq_tab_count = 2, - .type = SOC_ARCH_EXYNOS4210, - .registers = &exynos4210_tmu_registers, + .tmu_count = 1, }; #endif @@ -120,41 +126,48 @@ static const struct exynos_tmu_registers exynos5250_tmu_registers = { .emul_time_shift = EXYNOS_EMUL_TIME_SHIFT, .emul_time_mask = EXYNOS_EMUL_TIME_MASK, }; -struct exynos_tmu_platform_data const exynos5250_default_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, + +#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, + +struct exynos_tmu_init_data const exynos5250_default_tmu_data = { + .tmu_data = { + { EXYNOS5250_TMU_DATA }, + }, + .tmu_count = 1, }; #endif diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h index 4acf070..139dbbb 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.h +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -94,14 +94,14 @@ #define EXYNOS_MAX_TRIGGER_PER_REG 4 #if defined(CONFIG_CPU_EXYNOS4210) -extern struct exynos_tmu_platform_data const exynos4210_default_tmu_data; +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_platform_data const exynos5250_default_tmu_data; +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) -- cgit v0.10.2 From f4dae7532c33380aa23ddcf83d0260bfdee48549 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:40 +0530 Subject: thermal: exynos: Add TMU features to check instead of using SOC type This patch adds several features supported by TMU as bitfields. This features varies across different SOC type and comparing the features present in the TMU is more logical than comparing the soc itself. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 7f65fe0..05b95c6 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -142,13 +142,15 @@ static int exynos_tmu_initialize(struct platform_device *pdev) mutex_lock(&data->lock); clk_enable(data->clk); - status = readb(data->base + reg->tmu_status); - if (!status) { - ret = -EBUSY; - goto out; + if (TMU_SUPPORTS(pdata, READY_STATUS)) { + status = readb(data->base + reg->tmu_status); + if (!status) { + ret = -EBUSY; + goto out; + } } - if (data->soc == SOC_ARCH_EXYNOS) + if (TMU_SUPPORTS(pdata, TRIM_RELOAD)) __raw_writel(1, data->base + reg->triminfo_ctrl); /* Save trimming info in order to perform calibration */ @@ -287,7 +289,7 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on) 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 (pdata->threshold_falling) + if (TMU_SUPPORTS(pdata, FALLING_TRIP)) interrupt_en |= interrupt_en << reg->inten_fall0_shift; } else { @@ -329,7 +331,7 @@ static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) unsigned int val; int ret = -EINVAL; - if (data->soc == SOC_ARCH_EXYNOS4210) + if (!TMU_SUPPORTS(pdata, EMULATION)) goto out; if (temp && temp < MCELSIUS) @@ -343,9 +345,13 @@ static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp) if (temp) { temp /= MCELSIUS; - val = (EXYNOS_EMUL_TIME << reg->emul_time_shift) | - (temp_to_code(data, temp) - << reg->emul_temp_shift) | EXYNOS_EMUL_ENABLE; + 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; } diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h index 53fa702..ff8844f 100644 --- a/drivers/thermal/samsung/exynos_tmu.h +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -43,6 +43,34 @@ enum soc_type { }; /** + * 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. @@ -222,6 +250,8 @@ struct exynos_tmu_registers { * 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. */ @@ -249,6 +279,7 @@ struct exynos_tmu_platform_data { struct freq_clip_table freq_tab[4]; unsigned int freq_tab_count; const struct exynos_tmu_registers *registers; + unsigned int features; }; /** diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index a5c25b4..2612b45 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -83,6 +83,7 @@ struct exynos_tmu_init_data const exynos4210_default_tmu_data = { .freq_tab_count = 2, .type = SOC_ARCH_EXYNOS4210, .registers = &exynos4210_tmu_registers, + .features = TMU_SUPPORT_READY_STATUS, }, }, .tmu_count = 1, @@ -162,7 +163,10 @@ static const struct exynos_tmu_registers exynos5250_tmu_registers = { }, \ .freq_tab_count = 2, \ .type = SOC_ARCH_EXYNOS, \ - .registers = &exynos5250_tmu_registers, + .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 = { -- cgit v0.10.2 From c55930e33eba7003fea9890cf755b28e201dd037 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Tue, 25 Jun 2013 17:11:42 +0530 Subject: thermal: exynos: use device resource management infrastructure This patch uses the device pointer stored in the configuration structure and converts to dev_* prints and devm API's. Acked-by: Kukjin Kim Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c index af0ae77..4d8e444 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.c +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -53,7 +53,8 @@ static int exynos_set_mode(struct thermal_zone_device *thermal, { struct exynos_thermal_zone *th_zone = thermal->devdata; if (!th_zone) { - pr_notice("thermal zone not registered\n"); + dev_err(th_zone->sensor_conf->dev, + "thermal zone not registered\n"); return 0; } @@ -69,8 +70,9 @@ static int exynos_set_mode(struct thermal_zone_device *thermal, th_zone->mode = mode; thermal_zone_device_update(thermal); - pr_info("thermal polling set for duration=%d msec\n", - thermal->polling_delay); + dev_dbg(th_zone->sensor_conf->dev, + "thermal polling set for duration=%d msec\n", + thermal->polling_delay); return 0; } @@ -162,7 +164,8 @@ static int exynos_bind(struct thermal_zone_device *thermal, case WARN_ZONE: if (thermal_zone_bind_cooling_device(thermal, i, cdev, level, 0)) { - pr_err("error binding cdev inst %d\n", i); + dev_err(data->dev, + "error unbinding cdev inst=%d\n", i); ret = -EINVAL; } th_zone->bind = true; @@ -207,7 +210,8 @@ static int exynos_unbind(struct thermal_zone_device *thermal, case WARN_ZONE: if (thermal_zone_unbind_cooling_device(thermal, i, cdev)) { - pr_err("error unbinding cdev inst=%d\n", i); + dev_err(data->dev, + "error unbinding cdev inst=%d\n", i); ret = -EINVAL; } th_zone->bind = false; @@ -227,7 +231,8 @@ static int exynos_get_temp(struct thermal_zone_device *thermal, void *data; if (!th_zone->sensor_conf) { - pr_info("Temperature sensor not initialised\n"); + dev_err(th_zone->sensor_conf->dev, + "Temperature sensor not initialised\n"); return -EINVAL; } data = th_zone->sensor_conf->driver_data; @@ -246,7 +251,8 @@ static int exynos_set_emul_temp(struct thermal_zone_device *thermal, struct exynos_thermal_zone *th_zone = thermal->devdata; if (!th_zone->sensor_conf) { - pr_info("Temperature sensor not initialised\n"); + dev_err(th_zone->sensor_conf->dev, + "Temperature sensor not initialised\n"); return -EINVAL; } data = th_zone->sensor_conf->driver_data; @@ -351,7 +357,8 @@ int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) return -EINVAL; } - th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL); + th_zone = devm_kzalloc(sensor_conf->dev, + sizeof(struct exynos_thermal_zone), GFP_KERNEL); if (!th_zone) return -ENOMEM; @@ -366,7 +373,8 @@ int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) 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])) { - pr_err("Failed to register cpufreq cooling device\n"); + dev_err(sensor_conf->dev, + "Failed to register cpufreq cooling device\n"); ret = -EINVAL; goto err_unregister; } @@ -380,14 +388,16 @@ int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) IDLE_INTERVAL); if (IS_ERR(th_zone->therm_dev)) { - pr_err("Failed to register thermal zone device\n"); + 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; - pr_info("Exynos: Kernel Thermal management registered\n"); + dev_info(sensor_conf->dev, + "Exynos: Thermal zone(%s) registered\n", sensor_conf->name); return 0; @@ -417,6 +427,6 @@ void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) cpufreq_cooling_unregister(th_zone->cool_dev[i]); } - kfree(th_zone); - pr_info("Exynos: Kernel Thermal management unregistered\n"); + dev_info(sensor_conf->dev, + "Exynos: Kernel Thermal management unregistered\n"); } -- cgit v0.10.2 From d9b6ee148dadd85433803f25cc62024ffcb4a0b8 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:42 +0530 Subject: thermal: exynos: Add support to access common register for multistance This patch adds support to parse one more common set of TMU register. First set of register belongs to each instance of TMU and second set belongs to common TMU registers. Acked-by: Jonghwa Lee Acked-by: Kukjin Kim Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 05b95c6..31bf373 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -40,6 +40,7 @@ * @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. @@ -53,6 +54,7 @@ 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; @@ -478,6 +480,24 @@ static int exynos_map_dt_data(struct platform_device *pdev) 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) { + dev_err(&pdev->dev, "Failed to ioremap memory\n"); + return -ENOMEM; + } return 0; } -- cgit v0.10.2 From a0395eee7ca19623f69f067cc8cfbd4385d85dad Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:43 +0530 Subject: thermal: exynos: Add driver support for exynos5440 TMU sensor This patch modifies TMU controller to add changes needed to work with exynos5440 platform. This sensor registers 3 instance of the tmu controller with the thermal zone and hence reports 3 temperature output. This controller supports upto five trip points. For critical threshold the driver uses the core driver thermal framework for shutdown. Acked-by: Jonghwa Lee Acked-by: Kukjin Kim Signed-off-by: Jungseok Lee Signed-off-by: Amit Daniel Kachhap Acked-by: Eduardo Valentin Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h index bc3016e..3eb2ed9 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.h +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -27,7 +27,7 @@ #define SENSOR_NAME_LEN 16 #define MAX_TRIP_COUNT 8 #define MAX_COOLING_DEVICE 4 -#define MAX_THRESHOLD_LEVS 4 +#define MAX_THRESHOLD_LEVS 5 #define ACTIVE_INTERVAL 500 #define IDLE_INTERVAL 10000 diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 31bf373..6bc86f6 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -156,7 +156,26 @@ static int exynos_tmu_initialize(struct platform_device *pdev) __raw_writel(1, data->base + reg->triminfo_ctrl); /* Save trimming info in order to perform calibration */ - trim_info = readl(data->base + reg->triminfo_data); + 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); @@ -201,7 +220,7 @@ static int exynos_tmu_initialize(struct platform_device *pdev) reg->threshold_th0 + i * sizeof(reg->threshold_th0)); writel(reg->inten_rise_mask, data->base + reg->tmu_intclear); - } else if (data->soc == SOC_ARCH_EXYNOS) { + } else { /* Write temperature code for rising and falling threshold */ for (i = 0; i < trigger_levs && i < EXYNOS_MAX_TRIGGER_PER_REG; i++) { @@ -241,14 +260,26 @@ static int exynos_tmu_initialize(struct platform_device *pdev) ret = threshold_code; goto out; } - rising_threshold |= threshold_code << 8 * i; - writel(rising_threshold, - data->base + reg->threshold_th0); + 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); @@ -377,7 +408,14 @@ static void exynos_tmu_work(struct work_struct *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; + 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); @@ -390,7 +428,7 @@ static void exynos_tmu_work(struct work_struct *work) clk_disable(data->clk); mutex_unlock(&data->lock); - +out: enable_irq(data->irq); } @@ -538,7 +576,8 @@ static int exynos_tmu_probe(struct platform_device *pdev) return ret; if (pdata->type == SOC_ARCH_EXYNOS || - pdata->type == SOC_ARCH_EXYNOS4210) + pdata->type == SOC_ARCH_EXYNOS4210 || + pdata->type == SOC_ARCH_EXYNOS5440) data->soc = pdata->type; else { ret = -EINVAL; diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h index ff8844f..25c48d4 100644 --- a/drivers/thermal/samsung/exynos_tmu.h +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -40,6 +40,7 @@ enum calibration_mode { enum soc_type { SOC_ARCH_EXYNOS4210 = 1, SOC_ARCH_EXYNOS, + SOC_ARCH_EXYNOS5440, }; /** @@ -131,6 +132,8 @@ enum soc_type { * @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; @@ -198,6 +201,9 @@ struct exynos_tmu_registers { u32 emul_temp_shift; u32 emul_time_shift; u32 emul_time_mask; + + u32 tmu_irqstatus; + u32 tmu_pmin; }; /** diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h index 139dbbb..ad263e9 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.h +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -93,6 +93,42 @@ #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) -- cgit v0.10.2 From 90542546f057c3cd71601240f25e7f1e5d0eae7f Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:44 +0530 Subject: thermal: exynos: Add thermal configuration data for exynos5440 TMU sensor This patch adds configuration data for exynos5440 soc. Also register definations for the controller are added. Acked-by: Jonghwa Lee Acked-by: Kukjin Kim Signed-off-by: Amit Daniel Kachhap Acked-by: Eduardo Valentin Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 6bc86f6..651f460 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -456,6 +456,10 @@ static const struct of_device_id exynos_tmu_match[] = { .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); diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index 2612b45..5952915 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -175,3 +175,74 @@ struct exynos_tmu_init_data const exynos5250_default_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, + .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 index ad263e9..43ce5fb 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.h +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -143,4 +143,11 @@ extern struct exynos_tmu_init_data const exynos5250_default_tmu_data; #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*/ -- cgit v0.10.2 From 5000806c119799715f315e482fb60cc61e2536ea Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:45 +0530 Subject: thermal: exynos: Fix to set the second point correction value This patch sets the second point trimming value according to the platform data if the register value is 0. Acked-by: Jonghwa Lee Acked-by: Kukjin Kim Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 651f460..3fac0f9 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -180,10 +180,15 @@ static int exynos_tmu_initialize(struct platform_device *pdev) data->temp_error2 = ((trim_info >> reg->triminfo_85_shift) & EXYNOS_TMU_TEMP_MASK); - if ((pdata->min_efuse_value > data->temp_error1) || - (data->temp_error1 > pdata->max_efuse_value) || - (data->temp_error2 != 0)) - data->temp_error1 = pdata->efuse_value; + 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; if (pdata->max_trigger_level > MAX_THRESHOLD_LEVS) { dev_err(&pdev->dev, "Invalid max trigger level\n"); -- cgit v0.10.2 From 1928457ea6337043a06ca2acd9b4d01e75810a3f Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:46 +0530 Subject: thermal: exynos: Add hardware mode thermal calibration support This patch adds support for h/w mode calibration in the TMU controller. Soc's like 5440 support this features. The h/w bits needed for calibration setting are same as that of enum calibration_type. Acked-by: Jonghwa Lee Acked-by: Kukjin Kim Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 3fac0f9..a6fc379 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -73,6 +73,9 @@ 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) { @@ -107,6 +110,9 @@ 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) { @@ -155,6 +161,9 @@ static int exynos_tmu_initialize(struct platform_device *pdev) 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) { /* @@ -190,6 +199,7 @@ static int exynos_tmu_initialize(struct platform_device *pdev) (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; @@ -297,7 +307,7 @@ 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; + unsigned int con, interrupt_en, cal_val; mutex_lock(&data->lock); clk_enable(data->clk); @@ -320,6 +330,27 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on) 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 = diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h index 25c48d4..b364c9e 100644 --- a/drivers/thermal/samsung/exynos_tmu.h +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -28,6 +28,8 @@ enum calibration_type { TYPE_ONE_POINT_TRIMMING, + TYPE_ONE_POINT_TRIMMING_25, + TYPE_ONE_POINT_TRIMMING_85, TYPE_TWO_POINT_TRIMMING, TYPE_NONE, }; @@ -90,6 +92,10 @@ enum soc_type { * @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. @@ -151,6 +157,8 @@ struct exynos_tmu_registers { 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; diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index 5952915..9002499 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -189,6 +189,8 @@ static const struct exynos_tmu_registers exynos5440_tmu_registers = { .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, diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h index 43ce5fb..dc7feb5 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.h +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -75,6 +75,8 @@ #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 -- cgit v0.10.2 From 498d22f616f6880531b6d75630a9cc0ecd1f7865 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:47 +0530 Subject: thermal: exynos: Support for TMU regulator defined at device tree TMU probe function now checks for a device tree defined regulator. For compatibility reasons it is allowed to probe driver even without this regulator defined. Acked-by: Jonghwa Lee Acked-by: Eduardo Valentin Signed-off-by: Lukasz Majewski Signed-off-by: Kyungmin Park Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index a6fc379..ec01dfe 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "exynos_thermal_common.h" #include "exynos_tmu.h" @@ -48,6 +49,7 @@ * @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 { @@ -61,6 +63,7 @@ struct exynos_tmu_data { struct mutex lock; struct clk *clk; u8 temp_error1, temp_error2; + struct regulator *regulator; struct thermal_sensor_conf *reg_conf; }; @@ -527,10 +530,27 @@ 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) 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; @@ -698,6 +718,9 @@ static int exynos_tmu_remove(struct platform_device *pdev) clk_unprepare(data->clk); + if (!IS_ERR(data->regulator)) + regulator_disable(data->regulator); + return 0; } -- cgit v0.10.2 From 0e97194bcfa09fc835016e3aa12b932827a9e2a6 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:48 +0530 Subject: ARM: dts: thermal: exynos: Add documentation for Exynos SoC thermal bindings Proper description for Exynos4 bindings added to Documentation/devicetree/ bindings. It adds description to use multiple TMU instances, optional voltage supply node and optional shared register across multiple TMU's. Acked-by: Jonghwa Lee Signed-off-by: Lukasz Majewski Signed-off-by: Kyungmin Park Signed-off-by: Amit Daniel Kachhap Acked-by: Eduardo Valentin Signed-off-by: Eduardo Valentin diff --git a/Documentation/devicetree/bindings/thermal/exynos-thermal.txt b/Documentation/devicetree/bindings/thermal/exynos-thermal.txt new file mode 100644 index 0000000..284f530 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/exynos-thermal.txt @@ -0,0 +1,55 @@ +* Exynos Thermal Management Unit (TMU) + +** Required properties: + +- compatible : One of the following: + "samsung,exynos4412-tmu" + "samsung,exynos4210-tmu" + "samsung,exynos5250-tmu" + "samsung,exynos5440-tmu" +- interrupt-parent : The phandle for the interrupt controller +- reg : Address range of the thermal registers. For soc's which has multiple + instances of TMU and some registers are shared across all TMU's like + interrupt related then 2 set of register has to supplied. First set + belongs to each instance of TMU and second set belongs to common TMU + registers. +- interrupts : Should contain interrupt for thermal system +- clocks : The main clock for TMU device +- clock-names : Thermal system clock name +- vtmu-supply: This entry is optional and provides the regulator node supplying + voltage to TMU. If needed this entry can be placed inside + board/platform specific dts file. + +Example 1): + + tmu@100C0000 { + compatible = "samsung,exynos4412-tmu"; + interrupt-parent = <&combiner>; + reg = <0x100C0000 0x100>; + interrupts = <2 4>; + clocks = <&clock 383>; + clock-names = "tmu_apbif"; + status = "disabled"; + vtmu-supply = <&tmu_regulator_node>; + }; + +Example 2): + + tmuctrl_0: tmuctrl@160118 { + compatible = "samsung,exynos5440-tmu"; + reg = <0x160118 0x230>, <0x160368 0x10>; + interrupts = <0 58 0>; + clocks = <&clock 21>; + clock-names = "tmu_apbif"; + }; + +Note: For multi-instance tmu each instance should have an alias correctly +numbered in "aliases" node. + +Example: + +aliases { + tmuctrl0 = &tmuctrl_0; + tmuctrl1 = &tmuctrl_1; + tmuctrl2 = &tmuctrl_2; +}; -- cgit v0.10.2 From beb70b2d2fe211fc98ec13cf769f331243f5221a Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 24 Jun 2013 16:20:49 +0530 Subject: Documentation: thermal: Explain the exynos thermal driver model This patch updates the documentation to explain the driver model and file layout. Acked-by: Jonghwa Lee Acked-by: Kukjin Kim Acked-by: Eduardo Valentin Signed-off-by: Amit Daniel Kachhap Signed-off-by: Eduardo Valentin diff --git a/Documentation/thermal/exynos_thermal b/Documentation/thermal/exynos_thermal index 2b46f67..9010c44 100644 --- a/Documentation/thermal/exynos_thermal +++ b/Documentation/thermal/exynos_thermal @@ -1,17 +1,17 @@ -Kernel driver exynos4_tmu +Kernel driver exynos_tmu ================= Supported chips: -* ARM SAMSUNG EXYNOS4 series of SoC - Prefix: 'exynos4-tmu' +* ARM SAMSUNG EXYNOS4, EXYNOS5 series of SoC Datasheet: Not publicly available Authors: Donggeun Kim +Authors: Amit Daniel -Description ------------ +TMU controller Description: +--------------------------- -This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC. +This driver allows to read temperature inside SAMSUNG EXYNOS4/5 series of SoC. The chip only exposes the measured 8-bit temperature code value through a register. @@ -34,9 +34,9 @@ The three equations are: TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register) Temperature code measured at 85 degree Celsius which is unchanged -TMU(Thermal Management Unit) in EXYNOS4 generates interrupt +TMU(Thermal Management Unit) in EXYNOS4/5 generates interrupt when temperature exceeds pre-defined levels. -The maximum number of configurable threshold is four. +The maximum number of configurable threshold is five. The threshold levels are defined as follows: Level_0: current temperature > trigger_level_0 + threshold Level_1: current temperature > trigger_level_1 + threshold @@ -47,6 +47,31 @@ The threshold levels are defined as follows: through the corresponding registers. When an interrupt occurs, this driver notify kernel thermal framework -with the function exynos4_report_trigger. +with the function exynos_report_trigger. Although an interrupt condition for level_0 can be set, it can be used to synchronize the cooling action. + +TMU driver description: +----------------------- + +The exynos thermal driver is structured as, + + Kernel Core thermal framework + (thermal_core.c, step_wise.c, cpu_cooling.c) + ^ + | + | +TMU configuration data -------> TMU Driver <------> Exynos Core thermal wrapper +(exynos_tmu_data.c) (exynos_tmu.c) (exynos_thermal_common.c) +(exynos_tmu_data.h) (exynos_tmu.h) (exynos_thermal_common.h) + +a) TMU configuration data: This consist of TMU register offsets/bitfields + described through structure exynos_tmu_registers. Also several + other platform data (struct exynos_tmu_platform_data) members + are used to configure the TMU. +b) TMU driver: This component initialises the TMU controller and sets different + thresholds. It invokes core thermal implementation with the call + exynos_report_trigger. +c) Exynos Core thermal wrapper: This provides 3 wrapper function to use the + Kernel core thermal framework. They are exynos_unregister_thermal, + exynos_register_thermal and exynos_report_trigger. -- cgit v0.10.2 From 017e51420cc44098308b00dffd9d4e514ddf40f3 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 1 Aug 2013 18:33:11 +0200 Subject: thermal: imx: dynamic passive and SoC specific critical trip points Set passive and critical trip point values depending on the maximum die temperature stored in the OCOTP fuses. This allows higher trip points for industrial and automotive rated i.MX6 SoCs. Also allow to configure the passive trip point from userspace. Signed-off-by: Philipp Zabel Acked-by: Shawn Guo Signed-off-by: Zhang Rui diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index d16c33c..c9a55b02 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -55,12 +55,6 @@ enum imx_thermal_trip { */ #define IMX_TEMP_PASSIVE 85000 -/* - * The maximum die temperature on imx parts is 105C, let's give some cushion - * for noise and possible temperature rise between measurements. - */ -#define IMX_TEMP_CRITICAL 100000 - #define IMX_POLLING_DELAY 2000 /* millisecond */ #define IMX_PASSIVE_DELAY 1000 @@ -70,6 +64,8 @@ struct imx_thermal_data { 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; }; static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp) @@ -156,15 +152,35 @@ static int imx_get_trip_type(struct thermal_zone_device *tz, int trip, static int imx_get_crit_temp(struct thermal_zone_device *tz, unsigned long *temp) { - *temp = IMX_TEMP_CRITICAL; + 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) { - *temp = (trip == IMX_TRIP_PASSIVE) ? IMX_TEMP_PASSIVE : - IMX_TEMP_CRITICAL; + 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; + return 0; } @@ -211,6 +227,7 @@ static const struct thermal_zone_device_ops imx_tz_ops = { .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) @@ -267,6 +284,18 @@ static int imx_get_sensor_data(struct platform_device *pdev) 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; } @@ -314,7 +343,8 @@ static int imx_thermal_probe(struct platform_device *pdev) } data->tz = thermal_zone_device_register("imx_thermal_zone", - IMX_TRIP_NUM, 0, data, + IMX_TRIP_NUM, + BIT(IMX_TRIP_PASSIVE), data, &imx_tz_ops, NULL, IMX_PASSIVE_DELAY, IMX_POLLING_DELAY); -- cgit v0.10.2 From 37713a1e8e4c1a1067ad4c99296f78d3c82ed9c4 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Thu, 1 Aug 2013 18:33:12 +0200 Subject: thermal: imx: implement thermal alarm interrupt handling Enable automatic measurements at 10 Hz and use the alarm interrupt to react more quickly to sudden temperature changes above the passive or critical temperature trip points. Signed-off-by: Philipp Zabel Acked-by: Shawn Guo Signed-off-by: Zhang Rui diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index c9a55b02..1d6c801 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,8 @@ #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) @@ -66,33 +69,62 @@ struct imx_thermal_data { 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; - static unsigned long last_temp; unsigned int n_meas; + bool wait; u32 val; - /* - * 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); + 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. */ - usleep_range(20, 50); + if (wait) + usleep_range(20, 50); regmap_read(map, TEMPSENSE0, &val); - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + + 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"); @@ -104,9 +136,24 @@ static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp) /* See imx_get_sensor_data() for formula derivation */ *temp = data->c2 + data->c1 * n_meas; - if (*temp != last_temp) { + /* 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); - last_temp = *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; @@ -126,13 +173,30 @@ 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; @@ -181,6 +245,8 @@ static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip, data->temp_passive = temp; + imx_set_alarm_temp(data, temp); + return 0; } @@ -299,11 +365,34 @@ static int imx_get_sensor_data(struct platform_device *pdev) 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); @@ -318,6 +407,18 @@ static int imx_thermal_probe(struct platform_device *pdev) } 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); @@ -356,6 +457,15 @@ static int imx_thermal_probe(struct platform_device *pdev) 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; @@ -364,6 +474,10 @@ static int imx_thermal_probe(struct platform_device *pdev) 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); -- cgit v0.10.2 From aa1ab4347ec40da87085abaa0db18268da2d0042 Mon Sep 17 00:00:00 2001 From: Naveen Krishna Chatradhi Date: Wed, 7 Aug 2013 14:01:09 +0530 Subject: thermal: exynos_tmu: fix wrong error check for mapped memory The error check is checking for a "base" mapped memory base instead of "base_common". Fixing the same. Signed-off-by: Naveen Krishna Chatradhi Signed-off-by: Zhang Rui diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index ec01dfe..a033dbb 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -592,7 +592,7 @@ static int exynos_map_dt_data(struct platform_device *pdev) data->base_common = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); - if (!data->base) { + if (!data->base_common) { dev_err(&pdev->dev, "Failed to ioremap memory\n"); return -ENOMEM; } -- cgit v0.10.2 From 044d5c26da262fa433dacbe1c6962459050d6b06 Mon Sep 17 00:00:00 2001 From: Lan Tianyu Date: Tue, 13 Aug 2013 10:07:28 +0800 Subject: Thermal/cpu_cooling: Return directly for the cpu out of allowed_cpus in the cpufreq_thermal_notifier() cpufreq_thermal_notifier() is to change the cpu's cpufreq in the allowed_cpus mask when associated thermal-cpufreq cdev's cooling state is changed. It's a cpufreq policy notifier handler and it will be triggered even if those cpus out of allowed_cpus has changed freq policy. cpufreq_thermal_notifier() checks the policy->cpu. If it belongs to allowed_cpus, change max_freq(default to 0) to the desire cpufreq value and pass 0 and max_freq to cpufreq_verify_within_limits() as cpufreq scope. But if not, do nothing and max_freq will remain 0. This will cause the cpufreq scope to become 0~0. This is not right. This patch is to return directly after finding cpu not belonging to allowed_cpus. Signed-off-by: Lan Tianyu Signed-off-by: Zhang Rui diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 82e15db..5b3744e 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) -- cgit v0.10.2 From 178c2490b99f898efc06d1ad75cadc84f13021a6 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Mon, 17 Jun 2013 21:24:23 +0800 Subject: thermal: step_wise: cdev only needs update on a new target state The cooling device only needs update on a new target state. Since we already check old target in thermal_zone_trip_update(), we can do one more check to see if it's a new target state. If not, we can reasonably save some uncecesary code execution. Signed-off-by: Shawn Guo Acked-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/step_wise.c index 4d4ddae..0afbd86 100644 --- a/drivers/thermal/step_wise.c +++ b/drivers/thermal/step_wise.c @@ -133,6 +133,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) -- cgit v0.10.2 From ca56caa02182044667b680154a0550ad52726584 Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Mon, 17 Jun 2013 21:24:24 +0800 Subject: thermal: step_wise: return instance->target by default In case the trend is not changing or when there is no request for throttling, it is expected that the instance would not change its requested target. This patch improves the code implementation to cover for this expected behavior. With current implementation, the instance will always reset to cdev.cur_state, even in not expected cases, like those mentioned above. This patch changes the step_wise governor implementation of get_target so that we accomplish: (a) - default value will be current instance->target, so we do not change the thermal instance target unnecessarily. (b) - the code now it is clear about what is the intention. There is a clear statement of what are the expected outcomes (c) - removal of hardcoded constants, now it is put in use the THERMAL_NO_TARGET macro. (d) - variable names are also improved so that reader can clearly understand the difference between instance cur target, next target and cdev cur_state. Cc: Zhang Rui Cc: Durgadoss R Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Reported-by: Ruslan Ruslichenko Signed-of-by: Eduardo Valentin Signed-off-by: Zhang Rui diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/step_wise.c index 0afbd86..d89e781 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, -- cgit v0.10.2 From 547f72ab7da5e01c2170ca2abfca5f98c40318fa Mon Sep 17 00:00:00 2001 From: Ranganath Krishnan Date: Fri, 23 Aug 2013 11:08:21 -0500 Subject: thermal: ti-soc-thermal: Initialize counter_delay field for TI DRA752 sensors Initialize MPU, GPU, CORE, DSPEVE and IVA thermal sensors of DRA752 bandgap with the counter delay mask. Signed-off-by: Ranganath Krishnan Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c index e5d8326..a492927 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, -- cgit v0.10.2 From 10ccff1b578cc7842a0bab959f662305f3dfabc0 Mon Sep 17 00:00:00 2001 From: Ranganath Krishnan Date: Fri, 23 Aug 2013 11:08:22 -0500 Subject: thermal: ti-soc-thermal: Set the bandgap mask counter delay value Set the bandgap mask counter_delay with the polling_delay value on registering the thermal zone. This patch will ensure to get the correct update interval for computing the thermal trend. Signed-off-by: Ranganath Krishnan Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 4c5f55c37..4f8b9af 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; } -- cgit v0.10.2 From e838ff8119a2aee621d4326f5899b8f4cdae40ac Mon Sep 17 00:00:00 2001 From: Ranganath Krishnan Date: Fri, 23 Aug 2013 11:08:23 -0500 Subject: thermal: ti-soc-thermal: Ensure to compute thermal trend Workaround to compute thermal trend even when update interval is not set. This patch will ensure to compute the thermal trend when bandgap counter delay is not set. Signed-off-by: Ranganath Krishnan Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index 9dfd471..74c0e34 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", -- cgit v0.10.2 From 02ae59dc0e49b1db6e57435553fb7d4f68732688 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 19 Aug 2013 11:58:41 +0530 Subject: thermal: exynos: Fix typos in Kconfig Fixes some trivial typos. Signed-off-by: Sachin Kamat Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index b653f15..92492e4 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -3,16 +3,16 @@ config EXYNOS_THERMAL depends on ARCH_HAS_BANDGAP help If you say yes here you get support for the TMU (Thermal Management - Unit) driver for SAMSUNG EXYNOS series of soc. This driver initialises + 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 API's and TMU configuration - data from the supported soc's. + 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 SOC's" + 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 API's. + CPU cooling APIs. -- cgit v0.10.2 From 91ff81cf48f0c8f9cd608acbd83f0e98a228807b Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 19 Aug 2013 11:58:42 +0530 Subject: thermal: exynos: Fix potential NULL pointer dereference NULL pointer was being dereferenced in its own error message. Changed it to the correct device pointer. Signed-off-by: Sachin Kamat Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c index 4d8e444..f10a6ad 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.c +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -53,7 +53,7 @@ static int exynos_set_mode(struct thermal_zone_device *thermal, { struct exynos_thermal_zone *th_zone = thermal->devdata; if (!th_zone) { - dev_err(th_zone->sensor_conf->dev, + dev_err(&thermal->device, "thermal zone not registered\n"); return 0; } @@ -231,7 +231,7 @@ static int exynos_get_temp(struct thermal_zone_device *thermal, void *data; if (!th_zone->sensor_conf) { - dev_err(th_zone->sensor_conf->dev, + dev_err(&thermal->device, "Temperature sensor not initialised\n"); return -EINVAL; } @@ -251,7 +251,7 @@ static int exynos_set_emul_temp(struct thermal_zone_device *thermal, struct exynos_thermal_zone *th_zone = thermal->devdata; if (!th_zone->sensor_conf) { - dev_err(th_zone->sensor_conf->dev, + dev_err(&thermal->device, "Temperature sensor not initialised\n"); return -EINVAL; } -- cgit v0.10.2 From 73b5b1d7c5f9348c0fe3a3e4ac5586207d830d54 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 19 Aug 2013 11:58:43 +0530 Subject: thermal: exynos: Clean up non-DT remnants Commit 1cd1ecb6 ("thermal: exynos: Remove non DT based support") cleaned up some non-DT code. However, there were few more things needed for complete cleanup to make this driver DT only. Signed-off-by: Sachin Kamat Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index 92492e4..f760389 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -1,6 +1,6 @@ config EXYNOS_THERMAL tristate "Exynos thermal management unit driver" - depends on ARCH_HAS_BANDGAP + 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 diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index a033dbb..b43afda 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -481,7 +481,6 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id) return IRQ_HANDLED; } -#ifdef CONFIG_OF static const struct of_device_id exynos_tmu_match[] = { { .compatible = "samsung,exynos4210-tmu", @@ -502,27 +501,22 @@ static const struct of_device_id exynos_tmu_match[] = { {}, }; MODULE_DEVICE_TABLE(of, exynos_tmu_match); -#endif static inline struct exynos_tmu_platform_data *exynos_get_driver_data( struct platform_device *pdev, int id) { -#ifdef CONFIG_OF struct exynos_tmu_init_data *data_table; struct exynos_tmu_platform_data *tmu_data; - 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; - 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); - } -#endif - return NULL; + 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) @@ -532,7 +526,7 @@ static int exynos_map_dt_data(struct platform_device *pdev) struct resource res; int ret; - if (!data) + if (!data || !pdev->dev.of_node) return -ENODEV; /* @@ -754,7 +748,7 @@ static struct platform_driver exynos_tmu_driver = { .name = "exynos-tmu", .owner = THIS_MODULE, .pm = EXYNOS_TMU_PM, - .of_match_table = of_match_ptr(exynos_tmu_match), + .of_match_table = exynos_tmu_match, }, .probe = exynos_tmu_probe, .remove = exynos_tmu_remove, -- cgit v0.10.2 From 0dd88793aacd7c91b9724be7b618bb3f7c25befe Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Wed, 3 Jul 2013 15:14:28 -0400 Subject: thermal: hwmon: move hwmon support to single file In order to improve code organization, this patch moves the hwmon sysfs support to a file named thermal_hwmon. This helps to add extra support for hwmon without scrambling the code. In order to do this move, the hwmon list head is now using its own locking. Before, the list used the global thermal locking. Also, some minor changes in the code were required, as recommended by checkpatch.pl. Cc: Zhang Rui Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Acked-by: Durgadoss R Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index ae18f02..dbfc390 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" diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index c19df7a..584b363 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 diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 1f02e8e..247528b 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -38,6 +38,7 @@ #include #include "thermal_core.h" +#include "thermal_hwmon.h" MODULE_AUTHOR("Zhang Rui"); MODULE_DESCRIPTION("Generic thermal management sysfs support"); @@ -859,260 +860,6 @@ thermal_cooling_device_trip_point_show(struct device *dev, /* Device management */ -#if defined(CONFIG_THERMAL_HWMON) - -/* hwmon sys I/F */ -#include - -/* 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 diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c new file mode 100644 index 0000000..fdb0719 --- /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 + * Copyright (C) 2008 Sujith Thomas + * + * Copyright (C) 2013 Texas Instruments + * Copyright (C) 2013 Eduardo Valentin + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 +#include +#include +#include +#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(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(*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 0000000..c798fdb --- /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 + * Copyright (C) 2008 Sujith Thomas + * + * Copyright (C) 2013 Texas Instruments + * Copyright (C) 2013 Eduardo Valentin + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 + +#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__ */ -- cgit v0.10.2 From b82715fdd4a5407f56853b24d387d484dd9c3b5b Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Fri, 23 Aug 2013 17:07:58 -0400 Subject: drivers: thermal: parent virtual hwmon with thermal zone When creating virtual hwmon devices based out of thermal zone devices, the virtual devices won't have parents. This patch changes the code so that the parent of virtual hwmon devices is the thermal zone device that they are based of. Cc: Zhang Rui Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index fdb0719..eeef0e2 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -159,7 +159,7 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) INIT_LIST_HEAD(&hwmon->tz_list); strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); - hwmon->device = hwmon_device_register(NULL); + hwmon->device = hwmon_device_register(&tz->device); if (IS_ERR(hwmon->device)) { result = PTR_ERR(hwmon->device); goto free_mem; -- cgit v0.10.2 From ccba4ffd9eff6120a20cc7656458ac554aec4b0c Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Thu, 15 Aug 2013 11:34:17 -0400 Subject: drivers: thermal: make usage of CONFIG_THERMAL_HWMON optional When registering a new thermal_device, the thermal framework will always add a hwmon sysfs interface. This patch adds a flag to make this behavior optional. Now when registering a new thermal device, the caller can optionally inform if hwmon interface is desirable. This can be done by means of passing a thermal_zone_params.no_hwmon == true. In order to keep same behavior as of today, all current calls will by default create the hwmon interface. Cc: David Woodhouse Cc: linux-acpi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: linux-pm@vger.kernel.org Cc: Zhang Rui Suggested-by: Wei Ni Signed-off-by: Eduardo Valentin diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt index a71bd5b..37c5486 100644 --- a/Documentation/thermal/sysfs-api.txt +++ b/Documentation/thermal/sysfs-api.txt @@ -142,6 +142,11 @@ temperature) and throttle appropriate devices. This is an optional feature where some platforms can choose not to provide this data. .governor_name: Name of the thermal governor used for this zone + .no_hwmon: a boolean to indicate if the thermal to hwmon sysfs interface + is required. when no_hwmon == false, a hwmon sysfs interface + will be created. when no_hwmon == true, nothing will be done. + In case the thermal_zone_params is NULL, the hwmon interface + will be created (for backward compatibility). .num_tbps: Number of thermal_bind_params entries for this zone .tbp: thermal_bind_params entries diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 247528b..51648bf 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1462,9 +1462,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/include/linux/thermal.h b/include/linux/thermal.h index a386a1c..b4a975f 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -214,6 +214,14 @@ struct thermal_bind_params { /* Structure to define Thermal Zone parameters */ struct thermal_zone_params { char governor_name[THERMAL_NAME_LENGTH]; + + /* + * a boolean to indicate if the thermal to hwmon sysfs interface + * is required. when no_hwmon == false, a hwmon sysfs interface + * will be created. when no_hwmon == true, nothing will be done + */ + bool no_hwmon; + int num_tbps; /* Number of tbp entries */ struct thermal_bind_params *tbp; }; -- cgit v0.10.2 From a8892d83894bcbd2717846cfa85955152b73453f Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Tue, 16 Jul 2013 15:26:28 -0400 Subject: thermal: thermal_core: allow binding with limits on bind_params When registering a thermal zone device using platform information via bind_params, the thermal framework will always perform the cdev binding using the lowest and highest limits (THERMAL_NO_LIMIT). This patch changes the data structures so that it is possible to inform what are the desired limits for each trip point inside a bind_param. The way the binding is performed is also changed so that it uses the new data structure. Cc: Zhang Rui Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Eduardo Valentin diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt index 37c5486..87519cb 100644 --- a/Documentation/thermal/sysfs-api.txt +++ b/Documentation/thermal/sysfs-api.txt @@ -134,6 +134,13 @@ temperature) and throttle appropriate devices. this thermal zone and cdev, for a particular trip point. If nth bit is set, then the cdev and thermal zone are bound for trip point n. + .limits: This is an array of cooling state limits. Must have exactly + 2 * thermal_zone.number_of_trip_points. It is an array consisting + of tuples of state limits. Each trip + will be associated with one state limit tuple when binding. + A NULL pointer means + on all trips. These limits are used when binding a cdev to a + trip point. .match: This call back returns success(0) if the 'tz and cdev' need to be bound, as per platform data. 1.4.2 struct thermal_zone_params diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 51648bf..4962a6a 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -202,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); } @@ -254,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); } } @@ -292,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: diff --git a/include/linux/thermal.h b/include/linux/thermal.h index b4a975f..b268d3c 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -207,6 +207,16 @@ struct thermal_bind_params { * See Documentation/thermal/sysfs-api.txt for more information. */ int trip_mask; + + /* + * This is an array of cooling state limits. Must have exactly + * 2 * thermal_zone.number_of_trip_points. It is an array consisting + * of tuples of state limits. Each trip + * will be associated with one state limit tuple when binding. + * A NULL pointer means + * on all trips. + */ + unsigned long *binding_limits; int (*match) (struct thermal_zone_device *tz, struct thermal_cooling_device *cdev); }; -- cgit v0.10.2 From 50e66c7ed8a1cd7e933628f9f5cf2617394adf5a Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Thu, 15 Aug 2013 10:54:46 -0400 Subject: drivers: thermal: add check when unregistering cpu cooling This patch avoids NULL pointer accesses while unregistering cpu cooling devices, in case a NULL pointer is received. Cc: Zhang Rui Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 5b3744e..d179028 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -498,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--; -- cgit v0.10.2