diff options
Diffstat (limited to 'drivers/thermal')
23 files changed, 2561 insertions, 262 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index dbfc390330a..f9a13867cb7 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -29,6 +29,19 @@ config THERMAL_HWMON  	  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 @@ -56,7 +69,7 @@ config THERMAL_DEFAULT_GOV_USER_SPACE  	select THERMAL_GOV_USER_SPACE  	help  	  Select this if you want to let the user space manage the -	  lpatform thermals. +	  platform thermals.  endchoice @@ -69,6 +82,7 @@ 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" @@ -78,7 +92,7 @@ config THERMAL_GOV_USER_SPACE  config CPU_THERMAL  	bool "generic cpu cooling support"  	depends on CPU_FREQ -	select CPU_FREQ_TABLE +	depends on THERMAL_OF  	help  	  This implements the generic cpu cooling mechanism through frequency  	  reduction. An ACPI version of this already exists @@ -117,18 +131,19 @@ config SPEAR_THERMAL  	depends on OF  	help  	  Enable this to plug the SPEAr thermal sensor driver into the Linux -	  thermal framework +	  thermal framework.  config RCAR_THERMAL  	tristate "Renesas R-Car thermal driver" -	depends on ARCH_SHMOBILE +	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 +	  thermal framework.  config KIRKWOOD_THERMAL  	tristate "Temperature sensor on Marvell Kirkwood SoCs" -	depends on ARCH_KIRKWOOD +	depends on ARCH_KIRKWOOD || MACH_KIRKWOOD  	depends on OF  	help  	  Support for the Kirkwood thermal sensor driver into the Linux thermal @@ -192,12 +207,39 @@ config X86_PKG_TEMP_THERMAL  	  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 PLAT_SAMSUNG +depends on ARCH_EXYNOS  source "drivers/thermal/samsung/Kconfig"  endmenu diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 584b36319d5..de0636a57a6 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,6 +7,7 @@ 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 @@ -28,4 +29,6 @@ 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 index 5e53212b984..9d1420acb39 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -24,10 +24,7 @@  #include <linux/of_device.h>  #include <linux/thermal.h> -#define THERMAL_VALID_OFFSET		9  #define THERMAL_VALID_MASK		0x1 -#define THERMAL_TEMP_OFFSET		10 -#define THERMAL_TEMP_MASK		0x1ff  /* Thermal Manager Control and Status Register */  #define PMU_TDC0_SW_RST_MASK		(0x1 << 1) @@ -38,24 +35,47 @@  #define PMU_TDC0_OTF_CAL_MASK		(0x1 << 30)  #define PMU_TDC0_START_CAL_MASK		(0x1 << 25) -struct armada_thermal_ops; +#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_ops *ops; +	struct armada_thermal_data *data;  }; -struct armada_thermal_ops { +struct armada_thermal_data {  	/* Initialize the sensor */ -	void (*init_sensor)(struct armada_thermal_priv *); +	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 armada_thermal_priv *priv) +static void armadaxp_init_sensor(struct platform_device *pdev, +				 struct armada_thermal_priv *priv)  {  	unsigned long reg; @@ -80,7 +100,8 @@ static void armadaxp_init_sensor(struct armada_thermal_priv *priv)  	writel(reg, priv->sensor);  } -static void armada370_init_sensor(struct armada_thermal_priv *priv) +static void armada370_init_sensor(struct platform_device *pdev, +				  struct armada_thermal_priv *priv)  {  	unsigned long reg; @@ -99,11 +120,54 @@ static void armada370_init_sensor(struct armada_thermal_priv *priv)  	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 >> THERMAL_VALID_OFFSET) & THERMAL_VALID_MASK; +	return (reg >> priv->data->is_valid_shift) & THERMAL_VALID_MASK;  }  static int armada_get_temp(struct thermal_zone_device *thermal, @@ -111,17 +175,27 @@ static int armada_get_temp(struct thermal_zone_device *thermal,  {  	struct armada_thermal_priv *priv = thermal->devdata;  	unsigned long reg; +	unsigned long m, b, div;  	/* Valid check */ -	if (priv->ops->is_valid && !priv->ops->is_valid(priv)) { +	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 >> THERMAL_TEMP_OFFSET) & THERMAL_TEMP_MASK; -	*temp = (3153000000UL - (10000000UL*reg)) / 13825; +	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;  } @@ -129,23 +203,69 @@ static struct thermal_zone_device_ops ops = {  	.get_temp = armada_get_temp,  }; -static const struct armada_thermal_ops armadaxp_ops = { +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_ops armada370_ops = { +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_ops, +		.data       = &armadaxp_data,  	},  	{  		.compatible = "marvell,armada370-thermal", -		.data       = &armada370_ops, +		.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 */ @@ -178,8 +298,8 @@ static int armada_thermal_probe(struct platform_device *pdev)  	if (IS_ERR(priv->control))  		return PTR_ERR(priv->control); -	priv->ops = (struct armada_thermal_ops *)match->data; -	priv->ops->init_sensor(priv); +	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); diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index d17902886c3..84a75f89bf7 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -144,11 +144,11 @@ static int get_property(unsigned int cpu, unsigned long input,  			unsigned int *output,  			enum cpufreq_cooling_property property)  { -	int i, j; +	int i;  	unsigned long max_level = 0, level = 0;  	unsigned int freq = CPUFREQ_ENTRY_INVALID;  	int descend = -1; -	struct cpufreq_frequency_table *table = +	struct cpufreq_frequency_table *pos, *table =  					cpufreq_frequency_get_table(cpu);  	if (!output) @@ -157,23 +157,26 @@ static int get_property(unsigned int cpu, unsigned long input,  	if (!table)  		return -EINVAL; -	for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) { -		/* ignore invalid entries */ -		if (table[i].frequency == CPUFREQ_ENTRY_INVALID) -			continue; - +	cpufreq_for_each_valid_entry(pos, table) {  		/* ignore duplicate entry */ -		if (freq == table[i].frequency) +		if (freq == pos->frequency)  			continue;  		/* get the frequency order */  		if (freq != CPUFREQ_ENTRY_INVALID && descend == -1) -			descend = !!(freq > table[i].frequency); +			descend = freq > pos->frequency; -		freq = table[i].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; @@ -181,31 +184,28 @@ static int get_property(unsigned int cpu, unsigned long input,  	}  	if (property == GET_FREQ) -		level = descend ? input : (max_level - input - 1); - -	for (i = 0, j = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) { -		/* ignore invalid entry */ -		if (table[i].frequency == CPUFREQ_ENTRY_INVALID) -			continue; +		level = descend ? input : (max_level - input); +	i = 0; +	cpufreq_for_each_valid_entry(pos, table) {  		/* ignore duplicate entry */ -		if (freq == table[i].frequency) +		if (freq == pos->frequency)  			continue;  		/* now we have a valid frequency entry */ -		freq = table[i].frequency; +		freq = pos->frequency;  		if (property == GET_LEVEL && (unsigned int)input == freq) {  			/* get level by frequency */ -			*output = descend ? j : (max_level - j - 1); +			*output = descend ? i : (max_level - i);  			return 0;  		} -		if (property == GET_FREQ && level == j) { +		if (property == GET_FREQ && level == i) {  			/* get frequency by level */  			*output = freq;  			return 0;  		} -		j++; +		i++;  	}  	return -EINVAL; @@ -417,18 +417,21 @@ static struct notifier_block thermal_cpufreq_notifier_block = {  };  /** - * cpufreq_cooling_register - function to create cpufreq cooling device. + * __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. + * 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().   */ -struct thermal_cooling_device * -cpufreq_cooling_register(const struct cpumask *clip_cpus) +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; @@ -467,12 +470,12 @@ cpufreq_cooling_register(const struct cpumask *clip_cpus)  	snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d",  		 cpufreq_dev->id); -	cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev, -						   &cpufreq_cooling_ops); -	if (!cool_dev) { +	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 ERR_PTR(-EINVAL); +		return cool_dev;  	}  	cpufreq_dev->cool_dev = cool_dev;  	cpufreq_dev->cpufreq_state = 0; @@ -488,9 +491,50 @@ cpufreq_cooling_register(const struct cpumask *clip_cpus)  	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.   * diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index 1d6c801c1eb..2c516f2eebe 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -7,6 +7,7 @@   *   */ +#include <linux/clk.h>  #include <linux/cpu_cooling.h>  #include <linux/cpufreq.h>  #include <linux/delay.h> @@ -61,18 +62,23 @@ enum imx_thermal_trip {  #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; -	int c1, c2; /* See formula in imx_get_sensor_data() */ +	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, @@ -82,7 +88,7 @@ static void imx_set_alarm_temp(struct imx_thermal_data *data,  	int alarm_value;  	data->alarm_temp = alarm_temp; -	alarm_value = (alarm_temp - data->c2) / data->c1; +	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); @@ -134,7 +140,7 @@ static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp)  	n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT;  	/* See imx_get_sensor_data() for formula derivation */ -	*temp = data->c2 + data->c1 * n_meas; +	*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) @@ -284,7 +290,7 @@ static int imx_unbind(struct thermal_zone_device *tz,  	return 0;  } -static const struct thermal_zone_device_ops imx_tz_ops = { +static struct thermal_zone_device_ops imx_tz_ops = {  	.bind = imx_bind,  	.unbind = imx_unbind,  	.get_temp = imx_get_temp, @@ -300,9 +306,10 @@ static int imx_get_sensor_data(struct platform_device *pdev)  {  	struct imx_thermal_data *data = platform_get_drvdata(pdev);  	struct regmap *map; -	int t1, t2, n1, n2; +	int t1, n1;  	int ret;  	u32 val; +	u64 temp64;  	map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,  					      "fsl,tempmon-data"); @@ -326,41 +333,45 @@ static int imx_get_sensor_data(struct platform_device *pdev)  	/*  	 * Sensor data layout:  	 *   [31:20] - sensor value @ 25C -	 *    [19:8] - sensor value of hot -	 *     [7:0] - hot temperature value +	 * Use universal formula now and only need sensor value @ 25C +	 * slope = 0.4297157 - (0.0015976 * 25C fuse)  	 */  	n1 = val >> 20; -	n2 = (val & 0xfff00) >> 8; -	t2 = val & 0xff;  	t1 = 25; /* t1 always 25C */  	/* -	 * Derived from linear interpolation, -	 * Tmeas = T2 + (Nmeas - N2) * (T1 - T2) / (N1 - N2) +	 * 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... -	 * milli_Tmeas = 1000 * T2 + 1000 * (Nmeas - N2) * (T1 - T2) / (N1 - N2) -	 * Let constant c1 = 1000 * (T1 - T2) / (N1 - N2) -	 * milli_Tmeas = (1000 * T2) + c1 * (Nmeas - N2) -	 * milli_Tmeas = (1000 * T2) + (c1 * Nmeas) - (c1 * N2) -	 * Let constant c2 = (1000 * T2) - (c1 * N2) -	 * milli_Tmeas = c2 + (c1 * Nmeas) +	 * 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  	 */ -	data->c1 = 1000 * (t1 - t2) / (n1 - n2); -	data->c2 = 1000 * t2 - data->c1 * n2; +	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 to 20 °C below the -	 * maximum die temperature. Can be changed from userspace. +	 * Set the default passive cooling trip point, +	 * can be changed from userspace.  	 */ -	data->temp_passive = 1000 * (t2 - 20); +	data->temp_passive = IMX_TEMP_PASSIVE;  	/* -	 * The maximum die temperature is t2, let's give 5 °C cushion -	 * for noise and possible temperature rise between measurements. +	 * The maximum die temperature set to 20 C higher than +	 * IMX_TEMP_PASSIVE.  	 */ -	data->temp_critical = 1000 * (t2 - 5); +	data->temp_critical = 1000 * 20 + data->temp_passive;  	return 0;  } @@ -457,6 +468,22 @@ static int imx_thermal_probe(struct platform_device *pdev)  		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 */ @@ -478,6 +505,8 @@ static int imx_thermal_remove(struct platform_device *pdev)  	/* 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); @@ -490,27 +519,30 @@ static int imx_thermal_suspend(struct device *dev)  {  	struct imx_thermal_data *data = dev_get_drvdata(dev);  	struct regmap *map = data->tempmon; -	u32 val; -	regmap_read(map, TEMPSENSE0, &val); -	if ((val & TEMPSENSE0_POWER_DOWN) == 0) { -		/* -		 * If a measurement is taking place, wait for a long enough -		 * time for it to finish, and then check again.  If it still -		 * does not finish, something must go wrong. -		 */ -		udelay(50); -		regmap_read(map, TEMPSENSE0, &val); -		if ((val & TEMPSENSE0_POWER_DOWN) == 0) -			return -ETIMEDOUT; -	} +	/* +	 * 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)  { -	/* Nothing to do for now */ +	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 @@ -522,6 +554,7 @@ 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 = { 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 index b40b37cd25e..95cb7fc20e1 100644 --- a/drivers/thermal/intel_powerclamp.c +++ b/drivers/thermal/intel_powerclamp.c @@ -206,6 +206,15 @@ static void find_target_mwait(void)  } +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; @@ -438,14 +447,12 @@ static int clamp_thread(void *arg)  			 */  			local_touch_nmi();  			stop_critical_timings(); -			__monitor((void *)¤t_thread_info()->flags, 0, 0); -			cpu_relax(); /* allow HT sibling to run */ -			__mwait(eax, ecx); +			mwait_idle_with_hints(eax, ecx);  			start_critical_timings();  			atomic_inc(&idle_wakeup_counter);  		}  		tick_nohz_idle_exit(); -		preempt_enable_no_resched(); +		preempt_enable();  	}  	del_timer_sync(&wakeup_timer);  	clear_bit(cpunr, cpu_clamping_mask); @@ -500,7 +507,7 @@ static int start_power_clamp(void)  	struct task_struct *thread;  	/* check if pkg cstate counter is completely 0, abort in this case */ -	if (!pkg_state_counter()) { +	if (!has_pkg_state_counter()) {  		pr_err("pkg cstate counter not functional, abort\n");  		return -EINVAL;  	} @@ -674,7 +681,14 @@ static const struct x86_cpu_id intel_powerclamp_ids[] = {  	{ 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); @@ -758,21 +772,39 @@ static int powerclamp_init(void)  	/* probe cpu features and ids here */  	retval = powerclamp_probe();  	if (retval) -		return 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)) -		return -ENODEV; +	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); 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/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 index 88f92e1a994..8803e693fe6 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -299,12 +299,17 @@ static void _rcar_thermal_irq_ctrl(struct rcar_thermal_priv *priv, int enable)  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); -	thermal_zone_device_update(priv->zone); + +	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) @@ -313,7 +318,7 @@ static u32 rcar_thermal_had_changed(struct rcar_thermal_priv *priv, u32 status)  	status = (status >> rcar_id_to_shift(priv)) & 0x3; -	if (status & 0x3) { +	if (status) {  		dev_dbg(dev, "thermal%d %s%s\n",  			priv->id,  			(status & 0x2) ? "Rising " : "", @@ -369,10 +374,8 @@ static int rcar_thermal_probe(struct platform_device *pdev)  	int idle = IDLE_INTERVAL;  	common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL); -	if (!common) { -		dev_err(dev, "Could not allocate common\n"); +	if (!common)  		return -ENOMEM; -	}  	INIT_LIST_HEAD(&common->head);  	spin_lock_init(&common->lock); @@ -408,7 +411,7 @@ static int rcar_thermal_probe(struct platform_device *pdev)  		/* enable temperature comparation */  		rcar_thermal_common_write(common, ENR, 0x00030303); -		idle = 0; /* polling delaye is not needed */ +		idle = 0; /* polling delay is not needed */  	}  	for (i = 0;; i++) { @@ -418,7 +421,6 @@ static int rcar_thermal_probe(struct platform_device *pdev)  		priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);  		if (!priv) { -			dev_err(dev, "Could not allocate priv\n");  			ret = -ENOMEM;  			goto error_unregister;  		} @@ -465,7 +467,7 @@ error_unregister:  			rcar_thermal_irq_disable(priv);  	} -	pm_runtime_put_sync(dev); +	pm_runtime_put(dev);  	pm_runtime_disable(dev);  	return ret; @@ -483,7 +485,7 @@ static int rcar_thermal_remove(struct platform_device *pdev)  			rcar_thermal_irq_disable(priv);  	} -	pm_runtime_put_sync(dev); +	pm_runtime_put(dev);  	pm_runtime_disable(dev);  	return 0; diff --git a/drivers/thermal/samsung/exynos_thermal_common.c b/drivers/thermal/samsung/exynos_thermal_common.c index f10a6ad37c0..3f5ad25ddca 100644 --- a/drivers/thermal/samsung/exynos_thermal_common.c +++ b/drivers/thermal/samsung/exynos_thermal_common.c @@ -280,7 +280,7 @@ static int exynos_get_trend(struct thermal_zone_device *thermal,  	return 0;  }  /* Operation callback functions for thermal zone */ -static struct thermal_zone_device_ops const exynos_dev_ops = { +static struct thermal_zone_device_ops exynos_dev_ops = {  	.bind = exynos_bind,  	.unbind = exynos_unbind,  	.get_temp = exynos_get_temp, @@ -310,8 +310,6 @@ void exynos_report_trigger(struct thermal_sensor_conf *conf)  	}  	th_zone = conf->pzone_data; -	if (th_zone->therm_dev) -		return;  	if (th_zone->bind == false) {  		for (i = 0; i < th_zone->cool_dev_size; i++) { diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index b43afda8acd..d7ca9f49c9c 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -41,12 +41,13 @@   * @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_common: base address of the common registers 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. @@ -56,12 +57,12 @@ struct exynos_tmu_data {  	int id;  	struct exynos_tmu_platform_data *pdata;  	void __iomem *base; -	void __iomem *base_common; +	void __iomem *base_second;  	int irq;  	enum soc_type soc;  	struct work_struct irq_work;  	struct mutex lock; -	struct clk *clk; +	struct clk *clk, *clk_sec;  	u8 temp_error1, temp_error2;  	struct regulator *regulator;  	struct thermal_sensor_conf *reg_conf; @@ -152,6 +153,8 @@ static int exynos_tmu_initialize(struct platform_device *pdev)  	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); @@ -186,7 +189,12 @@ static int exynos_tmu_initialize(struct platform_device *pdev)  			EXYNOS5440_EFUSE_SWAP_OFFSET + reg->triminfo_data);  		}  	} else { -		trim_info = readl(data->base + reg->triminfo_data); +		/* 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) & @@ -205,6 +213,7 @@ static int exynos_tmu_initialize(struct platform_device *pdev)  skip_calib_data:  	if (pdata->max_trigger_level > MAX_THRESHOLD_LEVS) {  		dev_err(&pdev->dev, "Invalid max trigger level\n"); +		ret = -EINVAL;  		goto out;  	} @@ -224,6 +233,8 @@ skip_calib_data:  			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); @@ -237,7 +248,7 @@ skip_calib_data:  			writeb(pdata->trigger_levels[i], data->base +  			reg->threshold_th0 + i * sizeof(reg->threshold_th0)); -		writel(reg->inten_rise_mask, data->base + reg->tmu_intclear); +		writel(reg->intclr_rise_mask, data->base + reg->tmu_intclear);  	} else {  		/* Write temperature code for rising and falling threshold */  		for (i = 0; @@ -248,6 +259,7 @@ skip_calib_data:  				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, @@ -264,8 +276,8 @@ skip_calib_data:  		writel(falling_threshold,  				data->base + reg->threshold_th1); -		writel((reg->inten_rise_mask << reg->inten_rise_shift) | -			(reg->inten_fall_mask << reg->inten_fall_shift), +		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 */ @@ -280,6 +292,7 @@ skip_calib_data:  			}  			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); @@ -297,10 +310,12 @@ skip_calib_data:  	}  	/*Clear the PMIN in the common TMU register*/  	if (reg->tmu_pmin && !data->id) -		writel(0, data->base_common + reg->tmu_pmin); +		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;  } @@ -317,6 +332,9 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on)  	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; @@ -449,12 +467,16 @@ static void exynos_tmu_work(struct work_struct *work)  	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_common + 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); @@ -488,13 +510,25 @@ static const struct of_device_id exynos_tmu_match[] = {  	},  	{  		.compatible = "samsung,exynos4412-tmu", -		.data = (void *)EXYNOS5250_TMU_DRV_DATA, +		.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,  	}, @@ -576,7 +610,7 @@ static int exynos_map_dt_data(struct platform_device *pdev)  	 * Check if the TMU shares some registers and then try to map the  	 * memory of common registers.  	 */ -	if (!TMU_SUPPORTS(pdata, SHARED_MEMORY)) +	if (!TMU_SUPPORTS(pdata, ADDRESS_MULTIPLE))  		return 0;  	if (of_address_to_resource(pdev->dev.of_node, 1, &res)) { @@ -584,9 +618,9 @@ static int exynos_map_dt_data(struct platform_device *pdev)  		return -ENODEV;  	} -	data->base_common = devm_ioremap(&pdev->dev, res.start, +	data->base_second = devm_ioremap(&pdev->dev, res.start,  					resource_size(&res)); -	if (!data->base_common) { +	if (!data->base_second) {  		dev_err(&pdev->dev, "Failed to ioremap memory\n");  		return -ENOMEM;  	} @@ -603,10 +637,8 @@ static int exynos_tmu_probe(struct platform_device *pdev)  	data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data),  					GFP_KERNEL); -	if (!data) { -		dev_err(&pdev->dev, "Failed to allocate driver structure\n"); +	if (!data)  		return -ENOMEM; -	}  	platform_set_drvdata(pdev, data);  	mutex_init(&data->lock); @@ -625,13 +657,32 @@ static int exynos_tmu_probe(struct platform_device *pdev)  		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) -		return ret; +	if (ret) { +		dev_err(&pdev->dev, "Failed to get clock\n"); +		goto err_clk_sec; +	} -	if (pdata->type == SOC_ARCH_EXYNOS || -		pdata->type == SOC_ARCH_EXYNOS4210 || -				pdata->type == SOC_ARCH_EXYNOS5440) +	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; @@ -651,7 +702,6 @@ static int exynos_tmu_probe(struct platform_device *pdev)  	sensor_conf = devm_kzalloc(&pdev->dev,  				sizeof(struct thermal_sensor_conf), GFP_KERNEL);  	if (!sensor_conf) { -		dev_err(&pdev->dev, "Failed to allocate registration struct\n");  		ret = -ENOMEM;  		goto err_clk;  	} @@ -699,6 +749,9 @@ static int exynos_tmu_probe(struct platform_device *pdev)  	return 0;  err_clk:  	clk_unprepare(data->clk); +err_clk_sec: +	if (!IS_ERR(data->clk_sec)) +		clk_unprepare(data->clk_sec);  	return ret;  } @@ -711,6 +764,8 @@ static int exynos_tmu_remove(struct platform_device *pdev)  	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); diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h index b364c9eee70..edd08cf7672 100644 --- a/drivers/thermal/samsung/exynos_tmu.h +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -41,7 +41,10 @@ enum calibration_mode {  enum soc_type {  	SOC_ARCH_EXYNOS4210 = 1, -	SOC_ARCH_EXYNOS, +	SOC_ARCH_EXYNOS4412, +	SOC_ARCH_EXYNOS5250, +	SOC_ARCH_EXYNOS5260, +	SOC_ARCH_EXYNOS5420_TRIMINFO,  	SOC_ARCH_EXYNOS5440,  }; @@ -59,7 +62,7 @@ enum soc_type {   *			state(active/idle) can be checked.   * TMU_SUPPORT_EMUL_TIME - This features allows to set next temp emulation   *			sample time. - * TMU_SUPPORT_SHARED_MEMORY - This feature tells that the different TMU + * 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.   */ @@ -69,7 +72,7 @@ enum soc_type {  #define TMU_SUPPORT_FALLING_TRIP		BIT(3)  #define TMU_SUPPORT_READY_STATUS		BIT(4)  #define TMU_SUPPORT_EMUL_TIME			BIT(5) -#define TMU_SUPPORT_SHARED_MEMORY		BIT(6) +#define TMU_SUPPORT_ADDRESS_MULTIPLE		BIT(6)  #define TMU_SUPPORTS(a, b)	(a->features & TMU_SUPPORT_ ## b) @@ -84,6 +87,7 @@ enum soc_type {   * @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. @@ -120,10 +124,6 @@ enum soc_type {   * @threshold_th3_l0_shift: shift bits of level0 threshold temperature.   * @tmu_inten: register containing the different threshold interrupt  	enable bits. - * @inten_rise_shift: shift bits of all rising interrupt bits. - * @inten_rise_mask: mask bits of all rising interrupt bits. - * @inten_fall_shift: shift bits of all rising interrupt bits. - * @inten_fall_mask: mask bits of all rising interrupt 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. @@ -134,6 +134,10 @@ enum soc_type {   * @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. @@ -147,9 +151,11 @@ struct exynos_tmu_registers {  	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; @@ -188,10 +194,6 @@ struct exynos_tmu_registers {  	u32	threshold_th3_l0_shift;  	u32	tmu_inten; -	u32	inten_rise_shift; -	u32	inten_rise_mask; -	u32	inten_fall_shift; -	u32	inten_fall_mask;  	u32	inten_rise0_shift;  	u32	inten_rise1_shift;  	u32	inten_rise2_shift; @@ -204,6 +206,10 @@ struct exynos_tmu_registers {  	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; @@ -257,6 +263,7 @@ struct exynos_tmu_registers {   * @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. @@ -286,6 +293,7 @@ struct exynos_tmu_platform_data {  	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; diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index 9002499c1f6..c1d81dcd781 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.c +++ b/drivers/thermal/samsung/exynos_tmu_data.c @@ -40,13 +40,13 @@ static const struct exynos_tmu_registers exynos4210_tmu_registers = {  	.threshold_temp = EXYNOS4210_TMU_REG_THRESHOLD_TEMP,  	.threshold_th0 = EXYNOS4210_TMU_REG_TRIG_LEVEL0,  	.tmu_inten = EXYNOS_TMU_REG_INTEN, -	.inten_rise_mask = EXYNOS4210_TMU_TRIG_LEVEL_MASK,  	.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 = { @@ -90,14 +90,15 @@ struct exynos_tmu_init_data const exynos4210_default_tmu_data = {  };  #endif -#if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412) -static const struct exynos_tmu_registers exynos5250_tmu_registers = { +#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, @@ -111,10 +112,6 @@ static const struct exynos_tmu_registers exynos5250_tmu_registers = {  	.threshold_th0 = EXYNOS_THD_TEMP_RISE,  	.threshold_th1 = EXYNOS_THD_TEMP_FALL,  	.tmu_inten = EXYNOS_TMU_REG_INTEN, -	.inten_rise_mask = EXYNOS_TMU_RISE_INT_MASK, -	.inten_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, -	.inten_fall_mask = EXYNOS_TMU_FALL_INT_MASK, -	.inten_fall_shift = EXYNOS_TMU_FALL_INT_SHIFT,  	.inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT,  	.inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT,  	.inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, @@ -122,13 +119,119 @@ static const struct exynos_tmu_registers exynos5250_tmu_registers = {  	.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 EXYNOS5250_TMU_DATA \ +#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, \ @@ -162,17 +265,123 @@ static const struct exynos_tmu_registers exynos5250_tmu_registers = {  		.temp_level = 103, \  	}, \  	.freq_tab_count = 2, \ -	.type = SOC_ARCH_EXYNOS, \ -	.registers = &exynos5250_tmu_registers, \ +	.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 exynos5250_default_tmu_data = { +struct exynos_tmu_init_data const exynos5260_default_tmu_data = {  	.tmu_data = { -		{ EXYNOS5250_TMU_DATA }, +		{ EXYNOS5260_TMU_DATA }, +		{ EXYNOS5260_TMU_DATA }, +		{ EXYNOS5260_TMU_DATA }, +		{ EXYNOS5260_TMU_DATA }, +		{ EXYNOS5260_TMU_DATA },  	}, -	.tmu_count = 1, +	.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 @@ -199,10 +408,6 @@ static const struct exynos_tmu_registers exynos5440_tmu_registers = {  	.threshold_th2 = EXYNOS5440_TMU_S0_7_TH2,  	.threshold_th3_l0_shift = EXYNOS5440_TMU_TH_RISE4_SHIFT,  	.tmu_inten = EXYNOS5440_TMU_S0_7_IRQEN, -	.inten_rise_mask = EXYNOS5440_TMU_RISE_INT_MASK, -	.inten_rise_shift = EXYNOS5440_TMU_RISE_INT_SHIFT, -	.inten_fall_mask = EXYNOS5440_TMU_FALL_INT_MASK, -	.inten_fall_shift = EXYNOS5440_TMU_FALL_INT_SHIFT,  	.inten_rise0_shift = EXYNOS5440_TMU_INTEN_RISE0_SHIFT,  	.inten_rise1_shift = EXYNOS5440_TMU_INTEN_RISE1_SHIFT,  	.inten_rise2_shift = EXYNOS5440_TMU_INTEN_RISE2_SHIFT, @@ -210,6 +415,10 @@ static const struct exynos_tmu_registers exynos5440_tmu_registers = {  	.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, @@ -237,7 +446,7 @@ static const struct exynos_tmu_registers exynos5440_tmu_registers = {  	.type = SOC_ARCH_EXYNOS5440, \  	.registers = &exynos5440_tmu_registers, \  	.features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_FALLING_TRIP | \ -			TMU_SUPPORT_MULTI_INST | TMU_SUPPORT_SHARED_MEMORY), +			TMU_SUPPORT_MULTI_INST | TMU_SUPPORT_ADDRESS_MULTIPLE),  struct exynos_tmu_init_data const exynos5440_default_tmu_data = {  	.tmu_data = { diff --git a/drivers/thermal/samsung/exynos_tmu_data.h b/drivers/thermal/samsung/exynos_tmu_data.h index dc7feb51099..d268981b65e 100644 --- a/drivers/thermal/samsung/exynos_tmu_data.h +++ b/drivers/thermal/samsung/exynos_tmu_data.h @@ -69,9 +69,11 @@  #define EXYNOS_TMU_RISE_INT_MASK	0x111  #define EXYNOS_TMU_RISE_INT_SHIFT	0  #define EXYNOS_TMU_FALL_INT_MASK	0x111 -#define EXYNOS_TMU_FALL_INT_SHIFT	12  #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 @@ -85,6 +87,7 @@  #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 @@ -95,6 +98,21 @@  #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 @@ -115,7 +133,6 @@  #define EXYNOS5440_TMU_RISE_INT_MASK		0xf  #define EXYNOS5440_TMU_RISE_INT_SHIFT		0  #define EXYNOS5440_TMU_FALL_INT_MASK		0xf -#define EXYNOS5440_TMU_FALL_INT_SHIFT		4  #define EXYNOS5440_TMU_INTEN_RISE0_SHIFT	0  #define EXYNOS5440_TMU_INTEN_RISE1_SHIFT	1  #define EXYNOS5440_TMU_INTEN_RISE2_SHIFT	2 @@ -138,13 +155,34 @@ extern struct exynos_tmu_init_data const exynos4210_default_tmu_data;  #define EXYNOS4210_TMU_DRV_DATA (NULL)  #endif -#if (defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412)) +#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) diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c index ab79ea4701d..1e2193fc324 100644 --- a/drivers/thermal/spear_thermal.c +++ b/drivers/thermal/spear_thermal.c @@ -113,10 +113,8 @@ static int spear_thermal_probe(struct platform_device *pdev)  	}  	stdev = devm_kzalloc(&pdev->dev, sizeof(*stdev), GFP_KERNEL); -	if (!stdev) { -		dev_err(&pdev->dev, "kzalloc fail\n"); +	if (!stdev)  		return -ENOMEM; -	}  	/* Enable thermal sensor */  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/step_wise.c index d89e781b0a1..f251521baaa 100644 --- a/drivers/thermal/step_wise.c +++ b/drivers/thermal/step_wise.c @@ -60,6 +60,7 @@ static unsigned long get_target_state(struct thermal_instance *instance,  	 */  	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: @@ -131,6 +132,9 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int 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) { @@ -139,6 +143,8 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)  		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; diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 4962a6aaf29..71b0ec0c370 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -34,6 +34,7 @@  #include <linux/thermal.h>  #include <linux/reboot.h>  #include <linux/string.h> +#include <linux/of.h>  #include <net/netlink.h>  #include <net/genetlink.h> @@ -55,10 +56,15 @@ 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; @@ -81,17 +87,23 @@ int thermal_register_governor(struct thermal_governor *governor)  	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; -		if (pos->tzp) -			name = pos->tzp->governor_name; -		else -			name = DEFAULT_THERMAL_GOVERNOR; + +		name = pos->tzp->governor_name; +  		if (!strnicmp(name, governor->name, THERMAL_NAME_LENGTH))  			pos->governor = governor;  	} @@ -247,10 +259,11 @@ static void bind_cdev(struct thermal_cooling_device *cdev)  		if (!pos->tzp && !pos->ops->bind)  			continue; -		if (!pos->tzp && pos->ops->bind) { +		if (pos->ops->bind) {  			ret = pos->ops->bind(pos, cdev);  			if (ret)  				print_bind_err_msg(pos, cdev, ret); +			continue;  		}  		tzp = pos->tzp; @@ -282,8 +295,8 @@ static void bind_tz(struct thermal_zone_device *tz)  	mutex_lock(&thermal_list_lock); -	/* If there is no platform data, try to use ops->bind */ -	if (!tzp && tz->ops->bind) { +	/* 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) @@ -340,8 +353,8 @@ static void monitor_thermal_zone(struct thermal_zone_device *tz)  static void handle_non_critical_trips(struct thermal_zone_device *tz,  			int trip, enum thermal_trip_type trip_type)  { -	if (tz->governor) -		tz->governor->throttle(tz, trip); +	tz->governor ? tz->governor->throttle(tz, trip) : +		       def_governor->throttle(tz, trip);  }  static void handle_critical_trips(struct thermal_zone_device *tz, @@ -402,7 +415,7 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, unsigned long *temp)  	enum thermal_trip_type type;  #endif -	if (!tz || IS_ERR(tz)) +	if (!tz || IS_ERR(tz) || !tz->ops->get_temp)  		goto exit;  	mutex_lock(&tz->lock); @@ -449,12 +462,18 @@ static void update_temperature(struct thermal_zone_device *tz)  	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++) @@ -773,6 +792,9 @@ emul_temp_store(struct device *dev, struct device_attribute *attr,  		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); @@ -1038,7 +1060,8 @@ static void thermal_release(struct device *dev)  		     sizeof("thermal_zone") - 1)) {  		tz = to_thermal_zone(dev);  		kfree(tz); -	} else { +	} else if(!strncmp(dev_name(dev), "cooling_device", +			sizeof("cooling_device") - 1)){  		cdev = to_cooling_device(dev);  		kfree(cdev);  	} @@ -1050,7 +1073,8 @@ static struct class thermal_class = {  };  /** - * thermal_cooling_device_register() - register a new thermal cooling device + * __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. @@ -1058,13 +1082,16 @@ static struct class thermal_class = {   * 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.   */ -struct thermal_cooling_device * -thermal_cooling_device_register(char *type, void *devdata, -				const struct thermal_cooling_device_ops *ops) +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; @@ -1089,8 +1116,9 @@ thermal_cooling_device_register(char *type, void *devdata,  	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 = true; +	cdev->updated = false;  	cdev->device.class = &thermal_class;  	cdev->devdata = devdata;  	dev_set_name(&cdev->device, "cooling_device%d", cdev->id); @@ -1131,9 +1159,53 @@ unregister:  	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.   * @@ -1205,6 +1277,8 @@ void thermal_cdev_update(struct thermal_cooling_device *cdev)  	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) @@ -1213,6 +1287,7 @@ void thermal_cdev_update(struct thermal_cooling_device *cdev)  	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); @@ -1368,7 +1443,7 @@ static void remove_trip_attrs(struct thermal_zone_device *tz)   */  struct thermal_zone_device *thermal_zone_device_register(const char *type,  	int trips, int mask, void *devdata, -	const struct thermal_zone_device_ops *ops, +	struct thermal_zone_device_ops *ops,  	const struct thermal_zone_params *tzp,  	int passive_delay, int polling_delay)  { @@ -1384,7 +1459,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,  	if (trips > THERMAL_MAX_TRIPS || trips < 0 || mask >> trips)  		return ERR_PTR(-EINVAL); -	if (!ops || !ops->get_temp) +	if (!ops)  		return ERR_PTR(-EINVAL);  	if (trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp)) @@ -1469,7 +1544,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,  	if (tz->tzp)  		tz->governor = __find_governor(tz->tzp->governor_name);  	else -		tz->governor = __find_governor(DEFAULT_THERMAL_GOVERNOR); +		tz->governor = def_governor;  	mutex_unlock(&thermal_governor_lock); @@ -1488,6 +1563,9 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,  	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) @@ -1606,15 +1684,17 @@ exit:  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, -}; - -static struct genl_multicast_group thermal_event_mcgrp = { -	.name = THERMAL_GENL_MCAST_GROUP_NAME, +	.mcgrps = thermal_event_mcgrps, +	.n_mcgrps = ARRAY_SIZE(thermal_event_mcgrps),  };  int thermal_generate_netlink_event(struct thermal_zone_device *tz, @@ -1675,7 +1755,8 @@ int thermal_generate_netlink_event(struct thermal_zone_device *tz,  		return result;  	} -	result = genlmsg_multicast(skb, 0, thermal_event_mcgrp.id, GFP_ATOMIC); +	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); @@ -1685,17 +1766,7 @@ EXPORT_SYMBOL_GPL(thermal_generate_netlink_event);  static int genetlink_init(void)  { -	int result; - -	result = genl_register_family(&thermal_event_genl_family); -	if (result) -		return result; - -	result = genl_register_mc_group(&thermal_event_genl_family, -					&thermal_event_mcgrp); -	if (result) -		genl_unregister_family(&thermal_event_genl_family); -	return result; +	return genl_register_family(&thermal_event_genl_family);  }  static void genetlink_exit(void) @@ -1745,8 +1816,14 @@ static int __init thermal_init(void)  	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: @@ -1762,6 +1839,7 @@ error:  static void __exit thermal_exit(void)  { +	of_thermal_destroy_zones();  	genetlink_exit();  	class_unregister(&thermal_class);  	thermal_unregister_governors(); diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 7cf2f662625..3db339fb636 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -77,4 +77,13 @@ 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 index eeef0e2498c..1967bee4f07 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -140,6 +140,12 @@ thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,  	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; @@ -159,7 +165,7 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)  	INIT_LIST_HEAD(&hwmon->tz_list);  	strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); -	hwmon->device = hwmon_device_register(&tz->device); +	hwmon->device = hwmon_device_register(NULL);  	if (IS_ERR(hwmon->device)) {  		result = PTR_ERR(hwmon->device);  		goto free_mem; @@ -189,21 +195,18 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)  	if (result)  		goto free_temp_mem; -	if (tz->ops->get_crit_temp) { -		unsigned long temperature; -		if (!tz->ops->get_crit_temp(tz, &temperature)) { -			snprintf(temp->temp_crit.name, -				 sizeof(temp->temp_crit.name), +	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; -		} +		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); @@ -250,7 +253,7 @@ void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)  	}  	device_remove_file(hwmon->device, &temp->temp_input.attr); -	if (tz->ops->get_crit_temp) +	if (thermal_zone_crit_temp_valid(tz))  		device_remove_file(hwmon->device, &temp->temp_crit.attr);  	mutex_lock(&thermal_hwmon_list_lock); diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index 74c0e3474d6..634b6ce0e63 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -1155,7 +1155,7 @@ static struct ti_bandgap *ti_bandgap_build(struct platform_device *pdev)  	/* register shadow for context save and restore */  	bgp->regval = devm_kzalloc(&pdev->dev, sizeof(*bgp->regval) *  				   bgp->conf->sensor_count, GFP_KERNEL); -	if (!bgp) { +	if (!bgp->regval) {  		dev_err(&pdev->dev, "Unable to allocate mem for driver ref\n");  		return ERR_PTR(-ENOMEM);  	} @@ -1248,7 +1248,7 @@ int ti_bandgap_probe(struct platform_device *pdev)  	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 == 0xffffffff) { +	    clk_rate <= 0) {  		ret = -ENODEV;  		dev_err(&pdev->dev, "wrong clock rate (%d)\n", clk_rate);  		goto put_clks; @@ -1500,10 +1500,8 @@ static int ti_bandgap_resume(struct device *dev)  	return ti_bandgap_restore_ctxt(bgp);  } -static const struct dev_pm_ops ti_bandgap_dev_pm_ops = { -	SET_SYSTEM_SLEEP_PM_OPS(ti_bandgap_suspend, -				ti_bandgap_resume) -}; +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 diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 4f8b9af54a5..9eec26dc044 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -31,6 +31,7 @@  #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" @@ -44,6 +45,7 @@ struct ti_thermal_data {  	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) @@ -75,11 +77,10 @@ static inline int ti_thermal_hotspot_temperature(int t, int s, int c)  /* thermal zone ops */  /* Get temperature callback function for thermal zone*/ -static inline int ti_thermal_get_temp(struct thermal_zone_device *thermal, -				      unsigned long *temp) +static inline int __ti_thermal_get_temp(void *devdata, long *temp)  {  	struct thermal_zone_device *pcb_tz = NULL; -	struct ti_thermal_data *data = thermal->devdata; +	struct ti_thermal_data *data = devdata;  	struct ti_bandgap *bgp;  	const struct ti_temp_sensor *s;  	int ret, tmp, slope, constant; @@ -110,6 +111,7 @@ static inline int ti_thermal_get_temp(struct thermal_zone_device *thermal,  		} else {  			dev_err(bgp->dev,  				"Failed to read PCB state. Using defaults\n"); +			ret = 0;  		}  	}  	*temp = ti_thermal_hotspot_temperature(tmp, slope, constant); @@ -117,6 +119,14 @@ static inline int ti_thermal_get_temp(struct thermal_zone_device *thermal,  	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) @@ -229,11 +239,9 @@ static int ti_thermal_get_trip_temp(struct thermal_zone_device *thermal,  	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) +static int __ti_thermal_get_trend(void *p, long *trend)  { -	struct ti_thermal_data *data = thermal->devdata; +	struct ti_thermal_data *data = p;  	struct ti_bandgap *bgp;  	int id, tr, ret = 0; @@ -244,6 +252,22 @@ static int ti_thermal_get_trend(struct thermal_zone_device *thermal,  	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) @@ -307,16 +331,23 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id,  	if (!data)  		return -EINVAL; -	/* Create thermal zone */ -	data->ti_thermal = thermal_zone_device_register(domain, +	/* 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); +		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;  	} -	data->ti_thermal->polling_delay = FAST_TEMP_MONITORING_RATE;  	ti_bandgap_set_sensor_data(bgp, id, data);  	ti_bandgap_write_update_interval(bgp, data->sensor_id,  					data->ti_thermal->polling_delay); @@ -330,7 +361,13 @@ int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id)  	data = ti_bandgap_get_sensor_data(bgp, id); -	thermal_zone_device_unregister(data->ti_thermal); +	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;  } @@ -349,6 +386,15 @@ int ti_thermal_report_sensor_temperature(struct ti_bandgap *bgp, int id)  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)) @@ -379,7 +425,9 @@ 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); -	cpufreq_cooling_unregister(data->cool_dev); + +	if (data && data->cool_dev) +		cpufreq_cooling_unregister(data->cool_dev);  	return 0;  } diff --git a/drivers/thermal/x86_pkg_temp_thermal.c b/drivers/thermal/x86_pkg_temp_thermal.c index f36950e4134..9ea3d9d49ff 100644 --- a/drivers/thermal/x86_pkg_temp_thermal.c +++ b/drivers/thermal/x86_pkg_temp_thermal.c @@ -68,6 +68,10 @@ struct phy_dev_entry {  	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); @@ -215,7 +219,7 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzd,  	return 0;  } -int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, +static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,  							unsigned long temp)  {  	u32 l, h; @@ -316,18 +320,19 @@ static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work)  	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(&pkg_work_lock); +	spin_lock_irqsave(&pkg_work_lock, flags);  	++pkg_work_cnt;  	if (unlikely(phy_id > max_phy_id)) { -		spin_unlock(&pkg_work_lock); +		spin_unlock_irqrestore(&pkg_work_lock, flags);  		return;  	}  	pkg_work_scheduled[phy_id] = 0; -	spin_unlock(&pkg_work_lock); +	spin_unlock_irqrestore(&pkg_work_lock, flags);  	enable_pkg_thres_interrupt();  	rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val); @@ -393,10 +398,10 @@ static int pkg_temp_thermal_device_add(unsigned int cpu)  	int err;  	u32 tj_max;  	struct phy_dev_entry *phy_dev_entry; -	char buffer[30];  	int thres_count;  	u32 eax, ebx, ecx, edx;  	u8 *temp; +	unsigned long flags;  	cpuid(6, &eax, &ebx, &ecx, &edx);  	thres_count = ebx & 0x07; @@ -420,31 +425,29 @@ static int pkg_temp_thermal_device_add(unsigned int cpu)  		goto err_ret_unlock;  	} -	spin_lock(&pkg_work_lock); +	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(&pkg_work_lock); +		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(&pkg_work_lock); +	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; -	snprintf(buffer, sizeof(buffer), "pkg-temp-%d\n", -					phy_dev_entry->phys_proc_id); -	phy_dev_entry->tzone = thermal_zone_device_register(buffer, +	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, NULL, 0, 0); +			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; @@ -587,12 +590,12 @@ static int __init pkg_temp_thermal_init(void)  	platform_thermal_package_rate_control =  			pkg_temp_thermal_platform_thermal_rate_control; -	get_online_cpus(); +	cpu_notifier_register_begin();  	for_each_online_cpu(i)  		if (get_core_online(i))  			goto err_ret; -	register_hotcpu_notifier(&pkg_temp_thermal_notifier); -	put_online_cpus(); +	__register_hotcpu_notifier(&pkg_temp_thermal_notifier); +	cpu_notifier_register_done();  	pkg_temp_debugfs_init(); /* Don't care if fails */ @@ -601,7 +604,7 @@ static int __init pkg_temp_thermal_init(void)  err_ret:  	for_each_online_cpu(i)  		put_core_offline(i); -	put_online_cpus(); +	cpu_notifier_register_done();  	kfree(pkg_work_scheduled);  	platform_thermal_package_notify = NULL;  	platform_thermal_package_rate_control = NULL; @@ -614,8 +617,8 @@ static void __exit pkg_temp_thermal_exit(void)  	struct phy_dev_entry *phdev, *n;  	int i; -	get_online_cpus(); -	unregister_hotcpu_notifier(&pkg_temp_thermal_notifier); +	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 */ @@ -633,7 +636,7 @@ static void __exit pkg_temp_thermal_exit(void)  	for_each_online_cpu(i)  		cancel_delayed_work_sync(  			&per_cpu(pkg_temp_thermal_threshold_work, i)); -	put_online_cpus(); +	cpu_notifier_register_done();  	kfree(pkg_work_scheduled);  | 
