diff options
Diffstat (limited to 'drivers/thermal')
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, ¬ify_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, ¶m); + + while (true == clamping && !kthread_should_stop() && + cpu_online(cpunr)) { + int sleeptime; + unsigned long target_jiffies; + unsigned int guard; + unsigned int compensation = 0; + int interval; /* jiffies to sleep for each attempt */ + unsigned int duration_jiffies = msecs_to_jiffies(duration); + unsigned int window_size_now; + + try_to_freeze(); + /* + * make sure user selected ratio does not take effect until + * the next round. adjust target_ratio if user has changed + * target such that we can converge quickly. + */ + target_ratio = set_target_ratio; + guard = 1 + target_ratio/20; + window_size_now = window_size; + count++; + + /* + * systems may have different ability to enter package level + * c-states, thus we need to compensate the injected idle ratio + * to achieve the actual target reported by the HW. + */ + compensation = get_compensation(target_ratio); + interval = duration_jiffies*100/(target_ratio+compensation); + + /* align idle time */ + target_jiffies = roundup(jiffies, interval); + sleeptime = target_jiffies - jiffies; + if (sleeptime <= 0) + sleeptime = 1; + schedule_timeout_interruptible(sleeptime); + /* + * only elected controlling cpu can collect stats and update + * control parameters. + */ + if (cpunr == control_cpu && !(count%window_size_now)) { + should_skip = + powerclamp_adjust_controls(target_ratio, + guard, window_size_now); + smp_mb(); + } + + if (should_skip) + continue; + + target_jiffies = jiffies + duration_jiffies; + mod_timer(&wakeup_timer, target_jiffies); + if (unlikely(local_softirq_pending())) + continue; + /* + * stop tick sched during idle time, interrupts are still + * allowed. thus jiffies are updated properly. + */ + preempt_disable(); + tick_nohz_idle_enter(); + /* mwait until target jiffies is reached */ + while (time_before(jiffies, target_jiffies)) { + unsigned long ecx = 1; + unsigned long eax = target_mwait; + + /* + * REVISIT: may call enter_idle() to notify drivers who + * can save power during cpu idle. same for exit_idle() + */ + local_touch_nmi(); + stop_critical_timings(); + __monitor((void *)¤t_thread_info()->flags, 0, 0); + cpu_relax(); /* allow HT sibling to run */ + __mwait(eax, ecx); + start_critical_timings(); + atomic_inc(&idle_wakeup_counter); + } + tick_nohz_idle_exit(); + preempt_enable_no_resched(); + } + del_timer_sync(&wakeup_timer); + clear_bit(cpunr, cpu_clamping_mask); + + return 0; +} + +/* + * 1 HZ polling while clamping is active, useful for userspace + * to monitor actual idle ratio. + */ +static void poll_pkg_cstate(struct work_struct *dummy); +static DECLARE_DELAYED_WORK(poll_pkg_cstate_work, poll_pkg_cstate); +static void poll_pkg_cstate(struct work_struct *dummy) +{ + static u64 msr_last; + static u64 tsc_last; + static unsigned long jiffies_last; + + u64 msr_now; + unsigned long jiffies_now; + u64 tsc_now; + u64 val64; + + msr_now = pkg_state_counter(); + rdtscll(tsc_now); + jiffies_now = jiffies; + + /* calculate pkg cstate vs tsc ratio */ + if (!msr_last || !tsc_last) + pkg_cstate_ratio_cur = 1; + else { + if (tsc_now - tsc_last) { + val64 = 100 * (msr_now - msr_last); + do_div(val64, (tsc_now - tsc_last)); + pkg_cstate_ratio_cur = val64; + } + } + + /* update record */ + msr_last = msr_now; + jiffies_last = jiffies_now; + tsc_last = tsc_now; + + if (true == clamping) + schedule_delayed_work(&poll_pkg_cstate_work, HZ); +} + +static int start_power_clamp(void) +{ + unsigned long cpu; + struct task_struct *thread; + + /* check if pkg cstate counter is completely 0, abort in this case */ + if (!pkg_state_counter()) { + pr_err("pkg cstate counter not functional, abort\n"); + return -EINVAL; + } + + set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1); + /* prevent cpu hotplug */ + get_online_cpus(); + + /* prefer BSP */ + control_cpu = 0; + if (!cpu_online(control_cpu)) + control_cpu = smp_processor_id(); + + clamping = true; + schedule_delayed_work(&poll_pkg_cstate_work, 0); + + /* start one thread per online cpu */ + for_each_online_cpu(cpu) { + struct task_struct **p = + per_cpu_ptr(powerclamp_thread, cpu); + + thread = kthread_create_on_node(clamp_thread, + (void *) cpu, + cpu_to_node(cpu), + "kidle_inject/%ld", cpu); + /* bind to cpu here */ + if (likely(!IS_ERR(thread))) { + kthread_bind(thread, cpu); + wake_up_process(thread); + *p = thread; + } + + } + put_online_cpus(); + + return 0; +} + +static void end_power_clamp(void) +{ + int i; + struct task_struct *thread; + + clamping = false; + /* + * make clamping visible to other cpus and give per cpu clamping threads + * sometime to exit, or gets killed later. + */ + smp_mb(); + msleep(20); + if (bitmap_weight(cpu_clamping_mask, num_possible_cpus())) { + for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) { + pr_debug("clamping thread for cpu %d alive, kill\n", i); + thread = *per_cpu_ptr(powerclamp_thread, i); + kthread_stop(thread); + } + } +} + +static int powerclamp_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + unsigned long cpu = (unsigned long)hcpu; + struct task_struct *thread; + struct task_struct **percpu_thread = + per_cpu_ptr(powerclamp_thread, cpu); + + if (false == clamping) + goto exit_ok; + + switch (action) { + case CPU_ONLINE: + thread = kthread_create_on_node(clamp_thread, + (void *) cpu, + cpu_to_node(cpu), + "kidle_inject/%lu", cpu); + if (likely(!IS_ERR(thread))) { + kthread_bind(thread, cpu); + wake_up_process(thread); + *percpu_thread = thread; + } + /* prefer BSP as controlling CPU */ + if (cpu == 0) { + control_cpu = 0; + smp_mb(); + } + break; + case CPU_DEAD: + if (test_bit(cpu, cpu_clamping_mask)) { + pr_err("cpu %lu dead but powerclamping thread is not\n", + cpu); + kthread_stop(*percpu_thread); + } + if (cpu == control_cpu) { + control_cpu = smp_processor_id(); + smp_mb(); + } + } + +exit_ok: + return NOTIFY_OK; +} + +static struct notifier_block powerclamp_cpu_notifier = { + .notifier_call = powerclamp_cpu_callback, +}; + +static int powerclamp_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = MAX_TARGET_RATIO; + + return 0; +} + +static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + if (true == clamping) + *state = pkg_cstate_ratio_cur; + else + /* to save power, do not poll idle ratio while not clamping */ + *state = -1; /* indicates invalid state */ + + return 0; +} + +static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long new_target_ratio) +{ + int ret = 0; + + new_target_ratio = clamp(new_target_ratio, 0UL, + (unsigned long) (MAX_TARGET_RATIO-1)); + if (set_target_ratio == 0 && new_target_ratio > 0) { + pr_info("Start idle injection to reduce power\n"); + set_target_ratio = new_target_ratio; + ret = start_power_clamp(); + goto exit_set; + } else if (set_target_ratio > 0 && new_target_ratio == 0) { + pr_info("Stop forced idle injection\n"); + set_target_ratio = 0; + end_power_clamp(); + } else /* adjust currently running */ { + set_target_ratio = new_target_ratio; + /* make new set_target_ratio visible to other cpus */ + smp_mb(); + } + +exit_set: + return ret; +} + +/* bind to generic thermal layer as cooling device*/ +static struct thermal_cooling_device_ops powerclamp_cooling_ops = { + .get_max_state = powerclamp_get_max_state, + .get_cur_state = powerclamp_get_cur_state, + .set_cur_state = powerclamp_set_cur_state, +}; + +/* runs on Nehalem and later */ +static const struct x86_cpu_id intel_powerclamp_ids[] = { + { X86_VENDOR_INTEL, 6, 0x1a}, + { X86_VENDOR_INTEL, 6, 0x1c}, + { X86_VENDOR_INTEL, 6, 0x1e}, + { X86_VENDOR_INTEL, 6, 0x1f}, + { X86_VENDOR_INTEL, 6, 0x25}, + { X86_VENDOR_INTEL, 6, 0x26}, + { X86_VENDOR_INTEL, 6, 0x2a}, + { X86_VENDOR_INTEL, 6, 0x2c}, + { X86_VENDOR_INTEL, 6, 0x2d}, + { X86_VENDOR_INTEL, 6, 0x2e}, + { X86_VENDOR_INTEL, 6, 0x2f}, + { X86_VENDOR_INTEL, 6, 0x3a}, + {} +}; +MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids); + +static int powerclamp_probe(void) +{ + if (!x86_match_cpu(intel_powerclamp_ids)) { + pr_err("Intel powerclamp does not run on family %d model %d\n", + boot_cpu_data.x86, boot_cpu_data.x86_model); + return -ENODEV; + } + if (!boot_cpu_has(X86_FEATURE_NONSTOP_TSC) || + !boot_cpu_has(X86_FEATURE_CONSTANT_TSC) || + !boot_cpu_has(X86_FEATURE_MWAIT) || + !boot_cpu_has(X86_FEATURE_ARAT)) + return -ENODEV; + + /* find the deepest mwait value */ + find_target_mwait(); + + return 0; +} + +static int powerclamp_debug_show(struct seq_file *m, void *unused) +{ + int i = 0; + + seq_printf(m, "controlling cpu: %d\n", control_cpu); + seq_printf(m, "pct confidence steady dynamic (compensation)\n"); + for (i = 0; i < MAX_TARGET_RATIO; i++) { + seq_printf(m, "%d\t%lu\t%lu\t%lu\n", + i, + cal_data[i].confidence, + cal_data[i].steady_comp, + cal_data[i].dynamic_comp); + } + + return 0; +} + +static int powerclamp_debug_open(struct inode *inode, + struct file *file) +{ + return single_open(file, powerclamp_debug_show, inode->i_private); +} + +static const struct file_operations powerclamp_debug_fops = { + .open = powerclamp_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static inline void powerclamp_create_debug_files(void) +{ + debug_dir = debugfs_create_dir("intel_powerclamp", NULL); + if (!debug_dir) + return; + + if (!debugfs_create_file("powerclamp_calib", S_IRUGO, debug_dir, + cal_data, &powerclamp_debug_fops)) + goto file_error; + + return; + +file_error: + debugfs_remove_recursive(debug_dir); +} + +static int powerclamp_init(void) +{ + int retval; + int bitmap_size; + + bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long); + cpu_clamping_mask = kzalloc(bitmap_size, GFP_KERNEL); + if (!cpu_clamping_mask) + return -ENOMEM; + + /* probe cpu features and ids here */ + retval = powerclamp_probe(); + if (retval) + return retval; + /* set default limit, maybe adjusted during runtime based on feedback */ + window_size = 2; + register_hotcpu_notifier(&powerclamp_cpu_notifier); + powerclamp_thread = alloc_percpu(struct task_struct *); + cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL, + &powerclamp_cooling_ops); + if (IS_ERR(cooling_dev)) + return -ENODEV; + + if (!duration) + duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES); + powerclamp_create_debug_files(); + + return 0; +} +module_init(powerclamp_init); + +static void powerclamp_exit(void) +{ + unregister_hotcpu_notifier(&powerclamp_cpu_notifier); + end_power_clamp(); + free_percpu(powerclamp_thread); + thermal_cooling_device_unregister(cooling_dev); + kfree(cpu_clamping_mask); + + cancel_delayed_work_sync(&poll_pkg_cstate_work); + debugfs_remove_recursive(debug_dir); +} +module_exit(powerclamp_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>"); +MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>"); +MODULE_DESCRIPTION("Package Level C-state Idle Injection for Intel CPUs"); diff --git a/drivers/thermal/kirkwood_thermal.c b/drivers/thermal/kirkwood_thermal.c new file mode 100644 index 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", ®s[0],®s[1],®s[2],®s[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); +} + |
