On 12 May 2012 17:40, Amit Daniel Kachhap <amit.kach...@linaro.org> wrote:
> This code added creates a link between temperature sensors, linux thermal > framework and cooling devices for samsung exynos platform. This layer > monitors the temperature from the sensor and informs the generic thermal > layer to take the necessary cooling action. > > Signed-off-by: Amit Daniel Kachhap <amit.kach...@linaro.org> > --- > drivers/thermal/exynos_thermal.c | 344 > +++++++++++++++++++++++++- > include/linux/platform_data/exynos_thermal.h | 6 + > 2 files changed, 348 insertions(+), 2 deletions(-) > > diff --git a/drivers/thermal/exynos_thermal.c > b/drivers/thermal/exynos_thermal.c > index cfe4aeb..48106d8 100644 > --- a/drivers/thermal/exynos_thermal.c > +++ b/drivers/thermal/exynos_thermal.c > @@ -35,6 +35,9 @@ > #include <linux/mutex.h> > #include <linux/err.h> > #include <linux/platform_data/exynos_thermal.h> > +#include <linux/thermal.h> > +#include <linux/cpufreq.h> > +#include <linux/cpu_cooling.h> > #include <linux/of.h> > > #include <plat/cpu.h> > @@ -95,6 +98,7 @@ > > #define ACTIVE_INTERVAL 500 > #define IDLE_INTERVAL 10000 > +#define MCELSIUS 1000 > > /* CPU Zone information */ > #define PANIC_ZONE 4 > @@ -105,6 +109,8 @@ > #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; > @@ -117,6 +123,309 @@ struct exynos_tmu_data { > u8 temp_error1, temp_error2; > }; > > +struct thermal_trip_point_conf { > + int trip_val[MAX_TRIP_COUNT]; > + int trip_count; > +}; > + > +struct thermal_cooling_conf { > + struct freq_clip_table freq_data[MAX_TRIP_COUNT]; > + int freq_clip_count; > +}; > + > +struct thermal_sensor_conf { > + char name[SENSOR_NAME_LEN]; > + int (*read_temperature)(void *data); > + struct thermal_trip_point_conf trip_data; > + struct thermal_cooling_conf cooling_data; > + void *private_data; > +}; > + > +struct exynos_thermal_zone { > + enum thermal_device_mode mode; > + struct thermal_zone_device *therm_dev; > + struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE]; > + unsigned int cool_dev_size; > + struct platform_device *exynos4_dev; > + struct thermal_sensor_conf *sensor_conf; > +}; > + > +static struct exynos_thermal_zone *th_zone; > +static void exynos_unregister_thermal(void); > +static int exynos_register_thermal(struct thermal_sensor_conf > *sensor_conf); > + > +/* Get mode callback functions for thermal zone */ > +static int exynos_get_mode(struct thermal_zone_device *thermal, > + enum thermal_device_mode *mode) > +{ > + if (th_zone) > + *mode = th_zone->mode; > + return 0; > +} > + > +/* Set mode callback functions for thermal zone */ > +static int exynos_set_mode(struct thermal_zone_device *thermal, > + enum thermal_device_mode mode) > +{ > + if (!th_zone->therm_dev) { > + pr_notice("thermal zone not registered\n"); > + return 0; > + } > + > + mutex_lock(&th_zone->therm_dev->lock); > + > + if (mode == THERMAL_DEVICE_ENABLED) > + th_zone->therm_dev->polling_delay = IDLE_INTERVAL; > + else > + th_zone->therm_dev->polling_delay = 0; > + > + mutex_unlock(&th_zone->therm_dev->lock); > + > + th_zone->mode = mode; > + thermal_zone_device_update(th_zone->therm_dev); > I think it should be like this: if (mode == THERMAL_DEVICE_ENABLED) thermal_zone_device_update(th_zone->therm_dev); else disable cooling device; Imagine that when CPU goes high and the frequency is limited to low, and the you think it is safe I want a high speed CPU, then you disable the thermal mode by sysfs, but the CPU frequency is still low. + pr_info("thermal polling set for duration=%d msec\n", > + th_zone->therm_dev->polling_delay); > + return 0; > +} > + > +/* > + * 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; > + > + thermal_zone_device_update(th_zone->therm_dev); > + > + mutex_lock(&th_zone->therm_dev->lock); > + /* Find the level for which trip happened */ > + for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) { > + if (th_zone->therm_dev->last_temperature < > + th_zone->sensor_conf->trip_data.trip_val[i] * > MCELSIUS) > + break; > + } > + > + if (th_zone->mode == THERMAL_DEVICE_ENABLED) { > + if (i > 0) > + th_zone->therm_dev->polling_delay = > ACTIVE_INTERVAL; > + else > + th_zone->therm_dev->polling_delay = IDLE_INTERVAL; > + } > + > + snprintf(data, sizeof(data), "%u", i); > + kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, > envp); > + mutex_unlock(&th_zone->therm_dev->lock); > +} > + > +/* 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; > + > + /* 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; > + > + switch (GET_ZONE(i)) { > + case MONITOR_ZONE: > + case WARN_ZONE: > + if (thermal_zone_bind_cooling_device(thermal, i, cdev)) { > + pr_err("error binding cooling dev inst 0\n"); > + ret = -EINVAL; > + } > + 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; > + > + /* 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; > + > + switch (GET_ZONE(i)) { > + case MONITOR_ZONE: > + case WARN_ZONE: > + if (thermal_zone_unbind_cooling_device(thermal, i, cdev)) > { > Cooling devices should be disabled before unbinding? + pr_err("error unbinding cooling dev\n"); > + ret = -EINVAL; > + } > + 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; > +} > + > +/* Operation callback functions for thermal zone */ > +static struct thermal_zone_device_ops const exynos_dev_ops = { > + .bind = exynos_bind, > + .unbind = exynos_unbind, > + .get_temp = exynos_get_temp, > + .get_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, > +}; > + > +/* Register with the in-kernel thermal management */ > +static int exynos_register_thermal(struct thermal_sensor_conf > *sensor_conf) > +{ > + int ret, count, tab_size; > + struct freq_clip_table *tab_ptr, *clip_data; > + > + 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; > + > + tab_ptr = (struct freq_clip_table > *)sensor_conf->cooling_data.freq_data; > + tab_size = sensor_conf->cooling_data.freq_clip_count; > + > + /* Register the cpufreq cooling device */ > + for (count = 0; count < tab_size; count++) { > + clip_data = (struct freq_clip_table *)&(tab_ptr[count]); > + clip_data->mask_val = cpumask_of(0); > + th_zone->cool_dev[count] = cpufreq_cooling_register( > + clip_data, 1); > + if (IS_ERR(th_zone->cool_dev[count])) { > + pr_err("Failed to register cpufreq cooling > device\n"); > + ret = -EINVAL; > + th_zone->cool_dev_size = count; > + goto err_unregister; > + } > + } > + th_zone->cool_dev_size = count; > + > + th_zone->therm_dev = > thermal_zone_device_register(sensor_conf->name, > + EXYNOS_ZONE_COUNT, NULL, &exynos_dev_ops, 0, 0, 0, > + IDLE_INTERVAL); > + > + if (IS_ERR(th_zone->therm_dev)) { > + pr_err("Failed to register thermal zone device\n"); > + ret = -EINVAL; > + goto err_unregister; > + } > + th_zone->mode = THERMAL_DEVICE_ENABLED; > + > + pr_info("Exynos: Kernel Thermal management registered\n"); > + > + return 0; > + > +err_unregister: > + exynos_unregister_thermal(); > + return ret; > +} > + > +/* Un-Register with the in-kernel thermal management */ > +static void exynos_unregister_thermal(void) > +{ > + int i; > + > + for (i = 0; i < th_zone->cool_dev_size; i++) { > + if (th_zone && th_zone->cool_dev[i]) > + cpufreq_cooling_unregister(th_zone->cool_dev[i]); > + } > + > + if (th_zone && th_zone->therm_dev) > + thermal_zone_device_unregister(th_zone->therm_dev); > + > + 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. > @@ -337,6 +646,7 @@ static void exynos_tmu_work(struct work_struct *work) > > clk_disable(data->clk); > mutex_unlock(&data->lock); > + exynos_report_trigger(); > enable_irq(data->irq); > } > > @@ -349,12 +659,16 @@ 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, > +} > +; > static int __devinit exynos_tmu_probe(struct platform_device *pdev) > { > struct exynos_tmu_data *data; > struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data; > - int ret; > + int ret, i; > > if (!pdata) { > dev_err(&pdev->dev, "No platform init data supplied.\n"); > @@ -432,6 +746,30 @@ static int __devinit 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.trip_data.trip_count = pdata->trigger_level0_en > + > + pdata->trigger_level1_en + > pdata->trigger_level2_en + > + pdata->trigger_level3_en; > + > + for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++) > + exynos_sensor_conf.trip_data.trip_val[i] = > + pdata->threshold + pdata->trigger_levels[i]; > + > + exynos_sensor_conf.cooling_data.freq_clip_count = > + pdata->freq_tab_count; > + for (i = 0; i < pdata->freq_tab_count; i++) { > + exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max > = > + pdata->freq_tab[i].freq_clip_max; > + exynos_sensor_conf.cooling_data.freq_data[i].temp_level = > + pdata->freq_tab[i].temp_level; > + } > + > + ret = exynos_register_thermal(&exynos_sensor_conf); > + if (ret) { > + dev_err(&pdev->dev, "Failed to register thermal > interface\n"); > + goto err_clk; > + } > return 0; > err_clk: > platform_set_drvdata(pdev, NULL); > @@ -454,6 +792,8 @@ static int __devexit exynos_tmu_remove(struct > platform_device *pdev) > > exynos_tmu_control(pdev, false); > > + exynos_unregister_thermal(); > + > clk_put(data->clk); > > free_irq(data->irq, data); > diff --git a/include/linux/platform_data/exynos_thermal.h > b/include/linux/platform_data/exynos_thermal.h > index c980af6..858eaca 100644 > --- a/include/linux/platform_data/exynos_thermal.h > +++ b/include/linux/platform_data/exynos_thermal.h > @@ -21,6 +21,7 @@ > > #ifndef _LINUX_EXYNOS_THERMAL_H > #define _LINUX_EXYNOS_THERMAL_H > +#include <linux/cpu_cooling.h> > > enum calibration_type { > TYPE_ONE_POINT_TRIMMING, > @@ -72,6 +73,9 @@ enum soc_type { > * @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. > */ > @@ -90,5 +94,7 @@ struct exynos_tmu_platform_data { > > 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 */ > -- > 1.7.1 > > > _______________________________________________ > linaro-dev mailing list > linaro-dev@lists.linaro.org > http://lists.linaro.org/mailman/listinfo/linaro-dev >
_______________________________________________ linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev