diff options
Diffstat (limited to 'drivers/thermal')
-rw-r--r-- | drivers/thermal/Kconfig | 127 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 21 | ||||
-rw-r--r-- | drivers/thermal/cpu_cooling.c | 128 | ||||
-rw-r--r-- | drivers/thermal/db8500_cpufreq_cooling.c | 107 | ||||
-rw-r--r-- | drivers/thermal/db8500_thermal.c | 529 | ||||
-rw-r--r-- | drivers/thermal/dove_thermal.c | 209 | ||||
-rw-r--r-- | drivers/thermal/exynos_thermal.c | 227 | ||||
-rw-r--r-- | drivers/thermal/fair_share.c | 133 | ||||
-rw-r--r-- | drivers/thermal/intel_powerclamp.c | 795 | ||||
-rw-r--r-- | drivers/thermal/kirkwood_thermal.c | 134 | ||||
-rw-r--r-- | drivers/thermal/rcar_thermal.c | 501 | ||||
-rw-r--r-- | drivers/thermal/spear_thermal.c | 9 | ||||
-rw-r--r-- | drivers/thermal/step_wise.c | 202 | ||||
-rw-r--r-- | drivers/thermal/thermal_core.h | 53 | ||||
-rw-r--r-- | drivers/thermal/thermal_sys.c | 803 | ||||
-rw-r--r-- | drivers/thermal/user_space.c | 68 |
16 files changed, 3484 insertions, 562 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e1cb6bd75f60..a764f165b589 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -13,15 +13,62 @@ menuconfig THERMAL All platforms with ACPI thermal support can use this driver. If you want this support, you should say Y or M here. +if THERMAL + config THERMAL_HWMON bool - depends on THERMAL depends on HWMON=y || HWMON=THERMAL default y +choice + prompt "Default Thermal governor" + default THERMAL_DEFAULT_GOV_STEP_WISE + help + This option sets which thermal governor shall be loaded at + startup. If in doubt, select 'step_wise'. + +config THERMAL_DEFAULT_GOV_STEP_WISE + bool "step_wise" + select THERMAL_GOV_STEP_WISE + help + Use the step_wise governor as default. This throttles the + devices one step at a time. + +config THERMAL_DEFAULT_GOV_FAIR_SHARE + bool "fair_share" + select THERMAL_GOV_FAIR_SHARE + help + Use the fair_share governor as default. This throttles the + devices based on their 'contribution' to a zone. The + contribution should be provided through platform data. + +config THERMAL_DEFAULT_GOV_USER_SPACE + bool "user_space" + select THERMAL_GOV_USER_SPACE + help + Select this if you want to let the user space manage the + lpatform thermals. + +endchoice + +config THERMAL_GOV_FAIR_SHARE + bool "Fair-share thermal governor" + help + Enable this to manage platform thermals using fair-share governor. + +config THERMAL_GOV_STEP_WISE + bool "Step_wise thermal governor" + help + Enable this to manage platform thermals using a simple linear + +config THERMAL_GOV_USER_SPACE + bool "User_space thermal governor" + help + Enable this to let the user space manage the platform thermals. + config CPU_THERMAL - bool "generic cpu cooling support" - depends on THERMAL && CPU_FREQ + tristate "generic cpu cooling support" + depends on CPU_FREQ select CPU_FREQ_TABLE help This implements the generic cpu cooling mechanism through frequency @@ -31,9 +78,16 @@ config CPU_THERMAL and not the ACPI interface. If you want this support, you should say Y here. +config THERMAL_EMULATION + bool "Thermal emulation mode support" + help + Enable this option to make a emul_temp sysfs node in thermal zone + directory to support temperature emulation. With emulation sysfs node, + user can manually input temperature and test the different trip + threshold behaviour for simulation purpose. + config SPEAR_THERMAL bool "SPEAr thermal sensor driver" - depends on THERMAL depends on PLAT_SPEAR depends on OF help @@ -42,16 +96,73 @@ config SPEAR_THERMAL config RCAR_THERMAL tristate "Renesas R-Car thermal driver" - depends on THERMAL depends on ARCH_SHMOBILE help Enable this to plug the R-Car thermal sensor driver into the Linux thermal framework +config KIRKWOOD_THERMAL + tristate "Temperature sensor on Marvell Kirkwood SoCs" + depends on ARCH_KIRKWOOD + depends on OF + help + Support for the Kirkwood thermal sensor driver into the Linux thermal + framework. Only kirkwood 88F6282 and 88F6283 have this sensor. + config EXYNOS_THERMAL tristate "Temperature sensor on Samsung EXYNOS" - depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) && THERMAL - select CPU_FREQ_TABLE + depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) + depends on CPU_THERMAL help - If you say yes here you get support for TMU (Thermal Managment + If you say yes here you get support for TMU (Thermal Management Unit) on SAMSUNG EXYNOS series of SoC. + +config EXYNOS_THERMAL_EMUL + bool "EXYNOS TMU emulation mode support" + depends on EXYNOS_THERMAL + help + Exynos 4412 and 4414 and 5 series has emulation mode on TMU. + Enable this option will be make sysfs node in exynos thermal platform + device directory to support emulation mode. With emulation mode sysfs + node, you can manually input temperature to TMU for simulation purpose. + +config DOVE_THERMAL + tristate "Temperature sensor on Marvell Dove SoCs" + depends on ARCH_DOVE + depends on OF + help + Support for the Dove thermal sensor driver in the Linux thermal + framework. + +config DB8500_THERMAL + bool "DB8500 thermal management" + depends on ARCH_U8500 + default y + help + Adds DB8500 thermal management implementation according to the thermal + management framework. A thermal zone with several trip points will be + created. Cooling devices can be bound to the trip points to cool this + thermal zone if trip points reached. + +config DB8500_CPUFREQ_COOLING + tristate "DB8500 cpufreq cooling" + depends on ARCH_U8500 + depends on CPU_THERMAL + default y + help + Adds DB8500 cpufreq cooling devices, and these cooling devices can be + bound to thermal zone trip points. When a trip point reached, the + bound cpufreq cooling device turns active to set CPU frequency low to + cool down the CPU. + +config INTEL_POWERCLAMP + tristate "Intel PowerClamp idle injection driver" + depends on THERMAL + depends on X86 + depends on CPU_SUP_INTEL + help + Enable this to enable Intel PowerClamp idle injection driver. This + enforce idle time which results in more package C-state residency. The + user interface is exposed via generic thermal framework. + +endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 885550dc64b7..d3a2b38c31e8 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -3,7 +3,22 @@ # obj-$(CONFIG_THERMAL) += thermal_sys.o -obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o -obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o + +# governors +obj-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o +obj-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o +obj-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o + +# cpufreq cooling +obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o + +# platform thermal drivers +obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o -obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o +obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o +obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o +obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o +obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o + diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index cc1c930a90e4..8dc44cbb3e09 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -58,12 +58,13 @@ struct cpufreq_cooling_device { }; static LIST_HEAD(cooling_cpufreq_list); static DEFINE_IDR(cpufreq_idr); +static DEFINE_MUTEX(cooling_cpufreq_lock); -static struct mutex cooling_cpufreq_lock; +static unsigned int cpufreq_dev_count; /* notify_table passes value to the CPUFREQ_ADJUST callback function. */ #define NOTIFY_INVALID NULL -struct cpufreq_cooling_device *notify_device; +static struct cpufreq_cooling_device *notify_device; /** * get_idr - function to get a unique id. @@ -72,21 +73,14 @@ struct cpufreq_cooling_device *notify_device; */ static int get_idr(struct idr *idr, int *id) { - int err; -again: - if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0)) - return -ENOMEM; + int ret; mutex_lock(&cooling_cpufreq_lock); - err = idr_get_new(idr, NULL, id); + ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL); mutex_unlock(&cooling_cpufreq_lock); - - if (unlikely(err == -EAGAIN)) - goto again; - else if (unlikely(err)) - return err; - - *id = *id & MAX_IDR_MASK; + if (unlikely(ret < 0)) + return ret; + *id = ret; return 0; } @@ -117,8 +111,8 @@ static int is_cpufreq_valid(int cpu) /** * get_cpu_frequency - get the absolute value of frequency from level. * @cpu: cpu for which frequency is fetched. - * @level: level of frequency of the CPU - * e.g level=1 --> 1st MAX FREQ, LEVEL=2 ---> 2nd MAX FREQ, .... etc + * @level: level of frequency, equals cooling state of cpu cooling device + * e.g level=0 --> 1st MAX FREQ, level=1 ---> 2nd MAX FREQ, .... etc */ static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level) { @@ -240,42 +234,32 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb, static int cpufreq_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { - int ret = -EINVAL, i = 0; - struct cpufreq_cooling_device *cpufreq_device; - struct cpumask *maskPtr; + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; + struct cpumask *maskPtr = &cpufreq_device->allowed_cpus; unsigned int cpu; struct cpufreq_frequency_table *table; + unsigned long count = 0; + int i = 0; - mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) - break; - } - if (cpufreq_device == NULL) - goto return_get_max_state; - - maskPtr = &cpufreq_device->allowed_cpus; cpu = cpumask_any(maskPtr); table = cpufreq_frequency_get_table(cpu); if (!table) { *state = 0; - ret = 0; - goto return_get_max_state; + return 0; } - while (table[i].frequency != CPUFREQ_TABLE_END) { + for (i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++) { if (table[i].frequency == CPUFREQ_ENTRY_INVALID) continue; - i++; + count++; } - if (i > 0) { - *state = --i; - ret = 0; + + if (count > 0) { + *state = --count; + return 0; } -return_get_max_state: - mutex_unlock(&cooling_cpufreq_lock); - return ret; + return -EINVAL; } /** @@ -286,20 +270,10 @@ return_get_max_state: static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) { - int ret = -EINVAL; - struct cpufreq_cooling_device *cpufreq_device; - - mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) { - *state = cpufreq_device->cpufreq_state; - ret = 0; - break; - } - } - mutex_unlock(&cooling_cpufreq_lock); + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; - return ret; + *state = cpufreq_device->cpufreq_state; + return 0; } /** @@ -310,22 +284,9 @@ static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev, static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { - int ret = -EINVAL; - struct cpufreq_cooling_device *cpufreq_device; - - mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) { - if (cpufreq_device && cpufreq_device->cool_dev == cdev) { - ret = 0; - break; - } - } - if (!ret) - ret = cpufreq_apply_cooling(cpufreq_device, state); + struct cpufreq_cooling_device *cpufreq_device = cdev->devdata; - mutex_unlock(&cooling_cpufreq_lock); - - return ret; + return cpufreq_apply_cooling(cpufreq_device, state); } /* Bind cpufreq callbacks to thermal cooling device ops */ @@ -345,18 +306,15 @@ static struct notifier_block thermal_cpufreq_notifier_block = { * @clip_cpus: cpumask of cpus where the frequency constraints will happen. */ struct thermal_cooling_device *cpufreq_cooling_register( - struct cpumask *clip_cpus) + const struct cpumask *clip_cpus) { struct thermal_cooling_device *cool_dev; struct cpufreq_cooling_device *cpufreq_dev = NULL; - unsigned int cpufreq_dev_count = 0, min = 0, max = 0; + unsigned int min = 0, max = 0; char dev_name[THERMAL_NAME_LENGTH]; int ret = 0, i; struct cpufreq_policy policy; - list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) - cpufreq_dev_count++; - /*Verify that all the clip cpus have same freq_min, freq_max limit*/ for_each_cpu(i, clip_cpus) { /*continue if cpufreq policy not found and not return error*/ @@ -369,7 +327,7 @@ struct thermal_cooling_device *cpufreq_cooling_register( if (min != policy.cpuinfo.min_freq || max != policy.cpuinfo.max_freq) return ERR_PTR(-EINVAL); -} + } } cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device), GFP_KERNEL); @@ -378,9 +336,6 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus); - if (cpufreq_dev_count == 0) - mutex_init(&cooling_cpufreq_lock); - ret = get_idr(&cpufreq_idr, &cpufreq_dev->id); if (ret) { kfree(cpufreq_dev); @@ -399,12 +354,12 @@ struct thermal_cooling_device *cpufreq_cooling_register( cpufreq_dev->cool_dev = cool_dev; cpufreq_dev->cpufreq_state = 0; mutex_lock(&cooling_cpufreq_lock); - list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list); /* Register the notifier for first cpufreq cooling device */ if (cpufreq_dev_count == 0) cpufreq_register_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); + cpufreq_dev_count++; mutex_unlock(&cooling_cpufreq_lock); return cool_dev; @@ -417,33 +372,20 @@ EXPORT_SYMBOL(cpufreq_cooling_register); */ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev) { - struct cpufreq_cooling_device *cpufreq_dev = NULL; - unsigned int cpufreq_dev_count = 0; + struct cpufreq_cooling_device *cpufreq_dev = cdev->devdata; mutex_lock(&cooling_cpufreq_lock); - list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) { - if (cpufreq_dev && cpufreq_dev->cool_dev == cdev) - break; - cpufreq_dev_count++; - } - - if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) { - mutex_unlock(&cooling_cpufreq_lock); - return; - } - - list_del(&cpufreq_dev->node); + cpufreq_dev_count--; /* Unregister the notifier for the last cpufreq cooling device */ - if (cpufreq_dev_count == 1) { + if (cpufreq_dev_count == 0) { cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block, CPUFREQ_POLICY_NOTIFIER); } mutex_unlock(&cooling_cpufreq_lock); + thermal_cooling_device_unregister(cpufreq_dev->cool_dev); release_idr(&cpufreq_idr, cpufreq_dev->id); - if (cpufreq_dev_count == 1) - mutex_destroy(&cooling_cpufreq_lock); kfree(cpufreq_dev); } EXPORT_SYMBOL(cpufreq_cooling_unregister); diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c new file mode 100644 index 000000000000..21419851fc02 --- /dev/null +++ b/drivers/thermal/db8500_cpufreq_cooling.c @@ -0,0 +1,107 @@ +/* + * db8500_cpufreq_cooling.c - DB8500 cpufreq works as cooling device. + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang <hongbo.zhang@linaro.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. + */ + +#include <linux/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +static int db8500_cpufreq_cooling_probe(struct platform_device *pdev) +{ + struct thermal_cooling_device *cdev; + struct cpumask mask_val; + + /* make sure cpufreq driver has been initialized */ + if (!cpufreq_frequency_get_table(0)) + return -EPROBE_DEFER; + + cpumask_set_cpu(0, &mask_val); + cdev = cpufreq_cooling_register(&mask_val); + + if (IS_ERR_OR_NULL(cdev)) { + dev_err(&pdev->dev, "Failed to register cooling device\n"); + return PTR_ERR(cdev); + } + + platform_set_drvdata(pdev, cdev); + + dev_info(&pdev->dev, "Cooling device registered: %s\n", cdev->type); + + return 0; +} + +static int db8500_cpufreq_cooling_remove(struct platform_device *pdev) +{ + struct thermal_cooling_device *cdev = platform_get_drvdata(pdev); + + cpufreq_cooling_unregister(cdev); + + return 0; +} + +static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return -ENOSYS; +} + +static int db8500_cpufreq_cooling_resume(struct platform_device *pdev) +{ + return -ENOSYS; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_cpufreq_cooling_match[] = { + { .compatible = "stericsson,db8500-cpufreq-cooling" }, + {}, +}; +#endif + +static struct platform_driver db8500_cpufreq_cooling_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-cpufreq-cooling", + .of_match_table = of_match_ptr(db8500_cpufreq_cooling_match), + }, + .probe = db8500_cpufreq_cooling_probe, + .suspend = db8500_cpufreq_cooling_suspend, + .resume = db8500_cpufreq_cooling_resume, + .remove = db8500_cpufreq_cooling_remove, +}; + +static int __init db8500_cpufreq_cooling_init(void) +{ + return platform_driver_register(&db8500_cpufreq_cooling_driver); +} + +static void __exit db8500_cpufreq_cooling_exit(void) +{ + platform_driver_unregister(&db8500_cpufreq_cooling_driver); +} + +/* Should be later than db8500_cpufreq_register */ +late_initcall(db8500_cpufreq_cooling_init); +module_exit(db8500_cpufreq_cooling_exit); + +MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>"); +MODULE_DESCRIPTION("DB8500 cpufreq cooling driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c new file mode 100644 index 000000000000..61ce60a35921 --- /dev/null +++ b/drivers/thermal/db8500_thermal.c @@ -0,0 +1,529 @@ +/* + * db8500_thermal.c - DB8500 Thermal Management Implementation + * + * Copyright (C) 2012 ST-Ericsson + * Copyright (C) 2012 Linaro Ltd. + * + * Author: Hongbo Zhang <hongbo.zhang@linaro.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. + */ + +#include <linux/cpu_cooling.h> +#include <linux/interrupt.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_data/db8500_thermal.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h> + +#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF +#define PRCMU_DEFAULT_LOW_TEMP 0 + +struct db8500_thermal_zone { + struct thermal_zone_device *therm_dev; + struct mutex th_lock; + struct work_struct therm_work; + struct db8500_thsens_platform_data *trip_tab; + enum thermal_device_mode mode; + enum thermal_trend trend; + unsigned long cur_temp_pseudo; + unsigned int cur_index; +}; + +/* Local function to check if thermal zone matches cooling devices */ +static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev, + struct db8500_trip_point *trip_point) +{ + int i; + + if (!strlen(cdev->type)) + return -EINVAL; + + for (i = 0; i < COOLING_DEV_MAX; i++) { + if (!strcmp(trip_point->cdev_name[i], cdev->type)) + return 0; + } + + return -ENODEV; +} + +/* Callback to bind cooling device to thermal zone */ +static int db8500_cdev_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + unsigned long max_state, upper, lower; + int i, ret = -EINVAL; + + cdev->ops->get_max_state(cdev, &max_state); + + for (i = 0; i < ptrips->num_trips; i++) { + if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) + continue; + + upper = lower = i > max_state ? max_state : i; + + ret = thermal_zone_bind_cooling_device(thermal, i, cdev, + upper, lower); + + dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type, + i, ret, ret ? "fail" : "succeed"); + } + + return ret; +} + +/* Callback to unbind cooling device from thermal zone */ +static int db8500_cdev_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + int i, ret = -EINVAL; + + for (i = 0; i < ptrips->num_trips; i++) { + if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) + continue; + + ret = thermal_zone_unbind_cooling_device(thermal, i, cdev); + + dev_info(&cdev->device, "%s unbind from %d: %s\n", cdev->type, + i, ret ? "fail" : "succeed"); + } + + return ret; +} + +/* Callback to get current temperature */ +static int db8500_sys_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + /* + * TODO: There is no PRCMU interface to get temperature data currently, + * so a pseudo temperature is returned , it works for thermal framework + * and this will be fixed when the PRCMU interface is available. + */ + *temp = pzone->cur_temp_pseudo; + + return 0; +} + +/* Callback to get temperature changing trend */ +static int db8500_sys_get_trend(struct thermal_zone_device *thermal, + int trip, enum thermal_trend *trend) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + *trend = pzone->trend; + + return 0; +} + +/* Callback to get thermal zone mode */ +static int db8500_sys_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + mutex_lock(&pzone->th_lock); + *mode = pzone->mode; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Callback to set thermal zone mode */ +static int db8500_sys_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + + mutex_lock(&pzone->th_lock); + + pzone->mode = mode; + if (mode == THERMAL_DEVICE_ENABLED) + schedule_work(&pzone->therm_work); + + mutex_unlock(&pzone->th_lock); + + return 0; +} + +/* Callback to get trip point type */ +static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *type = ptrips->trip_points[trip].type; + + return 0; +} + +/* Callback to get trip point temperature */ +static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal, + int trip, unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + + if (trip >= ptrips->num_trips) + return -EINVAL; + + *temp = ptrips->trip_points[trip].temp; + + return 0; +} + +/* Callback to get critical trip point temperature */ +static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct db8500_thermal_zone *pzone = thermal->devdata; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + int i; + + for (i = ptrips->num_trips - 1; i > 0; i--) { + if (ptrips->trip_points[i].type == THERMAL_TRIP_CRITICAL) { + *temp = ptrips->trip_points[i].temp; + return 0; + } + } + + return -EINVAL; +} + +static struct thermal_zone_device_ops thdev_ops = { + .bind = db8500_cdev_bind, + .unbind = db8500_cdev_unbind, + .get_temp = db8500_sys_get_temp, + .get_trend = db8500_sys_get_trend, + .get_mode = db8500_sys_get_mode, + .set_mode = db8500_sys_set_mode, + .get_trip_type = db8500_sys_get_trip_type, + .get_trip_temp = db8500_sys_get_trip_temp, + .get_crit_temp = db8500_sys_get_crit_temp, +}; + +static void db8500_thermal_update_config(struct db8500_thermal_zone *pzone, + unsigned int idx, enum thermal_trend trend, + unsigned long next_low, unsigned long next_high) +{ + prcmu_stop_temp_sense(); + + pzone->cur_index = idx; + pzone->cur_temp_pseudo = (next_low + next_high)/2; + pzone->trend = trend; + + prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); + prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); +} + +static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data) +{ + struct db8500_thermal_zone *pzone = irq_data; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + unsigned int idx = pzone->cur_index; + unsigned long next_low, next_high; + + if (unlikely(idx == 0)) + /* Meaningless for thermal management, ignoring it */ + return IRQ_HANDLED; + + if (idx == 1) { + next_high = ptrips->trip_points[0].temp; + next_low = PRCMU_DEFAULT_LOW_TEMP; + } else { + next_high = ptrips->trip_points[idx-1].temp; + next_low = ptrips->trip_points[idx-2].temp; + } + idx -= 1; + + db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING, + next_low, next_high); + + dev_dbg(&pzone->therm_dev->device, + "PRCMU set max %ld, min %ld\n", next_high, next_low); + + schedule_work(&pzone->therm_work); + + return IRQ_HANDLED; +} + +static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data) +{ + struct db8500_thermal_zone *pzone = irq_data; + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + unsigned int idx = pzone->cur_index; + unsigned long next_low, next_high; + + if (idx < ptrips->num_trips - 1) { + next_high = ptrips->trip_points[idx+1].temp; + next_low = ptrips->trip_points[idx].temp; + idx += 1; + + db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING, + next_low, next_high); + + dev_dbg(&pzone->therm_dev->device, + "PRCMU set max %ld, min %ld\n", next_high, next_low); + } else if (idx == ptrips->num_trips - 1) + pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1; + + schedule_work(&pzone->therm_work); + + return IRQ_HANDLED; +} + +static void db8500_thermal_work(struct work_struct *work) +{ + enum thermal_device_mode cur_mode; + struct db8500_thermal_zone *pzone; + + pzone = container_of(work, struct db8500_thermal_zone, therm_work); + + mutex_lock(&pzone->th_lock); + cur_mode = pzone->mode; + mutex_unlock(&pzone->th_lock); + + if (cur_mode == THERMAL_DEVICE_DISABLED) + return; + + thermal_zone_device_update(pzone->therm_dev); + dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n"); +} + +#ifdef CONFIG_OF +static struct db8500_thsens_platform_data* + db8500_thermal_parse_dt(struct platform_device *pdev) +{ + struct db8500_thsens_platform_data *ptrips; + struct device_node *np = pdev->dev.of_node; + char prop_name[32]; + const char *tmp_str; + u32 tmp_data; + int i, j; + + ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL); + if (!ptrips) + return NULL; + + if (of_property_read_u32(np, "num-trips", &tmp_data)) + goto err_parse_dt; + + if (tmp_data > THERMAL_MAX_TRIPS) + goto err_parse_dt; + + ptrips->num_trips = tmp_data; + + for (i = 0; i < ptrips->num_trips; i++) { + sprintf(prop_name, "trip%d-temp", i); + if (of_property_read_u32(np, prop_name, &tmp_data)) + goto err_parse_dt; + + ptrips->trip_points[i].temp = tmp_data; + + sprintf(prop_name, "trip%d-type", i); + if (of_property_read_string(np, prop_name, &tmp_str)) + goto err_parse_dt; + + if (!strcmp(tmp_str, "active")) + ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE; + else if (!strcmp(tmp_str, "passive")) + ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE; + else if (!strcmp(tmp_str, "hot")) + ptrips->trip_points[i].type = THERMAL_TRIP_HOT; + else if (!strcmp(tmp_str, "critical")) + ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL; + else + goto err_parse_dt; + + sprintf(prop_name, "trip%d-cdev-num", i); + if (of_property_read_u32(np, prop_name, &tmp_data)) + goto err_parse_dt; + + if (tmp_data > COOLING_DEV_MAX) + goto err_parse_dt; + + for (j = 0; j < tmp_data; j++) { + sprintf(prop_name, "trip%d-cdev-name%d", i, j); + if (of_property_read_string(np, prop_name, &tmp_str)) + goto err_parse_dt; + + if (strlen(tmp_str) >= THERMAL_NAME_LENGTH) + goto err_parse_dt; + + strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str); + } + } + return ptrips; + +err_parse_dt: + dev_err(&pdev->dev, "Parsing device tree data error.\n"); + return NULL; +} +#else +static inline struct db8500_thsens_platform_data* + db8500_thermal_parse_dt(struct platform_device *pdev) +{ + return NULL; +} +#endif + +static int db8500_thermal_probe(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = NULL; + struct db8500_thsens_platform_data *ptrips = NULL; + struct device_node *np = pdev->dev.of_node; + int low_irq, high_irq, ret = 0; + unsigned long dft_low, dft_high; + + if (np) + ptrips = db8500_thermal_parse_dt(pdev); + else + ptrips = dev_get_platdata(&pdev->dev); + + if (!ptrips) + return -EINVAL; + + pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL); + if (!pzone) + return -ENOMEM; + + mutex_init(&pzone->th_lock); + mutex_lock(&pzone->th_lock); + + pzone->mode = THERMAL_DEVICE_DISABLED; + pzone->trip_tab = ptrips; + + INIT_WORK(&pzone->therm_work, db8500_thermal_work); + + low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); + if (low_irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n"); + return low_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL, + prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "dbx500_temp_low", pzone); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to allocate temp low irq.\n"); + return ret; + } + + high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (high_irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n"); + return high_irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL, + prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "dbx500_temp_high", pzone); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to allocate temp high irq.\n"); + return ret; + } + + pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone", + ptrips->num_trips, 0, pzone, &thdev_ops, NULL, 0, 0); + + if (IS_ERR_OR_NULL(pzone->therm_dev)) { + dev_err(&pdev->dev, "Register thermal zone device failed.\n"); + return PTR_ERR(pzone->therm_dev); + } + dev_info(&pdev->dev, "Thermal zone device registered.\n"); + + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, + dft_low, dft_high); + + platform_set_drvdata(pdev, pzone); + pzone->mode = THERMAL_DEVICE_ENABLED; + mutex_unlock(&pzone->th_lock); + + return 0; +} + +static int db8500_thermal_remove(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); + + thermal_zone_device_unregister(pzone->therm_dev); + cancel_work_sync(&pzone->therm_work); + mutex_destroy(&pzone->th_lock); + + return 0; +} + +static int db8500_thermal_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); + + flush_work(&pzone->therm_work); + prcmu_stop_temp_sense(); + + return 0; +} + +static int db8500_thermal_resume(struct platform_device *pdev) +{ + struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); + struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; + unsigned long dft_low, dft_high; + + dft_low = PRCMU_DEFAULT_LOW_TEMP; + dft_high = ptrips->trip_points[0].temp; + + db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, + dft_low, dft_high); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id db8500_thermal_match[] = { + { .compatible = "stericsson,db8500-thermal" }, + {}, +}; +#endif + +static struct platform_driver db8500_thermal_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500-thermal", + .of_match_table = of_match_ptr(db8500_thermal_match), + }, + .probe = db8500_thermal_probe, + .suspend = db8500_thermal_suspend, + .resume = db8500_thermal_resume, + .remove = db8500_thermal_remove, +}; + +module_platform_driver(db8500_thermal_driver); + +MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>"); +MODULE_DESCRIPTION("DB8500 thermal driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/dove_thermal.c b/drivers/thermal/dove_thermal.c new file mode 100644 index 000000000000..7b0bfa0e7a9c --- /dev/null +++ b/drivers/thermal/dove_thermal.c @@ -0,0 +1,209 @@ +/* + * Dove thermal sensor driver + * + * Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> + +#define DOVE_THERMAL_TEMP_OFFSET 1 +#define DOVE_THERMAL_TEMP_MASK 0x1FF + +/* Dove Thermal Manager Control and Status Register */ +#define PMU_TM_DISABLE_OFFS 0 +#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS) + +/* Dove Theraml Diode Control 0 Register */ +#define PMU_TDC0_SW_RST_MASK (0x1 << 1) +#define PMU_TDC0_SEL_VCAL_OFFS 5 +#define PMU_TDC0_SEL_VCAL_MASK (0x3 << PMU_TDC0_SEL_VCAL_OFFS) +#define PMU_TDC0_REF_CAL_CNT_OFFS 11 +#define PMU_TDC0_REF_CAL_CNT_MASK (0x1FF << PMU_TDC0_REF_CAL_CNT_OFFS) +#define PMU_TDC0_AVG_NUM_OFFS 25 +#define PMU_TDC0_AVG_NUM_MASK (0x7 << PMU_TDC0_AVG_NUM_OFFS) + +/* Dove Thermal Diode Control 1 Register */ +#define PMU_TEMP_DIOD_CTRL1_REG 0x04 +#define PMU_TDC1_TEMP_VALID_MASK (0x1 << 10) + +/* Dove Thermal Sensor Dev Structure */ +struct dove_thermal_priv { + void __iomem *sensor; + void __iomem *control; +}; + +static int dove_init_sensor(const struct dove_thermal_priv *priv) +{ + u32 reg; + u32 i; + + /* Configure the Diode Control Register #0 */ + reg = readl_relaxed(priv->control); + + /* Use average of 2 */ + reg &= ~PMU_TDC0_AVG_NUM_MASK; + reg |= (0x1 << PMU_TDC0_AVG_NUM_OFFS); + + /* Reference calibration value */ + reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; + reg |= (0x0F1 << PMU_TDC0_REF_CAL_CNT_OFFS); + + /* Set the high level reference for calibration */ + reg &= ~PMU_TDC0_SEL_VCAL_MASK; + reg |= (0x2 << PMU_TDC0_SEL_VCAL_OFFS); + writel(reg, priv->control); + + /* Reset the sensor */ + reg = readl_relaxed(priv->control); + writel((reg | PMU_TDC0_SW_RST_MASK), priv->control); + writel(reg, priv->control); + + /* Enable the sensor */ + reg = readl_relaxed(priv->sensor); + reg &= ~PMU_TM_DISABLE_MASK; + writel(reg, priv->sensor); + + /* Poll the sensor for the first reading */ + for (i = 0; i < 1000000; i++) { + reg = readl_relaxed(priv->sensor); + if (reg & DOVE_THERMAL_TEMP_MASK) + break; + } + + if (i == 1000000) + return -EIO; + + return 0; +} + +static int dove_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + unsigned long reg; + struct dove_thermal_priv *priv = thermal->devdata; + + /* Valid check */ + reg = readl_relaxed(priv->control + PMU_TEMP_DIOD_CTRL1_REG); + if ((reg & PMU_TDC1_TEMP_VALID_MASK) == 0x0) { + dev_err(&thermal->device, + "Temperature sensor reading not valid\n"); + return -EIO; + } + + /* + * Calculate temperature. See Section 8.10.1 of 88AP510, + * Documentation/arm/Marvell/README + */ + reg = readl_relaxed(priv->sensor); + reg = (reg >> DOVE_THERMAL_TEMP_OFFSET) & DOVE_THERMAL_TEMP_MASK; + *temp = ((2281638UL - (7298*reg)) / 10); + + return 0; +} + +static struct thermal_zone_device_ops ops = { + .get_temp = dove_get_temp, +}; + +static const struct of_device_id dove_thermal_id_table[] = { + { .compatible = "marvell,dove-thermal" }, + {} +}; + +static int dove_thermal_probe(struct platform_device *pdev) +{ + struct thermal_zone_device *thermal = NULL; + struct dove_thermal_priv *priv; + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get platform resource\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->sensor = devm_request_and_ioremap(&pdev->dev, res); + if (!priv->sensor) { + dev_err(&pdev->dev, "Failed to request_ioremap memory\n"); + return -EADDRNOTAVAIL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "Failed to get platform resource\n"); + return -ENODEV; + } + priv->control = devm_request_and_ioremap(&pdev->dev, res); + if (!priv->control) { + dev_err(&pdev->dev, "Failed to request_ioremap memory\n"); + return -EADDRNOTAVAIL; + } + + ret = dove_init_sensor(priv); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize sensor\n"); + return ret; + } + + thermal = thermal_zone_device_register("dove_thermal", 0, 0, + priv, &ops, NULL, 0, 0); + if (IS_ERR(thermal)) { + dev_err(&pdev->dev, + "Failed to register thermal zone device\n"); + return PTR_ERR(thermal); + } + + platform_set_drvdata(pdev, thermal); + + return 0; +} + +static int dove_thermal_exit(struct platform_device *pdev) +{ + struct thermal_zone_device *dove_thermal = + platform_get_drvdata(pdev); + + thermal_zone_device_unregister(dove_thermal); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +MODULE_DEVICE_TABLE(of, dove_thermal_id_table); + +static struct platform_driver dove_thermal_driver = { + .probe = dove_thermal_probe, + .remove = dove_thermal_exit, + .driver = { + .name = "dove_thermal", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(dove_thermal_id_table), + }, +}; + +module_platform_driver(dove_thermal_driver); + +MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>"); +MODULE_DESCRIPTION("Dove thermal driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c index 6dd29e4ce36b..e04ebd8671ac 100644 --- a/drivers/thermal/exynos_thermal.c +++ b/drivers/thermal/exynos_thermal.c @@ -82,7 +82,7 @@ #define EXYNOS_TRIMINFO_RELOAD 0x1 #define EXYNOS_TMU_CLEAR_RISE_INT 0x111 -#define EXYNOS_TMU_CLEAR_FALL_INT (0x111 << 16) +#define EXYNOS_TMU_CLEAR_FALL_INT (0x111 << 12) #define EXYNOS_MUX_ADDR_VALUE 6 #define EXYNOS_MUX_ADDR_SHIFT 20 #define EXYNOS_TMU_TRIP_MODE_SHIFT 13 @@ -94,11 +94,20 @@ #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_EXYNOS_THERMAL_EMUL +#define EXYNOS_EMUL_TIME 0x57F0 +#define EXYNOS_EMUL_TIME_SHIFT 16 +#define EXYNOS_EMUL_DATA_SHIFT 8 +#define EXYNOS_EMUL_DATA_MASK 0xFF +#define EXYNOS_EMUL_ENABLE 0x1 +#endif /* CONFIG_EXYNOS_THERMAL_EMUL */ + /* CPU Zone information */ #define PANIC_ZONE 4 #define WARN_ZONE 3 @@ -125,6 +134,7 @@ struct exynos_tmu_data { struct thermal_trip_point_conf { int trip_val[MAX_TRIP_COUNT]; int trip_count; + u8 trigger_falling; }; struct thermal_cooling_conf { @@ -174,7 +184,8 @@ static int exynos_set_mode(struct thermal_zone_device *thermal, mutex_lock(&th_zone->therm_dev->lock); - if (mode == THERMAL_DEVICE_ENABLED) + 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; @@ -284,7 +295,7 @@ static int exynos_bind(struct thermal_zone_device *thermal, case MONITOR_ZONE: case WARN_ZONE: if (thermal_zone_bind_cooling_device(thermal, i, cdev, - level, level)) { + level, 0)) { pr_err("error binding cdev inst %d\n", i); ret = -EINVAL; } @@ -362,10 +373,17 @@ static int exynos_get_temp(struct thermal_zone_device *thermal, static int exynos_get_trend(struct thermal_zone_device *thermal, int trip, enum thermal_trend *trend) { - if (thermal->temperature >= trip) - *trend = THERMAL_TREND_RAISING; + 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_DROPPING; + *trend = THERMAL_TREND_DROP_FULL; return 0; } @@ -413,7 +431,8 @@ static void exynos_report_trigger(void) break; } - if (th_zone->mode == THERMAL_DEVICE_ENABLED) { + 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 @@ -451,8 +470,9 @@ static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) th_zone->cool_dev_size++; th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name, - EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, 0, - IDLE_INTERVAL); + 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"); @@ -559,8 +579,9 @@ static int exynos_tmu_initialize(struct platform_device *pdev) { struct exynos_tmu_data *data = platform_get_drvdata(pdev); struct exynos_tmu_platform_data *pdata = data->pdata; - unsigned int status, trim_info, rising_threshold; - int ret = 0, threshold_code; + unsigned int status, trim_info; + unsigned int rising_threshold = 0, falling_threshold = 0; + int ret = 0, threshold_code, i, trigger_levs = 0; mutex_lock(&data->lock); clk_enable(data->clk); @@ -585,6 +606,11 @@ static int exynos_tmu_initialize(struct platform_device *pdev) (data->temp_error2 != 0)) data->temp_error1 = pdata->efuse_value; + /* Count trigger levels to be enabled */ + for (i = 0; i < MAX_THRESHOLD_LEVS; i++) + if (pdata->trigger_levels[i]) + trigger_levs++; + if (data->soc == SOC_ARCH_EXYNOS4210) { /* Write temperature code for threshold */ threshold_code = temp_to_code(data, pdata->threshold); @@ -594,44 +620,38 @@ static int exynos_tmu_initialize(struct platform_device *pdev) } writeb(threshold_code, data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP); - - writeb(pdata->trigger_levels[0], - data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0); - writeb(pdata->trigger_levels[1], - data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL1); - writeb(pdata->trigger_levels[2], - data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL2); - writeb(pdata->trigger_levels[3], - data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL3); + for (i = 0; i < trigger_levs; i++) + writeb(pdata->trigger_levels[i], + data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4); writel(EXYNOS4210_TMU_INTCLEAR_VAL, data->base + EXYNOS_TMU_REG_INTCLEAR); } else if (data->soc == SOC_ARCH_EXYNOS) { - /* Write temperature code for threshold */ - threshold_code = temp_to_code(data, pdata->trigger_levels[0]); - if (threshold_code < 0) { - ret = threshold_code; - goto out; - } - rising_threshold = threshold_code; - threshold_code = temp_to_code(data, pdata->trigger_levels[1]); - if (threshold_code < 0) { - ret = threshold_code; - goto out; - } - rising_threshold |= (threshold_code << 8); - threshold_code = temp_to_code(data, pdata->trigger_levels[2]); - if (threshold_code < 0) { - ret = threshold_code; - goto out; + /* Write temperature code for rising and falling threshold */ + for (i = 0; i < trigger_levs; i++) { + threshold_code = temp_to_code(data, + pdata->trigger_levels[i]); + if (threshold_code < 0) { + ret = threshold_code; + goto out; + } + rising_threshold |= threshold_code << 8 * i; + if (pdata->threshold_falling) { + threshold_code = temp_to_code(data, + pdata->trigger_levels[i] - + pdata->threshold_falling); + if (threshold_code > 0) + falling_threshold |= + threshold_code << 8 * i; + } } - rising_threshold |= (threshold_code << 16); writel(rising_threshold, data->base + EXYNOS_THD_TEMP_RISE); - writel(0, data->base + EXYNOS_THD_TEMP_FALL); + writel(falling_threshold, + data->base + EXYNOS_THD_TEMP_FALL); - writel(EXYNOS_TMU_CLEAR_RISE_INT|EXYNOS_TMU_CLEAR_FALL_INT, + writel(EXYNOS_TMU_CLEAR_RISE_INT | EXYNOS_TMU_CLEAR_FALL_INT, data->base + EXYNOS_TMU_REG_INTCLEAR); } out: @@ -664,6 +684,8 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on) pdata->trigger_level2_en << 8 | pdata->trigger_level1_en << 4 | pdata->trigger_level0_en; + if (pdata->threshold_falling) + interrupt_en |= interrupt_en << 16; } else { con |= EXYNOS_TMU_CORE_OFF; interrupt_en = 0; /* Disable all interrupts */ @@ -697,20 +719,19 @@ static void exynos_tmu_work(struct work_struct *work) struct exynos_tmu_data *data = container_of(work, struct exynos_tmu_data, irq_work); + exynos_report_trigger(); mutex_lock(&data->lock); clk_enable(data->clk); - - if (data->soc == SOC_ARCH_EXYNOS) - writel(EXYNOS_TMU_CLEAR_RISE_INT, + writel(EXYNOS_TMU_CLEAR_RISE_INT | + EXYNOS_TMU_CLEAR_FALL_INT, data->base + EXYNOS_TMU_REG_INTCLEAR); else writel(EXYNOS4210_TMU_INTCLEAR_VAL, data->base + EXYNOS_TMU_REG_INTCLEAR); - clk_disable(data->clk); mutex_unlock(&data->lock); - exynos_report_trigger(); + enable_irq(data->irq); } @@ -759,6 +780,7 @@ static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = { #if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412) static struct exynos_tmu_platform_data const exynos_default_tmu_data = { + .threshold_falling = 10, .trigger_levels[0] = 85, .trigger_levels[1] = 103, .trigger_levels[2] = 110, @@ -800,8 +822,6 @@ static const struct of_device_id exynos_tmu_match[] = { {}, }; MODULE_DEVICE_TABLE(of, exynos_tmu_match); -#else -#define exynos_tmu_match NULL #endif static struct platform_device_id exynos_tmu_driver_ids[] = { @@ -832,7 +852,95 @@ static inline struct exynos_tmu_platform_data *exynos_get_driver_data( return (struct exynos_tmu_platform_data *) platform_get_device_id(pdev)->driver_data; } -static int __devinit exynos_tmu_probe(struct platform_device *pdev) + +#ifdef CONFIG_EXYNOS_THERMAL_EMUL +static ssize_t exynos_tmu_emulation_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = container_of(dev, + struct platform_device, dev); + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + unsigned int reg; + u8 temp_code; + int temp = 0; + + if (data->soc == SOC_ARCH_EXYNOS4210) + goto out; + + mutex_lock(&data->lock); + clk_enable(data->clk); + reg = readl(data->base + EXYNOS_EMUL_CON); + clk_disable(data->clk); + mutex_unlock(&data->lock); + + if (reg & EXYNOS_EMUL_ENABLE) { + reg >>= EXYNOS_EMUL_DATA_SHIFT; + temp_code = reg & EXYNOS_EMUL_DATA_MASK; + temp = code_to_temp(data, temp_code); + } +out: + return sprintf(buf, "%d\n", temp * MCELSIUS); +} + +static ssize_t exynos_tmu_emulation_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = container_of(dev, + struct platform_device, dev); + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + unsigned int reg; + int temp; + + if (data->soc == SOC_ARCH_EXYNOS4210) + goto out; + + if (!sscanf(buf, "%d\n", &temp) || temp < 0) + return -EINVAL; + + mutex_lock(&data->lock); + clk_enable(data->clk); + + reg = readl(data->base + EXYNOS_EMUL_CON); + + if (temp) { + /* Both CELSIUS and MCELSIUS type are available for input */ + if (temp > MCELSIUS) + temp /= MCELSIUS; + + reg = (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT) | + (temp_to_code(data, (temp / MCELSIUS)) + << EXYNOS_EMUL_DATA_SHIFT) | EXYNOS_EMUL_ENABLE; + } else { + reg &= ~EXYNOS_EMUL_ENABLE; + } + + writel(reg, data->base + EXYNOS_EMUL_CON); + + clk_disable(data->clk); + mutex_unlock(&data->lock); + +out: + return count; +} + +static DEVICE_ATTR(emulation, 0644, exynos_tmu_emulation_show, + exynos_tmu_emulation_store); +static int create_emulation_sysfs(struct device *dev) +{ + return device_create_file(dev, &dev_attr_emulation); +} +static void remove_emulation_sysfs(struct device *dev) +{ + device_remove_file(dev, &dev_attr_emulation); +} +#else +static inline int create_emulation_sysfs(struct device *dev) { return 0; } +static inline void remove_emulation_sysfs(struct device *dev) {} +#endif + +static int exynos_tmu_probe(struct platform_device *pdev) { struct exynos_tmu_data *data; struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data; @@ -866,11 +974,9 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev) return -ENOENT; } - data->base = devm_request_and_ioremap(&pdev->dev, data->mem); - if (!data->base) { - dev_err(&pdev->dev, "Failed to ioremap memory\n"); - return -ENODEV; - } + data->base = devm_ioremap_resource(&pdev->dev, data->mem); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, IRQF_TRIGGER_RISING, "exynos-tmu", data); @@ -916,6 +1022,8 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev) exynos_sensor_conf.trip_data.trip_val[i] = pdata->threshold + pdata->trigger_levels[i]; + exynos_sensor_conf.trip_data.trigger_falling = pdata->threshold_falling; + exynos_sensor_conf.cooling_data.freq_clip_count = pdata->freq_tab_count; for (i = 0; i < pdata->freq_tab_count; i++) { @@ -930,6 +1038,11 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to register thermal interface\n"); goto err_clk; } + + ret = create_emulation_sysfs(&pdev->dev); + if (ret) + dev_err(&pdev->dev, "Failed to create emulation mode sysfs node\n"); + return 0; err_clk: platform_set_drvdata(pdev, NULL); @@ -937,10 +1050,12 @@ err_clk: return ret; } -static int __devexit exynos_tmu_remove(struct platform_device *pdev) +static int exynos_tmu_remove(struct platform_device *pdev) { struct exynos_tmu_data *data = platform_get_drvdata(pdev); + remove_emulation_sysfs(&pdev->dev); + exynos_tmu_control(pdev, false); exynos_unregister_thermal(); @@ -982,10 +1097,10 @@ static struct platform_driver exynos_tmu_driver = { .name = "exynos-tmu", .owner = THIS_MODULE, .pm = EXYNOS_TMU_PM, - .of_match_table = exynos_tmu_match, + .of_match_table = of_match_ptr(exynos_tmu_match), }, .probe = exynos_tmu_probe, - .remove = __devexit_p(exynos_tmu_remove), + .remove = exynos_tmu_remove, .id_table = exynos_tmu_driver_ids, }; diff --git a/drivers/thermal/fair_share.c b/drivers/thermal/fair_share.c new file mode 100644 index 000000000000..792479f2b64b --- /dev/null +++ b/drivers/thermal/fair_share.c @@ -0,0 +1,133 @@ +/* + * fair_share.c - A simple weight based Thermal governor + * + * Copyright (C) 2012 Intel Corp + * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/thermal.h> + +#include "thermal_core.h" + +/** + * get_trip_level: - obtains the current trip level for a zone + * @tz: thermal zone device + */ +static int get_trip_level(struct thermal_zone_device *tz) +{ + int count = 0; + unsigned long trip_temp; + + if (tz->trips == 0 || !tz->ops->get_trip_temp) + return 0; + + for (count = 0; count < tz->trips; count++) { + tz->ops->get_trip_temp(tz, count, &trip_temp); + if (tz->temperature < trip_temp) + break; + } + return count; +} + +static long get_target_state(struct thermal_zone_device *tz, + struct thermal_cooling_device *cdev, int weight, int level) +{ + unsigned long max_state; + + cdev->ops->get_max_state(cdev, &max_state); + + return (long)(weight * level * max_state) / (100 * tz->trips); +} + +/** + * fair_share_throttle - throttles devices asscciated with the given zone + * @tz - thermal_zone_device + * + * Throttling Logic: This uses three parameters to calculate the new + * throttle state of the cooling devices associated with the given zone. + * + * Parameters used for Throttling: + * P1. max_state: Maximum throttle state exposed by the cooling device. + * P2. weight[i]/100: + * How 'effective' the 'i'th device is, in cooling the given zone. + * P3. cur_trip_level/max_no_of_trips: + * This describes the extent to which the devices should be throttled. + * We do not want to throttle too much when we trip a lower temperature, + * whereas the throttling is at full swing if we trip critical levels. + * (Heavily assumes the trip points are in ascending order) + * new_state of cooling device = P3 * P2 * P1 + */ +static int fair_share_throttle(struct thermal_zone_device *tz, int trip) +{ + const struct thermal_zone_params *tzp; + struct thermal_cooling_device *cdev; + struct thermal_instance *instance; + int i; + int cur_trip_level = get_trip_level(tz); + + if (!tz->tzp || !tz->tzp->tbp) + return -EINVAL; + + tzp = tz->tzp; + + for (i = 0; i < tzp->num_tbps; i++) { + if (!tzp->tbp[i].cdev) + continue; + + cdev = tzp->tbp[i].cdev; + instance = get_thermal_instance(tz, cdev, trip); + if (!instance) + continue; + + instance->target = get_target_state(tz, cdev, + tzp->tbp[i].weight, cur_trip_level); + + instance->cdev->updated = false; + thermal_cdev_update(cdev); + } + return 0; +} + +static struct thermal_governor thermal_gov_fair_share = { + .name = "fair_share", + .throttle = fair_share_throttle, + .owner = THIS_MODULE, +}; + +static int __init thermal_gov_fair_share_init(void) +{ + return thermal_register_governor(&thermal_gov_fair_share); +} + +static void __exit thermal_gov_fair_share_exit(void) +{ + thermal_unregister_governor(&thermal_gov_fair_share); +} + +/* This should load after thermal framework */ +fs_initcall(thermal_gov_fair_share_init); +module_exit(thermal_gov_fair_share_exit); + +MODULE_AUTHOR("Durgadoss R"); +MODULE_DESCRIPTION("A simple weight based thermal throttling governor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/intel_powerclamp.c b/drivers/thermal/intel_powerclamp.c new file mode 100644 index 000000000000..b40b37cd25e0 --- /dev/null +++ b/drivers/thermal/intel_powerclamp.c @@ -0,0 +1,795 @@ +/* + * intel_powerclamp.c - package c-state idle injection + * + * Copyright (c) 2012, Intel Corporation. + * + * Authors: + * Arjan van de Ven <arjan@linux.intel.com> + * Jacob Pan <jacob.jun.pan@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * + * TODO: + * 1. better handle wakeup from external interrupts, currently a fixed + * compensation is added to clamping duration when excessive amount + * of wakeups are observed during idle time. the reason is that in + * case of external interrupts without need for ack, clamping down + * cpu in non-irq context does not reduce irq. for majority of the + * cases, clamping down cpu does help reduce irq as well, we should + * be able to differenciate the two cases and give a quantitative + * solution for the irqs that we can control. perhaps based on + * get_cpu_iowait_time_us() + * + * 2. synchronization with other hw blocks + * + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/cpu.h> +#include <linux/thermal.h> +#include <linux/slab.h> +#include <linux/tick.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/sched/rt.h> + +#include <asm/nmi.h> +#include <asm/msr.h> +#include <asm/mwait.h> +#include <asm/cpu_device_id.h> +#include <asm/idle.h> +#include <asm/hardirq.h> + +#define MAX_TARGET_RATIO (50U) +/* For each undisturbed clamping period (no extra wake ups during idle time), + * we increment the confidence counter for the given target ratio. + * CONFIDENCE_OK defines the level where runtime calibration results are + * valid. + */ +#define CONFIDENCE_OK (3) +/* Default idle injection duration, driver adjust sleep time to meet target + * idle ratio. Similar to frequency modulation. + */ +#define DEFAULT_DURATION_JIFFIES (6) + +static unsigned int target_mwait; +static struct dentry *debug_dir; + +/* user selected target */ +static unsigned int set_target_ratio; +static unsigned int current_ratio; +static bool should_skip; +static bool reduce_irq; +static atomic_t idle_wakeup_counter; +static unsigned int control_cpu; /* The cpu assigned to collect stat and update + * control parameters. default to BSP but BSP + * can be offlined. + */ +static bool clamping; + + +static struct task_struct * __percpu *powerclamp_thread; +static struct thermal_cooling_device *cooling_dev; +static unsigned long *cpu_clamping_mask; /* bit map for tracking per cpu + * clamping thread + */ + +static unsigned int duration; +static unsigned int pkg_cstate_ratio_cur; +static unsigned int window_size; + +static int duration_set(const char *arg, const struct kernel_param *kp) +{ + int ret = 0; + unsigned long new_duration; + + ret = kstrtoul(arg, 10, &new_duration); + if (ret) + goto exit; + if (new_duration > 25 || new_duration < 6) { + pr_err("Out of recommended range %lu, between 6-25ms\n", + new_duration); + ret = -EINVAL; + } + + duration = clamp(new_duration, 6ul, 25ul); + smp_mb(); + +exit: + + return ret; +} + +static struct kernel_param_ops duration_ops = { + .set = duration_set, + .get = param_get_int, +}; + + +module_param_cb(duration, &duration_ops, &duration, 0644); +MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec."); + +struct powerclamp_calibration_data { + unsigned long confidence; /* used for calibration, basically a counter + * gets incremented each time a clamping + * period is completed without extra wakeups + * once that counter is reached given level, + * compensation is deemed usable. + */ + unsigned long steady_comp; /* steady state compensation used when + * no extra wakeups occurred. + */ + unsigned long dynamic_comp; /* compensate excessive wakeup from idle + * mostly from external interrupts. + */ +}; + +static struct powerclamp_calibration_data cal_data[MAX_TARGET_RATIO]; + +static int window_size_set(const char *arg, const struct kernel_param *kp) +{ + int ret = 0; + unsigned long new_window_size; + + ret = kstrtoul(arg, 10, &new_window_size); + if (ret) + goto exit_win; + if (new_window_size > 10 || new_window_size < 2) { + pr_err("Out of recommended window size %lu, between 2-10\n", + new_window_size); + ret = -EINVAL; + } + + window_size = clamp(new_window_size, 2ul, 10ul); + smp_mb(); + +exit_win: + + return ret; +} + +static struct kernel_param_ops window_size_ops = { + .set = window_size_set, + .get = param_get_int, +}; + +module_param_cb(window_size, &window_size_ops, &window_size, 0644); +MODULE_PARM_DESC(window_size, "sliding window in number of clamping cycles\n" + "\tpowerclamp controls idle ratio within this window. larger\n" + "\twindow size results in slower response time but more smooth\n" + "\tclamping results. default to 2."); + +static void find_target_mwait(void) +{ + unsigned int eax, ebx, ecx, edx; + unsigned int highest_cstate = 0; + unsigned int highest_subcstate = 0; + int i; + + if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF) + return; + + cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &edx); + + if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) || + !(ecx & CPUID5_ECX_INTERRUPT_BREAK)) + return; + + edx >>= MWAIT_SUBSTATE_SIZE; + for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) { + if (edx & MWAIT_SUBSTATE_MASK) { + highest_cstate = i; + highest_subcstate = edx & MWAIT_SUBSTATE_MASK; + } + } + target_mwait = (highest_cstate << MWAIT_SUBSTATE_SIZE) | + (highest_subcstate - 1); + +} + +static u64 pkg_state_counter(void) +{ + u64 val; + u64 count = 0; + + static bool skip_c2; + static bool skip_c3; + static bool skip_c6; + static bool skip_c7; + + if (!skip_c2) { + if (!rdmsrl_safe(MSR_PKG_C2_RESIDENCY, &val)) + count += val; + else + skip_c2 = true; + } + + if (!skip_c3) { + if (!rdmsrl_safe(MSR_PKG_C3_RESIDENCY, &val)) + count += val; + else + skip_c3 = true; + } + + if (!skip_c6) { + if (!rdmsrl_safe(MSR_PKG_C6_RESIDENCY, &val)) + count += val; + else + skip_c6 = true; + } + + if (!skip_c7) { + if (!rdmsrl_safe(MSR_PKG_C7_RESIDENCY, &val)) + count += val; + else + skip_c7 = true; + } + + return count; +} + +static void noop_timer(unsigned long foo) +{ + /* empty... just the fact that we get the interrupt wakes us up */ +} + +static unsigned int get_compensation(int ratio) +{ + unsigned int comp = 0; + + /* we only use compensation if all adjacent ones are good */ + if (ratio == 1 && + cal_data[ratio].confidence >= CONFIDENCE_OK && + cal_data[ratio + 1].confidence >= CONFIDENCE_OK && + cal_data[ratio + 2].confidence >= CONFIDENCE_OK) { + comp = (cal_data[ratio].steady_comp + + cal_data[ratio + 1].steady_comp + + cal_data[ratio + 2].steady_comp) / 3; + } else if (ratio == MAX_TARGET_RATIO - 1 && + cal_data[ratio].confidence >= CONFIDENCE_OK && + cal_data[ratio - 1].confidence >= CONFIDENCE_OK && + cal_data[ratio - 2].confidence >= CONFIDENCE_OK) { + comp = (cal_data[ratio].steady_comp + + cal_data[ratio - 1].steady_comp + + cal_data[ratio - 2].steady_comp) / 3; + } else if (cal_data[ratio].confidence >= CONFIDENCE_OK && + cal_data[ratio - 1].confidence >= CONFIDENCE_OK && + cal_data[ratio + 1].confidence >= CONFIDENCE_OK) { + comp = (cal_data[ratio].steady_comp + + cal_data[ratio - 1].steady_comp + + cal_data[ratio + 1].steady_comp) / 3; + } + + /* REVISIT: simple penalty of double idle injection */ + if (reduce_irq) + comp = ratio; + /* do not exceed limit */ + if (comp + ratio >= MAX_TARGET_RATIO) + comp = MAX_TARGET_RATIO - ratio - 1; + + return comp; +} + +static void adjust_compensation(int target_ratio, unsigned int win) +{ + int delta; + struct powerclamp_calibration_data *d = &cal_data[target_ratio]; + + /* + * adjust compensations if confidence level has not been reached or + * there are too many wakeups during the last idle injection period, we + * cannot trust the data for compensation. + */ + if (d->confidence >= CONFIDENCE_OK || + atomic_read(&idle_wakeup_counter) > + win * num_online_cpus()) + return; + + delta = set_target_ratio - current_ratio; + /* filter out bad data */ + if (delta >= 0 && delta <= (1+target_ratio/10)) { + if (d->steady_comp) + d->steady_comp = + roundup(delta+d->steady_comp, 2)/2; + else + d->steady_comp = delta; + d->confidence++; + } +} + +static bool powerclamp_adjust_controls(unsigned int target_ratio, + unsigned int guard, unsigned int win) +{ + static u64 msr_last, tsc_last; + u64 msr_now, tsc_now; + u64 val64; + + /* check result for the last window */ + msr_now = pkg_state_counter(); + rdtscll(tsc_now); + + /* calculate pkg cstate vs tsc ratio */ + if (!msr_last || !tsc_last) + current_ratio = 1; + else if (tsc_now-tsc_last) { + val64 = 100*(msr_now-msr_last); + do_div(val64, (tsc_now-tsc_last)); + current_ratio = val64; + } + + /* update record */ + msr_last = msr_now; + tsc_last = tsc_now; + + adjust_compensation(target_ratio, win); + /* + * too many external interrupts, set flag such + * that we can take measure later. + */ + reduce_irq = atomic_read(&idle_wakeup_counter) >= + 2 * win * num_online_cpus(); + + atomic_set(&idle_wakeup_counter, 0); + /* if we are above target+guard, skip */ + return set_target_ratio + guard <= current_ratio; +} + +static int clamp_thread(void *arg) +{ + int cpunr = (unsigned long)arg; + DEFINE_TIMER(wakeup_timer, noop_timer, 0, 0); + static const struct sched_param param = { + .sched_priority = MAX_USER_RT_PRIO/2, + }; + unsigned int count = 0; + unsigned int target_ratio; + + set_bit(cpunr, cpu_clamping_mask); + set_freezable(); + init_timer_on_stack(&wakeup_timer); + sched_setscheduler(current, SCHED_FIFO, ¶m); + + while (true == clamping && !kthread_should_stop() && + cpu_online(cpunr)) { + int sleeptime; + unsigned long target_jiffies; + unsigned int guard; + unsigned int compensation = 0; + int interval; /* jiffies to sleep for each attempt */ + unsigned int duration_jiffies = msecs_to_jiffies(duration); + unsigned int window_size_now; + + try_to_freeze(); + /* + * make sure user selected ratio does not take effect until + * the next round. adjust target_ratio if user has changed + * target such that we can converge quickly. + */ + target_ratio = set_target_ratio; + guard = 1 + target_ratio/20; + window_size_now = window_size; + count++; + + /* + * systems may have different ability to enter package level + * c-states, thus we need to compensate the injected idle ratio + * to achieve the actual target reported by the HW. + */ + compensation = get_compensation(target_ratio); + interval = duration_jiffies*100/(target_ratio+compensation); + + /* align idle time */ + target_jiffies = roundup(jiffies, interval); + sleeptime = target_jiffies - jiffies; + if (sleeptime <= 0) + sleeptime = 1; + schedule_timeout_interruptible(sleeptime); + /* + * only elected controlling cpu can collect stats and update + * control parameters. + */ + if (cpunr == control_cpu && !(count%window_size_now)) { + should_skip = + powerclamp_adjust_controls(target_ratio, + guard, window_size_now); + smp_mb(); + } + + if (should_skip) + continue; + + target_jiffies = jiffies + duration_jiffies; + mod_timer(&wakeup_timer, target_jiffies); + if (unlikely(local_softirq_pending())) + continue; + /* + * stop tick sched during idle time, interrupts are still + * allowed. thus jiffies are updated properly. + */ + preempt_disable(); + tick_nohz_idle_enter(); + /* mwait until target jiffies is reached */ + while (time_before(jiffies, target_jiffies)) { + unsigned long ecx = 1; + unsigned long eax = target_mwait; + + /* + * REVISIT: may call enter_idle() to notify drivers who + * can save power during cpu idle. same for exit_idle() + */ + local_touch_nmi(); + stop_critical_timings(); + __monitor((void *)¤t_thread_info()->flags, 0, 0); + cpu_relax(); /* allow HT sibling to run */ + __mwait(eax, ecx); + start_critical_timings(); + atomic_inc(&idle_wakeup_counter); + } + tick_nohz_idle_exit(); + preempt_enable_no_resched(); + } + del_timer_sync(&wakeup_timer); + clear_bit(cpunr, cpu_clamping_mask); + + return 0; +} + +/* + * 1 HZ polling while clamping is active, useful for userspace + * to monitor actual idle ratio. + */ +static void poll_pkg_cstate(struct work_struct *dummy); +static DECLARE_DELAYED_WORK(poll_pkg_cstate_work, poll_pkg_cstate); +static void poll_pkg_cstate(struct work_struct *dummy) +{ + static u64 msr_last; + static u64 tsc_last; + static unsigned long jiffies_last; + + u64 msr_now; + unsigned long jiffies_now; + u64 tsc_now; + u64 val64; + + msr_now = pkg_state_counter(); + rdtscll(tsc_now); + jiffies_now = jiffies; + + /* calculate pkg cstate vs tsc ratio */ + if (!msr_last || !tsc_last) + pkg_cstate_ratio_cur = 1; + else { + if (tsc_now - tsc_last) { + val64 = 100 * (msr_now - msr_last); + do_div(val64, (tsc_now - tsc_last)); + pkg_cstate_ratio_cur = val64; + } + } + + /* update record */ + msr_last = msr_now; + jiffies_last = jiffies_now; + tsc_last = tsc_now; + + if (true == clamping) + schedule_delayed_work(&poll_pkg_cstate_work, HZ); +} + +static int start_power_clamp(void) +{ + unsigned long cpu; + struct task_struct *thread; + + /* check if pkg cstate counter is completely 0, abort in this case */ + if (!pkg_state_counter()) { + pr_err("pkg cstate counter not functional, abort\n"); + return -EINVAL; + } + + set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1); + /* prevent cpu hotplug */ + get_online_cpus(); + + /* prefer BSP */ + control_cpu = 0; + if (!cpu_online(control_cpu)) + control_cpu = smp_processor_id(); + + clamping = true; + schedule_delayed_work(&poll_pkg_cstate_work, 0); + + /* start one thread per online cpu */ + for_each_online_cpu(cpu) { + struct task_struct **p = + per_cpu_ptr(powerclamp_thread, cpu); + + thread = kthread_create_on_node(clamp_thread, + (void *) cpu, + cpu_to_node(cpu), + "kidle_inject/%ld", cpu); + /* bind to cpu here */ + if (likely(!IS_ERR(thread))) { + kthread_bind(thread, cpu); + wake_up_process(thread); + *p = thread; + } + + } + put_online_cpus(); + + return 0; +} + +static void end_power_clamp(void) +{ + int i; + struct task_struct *thread; + + clamping = false; + /* + * make clamping visible to other cpus and give per cpu clamping threads + * sometime to exit, or gets killed later. + */ + smp_mb(); + msleep(20); + if (bitmap_weight(cpu_clamping_mask, num_possible_cpus())) { + for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) { + pr_debug("clamping thread for cpu %d alive, kill\n", i); + thread = *per_cpu_ptr(powerclamp_thread, i); + kthread_stop(thread); + } + } +} + +static int powerclamp_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + unsigned long cpu = (unsigned long)hcpu; + struct task_struct *thread; + struct task_struct **percpu_thread = + per_cpu_ptr(powerclamp_thread, cpu); + + if (false == clamping) + goto exit_ok; + + switch (action) { + case CPU_ONLINE: + thread = kthread_create_on_node(clamp_thread, + (void *) cpu, + cpu_to_node(cpu), + "kidle_inject/%lu", cpu); + if (likely(!IS_ERR(thread))) { + kthread_bind(thread, cpu); + wake_up_process(thread); + *percpu_thread = thread; + } + /* prefer BSP as controlling CPU */ + if (cpu == 0) { + control_cpu = 0; + smp_mb(); + } + break; + case CPU_DEAD: + if (test_bit(cpu, cpu_clamping_mask)) { + pr_err("cpu %lu dead but powerclamping thread is not\n", + cpu); + kthread_stop(*percpu_thread); + } + if (cpu == control_cpu) { + control_cpu = smp_processor_id(); + smp_mb(); + } + } + +exit_ok: + return NOTIFY_OK; +} + +static struct notifier_block powerclamp_cpu_notifier = { + .notifier_call = powerclamp_cpu_callback, +}; + +static int powerclamp_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = MAX_TARGET_RATIO; + + return 0; +} + +static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + if (true == clamping) + *state = pkg_cstate_ratio_cur; + else + /* to save power, do not poll idle ratio while not clamping */ + *state = -1; /* indicates invalid state */ + + return 0; +} + +static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long new_target_ratio) +{ + int ret = 0; + + new_target_ratio = clamp(new_target_ratio, 0UL, + (unsigned long) (MAX_TARGET_RATIO-1)); + if (set_target_ratio == 0 && new_target_ratio > 0) { + pr_info("Start idle injection to reduce power\n"); + set_target_ratio = new_target_ratio; + ret = start_power_clamp(); + goto exit_set; + } else if (set_target_ratio > 0 && new_target_ratio == 0) { + pr_info("Stop forced idle injection\n"); + set_target_ratio = 0; + end_power_clamp(); + } else /* adjust currently running */ { + set_target_ratio = new_target_ratio; + /* make new set_target_ratio visible to other cpus */ + smp_mb(); + } + +exit_set: + return ret; +} + +/* bind to generic thermal layer as cooling device*/ +static struct thermal_cooling_device_ops powerclamp_cooling_ops = { + .get_max_state = powerclamp_get_max_state, + .get_cur_state = powerclamp_get_cur_state, + .set_cur_state = powerclamp_set_cur_state, +}; + +/* runs on Nehalem and later */ +static const struct x86_cpu_id intel_powerclamp_ids[] = { + { X86_VENDOR_INTEL, 6, 0x1a}, + { X86_VENDOR_INTEL, 6, 0x1c}, + { X86_VENDOR_INTEL, 6, 0x1e}, + { X86_VENDOR_INTEL, 6, 0x1f}, + { X86_VENDOR_INTEL, 6, 0x25}, + { X86_VENDOR_INTEL, 6, 0x26}, + { X86_VENDOR_INTEL, 6, 0x2a}, + { X86_VENDOR_INTEL, 6, 0x2c}, + { X86_VENDOR_INTEL, 6, 0x2d}, + { X86_VENDOR_INTEL, 6, 0x2e}, + { X86_VENDOR_INTEL, 6, 0x2f}, + { X86_VENDOR_INTEL, 6, 0x3a}, + {} +}; +MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids); + +static int powerclamp_probe(void) +{ + if (!x86_match_cpu(intel_powerclamp_ids)) { + pr_err("Intel powerclamp does not run on family %d model %d\n", + boot_cpu_data.x86, boot_cpu_data.x86_model); + return -ENODEV; + } + if (!boot_cpu_has(X86_FEATURE_NONSTOP_TSC) || + !boot_cpu_has(X86_FEATURE_CONSTANT_TSC) || + !boot_cpu_has(X86_FEATURE_MWAIT) || + !boot_cpu_has(X86_FEATURE_ARAT)) + return -ENODEV; + + /* find the deepest mwait value */ + find_target_mwait(); + + return 0; +} + +static int powerclamp_debug_show(struct seq_file *m, void *unused) +{ + int i = 0; + + seq_printf(m, "controlling cpu: %d\n", control_cpu); + seq_printf(m, "pct confidence steady dynamic (compensation)\n"); + for (i = 0; i < MAX_TARGET_RATIO; i++) { + seq_printf(m, "%d\t%lu\t%lu\t%lu\n", + i, + cal_data[i].confidence, + cal_data[i].steady_comp, + cal_data[i].dynamic_comp); + } + + return 0; +} + +static int powerclamp_debug_open(struct inode *inode, + struct file *file) +{ + return single_open(file, powerclamp_debug_show, inode->i_private); +} + +static const struct file_operations powerclamp_debug_fops = { + .open = powerclamp_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static inline void powerclamp_create_debug_files(void) +{ + debug_dir = debugfs_create_dir("intel_powerclamp", NULL); + if (!debug_dir) + return; + + if (!debugfs_create_file("powerclamp_calib", S_IRUGO, debug_dir, + cal_data, &powerclamp_debug_fops)) + goto file_error; + + return; + +file_error: + debugfs_remove_recursive(debug_dir); +} + +static int powerclamp_init(void) +{ + int retval; + int bitmap_size; + + bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long); + cpu_clamping_mask = kzalloc(bitmap_size, GFP_KERNEL); + if (!cpu_clamping_mask) + return -ENOMEM; + + /* probe cpu features and ids here */ + retval = powerclamp_probe(); + if (retval) + return retval; + /* set default limit, maybe adjusted during runtime based on feedback */ + window_size = 2; + register_hotcpu_notifier(&powerclamp_cpu_notifier); + powerclamp_thread = alloc_percpu(struct task_struct *); + cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL, + &powerclamp_cooling_ops); + if (IS_ERR(cooling_dev)) + return -ENODEV; + + if (!duration) + duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES); + powerclamp_create_debug_files(); + + return 0; +} +module_init(powerclamp_init); + +static void powerclamp_exit(void) +{ + unregister_hotcpu_notifier(&powerclamp_cpu_notifier); + end_power_clamp(); + free_percpu(powerclamp_thread); + thermal_cooling_device_unregister(cooling_dev); + kfree(cpu_clamping_mask); + + cancel_delayed_work_sync(&poll_pkg_cstate_work); + debugfs_remove_recursive(debug_dir); +} +module_exit(powerclamp_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>"); +MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>"); +MODULE_DESCRIPTION("Package Level C-state Idle Injection for Intel CPUs"); diff --git a/drivers/thermal/kirkwood_thermal.c b/drivers/thermal/kirkwood_thermal.c new file mode 100644 index 000000000000..65cb4f09e8f6 --- /dev/null +++ b/drivers/thermal/kirkwood_thermal.c @@ -0,0 +1,134 @@ +/* + * Kirkwood thermal sensor driver + * + * Copyright (C) 2012 Nobuhiro Iwamatsu <iwamatsu@nigauri.org> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> + +#define KIRKWOOD_THERMAL_VALID_OFFSET 9 +#define KIRKWOOD_THERMAL_VALID_MASK 0x1 +#define KIRKWOOD_THERMAL_TEMP_OFFSET 10 +#define KIRKWOOD_THERMAL_TEMP_MASK 0x1FF + +/* Kirkwood Thermal Sensor Dev Structure */ +struct kirkwood_thermal_priv { + void __iomem *sensor; +}; + +static int kirkwood_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + unsigned long reg; + struct kirkwood_thermal_priv *priv = thermal->devdata; + + reg = readl_relaxed(priv->sensor); + + /* Valid check */ + if (!(reg >> KIRKWOOD_THERMAL_VALID_OFFSET) & + KIRKWOOD_THERMAL_VALID_MASK) { + dev_err(&thermal->device, + "Temperature sensor reading not valid\n"); + return -EIO; + } + + /* + * Calculate temperature. See Section 8.10.1 of the 88AP510, + * datasheet, which has the same sensor. + * Documentation/arm/Marvell/README + */ + reg = (reg >> KIRKWOOD_THERMAL_TEMP_OFFSET) & + KIRKWOOD_THERMAL_TEMP_MASK; + *temp = ((2281638UL - (7298*reg)) / 10); + + return 0; +} + +static struct thermal_zone_device_ops ops = { + .get_temp = kirkwood_get_temp, +}; + +static const struct of_device_id kirkwood_thermal_id_table[] = { + { .compatible = "marvell,kirkwood-thermal" }, + {} +}; + +static int kirkwood_thermal_probe(struct platform_device *pdev) +{ + struct thermal_zone_device *thermal = NULL; + struct kirkwood_thermal_priv *priv; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get platform resource\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->sensor = devm_request_and_ioremap(&pdev->dev, res); + if (!priv->sensor) { + dev_err(&pdev->dev, "Failed to request_ioremap memory\n"); + return -EADDRNOTAVAIL; + } + + thermal = thermal_zone_device_register("kirkwood_thermal", 0, 0, + priv, &ops, NULL, 0, 0); + if (IS_ERR(thermal)) { + dev_err(&pdev->dev, + "Failed to register thermal zone device\n"); + return PTR_ERR(thermal); + } + + platform_set_drvdata(pdev, thermal); + + return 0; +} + +static int kirkwood_thermal_exit(struct platform_device *pdev) +{ + struct thermal_zone_device *kirkwood_thermal = + platform_get_drvdata(pdev); + + thermal_zone_device_unregister(kirkwood_thermal); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +MODULE_DEVICE_TABLE(of, kirkwood_thermal_id_table); + +static struct platform_driver kirkwood_thermal_driver = { + .probe = kirkwood_thermal_probe, + .remove = kirkwood_thermal_exit, + .driver = { + .name = "kirkwood_thermal", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(kirkwood_thermal_id_table), + }, +}; + +module_platform_driver(kirkwood_thermal_driver); + +MODULE_AUTHOR("Nobuhiro Iwamatsu <iwamatsu@nigauri.org>"); +MODULE_DESCRIPTION("kirkwood thermal driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c index f7a1b574a304..28f091994013 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -19,236 +19,473 @@ */ #include <linux/delay.h> #include <linux/err.h> +#include <linux/irq.h> +#include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/reboot.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/thermal.h> -#define THSCR 0x2c -#define THSSR 0x30 +#define IDLE_INTERVAL 5000 + +#define COMMON_STR 0x00 +#define COMMON_ENR 0x04 +#define COMMON_INTMSK 0x0c + +#define REG_POSNEG 0x20 +#define REG_FILONOFF 0x28 +#define REG_THSCR 0x2c +#define REG_THSSR 0x30 +#define REG_INTCTRL 0x34 /* THSCR */ -#define CPTAP 0xf +#define CPCTL (1 << 12) /* THSSR */ #define CTEMP 0x3f - -struct rcar_thermal_priv { +struct rcar_thermal_common { void __iomem *base; struct device *dev; + struct list_head head; spinlock_t lock; - u32 comp; }; +struct rcar_thermal_priv { + void __iomem *base; + struct rcar_thermal_common *common; + struct thermal_zone_device *zone; + struct delayed_work work; + struct mutex lock; + struct list_head list; + int id; + int ctemp; +}; + +#define rcar_thermal_for_each_priv(pos, common) \ + list_for_each_entry(pos, &common->head, list) + +#define MCELSIUS(temp) ((temp) * 1000) +#define rcar_zone_to_priv(zone) ((zone)->devdata) +#define rcar_priv_to_dev(priv) ((priv)->common->dev) +#define rcar_has_irq_support(priv) ((priv)->common->base) +#define rcar_id_to_shift(priv) ((priv)->id * 8) + +#ifdef DEBUG +# define rcar_force_update_temp(priv) 1 +#else +# define rcar_force_update_temp(priv) 0 +#endif + /* * basic functions */ -static u32 rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg) +#define rcar_thermal_common_read(c, r) \ + _rcar_thermal_common_read(c, COMMON_ ##r) +static u32 _rcar_thermal_common_read(struct rcar_thermal_common *common, + u32 reg) { - unsigned long flags; - u32 ret; - - spin_lock_irqsave(&priv->lock, flags); + return ioread32(common->base + reg); +} - ret = ioread32(priv->base + reg); +#define rcar_thermal_common_write(c, r, d) \ + _rcar_thermal_common_write(c, COMMON_ ##r, d) +static void _rcar_thermal_common_write(struct rcar_thermal_common *common, + u32 reg, u32 data) +{ + iowrite32(data, common->base + reg); +} - spin_unlock_irqrestore(&priv->lock, flags); +#define rcar_thermal_common_bset(c, r, m, d) \ + _rcar_thermal_common_bset(c, COMMON_ ##r, m, d) +static void _rcar_thermal_common_bset(struct rcar_thermal_common *common, + u32 reg, u32 mask, u32 data) +{ + u32 val; - return ret; + val = ioread32(common->base + reg); + val &= ~mask; + val |= (data & mask); + iowrite32(val, common->base + reg); } -#if 0 /* no user at this point */ -static void rcar_thermal_write(struct rcar_thermal_priv *priv, - u32 reg, u32 data) +#define rcar_thermal_read(p, r) _rcar_thermal_read(p, REG_ ##r) +static u32 _rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg) { - unsigned long flags; - - spin_lock_irqsave(&priv->lock, flags); + return ioread32(priv->base + reg); +} +#define rcar_thermal_write(p, r, d) _rcar_thermal_write(p, REG_ ##r, d) +static void _rcar_thermal_write(struct rcar_thermal_priv *priv, + u32 reg, u32 data) +{ iowrite32(data, priv->base + reg); - - spin_unlock_irqrestore(&priv->lock, flags); } -#endif -static void rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg, - u32 mask, u32 data) +#define rcar_thermal_bset(p, r, m, d) _rcar_thermal_bset(p, REG_ ##r, m, d) +static void _rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg, + u32 mask, u32 data) { - unsigned long flags; u32 val; - spin_lock_irqsave(&priv->lock, flags); - val = ioread32(priv->base + reg); val &= ~mask; val |= (data & mask); iowrite32(val, priv->base + reg); - - spin_unlock_irqrestore(&priv->lock, flags); } /* * zone device functions */ -static int rcar_thermal_get_temp(struct thermal_zone_device *zone, - unsigned long *temp) +static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv) { - struct rcar_thermal_priv *priv = zone->devdata; - int val, min, max, tmp; - - tmp = -200; /* default */ - while (1) { - if (priv->comp < 1 || priv->comp > 12) { - dev_err(priv->dev, - "THSSR invalid data (%d)\n", priv->comp); - priv->comp = 4; /* for next thermal */ - return -EINVAL; - } + struct device *dev = rcar_priv_to_dev(priv); + int i; + int ctemp, old, new; - /* - * THS comparator offset and the reference temperature - * - * Comparator | reference | Temperature field - * offset | temperature | measurement - * | (degrees C) | (degrees C) - * -------------+---------------+------------------- - * 1 | -45 | -45 to -30 - * 2 | -30 | -30 to -15 - * 3 | -15 | -15 to 0 - * 4 | 0 | 0 to +15 - * 5 | +15 | +15 to +30 - * 6 | +30 | +30 to +45 - * 7 | +45 | +45 to +60 - * 8 | +60 | +60 to +75 - * 9 | +75 | +75 to +90 - * 10 | +90 | +90 to +105 - * 11 | +105 | +105 to +120 - * 12 | +120 | +120 to +135 - */ + mutex_lock(&priv->lock); - /* calculate thermal limitation */ - min = (priv->comp * 15) - 60; - max = min + 15; + /* + * TSC decides a value of CPTAP automatically, + * and this is the conditions which validate interrupt. + */ + rcar_thermal_bset(priv, THSCR, CPCTL, CPCTL); + ctemp = 0; + old = ~0; + for (i = 0; i < 128; i++) { /* * we need to wait 300us after changing comparator offset * to get stable temperature. * see "Usage Notes" on datasheet */ - rcar_thermal_bset(priv, THSCR, CPTAP, priv->comp); udelay(300); - /* calculate current temperature */ - val = rcar_thermal_read(priv, THSSR) & CTEMP; - val = (val * 5) - 65; + new = rcar_thermal_read(priv, THSSR) & CTEMP; + if (new == old) { + ctemp = new; + break; + } + old = new; + } - dev_dbg(priv->dev, "comp/min/max/val = %d/%d/%d/%d\n", - priv->comp, min, max, val); + if (!ctemp) { + dev_err(dev, "thermal sensor was broken\n"); + return -EINVAL; + } - /* - * If val is same as min/max, then, - * it should try again on next comparator. - * But the val might be correct temperature. - * Keep it on "tmp" and compare with next val. - */ - if (tmp == val) - break; + /* + * enable IRQ + */ + if (rcar_has_irq_support(priv)) { + rcar_thermal_write(priv, FILONOFF, 0); - if (val <= min) { - tmp = min; - priv->comp--; /* try again */ - } else if (val >= max) { - tmp = max; - priv->comp++; /* try again */ - } else { - tmp = val; - break; - } + /* enable Rising/Falling edge interrupt */ + rcar_thermal_write(priv, POSNEG, 0x1); + rcar_thermal_write(priv, INTCTRL, (((ctemp - 0) << 8) | + ((ctemp - 1) << 0))); + } + + dev_dbg(dev, "thermal%d %d -> %d\n", priv->id, priv->ctemp, ctemp); + + priv->ctemp = ctemp; + + mutex_unlock(&priv->lock); + + return 0; +} + +static int rcar_thermal_get_temp(struct thermal_zone_device *zone, + unsigned long *temp) +{ + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); + + if (!rcar_has_irq_support(priv) || rcar_force_update_temp(priv)) + rcar_thermal_update_temp(priv); + + mutex_lock(&priv->lock); + *temp = MCELSIUS((priv->ctemp * 5) - 65); + mutex_unlock(&priv->lock); + + return 0; +} + +static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone, + int trip, enum thermal_trip_type *type) +{ + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); + struct device *dev = rcar_priv_to_dev(priv); + + /* see rcar_thermal_get_temp() */ + switch (trip) { + case 0: /* +90 <= temp */ + *type = THERMAL_TRIP_CRITICAL; + break; + default: + dev_err(dev, "rcar driver trip error\n"); + return -EINVAL; + } + + return 0; +} + +static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone, + int trip, unsigned long *temp) +{ + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); + struct device *dev = rcar_priv_to_dev(priv); + + /* see rcar_thermal_get_temp() */ + switch (trip) { + case 0: /* +90 <= temp */ + *temp = MCELSIUS(90); + break; + default: + dev_err(dev, "rcar driver trip error\n"); + return -EINVAL; + } + + return 0; +} + +static int rcar_thermal_notify(struct thermal_zone_device *zone, + int trip, enum thermal_trip_type type) +{ + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); + struct device *dev = rcar_priv_to_dev(priv); + + switch (type) { + case THERMAL_TRIP_CRITICAL: + /* FIXME */ + dev_warn(dev, "Thermal reached to critical temperature\n"); + break; + default: + break; } - *temp = tmp; return 0; } static struct thermal_zone_device_ops rcar_thermal_zone_ops = { - .get_temp = rcar_thermal_get_temp, + .get_temp = rcar_thermal_get_temp, + .get_trip_type = rcar_thermal_get_trip_type, + .get_trip_temp = rcar_thermal_get_trip_temp, + .notify = rcar_thermal_notify, }; /* - * platform functions + * interrupt */ -static int rcar_thermal_probe(struct platform_device *pdev) +#define rcar_thermal_irq_enable(p) _rcar_thermal_irq_ctrl(p, 1) +#define rcar_thermal_irq_disable(p) _rcar_thermal_irq_ctrl(p, 0) +static void _rcar_thermal_irq_ctrl(struct rcar_thermal_priv *priv, int enable) { - struct thermal_zone_device *zone; + struct rcar_thermal_common *common = priv->common; + unsigned long flags; + u32 mask = 0x3 << rcar_id_to_shift(priv); /* enable Rising/Falling */ + + spin_lock_irqsave(&common->lock, flags); + + rcar_thermal_common_bset(common, INTMSK, mask, enable ? 0 : mask); + + spin_unlock_irqrestore(&common->lock, flags); +} + +static void rcar_thermal_work(struct work_struct *work) +{ + struct rcar_thermal_priv *priv; + + priv = container_of(work, struct rcar_thermal_priv, work.work); + + rcar_thermal_update_temp(priv); + rcar_thermal_irq_enable(priv); + thermal_zone_device_update(priv->zone); +} + +static u32 rcar_thermal_had_changed(struct rcar_thermal_priv *priv, u32 status) +{ + struct device *dev = rcar_priv_to_dev(priv); + + status = (status >> rcar_id_to_shift(priv)) & 0x3; + + if (status & 0x3) { + dev_dbg(dev, "thermal%d %s%s\n", + priv->id, + (status & 0x2) ? "Rising " : "", + (status & 0x1) ? "Falling" : ""); + } + + return status; +} + +static irqreturn_t rcar_thermal_irq(int irq, void *data) +{ + struct rcar_thermal_common *common = data; struct rcar_thermal_priv *priv; - struct resource *res; - int ret; + unsigned long flags; + u32 status, mask; + + spin_lock_irqsave(&common->lock, flags); + + mask = rcar_thermal_common_read(common, INTMSK); + status = rcar_thermal_common_read(common, STR); + rcar_thermal_common_write(common, STR, 0x000F0F0F & mask); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "Could not get platform resource\n"); - return -ENODEV; + spin_unlock_irqrestore(&common->lock, flags); + + status = status & ~mask; + + /* + * check the status + */ + rcar_thermal_for_each_priv(priv, common) { + if (rcar_thermal_had_changed(priv, status)) { + rcar_thermal_irq_disable(priv); + schedule_delayed_work(&priv->work, + msecs_to_jiffies(300)); + } } - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) { - dev_err(&pdev->dev, "Could not allocate priv\n"); + return IRQ_HANDLED; +} + +/* + * platform functions + */ +static int rcar_thermal_probe(struct platform_device *pdev) +{ + struct rcar_thermal_common *common; + struct rcar_thermal_priv *priv; + struct device *dev = &pdev->dev; + struct resource *res, *irq; + int mres = 0; + int i; + int idle = IDLE_INTERVAL; + + common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL); + if (!common) { + dev_err(dev, "Could not allocate common\n"); return -ENOMEM; } - priv->comp = 4; /* basic setup */ - priv->dev = &pdev->dev; - spin_lock_init(&priv->lock); - priv->base = devm_ioremap_nocache(&pdev->dev, - res->start, resource_size(res)); - if (!priv->base) { - dev_err(&pdev->dev, "Unable to ioremap thermal register\n"); - ret = -ENOMEM; - goto error_free_priv; + INIT_LIST_HEAD(&common->head); + spin_lock_init(&common->lock); + common->dev = dev; + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (irq) { + int ret; + + /* + * platform has IRQ support. + * Then, drier use common register + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, mres++); + if (!res) { + dev_err(dev, "Could not get platform resource\n"); + return -ENODEV; + } + + ret = devm_request_irq(dev, irq->start, rcar_thermal_irq, 0, + dev_name(dev), common); + if (ret) { + dev_err(dev, "irq request failed\n "); + return ret; + } + + /* + * rcar_has_irq_support() will be enabled + */ + common->base = devm_request_and_ioremap(dev, res); + if (!common->base) { + dev_err(dev, "Unable to ioremap thermal register\n"); + return -ENOMEM; + } + + /* enable temperature comparation */ + rcar_thermal_common_write(common, ENR, 0x00030303); + + idle = 0; /* polling delaye is not needed */ } - zone = thermal_zone_device_register("rcar_thermal", 0, 0, priv, - &rcar_thermal_zone_ops, 0, 0); - if (IS_ERR(zone)) { - dev_err(&pdev->dev, "thermal zone device is NULL\n"); - ret = PTR_ERR(zone); - goto error_iounmap; + for (i = 0;; i++) { + res = platform_get_resource(pdev, IORESOURCE_MEM, mres++); + if (!res) + break; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(dev, "Could not allocate priv\n"); + return -ENOMEM; + } + + priv->base = devm_request_and_ioremap(dev, res); + if (!priv->base) { + dev_err(dev, "Unable to ioremap priv register\n"); + return -ENOMEM; + } + + priv->common = common; + priv->id = i; + mutex_init(&priv->lock); + INIT_LIST_HEAD(&priv->list); + INIT_DELAYED_WORK(&priv->work, rcar_thermal_work); + rcar_thermal_update_temp(priv); + + priv->zone = thermal_zone_device_register("rcar_thermal", + 1, 0, priv, + &rcar_thermal_zone_ops, NULL, 0, + idle); + if (IS_ERR(priv->zone)) { + dev_err(dev, "can't register thermal zone\n"); + goto error_unregister; + } + + list_move_tail(&priv->list, &common->head); + + if (rcar_has_irq_support(priv)) + rcar_thermal_irq_enable(priv); } - platform_set_drvdata(pdev, zone); + platform_set_drvdata(pdev, common); - dev_info(&pdev->dev, "proved\n"); + dev_info(dev, "%d sensor proved\n", i); return 0; -error_iounmap: - devm_iounmap(&pdev->dev, priv->base); -error_free_priv: - devm_kfree(&pdev->dev, priv); +error_unregister: + rcar_thermal_for_each_priv(priv, common) + thermal_zone_device_unregister(priv->zone); - return ret; + return -ENODEV; } static int rcar_thermal_remove(struct platform_device *pdev) { - struct thermal_zone_device *zone = platform_get_drvdata(pdev); - struct rcar_thermal_priv *priv = zone->devdata; + struct rcar_thermal_common *common = platform_get_drvdata(pdev); + struct rcar_thermal_priv *priv; - thermal_zone_device_unregister(zone); - platform_set_drvdata(pdev, NULL); + rcar_thermal_for_each_priv(priv, common) + thermal_zone_device_unregister(priv->zone); - devm_iounmap(&pdev->dev, priv->base); - devm_kfree(&pdev->dev, priv); + platform_set_drvdata(pdev, NULL); return 0; } +static const struct of_device_id rcar_thermal_dt_ids[] = { + { .compatible = "renesas,rcar-thermal", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rcar_thermal_dt_ids); + static struct platform_driver rcar_thermal_driver = { .driver = { .name = "rcar_thermal", + .of_match_table = rcar_thermal_dt_ids, }, .probe = rcar_thermal_probe, .remove = rcar_thermal_remove, diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c index 9bc969261d01..3c5ee5607977 100644 --- a/drivers/thermal/spear_thermal.c +++ b/drivers/thermal/spear_thermal.c @@ -131,7 +131,7 @@ static int spear_thermal_probe(struct platform_device *pdev) return -ENOMEM; } - stdev->clk = clk_get(&pdev->dev, NULL); + stdev->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(stdev->clk)) { dev_err(&pdev->dev, "Can't get clock\n"); return PTR_ERR(stdev->clk); @@ -140,14 +140,14 @@ static int spear_thermal_probe(struct platform_device *pdev) ret = clk_enable(stdev->clk); if (ret) { dev_err(&pdev->dev, "Can't enable clock\n"); - goto put_clk; + return ret; } stdev->flags = val; writel_relaxed(stdev->flags, stdev->thermal_base); spear_thermal = thermal_zone_device_register("spear_thermal", 0, 0, - stdev, &ops, 0, 0); + stdev, &ops, NULL, 0, 0); if (IS_ERR(spear_thermal)) { dev_err(&pdev->dev, "thermal zone device is NULL\n"); ret = PTR_ERR(spear_thermal); @@ -163,8 +163,6 @@ static int spear_thermal_probe(struct platform_device *pdev) disable_clk: clk_disable(stdev->clk); -put_clk: - clk_put(stdev->clk); return ret; } @@ -183,7 +181,6 @@ static int spear_thermal_exit(struct platform_device *pdev) writel_relaxed(actual_mask & ~stdev->flags, stdev->thermal_base); clk_disable(stdev->clk); - clk_put(stdev->clk); return 0; } diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/step_wise.c new file mode 100644 index 000000000000..407cde3211c1 --- /dev/null +++ b/drivers/thermal/step_wise.c @@ -0,0 +1,202 @@ +/* + * step_wise.c - A step-by-step Thermal throttling governor + * + * Copyright (C) 2012 Intel Corp + * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/thermal.h> + +#include "thermal_core.h" + +/* + * If the temperature is higher than a trip point, + * a. if the trend is THERMAL_TREND_RAISING, use higher cooling + * state for this trip point + * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling + * state for this trip point + * c. if the trend is THERMAL_TREND_RAISE_FULL, use upper limit + * for this trip point + * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit + * for this trip point + * If the temperature is lower than a trip point, + * a. if the trend is THERMAL_TREND_RAISING, do nothing + * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling + * state for this trip point, if the cooling state already + * equals lower limit, deactivate the thermal instance + * c. if the trend is THERMAL_TREND_RAISE_FULL, do nothing + * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit, + * if the cooling state already equals lower limit, + * deactive the thermal instance + */ +static unsigned long get_target_state(struct thermal_instance *instance, + enum thermal_trend trend, bool throttle) +{ + struct thermal_cooling_device *cdev = instance->cdev; + unsigned long cur_state; + + cdev->ops->get_cur_state(cdev, &cur_state); + + switch (trend) { + case THERMAL_TREND_RAISING: + if (throttle) + cur_state = cur_state < instance->upper ? + (cur_state + 1) : instance->upper; + break; + case THERMAL_TREND_RAISE_FULL: + if (throttle) + cur_state = instance->upper; + break; + case THERMAL_TREND_DROPPING: + if (cur_state == instance->lower) { + if (!throttle) + cur_state = -1; + } else + cur_state -= 1; + break; + case THERMAL_TREND_DROP_FULL: + if (cur_state == instance->lower) { + if (!throttle) + cur_state = -1; + } else + cur_state = instance->lower; + break; + default: + break; + } + + return cur_state; +} + +static void update_passive_instance(struct thermal_zone_device *tz, + enum thermal_trip_type type, int value) +{ + /* + * If value is +1, activate a passive instance. + * If value is -1, deactivate a passive instance. + */ + if (type == THERMAL_TRIP_PASSIVE || type == THERMAL_TRIPS_NONE) + tz->passive += value; +} + +static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip) +{ + long trip_temp; + enum thermal_trip_type trip_type; + enum thermal_trend trend; + struct thermal_instance *instance; + bool throttle = false; + int old_target; + + if (trip == THERMAL_TRIPS_NONE) { + trip_temp = tz->forced_passive; + trip_type = THERMAL_TRIPS_NONE; + } else { + tz->ops->get_trip_temp(tz, trip, &trip_temp); + tz->ops->get_trip_type(tz, trip, &trip_type); + } + + trend = get_tz_trend(tz, trip); + + if (tz->temperature >= trip_temp) + throttle = true; + + mutex_lock(&tz->lock); + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if (instance->trip != trip) + continue; + + old_target = instance->target; + instance->target = get_target_state(instance, trend, throttle); + + /* Activate a passive thermal instance */ + if (old_target == THERMAL_NO_TARGET && + instance->target != THERMAL_NO_TARGET) + update_passive_instance(tz, trip_type, 1); + /* Deactivate a passive thermal instance */ + else if (old_target != THERMAL_NO_TARGET && + instance->target == THERMAL_NO_TARGET) + update_passive_instance(tz, trip_type, -1); + + + instance->cdev->updated = false; /* cdev needs update */ + } + + mutex_unlock(&tz->lock); +} + +/** + * step_wise_throttle - throttles devices asscciated with the given zone + * @tz - thermal_zone_device + * @trip - the trip point + * @trip_type - type of the trip point + * + * Throttling Logic: This uses the trend of the thermal zone to throttle. + * If the thermal zone is 'heating up' this throttles all the cooling + * devices associated with the zone and its particular trip point, by one + * step. If the zone is 'cooling down' it brings back the performance of + * the devices by one step. + */ +static int step_wise_throttle(struct thermal_zone_device *tz, int trip) +{ + struct thermal_instance *instance; + + thermal_zone_trip_update(tz, trip); + + if (tz->forced_passive) + thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE); + + mutex_lock(&tz->lock); + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) + thermal_cdev_update(instance->cdev); + + mutex_unlock(&tz->lock); + + return 0; +} + +static struct thermal_governor thermal_gov_step_wise = { + .name = "step_wise", + .throttle = step_wise_throttle, + .owner = THIS_MODULE, +}; + +static int __init thermal_gov_step_wise_init(void) +{ + return thermal_register_governor(&thermal_gov_step_wise); +} + +static void __exit thermal_gov_step_wise_exit(void) +{ + thermal_unregister_governor(&thermal_gov_step_wise); +} + +/* This should load after thermal framework */ +fs_initcall(thermal_gov_step_wise_init); +module_exit(thermal_gov_step_wise_exit); + +MODULE_AUTHOR("Durgadoss R"); +MODULE_DESCRIPTION("A step-by-step thermal throttling governor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h new file mode 100644 index 000000000000..0d3205a18112 --- /dev/null +++ b/drivers/thermal/thermal_core.h @@ -0,0 +1,53 @@ +/* + * thermal_core.h + * + * Copyright (C) 2012 Intel Corp + * Author: Durgadoss R <durgadoss.r@intel.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#ifndef __THERMAL_CORE_H__ +#define __THERMAL_CORE_H__ + +#include <linux/device.h> +#include <linux/thermal.h> + +/* Initial state of a cooling device during binding */ +#define THERMAL_NO_TARGET -1UL + +/* + * This structure is used to describe the behavior of + * a certain cooling device on a certain trip point + * in a certain thermal zone + */ +struct thermal_instance { + int id; + char name[THERMAL_NAME_LENGTH]; + struct thermal_zone_device *tz; + struct thermal_cooling_device *cdev; + int trip; + unsigned long upper; /* Highest cooling state for this trip point */ + unsigned long lower; /* Lowest cooling state for this trip point */ + unsigned long target; /* expected cooling state */ + char attr_name[THERMAL_NAME_LENGTH]; + struct device_attribute attr; + struct list_head tz_node; /* node in tz->thermal_instances */ + struct list_head cdev_node; /* node in cdev->thermal_instances */ +}; + +#endif /* __THERMAL_CORE_H__ */ diff --git a/drivers/thermal/thermal_sys.c b/drivers/thermal/thermal_sys.c index 9ee42ca4d289..5b7863a03f98 100644 --- a/drivers/thermal/thermal_sys.c +++ b/drivers/thermal/thermal_sys.c @@ -32,63 +32,115 @@ #include <linux/kdev_t.h> #include <linux/idr.h> #include <linux/thermal.h> -#include <linux/spinlock.h> #include <linux/reboot.h> #include <net/netlink.h> #include <net/genetlink.h> +#include "thermal_core.h" + MODULE_AUTHOR("Zhang Rui"); MODULE_DESCRIPTION("Generic thermal management sysfs support"); MODULE_LICENSE("GPL"); -#define THERMAL_NO_TARGET -1UL -/* - * This structure is used to describe the behavior of - * a certain cooling device on a certain trip point - * in a certain thermal zone - */ -struct thermal_instance { - int id; - char name[THERMAL_NAME_LENGTH]; - struct thermal_zone_device *tz; - struct thermal_cooling_device *cdev; - int trip; - unsigned long upper; /* Highest cooling state for this trip point */ - unsigned long lower; /* Lowest cooling state for this trip point */ - unsigned long target; /* expected cooling state */ - char attr_name[THERMAL_NAME_LENGTH]; - struct device_attribute attr; - struct list_head tz_node; /* node in tz->thermal_instances */ - struct list_head cdev_node; /* node in cdev->thermal_instances */ -}; - static DEFINE_IDR(thermal_tz_idr); static DEFINE_IDR(thermal_cdev_idr); static DEFINE_MUTEX(thermal_idr_lock); static LIST_HEAD(thermal_tz_list); static LIST_HEAD(thermal_cdev_list); +static LIST_HEAD(thermal_governor_list); + static DEFINE_MUTEX(thermal_list_lock); +static DEFINE_MUTEX(thermal_governor_lock); -static int get_idr(struct idr *idr, struct mutex *lock, int *id) +static struct thermal_governor *__find_governor(const char *name) +{ + struct thermal_governor *pos; + + list_for_each_entry(pos, &thermal_governor_list, governor_list) + if (!strnicmp(name, pos->name, THERMAL_NAME_LENGTH)) + return pos; + + return NULL; +} + +int thermal_register_governor(struct thermal_governor *governor) { int err; + const char *name; + struct thermal_zone_device *pos; -again: - if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0)) - return -ENOMEM; + if (!governor) + return -EINVAL; + + mutex_lock(&thermal_governor_lock); + + err = -EBUSY; + if (__find_governor(governor->name) == NULL) { + err = 0; + list_add(&governor->governor_list, &thermal_governor_list); + } + + mutex_lock(&thermal_list_lock); + + list_for_each_entry(pos, &thermal_tz_list, node) { + if (pos->governor) + continue; + if (pos->tzp) + name = pos->tzp->governor_name; + else + name = DEFAULT_THERMAL_GOVERNOR; + if (!strnicmp(name, governor->name, THERMAL_NAME_LENGTH)) + pos->governor = governor; + } + + mutex_unlock(&thermal_list_lock); + mutex_unlock(&thermal_governor_lock); + + return err; +} +EXPORT_SYMBOL_GPL(thermal_register_governor); + +void thermal_unregister_governor(struct thermal_governor *governor) +{ + struct thermal_zone_device *pos; + + if (!governor) + return; + + mutex_lock(&thermal_governor_lock); + + if (__find_governor(governor->name) == NULL) + goto exit; + + mutex_lock(&thermal_list_lock); + + list_for_each_entry(pos, &thermal_tz_list, node) { + if (!strnicmp(pos->governor->name, governor->name, + THERMAL_NAME_LENGTH)) + pos->governor = NULL; + } + + mutex_unlock(&thermal_list_lock); + list_del(&governor->governor_list); +exit: + mutex_unlock(&thermal_governor_lock); + return; +} +EXPORT_SYMBOL_GPL(thermal_unregister_governor); + +static int get_idr(struct idr *idr, struct mutex *lock, int *id) +{ + int ret; if (lock) mutex_lock(lock); - err = idr_get_new(idr, NULL, id); + ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL); if (lock) mutex_unlock(lock); - if (unlikely(err == -EAGAIN)) - goto again; - else if (unlikely(err)) - return err; - - *id = *id & MAX_IDR_MASK; + if (unlikely(ret < 0)) + return ret; + *id = ret; return 0; } @@ -101,6 +153,297 @@ static void release_idr(struct idr *idr, struct mutex *lock, int id) mutex_unlock(lock); } +int get_tz_trend(struct thermal_zone_device *tz, int trip) +{ + enum thermal_trend trend; + + if (!tz->ops->get_trend || tz->ops->get_trend(tz, trip, &trend)) { + if (tz->temperature > tz->last_temperature) + trend = THERMAL_TREND_RAISING; + else if (tz->temperature < tz->last_temperature) + trend = THERMAL_TREND_DROPPING; + else + trend = THERMAL_TREND_STABLE; + } + + return trend; +} +EXPORT_SYMBOL(get_tz_trend); + +struct thermal_instance *get_thermal_instance(struct thermal_zone_device *tz, + struct thermal_cooling_device *cdev, int trip) +{ + struct thermal_instance *pos = NULL; + struct thermal_instance *target_instance = NULL; + + mutex_lock(&tz->lock); + mutex_lock(&cdev->lock); + + list_for_each_entry(pos, &tz->thermal_instances, tz_node) { + if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { + target_instance = pos; + break; + } + } + + mutex_unlock(&cdev->lock); + mutex_unlock(&tz->lock); + + return target_instance; +} +EXPORT_SYMBOL(get_thermal_instance); + +static void print_bind_err_msg(struct thermal_zone_device *tz, + struct thermal_cooling_device *cdev, int ret) +{ + dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n", + tz->type, cdev->type, ret); +} + +static void __bind(struct thermal_zone_device *tz, int mask, + struct thermal_cooling_device *cdev) +{ + int i, ret; + + for (i = 0; i < tz->trips; i++) { + if (mask & (1 << i)) { + ret = thermal_zone_bind_cooling_device(tz, i, cdev, + THERMAL_NO_LIMIT, THERMAL_NO_LIMIT); + if (ret) + print_bind_err_msg(tz, cdev, ret); + } + } +} + +static void __unbind(struct thermal_zone_device *tz, int mask, + struct thermal_cooling_device *cdev) +{ + int i; + + for (i = 0; i < tz->trips; i++) + if (mask & (1 << i)) + thermal_zone_unbind_cooling_device(tz, i, cdev); +} + +static void bind_cdev(struct thermal_cooling_device *cdev) +{ + int i, ret; + const struct thermal_zone_params *tzp; + struct thermal_zone_device *pos = NULL; + + mutex_lock(&thermal_list_lock); + + list_for_each_entry(pos, &thermal_tz_list, node) { + if (!pos->tzp && !pos->ops->bind) + continue; + + if (!pos->tzp && pos->ops->bind) { + ret = pos->ops->bind(pos, cdev); + if (ret) + print_bind_err_msg(pos, cdev, ret); + } + + tzp = pos->tzp; + if (!tzp || !tzp->tbp) + continue; + + for (i = 0; i < tzp->num_tbps; i++) { + if (tzp->tbp[i].cdev || !tzp->tbp[i].match) + continue; + if (tzp->tbp[i].match(pos, cdev)) + continue; + tzp->tbp[i].cdev = cdev; + __bind(pos, tzp->tbp[i].trip_mask, cdev); + } + } + + mutex_unlock(&thermal_list_lock); +} + +static void bind_tz(struct thermal_zone_device *tz) +{ + int i, ret; + struct thermal_cooling_device *pos = NULL; + const struct thermal_zone_params *tzp = tz->tzp; + + if (!tzp && !tz->ops->bind) + return; + + mutex_lock(&thermal_list_lock); + + /* If there is no platform data, try to use ops->bind */ + if (!tzp && tz->ops->bind) { + list_for_each_entry(pos, &thermal_cdev_list, node) { + ret = tz->ops->bind(tz, pos); + if (ret) + print_bind_err_msg(tz, pos, ret); + } + goto exit; + } + + if (!tzp || !tzp->tbp) + goto exit; + + list_for_each_entry(pos, &thermal_cdev_list, node) { + for (i = 0; i < tzp->num_tbps; i++) { + if (tzp->tbp[i].cdev || !tzp->tbp[i].match) + continue; + if (tzp->tbp[i].match(tz, pos)) + continue; + tzp->tbp[i].cdev = pos; + __bind(tz, tzp->tbp[i].trip_mask, pos); + } + } +exit: + mutex_unlock(&thermal_list_lock); +} + +static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, + int delay) +{ + if (delay > 1000) + mod_delayed_work(system_freezable_wq, &tz->poll_queue, + round_jiffies(msecs_to_jiffies(delay))); + else if (delay) + mod_delayed_work(system_freezable_wq, &tz->poll_queue, + msecs_to_jiffies(delay)); + else + cancel_delayed_work(&tz->poll_queue); +} + +static void monitor_thermal_zone(struct thermal_zone_device *tz) +{ + mutex_lock(&tz->lock); + + if (tz->passive) + thermal_zone_device_set_polling(tz, tz->passive_delay); + else if (tz->polling_delay) + thermal_zone_device_set_polling(tz, tz->polling_delay); + else + thermal_zone_device_set_polling(tz, 0); + + mutex_unlock(&tz->lock); +} + +static void handle_non_critical_trips(struct thermal_zone_device *tz, + int trip, enum thermal_trip_type trip_type) +{ + if (tz->governor) + tz->governor->throttle(tz, trip); +} + +static void handle_critical_trips(struct thermal_zone_device *tz, + int trip, enum thermal_trip_type trip_type) +{ + long trip_temp; + + tz->ops->get_trip_temp(tz, trip, &trip_temp); + + /* If we have not crossed the trip_temp, we do not care. */ + if (tz->temperature < trip_temp) + return; + + if (tz->ops->notify) + tz->ops->notify(tz, trip, trip_type); + + if (trip_type == THERMAL_TRIP_CRITICAL) { + dev_emerg(&tz->device, + "critical temperature reached(%d C),shutting down\n", + tz->temperature / 1000); + orderly_poweroff(true); + } +} + +static void handle_thermal_trip(struct thermal_zone_device *tz, int trip) +{ + enum thermal_trip_type type; + + tz->ops->get_trip_type(tz, trip, &type); + + if (type == THERMAL_TRIP_CRITICAL || type == THERMAL_TRIP_HOT) + handle_critical_trips(tz, trip, type); + else + handle_non_critical_trips(tz, trip, type); + /* + * Alright, we handled this trip successfully. + * So, start monitoring again. + */ + monitor_thermal_zone(tz); +} + +static int thermal_zone_get_temp(struct thermal_zone_device *tz, + unsigned long *temp) +{ + int ret = 0; +#ifdef CONFIG_THERMAL_EMULATION + int count; + unsigned long crit_temp = -1UL; + enum thermal_trip_type type; +#endif + + mutex_lock(&tz->lock); + + ret = tz->ops->get_temp(tz, temp); +#ifdef CONFIG_THERMAL_EMULATION + if (!tz->emul_temperature) + goto skip_emul; + + for (count = 0; count < tz->trips; count++) { + ret = tz->ops->get_trip_type(tz, count, &type); + if (!ret && type == THERMAL_TRIP_CRITICAL) { + ret = tz->ops->get_trip_temp(tz, count, &crit_temp); + break; + } + } + + if (ret) + goto skip_emul; + + if (*temp < crit_temp) + *temp = tz->emul_temperature; +skip_emul: +#endif + mutex_unlock(&tz->lock); + return ret; +} + +static void update_temperature(struct thermal_zone_device *tz) +{ + long temp; + int ret; + + ret = thermal_zone_get_temp(tz, &temp); + if (ret) { + dev_warn(&tz->device, "failed to read out thermal zone %d\n", + tz->id); + return; + } + + mutex_lock(&tz->lock); + tz->last_temperature = tz->temperature; + tz->temperature = temp; + mutex_unlock(&tz->lock); +} + +void thermal_zone_device_update(struct thermal_zone_device *tz) +{ + int count; + + update_temperature(tz); + + for (count = 0; count < tz->trips; count++) + handle_thermal_trip(tz, count); +} +EXPORT_SYMBOL(thermal_zone_device_update); + +static void thermal_zone_device_check(struct work_struct *work) +{ + struct thermal_zone_device *tz = container_of(work, struct + thermal_zone_device, + poll_queue.work); + thermal_zone_device_update(tz); +} + /* sys I/F for thermal zone */ #define to_thermal_zone(_dev) \ @@ -121,10 +464,7 @@ temp_show(struct device *dev, struct device_attribute *attr, char *buf) long temperature; int ret; - if (!tz->ops->get_temp) - return -EPERM; - - ret = tz->ops->get_temp(tz, &temperature); + ret = thermal_zone_get_temp(tz, &temperature); if (ret) return ret; @@ -354,10 +694,66 @@ passive_show(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%d\n", tz->forced_passive); } +static ssize_t +policy_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = -EINVAL; + struct thermal_zone_device *tz = to_thermal_zone(dev); + struct thermal_governor *gov; + + mutex_lock(&thermal_governor_lock); + + gov = __find_governor(buf); + if (!gov) + goto exit; + + tz->governor = gov; + ret = count; + +exit: + mutex_unlock(&thermal_governor_lock); + return ret; +} + +static ssize_t +policy_show(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + + return sprintf(buf, "%s\n", tz->governor->name); +} + +#ifdef CONFIG_THERMAL_EMULATION +static ssize_t +emul_temp_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + int ret = 0; + unsigned long temperature; + + if (kstrtoul(buf, 10, &temperature)) + return -EINVAL; + + if (!tz->ops->set_emul_temp) { + mutex_lock(&tz->lock); + tz->emul_temperature = temperature; + mutex_unlock(&tz->lock); + } else { + ret = tz->ops->set_emul_temp(tz, temperature); + } + + return ret ? ret : count; +} +static DEVICE_ATTR(emul_temp, S_IWUSR, NULL, emul_temp_store); +#endif/*CONFIG_THERMAL_EMULATION*/ + static DEVICE_ATTR(type, 0444, type_show, NULL); static DEVICE_ATTR(temp, 0444, temp_show, NULL); static DEVICE_ATTR(mode, 0644, mode_show, mode_store); static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store); +static DEVICE_ATTR(policy, S_IRUGO | S_IWUSR, policy_show, policy_store); /* sys I/F for cooling device */ #define to_cooling_device(_dev) \ @@ -495,7 +891,7 @@ temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) temp_input); struct thermal_zone_device *tz = temp->tz; - ret = tz->ops->get_temp(tz, &temperature); + ret = thermal_zone_get_temp(tz, &temperature); if (ret) return ret; @@ -700,27 +1096,6 @@ thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) } #endif -static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, - int delay) -{ - if (delay > 1000) - mod_delayed_work(system_freezable_wq, &tz->poll_queue, - round_jiffies(msecs_to_jiffies(delay))); - else if (delay) - mod_delayed_work(system_freezable_wq, &tz->poll_queue, - msecs_to_jiffies(delay)); - else - cancel_delayed_work(&tz->poll_queue); -} - -static void thermal_zone_device_check(struct work_struct *work) -{ - struct thermal_zone_device *tz = container_of(work, struct - thermal_zone_device, - poll_queue.work); - thermal_zone_device_update(tz); -} - /** * thermal_zone_bind_cooling_device - bind a cooling device to a thermal zone * @tz: thermal zone device @@ -895,7 +1270,6 @@ thermal_cooling_device_register(char *type, void *devdata, const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; - struct thermal_zone_device *pos; int result; if (type && strlen(type) >= THERMAL_NAME_LENGTH) @@ -945,20 +1319,15 @@ thermal_cooling_device_register(char *type, void *devdata, if (result) goto unregister; + /* Add 'this' new cdev to the global cdev list */ mutex_lock(&thermal_list_lock); list_add(&cdev->node, &thermal_cdev_list); - list_for_each_entry(pos, &thermal_tz_list, node) { - if (!pos->ops->bind) - continue; - result = pos->ops->bind(pos, cdev); - if (result) - break; - - } mutex_unlock(&thermal_list_lock); - if (!result) - return cdev; + /* Update binding information for 'this' new cdev */ + bind_cdev(cdev); + + return cdev; unregister: release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); @@ -974,10 +1343,10 @@ EXPORT_SYMBOL(thermal_cooling_device_register); * thermal_cooling_device_unregister() must be called when the device is no * longer needed. */ -void thermal_cooling_device_unregister(struct - thermal_cooling_device - *cdev) +void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev) { + int i; + const struct thermal_zone_params *tzp; struct thermal_zone_device *tz; struct thermal_cooling_device *pos = NULL; @@ -994,12 +1363,28 @@ void thermal_cooling_device_unregister(struct return; } list_del(&cdev->node); + + /* Unbind all thermal zones associated with 'this' cdev */ list_for_each_entry(tz, &thermal_tz_list, node) { - if (!tz->ops->unbind) + if (tz->ops->unbind) { + tz->ops->unbind(tz, cdev); + continue; + } + + if (!tz->tzp || !tz->tzp->tbp) continue; - tz->ops->unbind(tz, cdev); + + tzp = tz->tzp; + for (i = 0; i < tzp->num_tbps; i++) { + if (tzp->tbp[i].cdev == cdev) { + __unbind(tz, tzp->tbp[i].trip_mask, cdev); + tzp->tbp[i].cdev = NULL; + } + } } + mutex_unlock(&thermal_list_lock); + if (cdev->type[0]) device_remove_file(&cdev->device, &dev_attr_cdev_type); device_remove_file(&cdev->device, &dev_attr_max_state); @@ -1011,7 +1396,7 @@ void thermal_cooling_device_unregister(struct } EXPORT_SYMBOL(thermal_cooling_device_unregister); -static void thermal_cdev_do_update(struct thermal_cooling_device *cdev) +void thermal_cdev_update(struct thermal_cooling_device *cdev) { struct thermal_instance *instance; unsigned long target = 0; @@ -1032,183 +1417,25 @@ static void thermal_cdev_do_update(struct thermal_cooling_device *cdev) cdev->ops->set_cur_state(cdev, target); cdev->updated = true; } +EXPORT_SYMBOL(thermal_cdev_update); -static void thermal_zone_do_update(struct thermal_zone_device *tz) -{ - struct thermal_instance *instance; - - list_for_each_entry(instance, &tz->thermal_instances, tz_node) - thermal_cdev_do_update(instance->cdev); -} - -/* - * Cooling algorithm for both active and passive cooling - * - * 1. if the temperature is higher than a trip point, - * a. if the trend is THERMAL_TREND_RAISING, use higher cooling - * state for this trip point - * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling - * state for this trip point - * - * 2. if the temperature is lower than a trip point, use lower - * cooling state for this trip point - * - * Note that this behaves the same as the previous passive cooling - * algorithm. - */ - -static void thermal_zone_trip_update(struct thermal_zone_device *tz, - int trip, long temp) -{ - struct thermal_instance *instance; - struct thermal_cooling_device *cdev = NULL; - unsigned long cur_state, max_state; - long trip_temp; - enum thermal_trip_type trip_type; - enum thermal_trend trend; - - if (trip == THERMAL_TRIPS_NONE) { - trip_temp = tz->forced_passive; - trip_type = THERMAL_TRIPS_NONE; - } else { - tz->ops->get_trip_temp(tz, trip, &trip_temp); - tz->ops->get_trip_type(tz, trip, &trip_type); - } - - if (!tz->ops->get_trend || tz->ops->get_trend(tz, trip, &trend)) { - /* - * compare the current temperature and previous temperature - * to get the thermal trend, if no special requirement - */ - if (tz->temperature > tz->last_temperature) - trend = THERMAL_TREND_RAISING; - else if (tz->temperature < tz->last_temperature) - trend = THERMAL_TREND_DROPPING; - else - trend = THERMAL_TREND_STABLE; - } - - if (temp >= trip_temp) { - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - if (instance->trip != trip) - continue; - - cdev = instance->cdev; - - cdev->ops->get_cur_state(cdev, &cur_state); - cdev->ops->get_max_state(cdev, &max_state); - - if (trend == THERMAL_TREND_RAISING) { - cur_state = cur_state < instance->upper ? - (cur_state + 1) : instance->upper; - } else if (trend == THERMAL_TREND_DROPPING) { - cur_state = cur_state > instance->lower ? - (cur_state - 1) : instance->lower; - } - - /* activate a passive thermal instance */ - if ((trip_type == THERMAL_TRIP_PASSIVE || - trip_type == THERMAL_TRIPS_NONE) && - instance->target == THERMAL_NO_TARGET) - tz->passive++; - - instance->target = cur_state; - cdev->updated = false; /* cooling device needs update */ - } - } else { /* below trip */ - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - if (instance->trip != trip) - continue; - - /* Do not use the inactive thermal instance */ - if (instance->target == THERMAL_NO_TARGET) - continue; - cdev = instance->cdev; - cdev->ops->get_cur_state(cdev, &cur_state); - - cur_state = cur_state > instance->lower ? - (cur_state - 1) : THERMAL_NO_TARGET; - - /* deactivate a passive thermal instance */ - if ((trip_type == THERMAL_TRIP_PASSIVE || - trip_type == THERMAL_TRIPS_NONE) && - cur_state == THERMAL_NO_TARGET) - tz->passive--; - instance->target = cur_state; - cdev->updated = false; /* cooling device needs update */ - } - } - - return; -} /** - * thermal_zone_device_update - force an update of a thermal zone's state - * @ttz: the thermal zone to update + * notify_thermal_framework - Sensor drivers use this API to notify framework + * @tz: thermal zone device + * @trip: indicates which trip point has been crossed + * + * This function handles the trip events from sensor drivers. It starts + * throttling the cooling devices according to the policy configured. + * For CRITICAL and HOT trip points, this notifies the respective drivers, + * and does actual throttling for other trip points i.e ACTIVE and PASSIVE. + * The throttling policy is based on the configured platform data; if no + * platform data is provided, this uses the step_wise throttling policy. */ - -void thermal_zone_device_update(struct thermal_zone_device *tz) +void notify_thermal_framework(struct thermal_zone_device *tz, int trip) { - int count, ret = 0; - long temp, trip_temp; - enum thermal_trip_type trip_type; - - mutex_lock(&tz->lock); - - if (tz->ops->get_temp(tz, &temp)) { - /* get_temp failed - retry it later */ - pr_warn("failed to read out thermal zone %d\n", tz->id); - goto leave; - } - - tz->last_temperature = tz->temperature; - tz->temperature = temp; - - for (count = 0; count < tz->trips; count++) { - tz->ops->get_trip_type(tz, count, &trip_type); - tz->ops->get_trip_temp(tz, count, &trip_temp); - - switch (trip_type) { - case THERMAL_TRIP_CRITICAL: - if (temp >= trip_temp) { - if (tz->ops->notify) - ret = tz->ops->notify(tz, count, - trip_type); - if (!ret) { - pr_emerg("Critical temperature reached (%ld C), shutting down\n", - temp/1000); - orderly_poweroff(true); - } - } - break; - case THERMAL_TRIP_HOT: - if (temp >= trip_temp) - if (tz->ops->notify) - tz->ops->notify(tz, count, trip_type); - break; - case THERMAL_TRIP_ACTIVE: - thermal_zone_trip_update(tz, count, temp); - break; - case THERMAL_TRIP_PASSIVE: - if (temp >= trip_temp || tz->passive) - thermal_zone_trip_update(tz, count, temp); - break; - } - } - - if (tz->forced_passive) - thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE, temp); - thermal_zone_do_update(tz); - -leave: - if (tz->passive) - thermal_zone_device_set_polling(tz, tz->passive_delay); - else if (tz->polling_delay) - thermal_zone_device_set_polling(tz, tz->polling_delay); - else - thermal_zone_device_set_polling(tz, 0); - mutex_unlock(&tz->lock); + handle_thermal_trip(tz, trip); } -EXPORT_SYMBOL(thermal_zone_device_update); +EXPORT_SYMBOL(notify_thermal_framework); /** * create_trip_attrs - create attributes for trip points @@ -1320,6 +1547,7 @@ static void remove_trip_attrs(struct thermal_zone_device *tz) * @mask: a bit string indicating the writeablility of trip points * @devdata: private device data * @ops: standard thermal zone device callbacks + * @tzp: thermal zone platform parameters * @passive_delay: number of milliseconds to wait between polls when * performing passive cooling * @polling_delay: number of milliseconds to wait between polls when checking @@ -1332,10 +1560,10 @@ static void remove_trip_attrs(struct thermal_zone_device *tz) struct thermal_zone_device *thermal_zone_device_register(const char *type, int trips, int mask, void *devdata, const struct thermal_zone_device_ops *ops, + const struct thermal_zone_params *tzp, int passive_delay, int polling_delay) { struct thermal_zone_device *tz; - struct thermal_cooling_device *pos; enum thermal_trip_type trip_type; int result; int count; @@ -1350,6 +1578,9 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, if (!ops || !ops->get_temp) return ERR_PTR(-EINVAL); + if (trips > 0 && !ops->get_trip_type) + return ERR_PTR(-EINVAL); + tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL); if (!tz) return ERR_PTR(-ENOMEM); @@ -1365,6 +1596,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, strcpy(tz->type, type ? : ""); tz->ops = ops; + tz->tzp = tzp; tz->device.class = &thermal_class; tz->devdata = devdata; tz->trips = trips; @@ -1406,12 +1638,31 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, passive = 1; } - if (!passive) - result = device_create_file(&tz->device, - &dev_attr_passive); + if (!passive) { + result = device_create_file(&tz->device, &dev_attr_passive); + if (result) + goto unregister; + } +#ifdef CONFIG_THERMAL_EMULATION + result = device_create_file(&tz->device, &dev_attr_emul_temp); if (result) goto unregister; +#endif + /* Create policy attribute */ + result = device_create_file(&tz->device, &dev_attr_policy); + if (result) + goto unregister; + + /* Update 'this' zone's governor information */ + mutex_lock(&thermal_governor_lock); + + if (tz->tzp) + tz->governor = __find_governor(tz->tzp->governor_name); + else + tz->governor = __find_governor(DEFAULT_THERMAL_GOVERNOR); + + mutex_unlock(&thermal_governor_lock); result = thermal_add_hwmon_sysfs(tz); if (result) @@ -1419,14 +1670,11 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, mutex_lock(&thermal_list_lock); list_add_tail(&tz->node, &thermal_tz_list); - if (ops->bind) - list_for_each_entry(pos, &thermal_cdev_list, node) { - result = ops->bind(tz, pos); - if (result) - break; - } mutex_unlock(&thermal_list_lock); + /* Bind cooling devices for this zone */ + bind_tz(tz); + INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check); thermal_zone_device_update(tz); @@ -1447,12 +1695,16 @@ EXPORT_SYMBOL(thermal_zone_device_register); */ void thermal_zone_device_unregister(struct thermal_zone_device *tz) { + int i; + const struct thermal_zone_params *tzp; struct thermal_cooling_device *cdev; struct thermal_zone_device *pos = NULL; if (!tz) return; + tzp = tz->tzp; + mutex_lock(&thermal_list_lock); list_for_each_entry(pos, &thermal_tz_list, node) if (pos == tz) @@ -1463,9 +1715,25 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz) return; } list_del(&tz->node); - if (tz->ops->unbind) - list_for_each_entry(cdev, &thermal_cdev_list, node) - tz->ops->unbind(tz, cdev); + + /* Unbind all cdevs associated with 'this' thermal zone */ + list_for_each_entry(cdev, &thermal_cdev_list, node) { + if (tz->ops->unbind) { + tz->ops->unbind(tz, cdev); + continue; + } + + if (!tzp || !tzp->tbp) + break; + + for (i = 0; i < tzp->num_tbps; i++) { + if (tzp->tbp[i].cdev == cdev) { + __unbind(tz, tzp->tbp[i].trip_mask, cdev); + tzp->tbp[i].cdev = NULL; + } + } + } + mutex_unlock(&thermal_list_lock); thermal_zone_device_set_polling(tz, 0); @@ -1475,7 +1743,9 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz) device_remove_file(&tz->device, &dev_attr_temp); if (tz->ops->get_mode) device_remove_file(&tz->device, &dev_attr_mode); + device_remove_file(&tz->device, &dev_attr_policy); remove_trip_attrs(tz); + tz->governor = NULL; thermal_remove_hwmon_sysfs(tz); release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); @@ -1498,7 +1768,8 @@ static struct genl_multicast_group thermal_event_mcgrp = { .name = THERMAL_GENL_MCAST_GROUP_NAME, }; -int thermal_generate_netlink_event(u32 orig, enum events event) +int thermal_generate_netlink_event(struct thermal_zone_device *tz, + enum events event) { struct sk_buff *skb; struct nlattr *attr; @@ -1508,6 +1779,9 @@ int thermal_generate_netlink_event(u32 orig, enum events event) int result; static unsigned int thermal_event_seqnum; + if (!tz) + return -EINVAL; + /* allocate memory */ size = nla_total_size(sizeof(struct thermal_genl_event)) + nla_total_size(0); @@ -1542,7 +1816,7 @@ int thermal_generate_netlink_event(u32 orig, enum events event) memset(thermal_event, 0, sizeof(struct thermal_genl_event)); - thermal_event->orig = orig; + thermal_event->orig = tz->id; thermal_event->event = event; /* send multicast genetlink message */ @@ -1554,7 +1828,7 @@ int thermal_generate_netlink_event(u32 orig, enum events event) result = genlmsg_multicast(skb, 0, thermal_event_mcgrp.id, GFP_ATOMIC); if (result) - pr_info("failed to send netlink event:%d\n", result); + dev_err(&tz->device, "Failed to send netlink event:%d", result); return result; } @@ -1594,6 +1868,7 @@ static int __init thermal_init(void) idr_destroy(&thermal_cdev_idr); mutex_destroy(&thermal_idr_lock); mutex_destroy(&thermal_list_lock); + return result; } result = genetlink_init(); return result; diff --git a/drivers/thermal/user_space.c b/drivers/thermal/user_space.c new file mode 100644 index 000000000000..6bbb380b6d19 --- /dev/null +++ b/drivers/thermal/user_space.c @@ -0,0 +1,68 @@ +/* + * user_space.c - A simple user space Thermal events notifier + * + * Copyright (C) 2012 Intel Corp + * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/thermal.h> + +#include "thermal_core.h" + +/** + * notify_user_space - Notifies user space about thermal events + * @tz - thermal_zone_device + * + * This function notifies the user space through UEvents. + */ +static int notify_user_space(struct thermal_zone_device *tz, int trip) +{ + mutex_lock(&tz->lock); + kobject_uevent(&tz->device.kobj, KOBJ_CHANGE); + mutex_unlock(&tz->lock); + return 0; +} + +static struct thermal_governor thermal_gov_user_space = { + .name = "user_space", + .throttle = notify_user_space, + .owner = THIS_MODULE, +}; + +static int __init thermal_gov_user_space_init(void) +{ + return thermal_register_governor(&thermal_gov_user_space); +} + +static void __exit thermal_gov_user_space_exit(void) +{ + thermal_unregister_governor(&thermal_gov_user_space); +} + +/* This should load after thermal framework */ +fs_initcall(thermal_gov_user_space_init); +module_exit(thermal_gov_user_space_exit); + +MODULE_AUTHOR("Durgadoss R"); +MODULE_DESCRIPTION("A user space Thermal notifier"); +MODULE_LICENSE("GPL"); |