Message ID | 1366972671-9227-3-git-send-email-amit.daniel@samsung.com (mailing list archive) |
---|---|
State | Changes Requested |
Delegated to: | Zhang Rui |
Headers | show |
On Fri, 2013-04-26 at 16:07 +0530, Amit Daniel Kachhap wrote: > 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 <kgene.kim@samsung.com> > Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com> > --- > drivers/thermal/samsung/Kconfig | 26 +- > drivers/thermal/samsung/Makefile | 4 +- > drivers/thermal/samsung/exynos_thermal.c | 421 +---------------------- > drivers/thermal/samsung/exynos_thermal_common.c | 389 +++++++++++++++++++++ > drivers/thermal/samsung/exynos_thermal_common.h | 71 ++++ > 5 files changed, 491 insertions(+), 420 deletions(-) > create mode 100644 drivers/thermal/samsung/exynos_thermal_common.c > create mode 100644 drivers/thermal/samsung/exynos_thermal_common.h > > diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig > index 2d3d9dc..1e3ba31 100644 > --- a/drivers/thermal/samsung/Kconfig > +++ b/drivers/thermal/samsung/Kconfig > @@ -1,9 +1,21 @@ > -config EXYNOS_THERMAL > - tristate "Temperature sensor on Samsung EXYNOS" > +config EXYNOS_COMMON EXYNOS_COMMON is misleading here, better use EXYNOS_THERMAL_COMMON? > + bool "Common thermal support for EXYNOS SOC's" > 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. > + 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. > + > +if EXYNOS_COMMON > + > +config EXYNOS_SOC_THERMAL > + tristate "Temperature sensor on Samsung EXYNOS series SOC" > + depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412 || SOC_EXYNOS5250) > + help > + If you say yes here you can enable TMU (Thermal Management Unit) on > + SAMSUNG EXYNOS 4210, 4412, 4414 and 5250 series of SoC. This option > + initialises the TMU controller and registers/unregisters with exynos > + common thermal layer. > + > +endif > diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile > index 1fe6d93..fcda5b4 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_SOC_THERMAL) += exynos_soc_thermal.o > +exynos_soc_thermal-y := exynos_thermal.o > +exynos_soc_thermal-$(CONFIG_EXYNOS_COMMON) += exynos_thermal_common.o the Makefile suggests that exynos_soc_thermal driver could be built without CONFIG_EXYNOS_COMMON. But the Kconfig file shows that exynos_soc_thermal depends on CONFIG_EXYNOS_COMMON. If exynos_thermal_common.c contains the code to interact with the generic thermal layer and you do not want a soc driver without thermal support, just remove CONFIG_EXYNOS_COMMON and always build it into exynos_soc_thermal.o. what do you think? thanks, rui > diff --git a/drivers/thermal/samsung/exynos_thermal.c b/drivers/thermal/samsung/exynos_thermal.c > index d20ce9e..4c85945 100644 > --- a/drivers/thermal/samsung/exynos_thermal.c > +++ b/drivers/thermal/samsung/exynos_thermal.c > @@ -21,23 +21,19 @@ > * > */ > > -#include <linux/module.h> > -#include <linux/err.h> > -#include <linux/kernel.h> > -#include <linux/slab.h> > -#include <linux/platform_device.h> > -#include <linux/interrupt.h> > #include <linux/clk.h> > -#include <linux/workqueue.h> > -#include <linux/sysfs.h> > -#include <linux/kobject.h> > #include <linux/io.h> > +#include <linux/interrupt.h> > +#include <linux/kernel.h> > +#include <linux/kobject.h> > +#include <linux/module.h> > #include <linux/mutex.h> > -#include <linux/platform_data/exynos_thermal.h> > -#include <linux/thermal.h> > -#include <linux/cpufreq.h> > -#include <linux/cpu_cooling.h> > #include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/platform_data/exynos_thermal.h> > +#include <linux/slab.h> > +#include <linux/workqueue.h> > +#include "exynos_thermal_common.h" > > /* Exynos generic registers */ > #define EXYNOS_TMU_REG_TRIMINFO 0x0 > @@ -88,16 +84,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 +92,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 +104,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..9a57606 > --- /dev/null > +++ b/drivers/thermal/samsung/exynos_thermal_common.c > @@ -0,0 +1,389 @@ > +/* > + * exynos_thermal_common.c - Samsung EXYNOS common thermal file > + * > + * Copyright (C) 2013 Samsung Electronics > + * Amit Daniel Kachhap <amit.daniel@samsung.com> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + */ > + > +#include <linux/cpufreq.h> > +#include <linux/cpu_cooling.h> > +#include <linux/err.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/kobject.h> > +#include <linux/mutex.h> > +#include <linux/platform_data/exynos_thermal.h> > +#include <linux/slab.h> > +#include <linux/thermal.h> > +#include "exynos_thermal_common.h" > + > +struct exynos_thermal_zone { > + enum thermal_device_mode mode; > + struct thermal_zone_device *therm_dev; > + struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE]; > + unsigned int cool_dev_size; > + struct platform_device *exynos4_dev; > + struct thermal_sensor_conf *sensor_conf; > + bool bind; > +}; > + > +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..165401f > --- /dev/null > +++ b/drivers/thermal/samsung/exynos_thermal_common.h > @@ -0,0 +1,71 @@ > +/* > + * exynos_thermal_common.h - Samsung EXYNOS common header file > + * > + * Copyright (C) 2013 Samsung Electronics > + * Amit Daniel Kachhap <amit.daniel@samsung.com> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + */ > + > +#ifndef _LINUX_EXYNOS_THERMAL_COMMON_H > +#define _LINUX_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; > + 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; > +}; > + > +/*Functions used exynos based thermal sensor driver*/ > +void exynos_unregister_thermal(void); > +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); > +void exynos_report_trigger(void); > +#endif /* _LINUX_EXYNOS_THERMAL_COMMON_H */ -- To unsubscribe from this list: send the line "unsubscribe linux-pm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Rui, On Mon, May 6, 2013 at 8:46 PM, Zhang Rui <rui.zhang@intel.com> wrote: > On Fri, 2013-04-26 at 16:07 +0530, Amit Daniel Kachhap wrote: >> 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 <kgene.kim@samsung.com> >> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com> >> --- >> drivers/thermal/samsung/Kconfig | 26 +- >> drivers/thermal/samsung/Makefile | 4 +- >> drivers/thermal/samsung/exynos_thermal.c | 421 +---------------------- >> drivers/thermal/samsung/exynos_thermal_common.c | 389 +++++++++++++++++++++ >> drivers/thermal/samsung/exynos_thermal_common.h | 71 ++++ >> 5 files changed, 491 insertions(+), 420 deletions(-) >> create mode 100644 drivers/thermal/samsung/exynos_thermal_common.c >> create mode 100644 drivers/thermal/samsung/exynos_thermal_common.h >> >> diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig >> index 2d3d9dc..1e3ba31 100644 >> --- a/drivers/thermal/samsung/Kconfig >> +++ b/drivers/thermal/samsung/Kconfig >> @@ -1,9 +1,21 @@ >> -config EXYNOS_THERMAL >> - tristate "Temperature sensor on Samsung EXYNOS" >> +config EXYNOS_COMMON > > EXYNOS_COMMON is misleading here, better use EXYNOS_THERMAL_COMMON? Yes submitted V3 version with this change. > >> + bool "Common thermal support for EXYNOS SOC's" >> 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. >> + 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. >> + >> +if EXYNOS_COMMON >> + >> +config EXYNOS_SOC_THERMAL >> + tristate "Temperature sensor on Samsung EXYNOS series SOC" >> + depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412 || SOC_EXYNOS5250) >> + help >> + If you say yes here you can enable TMU (Thermal Management Unit) on >> + SAMSUNG EXYNOS 4210, 4412, 4414 and 5250 series of SoC. This option >> + initialises the TMU controller and registers/unregisters with exynos >> + common thermal layer. >> + >> +endif >> diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile >> index 1fe6d93..fcda5b4 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_SOC_THERMAL) += exynos_soc_thermal.o >> +exynos_soc_thermal-y := exynos_thermal.o >> +exynos_soc_thermal-$(CONFIG_EXYNOS_COMMON) += exynos_thermal_common.o > > the Makefile suggests that exynos_soc_thermal driver could be built > without CONFIG_EXYNOS_COMMON. > But the Kconfig file shows that exynos_soc_thermal depends on > CONFIG_EXYNOS_COMMON. > If exynos_thermal_common.c contains the code to interact with the > generic thermal layer and you do not want a soc driver without thermal > support, just remove CONFIG_EXYNOS_COMMON and always build it into > exynos_soc_thermal.o. > what do you think? This is a good catch. I somehow missed this. I just submitted a patch fixing this issue. Actually CONFIG_EXYNOS_COMMON is independent of CONFIG_EXYNOS_SOC_THERMAL. Now I have added 3 Kconfig symbol that maps to tmu driver. exynos common part and exynos configuration data. In the makefile TMU driver is selected when CONFIG_EXYNOS_SOC_THERMAL is enabled. To have extra support for common part and configuration data, those configs need to be enabled Thanks, Amit Daniel > > thanks, > rui >> diff --git a/drivers/thermal/samsung/exynos_thermal.c b/drivers/thermal/samsung/exynos_thermal.c >> index d20ce9e..4c85945 100644 >> --- a/drivers/thermal/samsung/exynos_thermal.c >> +++ b/drivers/thermal/samsung/exynos_thermal.c >> @@ -21,23 +21,19 @@ >> * >> */ >> >> -#include <linux/module.h> >> -#include <linux/err.h> >> -#include <linux/kernel.h> >> -#include <linux/slab.h> >> -#include <linux/platform_device.h> >> -#include <linux/interrupt.h> >> #include <linux/clk.h> >> -#include <linux/workqueue.h> >> -#include <linux/sysfs.h> >> -#include <linux/kobject.h> >> #include <linux/io.h> >> +#include <linux/interrupt.h> >> +#include <linux/kernel.h> >> +#include <linux/kobject.h> >> +#include <linux/module.h> >> #include <linux/mutex.h> >> -#include <linux/platform_data/exynos_thermal.h> >> -#include <linux/thermal.h> >> -#include <linux/cpufreq.h> >> -#include <linux/cpu_cooling.h> >> #include <linux/of.h> >> +#include <linux/platform_device.h> >> +#include <linux/platform_data/exynos_thermal.h> >> +#include <linux/slab.h> >> +#include <linux/workqueue.h> >> +#include "exynos_thermal_common.h" >> >> /* Exynos generic registers */ >> #define EXYNOS_TMU_REG_TRIMINFO 0x0 >> @@ -88,16 +84,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 +92,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 +104,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..9a57606 >> --- /dev/null >> +++ b/drivers/thermal/samsung/exynos_thermal_common.c >> @@ -0,0 +1,389 @@ >> +/* >> + * exynos_thermal_common.c - Samsung EXYNOS common thermal file >> + * >> + * Copyright (C) 2013 Samsung Electronics >> + * Amit Daniel Kachhap <amit.daniel@samsung.com> >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License as published by >> + * the Free Software Foundation; either version 2 of the License, or >> + * (at your option) any later version. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + * GNU General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public License >> + * along with this program; if not, write to the Free Software >> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA >> + * >> + */ >> + >> +#include <linux/cpufreq.h> >> +#include <linux/cpu_cooling.h> >> +#include <linux/err.h> >> +#include <linux/io.h> >> +#include <linux/kernel.h> >> +#include <linux/kobject.h> >> +#include <linux/mutex.h> >> +#include <linux/platform_data/exynos_thermal.h> >> +#include <linux/slab.h> >> +#include <linux/thermal.h> >> +#include "exynos_thermal_common.h" >> + >> +struct exynos_thermal_zone { >> + enum thermal_device_mode mode; >> + struct thermal_zone_device *therm_dev; >> + struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE]; >> + unsigned int cool_dev_size; >> + struct platform_device *exynos4_dev; >> + struct thermal_sensor_conf *sensor_conf; >> + bool bind; >> +}; >> + >> +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..165401f >> --- /dev/null >> +++ b/drivers/thermal/samsung/exynos_thermal_common.h >> @@ -0,0 +1,71 @@ >> +/* >> + * exynos_thermal_common.h - Samsung EXYNOS common header file >> + * >> + * Copyright (C) 2013 Samsung Electronics >> + * Amit Daniel Kachhap <amit.daniel@samsung.com> >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License as published by >> + * the Free Software Foundation; either version 2 of the License, or >> + * (at your option) any later version. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + * GNU General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public License >> + * along with this program; if not, write to the Free Software >> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA >> + * >> + */ >> + >> +#ifndef _LINUX_EXYNOS_THERMAL_COMMON_H >> +#define _LINUX_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; >> + 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; >> +}; >> + >> +/*Functions used exynos based thermal sensor driver*/ >> +void exynos_unregister_thermal(void); >> +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); >> +void exynos_report_trigger(void); >> +#endif /* _LINUX_EXYNOS_THERMAL_COMMON_H */ > > > -- > To unsubscribe from this list: send the line "unsubscribe linux-kernel" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html > Please read the FAQ at http://www.tux.org/lkml/ -- To unsubscribe from this list: send the line "unsubscribe linux-pm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index 2d3d9dc..1e3ba31 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -1,9 +1,21 @@ -config EXYNOS_THERMAL - tristate "Temperature sensor on Samsung EXYNOS" +config EXYNOS_COMMON + bool "Common thermal support for EXYNOS SOC's" 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. + 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. + +if EXYNOS_COMMON + +config EXYNOS_SOC_THERMAL + tristate "Temperature sensor on Samsung EXYNOS series SOC" + depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412 || SOC_EXYNOS5250) + help + If you say yes here you can enable TMU (Thermal Management Unit) on + SAMSUNG EXYNOS 4210, 4412, 4414 and 5250 series of SoC. This option + initialises the TMU controller and registers/unregisters with exynos + common thermal layer. + +endif diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile index 1fe6d93..fcda5b4 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_SOC_THERMAL) += exynos_soc_thermal.o +exynos_soc_thermal-y := exynos_thermal.o +exynos_soc_thermal-$(CONFIG_EXYNOS_COMMON) += exynos_thermal_common.o diff --git a/drivers/thermal/samsung/exynos_thermal.c b/drivers/thermal/samsung/exynos_thermal.c index d20ce9e..4c85945 100644 --- a/drivers/thermal/samsung/exynos_thermal.c +++ b/drivers/thermal/samsung/exynos_thermal.c @@ -21,23 +21,19 @@ * */ -#include <linux/module.h> -#include <linux/err.h> -#include <linux/kernel.h> -#include <linux/slab.h> -#include <linux/platform_device.h> -#include <linux/interrupt.h> #include <linux/clk.h> -#include <linux/workqueue.h> -#include <linux/sysfs.h> -#include <linux/kobject.h> #include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kobject.h> +#include <linux/module.h> #include <linux/mutex.h> -#include <linux/platform_data/exynos_thermal.h> -#include <linux/thermal.h> -#include <linux/cpufreq.h> -#include <linux/cpu_cooling.h> #include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/platform_data/exynos_thermal.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include "exynos_thermal_common.h" /* Exynos generic registers */ #define EXYNOS_TMU_REG_TRIMINFO 0x0 @@ -88,16 +84,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 +92,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 +104,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..9a57606 --- /dev/null +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -0,0 +1,389 @@ +/* + * exynos_thermal_common.c - Samsung EXYNOS common thermal file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap <amit.daniel@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/cpufreq.h> +#include <linux/cpu_cooling.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/kobject.h> +#include <linux/mutex.h> +#include <linux/platform_data/exynos_thermal.h> +#include <linux/slab.h> +#include <linux/thermal.h> +#include "exynos_thermal_common.h" + +struct exynos_thermal_zone { + enum thermal_device_mode mode; + struct thermal_zone_device *therm_dev; + struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE]; + unsigned int cool_dev_size; + struct platform_device *exynos4_dev; + struct thermal_sensor_conf *sensor_conf; + bool bind; +}; + +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..165401f --- /dev/null +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -0,0 +1,71 @@ +/* + * exynos_thermal_common.h - Samsung EXYNOS common header file + * + * Copyright (C) 2013 Samsung Electronics + * Amit Daniel Kachhap <amit.daniel@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _LINUX_EXYNOS_THERMAL_COMMON_H +#define _LINUX_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; + 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; +}; + +/*Functions used exynos based thermal sensor driver*/ +void exynos_unregister_thermal(void); +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); +void exynos_report_trigger(void); +#endif /* _LINUX_EXYNOS_THERMAL_COMMON_H */