aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/thermal
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thermal')
-rw-r--r--drivers/thermal/Kconfig127
-rw-r--r--drivers/thermal/Makefile21
-rw-r--r--drivers/thermal/cpu_cooling.c128
-rw-r--r--drivers/thermal/db8500_cpufreq_cooling.c107
-rw-r--r--drivers/thermal/db8500_thermal.c529
-rw-r--r--drivers/thermal/dove_thermal.c209
-rw-r--r--drivers/thermal/exynos_thermal.c227
-rw-r--r--drivers/thermal/fair_share.c133
-rw-r--r--drivers/thermal/intel_powerclamp.c795
-rw-r--r--drivers/thermal/kirkwood_thermal.c134
-rw-r--r--drivers/thermal/rcar_thermal.c501
-rw-r--r--drivers/thermal/spear_thermal.c9
-rw-r--r--drivers/thermal/step_wise.c202
-rw-r--r--drivers/thermal/thermal_core.h53
-rw-r--r--drivers/thermal/thermal_sys.c803
-rw-r--r--drivers/thermal/user_space.c68
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, &param);
+
+ 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 *)&current_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");