diff options
Diffstat (limited to 'drivers/thermal')
23 files changed, 2458 insertions, 216 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index f35a1f75b15..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 @@ -79,6 +92,7 @@ config THERMAL_GOV_USER_SPACE config CPU_THERMAL bool "generic cpu cooling support" depends on CPU_FREQ + depends on THERMAL_OF help This implements the generic cpu cooling mechanism through frequency reduction. An ACPI version of this already exists @@ -121,14 +135,15 @@ config SPEAR_THERMAL 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. 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 02a46f23d14..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,8 +470,8 @@ 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); + 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); @@ -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 8f181b3f842..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,8 +681,10 @@ 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}, 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 c2301da08ac..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, diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 32f38b90c4f..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; } @@ -452,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); @@ -498,6 +517,18 @@ static const struct of_device_id exynos_tmu_match[] = { .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, }, @@ -579,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)) { @@ -587,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; } @@ -606,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); @@ -628,13 +657,31 @@ 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_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 { @@ -655,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; } @@ -703,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; } @@ -715,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 3fb65547e64..edd08cf7672 100644 --- a/drivers/thermal/samsung/exynos_tmu.h +++ b/drivers/thermal/samsung/exynos_tmu.h @@ -43,6 +43,8 @@ enum soc_type { SOC_ARCH_EXYNOS4210 = 1, SOC_ARCH_EXYNOS4412, SOC_ARCH_EXYNOS5250, + SOC_ARCH_EXYNOS5260, + SOC_ARCH_EXYNOS5420_TRIMINFO, SOC_ARCH_EXYNOS5440, }; @@ -60,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. */ @@ -70,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) @@ -122,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. @@ -136,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. @@ -149,6 +151,7 @@ struct exynos_tmu_registers { u32 triminfo_85_shift; u32 triminfo_ctrl; + u32 triminfo_ctrl1; u32 triminfo_reload_shift; u32 tmu_ctrl; @@ -191,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; @@ -207,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; diff --git a/drivers/thermal/samsung/exynos_tmu_data.c b/drivers/thermal/samsung/exynos_tmu_data.c index 073c292baa5..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 = { @@ -112,10 +112,6 @@ static const struct exynos_tmu_registers exynos4412_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, @@ -123,6 +119,10 @@ static const struct exynos_tmu_registers exynos4412_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, @@ -131,8 +131,8 @@ static const struct exynos_tmu_registers exynos4412_tmu_registers = { #define EXYNOS4412_TMU_DATA \ .threshold_falling = 10, \ - .trigger_levels[0] = 85, \ - .trigger_levels[1] = 103, \ + .trigger_levels[0] = 70, \ + .trigger_levels[1] = 95, \ .trigger_levels[2] = 110, \ .trigger_levels[3] = 120, \ .trigger_enable[0] = true, \ @@ -155,12 +155,12 @@ static const struct exynos_tmu_registers exynos4412_tmu_registers = { .second_point_trim = 85, \ .default_temp_offset = 50, \ .freq_tab[0] = { \ - .freq_clip_max = 800 * 1000, \ - .temp_level = 85, \ + .freq_clip_max = 1400 * 1000, \ + .temp_level = 70, \ }, \ .freq_tab[1] = { \ - .freq_clip_max = 200 * 1000, \ - .temp_level = 103, \ + .freq_clip_max = 400 * 1000, \ + .temp_level = 95, \ }, \ .freq_tab_count = 2, \ .registers = &exynos4412_tmu_registers, \ @@ -194,6 +194,197 @@ struct exynos_tmu_init_data const exynos5250_default_tmu_data = { }; #endif +#if defined(CONFIG_SOC_EXYNOS5260) +static const struct exynos_tmu_registers exynos5260_tmu_registers = { + .triminfo_data = EXYNOS_TMU_REG_TRIMINFO, + .triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, + .triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, + .tmu_ctrl = EXYNOS_TMU_REG_CONTROL, + .tmu_ctrl = EXYNOS_TMU_REG_CONTROL1, + .buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, + .buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, + .therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT, + .therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK, + .therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT, + .buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, + .buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, + .core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, + .tmu_status = EXYNOS_TMU_REG_STATUS, + .tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP, + .threshold_th0 = EXYNOS_THD_TEMP_RISE, + .threshold_th1 = EXYNOS_THD_TEMP_FALL, + .tmu_inten = EXYNOS5260_TMU_REG_INTEN, + .inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, + .inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, + .inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, + .inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, + .inten_fall0_shift = EXYNOS_TMU_INTEN_FALL0_SHIFT, + .tmu_intstat = EXYNOS5260_TMU_REG_INTSTAT, + .tmu_intclear = EXYNOS5260_TMU_REG_INTCLEAR, + .intclr_fall_shift = EXYNOS5420_TMU_CLEAR_FALL_INT_SHIFT, + .intclr_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, + .intclr_rise_mask = EXYNOS5260_TMU_RISE_INT_MASK, + .intclr_fall_mask = EXYNOS5260_TMU_FALL_INT_MASK, + .emul_con = EXYNOS5260_EMUL_CON, + .emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, + .emul_time_shift = EXYNOS_EMUL_TIME_SHIFT, + .emul_time_mask = EXYNOS_EMUL_TIME_MASK, +}; + +#define __EXYNOS5260_TMU_DATA \ + .threshold_falling = 10, \ + .trigger_levels[0] = 85, \ + .trigger_levels[1] = 103, \ + .trigger_levels[2] = 110, \ + .trigger_levels[3] = 120, \ + .trigger_enable[0] = true, \ + .trigger_enable[1] = true, \ + .trigger_enable[2] = true, \ + .trigger_enable[3] = false, \ + .trigger_type[0] = THROTTLE_ACTIVE, \ + .trigger_type[1] = THROTTLE_ACTIVE, \ + .trigger_type[2] = SW_TRIP, \ + .trigger_type[3] = HW_TRIP, \ + .max_trigger_level = 4, \ + .gain = 8, \ + .reference_voltage = 16, \ + .noise_cancel_mode = 4, \ + .cal_type = TYPE_ONE_POINT_TRIMMING, \ + .efuse_value = 55, \ + .min_efuse_value = 40, \ + .max_efuse_value = 100, \ + .first_point_trim = 25, \ + .second_point_trim = 85, \ + .default_temp_offset = 50, \ + .freq_tab[0] = { \ + .freq_clip_max = 800 * 1000, \ + .temp_level = 85, \ + }, \ + .freq_tab[1] = { \ + .freq_clip_max = 200 * 1000, \ + .temp_level = 103, \ + }, \ + .freq_tab_count = 2, \ + .registers = &exynos5260_tmu_registers, \ + +#define EXYNOS5260_TMU_DATA \ + __EXYNOS5260_TMU_DATA \ + .type = SOC_ARCH_EXYNOS5260, \ + .features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ + TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ + TMU_SUPPORT_EMUL_TIME) + +struct exynos_tmu_init_data const exynos5260_default_tmu_data = { + .tmu_data = { + { EXYNOS5260_TMU_DATA }, + { EXYNOS5260_TMU_DATA }, + { EXYNOS5260_TMU_DATA }, + { EXYNOS5260_TMU_DATA }, + { EXYNOS5260_TMU_DATA }, + }, + .tmu_count = 5, +}; +#endif + +#if defined(CONFIG_SOC_EXYNOS5420) +static const struct exynos_tmu_registers exynos5420_tmu_registers = { + .triminfo_data = EXYNOS_TMU_REG_TRIMINFO, + .triminfo_25_shift = EXYNOS_TRIMINFO_25_SHIFT, + .triminfo_85_shift = EXYNOS_TRIMINFO_85_SHIFT, + .tmu_ctrl = EXYNOS_TMU_REG_CONTROL, + .buf_vref_sel_shift = EXYNOS_TMU_REF_VOLTAGE_SHIFT, + .buf_vref_sel_mask = EXYNOS_TMU_REF_VOLTAGE_MASK, + .therm_trip_mode_shift = EXYNOS_TMU_TRIP_MODE_SHIFT, + .therm_trip_mode_mask = EXYNOS_TMU_TRIP_MODE_MASK, + .therm_trip_en_shift = EXYNOS_TMU_THERM_TRIP_EN_SHIFT, + .buf_slope_sel_shift = EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT, + .buf_slope_sel_mask = EXYNOS_TMU_BUF_SLOPE_SEL_MASK, + .core_en_shift = EXYNOS_TMU_CORE_EN_SHIFT, + .tmu_status = EXYNOS_TMU_REG_STATUS, + .tmu_cur_temp = EXYNOS_TMU_REG_CURRENT_TEMP, + .threshold_th0 = EXYNOS_THD_TEMP_RISE, + .threshold_th1 = EXYNOS_THD_TEMP_FALL, + .tmu_inten = EXYNOS_TMU_REG_INTEN, + .inten_rise0_shift = EXYNOS_TMU_INTEN_RISE0_SHIFT, + .inten_rise1_shift = EXYNOS_TMU_INTEN_RISE1_SHIFT, + .inten_rise2_shift = EXYNOS_TMU_INTEN_RISE2_SHIFT, + /* INTEN_RISE3 Not availble in exynos5420 */ + .inten_rise3_shift = EXYNOS_TMU_INTEN_RISE3_SHIFT, + .inten_fall0_shift = EXYNOS_TMU_INTEN_FALL0_SHIFT, + .tmu_intstat = EXYNOS_TMU_REG_INTSTAT, + .tmu_intclear = EXYNOS_TMU_REG_INTCLEAR, + .intclr_fall_shift = EXYNOS5420_TMU_CLEAR_FALL_INT_SHIFT, + .intclr_rise_shift = EXYNOS_TMU_RISE_INT_SHIFT, + .intclr_rise_mask = EXYNOS_TMU_RISE_INT_MASK, + .intclr_fall_mask = EXYNOS_TMU_FALL_INT_MASK, + .emul_con = EXYNOS_EMUL_CON, + .emul_temp_shift = EXYNOS_EMUL_DATA_SHIFT, + .emul_time_shift = EXYNOS_EMUL_TIME_SHIFT, + .emul_time_mask = EXYNOS_EMUL_TIME_MASK, +}; + +#define __EXYNOS5420_TMU_DATA \ + .threshold_falling = 10, \ + .trigger_levels[0] = 85, \ + .trigger_levels[1] = 103, \ + .trigger_levels[2] = 110, \ + .trigger_levels[3] = 120, \ + .trigger_enable[0] = true, \ + .trigger_enable[1] = true, \ + .trigger_enable[2] = true, \ + .trigger_enable[3] = false, \ + .trigger_type[0] = THROTTLE_ACTIVE, \ + .trigger_type[1] = THROTTLE_ACTIVE, \ + .trigger_type[2] = SW_TRIP, \ + .trigger_type[3] = HW_TRIP, \ + .max_trigger_level = 4, \ + .gain = 8, \ + .reference_voltage = 16, \ + .noise_cancel_mode = 4, \ + .cal_type = TYPE_ONE_POINT_TRIMMING, \ + .efuse_value = 55, \ + .min_efuse_value = 40, \ + .max_efuse_value = 100, \ + .first_point_trim = 25, \ + .second_point_trim = 85, \ + .default_temp_offset = 50, \ + .freq_tab[0] = { \ + .freq_clip_max = 800 * 1000, \ + .temp_level = 85, \ + }, \ + .freq_tab[1] = { \ + .freq_clip_max = 200 * 1000, \ + .temp_level = 103, \ + }, \ + .freq_tab_count = 2, \ + .registers = &exynos5420_tmu_registers, \ + +#define EXYNOS5420_TMU_DATA \ + __EXYNOS5420_TMU_DATA \ + .type = SOC_ARCH_EXYNOS5250, \ + .features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ + TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ + TMU_SUPPORT_EMUL_TIME) + +#define EXYNOS5420_TMU_DATA_SHARED \ + __EXYNOS5420_TMU_DATA \ + .type = SOC_ARCH_EXYNOS5420_TRIMINFO, \ + .features = (TMU_SUPPORT_EMULATION | TMU_SUPPORT_TRIM_RELOAD | \ + TMU_SUPPORT_FALLING_TRIP | TMU_SUPPORT_READY_STATUS | \ + TMU_SUPPORT_EMUL_TIME | TMU_SUPPORT_ADDRESS_MULTIPLE) + +struct exynos_tmu_init_data const exynos5420_default_tmu_data = { + .tmu_data = { + { EXYNOS5420_TMU_DATA }, + { EXYNOS5420_TMU_DATA }, + { EXYNOS5420_TMU_DATA_SHARED }, + { EXYNOS5420_TMU_DATA_SHARED }, + { EXYNOS5420_TMU_DATA_SHARED }, + }, + .tmu_count = 5, +}; +#endif + #if defined(CONFIG_SOC_EXYNOS5440) static const struct exynos_tmu_registers exynos5440_tmu_registers = { .triminfo_data = EXYNOS5440_TMU_S0_7_TRIM, @@ -217,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, @@ -228,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, @@ -255,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 a1ea19d9e0a..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,17 @@ #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 @@ -119,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 @@ -156,6 +169,20 @@ extern struct exynos_tmu_init_data const exynos5250_default_tmu_data; #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 f1d511a9475..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; } @@ -341,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, @@ -403,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); @@ -450,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++) @@ -774,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); @@ -1052,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. @@ -1060,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; @@ -1091,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); @@ -1133,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. * @@ -1207,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) @@ -1215,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); @@ -1370,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) { @@ -1386,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)) @@ -1471,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); @@ -1490,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) @@ -1740,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: @@ -1757,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 fdb07199d9c..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; @@ -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 5a47cc8c8f8..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; @@ -118,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) @@ -230,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; @@ -245,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) @@ -308,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); @@ -331,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; } @@ -350,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)) @@ -380,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 7722cb9d5a8..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; @@ -394,7 +398,6 @@ 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; @@ -440,13 +443,11 @@ static int pkg_temp_thermal_device_add(unsigned int 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; @@ -589,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 */ @@ -603,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; @@ -616,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 */ @@ -635,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); |
