summaryrefslogtreecommitdiff
path: root/drivers/thermal
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/thermal
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/thermal')
-rw-r--r--drivers/thermal/Kconfig228
-rw-r--r--drivers/thermal/Makefile38
-rw-r--r--drivers/thermal/armada_thermal.c222
-rw-r--r--drivers/thermal/cpu_cooling.c571
-rw-r--r--drivers/thermal/db8500_cpufreq_cooling.c107
-rw-r--r--drivers/thermal/db8500_thermal.c534
-rw-r--r--drivers/thermal/dove_thermal.c202
-rw-r--r--drivers/thermal/exynos_thermal.c1061
-rw-r--r--drivers/thermal/fair_share.c122
-rw-r--r--drivers/thermal/intel_powerclamp.c795
-rw-r--r--drivers/thermal/kirkwood_thermal.c132
-rw-r--r--drivers/thermal/of-thermal.c850
-rw-r--r--drivers/thermal/rcar_thermal.c516
-rw-r--r--drivers/thermal/spear_thermal.c209
-rw-r--r--drivers/thermal/sprd_2713_thm.c500
-rw-r--r--drivers/thermal/sprd_board_sensor.c535
-rw-r--r--drivers/thermal/sprd_cpu_cooling.c604
-rw-r--r--drivers/thermal/sprd_ddie_alone_thm.c636
-rw-r--r--drivers/thermal/sprd_ddie_thm.c681
-rw-r--r--drivers/thermal/sprd_pmic_2723s_thm.c547
-rw-r--r--drivers/thermal/sprd_thm.c587
-rw-r--r--drivers/thermal/step_wise.c350
-rw-r--r--drivers/thermal/thermal_core.c1845
-rw-r--r--drivers/thermal/thermal_core.h90
-rw-r--r--drivers/thermal/thermal_hwmon.c269
-rw-r--r--drivers/thermal/thermal_hwmon.h49
-rw-r--r--drivers/thermal/thm.h76
-rw-r--r--drivers/thermal/user_space.c57
28 files changed, 12413 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
new file mode 100644
index 00000000..a1d5432e
--- /dev/null
+++ b/drivers/thermal/Kconfig
@@ -0,0 +1,228 @@
+#
+# Generic thermal sysfs drivers configuration
+#
+
+menuconfig THERMAL
+ tristate "Generic Thermal sysfs driver"
+ help
+ Generic Thermal Sysfs driver offers a generic mechanism for
+ thermal management. Usually it's made up of one or more thermal
+ zone and cooling device.
+ Each thermal zone contains its own temperature, trip points,
+ cooling devices.
+ All platforms with ACPI thermal support can use this driver.
+ If you want this support, you should say Y or M here.
+
+if THERMAL
+
+config THERMAL_HWMON
+ bool
+ prompt "Expose thermal sensors as hwmon device"
+ depends on HWMON=y || HWMON=THERMAL
+ default y
+ help
+ In case a sensor is registered with the thermal
+ framework, this option will also register it
+ as a hwmon. The sensor will then have the common
+ hwmon sysfs interface.
+
+ Say 'Y' here if you want all thermal sensors to
+ have hwmon sysfs interface too.
+
+config THERMAL_OF
+ bool
+ prompt "APIs to parse thermal data out of device tree"
+ depends on OF
+ default y
+ help
+ This options provides helpers to add the support to
+ read and parse thermal data definitions out of the
+ device tree blob.
+
+ Say 'Y' here if you need to build thermal infrastructure
+ based on device tree.
+
+choice
+ prompt "Default Thermal governor"
+ default THERMAL_DEFAULT_GOV_STEP_WISE
+ help
+ This option sets which thermal governor shall be loaded at
+ startup. If in doubt, select 'step_wise'.
+
+config THERMAL_DEFAULT_GOV_STEP_WISE
+ bool "step_wise"
+ select THERMAL_GOV_STEP_WISE
+ help
+ Use the step_wise governor as default. This throttles the
+ devices one step at a time.
+
+config THERMAL_DEFAULT_GOV_FAIR_SHARE
+ bool "fair_share"
+ select THERMAL_GOV_FAIR_SHARE
+ help
+ Use the fair_share governor as default. This throttles the
+ devices based on their 'contribution' to a zone. The
+ contribution should be provided through platform data.
+
+config THERMAL_DEFAULT_GOV_USER_SPACE
+ bool "user_space"
+ select THERMAL_GOV_USER_SPACE
+ help
+ Select this if you want to let the user space manage the
+ lpatform thermals.
+
+endchoice
+
+config THERMAL_GOV_FAIR_SHARE
+ bool "Fair-share thermal governor"
+ help
+ Enable this to manage platform thermals using fair-share governor.
+
+config THERMAL_GOV_STEP_WISE
+ bool "Step_wise thermal governor"
+ help
+ Enable this to manage platform thermals using a simple linear
+
+config THERMAL_GOV_USER_SPACE
+ bool "User_space thermal governor"
+ help
+ Enable this to let the user space manage the platform thermals.
+
+config CPU_THERMAL
+ bool "generic cpu cooling support"
+ depends on CPU_FREQ
+ depends on THERMAL_OF
+ select CPU_FREQ_TABLE
+ help
+ This implements the generic cpu cooling mechanism through frequency
+ reduction. 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 THERMAL_EMULATION
+ bool "Thermal emulation mode support"
+ help
+ Enable this option to make a emul_temp sysfs node in thermal zone
+ directory to support temperature emulation. With emulation sysfs node,
+ user can manually input temperature and test the different trip
+ threshold behaviour for simulation purpose.
+
+ WARNING: Be careful while enabling this option on production systems,
+ because userland can easily disable the thermal policy by simply
+ flooding this sysfs node with low temperature values.
+
+config SPEAR_THERMAL
+ bool "SPEAr thermal sensor driver"
+ depends on PLAT_SPEAR
+ depends on OF
+ help
+ Enable this to plug the SPEAr thermal sensor driver into the Linux
+ thermal framework
+
+config RCAR_THERMAL
+ tristate "Renesas R-Car thermal driver"
+ depends on ARCH_SHMOBILE
+ help
+ Enable this to plug the R-Car thermal sensor driver into the Linux
+ thermal framework
+
+config KIRKWOOD_THERMAL
+ tristate "Temperature sensor on Marvell Kirkwood SoCs"
+ depends on ARCH_KIRKWOOD
+ depends on OF
+ help
+ Support for the Kirkwood thermal sensor driver into the Linux thermal
+ framework. Only kirkwood 88F6282 and 88F6283 have this sensor.
+
+config EXYNOS_THERMAL
+ tristate "Temperature sensor on Samsung EXYNOS"
+ depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5)
+ depends on CPU_THERMAL
+ help
+ If you say yes here you get support for TMU (Thermal Management
+ Unit) on SAMSUNG EXYNOS series of SoC.
+
+config DOVE_THERMAL
+ tristate "Temperature sensor on Marvell Dove SoCs"
+ depends on ARCH_DOVE
+ depends on OF
+ help
+ Support for the Dove thermal sensor driver in the Linux thermal
+ framework.
+
+config DB8500_THERMAL
+ bool "DB8500 thermal management"
+ depends on ARCH_U8500
+ default y
+ help
+ Adds DB8500 thermal management implementation according to the thermal
+ management framework. A thermal zone with several trip points will be
+ created. Cooling devices can be bound to the trip points to cool this
+ thermal zone if trip points reached.
+
+config ARMADA_THERMAL
+ tristate "Armada 370/XP thermal management"
+ depends on ARCH_MVEBU
+ depends on OF
+ help
+ Enable this option if you want to have support for thermal management
+ controller present in Armada 370 and Armada XP SoC.
+
+config DB8500_CPUFREQ_COOLING
+ tristate "DB8500 cpufreq cooling"
+ depends on ARCH_U8500
+ depends on CPU_THERMAL
+ default y
+ help
+ Adds DB8500 cpufreq cooling devices, and these cooling devices can be
+ bound to thermal zone trip points. When a trip point reached, the
+ bound cpufreq cooling device turns active to set CPU frequency low to
+ cool down the CPU.
+
+config INTEL_POWERCLAMP
+ tristate "Intel PowerClamp idle injection driver"
+ depends on THERMAL
+ depends on X86
+ depends on CPU_SUP_INTEL
+ help
+ Enable this to enable Intel PowerClamp idle injection driver. This
+ enforce idle time which results in more package C-state residency. The
+ user interface is exposed via generic thermal framework.
+
+config THERMAL_DDIE
+ bool "thermal ddie"
+ depends on THERMAL
+ help
+ Use the 2713s thermal system.
+config THERMAL_SC2723S_PMIC
+ bool "thermal pmic"
+ depends on THERMAL
+ help
+ Use the 2713s thermal system.
+config THERMAL_SC2713
+ bool "thermal 2713"
+ depends on THERMAL
+ help
+ Choose the 2713 thermal system.
+
+config SPRD_CPU_COOLING
+ bool "sprd cpu cooling devices"
+ depends on THERMAL
+ help
+ Choose sprd cpu cooling devices.
+
+config THERMAL_BOARD_SENSOR
+ bool "thermal board sensor"
+ depends on THERMAL
+ help
+ the board thermal system
+config THERMAL_ALONE_DDIE
+ bool "thermal alone ddie"
+ depends on THERMAL
+ help
+ the thermal system
+
+endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
new file mode 100644
index 00000000..2c5cba13
--- /dev/null
+++ b/drivers/thermal/Makefile
@@ -0,0 +1,38 @@
+#
+# Makefile for sensor chip drivers.
+#
+
+obj-$(CONFIG_THERMAL) += thermal_sys.o
+obj-$(CONFIG_THERMAL) += sprd_thm.o
+thermal_sys-y += thermal_core.o
+
+# interface to/from other layers providing sensors
+thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o
+thermal_sys-$(CONFIG_THERMAL_OF) += of-thermal.o
+
+# governors
+thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o
+thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o
+thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o
+
+# cpufreq cooling
+thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
+obj-$(CONFIG_SPRD_CPU_COOLING) += sprd_cpu_cooling.o
+
+# platform thermal drivers
+obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
+obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
+obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o
+obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
+obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o
+obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o
+obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o
+obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o
+obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
+
+# Added for different sprd thm
+obj-$(CONFIG_THERMAL_DDIE) += sprd_ddie_thm.o
+obj-$(CONFIG_THERMAL_ALONE_DDIE) += sprd_ddie_alone_thm.o
+obj-$(CONFIG_THERMAL_SC2713) += sprd_2713_thm.o
+obj-$(CONFIG_THERMAL_SC2723S_PMIC) += sprd_pmic_2723s_thm.o
+obj-$(CONFIG_THERMAL_BOARD_SENSOR) += sprd_board_sensor.o
diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c
new file mode 100644
index 00000000..54ffd64c
--- /dev/null
+++ b/drivers/thermal/armada_thermal.c
@@ -0,0 +1,222 @@
+/*
+ * Marvell Armada 370/XP thermal sensor driver
+ *
+ * Copyright (C) 2013 Marvell
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/thermal.h>
+
+#define THERMAL_VALID_OFFSET 9
+#define THERMAL_VALID_MASK 0x1
+#define THERMAL_TEMP_OFFSET 10
+#define THERMAL_TEMP_MASK 0x1ff
+
+/* Thermal Manager Control and Status Register */
+#define PMU_TDC0_SW_RST_MASK (0x1 << 1)
+#define PMU_TM_DISABLE_OFFS 0
+#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS)
+#define PMU_TDC0_REF_CAL_CNT_OFFS 11
+#define PMU_TDC0_REF_CAL_CNT_MASK (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS)
+#define PMU_TDC0_OTF_CAL_MASK (0x1 << 30)
+#define PMU_TDC0_START_CAL_MASK (0x1 << 25)
+
+struct armada_thermal_ops;
+
+/* Marvell EBU Thermal Sensor Dev Structure */
+struct armada_thermal_priv {
+ void __iomem *sensor;
+ void __iomem *control;
+ struct armada_thermal_ops *ops;
+};
+
+struct armada_thermal_ops {
+ /* Initialize the sensor */
+ void (*init_sensor)(struct armada_thermal_priv *);
+
+ /* Test for a valid sensor value (optional) */
+ bool (*is_valid)(struct armada_thermal_priv *);
+};
+
+static void armadaxp_init_sensor(struct armada_thermal_priv *priv)
+{
+ unsigned long reg;
+
+ reg = readl_relaxed(priv->control);
+ reg |= PMU_TDC0_OTF_CAL_MASK;
+ writel(reg, priv->control);
+
+ /* Reference calibration value */
+ reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
+ reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
+ writel(reg, priv->control);
+
+ /* Reset the sensor */
+ reg = readl_relaxed(priv->control);
+ writel((reg | PMU_TDC0_SW_RST_MASK), priv->control);
+
+ writel(reg, priv->control);
+
+ /* Enable the sensor */
+ reg = readl_relaxed(priv->sensor);
+ reg &= ~PMU_TM_DISABLE_MASK;
+ writel(reg, priv->sensor);
+}
+
+static void armada370_init_sensor(struct armada_thermal_priv *priv)
+{
+ unsigned long reg;
+
+ reg = readl_relaxed(priv->control);
+ reg |= PMU_TDC0_OTF_CAL_MASK;
+ writel(reg, priv->control);
+
+ /* Reference calibration value */
+ reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
+ reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
+ writel(reg, priv->control);
+
+ reg &= ~PMU_TDC0_START_CAL_MASK;
+ writel(reg, priv->control);
+
+ mdelay(10);
+}
+
+static bool armada_is_valid(struct armada_thermal_priv *priv)
+{
+ unsigned long reg = readl_relaxed(priv->sensor);
+
+ return (reg >> THERMAL_VALID_OFFSET) & THERMAL_VALID_MASK;
+}
+
+static int armada_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct armada_thermal_priv *priv = thermal->devdata;
+ unsigned long reg;
+
+ /* Valid check */
+ if (priv->ops->is_valid && !priv->ops->is_valid(priv)) {
+ dev_err(&thermal->device,
+ "Temperature sensor reading not valid\n");
+ return -EIO;
+ }
+
+ reg = readl_relaxed(priv->sensor);
+ reg = (reg >> THERMAL_TEMP_OFFSET) & THERMAL_TEMP_MASK;
+ *temp = (3153000000UL - (10000000UL*reg)) / 13825;
+ return 0;
+}
+
+static struct thermal_zone_device_ops ops = {
+ .get_temp = armada_get_temp,
+};
+
+static const struct armada_thermal_ops armadaxp_ops = {
+ .init_sensor = armadaxp_init_sensor,
+};
+
+static const struct armada_thermal_ops armada370_ops = {
+ .is_valid = armada_is_valid,
+ .init_sensor = armada370_init_sensor,
+};
+
+static const struct of_device_id armada_thermal_id_table[] = {
+ {
+ .compatible = "marvell,armadaxp-thermal",
+ .data = &armadaxp_ops,
+ },
+ {
+ .compatible = "marvell,armada370-thermal",
+ .data = &armada370_ops,
+ },
+ {
+ /* sentinel */
+ },
+};
+MODULE_DEVICE_TABLE(of, armada_thermal_id_table);
+
+static int armada_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *thermal;
+ const struct of_device_id *match;
+ struct armada_thermal_priv *priv;
+ struct resource *res;
+
+ match = of_match_device(armada_thermal_id_table, &pdev->dev);
+ if (!match)
+ return -ENODEV;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->sensor = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->sensor))
+ return PTR_ERR(priv->sensor);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ priv->control = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->control))
+ return PTR_ERR(priv->control);
+
+ priv->ops = (struct armada_thermal_ops *)match->data;
+ priv->ops->init_sensor(priv);
+
+ thermal = thermal_zone_device_register("armada_thermal", 0, 0,
+ priv, &ops, NULL, 0, 0);
+ if (IS_ERR(thermal)) {
+ dev_err(&pdev->dev,
+ "Failed to register thermal zone device\n");
+ return PTR_ERR(thermal);
+ }
+
+ platform_set_drvdata(pdev, thermal);
+
+ return 0;
+}
+
+static int armada_thermal_exit(struct platform_device *pdev)
+{
+ struct thermal_zone_device *armada_thermal =
+ platform_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(armada_thermal);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver armada_thermal_driver = {
+ .probe = armada_thermal_probe,
+ .remove = armada_thermal_exit,
+ .driver = {
+ .name = "armada_thermal",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(armada_thermal_id_table),
+ },
+};
+
+module_platform_driver(armada_thermal_driver);
+
+MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>");
+MODULE_DESCRIPTION("Armada 370/XP thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 00000000..4246262c
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,571 @@
+/*
+ * 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/module.h>
+#include <linux/thermal.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 - data for cooling device with cpufreq
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ * registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the
+ * registered 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.
+ *
+ * This structure is required for keeping information of each
+ * cpufreq_cooling_device registered. In order to prevent corruption of this 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;
+};
+static DEFINE_IDR(cpufreq_idr);
+static DEFINE_MUTEX(cooling_cpufreq_lock);
+
+static unsigned int cpufreq_dev_count;
+
+/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
+#define NOTIFY_INVALID NULL
+static struct cpufreq_cooling_device *notify_device;
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ *
+ * This function will populate @id with an unique
+ * id, using the idr API.
+ *
+ * Return: 0 on success, an error code on failure.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+ int ret;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
+ mutex_unlock(&cooling_cpufreq_lock);
+ if (unlikely(ret < 0))
+ return ret;
+ *id = ret;
+
+ 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);
+}
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * is_cpufreq_valid - function to check frequency transitioning capability.
+ * @cpu: cpu for which check is needed.
+ *
+ * This function will check the current state of the system if
+ * it is capable of changing the frequency for a given @cpu.
+ *
+ * Return: 0 if the system is not currently capable of changing
+ * the frequency of given cpu. !0 in case the frequency is changeable.
+ */
+static int is_cpufreq_valid(int cpu)
+{
+ struct cpufreq_policy policy;
+
+ return !cpufreq_get_policy(&policy, cpu);
+}
+
+enum cpufreq_cooling_property {
+ GET_LEVEL,
+ GET_FREQ,
+ GET_MAXL,
+};
+
+/**
+ * get_property - fetch a property of interest for a give cpu.
+ * @cpu: cpu for which the property is required
+ * @input: query parameter
+ * @output: query return
+ * @property: type of query (frequency, level, max level)
+ *
+ * This is the common function to
+ * 1. get maximum cpu cooling states
+ * 2. translate frequency to cooling state
+ * 3. translate cooling state to frequency
+ * Note that the code may be not in good shape
+ * but it is written in this way in order to:
+ * a) reduce duplicate code as most of the code can be shared.
+ * b) make sure the logic is consistent when translating between
+ * cooling states and frequencies.
+ *
+ * Return: 0 on success, -EINVAL when invalid parameters are passed.
+ */
+static int get_property(unsigned int cpu, unsigned long input,
+ unsigned int *output,
+ enum cpufreq_cooling_property property)
+{
+ int i, j;
+ unsigned long max_level = 0, level = 0;
+ unsigned int freq = CPUFREQ_ENTRY_INVALID;
+ int descend = -1;
+ struct cpufreq_frequency_table *table =
+ cpufreq_frequency_get_table(cpu);
+
+ if (!output)
+ return -EINVAL;
+
+ if (!table)
+ return -EINVAL;
+
+ for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) {
+ /* ignore invalid entries */
+ if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+
+ /* ignore duplicate entry */
+ if (freq == table[i].frequency)
+ continue;
+
+ /* get the frequency order */
+ if (freq != CPUFREQ_ENTRY_INVALID && descend == -1)
+ descend = !!(freq > table[i].frequency);
+
+ freq = table[i].frequency;
+ max_level++;
+ }
+
+ /* No valid cpu frequency entry */
+ if (max_level == 0)
+ return -EINVAL;
+
+ /* max_level is an index, not a counter */
+ max_level--;
+
+ /* get max level */
+ if (property == GET_MAXL) {
+ *output = (unsigned int)max_level;
+ return 0;
+ }
+
+ if (property == GET_FREQ)
+ level = descend ? input : (max_level - input);
+
+ for (i = 0, j = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) {
+ /* ignore invalid entry */
+ if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+
+ /* ignore duplicate entry */
+ if (freq == table[i].frequency)
+ continue;
+
+ /* now we have a valid frequency entry */
+ freq = table[i].frequency;
+
+ if (property == GET_LEVEL && (unsigned int)input == freq) {
+ /* get level by frequency */
+ *output = descend ? j : (max_level - j);
+ return 0;
+ }
+ if (property == GET_FREQ && level == j) {
+ /* get frequency by level */
+ *output = freq;
+ return 0;
+ }
+ j++;
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * cpufreq_cooling_get_level - for a give cpu, return the cooling level.
+ * @cpu: cpu for which the level is required
+ * @freq: the frequency of interest
+ *
+ * This function will match the cooling level corresponding to the
+ * requested @freq and return it.
+ *
+ * Return: The matched cooling level on success or THERMAL_CSTATE_INVALID
+ * otherwise.
+ */
+unsigned long cpufreq_cooling_get_level(unsigned int cpu, unsigned int freq)
+{
+ unsigned int val;
+
+ if (get_property(cpu, (unsigned long)freq, &val, GET_LEVEL))
+ return THERMAL_CSTATE_INVALID;
+
+ return (unsigned long)val;
+}
+EXPORT_SYMBOL_GPL(cpufreq_cooling_get_level);
+
+/**
+ * get_cpu_frequency - get the absolute value of frequency from level.
+ * @cpu: cpu for which frequency is fetched.
+ * @level: cooling level
+ *
+ * This function matches cooling level with frequency. Based on a cooling level
+ * of frequency, equals cooling state of cpu cooling device, it will return
+ * the corresponding frequency.
+ * e.g level=0 --> 1st MAX FREQ, level=1 ---> 2nd MAX FREQ, .... etc
+ *
+ * Return: 0 on error, the corresponding frequency otherwise.
+ */
+static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
+{
+ int ret = 0;
+ unsigned int freq;
+
+ ret = get_property(cpu, level, &freq, GET_FREQ);
+ if (ret)
+ return 0;
+
+ return freq;
+}
+
+/**
+ * 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.
+ *
+ * Function used to make sure the cpufreq layer is aware of current thermal
+ * limits. The limits are applied by updating the cpufreq policy.
+ *
+ * Return: 0 on success, an error code otherwise (-EINVAL in case wrong
+ * cooling state).
+ */
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+ unsigned long cooling_state)
+{
+ unsigned int 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;
+
+ 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
+ *
+ * Callback to highjack the notification on cpufreq policy transition.
+ * Every time there is a change in policy, we will intercept and
+ * update the cpufreq policy with thermal constraints.
+ *
+ * Return: 0 (success)
+ */
+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;
+ else
+ return 0;
+
+ /* 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.
+ *
+ * Callback for the thermal cooling device to return the cpufreq
+ * max cooling state.
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+ struct cpumask *mask = &cpufreq_device->allowed_cpus;
+ unsigned int cpu;
+ unsigned int count = 0;
+ int ret;
+
+ cpu = cpumask_any(mask);
+
+ ret = get_property(cpu, 0, &count, GET_MAXL);
+
+ if (count > 0)
+ *state = count;
+
+ 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.
+ *
+ * Callback for the thermal cooling device to return the cpufreq
+ * current cooling state.
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+
+ *state = cpufreq_device->cpufreq_state;
+
+ return 0;
+}
+
+/**
+ * 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.
+ *
+ * Callback for the thermal cooling device to change the cpufreq
+ * current cooling state.
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+
+ return cpufreq_apply_cooling(cpufreq_device, state);
+}
+
+/* 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 - helper function to create cpufreq cooling device
+ * @np: a valid struct device_node to the cooling device device tree node
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ * cooling devices. It also gives the opportunity to link the cooling device
+ * with a device tree node, in order to bind it via the thermal DT code.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+static struct thermal_cooling_device *
+__cpufreq_cooling_register(struct device_node *np,
+ const struct cpumask *clip_cpus)
+{
+ struct thermal_cooling_device *cool_dev;
+ struct cpufreq_cooling_device *cpufreq_dev = NULL;
+ unsigned int min = 0, max = 0;
+ char dev_name[THERMAL_NAME_LENGTH];
+ int ret = 0, i;
+ struct cpufreq_policy policy;
+
+ /* 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);
+
+ ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
+ if (ret) {
+ kfree(cpufreq_dev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d",
+ cpufreq_dev->id);
+
+ cool_dev = thermal_of_cooling_device_register(np, dev_name, cpufreq_dev,
+ &cpufreq_cooling_ops);
+ if (IS_ERR(cool_dev)) {
+ release_idr(&cpufreq_idr, cpufreq_dev->id);
+ kfree(cpufreq_dev);
+ return cool_dev;
+ }
+ cpufreq_dev->cool_dev = cool_dev;
+ cpufreq_dev->cpufreq_state = 0;
+ mutex_lock(&cooling_cpufreq_lock);
+
+ /* Register the notifier for first cpufreq cooling device */
+ if (cpufreq_dev_count == 0)
+ cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+ cpufreq_dev_count++;
+
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ return cool_dev;
+}
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ * cooling devices.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+cpufreq_cooling_register(const struct cpumask *clip_cpus)
+{
+ return __cpufreq_cooling_register(NULL, clip_cpus);
+}
+EXPORT_SYMBOL_GPL(cpufreq_cooling_register);
+
+/**
+ * of_cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @np: a valid struct device_node to the cooling device device tree node
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ * cooling devices. Using this API, the cpufreq cooling device will be
+ * linked to the device tree node provided.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+of_cpufreq_cooling_register(struct device_node *np,
+ const struct cpumask *clip_cpus)
+{
+ if (!np)
+ return ERR_PTR(-EINVAL);
+
+ return __cpufreq_cooling_register(np, clip_cpus);
+}
+EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ *
+ * This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct cpufreq_cooling_device *cpufreq_dev;
+
+ if (!cdev)
+ return;
+
+ cpufreq_dev = cdev->devdata;
+ mutex_lock(&cooling_cpufreq_lock);
+ cpufreq_dev_count--;
+
+ /* Unregister the notifier for the last cpufreq cooling device */
+ if (cpufreq_dev_count == 0)
+ cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
+ release_idr(&cpufreq_idr, cpufreq_dev->id);
+ kfree(cpufreq_dev);
+}
+EXPORT_SYMBOL_GPL(cpufreq_cooling_unregister);
diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c
new file mode 100644
index 00000000..786d1926
--- /dev/null
+++ b/drivers/thermal/db8500_cpufreq_cooling.c
@@ -0,0 +1,107 @@
+/*
+ * db8500_cpufreq_cooling.c - DB8500 cpufreq works as cooling device.
+ *
+ * Copyright (C) 2012 ST-Ericsson
+ * Copyright (C) 2012 Linaro Ltd.
+ *
+ * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/cpu_cooling.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+static int db8500_cpufreq_cooling_probe(struct platform_device *pdev)
+{
+ struct thermal_cooling_device *cdev;
+ struct cpumask mask_val;
+
+ /* make sure cpufreq driver has been initialized */
+ if (!cpufreq_frequency_get_table(0))
+ return -EPROBE_DEFER;
+
+ cpumask_set_cpu(0, &mask_val);
+ cdev = cpufreq_cooling_register(&mask_val);
+
+ if (IS_ERR(cdev)) {
+ dev_err(&pdev->dev, "Failed to register cooling device\n");
+ return PTR_ERR(cdev);
+ }
+
+ platform_set_drvdata(pdev, cdev);
+
+ dev_info(&pdev->dev, "Cooling device registered: %s\n", cdev->type);
+
+ return 0;
+}
+
+static int db8500_cpufreq_cooling_remove(struct platform_device *pdev)
+{
+ struct thermal_cooling_device *cdev = platform_get_drvdata(pdev);
+
+ cpufreq_cooling_unregister(cdev);
+
+ return 0;
+}
+
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return -ENOSYS;
+}
+
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev)
+{
+ return -ENOSYS;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id db8500_cpufreq_cooling_match[] = {
+ { .compatible = "stericsson,db8500-cpufreq-cooling" },
+ {},
+};
+#endif
+
+static struct platform_driver db8500_cpufreq_cooling_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "db8500-cpufreq-cooling",
+ .of_match_table = of_match_ptr(db8500_cpufreq_cooling_match),
+ },
+ .probe = db8500_cpufreq_cooling_probe,
+ .suspend = db8500_cpufreq_cooling_suspend,
+ .resume = db8500_cpufreq_cooling_resume,
+ .remove = db8500_cpufreq_cooling_remove,
+};
+
+static int __init db8500_cpufreq_cooling_init(void)
+{
+ return platform_driver_register(&db8500_cpufreq_cooling_driver);
+}
+
+static void __exit db8500_cpufreq_cooling_exit(void)
+{
+ platform_driver_unregister(&db8500_cpufreq_cooling_driver);
+}
+
+/* Should be later than db8500_cpufreq_register */
+late_initcall(db8500_cpufreq_cooling_init);
+module_exit(db8500_cpufreq_cooling_exit);
+
+MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
+MODULE_DESCRIPTION("DB8500 cpufreq cooling driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c
new file mode 100644
index 00000000..1e3b3bf9
--- /dev/null
+++ b/drivers/thermal/db8500_thermal.c
@@ -0,0 +1,534 @@
+/*
+ * db8500_thermal.c - DB8500 Thermal Management Implementation
+ *
+ * Copyright (C) 2012 ST-Ericsson
+ * Copyright (C) 2012 Linaro Ltd.
+ *
+ * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/cpu_cooling.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/dbx500-prcmu.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_data/db8500_thermal.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF
+#define PRCMU_DEFAULT_LOW_TEMP 0
+
+struct db8500_thermal_zone {
+ struct thermal_zone_device *therm_dev;
+ struct mutex th_lock;
+ struct work_struct therm_work;
+ struct db8500_thsens_platform_data *trip_tab;
+ enum thermal_device_mode mode;
+ enum thermal_trend trend;
+ unsigned long cur_temp_pseudo;
+ unsigned int cur_index;
+};
+
+/* Local function to check if thermal zone matches cooling devices */
+static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
+ struct db8500_trip_point *trip_point)
+{
+ int i;
+
+ if (!strlen(cdev->type))
+ return -EINVAL;
+
+ for (i = 0; i < COOLING_DEV_MAX; i++) {
+ if (!strcmp(trip_point->cdev_name[i], cdev->type))
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+/* Callback to bind cooling device to thermal zone */
+static int db8500_cdev_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned long max_state, upper, lower;
+ int i, ret = -EINVAL;
+
+ cdev->ops->get_max_state(cdev, &max_state);
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
+ continue;
+
+ upper = lower = i > max_state ? max_state : i;
+
+ ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
+ upper, lower);
+
+ dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
+ i, ret, ret ? "fail" : "succeed");
+ }
+
+ return ret;
+}
+
+/* Callback to unbind cooling device from thermal zone */
+static int db8500_cdev_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ int i, ret = -EINVAL;
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
+ continue;
+
+ ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
+
+ dev_info(&cdev->device, "%s unbind from %d: %s\n", cdev->type,
+ i, ret ? "fail" : "succeed");
+ }
+
+ return ret;
+}
+
+/* Callback to get current temperature */
+static int db8500_sys_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ /*
+ * TODO: There is no PRCMU interface to get temperature data currently,
+ * so a pseudo temperature is returned , it works for thermal framework
+ * and this will be fixed when the PRCMU interface is available.
+ */
+ *temp = pzone->cur_temp_pseudo;
+
+ return 0;
+}
+
+/* Callback to get temperature changing trend */
+static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trend *trend)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ *trend = pzone->trend;
+
+ return 0;
+}
+
+/* Callback to get thermal zone mode */
+static int db8500_sys_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ mutex_lock(&pzone->th_lock);
+ *mode = pzone->mode;
+ mutex_unlock(&pzone->th_lock);
+
+ return 0;
+}
+
+/* Callback to set thermal zone mode */
+static int db8500_sys_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ mutex_lock(&pzone->th_lock);
+
+ pzone->mode = mode;
+ if (mode == THERMAL_DEVICE_ENABLED)
+ schedule_work(&pzone->therm_work);
+
+ mutex_unlock(&pzone->th_lock);
+
+ return 0;
+}
+
+/* Callback to get trip point type */
+static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trip_type *type)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+
+ if (trip >= ptrips->num_trips)
+ return -EINVAL;
+
+ *type = ptrips->trip_points[trip].type;
+
+ return 0;
+}
+
+/* Callback to get trip point temperature */
+static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
+ int trip, unsigned long *temp)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+
+ if (trip >= ptrips->num_trips)
+ return -EINVAL;
+
+ *temp = ptrips->trip_points[trip].temp;
+
+ return 0;
+}
+
+/* Callback to get critical trip point temperature */
+static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ int i;
+
+ for (i = ptrips->num_trips - 1; i > 0; i--) {
+ if (ptrips->trip_points[i].type == THERMAL_TRIP_CRITICAL) {
+ *temp = ptrips->trip_points[i].temp;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static struct thermal_zone_device_ops thdev_ops = {
+ .bind = db8500_cdev_bind,
+ .unbind = db8500_cdev_unbind,
+ .get_temp = db8500_sys_get_temp,
+ .get_trend = db8500_sys_get_trend,
+ .get_mode = db8500_sys_get_mode,
+ .set_mode = db8500_sys_set_mode,
+ .get_trip_type = db8500_sys_get_trip_type,
+ .get_trip_temp = db8500_sys_get_trip_temp,
+ .get_crit_temp = db8500_sys_get_crit_temp,
+};
+
+static void db8500_thermal_update_config(struct db8500_thermal_zone *pzone,
+ unsigned int idx, enum thermal_trend trend,
+ unsigned long next_low, unsigned long next_high)
+{
+ prcmu_stop_temp_sense();
+
+ pzone->cur_index = idx;
+ pzone->cur_temp_pseudo = (next_low + next_high)/2;
+ pzone->trend = trend;
+
+ prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
+ prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
+}
+
+static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
+{
+ struct db8500_thermal_zone *pzone = irq_data;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned int idx = pzone->cur_index;
+ unsigned long next_low, next_high;
+
+ if (unlikely(idx == 0))
+ /* Meaningless for thermal management, ignoring it */
+ return IRQ_HANDLED;
+
+ if (idx == 1) {
+ next_high = ptrips->trip_points[0].temp;
+ next_low = PRCMU_DEFAULT_LOW_TEMP;
+ } else {
+ next_high = ptrips->trip_points[idx-1].temp;
+ next_low = ptrips->trip_points[idx-2].temp;
+ }
+ idx -= 1;
+
+ db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
+ next_low, next_high);
+
+ dev_dbg(&pzone->therm_dev->device,
+ "PRCMU set max %ld, min %ld\n", next_high, next_low);
+
+ schedule_work(&pzone->therm_work);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
+{
+ struct db8500_thermal_zone *pzone = irq_data;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned int idx = pzone->cur_index;
+ unsigned long next_low, next_high;
+
+ if (idx < ptrips->num_trips - 1) {
+ next_high = ptrips->trip_points[idx+1].temp;
+ next_low = ptrips->trip_points[idx].temp;
+ idx += 1;
+
+ db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING,
+ next_low, next_high);
+
+ dev_dbg(&pzone->therm_dev->device,
+ "PRCMU set max %ld, min %ld\n", next_high, next_low);
+ } else if (idx == ptrips->num_trips - 1)
+ pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
+
+ schedule_work(&pzone->therm_work);
+
+ return IRQ_HANDLED;
+}
+
+static void db8500_thermal_work(struct work_struct *work)
+{
+ enum thermal_device_mode cur_mode;
+ struct db8500_thermal_zone *pzone;
+
+ pzone = container_of(work, struct db8500_thermal_zone, therm_work);
+
+ mutex_lock(&pzone->th_lock);
+ cur_mode = pzone->mode;
+ mutex_unlock(&pzone->th_lock);
+
+ if (cur_mode == THERMAL_DEVICE_DISABLED)
+ return;
+
+ thermal_zone_device_update(pzone->therm_dev);
+ dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n");
+}
+
+#ifdef CONFIG_OF
+static struct db8500_thsens_platform_data*
+ db8500_thermal_parse_dt(struct platform_device *pdev)
+{
+ struct db8500_thsens_platform_data *ptrips;
+ struct device_node *np = pdev->dev.of_node;
+ char prop_name[32];
+ const char *tmp_str;
+ u32 tmp_data;
+ int i, j;
+
+ ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
+ if (!ptrips)
+ return NULL;
+
+ if (of_property_read_u32(np, "num-trips", &tmp_data))
+ goto err_parse_dt;
+
+ if (tmp_data > THERMAL_MAX_TRIPS)
+ goto err_parse_dt;
+
+ ptrips->num_trips = tmp_data;
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ sprintf(prop_name, "trip%d-temp", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data))
+ goto err_parse_dt;
+
+ ptrips->trip_points[i].temp = tmp_data;
+
+ sprintf(prop_name, "trip%d-type", i);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto err_parse_dt;
+
+ if (!strcmp(tmp_str, "active"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
+ else if (!strcmp(tmp_str, "passive"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
+ else if (!strcmp(tmp_str, "hot"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
+ else if (!strcmp(tmp_str, "critical"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ else
+ goto err_parse_dt;
+
+ sprintf(prop_name, "trip%d-cdev-num", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data))
+ goto err_parse_dt;
+
+ if (tmp_data > COOLING_DEV_MAX)
+ goto err_parse_dt;
+
+ for (j = 0; j < tmp_data; j++) {
+ sprintf(prop_name, "trip%d-cdev-name%d", i, j);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto err_parse_dt;
+
+ if (strlen(tmp_str) >= THERMAL_NAME_LENGTH)
+ goto err_parse_dt;
+
+ strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
+ }
+ }
+ return ptrips;
+
+err_parse_dt:
+ dev_err(&pdev->dev, "Parsing device tree data error.\n");
+ return NULL;
+}
+#else
+static inline struct db8500_thsens_platform_data*
+ db8500_thermal_parse_dt(struct platform_device *pdev)
+{
+ return NULL;
+}
+#endif
+
+static int db8500_thermal_probe(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *pzone = NULL;
+ struct db8500_thsens_platform_data *ptrips = NULL;
+ struct device_node *np = pdev->dev.of_node;
+ int low_irq, high_irq, ret = 0;
+ unsigned long dft_low, dft_high;
+
+ if (np)
+ ptrips = db8500_thermal_parse_dt(pdev);
+ else
+ ptrips = dev_get_platdata(&pdev->dev);
+
+ if (!ptrips)
+ return -EINVAL;
+
+ pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
+ if (!pzone)
+ return -ENOMEM;
+
+ mutex_init(&pzone->th_lock);
+ mutex_lock(&pzone->th_lock);
+
+ pzone->mode = THERMAL_DEVICE_DISABLED;
+ pzone->trip_tab = ptrips;
+
+ INIT_WORK(&pzone->therm_work, db8500_thermal_work);
+
+ low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
+ if (low_irq < 0) {
+ dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n");
+ ret = low_irq;
+ goto out_unlock;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
+ prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ "dbx500_temp_low", pzone);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
+ goto out_unlock;
+ }
+
+ high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
+ if (high_irq < 0) {
+ dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
+ ret = high_irq;
+ goto out_unlock;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
+ prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ "dbx500_temp_high", pzone);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
+ goto out_unlock;
+ }
+
+ pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
+ ptrips->num_trips, 0, pzone, &thdev_ops, NULL, 0, 0);
+
+ if (IS_ERR(pzone->therm_dev)) {
+ dev_err(&pdev->dev, "Register thermal zone device failed.\n");
+ ret = PTR_ERR(pzone->therm_dev);
+ goto out_unlock;
+ }
+ dev_info(&pdev->dev, "Thermal zone device registered.\n");
+
+ dft_low = PRCMU_DEFAULT_LOW_TEMP;
+ dft_high = ptrips->trip_points[0].temp;
+
+ db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
+ dft_low, dft_high);
+
+ platform_set_drvdata(pdev, pzone);
+ pzone->mode = THERMAL_DEVICE_ENABLED;
+
+out_unlock:
+ mutex_unlock(&pzone->th_lock);
+
+ return ret;
+}
+
+static int db8500_thermal_remove(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(pzone->therm_dev);
+ cancel_work_sync(&pzone->therm_work);
+ mutex_destroy(&pzone->th_lock);
+
+ return 0;
+}
+
+static int db8500_thermal_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
+
+ flush_work(&pzone->therm_work);
+ prcmu_stop_temp_sense();
+
+ return 0;
+}
+
+static int db8500_thermal_resume(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned long dft_low, dft_high;
+
+ dft_low = PRCMU_DEFAULT_LOW_TEMP;
+ dft_high = ptrips->trip_points[0].temp;
+
+ db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
+ dft_low, dft_high);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id db8500_thermal_match[] = {
+ { .compatible = "stericsson,db8500-thermal" },
+ {},
+};
+#endif
+
+static struct platform_driver db8500_thermal_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "db8500-thermal",
+ .of_match_table = of_match_ptr(db8500_thermal_match),
+ },
+ .probe = db8500_thermal_probe,
+ .suspend = db8500_thermal_suspend,
+ .resume = db8500_thermal_resume,
+ .remove = db8500_thermal_remove,
+};
+
+module_platform_driver(db8500_thermal_driver);
+
+MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
+MODULE_DESCRIPTION("DB8500 thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/dove_thermal.c b/drivers/thermal/dove_thermal.c
new file mode 100644
index 00000000..a088d136
--- /dev/null
+++ b/drivers/thermal/dove_thermal.c
@@ -0,0 +1,202 @@
+/*
+ * Dove thermal sensor driver
+ *
+ * Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#define DOVE_THERMAL_TEMP_OFFSET 1
+#define DOVE_THERMAL_TEMP_MASK 0x1FF
+
+/* Dove Thermal Manager Control and Status Register */
+#define PMU_TM_DISABLE_OFFS 0
+#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS)
+
+/* Dove Theraml Diode Control 0 Register */
+#define PMU_TDC0_SW_RST_MASK (0x1 << 1)
+#define PMU_TDC0_SEL_VCAL_OFFS 5
+#define PMU_TDC0_SEL_VCAL_MASK (0x3 << PMU_TDC0_SEL_VCAL_OFFS)
+#define PMU_TDC0_REF_CAL_CNT_OFFS 11
+#define PMU_TDC0_REF_CAL_CNT_MASK (0x1FF << PMU_TDC0_REF_CAL_CNT_OFFS)
+#define PMU_TDC0_AVG_NUM_OFFS 25
+#define PMU_TDC0_AVG_NUM_MASK (0x7 << PMU_TDC0_AVG_NUM_OFFS)
+
+/* Dove Thermal Diode Control 1 Register */
+#define PMU_TEMP_DIOD_CTRL1_REG 0x04
+#define PMU_TDC1_TEMP_VALID_MASK (0x1 << 10)
+
+/* Dove Thermal Sensor Dev Structure */
+struct dove_thermal_priv {
+ void __iomem *sensor;
+ void __iomem *control;
+};
+
+static int dove_init_sensor(const struct dove_thermal_priv *priv)
+{
+ u32 reg;
+ u32 i;
+
+ /* Configure the Diode Control Register #0 */
+ reg = readl_relaxed(priv->control);
+
+ /* Use average of 2 */
+ reg &= ~PMU_TDC0_AVG_NUM_MASK;
+ reg |= (0x1 << PMU_TDC0_AVG_NUM_OFFS);
+
+ /* Reference calibration value */
+ reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
+ reg |= (0x0F1 << PMU_TDC0_REF_CAL_CNT_OFFS);
+
+ /* Set the high level reference for calibration */
+ reg &= ~PMU_TDC0_SEL_VCAL_MASK;
+ reg |= (0x2 << PMU_TDC0_SEL_VCAL_OFFS);
+ writel(reg, priv->control);
+
+ /* Reset the sensor */
+ reg = readl_relaxed(priv->control);
+ writel((reg | PMU_TDC0_SW_RST_MASK), priv->control);
+ writel(reg, priv->control);
+
+ /* Enable the sensor */
+ reg = readl_relaxed(priv->sensor);
+ reg &= ~PMU_TM_DISABLE_MASK;
+ writel(reg, priv->sensor);
+
+ /* Poll the sensor for the first reading */
+ for (i = 0; i < 1000000; i++) {
+ reg = readl_relaxed(priv->sensor);
+ if (reg & DOVE_THERMAL_TEMP_MASK)
+ break;
+ }
+
+ if (i == 1000000)
+ return -EIO;
+
+ return 0;
+}
+
+static int dove_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ unsigned long reg;
+ struct dove_thermal_priv *priv = thermal->devdata;
+
+ /* Valid check */
+ reg = readl_relaxed(priv->control + PMU_TEMP_DIOD_CTRL1_REG);
+ if ((reg & PMU_TDC1_TEMP_VALID_MASK) == 0x0) {
+ dev_err(&thermal->device,
+ "Temperature sensor reading not valid\n");
+ return -EIO;
+ }
+
+ /*
+ * Calculate temperature. According to Marvell internal
+ * documentation the formula for this is:
+ * Celsius = (322-reg)/1.3625
+ */
+ reg = readl_relaxed(priv->sensor);
+ reg = (reg >> DOVE_THERMAL_TEMP_OFFSET) & DOVE_THERMAL_TEMP_MASK;
+ *temp = ((3220000000UL - (10000000UL * reg)) / 13625);
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops ops = {
+ .get_temp = dove_get_temp,
+};
+
+static const struct of_device_id dove_thermal_id_table[] = {
+ { .compatible = "marvell,dove-thermal" },
+ {}
+};
+
+static int dove_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *thermal = NULL;
+ struct dove_thermal_priv *priv;
+ struct resource *res;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get platform resource\n");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->sensor = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->sensor))
+ return PTR_ERR(priv->sensor);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ priv->control = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->control))
+ return PTR_ERR(priv->control);
+
+ ret = dove_init_sensor(priv);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to initialize sensor\n");
+ return ret;
+ }
+
+ thermal = thermal_zone_device_register("dove_thermal", 0, 0,
+ priv, &ops, NULL, 0, 0);
+ if (IS_ERR(thermal)) {
+ dev_err(&pdev->dev,
+ "Failed to register thermal zone device\n");
+ return PTR_ERR(thermal);
+ }
+
+ platform_set_drvdata(pdev, thermal);
+
+ return 0;
+}
+
+static int dove_thermal_exit(struct platform_device *pdev)
+{
+ struct thermal_zone_device *dove_thermal =
+ platform_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(dove_thermal);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+MODULE_DEVICE_TABLE(of, dove_thermal_id_table);
+
+static struct platform_driver dove_thermal_driver = {
+ .probe = dove_thermal_probe,
+ .remove = dove_thermal_exit,
+ .driver = {
+ .name = "dove_thermal",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(dove_thermal_id_table),
+ },
+};
+
+module_platform_driver(dove_thermal_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_DESCRIPTION("Dove thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/exynos_thermal.c b/drivers/thermal/exynos_thermal.c
new file mode 100644
index 00000000..788b1ddc
--- /dev/null
+++ b/drivers/thermal/exynos_thermal.c
@@ -0,0 +1,1061 @@
+/*
+ * 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>
+#include <linux/thermal.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu_cooling.h>
+#include <linux/of.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 << 12)
+#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 MAX_THRESHOLD_LEVS 4
+
+#define ACTIVE_INTERVAL 500
+#define IDLE_INTERVAL 10000
+#define MCELSIUS 1000
+
+#ifdef CONFIG_THERMAL_EMULATION
+#define EXYNOS_EMUL_TIME 0x57F0
+#define EXYNOS_EMUL_TIME_SHIFT 16
+#define EXYNOS_EMUL_DATA_SHIFT 8
+#define EXYNOS_EMUL_DATA_MASK 0xFF
+#define EXYNOS_EMUL_ENABLE 0x1
+#endif /* CONFIG_THERMAL_EMULATION */
+
+/* CPU Zone information */
+#define PANIC_ZONE 4
+#define WARN_ZONE 3
+#define MONITOR_ZONE 2
+#define SAFE_ZONE 1
+
+#define GET_ZONE(trip) (trip + 2)
+#define GET_TRIP(zone) (zone - 2)
+
+#define EXYNOS_ZONE_COUNT 3
+
+struct exynos_tmu_data {
+ struct exynos_tmu_platform_data *pdata;
+ struct resource *mem;
+ void __iomem *base;
+ int irq;
+ enum soc_type soc;
+ struct work_struct irq_work;
+ struct mutex lock;
+ struct clk *clk;
+ u8 temp_error1, temp_error2;
+};
+
+struct thermal_trip_point_conf {
+ int trip_val[MAX_TRIP_COUNT];
+ int trip_count;
+ u8 trigger_falling;
+};
+
+struct thermal_cooling_conf {
+ struct freq_clip_table freq_data[MAX_TRIP_COUNT];
+ int freq_clip_count;
+};
+
+struct thermal_sensor_conf {
+ char name[SENSOR_NAME_LEN];
+ int (*read_temperature)(void *data);
+ int (*write_emul_temp)(void *drv_data, unsigned long temp);
+ struct thermal_trip_point_conf trip_data;
+ struct thermal_cooling_conf cooling_data;
+ void *private_data;
+};
+
+struct exynos_thermal_zone {
+ enum thermal_device_mode mode;
+ struct thermal_zone_device *therm_dev;
+ struct thermal_cooling_device *cool_dev[MAX_COOLING_DEVICE];
+ unsigned int cool_dev_size;
+ struct platform_device *exynos4_dev;
+ struct thermal_sensor_conf *sensor_conf;
+ bool bind;
+};
+
+static struct exynos_thermal_zone *th_zone;
+static void exynos_unregister_thermal(void);
+static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf);
+
+/* Get mode callback functions for thermal zone */
+static int exynos_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ if (th_zone)
+ *mode = th_zone->mode;
+ return 0;
+}
+
+/* Set mode callback functions for thermal zone */
+static int exynos_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ if (!th_zone->therm_dev) {
+ pr_notice("thermal zone not registered\n");
+ return 0;
+ }
+
+ mutex_lock(&th_zone->therm_dev->lock);
+
+ if (mode == THERMAL_DEVICE_ENABLED &&
+ !th_zone->sensor_conf->trip_data.trigger_falling)
+ th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+ else
+ th_zone->therm_dev->polling_delay = 0;
+
+ mutex_unlock(&th_zone->therm_dev->lock);
+
+ th_zone->mode = mode;
+ thermal_zone_device_update(th_zone->therm_dev);
+ pr_info("thermal polling set for duration=%d msec\n",
+ th_zone->therm_dev->polling_delay);
+ return 0;
+}
+
+
+/* Get trip type callback functions for thermal zone */
+static int exynos_get_trip_type(struct thermal_zone_device *thermal, int trip,
+ enum thermal_trip_type *type)
+{
+ switch (GET_ZONE(trip)) {
+ case MONITOR_ZONE:
+ case WARN_ZONE:
+ *type = THERMAL_TRIP_ACTIVE;
+ break;
+ case PANIC_ZONE:
+ *type = THERMAL_TRIP_CRITICAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Get trip temperature callback functions for thermal zone */
+static int exynos_get_trip_temp(struct thermal_zone_device *thermal, int trip,
+ unsigned long *temp)
+{
+ if (trip < GET_TRIP(MONITOR_ZONE) || trip > GET_TRIP(PANIC_ZONE))
+ return -EINVAL;
+
+ *temp = th_zone->sensor_conf->trip_data.trip_val[trip];
+ /* convert the temperature into millicelsius */
+ *temp = *temp * MCELSIUS;
+
+ return 0;
+}
+
+/* Get critical temperature callback functions for thermal zone */
+static int exynos_get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ int ret;
+ /* Panic zone */
+ ret = exynos_get_trip_temp(thermal, GET_TRIP(PANIC_ZONE), temp);
+ return ret;
+}
+
+/* Bind callback functions for thermal zone */
+static int exynos_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ int ret = 0, i, tab_size, level;
+ struct freq_clip_table *tab_ptr, *clip_data;
+ struct thermal_sensor_conf *data = th_zone->sensor_conf;
+
+ tab_ptr = (struct freq_clip_table *)data->cooling_data.freq_data;
+ tab_size = data->cooling_data.freq_clip_count;
+
+ if (tab_ptr == NULL || tab_size == 0)
+ return -EINVAL;
+
+ /* find the cooling device registered*/
+ for (i = 0; i < th_zone->cool_dev_size; i++)
+ if (cdev == th_zone->cool_dev[i])
+ break;
+
+ /* No matching cooling device */
+ if (i == th_zone->cool_dev_size)
+ return 0;
+
+ /* Bind the thermal zone to the cpufreq cooling device */
+ for (i = 0; i < tab_size; i++) {
+ clip_data = (struct freq_clip_table *)&(tab_ptr[i]);
+ level = cpufreq_cooling_get_level(0, clip_data->freq_clip_max);
+ if (level == THERMAL_CSTATE_INVALID)
+ return 0;
+ switch (GET_ZONE(i)) {
+ case MONITOR_ZONE:
+ case WARN_ZONE:
+ if (thermal_zone_bind_cooling_device(thermal, i, cdev,
+ level, 0)) {
+ pr_err("error binding cdev inst %d\n", i);
+ ret = -EINVAL;
+ }
+ th_zone->bind = true;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ }
+
+ return ret;
+}
+
+/* Unbind callback functions for thermal zone */
+static int exynos_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ int ret = 0, i, tab_size;
+ struct thermal_sensor_conf *data = th_zone->sensor_conf;
+
+ if (th_zone->bind == false)
+ return 0;
+
+ tab_size = data->cooling_data.freq_clip_count;
+
+ if (tab_size == 0)
+ return -EINVAL;
+
+ /* find the cooling device registered*/
+ for (i = 0; i < th_zone->cool_dev_size; i++)
+ if (cdev == th_zone->cool_dev[i])
+ break;
+
+ /* No matching cooling device */
+ if (i == th_zone->cool_dev_size)
+ return 0;
+
+ /* Bind the thermal zone to the cpufreq cooling device */
+ for (i = 0; i < tab_size; i++) {
+ switch (GET_ZONE(i)) {
+ case MONITOR_ZONE:
+ case WARN_ZONE:
+ if (thermal_zone_unbind_cooling_device(thermal, i,
+ cdev)) {
+ pr_err("error unbinding cdev inst=%d\n", i);
+ ret = -EINVAL;
+ }
+ th_zone->bind = false;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ }
+ return ret;
+}
+
+/* Get temperature callback functions for thermal zone */
+static int exynos_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ void *data;
+
+ if (!th_zone->sensor_conf) {
+ pr_info("Temperature sensor not initialised\n");
+ return -EINVAL;
+ }
+ data = th_zone->sensor_conf->private_data;
+ *temp = th_zone->sensor_conf->read_temperature(data);
+ /* convert the temperature into millicelsius */
+ *temp = *temp * MCELSIUS;
+ return 0;
+}
+
+/* Get temperature callback functions for thermal zone */
+static int exynos_set_emul_temp(struct thermal_zone_device *thermal,
+ unsigned long temp)
+{
+ void *data;
+ int ret = -EINVAL;
+
+ if (!th_zone->sensor_conf) {
+ pr_info("Temperature sensor not initialised\n");
+ return -EINVAL;
+ }
+ data = th_zone->sensor_conf->private_data;
+ if (th_zone->sensor_conf->write_emul_temp)
+ ret = th_zone->sensor_conf->write_emul_temp(data, temp);
+ return ret;
+}
+
+/* Get the temperature trend */
+static int exynos_get_trend(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trend *trend)
+{
+ int ret;
+ unsigned long trip_temp;
+
+ ret = exynos_get_trip_temp(thermal, trip, &trip_temp);
+ if (ret < 0)
+ return ret;
+
+ if (thermal->temperature >= trip_temp)
+ *trend = THERMAL_TREND_RAISE_FULL;
+ else
+ *trend = THERMAL_TREND_DROP_FULL;
+
+ return 0;
+}
+/* Operation callback functions for thermal zone */
+static struct thermal_zone_device_ops const exynos_dev_ops = {
+ .bind = exynos_bind,
+ .unbind = exynos_unbind,
+ .get_temp = exynos_get_temp,
+ .set_emul_temp = exynos_set_emul_temp,
+ .get_trend = exynos_get_trend,
+ .get_mode = exynos_get_mode,
+ .set_mode = exynos_set_mode,
+ .get_trip_type = exynos_get_trip_type,
+ .get_trip_temp = exynos_get_trip_temp,
+ .get_crit_temp = exynos_get_crit_temp,
+};
+
+/*
+ * This function may be called from interrupt based temperature sensor
+ * when threshold is changed.
+ */
+static void exynos_report_trigger(void)
+{
+ unsigned int i;
+ char data[10];
+ char *envp[] = { data, NULL };
+
+ if (!th_zone || !th_zone->therm_dev)
+ return;
+ if (th_zone->bind == false) {
+ for (i = 0; i < th_zone->cool_dev_size; i++) {
+ if (!th_zone->cool_dev[i])
+ continue;
+ exynos_bind(th_zone->therm_dev,
+ th_zone->cool_dev[i]);
+ }
+ }
+
+ thermal_zone_device_update(th_zone->therm_dev);
+
+ mutex_lock(&th_zone->therm_dev->lock);
+ /* Find the level for which trip happened */
+ for (i = 0; i < th_zone->sensor_conf->trip_data.trip_count; i++) {
+ if (th_zone->therm_dev->last_temperature <
+ th_zone->sensor_conf->trip_data.trip_val[i] * MCELSIUS)
+ break;
+ }
+
+ if (th_zone->mode == THERMAL_DEVICE_ENABLED &&
+ !th_zone->sensor_conf->trip_data.trigger_falling) {
+ if (i > 0)
+ th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
+ else
+ th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
+ }
+
+ snprintf(data, sizeof(data), "%u", i);
+ kobject_uevent_env(&th_zone->therm_dev->device.kobj, KOBJ_CHANGE, envp);
+ mutex_unlock(&th_zone->therm_dev->lock);
+}
+
+/* Register with the in-kernel thermal management */
+static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
+{
+ int ret;
+ struct cpumask mask_val;
+
+ if (!sensor_conf || !sensor_conf->read_temperature) {
+ pr_err("Temperature sensor not initialised\n");
+ return -EINVAL;
+ }
+
+ th_zone = kzalloc(sizeof(struct exynos_thermal_zone), GFP_KERNEL);
+ if (!th_zone)
+ return -ENOMEM;
+
+ th_zone->sensor_conf = sensor_conf;
+ cpumask_set_cpu(0, &mask_val);
+ th_zone->cool_dev[0] = cpufreq_cooling_register(&mask_val);
+ if (IS_ERR(th_zone->cool_dev[0])) {
+ pr_err("Failed to register cpufreq cooling device\n");
+ ret = -EINVAL;
+ goto err_unregister;
+ }
+ th_zone->cool_dev_size++;
+
+ th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name,
+ EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, NULL, 0,
+ sensor_conf->trip_data.trigger_falling ?
+ 0 : IDLE_INTERVAL);
+
+ if (IS_ERR(th_zone->therm_dev)) {
+ pr_err("Failed to register thermal zone device\n");
+ ret = PTR_ERR(th_zone->therm_dev);
+ goto err_unregister;
+ }
+ th_zone->mode = THERMAL_DEVICE_ENABLED;
+
+ pr_info("Exynos: Kernel Thermal management registered\n");
+
+ return 0;
+
+err_unregister:
+ exynos_unregister_thermal();
+ return ret;
+}
+
+/* Un-Register with the in-kernel thermal management */
+static void exynos_unregister_thermal(void)
+{
+ int i;
+
+ if (!th_zone)
+ return;
+
+ if (th_zone->therm_dev)
+ thermal_zone_device_unregister(th_zone->therm_dev);
+
+ for (i = 0; i < th_zone->cool_dev_size; i++) {
+ if (th_zone->cool_dev[i])
+ cpufreq_cooling_unregister(th_zone->cool_dev[i]);
+ }
+
+ kfree(th_zone);
+ pr_info("Exynos: Kernel Thermal management unregistered\n");
+}
+
+/*
+ * TMU treats temperature as a mapped temperature code.
+ * The temperature is converted differently depending on the calibration type.
+ */
+static int temp_to_code(struct exynos_tmu_data *data, u8 temp)
+{
+ struct exynos_tmu_platform_data *pdata = data->pdata;
+ int temp_code;
+
+ 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:
+ 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 + EXYNOS_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 exynos_tmu_data *data, u8 temp_code)
+{
+ struct exynos_tmu_platform_data *pdata = data->pdata;
+ int temp;
+
+ 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:
+ 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 - EXYNOS_TMU_DEF_CODE_TO_TEMP_OFFSET;
+ break;
+ }
+out:
+ return temp;
+}
+
+static int exynos_tmu_initialize(struct platform_device *pdev)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ struct exynos_tmu_platform_data *pdata = data->pdata;
+ unsigned int status, trim_info;
+ unsigned int rising_threshold = 0, falling_threshold = 0;
+ int ret = 0, threshold_code, i, trigger_levs = 0;
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+
+ 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 + 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;
+
+ /* Count trigger levels to be enabled */
+ for (i = 0; i < MAX_THRESHOLD_LEVS; i++)
+ if (pdata->trigger_levels[i])
+ trigger_levs++;
+
+ if (data->soc == SOC_ARCH_EXYNOS4210) {
+ /* Write temperature code for threshold */
+ threshold_code = temp_to_code(data, pdata->threshold);
+ if (threshold_code < 0) {
+ ret = threshold_code;
+ goto out;
+ }
+ writeb(threshold_code,
+ data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
+ for (i = 0; i < trigger_levs; i++)
+ writeb(pdata->trigger_levels[i],
+ data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4);
+
+ writel(EXYNOS4210_TMU_INTCLEAR_VAL,
+ data->base + EXYNOS_TMU_REG_INTCLEAR);
+ } else if (data->soc == SOC_ARCH_EXYNOS) {
+ /* Write temperature code for rising and falling threshold */
+ for (i = 0; i < trigger_levs; i++) {
+ threshold_code = temp_to_code(data,
+ pdata->trigger_levels[i]);
+ if (threshold_code < 0) {
+ ret = threshold_code;
+ goto out;
+ }
+ rising_threshold |= threshold_code << 8 * i;
+ if (pdata->threshold_falling) {
+ threshold_code = temp_to_code(data,
+ pdata->trigger_levels[i] -
+ pdata->threshold_falling);
+ if (threshold_code > 0)
+ falling_threshold |=
+ threshold_code << 8 * i;
+ }
+ }
+
+ writel(rising_threshold,
+ data->base + EXYNOS_THD_TEMP_RISE);
+ writel(falling_threshold,
+ data->base + EXYNOS_THD_TEMP_FALL);
+
+ writel(EXYNOS_TMU_CLEAR_RISE_INT | EXYNOS_TMU_CLEAR_FALL_INT,
+ data->base + EXYNOS_TMU_REG_INTCLEAR);
+ }
+out:
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static void exynos_tmu_control(struct platform_device *pdev, bool on)
+{
+ 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 << 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 |= 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;
+ if (pdata->threshold_falling)
+ interrupt_en |= interrupt_en << 16;
+ } else {
+ con |= EXYNOS_TMU_CORE_OFF;
+ interrupt_en = 0; /* Disable all interrupts */
+ }
+ 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 exynos_tmu_read(struct exynos_tmu_data *data)
+{
+ u8 temp_code;
+ int temp;
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+
+ temp_code = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);
+ temp = code_to_temp(data, temp_code);
+
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+
+ return temp;
+}
+
+#ifdef CONFIG_THERMAL_EMULATION
+static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp)
+{
+ struct exynos_tmu_data *data = drv_data;
+ unsigned int reg;
+ int ret = -EINVAL;
+
+ if (data->soc == SOC_ARCH_EXYNOS4210)
+ goto out;
+
+ if (temp && temp < MCELSIUS)
+ goto out;
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+
+ reg = readl(data->base + EXYNOS_EMUL_CON);
+
+ if (temp) {
+ temp /= MCELSIUS;
+
+ reg = (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT) |
+ (temp_to_code(data, temp)
+ << EXYNOS_EMUL_DATA_SHIFT) | EXYNOS_EMUL_ENABLE;
+ } else {
+ reg &= ~EXYNOS_EMUL_ENABLE;
+ }
+
+ writel(reg, data->base + EXYNOS_EMUL_CON);
+
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+ return 0;
+out:
+ return ret;
+}
+#else
+static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp)
+ { return -EINVAL; }
+#endif/*CONFIG_THERMAL_EMULATION*/
+
+static void exynos_tmu_work(struct work_struct *work)
+{
+ struct exynos_tmu_data *data = container_of(work,
+ struct exynos_tmu_data, irq_work);
+
+ exynos_report_trigger();
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+ if (data->soc == SOC_ARCH_EXYNOS)
+ writel(EXYNOS_TMU_CLEAR_RISE_INT |
+ EXYNOS_TMU_CLEAR_FALL_INT,
+ data->base + EXYNOS_TMU_REG_INTCLEAR);
+ else
+ writel(EXYNOS4210_TMU_INTCLEAR_VAL,
+ data->base + EXYNOS_TMU_REG_INTCLEAR);
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+
+ enable_irq(data->irq);
+}
+
+static irqreturn_t exynos_tmu_irq(int irq, void *id)
+{
+ struct exynos_tmu_data *data = id;
+
+ disable_irq_nosync(irq);
+ schedule_work(&data->irq_work);
+
+ return IRQ_HANDLED;
+}
+static struct thermal_sensor_conf exynos_sensor_conf = {
+ .name = "exynos-therm",
+ .read_temperature = (int (*)(void *))exynos_tmu_read,
+ .write_emul_temp = exynos_tmu_set_emulation,
+};
+
+#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 = {
+ .threshold_falling = 10,
+ .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,exynos4412-tmu",
+ .data = (void *)EXYNOS_TMU_DRV_DATA,
+ },
+ {
+ .compatible = "samsung,exynos5250-tmu",
+ .data = (void *)EXYNOS_TMU_DRV_DATA,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_tmu_match);
+#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, exynos_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 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;
+ }
+ 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;
+ }
+
+ data->irq = platform_get_irq(pdev, 0);
+ if (data->irq < 0) {
+ dev_err(&pdev->dev, "Failed to get platform irq\n");
+ return data->irq;
+ }
+
+ INIT_WORK(&data->irq_work, exynos_tmu_work);
+
+ data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ data->base = devm_ioremap_resource(&pdev->dev, data->mem);
+ if (IS_ERR(data->base))
+ return PTR_ERR(data->base);
+
+ ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
+ IRQF_TRIGGER_RISING, "exynos-tmu", data);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
+ return ret;
+ }
+
+ data->clk = devm_clk_get(&pdev->dev, "tmu_apbif");
+ if (IS_ERR(data->clk)) {
+ dev_err(&pdev->dev, "Failed to get clock\n");
+ return PTR_ERR(data->clk);
+ }
+
+ ret = clk_prepare(data->clk);
+ if (ret)
+ return ret;
+
+ 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 = exynos_tmu_initialize(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to initialize TMU\n");
+ goto err_clk;
+ }
+
+ 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.trip_data.trigger_falling = pdata->threshold_falling;
+
+ exynos_sensor_conf.cooling_data.freq_clip_count =
+ pdata->freq_tab_count;
+ for (i = 0; i < pdata->freq_tab_count; i++) {
+ 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);
+ clk_unprepare(data->clk);
+ return ret;
+}
+
+static int exynos_tmu_remove(struct platform_device *pdev)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+
+ exynos_tmu_control(pdev, false);
+
+ exynos_unregister_thermal();
+
+ clk_unprepare(data->clk);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos_tmu_suspend(struct device *dev)
+{
+ exynos_tmu_control(to_platform_device(dev), false);
+
+ return 0;
+}
+
+static int exynos_tmu_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ exynos_tmu_initialize(pdev);
+ exynos_tmu_control(pdev, true);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(exynos_tmu_pm,
+ exynos_tmu_suspend, exynos_tmu_resume);
+#define EXYNOS_TMU_PM (&exynos_tmu_pm)
+#else
+#define EXYNOS_TMU_PM NULL
+#endif
+
+static struct platform_driver exynos_tmu_driver = {
+ .driver = {
+ .name = "exynos-tmu",
+ .owner = THIS_MODULE,
+ .pm = EXYNOS_TMU_PM,
+ .of_match_table = of_match_ptr(exynos_tmu_match),
+ },
+ .probe = exynos_tmu_probe,
+ .remove = exynos_tmu_remove,
+ .id_table = exynos_tmu_driver_ids,
+};
+
+module_platform_driver(exynos_tmu_driver);
+
+MODULE_DESCRIPTION("EXYNOS TMU Driver");
+MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:exynos-tmu");
diff --git a/drivers/thermal/fair_share.c b/drivers/thermal/fair_share.c
new file mode 100644
index 00000000..944ba2f3
--- /dev/null
+++ b/drivers/thermal/fair_share.c
@@ -0,0 +1,122 @@
+/*
+ * fair_share.c - A simple weight based Thermal governor
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/thermal.h>
+
+#include "thermal_core.h"
+
+/**
+ * get_trip_level: - obtains the current trip level for a zone
+ * @tz: thermal zone device
+ */
+static int get_trip_level(struct thermal_zone_device *tz)
+{
+ int count = 0;
+ unsigned long trip_temp;
+
+ if (tz->trips == 0 || !tz->ops->get_trip_temp)
+ return 0;
+
+ for (count = 0; count < tz->trips; count++) {
+ tz->ops->get_trip_temp(tz, count, &trip_temp);
+ if (tz->temperature < trip_temp)
+ break;
+ }
+ return count;
+}
+
+static long get_target_state(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev, int weight, int level)
+{
+ unsigned long max_state;
+
+ cdev->ops->get_max_state(cdev, &max_state);
+
+ return (long)(weight * level * max_state) / (100 * tz->trips);
+}
+
+/**
+ * fair_share_throttle - throttles devices asscciated with the given zone
+ * @tz - thermal_zone_device
+ *
+ * Throttling Logic: This uses three parameters to calculate the new
+ * throttle state of the cooling devices associated with the given zone.
+ *
+ * Parameters used for Throttling:
+ * P1. max_state: Maximum throttle state exposed by the cooling device.
+ * P2. weight[i]/100:
+ * How 'effective' the 'i'th device is, in cooling the given zone.
+ * P3. cur_trip_level/max_no_of_trips:
+ * This describes the extent to which the devices should be throttled.
+ * We do not want to throttle too much when we trip a lower temperature,
+ * whereas the throttling is at full swing if we trip critical levels.
+ * (Heavily assumes the trip points are in ascending order)
+ * new_state of cooling device = P3 * P2 * P1
+ */
+static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
+{
+ const struct thermal_zone_params *tzp;
+ struct thermal_cooling_device *cdev;
+ struct thermal_instance *instance;
+ int i;
+ int cur_trip_level = get_trip_level(tz);
+
+ if (!tz->tzp || !tz->tzp->tbp)
+ return -EINVAL;
+
+ tzp = tz->tzp;
+
+ for (i = 0; i < tzp->num_tbps; i++) {
+ if (!tzp->tbp[i].cdev)
+ continue;
+
+ cdev = tzp->tbp[i].cdev;
+ instance = get_thermal_instance(tz, cdev, trip);
+ if (!instance)
+ continue;
+
+ instance->target = get_target_state(tz, cdev,
+ tzp->tbp[i].weight, cur_trip_level);
+
+ instance->cdev->updated = false;
+ thermal_cdev_update(cdev);
+ }
+ return 0;
+}
+
+static struct thermal_governor thermal_gov_fair_share = {
+ .name = "fair_share",
+ .throttle = fair_share_throttle,
+};
+
+int thermal_gov_fair_share_register(void)
+{
+ return thermal_register_governor(&thermal_gov_fair_share);
+}
+
+void thermal_gov_fair_share_unregister(void)
+{
+ thermal_unregister_governor(&thermal_gov_fair_share);
+}
+
diff --git a/drivers/thermal/intel_powerclamp.c b/drivers/thermal/intel_powerclamp.c
new file mode 100644
index 00000000..b40b37cd
--- /dev/null
+++ b/drivers/thermal/intel_powerclamp.c
@@ -0,0 +1,795 @@
+/*
+ * intel_powerclamp.c - package c-state idle injection
+ *
+ * Copyright (c) 2012, Intel Corporation.
+ *
+ * Authors:
+ * Arjan van de Ven <arjan@linux.intel.com>
+ * Jacob Pan <jacob.jun.pan@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *
+ * TODO:
+ * 1. better handle wakeup from external interrupts, currently a fixed
+ * compensation is added to clamping duration when excessive amount
+ * of wakeups are observed during idle time. the reason is that in
+ * case of external interrupts without need for ack, clamping down
+ * cpu in non-irq context does not reduce irq. for majority of the
+ * cases, clamping down cpu does help reduce irq as well, we should
+ * be able to differenciate the two cases and give a quantitative
+ * solution for the irqs that we can control. perhaps based on
+ * get_cpu_iowait_time_us()
+ *
+ * 2. synchronization with other hw blocks
+ *
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/cpu.h>
+#include <linux/thermal.h>
+#include <linux/slab.h>
+#include <linux/tick.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/sched/rt.h>
+
+#include <asm/nmi.h>
+#include <asm/msr.h>
+#include <asm/mwait.h>
+#include <asm/cpu_device_id.h>
+#include <asm/idle.h>
+#include <asm/hardirq.h>
+
+#define MAX_TARGET_RATIO (50U)
+/* For each undisturbed clamping period (no extra wake ups during idle time),
+ * we increment the confidence counter for the given target ratio.
+ * CONFIDENCE_OK defines the level where runtime calibration results are
+ * valid.
+ */
+#define CONFIDENCE_OK (3)
+/* Default idle injection duration, driver adjust sleep time to meet target
+ * idle ratio. Similar to frequency modulation.
+ */
+#define DEFAULT_DURATION_JIFFIES (6)
+
+static unsigned int target_mwait;
+static struct dentry *debug_dir;
+
+/* user selected target */
+static unsigned int set_target_ratio;
+static unsigned int current_ratio;
+static bool should_skip;
+static bool reduce_irq;
+static atomic_t idle_wakeup_counter;
+static unsigned int control_cpu; /* The cpu assigned to collect stat and update
+ * control parameters. default to BSP but BSP
+ * can be offlined.
+ */
+static bool clamping;
+
+
+static struct task_struct * __percpu *powerclamp_thread;
+static struct thermal_cooling_device *cooling_dev;
+static unsigned long *cpu_clamping_mask; /* bit map for tracking per cpu
+ * clamping thread
+ */
+
+static unsigned int duration;
+static unsigned int pkg_cstate_ratio_cur;
+static unsigned int window_size;
+
+static int duration_set(const char *arg, const struct kernel_param *kp)
+{
+ int ret = 0;
+ unsigned long new_duration;
+
+ ret = kstrtoul(arg, 10, &new_duration);
+ if (ret)
+ goto exit;
+ if (new_duration > 25 || new_duration < 6) {
+ pr_err("Out of recommended range %lu, between 6-25ms\n",
+ new_duration);
+ ret = -EINVAL;
+ }
+
+ duration = clamp(new_duration, 6ul, 25ul);
+ smp_mb();
+
+exit:
+
+ return ret;
+}
+
+static struct kernel_param_ops duration_ops = {
+ .set = duration_set,
+ .get = param_get_int,
+};
+
+
+module_param_cb(duration, &duration_ops, &duration, 0644);
+MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec.");
+
+struct powerclamp_calibration_data {
+ unsigned long confidence; /* used for calibration, basically a counter
+ * gets incremented each time a clamping
+ * period is completed without extra wakeups
+ * once that counter is reached given level,
+ * compensation is deemed usable.
+ */
+ unsigned long steady_comp; /* steady state compensation used when
+ * no extra wakeups occurred.
+ */
+ unsigned long dynamic_comp; /* compensate excessive wakeup from idle
+ * mostly from external interrupts.
+ */
+};
+
+static struct powerclamp_calibration_data cal_data[MAX_TARGET_RATIO];
+
+static int window_size_set(const char *arg, const struct kernel_param *kp)
+{
+ int ret = 0;
+ unsigned long new_window_size;
+
+ ret = kstrtoul(arg, 10, &new_window_size);
+ if (ret)
+ goto exit_win;
+ if (new_window_size > 10 || new_window_size < 2) {
+ pr_err("Out of recommended window size %lu, between 2-10\n",
+ new_window_size);
+ ret = -EINVAL;
+ }
+
+ window_size = clamp(new_window_size, 2ul, 10ul);
+ smp_mb();
+
+exit_win:
+
+ return ret;
+}
+
+static struct kernel_param_ops window_size_ops = {
+ .set = window_size_set,
+ .get = param_get_int,
+};
+
+module_param_cb(window_size, &window_size_ops, &window_size, 0644);
+MODULE_PARM_DESC(window_size, "sliding window in number of clamping cycles\n"
+ "\tpowerclamp controls idle ratio within this window. larger\n"
+ "\twindow size results in slower response time but more smooth\n"
+ "\tclamping results. default to 2.");
+
+static void find_target_mwait(void)
+{
+ unsigned int eax, ebx, ecx, edx;
+ unsigned int highest_cstate = 0;
+ unsigned int highest_subcstate = 0;
+ int i;
+
+ if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF)
+ return;
+
+ cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &edx);
+
+ if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) ||
+ !(ecx & CPUID5_ECX_INTERRUPT_BREAK))
+ return;
+
+ edx >>= MWAIT_SUBSTATE_SIZE;
+ for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) {
+ if (edx & MWAIT_SUBSTATE_MASK) {
+ highest_cstate = i;
+ highest_subcstate = edx & MWAIT_SUBSTATE_MASK;
+ }
+ }
+ target_mwait = (highest_cstate << MWAIT_SUBSTATE_SIZE) |
+ (highest_subcstate - 1);
+
+}
+
+static u64 pkg_state_counter(void)
+{
+ u64 val;
+ u64 count = 0;
+
+ static bool skip_c2;
+ static bool skip_c3;
+ static bool skip_c6;
+ static bool skip_c7;
+
+ if (!skip_c2) {
+ if (!rdmsrl_safe(MSR_PKG_C2_RESIDENCY, &val))
+ count += val;
+ else
+ skip_c2 = true;
+ }
+
+ if (!skip_c3) {
+ if (!rdmsrl_safe(MSR_PKG_C3_RESIDENCY, &val))
+ count += val;
+ else
+ skip_c3 = true;
+ }
+
+ if (!skip_c6) {
+ if (!rdmsrl_safe(MSR_PKG_C6_RESIDENCY, &val))
+ count += val;
+ else
+ skip_c6 = true;
+ }
+
+ if (!skip_c7) {
+ if (!rdmsrl_safe(MSR_PKG_C7_RESIDENCY, &val))
+ count += val;
+ else
+ skip_c7 = true;
+ }
+
+ return count;
+}
+
+static void noop_timer(unsigned long foo)
+{
+ /* empty... just the fact that we get the interrupt wakes us up */
+}
+
+static unsigned int get_compensation(int ratio)
+{
+ unsigned int comp = 0;
+
+ /* we only use compensation if all adjacent ones are good */
+ if (ratio == 1 &&
+ cal_data[ratio].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio + 1].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio + 2].confidence >= CONFIDENCE_OK) {
+ comp = (cal_data[ratio].steady_comp +
+ cal_data[ratio + 1].steady_comp +
+ cal_data[ratio + 2].steady_comp) / 3;
+ } else if (ratio == MAX_TARGET_RATIO - 1 &&
+ cal_data[ratio].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio - 1].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio - 2].confidence >= CONFIDENCE_OK) {
+ comp = (cal_data[ratio].steady_comp +
+ cal_data[ratio - 1].steady_comp +
+ cal_data[ratio - 2].steady_comp) / 3;
+ } else if (cal_data[ratio].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio - 1].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio + 1].confidence >= CONFIDENCE_OK) {
+ comp = (cal_data[ratio].steady_comp +
+ cal_data[ratio - 1].steady_comp +
+ cal_data[ratio + 1].steady_comp) / 3;
+ }
+
+ /* REVISIT: simple penalty of double idle injection */
+ if (reduce_irq)
+ comp = ratio;
+ /* do not exceed limit */
+ if (comp + ratio >= MAX_TARGET_RATIO)
+ comp = MAX_TARGET_RATIO - ratio - 1;
+
+ return comp;
+}
+
+static void adjust_compensation(int target_ratio, unsigned int win)
+{
+ int delta;
+ struct powerclamp_calibration_data *d = &cal_data[target_ratio];
+
+ /*
+ * adjust compensations if confidence level has not been reached or
+ * there are too many wakeups during the last idle injection period, we
+ * cannot trust the data for compensation.
+ */
+ if (d->confidence >= CONFIDENCE_OK ||
+ atomic_read(&idle_wakeup_counter) >
+ win * num_online_cpus())
+ return;
+
+ delta = set_target_ratio - current_ratio;
+ /* filter out bad data */
+ if (delta >= 0 && delta <= (1+target_ratio/10)) {
+ if (d->steady_comp)
+ d->steady_comp =
+ roundup(delta+d->steady_comp, 2)/2;
+ else
+ d->steady_comp = delta;
+ d->confidence++;
+ }
+}
+
+static bool powerclamp_adjust_controls(unsigned int target_ratio,
+ unsigned int guard, unsigned int win)
+{
+ static u64 msr_last, tsc_last;
+ u64 msr_now, tsc_now;
+ u64 val64;
+
+ /* check result for the last window */
+ msr_now = pkg_state_counter();
+ rdtscll(tsc_now);
+
+ /* calculate pkg cstate vs tsc ratio */
+ if (!msr_last || !tsc_last)
+ current_ratio = 1;
+ else if (tsc_now-tsc_last) {
+ val64 = 100*(msr_now-msr_last);
+ do_div(val64, (tsc_now-tsc_last));
+ current_ratio = val64;
+ }
+
+ /* update record */
+ msr_last = msr_now;
+ tsc_last = tsc_now;
+
+ adjust_compensation(target_ratio, win);
+ /*
+ * too many external interrupts, set flag such
+ * that we can take measure later.
+ */
+ reduce_irq = atomic_read(&idle_wakeup_counter) >=
+ 2 * win * num_online_cpus();
+
+ atomic_set(&idle_wakeup_counter, 0);
+ /* if we are above target+guard, skip */
+ return set_target_ratio + guard <= current_ratio;
+}
+
+static int clamp_thread(void *arg)
+{
+ int cpunr = (unsigned long)arg;
+ DEFINE_TIMER(wakeup_timer, noop_timer, 0, 0);
+ static const struct sched_param param = {
+ .sched_priority = MAX_USER_RT_PRIO/2,
+ };
+ unsigned int count = 0;
+ unsigned int target_ratio;
+
+ set_bit(cpunr, cpu_clamping_mask);
+ set_freezable();
+ init_timer_on_stack(&wakeup_timer);
+ sched_setscheduler(current, SCHED_FIFO, &param);
+
+ while (true == clamping && !kthread_should_stop() &&
+ cpu_online(cpunr)) {
+ int sleeptime;
+ unsigned long target_jiffies;
+ unsigned int guard;
+ unsigned int compensation = 0;
+ int interval; /* jiffies to sleep for each attempt */
+ unsigned int duration_jiffies = msecs_to_jiffies(duration);
+ unsigned int window_size_now;
+
+ try_to_freeze();
+ /*
+ * make sure user selected ratio does not take effect until
+ * the next round. adjust target_ratio if user has changed
+ * target such that we can converge quickly.
+ */
+ target_ratio = set_target_ratio;
+ guard = 1 + target_ratio/20;
+ window_size_now = window_size;
+ count++;
+
+ /*
+ * systems may have different ability to enter package level
+ * c-states, thus we need to compensate the injected idle ratio
+ * to achieve the actual target reported by the HW.
+ */
+ compensation = get_compensation(target_ratio);
+ interval = duration_jiffies*100/(target_ratio+compensation);
+
+ /* align idle time */
+ target_jiffies = roundup(jiffies, interval);
+ sleeptime = target_jiffies - jiffies;
+ if (sleeptime <= 0)
+ sleeptime = 1;
+ schedule_timeout_interruptible(sleeptime);
+ /*
+ * only elected controlling cpu can collect stats and update
+ * control parameters.
+ */
+ if (cpunr == control_cpu && !(count%window_size_now)) {
+ should_skip =
+ powerclamp_adjust_controls(target_ratio,
+ guard, window_size_now);
+ smp_mb();
+ }
+
+ if (should_skip)
+ continue;
+
+ target_jiffies = jiffies + duration_jiffies;
+ mod_timer(&wakeup_timer, target_jiffies);
+ if (unlikely(local_softirq_pending()))
+ continue;
+ /*
+ * stop tick sched during idle time, interrupts are still
+ * allowed. thus jiffies are updated properly.
+ */
+ preempt_disable();
+ tick_nohz_idle_enter();
+ /* mwait until target jiffies is reached */
+ while (time_before(jiffies, target_jiffies)) {
+ unsigned long ecx = 1;
+ unsigned long eax = target_mwait;
+
+ /*
+ * REVISIT: may call enter_idle() to notify drivers who
+ * can save power during cpu idle. same for exit_idle()
+ */
+ local_touch_nmi();
+ stop_critical_timings();
+ __monitor((void *)&current_thread_info()->flags, 0, 0);
+ cpu_relax(); /* allow HT sibling to run */
+ __mwait(eax, ecx);
+ start_critical_timings();
+ atomic_inc(&idle_wakeup_counter);
+ }
+ tick_nohz_idle_exit();
+ preempt_enable_no_resched();
+ }
+ del_timer_sync(&wakeup_timer);
+ clear_bit(cpunr, cpu_clamping_mask);
+
+ return 0;
+}
+
+/*
+ * 1 HZ polling while clamping is active, useful for userspace
+ * to monitor actual idle ratio.
+ */
+static void poll_pkg_cstate(struct work_struct *dummy);
+static DECLARE_DELAYED_WORK(poll_pkg_cstate_work, poll_pkg_cstate);
+static void poll_pkg_cstate(struct work_struct *dummy)
+{
+ static u64 msr_last;
+ static u64 tsc_last;
+ static unsigned long jiffies_last;
+
+ u64 msr_now;
+ unsigned long jiffies_now;
+ u64 tsc_now;
+ u64 val64;
+
+ msr_now = pkg_state_counter();
+ rdtscll(tsc_now);
+ jiffies_now = jiffies;
+
+ /* calculate pkg cstate vs tsc ratio */
+ if (!msr_last || !tsc_last)
+ pkg_cstate_ratio_cur = 1;
+ else {
+ if (tsc_now - tsc_last) {
+ val64 = 100 * (msr_now - msr_last);
+ do_div(val64, (tsc_now - tsc_last));
+ pkg_cstate_ratio_cur = val64;
+ }
+ }
+
+ /* update record */
+ msr_last = msr_now;
+ jiffies_last = jiffies_now;
+ tsc_last = tsc_now;
+
+ if (true == clamping)
+ schedule_delayed_work(&poll_pkg_cstate_work, HZ);
+}
+
+static int start_power_clamp(void)
+{
+ unsigned long cpu;
+ struct task_struct *thread;
+
+ /* check if pkg cstate counter is completely 0, abort in this case */
+ if (!pkg_state_counter()) {
+ pr_err("pkg cstate counter not functional, abort\n");
+ return -EINVAL;
+ }
+
+ set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1);
+ /* prevent cpu hotplug */
+ get_online_cpus();
+
+ /* prefer BSP */
+ control_cpu = 0;
+ if (!cpu_online(control_cpu))
+ control_cpu = smp_processor_id();
+
+ clamping = true;
+ schedule_delayed_work(&poll_pkg_cstate_work, 0);
+
+ /* start one thread per online cpu */
+ for_each_online_cpu(cpu) {
+ struct task_struct **p =
+ per_cpu_ptr(powerclamp_thread, cpu);
+
+ thread = kthread_create_on_node(clamp_thread,
+ (void *) cpu,
+ cpu_to_node(cpu),
+ "kidle_inject/%ld", cpu);
+ /* bind to cpu here */
+ if (likely(!IS_ERR(thread))) {
+ kthread_bind(thread, cpu);
+ wake_up_process(thread);
+ *p = thread;
+ }
+
+ }
+ put_online_cpus();
+
+ return 0;
+}
+
+static void end_power_clamp(void)
+{
+ int i;
+ struct task_struct *thread;
+
+ clamping = false;
+ /*
+ * make clamping visible to other cpus and give per cpu clamping threads
+ * sometime to exit, or gets killed later.
+ */
+ smp_mb();
+ msleep(20);
+ if (bitmap_weight(cpu_clamping_mask, num_possible_cpus())) {
+ for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) {
+ pr_debug("clamping thread for cpu %d alive, kill\n", i);
+ thread = *per_cpu_ptr(powerclamp_thread, i);
+ kthread_stop(thread);
+ }
+ }
+}
+
+static int powerclamp_cpu_callback(struct notifier_block *nfb,
+ unsigned long action, void *hcpu)
+{
+ unsigned long cpu = (unsigned long)hcpu;
+ struct task_struct *thread;
+ struct task_struct **percpu_thread =
+ per_cpu_ptr(powerclamp_thread, cpu);
+
+ if (false == clamping)
+ goto exit_ok;
+
+ switch (action) {
+ case CPU_ONLINE:
+ thread = kthread_create_on_node(clamp_thread,
+ (void *) cpu,
+ cpu_to_node(cpu),
+ "kidle_inject/%lu", cpu);
+ if (likely(!IS_ERR(thread))) {
+ kthread_bind(thread, cpu);
+ wake_up_process(thread);
+ *percpu_thread = thread;
+ }
+ /* prefer BSP as controlling CPU */
+ if (cpu == 0) {
+ control_cpu = 0;
+ smp_mb();
+ }
+ break;
+ case CPU_DEAD:
+ if (test_bit(cpu, cpu_clamping_mask)) {
+ pr_err("cpu %lu dead but powerclamping thread is not\n",
+ cpu);
+ kthread_stop(*percpu_thread);
+ }
+ if (cpu == control_cpu) {
+ control_cpu = smp_processor_id();
+ smp_mb();
+ }
+ }
+
+exit_ok:
+ return NOTIFY_OK;
+}
+
+static struct notifier_block powerclamp_cpu_notifier = {
+ .notifier_call = powerclamp_cpu_callback,
+};
+
+static int powerclamp_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ *state = MAX_TARGET_RATIO;
+
+ return 0;
+}
+
+static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ if (true == clamping)
+ *state = pkg_cstate_ratio_cur;
+ else
+ /* to save power, do not poll idle ratio while not clamping */
+ *state = -1; /* indicates invalid state */
+
+ return 0;
+}
+
+static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long new_target_ratio)
+{
+ int ret = 0;
+
+ new_target_ratio = clamp(new_target_ratio, 0UL,
+ (unsigned long) (MAX_TARGET_RATIO-1));
+ if (set_target_ratio == 0 && new_target_ratio > 0) {
+ pr_info("Start idle injection to reduce power\n");
+ set_target_ratio = new_target_ratio;
+ ret = start_power_clamp();
+ goto exit_set;
+ } else if (set_target_ratio > 0 && new_target_ratio == 0) {
+ pr_info("Stop forced idle injection\n");
+ set_target_ratio = 0;
+ end_power_clamp();
+ } else /* adjust currently running */ {
+ set_target_ratio = new_target_ratio;
+ /* make new set_target_ratio visible to other cpus */
+ smp_mb();
+ }
+
+exit_set:
+ return ret;
+}
+
+/* bind to generic thermal layer as cooling device*/
+static struct thermal_cooling_device_ops powerclamp_cooling_ops = {
+ .get_max_state = powerclamp_get_max_state,
+ .get_cur_state = powerclamp_get_cur_state,
+ .set_cur_state = powerclamp_set_cur_state,
+};
+
+/* runs on Nehalem and later */
+static const struct x86_cpu_id intel_powerclamp_ids[] = {
+ { X86_VENDOR_INTEL, 6, 0x1a},
+ { X86_VENDOR_INTEL, 6, 0x1c},
+ { X86_VENDOR_INTEL, 6, 0x1e},
+ { X86_VENDOR_INTEL, 6, 0x1f},
+ { X86_VENDOR_INTEL, 6, 0x25},
+ { X86_VENDOR_INTEL, 6, 0x26},
+ { X86_VENDOR_INTEL, 6, 0x2a},
+ { X86_VENDOR_INTEL, 6, 0x2c},
+ { X86_VENDOR_INTEL, 6, 0x2d},
+ { X86_VENDOR_INTEL, 6, 0x2e},
+ { X86_VENDOR_INTEL, 6, 0x2f},
+ { X86_VENDOR_INTEL, 6, 0x3a},
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids);
+
+static int powerclamp_probe(void)
+{
+ if (!x86_match_cpu(intel_powerclamp_ids)) {
+ pr_err("Intel powerclamp does not run on family %d model %d\n",
+ boot_cpu_data.x86, boot_cpu_data.x86_model);
+ return -ENODEV;
+ }
+ if (!boot_cpu_has(X86_FEATURE_NONSTOP_TSC) ||
+ !boot_cpu_has(X86_FEATURE_CONSTANT_TSC) ||
+ !boot_cpu_has(X86_FEATURE_MWAIT) ||
+ !boot_cpu_has(X86_FEATURE_ARAT))
+ return -ENODEV;
+
+ /* find the deepest mwait value */
+ find_target_mwait();
+
+ return 0;
+}
+
+static int powerclamp_debug_show(struct seq_file *m, void *unused)
+{
+ int i = 0;
+
+ seq_printf(m, "controlling cpu: %d\n", control_cpu);
+ seq_printf(m, "pct confidence steady dynamic (compensation)\n");
+ for (i = 0; i < MAX_TARGET_RATIO; i++) {
+ seq_printf(m, "%d\t%lu\t%lu\t%lu\n",
+ i,
+ cal_data[i].confidence,
+ cal_data[i].steady_comp,
+ cal_data[i].dynamic_comp);
+ }
+
+ return 0;
+}
+
+static int powerclamp_debug_open(struct inode *inode,
+ struct file *file)
+{
+ return single_open(file, powerclamp_debug_show, inode->i_private);
+}
+
+static const struct file_operations powerclamp_debug_fops = {
+ .open = powerclamp_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+static inline void powerclamp_create_debug_files(void)
+{
+ debug_dir = debugfs_create_dir("intel_powerclamp", NULL);
+ if (!debug_dir)
+ return;
+
+ if (!debugfs_create_file("powerclamp_calib", S_IRUGO, debug_dir,
+ cal_data, &powerclamp_debug_fops))
+ goto file_error;
+
+ return;
+
+file_error:
+ debugfs_remove_recursive(debug_dir);
+}
+
+static int powerclamp_init(void)
+{
+ int retval;
+ int bitmap_size;
+
+ bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long);
+ cpu_clamping_mask = kzalloc(bitmap_size, GFP_KERNEL);
+ if (!cpu_clamping_mask)
+ return -ENOMEM;
+
+ /* probe cpu features and ids here */
+ retval = powerclamp_probe();
+ if (retval)
+ return retval;
+ /* set default limit, maybe adjusted during runtime based on feedback */
+ window_size = 2;
+ register_hotcpu_notifier(&powerclamp_cpu_notifier);
+ powerclamp_thread = alloc_percpu(struct task_struct *);
+ cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL,
+ &powerclamp_cooling_ops);
+ if (IS_ERR(cooling_dev))
+ return -ENODEV;
+
+ if (!duration)
+ duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES);
+ powerclamp_create_debug_files();
+
+ return 0;
+}
+module_init(powerclamp_init);
+
+static void powerclamp_exit(void)
+{
+ unregister_hotcpu_notifier(&powerclamp_cpu_notifier);
+ end_power_clamp();
+ free_percpu(powerclamp_thread);
+ thermal_cooling_device_unregister(cooling_dev);
+ kfree(cpu_clamping_mask);
+
+ cancel_delayed_work_sync(&poll_pkg_cstate_work);
+ debugfs_remove_recursive(debug_dir);
+}
+module_exit(powerclamp_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
+MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
+MODULE_DESCRIPTION("Package Level C-state Idle Injection for Intel CPUs");
diff --git a/drivers/thermal/kirkwood_thermal.c b/drivers/thermal/kirkwood_thermal.c
new file mode 100644
index 00000000..dfeceaff
--- /dev/null
+++ b/drivers/thermal/kirkwood_thermal.c
@@ -0,0 +1,132 @@
+/*
+ * Kirkwood thermal sensor driver
+ *
+ * Copyright (C) 2012 Nobuhiro Iwamatsu <iwamatsu@nigauri.org>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#define KIRKWOOD_THERMAL_VALID_OFFSET 9
+#define KIRKWOOD_THERMAL_VALID_MASK 0x1
+#define KIRKWOOD_THERMAL_TEMP_OFFSET 10
+#define KIRKWOOD_THERMAL_TEMP_MASK 0x1FF
+
+/* Kirkwood Thermal Sensor Dev Structure */
+struct kirkwood_thermal_priv {
+ void __iomem *sensor;
+};
+
+static int kirkwood_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ unsigned long reg;
+ struct kirkwood_thermal_priv *priv = thermal->devdata;
+
+ reg = readl_relaxed(priv->sensor);
+
+ /* Valid check */
+ if (!((reg >> KIRKWOOD_THERMAL_VALID_OFFSET) &
+ KIRKWOOD_THERMAL_VALID_MASK)) {
+ dev_err(&thermal->device,
+ "Temperature sensor reading not valid\n");
+ return -EIO;
+ }
+
+ /*
+ * Calculate temperature. According to Marvell internal
+ * documentation the formula for this is:
+ * Celsius = (322-reg)/1.3625
+ */
+ reg = (reg >> KIRKWOOD_THERMAL_TEMP_OFFSET) &
+ KIRKWOOD_THERMAL_TEMP_MASK;
+ *temp = ((3220000000UL - (10000000UL * reg)) / 13625);
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops ops = {
+ .get_temp = kirkwood_get_temp,
+};
+
+static const struct of_device_id kirkwood_thermal_id_table[] = {
+ { .compatible = "marvell,kirkwood-thermal" },
+ {}
+};
+
+static int kirkwood_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *thermal = NULL;
+ struct kirkwood_thermal_priv *priv;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get platform resource\n");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->sensor = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->sensor))
+ return PTR_ERR(priv->sensor);
+
+ thermal = thermal_zone_device_register("kirkwood_thermal", 0, 0,
+ priv, &ops, NULL, 0, 0);
+ if (IS_ERR(thermal)) {
+ dev_err(&pdev->dev,
+ "Failed to register thermal zone device\n");
+ return PTR_ERR(thermal);
+ }
+
+ platform_set_drvdata(pdev, thermal);
+
+ return 0;
+}
+
+static int kirkwood_thermal_exit(struct platform_device *pdev)
+{
+ struct thermal_zone_device *kirkwood_thermal =
+ platform_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(kirkwood_thermal);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+MODULE_DEVICE_TABLE(of, kirkwood_thermal_id_table);
+
+static struct platform_driver kirkwood_thermal_driver = {
+ .probe = kirkwood_thermal_probe,
+ .remove = kirkwood_thermal_exit,
+ .driver = {
+ .name = "kirkwood_thermal",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(kirkwood_thermal_id_table),
+ },
+};
+
+module_platform_driver(kirkwood_thermal_driver);
+
+MODULE_AUTHOR("Nobuhiro Iwamatsu <iwamatsu@nigauri.org>");
+MODULE_DESCRIPTION("kirkwood thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c
new file mode 100644
index 00000000..4b2b999b
--- /dev/null
+++ b/drivers/thermal/of-thermal.c
@@ -0,0 +1,850 @@
+/*
+ * of-thermal.c - Generic Thermal Management device tree support.
+ *
+ * Copyright (C) 2013 Texas Instruments
+ * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
+ *
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/thermal.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/string.h>
+
+#include "thermal_core.h"
+
+/*** Private data structures to represent thermal device tree data ***/
+
+/**
+ * struct __thermal_trip - representation of a point in temperature domain
+ * @np: pointer to struct device_node that this trip point was created from
+ * @temperature: temperature value in miliCelsius
+ * @hysteresis: relative hysteresis in miliCelsius
+ * @type: trip point type
+ */
+
+struct __thermal_trip {
+ struct device_node *np;
+ unsigned long int temperature;
+ unsigned long int hysteresis;
+ enum thermal_trip_type type;
+};
+
+/**
+ * struct __thermal_bind_param - a match between trip and cooling device
+ * @cooling_device: a pointer to identify the referred cooling device
+ * @trip_id: the trip point index
+ * @usage: the percentage (from 0 to 100) of cooling contribution
+ * @min: minimum cooling state used at this trip point
+ * @max: maximum cooling state used at this trip point
+ */
+
+struct __thermal_bind_params {
+ struct device_node *cooling_device;
+ unsigned int trip_id;
+ unsigned int usage;
+ unsigned long min;
+ unsigned long max;
+};
+
+/**
+ * struct __thermal_zone - internal representation of a thermal zone
+ * @mode: current thermal zone device mode (enabled/disabled)
+ * @passive_delay: polling interval while passive cooling is activated
+ * @polling_delay: zone polling interval
+ * @ntrips: number of trip points
+ * @trips: an array of trip points (0..ntrips - 1)
+ * @num_tbps: number of thermal bind params
+ * @tbps: an array of thermal bind params (0..num_tbps - 1)
+ * @sensor_data: sensor private data used while reading temperature and trend
+ * @get_temp: sensor callback to read temperature
+ * @get_trend: sensor callback to read temperature trend
+ */
+
+struct __thermal_zone {
+ enum thermal_device_mode mode;
+ int passive_delay;
+ int polling_delay;
+
+ /* trip data */
+ int ntrips;
+ struct __thermal_trip *trips;
+
+ /* cooling binding data */
+ int num_tbps;
+ struct __thermal_bind_params *tbps;
+
+ /* sensor interface */
+ void *sensor_data;
+ int (*get_temp)(void *, long *);
+ int (*get_trend)(void *, long *);
+};
+
+/*** DT thermal zone device callbacks ***/
+
+static int of_thermal_get_temp(struct thermal_zone_device *tz,
+ unsigned long *temp)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (!data->get_temp)
+ return -EINVAL;
+
+ return data->get_temp(data->sensor_data, temp);
+}
+
+static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip,
+ enum thermal_trend *trend)
+{
+ struct __thermal_zone *data = tz->devdata;
+ long dev_trend;
+ int r;
+
+ if (!data->get_trend)
+ return -EINVAL;
+
+ r = data->get_trend(data->sensor_data, &dev_trend);
+ if (r)
+ return r;
+
+ /* TODO: These intervals might have some thresholds, but in core code */
+ if (dev_trend > 0)
+ *trend = THERMAL_TREND_RAISING;
+ else if (dev_trend < 0)
+ *trend = THERMAL_TREND_DROPPING;
+ else
+ *trend = THERMAL_TREND_STABLE;
+
+ return 0;
+}
+
+static int of_thermal_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct __thermal_zone *data = thermal->devdata;
+ int i;
+
+ if (!data || IS_ERR(data))
+ return -ENODEV;
+
+ /* find where to bind */
+ for (i = 0; i < data->num_tbps; i++) {
+ struct __thermal_bind_params *tbp = data->tbps + i;
+
+ if (tbp->cooling_device == cdev->np) {
+ int ret;
+
+ ret = thermal_zone_bind_cooling_device(thermal,
+ tbp->trip_id, cdev,
+ tbp->max,
+ tbp->min);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int of_thermal_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct __thermal_zone *data = thermal->devdata;
+ int i;
+
+ if (!data || IS_ERR(data))
+ return -ENODEV;
+
+ /* find where to unbind */
+ for (i = 0; i < data->num_tbps; i++) {
+ struct __thermal_bind_params *tbp = data->tbps + i;
+
+ if (tbp->cooling_device == cdev->np) {
+ int ret;
+
+ ret = thermal_zone_unbind_cooling_device(thermal,
+ tbp->trip_id, cdev);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int of_thermal_get_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode *mode)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ *mode = data->mode;
+
+ return 0;
+}
+
+static int of_thermal_set_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode mode)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ mutex_lock(&tz->lock);
+
+ if (mode == THERMAL_DEVICE_ENABLED)
+ tz->polling_delay = data->polling_delay;
+ else
+ tz->polling_delay = 0;
+
+ mutex_unlock(&tz->lock);
+
+ data->mode = mode;
+ thermal_zone_device_update(tz);
+
+ return 0;
+}
+
+static int of_thermal_get_trip_type(struct thermal_zone_device *tz, int trip,
+ enum thermal_trip_type *type)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ *type = data->trips[trip].type;
+
+ return 0;
+}
+
+static int of_thermal_get_trip_temp(struct thermal_zone_device *tz, int trip,
+ unsigned long *temp)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ *temp = data->trips[trip].temperature;
+
+ return 0;
+}
+
+static int of_thermal_set_trip_temp(struct thermal_zone_device *tz, int trip,
+ unsigned long temp)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ /* thermal framework should take care of data->mask & (1 << trip) */
+ data->trips[trip].temperature = temp;
+
+ return 0;
+}
+
+static int of_thermal_get_trip_hyst(struct thermal_zone_device *tz, int trip,
+ unsigned long *hyst)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ *hyst = data->trips[trip].hysteresis;
+
+ return 0;
+}
+
+static int of_thermal_set_trip_hyst(struct thermal_zone_device *tz, int trip,
+ unsigned long hyst)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ /* thermal framework should take care of data->mask & (1 << trip) */
+ data->trips[trip].hysteresis = hyst;
+
+ return 0;
+}
+
+static int of_thermal_get_crit_temp(struct thermal_zone_device *tz,
+ unsigned long *temp)
+{
+ struct __thermal_zone *data = tz->devdata;
+ int i;
+
+ for (i = 0; i < data->ntrips; i++)
+ if (data->trips[i].type == THERMAL_TRIP_CRITICAL) {
+ *temp = data->trips[i].temperature;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static struct thermal_zone_device_ops of_thermal_ops = {
+ .get_mode = of_thermal_get_mode,
+ .set_mode = of_thermal_set_mode,
+
+ .get_trip_type = of_thermal_get_trip_type,
+ .get_trip_temp = of_thermal_get_trip_temp,
+ .set_trip_temp = of_thermal_set_trip_temp,
+ .get_trip_hyst = of_thermal_get_trip_hyst,
+ .set_trip_hyst = of_thermal_set_trip_hyst,
+ .get_crit_temp = of_thermal_get_crit_temp,
+
+ .bind = of_thermal_bind,
+ .unbind = of_thermal_unbind,
+};
+
+/*** sensor API ***/
+
+static struct thermal_zone_device *
+thermal_zone_of_add_sensor(struct device_node *zone,
+ struct device_node *sensor, void *data,
+ int (*get_temp)(void *, long *),
+ int (*get_trend)(void *, long *))
+{
+ struct thermal_zone_device *tzd;
+ struct __thermal_zone *tz;
+
+ tzd = thermal_zone_get_zone_by_name(zone->name);
+ if (IS_ERR(tzd))
+ return ERR_PTR(-EPROBE_DEFER);
+
+ tz = tzd->devdata;
+
+ mutex_lock(&tzd->lock);
+ tz->get_temp = get_temp;
+ tz->get_trend = get_trend;
+ tz->sensor_data = data;
+
+ tzd->ops->get_temp = of_thermal_get_temp;
+ tzd->ops->get_trend = of_thermal_get_trend;
+ mutex_unlock(&tzd->lock);
+
+ return tzd;
+}
+
+/**
+ * thermal_zone_of_sensor_register - registers a sensor to a DT thermal zone
+ * @dev: a valid struct device pointer of a sensor device. Must contain
+ * a valid .of_node, for the sensor node.
+ * @sensor_id: a sensor identifier, in case the sensor IP has more
+ * than one sensors
+ * @data: a private pointer (owned by the caller) that will be passed
+ * back, when a temperature reading is needed.
+ * @get_temp: a pointer to a function that reads the sensor temperature.
+ * @get_trend: a pointer to a function that reads the sensor temperature trend.
+ *
+ * This function will search the list of thermal zones described in device
+ * tree and look for the zone that refer to the sensor device pointed by
+ * @dev->of_node as temperature providers. For the zone pointing to the
+ * sensor node, the sensor will be added to the DT thermal zone device.
+ *
+ * The thermal zone temperature is provided by the @get_temp function
+ * pointer. When called, it will have the private pointer @data back.
+ *
+ * The thermal zone temperature trend is provided by the @get_trend function
+ * pointer. When called, it will have the private pointer @data back.
+ *
+ * TODO:
+ * 01 - This function must enqueue the new sensor instead of using
+ * it as the only source of temperature values.
+ *
+ * 02 - There must be a way to match the sensor with all thermal zones
+ * that refer to it.
+ *
+ * Return: On success returns a valid struct thermal_zone_device,
+ * otherwise, it returns a corresponding ERR_PTR(). Caller must
+ * check the return value with help of IS_ERR() helper.
+ */
+struct thermal_zone_device *
+thermal_zone_of_sensor_register(struct device *dev, int sensor_id,
+ void *data, int (*get_temp)(void *, long *),
+ int (*get_trend)(void *, long *))
+{
+ struct device_node *np, *child, *sensor_np;
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np)
+ return ERR_PTR(-ENODEV);
+
+ if (!dev || !dev->of_node)
+ return ERR_PTR(-EINVAL);
+
+ sensor_np = dev->of_node;
+
+ for_each_child_of_node(np, child) {
+ struct of_phandle_args sensor_specs;
+ int ret, id;
+
+ /* For now, thermal framework supports only 1 sensor per zone */
+ ret = of_parse_phandle_with_args(child, "thermal-sensors",
+ "#thermal-sensor-cells",
+ 0, &sensor_specs);
+ if (ret)
+ continue;
+
+ if (sensor_specs.args_count >= 1) {
+ id = sensor_specs.args[0];
+ WARN(sensor_specs.args_count > 1,
+ "%s: too many cells in sensor specifier %d\n",
+ sensor_specs.np->name, sensor_specs.args_count);
+ } else {
+ id = 0;
+ }
+
+ if (sensor_specs.np == sensor_np && id == sensor_id) {
+ of_node_put(np);
+ return thermal_zone_of_add_sensor(child, sensor_np,
+ data,
+ get_temp,
+ get_trend);
+ }
+ }
+ of_node_put(np);
+
+ return ERR_PTR(-ENODEV);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_register);
+
+/**
+ * thermal_zone_of_sensor_unregister - unregisters a sensor from a DT thermal zone
+ * @dev: a valid struct device pointer of a sensor device. Must contain
+ * a valid .of_node, for the sensor node.
+ * @tzd: a pointer to struct thermal_zone_device where the sensor is registered.
+ *
+ * This function removes the sensor callbacks and private data from the
+ * thermal zone device registered with thermal_zone_of_sensor_register()
+ * API. It will also silent the zone by remove the .get_temp() and .get_trend()
+ * thermal zone device callbacks.
+ *
+ * TODO: When the support to several sensors per zone is added, this
+ * function must search the sensor list based on @dev parameter.
+ *
+ */
+void thermal_zone_of_sensor_unregister(struct device *dev,
+ struct thermal_zone_device *tzd)
+{
+ struct __thermal_zone *tz;
+
+ if (!dev || !tzd || !tzd->devdata)
+ return;
+
+ tz = tzd->devdata;
+
+ /* no __thermal_zone, nothing to be done */
+ if (!tz)
+ return;
+
+ mutex_lock(&tzd->lock);
+ tzd->ops->get_temp = NULL;
+ tzd->ops->get_trend = NULL;
+
+ tz->get_temp = NULL;
+ tz->get_trend = NULL;
+ tz->sensor_data = NULL;
+ mutex_unlock(&tzd->lock);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_unregister);
+
+/*** functions parsing device tree nodes ***/
+
+/**
+ * thermal_of_populate_bind_params - parse and fill cooling map data
+ * @np: DT node containing a cooling-map node
+ * @__tbp: data structure to be filled with cooling map info
+ * @trips: array of thermal zone trip points
+ * @ntrips: number of trip points inside trips.
+ *
+ * This function parses a cooling-map type of node represented by
+ * @np parameter and fills the read data into @__tbp data structure.
+ * It needs the already parsed array of trip points of the thermal zone
+ * in consideration.
+ *
+ * Return: 0 on success, proper error code otherwise
+ */
+static int thermal_of_populate_bind_params(struct device_node *np,
+ struct __thermal_bind_params *__tbp,
+ struct __thermal_trip *trips,
+ int ntrips)
+{
+ struct of_phandle_args cooling_spec;
+ struct device_node *trip;
+ int ret, i;
+ u32 prop;
+
+ /* Default weight. Usage is optional */
+ __tbp->usage = 0;
+ ret = of_property_read_u32(np, "contribution", &prop);
+ if (ret == 0)
+ __tbp->usage = prop;
+
+ trip = of_parse_phandle(np, "trip", 0);
+ if (!trip) {
+ pr_err("missing trip property\n");
+ return -ENODEV;
+ }
+
+ /* match using device_node */
+ for (i = 0; i < ntrips; i++)
+ if (trip == trips[i].np) {
+ __tbp->trip_id = i;
+ break;
+ }
+
+ if (i == ntrips) {
+ ret = -ENODEV;
+ goto end;
+ }
+
+ ret = of_parse_phandle_with_args(np, "cooling-device", "#cooling-cells",
+ 0, &cooling_spec);
+ if (ret < 0) {
+ pr_err("missing cooling_device property\n");
+ goto end;
+ }
+ __tbp->cooling_device = cooling_spec.np;
+ if (cooling_spec.args_count >= 2) { /* at least min and max */
+ __tbp->min = cooling_spec.args[0];
+ __tbp->max = cooling_spec.args[1];
+ } else {
+ pr_err("wrong reference to cooling device, missing limits\n");
+ }
+
+end:
+ of_node_put(trip);
+
+ return ret;
+}
+
+/**
+ * It maps 'enum thermal_trip_type' found in include/linux/thermal.h
+ * into the device tree binding of 'trip', property type.
+ */
+static const char * const trip_types[] = {
+ [THERMAL_TRIP_ACTIVE] = "active",
+ [THERMAL_TRIP_PASSIVE] = "passive",
+ [THERMAL_TRIP_HOT] = "hot",
+ [THERMAL_TRIP_CRITICAL] = "critical",
+};
+
+/**
+ * thermal_of_get_trip_type - Get phy mode for given device_node
+ * @np: Pointer to the given device_node
+ * @type: Pointer to resulting trip type
+ *
+ * The function gets trip type string from property 'type',
+ * and store its index in trip_types table in @type,
+ *
+ * Return: 0 on success, or errno in error case.
+ */
+static int thermal_of_get_trip_type(struct device_node *np,
+ enum thermal_trip_type *type)
+{
+ const char *t;
+ int err, i;
+
+ err = of_property_read_string(np, "type", &t);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(trip_types); i++)
+ if (!strcasecmp(t, trip_types[i])) {
+ *type = i;
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+/**
+ * thermal_of_populate_trip - parse and fill one trip point data
+ * @np: DT node containing a trip point node
+ * @trip: trip point data structure to be filled up
+ *
+ * This function parses a trip point type of node represented by
+ * @np parameter and fills the read data into @trip data structure.
+ *
+ * Return: 0 on success, proper error code otherwise
+ */
+static int thermal_of_populate_trip(struct device_node *np,
+ struct __thermal_trip *trip)
+{
+ int prop;
+ int ret;
+
+ ret = of_property_read_u32(np, "temperature", &prop);
+ if (ret < 0) {
+ pr_err("missing temperature property\n");
+ return ret;
+ }
+ trip->temperature = prop;
+
+ ret = of_property_read_u32(np, "hysteresis", &prop);
+ if (ret < 0) {
+ pr_err("missing hysteresis property\n");
+ return ret;
+ }
+ trip->hysteresis = prop;
+
+ ret = thermal_of_get_trip_type(np, &trip->type);
+ if (ret < 0) {
+ pr_err("wrong trip type property\n");
+ return ret;
+ }
+
+ /* Required for cooling map matching */
+ trip->np = np;
+
+ return 0;
+}
+
+/**
+ * thermal_of_build_thermal_zone - parse and fill one thermal zone data
+ * @np: DT node containing a thermal zone node
+ *
+ * This function parses a thermal zone type of node represented by
+ * @np parameter and fills the read data into a __thermal_zone data structure
+ * and return this pointer.
+ *
+ * TODO: Missing properties to parse: thermal-sensor-names and coefficients
+ *
+ * Return: On success returns a valid struct __thermal_zone,
+ * otherwise, it returns a corresponding ERR_PTR(). Caller must
+ * check the return value with help of IS_ERR() helper.
+ */
+static struct __thermal_zone *
+thermal_of_build_thermal_zone(struct device_node *np)
+{
+ struct device_node *child = NULL, *gchild;
+ struct __thermal_zone *tz;
+ int ret, i;
+ u32 prop;
+
+ if (!np) {
+ pr_err("no thermal zone np\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ tz = kzalloc(sizeof(*tz), GFP_KERNEL);
+ if (!tz)
+ return ERR_PTR(-ENOMEM);
+
+ ret = of_property_read_u32(np, "polling-delay-passive", &prop);
+ if (ret < 0) {
+ pr_err("missing polling-delay-passive property\n");
+ goto free_tz;
+ }
+ tz->passive_delay = prop;
+
+ ret = of_property_read_u32(np, "polling-delay", &prop);
+ if (ret < 0) {
+ pr_err("missing polling-delay property\n");
+ goto free_tz;
+ }
+ tz->polling_delay = prop;
+
+ /* trips */
+ child = of_get_child_by_name(np, "trips");
+
+ /* No trips provided */
+ if (!child)
+ goto finish;
+
+ tz->ntrips = of_get_child_count(child);
+ if (tz->ntrips == 0) /* must have at least one child */
+ goto finish;
+
+ tz->trips = kzalloc(tz->ntrips * sizeof(*tz->trips), GFP_KERNEL);
+ if (!tz->trips) {
+ ret = -ENOMEM;
+ goto free_tz;
+ }
+
+ i = 0;
+ for_each_child_of_node(child, gchild) {
+ ret = thermal_of_populate_trip(gchild, &tz->trips[i++]);
+ if (ret)
+ goto free_trips;
+ }
+
+ of_node_put(child);
+
+ /* cooling-maps */
+ child = of_get_child_by_name(np, "cooling-maps");
+
+ /* cooling-maps not provided */
+ if (!child)
+ goto finish;
+
+ tz->num_tbps = of_get_child_count(child);
+ if (tz->num_tbps == 0)
+ goto finish;
+
+ tz->tbps = kzalloc(tz->num_tbps * sizeof(*tz->tbps), GFP_KERNEL);
+ if (!tz->tbps) {
+ ret = -ENOMEM;
+ goto free_trips;
+ }
+
+ i = 0;
+ for_each_child_of_node(child, gchild) {
+ ret = thermal_of_populate_bind_params(gchild, &tz->tbps[i++],
+ tz->trips, tz->ntrips);
+ if (ret)
+ goto free_tbps;
+ }
+
+finish:
+ of_node_put(child);
+ tz->mode = THERMAL_DEVICE_DISABLED;
+
+ return tz;
+
+free_tbps:
+ kfree(tz->tbps);
+free_trips:
+ kfree(tz->trips);
+free_tz:
+ kfree(tz);
+ of_node_put(child);
+
+ return ERR_PTR(ret);
+}
+
+static inline void of_thermal_free_zone(struct __thermal_zone *tz)
+{
+ kfree(tz->tbps);
+ kfree(tz->trips);
+ kfree(tz);
+}
+
+/**
+ * of_parse_thermal_zones - parse device tree thermal data
+ *
+ * Initialization function that can be called by machine initialization
+ * code to parse thermal data and populate the thermal framework
+ * with hardware thermal zones info. This function only parses thermal zones.
+ * Cooling devices and sensor devices nodes are supposed to be parsed
+ * by their respective drivers.
+ *
+ * Return: 0 on success, proper error code otherwise
+ *
+ */
+int __init of_parse_thermal_zones(void)
+{
+ struct device_node *np, *child;
+ struct __thermal_zone *tz;
+ struct thermal_zone_device_ops *ops;
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np) {
+ pr_debug("unable to find thermal zones\n");
+ return 0; /* Run successfully on systems without thermal DT */
+ }
+
+ for_each_child_of_node(np, child) {
+ struct thermal_zone_device *zone;
+ struct thermal_zone_params *tzp;
+
+ tz = thermal_of_build_thermal_zone(child);
+ if (IS_ERR(tz)) {
+ pr_err("failed to build thermal zone %s: %ld\n",
+ child->name,
+ PTR_ERR(tz));
+ continue;
+ }
+
+ ops = kmemdup(&of_thermal_ops, sizeof(*ops), GFP_KERNEL);
+ if (!ops)
+ goto exit_free;
+
+ tzp = kzalloc(sizeof(*tzp), GFP_KERNEL);
+ if (!tzp) {
+ kfree(ops);
+ goto exit_free;
+ }
+
+ /* No hwmon because there might be hwmon drivers registering */
+ tzp->no_hwmon = true;
+
+ zone = thermal_zone_device_register(child->name, tz->ntrips,
+ 0, tz,
+ ops, tzp,
+ tz->passive_delay,
+ tz->polling_delay);
+ if (IS_ERR(zone)) {
+ pr_err("Failed to build %s zone %ld\n", child->name,
+ PTR_ERR(zone));
+ kfree(tzp);
+ kfree(ops);
+ of_thermal_free_zone(tz);
+ /* attempting to build remaining zones still */
+ }
+ }
+
+ return 0;
+
+exit_free:
+ of_thermal_free_zone(tz);
+
+ /* no memory available, so free what we have built */
+ of_thermal_destroy_zones();
+
+ return -ENOMEM;
+}
+
+/**
+ * of_thermal_destroy_zones - remove all zones parsed and allocated resources
+ *
+ * Finds all zones parsed and added to the thermal framework and remove them
+ * from the system, together with their resources.
+ *
+ */
+void of_thermal_destroy_zones(void)
+{
+ struct device_node *np, *child;
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np) {
+ pr_err("unable to find thermal zones\n");
+ return;
+ }
+
+ for_each_child_of_node(np, child) {
+ struct thermal_zone_device *zone;
+
+ zone = thermal_zone_get_zone_by_name(child->name);
+ if (IS_ERR(zone))
+ continue;
+
+ thermal_zone_device_unregister(zone);
+ kfree(zone->tzp);
+ kfree(zone->ops);
+ of_thermal_free_zone(zone->devdata);
+ }
+}
diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c
new file mode 100644
index 00000000..8d7edd4c
--- /dev/null
+++ b/drivers/thermal/rcar_thermal.c
@@ -0,0 +1,516 @@
+/*
+ * R-Car THS/TSC thermal sensor driver
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/thermal.h>
+
+#define IDLE_INTERVAL 5000
+
+#define COMMON_STR 0x00
+#define COMMON_ENR 0x04
+#define COMMON_INTMSK 0x0c
+
+#define REG_POSNEG 0x20
+#define REG_FILONOFF 0x28
+#define REG_THSCR 0x2c
+#define REG_THSSR 0x30
+#define REG_INTCTRL 0x34
+
+/* THSCR */
+#define CPCTL (1 << 12)
+
+/* THSSR */
+#define CTEMP 0x3f
+
+struct rcar_thermal_common {
+ void __iomem *base;
+ struct device *dev;
+ struct list_head head;
+ spinlock_t lock;
+};
+
+struct rcar_thermal_priv {
+ void __iomem *base;
+ struct rcar_thermal_common *common;
+ struct thermal_zone_device *zone;
+ struct delayed_work work;
+ struct mutex lock;
+ struct list_head list;
+ int id;
+ int ctemp;
+};
+
+#define rcar_thermal_for_each_priv(pos, common) \
+ list_for_each_entry(pos, &common->head, list)
+
+#define MCELSIUS(temp) ((temp) * 1000)
+#define rcar_zone_to_priv(zone) ((zone)->devdata)
+#define rcar_priv_to_dev(priv) ((priv)->common->dev)
+#define rcar_has_irq_support(priv) ((priv)->common->base)
+#define rcar_id_to_shift(priv) ((priv)->id * 8)
+
+#ifdef DEBUG
+# define rcar_force_update_temp(priv) 1
+#else
+# define rcar_force_update_temp(priv) 0
+#endif
+
+/*
+ * basic functions
+ */
+#define rcar_thermal_common_read(c, r) \
+ _rcar_thermal_common_read(c, COMMON_ ##r)
+static u32 _rcar_thermal_common_read(struct rcar_thermal_common *common,
+ u32 reg)
+{
+ return ioread32(common->base + reg);
+}
+
+#define rcar_thermal_common_write(c, r, d) \
+ _rcar_thermal_common_write(c, COMMON_ ##r, d)
+static void _rcar_thermal_common_write(struct rcar_thermal_common *common,
+ u32 reg, u32 data)
+{
+ iowrite32(data, common->base + reg);
+}
+
+#define rcar_thermal_common_bset(c, r, m, d) \
+ _rcar_thermal_common_bset(c, COMMON_ ##r, m, d)
+static void _rcar_thermal_common_bset(struct rcar_thermal_common *common,
+ u32 reg, u32 mask, u32 data)
+{
+ u32 val;
+
+ val = ioread32(common->base + reg);
+ val &= ~mask;
+ val |= (data & mask);
+ iowrite32(val, common->base + reg);
+}
+
+#define rcar_thermal_read(p, r) _rcar_thermal_read(p, REG_ ##r)
+static u32 _rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg)
+{
+ return ioread32(priv->base + reg);
+}
+
+#define rcar_thermal_write(p, r, d) _rcar_thermal_write(p, REG_ ##r, d)
+static void _rcar_thermal_write(struct rcar_thermal_priv *priv,
+ u32 reg, u32 data)
+{
+ iowrite32(data, priv->base + reg);
+}
+
+#define rcar_thermal_bset(p, r, m, d) _rcar_thermal_bset(p, REG_ ##r, m, d)
+static void _rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg,
+ u32 mask, u32 data)
+{
+ u32 val;
+
+ val = ioread32(priv->base + reg);
+ val &= ~mask;
+ val |= (data & mask);
+ iowrite32(val, priv->base + reg);
+}
+
+/*
+ * zone device functions
+ */
+static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
+{
+ struct device *dev = rcar_priv_to_dev(priv);
+ int i;
+ int ctemp, old, new;
+ int ret = -EINVAL;
+
+ mutex_lock(&priv->lock);
+
+ /*
+ * TSC decides a value of CPTAP automatically,
+ * and this is the conditions which validate interrupt.
+ */
+ rcar_thermal_bset(priv, THSCR, CPCTL, CPCTL);
+
+ ctemp = 0;
+ old = ~0;
+ for (i = 0; i < 128; i++) {
+ /*
+ * we need to wait 300us after changing comparator offset
+ * to get stable temperature.
+ * see "Usage Notes" on datasheet
+ */
+ udelay(300);
+
+ new = rcar_thermal_read(priv, THSSR) & CTEMP;
+ if (new == old) {
+ ctemp = new;
+ break;
+ }
+ old = new;
+ }
+
+ if (!ctemp) {
+ dev_err(dev, "thermal sensor was broken\n");
+ goto err_out_unlock;
+ }
+
+ /*
+ * enable IRQ
+ */
+ if (rcar_has_irq_support(priv)) {
+ rcar_thermal_write(priv, FILONOFF, 0);
+
+ /* enable Rising/Falling edge interrupt */
+ rcar_thermal_write(priv, POSNEG, 0x1);
+ rcar_thermal_write(priv, INTCTRL, (((ctemp - 0) << 8) |
+ ((ctemp - 1) << 0)));
+ }
+
+ dev_dbg(dev, "thermal%d %d -> %d\n", priv->id, priv->ctemp, ctemp);
+
+ priv->ctemp = ctemp;
+ ret = 0;
+err_out_unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
+ unsigned long *temp)
+{
+ struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
+
+ if (!rcar_has_irq_support(priv) || rcar_force_update_temp(priv))
+ rcar_thermal_update_temp(priv);
+
+ mutex_lock(&priv->lock);
+ *temp = MCELSIUS((priv->ctemp * 5) - 65);
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone,
+ int trip, enum thermal_trip_type *type)
+{
+ struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ /* see rcar_thermal_get_temp() */
+ switch (trip) {
+ case 0: /* +90 <= temp */
+ *type = THERMAL_TRIP_CRITICAL;
+ break;
+ default:
+ dev_err(dev, "rcar driver trip error\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone,
+ int trip, unsigned long *temp)
+{
+ struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ /* see rcar_thermal_get_temp() */
+ switch (trip) {
+ case 0: /* +90 <= temp */
+ *temp = MCELSIUS(90);
+ break;
+ default:
+ dev_err(dev, "rcar driver trip error\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rcar_thermal_notify(struct thermal_zone_device *zone,
+ int trip, enum thermal_trip_type type)
+{
+ struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ switch (type) {
+ case THERMAL_TRIP_CRITICAL:
+ /* FIXME */
+ dev_warn(dev, "Thermal reached to critical temperature\n");
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops rcar_thermal_zone_ops = {
+ .get_temp = rcar_thermal_get_temp,
+ .get_trip_type = rcar_thermal_get_trip_type,
+ .get_trip_temp = rcar_thermal_get_trip_temp,
+ .notify = rcar_thermal_notify,
+};
+
+/*
+ * interrupt
+ */
+#define rcar_thermal_irq_enable(p) _rcar_thermal_irq_ctrl(p, 1)
+#define rcar_thermal_irq_disable(p) _rcar_thermal_irq_ctrl(p, 0)
+static void _rcar_thermal_irq_ctrl(struct rcar_thermal_priv *priv, int enable)
+{
+ struct rcar_thermal_common *common = priv->common;
+ unsigned long flags;
+ u32 mask = 0x3 << rcar_id_to_shift(priv); /* enable Rising/Falling */
+
+ spin_lock_irqsave(&common->lock, flags);
+
+ rcar_thermal_common_bset(common, INTMSK, mask, enable ? 0 : mask);
+
+ spin_unlock_irqrestore(&common->lock, flags);
+}
+
+static void rcar_thermal_work(struct work_struct *work)
+{
+ struct rcar_thermal_priv *priv;
+
+ priv = container_of(work, struct rcar_thermal_priv, work.work);
+
+ rcar_thermal_update_temp(priv);
+ rcar_thermal_irq_enable(priv);
+ thermal_zone_device_update(priv->zone);
+}
+
+static u32 rcar_thermal_had_changed(struct rcar_thermal_priv *priv, u32 status)
+{
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ status = (status >> rcar_id_to_shift(priv)) & 0x3;
+
+ if (status & 0x3) {
+ dev_dbg(dev, "thermal%d %s%s\n",
+ priv->id,
+ (status & 0x2) ? "Rising " : "",
+ (status & 0x1) ? "Falling" : "");
+ }
+
+ return status;
+}
+
+static irqreturn_t rcar_thermal_irq(int irq, void *data)
+{
+ struct rcar_thermal_common *common = data;
+ struct rcar_thermal_priv *priv;
+ unsigned long flags;
+ u32 status, mask;
+
+ spin_lock_irqsave(&common->lock, flags);
+
+ mask = rcar_thermal_common_read(common, INTMSK);
+ status = rcar_thermal_common_read(common, STR);
+ rcar_thermal_common_write(common, STR, 0x000F0F0F & mask);
+
+ spin_unlock_irqrestore(&common->lock, flags);
+
+ status = status & ~mask;
+
+ /*
+ * check the status
+ */
+ rcar_thermal_for_each_priv(priv, common) {
+ if (rcar_thermal_had_changed(priv, status)) {
+ rcar_thermal_irq_disable(priv);
+ schedule_delayed_work(&priv->work,
+ msecs_to_jiffies(300));
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * platform functions
+ */
+static int rcar_thermal_probe(struct platform_device *pdev)
+{
+ struct rcar_thermal_common *common;
+ struct rcar_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct resource *res, *irq;
+ int mres = 0;
+ int i;
+ int ret = -ENODEV;
+ int idle = IDLE_INTERVAL;
+
+ common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL);
+ if (!common) {
+ dev_err(dev, "Could not allocate common\n");
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&common->head);
+ spin_lock_init(&common->lock);
+ common->dev = dev;
+
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+
+ irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (irq) {
+ int ret;
+
+ /*
+ * platform has IRQ support.
+ * Then, drier use common register
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, mres++);
+ if (!res) {
+ dev_err(dev, "Could not get platform resource\n");
+ return -ENODEV;
+ }
+
+ ret = devm_request_irq(dev, irq->start, rcar_thermal_irq, 0,
+ dev_name(dev), common);
+ if (ret) {
+ dev_err(dev, "irq request failed\n ");
+ return ret;
+ }
+
+ /*
+ * rcar_has_irq_support() will be enabled
+ */
+ common->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(common->base))
+ return PTR_ERR(common->base);
+
+ /* enable temperature comparation */
+ rcar_thermal_common_write(common, ENR, 0x00030303);
+
+ idle = 0; /* polling delaye is not needed */
+ }
+
+ for (i = 0;; i++) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, mres++);
+ if (!res)
+ break;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ dev_err(dev, "Could not allocate priv\n");
+ ret = -ENOMEM;
+ goto error_unregister;
+ }
+
+ priv->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->base)) {
+ ret = PTR_ERR(priv->base);
+ goto error_unregister;
+ }
+
+ priv->common = common;
+ priv->id = i;
+ mutex_init(&priv->lock);
+ INIT_LIST_HEAD(&priv->list);
+ INIT_DELAYED_WORK(&priv->work, rcar_thermal_work);
+ rcar_thermal_update_temp(priv);
+
+ priv->zone = thermal_zone_device_register("rcar_thermal",
+ 1, 0, priv,
+ &rcar_thermal_zone_ops, NULL, 0,
+ idle);
+ if (IS_ERR(priv->zone)) {
+ dev_err(dev, "can't register thermal zone\n");
+ ret = PTR_ERR(priv->zone);
+ goto error_unregister;
+ }
+
+ if (rcar_has_irq_support(priv))
+ rcar_thermal_irq_enable(priv);
+
+ list_move_tail(&priv->list, &common->head);
+ }
+
+ platform_set_drvdata(pdev, common);
+
+ dev_info(dev, "%d sensor proved\n", i);
+
+ return 0;
+
+error_unregister:
+ rcar_thermal_for_each_priv(priv, common) {
+ thermal_zone_device_unregister(priv->zone);
+ if (rcar_has_irq_support(priv))
+ rcar_thermal_irq_disable(priv);
+ }
+
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static int rcar_thermal_remove(struct platform_device *pdev)
+{
+ struct rcar_thermal_common *common = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct rcar_thermal_priv *priv;
+
+ rcar_thermal_for_each_priv(priv, common) {
+ thermal_zone_device_unregister(priv->zone);
+ if (rcar_has_irq_support(priv))
+ rcar_thermal_irq_disable(priv);
+ }
+
+ platform_set_drvdata(pdev, NULL);
+
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+
+ return 0;
+}
+
+static const struct of_device_id rcar_thermal_dt_ids[] = {
+ { .compatible = "renesas,rcar-thermal", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rcar_thermal_dt_ids);
+
+static struct platform_driver rcar_thermal_driver = {
+ .driver = {
+ .name = "rcar_thermal",
+ .of_match_table = rcar_thermal_dt_ids,
+ },
+ .probe = rcar_thermal_probe,
+ .remove = rcar_thermal_remove,
+};
+module_platform_driver(rcar_thermal_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("R-Car THS/TSC thermal sensor driver");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c
new file mode 100644
index 00000000..3c5ee560
--- /dev/null
+++ b/drivers/thermal/spear_thermal.c
@@ -0,0 +1,209 @@
+/*
+ * SPEAr thermal driver.
+ *
+ * Copyright (C) 2011-2012 ST Microelectronics
+ * Author: Vincenzo Frascino <vincenzo.frascino@st.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#define MD_FACTOR 1000
+
+/* SPEAr Thermal Sensor Dev Structure */
+struct spear_thermal_dev {
+ /* pointer to base address of the thermal sensor */
+ void __iomem *thermal_base;
+ /* clk structure */
+ struct clk *clk;
+ /* pointer to thermal flags */
+ unsigned int flags;
+};
+
+static inline int thermal_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct spear_thermal_dev *stdev = thermal->devdata;
+
+ /*
+ * Data are ready to be read after 628 usec from POWERDOWN signal
+ * (PDN) = 1
+ */
+ *temp = (readl_relaxed(stdev->thermal_base) & 0x7F) * MD_FACTOR;
+ return 0;
+}
+
+static struct thermal_zone_device_ops ops = {
+ .get_temp = thermal_get_temp,
+};
+
+#ifdef CONFIG_PM
+static int spear_thermal_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev);
+ struct spear_thermal_dev *stdev = spear_thermal->devdata;
+ unsigned int actual_mask = 0;
+
+ /* Disable SPEAr Thermal Sensor */
+ actual_mask = readl_relaxed(stdev->thermal_base);
+ writel_relaxed(actual_mask & ~stdev->flags, stdev->thermal_base);
+
+ clk_disable(stdev->clk);
+ dev_info(dev, "Suspended.\n");
+
+ return 0;
+}
+
+static int spear_thermal_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev);
+ struct spear_thermal_dev *stdev = spear_thermal->devdata;
+ unsigned int actual_mask = 0;
+ int ret = 0;
+
+ ret = clk_enable(stdev->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't enable clock\n");
+ return ret;
+ }
+
+ /* Enable SPEAr Thermal Sensor */
+ actual_mask = readl_relaxed(stdev->thermal_base);
+ writel_relaxed(actual_mask | stdev->flags, stdev->thermal_base);
+
+ dev_info(dev, "Resumed.\n");
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(spear_thermal_pm_ops, spear_thermal_suspend,
+ spear_thermal_resume);
+
+static int spear_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *spear_thermal = NULL;
+ struct spear_thermal_dev *stdev;
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *stres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ int ret = 0, val;
+
+ if (!np || !of_property_read_u32(np, "st,thermal-flags", &val)) {
+ dev_err(&pdev->dev, "Failed: DT Pdata not passed\n");
+ return -EINVAL;
+ }
+
+ if (!stres) {
+ dev_err(&pdev->dev, "memory resource missing\n");
+ return -ENODEV;
+ }
+
+ stdev = devm_kzalloc(&pdev->dev, sizeof(*stdev), GFP_KERNEL);
+ if (!stdev) {
+ dev_err(&pdev->dev, "kzalloc fail\n");
+ return -ENOMEM;
+ }
+
+ /* Enable thermal sensor */
+ stdev->thermal_base = devm_ioremap(&pdev->dev, stres->start,
+ resource_size(stres));
+ if (!stdev->thermal_base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ return -ENOMEM;
+ }
+
+ stdev->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(stdev->clk)) {
+ dev_err(&pdev->dev, "Can't get clock\n");
+ return PTR_ERR(stdev->clk);
+ }
+
+ ret = clk_enable(stdev->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't enable clock\n");
+ return ret;
+ }
+
+ stdev->flags = val;
+ writel_relaxed(stdev->flags, stdev->thermal_base);
+
+ spear_thermal = thermal_zone_device_register("spear_thermal", 0, 0,
+ stdev, &ops, NULL, 0, 0);
+ if (IS_ERR(spear_thermal)) {
+ dev_err(&pdev->dev, "thermal zone device is NULL\n");
+ ret = PTR_ERR(spear_thermal);
+ goto disable_clk;
+ }
+
+ platform_set_drvdata(pdev, spear_thermal);
+
+ dev_info(&spear_thermal->device, "Thermal Sensor Loaded at: 0x%p.\n",
+ stdev->thermal_base);
+
+ return 0;
+
+disable_clk:
+ clk_disable(stdev->clk);
+
+ return ret;
+}
+
+static int spear_thermal_exit(struct platform_device *pdev)
+{
+ unsigned int actual_mask = 0;
+ struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev);
+ struct spear_thermal_dev *stdev = spear_thermal->devdata;
+
+ thermal_zone_device_unregister(spear_thermal);
+ platform_set_drvdata(pdev, NULL);
+
+ /* Disable SPEAr Thermal Sensor */
+ actual_mask = readl_relaxed(stdev->thermal_base);
+ writel_relaxed(actual_mask & ~stdev->flags, stdev->thermal_base);
+
+ clk_disable(stdev->clk);
+
+ return 0;
+}
+
+static const struct of_device_id spear_thermal_id_table[] = {
+ { .compatible = "st,thermal-spear1340" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, spear_thermal_id_table);
+
+static struct platform_driver spear_thermal_driver = {
+ .probe = spear_thermal_probe,
+ .remove = spear_thermal_exit,
+ .driver = {
+ .name = "spear_thermal",
+ .owner = THIS_MODULE,
+ .pm = &spear_thermal_pm_ops,
+ .of_match_table = of_match_ptr(spear_thermal_id_table),
+ },
+};
+
+module_platform_driver(spear_thermal_driver);
+
+MODULE_AUTHOR("Vincenzo Frascino <vincenzo.frascino@st.com>");
+MODULE_DESCRIPTION("SPEAr thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/sprd_2713_thm.c b/drivers/thermal/sprd_2713_thm.c
new file mode 100644
index 00000000..cdb96ae9
--- /dev/null
+++ b/drivers/thermal/sprd_2713_thm.c
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <mach/hardware.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <linux/io.h>
+#include <soc/sprd/adi.h>
+#include "thm.h"
+#include <linux/sprd_thm.h>
+#include <mach/arch_misc.h>
+
+#define SPRD_THM_DEBUG
+#ifdef SPRD_THM_DEBUG
+#define THM_DEBUG(format, arg...) printk("sprd thm: " "@@@" format, ## arg)
+#else
+#define THM_DEBUG(format, arg...)
+#endif
+
+#define THM_CTRL (0x0000)
+#define THM_INT_CTRL (0x0004)
+#define SENSOR_CTRL (0x0020)
+#define SENSOR_DET_PERI (0x0024)
+#define SENSOR_INT_CTRL (0x0028)
+#define SENSOR_INT_STS (0x002C)
+#define SENSOR_INT_RAW_STS (0x0030)
+#define SENSOR_INT_CLR (0x0034)
+#define SENSOR_OVERHEAT_HOT_THRES (0X0040)
+#define SENSOR_HOT2NOR__HIGHOFF_THRES (0X0044)
+#define SENSOR_LOWOFF__COLD_THRES (0X0048)
+#define SENSOR_TEMPER0_READ (0x0058)
+
+#define SEN_OVERHEAT_INT_BIT (1 << 5)
+#define SEN_HOT_INT_BIT (1 << 4)
+#define SEN_HOT2NOR_INT_BIT (1 << 3)
+#define SEN_HIGHOFF_BIT (1 << 2)
+#define SEN_LOWOFF_INT_BIT (1 << 1)
+
+#define RAW_TEMP_OFFSET 8
+#define RAW_TEMP_RANGE_MSK 0x7F
+
+#define HIGH_BITS_OFFSET 4
+
+#define HIGH_TAB_SZ 8
+#define LOW_TAB_SZ 16
+
+#define HOT2NOR_RANGE 15
+#define LOCAL_SENSOR_ADDR_OFF 0x100
+#define DELAY_TEMPERATURE 3
+#define INTOFFSET 3
+
+#define TSMC_DOLPHINW4T_CHIP_ID_1 0x7715A001
+#define TSMC_DOLPHINW4T_CHIP_ID_2 0x7715A003
+#define TSMC_DOLPHINWT4T_CHIP_ID_1 0x8815A001
+
+static const short temp_search_high_40nm[HIGH_TAB_SZ] =
+ { -41, -14, 14, 41, 68, 95, 122, 150 };
+static const short temp_search_low_40nm[LOW_TAB_SZ] =
+ { 0, 2, 4, 5, 7, 8, 10, 12, 14, 15, 17, 19, 20, 22, 24, 26 };
+static const short temp_search_high_152nm[HIGH_TAB_SZ] =
+ { -45, -19, 7, 33, 59, 85, 111, 137 };
+static const short temp_search_low_152nm[LOW_TAB_SZ] =
+ { 0, 2, 3, 5, 6, 8, 10, 11, 13, 14, 16, 18, 19, 21, 22, 24 };
+
+static u32 current_trip_num = 0;
+static int arm_sen_cal_offset = 0;
+static int pmic_sen_cal_offset = 0;
+
+extern unsigned long SPRD_THM_BASE;
+extern unsigned int SPRD_THM_SIZE;
+
+static inline int __thm_reg_write(u32 reg, u16 bits, u16 clear_msk);
+static inline u32 __thm_reg_read(u32 reg);
+int sprd_thm_set_active_trip(struct sprd_thermal_zone *pzone, int trip );
+u32 sprd_thm_temp2rawdata(u32 sensor, int temp);
+
+static inline int __thm_reg_write(u32 reg, u16 bits, u16 clear_msk)
+{
+ if (reg >= SPRD_THM_BASE && reg <= (SPRD_THM_BASE + SPRD_THM_SIZE)) {
+ __raw_writel(((__raw_readl((volatile void *)reg) & ~clear_msk) | bits), (volatile void *)(reg));
+ } else if (reg >= ANA_THM_BASE && reg <= (ANA_THM_BASE + SPRD_THM_SIZE)) {
+ sci_adi_write(reg, bits, clear_msk);
+ } else {
+ printk(KERN_ERR "error thm reg0x:%x \n", reg);
+ }
+ return 0;
+}
+
+static inline u32 __thm_reg_read(u32 reg)
+{
+ if (reg >= SPRD_THM_BASE && reg <= (SPRD_THM_BASE + SPRD_THM_SIZE)) {
+ return __raw_readl((volatile void *)reg);
+ } else if (reg >= ANA_THM_BASE && reg <= (ANA_THM_BASE + SPRD_THM_SIZE)) {
+ return sci_adi_read(reg);
+ } else {
+ printk(KERN_ERR "error thm reg0x:%x \n", reg);
+ }
+ return 0;
+}
+
+int sprd_thm_set_active_trip(struct sprd_thermal_zone *pzone, int trip )
+{
+ u32 raw_temp = 0;
+ u32 local_sen_id = 0;
+ u32 local_sensor_addr = 0;
+ struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+
+ THM_DEBUG("thm sensor id:%d, trip:%d \n", pzone->sensor_id, trip);
+ if (trip < 0 || trip > (trip_tab->num_trips - 1))
+ return -1;
+ if (trip_tab->trip_points[trip].type != THERMAL_TRIP_ACTIVE)
+ return -1;
+
+ local_sen_id = pzone->sensor_id;
+ local_sensor_addr =
+ (u32) pzone->reg_base + local_sen_id * LOCAL_SENSOR_ADDR_OFF;
+
+ //Disable sensor int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL),0,(0x7F & (~SEN_OVERHEAT_INT_BIT)));
+
+ //set hot int temp value
+ THM_DEBUG("thm sensor trip:%d, temperature:%d \n", trip, trip_tab->trip_points[trip].temp);
+ raw_temp =
+ sprd_thm_temp2rawdata(pzone->sensor_id,
+ trip_tab->trip_points[trip].temp - pmic_sen_cal_offset - INTOFFSET);
+ if (raw_temp < RAW_TEMP_RANGE_MSK) {
+ raw_temp++;
+ }
+ __thm_reg_write((local_sensor_addr + SENSOR_OVERHEAT_HOT_THRES),
+ raw_temp,
+ RAW_TEMP_RANGE_MSK);
+
+ //set Hot2Normal int temp value
+ raw_temp =
+ sprd_thm_temp2rawdata(pzone->sensor_id, trip_tab->trip_points[trip].lowoff - INTOFFSET - pmic_sen_cal_offset);
+ __thm_reg_write((local_sensor_addr + SENSOR_HOT2NOR__HIGHOFF_THRES),
+ raw_temp << RAW_TEMP_OFFSET,
+ RAW_TEMP_RANGE_MSK << RAW_TEMP_OFFSET);
+
+ //set cold int temp value
+ raw_temp =
+ sprd_thm_temp2rawdata(pzone->sensor_id, trip_tab->trip_points[trip].lowoff - INTOFFSET - pmic_sen_cal_offset);
+ __thm_reg_write((local_sensor_addr + SENSOR_LOWOFF__COLD_THRES),
+ raw_temp << RAW_TEMP_OFFSET,
+ RAW_TEMP_RANGE_MSK << RAW_TEMP_OFFSET);
+
+ THM_DEBUG("thm OVERHEAT_HOT:0x%x, HOT2NOR__HIGHOFF:0x%x, LOWOFF__COLD:0x%x\n", __thm_reg_read(local_sensor_addr + SENSOR_OVERHEAT_HOT_THRES),
+ __thm_reg_read(local_sensor_addr + SENSOR_HOT2NOR__HIGHOFF_THRES), __thm_reg_read(local_sensor_addr + SENSOR_LOWOFF__COLD_THRES));
+
+ // Restart sensor to enable new paramter
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x9, 0x9);
+
+ if (trip > 0)
+ {
+ //enable Hot int and Lowoff int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL),
+ SEN_HOT_INT_BIT | SEN_LOWOFF_INT_BIT,
+ SEN_HOT_INT_BIT | SEN_LOWOFF_INT_BIT);
+ }
+ else
+ {
+ //enable Hot int and disable LOWOFF int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL),
+ SEN_HOT_INT_BIT,
+ SEN_HOT_INT_BIT | SEN_LOWOFF_INT_BIT);
+ }
+
+ return 0;
+}
+
+u32 sprd_thm_temp2rawdata(u32 sensor, int temp)
+{
+ u32 high_bits, low_bits;
+ int i;
+ const short *high_tab;
+ const short *low_tab;
+
+ if (SPRD_ARM_SENSOR == sensor) {
+ high_tab = temp_search_high_40nm;
+ low_tab = temp_search_low_40nm;
+ } else if (SPRD_PMIC_SENSOR == sensor) {
+ high_tab = temp_search_high_152nm;
+ low_tab = temp_search_low_152nm;
+ } else {
+ printk(KERN_ERR "error thm sensor id:%d \n", sensor);
+ return 0;
+ }
+
+ if (temp < high_tab[0]) {
+ printk(KERN_ERR "temp over low temp:%d \n", temp);
+ return 0;
+ }
+
+ for (i = HIGH_TAB_SZ - 1; i >= 0; i--) {
+ if (high_tab[i] <= temp)
+ break;
+ }
+ if (i < 0) {
+ i = 0;
+ }
+ temp -= high_tab[i];
+ high_bits = i;
+
+ for (i = LOW_TAB_SZ - 1; i >= 0; i--) {
+ if (low_tab[i] <= temp)
+ break;
+ }
+ if (i < 0) {
+ i = 0;
+ }
+ low_bits = i;
+ return ((high_bits << HIGH_BITS_OFFSET) | low_bits);
+
+}
+
+int sprd_thm_rawdata2temp(u32 sensor, int rawdata)
+{
+ const short *high_tab;
+ const short *low_tab;
+
+ if (SPRD_ARM_SENSOR == sensor) {
+ high_tab = temp_search_high_40nm;
+ low_tab = temp_search_low_40nm;
+ } else if (SPRD_PMIC_SENSOR == sensor) {
+ high_tab = temp_search_high_152nm;
+ low_tab = temp_search_low_152nm;
+ } else {
+ printk(KERN_ERR "error thm sensor id:%d \n", sensor);
+ return 0;
+ }
+
+ return high_tab[(rawdata >> HIGH_BITS_OFFSET) & 0x07] +
+ low_tab[rawdata & 0x0F];
+}
+
+int sprd_thm_temp_read(struct sprd_thermal_zone *pzone)
+{
+ u32 rawdata = 0;
+ int cal_offset = 0;
+ u32 sensor = pzone->sensor_id;
+
+ if (SPRD_ARM_SENSOR == sensor) {
+ rawdata = __thm_reg_read((SPRD_THM_BASE + SENSOR_TEMPER0_READ));
+ cal_offset = arm_sen_cal_offset;
+ } else if (SPRD_PMIC_SENSOR == sensor) {
+ rawdata = __thm_reg_read(ANA_THM_BASE + SENSOR_TEMPER0_READ);
+ cal_offset = pmic_sen_cal_offset;
+ } else {
+ printk(KERN_ERR "error thm sensor id:%d \n", sensor);
+ return 0;
+ }
+ THM_DEBUG("thm sensor id:%d, cal_offset:%d, rawdata:0x%x\n", sensor,
+ cal_offset, rawdata);
+ return sprd_thm_rawdata2temp(sensor, rawdata) + cal_offset;
+}
+int sprd_thm_chip_id_check(void)
+{
+ u32 chip_id_tmp;
+
+ chip_id_tmp = sci_get_chip_id();
+
+#if !defined(CONFIG_ARCH_SCX15)
+ return 0;
+#else
+ if ((TSMC_DOLPHINW4T_CHIP_ID_1 == chip_id_tmp) || (TSMC_DOLPHINW4T_CHIP_ID_2 == chip_id_tmp) ||
+ (TSMC_DOLPHINWT4T_CHIP_ID_1 == chip_id_tmp)) {
+ //printk("Sprd thm the chip support thermal CHIP_ID:0x%x \n",chip_id_tmp);
+ return 0;
+ } else {
+ printk("Sprd thm the chip don't support thermal CHIP_ID:0x%x \n",chip_id_tmp);
+ return -1;
+ }
+#endif
+}
+int sprd_thm_hw_init(struct sprd_thermal_zone *pzone)
+{
+ u32 local_sensor_addr, base_addr = 0;
+ u32 local_sen_id = 0;
+ u32 raw_temp = 0;
+ int cal_offset = 0;
+ struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+
+ base_addr = (u32) pzone->reg_base;
+
+ printk(KERN_NOTICE "sprd_thm_hw_init 2713_thm id:%d,base 0x%x\n",
+ pzone->sensor_id, base_addr);
+
+ if (SPRD_ARM_SENSOR == pzone->sensor_id) {
+ //ddie thm en
+ sci_glb_set(REG_AON_APB_APB_EB1, BIT_THM_EB);
+ sci_glb_set(REG_AON_APB_APB_RTC_EB,
+ (BIT_THM_RTC_EB | BIT_ARM_THMA_RTC_EB |
+ BIT_ARM_THMA_RTC_AUTO_EN));
+ cal_offset = arm_sen_cal_offset = 0;
+ local_sen_id = 0;
+ } else if (SPRD_PMIC_SENSOR == pzone->sensor_id) {
+ //adie thm en
+ sci_adi_set(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_THM_EN);
+ sci_adi_set(ANA_REG_GLB_RTC_CLK_EN,
+ BIT_RTC_THMA_AUTO_EN | BIT_RTC_THMA_EN |
+ BIT_RTC_THM_EN);
+ cal_offset = pmic_sen_cal_offset = 0;
+ local_sen_id = 0;
+ } else {
+ printk(KERN_ERR "error thm sensor id:%d \n", pzone->sensor_id);
+ return -EINVAL;
+ }
+ __thm_reg_write((base_addr + THM_CTRL), 0x1 >> local_sen_id, 0);
+ __thm_reg_write((base_addr + THM_INT_CTRL), 0x3, 0); //enable top int
+
+ local_sensor_addr = base_addr + local_sen_id * LOCAL_SENSOR_ADDR_OFF;
+
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), 0, ~0); //disable all int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), ~0, 0); //clr all int
+
+ //set int
+ if (trip_tab->num_trips > 0) {
+#if 0
+ //set hot
+ if (trip_tab->trip_points[0].type == THERMAL_TRIP_ACTIVE) {
+ raw_temp =
+ sprd_thm_temp2rawdata(pzone->sensor_id,
+ trip_tab->trip_points[0].
+ temp - cal_offset);
+ if (raw_temp < RAW_TEMP_RANGE_MSK) {
+ raw_temp++;
+ }
+ //set hot int temp value
+ __thm_reg_write((local_sensor_addr +
+ SENSOR_OVERHEAD_HOT_THRES), raw_temp,
+ RAW_TEMP_RANGE_MSK);
+ raw_temp =
+ sprd_thm_temp2rawdata(pzone->sensor_id,
+ trip_tab->trip_points[0].
+ temp - HOT2NOR_RANGE -
+ cal_offset);
+ //set hot2nor int temp value
+ __thm_reg_write((local_sensor_addr +
+ SENSOR_HOT2NOR__HIGHOFF_THRES),
+ raw_temp << RAW_TEMP_OFFSET,
+ RAW_TEMP_RANGE_MSK << RAW_TEMP_OFFSET);
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), //enable int
+ SEN_HOT2NOR_INT_BIT | SEN_HOT_INT_BIT,
+ 0);
+ }
+#else
+ current_trip_num = 0;
+ sprd_thm_set_active_trip(pzone,current_trip_num);
+#endif
+ //set overheat
+ if (trip_tab->trip_points[trip_tab->num_trips - 1].type ==
+ THERMAL_TRIP_CRITICAL) {
+ raw_temp =
+ sprd_thm_temp2rawdata(pzone->sensor_id,
+ trip_tab->trip_points
+ [trip_tab->num_trips -
+ 1].temp - cal_offset);
+ //set overheat int temp value
+ __thm_reg_write((local_sensor_addr +
+ SENSOR_OVERHEAT_HOT_THRES),
+ raw_temp << RAW_TEMP_OFFSET,
+ RAW_TEMP_RANGE_MSK << RAW_TEMP_OFFSET);
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), //enable int
+ SEN_OVERHEAT_INT_BIT, 0);
+ }
+ }
+
+ printk(KERN_NOTICE "sprd_thm_hw_init addr 0x:%x,int ctrl 0x%x\n",
+ local_sensor_addr,
+ __thm_reg_read((local_sensor_addr + SENSOR_INT_CTRL)));
+
+ // Set sensor det period is 2.25S
+ __thm_reg_write((local_sensor_addr + SENSOR_DET_PERI), 0x2000, 0x2000);
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x101, 0x101);
+
+ // Start the sensor by set Sen_set_rdy(bit3)
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x8, 0x8);
+
+ return 0;
+
+}
+
+int sprd_thm_hw_disable_sensor(struct sprd_thermal_zone *pzone)
+{
+ int ret = 0;
+ // Sensor minitor disable
+ if(SPRD_ARM_SENSOR == pzone->sensor_id){
+ __thm_reg_write((u32)(pzone->reg_base + SENSOR_CTRL), 0x0, 0x8);
+ __thm_reg_write((u32)(pzone->reg_base + SENSOR_CTRL), 0x00, 0x01);
+ }
+ return ret;
+
+}
+
+int sprd_thm_hw_enable_sensor(struct sprd_thermal_zone *pzone)
+{
+ int ret = 0;
+ // Sensor minitor enable
+ THM_DEBUG("sprd_2713_thm enable sensor sensor_ID:0x%x \n",pzone->sensor_id);
+ if(SPRD_ARM_SENSOR == pzone->sensor_id){
+ __thm_reg_write((u32)(pzone->reg_base + SENSOR_CTRL), 0x01, 0x01);
+ __thm_reg_write((u32)(pzone->reg_base + SENSOR_CTRL), 0x8, 0x8);
+ }
+ return ret;
+
+}
+
+
+u16 int_ctrl_reg[SPRD_MAX_SENSOR];
+int sprd_thm_hw_suspend(struct sprd_thermal_zone *pzone)
+{
+ u32 local_sen_id = 0;
+ u32 local_sensor_addr;
+ int ret = 0;
+
+ local_sensor_addr =
+ (u32) pzone->reg_base + local_sen_id * LOCAL_SENSOR_ADDR_OFF;
+ int_ctrl_reg[pzone->sensor_id] = __thm_reg_read((local_sensor_addr + SENSOR_INT_CTRL));
+
+ sprd_thm_hw_disable_sensor(pzone);
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), 0, ~0); //disable all int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), ~0, 0); //clr all int
+ return ret;
+}
+int sprd_thm_hw_resume(struct sprd_thermal_zone *pzone)
+{
+ u32 local_sen_id = 0;
+ u32 local_sensor_addr;
+ int ret = 0;
+
+ local_sensor_addr =
+ (u32) pzone->reg_base + local_sen_id * LOCAL_SENSOR_ADDR_OFF;
+
+ sprd_thm_hw_enable_sensor(pzone);
+ if(SPRD_ARM_SENSOR == pzone->sensor_id){
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), ~0, 0); //clr all int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), int_ctrl_reg[pzone->sensor_id], ~0); //enable int of saved
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x9, 0);
+ }
+ return ret;
+}
+
+int sprd_thm_hw_irq_handle(struct sprd_thermal_zone *pzone)
+{
+ u32 local_sen_id = 0;
+ u32 local_sensor_addr;
+ u32 int_sts;
+ int ret = 0;
+ u32 overhead_hot_tem_cur = 0;
+ struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+ int temp;
+
+ local_sensor_addr =
+ (u32) pzone->reg_base + local_sen_id * LOCAL_SENSOR_ADDR_OFF;
+ int_sts = __thm_reg_read(local_sensor_addr + SENSOR_INT_STS);
+
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), int_sts, ~0); //CLR INT
+ temp = sprd_thm_temp_read(pzone->sensor_id);
+
+ printk("sprd_thm_hw_irq_handle --------@@@------id:%d, int_sts :0x%x \n",
+ pzone->sensor_id, int_sts);
+ printk("sprd_thm_hw_irq_handle ------$$$--------temp:%d\n", temp);
+
+ overhead_hot_tem_cur = __thm_reg_read((local_sensor_addr + SENSOR_OVERHEAT_HOT_THRES))
+ & RAW_TEMP_RANGE_MSK;
+
+ if (int_sts & SEN_HOT_INT_BIT){
+ if ((current_trip_num) >= (trip_tab->num_trips - 2)){
+ current_trip_num = trip_tab->num_trips - 2;
+ return ret;
+ }
+ if (temp >= trip_tab->trip_points[current_trip_num].temp - INTOFFSET){
+ current_trip_num++;
+ sprd_thm_set_active_trip(pzone,current_trip_num);
+ }
+ }else if (int_sts & SEN_LOWOFF_INT_BIT){
+ if (temp < trip_tab->trip_points[current_trip_num].lowoff + INTOFFSET){
+ current_trip_num--;
+ sprd_thm_set_active_trip(pzone,current_trip_num);
+ }
+ }else{
+ THM_DEBUG("sprd_thm_hw_irq_handle NOT a HOT or LOWOFF interrupt \n");
+ return ret;
+ }
+ return ret;
+}
diff --git a/drivers/thermal/sprd_board_sensor.c b/drivers/thermal/sprd_board_sensor.c
new file mode 100644
index 00000000..ed730e1f
--- /dev/null
+++ b/drivers/thermal/sprd_board_sensor.c
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <linux/io.h>
+#include <soc/sprd/adi.h>
+#include "thm.h"
+#include <linux/sprd_thm.h>
+#include <soc/sprd/arch_misc.h>
+#include <soc/sprd/hardware.h>
+#include "thermal_core.h"
+#include <soc/sprd/adc.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <linux/slab.h>
+
+//#define SPRD_THM_BOARD_DEBUG
+#ifdef SPRD_THM_BOARD_DEBUG
+#define THM_BOARD_DEBUG(format, arg...) printk("sprd board thm: " "@@@" format, ## arg)
+#else
+#define THM_BOARD_DEBUG(format, arg...)
+#endif
+
+#define THM_SENOR_NUM 8
+extern int sprd_thermal_init(struct sprd_thermal_zone *pzone);
+extern void sprd_thermal_remove(struct sprd_thermal_zone *pzone);
+
+
+struct sprd_board_sensor_config *pthm_config;
+//extern uint16_t sprdchg_bat_adc_to_vol(uint16_t adcvalue);
+
+static int sprdthm_read_temp_adc(void)
+{
+#define SAMPLE_NUM 15
+ int cnt = pthm_config->temp_adc_sample_cnt;
+
+ if (cnt > SAMPLE_NUM) {
+ cnt = SAMPLE_NUM;
+ } else if (cnt < 1) {
+ cnt = 1;
+ }
+
+ if (pthm_config->temp_support) {
+ int ret, i, j, temp;
+ int adc_val[cnt];
+ struct adc_sample_data data = {
+ .channel_id = pthm_config->temp_adc_ch,
+ .channel_type = 0, /*sw */
+ .hw_channel_delay = 0, /*reserved */
+ .scale = pthm_config->temp_adc_scale, /*small scale */
+ .pbuf = &adc_val[0],
+ .sample_num = cnt,
+ .sample_bits = 1,
+ .sample_speed = 0, /*quick mode */
+ .signal_mode = 0, /*resistance path */
+ };
+
+ ret = sci_adc_get_values(&data);
+ WARN_ON(0 != ret);
+
+ for (j = 1; j <= cnt - 1; j++) {
+ for (i = 0; i < cnt - j; i++) {
+ if (adc_val[i] > adc_val[i + 1]) {
+ temp = adc_val[i];
+ adc_val[i] = adc_val[i + 1];
+ adc_val[i + 1] = temp;
+ }
+ }
+ }
+ THM_BOARD_DEBUG("sprdthm: channel:%d,sprdthm_read_temp_adc:%d\n",
+ data.channel_id, adc_val[cnt / 2]);
+ return adc_val[cnt / 2];
+ } else {
+ return 3000;
+ }
+}
+
+static int sprdthm_interpolate(int x, int n, struct sprdboard_table_data *tab)
+{
+ int index;
+ int y;
+
+ if (x >= tab[0].x)
+ y = tab[0].y;
+ else if (x <= tab[n - 1].x)
+ y = tab[n - 1].y;
+ else {
+ /* find interval */
+ for (index = 1; index < n; index++)
+ if (x > tab[index].x)
+ break;
+ /* interpolate */
+ y = (tab[index - 1].y - tab[index].y) * (x - tab[index].x)
+ * 2 / (tab[index - 1].x - tab[index].x);
+ y = (y + 1) / 2;
+ y += tab[index].y;
+ }
+ return y;
+}
+static uint16_t sprdthm_adc_to_vol(uint16_t channel, int scale,
+ uint16_t adcvalue)
+{
+ uint32_t result;
+ uint32_t vthm_vol = adc2vbat(adcvalue,scale);
+ uint32_t m, n;
+ uint32_t thm_numerators, thm_denominators;
+ uint32_t numerators, denominators;
+
+ sci_adc_get_vol_ratio(ADC_CHANNEL_VBAT, 0, &thm_numerators,
+ &thm_denominators);
+ sci_adc_get_vol_ratio(channel, scale, &numerators, &denominators);
+
+ ///v1 = vbat_vol*0.268 = vol_bat_m * r2 /(r1+r2)
+ n = thm_denominators * numerators;
+ m = vthm_vol * thm_numerators * (denominators);
+ result = (m + n / 2) / n;
+ return result;
+}
+
+static int sprdthm_search_temp_tab(int val)
+{
+ return sprdthm_interpolate(val, pthm_config->temp_tab_size,
+ pthm_config->temp_tab);
+}
+
+static int sprd_board_thm_get_reg_base(struct sprd_thermal_zone *pzone ,struct resource *regs)
+{
+ return 0;
+}
+static int sprd_board_thm_set_active_trip(struct sprd_thermal_zone *pzone, int trip )
+{
+ return 0;
+}
+
+static unsigned long sprd_board_thm_temp_read(struct sprd_thermal_zone *pzone)
+{
+ int temp;
+ if (pthm_config->temp_support) {
+ int val = sprdthm_read_temp_adc();
+ //voltage mode
+ if (pthm_config->temp_table_mode) {
+ val =sprdthm_adc_to_vol(pthm_config->temp_adc_ch,
+ pthm_config->temp_adc_scale, val);
+ THM_BOARD_DEBUG("sprdthm: sprdthm_read_temp voltage:%d\n", val);
+ }
+ THM_BOARD_DEBUG("sprd_board_thm_temp_read y=%d\n",sprdthm_search_temp_tab(val));
+ temp = sprdthm_search_temp_tab(val);
+ printk("sensor id:%d,rawdata:0x%x, temp:%lu\n", pzone->sensor_id, val, temp*1000);
+ return temp*1000;
+ } else {
+ return -35000;
+ }
+
+}
+
+static int sprd_board_thm_hw_init(struct sprd_thermal_zone *pzone)
+{
+ THM_BOARD_DEBUG("sprd_board_thm_hw_init\n");
+ pthm_config=pzone->sensor_config;
+
+ return 0;
+}
+
+
+static int sprd_board_thm_hw_suspend(struct sprd_thermal_zone *pzone)
+{
+ THM_BOARD_DEBUG("sprd_board_thm_hw_suspend\n");
+
+ return 0;
+}
+static int sprd_board_thm_hw_resume(struct sprd_thermal_zone *pzone)
+{
+
+ THM_BOARD_DEBUG("sprd_board_thm_hw_resume\n");
+
+ return 0;
+}
+#ifdef THM_TEST
+static int sprd_board_thm_trip_set(struct sprd_thermal_zone *pzone,int trip)
+{
+ THM_BOARD_DEBUG("sprd_thm_trip_set trip=%d, temp=%ld,lowoff =%ld\n",
+ trip,pzone->trip_tab->trip_points[trip].temp,pzone->trip_tab->trip_points[trip].lowoff);
+ return 0;
+}
+#endif
+
+static int sprd_board_thm_get_trend(struct sprd_thermal_zone *pzone, int trip, enum thermal_trend *ptrend)
+{
+ *ptrend = pzone->trend_val;
+ return 0;
+}
+
+static int sprd_board_thm_get_hyst(struct sprd_thermal_zone *pzone, int trip, unsigned long *physt)
+{
+ struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+ if (trip >= trip_tab->num_trips - 2){
+ *physt = 0;
+ }else{
+ *physt = trip_tab->trip_points[trip].temp - trip_tab->trip_points[trip + 1].lowoff;
+ }
+ return 0;
+}
+
+struct thm_handle_ops sprd_boardthm_ops =
+{
+ .hw_init = sprd_board_thm_hw_init,
+ .get_reg_base = sprd_board_thm_get_reg_base,
+ .read_temp = sprd_board_thm_temp_read,
+ .get_trend = sprd_board_thm_get_trend,
+ .get_hyst = sprd_board_thm_get_hyst,
+ .suspend = sprd_board_thm_hw_suspend,
+ .resume = sprd_board_thm_hw_resume,
+ .trip_debug_set = sprd_board_thm_trip_set,
+};
+
+#ifdef CONFIG_OF
+static struct sprd_thm_platform_data *thermal_detect_parse_dt(
+ struct device *dev)
+{
+ struct sprd_thm_platform_data *pdata;
+ struct device_node *np = dev->of_node;
+ u32 trip_points_critical,trip_num;
+ char prop_name[32];
+ const char *tmp_str;
+ u32 tmp_data,tmp_lowoff;
+ int ret,i ,j = 0;
+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(dev, "could not allocate memory for platform data\n");
+ return NULL;
+ }
+ ret = of_property_read_u32(np, "trip-points-critical", &trip_points_critical);
+ if(ret){
+ dev_err(dev, "fail to get trip_points_critical\n");
+ goto fail;
+ }
+ ret = of_property_read_u32(np, "trip-num", &trip_num);
+ if(ret){
+ dev_err(dev, "fail to get trip_num\n");
+ goto fail;
+ }
+ for (i = 0; i <trip_num-1; ++i) {
+ sprintf(prop_name, "trip%d-temp-active", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data)){
+ dev_err(dev, "fail to get trip%d-temp-active\n",i);
+ goto fail;
+ }
+
+ pdata->trip_points[i].temp = tmp_data;
+ sprintf(prop_name, "trip%d-temp-lowoff", i);
+ if (of_property_read_u32(np, prop_name, &tmp_lowoff)){
+ dev_err(dev, "fail to get trip%d-temp-lowoff\n",i);
+ goto fail;
+ }
+ pdata->trip_points[i].lowoff = tmp_lowoff;
+ sprintf(prop_name, "trip%d-type", i);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto fail;
+
+ if (!strcmp(tmp_str, "active"))
+ pdata->trip_points[i].type = THERMAL_TRIP_ACTIVE;
+ else if (!strcmp(tmp_str, "passive"))
+ pdata->trip_points[i].type = THERMAL_TRIP_PASSIVE;
+ else if (!strcmp(tmp_str, "hot"))
+ pdata->trip_points[i].type = THERMAL_TRIP_HOT;
+ else if (!strcmp(tmp_str, "critical"))
+ pdata->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ else
+ goto fail;
+
+ sprintf(prop_name, "trip%d-cdev-num", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data))
+ goto fail;
+ for (j = 0; j < tmp_data; j++) {
+ sprintf(prop_name, "trip%d-cdev-name%d", i, j);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto fail;
+ strcpy(pdata->trip_points[i].cdev_name[j], tmp_str);
+ dev_info(dev,"cdev name: %s \n", pdata->trip_points[i].cdev_name[j]);
+ }
+ dev_info(dev, "trip[%d] temp: %lu lowoff: %lu\n",
+ i, pdata->trip_points[i].temp, pdata->trip_points[i].lowoff);
+ }
+ pdata->trip_points[i].temp = trip_points_critical;
+ pdata->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ dev_info(dev, "trip[%d] temp: %lu \n",
+ i, pdata->trip_points[i].temp);
+ pdata->num_trips = trip_num;
+
+ return pdata;
+
+fail:
+ kfree(pdata);
+ return NULL;
+
+}
+
+static struct sprd_board_sensor_config *sprdboard_thermal_parse_dt(
+ struct device *dev)
+{
+ struct sprd_board_sensor_config *pconfig;
+ struct device_node *np = dev->of_node;
+ int ret,i;
+ int temp;
+ int temp_adc_ch,temp_adc_scale,temp_adc_sample_cnt;
+ int temp_table_mode,temp_tab_size,temp_support;
+ pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL);
+ if (!pconfig) {
+ dev_err(dev, "could not allocate memory for platform data\n");
+ return NULL;
+ }
+ ret =of_property_read_u32(np, "temp-adc-ch",&temp_adc_ch);
+ if(ret){
+ dev_err(dev, "sprd_thermal_probe No temp-adc-ch\n");
+ goto fail;
+ }
+ ret =of_property_read_u32(np, "temp-adc-scale",&temp_adc_scale);
+ if(ret){
+ dev_err(dev, "sprd_thermal_probe No temp_adc_scale\n");
+ goto fail;
+ }
+ ret =of_property_read_u32(np, "temp-adc-sample-cnt",&temp_adc_sample_cnt);
+ if(ret){
+ dev_err(dev, "fail to get temp_adc_sample_cnt\n");
+ goto fail;
+ }
+ ret =of_property_read_u32(np, "temp-table-mode",&temp_table_mode);
+ if(ret){
+ dev_err(dev, "fail to get temp_table_mode\n");
+ goto fail;
+ }
+ ret =of_property_read_u32(np, "temp-tab-size",&temp_tab_size);
+ if(ret){
+ dev_err(dev, "fail to get temp_tab_size\n");
+ goto fail;
+ }
+ ret =of_property_read_u32(np, "temp-support",&temp_support);
+ if(ret){
+ dev_err(dev, "fail to get temp_support\n");
+ goto fail;
+ }
+ pconfig->temp_adc_ch=temp_adc_ch;
+ pconfig->temp_adc_sample_cnt=temp_adc_sample_cnt;
+ pconfig->temp_adc_scale=temp_adc_scale;
+ pconfig->temp_table_mode=temp_table_mode;
+ pconfig->temp_support=temp_support;
+ pconfig->temp_tab_size=temp_tab_size;
+ pconfig->temp_tab = kzalloc(sizeof(struct sprdboard_table_data) *
+ pconfig->temp_tab_size-1, GFP_KERNEL);
+ for (i = 0; i < pconfig->temp_tab_size-1; i++) {
+ ret = of_property_read_u32_index(np, "temp-tab-val", i,
+ &pconfig->temp_tab[i].x);
+ if(ret){
+ dev_err(dev, "fail to get temp-tab-va\n");
+ goto fail;
+ }
+ ret = of_property_read_u32_index(np, "temp-tab-temp", i, &temp);
+ if(ret){
+ dev_err(dev, "fail to get temp-tab-temp\n");
+ goto fail;
+ }
+ pconfig->temp_tab[i].y = temp - 1000;
+ }
+ return pconfig;
+fail:
+ kfree(pconfig);
+ return NULL;
+}
+
+#endif
+static int sprd_board_thm_probe(struct platform_device *pdev)
+{
+ struct sprd_thermal_zone *pzone = NULL;
+ struct sprd_thm_platform_data *ptrips = NULL;
+ struct sprd_board_sensor_config *pconfig = NULL;
+ const char *thm_name;
+ int ret,temp_interval,sensor_id;
+ printk("sprd_thermal_probe---------start\n");
+#ifdef CONFIG_OF
+ struct device_node *np = pdev->dev.of_node;
+#endif
+
+#ifdef CONFIG_OF
+ if (!np) {
+ dev_err(&pdev->dev, "device node not found\n");
+ return -EINVAL;
+ }
+ pconfig = sprdboard_thermal_parse_dt(&pdev->dev);
+ if (!pconfig){
+ dev_err(&pdev->dev, "not found ptrips\n");
+ return -EINVAL;
+ }
+ ptrips = thermal_detect_parse_dt(&pdev->dev);
+#else
+ ptrips = dev_get_platdata(&pdev->dev);
+#endif
+ if (!ptrips){
+ dev_err(&pdev->dev, "not found ptrips\n");
+ return -EINVAL;
+ }
+ pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
+ if (!pzone){
+ kfree(pconfig);
+ kfree(ptrips);
+ return -ENOMEM;
+ }
+ mutex_init(&pzone->th_lock);
+ mutex_lock(&pzone->th_lock);
+ printk("sprd_ddie_thm_probe start\n");
+#if 0
+ pdev->id = of_alias_get_id(np, "thmzone");
+ printk(KERN_INFO " sprd_thermal_probe id:%d\n", pdev->id);
+ if (unlikely(pdev->id < 0 || pdev->id >= THM_SENOR_NUM)) {
+ dev_err(&pdev->dev, "does not support id %d\n", pdev->id);
+ return -ENXIO;
+ }
+#endif
+ ret = of_property_read_u32(np, "temp-inteval", &temp_interval);
+ if(ret){
+ dev_err(&pdev->dev, "fail to get temp-inteval\n");
+ return -EINVAL;
+ }
+ ret = of_property_read_u32(np, "id", &sensor_id);
+ if (ret) {
+ dev_err(&pdev->dev, "fail to get id\n");
+ return -EINVAL;
+ }
+ of_property_read_string(np, "thermal-name",&thm_name);
+ strcpy(pzone->thermal_zone_name, thm_name);
+ pzone->trip_tab = ptrips;
+ pzone->mode = THERMAL_DEVICE_DISABLED;
+ pzone->trip_tab = ptrips;
+ pzone->sensor_config= pconfig;
+ pzone->trend_val = THERMAL_TREND_STABLE;
+ pzone->temp_inteval = temp_interval;
+ pzone->sensor_id = sensor_id;
+ pzone->ops = &sprd_boardthm_ops;
+ ret = sprd_board_thm_hw_init(pzone);
+ if(ret){
+ dev_err(&pdev->dev, " pzone hw init error id =%d\n",pzone->sensor_id);
+ return -ENODEV;
+ }
+ ret = sprd_thermal_init(pzone);
+ if(ret){
+ dev_err(&pdev->dev, " pzone sw init error id =%d\n",pzone->sensor_id);
+ return -ENODEV;
+ }
+ platform_set_drvdata(pdev, pzone);
+ printk("sprd_ddie_thm_probe end\n");
+ mutex_unlock(&pzone->th_lock);
+ return 0;
+}
+
+static int sprd_board_thm_remove(struct platform_device *pdev)
+{
+
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ sprd_thermal_remove(pzone);
+ return 0;
+}
+static int sprd_board_thm_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ //flush_delayed_work(&pzone->thm_logtime_work);
+ flush_delayed_work(&pzone->thm_read_work);
+ flush_delayed_work(&pzone->resume_delay_work);
+ pzone->ops->suspend(pzone);
+ return 0;
+}
+static int sprd_board_thm_resume(struct platform_device *pdev)
+{
+
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ schedule_delayed_work(&pzone->resume_delay_work, (HZ * 1));
+ schedule_delayed_work(&pzone->thm_read_work, (HZ * 5));
+ //schedule_delayed_work(&pzone->thm_logtime_work, (HZ * 7));
+ //pzone->ops->resume(pzone);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id thermal_of_match[] = {
+ { .compatible = "sprd,board-thermal", },
+ {}
+};
+#endif
+
+static struct platform_driver sprd_thermal_driver = {
+ .probe = sprd_board_thm_probe,
+ .suspend = sprd_board_thm_suspend,
+ .resume = sprd_board_thm_resume,
+ .remove = sprd_board_thm_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "board-thermal",
+#ifdef CONFIG_OF
+ .of_match_table = of_match_ptr(thermal_of_match),
+#endif
+ },
+};
+static int __init sprd_board_thermal_init(void)
+{
+ return platform_driver_register(&sprd_thermal_driver);
+}
+
+static void __exit sprd_board_thermal_exit(void)
+{
+ platform_driver_unregister(&sprd_thermal_driver);
+}
+
+device_initcall_sync(sprd_board_thermal_init);
+module_exit(sprd_board_thermal_exit);
+MODULE_AUTHOR("Freeman Liu <freeman.liu@spreadtrum.com>");
+MODULE_DESCRIPTION("sprd thermal driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/thermal/sprd_cpu_cooling.c b/drivers/thermal/sprd_cpu_cooling.c
new file mode 100644
index 00000000..29e14bc3
--- /dev/null
+++ b/drivers/thermal/sprd_cpu_cooling.c
@@ -0,0 +1,604 @@
+/*
+ * linux/drivers/thermal/sprd_cpu_cooling.c
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * 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/slab.h>
+#include <linux/err.h>
+#include <linux/types.h>
+#include <linux/thermal.h>
+#include <linux/sprd_cpu_cooling.h>
+#include <linux/platform_device.h>
+#include <linux/cpumask.h>
+#ifdef CONFIG_OF
+#include <linux/of_device.h>
+#endif
+
+#define SPRDCPU_DEBUG__
+#ifdef SPRDCPU_DEBUG__
+#define SPRDCPU_DEBUG(format, arg...) pr_info("cpu-cooling: " format, ## arg)
+#else
+#define SPRDCPU_DEBUG(format, arg...)
+#endif
+
+struct cpu_cooling_param_t {
+ int state;
+ int max_core;
+ int limit_freq;
+ int low_freq;
+ int low_vol;
+};
+
+struct thermal_cooling_info_t {
+ unsigned long cooling_state;
+ struct thermal_cooling_device *cdev;
+ struct sprd_cpu_cooling_platform_data *pdata;
+ int max_state;
+ int enable;
+ int cluster;
+ struct cpu_cooling_param_t param;
+#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8)
+ int binning;
+#endif
+};
+
+static ssize_t sprd_cpu_store_caliberate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+static ssize_t sprd_cpu_show_caliberate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+
+/* sys I/F for cooling device */
+#define to_cooling_device(_dev) \
+ container_of(_dev, struct thermal_cooling_device, device)
+
+#define SPRD_CPU_CALIBERATE_ATTR(_name) \
+{ \
+ .attr = { .name = #_name, .mode = S_IRUGO | S_IWUSR | S_IWGRP,}, \
+ .show = sprd_cpu_show_caliberate, \
+ .store = sprd_cpu_store_caliberate, \
+}
+#define SPRD_CPU_CALIBERATE_ATTR_RO(_name) \
+{ \
+ .attr = { .name = #_name, .mode = S_IRUGO, }, \
+ .show = sprd_cpu_show_caliberate, \
+}
+#define SPRD_CPU_CALIBERATE_ATTR_WO(_name) \
+{ \
+ .attr = { .name = #_name, .mode = S_IWUSR | S_IWGRP, }, \
+ .store = sprd_cpu_store_caliberate, \
+}
+
+static struct device_attribute sprd_cpu_caliberate[] = {
+ SPRD_CPU_CALIBERATE_ATTR(cur_ctrl_param),
+};
+
+static int sprd_cpu_creat_caliberate_attr(struct device *dev)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(sprd_cpu_caliberate); i++) {
+ rc = device_create_file(dev, &sprd_cpu_caliberate[i]);
+ if (rc)
+ goto sprd_attrs_failed;
+ }
+ goto sprd_attrs_succeed;
+
+sprd_attrs_failed:
+ while (i--)
+ device_remove_file(dev, &sprd_cpu_caliberate[i]);
+
+sprd_attrs_succeed:
+ return rc;
+}
+
+static int sprd_cpu_remove_caliberate_attr(struct device *dev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sprd_cpu_caliberate); i++) {
+ device_remove_file(dev, &sprd_cpu_caliberate[i]);
+ }
+ return 0;
+}
+
+
+static ssize_t sprd_cpu_show_caliberate(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i = 0;
+ int offset = 0;
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ struct thermal_cooling_info_t *info = cdev->devdata;
+ unsigned long *data = &info->param;
+ int len = sizeof(info->param) / sizeof(info->param.state);
+
+ for (i = 0; i < len; i++)
+ offset += sprintf(buf + offset, "%ld,", data[i]);
+ buf[offset - 1] = '\n';
+
+ SPRDCPU_DEBUG("dev_attr_cur_ctrl_param: %s\n", buf);
+ return offset;
+}
+
+#define BUF_LEN 64
+static ssize_t sprd_cpu_store_caliberate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int i = 0;
+ char *str = NULL;
+ char *pbuf;
+ char *after = NULL;
+ char buffer[BUF_LEN];
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ struct thermal_cooling_info_t *info = cdev->devdata;
+ int *data = &info->param;
+ int len = sizeof(info->param) / sizeof(info->param.state);
+
+ if (count > BUF_LEN){
+ count = BUF_LEN;
+ }
+ strncpy(buffer, buf, count);
+ buffer[count] = '\0';
+ printk("buf param form usespace:%s\n", buf);
+ printk("buffer parm:%s\n", buffer);
+ pbuf = buffer;
+ while ((pbuf!= NULL) && (i < len))
+ {
+ str = strsep(&pbuf, ",");
+ data[i++] = simple_strtoul(str, &after, 0);
+ }
+ printk("sprd_cpu_cooling_param_t.status: %d\n", info->param.state);
+ printk("sprd_cpu_cooling_param_t.core_num: %d\n", info->param.max_core);
+ printk("sprd_cpu_cooling_param_t.freq: %d\n", info->param.limit_freq);
+ printk("sprd_cpu_cooling_param_t.low_freq: %d\n", info->param.low_freq);
+ printk("sprd_cpu_cooling_param_t.low_vol: %d\n", info->param.low_vol);
+
+ cpufreq_thermal_limit(info->cluster, info->param.limit_freq);
+ cpu_core_thermal_limit(info->cluster, info->param.max_core);
+#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8)
+ if (info->binning == 0){
+#endif
+ if (info->param.low_freq > 0 && info->param.low_vol > 0){
+ cpufreq_table_thermal_update(info->param.low_freq, info->param.low_vol);
+ info->param.low_freq = 0;
+ info->param.low_vol = 0;
+ }
+#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8)
+ }
+#endif
+
+ return count;
+}
+
+#if !defined(CONFIG_SPRD_CPU_DYNAMIC_HOTPLUG) && !defined(CONFIG_CPU_FREQ_GOV_SPRDEMAND)
+int cpu_core_thermal_limit(int cluster, int max_core)
+{
+#ifdef CONFIG_HOTPLUG_CPU
+ int cpuid, cpus;
+ int first_cpu, last_cpu;
+
+ if (cluster){
+ first_cpu = NR_CPUS / 2;
+ last_cpu = NR_CPUS - 1;
+ }else{
+ first_cpu = 0;
+#ifdef CONFIG_SCHED_HMP
+ last_cpu = NR_CPUS / 2 -1;
+#else
+ last_cpu = NR_CPUS - 1;
+#endif
+ }
+ for (cpus = 0, cpuid = first_cpu; cpuid <= last_cpu; ++cpuid){
+ if (cpu_online(cpuid)){
+ cpus++;
+ }
+ }
+ if (max_core == cpus){
+ return 0;
+ }
+ if (cpus < max_core){
+ /* plug cpu */
+ for (cpuid = first_cpu; cpuid <= last_cpu; ++cpuid){
+ if (cpu_online(cpuid)){
+ continue;
+ }
+ pr_info("cpu-cooling: we gonna plugin cpu%d !!\n", cpuid);
+ if (cpu_up(cpuid)){
+ pr_info("plug cpu%d failed!\n", cpuid);
+ }
+ if (++cpus >= max_core){
+ return 0;
+ }
+ }
+ }else{
+ /* unplug cpu */
+ for (cpuid = last_cpu; cpuid >= first_cpu; --cpuid){
+ if (!cpu_online(cpuid)){
+ continue;
+ }
+ pr_info("cpu-cooling: we gonna unplug cpu%d !!\n", cpuid);
+ if (cpu_down(cpuid)){
+ pr_info("unplug cpu%d failed!\n", cpuid);
+ }
+ if (--cpus <= max_core){
+ return 0;
+ }
+ }
+ }
+#endif
+
+ return 0;
+}
+#endif
+
+static int get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct thermal_cooling_info_t *info = cdev->devdata;
+ int ret = 0;
+
+ *state = info->max_state;
+
+ return ret;
+}
+
+static int get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct thermal_cooling_info_t *info = cdev->devdata;
+ int ret = 0;
+
+ *state = info->cooling_state;
+
+ return ret;
+}
+
+static int thermal_set_vddarm(struct thermal_cooling_info_t *c_info,
+ unsigned long state)
+{
+ int i, j;
+ struct vddarm_update *pvddarm = c_info->pdata->vddarm_update;
+ struct freq_vddarm *pfreq_vddarm;
+
+ if (pvddarm == NULL){
+ printk("%s vddarm_update isn't configed.\n", __func__);
+ return -1;
+ }
+ for (i = 0; pvddarm[i].freq_vddarm; ++i){
+ if (state != pvddarm[i].state){
+ continue;
+ }
+ pfreq_vddarm = pvddarm[i].freq_vddarm;
+ for (j = 0; pfreq_vddarm[j].freq; ++j){
+ cpufreq_table_thermal_update(pfreq_vddarm[j].freq,
+ pfreq_vddarm[j].vddarm_mv);
+ }
+ }
+
+ return 0;
+}
+
+static int set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct thermal_cooling_info_t *c_info = cdev->devdata;
+ int max_core;
+ int limit_freq;
+
+ if (c_info->max_state <= 0){
+ return 0;
+ }
+ thermal_set_vddarm(c_info, state);
+
+ if (c_info->enable && c_info->cooling_state == state){
+ return 0;
+ }else{
+ c_info->cooling_state = state;
+ }
+
+ limit_freq = c_info->pdata->cpu_state[state].max_freq;
+ max_core = c_info->pdata->cpu_state[state].max_core;
+ c_info->enable = 1;
+ pr_info("%s %s: %d limit_freq: %d kHz max_core: %d\n",
+ cdev->type, __func__, state, limit_freq, max_core);
+ cpufreq_thermal_limit(c_info->cluster, limit_freq);
+ cpu_core_thermal_limit(c_info->cluster, max_core);
+
+ return 0;
+}
+
+static struct thermal_cooling_device_ops sprd_cpufreq_cooling_ops = {
+ .get_max_state = get_max_state,
+ .get_cur_state = get_cur_state,
+ .set_cur_state = set_cur_state,
+};
+
+#ifdef CONFIG_OF
+static int get_vddarm_updata_dt_data(struct device_node *np,
+ struct sprd_cpu_cooling_platform_data *pdata)
+{
+ struct vddarm_update *pvddarm;
+ int vddarm_nr[MAX_CPU_STATE];
+ char name[32];
+ int data[32];
+ int freq_nr;
+ int i, j, k, ret;
+
+ ret = of_property_read_u32_array(np, "vddarm_nr", vddarm_nr, 1);
+ if(ret){
+ printk(KERN_ERR "fail to get vddarm_nr\n");
+ goto error;
+ }
+
+ ret = of_property_read_u32_array(np, "vddarm_nr", vddarm_nr, vddarm_nr[0] + 1);
+ if (ret){
+ printk(KERN_ERR "fail to get all vddarm_nr data.\n");
+ goto error;
+ }
+ printk("%s vddarm_nr:<", __func__);
+ for (i = 0; i < vddarm_nr[0]; ++i){
+ printk("%d ", vddarm_nr[i + 1]);
+ }
+ printk(">\n");
+
+ pvddarm = kzalloc(sizeof(*pvddarm) * (vddarm_nr[0] + 1), GFP_KERNEL);
+ if (pvddarm == NULL){
+ goto error;
+ }
+
+ for (i = 0; i < vddarm_nr[0]; ++i){
+ sprintf(name, "vddarm_update%d", i);
+ ret = of_property_read_u32_array(np, name, data, vddarm_nr[i + 1]);
+ if(ret){
+ printk(KERN_ERR "fail to get %s len:%d\n", name, vddarm_nr[i + 1]);
+ goto vddarm_err;
+ }
+ freq_nr = (vddarm_nr[i + 1] - 1) / 2;
+ pvddarm[i].freq_vddarm = kzalloc(sizeof(struct freq_vddarm) * (freq_nr + 1), GFP_KERNEL);
+ if (pvddarm[i].freq_vddarm == NULL){
+ goto vddarm_err;
+ }
+ printk("%s state: <", __func__);
+ for (j = 0; j < vddarm_nr[i + 1]; ++j){
+ printk("%d ", data[j]);
+ }
+ printk(">\n");
+ pvddarm[i].state = data[0];
+ for (j = 0, k = 1; j < freq_nr; ++j){
+ pvddarm[i].freq_vddarm[j].freq = data[k++];
+ pvddarm[i].freq_vddarm[j].vddarm_mv = data[k++];
+ }
+ pvddarm[i].freq_vddarm[j].freq = 0;
+ }
+ pvddarm[i].freq_vddarm = NULL;
+ pdata->vddarm_update = pvddarm;
+
+ return 0;
+vddarm_err:
+ for (j = 0; j < i; ++j){
+ if (pvddarm[j].freq_vddarm){
+ kfree(pvddarm[j].freq_vddarm);
+ pvddarm[j].freq_vddarm = NULL;
+ }
+ }
+ kfree(pvddarm);
+error:
+ pdata->vddarm_update = NULL;
+ return -1;
+}
+
+static struct sprd_cpu_cooling_platform_data *get_cpu_cooling_dt_data(
+ struct device *dev, struct thermal_cooling_info_t *info)
+{
+ struct sprd_cpu_cooling_platform_data *pdata = NULL;
+ struct device_node *np = dev->of_node;
+ int max_freq[MAX_CPU_STATE];
+ int max_core[MAX_CPU_STATE];
+ int ret, i;
+
+ if (!np) {
+ dev_err(dev, "device node not found\n");
+ return -EINVAL;
+ }
+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(dev, "could not allocate memory for platform data\n");
+ return -1;
+ }
+ ret = of_property_read_u32(np, "cluster", &info->cluster);
+ if(ret){
+ info->cluster = 0;
+ }
+ dev_info(dev, "cluster: %s\n", info->cluster ? "big" : "little");
+ ret = of_property_read_u32(np, "state_num", &pdata->state_num);
+ if(ret || pdata->state_num <= 0){
+ dev_info(dev, "no state_num dts config, Don't use kernel thermal policy.\n");
+ pdata->state_num = 0;
+ info->max_state = 0;
+ goto done;
+ }
+ dev_info(dev, "state_num=%d\n", pdata->state_num);
+ ret = of_property_read_u32_array(np, "max_freq", max_freq, pdata->state_num);
+ if(ret){
+ dev_err(dev, "fail to get max_freq\n");
+ goto error;
+ }
+ ret = of_property_read_u32_array(np, "max_core", max_core, pdata->state_num);
+ if(ret){
+ dev_err(dev, "fail to get max_core\n");
+ goto error;
+ }
+ for (i = 0; i < pdata->state_num; ++i){
+ pdata->cpu_state[i].max_freq = max_freq[i];
+ pdata->cpu_state[i].max_core = max_core[i];
+ dev_info(dev, "state:%d, max_freq:%d, max_core:%d\n", i, max_freq[i], max_core[i]);
+ }
+ info->max_state = pdata->state_num - 1;
+done:
+ get_vddarm_updata_dt_data(np, pdata);
+
+ return pdata;
+
+error:
+ kfree(pdata);
+ pdata = NULL;
+ return pdata;
+}
+#endif
+
+#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8)
+extern int sci_efuse_Dhryst_binning_get(int *val);
+#endif
+static int sprd_cpu_cooling_probe(struct platform_device *pdev)
+{
+ struct thermal_cooling_info_t *info = NULL;
+ struct sprd_cpu_cooling_platform_data *pdata = NULL;
+ char cdev_name[32];
+ int ret = 0;
+#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8)
+ int val = 0;
+#endif
+
+ if (NULL == pdev){
+ return -1;
+ }
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ dev_err(&pdev->dev, "could not allocate memory for info data\n");
+ return -1;
+ }
+#ifdef CONFIG_OF
+ ret = of_property_read_u32(pdev->dev.of_node, "id", &pdev->id);
+ if(ret || pdev->id < 0){
+ pdev->id = 0;
+ }
+ pdata = get_cpu_cooling_dt_data(&pdev->dev, info);
+ if (!pdata){
+ ret = -1;
+ goto err;
+ }
+ pdev->dev.platform_data = pdata;
+#else
+ pdata = dev_get_platdata(&pdev->dev);
+ if (NULL == pdata){
+ dev_err(&pdev->dev, "%s platform data is NULL!\n", __func__);
+ ret = -1;
+ goto err;
+ }
+ info->max_state = pdata->state_num - 1;
+#endif
+
+ pdata->devdata = info;
+ info->pdata = pdata;
+ info->cooling_state = 0,
+ info->enable = 0,
+
+ info->param.state = 0;
+ info->param.max_core = 0;
+ info->param.limit_freq = 0;
+ info->param.low_freq = 0;
+ info->param.low_vol = 0;
+#if defined(CONFIG_ARCH_SCX35L) && !defined(CONFIG_ARCH_SCX35LT8)
+ sci_efuse_Dhryst_binning_get(&val);
+ dev_info(&pdev->dev, "sci_efuse_Dhryst_binning_get--val : %d\n", val);
+ if ((val < 28) && (val >= 22)){
+ info->binning = 1;
+ }else{
+ info->binning = 0;
+ }
+ dev_info(&pdev->dev, "binning : %d\n", info->binning);
+#endif
+
+ sprintf(cdev_name, "thermal-cpufreq-%d", pdev->id);
+ info->cdev = thermal_cooling_device_register(cdev_name, info,
+ &sprd_cpufreq_cooling_ops);
+ if (IS_ERR(info->cdev)){
+ ret = PTR_ERR(info->cdev);
+ goto err;
+ }
+
+ sprd_cpu_creat_caliberate_attr(&info->cdev->device);
+
+ return ret;
+err:
+ kfree(info);
+ return ret;
+}
+
+static int sprd_cpu_cooling_remove(struct platform_device *pdev)
+{
+ struct sprd_cpu_cooling_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct thermal_cooling_info_t *info = pdata->devdata;
+
+ sprd_cpu_remove_caliberate_attr(&info->cdev->device);
+
+#ifdef CONFIG_OF
+ int i;
+
+ if (pdata->vddarm_update){
+ for (i = 0; pdata->vddarm_update[i].freq_vddarm; ++i){
+ kfree(pdata->vddarm_update[i].freq_vddarm);
+ pdata->vddarm_update[i].freq_vddarm = NULL;
+ }
+ kfree(pdata->vddarm_update);
+ }
+ kfree(pdata);
+ pdata = NULL;
+#endif
+ thermal_cooling_device_unregister(info->cdev);
+ kfree(info);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id cpu_cooling_of_match[] = {
+ { .compatible = "sprd,sprd-cpu-cooling", },
+ { }
+};
+#endif
+
+static struct platform_driver cpu_cooling_driver = {
+ .probe = sprd_cpu_cooling_probe,
+ .remove = sprd_cpu_cooling_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "sprd-cpu-cooling",
+#ifdef CONFIG_OF
+ .of_match_table = of_match_ptr(cpu_cooling_of_match),
+#endif
+ },
+};
+
+
+static int __init sprd_cpu_cooling_init(void)
+{
+ return platform_driver_register(&cpu_cooling_driver);
+}
+
+static void __exit sprd_cpu_cooling_exit(void)
+{
+ platform_driver_unregister(&cpu_cooling_driver);
+}
+
+late_initcall(sprd_cpu_cooling_init);
+module_exit(sprd_cpu_cooling_exit);
diff --git a/drivers/thermal/sprd_ddie_alone_thm.c b/drivers/thermal/sprd_ddie_alone_thm.c
new file mode 100644
index 00000000..63d1a205
--- /dev/null
+++ b/drivers/thermal/sprd_ddie_alone_thm.c
@@ -0,0 +1,636 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <linux/io.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/arch_misc.h>
+#include <soc/sprd/hardware.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <linux/of_irq.h>
+#include <linux/thermal.h>
+#include <linux/sprd_thm.h>
+#include "thm.h"
+
+#ifdef CONFIG_OF
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#endif
+
+#define SPRD_THM_DEBUG
+#ifdef SPRD_THM_DEBUG
+#define THM_DEBUG(format, arg...) printk( "sprd thm: " "@@@" format, ## arg)
+#else
+#define THM_DEBUG(format, arg...)
+#endif
+
+#define THM_CTRL (0x0000)
+#define THM_INT_CTRL (0x0004)
+#define SENSOR_CTRL (0x0020)
+#define SENSOR_DET_PERI (0x0024)
+#define SENSOR_INT_CTRL (0x0028)
+#define SENSOR_INT_STS (0x002C)
+#define SENSOR_INT_RAW_STS (0x0030)
+#define SENSOR_INT_CLR (0x0034)
+#define SENSOR_OVERHEAT_THRES (0X0040)
+#define SENSOR_HOT_THRES (0X0044)
+#define SENSOR_HOT2NOR_THRES (0X0048)
+#define SENSOR_HIGHOFF_THRES (0X004C)
+#define SENSOR_LOWOFF_THRES (0X0050)
+#define SENSOR_MON_PERI (0X0058)
+#define SENSOR_MON_CTL (0X005c)
+#define SENSOR_TEMPER0_READ (0x0060)
+#define SENSOR_READ_STATUS (0x0070)
+
+
+#define A_SEN_OVERHEAT_INT_BIT (1 << 5)
+#define A_SEN_HOT_INT_BIT (1 << 4)
+#define A_SEN_HOT2NOR_INT_BIT (1 << 3)
+#define A_SEN_HIGHOFF_BIT (1 << 2)
+#define A_SEN_LOWOFF_INT_BIT (1 << 1)
+#define SEN_OVERHEAT_ALARM_EN (1 << 7)
+#define SEN0_OVERHEAT_ALARM_EN (1 << 8)
+
+
+#define A_RAW_TEMP_OFFSET 8
+#define A_RAW_TEMP_RANGE_MSK 0x7F
+
+#define A_HIGH_BITS_OFFSET 4
+
+#define A_HIGH_TAB_SZ 8
+#define A_LOW_TAB_SZ 16
+
+#define A_HOT2NOR_RANGE 15
+#define A_LOCAL_SENSOR_ADDR_OFF 0x100
+#define A_DELAY_TEMPERATURE 3
+#define INTOFFSET 3
+
+#define A_TSMC_DOLPHINW4T_CHIP_ID_1 0x7715A001
+#define A_TSMC_DOLPHINW4T_CHIP_ID_2 0x7715A003
+#define A_TSMC_DOLPHINWT4T_CHIP_ID_1 0x8815A001
+
+static const short a_temp_search_high_152nm[A_HIGH_TAB_SZ] =
+ { -56, -29, -2, 26, 53, 79, 106, 133 };
+static const short a_temp_search_low_152nm[A_LOW_TAB_SZ] =
+ { 0, 2, 3, 5, 7, 8, 10, 11, 13, 15, 17, 18, 20, 21, 23, 25 };
+
+#define SEN_OVERHEAT_INT_BIT (1 << 5)
+#define SEN_HOT_INT_BIT (1 << 4)
+#define SEN_HOT2NOR_INT_BIT (1 << 3)
+#define SEN_HIGHOFF_BIT (1 << 2)
+#define SEN_LOWOFF_INT_BIT (1 << 1)
+
+#define RAW_TEMP_RANGE_MSK 0x3FFF
+#define RAW_READ_RANGE_MSK 0x7FFF
+
+#define HIGH_BITS_OFFSET 4
+
+#define HOT2NOR_RANGE 15
+#define LOCAL_SENSOR_ADDR_OFF 0x100
+#define DELAY_TEMPERATURE 3
+
+#define LOCAL_THM_ADDR_OFF 0x200
+
+
+#define SEN_DET_PRECISION (0x50)
+
+#define TSMC_DOLPHINW4T_CHIP_ID_1 0x7715A001
+#define TSMC_DOLPHINW4T_CHIP_ID_2 0x7715A003
+#define TSMC_DOLPHINWT4T_CHIP_ID_1 0x8815A001
+
+#define TEMP_TO_RAM_DEGREE 8
+#define TEMP_LOW (-40000)
+#define TEMP_HIGH (120000)
+#define RAW_DATA_LOW (623)
+#define RAW_DATA_HIGH (1030)
+#define TEMP_DATA_LOW (-40000)
+#define TEMP_DATA_HIGH (125000)
+
+#define CRITIC_TEMP 125000
+
+#define RAW_ADC_LOW (550)
+#define RAW_ADC_HIGH (1200)
+
+//static const u32 temp_to_raw[TEMP_TO_RAM_DEGREE + 1] = { RAW_DATA_LOW, 676, 725, 777, 828, 878, 928, 980,RAW_DATA_HIGH };
+
+//static u32 current_trip_num = 0;
+unsigned long SPRD_THM_BASE = 0;
+unsigned int SPRD_THM_SIZE = 0;
+
+#define THM_SENOR_NUM 8
+extern int sprd_thermal_init(struct sprd_thermal_zone *pzone);
+extern void sprd_thermal_remove(struct sprd_thermal_zone *pzone);
+static inline void __thm_reg_write(unsigned long reg, u16 bits, u16 clear_msk);
+static inline u32 __thm_reg_read(unsigned long reg);
+
+static inline void __thm_reg_write(unsigned long reg, u16 bits, u16 clear_msk)
+{
+ __raw_writel(((__raw_readl((volatile void *)reg) & ~clear_msk) | bits), ((volatile void *)reg));
+}
+
+static inline u32 __thm_reg_read(unsigned long reg)
+{
+ return __raw_readl((volatile void *)reg);
+}
+
+int sprd_thm_rawdata2temp(struct sprd_thermal_zone *pzone, int rawdata)
+{
+ u32 temp_result;
+ if ((rawdata < RAW_ADC_LOW) || (rawdata > RAW_ADC_HIGH))
+ return 0;
+ if (pzone->sensor_id == SPRD_ARM_SENSOR
+ || pzone->sensor_id == SPRD_BCORE_SENSOR) {
+ temp_result =
+ (pzone->thm_cal * rawdata - pzone->off_set) ;
+ } else {
+ temp_result = TEMP_LOW +
+ (rawdata - RAW_DATA_LOW) * (TEMP_HIGH -
+ TEMP_LOW) / (RAW_DATA_HIGH -
+ RAW_DATA_LOW);
+ }
+ return temp_result;
+}
+
+u32 sprd_thm_temp2rawdata(struct sprd_thermal_zone * pzone, int temp)
+{
+ u32 raw_result;
+ if ((temp < TEMP_DATA_LOW) || (temp > TEMP_DATA_HIGH))
+ return 0;
+ if (pzone->sensor_id == SPRD_ARM_SENSOR
+ || pzone->sensor_id == SPRD_BCORE_SENSOR) {
+ raw_result = (temp + pzone->off_set) / pzone->thm_cal;
+ } else {
+ raw_result = RAW_DATA_LOW +
+ (temp - TEMP_LOW) * (RAW_DATA_HIGH -
+ RAW_DATA_LOW) / (TEMP_HIGH - TEMP_LOW);
+ }
+ return raw_result;
+}
+
+
+static unsigned long sprd_thm_temp_read(struct sprd_thermal_zone *pzone)
+{
+ u32 rawdata = 0;
+ int cal_offset = 0;
+ unsigned long local_sensor_addr;
+ unsigned long temp1;
+ unsigned long temp2;
+ local_sensor_addr = pzone->reg_base;
+ rawdata = __thm_reg_read(local_sensor_addr + SENSOR_TEMPER0_READ);
+ rawdata = rawdata & RAW_READ_RANGE_MSK;
+ cal_offset = pzone->thm_cal;
+ if (pzone->sensor_id == SPRD_ARM_SENSOR
+ || pzone->sensor_id == SPRD_BCORE_SENSOR) {
+ temp1 = sprd_thm_rawdata2temp(pzone, rawdata);
+ printk("sensor id:%d, rawdata:0x%x, temp:%lu\n", pzone->sensor_id,rawdata,temp1);
+ return temp1;
+ } else {
+ temp2 = sprd_thm_rawdata2temp(pzone, rawdata) - cal_offset;
+ printk("sensor id:%d, rawdata:0x%x, temp:%lu\n", pzone->sensor_id,rawdata,temp2);
+ return temp2;
+ }
+}
+
+#ifdef THM_TEST
+static int sprd_thm_regs_read(struct sprd_thermal_zone *pzone,unsigned int *regs)
+{
+ unsigned long base_addr = 0;
+ printk(" sprd_thm_regs_read\n");
+ if(pzone->sensor_id == SPRD_ARM_SENSOR){
+ base_addr = (unsigned long) pzone->reg_base;
+ *regs = __thm_reg_read((base_addr + SENSOR_DET_PERI));
+ *(regs + 1) = __thm_reg_read((base_addr + SENSOR_MON_CTL));
+ *(regs + 2) = __thm_reg_read((base_addr + SENSOR_MON_PERI));
+ *(regs + 3) = __thm_reg_read((base_addr + SENSOR_CTRL));
+ }
+ return 0;
+}
+
+static int sprd_thm_regs_set(struct sprd_thermal_zone *pzone,unsigned int*regs)
+{
+ u32 pre_data[4] = {0};
+ unsigned long gpu_sensor_addr,base_addr;
+ gpu_sensor_addr =
+ (unsigned long ) pzone->reg_base +LOCAL_SENSOR_ADDR_OFF;
+ printk("sprd_thm_regs_set\n");
+ base_addr = (unsigned long) pzone->reg_base;
+ pre_data[0] = __thm_reg_read(base_addr + SENSOR_DET_PERI);
+ pre_data[1] = __thm_reg_read(base_addr + SENSOR_MON_CTL);
+ pre_data[2] = __thm_reg_read(base_addr + SENSOR_MON_PERI);
+ pre_data[3] = __thm_reg_read(base_addr + SENSOR_CTRL);
+ __thm_reg_write((base_addr + SENSOR_CTRL), 0x00, 0x01);
+ __thm_reg_write((base_addr+ SENSOR_CTRL), 0x8, 0x08);
+ __thm_reg_write((base_addr + SENSOR_DET_PERI), regs[0], pre_data[0]);
+ __thm_reg_write((base_addr + SENSOR_MON_CTL), regs[1], pre_data[1]);
+ __thm_reg_write((base_addr + SENSOR_MON_PERI), regs[2], pre_data[2] |0x100 );
+ __thm_reg_write((base_addr + SENSOR_CTRL), regs[3], pre_data[3]);
+ __thm_reg_write((base_addr + SENSOR_CTRL), 0x8, 0x8);
+
+ pre_data[0] = __thm_reg_read(gpu_sensor_addr + SENSOR_DET_PERI);
+ pre_data[1] = __thm_reg_read(gpu_sensor_addr + SENSOR_MON_CTL);
+ pre_data[2] = __thm_reg_read(gpu_sensor_addr + SENSOR_MON_PERI);
+ pre_data[3] = __thm_reg_read(gpu_sensor_addr + SENSOR_CTRL);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_CTRL), 0x00, 0x01);
+ __thm_reg_write((gpu_sensor_addr+ SENSOR_CTRL), 0x8, 0x08);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_DET_PERI), regs[0], pre_data[0]);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_MON_CTL), regs[1], pre_data[1]);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_MON_PERI), regs[2], pre_data[2] |0x100 );
+ __thm_reg_write((gpu_sensor_addr + SENSOR_CTRL), regs[3], pre_data[3]);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_CTRL), 0x8, 0x8);
+ return 0;
+}
+
+static int sprd_thm_trip_set(struct sprd_thermal_zone *pzone,int trip)
+{
+ THM_DEBUG("sprd_thm_trip_set trip=%d, temp=%ld,lowoff =%ld\n",
+ trip,pzone->trip_tab->trip_points[trip].temp,pzone->trip_tab->trip_points[trip].lowoff);
+ return 0;
+}
+
+
+#endif
+
+static int sprd_thm_hw_init(struct sprd_thermal_zone *pzone)
+{
+ unsigned long local_sensor_addr, base_addr = 0;
+ int ret = 0 ;
+ u32 raw_temp = 0;
+ base_addr = pzone->reg_base;
+ local_sensor_addr = pzone->reg_base;
+ struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+ printk(KERN_NOTICE "sprd_thm_hw_init thm id:%d,base 0x%lx \n",
+ pzone->sensor_id, base_addr);
+ if (pzone->sensor_id == SPRD_ARM_SENSOR) {
+ ret =
+ sci_efuse_arm_thm_cal_get(&pzone->thm_cal, &pzone->off_set);
+ } else if (pzone->sensor_id == SPRD_BCORE_SENSOR) {
+ ret =
+ sci_efuse_bcore_thm_cal_get(&pzone->thm_cal,&pzone->off_set);
+ } else {
+ ret = sci_efuse_thermal_cal_get(&pzone->thm_cal);
+ }
+ THM_DEBUG("pzone->thm_cal =%d,ret =%d\n", pzone->thm_cal, ret);
+ sci_glb_set(REG_AON_APB_APB_EB1, BIT_THM_EB);
+ sci_glb_set(REG_AON_APB_APB_RTC_EB,
+ (BIT_THM_RTC_EB | BIT_GPU_THMA_RTC_EB |
+ BIT_GPU_THMA_RTC_AUTO_EN | BIT_CA53_LIT_THMA_RTC_EB |
+ BIT_CA53_BIG_THMA_RTC_EB |BIT_ARM_THMA_RTC_AUTO_EN ) );
+ __thm_reg_write((base_addr + THM_CTRL), 0x3, 0);
+ __thm_reg_write((base_addr + THM_INT_CTRL), 0x3, 0);
+
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), 0, ~0); //disable all int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), ~0, 0); //clr all int
+#if 0
+ if (SPRD_ARM_SENSOR== pzone->sensor_id){
+ //raw_temp = sprd_thm_temp2rawdata(pzone,CRITIC_TEMP);
+ if (trip_tab->trip_points[trip_tab->num_trips - 1].type ==
+ THERMAL_TRIP_CRITICAL) {
+ raw_temp =
+ sprd_thm_temp2rawdata(pzone, trip_tab->trip_points[trip_tab->num_trips - 1].temp);
+ }
+ //set overheat int temp value
+ __thm_reg_write((local_sensor_addr +SENSOR_OVERHEAT_THRES),raw_temp, RAW_TEMP_RANGE_MSK);
+ __thm_reg_write((base_addr + THM_INT_CTRL), SEN0_OVERHEAT_ALARM_EN, 0);
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL),SEN_OVERHEAT_ALARM_EN, 0);
+ }
+#endif
+ printk(KERN_NOTICE "sprd_thm_hw_init addr 0x:%lx,int ctrl 0x%x\n",
+ local_sensor_addr,
+ __thm_reg_read((local_sensor_addr + SENSOR_INT_CTRL)));
+
+ __thm_reg_write((local_sensor_addr + SENSOR_DET_PERI), 0x4000, 0x4000);
+ __thm_reg_write((local_sensor_addr + SENSOR_MON_CTL), 0x21, 0X21);
+ __thm_reg_write((local_sensor_addr + SENSOR_MON_PERI), 0x400, 0x100);
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x031, 0x131);
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x8, 0x8);
+ return 0;
+}
+
+
+static int sprd_thm_hw_disable_sensor(struct sprd_thermal_zone *pzone)
+{
+ unsigned long local_sensor_addr ;
+ local_sensor_addr = pzone->reg_base;
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x00, 0x01);
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x8, 0x8);
+
+ return 0;
+}
+
+static int sprd_thm_hw_enable_sensor(struct sprd_thermal_zone *pzone)
+{
+ unsigned long local_sensor_addr ;
+ local_sensor_addr = pzone->reg_base;
+ THM_DEBUG("sprd_2713S_thm enable sensor sensor_ID:0x%x \n",pzone->sensor_id);
+ sci_glb_set(REG_AON_APB_APB_RTC_EB,
+ (BIT_THM_RTC_EB | BIT_GPU_THMA_RTC_EB |
+ BIT_GPU_THMA_RTC_AUTO_EN | BIT_CA53_LIT_THMA_RTC_EB |
+ BIT_CA53_BIG_THMA_RTC_EB |BIT_ARM_THMA_RTC_AUTO_EN ) );
+ __thm_reg_write((local_sensor_addr+ SENSOR_CTRL), 0x01, 0x01);
+ __thm_reg_write((local_sensor_addr+ SENSOR_CTRL), 0x8, 0x8);
+
+ return 0;
+}
+
+u16 int_ctrl_reg[SPRD_MAX_SENSOR];
+static int sprd_thm_hw_suspend(struct sprd_thermal_zone *pzone)
+{
+ unsigned long local_sensor_addr ;
+ local_sensor_addr = pzone->reg_base;
+ int_ctrl_reg[pzone->sensor_id] = __thm_reg_read((local_sensor_addr + SENSOR_INT_CTRL));
+
+ sprd_thm_hw_disable_sensor(pzone);
+
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), 0, ~0); //disable all int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), ~0, 0); //clr all int
+
+ return 0;
+}
+static int sprd_thm_hw_resume(struct sprd_thermal_zone *pzone)
+{
+ unsigned long local_sensor_addr ;
+ local_sensor_addr = pzone->reg_base;
+ sprd_thm_hw_enable_sensor(pzone);
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), ~0, 0); //clr all int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), int_ctrl_reg[pzone->sensor_id], ~0); //enable int of saved
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x9, 0);
+
+ return 0;
+}
+
+int sprd_thm_get_trend(struct sprd_thermal_zone *pzone, int trip, enum thermal_trend *ptrend)
+{
+ *ptrend = pzone->trend_val;
+ return 0;
+}
+
+int sprd_thm_get_hyst(struct sprd_thermal_zone *pzone, int trip, unsigned long *physt)
+{
+
+ struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+ if (trip >= trip_tab->num_trips - 2){
+ *physt = 0;
+ }else{
+ *physt = trip_tab->trip_points[trip].temp - trip_tab->trip_points[trip + 1].lowoff;
+ }
+ return 0;
+}
+
+struct thm_handle_ops sprd_ddie_ops =
+{
+ .hw_init = sprd_thm_hw_init,
+ .read_temp = sprd_thm_temp_read,
+ .get_trend = sprd_thm_get_trend,
+ .get_hyst = sprd_thm_get_hyst,
+ .suspend = sprd_thm_hw_suspend,
+ .resume = sprd_thm_hw_resume,
+ .trip_debug_set = sprd_thm_trip_set,
+ .reg_debug_get = sprd_thm_regs_read,
+ .reg_debug_set = sprd_thm_regs_set,
+};
+#ifdef CONFIG_OF
+static struct sprd_thm_platform_data *thermal_detect_parse_dt(
+ struct device *dev)
+{
+ struct sprd_thm_platform_data *pdata;
+ struct device_node *np = dev->of_node;
+ u32 trip_points_critical,trip_num;
+ char prop_name[32];
+ const char *tmp_str;
+ u32 tmp_data,tmp_lowoff;
+ int ret,i ,j = 0;
+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(dev, "could not allocate memory for platform data\n");
+ return NULL;
+ }
+ ret = of_property_read_u32(np, "trip-points-critical", &trip_points_critical);
+ if(ret){
+ dev_err(dev, "fail to get trip_points_critical\n");
+ goto fail;
+ }
+ ret = of_property_read_u32(np, "trip-num", &trip_num);
+ if(ret){
+ dev_err(dev, "fail to get trip_num\n");
+ goto fail;
+ }
+ for (i = 0; i <trip_num-1; ++i) {
+ sprintf(prop_name, "trip%d-temp-active", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data)){
+ dev_err(dev, "fail to get trip%d-temp-active\n",i);
+ goto fail;
+ }
+
+ pdata->trip_points[i].temp = tmp_data;
+ sprintf(prop_name, "trip%d-temp-lowoff", i);
+ if (of_property_read_u32(np, prop_name, &tmp_lowoff)){
+ dev_err(dev, "fail to get trip%d-temp-lowoff\n",i);
+ goto fail;
+ }
+ pdata->trip_points[i].lowoff = tmp_lowoff;
+ sprintf(prop_name, "trip%d-type", i);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto fail;
+
+ if (!strcmp(tmp_str, "active"))
+ pdata->trip_points[i].type = THERMAL_TRIP_ACTIVE;
+ else if (!strcmp(tmp_str, "passive"))
+ pdata->trip_points[i].type = THERMAL_TRIP_PASSIVE;
+ else if (!strcmp(tmp_str, "hot"))
+ pdata->trip_points[i].type = THERMAL_TRIP_HOT;
+ else if (!strcmp(tmp_str, "critical"))
+ pdata->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ else
+ goto fail;
+
+ sprintf(prop_name, "trip%d-cdev-num", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data))
+ goto fail;
+ for (j = 0; j < tmp_data; j++) {
+ sprintf(prop_name, "trip%d-cdev-name%d", i, j);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto fail;
+ strcpy(pdata->trip_points[i].cdev_name[j], tmp_str);
+ dev_info(dev,"cdev name: %s \n", pdata->trip_points[i].cdev_name[j]);
+ }
+ dev_info(dev, "trip[%d] temp: %lu lowoff: %lu\n",
+ i, pdata->trip_points[i].temp, pdata->trip_points[i].lowoff);
+ }
+ pdata->trip_points[i].temp = trip_points_critical;
+ pdata->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ dev_info(dev, "trip[%d] temp: %lu \n",
+ i, pdata->trip_points[i].temp);
+ pdata->num_trips = trip_num;
+
+ return pdata;
+
+fail:
+ kfree(pdata);
+ return NULL;
+
+}
+
+#endif
+static int sprd_ddie_thm_probe(struct platform_device *pdev)
+{
+ struct sprd_thm_platform_data *ptrips = NULL;
+ struct sprd_thermal_zone *pzone = NULL;
+ struct resource *res;
+ const char *thm_name;
+ int ret,temp_interval,sensor_id;
+ unsigned long ddie_thm_base;
+#ifdef CONFIG_OF
+ struct device_node *np = pdev->dev.of_node;
+ if (!np) {
+ dev_err(&pdev->dev, "device node not found\n");
+ return -EINVAL;
+ }
+#endif
+ printk("sprd_ddie_thm_probe start\n");
+#if 0
+ pdev->id = of_alias_get_id(np, "thmzone");
+ printk(KERN_INFO " sprd_thermal_probe id:%d\n", pdev->id);
+ if (unlikely(pdev->id < 0 || pdev->id >= THM_SENOR_NUM)) {
+ dev_err(&pdev->dev, "does not support id %d\n", pdev->id);
+ return -ENXIO;
+ }
+#endif
+ ret = of_property_read_u32(np, "temp-inteval", &temp_interval);
+ if(ret){
+ dev_err(&pdev->dev, "fail to get temp-inteval\n");
+ return -EINVAL;
+ }
+ ret = of_property_read_u32(np, "id", &sensor_id);
+ if (ret) {
+ dev_err(&pdev->dev, "fail to get id\n");
+ return -EINVAL;
+ }
+#ifdef CONFIG_OF
+ ptrips = thermal_detect_parse_dt(&pdev->dev);
+#else
+ ptrips = dev_get_platdata(&pdev->dev);
+#endif
+
+ if (!ptrips){
+ dev_err(&pdev->dev, "not found ptrips\n");
+ return -EINVAL;
+ }
+ pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
+ mutex_init(&pzone->th_lock);
+ mutex_lock(&pzone->th_lock);
+ if (!pzone)
+ return -ENOMEM;
+ of_property_read_string(np, "thermal-name",
+ &thm_name);
+ strcpy(pzone->thermal_zone_name, thm_name);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ddie_thm_base = (unsigned long)devm_ioremap_resource(&pdev->dev, res);
+ if (!ddie_thm_base) {
+ pr_err("thermal ioremap failed!\n");
+ return -ENOMEM;
+ }
+ pzone->reg_base= ddie_thm_base;
+ pzone->trip_tab = ptrips;
+ pzone->temp_inteval = temp_interval;
+ pzone->sensor_id = sensor_id;
+ pzone->ops = &sprd_ddie_ops;
+ ret = sprd_thm_hw_init(pzone);
+ if(ret){
+ dev_err(&pdev->dev, " pzone hw init error id =%d\n",pzone->sensor_id);
+ return -ENODEV;
+ }
+ ret = sprd_thermal_init(pzone);
+ if(ret){
+ dev_err(&pdev->dev, " pzone sw init error id =%d\n",pzone->sensor_id);
+ return -ENODEV;
+ }
+ platform_set_drvdata(pdev, pzone);
+ printk("sprd_ddie_thm_probe end\n");
+ mutex_unlock(&pzone->th_lock);
+ return 0;
+}
+
+static int sprd_ddie_thm_remove(struct platform_device *pdev)
+{
+
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ sprd_thermal_remove(pzone);
+ return 0;
+}
+
+static int sprd_ddie_thm_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ flush_delayed_work(&pzone->thm_read_work);
+ //flush_delayed_work(&pzone->thm_logtime_work);
+ flush_delayed_work(&pzone->resume_delay_work);
+ pzone->ops->suspend(pzone);
+ return 0;
+}
+
+static int sprd_ddie_thm_resume(struct platform_device *pdev)
+{
+
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ schedule_delayed_work(&pzone->resume_delay_work, (HZ * 1));
+ schedule_delayed_work(&pzone->thm_read_work, (HZ * 5));
+ //schedule_delayed_work(&pzone->thm_logtime_work, (HZ * 7));
+ //pzone->ops->resume(pzone);
+ return 0;
+}
+
+static const struct of_device_id thermal_of_match[] = {
+ { .compatible = "sprd,ddie-thermal", },
+ {}
+};
+
+static struct platform_driver sprd_thermal_driver = {
+ .probe = sprd_ddie_thm_probe,
+ .suspend = sprd_ddie_thm_suspend,
+ .resume = sprd_ddie_thm_resume,
+ .remove = sprd_ddie_thm_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ddie-thermal",
+ .of_match_table = of_match_ptr(thermal_of_match),
+ },
+};
+static int __init sprd_ddie_thermal_init(void)
+{
+ return platform_driver_register(&sprd_thermal_driver);
+}
+
+static void __exit sprd_ddie_thermal_exit(void)
+{
+ platform_driver_unregister(&sprd_thermal_driver);
+}
+
+
+device_initcall_sync(sprd_ddie_thermal_init);
+module_exit(sprd_ddie_thermal_exit);
+MODULE_AUTHOR("Freeman Liu <freeman.liu@spreadtrum.com>");
+MODULE_DESCRIPTION("sprd thermal driver");
+MODULE_LICENSE("GPL");
+
+
+
diff --git a/drivers/thermal/sprd_ddie_thm.c b/drivers/thermal/sprd_ddie_thm.c
new file mode 100644
index 00000000..269bda77
--- /dev/null
+++ b/drivers/thermal/sprd_ddie_thm.c
@@ -0,0 +1,681 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <linux/io.h>
+#include <soc/sprd/adi.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include<linux/string.h>
+#include <soc/sprd/arch_misc.h>
+#include <soc/sprd/hardware.h>
+#include <linux/thermal.h>
+#include <linux/sprd_thm.h>
+#include "thm.h"
+
+#ifdef CONFIG_OF
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#endif
+
+#define THM_SENOR_NUM 8
+#define SPRD_THM_DEBUG
+#ifdef SPRD_THM_DEBUG
+#define THM_DEBUG(format, arg...) printk( "sprd thm: " "@@@" format, ## arg)
+#else
+#define THM_DEBUG(format, arg...)
+#endif
+
+#define THM_CTRL (0x0000)
+#define THM_INT_CTRL (0x0004)
+#define SENSOR_CTRL (0x0020)
+#define SENSOR_DET_PERI (0x0024)
+#define SENSOR_INT_CTRL (0x0028)
+#define SENSOR_INT_STS (0x002C)
+#define SENSOR_INT_RAW_STS (0x0030)
+#define SENSOR_INT_CLR (0x0034)
+#define SENSOR_OVERHEAT_THRES (0X0040)
+#define SENSOR_HOT_THRES (0X0044)
+#define SENSOR_HOT2NOR_THRES (0X0048)
+#define SENSOR_HIGHOFF_THRES (0X004C)
+#define SENSOR_LOWOFF_THRES (0X0050)
+#define SENSOR_MON_PERI (0X0058)
+#define SENSOR_MON_CTL (0X005c)
+#define SENSOR_TEMPER0_READ (0x0060)
+#define SENSOR_READ_STATUS (0x0070)
+
+
+#define A_SEN_OVERHEAT_INT_BIT (1 << 5)
+#define A_SEN_HOT_INT_BIT (1 << 4)
+#define A_SEN_HOT2NOR_INT_BIT (1 << 3)
+#define A_SEN_HIGHOFF_BIT (1 << 2)
+#define A_SEN_LOWOFF_INT_BIT (1 << 1)
+
+#define A_RAW_TEMP_OFFSET 8
+#define A_RAW_TEMP_RANGE_MSK 0x7F
+
+#define A_HIGH_BITS_OFFSET 4
+
+#define A_HIGH_TAB_SZ 8
+#define A_LOW_TAB_SZ 16
+
+#define A_HOT2NOR_RANGE 15
+#define A_LOCAL_SENSOR_ADDR_OFF 0x100
+#define A_DELAY_TEMPERATURE 3
+#define INTOFFSET 3
+
+#define A_TSMC_DOLPHINW4T_CHIP_ID_1 0x7715A001
+#define A_TSMC_DOLPHINW4T_CHIP_ID_2 0x7715A003
+#define A_TSMC_DOLPHINWT4T_CHIP_ID_1 0x8815A001
+
+static const short a_temp_search_high_152nm[A_HIGH_TAB_SZ] =
+ { -56, -29, -2, 26, 53, 79, 106, 133 };
+static const short a_temp_search_low_152nm[A_LOW_TAB_SZ] =
+ { 0, 2, 3, 5, 7, 8, 10, 11, 13, 15, 17, 18, 20, 21, 23, 25 };
+
+#define SEN_OVERHEAT_INT_BIT (1 << 5)
+#define SEN_HOT_INT_BIT (1 << 4)
+#define SEN_HOT2NOR_INT_BIT (1 << 3)
+#define SEN_HIGHOFF_BIT (1 << 2)
+#define SEN_LOWOFF_INT_BIT (1 << 1)
+#define SEN_OVERHEAT_ALARM_EN (1 << 7)
+#define SEN0_OVERHEAT_ALARM_EN (1 << 8)
+
+
+#define RAW_TEMP_RANGE_MSK 0x3FFF
+#define RAW_READ_RANGE_MSK 0x7FFF
+
+#define HIGH_BITS_OFFSET 4
+
+#define HOT2NOR_RANGE 15
+#define LOCAL_SENSOR_ADDR_OFF 0x100
+#define DELAY_TEMPERATURE 3
+
+#define LOCAL_THM_ADDR_OFF 0x200
+
+
+#define SEN_DET_PRECISION (0x50)
+
+#define TSMC_DOLPHINW4T_CHIP_ID_1 0x7715A001
+#define TSMC_DOLPHINW4T_CHIP_ID_2 0x7715A003
+#define TSMC_DOLPHINWT4T_CHIP_ID_1 0x8815A001
+
+#define TEMP_TO_RAM_DEGREE 8
+#define TEMP_LOW (-40000)
+#define TEMP_HIGH (120000)
+#ifdef CONFIG_ARCH_SCX20
+#define RAW_DATA_LOW (627)
+#define RAW_DATA_HIGH (1075)
+#else
+#define RAW_DATA_LOW (623)
+#define RAW_DATA_HIGH (1030)
+#endif
+#define CRITIC_TEMP 80000
+
+#define RAW_ADC_LOW (550)
+#define RAW_ADC_HIGH (1200)
+
+//static const u32 temp_to_raw[TEMP_TO_RAM_DEGREE + 1] = { RAW_DATA_LOW, 676, 725, 777, 828, 878, 928, 980,RAW_DATA_HIGH };
+
+unsigned long SPRD_THM_BASE = 0;
+unsigned int SPRD_THM_SIZE = 0;
+extern int sprd_thermal_init(struct sprd_thermal_zone *pzone);
+extern void sprd_thermal_remove(struct sprd_thermal_zone *pzone);
+
+static inline void __thm_reg_write(unsigned long reg, u16 bits, u16 clear_msk);
+static inline u32 __thm_reg_read(unsigned long reg);
+int sprd_thm_set_active_trip(struct sprd_thermal_zone *pzone, int trip );
+u32 sprd_thm_temp2rawdata( int temp);
+
+static inline void __thm_reg_write(unsigned long reg, u16 bits, u16 clear_msk)
+{
+ __raw_writel(((__raw_readl((volatile void *)reg) & ~clear_msk) | bits), ((volatile void *)reg));
+}
+
+static inline u32 __thm_reg_read(unsigned long reg)
+{
+ return __raw_readl((volatile void *)reg);
+}
+
+u32 sprd_thm_temp2rawdata( int temp)
+{
+ u32 raw_result;
+ if ((temp < TEMP_LOW) || (temp > TEMP_HIGH))
+ return 0;
+ raw_result = RAW_DATA_LOW +
+ (temp - TEMP_LOW) * (RAW_DATA_HIGH - RAW_DATA_LOW) / (TEMP_HIGH - TEMP_LOW);
+
+ return raw_result;
+}
+
+int sprd_thm_rawdata2temp( int rawdata)
+{
+ int temp_result;
+ if ((rawdata < RAW_ADC_LOW) || (rawdata > RAW_ADC_HIGH))
+ return 0;
+ temp_result = TEMP_LOW +
+ (rawdata - RAW_DATA_LOW) * (TEMP_HIGH - TEMP_LOW) / (RAW_DATA_HIGH - RAW_DATA_LOW);
+
+ return temp_result;
+}
+
+static unsigned long sprd_thm_temp_read(struct sprd_thermal_zone *pzone)
+{
+ u32 rawdata = 0;
+ int cal_offset = 0;
+ u32 sensor = pzone->sensor_id, local_sen_id = 0;
+ unsigned long local_sensor_addr;
+ unsigned long temp;
+ if(pzone->sensor_id == SPRD_GPU_SENSOR)
+ local_sen_id = 1;
+ local_sensor_addr = pzone->reg_base + local_sen_id * LOCAL_SENSOR_ADDR_OFF;
+ rawdata = __thm_reg_read(local_sensor_addr + SENSOR_TEMPER0_READ);
+ rawdata = rawdata & RAW_READ_RANGE_MSK;
+ cal_offset = pzone->thm_cal;
+ temp = sprd_thm_rawdata2temp( rawdata) -cal_offset;
+ printk("sensor id:%d, rawdata:0x%x, temp:%lu\n", sensor, rawdata,temp);
+ return temp;
+
+}
+
+
+#ifdef THM_TEST
+static int sprd_thm_regs_read(struct sprd_thermal_zone *pzone,unsigned int *regs)
+{
+ unsigned long base_addr = 0;
+ printk(" sprd_thm_regs_read\n");
+ if(pzone->sensor_id == SPRD_ARM_SENSOR){
+ base_addr = (unsigned long) pzone->reg_base;
+ *regs = __thm_reg_read((base_addr + SENSOR_DET_PERI));
+ *(regs + 1) = __thm_reg_read((base_addr + SENSOR_MON_CTL));
+ *(regs + 2) = __thm_reg_read((base_addr + SENSOR_MON_PERI));
+ *(regs + 3) = __thm_reg_read((base_addr + SENSOR_CTRL));
+ }
+ return 0;
+}
+
+static int sprd_thm_regs_set(struct sprd_thermal_zone *pzone,unsigned int*regs)
+{
+ u32 pre_data[4] = {0};
+ unsigned long gpu_sensor_addr,base_addr;
+ gpu_sensor_addr =
+ (unsigned long ) pzone->reg_base +LOCAL_SENSOR_ADDR_OFF;
+ printk("sprd_thm_regs_set\n");
+ base_addr = (unsigned long) pzone->reg_base;
+ pre_data[0] = __thm_reg_read(base_addr + SENSOR_DET_PERI);
+ pre_data[1] = __thm_reg_read(base_addr + SENSOR_MON_CTL);
+ pre_data[2] = __thm_reg_read(base_addr + SENSOR_MON_PERI);
+ pre_data[3] = __thm_reg_read(base_addr + SENSOR_CTRL);
+ __thm_reg_write((base_addr + SENSOR_CTRL), 0x00, 0x01);
+ __thm_reg_write((base_addr+ SENSOR_CTRL), 0x8, 0x08);
+ __thm_reg_write((base_addr + SENSOR_DET_PERI), regs[0], pre_data[0]);
+ __thm_reg_write((base_addr + SENSOR_MON_CTL), regs[1], pre_data[1]);
+ __thm_reg_write((base_addr + SENSOR_MON_PERI), regs[2], pre_data[2] |0x100 );
+ __thm_reg_write((base_addr + SENSOR_CTRL), regs[3], pre_data[3]);
+ __thm_reg_write((base_addr + SENSOR_CTRL), 0x8, 0x8);
+
+ pre_data[0] = __thm_reg_read(gpu_sensor_addr + SENSOR_DET_PERI);
+ pre_data[1] = __thm_reg_read(gpu_sensor_addr + SENSOR_MON_CTL);
+ pre_data[2] = __thm_reg_read(gpu_sensor_addr + SENSOR_MON_PERI);
+ pre_data[3] = __thm_reg_read(gpu_sensor_addr + SENSOR_CTRL);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_CTRL), 0x00, 0x01);
+ __thm_reg_write((gpu_sensor_addr+ SENSOR_CTRL), 0x8, 0x08);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_DET_PERI), regs[0], pre_data[0]);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_MON_CTL), regs[1], pre_data[1]);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_MON_PERI), regs[2], pre_data[2] |0x100 );
+ __thm_reg_write((gpu_sensor_addr + SENSOR_CTRL), regs[3], pre_data[3]);
+ __thm_reg_write((gpu_sensor_addr + SENSOR_CTRL), 0x8, 0x8);
+ return 0;
+}
+
+static int sprd_thm_trip_set(struct sprd_thermal_zone *pzone,int trip)
+{
+ THM_DEBUG("sprd_thm_trip_set trip=%d, temp=%ld,lowoff =%ld\n",
+ trip,pzone->trip_tab->trip_points[trip].temp,pzone->trip_tab->trip_points[trip].lowoff);
+ return 0;
+}
+
+#endif
+
+int sprd_thm_get_trend(struct sprd_thermal_zone *pzone, int trip, enum thermal_trend *ptrend)
+{
+ *ptrend = pzone->trend_val;
+ return 0;
+}
+
+int sprd_thm_get_hyst(struct sprd_thermal_zone *pzone, int trip, unsigned long *physt)
+{
+
+ struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+ if (trip >= trip_tab->num_trips - 2){
+ *physt = 0;
+ }else{
+ *physt = trip_tab->trip_points[trip].temp - trip_tab->trip_points[trip + 1].lowoff;
+ }
+ return 0;
+}
+
+int init_flag=0;
+
+static int sprd_thm_hw_init(struct sprd_thermal_zone *pzone)
+{
+ unsigned long local_sensor_addr, base_addr = 0;
+ u32 local_sen_id = 0;
+ u32 raw_temp = 0;
+ int ret = 0;
+ int i;
+ struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+ ret = sci_efuse_thermal_cal_get(&pzone->thm_cal) ;
+ THM_DEBUG("arm_sen_cal_offset =%d,ret =%d\n",pzone->thm_cal,ret);
+ if(pzone->sensor_id == SPRD_GPU_SENSOR)
+ local_sen_id = 1;
+ base_addr = (unsigned long) pzone->reg_base ;
+ local_sensor_addr = base_addr + local_sen_id * LOCAL_SENSOR_ADDR_OFF;
+
+ printk(KERN_NOTICE "sprd_thm_hw_init 2713s_thm id:%d,base 0x%lx \n",
+ pzone->sensor_id, base_addr);
+ if(init_flag ==0)
+ {
+ sci_glb_set(REG_AON_APB_APB_EB1, BIT_THM_EB);
+ sci_glb_set(REG_AON_APB_APB_RTC_EB,
+ (BIT_THM_RTC_EB | BIT_GPU_THMA_RTC_EB |
+ BIT_GPU_THMA_RTC_AUTO_EN | BIT_ARM_THMA_RTC_EB |BIT_ARM_THMA_RTC_AUTO_EN ) );
+ sci_glb_set(REG_AON_APB_APB_RST1,BIT_THM_SOFT_RST);
+ for(i=0; i < 10000; i++);
+ sci_glb_clr(REG_AON_APB_APB_RST1,BIT_THM_SOFT_RST);
+ __thm_reg_write((base_addr + THM_CTRL), 0x3, 0);
+ __thm_reg_write((base_addr + THM_INT_CTRL), 0x1, 0);
+ init_flag =1;
+ }
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), 0, ~0); //disable all int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), ~0, 0); //clr all int
+#if 0
+ if (SPRD_ARM_SENSOR== pzone->sensor_id){
+ if (trip_tab->trip_points[trip_tab->num_trips - 1].type ==
+ THERMAL_TRIP_CRITICAL) {
+ raw_temp =
+ sprd_thm_temp2rawdata( trip_tab->trip_points[trip_tab->num_trips - 1].temp - cal_offset);
+ }
+// raw_temp = sprd_thm_temp2rawdata(CRITIC_TEMP - cal_offset);
+ __thm_reg_write((local_sensor_addr + SENSOR_OVERHEAT_THRES),raw_temp, RAW_TEMP_RANGE_MSK);//set overheat temp value
+ __thm_reg_write((base_addr + THM_INT_CTRL), SEN0_OVERHEAT_ALARM_EN, 0);
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL),SEN_OVERHEAT_ALARM_EN, 0);
+ }
+#endif
+ printk(KERN_NOTICE "sprd_thm_hw_init addr 0x:%lx,int ctrl 0x%x\n",
+ local_sensor_addr,
+ __thm_reg_read((local_sensor_addr + SENSOR_INT_CTRL)));
+
+ __thm_reg_write((local_sensor_addr + SENSOR_DET_PERI), 0x4000, 0x4000);
+ __thm_reg_write((local_sensor_addr + SENSOR_MON_CTL), 0x21, 0X21);
+ __thm_reg_write((local_sensor_addr + SENSOR_MON_PERI), 0x400, 0x100);
+ if (SPRD_ARM_SENSOR== pzone->sensor_id)
+ __thm_reg_write((base_addr + SENSOR_CTRL + LOCAL_SENSOR_ADDR_OFF), 0x030, 0x030);
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x39, 0x030);
+ return 0;
+}
+
+int sprd_thm_hw_disable_sensor(struct sprd_thermal_zone *pzone)
+{
+ int ret = 0;
+ /* Sensor minitor disable */
+ if (SPRD_ARM_SENSOR == pzone->sensor_id) {
+ /*__thm_reg_write((u32)(pzone->reg_base + SENSOR_CTRL), 0x00, 0x01);*/
+ __thm_reg_write((pzone->reg_base + SENSOR_CTRL), 0x8, 0x01);
+ } else if (SPRD_GPU_SENSOR == pzone->sensor_id) {
+ /*__thm_reg_write((u32)(pzone->reg_base + SENSOR_CTRL+ LOCAL_SENSOR_ADDR_OFF), 0x00, 0x01);*/
+ __thm_reg_write((pzone->reg_base + SENSOR_CTRL + LOCAL_SENSOR_ADDR_OFF), 0x8, 0x01);
+ } else {
+ THM_DEBUG("the sensor id is error \n");
+ }
+ return ret;
+}
+
+int sprd_thm_hw_enable_sensor(struct sprd_thermal_zone *pzone)
+{
+ int ret = 0;
+ // Sensor minitor enable
+ THM_DEBUG("sprd_2713S_thm enable sensor sensor_ID:0x%x \n",pzone->sensor_id);
+ if (SPRD_ARM_SENSOR == pzone->sensor_id) {
+ sci_glb_set(REG_AON_APB_APB_RTC_EB,
+ (BIT_THM_RTC_EB | BIT_GPU_THMA_RTC_EB |
+ BIT_GPU_THMA_RTC_AUTO_EN | BIT_ARM_THMA_RTC_EB | BIT_ARM_THMA_RTC_AUTO_EN));
+ __thm_reg_write((pzone->reg_base + SENSOR_CTRL + LOCAL_SENSOR_ADDR_OFF), 0x030, 0x0);
+ __thm_reg_write((pzone->reg_base + SENSOR_CTRL), 0x31, 0x0);
+ } else if (SPRD_GPU_SENSOR == pzone->sensor_id) {
+ /*sci_glb_set(REG_AON_APB_APB_RTC_EB,
+ (BIT_THM_RTC_EB | BIT_GPU_THMA_RTC_EB |
+ BIT_GPU_THMA_RTC_AUTO_EN | BIT_ARM_THMA_RTC_EB |BIT_ARM_THMA_RTC_AUTO_EN ) );*/
+ __thm_reg_write((pzone->reg_base + SENSOR_CTRL+LOCAL_SENSOR_ADDR_OFF), 0x31, 0x0);
+ } else {
+ THM_DEBUG("the sensor id is error \n");
+ }
+
+ return ret;
+}
+
+
+u16 int_ctrl_reg[SPRD_MAX_SENSOR];
+int sprd_thm_hw_suspend(struct sprd_thermal_zone *pzone)
+{
+ unsigned long local_sensor_addr;
+ int ret = 0;
+
+ local_sensor_addr = pzone->reg_base ;
+ /*int_ctrl_reg[pzone->sensor_id] = __thm_reg_read((local_sensor_addr + SENSOR_INT_CTRL));*/
+
+ sprd_thm_hw_disable_sensor(pzone);
+ if (SPRD_ARM_SENSOR == pzone->sensor_id) {
+ int_ctrl_reg[pzone->sensor_id] = __thm_reg_read((local_sensor_addr + SENSOR_INT_CTRL));
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), 0, ~0); //disable all int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), ~0, 0); //clr all int
+
+ } else if (SPRD_GPU_SENSOR == pzone->sensor_id) {
+ int_ctrl_reg[pzone->sensor_id] =
+ __thm_reg_read((local_sensor_addr + SENSOR_INT_CTRL+LOCAL_SENSOR_ADDR_OFF));
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL+LOCAL_SENSOR_ADDR_OFF), 0, ~0); /*disable all int*/
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL+LOCAL_SENSOR_ADDR_OFF), ~0, 0); /*clr all int*/
+
+ } else {
+ THM_DEBUG("the sensor id is error \n");
+ }
+ return ret;
+}
+int sprd_thm_hw_resume(struct sprd_thermal_zone *pzone)
+{
+ unsigned long local_sensor_addr;
+ int ret = 0;
+
+ local_sensor_addr = pzone->reg_base ;
+
+ sprd_thm_hw_enable_sensor(pzone);
+ if (SPRD_ARM_SENSOR == pzone->sensor_id) {
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR), ~0, 0); //clr all int
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL), int_ctrl_reg[pzone->sensor_id], ~0); //enable int of saved
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL+LOCAL_SENSOR_ADDR_OFF), 0x30, 0);
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL), 0x39, 0);
+ } else if (SPRD_GPU_SENSOR == pzone->sensor_id) {
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CLR+LOCAL_SENSOR_ADDR_OFF), ~0, 0); /*clr all int*/
+ __thm_reg_write((local_sensor_addr + SENSOR_INT_CTRL+LOCAL_SENSOR_ADDR_OFF), int_ctrl_reg[pzone->sensor_id], ~0); /*enable int of saved*/
+ __thm_reg_write((local_sensor_addr + SENSOR_CTRL+LOCAL_SENSOR_ADDR_OFF), 0x39, 0);
+ } else {
+ THM_DEBUG("the sensor id is error \n");
+ }
+ return ret;
+}
+
+struct thm_handle_ops sprd_ddie_ops =
+{
+ .hw_init = sprd_thm_hw_init,
+ .read_temp = sprd_thm_temp_read,
+ .get_trend = sprd_thm_get_trend,
+ .get_hyst = sprd_thm_get_hyst,
+ .suspend = sprd_thm_hw_suspend,
+ .resume = sprd_thm_hw_resume,
+ .trip_debug_set = sprd_thm_trip_set,
+ .reg_debug_get = sprd_thm_regs_read,
+ .reg_debug_set = sprd_thm_regs_set,
+};
+
+#ifdef CONFIG_OF
+static struct sprd_thm *thermal_detect_parse_dt(
+ struct device *dev)
+{
+ struct sprd_thm_platform_data *pdata;
+ struct sprd_thm *thm_data;
+ struct device_node *np = dev->of_node,*sensor_child = NULL;
+ int nsensor,sensor_id;
+ int ret;
+ u32 trip_points_critical,trip_num;
+ int i = 0,j = 0,temp_interval;
+ char prop_name[32];
+ const char *tmp_str;
+ const char *thm_name;
+ u32 tmp_data,tmp_lowoff;
+ nsensor = of_get_child_count(np);
+ if (nsensor == 0) {
+ dev_err(dev, "fail to get child node \n");
+ goto fail;
+ }
+ printk("the nubutton %d\n",nsensor);
+ thm_data = kzalloc(sizeof(*thm_data), GFP_KERNEL);
+ if (!thm_data) {
+ dev_err(dev, "could not allocate memory for platform sprd_thm\n");
+ return NULL;
+ }
+ thm_data->nsensor = nsensor;
+ pdata = kzalloc(thm_data->nsensor*sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(dev, "could not allocate memory for platform data\n");
+ goto fail;
+ }
+ thm_data->sprd_data = pdata;
+ for_each_child_of_node(np,sensor_child) {
+ ret = of_property_read_u32(sensor_child, "trip-points-critical", &trip_points_critical);
+ if(ret){
+ dev_err(dev, "fail to get trip_points_critical\n");
+ goto fail;
+ }
+ ret = of_property_read_u32(sensor_child, "trip-num", &trip_num);
+ if(ret){
+ dev_err(dev, "fail to get trip_num\n");
+ goto fail;
+ }
+ ret = of_property_read_u32(sensor_child, "id", &sensor_id);
+ if (ret) {
+ dev_err(dev, "fail to get id\n");
+ goto fail;
+ }
+ ret = of_property_read_u32(sensor_child, "temp-inteval", &temp_interval);
+ if(ret){
+ dev_err(dev, "fail to get temp-inteval\n");
+ goto fail;
+ }
+ of_property_read_string(sensor_child, "thermal-name",
+ &thm_name);
+ strcpy(pdata->thm_name, thm_name);
+ for (i = 0; i <trip_num-1; ++i) {
+ sprintf(prop_name, "trip%d-temp-active", i);
+ if (of_property_read_u32(sensor_child, prop_name, &tmp_data)){
+ dev_err(dev, "fail to get trip%d-temp-active\n",i);
+ goto fail;
+ }
+ pdata->trip_points[i].temp = tmp_data;
+ sprintf(prop_name, "trip%d-temp-lowoff", i);
+ if (of_property_read_u32(sensor_child, prop_name, &tmp_lowoff)){
+ dev_err(dev, "fail to get trip%d-temp-lowoff\n",i);
+ goto fail;
+ }
+ pdata->trip_points[i].lowoff = tmp_lowoff;
+ sprintf(prop_name, "trip%d-type", i);
+ if (of_property_read_string(sensor_child, prop_name, &tmp_str))
+ goto fail;
+
+ if (!strcmp(tmp_str, "active"))
+ pdata->trip_points[i].type = THERMAL_TRIP_ACTIVE;
+ else if (!strcmp(tmp_str, "passive"))
+ pdata->trip_points[i].type = THERMAL_TRIP_PASSIVE;
+ else if (!strcmp(tmp_str, "hot"))
+ pdata->trip_points[i].type = THERMAL_TRIP_HOT;
+ else if (!strcmp(tmp_str, "critical"))
+ pdata->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ else
+ goto fail;
+
+ sprintf(prop_name, "trip%d-cdev-num", i);
+ if (of_property_read_u32(sensor_child, prop_name, &tmp_data))
+ goto fail;
+ for (j = 0; j < tmp_data; j++) {
+ sprintf(prop_name, "trip%d-cdev-name%d", i, j);
+ if (of_property_read_string(sensor_child, prop_name, &tmp_str))
+ goto fail;
+ strcpy(pdata->trip_points[i].cdev_name[j], tmp_str);
+ dev_info(dev,"cdev name: %s \n", pdata->trip_points[i].cdev_name[j]);
+ }
+ dev_info(dev, "trip[%d] temp: %lu lowoff: %lu\n",
+ i, pdata->trip_points[i].temp, pdata->trip_points[i].lowoff);
+ }
+ pdata->trip_points[i].temp = trip_points_critical;
+ pdata->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ dev_info(dev, "trip[%d] temp: %lu \n",
+ i, pdata->trip_points[i].temp);
+ pdata->num_trips = trip_num;
+ pdata->sensor_id = sensor_id;
+ pdata->temp_interval = temp_interval;
+ printk("the trip_num=%d,sensor_id =%d\n",pdata->num_trips,pdata->sensor_id);
+ pdata++;
+ };
+ return thm_data;
+
+fail:
+ kfree(pdata);
+ kfree(thm_data);
+ return NULL;
+
+}
+#endif
+
+static int sprd_ddie_thm_probe(struct platform_device *pdev)
+{
+ struct sprd_thm_platform_data *ptrips = NULL;
+ struct sprd_thermal_zone *pzone = NULL;
+ struct resource *res;
+ struct sprd_thm *pthm = NULL;
+ unsigned long ddie_thm_base;
+ int ret,i;
+#ifdef CONFIG_OF
+ struct device_node *np = pdev->dev.of_node;
+ if (!np) {
+ dev_err(&pdev->dev, "device node not found\n");
+ return -EINVAL;
+ }
+#endif
+ printk("sprd_ddie_thm_probe start\n");
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ ddie_thm_base = (unsigned long)devm_ioremap_resource(&pdev->dev, res);
+ if (!ddie_thm_base) {
+ pr_err("thermal ioremap failed!\n");
+ return -ENOMEM;
+ }
+#ifdef CONFIG_OF
+ pthm = thermal_detect_parse_dt(&pdev->dev);
+#else
+ pthm = dev_get_platdata(&pdev->dev);
+#endif
+ if (!pthm){
+ dev_err(&pdev->dev, "not found pthm\n");
+ return -EINVAL;
+ }
+ for (i = 0; i < pthm->nsensor; i++) {
+ pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
+ mutex_init(&pzone->th_lock);
+ mutex_lock(&pzone->th_lock);
+ if (!pzone)
+ return -ENOMEM;
+ pzone->reg_base= ddie_thm_base;
+ ptrips = &pthm->sprd_data[i];
+ pzone->trip_tab = ptrips;
+ pzone->temp_inteval = ptrips->temp_interval;
+ pzone->sensor_id = ptrips->sensor_id;
+ memcpy(pzone->thermal_zone_name,
+ ptrips->thm_name, strlen(ptrips->thm_name)+1);
+ printk("the thermal-name1 is %s\n",pzone->thermal_zone_name);
+ pzone->ops = &sprd_ddie_ops;
+ ret = sprd_thm_hw_init(pzone);
+ if(ret){
+ dev_err(&pdev->dev, " pzone hw init error id =%d\n",pzone->sensor_id);
+ return -ENODEV;
+ }
+ ret = sprd_thermal_init(pzone);
+ if(ret){
+ dev_err(&pdev->dev, " pzone sw init error id =%d\n",pzone->sensor_id);
+ return -ENODEV;
+ }
+ platform_set_drvdata(pdev, pzone);
+ printk("sprd_ddie_thm_probe---end\n");
+ mutex_unlock(&pzone->th_lock);
+ pzone++;
+ }
+ return 0;
+}
+
+static int sprd_ddie_thm_remove(struct platform_device *pdev)
+{
+
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ sprd_thermal_remove(pzone);
+ return 0;
+}
+
+static int sprd_ddie_thm_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ flush_delayed_work(&pzone->thm_read_work);
+ //flush_delayed_work(&pzone->thm_logtime_work);
+ flush_delayed_work(&pzone->resume_delay_work);
+ pzone->ops->suspend(pzone);
+ return 0;
+}
+
+static int sprd_ddie_thm_resume(struct platform_device *pdev)
+{
+
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ schedule_delayed_work(&pzone->resume_delay_work, (HZ * 1));
+ schedule_delayed_work(&pzone->thm_read_work, (HZ * 5));
+ //schedule_delayed_work(&pzone->thm_logtime_work, (HZ * 7));
+ //pzone->ops->resume(pzone);
+ return 0;
+}
+
+static const struct of_device_id thermal_of_match[] = {
+ { .compatible = "sprd,ddie-thermal", },
+ {}
+};
+
+static struct platform_driver sprd_thermal_driver = {
+ .probe = sprd_ddie_thm_probe,
+ .suspend = sprd_ddie_thm_suspend,
+ .resume = sprd_ddie_thm_resume,
+ .remove = sprd_ddie_thm_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ddie-thermal",
+ .of_match_table = of_match_ptr(thermal_of_match),
+ },
+};
+static int __init sprd_ddie_thermal_init(void)
+{
+ return platform_driver_register(&sprd_thermal_driver);
+}
+
+static void __exit sprd_ddie_thermal_exit(void)
+{
+ platform_driver_unregister(&sprd_thermal_driver);
+}
+
+
+device_initcall_sync(sprd_ddie_thermal_init);
+module_exit(sprd_ddie_thermal_exit);
+MODULE_AUTHOR("Freeman Liu <freeman.liu@spreadtrum.com>");
+MODULE_DESCRIPTION("sprd thermal driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/thermal/sprd_pmic_2723s_thm.c b/drivers/thermal/sprd_pmic_2723s_thm.c
new file mode 100644
index 00000000..4d9d65c8
--- /dev/null
+++ b/drivers/thermal/sprd_pmic_2723s_thm.c
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <linux/io.h>
+#include <soc/sprd/adi.h>
+#include "thm.h"
+#include <linux/sprd_thm.h>
+#include <soc/sprd/arch_misc.h>
+#include <soc/sprd/hardware.h>
+#include "thermal_core.h"
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/of_device.h>
+#include <linux/io.h>
+#include <linux/sprd_thm.h>
+
+#define SPRD_THM_PMIC_DEBUG
+#ifdef SPRD_THM_PMIC_DEBUG
+#define THM_PMIC_DEBUG(format, arg...) printk("sprd PMIC thm: " "@@@" format, ## arg)
+#else
+#define THM_PMIC_DEBUG(format, arg...)
+#endif
+
+
+#define A_THM_CTRL (0x0000)
+#define A_THM_INT_CTRL (0x0004)
+#define A_SENSOR_CTRL (0x0020)
+#define A_SENSOR_DET_PERI (0x0024)
+#define A_SENSOR_INT_CTRL (0x0028)
+#define A_SENSOR_INT_STS (0x002C)
+#define A_SENSOR_INT_RAW_STS (0x0030)
+#define A_SENSOR_INT_CLR (0x0034)
+#define A_SENSOR_OVERHEAT_HOT_THRES (0X0040)
+#define A_SENSOR_HOT2NOR__HIGHOFF_THRES (0X0044)
+#define A_SENSOR_LOWOFF__COLD_THRES (0X0048)
+#define A_SENSOR_TEMPER0_READ (0x0058)
+
+#define A_SEN_OVERHEAT_INT_BIT (1 << 5)
+#define A_SEN_HOT_INT_BIT (1 << 4)
+#define A_SEN_HOT2NOR_INT_BIT (1 << 3)
+#define A_SEN_HIGHOFF_BIT (1 << 2)
+#define A_SEN_LOWOFF_INT_BIT (1 << 1)
+
+#define A_RAW_TEMP_OFFSET 8
+#define A_RAW_TEMP_RANGE_MSK 0x7F
+
+#define A_HIGH_BITS_OFFSET 4
+
+#define A_HIGH_TAB_SZ 8
+#define A_LOW_TAB_SZ 16
+
+#define A_HOT2NOR_RANGE 15
+#define A_LOCAL_SENSOR_ADDR_OFF 0x100
+#define A_DELAY_TEMPERATURE 3
+
+#define A_TSMC_DOLPHINW4T_CHIP_ID_1 0x7715A001
+#define A_TSMC_DOLPHINW4T_CHIP_ID_2 0x7715A003
+#define A_TSMC_DOLPHINWT4T_CHIP_ID_1 0x8815A001
+
+static const short a_temp_search_high_152nm[A_HIGH_TAB_SZ] =
+ { -56, -29, -2, 26, 53, 79, 106, 133 };
+static const short a_temp_search_low_152nm[A_LOW_TAB_SZ] =
+ { 0, 2, 3, 5, 7, 8, 10, 11, 13, 15, 17, 18, 20, 21, 23, 25 };
+
+#define SEN_OVERHEAT_INT_BIT (1 << 5)
+#define SEN_HOT_INT_BIT (1 << 4)
+#define SEN_HOT2NOR_INT_BIT (1 << 3)
+#define SEN_HIGHOFF_BIT (1 << 2)
+#define SEN_LOWOFF_INT_BIT (1 << 1)
+
+#define RAW_TEMP_RANGE_MSK 0x3FFF
+#define RAW_READ_RANGE_MSK 0x7FFF
+
+#define HIGH_BITS_OFFSET 4
+
+#define HOT2NOR_RANGE 15
+#define LOCAL_SENSOR_ADDR_OFF 0x100
+#define DELAY_TEMPERATURE 3
+
+#define SEN_DET_PRECISION (0x50)
+
+#define TSMC_DOLPHINW4T_CHIP_ID_1 0x7715A001
+#define TSMC_DOLPHINW4T_CHIP_ID_2 0x7715A003
+#define TSMC_DOLPHINWT4T_CHIP_ID_1 0x8815A001
+
+#define TEMP_TO_RAM_DEGREE 8
+#define TEMP_LOW (-40)
+#define TEMP_HIGH (120)
+#define RAW_DATA_LOW (623)
+#define RAW_DATA_HIGH (1030)
+#define THM_SENOR_NUM 8
+
+//static const u32 temp_to_raw[TEMP_TO_RAM_DEGREE + 1] = { RAW_DATA_LOW, 676, 725, 777, 828, 878, 928, 980,RAW_DATA_HIGH };
+
+static int pmic_sen_cal_offset = 0;
+
+unsigned long SPRD_ADIE_THM_BASE = 0;
+unsigned int SPRD_ADIE_THM_SIZE = 0;
+
+extern int sprd_thermal_init(struct sprd_thermal_zone *pzone);
+extern void sprd_thermal_remove(struct sprd_thermal_zone *pzone);
+
+static inline int __thm_pmic_reg_write(unsigned long reg, u16 bits, u16 clear_msk);
+static inline u32 __thm_pmic_reg_read(unsigned long reg);
+
+static inline int __thm_pmic_reg_write(unsigned long reg, u16 bits, u16 clear_msk)
+{
+ sci_adi_write(reg, bits, clear_msk);
+ return 0;
+}
+
+static inline u32 __thm_pmic_reg_read(unsigned long reg)
+{
+ return sci_adi_read(reg);
+}
+static u32 sprd_thm_temp2rawdata_a(u32 sensor, int temp)
+{
+ u32 high_bits, low_bits;
+ int i;
+ const short *high_tab;
+ const short *low_tab;
+ high_tab = a_temp_search_high_152nm;
+ low_tab = a_temp_search_low_152nm;
+
+ if (temp < high_tab[0]) {
+ printk(KERN_ERR "temp over low temp:%d \n", temp);
+ return 0;
+ }
+
+ for (i = A_HIGH_TAB_SZ - 1; i >= 0; i--) {
+ if (high_tab[i] <= temp)
+ break;
+ }
+ if (i < 0) {
+ i = 0;
+ }
+ temp -= high_tab[i];
+ high_bits = i;
+
+ for (i = A_LOW_TAB_SZ - 1; i >= 0; i--) {
+ if (low_tab[i] <= temp)
+ break;
+ }
+ if (i < 0) {
+ i = 0;
+ }
+ low_bits = i;
+ return ((high_bits << HIGH_BITS_OFFSET) | low_bits);
+
+}
+static int sprd_thm_rawdata2temp_a(u32 sensor, int rawdata)
+{
+ const short *high_tab;
+ const short *low_tab;
+ high_tab = a_temp_search_high_152nm;
+ low_tab = a_temp_search_low_152nm;
+
+ return high_tab[(rawdata >> HIGH_BITS_OFFSET) & 0x07] +
+ low_tab[rawdata & 0x0F];
+}
+
+static unsigned long sprd_pmic_thm_temp_read(struct sprd_thermal_zone *pzone)
+{
+ u32 rawdata = 0;
+ int cal_offset = 0;
+ u32 sensor = pzone->sensor_id;
+
+ rawdata = __thm_pmic_reg_read(pzone->reg_base + A_SENSOR_TEMPER0_READ);
+ cal_offset = pmic_sen_cal_offset;
+ THM_PMIC_DEBUG("A thm sensor id:%d, cal_offset:%d, rawdata:0x%x\n", sensor,
+ cal_offset, rawdata);
+ return (sprd_thm_rawdata2temp_a(sensor, rawdata)*1000 + cal_offset*1000);
+}
+
+static int sprd_pmic_thm_hw_init(struct sprd_thermal_zone *pzone)
+{
+ unsigned long local_sensor_addr, base_addr = 0;
+ u32 local_sen_id = 0;
+ int cal_offset = 0;
+ int i;
+ //struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+ base_addr = pzone->reg_base;
+ local_sensor_addr = pzone->reg_base;
+
+ THM_PMIC_DEBUG( "sprd_adie_thm_hw_init 2713s_thm id:%d,base 0x%lx \n",
+ pzone->sensor_id, base_addr);
+ // adie thm en
+ sci_adi_set(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_THM_EN);
+ sci_adi_set(ANA_REG_GLB_RTC_CLK_EN,
+ BIT_RTC_THMA_AUTO_EN | BIT_RTC_THMA_EN |
+ BIT_RTC_THM_EN);
+
+ // Reset Thermal
+ sci_adi_set(ANA_REG_GLB_ARM_RST,
+ BIT_ANA_THM_SOFT_RST | BIT_ANA_THMA_SOFT_RST);
+ for(i=0; i < 10000; i++);
+ sci_adi_clr(ANA_REG_GLB_ARM_RST,
+ BIT_ANA_THM_SOFT_RST | BIT_ANA_THMA_SOFT_RST);
+
+ cal_offset = pmic_sen_cal_offset = 0;
+ local_sen_id = 0;
+
+ __thm_pmic_reg_write((base_addr + A_THM_CTRL), 0x1 >> local_sen_id, 0);
+ __thm_pmic_reg_write((base_addr + A_THM_INT_CTRL), 0x3, 0); //enable top int
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_INT_CTRL), 0, ~0); //disable all int
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_INT_CLR), ~0, 0); //clr all int
+ THM_PMIC_DEBUG( "sprd_thm_hw_init addr 0x:%lx,int ctrl 0x%x\n",
+ local_sensor_addr,
+ __thm_pmic_reg_read((local_sensor_addr + A_SENSOR_INT_CTRL)));
+
+ // Set sensor det period is 0.25S
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_DET_PERI), 0x2000, 0x2000);
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_CTRL), 0x01, 0x01);
+ // Start the sensor by set Sen_set_rdy(bit3)
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_CTRL), 0x8, 0x8);
+
+ return 0;
+}
+
+static int sprd_pmic_thm_hw_disable_sensor(struct sprd_thermal_zone *pzone)
+{
+ int ret = 0;
+ THM_PMIC_DEBUG("sprd_2713S_thm disable PMIC sensor \n" );
+ __thm_pmic_reg_write((unsigned long )(pzone->reg_base + A_SENSOR_CTRL), 0x0, 0x8);
+ __thm_pmic_reg_write((unsigned long )(pzone->reg_base + A_SENSOR_CTRL), 0x00, 0x01);
+
+ return ret;
+}
+
+static int sprd_pmic_thm_hw_enable_sensor(struct sprd_thermal_zone *pzone)
+{
+ int ret = 0;
+ // Sensor minitor enable
+ THM_PMIC_DEBUG("sprd_2713S_thm enable PMIC sensor \n");
+ sci_adi_set(ANA_REG_GLB_RTC_CLK_EN,
+ BIT_RTC_THMA_AUTO_EN | BIT_RTC_THMA_EN |
+ BIT_RTC_THM_EN);
+ __thm_pmic_reg_write((unsigned long )(pzone->reg_base + A_SENSOR_CTRL), 0x01, 0x01);
+ __thm_pmic_reg_write((unsigned long )(pzone->reg_base + A_SENSOR_CTRL), 0x8, 0x8);
+
+
+ return ret;
+}
+
+
+u16 int_pmic_ctrl_reg[SPRD_MAX_SENSOR];
+static int sprd_pmic_thm_hw_suspend(struct sprd_thermal_zone *pzone)
+{
+ u32 local_sen_id = 0;
+ unsigned long local_sensor_addr;
+ int ret = 0;
+
+ local_sensor_addr =
+ (unsigned long ) pzone->reg_base + local_sen_id * LOCAL_SENSOR_ADDR_OFF;
+ int_pmic_ctrl_reg[pzone->sensor_id] = __thm_pmic_reg_read((local_sensor_addr + A_SENSOR_INT_CTRL));
+
+ sprd_pmic_thm_hw_disable_sensor(pzone);
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_INT_CTRL), 0, ~0); //disable all int
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_INT_CLR), ~0, 0); //clr all int
+ return ret;
+}
+static int sprd_pmic_thm_hw_resume(struct sprd_thermal_zone *pzone)
+{
+ u32 local_sen_id = 0;
+ unsigned long local_sensor_addr;
+ int ret = 0;
+
+ local_sensor_addr =
+ (unsigned long ) pzone->reg_base + local_sen_id * LOCAL_SENSOR_ADDR_OFF;
+
+ sprd_pmic_thm_hw_enable_sensor(pzone);
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_INT_CLR), ~0, 0); //clr all int
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_INT_CTRL), int_pmic_ctrl_reg[pzone->sensor_id], ~0); //enable int of saved
+ __thm_pmic_reg_write((local_sensor_addr + A_SENSOR_CTRL), 0x9, 0);
+
+ return ret;
+}
+
+static int sprd_pmic_thm_get_trend(struct sprd_thermal_zone *pzone, int trip, enum thermal_trend *ptrend)
+{
+ *ptrend = pzone->trend_val;
+ return 0;
+}
+
+static int sprd_pmic_thm_get_hyst(struct sprd_thermal_zone *pzone, int trip, unsigned long *physt)
+{
+ struct sprd_thm_platform_data *trip_tab = pzone->trip_tab;
+ if (trip >= trip_tab->num_trips - 2){
+ *physt = 0;
+ }else{
+ *physt = trip_tab->trip_points[trip].temp - trip_tab->trip_points[trip + 1].lowoff;
+ }
+ return 0;
+}
+
+struct thm_handle_ops sprd_pmic_ops =
+{
+ .hw_init = sprd_pmic_thm_hw_init,
+ .read_temp = sprd_pmic_thm_temp_read,
+ .get_trend = sprd_pmic_thm_get_trend,
+ .get_hyst = sprd_pmic_thm_get_hyst,
+ .suspend = sprd_pmic_thm_hw_suspend,
+ .resume = sprd_pmic_thm_hw_resume,
+};
+
+static struct sprd_thm_platform_data *thermal_detect_parse_dt(
+ struct device *dev)
+{
+ struct sprd_thm_platform_data *pdata;
+ struct device_node *np = dev->of_node;
+ u32 trip_points_critical,trip_num;
+ char prop_name[32];
+ const char *tmp_str;
+ u32 tmp_data,tmp_lowoff;
+ int ret,i ,j = 0;
+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(dev, "could not allocate memory for platform data\n");
+ return NULL;
+ }
+ ret = of_property_read_u32(np, "trip-points-critical", &trip_points_critical);
+ if(ret){
+ dev_err(dev, "fail to get trip_points_critical\n");
+ goto fail;
+ }
+ ret = of_property_read_u32(np, "trip-num", &trip_num);
+ if(ret){
+ dev_err(dev, "fail to get trip_num\n");
+ goto fail;
+ }
+ for (i = 0; i <trip_num-1; ++i) {
+ sprintf(prop_name, "trip%d-temp-active", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data)){
+ dev_err(dev, "fail to get trip%d-temp-active\n",i);
+ goto fail;
+ }
+
+ pdata->trip_points[i].temp = tmp_data;
+ sprintf(prop_name, "trip%d-temp-lowoff", i);
+ if (of_property_read_u32(np, prop_name, &tmp_lowoff)){
+ dev_err(dev, "fail to get trip%d-temp-lowoff\n",i);
+ goto fail;
+ }
+ pdata->trip_points[i].lowoff = tmp_lowoff;
+ sprintf(prop_name, "trip%d-type", i);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto fail;
+
+ if (!strcmp(tmp_str, "active"))
+ pdata->trip_points[i].type = THERMAL_TRIP_ACTIVE;
+ else if (!strcmp(tmp_str, "passive"))
+ pdata->trip_points[i].type = THERMAL_TRIP_PASSIVE;
+ else if (!strcmp(tmp_str, "hot"))
+ pdata->trip_points[i].type = THERMAL_TRIP_HOT;
+ else if (!strcmp(tmp_str, "critical"))
+ pdata->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ else
+ goto fail;
+
+ sprintf(prop_name, "trip%d-cdev-num", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data))
+ goto fail;
+ for (j = 0; j < tmp_data; j++) {
+ sprintf(prop_name, "trip%d-cdev-name%d", i, j);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto fail;
+ strcpy(pdata->trip_points[i].cdev_name[j], tmp_str);
+ dev_info(dev,"cdev name: %s \n", pdata->trip_points[i].cdev_name[j]);
+ }
+ dev_info(dev, "trip[%d] temp: %lu lowoff: %lu\n",
+ i, pdata->trip_points[i].temp, pdata->trip_points[i].lowoff);
+ }
+ pdata->trip_points[i].temp = trip_points_critical;
+ pdata->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ dev_info(dev, "trip[%d] temp: %lu \n",
+ i, pdata->trip_points[i].temp);
+ pdata->num_trips = trip_num;
+
+ return pdata;
+
+fail:
+ kfree(pdata);
+ return NULL;
+
+}
+
+
+static int sprd_adi_thm_probe(struct platform_device *pdev)
+{
+ struct sprd_thermal_zone *pzone = NULL;
+ struct sprd_thm_platform_data *ptrips = NULL;
+ struct resource *res;
+ const char *thm_name;
+ int ret,temp_interval,sensor_id;
+ unsigned long adie_thm_base;
+ struct device_node *np = pdev->dev.of_node;
+ if (!np) {
+ dev_err(&pdev->dev, "device node not found\n");
+ return -EINVAL;
+ }
+ printk("sprd_adi_thm_probe start\n");
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ adie_thm_base = (unsigned long)devm_ioremap_resource(&pdev->dev, res);
+ if (!adie_thm_base) {
+ pr_err("thermal ioremap failed!\n");
+ return -ENOMEM;
+ }
+#if 0
+ pdev->id = of_alias_get_id(np, "thmzone");
+ printk(KERN_INFO " sprd_thermal_probe id:%d\n", pdev->id);
+ if (unlikely(pdev->id < 0 || pdev->id >= THM_SENOR_NUM)) {
+ dev_err(&pdev->dev, "does not support id %d\n", pdev->id);
+ return -ENXIO;
+ }
+#endif
+ ret = of_property_read_u32(np, "temp-inteval", &temp_interval);
+ if(ret){
+ dev_err(&pdev->dev, "fail to get temp-inteval\n");
+ return -EINVAL;
+ }
+ ret = of_property_read_u32(np, "id", &sensor_id);
+ if (ret) {
+ dev_err(&pdev->dev, "fail to get id\n");
+ return -EINVAL;
+ }
+ ptrips = thermal_detect_parse_dt(&pdev->dev);
+ if (!ptrips){
+ dev_err(&pdev->dev, "not found ptrips\n");
+ return -EINVAL;
+ }
+ pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
+ mutex_init(&pzone->th_lock);
+ mutex_lock(&pzone->th_lock);
+ if (!pzone)
+ return -ENOMEM;
+ of_property_read_string(np, "thermal-name",&thm_name);
+ strcpy(pzone->thermal_zone_name, thm_name);
+ printk("the thermal-name1 is %s\n",pzone->thermal_zone_name);
+ pzone->reg_base = (unsigned long) ANA_THM_BASE;
+ pzone->trip_tab = ptrips;
+ pzone->temp_inteval = temp_interval;
+ pzone->sensor_id = sensor_id;
+ pzone->ops = &sprd_pmic_ops;
+ ret = sprd_pmic_thm_hw_init(pzone);
+ if(ret){
+ dev_err(&pdev->dev, " pzone hw init error id =%d\n",pzone->sensor_id);
+ return -ENODEV;
+ }
+ ret = sprd_thermal_init(pzone);
+ if(ret){
+ dev_err(&pdev->dev, " pzone sw init error id =%d\n",pzone->sensor_id);
+ return -ENODEV;
+ }
+ platform_set_drvdata(pdev, pzone);
+ mutex_unlock(&pzone->th_lock);
+ printk("sprd_adi_thm_probe end\n");
+ return 0;
+}
+
+static int sprd_adi_thm_remove(struct platform_device *pdev)
+{
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ sprd_thermal_remove(pzone);
+ return 0;
+}
+
+static int sprd_adi_thm_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ //flush_delayed_work(&pzone->thm_logtime_work);
+ flush_delayed_work(&pzone->thm_read_work);
+ flush_delayed_work(&pzone->resume_delay_work);
+ pzone->ops->suspend(pzone);
+ return 0;
+}
+
+static int sprd_adi_thm_resume(struct platform_device *pdev)
+{
+
+ struct sprd_thermal_zone *pzone = platform_get_drvdata(pdev);
+ schedule_delayed_work(&pzone->resume_delay_work, (HZ * 1));
+ schedule_delayed_work(&pzone->thm_read_work, (HZ * 5));
+ //schedule_delayed_work(&pzone->thm_logtime_work, (HZ * 7));
+ //pzone->ops->resume(pzone);
+ return 0;
+}
+
+static const struct of_device_id thermal_of_match[] = {
+ { .compatible = "sprd,sc2723-thermal", },
+ {}
+};
+
+static struct platform_driver sprd_thermal_driver = {
+ .probe = sprd_adi_thm_probe,
+ .suspend = sprd_adi_thm_suspend,
+ .resume = sprd_adi_thm_resume,
+ .remove = sprd_adi_thm_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "sc2723-thermal",
+#ifdef CONFIG_OF
+ .of_match_table = of_match_ptr(thermal_of_match),
+#endif
+ },
+};
+static int __init sprd_adi_thermal_init(void)
+{
+ return platform_driver_register(&sprd_thermal_driver);
+}
+
+static void __exit sprd_adi_thermal_exit(void)
+{
+ platform_driver_unregister(&sprd_thermal_driver);
+}
+
+device_initcall_sync(sprd_adi_thermal_init);
+module_exit(sprd_adi_thermal_exit);
+
+MODULE_AUTHOR("Freeman Liu <freeman.liu@spreadtrum.com>");
+MODULE_DESCRIPTION("sprd thermal driver");
+MODULE_LICENSE("GPL");
+
+
+
+
diff --git a/drivers/thermal/sprd_thm.c b/drivers/thermal/sprd_thm.c
new file mode 100644
index 00000000..4a56c3e6
--- /dev/null
+++ b/drivers/thermal/sprd_thm.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include<linux/string.h>
+#include <asm/io.h>
+#include <linux/thermal.h>
+#include <linux/sprd_thm.h>
+#include "thm.h"
+#include "linux/delay.h"
+#ifdef CONFIG_OF
+#include <linux/slab.h>
+#include <linux/of_device.h>
+//#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#endif
+
+struct thm_handle_ops *sprd_thm_handle_ops[SPRD_MAX_SENSOR] = {NULL};
+char sprd_thm_name[SPRD_MAX_SENSOR][THERMAL_NAME_LENGTH] = {{0},{0}};
+extern struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name);
+extern int thermal_zone_get_temp(struct thermal_zone_device *tz, unsigned long *temp);
+int sprd_thermal_temp_get(enum sprd_thm_sensor_id thermal_id,unsigned long *temp)
+{
+ char thermal_name[SPRD_MAX_SENSOR][THERMAL_NAME_LENGTH] = {
+ "sprd_arm_thm",
+ "sprd_gpu_thm",
+ "sprd_pmic_thm"
+ };
+ struct thermal_zone_device * tz = thermal_zone_get_zone_by_name(thermal_name[thermal_id]);
+ if(IS_ERR(tz) )
+ return -1;
+ return thermal_zone_get_temp(tz, temp);
+}
+EXPORT_SYMBOL(sprd_thermal_temp_get);
+#if 0
+static int sprd_thm_ops_init(struct sprd_thermal_zone *pzone)
+{
+ if((!sprd_thm_name[pzone->sensor_id]) ||(!sprd_thm_handle_ops[pzone->sensor_id]) )
+ return -1;
+ strcpy(pzone->thermal_zone_name,sprd_thm_name[pzone->sensor_id]);
+ pzone->ops = sprd_thm_handle_ops[pzone->sensor_id];
+ return 0;
+}
+int sprd_thm_add(struct thm_handle_ops* ops, char *p,int id)
+{
+ if( (ops == NULL) ||( p == NULL) ||( id >=SPRD_MAX_SENSOR))
+ return -1;
+ sprd_thm_handle_ops[id] = ops;
+ strcpy(sprd_thm_name[id],p);
+ return 0;
+}
+void sprd_thm_delete(int id)
+{
+ sprd_thm_handle_ops[id] = NULL;
+ strcpy(sprd_thm_name[id],"");
+}
+#endif
+static int sprd_thermal_match_cdev(struct thermal_cooling_device *cdev,
+ struct sprd_trip_point *trip_point)
+{
+ int i;
+ if (!strlen(cdev->type))
+ return -EINVAL;
+ for (i = 0; i < COOLING_DEV_MAX; i++) {
+ if (!strcmp(trip_point->cdev_name[i], cdev->type))
+ return 0;
+ }
+ return -ENODEV;
+}
+
+/* Callback to bind cooling device to thermal zone */
+static int sprd_cdev_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ struct sprd_thm_platform_data *ptrips = pzone->trip_tab;
+ int i, ret = -EINVAL;
+
+ printk(KERN_INFO "sprd_cdev_bind--%d \n", pzone->sensor_id);
+ for (i = 0; i < ptrips->num_trips; i++) {
+ if (sprd_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
+ continue;
+ ret = thermal_zone_bind_cooling_device(thermal, i, cdev, THERMAL_NO_LIMIT, THERMAL_NO_LIMIT);
+ dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
+ i, ret, ret ? "fail" : "succeed");
+ }
+ return 0;
+}
+
+/* Callback to unbind cooling device from thermal zone */
+static int sprd_cdev_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ struct sprd_thm_platform_data *ptrips = pzone->trip_tab;
+ int i, ret = -EINVAL;
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ if (sprd_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
+ continue;
+ ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
+ dev_info(&cdev->device, "%s unbind from %d: %s\n",
+ cdev->type, i, ret ? "fail" : "succeed");
+ }
+ return ret;
+}
+
+/* Callback to get current temperature */
+static int sprd_sys_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ if(!pzone->ops->read_temp)
+ return -1;
+ *temp = pzone->ops->read_temp(pzone);
+ return 0;
+}
+
+/* Callback to get thermal zone mode */
+static int sprd_sys_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ mutex_lock(&pzone->th_lock);
+ *mode = pzone->mode;
+ mutex_unlock(&pzone->th_lock);
+ return 0;
+}
+
+/* Callback to set thermal zone mode */
+static int sprd_sys_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ mutex_lock(&pzone->th_lock);
+ pzone->mode = mode;
+ if (mode == THERMAL_DEVICE_ENABLED)
+ schedule_work(&pzone->therm_work);
+ mutex_unlock(&pzone->th_lock);
+ return 0;
+}
+#ifdef THM_TEST
+static int sprd_sys_get_regs(struct thermal_zone_device *thermal,
+ unsigned int* regs)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ if(!pzone->ops->reg_debug_get)
+ return -1;
+ return pzone->ops->reg_debug_get(pzone,regs);
+}
+
+static int sprd_sys_set_regs(struct thermal_zone_device *thermal,
+ unsigned int *regs)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ if(!pzone->ops->reg_debug_set)
+ return -1;
+ return pzone->ops->reg_debug_set(pzone,regs);
+}
+
+/* Callback to get thermal zone logtime */
+static int sprd_sys_get_logtime(struct thermal_zone_device *thermal,
+ unsigned long *logtime)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ mutex_lock(&pzone->th_lock);
+ *logtime = pzone->logtime;
+ mutex_unlock(&pzone->th_lock);
+ return 0;
+}
+
+/* Callback to set thermal zone logtime */
+static int sprd_sys_set_logtime(struct thermal_zone_device *thermal,
+ unsigned long logtime)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ mutex_lock(&pzone->th_lock);
+ pzone->logtime = logtime;
+ mutex_unlock(&pzone->th_lock);
+ return 0;
+}
+
+/* Callback to get thermal log switch status*/
+static int sprd_sys_get_logswitch(struct thermal_zone_device *thermal,
+ enum thermal_log_switch *logswitch)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ mutex_lock(&pzone->th_lock);
+ *logswitch = pzone->thmlog_switch;
+ mutex_unlock(&pzone->th_lock);
+ return 0;
+}
+
+/* Callback to set thermal log switch status */
+static int sprd_sys_set_logswitch(struct thermal_zone_device *thermal,
+ enum thermal_log_switch logswitch)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ mutex_lock(&pzone->th_lock);
+ pzone->thmlog_switch = logswitch;
+ if (logswitch == THERMAL_LOG_ENABLED)
+ schedule_delayed_work(&pzone->thm_logtime_work, (HZ * pzone->logtime));
+ mutex_unlock(&pzone->th_lock);
+ return 0;
+}
+
+
+static ssize_t
+debug_reg_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_zone_device *tz = container_of(dev, struct thermal_zone_device, device);
+ u32 regs[4] = {0};
+ sprd_sys_get_regs(tz,regs);
+ return sprintf(buf, "DET_PERI=%08x,MON_CTL=%08x,MON_PERI=%08x,SENSOR_CTL=%08x\n",
+ regs[0],regs[1],regs[2],regs[3]);
+}
+
+static ssize_t
+debug_reg_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = container_of(dev, struct thermal_zone_device, device);
+ unsigned int regs[4]={0};
+ if (!sscanf(buf, "%x,%x,%x,%x", &regs[0],&regs[1],&regs[2],&regs[3]))
+ return -EINVAL;
+
+ printk("%x,%x,%x,%x\n", regs[0],regs[1],regs[2],regs[3]);
+ sprd_sys_set_regs(tz,regs);
+ return count;
+}
+static DEVICE_ATTR(debug_reg, 0644,debug_reg_show, debug_reg_store);
+
+static int sprd_debug_get_trip_temp(struct thermal_zone_device *thermal,
+ int trip, unsigned long *temp,unsigned long *low_off)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ struct sprd_thm_platform_data *ptrips = pzone->trip_tab;
+ if (trip >= ptrips->num_trips)
+ return -EINVAL;
+ *temp = ptrips->trip_points[trip].temp;
+ *low_off = ptrips->trip_points[trip].lowoff;
+ return 0;
+}
+
+static int sprd_debug_set_trip_temp(struct thermal_zone_device *thermal,
+ int trip, unsigned long temp,unsigned long low_off)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ struct sprd_thm_platform_data *ptrips = pzone->trip_tab;
+ if(!pzone->ops->trip_debug_set)
+ return 0;
+ ptrips->trip_points[trip].temp = temp;
+ ptrips->trip_points[trip].lowoff = low_off;
+ return pzone->ops->trip_debug_set(pzone,trip);
+}
+static ssize_t
+trip_point_debug_temp_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = container_of(dev, struct thermal_zone_device, device);
+ int trip, ret;
+ long temperature,low_off;
+ if (!sscanf(attr->attr.name, "trip_%d_temp", &trip))
+ return -EINVAL;
+ ret = sprd_debug_get_trip_temp(tz, trip, &temperature,&low_off);
+
+ if (ret)
+ return ret;
+ return sprintf(buf, "tem = %ld,low_off=%ld\n", temperature,low_off);
+}
+static ssize_t
+trip_point_debug_temp_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = container_of(dev, struct thermal_zone_device, device);
+ int trip, ret;
+ unsigned long temperature,low_off;
+ if (!sscanf(attr->attr.name, "trip_%d_temp", &trip))
+ return -EINVAL;
+ if(!sscanf(buf,"%ld,%ld",&temperature,&low_off))
+ return -EINVAL;
+ ret = sprd_debug_set_trip_temp(tz, trip, temperature, low_off);
+ return ret ? ret : count;
+}
+
+static ssize_t
+logtime_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ //struct thermal_zone_device *tz = to_thermal_zone(dev);
+ struct thermal_zone_device *tz = container_of(dev, struct thermal_zone_device, device);
+ unsigned long logtime;
+ int result;
+ result = sprd_sys_get_logtime(tz, &logtime);
+ if (result)
+ return result;
+
+ return sprintf(buf, "%ld\n", logtime);
+
+}
+static ssize_t
+logtime_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ //struct thermal_zone_device *tz = to_thermal_zone(dev);
+ struct thermal_zone_device *tz = container_of(dev, struct thermal_zone_device, device);
+ int result;
+ unsigned long logtime;
+ if (kstrtoul(buf, 10, &logtime))
+ return -EINVAL;
+ result = sprd_sys_set_logtime(tz, logtime);
+ if (result)
+ return result;
+
+ return count;
+}
+static ssize_t
+logswitch_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ //struct thermal_zone_device *tz = to_thermal_zone(dev);
+ struct thermal_zone_device *tz = container_of(dev, struct thermal_zone_device, device);
+ enum thermal_log_switch logswtich;
+ int result;
+
+ result = sprd_sys_get_logswitch(tz, &logswtich);
+ if (result)
+ return result;
+
+ return sprintf(buf, "%s\n", logswtich == THERMAL_LOG_ENABLED ? "enabled"
+ : "disabled");
+}
+static ssize_t
+logswitch_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = container_of(dev, struct thermal_zone_device, device);
+ int result;
+
+ if (!strncmp(buf, "enabled", sizeof("enabled") - 1))
+ result = sprd_sys_set_logswitch(tz, THERMAL_LOG_ENABLED);
+ else if (!strncmp(buf, "disabled", sizeof("disabled") - 1))
+ result = sprd_sys_set_logswitch(tz, THERMAL_LOG_DISABLED);
+ else
+ result = -EINVAL;
+
+ if (result)
+ return result;
+
+ return count;
+}
+
+static DEVICE_ATTR(logtime, 0644, logtime_show, logtime_store);
+static DEVICE_ATTR(logswitch, 0644, logswitch_show, logswitch_store);
+
+static int create_trip_attrs(struct thermal_zone_device *tz)
+{
+ int indx;
+ int size = sizeof(struct thermal_attr) * tz->trips;
+ int result;
+ result = device_create_file(&tz->device, &dev_attr_logswitch);
+ if (result)
+ return result;
+ result = device_create_file(&tz->device, &dev_attr_logtime);
+ if (result)
+ return result;
+ tz->trip_temp_attrs = kzalloc(size, GFP_KERNEL);
+ if (!tz->trip_temp_attrs) {
+ return -ENOMEM;
+ }
+
+ for (indx = 0; indx < tz->trips; indx++) {
+ /* create trip temp attribute */
+ snprintf(tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH,
+ "trip_%d_temp", indx);
+
+ sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr);
+ tz->trip_temp_attrs[indx].attr.attr.name =
+ tz->trip_temp_attrs[indx].name;
+ tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO;
+ tz->trip_temp_attrs[indx].attr.show = trip_point_debug_temp_show;
+ tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;
+ tz->trip_temp_attrs[indx].attr.store =trip_point_debug_temp_store;
+ device_create_file(&tz->device,
+ &tz->trip_temp_attrs[indx].attr);
+ }
+ return 0;
+}
+
+static void remove_trip_attrs(struct thermal_zone_device *tz)
+{
+ int indx;
+ device_remove_file(&tz->device, &dev_attr_logswitch);
+ device_remove_file(&tz->device, &dev_attr_logtime);
+ for (indx = 0; indx < tz->trips; indx++) {
+ device_remove_file(&tz->device,
+ &tz->trip_temp_attrs[indx].attr);
+ }
+ kfree(tz->trip_temp_attrs);
+}
+#endif
+
+void sprd_thermal_remove(struct sprd_thermal_zone *pzone)
+{
+#ifdef THM_TEST
+ remove_trip_attrs(pzone->therm_dev);
+ device_remove_file(&pzone->therm_dev->device, &dev_attr_debug_reg);
+ cancel_delayed_work_sync(&pzone->thm_logtime_work);
+#endif
+ thermal_zone_device_unregister(pzone->therm_dev);
+ cancel_delayed_work_sync(&pzone->thm_read_work);
+ cancel_delayed_work_sync(&pzone->resume_delay_work);
+ mutex_destroy(&pzone->th_lock);
+}
+
+static int sprd_sys_get_trend(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trend * ptrend)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ return pzone->ops->get_trend(pzone, trip, ptrend);
+}
+
+static int sprd_sys_get_hyst(struct thermal_zone_device *thermal,
+ int trip, unsigned long *physt)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ return pzone->ops->get_hyst(pzone, trip, physt);
+}
+
+
+/* Callback to get trip point type */
+static int sprd_sys_get_trip_type(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trip_type *type)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ struct sprd_thm_platform_data *ptrips = pzone->trip_tab;
+ if (trip >= ptrips->num_trips)
+ return -EINVAL;
+ *type = ptrips->trip_points[trip].type;
+ return 0;
+}
+
+/* Callback to get trip point temperature */
+static int sprd_sys_get_trip_temp(struct thermal_zone_device *thermal,
+ int trip, unsigned long *temp)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ struct sprd_thm_platform_data *ptrips = pzone->trip_tab;
+ if (trip >= ptrips->num_trips)
+ return -EINVAL;
+ *temp = ptrips->trip_points[trip].temp;
+ return 0;
+}
+
+/* Callback to get critical trip point temperature */
+static int sprd_sys_get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct sprd_thermal_zone *pzone = thermal->devdata;
+ struct sprd_thm_platform_data *ptrips = pzone->trip_tab;
+ int i;
+ for (i = ptrips->num_trips - 1; i > 0; i--) {
+ if (ptrips->trip_points[i].type == THERMAL_TRIP_CRITICAL) {
+ *temp = ptrips->trip_points[i].temp;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static struct thermal_zone_device_ops thdev_ops = {
+ .bind = sprd_cdev_bind,
+ .unbind = sprd_cdev_unbind,
+ .get_temp = sprd_sys_get_temp,
+ .get_mode = sprd_sys_get_mode,
+ .set_mode = sprd_sys_set_mode,
+ .get_trip_type = sprd_sys_get_trip_type,
+ .get_trip_temp = sprd_sys_get_trip_temp,
+ .get_trip_hyst = sprd_sys_get_hyst,
+ .get_crit_temp = sprd_sys_get_crit_temp,
+ .get_trend = sprd_sys_get_trend,
+};
+static void sprd_thermal_resume_delay_work(struct work_struct *work)
+{
+ struct sprd_thermal_zone *pzone;
+
+ pzone = container_of(work, struct sprd_thermal_zone, resume_delay_work.work);
+ dev_dbg(&pzone->therm_dev->device, "thermal resume delay work Started.\n");
+ pzone->ops->resume(pzone);
+ dev_dbg(&pzone->therm_dev->device, "thermal resume delay work finished.\n");
+}
+#ifdef THM_TEST
+static void thm_logtime_work(struct work_struct *work)
+{
+ struct sprd_thermal_zone *pzone;
+ unsigned long temp;
+ pzone = container_of(work, struct sprd_thermal_zone, thm_logtime_work.work);
+ temp = pzone->ops->read_temp(pzone);
+ if(pzone->thmlog_switch == THERMAL_LOG_ENABLED ){
+ schedule_delayed_work(&pzone->thm_logtime_work, (HZ * pzone->logtime));
+ }
+}
+#endif
+
+static void thm_read_work(struct work_struct *work)
+{
+ int ret;
+ enum thermal_device_mode cur_mode;
+ struct sprd_thermal_zone *pzone;
+ pzone = container_of(work, struct sprd_thermal_zone, thm_read_work.work);
+ mutex_lock(&pzone->th_lock);
+ cur_mode = pzone->mode;
+ mutex_unlock(&pzone->th_lock);
+ if (cur_mode == THERMAL_DEVICE_DISABLED)
+ return;
+ if(pzone->temp_inteval == 0)
+ return;
+ ret = thermal_zone_get_temp(pzone->therm_dev, &pzone->cur_temp);
+ if (ret) {
+ dev_warn(&pzone->therm_dev->device, "failed to read out thermal zone %d\n",
+ pzone->therm_dev->id);
+ return;
+ }
+ printk("thm id:%d, inteval:%d, last_temp:%ld,cur_temp:%ld\n", pzone->sensor_id,pzone->temp_inteval,pzone->last_temp,pzone->cur_temp);
+ if(pzone->cur_temp > pzone->last_temp)
+ {
+ pzone->trend_val = THERMAL_TREND_RAISING;
+ if (pzone->cur_temp >= pzone->trip_tab->trip_points[pzone->current_trip_num].temp ){
+ if((pzone->trip_tab->num_trips-1) > pzone->current_trip_num )
+ pzone->current_trip_num++;
+ thermal_zone_device_update(pzone->therm_dev);
+ }
+ }else if(pzone->cur_temp < pzone->last_temp){
+ pzone->trend_val = THERMAL_TREND_DROPPING;
+ printk("pzone->trend_val = THERMAL_TREND_DROPPING\n");
+ if (pzone->cur_temp < pzone->trip_tab->trip_points[pzone->current_trip_num].lowoff ){
+ pzone->current_trip_num--;
+ thermal_zone_device_update(pzone->therm_dev);
+ }
+ }else{
+ printk("pzone->trend_val = THERMAL_TREND_STABLE\n");
+ pzone->trend_val = THERMAL_TREND_STABLE;
+ }
+ pzone->last_temp = pzone->cur_temp;
+ schedule_delayed_work(&pzone->thm_read_work, (HZ * pzone->temp_inteval));
+}
+
+int sprd_thermal_init(struct sprd_thermal_zone *pzone)
+{
+ int ret=0;
+ pzone->mode = THERMAL_DEVICE_DISABLED;
+ pzone->trend_val = THERMAL_TREND_STABLE;
+ INIT_DELAYED_WORK(&pzone->thm_read_work, thm_read_work);
+ INIT_DELAYED_WORK(&pzone->resume_delay_work,sprd_thermal_resume_delay_work);
+ pzone->therm_dev =
+ thermal_zone_device_register(pzone->thermal_zone_name,
+ pzone->trip_tab->num_trips, 0, pzone,
+ &thdev_ops, 0, 0, 0);
+ if (IS_ERR_OR_NULL(pzone->therm_dev)) {
+ printk("Register thermal zone device failed.\n");
+ return PTR_ERR(pzone->therm_dev);
+ }
+ pzone->mode = THERMAL_DEVICE_ENABLED;
+ schedule_delayed_work(&pzone->thm_read_work, (HZ * pzone->temp_inteval));
+#ifdef THM_TEST
+ pzone->logtime= 2;
+ pzone->thmlog_switch= THERMAL_LOG_DISABLED;
+ INIT_DELAYED_WORK(&pzone->thm_logtime_work, thm_logtime_work);
+ create_trip_attrs(pzone->therm_dev);
+ ret = device_create_file(&pzone->therm_dev->device, &dev_attr_debug_reg);
+#endif
+ printk(" sprd_thermal_init end\n");
+ return 0;
+}
+
diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/step_wise.c
new file mode 100644
index 00000000..9f012873
--- /dev/null
+++ b/drivers/thermal/step_wise.c
@@ -0,0 +1,350 @@
+/*
+ * step_wise.c - A step-by-step Thermal throttling governor
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/thermal.h>
+
+#include "thermal_core.h"
+
+/*
+ * If the temperature is higher than a trip point,
+ * a. if the trend is THERMAL_TREND_RAISING, use higher cooling
+ * state for this trip point
+ * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
+ * state for this trip point
+ * c. if the trend is THERMAL_TREND_RAISE_FULL, use upper limit
+ * for this trip point
+ * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit
+ * for this trip point
+ * If the temperature is lower than a trip point,
+ * a. if the trend is THERMAL_TREND_RAISING, do nothing
+ * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
+ * state for this trip point, if the cooling state already
+ * equals lower limit, deactivate the thermal instance
+ * c. if the trend is THERMAL_TREND_RAISE_FULL, do nothing
+ * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit,
+ * if the cooling state already equals lower limit,
+ * deactive the thermal instance
+ */
+#if 0
+static unsigned long get_target_state(struct thermal_instance *instance,
+ enum thermal_trend trend, bool throttle)
+{
+ struct thermal_cooling_device *cdev = instance->cdev;
+ unsigned long cur_state;
+
+ cdev->ops->get_cur_state(cdev, &cur_state);
+ dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state);
+
+ switch (trend) {
+ case THERMAL_TREND_RAISING:
+ if (throttle) {
+ cur_state = cur_state < instance->upper ?
+ (cur_state + 1) : instance->upper;
+ if (cur_state < instance->lower)
+ cur_state = instance->lower;
+ }
+ break;
+ case THERMAL_TREND_RAISE_FULL:
+ if (throttle)
+ cur_state = instance->upper;
+ break;
+ case THERMAL_TREND_DROPPING:
+ if (cur_state == instance->lower) {
+ if (!throttle)
+ cur_state = -1;
+ } else {
+ cur_state -= 1;
+ if (cur_state > instance->upper)
+ cur_state = instance->upper;
+ }
+ break;
+ case THERMAL_TREND_DROP_FULL:
+ if (cur_state == instance->lower) {
+ if (!throttle)
+ cur_state = -1;
+ } else
+ cur_state = instance->lower;
+ break;
+ default:
+ break;
+ }
+
+ return cur_state;
+}
+
+static void update_passive_instance(struct thermal_zone_device *tz,
+ enum thermal_trip_type type, int value)
+{
+ /*
+ * If value is +1, activate a passive instance.
+ * If value is -1, deactivate a passive instance.
+ */
+ if (type == THERMAL_TRIP_PASSIVE || type == THERMAL_TRIPS_NONE)
+ tz->passive += value;
+}
+
+static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
+{
+ long trip_temp;
+ enum thermal_trip_type trip_type;
+ enum thermal_trend trend;
+ struct thermal_instance *instance;
+ bool throttle = false;
+ int old_target;
+
+ if (trip == THERMAL_TRIPS_NONE) {
+ trip_temp = tz->forced_passive;
+ trip_type = THERMAL_TRIPS_NONE;
+ } else {
+ tz->ops->get_trip_temp(tz, trip, &trip_temp);
+ tz->ops->get_trip_type(tz, trip, &trip_type);
+ }
+
+ trend = get_tz_trend(tz, trip);
+
+ if (tz->temperature >= trip_temp)
+ throttle = true;
+
+ dev_dbg(&tz->device, "Trip%d[type=%d,temp=%ld]:trend=%d,throttle=%d\n",
+ trip, trip_type, trip_temp, trend, throttle);
+
+ mutex_lock(&tz->lock);
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (instance->trip != trip)
+ continue;
+
+ old_target = instance->target;
+ instance->target = get_target_state(instance, trend, throttle);
+ dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n",
+ old_target, (int)instance->target);
+
+ /* Activate a passive thermal instance */
+ if (old_target == THERMAL_NO_TARGET &&
+ instance->target != THERMAL_NO_TARGET)
+ update_passive_instance(tz, trip_type, 1);
+ /* Deactivate a passive thermal instance */
+ else if (old_target != THERMAL_NO_TARGET &&
+ instance->target == THERMAL_NO_TARGET)
+ update_passive_instance(tz, trip_type, -1);
+
+
+ instance->cdev->updated = false; /* cdev needs update */
+ }
+
+ mutex_unlock(&tz->lock);
+}
+#else
+
+static unsigned long get_hyst_target(struct thermal_zone_device *tz, int trip)
+{
+ int count;
+ unsigned long trip_temp, trip_hyst, hyst_temp;
+
+ if (tz->trips == 0 || !tz->ops->get_trip_hyst){
+ return 0;
+ }
+ for (count = 0; count < tz->trips; count++) {
+ tz->ops->get_trip_temp(tz, count, &trip_temp);
+ tz->ops->get_trip_hyst(tz, count, &trip_hyst);
+ hyst_temp = trip_temp - trip_hyst;
+ printk("trip:%d, temp:%d, hyst_temp:%d\n", count, tz->temperature, hyst_temp);
+ if (tz->temperature < (long)hyst_temp + TRIP_TEMP_OFFSET)
+ break;
+ }
+ if (trip == count) {
+ return count;
+ } else {
+ return THERMAL_NO_TARGET;
+ }
+}
+
+static unsigned long get_trip_target(struct thermal_zone_device *tz, int trip)
+{
+ int count = 0;
+ unsigned long trip_temp;
+
+ if (tz->trips == 0 || !tz->ops->get_trip_temp)
+ return 0;
+
+ for (count = tz->trips - 1; count >= 0; count--) {
+ tz->ops->get_trip_temp(tz, count, &trip_temp);
+ printk("trip:%d, temp:%d, trip_temp:%d\n", count, tz->temperature, trip_temp);
+ if (tz->temperature > (long)trip_temp - TRIP_TEMP_OFFSET)
+ break;
+ }
+ if (trip == count) {
+ return count + 1;
+ } else {
+ return THERMAL_NO_TARGET;
+ }
+
+}
+
+static unsigned long get_step_wise_target(struct thermal_instance *instance,
+ struct thermal_zone_device *tz, int trip)
+{
+ struct thermal_cooling_device *cdev = instance->cdev;
+ enum thermal_trend trend;
+ unsigned long cur_target;
+ unsigned long cdev_state;
+ unsigned long new_target;
+
+ cur_target = instance->target;
+ printk("%s:%s trip:%d cur_target:%d\n", __func__, cdev->type, trip, cur_target);
+ cdev->ops->get_cur_state(cdev, &cdev_state);
+ printk("%s:%s trip:%d cdev_state:%d\n", __func__, cdev->type, trip, cdev_state);
+ if (cdev_state == THERMAL_NO_TARGET){
+ cdev_state = instance->lower;
+ }
+ if (tz->ops->get_trend){
+ tz->ops->get_trend(tz, trip, &trend);
+ }else{
+ new_target = get_trip_target(tz, trip);
+ printk("%s: %s trip:%d new_target:%d\n", __func__, cdev->type, trip, new_target);
+ if (new_target == THERMAL_NO_TARGET) {
+ return THERMAL_NO_TARGET;
+ }
+ if (new_target < cdev_state){
+ trend = THERMAL_TREND_DROPPING;
+ if (!tz->ops->get_trip_hyst){
+ goto dropping;
+ }
+ }else if(new_target >= cdev_state){
+ goto raising;
+ }
+ }
+ switch (trend){
+ case THERMAL_TREND_RAISING:
+ new_target = get_trip_target(tz, trip);
+ printk("%s: %s trend raising, target:%d\n", __func__, cdev->type, new_target);
+ if (new_target == THERMAL_NO_TARGET) {
+ return THERMAL_NO_TARGET;
+ }
+ goto raising;
+ case THERMAL_TREND_DROPPING:
+#if 0
+ if (tz->ops->get_trip_hyst){
+ new_target = get_hyst_target(tz, trip);
+ }else{
+ new_target = get_trip_target(tz, trip);
+ }
+ printk("%s: %s trend dropping, target:%d\n", cdev->type, __func__, new_target);
+ if (new_target == THERMAL_NO_TARGET) {
+ return THERMAL_NO_TARGET;
+ }
+#endif
+ goto dropping;
+ case THERMAL_TREND_STABLE:
+ default:
+ printk("%s: %s trend stable\n", cdev->type, __func__);
+ return THERMAL_NO_TARGET;
+ }
+raising:
+ if (cdev_state > trip){
+ cdev_state = trip;
+ }
+ return cdev_state + 1;
+dropping:
+ if (cur_target != THERMAL_NO_TARGET && cur_target > 0){
+ return cur_target - 1;
+ }
+ return cur_target;
+
+}
+
+static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
+{
+ struct thermal_instance *instance;
+ unsigned long target;
+
+ mutex_lock(&tz->lock);
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (instance->trip != trip)
+ continue;
+ target = get_step_wise_target(instance, tz, trip);
+ printk("%s: get target_state: %d\n", instance->cdev->type, target);
+ if (target == THERMAL_NO_TARGET){
+ continue;
+ }
+ if (target < instance->lower){
+ target = instance->lower;
+ }
+ if (target > instance->upper){
+ target = instance->upper;
+ }
+ instance->target = target;
+ instance->cdev->updated = false; /* cdev needs update */
+ }
+ mutex_unlock(&tz->lock);
+
+ return;
+}
+#endif
+
+/**
+ * step_wise_throttle - throttles devices asscciated with the given zone
+ * @tz - thermal_zone_device
+ * @trip - the trip point
+ * @trip_type - type of the trip point
+ *
+ * Throttling Logic: This uses the trend of the thermal zone to throttle.
+ * If the thermal zone is 'heating up' this throttles all the cooling
+ * devices associated with the zone and its particular trip point, by one
+ * step. If the zone is 'cooling down' it brings back the performance of
+ * the devices by one step.
+ */
+static int step_wise_throttle(struct thermal_zone_device *tz, int trip)
+{
+ struct thermal_instance *instance;
+
+ thermal_zone_trip_update(tz, trip);
+
+ if (tz->forced_passive)
+ thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE);
+
+ mutex_lock(&tz->lock);
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node)
+ thermal_cdev_update(instance->cdev);
+
+ mutex_unlock(&tz->lock);
+
+ return 0;
+}
+
+static struct thermal_governor thermal_gov_step_wise = {
+ .name = "step_wise",
+ .throttle = step_wise_throttle,
+};
+
+int thermal_gov_step_wise_register(void)
+{
+ return thermal_register_governor(&thermal_gov_step_wise);
+}
+
+void thermal_gov_step_wise_unregister(void)
+{
+ thermal_unregister_governor(&thermal_gov_step_wise);
+}
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
new file mode 100644
index 00000000..edc0cb88
--- /dev/null
+++ b/drivers/thermal/thermal_core.c
@@ -0,0 +1,1845 @@
+/*
+ * thermal.c - Generic Thermal Management Sysfs support.
+ *
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/kdev_t.h>
+#include <linux/idr.h>
+#include <linux/thermal.h>
+#include <linux/reboot.h>
+#include <linux/string.h>
+#include <linux/of.h>
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "thermal_core.h"
+#include "thermal_hwmon.h"
+
+MODULE_AUTHOR("Zhang Rui");
+MODULE_DESCRIPTION("Generic thermal management sysfs support");
+MODULE_LICENSE("GPL v2");
+
+static DEFINE_IDR(thermal_tz_idr);
+static DEFINE_IDR(thermal_cdev_idr);
+static DEFINE_MUTEX(thermal_idr_lock);
+
+static LIST_HEAD(thermal_tz_list);
+static LIST_HEAD(thermal_cdev_list);
+static LIST_HEAD(thermal_governor_list);
+
+static DEFINE_MUTEX(thermal_list_lock);
+static DEFINE_MUTEX(thermal_governor_lock);
+
+static struct thermal_governor *def_governor;
+
+static struct thermal_governor *__find_governor(const char *name)
+{
+ struct thermal_governor *pos;
+
+ if (!name || !name[0])
+ return def_governor;
+
+ list_for_each_entry(pos, &thermal_governor_list, governor_list)
+ if (!strnicmp(name, pos->name, THERMAL_NAME_LENGTH))
+ return pos;
+
+ return NULL;
+}
+
+int thermal_register_governor(struct thermal_governor *governor)
+{
+ int err;
+ const char *name;
+ struct thermal_zone_device *pos;
+
+ if (!governor)
+ return -EINVAL;
+
+ mutex_lock(&thermal_governor_lock);
+
+ err = -EBUSY;
+ if (__find_governor(governor->name) == NULL) {
+ err = 0;
+ list_add(&governor->governor_list, &thermal_governor_list);
+ if (!def_governor && !strncmp(governor->name,
+ DEFAULT_THERMAL_GOVERNOR, THERMAL_NAME_LENGTH))
+ def_governor = governor;
+ }
+
+ mutex_lock(&thermal_list_lock);
+
+ list_for_each_entry(pos, &thermal_tz_list, node) {
+ /*
+ * only thermal zones with specified tz->tzp->governor_name
+ * may run with tz->govenor unset
+ */
+ if (pos->governor)
+ continue;
+
+ name = pos->tzp->governor_name;
+
+ if (!strnicmp(name, governor->name, THERMAL_NAME_LENGTH))
+ pos->governor = governor;
+ }
+
+ mutex_unlock(&thermal_list_lock);
+ mutex_unlock(&thermal_governor_lock);
+
+ return err;
+}
+
+void thermal_unregister_governor(struct thermal_governor *governor)
+{
+ struct thermal_zone_device *pos;
+
+ if (!governor)
+ return;
+
+ mutex_lock(&thermal_governor_lock);
+
+ if (__find_governor(governor->name) == NULL)
+ goto exit;
+
+ mutex_lock(&thermal_list_lock);
+
+ list_for_each_entry(pos, &thermal_tz_list, node) {
+ if (!strnicmp(pos->governor->name, governor->name,
+ THERMAL_NAME_LENGTH))
+ pos->governor = NULL;
+ }
+
+ mutex_unlock(&thermal_list_lock);
+ list_del(&governor->governor_list);
+exit:
+ mutex_unlock(&thermal_governor_lock);
+ return;
+}
+
+static int get_idr(struct idr *idr, struct mutex *lock, int *id)
+{
+ int ret;
+
+ if (lock)
+ mutex_lock(lock);
+ ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
+ if (lock)
+ mutex_unlock(lock);
+ if (unlikely(ret < 0))
+ return ret;
+ *id = ret;
+ return 0;
+}
+
+static void release_idr(struct idr *idr, struct mutex *lock, int id)
+{
+ if (lock)
+ mutex_lock(lock);
+ idr_remove(idr, id);
+ if (lock)
+ mutex_unlock(lock);
+}
+
+int get_tz_trend(struct thermal_zone_device *tz, int trip)
+{
+ enum thermal_trend trend;
+
+ if (!tz->ops->get_trend || tz->ops->get_trend(tz, trip, &trend)) {
+ if (tz->temperature > tz->last_temperature)
+ trend = THERMAL_TREND_RAISING;
+ else if (tz->temperature < tz->last_temperature)
+ trend = THERMAL_TREND_DROPPING;
+ else
+ trend = THERMAL_TREND_STABLE;
+ }
+
+ return trend;
+}
+EXPORT_SYMBOL(get_tz_trend);
+
+struct thermal_instance *get_thermal_instance(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev, int trip)
+{
+ struct thermal_instance *pos = NULL;
+ struct thermal_instance *target_instance = NULL;
+
+ mutex_lock(&tz->lock);
+ mutex_lock(&cdev->lock);
+
+ list_for_each_entry(pos, &tz->thermal_instances, tz_node) {
+ if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
+ target_instance = pos;
+ break;
+ }
+ }
+
+ mutex_unlock(&cdev->lock);
+ mutex_unlock(&tz->lock);
+
+ return target_instance;
+}
+EXPORT_SYMBOL(get_thermal_instance);
+
+static void print_bind_err_msg(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev, int ret)
+{
+ dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
+ tz->type, cdev->type, ret);
+}
+
+static void __bind(struct thermal_zone_device *tz, int mask,
+ struct thermal_cooling_device *cdev)
+{
+ int i, ret;
+
+ for (i = 0; i < tz->trips; i++) {
+ if (mask & (1 << i)) {
+ ret = thermal_zone_bind_cooling_device(tz, i, cdev,
+ THERMAL_NO_LIMIT, THERMAL_NO_LIMIT);
+ if (ret)
+ print_bind_err_msg(tz, cdev, ret);
+ }
+ }
+}
+
+static void __unbind(struct thermal_zone_device *tz, int mask,
+ struct thermal_cooling_device *cdev)
+{
+ int i;
+
+ for (i = 0; i < tz->trips; i++)
+ if (mask & (1 << i))
+ thermal_zone_unbind_cooling_device(tz, i, cdev);
+}
+
+static void bind_cdev(struct thermal_cooling_device *cdev)
+{
+ int i, ret;
+ const struct thermal_zone_params *tzp;
+ struct thermal_zone_device *pos = NULL;
+
+ mutex_lock(&thermal_list_lock);
+
+ list_for_each_entry(pos, &thermal_tz_list, node) {
+ if (!pos->tzp && !pos->ops->bind)
+ continue;
+
+ if (pos->ops->bind) {
+ ret = pos->ops->bind(pos, cdev);
+ if (ret)
+ print_bind_err_msg(pos, cdev, ret);
+ continue;
+ }
+
+ tzp = pos->tzp;
+ if (!tzp || !tzp->tbp)
+ continue;
+
+ for (i = 0; i < tzp->num_tbps; i++) {
+ if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
+ continue;
+ if (tzp->tbp[i].match(pos, cdev))
+ continue;
+ tzp->tbp[i].cdev = cdev;
+ __bind(pos, tzp->tbp[i].trip_mask, cdev);
+ }
+ }
+
+ mutex_unlock(&thermal_list_lock);
+}
+
+static void bind_tz(struct thermal_zone_device *tz)
+{
+ int i, ret;
+ struct thermal_cooling_device *pos = NULL;
+ const struct thermal_zone_params *tzp = tz->tzp;
+
+ if (!tzp && !tz->ops->bind)
+ return;
+
+ mutex_lock(&thermal_list_lock);
+
+ /* If there is ops->bind, try to use ops->bind */
+ if (tz->ops->bind) {
+ list_for_each_entry(pos, &thermal_cdev_list, node) {
+ ret = tz->ops->bind(tz, pos);
+ if (ret)
+ print_bind_err_msg(tz, pos, ret);
+ }
+ goto exit;
+ }
+
+ if (!tzp || !tzp->tbp)
+ goto exit;
+
+ list_for_each_entry(pos, &thermal_cdev_list, node) {
+ for (i = 0; i < tzp->num_tbps; i++) {
+ if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
+ continue;
+ if (tzp->tbp[i].match(tz, pos))
+ continue;
+ tzp->tbp[i].cdev = pos;
+ __bind(tz, tzp->tbp[i].trip_mask, pos);
+ }
+ }
+exit:
+ mutex_unlock(&thermal_list_lock);
+}
+
+static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
+ int delay)
+{
+ if (delay > 1000)
+ mod_delayed_work(system_freezable_wq, &tz->poll_queue,
+ round_jiffies(msecs_to_jiffies(delay)));
+ else if (delay)
+ mod_delayed_work(system_freezable_wq, &tz->poll_queue,
+ msecs_to_jiffies(delay));
+ else
+ cancel_delayed_work(&tz->poll_queue);
+}
+
+static void monitor_thermal_zone(struct thermal_zone_device *tz)
+{
+ mutex_lock(&tz->lock);
+
+ if (tz->passive)
+ thermal_zone_device_set_polling(tz, tz->passive_delay);
+ else if (tz->polling_delay)
+ thermal_zone_device_set_polling(tz, tz->polling_delay);
+ else
+ thermal_zone_device_set_polling(tz, 0);
+
+ mutex_unlock(&tz->lock);
+}
+
+static void handle_non_critical_trips(struct thermal_zone_device *tz,
+ int trip, enum thermal_trip_type trip_type)
+{
+ tz->governor ? tz->governor->throttle(tz, trip) :
+ def_governor->throttle(tz, trip);
+}
+
+static void handle_critical_trips(struct thermal_zone_device *tz,
+ int trip, enum thermal_trip_type trip_type)
+{
+ long trip_temp;
+
+ tz->ops->get_trip_temp(tz, trip, &trip_temp);
+
+ /* If we have not crossed the trip_temp, we do not care. */
+ if (tz->temperature < trip_temp)
+ return;
+
+ if (tz->ops->notify)
+ tz->ops->notify(tz, trip, trip_type);
+
+ if (trip_type == THERMAL_TRIP_CRITICAL) {
+ dev_emerg(&tz->device,
+ "critical temperature reached(%d C),shutting down\n",
+ tz->temperature / 1000);
+ orderly_poweroff(true);
+ }
+}
+
+static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
+{
+ enum thermal_trip_type type;
+
+ tz->ops->get_trip_type(tz, trip, &type);
+
+ if (type == THERMAL_TRIP_CRITICAL || type == THERMAL_TRIP_HOT)
+ handle_critical_trips(tz, trip, type);
+ else
+ handle_non_critical_trips(tz, trip, type);
+ /*
+ * Alright, we handled this trip successfully.
+ * So, start monitoring again.
+ */
+ monitor_thermal_zone(tz);
+}
+
+/**
+ * thermal_zone_get_temp() - returns its the temperature of thermal zone
+ * @tz: a valid pointer to a struct thermal_zone_device
+ * @temp: a valid pointer to where to store the resulting temperature.
+ *
+ * When a valid thermal zone reference is passed, it will fetch its
+ * temperature and fill @temp.
+ *
+ * Return: On success returns 0, an error code otherwise
+ */
+int thermal_zone_get_temp(struct thermal_zone_device *tz, unsigned long *temp)
+{
+ int ret = -EINVAL;
+#ifdef CONFIG_THERMAL_EMULATION
+ int count;
+ unsigned long crit_temp = -1UL;
+ enum thermal_trip_type type;
+#endif
+
+ if (!tz || IS_ERR(tz) || !tz->ops->get_temp)
+ goto exit;
+
+ mutex_lock(&tz->lock);
+
+ ret = tz->ops->get_temp(tz, temp);
+#ifdef CONFIG_THERMAL_EMULATION
+ if (!tz->emul_temperature)
+ goto skip_emul;
+
+ for (count = 0; count < tz->trips; count++) {
+ ret = tz->ops->get_trip_type(tz, count, &type);
+ if (!ret && type == THERMAL_TRIP_CRITICAL) {
+ ret = tz->ops->get_trip_temp(tz, count, &crit_temp);
+ break;
+ }
+ }
+
+ if (ret)
+ goto skip_emul;
+
+ if (*temp < crit_temp)
+ *temp = tz->emul_temperature;
+skip_emul:
+#endif
+ mutex_unlock(&tz->lock);
+exit:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
+
+static void update_temperature(struct thermal_zone_device *tz)
+{
+ long temp;
+ int ret;
+
+ ret = thermal_zone_get_temp(tz, &temp);
+ if (ret) {
+ dev_warn(&tz->device, "failed to read out thermal zone %d\n",
+ tz->id);
+ return;
+ }
+
+ mutex_lock(&tz->lock);
+ tz->last_temperature = tz->temperature;
+ tz->temperature = temp;
+ mutex_unlock(&tz->lock);
+
+ dev_dbg(&tz->device, "last_temperature=%d, current_temperature=%d\n",
+ tz->last_temperature, tz->temperature);
+}
+
+void thermal_zone_device_update(struct thermal_zone_device *tz)
+{
+ int count;
+
+ if (!tz->ops->get_temp)
+ return;
+
+ update_temperature(tz);
+
+ for (count = 0; count < tz->trips; count++)
+ handle_thermal_trip(tz, count);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_update);
+
+static void thermal_zone_device_check(struct work_struct *work)
+{
+ struct thermal_zone_device *tz = container_of(work, struct
+ thermal_zone_device,
+ poll_queue.work);
+ thermal_zone_device_update(tz);
+}
+
+/* sys I/F for thermal zone */
+
+#define to_thermal_zone(_dev) \
+ container_of(_dev, struct thermal_zone_device, device)
+
+static ssize_t
+type_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ return sprintf(buf, "%s\n", tz->type);
+}
+
+static ssize_t
+temp_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ long temperature;
+ int ret;
+
+ ret = thermal_zone_get_temp(tz, &temperature);
+
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%ld\n", temperature);
+}
+
+static ssize_t
+mode_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ enum thermal_device_mode mode;
+ int result;
+
+ if (!tz->ops->get_mode)
+ return -EPERM;
+
+ result = tz->ops->get_mode(tz, &mode);
+ if (result)
+ return result;
+
+ return sprintf(buf, "%s\n", mode == THERMAL_DEVICE_ENABLED ? "enabled"
+ : "disabled");
+}
+
+static ssize_t
+mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int result;
+
+ if (!tz->ops->set_mode)
+ return -EPERM;
+
+ if (!strncmp(buf, "enabled", sizeof("enabled") - 1))
+ result = tz->ops->set_mode(tz, THERMAL_DEVICE_ENABLED);
+ else if (!strncmp(buf, "disabled", sizeof("disabled") - 1))
+ result = tz->ops->set_mode(tz, THERMAL_DEVICE_DISABLED);
+ else
+ result = -EINVAL;
+
+ if (result)
+ return result;
+
+ return count;
+}
+
+static ssize_t
+trip_point_type_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ enum thermal_trip_type type;
+ int trip, result;
+
+ if (!tz->ops->get_trip_type)
+ return -EPERM;
+
+ if (!sscanf(attr->attr.name, "trip_point_%d_type", &trip))
+ return -EINVAL;
+
+ result = tz->ops->get_trip_type(tz, trip, &type);
+ if (result)
+ return result;
+
+ switch (type) {
+ case THERMAL_TRIP_CRITICAL:
+ return sprintf(buf, "critical\n");
+ case THERMAL_TRIP_HOT:
+ return sprintf(buf, "hot\n");
+ case THERMAL_TRIP_PASSIVE:
+ return sprintf(buf, "passive\n");
+ case THERMAL_TRIP_ACTIVE:
+ return sprintf(buf, "active\n");
+ default:
+ return sprintf(buf, "unknown\n");
+ }
+}
+
+static ssize_t
+trip_point_temp_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int trip, ret;
+ unsigned long temperature;
+
+ if (!tz->ops->set_trip_temp)
+ return -EPERM;
+
+ if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip))
+ return -EINVAL;
+
+ if (kstrtoul(buf, 10, &temperature))
+ return -EINVAL;
+
+ ret = tz->ops->set_trip_temp(tz, trip, temperature);
+
+ return ret ? ret : count;
+}
+
+static ssize_t
+trip_point_temp_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int trip, ret;
+ long temperature;
+
+ if (!tz->ops->get_trip_temp)
+ return -EPERM;
+
+ if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip))
+ return -EINVAL;
+
+ ret = tz->ops->get_trip_temp(tz, trip, &temperature);
+
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%ld\n", temperature);
+}
+
+static ssize_t
+trip_point_hyst_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int trip, ret;
+ unsigned long temperature;
+
+ if (!tz->ops->set_trip_hyst)
+ return -EPERM;
+
+ if (!sscanf(attr->attr.name, "trip_point_%d_hyst", &trip))
+ return -EINVAL;
+
+ if (kstrtoul(buf, 10, &temperature))
+ return -EINVAL;
+
+ /*
+ * We are not doing any check on the 'temperature' value
+ * here. The driver implementing 'set_trip_hyst' has to
+ * take care of this.
+ */
+ ret = tz->ops->set_trip_hyst(tz, trip, temperature);
+
+ return ret ? ret : count;
+}
+
+static ssize_t
+trip_point_hyst_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int trip, ret;
+ unsigned long temperature;
+
+ if (!tz->ops->get_trip_hyst)
+ return -EPERM;
+
+ if (!sscanf(attr->attr.name, "trip_point_%d_hyst", &trip))
+ return -EINVAL;
+
+ ret = tz->ops->get_trip_hyst(tz, trip, &temperature);
+
+ return ret ? ret : sprintf(buf, "%ld\n", temperature);
+}
+
+static ssize_t
+passive_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ struct thermal_cooling_device *cdev = NULL;
+ int state;
+
+ if (!sscanf(buf, "%d\n", &state))
+ return -EINVAL;
+
+ /* sanity check: values below 1000 millicelcius don't make sense
+ * and can cause the system to go into a thermal heart attack
+ */
+ if (state && state < 1000)
+ return -EINVAL;
+
+ if (state && !tz->forced_passive) {
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(cdev, &thermal_cdev_list, node) {
+ if (!strncmp("Processor", cdev->type,
+ sizeof("Processor")))
+ thermal_zone_bind_cooling_device(tz,
+ THERMAL_TRIPS_NONE, cdev,
+ THERMAL_NO_LIMIT,
+ THERMAL_NO_LIMIT);
+ }
+ mutex_unlock(&thermal_list_lock);
+ if (!tz->passive_delay)
+ tz->passive_delay = 1000;
+ } else if (!state && tz->forced_passive) {
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(cdev, &thermal_cdev_list, node) {
+ if (!strncmp("Processor", cdev->type,
+ sizeof("Processor")))
+ thermal_zone_unbind_cooling_device(tz,
+ THERMAL_TRIPS_NONE,
+ cdev);
+ }
+ mutex_unlock(&thermal_list_lock);
+ tz->passive_delay = 0;
+ }
+
+ tz->forced_passive = state;
+
+ thermal_zone_device_update(tz);
+
+ return count;
+}
+
+static ssize_t
+passive_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ return sprintf(buf, "%d\n", tz->forced_passive);
+}
+
+static ssize_t
+policy_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = -EINVAL;
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ struct thermal_governor *gov;
+
+ mutex_lock(&thermal_governor_lock);
+
+ gov = __find_governor(buf);
+ if (!gov)
+ goto exit;
+
+ tz->governor = gov;
+ ret = count;
+
+exit:
+ mutex_unlock(&thermal_governor_lock);
+ return ret;
+}
+
+static ssize_t
+policy_show(struct device *dev, struct device_attribute *devattr, char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ return sprintf(buf, "%s\n", tz->governor->name);
+}
+
+#ifdef CONFIG_THERMAL_EMULATION
+static ssize_t
+emul_temp_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int ret = 0;
+ unsigned long temperature;
+
+ if (kstrtoul(buf, 10, &temperature))
+ return -EINVAL;
+
+ if (!tz->ops->set_emul_temp) {
+ mutex_lock(&tz->lock);
+ tz->emul_temperature = temperature;
+ mutex_unlock(&tz->lock);
+ } else {
+ ret = tz->ops->set_emul_temp(tz, temperature);
+ }
+
+ if (!ret)
+ thermal_zone_device_update(tz);
+
+ return ret ? ret : count;
+}
+static DEVICE_ATTR(emul_temp, S_IWUSR, NULL, emul_temp_store);
+#endif/*CONFIG_THERMAL_EMULATION*/
+
+static DEVICE_ATTR(type, 0444, type_show, NULL);
+static DEVICE_ATTR(temp, 0444, temp_show, NULL);
+static DEVICE_ATTR(mode, 0644, mode_show, mode_store);
+static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store);
+static DEVICE_ATTR(policy, S_IRUGO | S_IWUSR, policy_show, policy_store);
+
+/* sys I/F for cooling device */
+#define to_cooling_device(_dev) \
+ container_of(_dev, struct thermal_cooling_device, device)
+
+static ssize_t
+thermal_cooling_device_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+
+ return sprintf(buf, "%s\n", cdev->type);
+}
+
+static ssize_t
+thermal_cooling_device_max_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ unsigned long state;
+ int ret;
+
+ ret = cdev->ops->get_max_state(cdev, &state);
+ if (ret)
+ return ret;
+ return sprintf(buf, "%ld\n", state);
+}
+
+static ssize_t
+thermal_cooling_device_cur_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ unsigned long state;
+ int ret;
+
+ ret = cdev->ops->get_cur_state(cdev, &state);
+ if (ret)
+ return ret;
+ return sprintf(buf, "%ld\n", state);
+}
+
+static ssize_t
+thermal_cooling_device_cur_state_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ unsigned long state;
+ int result;
+
+ if (!sscanf(buf, "%ld\n", &state))
+ return -EINVAL;
+
+ if ((long)state < 0)
+ return -EINVAL;
+
+ result = cdev->ops->set_cur_state(cdev, state);
+ if (result)
+ return result;
+ return count;
+}
+
+static struct device_attribute dev_attr_cdev_type =
+__ATTR(type, 0444, thermal_cooling_device_type_show, NULL);
+static DEVICE_ATTR(max_state, 0444,
+ thermal_cooling_device_max_state_show, NULL);
+static DEVICE_ATTR(cur_state, 0644,
+ thermal_cooling_device_cur_state_show,
+ thermal_cooling_device_cur_state_store);
+
+static ssize_t
+thermal_cooling_device_trip_point_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct thermal_instance *instance;
+
+ instance =
+ container_of(attr, struct thermal_instance, attr);
+
+ if (instance->trip == THERMAL_TRIPS_NONE)
+ return sprintf(buf, "-1\n");
+ else
+ return sprintf(buf, "%d\n", instance->trip);
+}
+
+/* Device management */
+
+/**
+ * thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone
+ * @tz: pointer to struct thermal_zone_device
+ * @trip: indicates which trip point the cooling devices is
+ * associated with in this thermal zone.
+ * @cdev: pointer to struct thermal_cooling_device
+ * @upper: the Maximum cooling state for this trip point.
+ * THERMAL_NO_LIMIT means no upper limit,
+ * and the cooling device can be in max_state.
+ * @lower: the Minimum cooling state can be used for this trip point.
+ * THERMAL_NO_LIMIT means no lower limit,
+ * and the cooling device can be in cooling state 0.
+ *
+ * This interface function bind a thermal cooling device to the certain trip
+ * point of a thermal zone device.
+ * This function is usually called in the thermal zone device .bind callback.
+ *
+ * Return: 0 on success, the proper error value otherwise.
+ */
+int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
+ int trip,
+ struct thermal_cooling_device *cdev,
+ unsigned long upper, unsigned long lower)
+{
+ struct thermal_instance *dev;
+ struct thermal_instance *pos;
+ struct thermal_zone_device *pos1;
+ struct thermal_cooling_device *pos2;
+ unsigned long max_state;
+ int result;
+
+ if (trip >= tz->trips || (trip < 0 && trip != THERMAL_TRIPS_NONE))
+ return -EINVAL;
+
+ list_for_each_entry(pos1, &thermal_tz_list, node) {
+ if (pos1 == tz)
+ break;
+ }
+ list_for_each_entry(pos2, &thermal_cdev_list, node) {
+ if (pos2 == cdev)
+ break;
+ }
+
+ if (tz != pos1 || cdev != pos2)
+ return -EINVAL;
+
+ cdev->ops->get_max_state(cdev, &max_state);
+
+ /* lower default 0, upper default max_state */
+ lower = lower == THERMAL_NO_LIMIT ? 0 : lower;
+ upper = upper == THERMAL_NO_LIMIT ? max_state : upper;
+
+ if (lower > upper || upper > max_state)
+ return -EINVAL;
+
+ dev =
+ kzalloc(sizeof(struct thermal_instance), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+ dev->tz = tz;
+ dev->cdev = cdev;
+ dev->trip = trip;
+ dev->upper = upper;
+ dev->lower = lower;
+ dev->target = THERMAL_NO_TARGET;
+
+ result = get_idr(&tz->idr, &tz->lock, &dev->id);
+ if (result)
+ goto free_mem;
+
+ sprintf(dev->name, "cdev%d", dev->id);
+ result =
+ sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
+ if (result)
+ goto release_idr;
+
+ sprintf(dev->attr_name, "cdev%d_trip_point", dev->id);
+ sysfs_attr_init(&dev->attr.attr);
+ dev->attr.attr.name = dev->attr_name;
+ dev->attr.attr.mode = 0444;
+ dev->attr.show = thermal_cooling_device_trip_point_show;
+ result = device_create_file(&tz->device, &dev->attr);
+ if (result)
+ goto remove_symbol_link;
+
+ mutex_lock(&tz->lock);
+ mutex_lock(&cdev->lock);
+ list_for_each_entry(pos, &tz->thermal_instances, tz_node)
+ if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
+ result = -EEXIST;
+ break;
+ }
+ if (!result) {
+ list_add_tail(&dev->tz_node, &tz->thermal_instances);
+ list_add_tail(&dev->cdev_node, &cdev->thermal_instances);
+ }
+ mutex_unlock(&cdev->lock);
+ mutex_unlock(&tz->lock);
+
+ if (!result)
+ return 0;
+
+ device_remove_file(&tz->device, &dev->attr);
+remove_symbol_link:
+ sysfs_remove_link(&tz->device.kobj, dev->name);
+release_idr:
+ release_idr(&tz->idr, &tz->lock, dev->id);
+free_mem:
+ kfree(dev);
+ return result;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_bind_cooling_device);
+
+/**
+ * thermal_zone_unbind_cooling_device() - unbind a cooling device from a
+ * thermal zone.
+ * @tz: pointer to a struct thermal_zone_device.
+ * @trip: indicates which trip point the cooling devices is
+ * associated with in this thermal zone.
+ * @cdev: pointer to a struct thermal_cooling_device.
+ *
+ * This interface function unbind a thermal cooling device from the certain
+ * trip point of a thermal zone device.
+ * This function is usually called in the thermal zone device .unbind callback.
+ *
+ * Return: 0 on success, the proper error value otherwise.
+ */
+int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
+ int trip,
+ struct thermal_cooling_device *cdev)
+{
+ struct thermal_instance *pos, *next;
+
+ mutex_lock(&tz->lock);
+ mutex_lock(&cdev->lock);
+ list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) {
+ if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
+ list_del(&pos->tz_node);
+ list_del(&pos->cdev_node);
+ mutex_unlock(&cdev->lock);
+ mutex_unlock(&tz->lock);
+ goto unbind;
+ }
+ }
+ mutex_unlock(&cdev->lock);
+ mutex_unlock(&tz->lock);
+
+ return -ENODEV;
+
+unbind:
+ device_remove_file(&tz->device, &pos->attr);
+ sysfs_remove_link(&tz->device.kobj, pos->name);
+ release_idr(&tz->idr, &tz->lock, pos->id);
+ kfree(pos);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_unbind_cooling_device);
+
+static void thermal_release(struct device *dev)
+{
+ struct thermal_zone_device *tz;
+ struct thermal_cooling_device *cdev;
+
+ if (!strncmp(dev_name(dev), "thermal_zone",
+ sizeof("thermal_zone") - 1)) {
+ tz = to_thermal_zone(dev);
+ kfree(tz);
+ } else {
+ cdev = to_cooling_device(dev);
+ kfree(cdev);
+ }
+}
+
+static struct class thermal_class = {
+ .name = "thermal",
+ .dev_release = thermal_release,
+};
+
+/**
+ * __thermal_cooling_device_register() - register a new thermal cooling device
+ * @np: a pointer to a device tree node.
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ * It also gives the opportunity to link the cooling device to a device tree
+ * node, so that it can be bound to a thermal zone created out of device tree.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+static struct thermal_cooling_device *
+__thermal_cooling_device_register(struct device_node *np,
+ char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+ int result;
+
+ if (type && strlen(type) >= THERMAL_NAME_LENGTH)
+ return ERR_PTR(-EINVAL);
+
+ if (!ops || !ops->get_max_state || !ops->get_cur_state ||
+ !ops->set_cur_state)
+ return ERR_PTR(-EINVAL);
+
+ cdev = kzalloc(sizeof(struct thermal_cooling_device), GFP_KERNEL);
+ if (!cdev)
+ return ERR_PTR(-ENOMEM);
+
+ result = get_idr(&thermal_cdev_idr, &thermal_idr_lock, &cdev->id);
+ if (result) {
+ kfree(cdev);
+ return ERR_PTR(result);
+ }
+
+ strlcpy(cdev->type, type ? : "", sizeof(cdev->type));
+ mutex_init(&cdev->lock);
+ INIT_LIST_HEAD(&cdev->thermal_instances);
+ cdev->np = np;
+ cdev->ops = ops;
+ cdev->updated = false;
+ cdev->device.class = &thermal_class;
+ cdev->devdata = devdata;
+ dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
+ result = device_register(&cdev->device);
+ if (result) {
+ release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
+ kfree(cdev);
+ return ERR_PTR(result);
+ }
+
+ /* sys I/F */
+ if (type) {
+ result = device_create_file(&cdev->device, &dev_attr_cdev_type);
+ if (result)
+ goto unregister;
+ }
+
+ result = device_create_file(&cdev->device, &dev_attr_max_state);
+ if (result)
+ goto unregister;
+
+ result = device_create_file(&cdev->device, &dev_attr_cur_state);
+ if (result)
+ goto unregister;
+
+ /* Add 'this' new cdev to the global cdev list */
+ mutex_lock(&thermal_list_lock);
+ list_add(&cdev->node, &thermal_cdev_list);
+ mutex_unlock(&thermal_list_lock);
+
+ /* Update binding information for 'this' new cdev */
+ bind_cdev(cdev);
+
+ return cdev;
+
+unregister:
+ release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
+ device_unregister(&cdev->device);
+ return ERR_PTR(result);
+}
+
+/**
+ * thermal_cooling_device_register() - register a new thermal cooling device
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+thermal_cooling_device_register(char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return __thermal_cooling_device_register(NULL, type, devdata, ops);
+}
+EXPORT_SYMBOL_GPL(thermal_cooling_device_register);
+
+/**
+ * thermal_of_cooling_device_register() - register an OF thermal cooling device
+ * @np: a pointer to a device tree node.
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This function will register a cooling device with device tree node reference.
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+thermal_of_cooling_device_register(struct device_node *np,
+ char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return __thermal_cooling_device_register(np, type, devdata, ops);
+}
+EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
+
+/**
+ * thermal_cooling_device_unregister - removes the registered thermal cooling device
+ * @cdev: the thermal cooling device to remove.
+ *
+ * thermal_cooling_device_unregister() must be called when the device is no
+ * longer needed.
+ */
+void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
+{
+ int i;
+ const struct thermal_zone_params *tzp;
+ struct thermal_zone_device *tz;
+ struct thermal_cooling_device *pos = NULL;
+
+ if (!cdev)
+ return;
+
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(pos, &thermal_cdev_list, node)
+ if (pos == cdev)
+ break;
+ if (pos != cdev) {
+ /* thermal cooling device not found */
+ mutex_unlock(&thermal_list_lock);
+ return;
+ }
+ list_del(&cdev->node);
+
+ /* Unbind all thermal zones associated with 'this' cdev */
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ if (tz->ops->unbind) {
+ tz->ops->unbind(tz, cdev);
+ continue;
+ }
+
+ if (!tz->tzp || !tz->tzp->tbp)
+ continue;
+
+ tzp = tz->tzp;
+ for (i = 0; i < tzp->num_tbps; i++) {
+ if (tzp->tbp[i].cdev == cdev) {
+ __unbind(tz, tzp->tbp[i].trip_mask, cdev);
+ tzp->tbp[i].cdev = NULL;
+ }
+ }
+ }
+
+ mutex_unlock(&thermal_list_lock);
+
+ if (cdev->type[0])
+ device_remove_file(&cdev->device, &dev_attr_cdev_type);
+ device_remove_file(&cdev->device, &dev_attr_max_state);
+ device_remove_file(&cdev->device, &dev_attr_cur_state);
+
+ release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
+ device_unregister(&cdev->device);
+ return;
+}
+EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
+
+void thermal_cdev_update(struct thermal_cooling_device *cdev)
+{
+ struct thermal_instance *instance;
+ unsigned long target = 0;
+
+ /* cooling device is updated*/
+ if (cdev->updated)
+ return;
+
+ mutex_lock(&cdev->lock);
+ /* Make sure cdev enters the deepest cooling state */
+ list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
+ dev_dbg(&cdev->device, "zone%d->target=%lu\n",
+ instance->tz->id, instance->target);
+ if (instance->target == THERMAL_NO_TARGET)
+ continue;
+ if (instance->target > target)
+ target = instance->target;
+ }
+ mutex_unlock(&cdev->lock);
+ cdev->ops->set_cur_state(cdev, target);
+ cdev->updated = true;
+ dev_dbg(&cdev->device, "set to state %lu\n", target);
+}
+EXPORT_SYMBOL(thermal_cdev_update);
+
+/**
+ * thermal_notify_framework - Sensor drivers use this API to notify framework
+ * @tz: thermal zone device
+ * @trip: indicates which trip point has been crossed
+ *
+ * This function handles the trip events from sensor drivers. It starts
+ * throttling the cooling devices according to the policy configured.
+ * For CRITICAL and HOT trip points, this notifies the respective drivers,
+ * and does actual throttling for other trip points i.e ACTIVE and PASSIVE.
+ * The throttling policy is based on the configured platform data; if no
+ * platform data is provided, this uses the step_wise throttling policy.
+ */
+void thermal_notify_framework(struct thermal_zone_device *tz, int trip)
+{
+ handle_thermal_trip(tz, trip);
+}
+EXPORT_SYMBOL_GPL(thermal_notify_framework);
+
+/**
+ * create_trip_attrs() - create attributes for trip points
+ * @tz: the thermal zone device
+ * @mask: Writeable trip point bitmap.
+ *
+ * helper function to instantiate sysfs entries for every trip
+ * point and its properties of a struct thermal_zone_device.
+ *
+ * Return: 0 on success, the proper error value otherwise.
+ */
+static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
+{
+ int indx;
+ int size = sizeof(struct thermal_attr) * tz->trips;
+
+ tz->trip_type_attrs = kzalloc(size, GFP_KERNEL);
+ if (!tz->trip_type_attrs)
+ return -ENOMEM;
+
+ tz->trip_temp_attrs = kzalloc(size, GFP_KERNEL);
+ if (!tz->trip_temp_attrs) {
+ kfree(tz->trip_type_attrs);
+ return -ENOMEM;
+ }
+
+ if (tz->ops->get_trip_hyst) {
+ tz->trip_hyst_attrs = kzalloc(size, GFP_KERNEL);
+ if (!tz->trip_hyst_attrs) {
+ kfree(tz->trip_type_attrs);
+ kfree(tz->trip_temp_attrs);
+ return -ENOMEM;
+ }
+ }
+
+
+ for (indx = 0; indx < tz->trips; indx++) {
+ /* create trip type attribute */
+ snprintf(tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_type", indx);
+
+ sysfs_attr_init(&tz->trip_type_attrs[indx].attr.attr);
+ tz->trip_type_attrs[indx].attr.attr.name =
+ tz->trip_type_attrs[indx].name;
+ tz->trip_type_attrs[indx].attr.attr.mode = S_IRUGO;
+ tz->trip_type_attrs[indx].attr.show = trip_point_type_show;
+
+ device_create_file(&tz->device,
+ &tz->trip_type_attrs[indx].attr);
+
+ /* create trip temp attribute */
+ snprintf(tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_temp", indx);
+
+ sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr);
+ tz->trip_temp_attrs[indx].attr.attr.name =
+ tz->trip_temp_attrs[indx].name;
+ tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO;
+ tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show;
+ if (mask & (1 << indx)) {
+ tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;
+ tz->trip_temp_attrs[indx].attr.store =
+ trip_point_temp_store;
+ }
+
+ device_create_file(&tz->device,
+ &tz->trip_temp_attrs[indx].attr);
+
+ /* create Optional trip hyst attribute */
+ if (!tz->ops->get_trip_hyst)
+ continue;
+ snprintf(tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_hyst", indx);
+
+ sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr);
+ tz->trip_hyst_attrs[indx].attr.attr.name =
+ tz->trip_hyst_attrs[indx].name;
+ tz->trip_hyst_attrs[indx].attr.attr.mode = S_IRUGO;
+ tz->trip_hyst_attrs[indx].attr.show = trip_point_hyst_show;
+ if (tz->ops->set_trip_hyst) {
+ tz->trip_hyst_attrs[indx].attr.attr.mode |= S_IWUSR;
+ tz->trip_hyst_attrs[indx].attr.store =
+ trip_point_hyst_store;
+ }
+
+ device_create_file(&tz->device,
+ &tz->trip_hyst_attrs[indx].attr);
+ }
+ return 0;
+}
+
+static void remove_trip_attrs(struct thermal_zone_device *tz)
+{
+ int indx;
+
+ for (indx = 0; indx < tz->trips; indx++) {
+ device_remove_file(&tz->device,
+ &tz->trip_type_attrs[indx].attr);
+ device_remove_file(&tz->device,
+ &tz->trip_temp_attrs[indx].attr);
+ if (tz->ops->get_trip_hyst)
+ device_remove_file(&tz->device,
+ &tz->trip_hyst_attrs[indx].attr);
+ }
+ kfree(tz->trip_type_attrs);
+ kfree(tz->trip_temp_attrs);
+ kfree(tz->trip_hyst_attrs);
+}
+
+/**
+ * thermal_zone_device_register() - register a new thermal zone device
+ * @type: the thermal zone device type
+ * @trips: the number of trip points the thermal zone support
+ * @mask: a bit string indicating the writeablility of trip points
+ * @devdata: private device data
+ * @ops: standard thermal zone device callbacks
+ * @tzp: thermal zone platform parameters
+ * @passive_delay: number of milliseconds to wait between polls when
+ * performing passive cooling
+ * @polling_delay: number of milliseconds to wait between polls when checking
+ * whether trip points have been crossed (0 for interrupt
+ * driven systems)
+ *
+ * This interface function adds a new thermal zone device (sensor) to
+ * /sys/class/thermal folder as thermal_zone[0-*]. It tries to bind all the
+ * thermal cooling devices registered at the same time.
+ * thermal_zone_device_unregister() must be called when the device is no
+ * longer needed. The passive cooling depends on the .get_trend() return value.
+ *
+ * Return: a pointer to the created struct thermal_zone_device or an
+ * in case of error, an ERR_PTR. Caller must check return value with
+ * IS_ERR*() helpers.
+ */
+struct thermal_zone_device *thermal_zone_device_register(const char *type,
+ int trips, int mask, void *devdata,
+ struct thermal_zone_device_ops *ops,
+ const struct thermal_zone_params *tzp,
+ int passive_delay, int polling_delay)
+{
+ struct thermal_zone_device *tz;
+ enum thermal_trip_type trip_type;
+ int result;
+ int count;
+ int passive = 0;
+
+ if (type && strlen(type) >= THERMAL_NAME_LENGTH)
+ return ERR_PTR(-EINVAL);
+
+ if (trips > THERMAL_MAX_TRIPS || trips < 0 || mask >> trips)
+ return ERR_PTR(-EINVAL);
+
+ if (!ops)
+ return ERR_PTR(-EINVAL);
+
+ if (trips > 0 && !ops->get_trip_type)
+ return ERR_PTR(-EINVAL);
+
+ tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL);
+ if (!tz)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&tz->thermal_instances);
+ idr_init(&tz->idr);
+ mutex_init(&tz->lock);
+ result = get_idr(&thermal_tz_idr, &thermal_idr_lock, &tz->id);
+ if (result) {
+ kfree(tz);
+ return ERR_PTR(result);
+ }
+
+ strlcpy(tz->type, type ? : "", sizeof(tz->type));
+ tz->ops = ops;
+ tz->tzp = tzp;
+ tz->device.class = &thermal_class;
+ tz->devdata = devdata;
+ tz->trips = trips;
+ tz->passive_delay = passive_delay;
+ tz->polling_delay = polling_delay;
+
+ dev_set_name(&tz->device, "thermal_zone%d", tz->id);
+ result = device_register(&tz->device);
+ if (result) {
+ release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
+ kfree(tz);
+ return ERR_PTR(result);
+ }
+
+ /* sys I/F */
+ if (type) {
+ result = device_create_file(&tz->device, &dev_attr_type);
+ if (result)
+ goto unregister;
+ }
+
+ result = device_create_file(&tz->device, &dev_attr_temp);
+ if (result)
+ goto unregister;
+
+ if (ops->get_mode) {
+ result = device_create_file(&tz->device, &dev_attr_mode);
+ if (result)
+ goto unregister;
+ }
+
+ result = create_trip_attrs(tz, mask);
+ if (result)
+ goto unregister;
+
+ for (count = 0; count < trips; count++) {
+ tz->ops->get_trip_type(tz, count, &trip_type);
+ if (trip_type == THERMAL_TRIP_PASSIVE)
+ passive = 1;
+ }
+
+ if (!passive) {
+ result = device_create_file(&tz->device, &dev_attr_passive);
+ if (result)
+ goto unregister;
+ }
+
+#ifdef CONFIG_THERMAL_EMULATION
+ result = device_create_file(&tz->device, &dev_attr_emul_temp);
+ if (result)
+ goto unregister;
+#endif
+ /* Create policy attribute */
+ result = device_create_file(&tz->device, &dev_attr_policy);
+ if (result)
+ goto unregister;
+
+ /* Update 'this' zone's governor information */
+ mutex_lock(&thermal_governor_lock);
+
+ if (tz->tzp)
+ tz->governor = __find_governor(tz->tzp->governor_name);
+ else
+ tz->governor = def_governor;
+
+ mutex_unlock(&thermal_governor_lock);
+
+ if (!tz->tzp || !tz->tzp->no_hwmon) {
+ result = thermal_add_hwmon_sysfs(tz);
+ if (result)
+ goto unregister;
+ }
+
+ mutex_lock(&thermal_list_lock);
+ list_add_tail(&tz->node, &thermal_tz_list);
+ mutex_unlock(&thermal_list_lock);
+
+ /* Bind cooling devices for this zone */
+ bind_tz(tz);
+
+ INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check);
+
+ if (!tz->ops->get_temp)
+ thermal_zone_device_set_polling(tz, 0);
+
+ thermal_zone_device_update(tz);
+
+ if (!result)
+ return tz;
+
+unregister:
+ release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
+ device_unregister(&tz->device);
+ return ERR_PTR(result);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_register);
+
+/**
+ * thermal_device_unregister - removes the registered thermal zone device
+ * @tz: the thermal zone device to remove
+ */
+void thermal_zone_device_unregister(struct thermal_zone_device *tz)
+{
+ int i;
+ const struct thermal_zone_params *tzp;
+ struct thermal_cooling_device *cdev;
+ struct thermal_zone_device *pos = NULL;
+
+ if (!tz)
+ return;
+
+ tzp = tz->tzp;
+
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(pos, &thermal_tz_list, node)
+ if (pos == tz)
+ break;
+ if (pos != tz) {
+ /* thermal zone device not found */
+ mutex_unlock(&thermal_list_lock);
+ return;
+ }
+ list_del(&tz->node);
+
+ /* Unbind all cdevs associated with 'this' thermal zone */
+ list_for_each_entry(cdev, &thermal_cdev_list, node) {
+ if (tz->ops->unbind) {
+ tz->ops->unbind(tz, cdev);
+ continue;
+ }
+
+ if (!tzp || !tzp->tbp)
+ break;
+
+ for (i = 0; i < tzp->num_tbps; i++) {
+ if (tzp->tbp[i].cdev == cdev) {
+ __unbind(tz, tzp->tbp[i].trip_mask, cdev);
+ tzp->tbp[i].cdev = NULL;
+ }
+ }
+ }
+
+ mutex_unlock(&thermal_list_lock);
+
+ thermal_zone_device_set_polling(tz, 0);
+
+ if (tz->type[0])
+ device_remove_file(&tz->device, &dev_attr_type);
+ device_remove_file(&tz->device, &dev_attr_temp);
+ if (tz->ops->get_mode)
+ device_remove_file(&tz->device, &dev_attr_mode);
+ device_remove_file(&tz->device, &dev_attr_policy);
+ remove_trip_attrs(tz);
+ tz->governor = NULL;
+
+ thermal_remove_hwmon_sysfs(tz);
+ release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
+ idr_destroy(&tz->idr);
+ mutex_destroy(&tz->lock);
+ device_unregister(&tz->device);
+ return;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
+
+/**
+ * thermal_zone_get_zone_by_name() - search for a zone and returns its ref
+ * @name: thermal zone name to fetch the temperature
+ *
+ * When only one zone is found with the passed name, returns a reference to it.
+ *
+ * Return: On success returns a reference to an unique thermal zone with
+ * matching name equals to @name, an ERR_PTR otherwise (-EINVAL for invalid
+ * paramenters, -ENODEV for not found and -EEXIST for multiple matches).
+ */
+struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name)
+{
+ struct thermal_zone_device *pos = NULL, *ref = ERR_PTR(-EINVAL);
+ unsigned int found = 0;
+
+ if (!name)
+ goto exit;
+
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(pos, &thermal_tz_list, node)
+ if (!strnicmp(name, pos->type, THERMAL_NAME_LENGTH)) {
+ found++;
+ ref = pos;
+ }
+ mutex_unlock(&thermal_list_lock);
+
+ /* nothing has been found, thus an error code for it */
+ if (found == 0)
+ ref = ERR_PTR(-ENODEV);
+ else if (found > 1)
+ /* Success only when an unique zone is found */
+ ref = ERR_PTR(-EEXIST);
+
+exit:
+ return ref;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
+
+#ifdef CONFIG_NET
+static struct genl_family thermal_event_genl_family = {
+ .id = GENL_ID_GENERATE,
+ .name = THERMAL_GENL_FAMILY_NAME,
+ .version = THERMAL_GENL_VERSION,
+ .maxattr = THERMAL_GENL_ATTR_MAX,
+};
+
+static struct genl_multicast_group thermal_event_mcgrp = {
+ .name = THERMAL_GENL_MCAST_GROUP_NAME,
+};
+
+int thermal_generate_netlink_event(struct thermal_zone_device *tz,
+ enum events event)
+{
+ struct sk_buff *skb;
+ struct nlattr *attr;
+ struct thermal_genl_event *thermal_event;
+ void *msg_header;
+ int size;
+ int result;
+ static unsigned int thermal_event_seqnum;
+
+ if (!tz)
+ return -EINVAL;
+
+ /* allocate memory */
+ size = nla_total_size(sizeof(struct thermal_genl_event)) +
+ nla_total_size(0);
+
+ skb = genlmsg_new(size, GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ /* add the genetlink message header */
+ msg_header = genlmsg_put(skb, 0, thermal_event_seqnum++,
+ &thermal_event_genl_family, 0,
+ THERMAL_GENL_CMD_EVENT);
+ if (!msg_header) {
+ nlmsg_free(skb);
+ return -ENOMEM;
+ }
+
+ /* fill the data */
+ attr = nla_reserve(skb, THERMAL_GENL_ATTR_EVENT,
+ sizeof(struct thermal_genl_event));
+
+ if (!attr) {
+ nlmsg_free(skb);
+ return -EINVAL;
+ }
+
+ thermal_event = nla_data(attr);
+ if (!thermal_event) {
+ nlmsg_free(skb);
+ return -EINVAL;
+ }
+
+ memset(thermal_event, 0, sizeof(struct thermal_genl_event));
+
+ thermal_event->orig = tz->id;
+ thermal_event->event = event;
+
+ /* send multicast genetlink message */
+ result = genlmsg_end(skb, msg_header);
+ if (result < 0) {
+ nlmsg_free(skb);
+ return result;
+ }
+
+ result = genlmsg_multicast(skb, 0, thermal_event_mcgrp.id, GFP_ATOMIC);
+ if (result)
+ dev_err(&tz->device, "Failed to send netlink event:%d", result);
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(thermal_generate_netlink_event);
+
+static int genetlink_init(void)
+{
+ int result;
+
+ result = genl_register_family(&thermal_event_genl_family);
+ if (result)
+ return result;
+
+ result = genl_register_mc_group(&thermal_event_genl_family,
+ &thermal_event_mcgrp);
+ if (result)
+ genl_unregister_family(&thermal_event_genl_family);
+ return result;
+}
+
+static void genetlink_exit(void)
+{
+ genl_unregister_family(&thermal_event_genl_family);
+}
+#else /* !CONFIG_NET */
+static inline int genetlink_init(void) { return 0; }
+static inline void genetlink_exit(void) {}
+#endif /* !CONFIG_NET */
+
+static int __init thermal_register_governors(void)
+{
+ int result;
+
+ result = thermal_gov_step_wise_register();
+ if (result)
+ return result;
+
+ result = thermal_gov_fair_share_register();
+ if (result)
+ return result;
+
+ return thermal_gov_user_space_register();
+}
+
+static void thermal_unregister_governors(void)
+{
+ thermal_gov_step_wise_unregister();
+ thermal_gov_fair_share_unregister();
+ thermal_gov_user_space_unregister();
+}
+
+static int __init thermal_init(void)
+{
+ int result;
+
+ result = thermal_register_governors();
+ if (result)
+ goto error;
+
+ result = class_register(&thermal_class);
+ if (result)
+ goto unregister_governors;
+
+ result = genetlink_init();
+ if (result)
+ goto unregister_class;
+
+ result = of_parse_thermal_zones();
+ if (result)
+ goto exit_netlink;
+
+ return 0;
+
+exit_netlink:
+ genetlink_exit();
+unregister_governors:
+ thermal_unregister_governors();
+unregister_class:
+ class_unregister(&thermal_class);
+error:
+ idr_destroy(&thermal_tz_idr);
+ idr_destroy(&thermal_cdev_idr);
+ mutex_destroy(&thermal_idr_lock);
+ mutex_destroy(&thermal_list_lock);
+ mutex_destroy(&thermal_governor_lock);
+ return result;
+}
+
+static void __exit thermal_exit(void)
+{
+ of_thermal_destroy_zones();
+ genetlink_exit();
+ class_unregister(&thermal_class);
+ thermal_unregister_governors();
+ idr_destroy(&thermal_tz_idr);
+ idr_destroy(&thermal_cdev_idr);
+ mutex_destroy(&thermal_idr_lock);
+ mutex_destroy(&thermal_list_lock);
+ mutex_destroy(&thermal_governor_lock);
+}
+
+fs_initcall(thermal_init);
+module_exit(thermal_exit);
diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h
new file mode 100644
index 00000000..6bd504e2
--- /dev/null
+++ b/drivers/thermal/thermal_core.h
@@ -0,0 +1,90 @@
+/*
+ * thermal_core.h
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Author: Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __THERMAL_CORE_H__
+#define __THERMAL_CORE_H__
+
+#include <linux/device.h>
+#include <linux/thermal.h>
+
+/* Initial state of a cooling device during binding */
+#define THERMAL_NO_TARGET -1UL
+
+#define TRIP_TEMP_OFFSET 2
+/*
+ * This structure is used to describe the behavior of
+ * a certain cooling device on a certain trip point
+ * in a certain thermal zone
+ */
+struct thermal_instance {
+ int id;
+ char name[THERMAL_NAME_LENGTH];
+ struct thermal_zone_device *tz;
+ struct thermal_cooling_device *cdev;
+ int trip;
+ unsigned long upper; /* Highest cooling state for this trip point */
+ unsigned long lower; /* Lowest cooling state for this trip point */
+ unsigned long target; /* expected cooling state */
+ char attr_name[THERMAL_NAME_LENGTH];
+ struct device_attribute attr;
+ struct list_head tz_node; /* node in tz->thermal_instances */
+ struct list_head cdev_node; /* node in cdev->thermal_instances */
+};
+
+int thermal_register_governor(struct thermal_governor *);
+void thermal_unregister_governor(struct thermal_governor *);
+
+#ifdef CONFIG_THERMAL_GOV_STEP_WISE
+int thermal_gov_step_wise_register(void);
+void thermal_gov_step_wise_unregister(void);
+#else
+static inline int thermal_gov_step_wise_register(void) { return 0; }
+static inline void thermal_gov_step_wise_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_STEP_WISE */
+
+#ifdef CONFIG_THERMAL_GOV_FAIR_SHARE
+int thermal_gov_fair_share_register(void);
+void thermal_gov_fair_share_unregister(void);
+#else
+static inline int thermal_gov_fair_share_register(void) { return 0; }
+static inline void thermal_gov_fair_share_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_FAIR_SHARE */
+
+#ifdef CONFIG_THERMAL_GOV_USER_SPACE
+int thermal_gov_user_space_register(void);
+void thermal_gov_user_space_unregister(void);
+#else
+static inline int thermal_gov_user_space_register(void) { return 0; }
+static inline void thermal_gov_user_space_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_USER_SPACE */
+
+/* device tree support */
+#ifdef CONFIG_THERMAL_OF
+int of_parse_thermal_zones(void);
+void of_thermal_destroy_zones(void);
+#else
+static inline int of_parse_thermal_zones(void) { return 0; }
+static inline void of_thermal_destroy_zones(void) { }
+#endif
+
+#endif /* __THERMAL_CORE_H__ */
diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c
new file mode 100644
index 00000000..fdb07199
--- /dev/null
+++ b/drivers/thermal/thermal_hwmon.c
@@ -0,0 +1,269 @@
+/*
+ * thermal_hwmon.c - Generic Thermal Management hwmon support.
+ *
+ * Code based on Intel thermal_core.c. Copyrights of the original code:
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ *
+ * Copyright (C) 2013 Texas Instruments
+ * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/hwmon.h>
+#include <linux/thermal.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include "thermal_hwmon.h"
+
+/* hwmon sys I/F */
+/* thermal zone devices with the same type share one hwmon device */
+struct thermal_hwmon_device {
+ char type[THERMAL_NAME_LENGTH];
+ struct device *device;
+ int count;
+ struct list_head tz_list;
+ struct list_head node;
+};
+
+struct thermal_hwmon_attr {
+ struct device_attribute attr;
+ char name[16];
+};
+
+/* one temperature input for each thermal zone */
+struct thermal_hwmon_temp {
+ struct list_head hwmon_node;
+ struct thermal_zone_device *tz;
+ struct thermal_hwmon_attr temp_input; /* hwmon sys attr */
+ struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */
+};
+
+static LIST_HEAD(thermal_hwmon_list);
+
+static DEFINE_MUTEX(thermal_hwmon_list_lock);
+
+static ssize_t
+name_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev);
+ return sprintf(buf, "%s\n", hwmon->type);
+}
+static DEVICE_ATTR(name, 0444, name_show, NULL);
+
+static ssize_t
+temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ long temperature;
+ int ret;
+ struct thermal_hwmon_attr *hwmon_attr
+ = container_of(attr, struct thermal_hwmon_attr, attr);
+ struct thermal_hwmon_temp *temp
+ = container_of(hwmon_attr, struct thermal_hwmon_temp,
+ temp_input);
+ struct thermal_zone_device *tz = temp->tz;
+
+ ret = thermal_zone_get_temp(tz, &temperature);
+
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%ld\n", temperature);
+}
+
+static ssize_t
+temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_hwmon_attr *hwmon_attr
+ = container_of(attr, struct thermal_hwmon_attr, attr);
+ struct thermal_hwmon_temp *temp
+ = container_of(hwmon_attr, struct thermal_hwmon_temp,
+ temp_crit);
+ struct thermal_zone_device *tz = temp->tz;
+ long temperature;
+ int ret;
+
+ ret = tz->ops->get_trip_temp(tz, 0, &temperature);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%ld\n", temperature);
+}
+
+
+static struct thermal_hwmon_device *
+thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_device *hwmon;
+
+ mutex_lock(&thermal_hwmon_list_lock);
+ list_for_each_entry(hwmon, &thermal_hwmon_list, node)
+ if (!strcmp(hwmon->type, tz->type)) {
+ mutex_unlock(&thermal_hwmon_list_lock);
+ return hwmon;
+ }
+ mutex_unlock(&thermal_hwmon_list_lock);
+
+ return NULL;
+}
+
+/* Find the temperature input matching a given thermal zone */
+static struct thermal_hwmon_temp *
+thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
+ const struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_temp *temp;
+
+ mutex_lock(&thermal_hwmon_list_lock);
+ list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
+ if (temp->tz == tz) {
+ mutex_unlock(&thermal_hwmon_list_lock);
+ return temp;
+ }
+ mutex_unlock(&thermal_hwmon_list_lock);
+
+ return NULL;
+}
+
+int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_device *hwmon;
+ struct thermal_hwmon_temp *temp;
+ int new_hwmon_device = 1;
+ int result;
+
+ hwmon = thermal_hwmon_lookup_by_type(tz);
+ if (hwmon) {
+ new_hwmon_device = 0;
+ goto register_sys_interface;
+ }
+
+ hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
+ if (!hwmon)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&hwmon->tz_list);
+ strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
+ hwmon->device = hwmon_device_register(NULL);
+ if (IS_ERR(hwmon->device)) {
+ result = PTR_ERR(hwmon->device);
+ goto free_mem;
+ }
+ dev_set_drvdata(hwmon->device, hwmon);
+ result = device_create_file(hwmon->device, &dev_attr_name);
+ if (result)
+ goto free_mem;
+
+ register_sys_interface:
+ temp = kzalloc(sizeof(*temp), GFP_KERNEL);
+ if (!temp) {
+ result = -ENOMEM;
+ goto unregister_name;
+ }
+
+ temp->tz = tz;
+ hwmon->count++;
+
+ snprintf(temp->temp_input.name, sizeof(temp->temp_input.name),
+ "temp%d_input", hwmon->count);
+ temp->temp_input.attr.attr.name = temp->temp_input.name;
+ temp->temp_input.attr.attr.mode = 0444;
+ temp->temp_input.attr.show = temp_input_show;
+ sysfs_attr_init(&temp->temp_input.attr.attr);
+ result = device_create_file(hwmon->device, &temp->temp_input.attr);
+ if (result)
+ goto free_temp_mem;
+
+ if (tz->ops->get_crit_temp) {
+ unsigned long temperature;
+ if (!tz->ops->get_crit_temp(tz, &temperature)) {
+ snprintf(temp->temp_crit.name,
+ sizeof(temp->temp_crit.name),
+ "temp%d_crit", hwmon->count);
+ temp->temp_crit.attr.attr.name = temp->temp_crit.name;
+ temp->temp_crit.attr.attr.mode = 0444;
+ temp->temp_crit.attr.show = temp_crit_show;
+ sysfs_attr_init(&temp->temp_crit.attr.attr);
+ result = device_create_file(hwmon->device,
+ &temp->temp_crit.attr);
+ if (result)
+ goto unregister_input;
+ }
+ }
+
+ mutex_lock(&thermal_hwmon_list_lock);
+ if (new_hwmon_device)
+ list_add_tail(&hwmon->node, &thermal_hwmon_list);
+ list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
+ mutex_unlock(&thermal_hwmon_list_lock);
+
+ return 0;
+
+ unregister_input:
+ device_remove_file(hwmon->device, &temp->temp_input.attr);
+ free_temp_mem:
+ kfree(temp);
+ unregister_name:
+ if (new_hwmon_device) {
+ device_remove_file(hwmon->device, &dev_attr_name);
+ hwmon_device_unregister(hwmon->device);
+ }
+ free_mem:
+ if (new_hwmon_device)
+ kfree(hwmon);
+
+ return result;
+}
+
+void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_device *hwmon;
+ struct thermal_hwmon_temp *temp;
+
+ hwmon = thermal_hwmon_lookup_by_type(tz);
+ if (unlikely(!hwmon)) {
+ /* Should never happen... */
+ dev_dbg(&tz->device, "hwmon device lookup failed!\n");
+ return;
+ }
+
+ temp = thermal_hwmon_lookup_temp(hwmon, tz);
+ if (unlikely(!temp)) {
+ /* Should never happen... */
+ dev_dbg(&tz->device, "temperature input lookup failed!\n");
+ return;
+ }
+
+ device_remove_file(hwmon->device, &temp->temp_input.attr);
+ if (tz->ops->get_crit_temp)
+ device_remove_file(hwmon->device, &temp->temp_crit.attr);
+
+ mutex_lock(&thermal_hwmon_list_lock);
+ list_del(&temp->hwmon_node);
+ kfree(temp);
+ if (!list_empty(&hwmon->tz_list)) {
+ mutex_unlock(&thermal_hwmon_list_lock);
+ return;
+ }
+ list_del(&hwmon->node);
+ mutex_unlock(&thermal_hwmon_list_lock);
+
+ device_remove_file(hwmon->device, &dev_attr_name);
+ hwmon_device_unregister(hwmon->device);
+ kfree(hwmon);
+}
diff --git a/drivers/thermal/thermal_hwmon.h b/drivers/thermal/thermal_hwmon.h
new file mode 100644
index 00000000..c798fdb2
--- /dev/null
+++ b/drivers/thermal/thermal_hwmon.h
@@ -0,0 +1,49 @@
+/*
+ * thermal_hwmon.h - Generic Thermal Management hwmon support.
+ *
+ * Code based on Intel thermal_core.c. Copyrights of the original code:
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ *
+ * Copyright (C) 2013 Texas Instruments
+ * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#ifndef __THERMAL_HWMON_H__
+#define __THERMAL_HWMON_H__
+
+#include <linux/thermal.h>
+
+#ifdef CONFIG_THERMAL_HWMON
+int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz);
+void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz);
+#else
+static int
+thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
+static void
+thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+}
+#endif
+
+#endif /* __THERMAL_HWMON_H__ */
diff --git a/drivers/thermal/thm.h b/drivers/thermal/thm.h
new file mode 100644
index 00000000..1f212247
--- /dev/null
+++ b/drivers/thermal/thm.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ ************************************************
+ * Automatically generated C config: don't edit *
+ ************************************************
+ */
+
+#ifndef __CTL_THM_H__
+#define __CTL_THM_H__
+#include <linux/types.h>
+#include <linux/sprd_thm.h>
+#include <linux/interrupt.h>
+#define THM_TEST
+struct sprd_thermal_zone {
+ struct thermal_zone_device *therm_dev;
+ struct mutex th_lock;
+ struct work_struct therm_work;
+ struct delayed_work resume_delay_work;
+ struct delayed_work thm_read_work;
+ struct sprd_thm_platform_data *trip_tab;
+ struct sprd_board_sensor_config *sensor_config;
+ struct thm_handle_ops *ops;
+ struct delayed_work thm_logtime_work;
+ enum thermal_device_mode mode;
+ int sensor_id;
+ int thm_cal;
+ int off_set;
+ int temp_inteval;
+ int logtime;
+ int thmlog_switch;
+ struct regmap * thm_glb;
+ struct spi_device *spi_dev;
+ unsigned long reg_base;
+ unsigned long cur_temp;
+ unsigned long last_temp;
+ int current_trip_num;
+ int sen_cal_offset;
+ char thermal_zone_name[30];
+ enum thermal_trend trend_val;
+};
+
+enum thermal_log_switch{
+ THERMAL_LOG_DISABLED = 0,
+ THERMAL_LOG_ENABLED,
+};
+struct thm_handle_ops{
+ int (*hw_init)(struct sprd_thermal_zone*);
+ int (*get_reg_base)(struct sprd_thermal_zone*,struct resource *regs);
+ unsigned long (*read_temp)(struct sprd_thermal_zone*);
+ int (*get_trend)(struct sprd_thermal_zone*,int, enum thermal_trend *);
+ int (*get_hyst)(struct sprd_thermal_zone*,int,unsigned long *);
+ int (*irq_handle)(struct sprd_thermal_zone*);
+ int (*suspend)(struct sprd_thermal_zone*);
+ int (*resume)(struct sprd_thermal_zone*);
+ int (*reg_debug_set)(struct sprd_thermal_zone*, unsigned int*);
+ int (*reg_debug_get)(struct sprd_thermal_zone*, unsigned int*);
+ int (*trip_debug_set)(struct sprd_thermal_zone*,int );
+};
+extern int sprd_thm_add(struct thm_handle_ops* ops, char *p,int id);
+extern void sprd_thm_delete(int id);
+extern int sci_efuse_thermal_cal_get(int *cal);
+extern int sci_efuse_arm_thm_cal_get(int *cal,int *offset);
+extern int sci_efuse_bcore_thm_cal_get(int *cal,int *offset);
+extern int sprd_boardthm_add(struct thm_handle_ops* ops, char *p,int id);
+extern void sprd_boardthm_delete(int id);
+#endif
diff --git a/drivers/thermal/user_space.c b/drivers/thermal/user_space.c
new file mode 100644
index 00000000..10adcddc
--- /dev/null
+++ b/drivers/thermal/user_space.c
@@ -0,0 +1,57 @@
+/*
+ * user_space.c - A simple user space Thermal events notifier
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/thermal.h>
+
+#include "thermal_core.h"
+
+/**
+ * notify_user_space - Notifies user space about thermal events
+ * @tz - thermal_zone_device
+ *
+ * This function notifies the user space through UEvents.
+ */
+static int notify_user_space(struct thermal_zone_device *tz, int trip)
+{
+ mutex_lock(&tz->lock);
+ kobject_uevent(&tz->device.kobj, KOBJ_CHANGE);
+ mutex_unlock(&tz->lock);
+ return 0;
+}
+
+static struct thermal_governor thermal_gov_user_space = {
+ .name = "user_space",
+ .throttle = notify_user_space,
+};
+
+int thermal_gov_user_space_register(void)
+{
+ return thermal_register_governor(&thermal_gov_user_space);
+}
+
+void thermal_gov_user_space_unregister(void)
+{
+ thermal_unregister_governor(&thermal_gov_user_space);
+}
+