diff options
Diffstat (limited to 'drivers/thermal')
45 files changed, 15826 insertions, 1246 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index bf7c687519e..f9a13867cb7 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -13,12 +13,234 @@ menuconfig THERMAL  	  All platforms with ACPI thermal support can use this driver.  	  If you want this support, you should say Y or M here. +if THERMAL +  config THERMAL_HWMON -	bool "Hardware monitoring support" -	depends on THERMAL +	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 +	  platform 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 +	  governor. + +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 +	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 IMX_THERMAL +	tristate "Temperature sensor driver for Freescale i.MX SoCs" +	depends on CPU_THERMAL +	depends on MFD_SYSCON +	depends on OF +	help +	  Support for Temperature Monitor (TEMPMON) found on Freescale i.MX SoCs. +	  It supports one critical trip point and one passive trip point.  The +	  cpufreq is used as the cooling device to throttle CPUs when the +	  passive trip is crossed. + +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 || COMPILE_TEST +	depends on HAS_IOMEM +	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 || MACH_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 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 -	  The generic thermal sysfs driver's hardware monitoring support -	  requires a 2.10.7/3.0.2 or later lm-sensors userspace. +	  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 X86_PKG_TEMP_THERMAL +	tristate "X86 package temperature thermal driver" +	depends on X86_THERMAL_VECTOR +	select THERMAL_GOV_USER_SPACE +	default m +	help +	  Enable this to register CPU digital sensor for package temperature as +	  thermal zone. Each package will have its own thermal zone. There are +	  two trip points which can be set by user to get notifications via thermal +	  notification methods. + +config ACPI_INT3403_THERMAL +	tristate "ACPI INT3403 thermal driver" +	depends on X86 && ACPI +	help +	  Newer laptops and tablets that use ACPI may have thermal sensors +	  outside the core CPU/SOC for thermal safety reasons. These +	  temperature sensors are also exposed for the OS to use via the so +	  called INT3403 ACPI object. This driver will, on devices that have +	  such sensors, expose the temperature information from these sensors +	  to userspace via the normal thermal framework. This means that a wide +	  range of applications and GUI widgets can show this information to +	  the user or use this information for making decisions. For example, +	  the Intel Thermal Daemon can use this information to allow the user +	  to select his laptop to run without turning on the fans. + +config INTEL_SOC_DTS_THERMAL +	tristate "Intel SoCs DTS thermal driver" +	depends on X86 && IOSF_MBI +	help +	  Enable this to register Intel SoCs (e.g. Bay Trail) platform digital +	  temperature sensor (DTS). These SoCs have two additional DTSs in +	  addition to DTSs on CPU cores. Each DTS will be registered as a +	  thermal zone. There are two trip points. One of the trip point can +	  be set by user mode programs to get notifications via Linux thermal +	  notification methods.The other trip is a critical trip point, which +	  was set by the driver based on the TJ MAX temperature. + +menu "Texas Instruments thermal drivers" +source "drivers/thermal/ti-soc-thermal/Kconfig" +endmenu + +menu "Samsung thermal drivers" +depends on ARCH_EXYNOS +source "drivers/thermal/samsung/Kconfig" +endmenu -	  Say Y if your user-space is new enough. +endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 31108a01c22..de0636a57a6 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -3,3 +3,32 @@  #  obj-$(CONFIG_THERMAL)		+= thermal_sys.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 + +# 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-y				+= samsung/ +obj-$(CONFIG_DOVE_THERMAL)  	+= dove_thermal.o +obj-$(CONFIG_DB8500_THERMAL)	+= db8500_thermal.o +obj-$(CONFIG_ARMADA_THERMAL)	+= armada_thermal.o +obj-$(CONFIG_IMX_THERMAL)	+= imx_thermal.o +obj-$(CONFIG_DB8500_CPUFREQ_COOLING)	+= db8500_cpufreq_cooling.o +obj-$(CONFIG_INTEL_POWERCLAMP)	+= intel_powerclamp.o +obj-$(CONFIG_X86_PKG_TEMP_THERMAL)	+= x86_pkg_temp_thermal.o +obj-$(CONFIG_INTEL_SOC_DTS_THERMAL)	+= intel_soc_dts_thermal.o +obj-$(CONFIG_TI_SOC_THERMAL)	+= ti-soc-thermal/ +obj-$(CONFIG_ACPI_INT3403_THERMAL)	+= int3403_thermal.o diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c new file mode 100644 index 00000000000..9d1420acb39 --- /dev/null +++ b/drivers/thermal/armada_thermal.c @@ -0,0 +1,341 @@ +/* + * 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_MASK		0x1 + +/* 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) + +#define A375_Z1_CAL_RESET_LSB		0x8011e214 +#define A375_Z1_CAL_RESET_MSB		0x30a88019 +#define A375_Z1_WORKAROUND_BIT		BIT(9) + +#define A375_UNIT_CONTROL_SHIFT		27 +#define A375_UNIT_CONTROL_MASK		0x7 +#define A375_READOUT_INVERT		BIT(15) +#define A375_HW_RESETn			BIT(8) +#define A380_HW_RESET			BIT(8) + +struct armada_thermal_data; + +/* Marvell EBU Thermal Sensor Dev Structure */ +struct armada_thermal_priv { +	void __iomem *sensor; +	void __iomem *control; +	struct armada_thermal_data *data; +}; + +struct armada_thermal_data { +	/* Initialize the sensor */ +	void (*init_sensor)(struct platform_device *pdev, +			    struct armada_thermal_priv *); + +	/* Test for a valid sensor value (optional) */ +	bool (*is_valid)(struct armada_thermal_priv *); + +	/* Formula coeficients: temp = (b + m * reg) / div */ +	unsigned long coef_b; +	unsigned long coef_m; +	unsigned long coef_div; +	bool inverted; + +	/* Register shift and mask to access the sensor temperature */ +	unsigned int temp_shift; +	unsigned int temp_mask; +	unsigned int is_valid_shift; +}; + +static void armadaxp_init_sensor(struct platform_device *pdev, +				 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 platform_device *pdev, +				  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 void armada375_init_sensor(struct platform_device *pdev, +				  struct armada_thermal_priv *priv) +{ +	unsigned long reg; +	bool quirk_needed = +		!!of_device_is_compatible(pdev->dev.of_node, +					  "marvell,armada375-z1-thermal"); + +	if (quirk_needed) { +		/* Ensure these registers have the default (reset) values */ +		writel(A375_Z1_CAL_RESET_LSB, priv->control); +		writel(A375_Z1_CAL_RESET_MSB, priv->control + 0x4); +	} + +	reg = readl(priv->control + 4); +	reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT); +	reg &= ~A375_READOUT_INVERT; +	reg &= ~A375_HW_RESETn; + +	if (quirk_needed) +		reg |= A375_Z1_WORKAROUND_BIT; + +	writel(reg, priv->control + 4); +	mdelay(20); + +	reg |= A375_HW_RESETn; +	writel(reg, priv->control + 4); +	mdelay(50); +} + +static void armada380_init_sensor(struct platform_device *pdev, +				  struct armada_thermal_priv *priv) +{ +	unsigned long reg = readl_relaxed(priv->control); + +	/* Reset hardware once */ +	if (!(reg & A380_HW_RESET)) { +		reg |= A380_HW_RESET; +		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 >> priv->data->is_valid_shift) & 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; +	unsigned long m, b, div; + +	/* Valid check */ +	if (priv->data->is_valid && !priv->data->is_valid(priv)) { +		dev_err(&thermal->device, +			"Temperature sensor reading not valid\n"); +		return -EIO; +	} + +	reg = readl_relaxed(priv->sensor); +	reg = (reg >> priv->data->temp_shift) & priv->data->temp_mask; + +	/* Get formula coeficients */ +	b = priv->data->coef_b; +	m = priv->data->coef_m; +	div = priv->data->coef_div; + +	if (priv->data->inverted) +		*temp = ((m * reg) - b) / div; +	else +		*temp = (b - (m * reg)) / div; +	return 0; +} + +static struct thermal_zone_device_ops ops = { +	.get_temp = armada_get_temp, +}; + +static const struct armada_thermal_data armadaxp_data = { +	.init_sensor = armadaxp_init_sensor, +	.temp_shift = 10, +	.temp_mask = 0x1ff, +	.coef_b = 3153000000UL, +	.coef_m = 10000000UL, +	.coef_div = 13825, +}; + +static const struct armada_thermal_data armada370_data = { +	.is_valid = armada_is_valid, +	.init_sensor = armada370_init_sensor, +	.is_valid_shift = 9, +	.temp_shift = 10, +	.temp_mask = 0x1ff, +	.coef_b = 3153000000UL, +	.coef_m = 10000000UL, +	.coef_div = 13825, +}; + +static const struct armada_thermal_data armada375_data = { +	.is_valid = armada_is_valid, +	.init_sensor = armada375_init_sensor, +	.is_valid_shift = 10, +	.temp_shift = 0, +	.temp_mask = 0x1ff, +	.coef_b = 3171900000UL, +	.coef_m = 10000000UL, +	.coef_div = 13616, +}; + +static const struct armada_thermal_data armada380_data = { +	.is_valid = armada_is_valid, +	.init_sensor = armada380_init_sensor, +	.is_valid_shift = 10, +	.temp_shift = 0, +	.temp_mask = 0x3ff, +	.coef_b = 1169498786UL, +	.coef_m = 2000000UL, +	.coef_div = 4289, +	.inverted = true, +}; + +static const struct of_device_id armada_thermal_id_table[] = { +	{ +		.compatible = "marvell,armadaxp-thermal", +		.data       = &armadaxp_data, +	}, +	{ +		.compatible = "marvell,armada370-thermal", +		.data       = &armada370_data, +	}, +	{ +		.compatible = "marvell,armada375-thermal", +		.data       = &armada375_data, +	}, +	{ +		.compatible = "marvell,armada375-z1-thermal", +		.data       = &armada375_data, +	}, +	{ +		.compatible = "marvell,armada380-thermal", +		.data       = &armada380_data, +	}, +	{ +		/* 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->data = (struct armada_thermal_data *)match->data; +	priv->data->init_sensor(pdev, 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); + +	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 = 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 00000000000..84a75f89bf7 --- /dev/null +++ b/drivers/thermal/cpu_cooling.c @@ -0,0 +1,564 @@ +/* + *  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; +	unsigned long max_level = 0, level = 0; +	unsigned int freq = CPUFREQ_ENTRY_INVALID; +	int descend = -1; +	struct cpufreq_frequency_table *pos, *table = +					cpufreq_frequency_get_table(cpu); + +	if (!output) +		return -EINVAL; + +	if (!table) +		return -EINVAL; + +	cpufreq_for_each_valid_entry(pos, table) { +		/* ignore duplicate entry */ +		if (freq == pos->frequency) +			continue; + +		/* get the frequency order */ +		if (freq != CPUFREQ_ENTRY_INVALID && descend == -1) +			descend = freq > pos->frequency; + +		freq = pos->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); + +	i = 0; +	cpufreq_for_each_valid_entry(pos, table) { +		/* ignore duplicate entry */ +		if (freq == pos->frequency) +			continue; + +		/* now we have a valid frequency entry */ +		freq = pos->frequency; + +		if (property == GET_LEVEL && (unsigned int)input == freq) { +			/* get level by frequency */ +			*output = descend ? i : (max_level - i); +			return 0; +		} +		if (property == GET_FREQ && level == i) { +			/* get frequency by level */ +			*output = freq; +			return 0; +		} +		i++; +	} + +	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 00000000000..786d19263ab --- /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 00000000000..1e3b3bf9f99 --- /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 00000000000..828f5e345c3 --- /dev/null +++ b/drivers/thermal/dove_thermal.c @@ -0,0 +1,196 @@ +/* + * 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; + +	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); + +	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); + +	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 = 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/fair_share.c b/drivers/thermal/fair_share.c new file mode 100644 index 00000000000..944ba2f340c --- /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/imx_thermal.c b/drivers/thermal/imx_thermal.c new file mode 100644 index 00000000000..2c516f2eebe --- /dev/null +++ b/drivers/thermal/imx_thermal.c @@ -0,0 +1,574 @@ +/* + * Copyright 2013 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/clk.h> +#include <linux/cpu_cooling.h> +#include <linux/cpufreq.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/thermal.h> +#include <linux/types.h> + +#define REG_SET		0x4 +#define REG_CLR		0x8 +#define REG_TOG		0xc + +#define MISC0				0x0150 +#define MISC0_REFTOP_SELBIASOFF		(1 << 3) + +#define TEMPSENSE0			0x0180 +#define TEMPSENSE0_ALARM_VALUE_SHIFT	20 +#define TEMPSENSE0_ALARM_VALUE_MASK	(0xfff << TEMPSENSE0_ALARM_VALUE_SHIFT) +#define TEMPSENSE0_TEMP_CNT_SHIFT	8 +#define TEMPSENSE0_TEMP_CNT_MASK	(0xfff << TEMPSENSE0_TEMP_CNT_SHIFT) +#define TEMPSENSE0_FINISHED		(1 << 2) +#define TEMPSENSE0_MEASURE_TEMP		(1 << 1) +#define TEMPSENSE0_POWER_DOWN		(1 << 0) + +#define TEMPSENSE1			0x0190 +#define TEMPSENSE1_MEASURE_FREQ		0xffff + +#define OCOTP_ANA1			0x04e0 + +/* The driver supports 1 passive trip point and 1 critical trip point */ +enum imx_thermal_trip { +	IMX_TRIP_PASSIVE, +	IMX_TRIP_CRITICAL, +	IMX_TRIP_NUM, +}; + +/* + * It defines the temperature in millicelsius for passive trip point + * that will trigger cooling action when crossed. + */ +#define IMX_TEMP_PASSIVE		85000 + +#define IMX_POLLING_DELAY		2000 /* millisecond */ +#define IMX_PASSIVE_DELAY		1000 + +#define FACTOR0				10000000 +#define FACTOR1				15976 +#define FACTOR2				4297157 + +struct imx_thermal_data { +	struct thermal_zone_device *tz; +	struct thermal_cooling_device *cdev; +	enum thermal_device_mode mode; +	struct regmap *tempmon; +	u32 c1, c2; /* See formula in imx_get_sensor_data() */ +	unsigned long temp_passive; +	unsigned long temp_critical; +	unsigned long alarm_temp; +	unsigned long last_temp; +	bool irq_enabled; +	int irq; +	struct clk *thermal_clk; +}; + +static void imx_set_alarm_temp(struct imx_thermal_data *data, +			       signed long alarm_temp) +{ +	struct regmap *map = data->tempmon; +	int alarm_value; + +	data->alarm_temp = alarm_temp; +	alarm_value = (data->c2 - alarm_temp) / data->c1; +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_ALARM_VALUE_MASK); +	regmap_write(map, TEMPSENSE0 + REG_SET, alarm_value << +			TEMPSENSE0_ALARM_VALUE_SHIFT); +} + +static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp) +{ +	struct imx_thermal_data *data = tz->devdata; +	struct regmap *map = data->tempmon; +	unsigned int n_meas; +	bool wait; +	u32 val; + +	if (data->mode == THERMAL_DEVICE_ENABLED) { +		/* Check if a measurement is currently in progress */ +		regmap_read(map, TEMPSENSE0, &val); +		wait = !(val & TEMPSENSE0_FINISHED); +	} else { +		/* +		 * Every time we measure the temperature, we will power on the +		 * temperature sensor, enable measurements, take a reading, +		 * disable measurements, power off the temperature sensor. +		 */ +		regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); +		regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + +		wait = true; +	} + +	/* +	 * According to the temp sensor designers, it may require up to ~17us +	 * to complete a measurement. +	 */ +	if (wait) +		usleep_range(20, 50); + +	regmap_read(map, TEMPSENSE0, &val); + +	if (data->mode != THERMAL_DEVICE_ENABLED) { +		regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); +		regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); +	} + +	if ((val & TEMPSENSE0_FINISHED) == 0) { +		dev_dbg(&tz->device, "temp measurement never finished\n"); +		return -EAGAIN; +	} + +	n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT; + +	/* See imx_get_sensor_data() for formula derivation */ +	*temp = data->c2 - n_meas * data->c1; + +	/* Update alarm value to next higher trip point */ +	if (data->alarm_temp == data->temp_passive && *temp >= data->temp_passive) +		imx_set_alarm_temp(data, data->temp_critical); +	if (data->alarm_temp == data->temp_critical && *temp < data->temp_passive) { +		imx_set_alarm_temp(data, data->temp_passive); +		dev_dbg(&tz->device, "thermal alarm off: T < %lu\n", +			data->alarm_temp / 1000); +	} + +	if (*temp != data->last_temp) { +		dev_dbg(&tz->device, "millicelsius: %ld\n", *temp); +		data->last_temp = *temp; +	} + +	/* Reenable alarm IRQ if temperature below alarm temperature */ +	if (!data->irq_enabled && *temp < data->alarm_temp) { +		data->irq_enabled = true; +		enable_irq(data->irq); +	} + +	return 0; +} + +static int imx_get_mode(struct thermal_zone_device *tz, +			enum thermal_device_mode *mode) +{ +	struct imx_thermal_data *data = tz->devdata; + +	*mode = data->mode; + +	return 0; +} + +static int imx_set_mode(struct thermal_zone_device *tz, +			enum thermal_device_mode mode) +{ +	struct imx_thermal_data *data = tz->devdata; +	struct regmap *map = data->tempmon; + +	if (mode == THERMAL_DEVICE_ENABLED) { +		tz->polling_delay = IMX_POLLING_DELAY; +		tz->passive_delay = IMX_PASSIVE_DELAY; + +		regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); +		regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + +		if (!data->irq_enabled) { +			data->irq_enabled = true; +			enable_irq(data->irq); +		} +	} else { +		regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); +		regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + +		tz->polling_delay = 0; +		tz->passive_delay = 0; + +		if (data->irq_enabled) { +			disable_irq(data->irq); +			data->irq_enabled = false; +		} +	} + +	data->mode = mode; +	thermal_zone_device_update(tz); + +	return 0; +} + +static int imx_get_trip_type(struct thermal_zone_device *tz, int trip, +			     enum thermal_trip_type *type) +{ +	*type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE : +					     THERMAL_TRIP_CRITICAL; +	return 0; +} + +static int imx_get_crit_temp(struct thermal_zone_device *tz, +			     unsigned long *temp) +{ +	struct imx_thermal_data *data = tz->devdata; + +	*temp = data->temp_critical; +	return 0; +} + +static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip, +			     unsigned long *temp) +{ +	struct imx_thermal_data *data = tz->devdata; + +	*temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive : +					     data->temp_critical; +	return 0; +} + +static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip, +			     unsigned long temp) +{ +	struct imx_thermal_data *data = tz->devdata; + +	if (trip == IMX_TRIP_CRITICAL) +		return -EPERM; + +	if (temp > IMX_TEMP_PASSIVE) +		return -EINVAL; + +	data->temp_passive = temp; + +	imx_set_alarm_temp(data, temp); + +	return 0; +} + +static int imx_bind(struct thermal_zone_device *tz, +		    struct thermal_cooling_device *cdev) +{ +	int ret; + +	ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev, +					       THERMAL_NO_LIMIT, +					       THERMAL_NO_LIMIT); +	if (ret) { +		dev_err(&tz->device, +			"binding zone %s with cdev %s failed:%d\n", +			tz->type, cdev->type, ret); +		return ret; +	} + +	return 0; +} + +static int imx_unbind(struct thermal_zone_device *tz, +		      struct thermal_cooling_device *cdev) +{ +	int ret; + +	ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev); +	if (ret) { +		dev_err(&tz->device, +			"unbinding zone %s with cdev %s failed:%d\n", +			tz->type, cdev->type, ret); +		return ret; +	} + +	return 0; +} + +static struct thermal_zone_device_ops imx_tz_ops = { +	.bind = imx_bind, +	.unbind = imx_unbind, +	.get_temp = imx_get_temp, +	.get_mode = imx_get_mode, +	.set_mode = imx_set_mode, +	.get_trip_type = imx_get_trip_type, +	.get_trip_temp = imx_get_trip_temp, +	.get_crit_temp = imx_get_crit_temp, +	.set_trip_temp = imx_set_trip_temp, +}; + +static int imx_get_sensor_data(struct platform_device *pdev) +{ +	struct imx_thermal_data *data = platform_get_drvdata(pdev); +	struct regmap *map; +	int t1, n1; +	int ret; +	u32 val; +	u64 temp64; + +	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, +					      "fsl,tempmon-data"); +	if (IS_ERR(map)) { +		ret = PTR_ERR(map); +		dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret); +		return ret; +	} + +	ret = regmap_read(map, OCOTP_ANA1, &val); +	if (ret) { +		dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret); +		return ret; +	} + +	if (val == 0 || val == ~0) { +		dev_err(&pdev->dev, "invalid sensor calibration data\n"); +		return -EINVAL; +	} + +	/* +	 * Sensor data layout: +	 *   [31:20] - sensor value @ 25C +	 * Use universal formula now and only need sensor value @ 25C +	 * slope = 0.4297157 - (0.0015976 * 25C fuse) +	 */ +	n1 = val >> 20; +	t1 = 25; /* t1 always 25C */ + +	/* +	 * Derived from linear interpolation: +	 * slope = 0.4297157 - (0.0015976 * 25C fuse) +	 * slope = (FACTOR2 - FACTOR1 * n1) / FACTOR0 +	 * (Nmeas - n1) / (Tmeas - t1) = slope +	 * We want to reduce this down to the minimum computation necessary +	 * for each temperature read.  Also, we want Tmeas in millicelsius +	 * and we don't want to lose precision from integer division. So... +	 * Tmeas = (Nmeas - n1) / slope + t1 +	 * milli_Tmeas = 1000 * (Nmeas - n1) / slope + 1000 * t1 +	 * milli_Tmeas = -1000 * (n1 - Nmeas) / slope + 1000 * t1 +	 * Let constant c1 = (-1000 / slope) +	 * milli_Tmeas = (n1 - Nmeas) * c1 + 1000 * t1 +	 * Let constant c2 = n1 *c1 + 1000 * t1 +	 * milli_Tmeas = c2 - Nmeas * c1 +	 */ +	temp64 = FACTOR0; +	temp64 *= 1000; +	do_div(temp64, FACTOR1 * n1 - FACTOR2); +	data->c1 = temp64; +	data->c2 = n1 * data->c1 + 1000 * t1; + +	/* +	 * Set the default passive cooling trip point, +	 * can be changed from userspace. +	 */ +	data->temp_passive = IMX_TEMP_PASSIVE; + +	/* +	 * The maximum die temperature set to 20 C higher than +	 * IMX_TEMP_PASSIVE. +	 */ +	data->temp_critical = 1000 * 20 + data->temp_passive; + +	return 0; +} + +static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev) +{ +	struct imx_thermal_data *data = dev; + +	disable_irq_nosync(irq); +	data->irq_enabled = false; + +	return IRQ_WAKE_THREAD; +} + +static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) +{ +	struct imx_thermal_data *data = dev; + +	dev_dbg(&data->tz->device, "THERMAL ALARM: T > %lu\n", +		data->alarm_temp / 1000); + +	thermal_zone_device_update(data->tz); + +	return IRQ_HANDLED; +} + +static int imx_thermal_probe(struct platform_device *pdev) +{ +	struct imx_thermal_data *data; +	struct cpumask clip_cpus; +	struct regmap *map; +	int measure_freq; +	int ret; + +	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon"); +	if (IS_ERR(map)) { +		ret = PTR_ERR(map); +		dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret); +		return ret; +	} +	data->tempmon = map; + +	data->irq = platform_get_irq(pdev, 0); +	if (data->irq < 0) +		return data->irq; + +	ret = devm_request_threaded_irq(&pdev->dev, data->irq, +			imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread, +			0, "imx_thermal", data); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret); +		return ret; +	} + +	platform_set_drvdata(pdev, data); + +	ret = imx_get_sensor_data(pdev); +	if (ret) { +		dev_err(&pdev->dev, "failed to get sensor data\n"); +		return ret; +	} + +	/* Make sure sensor is in known good state for measurements */ +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); +	regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); +	regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF); +	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + +	cpumask_set_cpu(0, &clip_cpus); +	data->cdev = cpufreq_cooling_register(&clip_cpus); +	if (IS_ERR(data->cdev)) { +		ret = PTR_ERR(data->cdev); +		dev_err(&pdev->dev, +			"failed to register cpufreq cooling device: %d\n", ret); +		return ret; +	} + +	data->tz = thermal_zone_device_register("imx_thermal_zone", +						IMX_TRIP_NUM, +						BIT(IMX_TRIP_PASSIVE), data, +						&imx_tz_ops, NULL, +						IMX_PASSIVE_DELAY, +						IMX_POLLING_DELAY); +	if (IS_ERR(data->tz)) { +		ret = PTR_ERR(data->tz); +		dev_err(&pdev->dev, +			"failed to register thermal zone device %d\n", ret); +		cpufreq_cooling_unregister(data->cdev); +		return ret; +	} + +	data->thermal_clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(data->thermal_clk)) { +		dev_warn(&pdev->dev, "failed to get thermal clk!\n"); +	} else { +		/* +		 * Thermal sensor needs clk on to get correct value, normally +		 * we should enable its clk before taking measurement and disable +		 * clk after measurement is done, but if alarm function is enabled, +		 * hardware will auto measure the temperature periodically, so we +		 * need to keep the clk always on for alarm function. +		 */ +		ret = clk_prepare_enable(data->thermal_clk); +		if (ret) +			dev_warn(&pdev->dev, "failed to enable thermal clk: %d\n", ret); +	} + +	/* Enable measurements at ~ 10 Hz */ +	regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); +	measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */ +	regmap_write(map, TEMPSENSE1 + REG_SET, measure_freq); +	imx_set_alarm_temp(data, data->temp_passive); +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); +	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + +	data->irq_enabled = true; +	data->mode = THERMAL_DEVICE_ENABLED; + +	return 0; +} + +static int imx_thermal_remove(struct platform_device *pdev) +{ +	struct imx_thermal_data *data = platform_get_drvdata(pdev); +	struct regmap *map = data->tempmon; + +	/* Disable measurements */ +	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); +	if (!IS_ERR(data->thermal_clk)) +		clk_disable_unprepare(data->thermal_clk); + +	thermal_zone_device_unregister(data->tz); +	cpufreq_cooling_unregister(data->cdev); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx_thermal_suspend(struct device *dev) +{ +	struct imx_thermal_data *data = dev_get_drvdata(dev); +	struct regmap *map = data->tempmon; + +	/* +	 * Need to disable thermal sensor, otherwise, when thermal core +	 * try to get temperature before thermal sensor resume, a wrong +	 * temperature will be read as the thermal sensor is powered +	 * down. +	 */ +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); +	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); +	data->mode = THERMAL_DEVICE_DISABLED; + +	return 0; +} + +static int imx_thermal_resume(struct device *dev) +{ +	struct imx_thermal_data *data = dev_get_drvdata(dev); +	struct regmap *map = data->tempmon; + +	/* Enabled thermal sensor after resume */ +	regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); +	regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); +	data->mode = THERMAL_DEVICE_ENABLED; + +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops, +			 imx_thermal_suspend, imx_thermal_resume); + +static const struct of_device_id of_imx_thermal_match[] = { +	{ .compatible = "fsl,imx6q-tempmon", }, +	{ /* end */ } +}; +MODULE_DEVICE_TABLE(of, of_imx_thermal_match); + +static struct platform_driver imx_thermal = { +	.driver = { +		.name	= "imx_thermal", +		.owner  = THIS_MODULE, +		.pm	= &imx_thermal_pm_ops, +		.of_match_table = of_imx_thermal_match, +	}, +	.probe		= imx_thermal_probe, +	.remove		= imx_thermal_remove, +}; +module_platform_driver(imx_thermal); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-thermal"); diff --git a/drivers/thermal/int3403_thermal.c b/drivers/thermal/int3403_thermal.c new file mode 100644 index 00000000000..e93f0253f6e --- /dev/null +++ b/drivers/thermal/int3403_thermal.c @@ -0,0 +1,243 @@ +/* + * ACPI INT3403 thermal driver + * Copyright (c) 2013, Intel Corporation. + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/acpi.h> +#include <linux/thermal.h> + +#define INT3403_TYPE_SENSOR		0x03 +#define INT3403_PERF_CHANGED_EVENT	0x80 +#define INT3403_THERMAL_EVENT		0x90 + +#define DECI_KELVIN_TO_MILLI_CELSIUS(t, off) (((t) - (off)) * 100) +#define KELVIN_OFFSET	2732 +#define MILLI_CELSIUS_TO_DECI_KELVIN(t, off) (((t) / 100) + (off)) + +#define ACPI_INT3403_CLASS		"int3403" +#define ACPI_INT3403_FILE_STATE		"state" + +struct int3403_sensor { +	struct thermal_zone_device *tzone; +	unsigned long *thresholds; +}; + +static int sys_get_curr_temp(struct thermal_zone_device *tzone, +				unsigned long *temp) +{ +	struct acpi_device *device = tzone->devdata; +	unsigned long long tmp; +	acpi_status status; + +	status = acpi_evaluate_integer(device->handle, "_TMP", NULL, &tmp); +	if (ACPI_FAILURE(status)) +		return -EIO; + +	*temp = DECI_KELVIN_TO_MILLI_CELSIUS(tmp, KELVIN_OFFSET); + +	return 0; +} + +static int sys_get_trip_hyst(struct thermal_zone_device *tzone, +		int trip, unsigned long *temp) +{ +	struct acpi_device *device = tzone->devdata; +	unsigned long long hyst; +	acpi_status status; + +	status = acpi_evaluate_integer(device->handle, "GTSH", NULL, &hyst); +	if (ACPI_FAILURE(status)) +		return -EIO; + +	/* +	 * Thermal hysteresis represents a temperature difference. +	 * Kelvin and Celsius have same degree size. So the +	 * conversion here between tenths of degree Kelvin unit +	 * and Milli-Celsius unit is just to multiply 100. +	 */ +	*temp = hyst * 100; + +	return 0; +} + +static int sys_get_trip_temp(struct thermal_zone_device *tzone, +		int trip, unsigned long *temp) +{ +	struct acpi_device *device = tzone->devdata; +	struct int3403_sensor *obj = acpi_driver_data(device); + +	/* +	 * get_trip_temp is a mandatory callback but +	 * PATx method doesn't return any value, so return +	 * cached value, which was last set from user space. +	 */ +	*temp = obj->thresholds[trip]; + +	return 0; +} + +static int sys_get_trip_type(struct thermal_zone_device *thermal, +		int trip, enum thermal_trip_type *type) +{ +	/* Mandatory callback, may not mean much here */ +	*type = THERMAL_TRIP_PASSIVE; + +	return 0; +} + +int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip, +							unsigned long temp) +{ +	struct acpi_device *device = tzone->devdata; +	acpi_status status; +	char name[10]; +	int ret = 0; +	struct int3403_sensor *obj = acpi_driver_data(device); + +	snprintf(name, sizeof(name), "PAT%d", trip); +	if (acpi_has_method(device->handle, name)) { +		status = acpi_execute_simple_method(device->handle, name, +				MILLI_CELSIUS_TO_DECI_KELVIN(temp, +							KELVIN_OFFSET)); +		if (ACPI_FAILURE(status)) +			ret = -EIO; +		else +			obj->thresholds[trip] = temp; +	} else { +		ret = -EIO; +		dev_err(&device->dev, "sys_set_trip_temp: method not found\n"); +	} + +	return ret; +} + +static struct thermal_zone_device_ops tzone_ops = { +	.get_temp = sys_get_curr_temp, +	.get_trip_temp = sys_get_trip_temp, +	.get_trip_type = sys_get_trip_type, +	.set_trip_temp = sys_set_trip_temp, +	.get_trip_hyst =  sys_get_trip_hyst, +}; + +static void acpi_thermal_notify(struct acpi_device *device, u32 event) +{ +	struct int3403_sensor *obj; + +	if (!device) +		return; + +	obj = acpi_driver_data(device); +	if (!obj) +		return; + +	switch (event) { +	case INT3403_PERF_CHANGED_EVENT: +		break; +	case INT3403_THERMAL_EVENT: +		thermal_zone_device_update(obj->tzone); +		break; +	default: +		dev_err(&device->dev, "Unsupported event [0x%x]\n", event); +		break; +	} +} + +static int acpi_int3403_add(struct acpi_device *device) +{ +	int result = 0; +	unsigned long long ptyp; +	acpi_status status; +	struct int3403_sensor *obj; +	unsigned long long trip_cnt; +	int trip_mask = 0; + +	if (!device) +		return -EINVAL; + +	status = acpi_evaluate_integer(device->handle, "PTYP", NULL, &ptyp); +	if (ACPI_FAILURE(status)) +		return -EINVAL; + +	if (ptyp != INT3403_TYPE_SENSOR) +		return -EINVAL; + +	obj = devm_kzalloc(&device->dev, sizeof(*obj), GFP_KERNEL); +	if (!obj) +		return -ENOMEM; + +	device->driver_data = obj; + +	status = acpi_evaluate_integer(device->handle, "PATC", NULL, +						&trip_cnt); +	if (ACPI_FAILURE(status)) +		trip_cnt = 0; + +	if (trip_cnt) { +		/* We have to cache, thresholds can't be readback */ +		obj->thresholds = devm_kzalloc(&device->dev, +					sizeof(*obj->thresholds) * trip_cnt, +					GFP_KERNEL); +		if (!obj->thresholds) +			return -ENOMEM; +		trip_mask = BIT(trip_cnt) - 1; +	} +	obj->tzone = thermal_zone_device_register(acpi_device_bid(device), +				trip_cnt, trip_mask, device, &tzone_ops, +				NULL, 0, 0); +	if (IS_ERR(obj->tzone)) { +		result = PTR_ERR(obj->tzone); +		return result; +	} + +	strcpy(acpi_device_name(device), "INT3403"); +	strcpy(acpi_device_class(device), ACPI_INT3403_CLASS); + +	return 0; +} + +static int acpi_int3403_remove(struct acpi_device *device) +{ +	struct int3403_sensor *obj; + +	obj = acpi_driver_data(device); +	thermal_zone_device_unregister(obj->tzone); + +	return 0; +} + +ACPI_MODULE_NAME("int3403"); +static const struct acpi_device_id int3403_device_ids[] = { +	{"INT3403", 0}, +	{"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, int3403_device_ids); + +static struct acpi_driver acpi_int3403_driver = { +	.name = "INT3403", +	.class = ACPI_INT3403_CLASS, +	.ids = int3403_device_ids, +	.ops = { +		.add = acpi_int3403_add, +		.remove = acpi_int3403_remove, +		.notify = acpi_thermal_notify, +		}, +}; + +module_acpi_driver(acpi_int3403_driver); + +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ACPI INT3403 thermal driver"); diff --git a/drivers/thermal/intel_powerclamp.c b/drivers/thermal/intel_powerclamp.c new file mode 100644 index 00000000000..95cb7fc20e1 --- /dev/null +++ b/drivers/thermal/intel_powerclamp.c @@ -0,0 +1,827 @@ +/* + * 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 bool has_pkg_state_counter(void) +{ +	u64 tmp; +	return !rdmsrl_safe(MSR_PKG_C2_RESIDENCY, &tmp) || +	       !rdmsrl_safe(MSR_PKG_C3_RESIDENCY, &tmp) || +	       !rdmsrl_safe(MSR_PKG_C6_RESIDENCY, &tmp) || +	       !rdmsrl_safe(MSR_PKG_C7_RESIDENCY, &tmp); +} + +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(); +			mwait_idle_with_hints(eax, ecx); +			start_critical_timings(); +			atomic_inc(&idle_wakeup_counter); +		} +		tick_nohz_idle_exit(); +		preempt_enable(); +	} +	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 (!has_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, 0x37}, +	{ X86_VENDOR_INTEL, 6, 0x3a}, +	{ X86_VENDOR_INTEL, 6, 0x3c}, +	{ X86_VENDOR_INTEL, 6, 0x3d}, +	{ X86_VENDOR_INTEL, 6, 0x3e}, +	{ X86_VENDOR_INTEL, 6, 0x3f}, +	{ X86_VENDOR_INTEL, 6, 0x45}, +	{ X86_VENDOR_INTEL, 6, 0x46}, +	{} +}; +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) +		goto exit_free; + +	/* 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 *); +	if (!powerclamp_thread) { +		retval = -ENOMEM; +		goto exit_unregister; +	} + +	cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL, +						&powerclamp_cooling_ops); +	if (IS_ERR(cooling_dev)) { +		retval = -ENODEV; +		goto exit_free_thread; +	} + +	if (!duration) +		duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES); + +	powerclamp_create_debug_files(); + +	return 0; + +exit_free_thread: +	free_percpu(powerclamp_thread); +exit_unregister: +	unregister_hotcpu_notifier(&powerclamp_cpu_notifier); +exit_free: +	kfree(cpu_clamping_mask); +	return retval; +} +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/intel_soc_dts_thermal.c b/drivers/thermal/intel_soc_dts_thermal.c new file mode 100644 index 00000000000..a6a0a18ec0a --- /dev/null +++ b/drivers/thermal/intel_soc_dts_thermal.c @@ -0,0 +1,479 @@ +/* + * intel_soc_dts_thermal.c + * Copyright (c) 2014, Intel Corporation. + * + * 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. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/thermal.h> +#include <asm/cpu_device_id.h> +#include <asm/iosf_mbi.h> + +#define SOC_DTS_OFFSET_ENABLE	0xB0 +#define SOC_DTS_OFFSET_TEMP	0xB1 + +#define SOC_DTS_OFFSET_PTPS	0xB2 +#define SOC_DTS_OFFSET_PTTS	0xB3 +#define SOC_DTS_OFFSET_PTTSS	0xB4 +#define SOC_DTS_OFFSET_PTMC	0x80 +#define SOC_DTS_TE_AUX0		0xB5 +#define SOC_DTS_TE_AUX1		0xB6 + +#define SOC_DTS_AUX0_ENABLE_BIT		BIT(0) +#define SOC_DTS_AUX1_ENABLE_BIT		BIT(1) +#define SOC_DTS_CPU_MODULE0_ENABLE_BIT	BIT(16) +#define SOC_DTS_CPU_MODULE1_ENABLE_BIT	BIT(17) +#define SOC_DTS_TE_SCI_ENABLE		BIT(9) +#define SOC_DTS_TE_SMI_ENABLE		BIT(10) +#define SOC_DTS_TE_MSI_ENABLE		BIT(11) +#define SOC_DTS_TE_APICA_ENABLE		BIT(14) +#define SOC_DTS_PTMC_APIC_DEASSERT_BIT	BIT(4) + +/* DTS encoding for TJ MAX temperature */ +#define SOC_DTS_TJMAX_ENCODING	0x7F + +/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */ +#define BYT_SOC_DTS_APIC_IRQ	86 + +/* Only 2 out of 4 is allowed for OSPM */ +#define SOC_MAX_DTS_TRIPS	2 + +/* Mask for two trips in status bits */ +#define SOC_DTS_TRIP_MASK	0x03 + +/* DTS0 and DTS 1 */ +#define SOC_MAX_DTS_SENSORS	2 + +#define CRITICAL_OFFSET_FROM_TJ_MAX	5000 + +struct soc_sensor_entry { +	int id; +	u32 tj_max; +	u32 temp_mask; +	u32 temp_shift; +	u32 store_status; +	struct thermal_zone_device *tzone; +}; + +static struct soc_sensor_entry *soc_dts[SOC_MAX_DTS_SENSORS]; + +static int crit_offset = CRITICAL_OFFSET_FROM_TJ_MAX; +module_param(crit_offset, int, 0644); +MODULE_PARM_DESC(crit_offset, +	"Critical Temperature offset from tj max in millidegree Celsius."); + +static DEFINE_MUTEX(aux_update_mutex); +static spinlock_t intr_notify_lock; +static int soc_dts_thres_irq; + +static int get_tj_max(u32 *tj_max) +{ +	u32 eax, edx; +	u32 val; +	int err; + +	err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); +	if (err) +		goto err_ret; +	else { +		val = (eax >> 16) & 0xff; +		if (val) +			*tj_max = val * 1000; +		else { +			err = -EINVAL; +			goto err_ret; +		} +	} + +	return 0; +err_ret: +	*tj_max = 0; + +	return err; +} + +static int sys_get_trip_temp(struct thermal_zone_device *tzd, +					int trip, unsigned long *temp) +{ +	int status; +	u32 out; +	struct soc_sensor_entry *aux_entry; + +	aux_entry = tzd->devdata; + +	if (!trip) { +		/* Just return the critical temp */ +		*temp = aux_entry->tj_max - crit_offset; +		return 0; +	} + +	mutex_lock(&aux_update_mutex); +	status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, +					SOC_DTS_OFFSET_PTPS, &out); +	mutex_unlock(&aux_update_mutex); +	if (status) +		return status; + +	out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING; + +	if (!out) +		*temp = 0; +	else +		*temp = aux_entry->tj_max - out * 1000; + +	return 0; +} + +static int update_trip_temp(struct soc_sensor_entry *aux_entry, +				int thres_index, unsigned long temp) +{ +	int status; +	u32 temp_out; +	u32 out; +	u32 store_ptps; +	u32 store_ptmc; +	u32 store_te_out; +	u32 te_out; + +	u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE | +						SOC_DTS_TE_MSI_ENABLE; + +	temp_out = (aux_entry->tj_max - temp) / 1000; + +	status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, +				SOC_DTS_OFFSET_PTPS, &store_ptps); +	if (status) +		return status; + +	out = (store_ptps & ~(0xFF << (thres_index * 8))); +	out |= (temp_out & 0xFF) << (thres_index * 8); +	status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +				SOC_DTS_OFFSET_PTPS, out); +	if (status) +		return status; +	pr_debug("update_trip_temp PTPS = %x\n", out); +	status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, +					SOC_DTS_OFFSET_PTMC, &out); +	if (status) +		goto err_restore_ptps; + +	store_ptmc = out; + +	status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, +					SOC_DTS_TE_AUX0 + thres_index, +					&te_out); +	if (status) +		goto err_restore_ptmc; + +	store_te_out = te_out; + +	/* Enable for CPU module 0 and module 1 */ +	out |= (SOC_DTS_CPU_MODULE0_ENABLE_BIT | +					SOC_DTS_CPU_MODULE1_ENABLE_BIT); +	if (temp) { +		if (thres_index) +			out |= SOC_DTS_AUX1_ENABLE_BIT; +		else +			out |= SOC_DTS_AUX0_ENABLE_BIT; +		te_out |= int_enable_bit; +	} else { +		if (thres_index) +			out &= ~SOC_DTS_AUX1_ENABLE_BIT; +		else +			out &= ~SOC_DTS_AUX0_ENABLE_BIT; +		te_out &= ~int_enable_bit; +	} +	status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +					SOC_DTS_OFFSET_PTMC, out); +	if (status) +		goto err_restore_te_out; + +	status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +					SOC_DTS_TE_AUX0 + thres_index, +					te_out); +	if (status) +		goto err_restore_te_out; + +	return 0; + +err_restore_te_out: +	iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +				SOC_DTS_OFFSET_PTMC, store_te_out); +err_restore_ptmc: +	iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +				SOC_DTS_OFFSET_PTMC, store_ptmc); +err_restore_ptps: +	iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +				SOC_DTS_OFFSET_PTPS, store_ptps); +	/* Nothing we can do if restore fails */ + +	return status; +} + +static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, +							unsigned long temp) +{ +	struct soc_sensor_entry *aux_entry = tzd->devdata; +	int status; + +	if (temp > (aux_entry->tj_max - crit_offset)) +		return -EINVAL; + +	mutex_lock(&aux_update_mutex); +	status = update_trip_temp(tzd->devdata, trip, temp); +	mutex_unlock(&aux_update_mutex); + +	return status; +} + +static int sys_get_trip_type(struct thermal_zone_device *thermal, +		int trip, enum thermal_trip_type *type) +{ +	if (trip) +		*type = THERMAL_TRIP_PASSIVE; +	else +		*type = THERMAL_TRIP_CRITICAL; + +	return 0; +} + +static int sys_get_curr_temp(struct thermal_zone_device *tzd, +						unsigned long *temp) +{ +	int status; +	u32 out; +	struct soc_sensor_entry *aux_entry; + +	aux_entry = tzd->devdata; + +	status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, +					SOC_DTS_OFFSET_TEMP, &out); +	if (status) +		return status; + +	out = (out & aux_entry->temp_mask) >> aux_entry->temp_shift; +	out -= SOC_DTS_TJMAX_ENCODING; +	*temp = aux_entry->tj_max - out * 1000; + +	return 0; +} + +static struct thermal_zone_device_ops tzone_ops = { +	.get_temp = sys_get_curr_temp, +	.get_trip_temp = sys_get_trip_temp, +	.get_trip_type = sys_get_trip_type, +	.set_trip_temp = sys_set_trip_temp, +}; + +static void free_soc_dts(struct soc_sensor_entry *aux_entry) +{ +	if (aux_entry) { +		iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +			SOC_DTS_OFFSET_ENABLE, aux_entry->store_status); +		thermal_zone_device_unregister(aux_entry->tzone); +		kfree(aux_entry); +	} +} + +static int soc_dts_enable(int id) +{ +	u32 out; +	int ret; + +	ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, +					SOC_DTS_OFFSET_ENABLE, &out); +	if (ret) +		return ret; + +	if (!(out & BIT(id))) { +		out |= BIT(id); +		ret = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +					SOC_DTS_OFFSET_ENABLE, out); +		if (ret) +			return ret; +	} + +	return ret; +} + +static struct soc_sensor_entry *alloc_soc_dts(int id, u32 tj_max) +{ +	struct soc_sensor_entry *aux_entry; +	char name[10]; +	int err; + +	aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL); +	if (!aux_entry) { +		err = -ENOMEM; +		return ERR_PTR(-ENOMEM); +	} + +	/* Store status to restor on exit */ +	err = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, +					SOC_DTS_OFFSET_ENABLE, +					&aux_entry->store_status); +	if (err) +		goto err_ret; + +	aux_entry->id = id; +	aux_entry->tj_max = tj_max; +	aux_entry->temp_mask = 0x00FF << (id * 8); +	aux_entry->temp_shift = id * 8; +	snprintf(name, sizeof(name), "soc_dts%d", id); +	aux_entry->tzone = thermal_zone_device_register(name, +			SOC_MAX_DTS_TRIPS, +			0x02, +			aux_entry, &tzone_ops, NULL, 0, 0); +	if (IS_ERR(aux_entry->tzone)) { +		err = PTR_ERR(aux_entry->tzone); +		goto err_ret; +	} + +	err = soc_dts_enable(id); +	if (err) +		goto err_aux_status; + +	return aux_entry; + +err_aux_status: +	thermal_zone_device_unregister(aux_entry->tzone); +err_ret: +	kfree(aux_entry); +	return ERR_PTR(err); +} + +static void proc_thermal_interrupt(void) +{ +	u32 sticky_out; +	int status; +	u32 ptmc_out; + +	/* Clear APIC interrupt */ +	status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, +				SOC_DTS_OFFSET_PTMC, &ptmc_out); + +	ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT; +	status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +					SOC_DTS_OFFSET_PTMC, ptmc_out); + +	/* Read status here */ +	status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, +					SOC_DTS_OFFSET_PTTSS, &sticky_out); +	pr_debug("status %d PTTSS %x\n", status, sticky_out); +	if (sticky_out & SOC_DTS_TRIP_MASK) { +		int i; +		/* reset sticky bit */ +		status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, +					SOC_DTS_OFFSET_PTTSS, sticky_out); +		for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { +			pr_debug("TZD update for zone %d\n", i); +			thermal_zone_device_update(soc_dts[i]->tzone); +		} +	} + +} + +static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data) +{ +	unsigned long flags; + +	spin_lock_irqsave(&intr_notify_lock, flags); +	proc_thermal_interrupt(); +	spin_unlock_irqrestore(&intr_notify_lock, flags); +	pr_debug("proc_thermal_interrupt\n"); + +	return IRQ_HANDLED; +} + +static const struct x86_cpu_id soc_thermal_ids[] = { +	{ X86_VENDOR_INTEL, X86_FAMILY_ANY, 0x37, 0, BYT_SOC_DTS_APIC_IRQ}, +	{} +}; +MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids); + +static int __init intel_soc_thermal_init(void) +{ +	u32 tj_max; +	int err = 0; +	int i; +	const struct x86_cpu_id *match_cpu; + +	match_cpu = x86_match_cpu(soc_thermal_ids); +	if (!match_cpu) +		return -ENODEV; + +	if (get_tj_max(&tj_max)) +		return -EINVAL; + +	for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { +		soc_dts[i] = alloc_soc_dts(i, tj_max); +		if (IS_ERR(soc_dts[i])) { +			err = PTR_ERR(soc_dts[i]); +			goto err_free; +		} +	} + +	spin_lock_init(&intr_notify_lock); + +	soc_dts_thres_irq = (int)match_cpu->driver_data; + +	err = request_threaded_irq(soc_dts_thres_irq, NULL, +					soc_irq_thread_fn, +					IRQF_TRIGGER_RISING | IRQF_ONESHOT, +					"soc_dts", soc_dts); +	if (err) { +		pr_err("request_threaded_irq ret %d\n", err); +		goto err_free; +	} + +	for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) { +		err = update_trip_temp(soc_dts[i], 0, tj_max - crit_offset); +		if (err) +			goto err_trip_temp; +	} + +	return 0; + +err_trip_temp: +	i = SOC_MAX_DTS_SENSORS; +	free_irq(soc_dts_thres_irq, soc_dts); +err_free: +	while (--i >= 0) +		free_soc_dts(soc_dts[i]); + +	return err; +} + +static void __exit intel_soc_thermal_exit(void) +{ +	int i; + +	for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) +		update_trip_temp(soc_dts[i], 0, 0); + +	free_irq(soc_dts_thres_irq, soc_dts); + +	for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) +		free_soc_dts(soc_dts[i]); + +} + +module_init(intel_soc_thermal_init) +module_exit(intel_soc_thermal_exit) + +MODULE_DESCRIPTION("Intel SoC DTS Thermal Driver"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/kirkwood_thermal.c b/drivers/thermal/kirkwood_thermal.c new file mode 100644 index 00000000000..3b034a0dfc9 --- /dev/null +++ b/drivers/thermal/kirkwood_thermal.c @@ -0,0 +1,126 @@ +/* + * 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; + +	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); + +	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); + +	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 = 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 00000000000..4b2b999b761 --- /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 00000000000..8803e693fe6 --- /dev/null +++ b/drivers/thermal/rcar_thermal.c @@ -0,0 +1,512 @@ +/* + *  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; +	unsigned long cctemp, nctemp; + +	priv = container_of(work, struct rcar_thermal_priv, work.work); + +	rcar_thermal_get_temp(priv->zone, &cctemp); +	rcar_thermal_update_temp(priv); +	rcar_thermal_irq_enable(priv); + +	rcar_thermal_get_temp(priv->zone, &nctemp); +	if (nctemp != cctemp) +		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) { +		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) +		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 +		 */ + +		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 +		 */ +		res = platform_get_resource(pdev, IORESOURCE_MEM, mres++); +		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 delay 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) { +			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 probed\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(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); +	} + +	pm_runtime_put(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/samsung/Kconfig b/drivers/thermal/samsung/Kconfig new file mode 100644 index 00000000000..f760389a204 --- /dev/null +++ b/drivers/thermal/samsung/Kconfig @@ -0,0 +1,18 @@ +config EXYNOS_THERMAL +	tristate "Exynos thermal management unit driver" +	depends on ARCH_HAS_BANDGAP && OF +	help +	  If you say yes here you get support for the TMU (Thermal Management +	  Unit) driver for SAMSUNG EXYNOS series of SoCs. This driver initialises +	  the TMU, reports temperature and handles cooling action if defined. +	  This driver uses the Exynos core thermal APIs and TMU configuration +	  data from the supported SoCs. + +config EXYNOS_THERMAL_CORE +	bool "Core thermal framework support for EXYNOS SOCs" +	depends on EXYNOS_THERMAL +	help +	  If you say yes here you get support for EXYNOS TMU +	  (Thermal Management Unit) common registration/unregistration +	  functions to the core thermal layer and also to use the generic +	  CPU cooling APIs. diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile new file mode 100644 index 00000000000..c09d83095dc --- /dev/null +++ b/drivers/thermal/samsung/Makefile @@ -0,0 +1,7 @@ +# +# Samsung thermal specific Makefile +# +obj-$(CONFIG_EXYNOS_THERMAL)			+= exynos_thermal.o +exynos_thermal-y				:= exynos_tmu.o +exynos_thermal-y				+= exynos_tmu_data.o +exynos_thermal-$(CONFIG_EXYNOS_THERMAL_CORE)	+= exynos_thermal_common.o diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c new file mode 100644 index 00000000000..3f5ad25ddca --- /dev/null +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -0,0 +1,430 @@ +/* + * exynos_thermal_common.c - Samsung EXYNOS common thermal file + * + *  Copyright (C) 2013 Samsung Electronics + *  Amit Daniel Kachhap <amit.daniel@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + */ + +#include <linux/cpu_cooling.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/thermal.h> + +#include "exynos_thermal_common.h" + +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; +}; + +/* Get mode callback functions for thermal zone */ +static int exynos_get_mode(struct thermal_zone_device *thermal, +			enum thermal_device_mode *mode) +{ +	struct exynos_thermal_zone *th_zone = thermal->devdata; +	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) +{ +	struct exynos_thermal_zone *th_zone = thermal->devdata; +	if (!th_zone) { +		dev_err(&thermal->device, +			"thermal zone not registered\n"); +		return 0; +	} + +	mutex_lock(&thermal->lock); + +	if (mode == THERMAL_DEVICE_ENABLED && +		!th_zone->sensor_conf->trip_data.trigger_falling) +		thermal->polling_delay = IDLE_INTERVAL; +	else +		thermal->polling_delay = 0; + +	mutex_unlock(&thermal->lock); + +	th_zone->mode = mode; +	thermal_zone_device_update(thermal); +	dev_dbg(th_zone->sensor_conf->dev, +		"thermal polling set for duration=%d msec\n", +		thermal->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) +{ +	struct exynos_thermal_zone *th_zone = thermal->devdata; +	int max_trip = th_zone->sensor_conf->trip_data.trip_count; +	int trip_type; + +	if (trip < 0 || trip >= max_trip) +		return -EINVAL; + +	trip_type = th_zone->sensor_conf->trip_data.trip_type[trip]; + +	if (trip_type == SW_TRIP) +		*type = THERMAL_TRIP_CRITICAL; +	else if (trip_type == THROTTLE_ACTIVE) +		*type = THERMAL_TRIP_ACTIVE; +	else if (trip_type == THROTTLE_PASSIVE) +		*type = THERMAL_TRIP_PASSIVE; +	else +		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) +{ +	struct exynos_thermal_zone *th_zone = thermal->devdata; +	int max_trip = th_zone->sensor_conf->trip_data.trip_count; + +	if (trip < 0 || trip >= max_trip) +		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) +{ +	struct exynos_thermal_zone *th_zone = thermal->devdata; +	int max_trip = th_zone->sensor_conf->trip_data.trip_count; +	/* Get the temp of highest trip*/ +	return exynos_get_trip_temp(thermal, max_trip - 1, temp); +} + +/* 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 exynos_thermal_zone *th_zone = thermal->devdata; +	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 0; + +	/* 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)) { +				dev_err(data->dev, +					"error unbinding 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 exynos_thermal_zone *th_zone = thermal->devdata; +	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 0; + +	/* 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)) { +				dev_err(data->dev, +					"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) +{ +	struct exynos_thermal_zone *th_zone = thermal->devdata; +	void *data; + +	if (!th_zone->sensor_conf) { +		dev_err(&thermal->device, +			"Temperature sensor not initialised\n"); +		return -EINVAL; +	} +	data = th_zone->sensor_conf->driver_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; +	struct exynos_thermal_zone *th_zone = thermal->devdata; + +	if (!th_zone->sensor_conf) { +		dev_err(&thermal->device, +			"Temperature sensor not initialised\n"); +		return -EINVAL; +	} +	data = th_zone->sensor_conf->driver_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 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. + */ +void exynos_report_trigger(struct thermal_sensor_conf *conf) +{ +	unsigned int i; +	char data[10]; +	char *envp[] = { data, NULL }; +	struct exynos_thermal_zone *th_zone; + +	if (!conf || !conf->pzone_data) { +		pr_err("Invalid temperature sensor configuration data\n"); +		return; +	} + +	th_zone = conf->pzone_data; + +	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 */ +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) +{ +	int ret; +	struct cpumask mask_val; +	struct exynos_thermal_zone *th_zone; + +	if (!sensor_conf || !sensor_conf->read_temperature) { +		pr_err("Temperature sensor not initialised\n"); +		return -EINVAL; +	} + +	th_zone = devm_kzalloc(sensor_conf->dev, +				sizeof(struct exynos_thermal_zone), GFP_KERNEL); +	if (!th_zone) +		return -ENOMEM; + +	th_zone->sensor_conf = sensor_conf; +	/* +	 * TODO: 1) Handle multiple cooling devices in a thermal zone +	 *	 2) Add a flag/name in cooling info to map to specific +	 *	 sensor +	 */ +	if (sensor_conf->cooling_data.freq_clip_count > 0) { +		cpumask_set_cpu(0, &mask_val); +		th_zone->cool_dev[th_zone->cool_dev_size] = +					cpufreq_cooling_register(&mask_val); +		if (IS_ERR(th_zone->cool_dev[th_zone->cool_dev_size])) { +			dev_err(sensor_conf->dev, +				"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, sensor_conf->trip_data.trip_count, +			0, th_zone, &exynos_dev_ops, NULL, 0, +			sensor_conf->trip_data.trigger_falling ? 0 : +			IDLE_INTERVAL); + +	if (IS_ERR(th_zone->therm_dev)) { +		dev_err(sensor_conf->dev, +			"Failed to register thermal zone device\n"); +		ret = PTR_ERR(th_zone->therm_dev); +		goto err_unregister; +	} +	th_zone->mode = THERMAL_DEVICE_ENABLED; +	sensor_conf->pzone_data = th_zone; + +	dev_info(sensor_conf->dev, +		"Exynos: Thermal zone(%s) registered\n", sensor_conf->name); + +	return 0; + +err_unregister: +	exynos_unregister_thermal(sensor_conf); +	return ret; +} + +/* Un-Register with the in-kernel thermal management */ +void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) +{ +	int i; +	struct exynos_thermal_zone *th_zone; + +	if (!sensor_conf || !sensor_conf->pzone_data) { +		pr_err("Invalid temperature sensor configuration data\n"); +		return; +	} + +	th_zone = sensor_conf->pzone_data; + +	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]); +	} + +	dev_info(sensor_conf->dev, +		"Exynos: Kernel Thermal management unregistered\n"); +} diff --git a/drivers/thermal/samsung/exynos_thermal_common.h b/drivers/thermal/samsung/exynos_thermal_common.h new file mode 100644 index 00000000000..3eb2ed9ea3a --- /dev/null +++ b/drivers/thermal/samsung/exynos_thermal_common.h @@ -0,0 +1,107 @@ +/* + * exynos_thermal_common.h - Samsung EXYNOS common header file + * + *  Copyright (C) 2013 Samsung Electronics + *  Amit Daniel Kachhap <amit.daniel@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + */ + +#ifndef _EXYNOS_THERMAL_COMMON_H +#define _EXYNOS_THERMAL_COMMON_H + +/* 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 5 + +#define ACTIVE_INTERVAL 500 +#define IDLE_INTERVAL 10000 +#define MCELSIUS	1000 + +/* 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) + +enum trigger_type { +	THROTTLE_ACTIVE = 1, +	THROTTLE_PASSIVE, +	SW_TRIP, +	HW_TRIP, +}; + +/** + * struct freq_clip_table + * @freq_clip_max: maximum frequency allowed for this cooling state. + * @temp_level: Temperature level at which the temperature clipping will + *	happen. + * @mask_val: cpumask of the allowed cpu's where the clipping will take place. + * + * This structure is required to be filled and passed to the + * cpufreq_cooling_unregister function. + */ +struct freq_clip_table { +	unsigned int freq_clip_max; +	unsigned int temp_level; +	const struct cpumask *mask_val; +}; + +struct	thermal_trip_point_conf { +	int trip_val[MAX_TRIP_COUNT]; +	int trip_type[MAX_TRIP_COUNT]; +	int trip_count; +	unsigned char 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 *driver_data; +	void *pzone_data; +	struct device *dev; +}; + +/*Functions used exynos based thermal sensor driver*/ +#ifdef CONFIG_EXYNOS_THERMAL_CORE +void exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf); +int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf); +void exynos_report_trigger(struct thermal_sensor_conf *sensor_conf); +#else +static inline void +exynos_unregister_thermal(struct thermal_sensor_conf *sensor_conf) { return; } + +static inline int +exynos_register_thermal(struct thermal_sensor_conf *sensor_conf) { return 0; } + +static inline void +exynos_report_trigger(struct thermal_sensor_conf *sensor_conf) { return; } + +#endif /* CONFIG_EXYNOS_THERMAL_CORE */ +#endif /* _EXYNOS_THERMAL_COMMON_H */ diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c new file mode 100644 index 00000000000..d7ca9f49c9c --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -0,0 +1,817 @@ +/* + * exynos_tmu.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/clk.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include "exynos_thermal_common.h" +#include "exynos_tmu.h" +#include "exynos_tmu_data.h" + +/** + * struct exynos_tmu_data : A structure to hold the private data of the TMU +	driver + * @id: identifier of the one instance of the TMU controller. + * @pdata: pointer to the tmu platform/configuration data + * @base: base address of the single instance of the TMU controller. + * @base_second: base address of the common registers of the TMU controller. + * @irq: irq number of the TMU controller. + * @soc: id of the SOC type. + * @irq_work: pointer to the irq work structure. + * @lock: lock to implement synchronization. + * @clk: pointer to the clock structure. + * @clk_sec: pointer to the clock structure for accessing the base_second. + * @temp_error1: fused value of the first point trim. + * @temp_error2: fused value of the second point trim. + * @regulator: pointer to the TMU regulator structure. + * @reg_conf: pointer to structure to register with core thermal. + */ +struct exynos_tmu_data { +	int id; +	struct exynos_tmu_platform_data *pdata; +	void __iomem *base; +	void __iomem *base_second; +	int irq; +	enum soc_type soc; +	struct work_struct irq_work; +	struct mutex lock; +	struct clk *clk, *clk_sec; +	u8 temp_error1, temp_error2; +	struct regulator *regulator; +	struct thermal_sensor_conf *reg_conf; +}; + +/* + * 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 (pdata->cal_mode == HW_MODE) +		return temp; + +	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 - pdata->first_point_trim) * +			(data->temp_error2 - data->temp_error1) / +			(pdata->second_point_trim - pdata->first_point_trim) + +			data->temp_error1; +		break; +	case TYPE_ONE_POINT_TRIMMING: +		temp_code = temp + data->temp_error1 - pdata->first_point_trim; +		break; +	default: +		temp_code = temp + pdata->default_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 (pdata->cal_mode == HW_MODE) +		return temp_code; + +	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) * +			(pdata->second_point_trim - pdata->first_point_trim) / +			(data->temp_error2 - data->temp_error1) + +			pdata->first_point_trim; +		break; +	case TYPE_ONE_POINT_TRIMMING: +		temp = temp_code - data->temp_error1 + pdata->first_point_trim; +		break; +	default: +		temp = temp_code - pdata->default_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; +	const struct exynos_tmu_registers *reg = pdata->registers; +	unsigned int status, trim_info = 0, con; +	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); +	if (!IS_ERR(data->clk_sec)) +		clk_enable(data->clk_sec); + +	if (TMU_SUPPORTS(pdata, READY_STATUS)) { +		status = readb(data->base + reg->tmu_status); +		if (!status) { +			ret = -EBUSY; +			goto out; +		} +	} + +	if (TMU_SUPPORTS(pdata, TRIM_RELOAD)) +		__raw_writel(1, data->base + reg->triminfo_ctrl); + +	if (pdata->cal_mode == HW_MODE) +		goto skip_calib_data; + +	/* Save trimming info in order to perform calibration */ +	if (data->soc == SOC_ARCH_EXYNOS5440) { +		/* +		 * For exynos5440 soc triminfo value is swapped between TMU0 and +		 * TMU2, so the below logic is needed. +		 */ +		switch (data->id) { +		case 0: +			trim_info = readl(data->base + +			EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data); +			break; +		case 1: +			trim_info = readl(data->base + reg->triminfo_data); +			break; +		case 2: +			trim_info = readl(data->base - +			EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data); +		} +	} else { +		/* On exynos5420 the triminfo register is in the shared space */ +		if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) +			trim_info = readl(data->base_second + +							reg->triminfo_data); +		else +			trim_info = readl(data->base + reg->triminfo_data); +	} +	data->temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; +	data->temp_error2 = ((trim_info >> reg->triminfo_85_shift) & +				EXYNOS_TMU_TEMP_MASK); + +	if (!data->temp_error1 || +		(pdata->min_efuse_value > data->temp_error1) || +		(data->temp_error1 > pdata->max_efuse_value)) +		data->temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK; + +	if (!data->temp_error2) +		data->temp_error2 = +			(pdata->efuse_value >> reg->triminfo_85_shift) & +			EXYNOS_TMU_TEMP_MASK; + +skip_calib_data: +	if (pdata->max_trigger_level > MAX_THRESHOLD_LEVS) { +		dev_err(&pdev->dev, "Invalid max trigger level\n"); +		ret = -EINVAL; +		goto out; +	} + +	for (i = 0; i < pdata->max_trigger_level; i++) { +		if (!pdata->trigger_levels[i]) +			continue; + +		if ((pdata->trigger_type[i] == HW_TRIP) && +		(!pdata->trigger_levels[pdata->max_trigger_level - 1])) { +			dev_err(&pdev->dev, "Invalid hw trigger level\n"); +			ret = -EINVAL; +			goto out; +		} + +		/* Count trigger levels except the HW trip*/ +		if (!(pdata->trigger_type[i] == HW_TRIP)) +			trigger_levs++; +	} + +	rising_threshold = readl(data->base + reg->threshold_th0); + +	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 + reg->threshold_temp); +		for (i = 0; i < trigger_levs; i++) +			writeb(pdata->trigger_levels[i], data->base + +			reg->threshold_th0 + i * sizeof(reg->threshold_th0)); + +		writel(reg->intclr_rise_mask, data->base + reg->tmu_intclear); +	} else { +		/* Write temperature code for rising and falling threshold */ +		for (i = 0; +		i < trigger_levs && i < EXYNOS_MAX_TRIGGER_PER_REG; i++) { +			threshold_code = temp_to_code(data, +						pdata->trigger_levels[i]); +			if (threshold_code < 0) { +				ret = threshold_code; +				goto out; +			} +			rising_threshold &= ~(0xff << 8 * i); +			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 + reg->threshold_th0); +		writel(falling_threshold, +				data->base + reg->threshold_th1); + +		writel((reg->intclr_rise_mask << reg->intclr_rise_shift) | +			(reg->intclr_fall_mask << reg->intclr_fall_shift), +				data->base + reg->tmu_intclear); + +		/* if last threshold limit is also present */ +		i = pdata->max_trigger_level - 1; +		if (pdata->trigger_levels[i] && +				(pdata->trigger_type[i] == HW_TRIP)) { +			threshold_code = temp_to_code(data, +						pdata->trigger_levels[i]); +			if (threshold_code < 0) { +				ret = threshold_code; +				goto out; +			} +			if (i == EXYNOS_MAX_TRIGGER_PER_REG - 1) { +				/* 1-4 level to be assigned in th0 reg */ +				rising_threshold &= ~(0xff << 8 * i); +				rising_threshold |= threshold_code << 8 * i; +				writel(rising_threshold, +					data->base + reg->threshold_th0); +			} else if (i == EXYNOS_MAX_TRIGGER_PER_REG) { +				/* 5th level to be assigned in th2 reg */ +				rising_threshold = +				threshold_code << reg->threshold_th3_l0_shift; +				writel(rising_threshold, +					data->base + reg->threshold_th2); +			} +			con = readl(data->base + reg->tmu_ctrl); +			con |= (1 << reg->therm_trip_en_shift); +			writel(con, data->base + reg->tmu_ctrl); +		} +	} +	/*Clear the PMIN in the common TMU register*/ +	if (reg->tmu_pmin && !data->id) +		writel(0, data->base_second + reg->tmu_pmin); +out: +	clk_disable(data->clk); +	mutex_unlock(&data->lock); +	if (!IS_ERR(data->clk_sec)) +		clk_disable(data->clk_sec); + +	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; +	const struct exynos_tmu_registers *reg = pdata->registers; +	unsigned int con, interrupt_en, cal_val; + +	mutex_lock(&data->lock); +	clk_enable(data->clk); + +	con = readl(data->base + reg->tmu_ctrl); + +	if (pdata->test_mux) +		con |= (pdata->test_mux << reg->test_mux_addr_shift); + +	if (pdata->reference_voltage) { +		con &= ~(reg->buf_vref_sel_mask << reg->buf_vref_sel_shift); +		con |= pdata->reference_voltage << reg->buf_vref_sel_shift; +	} + +	if (pdata->gain) { +		con &= ~(reg->buf_slope_sel_mask << reg->buf_slope_sel_shift); +		con |= (pdata->gain << reg->buf_slope_sel_shift); +	} + +	if (pdata->noise_cancel_mode) { +		con &= ~(reg->therm_trip_mode_mask << +					reg->therm_trip_mode_shift); +		con |= (pdata->noise_cancel_mode << reg->therm_trip_mode_shift); +	} + +	if (pdata->cal_mode == HW_MODE) { +		con &= ~(reg->calib_mode_mask << reg->calib_mode_shift); +		cal_val = 0; +		switch (pdata->cal_type) { +		case TYPE_TWO_POINT_TRIMMING: +			cal_val = 3; +			break; +		case TYPE_ONE_POINT_TRIMMING_85: +			cal_val = 2; +			break; +		case TYPE_ONE_POINT_TRIMMING_25: +			cal_val = 1; +			break; +		case TYPE_NONE: +			break; +		default: +			dev_err(&pdev->dev, "Invalid calibration type, using none\n"); +		} +		con |= cal_val << reg->calib_mode_shift; +	} + +	if (on) { +		con |= (1 << reg->core_en_shift); +		interrupt_en = +			pdata->trigger_enable[3] << reg->inten_rise3_shift | +			pdata->trigger_enable[2] << reg->inten_rise2_shift | +			pdata->trigger_enable[1] << reg->inten_rise1_shift | +			pdata->trigger_enable[0] << reg->inten_rise0_shift; +		if (TMU_SUPPORTS(pdata, FALLING_TRIP)) +			interrupt_en |= +				interrupt_en << reg->inten_fall0_shift; +	} else { +		con &= ~(1 << reg->core_en_shift); +		interrupt_en = 0; /* Disable all interrupts */ +	} +	writel(interrupt_en, data->base + reg->tmu_inten); +	writel(con, data->base + reg->tmu_ctrl); + +	clk_disable(data->clk); +	mutex_unlock(&data->lock); +} + +static int exynos_tmu_read(struct exynos_tmu_data *data) +{ +	struct exynos_tmu_platform_data *pdata = data->pdata; +	const struct exynos_tmu_registers *reg = pdata->registers; +	u8 temp_code; +	int temp; + +	mutex_lock(&data->lock); +	clk_enable(data->clk); + +	temp_code = readb(data->base + reg->tmu_cur_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; +	struct exynos_tmu_platform_data *pdata = data->pdata; +	const struct exynos_tmu_registers *reg = pdata->registers; +	unsigned int val; +	int ret = -EINVAL; + +	if (!TMU_SUPPORTS(pdata, EMULATION)) +		goto out; + +	if (temp && temp < MCELSIUS) +		goto out; + +	mutex_lock(&data->lock); +	clk_enable(data->clk); + +	val = readl(data->base + reg->emul_con); + +	if (temp) { +		temp /= MCELSIUS; + +		if (TMU_SUPPORTS(pdata, EMUL_TIME)) { +			val &= ~(EXYNOS_EMUL_TIME_MASK << reg->emul_time_shift); +			val |= (EXYNOS_EMUL_TIME << reg->emul_time_shift); +		} +		val &= ~(EXYNOS_EMUL_DATA_MASK << reg->emul_temp_shift); +		val |= (temp_to_code(data, temp) << reg->emul_temp_shift) | +			EXYNOS_EMUL_ENABLE; +	} else { +		val &= ~EXYNOS_EMUL_ENABLE; +	} + +	writel(val, data->base + reg->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); +	struct exynos_tmu_platform_data *pdata = data->pdata; +	const struct exynos_tmu_registers *reg = pdata->registers; +	unsigned int val_irq, val_type; + +	if (!IS_ERR(data->clk_sec)) +		clk_enable(data->clk_sec); +	/* Find which sensor generated this interrupt */ +	if (reg->tmu_irqstatus) { +		val_type = readl(data->base_second + reg->tmu_irqstatus); +		if (!((val_type >> data->id) & 0x1)) +			goto out; +	} +	if (!IS_ERR(data->clk_sec)) +		clk_disable(data->clk_sec); + +	exynos_report_trigger(data->reg_conf); +	mutex_lock(&data->lock); +	clk_enable(data->clk); + +	/* TODO: take action based on particular interrupt */ +	val_irq = readl(data->base + reg->tmu_intstat); +	/* clear the interrupts */ +	writel(val_irq, data->base + reg->tmu_intclear); + +	clk_disable(data->clk); +	mutex_unlock(&data->lock); +out: +	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 const struct of_device_id exynos_tmu_match[] = { +	{ +		.compatible = "samsung,exynos4210-tmu", +		.data = (void *)EXYNOS4210_TMU_DRV_DATA, +	}, +	{ +		.compatible = "samsung,exynos4412-tmu", +		.data = (void *)EXYNOS4412_TMU_DRV_DATA, +	}, +	{ +		.compatible = "samsung,exynos5250-tmu", +		.data = (void *)EXYNOS5250_TMU_DRV_DATA, +	}, +	{ +		.compatible = "samsung,exynos5260-tmu", +		.data = (void *)EXYNOS5260_TMU_DRV_DATA, +	}, +	{ +		.compatible = "samsung,exynos5420-tmu", +		.data = (void *)EXYNOS5420_TMU_DRV_DATA, +	}, +	{ +		.compatible = "samsung,exynos5420-tmu-ext-triminfo", +		.data = (void *)EXYNOS5420_TMU_DRV_DATA, +	}, +	{ +		.compatible = "samsung,exynos5440-tmu", +		.data = (void *)EXYNOS5440_TMU_DRV_DATA, +	}, +	{}, +}; +MODULE_DEVICE_TABLE(of, exynos_tmu_match); + +static inline struct  exynos_tmu_platform_data *exynos_get_driver_data( +			struct platform_device *pdev, int id) +{ +	struct  exynos_tmu_init_data *data_table; +	struct exynos_tmu_platform_data *tmu_data; +	const struct of_device_id *match; + +	match = of_match_node(exynos_tmu_match, pdev->dev.of_node); +	if (!match) +		return NULL; +	data_table = (struct exynos_tmu_init_data *) match->data; +	if (!data_table || id >= data_table->tmu_count) +		return NULL; +	tmu_data = data_table->tmu_data; +	return (struct exynos_tmu_platform_data *) (tmu_data + id); +} + +static int exynos_map_dt_data(struct platform_device *pdev) +{ +	struct exynos_tmu_data *data = platform_get_drvdata(pdev); +	struct exynos_tmu_platform_data *pdata; +	struct resource res; +	int ret; + +	if (!data || !pdev->dev.of_node) +		return -ENODEV; + +	/* +	 * Try enabling the regulator if found +	 * TODO: Add regulator as an SOC feature, so that regulator enable +	 * is a compulsory call. +	 */ +	data->regulator = devm_regulator_get(&pdev->dev, "vtmu"); +	if (!IS_ERR(data->regulator)) { +		ret = regulator_enable(data->regulator); +		if (ret) { +			dev_err(&pdev->dev, "failed to enable vtmu\n"); +			return ret; +		} +	} else { +		dev_info(&pdev->dev, "Regulator node (vtmu) not found\n"); +	} + +	data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl"); +	if (data->id < 0) +		data->id = 0; + +	data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); +	if (data->irq <= 0) { +		dev_err(&pdev->dev, "failed to get IRQ\n"); +		return -ENODEV; +	} + +	if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { +		dev_err(&pdev->dev, "failed to get Resource 0\n"); +		return -ENODEV; +	} + +	data->base = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); +	if (!data->base) { +		dev_err(&pdev->dev, "Failed to ioremap memory\n"); +		return -EADDRNOTAVAIL; +	} + +	pdata = exynos_get_driver_data(pdev, data->id); +	if (!pdata) { +		dev_err(&pdev->dev, "No platform init data supplied.\n"); +		return -ENODEV; +	} +	data->pdata = pdata; +	/* +	 * Check if the TMU shares some registers and then try to map the +	 * memory of common registers. +	 */ +	if (!TMU_SUPPORTS(pdata, ADDRESS_MULTIPLE)) +		return 0; + +	if (of_address_to_resource(pdev->dev.of_node, 1, &res)) { +		dev_err(&pdev->dev, "failed to get Resource 1\n"); +		return -ENODEV; +	} + +	data->base_second = devm_ioremap(&pdev->dev, res.start, +					resource_size(&res)); +	if (!data->base_second) { +		dev_err(&pdev->dev, "Failed to ioremap memory\n"); +		return -ENOMEM; +	} + +	return 0; +} + +static int exynos_tmu_probe(struct platform_device *pdev) +{ +	struct exynos_tmu_data *data; +	struct exynos_tmu_platform_data *pdata; +	struct thermal_sensor_conf *sensor_conf; +	int ret, i; + +	data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data), +					GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	platform_set_drvdata(pdev, data); +	mutex_init(&data->lock); + +	ret = exynos_map_dt_data(pdev); +	if (ret) +		return ret; + +	pdata = data->pdata; + +	INIT_WORK(&data->irq_work, exynos_tmu_work); + +	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); +	} + +	data->clk_sec = devm_clk_get(&pdev->dev, "tmu_triminfo_apbif"); +	if (IS_ERR(data->clk_sec)) { +		if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) { +			dev_err(&pdev->dev, "Failed to get triminfo clock\n"); +			return PTR_ERR(data->clk_sec); +		} +	} else { +		ret = clk_prepare(data->clk_sec); +		if (ret) { +			dev_err(&pdev->dev, "Failed to get clock\n"); +			return ret; +		} +	} + +	ret = clk_prepare(data->clk); +	if (ret) { +		dev_err(&pdev->dev, "Failed to get clock\n"); +		goto err_clk_sec; +	} + +	if (pdata->type == SOC_ARCH_EXYNOS4210 || +	    pdata->type == SOC_ARCH_EXYNOS4412 || +	    pdata->type == SOC_ARCH_EXYNOS5250 || +	    pdata->type == SOC_ARCH_EXYNOS5260 || +	    pdata->type == SOC_ARCH_EXYNOS5420_TRIMINFO || +	    pdata->type == SOC_ARCH_EXYNOS5440) +		data->soc = pdata->type; +	else { +		ret = -EINVAL; +		dev_err(&pdev->dev, "Platform not supported\n"); +		goto err_clk; +	} + +	ret = exynos_tmu_initialize(pdev); +	if (ret) { +		dev_err(&pdev->dev, "Failed to initialize TMU\n"); +		goto err_clk; +	} + +	exynos_tmu_control(pdev, true); + +	/* Allocate a structure to register with the exynos core thermal */ +	sensor_conf = devm_kzalloc(&pdev->dev, +				sizeof(struct thermal_sensor_conf), GFP_KERNEL); +	if (!sensor_conf) { +		ret = -ENOMEM; +		goto err_clk; +	} +	sprintf(sensor_conf->name, "therm_zone%d", data->id); +	sensor_conf->read_temperature = (int (*)(void *))exynos_tmu_read; +	sensor_conf->write_emul_temp = +		(int (*)(void *, unsigned long))exynos_tmu_set_emulation; +	sensor_conf->driver_data = data; +	sensor_conf->trip_data.trip_count = pdata->trigger_enable[0] + +			pdata->trigger_enable[1] + pdata->trigger_enable[2]+ +			pdata->trigger_enable[3]; + +	for (i = 0; i < sensor_conf->trip_data.trip_count; i++) { +		sensor_conf->trip_data.trip_val[i] = +			pdata->threshold + pdata->trigger_levels[i]; +		sensor_conf->trip_data.trip_type[i] = +					pdata->trigger_type[i]; +	} + +	sensor_conf->trip_data.trigger_falling = pdata->threshold_falling; + +	sensor_conf->cooling_data.freq_clip_count = pdata->freq_tab_count; +	for (i = 0; i < pdata->freq_tab_count; i++) { +		sensor_conf->cooling_data.freq_data[i].freq_clip_max = +					pdata->freq_tab[i].freq_clip_max; +		sensor_conf->cooling_data.freq_data[i].temp_level = +					pdata->freq_tab[i].temp_level; +	} +	sensor_conf->dev = &pdev->dev; +	/* Register the sensor with thermal management interface */ +	ret = exynos_register_thermal(sensor_conf); +	if (ret) { +		dev_err(&pdev->dev, "Failed to register thermal interface\n"); +		goto err_clk; +	} +	data->reg_conf = sensor_conf; + +	ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq, +		IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data); +	if (ret) { +		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq); +		goto err_clk; +	} + +	return 0; +err_clk: +	clk_unprepare(data->clk); +err_clk_sec: +	if (!IS_ERR(data->clk_sec)) +		clk_unprepare(data->clk_sec); +	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(data->reg_conf); + +	clk_unprepare(data->clk); +	if (!IS_ERR(data->clk_sec)) +		clk_unprepare(data->clk_sec); + +	if (!IS_ERR(data->regulator)) +		regulator_disable(data->regulator); + +	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 = exynos_tmu_match, +	}, +	.probe = exynos_tmu_probe, +	.remove	= exynos_tmu_remove, +}; + +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/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h new file mode 100644 index 00000000000..edd08cf7672 --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -0,0 +1,319 @@ +/* + * exynos_tmu.h - Samsung EXYNOS TMU (Thermal Management Unit) + * + *  Copyright (C) 2011 Samsung Electronics + *  Donggeun Kim <dg77.kim@samsung.com> + *  Amit Daniel Kachhap <amit.daniel@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + */ + +#ifndef _EXYNOS_TMU_H +#define _EXYNOS_TMU_H +#include <linux/cpu_cooling.h> + +#include "exynos_thermal_common.h" + +enum calibration_type { +	TYPE_ONE_POINT_TRIMMING, +	TYPE_ONE_POINT_TRIMMING_25, +	TYPE_ONE_POINT_TRIMMING_85, +	TYPE_TWO_POINT_TRIMMING, +	TYPE_NONE, +}; + +enum calibration_mode { +	SW_MODE, +	HW_MODE, +}; + +enum soc_type { +	SOC_ARCH_EXYNOS4210 = 1, +	SOC_ARCH_EXYNOS4412, +	SOC_ARCH_EXYNOS5250, +	SOC_ARCH_EXYNOS5260, +	SOC_ARCH_EXYNOS5420_TRIMINFO, +	SOC_ARCH_EXYNOS5440, +}; + +/** + * EXYNOS TMU supported features. + * TMU_SUPPORT_EMULATION - This features is used to set user defined + *			temperature to the TMU controller. + * TMU_SUPPORT_MULTI_INST - This features denotes that the soc + *			has many instances of TMU. + * TMU_SUPPORT_TRIM_RELOAD - This features shows that trimming can + *			be reloaded. + * TMU_SUPPORT_FALLING_TRIP - This features shows that interrupt can + *			be registered for falling trips also. + * TMU_SUPPORT_READY_STATUS - This feature tells that the TMU current + *			state(active/idle) can be checked. + * TMU_SUPPORT_EMUL_TIME - This features allows to set next temp emulation + *			sample time. + * TMU_SUPPORT_ADDRESS_MULTIPLE - This feature tells that the different TMU + *			sensors shares some common registers. + * TMU_SUPPORT - macro to compare the above features with the supplied. + */ +#define TMU_SUPPORT_EMULATION			BIT(0) +#define TMU_SUPPORT_MULTI_INST			BIT(1) +#define TMU_SUPPORT_TRIM_RELOAD			BIT(2) +#define TMU_SUPPORT_FALLING_TRIP		BIT(3) +#define TMU_SUPPORT_READY_STATUS		BIT(4) +#define TMU_SUPPORT_EMUL_TIME			BIT(5) +#define TMU_SUPPORT_ADDRESS_MULTIPLE		BIT(6) + +#define TMU_SUPPORTS(a, b)	(a->features & TMU_SUPPORT_ ## b) + +/** + * struct exynos_tmu_register - register descriptors to access registers and + * bitfields. The register validity, offsets and bitfield values may vary + * slightly across different exynos SOC's. + * @triminfo_data: register containing 2 pont trimming data + * @triminfo_25_shift: shift bit of the 25 C trim value in triminfo_data reg. + * @triminfo_85_shift: shift bit of the 85 C trim value in triminfo_data reg. + * @triminfo_ctrl: trim info controller register. + * @triminfo_reload_shift: shift of triminfo reload enable bit in triminfo_ctrl +	reg. + * @tmu_ctrl: TMU main controller register. + * @test_mux_addr_shift: shift bits of test mux address. + * @buf_vref_sel_shift: shift bits of reference voltage in tmu_ctrl register. + * @buf_vref_sel_mask: mask bits of reference voltage in tmu_ctrl register. + * @therm_trip_mode_shift: shift bits of tripping mode in tmu_ctrl register. + * @therm_trip_mode_mask: mask bits of tripping mode in tmu_ctrl register. + * @therm_trip_en_shift: shift bits of tripping enable in tmu_ctrl register. + * @buf_slope_sel_shift: shift bits of amplifier gain value in tmu_ctrl +	register. + * @buf_slope_sel_mask: mask bits of amplifier gain value in tmu_ctrl register. + * @calib_mode_shift: shift bits of calibration mode value in tmu_ctrl +	register. + * @calib_mode_mask: mask bits of calibration mode value in tmu_ctrl +	register. + * @therm_trip_tq_en_shift: shift bits of thermal trip enable by TQ pin in +	tmu_ctrl register. + * @core_en_shift: shift bits of TMU core enable bit in tmu_ctrl register. + * @tmu_status: register drescribing the TMU status. + * @tmu_cur_temp: register containing the current temperature of the TMU. + * @tmu_cur_temp_shift: shift bits of current temp value in tmu_cur_temp +	register. + * @threshold_temp: register containing the base threshold level. + * @threshold_th0: Register containing first set of rising levels. + * @threshold_th0_l0_shift: shift bits of level0 threshold temperature. + * @threshold_th0_l1_shift: shift bits of level1 threshold temperature. + * @threshold_th0_l2_shift: shift bits of level2 threshold temperature. + * @threshold_th0_l3_shift: shift bits of level3 threshold temperature. + * @threshold_th1: Register containing second set of rising levels. + * @threshold_th1_l0_shift: shift bits of level0 threshold temperature. + * @threshold_th1_l1_shift: shift bits of level1 threshold temperature. + * @threshold_th1_l2_shift: shift bits of level2 threshold temperature. + * @threshold_th1_l3_shift: shift bits of level3 threshold temperature. + * @threshold_th2: Register containing third set of rising levels. + * @threshold_th2_l0_shift: shift bits of level0 threshold temperature. + * @threshold_th3: Register containing fourth set of rising levels. + * @threshold_th3_l0_shift: shift bits of level0 threshold temperature. + * @tmu_inten: register containing the different threshold interrupt +	enable bits. + * @inten_rise0_shift: shift bits of rising 0 interrupt bits. + * @inten_rise1_shift: shift bits of rising 1 interrupt bits. + * @inten_rise2_shift: shift bits of rising 2 interrupt bits. + * @inten_rise3_shift: shift bits of rising 3 interrupt bits. + * @inten_fall0_shift: shift bits of falling 0 interrupt bits. + * @inten_fall1_shift: shift bits of falling 1 interrupt bits. + * @inten_fall2_shift: shift bits of falling 2 interrupt bits. + * @inten_fall3_shift: shift bits of falling 3 interrupt bits. + * @tmu_intstat: Register containing the interrupt status values. + * @tmu_intclear: Register for clearing the raised interrupt status. + * @intclr_fall_shift: shift bits for interrupt clear fall 0 + * @intclr_rise_shift: shift bits of all rising interrupt bits. + * @intclr_rise_mask: mask bits of all rising interrupt bits. + * @intclr_fall_mask: mask bits of all rising interrupt bits. + * @emul_con: TMU emulation controller register. + * @emul_temp_shift: shift bits of emulation temperature. + * @emul_time_shift: shift bits of emulation time. + * @emul_time_mask: mask bits of emulation time. + * @tmu_irqstatus: register to find which TMU generated interrupts. + * @tmu_pmin: register to get/set the Pmin value. + */ +struct exynos_tmu_registers { +	u32	triminfo_data; +	u32	triminfo_25_shift; +	u32	triminfo_85_shift; + +	u32	triminfo_ctrl; +	u32	triminfo_ctrl1; +	u32	triminfo_reload_shift; + +	u32	tmu_ctrl; +	u32     test_mux_addr_shift; +	u32	buf_vref_sel_shift; +	u32	buf_vref_sel_mask; +	u32	therm_trip_mode_shift; +	u32	therm_trip_mode_mask; +	u32	therm_trip_en_shift; +	u32	buf_slope_sel_shift; +	u32	buf_slope_sel_mask; +	u32	calib_mode_shift; +	u32	calib_mode_mask; +	u32	therm_trip_tq_en_shift; +	u32	core_en_shift; + +	u32	tmu_status; + +	u32	tmu_cur_temp; +	u32	tmu_cur_temp_shift; + +	u32	threshold_temp; + +	u32	threshold_th0; +	u32	threshold_th0_l0_shift; +	u32	threshold_th0_l1_shift; +	u32	threshold_th0_l2_shift; +	u32	threshold_th0_l3_shift; + +	u32	threshold_th1; +	u32	threshold_th1_l0_shift; +	u32	threshold_th1_l1_shift; +	u32	threshold_th1_l2_shift; +	u32	threshold_th1_l3_shift; + +	u32	threshold_th2; +	u32	threshold_th2_l0_shift; + +	u32	threshold_th3; +	u32	threshold_th3_l0_shift; + +	u32	tmu_inten; +	u32	inten_rise0_shift; +	u32	inten_rise1_shift; +	u32	inten_rise2_shift; +	u32	inten_rise3_shift; +	u32	inten_fall0_shift; +	u32	inten_fall1_shift; +	u32	inten_fall2_shift; +	u32	inten_fall3_shift; + +	u32	tmu_intstat; + +	u32	tmu_intclear; +	u32	intclr_fall_shift; +	u32	intclr_rise_shift; +	u32	intclr_fall_mask; +	u32	intclr_rise_mask; + +	u32	emul_con; +	u32	emul_temp_shift; +	u32	emul_time_shift; +	u32	emul_time_mask; + +	u32	tmu_irqstatus; +	u32	tmu_pmin; +}; + +/** + * struct exynos_tmu_platform_data + * @threshold: basic temperature for generating interrupt + *	       25 <= threshold <= 125 [unit: degree Celsius] + * @threshold_falling: differntial value for setting threshold + *		       of temperature falling interrupt. + * @trigger_levels: array for each interrupt levels + *	[unit: degree Celsius] + *	0: temperature for trigger_level0 interrupt + *	   condition for trigger_level0 interrupt: + *		current temperature > threshold + trigger_levels[0] + *	1: temperature for trigger_level1 interrupt + *	   condition for trigger_level1 interrupt: + *		current temperature > threshold + trigger_levels[1] + *	2: temperature for trigger_level2 interrupt + *	   condition for trigger_level2 interrupt: + *		current temperature > threshold + trigger_levels[2] + *	3: temperature for trigger_level3 interrupt + *	   condition for trigger_level3 interrupt: + *		current temperature > threshold + trigger_levels[3] + * @trigger_type: defines the type of trigger. Possible values are, + *	THROTTLE_ACTIVE trigger type + *	THROTTLE_PASSIVE trigger type + *	SW_TRIP trigger type + *	HW_TRIP + * @trigger_enable[]: array to denote which trigger levels are enabled. + *	1 = enable trigger_level[] interrupt, + *	0 = disable trigger_level[] interrupt + * @max_trigger_level: max trigger level supported by the TMU + * @gain: gain of amplifier in the positive-TC generator block + *	0 <= gain <= 15 + * @reference_voltage: reference voltage of amplifier + *	in the positive-TC generator block + *	0 <= reference_voltage <= 31 + * @noise_cancel_mode: noise cancellation mode + *	000, 100, 101, 110 and 111 can be different modes + * @type: determines the type of SOC + * @efuse_value: platform defined fuse value + * @min_efuse_value: minimum valid trimming data + * @max_efuse_value: maximum valid trimming data + * @first_point_trim: temp value of the first point trimming + * @second_point_trim: temp value of the second point trimming + * @default_temp_offset: default temperature offset in case of no trimming + * @test_mux; information if SoC supports test MUX + * @cal_type: calibration type for temperature + * @cal_mode: calibration mode for temperature + * @freq_clip_table: Table representing frequency reduction percentage. + * @freq_tab_count: Count of the above table as frequency reduction may + *	applicable to only some of the trigger levels. + * @registers: Pointer to structure containing all the TMU controller registers + *	and bitfields shifts and masks. + * @features: a bitfield value indicating the features supported in SOC like + *	emulation, multi instance etc + * + * This structure is required for configuration of exynos_tmu driver. + */ +struct exynos_tmu_platform_data { +	u8 threshold; +	u8 threshold_falling; +	u8 trigger_levels[MAX_TRIP_COUNT]; +	enum trigger_type trigger_type[MAX_TRIP_COUNT]; +	bool trigger_enable[MAX_TRIP_COUNT]; +	u8 max_trigger_level; +	u8 gain; +	u8 reference_voltage; +	u8 noise_cancel_mode; + +	u32 efuse_value; +	u32 min_efuse_value; +	u32 max_efuse_value; +	u8 first_point_trim; +	u8 second_point_trim; +	u8 default_temp_offset; +	u8 test_mux; + +	enum calibration_type cal_type; +	enum calibration_mode cal_mode; +	enum soc_type type; +	struct freq_clip_table freq_tab[4]; +	unsigned int freq_tab_count; +	const struct exynos_tmu_registers *registers; +	unsigned int features; +}; + +/** + * struct exynos_tmu_init_data + * @tmu_count: number of TMU instances. + * @tmu_data: platform data of all TMU instances. + * This structure is required to store data for multi-instance exynos tmu + * driver. + */ +struct exynos_tmu_init_data { +	int tmu_count; +	struct exynos_tmu_platform_data tmu_data[]; +}; + +#endif /* _EXYNOS_TMU_H */ diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c new file mode 100644 index 00000000000..c1d81dcd781 --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -0,0 +1,459 @@ +/* + * exynos_tmu_data.c - Samsung EXYNOS tmu data file + * + *  Copyright (C) 2013 Samsung Electronics + *  Amit Daniel Kachhap <amit.daniel@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + */ + +#include "exynos_thermal_common.h" +#include "exynos_tmu.h" +#include "exynos_tmu_data.h" + +#if defined(CONFIG_CPU_EXYNOS4210) +static const struct exynos_tmu_registers exynos4210_tmu_registers = { +	.triminfo_data = EXYNOS_TMU_REG_TRIMINFO, +	.triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, +	.triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, +	.tmu_ctrl = EXYNOS_TMU_REG_CONTROL, +	.buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, +	.buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, +	.buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, +	.buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, +	.core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, +	.tmu_status = EXYNOS_TMU_REG_STATUS, +	.tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP, +	.threshold_temp = EXYNOS4210_TMU_REG_THRESHOLD_TEMP, +	.threshold_th0 = EXYNOS4210_TMU_REG_TRIG_LEVEL0, +	.tmu_inten = EXYNOS_TMU_REG_INTEN, +	.inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, +	.inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, +	.inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, +	.inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, +	.tmu_intstat = EXYNOS_TMU_REG_INTSTAT, +	.tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, +	.intclr_rise_mask = EXYNOS4210_TMU_TRIG_LEVEL_MASK, +}; + +struct exynos_tmu_init_data const exynos4210_default_tmu_data = { +	.tmu_data = { +		{ +		.threshold = 80, +		.trigger_levels[0] = 5, +		.trigger_levels[1] = 20, +		.trigger_levels[2] = 30, +		.trigger_enable[0] = true, +		.trigger_enable[1] = true, +		.trigger_enable[2] = true, +		.trigger_enable[3] = false, +		.trigger_type[0] = THROTTLE_ACTIVE, +		.trigger_type[1] = THROTTLE_ACTIVE, +		.trigger_type[2] = SW_TRIP, +		.max_trigger_level = 4, +		.gain = 15, +		.reference_voltage = 7, +		.cal_type = TYPE_ONE_POINT_TRIMMING, +		.min_efuse_value = 40, +		.max_efuse_value = 100, +		.first_point_trim = 25, +		.second_point_trim = 85, +		.default_temp_offset = 50, +		.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, +		.registers = &exynos4210_tmu_registers, +		.features = TMU_SUPPORT_READY_STATUS, +		}, +	}, +	.tmu_count = 1, +}; +#endif + +#if defined(CONFIG_SOC_EXYNOS4412) || defined(CONFIG_SOC_EXYNOS5250) +static const struct exynos_tmu_registers exynos4412_tmu_registers = { +	.triminfo_data = EXYNOS_TMU_REG_TRIMINFO, +	.triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, +	.triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, +	.triminfo_ctrl = EXYNOS_TMU_TRIMINFO_CON, +	.triminfo_reload_shift = EXYNOS_TRIMINFO_RELOAD_SHIFT, +	.tmu_ctrl = EXYNOS_TMU_REG_CONTROL, +	.test_mux_addr_shift = EXYNOS4412_MUX_ADDR_SHIFT, +	.buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, +	.buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, +	.therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT, +	.therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK, +	.therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT, +	.buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, +	.buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, +	.core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, +	.tmu_status = EXYNOS_TMU_REG_STATUS, +	.tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP, +	.threshold_th0 = EXYNOS_THD_TEMP_RISE, +	.threshold_th1 = EXYNOS_THD_TEMP_FALL, +	.tmu_inten = EXYNOS_TMU_REG_INTEN, +	.inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, +	.inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, +	.inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, +	.inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, +	.inten_fall0_shift = EXYNOS_TMU_INTEN_FALL0_SHIFT, +	.tmu_intstat = EXYNOS_TMU_REG_INTSTAT, +	.tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, +	.intclr_fall_shift = EXYNOS_TMU_CLEAR_FALL_INT_SHIFT, +	.intclr_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, +	.intclr_rise_mask = EXYNOS_TMU_RISE_INT_MASK, +	.intclr_fall_mask = EXYNOS_TMU_FALL_INT_MASK, +	.emul_con = EXYNOS_EMUL_CON, +	.emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, +	.emul_time_shift = EXYNOS_EMUL_TIME_SHIFT, +	.emul_time_mask = EXYNOS_EMUL_TIME_MASK, +}; + +#define EXYNOS4412_TMU_DATA \ +	.threshold_falling = 10, \ +	.trigger_levels[0] = 70, \ +	.trigger_levels[1] = 95, \ +	.trigger_levels[2] = 110, \ +	.trigger_levels[3] = 120, \ +	.trigger_enable[0] = true, \ +	.trigger_enable[1] = true, \ +	.trigger_enable[2] = true, \ +	.trigger_enable[3] = false, \ +	.trigger_type[0] = THROTTLE_ACTIVE, \ +	.trigger_type[1] = THROTTLE_ACTIVE, \ +	.trigger_type[2] = SW_TRIP, \ +	.trigger_type[3] = HW_TRIP, \ +	.max_trigger_level = 4, \ +	.gain = 8, \ +	.reference_voltage = 16, \ +	.noise_cancel_mode = 4, \ +	.cal_type = TYPE_ONE_POINT_TRIMMING, \ +	.efuse_value = 55, \ +	.min_efuse_value = 40, \ +	.max_efuse_value = 100, \ +	.first_point_trim = 25, \ +	.second_point_trim = 85, \ +	.default_temp_offset = 50, \ +	.freq_tab[0] = { \ +		.freq_clip_max = 1400 * 1000, \ +		.temp_level = 70, \ +	}, \ +	.freq_tab[1] = { \ +		.freq_clip_max = 400 * 1000, \ +		.temp_level = 95, \ +	}, \ +	.freq_tab_count = 2, \ +	.registers = &exynos4412_tmu_registers, \ +	.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ +			TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ +			TMU_SUPPORT_EMUL_TIME) +#endif + +#if defined(CONFIG_SOC_EXYNOS4412) +struct exynos_tmu_init_data const exynos4412_default_tmu_data = { +	.tmu_data = { +		{ +			EXYNOS4412_TMU_DATA, +			.type = SOC_ARCH_EXYNOS4412, +			.test_mux = EXYNOS4412_MUX_ADDR_VALUE, +		}, +	}, +	.tmu_count = 1, +}; +#endif + +#if defined(CONFIG_SOC_EXYNOS5250) +struct exynos_tmu_init_data const exynos5250_default_tmu_data = { +	.tmu_data = { +		{ +			EXYNOS4412_TMU_DATA, +			.type = SOC_ARCH_EXYNOS5250, +		}, +	}, +	.tmu_count = 1, +}; +#endif + +#if defined(CONFIG_SOC_EXYNOS5260) +static const struct exynos_tmu_registers exynos5260_tmu_registers = { +	.triminfo_data = EXYNOS_TMU_REG_TRIMINFO, +	.triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, +	.triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, +	.tmu_ctrl = EXYNOS_TMU_REG_CONTROL, +	.tmu_ctrl = EXYNOS_TMU_REG_CONTROL1, +	.buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, +	.buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, +	.therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT, +	.therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK, +	.therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT, +	.buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, +	.buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, +	.core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, +	.tmu_status = EXYNOS_TMU_REG_STATUS, +	.tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP, +	.threshold_th0 = EXYNOS_THD_TEMP_RISE, +	.threshold_th1 = EXYNOS_THD_TEMP_FALL, +	.tmu_inten = EXYNOS5260_TMU_REG_INTEN, +	.inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, +	.inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, +	.inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, +	.inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, +	.inten_fall0_shift = EXYNOS_TMU_INTEN_FALL0_SHIFT, +	.tmu_intstat = EXYNOS5260_TMU_REG_INTSTAT, +	.tmu_intclear = EXYNOS5260_TMU_REG_INTCLEAR, +	.intclr_fall_shift = EXYNOS5420_TMU_CLEAR_FALL_INT_SHIFT, +	.intclr_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, +	.intclr_rise_mask = EXYNOS5260_TMU_RISE_INT_MASK, +	.intclr_fall_mask = EXYNOS5260_TMU_FALL_INT_MASK, +	.emul_con = EXYNOS5260_EMUL_CON, +	.emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, +	.emul_time_shift = EXYNOS_EMUL_TIME_SHIFT, +	.emul_time_mask = EXYNOS_EMUL_TIME_MASK, +}; + +#define __EXYNOS5260_TMU_DATA	\ +	.threshold_falling = 10, \ +	.trigger_levels[0] = 85, \ +	.trigger_levels[1] = 103, \ +	.trigger_levels[2] = 110, \ +	.trigger_levels[3] = 120, \ +	.trigger_enable[0] = true, \ +	.trigger_enable[1] = true, \ +	.trigger_enable[2] = true, \ +	.trigger_enable[3] = false, \ +	.trigger_type[0] = THROTTLE_ACTIVE, \ +	.trigger_type[1] = THROTTLE_ACTIVE, \ +	.trigger_type[2] = SW_TRIP, \ +	.trigger_type[3] = HW_TRIP, \ +	.max_trigger_level = 4, \ +	.gain = 8, \ +	.reference_voltage = 16, \ +	.noise_cancel_mode = 4, \ +	.cal_type = TYPE_ONE_POINT_TRIMMING, \ +	.efuse_value = 55, \ +	.min_efuse_value = 40, \ +	.max_efuse_value = 100, \ +	.first_point_trim = 25, \ +	.second_point_trim = 85, \ +	.default_temp_offset = 50, \ +	.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, \ +	.registers = &exynos5260_tmu_registers, \ + +#define EXYNOS5260_TMU_DATA \ +	__EXYNOS5260_TMU_DATA \ +	.type = SOC_ARCH_EXYNOS5260, \ +	.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ +			TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ +			TMU_SUPPORT_EMUL_TIME) + +struct exynos_tmu_init_data const exynos5260_default_tmu_data = { +	.tmu_data = { +		{ EXYNOS5260_TMU_DATA }, +		{ EXYNOS5260_TMU_DATA }, +		{ EXYNOS5260_TMU_DATA }, +		{ EXYNOS5260_TMU_DATA }, +		{ EXYNOS5260_TMU_DATA }, +	}, +	.tmu_count = 5, +}; +#endif + +#if defined(CONFIG_SOC_EXYNOS5420) +static const struct exynos_tmu_registers exynos5420_tmu_registers = { +	.triminfo_data = EXYNOS_TMU_REG_TRIMINFO, +	.triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, +	.triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, +	.tmu_ctrl = EXYNOS_TMU_REG_CONTROL, +	.buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, +	.buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, +	.therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT, +	.therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK, +	.therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT, +	.buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, +	.buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, +	.core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, +	.tmu_status = EXYNOS_TMU_REG_STATUS, +	.tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP, +	.threshold_th0 = EXYNOS_THD_TEMP_RISE, +	.threshold_th1 = EXYNOS_THD_TEMP_FALL, +	.tmu_inten = EXYNOS_TMU_REG_INTEN, +	.inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, +	.inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, +	.inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, +	/* INTEN_RISE3 Not availble in exynos5420 */ +	.inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, +	.inten_fall0_shift = EXYNOS_TMU_INTEN_FALL0_SHIFT, +	.tmu_intstat = EXYNOS_TMU_REG_INTSTAT, +	.tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, +	.intclr_fall_shift = EXYNOS5420_TMU_CLEAR_FALL_INT_SHIFT, +	.intclr_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, +	.intclr_rise_mask = EXYNOS_TMU_RISE_INT_MASK, +	.intclr_fall_mask = EXYNOS_TMU_FALL_INT_MASK, +	.emul_con = EXYNOS_EMUL_CON, +	.emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, +	.emul_time_shift = EXYNOS_EMUL_TIME_SHIFT, +	.emul_time_mask = EXYNOS_EMUL_TIME_MASK, +}; + +#define __EXYNOS5420_TMU_DATA	\ +	.threshold_falling = 10, \ +	.trigger_levels[0] = 85, \ +	.trigger_levels[1] = 103, \ +	.trigger_levels[2] = 110, \ +	.trigger_levels[3] = 120, \ +	.trigger_enable[0] = true, \ +	.trigger_enable[1] = true, \ +	.trigger_enable[2] = true, \ +	.trigger_enable[3] = false, \ +	.trigger_type[0] = THROTTLE_ACTIVE, \ +	.trigger_type[1] = THROTTLE_ACTIVE, \ +	.trigger_type[2] = SW_TRIP, \ +	.trigger_type[3] = HW_TRIP, \ +	.max_trigger_level = 4, \ +	.gain = 8, \ +	.reference_voltage = 16, \ +	.noise_cancel_mode = 4, \ +	.cal_type = TYPE_ONE_POINT_TRIMMING, \ +	.efuse_value = 55, \ +	.min_efuse_value = 40, \ +	.max_efuse_value = 100, \ +	.first_point_trim = 25, \ +	.second_point_trim = 85, \ +	.default_temp_offset = 50, \ +	.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, \ +	.registers = &exynos5420_tmu_registers, \ + +#define EXYNOS5420_TMU_DATA \ +	__EXYNOS5420_TMU_DATA \ +	.type = SOC_ARCH_EXYNOS5250, \ +	.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ +			TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ +			TMU_SUPPORT_EMUL_TIME) + +#define EXYNOS5420_TMU_DATA_SHARED \ +	__EXYNOS5420_TMU_DATA \ +	.type = SOC_ARCH_EXYNOS5420_TRIMINFO, \ +	.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ +			TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ +			TMU_SUPPORT_EMUL_TIME | TMU_SUPPORT_ADDRESS_MULTIPLE) + +struct exynos_tmu_init_data const exynos5420_default_tmu_data = { +	.tmu_data = { +		{ EXYNOS5420_TMU_DATA }, +		{ EXYNOS5420_TMU_DATA }, +		{ EXYNOS5420_TMU_DATA_SHARED }, +		{ EXYNOS5420_TMU_DATA_SHARED }, +		{ EXYNOS5420_TMU_DATA_SHARED }, +	}, +	.tmu_count = 5, +}; +#endif + +#if defined(CONFIG_SOC_EXYNOS5440) +static const struct exynos_tmu_registers exynos5440_tmu_registers = { +	.triminfo_data = EXYNOS5440_TMU_S0_7_TRIM, +	.triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, +	.triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, +	.tmu_ctrl = EXYNOS5440_TMU_S0_7_CTRL, +	.buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, +	.buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, +	.therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT, +	.therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK, +	.therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT, +	.buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, +	.buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, +	.calib_mode_shift = EXYNOS_TMU_CALIB_MODE_SHIFT, +	.calib_mode_mask = EXYNOS_TMU_CALIB_MODE_MASK, +	.core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, +	.tmu_status = EXYNOS5440_TMU_S0_7_STATUS, +	.tmu_cur_temp = EXYNOS5440_TMU_S0_7_TEMP, +	.threshold_th0 = EXYNOS5440_TMU_S0_7_TH0, +	.threshold_th1 = EXYNOS5440_TMU_S0_7_TH1, +	.threshold_th2 = EXYNOS5440_TMU_S0_7_TH2, +	.threshold_th3_l0_shift = EXYNOS5440_TMU_TH_RISE4_SHIFT, +	.tmu_inten = EXYNOS5440_TMU_S0_7_IRQEN, +	.inten_rise0_shift = EXYNOS5440_TMU_INTEN_RISE0_SHIFT, +	.inten_rise1_shift = EXYNOS5440_TMU_INTEN_RISE1_SHIFT, +	.inten_rise2_shift = EXYNOS5440_TMU_INTEN_RISE2_SHIFT, +	.inten_rise3_shift = EXYNOS5440_TMU_INTEN_RISE3_SHIFT, +	.inten_fall0_shift = EXYNOS5440_TMU_INTEN_FALL0_SHIFT, +	.tmu_intstat = EXYNOS5440_TMU_S0_7_IRQ, +	.tmu_intclear = EXYNOS5440_TMU_S0_7_IRQ, +	.intclr_fall_shift = EXYNOS5440_TMU_CLEAR_FALL_INT_SHIFT, +	.intclr_rise_shift = EXYNOS5440_TMU_RISE_INT_SHIFT, +	.intclr_rise_mask = EXYNOS5440_TMU_RISE_INT_MASK, +	.intclr_fall_mask = EXYNOS5440_TMU_FALL_INT_MASK, +	.tmu_irqstatus = EXYNOS5440_TMU_IRQ_STATUS, +	.emul_con = EXYNOS5440_TMU_S0_7_DEBUG, +	.emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, +	.tmu_pmin = EXYNOS5440_TMU_PMIN, +}; + +#define EXYNOS5440_TMU_DATA \ +	.trigger_levels[0] = 100, \ +	.trigger_levels[4] = 105, \ +	.trigger_enable[0] = 1, \ +	.trigger_type[0] = SW_TRIP, \ +	.trigger_type[4] = HW_TRIP, \ +	.max_trigger_level = 5, \ +	.gain = 5, \ +	.reference_voltage = 16, \ +	.noise_cancel_mode = 4, \ +	.cal_type = TYPE_ONE_POINT_TRIMMING, \ +	.cal_mode = 0, \ +	.efuse_value = 0x5b2d, \ +	.min_efuse_value = 16, \ +	.max_efuse_value = 76, \ +	.first_point_trim = 25, \ +	.second_point_trim = 70, \ +	.default_temp_offset = 25, \ +	.type = SOC_ARCH_EXYNOS5440, \ +	.registers = &exynos5440_tmu_registers, \ +	.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_FALLING_TRIP | \ +			TMU_SUPPORT_MULTI_INST | TMU_SUPPORT_ADDRESS_MULTIPLE), + +struct exynos_tmu_init_data const exynos5440_default_tmu_data = { +	.tmu_data = { +		{ EXYNOS5440_TMU_DATA } , +		{ EXYNOS5440_TMU_DATA } , +		{ EXYNOS5440_TMU_DATA } , +	}, +	.tmu_count = 3, +}; +#endif diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h new file mode 100644 index 00000000000..d268981b65e --- /dev/null +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -0,0 +1,193 @@ +/* + * exynos_tmu_data.h - Samsung EXYNOS tmu data header file + * + *  Copyright (C) 2013 Samsung Electronics + *  Amit Daniel Kachhap <amit.daniel@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + */ + +#ifndef _EXYNOS_TMU_DATA_H +#define _EXYNOS_TMU_DATA_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_TEMP_MASK		0xff +#define EXYNOS_TMU_REF_VOLTAGE_SHIFT	24 +#define EXYNOS_TMU_REF_VOLTAGE_MASK	0x1f +#define EXYNOS_TMU_BUF_SLOPE_SEL_MASK	0xf +#define EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT	8 +#define EXYNOS_TMU_CORE_EN_SHIFT	0 + +/* 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_TRIG_LEVEL_MASK	0x1111 +#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_SHIFT	1 +#define EXYNOS_TRIMINFO_25_SHIFT	0 +#define EXYNOS_TRIMINFO_85_SHIFT	8 +#define EXYNOS_TMU_RISE_INT_MASK	0x111 +#define EXYNOS_TMU_RISE_INT_SHIFT	0 +#define EXYNOS_TMU_FALL_INT_MASK	0x111 +#define EXYNOS_TMU_CLEAR_RISE_INT	0x111 +#define EXYNOS_TMU_CLEAR_FALL_INT	(0x111 << 12) +#define EXYNOS_TMU_CLEAR_FALL_INT_SHIFT	12 +#define EXYNOS5420_TMU_CLEAR_FALL_INT_SHIFT	16 +#define EXYNOS5440_TMU_CLEAR_FALL_INT_SHIFT	4 +#define EXYNOS_TMU_TRIP_MODE_SHIFT	13 +#define EXYNOS_TMU_TRIP_MODE_MASK	0x7 +#define EXYNOS_TMU_THERM_TRIP_EN_SHIFT	12 +#define EXYNOS_TMU_CALIB_MODE_SHIFT	4 +#define EXYNOS_TMU_CALIB_MODE_MASK	0x3 + +#define EXYNOS_TMU_INTEN_RISE0_SHIFT	0 +#define EXYNOS_TMU_INTEN_RISE1_SHIFT	4 +#define EXYNOS_TMU_INTEN_RISE2_SHIFT	8 +#define EXYNOS_TMU_INTEN_RISE3_SHIFT	12 +#define EXYNOS_TMU_INTEN_FALL0_SHIFT	16 +#define EXYNOS_TMU_INTEN_FALL1_SHIFT	20 +#define EXYNOS_TMU_INTEN_FALL2_SHIFT	24 +#define EXYNOS_TMU_INTEN_FALL3_SHIFT	28 + +#define EXYNOS_EMUL_TIME	0x57F0 +#define EXYNOS_EMUL_TIME_MASK	0xffff +#define EXYNOS_EMUL_TIME_SHIFT	16 +#define EXYNOS_EMUL_DATA_SHIFT	8 +#define EXYNOS_EMUL_DATA_MASK	0xFF +#define EXYNOS_EMUL_ENABLE	0x1 + +#define EXYNOS_MAX_TRIGGER_PER_REG	4 + +/* Exynos5260 specific */ +#define EXYNOS_TMU_REG_CONTROL1			0x24 +#define EXYNOS5260_TMU_REG_INTEN		0xC0 +#define EXYNOS5260_TMU_REG_INTSTAT		0xC4 +#define EXYNOS5260_TMU_REG_INTCLEAR		0xC8 +#define EXYNOS5260_TMU_CLEAR_RISE_INT		0x1111 +#define EXYNOS5260_TMU_CLEAR_FALL_INT		(0x1111 << 16) +#define EXYNOS5260_TMU_RISE_INT_MASK		0x1111 +#define EXYNOS5260_TMU_FALL_INT_MASK		0x1111 +#define EXYNOS5260_EMUL_CON			0x100 + +/* Exynos4412 specific */ +#define EXYNOS4412_MUX_ADDR_VALUE          6 +#define EXYNOS4412_MUX_ADDR_SHIFT          20 + +/*exynos5440 specific registers*/ +#define EXYNOS5440_TMU_S0_7_TRIM		0x000 +#define EXYNOS5440_TMU_S0_7_CTRL		0x020 +#define EXYNOS5440_TMU_S0_7_DEBUG		0x040 +#define EXYNOS5440_TMU_S0_7_STATUS		0x060 +#define EXYNOS5440_TMU_S0_7_TEMP		0x0f0 +#define EXYNOS5440_TMU_S0_7_TH0			0x110 +#define EXYNOS5440_TMU_S0_7_TH1			0x130 +#define EXYNOS5440_TMU_S0_7_TH2			0x150 +#define EXYNOS5440_TMU_S0_7_EVTEN		0x1F0 +#define EXYNOS5440_TMU_S0_7_IRQEN		0x210 +#define EXYNOS5440_TMU_S0_7_IRQ			0x230 +/* exynos5440 common registers */ +#define EXYNOS5440_TMU_IRQ_STATUS		0x000 +#define EXYNOS5440_TMU_PMIN			0x004 +#define EXYNOS5440_TMU_TEMP			0x008 + +#define EXYNOS5440_TMU_RISE_INT_MASK		0xf +#define EXYNOS5440_TMU_RISE_INT_SHIFT		0 +#define EXYNOS5440_TMU_FALL_INT_MASK		0xf +#define EXYNOS5440_TMU_INTEN_RISE0_SHIFT	0 +#define EXYNOS5440_TMU_INTEN_RISE1_SHIFT	1 +#define EXYNOS5440_TMU_INTEN_RISE2_SHIFT	2 +#define EXYNOS5440_TMU_INTEN_RISE3_SHIFT	3 +#define EXYNOS5440_TMU_INTEN_FALL0_SHIFT	4 +#define EXYNOS5440_TMU_INTEN_FALL1_SHIFT	5 +#define EXYNOS5440_TMU_INTEN_FALL2_SHIFT	6 +#define EXYNOS5440_TMU_INTEN_FALL3_SHIFT	7 +#define EXYNOS5440_TMU_TH_RISE0_SHIFT		0 +#define EXYNOS5440_TMU_TH_RISE1_SHIFT		8 +#define EXYNOS5440_TMU_TH_RISE2_SHIFT		16 +#define EXYNOS5440_TMU_TH_RISE3_SHIFT		24 +#define EXYNOS5440_TMU_TH_RISE4_SHIFT		24 +#define EXYNOS5440_EFUSE_SWAP_OFFSET		8 + +#if defined(CONFIG_CPU_EXYNOS4210) +extern struct exynos_tmu_init_data const exynos4210_default_tmu_data; +#define EXYNOS4210_TMU_DRV_DATA (&exynos4210_default_tmu_data) +#else +#define EXYNOS4210_TMU_DRV_DATA (NULL) +#endif + +#if defined(CONFIG_SOC_EXYNOS4412) +extern struct exynos_tmu_init_data const exynos4412_default_tmu_data; +#define EXYNOS4412_TMU_DRV_DATA (&exynos4412_default_tmu_data) +#else +#define EXYNOS4412_TMU_DRV_DATA (NULL) +#endif + +#if defined(CONFIG_SOC_EXYNOS5250) +extern struct exynos_tmu_init_data const exynos5250_default_tmu_data; +#define EXYNOS5250_TMU_DRV_DATA (&exynos5250_default_tmu_data) +#else +#define EXYNOS5250_TMU_DRV_DATA (NULL) +#endif + +#if defined(CONFIG_SOC_EXYNOS5260) +extern struct exynos_tmu_init_data const exynos5260_default_tmu_data; +#define EXYNOS5260_TMU_DRV_DATA (&exynos5260_default_tmu_data) +#else +#define EXYNOS5260_TMU_DRV_DATA (NULL) +#endif + +#if defined(CONFIG_SOC_EXYNOS5420) +extern struct exynos_tmu_init_data const exynos5420_default_tmu_data; +#define EXYNOS5420_TMU_DRV_DATA (&exynos5420_default_tmu_data) +#else +#define EXYNOS5420_TMU_DRV_DATA (NULL) +#endif + +#if defined(CONFIG_SOC_EXYNOS5440) +extern struct exynos_tmu_init_data const exynos5440_default_tmu_data; +#define EXYNOS5440_TMU_DRV_DATA (&exynos5440_default_tmu_data) +#else +#define EXYNOS5440_TMU_DRV_DATA (NULL) +#endif + +#endif /*_EXYNOS_TMU_DATA_H*/ diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c new file mode 100644 index 00000000000..1e2193fc324 --- /dev/null +++ b/drivers/thermal/spear_thermal.c @@ -0,0 +1,199 @@ +/* + * 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 *res; +	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; +	} + +	stdev = devm_kzalloc(&pdev->dev, sizeof(*stdev), GFP_KERNEL); +	if (!stdev) +		return -ENOMEM; + +	/* Enable thermal sensor */ +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	stdev->thermal_base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(stdev->thermal_base)) +		return PTR_ERR(stdev->thermal_base); + +	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); + +	/* 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 = 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/step_wise.c b/drivers/thermal/step_wise.c new file mode 100644 index 00000000000..f251521baaa --- /dev/null +++ b/drivers/thermal/step_wise.c @@ -0,0 +1,212 @@ +/* + *  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 + */ +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; +	unsigned long next_target; + +	/* +	 * We keep this instance the way it is by default. +	 * Otherwise, we use the current state of the +	 * cdev in use to determine the next_target. +	 */ +	cdev->ops->get_cur_state(cdev, &cur_state); +	next_target = instance->target; +	dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state); + +	switch (trend) { +	case THERMAL_TREND_RAISING: +		if (throttle) { +			next_target = cur_state < instance->upper ? +				    (cur_state + 1) : instance->upper; +			if (next_target < instance->lower) +				next_target = instance->lower; +		} +		break; +	case THERMAL_TREND_RAISE_FULL: +		if (throttle) +			next_target = instance->upper; +		break; +	case THERMAL_TREND_DROPPING: +		if (cur_state == instance->lower) { +			if (!throttle) +				next_target = THERMAL_NO_TARGET; +		} else { +			next_target = cur_state - 1; +			if (next_target > instance->upper) +				next_target = instance->upper; +		} +		break; +	case THERMAL_TREND_DROP_FULL: +		if (cur_state == instance->lower) { +			if (!throttle) +				next_target = THERMAL_NO_TARGET; +		} else +			next_target = instance->lower; +		break; +	default: +		break; +	} + +	return next_target; +} + +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); + +		if (old_target == instance->target) +			continue; + +		/* Activate a passive thermal instance */ +		if (old_target == THERMAL_NO_TARGET && +			instance->target != THERMAL_NO_TARGET) +			update_passive_instance(tz, trip_type, 1); +		/* Deactivate a passive thermal instance */ +		else if (old_target != THERMAL_NO_TARGET && +			instance->target == THERMAL_NO_TARGET) +			update_passive_instance(tz, trip_type, -1); + + +		instance->cdev->updated = false; /* cdev needs update */ +	} + +	mutex_unlock(&tz->lock); +} + +/** + * step_wise_throttle - throttles devices asscciated with the given zone + * @tz - thermal_zone_device + * @trip - the trip point + * @trip_type - type of the trip point + * + * Throttling Logic: This uses the trend of the thermal zone to throttle. + * If the thermal zone is 'heating up' this throttles all the cooling + * devices associated with the zone and its particular trip point, by one + * step. If the zone is 'cooling down' it brings back the performance of + * the devices by one step. + */ +static int step_wise_throttle(struct thermal_zone_device *tz, int trip) +{ +	struct thermal_instance *instance; + +	thermal_zone_trip_update(tz, trip); + +	if (tz->forced_passive) +		thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE); + +	mutex_lock(&tz->lock); + +	list_for_each_entry(instance, &tz->thermal_instances, tz_node) +		thermal_cdev_update(instance->cdev); + +	mutex_unlock(&tz->lock); + +	return 0; +} + +static struct thermal_governor thermal_gov_step_wise = { +	.name		= "step_wise", +	.throttle	= step_wise_throttle, +}; + +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 00000000000..71b0ec0c370 --- /dev/null +++ b/drivers/thermal/thermal_core.c @@ -0,0 +1,1854 @@ +/* + *  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->emul_temperature || !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, +			unsigned long *limits) +{ +	int i, ret; + +	for (i = 0; i < tz->trips; i++) { +		if (mask & (1 << i)) { +			unsigned long upper, lower; + +			upper = THERMAL_NO_LIMIT; +			lower = THERMAL_NO_LIMIT; +			if (limits) { +				lower = limits[i * 2]; +				upper = limits[i * 2 + 1]; +			} +			ret = thermal_zone_bind_cooling_device(tz, i, cdev, +							       upper, lower); +			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, +			       tzp->tbp[i].binding_limits); +		} +	} + +	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, +			       tzp->tbp[i].binding_limits); +		} +	} +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; +	char name[THERMAL_NAME_LENGTH]; + +	snprintf(name, sizeof(name), "%s", buf); + +	mutex_lock(&thermal_governor_lock); + +	gov = __find_governor(strim(name)); +	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 if(!strncmp(dev_name(dev), "cooling_device", +			sizeof("cooling_device") - 1)){ +		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 || !ops->get_trip_temp)) +		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 const struct genl_multicast_group thermal_event_mcgrps[] = { +	{ .name = THERMAL_GENL_MCAST_GROUP_NAME, }, +}; + +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, +	.mcgrps = thermal_event_mcgrps, +	.n_mcgrps = ARRAY_SIZE(thermal_event_mcgrps), +}; + +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(&thermal_event_genl_family, skb, 0, +				   0, 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) +{ +	return genl_register_family(&thermal_event_genl_family); +} + +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 00000000000..3db339fb636 --- /dev/null +++ b/drivers/thermal/thermal_core.h @@ -0,0 +1,89 @@ +/* + *  thermal_core.h + * + *  Copyright (C) 2012  Intel Corp + *  Author: Durgadoss R <durgadoss.r@intel.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; version 2 of the License. + * + *  This program is distributed in the hope that it will be useful, but + *  WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + *  General Public License for more details. + * + *  You should have received a copy of the GNU General Public License along + *  with this program; if not, write to the Free Software Foundation, Inc., + *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#ifndef __THERMAL_CORE_H__ +#define __THERMAL_CORE_H__ + +#include <linux/device.h> +#include <linux/thermal.h> + +/* Initial state of a cooling device during binding */ +#define THERMAL_NO_TARGET -1UL + +/* + * This structure is used to describe the behavior of + * a certain cooling device on a certain trip point + * in a certain thermal zone + */ +struct thermal_instance { +	int id; +	char name[THERMAL_NAME_LENGTH]; +	struct thermal_zone_device *tz; +	struct thermal_cooling_device *cdev; +	int trip; +	unsigned long upper;	/* Highest cooling state for this trip point */ +	unsigned long lower;	/* Lowest cooling state for this trip point */ +	unsigned long target;	/* expected cooling state */ +	char attr_name[THERMAL_NAME_LENGTH]; +	struct device_attribute attr; +	struct list_head tz_node; /* node in tz->thermal_instances */ +	struct list_head cdev_node; /* node in cdev->thermal_instances */ +}; + +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 00000000000..1967bee4f07 --- /dev/null +++ b/drivers/thermal/thermal_hwmon.c @@ -0,0 +1,272 @@ +/* + *  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; +} + +static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) +{ +	unsigned long temp; +	return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); +} + +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 (thermal_zone_crit_temp_valid(tz)) { +		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 (thermal_zone_crit_temp_valid(tz)) +		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 00000000000..c798fdb2ae4 --- /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/thermal_sys.c b/drivers/thermal/thermal_sys.c deleted file mode 100644 index 13c72c62932..00000000000 --- a/drivers/thermal/thermal_sys.c +++ /dev/null @@ -1,1241 +0,0 @@ -/* - *  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. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -#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/spinlock.h> -#include <linux/reboot.h> - -MODULE_AUTHOR("Zhang Rui"); -MODULE_DESCRIPTION("Generic thermal management sysfs support"); -MODULE_LICENSE("GPL"); - -#define PREFIX "Thermal: " - -struct thermal_cooling_device_instance { -	int id; -	char name[THERMAL_NAME_LENGTH]; -	struct thermal_zone_device *tz; -	struct thermal_cooling_device *cdev; -	int trip; -	char attr_name[THERMAL_NAME_LENGTH]; -	struct device_attribute attr; -	struct list_head node; -}; - -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 DEFINE_MUTEX(thermal_list_lock); - -static int get_idr(struct idr *idr, struct mutex *lock, int *id) -{ -	int err; - -      again: -	if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0)) -		return -ENOMEM; - -	if (lock) -		mutex_lock(lock); -	err = idr_get_new(idr, NULL, id); -	if (lock) -		mutex_unlock(lock); -	if (unlikely(err == -EAGAIN)) -		goto again; -	else if (unlikely(err)) -		return err; - -	*id = *id & MAX_ID_MASK; -	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); -} - -/* 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; - -	if (!tz->ops->get_temp) -		return -EPERM; - -	ret = tz->ops->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"))) -		result = tz->ops->set_mode(tz, THERMAL_DEVICE_ENABLED); -	else if (!strncmp(buf, "disabled", sizeof("disabled"))) -		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_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 -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); -		} -		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->tc1 = 1; -	tz->tc2 = 1; - -	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 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 struct device_attribute trip_point_attrs[] = { -	__ATTR(trip_point_0_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_0_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_1_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_1_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_2_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_2_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_3_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_3_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_4_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_4_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_5_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_5_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_6_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_6_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_7_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_7_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_8_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_8_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_9_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_9_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_10_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_10_temp, 0444, trip_point_temp_show, NULL), -	__ATTR(trip_point_11_type, 0444, trip_point_type_show, NULL), -	__ATTR(trip_point_11_temp, 0444, trip_point_temp_show, NULL), -}; - -#define TRIP_POINT_ATTR_ADD(_dev, _index, result)     \ -do {    \ -	result = device_create_file(_dev,	\ -				&trip_point_attrs[_index * 2]);	\ -	if (result)	\ -		break;	\ -	result = device_create_file(_dev,	\ -			&trip_point_attrs[_index * 2 + 1]);	\ -} while (0) - -#define TRIP_POINT_ATTR_REMOVE(_dev, _index)	\ -do {	\ -	device_remove_file(_dev, &trip_point_attrs[_index * 2]);	\ -	device_remove_file(_dev, &trip_point_attrs[_index * 2 + 1]);	\ -} while (0) - -/* 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_cooling_device_instance *instance; - -	instance = -	    container_of(attr, struct thermal_cooling_device_instance, attr); - -	if (instance->trip == THERMAL_TRIPS_NONE) -		return sprintf(buf, "-1\n"); -	else -		return sprintf(buf, "%d\n", instance->trip); -} - -/* Device management */ - -#if defined(CONFIG_THERMAL_HWMON) - -/* hwmon sys I/F */ -#include <linux/hwmon.h> -static LIST_HEAD(thermal_hwmon_list); - -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_zone_device *tz -			= container_of(hwmon_attr, struct thermal_zone_device, -				       temp_input); - -	ret = tz->ops->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_zone_device *tz -			= container_of(hwmon_attr, struct thermal_zone_device, -				       temp_crit); -	long temperature; -	int ret; - -	ret = tz->ops->get_trip_temp(tz, 0, &temperature); -	if (ret) -		return ret; - -	return sprintf(buf, "%ld\n", temperature); -} - - -static int -thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) -{ -	struct thermal_hwmon_device *hwmon; -	int new_hwmon_device = 1; -	int result; - -	mutex_lock(&thermal_list_lock); -	list_for_each_entry(hwmon, &thermal_hwmon_list, node) -		if (!strcmp(hwmon->type, tz->type)) { -			new_hwmon_device = 0; -			mutex_unlock(&thermal_list_lock); -			goto register_sys_interface; -		} -	mutex_unlock(&thermal_list_lock); - -	hwmon = kzalloc(sizeof(struct thermal_hwmon_device), 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 unregister_hwmon_device; - - register_sys_interface: -	tz->hwmon = hwmon; -	hwmon->count++; - -	snprintf(tz->temp_input.name, THERMAL_NAME_LENGTH, -		 "temp%d_input", hwmon->count); -	tz->temp_input.attr.attr.name = tz->temp_input.name; -	tz->temp_input.attr.attr.mode = 0444; -	tz->temp_input.attr.show = temp_input_show; -	sysfs_attr_init(&tz->temp_input.attr.attr); -	result = device_create_file(hwmon->device, &tz->temp_input.attr); -	if (result) -		goto unregister_hwmon_device; - -	if (tz->ops->get_crit_temp) { -		unsigned long temperature; -		if (!tz->ops->get_crit_temp(tz, &temperature)) { -			snprintf(tz->temp_crit.name, THERMAL_NAME_LENGTH, -				"temp%d_crit", hwmon->count); -			tz->temp_crit.attr.attr.name = tz->temp_crit.name; -			tz->temp_crit.attr.attr.mode = 0444; -			tz->temp_crit.attr.show = temp_crit_show; -			sysfs_attr_init(&tz->temp_crit.attr.attr); -			result = device_create_file(hwmon->device, -						    &tz->temp_crit.attr); -			if (result) -				goto unregister_hwmon_device; -		} -	} - -	mutex_lock(&thermal_list_lock); -	if (new_hwmon_device) -		list_add_tail(&hwmon->node, &thermal_hwmon_list); -	list_add_tail(&tz->hwmon_node, &hwmon->tz_list); -	mutex_unlock(&thermal_list_lock); - -	return 0; - - unregister_hwmon_device: -	device_remove_file(hwmon->device, &tz->temp_crit.attr); -	device_remove_file(hwmon->device, &tz->temp_input.attr); -	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; -} - -static void -thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) -{ -	struct thermal_hwmon_device *hwmon = tz->hwmon; - -	tz->hwmon = NULL; -	device_remove_file(hwmon->device, &tz->temp_input.attr); -	device_remove_file(hwmon->device, &tz->temp_crit.attr); - -	mutex_lock(&thermal_list_lock); -	list_del(&tz->hwmon_node); -	if (!list_empty(&hwmon->tz_list)) { -		mutex_unlock(&thermal_list_lock); -		return; -	} -	list_del(&hwmon->node); -	mutex_unlock(&thermal_list_lock); - -	device_remove_file(hwmon->device, &dev_attr_name); -	hwmon_device_unregister(hwmon->device); -	kfree(hwmon); -} -#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 - -static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, -					    int delay) -{ -	cancel_delayed_work(&(tz->poll_queue)); - -	if (!delay) -		return; - -	if (delay > 1000) -		schedule_delayed_work(&(tz->poll_queue), -				      round_jiffies(msecs_to_jiffies(delay))); -	else -		schedule_delayed_work(&(tz->poll_queue), -				      msecs_to_jiffies(delay)); -} - -static void thermal_zone_device_passive(struct thermal_zone_device *tz, -					int temp, int trip_temp, int trip) -{ -	int trend = 0; -	struct thermal_cooling_device_instance *instance; -	struct thermal_cooling_device *cdev; -	long state, max_state; - -	/* -	 * Above Trip? -	 * ----------- -	 * Calculate the thermal trend (using the passive cooling equation) -	 * and modify the performance limit for all passive cooling devices -	 * accordingly.  Note that we assume symmetry. -	 */ -	if (temp >= trip_temp) { -		tz->passive = true; - -		trend = (tz->tc1 * (temp - tz->last_temperature)) + -			(tz->tc2 * (temp - trip_temp)); - -		/* Heating up? */ -		if (trend > 0) { -			list_for_each_entry(instance, &tz->cooling_devices, -					    node) { -				if (instance->trip != trip) -					continue; -				cdev = instance->cdev; -				cdev->ops->get_cur_state(cdev, &state); -				cdev->ops->get_max_state(cdev, &max_state); -				if (state++ < max_state) -					cdev->ops->set_cur_state(cdev, state); -			} -		} else if (trend < 0) { /* Cooling off? */ -			list_for_each_entry(instance, &tz->cooling_devices, -					    node) { -				if (instance->trip != trip) -					continue; -				cdev = instance->cdev; -				cdev->ops->get_cur_state(cdev, &state); -				cdev->ops->get_max_state(cdev, &max_state); -				if (state > 0) -					cdev->ops->set_cur_state(cdev, --state); -			} -		} -		return; -	} - -	/* -	 * Below Trip? -	 * ----------- -	 * Implement passive cooling hysteresis to slowly increase performance -	 * and avoid thrashing around the passive trip point.  Note that we -	 * assume symmetry. -	 */ -	list_for_each_entry(instance, &tz->cooling_devices, node) { -		if (instance->trip != trip) -			continue; -		cdev = instance->cdev; -		cdev->ops->get_cur_state(cdev, &state); -		cdev->ops->get_max_state(cdev, &max_state); -		if (state > 0) -			cdev->ops->set_cur_state(cdev, --state); -		if (state == 0) -			tz->passive = false; -	} -} - -static void thermal_zone_device_check(struct work_struct *work) -{ -	struct thermal_zone_device *tz = container_of(work, struct -						      thermal_zone_device, -						      poll_queue.work); -	thermal_zone_device_update(tz); -} - -/** - * thermal_zone_bind_cooling_device - bind a cooling device to a thermal zone - * @tz:		thermal zone device - * @trip:	indicates which trip point the cooling devices is - *		associated with in this thermal zone. - * @cdev:	thermal cooling device - * - * This function is usually called in the thermal zone device .bind callback. - */ -int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, -				     int trip, -				     struct thermal_cooling_device *cdev) -{ -	struct thermal_cooling_device_instance *dev; -	struct thermal_cooling_device_instance *pos; -	struct thermal_zone_device *pos1; -	struct thermal_cooling_device *pos2; -	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; - -	dev = -	    kzalloc(sizeof(struct thermal_cooling_device_instance), GFP_KERNEL); -	if (!dev) -		return -ENOMEM; -	dev->tz = tz; -	dev->cdev = cdev; -	dev->trip = trip; -	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); -	list_for_each_entry(pos, &tz->cooling_devices, node) -	    if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { -		result = -EEXIST; -		break; -	} -	if (!result) -		list_add_tail(&dev->node, &tz->cooling_devices); -	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(thermal_zone_bind_cooling_device); - -/** - * thermal_zone_unbind_cooling_device - unbind a cooling device from a thermal zone - * @tz:		thermal zone device - * @trip:	indicates which trip point the cooling devices is - *		associated with in this thermal zone. - * @cdev:	thermal cooling device - * - * This function is usually called in the thermal zone device .unbind callback. - */ -int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz, -				       int trip, -				       struct thermal_cooling_device *cdev) -{ -	struct thermal_cooling_device_instance *pos, *next; - -	mutex_lock(&tz->lock); -	list_for_each_entry_safe(pos, next, &tz->cooling_devices, node) { -		if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { -			list_del(&pos->node); -			mutex_unlock(&tz->lock); -			goto unbind; -		} -	} -	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(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 - * @type:	the thermal cooling device type. - * @devdata:	device private data. - * @ops:		standard thermal cooling devices callbacks. - */ -struct thermal_cooling_device *thermal_cooling_device_register(char *type, -							       void *devdata, -							       struct -							       thermal_cooling_device_ops -							       *ops) -{ -	struct thermal_cooling_device *cdev; -	struct thermal_zone_device *pos; -	int result; - -	if (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); -	} - -	strcpy(cdev->type, type); -	cdev->ops = ops; -	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; - -	mutex_lock(&thermal_list_lock); -	list_add(&cdev->node, &thermal_cdev_list); -	list_for_each_entry(pos, &thermal_tz_list, node) { -		if (!pos->ops->bind) -			continue; -		result = pos->ops->bind(pos, cdev); -		if (result) -			break; - -	} -	mutex_unlock(&thermal_list_lock); - -	if (!result) -		return cdev; - -      unregister: -	release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); -	device_unregister(&cdev->device); -	return ERR_PTR(result); -} - -EXPORT_SYMBOL(thermal_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) -{ -	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); -	list_for_each_entry(tz, &thermal_tz_list, node) { -		if (!tz->ops->unbind) -			continue; -		tz->ops->unbind(tz, cdev); -	} -	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(thermal_cooling_device_unregister); - -/** - * thermal_zone_device_update - force an update of a thermal zone's state - * @ttz:	the thermal zone to update - */ - -void thermal_zone_device_update(struct thermal_zone_device *tz) -{ -	int count, ret = 0; -	long temp, trip_temp; -	enum thermal_trip_type trip_type; -	struct thermal_cooling_device_instance *instance; -	struct thermal_cooling_device *cdev; - -	mutex_lock(&tz->lock); - -	if (tz->ops->get_temp(tz, &temp)) { -		/* get_temp failed - retry it later */ -		printk(KERN_WARNING PREFIX "failed to read out thermal zone " -		       "%d\n", tz->id); -		goto leave; -	} - -	for (count = 0; count < tz->trips; count++) { -		tz->ops->get_trip_type(tz, count, &trip_type); -		tz->ops->get_trip_temp(tz, count, &trip_temp); - -		switch (trip_type) { -		case THERMAL_TRIP_CRITICAL: -			if (temp >= trip_temp) { -				if (tz->ops->notify) -					ret = tz->ops->notify(tz, count, -							      trip_type); -				if (!ret) { -					printk(KERN_EMERG -					       "Critical temperature reached (%ld C), shutting down.\n", -					       temp/1000); -					orderly_poweroff(true); -				} -			} -			break; -		case THERMAL_TRIP_HOT: -			if (temp >= trip_temp) -				if (tz->ops->notify) -					tz->ops->notify(tz, count, trip_type); -			break; -		case THERMAL_TRIP_ACTIVE: -			list_for_each_entry(instance, &tz->cooling_devices, -					    node) { -				if (instance->trip != count) -					continue; - -				cdev = instance->cdev; - -				if (temp >= trip_temp) -					cdev->ops->set_cur_state(cdev, 1); -				else -					cdev->ops->set_cur_state(cdev, 0); -			} -			break; -		case THERMAL_TRIP_PASSIVE: -			if (temp >= trip_temp || tz->passive) -				thermal_zone_device_passive(tz, temp, -							    trip_temp, count); -			break; -		} -	} - -	if (tz->forced_passive) -		thermal_zone_device_passive(tz, temp, tz->forced_passive, -					    THERMAL_TRIPS_NONE); - -	tz->last_temperature = temp; - -      leave: -	if (tz->passive) -		thermal_zone_device_set_polling(tz, tz->passive_delay); -	else if (tz->polling_delay) -		thermal_zone_device_set_polling(tz, tz->polling_delay); -	else -		thermal_zone_device_set_polling(tz, 0); -	mutex_unlock(&tz->lock); -} -EXPORT_SYMBOL(thermal_zone_device_update); - -/** - * 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 - * @devdata:	private device data - * @ops:	standard thermal zone device callbacks - * @tc1:	thermal coefficient 1 for passive calculations - * @tc2:	thermal coefficient 2 for passive calculations - * @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) - * - * thermal_zone_device_unregister() must be called when the device is no - * longer needed. The passive cooling formula uses tc1 and tc2 as described in - * section 11.1.5.1 of the ACPI specification 3.0. - */ -struct thermal_zone_device *thermal_zone_device_register(char *type, -							 int trips, -							 void *devdata, struct -							 thermal_zone_device_ops -							 *ops, int tc1, int -							 tc2, -							 int passive_delay, -							 int polling_delay) -{ -	struct thermal_zone_device *tz; -	struct thermal_cooling_device *pos; -	enum thermal_trip_type trip_type; -	int result; -	int count; -	int passive = 0; - -	if (strlen(type) >= THERMAL_NAME_LENGTH) -		return ERR_PTR(-EINVAL); - -	if (trips > THERMAL_MAX_TRIPS || trips < 0) -		return ERR_PTR(-EINVAL); - -	if (!ops || !ops->get_temp) -		return ERR_PTR(-EINVAL); - -	tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL); -	if (!tz) -		return ERR_PTR(-ENOMEM); - -	INIT_LIST_HEAD(&tz->cooling_devices); -	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); -	} - -	strcpy(tz->type, type); -	tz->ops = ops; -	tz->device.class = &thermal_class; -	tz->devdata = devdata; -	tz->trips = trips; -	tz->tc1 = tc1; -	tz->tc2 = tc2; -	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; -	} - -	for (count = 0; count < trips; count++) { -		TRIP_POINT_ATTR_ADD(&tz->device, count, result); -		if (result) -			goto unregister; -		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; - -	result = thermal_add_hwmon_sysfs(tz); -	if (result) -		goto unregister; - -	mutex_lock(&thermal_list_lock); -	list_add_tail(&tz->node, &thermal_tz_list); -	if (ops->bind) -		list_for_each_entry(pos, &thermal_cdev_list, node) { -		result = ops->bind(tz, pos); -		if (result) -			break; -		} -	mutex_unlock(&thermal_list_lock); - -	INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check); - -	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(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) -{ -	struct thermal_cooling_device *cdev; -	struct thermal_zone_device *pos = NULL; -	int count; - -	if (!tz) -		return; - -	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); -	if (tz->ops->unbind) -		list_for_each_entry(cdev, &thermal_cdev_list, node) -		    tz->ops->unbind(tz, cdev); -	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); - -	for (count = 0; count < tz->trips; count++) -		TRIP_POINT_ATTR_REMOVE(&tz->device, count); - -	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(thermal_zone_device_unregister); - -static int __init thermal_init(void) -{ -	int result = 0; - -	result = class_register(&thermal_class); -	if (result) { -		idr_destroy(&thermal_tz_idr); -		idr_destroy(&thermal_cdev_idr); -		mutex_destroy(&thermal_idr_lock); -		mutex_destroy(&thermal_list_lock); -	} -	return result; -} - -static void __exit thermal_exit(void) -{ -	class_unregister(&thermal_class); -	idr_destroy(&thermal_tz_idr); -	idr_destroy(&thermal_cdev_idr); -	mutex_destroy(&thermal_idr_lock); -	mutex_destroy(&thermal_list_lock); -} - -subsys_initcall(thermal_init); -module_exit(thermal_exit); diff --git a/drivers/thermal/ti-soc-thermal/Kconfig b/drivers/thermal/ti-soc-thermal/Kconfig new file mode 100644 index 00000000000..bd4c7beba67 --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/Kconfig @@ -0,0 +1,60 @@ +config TI_SOC_THERMAL +	tristate "Texas Instruments SoCs temperature sensor driver" +	depends on THERMAL +	depends on ARCH_HAS_BANDGAP +	help +	  If you say yes here you get support for the Texas Instruments +	  OMAP4460+ on die bandgap temperature sensor support. The register +	  set is part of system control module. + +	  This includes alert interrupts generation and also the TSHUT +	  support. + +config TI_THERMAL +	bool "Texas Instruments SoCs thermal framework support" +	depends on TI_SOC_THERMAL +	depends on CPU_THERMAL +	help +	  If you say yes here you want to get support for generic thermal +	  framework for the Texas Instruments on die bandgap temperature sensor. + +	  This includes trip points definitions, extrapolation rules and +	  CPU cooling device bindings. + +config OMAP4_THERMAL +	bool "Texas Instruments OMAP4 thermal support" +	depends on TI_SOC_THERMAL +	depends on ARCH_OMAP4 +	help +	  If you say yes here you get thermal support for the Texas Instruments +	  OMAP4 SoC family. The current chip supported are: +	   - OMAP4430 +	   - OMAP4460 +	   - OMAP4470 + +	  This includes alert interrupts generation and also the TSHUT +	  support. + +config OMAP5_THERMAL +	bool "Texas Instruments OMAP5 thermal support" +	depends on TI_SOC_THERMAL +	depends on SOC_OMAP5 +	help +	  If you say yes here you get thermal support for the Texas Instruments +	  OMAP5 SoC family. The current chip supported are: +	   - OMAP5430 + +	  This includes alert interrupts generation and also the TSHUT +	  support. + +config DRA752_THERMAL +	bool "Texas Instruments DRA752 thermal support" +	depends on TI_SOC_THERMAL +	depends on SOC_DRA7XX +	help +	  If you say yes here you get thermal support for the Texas Instruments +	  DRA752 SoC family. The current chip supported are: +	   - DRA752 + +	  This includes alert interrupts generation and also the TSHUT +	  support. diff --git a/drivers/thermal/ti-soc-thermal/Makefile b/drivers/thermal/ti-soc-thermal/Makefile new file mode 100644 index 00000000000..1226b2484e5 --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_TI_SOC_THERMAL)		+= ti-soc-thermal.o +ti-soc-thermal-y			:= ti-bandgap.o +ti-soc-thermal-$(CONFIG_TI_THERMAL)	+= ti-thermal-common.o +ti-soc-thermal-$(CONFIG_DRA752_THERMAL)	+= dra752-thermal-data.o +ti-soc-thermal-$(CONFIG_OMAP4_THERMAL)	+= omap4-thermal-data.o +ti-soc-thermal-$(CONFIG_OMAP5_THERMAL)	+= omap5-thermal-data.o diff --git a/drivers/thermal/ti-soc-thermal/TODO b/drivers/thermal/ti-soc-thermal/TODO new file mode 100644 index 00000000000..7da787d1924 --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/TODO @@ -0,0 +1,12 @@ +List of TODOs (by Eduardo Valentin) + +on ti-bandgap.c: +- Revisit PM support + +on ti-thermal-common.c/ti-thermal.h: +- Revisit need for locking + +generally: +- make sure this code works on OMAP4430, OMAP4460 and OMAP5430 + +Copy patches to Eduardo Valentin <eduardo.valentin@ti.com> diff --git a/drivers/thermal/ti-soc-thermal/dra752-bandgap.h b/drivers/thermal/ti-soc-thermal/dra752-bandgap.h new file mode 100644 index 00000000000..6b0f2b1160f --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/dra752-bandgap.h @@ -0,0 +1,280 @@ +/* + * DRA752 bandgap registers, bitfields and temperature definitions + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/ + * Contact: + *   Eduardo Valentin <eduardo.valentin@ti.com> + *   Tero Kristo <t-kristo@ti.com> + * + * This is an auto generated file. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef __DRA752_BANDGAP_H +#define __DRA752_BANDGAP_H + +/** + * *** DRA752 *** + * + * Below, in sequence, are the Register definitions, + * the bitfields and the temperature definitions for DRA752. + */ + +/** + * DRA752 register definitions + * + * Registers are defined as offsets. The offsets are + * relative to FUSE_OPP_BGAP_GPU on DRA752. + * DRA752_BANDGAP_BASE		0x4a0021e0 + * + * Register below are grouped by domain (not necessarily in offset order) + */ + + +/* DRA752.common register offsets */ +#define DRA752_BANDGAP_CTRL_1_OFFSET		0x1a0 +#define DRA752_BANDGAP_STATUS_1_OFFSET		0x1c8 +#define DRA752_BANDGAP_CTRL_2_OFFSET		0x39c +#define DRA752_BANDGAP_STATUS_2_OFFSET		0x3b8 + +/* DRA752.core register offsets */ +#define DRA752_STD_FUSE_OPP_BGAP_CORE_OFFSET		0x8 +#define DRA752_TEMP_SENSOR_CORE_OFFSET			0x154 +#define DRA752_BANDGAP_THRESHOLD_CORE_OFFSET		0x1ac +#define DRA752_BANDGAP_TSHUT_CORE_OFFSET		0x1b8 +#define DRA752_BANDGAP_CUMUL_DTEMP_CORE_OFFSET		0x1c4 +#define DRA752_DTEMP_CORE_0_OFFSET			0x208 +#define DRA752_DTEMP_CORE_1_OFFSET			0x20c +#define DRA752_DTEMP_CORE_2_OFFSET			0x210 +#define DRA752_DTEMP_CORE_3_OFFSET			0x214 +#define DRA752_DTEMP_CORE_4_OFFSET			0x218 + +/* DRA752.iva register offsets */ +#define DRA752_STD_FUSE_OPP_BGAP_IVA_OFFSET		0x388 +#define DRA752_TEMP_SENSOR_IVA_OFFSET			0x398 +#define DRA752_BANDGAP_THRESHOLD_IVA_OFFSET		0x3a4 +#define DRA752_BANDGAP_TSHUT_IVA_OFFSET			0x3ac +#define DRA752_BANDGAP_CUMUL_DTEMP_IVA_OFFSET		0x3b4 +#define DRA752_DTEMP_IVA_0_OFFSET			0x3d0 +#define DRA752_DTEMP_IVA_1_OFFSET			0x3d4 +#define DRA752_DTEMP_IVA_2_OFFSET			0x3d8 +#define DRA752_DTEMP_IVA_3_OFFSET			0x3dc +#define DRA752_DTEMP_IVA_4_OFFSET			0x3e0 + +/* DRA752.mpu register offsets */ +#define DRA752_STD_FUSE_OPP_BGAP_MPU_OFFSET		0x4 +#define DRA752_TEMP_SENSOR_MPU_OFFSET			0x14c +#define DRA752_BANDGAP_THRESHOLD_MPU_OFFSET		0x1a4 +#define DRA752_BANDGAP_TSHUT_MPU_OFFSET			0x1b0 +#define DRA752_BANDGAP_CUMUL_DTEMP_MPU_OFFSET		0x1bc +#define DRA752_DTEMP_MPU_0_OFFSET			0x1e0 +#define DRA752_DTEMP_MPU_1_OFFSET			0x1e4 +#define DRA752_DTEMP_MPU_2_OFFSET			0x1e8 +#define DRA752_DTEMP_MPU_3_OFFSET			0x1ec +#define DRA752_DTEMP_MPU_4_OFFSET			0x1f0 + +/* DRA752.dspeve register offsets */ +#define DRA752_STD_FUSE_OPP_BGAP_DSPEVE_OFFSET			0x384 +#define DRA752_TEMP_SENSOR_DSPEVE_OFFSET			0x394 +#define DRA752_BANDGAP_THRESHOLD_DSPEVE_OFFSET			0x3a0 +#define DRA752_BANDGAP_TSHUT_DSPEVE_OFFSET			0x3a8 +#define DRA752_BANDGAP_CUMUL_DTEMP_DSPEVE_OFFSET		0x3b0 +#define DRA752_DTEMP_DSPEVE_0_OFFSET				0x3bc +#define DRA752_DTEMP_DSPEVE_1_OFFSET				0x3c0 +#define DRA752_DTEMP_DSPEVE_2_OFFSET				0x3c4 +#define DRA752_DTEMP_DSPEVE_3_OFFSET				0x3c8 +#define DRA752_DTEMP_DSPEVE_4_OFFSET				0x3cc + +/* DRA752.gpu register offsets */ +#define DRA752_STD_FUSE_OPP_BGAP_GPU_OFFSET		0x0 +#define DRA752_TEMP_SENSOR_GPU_OFFSET			0x150 +#define DRA752_BANDGAP_THRESHOLD_GPU_OFFSET		0x1a8 +#define DRA752_BANDGAP_TSHUT_GPU_OFFSET			0x1b4 +#define DRA752_BANDGAP_CUMUL_DTEMP_GPU_OFFSET		0x1c0 +#define DRA752_DTEMP_GPU_0_OFFSET			0x1f4 +#define DRA752_DTEMP_GPU_1_OFFSET			0x1f8 +#define DRA752_DTEMP_GPU_2_OFFSET			0x1fc +#define DRA752_DTEMP_GPU_3_OFFSET			0x200 +#define DRA752_DTEMP_GPU_4_OFFSET			0x204 + +/** + * Register bitfields for DRA752 + * + * All the macros bellow define the required bits for + * controlling temperature on DRA752. Bit defines are + * grouped by register. + */ + +/* DRA752.BANDGAP_STATUS_1 */ +#define DRA752_BANDGAP_STATUS_1_ALERT_MASK		BIT(31) +#define DRA752_BANDGAP_STATUS_1_HOT_CORE_MASK		BIT(5) +#define DRA752_BANDGAP_STATUS_1_COLD_CORE_MASK		BIT(4) +#define DRA752_BANDGAP_STATUS_1_HOT_GPU_MASK		BIT(3) +#define DRA752_BANDGAP_STATUS_1_COLD_GPU_MASK		BIT(2) +#define DRA752_BANDGAP_STATUS_1_HOT_MPU_MASK		BIT(1) +#define DRA752_BANDGAP_STATUS_1_COLD_MPU_MASK		BIT(0) + +/* DRA752.BANDGAP_CTRL_2 */ +#define DRA752_BANDGAP_CTRL_2_FREEZE_IVA_MASK			BIT(22) +#define DRA752_BANDGAP_CTRL_2_FREEZE_DSPEVE_MASK		BIT(21) +#define DRA752_BANDGAP_CTRL_2_CLEAR_IVA_MASK			BIT(19) +#define DRA752_BANDGAP_CTRL_2_CLEAR_DSPEVE_MASK			BIT(18) +#define DRA752_BANDGAP_CTRL_2_CLEAR_ACCUM_IVA_MASK		BIT(16) +#define DRA752_BANDGAP_CTRL_2_CLEAR_ACCUM_DSPEVE_MASK		BIT(15) +#define DRA752_BANDGAP_CTRL_2_MASK_HOT_IVA_MASK			BIT(3) +#define DRA752_BANDGAP_CTRL_2_MASK_COLD_IVA_MASK		BIT(2) +#define DRA752_BANDGAP_CTRL_2_MASK_HOT_DSPEVE_MASK		BIT(1) +#define DRA752_BANDGAP_CTRL_2_MASK_COLD_DSPEVE_MASK		BIT(0) + +/* DRA752.BANDGAP_STATUS_2 */ +#define DRA752_BANDGAP_STATUS_2_HOT_IVA_MASK			BIT(3) +#define DRA752_BANDGAP_STATUS_2_COLD_IVA_MASK			BIT(2) +#define DRA752_BANDGAP_STATUS_2_HOT_DSPEVE_MASK			BIT(1) +#define DRA752_BANDGAP_STATUS_2_COLD_DSPEVE_MASK		BIT(0) + +/* DRA752.BANDGAP_CTRL_1 */ +#define DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK			(0x3 << 30) +#define DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK		(0x7 << 27) +#define DRA752_BANDGAP_CTRL_1_FREEZE_CORE_MASK			BIT(23) +#define DRA752_BANDGAP_CTRL_1_FREEZE_GPU_MASK			BIT(22) +#define DRA752_BANDGAP_CTRL_1_FREEZE_MPU_MASK			BIT(21) +#define DRA752_BANDGAP_CTRL_1_CLEAR_CORE_MASK			BIT(20) +#define DRA752_BANDGAP_CTRL_1_CLEAR_GPU_MASK			BIT(19) +#define DRA752_BANDGAP_CTRL_1_CLEAR_MPU_MASK			BIT(18) +#define DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_CORE_MASK		BIT(17) +#define DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_GPU_MASK		BIT(16) +#define DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_MPU_MASK		BIT(15) +#define DRA752_BANDGAP_CTRL_1_MASK_HOT_CORE_MASK		BIT(5) +#define DRA752_BANDGAP_CTRL_1_MASK_COLD_CORE_MASK		BIT(4) +#define DRA752_BANDGAP_CTRL_1_MASK_HOT_GPU_MASK			BIT(3) +#define DRA752_BANDGAP_CTRL_1_MASK_COLD_GPU_MASK		BIT(2) +#define DRA752_BANDGAP_CTRL_1_MASK_HOT_MPU_MASK			BIT(1) +#define DRA752_BANDGAP_CTRL_1_MASK_COLD_MPU_MASK		BIT(0) + +/* DRA752.TEMP_SENSOR */ +#define DRA752_TEMP_SENSOR_TMPSOFF_MASK		BIT(11) +#define DRA752_TEMP_SENSOR_EOCZ_MASK		BIT(10) +#define DRA752_TEMP_SENSOR_DTEMP_MASK		(0x3ff << 0) + +/* DRA752.BANDGAP_THRESHOLD */ +#define DRA752_BANDGAP_THRESHOLD_HOT_MASK		(0x3ff << 16) +#define DRA752_BANDGAP_THRESHOLD_COLD_MASK		(0x3ff << 0) + +/* DRA752.TSHUT_THRESHOLD */ +#define DRA752_TSHUT_THRESHOLD_MUXCTRL_MASK		BIT(31) +#define DRA752_TSHUT_THRESHOLD_HOT_MASK			(0x3ff << 16) +#define DRA752_TSHUT_THRESHOLD_COLD_MASK		(0x3ff << 0) + +/* DRA752.BANDGAP_CUMUL_DTEMP_CORE */ +#define DRA752_BANDGAP_CUMUL_DTEMP_CORE_MASK		(0xffffffff << 0) + +/* DRA752.BANDGAP_CUMUL_DTEMP_IVA */ +#define DRA752_BANDGAP_CUMUL_DTEMP_IVA_MASK		(0xffffffff << 0) + +/* DRA752.BANDGAP_CUMUL_DTEMP_MPU */ +#define DRA752_BANDGAP_CUMUL_DTEMP_MPU_MASK		(0xffffffff << 0) + +/* DRA752.BANDGAP_CUMUL_DTEMP_DSPEVE */ +#define DRA752_BANDGAP_CUMUL_DTEMP_DSPEVE_MASK		(0xffffffff << 0) + +/* DRA752.BANDGAP_CUMUL_DTEMP_GPU */ +#define DRA752_BANDGAP_CUMUL_DTEMP_GPU_MASK		(0xffffffff << 0) + +/** + * Temperature limits and thresholds for DRA752 + * + * All the macros bellow are definitions for handling the + * ADC conversions and representation of temperature limits + * and thresholds for DRA752. Definitions are grouped + * by temperature domain. + */ + +/* DRA752.common temperature definitions */ +/* ADC conversion table limits */ +#define DRA752_ADC_START_VALUE		540 +#define DRA752_ADC_END_VALUE		945 + +/* DRA752.GPU temperature definitions */ +/* bandgap clock limits */ +#define DRA752_GPU_MAX_FREQ				1500000 +#define DRA752_GPU_MIN_FREQ				1000000 +/* sensor limits */ +#define DRA752_GPU_MIN_TEMP				-40000 +#define DRA752_GPU_MAX_TEMP				125000 +#define DRA752_GPU_HYST_VAL				5000 +/* interrupts thresholds */ +#define DRA752_GPU_TSHUT_HOT				915 +#define DRA752_GPU_TSHUT_COLD				900 +#define DRA752_GPU_T_HOT				800 +#define DRA752_GPU_T_COLD				795 + +/* DRA752.MPU temperature definitions */ +/* bandgap clock limits */ +#define DRA752_MPU_MAX_FREQ				1500000 +#define DRA752_MPU_MIN_FREQ				1000000 +/* sensor limits */ +#define DRA752_MPU_MIN_TEMP				-40000 +#define DRA752_MPU_MAX_TEMP				125000 +#define DRA752_MPU_HYST_VAL				5000 +/* interrupts thresholds */ +#define DRA752_MPU_TSHUT_HOT				915 +#define DRA752_MPU_TSHUT_COLD				900 +#define DRA752_MPU_T_HOT				800 +#define DRA752_MPU_T_COLD				795 + +/* DRA752.CORE temperature definitions */ +/* bandgap clock limits */ +#define DRA752_CORE_MAX_FREQ				1500000 +#define DRA752_CORE_MIN_FREQ				1000000 +/* sensor limits */ +#define DRA752_CORE_MIN_TEMP				-40000 +#define DRA752_CORE_MAX_TEMP				125000 +#define DRA752_CORE_HYST_VAL				5000 +/* interrupts thresholds */ +#define DRA752_CORE_TSHUT_HOT				915 +#define DRA752_CORE_TSHUT_COLD				900 +#define DRA752_CORE_T_HOT				800 +#define DRA752_CORE_T_COLD				795 + +/* DRA752.DSPEVE temperature definitions */ +/* bandgap clock limits */ +#define DRA752_DSPEVE_MAX_FREQ				1500000 +#define DRA752_DSPEVE_MIN_FREQ				1000000 +/* sensor limits */ +#define DRA752_DSPEVE_MIN_TEMP				-40000 +#define DRA752_DSPEVE_MAX_TEMP				125000 +#define DRA752_DSPEVE_HYST_VAL				5000 +/* interrupts thresholds */ +#define DRA752_DSPEVE_TSHUT_HOT				915 +#define DRA752_DSPEVE_TSHUT_COLD			900 +#define DRA752_DSPEVE_T_HOT				800 +#define DRA752_DSPEVE_T_COLD				795 + +/* DRA752.IVA temperature definitions */ +/* bandgap clock limits */ +#define DRA752_IVA_MAX_FREQ				1500000 +#define DRA752_IVA_MIN_FREQ				1000000 +/* sensor limits */ +#define DRA752_IVA_MIN_TEMP				-40000 +#define DRA752_IVA_MAX_TEMP				125000 +#define DRA752_IVA_HYST_VAL				5000 +/* interrupts thresholds */ +#define DRA752_IVA_TSHUT_HOT				915 +#define DRA752_IVA_TSHUT_COLD				900 +#define DRA752_IVA_T_HOT				800 +#define DRA752_IVA_T_COLD				795 + +#endif /* __DRA752_BANDGAP_H */ diff --git a/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c new file mode 100644 index 00000000000..a4929272074 --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c @@ -0,0 +1,481 @@ +/* + * DRA752 thermal data. + * + * Copyright (C) 2013 Texas Instruments Inc. + * Contact: + *	Eduardo Valentin <eduardo.valentin@ti.com> + *	Tero Kristo <t-kristo@ti.com> + * + * This file is partially autogenerated. + * + * 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 "ti-thermal.h" +#include "ti-bandgap.h" +#include "dra752-bandgap.h" + +/* + * DRA752 has five instances of thermal sensor: MPU, GPU, CORE, + * IVA and DSPEVE need to describe the individual registers and + * bit fields. + */ + +/* + * DRA752 CORE thermal sensor register offsets and bit-fields + */ +static struct temp_sensor_registers +dra752_core_temp_sensor_registers = { +	.temp_sensor_ctrl = DRA752_TEMP_SENSOR_CORE_OFFSET, +	.bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK, +	.bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK, +	.bgap_mask_ctrl = DRA752_BANDGAP_CTRL_1_OFFSET, +	.mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_CORE_MASK, +	.mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_CORE_MASK, +	.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, +	.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, +	.mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_CORE_MASK, +	.mask_clear_mask = DRA752_BANDGAP_CTRL_1_CLEAR_CORE_MASK, +	.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_CORE_MASK, +	.bgap_threshold = DRA752_BANDGAP_THRESHOLD_CORE_OFFSET, +	.threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK, +	.threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK, +	.tshut_threshold = DRA752_BANDGAP_TSHUT_CORE_OFFSET, +	.tshut_hot_mask = DRA752_TSHUT_THRESHOLD_HOT_MASK, +	.tshut_cold_mask = DRA752_TSHUT_THRESHOLD_COLD_MASK, +	.bgap_status = DRA752_BANDGAP_STATUS_1_OFFSET, +	.status_bgap_alert_mask = DRA752_BANDGAP_STATUS_1_ALERT_MASK, +	.status_hot_mask = DRA752_BANDGAP_STATUS_1_HOT_CORE_MASK, +	.status_cold_mask = DRA752_BANDGAP_STATUS_1_COLD_CORE_MASK, +	.bgap_cumul_dtemp = DRA752_BANDGAP_CUMUL_DTEMP_CORE_OFFSET, +	.ctrl_dtemp_0 = DRA752_DTEMP_CORE_0_OFFSET, +	.ctrl_dtemp_1 = DRA752_DTEMP_CORE_1_OFFSET, +	.ctrl_dtemp_2 = DRA752_DTEMP_CORE_2_OFFSET, +	.ctrl_dtemp_3 = DRA752_DTEMP_CORE_3_OFFSET, +	.ctrl_dtemp_4 = DRA752_DTEMP_CORE_4_OFFSET, +	.bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_CORE_OFFSET, +}; + +/* + * DRA752 IVA thermal sensor register offsets and bit-fields + */ +static struct temp_sensor_registers +dra752_iva_temp_sensor_registers = { +	.temp_sensor_ctrl = DRA752_TEMP_SENSOR_IVA_OFFSET, +	.bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK, +	.bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK, +	.bgap_mask_ctrl = DRA752_BANDGAP_CTRL_2_OFFSET, +	.mask_hot_mask = DRA752_BANDGAP_CTRL_2_MASK_HOT_IVA_MASK, +	.mask_cold_mask = DRA752_BANDGAP_CTRL_2_MASK_COLD_IVA_MASK, +	.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, +	.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, +	.mask_freeze_mask = DRA752_BANDGAP_CTRL_2_FREEZE_IVA_MASK, +	.mask_clear_mask = DRA752_BANDGAP_CTRL_2_CLEAR_IVA_MASK, +	.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_2_CLEAR_ACCUM_IVA_MASK, +	.bgap_threshold = DRA752_BANDGAP_THRESHOLD_IVA_OFFSET, +	.threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK, +	.threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK, +	.tshut_threshold = DRA752_BANDGAP_TSHUT_IVA_OFFSET, +	.tshut_hot_mask = DRA752_TSHUT_THRESHOLD_HOT_MASK, +	.tshut_cold_mask = DRA752_TSHUT_THRESHOLD_COLD_MASK, +	.bgap_status = DRA752_BANDGAP_STATUS_2_OFFSET, +	.status_bgap_alert_mask = DRA752_BANDGAP_STATUS_1_ALERT_MASK, +	.status_hot_mask = DRA752_BANDGAP_STATUS_2_HOT_IVA_MASK, +	.status_cold_mask = DRA752_BANDGAP_STATUS_2_COLD_IVA_MASK, +	.bgap_cumul_dtemp = DRA752_BANDGAP_CUMUL_DTEMP_IVA_OFFSET, +	.ctrl_dtemp_0 = DRA752_DTEMP_IVA_0_OFFSET, +	.ctrl_dtemp_1 = DRA752_DTEMP_IVA_1_OFFSET, +	.ctrl_dtemp_2 = DRA752_DTEMP_IVA_2_OFFSET, +	.ctrl_dtemp_3 = DRA752_DTEMP_IVA_3_OFFSET, +	.ctrl_dtemp_4 = DRA752_DTEMP_IVA_4_OFFSET, +	.bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_IVA_OFFSET, +}; + +/* + * DRA752 MPU thermal sensor register offsets and bit-fields + */ +static struct temp_sensor_registers +dra752_mpu_temp_sensor_registers = { +	.temp_sensor_ctrl = DRA752_TEMP_SENSOR_MPU_OFFSET, +	.bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK, +	.bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK, +	.bgap_mask_ctrl = DRA752_BANDGAP_CTRL_1_OFFSET, +	.mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_MPU_MASK, +	.mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_MPU_MASK, +	.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, +	.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, +	.mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_MPU_MASK, +	.mask_clear_mask = DRA752_BANDGAP_CTRL_1_CLEAR_MPU_MASK, +	.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_MPU_MASK, +	.bgap_threshold = DRA752_BANDGAP_THRESHOLD_MPU_OFFSET, +	.threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK, +	.threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK, +	.tshut_threshold = DRA752_BANDGAP_TSHUT_MPU_OFFSET, +	.tshut_hot_mask = DRA752_TSHUT_THRESHOLD_HOT_MASK, +	.tshut_cold_mask = DRA752_TSHUT_THRESHOLD_COLD_MASK, +	.bgap_status = DRA752_BANDGAP_STATUS_1_OFFSET, +	.status_bgap_alert_mask = DRA752_BANDGAP_STATUS_1_ALERT_MASK, +	.status_hot_mask = DRA752_BANDGAP_STATUS_1_HOT_MPU_MASK, +	.status_cold_mask = DRA752_BANDGAP_STATUS_1_COLD_MPU_MASK, +	.bgap_cumul_dtemp = DRA752_BANDGAP_CUMUL_DTEMP_MPU_OFFSET, +	.ctrl_dtemp_0 = DRA752_DTEMP_MPU_0_OFFSET, +	.ctrl_dtemp_1 = DRA752_DTEMP_MPU_1_OFFSET, +	.ctrl_dtemp_2 = DRA752_DTEMP_MPU_2_OFFSET, +	.ctrl_dtemp_3 = DRA752_DTEMP_MPU_3_OFFSET, +	.ctrl_dtemp_4 = DRA752_DTEMP_MPU_4_OFFSET, +	.bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_MPU_OFFSET, +}; + +/* + * DRA752 DSPEVE thermal sensor register offsets and bit-fields + */ +static struct temp_sensor_registers +dra752_dspeve_temp_sensor_registers = { +	.temp_sensor_ctrl = DRA752_TEMP_SENSOR_DSPEVE_OFFSET, +	.bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK, +	.bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK, +	.bgap_mask_ctrl = DRA752_BANDGAP_CTRL_2_OFFSET, +	.mask_hot_mask = DRA752_BANDGAP_CTRL_2_MASK_HOT_DSPEVE_MASK, +	.mask_cold_mask = DRA752_BANDGAP_CTRL_2_MASK_COLD_DSPEVE_MASK, +	.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, +	.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, +	.mask_freeze_mask = DRA752_BANDGAP_CTRL_2_FREEZE_DSPEVE_MASK, +	.mask_clear_mask = DRA752_BANDGAP_CTRL_2_CLEAR_DSPEVE_MASK, +	.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_2_CLEAR_ACCUM_DSPEVE_MASK, +	.bgap_threshold = DRA752_BANDGAP_THRESHOLD_DSPEVE_OFFSET, +	.threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK, +	.threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK, +	.tshut_threshold = DRA752_BANDGAP_TSHUT_DSPEVE_OFFSET, +	.tshut_hot_mask = DRA752_TSHUT_THRESHOLD_HOT_MASK, +	.tshut_cold_mask = DRA752_TSHUT_THRESHOLD_COLD_MASK, +	.bgap_status = DRA752_BANDGAP_STATUS_2_OFFSET, +	.status_bgap_alert_mask = DRA752_BANDGAP_STATUS_1_ALERT_MASK, +	.status_hot_mask = DRA752_BANDGAP_STATUS_2_HOT_DSPEVE_MASK, +	.status_cold_mask = DRA752_BANDGAP_STATUS_2_COLD_DSPEVE_MASK, +	.bgap_cumul_dtemp = DRA752_BANDGAP_CUMUL_DTEMP_DSPEVE_OFFSET, +	.ctrl_dtemp_0 = DRA752_DTEMP_DSPEVE_0_OFFSET, +	.ctrl_dtemp_1 = DRA752_DTEMP_DSPEVE_1_OFFSET, +	.ctrl_dtemp_2 = DRA752_DTEMP_DSPEVE_2_OFFSET, +	.ctrl_dtemp_3 = DRA752_DTEMP_DSPEVE_3_OFFSET, +	.ctrl_dtemp_4 = DRA752_DTEMP_DSPEVE_4_OFFSET, +	.bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_DSPEVE_OFFSET, +}; + +/* + * DRA752 GPU thermal sensor register offsets and bit-fields + */ +static struct temp_sensor_registers +dra752_gpu_temp_sensor_registers = { +	.temp_sensor_ctrl = DRA752_TEMP_SENSOR_GPU_OFFSET, +	.bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK, +	.bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK, +	.bgap_mask_ctrl = DRA752_BANDGAP_CTRL_1_OFFSET, +	.mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_GPU_MASK, +	.mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_GPU_MASK, +	.mask_sidlemode_mask = DRA752_BANDGAP_CTRL_1_SIDLEMODE_MASK, +	.mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK, +	.mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_GPU_MASK, +	.mask_clear_mask = DRA752_BANDGAP_CTRL_1_CLEAR_GPU_MASK, +	.mask_clear_accum_mask = DRA752_BANDGAP_CTRL_1_CLEAR_ACCUM_GPU_MASK, +	.bgap_threshold = DRA752_BANDGAP_THRESHOLD_GPU_OFFSET, +	.threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK, +	.threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK, +	.tshut_threshold = DRA752_BANDGAP_TSHUT_GPU_OFFSET, +	.tshut_hot_mask = DRA752_TSHUT_THRESHOLD_HOT_MASK, +	.tshut_cold_mask = DRA752_TSHUT_THRESHOLD_COLD_MASK, +	.bgap_status = DRA752_BANDGAP_STATUS_1_OFFSET, +	.status_bgap_alert_mask = DRA752_BANDGAP_STATUS_1_ALERT_MASK, +	.status_hot_mask = DRA752_BANDGAP_STATUS_1_HOT_GPU_MASK, +	.status_cold_mask = DRA752_BANDGAP_STATUS_1_COLD_GPU_MASK, +	.bgap_cumul_dtemp = DRA752_BANDGAP_CUMUL_DTEMP_GPU_OFFSET, +	.ctrl_dtemp_0 = DRA752_DTEMP_GPU_0_OFFSET, +	.ctrl_dtemp_1 = DRA752_DTEMP_GPU_1_OFFSET, +	.ctrl_dtemp_2 = DRA752_DTEMP_GPU_2_OFFSET, +	.ctrl_dtemp_3 = DRA752_DTEMP_GPU_3_OFFSET, +	.ctrl_dtemp_4 = DRA752_DTEMP_GPU_4_OFFSET, +	.bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_GPU_OFFSET, +}; + +/* Thresholds and limits for DRA752 MPU temperature sensor */ +static struct temp_sensor_data dra752_mpu_temp_sensor_data = { +	.tshut_hot = DRA752_MPU_TSHUT_HOT, +	.tshut_cold = DRA752_MPU_TSHUT_COLD, +	.t_hot = DRA752_MPU_T_HOT, +	.t_cold = DRA752_MPU_T_COLD, +	.min_freq = DRA752_MPU_MIN_FREQ, +	.max_freq = DRA752_MPU_MAX_FREQ, +	.max_temp = DRA752_MPU_MAX_TEMP, +	.min_temp = DRA752_MPU_MIN_TEMP, +	.hyst_val = DRA752_MPU_HYST_VAL, +	.update_int1 = 1000, +	.update_int2 = 2000, +}; + +/* Thresholds and limits for DRA752 GPU temperature sensor */ +static struct temp_sensor_data dra752_gpu_temp_sensor_data = { +	.tshut_hot = DRA752_GPU_TSHUT_HOT, +	.tshut_cold = DRA752_GPU_TSHUT_COLD, +	.t_hot = DRA752_GPU_T_HOT, +	.t_cold = DRA752_GPU_T_COLD, +	.min_freq = DRA752_GPU_MIN_FREQ, +	.max_freq = DRA752_GPU_MAX_FREQ, +	.max_temp = DRA752_GPU_MAX_TEMP, +	.min_temp = DRA752_GPU_MIN_TEMP, +	.hyst_val = DRA752_GPU_HYST_VAL, +	.update_int1 = 1000, +	.update_int2 = 2000, +}; + +/* Thresholds and limits for DRA752 CORE temperature sensor */ +static struct temp_sensor_data dra752_core_temp_sensor_data = { +	.tshut_hot = DRA752_CORE_TSHUT_HOT, +	.tshut_cold = DRA752_CORE_TSHUT_COLD, +	.t_hot = DRA752_CORE_T_HOT, +	.t_cold = DRA752_CORE_T_COLD, +	.min_freq = DRA752_CORE_MIN_FREQ, +	.max_freq = DRA752_CORE_MAX_FREQ, +	.max_temp = DRA752_CORE_MAX_TEMP, +	.min_temp = DRA752_CORE_MIN_TEMP, +	.hyst_val = DRA752_CORE_HYST_VAL, +	.update_int1 = 1000, +	.update_int2 = 2000, +}; + +/* Thresholds and limits for DRA752 DSPEVE temperature sensor */ +static struct temp_sensor_data dra752_dspeve_temp_sensor_data = { +	.tshut_hot = DRA752_DSPEVE_TSHUT_HOT, +	.tshut_cold = DRA752_DSPEVE_TSHUT_COLD, +	.t_hot = DRA752_DSPEVE_T_HOT, +	.t_cold = DRA752_DSPEVE_T_COLD, +	.min_freq = DRA752_DSPEVE_MIN_FREQ, +	.max_freq = DRA752_DSPEVE_MAX_FREQ, +	.max_temp = DRA752_DSPEVE_MAX_TEMP, +	.min_temp = DRA752_DSPEVE_MIN_TEMP, +	.hyst_val = DRA752_DSPEVE_HYST_VAL, +	.update_int1 = 1000, +	.update_int2 = 2000, +}; + +/* Thresholds and limits for DRA752 IVA temperature sensor */ +static struct temp_sensor_data dra752_iva_temp_sensor_data = { +	.tshut_hot = DRA752_IVA_TSHUT_HOT, +	.tshut_cold = DRA752_IVA_TSHUT_COLD, +	.t_hot = DRA752_IVA_T_HOT, +	.t_cold = DRA752_IVA_T_COLD, +	.min_freq = DRA752_IVA_MIN_FREQ, +	.max_freq = DRA752_IVA_MAX_FREQ, +	.max_temp = DRA752_IVA_MAX_TEMP, +	.min_temp = DRA752_IVA_MIN_TEMP, +	.hyst_val = DRA752_IVA_HYST_VAL, +	.update_int1 = 1000, +	.update_int2 = 2000, +}; + +/* + * DRA752 : Temperature values in milli degree celsius + * ADC code values from 540 to 945 + */ +static +int dra752_adc_to_temp[DRA752_ADC_END_VALUE - DRA752_ADC_START_VALUE + 1] = { +	/* Index 540 - 549 */ +	-40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200, +	-37800, +	/* Index 550 - 559 */ +	-37400, -37000, -36600, -36200, -35800, -35300, -34700, -34200, -33800, +	-33400, +	/* Index 560 - 569 */ +	-33000, -32600, -32200, -31800, -31400, -31000, -30600, -30200, -29800, +	-29400, +	/* Index 570 - 579 */ +	-29000, -28600, -28200, -27700, -27100, -26600, -26200, -25800, -25400, +	-25000, +	/* Index 580 - 589 */ +	-24600, -24200, -23800, -23400, -23000, -22600, -22200, -21800, -21400, +	-21000, +	/* Index 590 - 599 */ +	-20500, -19900, -19400, -19000, -18600, -18200, -17800, -17400, -17000, +	-16600, +	/* Index 600 - 609 */ +	-16200, -15800, -15400, -15000, -14600, -14200, -13800, -13400, -13000, +	-12500, +	/* Index 610 - 619 */ +	-11900, -11400, -11000, -10600, -10200, -9800, -9400, -9000, -8600, +	-8200, +	/* Index 620 - 629 */ +	-7800, -7400, -7000, -6600, -6200, -5800, -5400, -5000, -4500, +	-3900, +	/* Index 630 - 639 */ +	-3400, -3000, -2600, -2200, -1800, -1400, -1000, -600, -200, +	200, +	/* Index 640 - 649 */ +	600, 1000, 1400, 1800, 2200, 2600, 3000, 3400, 3900, +	4500, +	/* Index 650 - 659 */ +	5000, 5400, 5800, 6200, 6600, 7000, 7400, 7800, 8200, +	8600, +	/* Index 660 - 669 */ +	9000, 9400, 9800, 10200, 10600, 11000, 11400, 11800, 12200, +	12700, +	/* Index 670 - 679 */ +	13300, 13800, 14200, 14600, 15000, 15400, 15800, 16200, 16600, +	17000, +	/* Index 680 - 689 */ +	17400, 17800, 18200, 18600, 19000, 19400, 19800, 20200, 20600, +	21000, +	/* Index 690 - 699 */ +	21400, 21900, 22500, 23000, 23400, 23800, 24200, 24600, 25000, +	25400, +	/* Index 700 - 709 */ +	25800, 26200, 26600, 27000, 27400, 27800, 28200, 28600, 29000, +	29400, +	/* Index 710 - 719 */ +	29800, 30200, 30600, 31000, 31400, 31900, 32500, 33000, 33400, +	33800, +	/* Index 720 - 729 */ +	34200, 34600, 35000, 35400, 35800, 36200, 36600, 37000, 37400, +	37800, +	/* Index 730 - 739 */ +	38200, 38600, 39000, 39400, 39800, 40200, 40600, 41000, 41400, +	41800, +	/* Index 740 - 749 */ +	42200, 42600, 43100, 43700, 44200, 44600, 45000, 45400, 45800, +	46200, +	/* Index 750 - 759 */ +	46600, 47000, 47400, 47800, 48200, 48600, 49000, 49400, 49800, +	50200, +	/* Index 760 - 769 */ +	50600, 51000, 51400, 51800, 52200, 52600, 53000, 53400, 53800, +	54200, +	/* Index 770 - 779 */ +	54600, 55000, 55400, 55900, 56500, 57000, 57400, 57800, 58200, +	58600, +	/* Index 780 - 789 */ +	59000, 59400, 59800, 60200, 60600, 61000, 61400, 61800, 62200, +	62600, +	/* Index 790 - 799 */ +	63000, 63400, 63800, 64200, 64600, 65000, 65400, 65800, 66200, +	66600, +	/* Index 800 - 809 */ +	67000, 67400, 67800, 68200, 68600, 69000, 69400, 69800, 70200, +	70600, +	/* Index 810 - 819 */ +	71000, 71500, 72100, 72600, 73000, 73400, 73800, 74200, 74600, +	75000, +	/* Index 820 - 829 */ +	75400, 75800, 76200, 76600, 77000, 77400, 77800, 78200, 78600, +	79000, +	/* Index 830 - 839 */ +	79400, 79800, 80200, 80600, 81000, 81400, 81800, 82200, 82600, +	83000, +	/* Index 840 - 849 */ +	83400, 83800, 84200, 84600, 85000, 85400, 85800, 86200, 86600, +	87000, +	/* Index 850 - 859 */ +	87400, 87800, 88200, 88600, 89000, 89400, 89800, 90200, 90600, +	91000, +	/* Index 860 - 869 */ +	91400, 91800, 92200, 92600, 93000, 93400, 93800, 94200, 94600, +	95000, +	/* Index 870 - 879 */ +	95400, 95800, 96200, 96600, 97000, 97500, 98100, 98600, 99000, +	99400, +	/* Index 880 - 889 */ +	99800, 100200, 100600, 101000, 101400, 101800, 102200, 102600, 103000, +	103400, +	/* Index 890 - 899 */ +	103800, 104200, 104600, 105000, 105400, 105800, 106200, 106600, 107000, +	107400, +	/* Index 900 - 909 */ +	107800, 108200, 108600, 109000, 109400, 109800, 110200, 110600, 111000, +	111400, +	/* Index 910 - 919 */ +	111800, 112200, 112600, 113000, 113400, 113800, 114200, 114600, 115000, +	115400, +	/* Index 920 - 929 */ +	115800, 116200, 116600, 117000, 117400, 117800, 118200, 118600, 119000, +	119400, +	/* Index 930 - 939 */ +	119800, 120200, 120600, 121000, 121400, 121800, 122200, 122600, 123000, +	123400, +	/* Index 940 - 945 */ +	123800, 124200, 124600, 124900, 125000, 125000, +}; + +/* DRA752 data */ +const struct ti_bandgap_data dra752_data = { +	.features = TI_BANDGAP_FEATURE_TSHUT_CONFIG | +			TI_BANDGAP_FEATURE_FREEZE_BIT | +			TI_BANDGAP_FEATURE_TALERT | +			TI_BANDGAP_FEATURE_COUNTER_DELAY | +			TI_BANDGAP_FEATURE_HISTORY_BUFFER, +	.fclock_name = "l3instr_ts_gclk_div", +	.div_ck_name = "l3instr_ts_gclk_div", +	.conv_table = dra752_adc_to_temp, +	.adc_start_val = DRA752_ADC_START_VALUE, +	.adc_end_val = DRA752_ADC_END_VALUE, +	.expose_sensor = ti_thermal_expose_sensor, +	.remove_sensor = ti_thermal_remove_sensor, +	.sensors = { +		{ +		.registers = &dra752_mpu_temp_sensor_registers, +		.ts_data = &dra752_mpu_temp_sensor_data, +		.domain = "cpu", +		.register_cooling = ti_thermal_register_cpu_cooling, +		.unregister_cooling = ti_thermal_unregister_cpu_cooling, +		.slope = DRA752_GRADIENT_SLOPE, +		.constant = DRA752_GRADIENT_CONST, +		.slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, +		.constant_pcb = DRA752_GRADIENT_CONST_W_PCB, +		}, +		{ +		.registers = &dra752_gpu_temp_sensor_registers, +		.ts_data = &dra752_gpu_temp_sensor_data, +		.domain = "gpu", +		.slope = DRA752_GRADIENT_SLOPE, +		.constant = DRA752_GRADIENT_CONST, +		.slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, +		.constant_pcb = DRA752_GRADIENT_CONST_W_PCB, +		}, +		{ +		.registers = &dra752_core_temp_sensor_registers, +		.ts_data = &dra752_core_temp_sensor_data, +		.domain = "core", +		.slope = DRA752_GRADIENT_SLOPE, +		.constant = DRA752_GRADIENT_CONST, +		.slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, +		.constant_pcb = DRA752_GRADIENT_CONST_W_PCB, +		}, +		{ +		.registers = &dra752_dspeve_temp_sensor_registers, +		.ts_data = &dra752_dspeve_temp_sensor_data, +		.domain = "dspeve", +		.slope = DRA752_GRADIENT_SLOPE, +		.constant = DRA752_GRADIENT_CONST, +		.slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, +		.constant_pcb = DRA752_GRADIENT_CONST_W_PCB, +		}, +		{ +		.registers = &dra752_iva_temp_sensor_registers, +		.ts_data = &dra752_iva_temp_sensor_data, +		.domain = "iva", +		.slope = DRA752_GRADIENT_SLOPE, +		.constant = DRA752_GRADIENT_CONST, +		.slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, +		.constant_pcb = DRA752_GRADIENT_CONST_W_PCB, +		}, +	}, +	.sensor_count = 5, +}; diff --git a/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c new file mode 100644 index 00000000000..d255d33da9e --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c @@ -0,0 +1,267 @@ +/* + * OMAP4 thermal driver. + * + * Copyright (C) 2011-2012 Texas Instruments Inc. + * Contact: + *	Eduardo Valentin <eduardo.valentin@ti.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 "ti-thermal.h" +#include "ti-bandgap.h" +#include "omap4xxx-bandgap.h" + +/* + * OMAP4430 has one instance of thermal sensor for MPU + * need to describe the individual bit fields + */ +static struct temp_sensor_registers +omap4430_mpu_temp_sensor_registers = { +	.temp_sensor_ctrl = OMAP4430_TEMP_SENSOR_CTRL_OFFSET, +	.bgap_tempsoff_mask = OMAP4430_BGAP_TEMPSOFF_MASK, +	.bgap_soc_mask = OMAP4430_BGAP_TEMP_SENSOR_SOC_MASK, +	.bgap_eocz_mask = OMAP4430_BGAP_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = OMAP4430_BGAP_TEMP_SENSOR_DTEMP_MASK, + +	.bgap_mode_ctrl = OMAP4430_TEMP_SENSOR_CTRL_OFFSET, +	.mode_ctrl_mask = OMAP4430_SINGLE_MODE_MASK, + +	.bgap_efuse = OMAP4430_FUSE_OPP_BGAP, +}; + +/* Thresholds and limits for OMAP4430 MPU temperature sensor */ +static struct temp_sensor_data omap4430_mpu_temp_sensor_data = { +	.min_freq = OMAP4430_MIN_FREQ, +	.max_freq = OMAP4430_MAX_FREQ, +	.max_temp = OMAP4430_MAX_TEMP, +	.min_temp = OMAP4430_MIN_TEMP, +	.hyst_val = OMAP4430_HYST_VAL, +}; + +/* + * Temperature values in milli degree celsius + * ADC code values from 530 to 923 + */ +static const int +omap4430_adc_to_temp[OMAP4430_ADC_END_VALUE - OMAP4430_ADC_START_VALUE + 1] = { +	-38000, -35000, -34000, -32000, -30000, -28000, -26000, -24000, -22000, +	-20000, -18000, -17000, -15000, -13000, -12000, -10000, -8000, -6000, +	-5000, -3000, -1000, 0, 2000, 3000, 5000, 6000, 8000, 10000, 12000, +	13000, 15000, 17000, 19000, 21000, 23000, 25000, 27000, 28000, 30000, +	32000, 33000, 35000, 37000, 38000, 40000, 42000, 43000, 45000, 47000, +	48000, 50000, 52000, 53000, 55000, 57000, 58000, 60000, 62000, 64000, +	66000, 68000, 70000, 71000, 73000, 75000, 77000, 78000, 80000, 82000, +	83000, 85000, 87000, 88000, 90000, 92000, 93000, 95000, 97000, 98000, +	100000, 102000, 103000, 105000, 107000, 109000, 111000, 113000, 115000, +	117000, 118000, 120000, 122000, 123000, +}; + +/* OMAP4430 data */ +const struct ti_bandgap_data omap4430_data = { +	.features = TI_BANDGAP_FEATURE_MODE_CONFIG | +			TI_BANDGAP_FEATURE_CLK_CTRL | +			TI_BANDGAP_FEATURE_POWER_SWITCH, +	.fclock_name = "bandgap_fclk", +	.div_ck_name = "bandgap_fclk", +	.conv_table = omap4430_adc_to_temp, +	.adc_start_val = OMAP4430_ADC_START_VALUE, +	.adc_end_val = OMAP4430_ADC_END_VALUE, +	.expose_sensor = ti_thermal_expose_sensor, +	.remove_sensor = ti_thermal_remove_sensor, +	.sensors = { +		{ +		.registers = &omap4430_mpu_temp_sensor_registers, +		.ts_data = &omap4430_mpu_temp_sensor_data, +		.domain = "cpu", +		.slope = OMAP_GRADIENT_SLOPE_4430, +		.constant = OMAP_GRADIENT_CONST_4430, +		.slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_4430, +		.constant_pcb = OMAP_GRADIENT_CONST_W_PCB_4430, +		.register_cooling = ti_thermal_register_cpu_cooling, +		.unregister_cooling = ti_thermal_unregister_cpu_cooling, +		}, +	}, +	.sensor_count = 1, +}; +/* + * OMAP4460 has one instance of thermal sensor for MPU + * need to describe the individual bit fields + */ +static struct temp_sensor_registers +omap4460_mpu_temp_sensor_registers = { +	.temp_sensor_ctrl = OMAP4460_TEMP_SENSOR_CTRL_OFFSET, +	.bgap_tempsoff_mask = OMAP4460_BGAP_TEMPSOFF_MASK, +	.bgap_soc_mask = OMAP4460_BGAP_TEMP_SENSOR_SOC_MASK, +	.bgap_eocz_mask = OMAP4460_BGAP_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = OMAP4460_BGAP_TEMP_SENSOR_DTEMP_MASK, + +	.bgap_mask_ctrl = OMAP4460_BGAP_CTRL_OFFSET, +	.mask_hot_mask = OMAP4460_MASK_HOT_MASK, +	.mask_cold_mask = OMAP4460_MASK_COLD_MASK, + +	.bgap_mode_ctrl = OMAP4460_BGAP_CTRL_OFFSET, +	.mode_ctrl_mask = OMAP4460_SINGLE_MODE_MASK, + +	.bgap_counter = OMAP4460_BGAP_COUNTER_OFFSET, +	.counter_mask = OMAP4460_COUNTER_MASK, + +	.bgap_threshold = OMAP4460_BGAP_THRESHOLD_OFFSET, +	.threshold_thot_mask = OMAP4460_T_HOT_MASK, +	.threshold_tcold_mask = OMAP4460_T_COLD_MASK, + +	.tshut_threshold = OMAP4460_BGAP_TSHUT_OFFSET, +	.tshut_hot_mask = OMAP4460_TSHUT_HOT_MASK, +	.tshut_cold_mask = OMAP4460_TSHUT_COLD_MASK, + +	.bgap_status = OMAP4460_BGAP_STATUS_OFFSET, +	.status_clean_stop_mask = OMAP4460_CLEAN_STOP_MASK, +	.status_bgap_alert_mask = OMAP4460_BGAP_ALERT_MASK, +	.status_hot_mask = OMAP4460_HOT_FLAG_MASK, +	.status_cold_mask = OMAP4460_COLD_FLAG_MASK, + +	.bgap_efuse = OMAP4460_FUSE_OPP_BGAP, +}; + +/* Thresholds and limits for OMAP4460 MPU temperature sensor */ +static struct temp_sensor_data omap4460_mpu_temp_sensor_data = { +	.tshut_hot = OMAP4460_TSHUT_HOT, +	.tshut_cold = OMAP4460_TSHUT_COLD, +	.t_hot = OMAP4460_T_HOT, +	.t_cold = OMAP4460_T_COLD, +	.min_freq = OMAP4460_MIN_FREQ, +	.max_freq = OMAP4460_MAX_FREQ, +	.max_temp = OMAP4460_MAX_TEMP, +	.min_temp = OMAP4460_MIN_TEMP, +	.hyst_val = OMAP4460_HYST_VAL, +	.update_int1 = 1000, +	.update_int2 = 2000, +}; + +/* + * Temperature values in milli degree celsius + * ADC code values from 530 to 923 + */ +static const int +omap4460_adc_to_temp[OMAP4460_ADC_END_VALUE - OMAP4460_ADC_START_VALUE + 1] = { +	-40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200, +	-37800, -37300, -36800, -36400, -36000, -35600, -35200, -34800, +	-34300, -33800, -33400, -33000, -32600, -32200, -31800, -31300, +	-30800, -30400, -30000, -29600, -29200, -28700, -28200, -27800, +	-27400, -27000, -26600, -26200, -25700, -25200, -24800, -24400, +	-24000, -23600, -23200, -22700, -22200, -21800, -21400, -21000, +	-20600, -20200, -19700, -19200, -18800, -18400, -18000, -17600, +	-17200, -16700, -16200, -15800, -15400, -15000, -14600, -14200, +	-13700, -13200, -12800, -12400, -12000, -11600, -11200, -10700, +	-10200, -9800, -9400, -9000, -8600, -8200, -7700, -7200, -6800, +	-6400, -6000, -5600, -5200, -4800, -4300, -3800, -3400, -3000, +	-2600, -2200, -1800, -1300, -800, -400, 0, 400, 800, 1200, 1600, +	2100, 2600, 3000, 3400, 3800, 4200, 4600, 5100, 5600, 6000, 6400, +	6800, 7200, 7600, 8000, 8500, 9000, 9400, 9800, 10200, 10600, 11000, +	11400, 11900, 12400, 12800, 13200, 13600, 14000, 14400, 14800, +	15300, 15800, 16200, 16600, 17000, 17400, 17800, 18200, 18700, +	19200, 19600, 20000, 20400, 20800, 21200, 21600, 22100, 22600, +	23000, 23400, 23800, 24200, 24600, 25000, 25400, 25900, 26400, +	26800, 27200, 27600, 28000, 28400, 28800, 29300, 29800, 30200, +	30600, 31000, 31400, 31800, 32200, 32600, 33100, 33600, 34000, +	34400, 34800, 35200, 35600, 36000, 36400, 36800, 37300, 37800, +	38200, 38600, 39000, 39400, 39800, 40200, 40600, 41100, 41600, +	42000, 42400, 42800, 43200, 43600, 44000, 44400, 44800, 45300, +	45800, 46200, 46600, 47000, 47400, 47800, 48200, 48600, 49000, +	49500, 50000, 50400, 50800, 51200, 51600, 52000, 52400, 52800, +	53200, 53700, 54200, 54600, 55000, 55400, 55800, 56200, 56600, +	57000, 57400, 57800, 58200, 58700, 59200, 59600, 60000, 60400, +	60800, 61200, 61600, 62000, 62400, 62800, 63300, 63800, 64200, +	64600, 65000, 65400, 65800, 66200, 66600, 67000, 67400, 67800, +	68200, 68700, 69200, 69600, 70000, 70400, 70800, 71200, 71600, +	72000, 72400, 72800, 73200, 73600, 74100, 74600, 75000, 75400, +	75800, 76200, 76600, 77000, 77400, 77800, 78200, 78600, 79000, +	79400, 79800, 80300, 80800, 81200, 81600, 82000, 82400, 82800, +	83200, 83600, 84000, 84400, 84800, 85200, 85600, 86000, 86400, +	86800, 87300, 87800, 88200, 88600, 89000, 89400, 89800, 90200, +	90600, 91000, 91400, 91800, 92200, 92600, 93000, 93400, 93800, +	94200, 94600, 95000, 95500, 96000, 96400, 96800, 97200, 97600, +	98000, 98400, 98800, 99200, 99600, 100000, 100400, 100800, 101200, +	101600, 102000, 102400, 102800, 103200, 103600, 104000, 104400, +	104800, 105200, 105600, 106100, 106600, 107000, 107400, 107800, +	108200, 108600, 109000, 109400, 109800, 110200, 110600, 111000, +	111400, 111800, 112200, 112600, 113000, 113400, 113800, 114200, +	114600, 115000, 115400, 115800, 116200, 116600, 117000, 117400, +	117800, 118200, 118600, 119000, 119400, 119800, 120200, 120600, +	121000, 121400, 121800, 122200, 122600, 123000, 123400, 123800, 124200, +	124600, 124900, 125000, 125000, 125000, 125000 +}; + +/* OMAP4460 data */ +const struct ti_bandgap_data omap4460_data = { +	.features = TI_BANDGAP_FEATURE_TSHUT | +			TI_BANDGAP_FEATURE_TSHUT_CONFIG | +			TI_BANDGAP_FEATURE_TALERT | +			TI_BANDGAP_FEATURE_MODE_CONFIG | +			TI_BANDGAP_FEATURE_POWER_SWITCH | +			TI_BANDGAP_FEATURE_CLK_CTRL | +			TI_BANDGAP_FEATURE_COUNTER, +	.fclock_name = "bandgap_ts_fclk", +	.div_ck_name = "div_ts_ck", +	.conv_table = omap4460_adc_to_temp, +	.adc_start_val = OMAP4460_ADC_START_VALUE, +	.adc_end_val = OMAP4460_ADC_END_VALUE, +	.expose_sensor = ti_thermal_expose_sensor, +	.remove_sensor = ti_thermal_remove_sensor, +	.report_temperature = ti_thermal_report_sensor_temperature, +	.sensors = { +		{ +		.registers = &omap4460_mpu_temp_sensor_registers, +		.ts_data = &omap4460_mpu_temp_sensor_data, +		.domain = "cpu", +		.slope = OMAP_GRADIENT_SLOPE_4460, +		.constant = OMAP_GRADIENT_CONST_4460, +		.slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_4460, +		.constant_pcb = OMAP_GRADIENT_CONST_W_PCB_4460, +		.register_cooling = ti_thermal_register_cpu_cooling, +		.unregister_cooling = ti_thermal_unregister_cpu_cooling, +		}, +	}, +	.sensor_count = 1, +}; + +/* OMAP4470 data */ +const struct ti_bandgap_data omap4470_data = { +	.features = TI_BANDGAP_FEATURE_TSHUT | +			TI_BANDGAP_FEATURE_TSHUT_CONFIG | +			TI_BANDGAP_FEATURE_TALERT | +			TI_BANDGAP_FEATURE_MODE_CONFIG | +			TI_BANDGAP_FEATURE_POWER_SWITCH | +			TI_BANDGAP_FEATURE_CLK_CTRL | +			TI_BANDGAP_FEATURE_COUNTER, +	.fclock_name = "bandgap_ts_fclk", +	.div_ck_name = "div_ts_ck", +	.conv_table = omap4460_adc_to_temp, +	.adc_start_val = OMAP4460_ADC_START_VALUE, +	.adc_end_val = OMAP4460_ADC_END_VALUE, +	.expose_sensor = ti_thermal_expose_sensor, +	.remove_sensor = ti_thermal_remove_sensor, +	.report_temperature = ti_thermal_report_sensor_temperature, +	.sensors = { +		{ +		.registers = &omap4460_mpu_temp_sensor_registers, +		.ts_data = &omap4460_mpu_temp_sensor_data, +		.domain = "cpu", +		.slope = OMAP_GRADIENT_SLOPE_4470, +		.constant = OMAP_GRADIENT_CONST_4470, +		.slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_4470, +		.constant_pcb = OMAP_GRADIENT_CONST_W_PCB_4470, +		.register_cooling = ti_thermal_register_cpu_cooling, +		.unregister_cooling = ti_thermal_unregister_cpu_cooling, +		}, +	}, +	.sensor_count = 1, +}; diff --git a/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h new file mode 100644 index 00000000000..6f2de3a3356 --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h @@ -0,0 +1,175 @@ +/* + * OMAP4xxx bandgap registers, bitfields and temperature definitions + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/ + * Contact: + *   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 + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef __OMAP4XXX_BANDGAP_H +#define __OMAP4XXX_BANDGAP_H + +/** + * *** OMAP4430 *** + * + * Below, in sequence, are the Register definitions, + * the bitfields and the temperature definitions for OMAP4430. + */ + +/** + * OMAP4430 register definitions + * + * Registers are defined as offsets. The offsets are + * relative to FUSE_OPP_BGAP on 4430. + */ + +/* OMAP4430.FUSE_OPP_BGAP */ +#define OMAP4430_FUSE_OPP_BGAP				0x0 + +/* OMAP4430.TEMP_SENSOR  */ +#define OMAP4430_TEMP_SENSOR_CTRL_OFFSET		0xCC + +/** + * Register and bit definitions for OMAP4430 + * + * All the macros bellow define the required bits for + * controlling temperature on OMAP4430. Bit defines are + * grouped by register. + */ + +/* OMAP4430.TEMP_SENSOR bits */ +#define OMAP4430_BGAP_TEMPSOFF_MASK			BIT(12) +#define OMAP4430_BGAP_TSHUT_MASK			BIT(11) +#define OMAP4430_SINGLE_MODE_MASK			BIT(10) +#define OMAP4430_BGAP_TEMP_SENSOR_SOC_MASK		BIT(9) +#define OMAP4430_BGAP_TEMP_SENSOR_EOCZ_MASK		BIT(8) +#define OMAP4430_BGAP_TEMP_SENSOR_DTEMP_MASK		(0xff << 0) + +/** + * Temperature limits and thresholds for OMAP4430 + * + * All the macros bellow are definitions for handling the + * ADC conversions and representation of temperature limits + * and thresholds for OMAP4430. + */ + +/* ADC conversion table limits */ +#define OMAP4430_ADC_START_VALUE			0 +#define OMAP4430_ADC_END_VALUE				127 +/* bandgap clock limits (no control on 4430) */ +#define OMAP4430_MAX_FREQ				32768 +#define OMAP4430_MIN_FREQ				32768 +/* sensor limits */ +#define OMAP4430_MIN_TEMP				-40000 +#define OMAP4430_MAX_TEMP				125000 +#define OMAP4430_HYST_VAL				5000 + +/** + * *** OMAP4460 *** Applicable for OMAP4470 + * + * Below, in sequence, are the Register definitions, + * the bitfields and the temperature definitions for OMAP4460. + */ + +/** + * OMAP4460 register definitions + * + * Registers are defined as offsets. The offsets are + * relative to FUSE_OPP_BGAP on 4460. + */ + +/* OMAP4460.FUSE_OPP_BGAP */ +#define OMAP4460_FUSE_OPP_BGAP				0x0 + +/* OMAP4460.TEMP_SENSOR */ +#define OMAP4460_TEMP_SENSOR_CTRL_OFFSET		0xCC + +/* OMAP4460.BANDGAP_CTRL */ +#define OMAP4460_BGAP_CTRL_OFFSET			0x118 + +/* OMAP4460.BANDGAP_COUNTER */ +#define OMAP4460_BGAP_COUNTER_OFFSET			0x11C + +/* OMAP4460.BANDGAP_THRESHOLD */ +#define OMAP4460_BGAP_THRESHOLD_OFFSET			0x120 + +/* OMAP4460.TSHUT_THRESHOLD */ +#define OMAP4460_BGAP_TSHUT_OFFSET			0x124 + +/* OMAP4460.BANDGAP_STATUS */ +#define OMAP4460_BGAP_STATUS_OFFSET			0x128 + +/** + * Register bitfields for OMAP4460 + * + * All the macros bellow define the required bits for + * controlling temperature on OMAP4460. Bit defines are + * grouped by register. + */ +/* OMAP4460.TEMP_SENSOR bits */ +#define OMAP4460_BGAP_TEMPSOFF_MASK			BIT(13) +#define OMAP4460_BGAP_TEMP_SENSOR_SOC_MASK		BIT(11) +#define OMAP4460_BGAP_TEMP_SENSOR_EOCZ_MASK		BIT(10) +#define OMAP4460_BGAP_TEMP_SENSOR_DTEMP_MASK		(0x3ff << 0) + +/* OMAP4460.BANDGAP_CTRL bits */ +#define OMAP4460_SINGLE_MODE_MASK			BIT(31) +#define OMAP4460_MASK_HOT_MASK				BIT(1) +#define OMAP4460_MASK_COLD_MASK				BIT(0) + +/* OMAP4460.BANDGAP_COUNTER bits */ +#define OMAP4460_COUNTER_MASK				(0xffffff << 0) + +/* OMAP4460.BANDGAP_THRESHOLD bits */ +#define OMAP4460_T_HOT_MASK				(0x3ff << 16) +#define OMAP4460_T_COLD_MASK				(0x3ff << 0) + +/* OMAP4460.TSHUT_THRESHOLD bits */ +#define OMAP4460_TSHUT_HOT_MASK				(0x3ff << 16) +#define OMAP4460_TSHUT_COLD_MASK			(0x3ff << 0) + +/* OMAP4460.BANDGAP_STATUS bits */ +#define OMAP4460_CLEAN_STOP_MASK			BIT(3) +#define OMAP4460_BGAP_ALERT_MASK			BIT(2) +#define OMAP4460_HOT_FLAG_MASK				BIT(1) +#define OMAP4460_COLD_FLAG_MASK				BIT(0) + +/** + * Temperature limits and thresholds for OMAP4460 + * + * All the macros bellow are definitions for handling the + * ADC conversions and representation of temperature limits + * and thresholds for OMAP4460. + */ + +/* ADC conversion table limits */ +#define OMAP4460_ADC_START_VALUE			530 +#define OMAP4460_ADC_END_VALUE				932 +/* bandgap clock limits */ +#define OMAP4460_MAX_FREQ				1500000 +#define OMAP4460_MIN_FREQ				1000000 +/* sensor limits */ +#define OMAP4460_MIN_TEMP				-40000 +#define OMAP4460_MAX_TEMP				123000 +#define OMAP4460_HYST_VAL				5000 +/* interrupts thresholds */ +#define OMAP4460_TSHUT_HOT				900	/* 122 deg C */ +#define OMAP4460_TSHUT_COLD				895	/* 100 deg C */ +#define OMAP4460_T_HOT					800	/* 73 deg C */ +#define OMAP4460_T_COLD					795	/* 71 deg C */ + +#endif /* __OMAP4XXX_BANDGAP_H */ diff --git a/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c new file mode 100644 index 00000000000..eff0c80fd4a --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c @@ -0,0 +1,359 @@ +/* + * OMAP5 thermal driver. + * + * Copyright (C) 2011-2012 Texas Instruments Inc. + * Contact: + *	Eduardo Valentin <eduardo.valentin@ti.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 "ti-thermal.h" +#include "ti-bandgap.h" +#include "omap5xxx-bandgap.h" + +/* + * OMAP5430 has three instances of thermal sensor for MPU, GPU & CORE, + * need to describe the individual registers and bit fields. + */ + +/* + * OMAP5430 MPU thermal sensor register offset and bit-fields + */ +static struct temp_sensor_registers +omap5430_mpu_temp_sensor_registers = { +	.temp_sensor_ctrl = OMAP5430_TEMP_SENSOR_MPU_OFFSET, +	.bgap_tempsoff_mask = OMAP5430_BGAP_TEMPSOFF_MASK, +	.bgap_eocz_mask = OMAP5430_BGAP_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = OMAP5430_BGAP_TEMP_SENSOR_DTEMP_MASK, + +	.bgap_mask_ctrl = OMAP5430_BGAP_CTRL_OFFSET, +	.mask_hot_mask = OMAP5430_MASK_HOT_MPU_MASK, +	.mask_cold_mask = OMAP5430_MASK_COLD_MPU_MASK, +	.mask_sidlemode_mask = OMAP5430_MASK_SIDLEMODE_MASK, +	.mask_counter_delay_mask = OMAP5430_MASK_COUNTER_DELAY_MASK, +	.mask_freeze_mask = OMAP5430_MASK_FREEZE_MPU_MASK, +	.mask_clear_mask = OMAP5430_MASK_CLEAR_MPU_MASK, +	.mask_clear_accum_mask = OMAP5430_MASK_CLEAR_ACCUM_MPU_MASK, + + +	.bgap_counter = OMAP5430_BGAP_CTRL_OFFSET, +	.counter_mask = OMAP5430_COUNTER_MASK, + +	.bgap_threshold = OMAP5430_BGAP_THRESHOLD_MPU_OFFSET, +	.threshold_thot_mask = OMAP5430_T_HOT_MASK, +	.threshold_tcold_mask = OMAP5430_T_COLD_MASK, + +	.tshut_threshold = OMAP5430_BGAP_TSHUT_MPU_OFFSET, +	.tshut_hot_mask = OMAP5430_TSHUT_HOT_MASK, +	.tshut_cold_mask = OMAP5430_TSHUT_COLD_MASK, + +	.bgap_status = OMAP5430_BGAP_STATUS_OFFSET, +	.status_clean_stop_mask = 0x0, +	.status_bgap_alert_mask = OMAP5430_BGAP_ALERT_MASK, +	.status_hot_mask = OMAP5430_HOT_MPU_FLAG_MASK, +	.status_cold_mask = OMAP5430_COLD_MPU_FLAG_MASK, + +	.bgap_cumul_dtemp = OMAP5430_BGAP_CUMUL_DTEMP_MPU_OFFSET, +	.ctrl_dtemp_0 = OMAP5430_BGAP_DTEMP_MPU_0_OFFSET, +	.ctrl_dtemp_1 = OMAP5430_BGAP_DTEMP_MPU_1_OFFSET, +	.ctrl_dtemp_2 = OMAP5430_BGAP_DTEMP_MPU_2_OFFSET, +	.ctrl_dtemp_3 = OMAP5430_BGAP_DTEMP_MPU_3_OFFSET, +	.ctrl_dtemp_4 = OMAP5430_BGAP_DTEMP_MPU_4_OFFSET, +	.bgap_efuse = OMAP5430_FUSE_OPP_BGAP_MPU, +}; + +/* + * OMAP5430 GPU thermal sensor register offset and bit-fields + */ +static struct temp_sensor_registers +omap5430_gpu_temp_sensor_registers = { +	.temp_sensor_ctrl = OMAP5430_TEMP_SENSOR_GPU_OFFSET, +	.bgap_tempsoff_mask = OMAP5430_BGAP_TEMPSOFF_MASK, +	.bgap_eocz_mask = OMAP5430_BGAP_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = OMAP5430_BGAP_TEMP_SENSOR_DTEMP_MASK, + +	.bgap_mask_ctrl = OMAP5430_BGAP_CTRL_OFFSET, +	.mask_hot_mask = OMAP5430_MASK_HOT_GPU_MASK, +	.mask_cold_mask = OMAP5430_MASK_COLD_GPU_MASK, +	.mask_sidlemode_mask = OMAP5430_MASK_SIDLEMODE_MASK, +	.mask_counter_delay_mask = OMAP5430_MASK_COUNTER_DELAY_MASK, +	.mask_freeze_mask = OMAP5430_MASK_FREEZE_GPU_MASK, +	.mask_clear_mask = OMAP5430_MASK_CLEAR_GPU_MASK, +	.mask_clear_accum_mask = OMAP5430_MASK_CLEAR_ACCUM_GPU_MASK, + +	.bgap_counter = OMAP5430_BGAP_CTRL_OFFSET, +	.counter_mask = OMAP5430_COUNTER_MASK, + +	.bgap_threshold = OMAP5430_BGAP_THRESHOLD_GPU_OFFSET, +	.threshold_thot_mask = OMAP5430_T_HOT_MASK, +	.threshold_tcold_mask = OMAP5430_T_COLD_MASK, + +	.tshut_threshold = OMAP5430_BGAP_TSHUT_GPU_OFFSET, +	.tshut_hot_mask = OMAP5430_TSHUT_HOT_MASK, +	.tshut_cold_mask = OMAP5430_TSHUT_COLD_MASK, + +	.bgap_status = OMAP5430_BGAP_STATUS_OFFSET, +	.status_clean_stop_mask = 0x0, +	.status_bgap_alert_mask = OMAP5430_BGAP_ALERT_MASK, +	.status_hot_mask = OMAP5430_HOT_GPU_FLAG_MASK, +	.status_cold_mask = OMAP5430_COLD_GPU_FLAG_MASK, + +	.bgap_cumul_dtemp = OMAP5430_BGAP_CUMUL_DTEMP_GPU_OFFSET, +	.ctrl_dtemp_0 = OMAP5430_BGAP_DTEMP_GPU_0_OFFSET, +	.ctrl_dtemp_1 = OMAP5430_BGAP_DTEMP_GPU_1_OFFSET, +	.ctrl_dtemp_2 = OMAP5430_BGAP_DTEMP_GPU_2_OFFSET, +	.ctrl_dtemp_3 = OMAP5430_BGAP_DTEMP_GPU_3_OFFSET, +	.ctrl_dtemp_4 = OMAP5430_BGAP_DTEMP_GPU_4_OFFSET, + +	.bgap_efuse = OMAP5430_FUSE_OPP_BGAP_GPU, +}; + +/* + * OMAP5430 CORE thermal sensor register offset and bit-fields + */ +static struct temp_sensor_registers +omap5430_core_temp_sensor_registers = { +	.temp_sensor_ctrl = OMAP5430_TEMP_SENSOR_CORE_OFFSET, +	.bgap_tempsoff_mask = OMAP5430_BGAP_TEMPSOFF_MASK, +	.bgap_eocz_mask = OMAP5430_BGAP_TEMP_SENSOR_EOCZ_MASK, +	.bgap_dtemp_mask = OMAP5430_BGAP_TEMP_SENSOR_DTEMP_MASK, + +	.bgap_mask_ctrl = OMAP5430_BGAP_CTRL_OFFSET, +	.mask_hot_mask = OMAP5430_MASK_HOT_CORE_MASK, +	.mask_cold_mask = OMAP5430_MASK_COLD_CORE_MASK, +	.mask_sidlemode_mask = OMAP5430_MASK_SIDLEMODE_MASK, +	.mask_counter_delay_mask = OMAP5430_MASK_COUNTER_DELAY_MASK, +	.mask_freeze_mask = OMAP5430_MASK_FREEZE_CORE_MASK, +	.mask_clear_mask = OMAP5430_MASK_CLEAR_CORE_MASK, +	.mask_clear_accum_mask = OMAP5430_MASK_CLEAR_ACCUM_CORE_MASK, + +	.bgap_counter = OMAP5430_BGAP_CTRL_OFFSET, +	.counter_mask = OMAP5430_COUNTER_MASK, + +	.bgap_threshold = OMAP5430_BGAP_THRESHOLD_CORE_OFFSET, +	.threshold_thot_mask = OMAP5430_T_HOT_MASK, +	.threshold_tcold_mask = OMAP5430_T_COLD_MASK, + +	.tshut_threshold = OMAP5430_BGAP_TSHUT_CORE_OFFSET, +	.tshut_hot_mask = OMAP5430_TSHUT_HOT_MASK, +	.tshut_cold_mask = OMAP5430_TSHUT_COLD_MASK, + +	.bgap_status = OMAP5430_BGAP_STATUS_OFFSET, +	.status_clean_stop_mask = 0x0, +	.status_bgap_alert_mask = OMAP5430_BGAP_ALERT_MASK, +	.status_hot_mask = OMAP5430_HOT_CORE_FLAG_MASK, +	.status_cold_mask = OMAP5430_COLD_CORE_FLAG_MASK, + +	.bgap_cumul_dtemp = OMAP5430_BGAP_CUMUL_DTEMP_CORE_OFFSET, +	.ctrl_dtemp_0 = OMAP5430_BGAP_DTEMP_CORE_0_OFFSET, +	.ctrl_dtemp_1 = OMAP5430_BGAP_DTEMP_CORE_1_OFFSET, +	.ctrl_dtemp_2 = OMAP5430_BGAP_DTEMP_CORE_2_OFFSET, +	.ctrl_dtemp_3 = OMAP5430_BGAP_DTEMP_CORE_3_OFFSET, +	.ctrl_dtemp_4 = OMAP5430_BGAP_DTEMP_CORE_4_OFFSET, + +	.bgap_efuse = OMAP5430_FUSE_OPP_BGAP_CORE, +}; + +/* Thresholds and limits for OMAP5430 MPU temperature sensor */ +static struct temp_sensor_data omap5430_mpu_temp_sensor_data = { +	.tshut_hot = OMAP5430_MPU_TSHUT_HOT, +	.tshut_cold = OMAP5430_MPU_TSHUT_COLD, +	.t_hot = OMAP5430_MPU_T_HOT, +	.t_cold = OMAP5430_MPU_T_COLD, +	.min_freq = OMAP5430_MPU_MIN_FREQ, +	.max_freq = OMAP5430_MPU_MAX_FREQ, +	.max_temp = OMAP5430_MPU_MAX_TEMP, +	.min_temp = OMAP5430_MPU_MIN_TEMP, +	.hyst_val = OMAP5430_MPU_HYST_VAL, +	.update_int1 = 1000, +	.update_int2 = 2000, +}; + +/* Thresholds and limits for OMAP5430 GPU temperature sensor */ +static struct temp_sensor_data omap5430_gpu_temp_sensor_data = { +	.tshut_hot = OMAP5430_GPU_TSHUT_HOT, +	.tshut_cold = OMAP5430_GPU_TSHUT_COLD, +	.t_hot = OMAP5430_GPU_T_HOT, +	.t_cold = OMAP5430_GPU_T_COLD, +	.min_freq = OMAP5430_GPU_MIN_FREQ, +	.max_freq = OMAP5430_GPU_MAX_FREQ, +	.max_temp = OMAP5430_GPU_MAX_TEMP, +	.min_temp = OMAP5430_GPU_MIN_TEMP, +	.hyst_val = OMAP5430_GPU_HYST_VAL, +	.update_int1 = 1000, +	.update_int2 = 2000, +}; + +/* Thresholds and limits for OMAP5430 CORE temperature sensor */ +static struct temp_sensor_data omap5430_core_temp_sensor_data = { +	.tshut_hot = OMAP5430_CORE_TSHUT_HOT, +	.tshut_cold = OMAP5430_CORE_TSHUT_COLD, +	.t_hot = OMAP5430_CORE_T_HOT, +	.t_cold = OMAP5430_CORE_T_COLD, +	.min_freq = OMAP5430_CORE_MIN_FREQ, +	.max_freq = OMAP5430_CORE_MAX_FREQ, +	.max_temp = OMAP5430_CORE_MAX_TEMP, +	.min_temp = OMAP5430_CORE_MIN_TEMP, +	.hyst_val = OMAP5430_CORE_HYST_VAL, +	.update_int1 = 1000, +	.update_int2 = 2000, +}; + +/* + * OMAP54xx ES2.0 : Temperature values in milli degree celsius + * ADC code values from 540 to 945 + */ +static int +omap5430_adc_to_temp[ +	OMAP5430_ADC_END_VALUE - OMAP5430_ADC_START_VALUE + 1] = { +	/* Index 540 - 549 */ +	-40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200, +	-37800, +	/* Index 550 - 559 */ +	-37400, -37000, -36600, -36200, -35800, -35300, -34700, -34200, -33800, +	-33400, +	/* Index 560 - 569 */ +	-33000, -32600, -32200, -31800, -31400, -31000, -30600, -30200, -29800, +	-29400, +	/* Index 570 - 579 */ +	-29000, -28600, -28200, -27700, -27100, -26600, -26200, -25800, -25400, +	-25000, +	/* Index 580 - 589 */ +	-24600, -24200, -23800, -23400, -23000, -22600, -22200, -21600, -21400, +	-21000, +	/* Index 590 - 599 */ +	-20500, -19900, -19400, -19000, -18600, -18200, -17800, -17400, -17000, +	-16600, +	/* Index 600 - 609 */ +	-16200, -15800, -15400, -15000, -14600, -14200, -13800,	-13400, -13000, +	-12500, +	/* Index 610 - 619 */ +	-11900, -11400, -11000, -10600, -10200, -9800, -9400, -9000, -8600, +	-8200, +	/* Index 620 - 629 */ +	-7800, -7400, -7000, -6600, -6200, -5800, -5400, -5000, -4500, -3900, +	/* Index 630 - 639 */ +	-3400, -3000, -2600, -2200, -1800, -1400, -1000, -600, -200, 200, +	/* Index 640 - 649 */ +	600, 1000, 1400, 1800, 2200, 2600, 3000, 3400, 3900, 4500, +	/* Index 650 - 659 */ +	5000, 5400, 5800, 6200, 6600, 7000, 7400, 7800, 8200, 8600, +	/* Index 660 - 669 */ +	9000, 9400, 9800, 10200, 10600, 11000, 11400, 11800, 12200, 12700, +	/* Index 670 - 679 */ +	13300, 13800, 14200, 14600, 15000, 15400, 15800, 16200, 16600, 17000, +	/* Index 680 - 689 */ +	17400, 17800, 18200, 18600, 19000, 19400, 19800, 20200, 20600, 21100, +	/* Index 690 - 699 */ +	21400, 21900, 22500, 23000, 23400, 23800, 24200, 24600, 25000, 25400, +	/* Index 700 - 709 */ +	25800, 26200, 26600, 27000, 27400, 27800, 28200, 28600, 29000, 29400, +	/* Index 710 - 719 */ +	29800, 30200, 30600, 31000, 31400, 31900, 32500, 33000, 33400, 33800, +	/* Index 720 - 729 */ +	34200, 34600, 35000, 35400, 35800, 36200, 36600, 37000, 37400, 37800, +	/* Index 730 - 739 */ +	38200, 38600, 39000, 39400, 39800, 40200, 40600, 41000, 41400, 41800, +	/* Index 740 - 749 */ +	42200, 42600, 43100, 43700, 44200, 44600, 45000, 45400, 45800, 46200, +	/* Index 750 - 759 */ +	46600, 47000, 47400, 47800, 48200, 48600, 49000, 49400, 49800, 50200, +	/* Index 760 - 769 */ +	50600, 51000, 51400, 51800, 52200, 52600, 53000, 53400, 53800, 54200, +	/* Index 770 - 779 */ +	54600, 55000, 55400, 55900, 56500, 57000, 57400, 57800, 58200, 58600, +	/* Index 780 - 789 */ +	59000, 59400, 59800, 60200, 60600, 61000, 61400, 61800, 62200, 62600, +	/* Index 790 - 799 */ +	63000, 63400, 63800, 64200, 64600, 65000, 65400, 65800, 66200, 66600, +	/* Index 800 - 809 */ +	67000, 67400, 67800, 68200, 68600, 69000, 69400, 69800, 70200, 70600, +	/* Index 810 - 819 */ +	71000, 71500, 72100, 72600, 73000, 73400, 73800, 74200, 74600, 75000, +	/* Index 820 - 829 */ +	75400, 75800, 76200, 76600, 77000, 77400, 77800, 78200, 78600, 79000, +	/* Index 830 - 839 */ +	79400, 79800, 80200, 80600, 81000, 81400, 81800, 82200, 82600, 83000, +	/* Index 840 - 849 */ +	83400, 83800, 84200, 84600, 85000, 85400, 85800, 86200, 86600, 87000, +	/* Index 850 - 859 */ +	87400, 87800, 88200, 88600, 89000, 89400, 89800, 90200, 90600, 91000, +	/* Index 860 - 869 */ +	91400, 91800, 92200, 92600, 93000, 93400, 93800, 94200, 94600, 95000, +	/* Index 870 - 879 */ +	95400, 95800, 96200, 96600, 97000, 97500, 98100, 98600, 99000, 99400, +	/* Index 880 - 889 */ +	99800, 100200, 100600, 101000, 101400, 101800, 102200, 102600, 103000, +	103400, +	/* Index 890 - 899 */ +	103800, 104200, 104600, 105000, 105400, 105800, 106200, 106600, 107000, +	107400, +	/* Index 900 - 909 */ +	107800, 108200, 108600, 109000, 109400, 109800, 110200, 110600, 111000, +	111400, +	/* Index 910 - 919 */ +	111800, 112200, 112600, 113000, 113400, 113800, 114200, 114600, 115000, +	115400, +	/* Index 920 - 929 */ +	115800, 116200, 116600, 117000, 117400, 117800, 118200, 118600, 119000, +	119400, +	/* Index 930 - 939 */ +	119800, 120200, 120600, 121000, 121400, 121800, 122400, 122600, 123000, +	123400, +	/* Index 940 - 945 */ +	123800, 1242000, 124600, 124900, 125000, 125000, +}; + +/* OMAP54xx ES2.0 data */ +const struct ti_bandgap_data omap5430_data = { +	.features = TI_BANDGAP_FEATURE_TSHUT_CONFIG | +			TI_BANDGAP_FEATURE_FREEZE_BIT | +			TI_BANDGAP_FEATURE_TALERT | +			TI_BANDGAP_FEATURE_COUNTER_DELAY | +			TI_BANDGAP_FEATURE_HISTORY_BUFFER, +	.fclock_name = "l3instr_ts_gclk_div", +	.div_ck_name = "l3instr_ts_gclk_div", +	.conv_table = omap5430_adc_to_temp, +	.adc_start_val = OMAP5430_ADC_START_VALUE, +	.adc_end_val = OMAP5430_ADC_END_VALUE, +	.expose_sensor = ti_thermal_expose_sensor, +	.remove_sensor = ti_thermal_remove_sensor, +	.report_temperature = ti_thermal_report_sensor_temperature, +	.sensors = { +		{ +		.registers = &omap5430_mpu_temp_sensor_registers, +		.ts_data = &omap5430_mpu_temp_sensor_data, +		.domain = "cpu", +		.register_cooling = ti_thermal_register_cpu_cooling, +		.unregister_cooling = ti_thermal_unregister_cpu_cooling, +		.slope = OMAP_GRADIENT_SLOPE_5430_CPU, +		.constant = OMAP_GRADIENT_CONST_5430_CPU, +		.slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_5430_CPU, +		.constant_pcb = OMAP_GRADIENT_CONST_W_PCB_5430_CPU, +		}, +		{ +		.registers = &omap5430_gpu_temp_sensor_registers, +		.ts_data = &omap5430_gpu_temp_sensor_data, +		.domain = "gpu", +		.slope = OMAP_GRADIENT_SLOPE_5430_GPU, +		.constant = OMAP_GRADIENT_CONST_5430_GPU, +		.slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_5430_GPU, +		.constant_pcb = OMAP_GRADIENT_CONST_W_PCB_5430_GPU, +		}, +		{ +		.registers = &omap5430_core_temp_sensor_registers, +		.ts_data = &omap5430_core_temp_sensor_data, +		.domain = "core", +		}, +	}, +	.sensor_count = 3, +}; diff --git a/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h b/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h new file mode 100644 index 00000000000..400b55dffad --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h @@ -0,0 +1,200 @@ +/* + * OMAP5xxx bandgap registers, bitfields and temperature definitions + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/ + * Contact: + *   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 + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef __OMAP5XXX_BANDGAP_H +#define __OMAP5XXX_BANDGAP_H + +/** + * *** OMAP5430 *** + * + * Below, in sequence, are the Register definitions, + * the bitfields and the temperature definitions for OMAP5430. + */ + +/** + * OMAP5430 register definitions + * + * Registers are defined as offsets. The offsets are + * relative to FUSE_OPP_BGAP_GPU on 5430. + * + * Register below are grouped by domain (not necessarily in offset order) + */ + +/* OMAP5430.GPU register offsets */ +#define OMAP5430_FUSE_OPP_BGAP_GPU			0x0 +#define OMAP5430_TEMP_SENSOR_GPU_OFFSET			0x150 +#define OMAP5430_BGAP_THRESHOLD_GPU_OFFSET		0x1A8 +#define OMAP5430_BGAP_TSHUT_GPU_OFFSET			0x1B4 +#define OMAP5430_BGAP_CUMUL_DTEMP_GPU_OFFSET		0x1C0 +#define OMAP5430_BGAP_DTEMP_GPU_0_OFFSET		0x1F4 +#define OMAP5430_BGAP_DTEMP_GPU_1_OFFSET		0x1F8 +#define OMAP5430_BGAP_DTEMP_GPU_2_OFFSET		0x1FC +#define OMAP5430_BGAP_DTEMP_GPU_3_OFFSET		0x200 +#define OMAP5430_BGAP_DTEMP_GPU_4_OFFSET		0x204 + +/* OMAP5430.MPU register offsets */ +#define OMAP5430_FUSE_OPP_BGAP_MPU			0x4 +#define OMAP5430_TEMP_SENSOR_MPU_OFFSET			0x14C +#define OMAP5430_BGAP_THRESHOLD_MPU_OFFSET		0x1A4 +#define OMAP5430_BGAP_TSHUT_MPU_OFFSET			0x1B0 +#define OMAP5430_BGAP_CUMUL_DTEMP_MPU_OFFSET		0x1BC +#define OMAP5430_BGAP_DTEMP_MPU_0_OFFSET		0x1E0 +#define OMAP5430_BGAP_DTEMP_MPU_1_OFFSET		0x1E4 +#define OMAP5430_BGAP_DTEMP_MPU_2_OFFSET		0x1E8 +#define OMAP5430_BGAP_DTEMP_MPU_3_OFFSET		0x1EC +#define OMAP5430_BGAP_DTEMP_MPU_4_OFFSET		0x1F0 + +/* OMAP5430.MPU register offsets */ +#define OMAP5430_FUSE_OPP_BGAP_CORE			0x8 +#define OMAP5430_TEMP_SENSOR_CORE_OFFSET		0x154 +#define OMAP5430_BGAP_THRESHOLD_CORE_OFFSET		0x1AC +#define OMAP5430_BGAP_TSHUT_CORE_OFFSET			0x1B8 +#define OMAP5430_BGAP_CUMUL_DTEMP_CORE_OFFSET		0x1C4 +#define OMAP5430_BGAP_DTEMP_CORE_0_OFFSET		0x208 +#define OMAP5430_BGAP_DTEMP_CORE_1_OFFSET		0x20C +#define OMAP5430_BGAP_DTEMP_CORE_2_OFFSET		0x210 +#define OMAP5430_BGAP_DTEMP_CORE_3_OFFSET		0x214 +#define OMAP5430_BGAP_DTEMP_CORE_4_OFFSET		0x218 + +/* OMAP5430.common register offsets */ +#define OMAP5430_BGAP_CTRL_OFFSET			0x1A0 +#define OMAP5430_BGAP_STATUS_OFFSET			0x1C8 + +/** + * Register bitfields for OMAP5430 + * + * All the macros bellow define the required bits for + * controlling temperature on OMAP5430. Bit defines are + * grouped by register. + */ + +/* OMAP5430.TEMP_SENSOR */ +#define OMAP5430_BGAP_TEMP_SENSOR_SOC_MASK		BIT(12) +#define OMAP5430_BGAP_TEMPSOFF_MASK			BIT(11) +#define OMAP5430_BGAP_TEMP_SENSOR_EOCZ_MASK		BIT(10) +#define OMAP5430_BGAP_TEMP_SENSOR_DTEMP_MASK		(0x3ff << 0) + +/* OMAP5430.BANDGAP_CTRL */ +#define OMAP5430_MASK_SIDLEMODE_MASK			(0x3 << 30) +#define OMAP5430_MASK_COUNTER_DELAY_MASK		(0x7 << 27) +#define OMAP5430_MASK_FREEZE_CORE_MASK			BIT(23) +#define OMAP5430_MASK_FREEZE_GPU_MASK			BIT(22) +#define OMAP5430_MASK_FREEZE_MPU_MASK			BIT(21) +#define OMAP5430_MASK_CLEAR_CORE_MASK			BIT(20) +#define OMAP5430_MASK_CLEAR_GPU_MASK			BIT(19) +#define OMAP5430_MASK_CLEAR_MPU_MASK			BIT(18) +#define OMAP5430_MASK_CLEAR_ACCUM_CORE_MASK		BIT(17) +#define OMAP5430_MASK_CLEAR_ACCUM_GPU_MASK		BIT(16) +#define OMAP5430_MASK_CLEAR_ACCUM_MPU_MASK		BIT(15) +#define OMAP5430_MASK_HOT_CORE_MASK			BIT(5) +#define OMAP5430_MASK_COLD_CORE_MASK			BIT(4) +#define OMAP5430_MASK_HOT_GPU_MASK			BIT(3) +#define OMAP5430_MASK_COLD_GPU_MASK			BIT(2) +#define OMAP5430_MASK_HOT_MPU_MASK			BIT(1) +#define OMAP5430_MASK_COLD_MPU_MASK			BIT(0) + +/* OMAP5430.BANDGAP_COUNTER */ +#define OMAP5430_COUNTER_MASK				(0xffffff << 0) + +/* OMAP5430.BANDGAP_THRESHOLD */ +#define OMAP5430_T_HOT_MASK				(0x3ff << 16) +#define OMAP5430_T_COLD_MASK				(0x3ff << 0) + +/* OMAP5430.TSHUT_THRESHOLD */ +#define OMAP5430_TSHUT_HOT_MASK				(0x3ff << 16) +#define OMAP5430_TSHUT_COLD_MASK			(0x3ff << 0) + +/* OMAP5430.BANDGAP_CUMUL_DTEMP_MPU */ +#define OMAP5430_CUMUL_DTEMP_MPU_MASK			(0xffffffff << 0) + +/* OMAP5430.BANDGAP_CUMUL_DTEMP_GPU */ +#define OMAP5430_CUMUL_DTEMP_GPU_MASK			(0xffffffff << 0) + +/* OMAP5430.BANDGAP_CUMUL_DTEMP_CORE */ +#define OMAP5430_CUMUL_DTEMP_CORE_MASK			(0xffffffff << 0) + +/* OMAP5430.BANDGAP_STATUS */ +#define OMAP5430_BGAP_ALERT_MASK			BIT(31) +#define OMAP5430_HOT_CORE_FLAG_MASK			BIT(5) +#define OMAP5430_COLD_CORE_FLAG_MASK			BIT(4) +#define OMAP5430_HOT_GPU_FLAG_MASK			BIT(3) +#define OMAP5430_COLD_GPU_FLAG_MASK			BIT(2) +#define OMAP5430_HOT_MPU_FLAG_MASK			BIT(1) +#define OMAP5430_COLD_MPU_FLAG_MASK			BIT(0) + +/** + * Temperature limits and thresholds for OMAP5430 + * + * All the macros bellow are definitions for handling the + * ADC conversions and representation of temperature limits + * and thresholds for OMAP5430. Definitions are grouped + * by temperature domain. + */ + +/* OMAP5430.common temperature definitions */ +/* ADC conversion table limits */ +#define OMAP5430_ADC_START_VALUE			540 +#define OMAP5430_ADC_END_VALUE				945 + +/* OMAP5430.GPU temperature definitions */ +/* bandgap clock limits */ +#define OMAP5430_GPU_MAX_FREQ				1500000 +#define OMAP5430_GPU_MIN_FREQ				1000000 +/* sensor limits */ +#define OMAP5430_GPU_MIN_TEMP				-40000 +#define OMAP5430_GPU_MAX_TEMP				125000 +#define OMAP5430_GPU_HYST_VAL				5000 +/* interrupts thresholds */ +#define OMAP5430_GPU_TSHUT_HOT				915 +#define OMAP5430_GPU_TSHUT_COLD				900 +#define OMAP5430_GPU_T_HOT				800 +#define OMAP5430_GPU_T_COLD				795 + +/* OMAP5430.MPU temperature definitions */ +/* bandgap clock limits */ +#define OMAP5430_MPU_MAX_FREQ				1500000 +#define OMAP5430_MPU_MIN_FREQ				1000000 +/* sensor limits */ +#define OMAP5430_MPU_MIN_TEMP				-40000 +#define OMAP5430_MPU_MAX_TEMP				125000 +#define OMAP5430_MPU_HYST_VAL				5000 +/* interrupts thresholds */ +#define OMAP5430_MPU_TSHUT_HOT				915 +#define OMAP5430_MPU_TSHUT_COLD				900 +#define OMAP5430_MPU_T_HOT				800 +#define OMAP5430_MPU_T_COLD				795 + +/* OMAP5430.CORE temperature definitions */ +/* bandgap clock limits */ +#define OMAP5430_CORE_MAX_FREQ				1500000 +#define OMAP5430_CORE_MIN_FREQ				1000000 +/* sensor limits */ +#define OMAP5430_CORE_MIN_TEMP				-40000 +#define OMAP5430_CORE_MAX_TEMP				125000 +#define OMAP5430_CORE_HYST_VAL				5000 +/* interrupts thresholds */ +#define OMAP5430_CORE_TSHUT_HOT				915 +#define OMAP5430_CORE_TSHUT_COLD			900 +#define OMAP5430_CORE_T_HOT				800 +#define OMAP5430_CORE_T_COLD				795 + +#endif /* __OMAP5XXX_BANDGAP_H */ diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c new file mode 100644 index 00000000000..634b6ce0e63 --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -0,0 +1,1558 @@ +/* + * TI Bandgap temperature sensor driver + * + * Copyright (C) 2011-2012 Texas Instruments Incorporated - http://www.ti.com/ + * Author: J Keerthy <j-keerthy@ti.com> + * Author: Moiz Sonasath <m-sonasath@ti.com> + * Couple of fixes, DT and MFD adaptation: + *   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 + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/export.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/reboot.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <linux/io.h> + +#include "ti-bandgap.h" + +/***   Helper functions to access registers and their bitfields   ***/ + +/** + * ti_bandgap_readl() - simple read helper function + * @bgp: pointer to ti_bandgap structure + * @reg: desired register (offset) to be read + * + * Helper function to read bandgap registers. It uses the io remapped area. + * Return: the register value. + */ +static u32 ti_bandgap_readl(struct ti_bandgap *bgp, u32 reg) +{ +	return readl(bgp->base + reg); +} + +/** + * ti_bandgap_writel() - simple write helper function + * @bgp: pointer to ti_bandgap structure + * @val: desired register value to be written + * @reg: desired register (offset) to be written + * + * Helper function to write bandgap registers. It uses the io remapped area. + */ +static void ti_bandgap_writel(struct ti_bandgap *bgp, u32 val, u32 reg) +{ +	writel(val, bgp->base + reg); +} + +/** + * DOC: macro to update bits. + * + * RMW_BITS() - used to read, modify and update bandgap bitfields. + *            The value passed will be shifted. + */ +#define RMW_BITS(bgp, id, reg, mask, val)			\ +do {								\ +	struct temp_sensor_registers *t;			\ +	u32 r;							\ +								\ +	t = bgp->conf->sensors[(id)].registers;		\ +	r = ti_bandgap_readl(bgp, t->reg);			\ +	r &= ~t->mask;						\ +	r |= (val) << __ffs(t->mask);				\ +	ti_bandgap_writel(bgp, r, t->reg);			\ +} while (0) + +/***   Basic helper functions   ***/ + +/** + * ti_bandgap_power() - controls the power state of a bandgap device + * @bgp: pointer to ti_bandgap structure + * @on: desired power state (1 - on, 0 - off) + * + * Used to power on/off a bandgap device instance. Only used on those + * that features tempsoff bit. + * + * Return: 0 on success, -ENOTSUPP if tempsoff is not supported. + */ +static int ti_bandgap_power(struct ti_bandgap *bgp, bool on) +{ +	int i, ret = 0; + +	if (!TI_BANDGAP_HAS(bgp, POWER_SWITCH)) { +		ret = -ENOTSUPP; +		goto exit; +	} + +	for (i = 0; i < bgp->conf->sensor_count; i++) +		/* active on 0 */ +		RMW_BITS(bgp, i, temp_sensor_ctrl, bgap_tempsoff_mask, !on); + +exit: +	return ret; +} + +/** + * ti_bandgap_read_temp() - helper function to read sensor temperature + * @bgp: pointer to ti_bandgap structure + * @id: bandgap sensor id + * + * Function to concentrate the steps to read sensor temperature register. + * This function is desired because, depending on bandgap device version, + * it might be needed to freeze the bandgap state machine, before fetching + * the register value. + * + * Return: temperature in ADC values. + */ +static u32 ti_bandgap_read_temp(struct ti_bandgap *bgp, int id) +{ +	struct temp_sensor_registers *tsr; +	u32 temp, reg; + +	tsr = bgp->conf->sensors[id].registers; +	reg = tsr->temp_sensor_ctrl; + +	if (TI_BANDGAP_HAS(bgp, FREEZE_BIT)) { +		RMW_BITS(bgp, id, bgap_mask_ctrl, mask_freeze_mask, 1); +		/* +		 * In case we cannot read from cur_dtemp / dtemp_0, +		 * then we read from the last valid temp read +		 */ +		reg = tsr->ctrl_dtemp_1; +	} + +	/* read temperature */ +	temp = ti_bandgap_readl(bgp, reg); +	temp &= tsr->bgap_dtemp_mask; + +	if (TI_BANDGAP_HAS(bgp, FREEZE_BIT)) +		RMW_BITS(bgp, id, bgap_mask_ctrl, mask_freeze_mask, 0); + +	return temp; +} + +/***   IRQ handlers   ***/ + +/** + * ti_bandgap_talert_irq_handler() - handles Temperature alert IRQs + * @irq: IRQ number + * @data: private data (struct ti_bandgap *) + * + * This is the Talert handler. Use it only if bandgap device features + * HAS(TALERT). This handler goes over all sensors and checks their + * conditions and acts accordingly. In case there are events pending, + * it will reset the event mask to wait for the opposite event (next event). + * Every time there is a new event, it will be reported to thermal layer. + * + * Return: IRQ_HANDLED + */ +static irqreturn_t ti_bandgap_talert_irq_handler(int irq, void *data) +{ +	struct ti_bandgap *bgp = data; +	struct temp_sensor_registers *tsr; +	u32 t_hot = 0, t_cold = 0, ctrl; +	int i; + +	spin_lock(&bgp->lock); +	for (i = 0; i < bgp->conf->sensor_count; i++) { +		tsr = bgp->conf->sensors[i].registers; +		ctrl = ti_bandgap_readl(bgp, tsr->bgap_status); + +		/* Read the status of t_hot */ +		t_hot = ctrl & tsr->status_hot_mask; + +		/* Read the status of t_cold */ +		t_cold = ctrl & tsr->status_cold_mask; + +		if (!t_cold && !t_hot) +			continue; + +		ctrl = ti_bandgap_readl(bgp, tsr->bgap_mask_ctrl); +		/* +		 * One TALERT interrupt: Two sources +		 * If the interrupt is due to t_hot then mask t_hot and +		 * and unmask t_cold else mask t_cold and unmask t_hot +		 */ +		if (t_hot) { +			ctrl &= ~tsr->mask_hot_mask; +			ctrl |= tsr->mask_cold_mask; +		} else if (t_cold) { +			ctrl &= ~tsr->mask_cold_mask; +			ctrl |= tsr->mask_hot_mask; +		} + +		ti_bandgap_writel(bgp, ctrl, tsr->bgap_mask_ctrl); + +		dev_dbg(bgp->dev, +			"%s: IRQ from %s sensor: hotevent %d coldevent %d\n", +			__func__, bgp->conf->sensors[i].domain, +			t_hot, t_cold); + +		/* report temperature to whom may concern */ +		if (bgp->conf->report_temperature) +			bgp->conf->report_temperature(bgp, i); +	} +	spin_unlock(&bgp->lock); + +	return IRQ_HANDLED; +} + +/** + * ti_bandgap_tshut_irq_handler() - handles Temperature shutdown signal + * @irq: IRQ number + * @data: private data (unused) + * + * This is the Tshut handler. Use it only if bandgap device features + * HAS(TSHUT). If any sensor fires the Tshut signal, we simply shutdown + * the system. + * + * Return: IRQ_HANDLED + */ +static irqreturn_t ti_bandgap_tshut_irq_handler(int irq, void *data) +{ +	pr_emerg("%s: TSHUT temperature reached. Needs shut down...\n", +		 __func__); + +	orderly_poweroff(true); + +	return IRQ_HANDLED; +} + +/***   Helper functions which manipulate conversion ADC <-> mi Celsius   ***/ + +/** + * ti_bandgap_adc_to_mcelsius() - converts an ADC value to mCelsius scale + * @bgp: struct ti_bandgap pointer + * @adc_val: value in ADC representation + * @t: address where to write the resulting temperature in mCelsius + * + * Simple conversion from ADC representation to mCelsius. In case the ADC value + * is out of the ADC conv table range, it returns -ERANGE, 0 on success. + * The conversion table is indexed by the ADC values. + * + * Return: 0 if conversion was successful, else -ERANGE in case the @adc_val + * argument is out of the ADC conv table range. + */ +static +int ti_bandgap_adc_to_mcelsius(struct ti_bandgap *bgp, int adc_val, int *t) +{ +	const struct ti_bandgap_data *conf = bgp->conf; +	int ret = 0; + +	/* look up for temperature in the table and return the temperature */ +	if (adc_val < conf->adc_start_val || adc_val > conf->adc_end_val) { +		ret = -ERANGE; +		goto exit; +	} + +	*t = bgp->conf->conv_table[adc_val - conf->adc_start_val]; + +exit: +	return ret; +} + +/** + * ti_bandgap_mcelsius_to_adc() - converts a mCelsius value to ADC scale + * @bgp: struct ti_bandgap pointer + * @temp: value in mCelsius + * @adc: address where to write the resulting temperature in ADC representation + * + * Simple conversion from mCelsius to ADC values. In case the temp value + * is out of the ADC conv table range, it returns -ERANGE, 0 on success. + * The conversion table is indexed by the ADC values. + * + * Return: 0 if conversion was successful, else -ERANGE in case the @temp + * argument is out of the ADC conv table range. + */ +static +int ti_bandgap_mcelsius_to_adc(struct ti_bandgap *bgp, long temp, int *adc) +{ +	const struct ti_bandgap_data *conf = bgp->conf; +	const int *conv_table = bgp->conf->conv_table; +	int high, low, mid, ret = 0; + +	low = 0; +	high = conf->adc_end_val - conf->adc_start_val; +	mid = (high + low) / 2; + +	if (temp < conv_table[low] || temp > conv_table[high]) { +		ret = -ERANGE; +		goto exit; +	} + +	while (low < high) { +		if (temp < conv_table[mid]) +			high = mid - 1; +		else +			low = mid + 1; +		mid = (low + high) / 2; +	} + +	*adc = conf->adc_start_val + low; + +exit: +	return ret; +} + +/** + * ti_bandgap_add_hyst() - add hysteresis (in mCelsius) to an ADC value + * @bgp: struct ti_bandgap pointer + * @adc_val: temperature value in ADC representation + * @hyst_val: hysteresis value in mCelsius + * @sum: address where to write the resulting temperature (in ADC scale) + * + * Adds an hysteresis value (in mCelsius) to a ADC temperature value. + * + * Return: 0 on success, -ERANGE otherwise. + */ +static +int ti_bandgap_add_hyst(struct ti_bandgap *bgp, int adc_val, int hyst_val, +			u32 *sum) +{ +	int temp, ret; + +	/* +	 * Need to add in the mcelsius domain, so we have a temperature +	 * the conv_table range +	 */ +	ret = ti_bandgap_adc_to_mcelsius(bgp, adc_val, &temp); +	if (ret < 0) +		goto exit; + +	temp += hyst_val; + +	ret = ti_bandgap_mcelsius_to_adc(bgp, temp, sum); + +exit: +	return ret; +} + +/***   Helper functions handling device Alert/Shutdown signals   ***/ + +/** + * ti_bandgap_unmask_interrupts() - unmasks the events of thot & tcold + * @bgp: struct ti_bandgap pointer + * @id: bandgap sensor id + * @t_hot: hot temperature value to trigger alert signal + * @t_cold: cold temperature value to trigger alert signal + * + * Checks the requested t_hot and t_cold values and configures the IRQ event + * masks accordingly. Call this function only if bandgap features HAS(TALERT). + */ +static void ti_bandgap_unmask_interrupts(struct ti_bandgap *bgp, int id, +					 u32 t_hot, u32 t_cold) +{ +	struct temp_sensor_registers *tsr; +	u32 temp, reg_val; + +	/* Read the current on die temperature */ +	temp = ti_bandgap_read_temp(bgp, id); + +	tsr = bgp->conf->sensors[id].registers; +	reg_val = ti_bandgap_readl(bgp, tsr->bgap_mask_ctrl); + +	if (temp < t_hot) +		reg_val |= tsr->mask_hot_mask; +	else +		reg_val &= ~tsr->mask_hot_mask; + +	if (t_cold < temp) +		reg_val |= tsr->mask_cold_mask; +	else +		reg_val &= ~tsr->mask_cold_mask; +	ti_bandgap_writel(bgp, reg_val, tsr->bgap_mask_ctrl); +} + +/** + * ti_bandgap_update_alert_threshold() - sequence to update thresholds + * @bgp: struct ti_bandgap pointer + * @id: bandgap sensor id + * @val: value (ADC) of a new threshold + * @hot: desired threshold to be updated. true if threshold hot, false if + *       threshold cold + * + * It will program the required thresholds (hot and cold) for TALERT signal. + * This function can be used to update t_hot or t_cold, depending on @hot value. + * It checks the resulting t_hot and t_cold values, based on the new passed @val + * and configures the thresholds so that t_hot is always greater than t_cold. + * Call this function only if bandgap features HAS(TALERT). + * + * Return: 0 if no error, else corresponding error + */ +static int ti_bandgap_update_alert_threshold(struct ti_bandgap *bgp, int id, +					     int val, bool hot) +{ +	struct temp_sensor_data *ts_data = bgp->conf->sensors[id].ts_data; +	struct temp_sensor_registers *tsr; +	u32 thresh_val, reg_val, t_hot, t_cold; +	int err = 0; + +	tsr = bgp->conf->sensors[id].registers; + +	/* obtain the current value */ +	thresh_val = ti_bandgap_readl(bgp, tsr->bgap_threshold); +	t_cold = (thresh_val & tsr->threshold_tcold_mask) >> +		__ffs(tsr->threshold_tcold_mask); +	t_hot = (thresh_val & tsr->threshold_thot_mask) >> +		__ffs(tsr->threshold_thot_mask); +	if (hot) +		t_hot = val; +	else +		t_cold = val; + +	if (t_cold > t_hot) { +		if (hot) +			err = ti_bandgap_add_hyst(bgp, t_hot, +						  -ts_data->hyst_val, +						  &t_cold); +		else +			err = ti_bandgap_add_hyst(bgp, t_cold, +						  ts_data->hyst_val, +						  &t_hot); +	} + +	/* write the new threshold values */ +	reg_val = thresh_val & +		  ~(tsr->threshold_thot_mask | tsr->threshold_tcold_mask); +	reg_val |= (t_hot << __ffs(tsr->threshold_thot_mask)) | +		   (t_cold << __ffs(tsr->threshold_tcold_mask)); +	ti_bandgap_writel(bgp, reg_val, tsr->bgap_threshold); + +	if (err) { +		dev_err(bgp->dev, "failed to reprogram thot threshold\n"); +		err = -EIO; +		goto exit; +	} + +	ti_bandgap_unmask_interrupts(bgp, id, t_hot, t_cold); +exit: +	return err; +} + +/** + * ti_bandgap_validate() - helper to check the sanity of a struct ti_bandgap + * @bgp: struct ti_bandgap pointer + * @id: bandgap sensor id + * + * Checks if the bandgap pointer is valid and if the sensor id is also + * applicable. + * + * Return: 0 if no errors, -EINVAL for invalid @bgp pointer or -ERANGE if + * @id cannot index @bgp sensors. + */ +static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id) +{ +	int ret = 0; + +	if (!bgp || IS_ERR(bgp)) { +		pr_err("%s: invalid bandgap pointer\n", __func__); +		ret = -EINVAL; +		goto exit; +	} + +	if ((id < 0) || (id >= bgp->conf->sensor_count)) { +		dev_err(bgp->dev, "%s: sensor id out of range (%d)\n", +			__func__, id); +		ret = -ERANGE; +	} + +exit: +	return ret; +} + +/** + * _ti_bandgap_write_threshold() - helper to update TALERT t_cold or t_hot + * @bgp: struct ti_bandgap pointer + * @id: bandgap sensor id + * @val: value (mCelsius) of a new threshold + * @hot: desired threshold to be updated. true if threshold hot, false if + *       threshold cold + * + * It will update the required thresholds (hot and cold) for TALERT signal. + * This function can be used to update t_hot or t_cold, depending on @hot value. + * Validates the mCelsius range and update the requested threshold. + * Call this function only if bandgap features HAS(TALERT). + * + * Return: 0 if no error, else corresponding error value. + */ +static int _ti_bandgap_write_threshold(struct ti_bandgap *bgp, int id, int val, +				       bool hot) +{ +	struct temp_sensor_data *ts_data; +	struct temp_sensor_registers *tsr; +	u32 adc_val; +	int ret; + +	ret = ti_bandgap_validate(bgp, id); +	if (ret) +		goto exit; + +	if (!TI_BANDGAP_HAS(bgp, TALERT)) { +		ret = -ENOTSUPP; +		goto exit; +	} + +	ts_data = bgp->conf->sensors[id].ts_data; +	tsr = bgp->conf->sensors[id].registers; +	if (hot) { +		if (val < ts_data->min_temp + ts_data->hyst_val) +			ret = -EINVAL; +	} else { +		if (val > ts_data->max_temp + ts_data->hyst_val) +			ret = -EINVAL; +	} + +	if (ret) +		goto exit; + +	ret = ti_bandgap_mcelsius_to_adc(bgp, val, &adc_val); +	if (ret < 0) +		goto exit; + +	spin_lock(&bgp->lock); +	ret = ti_bandgap_update_alert_threshold(bgp, id, adc_val, hot); +	spin_unlock(&bgp->lock); + +exit: +	return ret; +} + +/** + * _ti_bandgap_read_threshold() - helper to read TALERT t_cold or t_hot + * @bgp: struct ti_bandgap pointer + * @id: bandgap sensor id + * @val: value (mCelsius) of a threshold + * @hot: desired threshold to be read. true if threshold hot, false if + *       threshold cold + * + * It will fetch the required thresholds (hot and cold) for TALERT signal. + * This function can be used to read t_hot or t_cold, depending on @hot value. + * Call this function only if bandgap features HAS(TALERT). + * + * Return: 0 if no error, -ENOTSUPP if it has no TALERT support, or the + * corresponding error value if some operation fails. + */ +static int _ti_bandgap_read_threshold(struct ti_bandgap *bgp, int id, +				      int *val, bool hot) +{ +	struct temp_sensor_registers *tsr; +	u32 temp, mask; +	int ret = 0; + +	ret = ti_bandgap_validate(bgp, id); +	if (ret) +		goto exit; + +	if (!TI_BANDGAP_HAS(bgp, TALERT)) { +		ret = -ENOTSUPP; +		goto exit; +	} + +	tsr = bgp->conf->sensors[id].registers; +	if (hot) +		mask = tsr->threshold_thot_mask; +	else +		mask = tsr->threshold_tcold_mask; + +	temp = ti_bandgap_readl(bgp, tsr->bgap_threshold); +	temp = (temp & mask) >> __ffs(mask); +	ret |= ti_bandgap_adc_to_mcelsius(bgp, temp, &temp); +	if (ret) { +		dev_err(bgp->dev, "failed to read thot\n"); +		ret = -EIO; +		goto exit; +	} + +	*val = temp; + +exit: +	return ret; +} + +/***   Exposed APIs   ***/ + +/** + * ti_bandgap_read_thot() - reads sensor current thot + * @bgp: pointer to bandgap instance + * @id: sensor id + * @thot: resulting current thot value + * + * Return: 0 on success or the proper error code + */ +int ti_bandgap_read_thot(struct ti_bandgap *bgp, int id, int *thot) +{ +	return _ti_bandgap_read_threshold(bgp, id, thot, true); +} + +/** + * ti_bandgap_write_thot() - sets sensor current thot + * @bgp: pointer to bandgap instance + * @id: sensor id + * @val: desired thot value + * + * Return: 0 on success or the proper error code + */ +int ti_bandgap_write_thot(struct ti_bandgap *bgp, int id, int val) +{ +	return _ti_bandgap_write_threshold(bgp, id, val, true); +} + +/** + * ti_bandgap_read_tcold() - reads sensor current tcold + * @bgp: pointer to bandgap instance + * @id: sensor id + * @tcold: resulting current tcold value + * + * Return: 0 on success or the proper error code + */ +int ti_bandgap_read_tcold(struct ti_bandgap *bgp, int id, int *tcold) +{ +	return _ti_bandgap_read_threshold(bgp, id, tcold, false); +} + +/** + * ti_bandgap_write_tcold() - sets the sensor tcold + * @bgp: pointer to bandgap instance + * @id: sensor id + * @val: desired tcold value + * + * Return: 0 on success or the proper error code + */ +int ti_bandgap_write_tcold(struct ti_bandgap *bgp, int id, int val) +{ +	return _ti_bandgap_write_threshold(bgp, id, val, false); +} + +/** + * ti_bandgap_read_counter() - read the sensor counter + * @bgp: pointer to bandgap instance + * @id: sensor id + * @interval: resulting update interval in miliseconds + */ +static void ti_bandgap_read_counter(struct ti_bandgap *bgp, int id, +				    int *interval) +{ +	struct temp_sensor_registers *tsr; +	int time; + +	tsr = bgp->conf->sensors[id].registers; +	time = ti_bandgap_readl(bgp, tsr->bgap_counter); +	time = (time & tsr->counter_mask) >> +					__ffs(tsr->counter_mask); +	time = time * 1000 / bgp->clk_rate; +	*interval = time; +} + +/** + * ti_bandgap_read_counter_delay() - read the sensor counter delay + * @bgp: pointer to bandgap instance + * @id: sensor id + * @interval: resulting update interval in miliseconds + */ +static void ti_bandgap_read_counter_delay(struct ti_bandgap *bgp, int id, +					  int *interval) +{ +	struct temp_sensor_registers *tsr; +	int reg_val; + +	tsr = bgp->conf->sensors[id].registers; + +	reg_val = ti_bandgap_readl(bgp, tsr->bgap_mask_ctrl); +	reg_val = (reg_val & tsr->mask_counter_delay_mask) >> +				__ffs(tsr->mask_counter_delay_mask); +	switch (reg_val) { +	case 0: +		*interval = 0; +		break; +	case 1: +		*interval = 1; +		break; +	case 2: +		*interval = 10; +		break; +	case 3: +		*interval = 100; +		break; +	case 4: +		*interval = 250; +		break; +	case 5: +		*interval = 500; +		break; +	default: +		dev_warn(bgp->dev, "Wrong counter delay value read from register %X", +			 reg_val); +	} +} + +/** + * ti_bandgap_read_update_interval() - read the sensor update interval + * @bgp: pointer to bandgap instance + * @id: sensor id + * @interval: resulting update interval in miliseconds + * + * Return: 0 on success or the proper error code + */ +int ti_bandgap_read_update_interval(struct ti_bandgap *bgp, int id, +				    int *interval) +{ +	int ret = 0; + +	ret = ti_bandgap_validate(bgp, id); +	if (ret) +		goto exit; + +	if (!TI_BANDGAP_HAS(bgp, COUNTER) && +	    !TI_BANDGAP_HAS(bgp, COUNTER_DELAY)) { +		ret = -ENOTSUPP; +		goto exit; +	} + +	if (TI_BANDGAP_HAS(bgp, COUNTER)) { +		ti_bandgap_read_counter(bgp, id, interval); +		goto exit; +	} + +	ti_bandgap_read_counter_delay(bgp, id, interval); +exit: +	return ret; +} + +/** + * ti_bandgap_write_counter_delay() - set the counter_delay + * @bgp: pointer to bandgap instance + * @id: sensor id + * @interval: desired update interval in miliseconds + * + * Return: 0 on success or the proper error code + */ +static int ti_bandgap_write_counter_delay(struct ti_bandgap *bgp, int id, +					  u32 interval) +{ +	int rval; + +	switch (interval) { +	case 0: /* Immediate conversion */ +		rval = 0x0; +		break; +	case 1: /* Conversion after ever 1ms */ +		rval = 0x1; +		break; +	case 10: /* Conversion after ever 10ms */ +		rval = 0x2; +		break; +	case 100: /* Conversion after ever 100ms */ +		rval = 0x3; +		break; +	case 250: /* Conversion after ever 250ms */ +		rval = 0x4; +		break; +	case 500: /* Conversion after ever 500ms */ +		rval = 0x5; +		break; +	default: +		dev_warn(bgp->dev, "Delay %d ms is not supported\n", interval); +		return -EINVAL; +	} + +	spin_lock(&bgp->lock); +	RMW_BITS(bgp, id, bgap_mask_ctrl, mask_counter_delay_mask, rval); +	spin_unlock(&bgp->lock); + +	return 0; +} + +/** + * ti_bandgap_write_counter() - set the bandgap sensor counter + * @bgp: pointer to bandgap instance + * @id: sensor id + * @interval: desired update interval in miliseconds + */ +static void ti_bandgap_write_counter(struct ti_bandgap *bgp, int id, +				     u32 interval) +{ +	interval = interval * bgp->clk_rate / 1000; +	spin_lock(&bgp->lock); +	RMW_BITS(bgp, id, bgap_counter, counter_mask, interval); +	spin_unlock(&bgp->lock); +} + +/** + * ti_bandgap_write_update_interval() - set the update interval + * @bgp: pointer to bandgap instance + * @id: sensor id + * @interval: desired update interval in miliseconds + * + * Return: 0 on success or the proper error code + */ +int ti_bandgap_write_update_interval(struct ti_bandgap *bgp, +				     int id, u32 interval) +{ +	int ret = ti_bandgap_validate(bgp, id); +	if (ret) +		goto exit; + +	if (!TI_BANDGAP_HAS(bgp, COUNTER) && +	    !TI_BANDGAP_HAS(bgp, COUNTER_DELAY)) { +		ret = -ENOTSUPP; +		goto exit; +	} + +	if (TI_BANDGAP_HAS(bgp, COUNTER)) { +		ti_bandgap_write_counter(bgp, id, interval); +		goto exit; +	} + +	ret = ti_bandgap_write_counter_delay(bgp, id, interval); +exit: +	return ret; +} + +/** + * ti_bandgap_read_temperature() - report current temperature + * @bgp: pointer to bandgap instance + * @id: sensor id + * @temperature: resulting temperature + * + * Return: 0 on success or the proper error code + */ +int ti_bandgap_read_temperature(struct ti_bandgap *bgp, int id, +				int *temperature) +{ +	u32 temp; +	int ret; + +	ret = ti_bandgap_validate(bgp, id); +	if (ret) +		return ret; + +	spin_lock(&bgp->lock); +	temp = ti_bandgap_read_temp(bgp, id); +	spin_unlock(&bgp->lock); + +	ret |= ti_bandgap_adc_to_mcelsius(bgp, temp, &temp); +	if (ret) +		return -EIO; + +	*temperature = temp; + +	return 0; +} + +/** + * ti_bandgap_set_sensor_data() - helper function to store thermal + * framework related data. + * @bgp: pointer to bandgap instance + * @id: sensor id + * @data: thermal framework related data to be stored + * + * Return: 0 on success or the proper error code + */ +int ti_bandgap_set_sensor_data(struct ti_bandgap *bgp, int id, void *data) +{ +	int ret = ti_bandgap_validate(bgp, id); +	if (ret) +		return ret; + +	bgp->regval[id].data = data; + +	return 0; +} + +/** + * ti_bandgap_get_sensor_data() - helper function to get thermal + * framework related data. + * @bgp: pointer to bandgap instance + * @id: sensor id + * + * Return: data stored by set function with sensor id on success or NULL + */ +void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id) +{ +	int ret = ti_bandgap_validate(bgp, id); +	if (ret) +		return ERR_PTR(ret); + +	return bgp->regval[id].data; +} + +/***   Helper functions used during device initialization   ***/ + +/** + * ti_bandgap_force_single_read() - executes 1 single ADC conversion + * @bgp: pointer to struct ti_bandgap + * @id: sensor id which it is desired to read 1 temperature + * + * Used to initialize the conversion state machine and set it to a valid + * state. Called during device initialization and context restore events. + * + * Return: 0 + */ +static int +ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id) +{ +	u32 temp = 0, counter = 1000; + +	/* Select single conversion mode */ +	if (TI_BANDGAP_HAS(bgp, MODE_CONFIG)) +		RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 0); + +	/* Start of Conversion = 1 */ +	RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 1); +	/* Wait until DTEMP is updated */ +	temp = ti_bandgap_read_temp(bgp, id); + +	while ((temp == 0) && --counter) +		temp = ti_bandgap_read_temp(bgp, id); +	/* REVISIT: Check correct condition for end of conversion */ + +	/* Start of Conversion = 0 */ +	RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 0); + +	return 0; +} + +/** + * ti_bandgap_set_continous_mode() - One time enabling of continuous mode + * @bgp: pointer to struct ti_bandgap + * + * Call this function only if HAS(MODE_CONFIG) is set. As this driver may + * be used for junction temperature monitoring, it is desirable that the + * sensors are operational all the time, so that alerts are generated + * properly. + * + * Return: 0 + */ +static int ti_bandgap_set_continuous_mode(struct ti_bandgap *bgp) +{ +	int i; + +	for (i = 0; i < bgp->conf->sensor_count; i++) { +		/* Perform a single read just before enabling continuous */ +		ti_bandgap_force_single_read(bgp, i); +		RMW_BITS(bgp, i, bgap_mode_ctrl, mode_ctrl_mask, 1); +	} + +	return 0; +} + +/** + * ti_bandgap_get_trend() - To fetch the temperature trend of a sensor + * @bgp: pointer to struct ti_bandgap + * @id: id of the individual sensor + * @trend: Pointer to trend. + * + * This function needs to be called to fetch the temperature trend of a + * Particular sensor. The function computes the difference in temperature + * w.r.t time. For the bandgaps with built in history buffer the temperatures + * are read from the buffer and for those without the Buffer -ENOTSUPP is + * returned. + * + * Return: 0 if no error, else return corresponding error. If no + *		error then the trend value is passed on to trend parameter + */ +int ti_bandgap_get_trend(struct ti_bandgap *bgp, int id, int *trend) +{ +	struct temp_sensor_registers *tsr; +	u32 temp1, temp2, reg1, reg2; +	int t1, t2, interval, ret = 0; + +	ret = ti_bandgap_validate(bgp, id); +	if (ret) +		goto exit; + +	if (!TI_BANDGAP_HAS(bgp, HISTORY_BUFFER) || +	    !TI_BANDGAP_HAS(bgp, FREEZE_BIT)) { +		ret = -ENOTSUPP; +		goto exit; +	} + +	spin_lock(&bgp->lock); + +	tsr = bgp->conf->sensors[id].registers; + +	/* Freeze and read the last 2 valid readings */ +	RMW_BITS(bgp, id, bgap_mask_ctrl, mask_freeze_mask, 1); +	reg1 = tsr->ctrl_dtemp_1; +	reg2 = tsr->ctrl_dtemp_2; + +	/* read temperature from history buffer */ +	temp1 = ti_bandgap_readl(bgp, reg1); +	temp1 &= tsr->bgap_dtemp_mask; + +	temp2 = ti_bandgap_readl(bgp, reg2); +	temp2 &= tsr->bgap_dtemp_mask; + +	/* Convert from adc values to mCelsius temperature */ +	ret = ti_bandgap_adc_to_mcelsius(bgp, temp1, &t1); +	if (ret) +		goto unfreeze; + +	ret = ti_bandgap_adc_to_mcelsius(bgp, temp2, &t2); +	if (ret) +		goto unfreeze; + +	/* Fetch the update interval */ +	ret = ti_bandgap_read_update_interval(bgp, id, &interval); +	if (ret) +		goto unfreeze; + +	/* Set the interval to 1 ms if bandgap counter delay is not set */ +	if (interval == 0) +		interval = 1; + +	*trend = (t1 - t2) / interval; + +	dev_dbg(bgp->dev, "The temperatures are t1 = %d and t2 = %d and trend =%d\n", +		t1, t2, *trend); + +unfreeze: +	RMW_BITS(bgp, id, bgap_mask_ctrl, mask_freeze_mask, 0); +	spin_unlock(&bgp->lock); +exit: +	return ret; +} + +/** + * ti_bandgap_tshut_init() - setup and initialize tshut handling + * @bgp: pointer to struct ti_bandgap + * @pdev: pointer to device struct platform_device + * + * Call this function only in case the bandgap features HAS(TSHUT). + * In this case, the driver needs to handle the TSHUT signal as an IRQ. + * The IRQ is wired as a GPIO, and for this purpose, it is required + * to specify which GPIO line is used. TSHUT IRQ is fired anytime + * one of the bandgap sensors violates the TSHUT high/hot threshold. + * And in that case, the system must go off. + * + * Return: 0 if no error, else error status + */ +static int ti_bandgap_tshut_init(struct ti_bandgap *bgp, +				 struct platform_device *pdev) +{ +	int gpio_nr = bgp->tshut_gpio; +	int status; + +	/* Request for gpio_86 line */ +	status = gpio_request(gpio_nr, "tshut"); +	if (status < 0) { +		dev_err(bgp->dev, "Could not request for TSHUT GPIO:%i\n", 86); +		return status; +	} +	status = gpio_direction_input(gpio_nr); +	if (status) { +		dev_err(bgp->dev, "Cannot set input TSHUT GPIO %d\n", gpio_nr); +		return status; +	} + +	status = request_irq(gpio_to_irq(gpio_nr), ti_bandgap_tshut_irq_handler, +			     IRQF_TRIGGER_RISING, "tshut", NULL); +	if (status) { +		gpio_free(gpio_nr); +		dev_err(bgp->dev, "request irq failed for TSHUT"); +	} + +	return 0; +} + +/** + * ti_bandgap_alert_init() - setup and initialize talert handling + * @bgp: pointer to struct ti_bandgap + * @pdev: pointer to device struct platform_device + * + * Call this function only in case the bandgap features HAS(TALERT). + * In this case, the driver needs to handle the TALERT signals as an IRQs. + * TALERT is a normal IRQ and it is fired any time thresholds (hot or cold) + * are violated. In these situation, the driver must reprogram the thresholds, + * accordingly to specified policy. + * + * Return: 0 if no error, else return corresponding error. + */ +static int ti_bandgap_talert_init(struct ti_bandgap *bgp, +				  struct platform_device *pdev) +{ +	int ret; + +	bgp->irq = platform_get_irq(pdev, 0); +	if (bgp->irq < 0) { +		dev_err(&pdev->dev, "get_irq failed\n"); +		return bgp->irq; +	} +	ret = request_threaded_irq(bgp->irq, NULL, +				   ti_bandgap_talert_irq_handler, +				   IRQF_TRIGGER_HIGH | IRQF_ONESHOT, +				   "talert", bgp); +	if (ret) { +		dev_err(&pdev->dev, "Request threaded irq failed.\n"); +		return ret; +	} + +	return 0; +} + +static const struct of_device_id of_ti_bandgap_match[]; +/** + * ti_bandgap_build() - parse DT and setup a struct ti_bandgap + * @pdev: pointer to device struct platform_device + * + * Used to read the device tree properties accordingly to the bandgap + * matching version. Based on bandgap version and its capabilities it + * will build a struct ti_bandgap out of the required DT entries. + * + * Return: valid bandgap structure if successful, else returns ERR_PTR + * return value must be verified with IS_ERR. + */ +static struct ti_bandgap *ti_bandgap_build(struct platform_device *pdev) +{ +	struct device_node *node = pdev->dev.of_node; +	const struct of_device_id *of_id; +	struct ti_bandgap *bgp; +	struct resource *res; +	int i; + +	/* just for the sake */ +	if (!node) { +		dev_err(&pdev->dev, "no platform information available\n"); +		return ERR_PTR(-EINVAL); +	} + +	bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL); +	if (!bgp) { +		dev_err(&pdev->dev, "Unable to allocate mem for driver ref\n"); +		return ERR_PTR(-ENOMEM); +	} + +	of_id = of_match_device(of_ti_bandgap_match, &pdev->dev); +	if (of_id) +		bgp->conf = of_id->data; + +	/* register shadow for context save and restore */ +	bgp->regval = devm_kzalloc(&pdev->dev, sizeof(*bgp->regval) * +				   bgp->conf->sensor_count, GFP_KERNEL); +	if (!bgp->regval) { +		dev_err(&pdev->dev, "Unable to allocate mem for driver ref\n"); +		return ERR_PTR(-ENOMEM); +	} + +	i = 0; +	do { +		void __iomem *chunk; + +		res = platform_get_resource(pdev, IORESOURCE_MEM, i); +		if (!res) +			break; +		chunk = devm_ioremap_resource(&pdev->dev, res); +		if (i == 0) +			bgp->base = chunk; +		if (IS_ERR(chunk)) +			return ERR_CAST(chunk); + +		i++; +	} while (res); + +	if (TI_BANDGAP_HAS(bgp, TSHUT)) { +		bgp->tshut_gpio = of_get_gpio(node, 0); +		if (!gpio_is_valid(bgp->tshut_gpio)) { +			dev_err(&pdev->dev, "invalid gpio for tshut (%d)\n", +				bgp->tshut_gpio); +			return ERR_PTR(-EINVAL); +		} +	} + +	return bgp; +} + +/***   Device driver call backs   ***/ + +static +int ti_bandgap_probe(struct platform_device *pdev) +{ +	struct ti_bandgap *bgp; +	int clk_rate, ret = 0, i; + +	bgp = ti_bandgap_build(pdev); +	if (IS_ERR(bgp)) { +		dev_err(&pdev->dev, "failed to fetch platform data\n"); +		return PTR_ERR(bgp); +	} +	bgp->dev = &pdev->dev; + +	if (TI_BANDGAP_HAS(bgp, TSHUT)) { +		ret = ti_bandgap_tshut_init(bgp, pdev); +		if (ret) { +			dev_err(&pdev->dev, +				"failed to initialize system tshut IRQ\n"); +			return ret; +		} +	} + +	bgp->fclock = clk_get(NULL, bgp->conf->fclock_name); +	ret = IS_ERR(bgp->fclock); +	if (ret) { +		dev_err(&pdev->dev, "failed to request fclock reference\n"); +		ret = PTR_ERR(bgp->fclock); +		goto free_irqs; +	} + +	bgp->div_clk = clk_get(NULL,  bgp->conf->div_ck_name); +	ret = IS_ERR(bgp->div_clk); +	if (ret) { +		dev_err(&pdev->dev, +			"failed to request div_ts_ck clock ref\n"); +		ret = PTR_ERR(bgp->div_clk); +		goto free_irqs; +	} + +	for (i = 0; i < bgp->conf->sensor_count; i++) { +		struct temp_sensor_registers *tsr; +		u32 val; + +		tsr = bgp->conf->sensors[i].registers; +		/* +		 * check if the efuse has a non-zero value if not +		 * it is an untrimmed sample and the temperatures +		 * may not be accurate +		 */ +		val = ti_bandgap_readl(bgp, tsr->bgap_efuse); +		if (ret || !val) +			dev_info(&pdev->dev, +				 "Non-trimmed BGAP, Temp not accurate\n"); +	} + +	clk_rate = clk_round_rate(bgp->div_clk, +				  bgp->conf->sensors[0].ts_data->max_freq); +	if (clk_rate < bgp->conf->sensors[0].ts_data->min_freq || +	    clk_rate <= 0) { +		ret = -ENODEV; +		dev_err(&pdev->dev, "wrong clock rate (%d)\n", clk_rate); +		goto put_clks; +	} + +	ret = clk_set_rate(bgp->div_clk, clk_rate); +	if (ret) +		dev_err(&pdev->dev, "Cannot re-set clock rate. Continuing\n"); + +	bgp->clk_rate = clk_rate; +	if (TI_BANDGAP_HAS(bgp, CLK_CTRL)) +		clk_prepare_enable(bgp->fclock); + + +	spin_lock_init(&bgp->lock); +	bgp->dev = &pdev->dev; +	platform_set_drvdata(pdev, bgp); + +	ti_bandgap_power(bgp, true); + +	/* Set default counter to 1 for now */ +	if (TI_BANDGAP_HAS(bgp, COUNTER)) +		for (i = 0; i < bgp->conf->sensor_count; i++) +			RMW_BITS(bgp, i, bgap_counter, counter_mask, 1); + +	/* Set default thresholds for alert and shutdown */ +	for (i = 0; i < bgp->conf->sensor_count; i++) { +		struct temp_sensor_data *ts_data; + +		ts_data = bgp->conf->sensors[i].ts_data; + +		if (TI_BANDGAP_HAS(bgp, TALERT)) { +			/* Set initial Talert thresholds */ +			RMW_BITS(bgp, i, bgap_threshold, +				 threshold_tcold_mask, ts_data->t_cold); +			RMW_BITS(bgp, i, bgap_threshold, +				 threshold_thot_mask, ts_data->t_hot); +			/* Enable the alert events */ +			RMW_BITS(bgp, i, bgap_mask_ctrl, mask_hot_mask, 1); +			RMW_BITS(bgp, i, bgap_mask_ctrl, mask_cold_mask, 1); +		} + +		if (TI_BANDGAP_HAS(bgp, TSHUT_CONFIG)) { +			/* Set initial Tshut thresholds */ +			RMW_BITS(bgp, i, tshut_threshold, +				 tshut_hot_mask, ts_data->tshut_hot); +			RMW_BITS(bgp, i, tshut_threshold, +				 tshut_cold_mask, ts_data->tshut_cold); +		} +	} + +	if (TI_BANDGAP_HAS(bgp, MODE_CONFIG)) +		ti_bandgap_set_continuous_mode(bgp); + +	/* Set .250 seconds time as default counter */ +	if (TI_BANDGAP_HAS(bgp, COUNTER)) +		for (i = 0; i < bgp->conf->sensor_count; i++) +			RMW_BITS(bgp, i, bgap_counter, counter_mask, +				 bgp->clk_rate / 4); + +	/* Every thing is good? Then expose the sensors */ +	for (i = 0; i < bgp->conf->sensor_count; i++) { +		char *domain; + +		if (bgp->conf->sensors[i].register_cooling) { +			ret = bgp->conf->sensors[i].register_cooling(bgp, i); +			if (ret) +				goto remove_sensors; +		} + +		if (bgp->conf->expose_sensor) { +			domain = bgp->conf->sensors[i].domain; +			ret = bgp->conf->expose_sensor(bgp, i, domain); +			if (ret) +				goto remove_last_cooling; +		} +	} + +	/* +	 * Enable the Interrupts once everything is set. Otherwise irq handler +	 * might be called as soon as it is enabled where as rest of framework +	 * is still getting initialised. +	 */ +	if (TI_BANDGAP_HAS(bgp, TALERT)) { +		ret = ti_bandgap_talert_init(bgp, pdev); +		if (ret) { +			dev_err(&pdev->dev, "failed to initialize Talert IRQ\n"); +			i = bgp->conf->sensor_count; +			goto disable_clk; +		} +	} + +	return 0; + +remove_last_cooling: +	if (bgp->conf->sensors[i].unregister_cooling) +		bgp->conf->sensors[i].unregister_cooling(bgp, i); +remove_sensors: +	for (i--; i >= 0; i--) { +		if (bgp->conf->sensors[i].unregister_cooling) +			bgp->conf->sensors[i].unregister_cooling(bgp, i); +		if (bgp->conf->remove_sensor) +			bgp->conf->remove_sensor(bgp, i); +	} +	ti_bandgap_power(bgp, false); +disable_clk: +	if (TI_BANDGAP_HAS(bgp, CLK_CTRL)) +		clk_disable_unprepare(bgp->fclock); +put_clks: +	clk_put(bgp->fclock); +	clk_put(bgp->div_clk); +free_irqs: +	if (TI_BANDGAP_HAS(bgp, TSHUT)) { +		free_irq(gpio_to_irq(bgp->tshut_gpio), NULL); +		gpio_free(bgp->tshut_gpio); +	} + +	return ret; +} + +static +int ti_bandgap_remove(struct platform_device *pdev) +{ +	struct ti_bandgap *bgp = platform_get_drvdata(pdev); +	int i; + +	/* First thing is to remove sensor interfaces */ +	for (i = 0; i < bgp->conf->sensor_count; i++) { +		if (bgp->conf->sensors[i].unregister_cooling) +			bgp->conf->sensors[i].unregister_cooling(bgp, i); + +		if (bgp->conf->remove_sensor) +			bgp->conf->remove_sensor(bgp, i); +	} + +	ti_bandgap_power(bgp, false); + +	if (TI_BANDGAP_HAS(bgp, CLK_CTRL)) +		clk_disable_unprepare(bgp->fclock); +	clk_put(bgp->fclock); +	clk_put(bgp->div_clk); + +	if (TI_BANDGAP_HAS(bgp, TALERT)) +		free_irq(bgp->irq, bgp); + +	if (TI_BANDGAP_HAS(bgp, TSHUT)) { +		free_irq(gpio_to_irq(bgp->tshut_gpio), NULL); +		gpio_free(bgp->tshut_gpio); +	} + +	return 0; +} + +#ifdef CONFIG_PM +static int ti_bandgap_save_ctxt(struct ti_bandgap *bgp) +{ +	int i; + +	for (i = 0; i < bgp->conf->sensor_count; i++) { +		struct temp_sensor_registers *tsr; +		struct temp_sensor_regval *rval; + +		rval = &bgp->regval[i]; +		tsr = bgp->conf->sensors[i].registers; + +		if (TI_BANDGAP_HAS(bgp, MODE_CONFIG)) +			rval->bg_mode_ctrl = ti_bandgap_readl(bgp, +							tsr->bgap_mode_ctrl); +		if (TI_BANDGAP_HAS(bgp, COUNTER)) +			rval->bg_counter = ti_bandgap_readl(bgp, +							tsr->bgap_counter); +		if (TI_BANDGAP_HAS(bgp, TALERT)) { +			rval->bg_threshold = ti_bandgap_readl(bgp, +							tsr->bgap_threshold); +			rval->bg_ctrl = ti_bandgap_readl(bgp, +						   tsr->bgap_mask_ctrl); +		} + +		if (TI_BANDGAP_HAS(bgp, TSHUT_CONFIG)) +			rval->tshut_threshold = ti_bandgap_readl(bgp, +						   tsr->tshut_threshold); +	} + +	return 0; +} + +static int ti_bandgap_restore_ctxt(struct ti_bandgap *bgp) +{ +	int i; + +	for (i = 0; i < bgp->conf->sensor_count; i++) { +		struct temp_sensor_registers *tsr; +		struct temp_sensor_regval *rval; +		u32 val = 0; + +		rval = &bgp->regval[i]; +		tsr = bgp->conf->sensors[i].registers; + +		if (TI_BANDGAP_HAS(bgp, COUNTER)) +			val = ti_bandgap_readl(bgp, tsr->bgap_counter); + +		if (TI_BANDGAP_HAS(bgp, TSHUT_CONFIG)) +			ti_bandgap_writel(bgp, rval->tshut_threshold, +					  tsr->tshut_threshold); +		/* Force immediate temperature measurement and update +		 * of the DTEMP field +		 */ +		ti_bandgap_force_single_read(bgp, i); + +		if (TI_BANDGAP_HAS(bgp, COUNTER)) +			ti_bandgap_writel(bgp, rval->bg_counter, +					  tsr->bgap_counter); +		if (TI_BANDGAP_HAS(bgp, MODE_CONFIG)) +			ti_bandgap_writel(bgp, rval->bg_mode_ctrl, +					  tsr->bgap_mode_ctrl); +		if (TI_BANDGAP_HAS(bgp, TALERT)) { +			ti_bandgap_writel(bgp, rval->bg_threshold, +					  tsr->bgap_threshold); +			ti_bandgap_writel(bgp, rval->bg_ctrl, +					  tsr->bgap_mask_ctrl); +		} +	} + +	return 0; +} + +static int ti_bandgap_suspend(struct device *dev) +{ +	struct ti_bandgap *bgp = dev_get_drvdata(dev); +	int err; + +	err = ti_bandgap_save_ctxt(bgp); +	ti_bandgap_power(bgp, false); + +	if (TI_BANDGAP_HAS(bgp, CLK_CTRL)) +		clk_disable_unprepare(bgp->fclock); + +	return err; +} + +static int ti_bandgap_resume(struct device *dev) +{ +	struct ti_bandgap *bgp = dev_get_drvdata(dev); + +	if (TI_BANDGAP_HAS(bgp, CLK_CTRL)) +		clk_prepare_enable(bgp->fclock); + +	ti_bandgap_power(bgp, true); + +	return ti_bandgap_restore_ctxt(bgp); +} +static SIMPLE_DEV_PM_OPS(ti_bandgap_dev_pm_ops, ti_bandgap_suspend, +			 ti_bandgap_resume); + +#define DEV_PM_OPS	(&ti_bandgap_dev_pm_ops) +#else +#define DEV_PM_OPS	NULL +#endif + +static const struct of_device_id of_ti_bandgap_match[] = { +#ifdef CONFIG_OMAP4_THERMAL +	{ +		.compatible = "ti,omap4430-bandgap", +		.data = (void *)&omap4430_data, +	}, +	{ +		.compatible = "ti,omap4460-bandgap", +		.data = (void *)&omap4460_data, +	}, +	{ +		.compatible = "ti,omap4470-bandgap", +		.data = (void *)&omap4470_data, +	}, +#endif +#ifdef CONFIG_OMAP5_THERMAL +	{ +		.compatible = "ti,omap5430-bandgap", +		.data = (void *)&omap5430_data, +	}, +#endif +#ifdef CONFIG_DRA752_THERMAL +	{ +		.compatible = "ti,dra752-bandgap", +		.data = (void *)&dra752_data, +	}, +#endif +	/* Sentinel */ +	{ }, +}; +MODULE_DEVICE_TABLE(of, of_ti_bandgap_match); + +static struct platform_driver ti_bandgap_sensor_driver = { +	.probe = ti_bandgap_probe, +	.remove = ti_bandgap_remove, +	.driver = { +			.name = "ti-soc-thermal", +			.pm = DEV_PM_OPS, +			.of_match_table	= of_ti_bandgap_match, +	}, +}; + +module_platform_driver(ti_bandgap_sensor_driver); + +MODULE_DESCRIPTION("OMAP4+ bandgap temperature sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ti-soc-thermal"); +MODULE_AUTHOR("Texas Instrument Inc."); diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.h b/drivers/thermal/ti-soc-thermal/ti-bandgap.h new file mode 100644 index 00000000000..b3adf72f252 --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.h @@ -0,0 +1,408 @@ +/* + * OMAP4 Bandgap temperature sensor driver + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Contact: + *   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 + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef __TI_BANDGAP_H +#define __TI_BANDGAP_H + +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/err.h> + +/** + * DOC: bandgap driver data structure + * ================================== + * + *   +----------+----------------+ + *   | struct temp_sensor_regval | + *   +---------------------------+ + *              * (Array of) + *              | + *              | + *   +-------------------+   +-----------------+ + *   | struct ti_bandgap |-->| struct device * | + *   +----------+--------+   +-----------------+ + *              | + *              | + *              V + *   +------------------------+ + *   | struct ti_bandgap_data | + *   +------------------------+ + *              | + *              | + *              * (Array of) + * +------------+------------------------------------------------------+ + * | +----------+------------+   +-------------------------+           | + * | | struct ti_temp_sensor |-->| struct temp_sensor_data |           | + * | +-----------------------+   +------------+------------+           | + * |            |                                                      | + * |            +                                                      | + * |            V                                                      | + * | +----------+-------------------+                                  | + * | | struct temp_sensor_registers |                                  | + * | +------------------------------+                                  | + * |                                                                   | + * +-------------------------------------------------------------------+ + * + * Above is a simple diagram describing how the data structure below + * are organized. For each bandgap device there should be a ti_bandgap_data + * containing the device instance configuration, as well as, an array of + * sensors, representing every sensor instance present in this bandgap. + */ + +/** + * struct temp_sensor_registers - descriptor to access registers and bitfields + * @temp_sensor_ctrl: TEMP_SENSOR_CTRL register offset + * @bgap_tempsoff_mask: mask to temp_sensor_ctrl.tempsoff + * @bgap_soc_mask: mask to temp_sensor_ctrl.soc + * @bgap_eocz_mask: mask to temp_sensor_ctrl.eocz + * @bgap_dtemp_mask: mask to temp_sensor_ctrl.dtemp + * @bgap_mask_ctrl: BANDGAP_MASK_CTRL register offset + * @mask_hot_mask: mask to bandgap_mask_ctrl.mask_hot + * @mask_cold_mask: mask to bandgap_mask_ctrl.mask_cold + * @mask_sidlemode_mask: mask to bandgap_mask_ctrl.mask_sidlemode + * @mask_counter_delay_mask: mask to bandgap_mask_ctrl.mask_counter_delay + * @mask_freeze_mask: mask to bandgap_mask_ctrl.mask_free + * @mask_clear_mask: mask to bandgap_mask_ctrl.mask_clear + * @mask_clear_accum_mask: mask to bandgap_mask_ctrl.mask_clear_accum + * @bgap_mode_ctrl: BANDGAP_MODE_CTRL register offset + * @mode_ctrl_mask: mask to bandgap_mode_ctrl.mode_ctrl + * @bgap_counter: BANDGAP_COUNTER register offset + * @counter_mask: mask to bandgap_counter.counter + * @bgap_threshold: BANDGAP_THRESHOLD register offset (TALERT thresholds) + * @threshold_thot_mask: mask to bandgap_threhold.thot + * @threshold_tcold_mask: mask to bandgap_threhold.tcold + * @tshut_threshold: TSHUT_THRESHOLD register offset (TSHUT thresholds) + * @tshut_efuse_mask: mask to tshut_threshold.tshut_efuse + * @tshut_efuse_shift: shift to tshut_threshold.tshut_efuse + * @tshut_hot_mask: mask to tshut_threhold.thot + * @tshut_cold_mask: mask to tshut_threhold.thot + * @bgap_status: BANDGAP_STATUS register offset + * @status_clean_stop_mask: mask to bandgap_status.clean_stop + * @status_bgap_alert_mask: mask to bandgap_status.bandgap_alert + * @status_hot_mask: mask to bandgap_status.hot + * @status_cold_mask: mask to bandgap_status.cold + * @bgap_cumul_dtemp: BANDGAP_CUMUL_DTEMP register offset + * @ctrl_dtemp_0: CTRL_DTEMP0 register offset + * @ctrl_dtemp_1: CTRL_DTEMP1 register offset + * @ctrl_dtemp_2: CTRL_DTEMP2 register offset + * @ctrl_dtemp_3: CTRL_DTEMP3 register offset + * @ctrl_dtemp_4: CTRL_DTEMP4 register offset + * @bgap_efuse: BANDGAP_EFUSE register offset + * + * The register offsets and bitfields might change across + * OMAP and variants versions. Hence this struct serves as a + * descriptor map on how to access the registers and the bitfields. + * + * This descriptor contains registers of all versions of bandgap chips. + * Not all versions will use all registers, depending on the available + * features. Please read TRMs for descriptive explanation on each bitfield. + */ + +struct temp_sensor_registers { +	u32	temp_sensor_ctrl; +	u32	bgap_tempsoff_mask; +	u32	bgap_soc_mask; +	u32	bgap_eocz_mask; /* not used: but needs revisit */ +	u32	bgap_dtemp_mask; + +	u32	bgap_mask_ctrl; +	u32	mask_hot_mask; +	u32	mask_cold_mask; +	u32	mask_sidlemode_mask; /* not used: but may be needed for pm */ +	u32	mask_counter_delay_mask; +	u32	mask_freeze_mask; +	u32	mask_clear_mask; /* not used: but needed for trending */ +	u32	mask_clear_accum_mask; /* not used: but needed for trending */ + +	u32	bgap_mode_ctrl; +	u32	mode_ctrl_mask; + +	u32	bgap_counter; +	u32	counter_mask; + +	u32	bgap_threshold; +	u32	threshold_thot_mask; +	u32	threshold_tcold_mask; + +	u32	tshut_threshold; +	u32	tshut_efuse_mask; /* not used */ +	u32	tshut_efuse_shift; /* not used */ +	u32	tshut_hot_mask; +	u32	tshut_cold_mask; + +	u32	bgap_status; +	u32	status_clean_stop_mask; /* not used: but needed for trending */ +	u32	status_bgap_alert_mask; /* not used */ +	u32	status_hot_mask; +	u32	status_cold_mask; + +	u32	bgap_cumul_dtemp; /* not used: but needed for trending */ +	u32	ctrl_dtemp_0; /* not used: but needed for trending */ +	u32	ctrl_dtemp_1; /* not used: but needed for trending */ +	u32	ctrl_dtemp_2; /* not used: but needed for trending */ +	u32	ctrl_dtemp_3; /* not used: but needed for trending */ +	u32	ctrl_dtemp_4; /* not used: but needed for trending */ +	u32	bgap_efuse; +}; + +/** + * struct temp_sensor_data - The thresholds and limits for temperature sensors. + * @tshut_hot: temperature to trigger a thermal reset (initial value) + * @tshut_cold: temp to get the plat out of reset due to thermal (init val) + * @t_hot: temperature to trigger a thermal alert (high initial value) + * @t_cold: temperature to trigger a thermal alert (low initial value) + * @min_freq: sensor minimum clock rate + * @max_freq: sensor maximum clock rate + * @max_temp: sensor maximum temperature + * @min_temp: sensor minimum temperature + * @hyst_val: temperature hysteresis considered while converting ADC values + * @update_int1: update interval + * @update_int2: update interval + * + * This data structure will hold the required thresholds and temperature limits + * for a specific temperature sensor, like shutdown temperature, alert + * temperature, clock / rate used, ADC conversion limits and update intervals + */ +struct temp_sensor_data { +	u32	tshut_hot; +	u32	tshut_cold; +	u32	t_hot; +	u32	t_cold; +	u32	min_freq; +	u32	max_freq; +	int     max_temp; +	int     min_temp; +	int     hyst_val; +	u32     update_int1; /* not used */ +	u32     update_int2; /* not used */ +}; + +struct ti_bandgap_data; + +/** + * struct temp_sensor_regval - temperature sensor register values and priv data + * @bg_mode_ctrl: temp sensor control register value + * @bg_ctrl: bandgap ctrl register value + * @bg_counter: bandgap counter value + * @bg_threshold: bandgap threshold register value + * @tshut_threshold: bandgap tshut register value + * @data: private data + * + * Data structure to save and restore bandgap register set context. Only + * required registers are shadowed, when needed. + */ +struct temp_sensor_regval { +	u32			bg_mode_ctrl; +	u32			bg_ctrl; +	u32			bg_counter; +	u32			bg_threshold; +	u32			tshut_threshold; +	void			*data; +}; + +/** + * struct ti_bandgap - bandgap device structure + * @dev: struct device pointer + * @base: io memory base address + * @conf: struct with bandgap configuration set (# sensors, conv_table, etc) + * @regval: temperature sensor register values + * @fclock: pointer to functional clock of temperature sensor + * @div_clk: pointer to divider clock of temperature sensor fclk + * @lock: spinlock for ti_bandgap structure + * @irq: MPU IRQ number for thermal alert + * @tshut_gpio: GPIO where Tshut signal is routed + * @clk_rate: Holds current clock rate + * + * The bandgap device structure representing the bandgap device instance. + * It holds most of the dynamic stuff. Configurations and sensor specific + * entries are inside the @conf structure. + */ +struct ti_bandgap { +	struct device			*dev; +	void __iomem			*base; +	const struct ti_bandgap_data	*conf; +	struct temp_sensor_regval	*regval; +	struct clk			*fclock; +	struct clk			*div_clk; +	spinlock_t			lock; /* shields this struct */ +	int				irq; +	int				tshut_gpio; +	u32				clk_rate; +}; + +/** + * struct ti_temp_sensor - bandgap temperature sensor configuration data + * @ts_data: pointer to struct with thresholds, limits of temperature sensor + * @registers: pointer to the list of register offsets and bitfields + * @domain: the name of the domain where the sensor is located + * @slope: sensor gradient slope info for hotspot extrapolation equation + * @constant: sensor gradient const info for hotspot extrapolation equation + * @slope_pcb: sensor gradient slope info for hotspot extrapolation equation + *             with no external influence + * @constant_pcb: sensor gradient const info for hotspot extrapolation equation + *             with no external influence + * @register_cooling: function to describe how this sensor is going to be cooled + * @unregister_cooling: function to release cooling data + * + * Data structure to describe a temperature sensor handled by a bandgap device. + * It should provide configuration details on this sensor, such as how to + * access the registers affecting this sensor, shadow register buffer, how to + * assess the gradient from hotspot, how to cooldown the domain when sensor + * reports too hot temperature. + */ +struct ti_temp_sensor { +	struct temp_sensor_data		*ts_data; +	struct temp_sensor_registers	*registers; +	char				*domain; +	/* for hotspot extrapolation */ +	const int			slope; +	const int			constant; +	const int			slope_pcb; +	const int			constant_pcb; +	int (*register_cooling)(struct ti_bandgap *bgp, int id); +	int (*unregister_cooling)(struct ti_bandgap *bgp, int id); +}; + +/** + * DOC: ti bandgap feature types + * + * TI_BANDGAP_FEATURE_TSHUT - used when the thermal shutdown signal output + *      of a bandgap device instance is routed to the processor. This means + *      the system must react and perform the shutdown by itself (handle an + *      IRQ, for instance). + * + * TI_BANDGAP_FEATURE_TSHUT_CONFIG - used when the bandgap device has control + *      over the thermal shutdown configuration. This means that the thermal + *      shutdown thresholds are programmable, for instance. + * + * TI_BANDGAP_FEATURE_TALERT - used when the bandgap device instance outputs + *      a signal representing violation of programmable alert thresholds. + * + * TI_BANDGAP_FEATURE_MODE_CONFIG - used when it is possible to choose which + *      mode, continuous or one shot, the bandgap device instance will operate. + * + * TI_BANDGAP_FEATURE_COUNTER - used when the bandgap device instance allows + *      programming the update interval of its internal state machine. + * + * TI_BANDGAP_FEATURE_POWER_SWITCH - used when the bandgap device allows + *      itself to be switched on/off. + * + * TI_BANDGAP_FEATURE_CLK_CTRL - used when the clocks feeding the bandgap + *      device are gateable or not. + * + * TI_BANDGAP_FEATURE_FREEZE_BIT - used when the bandgap device features + *      a history buffer that its update can be freezed/unfreezed. + * + * TI_BANDGAP_FEATURE_COUNTER_DELAY - used when the bandgap device features + *	a delay programming based on distinct values. + * + * TI_BANDGAP_FEATURE_HISTORY_BUFFER - used when the bandgap device features + *	a history buffer of temperatures. + * + * TI_BANDGAP_HAS(b, f) - macro to check if a bandgap device is capable of a + *      specific feature (above) or not. Return non-zero, if yes. + */ +#define TI_BANDGAP_FEATURE_TSHUT		BIT(0) +#define TI_BANDGAP_FEATURE_TSHUT_CONFIG		BIT(1) +#define TI_BANDGAP_FEATURE_TALERT		BIT(2) +#define TI_BANDGAP_FEATURE_MODE_CONFIG		BIT(3) +#define TI_BANDGAP_FEATURE_COUNTER		BIT(4) +#define TI_BANDGAP_FEATURE_POWER_SWITCH		BIT(5) +#define TI_BANDGAP_FEATURE_CLK_CTRL		BIT(6) +#define TI_BANDGAP_FEATURE_FREEZE_BIT		BIT(7) +#define TI_BANDGAP_FEATURE_COUNTER_DELAY	BIT(8) +#define TI_BANDGAP_FEATURE_HISTORY_BUFFER	BIT(9) +#define TI_BANDGAP_HAS(b, f)			\ +			((b)->conf->features & TI_BANDGAP_FEATURE_ ## f) + +/** + * struct ti_bandgap_data - ti bandgap data configuration structure + * @features: a bitwise flag set to describe the device features + * @conv_table: Pointer to ADC to temperature conversion table + * @adc_start_val: ADC conversion table starting value + * @adc_end_val: ADC conversion table ending value + * @fclock_name: clock name of the functional clock + * @div_ck_name: clock name of the clock divisor + * @sensor_count: count of temperature sensor within this bandgap device + * @report_temperature: callback to report thermal alert to thermal API + * @expose_sensor: callback to export sensor to thermal API + * @remove_sensor: callback to destroy sensor from thermal API + * @sensors: array of sensors present in this bandgap instance + * + * This is a data structure which should hold most of the static configuration + * of a bandgap device instance. It should describe which features this instance + * is capable of, the clock names to feed this device, the amount of sensors and + * their configuration representation, and how to export and unexport them to + * a thermal API. + */ +struct ti_bandgap_data { +	unsigned int			features; +	const int			*conv_table; +	u32				adc_start_val; +	u32				adc_end_val; +	char				*fclock_name; +	char				*div_ck_name; +	int				sensor_count; +	int (*report_temperature)(struct ti_bandgap *bgp, int id); +	int (*expose_sensor)(struct ti_bandgap *bgp, int id, char *domain); +	int (*remove_sensor)(struct ti_bandgap *bgp, int id); + +	/* this needs to be at the end */ +	struct ti_temp_sensor		sensors[]; +}; + +int ti_bandgap_read_thot(struct ti_bandgap *bgp, int id, int *thot); +int ti_bandgap_write_thot(struct ti_bandgap *bgp, int id, int val); +int ti_bandgap_read_tcold(struct ti_bandgap *bgp, int id, int *tcold); +int ti_bandgap_write_tcold(struct ti_bandgap *bgp, int id, int val); +int ti_bandgap_read_update_interval(struct ti_bandgap *bgp, int id, +				    int *interval); +int ti_bandgap_write_update_interval(struct ti_bandgap *bgp, int id, +				     u32 interval); +int ti_bandgap_read_temperature(struct ti_bandgap *bgp, int id, +				  int *temperature); +int ti_bandgap_set_sensor_data(struct ti_bandgap *bgp, int id, void *data); +void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id); +int ti_bandgap_get_trend(struct ti_bandgap *bgp, int id, int *trend); + +#ifdef CONFIG_OMAP4_THERMAL +extern const struct ti_bandgap_data omap4430_data; +extern const struct ti_bandgap_data omap4460_data; +extern const struct ti_bandgap_data omap4470_data; +#else +#define omap4430_data					NULL +#define omap4460_data					NULL +#define omap4470_data					NULL +#endif + +#ifdef CONFIG_OMAP5_THERMAL +extern const struct ti_bandgap_data omap5430_data; +#else +#define omap5430_data					NULL +#endif + +#ifdef CONFIG_DRA752_THERMAL +extern const struct ti_bandgap_data dra752_data; +#else +#define dra752_data					NULL +#endif +#endif diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c new file mode 100644 index 00000000000..9eec26dc044 --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -0,0 +1,433 @@ +/* + * OMAP thermal driver interface + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * Contact: + *   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 + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/gfp.h> +#include <linux/kernel.h> +#include <linux/workqueue.h> +#include <linux/thermal.h> +#include <linux/cpufreq.h> +#include <linux/cpumask.h> +#include <linux/cpu_cooling.h> +#include <linux/of.h> + +#include "ti-thermal.h" +#include "ti-bandgap.h" + +/* common data structures */ +struct ti_thermal_data { +	struct thermal_zone_device *ti_thermal; +	struct thermal_zone_device *pcb_tz; +	struct thermal_cooling_device *cool_dev; +	struct ti_bandgap *bgp; +	enum thermal_device_mode mode; +	struct work_struct thermal_wq; +	int sensor_id; +	bool our_zone; +}; + +static void ti_thermal_work(struct work_struct *work) +{ +	struct ti_thermal_data *data = container_of(work, +					struct ti_thermal_data, thermal_wq); + +	thermal_zone_device_update(data->ti_thermal); + +	dev_dbg(&data->ti_thermal->device, "updated thermal zone %s\n", +		data->ti_thermal->type); +} + +/** + * ti_thermal_hotspot_temperature - returns sensor extrapolated temperature + * @t:	omap sensor temperature + * @s:	omap sensor slope value + * @c:	omap sensor const value + */ +static inline int ti_thermal_hotspot_temperature(int t, int s, int c) +{ +	int delta = t * s / 1000 + c; + +	if (delta < 0) +		delta = 0; + +	return t + delta; +} + +/* thermal zone ops */ +/* Get temperature callback function for thermal zone*/ +static inline int __ti_thermal_get_temp(void *devdata, long *temp) +{ +	struct thermal_zone_device *pcb_tz = NULL; +	struct ti_thermal_data *data = devdata; +	struct ti_bandgap *bgp; +	const struct ti_temp_sensor *s; +	int ret, tmp, slope, constant; +	unsigned long pcb_temp; + +	if (!data) +		return 0; + +	bgp = data->bgp; +	s = &bgp->conf->sensors[data->sensor_id]; + +	ret = ti_bandgap_read_temperature(bgp, data->sensor_id, &tmp); +	if (ret) +		return ret; + +	/* Default constants */ +	slope = s->slope; +	constant = s->constant; + +	pcb_tz = data->pcb_tz; +	/* In case pcb zone is available, use the extrapolation rule with it */ +	if (!IS_ERR(pcb_tz)) { +		ret = thermal_zone_get_temp(pcb_tz, &pcb_temp); +		if (!ret) { +			tmp -= pcb_temp; /* got a valid PCB temp */ +			slope = s->slope_pcb; +			constant = s->constant_pcb; +		} else { +			dev_err(bgp->dev, +				"Failed to read PCB state. Using defaults\n"); +			ret = 0; +		} +	} +	*temp = ti_thermal_hotspot_temperature(tmp, slope, constant); + +	return ret; +} + +static inline int ti_thermal_get_temp(struct thermal_zone_device *thermal, +				      unsigned long *temp) +{ +	struct ti_thermal_data *data = thermal->devdata; + +	return __ti_thermal_get_temp(data, temp); +} + +/* Bind callback functions for thermal zone */ +static int ti_thermal_bind(struct thermal_zone_device *thermal, +			   struct thermal_cooling_device *cdev) +{ +	struct ti_thermal_data *data = thermal->devdata; +	int id; + +	if (!data || IS_ERR(data)) +		return -ENODEV; + +	/* check if this is the cooling device we registered */ +	if (data->cool_dev != cdev) +		return 0; + +	id = data->sensor_id; + +	/* Simple thing, two trips, one passive another critical */ +	return thermal_zone_bind_cooling_device(thermal, 0, cdev, +	/* bind with min and max states defined by cpu_cooling */ +						THERMAL_NO_LIMIT, +						THERMAL_NO_LIMIT); +} + +/* Unbind callback functions for thermal zone */ +static int ti_thermal_unbind(struct thermal_zone_device *thermal, +			     struct thermal_cooling_device *cdev) +{ +	struct ti_thermal_data *data = thermal->devdata; + +	if (!data || IS_ERR(data)) +		return -ENODEV; + +	/* check if this is the cooling device we registered */ +	if (data->cool_dev != cdev) +		return 0; + +	/* Simple thing, two trips, one passive another critical */ +	return thermal_zone_unbind_cooling_device(thermal, 0, cdev); +} + +/* Get mode callback functions for thermal zone */ +static int ti_thermal_get_mode(struct thermal_zone_device *thermal, +			       enum thermal_device_mode *mode) +{ +	struct ti_thermal_data *data = thermal->devdata; + +	if (data) +		*mode = data->mode; + +	return 0; +} + +/* Set mode callback functions for thermal zone */ +static int ti_thermal_set_mode(struct thermal_zone_device *thermal, +			       enum thermal_device_mode mode) +{ +	struct ti_thermal_data *data = thermal->devdata; +	struct ti_bandgap *bgp; + +	bgp = data->bgp; + +	if (!data->ti_thermal) { +		dev_notice(&thermal->device, "thermal zone not registered\n"); +		return 0; +	} + +	mutex_lock(&data->ti_thermal->lock); + +	if (mode == THERMAL_DEVICE_ENABLED) +		data->ti_thermal->polling_delay = FAST_TEMP_MONITORING_RATE; +	else +		data->ti_thermal->polling_delay = 0; + +	mutex_unlock(&data->ti_thermal->lock); + +	data->mode = mode; +	ti_bandgap_write_update_interval(bgp, data->sensor_id, +					data->ti_thermal->polling_delay); +	thermal_zone_device_update(data->ti_thermal); +	dev_dbg(&thermal->device, "thermal polling set for duration=%d msec\n", +		data->ti_thermal->polling_delay); + +	return 0; +} + +/* Get trip type callback functions for thermal zone */ +static int ti_thermal_get_trip_type(struct thermal_zone_device *thermal, +				    int trip, enum thermal_trip_type *type) +{ +	if (!ti_thermal_is_valid_trip(trip)) +		return -EINVAL; + +	if (trip + 1 == OMAP_TRIP_NUMBER) +		*type = THERMAL_TRIP_CRITICAL; +	else +		*type = THERMAL_TRIP_PASSIVE; + +	return 0; +} + +/* Get trip temperature callback functions for thermal zone */ +static int ti_thermal_get_trip_temp(struct thermal_zone_device *thermal, +				    int trip, unsigned long *temp) +{ +	if (!ti_thermal_is_valid_trip(trip)) +		return -EINVAL; + +	*temp = ti_thermal_get_trip_value(trip); + +	return 0; +} + +static int __ti_thermal_get_trend(void *p, long *trend) +{ +	struct ti_thermal_data *data = p; +	struct ti_bandgap *bgp; +	int id, tr, ret = 0; + +	bgp = data->bgp; +	id = data->sensor_id; + +	ret = ti_bandgap_get_trend(bgp, id, &tr); +	if (ret) +		return ret; + +	*trend = tr; + +	return 0; +} + +/* Get the temperature trend callback functions for thermal zone */ +static int ti_thermal_get_trend(struct thermal_zone_device *thermal, +				int trip, enum thermal_trend *trend) +{ +	int ret; +	long tr; + +	ret = __ti_thermal_get_trend(thermal->devdata, &tr); +	if (ret) +		return ret; + +	if (tr > 0) +		*trend = THERMAL_TREND_RAISING; +	else if (tr < 0) +		*trend = THERMAL_TREND_DROPPING; +	else +		*trend = THERMAL_TREND_STABLE; + +	return 0; +} + +/* Get critical temperature callback functions for thermal zone */ +static int ti_thermal_get_crit_temp(struct thermal_zone_device *thermal, +				    unsigned long *temp) +{ +	/* shutdown zone */ +	return ti_thermal_get_trip_temp(thermal, OMAP_TRIP_NUMBER - 1, temp); +} + +static struct thermal_zone_device_ops ti_thermal_ops = { +	.get_temp = ti_thermal_get_temp, +	.get_trend = ti_thermal_get_trend, +	.bind = ti_thermal_bind, +	.unbind = ti_thermal_unbind, +	.get_mode = ti_thermal_get_mode, +	.set_mode = ti_thermal_set_mode, +	.get_trip_type = ti_thermal_get_trip_type, +	.get_trip_temp = ti_thermal_get_trip_temp, +	.get_crit_temp = ti_thermal_get_crit_temp, +}; + +static struct ti_thermal_data +*ti_thermal_build_data(struct ti_bandgap *bgp, int id) +{ +	struct ti_thermal_data *data; + +	data = devm_kzalloc(bgp->dev, sizeof(*data), GFP_KERNEL); +	if (!data) { +		dev_err(bgp->dev, "kzalloc fail\n"); +		return NULL; +	} +	data->sensor_id = id; +	data->bgp = bgp; +	data->mode = THERMAL_DEVICE_ENABLED; +	/* pcb_tz will be either valid or PTR_ERR() */ +	data->pcb_tz = thermal_zone_get_zone_by_name("pcb"); +	INIT_WORK(&data->thermal_wq, ti_thermal_work); + +	return data; +} + +int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, +			     char *domain) +{ +	struct ti_thermal_data *data; + +	data = ti_bandgap_get_sensor_data(bgp, id); + +	if (!data || IS_ERR(data)) +		data = ti_thermal_build_data(bgp, id); + +	if (!data) +		return -EINVAL; + +	/* in case this is specified by DT */ +	data->ti_thermal = thermal_zone_of_sensor_register(bgp->dev, id, +					data, __ti_thermal_get_temp, +					__ti_thermal_get_trend); +	if (IS_ERR(data->ti_thermal)) { +		/* Create thermal zone */ +		data->ti_thermal = thermal_zone_device_register(domain, +				OMAP_TRIP_NUMBER, 0, data, &ti_thermal_ops, +				NULL, FAST_TEMP_MONITORING_RATE, +				FAST_TEMP_MONITORING_RATE); +		if (IS_ERR(data->ti_thermal)) { +			dev_err(bgp->dev, "thermal zone device is NULL\n"); +			return PTR_ERR(data->ti_thermal); +		} +		data->ti_thermal->polling_delay = FAST_TEMP_MONITORING_RATE; +		data->our_zone = true; +	} +	ti_bandgap_set_sensor_data(bgp, id, data); +	ti_bandgap_write_update_interval(bgp, data->sensor_id, +					data->ti_thermal->polling_delay); + +	return 0; +} + +int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id) +{ +	struct ti_thermal_data *data; + +	data = ti_bandgap_get_sensor_data(bgp, id); + +	if (data && data->ti_thermal) { +		if (data->our_zone) +			thermal_zone_device_unregister(data->ti_thermal); +		else +			thermal_zone_of_sensor_unregister(bgp->dev, +							  data->ti_thermal); +	} + +	return 0; +} + +int ti_thermal_report_sensor_temperature(struct ti_bandgap *bgp, int id) +{ +	struct ti_thermal_data *data; + +	data = ti_bandgap_get_sensor_data(bgp, id); + +	schedule_work(&data->thermal_wq); + +	return 0; +} + +int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id) +{ +	struct ti_thermal_data *data; +	struct device_node *np = bgp->dev->of_node; + +	/* +	 * We are assuming here that if one deploys the zone +	 * using DT, then it must be aware that the cooling device +	 * loading has to happen via cpufreq driver. +	 */ +	if (of_find_property(np, "#thermal-sensor-cells", NULL)) +		return 0; + +	data = ti_bandgap_get_sensor_data(bgp, id); +	if (!data || IS_ERR(data)) +		data = ti_thermal_build_data(bgp, id); + +	if (!data) +		return -EINVAL; + +	if (!cpufreq_get_current_driver()) { +		dev_dbg(bgp->dev, "no cpufreq driver yet\n"); +		return -EPROBE_DEFER; +	} + +	/* Register cooling device */ +	data->cool_dev = cpufreq_cooling_register(cpu_present_mask); +	if (IS_ERR(data->cool_dev)) { +		dev_err(bgp->dev, +			"Failed to register cpufreq cooling device\n"); +		return PTR_ERR(data->cool_dev); +	} +	ti_bandgap_set_sensor_data(bgp, id, data); + +	return 0; +} + +int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id) +{ +	struct ti_thermal_data *data; + +	data = ti_bandgap_get_sensor_data(bgp, id); + +	if (data && data->cool_dev) +		cpufreq_cooling_unregister(data->cool_dev); + +	return 0; +} diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal.h b/drivers/thermal/ti-soc-thermal/ti-thermal.h new file mode 100644 index 00000000000..f8b7ffea619 --- /dev/null +++ b/drivers/thermal/ti-soc-thermal/ti-thermal.h @@ -0,0 +1,123 @@ +/* + * OMAP thermal definitions + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * Contact: + *   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 + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef __TI_THERMAL_H +#define __TI_THERMAL_H + +#include "ti-bandgap.h" + +/* sensors gradient and offsets */ +#define OMAP_GRADIENT_SLOPE_4430				0 +#define OMAP_GRADIENT_CONST_4430				20000 +#define OMAP_GRADIENT_SLOPE_4460				348 +#define OMAP_GRADIENT_CONST_4460				-9301 +#define OMAP_GRADIENT_SLOPE_4470				308 +#define OMAP_GRADIENT_CONST_4470				-7896 + +#define OMAP_GRADIENT_SLOPE_5430_CPU				65 +#define OMAP_GRADIENT_CONST_5430_CPU				-1791 +#define OMAP_GRADIENT_SLOPE_5430_GPU				117 +#define OMAP_GRADIENT_CONST_5430_GPU				-2992 + +#define DRA752_GRADIENT_SLOPE					0 +#define DRA752_GRADIENT_CONST					2000 + +/* PCB sensor calculation constants */ +#define OMAP_GRADIENT_SLOPE_W_PCB_4430				0 +#define OMAP_GRADIENT_CONST_W_PCB_4430				20000 +#define OMAP_GRADIENT_SLOPE_W_PCB_4460				1142 +#define OMAP_GRADIENT_CONST_W_PCB_4460				-393 +#define OMAP_GRADIENT_SLOPE_W_PCB_4470				1063 +#define OMAP_GRADIENT_CONST_W_PCB_4470				-477 + +#define OMAP_GRADIENT_SLOPE_W_PCB_5430_CPU			100 +#define OMAP_GRADIENT_CONST_W_PCB_5430_CPU			484 +#define OMAP_GRADIENT_SLOPE_W_PCB_5430_GPU			464 +#define OMAP_GRADIENT_CONST_W_PCB_5430_GPU			-5102 + +#define DRA752_GRADIENT_SLOPE_W_PCB				0 +#define DRA752_GRADIENT_CONST_W_PCB				2000 + +/* trip points of interest in milicelsius (at hotspot level) */ +#define OMAP_TRIP_COLD						100000 +#define OMAP_TRIP_HOT						110000 +#define OMAP_TRIP_SHUTDOWN					125000 +#define OMAP_TRIP_NUMBER					2 +#define OMAP_TRIP_STEP							\ +	((OMAP_TRIP_SHUTDOWN - OMAP_TRIP_HOT) / (OMAP_TRIP_NUMBER - 1)) + +/* Update rates */ +#define FAST_TEMP_MONITORING_RATE				250 + +/* helper macros */ +/** + * ti_thermal_get_trip_value - returns trip temperature based on index + * @i:	trip index + */ +#define ti_thermal_get_trip_value(i)					\ +	(OMAP_TRIP_HOT + ((i) * OMAP_TRIP_STEP)) + +/** + * ti_thermal_is_valid_trip - check for trip index + * @i:	trip index + */ +#define ti_thermal_is_valid_trip(trip)				\ +	((trip) >= 0 && (trip) < OMAP_TRIP_NUMBER) + +#ifdef CONFIG_TI_THERMAL +int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, char *domain); +int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id); +int ti_thermal_report_sensor_temperature(struct ti_bandgap *bgp, int id); +int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id); +int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id); +#else +static inline +int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, char *domain) +{ +	return 0; +} + +static inline +int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id) +{ +	return 0; +} + +static inline +int ti_thermal_report_sensor_temperature(struct ti_bandgap *bgp, int id) +{ +	return 0; +} + +static inline +int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id) +{ +	return 0; +} + +static inline +int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id) +{ +	return 0; +} +#endif +#endif diff --git a/drivers/thermal/user_space.c b/drivers/thermal/user_space.c new file mode 100644 index 00000000000..10adcddc882 --- /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); +} + diff --git a/drivers/thermal/x86_pkg_temp_thermal.c b/drivers/thermal/x86_pkg_temp_thermal.c new file mode 100644 index 00000000000..9ea3d9d49ff --- /dev/null +++ b/drivers/thermal/x86_pkg_temp_thermal.c @@ -0,0 +1,651 @@ +/* + * x86_pkg_temp_thermal driver + * Copyright (c) 2013, Intel Corporation. + * + * 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. + * + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/param.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/cpu.h> +#include <linux/smp.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/thermal.h> +#include <linux/debugfs.h> +#include <asm/cpu_device_id.h> +#include <asm/mce.h> + +/* +* Rate control delay: Idea is to introduce denounce effect +* This should be long enough to avoid reduce events, when +* threshold is set to a temperature, which is constantly +* violated, but at the short enough to take any action. +* The action can be remove threshold or change it to next +* interesting setting. Based on experiments, in around +* every 5 seconds under load will give us a significant +* temperature change. +*/ +#define PKG_TEMP_THERMAL_NOTIFY_DELAY	5000 +static int notify_delay_ms = PKG_TEMP_THERMAL_NOTIFY_DELAY; +module_param(notify_delay_ms, int, 0644); +MODULE_PARM_DESC(notify_delay_ms, +	"User space notification delay in milli seconds."); + +/* Number of trip points in thermal zone. Currently it can't +* be more than 2. MSR can allow setting and getting notifications +* for only 2 thresholds. This define enforces this, if there +* is some wrong values returned by cpuid for number of thresholds. +*/ +#define MAX_NUMBER_OF_TRIPS	2 +/* Limit number of package temp zones */ +#define MAX_PKG_TEMP_ZONE_IDS	256 + +struct phy_dev_entry { +	struct list_head list; +	u16 phys_proc_id; +	u16 first_cpu; +	u32 tj_max; +	int ref_cnt; +	u32 start_pkg_therm_low; +	u32 start_pkg_therm_high; +	struct thermal_zone_device *tzone; +}; + +static const struct thermal_zone_params pkg_temp_tz_params = { +	.no_hwmon	= true, +}; + +/* List maintaining number of package instances */ +static LIST_HEAD(phy_dev_list); +static DEFINE_MUTEX(phy_dev_list_mutex); + +/* Interrupt to work function schedule queue */ +static DEFINE_PER_CPU(struct delayed_work, pkg_temp_thermal_threshold_work); + +/* To track if the work is already scheduled on a package */ +static u8 *pkg_work_scheduled; + +/* Spin lock to prevent races with pkg_work_scheduled */ +static spinlock_t pkg_work_lock; +static u16 max_phy_id; + +/* Debug counters to show using debugfs */ +static struct dentry *debugfs; +static unsigned int pkg_interrupt_cnt; +static unsigned int pkg_work_cnt; + +static int pkg_temp_debugfs_init(void) +{ +	struct dentry *d; + +	debugfs = debugfs_create_dir("pkg_temp_thermal", NULL); +	if (!debugfs) +		return -ENOENT; + +	d = debugfs_create_u32("pkg_thres_interrupt", S_IRUGO, debugfs, +				(u32 *)&pkg_interrupt_cnt); +	if (!d) +		goto err_out; + +	d = debugfs_create_u32("pkg_thres_work", S_IRUGO, debugfs, +				(u32 *)&pkg_work_cnt); +	if (!d) +		goto err_out; + +	return 0; + +err_out: +	debugfs_remove_recursive(debugfs); +	return -ENOENT; +} + +static struct phy_dev_entry +			*pkg_temp_thermal_get_phy_entry(unsigned int cpu) +{ +	u16 phys_proc_id = topology_physical_package_id(cpu); +	struct phy_dev_entry *phy_ptr; + +	mutex_lock(&phy_dev_list_mutex); + +	list_for_each_entry(phy_ptr, &phy_dev_list, list) +		if (phy_ptr->phys_proc_id == phys_proc_id) { +			mutex_unlock(&phy_dev_list_mutex); +			return phy_ptr; +		} + +	mutex_unlock(&phy_dev_list_mutex); + +	return NULL; +} + +/* +* tj-max is is interesting because threshold is set relative to this +* temperature. +*/ +static int get_tj_max(int cpu, u32 *tj_max) +{ +	u32 eax, edx; +	u32 val; +	int err; + +	err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); +	if (err) +		goto err_ret; +	else { +		val = (eax >> 16) & 0xff; +		if (val) +			*tj_max = val * 1000; +		else { +			err = -EINVAL; +			goto err_ret; +		} +	} + +	return 0; +err_ret: +	*tj_max = 0; +	return err; +} + +static int sys_get_curr_temp(struct thermal_zone_device *tzd, unsigned long *temp) +{ +	u32 eax, edx; +	struct phy_dev_entry *phy_dev_entry; + +	phy_dev_entry = tzd->devdata; +	rdmsr_on_cpu(phy_dev_entry->first_cpu, MSR_IA32_PACKAGE_THERM_STATUS, +			&eax, &edx); +	if (eax & 0x80000000) { +		*temp = phy_dev_entry->tj_max - +				((eax >> 16) & 0x7f) * 1000; +		pr_debug("sys_get_curr_temp %ld\n", *temp); +		return 0; +	} + +	return -EINVAL; +} + +static int sys_get_trip_temp(struct thermal_zone_device *tzd, +		int trip, unsigned long *temp) +{ +	u32 eax, edx; +	struct phy_dev_entry *phy_dev_entry; +	u32 mask, shift; +	unsigned long thres_reg_value; +	int ret; + +	if (trip >= MAX_NUMBER_OF_TRIPS) +		return -EINVAL; + +	phy_dev_entry = tzd->devdata; + +	if (trip) { +		mask = THERM_MASK_THRESHOLD1; +		shift = THERM_SHIFT_THRESHOLD1; +	} else { +		mask = THERM_MASK_THRESHOLD0; +		shift = THERM_SHIFT_THRESHOLD0; +	} + +	ret = rdmsr_on_cpu(phy_dev_entry->first_cpu, +				MSR_IA32_PACKAGE_THERM_INTERRUPT, &eax, &edx); +	if (ret < 0) +		return -EINVAL; + +	thres_reg_value = (eax & mask) >> shift; +	if (thres_reg_value) +		*temp = phy_dev_entry->tj_max - thres_reg_value * 1000; +	else +		*temp = 0; +	pr_debug("sys_get_trip_temp %ld\n", *temp); + +	return 0; +} + +static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, +							unsigned long temp) +{ +	u32 l, h; +	struct phy_dev_entry *phy_dev_entry; +	u32 mask, shift, intr; +	int ret; + +	phy_dev_entry = tzd->devdata; + +	if (trip >= MAX_NUMBER_OF_TRIPS || temp >= phy_dev_entry->tj_max) +		return -EINVAL; + +	ret = rdmsr_on_cpu(phy_dev_entry->first_cpu, +					MSR_IA32_PACKAGE_THERM_INTERRUPT, +					&l, &h); +	if (ret < 0) +		return -EINVAL; + +	if (trip) { +		mask = THERM_MASK_THRESHOLD1; +		shift = THERM_SHIFT_THRESHOLD1; +		intr = THERM_INT_THRESHOLD1_ENABLE; +	} else { +		mask = THERM_MASK_THRESHOLD0; +		shift = THERM_SHIFT_THRESHOLD0; +		intr = THERM_INT_THRESHOLD0_ENABLE; +	} +	l &= ~mask; +	/* +	* When users space sets a trip temperature == 0, which is indication +	* that, it is no longer interested in receiving notifications. +	*/ +	if (!temp) +		l &= ~intr; +	else { +		l |= (phy_dev_entry->tj_max - temp)/1000 << shift; +		l |= intr; +	} + +	return wrmsr_on_cpu(phy_dev_entry->first_cpu, +					MSR_IA32_PACKAGE_THERM_INTERRUPT, +					l, h); +} + +static int sys_get_trip_type(struct thermal_zone_device *thermal, +		int trip, enum thermal_trip_type *type) +{ + +	*type = THERMAL_TRIP_PASSIVE; + +	return 0; +} + +/* Thermal zone callback registry */ +static struct thermal_zone_device_ops tzone_ops = { +	.get_temp = sys_get_curr_temp, +	.get_trip_temp = sys_get_trip_temp, +	.get_trip_type = sys_get_trip_type, +	.set_trip_temp = sys_set_trip_temp, +}; + +static bool pkg_temp_thermal_platform_thermal_rate_control(void) +{ +	return true; +} + +/* Enable threshold interrupt on local package/cpu */ +static inline void enable_pkg_thres_interrupt(void) +{ +	u32 l, h; +	u8 thres_0, thres_1; + +	rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); +	/* only enable/disable if it had valid threshold value */ +	thres_0 = (l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0; +	thres_1 = (l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1; +	if (thres_0) +		l |= THERM_INT_THRESHOLD0_ENABLE; +	if (thres_1) +		l |= THERM_INT_THRESHOLD1_ENABLE; +	wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); +} + +/* Disable threshold interrupt on local package/cpu */ +static inline void disable_pkg_thres_interrupt(void) +{ +	u32 l, h; +	rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); +	wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, +			l & (~THERM_INT_THRESHOLD0_ENABLE) & +				(~THERM_INT_THRESHOLD1_ENABLE), h); +} + +static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work) +{ +	__u64 msr_val; +	int cpu = smp_processor_id(); +	int phy_id = topology_physical_package_id(cpu); +	struct phy_dev_entry *phdev = pkg_temp_thermal_get_phy_entry(cpu); +	bool notify = false; +	unsigned long flags; + +	if (!phdev) +		return; + +	spin_lock_irqsave(&pkg_work_lock, flags); +	++pkg_work_cnt; +	if (unlikely(phy_id > max_phy_id)) { +		spin_unlock_irqrestore(&pkg_work_lock, flags); +		return; +	} +	pkg_work_scheduled[phy_id] = 0; +	spin_unlock_irqrestore(&pkg_work_lock, flags); + +	enable_pkg_thres_interrupt(); +	rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val); +	if (msr_val & THERM_LOG_THRESHOLD0) { +		wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, +				msr_val & ~THERM_LOG_THRESHOLD0); +		notify = true; +	} +	if (msr_val & THERM_LOG_THRESHOLD1) { +		wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, +				msr_val & ~THERM_LOG_THRESHOLD1); +		notify = true; +	} +	if (notify) { +		pr_debug("thermal_zone_device_update\n"); +		thermal_zone_device_update(phdev->tzone); +	} +} + +static int pkg_temp_thermal_platform_thermal_notify(__u64 msr_val) +{ +	unsigned long flags; +	int cpu = smp_processor_id(); +	int phy_id = topology_physical_package_id(cpu); + +	/* +	* When a package is in interrupted state, all CPU's in that package +	* are in the same interrupt state. So scheduling on any one CPU in +	* the package is enough and simply return for others. +	*/ +	spin_lock_irqsave(&pkg_work_lock, flags); +	++pkg_interrupt_cnt; +	if (unlikely(phy_id > max_phy_id) || unlikely(!pkg_work_scheduled) || +			pkg_work_scheduled[phy_id]) { +		disable_pkg_thres_interrupt(); +		spin_unlock_irqrestore(&pkg_work_lock, flags); +		return -EINVAL; +	} +	pkg_work_scheduled[phy_id] = 1; +	spin_unlock_irqrestore(&pkg_work_lock, flags); + +	disable_pkg_thres_interrupt(); +	schedule_delayed_work_on(cpu, +				&per_cpu(pkg_temp_thermal_threshold_work, cpu), +				msecs_to_jiffies(notify_delay_ms)); +	return 0; +} + +static int find_siblings_cpu(int cpu) +{ +	int i; +	int id = topology_physical_package_id(cpu); + +	for_each_online_cpu(i) +		if (i != cpu && topology_physical_package_id(i) == id) +			return i; + +	return 0; +} + +static int pkg_temp_thermal_device_add(unsigned int cpu) +{ +	int err; +	u32 tj_max; +	struct phy_dev_entry *phy_dev_entry; +	int thres_count; +	u32 eax, ebx, ecx, edx; +	u8 *temp; +	unsigned long flags; + +	cpuid(6, &eax, &ebx, &ecx, &edx); +	thres_count = ebx & 0x07; +	if (!thres_count) +		return -ENODEV; + +	if (topology_physical_package_id(cpu) > MAX_PKG_TEMP_ZONE_IDS) +		return -ENODEV; + +	thres_count = clamp_val(thres_count, 0, MAX_NUMBER_OF_TRIPS); + +	err = get_tj_max(cpu, &tj_max); +	if (err) +		goto err_ret; + +	mutex_lock(&phy_dev_list_mutex); + +	phy_dev_entry = kzalloc(sizeof(*phy_dev_entry), GFP_KERNEL); +	if (!phy_dev_entry) { +		err = -ENOMEM; +		goto err_ret_unlock; +	} + +	spin_lock_irqsave(&pkg_work_lock, flags); +	if (topology_physical_package_id(cpu) > max_phy_id) +		max_phy_id = topology_physical_package_id(cpu); +	temp = krealloc(pkg_work_scheduled, +			(max_phy_id+1) * sizeof(u8), GFP_ATOMIC); +	if (!temp) { +		spin_unlock_irqrestore(&pkg_work_lock, flags); +		err = -ENOMEM; +		goto err_ret_free; +	} +	pkg_work_scheduled = temp; +	pkg_work_scheduled[topology_physical_package_id(cpu)] = 0; +	spin_unlock_irqrestore(&pkg_work_lock, flags); + +	phy_dev_entry->phys_proc_id = topology_physical_package_id(cpu); +	phy_dev_entry->first_cpu = cpu; +	phy_dev_entry->tj_max = tj_max; +	phy_dev_entry->ref_cnt = 1; +	phy_dev_entry->tzone = thermal_zone_device_register("x86_pkg_temp", +			thres_count, +			(thres_count == MAX_NUMBER_OF_TRIPS) ? +				0x03 : 0x01, +			phy_dev_entry, &tzone_ops, &pkg_temp_tz_params, 0, 0); +	if (IS_ERR(phy_dev_entry->tzone)) { +		err = PTR_ERR(phy_dev_entry->tzone); +		goto err_ret_free; +	} +	/* Store MSR value for package thermal interrupt, to restore at exit */ +	rdmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, +				&phy_dev_entry->start_pkg_therm_low, +				&phy_dev_entry->start_pkg_therm_high); + +	list_add_tail(&phy_dev_entry->list, &phy_dev_list); +	pr_debug("pkg_temp_thermal_device_add :phy_id %d cpu %d\n", +			phy_dev_entry->phys_proc_id, cpu); + +	mutex_unlock(&phy_dev_list_mutex); + +	return 0; + +err_ret_free: +	kfree(phy_dev_entry); +err_ret_unlock: +	mutex_unlock(&phy_dev_list_mutex); + +err_ret: +	return err; +} + +static int pkg_temp_thermal_device_remove(unsigned int cpu) +{ +	struct phy_dev_entry *n; +	u16 phys_proc_id = topology_physical_package_id(cpu); +	struct phy_dev_entry *phdev = +			pkg_temp_thermal_get_phy_entry(cpu); + +	if (!phdev) +		return -ENODEV; + +	mutex_lock(&phy_dev_list_mutex); +	/* If we are loosing the first cpu for this package, we need change */ +	if (phdev->first_cpu == cpu) { +		phdev->first_cpu = find_siblings_cpu(cpu); +		pr_debug("thermal_device_remove: first cpu switched %d\n", +					phdev->first_cpu); +	} +	/* +	* It is possible that no siblings left as this was the last cpu +	* going offline. We don't need to worry about this assignment +	* as the phydev entry will be removed in this case and +	* thermal zone is removed. +	*/ +	--phdev->ref_cnt; +	pr_debug("thermal_device_remove: pkg: %d cpu %d ref_cnt %d\n", +					phys_proc_id, cpu, phdev->ref_cnt); +	if (!phdev->ref_cnt) +		list_for_each_entry_safe(phdev, n, &phy_dev_list, list) { +			if (phdev->phys_proc_id == phys_proc_id) { +				thermal_zone_device_unregister(phdev->tzone); +				list_del(&phdev->list); +				kfree(phdev); +				break; +			} +		} +	mutex_unlock(&phy_dev_list_mutex); + +	return 0; +} + +static int get_core_online(unsigned int cpu) +{ +	struct cpuinfo_x86 *c = &cpu_data(cpu); +	struct phy_dev_entry *phdev = pkg_temp_thermal_get_phy_entry(cpu); + +	/* Check if there is already an instance for this package */ +	if (!phdev) { +		if (!cpu_has(c, X86_FEATURE_DTHERM) || +					!cpu_has(c, X86_FEATURE_PTS)) +			return -ENODEV; +		if (pkg_temp_thermal_device_add(cpu)) +			return -ENODEV; +	} else { +		mutex_lock(&phy_dev_list_mutex); +		++phdev->ref_cnt; +		pr_debug("get_core_online: cpu %d ref_cnt %d\n", +						cpu, phdev->ref_cnt); +		mutex_unlock(&phy_dev_list_mutex); +	} +	INIT_DELAYED_WORK(&per_cpu(pkg_temp_thermal_threshold_work, cpu), +			pkg_temp_thermal_threshold_work_fn); + +	pr_debug("get_core_online: cpu %d successful\n", cpu); + +	return 0; +} + +static void put_core_offline(unsigned int cpu) +{ +	if (!pkg_temp_thermal_device_remove(cpu)) +		cancel_delayed_work_sync( +			&per_cpu(pkg_temp_thermal_threshold_work, cpu)); + +	pr_debug("put_core_offline: cpu %d\n", cpu); +} + +static int pkg_temp_thermal_cpu_callback(struct notifier_block *nfb, +				 unsigned long action, void *hcpu) +{ +	unsigned int cpu = (unsigned long) hcpu; + +	switch (action) { +	case CPU_ONLINE: +	case CPU_DOWN_FAILED: +		get_core_online(cpu); +		break; +	case CPU_DOWN_PREPARE: +		put_core_offline(cpu); +		break; +	} +	return NOTIFY_OK; +} + +static struct notifier_block pkg_temp_thermal_notifier __refdata = { +	.notifier_call = pkg_temp_thermal_cpu_callback, +}; + +static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = { +	{ X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_PTS }, +	{} +}; +MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids); + +static int __init pkg_temp_thermal_init(void) +{ +	int i; + +	if (!x86_match_cpu(pkg_temp_thermal_ids)) +		return -ENODEV; + +	spin_lock_init(&pkg_work_lock); +	platform_thermal_package_notify = +			pkg_temp_thermal_platform_thermal_notify; +	platform_thermal_package_rate_control = +			pkg_temp_thermal_platform_thermal_rate_control; + +	cpu_notifier_register_begin(); +	for_each_online_cpu(i) +		if (get_core_online(i)) +			goto err_ret; +	__register_hotcpu_notifier(&pkg_temp_thermal_notifier); +	cpu_notifier_register_done(); + +	pkg_temp_debugfs_init(); /* Don't care if fails */ + +	return 0; + +err_ret: +	for_each_online_cpu(i) +		put_core_offline(i); +	cpu_notifier_register_done(); +	kfree(pkg_work_scheduled); +	platform_thermal_package_notify = NULL; +	platform_thermal_package_rate_control = NULL; + +	return -ENODEV; +} + +static void __exit pkg_temp_thermal_exit(void) +{ +	struct phy_dev_entry *phdev, *n; +	int i; + +	cpu_notifier_register_begin(); +	__unregister_hotcpu_notifier(&pkg_temp_thermal_notifier); +	mutex_lock(&phy_dev_list_mutex); +	list_for_each_entry_safe(phdev, n, &phy_dev_list, list) { +		/* Retore old MSR value for package thermal interrupt */ +		wrmsr_on_cpu(phdev->first_cpu, +			MSR_IA32_PACKAGE_THERM_INTERRUPT, +			phdev->start_pkg_therm_low, +			phdev->start_pkg_therm_high); +		thermal_zone_device_unregister(phdev->tzone); +		list_del(&phdev->list); +		kfree(phdev); +	} +	mutex_unlock(&phy_dev_list_mutex); +	platform_thermal_package_notify = NULL; +	platform_thermal_package_rate_control = NULL; +	for_each_online_cpu(i) +		cancel_delayed_work_sync( +			&per_cpu(pkg_temp_thermal_threshold_work, i)); +	cpu_notifier_register_done(); + +	kfree(pkg_work_scheduled); + +	debugfs_remove_recursive(debugfs); +} + +module_init(pkg_temp_thermal_init) +module_exit(pkg_temp_thermal_exit) + +MODULE_DESCRIPTION("X86 PKG TEMP Thermal Driver"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); +MODULE_LICENSE("GPL v2");  | 
