All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 0/6] thermal: Add kernel thermal support for exynos platform
@ 2012-08-16 11:53 ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors, amit.kachhap

Hi Andrew/Zhang/Len,

This current patchset is based on 3.6-rc1 and Zhang Rui's core thermal
enhancement patches(http://permalink.gmane.org/gmane.linux.acpi.devel/54564).

Version V5 is present in linux-next tree. Since Zhang Rui's implementation are
already present in linux-next so requesting Andrew to add this V6 series and drop
earlier V5 version.

Changes since v5:
This patchset basically simplifies the cpufreq cooling API's to take just
cpumask as an input parameter and also removes the state management logic as they
are now taken care in the core thermal layer.

All these patches over Zhang Rui's work can be found in the git link
git://git.linaro.org/people/amitdanielk/linux.git exynos_v6_thermal_tree

Thanks,
Amit Daniel

Amit Daniel Kachhap (6):
  thermal: add generic cpufreq cooling implementation
  hwmon: exynos4: move thermal sensor driver to driver/thermal
    directory
  thermal: exynos5: add exynos5250 thermal sensor driver support
  thermal: exynos: register the tmu sensor with the kernel thermal
    layer
  ARM: exynos: add thermal sensor driver platform data support
  thermal: exynos: Use devm_* functions

 Documentation/hwmon/exynos4_tmu              |   81 ---
 Documentation/thermal/cpu-cooling-api.txt    |   52 ++
 Documentation/thermal/exynos_thermal         |   52 ++
 drivers/hwmon/Kconfig                        |   10 -
 drivers/hwmon/Makefile                       |    1 -
 drivers/hwmon/exynos4_tmu.c                  |  518 --------------
 drivers/thermal/Kconfig                      |   18 +
 drivers/thermal/Makefile                     |    2 +
 drivers/thermal/cpu_cooling.c                |  512 +++++++++++++
 drivers/thermal/exynos_thermal.c             |  994 ++++++++++++++++++++++++++
 include/linux/cpu_cooling.h                  |   79 ++
 include/linux/platform_data/exynos4_tmu.h    |   83 ---
 include/linux/platform_data/exynos_thermal.h |  116 +++
 13 files changed, 1825 insertions(+), 693 deletions(-)
 delete mode 100644 Documentation/hwmon/exynos4_tmu
 create mode 100644 Documentation/thermal/cpu-cooling-api.txt
 create mode 100644 Documentation/thermal/exynos_thermal
 delete mode 100644 drivers/hwmon/exynos4_tmu.c
 create mode 100644 drivers/thermal/cpu_cooling.c
 create mode 100644 drivers/thermal/exynos_thermal.c
 create mode 100644 include/linux/cpu_cooling.h
 delete mode 100644 include/linux/platform_data/exynos4_tmu.h
 create mode 100644 include/linux/platform_data/exynos_thermal.h


^ permalink raw reply	[flat|nested] 50+ messages in thread

* [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-16 11:53 ` [lm-sensors] " Amit Daniel Kachhap
  (?)
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park,
	Guenter Roeck

This patchset introduces a new generic cooling device based on cpufreq
that can be used on non-ACPI platforms.  As a proof of concept, we have
drivers for the following platforms using this mechanism now:

 * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
 * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)

There is a small change in cpufreq cooling registration APIs, so a minor
change is needed for Freescale platforms.

Brief Description:

1) The generic cooling devices code is placed inside driver/thermal/*
   as placing inside acpi folder will need un-necessary enabling of acpi
   code.  This code is architecture independent.

2) This patchset adds generic cpu cooling low level implementation
   through frequency clipping.  In future, other cpu related cooling
   devices may be added here.  An ACPI version of this already exists
   (drivers/acpi/processor_thermal.c) .But this will be useful for
   platforms like ARM using the generic thermal interface along with the
   generic cpu cooling devices.  The cooling device registration API's
   return cooling device pointers which can be easily binded with the
   thermal zone trip points.  The important APIs exposed are,

   a) struct thermal_cooling_device *cpufreq_cooling_register(
        struct cpumask *clip_cpus)
   b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)

3) Samsung exynos platform thermal implementation is done using the
   generic cpu cooling APIs and the new trip type.  The temperature sensor
   driver present in the hwmon folder(registered as hwmon driver) is moved
   to thermal folder and registered as a thermal driver.

A simple data/control flow diagrams is shown below,

Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
          |                             |
         \|/                            |
  Cpufreq cooling device <---------------

TODO:
*Will send the DT enablement patches later after the driver is merged.

This patch:

Add support for generic cpu thermal cooling low level implementations
using frequency scaling up/down based on the registration parameters.
Different cpu related cooling devices can be registered by the user and
the binding of these cooling devices to the corresponding trip points can
be easily done as the registration APIs return the cooling device pointer.
The user of these APIs are responsible for passing clipping frequency .
The drivers can also register to recieve notification about any cooling
action called.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Cc: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 Documentation/thermal/cpu-cooling-api.txt |   52 +++
 drivers/thermal/Kconfig                   |   11 +
 drivers/thermal/Makefile                  |    1 +
 drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
 include/linux/cpu_cooling.h               |   79 +++++
 5 files changed, 655 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/thermal/cpu-cooling-api.txt
 create mode 100644 drivers/thermal/cpu_cooling.c
 create mode 100644 include/linux/cpu_cooling.h

diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
new file mode 100644
index 0000000..a1f2a6b
--- /dev/null
+++ b/Documentation/thermal/cpu-cooling-api.txt
@@ -0,0 +1,52 @@
+CPU cooling APIs How To
+===================================
+
+Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
+
+Updated: 12 May 2012
+
+Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+
+0. Introduction
+
+The generic cpu cooling(freq clipping) provides registration/unregistration APIs
+to the caller. The binding of the cooling devices to the trip point is left for
+the user. The registration APIs returns the cooling device pointer.
+
+1. cpu cooling APIs
+
+1.1 cpufreq registration/unregistration APIs
+1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+
+    This interface function registers the cpufreq cooling device with the name
+    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+    cooling devices.
+
+   clip_cpus: cpumask of cpus where the frequency constraints will happen.
+
+1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+
+    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+
+    cdev: Cooling device pointer which has to be unregistered.
+
+
+1.2 CPU cooling action notifier register/unregister interface
+1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
+
+1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 7dd8c34..996003b 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -19,6 +19,17 @@ config THERMAL_HWMON
 	depends on HWMON=y || HWMON=THERMAL
 	default y
 
+config CPU_THERMAL
+	bool "generic cpu cooling support"
+	depends on THERMAL && CPU_FREQ
+	help
+	  This implements the generic cpu cooling mechanism through frequency
+	  reduction, cpu hotplug and any other ways of reducing temperature. An
+	  ACPI version of this already exists(drivers/acpi/processor_thermal.c).
+	  This will be useful for platforms using the generic thermal interface
+	  and not the ACPI interface.
+	  If you want this support, you should say Y here.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fd9369a..aae59ad 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,5 +3,6 @@
 #
 
 obj-$(CONFIG_THERMAL)		+= thermal_sys.o
+obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 0000000..c42e557
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,512 @@
+/*
+ *  linux/drivers/thermal/cpu_cooling.c
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+/**
+ * struct cpufreq_cooling_device
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ *	registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the the
+ *	egistered cooling device.
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ *	cooling	devices.
+ * @cpufreq_val: integer value representing the absolute value of the clipped
+ *	frequency.
+ * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ *
+ * This structure is required for keeping information of each
+ * cpufreq_cooling_device registered as a list whose head is represented by
+ * cooling_cpufreq_list. In order to prevent corruption of this list a
+ * mutex lock cooling_cpufreq_lock is used.
+ */
+struct cpufreq_cooling_device {
+	int id;
+	struct thermal_cooling_device *cool_dev;
+	unsigned int cpufreq_state;
+	unsigned int cpufreq_val;
+	struct cpumask allowed_cpus;
+	struct list_head node;
+};
+static LIST_HEAD(cooling_cpufreq_list);
+static DEFINE_IDR(cpufreq_idr);
+
+static struct mutex cooling_cpufreq_lock;
+
+/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
+#define NOTIFY_INVALID NULL
+struct cpufreq_cooling_device *notify_device;
+
+/* Head of the blocking notifier chain to inform about frequency clamping */
+static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+	int err;
+again:
+	if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
+		return -ENOMEM;
+
+	mutex_lock(&cooling_cpufreq_lock);
+	err = idr_get_new(idr, NULL, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	if (unlikely(err == -EAGAIN))
+		goto again;
+	else if (unlikely(err))
+		return err;
+
+	*id = *id & MAX_ID_MASK;
+	return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+	mutex_lock(&cooling_cpufreq_lock);
+	idr_remove(idr, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+}
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_register(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_register_notifier);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_unregister(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_unregister_notifier);
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
+ * @cpu: cpu for which check is needed.
+ */
+static int is_cpufreq_valid(int cpu)
+{
+	struct cpufreq_policy policy;
+	return !cpufreq_get_policy(&policy, 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
+ */
+static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
+{
+	int ret = 0, i = 0;
+	unsigned long level_index;
+	bool descend = false;
+	struct cpufreq_frequency_table *table =
+					cpufreq_frequency_get_table(cpu);
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/*check if table in ascending or descending order*/
+		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+			(table[i + 1].frequency < table[i].frequency)
+			&& !descend) {
+			descend = true;
+		}
+
+		/*return if level matched and table in descending order*/
+		if (descend && i == level)
+			return table[i].frequency;
+		i++;
+	}
+	i--;
+
+	if (level > i || descend)
+		return ret;
+	level_index = i - level;
+
+	/*Scan the table in reverse order and match the level*/
+	while (i >= 0) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		/*return if level matched*/
+		if (i == level_index)
+			return table[i].frequency;
+		i--;
+	}
+	return ret;
+}
+
+/**
+ * cpufreq_apply_cooling - function to apply frequency clipping.
+ * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
+ *	clipping data.
+ * @cooling_state: value of the cooling state.
+ */
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+				unsigned long cooling_state)
+{
+	unsigned int event, cpuid, clip_freq;
+	struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
+	unsigned int cpu = cpumask_any(maskPtr);
+
+
+	/* Check if the old cooling action is same as new cooling action */
+	if (cpufreq_device->cpufreq_state == cooling_state)
+		return 0;
+
+	clip_freq = get_cpu_frequency(cpu, cooling_state);
+	if (!clip_freq)
+		return -EINVAL;
+
+	cpufreq_device->cpufreq_state = cooling_state;
+	cpufreq_device->cpufreq_val = clip_freq;
+	notify_device = cpufreq_device;
+
+	if (cooling_state != 0)
+		event = CPUFREQ_COOLING_START;
+	else
+		event = CPUFREQ_COOLING_STOP;
+
+	blocking_notifier_call_chain(&cputherm_state_notifier_list,
+						event, &clip_freq);
+
+	for_each_cpu(cpuid, maskPtr) {
+		if (is_cpufreq_valid(cpuid))
+			cpufreq_update_policy(cpuid);
+	}
+
+	notify_device = NOTIFY_INVALID;
+
+	return 0;
+}
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb:	struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+					unsigned long event, void *data)
+{
+	struct cpufreq_policy *policy = data;
+	unsigned long max_freq = 0;
+
+	if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
+		return 0;
+
+	if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
+		max_freq = notify_device->cpufreq_val;
+
+	/* Never exceed user_policy.max*/
+	if (max_freq > policy->user_policy.max)
+		max_freq = policy->user_policy.max;
+
+	if (policy->max != max_freq)
+		cpufreq_verify_within_limits(policy, 0, max_freq);
+
+	return 0;
+}
+
+/*
+ * cpufreq cooling device callback functions are defined below
+ */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ */
+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;
+	unsigned int cpu;
+	struct cpufreq_frequency_table *table;
+
+	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;
+	}
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		i++;
+	}
+	if (i > 0) {
+		*state = --i;
+		ret = 0;
+	}
+
+return_get_max_state:
+	mutex_unlock(&cooling_cpufreq_lock);
+	return ret;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling 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);
+
+	return ret;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ */
+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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	return ret;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+	.get_max_state = cpufreq_get_max_state,
+	.get_cur_state = cpufreq_get_cur_state,
+	.set_cur_state = cpufreq_set_cur_state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+	.notifier_call = cpufreq_thermal_notifier,
+};
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+	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;
+	char dev_name[THERMAL_NAME_LENGTH];
+	int ret = 0, id = 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*/
+		if (!cpufreq_get_policy(&policy, i))
+			continue;
+		if (min == 0 && max == 0) {
+			min = policy.cpuinfo.min_freq;
+			max = policy.cpuinfo.max_freq;
+		} else {
+			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);
+	if (!cpufreq_dev)
+		return ERR_PTR(-ENOMEM);
+
+	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);
+		return ERR_PTR(-EINVAL);
+	}
+
+	sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
+
+	cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
+						&cpufreq_cooling_ops);
+	if (!cool_dev) {
+		release_idr(&cpufreq_idr, cpufreq_dev->id);
+		kfree(cpufreq_dev);
+		return ERR_PTR(-EINVAL);
+	}
+	cpufreq_dev->id = id;
+	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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+	return cool_dev;
+}
+EXPORT_SYMBOL(cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+	struct cpufreq_cooling_device *cpufreq_dev = NULL;
+	unsigned int cpufreq_dev_count = 0;
+
+	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);
+
+	/* Unregister the notifier for the last cpufreq cooling device */
+	if (cpufreq_dev_count == 1) {
+		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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
new file mode 100644
index 0000000..555b917
--- /dev/null
+++ b/include/linux/cpu_cooling.h
@@ -0,0 +1,79 @@
+/*
+ *  linux/include/linux/cpu_cooling.h
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  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 __CPU_COOLING_H__
+#define __CPU_COOLING_H__
+
+#include <linux/thermal.h>
+
+#define CPUFREQ_COOLING_START		0
+#define CPUFREQ_COOLING_STOP		1
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
+
+#ifdef CONFIG_CPU_THERMAL
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+		struct cpumask *clip_cpus);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
+#else /* !CONFIG_CPU_THERMAL */
+static inline struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+{
+	return NULL;
+}
+static inline void cpufreq_cooling_unregister(
+		struct thermal_cooling_device *cdev)
+{
+	return;
+}
+#endif	/* CONFIG_CPU_THERMAL */
+
+#endif /* __CPU_COOLING_H__ */
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	amit.kachhap, Guenter Roeck, SangWook Ju, Durgadoss, Len Brown,
	Jean Delvare, Kyungmin Park, Kukjin Kim

This patchset introduces a new generic cooling device based on cpufreq
that can be used on non-ACPI platforms.  As a proof of concept, we have
drivers for the following platforms using this mechanism now:

 * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
 * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)

There is a small change in cpufreq cooling registration APIs, so a minor
change is needed for Freescale platforms.

Brief Description:

1) The generic cooling devices code is placed inside driver/thermal/*
   as placing inside acpi folder will need un-necessary enabling of acpi
   code.  This code is architecture independent.

2) This patchset adds generic cpu cooling low level implementation
   through frequency clipping.  In future, other cpu related cooling
   devices may be added here.  An ACPI version of this already exists
   (drivers/acpi/processor_thermal.c) .But this will be useful for
   platforms like ARM using the generic thermal interface along with the
   generic cpu cooling devices.  The cooling device registration API's
   return cooling device pointers which can be easily binded with the
   thermal zone trip points.  The important APIs exposed are,

   a) struct thermal_cooling_device *cpufreq_cooling_register(
        struct cpumask *clip_cpus)
   b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)

3) Samsung exynos platform thermal implementation is done using the
   generic cpu cooling APIs and the new trip type.  The temperature sensor
   driver present in the hwmon folder(registered as hwmon driver) is moved
   to thermal folder and registered as a thermal driver.

A simple data/control flow diagrams is shown below,

Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
          |                             |
         \|/                            |
  Cpufreq cooling device <---------------

TODO:
*Will send the DT enablement patches later after the driver is merged.

This patch:

Add support for generic cpu thermal cooling low level implementations
using frequency scaling up/down based on the registration parameters.
Different cpu related cooling devices can be registered by the user and
the binding of these cooling devices to the corresponding trip points can
be easily done as the registration APIs return the cooling device pointer.
The user of these APIs are responsible for passing clipping frequency .
The drivers can also register to recieve notification about any cooling
action called.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Cc: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 Documentation/thermal/cpu-cooling-api.txt |   52 +++
 drivers/thermal/Kconfig                   |   11 +
 drivers/thermal/Makefile                  |    1 +
 drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
 include/linux/cpu_cooling.h               |   79 +++++
 5 files changed, 655 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/thermal/cpu-cooling-api.txt
 create mode 100644 drivers/thermal/cpu_cooling.c
 create mode 100644 include/linux/cpu_cooling.h

diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
new file mode 100644
index 0000000..a1f2a6b
--- /dev/null
+++ b/Documentation/thermal/cpu-cooling-api.txt
@@ -0,0 +1,52 @@
+CPU cooling APIs How To
+===================================
+
+Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
+
+Updated: 12 May 2012
+
+Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+
+0. Introduction
+
+The generic cpu cooling(freq clipping) provides registration/unregistration APIs
+to the caller. The binding of the cooling devices to the trip point is left for
+the user. The registration APIs returns the cooling device pointer.
+
+1. cpu cooling APIs
+
+1.1 cpufreq registration/unregistration APIs
+1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+
+    This interface function registers the cpufreq cooling device with the name
+    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+    cooling devices.
+
+   clip_cpus: cpumask of cpus where the frequency constraints will happen.
+
+1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+
+    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+
+    cdev: Cooling device pointer which has to be unregistered.
+
+
+1.2 CPU cooling action notifier register/unregister interface
+1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
+
+1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 7dd8c34..996003b 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -19,6 +19,17 @@ config THERMAL_HWMON
 	depends on HWMON=y || HWMON=THERMAL
 	default y
 
+config CPU_THERMAL
+	bool "generic cpu cooling support"
+	depends on THERMAL && CPU_FREQ
+	help
+	  This implements the generic cpu cooling mechanism through frequency
+	  reduction, cpu hotplug and any other ways of reducing temperature. An
+	  ACPI version of this already exists(drivers/acpi/processor_thermal.c).
+	  This will be useful for platforms using the generic thermal interface
+	  and not the ACPI interface.
+	  If you want this support, you should say Y here.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fd9369a..aae59ad 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,5 +3,6 @@
 #
 
 obj-$(CONFIG_THERMAL)		+= thermal_sys.o
+obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 0000000..c42e557
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,512 @@
+/*
+ *  linux/drivers/thermal/cpu_cooling.c
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+/**
+ * struct cpufreq_cooling_device
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ *	registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the the
+ *	egistered cooling device.
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ *	cooling	devices.
+ * @cpufreq_val: integer value representing the absolute value of the clipped
+ *	frequency.
+ * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ *
+ * This structure is required for keeping information of each
+ * cpufreq_cooling_device registered as a list whose head is represented by
+ * cooling_cpufreq_list. In order to prevent corruption of this list a
+ * mutex lock cooling_cpufreq_lock is used.
+ */
+struct cpufreq_cooling_device {
+	int id;
+	struct thermal_cooling_device *cool_dev;
+	unsigned int cpufreq_state;
+	unsigned int cpufreq_val;
+	struct cpumask allowed_cpus;
+	struct list_head node;
+};
+static LIST_HEAD(cooling_cpufreq_list);
+static DEFINE_IDR(cpufreq_idr);
+
+static struct mutex cooling_cpufreq_lock;
+
+/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
+#define NOTIFY_INVALID NULL
+struct cpufreq_cooling_device *notify_device;
+
+/* Head of the blocking notifier chain to inform about frequency clamping */
+static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+	int err;
+again:
+	if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
+		return -ENOMEM;
+
+	mutex_lock(&cooling_cpufreq_lock);
+	err = idr_get_new(idr, NULL, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	if (unlikely(err == -EAGAIN))
+		goto again;
+	else if (unlikely(err))
+		return err;
+
+	*id = *id & MAX_ID_MASK;
+	return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+	mutex_lock(&cooling_cpufreq_lock);
+	idr_remove(idr, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+}
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_register(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_register_notifier);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_unregister(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_unregister_notifier);
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
+ * @cpu: cpu for which check is needed.
+ */
+static int is_cpufreq_valid(int cpu)
+{
+	struct cpufreq_policy policy;
+	return !cpufreq_get_policy(&policy, 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
+ */
+static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
+{
+	int ret = 0, i = 0;
+	unsigned long level_index;
+	bool descend = false;
+	struct cpufreq_frequency_table *table =
+					cpufreq_frequency_get_table(cpu);
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/*check if table in ascending or descending order*/
+		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+			(table[i + 1].frequency < table[i].frequency)
+			&& !descend) {
+			descend = true;
+		}
+
+		/*return if level matched and table in descending order*/
+		if (descend && i == level)
+			return table[i].frequency;
+		i++;
+	}
+	i--;
+
+	if (level > i || descend)
+		return ret;
+	level_index = i - level;
+
+	/*Scan the table in reverse order and match the level*/
+	while (i >= 0) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		/*return if level matched*/
+		if (i == level_index)
+			return table[i].frequency;
+		i--;
+	}
+	return ret;
+}
+
+/**
+ * cpufreq_apply_cooling - function to apply frequency clipping.
+ * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
+ *	clipping data.
+ * @cooling_state: value of the cooling state.
+ */
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+				unsigned long cooling_state)
+{
+	unsigned int event, cpuid, clip_freq;
+	struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
+	unsigned int cpu = cpumask_any(maskPtr);
+
+
+	/* Check if the old cooling action is same as new cooling action */
+	if (cpufreq_device->cpufreq_state == cooling_state)
+		return 0;
+
+	clip_freq = get_cpu_frequency(cpu, cooling_state);
+	if (!clip_freq)
+		return -EINVAL;
+
+	cpufreq_device->cpufreq_state = cooling_state;
+	cpufreq_device->cpufreq_val = clip_freq;
+	notify_device = cpufreq_device;
+
+	if (cooling_state != 0)
+		event = CPUFREQ_COOLING_START;
+	else
+		event = CPUFREQ_COOLING_STOP;
+
+	blocking_notifier_call_chain(&cputherm_state_notifier_list,
+						event, &clip_freq);
+
+	for_each_cpu(cpuid, maskPtr) {
+		if (is_cpufreq_valid(cpuid))
+			cpufreq_update_policy(cpuid);
+	}
+
+	notify_device = NOTIFY_INVALID;
+
+	return 0;
+}
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb:	struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+					unsigned long event, void *data)
+{
+	struct cpufreq_policy *policy = data;
+	unsigned long max_freq = 0;
+
+	if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
+		return 0;
+
+	if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
+		max_freq = notify_device->cpufreq_val;
+
+	/* Never exceed user_policy.max*/
+	if (max_freq > policy->user_policy.max)
+		max_freq = policy->user_policy.max;
+
+	if (policy->max != max_freq)
+		cpufreq_verify_within_limits(policy, 0, max_freq);
+
+	return 0;
+}
+
+/*
+ * cpufreq cooling device callback functions are defined below
+ */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ */
+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;
+	unsigned int cpu;
+	struct cpufreq_frequency_table *table;
+
+	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;
+	}
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		i++;
+	}
+	if (i > 0) {
+		*state = --i;
+		ret = 0;
+	}
+
+return_get_max_state:
+	mutex_unlock(&cooling_cpufreq_lock);
+	return ret;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling 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);
+
+	return ret;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ */
+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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	return ret;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+	.get_max_state = cpufreq_get_max_state,
+	.get_cur_state = cpufreq_get_cur_state,
+	.set_cur_state = cpufreq_set_cur_state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+	.notifier_call = cpufreq_thermal_notifier,
+};
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+	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;
+	char dev_name[THERMAL_NAME_LENGTH];
+	int ret = 0, id = 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*/
+		if (!cpufreq_get_policy(&policy, i))
+			continue;
+		if (min == 0 && max == 0) {
+			min = policy.cpuinfo.min_freq;
+			max = policy.cpuinfo.max_freq;
+		} else {
+			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);
+	if (!cpufreq_dev)
+		return ERR_PTR(-ENOMEM);
+
+	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);
+		return ERR_PTR(-EINVAL);
+	}
+
+	sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
+
+	cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
+						&cpufreq_cooling_ops);
+	if (!cool_dev) {
+		release_idr(&cpufreq_idr, cpufreq_dev->id);
+		kfree(cpufreq_dev);
+		return ERR_PTR(-EINVAL);
+	}
+	cpufreq_dev->id = id;
+	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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+	return cool_dev;
+}
+EXPORT_SYMBOL(cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+	struct cpufreq_cooling_device *cpufreq_dev = NULL;
+	unsigned int cpufreq_dev_count = 0;
+
+	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);
+
+	/* Unregister the notifier for the last cpufreq cooling device */
+	if (cpufreq_dev_count == 1) {
+		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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
new file mode 100644
index 0000000..555b917
--- /dev/null
+++ b/include/linux/cpu_cooling.h
@@ -0,0 +1,79 @@
+/*
+ *  linux/include/linux/cpu_cooling.h
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  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 __CPU_COOLING_H__
+#define __CPU_COOLING_H__
+
+#include <linux/thermal.h>
+
+#define CPUFREQ_COOLING_START		0
+#define CPUFREQ_COOLING_STOP		1
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
+
+#ifdef CONFIG_CPU_THERMAL
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+		struct cpumask *clip_cpus);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
+#else /* !CONFIG_CPU_THERMAL */
+static inline struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+{
+	return NULL;
+}
+static inline void cpufreq_cooling_unregister(
+		struct thermal_cooling_device *cdev)
+{
+	return;
+}
+#endif	/* CONFIG_CPU_THERMAL */
+
+#endif /* __CPU_COOLING_H__ */
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 2/6] hwmon: exynos4: move thermal sensor driver to driver/thermal directory
  2012-08-16 11:53 ` [lm-sensors] " Amit Daniel Kachhap
  (?)
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park

This movement is needed because the hwmon entries and corresponding sysfs
interface is a duplicate of utilities already provided by
driver/thermal/thermal_sys.c.  The goal is to place it in thermal folder
and add necessary functions to use the in-kernel thermal interfaces.

Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 Documentation/hwmon/exynos4_tmu              |   81 ----
 Documentation/thermal/exynos_thermal         |   52 +++
 drivers/hwmon/Kconfig                        |   10 -
 drivers/hwmon/Makefile                       |    1 -
 drivers/hwmon/exynos4_tmu.c                  |  518 --------------------------
 drivers/thermal/Kconfig                      |    7 +
 drivers/thermal/Makefile                     |    1 +
 drivers/thermal/exynos_thermal.c             |  413 ++++++++++++++++++++
 include/linux/platform_data/exynos4_tmu.h    |   83 ----
 include/linux/platform_data/exynos_thermal.h |   83 ++++
 10 files changed, 556 insertions(+), 693 deletions(-)
 delete mode 100644 Documentation/hwmon/exynos4_tmu
 create mode 100644 Documentation/thermal/exynos_thermal
 delete mode 100644 drivers/hwmon/exynos4_tmu.c
 create mode 100644 drivers/thermal/exynos_thermal.c
 delete mode 100644 include/linux/platform_data/exynos4_tmu.h
 create mode 100644 include/linux/platform_data/exynos_thermal.h

diff --git a/Documentation/hwmon/exynos4_tmu b/Documentation/hwmon/exynos4_tmu
deleted file mode 100644
index c3c6b41..0000000
--- a/Documentation/hwmon/exynos4_tmu
+++ /dev/null
@@ -1,81 +0,0 @@
-Kernel driver exynos4_tmu
-=================
-
-Supported chips:
-* ARM SAMSUNG EXYNOS4 series of SoC
-  Prefix: 'exynos4-tmu'
-  Datasheet: Not publicly available
-
-Authors: Donggeun Kim <dg77.kim@samsung.com>
-
-Description
------------
-
-This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC.
-
-The chip only exposes the measured 8-bit temperature code value
-through a register.
-Temperature can be taken from the temperature code.
-There are three equations converting from temperature to temperature code.
-
-The three equations are:
-  1. Two point trimming
-	Tc = (T - 25) * (TI2 - TI1) / (85 - 25) + TI1
-
-  2. One point trimming
-	Tc = T + TI1 - 25
-
-  3. No trimming
-	Tc = T + 50
-
-  Tc: Temperature code, T: Temperature,
-  TI1: Trimming info for 25 degree Celsius (stored at TRIMINFO register)
-       Temperature code measured at 25 degree Celsius which is unchanged
-  TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register)
-       Temperature code measured at 85 degree Celsius which is unchanged
-
-TMU(Thermal Management Unit) in EXYNOS4 generates interrupt
-when temperature exceeds pre-defined levels.
-The maximum number of configurable threshold is four.
-The threshold levels are defined as follows:
-  Level_0: current temperature > trigger_level_0 + threshold
-  Level_1: current temperature > trigger_level_1 + threshold
-  Level_2: current temperature > trigger_level_2 + threshold
-  Level_3: current temperature > trigger_level_3 + threshold
-
-  The threshold and each trigger_level are set
-  through the corresponding registers.
-
-When an interrupt occurs, this driver notify user space of
-one of four threshold levels for the interrupt
-through kobject_uevent_env and sysfs_notify functions.
-Although an interrupt condition for level_0 can be set,
-it is not notified to user space through sysfs_notify function.
-
-Sysfs Interface
----------------
-name		name of the temperature sensor
-		RO
-
-temp1_input	temperature
-		RO
-
-temp1_max	temperature for level_1 interrupt
-		RO
-
-temp1_crit	temperature for level_2 interrupt
-		RO
-
-temp1_emergency	temperature for level_3 interrupt
-		RO
-
-temp1_max_alarm	alarm for level_1 interrupt
-		RO
-
-temp1_crit_alarm
-		alarm for level_2 interrupt
-		RO
-
-temp1_emergency_alarm
-		alarm for level_3 interrupt
-		RO
diff --git a/Documentation/thermal/exynos_thermal b/Documentation/thermal/exynos_thermal
new file mode 100644
index 0000000..2b46f67
--- /dev/null
+++ b/Documentation/thermal/exynos_thermal
@@ -0,0 +1,52 @@
+Kernel driver exynos4_tmu
+=================
+
+Supported chips:
+* ARM SAMSUNG EXYNOS4 series of SoC
+  Prefix: 'exynos4-tmu'
+  Datasheet: Not publicly available
+
+Authors: Donggeun Kim <dg77.kim@samsung.com>
+
+Description
+-----------
+
+This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC.
+
+The chip only exposes the measured 8-bit temperature code value
+through a register.
+Temperature can be taken from the temperature code.
+There are three equations converting from temperature to temperature code.
+
+The three equations are:
+  1. Two point trimming
+	Tc = (T - 25) * (TI2 - TI1) / (85 - 25) + TI1
+
+  2. One point trimming
+	Tc = T + TI1 - 25
+
+  3. No trimming
+	Tc = T + 50
+
+  Tc: Temperature code, T: Temperature,
+  TI1: Trimming info for 25 degree Celsius (stored at TRIMINFO register)
+       Temperature code measured at 25 degree Celsius which is unchanged
+  TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register)
+       Temperature code measured at 85 degree Celsius which is unchanged
+
+TMU(Thermal Management Unit) in EXYNOS4 generates interrupt
+when temperature exceeds pre-defined levels.
+The maximum number of configurable threshold is four.
+The threshold levels are defined as follows:
+  Level_0: current temperature > trigger_level_0 + threshold
+  Level_1: current temperature > trigger_level_1 + threshold
+  Level_2: current temperature > trigger_level_2 + threshold
+  Level_3: current temperature > trigger_level_3 + threshold
+
+  The threshold and each trigger_level are set
+  through the corresponding registers.
+
+When an interrupt occurs, this driver notify kernel thermal framework
+with the function exynos4_report_trigger.
+Although an interrupt condition for level_0 can be set,
+it can be used to synchronize the cooling action.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b0a2e4c..84e02b4 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -324,16 +324,6 @@ config SENSORS_DA9052_ADC
 	  This driver can also be built as module. If so, the module
 	  will be called da9052-hwmon.
 
-config SENSORS_EXYNOS4_TMU
-	tristate "Temperature sensor on Samsung EXYNOS4"
-	depends on ARCH_EXYNOS4
-	help
-	  If you say yes here you get support for TMU (Thermal Management
-	  Unit) on SAMSUNG EXYNOS4 series of SoC.
-
-	  This driver can also be built as a module. If so, the module
-	  will be called exynos4-tmu.
-
 config SENSORS_I5K_AMB
 	tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
 	depends on PCI && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 7aa9811..3eafe48 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -49,7 +49,6 @@ obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
 obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
 obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
 obj-$(CONFIG_SENSORS_EMC6W201)	+= emc6w201.o
-obj-$(CONFIG_SENSORS_EXYNOS4_TMU)	+= exynos4_tmu.o
 obj-$(CONFIG_SENSORS_F71805F)	+= f71805f.o
 obj-$(CONFIG_SENSORS_F71882FG)	+= f71882fg.o
 obj-$(CONFIG_SENSORS_F75375S)	+= f75375s.o
diff --git a/drivers/hwmon/exynos4_tmu.c b/drivers/hwmon/exynos4_tmu.c
deleted file mode 100644
index e912059..0000000
--- a/drivers/hwmon/exynos4_tmu.c
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * exynos4_tmu.c - Samsung EXYNOS4 TMU (Thermal Management Unit)
- *
- *  Copyright (C) 2011 Samsung Electronics
- *  Donggeun Kim <dg77.kim@samsung.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- */
-
-#include <linux/module.h>
-#include <linux/err.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/platform_device.h>
-#include <linux/interrupt.h>
-#include <linux/clk.h>
-#include <linux/workqueue.h>
-#include <linux/sysfs.h>
-#include <linux/kobject.h>
-#include <linux/io.h>
-#include <linux/mutex.h>
-
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-
-#include <linux/platform_data/exynos4_tmu.h>
-
-#define EXYNOS4_TMU_REG_TRIMINFO	0x0
-#define EXYNOS4_TMU_REG_CONTROL		0x20
-#define EXYNOS4_TMU_REG_STATUS		0x28
-#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
-#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
-#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
-#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
-#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
-#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
-#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
-#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
-#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
-#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
-#define EXYNOS4_TMU_REG_INTEN		0x70
-#define EXYNOS4_TMU_REG_INTSTAT		0x74
-#define EXYNOS4_TMU_REG_INTCLEAR	0x78
-
-#define EXYNOS4_TMU_GAIN_SHIFT		8
-#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
-
-#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
-#define EXYNOS4_TMU_CORE_ON	3
-#define EXYNOS4_TMU_CORE_OFF	2
-#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
-#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
-#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
-#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
-#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
-#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
-
-struct exynos4_tmu_data {
-	struct exynos4_tmu_platform_data *pdata;
-	struct device *hwmon_dev;
-	struct resource *mem;
-	void __iomem *base;
-	int irq;
-	struct work_struct irq_work;
-	struct mutex lock;
-	struct clk *clk;
-	u8 temp_error1, temp_error2;
-};
-
-/*
- * TMU treats temperature as a mapped temperature code.
- * The temperature is converted differently depending on the calibration type.
- */
-static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
-{
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	int temp_code;
-
-	/* temp should range between 25 and 125 */
-	if (temp < 25 || temp > 125) {
-		temp_code = -EINVAL;
-		goto out;
-	}
-
-	switch (pdata->cal_type) {
-	case TYPE_TWO_POINT_TRIMMING:
-		temp_code = (temp - 25) *
-		    (data->temp_error2 - data->temp_error1) /
-		    (85 - 25) + data->temp_error1;
-		break;
-	case TYPE_ONE_POINT_TRIMMING:
-		temp_code = temp + data->temp_error1 - 25;
-		break;
-	default:
-		temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
-		break;
-	}
-out:
-	return temp_code;
-}
-
-/*
- * Calculate a temperature value from a temperature code.
- * The unit of the temperature is degree Celsius.
- */
-static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
-{
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	int temp;
-
-	/* temp_code should range between 75 and 175 */
-	if (temp_code < 75 || temp_code > 175) {
-		temp = -ENODATA;
-		goto out;
-	}
-
-	switch (pdata->cal_type) {
-	case TYPE_TWO_POINT_TRIMMING:
-		temp = (temp_code - data->temp_error1) * (85 - 25) /
-		    (data->temp_error2 - data->temp_error1) + 25;
-		break;
-	case TYPE_ONE_POINT_TRIMMING:
-		temp = temp_code - data->temp_error1 + 25;
-		break;
-	default:
-		temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
-		break;
-	}
-out:
-	return temp;
-}
-
-static int exynos4_tmu_initialize(struct platform_device *pdev)
-{
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int status, trim_info;
-	int ret = 0, threshold_code;
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
-	if (!status) {
-		ret = -EBUSY;
-		goto out;
-	}
-
-	/* Save trimming info in order to perform calibration */
-	trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
-	data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
-	data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
-
-	/* Write temperature code for threshold */
-	threshold_code = temp_to_code(data, pdata->threshold);
-	if (threshold_code < 0) {
-		ret = threshold_code;
-		goto out;
-	}
-	writeb(threshold_code,
-		data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
-
-	writeb(pdata->trigger_levels[0],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
-	writeb(pdata->trigger_levels[1],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
-	writeb(pdata->trigger_levels[2],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
-	writeb(pdata->trigger_levels[3],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
-
-	writel(EXYNOS4_TMU_INTCLEAR_VAL,
-		data->base + EXYNOS4_TMU_REG_INTCLEAR);
-out:
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-
-	return ret;
-}
-
-static void exynos4_tmu_control(struct platform_device *pdev, bool on)
-{
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int con, interrupt_en;
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
-		pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
-	if (on) {
-		con |= EXYNOS4_TMU_CORE_ON;
-		interrupt_en = pdata->trigger_level3_en << 12 |
-			pdata->trigger_level2_en << 8 |
-			pdata->trigger_level1_en << 4 |
-			pdata->trigger_level0_en;
-	} else {
-		con |= EXYNOS4_TMU_CORE_OFF;
-		interrupt_en = 0; /* Disable all interrupts */
-	}
-	writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
-	writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
-
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-}
-
-static int exynos4_tmu_read(struct exynos4_tmu_data *data)
-{
-	u8 temp_code;
-	int temp;
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
-	temp = code_to_temp(data, temp_code);
-
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-
-	return temp;
-}
-
-static void exynos4_tmu_work(struct work_struct *work)
-{
-	struct exynos4_tmu_data *data = container_of(work,
-			struct exynos4_tmu_data, irq_work);
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
-
-	kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE);
-
-	enable_irq(data->irq);
-
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-}
-
-static irqreturn_t exynos4_tmu_irq(int irq, void *id)
-{
-	struct exynos4_tmu_data *data = id;
-
-	disable_irq_nosync(irq);
-	schedule_work(&data->irq_work);
-
-	return IRQ_HANDLED;
-}
-
-static ssize_t exynos4_tmu_show_name(struct device *dev,
-		struct device_attribute *attr, char *buf)
-{
-	return sprintf(buf, "exynos4-tmu\n");
-}
-
-static ssize_t exynos4_tmu_show_temp(struct device *dev,
-		struct device_attribute *attr, char *buf)
-{
-	struct exynos4_tmu_data *data = dev_get_drvdata(dev);
-	int ret;
-
-	ret = exynos4_tmu_read(data);
-	if (ret < 0)
-		return ret;
-
-	/* convert from degree Celsius to millidegree Celsius */
-	return sprintf(buf, "%d\n", ret * 1000);
-}
-
-static ssize_t exynos4_tmu_show_alarm(struct device *dev,
-		struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct exynos4_tmu_data *data = dev_get_drvdata(dev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	int temp;
-	unsigned int trigger_level;
-
-	temp = exynos4_tmu_read(data);
-	if (temp < 0)
-		return temp;
-
-	trigger_level = pdata->threshold + pdata->trigger_levels[attr->index];
-
-	return sprintf(buf, "%d\n", !!(temp > trigger_level));
-}
-
-static ssize_t exynos4_tmu_show_level(struct device *dev,
-		struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct exynos4_tmu_data *data = dev_get_drvdata(dev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int temp = pdata->threshold +
-			pdata->trigger_levels[attr->index];
-
-	return sprintf(buf, "%u\n", temp * 1000);
-}
-
-static DEVICE_ATTR(name, S_IRUGO, exynos4_tmu_show_name, NULL);
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, exynos4_tmu_show_temp, NULL, 0);
-
-static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
-		exynos4_tmu_show_alarm, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
-		exynos4_tmu_show_alarm, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO,
-		exynos4_tmu_show_alarm, NULL, 3);
-
-static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, exynos4_tmu_show_level, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, exynos4_tmu_show_level, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO,
-		exynos4_tmu_show_level, NULL, 3);
-
-static struct attribute *exynos4_tmu_attributes[] = {
-	&dev_attr_name.attr,
-	&sensor_dev_attr_temp1_input.dev_attr.attr,
-	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
-	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
-	&sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr,
-	&sensor_dev_attr_temp1_max.dev_attr.attr,
-	&sensor_dev_attr_temp1_crit.dev_attr.attr,
-	&sensor_dev_attr_temp1_emergency.dev_attr.attr,
-	NULL,
-};
-
-static const struct attribute_group exynos4_tmu_attr_group = {
-	.attrs = exynos4_tmu_attributes,
-};
-
-static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
-{
-	struct exynos4_tmu_data *data;
-	struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
-	int ret;
-
-	if (!pdata) {
-		dev_err(&pdev->dev, "No platform init data supplied.\n");
-		return -ENODEV;
-	}
-
-	data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
-	if (!data) {
-		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
-		return -ENOMEM;
-	}
-
-	data->irq = platform_get_irq(pdev, 0);
-	if (data->irq < 0) {
-		ret = data->irq;
-		dev_err(&pdev->dev, "Failed to get platform irq\n");
-		goto err_free;
-	}
-
-	INIT_WORK(&data->irq_work, exynos4_tmu_work);
-
-	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	if (!data->mem) {
-		ret = -ENOENT;
-		dev_err(&pdev->dev, "Failed to get platform resource\n");
-		goto err_free;
-	}
-
-	data->mem = request_mem_region(data->mem->start,
-			resource_size(data->mem), pdev->name);
-	if (!data->mem) {
-		ret = -ENODEV;
-		dev_err(&pdev->dev, "Failed to request memory region\n");
-		goto err_free;
-	}
-
-	data->base = ioremap(data->mem->start, resource_size(data->mem));
-	if (!data->base) {
-		ret = -ENODEV;
-		dev_err(&pdev->dev, "Failed to ioremap memory\n");
-		goto err_mem_region;
-	}
-
-	ret = request_irq(data->irq, exynos4_tmu_irq,
-		IRQF_TRIGGER_RISING,
-		"exynos4-tmu", data);
-	if (ret) {
-		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
-		goto err_io_remap;
-	}
-
-	data->clk = clk_get(NULL, "tmu_apbif");
-	if (IS_ERR(data->clk)) {
-		ret = PTR_ERR(data->clk);
-		dev_err(&pdev->dev, "Failed to get clock\n");
-		goto err_irq;
-	}
-
-	data->pdata = pdata;
-	platform_set_drvdata(pdev, data);
-	mutex_init(&data->lock);
-
-	ret = exynos4_tmu_initialize(pdev);
-	if (ret) {
-		dev_err(&pdev->dev, "Failed to initialize TMU\n");
-		goto err_clk;
-	}
-
-	ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
-	if (ret) {
-		dev_err(&pdev->dev, "Failed to create sysfs group\n");
-		goto err_clk;
-	}
-
-	data->hwmon_dev = hwmon_device_register(&pdev->dev);
-	if (IS_ERR(data->hwmon_dev)) {
-		ret = PTR_ERR(data->hwmon_dev);
-		dev_err(&pdev->dev, "Failed to register hwmon device\n");
-		goto err_create_group;
-	}
-
-	exynos4_tmu_control(pdev, true);
-
-	return 0;
-
-err_create_group:
-	sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
-err_clk:
-	platform_set_drvdata(pdev, NULL);
-	clk_put(data->clk);
-err_irq:
-	free_irq(data->irq, data);
-err_io_remap:
-	iounmap(data->base);
-err_mem_region:
-	release_mem_region(data->mem->start, resource_size(data->mem));
-err_free:
-	kfree(data);
-
-	return ret;
-}
-
-static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
-{
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-
-	exynos4_tmu_control(pdev, false);
-
-	hwmon_device_unregister(data->hwmon_dev);
-	sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
-
-	clk_put(data->clk);
-
-	free_irq(data->irq, data);
-
-	iounmap(data->base);
-	release_mem_region(data->mem->start, resource_size(data->mem));
-
-	platform_set_drvdata(pdev, NULL);
-
-	kfree(data);
-
-	return 0;
-}
-
-#ifdef CONFIG_PM_SLEEP
-static int exynos4_tmu_suspend(struct device *dev)
-{
-	exynos4_tmu_control(to_platform_device(dev), false);
-
-	return 0;
-}
-
-static int exynos4_tmu_resume(struct device *dev)
-{
-	struct platform_device *pdev = to_platform_device(dev);
-
-	exynos4_tmu_initialize(pdev);
-	exynos4_tmu_control(pdev, true);
-
-	return 0;
-}
-
-static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
-			 exynos4_tmu_suspend, exynos4_tmu_resume);
-#define EXYNOS4_TMU_PM	&exynos4_tmu_pm
-#else
-#define EXYNOS4_TMU_PM	NULL
-#endif
-
-static struct platform_driver exynos4_tmu_driver = {
-	.driver = {
-		.name   = "exynos4-tmu",
-		.owner  = THIS_MODULE,
-		.pm     = EXYNOS4_TMU_PM,
-	},
-	.probe = exynos4_tmu_probe,
-	.remove	= __devexit_p(exynos4_tmu_remove),
-};
-
-module_platform_driver(exynos4_tmu_driver);
-
-MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
-MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:exynos4-tmu");
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 996003b..8f2b6ea 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -46,3 +46,10 @@ config RCAR_THERMAL
 	help
 	  Enable this to plug the R-Car thermal sensor driver into the Linux
 	  thermal framework
+
+config EXYNOS_THERMAL
+	tristate "Temperature sensor on Samsung EXYNOS"
+	depends on ARCH_EXYNOS4 && THERMAL
+	help
+	  If you say yes here you get support for TMU (Thermal Managment
+	  Unit) on SAMSUNG EXYNOS series of SoC.
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index aae59ad..885550d 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_THERMAL)		+= thermal_sys.o
 obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
+obj-$(CONFIG_EXYNOS_THERMAL)		+= exynos_thermal.o
diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
new file mode 100644
index 0000000..556d15b
--- /dev/null
+++ b/drivers/thermal/exynos_thermal.c
@@ -0,0 +1,413 @@
+/*
+ * exynos_thermal.c - Samsung EXYNOS TMU (Thermal Management Unit)
+ *
+ *  Copyright (C) 2011 Samsung Electronics
+ *  Donggeun Kim <dg77.kim@samsung.com>
+ *  Amit Daniel Kachhap <amit.kachhap@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+
+#include <linux/platform_data/exynos_thermal.h>
+
+#define EXYNOS4_TMU_REG_TRIMINFO	0x0
+#define EXYNOS4_TMU_REG_CONTROL		0x20
+#define EXYNOS4_TMU_REG_STATUS		0x28
+#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
+#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
+#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
+#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
+#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
+#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
+#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
+#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
+#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
+#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
+#define EXYNOS4_TMU_REG_INTEN		0x70
+#define EXYNOS4_TMU_REG_INTSTAT		0x74
+#define EXYNOS4_TMU_REG_INTCLEAR	0x78
+
+#define EXYNOS4_TMU_GAIN_SHIFT		8
+#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
+
+#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
+#define EXYNOS4_TMU_CORE_ON	3
+#define EXYNOS4_TMU_CORE_OFF	2
+#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
+#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
+#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
+#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
+#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
+#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
+
+struct exynos4_tmu_data {
+	struct exynos4_tmu_platform_data *pdata;
+	struct resource *mem;
+	void __iomem *base;
+	int irq;
+	struct work_struct irq_work;
+	struct mutex lock;
+	struct clk *clk;
+	u8 temp_error1, temp_error2;
+};
+
+/*
+ * TMU treats temperature as a mapped temperature code.
+ * The temperature is converted differently depending on the calibration type.
+ */
+static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
+{
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	int temp_code;
+
+	/* temp should range between 25 and 125 */
+	if (temp < 25 || temp > 125) {
+		temp_code = -EINVAL;
+		goto out;
+	}
+
+	switch (pdata->cal_type) {
+	case TYPE_TWO_POINT_TRIMMING:
+		temp_code = (temp - 25) *
+		    (data->temp_error2 - data->temp_error1) /
+		    (85 - 25) + data->temp_error1;
+		break;
+	case TYPE_ONE_POINT_TRIMMING:
+		temp_code = temp + data->temp_error1 - 25;
+		break;
+	default:
+		temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		break;
+	}
+out:
+	return temp_code;
+}
+
+/*
+ * Calculate a temperature value from a temperature code.
+ * The unit of the temperature is degree Celsius.
+ */
+static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
+{
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	int temp;
+
+	/* temp_code should range between 75 and 175 */
+	if (temp_code < 75 || temp_code > 175) {
+		temp = -ENODATA;
+		goto out;
+	}
+
+	switch (pdata->cal_type) {
+	case TYPE_TWO_POINT_TRIMMING:
+		temp = (temp_code - data->temp_error1) * (85 - 25) /
+		    (data->temp_error2 - data->temp_error1) + 25;
+		break;
+	case TYPE_ONE_POINT_TRIMMING:
+		temp = temp_code - data->temp_error1 + 25;
+		break;
+	default:
+		temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		break;
+	}
+out:
+	return temp;
+}
+
+static int exynos4_tmu_initialize(struct platform_device *pdev)
+{
+	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	unsigned int status, trim_info;
+	int ret = 0, threshold_code;
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
+	if (!status) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Save trimming info in order to perform calibration */
+	trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
+	data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
+	data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
+
+	/* Write temperature code for threshold */
+	threshold_code = temp_to_code(data, pdata->threshold);
+	if (threshold_code < 0) {
+		ret = threshold_code;
+		goto out;
+	}
+	writeb(threshold_code,
+		data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
+
+	writeb(pdata->trigger_levels[0],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
+	writeb(pdata->trigger_levels[1],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
+	writeb(pdata->trigger_levels[2],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
+	writeb(pdata->trigger_levels[3],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
+
+	writel(EXYNOS4_TMU_INTCLEAR_VAL,
+		data->base + EXYNOS4_TMU_REG_INTCLEAR);
+out:
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static void exynos4_tmu_control(struct platform_device *pdev, bool on)
+{
+	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	unsigned int con, interrupt_en;
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
+		pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
+	if (on) {
+		con |= EXYNOS4_TMU_CORE_ON;
+		interrupt_en = pdata->trigger_level3_en << 12 |
+			pdata->trigger_level2_en << 8 |
+			pdata->trigger_level1_en << 4 |
+			pdata->trigger_level0_en;
+	} else {
+		con |= EXYNOS4_TMU_CORE_OFF;
+		interrupt_en = 0; /* Disable all interrupts */
+	}
+	writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
+	writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
+
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+}
+
+static int exynos4_tmu_read(struct exynos4_tmu_data *data)
+{
+	u8 temp_code;
+	int temp;
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
+	temp = code_to_temp(data, temp_code);
+
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+
+	return temp;
+}
+
+static void exynos4_tmu_work(struct work_struct *work)
+{
+	struct exynos4_tmu_data *data = container_of(work,
+			struct exynos4_tmu_data, irq_work);
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
+
+	enable_irq(data->irq);
+
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+}
+
+static irqreturn_t exynos4_tmu_irq(int irq, void *id)
+{
+	struct exynos4_tmu_data *data = id;
+
+	disable_irq_nosync(irq);
+	schedule_work(&data->irq_work);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
+{
+	struct exynos4_tmu_data *data;
+	struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
+	int ret;
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform init data supplied.\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
+	if (!data) {
+		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
+		return -ENOMEM;
+	}
+
+	data->irq = platform_get_irq(pdev, 0);
+	if (data->irq < 0) {
+		ret = data->irq;
+		dev_err(&pdev->dev, "Failed to get platform irq\n");
+		goto err_free;
+	}
+
+	INIT_WORK(&data->irq_work, exynos4_tmu_work);
+
+	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!data->mem) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get platform resource\n");
+		goto err_free;
+	}
+
+	data->mem = request_mem_region(data->mem->start,
+			resource_size(data->mem), pdev->name);
+	if (!data->mem) {
+		ret = -ENODEV;
+		dev_err(&pdev->dev, "Failed to request memory region\n");
+		goto err_free;
+	}
+
+	data->base = ioremap(data->mem->start, resource_size(data->mem));
+	if (!data->base) {
+		ret = -ENODEV;
+		dev_err(&pdev->dev, "Failed to ioremap memory\n");
+		goto err_mem_region;
+	}
+
+	ret = request_irq(data->irq, exynos4_tmu_irq,
+		IRQF_TRIGGER_RISING,
+		"exynos4-tmu", data);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
+		goto err_io_remap;
+	}
+
+	data->clk = clk_get(NULL, "tmu_apbif");
+	if (IS_ERR(data->clk)) {
+		ret = PTR_ERR(data->clk);
+		dev_err(&pdev->dev, "Failed to get clock\n");
+		goto err_irq;
+	}
+
+	data->pdata = pdata;
+	platform_set_drvdata(pdev, data);
+	mutex_init(&data->lock);
+
+	ret = exynos4_tmu_initialize(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to initialize TMU\n");
+		goto err_clk;
+	}
+
+	exynos4_tmu_control(pdev, true);
+
+	return 0;
+err_clk:
+	platform_set_drvdata(pdev, NULL);
+	clk_put(data->clk);
+err_irq:
+	free_irq(data->irq, data);
+err_io_remap:
+	iounmap(data->base);
+err_mem_region:
+	release_mem_region(data->mem->start, resource_size(data->mem));
+err_free:
+	kfree(data);
+
+	return ret;
+}
+
+static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
+{
+	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+
+	exynos4_tmu_control(pdev, false);
+
+	clk_put(data->clk);
+
+	free_irq(data->irq, data);
+
+	iounmap(data->base);
+	release_mem_region(data->mem->start, resource_size(data->mem));
+
+	platform_set_drvdata(pdev, NULL);
+
+	kfree(data);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos4_tmu_suspend(struct device *dev)
+{
+	exynos4_tmu_control(to_platform_device(dev), false);
+
+	return 0;
+}
+
+static int exynos4_tmu_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	exynos4_tmu_initialize(pdev);
+	exynos4_tmu_control(pdev, true);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
+			 exynos4_tmu_suspend, exynos4_tmu_resume);
+#define EXYNOS4_TMU_PM	(&exynos4_tmu_pm)
+#else
+#define EXYNOS4_TMU_PM	NULL
+#endif
+
+static struct platform_driver exynos4_tmu_driver = {
+	.driver = {
+		.name   = "exynos4-tmu",
+		.owner  = THIS_MODULE,
+		.pm     = EXYNOS4_TMU_PM,
+	},
+	.probe = exynos4_tmu_probe,
+	.remove	= __devexit_p(exynos4_tmu_remove),
+};
+
+module_platform_driver(exynos4_tmu_driver);
+
+MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
+MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:exynos4-tmu");
diff --git a/include/linux/platform_data/exynos4_tmu.h b/include/linux/platform_data/exynos4_tmu.h
deleted file mode 100644
index 39e038c..0000000
--- a/include/linux/platform_data/exynos4_tmu.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * exynos4_tmu.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
- *
- *  Copyright (C) 2011 Samsung Electronics
- *  Donggeun Kim <dg77.kim@samsung.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#ifndef _LINUX_EXYNOS4_TMU_H
-#define _LINUX_EXYNOS4_TMU_H
-
-enum calibration_type {
-	TYPE_ONE_POINT_TRIMMING,
-	TYPE_TWO_POINT_TRIMMING,
-	TYPE_NONE,
-};
-
-/**
- * struct exynos4_tmu_platform_data
- * @threshold: basic temperature for generating interrupt
- *	       25 <= threshold <= 125 [unit: degree Celsius]
- * @trigger_levels: array for each interrupt levels
- *	[unit: degree Celsius]
- *	0: temperature for trigger_level0 interrupt
- *	   condition for trigger_level0 interrupt:
- *		current temperature > threshold + trigger_levels[0]
- *	1: temperature for trigger_level1 interrupt
- *	   condition for trigger_level1 interrupt:
- *		current temperature > threshold + trigger_levels[1]
- *	2: temperature for trigger_level2 interrupt
- *	   condition for trigger_level2 interrupt:
- *		current temperature > threshold + trigger_levels[2]
- *	3: temperature for trigger_level3 interrupt
- *	   condition for trigger_level3 interrupt:
- *		current temperature > threshold + trigger_levels[3]
- * @trigger_level0_en:
- *	1 = enable trigger_level0 interrupt,
- *	0 = disable trigger_level0 interrupt
- * @trigger_level1_en:
- *	1 = enable trigger_level1 interrupt,
- *	0 = disable trigger_level1 interrupt
- * @trigger_level2_en:
- *	1 = enable trigger_level2 interrupt,
- *	0 = disable trigger_level2 interrupt
- * @trigger_level3_en:
- *	1 = enable trigger_level3 interrupt,
- *	0 = disable trigger_level3 interrupt
- * @gain: gain of amplifier in the positive-TC generator block
- *	0 <= gain <= 15
- * @reference_voltage: reference voltage of amplifier
- *	in the positive-TC generator block
- *	0 <= reference_voltage <= 31
- * @cal_type: calibration type for temperature
- *
- * This structure is required for configuration of exynos4_tmu driver.
- */
-struct exynos4_tmu_platform_data {
-	u8 threshold;
-	u8 trigger_levels[4];
-	bool trigger_level0_en;
-	bool trigger_level1_en;
-	bool trigger_level2_en;
-	bool trigger_level3_en;
-
-	u8 gain;
-	u8 reference_voltage;
-
-	enum calibration_type cal_type;
-};
-#endif /* _LINUX_EXYNOS4_TMU_H */
diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h
new file mode 100644
index 0000000..d6c3f93
--- /dev/null
+++ b/include/linux/platform_data/exynos_thermal.h
@@ -0,0 +1,83 @@
+/*
+ * exynos_thermal.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
+ *
+ *  Copyright (C) 2011 Samsung Electronics
+ *  Donggeun Kim <dg77.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _LINUX_EXYNOS_THERMAL_H
+#define _LINUX_EXYNOS_THERMAL_H
+
+enum calibration_type {
+	TYPE_ONE_POINT_TRIMMING,
+	TYPE_TWO_POINT_TRIMMING,
+	TYPE_NONE,
+};
+
+/**
+ * struct exynos4_tmu_platform_data
+ * @threshold: basic temperature for generating interrupt
+ *	       25 <= threshold <= 125 [unit: degree Celsius]
+ * @trigger_levels: array for each interrupt levels
+ *	[unit: degree Celsius]
+ *	0: temperature for trigger_level0 interrupt
+ *	   condition for trigger_level0 interrupt:
+ *		current temperature > threshold + trigger_levels[0]
+ *	1: temperature for trigger_level1 interrupt
+ *	   condition for trigger_level1 interrupt:
+ *		current temperature > threshold + trigger_levels[1]
+ *	2: temperature for trigger_level2 interrupt
+ *	   condition for trigger_level2 interrupt:
+ *		current temperature > threshold + trigger_levels[2]
+ *	3: temperature for trigger_level3 interrupt
+ *	   condition for trigger_level3 interrupt:
+ *		current temperature > threshold + trigger_levels[3]
+ * @trigger_level0_en:
+ *	1 = enable trigger_level0 interrupt,
+ *	0 = disable trigger_level0 interrupt
+ * @trigger_level1_en:
+ *	1 = enable trigger_level1 interrupt,
+ *	0 = disable trigger_level1 interrupt
+ * @trigger_level2_en:
+ *	1 = enable trigger_level2 interrupt,
+ *	0 = disable trigger_level2 interrupt
+ * @trigger_level3_en:
+ *	1 = enable trigger_level3 interrupt,
+ *	0 = disable trigger_level3 interrupt
+ * @gain: gain of amplifier in the positive-TC generator block
+ *	0 <= gain <= 15
+ * @reference_voltage: reference voltage of amplifier
+ *	in the positive-TC generator block
+ *	0 <= reference_voltage <= 31
+ * @cal_type: calibration type for temperature
+ *
+ * This structure is required for configuration of exynos4_tmu driver.
+ */
+struct exynos4_tmu_platform_data {
+	u8 threshold;
+	u8 trigger_levels[4];
+	bool trigger_level0_en;
+	bool trigger_level1_en;
+	bool trigger_level2_en;
+	bool trigger_level3_en;
+
+	u8 gain;
+	u8 reference_voltage;
+
+	enum calibration_type cal_type;
+};
+#endif /* _LINUX_EXYNOS_THERMAL_H */
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 2/6] hwmon: exynos4: move thermal sensor driver to driver/thermal directory
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	amit.kachhap, SangWook Ju, Durgadoss, Len Brown, Jean Delvare,
	Kyungmin Park, Kukjin Kim

This movement is needed because the hwmon entries and corresponding sysfs
interface is a duplicate of utilities already provided by
driver/thermal/thermal_sys.c.  The goal is to place it in thermal folder
and add necessary functions to use the in-kernel thermal interfaces.

Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 Documentation/hwmon/exynos4_tmu              |   81 ----
 Documentation/thermal/exynos_thermal         |   52 +++
 drivers/hwmon/Kconfig                        |   10 -
 drivers/hwmon/Makefile                       |    1 -
 drivers/hwmon/exynos4_tmu.c                  |  518 --------------------------
 drivers/thermal/Kconfig                      |    7 +
 drivers/thermal/Makefile                     |    1 +
 drivers/thermal/exynos_thermal.c             |  413 ++++++++++++++++++++
 include/linux/platform_data/exynos4_tmu.h    |   83 ----
 include/linux/platform_data/exynos_thermal.h |   83 ++++
 10 files changed, 556 insertions(+), 693 deletions(-)
 delete mode 100644 Documentation/hwmon/exynos4_tmu
 create mode 100644 Documentation/thermal/exynos_thermal
 delete mode 100644 drivers/hwmon/exynos4_tmu.c
 create mode 100644 drivers/thermal/exynos_thermal.c
 delete mode 100644 include/linux/platform_data/exynos4_tmu.h
 create mode 100644 include/linux/platform_data/exynos_thermal.h

diff --git a/Documentation/hwmon/exynos4_tmu b/Documentation/hwmon/exynos4_tmu
deleted file mode 100644
index c3c6b41..0000000
--- a/Documentation/hwmon/exynos4_tmu
+++ /dev/null
@@ -1,81 +0,0 @@
-Kernel driver exynos4_tmu
-=================
-
-Supported chips:
-* ARM SAMSUNG EXYNOS4 series of SoC
-  Prefix: 'exynos4-tmu'
-  Datasheet: Not publicly available
-
-Authors: Donggeun Kim <dg77.kim@samsung.com>
-
-Description
------------
-
-This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC.
-
-The chip only exposes the measured 8-bit temperature code value
-through a register.
-Temperature can be taken from the temperature code.
-There are three equations converting from temperature to temperature code.
-
-The three equations are:
-  1. Two point trimming
-	Tc = (T - 25) * (TI2 - TI1) / (85 - 25) + TI1
-
-  2. One point trimming
-	Tc = T + TI1 - 25
-
-  3. No trimming
-	Tc = T + 50
-
-  Tc: Temperature code, T: Temperature,
-  TI1: Trimming info for 25 degree Celsius (stored at TRIMINFO register)
-       Temperature code measured at 25 degree Celsius which is unchanged
-  TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register)
-       Temperature code measured at 85 degree Celsius which is unchanged
-
-TMU(Thermal Management Unit) in EXYNOS4 generates interrupt
-when temperature exceeds pre-defined levels.
-The maximum number of configurable threshold is four.
-The threshold levels are defined as follows:
-  Level_0: current temperature > trigger_level_0 + threshold
-  Level_1: current temperature > trigger_level_1 + threshold
-  Level_2: current temperature > trigger_level_2 + threshold
-  Level_3: current temperature > trigger_level_3 + threshold
-
-  The threshold and each trigger_level are set
-  through the corresponding registers.
-
-When an interrupt occurs, this driver notify user space of
-one of four threshold levels for the interrupt
-through kobject_uevent_env and sysfs_notify functions.
-Although an interrupt condition for level_0 can be set,
-it is not notified to user space through sysfs_notify function.
-
-Sysfs Interface
----------------
-name		name of the temperature sensor
-		RO
-
-temp1_input	temperature
-		RO
-
-temp1_max	temperature for level_1 interrupt
-		RO
-
-temp1_crit	temperature for level_2 interrupt
-		RO
-
-temp1_emergency	temperature for level_3 interrupt
-		RO
-
-temp1_max_alarm	alarm for level_1 interrupt
-		RO
-
-temp1_crit_alarm
-		alarm for level_2 interrupt
-		RO
-
-temp1_emergency_alarm
-		alarm for level_3 interrupt
-		RO
diff --git a/Documentation/thermal/exynos_thermal b/Documentation/thermal/exynos_thermal
new file mode 100644
index 0000000..2b46f67
--- /dev/null
+++ b/Documentation/thermal/exynos_thermal
@@ -0,0 +1,52 @@
+Kernel driver exynos4_tmu
+=================
+
+Supported chips:
+* ARM SAMSUNG EXYNOS4 series of SoC
+  Prefix: 'exynos4-tmu'
+  Datasheet: Not publicly available
+
+Authors: Donggeun Kim <dg77.kim@samsung.com>
+
+Description
+-----------
+
+This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC.
+
+The chip only exposes the measured 8-bit temperature code value
+through a register.
+Temperature can be taken from the temperature code.
+There are three equations converting from temperature to temperature code.
+
+The three equations are:
+  1. Two point trimming
+	Tc = (T - 25) * (TI2 - TI1) / (85 - 25) + TI1
+
+  2. One point trimming
+	Tc = T + TI1 - 25
+
+  3. No trimming
+	Tc = T + 50
+
+  Tc: Temperature code, T: Temperature,
+  TI1: Trimming info for 25 degree Celsius (stored at TRIMINFO register)
+       Temperature code measured at 25 degree Celsius which is unchanged
+  TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register)
+       Temperature code measured at 85 degree Celsius which is unchanged
+
+TMU(Thermal Management Unit) in EXYNOS4 generates interrupt
+when temperature exceeds pre-defined levels.
+The maximum number of configurable threshold is four.
+The threshold levels are defined as follows:
+  Level_0: current temperature > trigger_level_0 + threshold
+  Level_1: current temperature > trigger_level_1 + threshold
+  Level_2: current temperature > trigger_level_2 + threshold
+  Level_3: current temperature > trigger_level_3 + threshold
+
+  The threshold and each trigger_level are set
+  through the corresponding registers.
+
+When an interrupt occurs, this driver notify kernel thermal framework
+with the function exynos4_report_trigger.
+Although an interrupt condition for level_0 can be set,
+it can be used to synchronize the cooling action.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b0a2e4c..84e02b4 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -324,16 +324,6 @@ config SENSORS_DA9052_ADC
 	  This driver can also be built as module. If so, the module
 	  will be called da9052-hwmon.
 
-config SENSORS_EXYNOS4_TMU
-	tristate "Temperature sensor on Samsung EXYNOS4"
-	depends on ARCH_EXYNOS4
-	help
-	  If you say yes here you get support for TMU (Thermal Management
-	  Unit) on SAMSUNG EXYNOS4 series of SoC.
-
-	  This driver can also be built as a module. If so, the module
-	  will be called exynos4-tmu.
-
 config SENSORS_I5K_AMB
 	tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
 	depends on PCI && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 7aa9811..3eafe48 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -49,7 +49,6 @@ obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
 obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
 obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
 obj-$(CONFIG_SENSORS_EMC6W201)	+= emc6w201.o
-obj-$(CONFIG_SENSORS_EXYNOS4_TMU)	+= exynos4_tmu.o
 obj-$(CONFIG_SENSORS_F71805F)	+= f71805f.o
 obj-$(CONFIG_SENSORS_F71882FG)	+= f71882fg.o
 obj-$(CONFIG_SENSORS_F75375S)	+= f75375s.o
diff --git a/drivers/hwmon/exynos4_tmu.c b/drivers/hwmon/exynos4_tmu.c
deleted file mode 100644
index e912059..0000000
--- a/drivers/hwmon/exynos4_tmu.c
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * exynos4_tmu.c - Samsung EXYNOS4 TMU (Thermal Management Unit)
- *
- *  Copyright (C) 2011 Samsung Electronics
- *  Donggeun Kim <dg77.kim@samsung.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- */
-
-#include <linux/module.h>
-#include <linux/err.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/platform_device.h>
-#include <linux/interrupt.h>
-#include <linux/clk.h>
-#include <linux/workqueue.h>
-#include <linux/sysfs.h>
-#include <linux/kobject.h>
-#include <linux/io.h>
-#include <linux/mutex.h>
-
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-
-#include <linux/platform_data/exynos4_tmu.h>
-
-#define EXYNOS4_TMU_REG_TRIMINFO	0x0
-#define EXYNOS4_TMU_REG_CONTROL		0x20
-#define EXYNOS4_TMU_REG_STATUS		0x28
-#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
-#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
-#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
-#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
-#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
-#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
-#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
-#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
-#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
-#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
-#define EXYNOS4_TMU_REG_INTEN		0x70
-#define EXYNOS4_TMU_REG_INTSTAT		0x74
-#define EXYNOS4_TMU_REG_INTCLEAR	0x78
-
-#define EXYNOS4_TMU_GAIN_SHIFT		8
-#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
-
-#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
-#define EXYNOS4_TMU_CORE_ON	3
-#define EXYNOS4_TMU_CORE_OFF	2
-#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
-#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
-#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
-#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
-#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
-#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
-
-struct exynos4_tmu_data {
-	struct exynos4_tmu_platform_data *pdata;
-	struct device *hwmon_dev;
-	struct resource *mem;
-	void __iomem *base;
-	int irq;
-	struct work_struct irq_work;
-	struct mutex lock;
-	struct clk *clk;
-	u8 temp_error1, temp_error2;
-};
-
-/*
- * TMU treats temperature as a mapped temperature code.
- * The temperature is converted differently depending on the calibration type.
- */
-static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
-{
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	int temp_code;
-
-	/* temp should range between 25 and 125 */
-	if (temp < 25 || temp > 125) {
-		temp_code = -EINVAL;
-		goto out;
-	}
-
-	switch (pdata->cal_type) {
-	case TYPE_TWO_POINT_TRIMMING:
-		temp_code = (temp - 25) *
-		    (data->temp_error2 - data->temp_error1) /
-		    (85 - 25) + data->temp_error1;
-		break;
-	case TYPE_ONE_POINT_TRIMMING:
-		temp_code = temp + data->temp_error1 - 25;
-		break;
-	default:
-		temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
-		break;
-	}
-out:
-	return temp_code;
-}
-
-/*
- * Calculate a temperature value from a temperature code.
- * The unit of the temperature is degree Celsius.
- */
-static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
-{
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	int temp;
-
-	/* temp_code should range between 75 and 175 */
-	if (temp_code < 75 || temp_code > 175) {
-		temp = -ENODATA;
-		goto out;
-	}
-
-	switch (pdata->cal_type) {
-	case TYPE_TWO_POINT_TRIMMING:
-		temp = (temp_code - data->temp_error1) * (85 - 25) /
-		    (data->temp_error2 - data->temp_error1) + 25;
-		break;
-	case TYPE_ONE_POINT_TRIMMING:
-		temp = temp_code - data->temp_error1 + 25;
-		break;
-	default:
-		temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
-		break;
-	}
-out:
-	return temp;
-}
-
-static int exynos4_tmu_initialize(struct platform_device *pdev)
-{
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int status, trim_info;
-	int ret = 0, threshold_code;
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
-	if (!status) {
-		ret = -EBUSY;
-		goto out;
-	}
-
-	/* Save trimming info in order to perform calibration */
-	trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
-	data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
-	data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
-
-	/* Write temperature code for threshold */
-	threshold_code = temp_to_code(data, pdata->threshold);
-	if (threshold_code < 0) {
-		ret = threshold_code;
-		goto out;
-	}
-	writeb(threshold_code,
-		data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
-
-	writeb(pdata->trigger_levels[0],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
-	writeb(pdata->trigger_levels[1],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
-	writeb(pdata->trigger_levels[2],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
-	writeb(pdata->trigger_levels[3],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
-
-	writel(EXYNOS4_TMU_INTCLEAR_VAL,
-		data->base + EXYNOS4_TMU_REG_INTCLEAR);
-out:
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-
-	return ret;
-}
-
-static void exynos4_tmu_control(struct platform_device *pdev, bool on)
-{
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int con, interrupt_en;
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
-		pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
-	if (on) {
-		con |= EXYNOS4_TMU_CORE_ON;
-		interrupt_en = pdata->trigger_level3_en << 12 |
-			pdata->trigger_level2_en << 8 |
-			pdata->trigger_level1_en << 4 |
-			pdata->trigger_level0_en;
-	} else {
-		con |= EXYNOS4_TMU_CORE_OFF;
-		interrupt_en = 0; /* Disable all interrupts */
-	}
-	writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
-	writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
-
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-}
-
-static int exynos4_tmu_read(struct exynos4_tmu_data *data)
-{
-	u8 temp_code;
-	int temp;
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
-	temp = code_to_temp(data, temp_code);
-
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-
-	return temp;
-}
-
-static void exynos4_tmu_work(struct work_struct *work)
-{
-	struct exynos4_tmu_data *data = container_of(work,
-			struct exynos4_tmu_data, irq_work);
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
-
-	kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE);
-
-	enable_irq(data->irq);
-
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-}
-
-static irqreturn_t exynos4_tmu_irq(int irq, void *id)
-{
-	struct exynos4_tmu_data *data = id;
-
-	disable_irq_nosync(irq);
-	schedule_work(&data->irq_work);
-
-	return IRQ_HANDLED;
-}
-
-static ssize_t exynos4_tmu_show_name(struct device *dev,
-		struct device_attribute *attr, char *buf)
-{
-	return sprintf(buf, "exynos4-tmu\n");
-}
-
-static ssize_t exynos4_tmu_show_temp(struct device *dev,
-		struct device_attribute *attr, char *buf)
-{
-	struct exynos4_tmu_data *data = dev_get_drvdata(dev);
-	int ret;
-
-	ret = exynos4_tmu_read(data);
-	if (ret < 0)
-		return ret;
-
-	/* convert from degree Celsius to millidegree Celsius */
-	return sprintf(buf, "%d\n", ret * 1000);
-}
-
-static ssize_t exynos4_tmu_show_alarm(struct device *dev,
-		struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct exynos4_tmu_data *data = dev_get_drvdata(dev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	int temp;
-	unsigned int trigger_level;
-
-	temp = exynos4_tmu_read(data);
-	if (temp < 0)
-		return temp;
-
-	trigger_level = pdata->threshold + pdata->trigger_levels[attr->index];
-
-	return sprintf(buf, "%d\n", !!(temp > trigger_level));
-}
-
-static ssize_t exynos4_tmu_show_level(struct device *dev,
-		struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct exynos4_tmu_data *data = dev_get_drvdata(dev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int temp = pdata->threshold +
-			pdata->trigger_levels[attr->index];
-
-	return sprintf(buf, "%u\n", temp * 1000);
-}
-
-static DEVICE_ATTR(name, S_IRUGO, exynos4_tmu_show_name, NULL);
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, exynos4_tmu_show_temp, NULL, 0);
-
-static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
-		exynos4_tmu_show_alarm, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
-		exynos4_tmu_show_alarm, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO,
-		exynos4_tmu_show_alarm, NULL, 3);
-
-static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, exynos4_tmu_show_level, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, exynos4_tmu_show_level, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO,
-		exynos4_tmu_show_level, NULL, 3);
-
-static struct attribute *exynos4_tmu_attributes[] = {
-	&dev_attr_name.attr,
-	&sensor_dev_attr_temp1_input.dev_attr.attr,
-	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
-	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
-	&sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr,
-	&sensor_dev_attr_temp1_max.dev_attr.attr,
-	&sensor_dev_attr_temp1_crit.dev_attr.attr,
-	&sensor_dev_attr_temp1_emergency.dev_attr.attr,
-	NULL,
-};
-
-static const struct attribute_group exynos4_tmu_attr_group = {
-	.attrs = exynos4_tmu_attributes,
-};
-
-static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
-{
-	struct exynos4_tmu_data *data;
-	struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
-	int ret;
-
-	if (!pdata) {
-		dev_err(&pdev->dev, "No platform init data supplied.\n");
-		return -ENODEV;
-	}
-
-	data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
-	if (!data) {
-		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
-		return -ENOMEM;
-	}
-
-	data->irq = platform_get_irq(pdev, 0);
-	if (data->irq < 0) {
-		ret = data->irq;
-		dev_err(&pdev->dev, "Failed to get platform irq\n");
-		goto err_free;
-	}
-
-	INIT_WORK(&data->irq_work, exynos4_tmu_work);
-
-	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	if (!data->mem) {
-		ret = -ENOENT;
-		dev_err(&pdev->dev, "Failed to get platform resource\n");
-		goto err_free;
-	}
-
-	data->mem = request_mem_region(data->mem->start,
-			resource_size(data->mem), pdev->name);
-	if (!data->mem) {
-		ret = -ENODEV;
-		dev_err(&pdev->dev, "Failed to request memory region\n");
-		goto err_free;
-	}
-
-	data->base = ioremap(data->mem->start, resource_size(data->mem));
-	if (!data->base) {
-		ret = -ENODEV;
-		dev_err(&pdev->dev, "Failed to ioremap memory\n");
-		goto err_mem_region;
-	}
-
-	ret = request_irq(data->irq, exynos4_tmu_irq,
-		IRQF_TRIGGER_RISING,
-		"exynos4-tmu", data);
-	if (ret) {
-		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
-		goto err_io_remap;
-	}
-
-	data->clk = clk_get(NULL, "tmu_apbif");
-	if (IS_ERR(data->clk)) {
-		ret = PTR_ERR(data->clk);
-		dev_err(&pdev->dev, "Failed to get clock\n");
-		goto err_irq;
-	}
-
-	data->pdata = pdata;
-	platform_set_drvdata(pdev, data);
-	mutex_init(&data->lock);
-
-	ret = exynos4_tmu_initialize(pdev);
-	if (ret) {
-		dev_err(&pdev->dev, "Failed to initialize TMU\n");
-		goto err_clk;
-	}
-
-	ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
-	if (ret) {
-		dev_err(&pdev->dev, "Failed to create sysfs group\n");
-		goto err_clk;
-	}
-
-	data->hwmon_dev = hwmon_device_register(&pdev->dev);
-	if (IS_ERR(data->hwmon_dev)) {
-		ret = PTR_ERR(data->hwmon_dev);
-		dev_err(&pdev->dev, "Failed to register hwmon device\n");
-		goto err_create_group;
-	}
-
-	exynos4_tmu_control(pdev, true);
-
-	return 0;
-
-err_create_group:
-	sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
-err_clk:
-	platform_set_drvdata(pdev, NULL);
-	clk_put(data->clk);
-err_irq:
-	free_irq(data->irq, data);
-err_io_remap:
-	iounmap(data->base);
-err_mem_region:
-	release_mem_region(data->mem->start, resource_size(data->mem));
-err_free:
-	kfree(data);
-
-	return ret;
-}
-
-static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
-{
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-
-	exynos4_tmu_control(pdev, false);
-
-	hwmon_device_unregister(data->hwmon_dev);
-	sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
-
-	clk_put(data->clk);
-
-	free_irq(data->irq, data);
-
-	iounmap(data->base);
-	release_mem_region(data->mem->start, resource_size(data->mem));
-
-	platform_set_drvdata(pdev, NULL);
-
-	kfree(data);
-
-	return 0;
-}
-
-#ifdef CONFIG_PM_SLEEP
-static int exynos4_tmu_suspend(struct device *dev)
-{
-	exynos4_tmu_control(to_platform_device(dev), false);
-
-	return 0;
-}
-
-static int exynos4_tmu_resume(struct device *dev)
-{
-	struct platform_device *pdev = to_platform_device(dev);
-
-	exynos4_tmu_initialize(pdev);
-	exynos4_tmu_control(pdev, true);
-
-	return 0;
-}
-
-static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
-			 exynos4_tmu_suspend, exynos4_tmu_resume);
-#define EXYNOS4_TMU_PM	&exynos4_tmu_pm
-#else
-#define EXYNOS4_TMU_PM	NULL
-#endif
-
-static struct platform_driver exynos4_tmu_driver = {
-	.driver = {
-		.name   = "exynos4-tmu",
-		.owner  = THIS_MODULE,
-		.pm     = EXYNOS4_TMU_PM,
-	},
-	.probe = exynos4_tmu_probe,
-	.remove	= __devexit_p(exynos4_tmu_remove),
-};
-
-module_platform_driver(exynos4_tmu_driver);
-
-MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
-MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:exynos4-tmu");
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 996003b..8f2b6ea 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -46,3 +46,10 @@ config RCAR_THERMAL
 	help
 	  Enable this to plug the R-Car thermal sensor driver into the Linux
 	  thermal framework
+
+config EXYNOS_THERMAL
+	tristate "Temperature sensor on Samsung EXYNOS"
+	depends on ARCH_EXYNOS4 && THERMAL
+	help
+	  If you say yes here you get support for TMU (Thermal Managment
+	  Unit) on SAMSUNG EXYNOS series of SoC.
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index aae59ad..885550d 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_THERMAL)		+= thermal_sys.o
 obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
+obj-$(CONFIG_EXYNOS_THERMAL)		+= exynos_thermal.o
diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
new file mode 100644
index 0000000..556d15b
--- /dev/null
+++ b/drivers/thermal/exynos_thermal.c
@@ -0,0 +1,413 @@
+/*
+ * exynos_thermal.c - Samsung EXYNOS TMU (Thermal Management Unit)
+ *
+ *  Copyright (C) 2011 Samsung Electronics
+ *  Donggeun Kim <dg77.kim@samsung.com>
+ *  Amit Daniel Kachhap <amit.kachhap@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+
+#include <linux/platform_data/exynos_thermal.h>
+
+#define EXYNOS4_TMU_REG_TRIMINFO	0x0
+#define EXYNOS4_TMU_REG_CONTROL		0x20
+#define EXYNOS4_TMU_REG_STATUS		0x28
+#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
+#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
+#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
+#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
+#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
+#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
+#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
+#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
+#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
+#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
+#define EXYNOS4_TMU_REG_INTEN		0x70
+#define EXYNOS4_TMU_REG_INTSTAT		0x74
+#define EXYNOS4_TMU_REG_INTCLEAR	0x78
+
+#define EXYNOS4_TMU_GAIN_SHIFT		8
+#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
+
+#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
+#define EXYNOS4_TMU_CORE_ON	3
+#define EXYNOS4_TMU_CORE_OFF	2
+#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
+#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
+#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
+#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
+#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
+#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
+
+struct exynos4_tmu_data {
+	struct exynos4_tmu_platform_data *pdata;
+	struct resource *mem;
+	void __iomem *base;
+	int irq;
+	struct work_struct irq_work;
+	struct mutex lock;
+	struct clk *clk;
+	u8 temp_error1, temp_error2;
+};
+
+/*
+ * TMU treats temperature as a mapped temperature code.
+ * The temperature is converted differently depending on the calibration type.
+ */
+static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
+{
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	int temp_code;
+
+	/* temp should range between 25 and 125 */
+	if (temp < 25 || temp > 125) {
+		temp_code = -EINVAL;
+		goto out;
+	}
+
+	switch (pdata->cal_type) {
+	case TYPE_TWO_POINT_TRIMMING:
+		temp_code = (temp - 25) *
+		    (data->temp_error2 - data->temp_error1) /
+		    (85 - 25) + data->temp_error1;
+		break;
+	case TYPE_ONE_POINT_TRIMMING:
+		temp_code = temp + data->temp_error1 - 25;
+		break;
+	default:
+		temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		break;
+	}
+out:
+	return temp_code;
+}
+
+/*
+ * Calculate a temperature value from a temperature code.
+ * The unit of the temperature is degree Celsius.
+ */
+static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
+{
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	int temp;
+
+	/* temp_code should range between 75 and 175 */
+	if (temp_code < 75 || temp_code > 175) {
+		temp = -ENODATA;
+		goto out;
+	}
+
+	switch (pdata->cal_type) {
+	case TYPE_TWO_POINT_TRIMMING:
+		temp = (temp_code - data->temp_error1) * (85 - 25) /
+		    (data->temp_error2 - data->temp_error1) + 25;
+		break;
+	case TYPE_ONE_POINT_TRIMMING:
+		temp = temp_code - data->temp_error1 + 25;
+		break;
+	default:
+		temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		break;
+	}
+out:
+	return temp;
+}
+
+static int exynos4_tmu_initialize(struct platform_device *pdev)
+{
+	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	unsigned int status, trim_info;
+	int ret = 0, threshold_code;
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
+	if (!status) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Save trimming info in order to perform calibration */
+	trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
+	data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
+	data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
+
+	/* Write temperature code for threshold */
+	threshold_code = temp_to_code(data, pdata->threshold);
+	if (threshold_code < 0) {
+		ret = threshold_code;
+		goto out;
+	}
+	writeb(threshold_code,
+		data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
+
+	writeb(pdata->trigger_levels[0],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
+	writeb(pdata->trigger_levels[1],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
+	writeb(pdata->trigger_levels[2],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
+	writeb(pdata->trigger_levels[3],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
+
+	writel(EXYNOS4_TMU_INTCLEAR_VAL,
+		data->base + EXYNOS4_TMU_REG_INTCLEAR);
+out:
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static void exynos4_tmu_control(struct platform_device *pdev, bool on)
+{
+	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	unsigned int con, interrupt_en;
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
+		pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
+	if (on) {
+		con |= EXYNOS4_TMU_CORE_ON;
+		interrupt_en = pdata->trigger_level3_en << 12 |
+			pdata->trigger_level2_en << 8 |
+			pdata->trigger_level1_en << 4 |
+			pdata->trigger_level0_en;
+	} else {
+		con |= EXYNOS4_TMU_CORE_OFF;
+		interrupt_en = 0; /* Disable all interrupts */
+	}
+	writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
+	writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
+
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+}
+
+static int exynos4_tmu_read(struct exynos4_tmu_data *data)
+{
+	u8 temp_code;
+	int temp;
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
+	temp = code_to_temp(data, temp_code);
+
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+
+	return temp;
+}
+
+static void exynos4_tmu_work(struct work_struct *work)
+{
+	struct exynos4_tmu_data *data = container_of(work,
+			struct exynos4_tmu_data, irq_work);
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
+
+	enable_irq(data->irq);
+
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+}
+
+static irqreturn_t exynos4_tmu_irq(int irq, void *id)
+{
+	struct exynos4_tmu_data *data = id;
+
+	disable_irq_nosync(irq);
+	schedule_work(&data->irq_work);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
+{
+	struct exynos4_tmu_data *data;
+	struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
+	int ret;
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform init data supplied.\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
+	if (!data) {
+		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
+		return -ENOMEM;
+	}
+
+	data->irq = platform_get_irq(pdev, 0);
+	if (data->irq < 0) {
+		ret = data->irq;
+		dev_err(&pdev->dev, "Failed to get platform irq\n");
+		goto err_free;
+	}
+
+	INIT_WORK(&data->irq_work, exynos4_tmu_work);
+
+	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!data->mem) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get platform resource\n");
+		goto err_free;
+	}
+
+	data->mem = request_mem_region(data->mem->start,
+			resource_size(data->mem), pdev->name);
+	if (!data->mem) {
+		ret = -ENODEV;
+		dev_err(&pdev->dev, "Failed to request memory region\n");
+		goto err_free;
+	}
+
+	data->base = ioremap(data->mem->start, resource_size(data->mem));
+	if (!data->base) {
+		ret = -ENODEV;
+		dev_err(&pdev->dev, "Failed to ioremap memory\n");
+		goto err_mem_region;
+	}
+
+	ret = request_irq(data->irq, exynos4_tmu_irq,
+		IRQF_TRIGGER_RISING,
+		"exynos4-tmu", data);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
+		goto err_io_remap;
+	}
+
+	data->clk = clk_get(NULL, "tmu_apbif");
+	if (IS_ERR(data->clk)) {
+		ret = PTR_ERR(data->clk);
+		dev_err(&pdev->dev, "Failed to get clock\n");
+		goto err_irq;
+	}
+
+	data->pdata = pdata;
+	platform_set_drvdata(pdev, data);
+	mutex_init(&data->lock);
+
+	ret = exynos4_tmu_initialize(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to initialize TMU\n");
+		goto err_clk;
+	}
+
+	exynos4_tmu_control(pdev, true);
+
+	return 0;
+err_clk:
+	platform_set_drvdata(pdev, NULL);
+	clk_put(data->clk);
+err_irq:
+	free_irq(data->irq, data);
+err_io_remap:
+	iounmap(data->base);
+err_mem_region:
+	release_mem_region(data->mem->start, resource_size(data->mem));
+err_free:
+	kfree(data);
+
+	return ret;
+}
+
+static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
+{
+	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+
+	exynos4_tmu_control(pdev, false);
+
+	clk_put(data->clk);
+
+	free_irq(data->irq, data);
+
+	iounmap(data->base);
+	release_mem_region(data->mem->start, resource_size(data->mem));
+
+	platform_set_drvdata(pdev, NULL);
+
+	kfree(data);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos4_tmu_suspend(struct device *dev)
+{
+	exynos4_tmu_control(to_platform_device(dev), false);
+
+	return 0;
+}
+
+static int exynos4_tmu_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	exynos4_tmu_initialize(pdev);
+	exynos4_tmu_control(pdev, true);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
+			 exynos4_tmu_suspend, exynos4_tmu_resume);
+#define EXYNOS4_TMU_PM	(&exynos4_tmu_pm)
+#else
+#define EXYNOS4_TMU_PM	NULL
+#endif
+
+static struct platform_driver exynos4_tmu_driver = {
+	.driver = {
+		.name   = "exynos4-tmu",
+		.owner  = THIS_MODULE,
+		.pm     = EXYNOS4_TMU_PM,
+	},
+	.probe = exynos4_tmu_probe,
+	.remove	= __devexit_p(exynos4_tmu_remove),
+};
+
+module_platform_driver(exynos4_tmu_driver);
+
+MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
+MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:exynos4-tmu");
diff --git a/include/linux/platform_data/exynos4_tmu.h b/include/linux/platform_data/exynos4_tmu.h
deleted file mode 100644
index 39e038c..0000000
--- a/include/linux/platform_data/exynos4_tmu.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * exynos4_tmu.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
- *
- *  Copyright (C) 2011 Samsung Electronics
- *  Donggeun Kim <dg77.kim@samsung.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#ifndef _LINUX_EXYNOS4_TMU_H
-#define _LINUX_EXYNOS4_TMU_H
-
-enum calibration_type {
-	TYPE_ONE_POINT_TRIMMING,
-	TYPE_TWO_POINT_TRIMMING,
-	TYPE_NONE,
-};
-
-/**
- * struct exynos4_tmu_platform_data
- * @threshold: basic temperature for generating interrupt
- *	       25 <= threshold <= 125 [unit: degree Celsius]
- * @trigger_levels: array for each interrupt levels
- *	[unit: degree Celsius]
- *	0: temperature for trigger_level0 interrupt
- *	   condition for trigger_level0 interrupt:
- *		current temperature > threshold + trigger_levels[0]
- *	1: temperature for trigger_level1 interrupt
- *	   condition for trigger_level1 interrupt:
- *		current temperature > threshold + trigger_levels[1]
- *	2: temperature for trigger_level2 interrupt
- *	   condition for trigger_level2 interrupt:
- *		current temperature > threshold + trigger_levels[2]
- *	3: temperature for trigger_level3 interrupt
- *	   condition for trigger_level3 interrupt:
- *		current temperature > threshold + trigger_levels[3]
- * @trigger_level0_en:
- *	1 = enable trigger_level0 interrupt,
- *	0 = disable trigger_level0 interrupt
- * @trigger_level1_en:
- *	1 = enable trigger_level1 interrupt,
- *	0 = disable trigger_level1 interrupt
- * @trigger_level2_en:
- *	1 = enable trigger_level2 interrupt,
- *	0 = disable trigger_level2 interrupt
- * @trigger_level3_en:
- *	1 = enable trigger_level3 interrupt,
- *	0 = disable trigger_level3 interrupt
- * @gain: gain of amplifier in the positive-TC generator block
- *	0 <= gain <= 15
- * @reference_voltage: reference voltage of amplifier
- *	in the positive-TC generator block
- *	0 <= reference_voltage <= 31
- * @cal_type: calibration type for temperature
- *
- * This structure is required for configuration of exynos4_tmu driver.
- */
-struct exynos4_tmu_platform_data {
-	u8 threshold;
-	u8 trigger_levels[4];
-	bool trigger_level0_en;
-	bool trigger_level1_en;
-	bool trigger_level2_en;
-	bool trigger_level3_en;
-
-	u8 gain;
-	u8 reference_voltage;
-
-	enum calibration_type cal_type;
-};
-#endif /* _LINUX_EXYNOS4_TMU_H */
diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h
new file mode 100644
index 0000000..d6c3f93
--- /dev/null
+++ b/include/linux/platform_data/exynos_thermal.h
@@ -0,0 +1,83 @@
+/*
+ * exynos_thermal.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
+ *
+ *  Copyright (C) 2011 Samsung Electronics
+ *  Donggeun Kim <dg77.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _LINUX_EXYNOS_THERMAL_H
+#define _LINUX_EXYNOS_THERMAL_H
+
+enum calibration_type {
+	TYPE_ONE_POINT_TRIMMING,
+	TYPE_TWO_POINT_TRIMMING,
+	TYPE_NONE,
+};
+
+/**
+ * struct exynos4_tmu_platform_data
+ * @threshold: basic temperature for generating interrupt
+ *	       25 <= threshold <= 125 [unit: degree Celsius]
+ * @trigger_levels: array for each interrupt levels
+ *	[unit: degree Celsius]
+ *	0: temperature for trigger_level0 interrupt
+ *	   condition for trigger_level0 interrupt:
+ *		current temperature > threshold + trigger_levels[0]
+ *	1: temperature for trigger_level1 interrupt
+ *	   condition for trigger_level1 interrupt:
+ *		current temperature > threshold + trigger_levels[1]
+ *	2: temperature for trigger_level2 interrupt
+ *	   condition for trigger_level2 interrupt:
+ *		current temperature > threshold + trigger_levels[2]
+ *	3: temperature for trigger_level3 interrupt
+ *	   condition for trigger_level3 interrupt:
+ *		current temperature > threshold + trigger_levels[3]
+ * @trigger_level0_en:
+ *	1 = enable trigger_level0 interrupt,
+ *	0 = disable trigger_level0 interrupt
+ * @trigger_level1_en:
+ *	1 = enable trigger_level1 interrupt,
+ *	0 = disable trigger_level1 interrupt
+ * @trigger_level2_en:
+ *	1 = enable trigger_level2 interrupt,
+ *	0 = disable trigger_level2 interrupt
+ * @trigger_level3_en:
+ *	1 = enable trigger_level3 interrupt,
+ *	0 = disable trigger_level3 interrupt
+ * @gain: gain of amplifier in the positive-TC generator block
+ *	0 <= gain <= 15
+ * @reference_voltage: reference voltage of amplifier
+ *	in the positive-TC generator block
+ *	0 <= reference_voltage <= 31
+ * @cal_type: calibration type for temperature
+ *
+ * This structure is required for configuration of exynos4_tmu driver.
+ */
+struct exynos4_tmu_platform_data {
+	u8 threshold;
+	u8 trigger_levels[4];
+	bool trigger_level0_en;
+	bool trigger_level1_en;
+	bool trigger_level2_en;
+	bool trigger_level3_en;
+
+	u8 gain;
+	u8 reference_voltage;
+
+	enum calibration_type cal_type;
+};
+#endif /* _LINUX_EXYNOS_THERMAL_H */
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 3/6] thermal: exynos5: add exynos5250 thermal sensor driver support
  2012-08-16 11:53 ` [lm-sensors] " Amit Daniel Kachhap
  (?)
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, lm-sensors, linux-acpi,
	Kukjin Kim, Jean Delvare, Kyungmin Park

Insert exynos5 TMU sensor changes into the thermal driver.  Some exynos4
changes are made generic for exynos series.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: SangWook Ju <sw.ju@samsung.com>
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 drivers/thermal/Kconfig                      |    2 +-
 drivers/thermal/exynos_thermal.c             |  351 ++++++++++++++++----------
 include/linux/platform_data/exynos_thermal.h |   19 ++-
 3 files changed, 240 insertions(+), 132 deletions(-)

diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 8f2b6ea..edfd67d 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -49,7 +49,7 @@ config RCAR_THERMAL
 
 config EXYNOS_THERMAL
 	tristate "Temperature sensor on Samsung EXYNOS"
-	depends on ARCH_EXYNOS4 && THERMAL
+	depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) && THERMAL
 	help
 	  If you say yes here you get support for TMU (Thermal Managment
 	  Unit) on SAMSUNG EXYNOS series of SoC.
diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index 556d15b..c9a33dd 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -33,44 +33,83 @@
 #include <linux/kobject.h>
 #include <linux/io.h>
 #include <linux/mutex.h>
-
 #include <linux/platform_data/exynos_thermal.h>
-
-#define EXYNOS4_TMU_REG_TRIMINFO	0x0
-#define EXYNOS4_TMU_REG_CONTROL		0x20
-#define EXYNOS4_TMU_REG_STATUS		0x28
-#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
-#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
-#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
-#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
-#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
-#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
-#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
-#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
-#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
-#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
-#define EXYNOS4_TMU_REG_INTEN		0x70
-#define EXYNOS4_TMU_REG_INTSTAT		0x74
-#define EXYNOS4_TMU_REG_INTCLEAR	0x78
-
-#define EXYNOS4_TMU_GAIN_SHIFT		8
-#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
-
-#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
-#define EXYNOS4_TMU_CORE_ON	3
-#define EXYNOS4_TMU_CORE_OFF	2
-#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
-#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
-#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
-#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
-#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
-#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
-
-struct exynos4_tmu_data {
-	struct exynos4_tmu_platform_data *pdata;
+#include <linux/of.h>
+
+#include <plat/cpu.h>
+
+/* Exynos generic registers */
+#define EXYNOS_TMU_REG_TRIMINFO		0x0
+#define EXYNOS_TMU_REG_CONTROL		0x20
+#define EXYNOS_TMU_REG_STATUS		0x28
+#define EXYNOS_TMU_REG_CURRENT_TEMP	0x40
+#define EXYNOS_TMU_REG_INTEN		0x70
+#define EXYNOS_TMU_REG_INTSTAT		0x74
+#define EXYNOS_TMU_REG_INTCLEAR		0x78
+
+#define EXYNOS_TMU_TRIM_TEMP_MASK	0xff
+#define EXYNOS_TMU_GAIN_SHIFT		8
+#define EXYNOS_TMU_REF_VOLTAGE_SHIFT	24
+#define EXYNOS_TMU_CORE_ON		3
+#define EXYNOS_TMU_CORE_OFF		2
+#define EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET	50
+
+/* Exynos4210 specific registers */
+#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP	0x44
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL0	0x50
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL1	0x54
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL2	0x58
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL3	0x5C
+#define EXYNOS4210_TMU_REG_PAST_TEMP0	0x60
+#define EXYNOS4210_TMU_REG_PAST_TEMP1	0x64
+#define EXYNOS4210_TMU_REG_PAST_TEMP2	0x68
+#define EXYNOS4210_TMU_REG_PAST_TEMP3	0x6C
+
+#define EXYNOS4210_TMU_TRIG_LEVEL0_MASK	0x1
+#define EXYNOS4210_TMU_TRIG_LEVEL1_MASK	0x10
+#define EXYNOS4210_TMU_TRIG_LEVEL2_MASK	0x100
+#define EXYNOS4210_TMU_TRIG_LEVEL3_MASK	0x1000
+#define EXYNOS4210_TMU_INTCLEAR_VAL	0x1111
+
+/* Exynos5250 and Exynos4412 specific registers */
+#define EXYNOS_TMU_TRIMINFO_CON	0x14
+#define EXYNOS_THD_TEMP_RISE		0x50
+#define EXYNOS_THD_TEMP_FALL		0x54
+#define EXYNOS_EMUL_CON		0x80
+
+#define EXYNOS_TRIMINFO_RELOAD		0x1
+#define EXYNOS_TMU_CLEAR_RISE_INT	0x111
+#define EXYNOS_TMU_CLEAR_FALL_INT	(0x111 << 16)
+#define EXYNOS_MUX_ADDR_VALUE		6
+#define EXYNOS_MUX_ADDR_SHIFT		20
+#define EXYNOS_TMU_TRIP_MODE_SHIFT	13
+
+#define EFUSE_MIN_VALUE 40
+#define EFUSE_MAX_VALUE 100
+
+/* In-kernel thermal framework related macros & definations */
+#define SENSOR_NAME_LEN	16
+#define MAX_TRIP_COUNT	8
+#define MAX_COOLING_DEVICE 4
+
+#define ACTIVE_INTERVAL 500
+#define IDLE_INTERVAL 10000
+
+/* CPU Zone information */
+#define PANIC_ZONE      4
+#define WARN_ZONE       3
+#define MONITOR_ZONE    2
+#define SAFE_ZONE       1
+
+#define GET_ZONE(trip) (trip + 2)
+#define GET_TRIP(zone) (zone - 2)
+
+struct exynos_tmu_data {
+	struct exynos_tmu_platform_data *pdata;
 	struct resource *mem;
 	void __iomem *base;
 	int irq;
+	enum soc_type soc;
 	struct work_struct irq_work;
 	struct mutex lock;
 	struct clk *clk;
@@ -81,16 +120,17 @@ struct exynos4_tmu_data {
  * TMU treats temperature as a mapped temperature code.
  * The temperature is converted differently depending on the calibration type.
  */
-static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
+static int temp_to_code(struct exynos_tmu_data *data, u8 temp)
 {
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	struct exynos_tmu_platform_data *pdata = data->pdata;
 	int temp_code;
 
-	/* temp should range between 25 and 125 */
-	if (temp < 25 || temp > 125) {
-		temp_code = -EINVAL;
-		goto out;
-	}
+	if (data->soc == SOC_ARCH_EXYNOS4210)
+		/* temp should range between 25 and 125 */
+		if (temp < 25 || temp > 125) {
+			temp_code = -EINVAL;
+			goto out;
+		}
 
 	switch (pdata->cal_type) {
 	case TYPE_TWO_POINT_TRIMMING:
@@ -102,7 +142,7 @@ static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
 		temp_code = temp + data->temp_error1 - 25;
 		break;
 	default:
-		temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		temp_code = temp + EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET;
 		break;
 	}
 out:
@@ -113,16 +153,17 @@ out:
  * Calculate a temperature value from a temperature code.
  * The unit of the temperature is degree Celsius.
  */
-static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
+static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code)
 {
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	struct exynos_tmu_platform_data *pdata = data->pdata;
 	int temp;
 
-	/* temp_code should range between 75 and 175 */
-	if (temp_code < 75 || temp_code > 175) {
-		temp = -ENODATA;
-		goto out;
-	}
+	if (data->soc == SOC_ARCH_EXYNOS4210)
+		/* temp_code should range between 75 and 175 */
+		if (temp_code < 75 || temp_code > 175) {
+			temp = -ENODATA;
+			goto out;
+		}
 
 	switch (pdata->cal_type) {
 	case TYPE_TWO_POINT_TRIMMING:
@@ -133,54 +174,92 @@ static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
 		temp = temp_code - data->temp_error1 + 25;
 		break;
 	default:
-		temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		temp = temp_code - EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET;
 		break;
 	}
 out:
 	return temp;
 }
 
-static int exynos4_tmu_initialize(struct platform_device *pdev)
+static int exynos_tmu_initialize(struct platform_device *pdev)
 {
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int status, trim_info;
+	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;
 
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
+	status = readb(data->base + EXYNOS_TMU_REG_STATUS);
 	if (!status) {
 		ret = -EBUSY;
 		goto out;
 	}
 
+	if (data->soc == SOC_ARCH_EXYNOS) {
+		__raw_writel(EXYNOS_TRIMINFO_RELOAD,
+				data->base + EXYNOS_TMU_TRIMINFO_CON);
+	}
 	/* Save trimming info in order to perform calibration */
-	trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
-	data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
-	data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
-
-	/* Write temperature code for threshold */
-	threshold_code = temp_to_code(data, pdata->threshold);
-	if (threshold_code < 0) {
-		ret = threshold_code;
-		goto out;
+	trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+	data->temp_error1 = trim_info & EXYNOS_TMU_TRIM_TEMP_MASK;
+	data->temp_error2 = ((trim_info >> 8) & EXYNOS_TMU_TRIM_TEMP_MASK);
+
+	if ((EFUSE_MIN_VALUE > data->temp_error1) ||
+			(data->temp_error1 > EFUSE_MAX_VALUE) ||
+			(data->temp_error2 != 0))
+		data->temp_error1 = pdata->efuse_value;
+
+	if (data->soc == SOC_ARCH_EXYNOS4210) {
+		/* Write temperature code for threshold */
+		threshold_code = temp_to_code(data, pdata->threshold);
+		if (threshold_code < 0) {
+			ret = threshold_code;
+			goto out;
+		}
+		writeb(threshold_code,
+			data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
+
+		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);
+
+		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;
+		}
+		rising_threshold |= (threshold_code << 16);
+
+		writel(rising_threshold,
+				data->base + EXYNOS_THD_TEMP_RISE);
+		writel(0, data->base + EXYNOS_THD_TEMP_FALL);
+
+		writel(EXYNOS_TMU_CLEAR_RISE_INT|EXYNOS_TMU_CLEAR_FALL_INT,
+				data->base + EXYNOS_TMU_REG_INTCLEAR);
 	}
-	writeb(threshold_code,
-		data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
-
-	writeb(pdata->trigger_levels[0],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
-	writeb(pdata->trigger_levels[1],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
-	writeb(pdata->trigger_levels[2],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
-	writeb(pdata->trigger_levels[3],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
-
-	writel(EXYNOS4_TMU_INTCLEAR_VAL,
-		data->base + EXYNOS4_TMU_REG_INTCLEAR);
 out:
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
@@ -188,35 +267,41 @@ out:
 	return ret;
 }
 
-static void exynos4_tmu_control(struct platform_device *pdev, bool on)
+static void exynos_tmu_control(struct platform_device *pdev, bool on)
 {
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos_tmu_platform_data *pdata = data->pdata;
 	unsigned int con, interrupt_en;
 
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
-		pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
+	con = pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT |
+		pdata->gain << EXYNOS_TMU_GAIN_SHIFT;
+
+	if (data->soc == SOC_ARCH_EXYNOS) {
+		con |= pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT;
+		con |= (EXYNOS_MUX_ADDR_VALUE << EXYNOS_MUX_ADDR_SHIFT);
+	}
+
 	if (on) {
-		con |= EXYNOS4_TMU_CORE_ON;
+		con |= EXYNOS_TMU_CORE_ON;
 		interrupt_en = pdata->trigger_level3_en << 12 |
 			pdata->trigger_level2_en << 8 |
 			pdata->trigger_level1_en << 4 |
 			pdata->trigger_level0_en;
 	} else {
-		con |= EXYNOS4_TMU_CORE_OFF;
+		con |= EXYNOS_TMU_CORE_OFF;
 		interrupt_en = 0; /* Disable all interrupts */
 	}
-	writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
-	writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
+	writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN);
+	writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
 
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
 }
 
-static int exynos4_tmu_read(struct exynos4_tmu_data *data)
+static int exynos_tmu_read(struct exynos_tmu_data *data)
 {
 	u8 temp_code;
 	int temp;
@@ -224,7 +309,7 @@ static int exynos4_tmu_read(struct exynos4_tmu_data *data)
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
+	temp_code = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);
 	temp = code_to_temp(data, temp_code);
 
 	clk_disable(data->clk);
@@ -233,25 +318,30 @@ static int exynos4_tmu_read(struct exynos4_tmu_data *data)
 	return temp;
 }
 
-static void exynos4_tmu_work(struct work_struct *work)
+static void exynos_tmu_work(struct work_struct *work)
 {
-	struct exynos4_tmu_data *data = container_of(work,
-			struct exynos4_tmu_data, irq_work);
+	struct exynos_tmu_data *data = container_of(work,
+			struct exynos_tmu_data, irq_work);
 
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
 
-	enable_irq(data->irq);
+	if (data->soc == SOC_ARCH_EXYNOS)
+		writel(EXYNOS_TMU_CLEAR_RISE_INT,
+				data->base + EXYNOS_TMU_REG_INTCLEAR);
+	else
+		writel(EXYNOS4210_TMU_INTCLEAR_VAL,
+				data->base + EXYNOS_TMU_REG_INTCLEAR);
 
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
+	enable_irq(data->irq);
 }
 
-static irqreturn_t exynos4_tmu_irq(int irq, void *id)
+static irqreturn_t exynos_tmu_irq(int irq, void *id)
 {
-	struct exynos4_tmu_data *data = id;
+	struct exynos_tmu_data *data = id;
 
 	disable_irq_nosync(irq);
 	schedule_work(&data->irq_work);
@@ -259,18 +349,17 @@ static irqreturn_t exynos4_tmu_irq(int irq, void *id)
 	return IRQ_HANDLED;
 }
 
-static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
+static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 {
-	struct exynos4_tmu_data *data;
-	struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
+	struct exynos_tmu_data *data;
+	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
 	int ret;
 
 	if (!pdata) {
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
 		return -ENODEV;
 	}
-
-	data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
+	data = kzalloc(sizeof(struct exynos_tmu_data), GFP_KERNEL);
 	if (!data) {
 		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
 		return -ENOMEM;
@@ -283,7 +372,7 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 		goto err_free;
 	}
 
-	INIT_WORK(&data->irq_work, exynos4_tmu_work);
+	INIT_WORK(&data->irq_work, exynos_tmu_work);
 
 	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!data->mem) {
@@ -307,9 +396,8 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 		goto err_mem_region;
 	}
 
-	ret = request_irq(data->irq, exynos4_tmu_irq,
-		IRQF_TRIGGER_RISING,
-		"exynos4-tmu", data);
+	ret = request_irq(data->irq, exynos_tmu_irq,
+		IRQF_TRIGGER_RISING, "exynos-tmu", data);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
 		goto err_io_remap;
@@ -322,17 +410,26 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 		goto err_irq;
 	}
 
+	if (pdata->type == SOC_ARCH_EXYNOS ||
+				pdata->type == SOC_ARCH_EXYNOS4210)
+		data->soc = pdata->type;
+	else {
+		ret = -EINVAL;
+		dev_err(&pdev->dev, "Platform not supported\n");
+		goto err_clk;
+	}
+
 	data->pdata = pdata;
 	platform_set_drvdata(pdev, data);
 	mutex_init(&data->lock);
 
-	ret = exynos4_tmu_initialize(pdev);
+	ret = exynos_tmu_initialize(pdev);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to initialize TMU\n");
 		goto err_clk;
 	}
 
-	exynos4_tmu_control(pdev, true);
+	exynos_tmu_control(pdev, true);
 
 	return 0;
 err_clk:
@@ -350,11 +447,11 @@ err_free:
 	return ret;
 }
 
-static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
+static int __devexit exynos_tmu_remove(struct platform_device *pdev)
 {
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
 
-	exynos4_tmu_control(pdev, false);
+	exynos_tmu_control(pdev, false);
 
 	clk_put(data->clk);
 
@@ -371,43 +468,43 @@ static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
 }
 
 #ifdef CONFIG_PM_SLEEP
-static int exynos4_tmu_suspend(struct device *dev)
+static int exynos_tmu_suspend(struct device *dev)
 {
-	exynos4_tmu_control(to_platform_device(dev), false);
+	exynos_tmu_control(to_platform_device(dev), false);
 
 	return 0;
 }
 
-static int exynos4_tmu_resume(struct device *dev)
+static int exynos_tmu_resume(struct device *dev)
 {
 	struct platform_device *pdev = to_platform_device(dev);
 
-	exynos4_tmu_initialize(pdev);
-	exynos4_tmu_control(pdev, true);
+	exynos_tmu_initialize(pdev);
+	exynos_tmu_control(pdev, true);
 
 	return 0;
 }
 
-static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
-			 exynos4_tmu_suspend, exynos4_tmu_resume);
-#define EXYNOS4_TMU_PM	(&exynos4_tmu_pm)
+static SIMPLE_DEV_PM_OPS(exynos_tmu_pm,
+			 exynos_tmu_suspend, exynos_tmu_resume);
+#define EXYNOS_TMU_PM	(&exynos_tmu_pm)
 #else
-#define EXYNOS4_TMU_PM	NULL
+#define EXYNOS_TMU_PM	NULL
 #endif
 
-static struct platform_driver exynos4_tmu_driver = {
+static struct platform_driver exynos_tmu_driver = {
 	.driver = {
-		.name   = "exynos4-tmu",
+		.name   = "exynos-tmu",
 		.owner  = THIS_MODULE,
-		.pm     = EXYNOS4_TMU_PM,
+		.pm     = EXYNOS_TMU_PM,
 	},
-	.probe = exynos4_tmu_probe,
-	.remove	= __devexit_p(exynos4_tmu_remove),
+	.probe = exynos_tmu_probe,
+	.remove	= __devexit_p(exynos_tmu_remove),
 };
 
-module_platform_driver(exynos4_tmu_driver);
+module_platform_driver(exynos_tmu_driver);
 
-MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
+MODULE_DESCRIPTION("EXYNOS TMU Driver");
 MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
 MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:exynos4-tmu");
+MODULE_ALIAS("platform:exynos-tmu");
diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h
index d6c3f93..a9e960a 100644
--- a/include/linux/platform_data/exynos_thermal.h
+++ b/include/linux/platform_data/exynos_thermal.h
@@ -1,5 +1,5 @@
 /*
- * exynos_thermal.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
+ * exynos_thermal.h - Samsung EXYNOS TMU (Thermal Management Unit)
  *
  *  Copyright (C) 2011 Samsung Electronics
  *  Donggeun Kim <dg77.kim@samsung.com>
@@ -28,8 +28,12 @@ enum calibration_type {
 	TYPE_NONE,
 };
 
+enum soc_type {
+	SOC_ARCH_EXYNOS4210 = 1,
+	SOC_ARCH_EXYNOS,
+};
 /**
- * struct exynos4_tmu_platform_data
+ * struct exynos_tmu_platform_data
  * @threshold: basic temperature for generating interrupt
  *	       25 <= threshold <= 125 [unit: degree Celsius]
  * @trigger_levels: array for each interrupt levels
@@ -63,11 +67,15 @@ enum calibration_type {
  * @reference_voltage: reference voltage of amplifier
  *	in the positive-TC generator block
  *	0 <= reference_voltage <= 31
+ * @noise_cancel_mode: noise cancellation mode
+ *	000, 100, 101, 110 and 111 can be different modes
+ * @type: determines the type of SOC
+ * @efuse_value: platform defined fuse value
  * @cal_type: calibration type for temperature
  *
- * This structure is required for configuration of exynos4_tmu driver.
+ * This structure is required for configuration of exynos_tmu driver.
  */
-struct exynos4_tmu_platform_data {
+struct exynos_tmu_platform_data {
 	u8 threshold;
 	u8 trigger_levels[4];
 	bool trigger_level0_en;
@@ -77,7 +85,10 @@ struct exynos4_tmu_platform_data {
 
 	u8 gain;
 	u8 reference_voltage;
+	u8 noise_cancel_mode;
+	u32 efuse_value;
 
 	enum calibration_type cal_type;
+	enum soc_type type;
 };
 #endif /* _LINUX_EXYNOS_THERMAL_H */
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 3/6] thermal: exynos5: add exynos5250 thermal sensor driver support
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	amit.kachhap, Durgadoss, Len Brown, Jean Delvare, Kyungmin Park,
	Kukjin Kim

Insert exynos5 TMU sensor changes into the thermal driver.  Some exynos4
changes are made generic for exynos series.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: SangWook Ju <sw.ju@samsung.com>
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 drivers/thermal/Kconfig                      |    2 +-
 drivers/thermal/exynos_thermal.c             |  351 ++++++++++++++++----------
 include/linux/platform_data/exynos_thermal.h |   19 ++-
 3 files changed, 240 insertions(+), 132 deletions(-)

diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 8f2b6ea..edfd67d 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -49,7 +49,7 @@ config RCAR_THERMAL
 
 config EXYNOS_THERMAL
 	tristate "Temperature sensor on Samsung EXYNOS"
-	depends on ARCH_EXYNOS4 && THERMAL
+	depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) && THERMAL
 	help
 	  If you say yes here you get support for TMU (Thermal Managment
 	  Unit) on SAMSUNG EXYNOS series of SoC.
diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index 556d15b..c9a33dd 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -33,44 +33,83 @@
 #include <linux/kobject.h>
 #include <linux/io.h>
 #include <linux/mutex.h>
-
 #include <linux/platform_data/exynos_thermal.h>
-
-#define EXYNOS4_TMU_REG_TRIMINFO	0x0
-#define EXYNOS4_TMU_REG_CONTROL		0x20
-#define EXYNOS4_TMU_REG_STATUS		0x28
-#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
-#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
-#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
-#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
-#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
-#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
-#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
-#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
-#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
-#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
-#define EXYNOS4_TMU_REG_INTEN		0x70
-#define EXYNOS4_TMU_REG_INTSTAT		0x74
-#define EXYNOS4_TMU_REG_INTCLEAR	0x78
-
-#define EXYNOS4_TMU_GAIN_SHIFT		8
-#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
-
-#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
-#define EXYNOS4_TMU_CORE_ON	3
-#define EXYNOS4_TMU_CORE_OFF	2
-#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
-#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
-#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
-#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
-#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
-#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
-
-struct exynos4_tmu_data {
-	struct exynos4_tmu_platform_data *pdata;
+#include <linux/of.h>
+
+#include <plat/cpu.h>
+
+/* Exynos generic registers */
+#define EXYNOS_TMU_REG_TRIMINFO		0x0
+#define EXYNOS_TMU_REG_CONTROL		0x20
+#define EXYNOS_TMU_REG_STATUS		0x28
+#define EXYNOS_TMU_REG_CURRENT_TEMP	0x40
+#define EXYNOS_TMU_REG_INTEN		0x70
+#define EXYNOS_TMU_REG_INTSTAT		0x74
+#define EXYNOS_TMU_REG_INTCLEAR		0x78
+
+#define EXYNOS_TMU_TRIM_TEMP_MASK	0xff
+#define EXYNOS_TMU_GAIN_SHIFT		8
+#define EXYNOS_TMU_REF_VOLTAGE_SHIFT	24
+#define EXYNOS_TMU_CORE_ON		3
+#define EXYNOS_TMU_CORE_OFF		2
+#define EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET	50
+
+/* Exynos4210 specific registers */
+#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP	0x44
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL0	0x50
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL1	0x54
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL2	0x58
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL3	0x5C
+#define EXYNOS4210_TMU_REG_PAST_TEMP0	0x60
+#define EXYNOS4210_TMU_REG_PAST_TEMP1	0x64
+#define EXYNOS4210_TMU_REG_PAST_TEMP2	0x68
+#define EXYNOS4210_TMU_REG_PAST_TEMP3	0x6C
+
+#define EXYNOS4210_TMU_TRIG_LEVEL0_MASK	0x1
+#define EXYNOS4210_TMU_TRIG_LEVEL1_MASK	0x10
+#define EXYNOS4210_TMU_TRIG_LEVEL2_MASK	0x100
+#define EXYNOS4210_TMU_TRIG_LEVEL3_MASK	0x1000
+#define EXYNOS4210_TMU_INTCLEAR_VAL	0x1111
+
+/* Exynos5250 and Exynos4412 specific registers */
+#define EXYNOS_TMU_TRIMINFO_CON	0x14
+#define EXYNOS_THD_TEMP_RISE		0x50
+#define EXYNOS_THD_TEMP_FALL		0x54
+#define EXYNOS_EMUL_CON		0x80
+
+#define EXYNOS_TRIMINFO_RELOAD		0x1
+#define EXYNOS_TMU_CLEAR_RISE_INT	0x111
+#define EXYNOS_TMU_CLEAR_FALL_INT	(0x111 << 16)
+#define EXYNOS_MUX_ADDR_VALUE		6
+#define EXYNOS_MUX_ADDR_SHIFT		20
+#define EXYNOS_TMU_TRIP_MODE_SHIFT	13
+
+#define EFUSE_MIN_VALUE 40
+#define EFUSE_MAX_VALUE 100
+
+/* In-kernel thermal framework related macros & definations */
+#define SENSOR_NAME_LEN	16
+#define MAX_TRIP_COUNT	8
+#define MAX_COOLING_DEVICE 4
+
+#define ACTIVE_INTERVAL 500
+#define IDLE_INTERVAL 10000
+
+/* CPU Zone information */
+#define PANIC_ZONE      4
+#define WARN_ZONE       3
+#define MONITOR_ZONE    2
+#define SAFE_ZONE       1
+
+#define GET_ZONE(trip) (trip + 2)
+#define GET_TRIP(zone) (zone - 2)
+
+struct exynos_tmu_data {
+	struct exynos_tmu_platform_data *pdata;
 	struct resource *mem;
 	void __iomem *base;
 	int irq;
+	enum soc_type soc;
 	struct work_struct irq_work;
 	struct mutex lock;
 	struct clk *clk;
@@ -81,16 +120,17 @@ struct exynos4_tmu_data {
  * TMU treats temperature as a mapped temperature code.
  * The temperature is converted differently depending on the calibration type.
  */
-static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
+static int temp_to_code(struct exynos_tmu_data *data, u8 temp)
 {
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	struct exynos_tmu_platform_data *pdata = data->pdata;
 	int temp_code;
 
-	/* temp should range between 25 and 125 */
-	if (temp < 25 || temp > 125) {
-		temp_code = -EINVAL;
-		goto out;
-	}
+	if (data->soc == SOC_ARCH_EXYNOS4210)
+		/* temp should range between 25 and 125 */
+		if (temp < 25 || temp > 125) {
+			temp_code = -EINVAL;
+			goto out;
+		}
 
 	switch (pdata->cal_type) {
 	case TYPE_TWO_POINT_TRIMMING:
@@ -102,7 +142,7 @@ static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
 		temp_code = temp + data->temp_error1 - 25;
 		break;
 	default:
-		temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		temp_code = temp + EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET;
 		break;
 	}
 out:
@@ -113,16 +153,17 @@ out:
  * Calculate a temperature value from a temperature code.
  * The unit of the temperature is degree Celsius.
  */
-static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
+static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code)
 {
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	struct exynos_tmu_platform_data *pdata = data->pdata;
 	int temp;
 
-	/* temp_code should range between 75 and 175 */
-	if (temp_code < 75 || temp_code > 175) {
-		temp = -ENODATA;
-		goto out;
-	}
+	if (data->soc == SOC_ARCH_EXYNOS4210)
+		/* temp_code should range between 75 and 175 */
+		if (temp_code < 75 || temp_code > 175) {
+			temp = -ENODATA;
+			goto out;
+		}
 
 	switch (pdata->cal_type) {
 	case TYPE_TWO_POINT_TRIMMING:
@@ -133,54 +174,92 @@ static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
 		temp = temp_code - data->temp_error1 + 25;
 		break;
 	default:
-		temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		temp = temp_code - EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET;
 		break;
 	}
 out:
 	return temp;
 }
 
-static int exynos4_tmu_initialize(struct platform_device *pdev)
+static int exynos_tmu_initialize(struct platform_device *pdev)
 {
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int status, trim_info;
+	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;
 
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
+	status = readb(data->base + EXYNOS_TMU_REG_STATUS);
 	if (!status) {
 		ret = -EBUSY;
 		goto out;
 	}
 
+	if (data->soc == SOC_ARCH_EXYNOS) {
+		__raw_writel(EXYNOS_TRIMINFO_RELOAD,
+				data->base + EXYNOS_TMU_TRIMINFO_CON);
+	}
 	/* Save trimming info in order to perform calibration */
-	trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
-	data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
-	data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
-
-	/* Write temperature code for threshold */
-	threshold_code = temp_to_code(data, pdata->threshold);
-	if (threshold_code < 0) {
-		ret = threshold_code;
-		goto out;
+	trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+	data->temp_error1 = trim_info & EXYNOS_TMU_TRIM_TEMP_MASK;
+	data->temp_error2 = ((trim_info >> 8) & EXYNOS_TMU_TRIM_TEMP_MASK);
+
+	if ((EFUSE_MIN_VALUE > data->temp_error1) ||
+			(data->temp_error1 > EFUSE_MAX_VALUE) ||
+			(data->temp_error2 != 0))
+		data->temp_error1 = pdata->efuse_value;
+
+	if (data->soc == SOC_ARCH_EXYNOS4210) {
+		/* Write temperature code for threshold */
+		threshold_code = temp_to_code(data, pdata->threshold);
+		if (threshold_code < 0) {
+			ret = threshold_code;
+			goto out;
+		}
+		writeb(threshold_code,
+			data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
+
+		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);
+
+		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;
+		}
+		rising_threshold |= (threshold_code << 16);
+
+		writel(rising_threshold,
+				data->base + EXYNOS_THD_TEMP_RISE);
+		writel(0, data->base + EXYNOS_THD_TEMP_FALL);
+
+		writel(EXYNOS_TMU_CLEAR_RISE_INT|EXYNOS_TMU_CLEAR_FALL_INT,
+				data->base + EXYNOS_TMU_REG_INTCLEAR);
 	}
-	writeb(threshold_code,
-		data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
-
-	writeb(pdata->trigger_levels[0],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
-	writeb(pdata->trigger_levels[1],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
-	writeb(pdata->trigger_levels[2],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
-	writeb(pdata->trigger_levels[3],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
-
-	writel(EXYNOS4_TMU_INTCLEAR_VAL,
-		data->base + EXYNOS4_TMU_REG_INTCLEAR);
 out:
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
@@ -188,35 +267,41 @@ out:
 	return ret;
 }
 
-static void exynos4_tmu_control(struct platform_device *pdev, bool on)
+static void exynos_tmu_control(struct platform_device *pdev, bool on)
 {
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos_tmu_platform_data *pdata = data->pdata;
 	unsigned int con, interrupt_en;
 
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
-		pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
+	con = pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT |
+		pdata->gain << EXYNOS_TMU_GAIN_SHIFT;
+
+	if (data->soc == SOC_ARCH_EXYNOS) {
+		con |= pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT;
+		con |= (EXYNOS_MUX_ADDR_VALUE << EXYNOS_MUX_ADDR_SHIFT);
+	}
+
 	if (on) {
-		con |= EXYNOS4_TMU_CORE_ON;
+		con |= EXYNOS_TMU_CORE_ON;
 		interrupt_en = pdata->trigger_level3_en << 12 |
 			pdata->trigger_level2_en << 8 |
 			pdata->trigger_level1_en << 4 |
 			pdata->trigger_level0_en;
 	} else {
-		con |= EXYNOS4_TMU_CORE_OFF;
+		con |= EXYNOS_TMU_CORE_OFF;
 		interrupt_en = 0; /* Disable all interrupts */
 	}
-	writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
-	writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
+	writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN);
+	writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
 
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
 }
 
-static int exynos4_tmu_read(struct exynos4_tmu_data *data)
+static int exynos_tmu_read(struct exynos_tmu_data *data)
 {
 	u8 temp_code;
 	int temp;
@@ -224,7 +309,7 @@ static int exynos4_tmu_read(struct exynos4_tmu_data *data)
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
+	temp_code = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);
 	temp = code_to_temp(data, temp_code);
 
 	clk_disable(data->clk);
@@ -233,25 +318,30 @@ static int exynos4_tmu_read(struct exynos4_tmu_data *data)
 	return temp;
 }
 
-static void exynos4_tmu_work(struct work_struct *work)
+static void exynos_tmu_work(struct work_struct *work)
 {
-	struct exynos4_tmu_data *data = container_of(work,
-			struct exynos4_tmu_data, irq_work);
+	struct exynos_tmu_data *data = container_of(work,
+			struct exynos_tmu_data, irq_work);
 
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
 
-	enable_irq(data->irq);
+	if (data->soc == SOC_ARCH_EXYNOS)
+		writel(EXYNOS_TMU_CLEAR_RISE_INT,
+				data->base + EXYNOS_TMU_REG_INTCLEAR);
+	else
+		writel(EXYNOS4210_TMU_INTCLEAR_VAL,
+				data->base + EXYNOS_TMU_REG_INTCLEAR);
 
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
+	enable_irq(data->irq);
 }
 
-static irqreturn_t exynos4_tmu_irq(int irq, void *id)
+static irqreturn_t exynos_tmu_irq(int irq, void *id)
 {
-	struct exynos4_tmu_data *data = id;
+	struct exynos_tmu_data *data = id;
 
 	disable_irq_nosync(irq);
 	schedule_work(&data->irq_work);
@@ -259,18 +349,17 @@ static irqreturn_t exynos4_tmu_irq(int irq, void *id)
 	return IRQ_HANDLED;
 }
 
-static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
+static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 {
-	struct exynos4_tmu_data *data;
-	struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
+	struct exynos_tmu_data *data;
+	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
 	int ret;
 
 	if (!pdata) {
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
 		return -ENODEV;
 	}
-
-	data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
+	data = kzalloc(sizeof(struct exynos_tmu_data), GFP_KERNEL);
 	if (!data) {
 		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
 		return -ENOMEM;
@@ -283,7 +372,7 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 		goto err_free;
 	}
 
-	INIT_WORK(&data->irq_work, exynos4_tmu_work);
+	INIT_WORK(&data->irq_work, exynos_tmu_work);
 
 	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!data->mem) {
@@ -307,9 +396,8 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 		goto err_mem_region;
 	}
 
-	ret = request_irq(data->irq, exynos4_tmu_irq,
-		IRQF_TRIGGER_RISING,
-		"exynos4-tmu", data);
+	ret = request_irq(data->irq, exynos_tmu_irq,
+		IRQF_TRIGGER_RISING, "exynos-tmu", data);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
 		goto err_io_remap;
@@ -322,17 +410,26 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 		goto err_irq;
 	}
 
+	if (pdata->type == SOC_ARCH_EXYNOS ||
+				pdata->type == SOC_ARCH_EXYNOS4210)
+		data->soc = pdata->type;
+	else {
+		ret = -EINVAL;
+		dev_err(&pdev->dev, "Platform not supported\n");
+		goto err_clk;
+	}
+
 	data->pdata = pdata;
 	platform_set_drvdata(pdev, data);
 	mutex_init(&data->lock);
 
-	ret = exynos4_tmu_initialize(pdev);
+	ret = exynos_tmu_initialize(pdev);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to initialize TMU\n");
 		goto err_clk;
 	}
 
-	exynos4_tmu_control(pdev, true);
+	exynos_tmu_control(pdev, true);
 
 	return 0;
 err_clk:
@@ -350,11 +447,11 @@ err_free:
 	return ret;
 }
 
-static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
+static int __devexit exynos_tmu_remove(struct platform_device *pdev)
 {
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
 
-	exynos4_tmu_control(pdev, false);
+	exynos_tmu_control(pdev, false);
 
 	clk_put(data->clk);
 
@@ -371,43 +468,43 @@ static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
 }
 
 #ifdef CONFIG_PM_SLEEP
-static int exynos4_tmu_suspend(struct device *dev)
+static int exynos_tmu_suspend(struct device *dev)
 {
-	exynos4_tmu_control(to_platform_device(dev), false);
+	exynos_tmu_control(to_platform_device(dev), false);
 
 	return 0;
 }
 
-static int exynos4_tmu_resume(struct device *dev)
+static int exynos_tmu_resume(struct device *dev)
 {
 	struct platform_device *pdev = to_platform_device(dev);
 
-	exynos4_tmu_initialize(pdev);
-	exynos4_tmu_control(pdev, true);
+	exynos_tmu_initialize(pdev);
+	exynos_tmu_control(pdev, true);
 
 	return 0;
 }
 
-static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
-			 exynos4_tmu_suspend, exynos4_tmu_resume);
-#define EXYNOS4_TMU_PM	(&exynos4_tmu_pm)
+static SIMPLE_DEV_PM_OPS(exynos_tmu_pm,
+			 exynos_tmu_suspend, exynos_tmu_resume);
+#define EXYNOS_TMU_PM	(&exynos_tmu_pm)
 #else
-#define EXYNOS4_TMU_PM	NULL
+#define EXYNOS_TMU_PM	NULL
 #endif
 
-static struct platform_driver exynos4_tmu_driver = {
+static struct platform_driver exynos_tmu_driver = {
 	.driver = {
-		.name   = "exynos4-tmu",
+		.name   = "exynos-tmu",
 		.owner  = THIS_MODULE,
-		.pm     = EXYNOS4_TMU_PM,
+		.pm     = EXYNOS_TMU_PM,
 	},
-	.probe = exynos4_tmu_probe,
-	.remove	= __devexit_p(exynos4_tmu_remove),
+	.probe = exynos_tmu_probe,
+	.remove	= __devexit_p(exynos_tmu_remove),
 };
 
-module_platform_driver(exynos4_tmu_driver);
+module_platform_driver(exynos_tmu_driver);
 
-MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
+MODULE_DESCRIPTION("EXYNOS TMU Driver");
 MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
 MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:exynos4-tmu");
+MODULE_ALIAS("platform:exynos-tmu");
diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h
index d6c3f93..a9e960a 100644
--- a/include/linux/platform_data/exynos_thermal.h
+++ b/include/linux/platform_data/exynos_thermal.h
@@ -1,5 +1,5 @@
 /*
- * exynos_thermal.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
+ * exynos_thermal.h - Samsung EXYNOS TMU (Thermal Management Unit)
  *
  *  Copyright (C) 2011 Samsung Electronics
  *  Donggeun Kim <dg77.kim@samsung.com>
@@ -28,8 +28,12 @@ enum calibration_type {
 	TYPE_NONE,
 };
 
+enum soc_type {
+	SOC_ARCH_EXYNOS4210 = 1,
+	SOC_ARCH_EXYNOS,
+};
 /**
- * struct exynos4_tmu_platform_data
+ * struct exynos_tmu_platform_data
  * @threshold: basic temperature for generating interrupt
  *	       25 <= threshold <= 125 [unit: degree Celsius]
  * @trigger_levels: array for each interrupt levels
@@ -63,11 +67,15 @@ enum calibration_type {
  * @reference_voltage: reference voltage of amplifier
  *	in the positive-TC generator block
  *	0 <= reference_voltage <= 31
+ * @noise_cancel_mode: noise cancellation mode
+ *	000, 100, 101, 110 and 111 can be different modes
+ * @type: determines the type of SOC
+ * @efuse_value: platform defined fuse value
  * @cal_type: calibration type for temperature
  *
- * This structure is required for configuration of exynos4_tmu driver.
+ * This structure is required for configuration of exynos_tmu driver.
  */
-struct exynos4_tmu_platform_data {
+struct exynos_tmu_platform_data {
 	u8 threshold;
 	u8 trigger_levels[4];
 	bool trigger_level0_en;
@@ -77,7 +85,10 @@ struct exynos4_tmu_platform_data {
 
 	u8 gain;
 	u8 reference_voltage;
+	u8 noise_cancel_mode;
+	u32 efuse_value;
 
 	enum calibration_type cal_type;
+	enum soc_type type;
 };
 #endif /* _LINUX_EXYNOS_THERMAL_H */
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 4/6] thermal: exynos: register the tmu sensor with the kernel thermal layer
  2012-08-16 11:53 ` [lm-sensors] " Amit Daniel Kachhap
  (?)
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park

This code added creates a link between temperature sensors, linux thermal
framework and cooling devices for samsung exynos platform.  This layer
monitors the temperature from the sensor and informs the generic thermal
layer to take the necessary cooling action.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 drivers/thermal/exynos_thermal.c             |  406 +++++++++++++++++++++++++-
 include/linux/platform_data/exynos_thermal.h |   22 ++
 2 files changed, 426 insertions(+), 2 deletions(-)

diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index c9a33dd..e79cdc9 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -34,6 +34,9 @@
 #include <linux/io.h>
 #include <linux/mutex.h>
 #include <linux/platform_data/exynos_thermal.h>
+#include <linux/thermal.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu_cooling.h>
 #include <linux/of.h>
 
 #include <plat/cpu.h>
@@ -94,6 +97,7 @@
 
 #define ACTIVE_INTERVAL 500
 #define IDLE_INTERVAL 10000
+#define MCELSIUS	1000
 
 /* CPU Zone information */
 #define PANIC_ZONE      4
@@ -104,6 +108,8 @@
 #define GET_ZONE(trip) (trip + 2)
 #define GET_TRIP(zone) (zone - 2)
 
+#define EXYNOS_ZONE_COUNT	3
+
 struct exynos_tmu_data {
 	struct exynos_tmu_platform_data *pdata;
 	struct resource *mem;
@@ -116,6 +122,371 @@ struct exynos_tmu_data {
 	u8 temp_error1, temp_error2;
 };
 
+struct	thermal_trip_point_conf {
+	int trip_val[MAX_TRIP_COUNT];
+	int trip_count;
+};
+
+struct	thermal_cooling_conf {
+	struct freq_clip_table freq_data[MAX_TRIP_COUNT];
+	int freq_clip_count;
+};
+
+struct thermal_sensor_conf {
+	char name[SENSOR_NAME_LEN];
+	int (*read_temperature)(void *data);
+	struct thermal_trip_point_conf trip_data;
+	struct thermal_cooling_conf cooling_data;
+	void *private_data;
+};
+
+struct exynos_thermal_zone {
+	enum thermal_device_mode mode;
+	struct thermal_zone_device *therm_dev;
+	struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE];
+	unsigned int cool_dev_size;
+	struct platform_device *exynos4_dev;
+	struct thermal_sensor_conf *sensor_conf;
+	bool bind;
+};
+
+static struct exynos_thermal_zone *th_zone;
+static void exynos_unregister_thermal(void);
+static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf);
+
+/* Get mode callback functions for thermal zone */
+static int exynos_get_mode(struct thermal_zone_device *thermal,
+			enum thermal_device_mode *mode)
+{
+	if (th_zone)
+		*mode = th_zone->mode;
+	return 0;
+}
+
+/* Set mode callback functions for thermal zone */
+static int exynos_set_mode(struct thermal_zone_device *thermal,
+			enum thermal_device_mode mode)
+{
+	if (!th_zone->therm_dev) {
+		pr_notice("thermal zone not registered\n");
+		return 0;
+	}
+
+	mutex_lock(&th_zone->therm_dev->lock);
+
+	if (mode == THERMAL_DEVICE_ENABLED)
+		th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+	else
+		th_zone->therm_dev->polling_delay = 0;
+
+	mutex_unlock(&th_zone->therm_dev->lock);
+
+	th_zone->mode = mode;
+	thermal_zone_device_update(th_zone->therm_dev);
+	pr_info("thermal polling set for duration=%d msec\n",
+				th_zone->therm_dev->polling_delay);
+	return 0;
+}
+
+
+/* Get trip type callback functions for thermal zone */
+static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip,
+				 enum thermal_trip_type *type)
+{
+	switch (GET_ZONE(trip)) {
+	case MONITOR_ZONE:
+	case WARN_ZONE:
+		*type = THERMAL_TRIP_ACTIVE;
+		break;
+	case PANIC_ZONE:
+		*type = THERMAL_TRIP_CRITICAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/* Get trip temperature callback functions for thermal zone */
+static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip,
+				unsigned long *temp)
+{
+	if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE))
+		return -EINVAL;
+
+	*temp = th_zone->sensor_conf->trip_data.trip_val[trip];
+	/* convert the temperature into millicelsius */
+	*temp = *temp * MCELSIUS;
+
+	return 0;
+}
+
+/* Get critical temperature callback functions for thermal zone */
+static int exynos_get_crit_temp(struct thermal_zone_device *thermal,
+				unsigned long *temp)
+{
+	int ret;
+	/* Panic zone */
+	ret = exynos_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp);
+	return ret;
+}
+
+static int exynos_get_frequency_level(unsigned int cpu, unsigned int freq)
+{
+	int i = 0, ret = -EINVAL;
+	struct cpufreq_frequency_table *table = NULL;
+#ifdef CONFIG_CPU_FREQ
+	table = cpufreq_frequency_get_table(cpu);
+#endif
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		if (table[i].frequency == freq)
+			return i;
+		i++;
+	}
+	return ret;
+}
+
+/* Bind callback functions for thermal zone */
+static int exynos_bind(struct thermal_zone_device *thermal,
+			struct thermal_cooling_device *cdev)
+{
+	int ret = 0, i, tab_size, level;
+	struct freq_clip_table *tab_ptr, *clip_data;
+	struct thermal_sensor_conf *data = th_zone->sensor_conf;
+
+	tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data;
+	tab_size = data->cooling_data.freq_clip_count;
+
+	if (tab_ptr == NULL || tab_size == 0)
+		return -EINVAL;
+
+	/* find the cooling device registered*/
+	for (i = 0; i < th_zone->cool_dev_size; i++)
+		if (cdev == th_zone->cool_dev[i])
+			break;
+
+	/* No matching cooling device */
+	if (i == th_zone->cool_dev_size)
+		return 0;
+
+	/* Bind the thermal zone to the cpufreq cooling device */
+	for (i = 0; i < tab_size; i++) {
+		clip_data = (struct freq_clip_table *)&(tab_ptr[i]);
+		level = exynos_get_frequency_level(0, clip_data->freq_clip_max);
+		if (level < 0)
+			return 0;
+		switch (GET_ZONE(i)) {
+		case MONITOR_ZONE:
+		case WARN_ZONE:
+			if (thermal_zone_bind_cooling_device(thermal, i, cdev,
+								level, level)) {
+				pr_err("error binding cdev inst %d\n", i);
+				ret = -EINVAL;
+			}
+			th_zone->bind = true;
+			break;
+		default:
+			ret = -EINVAL;
+		}
+	}
+
+	return ret;
+}
+
+/* Unbind callback functions for thermal zone */
+static int exynos_unbind(struct thermal_zone_device *thermal,
+			struct thermal_cooling_device *cdev)
+{
+	int ret = 0, i, tab_size;
+	struct thermal_sensor_conf *data = th_zone->sensor_conf;
+
+	if (th_zone->bind == false)
+		return 0;
+
+	tab_size = data->cooling_data.freq_clip_count;
+
+	if (tab_size == 0)
+		return -EINVAL;
+
+	/* find the cooling device registered*/
+	for (i = 0; i < th_zone->cool_dev_size; i++)
+		if (cdev == th_zone->cool_dev[i])
+			break;
+
+	/* No matching cooling device */
+	if (i == th_zone->cool_dev_size)
+		return 0;
+
+	/* Bind the thermal zone to the cpufreq cooling device */
+	for (i = 0; i < tab_size; i++) {
+		switch (GET_ZONE(i)) {
+		case MONITOR_ZONE:
+		case WARN_ZONE:
+			if (thermal_zone_unbind_cooling_device(thermal, i,
+								cdev)) {
+				pr_err("error unbinding cdev inst=%d\n", i);
+				ret = -EINVAL;
+			}
+			th_zone->bind = false;
+			break;
+		default:
+			ret = -EINVAL;
+		}
+	}
+	return ret;
+}
+
+/* Get temperature callback functions for thermal zone */
+static int exynos_get_temp(struct thermal_zone_device *thermal,
+			unsigned long *temp)
+{
+	void *data;
+
+	if (!th_zone->sensor_conf) {
+		pr_info("Temperature sensor not initialised\n");
+		return -EINVAL;
+	}
+	data = th_zone->sensor_conf->private_data;
+	*temp = th_zone->sensor_conf->read_temperature(data);
+	/* convert the temperature into millicelsius */
+	*temp = *temp * MCELSIUS;
+	return 0;
+}
+
+/* Get the temperature trend */
+static int exynos_get_trend(struct thermal_zone_device *thermal,
+			int trip, enum thermal_trend *trend)
+{
+	if (thermal->temperature >= trip)
+		*trend = THERMAL_TREND_RAISING;
+	else
+		*trend = THERMAL_TREND_DROPPING;
+
+	return 0;
+}
+/* Operation callback functions for thermal zone */
+static struct thermal_zone_device_ops const exynos_dev_ops = {
+	.bind = exynos_bind,
+	.unbind = exynos_unbind,
+	.get_temp = exynos_get_temp,
+	.get_trend = exynos_get_trend,
+	.get_mode = exynos_get_mode,
+	.set_mode = exynos_set_mode,
+	.get_trip_type = exynos_get_trip_type,
+	.get_trip_temp = exynos_get_trip_temp,
+	.get_crit_temp = exynos_get_crit_temp,
+};
+
+/*
+ * This function may be called from interrupt based temperature sensor
+ * when threshold is changed.
+ */
+static void exynos_report_trigger(void)
+{
+	unsigned int i;
+	char data[10];
+	char *envp[] = { data, NULL };
+
+	if (!th_zone || !th_zone->therm_dev)
+		return;
+	if (th_zone->bind == false) {
+		for (i = 0; i < th_zone->cool_dev_size; i++) {
+			if (!th_zone->cool_dev[i])
+				continue;
+			exynos_bind(th_zone->therm_dev,
+					th_zone->cool_dev[i]);
+		}
+	}
+
+	thermal_zone_device_update(th_zone->therm_dev);
+
+	mutex_lock(&th_zone->therm_dev->lock);
+	/* Find the level for which trip happened */
+	for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) {
+		if (th_zone->therm_dev->last_temperature <
+			th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS)
+			break;
+	}
+
+	if (th_zone->mode == THERMAL_DEVICE_ENABLED) {
+		if (i > 0)
+			th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
+		else
+			th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+	}
+
+	snprintf(data, sizeof(data), "%u", i);
+	kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp);
+	mutex_unlock(&th_zone->therm_dev->lock);
+}
+
+/* Register with the in-kernel thermal management */
+static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
+{
+	int ret;
+	struct cpumask mask_val;
+
+	if (!sensor_conf || !sensor_conf->read_temperature) {
+		pr_err("Temperature sensor not initialised\n");
+		return -EINVAL;
+	}
+
+	th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL);
+	if (!th_zone)
+		return -ENOMEM;
+
+	th_zone->sensor_conf = sensor_conf;
+	cpumask_set_cpu(0, &mask_val);
+	th_zone->cool_dev[0] = cpufreq_cooling_register(&mask_val);
+	if (IS_ERR(th_zone->cool_dev[0])) {
+		pr_err("Failed to register cpufreq cooling device\n");
+		ret = -EINVAL;
+		goto err_unregister;
+	}
+	th_zone->cool_dev_size++;
+
+	th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name,
+			EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, 0,
+			IDLE_INTERVAL);
+
+	if (IS_ERR(th_zone->therm_dev)) {
+		pr_err("Failed to register thermal zone device\n");
+		ret = -EINVAL;
+		goto err_unregister;
+	}
+	th_zone->mode = THERMAL_DEVICE_ENABLED;
+
+	pr_info("Exynos: Kernel Thermal management registered\n");
+
+	return 0;
+
+err_unregister:
+	exynos_unregister_thermal();
+	return ret;
+}
+
+/* Un-Register with the in-kernel thermal management */
+static void exynos_unregister_thermal(void)
+{
+	int i;
+
+	if (th_zone && th_zone->therm_dev)
+		thermal_zone_device_unregister(th_zone->therm_dev);
+
+	for (i = 0; i < th_zone->cool_dev_size; i++) {
+		if (th_zone && th_zone->cool_dev[i])
+			cpufreq_cooling_unregister(th_zone->cool_dev[i]);
+	}
+
+	kfree(th_zone);
+	pr_info("Exynos: Kernel Thermal management unregistered\n");
+}
+
 /*
  * TMU treats temperature as a mapped temperature code.
  * The temperature is converted differently depending on the calibration type.
@@ -336,6 +707,7 @@ static void exynos_tmu_work(struct work_struct *work)
 
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
+	exynos_report_trigger();
 	enable_irq(data->irq);
 }
 
@@ -348,12 +720,16 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id)
 
 	return IRQ_HANDLED;
 }
-
+static struct thermal_sensor_conf exynos_sensor_conf = {
+	.name			= "exynos-therm",
+	.read_temperature	= (int (*)(void *))exynos_tmu_read,
+}
+;
 static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 {
 	struct exynos_tmu_data *data;
 	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
-	int ret;
+	int ret, i;
 
 	if (!pdata) {
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
@@ -431,6 +807,30 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 
 	exynos_tmu_control(pdev, true);
 
+	/* Register the sensor with thermal management interface */
+	(&exynos_sensor_conf)->private_data = data;
+	exynos_sensor_conf.trip_data.trip_count = pdata->trigger_level0_en +
+			pdata->trigger_level1_en + pdata->trigger_level2_en +
+			pdata->trigger_level3_en;
+
+	for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++)
+		exynos_sensor_conf.trip_data.trip_val[i] =
+			pdata->threshold + pdata->trigger_levels[i];
+
+	exynos_sensor_conf.cooling_data.freq_clip_count =
+						pdata->freq_tab_count;
+	for (i = 0; i < pdata->freq_tab_count; i++) {
+		exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max =
+					pdata->freq_tab[i].freq_clip_max;
+		exynos_sensor_conf.cooling_data.freq_data[i].temp_level =
+					pdata->freq_tab[i].temp_level;
+	}
+
+	ret = exynos_register_thermal(&exynos_sensor_conf);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register thermal interface\n");
+		goto err_clk;
+	}
 	return 0;
 err_clk:
 	platform_set_drvdata(pdev, NULL);
@@ -453,6 +853,8 @@ static int __devexit exynos_tmu_remove(struct platform_device *pdev)
 
 	exynos_tmu_control(pdev, false);
 
+	exynos_unregister_thermal();
+
 	clk_put(data->clk);
 
 	free_irq(data->irq, data);
diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h
index a9e960a..a7bdb2f 100644
--- a/include/linux/platform_data/exynos_thermal.h
+++ b/include/linux/platform_data/exynos_thermal.h
@@ -21,6 +21,7 @@
 
 #ifndef _LINUX_EXYNOS_THERMAL_H
 #define _LINUX_EXYNOS_THERMAL_H
+#include <linux/cpu_cooling.h>
 
 enum calibration_type {
 	TYPE_ONE_POINT_TRIMMING,
@@ -33,6 +34,22 @@ enum soc_type {
 	SOC_ARCH_EXYNOS,
 };
 /**
+ * struct freq_clip_table
+ * @freq_clip_max: maximum frequency allowed for this cooling state.
+ * @temp_level: Temperature level at which the temperature clipping will
+ *	happen.
+ * @mask_val: cpumask of the allowed cpu's where the clipping will take place.
+ *
+ * This structure is required to be filled and passed to the
+ * cpufreq_cooling_unregister function.
+ */
+struct freq_clip_table {
+	unsigned int freq_clip_max;
+	unsigned int temp_level;
+	const struct cpumask *mask_val;
+};
+
+/**
  * struct exynos_tmu_platform_data
  * @threshold: basic temperature for generating interrupt
  *	       25 <= threshold <= 125 [unit: degree Celsius]
@@ -72,6 +89,9 @@ enum soc_type {
  * @type: determines the type of SOC
  * @efuse_value: platform defined fuse value
  * @cal_type: calibration type for temperature
+ * @freq_clip_table: Table representing frequency reduction percentage.
+ * @freq_tab_count: Count of the above table as frequency reduction may
+ *	applicable to only some of the trigger levels.
  *
  * This structure is required for configuration of exynos_tmu driver.
  */
@@ -90,5 +110,7 @@ struct exynos_tmu_platform_data {
 
 	enum calibration_type cal_type;
 	enum soc_type type;
+	struct freq_clip_table freq_tab[4];
+	unsigned int freq_tab_count;
 };
 #endif /* _LINUX_EXYNOS_THERMAL_H */
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 4/6] thermal: exynos: register the tmu sensor with the kernel thermal layer
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	amit.kachhap, SangWook Ju, Durgadoss, Len Brown, Jean Delvare,
	Kyungmin Park, Kukjin Kim

This code added creates a link between temperature sensors, linux thermal
framework and cooling devices for samsung exynos platform.  This layer
monitors the temperature from the sensor and informs the generic thermal
layer to take the necessary cooling action.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 drivers/thermal/exynos_thermal.c             |  406 +++++++++++++++++++++++++-
 include/linux/platform_data/exynos_thermal.h |   22 ++
 2 files changed, 426 insertions(+), 2 deletions(-)

diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index c9a33dd..e79cdc9 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -34,6 +34,9 @@
 #include <linux/io.h>
 #include <linux/mutex.h>
 #include <linux/platform_data/exynos_thermal.h>
+#include <linux/thermal.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu_cooling.h>
 #include <linux/of.h>
 
 #include <plat/cpu.h>
@@ -94,6 +97,7 @@
 
 #define ACTIVE_INTERVAL 500
 #define IDLE_INTERVAL 10000
+#define MCELSIUS	1000
 
 /* CPU Zone information */
 #define PANIC_ZONE      4
@@ -104,6 +108,8 @@
 #define GET_ZONE(trip) (trip + 2)
 #define GET_TRIP(zone) (zone - 2)
 
+#define EXYNOS_ZONE_COUNT	3
+
 struct exynos_tmu_data {
 	struct exynos_tmu_platform_data *pdata;
 	struct resource *mem;
@@ -116,6 +122,371 @@ struct exynos_tmu_data {
 	u8 temp_error1, temp_error2;
 };
 
+struct	thermal_trip_point_conf {
+	int trip_val[MAX_TRIP_COUNT];
+	int trip_count;
+};
+
+struct	thermal_cooling_conf {
+	struct freq_clip_table freq_data[MAX_TRIP_COUNT];
+	int freq_clip_count;
+};
+
+struct thermal_sensor_conf {
+	char name[SENSOR_NAME_LEN];
+	int (*read_temperature)(void *data);
+	struct thermal_trip_point_conf trip_data;
+	struct thermal_cooling_conf cooling_data;
+	void *private_data;
+};
+
+struct exynos_thermal_zone {
+	enum thermal_device_mode mode;
+	struct thermal_zone_device *therm_dev;
+	struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE];
+	unsigned int cool_dev_size;
+	struct platform_device *exynos4_dev;
+	struct thermal_sensor_conf *sensor_conf;
+	bool bind;
+};
+
+static struct exynos_thermal_zone *th_zone;
+static void exynos_unregister_thermal(void);
+static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf);
+
+/* Get mode callback functions for thermal zone */
+static int exynos_get_mode(struct thermal_zone_device *thermal,
+			enum thermal_device_mode *mode)
+{
+	if (th_zone)
+		*mode = th_zone->mode;
+	return 0;
+}
+
+/* Set mode callback functions for thermal zone */
+static int exynos_set_mode(struct thermal_zone_device *thermal,
+			enum thermal_device_mode mode)
+{
+	if (!th_zone->therm_dev) {
+		pr_notice("thermal zone not registered\n");
+		return 0;
+	}
+
+	mutex_lock(&th_zone->therm_dev->lock);
+
+	if (mode == THERMAL_DEVICE_ENABLED)
+		th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+	else
+		th_zone->therm_dev->polling_delay = 0;
+
+	mutex_unlock(&th_zone->therm_dev->lock);
+
+	th_zone->mode = mode;
+	thermal_zone_device_update(th_zone->therm_dev);
+	pr_info("thermal polling set for duration=%d msec\n",
+				th_zone->therm_dev->polling_delay);
+	return 0;
+}
+
+
+/* Get trip type callback functions for thermal zone */
+static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip,
+				 enum thermal_trip_type *type)
+{
+	switch (GET_ZONE(trip)) {
+	case MONITOR_ZONE:
+	case WARN_ZONE:
+		*type = THERMAL_TRIP_ACTIVE;
+		break;
+	case PANIC_ZONE:
+		*type = THERMAL_TRIP_CRITICAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/* Get trip temperature callback functions for thermal zone */
+static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip,
+				unsigned long *temp)
+{
+	if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE))
+		return -EINVAL;
+
+	*temp = th_zone->sensor_conf->trip_data.trip_val[trip];
+	/* convert the temperature into millicelsius */
+	*temp = *temp * MCELSIUS;
+
+	return 0;
+}
+
+/* Get critical temperature callback functions for thermal zone */
+static int exynos_get_crit_temp(struct thermal_zone_device *thermal,
+				unsigned long *temp)
+{
+	int ret;
+	/* Panic zone */
+	ret = exynos_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp);
+	return ret;
+}
+
+static int exynos_get_frequency_level(unsigned int cpu, unsigned int freq)
+{
+	int i = 0, ret = -EINVAL;
+	struct cpufreq_frequency_table *table = NULL;
+#ifdef CONFIG_CPU_FREQ
+	table = cpufreq_frequency_get_table(cpu);
+#endif
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		if (table[i].frequency == freq)
+			return i;
+		i++;
+	}
+	return ret;
+}
+
+/* Bind callback functions for thermal zone */
+static int exynos_bind(struct thermal_zone_device *thermal,
+			struct thermal_cooling_device *cdev)
+{
+	int ret = 0, i, tab_size, level;
+	struct freq_clip_table *tab_ptr, *clip_data;
+	struct thermal_sensor_conf *data = th_zone->sensor_conf;
+
+	tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data;
+	tab_size = data->cooling_data.freq_clip_count;
+
+	if (tab_ptr == NULL || tab_size == 0)
+		return -EINVAL;
+
+	/* find the cooling device registered*/
+	for (i = 0; i < th_zone->cool_dev_size; i++)
+		if (cdev == th_zone->cool_dev[i])
+			break;
+
+	/* No matching cooling device */
+	if (i == th_zone->cool_dev_size)
+		return 0;
+
+	/* Bind the thermal zone to the cpufreq cooling device */
+	for (i = 0; i < tab_size; i++) {
+		clip_data = (struct freq_clip_table *)&(tab_ptr[i]);
+		level = exynos_get_frequency_level(0, clip_data->freq_clip_max);
+		if (level < 0)
+			return 0;
+		switch (GET_ZONE(i)) {
+		case MONITOR_ZONE:
+		case WARN_ZONE:
+			if (thermal_zone_bind_cooling_device(thermal, i, cdev,
+								level, level)) {
+				pr_err("error binding cdev inst %d\n", i);
+				ret = -EINVAL;
+			}
+			th_zone->bind = true;
+			break;
+		default:
+			ret = -EINVAL;
+		}
+	}
+
+	return ret;
+}
+
+/* Unbind callback functions for thermal zone */
+static int exynos_unbind(struct thermal_zone_device *thermal,
+			struct thermal_cooling_device *cdev)
+{
+	int ret = 0, i, tab_size;
+	struct thermal_sensor_conf *data = th_zone->sensor_conf;
+
+	if (th_zone->bind == false)
+		return 0;
+
+	tab_size = data->cooling_data.freq_clip_count;
+
+	if (tab_size == 0)
+		return -EINVAL;
+
+	/* find the cooling device registered*/
+	for (i = 0; i < th_zone->cool_dev_size; i++)
+		if (cdev == th_zone->cool_dev[i])
+			break;
+
+	/* No matching cooling device */
+	if (i == th_zone->cool_dev_size)
+		return 0;
+
+	/* Bind the thermal zone to the cpufreq cooling device */
+	for (i = 0; i < tab_size; i++) {
+		switch (GET_ZONE(i)) {
+		case MONITOR_ZONE:
+		case WARN_ZONE:
+			if (thermal_zone_unbind_cooling_device(thermal, i,
+								cdev)) {
+				pr_err("error unbinding cdev inst=%d\n", i);
+				ret = -EINVAL;
+			}
+			th_zone->bind = false;
+			break;
+		default:
+			ret = -EINVAL;
+		}
+	}
+	return ret;
+}
+
+/* Get temperature callback functions for thermal zone */
+static int exynos_get_temp(struct thermal_zone_device *thermal,
+			unsigned long *temp)
+{
+	void *data;
+
+	if (!th_zone->sensor_conf) {
+		pr_info("Temperature sensor not initialised\n");
+		return -EINVAL;
+	}
+	data = th_zone->sensor_conf->private_data;
+	*temp = th_zone->sensor_conf->read_temperature(data);
+	/* convert the temperature into millicelsius */
+	*temp = *temp * MCELSIUS;
+	return 0;
+}
+
+/* Get the temperature trend */
+static int exynos_get_trend(struct thermal_zone_device *thermal,
+			int trip, enum thermal_trend *trend)
+{
+	if (thermal->temperature >= trip)
+		*trend = THERMAL_TREND_RAISING;
+	else
+		*trend = THERMAL_TREND_DROPPING;
+
+	return 0;
+}
+/* Operation callback functions for thermal zone */
+static struct thermal_zone_device_ops const exynos_dev_ops = {
+	.bind = exynos_bind,
+	.unbind = exynos_unbind,
+	.get_temp = exynos_get_temp,
+	.get_trend = exynos_get_trend,
+	.get_mode = exynos_get_mode,
+	.set_mode = exynos_set_mode,
+	.get_trip_type = exynos_get_trip_type,
+	.get_trip_temp = exynos_get_trip_temp,
+	.get_crit_temp = exynos_get_crit_temp,
+};
+
+/*
+ * This function may be called from interrupt based temperature sensor
+ * when threshold is changed.
+ */
+static void exynos_report_trigger(void)
+{
+	unsigned int i;
+	char data[10];
+	char *envp[] = { data, NULL };
+
+	if (!th_zone || !th_zone->therm_dev)
+		return;
+	if (th_zone->bind == false) {
+		for (i = 0; i < th_zone->cool_dev_size; i++) {
+			if (!th_zone->cool_dev[i])
+				continue;
+			exynos_bind(th_zone->therm_dev,
+					th_zone->cool_dev[i]);
+		}
+	}
+
+	thermal_zone_device_update(th_zone->therm_dev);
+
+	mutex_lock(&th_zone->therm_dev->lock);
+	/* Find the level for which trip happened */
+	for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) {
+		if (th_zone->therm_dev->last_temperature <
+			th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS)
+			break;
+	}
+
+	if (th_zone->mode == THERMAL_DEVICE_ENABLED) {
+		if (i > 0)
+			th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
+		else
+			th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+	}
+
+	snprintf(data, sizeof(data), "%u", i);
+	kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp);
+	mutex_unlock(&th_zone->therm_dev->lock);
+}
+
+/* Register with the in-kernel thermal management */
+static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
+{
+	int ret;
+	struct cpumask mask_val;
+
+	if (!sensor_conf || !sensor_conf->read_temperature) {
+		pr_err("Temperature sensor not initialised\n");
+		return -EINVAL;
+	}
+
+	th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL);
+	if (!th_zone)
+		return -ENOMEM;
+
+	th_zone->sensor_conf = sensor_conf;
+	cpumask_set_cpu(0, &mask_val);
+	th_zone->cool_dev[0] = cpufreq_cooling_register(&mask_val);
+	if (IS_ERR(th_zone->cool_dev[0])) {
+		pr_err("Failed to register cpufreq cooling device\n");
+		ret = -EINVAL;
+		goto err_unregister;
+	}
+	th_zone->cool_dev_size++;
+
+	th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name,
+			EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, 0,
+			IDLE_INTERVAL);
+
+	if (IS_ERR(th_zone->therm_dev)) {
+		pr_err("Failed to register thermal zone device\n");
+		ret = -EINVAL;
+		goto err_unregister;
+	}
+	th_zone->mode = THERMAL_DEVICE_ENABLED;
+
+	pr_info("Exynos: Kernel Thermal management registered\n");
+
+	return 0;
+
+err_unregister:
+	exynos_unregister_thermal();
+	return ret;
+}
+
+/* Un-Register with the in-kernel thermal management */
+static void exynos_unregister_thermal(void)
+{
+	int i;
+
+	if (th_zone && th_zone->therm_dev)
+		thermal_zone_device_unregister(th_zone->therm_dev);
+
+	for (i = 0; i < th_zone->cool_dev_size; i++) {
+		if (th_zone && th_zone->cool_dev[i])
+			cpufreq_cooling_unregister(th_zone->cool_dev[i]);
+	}
+
+	kfree(th_zone);
+	pr_info("Exynos: Kernel Thermal management unregistered\n");
+}
+
 /*
  * TMU treats temperature as a mapped temperature code.
  * The temperature is converted differently depending on the calibration type.
@@ -336,6 +707,7 @@ static void exynos_tmu_work(struct work_struct *work)
 
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
+	exynos_report_trigger();
 	enable_irq(data->irq);
 }
 
@@ -348,12 +720,16 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id)
 
 	return IRQ_HANDLED;
 }
-
+static struct thermal_sensor_conf exynos_sensor_conf = {
+	.name			= "exynos-therm",
+	.read_temperature	= (int (*)(void *))exynos_tmu_read,
+}
+;
 static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 {
 	struct exynos_tmu_data *data;
 	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
-	int ret;
+	int ret, i;
 
 	if (!pdata) {
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
@@ -431,6 +807,30 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 
 	exynos_tmu_control(pdev, true);
 
+	/* Register the sensor with thermal management interface */
+	(&exynos_sensor_conf)->private_data = data;
+	exynos_sensor_conf.trip_data.trip_count = pdata->trigger_level0_en +
+			pdata->trigger_level1_en + pdata->trigger_level2_en +
+			pdata->trigger_level3_en;
+
+	for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++)
+		exynos_sensor_conf.trip_data.trip_val[i] =
+			pdata->threshold + pdata->trigger_levels[i];
+
+	exynos_sensor_conf.cooling_data.freq_clip_count =
+						pdata->freq_tab_count;
+	for (i = 0; i < pdata->freq_tab_count; i++) {
+		exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max =
+					pdata->freq_tab[i].freq_clip_max;
+		exynos_sensor_conf.cooling_data.freq_data[i].temp_level =
+					pdata->freq_tab[i].temp_level;
+	}
+
+	ret = exynos_register_thermal(&exynos_sensor_conf);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register thermal interface\n");
+		goto err_clk;
+	}
 	return 0;
 err_clk:
 	platform_set_drvdata(pdev, NULL);
@@ -453,6 +853,8 @@ static int __devexit exynos_tmu_remove(struct platform_device *pdev)
 
 	exynos_tmu_control(pdev, false);
 
+	exynos_unregister_thermal();
+
 	clk_put(data->clk);
 
 	free_irq(data->irq, data);
diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h
index a9e960a..a7bdb2f 100644
--- a/include/linux/platform_data/exynos_thermal.h
+++ b/include/linux/platform_data/exynos_thermal.h
@@ -21,6 +21,7 @@
 
 #ifndef _LINUX_EXYNOS_THERMAL_H
 #define _LINUX_EXYNOS_THERMAL_H
+#include <linux/cpu_cooling.h>
 
 enum calibration_type {
 	TYPE_ONE_POINT_TRIMMING,
@@ -33,6 +34,22 @@ enum soc_type {
 	SOC_ARCH_EXYNOS,
 };
 /**
+ * struct freq_clip_table
+ * @freq_clip_max: maximum frequency allowed for this cooling state.
+ * @temp_level: Temperature level at which the temperature clipping will
+ *	happen.
+ * @mask_val: cpumask of the allowed cpu's where the clipping will take place.
+ *
+ * This structure is required to be filled and passed to the
+ * cpufreq_cooling_unregister function.
+ */
+struct freq_clip_table {
+	unsigned int freq_clip_max;
+	unsigned int temp_level;
+	const struct cpumask *mask_val;
+};
+
+/**
  * struct exynos_tmu_platform_data
  * @threshold: basic temperature for generating interrupt
  *	       25 <= threshold <= 125 [unit: degree Celsius]
@@ -72,6 +89,9 @@ enum soc_type {
  * @type: determines the type of SOC
  * @efuse_value: platform defined fuse value
  * @cal_type: calibration type for temperature
+ * @freq_clip_table: Table representing frequency reduction percentage.
+ * @freq_tab_count: Count of the above table as frequency reduction may
+ *	applicable to only some of the trigger levels.
  *
  * This structure is required for configuration of exynos_tmu driver.
  */
@@ -90,5 +110,7 @@ struct exynos_tmu_platform_data {
 
 	enum calibration_type cal_type;
 	enum soc_type type;
+	struct freq_clip_table freq_tab[4];
+	unsigned int freq_tab_count;
 };
 #endif /* _LINUX_EXYNOS_THERMAL_H */
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 5/6] ARM: exynos: add thermal sensor driver platform data support
  2012-08-16 11:53 ` [lm-sensors] " Amit Daniel Kachhap
  (?)
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: jonghwa lee, linux-samsung-soc, SangWook Ju, linux-kernel,
	lm-sensors, linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park

Add necessary default platform data support needed for TMU driver.  The
supplied dt/non-dt values are tested for origen exynos4210 and smdk exynos5250
platforms and only compile tested for exynos4412.

Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: jonghwa lee <jonghwa3.lee@samsung.com>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 drivers/thermal/exynos_thermal.c |  111 +++++++++++++++++++++++++++++++++++++-
 1 files changed, 110 insertions(+), 1 deletions(-)

diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index e79cdc9..03a99e4 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -723,14 +723,121 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id)
 static struct thermal_sensor_conf exynos_sensor_conf = {
 	.name			= "exynos-therm",
 	.read_temperature	= (int (*)(void *))exynos_tmu_read,
+};
+
+#if defined(CONFIG_CPU_EXYNOS4210)
+static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = {
+	.threshold = 80,
+	.trigger_levels[0] = 5,
+	.trigger_levels[1] = 20,
+	.trigger_levels[2] = 30,
+	.trigger_level0_en = 1,
+	.trigger_level1_en = 1,
+	.trigger_level2_en = 1,
+	.trigger_level3_en = 0,
+	.gain = 15,
+	.reference_voltage = 7,
+	.cal_type = TYPE_ONE_POINT_TRIMMING,
+	.freq_tab[0] = {
+		.freq_clip_max = 800 * 1000,
+		.temp_level = 85,
+	},
+	.freq_tab[1] = {
+		.freq_clip_max = 200 * 1000,
+		.temp_level = 100,
+	},
+	.freq_tab_count = 2,
+	.type = SOC_ARCH_EXYNOS4210,
+};
+#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data)
+#else
+#define EXYNOS4210_TMU_DRV_DATA (NULL)
+#endif
+
+#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412)
+static struct exynos_tmu_platform_data const exynos_default_tmu_data = {
+	.trigger_levels[0] = 85,
+	.trigger_levels[1] = 103,
+	.trigger_levels[2] = 110,
+	.trigger_level0_en = 1,
+	.trigger_level1_en = 1,
+	.trigger_level2_en = 1,
+	.trigger_level3_en = 0,
+	.gain = 8,
+	.reference_voltage = 16,
+	.noise_cancel_mode = 4,
+	.cal_type = TYPE_ONE_POINT_TRIMMING,
+	.efuse_value = 55,
+	.freq_tab[0] = {
+		.freq_clip_max = 800 * 1000,
+		.temp_level = 85,
+	},
+	.freq_tab[1] = {
+		.freq_clip_max = 200 * 1000,
+		.temp_level = 103,
+	},
+	.freq_tab_count = 2,
+	.type = SOC_ARCH_EXYNOS,
+};
+#define EXYNOS_TMU_DRV_DATA (&exynos_default_tmu_data)
+#else
+#define EXYNOS_TMU_DRV_DATA (NULL)
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id exynos_tmu_match[] = {
+	{
+		.compatible = "samsung,exynos4210-tmu",
+		.data = (void *)EXYNOS4210_TMU_DRV_DATA,
+	},
+	{
+		.compatible = "samsung,exynos5250-tmu",
+		.data = (void *)EXYNOS_TMU_DRV_DATA,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos_tmu_match);
+#else
+#define  exynos_tmu_match NULL
+#endif
+
+static struct platform_device_id exynos_tmu_driver_ids[] = {
+	{
+		.name		= "exynos4210-tmu",
+		.driver_data    = (kernel_ulong_t)EXYNOS4210_TMU_DRV_DATA,
+	},
+	{
+		.name		= "exynos5250-tmu",
+		.driver_data    = (kernel_ulong_t)EXYNOS_TMU_DRV_DATA,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(platform, exynos4_tmu_driver_ids);
+
+static inline struct  exynos_tmu_platform_data *exynos_get_driver_data(
+			struct platform_device *pdev)
+{
+#ifdef CONFIG_OF
+	if (pdev->dev.of_node) {
+		const struct of_device_id *match;
+		match = of_match_node(exynos_tmu_match, pdev->dev.of_node);
+		if (!match)
+			return NULL;
+		return (struct exynos_tmu_platform_data *) match->data;
+	}
+#endif
+	return (struct exynos_tmu_platform_data *)
+			platform_get_device_id(pdev)->driver_data;
 }
-;
 static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 {
 	struct exynos_tmu_data *data;
 	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
 	int ret, i;
 
+	if (!pdata)
+		pdata = exynos_get_driver_data(pdev);
+
 	if (!pdata) {
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
 		return -ENODEV;
@@ -899,9 +1006,11 @@ static struct platform_driver exynos_tmu_driver = {
 		.name   = "exynos-tmu",
 		.owner  = THIS_MODULE,
 		.pm     = EXYNOS_TMU_PM,
+		.of_match_table = exynos_tmu_match,
 	},
 	.probe = exynos_tmu_probe,
 	.remove	= __devexit_p(exynos_tmu_remove),
+	.id_table = exynos_tmu_driver_ids,
 };
 
 module_platform_driver(exynos_tmu_driver);
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 5/6] ARM: exynos: add thermal sensor driver platform data support
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	amit.kachhap, SangWook Ju, Durgadoss, Len Brown, Jean Delvare,
	jonghwa lee, Kyungmin Park, Kukjin Kim

Add necessary default platform data support needed for TMU driver.  The
supplied dt/non-dt values are tested for origen exynos4210 and smdk exynos5250
platforms and only compile tested for exynos4412.

Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: jonghwa lee <jonghwa3.lee@samsung.com>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 drivers/thermal/exynos_thermal.c |  111 +++++++++++++++++++++++++++++++++++++-
 1 files changed, 110 insertions(+), 1 deletions(-)

diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index e79cdc9..03a99e4 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -723,14 +723,121 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id)
 static struct thermal_sensor_conf exynos_sensor_conf = {
 	.name			= "exynos-therm",
 	.read_temperature	= (int (*)(void *))exynos_tmu_read,
+};
+
+#if defined(CONFIG_CPU_EXYNOS4210)
+static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = {
+	.threshold = 80,
+	.trigger_levels[0] = 5,
+	.trigger_levels[1] = 20,
+	.trigger_levels[2] = 30,
+	.trigger_level0_en = 1,
+	.trigger_level1_en = 1,
+	.trigger_level2_en = 1,
+	.trigger_level3_en = 0,
+	.gain = 15,
+	.reference_voltage = 7,
+	.cal_type = TYPE_ONE_POINT_TRIMMING,
+	.freq_tab[0] = {
+		.freq_clip_max = 800 * 1000,
+		.temp_level = 85,
+	},
+	.freq_tab[1] = {
+		.freq_clip_max = 200 * 1000,
+		.temp_level = 100,
+	},
+	.freq_tab_count = 2,
+	.type = SOC_ARCH_EXYNOS4210,
+};
+#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data)
+#else
+#define EXYNOS4210_TMU_DRV_DATA (NULL)
+#endif
+
+#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412)
+static struct exynos_tmu_platform_data const exynos_default_tmu_data = {
+	.trigger_levels[0] = 85,
+	.trigger_levels[1] = 103,
+	.trigger_levels[2] = 110,
+	.trigger_level0_en = 1,
+	.trigger_level1_en = 1,
+	.trigger_level2_en = 1,
+	.trigger_level3_en = 0,
+	.gain = 8,
+	.reference_voltage = 16,
+	.noise_cancel_mode = 4,
+	.cal_type = TYPE_ONE_POINT_TRIMMING,
+	.efuse_value = 55,
+	.freq_tab[0] = {
+		.freq_clip_max = 800 * 1000,
+		.temp_level = 85,
+	},
+	.freq_tab[1] = {
+		.freq_clip_max = 200 * 1000,
+		.temp_level = 103,
+	},
+	.freq_tab_count = 2,
+	.type = SOC_ARCH_EXYNOS,
+};
+#define EXYNOS_TMU_DRV_DATA (&exynos_default_tmu_data)
+#else
+#define EXYNOS_TMU_DRV_DATA (NULL)
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id exynos_tmu_match[] = {
+	{
+		.compatible = "samsung,exynos4210-tmu",
+		.data = (void *)EXYNOS4210_TMU_DRV_DATA,
+	},
+	{
+		.compatible = "samsung,exynos5250-tmu",
+		.data = (void *)EXYNOS_TMU_DRV_DATA,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos_tmu_match);
+#else
+#define  exynos_tmu_match NULL
+#endif
+
+static struct platform_device_id exynos_tmu_driver_ids[] = {
+	{
+		.name		= "exynos4210-tmu",
+		.driver_data    = (kernel_ulong_t)EXYNOS4210_TMU_DRV_DATA,
+	},
+	{
+		.name		= "exynos5250-tmu",
+		.driver_data    = (kernel_ulong_t)EXYNOS_TMU_DRV_DATA,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(platform, exynos4_tmu_driver_ids);
+
+static inline struct  exynos_tmu_platform_data *exynos_get_driver_data(
+			struct platform_device *pdev)
+{
+#ifdef CONFIG_OF
+	if (pdev->dev.of_node) {
+		const struct of_device_id *match;
+		match = of_match_node(exynos_tmu_match, pdev->dev.of_node);
+		if (!match)
+			return NULL;
+		return (struct exynos_tmu_platform_data *) match->data;
+	}
+#endif
+	return (struct exynos_tmu_platform_data *)
+			platform_get_device_id(pdev)->driver_data;
 }
-;
 static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 {
 	struct exynos_tmu_data *data;
 	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
 	int ret, i;
 
+	if (!pdata)
+		pdata = exynos_get_driver_data(pdev);
+
 	if (!pdata) {
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
 		return -ENODEV;
@@ -899,9 +1006,11 @@ static struct platform_driver exynos_tmu_driver = {
 		.name   = "exynos-tmu",
 		.owner  = THIS_MODULE,
 		.pm     = EXYNOS_TMU_PM,
+		.of_match_table = exynos_tmu_match,
 	},
 	.probe = exynos_tmu_probe,
 	.remove	= __devexit_p(exynos_tmu_remove),
+	.id_table = exynos_tmu_driver_ids,
 };
 
 module_platform_driver(exynos_tmu_driver);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 6/6] thermal: exynos: Use devm_* functions
  2012-08-16 11:53 ` [lm-sensors] " Amit Daniel Kachhap
  (?)
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-acpi, linux-samsung-soc, linux-kernel, lm-sensors

devm_* functions are used to replace kzalloc, request_mem_region, ioremap
and request_irq functions in probe call. With the usage of devm_* functions
explicit freeing and unmapping is not required.

Signed-off-by: Sachin Kamat <sachin.kamat@linaro.org>
Signed-off-by: Sachin Kamat <sachin.kamat@samsung.com>
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
---
 drivers/thermal/exynos_thermal.c |   45 +++++++------------------------------
 1 files changed, 9 insertions(+), 36 deletions(-)

diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index 03a99e4..e84acde 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -842,7 +842,8 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
 		return -ENODEV;
 	}
-	data = kzalloc(sizeof(struct exynos_tmu_data), GFP_KERNEL);
+	data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data),
+					GFP_KERNEL);
 	if (!data) {
 		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
 		return -ENOMEM;
@@ -850,47 +851,35 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 
 	data->irq = platform_get_irq(pdev, 0);
 	if (data->irq < 0) {
-		ret = data->irq;
 		dev_err(&pdev->dev, "Failed to get platform irq\n");
-		goto err_free;
+		return data->irq;
 	}
 
 	INIT_WORK(&data->irq_work, exynos_tmu_work);
 
 	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!data->mem) {
-		ret = -ENOENT;
 		dev_err(&pdev->dev, "Failed to get platform resource\n");
-		goto err_free;
+		return -ENOENT;
 	}
 
-	data->mem = request_mem_region(data->mem->start,
-			resource_size(data->mem), pdev->name);
-	if (!data->mem) {
-		ret = -ENODEV;
-		dev_err(&pdev->dev, "Failed to request memory region\n");
-		goto err_free;
-	}
-
-	data->base = ioremap(data->mem->start, resource_size(data->mem));
+	data->base = devm_request_and_ioremap(&pdev->dev, data->mem);
 	if (!data->base) {
-		ret = -ENODEV;
 		dev_err(&pdev->dev, "Failed to ioremap memory\n");
-		goto err_mem_region;
+		return -ENODEV;
 	}
 
-	ret = request_irq(data->irq, exynos_tmu_irq,
+	ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
 		IRQF_TRIGGER_RISING, "exynos-tmu", data);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
-		goto err_io_remap;
+		return ret;
 	}
 
 	data->clk = clk_get(NULL, "tmu_apbif");
 	if (IS_ERR(data->clk)) {
-		ret = PTR_ERR(data->clk);
 		dev_err(&pdev->dev, "Failed to get clock\n");
-		goto err_irq;
+		return  PTR_ERR(data->clk);
 	}
 
 	if (pdata->type == SOC_ARCH_EXYNOS ||
@@ -942,15 +931,6 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 err_clk:
 	platform_set_drvdata(pdev, NULL);
 	clk_put(data->clk);
-err_irq:
-	free_irq(data->irq, data);
-err_io_remap:
-	iounmap(data->base);
-err_mem_region:
-	release_mem_region(data->mem->start, resource_size(data->mem));
-err_free:
-	kfree(data);
-
 	return ret;
 }
 
@@ -964,15 +944,8 @@ static int __devexit exynos_tmu_remove(struct platform_device *pdev)
 
 	clk_put(data->clk);
 
-	free_irq(data->irq, data);
-
-	iounmap(data->base);
-	release_mem_region(data->mem->start, resource_size(data->mem));
-
 	platform_set_drvdata(pdev, NULL);
 
-	kfree(data);
-
 	return 0;
 }
 
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [PATCH v6 6/6] thermal: exynos: Use devm_* functions
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors, amit.kachhap

devm_* functions are used to replace kzalloc, request_mem_region, ioremap
and request_irq functions in probe call. With the usage of devm_* functions
explicit freeing and unmapping is not required.

Signed-off-by: Sachin Kamat <sachin.kamat@linaro.org>
Signed-off-by: Sachin Kamat <sachin.kamat@samsung.com>
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
---
 drivers/thermal/exynos_thermal.c |   45 +++++++------------------------------
 1 files changed, 9 insertions(+), 36 deletions(-)

diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index 03a99e4..e84acde 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -842,7 +842,8 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
 		return -ENODEV;
 	}
-	data = kzalloc(sizeof(struct exynos_tmu_data), GFP_KERNEL);
+	data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data),
+					GFP_KERNEL);
 	if (!data) {
 		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
 		return -ENOMEM;
@@ -850,47 +851,35 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 
 	data->irq = platform_get_irq(pdev, 0);
 	if (data->irq < 0) {
-		ret = data->irq;
 		dev_err(&pdev->dev, "Failed to get platform irq\n");
-		goto err_free;
+		return data->irq;
 	}
 
 	INIT_WORK(&data->irq_work, exynos_tmu_work);
 
 	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!data->mem) {
-		ret = -ENOENT;
 		dev_err(&pdev->dev, "Failed to get platform resource\n");
-		goto err_free;
+		return -ENOENT;
 	}
 
-	data->mem = request_mem_region(data->mem->start,
-			resource_size(data->mem), pdev->name);
-	if (!data->mem) {
-		ret = -ENODEV;
-		dev_err(&pdev->dev, "Failed to request memory region\n");
-		goto err_free;
-	}
-
-	data->base = ioremap(data->mem->start, resource_size(data->mem));
+	data->base = devm_request_and_ioremap(&pdev->dev, data->mem);
 	if (!data->base) {
-		ret = -ENODEV;
 		dev_err(&pdev->dev, "Failed to ioremap memory\n");
-		goto err_mem_region;
+		return -ENODEV;
 	}
 
-	ret = request_irq(data->irq, exynos_tmu_irq,
+	ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
 		IRQF_TRIGGER_RISING, "exynos-tmu", data);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
-		goto err_io_remap;
+		return ret;
 	}
 
 	data->clk = clk_get(NULL, "tmu_apbif");
 	if (IS_ERR(data->clk)) {
-		ret = PTR_ERR(data->clk);
 		dev_err(&pdev->dev, "Failed to get clock\n");
-		goto err_irq;
+		return  PTR_ERR(data->clk);
 	}
 
 	if (pdata->type == SOC_ARCH_EXYNOS ||
@@ -942,15 +931,6 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 err_clk:
 	platform_set_drvdata(pdev, NULL);
 	clk_put(data->clk);
-err_irq:
-	free_irq(data->irq, data);
-err_io_remap:
-	iounmap(data->base);
-err_mem_region:
-	release_mem_region(data->mem->start, resource_size(data->mem));
-err_free:
-	kfree(data);
-
 	return ret;
 }
 
@@ -964,15 +944,8 @@ static int __devexit exynos_tmu_remove(struct platform_device *pdev)
 
 	clk_put(data->clk);
 
-	free_irq(data->irq, data);
-
-	iounmap(data->base);
-	release_mem_region(data->mem->start, resource_size(data->mem));
-
 	platform_set_drvdata(pdev, NULL);
 
-	kfree(data);
-
 	return 0;
 }
 
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [lm-sensors] [PATCH v6 0/6] thermal: Add kernel thermal support for exynos platform
@ 2012-08-16 11:53 ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:53 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors, amit.kachhap

Hi Andrew/Zhang/Len,

This current patchset is based on 3.6-rc1 and Zhang Rui's core thermal
enhancement patches(http://permalink.gmane.org/gmane.linux.acpi.devel/54564).

Version V5 is present in linux-next tree. Since Zhang Rui's implementation are
already present in linux-next so requesting Andrew to add this V6 series and drop
earlier V5 version.

Changes since v5:
This patchset basically simplifies the cpufreq cooling API's to take just
cpumask as an input parameter and also removes the state management logic as they
are now taken care in the core thermal layer.

All these patches over Zhang Rui's work can be found in the git link
git://git.linaro.org/people/amitdanielk/linux.git exynos_v6_thermal_tree

Thanks,
Amit Daniel

Amit Daniel Kachhap (6):
  thermal: add generic cpufreq cooling implementation
  hwmon: exynos4: move thermal sensor driver to driver/thermal
    directory
  thermal: exynos5: add exynos5250 thermal sensor driver support
  thermal: exynos: register the tmu sensor with the kernel thermal
    layer
  ARM: exynos: add thermal sensor driver platform data support
  thermal: exynos: Use devm_* functions

 Documentation/hwmon/exynos4_tmu              |   81 ---
 Documentation/thermal/cpu-cooling-api.txt    |   52 ++
 Documentation/thermal/exynos_thermal         |   52 ++
 drivers/hwmon/Kconfig                        |   10 -
 drivers/hwmon/Makefile                       |    1 -
 drivers/hwmon/exynos4_tmu.c                  |  518 --------------
 drivers/thermal/Kconfig                      |   18 +
 drivers/thermal/Makefile                     |    2 +
 drivers/thermal/cpu_cooling.c                |  512 +++++++++++++
 drivers/thermal/exynos_thermal.c             |  994 ++++++++++++++++++++++++++
 include/linux/cpu_cooling.h                  |   79 ++
 include/linux/platform_data/exynos4_tmu.h    |   83 ---
 include/linux/platform_data/exynos_thermal.h |  116 +++
 13 files changed, 1825 insertions(+), 693 deletions(-)
 delete mode 100644 Documentation/hwmon/exynos4_tmu
 create mode 100644 Documentation/thermal/cpu-cooling-api.txt
 create mode 100644 Documentation/thermal/exynos_thermal
 delete mode 100644 drivers/hwmon/exynos4_tmu.c
 create mode 100644 drivers/thermal/cpu_cooling.c
 create mode 100644 drivers/thermal/exynos_thermal.c
 create mode 100644 include/linux/cpu_cooling.h
 delete mode 100644 include/linux/platform_data/exynos4_tmu.h
 create mode 100644 include/linux/platform_data/exynos_thermal.h


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 50+ messages in thread

* [lm-sensors] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:53 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park,
	Guenter Roeck

This patchset introduces a new generic cooling device based on cpufreq
that can be used on non-ACPI platforms.  As a proof of concept, we have
drivers for the following platforms using this mechanism now:

 * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
 * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)

There is a small change in cpufreq cooling registration APIs, so a minor
change is needed for Freescale platforms.

Brief Description:

1) The generic cooling devices code is placed inside driver/thermal/*
   as placing inside acpi folder will need un-necessary enabling of acpi
   code.  This code is architecture independent.

2) This patchset adds generic cpu cooling low level implementation
   through frequency clipping.  In future, other cpu related cooling
   devices may be added here.  An ACPI version of this already exists
   (drivers/acpi/processor_thermal.c) .But this will be useful for
   platforms like ARM using the generic thermal interface along with the
   generic cpu cooling devices.  The cooling device registration API's
   return cooling device pointers which can be easily binded with the
   thermal zone trip points.  The important APIs exposed are,

   a) struct thermal_cooling_device *cpufreq_cooling_register(
        struct cpumask *clip_cpus)
   b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)

3) Samsung exynos platform thermal implementation is done using the
   generic cpu cooling APIs and the new trip type.  The temperature sensor
   driver present in the hwmon folder(registered as hwmon driver) is moved
   to thermal folder and registered as a thermal driver.

A simple data/control flow diagrams is shown below,

Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
          |                             |
         \|/                            |
  Cpufreq cooling device <---------------

TODO:
*Will send the DT enablement patches later after the driver is merged.

This patch:

Add support for generic cpu thermal cooling low level implementations
using frequency scaling up/down based on the registration parameters.
Different cpu related cooling devices can be registered by the user and
the binding of these cooling devices to the corresponding trip points can
be easily done as the registration APIs return the cooling device pointer.
The user of these APIs are responsible for passing clipping frequency .
The drivers can also register to recieve notification about any cooling
action called.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Cc: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 Documentation/thermal/cpu-cooling-api.txt |   52 +++
 drivers/thermal/Kconfig                   |   11 +
 drivers/thermal/Makefile                  |    1 +
 drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
 include/linux/cpu_cooling.h               |   79 +++++
 5 files changed, 655 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/thermal/cpu-cooling-api.txt
 create mode 100644 drivers/thermal/cpu_cooling.c
 create mode 100644 include/linux/cpu_cooling.h

diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
new file mode 100644
index 0000000..a1f2a6b
--- /dev/null
+++ b/Documentation/thermal/cpu-cooling-api.txt
@@ -0,0 +1,52 @@
+CPU cooling APIs How To
+=================+
+Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
+
+Updated: 12 May 2012
+
+Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+
+0. Introduction
+
+The generic cpu cooling(freq clipping) provides registration/unregistration APIs
+to the caller. The binding of the cooling devices to the trip point is left for
+the user. The registration APIs returns the cooling device pointer.
+
+1. cpu cooling APIs
+
+1.1 cpufreq registration/unregistration APIs
+1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+
+    This interface function registers the cpufreq cooling device with the name
+    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+    cooling devices.
+
+   clip_cpus: cpumask of cpus where the frequency constraints will happen.
+
+1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+
+    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+
+    cdev: Cooling device pointer which has to be unregistered.
+
+
+1.2 CPU cooling action notifier register/unregister interface
+1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
+
+1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 7dd8c34..996003b 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -19,6 +19,17 @@ config THERMAL_HWMON
 	depends on HWMON=y || HWMON=THERMAL
 	default y
 
+config CPU_THERMAL
+	bool "generic cpu cooling support"
+	depends on THERMAL && CPU_FREQ
+	help
+	  This implements the generic cpu cooling mechanism through frequency
+	  reduction, cpu hotplug and any other ways of reducing temperature. An
+	  ACPI version of this already exists(drivers/acpi/processor_thermal.c).
+	  This will be useful for platforms using the generic thermal interface
+	  and not the ACPI interface.
+	  If you want this support, you should say Y here.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fd9369a..aae59ad 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,5 +3,6 @@
 #
 
 obj-$(CONFIG_THERMAL)		+= thermal_sys.o
+obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 0000000..c42e557
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,512 @@
+/*
+ *  linux/drivers/thermal/cpu_cooling.c
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+/**
+ * struct cpufreq_cooling_device
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ *	registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the the
+ *	egistered cooling device.
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ *	cooling	devices.
+ * @cpufreq_val: integer value representing the absolute value of the clipped
+ *	frequency.
+ * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ *
+ * This structure is required for keeping information of each
+ * cpufreq_cooling_device registered as a list whose head is represented by
+ * cooling_cpufreq_list. In order to prevent corruption of this list a
+ * mutex lock cooling_cpufreq_lock is used.
+ */
+struct cpufreq_cooling_device {
+	int id;
+	struct thermal_cooling_device *cool_dev;
+	unsigned int cpufreq_state;
+	unsigned int cpufreq_val;
+	struct cpumask allowed_cpus;
+	struct list_head node;
+};
+static LIST_HEAD(cooling_cpufreq_list);
+static DEFINE_IDR(cpufreq_idr);
+
+static struct mutex cooling_cpufreq_lock;
+
+/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
+#define NOTIFY_INVALID NULL
+struct cpufreq_cooling_device *notify_device;
+
+/* Head of the blocking notifier chain to inform about frequency clamping */
+static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+	int err;
+again:
+	if (unlikely(idr_pre_get(idr, GFP_KERNEL) = 0))
+		return -ENOMEM;
+
+	mutex_lock(&cooling_cpufreq_lock);
+	err = idr_get_new(idr, NULL, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	if (unlikely(err = -EAGAIN))
+		goto again;
+	else if (unlikely(err))
+		return err;
+
+	*id = *id & MAX_ID_MASK;
+	return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+	mutex_lock(&cooling_cpufreq_lock);
+	idr_remove(idr, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+}
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_register(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_register_notifier);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_unregister(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_unregister_notifier);
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
+ * @cpu: cpu for which check is needed.
+ */
+static int is_cpufreq_valid(int cpu)
+{
+	struct cpufreq_policy policy;
+	return !cpufreq_get_policy(&policy, 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
+ */
+static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
+{
+	int ret = 0, i = 0;
+	unsigned long level_index;
+	bool descend = false;
+	struct cpufreq_frequency_table *table +					cpufreq_frequency_get_table(cpu);
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/*check if table in ascending or descending order*/
+		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+			(table[i + 1].frequency < table[i].frequency)
+			&& !descend) {
+			descend = true;
+		}
+
+		/*return if level matched and table in descending order*/
+		if (descend && i = level)
+			return table[i].frequency;
+		i++;
+	}
+	i--;
+
+	if (level > i || descend)
+		return ret;
+	level_index = i - level;
+
+	/*Scan the table in reverse order and match the level*/
+	while (i >= 0) {
+		if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
+			continue;
+		/*return if level matched*/
+		if (i = level_index)
+			return table[i].frequency;
+		i--;
+	}
+	return ret;
+}
+
+/**
+ * cpufreq_apply_cooling - function to apply frequency clipping.
+ * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
+ *	clipping data.
+ * @cooling_state: value of the cooling state.
+ */
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+				unsigned long cooling_state)
+{
+	unsigned int event, cpuid, clip_freq;
+	struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
+	unsigned int cpu = cpumask_any(maskPtr);
+
+
+	/* Check if the old cooling action is same as new cooling action */
+	if (cpufreq_device->cpufreq_state = cooling_state)
+		return 0;
+
+	clip_freq = get_cpu_frequency(cpu, cooling_state);
+	if (!clip_freq)
+		return -EINVAL;
+
+	cpufreq_device->cpufreq_state = cooling_state;
+	cpufreq_device->cpufreq_val = clip_freq;
+	notify_device = cpufreq_device;
+
+	if (cooling_state != 0)
+		event = CPUFREQ_COOLING_START;
+	else
+		event = CPUFREQ_COOLING_STOP;
+
+	blocking_notifier_call_chain(&cputherm_state_notifier_list,
+						event, &clip_freq);
+
+	for_each_cpu(cpuid, maskPtr) {
+		if (is_cpufreq_valid(cpuid))
+			cpufreq_update_policy(cpuid);
+	}
+
+	notify_device = NOTIFY_INVALID;
+
+	return 0;
+}
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb:	struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+					unsigned long event, void *data)
+{
+	struct cpufreq_policy *policy = data;
+	unsigned long max_freq = 0;
+
+	if (event != CPUFREQ_ADJUST || notify_device = NOTIFY_INVALID)
+		return 0;
+
+	if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
+		max_freq = notify_device->cpufreq_val;
+
+	/* Never exceed user_policy.max*/
+	if (max_freq > policy->user_policy.max)
+		max_freq = policy->user_policy.max;
+
+	if (policy->max != max_freq)
+		cpufreq_verify_within_limits(policy, 0, max_freq);
+
+	return 0;
+}
+
+/*
+ * cpufreq cooling device callback functions are defined below
+ */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ */
+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;
+	unsigned int cpu;
+	struct cpufreq_frequency_table *table;
+
+	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;
+	}
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
+			continue;
+		i++;
+	}
+	if (i > 0) {
+		*state = --i;
+		ret = 0;
+	}
+
+return_get_max_state:
+	mutex_unlock(&cooling_cpufreq_lock);
+	return ret;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling 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);
+
+	return ret;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ */
+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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	return ret;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+	.get_max_state = cpufreq_get_max_state,
+	.get_cur_state = cpufreq_get_cur_state,
+	.set_cur_state = cpufreq_set_cur_state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+	.notifier_call = cpufreq_thermal_notifier,
+};
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+	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;
+	char dev_name[THERMAL_NAME_LENGTH];
+	int ret = 0, id = 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*/
+		if (!cpufreq_get_policy(&policy, i))
+			continue;
+		if (min = 0 && max = 0) {
+			min = policy.cpuinfo.min_freq;
+			max = policy.cpuinfo.max_freq;
+		} else {
+			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);
+	if (!cpufreq_dev)
+		return ERR_PTR(-ENOMEM);
+
+	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);
+		return ERR_PTR(-EINVAL);
+	}
+
+	sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
+
+	cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
+						&cpufreq_cooling_ops);
+	if (!cool_dev) {
+		release_idr(&cpufreq_idr, cpufreq_dev->id);
+		kfree(cpufreq_dev);
+		return ERR_PTR(-EINVAL);
+	}
+	cpufreq_dev->id = id;
+	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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+	return cool_dev;
+}
+EXPORT_SYMBOL(cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+	struct cpufreq_cooling_device *cpufreq_dev = NULL;
+	unsigned int cpufreq_dev_count = 0;
+
+	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);
+
+	/* Unregister the notifier for the last cpufreq cooling device */
+	if (cpufreq_dev_count = 1) {
+		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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
new file mode 100644
index 0000000..555b917
--- /dev/null
+++ b/include/linux/cpu_cooling.h
@@ -0,0 +1,79 @@
+/*
+ *  linux/include/linux/cpu_cooling.h
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  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 __CPU_COOLING_H__
+#define __CPU_COOLING_H__
+
+#include <linux/thermal.h>
+
+#define CPUFREQ_COOLING_START		0
+#define CPUFREQ_COOLING_STOP		1
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
+
+#ifdef CONFIG_CPU_THERMAL
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+		struct cpumask *clip_cpus);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
+#else /* !CONFIG_CPU_THERMAL */
+static inline struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+{
+	return NULL;
+}
+static inline void cpufreq_cooling_unregister(
+		struct thermal_cooling_device *cdev)
+{
+	return;
+}
+#endif	/* CONFIG_CPU_THERMAL */
+
+#endif /* __CPU_COOLING_H__ */
-- 
1.7.1


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [lm-sensors] [PATCH v6 2/6] hwmon: exynos4: move thermal sensor driver to driver/thermal directory
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:53 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park

This movement is needed because the hwmon entries and corresponding sysfs
interface is a duplicate of utilities already provided by
driver/thermal/thermal_sys.c.  The goal is to place it in thermal folder
and add necessary functions to use the in-kernel thermal interfaces.

Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 Documentation/hwmon/exynos4_tmu              |   81 ----
 Documentation/thermal/exynos_thermal         |   52 +++
 drivers/hwmon/Kconfig                        |   10 -
 drivers/hwmon/Makefile                       |    1 -
 drivers/hwmon/exynos4_tmu.c                  |  518 --------------------------
 drivers/thermal/Kconfig                      |    7 +
 drivers/thermal/Makefile                     |    1 +
 drivers/thermal/exynos_thermal.c             |  413 ++++++++++++++++++++
 include/linux/platform_data/exynos4_tmu.h    |   83 ----
 include/linux/platform_data/exynos_thermal.h |   83 ++++
 10 files changed, 556 insertions(+), 693 deletions(-)
 delete mode 100644 Documentation/hwmon/exynos4_tmu
 create mode 100644 Documentation/thermal/exynos_thermal
 delete mode 100644 drivers/hwmon/exynos4_tmu.c
 create mode 100644 drivers/thermal/exynos_thermal.c
 delete mode 100644 include/linux/platform_data/exynos4_tmu.h
 create mode 100644 include/linux/platform_data/exynos_thermal.h

diff --git a/Documentation/hwmon/exynos4_tmu b/Documentation/hwmon/exynos4_tmu
deleted file mode 100644
index c3c6b41..0000000
--- a/Documentation/hwmon/exynos4_tmu
+++ /dev/null
@@ -1,81 +0,0 @@
-Kernel driver exynos4_tmu
-========-
-Supported chips:
-* ARM SAMSUNG EXYNOS4 series of SoC
-  Prefix: 'exynos4-tmu'
-  Datasheet: Not publicly available
-
-Authors: Donggeun Kim <dg77.kim@samsung.com>
-
-Description
------------
-
-This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC.
-
-The chip only exposes the measured 8-bit temperature code value
-through a register.
-Temperature can be taken from the temperature code.
-There are three equations converting from temperature to temperature code.
-
-The three equations are:
-  1. Two point trimming
-	Tc = (T - 25) * (TI2 - TI1) / (85 - 25) + TI1
-
-  2. One point trimming
-	Tc = T + TI1 - 25
-
-  3. No trimming
-	Tc = T + 50
-
-  Tc: Temperature code, T: Temperature,
-  TI1: Trimming info for 25 degree Celsius (stored at TRIMINFO register)
-       Temperature code measured at 25 degree Celsius which is unchanged
-  TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register)
-       Temperature code measured at 85 degree Celsius which is unchanged
-
-TMU(Thermal Management Unit) in EXYNOS4 generates interrupt
-when temperature exceeds pre-defined levels.
-The maximum number of configurable threshold is four.
-The threshold levels are defined as follows:
-  Level_0: current temperature > trigger_level_0 + threshold
-  Level_1: current temperature > trigger_level_1 + threshold
-  Level_2: current temperature > trigger_level_2 + threshold
-  Level_3: current temperature > trigger_level_3 + threshold
-
-  The threshold and each trigger_level are set
-  through the corresponding registers.
-
-When an interrupt occurs, this driver notify user space of
-one of four threshold levels for the interrupt
-through kobject_uevent_env and sysfs_notify functions.
-Although an interrupt condition for level_0 can be set,
-it is not notified to user space through sysfs_notify function.
-
-Sysfs Interface
----------------
-name		name of the temperature sensor
-		RO
-
-temp1_input	temperature
-		RO
-
-temp1_max	temperature for level_1 interrupt
-		RO
-
-temp1_crit	temperature for level_2 interrupt
-		RO
-
-temp1_emergency	temperature for level_3 interrupt
-		RO
-
-temp1_max_alarm	alarm for level_1 interrupt
-		RO
-
-temp1_crit_alarm
-		alarm for level_2 interrupt
-		RO
-
-temp1_emergency_alarm
-		alarm for level_3 interrupt
-		RO
diff --git a/Documentation/thermal/exynos_thermal b/Documentation/thermal/exynos_thermal
new file mode 100644
index 0000000..2b46f67
--- /dev/null
+++ b/Documentation/thermal/exynos_thermal
@@ -0,0 +1,52 @@
+Kernel driver exynos4_tmu
+========+
+Supported chips:
+* ARM SAMSUNG EXYNOS4 series of SoC
+  Prefix: 'exynos4-tmu'
+  Datasheet: Not publicly available
+
+Authors: Donggeun Kim <dg77.kim@samsung.com>
+
+Description
+-----------
+
+This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC.
+
+The chip only exposes the measured 8-bit temperature code value
+through a register.
+Temperature can be taken from the temperature code.
+There are three equations converting from temperature to temperature code.
+
+The three equations are:
+  1. Two point trimming
+	Tc = (T - 25) * (TI2 - TI1) / (85 - 25) + TI1
+
+  2. One point trimming
+	Tc = T + TI1 - 25
+
+  3. No trimming
+	Tc = T + 50
+
+  Tc: Temperature code, T: Temperature,
+  TI1: Trimming info for 25 degree Celsius (stored at TRIMINFO register)
+       Temperature code measured at 25 degree Celsius which is unchanged
+  TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register)
+       Temperature code measured at 85 degree Celsius which is unchanged
+
+TMU(Thermal Management Unit) in EXYNOS4 generates interrupt
+when temperature exceeds pre-defined levels.
+The maximum number of configurable threshold is four.
+The threshold levels are defined as follows:
+  Level_0: current temperature > trigger_level_0 + threshold
+  Level_1: current temperature > trigger_level_1 + threshold
+  Level_2: current temperature > trigger_level_2 + threshold
+  Level_3: current temperature > trigger_level_3 + threshold
+
+  The threshold and each trigger_level are set
+  through the corresponding registers.
+
+When an interrupt occurs, this driver notify kernel thermal framework
+with the function exynos4_report_trigger.
+Although an interrupt condition for level_0 can be set,
+it can be used to synchronize the cooling action.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b0a2e4c..84e02b4 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -324,16 +324,6 @@ config SENSORS_DA9052_ADC
 	  This driver can also be built as module. If so, the module
 	  will be called da9052-hwmon.
 
-config SENSORS_EXYNOS4_TMU
-	tristate "Temperature sensor on Samsung EXYNOS4"
-	depends on ARCH_EXYNOS4
-	help
-	  If you say yes here you get support for TMU (Thermal Management
-	  Unit) on SAMSUNG EXYNOS4 series of SoC.
-
-	  This driver can also be built as a module. If so, the module
-	  will be called exynos4-tmu.
-
 config SENSORS_I5K_AMB
 	tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
 	depends on PCI && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 7aa9811..3eafe48 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -49,7 +49,6 @@ obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
 obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
 obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
 obj-$(CONFIG_SENSORS_EMC6W201)	+= emc6w201.o
-obj-$(CONFIG_SENSORS_EXYNOS4_TMU)	+= exynos4_tmu.o
 obj-$(CONFIG_SENSORS_F71805F)	+= f71805f.o
 obj-$(CONFIG_SENSORS_F71882FG)	+= f71882fg.o
 obj-$(CONFIG_SENSORS_F75375S)	+= f75375s.o
diff --git a/drivers/hwmon/exynos4_tmu.c b/drivers/hwmon/exynos4_tmu.c
deleted file mode 100644
index e912059..0000000
--- a/drivers/hwmon/exynos4_tmu.c
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * exynos4_tmu.c - Samsung EXYNOS4 TMU (Thermal Management Unit)
- *
- *  Copyright (C) 2011 Samsung Electronics
- *  Donggeun Kim <dg77.kim@samsung.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- */
-
-#include <linux/module.h>
-#include <linux/err.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/platform_device.h>
-#include <linux/interrupt.h>
-#include <linux/clk.h>
-#include <linux/workqueue.h>
-#include <linux/sysfs.h>
-#include <linux/kobject.h>
-#include <linux/io.h>
-#include <linux/mutex.h>
-
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-
-#include <linux/platform_data/exynos4_tmu.h>
-
-#define EXYNOS4_TMU_REG_TRIMINFO	0x0
-#define EXYNOS4_TMU_REG_CONTROL		0x20
-#define EXYNOS4_TMU_REG_STATUS		0x28
-#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
-#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
-#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
-#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
-#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
-#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
-#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
-#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
-#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
-#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
-#define EXYNOS4_TMU_REG_INTEN		0x70
-#define EXYNOS4_TMU_REG_INTSTAT		0x74
-#define EXYNOS4_TMU_REG_INTCLEAR	0x78
-
-#define EXYNOS4_TMU_GAIN_SHIFT		8
-#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
-
-#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
-#define EXYNOS4_TMU_CORE_ON	3
-#define EXYNOS4_TMU_CORE_OFF	2
-#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
-#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
-#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
-#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
-#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
-#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
-
-struct exynos4_tmu_data {
-	struct exynos4_tmu_platform_data *pdata;
-	struct device *hwmon_dev;
-	struct resource *mem;
-	void __iomem *base;
-	int irq;
-	struct work_struct irq_work;
-	struct mutex lock;
-	struct clk *clk;
-	u8 temp_error1, temp_error2;
-};
-
-/*
- * TMU treats temperature as a mapped temperature code.
- * The temperature is converted differently depending on the calibration type.
- */
-static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
-{
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	int temp_code;
-
-	/* temp should range between 25 and 125 */
-	if (temp < 25 || temp > 125) {
-		temp_code = -EINVAL;
-		goto out;
-	}
-
-	switch (pdata->cal_type) {
-	case TYPE_TWO_POINT_TRIMMING:
-		temp_code = (temp - 25) *
-		    (data->temp_error2 - data->temp_error1) /
-		    (85 - 25) + data->temp_error1;
-		break;
-	case TYPE_ONE_POINT_TRIMMING:
-		temp_code = temp + data->temp_error1 - 25;
-		break;
-	default:
-		temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
-		break;
-	}
-out:
-	return temp_code;
-}
-
-/*
- * Calculate a temperature value from a temperature code.
- * The unit of the temperature is degree Celsius.
- */
-static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
-{
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	int temp;
-
-	/* temp_code should range between 75 and 175 */
-	if (temp_code < 75 || temp_code > 175) {
-		temp = -ENODATA;
-		goto out;
-	}
-
-	switch (pdata->cal_type) {
-	case TYPE_TWO_POINT_TRIMMING:
-		temp = (temp_code - data->temp_error1) * (85 - 25) /
-		    (data->temp_error2 - data->temp_error1) + 25;
-		break;
-	case TYPE_ONE_POINT_TRIMMING:
-		temp = temp_code - data->temp_error1 + 25;
-		break;
-	default:
-		temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
-		break;
-	}
-out:
-	return temp;
-}
-
-static int exynos4_tmu_initialize(struct platform_device *pdev)
-{
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int status, trim_info;
-	int ret = 0, threshold_code;
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
-	if (!status) {
-		ret = -EBUSY;
-		goto out;
-	}
-
-	/* Save trimming info in order to perform calibration */
-	trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
-	data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
-	data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
-
-	/* Write temperature code for threshold */
-	threshold_code = temp_to_code(data, pdata->threshold);
-	if (threshold_code < 0) {
-		ret = threshold_code;
-		goto out;
-	}
-	writeb(threshold_code,
-		data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
-
-	writeb(pdata->trigger_levels[0],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
-	writeb(pdata->trigger_levels[1],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
-	writeb(pdata->trigger_levels[2],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
-	writeb(pdata->trigger_levels[3],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
-
-	writel(EXYNOS4_TMU_INTCLEAR_VAL,
-		data->base + EXYNOS4_TMU_REG_INTCLEAR);
-out:
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-
-	return ret;
-}
-
-static void exynos4_tmu_control(struct platform_device *pdev, bool on)
-{
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int con, interrupt_en;
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
-		pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
-	if (on) {
-		con |= EXYNOS4_TMU_CORE_ON;
-		interrupt_en = pdata->trigger_level3_en << 12 |
-			pdata->trigger_level2_en << 8 |
-			pdata->trigger_level1_en << 4 |
-			pdata->trigger_level0_en;
-	} else {
-		con |= EXYNOS4_TMU_CORE_OFF;
-		interrupt_en = 0; /* Disable all interrupts */
-	}
-	writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
-	writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
-
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-}
-
-static int exynos4_tmu_read(struct exynos4_tmu_data *data)
-{
-	u8 temp_code;
-	int temp;
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
-	temp = code_to_temp(data, temp_code);
-
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-
-	return temp;
-}
-
-static void exynos4_tmu_work(struct work_struct *work)
-{
-	struct exynos4_tmu_data *data = container_of(work,
-			struct exynos4_tmu_data, irq_work);
-
-	mutex_lock(&data->lock);
-	clk_enable(data->clk);
-
-	writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
-
-	kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE);
-
-	enable_irq(data->irq);
-
-	clk_disable(data->clk);
-	mutex_unlock(&data->lock);
-}
-
-static irqreturn_t exynos4_tmu_irq(int irq, void *id)
-{
-	struct exynos4_tmu_data *data = id;
-
-	disable_irq_nosync(irq);
-	schedule_work(&data->irq_work);
-
-	return IRQ_HANDLED;
-}
-
-static ssize_t exynos4_tmu_show_name(struct device *dev,
-		struct device_attribute *attr, char *buf)
-{
-	return sprintf(buf, "exynos4-tmu\n");
-}
-
-static ssize_t exynos4_tmu_show_temp(struct device *dev,
-		struct device_attribute *attr, char *buf)
-{
-	struct exynos4_tmu_data *data = dev_get_drvdata(dev);
-	int ret;
-
-	ret = exynos4_tmu_read(data);
-	if (ret < 0)
-		return ret;
-
-	/* convert from degree Celsius to millidegree Celsius */
-	return sprintf(buf, "%d\n", ret * 1000);
-}
-
-static ssize_t exynos4_tmu_show_alarm(struct device *dev,
-		struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct exynos4_tmu_data *data = dev_get_drvdata(dev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	int temp;
-	unsigned int trigger_level;
-
-	temp = exynos4_tmu_read(data);
-	if (temp < 0)
-		return temp;
-
-	trigger_level = pdata->threshold + pdata->trigger_levels[attr->index];
-
-	return sprintf(buf, "%d\n", !!(temp > trigger_level));
-}
-
-static ssize_t exynos4_tmu_show_level(struct device *dev,
-		struct device_attribute *devattr, char *buf)
-{
-	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-	struct exynos4_tmu_data *data = dev_get_drvdata(dev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int temp = pdata->threshold +
-			pdata->trigger_levels[attr->index];
-
-	return sprintf(buf, "%u\n", temp * 1000);
-}
-
-static DEVICE_ATTR(name, S_IRUGO, exynos4_tmu_show_name, NULL);
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, exynos4_tmu_show_temp, NULL, 0);
-
-static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
-		exynos4_tmu_show_alarm, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
-		exynos4_tmu_show_alarm, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO,
-		exynos4_tmu_show_alarm, NULL, 3);
-
-static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, exynos4_tmu_show_level, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, exynos4_tmu_show_level, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO,
-		exynos4_tmu_show_level, NULL, 3);
-
-static struct attribute *exynos4_tmu_attributes[] = {
-	&dev_attr_name.attr,
-	&sensor_dev_attr_temp1_input.dev_attr.attr,
-	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
-	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
-	&sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr,
-	&sensor_dev_attr_temp1_max.dev_attr.attr,
-	&sensor_dev_attr_temp1_crit.dev_attr.attr,
-	&sensor_dev_attr_temp1_emergency.dev_attr.attr,
-	NULL,
-};
-
-static const struct attribute_group exynos4_tmu_attr_group = {
-	.attrs = exynos4_tmu_attributes,
-};
-
-static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
-{
-	struct exynos4_tmu_data *data;
-	struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
-	int ret;
-
-	if (!pdata) {
-		dev_err(&pdev->dev, "No platform init data supplied.\n");
-		return -ENODEV;
-	}
-
-	data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
-	if (!data) {
-		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
-		return -ENOMEM;
-	}
-
-	data->irq = platform_get_irq(pdev, 0);
-	if (data->irq < 0) {
-		ret = data->irq;
-		dev_err(&pdev->dev, "Failed to get platform irq\n");
-		goto err_free;
-	}
-
-	INIT_WORK(&data->irq_work, exynos4_tmu_work);
-
-	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	if (!data->mem) {
-		ret = -ENOENT;
-		dev_err(&pdev->dev, "Failed to get platform resource\n");
-		goto err_free;
-	}
-
-	data->mem = request_mem_region(data->mem->start,
-			resource_size(data->mem), pdev->name);
-	if (!data->mem) {
-		ret = -ENODEV;
-		dev_err(&pdev->dev, "Failed to request memory region\n");
-		goto err_free;
-	}
-
-	data->base = ioremap(data->mem->start, resource_size(data->mem));
-	if (!data->base) {
-		ret = -ENODEV;
-		dev_err(&pdev->dev, "Failed to ioremap memory\n");
-		goto err_mem_region;
-	}
-
-	ret = request_irq(data->irq, exynos4_tmu_irq,
-		IRQF_TRIGGER_RISING,
-		"exynos4-tmu", data);
-	if (ret) {
-		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
-		goto err_io_remap;
-	}
-
-	data->clk = clk_get(NULL, "tmu_apbif");
-	if (IS_ERR(data->clk)) {
-		ret = PTR_ERR(data->clk);
-		dev_err(&pdev->dev, "Failed to get clock\n");
-		goto err_irq;
-	}
-
-	data->pdata = pdata;
-	platform_set_drvdata(pdev, data);
-	mutex_init(&data->lock);
-
-	ret = exynos4_tmu_initialize(pdev);
-	if (ret) {
-		dev_err(&pdev->dev, "Failed to initialize TMU\n");
-		goto err_clk;
-	}
-
-	ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
-	if (ret) {
-		dev_err(&pdev->dev, "Failed to create sysfs group\n");
-		goto err_clk;
-	}
-
-	data->hwmon_dev = hwmon_device_register(&pdev->dev);
-	if (IS_ERR(data->hwmon_dev)) {
-		ret = PTR_ERR(data->hwmon_dev);
-		dev_err(&pdev->dev, "Failed to register hwmon device\n");
-		goto err_create_group;
-	}
-
-	exynos4_tmu_control(pdev, true);
-
-	return 0;
-
-err_create_group:
-	sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
-err_clk:
-	platform_set_drvdata(pdev, NULL);
-	clk_put(data->clk);
-err_irq:
-	free_irq(data->irq, data);
-err_io_remap:
-	iounmap(data->base);
-err_mem_region:
-	release_mem_region(data->mem->start, resource_size(data->mem));
-err_free:
-	kfree(data);
-
-	return ret;
-}
-
-static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
-{
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-
-	exynos4_tmu_control(pdev, false);
-
-	hwmon_device_unregister(data->hwmon_dev);
-	sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
-
-	clk_put(data->clk);
-
-	free_irq(data->irq, data);
-
-	iounmap(data->base);
-	release_mem_region(data->mem->start, resource_size(data->mem));
-
-	platform_set_drvdata(pdev, NULL);
-
-	kfree(data);
-
-	return 0;
-}
-
-#ifdef CONFIG_PM_SLEEP
-static int exynos4_tmu_suspend(struct device *dev)
-{
-	exynos4_tmu_control(to_platform_device(dev), false);
-
-	return 0;
-}
-
-static int exynos4_tmu_resume(struct device *dev)
-{
-	struct platform_device *pdev = to_platform_device(dev);
-
-	exynos4_tmu_initialize(pdev);
-	exynos4_tmu_control(pdev, true);
-
-	return 0;
-}
-
-static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
-			 exynos4_tmu_suspend, exynos4_tmu_resume);
-#define EXYNOS4_TMU_PM	&exynos4_tmu_pm
-#else
-#define EXYNOS4_TMU_PM	NULL
-#endif
-
-static struct platform_driver exynos4_tmu_driver = {
-	.driver = {
-		.name   = "exynos4-tmu",
-		.owner  = THIS_MODULE,
-		.pm     = EXYNOS4_TMU_PM,
-	},
-	.probe = exynos4_tmu_probe,
-	.remove	= __devexit_p(exynos4_tmu_remove),
-};
-
-module_platform_driver(exynos4_tmu_driver);
-
-MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
-MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:exynos4-tmu");
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 996003b..8f2b6ea 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -46,3 +46,10 @@ config RCAR_THERMAL
 	help
 	  Enable this to plug the R-Car thermal sensor driver into the Linux
 	  thermal framework
+
+config EXYNOS_THERMAL
+	tristate "Temperature sensor on Samsung EXYNOS"
+	depends on ARCH_EXYNOS4 && THERMAL
+	help
+	  If you say yes here you get support for TMU (Thermal Managment
+	  Unit) on SAMSUNG EXYNOS series of SoC.
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index aae59ad..885550d 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_THERMAL)		+= thermal_sys.o
 obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
+obj-$(CONFIG_EXYNOS_THERMAL)		+= exynos_thermal.o
diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
new file mode 100644
index 0000000..556d15b
--- /dev/null
+++ b/drivers/thermal/exynos_thermal.c
@@ -0,0 +1,413 @@
+/*
+ * exynos_thermal.c - Samsung EXYNOS TMU (Thermal Management Unit)
+ *
+ *  Copyright (C) 2011 Samsung Electronics
+ *  Donggeun Kim <dg77.kim@samsung.com>
+ *  Amit Daniel Kachhap <amit.kachhap@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+
+#include <linux/platform_data/exynos_thermal.h>
+
+#define EXYNOS4_TMU_REG_TRIMINFO	0x0
+#define EXYNOS4_TMU_REG_CONTROL		0x20
+#define EXYNOS4_TMU_REG_STATUS		0x28
+#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
+#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
+#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
+#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
+#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
+#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
+#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
+#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
+#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
+#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
+#define EXYNOS4_TMU_REG_INTEN		0x70
+#define EXYNOS4_TMU_REG_INTSTAT		0x74
+#define EXYNOS4_TMU_REG_INTCLEAR	0x78
+
+#define EXYNOS4_TMU_GAIN_SHIFT		8
+#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
+
+#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
+#define EXYNOS4_TMU_CORE_ON	3
+#define EXYNOS4_TMU_CORE_OFF	2
+#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
+#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
+#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
+#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
+#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
+#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
+
+struct exynos4_tmu_data {
+	struct exynos4_tmu_platform_data *pdata;
+	struct resource *mem;
+	void __iomem *base;
+	int irq;
+	struct work_struct irq_work;
+	struct mutex lock;
+	struct clk *clk;
+	u8 temp_error1, temp_error2;
+};
+
+/*
+ * TMU treats temperature as a mapped temperature code.
+ * The temperature is converted differently depending on the calibration type.
+ */
+static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
+{
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	int temp_code;
+
+	/* temp should range between 25 and 125 */
+	if (temp < 25 || temp > 125) {
+		temp_code = -EINVAL;
+		goto out;
+	}
+
+	switch (pdata->cal_type) {
+	case TYPE_TWO_POINT_TRIMMING:
+		temp_code = (temp - 25) *
+		    (data->temp_error2 - data->temp_error1) /
+		    (85 - 25) + data->temp_error1;
+		break;
+	case TYPE_ONE_POINT_TRIMMING:
+		temp_code = temp + data->temp_error1 - 25;
+		break;
+	default:
+		temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		break;
+	}
+out:
+	return temp_code;
+}
+
+/*
+ * Calculate a temperature value from a temperature code.
+ * The unit of the temperature is degree Celsius.
+ */
+static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
+{
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	int temp;
+
+	/* temp_code should range between 75 and 175 */
+	if (temp_code < 75 || temp_code > 175) {
+		temp = -ENODATA;
+		goto out;
+	}
+
+	switch (pdata->cal_type) {
+	case TYPE_TWO_POINT_TRIMMING:
+		temp = (temp_code - data->temp_error1) * (85 - 25) /
+		    (data->temp_error2 - data->temp_error1) + 25;
+		break;
+	case TYPE_ONE_POINT_TRIMMING:
+		temp = temp_code - data->temp_error1 + 25;
+		break;
+	default:
+		temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		break;
+	}
+out:
+	return temp;
+}
+
+static int exynos4_tmu_initialize(struct platform_device *pdev)
+{
+	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	unsigned int status, trim_info;
+	int ret = 0, threshold_code;
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
+	if (!status) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Save trimming info in order to perform calibration */
+	trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
+	data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
+	data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
+
+	/* Write temperature code for threshold */
+	threshold_code = temp_to_code(data, pdata->threshold);
+	if (threshold_code < 0) {
+		ret = threshold_code;
+		goto out;
+	}
+	writeb(threshold_code,
+		data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
+
+	writeb(pdata->trigger_levels[0],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
+	writeb(pdata->trigger_levels[1],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
+	writeb(pdata->trigger_levels[2],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
+	writeb(pdata->trigger_levels[3],
+		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
+
+	writel(EXYNOS4_TMU_INTCLEAR_VAL,
+		data->base + EXYNOS4_TMU_REG_INTCLEAR);
+out:
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static void exynos4_tmu_control(struct platform_device *pdev, bool on)
+{
+	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	unsigned int con, interrupt_en;
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
+		pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
+	if (on) {
+		con |= EXYNOS4_TMU_CORE_ON;
+		interrupt_en = pdata->trigger_level3_en << 12 |
+			pdata->trigger_level2_en << 8 |
+			pdata->trigger_level1_en << 4 |
+			pdata->trigger_level0_en;
+	} else {
+		con |= EXYNOS4_TMU_CORE_OFF;
+		interrupt_en = 0; /* Disable all interrupts */
+	}
+	writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
+	writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
+
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+}
+
+static int exynos4_tmu_read(struct exynos4_tmu_data *data)
+{
+	u8 temp_code;
+	int temp;
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
+	temp = code_to_temp(data, temp_code);
+
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+
+	return temp;
+}
+
+static void exynos4_tmu_work(struct work_struct *work)
+{
+	struct exynos4_tmu_data *data = container_of(work,
+			struct exynos4_tmu_data, irq_work);
+
+	mutex_lock(&data->lock);
+	clk_enable(data->clk);
+
+	writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
+
+	enable_irq(data->irq);
+
+	clk_disable(data->clk);
+	mutex_unlock(&data->lock);
+}
+
+static irqreturn_t exynos4_tmu_irq(int irq, void *id)
+{
+	struct exynos4_tmu_data *data = id;
+
+	disable_irq_nosync(irq);
+	schedule_work(&data->irq_work);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
+{
+	struct exynos4_tmu_data *data;
+	struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
+	int ret;
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform init data supplied.\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
+	if (!data) {
+		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
+		return -ENOMEM;
+	}
+
+	data->irq = platform_get_irq(pdev, 0);
+	if (data->irq < 0) {
+		ret = data->irq;
+		dev_err(&pdev->dev, "Failed to get platform irq\n");
+		goto err_free;
+	}
+
+	INIT_WORK(&data->irq_work, exynos4_tmu_work);
+
+	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!data->mem) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get platform resource\n");
+		goto err_free;
+	}
+
+	data->mem = request_mem_region(data->mem->start,
+			resource_size(data->mem), pdev->name);
+	if (!data->mem) {
+		ret = -ENODEV;
+		dev_err(&pdev->dev, "Failed to request memory region\n");
+		goto err_free;
+	}
+
+	data->base = ioremap(data->mem->start, resource_size(data->mem));
+	if (!data->base) {
+		ret = -ENODEV;
+		dev_err(&pdev->dev, "Failed to ioremap memory\n");
+		goto err_mem_region;
+	}
+
+	ret = request_irq(data->irq, exynos4_tmu_irq,
+		IRQF_TRIGGER_RISING,
+		"exynos4-tmu", data);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
+		goto err_io_remap;
+	}
+
+	data->clk = clk_get(NULL, "tmu_apbif");
+	if (IS_ERR(data->clk)) {
+		ret = PTR_ERR(data->clk);
+		dev_err(&pdev->dev, "Failed to get clock\n");
+		goto err_irq;
+	}
+
+	data->pdata = pdata;
+	platform_set_drvdata(pdev, data);
+	mutex_init(&data->lock);
+
+	ret = exynos4_tmu_initialize(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to initialize TMU\n");
+		goto err_clk;
+	}
+
+	exynos4_tmu_control(pdev, true);
+
+	return 0;
+err_clk:
+	platform_set_drvdata(pdev, NULL);
+	clk_put(data->clk);
+err_irq:
+	free_irq(data->irq, data);
+err_io_remap:
+	iounmap(data->base);
+err_mem_region:
+	release_mem_region(data->mem->start, resource_size(data->mem));
+err_free:
+	kfree(data);
+
+	return ret;
+}
+
+static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
+{
+	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+
+	exynos4_tmu_control(pdev, false);
+
+	clk_put(data->clk);
+
+	free_irq(data->irq, data);
+
+	iounmap(data->base);
+	release_mem_region(data->mem->start, resource_size(data->mem));
+
+	platform_set_drvdata(pdev, NULL);
+
+	kfree(data);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos4_tmu_suspend(struct device *dev)
+{
+	exynos4_tmu_control(to_platform_device(dev), false);
+
+	return 0;
+}
+
+static int exynos4_tmu_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	exynos4_tmu_initialize(pdev);
+	exynos4_tmu_control(pdev, true);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
+			 exynos4_tmu_suspend, exynos4_tmu_resume);
+#define EXYNOS4_TMU_PM	(&exynos4_tmu_pm)
+#else
+#define EXYNOS4_TMU_PM	NULL
+#endif
+
+static struct platform_driver exynos4_tmu_driver = {
+	.driver = {
+		.name   = "exynos4-tmu",
+		.owner  = THIS_MODULE,
+		.pm     = EXYNOS4_TMU_PM,
+	},
+	.probe = exynos4_tmu_probe,
+	.remove	= __devexit_p(exynos4_tmu_remove),
+};
+
+module_platform_driver(exynos4_tmu_driver);
+
+MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
+MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:exynos4-tmu");
diff --git a/include/linux/platform_data/exynos4_tmu.h b/include/linux/platform_data/exynos4_tmu.h
deleted file mode 100644
index 39e038c..0000000
--- a/include/linux/platform_data/exynos4_tmu.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * exynos4_tmu.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
- *
- *  Copyright (C) 2011 Samsung Electronics
- *  Donggeun Kim <dg77.kim@samsung.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#ifndef _LINUX_EXYNOS4_TMU_H
-#define _LINUX_EXYNOS4_TMU_H
-
-enum calibration_type {
-	TYPE_ONE_POINT_TRIMMING,
-	TYPE_TWO_POINT_TRIMMING,
-	TYPE_NONE,
-};
-
-/**
- * struct exynos4_tmu_platform_data
- * @threshold: basic temperature for generating interrupt
- *	       25 <= threshold <= 125 [unit: degree Celsius]
- * @trigger_levels: array for each interrupt levels
- *	[unit: degree Celsius]
- *	0: temperature for trigger_level0 interrupt
- *	   condition for trigger_level0 interrupt:
- *		current temperature > threshold + trigger_levels[0]
- *	1: temperature for trigger_level1 interrupt
- *	   condition for trigger_level1 interrupt:
- *		current temperature > threshold + trigger_levels[1]
- *	2: temperature for trigger_level2 interrupt
- *	   condition for trigger_level2 interrupt:
- *		current temperature > threshold + trigger_levels[2]
- *	3: temperature for trigger_level3 interrupt
- *	   condition for trigger_level3 interrupt:
- *		current temperature > threshold + trigger_levels[3]
- * @trigger_level0_en:
- *	1 = enable trigger_level0 interrupt,
- *	0 = disable trigger_level0 interrupt
- * @trigger_level1_en:
- *	1 = enable trigger_level1 interrupt,
- *	0 = disable trigger_level1 interrupt
- * @trigger_level2_en:
- *	1 = enable trigger_level2 interrupt,
- *	0 = disable trigger_level2 interrupt
- * @trigger_level3_en:
- *	1 = enable trigger_level3 interrupt,
- *	0 = disable trigger_level3 interrupt
- * @gain: gain of amplifier in the positive-TC generator block
- *	0 <= gain <= 15
- * @reference_voltage: reference voltage of amplifier
- *	in the positive-TC generator block
- *	0 <= reference_voltage <= 31
- * @cal_type: calibration type for temperature
- *
- * This structure is required for configuration of exynos4_tmu driver.
- */
-struct exynos4_tmu_platform_data {
-	u8 threshold;
-	u8 trigger_levels[4];
-	bool trigger_level0_en;
-	bool trigger_level1_en;
-	bool trigger_level2_en;
-	bool trigger_level3_en;
-
-	u8 gain;
-	u8 reference_voltage;
-
-	enum calibration_type cal_type;
-};
-#endif /* _LINUX_EXYNOS4_TMU_H */
diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h
new file mode 100644
index 0000000..d6c3f93
--- /dev/null
+++ b/include/linux/platform_data/exynos_thermal.h
@@ -0,0 +1,83 @@
+/*
+ * exynos_thermal.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
+ *
+ *  Copyright (C) 2011 Samsung Electronics
+ *  Donggeun Kim <dg77.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _LINUX_EXYNOS_THERMAL_H
+#define _LINUX_EXYNOS_THERMAL_H
+
+enum calibration_type {
+	TYPE_ONE_POINT_TRIMMING,
+	TYPE_TWO_POINT_TRIMMING,
+	TYPE_NONE,
+};
+
+/**
+ * struct exynos4_tmu_platform_data
+ * @threshold: basic temperature for generating interrupt
+ *	       25 <= threshold <= 125 [unit: degree Celsius]
+ * @trigger_levels: array for each interrupt levels
+ *	[unit: degree Celsius]
+ *	0: temperature for trigger_level0 interrupt
+ *	   condition for trigger_level0 interrupt:
+ *		current temperature > threshold + trigger_levels[0]
+ *	1: temperature for trigger_level1 interrupt
+ *	   condition for trigger_level1 interrupt:
+ *		current temperature > threshold + trigger_levels[1]
+ *	2: temperature for trigger_level2 interrupt
+ *	   condition for trigger_level2 interrupt:
+ *		current temperature > threshold + trigger_levels[2]
+ *	3: temperature for trigger_level3 interrupt
+ *	   condition for trigger_level3 interrupt:
+ *		current temperature > threshold + trigger_levels[3]
+ * @trigger_level0_en:
+ *	1 = enable trigger_level0 interrupt,
+ *	0 = disable trigger_level0 interrupt
+ * @trigger_level1_en:
+ *	1 = enable trigger_level1 interrupt,
+ *	0 = disable trigger_level1 interrupt
+ * @trigger_level2_en:
+ *	1 = enable trigger_level2 interrupt,
+ *	0 = disable trigger_level2 interrupt
+ * @trigger_level3_en:
+ *	1 = enable trigger_level3 interrupt,
+ *	0 = disable trigger_level3 interrupt
+ * @gain: gain of amplifier in the positive-TC generator block
+ *	0 <= gain <= 15
+ * @reference_voltage: reference voltage of amplifier
+ *	in the positive-TC generator block
+ *	0 <= reference_voltage <= 31
+ * @cal_type: calibration type for temperature
+ *
+ * This structure is required for configuration of exynos4_tmu driver.
+ */
+struct exynos4_tmu_platform_data {
+	u8 threshold;
+	u8 trigger_levels[4];
+	bool trigger_level0_en;
+	bool trigger_level1_en;
+	bool trigger_level2_en;
+	bool trigger_level3_en;
+
+	u8 gain;
+	u8 reference_voltage;
+
+	enum calibration_type cal_type;
+};
+#endif /* _LINUX_EXYNOS_THERMAL_H */
-- 
1.7.1


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [lm-sensors] [PATCH v6 3/6] thermal: exynos5: add exynos5250 thermal sensor driver support
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:53 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, lm-sensors, linux-acpi,
	Kukjin Kim, Jean Delvare, Kyungmin Park

Insert exynos5 TMU sensor changes into the thermal driver.  Some exynos4
changes are made generic for exynos series.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: SangWook Ju <sw.ju@samsung.com>
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 drivers/thermal/Kconfig                      |    2 +-
 drivers/thermal/exynos_thermal.c             |  351 ++++++++++++++++----------
 include/linux/platform_data/exynos_thermal.h |   19 ++-
 3 files changed, 240 insertions(+), 132 deletions(-)

diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 8f2b6ea..edfd67d 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -49,7 +49,7 @@ config RCAR_THERMAL
 
 config EXYNOS_THERMAL
 	tristate "Temperature sensor on Samsung EXYNOS"
-	depends on ARCH_EXYNOS4 && THERMAL
+	depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5) && THERMAL
 	help
 	  If you say yes here you get support for TMU (Thermal Managment
 	  Unit) on SAMSUNG EXYNOS series of SoC.
diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index 556d15b..c9a33dd 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -33,44 +33,83 @@
 #include <linux/kobject.h>
 #include <linux/io.h>
 #include <linux/mutex.h>
-
 #include <linux/platform_data/exynos_thermal.h>
-
-#define EXYNOS4_TMU_REG_TRIMINFO	0x0
-#define EXYNOS4_TMU_REG_CONTROL		0x20
-#define EXYNOS4_TMU_REG_STATUS		0x28
-#define EXYNOS4_TMU_REG_CURRENT_TEMP	0x40
-#define EXYNOS4_TMU_REG_THRESHOLD_TEMP	0x44
-#define EXYNOS4_TMU_REG_TRIG_LEVEL0	0x50
-#define EXYNOS4_TMU_REG_TRIG_LEVEL1	0x54
-#define EXYNOS4_TMU_REG_TRIG_LEVEL2	0x58
-#define EXYNOS4_TMU_REG_TRIG_LEVEL3	0x5C
-#define EXYNOS4_TMU_REG_PAST_TEMP0	0x60
-#define EXYNOS4_TMU_REG_PAST_TEMP1	0x64
-#define EXYNOS4_TMU_REG_PAST_TEMP2	0x68
-#define EXYNOS4_TMU_REG_PAST_TEMP3	0x6C
-#define EXYNOS4_TMU_REG_INTEN		0x70
-#define EXYNOS4_TMU_REG_INTSTAT		0x74
-#define EXYNOS4_TMU_REG_INTCLEAR	0x78
-
-#define EXYNOS4_TMU_GAIN_SHIFT		8
-#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT	24
-
-#define EXYNOS4_TMU_TRIM_TEMP_MASK	0xff
-#define EXYNOS4_TMU_CORE_ON	3
-#define EXYNOS4_TMU_CORE_OFF	2
-#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET	50
-#define EXYNOS4_TMU_TRIG_LEVEL0_MASK	0x1
-#define EXYNOS4_TMU_TRIG_LEVEL1_MASK	0x10
-#define EXYNOS4_TMU_TRIG_LEVEL2_MASK	0x100
-#define EXYNOS4_TMU_TRIG_LEVEL3_MASK	0x1000
-#define EXYNOS4_TMU_INTCLEAR_VAL	0x1111
-
-struct exynos4_tmu_data {
-	struct exynos4_tmu_platform_data *pdata;
+#include <linux/of.h>
+
+#include <plat/cpu.h>
+
+/* Exynos generic registers */
+#define EXYNOS_TMU_REG_TRIMINFO		0x0
+#define EXYNOS_TMU_REG_CONTROL		0x20
+#define EXYNOS_TMU_REG_STATUS		0x28
+#define EXYNOS_TMU_REG_CURRENT_TEMP	0x40
+#define EXYNOS_TMU_REG_INTEN		0x70
+#define EXYNOS_TMU_REG_INTSTAT		0x74
+#define EXYNOS_TMU_REG_INTCLEAR		0x78
+
+#define EXYNOS_TMU_TRIM_TEMP_MASK	0xff
+#define EXYNOS_TMU_GAIN_SHIFT		8
+#define EXYNOS_TMU_REF_VOLTAGE_SHIFT	24
+#define EXYNOS_TMU_CORE_ON		3
+#define EXYNOS_TMU_CORE_OFF		2
+#define EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET	50
+
+/* Exynos4210 specific registers */
+#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP	0x44
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL0	0x50
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL1	0x54
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL2	0x58
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL3	0x5C
+#define EXYNOS4210_TMU_REG_PAST_TEMP0	0x60
+#define EXYNOS4210_TMU_REG_PAST_TEMP1	0x64
+#define EXYNOS4210_TMU_REG_PAST_TEMP2	0x68
+#define EXYNOS4210_TMU_REG_PAST_TEMP3	0x6C
+
+#define EXYNOS4210_TMU_TRIG_LEVEL0_MASK	0x1
+#define EXYNOS4210_TMU_TRIG_LEVEL1_MASK	0x10
+#define EXYNOS4210_TMU_TRIG_LEVEL2_MASK	0x100
+#define EXYNOS4210_TMU_TRIG_LEVEL3_MASK	0x1000
+#define EXYNOS4210_TMU_INTCLEAR_VAL	0x1111
+
+/* Exynos5250 and Exynos4412 specific registers */
+#define EXYNOS_TMU_TRIMINFO_CON	0x14
+#define EXYNOS_THD_TEMP_RISE		0x50
+#define EXYNOS_THD_TEMP_FALL		0x54
+#define EXYNOS_EMUL_CON		0x80
+
+#define EXYNOS_TRIMINFO_RELOAD		0x1
+#define EXYNOS_TMU_CLEAR_RISE_INT	0x111
+#define EXYNOS_TMU_CLEAR_FALL_INT	(0x111 << 16)
+#define EXYNOS_MUX_ADDR_VALUE		6
+#define EXYNOS_MUX_ADDR_SHIFT		20
+#define EXYNOS_TMU_TRIP_MODE_SHIFT	13
+
+#define EFUSE_MIN_VALUE 40
+#define EFUSE_MAX_VALUE 100
+
+/* In-kernel thermal framework related macros & definations */
+#define SENSOR_NAME_LEN	16
+#define MAX_TRIP_COUNT	8
+#define MAX_COOLING_DEVICE 4
+
+#define ACTIVE_INTERVAL 500
+#define IDLE_INTERVAL 10000
+
+/* CPU Zone information */
+#define PANIC_ZONE      4
+#define WARN_ZONE       3
+#define MONITOR_ZONE    2
+#define SAFE_ZONE       1
+
+#define GET_ZONE(trip) (trip + 2)
+#define GET_TRIP(zone) (zone - 2)
+
+struct exynos_tmu_data {
+	struct exynos_tmu_platform_data *pdata;
 	struct resource *mem;
 	void __iomem *base;
 	int irq;
+	enum soc_type soc;
 	struct work_struct irq_work;
 	struct mutex lock;
 	struct clk *clk;
@@ -81,16 +120,17 @@ struct exynos4_tmu_data {
  * TMU treats temperature as a mapped temperature code.
  * The temperature is converted differently depending on the calibration type.
  */
-static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
+static int temp_to_code(struct exynos_tmu_data *data, u8 temp)
 {
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	struct exynos_tmu_platform_data *pdata = data->pdata;
 	int temp_code;
 
-	/* temp should range between 25 and 125 */
-	if (temp < 25 || temp > 125) {
-		temp_code = -EINVAL;
-		goto out;
-	}
+	if (data->soc = SOC_ARCH_EXYNOS4210)
+		/* temp should range between 25 and 125 */
+		if (temp < 25 || temp > 125) {
+			temp_code = -EINVAL;
+			goto out;
+		}
 
 	switch (pdata->cal_type) {
 	case TYPE_TWO_POINT_TRIMMING:
@@ -102,7 +142,7 @@ static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
 		temp_code = temp + data->temp_error1 - 25;
 		break;
 	default:
-		temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		temp_code = temp + EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET;
 		break;
 	}
 out:
@@ -113,16 +153,17 @@ out:
  * Calculate a temperature value from a temperature code.
  * The unit of the temperature is degree Celsius.
  */
-static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
+static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code)
 {
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	struct exynos_tmu_platform_data *pdata = data->pdata;
 	int temp;
 
-	/* temp_code should range between 75 and 175 */
-	if (temp_code < 75 || temp_code > 175) {
-		temp = -ENODATA;
-		goto out;
-	}
+	if (data->soc = SOC_ARCH_EXYNOS4210)
+		/* temp_code should range between 75 and 175 */
+		if (temp_code < 75 || temp_code > 175) {
+			temp = -ENODATA;
+			goto out;
+		}
 
 	switch (pdata->cal_type) {
 	case TYPE_TWO_POINT_TRIMMING:
@@ -133,54 +174,92 @@ static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
 		temp = temp_code - data->temp_error1 + 25;
 		break;
 	default:
-		temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
+		temp = temp_code - EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET;
 		break;
 	}
 out:
 	return temp;
 }
 
-static int exynos4_tmu_initialize(struct platform_device *pdev)
+static int exynos_tmu_initialize(struct platform_device *pdev)
 {
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
-	unsigned int status, trim_info;
+	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;
 
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
+	status = readb(data->base + EXYNOS_TMU_REG_STATUS);
 	if (!status) {
 		ret = -EBUSY;
 		goto out;
 	}
 
+	if (data->soc = SOC_ARCH_EXYNOS) {
+		__raw_writel(EXYNOS_TRIMINFO_RELOAD,
+				data->base + EXYNOS_TMU_TRIMINFO_CON);
+	}
 	/* Save trimming info in order to perform calibration */
-	trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
-	data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
-	data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
-
-	/* Write temperature code for threshold */
-	threshold_code = temp_to_code(data, pdata->threshold);
-	if (threshold_code < 0) {
-		ret = threshold_code;
-		goto out;
+	trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+	data->temp_error1 = trim_info & EXYNOS_TMU_TRIM_TEMP_MASK;
+	data->temp_error2 = ((trim_info >> 8) & EXYNOS_TMU_TRIM_TEMP_MASK);
+
+	if ((EFUSE_MIN_VALUE > data->temp_error1) ||
+			(data->temp_error1 > EFUSE_MAX_VALUE) ||
+			(data->temp_error2 != 0))
+		data->temp_error1 = pdata->efuse_value;
+
+	if (data->soc = SOC_ARCH_EXYNOS4210) {
+		/* Write temperature code for threshold */
+		threshold_code = temp_to_code(data, pdata->threshold);
+		if (threshold_code < 0) {
+			ret = threshold_code;
+			goto out;
+		}
+		writeb(threshold_code,
+			data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
+
+		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);
+
+		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;
+		}
+		rising_threshold |= (threshold_code << 16);
+
+		writel(rising_threshold,
+				data->base + EXYNOS_THD_TEMP_RISE);
+		writel(0, data->base + EXYNOS_THD_TEMP_FALL);
+
+		writel(EXYNOS_TMU_CLEAR_RISE_INT|EXYNOS_TMU_CLEAR_FALL_INT,
+				data->base + EXYNOS_TMU_REG_INTCLEAR);
 	}
-	writeb(threshold_code,
-		data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
-
-	writeb(pdata->trigger_levels[0],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
-	writeb(pdata->trigger_levels[1],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
-	writeb(pdata->trigger_levels[2],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
-	writeb(pdata->trigger_levels[3],
-		data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
-
-	writel(EXYNOS4_TMU_INTCLEAR_VAL,
-		data->base + EXYNOS4_TMU_REG_INTCLEAR);
 out:
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
@@ -188,35 +267,41 @@ out:
 	return ret;
 }
 
-static void exynos4_tmu_control(struct platform_device *pdev, bool on)
+static void exynos_tmu_control(struct platform_device *pdev, bool on)
 {
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
-	struct exynos4_tmu_platform_data *pdata = data->pdata;
+	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos_tmu_platform_data *pdata = data->pdata;
 	unsigned int con, interrupt_en;
 
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
-		pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
+	con = pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT |
+		pdata->gain << EXYNOS_TMU_GAIN_SHIFT;
+
+	if (data->soc = SOC_ARCH_EXYNOS) {
+		con |= pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT;
+		con |= (EXYNOS_MUX_ADDR_VALUE << EXYNOS_MUX_ADDR_SHIFT);
+	}
+
 	if (on) {
-		con |= EXYNOS4_TMU_CORE_ON;
+		con |= EXYNOS_TMU_CORE_ON;
 		interrupt_en = pdata->trigger_level3_en << 12 |
 			pdata->trigger_level2_en << 8 |
 			pdata->trigger_level1_en << 4 |
 			pdata->trigger_level0_en;
 	} else {
-		con |= EXYNOS4_TMU_CORE_OFF;
+		con |= EXYNOS_TMU_CORE_OFF;
 		interrupt_en = 0; /* Disable all interrupts */
 	}
-	writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
-	writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
+	writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN);
+	writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
 
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
 }
 
-static int exynos4_tmu_read(struct exynos4_tmu_data *data)
+static int exynos_tmu_read(struct exynos_tmu_data *data)
 {
 	u8 temp_code;
 	int temp;
@@ -224,7 +309,7 @@ static int exynos4_tmu_read(struct exynos4_tmu_data *data)
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
+	temp_code = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);
 	temp = code_to_temp(data, temp_code);
 
 	clk_disable(data->clk);
@@ -233,25 +318,30 @@ static int exynos4_tmu_read(struct exynos4_tmu_data *data)
 	return temp;
 }
 
-static void exynos4_tmu_work(struct work_struct *work)
+static void exynos_tmu_work(struct work_struct *work)
 {
-	struct exynos4_tmu_data *data = container_of(work,
-			struct exynos4_tmu_data, irq_work);
+	struct exynos_tmu_data *data = container_of(work,
+			struct exynos_tmu_data, irq_work);
 
 	mutex_lock(&data->lock);
 	clk_enable(data->clk);
 
-	writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
 
-	enable_irq(data->irq);
+	if (data->soc = SOC_ARCH_EXYNOS)
+		writel(EXYNOS_TMU_CLEAR_RISE_INT,
+				data->base + EXYNOS_TMU_REG_INTCLEAR);
+	else
+		writel(EXYNOS4210_TMU_INTCLEAR_VAL,
+				data->base + EXYNOS_TMU_REG_INTCLEAR);
 
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
+	enable_irq(data->irq);
 }
 
-static irqreturn_t exynos4_tmu_irq(int irq, void *id)
+static irqreturn_t exynos_tmu_irq(int irq, void *id)
 {
-	struct exynos4_tmu_data *data = id;
+	struct exynos_tmu_data *data = id;
 
 	disable_irq_nosync(irq);
 	schedule_work(&data->irq_work);
@@ -259,18 +349,17 @@ static irqreturn_t exynos4_tmu_irq(int irq, void *id)
 	return IRQ_HANDLED;
 }
 
-static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
+static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 {
-	struct exynos4_tmu_data *data;
-	struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
+	struct exynos_tmu_data *data;
+	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
 	int ret;
 
 	if (!pdata) {
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
 		return -ENODEV;
 	}
-
-	data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
+	data = kzalloc(sizeof(struct exynos_tmu_data), GFP_KERNEL);
 	if (!data) {
 		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
 		return -ENOMEM;
@@ -283,7 +372,7 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 		goto err_free;
 	}
 
-	INIT_WORK(&data->irq_work, exynos4_tmu_work);
+	INIT_WORK(&data->irq_work, exynos_tmu_work);
 
 	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!data->mem) {
@@ -307,9 +396,8 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 		goto err_mem_region;
 	}
 
-	ret = request_irq(data->irq, exynos4_tmu_irq,
-		IRQF_TRIGGER_RISING,
-		"exynos4-tmu", data);
+	ret = request_irq(data->irq, exynos_tmu_irq,
+		IRQF_TRIGGER_RISING, "exynos-tmu", data);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
 		goto err_io_remap;
@@ -322,17 +410,26 @@ static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
 		goto err_irq;
 	}
 
+	if (pdata->type = SOC_ARCH_EXYNOS ||
+				pdata->type = SOC_ARCH_EXYNOS4210)
+		data->soc = pdata->type;
+	else {
+		ret = -EINVAL;
+		dev_err(&pdev->dev, "Platform not supported\n");
+		goto err_clk;
+	}
+
 	data->pdata = pdata;
 	platform_set_drvdata(pdev, data);
 	mutex_init(&data->lock);
 
-	ret = exynos4_tmu_initialize(pdev);
+	ret = exynos_tmu_initialize(pdev);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to initialize TMU\n");
 		goto err_clk;
 	}
 
-	exynos4_tmu_control(pdev, true);
+	exynos_tmu_control(pdev, true);
 
 	return 0;
 err_clk:
@@ -350,11 +447,11 @@ err_free:
 	return ret;
 }
 
-static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
+static int __devexit exynos_tmu_remove(struct platform_device *pdev)
 {
-	struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
+	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
 
-	exynos4_tmu_control(pdev, false);
+	exynos_tmu_control(pdev, false);
 
 	clk_put(data->clk);
 
@@ -371,43 +468,43 @@ static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
 }
 
 #ifdef CONFIG_PM_SLEEP
-static int exynos4_tmu_suspend(struct device *dev)
+static int exynos_tmu_suspend(struct device *dev)
 {
-	exynos4_tmu_control(to_platform_device(dev), false);
+	exynos_tmu_control(to_platform_device(dev), false);
 
 	return 0;
 }
 
-static int exynos4_tmu_resume(struct device *dev)
+static int exynos_tmu_resume(struct device *dev)
 {
 	struct platform_device *pdev = to_platform_device(dev);
 
-	exynos4_tmu_initialize(pdev);
-	exynos4_tmu_control(pdev, true);
+	exynos_tmu_initialize(pdev);
+	exynos_tmu_control(pdev, true);
 
 	return 0;
 }
 
-static SIMPLE_DEV_PM_OPS(exynos4_tmu_pm,
-			 exynos4_tmu_suspend, exynos4_tmu_resume);
-#define EXYNOS4_TMU_PM	(&exynos4_tmu_pm)
+static SIMPLE_DEV_PM_OPS(exynos_tmu_pm,
+			 exynos_tmu_suspend, exynos_tmu_resume);
+#define EXYNOS_TMU_PM	(&exynos_tmu_pm)
 #else
-#define EXYNOS4_TMU_PM	NULL
+#define EXYNOS_TMU_PM	NULL
 #endif
 
-static struct platform_driver exynos4_tmu_driver = {
+static struct platform_driver exynos_tmu_driver = {
 	.driver = {
-		.name   = "exynos4-tmu",
+		.name   = "exynos-tmu",
 		.owner  = THIS_MODULE,
-		.pm     = EXYNOS4_TMU_PM,
+		.pm     = EXYNOS_TMU_PM,
 	},
-	.probe = exynos4_tmu_probe,
-	.remove	= __devexit_p(exynos4_tmu_remove),
+	.probe = exynos_tmu_probe,
+	.remove	= __devexit_p(exynos_tmu_remove),
 };
 
-module_platform_driver(exynos4_tmu_driver);
+module_platform_driver(exynos_tmu_driver);
 
-MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
+MODULE_DESCRIPTION("EXYNOS TMU Driver");
 MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
 MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:exynos4-tmu");
+MODULE_ALIAS("platform:exynos-tmu");
diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h
index d6c3f93..a9e960a 100644
--- a/include/linux/platform_data/exynos_thermal.h
+++ b/include/linux/platform_data/exynos_thermal.h
@@ -1,5 +1,5 @@
 /*
- * exynos_thermal.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
+ * exynos_thermal.h - Samsung EXYNOS TMU (Thermal Management Unit)
  *
  *  Copyright (C) 2011 Samsung Electronics
  *  Donggeun Kim <dg77.kim@samsung.com>
@@ -28,8 +28,12 @@ enum calibration_type {
 	TYPE_NONE,
 };
 
+enum soc_type {
+	SOC_ARCH_EXYNOS4210 = 1,
+	SOC_ARCH_EXYNOS,
+};
 /**
- * struct exynos4_tmu_platform_data
+ * struct exynos_tmu_platform_data
  * @threshold: basic temperature for generating interrupt
  *	       25 <= threshold <= 125 [unit: degree Celsius]
  * @trigger_levels: array for each interrupt levels
@@ -63,11 +67,15 @@ enum calibration_type {
  * @reference_voltage: reference voltage of amplifier
  *	in the positive-TC generator block
  *	0 <= reference_voltage <= 31
+ * @noise_cancel_mode: noise cancellation mode
+ *	000, 100, 101, 110 and 111 can be different modes
+ * @type: determines the type of SOC
+ * @efuse_value: platform defined fuse value
  * @cal_type: calibration type for temperature
  *
- * This structure is required for configuration of exynos4_tmu driver.
+ * This structure is required for configuration of exynos_tmu driver.
  */
-struct exynos4_tmu_platform_data {
+struct exynos_tmu_platform_data {
 	u8 threshold;
 	u8 trigger_levels[4];
 	bool trigger_level0_en;
@@ -77,7 +85,10 @@ struct exynos4_tmu_platform_data {
 
 	u8 gain;
 	u8 reference_voltage;
+	u8 noise_cancel_mode;
+	u32 efuse_value;
 
 	enum calibration_type cal_type;
+	enum soc_type type;
 };
 #endif /* _LINUX_EXYNOS_THERMAL_H */
-- 
1.7.1


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [lm-sensors] [PATCH v6 4/6] thermal: exynos: register the tmu sensor with the kernel thermal layer
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:53 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park

This code added creates a link between temperature sensors, linux thermal
framework and cooling devices for samsung exynos platform.  This layer
monitors the temperature from the sensor and informs the generic thermal
layer to take the necessary cooling action.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 drivers/thermal/exynos_thermal.c             |  406 +++++++++++++++++++++++++-
 include/linux/platform_data/exynos_thermal.h |   22 ++
 2 files changed, 426 insertions(+), 2 deletions(-)

diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index c9a33dd..e79cdc9 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -34,6 +34,9 @@
 #include <linux/io.h>
 #include <linux/mutex.h>
 #include <linux/platform_data/exynos_thermal.h>
+#include <linux/thermal.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu_cooling.h>
 #include <linux/of.h>
 
 #include <plat/cpu.h>
@@ -94,6 +97,7 @@
 
 #define ACTIVE_INTERVAL 500
 #define IDLE_INTERVAL 10000
+#define MCELSIUS	1000
 
 /* CPU Zone information */
 #define PANIC_ZONE      4
@@ -104,6 +108,8 @@
 #define GET_ZONE(trip) (trip + 2)
 #define GET_TRIP(zone) (zone - 2)
 
+#define EXYNOS_ZONE_COUNT	3
+
 struct exynos_tmu_data {
 	struct exynos_tmu_platform_data *pdata;
 	struct resource *mem;
@@ -116,6 +122,371 @@ struct exynos_tmu_data {
 	u8 temp_error1, temp_error2;
 };
 
+struct	thermal_trip_point_conf {
+	int trip_val[MAX_TRIP_COUNT];
+	int trip_count;
+};
+
+struct	thermal_cooling_conf {
+	struct freq_clip_table freq_data[MAX_TRIP_COUNT];
+	int freq_clip_count;
+};
+
+struct thermal_sensor_conf {
+	char name[SENSOR_NAME_LEN];
+	int (*read_temperature)(void *data);
+	struct thermal_trip_point_conf trip_data;
+	struct thermal_cooling_conf cooling_data;
+	void *private_data;
+};
+
+struct exynos_thermal_zone {
+	enum thermal_device_mode mode;
+	struct thermal_zone_device *therm_dev;
+	struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE];
+	unsigned int cool_dev_size;
+	struct platform_device *exynos4_dev;
+	struct thermal_sensor_conf *sensor_conf;
+	bool bind;
+};
+
+static struct exynos_thermal_zone *th_zone;
+static void exynos_unregister_thermal(void);
+static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf);
+
+/* Get mode callback functions for thermal zone */
+static int exynos_get_mode(struct thermal_zone_device *thermal,
+			enum thermal_device_mode *mode)
+{
+	if (th_zone)
+		*mode = th_zone->mode;
+	return 0;
+}
+
+/* Set mode callback functions for thermal zone */
+static int exynos_set_mode(struct thermal_zone_device *thermal,
+			enum thermal_device_mode mode)
+{
+	if (!th_zone->therm_dev) {
+		pr_notice("thermal zone not registered\n");
+		return 0;
+	}
+
+	mutex_lock(&th_zone->therm_dev->lock);
+
+	if (mode = THERMAL_DEVICE_ENABLED)
+		th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+	else
+		th_zone->therm_dev->polling_delay = 0;
+
+	mutex_unlock(&th_zone->therm_dev->lock);
+
+	th_zone->mode = mode;
+	thermal_zone_device_update(th_zone->therm_dev);
+	pr_info("thermal polling set for duration=%d msec\n",
+				th_zone->therm_dev->polling_delay);
+	return 0;
+}
+
+
+/* Get trip type callback functions for thermal zone */
+static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip,
+				 enum thermal_trip_type *type)
+{
+	switch (GET_ZONE(trip)) {
+	case MONITOR_ZONE:
+	case WARN_ZONE:
+		*type = THERMAL_TRIP_ACTIVE;
+		break;
+	case PANIC_ZONE:
+		*type = THERMAL_TRIP_CRITICAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/* Get trip temperature callback functions for thermal zone */
+static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip,
+				unsigned long *temp)
+{
+	if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE))
+		return -EINVAL;
+
+	*temp = th_zone->sensor_conf->trip_data.trip_val[trip];
+	/* convert the temperature into millicelsius */
+	*temp = *temp * MCELSIUS;
+
+	return 0;
+}
+
+/* Get critical temperature callback functions for thermal zone */
+static int exynos_get_crit_temp(struct thermal_zone_device *thermal,
+				unsigned long *temp)
+{
+	int ret;
+	/* Panic zone */
+	ret = exynos_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp);
+	return ret;
+}
+
+static int exynos_get_frequency_level(unsigned int cpu, unsigned int freq)
+{
+	int i = 0, ret = -EINVAL;
+	struct cpufreq_frequency_table *table = NULL;
+#ifdef CONFIG_CPU_FREQ
+	table = cpufreq_frequency_get_table(cpu);
+#endif
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
+			continue;
+		if (table[i].frequency = freq)
+			return i;
+		i++;
+	}
+	return ret;
+}
+
+/* Bind callback functions for thermal zone */
+static int exynos_bind(struct thermal_zone_device *thermal,
+			struct thermal_cooling_device *cdev)
+{
+	int ret = 0, i, tab_size, level;
+	struct freq_clip_table *tab_ptr, *clip_data;
+	struct thermal_sensor_conf *data = th_zone->sensor_conf;
+
+	tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data;
+	tab_size = data->cooling_data.freq_clip_count;
+
+	if (tab_ptr = NULL || tab_size = 0)
+		return -EINVAL;
+
+	/* find the cooling device registered*/
+	for (i = 0; i < th_zone->cool_dev_size; i++)
+		if (cdev = th_zone->cool_dev[i])
+			break;
+
+	/* No matching cooling device */
+	if (i = th_zone->cool_dev_size)
+		return 0;
+
+	/* Bind the thermal zone to the cpufreq cooling device */
+	for (i = 0; i < tab_size; i++) {
+		clip_data = (struct freq_clip_table *)&(tab_ptr[i]);
+		level = exynos_get_frequency_level(0, clip_data->freq_clip_max);
+		if (level < 0)
+			return 0;
+		switch (GET_ZONE(i)) {
+		case MONITOR_ZONE:
+		case WARN_ZONE:
+			if (thermal_zone_bind_cooling_device(thermal, i, cdev,
+								level, level)) {
+				pr_err("error binding cdev inst %d\n", i);
+				ret = -EINVAL;
+			}
+			th_zone->bind = true;
+			break;
+		default:
+			ret = -EINVAL;
+		}
+	}
+
+	return ret;
+}
+
+/* Unbind callback functions for thermal zone */
+static int exynos_unbind(struct thermal_zone_device *thermal,
+			struct thermal_cooling_device *cdev)
+{
+	int ret = 0, i, tab_size;
+	struct thermal_sensor_conf *data = th_zone->sensor_conf;
+
+	if (th_zone->bind = false)
+		return 0;
+
+	tab_size = data->cooling_data.freq_clip_count;
+
+	if (tab_size = 0)
+		return -EINVAL;
+
+	/* find the cooling device registered*/
+	for (i = 0; i < th_zone->cool_dev_size; i++)
+		if (cdev = th_zone->cool_dev[i])
+			break;
+
+	/* No matching cooling device */
+	if (i = th_zone->cool_dev_size)
+		return 0;
+
+	/* Bind the thermal zone to the cpufreq cooling device */
+	for (i = 0; i < tab_size; i++) {
+		switch (GET_ZONE(i)) {
+		case MONITOR_ZONE:
+		case WARN_ZONE:
+			if (thermal_zone_unbind_cooling_device(thermal, i,
+								cdev)) {
+				pr_err("error unbinding cdev inst=%d\n", i);
+				ret = -EINVAL;
+			}
+			th_zone->bind = false;
+			break;
+		default:
+			ret = -EINVAL;
+		}
+	}
+	return ret;
+}
+
+/* Get temperature callback functions for thermal zone */
+static int exynos_get_temp(struct thermal_zone_device *thermal,
+			unsigned long *temp)
+{
+	void *data;
+
+	if (!th_zone->sensor_conf) {
+		pr_info("Temperature sensor not initialised\n");
+		return -EINVAL;
+	}
+	data = th_zone->sensor_conf->private_data;
+	*temp = th_zone->sensor_conf->read_temperature(data);
+	/* convert the temperature into millicelsius */
+	*temp = *temp * MCELSIUS;
+	return 0;
+}
+
+/* Get the temperature trend */
+static int exynos_get_trend(struct thermal_zone_device *thermal,
+			int trip, enum thermal_trend *trend)
+{
+	if (thermal->temperature >= trip)
+		*trend = THERMAL_TREND_RAISING;
+	else
+		*trend = THERMAL_TREND_DROPPING;
+
+	return 0;
+}
+/* Operation callback functions for thermal zone */
+static struct thermal_zone_device_ops const exynos_dev_ops = {
+	.bind = exynos_bind,
+	.unbind = exynos_unbind,
+	.get_temp = exynos_get_temp,
+	.get_trend = exynos_get_trend,
+	.get_mode = exynos_get_mode,
+	.set_mode = exynos_set_mode,
+	.get_trip_type = exynos_get_trip_type,
+	.get_trip_temp = exynos_get_trip_temp,
+	.get_crit_temp = exynos_get_crit_temp,
+};
+
+/*
+ * This function may be called from interrupt based temperature sensor
+ * when threshold is changed.
+ */
+static void exynos_report_trigger(void)
+{
+	unsigned int i;
+	char data[10];
+	char *envp[] = { data, NULL };
+
+	if (!th_zone || !th_zone->therm_dev)
+		return;
+	if (th_zone->bind = false) {
+		for (i = 0; i < th_zone->cool_dev_size; i++) {
+			if (!th_zone->cool_dev[i])
+				continue;
+			exynos_bind(th_zone->therm_dev,
+					th_zone->cool_dev[i]);
+		}
+	}
+
+	thermal_zone_device_update(th_zone->therm_dev);
+
+	mutex_lock(&th_zone->therm_dev->lock);
+	/* Find the level for which trip happened */
+	for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) {
+		if (th_zone->therm_dev->last_temperature <
+			th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS)
+			break;
+	}
+
+	if (th_zone->mode = THERMAL_DEVICE_ENABLED) {
+		if (i > 0)
+			th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
+		else
+			th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+	}
+
+	snprintf(data, sizeof(data), "%u", i);
+	kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp);
+	mutex_unlock(&th_zone->therm_dev->lock);
+}
+
+/* Register with the in-kernel thermal management */
+static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
+{
+	int ret;
+	struct cpumask mask_val;
+
+	if (!sensor_conf || !sensor_conf->read_temperature) {
+		pr_err("Temperature sensor not initialised\n");
+		return -EINVAL;
+	}
+
+	th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL);
+	if (!th_zone)
+		return -ENOMEM;
+
+	th_zone->sensor_conf = sensor_conf;
+	cpumask_set_cpu(0, &mask_val);
+	th_zone->cool_dev[0] = cpufreq_cooling_register(&mask_val);
+	if (IS_ERR(th_zone->cool_dev[0])) {
+		pr_err("Failed to register cpufreq cooling device\n");
+		ret = -EINVAL;
+		goto err_unregister;
+	}
+	th_zone->cool_dev_size++;
+
+	th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name,
+			EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, 0,
+			IDLE_INTERVAL);
+
+	if (IS_ERR(th_zone->therm_dev)) {
+		pr_err("Failed to register thermal zone device\n");
+		ret = -EINVAL;
+		goto err_unregister;
+	}
+	th_zone->mode = THERMAL_DEVICE_ENABLED;
+
+	pr_info("Exynos: Kernel Thermal management registered\n");
+
+	return 0;
+
+err_unregister:
+	exynos_unregister_thermal();
+	return ret;
+}
+
+/* Un-Register with the in-kernel thermal management */
+static void exynos_unregister_thermal(void)
+{
+	int i;
+
+	if (th_zone && th_zone->therm_dev)
+		thermal_zone_device_unregister(th_zone->therm_dev);
+
+	for (i = 0; i < th_zone->cool_dev_size; i++) {
+		if (th_zone && th_zone->cool_dev[i])
+			cpufreq_cooling_unregister(th_zone->cool_dev[i]);
+	}
+
+	kfree(th_zone);
+	pr_info("Exynos: Kernel Thermal management unregistered\n");
+}
+
 /*
  * TMU treats temperature as a mapped temperature code.
  * The temperature is converted differently depending on the calibration type.
@@ -336,6 +707,7 @@ static void exynos_tmu_work(struct work_struct *work)
 
 	clk_disable(data->clk);
 	mutex_unlock(&data->lock);
+	exynos_report_trigger();
 	enable_irq(data->irq);
 }
 
@@ -348,12 +720,16 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id)
 
 	return IRQ_HANDLED;
 }
-
+static struct thermal_sensor_conf exynos_sensor_conf = {
+	.name			= "exynos-therm",
+	.read_temperature	= (int (*)(void *))exynos_tmu_read,
+}
+;
 static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 {
 	struct exynos_tmu_data *data;
 	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
-	int ret;
+	int ret, i;
 
 	if (!pdata) {
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
@@ -431,6 +807,30 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 
 	exynos_tmu_control(pdev, true);
 
+	/* Register the sensor with thermal management interface */
+	(&exynos_sensor_conf)->private_data = data;
+	exynos_sensor_conf.trip_data.trip_count = pdata->trigger_level0_en +
+			pdata->trigger_level1_en + pdata->trigger_level2_en +
+			pdata->trigger_level3_en;
+
+	for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++)
+		exynos_sensor_conf.trip_data.trip_val[i] +			pdata->threshold + pdata->trigger_levels[i];
+
+	exynos_sensor_conf.cooling_data.freq_clip_count +						pdata->freq_tab_count;
+	for (i = 0; i < pdata->freq_tab_count; i++) {
+		exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max +					pdata->freq_tab[i].freq_clip_max;
+		exynos_sensor_conf.cooling_data.freq_data[i].temp_level +					pdata->freq_tab[i].temp_level;
+	}
+
+	ret = exynos_register_thermal(&exynos_sensor_conf);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register thermal interface\n");
+		goto err_clk;
+	}
 	return 0;
 err_clk:
 	platform_set_drvdata(pdev, NULL);
@@ -453,6 +853,8 @@ static int __devexit exynos_tmu_remove(struct platform_device *pdev)
 
 	exynos_tmu_control(pdev, false);
 
+	exynos_unregister_thermal();
+
 	clk_put(data->clk);
 
 	free_irq(data->irq, data);
diff --git a/include/linux/platform_data/exynos_thermal.h b/include/linux/platform_data/exynos_thermal.h
index a9e960a..a7bdb2f 100644
--- a/include/linux/platform_data/exynos_thermal.h
+++ b/include/linux/platform_data/exynos_thermal.h
@@ -21,6 +21,7 @@
 
 #ifndef _LINUX_EXYNOS_THERMAL_H
 #define _LINUX_EXYNOS_THERMAL_H
+#include <linux/cpu_cooling.h>
 
 enum calibration_type {
 	TYPE_ONE_POINT_TRIMMING,
@@ -33,6 +34,22 @@ enum soc_type {
 	SOC_ARCH_EXYNOS,
 };
 /**
+ * struct freq_clip_table
+ * @freq_clip_max: maximum frequency allowed for this cooling state.
+ * @temp_level: Temperature level at which the temperature clipping will
+ *	happen.
+ * @mask_val: cpumask of the allowed cpu's where the clipping will take place.
+ *
+ * This structure is required to be filled and passed to the
+ * cpufreq_cooling_unregister function.
+ */
+struct freq_clip_table {
+	unsigned int freq_clip_max;
+	unsigned int temp_level;
+	const struct cpumask *mask_val;
+};
+
+/**
  * struct exynos_tmu_platform_data
  * @threshold: basic temperature for generating interrupt
  *	       25 <= threshold <= 125 [unit: degree Celsius]
@@ -72,6 +89,9 @@ enum soc_type {
  * @type: determines the type of SOC
  * @efuse_value: platform defined fuse value
  * @cal_type: calibration type for temperature
+ * @freq_clip_table: Table representing frequency reduction percentage.
+ * @freq_tab_count: Count of the above table as frequency reduction may
+ *	applicable to only some of the trigger levels.
  *
  * This structure is required for configuration of exynos_tmu driver.
  */
@@ -90,5 +110,7 @@ struct exynos_tmu_platform_data {
 
 	enum calibration_type cal_type;
 	enum soc_type type;
+	struct freq_clip_table freq_tab[4];
+	unsigned int freq_tab_count;
 };
 #endif /* _LINUX_EXYNOS_THERMAL_H */
-- 
1.7.1


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [lm-sensors] [PATCH v6 5/6] ARM: exynos: add thermal sensor driver platform data support
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:53 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: jonghwa lee, linux-samsung-soc, SangWook Ju, linux-kernel,
	lm-sensors, linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park

Add necessary default platform data support needed for TMU driver.  The
supplied dt/non-dt values are tested for origen exynos4210 and smdk exynos5250
platforms and only compile tested for exynos4412.

Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: jonghwa lee <jonghwa3.lee@samsung.com>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 drivers/thermal/exynos_thermal.c |  111 +++++++++++++++++++++++++++++++++++++-
 1 files changed, 110 insertions(+), 1 deletions(-)

diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index e79cdc9..03a99e4 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -723,14 +723,121 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id)
 static struct thermal_sensor_conf exynos_sensor_conf = {
 	.name			= "exynos-therm",
 	.read_temperature	= (int (*)(void *))exynos_tmu_read,
+};
+
+#if defined(CONFIG_CPU_EXYNOS4210)
+static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = {
+	.threshold = 80,
+	.trigger_levels[0] = 5,
+	.trigger_levels[1] = 20,
+	.trigger_levels[2] = 30,
+	.trigger_level0_en = 1,
+	.trigger_level1_en = 1,
+	.trigger_level2_en = 1,
+	.trigger_level3_en = 0,
+	.gain = 15,
+	.reference_voltage = 7,
+	.cal_type = TYPE_ONE_POINT_TRIMMING,
+	.freq_tab[0] = {
+		.freq_clip_max = 800 * 1000,
+		.temp_level = 85,
+	},
+	.freq_tab[1] = {
+		.freq_clip_max = 200 * 1000,
+		.temp_level = 100,
+	},
+	.freq_tab_count = 2,
+	.type = SOC_ARCH_EXYNOS4210,
+};
+#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data)
+#else
+#define EXYNOS4210_TMU_DRV_DATA (NULL)
+#endif
+
+#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412)
+static struct exynos_tmu_platform_data const exynos_default_tmu_data = {
+	.trigger_levels[0] = 85,
+	.trigger_levels[1] = 103,
+	.trigger_levels[2] = 110,
+	.trigger_level0_en = 1,
+	.trigger_level1_en = 1,
+	.trigger_level2_en = 1,
+	.trigger_level3_en = 0,
+	.gain = 8,
+	.reference_voltage = 16,
+	.noise_cancel_mode = 4,
+	.cal_type = TYPE_ONE_POINT_TRIMMING,
+	.efuse_value = 55,
+	.freq_tab[0] = {
+		.freq_clip_max = 800 * 1000,
+		.temp_level = 85,
+	},
+	.freq_tab[1] = {
+		.freq_clip_max = 200 * 1000,
+		.temp_level = 103,
+	},
+	.freq_tab_count = 2,
+	.type = SOC_ARCH_EXYNOS,
+};
+#define EXYNOS_TMU_DRV_DATA (&exynos_default_tmu_data)
+#else
+#define EXYNOS_TMU_DRV_DATA (NULL)
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id exynos_tmu_match[] = {
+	{
+		.compatible = "samsung,exynos4210-tmu",
+		.data = (void *)EXYNOS4210_TMU_DRV_DATA,
+	},
+	{
+		.compatible = "samsung,exynos5250-tmu",
+		.data = (void *)EXYNOS_TMU_DRV_DATA,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos_tmu_match);
+#else
+#define  exynos_tmu_match NULL
+#endif
+
+static struct platform_device_id exynos_tmu_driver_ids[] = {
+	{
+		.name		= "exynos4210-tmu",
+		.driver_data    = (kernel_ulong_t)EXYNOS4210_TMU_DRV_DATA,
+	},
+	{
+		.name		= "exynos5250-tmu",
+		.driver_data    = (kernel_ulong_t)EXYNOS_TMU_DRV_DATA,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(platform, exynos4_tmu_driver_ids);
+
+static inline struct  exynos_tmu_platform_data *exynos_get_driver_data(
+			struct platform_device *pdev)
+{
+#ifdef CONFIG_OF
+	if (pdev->dev.of_node) {
+		const struct of_device_id *match;
+		match = of_match_node(exynos_tmu_match, pdev->dev.of_node);
+		if (!match)
+			return NULL;
+		return (struct exynos_tmu_platform_data *) match->data;
+	}
+#endif
+	return (struct exynos_tmu_platform_data *)
+			platform_get_device_id(pdev)->driver_data;
 }
-;
 static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 {
 	struct exynos_tmu_data *data;
 	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
 	int ret, i;
 
+	if (!pdata)
+		pdata = exynos_get_driver_data(pdev);
+
 	if (!pdata) {
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
 		return -ENODEV;
@@ -899,9 +1006,11 @@ static struct platform_driver exynos_tmu_driver = {
 		.name   = "exynos-tmu",
 		.owner  = THIS_MODULE,
 		.pm     = EXYNOS_TMU_PM,
+		.of_match_table = exynos_tmu_match,
 	},
 	.probe = exynos_tmu_probe,
 	.remove	= __devexit_p(exynos_tmu_remove),
+	.id_table = exynos_tmu_driver_ids,
 };
 
 module_platform_driver(exynos_tmu_driver);
-- 
1.7.1


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [lm-sensors] [PATCH v6 6/6] thermal: exynos: Use devm_* functions
@ 2012-08-16 11:41   ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-16 11:53 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-acpi, linux-samsung-soc, linux-kernel, lm-sensors

devm_* functions are used to replace kzalloc, request_mem_region, ioremap
and request_irq functions in probe call. With the usage of devm_* functions
explicit freeing and unmapping is not required.

Signed-off-by: Sachin Kamat <sachin.kamat@linaro.org>
Signed-off-by: Sachin Kamat <sachin.kamat@samsung.com>
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
---
 drivers/thermal/exynos_thermal.c |   45 +++++++------------------------------
 1 files changed, 9 insertions(+), 36 deletions(-)

diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
index 03a99e4..e84acde 100644
--- a/drivers/thermal/exynos_thermal.c
+++ b/drivers/thermal/exynos_thermal.c
@@ -842,7 +842,8 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 		dev_err(&pdev->dev, "No platform init data supplied.\n");
 		return -ENODEV;
 	}
-	data = kzalloc(sizeof(struct exynos_tmu_data), GFP_KERNEL);
+	data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data),
+					GFP_KERNEL);
 	if (!data) {
 		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
 		return -ENOMEM;
@@ -850,47 +851,35 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 
 	data->irq = platform_get_irq(pdev, 0);
 	if (data->irq < 0) {
-		ret = data->irq;
 		dev_err(&pdev->dev, "Failed to get platform irq\n");
-		goto err_free;
+		return data->irq;
 	}
 
 	INIT_WORK(&data->irq_work, exynos_tmu_work);
 
 	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	if (!data->mem) {
-		ret = -ENOENT;
 		dev_err(&pdev->dev, "Failed to get platform resource\n");
-		goto err_free;
+		return -ENOENT;
 	}
 
-	data->mem = request_mem_region(data->mem->start,
-			resource_size(data->mem), pdev->name);
-	if (!data->mem) {
-		ret = -ENODEV;
-		dev_err(&pdev->dev, "Failed to request memory region\n");
-		goto err_free;
-	}
-
-	data->base = ioremap(data->mem->start, resource_size(data->mem));
+	data->base = devm_request_and_ioremap(&pdev->dev, data->mem);
 	if (!data->base) {
-		ret = -ENODEV;
 		dev_err(&pdev->dev, "Failed to ioremap memory\n");
-		goto err_mem_region;
+		return -ENODEV;
 	}
 
-	ret = request_irq(data->irq, exynos_tmu_irq,
+	ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
 		IRQF_TRIGGER_RISING, "exynos-tmu", data);
 	if (ret) {
 		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
-		goto err_io_remap;
+		return ret;
 	}
 
 	data->clk = clk_get(NULL, "tmu_apbif");
 	if (IS_ERR(data->clk)) {
-		ret = PTR_ERR(data->clk);
 		dev_err(&pdev->dev, "Failed to get clock\n");
-		goto err_irq;
+		return  PTR_ERR(data->clk);
 	}
 
 	if (pdata->type = SOC_ARCH_EXYNOS ||
@@ -942,15 +931,6 @@ static int __devinit exynos_tmu_probe(struct platform_device *pdev)
 err_clk:
 	platform_set_drvdata(pdev, NULL);
 	clk_put(data->clk);
-err_irq:
-	free_irq(data->irq, data);
-err_io_remap:
-	iounmap(data->base);
-err_mem_region:
-	release_mem_region(data->mem->start, resource_size(data->mem));
-err_free:
-	kfree(data);
-
 	return ret;
 }
 
@@ -964,15 +944,8 @@ static int __devexit exynos_tmu_remove(struct platform_device *pdev)
 
 	clk_put(data->clk);
 
-	free_irq(data->irq, data);
-
-	iounmap(data->base);
-	release_mem_region(data->mem->start, resource_size(data->mem));
-
 	platform_set_drvdata(pdev, NULL);
 
-	kfree(data);
-
 	return 0;
 }
 
-- 
1.7.1


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-16 11:41   ` Amit Daniel Kachhap
  (?)
@ 2012-08-16 14:52     ` Valentin, Eduardo
  -1 siblings, 0 replies; 50+ messages in thread
From: Valentin, Eduardo @ 2012-08-16 14:52 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: Guenter Roeck, linux-samsung-soc, Kyungmin Park, SangWook Ju,
	linux-kernel, linux-acpi, Kukjin Kim, Jean Delvare, linux-pm,
	lm-sensors, Andrew Morton

Amit,

Thanks again for keeping this up.

On Thu, Aug 16, 2012 at 2:41 PM, Amit Daniel Kachhap
<amit.kachhap@linaro.org> wrote:
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms.  As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
>
>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)

FYI, the OMAP code is under drivers/staging/omap-thermal/. The file
omap-thermal-common.c is the one which is using your generic cooling
device. But it needs to be updated accordingly to the API change you
mention.

>
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale platforms.
>
> Brief Description:
>
> 1) The generic cooling devices code is placed inside driver/thermal/*
>    as placing inside acpi folder will need un-necessary enabling of acpi
>    code.  This code is architecture independent.
>
> 2) This patchset adds generic cpu cooling low level implementation
>    through frequency clipping.  In future, other cpu related cooling
>    devices may be added here.  An ACPI version of this already exists
>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>    platforms like ARM using the generic thermal interface along with the
>    generic cpu cooling devices.  The cooling device registration API's
>    return cooling device pointers which can be easily binded with the
>    thermal zone trip points.  The important APIs exposed are,
>
>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>         struct cpumask *clip_cpus)
>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>
> 3) Samsung exynos platform thermal implementation is done using the
>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>    driver present in the hwmon folder(registered as hwmon driver) is moved
>    to thermal folder and registered as a thermal driver.
>
> A simple data/control flow diagrams is shown below,
>
> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>           |                             |
>          \|/                            |
>   Cpufreq cooling device <---------------
>
> TODO:
> *Will send the DT enablement patches later after the driver is merged.
>
> This patch:
>
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
>
> [akpm@linux-foundation.org: fix comment layout]
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>


Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>


> ---
>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>  drivers/thermal/Kconfig                   |   11 +
>  drivers/thermal/Makefile                  |    1 +
>  drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
>  include/linux/cpu_cooling.h               |   79 +++++
>  5 files changed, 655 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>  create mode 100644 drivers/thermal/cpu_cooling.c
>  create mode 100644 include/linux/cpu_cooling.h
>
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..a1f2a6b
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,52 @@
> +CPU cooling APIs How To
> +===================================
> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +
> +    This interface function registers the cpufreq cooling device with the name
> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> +    cooling devices.
> +
> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> +    cdev: Cooling device pointer which has to be unregistered.
> +
> +
> +1.2 CPU cooling action notifier register/unregister interface
> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> +
> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>         depends on HWMON=y || HWMON=THERMAL
>         default y
>
> +config CPU_THERMAL
> +       bool "generic cpu cooling support"
> +       depends on THERMAL && CPU_FREQ
> +       help
> +         This implements the generic cpu cooling mechanism through frequency
> +         reduction, cpu hotplug and any other ways of reducing temperature. An
> +         ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> +         This will be useful for platforms using the generic thermal interface
> +         and not the ACPI interface.
> +         If you want this support, you should say Y here.
> +
>  config SPEAR_THERMAL
>         bool "SPEAr thermal sensor driver"
>         depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
>  #
>
>  obj-$(CONFIG_THERMAL)          += thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL)              += cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
>  obj-$(CONFIG_RCAR_THERMAL)     += rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..c42e557
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,512 @@
> +/*
> + *  linux/drivers/thermal/cpu_cooling.c
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful, but
> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + *  General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + *     registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + *     egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + *     cooling devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + *     frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> +       int id;
> +       struct thermal_cooling_device *cool_dev;
> +       unsigned int cpufreq_state;
> +       unsigned int cpufreq_val;
> +       struct cpumask allowed_cpus;
> +       struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/* Head of the blocking notifier chain to inform about frequency clamping */
> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> +       int err;
> +again:
> +       if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> +               return -ENOMEM;
> +
> +       mutex_lock(&cooling_cpufreq_lock);
> +       err = idr_get_new(idr, NULL, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       if (unlikely(err == -EAGAIN))
> +               goto again;
> +       else if (unlikely(err))
> +               return err;
> +
> +       *id = *id & MAX_ID_MASK;
> +       return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> +       mutex_lock(&cooling_cpufreq_lock);
> +       idr_remove(idr, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_register(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_register_notifier);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_unregister(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> +       struct cpufreq_policy policy;
> +       return !cpufreq_get_policy(&policy, 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
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)


I like this. With previous implemention I had to write a similar
function, but with output of the required freq  clip table. Now that
you have the freq walk here it simplifies the zone setup code.


> +{
> +       int ret = 0, i = 0;
> +       unsigned long level_index;
> +       bool descend = false;
> +       struct cpufreq_frequency_table *table =
> +                                       cpufreq_frequency_get_table(cpu);
> +       if (!table)
> +               return ret;
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +
> +               /*check if table in ascending or descending order*/
> +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +                       (table[i + 1].frequency < table[i].frequency)
> +                       && !descend) {
> +                       descend = true;
> +               }
> +
> +               /*return if level matched and table in descending order*/
> +               if (descend && i == level)
> +                       return table[i].frequency;
> +               i++;
> +       }
> +       i--;
> +
> +       if (level > i || descend)
> +               return ret;
> +       level_index = i - level;
> +
> +       /*Scan the table in reverse order and match the level*/
> +       while (i >= 0) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               /*return if level matched*/
> +               if (i == level_index)
> +                       return table[i].frequency;
> +               i--;
> +       }
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + *     clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> +                               unsigned long cooling_state)
> +{
> +       unsigned int event, cpuid, clip_freq;
> +       struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> +       unsigned int cpu = cpumask_any(maskPtr);
> +
> +
> +       /* Check if the old cooling action is same as new cooling action */
> +       if (cpufreq_device->cpufreq_state == cooling_state)
> +               return 0;
> +
> +       clip_freq = get_cpu_frequency(cpu, cooling_state);
> +       if (!clip_freq)
> +               return -EINVAL;
> +
> +       cpufreq_device->cpufreq_state = cooling_state;
> +       cpufreq_device->cpufreq_val = clip_freq;
> +       notify_device = cpufreq_device;
> +
> +       if (cooling_state != 0)
> +               event = CPUFREQ_COOLING_START;
> +       else
> +               event = CPUFREQ_COOLING_STOP;
> +
> +       blocking_notifier_call_chain(&cputherm_state_notifier_list,
> +                                               event, &clip_freq);
> +
> +       for_each_cpu(cpuid, maskPtr) {
> +               if (is_cpufreq_valid(cpuid))
> +                       cpufreq_update_policy(cpuid);
> +       }
> +
> +       notify_device = NOTIFY_INVALID;
> +
> +       return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb:        struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> +                                       unsigned long event, void *data)
> +{
> +       struct cpufreq_policy *policy = data;
> +       unsigned long max_freq = 0;
> +
> +       if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> +               return 0;
> +
> +       if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> +               max_freq = notify_device->cpufreq_val;
> +
> +       /* Never exceed user_policy.max*/
> +       if (max_freq > policy->user_policy.max)
> +               max_freq = policy->user_policy.max;
> +
> +       if (policy->max != max_freq)
> +               cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> +       return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +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;
> +       unsigned int cpu;
> +       struct cpufreq_frequency_table *table;
> +
> +       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;
> +       }
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               i++;
> +       }
> +       if (i > 0) {
> +               *state = --i;
> +               ret = 0;
> +       }
> +
> +return_get_max_state:
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling 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);
> +
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_set_cur_state - callback function to set the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: set this variable to the current cooling state.
> + */
> +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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       return ret;
> +}
> +
> +/* Bind cpufreq callbacks to thermal cooling device ops */
> +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> +       .get_max_state = cpufreq_get_max_state,
> +       .get_cur_state = cpufreq_get_cur_state,
> +       .set_cur_state = cpufreq_set_cur_state,
> +};
> +
> +/* Notifier for cpufreq policy change */
> +static struct notifier_block thermal_cpufreq_notifier_block = {
> +       .notifier_call = cpufreq_thermal_notifier,
> +};
> +
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +       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;
> +       char dev_name[THERMAL_NAME_LENGTH];
> +       int ret = 0, id = 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*/
> +               if (!cpufreq_get_policy(&policy, i))
> +                       continue;
> +               if (min == 0 && max == 0) {
> +                       min = policy.cpuinfo.min_freq;
> +                       max = policy.cpuinfo.max_freq;
> +               } else {
> +                       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);
> +       if (!cpufreq_dev)
> +               return ERR_PTR(-ENOMEM);
> +
> +       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);
> +               return ERR_PTR(-EINVAL);
> +       }
> +
> +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> +
> +       cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> +                                               &cpufreq_cooling_ops);
> +       if (!cool_dev) {
> +               release_idr(&cpufreq_idr, cpufreq_dev->id);
> +               kfree(cpufreq_dev);
> +               return ERR_PTR(-EINVAL);
> +       }
> +       cpufreq_dev->id = id;
> +       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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return cool_dev;
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_register);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +{
> +       struct cpufreq_cooling_device *cpufreq_dev = NULL;
> +       unsigned int cpufreq_dev_count = 0;
> +
> +       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);
> +
> +       /* Unregister the notifier for the last cpufreq cooling device */
> +       if (cpufreq_dev_count == 1) {
> +               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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> new file mode 100644
> index 0000000..555b917
> --- /dev/null
> +++ b/include/linux/cpu_cooling.h
> @@ -0,0 +1,79 @@
> +/*
> + *  linux/include/linux/cpu_cooling.h
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  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 __CPU_COOLING_H__
> +#define __CPU_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +#define CPUFREQ_COOLING_START          0
> +#define CPUFREQ_COOLING_STOP           1
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
> +
> +#ifdef CONFIG_CPU_THERMAL
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +               struct cpumask *clip_cpus);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> +#else /* !CONFIG_CPU_THERMAL */
> +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +{
> +       return NULL;
> +}
> +static inline void cpufreq_cooling_unregister(
> +               struct thermal_cooling_device *cdev)
> +{
> +       return;
> +}
> +#endif /* CONFIG_CPU_THERMAL */
> +
> +#endif /* __CPU_COOLING_H__ */
> --
> 1.7.1
>



-- 

Eduardo Valentin

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [linux-pm] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-16 14:52     ` Valentin, Eduardo
  0 siblings, 0 replies; 50+ messages in thread
From: Valentin, Eduardo @ 2012-08-16 14:52 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: linux-pm, Andrew Morton, Zhang Rui, Len Brown, linux-samsung-soc,
	SangWook Ju, linux-kernel, lm-sensors, linux-acpi, Kukjin Kim,
	Jean Delvare, Kyungmin Park, Guenter Roeck

Amit,

Thanks again for keeping this up.

On Thu, Aug 16, 2012 at 2:41 PM, Amit Daniel Kachhap
<amit.kachhap@linaro.org> wrote:
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms.  As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
>
>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)

FYI, the OMAP code is under drivers/staging/omap-thermal/. The file
omap-thermal-common.c is the one which is using your generic cooling
device. But it needs to be updated accordingly to the API change you
mention.

>
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale platforms.
>
> Brief Description:
>
> 1) The generic cooling devices code is placed inside driver/thermal/*
>    as placing inside acpi folder will need un-necessary enabling of acpi
>    code.  This code is architecture independent.
>
> 2) This patchset adds generic cpu cooling low level implementation
>    through frequency clipping.  In future, other cpu related cooling
>    devices may be added here.  An ACPI version of this already exists
>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>    platforms like ARM using the generic thermal interface along with the
>    generic cpu cooling devices.  The cooling device registration API's
>    return cooling device pointers which can be easily binded with the
>    thermal zone trip points.  The important APIs exposed are,
>
>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>         struct cpumask *clip_cpus)
>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>
> 3) Samsung exynos platform thermal implementation is done using the
>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>    driver present in the hwmon folder(registered as hwmon driver) is moved
>    to thermal folder and registered as a thermal driver.
>
> A simple data/control flow diagrams is shown below,
>
> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>           |                             |
>          \|/                            |
>   Cpufreq cooling device <---------------
>
> TODO:
> *Will send the DT enablement patches later after the driver is merged.
>
> This patch:
>
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
>
> [akpm@linux-foundation.org: fix comment layout]
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>


Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>


> ---
>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>  drivers/thermal/Kconfig                   |   11 +
>  drivers/thermal/Makefile                  |    1 +
>  drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
>  include/linux/cpu_cooling.h               |   79 +++++
>  5 files changed, 655 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>  create mode 100644 drivers/thermal/cpu_cooling.c
>  create mode 100644 include/linux/cpu_cooling.h
>
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..a1f2a6b
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,52 @@
> +CPU cooling APIs How To
> +===================================
> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +
> +    This interface function registers the cpufreq cooling device with the name
> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> +    cooling devices.
> +
> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> +    cdev: Cooling device pointer which has to be unregistered.
> +
> +
> +1.2 CPU cooling action notifier register/unregister interface
> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> +
> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>         depends on HWMON=y || HWMON=THERMAL
>         default y
>
> +config CPU_THERMAL
> +       bool "generic cpu cooling support"
> +       depends on THERMAL && CPU_FREQ
> +       help
> +         This implements the generic cpu cooling mechanism through frequency
> +         reduction, cpu hotplug and any other ways of reducing temperature. An
> +         ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> +         This will be useful for platforms using the generic thermal interface
> +         and not the ACPI interface.
> +         If you want this support, you should say Y here.
> +
>  config SPEAR_THERMAL
>         bool "SPEAr thermal sensor driver"
>         depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
>  #
>
>  obj-$(CONFIG_THERMAL)          += thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL)              += cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
>  obj-$(CONFIG_RCAR_THERMAL)     += rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..c42e557
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,512 @@
> +/*
> + *  linux/drivers/thermal/cpu_cooling.c
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful, but
> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + *  General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + *     registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + *     egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + *     cooling devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + *     frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> +       int id;
> +       struct thermal_cooling_device *cool_dev;
> +       unsigned int cpufreq_state;
> +       unsigned int cpufreq_val;
> +       struct cpumask allowed_cpus;
> +       struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/* Head of the blocking notifier chain to inform about frequency clamping */
> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> +       int err;
> +again:
> +       if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> +               return -ENOMEM;
> +
> +       mutex_lock(&cooling_cpufreq_lock);
> +       err = idr_get_new(idr, NULL, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       if (unlikely(err == -EAGAIN))
> +               goto again;
> +       else if (unlikely(err))
> +               return err;
> +
> +       *id = *id & MAX_ID_MASK;
> +       return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> +       mutex_lock(&cooling_cpufreq_lock);
> +       idr_remove(idr, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_register(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_register_notifier);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_unregister(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> +       struct cpufreq_policy policy;
> +       return !cpufreq_get_policy(&policy, 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
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)


I like this. With previous implemention I had to write a similar
function, but with output of the required freq  clip table. Now that
you have the freq walk here it simplifies the zone setup code.


> +{
> +       int ret = 0, i = 0;
> +       unsigned long level_index;
> +       bool descend = false;
> +       struct cpufreq_frequency_table *table =
> +                                       cpufreq_frequency_get_table(cpu);
> +       if (!table)
> +               return ret;
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +
> +               /*check if table in ascending or descending order*/
> +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +                       (table[i + 1].frequency < table[i].frequency)
> +                       && !descend) {
> +                       descend = true;
> +               }
> +
> +               /*return if level matched and table in descending order*/
> +               if (descend && i == level)
> +                       return table[i].frequency;
> +               i++;
> +       }
> +       i--;
> +
> +       if (level > i || descend)
> +               return ret;
> +       level_index = i - level;
> +
> +       /*Scan the table in reverse order and match the level*/
> +       while (i >= 0) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               /*return if level matched*/
> +               if (i == level_index)
> +                       return table[i].frequency;
> +               i--;
> +       }
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + *     clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> +                               unsigned long cooling_state)
> +{
> +       unsigned int event, cpuid, clip_freq;
> +       struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> +       unsigned int cpu = cpumask_any(maskPtr);
> +
> +
> +       /* Check if the old cooling action is same as new cooling action */
> +       if (cpufreq_device->cpufreq_state == cooling_state)
> +               return 0;
> +
> +       clip_freq = get_cpu_frequency(cpu, cooling_state);
> +       if (!clip_freq)
> +               return -EINVAL;
> +
> +       cpufreq_device->cpufreq_state = cooling_state;
> +       cpufreq_device->cpufreq_val = clip_freq;
> +       notify_device = cpufreq_device;
> +
> +       if (cooling_state != 0)
> +               event = CPUFREQ_COOLING_START;
> +       else
> +               event = CPUFREQ_COOLING_STOP;
> +
> +       blocking_notifier_call_chain(&cputherm_state_notifier_list,
> +                                               event, &clip_freq);
> +
> +       for_each_cpu(cpuid, maskPtr) {
> +               if (is_cpufreq_valid(cpuid))
> +                       cpufreq_update_policy(cpuid);
> +       }
> +
> +       notify_device = NOTIFY_INVALID;
> +
> +       return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb:        struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> +                                       unsigned long event, void *data)
> +{
> +       struct cpufreq_policy *policy = data;
> +       unsigned long max_freq = 0;
> +
> +       if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> +               return 0;
> +
> +       if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> +               max_freq = notify_device->cpufreq_val;
> +
> +       /* Never exceed user_policy.max*/
> +       if (max_freq > policy->user_policy.max)
> +               max_freq = policy->user_policy.max;
> +
> +       if (policy->max != max_freq)
> +               cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> +       return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +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;
> +       unsigned int cpu;
> +       struct cpufreq_frequency_table *table;
> +
> +       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;
> +       }
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               i++;
> +       }
> +       if (i > 0) {
> +               *state = --i;
> +               ret = 0;
> +       }
> +
> +return_get_max_state:
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling 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);
> +
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_set_cur_state - callback function to set the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: set this variable to the current cooling state.
> + */
> +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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       return ret;
> +}
> +
> +/* Bind cpufreq callbacks to thermal cooling device ops */
> +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> +       .get_max_state = cpufreq_get_max_state,
> +       .get_cur_state = cpufreq_get_cur_state,
> +       .set_cur_state = cpufreq_set_cur_state,
> +};
> +
> +/* Notifier for cpufreq policy change */
> +static struct notifier_block thermal_cpufreq_notifier_block = {
> +       .notifier_call = cpufreq_thermal_notifier,
> +};
> +
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +       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;
> +       char dev_name[THERMAL_NAME_LENGTH];
> +       int ret = 0, id = 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*/
> +               if (!cpufreq_get_policy(&policy, i))
> +                       continue;
> +               if (min == 0 && max == 0) {
> +                       min = policy.cpuinfo.min_freq;
> +                       max = policy.cpuinfo.max_freq;
> +               } else {
> +                       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);
> +       if (!cpufreq_dev)
> +               return ERR_PTR(-ENOMEM);
> +
> +       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);
> +               return ERR_PTR(-EINVAL);
> +       }
> +
> +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> +
> +       cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> +                                               &cpufreq_cooling_ops);
> +       if (!cool_dev) {
> +               release_idr(&cpufreq_idr, cpufreq_dev->id);
> +               kfree(cpufreq_dev);
> +               return ERR_PTR(-EINVAL);
> +       }
> +       cpufreq_dev->id = id;
> +       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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return cool_dev;
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_register);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +{
> +       struct cpufreq_cooling_device *cpufreq_dev = NULL;
> +       unsigned int cpufreq_dev_count = 0;
> +
> +       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);
> +
> +       /* Unregister the notifier for the last cpufreq cooling device */
> +       if (cpufreq_dev_count == 1) {
> +               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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> new file mode 100644
> index 0000000..555b917
> --- /dev/null
> +++ b/include/linux/cpu_cooling.h
> @@ -0,0 +1,79 @@
> +/*
> + *  linux/include/linux/cpu_cooling.h
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  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 __CPU_COOLING_H__
> +#define __CPU_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +#define CPUFREQ_COOLING_START          0
> +#define CPUFREQ_COOLING_STOP           1
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
> +
> +#ifdef CONFIG_CPU_THERMAL
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +               struct cpumask *clip_cpus);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> +#else /* !CONFIG_CPU_THERMAL */
> +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +{
> +       return NULL;
> +}
> +static inline void cpufreq_cooling_unregister(
> +               struct thermal_cooling_device *cdev)
> +{
> +       return;
> +}
> +#endif /* CONFIG_CPU_THERMAL */
> +
> +#endif /* __CPU_COOLING_H__ */
> --
> 1.7.1
>



-- 

Eduardo Valentin

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [lm-sensors] [linux-pm] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-16 14:52     ` Valentin, Eduardo
  0 siblings, 0 replies; 50+ messages in thread
From: Valentin, Eduardo @ 2012-08-16 14:52 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: Guenter Roeck, linux-samsung-soc, Kyungmin Park, SangWook Ju,
	linux-kernel, linux-acpi, Kukjin Kim, Jean Delvare, linux-pm,
	lm-sensors, Andrew Morton

Amit,

Thanks again for keeping this up.

On Thu, Aug 16, 2012 at 2:41 PM, Amit Daniel Kachhap
<amit.kachhap@linaro.org> wrote:
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms.  As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
>
>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)

FYI, the OMAP code is under drivers/staging/omap-thermal/. The file
omap-thermal-common.c is the one which is using your generic cooling
device. But it needs to be updated accordingly to the API change you
mention.

>
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale platforms.
>
> Brief Description:
>
> 1) The generic cooling devices code is placed inside driver/thermal/*
>    as placing inside acpi folder will need un-necessary enabling of acpi
>    code.  This code is architecture independent.
>
> 2) This patchset adds generic cpu cooling low level implementation
>    through frequency clipping.  In future, other cpu related cooling
>    devices may be added here.  An ACPI version of this already exists
>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>    platforms like ARM using the generic thermal interface along with the
>    generic cpu cooling devices.  The cooling device registration API's
>    return cooling device pointers which can be easily binded with the
>    thermal zone trip points.  The important APIs exposed are,
>
>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>         struct cpumask *clip_cpus)
>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>
> 3) Samsung exynos platform thermal implementation is done using the
>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>    driver present in the hwmon folder(registered as hwmon driver) is moved
>    to thermal folder and registered as a thermal driver.
>
> A simple data/control flow diagrams is shown below,
>
> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>           |                             |
>          \|/                            |
>   Cpufreq cooling device <---------------
>
> TODO:
> *Will send the DT enablement patches later after the driver is merged.
>
> This patch:
>
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
>
> [akpm@linux-foundation.org: fix comment layout]
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>


Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>


> ---
>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>  drivers/thermal/Kconfig                   |   11 +
>  drivers/thermal/Makefile                  |    1 +
>  drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
>  include/linux/cpu_cooling.h               |   79 +++++
>  5 files changed, 655 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>  create mode 100644 drivers/thermal/cpu_cooling.c
>  create mode 100644 include/linux/cpu_cooling.h
>
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..a1f2a6b
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,52 @@
> +CPU cooling APIs How To
> +=================> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +
> +    This interface function registers the cpufreq cooling device with the name
> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> +    cooling devices.
> +
> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> +    cdev: Cooling device pointer which has to be unregistered.
> +
> +
> +1.2 CPU cooling action notifier register/unregister interface
> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> +
> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>         depends on HWMON=y || HWMON=THERMAL
>         default y
>
> +config CPU_THERMAL
> +       bool "generic cpu cooling support"
> +       depends on THERMAL && CPU_FREQ
> +       help
> +         This implements the generic cpu cooling mechanism through frequency
> +         reduction, cpu hotplug and any other ways of reducing temperature. An
> +         ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> +         This will be useful for platforms using the generic thermal interface
> +         and not the ACPI interface.
> +         If you want this support, you should say Y here.
> +
>  config SPEAR_THERMAL
>         bool "SPEAr thermal sensor driver"
>         depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
>  #
>
>  obj-$(CONFIG_THERMAL)          += thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL)              += cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
>  obj-$(CONFIG_RCAR_THERMAL)     += rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..c42e557
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,512 @@
> +/*
> + *  linux/drivers/thermal/cpu_cooling.c
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful, but
> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + *  General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + *     registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + *     egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + *     cooling devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + *     frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> +       int id;
> +       struct thermal_cooling_device *cool_dev;
> +       unsigned int cpufreq_state;
> +       unsigned int cpufreq_val;
> +       struct cpumask allowed_cpus;
> +       struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/* Head of the blocking notifier chain to inform about frequency clamping */
> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> +       int err;
> +again:
> +       if (unlikely(idr_pre_get(idr, GFP_KERNEL) = 0))
> +               return -ENOMEM;
> +
> +       mutex_lock(&cooling_cpufreq_lock);
> +       err = idr_get_new(idr, NULL, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       if (unlikely(err = -EAGAIN))
> +               goto again;
> +       else if (unlikely(err))
> +               return err;
> +
> +       *id = *id & MAX_ID_MASK;
> +       return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> +       mutex_lock(&cooling_cpufreq_lock);
> +       idr_remove(idr, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_register(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_register_notifier);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_unregister(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> +       struct cpufreq_policy policy;
> +       return !cpufreq_get_policy(&policy, 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
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)


I like this. With previous implemention I had to write a similar
function, but with output of the required freq  clip table. Now that
you have the freq walk here it simplifies the zone setup code.


> +{
> +       int ret = 0, i = 0;
> +       unsigned long level_index;
> +       bool descend = false;
> +       struct cpufreq_frequency_table *table > +                                       cpufreq_frequency_get_table(cpu);
> +       if (!table)
> +               return ret;
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +
> +               /*check if table in ascending or descending order*/
> +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +                       (table[i + 1].frequency < table[i].frequency)
> +                       && !descend) {
> +                       descend = true;
> +               }
> +
> +               /*return if level matched and table in descending order*/
> +               if (descend && i = level)
> +                       return table[i].frequency;
> +               i++;
> +       }
> +       i--;
> +
> +       if (level > i || descend)
> +               return ret;
> +       level_index = i - level;
> +
> +       /*Scan the table in reverse order and match the level*/
> +       while (i >= 0) {
> +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               /*return if level matched*/
> +               if (i = level_index)
> +                       return table[i].frequency;
> +               i--;
> +       }
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + *     clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> +                               unsigned long cooling_state)
> +{
> +       unsigned int event, cpuid, clip_freq;
> +       struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> +       unsigned int cpu = cpumask_any(maskPtr);
> +
> +
> +       /* Check if the old cooling action is same as new cooling action */
> +       if (cpufreq_device->cpufreq_state = cooling_state)
> +               return 0;
> +
> +       clip_freq = get_cpu_frequency(cpu, cooling_state);
> +       if (!clip_freq)
> +               return -EINVAL;
> +
> +       cpufreq_device->cpufreq_state = cooling_state;
> +       cpufreq_device->cpufreq_val = clip_freq;
> +       notify_device = cpufreq_device;
> +
> +       if (cooling_state != 0)
> +               event = CPUFREQ_COOLING_START;
> +       else
> +               event = CPUFREQ_COOLING_STOP;
> +
> +       blocking_notifier_call_chain(&cputherm_state_notifier_list,
> +                                               event, &clip_freq);
> +
> +       for_each_cpu(cpuid, maskPtr) {
> +               if (is_cpufreq_valid(cpuid))
> +                       cpufreq_update_policy(cpuid);
> +       }
> +
> +       notify_device = NOTIFY_INVALID;
> +
> +       return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb:        struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> +                                       unsigned long event, void *data)
> +{
> +       struct cpufreq_policy *policy = data;
> +       unsigned long max_freq = 0;
> +
> +       if (event != CPUFREQ_ADJUST || notify_device = NOTIFY_INVALID)
> +               return 0;
> +
> +       if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> +               max_freq = notify_device->cpufreq_val;
> +
> +       /* Never exceed user_policy.max*/
> +       if (max_freq > policy->user_policy.max)
> +               max_freq = policy->user_policy.max;
> +
> +       if (policy->max != max_freq)
> +               cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> +       return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +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;
> +       unsigned int cpu;
> +       struct cpufreq_frequency_table *table;
> +
> +       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;
> +       }
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               i++;
> +       }
> +       if (i > 0) {
> +               *state = --i;
> +               ret = 0;
> +       }
> +
> +return_get_max_state:
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling 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);
> +
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_set_cur_state - callback function to set the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: set this variable to the current cooling state.
> + */
> +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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       return ret;
> +}
> +
> +/* Bind cpufreq callbacks to thermal cooling device ops */
> +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> +       .get_max_state = cpufreq_get_max_state,
> +       .get_cur_state = cpufreq_get_cur_state,
> +       .set_cur_state = cpufreq_set_cur_state,
> +};
> +
> +/* Notifier for cpufreq policy change */
> +static struct notifier_block thermal_cpufreq_notifier_block = {
> +       .notifier_call = cpufreq_thermal_notifier,
> +};
> +
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +       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;
> +       char dev_name[THERMAL_NAME_LENGTH];
> +       int ret = 0, id = 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*/
> +               if (!cpufreq_get_policy(&policy, i))
> +                       continue;
> +               if (min = 0 && max = 0) {
> +                       min = policy.cpuinfo.min_freq;
> +                       max = policy.cpuinfo.max_freq;
> +               } else {
> +                       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);
> +       if (!cpufreq_dev)
> +               return ERR_PTR(-ENOMEM);
> +
> +       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);
> +               return ERR_PTR(-EINVAL);
> +       }
> +
> +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> +
> +       cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> +                                               &cpufreq_cooling_ops);
> +       if (!cool_dev) {
> +               release_idr(&cpufreq_idr, cpufreq_dev->id);
> +               kfree(cpufreq_dev);
> +               return ERR_PTR(-EINVAL);
> +       }
> +       cpufreq_dev->id = id;
> +       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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return cool_dev;
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_register);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +{
> +       struct cpufreq_cooling_device *cpufreq_dev = NULL;
> +       unsigned int cpufreq_dev_count = 0;
> +
> +       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);
> +
> +       /* Unregister the notifier for the last cpufreq cooling device */
> +       if (cpufreq_dev_count = 1) {
> +               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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> new file mode 100644
> index 0000000..555b917
> --- /dev/null
> +++ b/include/linux/cpu_cooling.h
> @@ -0,0 +1,79 @@
> +/*
> + *  linux/include/linux/cpu_cooling.h
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  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 __CPU_COOLING_H__
> +#define __CPU_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +#define CPUFREQ_COOLING_START          0
> +#define CPUFREQ_COOLING_STOP           1
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
> +
> +#ifdef CONFIG_CPU_THERMAL
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +               struct cpumask *clip_cpus);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> +#else /* !CONFIG_CPU_THERMAL */
> +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +{
> +       return NULL;
> +}
> +static inline void cpufreq_cooling_unregister(
> +               struct thermal_cooling_device *cdev)
> +{
> +       return;
> +}
> +#endif /* CONFIG_CPU_THERMAL */
> +
> +#endif /* __CPU_COOLING_H__ */
> --
> 1.7.1
>



-- 

Eduardo Valentin

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [linux-pm] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-16 14:52     ` Valentin, Eduardo
@ 2012-08-17  3:55       ` Amit Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Kachhap @ 2012-08-17  3:43 UTC (permalink / raw)
  To: Valentin, Eduardo
  Cc: linux-pm, Andrew Morton, Zhang Rui, Len Brown, linux-samsung-soc,
	SangWook Ju, linux-kernel, lm-sensors, linux-acpi, Kukjin Kim,
	Jean Delvare, Kyungmin Park, Guenter Roeck

On 16 August 2012 20:22, Valentin, Eduardo <eduardo.valentin@ti.com> wrote:
>
> Amit,
>
> Thanks again for keeping this up.
>
> On Thu, Aug 16, 2012 at 2:41 PM, Amit Daniel Kachhap
> <amit.kachhap@linaro.org> wrote:
> > This patchset introduces a new generic cooling device based on cpufreq
> > that can be used on non-ACPI platforms.  As a proof of concept, we have
> > drivers for the following platforms using this mechanism now:
> >
> >  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
> >  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git
> > imx6q_thermal)
>
> FYI, the OMAP code is under drivers/staging/omap-thermal/. The file
> omap-thermal-common.c is the one which is using your generic cooling
> device. But it needs to be updated accordingly to the API change you
> mention.
>
> >
> > There is a small change in cpufreq cooling registration APIs, so a minor
> > change is needed for Freescale platforms.
> >
> > Brief Description:
> >
> > 1) The generic cooling devices code is placed inside driver/thermal/*
> >    as placing inside acpi folder will need un-necessary enabling of acpi
> >    code.  This code is architecture independent.
> >
> > 2) This patchset adds generic cpu cooling low level implementation
> >    through frequency clipping.  In future, other cpu related cooling
> >    devices may be added here.  An ACPI version of this already exists
> >    (drivers/acpi/processor_thermal.c) .But this will be useful for
> >    platforms like ARM using the generic thermal interface along with the
> >    generic cpu cooling devices.  The cooling device registration API's
> >    return cooling device pointers which can be easily binded with the
> >    thermal zone trip points.  The important APIs exposed are,
> >
> >    a) struct thermal_cooling_device *cpufreq_cooling_register(
> >         struct cpumask *clip_cpus)
> >    b) void cpufreq_cooling_unregister(struct thermal_cooling_device
> > *cdev)
> >
> > 3) Samsung exynos platform thermal implementation is done using the
> >    generic cpu cooling APIs and the new trip type.  The temperature
> > sensor
> >    driver present in the hwmon folder(registered as hwmon driver) is
> > moved
> >    to thermal folder and registered as a thermal driver.
> >
> > A simple data/control flow diagrams is shown below,
> >
> > Core Linux thermal <----->  Exynos thermal interface <----- Temperature
> > Sensor
> >           |                             |
> >          \|/                            |
> >   Cpufreq cooling device <---------------
> >
> > TODO:
> > *Will send the DT enablement patches later after the driver is merged.
> >
> > This patch:
> >
> > Add support for generic cpu thermal cooling low level implementations
> > using frequency scaling up/down based on the registration parameters.
> > Different cpu related cooling devices can be registered by the user and
> > the binding of these cooling devices to the corresponding trip points
> > can
> > be easily done as the registration APIs return the cooling device
> > pointer.
> > The user of these APIs are responsible for passing clipping frequency .
> > The drivers can also register to recieve notification about any cooling
> > action called.
> >
> > [akpm@linux-foundation.org: fix comment layout]
> > Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> > Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> > Cc: SangWook Ju <sw.ju@samsung.com>
> > Cc: Durgadoss <durgadoss.r@intel.com>
> > Cc: Len Brown <lenb@kernel.org>
> > Cc: Jean Delvare <khali@linux-fr.org>
> > Cc: Kyungmin Park <kmpark@infradead.org>
> > Cc: Kukjin Kim <kgene.kim@samsung.com>
> > Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> > Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
>
>
> Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>

Thanks Eduardo .

>
>
> > ---
> >  Documentation/thermal/cpu-cooling-api.txt |   52 +++
> >  drivers/thermal/Kconfig                   |   11 +
> >  drivers/thermal/Makefile                  |    1 +
> >  drivers/thermal/cpu_cooling.c             |  512
> > +++++++++++++++++++++++++++++
> >  include/linux/cpu_cooling.h               |   79 +++++
> >  5 files changed, 655 insertions(+), 0 deletions(-)
> >  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
> >  create mode 100644 drivers/thermal/cpu_cooling.c
> >  create mode 100644 include/linux/cpu_cooling.h
> >
> > diff --git a/Documentation/thermal/cpu-cooling-api.txt
> > b/Documentation/thermal/cpu-cooling-api.txt
> > new file mode 100644
> > index 0000000..a1f2a6b
> > --- /dev/null
> > +++ b/Documentation/thermal/cpu-cooling-api.txt
> > @@ -0,0 +1,52 @@
> > +CPU cooling APIs How To
> > +===================================
> > +
> > +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> > +
> > +Updated: 12 May 2012
> > +
> > +Copyright (c)  2012 Samsung Electronics Co.,
> > Ltd(http://www.samsung.com)
> > +
> > +0. Introduction
> > +
> > +The generic cpu cooling(freq clipping) provides
> > registration/unregistration APIs
> > +to the caller. The binding of the cooling devices to the trip point is
> > left for
> > +the user. The registration APIs returns the cooling device pointer.
> > +
> > +1. cpu cooling APIs
> > +
> > +1.1 cpufreq registration/unregistration APIs
> > +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> > +       struct cpumask *clip_cpus)
> > +
> > +    This interface function registers the cpufreq cooling device with
> > the name
> > +    "thermal-cpufreq-%x". This api can support multiple instances of
> > cpufreq
> > +    cooling devices.
> > +
> > +   clip_cpus: cpumask of cpus where the frequency constraints will
> > happen.
> > +
> > +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device
> > *cdev)
> > +
> > +    This interface function unregisters the "thermal-cpufreq-%x"
> > cooling device.
> > +
> > +    cdev: Cooling device pointer which has to be unregistered.
> > +
> > +
> > +1.2 CPU cooling action notifier register/unregister interface
> > +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> > +       unsigned int list)
> > +
> > +    This interface registers a driver with cpu cooling layer. The
> > driver will
> > +    be notified when any cpu cooling action is called.
> > +
> > +    nb: notifier function to register
> > +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> > +
> > +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> > +       unsigned int list)
> > +
> > +    This interface registers a driver with cpu cooling layer. The
> > driver will
> > +    be notified when any cpu cooling action is called.
> > +
> > +    nb: notifier function to register
> > +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> > index 7dd8c34..996003b 100644
> > --- a/drivers/thermal/Kconfig
> > +++ b/drivers/thermal/Kconfig
> > @@ -19,6 +19,17 @@ config THERMAL_HWMON
> >         depends on HWMON=y || HWMON=THERMAL
> >         default y
> >
> > +config CPU_THERMAL
> > +       bool "generic cpu cooling support"
> > +       depends on THERMAL && CPU_FREQ
> > +       help
> > +         This implements the generic cpu cooling mechanism through
> > frequency
> > +         reduction, cpu hotplug and any other ways of reducing
> > temperature. An
> > +         ACPI version of this already
> > exists(drivers/acpi/processor_thermal.c).
> > +         This will be useful for platforms using the generic thermal
> > interface
> > +         and not the ACPI interface.
> > +         If you want this support, you should say Y here.
> > +
> >  config SPEAR_THERMAL
> >         bool "SPEAr thermal sensor driver"
> >         depends on THERMAL
> > diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> > index fd9369a..aae59ad 100644
> > --- a/drivers/thermal/Makefile
> > +++ b/drivers/thermal/Makefile
> > @@ -3,5 +3,6 @@
> >  #
> >
> >  obj-$(CONFIG_THERMAL)          += thermal_sys.o
> > +obj-$(CONFIG_CPU_THERMAL)              += cpu_cooling.o
> >  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
> >  obj-$(CONFIG_RCAR_THERMAL)     += rcar_thermal.o
> > diff --git a/drivers/thermal/cpu_cooling.c
> > b/drivers/thermal/cpu_cooling.c
> > new file mode 100644
> > index 0000000..c42e557
> > --- /dev/null
> > +++ b/drivers/thermal/cpu_cooling.c
> > @@ -0,0 +1,512 @@
> > +/*
> > + *  linux/drivers/thermal/cpu_cooling.c
> > + *
> > + *  Copyright (C) 2012 Samsung Electronics Co.,
> > Ltd(http://www.samsung.com)
> > + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> > + *
> > + *
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + *  This program is free software; you can redistribute it and/or
> > modify
> > + *  it under the terms of the GNU General Public License as published
> > by
> > + *  the Free Software Foundation; version 2 of the License.
> > + *
> > + *  This program is distributed in the hope that it will be useful, but
> > + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> > + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > + *  General Public License for more details.
> > + *
> > + *  You should have received a copy of the GNU General Public License
> > along
> > + *  with this program; if not, write to the Free Software Foundation,
> > Inc.,
> > + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> > + *
> > + *
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + */
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/thermal.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/cpufreq.h>
> > +#include <linux/err.h>
> > +#include <linux/slab.h>
> > +#include <linux/cpu.h>
> > +#include <linux/cpu_cooling.h>
> > +
> > +/**
> > + * struct cpufreq_cooling_device
> > + * @id: unique integer value corresponding to each
> > cpufreq_cooling_device
> > + *     registered.
> > + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> > + *     egistered cooling device.
> > + * @cpufreq_state: integer value representing the current state of
> > cpufreq
> > + *     cooling devices.
> > + * @cpufreq_val: integer value representing the absolute value of the
> > clipped
> > + *     frequency.
> > + * @allowed_cpus: all the cpus involved for this
> > cpufreq_cooling_device.
> > + * @node: list_head to link all cpufreq_cooling_device together.
> > + *
> > + * This structure is required for keeping information of each
> > + * cpufreq_cooling_device registered as a list whose head is
> > represented by
> > + * cooling_cpufreq_list. In order to prevent corruption of this list a
> > + * mutex lock cooling_cpufreq_lock is used.
> > + */
> > +struct cpufreq_cooling_device {
> > +       int id;
> > +       struct thermal_cooling_device *cool_dev;
> > +       unsigned int cpufreq_state;
> > +       unsigned int cpufreq_val;
> > +       struct cpumask allowed_cpus;
> > +       struct list_head node;
> > +};
> > +static LIST_HEAD(cooling_cpufreq_list);
> > +static DEFINE_IDR(cpufreq_idr);
> > +
> > +static struct mutex cooling_cpufreq_lock;
> > +
> > +/* notify_table passes value to the CPUFREQ_ADJUST callback function.
> > */
> > +#define NOTIFY_INVALID NULL
> > +struct cpufreq_cooling_device *notify_device;
> > +
> > +/* Head of the blocking notifier chain to inform about frequency
> > clamping */
> > +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> > +
> > +/**
> > + * get_idr - function to get a unique id.
> > + * @idr: struct idr * handle used to create a id.
> > + * @id: int * value generated by this function.
> > + */
> > +static int get_idr(struct idr *idr, int *id)
> > +{
> > +       int err;
> > +again:
> > +       if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> > +               return -ENOMEM;
> > +
> > +       mutex_lock(&cooling_cpufreq_lock);
> > +       err = idr_get_new(idr, NULL, id);
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +
> > +       if (unlikely(err == -EAGAIN))
> > +               goto again;
> > +       else if (unlikely(err))
> > +               return err;
> > +
> > +       *id = *id & MAX_ID_MASK;
> > +       return 0;
> > +}
> > +
> > +/**
> > + * release_idr - function to free the unique id.
> > + * @idr: struct idr * handle used for creating the id.
> > + * @id: int value representing the unique id.
> > + */
> > +static void release_idr(struct idr *idr, int id)
> > +{
> > +       mutex_lock(&cooling_cpufreq_lock);
> > +       idr_remove(idr, id);
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +}
> > +
> > +/**
> > + * cputherm_register_notifier - Register a notifier with cpu cooling
> > interface.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @list: integer value for which notification is needed. possible
> > values are
> > + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> > + *
> > + * This exported function registers a driver with cpu cooling layer.
> > The driver
> > + * will be notified when any cpu cooling action is called.
> > + */
> > +int cputherm_register_notifier(struct notifier_block *nb, unsigned int
> > list)
> > +{
> > +       int ret = 0;
> > +
> > +       switch (list) {
> > +       case CPUFREQ_COOLING_START:
> > +       case CPUFREQ_COOLING_STOP:
> > +               ret = blocking_notifier_chain_register(
> > +                               &cputherm_state_notifier_list, nb);
> > +               break;
> > +       default:
> > +               ret = -EINVAL;
> > +       }
> > +       return ret;
> > +}
> > +EXPORT_SYMBOL(cputherm_register_notifier);
> > +
> > +/**
> > + * cputherm_unregister_notifier - Un-register a notifier.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @list: integer value for which notification is needed. values
> > possible are
> > + *     CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> > + *
> > + * This exported function un-registers a driver with cpu cooling layer.
> > + */
> > +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned
> > int list)
> > +{
> > +       int ret = 0;
> > +
> > +       switch (list) {
> > +       case CPUFREQ_COOLING_START:
> > +       case CPUFREQ_COOLING_STOP:
> > +               ret = blocking_notifier_chain_unregister(
> > +                               &cputherm_state_notifier_list, nb);
> > +               break;
> > +       default:
> > +               ret = -EINVAL;
> > +       }
> > +       return ret;
> > +}
> > +EXPORT_SYMBOL(cputherm_unregister_notifier);
> > +
> > +/* Below code defines functions to be used for cpufreq as cooling
> > device */
> > +
> > +/**
> > + * is_cpufreq_valid - function to check if a cpu has frequency
> > transition policy.
> > + * @cpu: cpu for which check is needed.
> > + */
> > +static int is_cpufreq_valid(int cpu)
> > +{
> > +       struct cpufreq_policy policy;
> > +       return !cpufreq_get_policy(&policy, 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
> > + */
> > +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long
> > level)
>
>
> I like this. With previous implemention I had to write a similar
> function, but with output of the required freq  clip table. Now that
> you have the freq walk here it simplifies the zone setup code.
>
>
> > +{
> > +       int ret = 0, i = 0;
> > +       unsigned long level_index;
> > +       bool descend = false;
> > +       struct cpufreq_frequency_table *table =
> > +
> > cpufreq_frequency_get_table(cpu);
> > +       if (!table)
> > +               return ret;
> > +
> > +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> > +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> > +                       continue;
> > +
> > +               /*check if table in ascending or descending order*/
> > +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> > +                       (table[i + 1].frequency < table[i].frequency)
> > +                       && !descend) {
> > +                       descend = true;
> > +               }
> > +
> > +               /*return if level matched and table in descending
> > order*/
> > +               if (descend && i == level)
> > +                       return table[i].frequency;
> > +               i++;
> > +       }
> > +       i--;
> > +
> > +       if (level > i || descend)
> > +               return ret;
> > +       level_index = i - level;
> > +
> > +       /*Scan the table in reverse order and match the level*/
> > +       while (i >= 0) {
> > +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> > +                       continue;
> > +               /*return if level matched*/
> > +               if (i == level_index)
> > +                       return table[i].frequency;
> > +               i--;
> > +       }
> > +       return ret;
> > +}
> > +
> > +/**
> > + * cpufreq_apply_cooling - function to apply frequency clipping.
> > + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> > + *     clipping data.
> > + * @cooling_state: value of the cooling state.
> > + */
> > +static int cpufreq_apply_cooling(struct cpufreq_cooling_device
> > *cpufreq_device,
> > +                               unsigned long cooling_state)
> > +{
> > +       unsigned int event, cpuid, clip_freq;
> > +       struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> > +       unsigned int cpu = cpumask_any(maskPtr);
> > +
> > +
> > +       /* Check if the old cooling action is same as new cooling action
> > */
> > +       if (cpufreq_device->cpufreq_state == cooling_state)
> > +               return 0;
> > +
> > +       clip_freq = get_cpu_frequency(cpu, cooling_state);
> > +       if (!clip_freq)
> > +               return -EINVAL;
> > +
> > +       cpufreq_device->cpufreq_state = cooling_state;
> > +       cpufreq_device->cpufreq_val = clip_freq;
> > +       notify_device = cpufreq_device;
> > +
> > +       if (cooling_state != 0)
> > +               event = CPUFREQ_COOLING_START;
> > +       else
> > +               event = CPUFREQ_COOLING_STOP;
> > +
> > +       blocking_notifier_call_chain(&cputherm_state_notifier_list,
> > +                                               event, &clip_freq);
> > +
> > +       for_each_cpu(cpuid, maskPtr) {
> > +               if (is_cpufreq_valid(cpuid))
> > +                       cpufreq_update_policy(cpuid);
> > +       }
> > +
> > +       notify_device = NOTIFY_INVALID;
> > +
> > +       return 0;
> > +}
> > +
> > +/**
> > + * cpufreq_thermal_notifier - notifier callback for cpufreq policy
> > change.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @event: value showing cpufreq event for which this function invoked.
> > + * @data: callback-specific data
> > + */
> > +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> > +                                       unsigned long event, void *data)
> > +{
> > +       struct cpufreq_policy *policy = data;
> > +       unsigned long max_freq = 0;
> > +
> > +       if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> > +               return 0;
> > +
> > +       if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> > +               max_freq = notify_device->cpufreq_val;
> > +
> > +       /* Never exceed user_policy.max*/
> > +       if (max_freq > policy->user_policy.max)
> > +               max_freq = policy->user_policy.max;
> > +
> > +       if (policy->max != max_freq)
> > +               cpufreq_verify_within_limits(policy, 0, max_freq);
> > +
> > +       return 0;
> > +}
> > +
> > +/*
> > + * cpufreq cooling device callback functions are defined below
> > + */
> > +
> > +/**
> > + * cpufreq_get_max_state - callback function to get the max cooling
> > state.
> > + * @cdev: thermal cooling device pointer.
> > + * @state: fill this variable with the max cooling state.
> > + */
> > +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;
> > +       unsigned int cpu;
> > +       struct cpufreq_frequency_table *table;
> > +
> > +       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;
> > +       }
> > +
> > +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> > +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> > +                       continue;
> > +               i++;
> > +       }
> > +       if (i > 0) {
> > +               *state = --i;
> > +               ret = 0;
> > +       }
> > +
> > +return_get_max_state:
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +       return ret;
> > +}
> > +
> > +/**
> > + * cpufreq_get_cur_state - callback function to get the current cooling
> > state.
> > + * @cdev: thermal cooling device pointer.
> > + * @state: fill this variable with the current cooling 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);
> > +
> > +       return ret;
> > +}
> > +
> > +/**
> > + * cpufreq_set_cur_state - callback function to set the current cooling
> > state.
> > + * @cdev: thermal cooling device pointer.
> > + * @state: set this variable to the current cooling state.
> > + */
> > +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);
> > +
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +
> > +       return ret;
> > +}
> > +
> > +/* Bind cpufreq callbacks to thermal cooling device ops */
> > +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> > +       .get_max_state = cpufreq_get_max_state,
> > +       .get_cur_state = cpufreq_get_cur_state,
> > +       .set_cur_state = cpufreq_set_cur_state,
> > +};
> > +
> > +/* Notifier for cpufreq policy change */
> > +static struct notifier_block thermal_cpufreq_notifier_block = {
> > +       .notifier_call = cpufreq_thermal_notifier,
> > +};
> > +
> > +/**
> > + * cpufreq_cooling_register - function to create cpufreq cooling
> > device.
> > + * @clip_cpus: cpumask of cpus where the frequency constraints will
> > happen.
> > + */
> > +struct thermal_cooling_device *cpufreq_cooling_register(
> > +       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;
> > +       char dev_name[THERMAL_NAME_LENGTH];
> > +       int ret = 0, id = 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*/
> > +               if (!cpufreq_get_policy(&policy, i))
> > +                       continue;
> > +               if (min == 0 && max == 0) {
> > +                       min = policy.cpuinfo.min_freq;
> > +                       max = policy.cpuinfo.max_freq;
> > +               } else {
> > +                       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);
> > +       if (!cpufreq_dev)
> > +               return ERR_PTR(-ENOMEM);
> > +
> > +       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);
> > +               return ERR_PTR(-EINVAL);
> > +       }
> > +
> > +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> > +
> > +       cool_dev = thermal_cooling_device_register(dev_name,
> > cpufreq_dev,
> > +                                               &cpufreq_cooling_ops);
> > +       if (!cool_dev) {
> > +               release_idr(&cpufreq_idr, cpufreq_dev->id);
> > +               kfree(cpufreq_dev);
> > +               return ERR_PTR(-EINVAL);
> > +       }
> > +       cpufreq_dev->id = id;
> > +       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);
> > +
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +       return cool_dev;
> > +}
> > +EXPORT_SYMBOL(cpufreq_cooling_register);
> > +
> > +/**
> > + * cpufreq_cooling_unregister - function to remove cpufreq cooling
> > device.
> > + * @cdev: thermal cooling device pointer.
> > + */
> > +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> > +{
> > +       struct cpufreq_cooling_device *cpufreq_dev = NULL;
> > +       unsigned int cpufreq_dev_count = 0;
> > +
> > +       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);
> > +
> > +       /* Unregister the notifier for the last cpufreq cooling device
> > */
> > +       if (cpufreq_dev_count == 1) {
> > +
> > 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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> > new file mode 100644
> > index 0000000..555b917
> > --- /dev/null
> > +++ b/include/linux/cpu_cooling.h
> > @@ -0,0 +1,79 @@
> > +/*
> > + *  linux/include/linux/cpu_cooling.h
> > + *
> > + *  Copyright (C) 2012 Samsung Electronics Co.,
> > Ltd(http://www.samsung.com)
> > + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> > + *
> > + *
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + *  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 __CPU_COOLING_H__
> > +#define __CPU_COOLING_H__
> > +
> > +#include <linux/thermal.h>
> > +
> > +#define CPUFREQ_COOLING_START          0
> > +#define CPUFREQ_COOLING_STOP           1
> > +
> > +/**
> > + * cputherm_register_notifier - Register a notifier with cpu cooling
> > interface.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @list: integer value for which notification is needed. possible
> > values are
> > + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> > + *
> > + * This exported function registers a driver with cpu cooling layer.
> > The driver
> > + * will be notified when any cpu cooling action is called.
> > + */
> > +int cputherm_register_notifier(struct notifier_block *nb, unsigned int
> > list);
> > +
> > +/**
> > + * cputherm_unregister_notifier - Un-register a notifier.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @list: integer value for which notification is needed. values
> > possible are
> > + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> > + *
> > + * This exported function un-registers a driver with cpu cooling layer.
> > + */
> > +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned
> > int list);
> > +
> > +#ifdef CONFIG_CPU_THERMAL
> > +/**
> > + * cpufreq_cooling_register - function to create cpufreq cooling
> > device.
> > + * @clip_cpus: cpumask of cpus where the frequency constraints will
> > happen
> > + */
> > +struct thermal_cooling_device *cpufreq_cooling_register(
> > +               struct cpumask *clip_cpus);
> > +
> > +/**
> > + * cpufreq_cooling_unregister - function to remove cpufreq cooling
> > device.
> > + * @cdev: thermal cooling device pointer.
> > + */
> > +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> > +#else /* !CONFIG_CPU_THERMAL */
> > +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> > +       struct cpumask *clip_cpus)
> > +{
> > +       return NULL;
> > +}
> > +static inline void cpufreq_cooling_unregister(
> > +               struct thermal_cooling_device *cdev)
> > +{
> > +       return;
> > +}
> > +#endif /* CONFIG_CPU_THERMAL */
> > +
> > +#endif /* __CPU_COOLING_H__ */
> > --
> > 1.7.1
> >
>
>
>
> --
>
> Eduardo Valentin

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [lm-sensors] [linux-pm] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17  3:55       ` Amit Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Kachhap @ 2012-08-17  3:55 UTC (permalink / raw)
  To: Valentin, Eduardo
  Cc: linux-pm, Andrew Morton, Zhang Rui, Len Brown, linux-samsung-soc,
	SangWook Ju, linux-kernel, lm-sensors, linux-acpi, Kukjin Kim,
	Jean Delvare, Kyungmin Park, Guenter Roeck

On 16 August 2012 20:22, Valentin, Eduardo <eduardo.valentin@ti.com> wrote:
>
> Amit,
>
> Thanks again for keeping this up.
>
> On Thu, Aug 16, 2012 at 2:41 PM, Amit Daniel Kachhap
> <amit.kachhap@linaro.org> wrote:
> > This patchset introduces a new generic cooling device based on cpufreq
> > that can be used on non-ACPI platforms.  As a proof of concept, we have
> > drivers for the following platforms using this mechanism now:
> >
> >  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
> >  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git
> > imx6q_thermal)
>
> FYI, the OMAP code is under drivers/staging/omap-thermal/. The file
> omap-thermal-common.c is the one which is using your generic cooling
> device. But it needs to be updated accordingly to the API change you
> mention.
>
> >
> > There is a small change in cpufreq cooling registration APIs, so a minor
> > change is needed for Freescale platforms.
> >
> > Brief Description:
> >
> > 1) The generic cooling devices code is placed inside driver/thermal/*
> >    as placing inside acpi folder will need un-necessary enabling of acpi
> >    code.  This code is architecture independent.
> >
> > 2) This patchset adds generic cpu cooling low level implementation
> >    through frequency clipping.  In future, other cpu related cooling
> >    devices may be added here.  An ACPI version of this already exists
> >    (drivers/acpi/processor_thermal.c) .But this will be useful for
> >    platforms like ARM using the generic thermal interface along with the
> >    generic cpu cooling devices.  The cooling device registration API's
> >    return cooling device pointers which can be easily binded with the
> >    thermal zone trip points.  The important APIs exposed are,
> >
> >    a) struct thermal_cooling_device *cpufreq_cooling_register(
> >         struct cpumask *clip_cpus)
> >    b) void cpufreq_cooling_unregister(struct thermal_cooling_device
> > *cdev)
> >
> > 3) Samsung exynos platform thermal implementation is done using the
> >    generic cpu cooling APIs and the new trip type.  The temperature
> > sensor
> >    driver present in the hwmon folder(registered as hwmon driver) is
> > moved
> >    to thermal folder and registered as a thermal driver.
> >
> > A simple data/control flow diagrams is shown below,
> >
> > Core Linux thermal <----->  Exynos thermal interface <----- Temperature
> > Sensor
> >           |                             |
> >          \|/                            |
> >   Cpufreq cooling device <---------------
> >
> > TODO:
> > *Will send the DT enablement patches later after the driver is merged.
> >
> > This patch:
> >
> > Add support for generic cpu thermal cooling low level implementations
> > using frequency scaling up/down based on the registration parameters.
> > Different cpu related cooling devices can be registered by the user and
> > the binding of these cooling devices to the corresponding trip points
> > can
> > be easily done as the registration APIs return the cooling device
> > pointer.
> > The user of these APIs are responsible for passing clipping frequency .
> > The drivers can also register to recieve notification about any cooling
> > action called.
> >
> > [akpm@linux-foundation.org: fix comment layout]
> > Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> > Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> > Cc: SangWook Ju <sw.ju@samsung.com>
> > Cc: Durgadoss <durgadoss.r@intel.com>
> > Cc: Len Brown <lenb@kernel.org>
> > Cc: Jean Delvare <khali@linux-fr.org>
> > Cc: Kyungmin Park <kmpark@infradead.org>
> > Cc: Kukjin Kim <kgene.kim@samsung.com>
> > Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> > Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
>
>
> Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>

Thanks Eduardo .

>
>
> > ---
> >  Documentation/thermal/cpu-cooling-api.txt |   52 +++
> >  drivers/thermal/Kconfig                   |   11 +
> >  drivers/thermal/Makefile                  |    1 +
> >  drivers/thermal/cpu_cooling.c             |  512
> > +++++++++++++++++++++++++++++
> >  include/linux/cpu_cooling.h               |   79 +++++
> >  5 files changed, 655 insertions(+), 0 deletions(-)
> >  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
> >  create mode 100644 drivers/thermal/cpu_cooling.c
> >  create mode 100644 include/linux/cpu_cooling.h
> >
> > diff --git a/Documentation/thermal/cpu-cooling-api.txt
> > b/Documentation/thermal/cpu-cooling-api.txt
> > new file mode 100644
> > index 0000000..a1f2a6b
> > --- /dev/null
> > +++ b/Documentation/thermal/cpu-cooling-api.txt
> > @@ -0,0 +1,52 @@
> > +CPU cooling APIs How To
> > +=================> > +
> > +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> > +
> > +Updated: 12 May 2012
> > +
> > +Copyright (c)  2012 Samsung Electronics Co.,
> > Ltd(http://www.samsung.com)
> > +
> > +0. Introduction
> > +
> > +The generic cpu cooling(freq clipping) provides
> > registration/unregistration APIs
> > +to the caller. The binding of the cooling devices to the trip point is
> > left for
> > +the user. The registration APIs returns the cooling device pointer.
> > +
> > +1. cpu cooling APIs
> > +
> > +1.1 cpufreq registration/unregistration APIs
> > +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> > +       struct cpumask *clip_cpus)
> > +
> > +    This interface function registers the cpufreq cooling device with
> > the name
> > +    "thermal-cpufreq-%x". This api can support multiple instances of
> > cpufreq
> > +    cooling devices.
> > +
> > +   clip_cpus: cpumask of cpus where the frequency constraints will
> > happen.
> > +
> > +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device
> > *cdev)
> > +
> > +    This interface function unregisters the "thermal-cpufreq-%x"
> > cooling device.
> > +
> > +    cdev: Cooling device pointer which has to be unregistered.
> > +
> > +
> > +1.2 CPU cooling action notifier register/unregister interface
> > +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> > +       unsigned int list)
> > +
> > +    This interface registers a driver with cpu cooling layer. The
> > driver will
> > +    be notified when any cpu cooling action is called.
> > +
> > +    nb: notifier function to register
> > +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> > +
> > +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> > +       unsigned int list)
> > +
> > +    This interface registers a driver with cpu cooling layer. The
> > driver will
> > +    be notified when any cpu cooling action is called.
> > +
> > +    nb: notifier function to register
> > +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> > index 7dd8c34..996003b 100644
> > --- a/drivers/thermal/Kconfig
> > +++ b/drivers/thermal/Kconfig
> > @@ -19,6 +19,17 @@ config THERMAL_HWMON
> >         depends on HWMON=y || HWMON=THERMAL
> >         default y
> >
> > +config CPU_THERMAL
> > +       bool "generic cpu cooling support"
> > +       depends on THERMAL && CPU_FREQ
> > +       help
> > +         This implements the generic cpu cooling mechanism through
> > frequency
> > +         reduction, cpu hotplug and any other ways of reducing
> > temperature. An
> > +         ACPI version of this already
> > exists(drivers/acpi/processor_thermal.c).
> > +         This will be useful for platforms using the generic thermal
> > interface
> > +         and not the ACPI interface.
> > +         If you want this support, you should say Y here.
> > +
> >  config SPEAR_THERMAL
> >         bool "SPEAr thermal sensor driver"
> >         depends on THERMAL
> > diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> > index fd9369a..aae59ad 100644
> > --- a/drivers/thermal/Makefile
> > +++ b/drivers/thermal/Makefile
> > @@ -3,5 +3,6 @@
> >  #
> >
> >  obj-$(CONFIG_THERMAL)          += thermal_sys.o
> > +obj-$(CONFIG_CPU_THERMAL)              += cpu_cooling.o
> >  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
> >  obj-$(CONFIG_RCAR_THERMAL)     += rcar_thermal.o
> > diff --git a/drivers/thermal/cpu_cooling.c
> > b/drivers/thermal/cpu_cooling.c
> > new file mode 100644
> > index 0000000..c42e557
> > --- /dev/null
> > +++ b/drivers/thermal/cpu_cooling.c
> > @@ -0,0 +1,512 @@
> > +/*
> > + *  linux/drivers/thermal/cpu_cooling.c
> > + *
> > + *  Copyright (C) 2012 Samsung Electronics Co.,
> > Ltd(http://www.samsung.com)
> > + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> > + *
> > + *
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + *  This program is free software; you can redistribute it and/or
> > modify
> > + *  it under the terms of the GNU General Public License as published
> > by
> > + *  the Free Software Foundation; version 2 of the License.
> > + *
> > + *  This program is distributed in the hope that it will be useful, but
> > + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> > + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > + *  General Public License for more details.
> > + *
> > + *  You should have received a copy of the GNU General Public License
> > along
> > + *  with this program; if not, write to the Free Software Foundation,
> > Inc.,
> > + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> > + *
> > + *
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + */
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/thermal.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/cpufreq.h>
> > +#include <linux/err.h>
> > +#include <linux/slab.h>
> > +#include <linux/cpu.h>
> > +#include <linux/cpu_cooling.h>
> > +
> > +/**
> > + * struct cpufreq_cooling_device
> > + * @id: unique integer value corresponding to each
> > cpufreq_cooling_device
> > + *     registered.
> > + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> > + *     egistered cooling device.
> > + * @cpufreq_state: integer value representing the current state of
> > cpufreq
> > + *     cooling devices.
> > + * @cpufreq_val: integer value representing the absolute value of the
> > clipped
> > + *     frequency.
> > + * @allowed_cpus: all the cpus involved for this
> > cpufreq_cooling_device.
> > + * @node: list_head to link all cpufreq_cooling_device together.
> > + *
> > + * This structure is required for keeping information of each
> > + * cpufreq_cooling_device registered as a list whose head is
> > represented by
> > + * cooling_cpufreq_list. In order to prevent corruption of this list a
> > + * mutex lock cooling_cpufreq_lock is used.
> > + */
> > +struct cpufreq_cooling_device {
> > +       int id;
> > +       struct thermal_cooling_device *cool_dev;
> > +       unsigned int cpufreq_state;
> > +       unsigned int cpufreq_val;
> > +       struct cpumask allowed_cpus;
> > +       struct list_head node;
> > +};
> > +static LIST_HEAD(cooling_cpufreq_list);
> > +static DEFINE_IDR(cpufreq_idr);
> > +
> > +static struct mutex cooling_cpufreq_lock;
> > +
> > +/* notify_table passes value to the CPUFREQ_ADJUST callback function.
> > */
> > +#define NOTIFY_INVALID NULL
> > +struct cpufreq_cooling_device *notify_device;
> > +
> > +/* Head of the blocking notifier chain to inform about frequency
> > clamping */
> > +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> > +
> > +/**
> > + * get_idr - function to get a unique id.
> > + * @idr: struct idr * handle used to create a id.
> > + * @id: int * value generated by this function.
> > + */
> > +static int get_idr(struct idr *idr, int *id)
> > +{
> > +       int err;
> > +again:
> > +       if (unlikely(idr_pre_get(idr, GFP_KERNEL) = 0))
> > +               return -ENOMEM;
> > +
> > +       mutex_lock(&cooling_cpufreq_lock);
> > +       err = idr_get_new(idr, NULL, id);
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +
> > +       if (unlikely(err = -EAGAIN))
> > +               goto again;
> > +       else if (unlikely(err))
> > +               return err;
> > +
> > +       *id = *id & MAX_ID_MASK;
> > +       return 0;
> > +}
> > +
> > +/**
> > + * release_idr - function to free the unique id.
> > + * @idr: struct idr * handle used for creating the id.
> > + * @id: int value representing the unique id.
> > + */
> > +static void release_idr(struct idr *idr, int id)
> > +{
> > +       mutex_lock(&cooling_cpufreq_lock);
> > +       idr_remove(idr, id);
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +}
> > +
> > +/**
> > + * cputherm_register_notifier - Register a notifier with cpu cooling
> > interface.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @list: integer value for which notification is needed. possible
> > values are
> > + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> > + *
> > + * This exported function registers a driver with cpu cooling layer.
> > The driver
> > + * will be notified when any cpu cooling action is called.
> > + */
> > +int cputherm_register_notifier(struct notifier_block *nb, unsigned int
> > list)
> > +{
> > +       int ret = 0;
> > +
> > +       switch (list) {
> > +       case CPUFREQ_COOLING_START:
> > +       case CPUFREQ_COOLING_STOP:
> > +               ret = blocking_notifier_chain_register(
> > +                               &cputherm_state_notifier_list, nb);
> > +               break;
> > +       default:
> > +               ret = -EINVAL;
> > +       }
> > +       return ret;
> > +}
> > +EXPORT_SYMBOL(cputherm_register_notifier);
> > +
> > +/**
> > + * cputherm_unregister_notifier - Un-register a notifier.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @list: integer value for which notification is needed. values
> > possible are
> > + *     CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> > + *
> > + * This exported function un-registers a driver with cpu cooling layer.
> > + */
> > +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned
> > int list)
> > +{
> > +       int ret = 0;
> > +
> > +       switch (list) {
> > +       case CPUFREQ_COOLING_START:
> > +       case CPUFREQ_COOLING_STOP:
> > +               ret = blocking_notifier_chain_unregister(
> > +                               &cputherm_state_notifier_list, nb);
> > +               break;
> > +       default:
> > +               ret = -EINVAL;
> > +       }
> > +       return ret;
> > +}
> > +EXPORT_SYMBOL(cputherm_unregister_notifier);
> > +
> > +/* Below code defines functions to be used for cpufreq as cooling
> > device */
> > +
> > +/**
> > + * is_cpufreq_valid - function to check if a cpu has frequency
> > transition policy.
> > + * @cpu: cpu for which check is needed.
> > + */
> > +static int is_cpufreq_valid(int cpu)
> > +{
> > +       struct cpufreq_policy policy;
> > +       return !cpufreq_get_policy(&policy, 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
> > + */
> > +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long
> > level)
>
>
> I like this. With previous implemention I had to write a similar
> function, but with output of the required freq  clip table. Now that
> you have the freq walk here it simplifies the zone setup code.
>
>
> > +{
> > +       int ret = 0, i = 0;
> > +       unsigned long level_index;
> > +       bool descend = false;
> > +       struct cpufreq_frequency_table *table > > +
> > cpufreq_frequency_get_table(cpu);
> > +       if (!table)
> > +               return ret;
> > +
> > +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> > +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> > +                       continue;
> > +
> > +               /*check if table in ascending or descending order*/
> > +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> > +                       (table[i + 1].frequency < table[i].frequency)
> > +                       && !descend) {
> > +                       descend = true;
> > +               }
> > +
> > +               /*return if level matched and table in descending
> > order*/
> > +               if (descend && i = level)
> > +                       return table[i].frequency;
> > +               i++;
> > +       }
> > +       i--;
> > +
> > +       if (level > i || descend)
> > +               return ret;
> > +       level_index = i - level;
> > +
> > +       /*Scan the table in reverse order and match the level*/
> > +       while (i >= 0) {
> > +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> > +                       continue;
> > +               /*return if level matched*/
> > +               if (i = level_index)
> > +                       return table[i].frequency;
> > +               i--;
> > +       }
> > +       return ret;
> > +}
> > +
> > +/**
> > + * cpufreq_apply_cooling - function to apply frequency clipping.
> > + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> > + *     clipping data.
> > + * @cooling_state: value of the cooling state.
> > + */
> > +static int cpufreq_apply_cooling(struct cpufreq_cooling_device
> > *cpufreq_device,
> > +                               unsigned long cooling_state)
> > +{
> > +       unsigned int event, cpuid, clip_freq;
> > +       struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> > +       unsigned int cpu = cpumask_any(maskPtr);
> > +
> > +
> > +       /* Check if the old cooling action is same as new cooling action
> > */
> > +       if (cpufreq_device->cpufreq_state = cooling_state)
> > +               return 0;
> > +
> > +       clip_freq = get_cpu_frequency(cpu, cooling_state);
> > +       if (!clip_freq)
> > +               return -EINVAL;
> > +
> > +       cpufreq_device->cpufreq_state = cooling_state;
> > +       cpufreq_device->cpufreq_val = clip_freq;
> > +       notify_device = cpufreq_device;
> > +
> > +       if (cooling_state != 0)
> > +               event = CPUFREQ_COOLING_START;
> > +       else
> > +               event = CPUFREQ_COOLING_STOP;
> > +
> > +       blocking_notifier_call_chain(&cputherm_state_notifier_list,
> > +                                               event, &clip_freq);
> > +
> > +       for_each_cpu(cpuid, maskPtr) {
> > +               if (is_cpufreq_valid(cpuid))
> > +                       cpufreq_update_policy(cpuid);
> > +       }
> > +
> > +       notify_device = NOTIFY_INVALID;
> > +
> > +       return 0;
> > +}
> > +
> > +/**
> > + * cpufreq_thermal_notifier - notifier callback for cpufreq policy
> > change.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @event: value showing cpufreq event for which this function invoked.
> > + * @data: callback-specific data
> > + */
> > +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> > +                                       unsigned long event, void *data)
> > +{
> > +       struct cpufreq_policy *policy = data;
> > +       unsigned long max_freq = 0;
> > +
> > +       if (event != CPUFREQ_ADJUST || notify_device = NOTIFY_INVALID)
> > +               return 0;
> > +
> > +       if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> > +               max_freq = notify_device->cpufreq_val;
> > +
> > +       /* Never exceed user_policy.max*/
> > +       if (max_freq > policy->user_policy.max)
> > +               max_freq = policy->user_policy.max;
> > +
> > +       if (policy->max != max_freq)
> > +               cpufreq_verify_within_limits(policy, 0, max_freq);
> > +
> > +       return 0;
> > +}
> > +
> > +/*
> > + * cpufreq cooling device callback functions are defined below
> > + */
> > +
> > +/**
> > + * cpufreq_get_max_state - callback function to get the max cooling
> > state.
> > + * @cdev: thermal cooling device pointer.
> > + * @state: fill this variable with the max cooling state.
> > + */
> > +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;
> > +       unsigned int cpu;
> > +       struct cpufreq_frequency_table *table;
> > +
> > +       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;
> > +       }
> > +
> > +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> > +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> > +                       continue;
> > +               i++;
> > +       }
> > +       if (i > 0) {
> > +               *state = --i;
> > +               ret = 0;
> > +       }
> > +
> > +return_get_max_state:
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +       return ret;
> > +}
> > +
> > +/**
> > + * cpufreq_get_cur_state - callback function to get the current cooling
> > state.
> > + * @cdev: thermal cooling device pointer.
> > + * @state: fill this variable with the current cooling 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);
> > +
> > +       return ret;
> > +}
> > +
> > +/**
> > + * cpufreq_set_cur_state - callback function to set the current cooling
> > state.
> > + * @cdev: thermal cooling device pointer.
> > + * @state: set this variable to the current cooling state.
> > + */
> > +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);
> > +
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +
> > +       return ret;
> > +}
> > +
> > +/* Bind cpufreq callbacks to thermal cooling device ops */
> > +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> > +       .get_max_state = cpufreq_get_max_state,
> > +       .get_cur_state = cpufreq_get_cur_state,
> > +       .set_cur_state = cpufreq_set_cur_state,
> > +};
> > +
> > +/* Notifier for cpufreq policy change */
> > +static struct notifier_block thermal_cpufreq_notifier_block = {
> > +       .notifier_call = cpufreq_thermal_notifier,
> > +};
> > +
> > +/**
> > + * cpufreq_cooling_register - function to create cpufreq cooling
> > device.
> > + * @clip_cpus: cpumask of cpus where the frequency constraints will
> > happen.
> > + */
> > +struct thermal_cooling_device *cpufreq_cooling_register(
> > +       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;
> > +       char dev_name[THERMAL_NAME_LENGTH];
> > +       int ret = 0, id = 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*/
> > +               if (!cpufreq_get_policy(&policy, i))
> > +                       continue;
> > +               if (min = 0 && max = 0) {
> > +                       min = policy.cpuinfo.min_freq;
> > +                       max = policy.cpuinfo.max_freq;
> > +               } else {
> > +                       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);
> > +       if (!cpufreq_dev)
> > +               return ERR_PTR(-ENOMEM);
> > +
> > +       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);
> > +               return ERR_PTR(-EINVAL);
> > +       }
> > +
> > +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> > +
> > +       cool_dev = thermal_cooling_device_register(dev_name,
> > cpufreq_dev,
> > +                                               &cpufreq_cooling_ops);
> > +       if (!cool_dev) {
> > +               release_idr(&cpufreq_idr, cpufreq_dev->id);
> > +               kfree(cpufreq_dev);
> > +               return ERR_PTR(-EINVAL);
> > +       }
> > +       cpufreq_dev->id = id;
> > +       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);
> > +
> > +       mutex_unlock(&cooling_cpufreq_lock);
> > +       return cool_dev;
> > +}
> > +EXPORT_SYMBOL(cpufreq_cooling_register);
> > +
> > +/**
> > + * cpufreq_cooling_unregister - function to remove cpufreq cooling
> > device.
> > + * @cdev: thermal cooling device pointer.
> > + */
> > +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> > +{
> > +       struct cpufreq_cooling_device *cpufreq_dev = NULL;
> > +       unsigned int cpufreq_dev_count = 0;
> > +
> > +       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);
> > +
> > +       /* Unregister the notifier for the last cpufreq cooling device
> > */
> > +       if (cpufreq_dev_count = 1) {
> > +
> > 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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> > new file mode 100644
> > index 0000000..555b917
> > --- /dev/null
> > +++ b/include/linux/cpu_cooling.h
> > @@ -0,0 +1,79 @@
> > +/*
> > + *  linux/include/linux/cpu_cooling.h
> > + *
> > + *  Copyright (C) 2012 Samsung Electronics Co.,
> > Ltd(http://www.samsung.com)
> > + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> > + *
> > + *
> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + *  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 __CPU_COOLING_H__
> > +#define __CPU_COOLING_H__
> > +
> > +#include <linux/thermal.h>
> > +
> > +#define CPUFREQ_COOLING_START          0
> > +#define CPUFREQ_COOLING_STOP           1
> > +
> > +/**
> > + * cputherm_register_notifier - Register a notifier with cpu cooling
> > interface.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @list: integer value for which notification is needed. possible
> > values are
> > + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> > + *
> > + * This exported function registers a driver with cpu cooling layer.
> > The driver
> > + * will be notified when any cpu cooling action is called.
> > + */
> > +int cputherm_register_notifier(struct notifier_block *nb, unsigned int
> > list);
> > +
> > +/**
> > + * cputherm_unregister_notifier - Un-register a notifier.
> > + * @nb:        struct notifier_block * with callback info.
> > + * @list: integer value for which notification is needed. values
> > possible are
> > + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> > + *
> > + * This exported function un-registers a driver with cpu cooling layer.
> > + */
> > +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned
> > int list);
> > +
> > +#ifdef CONFIG_CPU_THERMAL
> > +/**
> > + * cpufreq_cooling_register - function to create cpufreq cooling
> > device.
> > + * @clip_cpus: cpumask of cpus where the frequency constraints will
> > happen
> > + */
> > +struct thermal_cooling_device *cpufreq_cooling_register(
> > +               struct cpumask *clip_cpus);
> > +
> > +/**
> > + * cpufreq_cooling_unregister - function to remove cpufreq cooling
> > device.
> > + * @cdev: thermal cooling device pointer.
> > + */
> > +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> > +#else /* !CONFIG_CPU_THERMAL */
> > +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> > +       struct cpumask *clip_cpus)
> > +{
> > +       return NULL;
> > +}
> > +static inline void cpufreq_cooling_unregister(
> > +               struct thermal_cooling_device *cdev)
> > +{
> > +       return;
> > +}
> > +#endif /* CONFIG_CPU_THERMAL */
> > +
> > +#endif /* __CPU_COOLING_H__ */
> > --
> > 1.7.1
> >
>
>
>
> --
>
> Eduardo Valentin

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 5/6] ARM: exynos: add thermal sensor driver platform data support
  2012-08-16 11:41   ` Amit Daniel Kachhap
@ 2012-08-17  4:07     ` Thomas Abraham
  -1 siblings, 0 replies; 50+ messages in thread
From: Thomas Abraham @ 2012-08-17  3:55 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: linux-pm, Andrew Morton, Zhang Rui, Len Brown, linux-samsung-soc,
	linux-kernel, linux-acpi, lm-sensors, SangWook Ju, Durgadoss,
	Jean Delvare, jonghwa lee, Kyungmin Park, Kukjin Kim

On 16 August 2012 17:11, Amit Daniel Kachhap <amit.kachhap@linaro.org> wrote:
> Add necessary default platform data support needed for TMU driver.  The
> supplied dt/non-dt values are tested for origen exynos4210 and smdk exynos5250
> platforms and only compile tested for exynos4412.
>
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: jonghwa lee <jonghwa3.lee@samsung.com>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
> ---
>  drivers/thermal/exynos_thermal.c |  111 +++++++++++++++++++++++++++++++++++++-
>  1 files changed, 110 insertions(+), 1 deletions(-)

Reviewed-by: Thomas Abraham <thomas.abraham@linaro.org>

Few minor comments inline below.

>
> diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
> index e79cdc9..03a99e4 100644
> --- a/drivers/thermal/exynos_thermal.c
> +++ b/drivers/thermal/exynos_thermal.c
> @@ -723,14 +723,121 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id)
>  static struct thermal_sensor_conf exynos_sensor_conf = {
>         .name                   = "exynos-therm",
>         .read_temperature       = (int (*)(void *))exynos_tmu_read,
> +};
> +
> +#if defined(CONFIG_CPU_EXYNOS4210)
> +static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = {
> +       .threshold = 80,
> +       .trigger_levels[0] = 5,
> +       .trigger_levels[1] = 20,
> +       .trigger_levels[2] = 30,
> +       .trigger_level0_en = 1,
> +       .trigger_level1_en = 1,
> +       .trigger_level2_en = 1,
> +       .trigger_level3_en = 0,
> +       .gain = 15,
> +       .reference_voltage = 7,
> +       .cal_type = TYPE_ONE_POINT_TRIMMING,
> +       .freq_tab[0] = {
> +               .freq_clip_max = 800 * 1000,
> +               .temp_level = 85,
> +       },
> +       .freq_tab[1] = {
> +               .freq_clip_max = 200 * 1000,
> +               .temp_level = 100,
> +       },
> +       .freq_tab_count = 2,
> +       .type = SOC_ARCH_EXYNOS4210,
> +};
> +#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data)
> +#else
> +#define EXYNOS4210_TMU_DRV_DATA (NULL)
> +#endif
> +
> +#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412)
> +static struct exynos_tmu_platform_data const exynos_default_tmu_data = {
> +       .trigger_levels[0] = 85,
> +       .trigger_levels[1] = 103,
> +       .trigger_levels[2] = 110,
> +       .trigger_level0_en = 1,
> +       .trigger_level1_en = 1,
> +       .trigger_level2_en = 1,
> +       .trigger_level3_en = 0,
> +       .gain = 8,
> +       .reference_voltage = 16,
> +       .noise_cancel_mode = 4,
> +       .cal_type = TYPE_ONE_POINT_TRIMMING,
> +       .efuse_value = 55,
> +       .freq_tab[0] = {
> +               .freq_clip_max = 800 * 1000,
> +               .temp_level = 85,
> +       },
> +       .freq_tab[1] = {
> +               .freq_clip_max = 200 * 1000,
> +               .temp_level = 103,
> +       },
> +       .freq_tab_count = 2,
> +       .type = SOC_ARCH_EXYNOS,
> +};
> +#define EXYNOS_TMU_DRV_DATA (&exynos_default_tmu_data)
> +#else
> +#define EXYNOS_TMU_DRV_DATA (NULL)
> +#endif
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id exynos_tmu_match[] = {
> +       {
> +               .compatible = "samsung,exynos4210-tmu",
> +               .data = (void *)EXYNOS4210_TMU_DRV_DATA,
> +       },
> +       {

This can be  }, {

> +               .compatible = "samsung,exynos5250-tmu",
> +               .data = (void *)EXYNOS_TMU_DRV_DATA,
> +       },
> +       {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos_tmu_match);
> +#else
> +#define  exynos_tmu_match NULL
> +#endif
> +
> +static struct platform_device_id exynos_tmu_driver_ids[] = {
> +       {
> +               .name           = "exynos4210-tmu",
> +               .driver_data    = (kernel_ulong_t)EXYNOS4210_TMU_DRV_DATA,
> +       },
> +       {
> +               .name           = "exynos5250-tmu",
> +               .driver_data    = (kernel_ulong_t)EXYNOS_TMU_DRV_DATA,
> +       },

Since Exynos5250 platforms are dt based, the above entry could be left out.

> +       { },
> +};
> +MODULE_DEVICE_TABLE(platform, exynos4_tmu_driver_ids);
> +
> +static inline struct  exynos_tmu_platform_data *exynos_get_driver_data(
> +                       struct platform_device *pdev)
> +{
> +#ifdef CONFIG_OF
> +       if (pdev->dev.of_node) {
> +               const struct of_device_id *match;
> +               match = of_match_node(exynos_tmu_match, pdev->dev.of_node);
> +               if (!match)
> +                       return NULL;
> +               return (struct exynos_tmu_platform_data *) match->data;
> +       }
> +#endif
> +       return (struct exynos_tmu_platform_data *)
> +                       platform_get_device_id(pdev)->driver_data;
>  }
> -;
>  static int __devinit exynos_tmu_probe(struct platform_device *pdev)
>  {
>         struct exynos_tmu_data *data;
>         struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
>         int ret, i;
>
> +       if (!pdata)
> +               pdata = exynos_get_driver_data(pdev);
> +
>         if (!pdata) {
>                 dev_err(&pdev->dev, "No platform init data supplied.\n");
>                 return -ENODEV;
> @@ -899,9 +1006,11 @@ static struct platform_driver exynos_tmu_driver = {
>                 .name   = "exynos-tmu",
>                 .owner  = THIS_MODULE,
>                 .pm     = EXYNOS_TMU_PM,
> +               .of_match_table = exynos_tmu_match,
>         },
>         .probe = exynos_tmu_probe,
>         .remove = __devexit_p(exynos_tmu_remove),
> +       .id_table = exynos_tmu_driver_ids,
>  };
>
>  module_platform_driver(exynos_tmu_driver);
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [lm-sensors] [PATCH v6 5/6] ARM: exynos: add thermal sensor driver platform data support
@ 2012-08-17  4:07     ` Thomas Abraham
  0 siblings, 0 replies; 50+ messages in thread
From: Thomas Abraham @ 2012-08-17  4:07 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: linux-pm, Andrew Morton, Zhang Rui, Len Brown, linux-samsung-soc,
	linux-kernel, linux-acpi, lm-sensors, SangWook Ju, Durgadoss,
	Jean Delvare, jonghwa lee, Kyungmin Park, Kukjin Kim

On 16 August 2012 17:11, Amit Daniel Kachhap <amit.kachhap@linaro.org> wrote:
> Add necessary default platform data support needed for TMU driver.  The
> supplied dt/non-dt values are tested for origen exynos4210 and smdk exynos5250
> platforms and only compile tested for exynos4412.
>
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: jonghwa lee <jonghwa3.lee@samsung.com>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
> ---
>  drivers/thermal/exynos_thermal.c |  111 +++++++++++++++++++++++++++++++++++++-
>  1 files changed, 110 insertions(+), 1 deletions(-)

Reviewed-by: Thomas Abraham <thomas.abraham@linaro.org>

Few minor comments inline below.

>
> diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
> index e79cdc9..03a99e4 100644
> --- a/drivers/thermal/exynos_thermal.c
> +++ b/drivers/thermal/exynos_thermal.c
> @@ -723,14 +723,121 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id)
>  static struct thermal_sensor_conf exynos_sensor_conf = {
>         .name                   = "exynos-therm",
>         .read_temperature       = (int (*)(void *))exynos_tmu_read,
> +};
> +
> +#if defined(CONFIG_CPU_EXYNOS4210)
> +static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = {
> +       .threshold = 80,
> +       .trigger_levels[0] = 5,
> +       .trigger_levels[1] = 20,
> +       .trigger_levels[2] = 30,
> +       .trigger_level0_en = 1,
> +       .trigger_level1_en = 1,
> +       .trigger_level2_en = 1,
> +       .trigger_level3_en = 0,
> +       .gain = 15,
> +       .reference_voltage = 7,
> +       .cal_type = TYPE_ONE_POINT_TRIMMING,
> +       .freq_tab[0] = {
> +               .freq_clip_max = 800 * 1000,
> +               .temp_level = 85,
> +       },
> +       .freq_tab[1] = {
> +               .freq_clip_max = 200 * 1000,
> +               .temp_level = 100,
> +       },
> +       .freq_tab_count = 2,
> +       .type = SOC_ARCH_EXYNOS4210,
> +};
> +#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data)
> +#else
> +#define EXYNOS4210_TMU_DRV_DATA (NULL)
> +#endif
> +
> +#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412)
> +static struct exynos_tmu_platform_data const exynos_default_tmu_data = {
> +       .trigger_levels[0] = 85,
> +       .trigger_levels[1] = 103,
> +       .trigger_levels[2] = 110,
> +       .trigger_level0_en = 1,
> +       .trigger_level1_en = 1,
> +       .trigger_level2_en = 1,
> +       .trigger_level3_en = 0,
> +       .gain = 8,
> +       .reference_voltage = 16,
> +       .noise_cancel_mode = 4,
> +       .cal_type = TYPE_ONE_POINT_TRIMMING,
> +       .efuse_value = 55,
> +       .freq_tab[0] = {
> +               .freq_clip_max = 800 * 1000,
> +               .temp_level = 85,
> +       },
> +       .freq_tab[1] = {
> +               .freq_clip_max = 200 * 1000,
> +               .temp_level = 103,
> +       },
> +       .freq_tab_count = 2,
> +       .type = SOC_ARCH_EXYNOS,
> +};
> +#define EXYNOS_TMU_DRV_DATA (&exynos_default_tmu_data)
> +#else
> +#define EXYNOS_TMU_DRV_DATA (NULL)
> +#endif
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id exynos_tmu_match[] = {
> +       {
> +               .compatible = "samsung,exynos4210-tmu",
> +               .data = (void *)EXYNOS4210_TMU_DRV_DATA,
> +       },
> +       {

This can be  }, {

> +               .compatible = "samsung,exynos5250-tmu",
> +               .data = (void *)EXYNOS_TMU_DRV_DATA,
> +       },
> +       {},
> +};
> +MODULE_DEVICE_TABLE(of, exynos_tmu_match);
> +#else
> +#define  exynos_tmu_match NULL
> +#endif
> +
> +static struct platform_device_id exynos_tmu_driver_ids[] = {
> +       {
> +               .name           = "exynos4210-tmu",
> +               .driver_data    = (kernel_ulong_t)EXYNOS4210_TMU_DRV_DATA,
> +       },
> +       {
> +               .name           = "exynos5250-tmu",
> +               .driver_data    = (kernel_ulong_t)EXYNOS_TMU_DRV_DATA,
> +       },

Since Exynos5250 platforms are dt based, the above entry could be left out.

> +       { },
> +};
> +MODULE_DEVICE_TABLE(platform, exynos4_tmu_driver_ids);
> +
> +static inline struct  exynos_tmu_platform_data *exynos_get_driver_data(
> +                       struct platform_device *pdev)
> +{
> +#ifdef CONFIG_OF
> +       if (pdev->dev.of_node) {
> +               const struct of_device_id *match;
> +               match = of_match_node(exynos_tmu_match, pdev->dev.of_node);
> +               if (!match)
> +                       return NULL;
> +               return (struct exynos_tmu_platform_data *) match->data;
> +       }
> +#endif
> +       return (struct exynos_tmu_platform_data *)
> +                       platform_get_device_id(pdev)->driver_data;
>  }
> -;
>  static int __devinit exynos_tmu_probe(struct platform_device *pdev)
>  {
>         struct exynos_tmu_data *data;
>         struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
>         int ret, i;
>
> +       if (!pdata)
> +               pdata = exynos_get_driver_data(pdev);
> +
>         if (!pdata) {
>                 dev_err(&pdev->dev, "No platform init data supplied.\n");
>                 return -ENODEV;
> @@ -899,9 +1006,11 @@ static struct platform_driver exynos_tmu_driver = {
>                 .name   = "exynos-tmu",
>                 .owner  = THIS_MODULE,
>                 .pm     = EXYNOS_TMU_PM,
> +               .of_match_table = exynos_tmu_match,
>         },
>         .probe = exynos_tmu_probe,
>         .remove = __devexit_p(exynos_tmu_remove),
> +       .id_table = exynos_tmu_driver_ids,
>  };
>
>  module_platform_driver(exynos_tmu_driver);
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-16 11:41   ` Amit Daniel Kachhap
  (?)
@ 2012-08-17  7:24     ` Zhang Rui
  -1 siblings, 0 replies; 50+ messages in thread
From: Zhang Rui @ 2012-08-17  7:24 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: linux-pm, Andrew Morton, Len Brown, linux-samsung-soc,
	linux-kernel, linux-acpi, lm-sensors, Guenter Roeck, SangWook Ju,
	Durgadoss, Jean Delvare, Kyungmin Park, Kukjin Kim

On 四, 2012-08-16 at 17:11 +0530, Amit Daniel Kachhap wrote:
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms.  As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
> 
>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
> 
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale platforms.
> 
> Brief Description:
> 
> 1) The generic cooling devices code is placed inside driver/thermal/*
>    as placing inside acpi folder will need un-necessary enabling of acpi
>    code.  This code is architecture independent.
> 
> 2) This patchset adds generic cpu cooling low level implementation
>    through frequency clipping.  In future, other cpu related cooling
>    devices may be added here.  An ACPI version of this already exists
>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>    platforms like ARM using the generic thermal interface along with the
>    generic cpu cooling devices.  The cooling device registration API's
>    return cooling device pointers which can be easily binded with the
>    thermal zone trip points.  The important APIs exposed are,
> 
>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>         struct cpumask *clip_cpus)
>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> 
> 3) Samsung exynos platform thermal implementation is done using the
>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>    driver present in the hwmon folder(registered as hwmon driver) is moved
>    to thermal folder and registered as a thermal driver.
> 
> A simple data/control flow diagrams is shown below,
> 
> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>           |                             |
>          \|/                            |
>   Cpufreq cooling device <---------------
> 
> TODO:
> *Will send the DT enablement patches later after the driver is merged.
> 
> This patch:
> 
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
> 
> [akpm@linux-foundation.org: fix comment layout]
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
> ---
>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>  drivers/thermal/Kconfig                   |   11 +
>  drivers/thermal/Makefile                  |    1 +
>  drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
>  include/linux/cpu_cooling.h               |   79 +++++
>  5 files changed, 655 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>  create mode 100644 drivers/thermal/cpu_cooling.c
>  create mode 100644 include/linux/cpu_cooling.h
> 
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..a1f2a6b
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,52 @@
> +CPU cooling APIs How To
> +===================================
> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> +	struct cpumask *clip_cpus)
> +
> +    This interface function registers the cpufreq cooling device with the name
> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> +    cooling devices.
> +
> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> +    cdev: Cooling device pointer which has to be unregistered.
> +
> +
> +1.2 CPU cooling action notifier register/unregister interface
> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> +	unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> +
> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> +	unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP

what are these two APIs used for?
I did not see they are used in your patch set, do I miss something?

> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>  	depends on HWMON=y || HWMON=THERMAL
>  	default y
>  
> +config CPU_THERMAL
> +	bool "generic cpu cooling support"
> +	depends on THERMAL && CPU_FREQ
> +	help
> +	  This implements the generic cpu cooling mechanism through frequency
> +	  reduction, cpu hotplug and any other ways of reducing temperature. An
> +	  ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> +	  This will be useful for platforms using the generic thermal interface
> +	  and not the ACPI interface.
> +	  If you want this support, you should say Y here.
> +
>  config SPEAR_THERMAL
>  	bool "SPEAr thermal sensor driver"
>  	depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
>  #
>  
>  obj-$(CONFIG_THERMAL)		+= thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
>  obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..c42e557
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,512 @@
> +/*
> + *  linux/drivers/thermal/cpu_cooling.c
> + *
> + *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful, but
> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + *  General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + *	registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + *	egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + *	cooling	devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + *	frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> +	int id;
> +	struct thermal_cooling_device *cool_dev;
> +	unsigned int cpufreq_state;
> +	unsigned int cpufreq_val;
> +	struct cpumask allowed_cpus;
> +	struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/* Head of the blocking notifier chain to inform about frequency clamping */
> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> +	int err;
> +again:
> +	if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> +		return -ENOMEM;
> +
> +	mutex_lock(&cooling_cpufreq_lock);
> +	err = idr_get_new(idr, NULL, id);
> +	mutex_unlock(&cooling_cpufreq_lock);
> +
> +	if (unlikely(err == -EAGAIN))
> +		goto again;
> +	else if (unlikely(err))
> +		return err;
> +
> +	*id = *id & MAX_ID_MASK;
> +	return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> +	mutex_lock(&cooling_cpufreq_lock);
> +	idr_remove(idr, id);
> +	mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:	struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +	int ret = 0;
> +
> +	switch (list) {
> +	case CPUFREQ_COOLING_START:
> +	case CPUFREQ_COOLING_STOP:
> +		ret = blocking_notifier_chain_register(
> +				&cputherm_state_notifier_list, nb);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +	}
> +	return ret;
> +}
> +EXPORT_SYMBOL(cputherm_register_notifier);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:	struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *	CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +	int ret = 0;
> +
> +	switch (list) {
> +	case CPUFREQ_COOLING_START:
> +	case CPUFREQ_COOLING_STOP:
> +		ret = blocking_notifier_chain_unregister(
> +				&cputherm_state_notifier_list, nb);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +	}
> +	return ret;
> +}
> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> +	struct cpufreq_policy policy;
> +	return !cpufreq_get_policy(&policy, 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
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
> +{
> +	int ret = 0, i = 0;
> +	unsigned long level_index;
> +	bool descend = false;
> +	struct cpufreq_frequency_table *table =
> +					cpufreq_frequency_get_table(cpu);
> +	if (!table)
> +		return ret;
> +
> +	while (table[i].frequency != CPUFREQ_TABLE_END) {
> +		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +			continue;
> +
> +		/*check if table in ascending or descending order*/
> +		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +			(table[i + 1].frequency < table[i].frequency)
> +			&& !descend) {
> +			descend = true;
> +		}
> +
> +		/*return if level matched and table in descending order*/
> +		if (descend && i == level)
> +			return table[i].frequency;
> +		i++;
> +	}
> +	i--;
> +
> +	if (level > i || descend)
> +		return ret;
> +	level_index = i - level;
> +
> +	/*Scan the table in reverse order and match the level*/
> +	while (i >= 0) {
> +		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +			continue;
> +		/*return if level matched*/
> +		if (i == level_index)
> +			return table[i].frequency;
> +		i--;
> +	}
> +	return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + *	clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> +				unsigned long cooling_state)
> +{
> +	unsigned int event, cpuid, clip_freq;
> +	struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> +	unsigned int cpu = cpumask_any(maskPtr);
> +
> +
> +	/* Check if the old cooling action is same as new cooling action */
> +	if (cpufreq_device->cpufreq_state == cooling_state)
> +		return 0;
> +
> +	clip_freq = get_cpu_frequency(cpu, cooling_state);
> +	if (!clip_freq)
> +		return -EINVAL;
> +
> +	cpufreq_device->cpufreq_state = cooling_state;
> +	cpufreq_device->cpufreq_val = clip_freq;
> +	notify_device = cpufreq_device;
> +
> +	if (cooling_state != 0)
> +		event = CPUFREQ_COOLING_START;
> +	else
> +		event = CPUFREQ_COOLING_STOP;
> +
> +	blocking_notifier_call_chain(&cputherm_state_notifier_list,
> +						event, &clip_freq);
> +
> +	for_each_cpu(cpuid, maskPtr) {
> +		if (is_cpufreq_valid(cpuid))
> +			cpufreq_update_policy(cpuid);
> +	}
> +
> +	notify_device = NOTIFY_INVALID;
> +
> +	return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb:	struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> +					unsigned long event, void *data)
> +{
> +	struct cpufreq_policy *policy = data;
> +	unsigned long max_freq = 0;
> +
> +	if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> +		return 0;
> +
> +	if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> +		max_freq = notify_device->cpufreq_val;
> +
> +	/* Never exceed user_policy.max*/
> +	if (max_freq > policy->user_policy.max)
> +		max_freq = policy->user_policy.max;
> +
> +	if (policy->max != max_freq)
> +		cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> +	return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +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;
> +	unsigned int cpu;
> +	struct cpufreq_frequency_table *table;
> +
> +	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;
> +	}
> +
> +	while (table[i].frequency != CPUFREQ_TABLE_END) {
> +		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +			continue;
> +		i++;
> +	}
> +	if (i > 0) {
> +		*state = --i;
> +		ret = 0;
> +	}
> +
> +return_get_max_state:
> +	mutex_unlock(&cooling_cpufreq_lock);
> +	return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling 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);
> +

as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
use the current cpu frequency to get the REAL cooling state, rather than
using a cached value.

thanks,
rui


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17  7:24     ` Zhang Rui
  0 siblings, 0 replies; 50+ messages in thread
From: Zhang Rui @ 2012-08-17  7:24 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: linux-pm, Andrew Morton, Len Brown, linux-samsung-soc,
	linux-kernel, linux-acpi, lm-sensors, Guenter Roeck, SangWook Ju,
	Durgadoss, Jean Delvare, Kyungmin Park, Kukjin Kim

On 四, 2012-08-16 at 17:11 +0530, Amit Daniel Kachhap wrote:
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms.  As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
> 
>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
> 
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale platforms.
> 
> Brief Description:
> 
> 1) The generic cooling devices code is placed inside driver/thermal/*
>    as placing inside acpi folder will need un-necessary enabling of acpi
>    code.  This code is architecture independent.
> 
> 2) This patchset adds generic cpu cooling low level implementation
>    through frequency clipping.  In future, other cpu related cooling
>    devices may be added here.  An ACPI version of this already exists
>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>    platforms like ARM using the generic thermal interface along with the
>    generic cpu cooling devices.  The cooling device registration API's
>    return cooling device pointers which can be easily binded with the
>    thermal zone trip points.  The important APIs exposed are,
> 
>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>         struct cpumask *clip_cpus)
>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> 
> 3) Samsung exynos platform thermal implementation is done using the
>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>    driver present in the hwmon folder(registered as hwmon driver) is moved
>    to thermal folder and registered as a thermal driver.
> 
> A simple data/control flow diagrams is shown below,
> 
> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>           |                             |
>          \|/                            |
>   Cpufreq cooling device <---------------
> 
> TODO:
> *Will send the DT enablement patches later after the driver is merged.
> 
> This patch:
> 
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
> 
> [akpm@linux-foundation.org: fix comment layout]
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
> ---
>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>  drivers/thermal/Kconfig                   |   11 +
>  drivers/thermal/Makefile                  |    1 +
>  drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
>  include/linux/cpu_cooling.h               |   79 +++++
>  5 files changed, 655 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>  create mode 100644 drivers/thermal/cpu_cooling.c
>  create mode 100644 include/linux/cpu_cooling.h
> 
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..a1f2a6b
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,52 @@
> +CPU cooling APIs How To
> +===================================
> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> +	struct cpumask *clip_cpus)
> +
> +    This interface function registers the cpufreq cooling device with the name
> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> +    cooling devices.
> +
> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> +    cdev: Cooling device pointer which has to be unregistered.
> +
> +
> +1.2 CPU cooling action notifier register/unregister interface
> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> +	unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> +
> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> +	unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP

what are these two APIs used for?
I did not see they are used in your patch set, do I miss something?

> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>  	depends on HWMON=y || HWMON=THERMAL
>  	default y
>  
> +config CPU_THERMAL
> +	bool "generic cpu cooling support"
> +	depends on THERMAL && CPU_FREQ
> +	help
> +	  This implements the generic cpu cooling mechanism through frequency
> +	  reduction, cpu hotplug and any other ways of reducing temperature. An
> +	  ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> +	  This will be useful for platforms using the generic thermal interface
> +	  and not the ACPI interface.
> +	  If you want this support, you should say Y here.
> +
>  config SPEAR_THERMAL
>  	bool "SPEAr thermal sensor driver"
>  	depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
>  #
>  
>  obj-$(CONFIG_THERMAL)		+= thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
>  obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..c42e557
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,512 @@
> +/*
> + *  linux/drivers/thermal/cpu_cooling.c
> + *
> + *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful, but
> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + *  General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + *	registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + *	egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + *	cooling	devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + *	frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> +	int id;
> +	struct thermal_cooling_device *cool_dev;
> +	unsigned int cpufreq_state;
> +	unsigned int cpufreq_val;
> +	struct cpumask allowed_cpus;
> +	struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/* Head of the blocking notifier chain to inform about frequency clamping */
> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> +	int err;
> +again:
> +	if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> +		return -ENOMEM;
> +
> +	mutex_lock(&cooling_cpufreq_lock);
> +	err = idr_get_new(idr, NULL, id);
> +	mutex_unlock(&cooling_cpufreq_lock);
> +
> +	if (unlikely(err == -EAGAIN))
> +		goto again;
> +	else if (unlikely(err))
> +		return err;
> +
> +	*id = *id & MAX_ID_MASK;
> +	return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> +	mutex_lock(&cooling_cpufreq_lock);
> +	idr_remove(idr, id);
> +	mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:	struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +	int ret = 0;
> +
> +	switch (list) {
> +	case CPUFREQ_COOLING_START:
> +	case CPUFREQ_COOLING_STOP:
> +		ret = blocking_notifier_chain_register(
> +				&cputherm_state_notifier_list, nb);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +	}
> +	return ret;
> +}
> +EXPORT_SYMBOL(cputherm_register_notifier);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:	struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *	CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +	int ret = 0;
> +
> +	switch (list) {
> +	case CPUFREQ_COOLING_START:
> +	case CPUFREQ_COOLING_STOP:
> +		ret = blocking_notifier_chain_unregister(
> +				&cputherm_state_notifier_list, nb);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +	}
> +	return ret;
> +}
> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> +	struct cpufreq_policy policy;
> +	return !cpufreq_get_policy(&policy, 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
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
> +{
> +	int ret = 0, i = 0;
> +	unsigned long level_index;
> +	bool descend = false;
> +	struct cpufreq_frequency_table *table =
> +					cpufreq_frequency_get_table(cpu);
> +	if (!table)
> +		return ret;
> +
> +	while (table[i].frequency != CPUFREQ_TABLE_END) {
> +		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +			continue;
> +
> +		/*check if table in ascending or descending order*/
> +		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +			(table[i + 1].frequency < table[i].frequency)
> +			&& !descend) {
> +			descend = true;
> +		}
> +
> +		/*return if level matched and table in descending order*/
> +		if (descend && i == level)
> +			return table[i].frequency;
> +		i++;
> +	}
> +	i--;
> +
> +	if (level > i || descend)
> +		return ret;
> +	level_index = i - level;
> +
> +	/*Scan the table in reverse order and match the level*/
> +	while (i >= 0) {
> +		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +			continue;
> +		/*return if level matched*/
> +		if (i == level_index)
> +			return table[i].frequency;
> +		i--;
> +	}
> +	return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + *	clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> +				unsigned long cooling_state)
> +{
> +	unsigned int event, cpuid, clip_freq;
> +	struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> +	unsigned int cpu = cpumask_any(maskPtr);
> +
> +
> +	/* Check if the old cooling action is same as new cooling action */
> +	if (cpufreq_device->cpufreq_state == cooling_state)
> +		return 0;
> +
> +	clip_freq = get_cpu_frequency(cpu, cooling_state);
> +	if (!clip_freq)
> +		return -EINVAL;
> +
> +	cpufreq_device->cpufreq_state = cooling_state;
> +	cpufreq_device->cpufreq_val = clip_freq;
> +	notify_device = cpufreq_device;
> +
> +	if (cooling_state != 0)
> +		event = CPUFREQ_COOLING_START;
> +	else
> +		event = CPUFREQ_COOLING_STOP;
> +
> +	blocking_notifier_call_chain(&cputherm_state_notifier_list,
> +						event, &clip_freq);
> +
> +	for_each_cpu(cpuid, maskPtr) {
> +		if (is_cpufreq_valid(cpuid))
> +			cpufreq_update_policy(cpuid);
> +	}
> +
> +	notify_device = NOTIFY_INVALID;
> +
> +	return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb:	struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> +					unsigned long event, void *data)
> +{
> +	struct cpufreq_policy *policy = data;
> +	unsigned long max_freq = 0;
> +
> +	if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> +		return 0;
> +
> +	if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> +		max_freq = notify_device->cpufreq_val;
> +
> +	/* Never exceed user_policy.max*/
> +	if (max_freq > policy->user_policy.max)
> +		max_freq = policy->user_policy.max;
> +
> +	if (policy->max != max_freq)
> +		cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> +	return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +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;
> +	unsigned int cpu;
> +	struct cpufreq_frequency_table *table;
> +
> +	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;
> +	}
> +
> +	while (table[i].frequency != CPUFREQ_TABLE_END) {
> +		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +			continue;
> +		i++;
> +	}
> +	if (i > 0) {
> +		*state = --i;
> +		ret = 0;
> +	}
> +
> +return_get_max_state:
> +	mutex_unlock(&cooling_cpufreq_lock);
> +	return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling 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);
> +

as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
use the current cpu frequency to get the REAL cooling state, rather than
using a cached value.

thanks,
rui



^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [lm-sensors] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17  7:24     ` Zhang Rui
  0 siblings, 0 replies; 50+ messages in thread
From: Zhang Rui @ 2012-08-17  7:24 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: linux-pm, Andrew Morton, Len Brown, linux-samsung-soc,
	linux-kernel, linux-acpi, lm-sensors, Guenter Roeck, SangWook Ju,
	Durgadoss, Jean Delvare, Kyungmin Park, Kukjin Kim

T24g5ZubLCAyMDEyLTA4LTE2IGF0IDE3OjExICswNTMwLCBBbWl0IERhbmllbCBLYWNoaGFwIHdy
b3RlOgo+IFRoaXMgcGF0Y2hzZXQgaW50cm9kdWNlcyBhIG5ldyBnZW5lcmljIGNvb2xpbmcgZGV2
aWNlIGJhc2VkIG9uIGNwdWZyZXEKPiB0aGF0IGNhbiBiZSB1c2VkIG9uIG5vbi1BQ1BJIHBsYXRm
b3Jtcy4gIEFzIGEgcHJvb2Ygb2YgY29uY2VwdCwgd2UgaGF2ZQo+IGRyaXZlcnMgZm9yIHRoZSBm
b2xsb3dpbmcgcGxhdGZvcm1zIHVzaW5nIHRoaXMgbWVjaGFuaXNtIG5vdzoKPiAKPiAgKiBTYW1z
dW5nIEV4eW5vcyAoRXh5bm9zNCBhbmQgRXh5bm9zNSkgaW4gdGhlIGN1cnJlbnQgcGF0Y2hzZXQu
Cj4gICogRnJlZXNjYWxlIGkuTVggKGdpdDovL2dpdC5saW5hcm8ub3JnL3Blb3BsZS9hbWl0ZGFu
aWVsay9saW51eC5naXQgaW14NnFfdGhlcm1hbCkKPiAKPiBUaGVyZSBpcyBhIHNtYWxsIGNoYW5n
ZSBpbiBjcHVmcmVxIGNvb2xpbmcgcmVnaXN0cmF0aW9uIEFQSXMsIHNvIGEgbWlub3IKPiBjaGFu
Z2UgaXMgbmVlZGVkIGZvciBGcmVlc2NhbGUgcGxhdGZvcm1zLgo+IAo+IEJyaWVmIERlc2NyaXB0
aW9uOgo+IAo+IDEpIFRoZSBnZW5lcmljIGNvb2xpbmcgZGV2aWNlcyBjb2RlIGlzIHBsYWNlZCBp
bnNpZGUgZHJpdmVyL3RoZXJtYWwvKgo+ICAgIGFzIHBsYWNpbmcgaW5zaWRlIGFjcGkgZm9sZGVy
IHdpbGwgbmVlZCB1bi1uZWNlc3NhcnkgZW5hYmxpbmcgb2YgYWNwaQo+ICAgIGNvZGUuICBUaGlz
IGNvZGUgaXMgYXJjaGl0ZWN0dXJlIGluZGVwZW5kZW50Lgo+IAo+IDIpIFRoaXMgcGF0Y2hzZXQg
YWRkcyBnZW5lcmljIGNwdSBjb29saW5nIGxvdyBsZXZlbCBpbXBsZW1lbnRhdGlvbgo+ICAgIHRo
cm91Z2ggZnJlcXVlbmN5IGNsaXBwaW5nLiAgSW4gZnV0dXJlLCBvdGhlciBjcHUgcmVsYXRlZCBj
b29saW5nCj4gICAgZGV2aWNlcyBtYXkgYmUgYWRkZWQgaGVyZS4gIEFuIEFDUEkgdmVyc2lvbiBv
ZiB0aGlzIGFscmVhZHkgZXhpc3RzCj4gICAgKGRyaXZlcnMvYWNwaS9wcm9jZXNzb3JfdGhlcm1h
bC5jKSAuQnV0IHRoaXMgd2lsbCBiZSB1c2VmdWwgZm9yCj4gICAgcGxhdGZvcm1zIGxpa2UgQVJN
IHVzaW5nIHRoZSBnZW5lcmljIHRoZXJtYWwgaW50ZXJmYWNlIGFsb25nIHdpdGggdGhlCj4gICAg
Z2VuZXJpYyBjcHUgY29vbGluZyBkZXZpY2VzLiAgVGhlIGNvb2xpbmcgZGV2aWNlIHJlZ2lzdHJh
dGlvbiBBUEkncwo+ICAgIHJldHVybiBjb29saW5nIGRldmljZSBwb2ludGVycyB3aGljaCBjYW4g
YmUgZWFzaWx5IGJpbmRlZCB3aXRoIHRoZQo+ICAgIHRoZXJtYWwgem9uZSB0cmlwIHBvaW50cy4g
IFRoZSBpbXBvcnRhbnQgQVBJcyBleHBvc2VkIGFyZSwKPiAKPiAgICBhKSBzdHJ1Y3QgdGhlcm1h
bF9jb29saW5nX2RldmljZSAqY3B1ZnJlcV9jb29saW5nX3JlZ2lzdGVyKAo+ICAgICAgICAgc3Ry
dWN0IGNwdW1hc2sgKmNsaXBfY3B1cykKPiAgICBiKSB2b2lkIGNwdWZyZXFfY29vbGluZ191bnJl
Z2lzdGVyKHN0cnVjdCB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjZGV2KQo+IAo+IDMpIFNhbXN1
bmcgZXh5bm9zIHBsYXRmb3JtIHRoZXJtYWwgaW1wbGVtZW50YXRpb24gaXMgZG9uZSB1c2luZyB0
aGUKPiAgICBnZW5lcmljIGNwdSBjb29saW5nIEFQSXMgYW5kIHRoZSBuZXcgdHJpcCB0eXBlLiAg
VGhlIHRlbXBlcmF0dXJlIHNlbnNvcgo+ICAgIGRyaXZlciBwcmVzZW50IGluIHRoZSBod21vbiBm
b2xkZXIocmVnaXN0ZXJlZCBhcyBod21vbiBkcml2ZXIpIGlzIG1vdmVkCj4gICAgdG8gdGhlcm1h
bCBmb2xkZXIgYW5kIHJlZ2lzdGVyZWQgYXMgYSB0aGVybWFsIGRyaXZlci4KPiAKPiBBIHNpbXBs
ZSBkYXRhL2NvbnRyb2wgZmxvdyBkaWFncmFtcyBpcyBzaG93biBiZWxvdywKPiAKPiBDb3JlIExp
bnV4IHRoZXJtYWwgPC0tLS0tPiAgRXh5bm9zIHRoZXJtYWwgaW50ZXJmYWNlIDwtLS0tLSBUZW1w
ZXJhdHVyZSBTZW5zb3IKPiAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
fAo+ICAgICAgICAgIFx8LyAgICAgICAgICAgICAgICAgICAgICAgICAgICB8Cj4gICBDcHVmcmVx
IGNvb2xpbmcgZGV2aWNlIDwtLS0tLS0tLS0tLS0tLS0KPiAKPiBUT0RPOgo+ICpXaWxsIHNlbmQg
dGhlIERUIGVuYWJsZW1lbnQgcGF0Y2hlcyBsYXRlciBhZnRlciB0aGUgZHJpdmVyIGlzIG1lcmdl
ZC4KPiAKPiBUaGlzIHBhdGNoOgo+IAo+IEFkZCBzdXBwb3J0IGZvciBnZW5lcmljIGNwdSB0aGVy
bWFsIGNvb2xpbmcgbG93IGxldmVsIGltcGxlbWVudGF0aW9ucwo+IHVzaW5nIGZyZXF1ZW5jeSBz
Y2FsaW5nIHVwL2Rvd24gYmFzZWQgb24gdGhlIHJlZ2lzdHJhdGlvbiBwYXJhbWV0ZXJzLgo+IERp
ZmZlcmVudCBjcHUgcmVsYXRlZCBjb29saW5nIGRldmljZXMgY2FuIGJlIHJlZ2lzdGVyZWQgYnkg
dGhlIHVzZXIgYW5kCj4gdGhlIGJpbmRpbmcgb2YgdGhlc2UgY29vbGluZyBkZXZpY2VzIHRvIHRo
ZSBjb3JyZXNwb25kaW5nIHRyaXAgcG9pbnRzIGNhbgo+IGJlIGVhc2lseSBkb25lIGFzIHRoZSBy
ZWdpc3RyYXRpb24gQVBJcyByZXR1cm4gdGhlIGNvb2xpbmcgZGV2aWNlIHBvaW50ZXIuCj4gVGhl
IHVzZXIgb2YgdGhlc2UgQVBJcyBhcmUgcmVzcG9uc2libGUgZm9yIHBhc3NpbmcgY2xpcHBpbmcg
ZnJlcXVlbmN5IC4KPiBUaGUgZHJpdmVycyBjYW4gYWxzbyByZWdpc3RlciB0byByZWNpZXZlIG5v
dGlmaWNhdGlvbiBhYm91dCBhbnkgY29vbGluZwo+IGFjdGlvbiBjYWxsZWQuCj4gCj4gW2FrcG1A
bGludXgtZm91bmRhdGlvbi5vcmc6IGZpeCBjb21tZW50IGxheW91dF0KPiBTaWduZWQtb2ZmLWJ5
OiBBbWl0IERhbmllbCBLYWNoaGFwIDxhbWl0LmthY2hoYXBAbGluYXJvLm9yZz4KPiBDYzogR3Vl
bnRlciBSb2VjayA8Z3VlbnRlci5yb2Vja0Blcmljc3Nvbi5jb20+Cj4gQ2M6IFNhbmdXb29rIEp1
IDxzdy5qdUBzYW1zdW5nLmNvbT4KPiBDYzogRHVyZ2Fkb3NzIDxkdXJnYWRvc3MuckBpbnRlbC5j
b20+Cj4gQ2M6IExlbiBCcm93biA8bGVuYkBrZXJuZWwub3JnPgo+IENjOiBKZWFuIERlbHZhcmUg
PGtoYWxpQGxpbnV4LWZyLm9yZz4KPiBDYzogS3l1bmdtaW4gUGFyayA8a21wYXJrQGluZnJhZGVh
ZC5vcmc+Cj4gQ2M6IEt1a2ppbiBLaW0gPGtnZW5lLmtpbUBzYW1zdW5nLmNvbT4KPiBTaWduZWQt
b2ZmLWJ5OiBBbmRyZXcgTW9ydG9uIDxha3BtQGxpbnV4LWZvdW5kYXRpb24ub3JnPgo+IFNpZ25l
ZC1vZmYtYnk6IEFtaXQgRGFuaWVsIEthY2hoYXAgPGFtaXQuZGFuaWVsQHNhbXN1bmcuY29tPgo+
IC0tLQo+ICBEb2N1bWVudGF0aW9uL3RoZXJtYWwvY3B1LWNvb2xpbmctYXBpLnR4dCB8ICAgNTIg
KysrCj4gIGRyaXZlcnMvdGhlcm1hbC9LY29uZmlnICAgICAgICAgICAgICAgICAgIHwgICAxMSAr
Cj4gIGRyaXZlcnMvdGhlcm1hbC9NYWtlZmlsZSAgICAgICAgICAgICAgICAgIHwgICAgMSArCj4g
IGRyaXZlcnMvdGhlcm1hbC9jcHVfY29vbGluZy5jICAgICAgICAgICAgIHwgIDUxMiArKysrKysr
KysrKysrKysrKysrKysrKysrKysrKwo+ICBpbmNsdWRlL2xpbnV4L2NwdV9jb29saW5nLmggICAg
ICAgICAgICAgICB8ICAgNzkgKysrKysKPiAgNSBmaWxlcyBjaGFuZ2VkLCA2NTUgaW5zZXJ0aW9u
cygrKSwgMCBkZWxldGlvbnMoLSkKPiAgY3JlYXRlIG1vZGUgMTAwNjQ0IERvY3VtZW50YXRpb24v
dGhlcm1hbC9jcHUtY29vbGluZy1hcGkudHh0Cj4gIGNyZWF0ZSBtb2RlIDEwMDY0NCBkcml2ZXJz
L3RoZXJtYWwvY3B1X2Nvb2xpbmcuYwo+ICBjcmVhdGUgbW9kZSAxMDA2NDQgaW5jbHVkZS9saW51
eC9jcHVfY29vbGluZy5oCj4gCj4gZGlmZiAtLWdpdCBhL0RvY3VtZW50YXRpb24vdGhlcm1hbC9j
cHUtY29vbGluZy1hcGkudHh0IGIvRG9jdW1lbnRhdGlvbi90aGVybWFsL2NwdS1jb29saW5nLWFw
aS50eHQKPiBuZXcgZmlsZSBtb2RlIDEwMDY0NAo+IGluZGV4IDAwMDAwMDAuLmExZjJhNmIKPiAt
LS0gL2Rldi9udWxsCj4gKysrIGIvRG9jdW1lbnRhdGlvbi90aGVybWFsL2NwdS1jb29saW5nLWFw
aS50eHQKPiBAQCAtMCwwICsxLDUyIEBACj4gK0NQVSBjb29saW5nIEFQSXMgSG93IFRvCj4gKz09
PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Cj4gKwo+ICtXcml0dGVuIGJ5IEFtaXQg
RGFuaWVsIEthY2hoYXAgPGFtaXQua2FjaGhhcEBsaW5hcm8ub3JnPgo+ICsKPiArVXBkYXRlZDog
MTIgTWF5IDIwMTIKPiArCj4gK0NvcHlyaWdodCAoYykgIDIwMTIgU2Ftc3VuZyBFbGVjdHJvbmlj
cyBDby4sIEx0ZChodHRwOi8vd3d3LnNhbXN1bmcuY29tKQo+ICsKPiArMC4gSW50cm9kdWN0aW9u
Cj4gKwo+ICtUaGUgZ2VuZXJpYyBjcHUgY29vbGluZyhmcmVxIGNsaXBwaW5nKSBwcm92aWRlcyBy
ZWdpc3RyYXRpb24vdW5yZWdpc3RyYXRpb24gQVBJcwo+ICt0byB0aGUgY2FsbGVyLiBUaGUgYmlu
ZGluZyBvZiB0aGUgY29vbGluZyBkZXZpY2VzIHRvIHRoZSB0cmlwIHBvaW50IGlzIGxlZnQgZm9y
Cj4gK3RoZSB1c2VyLiBUaGUgcmVnaXN0cmF0aW9uIEFQSXMgcmV0dXJucyB0aGUgY29vbGluZyBk
ZXZpY2UgcG9pbnRlci4KPiArCj4gKzEuIGNwdSBjb29saW5nIEFQSXMKPiArCj4gKzEuMSBjcHVm
cmVxIHJlZ2lzdHJhdGlvbi91bnJlZ2lzdHJhdGlvbiBBUElzCj4gKzEuMS4xIHN0cnVjdCB0aGVy
bWFsX2Nvb2xpbmdfZGV2aWNlICpjcHVmcmVxX2Nvb2xpbmdfcmVnaXN0ZXIoCj4gKwlzdHJ1Y3Qg
Y3B1bWFzayAqY2xpcF9jcHVzKQo+ICsKPiArICAgIFRoaXMgaW50ZXJmYWNlIGZ1bmN0aW9uIHJl
Z2lzdGVycyB0aGUgY3B1ZnJlcSBjb29saW5nIGRldmljZSB3aXRoIHRoZSBuYW1lCj4gKyAgICAi
dGhlcm1hbC1jcHVmcmVxLSV4Ii4gVGhpcyBhcGkgY2FuIHN1cHBvcnQgbXVsdGlwbGUgaW5zdGFu
Y2VzIG9mIGNwdWZyZXEKPiArICAgIGNvb2xpbmcgZGV2aWNlcy4KPiArCj4gKyAgIGNsaXBfY3B1
czogY3B1bWFzayBvZiBjcHVzIHdoZXJlIHRoZSBmcmVxdWVuY3kgY29uc3RyYWludHMgd2lsbCBo
YXBwZW4uCj4gKwo+ICsxLjEuMiB2b2lkIGNwdWZyZXFfY29vbGluZ191bnJlZ2lzdGVyKHN0cnVj
dCB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjZGV2KQo+ICsKPiArICAgIFRoaXMgaW50ZXJmYWNl
IGZ1bmN0aW9uIHVucmVnaXN0ZXJzIHRoZSAidGhlcm1hbC1jcHVmcmVxLSV4IiBjb29saW5nIGRl
dmljZS4KPiArCj4gKyAgICBjZGV2OiBDb29saW5nIGRldmljZSBwb2ludGVyIHdoaWNoIGhhcyB0
byBiZSB1bnJlZ2lzdGVyZWQuCj4gKwo+ICsKPiArMS4yIENQVSBjb29saW5nIGFjdGlvbiBub3Rp
ZmllciByZWdpc3Rlci91bnJlZ2lzdGVyIGludGVyZmFjZQo+ICsxLjIuMSBpbnQgY3B1dGhlcm1f
cmVnaXN0ZXJfbm90aWZpZXIoc3RydWN0IG5vdGlmaWVyX2Jsb2NrICpuYiwKPiArCXVuc2lnbmVk
IGludCBsaXN0KQo+ICsKPiArICAgIFRoaXMgaW50ZXJmYWNlIHJlZ2lzdGVycyBhIGRyaXZlciB3
aXRoIGNwdSBjb29saW5nIGxheWVyLiBUaGUgZHJpdmVyIHdpbGwKPiArICAgIGJlIG5vdGlmaWVk
IHdoZW4gYW55IGNwdSBjb29saW5nIGFjdGlvbiBpcyBjYWxsZWQuCj4gKwo+ICsgICAgbmI6IG5v
dGlmaWVyIGZ1bmN0aW9uIHRvIHJlZ2lzdGVyCj4gKyAgICBsaXN0OiBDUFVGUkVRX0NPT0xJTkdf
U1RBUlQgb3IgQ1BVRlJFUV9DT09MSU5HX1NUT1AKPiArCj4gKzEuMi4yIGludCBjcHV0aGVybV91
bnJlZ2lzdGVyX25vdGlmaWVyKHN0cnVjdCBub3RpZmllcl9ibG9jayAqbmIsCj4gKwl1bnNpZ25l
ZCBpbnQgbGlzdCkKPiArCj4gKyAgICBUaGlzIGludGVyZmFjZSByZWdpc3RlcnMgYSBkcml2ZXIg
d2l0aCBjcHUgY29vbGluZyBsYXllci4gVGhlIGRyaXZlciB3aWxsCj4gKyAgICBiZSBub3RpZmll
ZCB3aGVuIGFueSBjcHUgY29vbGluZyBhY3Rpb24gaXMgY2FsbGVkLgo+ICsKPiArICAgIG5iOiBu
b3RpZmllciBmdW5jdGlvbiB0byByZWdpc3Rlcgo+ICsgICAgbGlzdDogQ1BVRlJFUV9DT09MSU5H
X1NUQVJUIG9yIENQVUZSRVFfQ09PTElOR19TVE9QCgp3aGF0IGFyZSB0aGVzZSB0d28gQVBJcyB1
c2VkIGZvcj8KSSBkaWQgbm90IHNlZSB0aGV5IGFyZSB1c2VkIGluIHlvdXIgcGF0Y2ggc2V0LCBk
byBJIG1pc3Mgc29tZXRoaW5nPwoKPiBkaWZmIC0tZ2l0IGEvZHJpdmVycy90aGVybWFsL0tjb25m
aWcgYi9kcml2ZXJzL3RoZXJtYWwvS2NvbmZpZwo+IGluZGV4IDdkZDhjMzQuLjk5NjAwM2IgMTAw
NjQ0Cj4gLS0tIGEvZHJpdmVycy90aGVybWFsL0tjb25maWcKPiArKysgYi9kcml2ZXJzL3RoZXJt
YWwvS2NvbmZpZwo+IEBAIC0xOSw2ICsxOSwxNyBAQCBjb25maWcgVEhFUk1BTF9IV01PTgo+ICAJ
ZGVwZW5kcyBvbiBIV01PTj15IHx8IEhXTU9OPVRIRVJNQUwKPiAgCWRlZmF1bHQgeQo+ICAKPiAr
Y29uZmlnIENQVV9USEVSTUFMCj4gKwlib29sICJnZW5lcmljIGNwdSBjb29saW5nIHN1cHBvcnQi
Cj4gKwlkZXBlbmRzIG9uIFRIRVJNQUwgJiYgQ1BVX0ZSRVEKPiArCWhlbHAKPiArCSAgVGhpcyBp
bXBsZW1lbnRzIHRoZSBnZW5lcmljIGNwdSBjb29saW5nIG1lY2hhbmlzbSB0aHJvdWdoIGZyZXF1
ZW5jeQo+ICsJICByZWR1Y3Rpb24sIGNwdSBob3RwbHVnIGFuZCBhbnkgb3RoZXIgd2F5cyBvZiBy
ZWR1Y2luZyB0ZW1wZXJhdHVyZS4gQW4KPiArCSAgQUNQSSB2ZXJzaW9uIG9mIHRoaXMgYWxyZWFk
eSBleGlzdHMoZHJpdmVycy9hY3BpL3Byb2Nlc3Nvcl90aGVybWFsLmMpLgo+ICsJICBUaGlzIHdp
bGwgYmUgdXNlZnVsIGZvciBwbGF0Zm9ybXMgdXNpbmcgdGhlIGdlbmVyaWMgdGhlcm1hbCBpbnRl
cmZhY2UKPiArCSAgYW5kIG5vdCB0aGUgQUNQSSBpbnRlcmZhY2UuCj4gKwkgIElmIHlvdSB3YW50
IHRoaXMgc3VwcG9ydCwgeW91IHNob3VsZCBzYXkgWSBoZXJlLgo+ICsKPiAgY29uZmlnIFNQRUFS
X1RIRVJNQUwKPiAgCWJvb2wgIlNQRUFyIHRoZXJtYWwgc2Vuc29yIGRyaXZlciIKPiAgCWRlcGVu
ZHMgb24gVEhFUk1BTAo+IGRpZmYgLS1naXQgYS9kcml2ZXJzL3RoZXJtYWwvTWFrZWZpbGUgYi9k
cml2ZXJzL3RoZXJtYWwvTWFrZWZpbGUKPiBpbmRleCBmZDkzNjlhLi5hYWU1OWFkIDEwMDY0NAo+
IC0tLSBhL2RyaXZlcnMvdGhlcm1hbC9NYWtlZmlsZQo+ICsrKyBiL2RyaXZlcnMvdGhlcm1hbC9N
YWtlZmlsZQo+IEBAIC0zLDUgKzMsNiBAQAo+ICAjCj4gIAo+ICBvYmotJChDT05GSUdfVEhFUk1B
TCkJCSs9IHRoZXJtYWxfc3lzLm8KPiArb2JqLSQoQ09ORklHX0NQVV9USEVSTUFMKQkJKz0gY3B1
X2Nvb2xpbmcubwo+ICBvYmotJChDT05GSUdfU1BFQVJfVEhFUk1BTCkJCSs9IHNwZWFyX3RoZXJt
YWwubwo+ICBvYmotJChDT05GSUdfUkNBUl9USEVSTUFMKQkrPSByY2FyX3RoZXJtYWwubwo+IGRp
ZmYgLS1naXQgYS9kcml2ZXJzL3RoZXJtYWwvY3B1X2Nvb2xpbmcuYyBiL2RyaXZlcnMvdGhlcm1h
bC9jcHVfY29vbGluZy5jCj4gbmV3IGZpbGUgbW9kZSAxMDA2NDQKPiBpbmRleCAwMDAwMDAwLi5j
NDJlNTU3Cj4gLS0tIC9kZXYvbnVsbAo+ICsrKyBiL2RyaXZlcnMvdGhlcm1hbC9jcHVfY29vbGlu
Zy5jCj4gQEAgLTAsMCArMSw1MTIgQEAKPiArLyoKPiArICogIGxpbnV4L2RyaXZlcnMvdGhlcm1h
bC9jcHVfY29vbGluZy5jCj4gKyAqCj4gKyAqICBDb3B5cmlnaHQgKEMpIDIwMTIJU2Ftc3VuZyBF
bGVjdHJvbmljcyBDby4sIEx0ZChodHRwOi8vd3d3LnNhbXN1bmcuY29tKQo+ICsgKiAgQ29weXJp
Z2h0IChDKSAyMDEyICBBbWl0IERhbmllbCA8YW1pdC5rYWNoaGFwQGxpbmFyby5vcmc+Cj4gKyAq
Cj4gKyAqIH5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+Cj4gKyAqICBUaGlzIHByb2dyYW0gaXMgZnJlZSBzb2Z0
d2FyZTsgeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeQo+ICsgKiAgaXQgdW5k
ZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNo
ZWQgYnkKPiArICogIHRoZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb247IHZlcnNpb24gMiBvZiB0
aGUgTGljZW5zZS4KPiArICoKPiArICogIFRoaXMgcHJvZ3JhbSBpcyBkaXN0cmlidXRlZCBpbiB0
aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQKPiArICogIFdJVEhPVVQgQU5ZIFdB
UlJBTlRZOyB3aXRob3V0IGV2ZW4gdGhlIGltcGxpZWQgd2FycmFudHkgb2YKPiArICogIE1FUkNI
QU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUg
R05VCj4gKyAqICBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuCj4gKyAq
Cj4gKyAqICBZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJh
bCBQdWJsaWMgTGljZW5zZSBhbG9uZwo+ICsgKiAgd2l0aCB0aGlzIHByb2dyYW07IGlmIG5vdCwg
d3JpdGUgdG8gdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgSW5jLiwKPiArICogIDU5IFRl
bXBsZSBQbGFjZSwgU3VpdGUgMzMwLCBCb3N0b24sIE1BIDAyMTExLTEzMDcgVVNBLgo+ICsgKgo+
ICsgKiB+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fgo+ICsgKi8KPiArI2luY2x1ZGUgPGxpbnV4L2tlcm5lbC5o
Pgo+ICsjaW5jbHVkZSA8bGludXgvbW9kdWxlLmg+Cj4gKyNpbmNsdWRlIDxsaW51eC90aGVybWFs
Lmg+Cj4gKyNpbmNsdWRlIDxsaW51eC9wbGF0Zm9ybV9kZXZpY2UuaD4KPiArI2luY2x1ZGUgPGxp
bnV4L2NwdWZyZXEuaD4KPiArI2luY2x1ZGUgPGxpbnV4L2Vyci5oPgo+ICsjaW5jbHVkZSA8bGlu
dXgvc2xhYi5oPgo+ICsjaW5jbHVkZSA8bGludXgvY3B1Lmg+Cj4gKyNpbmNsdWRlIDxsaW51eC9j
cHVfY29vbGluZy5oPgo+ICsKPiArLyoqCj4gKyAqIHN0cnVjdCBjcHVmcmVxX2Nvb2xpbmdfZGV2
aWNlCj4gKyAqIEBpZDogdW5pcXVlIGludGVnZXIgdmFsdWUgY29ycmVzcG9uZGluZyB0byBlYWNo
IGNwdWZyZXFfY29vbGluZ19kZXZpY2UKPiArICoJcmVnaXN0ZXJlZC4KPiArICogQGNvb2xfZGV2
OiB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlIHBvaW50ZXIgdG8ga2VlcCB0cmFjayBvZiB0aGUgdGhl
Cj4gKyAqCWVnaXN0ZXJlZCBjb29saW5nIGRldmljZS4KPiArICogQGNwdWZyZXFfc3RhdGU6IGlu
dGVnZXIgdmFsdWUgcmVwcmVzZW50aW5nIHRoZSBjdXJyZW50IHN0YXRlIG9mIGNwdWZyZXEKPiAr
ICoJY29vbGluZwlkZXZpY2VzLgo+ICsgKiBAY3B1ZnJlcV92YWw6IGludGVnZXIgdmFsdWUgcmVw
cmVzZW50aW5nIHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiB0aGUgY2xpcHBlZAo+ICsgKglmcmVxdWVu
Y3kuCj4gKyAqIEBhbGxvd2VkX2NwdXM6IGFsbCB0aGUgY3B1cyBpbnZvbHZlZCBmb3IgdGhpcyBj
cHVmcmVxX2Nvb2xpbmdfZGV2aWNlLgo+ICsgKiBAbm9kZTogbGlzdF9oZWFkIHRvIGxpbmsgYWxs
IGNwdWZyZXFfY29vbGluZ19kZXZpY2UgdG9nZXRoZXIuCj4gKyAqCj4gKyAqIFRoaXMgc3RydWN0
dXJlIGlzIHJlcXVpcmVkIGZvciBrZWVwaW5nIGluZm9ybWF0aW9uIG9mIGVhY2gKPiArICogY3B1
ZnJlcV9jb29saW5nX2RldmljZSByZWdpc3RlcmVkIGFzIGEgbGlzdCB3aG9zZSBoZWFkIGlzIHJl
cHJlc2VudGVkIGJ5Cj4gKyAqIGNvb2xpbmdfY3B1ZnJlcV9saXN0LiBJbiBvcmRlciB0byBwcmV2
ZW50IGNvcnJ1cHRpb24gb2YgdGhpcyBsaXN0IGEKPiArICogbXV0ZXggbG9jayBjb29saW5nX2Nw
dWZyZXFfbG9jayBpcyB1c2VkLgo+ICsgKi8KPiArc3RydWN0IGNwdWZyZXFfY29vbGluZ19kZXZp
Y2Ugewo+ICsJaW50IGlkOwo+ICsJc3RydWN0IHRoZXJtYWxfY29vbGluZ19kZXZpY2UgKmNvb2xf
ZGV2Owo+ICsJdW5zaWduZWQgaW50IGNwdWZyZXFfc3RhdGU7Cj4gKwl1bnNpZ25lZCBpbnQgY3B1
ZnJlcV92YWw7Cj4gKwlzdHJ1Y3QgY3B1bWFzayBhbGxvd2VkX2NwdXM7Cj4gKwlzdHJ1Y3QgbGlz
dF9oZWFkIG5vZGU7Cj4gK307Cj4gK3N0YXRpYyBMSVNUX0hFQUQoY29vbGluZ19jcHVmcmVxX2xp
c3QpOwo+ICtzdGF0aWMgREVGSU5FX0lEUihjcHVmcmVxX2lkcik7Cj4gKwo+ICtzdGF0aWMgc3Ry
dWN0IG11dGV4IGNvb2xpbmdfY3B1ZnJlcV9sb2NrOwo+ICsKPiArLyogbm90aWZ5X3RhYmxlIHBh
c3NlcyB2YWx1ZSB0byB0aGUgQ1BVRlJFUV9BREpVU1QgY2FsbGJhY2sgZnVuY3Rpb24uICovCj4g
KyNkZWZpbmUgTk9USUZZX0lOVkFMSUQgTlVMTAo+ICtzdHJ1Y3QgY3B1ZnJlcV9jb29saW5nX2Rl
dmljZSAqbm90aWZ5X2RldmljZTsKPiArCj4gKy8qIEhlYWQgb2YgdGhlIGJsb2NraW5nIG5vdGlm
aWVyIGNoYWluIHRvIGluZm9ybSBhYm91dCBmcmVxdWVuY3kgY2xhbXBpbmcgKi8KPiArc3RhdGlj
IEJMT0NLSU5HX05PVElGSUVSX0hFQUQoY3B1dGhlcm1fc3RhdGVfbm90aWZpZXJfbGlzdCk7Cj4g
Kwo+ICsvKioKPiArICogZ2V0X2lkciAtIGZ1bmN0aW9uIHRvIGdldCBhIHVuaXF1ZSBpZC4KPiAr
ICogQGlkcjogc3RydWN0IGlkciAqIGhhbmRsZSB1c2VkIHRvIGNyZWF0ZSBhIGlkLgo+ICsgKiBA
aWQ6IGludCAqIHZhbHVlIGdlbmVyYXRlZCBieSB0aGlzIGZ1bmN0aW9uLgo+ICsgKi8KPiArc3Rh
dGljIGludCBnZXRfaWRyKHN0cnVjdCBpZHIgKmlkciwgaW50ICppZCkKPiArewo+ICsJaW50IGVy
cjsKPiArYWdhaW46Cj4gKwlpZiAodW5saWtlbHkoaWRyX3ByZV9nZXQoaWRyLCBHRlBfS0VSTkVM
KSA9PSAwKSkKPiArCQlyZXR1cm4gLUVOT01FTTsKPiArCj4gKwltdXRleF9sb2NrKCZjb29saW5n
X2NwdWZyZXFfbG9jayk7Cj4gKwllcnIgPSBpZHJfZ2V0X25ldyhpZHIsIE5VTEwsIGlkKTsKPiAr
CW11dGV4X3VubG9jaygmY29vbGluZ19jcHVmcmVxX2xvY2spOwo+ICsKPiArCWlmICh1bmxpa2Vs
eShlcnIgPT0gLUVBR0FJTikpCj4gKwkJZ290byBhZ2FpbjsKPiArCWVsc2UgaWYgKHVubGlrZWx5
KGVycikpCj4gKwkJcmV0dXJuIGVycjsKPiArCj4gKwkqaWQgPSAqaWQgJiBNQVhfSURfTUFTSzsK
PiArCXJldHVybiAwOwo+ICt9Cj4gKwo+ICsvKioKPiArICogcmVsZWFzZV9pZHIgLSBmdW5jdGlv
biB0byBmcmVlIHRoZSB1bmlxdWUgaWQuCj4gKyAqIEBpZHI6IHN0cnVjdCBpZHIgKiBoYW5kbGUg
dXNlZCBmb3IgY3JlYXRpbmcgdGhlIGlkLgo+ICsgKiBAaWQ6IGludCB2YWx1ZSByZXByZXNlbnRp
bmcgdGhlIHVuaXF1ZSBpZC4KPiArICovCj4gK3N0YXRpYyB2b2lkIHJlbGVhc2VfaWRyKHN0cnVj
dCBpZHIgKmlkciwgaW50IGlkKQo+ICt7Cj4gKwltdXRleF9sb2NrKCZjb29saW5nX2NwdWZyZXFf
bG9jayk7Cj4gKwlpZHJfcmVtb3ZlKGlkciwgaWQpOwo+ICsJbXV0ZXhfdW5sb2NrKCZjb29saW5n
X2NwdWZyZXFfbG9jayk7Cj4gK30KPiArCj4gKy8qKgo+ICsgKiBjcHV0aGVybV9yZWdpc3Rlcl9u
b3RpZmllciAtIFJlZ2lzdGVyIGEgbm90aWZpZXIgd2l0aCBjcHUgY29vbGluZyBpbnRlcmZhY2Uu
Cj4gKyAqIEBuYjoJc3RydWN0IG5vdGlmaWVyX2Jsb2NrICogd2l0aCBjYWxsYmFjayBpbmZvLgo+
ICsgKiBAbGlzdDogaW50ZWdlciB2YWx1ZSBmb3Igd2hpY2ggbm90aWZpY2F0aW9uIGlzIG5lZWRl
ZC4gcG9zc2libGUgdmFsdWVzIGFyZQo+ICsgKglDUFVGUkVRX0NPT0xJTkdfU1RBUlQgYW5kIENQ
VUZSRVFfQ09PTElOR19TVE9QLgo+ICsgKgo+ICsgKiBUaGlzIGV4cG9ydGVkIGZ1bmN0aW9uIHJl
Z2lzdGVycyBhIGRyaXZlciB3aXRoIGNwdSBjb29saW5nIGxheWVyLiBUaGUgZHJpdmVyCj4gKyAq
IHdpbGwgYmUgbm90aWZpZWQgd2hlbiBhbnkgY3B1IGNvb2xpbmcgYWN0aW9uIGlzIGNhbGxlZC4K
PiArICovCj4gK2ludCBjcHV0aGVybV9yZWdpc3Rlcl9ub3RpZmllcihzdHJ1Y3Qgbm90aWZpZXJf
YmxvY2sgKm5iLCB1bnNpZ25lZCBpbnQgbGlzdCkKPiArewo+ICsJaW50IHJldCA9IDA7Cj4gKwo+
ICsJc3dpdGNoIChsaXN0KSB7Cj4gKwljYXNlIENQVUZSRVFfQ09PTElOR19TVEFSVDoKPiArCWNh
c2UgQ1BVRlJFUV9DT09MSU5HX1NUT1A6Cj4gKwkJcmV0ID0gYmxvY2tpbmdfbm90aWZpZXJfY2hh
aW5fcmVnaXN0ZXIoCj4gKwkJCQkmY3B1dGhlcm1fc3RhdGVfbm90aWZpZXJfbGlzdCwgbmIpOwo+
ICsJCWJyZWFrOwo+ICsJZGVmYXVsdDoKPiArCQlyZXQgPSAtRUlOVkFMOwo+ICsJfQo+ICsJcmV0
dXJuIHJldDsKPiArfQo+ICtFWFBPUlRfU1lNQk9MKGNwdXRoZXJtX3JlZ2lzdGVyX25vdGlmaWVy
KTsKPiArCj4gKy8qKgo+ICsgKiBjcHV0aGVybV91bnJlZ2lzdGVyX25vdGlmaWVyIC0gVW4tcmVn
aXN0ZXIgYSBub3RpZmllci4KPiArICogQG5iOglzdHJ1Y3Qgbm90aWZpZXJfYmxvY2sgKiB3aXRo
IGNhbGxiYWNrIGluZm8uCj4gKyAqIEBsaXN0OiBpbnRlZ2VyIHZhbHVlIGZvciB3aGljaCBub3Rp
ZmljYXRpb24gaXMgbmVlZGVkLiB2YWx1ZXMgcG9zc2libGUgYXJlCj4gKyAqCUNQVUZSRVFfQ09P
TElOR19TVEFSVCBvciBDUFVGUkVRX0NPT0xJTkdfU1RPUC4KPiArICoKPiArICogVGhpcyBleHBv
cnRlZCBmdW5jdGlvbiB1bi1yZWdpc3RlcnMgYSBkcml2ZXIgd2l0aCBjcHUgY29vbGluZyBsYXll
ci4KPiArICovCj4gK2ludCBjcHV0aGVybV91bnJlZ2lzdGVyX25vdGlmaWVyKHN0cnVjdCBub3Rp
Zmllcl9ibG9jayAqbmIsIHVuc2lnbmVkIGludCBsaXN0KQo+ICt7Cj4gKwlpbnQgcmV0ID0gMDsK
PiArCj4gKwlzd2l0Y2ggKGxpc3QpIHsKPiArCWNhc2UgQ1BVRlJFUV9DT09MSU5HX1NUQVJUOgo+
ICsJY2FzZSBDUFVGUkVRX0NPT0xJTkdfU1RPUDoKPiArCQlyZXQgPSBibG9ja2luZ19ub3RpZmll
cl9jaGFpbl91bnJlZ2lzdGVyKAo+ICsJCQkJJmNwdXRoZXJtX3N0YXRlX25vdGlmaWVyX2xpc3Qs
IG5iKTsKPiArCQlicmVhazsKPiArCWRlZmF1bHQ6Cj4gKwkJcmV0ID0gLUVJTlZBTDsKPiArCX0K
PiArCXJldHVybiByZXQ7Cj4gK30KPiArRVhQT1JUX1NZTUJPTChjcHV0aGVybV91bnJlZ2lzdGVy
X25vdGlmaWVyKTsKPiArCj4gKy8qIEJlbG93IGNvZGUgZGVmaW5lcyBmdW5jdGlvbnMgdG8gYmUg
dXNlZCBmb3IgY3B1ZnJlcSBhcyBjb29saW5nIGRldmljZSAqLwo+ICsKPiArLyoqCj4gKyAqIGlz
X2NwdWZyZXFfdmFsaWQgLSBmdW5jdGlvbiB0byBjaGVjayBpZiBhIGNwdSBoYXMgZnJlcXVlbmN5
IHRyYW5zaXRpb24gcG9saWN5Lgo+ICsgKiBAY3B1OiBjcHUgZm9yIHdoaWNoIGNoZWNrIGlzIG5l
ZWRlZC4KPiArICovCj4gK3N0YXRpYyBpbnQgaXNfY3B1ZnJlcV92YWxpZChpbnQgY3B1KQo+ICt7
Cj4gKwlzdHJ1Y3QgY3B1ZnJlcV9wb2xpY3kgcG9saWN5Owo+ICsJcmV0dXJuICFjcHVmcmVxX2dl
dF9wb2xpY3koJnBvbGljeSwgY3B1KTsKPiArfQo+ICsKPiArLyoqCj4gKyAqIGdldF9jcHVfZnJl
cXVlbmN5IC0gZ2V0IHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiBmcmVxdWVuY3kgZnJvbSBsZXZlbC4K
PiArICogQGNwdTogY3B1IGZvciB3aGljaCBmcmVxdWVuY3kgaXMgZmV0Y2hlZC4KPiArICogQGxl
dmVsOiBsZXZlbCBvZiBmcmVxdWVuY3kgb2YgdGhlIENQVQo+ICsgKgllLmcgbGV2ZWw9MSAtLT4g
MXN0IE1BWCBGUkVRLCBMRVZFTD0yIC0tLT4gMm5kIE1BWCBGUkVRLCAuLi4uIGV0Ywo+ICsgKi8K
PiArc3RhdGljIHVuc2lnbmVkIGludCBnZXRfY3B1X2ZyZXF1ZW5jeSh1bnNpZ25lZCBpbnQgY3B1
LCB1bnNpZ25lZCBsb25nIGxldmVsKQo+ICt7Cj4gKwlpbnQgcmV0ID0gMCwgaSA9IDA7Cj4gKwl1
bnNpZ25lZCBsb25nIGxldmVsX2luZGV4Owo+ICsJYm9vbCBkZXNjZW5kID0gZmFsc2U7Cj4gKwlz
dHJ1Y3QgY3B1ZnJlcV9mcmVxdWVuY3lfdGFibGUgKnRhYmxlID0KPiArCQkJCQljcHVmcmVxX2Zy
ZXF1ZW5jeV9nZXRfdGFibGUoY3B1KTsKPiArCWlmICghdGFibGUpCj4gKwkJcmV0dXJuIHJldDsK
PiArCj4gKwl3aGlsZSAodGFibGVbaV0uZnJlcXVlbmN5ICE9IENQVUZSRVFfVEFCTEVfRU5EKSB7
Cj4gKwkJaWYgKHRhYmxlW2ldLmZyZXF1ZW5jeSA9PSBDUFVGUkVRX0VOVFJZX0lOVkFMSUQpCj4g
KwkJCWNvbnRpbnVlOwo+ICsKPiArCQkvKmNoZWNrIGlmIHRhYmxlIGluIGFzY2VuZGluZyBvciBk
ZXNjZW5kaW5nIG9yZGVyKi8KPiArCQlpZiAoKHRhYmxlW2kgKyAxXS5mcmVxdWVuY3kgIT0gQ1BV
RlJFUV9UQUJMRV9FTkQpICYmCj4gKwkJCSh0YWJsZVtpICsgMV0uZnJlcXVlbmN5IDwgdGFibGVb
aV0uZnJlcXVlbmN5KQo+ICsJCQkmJiAhZGVzY2VuZCkgewo+ICsJCQlkZXNjZW5kID0gdHJ1ZTsK
PiArCQl9Cj4gKwo+ICsJCS8qcmV0dXJuIGlmIGxldmVsIG1hdGNoZWQgYW5kIHRhYmxlIGluIGRl
c2NlbmRpbmcgb3JkZXIqLwo+ICsJCWlmIChkZXNjZW5kICYmIGkgPT0gbGV2ZWwpCj4gKwkJCXJl
dHVybiB0YWJsZVtpXS5mcmVxdWVuY3k7Cj4gKwkJaSsrOwo+ICsJfQo+ICsJaS0tOwo+ICsKPiAr
CWlmIChsZXZlbCA+IGkgfHwgZGVzY2VuZCkKPiArCQlyZXR1cm4gcmV0Owo+ICsJbGV2ZWxfaW5k
ZXggPSBpIC0gbGV2ZWw7Cj4gKwo+ICsJLypTY2FuIHRoZSB0YWJsZSBpbiByZXZlcnNlIG9yZGVy
IGFuZCBtYXRjaCB0aGUgbGV2ZWwqLwo+ICsJd2hpbGUgKGkgPj0gMCkgewo+ICsJCWlmICh0YWJs
ZVtpXS5mcmVxdWVuY3kgPT0gQ1BVRlJFUV9FTlRSWV9JTlZBTElEKQo+ICsJCQljb250aW51ZTsK
PiArCQkvKnJldHVybiBpZiBsZXZlbCBtYXRjaGVkKi8KPiArCQlpZiAoaSA9PSBsZXZlbF9pbmRl
eCkKPiArCQkJcmV0dXJuIHRhYmxlW2ldLmZyZXF1ZW5jeTsKPiArCQlpLS07Cj4gKwl9Cj4gKwly
ZXR1cm4gcmV0Owo+ICt9Cj4gKwo+ICsvKioKPiArICogY3B1ZnJlcV9hcHBseV9jb29saW5nIC0g
ZnVuY3Rpb24gdG8gYXBwbHkgZnJlcXVlbmN5IGNsaXBwaW5nLgo+ICsgKiBAY3B1ZnJlcV9kZXZp
Y2U6IGNwdWZyZXFfY29vbGluZ19kZXZpY2UgcG9pbnRlciBjb250YWluaW5nIGZyZXF1ZW5jeQo+
ICsgKgljbGlwcGluZyBkYXRhLgo+ICsgKiBAY29vbGluZ19zdGF0ZTogdmFsdWUgb2YgdGhlIGNv
b2xpbmcgc3RhdGUuCj4gKyAqLwo+ICtzdGF0aWMgaW50IGNwdWZyZXFfYXBwbHlfY29vbGluZyhz
dHJ1Y3QgY3B1ZnJlcV9jb29saW5nX2RldmljZSAqY3B1ZnJlcV9kZXZpY2UsCj4gKwkJCQl1bnNp
Z25lZCBsb25nIGNvb2xpbmdfc3RhdGUpCj4gK3sKPiArCXVuc2lnbmVkIGludCBldmVudCwgY3B1
aWQsIGNsaXBfZnJlcTsKPiArCXN0cnVjdCBjcHVtYXNrICptYXNrUHRyID0gJmNwdWZyZXFfZGV2
aWNlLT5hbGxvd2VkX2NwdXM7Cj4gKwl1bnNpZ25lZCBpbnQgY3B1ID0gY3B1bWFza19hbnkobWFz
a1B0cik7Cj4gKwo+ICsKPiArCS8qIENoZWNrIGlmIHRoZSBvbGQgY29vbGluZyBhY3Rpb24gaXMg
c2FtZSBhcyBuZXcgY29vbGluZyBhY3Rpb24gKi8KPiArCWlmIChjcHVmcmVxX2RldmljZS0+Y3B1
ZnJlcV9zdGF0ZSA9PSBjb29saW5nX3N0YXRlKQo+ICsJCXJldHVybiAwOwo+ICsKPiArCWNsaXBf
ZnJlcSA9IGdldF9jcHVfZnJlcXVlbmN5KGNwdSwgY29vbGluZ19zdGF0ZSk7Cj4gKwlpZiAoIWNs
aXBfZnJlcSkKPiArCQlyZXR1cm4gLUVJTlZBTDsKPiArCj4gKwljcHVmcmVxX2RldmljZS0+Y3B1
ZnJlcV9zdGF0ZSA9IGNvb2xpbmdfc3RhdGU7Cj4gKwljcHVmcmVxX2RldmljZS0+Y3B1ZnJlcV92
YWwgPSBjbGlwX2ZyZXE7Cj4gKwlub3RpZnlfZGV2aWNlID0gY3B1ZnJlcV9kZXZpY2U7Cj4gKwo+
ICsJaWYgKGNvb2xpbmdfc3RhdGUgIT0gMCkKPiArCQlldmVudCA9IENQVUZSRVFfQ09PTElOR19T
VEFSVDsKPiArCWVsc2UKPiArCQlldmVudCA9IENQVUZSRVFfQ09PTElOR19TVE9QOwo+ICsKPiAr
CWJsb2NraW5nX25vdGlmaWVyX2NhbGxfY2hhaW4oJmNwdXRoZXJtX3N0YXRlX25vdGlmaWVyX2xp
c3QsCj4gKwkJCQkJCWV2ZW50LCAmY2xpcF9mcmVxKTsKPiArCj4gKwlmb3JfZWFjaF9jcHUoY3B1
aWQsIG1hc2tQdHIpIHsKPiArCQlpZiAoaXNfY3B1ZnJlcV92YWxpZChjcHVpZCkpCj4gKwkJCWNw
dWZyZXFfdXBkYXRlX3BvbGljeShjcHVpZCk7Cj4gKwl9Cj4gKwo+ICsJbm90aWZ5X2RldmljZSA9
IE5PVElGWV9JTlZBTElEOwo+ICsKPiArCXJldHVybiAwOwo+ICt9Cj4gKwo+ICsvKioKPiArICog
Y3B1ZnJlcV90aGVybWFsX25vdGlmaWVyIC0gbm90aWZpZXIgY2FsbGJhY2sgZm9yIGNwdWZyZXEg
cG9saWN5IGNoYW5nZS4KPiArICogQG5iOglzdHJ1Y3Qgbm90aWZpZXJfYmxvY2sgKiB3aXRoIGNh
bGxiYWNrIGluZm8uCj4gKyAqIEBldmVudDogdmFsdWUgc2hvd2luZyBjcHVmcmVxIGV2ZW50IGZv
ciB3aGljaCB0aGlzIGZ1bmN0aW9uIGludm9rZWQuCj4gKyAqIEBkYXRhOiBjYWxsYmFjay1zcGVj
aWZpYyBkYXRhCj4gKyAqLwo+ICtzdGF0aWMgaW50IGNwdWZyZXFfdGhlcm1hbF9ub3RpZmllcihz
dHJ1Y3Qgbm90aWZpZXJfYmxvY2sgKm5iLAo+ICsJCQkJCXVuc2lnbmVkIGxvbmcgZXZlbnQsIHZv
aWQgKmRhdGEpCj4gK3sKPiArCXN0cnVjdCBjcHVmcmVxX3BvbGljeSAqcG9saWN5ID0gZGF0YTsK
PiArCXVuc2lnbmVkIGxvbmcgbWF4X2ZyZXEgPSAwOwo+ICsKPiArCWlmIChldmVudCAhPSBDUFVG
UkVRX0FESlVTVCB8fCBub3RpZnlfZGV2aWNlID09IE5PVElGWV9JTlZBTElEKQo+ICsJCXJldHVy
biAwOwo+ICsKPiArCWlmIChjcHVtYXNrX3Rlc3RfY3B1KHBvbGljeS0+Y3B1LCAmbm90aWZ5X2Rl
dmljZS0+YWxsb3dlZF9jcHVzKSkKPiArCQltYXhfZnJlcSA9IG5vdGlmeV9kZXZpY2UtPmNwdWZy
ZXFfdmFsOwo+ICsKPiArCS8qIE5ldmVyIGV4Y2VlZCB1c2VyX3BvbGljeS5tYXgqLwo+ICsJaWYg
KG1heF9mcmVxID4gcG9saWN5LT51c2VyX3BvbGljeS5tYXgpCj4gKwkJbWF4X2ZyZXEgPSBwb2xp
Y3ktPnVzZXJfcG9saWN5Lm1heDsKPiArCj4gKwlpZiAocG9saWN5LT5tYXggIT0gbWF4X2ZyZXEp
Cj4gKwkJY3B1ZnJlcV92ZXJpZnlfd2l0aGluX2xpbWl0cyhwb2xpY3ksIDAsIG1heF9mcmVxKTsK
PiArCj4gKwlyZXR1cm4gMDsKPiArfQo+ICsKPiArLyoKPiArICogY3B1ZnJlcSBjb29saW5nIGRl
dmljZSBjYWxsYmFjayBmdW5jdGlvbnMgYXJlIGRlZmluZWQgYmVsb3cKPiArICovCj4gKwo+ICsv
KioKPiArICogY3B1ZnJlcV9nZXRfbWF4X3N0YXRlIC0gY2FsbGJhY2sgZnVuY3Rpb24gdG8gZ2V0
IHRoZSBtYXggY29vbGluZyBzdGF0ZS4KPiArICogQGNkZXY6IHRoZXJtYWwgY29vbGluZyBkZXZp
Y2UgcG9pbnRlci4KPiArICogQHN0YXRlOiBmaWxsIHRoaXMgdmFyaWFibGUgd2l0aCB0aGUgbWF4
IGNvb2xpbmcgc3RhdGUuCj4gKyAqLwo+ICtzdGF0aWMgaW50IGNwdWZyZXFfZ2V0X21heF9zdGF0
ZShzdHJ1Y3QgdGhlcm1hbF9jb29saW5nX2RldmljZSAqY2RldiwKPiArCQkJCSB1bnNpZ25lZCBs
b25nICpzdGF0ZSkKPiArewo+ICsJaW50IHJldCA9IC1FSU5WQUwsIGkgPSAwOwo+ICsJc3RydWN0
IGNwdWZyZXFfY29vbGluZ19kZXZpY2UgKmNwdWZyZXFfZGV2aWNlOwo+ICsJc3RydWN0IGNwdW1h
c2sgKm1hc2tQdHI7Cj4gKwl1bnNpZ25lZCBpbnQgY3B1Owo+ICsJc3RydWN0IGNwdWZyZXFfZnJl
cXVlbmN5X3RhYmxlICp0YWJsZTsKPiArCj4gKwltdXRleF9sb2NrKCZjb29saW5nX2NwdWZyZXFf
bG9jayk7Cj4gKwlsaXN0X2Zvcl9lYWNoX2VudHJ5KGNwdWZyZXFfZGV2aWNlLCAmY29vbGluZ19j
cHVmcmVxX2xpc3QsIG5vZGUpIHsKPiArCQlpZiAoY3B1ZnJlcV9kZXZpY2UgJiYgY3B1ZnJlcV9k
ZXZpY2UtPmNvb2xfZGV2ID09IGNkZXYpCj4gKwkJCWJyZWFrOwo+ICsJfQo+ICsJaWYgKGNwdWZy
ZXFfZGV2aWNlID09IE5VTEwpCj4gKwkJZ290byByZXR1cm5fZ2V0X21heF9zdGF0ZTsKPiArCj4g
KwltYXNrUHRyID0gJmNwdWZyZXFfZGV2aWNlLT5hbGxvd2VkX2NwdXM7Cj4gKwljcHUgPSBjcHVt
YXNrX2FueShtYXNrUHRyKTsKPiArCXRhYmxlID0gY3B1ZnJlcV9mcmVxdWVuY3lfZ2V0X3RhYmxl
KGNwdSk7Cj4gKwlpZiAoIXRhYmxlKSB7Cj4gKwkJKnN0YXRlID0gMDsKPiArCQlyZXQgPSAwOwo+
ICsJCWdvdG8gcmV0dXJuX2dldF9tYXhfc3RhdGU7Cj4gKwl9Cj4gKwo+ICsJd2hpbGUgKHRhYmxl
W2ldLmZyZXF1ZW5jeSAhPSBDUFVGUkVRX1RBQkxFX0VORCkgewo+ICsJCWlmICh0YWJsZVtpXS5m
cmVxdWVuY3kgPT0gQ1BVRlJFUV9FTlRSWV9JTlZBTElEKQo+ICsJCQljb250aW51ZTsKPiArCQlp
Kys7Cj4gKwl9Cj4gKwlpZiAoaSA+IDApIHsKPiArCQkqc3RhdGUgPSAtLWk7Cj4gKwkJcmV0ID0g
MDsKPiArCX0KPiArCj4gK3JldHVybl9nZXRfbWF4X3N0YXRlOgo+ICsJbXV0ZXhfdW5sb2NrKCZj
b29saW5nX2NwdWZyZXFfbG9jayk7Cj4gKwlyZXR1cm4gcmV0Owo+ICt9Cj4gKwo+ICsvKioKPiAr
ICogY3B1ZnJlcV9nZXRfY3VyX3N0YXRlIC0gY2FsbGJhY2sgZnVuY3Rpb24gdG8gZ2V0IHRoZSBj
dXJyZW50IGNvb2xpbmcgc3RhdGUuCj4gKyAqIEBjZGV2OiB0aGVybWFsIGNvb2xpbmcgZGV2aWNl
IHBvaW50ZXIuCj4gKyAqIEBzdGF0ZTogZmlsbCB0aGlzIHZhcmlhYmxlIHdpdGggdGhlIGN1cnJl
bnQgY29vbGluZyBzdGF0ZS4KPiArICovCj4gK3N0YXRpYyBpbnQgY3B1ZnJlcV9nZXRfY3VyX3N0
YXRlKHN0cnVjdCB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjZGV2LAo+ICsJCQkJIHVuc2lnbmVk
IGxvbmcgKnN0YXRlKQo+ICt7Cj4gKwlpbnQgcmV0ID0gLUVJTlZBTDsKPiArCXN0cnVjdCBjcHVm
cmVxX2Nvb2xpbmdfZGV2aWNlICpjcHVmcmVxX2RldmljZTsKPiArCj4gKwltdXRleF9sb2NrKCZj
b29saW5nX2NwdWZyZXFfbG9jayk7Cj4gKwlsaXN0X2Zvcl9lYWNoX2VudHJ5KGNwdWZyZXFfZGV2
aWNlLCAmY29vbGluZ19jcHVmcmVxX2xpc3QsIG5vZGUpIHsKPiArCQlpZiAoY3B1ZnJlcV9kZXZp
Y2UgJiYgY3B1ZnJlcV9kZXZpY2UtPmNvb2xfZGV2ID09IGNkZXYpIHsKPiArCQkJKnN0YXRlID0g
Y3B1ZnJlcV9kZXZpY2UtPmNwdWZyZXFfc3RhdGU7Cj4gKwkJCXJldCA9IDA7Cj4gKwkJCWJyZWFr
Owo+ICsJCX0KPiArCX0KPiArCW11dGV4X3VubG9jaygmY29vbGluZ19jcHVmcmVxX2xvY2spOwo+
ICsKCmFzIGNwdWZyZXEgbWF5IGJlIGNoYW5nZWQgaW4gb3RoZXIgcGxhY2VzLCBlLmcuIHZpYSBz
eXNmcyBJL0YsIHdlIHNob3VsZAp1c2UgdGhlIGN1cnJlbnQgY3B1IGZyZXF1ZW5jeSB0byBnZXQg
dGhlIFJFQUwgY29vbGluZyBzdGF0ZSwgcmF0aGVyIHRoYW4KdXNpbmcgYSBjYWNoZWQgdmFsdWUu
Cgp0aGFua3MsCnJ1aQoKCgpfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f
X19fX19fXwpsbS1zZW5zb3JzIG1haWxpbmcgbGlzdApsbS1zZW5zb3JzQGxtLXNlbnNvcnMub3Jn
Cmh0dHA6Ly9saXN0cy5sbS1zZW5zb3JzLm9yZy9tYWlsbWFuL2xpc3RpbmZvL2xtLXNlbnNvcnM

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-17  7:24     ` Zhang Rui
  (?)
@ 2012-08-17  7:58       ` Amit Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Kachhap @ 2012-08-17  7:58 UTC (permalink / raw)
  To: Zhang Rui
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park, linux-pm,
	Guenter Roeck, Andrew Morton

On 17 August 2012 12:54, Zhang Rui <rui.zhang@intel.com> wrote:
> On 四, 2012-08-16 at 17:11 +0530, Amit Daniel Kachhap wrote:
>> This patchset introduces a new generic cooling device based on cpufreq
>> that can be used on non-ACPI platforms.  As a proof of concept, we have
>> drivers for the following platforms using this mechanism now:
>>
>>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>>
>> There is a small change in cpufreq cooling registration APIs, so a minor
>> change is needed for Freescale platforms.
>>
>> Brief Description:
>>
>> 1) The generic cooling devices code is placed inside driver/thermal/*
>>    as placing inside acpi folder will need un-necessary enabling of acpi
>>    code.  This code is architecture independent.
>>
>> 2) This patchset adds generic cpu cooling low level implementation
>>    through frequency clipping.  In future, other cpu related cooling
>>    devices may be added here.  An ACPI version of this already exists
>>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>>    platforms like ARM using the generic thermal interface along with the
>>    generic cpu cooling devices.  The cooling device registration API's
>>    return cooling device pointers which can be easily binded with the
>>    thermal zone trip points.  The important APIs exposed are,
>>
>>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>>         struct cpumask *clip_cpus)
>>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>>
>> 3) Samsung exynos platform thermal implementation is done using the
>>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>>    driver present in the hwmon folder(registered as hwmon driver) is moved
>>    to thermal folder and registered as a thermal driver.
>>
>> A simple data/control flow diagrams is shown below,
>>
>> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>>           |                             |
>>          \|/                            |
>>   Cpufreq cooling device <---------------
>>
>> TODO:
>> *Will send the DT enablement patches later after the driver is merged.
>>
>> This patch:
>>
>> Add support for generic cpu thermal cooling low level implementations
>> using frequency scaling up/down based on the registration parameters.
>> Different cpu related cooling devices can be registered by the user and
>> the binding of these cooling devices to the corresponding trip points can
>> be easily done as the registration APIs return the cooling device pointer.
>> The user of these APIs are responsible for passing clipping frequency .
>> The drivers can also register to recieve notification about any cooling
>> action called.
>>
>> [akpm@linux-foundation.org: fix comment layout]
>> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
>> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
>> Cc: SangWook Ju <sw.ju@samsung.com>
>> Cc: Durgadoss <durgadoss.r@intel.com>
>> Cc: Len Brown <lenb@kernel.org>
>> Cc: Jean Delvare <khali@linux-fr.org>
>> Cc: Kyungmin Park <kmpark@infradead.org>
>> Cc: Kukjin Kim <kgene.kim@samsung.com>
>> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
>> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
>> ---
>>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>>  drivers/thermal/Kconfig                   |   11 +
>>  drivers/thermal/Makefile                  |    1 +
>>  drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
>>  include/linux/cpu_cooling.h               |   79 +++++
>>  5 files changed, 655 insertions(+), 0 deletions(-)
>>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>>  create mode 100644 drivers/thermal/cpu_cooling.c
>>  create mode 100644 include/linux/cpu_cooling.h
>>
>> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
>> new file mode 100644
>> index 0000000..a1f2a6b
>> --- /dev/null
>> +++ b/Documentation/thermal/cpu-cooling-api.txt
>> @@ -0,0 +1,52 @@
>> +CPU cooling APIs How To
>> +===================================
>> +
>> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
>> +
>> +Updated: 12 May 2012
>> +
>> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
>> +
>> +0. Introduction
>> +
>> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
>> +to the caller. The binding of the cooling devices to the trip point is left for
>> +the user. The registration APIs returns the cooling device pointer.
>> +
>> +1. cpu cooling APIs
>> +
>> +1.1 cpufreq registration/unregistration APIs
>> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
>> +     struct cpumask *clip_cpus)
>> +
>> +    This interface function registers the cpufreq cooling device with the name
>> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
>> +    cooling devices.
>> +
>> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
>> +
>> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>> +
>> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
>> +
>> +    cdev: Cooling device pointer which has to be unregistered.
>> +
>> +
>> +1.2 CPU cooling action notifier register/unregister interface
>> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
>> +     unsigned int list)
>> +
>> +    This interface registers a driver with cpu cooling layer. The driver will
>> +    be notified when any cpu cooling action is called.
>> +
>> +    nb: notifier function to register
>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>> +
>> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
>> +     unsigned int list)
>> +
>> +    This interface registers a driver with cpu cooling layer. The driver will
>> +    be notified when any cpu cooling action is called.
>> +
>> +    nb: notifier function to register
>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>
> what are these two APIs used for?
> I did not see they are used in your patch set, do I miss something?
No currently they are not used by my patches. I added them on request
from Eduardo and others
>
>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>> index 7dd8c34..996003b 100644
>> --- a/drivers/thermal/Kconfig
>> +++ b/drivers/thermal/Kconfig
>> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>>       depends on HWMON=y || HWMON=THERMAL
>>       default y
>>
>> +config CPU_THERMAL
>> +     bool "generic cpu cooling support"
>> +     depends on THERMAL && CPU_FREQ
>> +     help
>> +       This implements the generic cpu cooling mechanism through frequency
>> +       reduction, cpu hotplug and any other ways of reducing temperature. An
>> +       ACPI version of this already exists(drivers/acpi/processor_thermal.c).
>> +       This will be useful for platforms using the generic thermal interface
>> +       and not the ACPI interface.
>> +       If you want this support, you should say Y here.
>> +
>>  config SPEAR_THERMAL
>>       bool "SPEAr thermal sensor driver"
>>       depends on THERMAL
>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>> index fd9369a..aae59ad 100644
>> --- a/drivers/thermal/Makefile
>> +++ b/drivers/thermal/Makefile
>> @@ -3,5 +3,6 @@
>>  #
>>
>>  obj-$(CONFIG_THERMAL)                += thermal_sys.o
>> +obj-$(CONFIG_CPU_THERMAL)            += cpu_cooling.o
>>  obj-$(CONFIG_SPEAR_THERMAL)          += spear_thermal.o
>>  obj-$(CONFIG_RCAR_THERMAL)   += rcar_thermal.o
>> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
>> new file mode 100644
>> index 0000000..c42e557
>> --- /dev/null
>> +++ b/drivers/thermal/cpu_cooling.c
>> @@ -0,0 +1,512 @@
>> +/*
>> + *  linux/drivers/thermal/cpu_cooling.c
>> + *
>> + *  Copyright (C) 2012       Samsung Electronics Co., Ltd(http://www.samsung.com)
>> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
>> + *
>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> + *  This program is free software; you can redistribute it and/or modify
>> + *  it under the terms of the GNU General Public License as published by
>> + *  the Free Software Foundation; version 2 of the License.
>> + *
>> + *  This program is distributed in the hope that it will be useful, but
>> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + *  General Public License for more details.
>> + *
>> + *  You should have received a copy of the GNU General Public License along
>> + *  with this program; if not, write to the Free Software Foundation, Inc.,
>> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
>> + *
>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> + */
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/thermal.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/cpufreq.h>
>> +#include <linux/err.h>
>> +#include <linux/slab.h>
>> +#include <linux/cpu.h>
>> +#include <linux/cpu_cooling.h>
>> +
>> +/**
>> + * struct cpufreq_cooling_device
>> + * @id: unique integer value corresponding to each cpufreq_cooling_device
>> + *   registered.
>> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
>> + *   egistered cooling device.
>> + * @cpufreq_state: integer value representing the current state of cpufreq
>> + *   cooling devices.
>> + * @cpufreq_val: integer value representing the absolute value of the clipped
>> + *   frequency.
>> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
>> + * @node: list_head to link all cpufreq_cooling_device together.
>> + *
>> + * This structure is required for keeping information of each
>> + * cpufreq_cooling_device registered as a list whose head is represented by
>> + * cooling_cpufreq_list. In order to prevent corruption of this list a
>> + * mutex lock cooling_cpufreq_lock is used.
>> + */
>> +struct cpufreq_cooling_device {
>> +     int id;
>> +     struct thermal_cooling_device *cool_dev;
>> +     unsigned int cpufreq_state;
>> +     unsigned int cpufreq_val;
>> +     struct cpumask allowed_cpus;
>> +     struct list_head node;
>> +};
>> +static LIST_HEAD(cooling_cpufreq_list);
>> +static DEFINE_IDR(cpufreq_idr);
>> +
>> +static struct mutex cooling_cpufreq_lock;
>> +
>> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
>> +#define NOTIFY_INVALID NULL
>> +struct cpufreq_cooling_device *notify_device;
>> +
>> +/* Head of the blocking notifier chain to inform about frequency clamping */
>> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
>> +
>> +/**
>> + * get_idr - function to get a unique id.
>> + * @idr: struct idr * handle used to create a id.
>> + * @id: int * value generated by this function.
>> + */
>> +static int get_idr(struct idr *idr, int *id)
>> +{
>> +     int err;
>> +again:
>> +     if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
>> +             return -ENOMEM;
>> +
>> +     mutex_lock(&cooling_cpufreq_lock);
>> +     err = idr_get_new(idr, NULL, id);
>> +     mutex_unlock(&cooling_cpufreq_lock);
>> +
>> +     if (unlikely(err == -EAGAIN))
>> +             goto again;
>> +     else if (unlikely(err))
>> +             return err;
>> +
>> +     *id = *id & MAX_ID_MASK;
>> +     return 0;
>> +}
>> +
>> +/**
>> + * release_idr - function to free the unique id.
>> + * @idr: struct idr * handle used for creating the id.
>> + * @id: int value representing the unique id.
>> + */
>> +static void release_idr(struct idr *idr, int id)
>> +{
>> +     mutex_lock(&cooling_cpufreq_lock);
>> +     idr_remove(idr, id);
>> +     mutex_unlock(&cooling_cpufreq_lock);
>> +}
>> +
>> +/**
>> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
>> + * @nb:      struct notifier_block * with callback info.
>> + * @list: integer value for which notification is needed. possible values are
>> + *   CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
>> + *
>> + * This exported function registers a driver with cpu cooling layer. The driver
>> + * will be notified when any cpu cooling action is called.
>> + */
>> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
>> +{
>> +     int ret = 0;
>> +
>> +     switch (list) {
>> +     case CPUFREQ_COOLING_START:
>> +     case CPUFREQ_COOLING_STOP:
>> +             ret = blocking_notifier_chain_register(
>> +                             &cputherm_state_notifier_list, nb);
>> +             break;
>> +     default:
>> +             ret = -EINVAL;
>> +     }
>> +     return ret;
>> +}
>> +EXPORT_SYMBOL(cputherm_register_notifier);
>> +
>> +/**
>> + * cputherm_unregister_notifier - Un-register a notifier.
>> + * @nb:      struct notifier_block * with callback info.
>> + * @list: integer value for which notification is needed. values possible are
>> + *   CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
>> + *
>> + * This exported function un-registers a driver with cpu cooling layer.
>> + */
>> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
>> +{
>> +     int ret = 0;
>> +
>> +     switch (list) {
>> +     case CPUFREQ_COOLING_START:
>> +     case CPUFREQ_COOLING_STOP:
>> +             ret = blocking_notifier_chain_unregister(
>> +                             &cputherm_state_notifier_list, nb);
>> +             break;
>> +     default:
>> +             ret = -EINVAL;
>> +     }
>> +     return ret;
>> +}
>> +EXPORT_SYMBOL(cputherm_unregister_notifier);
>> +
>> +/* Below code defines functions to be used for cpufreq as cooling device */
>> +
>> +/**
>> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
>> + * @cpu: cpu for which check is needed.
>> + */
>> +static int is_cpufreq_valid(int cpu)
>> +{
>> +     struct cpufreq_policy policy;
>> +     return !cpufreq_get_policy(&policy, 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
>> + */
>> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
>> +{
>> +     int ret = 0, i = 0;
>> +     unsigned long level_index;
>> +     bool descend = false;
>> +     struct cpufreq_frequency_table *table =
>> +                                     cpufreq_frequency_get_table(cpu);
>> +     if (!table)
>> +             return ret;
>> +
>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> +                     continue;
>> +
>> +             /*check if table in ascending or descending order*/
>> +             if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
>> +                     (table[i + 1].frequency < table[i].frequency)
>> +                     && !descend) {
>> +                     descend = true;
>> +             }
>> +
>> +             /*return if level matched and table in descending order*/
>> +             if (descend && i == level)
>> +                     return table[i].frequency;
>> +             i++;
>> +     }
>> +     i--;
>> +
>> +     if (level > i || descend)
>> +             return ret;
>> +     level_index = i - level;
>> +
>> +     /*Scan the table in reverse order and match the level*/
>> +     while (i >= 0) {
>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> +                     continue;
>> +             /*return if level matched*/
>> +             if (i == level_index)
>> +                     return table[i].frequency;
>> +             i--;
>> +     }
>> +     return ret;
>> +}
>> +
>> +/**
>> + * cpufreq_apply_cooling - function to apply frequency clipping.
>> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
>> + *   clipping data.
>> + * @cooling_state: value of the cooling state.
>> + */
>> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
>> +                             unsigned long cooling_state)
>> +{
>> +     unsigned int event, cpuid, clip_freq;
>> +     struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
>> +     unsigned int cpu = cpumask_any(maskPtr);
>> +
>> +
>> +     /* Check if the old cooling action is same as new cooling action */
>> +     if (cpufreq_device->cpufreq_state == cooling_state)
>> +             return 0;
>> +
>> +     clip_freq = get_cpu_frequency(cpu, cooling_state);
>> +     if (!clip_freq)
>> +             return -EINVAL;
>> +
>> +     cpufreq_device->cpufreq_state = cooling_state;
>> +     cpufreq_device->cpufreq_val = clip_freq;
>> +     notify_device = cpufreq_device;
>> +
>> +     if (cooling_state != 0)
>> +             event = CPUFREQ_COOLING_START;
>> +     else
>> +             event = CPUFREQ_COOLING_STOP;
>> +
>> +     blocking_notifier_call_chain(&cputherm_state_notifier_list,
>> +                                             event, &clip_freq);
>> +
>> +     for_each_cpu(cpuid, maskPtr) {
>> +             if (is_cpufreq_valid(cpuid))
>> +                     cpufreq_update_policy(cpuid);
>> +     }
>> +
>> +     notify_device = NOTIFY_INVALID;
>> +
>> +     return 0;
>> +}
>> +
>> +/**
>> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
>> + * @nb:      struct notifier_block * with callback info.
>> + * @event: value showing cpufreq event for which this function invoked.
>> + * @data: callback-specific data
>> + */
>> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
>> +                                     unsigned long event, void *data)
>> +{
>> +     struct cpufreq_policy *policy = data;
>> +     unsigned long max_freq = 0;
>> +
>> +     if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
>> +             return 0;
>> +
>> +     if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
>> +             max_freq = notify_device->cpufreq_val;
>> +
>> +     /* Never exceed user_policy.max*/
>> +     if (max_freq > policy->user_policy.max)
>> +             max_freq = policy->user_policy.max;
>> +
>> +     if (policy->max != max_freq)
>> +             cpufreq_verify_within_limits(policy, 0, max_freq);
>> +
>> +     return 0;
>> +}
>> +
>> +/*
>> + * cpufreq cooling device callback functions are defined below
>> + */
>> +
>> +/**
>> + * cpufreq_get_max_state - callback function to get the max cooling state.
>> + * @cdev: thermal cooling device pointer.
>> + * @state: fill this variable with the max cooling state.
>> + */
>> +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;
>> +     unsigned int cpu;
>> +     struct cpufreq_frequency_table *table;
>> +
>> +     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;
>> +     }
>> +
>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> +                     continue;
>> +             i++;
>> +     }
>> +     if (i > 0) {
>> +             *state = --i;
>> +             ret = 0;
>> +     }
>> +
>> +return_get_max_state:
>> +     mutex_unlock(&cooling_cpufreq_lock);
>> +     return ret;
>> +}
>> +
>> +/**
>> + * cpufreq_get_cur_state - callback function to get the current cooling state.
>> + * @cdev: thermal cooling device pointer.
>> + * @state: fill this variable with the current cooling 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);
>> +
>
> as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
> use the current cpu frequency to get the REAL cooling state, rather than
> using a cached value.

Yes agreed , I will repost with your suggestion.

Thanks,
Amit
>
> thanks,
> rui
>
>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17  7:58       ` Amit Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Kachhap @ 2012-08-17  7:58 UTC (permalink / raw)
  To: Zhang Rui
  Cc: linux-pm, Andrew Morton, Len Brown, linux-samsung-soc,
	linux-kernel, linux-acpi, lm-sensors, Guenter Roeck, SangWook Ju,
	Durgadoss, Jean Delvare, Kyungmin Park, Kukjin Kim

On 17 August 2012 12:54, Zhang Rui <rui.zhang@intel.com> wrote:
> On 四, 2012-08-16 at 17:11 +0530, Amit Daniel Kachhap wrote:
>> This patchset introduces a new generic cooling device based on cpufreq
>> that can be used on non-ACPI platforms.  As a proof of concept, we have
>> drivers for the following platforms using this mechanism now:
>>
>>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>>
>> There is a small change in cpufreq cooling registration APIs, so a minor
>> change is needed for Freescale platforms.
>>
>> Brief Description:
>>
>> 1) The generic cooling devices code is placed inside driver/thermal/*
>>    as placing inside acpi folder will need un-necessary enabling of acpi
>>    code.  This code is architecture independent.
>>
>> 2) This patchset adds generic cpu cooling low level implementation
>>    through frequency clipping.  In future, other cpu related cooling
>>    devices may be added here.  An ACPI version of this already exists
>>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>>    platforms like ARM using the generic thermal interface along with the
>>    generic cpu cooling devices.  The cooling device registration API's
>>    return cooling device pointers which can be easily binded with the
>>    thermal zone trip points.  The important APIs exposed are,
>>
>>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>>         struct cpumask *clip_cpus)
>>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>>
>> 3) Samsung exynos platform thermal implementation is done using the
>>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>>    driver present in the hwmon folder(registered as hwmon driver) is moved
>>    to thermal folder and registered as a thermal driver.
>>
>> A simple data/control flow diagrams is shown below,
>>
>> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>>           |                             |
>>          \|/                            |
>>   Cpufreq cooling device <---------------
>>
>> TODO:
>> *Will send the DT enablement patches later after the driver is merged.
>>
>> This patch:
>>
>> Add support for generic cpu thermal cooling low level implementations
>> using frequency scaling up/down based on the registration parameters.
>> Different cpu related cooling devices can be registered by the user and
>> the binding of these cooling devices to the corresponding trip points can
>> be easily done as the registration APIs return the cooling device pointer.
>> The user of these APIs are responsible for passing clipping frequency .
>> The drivers can also register to recieve notification about any cooling
>> action called.
>>
>> [akpm@linux-foundation.org: fix comment layout]
>> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
>> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
>> Cc: SangWook Ju <sw.ju@samsung.com>
>> Cc: Durgadoss <durgadoss.r@intel.com>
>> Cc: Len Brown <lenb@kernel.org>
>> Cc: Jean Delvare <khali@linux-fr.org>
>> Cc: Kyungmin Park <kmpark@infradead.org>
>> Cc: Kukjin Kim <kgene.kim@samsung.com>
>> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
>> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
>> ---
>>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>>  drivers/thermal/Kconfig                   |   11 +
>>  drivers/thermal/Makefile                  |    1 +
>>  drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
>>  include/linux/cpu_cooling.h               |   79 +++++
>>  5 files changed, 655 insertions(+), 0 deletions(-)
>>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>>  create mode 100644 drivers/thermal/cpu_cooling.c
>>  create mode 100644 include/linux/cpu_cooling.h
>>
>> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
>> new file mode 100644
>> index 0000000..a1f2a6b
>> --- /dev/null
>> +++ b/Documentation/thermal/cpu-cooling-api.txt
>> @@ -0,0 +1,52 @@
>> +CPU cooling APIs How To
>> +===================================
>> +
>> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
>> +
>> +Updated: 12 May 2012
>> +
>> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
>> +
>> +0. Introduction
>> +
>> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
>> +to the caller. The binding of the cooling devices to the trip point is left for
>> +the user. The registration APIs returns the cooling device pointer.
>> +
>> +1. cpu cooling APIs
>> +
>> +1.1 cpufreq registration/unregistration APIs
>> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
>> +     struct cpumask *clip_cpus)
>> +
>> +    This interface function registers the cpufreq cooling device with the name
>> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
>> +    cooling devices.
>> +
>> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
>> +
>> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>> +
>> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
>> +
>> +    cdev: Cooling device pointer which has to be unregistered.
>> +
>> +
>> +1.2 CPU cooling action notifier register/unregister interface
>> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
>> +     unsigned int list)
>> +
>> +    This interface registers a driver with cpu cooling layer. The driver will
>> +    be notified when any cpu cooling action is called.
>> +
>> +    nb: notifier function to register
>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>> +
>> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
>> +     unsigned int list)
>> +
>> +    This interface registers a driver with cpu cooling layer. The driver will
>> +    be notified when any cpu cooling action is called.
>> +
>> +    nb: notifier function to register
>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>
> what are these two APIs used for?
> I did not see they are used in your patch set, do I miss something?
No currently they are not used by my patches. I added them on request
from Eduardo and others
>
>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>> index 7dd8c34..996003b 100644
>> --- a/drivers/thermal/Kconfig
>> +++ b/drivers/thermal/Kconfig
>> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>>       depends on HWMON=y || HWMON=THERMAL
>>       default y
>>
>> +config CPU_THERMAL
>> +     bool "generic cpu cooling support"
>> +     depends on THERMAL && CPU_FREQ
>> +     help
>> +       This implements the generic cpu cooling mechanism through frequency
>> +       reduction, cpu hotplug and any other ways of reducing temperature. An
>> +       ACPI version of this already exists(drivers/acpi/processor_thermal.c).
>> +       This will be useful for platforms using the generic thermal interface
>> +       and not the ACPI interface.
>> +       If you want this support, you should say Y here.
>> +
>>  config SPEAR_THERMAL
>>       bool "SPEAr thermal sensor driver"
>>       depends on THERMAL
>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>> index fd9369a..aae59ad 100644
>> --- a/drivers/thermal/Makefile
>> +++ b/drivers/thermal/Makefile
>> @@ -3,5 +3,6 @@
>>  #
>>
>>  obj-$(CONFIG_THERMAL)                += thermal_sys.o
>> +obj-$(CONFIG_CPU_THERMAL)            += cpu_cooling.o
>>  obj-$(CONFIG_SPEAR_THERMAL)          += spear_thermal.o
>>  obj-$(CONFIG_RCAR_THERMAL)   += rcar_thermal.o
>> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
>> new file mode 100644
>> index 0000000..c42e557
>> --- /dev/null
>> +++ b/drivers/thermal/cpu_cooling.c
>> @@ -0,0 +1,512 @@
>> +/*
>> + *  linux/drivers/thermal/cpu_cooling.c
>> + *
>> + *  Copyright (C) 2012       Samsung Electronics Co., Ltd(http://www.samsung.com)
>> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
>> + *
>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> + *  This program is free software; you can redistribute it and/or modify
>> + *  it under the terms of the GNU General Public License as published by
>> + *  the Free Software Foundation; version 2 of the License.
>> + *
>> + *  This program is distributed in the hope that it will be useful, but
>> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + *  General Public License for more details.
>> + *
>> + *  You should have received a copy of the GNU General Public License along
>> + *  with this program; if not, write to the Free Software Foundation, Inc.,
>> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
>> + *
>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> + */
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/thermal.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/cpufreq.h>
>> +#include <linux/err.h>
>> +#include <linux/slab.h>
>> +#include <linux/cpu.h>
>> +#include <linux/cpu_cooling.h>
>> +
>> +/**
>> + * struct cpufreq_cooling_device
>> + * @id: unique integer value corresponding to each cpufreq_cooling_device
>> + *   registered.
>> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
>> + *   egistered cooling device.
>> + * @cpufreq_state: integer value representing the current state of cpufreq
>> + *   cooling devices.
>> + * @cpufreq_val: integer value representing the absolute value of the clipped
>> + *   frequency.
>> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
>> + * @node: list_head to link all cpufreq_cooling_device together.
>> + *
>> + * This structure is required for keeping information of each
>> + * cpufreq_cooling_device registered as a list whose head is represented by
>> + * cooling_cpufreq_list. In order to prevent corruption of this list a
>> + * mutex lock cooling_cpufreq_lock is used.
>> + */
>> +struct cpufreq_cooling_device {
>> +     int id;
>> +     struct thermal_cooling_device *cool_dev;
>> +     unsigned int cpufreq_state;
>> +     unsigned int cpufreq_val;
>> +     struct cpumask allowed_cpus;
>> +     struct list_head node;
>> +};
>> +static LIST_HEAD(cooling_cpufreq_list);
>> +static DEFINE_IDR(cpufreq_idr);
>> +
>> +static struct mutex cooling_cpufreq_lock;
>> +
>> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
>> +#define NOTIFY_INVALID NULL
>> +struct cpufreq_cooling_device *notify_device;
>> +
>> +/* Head of the blocking notifier chain to inform about frequency clamping */
>> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
>> +
>> +/**
>> + * get_idr - function to get a unique id.
>> + * @idr: struct idr * handle used to create a id.
>> + * @id: int * value generated by this function.
>> + */
>> +static int get_idr(struct idr *idr, int *id)
>> +{
>> +     int err;
>> +again:
>> +     if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
>> +             return -ENOMEM;
>> +
>> +     mutex_lock(&cooling_cpufreq_lock);
>> +     err = idr_get_new(idr, NULL, id);
>> +     mutex_unlock(&cooling_cpufreq_lock);
>> +
>> +     if (unlikely(err == -EAGAIN))
>> +             goto again;
>> +     else if (unlikely(err))
>> +             return err;
>> +
>> +     *id = *id & MAX_ID_MASK;
>> +     return 0;
>> +}
>> +
>> +/**
>> + * release_idr - function to free the unique id.
>> + * @idr: struct idr * handle used for creating the id.
>> + * @id: int value representing the unique id.
>> + */
>> +static void release_idr(struct idr *idr, int id)
>> +{
>> +     mutex_lock(&cooling_cpufreq_lock);
>> +     idr_remove(idr, id);
>> +     mutex_unlock(&cooling_cpufreq_lock);
>> +}
>> +
>> +/**
>> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
>> + * @nb:      struct notifier_block * with callback info.
>> + * @list: integer value for which notification is needed. possible values are
>> + *   CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
>> + *
>> + * This exported function registers a driver with cpu cooling layer. The driver
>> + * will be notified when any cpu cooling action is called.
>> + */
>> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
>> +{
>> +     int ret = 0;
>> +
>> +     switch (list) {
>> +     case CPUFREQ_COOLING_START:
>> +     case CPUFREQ_COOLING_STOP:
>> +             ret = blocking_notifier_chain_register(
>> +                             &cputherm_state_notifier_list, nb);
>> +             break;
>> +     default:
>> +             ret = -EINVAL;
>> +     }
>> +     return ret;
>> +}
>> +EXPORT_SYMBOL(cputherm_register_notifier);
>> +
>> +/**
>> + * cputherm_unregister_notifier - Un-register a notifier.
>> + * @nb:      struct notifier_block * with callback info.
>> + * @list: integer value for which notification is needed. values possible are
>> + *   CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
>> + *
>> + * This exported function un-registers a driver with cpu cooling layer.
>> + */
>> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
>> +{
>> +     int ret = 0;
>> +
>> +     switch (list) {
>> +     case CPUFREQ_COOLING_START:
>> +     case CPUFREQ_COOLING_STOP:
>> +             ret = blocking_notifier_chain_unregister(
>> +                             &cputherm_state_notifier_list, nb);
>> +             break;
>> +     default:
>> +             ret = -EINVAL;
>> +     }
>> +     return ret;
>> +}
>> +EXPORT_SYMBOL(cputherm_unregister_notifier);
>> +
>> +/* Below code defines functions to be used for cpufreq as cooling device */
>> +
>> +/**
>> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
>> + * @cpu: cpu for which check is needed.
>> + */
>> +static int is_cpufreq_valid(int cpu)
>> +{
>> +     struct cpufreq_policy policy;
>> +     return !cpufreq_get_policy(&policy, 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
>> + */
>> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
>> +{
>> +     int ret = 0, i = 0;
>> +     unsigned long level_index;
>> +     bool descend = false;
>> +     struct cpufreq_frequency_table *table =
>> +                                     cpufreq_frequency_get_table(cpu);
>> +     if (!table)
>> +             return ret;
>> +
>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> +                     continue;
>> +
>> +             /*check if table in ascending or descending order*/
>> +             if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
>> +                     (table[i + 1].frequency < table[i].frequency)
>> +                     && !descend) {
>> +                     descend = true;
>> +             }
>> +
>> +             /*return if level matched and table in descending order*/
>> +             if (descend && i == level)
>> +                     return table[i].frequency;
>> +             i++;
>> +     }
>> +     i--;
>> +
>> +     if (level > i || descend)
>> +             return ret;
>> +     level_index = i - level;
>> +
>> +     /*Scan the table in reverse order and match the level*/
>> +     while (i >= 0) {
>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> +                     continue;
>> +             /*return if level matched*/
>> +             if (i == level_index)
>> +                     return table[i].frequency;
>> +             i--;
>> +     }
>> +     return ret;
>> +}
>> +
>> +/**
>> + * cpufreq_apply_cooling - function to apply frequency clipping.
>> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
>> + *   clipping data.
>> + * @cooling_state: value of the cooling state.
>> + */
>> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
>> +                             unsigned long cooling_state)
>> +{
>> +     unsigned int event, cpuid, clip_freq;
>> +     struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
>> +     unsigned int cpu = cpumask_any(maskPtr);
>> +
>> +
>> +     /* Check if the old cooling action is same as new cooling action */
>> +     if (cpufreq_device->cpufreq_state == cooling_state)
>> +             return 0;
>> +
>> +     clip_freq = get_cpu_frequency(cpu, cooling_state);
>> +     if (!clip_freq)
>> +             return -EINVAL;
>> +
>> +     cpufreq_device->cpufreq_state = cooling_state;
>> +     cpufreq_device->cpufreq_val = clip_freq;
>> +     notify_device = cpufreq_device;
>> +
>> +     if (cooling_state != 0)
>> +             event = CPUFREQ_COOLING_START;
>> +     else
>> +             event = CPUFREQ_COOLING_STOP;
>> +
>> +     blocking_notifier_call_chain(&cputherm_state_notifier_list,
>> +                                             event, &clip_freq);
>> +
>> +     for_each_cpu(cpuid, maskPtr) {
>> +             if (is_cpufreq_valid(cpuid))
>> +                     cpufreq_update_policy(cpuid);
>> +     }
>> +
>> +     notify_device = NOTIFY_INVALID;
>> +
>> +     return 0;
>> +}
>> +
>> +/**
>> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
>> + * @nb:      struct notifier_block * with callback info.
>> + * @event: value showing cpufreq event for which this function invoked.
>> + * @data: callback-specific data
>> + */
>> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
>> +                                     unsigned long event, void *data)
>> +{
>> +     struct cpufreq_policy *policy = data;
>> +     unsigned long max_freq = 0;
>> +
>> +     if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
>> +             return 0;
>> +
>> +     if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
>> +             max_freq = notify_device->cpufreq_val;
>> +
>> +     /* Never exceed user_policy.max*/
>> +     if (max_freq > policy->user_policy.max)
>> +             max_freq = policy->user_policy.max;
>> +
>> +     if (policy->max != max_freq)
>> +             cpufreq_verify_within_limits(policy, 0, max_freq);
>> +
>> +     return 0;
>> +}
>> +
>> +/*
>> + * cpufreq cooling device callback functions are defined below
>> + */
>> +
>> +/**
>> + * cpufreq_get_max_state - callback function to get the max cooling state.
>> + * @cdev: thermal cooling device pointer.
>> + * @state: fill this variable with the max cooling state.
>> + */
>> +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;
>> +     unsigned int cpu;
>> +     struct cpufreq_frequency_table *table;
>> +
>> +     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;
>> +     }
>> +
>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> +                     continue;
>> +             i++;
>> +     }
>> +     if (i > 0) {
>> +             *state = --i;
>> +             ret = 0;
>> +     }
>> +
>> +return_get_max_state:
>> +     mutex_unlock(&cooling_cpufreq_lock);
>> +     return ret;
>> +}
>> +
>> +/**
>> + * cpufreq_get_cur_state - callback function to get the current cooling state.
>> + * @cdev: thermal cooling device pointer.
>> + * @state: fill this variable with the current cooling 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);
>> +
>
> as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
> use the current cpu frequency to get the REAL cooling state, rather than
> using a cached value.

Yes agreed , I will repost with your suggestion.

Thanks,
Amit
>
> thanks,
> rui
>
>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [lm-sensors] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17  7:58       ` Amit Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Kachhap @ 2012-08-17  8:10 UTC (permalink / raw)
  To: Zhang Rui
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park, linux-pm,
	Guenter Roeck, Andrew Morton

T24gMTcgQXVndXN0IDIwMTIgMTI6NTQsIFpoYW5nIFJ1aSA8cnVpLnpoYW5nQGludGVsLmNvbT4g
d3JvdGU6Cj4gT24g5ZubLCAyMDEyLTA4LTE2IGF0IDE3OjExICswNTMwLCBBbWl0IERhbmllbCBL
YWNoaGFwIHdyb3RlOgo+PiBUaGlzIHBhdGNoc2V0IGludHJvZHVjZXMgYSBuZXcgZ2VuZXJpYyBj
b29saW5nIGRldmljZSBiYXNlZCBvbiBjcHVmcmVxCj4+IHRoYXQgY2FuIGJlIHVzZWQgb24gbm9u
LUFDUEkgcGxhdGZvcm1zLiAgQXMgYSBwcm9vZiBvZiBjb25jZXB0LCB3ZSBoYXZlCj4+IGRyaXZl
cnMgZm9yIHRoZSBmb2xsb3dpbmcgcGxhdGZvcm1zIHVzaW5nIHRoaXMgbWVjaGFuaXNtIG5vdzoK
Pj4KPj4gICogU2Ftc3VuZyBFeHlub3MgKEV4eW5vczQgYW5kIEV4eW5vczUpIGluIHRoZSBjdXJy
ZW50IHBhdGNoc2V0Lgo+PiAgKiBGcmVlc2NhbGUgaS5NWCAoZ2l0Oi8vZ2l0LmxpbmFyby5vcmcv
cGVvcGxlL2FtaXRkYW5pZWxrL2xpbnV4LmdpdCBpbXg2cV90aGVybWFsKQo+Pgo+PiBUaGVyZSBp
cyBhIHNtYWxsIGNoYW5nZSBpbiBjcHVmcmVxIGNvb2xpbmcgcmVnaXN0cmF0aW9uIEFQSXMsIHNv
IGEgbWlub3IKPj4gY2hhbmdlIGlzIG5lZWRlZCBmb3IgRnJlZXNjYWxlIHBsYXRmb3Jtcy4KPj4K
Pj4gQnJpZWYgRGVzY3JpcHRpb246Cj4+Cj4+IDEpIFRoZSBnZW5lcmljIGNvb2xpbmcgZGV2aWNl
cyBjb2RlIGlzIHBsYWNlZCBpbnNpZGUgZHJpdmVyL3RoZXJtYWwvKgo+PiAgICBhcyBwbGFjaW5n
IGluc2lkZSBhY3BpIGZvbGRlciB3aWxsIG5lZWQgdW4tbmVjZXNzYXJ5IGVuYWJsaW5nIG9mIGFj
cGkKPj4gICAgY29kZS4gIFRoaXMgY29kZSBpcyBhcmNoaXRlY3R1cmUgaW5kZXBlbmRlbnQuCj4+
Cj4+IDIpIFRoaXMgcGF0Y2hzZXQgYWRkcyBnZW5lcmljIGNwdSBjb29saW5nIGxvdyBsZXZlbCBp
bXBsZW1lbnRhdGlvbgo+PiAgICB0aHJvdWdoIGZyZXF1ZW5jeSBjbGlwcGluZy4gIEluIGZ1dHVy
ZSwgb3RoZXIgY3B1IHJlbGF0ZWQgY29vbGluZwo+PiAgICBkZXZpY2VzIG1heSBiZSBhZGRlZCBo
ZXJlLiAgQW4gQUNQSSB2ZXJzaW9uIG9mIHRoaXMgYWxyZWFkeSBleGlzdHMKPj4gICAgKGRyaXZl
cnMvYWNwaS9wcm9jZXNzb3JfdGhlcm1hbC5jKSAuQnV0IHRoaXMgd2lsbCBiZSB1c2VmdWwgZm9y
Cj4+ICAgIHBsYXRmb3JtcyBsaWtlIEFSTSB1c2luZyB0aGUgZ2VuZXJpYyB0aGVybWFsIGludGVy
ZmFjZSBhbG9uZyB3aXRoIHRoZQo+PiAgICBnZW5lcmljIGNwdSBjb29saW5nIGRldmljZXMuICBU
aGUgY29vbGluZyBkZXZpY2UgcmVnaXN0cmF0aW9uIEFQSSdzCj4+ICAgIHJldHVybiBjb29saW5n
IGRldmljZSBwb2ludGVycyB3aGljaCBjYW4gYmUgZWFzaWx5IGJpbmRlZCB3aXRoIHRoZQo+PiAg
ICB0aGVybWFsIHpvbmUgdHJpcCBwb2ludHMuICBUaGUgaW1wb3J0YW50IEFQSXMgZXhwb3NlZCBh
cmUsCj4+Cj4+ICAgIGEpIHN0cnVjdCB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjcHVmcmVxX2Nv
b2xpbmdfcmVnaXN0ZXIoCj4+ICAgICAgICAgc3RydWN0IGNwdW1hc2sgKmNsaXBfY3B1cykKPj4g
ICAgYikgdm9pZCBjcHVmcmVxX2Nvb2xpbmdfdW5yZWdpc3RlcihzdHJ1Y3QgdGhlcm1hbF9jb29s
aW5nX2RldmljZSAqY2RldikKPj4KPj4gMykgU2Ftc3VuZyBleHlub3MgcGxhdGZvcm0gdGhlcm1h
bCBpbXBsZW1lbnRhdGlvbiBpcyBkb25lIHVzaW5nIHRoZQo+PiAgICBnZW5lcmljIGNwdSBjb29s
aW5nIEFQSXMgYW5kIHRoZSBuZXcgdHJpcCB0eXBlLiAgVGhlIHRlbXBlcmF0dXJlIHNlbnNvcgo+
PiAgICBkcml2ZXIgcHJlc2VudCBpbiB0aGUgaHdtb24gZm9sZGVyKHJlZ2lzdGVyZWQgYXMgaHdt
b24gZHJpdmVyKSBpcyBtb3ZlZAo+PiAgICB0byB0aGVybWFsIGZvbGRlciBhbmQgcmVnaXN0ZXJl
ZCBhcyBhIHRoZXJtYWwgZHJpdmVyLgo+Pgo+PiBBIHNpbXBsZSBkYXRhL2NvbnRyb2wgZmxvdyBk
aWFncmFtcyBpcyBzaG93biBiZWxvdywKPj4KPj4gQ29yZSBMaW51eCB0aGVybWFsIDwtLS0tLT4g
IEV4eW5vcyB0aGVybWFsIGludGVyZmFjZSA8LS0tLS0gVGVtcGVyYXR1cmUgU2Vuc29yCj4+ICAg
ICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8Cj4+ICAgICAgICAgIFx8LyAg
ICAgICAgICAgICAgICAgICAgICAgICAgICB8Cj4+ICAgQ3B1ZnJlcSBjb29saW5nIGRldmljZSA8
LS0tLS0tLS0tLS0tLS0tCj4+Cj4+IFRPRE86Cj4+ICpXaWxsIHNlbmQgdGhlIERUIGVuYWJsZW1l
bnQgcGF0Y2hlcyBsYXRlciBhZnRlciB0aGUgZHJpdmVyIGlzIG1lcmdlZC4KPj4KPj4gVGhpcyBw
YXRjaDoKPj4KPj4gQWRkIHN1cHBvcnQgZm9yIGdlbmVyaWMgY3B1IHRoZXJtYWwgY29vbGluZyBs
b3cgbGV2ZWwgaW1wbGVtZW50YXRpb25zCj4+IHVzaW5nIGZyZXF1ZW5jeSBzY2FsaW5nIHVwL2Rv
d24gYmFzZWQgb24gdGhlIHJlZ2lzdHJhdGlvbiBwYXJhbWV0ZXJzLgo+PiBEaWZmZXJlbnQgY3B1
IHJlbGF0ZWQgY29vbGluZyBkZXZpY2VzIGNhbiBiZSByZWdpc3RlcmVkIGJ5IHRoZSB1c2VyIGFu
ZAo+PiB0aGUgYmluZGluZyBvZiB0aGVzZSBjb29saW5nIGRldmljZXMgdG8gdGhlIGNvcnJlc3Bv
bmRpbmcgdHJpcCBwb2ludHMgY2FuCj4+IGJlIGVhc2lseSBkb25lIGFzIHRoZSByZWdpc3RyYXRp
b24gQVBJcyByZXR1cm4gdGhlIGNvb2xpbmcgZGV2aWNlIHBvaW50ZXIuCj4+IFRoZSB1c2VyIG9m
IHRoZXNlIEFQSXMgYXJlIHJlc3BvbnNpYmxlIGZvciBwYXNzaW5nIGNsaXBwaW5nIGZyZXF1ZW5j
eSAuCj4+IFRoZSBkcml2ZXJzIGNhbiBhbHNvIHJlZ2lzdGVyIHRvIHJlY2lldmUgbm90aWZpY2F0
aW9uIGFib3V0IGFueSBjb29saW5nCj4+IGFjdGlvbiBjYWxsZWQuCj4+Cj4+IFtha3BtQGxpbnV4
LWZvdW5kYXRpb24ub3JnOiBmaXggY29tbWVudCBsYXlvdXRdCj4+IFNpZ25lZC1vZmYtYnk6IEFt
aXQgRGFuaWVsIEthY2hoYXAgPGFtaXQua2FjaGhhcEBsaW5hcm8ub3JnPgo+PiBDYzogR3VlbnRl
ciBSb2VjayA8Z3VlbnRlci5yb2Vja0Blcmljc3Nvbi5jb20+Cj4+IENjOiBTYW5nV29vayBKdSA8
c3cuanVAc2Ftc3VuZy5jb20+Cj4+IENjOiBEdXJnYWRvc3MgPGR1cmdhZG9zcy5yQGludGVsLmNv
bT4KPj4gQ2M6IExlbiBCcm93biA8bGVuYkBrZXJuZWwub3JnPgo+PiBDYzogSmVhbiBEZWx2YXJl
IDxraGFsaUBsaW51eC1mci5vcmc+Cj4+IENjOiBLeXVuZ21pbiBQYXJrIDxrbXBhcmtAaW5mcmFk
ZWFkLm9yZz4KPj4gQ2M6IEt1a2ppbiBLaW0gPGtnZW5lLmtpbUBzYW1zdW5nLmNvbT4KPj4gU2ln
bmVkLW9mZi1ieTogQW5kcmV3IE1vcnRvbiA8YWtwbUBsaW51eC1mb3VuZGF0aW9uLm9yZz4KPj4g
U2lnbmVkLW9mZi1ieTogQW1pdCBEYW5pZWwgS2FjaGhhcCA8YW1pdC5kYW5pZWxAc2Ftc3VuZy5j
b20+Cj4+IC0tLQo+PiAgRG9jdW1lbnRhdGlvbi90aGVybWFsL2NwdS1jb29saW5nLWFwaS50eHQg
fCAgIDUyICsrKwo+PiAgZHJpdmVycy90aGVybWFsL0tjb25maWcgICAgICAgICAgICAgICAgICAg
fCAgIDExICsKPj4gIGRyaXZlcnMvdGhlcm1hbC9NYWtlZmlsZSAgICAgICAgICAgICAgICAgIHwg
ICAgMSArCj4+ICBkcml2ZXJzL3RoZXJtYWwvY3B1X2Nvb2xpbmcuYyAgICAgICAgICAgICB8ICA1
MTIgKysrKysrKysrKysrKysrKysrKysrKysrKysrKysKPj4gIGluY2x1ZGUvbGludXgvY3B1X2Nv
b2xpbmcuaCAgICAgICAgICAgICAgIHwgICA3OSArKysrKwo+PiAgNSBmaWxlcyBjaGFuZ2VkLCA2
NTUgaW5zZXJ0aW9ucygrKSwgMCBkZWxldGlvbnMoLSkKPj4gIGNyZWF0ZSBtb2RlIDEwMDY0NCBE
b2N1bWVudGF0aW9uL3RoZXJtYWwvY3B1LWNvb2xpbmctYXBpLnR4dAo+PiAgY3JlYXRlIG1vZGUg
MTAwNjQ0IGRyaXZlcnMvdGhlcm1hbC9jcHVfY29vbGluZy5jCj4+ICBjcmVhdGUgbW9kZSAxMDA2
NDQgaW5jbHVkZS9saW51eC9jcHVfY29vbGluZy5oCj4+Cj4+IGRpZmYgLS1naXQgYS9Eb2N1bWVu
dGF0aW9uL3RoZXJtYWwvY3B1LWNvb2xpbmctYXBpLnR4dCBiL0RvY3VtZW50YXRpb24vdGhlcm1h
bC9jcHUtY29vbGluZy1hcGkudHh0Cj4+IG5ldyBmaWxlIG1vZGUgMTAwNjQ0Cj4+IGluZGV4IDAw
MDAwMDAuLmExZjJhNmIKPj4gLS0tIC9kZXYvbnVsbAo+PiArKysgYi9Eb2N1bWVudGF0aW9uL3Ro
ZXJtYWwvY3B1LWNvb2xpbmctYXBpLnR4dAo+PiBAQCAtMCwwICsxLDUyIEBACj4+ICtDUFUgY29v
bGluZyBBUElzIEhvdyBUbwo+PiArPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0K
Pj4gKwo+PiArV3JpdHRlbiBieSBBbWl0IERhbmllbCBLYWNoaGFwIDxhbWl0LmthY2hoYXBAbGlu
YXJvLm9yZz4KPj4gKwo+PiArVXBkYXRlZDogMTIgTWF5IDIwMTIKPj4gKwo+PiArQ29weXJpZ2h0
IChjKSAgMjAxMiBTYW1zdW5nIEVsZWN0cm9uaWNzIENvLiwgTHRkKGh0dHA6Ly93d3cuc2Ftc3Vu
Zy5jb20pCj4+ICsKPj4gKzAuIEludHJvZHVjdGlvbgo+PiArCj4+ICtUaGUgZ2VuZXJpYyBjcHUg
Y29vbGluZyhmcmVxIGNsaXBwaW5nKSBwcm92aWRlcyByZWdpc3RyYXRpb24vdW5yZWdpc3RyYXRp
b24gQVBJcwo+PiArdG8gdGhlIGNhbGxlci4gVGhlIGJpbmRpbmcgb2YgdGhlIGNvb2xpbmcgZGV2
aWNlcyB0byB0aGUgdHJpcCBwb2ludCBpcyBsZWZ0IGZvcgo+PiArdGhlIHVzZXIuIFRoZSByZWdp
c3RyYXRpb24gQVBJcyByZXR1cm5zIHRoZSBjb29saW5nIGRldmljZSBwb2ludGVyLgo+PiArCj4+
ICsxLiBjcHUgY29vbGluZyBBUElzCj4+ICsKPj4gKzEuMSBjcHVmcmVxIHJlZ2lzdHJhdGlvbi91
bnJlZ2lzdHJhdGlvbiBBUElzCj4+ICsxLjEuMSBzdHJ1Y3QgdGhlcm1hbF9jb29saW5nX2Rldmlj
ZSAqY3B1ZnJlcV9jb29saW5nX3JlZ2lzdGVyKAo+PiArICAgICBzdHJ1Y3QgY3B1bWFzayAqY2xp
cF9jcHVzKQo+PiArCj4+ICsgICAgVGhpcyBpbnRlcmZhY2UgZnVuY3Rpb24gcmVnaXN0ZXJzIHRo
ZSBjcHVmcmVxIGNvb2xpbmcgZGV2aWNlIHdpdGggdGhlIG5hbWUKPj4gKyAgICAidGhlcm1hbC1j
cHVmcmVxLSV4Ii4gVGhpcyBhcGkgY2FuIHN1cHBvcnQgbXVsdGlwbGUgaW5zdGFuY2VzIG9mIGNw
dWZyZXEKPj4gKyAgICBjb29saW5nIGRldmljZXMuCj4+ICsKPj4gKyAgIGNsaXBfY3B1czogY3B1
bWFzayBvZiBjcHVzIHdoZXJlIHRoZSBmcmVxdWVuY3kgY29uc3RyYWludHMgd2lsbCBoYXBwZW4u
Cj4+ICsKPj4gKzEuMS4yIHZvaWQgY3B1ZnJlcV9jb29saW5nX3VucmVnaXN0ZXIoc3RydWN0IHRo
ZXJtYWxfY29vbGluZ19kZXZpY2UgKmNkZXYpCj4+ICsKPj4gKyAgICBUaGlzIGludGVyZmFjZSBm
dW5jdGlvbiB1bnJlZ2lzdGVycyB0aGUgInRoZXJtYWwtY3B1ZnJlcS0leCIgY29vbGluZyBkZXZp
Y2UuCj4+ICsKPj4gKyAgICBjZGV2OiBDb29saW5nIGRldmljZSBwb2ludGVyIHdoaWNoIGhhcyB0
byBiZSB1bnJlZ2lzdGVyZWQuCj4+ICsKPj4gKwo+PiArMS4yIENQVSBjb29saW5nIGFjdGlvbiBu
b3RpZmllciByZWdpc3Rlci91bnJlZ2lzdGVyIGludGVyZmFjZQo+PiArMS4yLjEgaW50IGNwdXRo
ZXJtX3JlZ2lzdGVyX25vdGlmaWVyKHN0cnVjdCBub3RpZmllcl9ibG9jayAqbmIsCj4+ICsgICAg
IHVuc2lnbmVkIGludCBsaXN0KQo+PiArCj4+ICsgICAgVGhpcyBpbnRlcmZhY2UgcmVnaXN0ZXJz
IGEgZHJpdmVyIHdpdGggY3B1IGNvb2xpbmcgbGF5ZXIuIFRoZSBkcml2ZXIgd2lsbAo+PiArICAg
IGJlIG5vdGlmaWVkIHdoZW4gYW55IGNwdSBjb29saW5nIGFjdGlvbiBpcyBjYWxsZWQuCj4+ICsK
Pj4gKyAgICBuYjogbm90aWZpZXIgZnVuY3Rpb24gdG8gcmVnaXN0ZXIKPj4gKyAgICBsaXN0OiBD
UFVGUkVRX0NPT0xJTkdfU1RBUlQgb3IgQ1BVRlJFUV9DT09MSU5HX1NUT1AKPj4gKwo+PiArMS4y
LjIgaW50IGNwdXRoZXJtX3VucmVnaXN0ZXJfbm90aWZpZXIoc3RydWN0IG5vdGlmaWVyX2Jsb2Nr
ICpuYiwKPj4gKyAgICAgdW5zaWduZWQgaW50IGxpc3QpCj4+ICsKPj4gKyAgICBUaGlzIGludGVy
ZmFjZSByZWdpc3RlcnMgYSBkcml2ZXIgd2l0aCBjcHUgY29vbGluZyBsYXllci4gVGhlIGRyaXZl
ciB3aWxsCj4+ICsgICAgYmUgbm90aWZpZWQgd2hlbiBhbnkgY3B1IGNvb2xpbmcgYWN0aW9uIGlz
IGNhbGxlZC4KPj4gKwo+PiArICAgIG5iOiBub3RpZmllciBmdW5jdGlvbiB0byByZWdpc3Rlcgo+
PiArICAgIGxpc3Q6IENQVUZSRVFfQ09PTElOR19TVEFSVCBvciBDUFVGUkVRX0NPT0xJTkdfU1RP
UAo+Cj4gd2hhdCBhcmUgdGhlc2UgdHdvIEFQSXMgdXNlZCBmb3I/Cj4gSSBkaWQgbm90IHNlZSB0
aGV5IGFyZSB1c2VkIGluIHlvdXIgcGF0Y2ggc2V0LCBkbyBJIG1pc3Mgc29tZXRoaW5nPwpObyBj
dXJyZW50bHkgdGhleSBhcmUgbm90IHVzZWQgYnkgbXkgcGF0Y2hlcy4gSSBhZGRlZCB0aGVtIG9u
IHJlcXVlc3QKZnJvbSBFZHVhcmRvIGFuZCBvdGhlcnMKPgo+PiBkaWZmIC0tZ2l0IGEvZHJpdmVy
cy90aGVybWFsL0tjb25maWcgYi9kcml2ZXJzL3RoZXJtYWwvS2NvbmZpZwo+PiBpbmRleCA3ZGQ4
YzM0Li45OTYwMDNiIDEwMDY0NAo+PiAtLS0gYS9kcml2ZXJzL3RoZXJtYWwvS2NvbmZpZwo+PiAr
KysgYi9kcml2ZXJzL3RoZXJtYWwvS2NvbmZpZwo+PiBAQCAtMTksNiArMTksMTcgQEAgY29uZmln
IFRIRVJNQUxfSFdNT04KPj4gICAgICAgZGVwZW5kcyBvbiBIV01PTj15IHx8IEhXTU9OPVRIRVJN
QUwKPj4gICAgICAgZGVmYXVsdCB5Cj4+Cj4+ICtjb25maWcgQ1BVX1RIRVJNQUwKPj4gKyAgICAg
Ym9vbCAiZ2VuZXJpYyBjcHUgY29vbGluZyBzdXBwb3J0Igo+PiArICAgICBkZXBlbmRzIG9uIFRI
RVJNQUwgJiYgQ1BVX0ZSRVEKPj4gKyAgICAgaGVscAo+PiArICAgICAgIFRoaXMgaW1wbGVtZW50
cyB0aGUgZ2VuZXJpYyBjcHUgY29vbGluZyBtZWNoYW5pc20gdGhyb3VnaCBmcmVxdWVuY3kKPj4g
KyAgICAgICByZWR1Y3Rpb24sIGNwdSBob3RwbHVnIGFuZCBhbnkgb3RoZXIgd2F5cyBvZiByZWR1
Y2luZyB0ZW1wZXJhdHVyZS4gQW4KPj4gKyAgICAgICBBQ1BJIHZlcnNpb24gb2YgdGhpcyBhbHJl
YWR5IGV4aXN0cyhkcml2ZXJzL2FjcGkvcHJvY2Vzc29yX3RoZXJtYWwuYykuCj4+ICsgICAgICAg
VGhpcyB3aWxsIGJlIHVzZWZ1bCBmb3IgcGxhdGZvcm1zIHVzaW5nIHRoZSBnZW5lcmljIHRoZXJt
YWwgaW50ZXJmYWNlCj4+ICsgICAgICAgYW5kIG5vdCB0aGUgQUNQSSBpbnRlcmZhY2UuCj4+ICsg
ICAgICAgSWYgeW91IHdhbnQgdGhpcyBzdXBwb3J0LCB5b3Ugc2hvdWxkIHNheSBZIGhlcmUuCj4+
ICsKPj4gIGNvbmZpZyBTUEVBUl9USEVSTUFMCj4+ICAgICAgIGJvb2wgIlNQRUFyIHRoZXJtYWwg
c2Vuc29yIGRyaXZlciIKPj4gICAgICAgZGVwZW5kcyBvbiBUSEVSTUFMCj4+IGRpZmYgLS1naXQg
YS9kcml2ZXJzL3RoZXJtYWwvTWFrZWZpbGUgYi9kcml2ZXJzL3RoZXJtYWwvTWFrZWZpbGUKPj4g
aW5kZXggZmQ5MzY5YS4uYWFlNTlhZCAxMDA2NDQKPj4gLS0tIGEvZHJpdmVycy90aGVybWFsL01h
a2VmaWxlCj4+ICsrKyBiL2RyaXZlcnMvdGhlcm1hbC9NYWtlZmlsZQo+PiBAQCAtMyw1ICszLDYg
QEAKPj4gICMKPj4KPj4gIG9iai0kKENPTkZJR19USEVSTUFMKSAgICAgICAgICAgICAgICArPSB0
aGVybWFsX3N5cy5vCj4+ICtvYmotJChDT05GSUdfQ1BVX1RIRVJNQUwpICAgICAgICAgICAgKz0g
Y3B1X2Nvb2xpbmcubwo+PiAgb2JqLSQoQ09ORklHX1NQRUFSX1RIRVJNQUwpICAgICAgICAgICs9
IHNwZWFyX3RoZXJtYWwubwo+PiAgb2JqLSQoQ09ORklHX1JDQVJfVEhFUk1BTCkgICArPSByY2Fy
X3RoZXJtYWwubwo+PiBkaWZmIC0tZ2l0IGEvZHJpdmVycy90aGVybWFsL2NwdV9jb29saW5nLmMg
Yi9kcml2ZXJzL3RoZXJtYWwvY3B1X2Nvb2xpbmcuYwo+PiBuZXcgZmlsZSBtb2RlIDEwMDY0NAo+
PiBpbmRleCAwMDAwMDAwLi5jNDJlNTU3Cj4+IC0tLSAvZGV2L251bGwKPj4gKysrIGIvZHJpdmVy
cy90aGVybWFsL2NwdV9jb29saW5nLmMKPj4gQEAgLTAsMCArMSw1MTIgQEAKPj4gKy8qCj4+ICsg
KiAgbGludXgvZHJpdmVycy90aGVybWFsL2NwdV9jb29saW5nLmMKPj4gKyAqCj4+ICsgKiAgQ29w
eXJpZ2h0IChDKSAyMDEyICAgICAgIFNhbXN1bmcgRWxlY3Ryb25pY3MgQ28uLCBMdGQoaHR0cDov
L3d3dy5zYW1zdW5nLmNvbSkKPj4gKyAqICBDb3B5cmlnaHQgKEMpIDIwMTIgIEFtaXQgRGFuaWVs
IDxhbWl0LmthY2hoYXBAbGluYXJvLm9yZz4KPj4gKyAqCj4+ICsgKiB+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fgo+PiArICogIFRoaXMgcHJvZ3JhbSBpcyBmcmVlIHNvZnR3YXJlOyB5b3UgY2FuIHJlZGlzdHJp
YnV0ZSBpdCBhbmQvb3IgbW9kaWZ5Cj4+ICsgKiAgaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBH
TlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkKPj4gKyAqICB0aGUgRnJl
ZSBTb2Z0d2FyZSBGb3VuZGF0aW9uOyB2ZXJzaW9uIDIgb2YgdGhlIExpY2Vuc2UuCj4+ICsgKgo+
PiArICogIFRoaXMgcHJvZ3JhbSBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdp
bGwgYmUgdXNlZnVsLCBidXQKPj4gKyAqICBXSVRIT1VUIEFOWSBXQVJSQU5UWTsgd2l0aG91dCBl
dmVuIHRoZSBpbXBsaWVkIHdhcnJhbnR5IG9mCj4+ICsgKiAgTUVSQ0hBTlRBQklMSVRZIG9yIEZJ
VE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFLiAgU2VlIHRoZSBHTlUKPj4gKyAqICBHZW5l
cmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuCj4+ICsgKgo+PiArICogIFlvdSBz
aG91bGQgaGF2ZSByZWNlaXZlZCBhIGNvcHkgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNl
bnNlIGFsb25nCj4+ICsgKiAgd2l0aCB0aGlzIHByb2dyYW07IGlmIG5vdCwgd3JpdGUgdG8gdGhl
IEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgSW5jLiwKPj4gKyAqICA1OSBUZW1wbGUgUGxhY2Us
IFN1aXRlIDMzMCwgQm9zdG9uLCBNQSAwMjExMS0xMzA3IFVTQS4KPj4gKyAqCj4+ICsgKiB+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fgo+PiArICovCj4+ICsjaW5jbHVkZSA8bGludXgva2VybmVsLmg+Cj4+ICsj
aW5jbHVkZSA8bGludXgvbW9kdWxlLmg+Cj4+ICsjaW5jbHVkZSA8bGludXgvdGhlcm1hbC5oPgo+
PiArI2luY2x1ZGUgPGxpbnV4L3BsYXRmb3JtX2RldmljZS5oPgo+PiArI2luY2x1ZGUgPGxpbnV4
L2NwdWZyZXEuaD4KPj4gKyNpbmNsdWRlIDxsaW51eC9lcnIuaD4KPj4gKyNpbmNsdWRlIDxsaW51
eC9zbGFiLmg+Cj4+ICsjaW5jbHVkZSA8bGludXgvY3B1Lmg+Cj4+ICsjaW5jbHVkZSA8bGludXgv
Y3B1X2Nvb2xpbmcuaD4KPj4gKwo+PiArLyoqCj4+ICsgKiBzdHJ1Y3QgY3B1ZnJlcV9jb29saW5n
X2RldmljZQo+PiArICogQGlkOiB1bmlxdWUgaW50ZWdlciB2YWx1ZSBjb3JyZXNwb25kaW5nIHRv
IGVhY2ggY3B1ZnJlcV9jb29saW5nX2RldmljZQo+PiArICogICByZWdpc3RlcmVkLgo+PiArICog
QGNvb2xfZGV2OiB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlIHBvaW50ZXIgdG8ga2VlcCB0cmFjayBv
ZiB0aGUgdGhlCj4+ICsgKiAgIGVnaXN0ZXJlZCBjb29saW5nIGRldmljZS4KPj4gKyAqIEBjcHVm
cmVxX3N0YXRlOiBpbnRlZ2VyIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgY3VycmVudCBzdGF0ZSBv
ZiBjcHVmcmVxCj4+ICsgKiAgIGNvb2xpbmcgZGV2aWNlcy4KPj4gKyAqIEBjcHVmcmVxX3ZhbDog
aW50ZWdlciB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIGFic29sdXRlIHZhbHVlIG9mIHRoZSBjbGlw
cGVkCj4+ICsgKiAgIGZyZXF1ZW5jeS4KPj4gKyAqIEBhbGxvd2VkX2NwdXM6IGFsbCB0aGUgY3B1
cyBpbnZvbHZlZCBmb3IgdGhpcyBjcHVmcmVxX2Nvb2xpbmdfZGV2aWNlLgo+PiArICogQG5vZGU6
IGxpc3RfaGVhZCB0byBsaW5rIGFsbCBjcHVmcmVxX2Nvb2xpbmdfZGV2aWNlIHRvZ2V0aGVyLgo+
PiArICoKPj4gKyAqIFRoaXMgc3RydWN0dXJlIGlzIHJlcXVpcmVkIGZvciBrZWVwaW5nIGluZm9y
bWF0aW9uIG9mIGVhY2gKPj4gKyAqIGNwdWZyZXFfY29vbGluZ19kZXZpY2UgcmVnaXN0ZXJlZCBh
cyBhIGxpc3Qgd2hvc2UgaGVhZCBpcyByZXByZXNlbnRlZCBieQo+PiArICogY29vbGluZ19jcHVm
cmVxX2xpc3QuIEluIG9yZGVyIHRvIHByZXZlbnQgY29ycnVwdGlvbiBvZiB0aGlzIGxpc3QgYQo+
PiArICogbXV0ZXggbG9jayBjb29saW5nX2NwdWZyZXFfbG9jayBpcyB1c2VkLgo+PiArICovCj4+
ICtzdHJ1Y3QgY3B1ZnJlcV9jb29saW5nX2RldmljZSB7Cj4+ICsgICAgIGludCBpZDsKPj4gKyAg
ICAgc3RydWN0IHRoZXJtYWxfY29vbGluZ19kZXZpY2UgKmNvb2xfZGV2Owo+PiArICAgICB1bnNp
Z25lZCBpbnQgY3B1ZnJlcV9zdGF0ZTsKPj4gKyAgICAgdW5zaWduZWQgaW50IGNwdWZyZXFfdmFs
Owo+PiArICAgICBzdHJ1Y3QgY3B1bWFzayBhbGxvd2VkX2NwdXM7Cj4+ICsgICAgIHN0cnVjdCBs
aXN0X2hlYWQgbm9kZTsKPj4gK307Cj4+ICtzdGF0aWMgTElTVF9IRUFEKGNvb2xpbmdfY3B1ZnJl
cV9saXN0KTsKPj4gK3N0YXRpYyBERUZJTkVfSURSKGNwdWZyZXFfaWRyKTsKPj4gKwo+PiArc3Rh
dGljIHN0cnVjdCBtdXRleCBjb29saW5nX2NwdWZyZXFfbG9jazsKPj4gKwo+PiArLyogbm90aWZ5
X3RhYmxlIHBhc3NlcyB2YWx1ZSB0byB0aGUgQ1BVRlJFUV9BREpVU1QgY2FsbGJhY2sgZnVuY3Rp
b24uICovCj4+ICsjZGVmaW5lIE5PVElGWV9JTlZBTElEIE5VTEwKPj4gK3N0cnVjdCBjcHVmcmVx
X2Nvb2xpbmdfZGV2aWNlICpub3RpZnlfZGV2aWNlOwo+PiArCj4+ICsvKiBIZWFkIG9mIHRoZSBi
bG9ja2luZyBub3RpZmllciBjaGFpbiB0byBpbmZvcm0gYWJvdXQgZnJlcXVlbmN5IGNsYW1waW5n
ICovCj4+ICtzdGF0aWMgQkxPQ0tJTkdfTk9USUZJRVJfSEVBRChjcHV0aGVybV9zdGF0ZV9ub3Rp
Zmllcl9saXN0KTsKPj4gKwo+PiArLyoqCj4+ICsgKiBnZXRfaWRyIC0gZnVuY3Rpb24gdG8gZ2V0
IGEgdW5pcXVlIGlkLgo+PiArICogQGlkcjogc3RydWN0IGlkciAqIGhhbmRsZSB1c2VkIHRvIGNy
ZWF0ZSBhIGlkLgo+PiArICogQGlkOiBpbnQgKiB2YWx1ZSBnZW5lcmF0ZWQgYnkgdGhpcyBmdW5j
dGlvbi4KPj4gKyAqLwo+PiArc3RhdGljIGludCBnZXRfaWRyKHN0cnVjdCBpZHIgKmlkciwgaW50
ICppZCkKPj4gK3sKPj4gKyAgICAgaW50IGVycjsKPj4gK2FnYWluOgo+PiArICAgICBpZiAodW5s
aWtlbHkoaWRyX3ByZV9nZXQoaWRyLCBHRlBfS0VSTkVMKSA9PSAwKSkKPj4gKyAgICAgICAgICAg
ICByZXR1cm4gLUVOT01FTTsKPj4gKwo+PiArICAgICBtdXRleF9sb2NrKCZjb29saW5nX2NwdWZy
ZXFfbG9jayk7Cj4+ICsgICAgIGVyciA9IGlkcl9nZXRfbmV3KGlkciwgTlVMTCwgaWQpOwo+PiAr
ICAgICBtdXRleF91bmxvY2soJmNvb2xpbmdfY3B1ZnJlcV9sb2NrKTsKPj4gKwo+PiArICAgICBp
ZiAodW5saWtlbHkoZXJyID09IC1FQUdBSU4pKQo+PiArICAgICAgICAgICAgIGdvdG8gYWdhaW47
Cj4+ICsgICAgIGVsc2UgaWYgKHVubGlrZWx5KGVycikpCj4+ICsgICAgICAgICAgICAgcmV0dXJu
IGVycjsKPj4gKwo+PiArICAgICAqaWQgPSAqaWQgJiBNQVhfSURfTUFTSzsKPj4gKyAgICAgcmV0
dXJuIDA7Cj4+ICt9Cj4+ICsKPj4gKy8qKgo+PiArICogcmVsZWFzZV9pZHIgLSBmdW5jdGlvbiB0
byBmcmVlIHRoZSB1bmlxdWUgaWQuCj4+ICsgKiBAaWRyOiBzdHJ1Y3QgaWRyICogaGFuZGxlIHVz
ZWQgZm9yIGNyZWF0aW5nIHRoZSBpZC4KPj4gKyAqIEBpZDogaW50IHZhbHVlIHJlcHJlc2VudGlu
ZyB0aGUgdW5pcXVlIGlkLgo+PiArICovCj4+ICtzdGF0aWMgdm9pZCByZWxlYXNlX2lkcihzdHJ1
Y3QgaWRyICppZHIsIGludCBpZCkKPj4gK3sKPj4gKyAgICAgbXV0ZXhfbG9jaygmY29vbGluZ19j
cHVmcmVxX2xvY2spOwo+PiArICAgICBpZHJfcmVtb3ZlKGlkciwgaWQpOwo+PiArICAgICBtdXRl
eF91bmxvY2soJmNvb2xpbmdfY3B1ZnJlcV9sb2NrKTsKPj4gK30KPj4gKwo+PiArLyoqCj4+ICsg
KiBjcHV0aGVybV9yZWdpc3Rlcl9ub3RpZmllciAtIFJlZ2lzdGVyIGEgbm90aWZpZXIgd2l0aCBj
cHUgY29vbGluZyBpbnRlcmZhY2UuCj4+ICsgKiBAbmI6ICAgICAgc3RydWN0IG5vdGlmaWVyX2Js
b2NrICogd2l0aCBjYWxsYmFjayBpbmZvLgo+PiArICogQGxpc3Q6IGludGVnZXIgdmFsdWUgZm9y
IHdoaWNoIG5vdGlmaWNhdGlvbiBpcyBuZWVkZWQuIHBvc3NpYmxlIHZhbHVlcyBhcmUKPj4gKyAq
ICAgQ1BVRlJFUV9DT09MSU5HX1NUQVJUIGFuZCBDUFVGUkVRX0NPT0xJTkdfU1RPUC4KPj4gKyAq
Cj4+ICsgKiBUaGlzIGV4cG9ydGVkIGZ1bmN0aW9uIHJlZ2lzdGVycyBhIGRyaXZlciB3aXRoIGNw
dSBjb29saW5nIGxheWVyLiBUaGUgZHJpdmVyCj4+ICsgKiB3aWxsIGJlIG5vdGlmaWVkIHdoZW4g
YW55IGNwdSBjb29saW5nIGFjdGlvbiBpcyBjYWxsZWQuCj4+ICsgKi8KPj4gK2ludCBjcHV0aGVy
bV9yZWdpc3Rlcl9ub3RpZmllcihzdHJ1Y3Qgbm90aWZpZXJfYmxvY2sgKm5iLCB1bnNpZ25lZCBp
bnQgbGlzdCkKPj4gK3sKPj4gKyAgICAgaW50IHJldCA9IDA7Cj4+ICsKPj4gKyAgICAgc3dpdGNo
IChsaXN0KSB7Cj4+ICsgICAgIGNhc2UgQ1BVRlJFUV9DT09MSU5HX1NUQVJUOgo+PiArICAgICBj
YXNlIENQVUZSRVFfQ09PTElOR19TVE9QOgo+PiArICAgICAgICAgICAgIHJldCA9IGJsb2NraW5n
X25vdGlmaWVyX2NoYWluX3JlZ2lzdGVyKAo+PiArICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAmY3B1dGhlcm1fc3RhdGVfbm90aWZpZXJfbGlzdCwgbmIpOwo+PiArICAgICAgICAgICAgIGJy
ZWFrOwo+PiArICAgICBkZWZhdWx0Ogo+PiArICAgICAgICAgICAgIHJldCA9IC1FSU5WQUw7Cj4+
ICsgICAgIH0KPj4gKyAgICAgcmV0dXJuIHJldDsKPj4gK30KPj4gK0VYUE9SVF9TWU1CT0woY3B1
dGhlcm1fcmVnaXN0ZXJfbm90aWZpZXIpOwo+PiArCj4+ICsvKioKPj4gKyAqIGNwdXRoZXJtX3Vu
cmVnaXN0ZXJfbm90aWZpZXIgLSBVbi1yZWdpc3RlciBhIG5vdGlmaWVyLgo+PiArICogQG5iOiAg
ICAgIHN0cnVjdCBub3RpZmllcl9ibG9jayAqIHdpdGggY2FsbGJhY2sgaW5mby4KPj4gKyAqIEBs
aXN0OiBpbnRlZ2VyIHZhbHVlIGZvciB3aGljaCBub3RpZmljYXRpb24gaXMgbmVlZGVkLiB2YWx1
ZXMgcG9zc2libGUgYXJlCj4+ICsgKiAgIENQVUZSRVFfQ09PTElOR19TVEFSVCBvciBDUFVGUkVR
X0NPT0xJTkdfU1RPUC4KPj4gKyAqCj4+ICsgKiBUaGlzIGV4cG9ydGVkIGZ1bmN0aW9uIHVuLXJl
Z2lzdGVycyBhIGRyaXZlciB3aXRoIGNwdSBjb29saW5nIGxheWVyLgo+PiArICovCj4+ICtpbnQg
Y3B1dGhlcm1fdW5yZWdpc3Rlcl9ub3RpZmllcihzdHJ1Y3Qgbm90aWZpZXJfYmxvY2sgKm5iLCB1
bnNpZ25lZCBpbnQgbGlzdCkKPj4gK3sKPj4gKyAgICAgaW50IHJldCA9IDA7Cj4+ICsKPj4gKyAg
ICAgc3dpdGNoIChsaXN0KSB7Cj4+ICsgICAgIGNhc2UgQ1BVRlJFUV9DT09MSU5HX1NUQVJUOgo+
PiArICAgICBjYXNlIENQVUZSRVFfQ09PTElOR19TVE9QOgo+PiArICAgICAgICAgICAgIHJldCA9
IGJsb2NraW5nX25vdGlmaWVyX2NoYWluX3VucmVnaXN0ZXIoCj4+ICsgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICZjcHV0aGVybV9zdGF0ZV9ub3RpZmllcl9saXN0LCBuYik7Cj4+ICsgICAg
ICAgICAgICAgYnJlYWs7Cj4+ICsgICAgIGRlZmF1bHQ6Cj4+ICsgICAgICAgICAgICAgcmV0ID0g
LUVJTlZBTDsKPj4gKyAgICAgfQo+PiArICAgICByZXR1cm4gcmV0Owo+PiArfQo+PiArRVhQT1JU
X1NZTUJPTChjcHV0aGVybV91bnJlZ2lzdGVyX25vdGlmaWVyKTsKPj4gKwo+PiArLyogQmVsb3cg
Y29kZSBkZWZpbmVzIGZ1bmN0aW9ucyB0byBiZSB1c2VkIGZvciBjcHVmcmVxIGFzIGNvb2xpbmcg
ZGV2aWNlICovCj4+ICsKPj4gKy8qKgo+PiArICogaXNfY3B1ZnJlcV92YWxpZCAtIGZ1bmN0aW9u
IHRvIGNoZWNrIGlmIGEgY3B1IGhhcyBmcmVxdWVuY3kgdHJhbnNpdGlvbiBwb2xpY3kuCj4+ICsg
KiBAY3B1OiBjcHUgZm9yIHdoaWNoIGNoZWNrIGlzIG5lZWRlZC4KPj4gKyAqLwo+PiArc3RhdGlj
IGludCBpc19jcHVmcmVxX3ZhbGlkKGludCBjcHUpCj4+ICt7Cj4+ICsgICAgIHN0cnVjdCBjcHVm
cmVxX3BvbGljeSBwb2xpY3k7Cj4+ICsgICAgIHJldHVybiAhY3B1ZnJlcV9nZXRfcG9saWN5KCZw
b2xpY3ksIGNwdSk7Cj4+ICt9Cj4+ICsKPj4gKy8qKgo+PiArICogZ2V0X2NwdV9mcmVxdWVuY3kg
LSBnZXQgdGhlIGFic29sdXRlIHZhbHVlIG9mIGZyZXF1ZW5jeSBmcm9tIGxldmVsLgo+PiArICog
QGNwdTogY3B1IGZvciB3aGljaCBmcmVxdWVuY3kgaXMgZmV0Y2hlZC4KPj4gKyAqIEBsZXZlbDog
bGV2ZWwgb2YgZnJlcXVlbmN5IG9mIHRoZSBDUFUKPj4gKyAqICAgZS5nIGxldmVsPTEgLS0+IDFz
dCBNQVggRlJFUSwgTEVWRUw9MiAtLS0+IDJuZCBNQVggRlJFUSwgLi4uLiBldGMKPj4gKyAqLwo+
PiArc3RhdGljIHVuc2lnbmVkIGludCBnZXRfY3B1X2ZyZXF1ZW5jeSh1bnNpZ25lZCBpbnQgY3B1
LCB1bnNpZ25lZCBsb25nIGxldmVsKQo+PiArewo+PiArICAgICBpbnQgcmV0ID0gMCwgaSA9IDA7
Cj4+ICsgICAgIHVuc2lnbmVkIGxvbmcgbGV2ZWxfaW5kZXg7Cj4+ICsgICAgIGJvb2wgZGVzY2Vu
ZCA9IGZhbHNlOwo+PiArICAgICBzdHJ1Y3QgY3B1ZnJlcV9mcmVxdWVuY3lfdGFibGUgKnRhYmxl
ID0KPj4gKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjcHVmcmVxX2ZyZXF1
ZW5jeV9nZXRfdGFibGUoY3B1KTsKPj4gKyAgICAgaWYgKCF0YWJsZSkKPj4gKyAgICAgICAgICAg
ICByZXR1cm4gcmV0Owo+PiArCj4+ICsgICAgIHdoaWxlICh0YWJsZVtpXS5mcmVxdWVuY3kgIT0g
Q1BVRlJFUV9UQUJMRV9FTkQpIHsKPj4gKyAgICAgICAgICAgICBpZiAodGFibGVbaV0uZnJlcXVl
bmN5ID09IENQVUZSRVFfRU5UUllfSU5WQUxJRCkKPj4gKyAgICAgICAgICAgICAgICAgICAgIGNv
bnRpbnVlOwo+PiArCj4+ICsgICAgICAgICAgICAgLypjaGVjayBpZiB0YWJsZSBpbiBhc2NlbmRp
bmcgb3IgZGVzY2VuZGluZyBvcmRlciovCj4+ICsgICAgICAgICAgICAgaWYgKCh0YWJsZVtpICsg
MV0uZnJlcXVlbmN5ICE9IENQVUZSRVFfVEFCTEVfRU5EKSAmJgo+PiArICAgICAgICAgICAgICAg
ICAgICAgKHRhYmxlW2kgKyAxXS5mcmVxdWVuY3kgPCB0YWJsZVtpXS5mcmVxdWVuY3kpCj4+ICsg
ICAgICAgICAgICAgICAgICAgICAmJiAhZGVzY2VuZCkgewo+PiArICAgICAgICAgICAgICAgICAg
ICAgZGVzY2VuZCA9IHRydWU7Cj4+ICsgICAgICAgICAgICAgfQo+PiArCj4+ICsgICAgICAgICAg
ICAgLypyZXR1cm4gaWYgbGV2ZWwgbWF0Y2hlZCBhbmQgdGFibGUgaW4gZGVzY2VuZGluZyBvcmRl
ciovCj4+ICsgICAgICAgICAgICAgaWYgKGRlc2NlbmQgJiYgaSA9PSBsZXZlbCkKPj4gKyAgICAg
ICAgICAgICAgICAgICAgIHJldHVybiB0YWJsZVtpXS5mcmVxdWVuY3k7Cj4+ICsgICAgICAgICAg
ICAgaSsrOwo+PiArICAgICB9Cj4+ICsgICAgIGktLTsKPj4gKwo+PiArICAgICBpZiAobGV2ZWwg
PiBpIHx8IGRlc2NlbmQpCj4+ICsgICAgICAgICAgICAgcmV0dXJuIHJldDsKPj4gKyAgICAgbGV2
ZWxfaW5kZXggPSBpIC0gbGV2ZWw7Cj4+ICsKPj4gKyAgICAgLypTY2FuIHRoZSB0YWJsZSBpbiBy
ZXZlcnNlIG9yZGVyIGFuZCBtYXRjaCB0aGUgbGV2ZWwqLwo+PiArICAgICB3aGlsZSAoaSA+PSAw
KSB7Cj4+ICsgICAgICAgICAgICAgaWYgKHRhYmxlW2ldLmZyZXF1ZW5jeSA9PSBDUFVGUkVRX0VO
VFJZX0lOVkFMSUQpCj4+ICsgICAgICAgICAgICAgICAgICAgICBjb250aW51ZTsKPj4gKyAgICAg
ICAgICAgICAvKnJldHVybiBpZiBsZXZlbCBtYXRjaGVkKi8KPj4gKyAgICAgICAgICAgICBpZiAo
aSA9PSBsZXZlbF9pbmRleCkKPj4gKyAgICAgICAgICAgICAgICAgICAgIHJldHVybiB0YWJsZVtp
XS5mcmVxdWVuY3k7Cj4+ICsgICAgICAgICAgICAgaS0tOwo+PiArICAgICB9Cj4+ICsgICAgIHJl
dHVybiByZXQ7Cj4+ICt9Cj4+ICsKPj4gKy8qKgo+PiArICogY3B1ZnJlcV9hcHBseV9jb29saW5n
IC0gZnVuY3Rpb24gdG8gYXBwbHkgZnJlcXVlbmN5IGNsaXBwaW5nLgo+PiArICogQGNwdWZyZXFf
ZGV2aWNlOiBjcHVmcmVxX2Nvb2xpbmdfZGV2aWNlIHBvaW50ZXIgY29udGFpbmluZyBmcmVxdWVu
Y3kKPj4gKyAqICAgY2xpcHBpbmcgZGF0YS4KPj4gKyAqIEBjb29saW5nX3N0YXRlOiB2YWx1ZSBv
ZiB0aGUgY29vbGluZyBzdGF0ZS4KPj4gKyAqLwo+PiArc3RhdGljIGludCBjcHVmcmVxX2FwcGx5
X2Nvb2xpbmcoc3RydWN0IGNwdWZyZXFfY29vbGluZ19kZXZpY2UgKmNwdWZyZXFfZGV2aWNlLAo+
PiArICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bnNpZ25lZCBsb25nIGNvb2xpbmdfc3Rh
dGUpCj4+ICt7Cj4+ICsgICAgIHVuc2lnbmVkIGludCBldmVudCwgY3B1aWQsIGNsaXBfZnJlcTsK
Pj4gKyAgICAgc3RydWN0IGNwdW1hc2sgKm1hc2tQdHIgPSAmY3B1ZnJlcV9kZXZpY2UtPmFsbG93
ZWRfY3B1czsKPj4gKyAgICAgdW5zaWduZWQgaW50IGNwdSA9IGNwdW1hc2tfYW55KG1hc2tQdHIp
Owo+PiArCj4+ICsKPj4gKyAgICAgLyogQ2hlY2sgaWYgdGhlIG9sZCBjb29saW5nIGFjdGlvbiBp
cyBzYW1lIGFzIG5ldyBjb29saW5nIGFjdGlvbiAqLwo+PiArICAgICBpZiAoY3B1ZnJlcV9kZXZp
Y2UtPmNwdWZyZXFfc3RhdGUgPT0gY29vbGluZ19zdGF0ZSkKPj4gKyAgICAgICAgICAgICByZXR1
cm4gMDsKPj4gKwo+PiArICAgICBjbGlwX2ZyZXEgPSBnZXRfY3B1X2ZyZXF1ZW5jeShjcHUsIGNv
b2xpbmdfc3RhdGUpOwo+PiArICAgICBpZiAoIWNsaXBfZnJlcSkKPj4gKyAgICAgICAgICAgICBy
ZXR1cm4gLUVJTlZBTDsKPj4gKwo+PiArICAgICBjcHVmcmVxX2RldmljZS0+Y3B1ZnJlcV9zdGF0
ZSA9IGNvb2xpbmdfc3RhdGU7Cj4+ICsgICAgIGNwdWZyZXFfZGV2aWNlLT5jcHVmcmVxX3ZhbCA9
IGNsaXBfZnJlcTsKPj4gKyAgICAgbm90aWZ5X2RldmljZSA9IGNwdWZyZXFfZGV2aWNlOwo+PiAr
Cj4+ICsgICAgIGlmIChjb29saW5nX3N0YXRlICE9IDApCj4+ICsgICAgICAgICAgICAgZXZlbnQg
PSBDUFVGUkVRX0NPT0xJTkdfU1RBUlQ7Cj4+ICsgICAgIGVsc2UKPj4gKyAgICAgICAgICAgICBl
dmVudCA9IENQVUZSRVFfQ09PTElOR19TVE9QOwo+PiArCj4+ICsgICAgIGJsb2NraW5nX25vdGlm
aWVyX2NhbGxfY2hhaW4oJmNwdXRoZXJtX3N0YXRlX25vdGlmaWVyX2xpc3QsCj4+ICsgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudCwgJmNsaXBfZnJlcSk7
Cj4+ICsKPj4gKyAgICAgZm9yX2VhY2hfY3B1KGNwdWlkLCBtYXNrUHRyKSB7Cj4+ICsgICAgICAg
ICAgICAgaWYgKGlzX2NwdWZyZXFfdmFsaWQoY3B1aWQpKQo+PiArICAgICAgICAgICAgICAgICAg
ICAgY3B1ZnJlcV91cGRhdGVfcG9saWN5KGNwdWlkKTsKPj4gKyAgICAgfQo+PiArCj4+ICsgICAg
IG5vdGlmeV9kZXZpY2UgPSBOT1RJRllfSU5WQUxJRDsKPj4gKwo+PiArICAgICByZXR1cm4gMDsK
Pj4gK30KPj4gKwo+PiArLyoqCj4+ICsgKiBjcHVmcmVxX3RoZXJtYWxfbm90aWZpZXIgLSBub3Rp
ZmllciBjYWxsYmFjayBmb3IgY3B1ZnJlcSBwb2xpY3kgY2hhbmdlLgo+PiArICogQG5iOiAgICAg
IHN0cnVjdCBub3RpZmllcl9ibG9jayAqIHdpdGggY2FsbGJhY2sgaW5mby4KPj4gKyAqIEBldmVu
dDogdmFsdWUgc2hvd2luZyBjcHVmcmVxIGV2ZW50IGZvciB3aGljaCB0aGlzIGZ1bmN0aW9uIGlu
dm9rZWQuCj4+ICsgKiBAZGF0YTogY2FsbGJhY2stc3BlY2lmaWMgZGF0YQo+PiArICovCj4+ICtz
dGF0aWMgaW50IGNwdWZyZXFfdGhlcm1hbF9ub3RpZmllcihzdHJ1Y3Qgbm90aWZpZXJfYmxvY2sg
Km5iLAo+PiArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVuc2lnbmVkIGxv
bmcgZXZlbnQsIHZvaWQgKmRhdGEpCj4+ICt7Cj4+ICsgICAgIHN0cnVjdCBjcHVmcmVxX3BvbGlj
eSAqcG9saWN5ID0gZGF0YTsKPj4gKyAgICAgdW5zaWduZWQgbG9uZyBtYXhfZnJlcSA9IDA7Cj4+
ICsKPj4gKyAgICAgaWYgKGV2ZW50ICE9IENQVUZSRVFfQURKVVNUIHx8IG5vdGlmeV9kZXZpY2Ug
PT0gTk9USUZZX0lOVkFMSUQpCj4+ICsgICAgICAgICAgICAgcmV0dXJuIDA7Cj4+ICsKPj4gKyAg
ICAgaWYgKGNwdW1hc2tfdGVzdF9jcHUocG9saWN5LT5jcHUsICZub3RpZnlfZGV2aWNlLT5hbGxv
d2VkX2NwdXMpKQo+PiArICAgICAgICAgICAgIG1heF9mcmVxID0gbm90aWZ5X2RldmljZS0+Y3B1
ZnJlcV92YWw7Cj4+ICsKPj4gKyAgICAgLyogTmV2ZXIgZXhjZWVkIHVzZXJfcG9saWN5Lm1heCov
Cj4+ICsgICAgIGlmIChtYXhfZnJlcSA+IHBvbGljeS0+dXNlcl9wb2xpY3kubWF4KQo+PiArICAg
ICAgICAgICAgIG1heF9mcmVxID0gcG9saWN5LT51c2VyX3BvbGljeS5tYXg7Cj4+ICsKPj4gKyAg
ICAgaWYgKHBvbGljeS0+bWF4ICE9IG1heF9mcmVxKQo+PiArICAgICAgICAgICAgIGNwdWZyZXFf
dmVyaWZ5X3dpdGhpbl9saW1pdHMocG9saWN5LCAwLCBtYXhfZnJlcSk7Cj4+ICsKPj4gKyAgICAg
cmV0dXJuIDA7Cj4+ICt9Cj4+ICsKPj4gKy8qCj4+ICsgKiBjcHVmcmVxIGNvb2xpbmcgZGV2aWNl
IGNhbGxiYWNrIGZ1bmN0aW9ucyBhcmUgZGVmaW5lZCBiZWxvdwo+PiArICovCj4+ICsKPj4gKy8q
Kgo+PiArICogY3B1ZnJlcV9nZXRfbWF4X3N0YXRlIC0gY2FsbGJhY2sgZnVuY3Rpb24gdG8gZ2V0
IHRoZSBtYXggY29vbGluZyBzdGF0ZS4KPj4gKyAqIEBjZGV2OiB0aGVybWFsIGNvb2xpbmcgZGV2
aWNlIHBvaW50ZXIuCj4+ICsgKiBAc3RhdGU6IGZpbGwgdGhpcyB2YXJpYWJsZSB3aXRoIHRoZSBt
YXggY29vbGluZyBzdGF0ZS4KPj4gKyAqLwo+PiArc3RhdGljIGludCBjcHVmcmVxX2dldF9tYXhf
c3RhdGUoc3RydWN0IHRoZXJtYWxfY29vbGluZ19kZXZpY2UgKmNkZXYsCj4+ICsgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICB1bnNpZ25lZCBsb25nICpzdGF0ZSkKPj4gK3sKPj4gKyAgICAg
aW50IHJldCA9IC1FSU5WQUwsIGkgPSAwOwo+PiArICAgICBzdHJ1Y3QgY3B1ZnJlcV9jb29saW5n
X2RldmljZSAqY3B1ZnJlcV9kZXZpY2U7Cj4+ICsgICAgIHN0cnVjdCBjcHVtYXNrICptYXNrUHRy
Owo+PiArICAgICB1bnNpZ25lZCBpbnQgY3B1Owo+PiArICAgICBzdHJ1Y3QgY3B1ZnJlcV9mcmVx
dWVuY3lfdGFibGUgKnRhYmxlOwo+PiArCj4+ICsgICAgIG11dGV4X2xvY2soJmNvb2xpbmdfY3B1
ZnJlcV9sb2NrKTsKPj4gKyAgICAgbGlzdF9mb3JfZWFjaF9lbnRyeShjcHVmcmVxX2RldmljZSwg
JmNvb2xpbmdfY3B1ZnJlcV9saXN0LCBub2RlKSB7Cj4+ICsgICAgICAgICAgICAgaWYgKGNwdWZy
ZXFfZGV2aWNlICYmIGNwdWZyZXFfZGV2aWNlLT5jb29sX2RldiA9PSBjZGV2KQo+PiArICAgICAg
ICAgICAgICAgICAgICAgYnJlYWs7Cj4+ICsgICAgIH0KPj4gKyAgICAgaWYgKGNwdWZyZXFfZGV2
aWNlID09IE5VTEwpCj4+ICsgICAgICAgICAgICAgZ290byByZXR1cm5fZ2V0X21heF9zdGF0ZTsK
Pj4gKwo+PiArICAgICBtYXNrUHRyID0gJmNwdWZyZXFfZGV2aWNlLT5hbGxvd2VkX2NwdXM7Cj4+
ICsgICAgIGNwdSA9IGNwdW1hc2tfYW55KG1hc2tQdHIpOwo+PiArICAgICB0YWJsZSA9IGNwdWZy
ZXFfZnJlcXVlbmN5X2dldF90YWJsZShjcHUpOwo+PiArICAgICBpZiAoIXRhYmxlKSB7Cj4+ICsg
ICAgICAgICAgICAgKnN0YXRlID0gMDsKPj4gKyAgICAgICAgICAgICByZXQgPSAwOwo+PiArICAg
ICAgICAgICAgIGdvdG8gcmV0dXJuX2dldF9tYXhfc3RhdGU7Cj4+ICsgICAgIH0KPj4gKwo+PiAr
ICAgICB3aGlsZSAodGFibGVbaV0uZnJlcXVlbmN5ICE9IENQVUZSRVFfVEFCTEVfRU5EKSB7Cj4+
ICsgICAgICAgICAgICAgaWYgKHRhYmxlW2ldLmZyZXF1ZW5jeSA9PSBDUFVGUkVRX0VOVFJZX0lO
VkFMSUQpCj4+ICsgICAgICAgICAgICAgICAgICAgICBjb250aW51ZTsKPj4gKyAgICAgICAgICAg
ICBpKys7Cj4+ICsgICAgIH0KPj4gKyAgICAgaWYgKGkgPiAwKSB7Cj4+ICsgICAgICAgICAgICAg
KnN0YXRlID0gLS1pOwo+PiArICAgICAgICAgICAgIHJldCA9IDA7Cj4+ICsgICAgIH0KPj4gKwo+
PiArcmV0dXJuX2dldF9tYXhfc3RhdGU6Cj4+ICsgICAgIG11dGV4X3VubG9jaygmY29vbGluZ19j
cHVmcmVxX2xvY2spOwo+PiArICAgICByZXR1cm4gcmV0Owo+PiArfQo+PiArCj4+ICsvKioKPj4g
KyAqIGNwdWZyZXFfZ2V0X2N1cl9zdGF0ZSAtIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGdldCB0aGUg
Y3VycmVudCBjb29saW5nIHN0YXRlLgo+PiArICogQGNkZXY6IHRoZXJtYWwgY29vbGluZyBkZXZp
Y2UgcG9pbnRlci4KPj4gKyAqIEBzdGF0ZTogZmlsbCB0aGlzIHZhcmlhYmxlIHdpdGggdGhlIGN1
cnJlbnQgY29vbGluZyBzdGF0ZS4KPj4gKyAqLwo+PiArc3RhdGljIGludCBjcHVmcmVxX2dldF9j
dXJfc3RhdGUoc3RydWN0IHRoZXJtYWxfY29vbGluZ19kZXZpY2UgKmNkZXYsCj4+ICsgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICB1bnNpZ25lZCBsb25nICpzdGF0ZSkKPj4gK3sKPj4gKyAg
ICAgaW50IHJldCA9IC1FSU5WQUw7Cj4+ICsgICAgIHN0cnVjdCBjcHVmcmVxX2Nvb2xpbmdfZGV2
aWNlICpjcHVmcmVxX2RldmljZTsKPj4gKwo+PiArICAgICBtdXRleF9sb2NrKCZjb29saW5nX2Nw
dWZyZXFfbG9jayk7Cj4+ICsgICAgIGxpc3RfZm9yX2VhY2hfZW50cnkoY3B1ZnJlcV9kZXZpY2Us
ICZjb29saW5nX2NwdWZyZXFfbGlzdCwgbm9kZSkgewo+PiArICAgICAgICAgICAgIGlmIChjcHVm
cmVxX2RldmljZSAmJiBjcHVmcmVxX2RldmljZS0+Y29vbF9kZXYgPT0gY2Rldikgewo+PiArICAg
ICAgICAgICAgICAgICAgICAgKnN0YXRlID0gY3B1ZnJlcV9kZXZpY2UtPmNwdWZyZXFfc3RhdGU7
Cj4+ICsgICAgICAgICAgICAgICAgICAgICByZXQgPSAwOwo+PiArICAgICAgICAgICAgICAgICAg
ICAgYnJlYWs7Cj4+ICsgICAgICAgICAgICAgfQo+PiArICAgICB9Cj4+ICsgICAgIG11dGV4X3Vu
bG9jaygmY29vbGluZ19jcHVmcmVxX2xvY2spOwo+PiArCj4KPiBhcyBjcHVmcmVxIG1heSBiZSBj
aGFuZ2VkIGluIG90aGVyIHBsYWNlcywgZS5nLiB2aWEgc3lzZnMgSS9GLCB3ZSBzaG91bGQKPiB1
c2UgdGhlIGN1cnJlbnQgY3B1IGZyZXF1ZW5jeSB0byBnZXQgdGhlIFJFQUwgY29vbGluZyBzdGF0
ZSwgcmF0aGVyIHRoYW4KPiB1c2luZyBhIGNhY2hlZCB2YWx1ZS4KClllcyBhZ3JlZWQgLCBJIHdp
bGwgcmVwb3N0IHdpdGggeW91ciBzdWdnZXN0aW9uLgoKVGhhbmtzLApBbWl0Cj4KPiB0aGFua3Ms
Cj4gcnVpCj4KPgoKX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f
X18KbG0tc2Vuc29ycyBtYWlsaW5nIGxpc3QKbG0tc2Vuc29yc0BsbS1zZW5zb3JzLm9yZwpodHRw
Oi8vbGlzdHMubG0tc2Vuc29ycy5vcmcvbWFpbG1hbi9saXN0aW5mby9sbS1zZW5zb3Jz

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-17  7:58       ` Amit Kachhap
  (?)
@ 2012-08-17  8:56         ` Valentin, Eduardo
  -1 siblings, 0 replies; 50+ messages in thread
From: Valentin, Eduardo @ 2012-08-17  8:56 UTC (permalink / raw)
  To: Amit Kachhap
  Cc: linux-samsung-soc, Andrew Morton, SangWook Ju, linux-kernel,
	lm-sensors, linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park,
	Guenter Roeck, linux-pm

Hello,

On Fri, Aug 17, 2012 at 10:58 AM, Amit Kachhap <amit.kachhap@linaro.org> wrote:
> On 17 August 2012 12:54, Zhang Rui <rui.zhang@intel.com> wrote:
>> On 四, 2012-08-16 at 17:11 +0530, Amit Daniel Kachhap wrote:
>>> This patchset introduces a new generic cooling device based on cpufreq
>>> that can be used on non-ACPI platforms.  As a proof of concept, we have
>>> drivers for the following platforms using this mechanism now:
>>>
>>>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>>>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>>>
>>> There is a small change in cpufreq cooling registration APIs, so a minor
>>> change is needed for Freescale platforms.
>>>
>>> Brief Description:
>>>
>>> 1) The generic cooling devices code is placed inside driver/thermal/*
>>>    as placing inside acpi folder will need un-necessary enabling of acpi
>>>    code.  This code is architecture independent.
>>>
>>> 2) This patchset adds generic cpu cooling low level implementation
>>>    through frequency clipping.  In future, other cpu related cooling
>>>    devices may be added here.  An ACPI version of this already exists
>>>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>>>    platforms like ARM using the generic thermal interface along with the
>>>    generic cpu cooling devices.  The cooling device registration API's
>>>    return cooling device pointers which can be easily binded with the
>>>    thermal zone trip points.  The important APIs exposed are,
>>>
>>>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>>>         struct cpumask *clip_cpus)
>>>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>>>
>>> 3) Samsung exynos platform thermal implementation is done using the
>>>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>>>    driver present in the hwmon folder(registered as hwmon driver) is moved
>>>    to thermal folder and registered as a thermal driver.
>>>
>>> A simple data/control flow diagrams is shown below,
>>>
>>> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>>>           |                             |
>>>          \|/                            |
>>>   Cpufreq cooling device <---------------
>>>
>>> TODO:
>>> *Will send the DT enablement patches later after the driver is merged.
>>>
>>> This patch:
>>>
>>> Add support for generic cpu thermal cooling low level implementations
>>> using frequency scaling up/down based on the registration parameters.
>>> Different cpu related cooling devices can be registered by the user and
>>> the binding of these cooling devices to the corresponding trip points can
>>> be easily done as the registration APIs return the cooling device pointer.
>>> The user of these APIs are responsible for passing clipping frequency .
>>> The drivers can also register to recieve notification about any cooling
>>> action called.
>>>
>>> [akpm@linux-foundation.org: fix comment layout]
>>> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
>>> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
>>> Cc: SangWook Ju <sw.ju@samsung.com>
>>> Cc: Durgadoss <durgadoss.r@intel.com>
>>> Cc: Len Brown <lenb@kernel.org>
>>> Cc: Jean Delvare <khali@linux-fr.org>
>>> Cc: Kyungmin Park <kmpark@infradead.org>
>>> Cc: Kukjin Kim <kgene.kim@samsung.com>
>>> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
>>> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
>>> ---
>>>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>>>  drivers/thermal/Kconfig                   |   11 +
>>>  drivers/thermal/Makefile                  |    1 +
>>>  drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
>>>  include/linux/cpu_cooling.h               |   79 +++++
>>>  5 files changed, 655 insertions(+), 0 deletions(-)
>>>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>>>  create mode 100644 drivers/thermal/cpu_cooling.c
>>>  create mode 100644 include/linux/cpu_cooling.h
>>>
>>> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
>>> new file mode 100644
>>> index 0000000..a1f2a6b
>>> --- /dev/null
>>> +++ b/Documentation/thermal/cpu-cooling-api.txt
>>> @@ -0,0 +1,52 @@
>>> +CPU cooling APIs How To
>>> +===================================
>>> +
>>> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
>>> +
>>> +Updated: 12 May 2012
>>> +
>>> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
>>> +
>>> +0. Introduction
>>> +
>>> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
>>> +to the caller. The binding of the cooling devices to the trip point is left for
>>> +the user. The registration APIs returns the cooling device pointer.
>>> +
>>> +1. cpu cooling APIs
>>> +
>>> +1.1 cpufreq registration/unregistration APIs
>>> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
>>> +     struct cpumask *clip_cpus)
>>> +
>>> +    This interface function registers the cpufreq cooling device with the name
>>> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
>>> +    cooling devices.
>>> +
>>> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
>>> +
>>> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>>> +
>>> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
>>> +
>>> +    cdev: Cooling device pointer which has to be unregistered.
>>> +
>>> +
>>> +1.2 CPU cooling action notifier register/unregister interface
>>> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
>>> +     unsigned int list)
>>> +
>>> +    This interface registers a driver with cpu cooling layer. The driver will
>>> +    be notified when any cpu cooling action is called.
>>> +
>>> +    nb: notifier function to register
>>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>>> +
>>> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
>>> +     unsigned int list)
>>> +
>>> +    This interface registers a driver with cpu cooling layer. The driver will
>>> +    be notified when any cpu cooling action is called.
>>> +
>>> +    nb: notifier function to register
>>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>>
>> what are these two APIs used for?
>> I did not see they are used in your patch set, do I miss something?
> No currently they are not used by my patches. I added them on request
> from Eduardo and others

Yeah, this was a suggestion as we didn't really know how the FW part
would evolve by that time.

The reasoning is to allow any interested user (in kernel) to be
notified when max frequency changes. Actually, the use case behind
this is to allow such users to perform some handshaking or stop their
activities or even change some paramenters, in case the max frequency
would change. Ideally it would be possible to nack the cooling
transition. But that is yet a wider discussion. So far we don't have
users for this.

>>
>>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>>> index 7dd8c34..996003b 100644
>>> --- a/drivers/thermal/Kconfig
>>> +++ b/drivers/thermal/Kconfig
>>> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>>>       depends on HWMON=y || HWMON=THERMAL
>>>       default y
>>>
>>> +config CPU_THERMAL
>>> +     bool "generic cpu cooling support"
>>> +     depends on THERMAL && CPU_FREQ
>>> +     help
>>> +       This implements the generic cpu cooling mechanism through frequency
>>> +       reduction, cpu hotplug and any other ways of reducing temperature. An
>>> +       ACPI version of this already exists(drivers/acpi/processor_thermal.c).
>>> +       This will be useful for platforms using the generic thermal interface
>>> +       and not the ACPI interface.
>>> +       If you want this support, you should say Y here.
>>> +
>>>  config SPEAR_THERMAL
>>>       bool "SPEAr thermal sensor driver"
>>>       depends on THERMAL
>>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>>> index fd9369a..aae59ad 100644
>>> --- a/drivers/thermal/Makefile
>>> +++ b/drivers/thermal/Makefile
>>> @@ -3,5 +3,6 @@
>>>  #
>>>
>>>  obj-$(CONFIG_THERMAL)                += thermal_sys.o
>>> +obj-$(CONFIG_CPU_THERMAL)            += cpu_cooling.o
>>>  obj-$(CONFIG_SPEAR_THERMAL)          += spear_thermal.o
>>>  obj-$(CONFIG_RCAR_THERMAL)   += rcar_thermal.o
>>> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
>>> new file mode 100644
>>> index 0000000..c42e557
>>> --- /dev/null
>>> +++ b/drivers/thermal/cpu_cooling.c
>>> @@ -0,0 +1,512 @@
>>> +/*
>>> + *  linux/drivers/thermal/cpu_cooling.c
>>> + *
>>> + *  Copyright (C) 2012       Samsung Electronics Co., Ltd(http://www.samsung.com)
>>> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
>>> + *
>>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>> + *  This program is free software; you can redistribute it and/or modify
>>> + *  it under the terms of the GNU General Public License as published by
>>> + *  the Free Software Foundation; version 2 of the License.
>>> + *
>>> + *  This program is distributed in the hope that it will be useful, but
>>> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>> + *  General Public License for more details.
>>> + *
>>> + *  You should have received a copy of the GNU General Public License along
>>> + *  with this program; if not, write to the Free Software Foundation, Inc.,
>>> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
>>> + *
>>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>> + */
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/thermal.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/cpufreq.h>
>>> +#include <linux/err.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/cpu.h>
>>> +#include <linux/cpu_cooling.h>
>>> +
>>> +/**
>>> + * struct cpufreq_cooling_device
>>> + * @id: unique integer value corresponding to each cpufreq_cooling_device
>>> + *   registered.
>>> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
>>> + *   egistered cooling device.
>>> + * @cpufreq_state: integer value representing the current state of cpufreq
>>> + *   cooling devices.
>>> + * @cpufreq_val: integer value representing the absolute value of the clipped
>>> + *   frequency.
>>> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
>>> + * @node: list_head to link all cpufreq_cooling_device together.
>>> + *
>>> + * This structure is required for keeping information of each
>>> + * cpufreq_cooling_device registered as a list whose head is represented by
>>> + * cooling_cpufreq_list. In order to prevent corruption of this list a
>>> + * mutex lock cooling_cpufreq_lock is used.
>>> + */
>>> +struct cpufreq_cooling_device {
>>> +     int id;
>>> +     struct thermal_cooling_device *cool_dev;
>>> +     unsigned int cpufreq_state;
>>> +     unsigned int cpufreq_val;
>>> +     struct cpumask allowed_cpus;
>>> +     struct list_head node;
>>> +};
>>> +static LIST_HEAD(cooling_cpufreq_list);
>>> +static DEFINE_IDR(cpufreq_idr);
>>> +
>>> +static struct mutex cooling_cpufreq_lock;
>>> +
>>> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
>>> +#define NOTIFY_INVALID NULL
>>> +struct cpufreq_cooling_device *notify_device;
>>> +
>>> +/* Head of the blocking notifier chain to inform about frequency clamping */
>>> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
>>> +
>>> +/**
>>> + * get_idr - function to get a unique id.
>>> + * @idr: struct idr * handle used to create a id.
>>> + * @id: int * value generated by this function.
>>> + */
>>> +static int get_idr(struct idr *idr, int *id)
>>> +{
>>> +     int err;
>>> +again:
>>> +     if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
>>> +             return -ENOMEM;
>>> +
>>> +     mutex_lock(&cooling_cpufreq_lock);
>>> +     err = idr_get_new(idr, NULL, id);
>>> +     mutex_unlock(&cooling_cpufreq_lock);
>>> +
>>> +     if (unlikely(err == -EAGAIN))
>>> +             goto again;
>>> +     else if (unlikely(err))
>>> +             return err;
>>> +
>>> +     *id = *id & MAX_ID_MASK;
>>> +     return 0;
>>> +}
>>> +
>>> +/**
>>> + * release_idr - function to free the unique id.
>>> + * @idr: struct idr * handle used for creating the id.
>>> + * @id: int value representing the unique id.
>>> + */
>>> +static void release_idr(struct idr *idr, int id)
>>> +{
>>> +     mutex_lock(&cooling_cpufreq_lock);
>>> +     idr_remove(idr, id);
>>> +     mutex_unlock(&cooling_cpufreq_lock);
>>> +}
>>> +
>>> +/**
>>> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
>>> + * @nb:      struct notifier_block * with callback info.
>>> + * @list: integer value for which notification is needed. possible values are
>>> + *   CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
>>> + *
>>> + * This exported function registers a driver with cpu cooling layer. The driver
>>> + * will be notified when any cpu cooling action is called.
>>> + */
>>> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
>>> +{
>>> +     int ret = 0;
>>> +
>>> +     switch (list) {
>>> +     case CPUFREQ_COOLING_START:
>>> +     case CPUFREQ_COOLING_STOP:
>>> +             ret = blocking_notifier_chain_register(
>>> +                             &cputherm_state_notifier_list, nb);
>>> +             break;
>>> +     default:
>>> +             ret = -EINVAL;
>>> +     }
>>> +     return ret;
>>> +}
>>> +EXPORT_SYMBOL(cputherm_register_notifier);
>>> +
>>> +/**
>>> + * cputherm_unregister_notifier - Un-register a notifier.
>>> + * @nb:      struct notifier_block * with callback info.
>>> + * @list: integer value for which notification is needed. values possible are
>>> + *   CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
>>> + *
>>> + * This exported function un-registers a driver with cpu cooling layer.
>>> + */
>>> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
>>> +{
>>> +     int ret = 0;
>>> +
>>> +     switch (list) {
>>> +     case CPUFREQ_COOLING_START:
>>> +     case CPUFREQ_COOLING_STOP:
>>> +             ret = blocking_notifier_chain_unregister(
>>> +                             &cputherm_state_notifier_list, nb);
>>> +             break;
>>> +     default:
>>> +             ret = -EINVAL;
>>> +     }
>>> +     return ret;
>>> +}
>>> +EXPORT_SYMBOL(cputherm_unregister_notifier);
>>> +
>>> +/* Below code defines functions to be used for cpufreq as cooling device */
>>> +
>>> +/**
>>> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
>>> + * @cpu: cpu for which check is needed.
>>> + */
>>> +static int is_cpufreq_valid(int cpu)
>>> +{
>>> +     struct cpufreq_policy policy;
>>> +     return !cpufreq_get_policy(&policy, 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
>>> + */
>>> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
>>> +{
>>> +     int ret = 0, i = 0;
>>> +     unsigned long level_index;
>>> +     bool descend = false;
>>> +     struct cpufreq_frequency_table *table =
>>> +                                     cpufreq_frequency_get_table(cpu);
>>> +     if (!table)
>>> +             return ret;
>>> +
>>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>>> +                     continue;
>>> +
>>> +             /*check if table in ascending or descending order*/
>>> +             if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
>>> +                     (table[i + 1].frequency < table[i].frequency)
>>> +                     && !descend) {
>>> +                     descend = true;
>>> +             }
>>> +
>>> +             /*return if level matched and table in descending order*/
>>> +             if (descend && i == level)
>>> +                     return table[i].frequency;
>>> +             i++;
>>> +     }
>>> +     i--;
>>> +
>>> +     if (level > i || descend)
>>> +             return ret;
>>> +     level_index = i - level;
>>> +
>>> +     /*Scan the table in reverse order and match the level*/
>>> +     while (i >= 0) {
>>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>>> +                     continue;
>>> +             /*return if level matched*/
>>> +             if (i == level_index)
>>> +                     return table[i].frequency;
>>> +             i--;
>>> +     }
>>> +     return ret;
>>> +}
>>> +
>>> +/**
>>> + * cpufreq_apply_cooling - function to apply frequency clipping.
>>> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
>>> + *   clipping data.
>>> + * @cooling_state: value of the cooling state.
>>> + */
>>> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
>>> +                             unsigned long cooling_state)
>>> +{
>>> +     unsigned int event, cpuid, clip_freq;
>>> +     struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
>>> +     unsigned int cpu = cpumask_any(maskPtr);
>>> +
>>> +
>>> +     /* Check if the old cooling action is same as new cooling action */
>>> +     if (cpufreq_device->cpufreq_state == cooling_state)
>>> +             return 0;
>>> +
>>> +     clip_freq = get_cpu_frequency(cpu, cooling_state);
>>> +     if (!clip_freq)
>>> +             return -EINVAL;
>>> +
>>> +     cpufreq_device->cpufreq_state = cooling_state;
>>> +     cpufreq_device->cpufreq_val = clip_freq;
>>> +     notify_device = cpufreq_device;
>>> +
>>> +     if (cooling_state != 0)
>>> +             event = CPUFREQ_COOLING_START;
>>> +     else
>>> +             event = CPUFREQ_COOLING_STOP;
>>> +
>>> +     blocking_notifier_call_chain(&cputherm_state_notifier_list,
>>> +                                             event, &clip_freq);
>>> +
>>> +     for_each_cpu(cpuid, maskPtr) {
>>> +             if (is_cpufreq_valid(cpuid))
>>> +                     cpufreq_update_policy(cpuid);
>>> +     }
>>> +
>>> +     notify_device = NOTIFY_INVALID;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +/**
>>> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
>>> + * @nb:      struct notifier_block * with callback info.
>>> + * @event: value showing cpufreq event for which this function invoked.
>>> + * @data: callback-specific data
>>> + */
>>> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
>>> +                                     unsigned long event, void *data)
>>> +{
>>> +     struct cpufreq_policy *policy = data;
>>> +     unsigned long max_freq = 0;
>>> +
>>> +     if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
>>> +             return 0;
>>> +
>>> +     if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
>>> +             max_freq = notify_device->cpufreq_val;
>>> +
>>> +     /* Never exceed user_policy.max*/
>>> +     if (max_freq > policy->user_policy.max)
>>> +             max_freq = policy->user_policy.max;
>>> +
>>> +     if (policy->max != max_freq)
>>> +             cpufreq_verify_within_limits(policy, 0, max_freq);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +/*
>>> + * cpufreq cooling device callback functions are defined below
>>> + */
>>> +
>>> +/**
>>> + * cpufreq_get_max_state - callback function to get the max cooling state.
>>> + * @cdev: thermal cooling device pointer.
>>> + * @state: fill this variable with the max cooling state.
>>> + */
>>> +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;
>>> +     unsigned int cpu;
>>> +     struct cpufreq_frequency_table *table;
>>> +
>>> +     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;
>>> +     }
>>> +
>>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>>> +                     continue;
>>> +             i++;
>>> +     }
>>> +     if (i > 0) {
>>> +             *state = --i;
>>> +             ret = 0;
>>> +     }
>>> +
>>> +return_get_max_state:
>>> +     mutex_unlock(&cooling_cpufreq_lock);
>>> +     return ret;
>>> +}
>>> +
>>> +/**
>>> + * cpufreq_get_cur_state - callback function to get the current cooling state.
>>> + * @cdev: thermal cooling device pointer.
>>> + * @state: fill this variable with the current cooling 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);
>>> +
>>
>> as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
>> use the current cpu frequency to get the REAL cooling state, rather than
>> using a cached value.
>
> Yes agreed , I will repost with your suggestion.
>
> Thanks,
> Amit
>>
>> thanks,
>> rui
>>
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html



-- 

Eduardo Valentin

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17  8:56         ` Valentin, Eduardo
  0 siblings, 0 replies; 50+ messages in thread
From: Valentin, Eduardo @ 2012-08-17  8:56 UTC (permalink / raw)
  To: Amit Kachhap
  Cc: Zhang Rui, linux-pm, Andrew Morton, Len Brown, linux-samsung-soc,
	linux-kernel, linux-acpi, lm-sensors, Guenter Roeck, SangWook Ju,
	Durgadoss, Jean Delvare, Kyungmin Park, Kukjin Kim

Hello,

On Fri, Aug 17, 2012 at 10:58 AM, Amit Kachhap <amit.kachhap@linaro.org> wrote:
> On 17 August 2012 12:54, Zhang Rui <rui.zhang@intel.com> wrote:
>> On 四, 2012-08-16 at 17:11 +0530, Amit Daniel Kachhap wrote:
>>> This patchset introduces a new generic cooling device based on cpufreq
>>> that can be used on non-ACPI platforms.  As a proof of concept, we have
>>> drivers for the following platforms using this mechanism now:
>>>
>>>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>>>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>>>
>>> There is a small change in cpufreq cooling registration APIs, so a minor
>>> change is needed for Freescale platforms.
>>>
>>> Brief Description:
>>>
>>> 1) The generic cooling devices code is placed inside driver/thermal/*
>>>    as placing inside acpi folder will need un-necessary enabling of acpi
>>>    code.  This code is architecture independent.
>>>
>>> 2) This patchset adds generic cpu cooling low level implementation
>>>    through frequency clipping.  In future, other cpu related cooling
>>>    devices may be added here.  An ACPI version of this already exists
>>>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>>>    platforms like ARM using the generic thermal interface along with the
>>>    generic cpu cooling devices.  The cooling device registration API's
>>>    return cooling device pointers which can be easily binded with the
>>>    thermal zone trip points.  The important APIs exposed are,
>>>
>>>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>>>         struct cpumask *clip_cpus)
>>>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>>>
>>> 3) Samsung exynos platform thermal implementation is done using the
>>>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>>>    driver present in the hwmon folder(registered as hwmon driver) is moved
>>>    to thermal folder and registered as a thermal driver.
>>>
>>> A simple data/control flow diagrams is shown below,
>>>
>>> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>>>           |                             |
>>>          \|/                            |
>>>   Cpufreq cooling device <---------------
>>>
>>> TODO:
>>> *Will send the DT enablement patches later after the driver is merged.
>>>
>>> This patch:
>>>
>>> Add support for generic cpu thermal cooling low level implementations
>>> using frequency scaling up/down based on the registration parameters.
>>> Different cpu related cooling devices can be registered by the user and
>>> the binding of these cooling devices to the corresponding trip points can
>>> be easily done as the registration APIs return the cooling device pointer.
>>> The user of these APIs are responsible for passing clipping frequency .
>>> The drivers can also register to recieve notification about any cooling
>>> action called.
>>>
>>> [akpm@linux-foundation.org: fix comment layout]
>>> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
>>> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
>>> Cc: SangWook Ju <sw.ju@samsung.com>
>>> Cc: Durgadoss <durgadoss.r@intel.com>
>>> Cc: Len Brown <lenb@kernel.org>
>>> Cc: Jean Delvare <khali@linux-fr.org>
>>> Cc: Kyungmin Park <kmpark@infradead.org>
>>> Cc: Kukjin Kim <kgene.kim@samsung.com>
>>> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
>>> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
>>> ---
>>>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>>>  drivers/thermal/Kconfig                   |   11 +
>>>  drivers/thermal/Makefile                  |    1 +
>>>  drivers/thermal/cpu_cooling.c             |  512 +++++++++++++++++++++++++++++
>>>  include/linux/cpu_cooling.h               |   79 +++++
>>>  5 files changed, 655 insertions(+), 0 deletions(-)
>>>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>>>  create mode 100644 drivers/thermal/cpu_cooling.c
>>>  create mode 100644 include/linux/cpu_cooling.h
>>>
>>> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
>>> new file mode 100644
>>> index 0000000..a1f2a6b
>>> --- /dev/null
>>> +++ b/Documentation/thermal/cpu-cooling-api.txt
>>> @@ -0,0 +1,52 @@
>>> +CPU cooling APIs How To
>>> +===================================
>>> +
>>> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
>>> +
>>> +Updated: 12 May 2012
>>> +
>>> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
>>> +
>>> +0. Introduction
>>> +
>>> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
>>> +to the caller. The binding of the cooling devices to the trip point is left for
>>> +the user. The registration APIs returns the cooling device pointer.
>>> +
>>> +1. cpu cooling APIs
>>> +
>>> +1.1 cpufreq registration/unregistration APIs
>>> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
>>> +     struct cpumask *clip_cpus)
>>> +
>>> +    This interface function registers the cpufreq cooling device with the name
>>> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
>>> +    cooling devices.
>>> +
>>> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
>>> +
>>> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>>> +
>>> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
>>> +
>>> +    cdev: Cooling device pointer which has to be unregistered.
>>> +
>>> +
>>> +1.2 CPU cooling action notifier register/unregister interface
>>> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
>>> +     unsigned int list)
>>> +
>>> +    This interface registers a driver with cpu cooling layer. The driver will
>>> +    be notified when any cpu cooling action is called.
>>> +
>>> +    nb: notifier function to register
>>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>>> +
>>> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
>>> +     unsigned int list)
>>> +
>>> +    This interface registers a driver with cpu cooling layer. The driver will
>>> +    be notified when any cpu cooling action is called.
>>> +
>>> +    nb: notifier function to register
>>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>>
>> what are these two APIs used for?
>> I did not see they are used in your patch set, do I miss something?
> No currently they are not used by my patches. I added them on request
> from Eduardo and others

Yeah, this was a suggestion as we didn't really know how the FW part
would evolve by that time.

The reasoning is to allow any interested user (in kernel) to be
notified when max frequency changes. Actually, the use case behind
this is to allow such users to perform some handshaking or stop their
activities or even change some paramenters, in case the max frequency
would change. Ideally it would be possible to nack the cooling
transition. But that is yet a wider discussion. So far we don't have
users for this.

>>
>>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>>> index 7dd8c34..996003b 100644
>>> --- a/drivers/thermal/Kconfig
>>> +++ b/drivers/thermal/Kconfig
>>> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>>>       depends on HWMON=y || HWMON=THERMAL
>>>       default y
>>>
>>> +config CPU_THERMAL
>>> +     bool "generic cpu cooling support"
>>> +     depends on THERMAL && CPU_FREQ
>>> +     help
>>> +       This implements the generic cpu cooling mechanism through frequency
>>> +       reduction, cpu hotplug and any other ways of reducing temperature. An
>>> +       ACPI version of this already exists(drivers/acpi/processor_thermal.c).
>>> +       This will be useful for platforms using the generic thermal interface
>>> +       and not the ACPI interface.
>>> +       If you want this support, you should say Y here.
>>> +
>>>  config SPEAR_THERMAL
>>>       bool "SPEAr thermal sensor driver"
>>>       depends on THERMAL
>>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>>> index fd9369a..aae59ad 100644
>>> --- a/drivers/thermal/Makefile
>>> +++ b/drivers/thermal/Makefile
>>> @@ -3,5 +3,6 @@
>>>  #
>>>
>>>  obj-$(CONFIG_THERMAL)                += thermal_sys.o
>>> +obj-$(CONFIG_CPU_THERMAL)            += cpu_cooling.o
>>>  obj-$(CONFIG_SPEAR_THERMAL)          += spear_thermal.o
>>>  obj-$(CONFIG_RCAR_THERMAL)   += rcar_thermal.o
>>> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
>>> new file mode 100644
>>> index 0000000..c42e557
>>> --- /dev/null
>>> +++ b/drivers/thermal/cpu_cooling.c
>>> @@ -0,0 +1,512 @@
>>> +/*
>>> + *  linux/drivers/thermal/cpu_cooling.c
>>> + *
>>> + *  Copyright (C) 2012       Samsung Electronics Co., Ltd(http://www.samsung.com)
>>> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
>>> + *
>>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>> + *  This program is free software; you can redistribute it and/or modify
>>> + *  it under the terms of the GNU General Public License as published by
>>> + *  the Free Software Foundation; version 2 of the License.
>>> + *
>>> + *  This program is distributed in the hope that it will be useful, but
>>> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>> + *  General Public License for more details.
>>> + *
>>> + *  You should have received a copy of the GNU General Public License along
>>> + *  with this program; if not, write to the Free Software Foundation, Inc.,
>>> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
>>> + *
>>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>> + */
>>> +#include <linux/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/thermal.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/cpufreq.h>
>>> +#include <linux/err.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/cpu.h>
>>> +#include <linux/cpu_cooling.h>
>>> +
>>> +/**
>>> + * struct cpufreq_cooling_device
>>> + * @id: unique integer value corresponding to each cpufreq_cooling_device
>>> + *   registered.
>>> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
>>> + *   egistered cooling device.
>>> + * @cpufreq_state: integer value representing the current state of cpufreq
>>> + *   cooling devices.
>>> + * @cpufreq_val: integer value representing the absolute value of the clipped
>>> + *   frequency.
>>> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
>>> + * @node: list_head to link all cpufreq_cooling_device together.
>>> + *
>>> + * This structure is required for keeping information of each
>>> + * cpufreq_cooling_device registered as a list whose head is represented by
>>> + * cooling_cpufreq_list. In order to prevent corruption of this list a
>>> + * mutex lock cooling_cpufreq_lock is used.
>>> + */
>>> +struct cpufreq_cooling_device {
>>> +     int id;
>>> +     struct thermal_cooling_device *cool_dev;
>>> +     unsigned int cpufreq_state;
>>> +     unsigned int cpufreq_val;
>>> +     struct cpumask allowed_cpus;
>>> +     struct list_head node;
>>> +};
>>> +static LIST_HEAD(cooling_cpufreq_list);
>>> +static DEFINE_IDR(cpufreq_idr);
>>> +
>>> +static struct mutex cooling_cpufreq_lock;
>>> +
>>> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
>>> +#define NOTIFY_INVALID NULL
>>> +struct cpufreq_cooling_device *notify_device;
>>> +
>>> +/* Head of the blocking notifier chain to inform about frequency clamping */
>>> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
>>> +
>>> +/**
>>> + * get_idr - function to get a unique id.
>>> + * @idr: struct idr * handle used to create a id.
>>> + * @id: int * value generated by this function.
>>> + */
>>> +static int get_idr(struct idr *idr, int *id)
>>> +{
>>> +     int err;
>>> +again:
>>> +     if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
>>> +             return -ENOMEM;
>>> +
>>> +     mutex_lock(&cooling_cpufreq_lock);
>>> +     err = idr_get_new(idr, NULL, id);
>>> +     mutex_unlock(&cooling_cpufreq_lock);
>>> +
>>> +     if (unlikely(err == -EAGAIN))
>>> +             goto again;
>>> +     else if (unlikely(err))
>>> +             return err;
>>> +
>>> +     *id = *id & MAX_ID_MASK;
>>> +     return 0;
>>> +}
>>> +
>>> +/**
>>> + * release_idr - function to free the unique id.
>>> + * @idr: struct idr * handle used for creating the id.
>>> + * @id: int value representing the unique id.
>>> + */
>>> +static void release_idr(struct idr *idr, int id)
>>> +{
>>> +     mutex_lock(&cooling_cpufreq_lock);
>>> +     idr_remove(idr, id);
>>> +     mutex_unlock(&cooling_cpufreq_lock);
>>> +}
>>> +
>>> +/**
>>> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
>>> + * @nb:      struct notifier_block * with callback info.
>>> + * @list: integer value for which notification is needed. possible values are
>>> + *   CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
>>> + *
>>> + * This exported function registers a driver with cpu cooling layer. The driver
>>> + * will be notified when any cpu cooling action is called.
>>> + */
>>> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
>>> +{
>>> +     int ret = 0;
>>> +
>>> +     switch (list) {
>>> +     case CPUFREQ_COOLING_START:
>>> +     case CPUFREQ_COOLING_STOP:
>>> +             ret = blocking_notifier_chain_register(
>>> +                             &cputherm_state_notifier_list, nb);
>>> +             break;
>>> +     default:
>>> +             ret = -EINVAL;
>>> +     }
>>> +     return ret;
>>> +}
>>> +EXPORT_SYMBOL(cputherm_register_notifier);
>>> +
>>> +/**
>>> + * cputherm_unregister_notifier - Un-register a notifier.
>>> + * @nb:      struct notifier_block * with callback info.
>>> + * @list: integer value for which notification is needed. values possible are
>>> + *   CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
>>> + *
>>> + * This exported function un-registers a driver with cpu cooling layer.
>>> + */
>>> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
>>> +{
>>> +     int ret = 0;
>>> +
>>> +     switch (list) {
>>> +     case CPUFREQ_COOLING_START:
>>> +     case CPUFREQ_COOLING_STOP:
>>> +             ret = blocking_notifier_chain_unregister(
>>> +                             &cputherm_state_notifier_list, nb);
>>> +             break;
>>> +     default:
>>> +             ret = -EINVAL;
>>> +     }
>>> +     return ret;
>>> +}
>>> +EXPORT_SYMBOL(cputherm_unregister_notifier);
>>> +
>>> +/* Below code defines functions to be used for cpufreq as cooling device */
>>> +
>>> +/**
>>> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
>>> + * @cpu: cpu for which check is needed.
>>> + */
>>> +static int is_cpufreq_valid(int cpu)
>>> +{
>>> +     struct cpufreq_policy policy;
>>> +     return !cpufreq_get_policy(&policy, 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
>>> + */
>>> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
>>> +{
>>> +     int ret = 0, i = 0;
>>> +     unsigned long level_index;
>>> +     bool descend = false;
>>> +     struct cpufreq_frequency_table *table =
>>> +                                     cpufreq_frequency_get_table(cpu);
>>> +     if (!table)
>>> +             return ret;
>>> +
>>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>>> +                     continue;
>>> +
>>> +             /*check if table in ascending or descending order*/
>>> +             if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
>>> +                     (table[i + 1].frequency < table[i].frequency)
>>> +                     && !descend) {
>>> +                     descend = true;
>>> +             }
>>> +
>>> +             /*return if level matched and table in descending order*/
>>> +             if (descend && i == level)
>>> +                     return table[i].frequency;
>>> +             i++;
>>> +     }
>>> +     i--;
>>> +
>>> +     if (level > i || descend)
>>> +             return ret;
>>> +     level_index = i - level;
>>> +
>>> +     /*Scan the table in reverse order and match the level*/
>>> +     while (i >= 0) {
>>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>>> +                     continue;
>>> +             /*return if level matched*/
>>> +             if (i == level_index)
>>> +                     return table[i].frequency;
>>> +             i--;
>>> +     }
>>> +     return ret;
>>> +}
>>> +
>>> +/**
>>> + * cpufreq_apply_cooling - function to apply frequency clipping.
>>> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
>>> + *   clipping data.
>>> + * @cooling_state: value of the cooling state.
>>> + */
>>> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
>>> +                             unsigned long cooling_state)
>>> +{
>>> +     unsigned int event, cpuid, clip_freq;
>>> +     struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
>>> +     unsigned int cpu = cpumask_any(maskPtr);
>>> +
>>> +
>>> +     /* Check if the old cooling action is same as new cooling action */
>>> +     if (cpufreq_device->cpufreq_state == cooling_state)
>>> +             return 0;
>>> +
>>> +     clip_freq = get_cpu_frequency(cpu, cooling_state);
>>> +     if (!clip_freq)
>>> +             return -EINVAL;
>>> +
>>> +     cpufreq_device->cpufreq_state = cooling_state;
>>> +     cpufreq_device->cpufreq_val = clip_freq;
>>> +     notify_device = cpufreq_device;
>>> +
>>> +     if (cooling_state != 0)
>>> +             event = CPUFREQ_COOLING_START;
>>> +     else
>>> +             event = CPUFREQ_COOLING_STOP;
>>> +
>>> +     blocking_notifier_call_chain(&cputherm_state_notifier_list,
>>> +                                             event, &clip_freq);
>>> +
>>> +     for_each_cpu(cpuid, maskPtr) {
>>> +             if (is_cpufreq_valid(cpuid))
>>> +                     cpufreq_update_policy(cpuid);
>>> +     }
>>> +
>>> +     notify_device = NOTIFY_INVALID;
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +/**
>>> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
>>> + * @nb:      struct notifier_block * with callback info.
>>> + * @event: value showing cpufreq event for which this function invoked.
>>> + * @data: callback-specific data
>>> + */
>>> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
>>> +                                     unsigned long event, void *data)
>>> +{
>>> +     struct cpufreq_policy *policy = data;
>>> +     unsigned long max_freq = 0;
>>> +
>>> +     if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
>>> +             return 0;
>>> +
>>> +     if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
>>> +             max_freq = notify_device->cpufreq_val;
>>> +
>>> +     /* Never exceed user_policy.max*/
>>> +     if (max_freq > policy->user_policy.max)
>>> +             max_freq = policy->user_policy.max;
>>> +
>>> +     if (policy->max != max_freq)
>>> +             cpufreq_verify_within_limits(policy, 0, max_freq);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +/*
>>> + * cpufreq cooling device callback functions are defined below
>>> + */
>>> +
>>> +/**
>>> + * cpufreq_get_max_state - callback function to get the max cooling state.
>>> + * @cdev: thermal cooling device pointer.
>>> + * @state: fill this variable with the max cooling state.
>>> + */
>>> +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;
>>> +     unsigned int cpu;
>>> +     struct cpufreq_frequency_table *table;
>>> +
>>> +     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;
>>> +     }
>>> +
>>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>>> +                     continue;
>>> +             i++;
>>> +     }
>>> +     if (i > 0) {
>>> +             *state = --i;
>>> +             ret = 0;
>>> +     }
>>> +
>>> +return_get_max_state:
>>> +     mutex_unlock(&cooling_cpufreq_lock);
>>> +     return ret;
>>> +}
>>> +
>>> +/**
>>> + * cpufreq_get_cur_state - callback function to get the current cooling state.
>>> + * @cdev: thermal cooling device pointer.
>>> + * @state: fill this variable with the current cooling 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);
>>> +
>>
>> as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
>> use the current cpu frequency to get the REAL cooling state, rather than
>> using a cached value.
>
> Yes agreed , I will repost with your suggestion.
>
> Thanks,
> Amit
>>
>> thanks,
>> rui
>>
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html



-- 

Eduardo Valentin

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [lm-sensors] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17  8:56         ` Valentin, Eduardo
  0 siblings, 0 replies; 50+ messages in thread
From: Valentin, Eduardo @ 2012-08-17  8:56 UTC (permalink / raw)
  To: Amit Kachhap
  Cc: linux-samsung-soc, Andrew Morton, SangWook Ju, linux-kernel,
	lm-sensors, linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park,
	Guenter Roeck, linux-pm

SGVsbG8sCgpPbiBGcmksIEF1ZyAxNywgMjAxMiBhdCAxMDo1OCBBTSwgQW1pdCBLYWNoaGFwIDxh
bWl0LmthY2hoYXBAbGluYXJvLm9yZz4gd3JvdGU6Cj4gT24gMTcgQXVndXN0IDIwMTIgMTI6NTQs
IFpoYW5nIFJ1aSA8cnVpLnpoYW5nQGludGVsLmNvbT4gd3JvdGU6Cj4+IE9uIOWbmywgMjAxMi0w
OC0xNiBhdCAxNzoxMSArMDUzMCwgQW1pdCBEYW5pZWwgS2FjaGhhcCB3cm90ZToKPj4+IFRoaXMg
cGF0Y2hzZXQgaW50cm9kdWNlcyBhIG5ldyBnZW5lcmljIGNvb2xpbmcgZGV2aWNlIGJhc2VkIG9u
IGNwdWZyZXEKPj4+IHRoYXQgY2FuIGJlIHVzZWQgb24gbm9uLUFDUEkgcGxhdGZvcm1zLiAgQXMg
YSBwcm9vZiBvZiBjb25jZXB0LCB3ZSBoYXZlCj4+PiBkcml2ZXJzIGZvciB0aGUgZm9sbG93aW5n
IHBsYXRmb3JtcyB1c2luZyB0aGlzIG1lY2hhbmlzbSBub3c6Cj4+Pgo+Pj4gICogU2Ftc3VuZyBF
eHlub3MgKEV4eW5vczQgYW5kIEV4eW5vczUpIGluIHRoZSBjdXJyZW50IHBhdGNoc2V0Lgo+Pj4g
ICogRnJlZXNjYWxlIGkuTVggKGdpdDovL2dpdC5saW5hcm8ub3JnL3Blb3BsZS9hbWl0ZGFuaWVs
ay9saW51eC5naXQgaW14NnFfdGhlcm1hbCkKPj4+Cj4+PiBUaGVyZSBpcyBhIHNtYWxsIGNoYW5n
ZSBpbiBjcHVmcmVxIGNvb2xpbmcgcmVnaXN0cmF0aW9uIEFQSXMsIHNvIGEgbWlub3IKPj4+IGNo
YW5nZSBpcyBuZWVkZWQgZm9yIEZyZWVzY2FsZSBwbGF0Zm9ybXMuCj4+Pgo+Pj4gQnJpZWYgRGVz
Y3JpcHRpb246Cj4+Pgo+Pj4gMSkgVGhlIGdlbmVyaWMgY29vbGluZyBkZXZpY2VzIGNvZGUgaXMg
cGxhY2VkIGluc2lkZSBkcml2ZXIvdGhlcm1hbC8qCj4+PiAgICBhcyBwbGFjaW5nIGluc2lkZSBh
Y3BpIGZvbGRlciB3aWxsIG5lZWQgdW4tbmVjZXNzYXJ5IGVuYWJsaW5nIG9mIGFjcGkKPj4+ICAg
IGNvZGUuICBUaGlzIGNvZGUgaXMgYXJjaGl0ZWN0dXJlIGluZGVwZW5kZW50Lgo+Pj4KPj4+IDIp
IFRoaXMgcGF0Y2hzZXQgYWRkcyBnZW5lcmljIGNwdSBjb29saW5nIGxvdyBsZXZlbCBpbXBsZW1l
bnRhdGlvbgo+Pj4gICAgdGhyb3VnaCBmcmVxdWVuY3kgY2xpcHBpbmcuICBJbiBmdXR1cmUsIG90
aGVyIGNwdSByZWxhdGVkIGNvb2xpbmcKPj4+ICAgIGRldmljZXMgbWF5IGJlIGFkZGVkIGhlcmUu
ICBBbiBBQ1BJIHZlcnNpb24gb2YgdGhpcyBhbHJlYWR5IGV4aXN0cwo+Pj4gICAgKGRyaXZlcnMv
YWNwaS9wcm9jZXNzb3JfdGhlcm1hbC5jKSAuQnV0IHRoaXMgd2lsbCBiZSB1c2VmdWwgZm9yCj4+
PiAgICBwbGF0Zm9ybXMgbGlrZSBBUk0gdXNpbmcgdGhlIGdlbmVyaWMgdGhlcm1hbCBpbnRlcmZh
Y2UgYWxvbmcgd2l0aCB0aGUKPj4+ICAgIGdlbmVyaWMgY3B1IGNvb2xpbmcgZGV2aWNlcy4gIFRo
ZSBjb29saW5nIGRldmljZSByZWdpc3RyYXRpb24gQVBJJ3MKPj4+ICAgIHJldHVybiBjb29saW5n
IGRldmljZSBwb2ludGVycyB3aGljaCBjYW4gYmUgZWFzaWx5IGJpbmRlZCB3aXRoIHRoZQo+Pj4g
ICAgdGhlcm1hbCB6b25lIHRyaXAgcG9pbnRzLiAgVGhlIGltcG9ydGFudCBBUElzIGV4cG9zZWQg
YXJlLAo+Pj4KPj4+ICAgIGEpIHN0cnVjdCB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjcHVmcmVx
X2Nvb2xpbmdfcmVnaXN0ZXIoCj4+PiAgICAgICAgIHN0cnVjdCBjcHVtYXNrICpjbGlwX2NwdXMp
Cj4+PiAgICBiKSB2b2lkIGNwdWZyZXFfY29vbGluZ191bnJlZ2lzdGVyKHN0cnVjdCB0aGVybWFs
X2Nvb2xpbmdfZGV2aWNlICpjZGV2KQo+Pj4KPj4+IDMpIFNhbXN1bmcgZXh5bm9zIHBsYXRmb3Jt
IHRoZXJtYWwgaW1wbGVtZW50YXRpb24gaXMgZG9uZSB1c2luZyB0aGUKPj4+ICAgIGdlbmVyaWMg
Y3B1IGNvb2xpbmcgQVBJcyBhbmQgdGhlIG5ldyB0cmlwIHR5cGUuICBUaGUgdGVtcGVyYXR1cmUg
c2Vuc29yCj4+PiAgICBkcml2ZXIgcHJlc2VudCBpbiB0aGUgaHdtb24gZm9sZGVyKHJlZ2lzdGVy
ZWQgYXMgaHdtb24gZHJpdmVyKSBpcyBtb3ZlZAo+Pj4gICAgdG8gdGhlcm1hbCBmb2xkZXIgYW5k
IHJlZ2lzdGVyZWQgYXMgYSB0aGVybWFsIGRyaXZlci4KPj4+Cj4+PiBBIHNpbXBsZSBkYXRhL2Nv
bnRyb2wgZmxvdyBkaWFncmFtcyBpcyBzaG93biBiZWxvdywKPj4+Cj4+PiBDb3JlIExpbnV4IHRo
ZXJtYWwgPC0tLS0tPiAgRXh5bm9zIHRoZXJtYWwgaW50ZXJmYWNlIDwtLS0tLSBUZW1wZXJhdHVy
ZSBTZW5zb3IKPj4+ICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8Cj4+
PiAgICAgICAgICBcfC8gICAgICAgICAgICAgICAgICAgICAgICAgICAgfAo+Pj4gICBDcHVmcmVx
IGNvb2xpbmcgZGV2aWNlIDwtLS0tLS0tLS0tLS0tLS0KPj4+Cj4+PiBUT0RPOgo+Pj4gKldpbGwg
c2VuZCB0aGUgRFQgZW5hYmxlbWVudCBwYXRjaGVzIGxhdGVyIGFmdGVyIHRoZSBkcml2ZXIgaXMg
bWVyZ2VkLgo+Pj4KPj4+IFRoaXMgcGF0Y2g6Cj4+Pgo+Pj4gQWRkIHN1cHBvcnQgZm9yIGdlbmVy
aWMgY3B1IHRoZXJtYWwgY29vbGluZyBsb3cgbGV2ZWwgaW1wbGVtZW50YXRpb25zCj4+PiB1c2lu
ZyBmcmVxdWVuY3kgc2NhbGluZyB1cC9kb3duIGJhc2VkIG9uIHRoZSByZWdpc3RyYXRpb24gcGFy
YW1ldGVycy4KPj4+IERpZmZlcmVudCBjcHUgcmVsYXRlZCBjb29saW5nIGRldmljZXMgY2FuIGJl
IHJlZ2lzdGVyZWQgYnkgdGhlIHVzZXIgYW5kCj4+PiB0aGUgYmluZGluZyBvZiB0aGVzZSBjb29s
aW5nIGRldmljZXMgdG8gdGhlIGNvcnJlc3BvbmRpbmcgdHJpcCBwb2ludHMgY2FuCj4+PiBiZSBl
YXNpbHkgZG9uZSBhcyB0aGUgcmVnaXN0cmF0aW9uIEFQSXMgcmV0dXJuIHRoZSBjb29saW5nIGRl
dmljZSBwb2ludGVyLgo+Pj4gVGhlIHVzZXIgb2YgdGhlc2UgQVBJcyBhcmUgcmVzcG9uc2libGUg
Zm9yIHBhc3NpbmcgY2xpcHBpbmcgZnJlcXVlbmN5IC4KPj4+IFRoZSBkcml2ZXJzIGNhbiBhbHNv
IHJlZ2lzdGVyIHRvIHJlY2lldmUgbm90aWZpY2F0aW9uIGFib3V0IGFueSBjb29saW5nCj4+PiBh
Y3Rpb24gY2FsbGVkLgo+Pj4KPj4+IFtha3BtQGxpbnV4LWZvdW5kYXRpb24ub3JnOiBmaXggY29t
bWVudCBsYXlvdXRdCj4+PiBTaWduZWQtb2ZmLWJ5OiBBbWl0IERhbmllbCBLYWNoaGFwIDxhbWl0
LmthY2hoYXBAbGluYXJvLm9yZz4KPj4+IENjOiBHdWVudGVyIFJvZWNrIDxndWVudGVyLnJvZWNr
QGVyaWNzc29uLmNvbT4KPj4+IENjOiBTYW5nV29vayBKdSA8c3cuanVAc2Ftc3VuZy5jb20+Cj4+
PiBDYzogRHVyZ2Fkb3NzIDxkdXJnYWRvc3MuckBpbnRlbC5jb20+Cj4+PiBDYzogTGVuIEJyb3du
IDxsZW5iQGtlcm5lbC5vcmc+Cj4+PiBDYzogSmVhbiBEZWx2YXJlIDxraGFsaUBsaW51eC1mci5v
cmc+Cj4+PiBDYzogS3l1bmdtaW4gUGFyayA8a21wYXJrQGluZnJhZGVhZC5vcmc+Cj4+PiBDYzog
S3VramluIEtpbSA8a2dlbmUua2ltQHNhbXN1bmcuY29tPgo+Pj4gU2lnbmVkLW9mZi1ieTogQW5k
cmV3IE1vcnRvbiA8YWtwbUBsaW51eC1mb3VuZGF0aW9uLm9yZz4KPj4+IFNpZ25lZC1vZmYtYnk6
IEFtaXQgRGFuaWVsIEthY2hoYXAgPGFtaXQuZGFuaWVsQHNhbXN1bmcuY29tPgo+Pj4gLS0tCj4+
PiAgRG9jdW1lbnRhdGlvbi90aGVybWFsL2NwdS1jb29saW5nLWFwaS50eHQgfCAgIDUyICsrKwo+
Pj4gIGRyaXZlcnMvdGhlcm1hbC9LY29uZmlnICAgICAgICAgICAgICAgICAgIHwgICAxMSArCj4+
PiAgZHJpdmVycy90aGVybWFsL01ha2VmaWxlICAgICAgICAgICAgICAgICAgfCAgICAxICsKPj4+
ICBkcml2ZXJzL3RoZXJtYWwvY3B1X2Nvb2xpbmcuYyAgICAgICAgICAgICB8ICA1MTIgKysrKysr
KysrKysrKysrKysrKysrKysrKysrKysKPj4+ICBpbmNsdWRlL2xpbnV4L2NwdV9jb29saW5nLmgg
ICAgICAgICAgICAgICB8ICAgNzkgKysrKysKPj4+ICA1IGZpbGVzIGNoYW5nZWQsIDY1NSBpbnNl
cnRpb25zKCspLCAwIGRlbGV0aW9ucygtKQo+Pj4gIGNyZWF0ZSBtb2RlIDEwMDY0NCBEb2N1bWVu
dGF0aW9uL3RoZXJtYWwvY3B1LWNvb2xpbmctYXBpLnR4dAo+Pj4gIGNyZWF0ZSBtb2RlIDEwMDY0
NCBkcml2ZXJzL3RoZXJtYWwvY3B1X2Nvb2xpbmcuYwo+Pj4gIGNyZWF0ZSBtb2RlIDEwMDY0NCBp
bmNsdWRlL2xpbnV4L2NwdV9jb29saW5nLmgKPj4+Cj4+PiBkaWZmIC0tZ2l0IGEvRG9jdW1lbnRh
dGlvbi90aGVybWFsL2NwdS1jb29saW5nLWFwaS50eHQgYi9Eb2N1bWVudGF0aW9uL3RoZXJtYWwv
Y3B1LWNvb2xpbmctYXBpLnR4dAo+Pj4gbmV3IGZpbGUgbW9kZSAxMDA2NDQKPj4+IGluZGV4IDAw
MDAwMDAuLmExZjJhNmIKPj4+IC0tLSAvZGV2L251bGwKPj4+ICsrKyBiL0RvY3VtZW50YXRpb24v
dGhlcm1hbC9jcHUtY29vbGluZy1hcGkudHh0Cj4+PiBAQCAtMCwwICsxLDUyIEBACj4+PiArQ1BV
IGNvb2xpbmcgQVBJcyBIb3cgVG8KPj4+ICs9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09
PT09PQo+Pj4gKwo+Pj4gK1dyaXR0ZW4gYnkgQW1pdCBEYW5pZWwgS2FjaGhhcCA8YW1pdC5rYWNo
aGFwQGxpbmFyby5vcmc+Cj4+PiArCj4+PiArVXBkYXRlZDogMTIgTWF5IDIwMTIKPj4+ICsKPj4+
ICtDb3B5cmlnaHQgKGMpICAyMDEyIFNhbXN1bmcgRWxlY3Ryb25pY3MgQ28uLCBMdGQoaHR0cDov
L3d3dy5zYW1zdW5nLmNvbSkKPj4+ICsKPj4+ICswLiBJbnRyb2R1Y3Rpb24KPj4+ICsKPj4+ICtU
aGUgZ2VuZXJpYyBjcHUgY29vbGluZyhmcmVxIGNsaXBwaW5nKSBwcm92aWRlcyByZWdpc3RyYXRp
b24vdW5yZWdpc3RyYXRpb24gQVBJcwo+Pj4gK3RvIHRoZSBjYWxsZXIuIFRoZSBiaW5kaW5nIG9m
IHRoZSBjb29saW5nIGRldmljZXMgdG8gdGhlIHRyaXAgcG9pbnQgaXMgbGVmdCBmb3IKPj4+ICt0
aGUgdXNlci4gVGhlIHJlZ2lzdHJhdGlvbiBBUElzIHJldHVybnMgdGhlIGNvb2xpbmcgZGV2aWNl
IHBvaW50ZXIuCj4+PiArCj4+PiArMS4gY3B1IGNvb2xpbmcgQVBJcwo+Pj4gKwo+Pj4gKzEuMSBj
cHVmcmVxIHJlZ2lzdHJhdGlvbi91bnJlZ2lzdHJhdGlvbiBBUElzCj4+PiArMS4xLjEgc3RydWN0
IHRoZXJtYWxfY29vbGluZ19kZXZpY2UgKmNwdWZyZXFfY29vbGluZ19yZWdpc3RlcigKPj4+ICsg
ICAgIHN0cnVjdCBjcHVtYXNrICpjbGlwX2NwdXMpCj4+PiArCj4+PiArICAgIFRoaXMgaW50ZXJm
YWNlIGZ1bmN0aW9uIHJlZ2lzdGVycyB0aGUgY3B1ZnJlcSBjb29saW5nIGRldmljZSB3aXRoIHRo
ZSBuYW1lCj4+PiArICAgICJ0aGVybWFsLWNwdWZyZXEtJXgiLiBUaGlzIGFwaSBjYW4gc3VwcG9y
dCBtdWx0aXBsZSBpbnN0YW5jZXMgb2YgY3B1ZnJlcQo+Pj4gKyAgICBjb29saW5nIGRldmljZXMu
Cj4+PiArCj4+PiArICAgY2xpcF9jcHVzOiBjcHVtYXNrIG9mIGNwdXMgd2hlcmUgdGhlIGZyZXF1
ZW5jeSBjb25zdHJhaW50cyB3aWxsIGhhcHBlbi4KPj4+ICsKPj4+ICsxLjEuMiB2b2lkIGNwdWZy
ZXFfY29vbGluZ191bnJlZ2lzdGVyKHN0cnVjdCB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjZGV2
KQo+Pj4gKwo+Pj4gKyAgICBUaGlzIGludGVyZmFjZSBmdW5jdGlvbiB1bnJlZ2lzdGVycyB0aGUg
InRoZXJtYWwtY3B1ZnJlcS0leCIgY29vbGluZyBkZXZpY2UuCj4+PiArCj4+PiArICAgIGNkZXY6
IENvb2xpbmcgZGV2aWNlIHBvaW50ZXIgd2hpY2ggaGFzIHRvIGJlIHVucmVnaXN0ZXJlZC4KPj4+
ICsKPj4+ICsKPj4+ICsxLjIgQ1BVIGNvb2xpbmcgYWN0aW9uIG5vdGlmaWVyIHJlZ2lzdGVyL3Vu
cmVnaXN0ZXIgaW50ZXJmYWNlCj4+PiArMS4yLjEgaW50IGNwdXRoZXJtX3JlZ2lzdGVyX25vdGlm
aWVyKHN0cnVjdCBub3RpZmllcl9ibG9jayAqbmIsCj4+PiArICAgICB1bnNpZ25lZCBpbnQgbGlz
dCkKPj4+ICsKPj4+ICsgICAgVGhpcyBpbnRlcmZhY2UgcmVnaXN0ZXJzIGEgZHJpdmVyIHdpdGgg
Y3B1IGNvb2xpbmcgbGF5ZXIuIFRoZSBkcml2ZXIgd2lsbAo+Pj4gKyAgICBiZSBub3RpZmllZCB3
aGVuIGFueSBjcHUgY29vbGluZyBhY3Rpb24gaXMgY2FsbGVkLgo+Pj4gKwo+Pj4gKyAgICBuYjog
bm90aWZpZXIgZnVuY3Rpb24gdG8gcmVnaXN0ZXIKPj4+ICsgICAgbGlzdDogQ1BVRlJFUV9DT09M
SU5HX1NUQVJUIG9yIENQVUZSRVFfQ09PTElOR19TVE9QCj4+PiArCj4+PiArMS4yLjIgaW50IGNw
dXRoZXJtX3VucmVnaXN0ZXJfbm90aWZpZXIoc3RydWN0IG5vdGlmaWVyX2Jsb2NrICpuYiwKPj4+
ICsgICAgIHVuc2lnbmVkIGludCBsaXN0KQo+Pj4gKwo+Pj4gKyAgICBUaGlzIGludGVyZmFjZSBy
ZWdpc3RlcnMgYSBkcml2ZXIgd2l0aCBjcHUgY29vbGluZyBsYXllci4gVGhlIGRyaXZlciB3aWxs
Cj4+PiArICAgIGJlIG5vdGlmaWVkIHdoZW4gYW55IGNwdSBjb29saW5nIGFjdGlvbiBpcyBjYWxs
ZWQuCj4+PiArCj4+PiArICAgIG5iOiBub3RpZmllciBmdW5jdGlvbiB0byByZWdpc3Rlcgo+Pj4g
KyAgICBsaXN0OiBDUFVGUkVRX0NPT0xJTkdfU1RBUlQgb3IgQ1BVRlJFUV9DT09MSU5HX1NUT1AK
Pj4KPj4gd2hhdCBhcmUgdGhlc2UgdHdvIEFQSXMgdXNlZCBmb3I/Cj4+IEkgZGlkIG5vdCBzZWUg
dGhleSBhcmUgdXNlZCBpbiB5b3VyIHBhdGNoIHNldCwgZG8gSSBtaXNzIHNvbWV0aGluZz8KPiBO
byBjdXJyZW50bHkgdGhleSBhcmUgbm90IHVzZWQgYnkgbXkgcGF0Y2hlcy4gSSBhZGRlZCB0aGVt
IG9uIHJlcXVlc3QKPiBmcm9tIEVkdWFyZG8gYW5kIG90aGVycwoKWWVhaCwgdGhpcyB3YXMgYSBz
dWdnZXN0aW9uIGFzIHdlIGRpZG4ndCByZWFsbHkga25vdyBob3cgdGhlIEZXIHBhcnQKd291bGQg
ZXZvbHZlIGJ5IHRoYXQgdGltZS4KClRoZSByZWFzb25pbmcgaXMgdG8gYWxsb3cgYW55IGludGVy
ZXN0ZWQgdXNlciAoaW4ga2VybmVsKSB0byBiZQpub3RpZmllZCB3aGVuIG1heCBmcmVxdWVuY3kg
Y2hhbmdlcy4gQWN0dWFsbHksIHRoZSB1c2UgY2FzZSBiZWhpbmQKdGhpcyBpcyB0byBhbGxvdyBz
dWNoIHVzZXJzIHRvIHBlcmZvcm0gc29tZSBoYW5kc2hha2luZyBvciBzdG9wIHRoZWlyCmFjdGl2
aXRpZXMgb3IgZXZlbiBjaGFuZ2Ugc29tZSBwYXJhbWVudGVycywgaW4gY2FzZSB0aGUgbWF4IGZy
ZXF1ZW5jeQp3b3VsZCBjaGFuZ2UuIElkZWFsbHkgaXQgd291bGQgYmUgcG9zc2libGUgdG8gbmFj
ayB0aGUgY29vbGluZwp0cmFuc2l0aW9uLiBCdXQgdGhhdCBpcyB5ZXQgYSB3aWRlciBkaXNjdXNz
aW9uLiBTbyBmYXIgd2UgZG9uJ3QgaGF2ZQp1c2VycyBmb3IgdGhpcy4KCj4+Cj4+PiBkaWZmIC0t
Z2l0IGEvZHJpdmVycy90aGVybWFsL0tjb25maWcgYi9kcml2ZXJzL3RoZXJtYWwvS2NvbmZpZwo+
Pj4gaW5kZXggN2RkOGMzNC4uOTk2MDAzYiAxMDA2NDQKPj4+IC0tLSBhL2RyaXZlcnMvdGhlcm1h
bC9LY29uZmlnCj4+PiArKysgYi9kcml2ZXJzL3RoZXJtYWwvS2NvbmZpZwo+Pj4gQEAgLTE5LDYg
KzE5LDE3IEBAIGNvbmZpZyBUSEVSTUFMX0hXTU9OCj4+PiAgICAgICBkZXBlbmRzIG9uIEhXTU9O
PXkgfHwgSFdNT049VEhFUk1BTAo+Pj4gICAgICAgZGVmYXVsdCB5Cj4+Pgo+Pj4gK2NvbmZpZyBD
UFVfVEhFUk1BTAo+Pj4gKyAgICAgYm9vbCAiZ2VuZXJpYyBjcHUgY29vbGluZyBzdXBwb3J0Igo+
Pj4gKyAgICAgZGVwZW5kcyBvbiBUSEVSTUFMICYmIENQVV9GUkVRCj4+PiArICAgICBoZWxwCj4+
PiArICAgICAgIFRoaXMgaW1wbGVtZW50cyB0aGUgZ2VuZXJpYyBjcHUgY29vbGluZyBtZWNoYW5p
c20gdGhyb3VnaCBmcmVxdWVuY3kKPj4+ICsgICAgICAgcmVkdWN0aW9uLCBjcHUgaG90cGx1ZyBh
bmQgYW55IG90aGVyIHdheXMgb2YgcmVkdWNpbmcgdGVtcGVyYXR1cmUuIEFuCj4+PiArICAgICAg
IEFDUEkgdmVyc2lvbiBvZiB0aGlzIGFscmVhZHkgZXhpc3RzKGRyaXZlcnMvYWNwaS9wcm9jZXNz
b3JfdGhlcm1hbC5jKS4KPj4+ICsgICAgICAgVGhpcyB3aWxsIGJlIHVzZWZ1bCBmb3IgcGxhdGZv
cm1zIHVzaW5nIHRoZSBnZW5lcmljIHRoZXJtYWwgaW50ZXJmYWNlCj4+PiArICAgICAgIGFuZCBu
b3QgdGhlIEFDUEkgaW50ZXJmYWNlLgo+Pj4gKyAgICAgICBJZiB5b3Ugd2FudCB0aGlzIHN1cHBv
cnQsIHlvdSBzaG91bGQgc2F5IFkgaGVyZS4KPj4+ICsKPj4+ICBjb25maWcgU1BFQVJfVEhFUk1B
TAo+Pj4gICAgICAgYm9vbCAiU1BFQXIgdGhlcm1hbCBzZW5zb3IgZHJpdmVyIgo+Pj4gICAgICAg
ZGVwZW5kcyBvbiBUSEVSTUFMCj4+PiBkaWZmIC0tZ2l0IGEvZHJpdmVycy90aGVybWFsL01ha2Vm
aWxlIGIvZHJpdmVycy90aGVybWFsL01ha2VmaWxlCj4+PiBpbmRleCBmZDkzNjlhLi5hYWU1OWFk
IDEwMDY0NAo+Pj4gLS0tIGEvZHJpdmVycy90aGVybWFsL01ha2VmaWxlCj4+PiArKysgYi9kcml2
ZXJzL3RoZXJtYWwvTWFrZWZpbGUKPj4+IEBAIC0zLDUgKzMsNiBAQAo+Pj4gICMKPj4+Cj4+PiAg
b2JqLSQoQ09ORklHX1RIRVJNQUwpICAgICAgICAgICAgICAgICs9IHRoZXJtYWxfc3lzLm8KPj4+
ICtvYmotJChDT05GSUdfQ1BVX1RIRVJNQUwpICAgICAgICAgICAgKz0gY3B1X2Nvb2xpbmcubwo+
Pj4gIG9iai0kKENPTkZJR19TUEVBUl9USEVSTUFMKSAgICAgICAgICArPSBzcGVhcl90aGVybWFs
Lm8KPj4+ICBvYmotJChDT05GSUdfUkNBUl9USEVSTUFMKSAgICs9IHJjYXJfdGhlcm1hbC5vCj4+
PiBkaWZmIC0tZ2l0IGEvZHJpdmVycy90aGVybWFsL2NwdV9jb29saW5nLmMgYi9kcml2ZXJzL3Ro
ZXJtYWwvY3B1X2Nvb2xpbmcuYwo+Pj4gbmV3IGZpbGUgbW9kZSAxMDA2NDQKPj4+IGluZGV4IDAw
MDAwMDAuLmM0MmU1NTcKPj4+IC0tLSAvZGV2L251bGwKPj4+ICsrKyBiL2RyaXZlcnMvdGhlcm1h
bC9jcHVfY29vbGluZy5jCj4+PiBAQCAtMCwwICsxLDUxMiBAQAo+Pj4gKy8qCj4+PiArICogIGxp
bnV4L2RyaXZlcnMvdGhlcm1hbC9jcHVfY29vbGluZy5jCj4+PiArICoKPj4+ICsgKiAgQ29weXJp
Z2h0IChDKSAyMDEyICAgICAgIFNhbXN1bmcgRWxlY3Ryb25pY3MgQ28uLCBMdGQoaHR0cDovL3d3
dy5zYW1zdW5nLmNvbSkKPj4+ICsgKiAgQ29weXJpZ2h0IChDKSAyMDEyICBBbWl0IERhbmllbCA8
YW1pdC5rYWNoaGFwQGxpbmFyby5vcmc+Cj4+PiArICoKPj4+ICsgKiB+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fgo+Pj4gKyAqICBUaGlzIHByb2dyYW0gaXMgZnJlZSBzb2Z0d2FyZTsgeW91IGNhbiByZWRpc3Ry
aWJ1dGUgaXQgYW5kL29yIG1vZGlmeQo+Pj4gKyAqICBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhl
IEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieQo+Pj4gKyAqICB0aGUg
RnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uOyB2ZXJzaW9uIDIgb2YgdGhlIExpY2Vuc2UuCj4+PiAr
ICoKPj4+ICsgKiAgVGhpcyBwcm9ncmFtIGlzIGRpc3RyaWJ1dGVkIGluIHRoZSBob3BlIHRoYXQg
aXQgd2lsbCBiZSB1c2VmdWwsIGJ1dAo+Pj4gKyAqICBXSVRIT1VUIEFOWSBXQVJSQU5UWTsgd2l0
aG91dCBldmVuIHRoZSBpbXBsaWVkIHdhcnJhbnR5IG9mCj4+PiArICogIE1FUkNIQU5UQUJJTElU
WSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUgR05VCj4+PiAr
ICogIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgZm9yIG1vcmUgZGV0YWlscy4KPj4+ICsgKgo+Pj4g
KyAqICBZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQ
dWJsaWMgTGljZW5zZSBhbG9uZwo+Pj4gKyAqICB3aXRoIHRoaXMgcHJvZ3JhbTsgaWYgbm90LCB3
cml0ZSB0byB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBJbmMuLAo+Pj4gKyAqICA1OSBU
ZW1wbGUgUGxhY2UsIFN1aXRlIDMzMCwgQm9zdG9uLCBNQSAwMjExMS0xMzA3IFVTQS4KPj4+ICsg
Kgo+Pj4gKyAqIH5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+Cj4+PiArICovCj4+PiArI2luY2x1ZGUgPGxpbnV4
L2tlcm5lbC5oPgo+Pj4gKyNpbmNsdWRlIDxsaW51eC9tb2R1bGUuaD4KPj4+ICsjaW5jbHVkZSA8
bGludXgvdGhlcm1hbC5oPgo+Pj4gKyNpbmNsdWRlIDxsaW51eC9wbGF0Zm9ybV9kZXZpY2UuaD4K
Pj4+ICsjaW5jbHVkZSA8bGludXgvY3B1ZnJlcS5oPgo+Pj4gKyNpbmNsdWRlIDxsaW51eC9lcnIu
aD4KPj4+ICsjaW5jbHVkZSA8bGludXgvc2xhYi5oPgo+Pj4gKyNpbmNsdWRlIDxsaW51eC9jcHUu
aD4KPj4+ICsjaW5jbHVkZSA8bGludXgvY3B1X2Nvb2xpbmcuaD4KPj4+ICsKPj4+ICsvKioKPj4+
ICsgKiBzdHJ1Y3QgY3B1ZnJlcV9jb29saW5nX2RldmljZQo+Pj4gKyAqIEBpZDogdW5pcXVlIGlu
dGVnZXIgdmFsdWUgY29ycmVzcG9uZGluZyB0byBlYWNoIGNwdWZyZXFfY29vbGluZ19kZXZpY2UK
Pj4+ICsgKiAgIHJlZ2lzdGVyZWQuCj4+PiArICogQGNvb2xfZGV2OiB0aGVybWFsX2Nvb2xpbmdf
ZGV2aWNlIHBvaW50ZXIgdG8ga2VlcCB0cmFjayBvZiB0aGUgdGhlCj4+PiArICogICBlZ2lzdGVy
ZWQgY29vbGluZyBkZXZpY2UuCj4+PiArICogQGNwdWZyZXFfc3RhdGU6IGludGVnZXIgdmFsdWUg
cmVwcmVzZW50aW5nIHRoZSBjdXJyZW50IHN0YXRlIG9mIGNwdWZyZXEKPj4+ICsgKiAgIGNvb2xp
bmcgZGV2aWNlcy4KPj4+ICsgKiBAY3B1ZnJlcV92YWw6IGludGVnZXIgdmFsdWUgcmVwcmVzZW50
aW5nIHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiB0aGUgY2xpcHBlZAo+Pj4gKyAqICAgZnJlcXVlbmN5
Lgo+Pj4gKyAqIEBhbGxvd2VkX2NwdXM6IGFsbCB0aGUgY3B1cyBpbnZvbHZlZCBmb3IgdGhpcyBj
cHVmcmVxX2Nvb2xpbmdfZGV2aWNlLgo+Pj4gKyAqIEBub2RlOiBsaXN0X2hlYWQgdG8gbGluayBh
bGwgY3B1ZnJlcV9jb29saW5nX2RldmljZSB0b2dldGhlci4KPj4+ICsgKgo+Pj4gKyAqIFRoaXMg
c3RydWN0dXJlIGlzIHJlcXVpcmVkIGZvciBrZWVwaW5nIGluZm9ybWF0aW9uIG9mIGVhY2gKPj4+
ICsgKiBjcHVmcmVxX2Nvb2xpbmdfZGV2aWNlIHJlZ2lzdGVyZWQgYXMgYSBsaXN0IHdob3NlIGhl
YWQgaXMgcmVwcmVzZW50ZWQgYnkKPj4+ICsgKiBjb29saW5nX2NwdWZyZXFfbGlzdC4gSW4gb3Jk
ZXIgdG8gcHJldmVudCBjb3JydXB0aW9uIG9mIHRoaXMgbGlzdCBhCj4+PiArICogbXV0ZXggbG9j
ayBjb29saW5nX2NwdWZyZXFfbG9jayBpcyB1c2VkLgo+Pj4gKyAqLwo+Pj4gK3N0cnVjdCBjcHVm
cmVxX2Nvb2xpbmdfZGV2aWNlIHsKPj4+ICsgICAgIGludCBpZDsKPj4+ICsgICAgIHN0cnVjdCB0
aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjb29sX2RldjsKPj4+ICsgICAgIHVuc2lnbmVkIGludCBj
cHVmcmVxX3N0YXRlOwo+Pj4gKyAgICAgdW5zaWduZWQgaW50IGNwdWZyZXFfdmFsOwo+Pj4gKyAg
ICAgc3RydWN0IGNwdW1hc2sgYWxsb3dlZF9jcHVzOwo+Pj4gKyAgICAgc3RydWN0IGxpc3RfaGVh
ZCBub2RlOwo+Pj4gK307Cj4+PiArc3RhdGljIExJU1RfSEVBRChjb29saW5nX2NwdWZyZXFfbGlz
dCk7Cj4+PiArc3RhdGljIERFRklORV9JRFIoY3B1ZnJlcV9pZHIpOwo+Pj4gKwo+Pj4gK3N0YXRp
YyBzdHJ1Y3QgbXV0ZXggY29vbGluZ19jcHVmcmVxX2xvY2s7Cj4+PiArCj4+PiArLyogbm90aWZ5
X3RhYmxlIHBhc3NlcyB2YWx1ZSB0byB0aGUgQ1BVRlJFUV9BREpVU1QgY2FsbGJhY2sgZnVuY3Rp
b24uICovCj4+PiArI2RlZmluZSBOT1RJRllfSU5WQUxJRCBOVUxMCj4+PiArc3RydWN0IGNwdWZy
ZXFfY29vbGluZ19kZXZpY2UgKm5vdGlmeV9kZXZpY2U7Cj4+PiArCj4+PiArLyogSGVhZCBvZiB0
aGUgYmxvY2tpbmcgbm90aWZpZXIgY2hhaW4gdG8gaW5mb3JtIGFib3V0IGZyZXF1ZW5jeSBjbGFt
cGluZyAqLwo+Pj4gK3N0YXRpYyBCTE9DS0lOR19OT1RJRklFUl9IRUFEKGNwdXRoZXJtX3N0YXRl
X25vdGlmaWVyX2xpc3QpOwo+Pj4gKwo+Pj4gKy8qKgo+Pj4gKyAqIGdldF9pZHIgLSBmdW5jdGlv
biB0byBnZXQgYSB1bmlxdWUgaWQuCj4+PiArICogQGlkcjogc3RydWN0IGlkciAqIGhhbmRsZSB1
c2VkIHRvIGNyZWF0ZSBhIGlkLgo+Pj4gKyAqIEBpZDogaW50ICogdmFsdWUgZ2VuZXJhdGVkIGJ5
IHRoaXMgZnVuY3Rpb24uCj4+PiArICovCj4+PiArc3RhdGljIGludCBnZXRfaWRyKHN0cnVjdCBp
ZHIgKmlkciwgaW50ICppZCkKPj4+ICt7Cj4+PiArICAgICBpbnQgZXJyOwo+Pj4gK2FnYWluOgo+
Pj4gKyAgICAgaWYgKHVubGlrZWx5KGlkcl9wcmVfZ2V0KGlkciwgR0ZQX0tFUk5FTCkgPT0gMCkp
Cj4+PiArICAgICAgICAgICAgIHJldHVybiAtRU5PTUVNOwo+Pj4gKwo+Pj4gKyAgICAgbXV0ZXhf
bG9jaygmY29vbGluZ19jcHVmcmVxX2xvY2spOwo+Pj4gKyAgICAgZXJyID0gaWRyX2dldF9uZXco
aWRyLCBOVUxMLCBpZCk7Cj4+PiArICAgICBtdXRleF91bmxvY2soJmNvb2xpbmdfY3B1ZnJlcV9s
b2NrKTsKPj4+ICsKPj4+ICsgICAgIGlmICh1bmxpa2VseShlcnIgPT0gLUVBR0FJTikpCj4+PiAr
ICAgICAgICAgICAgIGdvdG8gYWdhaW47Cj4+PiArICAgICBlbHNlIGlmICh1bmxpa2VseShlcnIp
KQo+Pj4gKyAgICAgICAgICAgICByZXR1cm4gZXJyOwo+Pj4gKwo+Pj4gKyAgICAgKmlkID0gKmlk
ICYgTUFYX0lEX01BU0s7Cj4+PiArICAgICByZXR1cm4gMDsKPj4+ICt9Cj4+PiArCj4+PiArLyoq
Cj4+PiArICogcmVsZWFzZV9pZHIgLSBmdW5jdGlvbiB0byBmcmVlIHRoZSB1bmlxdWUgaWQuCj4+
PiArICogQGlkcjogc3RydWN0IGlkciAqIGhhbmRsZSB1c2VkIGZvciBjcmVhdGluZyB0aGUgaWQu
Cj4+PiArICogQGlkOiBpbnQgdmFsdWUgcmVwcmVzZW50aW5nIHRoZSB1bmlxdWUgaWQuCj4+PiAr
ICovCj4+PiArc3RhdGljIHZvaWQgcmVsZWFzZV9pZHIoc3RydWN0IGlkciAqaWRyLCBpbnQgaWQp
Cj4+PiArewo+Pj4gKyAgICAgbXV0ZXhfbG9jaygmY29vbGluZ19jcHVmcmVxX2xvY2spOwo+Pj4g
KyAgICAgaWRyX3JlbW92ZShpZHIsIGlkKTsKPj4+ICsgICAgIG11dGV4X3VubG9jaygmY29vbGlu
Z19jcHVmcmVxX2xvY2spOwo+Pj4gK30KPj4+ICsKPj4+ICsvKioKPj4+ICsgKiBjcHV0aGVybV9y
ZWdpc3Rlcl9ub3RpZmllciAtIFJlZ2lzdGVyIGEgbm90aWZpZXIgd2l0aCBjcHUgY29vbGluZyBp
bnRlcmZhY2UuCj4+PiArICogQG5iOiAgICAgIHN0cnVjdCBub3RpZmllcl9ibG9jayAqIHdpdGgg
Y2FsbGJhY2sgaW5mby4KPj4+ICsgKiBAbGlzdDogaW50ZWdlciB2YWx1ZSBmb3Igd2hpY2ggbm90
aWZpY2F0aW9uIGlzIG5lZWRlZC4gcG9zc2libGUgdmFsdWVzIGFyZQo+Pj4gKyAqICAgQ1BVRlJF
UV9DT09MSU5HX1NUQVJUIGFuZCBDUFVGUkVRX0NPT0xJTkdfU1RPUC4KPj4+ICsgKgo+Pj4gKyAq
IFRoaXMgZXhwb3J0ZWQgZnVuY3Rpb24gcmVnaXN0ZXJzIGEgZHJpdmVyIHdpdGggY3B1IGNvb2xp
bmcgbGF5ZXIuIFRoZSBkcml2ZXIKPj4+ICsgKiB3aWxsIGJlIG5vdGlmaWVkIHdoZW4gYW55IGNw
dSBjb29saW5nIGFjdGlvbiBpcyBjYWxsZWQuCj4+PiArICovCj4+PiAraW50IGNwdXRoZXJtX3Jl
Z2lzdGVyX25vdGlmaWVyKHN0cnVjdCBub3RpZmllcl9ibG9jayAqbmIsIHVuc2lnbmVkIGludCBs
aXN0KQo+Pj4gK3sKPj4+ICsgICAgIGludCByZXQgPSAwOwo+Pj4gKwo+Pj4gKyAgICAgc3dpdGNo
IChsaXN0KSB7Cj4+PiArICAgICBjYXNlIENQVUZSRVFfQ09PTElOR19TVEFSVDoKPj4+ICsgICAg
IGNhc2UgQ1BVRlJFUV9DT09MSU5HX1NUT1A6Cj4+PiArICAgICAgICAgICAgIHJldCA9IGJsb2Nr
aW5nX25vdGlmaWVyX2NoYWluX3JlZ2lzdGVyKAo+Pj4gKyAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgJmNwdXRoZXJtX3N0YXRlX25vdGlmaWVyX2xpc3QsIG5iKTsKPj4+ICsgICAgICAgICAg
ICAgYnJlYWs7Cj4+PiArICAgICBkZWZhdWx0Ogo+Pj4gKyAgICAgICAgICAgICByZXQgPSAtRUlO
VkFMOwo+Pj4gKyAgICAgfQo+Pj4gKyAgICAgcmV0dXJuIHJldDsKPj4+ICt9Cj4+PiArRVhQT1JU
X1NZTUJPTChjcHV0aGVybV9yZWdpc3Rlcl9ub3RpZmllcik7Cj4+PiArCj4+PiArLyoqCj4+PiAr
ICogY3B1dGhlcm1fdW5yZWdpc3Rlcl9ub3RpZmllciAtIFVuLXJlZ2lzdGVyIGEgbm90aWZpZXIu
Cj4+PiArICogQG5iOiAgICAgIHN0cnVjdCBub3RpZmllcl9ibG9jayAqIHdpdGggY2FsbGJhY2sg
aW5mby4KPj4+ICsgKiBAbGlzdDogaW50ZWdlciB2YWx1ZSBmb3Igd2hpY2ggbm90aWZpY2F0aW9u
IGlzIG5lZWRlZC4gdmFsdWVzIHBvc3NpYmxlIGFyZQo+Pj4gKyAqICAgQ1BVRlJFUV9DT09MSU5H
X1NUQVJUIG9yIENQVUZSRVFfQ09PTElOR19TVE9QLgo+Pj4gKyAqCj4+PiArICogVGhpcyBleHBv
cnRlZCBmdW5jdGlvbiB1bi1yZWdpc3RlcnMgYSBkcml2ZXIgd2l0aCBjcHUgY29vbGluZyBsYXll
ci4KPj4+ICsgKi8KPj4+ICtpbnQgY3B1dGhlcm1fdW5yZWdpc3Rlcl9ub3RpZmllcihzdHJ1Y3Qg
bm90aWZpZXJfYmxvY2sgKm5iLCB1bnNpZ25lZCBpbnQgbGlzdCkKPj4+ICt7Cj4+PiArICAgICBp
bnQgcmV0ID0gMDsKPj4+ICsKPj4+ICsgICAgIHN3aXRjaCAobGlzdCkgewo+Pj4gKyAgICAgY2Fz
ZSBDUFVGUkVRX0NPT0xJTkdfU1RBUlQ6Cj4+PiArICAgICBjYXNlIENQVUZSRVFfQ09PTElOR19T
VE9QOgo+Pj4gKyAgICAgICAgICAgICByZXQgPSBibG9ja2luZ19ub3RpZmllcl9jaGFpbl91bnJl
Z2lzdGVyKAo+Pj4gKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJmNwdXRoZXJtX3N0YXRl
X25vdGlmaWVyX2xpc3QsIG5iKTsKPj4+ICsgICAgICAgICAgICAgYnJlYWs7Cj4+PiArICAgICBk
ZWZhdWx0Ogo+Pj4gKyAgICAgICAgICAgICByZXQgPSAtRUlOVkFMOwo+Pj4gKyAgICAgfQo+Pj4g
KyAgICAgcmV0dXJuIHJldDsKPj4+ICt9Cj4+PiArRVhQT1JUX1NZTUJPTChjcHV0aGVybV91bnJl
Z2lzdGVyX25vdGlmaWVyKTsKPj4+ICsKPj4+ICsvKiBCZWxvdyBjb2RlIGRlZmluZXMgZnVuY3Rp
b25zIHRvIGJlIHVzZWQgZm9yIGNwdWZyZXEgYXMgY29vbGluZyBkZXZpY2UgKi8KPj4+ICsKPj4+
ICsvKioKPj4+ICsgKiBpc19jcHVmcmVxX3ZhbGlkIC0gZnVuY3Rpb24gdG8gY2hlY2sgaWYgYSBj
cHUgaGFzIGZyZXF1ZW5jeSB0cmFuc2l0aW9uIHBvbGljeS4KPj4+ICsgKiBAY3B1OiBjcHUgZm9y
IHdoaWNoIGNoZWNrIGlzIG5lZWRlZC4KPj4+ICsgKi8KPj4+ICtzdGF0aWMgaW50IGlzX2NwdWZy
ZXFfdmFsaWQoaW50IGNwdSkKPj4+ICt7Cj4+PiArICAgICBzdHJ1Y3QgY3B1ZnJlcV9wb2xpY3kg
cG9saWN5Owo+Pj4gKyAgICAgcmV0dXJuICFjcHVmcmVxX2dldF9wb2xpY3koJnBvbGljeSwgY3B1
KTsKPj4+ICt9Cj4+PiArCj4+PiArLyoqCj4+PiArICogZ2V0X2NwdV9mcmVxdWVuY3kgLSBnZXQg
dGhlIGFic29sdXRlIHZhbHVlIG9mIGZyZXF1ZW5jeSBmcm9tIGxldmVsLgo+Pj4gKyAqIEBjcHU6
IGNwdSBmb3Igd2hpY2ggZnJlcXVlbmN5IGlzIGZldGNoZWQuCj4+PiArICogQGxldmVsOiBsZXZl
bCBvZiBmcmVxdWVuY3kgb2YgdGhlIENQVQo+Pj4gKyAqICAgZS5nIGxldmVsPTEgLS0+IDFzdCBN
QVggRlJFUSwgTEVWRUw9MiAtLS0+IDJuZCBNQVggRlJFUSwgLi4uLiBldGMKPj4+ICsgKi8KPj4+
ICtzdGF0aWMgdW5zaWduZWQgaW50IGdldF9jcHVfZnJlcXVlbmN5KHVuc2lnbmVkIGludCBjcHUs
IHVuc2lnbmVkIGxvbmcgbGV2ZWwpCj4+PiArewo+Pj4gKyAgICAgaW50IHJldCA9IDAsIGkgPSAw
Owo+Pj4gKyAgICAgdW5zaWduZWQgbG9uZyBsZXZlbF9pbmRleDsKPj4+ICsgICAgIGJvb2wgZGVz
Y2VuZCA9IGZhbHNlOwo+Pj4gKyAgICAgc3RydWN0IGNwdWZyZXFfZnJlcXVlbmN5X3RhYmxlICp0
YWJsZSA9Cj4+PiArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNwdWZyZXFf
ZnJlcXVlbmN5X2dldF90YWJsZShjcHUpOwo+Pj4gKyAgICAgaWYgKCF0YWJsZSkKPj4+ICsgICAg
ICAgICAgICAgcmV0dXJuIHJldDsKPj4+ICsKPj4+ICsgICAgIHdoaWxlICh0YWJsZVtpXS5mcmVx
dWVuY3kgIT0gQ1BVRlJFUV9UQUJMRV9FTkQpIHsKPj4+ICsgICAgICAgICAgICAgaWYgKHRhYmxl
W2ldLmZyZXF1ZW5jeSA9PSBDUFVGUkVRX0VOVFJZX0lOVkFMSUQpCj4+PiArICAgICAgICAgICAg
ICAgICAgICAgY29udGludWU7Cj4+PiArCj4+PiArICAgICAgICAgICAgIC8qY2hlY2sgaWYgdGFi
bGUgaW4gYXNjZW5kaW5nIG9yIGRlc2NlbmRpbmcgb3JkZXIqLwo+Pj4gKyAgICAgICAgICAgICBp
ZiAoKHRhYmxlW2kgKyAxXS5mcmVxdWVuY3kgIT0gQ1BVRlJFUV9UQUJMRV9FTkQpICYmCj4+PiAr
ICAgICAgICAgICAgICAgICAgICAgKHRhYmxlW2kgKyAxXS5mcmVxdWVuY3kgPCB0YWJsZVtpXS5m
cmVxdWVuY3kpCj4+PiArICAgICAgICAgICAgICAgICAgICAgJiYgIWRlc2NlbmQpIHsKPj4+ICsg
ICAgICAgICAgICAgICAgICAgICBkZXNjZW5kID0gdHJ1ZTsKPj4+ICsgICAgICAgICAgICAgfQo+
Pj4gKwo+Pj4gKyAgICAgICAgICAgICAvKnJldHVybiBpZiBsZXZlbCBtYXRjaGVkIGFuZCB0YWJs
ZSBpbiBkZXNjZW5kaW5nIG9yZGVyKi8KPj4+ICsgICAgICAgICAgICAgaWYgKGRlc2NlbmQgJiYg
aSA9PSBsZXZlbCkKPj4+ICsgICAgICAgICAgICAgICAgICAgICByZXR1cm4gdGFibGVbaV0uZnJl
cXVlbmN5Owo+Pj4gKyAgICAgICAgICAgICBpKys7Cj4+PiArICAgICB9Cj4+PiArICAgICBpLS07
Cj4+PiArCj4+PiArICAgICBpZiAobGV2ZWwgPiBpIHx8IGRlc2NlbmQpCj4+PiArICAgICAgICAg
ICAgIHJldHVybiByZXQ7Cj4+PiArICAgICBsZXZlbF9pbmRleCA9IGkgLSBsZXZlbDsKPj4+ICsK
Pj4+ICsgICAgIC8qU2NhbiB0aGUgdGFibGUgaW4gcmV2ZXJzZSBvcmRlciBhbmQgbWF0Y2ggdGhl
IGxldmVsKi8KPj4+ICsgICAgIHdoaWxlIChpID49IDApIHsKPj4+ICsgICAgICAgICAgICAgaWYg
KHRhYmxlW2ldLmZyZXF1ZW5jeSA9PSBDUFVGUkVRX0VOVFJZX0lOVkFMSUQpCj4+PiArICAgICAg
ICAgICAgICAgICAgICAgY29udGludWU7Cj4+PiArICAgICAgICAgICAgIC8qcmV0dXJuIGlmIGxl
dmVsIG1hdGNoZWQqLwo+Pj4gKyAgICAgICAgICAgICBpZiAoaSA9PSBsZXZlbF9pbmRleCkKPj4+
ICsgICAgICAgICAgICAgICAgICAgICByZXR1cm4gdGFibGVbaV0uZnJlcXVlbmN5Owo+Pj4gKyAg
ICAgICAgICAgICBpLS07Cj4+PiArICAgICB9Cj4+PiArICAgICByZXR1cm4gcmV0Owo+Pj4gK30K
Pj4+ICsKPj4+ICsvKioKPj4+ICsgKiBjcHVmcmVxX2FwcGx5X2Nvb2xpbmcgLSBmdW5jdGlvbiB0
byBhcHBseSBmcmVxdWVuY3kgY2xpcHBpbmcuCj4+PiArICogQGNwdWZyZXFfZGV2aWNlOiBjcHVm
cmVxX2Nvb2xpbmdfZGV2aWNlIHBvaW50ZXIgY29udGFpbmluZyBmcmVxdWVuY3kKPj4+ICsgKiAg
IGNsaXBwaW5nIGRhdGEuCj4+PiArICogQGNvb2xpbmdfc3RhdGU6IHZhbHVlIG9mIHRoZSBjb29s
aW5nIHN0YXRlLgo+Pj4gKyAqLwo+Pj4gK3N0YXRpYyBpbnQgY3B1ZnJlcV9hcHBseV9jb29saW5n
KHN0cnVjdCBjcHVmcmVxX2Nvb2xpbmdfZGV2aWNlICpjcHVmcmVxX2RldmljZSwKPj4+ICsgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgIHVuc2lnbmVkIGxvbmcgY29vbGluZ19zdGF0ZSkKPj4+
ICt7Cj4+PiArICAgICB1bnNpZ25lZCBpbnQgZXZlbnQsIGNwdWlkLCBjbGlwX2ZyZXE7Cj4+PiAr
ICAgICBzdHJ1Y3QgY3B1bWFzayAqbWFza1B0ciA9ICZjcHVmcmVxX2RldmljZS0+YWxsb3dlZF9j
cHVzOwo+Pj4gKyAgICAgdW5zaWduZWQgaW50IGNwdSA9IGNwdW1hc2tfYW55KG1hc2tQdHIpOwo+
Pj4gKwo+Pj4gKwo+Pj4gKyAgICAgLyogQ2hlY2sgaWYgdGhlIG9sZCBjb29saW5nIGFjdGlvbiBp
cyBzYW1lIGFzIG5ldyBjb29saW5nIGFjdGlvbiAqLwo+Pj4gKyAgICAgaWYgKGNwdWZyZXFfZGV2
aWNlLT5jcHVmcmVxX3N0YXRlID09IGNvb2xpbmdfc3RhdGUpCj4+PiArICAgICAgICAgICAgIHJl
dHVybiAwOwo+Pj4gKwo+Pj4gKyAgICAgY2xpcF9mcmVxID0gZ2V0X2NwdV9mcmVxdWVuY3koY3B1
LCBjb29saW5nX3N0YXRlKTsKPj4+ICsgICAgIGlmICghY2xpcF9mcmVxKQo+Pj4gKyAgICAgICAg
ICAgICByZXR1cm4gLUVJTlZBTDsKPj4+ICsKPj4+ICsgICAgIGNwdWZyZXFfZGV2aWNlLT5jcHVm
cmVxX3N0YXRlID0gY29vbGluZ19zdGF0ZTsKPj4+ICsgICAgIGNwdWZyZXFfZGV2aWNlLT5jcHVm
cmVxX3ZhbCA9IGNsaXBfZnJlcTsKPj4+ICsgICAgIG5vdGlmeV9kZXZpY2UgPSBjcHVmcmVxX2Rl
dmljZTsKPj4+ICsKPj4+ICsgICAgIGlmIChjb29saW5nX3N0YXRlICE9IDApCj4+PiArICAgICAg
ICAgICAgIGV2ZW50ID0gQ1BVRlJFUV9DT09MSU5HX1NUQVJUOwo+Pj4gKyAgICAgZWxzZQo+Pj4g
KyAgICAgICAgICAgICBldmVudCA9IENQVUZSRVFfQ09PTElOR19TVE9QOwo+Pj4gKwo+Pj4gKyAg
ICAgYmxvY2tpbmdfbm90aWZpZXJfY2FsbF9jaGFpbigmY3B1dGhlcm1fc3RhdGVfbm90aWZpZXJf
bGlzdCwKPj4+ICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBl
dmVudCwgJmNsaXBfZnJlcSk7Cj4+PiArCj4+PiArICAgICBmb3JfZWFjaF9jcHUoY3B1aWQsIG1h
c2tQdHIpIHsKPj4+ICsgICAgICAgICAgICAgaWYgKGlzX2NwdWZyZXFfdmFsaWQoY3B1aWQpKQo+
Pj4gKyAgICAgICAgICAgICAgICAgICAgIGNwdWZyZXFfdXBkYXRlX3BvbGljeShjcHVpZCk7Cj4+
PiArICAgICB9Cj4+PiArCj4+PiArICAgICBub3RpZnlfZGV2aWNlID0gTk9USUZZX0lOVkFMSUQ7
Cj4+PiArCj4+PiArICAgICByZXR1cm4gMDsKPj4+ICt9Cj4+PiArCj4+PiArLyoqCj4+PiArICog
Y3B1ZnJlcV90aGVybWFsX25vdGlmaWVyIC0gbm90aWZpZXIgY2FsbGJhY2sgZm9yIGNwdWZyZXEg
cG9saWN5IGNoYW5nZS4KPj4+ICsgKiBAbmI6ICAgICAgc3RydWN0IG5vdGlmaWVyX2Jsb2NrICog
d2l0aCBjYWxsYmFjayBpbmZvLgo+Pj4gKyAqIEBldmVudDogdmFsdWUgc2hvd2luZyBjcHVmcmVx
IGV2ZW50IGZvciB3aGljaCB0aGlzIGZ1bmN0aW9uIGludm9rZWQuCj4+PiArICogQGRhdGE6IGNh
bGxiYWNrLXNwZWNpZmljIGRhdGEKPj4+ICsgKi8KPj4+ICtzdGF0aWMgaW50IGNwdWZyZXFfdGhl
cm1hbF9ub3RpZmllcihzdHJ1Y3Qgbm90aWZpZXJfYmxvY2sgKm5iLAo+Pj4gKyAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICB1bnNpZ25lZCBsb25nIGV2ZW50LCB2b2lkICpkYXRh
KQo+Pj4gK3sKPj4+ICsgICAgIHN0cnVjdCBjcHVmcmVxX3BvbGljeSAqcG9saWN5ID0gZGF0YTsK
Pj4+ICsgICAgIHVuc2lnbmVkIGxvbmcgbWF4X2ZyZXEgPSAwOwo+Pj4gKwo+Pj4gKyAgICAgaWYg
KGV2ZW50ICE9IENQVUZSRVFfQURKVVNUIHx8IG5vdGlmeV9kZXZpY2UgPT0gTk9USUZZX0lOVkFM
SUQpCj4+PiArICAgICAgICAgICAgIHJldHVybiAwOwo+Pj4gKwo+Pj4gKyAgICAgaWYgKGNwdW1h
c2tfdGVzdF9jcHUocG9saWN5LT5jcHUsICZub3RpZnlfZGV2aWNlLT5hbGxvd2VkX2NwdXMpKQo+
Pj4gKyAgICAgICAgICAgICBtYXhfZnJlcSA9IG5vdGlmeV9kZXZpY2UtPmNwdWZyZXFfdmFsOwo+
Pj4gKwo+Pj4gKyAgICAgLyogTmV2ZXIgZXhjZWVkIHVzZXJfcG9saWN5Lm1heCovCj4+PiArICAg
ICBpZiAobWF4X2ZyZXEgPiBwb2xpY3ktPnVzZXJfcG9saWN5Lm1heCkKPj4+ICsgICAgICAgICAg
ICAgbWF4X2ZyZXEgPSBwb2xpY3ktPnVzZXJfcG9saWN5Lm1heDsKPj4+ICsKPj4+ICsgICAgIGlm
IChwb2xpY3ktPm1heCAhPSBtYXhfZnJlcSkKPj4+ICsgICAgICAgICAgICAgY3B1ZnJlcV92ZXJp
Znlfd2l0aGluX2xpbWl0cyhwb2xpY3ksIDAsIG1heF9mcmVxKTsKPj4+ICsKPj4+ICsgICAgIHJl
dHVybiAwOwo+Pj4gK30KPj4+ICsKPj4+ICsvKgo+Pj4gKyAqIGNwdWZyZXEgY29vbGluZyBkZXZp
Y2UgY2FsbGJhY2sgZnVuY3Rpb25zIGFyZSBkZWZpbmVkIGJlbG93Cj4+PiArICovCj4+PiArCj4+
PiArLyoqCj4+PiArICogY3B1ZnJlcV9nZXRfbWF4X3N0YXRlIC0gY2FsbGJhY2sgZnVuY3Rpb24g
dG8gZ2V0IHRoZSBtYXggY29vbGluZyBzdGF0ZS4KPj4+ICsgKiBAY2RldjogdGhlcm1hbCBjb29s
aW5nIGRldmljZSBwb2ludGVyLgo+Pj4gKyAqIEBzdGF0ZTogZmlsbCB0aGlzIHZhcmlhYmxlIHdp
dGggdGhlIG1heCBjb29saW5nIHN0YXRlLgo+Pj4gKyAqLwo+Pj4gK3N0YXRpYyBpbnQgY3B1ZnJl
cV9nZXRfbWF4X3N0YXRlKHN0cnVjdCB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjZGV2LAo+Pj4g
KyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVuc2lnbmVkIGxvbmcgKnN0YXRlKQo+Pj4g
K3sKPj4+ICsgICAgIGludCByZXQgPSAtRUlOVkFMLCBpID0gMDsKPj4+ICsgICAgIHN0cnVjdCBj
cHVmcmVxX2Nvb2xpbmdfZGV2aWNlICpjcHVmcmVxX2RldmljZTsKPj4+ICsgICAgIHN0cnVjdCBj
cHVtYXNrICptYXNrUHRyOwo+Pj4gKyAgICAgdW5zaWduZWQgaW50IGNwdTsKPj4+ICsgICAgIHN0
cnVjdCBjcHVmcmVxX2ZyZXF1ZW5jeV90YWJsZSAqdGFibGU7Cj4+PiArCj4+PiArICAgICBtdXRl
eF9sb2NrKCZjb29saW5nX2NwdWZyZXFfbG9jayk7Cj4+PiArICAgICBsaXN0X2Zvcl9lYWNoX2Vu
dHJ5KGNwdWZyZXFfZGV2aWNlLCAmY29vbGluZ19jcHVmcmVxX2xpc3QsIG5vZGUpIHsKPj4+ICsg
ICAgICAgICAgICAgaWYgKGNwdWZyZXFfZGV2aWNlICYmIGNwdWZyZXFfZGV2aWNlLT5jb29sX2Rl
diA9PSBjZGV2KQo+Pj4gKyAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwo+Pj4gKyAgICAgfQo+
Pj4gKyAgICAgaWYgKGNwdWZyZXFfZGV2aWNlID09IE5VTEwpCj4+PiArICAgICAgICAgICAgIGdv
dG8gcmV0dXJuX2dldF9tYXhfc3RhdGU7Cj4+PiArCj4+PiArICAgICBtYXNrUHRyID0gJmNwdWZy
ZXFfZGV2aWNlLT5hbGxvd2VkX2NwdXM7Cj4+PiArICAgICBjcHUgPSBjcHVtYXNrX2FueShtYXNr
UHRyKTsKPj4+ICsgICAgIHRhYmxlID0gY3B1ZnJlcV9mcmVxdWVuY3lfZ2V0X3RhYmxlKGNwdSk7
Cj4+PiArICAgICBpZiAoIXRhYmxlKSB7Cj4+PiArICAgICAgICAgICAgICpzdGF0ZSA9IDA7Cj4+
PiArICAgICAgICAgICAgIHJldCA9IDA7Cj4+PiArICAgICAgICAgICAgIGdvdG8gcmV0dXJuX2dl
dF9tYXhfc3RhdGU7Cj4+PiArICAgICB9Cj4+PiArCj4+PiArICAgICB3aGlsZSAodGFibGVbaV0u
ZnJlcXVlbmN5ICE9IENQVUZSRVFfVEFCTEVfRU5EKSB7Cj4+PiArICAgICAgICAgICAgIGlmICh0
YWJsZVtpXS5mcmVxdWVuY3kgPT0gQ1BVRlJFUV9FTlRSWV9JTlZBTElEKQo+Pj4gKyAgICAgICAg
ICAgICAgICAgICAgIGNvbnRpbnVlOwo+Pj4gKyAgICAgICAgICAgICBpKys7Cj4+PiArICAgICB9
Cj4+PiArICAgICBpZiAoaSA+IDApIHsKPj4+ICsgICAgICAgICAgICAgKnN0YXRlID0gLS1pOwo+
Pj4gKyAgICAgICAgICAgICByZXQgPSAwOwo+Pj4gKyAgICAgfQo+Pj4gKwo+Pj4gK3JldHVybl9n
ZXRfbWF4X3N0YXRlOgo+Pj4gKyAgICAgbXV0ZXhfdW5sb2NrKCZjb29saW5nX2NwdWZyZXFfbG9j
ayk7Cj4+PiArICAgICByZXR1cm4gcmV0Owo+Pj4gK30KPj4+ICsKPj4+ICsvKioKPj4+ICsgKiBj
cHVmcmVxX2dldF9jdXJfc3RhdGUgLSBjYWxsYmFjayBmdW5jdGlvbiB0byBnZXQgdGhlIGN1cnJl
bnQgY29vbGluZyBzdGF0ZS4KPj4+ICsgKiBAY2RldjogdGhlcm1hbCBjb29saW5nIGRldmljZSBw
b2ludGVyLgo+Pj4gKyAqIEBzdGF0ZTogZmlsbCB0aGlzIHZhcmlhYmxlIHdpdGggdGhlIGN1cnJl
bnQgY29vbGluZyBzdGF0ZS4KPj4+ICsgKi8KPj4+ICtzdGF0aWMgaW50IGNwdWZyZXFfZ2V0X2N1
cl9zdGF0ZShzdHJ1Y3QgdGhlcm1hbF9jb29saW5nX2RldmljZSAqY2RldiwKPj4+ICsgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICB1bnNpZ25lZCBsb25nICpzdGF0ZSkKPj4+ICt7Cj4+PiAr
ICAgICBpbnQgcmV0ID0gLUVJTlZBTDsKPj4+ICsgICAgIHN0cnVjdCBjcHVmcmVxX2Nvb2xpbmdf
ZGV2aWNlICpjcHVmcmVxX2RldmljZTsKPj4+ICsKPj4+ICsgICAgIG11dGV4X2xvY2soJmNvb2xp
bmdfY3B1ZnJlcV9sb2NrKTsKPj4+ICsgICAgIGxpc3RfZm9yX2VhY2hfZW50cnkoY3B1ZnJlcV9k
ZXZpY2UsICZjb29saW5nX2NwdWZyZXFfbGlzdCwgbm9kZSkgewo+Pj4gKyAgICAgICAgICAgICBp
ZiAoY3B1ZnJlcV9kZXZpY2UgJiYgY3B1ZnJlcV9kZXZpY2UtPmNvb2xfZGV2ID09IGNkZXYpIHsK
Pj4+ICsgICAgICAgICAgICAgICAgICAgICAqc3RhdGUgPSBjcHVmcmVxX2RldmljZS0+Y3B1ZnJl
cV9zdGF0ZTsKPj4+ICsgICAgICAgICAgICAgICAgICAgICByZXQgPSAwOwo+Pj4gKyAgICAgICAg
ICAgICAgICAgICAgIGJyZWFrOwo+Pj4gKyAgICAgICAgICAgICB9Cj4+PiArICAgICB9Cj4+PiAr
ICAgICBtdXRleF91bmxvY2soJmNvb2xpbmdfY3B1ZnJlcV9sb2NrKTsKPj4+ICsKPj4KPj4gYXMg
Y3B1ZnJlcSBtYXkgYmUgY2hhbmdlZCBpbiBvdGhlciBwbGFjZXMsIGUuZy4gdmlhIHN5c2ZzIEkv
Riwgd2Ugc2hvdWxkCj4+IHVzZSB0aGUgY3VycmVudCBjcHUgZnJlcXVlbmN5IHRvIGdldCB0aGUg
UkVBTCBjb29saW5nIHN0YXRlLCByYXRoZXIgdGhhbgo+PiB1c2luZyBhIGNhY2hlZCB2YWx1ZS4K
Pgo+IFllcyBhZ3JlZWQgLCBJIHdpbGwgcmVwb3N0IHdpdGggeW91ciBzdWdnZXN0aW9uLgo+Cj4g
VGhhbmtzLAo+IEFtaXQKPj4KPj4gdGhhbmtzLAo+PiBydWkKPj4KPj4KPiAtLQo+IFRvIHVuc3Vi
c2NyaWJlIGZyb20gdGhpcyBsaXN0OiBzZW5kIHRoZSBsaW5lICJ1bnN1YnNjcmliZSBsaW51eC1h
Y3BpIiBpbgo+IHRoZSBib2R5IG9mIGEgbWVzc2FnZSB0byBtYWpvcmRvbW9Admdlci5rZXJuZWwu
b3JnCj4gTW9yZSBtYWpvcmRvbW8gaW5mbyBhdCAgaHR0cDovL3ZnZXIua2VybmVsLm9yZy9tYWpv
cmRvbW8taW5mby5odG1sCgoKCi0tIAoKRWR1YXJkbyBWYWxlbnRpbgoKX19fX19fX19fX19fX19f
X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KbG0tc2Vuc29ycyBtYWlsaW5nIGxpc3QK
bG0tc2Vuc29yc0BsbS1zZW5zb3JzLm9yZwpodHRwOi8vbGlzdHMubG0tc2Vuc29ycy5vcmcvbWFp
bG1hbi9saXN0aW5mby9sbS1zZW5zb3Jz

^ permalink raw reply	[flat|nested] 50+ messages in thread

* [RESEND PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-17  7:58       ` Amit Kachhap
  (?)
@ 2012-08-17 13:29         ` Amit Daniel Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-17 13:29 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park,
	Guenter Roeck

This patchset introduces a new generic cooling device based on cpufreq
that can be used on non-ACPI platforms.  As a proof of concept, we have
drivers for the following platforms using this mechanism now:

 * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
 * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
 * Omap platforms in drivers/staging/omap-thermal/

There is a small change in cpufreq cooling registration APIs, so a minor
change is needed for Freescale and omap platforms.

Brief Description:

1) The generic cooling devices code is placed inside driver/thermal/*
   as placing inside acpi folder will need un-necessary enabling of acpi
   code.  This code is architecture independent.

2) This patchset adds generic cpu cooling low level implementation
   through frequency clipping.  In future, other cpu related cooling
   devices may be added here.  An ACPI version of this already exists
   (drivers/acpi/processor_thermal.c) .But this will be useful for
   platforms like ARM using the generic thermal interface along with the
   generic cpu cooling devices.  The cooling device registration API's
   return cooling device pointers which can be easily binded with the
   thermal zone trip points.  The important APIs exposed are,

   a) struct thermal_cooling_device *cpufreq_cooling_register(
        struct cpumask *clip_cpus)
   b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)

3) Samsung exynos platform thermal implementation is done using the
   generic cpu cooling APIs and the new trip type.  The temperature sensor
   driver present in the hwmon folder(registered as hwmon driver) is moved
   to thermal folder and registered as a thermal driver.

A simple data/control flow diagrams is shown below,

Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
          |                             |
         \|/                            |
  Cpufreq cooling device <---------------

About this patch:

Add support for generic cpu thermal cooling low level implementations
using frequency scaling up/down based on the registration parameters.
Different cpu related cooling devices can be registered by the user and
the binding of these cooling devices to the corresponding trip points can
be easily done as the registration APIs return the cooling device pointer.
The user of these APIs are responsible for passing clipping frequency .
The drivers can also register to recieve notification about any cooling
action called.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Cc: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 Documentation/thermal/cpu-cooling-api.txt |   52 +++
 drivers/thermal/Kconfig                   |   11 +
 drivers/thermal/Makefile                  |    1 +
 drivers/thermal/cpu_cooling.c             |  586 +++++++++++++++++++++++++++++
 include/linux/cpu_cooling.h               |   79 ++++
 5 files changed, 729 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/thermal/cpu-cooling-api.txt
 create mode 100644 drivers/thermal/cpu_cooling.c
 create mode 100644 include/linux/cpu_cooling.h

diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
new file mode 100644
index 0000000..a1f2a6b
--- /dev/null
+++ b/Documentation/thermal/cpu-cooling-api.txt
@@ -0,0 +1,52 @@
+CPU cooling APIs How To
+===================================
+
+Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
+
+Updated: 12 May 2012
+
+Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+
+0. Introduction
+
+The generic cpu cooling(freq clipping) provides registration/unregistration APIs
+to the caller. The binding of the cooling devices to the trip point is left for
+the user. The registration APIs returns the cooling device pointer.
+
+1. cpu cooling APIs
+
+1.1 cpufreq registration/unregistration APIs
+1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+
+    This interface function registers the cpufreq cooling device with the name
+    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+    cooling devices.
+
+   clip_cpus: cpumask of cpus where the frequency constraints will happen.
+
+1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+
+    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+
+    cdev: Cooling device pointer which has to be unregistered.
+
+
+1.2 CPU cooling action notifier register/unregister interface
+1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
+
+1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 7dd8c34..996003b 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -19,6 +19,17 @@ config THERMAL_HWMON
 	depends on HWMON=y || HWMON=THERMAL
 	default y
 
+config CPU_THERMAL
+	bool "generic cpu cooling support"
+	depends on THERMAL && CPU_FREQ
+	help
+	  This implements the generic cpu cooling mechanism through frequency
+	  reduction, cpu hotplug and any other ways of reducing temperature. An
+	  ACPI version of this already exists(drivers/acpi/processor_thermal.c).
+	  This will be useful for platforms using the generic thermal interface
+	  and not the ACPI interface.
+	  If you want this support, you should say Y here.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fd9369a..aae59ad 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,5 +3,6 @@
 #
 
 obj-$(CONFIG_THERMAL)		+= thermal_sys.o
+obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 0000000..66cbd52
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,586 @@
+/*
+ *  linux/drivers/thermal/cpu_cooling.c
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+/**
+ * struct cpufreq_cooling_device
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ *	registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the the
+ *	egistered cooling device.
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ *	cooling	devices.
+ * @cpufreq_val: integer value representing the absolute value of the clipped
+ *	frequency.
+ * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ *
+ * This structure is required for keeping information of each
+ * cpufreq_cooling_device registered as a list whose head is represented by
+ * cooling_cpufreq_list. In order to prevent corruption of this list a
+ * mutex lock cooling_cpufreq_lock is used.
+ */
+struct cpufreq_cooling_device {
+	int id;
+	struct thermal_cooling_device *cool_dev;
+	unsigned int cpufreq_state;
+	unsigned int cpufreq_val;
+	struct cpumask allowed_cpus;
+	struct list_head node;
+};
+static LIST_HEAD(cooling_cpufreq_list);
+static DEFINE_IDR(cpufreq_idr);
+
+static struct mutex cooling_cpufreq_lock;
+
+/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
+#define NOTIFY_INVALID NULL
+struct cpufreq_cooling_device *notify_device;
+
+/* Head of the blocking notifier chain to inform about frequency clamping */
+static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+	int err;
+again:
+	if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
+		return -ENOMEM;
+
+	mutex_lock(&cooling_cpufreq_lock);
+	err = idr_get_new(idr, NULL, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	if (unlikely(err == -EAGAIN))
+		goto again;
+	else if (unlikely(err))
+		return err;
+
+	*id = *id & MAX_ID_MASK;
+	return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+	mutex_lock(&cooling_cpufreq_lock);
+	idr_remove(idr, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+}
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_register(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_register_notifier);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_unregister(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_unregister_notifier);
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
+ * @cpu: cpu for which check is needed.
+ */
+static int is_cpufreq_valid(int cpu)
+{
+	struct cpufreq_policy policy;
+	return !cpufreq_get_policy(&policy, 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=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
+ * @return: frequency of the cpu or 0 for error.
+ */
+static unsigned int get_cpu_frequency(unsigned int cpu, unsigned int level)
+{
+	int ret = 0, i = 0;
+	unsigned int level_index;
+	bool descend = false;
+	struct cpufreq_frequency_table *table =
+					cpufreq_frequency_get_table(cpu);
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/*check if table in ascending or descending order*/
+		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+			(table[i + 1].frequency < table[i].frequency)
+			&& !descend) {
+			descend = true;
+		}
+
+		/*return if level matched and table in descending order*/
+		if (descend && i == level)
+			return table[i].frequency;
+		i++;
+	}
+	i--;
+
+	if (level > i || descend)
+		return ret;
+	level_index = i - level;
+
+	/*Scan the table in reverse order and match the level*/
+	while (i >= 0) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		/*return if level matched*/
+		if (i == level_index)
+			return table[i].frequency;
+		i--;
+	}
+	return ret;
+}
+
+/**
+ * get_frequency_level - get the level from the frequency.
+ * @cpu: cpu for which frequency is fetched.
+ * @freq: frequency for which level is requested.
+ *
+ * @return: level corresponding to the frequency.
+ *	e.g level=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
+ *	or negative value for any error.
+ */
+static int get_frequency_level(unsigned int cpu, unsigned int freq)
+{
+	int ret = -EINVAL, i = 0;
+	unsigned int max_index;
+	bool descend = false;
+	struct cpufreq_frequency_table *table =
+					cpufreq_frequency_get_table(cpu);
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/*check if table in ascending or descending order*/
+		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+			(table[i + 1].frequency < table[i].frequency)
+			&& !descend) {
+			descend = true;
+		}
+
+		/*return if level matched and table in descending order*/
+		if (descend && table[i].frequency == freq)
+			return i;
+		i++;
+	}
+	i--;
+
+	if (descend)
+		return ret;
+
+	max_index = i;
+
+	/*Scan the table in reverse order and match the freq*/
+	while (i >= 0) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		/*return if level matched*/
+		if (table[i].frequency == freq)
+			return max_index - i;
+		i--;
+	}
+	return ret;
+}
+
+/**
+ * cpufreq_apply_cooling - function to apply frequency clipping.
+ * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
+ *	clipping data.
+ * @cooling_state: value of the cooling state.
+ */
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+				unsigned long cooling_state)
+{
+	unsigned int event, cpuid, clip_freq;
+	struct cpumask *mask = &cpufreq_device->allowed_cpus;
+	unsigned int cpu = cpumask_any(mask);
+
+
+	/* Check if the old cooling action is same as new cooling action */
+	if (cpufreq_device->cpufreq_state == cooling_state)
+		return 0;
+
+	clip_freq = get_cpu_frequency(cpu, cooling_state);
+	if (!clip_freq)
+		return -EINVAL;
+
+	cpufreq_device->cpufreq_state = cooling_state;
+	cpufreq_device->cpufreq_val = clip_freq;
+	notify_device = cpufreq_device;
+
+	if (cooling_state != 0)
+		event = CPUFREQ_COOLING_START;
+	else
+		event = CPUFREQ_COOLING_STOP;
+
+	blocking_notifier_call_chain(&cputherm_state_notifier_list,
+						event, &clip_freq);
+
+	for_each_cpu(cpuid, mask) {
+		if (is_cpufreq_valid(cpuid))
+			cpufreq_update_policy(cpuid);
+	}
+
+	notify_device = NOTIFY_INVALID;
+
+	return 0;
+}
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb:	struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+					unsigned long event, void *data)
+{
+	struct cpufreq_policy *policy = data;
+	unsigned long max_freq = 0;
+
+	if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
+		return 0;
+
+	if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
+		max_freq = notify_device->cpufreq_val;
+
+	/* Never exceed user_policy.max*/
+	if (max_freq > policy->user_policy.max)
+		max_freq = policy->user_policy.max;
+
+	if (policy->max > max_freq)
+		cpufreq_verify_within_limits(policy, 0, max_freq);
+
+	return 0;
+}
+
+/*
+ * cpufreq cooling device callback functions are defined below
+ */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ */
+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 *mask;
+	unsigned int cpu;
+	struct cpufreq_frequency_table *table;
+
+	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;
+
+	mask = &cpufreq_device->allowed_cpus;
+	cpu = cpumask_any(mask);
+	table = cpufreq_frequency_get_table(cpu);
+	if (!table) {
+		*state = 0;
+		ret = 0;
+		goto return_get_max_state;
+	}
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		i++;
+	}
+	if (i > 0) {
+		*state = --i;
+		ret = 0;
+	}
+
+return_get_max_state:
+	mutex_unlock(&cooling_cpufreq_lock);
+	return ret;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling state.
+ */
+static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
+				 unsigned long *state)
+{
+	int ret = -EINVAL;
+	struct cpufreq_cooling_device *cpufreq_device;
+	struct cpumask *mask;
+	unsigned int cpu;
+	struct cpufreq_policy policy;
+
+	*state = 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 err_get_cur_state;
+
+	mask = &cpufreq_device->allowed_cpus;
+	cpu = cpumask_any(mask);
+
+	ret = cpufreq_get_policy(&policy, cpu);
+	if (ret)
+		goto uninit_get_cur_state;
+
+	ret = get_frequency_level(cpu, policy.max);
+	if (ret < 0)
+		goto uninit_get_cur_state;
+
+	*state = ret;
+
+uninit_get_cur_state:
+	ret = 0;
+err_get_cur_state:
+	mutex_unlock(&cooling_cpufreq_lock);
+	return ret;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ */
+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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	return ret;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+	.get_max_state = cpufreq_get_max_state,
+	.get_cur_state = cpufreq_get_cur_state,
+	.set_cur_state = cpufreq_set_cur_state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+	.notifier_call = cpufreq_thermal_notifier,
+};
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+	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;
+	char dev_name[THERMAL_NAME_LENGTH];
+	int ret = 0, id = 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*/
+		if (!cpufreq_get_policy(&policy, i))
+			continue;
+		if (min == 0 && max == 0) {
+			min = policy.cpuinfo.min_freq;
+			max = policy.cpuinfo.max_freq;
+		} else {
+			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);
+	if (!cpufreq_dev)
+		return ERR_PTR(-ENOMEM);
+
+	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);
+		return ERR_PTR(-EINVAL);
+	}
+
+	sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
+
+	cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
+						&cpufreq_cooling_ops);
+	if (!cool_dev) {
+		release_idr(&cpufreq_idr, cpufreq_dev->id);
+		kfree(cpufreq_dev);
+		return ERR_PTR(-EINVAL);
+	}
+	cpufreq_dev->id = id;
+	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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+	return cool_dev;
+}
+EXPORT_SYMBOL(cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+	struct cpufreq_cooling_device *cpufreq_dev = NULL;
+	unsigned int cpufreq_dev_count = 0;
+
+	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);
+
+	/* Unregister the notifier for the last cpufreq cooling device */
+	if (cpufreq_dev_count == 1) {
+		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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
new file mode 100644
index 0000000..555b917
--- /dev/null
+++ b/include/linux/cpu_cooling.h
@@ -0,0 +1,79 @@
+/*
+ *  linux/include/linux/cpu_cooling.h
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  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 __CPU_COOLING_H__
+#define __CPU_COOLING_H__
+
+#include <linux/thermal.h>
+
+#define CPUFREQ_COOLING_START		0
+#define CPUFREQ_COOLING_STOP		1
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
+
+#ifdef CONFIG_CPU_THERMAL
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+		struct cpumask *clip_cpus);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
+#else /* !CONFIG_CPU_THERMAL */
+static inline struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+{
+	return NULL;
+}
+static inline void cpufreq_cooling_unregister(
+		struct thermal_cooling_device *cdev)
+{
+	return;
+}
+#endif	/* CONFIG_CPU_THERMAL */
+
+#endif /* __CPU_COOLING_H__ */
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* [RESEND PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17 13:29         ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-17 13:29 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	amit.kachhap, Guenter Roeck, SangWook Ju, Durgadoss, Len Brown,
	Jean Delvare, Kyungmin Park, Kukjin Kim

This patchset introduces a new generic cooling device based on cpufreq
that can be used on non-ACPI platforms.  As a proof of concept, we have
drivers for the following platforms using this mechanism now:

 * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
 * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
 * Omap platforms in drivers/staging/omap-thermal/

There is a small change in cpufreq cooling registration APIs, so a minor
change is needed for Freescale and omap platforms.

Brief Description:

1) The generic cooling devices code is placed inside driver/thermal/*
   as placing inside acpi folder will need un-necessary enabling of acpi
   code.  This code is architecture independent.

2) This patchset adds generic cpu cooling low level implementation
   through frequency clipping.  In future, other cpu related cooling
   devices may be added here.  An ACPI version of this already exists
   (drivers/acpi/processor_thermal.c) .But this will be useful for
   platforms like ARM using the generic thermal interface along with the
   generic cpu cooling devices.  The cooling device registration API's
   return cooling device pointers which can be easily binded with the
   thermal zone trip points.  The important APIs exposed are,

   a) struct thermal_cooling_device *cpufreq_cooling_register(
        struct cpumask *clip_cpus)
   b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)

3) Samsung exynos platform thermal implementation is done using the
   generic cpu cooling APIs and the new trip type.  The temperature sensor
   driver present in the hwmon folder(registered as hwmon driver) is moved
   to thermal folder and registered as a thermal driver.

A simple data/control flow diagrams is shown below,

Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
          |                             |
         \|/                            |
  Cpufreq cooling device <---------------

About this patch:

Add support for generic cpu thermal cooling low level implementations
using frequency scaling up/down based on the registration parameters.
Different cpu related cooling devices can be registered by the user and
the binding of these cooling devices to the corresponding trip points can
be easily done as the registration APIs return the cooling device pointer.
The user of these APIs are responsible for passing clipping frequency .
The drivers can also register to recieve notification about any cooling
action called.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Cc: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 Documentation/thermal/cpu-cooling-api.txt |   52 +++
 drivers/thermal/Kconfig                   |   11 +
 drivers/thermal/Makefile                  |    1 +
 drivers/thermal/cpu_cooling.c             |  586 +++++++++++++++++++++++++++++
 include/linux/cpu_cooling.h               |   79 ++++
 5 files changed, 729 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/thermal/cpu-cooling-api.txt
 create mode 100644 drivers/thermal/cpu_cooling.c
 create mode 100644 include/linux/cpu_cooling.h

diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
new file mode 100644
index 0000000..a1f2a6b
--- /dev/null
+++ b/Documentation/thermal/cpu-cooling-api.txt
@@ -0,0 +1,52 @@
+CPU cooling APIs How To
+===================================
+
+Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
+
+Updated: 12 May 2012
+
+Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+
+0. Introduction
+
+The generic cpu cooling(freq clipping) provides registration/unregistration APIs
+to the caller. The binding of the cooling devices to the trip point is left for
+the user. The registration APIs returns the cooling device pointer.
+
+1. cpu cooling APIs
+
+1.1 cpufreq registration/unregistration APIs
+1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+
+    This interface function registers the cpufreq cooling device with the name
+    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+    cooling devices.
+
+   clip_cpus: cpumask of cpus where the frequency constraints will happen.
+
+1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+
+    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+
+    cdev: Cooling device pointer which has to be unregistered.
+
+
+1.2 CPU cooling action notifier register/unregister interface
+1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
+
+1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 7dd8c34..996003b 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -19,6 +19,17 @@ config THERMAL_HWMON
 	depends on HWMON=y || HWMON=THERMAL
 	default y
 
+config CPU_THERMAL
+	bool "generic cpu cooling support"
+	depends on THERMAL && CPU_FREQ
+	help
+	  This implements the generic cpu cooling mechanism through frequency
+	  reduction, cpu hotplug and any other ways of reducing temperature. An
+	  ACPI version of this already exists(drivers/acpi/processor_thermal.c).
+	  This will be useful for platforms using the generic thermal interface
+	  and not the ACPI interface.
+	  If you want this support, you should say Y here.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fd9369a..aae59ad 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,5 +3,6 @@
 #
 
 obj-$(CONFIG_THERMAL)		+= thermal_sys.o
+obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 0000000..66cbd52
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,586 @@
+/*
+ *  linux/drivers/thermal/cpu_cooling.c
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+/**
+ * struct cpufreq_cooling_device
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ *	registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the the
+ *	egistered cooling device.
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ *	cooling	devices.
+ * @cpufreq_val: integer value representing the absolute value of the clipped
+ *	frequency.
+ * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ *
+ * This structure is required for keeping information of each
+ * cpufreq_cooling_device registered as a list whose head is represented by
+ * cooling_cpufreq_list. In order to prevent corruption of this list a
+ * mutex lock cooling_cpufreq_lock is used.
+ */
+struct cpufreq_cooling_device {
+	int id;
+	struct thermal_cooling_device *cool_dev;
+	unsigned int cpufreq_state;
+	unsigned int cpufreq_val;
+	struct cpumask allowed_cpus;
+	struct list_head node;
+};
+static LIST_HEAD(cooling_cpufreq_list);
+static DEFINE_IDR(cpufreq_idr);
+
+static struct mutex cooling_cpufreq_lock;
+
+/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
+#define NOTIFY_INVALID NULL
+struct cpufreq_cooling_device *notify_device;
+
+/* Head of the blocking notifier chain to inform about frequency clamping */
+static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+	int err;
+again:
+	if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
+		return -ENOMEM;
+
+	mutex_lock(&cooling_cpufreq_lock);
+	err = idr_get_new(idr, NULL, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	if (unlikely(err == -EAGAIN))
+		goto again;
+	else if (unlikely(err))
+		return err;
+
+	*id = *id & MAX_ID_MASK;
+	return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+	mutex_lock(&cooling_cpufreq_lock);
+	idr_remove(idr, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+}
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_register(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_register_notifier);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_unregister(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_unregister_notifier);
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
+ * @cpu: cpu for which check is needed.
+ */
+static int is_cpufreq_valid(int cpu)
+{
+	struct cpufreq_policy policy;
+	return !cpufreq_get_policy(&policy, 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=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
+ * @return: frequency of the cpu or 0 for error.
+ */
+static unsigned int get_cpu_frequency(unsigned int cpu, unsigned int level)
+{
+	int ret = 0, i = 0;
+	unsigned int level_index;
+	bool descend = false;
+	struct cpufreq_frequency_table *table =
+					cpufreq_frequency_get_table(cpu);
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/*check if table in ascending or descending order*/
+		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+			(table[i + 1].frequency < table[i].frequency)
+			&& !descend) {
+			descend = true;
+		}
+
+		/*return if level matched and table in descending order*/
+		if (descend && i == level)
+			return table[i].frequency;
+		i++;
+	}
+	i--;
+
+	if (level > i || descend)
+		return ret;
+	level_index = i - level;
+
+	/*Scan the table in reverse order and match the level*/
+	while (i >= 0) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		/*return if level matched*/
+		if (i == level_index)
+			return table[i].frequency;
+		i--;
+	}
+	return ret;
+}
+
+/**
+ * get_frequency_level - get the level from the frequency.
+ * @cpu: cpu for which frequency is fetched.
+ * @freq: frequency for which level is requested.
+ *
+ * @return: level corresponding to the frequency.
+ *	e.g level=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
+ *	or negative value for any error.
+ */
+static int get_frequency_level(unsigned int cpu, unsigned int freq)
+{
+	int ret = -EINVAL, i = 0;
+	unsigned int max_index;
+	bool descend = false;
+	struct cpufreq_frequency_table *table =
+					cpufreq_frequency_get_table(cpu);
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/*check if table in ascending or descending order*/
+		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+			(table[i + 1].frequency < table[i].frequency)
+			&& !descend) {
+			descend = true;
+		}
+
+		/*return if level matched and table in descending order*/
+		if (descend && table[i].frequency == freq)
+			return i;
+		i++;
+	}
+	i--;
+
+	if (descend)
+		return ret;
+
+	max_index = i;
+
+	/*Scan the table in reverse order and match the freq*/
+	while (i >= 0) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		/*return if level matched*/
+		if (table[i].frequency == freq)
+			return max_index - i;
+		i--;
+	}
+	return ret;
+}
+
+/**
+ * cpufreq_apply_cooling - function to apply frequency clipping.
+ * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
+ *	clipping data.
+ * @cooling_state: value of the cooling state.
+ */
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+				unsigned long cooling_state)
+{
+	unsigned int event, cpuid, clip_freq;
+	struct cpumask *mask = &cpufreq_device->allowed_cpus;
+	unsigned int cpu = cpumask_any(mask);
+
+
+	/* Check if the old cooling action is same as new cooling action */
+	if (cpufreq_device->cpufreq_state == cooling_state)
+		return 0;
+
+	clip_freq = get_cpu_frequency(cpu, cooling_state);
+	if (!clip_freq)
+		return -EINVAL;
+
+	cpufreq_device->cpufreq_state = cooling_state;
+	cpufreq_device->cpufreq_val = clip_freq;
+	notify_device = cpufreq_device;
+
+	if (cooling_state != 0)
+		event = CPUFREQ_COOLING_START;
+	else
+		event = CPUFREQ_COOLING_STOP;
+
+	blocking_notifier_call_chain(&cputherm_state_notifier_list,
+						event, &clip_freq);
+
+	for_each_cpu(cpuid, mask) {
+		if (is_cpufreq_valid(cpuid))
+			cpufreq_update_policy(cpuid);
+	}
+
+	notify_device = NOTIFY_INVALID;
+
+	return 0;
+}
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb:	struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+					unsigned long event, void *data)
+{
+	struct cpufreq_policy *policy = data;
+	unsigned long max_freq = 0;
+
+	if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
+		return 0;
+
+	if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
+		max_freq = notify_device->cpufreq_val;
+
+	/* Never exceed user_policy.max*/
+	if (max_freq > policy->user_policy.max)
+		max_freq = policy->user_policy.max;
+
+	if (policy->max > max_freq)
+		cpufreq_verify_within_limits(policy, 0, max_freq);
+
+	return 0;
+}
+
+/*
+ * cpufreq cooling device callback functions are defined below
+ */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ */
+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 *mask;
+	unsigned int cpu;
+	struct cpufreq_frequency_table *table;
+
+	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;
+
+	mask = &cpufreq_device->allowed_cpus;
+	cpu = cpumask_any(mask);
+	table = cpufreq_frequency_get_table(cpu);
+	if (!table) {
+		*state = 0;
+		ret = 0;
+		goto return_get_max_state;
+	}
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+			continue;
+		i++;
+	}
+	if (i > 0) {
+		*state = --i;
+		ret = 0;
+	}
+
+return_get_max_state:
+	mutex_unlock(&cooling_cpufreq_lock);
+	return ret;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling state.
+ */
+static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
+				 unsigned long *state)
+{
+	int ret = -EINVAL;
+	struct cpufreq_cooling_device *cpufreq_device;
+	struct cpumask *mask;
+	unsigned int cpu;
+	struct cpufreq_policy policy;
+
+	*state = 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 err_get_cur_state;
+
+	mask = &cpufreq_device->allowed_cpus;
+	cpu = cpumask_any(mask);
+
+	ret = cpufreq_get_policy(&policy, cpu);
+	if (ret)
+		goto uninit_get_cur_state;
+
+	ret = get_frequency_level(cpu, policy.max);
+	if (ret < 0)
+		goto uninit_get_cur_state;
+
+	*state = ret;
+
+uninit_get_cur_state:
+	ret = 0;
+err_get_cur_state:
+	mutex_unlock(&cooling_cpufreq_lock);
+	return ret;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ */
+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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	return ret;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+	.get_max_state = cpufreq_get_max_state,
+	.get_cur_state = cpufreq_get_cur_state,
+	.set_cur_state = cpufreq_set_cur_state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+	.notifier_call = cpufreq_thermal_notifier,
+};
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+	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;
+	char dev_name[THERMAL_NAME_LENGTH];
+	int ret = 0, id = 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*/
+		if (!cpufreq_get_policy(&policy, i))
+			continue;
+		if (min == 0 && max == 0) {
+			min = policy.cpuinfo.min_freq;
+			max = policy.cpuinfo.max_freq;
+		} else {
+			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);
+	if (!cpufreq_dev)
+		return ERR_PTR(-ENOMEM);
+
+	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);
+		return ERR_PTR(-EINVAL);
+	}
+
+	sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
+
+	cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
+						&cpufreq_cooling_ops);
+	if (!cool_dev) {
+		release_idr(&cpufreq_idr, cpufreq_dev->id);
+		kfree(cpufreq_dev);
+		return ERR_PTR(-EINVAL);
+	}
+	cpufreq_dev->id = id;
+	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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+	return cool_dev;
+}
+EXPORT_SYMBOL(cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+	struct cpufreq_cooling_device *cpufreq_dev = NULL;
+	unsigned int cpufreq_dev_count = 0;
+
+	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);
+
+	/* Unregister the notifier for the last cpufreq cooling device */
+	if (cpufreq_dev_count == 1) {
+		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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
new file mode 100644
index 0000000..555b917
--- /dev/null
+++ b/include/linux/cpu_cooling.h
@@ -0,0 +1,79 @@
+/*
+ *  linux/include/linux/cpu_cooling.h
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  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 __CPU_COOLING_H__
+#define __CPU_COOLING_H__
+
+#include <linux/thermal.h>
+
+#define CPUFREQ_COOLING_START		0
+#define CPUFREQ_COOLING_STOP		1
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
+
+#ifdef CONFIG_CPU_THERMAL
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+		struct cpumask *clip_cpus);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
+#else /* !CONFIG_CPU_THERMAL */
+static inline struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+{
+	return NULL;
+}
+static inline void cpufreq_cooling_unregister(
+		struct thermal_cooling_device *cdev)
+{
+	return;
+}
+#endif	/* CONFIG_CPU_THERMAL */
+
+#endif /* __CPU_COOLING_H__ */
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 50+ messages in thread

* Re: [RESEND PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-17 13:29         ` Amit Daniel Kachhap
  (?)
@ 2012-08-17 13:32           ` Amit Kachhap
  -1 siblings, 0 replies; 50+ messages in thread
From: Amit Kachhap @ 2012-08-17 13:32 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park,
	Guenter Roeck

This is repost containing the changes suggested by Zhang Rui. Now the
cpufreq current state will be calculated each time instead of earlier
way of returning the cached value.

Thanks,
Amit Daniel

On 17 August 2012 18:59, Amit Daniel Kachhap <amit.kachhap@linaro.org> wrote:
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms.  As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
>
>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>  * Omap platforms in drivers/staging/omap-thermal/
>
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale and omap platforms.
>
> Brief Description:
>
> 1) The generic cooling devices code is placed inside driver/thermal/*
>    as placing inside acpi folder will need un-necessary enabling of acpi
>    code.  This code is architecture independent.
>
> 2) This patchset adds generic cpu cooling low level implementation
>    through frequency clipping.  In future, other cpu related cooling
>    devices may be added here.  An ACPI version of this already exists
>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>    platforms like ARM using the generic thermal interface along with the
>    generic cpu cooling devices.  The cooling device registration API's
>    return cooling device pointers which can be easily binded with the
>    thermal zone trip points.  The important APIs exposed are,
>
>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>         struct cpumask *clip_cpus)
>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>
> 3) Samsung exynos platform thermal implementation is done using the
>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>    driver present in the hwmon folder(registered as hwmon driver) is moved
>    to thermal folder and registered as a thermal driver.
>
> A simple data/control flow diagrams is shown below,
>
> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>           |                             |
>          \|/                            |
>   Cpufreq cooling device <---------------
>
> About this patch:
>
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
>
> [akpm@linux-foundation.org: fix comment layout]
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
> ---
>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>  drivers/thermal/Kconfig                   |   11 +
>  drivers/thermal/Makefile                  |    1 +
>  drivers/thermal/cpu_cooling.c             |  586 +++++++++++++++++++++++++++++
>  include/linux/cpu_cooling.h               |   79 ++++
>  5 files changed, 729 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>  create mode 100644 drivers/thermal/cpu_cooling.c
>  create mode 100644 include/linux/cpu_cooling.h
>
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..a1f2a6b
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,52 @@
> +CPU cooling APIs How To
> +===================================
> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +
> +    This interface function registers the cpufreq cooling device with the name
> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> +    cooling devices.
> +
> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> +    cdev: Cooling device pointer which has to be unregistered.
> +
> +
> +1.2 CPU cooling action notifier register/unregister interface
> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> +
> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>         depends on HWMON=y || HWMON=THERMAL
>         default y
>
> +config CPU_THERMAL
> +       bool "generic cpu cooling support"
> +       depends on THERMAL && CPU_FREQ
> +       help
> +         This implements the generic cpu cooling mechanism through frequency
> +         reduction, cpu hotplug and any other ways of reducing temperature. An
> +         ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> +         This will be useful for platforms using the generic thermal interface
> +         and not the ACPI interface.
> +         If you want this support, you should say Y here.
> +
>  config SPEAR_THERMAL
>         bool "SPEAr thermal sensor driver"
>         depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
>  #
>
>  obj-$(CONFIG_THERMAL)          += thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL)              += cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
>  obj-$(CONFIG_RCAR_THERMAL)     += rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..66cbd52
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,586 @@
> +/*
> + *  linux/drivers/thermal/cpu_cooling.c
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful, but
> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + *  General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + *     registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + *     egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + *     cooling devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + *     frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> +       int id;
> +       struct thermal_cooling_device *cool_dev;
> +       unsigned int cpufreq_state;
> +       unsigned int cpufreq_val;
> +       struct cpumask allowed_cpus;
> +       struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/* Head of the blocking notifier chain to inform about frequency clamping */
> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> +       int err;
> +again:
> +       if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> +               return -ENOMEM;
> +
> +       mutex_lock(&cooling_cpufreq_lock);
> +       err = idr_get_new(idr, NULL, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       if (unlikely(err == -EAGAIN))
> +               goto again;
> +       else if (unlikely(err))
> +               return err;
> +
> +       *id = *id & MAX_ID_MASK;
> +       return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> +       mutex_lock(&cooling_cpufreq_lock);
> +       idr_remove(idr, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_register(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_register_notifier);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_unregister(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> +       struct cpufreq_policy policy;
> +       return !cpufreq_get_policy(&policy, 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=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
> + * @return: frequency of the cpu or 0 for error.
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned int level)
> +{
> +       int ret = 0, i = 0;
> +       unsigned int level_index;
> +       bool descend = false;
> +       struct cpufreq_frequency_table *table =
> +                                       cpufreq_frequency_get_table(cpu);
> +       if (!table)
> +               return ret;
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +
> +               /*check if table in ascending or descending order*/
> +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +                       (table[i + 1].frequency < table[i].frequency)
> +                       && !descend) {
> +                       descend = true;
> +               }
> +
> +               /*return if level matched and table in descending order*/
> +               if (descend && i == level)
> +                       return table[i].frequency;
> +               i++;
> +       }
> +       i--;
> +
> +       if (level > i || descend)
> +               return ret;
> +       level_index = i - level;
> +
> +       /*Scan the table in reverse order and match the level*/
> +       while (i >= 0) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               /*return if level matched*/
> +               if (i == level_index)
> +                       return table[i].frequency;
> +               i--;
> +       }
> +       return ret;
> +}
> +
> +/**
> + * get_frequency_level - get the level from the frequency.
> + * @cpu: cpu for which frequency is fetched.
> + * @freq: frequency for which level is requested.
> + *
> + * @return: level corresponding to the frequency.
> + *     e.g level=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
> + *     or negative value for any error.
> + */
> +static int get_frequency_level(unsigned int cpu, unsigned int freq)
> +{
> +       int ret = -EINVAL, i = 0;
> +       unsigned int max_index;
> +       bool descend = false;
> +       struct cpufreq_frequency_table *table =
> +                                       cpufreq_frequency_get_table(cpu);
> +       if (!table)
> +               return ret;
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +
> +               /*check if table in ascending or descending order*/
> +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +                       (table[i + 1].frequency < table[i].frequency)
> +                       && !descend) {
> +                       descend = true;
> +               }
> +
> +               /*return if level matched and table in descending order*/
> +               if (descend && table[i].frequency == freq)
> +                       return i;
> +               i++;
> +       }
> +       i--;
> +
> +       if (descend)
> +               return ret;
> +
> +       max_index = i;
> +
> +       /*Scan the table in reverse order and match the freq*/
> +       while (i >= 0) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               /*return if level matched*/
> +               if (table[i].frequency == freq)
> +                       return max_index - i;
> +               i--;
> +       }
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + *     clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> +                               unsigned long cooling_state)
> +{
> +       unsigned int event, cpuid, clip_freq;
> +       struct cpumask *mask = &cpufreq_device->allowed_cpus;
> +       unsigned int cpu = cpumask_any(mask);
> +
> +
> +       /* Check if the old cooling action is same as new cooling action */
> +       if (cpufreq_device->cpufreq_state == cooling_state)
> +               return 0;
> +
> +       clip_freq = get_cpu_frequency(cpu, cooling_state);
> +       if (!clip_freq)
> +               return -EINVAL;
> +
> +       cpufreq_device->cpufreq_state = cooling_state;
> +       cpufreq_device->cpufreq_val = clip_freq;
> +       notify_device = cpufreq_device;
> +
> +       if (cooling_state != 0)
> +               event = CPUFREQ_COOLING_START;
> +       else
> +               event = CPUFREQ_COOLING_STOP;
> +
> +       blocking_notifier_call_chain(&cputherm_state_notifier_list,
> +                                               event, &clip_freq);
> +
> +       for_each_cpu(cpuid, mask) {
> +               if (is_cpufreq_valid(cpuid))
> +                       cpufreq_update_policy(cpuid);
> +       }
> +
> +       notify_device = NOTIFY_INVALID;
> +
> +       return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb:        struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> +                                       unsigned long event, void *data)
> +{
> +       struct cpufreq_policy *policy = data;
> +       unsigned long max_freq = 0;
> +
> +       if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> +               return 0;
> +
> +       if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> +               max_freq = notify_device->cpufreq_val;
> +
> +       /* Never exceed user_policy.max*/
> +       if (max_freq > policy->user_policy.max)
> +               max_freq = policy->user_policy.max;
> +
> +       if (policy->max > max_freq)
> +               cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> +       return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +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 *mask;
> +       unsigned int cpu;
> +       struct cpufreq_frequency_table *table;
> +
> +       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;
> +
> +       mask = &cpufreq_device->allowed_cpus;
> +       cpu = cpumask_any(mask);
> +       table = cpufreq_frequency_get_table(cpu);
> +       if (!table) {
> +               *state = 0;
> +               ret = 0;
> +               goto return_get_max_state;
> +       }
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               i++;
> +       }
> +       if (i > 0) {
> +               *state = --i;
> +               ret = 0;
> +       }
> +
> +return_get_max_state:
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling state.
> + */
> +static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
> +                                unsigned long *state)
> +{
> +       int ret = -EINVAL;
> +       struct cpufreq_cooling_device *cpufreq_device;
> +       struct cpumask *mask;
> +       unsigned int cpu;
> +       struct cpufreq_policy policy;
> +
> +       *state = 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 err_get_cur_state;
> +
> +       mask = &cpufreq_device->allowed_cpus;
> +       cpu = cpumask_any(mask);
> +
> +       ret = cpufreq_get_policy(&policy, cpu);
> +       if (ret)
> +               goto uninit_get_cur_state;
> +
> +       ret = get_frequency_level(cpu, policy.max);
> +       if (ret < 0)
> +               goto uninit_get_cur_state;
> +
> +       *state = ret;
> +
> +uninit_get_cur_state:
> +       ret = 0;
> +err_get_cur_state:
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_set_cur_state - callback function to set the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: set this variable to the current cooling state.
> + */
> +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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       return ret;
> +}
> +
> +/* Bind cpufreq callbacks to thermal cooling device ops */
> +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> +       .get_max_state = cpufreq_get_max_state,
> +       .get_cur_state = cpufreq_get_cur_state,
> +       .set_cur_state = cpufreq_set_cur_state,
> +};
> +
> +/* Notifier for cpufreq policy change */
> +static struct notifier_block thermal_cpufreq_notifier_block = {
> +       .notifier_call = cpufreq_thermal_notifier,
> +};
> +
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +       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;
> +       char dev_name[THERMAL_NAME_LENGTH];
> +       int ret = 0, id = 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*/
> +               if (!cpufreq_get_policy(&policy, i))
> +                       continue;
> +               if (min == 0 && max == 0) {
> +                       min = policy.cpuinfo.min_freq;
> +                       max = policy.cpuinfo.max_freq;
> +               } else {
> +                       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);
> +       if (!cpufreq_dev)
> +               return ERR_PTR(-ENOMEM);
> +
> +       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);
> +               return ERR_PTR(-EINVAL);
> +       }
> +
> +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> +
> +       cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> +                                               &cpufreq_cooling_ops);
> +       if (!cool_dev) {
> +               release_idr(&cpufreq_idr, cpufreq_dev->id);
> +               kfree(cpufreq_dev);
> +               return ERR_PTR(-EINVAL);
> +       }
> +       cpufreq_dev->id = id;
> +       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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return cool_dev;
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_register);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +{
> +       struct cpufreq_cooling_device *cpufreq_dev = NULL;
> +       unsigned int cpufreq_dev_count = 0;
> +
> +       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);
> +
> +       /* Unregister the notifier for the last cpufreq cooling device */
> +       if (cpufreq_dev_count == 1) {
> +               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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> new file mode 100644
> index 0000000..555b917
> --- /dev/null
> +++ b/include/linux/cpu_cooling.h
> @@ -0,0 +1,79 @@
> +/*
> + *  linux/include/linux/cpu_cooling.h
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  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 __CPU_COOLING_H__
> +#define __CPU_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +#define CPUFREQ_COOLING_START          0
> +#define CPUFREQ_COOLING_STOP           1
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
> +
> +#ifdef CONFIG_CPU_THERMAL
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +               struct cpumask *clip_cpus);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> +#else /* !CONFIG_CPU_THERMAL */
> +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +{
> +       return NULL;
> +}
> +static inline void cpufreq_cooling_unregister(
> +               struct thermal_cooling_device *cdev)
> +{
> +       return;
> +}
> +#endif /* CONFIG_CPU_THERMAL */
> +
> +#endif /* __CPU_COOLING_H__ */
> --
> 1.7.1
>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [RESEND PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17 13:32           ` Amit Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Kachhap @ 2012-08-17 13:32 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	amit.kachhap, Guenter Roeck, SangWook Ju, Durgadoss,
	Jean Delvare, Kyungmin Park, Kukjin Kim

This is repost containing the changes suggested by Zhang Rui. Now the
cpufreq current state will be calculated each time instead of earlier
way of returning the cached value.

Thanks,
Amit Daniel

On 17 August 2012 18:59, Amit Daniel Kachhap <amit.kachhap@linaro.org> wrote:
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms.  As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
>
>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>  * Omap platforms in drivers/staging/omap-thermal/
>
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale and omap platforms.
>
> Brief Description:
>
> 1) The generic cooling devices code is placed inside driver/thermal/*
>    as placing inside acpi folder will need un-necessary enabling of acpi
>    code.  This code is architecture independent.
>
> 2) This patchset adds generic cpu cooling low level implementation
>    through frequency clipping.  In future, other cpu related cooling
>    devices may be added here.  An ACPI version of this already exists
>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>    platforms like ARM using the generic thermal interface along with the
>    generic cpu cooling devices.  The cooling device registration API's
>    return cooling device pointers which can be easily binded with the
>    thermal zone trip points.  The important APIs exposed are,
>
>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>         struct cpumask *clip_cpus)
>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>
> 3) Samsung exynos platform thermal implementation is done using the
>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>    driver present in the hwmon folder(registered as hwmon driver) is moved
>    to thermal folder and registered as a thermal driver.
>
> A simple data/control flow diagrams is shown below,
>
> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>           |                             |
>          \|/                            |
>   Cpufreq cooling device <---------------
>
> About this patch:
>
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
>
> [akpm@linux-foundation.org: fix comment layout]
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
> ---
>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>  drivers/thermal/Kconfig                   |   11 +
>  drivers/thermal/Makefile                  |    1 +
>  drivers/thermal/cpu_cooling.c             |  586 +++++++++++++++++++++++++++++
>  include/linux/cpu_cooling.h               |   79 ++++
>  5 files changed, 729 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>  create mode 100644 drivers/thermal/cpu_cooling.c
>  create mode 100644 include/linux/cpu_cooling.h
>
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..a1f2a6b
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,52 @@
> +CPU cooling APIs How To
> +===================================
> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +
> +    This interface function registers the cpufreq cooling device with the name
> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> +    cooling devices.
> +
> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> +    cdev: Cooling device pointer which has to be unregistered.
> +
> +
> +1.2 CPU cooling action notifier register/unregister interface
> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> +
> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>         depends on HWMON=y || HWMON=THERMAL
>         default y
>
> +config CPU_THERMAL
> +       bool "generic cpu cooling support"
> +       depends on THERMAL && CPU_FREQ
> +       help
> +         This implements the generic cpu cooling mechanism through frequency
> +         reduction, cpu hotplug and any other ways of reducing temperature. An
> +         ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> +         This will be useful for platforms using the generic thermal interface
> +         and not the ACPI interface.
> +         If you want this support, you should say Y here.
> +
>  config SPEAR_THERMAL
>         bool "SPEAr thermal sensor driver"
>         depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
>  #
>
>  obj-$(CONFIG_THERMAL)          += thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL)              += cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
>  obj-$(CONFIG_RCAR_THERMAL)     += rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..66cbd52
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,586 @@
> +/*
> + *  linux/drivers/thermal/cpu_cooling.c
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful, but
> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + *  General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + *     registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + *     egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + *     cooling devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + *     frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> +       int id;
> +       struct thermal_cooling_device *cool_dev;
> +       unsigned int cpufreq_state;
> +       unsigned int cpufreq_val;
> +       struct cpumask allowed_cpus;
> +       struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/* Head of the blocking notifier chain to inform about frequency clamping */
> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> +       int err;
> +again:
> +       if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> +               return -ENOMEM;
> +
> +       mutex_lock(&cooling_cpufreq_lock);
> +       err = idr_get_new(idr, NULL, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       if (unlikely(err == -EAGAIN))
> +               goto again;
> +       else if (unlikely(err))
> +               return err;
> +
> +       *id = *id & MAX_ID_MASK;
> +       return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> +       mutex_lock(&cooling_cpufreq_lock);
> +       idr_remove(idr, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_register(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_register_notifier);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_unregister(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> +       struct cpufreq_policy policy;
> +       return !cpufreq_get_policy(&policy, 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=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
> + * @return: frequency of the cpu or 0 for error.
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned int level)
> +{
> +       int ret = 0, i = 0;
> +       unsigned int level_index;
> +       bool descend = false;
> +       struct cpufreq_frequency_table *table =
> +                                       cpufreq_frequency_get_table(cpu);
> +       if (!table)
> +               return ret;
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +
> +               /*check if table in ascending or descending order*/
> +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +                       (table[i + 1].frequency < table[i].frequency)
> +                       && !descend) {
> +                       descend = true;
> +               }
> +
> +               /*return if level matched and table in descending order*/
> +               if (descend && i == level)
> +                       return table[i].frequency;
> +               i++;
> +       }
> +       i--;
> +
> +       if (level > i || descend)
> +               return ret;
> +       level_index = i - level;
> +
> +       /*Scan the table in reverse order and match the level*/
> +       while (i >= 0) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               /*return if level matched*/
> +               if (i == level_index)
> +                       return table[i].frequency;
> +               i--;
> +       }
> +       return ret;
> +}
> +
> +/**
> + * get_frequency_level - get the level from the frequency.
> + * @cpu: cpu for which frequency is fetched.
> + * @freq: frequency for which level is requested.
> + *
> + * @return: level corresponding to the frequency.
> + *     e.g level=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
> + *     or negative value for any error.
> + */
> +static int get_frequency_level(unsigned int cpu, unsigned int freq)
> +{
> +       int ret = -EINVAL, i = 0;
> +       unsigned int max_index;
> +       bool descend = false;
> +       struct cpufreq_frequency_table *table =
> +                                       cpufreq_frequency_get_table(cpu);
> +       if (!table)
> +               return ret;
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +
> +               /*check if table in ascending or descending order*/
> +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +                       (table[i + 1].frequency < table[i].frequency)
> +                       && !descend) {
> +                       descend = true;
> +               }
> +
> +               /*return if level matched and table in descending order*/
> +               if (descend && table[i].frequency == freq)
> +                       return i;
> +               i++;
> +       }
> +       i--;
> +
> +       if (descend)
> +               return ret;
> +
> +       max_index = i;
> +
> +       /*Scan the table in reverse order and match the freq*/
> +       while (i >= 0) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               /*return if level matched*/
> +               if (table[i].frequency == freq)
> +                       return max_index - i;
> +               i--;
> +       }
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + *     clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> +                               unsigned long cooling_state)
> +{
> +       unsigned int event, cpuid, clip_freq;
> +       struct cpumask *mask = &cpufreq_device->allowed_cpus;
> +       unsigned int cpu = cpumask_any(mask);
> +
> +
> +       /* Check if the old cooling action is same as new cooling action */
> +       if (cpufreq_device->cpufreq_state == cooling_state)
> +               return 0;
> +
> +       clip_freq = get_cpu_frequency(cpu, cooling_state);
> +       if (!clip_freq)
> +               return -EINVAL;
> +
> +       cpufreq_device->cpufreq_state = cooling_state;
> +       cpufreq_device->cpufreq_val = clip_freq;
> +       notify_device = cpufreq_device;
> +
> +       if (cooling_state != 0)
> +               event = CPUFREQ_COOLING_START;
> +       else
> +               event = CPUFREQ_COOLING_STOP;
> +
> +       blocking_notifier_call_chain(&cputherm_state_notifier_list,
> +                                               event, &clip_freq);
> +
> +       for_each_cpu(cpuid, mask) {
> +               if (is_cpufreq_valid(cpuid))
> +                       cpufreq_update_policy(cpuid);
> +       }
> +
> +       notify_device = NOTIFY_INVALID;
> +
> +       return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb:        struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> +                                       unsigned long event, void *data)
> +{
> +       struct cpufreq_policy *policy = data;
> +       unsigned long max_freq = 0;
> +
> +       if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> +               return 0;
> +
> +       if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> +               max_freq = notify_device->cpufreq_val;
> +
> +       /* Never exceed user_policy.max*/
> +       if (max_freq > policy->user_policy.max)
> +               max_freq = policy->user_policy.max;
> +
> +       if (policy->max > max_freq)
> +               cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> +       return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +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 *mask;
> +       unsigned int cpu;
> +       struct cpufreq_frequency_table *table;
> +
> +       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;
> +
> +       mask = &cpufreq_device->allowed_cpus;
> +       cpu = cpumask_any(mask);
> +       table = cpufreq_frequency_get_table(cpu);
> +       if (!table) {
> +               *state = 0;
> +               ret = 0;
> +               goto return_get_max_state;
> +       }
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               i++;
> +       }
> +       if (i > 0) {
> +               *state = --i;
> +               ret = 0;
> +       }
> +
> +return_get_max_state:
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling state.
> + */
> +static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
> +                                unsigned long *state)
> +{
> +       int ret = -EINVAL;
> +       struct cpufreq_cooling_device *cpufreq_device;
> +       struct cpumask *mask;
> +       unsigned int cpu;
> +       struct cpufreq_policy policy;
> +
> +       *state = 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 err_get_cur_state;
> +
> +       mask = &cpufreq_device->allowed_cpus;
> +       cpu = cpumask_any(mask);
> +
> +       ret = cpufreq_get_policy(&policy, cpu);
> +       if (ret)
> +               goto uninit_get_cur_state;
> +
> +       ret = get_frequency_level(cpu, policy.max);
> +       if (ret < 0)
> +               goto uninit_get_cur_state;
> +
> +       *state = ret;
> +
> +uninit_get_cur_state:
> +       ret = 0;
> +err_get_cur_state:
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_set_cur_state - callback function to set the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: set this variable to the current cooling state.
> + */
> +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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       return ret;
> +}
> +
> +/* Bind cpufreq callbacks to thermal cooling device ops */
> +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> +       .get_max_state = cpufreq_get_max_state,
> +       .get_cur_state = cpufreq_get_cur_state,
> +       .set_cur_state = cpufreq_set_cur_state,
> +};
> +
> +/* Notifier for cpufreq policy change */
> +static struct notifier_block thermal_cpufreq_notifier_block = {
> +       .notifier_call = cpufreq_thermal_notifier,
> +};
> +
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +       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;
> +       char dev_name[THERMAL_NAME_LENGTH];
> +       int ret = 0, id = 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*/
> +               if (!cpufreq_get_policy(&policy, i))
> +                       continue;
> +               if (min == 0 && max == 0) {
> +                       min = policy.cpuinfo.min_freq;
> +                       max = policy.cpuinfo.max_freq;
> +               } else {
> +                       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);
> +       if (!cpufreq_dev)
> +               return ERR_PTR(-ENOMEM);
> +
> +       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);
> +               return ERR_PTR(-EINVAL);
> +       }
> +
> +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> +
> +       cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> +                                               &cpufreq_cooling_ops);
> +       if (!cool_dev) {
> +               release_idr(&cpufreq_idr, cpufreq_dev->id);
> +               kfree(cpufreq_dev);
> +               return ERR_PTR(-EINVAL);
> +       }
> +       cpufreq_dev->id = id;
> +       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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return cool_dev;
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_register);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +{
> +       struct cpufreq_cooling_device *cpufreq_dev = NULL;
> +       unsigned int cpufreq_dev_count = 0;
> +
> +       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);
> +
> +       /* Unregister the notifier for the last cpufreq cooling device */
> +       if (cpufreq_dev_count == 1) {
> +               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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> new file mode 100644
> index 0000000..555b917
> --- /dev/null
> +++ b/include/linux/cpu_cooling.h
> @@ -0,0 +1,79 @@
> +/*
> + *  linux/include/linux/cpu_cooling.h
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  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 __CPU_COOLING_H__
> +#define __CPU_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +#define CPUFREQ_COOLING_START          0
> +#define CPUFREQ_COOLING_STOP           1
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
> +
> +#ifdef CONFIG_CPU_THERMAL
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +               struct cpumask *clip_cpus);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> +#else /* !CONFIG_CPU_THERMAL */
> +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +{
> +       return NULL;
> +}
> +static inline void cpufreq_cooling_unregister(
> +               struct thermal_cooling_device *cdev)
> +{
> +       return;
> +}
> +#endif /* CONFIG_CPU_THERMAL */
> +
> +#endif /* __CPU_COOLING_H__ */
> --
> 1.7.1
>

^ permalink raw reply	[flat|nested] 50+ messages in thread

* [lm-sensors] [RESEND PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17 13:29         ` Amit Daniel Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Daniel Kachhap @ 2012-08-17 13:41 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park,
	Guenter Roeck

This patchset introduces a new generic cooling device based on cpufreq
that can be used on non-ACPI platforms.  As a proof of concept, we have
drivers for the following platforms using this mechanism now:

 * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
 * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
 * Omap platforms in drivers/staging/omap-thermal/

There is a small change in cpufreq cooling registration APIs, so a minor
change is needed for Freescale and omap platforms.

Brief Description:

1) The generic cooling devices code is placed inside driver/thermal/*
   as placing inside acpi folder will need un-necessary enabling of acpi
   code.  This code is architecture independent.

2) This patchset adds generic cpu cooling low level implementation
   through frequency clipping.  In future, other cpu related cooling
   devices may be added here.  An ACPI version of this already exists
   (drivers/acpi/processor_thermal.c) .But this will be useful for
   platforms like ARM using the generic thermal interface along with the
   generic cpu cooling devices.  The cooling device registration API's
   return cooling device pointers which can be easily binded with the
   thermal zone trip points.  The important APIs exposed are,

   a) struct thermal_cooling_device *cpufreq_cooling_register(
        struct cpumask *clip_cpus)
   b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)

3) Samsung exynos platform thermal implementation is done using the
   generic cpu cooling APIs and the new trip type.  The temperature sensor
   driver present in the hwmon folder(registered as hwmon driver) is moved
   to thermal folder and registered as a thermal driver.

A simple data/control flow diagrams is shown below,

Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
          |                             |
         \|/                            |
  Cpufreq cooling device <---------------

About this patch:

Add support for generic cpu thermal cooling low level implementations
using frequency scaling up/down based on the registration parameters.
Different cpu related cooling devices can be registered by the user and
the binding of these cooling devices to the corresponding trip points can
be easily done as the registration APIs return the cooling device pointer.
The user of these APIs are responsible for passing clipping frequency .
The drivers can also register to recieve notification about any cooling
action called.

[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Cc: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
 Documentation/thermal/cpu-cooling-api.txt |   52 +++
 drivers/thermal/Kconfig                   |   11 +
 drivers/thermal/Makefile                  |    1 +
 drivers/thermal/cpu_cooling.c             |  586 +++++++++++++++++++++++++++++
 include/linux/cpu_cooling.h               |   79 ++++
 5 files changed, 729 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/thermal/cpu-cooling-api.txt
 create mode 100644 drivers/thermal/cpu_cooling.c
 create mode 100644 include/linux/cpu_cooling.h

diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
new file mode 100644
index 0000000..a1f2a6b
--- /dev/null
+++ b/Documentation/thermal/cpu-cooling-api.txt
@@ -0,0 +1,52 @@
+CPU cooling APIs How To
+=================+
+Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
+
+Updated: 12 May 2012
+
+Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+
+0. Introduction
+
+The generic cpu cooling(freq clipping) provides registration/unregistration APIs
+to the caller. The binding of the cooling devices to the trip point is left for
+the user. The registration APIs returns the cooling device pointer.
+
+1. cpu cooling APIs
+
+1.1 cpufreq registration/unregistration APIs
+1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+
+    This interface function registers the cpufreq cooling device with the name
+    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+    cooling devices.
+
+   clip_cpus: cpumask of cpus where the frequency constraints will happen.
+
+1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+
+    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+
+    cdev: Cooling device pointer which has to be unregistered.
+
+
+1.2 CPU cooling action notifier register/unregister interface
+1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
+
+1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
+	unsigned int list)
+
+    This interface registers a driver with cpu cooling layer. The driver will
+    be notified when any cpu cooling action is called.
+
+    nb: notifier function to register
+    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 7dd8c34..996003b 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -19,6 +19,17 @@ config THERMAL_HWMON
 	depends on HWMON=y || HWMON=THERMAL
 	default y
 
+config CPU_THERMAL
+	bool "generic cpu cooling support"
+	depends on THERMAL && CPU_FREQ
+	help
+	  This implements the generic cpu cooling mechanism through frequency
+	  reduction, cpu hotplug and any other ways of reducing temperature. An
+	  ACPI version of this already exists(drivers/acpi/processor_thermal.c).
+	  This will be useful for platforms using the generic thermal interface
+	  and not the ACPI interface.
+	  If you want this support, you should say Y here.
+
 config SPEAR_THERMAL
 	bool "SPEAr thermal sensor driver"
 	depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fd9369a..aae59ad 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,5 +3,6 @@
 #
 
 obj-$(CONFIG_THERMAL)		+= thermal_sys.o
+obj-$(CONFIG_CPU_THERMAL)		+= cpu_cooling.o
 obj-$(CONFIG_SPEAR_THERMAL)		+= spear_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)	+= rcar_thermal.o
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 0000000..66cbd52
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,586 @@
+/*
+ *  linux/drivers/thermal/cpu_cooling.c
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+/**
+ * struct cpufreq_cooling_device
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ *	registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the the
+ *	egistered cooling device.
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ *	cooling	devices.
+ * @cpufreq_val: integer value representing the absolute value of the clipped
+ *	frequency.
+ * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ *
+ * This structure is required for keeping information of each
+ * cpufreq_cooling_device registered as a list whose head is represented by
+ * cooling_cpufreq_list. In order to prevent corruption of this list a
+ * mutex lock cooling_cpufreq_lock is used.
+ */
+struct cpufreq_cooling_device {
+	int id;
+	struct thermal_cooling_device *cool_dev;
+	unsigned int cpufreq_state;
+	unsigned int cpufreq_val;
+	struct cpumask allowed_cpus;
+	struct list_head node;
+};
+static LIST_HEAD(cooling_cpufreq_list);
+static DEFINE_IDR(cpufreq_idr);
+
+static struct mutex cooling_cpufreq_lock;
+
+/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
+#define NOTIFY_INVALID NULL
+struct cpufreq_cooling_device *notify_device;
+
+/* Head of the blocking notifier chain to inform about frequency clamping */
+static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+	int err;
+again:
+	if (unlikely(idr_pre_get(idr, GFP_KERNEL) = 0))
+		return -ENOMEM;
+
+	mutex_lock(&cooling_cpufreq_lock);
+	err = idr_get_new(idr, NULL, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	if (unlikely(err = -EAGAIN))
+		goto again;
+	else if (unlikely(err))
+		return err;
+
+	*id = *id & MAX_ID_MASK;
+	return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+	mutex_lock(&cooling_cpufreq_lock);
+	idr_remove(idr, id);
+	mutex_unlock(&cooling_cpufreq_lock);
+}
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_register(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_register_notifier);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
+{
+	int ret = 0;
+
+	switch (list) {
+	case CPUFREQ_COOLING_START:
+	case CPUFREQ_COOLING_STOP:
+		ret = blocking_notifier_chain_unregister(
+				&cputherm_state_notifier_list, nb);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL(cputherm_unregister_notifier);
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
+ * @cpu: cpu for which check is needed.
+ */
+static int is_cpufreq_valid(int cpu)
+{
+	struct cpufreq_policy policy;
+	return !cpufreq_get_policy(&policy, 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=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
+ * @return: frequency of the cpu or 0 for error.
+ */
+static unsigned int get_cpu_frequency(unsigned int cpu, unsigned int level)
+{
+	int ret = 0, i = 0;
+	unsigned int level_index;
+	bool descend = false;
+	struct cpufreq_frequency_table *table +					cpufreq_frequency_get_table(cpu);
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/*check if table in ascending or descending order*/
+		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+			(table[i + 1].frequency < table[i].frequency)
+			&& !descend) {
+			descend = true;
+		}
+
+		/*return if level matched and table in descending order*/
+		if (descend && i = level)
+			return table[i].frequency;
+		i++;
+	}
+	i--;
+
+	if (level > i || descend)
+		return ret;
+	level_index = i - level;
+
+	/*Scan the table in reverse order and match the level*/
+	while (i >= 0) {
+		if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
+			continue;
+		/*return if level matched*/
+		if (i = level_index)
+			return table[i].frequency;
+		i--;
+	}
+	return ret;
+}
+
+/**
+ * get_frequency_level - get the level from the frequency.
+ * @cpu: cpu for which frequency is fetched.
+ * @freq: frequency for which level is requested.
+ *
+ * @return: level corresponding to the frequency.
+ *	e.g level=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
+ *	or negative value for any error.
+ */
+static int get_frequency_level(unsigned int cpu, unsigned int freq)
+{
+	int ret = -EINVAL, i = 0;
+	unsigned int max_index;
+	bool descend = false;
+	struct cpufreq_frequency_table *table +					cpufreq_frequency_get_table(cpu);
+	if (!table)
+		return ret;
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
+			continue;
+
+		/*check if table in ascending or descending order*/
+		if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+			(table[i + 1].frequency < table[i].frequency)
+			&& !descend) {
+			descend = true;
+		}
+
+		/*return if level matched and table in descending order*/
+		if (descend && table[i].frequency = freq)
+			return i;
+		i++;
+	}
+	i--;
+
+	if (descend)
+		return ret;
+
+	max_index = i;
+
+	/*Scan the table in reverse order and match the freq*/
+	while (i >= 0) {
+		if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
+			continue;
+		/*return if level matched*/
+		if (table[i].frequency = freq)
+			return max_index - i;
+		i--;
+	}
+	return ret;
+}
+
+/**
+ * cpufreq_apply_cooling - function to apply frequency clipping.
+ * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
+ *	clipping data.
+ * @cooling_state: value of the cooling state.
+ */
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+				unsigned long cooling_state)
+{
+	unsigned int event, cpuid, clip_freq;
+	struct cpumask *mask = &cpufreq_device->allowed_cpus;
+	unsigned int cpu = cpumask_any(mask);
+
+
+	/* Check if the old cooling action is same as new cooling action */
+	if (cpufreq_device->cpufreq_state = cooling_state)
+		return 0;
+
+	clip_freq = get_cpu_frequency(cpu, cooling_state);
+	if (!clip_freq)
+		return -EINVAL;
+
+	cpufreq_device->cpufreq_state = cooling_state;
+	cpufreq_device->cpufreq_val = clip_freq;
+	notify_device = cpufreq_device;
+
+	if (cooling_state != 0)
+		event = CPUFREQ_COOLING_START;
+	else
+		event = CPUFREQ_COOLING_STOP;
+
+	blocking_notifier_call_chain(&cputherm_state_notifier_list,
+						event, &clip_freq);
+
+	for_each_cpu(cpuid, mask) {
+		if (is_cpufreq_valid(cpuid))
+			cpufreq_update_policy(cpuid);
+	}
+
+	notify_device = NOTIFY_INVALID;
+
+	return 0;
+}
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb:	struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+					unsigned long event, void *data)
+{
+	struct cpufreq_policy *policy = data;
+	unsigned long max_freq = 0;
+
+	if (event != CPUFREQ_ADJUST || notify_device = NOTIFY_INVALID)
+		return 0;
+
+	if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
+		max_freq = notify_device->cpufreq_val;
+
+	/* Never exceed user_policy.max*/
+	if (max_freq > policy->user_policy.max)
+		max_freq = policy->user_policy.max;
+
+	if (policy->max > max_freq)
+		cpufreq_verify_within_limits(policy, 0, max_freq);
+
+	return 0;
+}
+
+/*
+ * cpufreq cooling device callback functions are defined below
+ */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ */
+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 *mask;
+	unsigned int cpu;
+	struct cpufreq_frequency_table *table;
+
+	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;
+
+	mask = &cpufreq_device->allowed_cpus;
+	cpu = cpumask_any(mask);
+	table = cpufreq_frequency_get_table(cpu);
+	if (!table) {
+		*state = 0;
+		ret = 0;
+		goto return_get_max_state;
+	}
+
+	while (table[i].frequency != CPUFREQ_TABLE_END) {
+		if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
+			continue;
+		i++;
+	}
+	if (i > 0) {
+		*state = --i;
+		ret = 0;
+	}
+
+return_get_max_state:
+	mutex_unlock(&cooling_cpufreq_lock);
+	return ret;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling state.
+ */
+static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
+				 unsigned long *state)
+{
+	int ret = -EINVAL;
+	struct cpufreq_cooling_device *cpufreq_device;
+	struct cpumask *mask;
+	unsigned int cpu;
+	struct cpufreq_policy policy;
+
+	*state = 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 err_get_cur_state;
+
+	mask = &cpufreq_device->allowed_cpus;
+	cpu = cpumask_any(mask);
+
+	ret = cpufreq_get_policy(&policy, cpu);
+	if (ret)
+		goto uninit_get_cur_state;
+
+	ret = get_frequency_level(cpu, policy.max);
+	if (ret < 0)
+		goto uninit_get_cur_state;
+
+	*state = ret;
+
+uninit_get_cur_state:
+	ret = 0;
+err_get_cur_state:
+	mutex_unlock(&cooling_cpufreq_lock);
+	return ret;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ */
+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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+
+	return ret;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+	.get_max_state = cpufreq_get_max_state,
+	.get_cur_state = cpufreq_get_cur_state,
+	.set_cur_state = cpufreq_set_cur_state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+	.notifier_call = cpufreq_thermal_notifier,
+};
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+	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;
+	char dev_name[THERMAL_NAME_LENGTH];
+	int ret = 0, id = 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*/
+		if (!cpufreq_get_policy(&policy, i))
+			continue;
+		if (min = 0 && max = 0) {
+			min = policy.cpuinfo.min_freq;
+			max = policy.cpuinfo.max_freq;
+		} else {
+			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);
+	if (!cpufreq_dev)
+		return ERR_PTR(-ENOMEM);
+
+	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);
+		return ERR_PTR(-EINVAL);
+	}
+
+	sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
+
+	cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
+						&cpufreq_cooling_ops);
+	if (!cool_dev) {
+		release_idr(&cpufreq_idr, cpufreq_dev->id);
+		kfree(cpufreq_dev);
+		return ERR_PTR(-EINVAL);
+	}
+	cpufreq_dev->id = id;
+	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);
+
+	mutex_unlock(&cooling_cpufreq_lock);
+	return cool_dev;
+}
+EXPORT_SYMBOL(cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+	struct cpufreq_cooling_device *cpufreq_dev = NULL;
+	unsigned int cpufreq_dev_count = 0;
+
+	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);
+
+	/* Unregister the notifier for the last cpufreq cooling device */
+	if (cpufreq_dev_count = 1) {
+		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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
new file mode 100644
index 0000000..555b917
--- /dev/null
+++ b/include/linux/cpu_cooling.h
@@ -0,0 +1,79 @@
+/*
+ *  linux/include/linux/cpu_cooling.h
+ *
+ *  Copyright (C) 2012	Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  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 __CPU_COOLING_H__
+#define __CPU_COOLING_H__
+
+#include <linux/thermal.h>
+
+#define CPUFREQ_COOLING_START		0
+#define CPUFREQ_COOLING_STOP		1
+
+/**
+ * cputherm_register_notifier - Register a notifier with cpu cooling interface.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. possible values are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function registers a driver with cpu cooling layer. The driver
+ * will be notified when any cpu cooling action is called.
+ */
+int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
+
+/**
+ * cputherm_unregister_notifier - Un-register a notifier.
+ * @nb:	struct notifier_block * with callback info.
+ * @list: integer value for which notification is needed. values possible are
+ *	CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
+ *
+ * This exported function un-registers a driver with cpu cooling layer.
+ */
+int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
+
+#ifdef CONFIG_CPU_THERMAL
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+		struct cpumask *clip_cpus);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
+#else /* !CONFIG_CPU_THERMAL */
+static inline struct thermal_cooling_device *cpufreq_cooling_register(
+	struct cpumask *clip_cpus)
+{
+	return NULL;
+}
+static inline void cpufreq_cooling_unregister(
+		struct thermal_cooling_device *cdev)
+{
+	return;
+}
+#endif	/* CONFIG_CPU_THERMAL */
+
+#endif /* __CPU_COOLING_H__ */
-- 
1.7.1


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 50+ messages in thread

* Re: [lm-sensors] [RESEND PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-17 13:32           ` Amit Kachhap
  0 siblings, 0 replies; 50+ messages in thread
From: Amit Kachhap @ 2012-08-17 13:44 UTC (permalink / raw)
  To: linux-pm, Andrew Morton, Zhang Rui, Len Brown
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park,
	Guenter Roeck

This is repost containing the changes suggested by Zhang Rui. Now the
cpufreq current state will be calculated each time instead of earlier
way of returning the cached value.

Thanks,
Amit Daniel

On 17 August 2012 18:59, Amit Daniel Kachhap <amit.kachhap@linaro.org> wrote:
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms.  As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
>
>  * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
>  * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>  * Omap platforms in drivers/staging/omap-thermal/
>
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale and omap platforms.
>
> Brief Description:
>
> 1) The generic cooling devices code is placed inside driver/thermal/*
>    as placing inside acpi folder will need un-necessary enabling of acpi
>    code.  This code is architecture independent.
>
> 2) This patchset adds generic cpu cooling low level implementation
>    through frequency clipping.  In future, other cpu related cooling
>    devices may be added here.  An ACPI version of this already exists
>    (drivers/acpi/processor_thermal.c) .But this will be useful for
>    platforms like ARM using the generic thermal interface along with the
>    generic cpu cooling devices.  The cooling device registration API's
>    return cooling device pointers which can be easily binded with the
>    thermal zone trip points.  The important APIs exposed are,
>
>    a) struct thermal_cooling_device *cpufreq_cooling_register(
>         struct cpumask *clip_cpus)
>    b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>
> 3) Samsung exynos platform thermal implementation is done using the
>    generic cpu cooling APIs and the new trip type.  The temperature sensor
>    driver present in the hwmon folder(registered as hwmon driver) is moved
>    to thermal folder and registered as a thermal driver.
>
> A simple data/control flow diagrams is shown below,
>
> Core Linux thermal <----->  Exynos thermal interface <----- Temperature Sensor
>           |                             |
>          \|/                            |
>   Cpufreq cooling device <---------------
>
> About this patch:
>
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
>
> [akpm@linux-foundation.org: fix comment layout]
> Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
> ---
>  Documentation/thermal/cpu-cooling-api.txt |   52 +++
>  drivers/thermal/Kconfig                   |   11 +
>  drivers/thermal/Makefile                  |    1 +
>  drivers/thermal/cpu_cooling.c             |  586 +++++++++++++++++++++++++++++
>  include/linux/cpu_cooling.h               |   79 ++++
>  5 files changed, 729 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/thermal/cpu-cooling-api.txt
>  create mode 100644 drivers/thermal/cpu_cooling.c
>  create mode 100644 include/linux/cpu_cooling.h
>
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..a1f2a6b
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,52 @@
> +CPU cooling APIs How To
> +=================> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c)  2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +
> +    This interface function registers the cpufreq cooling device with the name
> +    "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> +    cooling devices.
> +
> +   clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> +    This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> +    cdev: Cooling device pointer which has to be unregistered.
> +
> +
> +1.2 CPU cooling action notifier register/unregister interface
> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> +
> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> +       unsigned int list)
> +
> +    This interface registers a driver with cpu cooling layer. The driver will
> +    be notified when any cpu cooling action is called.
> +
> +    nb: notifier function to register
> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>         depends on HWMON=y || HWMON=THERMAL
>         default y
>
> +config CPU_THERMAL
> +       bool "generic cpu cooling support"
> +       depends on THERMAL && CPU_FREQ
> +       help
> +         This implements the generic cpu cooling mechanism through frequency
> +         reduction, cpu hotplug and any other ways of reducing temperature. An
> +         ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> +         This will be useful for platforms using the generic thermal interface
> +         and not the ACPI interface.
> +         If you want this support, you should say Y here.
> +
>  config SPEAR_THERMAL
>         bool "SPEAr thermal sensor driver"
>         depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
>  #
>
>  obj-$(CONFIG_THERMAL)          += thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL)              += cpu_cooling.o
>  obj-$(CONFIG_SPEAR_THERMAL)            += spear_thermal.o
>  obj-$(CONFIG_RCAR_THERMAL)     += rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..66cbd52
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,586 @@
> +/*
> + *  linux/drivers/thermal/cpu_cooling.c
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful, but
> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + *  General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License along
> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + *     registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + *     egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + *     cooling devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + *     frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> +       int id;
> +       struct thermal_cooling_device *cool_dev;
> +       unsigned int cpufreq_state;
> +       unsigned int cpufreq_val;
> +       struct cpumask allowed_cpus;
> +       struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/* Head of the blocking notifier chain to inform about frequency clamping */
> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> +       int err;
> +again:
> +       if (unlikely(idr_pre_get(idr, GFP_KERNEL) = 0))
> +               return -ENOMEM;
> +
> +       mutex_lock(&cooling_cpufreq_lock);
> +       err = idr_get_new(idr, NULL, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       if (unlikely(err = -EAGAIN))
> +               goto again;
> +       else if (unlikely(err))
> +               return err;
> +
> +       *id = *id & MAX_ID_MASK;
> +       return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> +       mutex_lock(&cooling_cpufreq_lock);
> +       idr_remove(idr, id);
> +       mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_register(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_register_notifier);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> +{
> +       int ret = 0;
> +
> +       switch (list) {
> +       case CPUFREQ_COOLING_START:
> +       case CPUFREQ_COOLING_STOP:
> +               ret = blocking_notifier_chain_unregister(
> +                               &cputherm_state_notifier_list, nb);
> +               break;
> +       default:
> +               ret = -EINVAL;
> +       }
> +       return ret;
> +}
> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> +       struct cpufreq_policy policy;
> +       return !cpufreq_get_policy(&policy, 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=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
> + * @return: frequency of the cpu or 0 for error.
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned int level)
> +{
> +       int ret = 0, i = 0;
> +       unsigned int level_index;
> +       bool descend = false;
> +       struct cpufreq_frequency_table *table > +                                       cpufreq_frequency_get_table(cpu);
> +       if (!table)
> +               return ret;
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +
> +               /*check if table in ascending or descending order*/
> +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +                       (table[i + 1].frequency < table[i].frequency)
> +                       && !descend) {
> +                       descend = true;
> +               }
> +
> +               /*return if level matched and table in descending order*/
> +               if (descend && i = level)
> +                       return table[i].frequency;
> +               i++;
> +       }
> +       i--;
> +
> +       if (level > i || descend)
> +               return ret;
> +       level_index = i - level;
> +
> +       /*Scan the table in reverse order and match the level*/
> +       while (i >= 0) {
> +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               /*return if level matched*/
> +               if (i = level_index)
> +                       return table[i].frequency;
> +               i--;
> +       }
> +       return ret;
> +}
> +
> +/**
> + * get_frequency_level - get the level from the frequency.
> + * @cpu: cpu for which frequency is fetched.
> + * @freq: frequency for which level is requested.
> + *
> + * @return: level corresponding to the frequency.
> + *     e.g level=0 --> 1st MAX FREQ, LEVEL=1 ---> 2nd MAX FREQ, .... etc
> + *     or negative value for any error.
> + */
> +static int get_frequency_level(unsigned int cpu, unsigned int freq)
> +{
> +       int ret = -EINVAL, i = 0;
> +       unsigned int max_index;
> +       bool descend = false;
> +       struct cpufreq_frequency_table *table > +                                       cpufreq_frequency_get_table(cpu);
> +       if (!table)
> +               return ret;
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +
> +               /*check if table in ascending or descending order*/
> +               if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> +                       (table[i + 1].frequency < table[i].frequency)
> +                       && !descend) {
> +                       descend = true;
> +               }
> +
> +               /*return if level matched and table in descending order*/
> +               if (descend && table[i].frequency = freq)
> +                       return i;
> +               i++;
> +       }
> +       i--;
> +
> +       if (descend)
> +               return ret;
> +
> +       max_index = i;
> +
> +       /*Scan the table in reverse order and match the freq*/
> +       while (i >= 0) {
> +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               /*return if level matched*/
> +               if (table[i].frequency = freq)
> +                       return max_index - i;
> +               i--;
> +       }
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + *     clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> +                               unsigned long cooling_state)
> +{
> +       unsigned int event, cpuid, clip_freq;
> +       struct cpumask *mask = &cpufreq_device->allowed_cpus;
> +       unsigned int cpu = cpumask_any(mask);
> +
> +
> +       /* Check if the old cooling action is same as new cooling action */
> +       if (cpufreq_device->cpufreq_state = cooling_state)
> +               return 0;
> +
> +       clip_freq = get_cpu_frequency(cpu, cooling_state);
> +       if (!clip_freq)
> +               return -EINVAL;
> +
> +       cpufreq_device->cpufreq_state = cooling_state;
> +       cpufreq_device->cpufreq_val = clip_freq;
> +       notify_device = cpufreq_device;
> +
> +       if (cooling_state != 0)
> +               event = CPUFREQ_COOLING_START;
> +       else
> +               event = CPUFREQ_COOLING_STOP;
> +
> +       blocking_notifier_call_chain(&cputherm_state_notifier_list,
> +                                               event, &clip_freq);
> +
> +       for_each_cpu(cpuid, mask) {
> +               if (is_cpufreq_valid(cpuid))
> +                       cpufreq_update_policy(cpuid);
> +       }
> +
> +       notify_device = NOTIFY_INVALID;
> +
> +       return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb:        struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> +                                       unsigned long event, void *data)
> +{
> +       struct cpufreq_policy *policy = data;
> +       unsigned long max_freq = 0;
> +
> +       if (event != CPUFREQ_ADJUST || notify_device = NOTIFY_INVALID)
> +               return 0;
> +
> +       if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> +               max_freq = notify_device->cpufreq_val;
> +
> +       /* Never exceed user_policy.max*/
> +       if (max_freq > policy->user_policy.max)
> +               max_freq = policy->user_policy.max;
> +
> +       if (policy->max > max_freq)
> +               cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> +       return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +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 *mask;
> +       unsigned int cpu;
> +       struct cpufreq_frequency_table *table;
> +
> +       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;
> +
> +       mask = &cpufreq_device->allowed_cpus;
> +       cpu = cpumask_any(mask);
> +       table = cpufreq_frequency_get_table(cpu);
> +       if (!table) {
> +               *state = 0;
> +               ret = 0;
> +               goto return_get_max_state;
> +       }
> +
> +       while (table[i].frequency != CPUFREQ_TABLE_END) {
> +               if (table[i].frequency = CPUFREQ_ENTRY_INVALID)
> +                       continue;
> +               i++;
> +       }
> +       if (i > 0) {
> +               *state = --i;
> +               ret = 0;
> +       }
> +
> +return_get_max_state:
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling state.
> + */
> +static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
> +                                unsigned long *state)
> +{
> +       int ret = -EINVAL;
> +       struct cpufreq_cooling_device *cpufreq_device;
> +       struct cpumask *mask;
> +       unsigned int cpu;
> +       struct cpufreq_policy policy;
> +
> +       *state = 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 err_get_cur_state;
> +
> +       mask = &cpufreq_device->allowed_cpus;
> +       cpu = cpumask_any(mask);
> +
> +       ret = cpufreq_get_policy(&policy, cpu);
> +       if (ret)
> +               goto uninit_get_cur_state;
> +
> +       ret = get_frequency_level(cpu, policy.max);
> +       if (ret < 0)
> +               goto uninit_get_cur_state;
> +
> +       *state = ret;
> +
> +uninit_get_cur_state:
> +       ret = 0;
> +err_get_cur_state:
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return ret;
> +}
> +
> +/**
> + * cpufreq_set_cur_state - callback function to set the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: set this variable to the current cooling state.
> + */
> +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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +
> +       return ret;
> +}
> +
> +/* Bind cpufreq callbacks to thermal cooling device ops */
> +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> +       .get_max_state = cpufreq_get_max_state,
> +       .get_cur_state = cpufreq_get_cur_state,
> +       .set_cur_state = cpufreq_set_cur_state,
> +};
> +
> +/* Notifier for cpufreq policy change */
> +static struct notifier_block thermal_cpufreq_notifier_block = {
> +       .notifier_call = cpufreq_thermal_notifier,
> +};
> +
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +       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;
> +       char dev_name[THERMAL_NAME_LENGTH];
> +       int ret = 0, id = 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*/
> +               if (!cpufreq_get_policy(&policy, i))
> +                       continue;
> +               if (min = 0 && max = 0) {
> +                       min = policy.cpuinfo.min_freq;
> +                       max = policy.cpuinfo.max_freq;
> +               } else {
> +                       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);
> +       if (!cpufreq_dev)
> +               return ERR_PTR(-ENOMEM);
> +
> +       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);
> +               return ERR_PTR(-EINVAL);
> +       }
> +
> +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> +
> +       cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> +                                               &cpufreq_cooling_ops);
> +       if (!cool_dev) {
> +               release_idr(&cpufreq_idr, cpufreq_dev->id);
> +               kfree(cpufreq_dev);
> +               return ERR_PTR(-EINVAL);
> +       }
> +       cpufreq_dev->id = id;
> +       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);
> +
> +       mutex_unlock(&cooling_cpufreq_lock);
> +       return cool_dev;
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_register);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +{
> +       struct cpufreq_cooling_device *cpufreq_dev = NULL;
> +       unsigned int cpufreq_dev_count = 0;
> +
> +       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);
> +
> +       /* Unregister the notifier for the last cpufreq cooling device */
> +       if (cpufreq_dev_count = 1) {
> +               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/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> new file mode 100644
> index 0000000..555b917
> --- /dev/null
> +++ b/include/linux/cpu_cooling.h
> @@ -0,0 +1,79 @@
> +/*
> + *  linux/include/linux/cpu_cooling.h
> + *
> + *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *  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 __CPU_COOLING_H__
> +#define __CPU_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +#define CPUFREQ_COOLING_START          0
> +#define CPUFREQ_COOLING_STOP           1
> +
> +/**
> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. possible values are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function registers a driver with cpu cooling layer. The driver
> + * will be notified when any cpu cooling action is called.
> + */
> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list);
> +
> +/**
> + * cputherm_unregister_notifier - Un-register a notifier.
> + * @nb:        struct notifier_block * with callback info.
> + * @list: integer value for which notification is needed. values possible are
> + *     CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> + *
> + * This exported function un-registers a driver with cpu cooling layer.
> + */
> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list);
> +
> +#ifdef CONFIG_CPU_THERMAL
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> +               struct cpumask *clip_cpus);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> +#else /* !CONFIG_CPU_THERMAL */
> +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> +       struct cpumask *clip_cpus)
> +{
> +       return NULL;
> +}
> +static inline void cpufreq_cooling_unregister(
> +               struct thermal_cooling_device *cdev)
> +{
> +       return;
> +}
> +#endif /* CONFIG_CPU_THERMAL */
> +
> +#endif /* __CPU_COOLING_H__ */
> --
> 1.7.1
>

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-17  8:56         ` Valentin, Eduardo
  (?)
@ 2012-08-20  2:16           ` Zhang Rui
  -1 siblings, 0 replies; 50+ messages in thread
From: Zhang Rui @ 2012-08-20  2:16 UTC (permalink / raw)
  To: Valentin, Eduardo
  Cc: Amit Kachhap, linux-pm, Andrew Morton, Len Brown,
	linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	Guenter Roeck, SangWook Ju, Durgadoss, Jean Delvare,
	Kyungmin Park, Kukjin Kim

On 五, 2012-08-17 at 11:56 +0300, Valentin, Eduardo wrote:
> Hello,

> >>> +
> >>> +
> >>> +1.2 CPU cooling action notifier register/unregister interface
> >>> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> >>> +     unsigned int list)
> >>> +
> >>> +    This interface registers a driver with cpu cooling layer. The driver will
> >>> +    be notified when any cpu cooling action is called.
> >>> +
> >>> +    nb: notifier function to register
> >>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> >>> +
> >>> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> >>> +     unsigned int list)
> >>> +
> >>> +    This interface registers a driver with cpu cooling layer. The driver will
> >>> +    be notified when any cpu cooling action is called.
> >>> +
> >>> +    nb: notifier function to register
> >>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> >>
> >> what are these two APIs used for?
> >> I did not see they are used in your patch set, do I miss something?
> > No currently they are not used by my patches. I added them on request
> > from Eduardo and others
> 
> Yeah, this was a suggestion as we didn't really know how the FW part
> would evolve by that time.
> 
> The reasoning is to allow any interested user (in kernel) to be
> notified when max frequency changes.

in this case, the cooling device should be updated.
Say all the target of the thermal_instances of a cooling devices is 0,
which means they want the cpu to run at maximum frequency, when the max
frequency changes, we should set the processor to the new max frequency
as well.
Using notification is not a good idea as user can not handle this
without interacting with the thermal framework.

IMO, we should introduce a new API to handle this, rather than just
notifications to users.

>  Actually, the use case behind
> this is to allow such users to perform some handshaking or stop their
> activities or even change some paramenters, in case the max frequency
> would change.

It is the cooling device driver that notice this change first, and it
should invoke something like thermal_cooling_device_update/rebind() to
tell the thermal framework this change.

>  Ideally it would be possible to nack the cooling
> transition. But that is yet a wider discussion. So far we don't have
> users for this.
> 
yep.
I thought about this before, but I'd prefer to do this when there is a
real user. Or else, we are kind of over-designing here.
how about removing this piece of code for now?

thanks,
rui

> >>
> >>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> >>> index 7dd8c34..996003b 100644
> >>> --- a/drivers/thermal/Kconfig
> >>> +++ b/drivers/thermal/Kconfig
> >>> @@ -19,6 +19,17 @@ config THERMAL_HWMON
> >>>       depends on HWMON=y || HWMON=THERMAL
> >>>       default y
> >>>
> >>> +config CPU_THERMAL
> >>> +     bool "generic cpu cooling support"
> >>> +     depends on THERMAL && CPU_FREQ
> >>> +     help
> >>> +       This implements the generic cpu cooling mechanism through frequency
> >>> +       reduction, cpu hotplug and any other ways of reducing temperature. An
> >>> +       ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> >>> +       This will be useful for platforms using the generic thermal interface
> >>> +       and not the ACPI interface.
> >>> +       If you want this support, you should say Y here.
> >>> +
> >>>  config SPEAR_THERMAL
> >>>       bool "SPEAr thermal sensor driver"
> >>>       depends on THERMAL
> >>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> >>> index fd9369a..aae59ad 100644
> >>> --- a/drivers/thermal/Makefile
> >>> +++ b/drivers/thermal/Makefile
> >>> @@ -3,5 +3,6 @@
> >>>  #
> >>>
> >>>  obj-$(CONFIG_THERMAL)                += thermal_sys.o
> >>> +obj-$(CONFIG_CPU_THERMAL)            += cpu_cooling.o
> >>>  obj-$(CONFIG_SPEAR_THERMAL)          += spear_thermal.o
> >>>  obj-$(CONFIG_RCAR_THERMAL)   += rcar_thermal.o
> >>> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> >>> new file mode 100644
> >>> index 0000000..c42e557
> >>> --- /dev/null
> >>> +++ b/drivers/thermal/cpu_cooling.c
> >>> @@ -0,0 +1,512 @@
> >>> +/*
> >>> + *  linux/drivers/thermal/cpu_cooling.c
> >>> + *
> >>> + *  Copyright (C) 2012       Samsung Electronics Co., Ltd(http://www.samsung.com)
> >>> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> >>> + *
> >>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >>> + *  This program is free software; you can redistribute it and/or modify
> >>> + *  it under the terms of the GNU General Public License as published by
> >>> + *  the Free Software Foundation; version 2 of the License.
> >>> + *
> >>> + *  This program is distributed in the hope that it will be useful, but
> >>> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> >>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> >>> + *  General Public License for more details.
> >>> + *
> >>> + *  You should have received a copy of the GNU General Public License along
> >>> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> >>> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> >>> + *
> >>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >>> + */
> >>> +#include <linux/kernel.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/thermal.h>
> >>> +#include <linux/platform_device.h>
> >>> +#include <linux/cpufreq.h>
> >>> +#include <linux/err.h>
> >>> +#include <linux/slab.h>
> >>> +#include <linux/cpu.h>
> >>> +#include <linux/cpu_cooling.h>
> >>> +
> >>> +/**
> >>> + * struct cpufreq_cooling_device
> >>> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> >>> + *   registered.
> >>> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> >>> + *   egistered cooling device.
> >>> + * @cpufreq_state: integer value representing the current state of cpufreq
> >>> + *   cooling devices.
> >>> + * @cpufreq_val: integer value representing the absolute value of the clipped
> >>> + *   frequency.
> >>> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> >>> + * @node: list_head to link all cpufreq_cooling_device together.
> >>> + *
> >>> + * This structure is required for keeping information of each
> >>> + * cpufreq_cooling_device registered as a list whose head is represented by
> >>> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> >>> + * mutex lock cooling_cpufreq_lock is used.
> >>> + */
> >>> +struct cpufreq_cooling_device {
> >>> +     int id;
> >>> +     struct thermal_cooling_device *cool_dev;
> >>> +     unsigned int cpufreq_state;
> >>> +     unsigned int cpufreq_val;
> >>> +     struct cpumask allowed_cpus;
> >>> +     struct list_head node;
> >>> +};
> >>> +static LIST_HEAD(cooling_cpufreq_list);
> >>> +static DEFINE_IDR(cpufreq_idr);
> >>> +
> >>> +static struct mutex cooling_cpufreq_lock;
> >>> +
> >>> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> >>> +#define NOTIFY_INVALID NULL
> >>> +struct cpufreq_cooling_device *notify_device;
> >>> +
> >>> +/* Head of the blocking notifier chain to inform about frequency clamping */
> >>> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> >>> +
> >>> +/**
> >>> + * get_idr - function to get a unique id.
> >>> + * @idr: struct idr * handle used to create a id.
> >>> + * @id: int * value generated by this function.
> >>> + */
> >>> +static int get_idr(struct idr *idr, int *id)
> >>> +{
> >>> +     int err;
> >>> +again:
> >>> +     if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> >>> +             return -ENOMEM;
> >>> +
> >>> +     mutex_lock(&cooling_cpufreq_lock);
> >>> +     err = idr_get_new(idr, NULL, id);
> >>> +     mutex_unlock(&cooling_cpufreq_lock);
> >>> +
> >>> +     if (unlikely(err == -EAGAIN))
> >>> +             goto again;
> >>> +     else if (unlikely(err))
> >>> +             return err;
> >>> +
> >>> +     *id = *id & MAX_ID_MASK;
> >>> +     return 0;
> >>> +}
> >>> +
> >>> +/**
> >>> + * release_idr - function to free the unique id.
> >>> + * @idr: struct idr * handle used for creating the id.
> >>> + * @id: int value representing the unique id.
> >>> + */
> >>> +static void release_idr(struct idr *idr, int id)
> >>> +{
> >>> +     mutex_lock(&cooling_cpufreq_lock);
> >>> +     idr_remove(idr, id);
> >>> +     mutex_unlock(&cooling_cpufreq_lock);
> >>> +}
> >>> +
> >>> +/**
> >>> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> >>> + * @nb:      struct notifier_block * with callback info.
> >>> + * @list: integer value for which notification is needed. possible values are
> >>> + *   CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> >>> + *
> >>> + * This exported function registers a driver with cpu cooling layer. The driver
> >>> + * will be notified when any cpu cooling action is called.
> >>> + */
> >>> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> >>> +{
> >>> +     int ret = 0;
> >>> +
> >>> +     switch (list) {
> >>> +     case CPUFREQ_COOLING_START:
> >>> +     case CPUFREQ_COOLING_STOP:
> >>> +             ret = blocking_notifier_chain_register(
> >>> +                             &cputherm_state_notifier_list, nb);
> >>> +             break;
> >>> +     default:
> >>> +             ret = -EINVAL;
> >>> +     }
> >>> +     return ret;
> >>> +}
> >>> +EXPORT_SYMBOL(cputherm_register_notifier);
> >>> +
> >>> +/**
> >>> + * cputherm_unregister_notifier - Un-register a notifier.
> >>> + * @nb:      struct notifier_block * with callback info.
> >>> + * @list: integer value for which notification is needed. values possible are
> >>> + *   CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> >>> + *
> >>> + * This exported function un-registers a driver with cpu cooling layer.
> >>> + */
> >>> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> >>> +{
> >>> +     int ret = 0;
> >>> +
> >>> +     switch (list) {
> >>> +     case CPUFREQ_COOLING_START:
> >>> +     case CPUFREQ_COOLING_STOP:
> >>> +             ret = blocking_notifier_chain_unregister(
> >>> +                             &cputherm_state_notifier_list, nb);
> >>> +             break;
> >>> +     default:
> >>> +             ret = -EINVAL;
> >>> +     }
> >>> +     return ret;
> >>> +}
> >>> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> >>> +
> >>> +/* Below code defines functions to be used for cpufreq as cooling device */
> >>> +
> >>> +/**
> >>> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> >>> + * @cpu: cpu for which check is needed.
> >>> + */
> >>> +static int is_cpufreq_valid(int cpu)
> >>> +{
> >>> +     struct cpufreq_policy policy;
> >>> +     return !cpufreq_get_policy(&policy, 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
> >>> + */
> >>> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
> >>> +{
> >>> +     int ret = 0, i = 0;
> >>> +     unsigned long level_index;
> >>> +     bool descend = false;
> >>> +     struct cpufreq_frequency_table *table =
> >>> +                                     cpufreq_frequency_get_table(cpu);
> >>> +     if (!table)
> >>> +             return ret;
> >>> +
> >>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> >>> +                     continue;
> >>> +
> >>> +             /*check if table in ascending or descending order*/
> >>> +             if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> >>> +                     (table[i + 1].frequency < table[i].frequency)
> >>> +                     && !descend) {
> >>> +                     descend = true;
> >>> +             }
> >>> +
> >>> +             /*return if level matched and table in descending order*/
> >>> +             if (descend && i == level)
> >>> +                     return table[i].frequency;
> >>> +             i++;
> >>> +     }
> >>> +     i--;
> >>> +
> >>> +     if (level > i || descend)
> >>> +             return ret;
> >>> +     level_index = i - level;
> >>> +
> >>> +     /*Scan the table in reverse order and match the level*/
> >>> +     while (i >= 0) {
> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> >>> +                     continue;
> >>> +             /*return if level matched*/
> >>> +             if (i == level_index)
> >>> +                     return table[i].frequency;
> >>> +             i--;
> >>> +     }
> >>> +     return ret;
> >>> +}
> >>> +
> >>> +/**
> >>> + * cpufreq_apply_cooling - function to apply frequency clipping.
> >>> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> >>> + *   clipping data.
> >>> + * @cooling_state: value of the cooling state.
> >>> + */
> >>> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> >>> +                             unsigned long cooling_state)
> >>> +{
> >>> +     unsigned int event, cpuid, clip_freq;
> >>> +     struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> >>> +     unsigned int cpu = cpumask_any(maskPtr);
> >>> +
> >>> +
> >>> +     /* Check if the old cooling action is same as new cooling action */
> >>> +     if (cpufreq_device->cpufreq_state == cooling_state)
> >>> +             return 0;
> >>> +
> >>> +     clip_freq = get_cpu_frequency(cpu, cooling_state);
> >>> +     if (!clip_freq)
> >>> +             return -EINVAL;
> >>> +
> >>> +     cpufreq_device->cpufreq_state = cooling_state;
> >>> +     cpufreq_device->cpufreq_val = clip_freq;
> >>> +     notify_device = cpufreq_device;
> >>> +
> >>> +     if (cooling_state != 0)
> >>> +             event = CPUFREQ_COOLING_START;
> >>> +     else
> >>> +             event = CPUFREQ_COOLING_STOP;
> >>> +
> >>> +     blocking_notifier_call_chain(&cputherm_state_notifier_list,
> >>> +                                             event, &clip_freq);
> >>> +
> >>> +     for_each_cpu(cpuid, maskPtr) {
> >>> +             if (is_cpufreq_valid(cpuid))
> >>> +                     cpufreq_update_policy(cpuid);
> >>> +     }
> >>> +
> >>> +     notify_device = NOTIFY_INVALID;
> >>> +
> >>> +     return 0;
> >>> +}
> >>> +
> >>> +/**
> >>> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> >>> + * @nb:      struct notifier_block * with callback info.
> >>> + * @event: value showing cpufreq event for which this function invoked.
> >>> + * @data: callback-specific data
> >>> + */
> >>> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> >>> +                                     unsigned long event, void *data)
> >>> +{
> >>> +     struct cpufreq_policy *policy = data;
> >>> +     unsigned long max_freq = 0;
> >>> +
> >>> +     if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> >>> +             return 0;
> >>> +
> >>> +     if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> >>> +             max_freq = notify_device->cpufreq_val;
> >>> +
> >>> +     /* Never exceed user_policy.max*/
> >>> +     if (max_freq > policy->user_policy.max)
> >>> +             max_freq = policy->user_policy.max;
> >>> +
> >>> +     if (policy->max != max_freq)
> >>> +             cpufreq_verify_within_limits(policy, 0, max_freq);
> >>> +
> >>> +     return 0;
> >>> +}
> >>> +
> >>> +/*
> >>> + * cpufreq cooling device callback functions are defined below
> >>> + */
> >>> +
> >>> +/**
> >>> + * cpufreq_get_max_state - callback function to get the max cooling state.
> >>> + * @cdev: thermal cooling device pointer.
> >>> + * @state: fill this variable with the max cooling state.
> >>> + */
> >>> +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;
> >>> +     unsigned int cpu;
> >>> +     struct cpufreq_frequency_table *table;
> >>> +
> >>> +     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;
> >>> +     }
> >>> +
> >>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> >>> +                     continue;
> >>> +             i++;
> >>> +     }
> >>> +     if (i > 0) {
> >>> +             *state = --i;
> >>> +             ret = 0;
> >>> +     }
> >>> +
> >>> +return_get_max_state:
> >>> +     mutex_unlock(&cooling_cpufreq_lock);
> >>> +     return ret;
> >>> +}
> >>> +
> >>> +/**
> >>> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> >>> + * @cdev: thermal cooling device pointer.
> >>> + * @state: fill this variable with the current cooling 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);
> >>> +
> >>
> >> as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
> >> use the current cpu frequency to get the REAL cooling state, rather than
> >> using a cached value.
> >
> > Yes agreed , I will repost with your suggestion.
> >
> > Thanks,
> > Amit
> >>
> >> thanks,
> >> rui
> >>
> >>
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 
> 


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-20  2:16           ` Zhang Rui
  0 siblings, 0 replies; 50+ messages in thread
From: Zhang Rui @ 2012-08-20  2:16 UTC (permalink / raw)
  To: Valentin, Eduardo
  Cc: Amit Kachhap, linux-pm, Andrew Morton, Len Brown,
	linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	Guenter Roeck, SangWook Ju, Durgadoss, Jean Delvare,
	Kyungmin Park, Kukjin Kim

On 五, 2012-08-17 at 11:56 +0300, Valentin, Eduardo wrote:
> Hello,

> >>> +
> >>> +
> >>> +1.2 CPU cooling action notifier register/unregister interface
> >>> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
> >>> +     unsigned int list)
> >>> +
> >>> +    This interface registers a driver with cpu cooling layer. The driver will
> >>> +    be notified when any cpu cooling action is called.
> >>> +
> >>> +    nb: notifier function to register
> >>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> >>> +
> >>> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
> >>> +     unsigned int list)
> >>> +
> >>> +    This interface registers a driver with cpu cooling layer. The driver will
> >>> +    be notified when any cpu cooling action is called.
> >>> +
> >>> +    nb: notifier function to register
> >>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
> >>
> >> what are these two APIs used for?
> >> I did not see they are used in your patch set, do I miss something?
> > No currently they are not used by my patches. I added them on request
> > from Eduardo and others
> 
> Yeah, this was a suggestion as we didn't really know how the FW part
> would evolve by that time.
> 
> The reasoning is to allow any interested user (in kernel) to be
> notified when max frequency changes.

in this case, the cooling device should be updated.
Say all the target of the thermal_instances of a cooling devices is 0,
which means they want the cpu to run at maximum frequency, when the max
frequency changes, we should set the processor to the new max frequency
as well.
Using notification is not a good idea as user can not handle this
without interacting with the thermal framework.

IMO, we should introduce a new API to handle this, rather than just
notifications to users.

>  Actually, the use case behind
> this is to allow such users to perform some handshaking or stop their
> activities or even change some paramenters, in case the max frequency
> would change.

It is the cooling device driver that notice this change first, and it
should invoke something like thermal_cooling_device_update/rebind() to
tell the thermal framework this change.

>  Ideally it would be possible to nack the cooling
> transition. But that is yet a wider discussion. So far we don't have
> users for this.
> 
yep.
I thought about this before, but I'd prefer to do this when there is a
real user. Or else, we are kind of over-designing here.
how about removing this piece of code for now?

thanks,
rui

> >>
> >>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> >>> index 7dd8c34..996003b 100644
> >>> --- a/drivers/thermal/Kconfig
> >>> +++ b/drivers/thermal/Kconfig
> >>> @@ -19,6 +19,17 @@ config THERMAL_HWMON
> >>>       depends on HWMON=y || HWMON=THERMAL
> >>>       default y
> >>>
> >>> +config CPU_THERMAL
> >>> +     bool "generic cpu cooling support"
> >>> +     depends on THERMAL && CPU_FREQ
> >>> +     help
> >>> +       This implements the generic cpu cooling mechanism through frequency
> >>> +       reduction, cpu hotplug and any other ways of reducing temperature. An
> >>> +       ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> >>> +       This will be useful for platforms using the generic thermal interface
> >>> +       and not the ACPI interface.
> >>> +       If you want this support, you should say Y here.
> >>> +
> >>>  config SPEAR_THERMAL
> >>>       bool "SPEAr thermal sensor driver"
> >>>       depends on THERMAL
> >>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> >>> index fd9369a..aae59ad 100644
> >>> --- a/drivers/thermal/Makefile
> >>> +++ b/drivers/thermal/Makefile
> >>> @@ -3,5 +3,6 @@
> >>>  #
> >>>
> >>>  obj-$(CONFIG_THERMAL)                += thermal_sys.o
> >>> +obj-$(CONFIG_CPU_THERMAL)            += cpu_cooling.o
> >>>  obj-$(CONFIG_SPEAR_THERMAL)          += spear_thermal.o
> >>>  obj-$(CONFIG_RCAR_THERMAL)   += rcar_thermal.o
> >>> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> >>> new file mode 100644
> >>> index 0000000..c42e557
> >>> --- /dev/null
> >>> +++ b/drivers/thermal/cpu_cooling.c
> >>> @@ -0,0 +1,512 @@
> >>> +/*
> >>> + *  linux/drivers/thermal/cpu_cooling.c
> >>> + *
> >>> + *  Copyright (C) 2012       Samsung Electronics Co., Ltd(http://www.samsung.com)
> >>> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
> >>> + *
> >>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >>> + *  This program is free software; you can redistribute it and/or modify
> >>> + *  it under the terms of the GNU General Public License as published by
> >>> + *  the Free Software Foundation; version 2 of the License.
> >>> + *
> >>> + *  This program is distributed in the hope that it will be useful, but
> >>> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
> >>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> >>> + *  General Public License for more details.
> >>> + *
> >>> + *  You should have received a copy of the GNU General Public License along
> >>> + *  with this program; if not, write to the Free Software Foundation, Inc.,
> >>> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> >>> + *
> >>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> >>> + */
> >>> +#include <linux/kernel.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/thermal.h>
> >>> +#include <linux/platform_device.h>
> >>> +#include <linux/cpufreq.h>
> >>> +#include <linux/err.h>
> >>> +#include <linux/slab.h>
> >>> +#include <linux/cpu.h>
> >>> +#include <linux/cpu_cooling.h>
> >>> +
> >>> +/**
> >>> + * struct cpufreq_cooling_device
> >>> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> >>> + *   registered.
> >>> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> >>> + *   egistered cooling device.
> >>> + * @cpufreq_state: integer value representing the current state of cpufreq
> >>> + *   cooling devices.
> >>> + * @cpufreq_val: integer value representing the absolute value of the clipped
> >>> + *   frequency.
> >>> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> >>> + * @node: list_head to link all cpufreq_cooling_device together.
> >>> + *
> >>> + * This structure is required for keeping information of each
> >>> + * cpufreq_cooling_device registered as a list whose head is represented by
> >>> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> >>> + * mutex lock cooling_cpufreq_lock is used.
> >>> + */
> >>> +struct cpufreq_cooling_device {
> >>> +     int id;
> >>> +     struct thermal_cooling_device *cool_dev;
> >>> +     unsigned int cpufreq_state;
> >>> +     unsigned int cpufreq_val;
> >>> +     struct cpumask allowed_cpus;
> >>> +     struct list_head node;
> >>> +};
> >>> +static LIST_HEAD(cooling_cpufreq_list);
> >>> +static DEFINE_IDR(cpufreq_idr);
> >>> +
> >>> +static struct mutex cooling_cpufreq_lock;
> >>> +
> >>> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> >>> +#define NOTIFY_INVALID NULL
> >>> +struct cpufreq_cooling_device *notify_device;
> >>> +
> >>> +/* Head of the blocking notifier chain to inform about frequency clamping */
> >>> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
> >>> +
> >>> +/**
> >>> + * get_idr - function to get a unique id.
> >>> + * @idr: struct idr * handle used to create a id.
> >>> + * @id: int * value generated by this function.
> >>> + */
> >>> +static int get_idr(struct idr *idr, int *id)
> >>> +{
> >>> +     int err;
> >>> +again:
> >>> +     if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> >>> +             return -ENOMEM;
> >>> +
> >>> +     mutex_lock(&cooling_cpufreq_lock);
> >>> +     err = idr_get_new(idr, NULL, id);
> >>> +     mutex_unlock(&cooling_cpufreq_lock);
> >>> +
> >>> +     if (unlikely(err == -EAGAIN))
> >>> +             goto again;
> >>> +     else if (unlikely(err))
> >>> +             return err;
> >>> +
> >>> +     *id = *id & MAX_ID_MASK;
> >>> +     return 0;
> >>> +}
> >>> +
> >>> +/**
> >>> + * release_idr - function to free the unique id.
> >>> + * @idr: struct idr * handle used for creating the id.
> >>> + * @id: int value representing the unique id.
> >>> + */
> >>> +static void release_idr(struct idr *idr, int id)
> >>> +{
> >>> +     mutex_lock(&cooling_cpufreq_lock);
> >>> +     idr_remove(idr, id);
> >>> +     mutex_unlock(&cooling_cpufreq_lock);
> >>> +}
> >>> +
> >>> +/**
> >>> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
> >>> + * @nb:      struct notifier_block * with callback info.
> >>> + * @list: integer value for which notification is needed. possible values are
> >>> + *   CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
> >>> + *
> >>> + * This exported function registers a driver with cpu cooling layer. The driver
> >>> + * will be notified when any cpu cooling action is called.
> >>> + */
> >>> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
> >>> +{
> >>> +     int ret = 0;
> >>> +
> >>> +     switch (list) {
> >>> +     case CPUFREQ_COOLING_START:
> >>> +     case CPUFREQ_COOLING_STOP:
> >>> +             ret = blocking_notifier_chain_register(
> >>> +                             &cputherm_state_notifier_list, nb);
> >>> +             break;
> >>> +     default:
> >>> +             ret = -EINVAL;
> >>> +     }
> >>> +     return ret;
> >>> +}
> >>> +EXPORT_SYMBOL(cputherm_register_notifier);
> >>> +
> >>> +/**
> >>> + * cputherm_unregister_notifier - Un-register a notifier.
> >>> + * @nb:      struct notifier_block * with callback info.
> >>> + * @list: integer value for which notification is needed. values possible are
> >>> + *   CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
> >>> + *
> >>> + * This exported function un-registers a driver with cpu cooling layer.
> >>> + */
> >>> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
> >>> +{
> >>> +     int ret = 0;
> >>> +
> >>> +     switch (list) {
> >>> +     case CPUFREQ_COOLING_START:
> >>> +     case CPUFREQ_COOLING_STOP:
> >>> +             ret = blocking_notifier_chain_unregister(
> >>> +                             &cputherm_state_notifier_list, nb);
> >>> +             break;
> >>> +     default:
> >>> +             ret = -EINVAL;
> >>> +     }
> >>> +     return ret;
> >>> +}
> >>> +EXPORT_SYMBOL(cputherm_unregister_notifier);
> >>> +
> >>> +/* Below code defines functions to be used for cpufreq as cooling device */
> >>> +
> >>> +/**
> >>> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> >>> + * @cpu: cpu for which check is needed.
> >>> + */
> >>> +static int is_cpufreq_valid(int cpu)
> >>> +{
> >>> +     struct cpufreq_policy policy;
> >>> +     return !cpufreq_get_policy(&policy, 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
> >>> + */
> >>> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
> >>> +{
> >>> +     int ret = 0, i = 0;
> >>> +     unsigned long level_index;
> >>> +     bool descend = false;
> >>> +     struct cpufreq_frequency_table *table =
> >>> +                                     cpufreq_frequency_get_table(cpu);
> >>> +     if (!table)
> >>> +             return ret;
> >>> +
> >>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> >>> +                     continue;
> >>> +
> >>> +             /*check if table in ascending or descending order*/
> >>> +             if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> >>> +                     (table[i + 1].frequency < table[i].frequency)
> >>> +                     && !descend) {
> >>> +                     descend = true;
> >>> +             }
> >>> +
> >>> +             /*return if level matched and table in descending order*/
> >>> +             if (descend && i == level)
> >>> +                     return table[i].frequency;
> >>> +             i++;
> >>> +     }
> >>> +     i--;
> >>> +
> >>> +     if (level > i || descend)
> >>> +             return ret;
> >>> +     level_index = i - level;
> >>> +
> >>> +     /*Scan the table in reverse order and match the level*/
> >>> +     while (i >= 0) {
> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> >>> +                     continue;
> >>> +             /*return if level matched*/
> >>> +             if (i == level_index)
> >>> +                     return table[i].frequency;
> >>> +             i--;
> >>> +     }
> >>> +     return ret;
> >>> +}
> >>> +
> >>> +/**
> >>> + * cpufreq_apply_cooling - function to apply frequency clipping.
> >>> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> >>> + *   clipping data.
> >>> + * @cooling_state: value of the cooling state.
> >>> + */
> >>> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> >>> +                             unsigned long cooling_state)
> >>> +{
> >>> +     unsigned int event, cpuid, clip_freq;
> >>> +     struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> >>> +     unsigned int cpu = cpumask_any(maskPtr);
> >>> +
> >>> +
> >>> +     /* Check if the old cooling action is same as new cooling action */
> >>> +     if (cpufreq_device->cpufreq_state == cooling_state)
> >>> +             return 0;
> >>> +
> >>> +     clip_freq = get_cpu_frequency(cpu, cooling_state);
> >>> +     if (!clip_freq)
> >>> +             return -EINVAL;
> >>> +
> >>> +     cpufreq_device->cpufreq_state = cooling_state;
> >>> +     cpufreq_device->cpufreq_val = clip_freq;
> >>> +     notify_device = cpufreq_device;
> >>> +
> >>> +     if (cooling_state != 0)
> >>> +             event = CPUFREQ_COOLING_START;
> >>> +     else
> >>> +             event = CPUFREQ_COOLING_STOP;
> >>> +
> >>> +     blocking_notifier_call_chain(&cputherm_state_notifier_list,
> >>> +                                             event, &clip_freq);
> >>> +
> >>> +     for_each_cpu(cpuid, maskPtr) {
> >>> +             if (is_cpufreq_valid(cpuid))
> >>> +                     cpufreq_update_policy(cpuid);
> >>> +     }
> >>> +
> >>> +     notify_device = NOTIFY_INVALID;
> >>> +
> >>> +     return 0;
> >>> +}
> >>> +
> >>> +/**
> >>> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> >>> + * @nb:      struct notifier_block * with callback info.
> >>> + * @event: value showing cpufreq event for which this function invoked.
> >>> + * @data: callback-specific data
> >>> + */
> >>> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> >>> +                                     unsigned long event, void *data)
> >>> +{
> >>> +     struct cpufreq_policy *policy = data;
> >>> +     unsigned long max_freq = 0;
> >>> +
> >>> +     if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> >>> +             return 0;
> >>> +
> >>> +     if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
> >>> +             max_freq = notify_device->cpufreq_val;
> >>> +
> >>> +     /* Never exceed user_policy.max*/
> >>> +     if (max_freq > policy->user_policy.max)
> >>> +             max_freq = policy->user_policy.max;
> >>> +
> >>> +     if (policy->max != max_freq)
> >>> +             cpufreq_verify_within_limits(policy, 0, max_freq);
> >>> +
> >>> +     return 0;
> >>> +}
> >>> +
> >>> +/*
> >>> + * cpufreq cooling device callback functions are defined below
> >>> + */
> >>> +
> >>> +/**
> >>> + * cpufreq_get_max_state - callback function to get the max cooling state.
> >>> + * @cdev: thermal cooling device pointer.
> >>> + * @state: fill this variable with the max cooling state.
> >>> + */
> >>> +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;
> >>> +     unsigned int cpu;
> >>> +     struct cpufreq_frequency_table *table;
> >>> +
> >>> +     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;
> >>> +     }
> >>> +
> >>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> >>> +                     continue;
> >>> +             i++;
> >>> +     }
> >>> +     if (i > 0) {
> >>> +             *state = --i;
> >>> +             ret = 0;
> >>> +     }
> >>> +
> >>> +return_get_max_state:
> >>> +     mutex_unlock(&cooling_cpufreq_lock);
> >>> +     return ret;
> >>> +}
> >>> +
> >>> +/**
> >>> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> >>> + * @cdev: thermal cooling device pointer.
> >>> + * @state: fill this variable with the current cooling 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);
> >>> +
> >>
> >> as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
> >> use the current cpu frequency to get the REAL cooling state, rather than
> >> using a cached value.
> >
> > Yes agreed , I will repost with your suggestion.
> >
> > Thanks,
> > Amit
> >>
> >> thanks,
> >> rui
> >>
> >>
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 
> 



^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [lm-sensors] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-20  2:16           ` Zhang Rui
  0 siblings, 0 replies; 50+ messages in thread
From: Zhang Rui @ 2012-08-20  2:16 UTC (permalink / raw)
  To: Valentin, Eduardo
  Cc: Amit Kachhap, linux-pm, Andrew Morton, Len Brown,
	linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	Guenter Roeck, SangWook Ju, Durgadoss, Jean Delvare,
	Kyungmin Park, Kukjin Kim

T24g5LqULCAyMDEyLTA4LTE3IGF0IDExOjU2ICswMzAwLCBWYWxlbnRpbiwgRWR1YXJkbyB3cm90
ZToKPiBIZWxsbywKCj4gPj4+ICsKPiA+Pj4gKwo+ID4+PiArMS4yIENQVSBjb29saW5nIGFjdGlv
biBub3RpZmllciByZWdpc3Rlci91bnJlZ2lzdGVyIGludGVyZmFjZQo+ID4+PiArMS4yLjEgaW50
IGNwdXRoZXJtX3JlZ2lzdGVyX25vdGlmaWVyKHN0cnVjdCBub3RpZmllcl9ibG9jayAqbmIsCj4g
Pj4+ICsgICAgIHVuc2lnbmVkIGludCBsaXN0KQo+ID4+PiArCj4gPj4+ICsgICAgVGhpcyBpbnRl
cmZhY2UgcmVnaXN0ZXJzIGEgZHJpdmVyIHdpdGggY3B1IGNvb2xpbmcgbGF5ZXIuIFRoZSBkcml2
ZXIgd2lsbAo+ID4+PiArICAgIGJlIG5vdGlmaWVkIHdoZW4gYW55IGNwdSBjb29saW5nIGFjdGlv
biBpcyBjYWxsZWQuCj4gPj4+ICsKPiA+Pj4gKyAgICBuYjogbm90aWZpZXIgZnVuY3Rpb24gdG8g
cmVnaXN0ZXIKPiA+Pj4gKyAgICBsaXN0OiBDUFVGUkVRX0NPT0xJTkdfU1RBUlQgb3IgQ1BVRlJF
UV9DT09MSU5HX1NUT1AKPiA+Pj4gKwo+ID4+PiArMS4yLjIgaW50IGNwdXRoZXJtX3VucmVnaXN0
ZXJfbm90aWZpZXIoc3RydWN0IG5vdGlmaWVyX2Jsb2NrICpuYiwKPiA+Pj4gKyAgICAgdW5zaWdu
ZWQgaW50IGxpc3QpCj4gPj4+ICsKPiA+Pj4gKyAgICBUaGlzIGludGVyZmFjZSByZWdpc3RlcnMg
YSBkcml2ZXIgd2l0aCBjcHUgY29vbGluZyBsYXllci4gVGhlIGRyaXZlciB3aWxsCj4gPj4+ICsg
ICAgYmUgbm90aWZpZWQgd2hlbiBhbnkgY3B1IGNvb2xpbmcgYWN0aW9uIGlzIGNhbGxlZC4KPiA+
Pj4gKwo+ID4+PiArICAgIG5iOiBub3RpZmllciBmdW5jdGlvbiB0byByZWdpc3Rlcgo+ID4+PiAr
ICAgIGxpc3Q6IENQVUZSRVFfQ09PTElOR19TVEFSVCBvciBDUFVGUkVRX0NPT0xJTkdfU1RPUAo+
ID4+Cj4gPj4gd2hhdCBhcmUgdGhlc2UgdHdvIEFQSXMgdXNlZCBmb3I/Cj4gPj4gSSBkaWQgbm90
IHNlZSB0aGV5IGFyZSB1c2VkIGluIHlvdXIgcGF0Y2ggc2V0LCBkbyBJIG1pc3Mgc29tZXRoaW5n
Pwo+ID4gTm8gY3VycmVudGx5IHRoZXkgYXJlIG5vdCB1c2VkIGJ5IG15IHBhdGNoZXMuIEkgYWRk
ZWQgdGhlbSBvbiByZXF1ZXN0Cj4gPiBmcm9tIEVkdWFyZG8gYW5kIG90aGVycwo+IAo+IFllYWgs
IHRoaXMgd2FzIGEgc3VnZ2VzdGlvbiBhcyB3ZSBkaWRuJ3QgcmVhbGx5IGtub3cgaG93IHRoZSBG
VyBwYXJ0Cj4gd291bGQgZXZvbHZlIGJ5IHRoYXQgdGltZS4KPiAKPiBUaGUgcmVhc29uaW5nIGlz
IHRvIGFsbG93IGFueSBpbnRlcmVzdGVkIHVzZXIgKGluIGtlcm5lbCkgdG8gYmUKPiBub3RpZmll
ZCB3aGVuIG1heCBmcmVxdWVuY3kgY2hhbmdlcy4KCmluIHRoaXMgY2FzZSwgdGhlIGNvb2xpbmcg
ZGV2aWNlIHNob3VsZCBiZSB1cGRhdGVkLgpTYXkgYWxsIHRoZSB0YXJnZXQgb2YgdGhlIHRoZXJt
YWxfaW5zdGFuY2VzIG9mIGEgY29vbGluZyBkZXZpY2VzIGlzIDAsCndoaWNoIG1lYW5zIHRoZXkg
d2FudCB0aGUgY3B1IHRvIHJ1biBhdCBtYXhpbXVtIGZyZXF1ZW5jeSwgd2hlbiB0aGUgbWF4CmZy
ZXF1ZW5jeSBjaGFuZ2VzLCB3ZSBzaG91bGQgc2V0IHRoZSBwcm9jZXNzb3IgdG8gdGhlIG5ldyBt
YXggZnJlcXVlbmN5CmFzIHdlbGwuClVzaW5nIG5vdGlmaWNhdGlvbiBpcyBub3QgYSBnb29kIGlk
ZWEgYXMgdXNlciBjYW4gbm90IGhhbmRsZSB0aGlzCndpdGhvdXQgaW50ZXJhY3Rpbmcgd2l0aCB0
aGUgdGhlcm1hbCBmcmFtZXdvcmsuCgpJTU8sIHdlIHNob3VsZCBpbnRyb2R1Y2UgYSBuZXcgQVBJ
IHRvIGhhbmRsZSB0aGlzLCByYXRoZXIgdGhhbiBqdXN0Cm5vdGlmaWNhdGlvbnMgdG8gdXNlcnMu
Cgo+ICBBY3R1YWxseSwgdGhlIHVzZSBjYXNlIGJlaGluZAo+IHRoaXMgaXMgdG8gYWxsb3cgc3Vj
aCB1c2VycyB0byBwZXJmb3JtIHNvbWUgaGFuZHNoYWtpbmcgb3Igc3RvcCB0aGVpcgo+IGFjdGl2
aXRpZXMgb3IgZXZlbiBjaGFuZ2Ugc29tZSBwYXJhbWVudGVycywgaW4gY2FzZSB0aGUgbWF4IGZy
ZXF1ZW5jeQo+IHdvdWxkIGNoYW5nZS4KCkl0IGlzIHRoZSBjb29saW5nIGRldmljZSBkcml2ZXIg
dGhhdCBub3RpY2UgdGhpcyBjaGFuZ2UgZmlyc3QsIGFuZCBpdApzaG91bGQgaW52b2tlIHNvbWV0
aGluZyBsaWtlIHRoZXJtYWxfY29vbGluZ19kZXZpY2VfdXBkYXRlL3JlYmluZCgpIHRvCnRlbGwg
dGhlIHRoZXJtYWwgZnJhbWV3b3JrIHRoaXMgY2hhbmdlLgoKPiAgSWRlYWxseSBpdCB3b3VsZCBi
ZSBwb3NzaWJsZSB0byBuYWNrIHRoZSBjb29saW5nCj4gdHJhbnNpdGlvbi4gQnV0IHRoYXQgaXMg
eWV0IGEgd2lkZXIgZGlzY3Vzc2lvbi4gU28gZmFyIHdlIGRvbid0IGhhdmUKPiB1c2VycyBmb3Ig
dGhpcy4KPiAKeWVwLgpJIHRob3VnaHQgYWJvdXQgdGhpcyBiZWZvcmUsIGJ1dCBJJ2QgcHJlZmVy
IHRvIGRvIHRoaXMgd2hlbiB0aGVyZSBpcyBhCnJlYWwgdXNlci4gT3IgZWxzZSwgd2UgYXJlIGtp
bmQgb2Ygb3Zlci1kZXNpZ25pbmcgaGVyZS4KaG93IGFib3V0IHJlbW92aW5nIHRoaXMgcGllY2Ug
b2YgY29kZSBmb3Igbm93PwoKdGhhbmtzLApydWkKCj4gPj4KPiA+Pj4gZGlmZiAtLWdpdCBhL2Ry
aXZlcnMvdGhlcm1hbC9LY29uZmlnIGIvZHJpdmVycy90aGVybWFsL0tjb25maWcKPiA+Pj4gaW5k
ZXggN2RkOGMzNC4uOTk2MDAzYiAxMDA2NDQKPiA+Pj4gLS0tIGEvZHJpdmVycy90aGVybWFsL0tj
b25maWcKPiA+Pj4gKysrIGIvZHJpdmVycy90aGVybWFsL0tjb25maWcKPiA+Pj4gQEAgLTE5LDYg
KzE5LDE3IEBAIGNvbmZpZyBUSEVSTUFMX0hXTU9OCj4gPj4+ICAgICAgIGRlcGVuZHMgb24gSFdN
T049eSB8fCBIV01PTj1USEVSTUFMCj4gPj4+ICAgICAgIGRlZmF1bHQgeQo+ID4+Pgo+ID4+PiAr
Y29uZmlnIENQVV9USEVSTUFMCj4gPj4+ICsgICAgIGJvb2wgImdlbmVyaWMgY3B1IGNvb2xpbmcg
c3VwcG9ydCIKPiA+Pj4gKyAgICAgZGVwZW5kcyBvbiBUSEVSTUFMICYmIENQVV9GUkVRCj4gPj4+
ICsgICAgIGhlbHAKPiA+Pj4gKyAgICAgICBUaGlzIGltcGxlbWVudHMgdGhlIGdlbmVyaWMgY3B1
IGNvb2xpbmcgbWVjaGFuaXNtIHRocm91Z2ggZnJlcXVlbmN5Cj4gPj4+ICsgICAgICAgcmVkdWN0
aW9uLCBjcHUgaG90cGx1ZyBhbmQgYW55IG90aGVyIHdheXMgb2YgcmVkdWNpbmcgdGVtcGVyYXR1
cmUuIEFuCj4gPj4+ICsgICAgICAgQUNQSSB2ZXJzaW9uIG9mIHRoaXMgYWxyZWFkeSBleGlzdHMo
ZHJpdmVycy9hY3BpL3Byb2Nlc3Nvcl90aGVybWFsLmMpLgo+ID4+PiArICAgICAgIFRoaXMgd2ls
bCBiZSB1c2VmdWwgZm9yIHBsYXRmb3JtcyB1c2luZyB0aGUgZ2VuZXJpYyB0aGVybWFsIGludGVy
ZmFjZQo+ID4+PiArICAgICAgIGFuZCBub3QgdGhlIEFDUEkgaW50ZXJmYWNlLgo+ID4+PiArICAg
ICAgIElmIHlvdSB3YW50IHRoaXMgc3VwcG9ydCwgeW91IHNob3VsZCBzYXkgWSBoZXJlLgo+ID4+
PiArCj4gPj4+ICBjb25maWcgU1BFQVJfVEhFUk1BTAo+ID4+PiAgICAgICBib29sICJTUEVBciB0
aGVybWFsIHNlbnNvciBkcml2ZXIiCj4gPj4+ICAgICAgIGRlcGVuZHMgb24gVEhFUk1BTAo+ID4+
PiBkaWZmIC0tZ2l0IGEvZHJpdmVycy90aGVybWFsL01ha2VmaWxlIGIvZHJpdmVycy90aGVybWFs
L01ha2VmaWxlCj4gPj4+IGluZGV4IGZkOTM2OWEuLmFhZTU5YWQgMTAwNjQ0Cj4gPj4+IC0tLSBh
L2RyaXZlcnMvdGhlcm1hbC9NYWtlZmlsZQo+ID4+PiArKysgYi9kcml2ZXJzL3RoZXJtYWwvTWFr
ZWZpbGUKPiA+Pj4gQEAgLTMsNSArMyw2IEBACj4gPj4+ICAjCj4gPj4+Cj4gPj4+ICBvYmotJChD
T05GSUdfVEhFUk1BTCkgICAgICAgICAgICAgICAgKz0gdGhlcm1hbF9zeXMubwo+ID4+PiArb2Jq
LSQoQ09ORklHX0NQVV9USEVSTUFMKSAgICAgICAgICAgICs9IGNwdV9jb29saW5nLm8KPiA+Pj4g
IG9iai0kKENPTkZJR19TUEVBUl9USEVSTUFMKSAgICAgICAgICArPSBzcGVhcl90aGVybWFsLm8K
PiA+Pj4gIG9iai0kKENPTkZJR19SQ0FSX1RIRVJNQUwpICAgKz0gcmNhcl90aGVybWFsLm8KPiA+
Pj4gZGlmZiAtLWdpdCBhL2RyaXZlcnMvdGhlcm1hbC9jcHVfY29vbGluZy5jIGIvZHJpdmVycy90
aGVybWFsL2NwdV9jb29saW5nLmMKPiA+Pj4gbmV3IGZpbGUgbW9kZSAxMDA2NDQKPiA+Pj4gaW5k
ZXggMDAwMDAwMC4uYzQyZTU1Nwo+ID4+PiAtLS0gL2Rldi9udWxsCj4gPj4+ICsrKyBiL2RyaXZl
cnMvdGhlcm1hbC9jcHVfY29vbGluZy5jCj4gPj4+IEBAIC0wLDAgKzEsNTEyIEBACj4gPj4+ICsv
Kgo+ID4+PiArICogIGxpbnV4L2RyaXZlcnMvdGhlcm1hbC9jcHVfY29vbGluZy5jCj4gPj4+ICsg
Kgo+ID4+PiArICogIENvcHlyaWdodCAoQykgMjAxMiAgICAgICBTYW1zdW5nIEVsZWN0cm9uaWNz
IENvLiwgTHRkKGh0dHA6Ly93d3cuc2Ftc3VuZy5jb20pCj4gPj4+ICsgKiAgQ29weXJpZ2h0IChD
KSAyMDEyICBBbWl0IERhbmllbCA8YW1pdC5rYWNoaGFwQGxpbmFyby5vcmc+Cj4gPj4+ICsgKgo+
ID4+PiArICogfn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn4KPiA+Pj4gKyAqICBUaGlzIHByb2dyYW0gaXMgZnJl
ZSBzb2Z0d2FyZTsgeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeQo+ID4+PiAr
ICogIGl0IHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2Ug
YXMgcHVibGlzaGVkIGJ5Cj4gPj4+ICsgKiAgdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbjsg
dmVyc2lvbiAyIG9mIHRoZSBMaWNlbnNlLgo+ID4+PiArICoKPiA+Pj4gKyAqICBUaGlzIHByb2dy
YW0gaXMgZGlzdHJpYnV0ZWQgaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwgYnV0
Cj4gPj4+ICsgKiAgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGll
ZCB3YXJyYW50eSBvZgo+ID4+PiArICogIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBB
IFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUgR05VCj4gPj4+ICsgKiAgR2VuZXJhbCBQdWJs
aWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgo+ID4+PiArICoKPiA+Pj4gKyAqICBZb3Ugc2hv
dWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5z
ZSBhbG9uZwo+ID4+PiArICogIHdpdGggdGhpcyBwcm9ncmFtOyBpZiBub3QsIHdyaXRlIHRvIHRo
ZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb24sIEluYy4sCj4gPj4+ICsgKiAgNTkgVGVtcGxlIFBs
YWNlLCBTdWl0ZSAzMzAsIEJvc3RvbiwgTUEgMDIxMTEtMTMwNyBVU0EuCj4gPj4+ICsgKgo+ID4+
PiArICogfn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn4KPiA+Pj4gKyAqLwo+ID4+PiArI2luY2x1ZGUgPGxpbnV4
L2tlcm5lbC5oPgo+ID4+PiArI2luY2x1ZGUgPGxpbnV4L21vZHVsZS5oPgo+ID4+PiArI2luY2x1
ZGUgPGxpbnV4L3RoZXJtYWwuaD4KPiA+Pj4gKyNpbmNsdWRlIDxsaW51eC9wbGF0Zm9ybV9kZXZp
Y2UuaD4KPiA+Pj4gKyNpbmNsdWRlIDxsaW51eC9jcHVmcmVxLmg+Cj4gPj4+ICsjaW5jbHVkZSA8
bGludXgvZXJyLmg+Cj4gPj4+ICsjaW5jbHVkZSA8bGludXgvc2xhYi5oPgo+ID4+PiArI2luY2x1
ZGUgPGxpbnV4L2NwdS5oPgo+ID4+PiArI2luY2x1ZGUgPGxpbnV4L2NwdV9jb29saW5nLmg+Cj4g
Pj4+ICsKPiA+Pj4gKy8qKgo+ID4+PiArICogc3RydWN0IGNwdWZyZXFfY29vbGluZ19kZXZpY2UK
PiA+Pj4gKyAqIEBpZDogdW5pcXVlIGludGVnZXIgdmFsdWUgY29ycmVzcG9uZGluZyB0byBlYWNo
IGNwdWZyZXFfY29vbGluZ19kZXZpY2UKPiA+Pj4gKyAqICAgcmVnaXN0ZXJlZC4KPiA+Pj4gKyAq
IEBjb29sX2RldjogdGhlcm1hbF9jb29saW5nX2RldmljZSBwb2ludGVyIHRvIGtlZXAgdHJhY2sg
b2YgdGhlIHRoZQo+ID4+PiArICogICBlZ2lzdGVyZWQgY29vbGluZyBkZXZpY2UuCj4gPj4+ICsg
KiBAY3B1ZnJlcV9zdGF0ZTogaW50ZWdlciB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIGN1cnJlbnQg
c3RhdGUgb2YgY3B1ZnJlcQo+ID4+PiArICogICBjb29saW5nIGRldmljZXMuCj4gPj4+ICsgKiBA
Y3B1ZnJlcV92YWw6IGludGVnZXIgdmFsdWUgcmVwcmVzZW50aW5nIHRoZSBhYnNvbHV0ZSB2YWx1
ZSBvZiB0aGUgY2xpcHBlZAo+ID4+PiArICogICBmcmVxdWVuY3kuCj4gPj4+ICsgKiBAYWxsb3dl
ZF9jcHVzOiBhbGwgdGhlIGNwdXMgaW52b2x2ZWQgZm9yIHRoaXMgY3B1ZnJlcV9jb29saW5nX2Rl
dmljZS4KPiA+Pj4gKyAqIEBub2RlOiBsaXN0X2hlYWQgdG8gbGluayBhbGwgY3B1ZnJlcV9jb29s
aW5nX2RldmljZSB0b2dldGhlci4KPiA+Pj4gKyAqCj4gPj4+ICsgKiBUaGlzIHN0cnVjdHVyZSBp
cyByZXF1aXJlZCBmb3Iga2VlcGluZyBpbmZvcm1hdGlvbiBvZiBlYWNoCj4gPj4+ICsgKiBjcHVm
cmVxX2Nvb2xpbmdfZGV2aWNlIHJlZ2lzdGVyZWQgYXMgYSBsaXN0IHdob3NlIGhlYWQgaXMgcmVw
cmVzZW50ZWQgYnkKPiA+Pj4gKyAqIGNvb2xpbmdfY3B1ZnJlcV9saXN0LiBJbiBvcmRlciB0byBw
cmV2ZW50IGNvcnJ1cHRpb24gb2YgdGhpcyBsaXN0IGEKPiA+Pj4gKyAqIG11dGV4IGxvY2sgY29v
bGluZ19jcHVmcmVxX2xvY2sgaXMgdXNlZC4KPiA+Pj4gKyAqLwo+ID4+PiArc3RydWN0IGNwdWZy
ZXFfY29vbGluZ19kZXZpY2Ugewo+ID4+PiArICAgICBpbnQgaWQ7Cj4gPj4+ICsgICAgIHN0cnVj
dCB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjb29sX2RldjsKPiA+Pj4gKyAgICAgdW5zaWduZWQg
aW50IGNwdWZyZXFfc3RhdGU7Cj4gPj4+ICsgICAgIHVuc2lnbmVkIGludCBjcHVmcmVxX3ZhbDsK
PiA+Pj4gKyAgICAgc3RydWN0IGNwdW1hc2sgYWxsb3dlZF9jcHVzOwo+ID4+PiArICAgICBzdHJ1
Y3QgbGlzdF9oZWFkIG5vZGU7Cj4gPj4+ICt9Owo+ID4+PiArc3RhdGljIExJU1RfSEVBRChjb29s
aW5nX2NwdWZyZXFfbGlzdCk7Cj4gPj4+ICtzdGF0aWMgREVGSU5FX0lEUihjcHVmcmVxX2lkcik7
Cj4gPj4+ICsKPiA+Pj4gK3N0YXRpYyBzdHJ1Y3QgbXV0ZXggY29vbGluZ19jcHVmcmVxX2xvY2s7
Cj4gPj4+ICsKPiA+Pj4gKy8qIG5vdGlmeV90YWJsZSBwYXNzZXMgdmFsdWUgdG8gdGhlIENQVUZS
RVFfQURKVVNUIGNhbGxiYWNrIGZ1bmN0aW9uLiAqLwo+ID4+PiArI2RlZmluZSBOT1RJRllfSU5W
QUxJRCBOVUxMCj4gPj4+ICtzdHJ1Y3QgY3B1ZnJlcV9jb29saW5nX2RldmljZSAqbm90aWZ5X2Rl
dmljZTsKPiA+Pj4gKwo+ID4+PiArLyogSGVhZCBvZiB0aGUgYmxvY2tpbmcgbm90aWZpZXIgY2hh
aW4gdG8gaW5mb3JtIGFib3V0IGZyZXF1ZW5jeSBjbGFtcGluZyAqLwo+ID4+PiArc3RhdGljIEJM
T0NLSU5HX05PVElGSUVSX0hFQUQoY3B1dGhlcm1fc3RhdGVfbm90aWZpZXJfbGlzdCk7Cj4gPj4+
ICsKPiA+Pj4gKy8qKgo+ID4+PiArICogZ2V0X2lkciAtIGZ1bmN0aW9uIHRvIGdldCBhIHVuaXF1
ZSBpZC4KPiA+Pj4gKyAqIEBpZHI6IHN0cnVjdCBpZHIgKiBoYW5kbGUgdXNlZCB0byBjcmVhdGUg
YSBpZC4KPiA+Pj4gKyAqIEBpZDogaW50ICogdmFsdWUgZ2VuZXJhdGVkIGJ5IHRoaXMgZnVuY3Rp
b24uCj4gPj4+ICsgKi8KPiA+Pj4gK3N0YXRpYyBpbnQgZ2V0X2lkcihzdHJ1Y3QgaWRyICppZHIs
IGludCAqaWQpCj4gPj4+ICt7Cj4gPj4+ICsgICAgIGludCBlcnI7Cj4gPj4+ICthZ2FpbjoKPiA+
Pj4gKyAgICAgaWYgKHVubGlrZWx5KGlkcl9wcmVfZ2V0KGlkciwgR0ZQX0tFUk5FTCkgPT0gMCkp
Cj4gPj4+ICsgICAgICAgICAgICAgcmV0dXJuIC1FTk9NRU07Cj4gPj4+ICsKPiA+Pj4gKyAgICAg
bXV0ZXhfbG9jaygmY29vbGluZ19jcHVmcmVxX2xvY2spOwo+ID4+PiArICAgICBlcnIgPSBpZHJf
Z2V0X25ldyhpZHIsIE5VTEwsIGlkKTsKPiA+Pj4gKyAgICAgbXV0ZXhfdW5sb2NrKCZjb29saW5n
X2NwdWZyZXFfbG9jayk7Cj4gPj4+ICsKPiA+Pj4gKyAgICAgaWYgKHVubGlrZWx5KGVyciA9PSAt
RUFHQUlOKSkKPiA+Pj4gKyAgICAgICAgICAgICBnb3RvIGFnYWluOwo+ID4+PiArICAgICBlbHNl
IGlmICh1bmxpa2VseShlcnIpKQo+ID4+PiArICAgICAgICAgICAgIHJldHVybiBlcnI7Cj4gPj4+
ICsKPiA+Pj4gKyAgICAgKmlkID0gKmlkICYgTUFYX0lEX01BU0s7Cj4gPj4+ICsgICAgIHJldHVy
biAwOwo+ID4+PiArfQo+ID4+PiArCj4gPj4+ICsvKioKPiA+Pj4gKyAqIHJlbGVhc2VfaWRyIC0g
ZnVuY3Rpb24gdG8gZnJlZSB0aGUgdW5pcXVlIGlkLgo+ID4+PiArICogQGlkcjogc3RydWN0IGlk
ciAqIGhhbmRsZSB1c2VkIGZvciBjcmVhdGluZyB0aGUgaWQuCj4gPj4+ICsgKiBAaWQ6IGludCB2
YWx1ZSByZXByZXNlbnRpbmcgdGhlIHVuaXF1ZSBpZC4KPiA+Pj4gKyAqLwo+ID4+PiArc3RhdGlj
IHZvaWQgcmVsZWFzZV9pZHIoc3RydWN0IGlkciAqaWRyLCBpbnQgaWQpCj4gPj4+ICt7Cj4gPj4+
ICsgICAgIG11dGV4X2xvY2soJmNvb2xpbmdfY3B1ZnJlcV9sb2NrKTsKPiA+Pj4gKyAgICAgaWRy
X3JlbW92ZShpZHIsIGlkKTsKPiA+Pj4gKyAgICAgbXV0ZXhfdW5sb2NrKCZjb29saW5nX2NwdWZy
ZXFfbG9jayk7Cj4gPj4+ICt9Cj4gPj4+ICsKPiA+Pj4gKy8qKgo+ID4+PiArICogY3B1dGhlcm1f
cmVnaXN0ZXJfbm90aWZpZXIgLSBSZWdpc3RlciBhIG5vdGlmaWVyIHdpdGggY3B1IGNvb2xpbmcg
aW50ZXJmYWNlLgo+ID4+PiArICogQG5iOiAgICAgIHN0cnVjdCBub3RpZmllcl9ibG9jayAqIHdp
dGggY2FsbGJhY2sgaW5mby4KPiA+Pj4gKyAqIEBsaXN0OiBpbnRlZ2VyIHZhbHVlIGZvciB3aGlj
aCBub3RpZmljYXRpb24gaXMgbmVlZGVkLiBwb3NzaWJsZSB2YWx1ZXMgYXJlCj4gPj4+ICsgKiAg
IENQVUZSRVFfQ09PTElOR19TVEFSVCBhbmQgQ1BVRlJFUV9DT09MSU5HX1NUT1AuCj4gPj4+ICsg
Kgo+ID4+PiArICogVGhpcyBleHBvcnRlZCBmdW5jdGlvbiByZWdpc3RlcnMgYSBkcml2ZXIgd2l0
aCBjcHUgY29vbGluZyBsYXllci4gVGhlIGRyaXZlcgo+ID4+PiArICogd2lsbCBiZSBub3RpZmll
ZCB3aGVuIGFueSBjcHUgY29vbGluZyBhY3Rpb24gaXMgY2FsbGVkLgo+ID4+PiArICovCj4gPj4+
ICtpbnQgY3B1dGhlcm1fcmVnaXN0ZXJfbm90aWZpZXIoc3RydWN0IG5vdGlmaWVyX2Jsb2NrICpu
YiwgdW5zaWduZWQgaW50IGxpc3QpCj4gPj4+ICt7Cj4gPj4+ICsgICAgIGludCByZXQgPSAwOwo+
ID4+PiArCj4gPj4+ICsgICAgIHN3aXRjaCAobGlzdCkgewo+ID4+PiArICAgICBjYXNlIENQVUZS
RVFfQ09PTElOR19TVEFSVDoKPiA+Pj4gKyAgICAgY2FzZSBDUFVGUkVRX0NPT0xJTkdfU1RPUDoK
PiA+Pj4gKyAgICAgICAgICAgICByZXQgPSBibG9ja2luZ19ub3RpZmllcl9jaGFpbl9yZWdpc3Rl
cigKPiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJmNwdXRoZXJtX3N0YXRlX25v
dGlmaWVyX2xpc3QsIG5iKTsKPiA+Pj4gKyAgICAgICAgICAgICBicmVhazsKPiA+Pj4gKyAgICAg
ZGVmYXVsdDoKPiA+Pj4gKyAgICAgICAgICAgICByZXQgPSAtRUlOVkFMOwo+ID4+PiArICAgICB9
Cj4gPj4+ICsgICAgIHJldHVybiByZXQ7Cj4gPj4+ICt9Cj4gPj4+ICtFWFBPUlRfU1lNQk9MKGNw
dXRoZXJtX3JlZ2lzdGVyX25vdGlmaWVyKTsKPiA+Pj4gKwo+ID4+PiArLyoqCj4gPj4+ICsgKiBj
cHV0aGVybV91bnJlZ2lzdGVyX25vdGlmaWVyIC0gVW4tcmVnaXN0ZXIgYSBub3RpZmllci4KPiA+
Pj4gKyAqIEBuYjogICAgICBzdHJ1Y3Qgbm90aWZpZXJfYmxvY2sgKiB3aXRoIGNhbGxiYWNrIGlu
Zm8uCj4gPj4+ICsgKiBAbGlzdDogaW50ZWdlciB2YWx1ZSBmb3Igd2hpY2ggbm90aWZpY2F0aW9u
IGlzIG5lZWRlZC4gdmFsdWVzIHBvc3NpYmxlIGFyZQo+ID4+PiArICogICBDUFVGUkVRX0NPT0xJ
TkdfU1RBUlQgb3IgQ1BVRlJFUV9DT09MSU5HX1NUT1AuCj4gPj4+ICsgKgo+ID4+PiArICogVGhp
cyBleHBvcnRlZCBmdW5jdGlvbiB1bi1yZWdpc3RlcnMgYSBkcml2ZXIgd2l0aCBjcHUgY29vbGlu
ZyBsYXllci4KPiA+Pj4gKyAqLwo+ID4+PiAraW50IGNwdXRoZXJtX3VucmVnaXN0ZXJfbm90aWZp
ZXIoc3RydWN0IG5vdGlmaWVyX2Jsb2NrICpuYiwgdW5zaWduZWQgaW50IGxpc3QpCj4gPj4+ICt7
Cj4gPj4+ICsgICAgIGludCByZXQgPSAwOwo+ID4+PiArCj4gPj4+ICsgICAgIHN3aXRjaCAobGlz
dCkgewo+ID4+PiArICAgICBjYXNlIENQVUZSRVFfQ09PTElOR19TVEFSVDoKPiA+Pj4gKyAgICAg
Y2FzZSBDUFVGUkVRX0NPT0xJTkdfU1RPUDoKPiA+Pj4gKyAgICAgICAgICAgICByZXQgPSBibG9j
a2luZ19ub3RpZmllcl9jaGFpbl91bnJlZ2lzdGVyKAo+ID4+PiArICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAmY3B1dGhlcm1fc3RhdGVfbm90aWZpZXJfbGlzdCwgbmIpOwo+ID4+PiArICAg
ICAgICAgICAgIGJyZWFrOwo+ID4+PiArICAgICBkZWZhdWx0Ogo+ID4+PiArICAgICAgICAgICAg
IHJldCA9IC1FSU5WQUw7Cj4gPj4+ICsgICAgIH0KPiA+Pj4gKyAgICAgcmV0dXJuIHJldDsKPiA+
Pj4gK30KPiA+Pj4gK0VYUE9SVF9TWU1CT0woY3B1dGhlcm1fdW5yZWdpc3Rlcl9ub3RpZmllcik7
Cj4gPj4+ICsKPiA+Pj4gKy8qIEJlbG93IGNvZGUgZGVmaW5lcyBmdW5jdGlvbnMgdG8gYmUgdXNl
ZCBmb3IgY3B1ZnJlcSBhcyBjb29saW5nIGRldmljZSAqLwo+ID4+PiArCj4gPj4+ICsvKioKPiA+
Pj4gKyAqIGlzX2NwdWZyZXFfdmFsaWQgLSBmdW5jdGlvbiB0byBjaGVjayBpZiBhIGNwdSBoYXMg
ZnJlcXVlbmN5IHRyYW5zaXRpb24gcG9saWN5Lgo+ID4+PiArICogQGNwdTogY3B1IGZvciB3aGlj
aCBjaGVjayBpcyBuZWVkZWQuCj4gPj4+ICsgKi8KPiA+Pj4gK3N0YXRpYyBpbnQgaXNfY3B1ZnJl
cV92YWxpZChpbnQgY3B1KQo+ID4+PiArewo+ID4+PiArICAgICBzdHJ1Y3QgY3B1ZnJlcV9wb2xp
Y3kgcG9saWN5Owo+ID4+PiArICAgICByZXR1cm4gIWNwdWZyZXFfZ2V0X3BvbGljeSgmcG9saWN5
LCBjcHUpOwo+ID4+PiArfQo+ID4+PiArCj4gPj4+ICsvKioKPiA+Pj4gKyAqIGdldF9jcHVfZnJl
cXVlbmN5IC0gZ2V0IHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiBmcmVxdWVuY3kgZnJvbSBsZXZlbC4K
PiA+Pj4gKyAqIEBjcHU6IGNwdSBmb3Igd2hpY2ggZnJlcXVlbmN5IGlzIGZldGNoZWQuCj4gPj4+
ICsgKiBAbGV2ZWw6IGxldmVsIG9mIGZyZXF1ZW5jeSBvZiB0aGUgQ1BVCj4gPj4+ICsgKiAgIGUu
ZyBsZXZlbD0xIC0tPiAxc3QgTUFYIEZSRVEsIExFVkVMPTIgLS0tPiAybmQgTUFYIEZSRVEsIC4u
Li4gZXRjCj4gPj4+ICsgKi8KPiA+Pj4gK3N0YXRpYyB1bnNpZ25lZCBpbnQgZ2V0X2NwdV9mcmVx
dWVuY3kodW5zaWduZWQgaW50IGNwdSwgdW5zaWduZWQgbG9uZyBsZXZlbCkKPiA+Pj4gK3sKPiA+
Pj4gKyAgICAgaW50IHJldCA9IDAsIGkgPSAwOwo+ID4+PiArICAgICB1bnNpZ25lZCBsb25nIGxl
dmVsX2luZGV4Owo+ID4+PiArICAgICBib29sIGRlc2NlbmQgPSBmYWxzZTsKPiA+Pj4gKyAgICAg
c3RydWN0IGNwdWZyZXFfZnJlcXVlbmN5X3RhYmxlICp0YWJsZSA9Cj4gPj4+ICsgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgY3B1ZnJlcV9mcmVxdWVuY3lfZ2V0X3RhYmxlKGNw
dSk7Cj4gPj4+ICsgICAgIGlmICghdGFibGUpCj4gPj4+ICsgICAgICAgICAgICAgcmV0dXJuIHJl
dDsKPiA+Pj4gKwo+ID4+PiArICAgICB3aGlsZSAodGFibGVbaV0uZnJlcXVlbmN5ICE9IENQVUZS
RVFfVEFCTEVfRU5EKSB7Cj4gPj4+ICsgICAgICAgICAgICAgaWYgKHRhYmxlW2ldLmZyZXF1ZW5j
eSA9PSBDUFVGUkVRX0VOVFJZX0lOVkFMSUQpCj4gPj4+ICsgICAgICAgICAgICAgICAgICAgICBj
b250aW51ZTsKPiA+Pj4gKwo+ID4+PiArICAgICAgICAgICAgIC8qY2hlY2sgaWYgdGFibGUgaW4g
YXNjZW5kaW5nIG9yIGRlc2NlbmRpbmcgb3JkZXIqLwo+ID4+PiArICAgICAgICAgICAgIGlmICgo
dGFibGVbaSArIDFdLmZyZXF1ZW5jeSAhPSBDUFVGUkVRX1RBQkxFX0VORCkgJiYKPiA+Pj4gKyAg
ICAgICAgICAgICAgICAgICAgICh0YWJsZVtpICsgMV0uZnJlcXVlbmN5IDwgdGFibGVbaV0uZnJl
cXVlbmN5KQo+ID4+PiArICAgICAgICAgICAgICAgICAgICAgJiYgIWRlc2NlbmQpIHsKPiA+Pj4g
KyAgICAgICAgICAgICAgICAgICAgIGRlc2NlbmQgPSB0cnVlOwo+ID4+PiArICAgICAgICAgICAg
IH0KPiA+Pj4gKwo+ID4+PiArICAgICAgICAgICAgIC8qcmV0dXJuIGlmIGxldmVsIG1hdGNoZWQg
YW5kIHRhYmxlIGluIGRlc2NlbmRpbmcgb3JkZXIqLwo+ID4+PiArICAgICAgICAgICAgIGlmIChk
ZXNjZW5kICYmIGkgPT0gbGV2ZWwpCj4gPj4+ICsgICAgICAgICAgICAgICAgICAgICByZXR1cm4g
dGFibGVbaV0uZnJlcXVlbmN5Owo+ID4+PiArICAgICAgICAgICAgIGkrKzsKPiA+Pj4gKyAgICAg
fQo+ID4+PiArICAgICBpLS07Cj4gPj4+ICsKPiA+Pj4gKyAgICAgaWYgKGxldmVsID4gaSB8fCBk
ZXNjZW5kKQo+ID4+PiArICAgICAgICAgICAgIHJldHVybiByZXQ7Cj4gPj4+ICsgICAgIGxldmVs
X2luZGV4ID0gaSAtIGxldmVsOwo+ID4+PiArCj4gPj4+ICsgICAgIC8qU2NhbiB0aGUgdGFibGUg
aW4gcmV2ZXJzZSBvcmRlciBhbmQgbWF0Y2ggdGhlIGxldmVsKi8KPiA+Pj4gKyAgICAgd2hpbGUg
KGkgPj0gMCkgewo+ID4+PiArICAgICAgICAgICAgIGlmICh0YWJsZVtpXS5mcmVxdWVuY3kgPT0g
Q1BVRlJFUV9FTlRSWV9JTlZBTElEKQo+ID4+PiArICAgICAgICAgICAgICAgICAgICAgY29udGlu
dWU7Cj4gPj4+ICsgICAgICAgICAgICAgLypyZXR1cm4gaWYgbGV2ZWwgbWF0Y2hlZCovCj4gPj4+
ICsgICAgICAgICAgICAgaWYgKGkgPT0gbGV2ZWxfaW5kZXgpCj4gPj4+ICsgICAgICAgICAgICAg
ICAgICAgICByZXR1cm4gdGFibGVbaV0uZnJlcXVlbmN5Owo+ID4+PiArICAgICAgICAgICAgIGkt
LTsKPiA+Pj4gKyAgICAgfQo+ID4+PiArICAgICByZXR1cm4gcmV0Owo+ID4+PiArfQo+ID4+PiAr
Cj4gPj4+ICsvKioKPiA+Pj4gKyAqIGNwdWZyZXFfYXBwbHlfY29vbGluZyAtIGZ1bmN0aW9uIHRv
IGFwcGx5IGZyZXF1ZW5jeSBjbGlwcGluZy4KPiA+Pj4gKyAqIEBjcHVmcmVxX2RldmljZTogY3B1
ZnJlcV9jb29saW5nX2RldmljZSBwb2ludGVyIGNvbnRhaW5pbmcgZnJlcXVlbmN5Cj4gPj4+ICsg
KiAgIGNsaXBwaW5nIGRhdGEuCj4gPj4+ICsgKiBAY29vbGluZ19zdGF0ZTogdmFsdWUgb2YgdGhl
IGNvb2xpbmcgc3RhdGUuCj4gPj4+ICsgKi8KPiA+Pj4gK3N0YXRpYyBpbnQgY3B1ZnJlcV9hcHBs
eV9jb29saW5nKHN0cnVjdCBjcHVmcmVxX2Nvb2xpbmdfZGV2aWNlICpjcHVmcmVxX2RldmljZSwK
PiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5zaWduZWQgbG9uZyBjb29saW5n
X3N0YXRlKQo+ID4+PiArewo+ID4+PiArICAgICB1bnNpZ25lZCBpbnQgZXZlbnQsIGNwdWlkLCBj
bGlwX2ZyZXE7Cj4gPj4+ICsgICAgIHN0cnVjdCBjcHVtYXNrICptYXNrUHRyID0gJmNwdWZyZXFf
ZGV2aWNlLT5hbGxvd2VkX2NwdXM7Cj4gPj4+ICsgICAgIHVuc2lnbmVkIGludCBjcHUgPSBjcHVt
YXNrX2FueShtYXNrUHRyKTsKPiA+Pj4gKwo+ID4+PiArCj4gPj4+ICsgICAgIC8qIENoZWNrIGlm
IHRoZSBvbGQgY29vbGluZyBhY3Rpb24gaXMgc2FtZSBhcyBuZXcgY29vbGluZyBhY3Rpb24gKi8K
PiA+Pj4gKyAgICAgaWYgKGNwdWZyZXFfZGV2aWNlLT5jcHVmcmVxX3N0YXRlID09IGNvb2xpbmdf
c3RhdGUpCj4gPj4+ICsgICAgICAgICAgICAgcmV0dXJuIDA7Cj4gPj4+ICsKPiA+Pj4gKyAgICAg
Y2xpcF9mcmVxID0gZ2V0X2NwdV9mcmVxdWVuY3koY3B1LCBjb29saW5nX3N0YXRlKTsKPiA+Pj4g
KyAgICAgaWYgKCFjbGlwX2ZyZXEpCj4gPj4+ICsgICAgICAgICAgICAgcmV0dXJuIC1FSU5WQUw7
Cj4gPj4+ICsKPiA+Pj4gKyAgICAgY3B1ZnJlcV9kZXZpY2UtPmNwdWZyZXFfc3RhdGUgPSBjb29s
aW5nX3N0YXRlOwo+ID4+PiArICAgICBjcHVmcmVxX2RldmljZS0+Y3B1ZnJlcV92YWwgPSBjbGlw
X2ZyZXE7Cj4gPj4+ICsgICAgIG5vdGlmeV9kZXZpY2UgPSBjcHVmcmVxX2RldmljZTsKPiA+Pj4g
Kwo+ID4+PiArICAgICBpZiAoY29vbGluZ19zdGF0ZSAhPSAwKQo+ID4+PiArICAgICAgICAgICAg
IGV2ZW50ID0gQ1BVRlJFUV9DT09MSU5HX1NUQVJUOwo+ID4+PiArICAgICBlbHNlCj4gPj4+ICsg
ICAgICAgICAgICAgZXZlbnQgPSBDUFVGUkVRX0NPT0xJTkdfU1RPUDsKPiA+Pj4gKwo+ID4+PiAr
ICAgICBibG9ja2luZ19ub3RpZmllcl9jYWxsX2NoYWluKCZjcHV0aGVybV9zdGF0ZV9ub3RpZmll
cl9saXN0LAo+ID4+PiArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgZXZlbnQsICZjbGlwX2ZyZXEpOwo+ID4+PiArCj4gPj4+ICsgICAgIGZvcl9lYWNoX2NwdShj
cHVpZCwgbWFza1B0cikgewo+ID4+PiArICAgICAgICAgICAgIGlmIChpc19jcHVmcmVxX3ZhbGlk
KGNwdWlkKSkKPiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgIGNwdWZyZXFfdXBkYXRlX3BvbGlj
eShjcHVpZCk7Cj4gPj4+ICsgICAgIH0KPiA+Pj4gKwo+ID4+PiArICAgICBub3RpZnlfZGV2aWNl
ID0gTk9USUZZX0lOVkFMSUQ7Cj4gPj4+ICsKPiA+Pj4gKyAgICAgcmV0dXJuIDA7Cj4gPj4+ICt9
Cj4gPj4+ICsKPiA+Pj4gKy8qKgo+ID4+PiArICogY3B1ZnJlcV90aGVybWFsX25vdGlmaWVyIC0g
bm90aWZpZXIgY2FsbGJhY2sgZm9yIGNwdWZyZXEgcG9saWN5IGNoYW5nZS4KPiA+Pj4gKyAqIEBu
YjogICAgICBzdHJ1Y3Qgbm90aWZpZXJfYmxvY2sgKiB3aXRoIGNhbGxiYWNrIGluZm8uCj4gPj4+
ICsgKiBAZXZlbnQ6IHZhbHVlIHNob3dpbmcgY3B1ZnJlcSBldmVudCBmb3Igd2hpY2ggdGhpcyBm
dW5jdGlvbiBpbnZva2VkLgo+ID4+PiArICogQGRhdGE6IGNhbGxiYWNrLXNwZWNpZmljIGRhdGEK
PiA+Pj4gKyAqLwo+ID4+PiArc3RhdGljIGludCBjcHVmcmVxX3RoZXJtYWxfbm90aWZpZXIoc3Ry
dWN0IG5vdGlmaWVyX2Jsb2NrICpuYiwKPiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICB1bnNpZ25lZCBsb25nIGV2ZW50LCB2b2lkICpkYXRhKQo+ID4+PiArewo+ID4+
PiArICAgICBzdHJ1Y3QgY3B1ZnJlcV9wb2xpY3kgKnBvbGljeSA9IGRhdGE7Cj4gPj4+ICsgICAg
IHVuc2lnbmVkIGxvbmcgbWF4X2ZyZXEgPSAwOwo+ID4+PiArCj4gPj4+ICsgICAgIGlmIChldmVu
dCAhPSBDUFVGUkVRX0FESlVTVCB8fCBub3RpZnlfZGV2aWNlID09IE5PVElGWV9JTlZBTElEKQo+
ID4+PiArICAgICAgICAgICAgIHJldHVybiAwOwo+ID4+PiArCj4gPj4+ICsgICAgIGlmIChjcHVt
YXNrX3Rlc3RfY3B1KHBvbGljeS0+Y3B1LCAmbm90aWZ5X2RldmljZS0+YWxsb3dlZF9jcHVzKSkK
PiA+Pj4gKyAgICAgICAgICAgICBtYXhfZnJlcSA9IG5vdGlmeV9kZXZpY2UtPmNwdWZyZXFfdmFs
Owo+ID4+PiArCj4gPj4+ICsgICAgIC8qIE5ldmVyIGV4Y2VlZCB1c2VyX3BvbGljeS5tYXgqLwo+
ID4+PiArICAgICBpZiAobWF4X2ZyZXEgPiBwb2xpY3ktPnVzZXJfcG9saWN5Lm1heCkKPiA+Pj4g
KyAgICAgICAgICAgICBtYXhfZnJlcSA9IHBvbGljeS0+dXNlcl9wb2xpY3kubWF4Owo+ID4+PiAr
Cj4gPj4+ICsgICAgIGlmIChwb2xpY3ktPm1heCAhPSBtYXhfZnJlcSkKPiA+Pj4gKyAgICAgICAg
ICAgICBjcHVmcmVxX3ZlcmlmeV93aXRoaW5fbGltaXRzKHBvbGljeSwgMCwgbWF4X2ZyZXEpOwo+
ID4+PiArCj4gPj4+ICsgICAgIHJldHVybiAwOwo+ID4+PiArfQo+ID4+PiArCj4gPj4+ICsvKgo+
ID4+PiArICogY3B1ZnJlcSBjb29saW5nIGRldmljZSBjYWxsYmFjayBmdW5jdGlvbnMgYXJlIGRl
ZmluZWQgYmVsb3cKPiA+Pj4gKyAqLwo+ID4+PiArCj4gPj4+ICsvKioKPiA+Pj4gKyAqIGNwdWZy
ZXFfZ2V0X21heF9zdGF0ZSAtIGNhbGxiYWNrIGZ1bmN0aW9uIHRvIGdldCB0aGUgbWF4IGNvb2xp
bmcgc3RhdGUuCj4gPj4+ICsgKiBAY2RldjogdGhlcm1hbCBjb29saW5nIGRldmljZSBwb2ludGVy
Lgo+ID4+PiArICogQHN0YXRlOiBmaWxsIHRoaXMgdmFyaWFibGUgd2l0aCB0aGUgbWF4IGNvb2xp
bmcgc3RhdGUuCj4gPj4+ICsgKi8KPiA+Pj4gK3N0YXRpYyBpbnQgY3B1ZnJlcV9nZXRfbWF4X3N0
YXRlKHN0cnVjdCB0aGVybWFsX2Nvb2xpbmdfZGV2aWNlICpjZGV2LAo+ID4+PiArICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgdW5zaWduZWQgbG9uZyAqc3RhdGUpCj4gPj4+ICt7Cj4gPj4+
ICsgICAgIGludCByZXQgPSAtRUlOVkFMLCBpID0gMDsKPiA+Pj4gKyAgICAgc3RydWN0IGNwdWZy
ZXFfY29vbGluZ19kZXZpY2UgKmNwdWZyZXFfZGV2aWNlOwo+ID4+PiArICAgICBzdHJ1Y3QgY3B1
bWFzayAqbWFza1B0cjsKPiA+Pj4gKyAgICAgdW5zaWduZWQgaW50IGNwdTsKPiA+Pj4gKyAgICAg
c3RydWN0IGNwdWZyZXFfZnJlcXVlbmN5X3RhYmxlICp0YWJsZTsKPiA+Pj4gKwo+ID4+PiArICAg
ICBtdXRleF9sb2NrKCZjb29saW5nX2NwdWZyZXFfbG9jayk7Cj4gPj4+ICsgICAgIGxpc3RfZm9y
X2VhY2hfZW50cnkoY3B1ZnJlcV9kZXZpY2UsICZjb29saW5nX2NwdWZyZXFfbGlzdCwgbm9kZSkg
ewo+ID4+PiArICAgICAgICAgICAgIGlmIChjcHVmcmVxX2RldmljZSAmJiBjcHVmcmVxX2Rldmlj
ZS0+Y29vbF9kZXYgPT0gY2RldikKPiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwo+
ID4+PiArICAgICB9Cj4gPj4+ICsgICAgIGlmIChjcHVmcmVxX2RldmljZSA9PSBOVUxMKQo+ID4+
PiArICAgICAgICAgICAgIGdvdG8gcmV0dXJuX2dldF9tYXhfc3RhdGU7Cj4gPj4+ICsKPiA+Pj4g
KyAgICAgbWFza1B0ciA9ICZjcHVmcmVxX2RldmljZS0+YWxsb3dlZF9jcHVzOwo+ID4+PiArICAg
ICBjcHUgPSBjcHVtYXNrX2FueShtYXNrUHRyKTsKPiA+Pj4gKyAgICAgdGFibGUgPSBjcHVmcmVx
X2ZyZXF1ZW5jeV9nZXRfdGFibGUoY3B1KTsKPiA+Pj4gKyAgICAgaWYgKCF0YWJsZSkgewo+ID4+
PiArICAgICAgICAgICAgICpzdGF0ZSA9IDA7Cj4gPj4+ICsgICAgICAgICAgICAgcmV0ID0gMDsK
PiA+Pj4gKyAgICAgICAgICAgICBnb3RvIHJldHVybl9nZXRfbWF4X3N0YXRlOwo+ID4+PiArICAg
ICB9Cj4gPj4+ICsKPiA+Pj4gKyAgICAgd2hpbGUgKHRhYmxlW2ldLmZyZXF1ZW5jeSAhPSBDUFVG
UkVRX1RBQkxFX0VORCkgewo+ID4+PiArICAgICAgICAgICAgIGlmICh0YWJsZVtpXS5mcmVxdWVu
Y3kgPT0gQ1BVRlJFUV9FTlRSWV9JTlZBTElEKQo+ID4+PiArICAgICAgICAgICAgICAgICAgICAg
Y29udGludWU7Cj4gPj4+ICsgICAgICAgICAgICAgaSsrOwo+ID4+PiArICAgICB9Cj4gPj4+ICsg
ICAgIGlmIChpID4gMCkgewo+ID4+PiArICAgICAgICAgICAgICpzdGF0ZSA9IC0taTsKPiA+Pj4g
KyAgICAgICAgICAgICByZXQgPSAwOwo+ID4+PiArICAgICB9Cj4gPj4+ICsKPiA+Pj4gK3JldHVy
bl9nZXRfbWF4X3N0YXRlOgo+ID4+PiArICAgICBtdXRleF91bmxvY2soJmNvb2xpbmdfY3B1ZnJl
cV9sb2NrKTsKPiA+Pj4gKyAgICAgcmV0dXJuIHJldDsKPiA+Pj4gK30KPiA+Pj4gKwo+ID4+PiAr
LyoqCj4gPj4+ICsgKiBjcHVmcmVxX2dldF9jdXJfc3RhdGUgLSBjYWxsYmFjayBmdW5jdGlvbiB0
byBnZXQgdGhlIGN1cnJlbnQgY29vbGluZyBzdGF0ZS4KPiA+Pj4gKyAqIEBjZGV2OiB0aGVybWFs
IGNvb2xpbmcgZGV2aWNlIHBvaW50ZXIuCj4gPj4+ICsgKiBAc3RhdGU6IGZpbGwgdGhpcyB2YXJp
YWJsZSB3aXRoIHRoZSBjdXJyZW50IGNvb2xpbmcgc3RhdGUuCj4gPj4+ICsgKi8KPiA+Pj4gK3N0
YXRpYyBpbnQgY3B1ZnJlcV9nZXRfY3VyX3N0YXRlKHN0cnVjdCB0aGVybWFsX2Nvb2xpbmdfZGV2
aWNlICpjZGV2LAo+ID4+PiArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5zaWduZWQg
bG9uZyAqc3RhdGUpCj4gPj4+ICt7Cj4gPj4+ICsgICAgIGludCByZXQgPSAtRUlOVkFMOwo+ID4+
PiArICAgICBzdHJ1Y3QgY3B1ZnJlcV9jb29saW5nX2RldmljZSAqY3B1ZnJlcV9kZXZpY2U7Cj4g
Pj4+ICsKPiA+Pj4gKyAgICAgbXV0ZXhfbG9jaygmY29vbGluZ19jcHVmcmVxX2xvY2spOwo+ID4+
PiArICAgICBsaXN0X2Zvcl9lYWNoX2VudHJ5KGNwdWZyZXFfZGV2aWNlLCAmY29vbGluZ19jcHVm
cmVxX2xpc3QsIG5vZGUpIHsKPiA+Pj4gKyAgICAgICAgICAgICBpZiAoY3B1ZnJlcV9kZXZpY2Ug
JiYgY3B1ZnJlcV9kZXZpY2UtPmNvb2xfZGV2ID09IGNkZXYpIHsKPiA+Pj4gKyAgICAgICAgICAg
ICAgICAgICAgICpzdGF0ZSA9IGNwdWZyZXFfZGV2aWNlLT5jcHVmcmVxX3N0YXRlOwo+ID4+PiAr
ICAgICAgICAgICAgICAgICAgICAgcmV0ID0gMDsKPiA+Pj4gKyAgICAgICAgICAgICAgICAgICAg
IGJyZWFrOwo+ID4+PiArICAgICAgICAgICAgIH0KPiA+Pj4gKyAgICAgfQo+ID4+PiArICAgICBt
dXRleF91bmxvY2soJmNvb2xpbmdfY3B1ZnJlcV9sb2NrKTsKPiA+Pj4gKwo+ID4+Cj4gPj4gYXMg
Y3B1ZnJlcSBtYXkgYmUgY2hhbmdlZCBpbiBvdGhlciBwbGFjZXMsIGUuZy4gdmlhIHN5c2ZzIEkv
Riwgd2Ugc2hvdWxkCj4gPj4gdXNlIHRoZSBjdXJyZW50IGNwdSBmcmVxdWVuY3kgdG8gZ2V0IHRo
ZSBSRUFMIGNvb2xpbmcgc3RhdGUsIHJhdGhlciB0aGFuCj4gPj4gdXNpbmcgYSBjYWNoZWQgdmFs
dWUuCj4gPgo+ID4gWWVzIGFncmVlZCAsIEkgd2lsbCByZXBvc3Qgd2l0aCB5b3VyIHN1Z2dlc3Rp
b24uCj4gPgo+ID4gVGhhbmtzLAo+ID4gQW1pdAo+ID4+Cj4gPj4gdGhhbmtzLAo+ID4+IHJ1aQo+
ID4+Cj4gPj4KPiA+IC0tCj4gPiBUbyB1bnN1YnNjcmliZSBmcm9tIHRoaXMgbGlzdDogc2VuZCB0
aGUgbGluZSAidW5zdWJzY3JpYmUgbGludXgtYWNwaSIgaW4KPiA+IHRoZSBib2R5IG9mIGEgbWVz
c2FnZSB0byBtYWpvcmRvbW9Admdlci5rZXJuZWwub3JnCj4gPiBNb3JlIG1ham9yZG9tbyBpbmZv
IGF0ICBodHRwOi8vdmdlci5rZXJuZWwub3JnL21ham9yZG9tby1pbmZvLmh0bWwKPiAKPiAKPiAK
CgoKX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KbG0tc2Vu
c29ycyBtYWlsaW5nIGxpc3QKbG0tc2Vuc29yc0BsbS1zZW5zb3JzLm9yZwpodHRwOi8vbGlzdHMu
bG0tc2Vuc29ycy5vcmcvbWFpbG1hbi9saXN0aW5mby9sbS1zZW5zb3Jz

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
  2012-08-20  2:16           ` Zhang Rui
  (?)
@ 2012-08-20  9:36             ` Valentin, Eduardo
  -1 siblings, 0 replies; 50+ messages in thread
From: Valentin, Eduardo @ 2012-08-20  9:36 UTC (permalink / raw)
  To: Zhang Rui
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park, linux-pm,
	Guenter Roeck, Andrew Morton

Hello Rui,

On Mon, Aug 20, 2012 at 5:16 AM, Zhang Rui <rui.zhang@intel.com> wrote:
> On 五, 2012-08-17 at 11:56 +0300, Valentin, Eduardo wrote:
>> Hello,
>
>> >>> +
>> >>> +
>> >>> +1.2 CPU cooling action notifier register/unregister interface
>> >>> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
>> >>> +     unsigned int list)
>> >>> +
>> >>> +    This interface registers a driver with cpu cooling layer. The driver will
>> >>> +    be notified when any cpu cooling action is called.
>> >>> +
>> >>> +    nb: notifier function to register
>> >>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>> >>> +
>> >>> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
>> >>> +     unsigned int list)
>> >>> +
>> >>> +    This interface registers a driver with cpu cooling layer. The driver will
>> >>> +    be notified when any cpu cooling action is called.
>> >>> +
>> >>> +    nb: notifier function to register
>> >>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>> >>
>> >> what are these two APIs used for?
>> >> I did not see they are used in your patch set, do I miss something?
>> > No currently they are not used by my patches. I added them on request
>> > from Eduardo and others
>>
>> Yeah, this was a suggestion as we didn't really know how the FW part
>> would evolve by that time.
>>
>> The reasoning is to allow any interested user (in kernel) to be
>> notified when max frequency changes.
>
> in this case, the cooling device should be updated.
> Say all the target of the thermal_instances of a cooling devices is 0,
> which means they want the cpu to run at maximum frequency, when the max
> frequency changes, we should set the processor to the new max frequency
> as well.
> Using notification is not a good idea as user can not handle this
> without interacting with the thermal framework.
>
> IMO, we should introduce a new API to handle this, rather than just
> notifications to users.

Agreed. The context is actually much wider than the CPU cooling. In
fact, cooling co-processors is actually where things gets a bit
complicated. The idea behind this type of handshaking is to allow the
affected subsystem to change their actual setup when max performance
is not allowed anymore.

>
>>  Actually, the use case behind
>> this is to allow such users to perform some handshaking or stop their
>> activities or even change some paramenters, in case the max frequency
>> would change.
>
> It is the cooling device driver that notice this change first, and it
> should invoke something like thermal_cooling_device_update/rebind() to
> tell the thermal framework this change.
>

Yeah. Ideally, the framework needs to be aware of cooling device state
change. Today, as we have pretty much a broadcast/unidirectional type
of messaging, the framework/policy always assumes the cooling devices
will be in sync with what it is dictated by the policy.

>>  Ideally it would be possible to nack the cooling
>> transition. But that is yet a wider discussion. So far we don't have
>> users for this.
>>
> yep.
> I thought about this before, but I'd prefer to do this when there is a
> real user. Or else, we are kind of over-designing here.
> how about removing this piece of code for now?


Agreed. I second you that this problem is a much wider issue and needs
improvement in the FW itself and how the cooling devices are designed.

>
> thanks,
> rui
>
>> >>
>> >>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>> >>> index 7dd8c34..996003b 100644
>> >>> --- a/drivers/thermal/Kconfig
>> >>> +++ b/drivers/thermal/Kconfig
>> >>> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>> >>>       depends on HWMON=y || HWMON=THERMAL
>> >>>       default y
>> >>>
>> >>> +config CPU_THERMAL
>> >>> +     bool "generic cpu cooling support"
>> >>> +     depends on THERMAL && CPU_FREQ
>> >>> +     help
>> >>> +       This implements the generic cpu cooling mechanism through frequency
>> >>> +       reduction, cpu hotplug and any other ways of reducing temperature. An
>> >>> +       ACPI version of this already exists(drivers/acpi/processor_thermal.c).
>> >>> +       This will be useful for platforms using the generic thermal interface
>> >>> +       and not the ACPI interface.
>> >>> +       If you want this support, you should say Y here.
>> >>> +
>> >>>  config SPEAR_THERMAL
>> >>>       bool "SPEAr thermal sensor driver"
>> >>>       depends on THERMAL
>> >>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>> >>> index fd9369a..aae59ad 100644
>> >>> --- a/drivers/thermal/Makefile
>> >>> +++ b/drivers/thermal/Makefile
>> >>> @@ -3,5 +3,6 @@
>> >>>  #
>> >>>
>> >>>  obj-$(CONFIG_THERMAL)                += thermal_sys.o
>> >>> +obj-$(CONFIG_CPU_THERMAL)            += cpu_cooling.o
>> >>>  obj-$(CONFIG_SPEAR_THERMAL)          += spear_thermal.o
>> >>>  obj-$(CONFIG_RCAR_THERMAL)   += rcar_thermal.o
>> >>> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
>> >>> new file mode 100644
>> >>> index 0000000..c42e557
>> >>> --- /dev/null
>> >>> +++ b/drivers/thermal/cpu_cooling.c
>> >>> @@ -0,0 +1,512 @@
>> >>> +/*
>> >>> + *  linux/drivers/thermal/cpu_cooling.c
>> >>> + *
>> >>> + *  Copyright (C) 2012       Samsung Electronics Co., Ltd(http://www.samsung.com)
>> >>> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
>> >>> + *
>> >>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> >>> + *  This program is free software; you can redistribute it and/or modify
>> >>> + *  it under the terms of the GNU General Public License as published by
>> >>> + *  the Free Software Foundation; version 2 of the License.
>> >>> + *
>> >>> + *  This program is distributed in the hope that it will be useful, but
>> >>> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
>> >>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> >>> + *  General Public License for more details.
>> >>> + *
>> >>> + *  You should have received a copy of the GNU General Public License along
>> >>> + *  with this program; if not, write to the Free Software Foundation, Inc.,
>> >>> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
>> >>> + *
>> >>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> >>> + */
>> >>> +#include <linux/kernel.h>
>> >>> +#include <linux/module.h>
>> >>> +#include <linux/thermal.h>
>> >>> +#include <linux/platform_device.h>
>> >>> +#include <linux/cpufreq.h>
>> >>> +#include <linux/err.h>
>> >>> +#include <linux/slab.h>
>> >>> +#include <linux/cpu.h>
>> >>> +#include <linux/cpu_cooling.h>
>> >>> +
>> >>> +/**
>> >>> + * struct cpufreq_cooling_device
>> >>> + * @id: unique integer value corresponding to each cpufreq_cooling_device
>> >>> + *   registered.
>> >>> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
>> >>> + *   egistered cooling device.
>> >>> + * @cpufreq_state: integer value representing the current state of cpufreq
>> >>> + *   cooling devices.
>> >>> + * @cpufreq_val: integer value representing the absolute value of the clipped
>> >>> + *   frequency.
>> >>> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
>> >>> + * @node: list_head to link all cpufreq_cooling_device together.
>> >>> + *
>> >>> + * This structure is required for keeping information of each
>> >>> + * cpufreq_cooling_device registered as a list whose head is represented by
>> >>> + * cooling_cpufreq_list. In order to prevent corruption of this list a
>> >>> + * mutex lock cooling_cpufreq_lock is used.
>> >>> + */
>> >>> +struct cpufreq_cooling_device {
>> >>> +     int id;
>> >>> +     struct thermal_cooling_device *cool_dev;
>> >>> +     unsigned int cpufreq_state;
>> >>> +     unsigned int cpufreq_val;
>> >>> +     struct cpumask allowed_cpus;
>> >>> +     struct list_head node;
>> >>> +};
>> >>> +static LIST_HEAD(cooling_cpufreq_list);
>> >>> +static DEFINE_IDR(cpufreq_idr);
>> >>> +
>> >>> +static struct mutex cooling_cpufreq_lock;
>> >>> +
>> >>> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
>> >>> +#define NOTIFY_INVALID NULL
>> >>> +struct cpufreq_cooling_device *notify_device;
>> >>> +
>> >>> +/* Head of the blocking notifier chain to inform about frequency clamping */
>> >>> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
>> >>> +
>> >>> +/**
>> >>> + * get_idr - function to get a unique id.
>> >>> + * @idr: struct idr * handle used to create a id.
>> >>> + * @id: int * value generated by this function.
>> >>> + */
>> >>> +static int get_idr(struct idr *idr, int *id)
>> >>> +{
>> >>> +     int err;
>> >>> +again:
>> >>> +     if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
>> >>> +             return -ENOMEM;
>> >>> +
>> >>> +     mutex_lock(&cooling_cpufreq_lock);
>> >>> +     err = idr_get_new(idr, NULL, id);
>> >>> +     mutex_unlock(&cooling_cpufreq_lock);
>> >>> +
>> >>> +     if (unlikely(err == -EAGAIN))
>> >>> +             goto again;
>> >>> +     else if (unlikely(err))
>> >>> +             return err;
>> >>> +
>> >>> +     *id = *id & MAX_ID_MASK;
>> >>> +     return 0;
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * release_idr - function to free the unique id.
>> >>> + * @idr: struct idr * handle used for creating the id.
>> >>> + * @id: int value representing the unique id.
>> >>> + */
>> >>> +static void release_idr(struct idr *idr, int id)
>> >>> +{
>> >>> +     mutex_lock(&cooling_cpufreq_lock);
>> >>> +     idr_remove(idr, id);
>> >>> +     mutex_unlock(&cooling_cpufreq_lock);
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
>> >>> + * @nb:      struct notifier_block * with callback info.
>> >>> + * @list: integer value for which notification is needed. possible values are
>> >>> + *   CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
>> >>> + *
>> >>> + * This exported function registers a driver with cpu cooling layer. The driver
>> >>> + * will be notified when any cpu cooling action is called.
>> >>> + */
>> >>> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
>> >>> +{
>> >>> +     int ret = 0;
>> >>> +
>> >>> +     switch (list) {
>> >>> +     case CPUFREQ_COOLING_START:
>> >>> +     case CPUFREQ_COOLING_STOP:
>> >>> +             ret = blocking_notifier_chain_register(
>> >>> +                             &cputherm_state_notifier_list, nb);
>> >>> +             break;
>> >>> +     default:
>> >>> +             ret = -EINVAL;
>> >>> +     }
>> >>> +     return ret;
>> >>> +}
>> >>> +EXPORT_SYMBOL(cputherm_register_notifier);
>> >>> +
>> >>> +/**
>> >>> + * cputherm_unregister_notifier - Un-register a notifier.
>> >>> + * @nb:      struct notifier_block * with callback info.
>> >>> + * @list: integer value for which notification is needed. values possible are
>> >>> + *   CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
>> >>> + *
>> >>> + * This exported function un-registers a driver with cpu cooling layer.
>> >>> + */
>> >>> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
>> >>> +{
>> >>> +     int ret = 0;
>> >>> +
>> >>> +     switch (list) {
>> >>> +     case CPUFREQ_COOLING_START:
>> >>> +     case CPUFREQ_COOLING_STOP:
>> >>> +             ret = blocking_notifier_chain_unregister(
>> >>> +                             &cputherm_state_notifier_list, nb);
>> >>> +             break;
>> >>> +     default:
>> >>> +             ret = -EINVAL;
>> >>> +     }
>> >>> +     return ret;
>> >>> +}
>> >>> +EXPORT_SYMBOL(cputherm_unregister_notifier);
>> >>> +
>> >>> +/* Below code defines functions to be used for cpufreq as cooling device */
>> >>> +
>> >>> +/**
>> >>> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
>> >>> + * @cpu: cpu for which check is needed.
>> >>> + */
>> >>> +static int is_cpufreq_valid(int cpu)
>> >>> +{
>> >>> +     struct cpufreq_policy policy;
>> >>> +     return !cpufreq_get_policy(&policy, 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
>> >>> + */
>> >>> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
>> >>> +{
>> >>> +     int ret = 0, i = 0;
>> >>> +     unsigned long level_index;
>> >>> +     bool descend = false;
>> >>> +     struct cpufreq_frequency_table *table =
>> >>> +                                     cpufreq_frequency_get_table(cpu);
>> >>> +     if (!table)
>> >>> +             return ret;
>> >>> +
>> >>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> >>> +                     continue;
>> >>> +
>> >>> +             /*check if table in ascending or descending order*/
>> >>> +             if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
>> >>> +                     (table[i + 1].frequency < table[i].frequency)
>> >>> +                     && !descend) {
>> >>> +                     descend = true;
>> >>> +             }
>> >>> +
>> >>> +             /*return if level matched and table in descending order*/
>> >>> +             if (descend && i == level)
>> >>> +                     return table[i].frequency;
>> >>> +             i++;
>> >>> +     }
>> >>> +     i--;
>> >>> +
>> >>> +     if (level > i || descend)
>> >>> +             return ret;
>> >>> +     level_index = i - level;
>> >>> +
>> >>> +     /*Scan the table in reverse order and match the level*/
>> >>> +     while (i >= 0) {
>> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> >>> +                     continue;
>> >>> +             /*return if level matched*/
>> >>> +             if (i == level_index)
>> >>> +                     return table[i].frequency;
>> >>> +             i--;
>> >>> +     }
>> >>> +     return ret;
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * cpufreq_apply_cooling - function to apply frequency clipping.
>> >>> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
>> >>> + *   clipping data.
>> >>> + * @cooling_state: value of the cooling state.
>> >>> + */
>> >>> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
>> >>> +                             unsigned long cooling_state)
>> >>> +{
>> >>> +     unsigned int event, cpuid, clip_freq;
>> >>> +     struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
>> >>> +     unsigned int cpu = cpumask_any(maskPtr);
>> >>> +
>> >>> +
>> >>> +     /* Check if the old cooling action is same as new cooling action */
>> >>> +     if (cpufreq_device->cpufreq_state == cooling_state)
>> >>> +             return 0;
>> >>> +
>> >>> +     clip_freq = get_cpu_frequency(cpu, cooling_state);
>> >>> +     if (!clip_freq)
>> >>> +             return -EINVAL;
>> >>> +
>> >>> +     cpufreq_device->cpufreq_state = cooling_state;
>> >>> +     cpufreq_device->cpufreq_val = clip_freq;
>> >>> +     notify_device = cpufreq_device;
>> >>> +
>> >>> +     if (cooling_state != 0)
>> >>> +             event = CPUFREQ_COOLING_START;
>> >>> +     else
>> >>> +             event = CPUFREQ_COOLING_STOP;
>> >>> +
>> >>> +     blocking_notifier_call_chain(&cputherm_state_notifier_list,
>> >>> +                                             event, &clip_freq);
>> >>> +
>> >>> +     for_each_cpu(cpuid, maskPtr) {
>> >>> +             if (is_cpufreq_valid(cpuid))
>> >>> +                     cpufreq_update_policy(cpuid);
>> >>> +     }
>> >>> +
>> >>> +     notify_device = NOTIFY_INVALID;
>> >>> +
>> >>> +     return 0;
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
>> >>> + * @nb:      struct notifier_block * with callback info.
>> >>> + * @event: value showing cpufreq event for which this function invoked.
>> >>> + * @data: callback-specific data
>> >>> + */
>> >>> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
>> >>> +                                     unsigned long event, void *data)
>> >>> +{
>> >>> +     struct cpufreq_policy *policy = data;
>> >>> +     unsigned long max_freq = 0;
>> >>> +
>> >>> +     if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
>> >>> +             return 0;
>> >>> +
>> >>> +     if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
>> >>> +             max_freq = notify_device->cpufreq_val;
>> >>> +
>> >>> +     /* Never exceed user_policy.max*/
>> >>> +     if (max_freq > policy->user_policy.max)
>> >>> +             max_freq = policy->user_policy.max;
>> >>> +
>> >>> +     if (policy->max != max_freq)
>> >>> +             cpufreq_verify_within_limits(policy, 0, max_freq);
>> >>> +
>> >>> +     return 0;
>> >>> +}
>> >>> +
>> >>> +/*
>> >>> + * cpufreq cooling device callback functions are defined below
>> >>> + */
>> >>> +
>> >>> +/**
>> >>> + * cpufreq_get_max_state - callback function to get the max cooling state.
>> >>> + * @cdev: thermal cooling device pointer.
>> >>> + * @state: fill this variable with the max cooling state.
>> >>> + */
>> >>> +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;
>> >>> +     unsigned int cpu;
>> >>> +     struct cpufreq_frequency_table *table;
>> >>> +
>> >>> +     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;
>> >>> +     }
>> >>> +
>> >>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> >>> +                     continue;
>> >>> +             i++;
>> >>> +     }
>> >>> +     if (i > 0) {
>> >>> +             *state = --i;
>> >>> +             ret = 0;
>> >>> +     }
>> >>> +
>> >>> +return_get_max_state:
>> >>> +     mutex_unlock(&cooling_cpufreq_lock);
>> >>> +     return ret;
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * cpufreq_get_cur_state - callback function to get the current cooling state.
>> >>> + * @cdev: thermal cooling device pointer.
>> >>> + * @state: fill this variable with the current cooling 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);
>> >>> +
>> >>
>> >> as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
>> >> use the current cpu frequency to get the REAL cooling state, rather than
>> >> using a cached value.
>> >
>> > Yes agreed , I will repost with your suggestion.
>> >
>> > Thanks,
>> > Amit
>> >>
>> >> thanks,
>> >> rui
>> >>
>> >>
>> > --
>> > To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
>> > the body of a message to majordomo@vger.kernel.org
>> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>
>>
>>
>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html



-- 

Eduardo Valentin

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-20  9:36             ` Valentin, Eduardo
  0 siblings, 0 replies; 50+ messages in thread
From: Valentin, Eduardo @ 2012-08-20  9:36 UTC (permalink / raw)
  To: Zhang Rui
  Cc: Amit Kachhap, linux-pm, Andrew Morton, Len Brown,
	linux-samsung-soc, linux-kernel, linux-acpi, lm-sensors,
	Guenter Roeck, SangWook Ju, Durgadoss, Jean Delvare,
	Kyungmin Park, Kukjin Kim

Hello Rui,

On Mon, Aug 20, 2012 at 5:16 AM, Zhang Rui <rui.zhang@intel.com> wrote:
> On 五, 2012-08-17 at 11:56 +0300, Valentin, Eduardo wrote:
>> Hello,
>
>> >>> +
>> >>> +
>> >>> +1.2 CPU cooling action notifier register/unregister interface
>> >>> +1.2.1 int cputherm_register_notifier(struct notifier_block *nb,
>> >>> +     unsigned int list)
>> >>> +
>> >>> +    This interface registers a driver with cpu cooling layer. The driver will
>> >>> +    be notified when any cpu cooling action is called.
>> >>> +
>> >>> +    nb: notifier function to register
>> >>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>> >>> +
>> >>> +1.2.2 int cputherm_unregister_notifier(struct notifier_block *nb,
>> >>> +     unsigned int list)
>> >>> +
>> >>> +    This interface registers a driver with cpu cooling layer. The driver will
>> >>> +    be notified when any cpu cooling action is called.
>> >>> +
>> >>> +    nb: notifier function to register
>> >>> +    list: CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP
>> >>
>> >> what are these two APIs used for?
>> >> I did not see they are used in your patch set, do I miss something?
>> > No currently they are not used by my patches. I added them on request
>> > from Eduardo and others
>>
>> Yeah, this was a suggestion as we didn't really know how the FW part
>> would evolve by that time.
>>
>> The reasoning is to allow any interested user (in kernel) to be
>> notified when max frequency changes.
>
> in this case, the cooling device should be updated.
> Say all the target of the thermal_instances of a cooling devices is 0,
> which means they want the cpu to run at maximum frequency, when the max
> frequency changes, we should set the processor to the new max frequency
> as well.
> Using notification is not a good idea as user can not handle this
> without interacting with the thermal framework.
>
> IMO, we should introduce a new API to handle this, rather than just
> notifications to users.

Agreed. The context is actually much wider than the CPU cooling. In
fact, cooling co-processors is actually where things gets a bit
complicated. The idea behind this type of handshaking is to allow the
affected subsystem to change their actual setup when max performance
is not allowed anymore.

>
>>  Actually, the use case behind
>> this is to allow such users to perform some handshaking or stop their
>> activities or even change some paramenters, in case the max frequency
>> would change.
>
> It is the cooling device driver that notice this change first, and it
> should invoke something like thermal_cooling_device_update/rebind() to
> tell the thermal framework this change.
>

Yeah. Ideally, the framework needs to be aware of cooling device state
change. Today, as we have pretty much a broadcast/unidirectional type
of messaging, the framework/policy always assumes the cooling devices
will be in sync with what it is dictated by the policy.

>>  Ideally it would be possible to nack the cooling
>> transition. But that is yet a wider discussion. So far we don't have
>> users for this.
>>
> yep.
> I thought about this before, but I'd prefer to do this when there is a
> real user. Or else, we are kind of over-designing here.
> how about removing this piece of code for now?


Agreed. I second you that this problem is a much wider issue and needs
improvement in the FW itself and how the cooling devices are designed.

>
> thanks,
> rui
>
>> >>
>> >>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>> >>> index 7dd8c34..996003b 100644
>> >>> --- a/drivers/thermal/Kconfig
>> >>> +++ b/drivers/thermal/Kconfig
>> >>> @@ -19,6 +19,17 @@ config THERMAL_HWMON
>> >>>       depends on HWMON=y || HWMON=THERMAL
>> >>>       default y
>> >>>
>> >>> +config CPU_THERMAL
>> >>> +     bool "generic cpu cooling support"
>> >>> +     depends on THERMAL && CPU_FREQ
>> >>> +     help
>> >>> +       This implements the generic cpu cooling mechanism through frequency
>> >>> +       reduction, cpu hotplug and any other ways of reducing temperature. An
>> >>> +       ACPI version of this already exists(drivers/acpi/processor_thermal.c).
>> >>> +       This will be useful for platforms using the generic thermal interface
>> >>> +       and not the ACPI interface.
>> >>> +       If you want this support, you should say Y here.
>> >>> +
>> >>>  config SPEAR_THERMAL
>> >>>       bool "SPEAr thermal sensor driver"
>> >>>       depends on THERMAL
>> >>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>> >>> index fd9369a..aae59ad 100644
>> >>> --- a/drivers/thermal/Makefile
>> >>> +++ b/drivers/thermal/Makefile
>> >>> @@ -3,5 +3,6 @@
>> >>>  #
>> >>>
>> >>>  obj-$(CONFIG_THERMAL)                += thermal_sys.o
>> >>> +obj-$(CONFIG_CPU_THERMAL)            += cpu_cooling.o
>> >>>  obj-$(CONFIG_SPEAR_THERMAL)          += spear_thermal.o
>> >>>  obj-$(CONFIG_RCAR_THERMAL)   += rcar_thermal.o
>> >>> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
>> >>> new file mode 100644
>> >>> index 0000000..c42e557
>> >>> --- /dev/null
>> >>> +++ b/drivers/thermal/cpu_cooling.c
>> >>> @@ -0,0 +1,512 @@
>> >>> +/*
>> >>> + *  linux/drivers/thermal/cpu_cooling.c
>> >>> + *
>> >>> + *  Copyright (C) 2012       Samsung Electronics Co., Ltd(http://www.samsung.com)
>> >>> + *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
>> >>> + *
>> >>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> >>> + *  This program is free software; you can redistribute it and/or modify
>> >>> + *  it under the terms of the GNU General Public License as published by
>> >>> + *  the Free Software Foundation; version 2 of the License.
>> >>> + *
>> >>> + *  This program is distributed in the hope that it will be useful, but
>> >>> + *  WITHOUT ANY WARRANTY; without even the implied warranty of
>> >>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> >>> + *  General Public License for more details.
>> >>> + *
>> >>> + *  You should have received a copy of the GNU General Public License along
>> >>> + *  with this program; if not, write to the Free Software Foundation, Inc.,
>> >>> + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
>> >>> + *
>> >>> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> >>> + */
>> >>> +#include <linux/kernel.h>
>> >>> +#include <linux/module.h>
>> >>> +#include <linux/thermal.h>
>> >>> +#include <linux/platform_device.h>
>> >>> +#include <linux/cpufreq.h>
>> >>> +#include <linux/err.h>
>> >>> +#include <linux/slab.h>
>> >>> +#include <linux/cpu.h>
>> >>> +#include <linux/cpu_cooling.h>
>> >>> +
>> >>> +/**
>> >>> + * struct cpufreq_cooling_device
>> >>> + * @id: unique integer value corresponding to each cpufreq_cooling_device
>> >>> + *   registered.
>> >>> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
>> >>> + *   egistered cooling device.
>> >>> + * @cpufreq_state: integer value representing the current state of cpufreq
>> >>> + *   cooling devices.
>> >>> + * @cpufreq_val: integer value representing the absolute value of the clipped
>> >>> + *   frequency.
>> >>> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
>> >>> + * @node: list_head to link all cpufreq_cooling_device together.
>> >>> + *
>> >>> + * This structure is required for keeping information of each
>> >>> + * cpufreq_cooling_device registered as a list whose head is represented by
>> >>> + * cooling_cpufreq_list. In order to prevent corruption of this list a
>> >>> + * mutex lock cooling_cpufreq_lock is used.
>> >>> + */
>> >>> +struct cpufreq_cooling_device {
>> >>> +     int id;
>> >>> +     struct thermal_cooling_device *cool_dev;
>> >>> +     unsigned int cpufreq_state;
>> >>> +     unsigned int cpufreq_val;
>> >>> +     struct cpumask allowed_cpus;
>> >>> +     struct list_head node;
>> >>> +};
>> >>> +static LIST_HEAD(cooling_cpufreq_list);
>> >>> +static DEFINE_IDR(cpufreq_idr);
>> >>> +
>> >>> +static struct mutex cooling_cpufreq_lock;
>> >>> +
>> >>> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
>> >>> +#define NOTIFY_INVALID NULL
>> >>> +struct cpufreq_cooling_device *notify_device;
>> >>> +
>> >>> +/* Head of the blocking notifier chain to inform about frequency clamping */
>> >>> +static BLOCKING_NOTIFIER_HEAD(cputherm_state_notifier_list);
>> >>> +
>> >>> +/**
>> >>> + * get_idr - function to get a unique id.
>> >>> + * @idr: struct idr * handle used to create a id.
>> >>> + * @id: int * value generated by this function.
>> >>> + */
>> >>> +static int get_idr(struct idr *idr, int *id)
>> >>> +{
>> >>> +     int err;
>> >>> +again:
>> >>> +     if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
>> >>> +             return -ENOMEM;
>> >>> +
>> >>> +     mutex_lock(&cooling_cpufreq_lock);
>> >>> +     err = idr_get_new(idr, NULL, id);
>> >>> +     mutex_unlock(&cooling_cpufreq_lock);
>> >>> +
>> >>> +     if (unlikely(err == -EAGAIN))
>> >>> +             goto again;
>> >>> +     else if (unlikely(err))
>> >>> +             return err;
>> >>> +
>> >>> +     *id = *id & MAX_ID_MASK;
>> >>> +     return 0;
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * release_idr - function to free the unique id.
>> >>> + * @idr: struct idr * handle used for creating the id.
>> >>> + * @id: int value representing the unique id.
>> >>> + */
>> >>> +static void release_idr(struct idr *idr, int id)
>> >>> +{
>> >>> +     mutex_lock(&cooling_cpufreq_lock);
>> >>> +     idr_remove(idr, id);
>> >>> +     mutex_unlock(&cooling_cpufreq_lock);
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * cputherm_register_notifier - Register a notifier with cpu cooling interface.
>> >>> + * @nb:      struct notifier_block * with callback info.
>> >>> + * @list: integer value for which notification is needed. possible values are
>> >>> + *   CPUFREQ_COOLING_START and CPUFREQ_COOLING_STOP.
>> >>> + *
>> >>> + * This exported function registers a driver with cpu cooling layer. The driver
>> >>> + * will be notified when any cpu cooling action is called.
>> >>> + */
>> >>> +int cputherm_register_notifier(struct notifier_block *nb, unsigned int list)
>> >>> +{
>> >>> +     int ret = 0;
>> >>> +
>> >>> +     switch (list) {
>> >>> +     case CPUFREQ_COOLING_START:
>> >>> +     case CPUFREQ_COOLING_STOP:
>> >>> +             ret = blocking_notifier_chain_register(
>> >>> +                             &cputherm_state_notifier_list, nb);
>> >>> +             break;
>> >>> +     default:
>> >>> +             ret = -EINVAL;
>> >>> +     }
>> >>> +     return ret;
>> >>> +}
>> >>> +EXPORT_SYMBOL(cputherm_register_notifier);
>> >>> +
>> >>> +/**
>> >>> + * cputherm_unregister_notifier - Un-register a notifier.
>> >>> + * @nb:      struct notifier_block * with callback info.
>> >>> + * @list: integer value for which notification is needed. values possible are
>> >>> + *   CPUFREQ_COOLING_START or CPUFREQ_COOLING_STOP.
>> >>> + *
>> >>> + * This exported function un-registers a driver with cpu cooling layer.
>> >>> + */
>> >>> +int cputherm_unregister_notifier(struct notifier_block *nb, unsigned int list)
>> >>> +{
>> >>> +     int ret = 0;
>> >>> +
>> >>> +     switch (list) {
>> >>> +     case CPUFREQ_COOLING_START:
>> >>> +     case CPUFREQ_COOLING_STOP:
>> >>> +             ret = blocking_notifier_chain_unregister(
>> >>> +                             &cputherm_state_notifier_list, nb);
>> >>> +             break;
>> >>> +     default:
>> >>> +             ret = -EINVAL;
>> >>> +     }
>> >>> +     return ret;
>> >>> +}
>> >>> +EXPORT_SYMBOL(cputherm_unregister_notifier);
>> >>> +
>> >>> +/* Below code defines functions to be used for cpufreq as cooling device */
>> >>> +
>> >>> +/**
>> >>> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
>> >>> + * @cpu: cpu for which check is needed.
>> >>> + */
>> >>> +static int is_cpufreq_valid(int cpu)
>> >>> +{
>> >>> +     struct cpufreq_policy policy;
>> >>> +     return !cpufreq_get_policy(&policy, 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
>> >>> + */
>> >>> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
>> >>> +{
>> >>> +     int ret = 0, i = 0;
>> >>> +     unsigned long level_index;
>> >>> +     bool descend = false;
>> >>> +     struct cpufreq_frequency_table *table =
>> >>> +                                     cpufreq_frequency_get_table(cpu);
>> >>> +     if (!table)
>> >>> +             return ret;
>> >>> +
>> >>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> >>> +                     continue;
>> >>> +
>> >>> +             /*check if table in ascending or descending order*/
>> >>> +             if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
>> >>> +                     (table[i + 1].frequency < table[i].frequency)
>> >>> +                     && !descend) {
>> >>> +                     descend = true;
>> >>> +             }
>> >>> +
>> >>> +             /*return if level matched and table in descending order*/
>> >>> +             if (descend && i == level)
>> >>> +                     return table[i].frequency;
>> >>> +             i++;
>> >>> +     }
>> >>> +     i--;
>> >>> +
>> >>> +     if (level > i || descend)
>> >>> +             return ret;
>> >>> +     level_index = i - level;
>> >>> +
>> >>> +     /*Scan the table in reverse order and match the level*/
>> >>> +     while (i >= 0) {
>> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> >>> +                     continue;
>> >>> +             /*return if level matched*/
>> >>> +             if (i == level_index)
>> >>> +                     return table[i].frequency;
>> >>> +             i--;
>> >>> +     }
>> >>> +     return ret;
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * cpufreq_apply_cooling - function to apply frequency clipping.
>> >>> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
>> >>> + *   clipping data.
>> >>> + * @cooling_state: value of the cooling state.
>> >>> + */
>> >>> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
>> >>> +                             unsigned long cooling_state)
>> >>> +{
>> >>> +     unsigned int event, cpuid, clip_freq;
>> >>> +     struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
>> >>> +     unsigned int cpu = cpumask_any(maskPtr);
>> >>> +
>> >>> +
>> >>> +     /* Check if the old cooling action is same as new cooling action */
>> >>> +     if (cpufreq_device->cpufreq_state == cooling_state)
>> >>> +             return 0;
>> >>> +
>> >>> +     clip_freq = get_cpu_frequency(cpu, cooling_state);
>> >>> +     if (!clip_freq)
>> >>> +             return -EINVAL;
>> >>> +
>> >>> +     cpufreq_device->cpufreq_state = cooling_state;
>> >>> +     cpufreq_device->cpufreq_val = clip_freq;
>> >>> +     notify_device = cpufreq_device;
>> >>> +
>> >>> +     if (cooling_state != 0)
>> >>> +             event = CPUFREQ_COOLING_START;
>> >>> +     else
>> >>> +             event = CPUFREQ_COOLING_STOP;
>> >>> +
>> >>> +     blocking_notifier_call_chain(&cputherm_state_notifier_list,
>> >>> +                                             event, &clip_freq);
>> >>> +
>> >>> +     for_each_cpu(cpuid, maskPtr) {
>> >>> +             if (is_cpufreq_valid(cpuid))
>> >>> +                     cpufreq_update_policy(cpuid);
>> >>> +     }
>> >>> +
>> >>> +     notify_device = NOTIFY_INVALID;
>> >>> +
>> >>> +     return 0;
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
>> >>> + * @nb:      struct notifier_block * with callback info.
>> >>> + * @event: value showing cpufreq event for which this function invoked.
>> >>> + * @data: callback-specific data
>> >>> + */
>> >>> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
>> >>> +                                     unsigned long event, void *data)
>> >>> +{
>> >>> +     struct cpufreq_policy *policy = data;
>> >>> +     unsigned long max_freq = 0;
>> >>> +
>> >>> +     if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
>> >>> +             return 0;
>> >>> +
>> >>> +     if (cpumask_test_cpu(policy->cpu, &notify_device->allowed_cpus))
>> >>> +             max_freq = notify_device->cpufreq_val;
>> >>> +
>> >>> +     /* Never exceed user_policy.max*/
>> >>> +     if (max_freq > policy->user_policy.max)
>> >>> +             max_freq = policy->user_policy.max;
>> >>> +
>> >>> +     if (policy->max != max_freq)
>> >>> +             cpufreq_verify_within_limits(policy, 0, max_freq);
>> >>> +
>> >>> +     return 0;
>> >>> +}
>> >>> +
>> >>> +/*
>> >>> + * cpufreq cooling device callback functions are defined below
>> >>> + */
>> >>> +
>> >>> +/**
>> >>> + * cpufreq_get_max_state - callback function to get the max cooling state.
>> >>> + * @cdev: thermal cooling device pointer.
>> >>> + * @state: fill this variable with the max cooling state.
>> >>> + */
>> >>> +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;
>> >>> +     unsigned int cpu;
>> >>> +     struct cpufreq_frequency_table *table;
>> >>> +
>> >>> +     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;
>> >>> +     }
>> >>> +
>> >>> +     while (table[i].frequency != CPUFREQ_TABLE_END) {
>> >>> +             if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
>> >>> +                     continue;
>> >>> +             i++;
>> >>> +     }
>> >>> +     if (i > 0) {
>> >>> +             *state = --i;
>> >>> +             ret = 0;
>> >>> +     }
>> >>> +
>> >>> +return_get_max_state:
>> >>> +     mutex_unlock(&cooling_cpufreq_lock);
>> >>> +     return ret;
>> >>> +}
>> >>> +
>> >>> +/**
>> >>> + * cpufreq_get_cur_state - callback function to get the current cooling state.
>> >>> + * @cdev: thermal cooling device pointer.
>> >>> + * @state: fill this variable with the current cooling 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);
>> >>> +
>> >>
>> >> as cpufreq may be changed in other places, e.g. via sysfs I/F, we should
>> >> use the current cpu frequency to get the REAL cooling state, rather than
>> >> using a cached value.
>> >
>> > Yes agreed , I will repost with your suggestion.
>> >
>> > Thanks,
>> > Amit
>> >>
>> >> thanks,
>> >> rui
>> >>
>> >>
>> > --
>> > To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
>> > the body of a message to majordomo@vger.kernel.org
>> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>
>>
>>
>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html



-- 

Eduardo Valentin

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [lm-sensors] [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-08-20  9:36             ` Valentin, Eduardo
  0 siblings, 0 replies; 50+ messages in thread
From: Valentin, Eduardo @ 2012-08-20  9:36 UTC (permalink / raw)
  To: Zhang Rui
  Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
	linux-acpi, Kukjin Kim, Jean Delvare, Kyungmin Park, linux-pm,
	Guenter Roeck, Andrew Morton

SGVsbG8gUnVpLAoKT24gTW9uLCBBdWcgMjAsIDIwMTIgYXQgNToxNiBBTSwgWmhhbmcgUnVpIDxy
dWkuemhhbmdAaW50ZWwuY29tPiB3cm90ZToKPiBPbiDkupQsIDIwMTItMDgtMTcgYXQgMTE6NTYg
KzAzMDAsIFZhbGVudGluLCBFZHVhcmRvIHdyb3RlOgo+PiBIZWxsbywKPgo+PiA+Pj4gKwo+PiA+
Pj4gKwo+PiA+Pj4gKzEuMiBDUFUgY29vbGluZyBhY3Rpb24gbm90aWZpZXIgcmVnaXN0ZXIvdW5y
ZWdpc3RlciBpbnRlcmZhY2UKPj4gPj4+ICsxLjIuMSBpbnQgY3B1dGhlcm1fcmVnaXN0ZXJfbm90
aWZpZXIoc3RydWN0IG5vdGlmaWVyX2Jsb2NrICpuYiwKPj4gPj4+ICsgICAgIHVuc2lnbmVkIGlu
dCBsaXN0KQo+PiA+Pj4gKwo+PiA+Pj4gKyAgICBUaGlzIGludGVyZmFjZSByZWdpc3RlcnMgYSBk
cml2ZXIgd2l0aCBjcHUgY29vbGluZyBsYXllci4gVGhlIGRyaXZlciB3aWxsCj4+ID4+PiArICAg
IGJlIG5vdGlmaWVkIHdoZW4gYW55IGNwdSBjb29saW5nIGFjdGlvbiBpcyBjYWxsZWQuCj4+ID4+
PiArCj4+ID4+PiArICAgIG5iOiBub3RpZmllciBmdW5jdGlvbiB0byByZWdpc3Rlcgo+PiA+Pj4g
KyAgICBsaXN0OiBDUFVGUkVRX0NPT0xJTkdfU1RBUlQgb3IgQ1BVRlJFUV9DT09MSU5HX1NUT1AK
Pj4gPj4+ICsKPj4gPj4+ICsxLjIuMiBpbnQgY3B1dGhlcm1fdW5yZWdpc3Rlcl9ub3RpZmllcihz
dHJ1Y3Qgbm90aWZpZXJfYmxvY2sgKm5iLAo+PiA+Pj4gKyAgICAgdW5zaWduZWQgaW50IGxpc3Qp
Cj4+ID4+PiArCj4+ID4+PiArICAgIFRoaXMgaW50ZXJmYWNlIHJlZ2lzdGVycyBhIGRyaXZlciB3
aXRoIGNwdSBjb29saW5nIGxheWVyLiBUaGUgZHJpdmVyIHdpbGwKPj4gPj4+ICsgICAgYmUgbm90
aWZpZWQgd2hlbiBhbnkgY3B1IGNvb2xpbmcgYWN0aW9uIGlzIGNhbGxlZC4KPj4gPj4+ICsKPj4g
Pj4+ICsgICAgbmI6IG5vdGlmaWVyIGZ1bmN0aW9uIHRvIHJlZ2lzdGVyCj4+ID4+PiArICAgIGxp
c3Q6IENQVUZSRVFfQ09PTElOR19TVEFSVCBvciBDUFVGUkVRX0NPT0xJTkdfU1RPUAo+PiA+Pgo+
PiA+PiB3aGF0IGFyZSB0aGVzZSB0d28gQVBJcyB1c2VkIGZvcj8KPj4gPj4gSSBkaWQgbm90IHNl
ZSB0aGV5IGFyZSB1c2VkIGluIHlvdXIgcGF0Y2ggc2V0LCBkbyBJIG1pc3Mgc29tZXRoaW5nPwo+
PiA+IE5vIGN1cnJlbnRseSB0aGV5IGFyZSBub3QgdXNlZCBieSBteSBwYXRjaGVzLiBJIGFkZGVk
IHRoZW0gb24gcmVxdWVzdAo+PiA+IGZyb20gRWR1YXJkbyBhbmQgb3RoZXJzCj4+Cj4+IFllYWgs
IHRoaXMgd2FzIGEgc3VnZ2VzdGlvbiBhcyB3ZSBkaWRuJ3QgcmVhbGx5IGtub3cgaG93IHRoZSBG
VyBwYXJ0Cj4+IHdvdWxkIGV2b2x2ZSBieSB0aGF0IHRpbWUuCj4+Cj4+IFRoZSByZWFzb25pbmcg
aXMgdG8gYWxsb3cgYW55IGludGVyZXN0ZWQgdXNlciAoaW4ga2VybmVsKSB0byBiZQo+PiBub3Rp
ZmllZCB3aGVuIG1heCBmcmVxdWVuY3kgY2hhbmdlcy4KPgo+IGluIHRoaXMgY2FzZSwgdGhlIGNv
b2xpbmcgZGV2aWNlIHNob3VsZCBiZSB1cGRhdGVkLgo+IFNheSBhbGwgdGhlIHRhcmdldCBvZiB0
aGUgdGhlcm1hbF9pbnN0YW5jZXMgb2YgYSBjb29saW5nIGRldmljZXMgaXMgMCwKPiB3aGljaCBt
ZWFucyB0aGV5IHdhbnQgdGhlIGNwdSB0byBydW4gYXQgbWF4aW11bSBmcmVxdWVuY3ksIHdoZW4g
dGhlIG1heAo+IGZyZXF1ZW5jeSBjaGFuZ2VzLCB3ZSBzaG91bGQgc2V0IHRoZSBwcm9jZXNzb3Ig
dG8gdGhlIG5ldyBtYXggZnJlcXVlbmN5Cj4gYXMgd2VsbC4KPiBVc2luZyBub3RpZmljYXRpb24g
aXMgbm90IGEgZ29vZCBpZGVhIGFzIHVzZXIgY2FuIG5vdCBoYW5kbGUgdGhpcwo+IHdpdGhvdXQg
aW50ZXJhY3Rpbmcgd2l0aCB0aGUgdGhlcm1hbCBmcmFtZXdvcmsuCj4KPiBJTU8sIHdlIHNob3Vs
ZCBpbnRyb2R1Y2UgYSBuZXcgQVBJIHRvIGhhbmRsZSB0aGlzLCByYXRoZXIgdGhhbiBqdXN0Cj4g
bm90aWZpY2F0aW9ucyB0byB1c2Vycy4KCkFncmVlZC4gVGhlIGNvbnRleHQgaXMgYWN0dWFsbHkg
bXVjaCB3aWRlciB0aGFuIHRoZSBDUFUgY29vbGluZy4gSW4KZmFjdCwgY29vbGluZyBjby1wcm9j
ZXNzb3JzIGlzIGFjdHVhbGx5IHdoZXJlIHRoaW5ncyBnZXRzIGEgYml0CmNvbXBsaWNhdGVkLiBU
aGUgaWRlYSBiZWhpbmQgdGhpcyB0eXBlIG9mIGhhbmRzaGFraW5nIGlzIHRvIGFsbG93IHRoZQph
ZmZlY3RlZCBzdWJzeXN0ZW0gdG8gY2hhbmdlIHRoZWlyIGFjdHVhbCBzZXR1cCB3aGVuIG1heCBw
ZXJmb3JtYW5jZQppcyBub3QgYWxsb3dlZCBhbnltb3JlLgoKPgo+PiAgQWN0dWFsbHksIHRoZSB1
c2UgY2FzZSBiZWhpbmQKPj4gdGhpcyBpcyB0byBhbGxvdyBzdWNoIHVzZXJzIHRvIHBlcmZvcm0g
c29tZSBoYW5kc2hha2luZyBvciBzdG9wIHRoZWlyCj4+IGFjdGl2aXRpZXMgb3IgZXZlbiBjaGFu
Z2Ugc29tZSBwYXJhbWVudGVycywgaW4gY2FzZSB0aGUgbWF4IGZyZXF1ZW5jeQo+PiB3b3VsZCBj
aGFuZ2UuCj4KPiBJdCBpcyB0aGUgY29vbGluZyBkZXZpY2UgZHJpdmVyIHRoYXQgbm90aWNlIHRo
aXMgY2hhbmdlIGZpcnN0LCBhbmQgaXQKPiBzaG91bGQgaW52b2tlIHNvbWV0aGluZyBsaWtlIHRo
ZXJtYWxfY29vbGluZ19kZXZpY2VfdXBkYXRlL3JlYmluZCgpIHRvCj4gdGVsbCB0aGUgdGhlcm1h
bCBmcmFtZXdvcmsgdGhpcyBjaGFuZ2UuCj4KClllYWguIElkZWFsbHksIHRoZSBmcmFtZXdvcmsg
bmVlZHMgdG8gYmUgYXdhcmUgb2YgY29vbGluZyBkZXZpY2Ugc3RhdGUKY2hhbmdlLiBUb2RheSwg
YXMgd2UgaGF2ZSBwcmV0dHkgbXVjaCBhIGJyb2FkY2FzdC91bmlkaXJlY3Rpb25hbCB0eXBlCm9m
IG1lc3NhZ2luZywgdGhlIGZyYW1ld29yay9wb2xpY3kgYWx3YXlzIGFzc3VtZXMgdGhlIGNvb2xp
bmcgZGV2aWNlcwp3aWxsIGJlIGluIHN5bmMgd2l0aCB3aGF0IGl0IGlzIGRpY3RhdGVkIGJ5IHRo
ZSBwb2xpY3kuCgo+PiAgSWRlYWxseSBpdCB3b3VsZCBiZSBwb3NzaWJsZSB0byBuYWNrIHRoZSBj
b29saW5nCj4+IHRyYW5zaXRpb24uIEJ1dCB0aGF0IGlzIHlldCBhIHdpZGVyIGRpc2N1c3Npb24u
IFNvIGZhciB3ZSBkb24ndCBoYXZlCj4+IHVzZXJzIGZvciB0aGlzLgo+Pgo+IHllcC4KPiBJIHRo
b3VnaHQgYWJvdXQgdGhpcyBiZWZvcmUsIGJ1dCBJJ2QgcHJlZmVyIHRvIGRvIHRoaXMgd2hlbiB0
aGVyZSBpcyBhCj4gcmVhbCB1c2VyLiBPciBlbHNlLCB3ZSBhcmUga2luZCBvZiBvdmVyLWRlc2ln
bmluZyBoZXJlLgo+IGhvdyBhYm91dCByZW1vdmluZyB0aGlzIHBpZWNlIG9mIGNvZGUgZm9yIG5v
dz8KCgpBZ3JlZWQuIEkgc2Vjb25kIHlvdSB0aGF0IHRoaXMgcHJvYmxlbSBpcyBhIG11Y2ggd2lk
ZXIgaXNzdWUgYW5kIG5lZWRzCmltcHJvdmVtZW50IGluIHRoZSBGVyBpdHNlbGYgYW5kIGhvdyB0
aGUgY29vbGluZyBkZXZpY2VzIGFyZSBkZXNpZ25lZC4KCj4KPiB0aGFua3MsCj4gcnVpCj4KPj4g
Pj4KPj4gPj4+IGRpZmYgLS1naXQgYS9kcml2ZXJzL3RoZXJtYWwvS2NvbmZpZyBiL2RyaXZlcnMv
dGhlcm1hbC9LY29uZmlnCj4+ID4+PiBpbmRleCA3ZGQ4YzM0Li45OTYwMDNiIDEwMDY0NAo+PiA+
Pj4gLS0tIGEvZHJpdmVycy90aGVybWFsL0tjb25maWcKPj4gPj4+ICsrKyBiL2RyaXZlcnMvdGhl
cm1hbC9LY29uZmlnCj4+ID4+PiBAQCAtMTksNiArMTksMTcgQEAgY29uZmlnIFRIRVJNQUxfSFdN
T04KPj4gPj4+ICAgICAgIGRlcGVuZHMgb24gSFdNT049eSB8fCBIV01PTj1USEVSTUFMCj4+ID4+
PiAgICAgICBkZWZhdWx0IHkKPj4gPj4+Cj4+ID4+PiArY29uZmlnIENQVV9USEVSTUFMCj4+ID4+
PiArICAgICBib29sICJnZW5lcmljIGNwdSBjb29saW5nIHN1cHBvcnQiCj4+ID4+PiArICAgICBk
ZXBlbmRzIG9uIFRIRVJNQUwgJiYgQ1BVX0ZSRVEKPj4gPj4+ICsgICAgIGhlbHAKPj4gPj4+ICsg
ICAgICAgVGhpcyBpbXBsZW1lbnRzIHRoZSBnZW5lcmljIGNwdSBjb29saW5nIG1lY2hhbmlzbSB0
aHJvdWdoIGZyZXF1ZW5jeQo+PiA+Pj4gKyAgICAgICByZWR1Y3Rpb24sIGNwdSBob3RwbHVnIGFu
ZCBhbnkgb3RoZXIgd2F5cyBvZiByZWR1Y2luZyB0ZW1wZXJhdHVyZS4gQW4KPj4gPj4+ICsgICAg
ICAgQUNQSSB2ZXJzaW9uIG9mIHRoaXMgYWxyZWFkeSBleGlzdHMoZHJpdmVycy9hY3BpL3Byb2Nl
c3Nvcl90aGVybWFsLmMpLgo+PiA+Pj4gKyAgICAgICBUaGlzIHdpbGwgYmUgdXNlZnVsIGZvciBw
bGF0Zm9ybXMgdXNpbmcgdGhlIGdlbmVyaWMgdGhlcm1hbCBpbnRlcmZhY2UKPj4gPj4+ICsgICAg
ICAgYW5kIG5vdCB0aGUgQUNQSSBpbnRlcmZhY2UuCj4+ID4+PiArICAgICAgIElmIHlvdSB3YW50
IHRoaXMgc3VwcG9ydCwgeW91IHNob3VsZCBzYXkgWSBoZXJlLgo+PiA+Pj4gKwo+PiA+Pj4gIGNv
bmZpZyBTUEVBUl9USEVSTUFMCj4+ID4+PiAgICAgICBib29sICJTUEVBciB0aGVybWFsIHNlbnNv
ciBkcml2ZXIiCj4+ID4+PiAgICAgICBkZXBlbmRzIG9uIFRIRVJNQUwKPj4gPj4+IGRpZmYgLS1n
aXQgYS9kcml2ZXJzL3RoZXJtYWwvTWFrZWZpbGUgYi9kcml2ZXJzL3RoZXJtYWwvTWFrZWZpbGUK
Pj4gPj4+IGluZGV4IGZkOTM2OWEuLmFhZTU5YWQgMTAwNjQ0Cj4+ID4+PiAtLS0gYS9kcml2ZXJz
L3RoZXJtYWwvTWFrZWZpbGUKPj4gPj4+ICsrKyBiL2RyaXZlcnMvdGhlcm1hbC9NYWtlZmlsZQo+
PiA+Pj4gQEAgLTMsNSArMyw2IEBACj4+ID4+PiAgIwo+PiA+Pj4KPj4gPj4+ICBvYmotJChDT05G
SUdfVEhFUk1BTCkgICAgICAgICAgICAgICAgKz0gdGhlcm1hbF9zeXMubwo+PiA+Pj4gK29iai0k
KENPTkZJR19DUFVfVEhFUk1BTCkgICAgICAgICAgICArPSBjcHVfY29vbGluZy5vCj4+ID4+PiAg
b2JqLSQoQ09ORklHX1NQRUFSX1RIRVJNQUwpICAgICAgICAgICs9IHNwZWFyX3RoZXJtYWwubwo+
PiA+Pj4gIG9iai0kKENPTkZJR19SQ0FSX1RIRVJNQUwpICAgKz0gcmNhcl90aGVybWFsLm8KPj4g
Pj4+IGRpZmYgLS1naXQgYS9kcml2ZXJzL3RoZXJtYWwvY3B1X2Nvb2xpbmcuYyBiL2RyaXZlcnMv
dGhlcm1hbC9jcHVfY29vbGluZy5jCj4+ID4+PiBuZXcgZmlsZSBtb2RlIDEwMDY0NAo+PiA+Pj4g
aW5kZXggMDAwMDAwMC4uYzQyZTU1Nwo+PiA+Pj4gLS0tIC9kZXYvbnVsbAo+PiA+Pj4gKysrIGIv
ZHJpdmVycy90aGVybWFsL2NwdV9jb29saW5nLmMKPj4gPj4+IEBAIC0wLDAgKzEsNTEyIEBACj4+
ID4+PiArLyoKPj4gPj4+ICsgKiAgbGludXgvZHJpdmVycy90aGVybWFsL2NwdV9jb29saW5nLmMK
Pj4gPj4+ICsgKgo+PiA+Pj4gKyAqICBDb3B5cmlnaHQgKEMpIDIwMTIgICAgICAgU2Ftc3VuZyBF
bGVjdHJvbmljcyBDby4sIEx0ZChodHRwOi8vd3d3LnNhbXN1bmcuY29tKQo+PiA+Pj4gKyAqICBD
b3B5cmlnaHQgKEMpIDIwMTIgIEFtaXQgRGFuaWVsIDxhbWl0LmthY2hoYXBAbGluYXJvLm9yZz4K
Pj4gPj4+ICsgKgo+PiA+Pj4gKyAqIH5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+Cj4+ID4+PiArICogIFRoaXMg
cHJvZ3JhbSBpcyBmcmVlIHNvZnR3YXJlOyB5b3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQvb3Ig
bW9kaWZ5Cj4+ID4+PiArICogIGl0IHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgR05VIEdlbmVyYWwg
UHVibGljIExpY2Vuc2UgYXMgcHVibGlzaGVkIGJ5Cj4+ID4+PiArICogIHRoZSBGcmVlIFNvZnR3
YXJlIEZvdW5kYXRpb247IHZlcnNpb24gMiBvZiB0aGUgTGljZW5zZS4KPj4gPj4+ICsgKgo+PiA+
Pj4gKyAqICBUaGlzIHByb2dyYW0gaXMgZGlzdHJpYnV0ZWQgaW4gdGhlIGhvcGUgdGhhdCBpdCB3
aWxsIGJlIHVzZWZ1bCwgYnV0Cj4+ID4+PiArICogIFdJVEhPVVQgQU5ZIFdBUlJBTlRZOyB3aXRo
b3V0IGV2ZW4gdGhlIGltcGxpZWQgd2FycmFudHkgb2YKPj4gPj4+ICsgKiAgTUVSQ0hBTlRBQklM
SVRZIG9yIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFLiAgU2VlIHRoZSBHTlUKPj4g
Pj4+ICsgKiAgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgo+PiA+Pj4g
KyAqCj4+ID4+PiArICogIFlvdSBzaG91bGQgaGF2ZSByZWNlaXZlZCBhIGNvcHkgb2YgdGhlIEdO
VSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFsb25nCj4+ID4+PiArICogIHdpdGggdGhpcyBwcm9n
cmFtOyBpZiBub3QsIHdyaXRlIHRvIHRoZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb24sIEluYy4s
Cj4+ID4+PiArICogIDU5IFRlbXBsZSBQbGFjZSwgU3VpdGUgMzMwLCBCb3N0b24sIE1BIDAyMTEx
LTEzMDcgVVNBLgo+PiA+Pj4gKyAqCj4+ID4+PiArICogfn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+
fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn4KPj4gPj4+
ICsgKi8KPj4gPj4+ICsjaW5jbHVkZSA8bGludXgva2VybmVsLmg+Cj4+ID4+PiArI2luY2x1ZGUg
PGxpbnV4L21vZHVsZS5oPgo+PiA+Pj4gKyNpbmNsdWRlIDxsaW51eC90aGVybWFsLmg+Cj4+ID4+
PiArI2luY2x1ZGUgPGxpbnV4L3BsYXRmb3JtX2RldmljZS5oPgo+PiA+Pj4gKyNpbmNsdWRlIDxs
aW51eC9jcHVmcmVxLmg+Cj4+ID4+PiArI2luY2x1ZGUgPGxpbnV4L2Vyci5oPgo+PiA+Pj4gKyNp
bmNsdWRlIDxsaW51eC9zbGFiLmg+Cj4+ID4+PiArI2luY2x1ZGUgPGxpbnV4L2NwdS5oPgo+PiA+
Pj4gKyNpbmNsdWRlIDxsaW51eC9jcHVfY29vbGluZy5oPgo+PiA+Pj4gKwo+PiA+Pj4gKy8qKgo+
PiA+Pj4gKyAqIHN0cnVjdCBjcHVmcmVxX2Nvb2xpbmdfZGV2aWNlCj4+ID4+PiArICogQGlkOiB1
bmlxdWUgaW50ZWdlciB2YWx1ZSBjb3JyZXNwb25kaW5nIHRvIGVhY2ggY3B1ZnJlcV9jb29saW5n
X2RldmljZQo+PiA+Pj4gKyAqICAgcmVnaXN0ZXJlZC4KPj4gPj4+ICsgKiBAY29vbF9kZXY6IHRo
ZXJtYWxfY29vbGluZ19kZXZpY2UgcG9pbnRlciB0byBrZWVwIHRyYWNrIG9mIHRoZSB0aGUKPj4g
Pj4+ICsgKiAgIGVnaXN0ZXJlZCBjb29saW5nIGRldmljZS4KPj4gPj4+ICsgKiBAY3B1ZnJlcV9z
dGF0ZTogaW50ZWdlciB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIGN1cnJlbnQgc3RhdGUgb2YgY3B1
ZnJlcQo+PiA+Pj4gKyAqICAgY29vbGluZyBkZXZpY2VzLgo+PiA+Pj4gKyAqIEBjcHVmcmVxX3Zh
bDogaW50ZWdlciB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIGFic29sdXRlIHZhbHVlIG9mIHRoZSBj
bGlwcGVkCj4+ID4+PiArICogICBmcmVxdWVuY3kuCj4+ID4+PiArICogQGFsbG93ZWRfY3B1czog
YWxsIHRoZSBjcHVzIGludm9sdmVkIGZvciB0aGlzIGNwdWZyZXFfY29vbGluZ19kZXZpY2UuCj4+
ID4+PiArICogQG5vZGU6IGxpc3RfaGVhZCB0byBsaW5rIGFsbCBjcHVmcmVxX2Nvb2xpbmdfZGV2
aWNlIHRvZ2V0aGVyLgo+PiA+Pj4gKyAqCj4+ID4+PiArICogVGhpcyBzdHJ1Y3R1cmUgaXMgcmVx
dWlyZWQgZm9yIGtlZXBpbmcgaW5mb3JtYXRpb24gb2YgZWFjaAo+PiA+Pj4gKyAqIGNwdWZyZXFf
Y29vbGluZ19kZXZpY2UgcmVnaXN0ZXJlZCBhcyBhIGxpc3Qgd2hvc2UgaGVhZCBpcyByZXByZXNl
bnRlZCBieQo+PiA+Pj4gKyAqIGNvb2xpbmdfY3B1ZnJlcV9saXN0LiBJbiBvcmRlciB0byBwcmV2
ZW50IGNvcnJ1cHRpb24gb2YgdGhpcyBsaXN0IGEKPj4gPj4+ICsgKiBtdXRleCBsb2NrIGNvb2xp
bmdfY3B1ZnJlcV9sb2NrIGlzIHVzZWQuCj4+ID4+PiArICovCj4+ID4+PiArc3RydWN0IGNwdWZy
ZXFfY29vbGluZ19kZXZpY2Ugewo+PiA+Pj4gKyAgICAgaW50IGlkOwo+PiA+Pj4gKyAgICAgc3Ry
dWN0IHRoZXJtYWxfY29vbGluZ19kZXZpY2UgKmNvb2xfZGV2Owo+PiA+Pj4gKyAgICAgdW5zaWdu
ZWQgaW50IGNwdWZyZXFfc3RhdGU7Cj4+ID4+PiArICAgICB1bnNpZ25lZCBpbnQgY3B1ZnJlcV92
YWw7Cj4+ID4+PiArICAgICBzdHJ1Y3QgY3B1bWFzayBhbGxvd2VkX2NwdXM7Cj4+ID4+PiArICAg
ICBzdHJ1Y3QgbGlzdF9oZWFkIG5vZGU7Cj4+ID4+PiArfTsKPj4gPj4+ICtzdGF0aWMgTElTVF9I
RUFEKGNvb2xpbmdfY3B1ZnJlcV9saXN0KTsKPj4gPj4+ICtzdGF0aWMgREVGSU5FX0lEUihjcHVm
cmVxX2lkcik7Cj4+ID4+PiArCj4+ID4+PiArc3RhdGljIHN0cnVjdCBtdXRleCBjb29saW5nX2Nw
dWZyZXFfbG9jazsKPj4gPj4+ICsKPj4gPj4+ICsvKiBub3RpZnlfdGFibGUgcGFzc2VzIHZhbHVl
IHRvIHRoZSBDUFVGUkVRX0FESlVTVCBjYWxsYmFjayBmdW5jdGlvbi4gKi8KPj4gPj4+ICsjZGVm
aW5lIE5PVElGWV9JTlZBTElEIE5VTEwKPj4gPj4+ICtzdHJ1Y3QgY3B1ZnJlcV9jb29saW5nX2Rl
dmljZSAqbm90aWZ5X2RldmljZTsKPj4gPj4+ICsKPj4gPj4+ICsvKiBIZWFkIG9mIHRoZSBibG9j
a2luZyBub3RpZmllciBjaGFpbiB0byBpbmZvcm0gYWJvdXQgZnJlcXVlbmN5IGNsYW1waW5nICov
Cj4+ID4+PiArc3RhdGljIEJMT0NLSU5HX05PVElGSUVSX0hFQUQoY3B1dGhlcm1fc3RhdGVfbm90
aWZpZXJfbGlzdCk7Cj4+ID4+PiArCj4+ID4+PiArLyoqCj4+ID4+PiArICogZ2V0X2lkciAtIGZ1
bmN0aW9uIHRvIGdldCBhIHVuaXF1ZSBpZC4KPj4gPj4+ICsgKiBAaWRyOiBzdHJ1Y3QgaWRyICog
aGFuZGxlIHVzZWQgdG8gY3JlYXRlIGEgaWQuCj4+ID4+PiArICogQGlkOiBpbnQgKiB2YWx1ZSBn
ZW5lcmF0ZWQgYnkgdGhpcyBmdW5jdGlvbi4KPj4gPj4+ICsgKi8KPj4gPj4+ICtzdGF0aWMgaW50
IGdldF9pZHIoc3RydWN0IGlkciAqaWRyLCBpbnQgKmlkKQo+PiA+Pj4gK3sKPj4gPj4+ICsgICAg
IGludCBlcnI7Cj4+ID4+PiArYWdhaW46Cj4+ID4+PiArICAgICBpZiAodW5saWtlbHkoaWRyX3By
ZV9nZXQoaWRyLCBHRlBfS0VSTkVMKSA9PSAwKSkKPj4gPj4+ICsgICAgICAgICAgICAgcmV0dXJu
IC1FTk9NRU07Cj4+ID4+PiArCj4+ID4+PiArICAgICBtdXRleF9sb2NrKCZjb29saW5nX2NwdWZy
ZXFfbG9jayk7Cj4+ID4+PiArICAgICBlcnIgPSBpZHJfZ2V0X25ldyhpZHIsIE5VTEwsIGlkKTsK
Pj4gPj4+ICsgICAgIG11dGV4X3VubG9jaygmY29vbGluZ19jcHVmcmVxX2xvY2spOwo+PiA+Pj4g
Kwo+PiA+Pj4gKyAgICAgaWYgKHVubGlrZWx5KGVyciA9PSAtRUFHQUlOKSkKPj4gPj4+ICsgICAg
ICAgICAgICAgZ290byBhZ2FpbjsKPj4gPj4+ICsgICAgIGVsc2UgaWYgKHVubGlrZWx5KGVycikp
Cj4+ID4+PiArICAgICAgICAgICAgIHJldHVybiBlcnI7Cj4+ID4+PiArCj4+ID4+PiArICAgICAq
aWQgPSAqaWQgJiBNQVhfSURfTUFTSzsKPj4gPj4+ICsgICAgIHJldHVybiAwOwo+PiA+Pj4gK30K
Pj4gPj4+ICsKPj4gPj4+ICsvKioKPj4gPj4+ICsgKiByZWxlYXNlX2lkciAtIGZ1bmN0aW9uIHRv
IGZyZWUgdGhlIHVuaXF1ZSBpZC4KPj4gPj4+ICsgKiBAaWRyOiBzdHJ1Y3QgaWRyICogaGFuZGxl
IHVzZWQgZm9yIGNyZWF0aW5nIHRoZSBpZC4KPj4gPj4+ICsgKiBAaWQ6IGludCB2YWx1ZSByZXBy
ZXNlbnRpbmcgdGhlIHVuaXF1ZSBpZC4KPj4gPj4+ICsgKi8KPj4gPj4+ICtzdGF0aWMgdm9pZCBy
ZWxlYXNlX2lkcihzdHJ1Y3QgaWRyICppZHIsIGludCBpZCkKPj4gPj4+ICt7Cj4+ID4+PiArICAg
ICBtdXRleF9sb2NrKCZjb29saW5nX2NwdWZyZXFfbG9jayk7Cj4+ID4+PiArICAgICBpZHJfcmVt
b3ZlKGlkciwgaWQpOwo+PiA+Pj4gKyAgICAgbXV0ZXhfdW5sb2NrKCZjb29saW5nX2NwdWZyZXFf
bG9jayk7Cj4+ID4+PiArfQo+PiA+Pj4gKwo+PiA+Pj4gKy8qKgo+PiA+Pj4gKyAqIGNwdXRoZXJt
X3JlZ2lzdGVyX25vdGlmaWVyIC0gUmVnaXN0ZXIgYSBub3RpZmllciB3aXRoIGNwdSBjb29saW5n
IGludGVyZmFjZS4KPj4gPj4+ICsgKiBAbmI6ICAgICAgc3RydWN0IG5vdGlmaWVyX2Jsb2NrICog
d2l0aCBjYWxsYmFjayBpbmZvLgo+PiA+Pj4gKyAqIEBsaXN0OiBpbnRlZ2VyIHZhbHVlIGZvciB3
aGljaCBub3RpZmljYXRpb24gaXMgbmVlZGVkLiBwb3NzaWJsZSB2YWx1ZXMgYXJlCj4+ID4+PiAr
ICogICBDUFVGUkVRX0NPT0xJTkdfU1RBUlQgYW5kIENQVUZSRVFfQ09PTElOR19TVE9QLgo+PiA+
Pj4gKyAqCj4+ID4+PiArICogVGhpcyBleHBvcnRlZCBmdW5jdGlvbiByZWdpc3RlcnMgYSBkcml2
ZXIgd2l0aCBjcHUgY29vbGluZyBsYXllci4gVGhlIGRyaXZlcgo+PiA+Pj4gKyAqIHdpbGwgYmUg
bm90aWZpZWQgd2hlbiBhbnkgY3B1IGNvb2xpbmcgYWN0aW9uIGlzIGNhbGxlZC4KPj4gPj4+ICsg
Ki8KPj4gPj4+ICtpbnQgY3B1dGhlcm1fcmVnaXN0ZXJfbm90aWZpZXIoc3RydWN0IG5vdGlmaWVy
X2Jsb2NrICpuYiwgdW5zaWduZWQgaW50IGxpc3QpCj4+ID4+PiArewo+PiA+Pj4gKyAgICAgaW50
IHJldCA9IDA7Cj4+ID4+PiArCj4+ID4+PiArICAgICBzd2l0Y2ggKGxpc3QpIHsKPj4gPj4+ICsg
ICAgIGNhc2UgQ1BVRlJFUV9DT09MSU5HX1NUQVJUOgo+PiA+Pj4gKyAgICAgY2FzZSBDUFVGUkVR
X0NPT0xJTkdfU1RPUDoKPj4gPj4+ICsgICAgICAgICAgICAgcmV0ID0gYmxvY2tpbmdfbm90aWZp
ZXJfY2hhaW5fcmVnaXN0ZXIoCj4+ID4+PiArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAm
Y3B1dGhlcm1fc3RhdGVfbm90aWZpZXJfbGlzdCwgbmIpOwo+PiA+Pj4gKyAgICAgICAgICAgICBi
cmVhazsKPj4gPj4+ICsgICAgIGRlZmF1bHQ6Cj4+ID4+PiArICAgICAgICAgICAgIHJldCA9IC1F
SU5WQUw7Cj4+ID4+PiArICAgICB9Cj4+ID4+PiArICAgICByZXR1cm4gcmV0Owo+PiA+Pj4gK30K
Pj4gPj4+ICtFWFBPUlRfU1lNQk9MKGNwdXRoZXJtX3JlZ2lzdGVyX25vdGlmaWVyKTsKPj4gPj4+
ICsKPj4gPj4+ICsvKioKPj4gPj4+ICsgKiBjcHV0aGVybV91bnJlZ2lzdGVyX25vdGlmaWVyIC0g
VW4tcmVnaXN0ZXIgYSBub3RpZmllci4KPj4gPj4+ICsgKiBAbmI6ICAgICAgc3RydWN0IG5vdGlm
aWVyX2Jsb2NrICogd2l0aCBjYWxsYmFjayBpbmZvLgo+PiA+Pj4gKyAqIEBsaXN0OiBpbnRlZ2Vy
IHZhbHVlIGZvciB3aGljaCBub3RpZmljYXRpb24gaXMgbmVlZGVkLiB2YWx1ZXMgcG9zc2libGUg
YXJlCj4+ID4+PiArICogICBDUFVGUkVRX0NPT0xJTkdfU1RBUlQgb3IgQ1BVRlJFUV9DT09MSU5H
X1NUT1AuCj4+ID4+PiArICoKPj4gPj4+ICsgKiBUaGlzIGV4cG9ydGVkIGZ1bmN0aW9uIHVuLXJl
Z2lzdGVycyBhIGRyaXZlciB3aXRoIGNwdSBjb29saW5nIGxheWVyLgo+PiA+Pj4gKyAqLwo+PiA+
Pj4gK2ludCBjcHV0aGVybV91bnJlZ2lzdGVyX25vdGlmaWVyKHN0cnVjdCBub3RpZmllcl9ibG9j
ayAqbmIsIHVuc2lnbmVkIGludCBsaXN0KQo+PiA+Pj4gK3sKPj4gPj4+ICsgICAgIGludCByZXQg
PSAwOwo+PiA+Pj4gKwo+PiA+Pj4gKyAgICAgc3dpdGNoIChsaXN0KSB7Cj4+ID4+PiArICAgICBj
YXNlIENQVUZSRVFfQ09PTElOR19TVEFSVDoKPj4gPj4+ICsgICAgIGNhc2UgQ1BVRlJFUV9DT09M
SU5HX1NUT1A6Cj4+ID4+PiArICAgICAgICAgICAgIHJldCA9IGJsb2NraW5nX25vdGlmaWVyX2No
YWluX3VucmVnaXN0ZXIoCj4+ID4+PiArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmY3B1
dGhlcm1fc3RhdGVfbm90aWZpZXJfbGlzdCwgbmIpOwo+PiA+Pj4gKyAgICAgICAgICAgICBicmVh
azsKPj4gPj4+ICsgICAgIGRlZmF1bHQ6Cj4+ID4+PiArICAgICAgICAgICAgIHJldCA9IC1FSU5W
QUw7Cj4+ID4+PiArICAgICB9Cj4+ID4+PiArICAgICByZXR1cm4gcmV0Owo+PiA+Pj4gK30KPj4g
Pj4+ICtFWFBPUlRfU1lNQk9MKGNwdXRoZXJtX3VucmVnaXN0ZXJfbm90aWZpZXIpOwo+PiA+Pj4g
Kwo+PiA+Pj4gKy8qIEJlbG93IGNvZGUgZGVmaW5lcyBmdW5jdGlvbnMgdG8gYmUgdXNlZCBmb3Ig
Y3B1ZnJlcSBhcyBjb29saW5nIGRldmljZSAqLwo+PiA+Pj4gKwo+PiA+Pj4gKy8qKgo+PiA+Pj4g
KyAqIGlzX2NwdWZyZXFfdmFsaWQgLSBmdW5jdGlvbiB0byBjaGVjayBpZiBhIGNwdSBoYXMgZnJl
cXVlbmN5IHRyYW5zaXRpb24gcG9saWN5Lgo+PiA+Pj4gKyAqIEBjcHU6IGNwdSBmb3Igd2hpY2gg
Y2hlY2sgaXMgbmVlZGVkLgo+PiA+Pj4gKyAqLwo+PiA+Pj4gK3N0YXRpYyBpbnQgaXNfY3B1ZnJl
cV92YWxpZChpbnQgY3B1KQo+PiA+Pj4gK3sKPj4gPj4+ICsgICAgIHN0cnVjdCBjcHVmcmVxX3Bv
bGljeSBwb2xpY3k7Cj4+ID4+PiArICAgICByZXR1cm4gIWNwdWZyZXFfZ2V0X3BvbGljeSgmcG9s
aWN5LCBjcHUpOwo+PiA+Pj4gK30KPj4gPj4+ICsKPj4gPj4+ICsvKioKPj4gPj4+ICsgKiBnZXRf
Y3B1X2ZyZXF1ZW5jeSAtIGdldCB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgZnJlcXVlbmN5IGZyb20g
bGV2ZWwuCj4+ID4+PiArICogQGNwdTogY3B1IGZvciB3aGljaCBmcmVxdWVuY3kgaXMgZmV0Y2hl
ZC4KPj4gPj4+ICsgKiBAbGV2ZWw6IGxldmVsIG9mIGZyZXF1ZW5jeSBvZiB0aGUgQ1BVCj4+ID4+
PiArICogICBlLmcgbGV2ZWw9MSAtLT4gMXN0IE1BWCBGUkVRLCBMRVZFTD0yIC0tLT4gMm5kIE1B
WCBGUkVRLCAuLi4uIGV0Ywo+PiA+Pj4gKyAqLwo+PiA+Pj4gK3N0YXRpYyB1bnNpZ25lZCBpbnQg
Z2V0X2NwdV9mcmVxdWVuY3kodW5zaWduZWQgaW50IGNwdSwgdW5zaWduZWQgbG9uZyBsZXZlbCkK
Pj4gPj4+ICt7Cj4+ID4+PiArICAgICBpbnQgcmV0ID0gMCwgaSA9IDA7Cj4+ID4+PiArICAgICB1
bnNpZ25lZCBsb25nIGxldmVsX2luZGV4Owo+PiA+Pj4gKyAgICAgYm9vbCBkZXNjZW5kID0gZmFs
c2U7Cj4+ID4+PiArICAgICBzdHJ1Y3QgY3B1ZnJlcV9mcmVxdWVuY3lfdGFibGUgKnRhYmxlID0K
Pj4gPj4+ICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3B1ZnJlcV9mcmVx
dWVuY3lfZ2V0X3RhYmxlKGNwdSk7Cj4+ID4+PiArICAgICBpZiAoIXRhYmxlKQo+PiA+Pj4gKyAg
ICAgICAgICAgICByZXR1cm4gcmV0Owo+PiA+Pj4gKwo+PiA+Pj4gKyAgICAgd2hpbGUgKHRhYmxl
W2ldLmZyZXF1ZW5jeSAhPSBDUFVGUkVRX1RBQkxFX0VORCkgewo+PiA+Pj4gKyAgICAgICAgICAg
ICBpZiAodGFibGVbaV0uZnJlcXVlbmN5ID09IENQVUZSRVFfRU5UUllfSU5WQUxJRCkKPj4gPj4+
ICsgICAgICAgICAgICAgICAgICAgICBjb250aW51ZTsKPj4gPj4+ICsKPj4gPj4+ICsgICAgICAg
ICAgICAgLypjaGVjayBpZiB0YWJsZSBpbiBhc2NlbmRpbmcgb3IgZGVzY2VuZGluZyBvcmRlciov
Cj4+ID4+PiArICAgICAgICAgICAgIGlmICgodGFibGVbaSArIDFdLmZyZXF1ZW5jeSAhPSBDUFVG
UkVRX1RBQkxFX0VORCkgJiYKPj4gPj4+ICsgICAgICAgICAgICAgICAgICAgICAodGFibGVbaSAr
IDFdLmZyZXF1ZW5jeSA8IHRhYmxlW2ldLmZyZXF1ZW5jeSkKPj4gPj4+ICsgICAgICAgICAgICAg
ICAgICAgICAmJiAhZGVzY2VuZCkgewo+PiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgIGRlc2Nl
bmQgPSB0cnVlOwo+PiA+Pj4gKyAgICAgICAgICAgICB9Cj4+ID4+PiArCj4+ID4+PiArICAgICAg
ICAgICAgIC8qcmV0dXJuIGlmIGxldmVsIG1hdGNoZWQgYW5kIHRhYmxlIGluIGRlc2NlbmRpbmcg
b3JkZXIqLwo+PiA+Pj4gKyAgICAgICAgICAgICBpZiAoZGVzY2VuZCAmJiBpID09IGxldmVsKQo+
PiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgIHJldHVybiB0YWJsZVtpXS5mcmVxdWVuY3k7Cj4+
ID4+PiArICAgICAgICAgICAgIGkrKzsKPj4gPj4+ICsgICAgIH0KPj4gPj4+ICsgICAgIGktLTsK
Pj4gPj4+ICsKPj4gPj4+ICsgICAgIGlmIChsZXZlbCA+IGkgfHwgZGVzY2VuZCkKPj4gPj4+ICsg
ICAgICAgICAgICAgcmV0dXJuIHJldDsKPj4gPj4+ICsgICAgIGxldmVsX2luZGV4ID0gaSAtIGxl
dmVsOwo+PiA+Pj4gKwo+PiA+Pj4gKyAgICAgLypTY2FuIHRoZSB0YWJsZSBpbiByZXZlcnNlIG9y
ZGVyIGFuZCBtYXRjaCB0aGUgbGV2ZWwqLwo+PiA+Pj4gKyAgICAgd2hpbGUgKGkgPj0gMCkgewo+
PiA+Pj4gKyAgICAgICAgICAgICBpZiAodGFibGVbaV0uZnJlcXVlbmN5ID09IENQVUZSRVFfRU5U
UllfSU5WQUxJRCkKPj4gPj4+ICsgICAgICAgICAgICAgICAgICAgICBjb250aW51ZTsKPj4gPj4+
ICsgICAgICAgICAgICAgLypyZXR1cm4gaWYgbGV2ZWwgbWF0Y2hlZCovCj4+ID4+PiArICAgICAg
ICAgICAgIGlmIChpID09IGxldmVsX2luZGV4KQo+PiA+Pj4gKyAgICAgICAgICAgICAgICAgICAg
IHJldHVybiB0YWJsZVtpXS5mcmVxdWVuY3k7Cj4+ID4+PiArICAgICAgICAgICAgIGktLTsKPj4g
Pj4+ICsgICAgIH0KPj4gPj4+ICsgICAgIHJldHVybiByZXQ7Cj4+ID4+PiArfQo+PiA+Pj4gKwo+
PiA+Pj4gKy8qKgo+PiA+Pj4gKyAqIGNwdWZyZXFfYXBwbHlfY29vbGluZyAtIGZ1bmN0aW9uIHRv
IGFwcGx5IGZyZXF1ZW5jeSBjbGlwcGluZy4KPj4gPj4+ICsgKiBAY3B1ZnJlcV9kZXZpY2U6IGNw
dWZyZXFfY29vbGluZ19kZXZpY2UgcG9pbnRlciBjb250YWluaW5nIGZyZXF1ZW5jeQo+PiA+Pj4g
KyAqICAgY2xpcHBpbmcgZGF0YS4KPj4gPj4+ICsgKiBAY29vbGluZ19zdGF0ZTogdmFsdWUgb2Yg
dGhlIGNvb2xpbmcgc3RhdGUuCj4+ID4+PiArICovCj4+ID4+PiArc3RhdGljIGludCBjcHVmcmVx
X2FwcGx5X2Nvb2xpbmcoc3RydWN0IGNwdWZyZXFfY29vbGluZ19kZXZpY2UgKmNwdWZyZXFfZGV2
aWNlLAo+PiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5zaWduZWQgbG9uZyBj
b29saW5nX3N0YXRlKQo+PiA+Pj4gK3sKPj4gPj4+ICsgICAgIHVuc2lnbmVkIGludCBldmVudCwg
Y3B1aWQsIGNsaXBfZnJlcTsKPj4gPj4+ICsgICAgIHN0cnVjdCBjcHVtYXNrICptYXNrUHRyID0g
JmNwdWZyZXFfZGV2aWNlLT5hbGxvd2VkX2NwdXM7Cj4+ID4+PiArICAgICB1bnNpZ25lZCBpbnQg
Y3B1ID0gY3B1bWFza19hbnkobWFza1B0cik7Cj4+ID4+PiArCj4+ID4+PiArCj4+ID4+PiArICAg
ICAvKiBDaGVjayBpZiB0aGUgb2xkIGNvb2xpbmcgYWN0aW9uIGlzIHNhbWUgYXMgbmV3IGNvb2xp
bmcgYWN0aW9uICovCj4+ID4+PiArICAgICBpZiAoY3B1ZnJlcV9kZXZpY2UtPmNwdWZyZXFfc3Rh
dGUgPT0gY29vbGluZ19zdGF0ZSkKPj4gPj4+ICsgICAgICAgICAgICAgcmV0dXJuIDA7Cj4+ID4+
PiArCj4+ID4+PiArICAgICBjbGlwX2ZyZXEgPSBnZXRfY3B1X2ZyZXF1ZW5jeShjcHUsIGNvb2xp
bmdfc3RhdGUpOwo+PiA+Pj4gKyAgICAgaWYgKCFjbGlwX2ZyZXEpCj4+ID4+PiArICAgICAgICAg
ICAgIHJldHVybiAtRUlOVkFMOwo+PiA+Pj4gKwo+PiA+Pj4gKyAgICAgY3B1ZnJlcV9kZXZpY2Ut
PmNwdWZyZXFfc3RhdGUgPSBjb29saW5nX3N0YXRlOwo+PiA+Pj4gKyAgICAgY3B1ZnJlcV9kZXZp
Y2UtPmNwdWZyZXFfdmFsID0gY2xpcF9mcmVxOwo+PiA+Pj4gKyAgICAgbm90aWZ5X2RldmljZSA9
IGNwdWZyZXFfZGV2aWNlOwo+PiA+Pj4gKwo+PiA+Pj4gKyAgICAgaWYgKGNvb2xpbmdfc3RhdGUg
IT0gMCkKPj4gPj4+ICsgICAgICAgICAgICAgZXZlbnQgPSBDUFVGUkVRX0NPT0xJTkdfU1RBUlQ7
Cj4+ID4+PiArICAgICBlbHNlCj4+ID4+PiArICAgICAgICAgICAgIGV2ZW50ID0gQ1BVRlJFUV9D
T09MSU5HX1NUT1A7Cj4+ID4+PiArCj4+ID4+PiArICAgICBibG9ja2luZ19ub3RpZmllcl9jYWxs
X2NoYWluKCZjcHV0aGVybV9zdGF0ZV9ub3RpZmllcl9saXN0LAo+PiA+Pj4gKyAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50LCAmY2xpcF9mcmVxKTsKPj4g
Pj4+ICsKPj4gPj4+ICsgICAgIGZvcl9lYWNoX2NwdShjcHVpZCwgbWFza1B0cikgewo+PiA+Pj4g
KyAgICAgICAgICAgICBpZiAoaXNfY3B1ZnJlcV92YWxpZChjcHVpZCkpCj4+ID4+PiArICAgICAg
ICAgICAgICAgICAgICAgY3B1ZnJlcV91cGRhdGVfcG9saWN5KGNwdWlkKTsKPj4gPj4+ICsgICAg
IH0KPj4gPj4+ICsKPj4gPj4+ICsgICAgIG5vdGlmeV9kZXZpY2UgPSBOT1RJRllfSU5WQUxJRDsK
Pj4gPj4+ICsKPj4gPj4+ICsgICAgIHJldHVybiAwOwo+PiA+Pj4gK30KPj4gPj4+ICsKPj4gPj4+
ICsvKioKPj4gPj4+ICsgKiBjcHVmcmVxX3RoZXJtYWxfbm90aWZpZXIgLSBub3RpZmllciBjYWxs
YmFjayBmb3IgY3B1ZnJlcSBwb2xpY3kgY2hhbmdlLgo+PiA+Pj4gKyAqIEBuYjogICAgICBzdHJ1
Y3Qgbm90aWZpZXJfYmxvY2sgKiB3aXRoIGNhbGxiYWNrIGluZm8uCj4+ID4+PiArICogQGV2ZW50
OiB2YWx1ZSBzaG93aW5nIGNwdWZyZXEgZXZlbnQgZm9yIHdoaWNoIHRoaXMgZnVuY3Rpb24gaW52
b2tlZC4KPj4gPj4+ICsgKiBAZGF0YTogY2FsbGJhY2stc3BlY2lmaWMgZGF0YQo+PiA+Pj4gKyAq
Lwo+PiA+Pj4gK3N0YXRpYyBpbnQgY3B1ZnJlcV90aGVybWFsX25vdGlmaWVyKHN0cnVjdCBub3Rp
Zmllcl9ibG9jayAqbmIsCj4+ID4+PiArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgIHVuc2lnbmVkIGxvbmcgZXZlbnQsIHZvaWQgKmRhdGEpCj4+ID4+PiArewo+PiA+Pj4gKyAg
ICAgc3RydWN0IGNwdWZyZXFfcG9saWN5ICpwb2xpY3kgPSBkYXRhOwo+PiA+Pj4gKyAgICAgdW5z
aWduZWQgbG9uZyBtYXhfZnJlcSA9IDA7Cj4+ID4+PiArCj4+ID4+PiArICAgICBpZiAoZXZlbnQg
IT0gQ1BVRlJFUV9BREpVU1QgfHwgbm90aWZ5X2RldmljZSA9PSBOT1RJRllfSU5WQUxJRCkKPj4g
Pj4+ICsgICAgICAgICAgICAgcmV0dXJuIDA7Cj4+ID4+PiArCj4+ID4+PiArICAgICBpZiAoY3B1
bWFza190ZXN0X2NwdShwb2xpY3ktPmNwdSwgJm5vdGlmeV9kZXZpY2UtPmFsbG93ZWRfY3B1cykp
Cj4+ID4+PiArICAgICAgICAgICAgIG1heF9mcmVxID0gbm90aWZ5X2RldmljZS0+Y3B1ZnJlcV92
YWw7Cj4+ID4+PiArCj4+ID4+PiArICAgICAvKiBOZXZlciBleGNlZWQgdXNlcl9wb2xpY3kubWF4
Ki8KPj4gPj4+ICsgICAgIGlmIChtYXhfZnJlcSA+IHBvbGljeS0+dXNlcl9wb2xpY3kubWF4KQo+
PiA+Pj4gKyAgICAgICAgICAgICBtYXhfZnJlcSA9IHBvbGljeS0+dXNlcl9wb2xpY3kubWF4Owo+
PiA+Pj4gKwo+PiA+Pj4gKyAgICAgaWYgKHBvbGljeS0+bWF4ICE9IG1heF9mcmVxKQo+PiA+Pj4g
KyAgICAgICAgICAgICBjcHVmcmVxX3ZlcmlmeV93aXRoaW5fbGltaXRzKHBvbGljeSwgMCwgbWF4
X2ZyZXEpOwo+PiA+Pj4gKwo+PiA+Pj4gKyAgICAgcmV0dXJuIDA7Cj4+ID4+PiArfQo+PiA+Pj4g
Kwo+PiA+Pj4gKy8qCj4+ID4+PiArICogY3B1ZnJlcSBjb29saW5nIGRldmljZSBjYWxsYmFjayBm
dW5jdGlvbnMgYXJlIGRlZmluZWQgYmVsb3cKPj4gPj4+ICsgKi8KPj4gPj4+ICsKPj4gPj4+ICsv
KioKPj4gPj4+ICsgKiBjcHVmcmVxX2dldF9tYXhfc3RhdGUgLSBjYWxsYmFjayBmdW5jdGlvbiB0
byBnZXQgdGhlIG1heCBjb29saW5nIHN0YXRlLgo+PiA+Pj4gKyAqIEBjZGV2OiB0aGVybWFsIGNv
b2xpbmcgZGV2aWNlIHBvaW50ZXIuCj4+ID4+PiArICogQHN0YXRlOiBmaWxsIHRoaXMgdmFyaWFi
bGUgd2l0aCB0aGUgbWF4IGNvb2xpbmcgc3RhdGUuCj4+ID4+PiArICovCj4+ID4+PiArc3RhdGlj
IGludCBjcHVmcmVxX2dldF9tYXhfc3RhdGUoc3RydWN0IHRoZXJtYWxfY29vbGluZ19kZXZpY2Ug
KmNkZXYsCj4+ID4+PiArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5zaWduZWQgbG9u
ZyAqc3RhdGUpCj4+ID4+PiArewo+PiA+Pj4gKyAgICAgaW50IHJldCA9IC1FSU5WQUwsIGkgPSAw
Owo+PiA+Pj4gKyAgICAgc3RydWN0IGNwdWZyZXFfY29vbGluZ19kZXZpY2UgKmNwdWZyZXFfZGV2
aWNlOwo+PiA+Pj4gKyAgICAgc3RydWN0IGNwdW1hc2sgKm1hc2tQdHI7Cj4+ID4+PiArICAgICB1
bnNpZ25lZCBpbnQgY3B1Owo+PiA+Pj4gKyAgICAgc3RydWN0IGNwdWZyZXFfZnJlcXVlbmN5X3Rh
YmxlICp0YWJsZTsKPj4gPj4+ICsKPj4gPj4+ICsgICAgIG11dGV4X2xvY2soJmNvb2xpbmdfY3B1
ZnJlcV9sb2NrKTsKPj4gPj4+ICsgICAgIGxpc3RfZm9yX2VhY2hfZW50cnkoY3B1ZnJlcV9kZXZp
Y2UsICZjb29saW5nX2NwdWZyZXFfbGlzdCwgbm9kZSkgewo+PiA+Pj4gKyAgICAgICAgICAgICBp
ZiAoY3B1ZnJlcV9kZXZpY2UgJiYgY3B1ZnJlcV9kZXZpY2UtPmNvb2xfZGV2ID09IGNkZXYpCj4+
ID4+PiArICAgICAgICAgICAgICAgICAgICAgYnJlYWs7Cj4+ID4+PiArICAgICB9Cj4+ID4+PiAr
ICAgICBpZiAoY3B1ZnJlcV9kZXZpY2UgPT0gTlVMTCkKPj4gPj4+ICsgICAgICAgICAgICAgZ290
byByZXR1cm5fZ2V0X21heF9zdGF0ZTsKPj4gPj4+ICsKPj4gPj4+ICsgICAgIG1hc2tQdHIgPSAm
Y3B1ZnJlcV9kZXZpY2UtPmFsbG93ZWRfY3B1czsKPj4gPj4+ICsgICAgIGNwdSA9IGNwdW1hc2tf
YW55KG1hc2tQdHIpOwo+PiA+Pj4gKyAgICAgdGFibGUgPSBjcHVmcmVxX2ZyZXF1ZW5jeV9nZXRf
dGFibGUoY3B1KTsKPj4gPj4+ICsgICAgIGlmICghdGFibGUpIHsKPj4gPj4+ICsgICAgICAgICAg
ICAgKnN0YXRlID0gMDsKPj4gPj4+ICsgICAgICAgICAgICAgcmV0ID0gMDsKPj4gPj4+ICsgICAg
ICAgICAgICAgZ290byByZXR1cm5fZ2V0X21heF9zdGF0ZTsKPj4gPj4+ICsgICAgIH0KPj4gPj4+
ICsKPj4gPj4+ICsgICAgIHdoaWxlICh0YWJsZVtpXS5mcmVxdWVuY3kgIT0gQ1BVRlJFUV9UQUJM
RV9FTkQpIHsKPj4gPj4+ICsgICAgICAgICAgICAgaWYgKHRhYmxlW2ldLmZyZXF1ZW5jeSA9PSBD
UFVGUkVRX0VOVFJZX0lOVkFMSUQpCj4+ID4+PiArICAgICAgICAgICAgICAgICAgICAgY29udGlu
dWU7Cj4+ID4+PiArICAgICAgICAgICAgIGkrKzsKPj4gPj4+ICsgICAgIH0KPj4gPj4+ICsgICAg
IGlmIChpID4gMCkgewo+PiA+Pj4gKyAgICAgICAgICAgICAqc3RhdGUgPSAtLWk7Cj4+ID4+PiAr
ICAgICAgICAgICAgIHJldCA9IDA7Cj4+ID4+PiArICAgICB9Cj4+ID4+PiArCj4+ID4+PiArcmV0
dXJuX2dldF9tYXhfc3RhdGU6Cj4+ID4+PiArICAgICBtdXRleF91bmxvY2soJmNvb2xpbmdfY3B1
ZnJlcV9sb2NrKTsKPj4gPj4+ICsgICAgIHJldHVybiByZXQ7Cj4+ID4+PiArfQo+PiA+Pj4gKwo+
PiA+Pj4gKy8qKgo+PiA+Pj4gKyAqIGNwdWZyZXFfZ2V0X2N1cl9zdGF0ZSAtIGNhbGxiYWNrIGZ1
bmN0aW9uIHRvIGdldCB0aGUgY3VycmVudCBjb29saW5nIHN0YXRlLgo+PiA+Pj4gKyAqIEBjZGV2
OiB0aGVybWFsIGNvb2xpbmcgZGV2aWNlIHBvaW50ZXIuCj4+ID4+PiArICogQHN0YXRlOiBmaWxs
IHRoaXMgdmFyaWFibGUgd2l0aCB0aGUgY3VycmVudCBjb29saW5nIHN0YXRlLgo+PiA+Pj4gKyAq
Lwo+PiA+Pj4gK3N0YXRpYyBpbnQgY3B1ZnJlcV9nZXRfY3VyX3N0YXRlKHN0cnVjdCB0aGVybWFs
X2Nvb2xpbmdfZGV2aWNlICpjZGV2LAo+PiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgIHVuc2lnbmVkIGxvbmcgKnN0YXRlKQo+PiA+Pj4gK3sKPj4gPj4+ICsgICAgIGludCByZXQg
PSAtRUlOVkFMOwo+PiA+Pj4gKyAgICAgc3RydWN0IGNwdWZyZXFfY29vbGluZ19kZXZpY2UgKmNw
dWZyZXFfZGV2aWNlOwo+PiA+Pj4gKwo+PiA+Pj4gKyAgICAgbXV0ZXhfbG9jaygmY29vbGluZ19j
cHVmcmVxX2xvY2spOwo+PiA+Pj4gKyAgICAgbGlzdF9mb3JfZWFjaF9lbnRyeShjcHVmcmVxX2Rl
dmljZSwgJmNvb2xpbmdfY3B1ZnJlcV9saXN0LCBub2RlKSB7Cj4+ID4+PiArICAgICAgICAgICAg
IGlmIChjcHVmcmVxX2RldmljZSAmJiBjcHVmcmVxX2RldmljZS0+Y29vbF9kZXYgPT0gY2Rldikg
ewo+PiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgICpzdGF0ZSA9IGNwdWZyZXFfZGV2aWNlLT5j
cHVmcmVxX3N0YXRlOwo+PiA+Pj4gKyAgICAgICAgICAgICAgICAgICAgIHJldCA9IDA7Cj4+ID4+
PiArICAgICAgICAgICAgICAgICAgICAgYnJlYWs7Cj4+ID4+PiArICAgICAgICAgICAgIH0KPj4g
Pj4+ICsgICAgIH0KPj4gPj4+ICsgICAgIG11dGV4X3VubG9jaygmY29vbGluZ19jcHVmcmVxX2xv
Y2spOwo+PiA+Pj4gKwo+PiA+Pgo+PiA+PiBhcyBjcHVmcmVxIG1heSBiZSBjaGFuZ2VkIGluIG90
aGVyIHBsYWNlcywgZS5nLiB2aWEgc3lzZnMgSS9GLCB3ZSBzaG91bGQKPj4gPj4gdXNlIHRoZSBj
dXJyZW50IGNwdSBmcmVxdWVuY3kgdG8gZ2V0IHRoZSBSRUFMIGNvb2xpbmcgc3RhdGUsIHJhdGhl
ciB0aGFuCj4+ID4+IHVzaW5nIGEgY2FjaGVkIHZhbHVlLgo+PiA+Cj4+ID4gWWVzIGFncmVlZCAs
IEkgd2lsbCByZXBvc3Qgd2l0aCB5b3VyIHN1Z2dlc3Rpb24uCj4+ID4KPj4gPiBUaGFua3MsCj4+
ID4gQW1pdAo+PiA+Pgo+PiA+PiB0aGFua3MsCj4+ID4+IHJ1aQo+PiA+Pgo+PiA+Pgo+PiA+IC0t
Cj4+ID4gVG8gdW5zdWJzY3JpYmUgZnJvbSB0aGlzIGxpc3Q6IHNlbmQgdGhlIGxpbmUgInVuc3Vi
c2NyaWJlIGxpbnV4LWFjcGkiIGluCj4+ID4gdGhlIGJvZHkgb2YgYSBtZXNzYWdlIHRvIG1ham9y
ZG9tb0B2Z2VyLmtlcm5lbC5vcmcKPj4gPiBNb3JlIG1ham9yZG9tbyBpbmZvIGF0ICBodHRwOi8v
dmdlci5rZXJuZWwub3JnL21ham9yZG9tby1pbmZvLmh0bWwKPj4KPj4KPj4KPgo+Cj4gLS0KPiBU
byB1bnN1YnNjcmliZSBmcm9tIHRoaXMgbGlzdDogc2VuZCB0aGUgbGluZSAidW5zdWJzY3JpYmUg
bGludXgtYWNwaSIgaW4KPiB0aGUgYm9keSBvZiBhIG1lc3NhZ2UgdG8gbWFqb3Jkb21vQHZnZXIu
a2VybmVsLm9yZwo+IE1vcmUgbWFqb3Jkb21vIGluZm8gYXQgIGh0dHA6Ly92Z2VyLmtlcm5lbC5v
cmcvbWFqb3Jkb21vLWluZm8uaHRtbAoKCgotLSAKCkVkdWFyZG8gVmFsZW50aW4KCl9fX19fX19f
X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fCmxtLXNlbnNvcnMgbWFpbGlu
ZyBsaXN0CmxtLXNlbnNvcnNAbG0tc2Vuc29ycy5vcmcKaHR0cDovL2xpc3RzLmxtLXNlbnNvcnMu
b3JnL21haWxtYW4vbGlzdGluZm8vbG0tc2Vuc29ycw=

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
       [not found] <0MAO00DCVFKI4NS0@ms2.samsung.com>
@ 2012-09-21  2:23   ` jonghwa3.lee
  0 siblings, 0 replies; 50+ messages in thread
From: jonghwa3.lee @ 2012-09-21  2:23 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: linux-pm, Andrew Morton, Zhang Rui, Len Brown, linux-samsung-soc,
	linux-kernel, linux-acpi, lm-sensors

Hi, Amit,
On 2012년 08월 16일 20:41, Amit Daniel Kachhap wrote:

>  diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
>  new file mode 100644
>  index 0000000..66cbd52
>  --- /dev/null
>  +++ b/drivers/thermal/cpu_cooling.c
>  @@ -0,0 +1,586 @@

>  +/**
>  + * cpufreq_cooling_register - function to create cpufreq cooling device.
>  + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
>  + */
>  +struct thermal_cooling_device *cpufreq_cooling_register(
>  +       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;
>  +       char dev_name[THERMAL_NAME_LENGTH];
>  +       int ret = 0, id = 0, i;
>  +       struct cpufreq_policy policy;
>  +       ret = get_idr(&cpufreq_idr,&cpufreq_dev->id);
>  +       if (ret) {
>  +               kfree(cpufreq_dev);
>  +               return ERR_PTR(-EINVAL);
>  +       }
>  +
>  +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
>  +
>  +       cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
>  +&cpufreq_cooling_ops);
>  +       if (!cool_dev) {
>  +               release_idr(&cpufreq_idr, cpufreq_dev->id);
>  +               kfree(cpufreq_dev);
>  +               return ERR_PTR(-EINVAL);
>  +       }
>  +       cpufreq_dev->id = id;


Why is this needed? Should every cpufreq_dev instance's id be same for
zero? It looks odd. And it also has problem which can be occurred when
it release id during unregistering cpufreq_dev. I think it should keep
the idr value taken before.


>  1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 50+ messages in thread

* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
@ 2012-09-21  2:23   ` jonghwa3.lee
  0 siblings, 0 replies; 50+ messages in thread
From: jonghwa3.lee @ 2012-09-21  2:23 UTC (permalink / raw)
  To: Amit Daniel Kachhap
  Cc: linux-pm, Andrew Morton, Zhang Rui, Len Brown, linux-samsung-soc,
	linux-kernel, linux-acpi, lm-sensors

Hi, Amit,
On 2012년 08월 16일 20:41, Amit Daniel Kachhap wrote:

>  diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
>  new file mode 100644
>  index 0000000..66cbd52
>  --- /dev/null
>  +++ b/drivers/thermal/cpu_cooling.c
>  @@ -0,0 +1,586 @@

>  +/**
>  + * cpufreq_cooling_register - function to create cpufreq cooling device.
>  + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
>  + */
>  +struct thermal_cooling_device *cpufreq_cooling_register(
>  +       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;
>  +       char dev_name[THERMAL_NAME_LENGTH];
>  +       int ret = 0, id = 0, i;
>  +       struct cpufreq_policy policy;
>  +       ret = get_idr(&cpufreq_idr,&cpufreq_dev->id);
>  +       if (ret) {
>  +               kfree(cpufreq_dev);
>  +               return ERR_PTR(-EINVAL);
>  +       }
>  +
>  +       sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
>  +
>  +       cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
>  +&cpufreq_cooling_ops);
>  +       if (!cool_dev) {
>  +               release_idr(&cpufreq_idr, cpufreq_dev->id);
>  +               kfree(cpufreq_dev);
>  +               return ERR_PTR(-EINVAL);
>  +       }
>  +       cpufreq_dev->id = id;


Why is this needed? Should every cpufreq_dev instance's id be same for
zero? It looks odd. And it also has problem which can be occurred when
it release id during unregistering cpufreq_dev. I think it should keep
the idr value taken before.


>  1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/



^ permalink raw reply	[flat|nested] 50+ messages in thread

end of thread, other threads:[~2012-09-21  2:24 UTC | newest]

Thread overview: 50+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-08-16 11:41 [PATCH v6 0/6] thermal: Add kernel thermal support for exynos platform Amit Daniel Kachhap
2012-08-16 11:53 ` [lm-sensors] " Amit Daniel Kachhap
2012-08-16 11:41 ` [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation Amit Daniel Kachhap
2012-08-16 11:53   ` [lm-sensors] " Amit Daniel Kachhap
2012-08-16 11:41   ` Amit Daniel Kachhap
2012-08-16 14:52   ` Valentin, Eduardo
2012-08-16 14:52     ` [lm-sensors] [linux-pm] " Valentin, Eduardo
2012-08-16 14:52     ` Valentin, Eduardo
2012-08-17  3:43     ` Amit Kachhap
2012-08-17  3:55       ` [lm-sensors] " Amit Kachhap
2012-08-17  7:24   ` Zhang Rui
2012-08-17  7:24     ` [lm-sensors] " Zhang Rui
2012-08-17  7:24     ` Zhang Rui
2012-08-17  7:58     ` Amit Kachhap
2012-08-17  8:10       ` [lm-sensors] " Amit Kachhap
2012-08-17  7:58       ` Amit Kachhap
2012-08-17  8:56       ` Valentin, Eduardo
2012-08-17  8:56         ` [lm-sensors] " Valentin, Eduardo
2012-08-17  8:56         ` Valentin, Eduardo
2012-08-20  2:16         ` Zhang Rui
2012-08-20  2:16           ` [lm-sensors] " Zhang Rui
2012-08-20  2:16           ` Zhang Rui
2012-08-20  9:36           ` Valentin, Eduardo
2012-08-20  9:36             ` [lm-sensors] " Valentin, Eduardo
2012-08-20  9:36             ` Valentin, Eduardo
2012-08-17 13:29       ` [RESEND PATCH " Amit Daniel Kachhap
2012-08-17 13:41         ` [lm-sensors] " Amit Daniel Kachhap
2012-08-17 13:29         ` Amit Daniel Kachhap
2012-08-17 13:32         ` Amit Kachhap
2012-08-17 13:44           ` [lm-sensors] " Amit Kachhap
2012-08-17 13:32           ` Amit Kachhap
2012-08-16 11:41 ` [PATCH v6 2/6] hwmon: exynos4: move thermal sensor driver to driver/thermal directory Amit Daniel Kachhap
2012-08-16 11:53   ` [lm-sensors] " Amit Daniel Kachhap
2012-08-16 11:41   ` Amit Daniel Kachhap
2012-08-16 11:41 ` [PATCH v6 3/6] thermal: exynos5: add exynos5250 thermal sensor driver support Amit Daniel Kachhap
2012-08-16 11:53   ` [lm-sensors] " Amit Daniel Kachhap
2012-08-16 11:41   ` Amit Daniel Kachhap
2012-08-16 11:41 ` [PATCH v6 4/6] thermal: exynos: register the tmu sensor with the kernel thermal layer Amit Daniel Kachhap
2012-08-16 11:53   ` [lm-sensors] " Amit Daniel Kachhap
2012-08-16 11:41   ` Amit Daniel Kachhap
2012-08-16 11:41 ` [PATCH v6 5/6] ARM: exynos: add thermal sensor driver platform data support Amit Daniel Kachhap
2012-08-16 11:53   ` [lm-sensors] " Amit Daniel Kachhap
2012-08-16 11:41   ` Amit Daniel Kachhap
2012-08-17  3:55   ` Thomas Abraham
2012-08-17  4:07     ` [lm-sensors] " Thomas Abraham
2012-08-16 11:41 ` [PATCH v6 6/6] thermal: exynos: Use devm_* functions Amit Daniel Kachhap
2012-08-16 11:53   ` [lm-sensors] " Amit Daniel Kachhap
2012-08-16 11:41   ` Amit Daniel Kachhap
     [not found] <0MAO00DCVFKI4NS0@ms2.samsung.com>
2012-09-21  2:23 ` [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation jonghwa3.lee
2012-09-21  2:23   ` jonghwa3.lee

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.