aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/thermal
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thermal')
-rw-r--r--drivers/thermal/Kconfig124
-rw-r--r--drivers/thermal/Makefile26
-rw-r--r--drivers/thermal/amlogic_thermal.c24
-rw-r--r--drivers/thermal/armada_thermal.c18
-rw-r--r--drivers/thermal/broadcom/bcm2711_thermal.c25
-rw-r--r--drivers/thermal/broadcom/bcm2835_thermal.c15
-rw-r--r--drivers/thermal/broadcom/brcmstb_thermal.c22
-rw-r--r--drivers/thermal/broadcom/ns-thermal.c50
-rw-r--r--drivers/thermal/broadcom/sr-thermal.c19
-rw-r--r--drivers/thermal/clock_cooling.c444
-rw-r--r--drivers/thermal/cpufreq_cooling.c272
-rw-r--r--drivers/thermal/cpuidle_cooling.c100
-rw-r--r--drivers/thermal/da9062-thermal.c25
-rw-r--r--drivers/thermal/db8500_thermal.c42
-rw-r--r--drivers/thermal/devfreq_cooling.c515
-rw-r--r--drivers/thermal/dove_thermal.c6
-rw-r--r--drivers/thermal/gov_bang_bang.c10
-rw-r--r--drivers/thermal/gov_fair_share.c (renamed from drivers/thermal/fair_share.c)16
-rw-r--r--drivers/thermal/gov_power_allocator.c (renamed from drivers/thermal/power_allocator.c)216
-rw-r--r--drivers/thermal/gov_step_wise.c (renamed from drivers/thermal/step_wise.c)50
-rw-r--r--drivers/thermal/gov_user_space.c (renamed from drivers/thermal/user_space.c)15
-rw-r--r--drivers/thermal/hisi_thermal.c46
-rw-r--r--drivers/thermal/imx8mm_thermal.c241
-rw-r--r--drivers/thermal/imx_sc_thermal.c155
-rw-r--r--drivers/thermal/imx_thermal.c234
-rw-r--r--drivers/thermal/intel/Kconfig38
-rw-r--r--drivers/thermal/intel/Makefile4
-rw-r--r--drivers/thermal/intel/int340x_thermal/Kconfig8
-rw-r--r--drivers/thermal/intel/int340x_thermal/Makefile6
-rw-r--r--drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c29
-rw-r--r--drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h48
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3400_thermal.c452
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3401_thermal.c88
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3403_thermal.c7
-rw-r--r--drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c15
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device.c523
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device.h95
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c380
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c158
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c241
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c134
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c300
-rw-r--r--drivers/thermal/intel/intel_hfi.c567
-rw-r--r--drivers/thermal/intel/intel_hfi.h17
-rw-r--r--drivers/thermal/intel/intel_menlow.c523
-rw-r--r--drivers/thermal/intel/intel_pch_thermal.c126
-rw-r--r--drivers/thermal/intel/intel_powerclamp.c55
-rw-r--r--drivers/thermal/intel/intel_quark_dts_thermal.c39
-rw-r--r--drivers/thermal/intel/intel_soc_dts_iosf.c18
-rw-r--r--drivers/thermal/intel/intel_soc_dts_thermal.c3
-rw-r--r--drivers/thermal/intel/intel_tcc_cooling.c134
-rw-r--r--drivers/thermal/intel/therm_throt.c752
-rw-r--r--drivers/thermal/intel/thermal_interrupt.h18
-rw-r--r--drivers/thermal/intel/x86_pkg_temp_thermal.c40
-rw-r--r--drivers/thermal/k3_bandgap.c269
-rw-r--r--drivers/thermal/k3_j72xx_bandgap.c565
-rw-r--r--drivers/thermal/khadas_mcu_fan.c161
-rw-r--r--drivers/thermal/kirkwood_thermal.c7
-rw-r--r--drivers/thermal/max77620_thermal.c8
-rw-r--r--drivers/thermal/mtk_thermal.c265
-rw-r--r--drivers/thermal/of-thermal.c1121
-rw-r--r--drivers/thermal/qcom/Kconfig23
-rw-r--r--drivers/thermal/qcom/Makefile6
-rw-r--r--drivers/thermal/qcom/lmh.c241
-rw-r--r--drivers/thermal/qcom/qcom-spmi-adc-tm5.c1081
-rw-r--r--drivers/thermal/qcom/qcom-spmi-temp-alarm.c112
-rw-r--r--drivers/thermal/qcom/tsens-8960.c237
-rw-r--r--drivers/thermal/qcom/tsens-common.c695
-rw-r--r--drivers/thermal/qcom/tsens-v0_1.c254
-rw-r--r--drivers/thermal/qcom/tsens-v1.c14
-rw-r--r--drivers/thermal/qcom/tsens-v2.c24
-rw-r--r--drivers/thermal/qcom/tsens.c1022
-rw-r--r--drivers/thermal/qcom/tsens.h114
-rw-r--r--drivers/thermal/qoriq_thermal.c78
-rw-r--r--drivers/thermal/rcar_gen3_thermal.c273
-rw-r--r--drivers/thermal/rcar_thermal.c125
-rw-r--r--drivers/thermal/rockchip_thermal.c147
-rw-r--r--drivers/thermal/rzg2l_thermal.c252
-rw-r--r--drivers/thermal/samsung/exynos_tmu.c29
-rw-r--r--drivers/thermal/spear_thermal.c7
-rw-r--r--drivers/thermal/sprd_thermal.c558
-rw-r--r--drivers/thermal/st/Kconfig2
-rw-r--r--drivers/thermal/st/st_thermal.c5
-rw-r--r--drivers/thermal/st/st_thermal_memmap.c17
-rw-r--r--drivers/thermal/st/stm_thermal.c32
-rw-r--r--drivers/thermal/sun8i_thermal.c69
-rw-r--r--drivers/thermal/tango_thermal.c126
-rw-r--r--drivers/thermal/tegra/Kconfig9
-rw-r--r--drivers/thermal/tegra/Makefile1
-rw-r--r--drivers/thermal/tegra/soctherm.c72
-rw-r--r--drivers/thermal/tegra/tegra-bpmp-thermal.c32
-rw-r--r--drivers/thermal/tegra/tegra30-tsensor.c673
-rw-r--r--drivers/thermal/thermal-generic-adc.c10
-rw-r--r--drivers/thermal/thermal_core.c573
-rw-r--r--drivers/thermal/thermal_core.h60
-rw-r--r--drivers/thermal/thermal_helpers.c115
-rw-r--r--drivers/thermal/thermal_hwmon.c17
-rw-r--r--drivers/thermal/thermal_mmio.c24
-rw-r--r--drivers/thermal/thermal_netlink.c704
-rw-r--r--drivers/thermal/thermal_netlink.h118
-rw-r--r--drivers/thermal/thermal_of.c734
-rw-r--r--drivers/thermal/thermal_sysfs.c187
-rw-r--r--drivers/thermal/ti-soc-thermal/omap4-thermal-data.c30
-rw-r--r--drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h14
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-bandgap.c198
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-bandgap.h12
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-thermal-common.c40
-rw-r--r--drivers/thermal/uniphier_thermal.c14
-rw-r--r--drivers/thermal/zx2967_thermal.c256
109 files changed, 13837 insertions, 5523 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 5a05db5438d6..e052dae614eb 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -1,21 +1,31 @@
# SPDX-License-Identifier: GPL-2.0-only
#
-# Generic thermal sysfs drivers configuration
+# Generic thermal drivers configuration
#
menuconfig THERMAL
- bool "Generic Thermal sysfs driver"
+ bool "Thermal drivers"
help
- Generic Thermal Sysfs driver offers a generic mechanism for
+ Thermal drivers offer a generic mechanism for
thermal management. Usually it's made up of one or more thermal
- zone and cooling device.
+ zones and cooling devices.
Each thermal zone contains its own temperature, trip points,
- cooling devices.
- All platforms with ACPI thermal support can use this driver.
+ and cooling devices.
+ All platforms with ACPI or Open Firmware thermal support can use
+ this driver.
If you want this support, you should say Y here.
if THERMAL
+config THERMAL_NETLINK
+ bool "Thermal netlink management"
+ depends on NET
+ help
+ The thermal framework has a netlink interface to do thermal
+ zones discovery, temperature readings and events such as
+ trip point crossed, cooling device update or governor
+ change. It is recommended to enable the feature.
+
config THERMAL_STATISTICS
bool "Thermal state transition statistics"
help
@@ -103,8 +113,10 @@ config THERMAL_DEFAULT_GOV_USER_SPACE
bool "user_space"
select THERMAL_GOV_USER_SPACE
help
- Select this if you want to let the user space manage the
- platform thermals.
+ The Userspace governor allows to get trip point crossed
+ notification from the kernel via uevents. It is recommended
+ to use the netlink interface instead which gives richer
+ information about the thermal framework events.
config THERMAL_DEFAULT_GOV_POWER_ALLOCATOR
bool "power_allocator"
@@ -179,16 +191,6 @@ config CPU_IDLE_THERMAL
idle cycle.
endif
-config CLOCK_THERMAL
- bool "Generic clock cooling support"
- depends on COMMON_CLK
- depends on PM_OPP
- help
- This entry implements the generic clock cooling mechanism through
- frequency clipping. Typically used to cool off co-processors. The
- device that is configured to use this cooling mechanism will be
- controlled to reduce clock frequency whenever temperature is high.
-
config DEVFREQ_THERMAL
bool "Generic device cooling support"
depends on PM_DEVFREQ
@@ -219,7 +221,7 @@ config THERMAL_EMULATION
config THERMAL_MMIO
tristate "Generic Thermal MMIO driver"
- depends on OF || COMPILE_TEST
+ depends on OF
depends on HAS_IOMEM
help
This option enables the generic thermal MMIO driver that will use
@@ -251,6 +253,37 @@ config IMX_THERMAL
cpufreq is used as the cooling device to throttle CPUs when the
passive trip is crossed.
+config IMX_SC_THERMAL
+ tristate "Temperature sensor driver for NXP i.MX SoCs with System Controller"
+ depends on IMX_SCU
+ depends on OF
+ help
+ Support for Temperature Monitor (TEMPMON) found on NXP i.MX SoCs with
+ system controller inside, Linux kernel has to communicate with system
+ controller via MU (message unit) IPC to get temperature from thermal
+ sensor. It supports one critical trip point and one
+ passive trip point for each thermal sensor.
+
+config IMX8MM_THERMAL
+ tristate "Temperature sensor driver for Freescale i.MX8MM SoC"
+ depends on ARCH_MXC || COMPILE_TEST
+ depends on OF
+ help
+ Support for Thermal Monitoring Unit (TMU) found on Freescale i.MX8MM SoC.
+ It supports one critical trip point and one passive trip point. The
+ cpufreq is used as the cooling device to throttle CPUs when the passive
+ trip is crossed.
+
+config K3_THERMAL
+ tristate "Texas Instruments K3 thermal support"
+ depends on ARCH_K3 || COMPILE_TEST
+ help
+ If you say yes here you get thermal support for the Texas Instruments
+ K3 SoC family. The current chip supported is:
+ - AM654
+
+ This includes temperature reading functionality.
+
config MAX77620_THERMAL
tristate "Temperature sensor driver for Maxim MAX77620 PMIC"
depends on MFD_MAX77620
@@ -263,8 +296,9 @@ config MAX77620_THERMAL
config QORIQ_THERMAL
tristate "QorIQ Thermal Monitoring Unit"
- depends on THERMAL_OF
- depends on HAS_IOMEM
+ depends on THERMAL_OF && HAS_IOMEM
+ depends on PPC_E500MC || SOC_LS1021A || ARCH_LAYERSCAPE || (ARCH_MXC && ARM64) || COMPILE_TEST
+ select REGMAP_MMIO
help
Support for Thermal Monitoring Unit (TMU) found on QorIQ platforms.
It supports one critical trip point and one passive trip point. The
@@ -314,12 +348,21 @@ config RCAR_THERMAL
thermal framework.
config RCAR_GEN3_THERMAL
- tristate "Renesas R-Car Gen3 thermal driver"
+ tristate "Renesas R-Car Gen3 and RZ/G2 thermal driver"
depends on ARCH_RENESAS || COMPILE_TEST
depends on HAS_IOMEM
depends on OF
help
- Enable this to plug the R-Car Gen3 thermal sensor driver into the Linux
+ Enable this to plug the R-Car Gen3 or RZ/G2 thermal sensor driver into
+ the Linux thermal framework.
+
+config RZG2L_THERMAL
+ tristate "Renesas RZ/G2L thermal driver"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ Enable this to plug the RZ/G2L thermal sensor driver into the Linux
thermal framework.
config KIRKWOOD_THERMAL
@@ -418,15 +461,6 @@ depends on (ARCH_STI || ARCH_STM32) && OF
source "drivers/thermal/st/Kconfig"
endmenu
-config TANGO_THERMAL
- tristate "Tango thermal management"
- depends on ARCH_TANGO || COMPILE_TEST
- help
- Enable the Tango thermal driver, which supports the primitive
- temperature sensor embedded in Tango chips since the SMP8758.
- This sensor only generates a 1-bit signal to indicate whether
- the die temperature exceeds a programmable threshold.
-
source "drivers/thermal/tegra/Kconfig"
config GENERIC_ADC_THERMAL
@@ -444,14 +478,6 @@ depends on (ARCH_QCOM && OF) || COMPILE_TEST
source "drivers/thermal/qcom/Kconfig"
endmenu
-config ZX2967_THERMAL
- tristate "Thermal sensors on zx2967 SoC"
- depends on ARCH_ZX || COMPILE_TEST
- help
- Enable the zx2967 thermal sensors driver, which supports
- the primitive temperature sensor embedded in zx2967 SoCs.
- This sensor generates the real time die temperature.
-
config UNIPHIER_THERMAL
tristate "Socionext UniPhier thermal driver"
depends on ARCH_UNIPHIER || COMPILE_TEST
@@ -460,4 +486,22 @@ config UNIPHIER_THERMAL
Enable this to plug in UniPhier on-chip PVT thermal driver into the
thermal framework. The driver supports CPU thermal zone temperature
reporting and a couple of trip points.
+
+config SPRD_THERMAL
+ tristate "Temperature sensor on Spreadtrum SoCs"
+ depends on ARCH_SPRD || COMPILE_TEST
+ help
+ Support for the Spreadtrum thermal sensor driver in the Linux thermal
+ framework.
+
+config KHADAS_MCU_FAN_THERMAL
+ tristate "Khadas MCU controller FAN cooling support"
+ depends on OF
+ depends on MFD_KHADAS_MCU
+ select MFD_CORE
+ select REGMAP
+ help
+ If you say yes here you get support for the FAN controlled
+ by the Microcontroller found on the Khadas VIM boards.
+
endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 9fb88e26fb10..2506c6c8ca83 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -7,27 +7,28 @@ obj-$(CONFIG_THERMAL) += thermal_sys.o
thermal_sys-y += thermal_core.o thermal_sysfs.o \
thermal_helpers.o
+# netlink interface to manage the thermal framework
+thermal_sys-$(CONFIG_THERMAL_NETLINK) += thermal_netlink.o
+
# interface to/from other layers providing sensors
thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o
-thermal_sys-$(CONFIG_THERMAL_OF) += of-thermal.o
+thermal_sys-$(CONFIG_THERMAL_OF) += thermal_of.o
# governors
-thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o
+thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += gov_fair_share.o
thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG) += gov_bang_bang.o
-thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o
-thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o
-thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += power_allocator.o
+thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += gov_step_wise.o
+thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += gov_user_space.o
+thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += gov_power_allocator.o
# cpufreq cooling
thermal_sys-$(CONFIG_CPU_FREQ_THERMAL) += cpufreq_cooling.o
thermal_sys-$(CONFIG_CPU_IDLE_THERMAL) += cpuidle_cooling.o
-# clock cooling
-thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o
-
# devfreq cooling
thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
+obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
# platform thermal drivers
obj-y += broadcom/
obj-$(CONFIG_THERMAL_MMIO) += thermal_mmio.o
@@ -36,24 +37,27 @@ obj-$(CONFIG_SUN8I_THERMAL) += sun8i_thermal.o
obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
obj-$(CONFIG_RCAR_GEN3_THERMAL) += rcar_gen3_thermal.o
+obj-$(CONFIG_RZG2L_THERMAL) += rzg2l_thermal.o
obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o
obj-y += samsung/
obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o
obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o
obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o
-obj-$(CONFIG_TANGO_THERMAL) += tango_thermal.o
obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o
+obj-$(CONFIG_IMX_SC_THERMAL) += imx_sc_thermal.o
+obj-$(CONFIG_IMX8MM_THERMAL) += imx8mm_thermal.o
obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o
obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o
obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o
obj-y += intel/
obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
obj-y += st/
-obj-$(CONFIG_QCOM_TSENS) += qcom/
+obj-y += qcom/
obj-y += tegra/
obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
-obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o
obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o
obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o
+obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o
+obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o
diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c
index ccb1fe18e993..d30cb791e63c 100644
--- a/drivers/thermal/amlogic_thermal.c
+++ b/drivers/thermal/amlogic_thermal.c
@@ -29,6 +29,7 @@
#include <linux/thermal.h>
#include "thermal_core.h"
+#include "thermal_hwmon.h"
#define TSENSOR_CFG_REG1 0x4
#define TSENSOR_CFG_REG1_RSET_VBG BIT(12)
@@ -178,12 +179,12 @@ static int amlogic_thermal_disable(struct amlogic_thermal *data)
return 0;
}
-static int amlogic_thermal_get_temp(void *data, int *temp)
+static int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
unsigned int tval;
- struct amlogic_thermal *pdata = data;
+ struct amlogic_thermal *pdata = tz->devdata;
- if (!data)
+ if (!pdata)
return -EINVAL;
regmap_read(pdata->regmap, TSENSOR_STAT0, &tval);
@@ -194,7 +195,7 @@ static int amlogic_thermal_get_temp(void *data, int *temp)
return 0;
}
-static const struct thermal_zone_of_device_ops amlogic_thermal_ops = {
+static const struct thermal_zone_device_ops amlogic_thermal_ops = {
.get_temp = amlogic_thermal_get_temp,
};
@@ -253,10 +254,8 @@ static int amlogic_thermal_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, pdata);
base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(base)) {
- dev_err(dev, "failed to get io address\n");
+ if (IS_ERR(base))
return PTR_ERR(base);
- }
pdata->regmap = devm_regmap_init_mmio(dev, base,
pdata->data->regmap_config);
@@ -277,16 +276,19 @@ static int amlogic_thermal_probe(struct platform_device *pdev)
return PTR_ERR(pdata->sec_ao_map);
}
- pdata->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
- 0,
- pdata,
- &amlogic_thermal_ops);
+ pdata->tzd = devm_thermal_of_zone_register(&pdev->dev,
+ 0,
+ pdata,
+ &amlogic_thermal_ops);
if (IS_ERR(pdata->tzd)) {
ret = PTR_ERR(pdata->tzd);
dev_err(dev, "Failed to register tsensor: %d\n", ret);
return ret;
}
+ if (devm_thermal_add_hwmon_sysfs(pdata->tzd))
+ dev_warn(&pdev->dev, "Failed to add hwmon sysfs attributes\n");
+
ret = amlogic_thermal_initialize(pdata);
if (ret)
return ret;
diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c
index 7c447cd149e7..52d63b3997fe 100644
--- a/drivers/thermal/armada_thermal.c
+++ b/drivers/thermal/armada_thermal.c
@@ -420,9 +420,9 @@ static struct thermal_zone_device_ops legacy_ops = {
.get_temp = armada_get_temp_legacy,
};
-static int armada_get_temp(void *_sensor, int *temp)
+static int armada_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct armada_thermal_sensor *sensor = _sensor;
+ struct armada_thermal_sensor *sensor = tz->devdata;
struct armada_thermal_priv *priv = sensor->priv;
int ret;
@@ -450,7 +450,7 @@ unlock_mutex:
return ret;
}
-static const struct thermal_zone_of_device_ops of_ops = {
+static const struct thermal_zone_device_ops of_ops = {
.get_temp = armada_get_temp,
};
@@ -874,6 +874,12 @@ static int armada_thermal_probe(struct platform_device *pdev)
return PTR_ERR(tz);
}
+ ret = thermal_zone_device_enable(tz);
+ if (ret) {
+ thermal_zone_device_unregister(tz);
+ return ret;
+ }
+
drvdata->type = LEGACY;
drvdata->data.tz = tz;
platform_set_drvdata(pdev, drvdata);
@@ -922,9 +928,9 @@ static int armada_thermal_probe(struct platform_device *pdev)
/* Register the sensor */
sensor->priv = priv;
sensor->id = sensor_id;
- tz = devm_thermal_zone_of_sensor_register(&pdev->dev,
- sensor->id, sensor,
- &of_ops);
+ tz = devm_thermal_of_zone_register(&pdev->dev,
+ sensor->id, sensor,
+ &of_ops);
if (IS_ERR(tz)) {
dev_info(&pdev->dev, "Thermal sensor %d unavailable\n",
sensor_id);
diff --git a/drivers/thermal/broadcom/bcm2711_thermal.c b/drivers/thermal/broadcom/bcm2711_thermal.c
index 67c2a737bc9d..1f8651d15160 100644
--- a/drivers/thermal/broadcom/bcm2711_thermal.c
+++ b/drivers/thermal/broadcom/bcm2711_thermal.c
@@ -31,14 +31,13 @@ struct bcm2711_thermal_priv {
struct thermal_zone_device *thermal;
};
-static int bcm2711_get_temp(void *data, int *temp)
+static int bcm2711_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct bcm2711_thermal_priv *priv = data;
- int slope = thermal_zone_get_slope(priv->thermal);
- int offset = thermal_zone_get_offset(priv->thermal);
+ struct bcm2711_thermal_priv *priv = tz->devdata;
+ int slope = thermal_zone_get_slope(tz);
+ int offset = thermal_zone_get_offset(tz);
u32 val;
int ret;
- long t;
ret = regmap_read(priv->regmap, AVS_RO_TEMP_STATUS, &val);
if (ret)
@@ -50,14 +49,12 @@ static int bcm2711_get_temp(void *data, int *temp)
val &= AVS_RO_TEMP_STATUS_DATA_MSK;
/* Convert a HW code to a temperature reading (millidegree celsius) */
- t = slope * val + offset;
-
- *temp = t < 0 ? 0 : t;
+ *temp = slope * val + offset;
return 0;
}
-static const struct thermal_zone_of_device_ops bcm2711_thermal_of_ops = {
+static const struct thermal_zone_device_ops bcm2711_thermal_of_ops = {
.get_temp = bcm2711_get_temp,
};
@@ -91,8 +88,8 @@ static int bcm2711_thermal_probe(struct platform_device *pdev)
}
priv->regmap = regmap;
- thermal = devm_thermal_zone_of_sensor_register(dev, 0, priv,
- &bcm2711_thermal_of_ops);
+ thermal = devm_thermal_of_zone_register(dev, 0, priv,
+ &bcm2711_thermal_of_ops);
if (IS_ERR(thermal)) {
ret = PTR_ERR(thermal);
dev_err(dev, "could not register sensor: %d\n", ret);
@@ -102,11 +99,7 @@ static int bcm2711_thermal_probe(struct platform_device *pdev)
priv->thermal = thermal;
thermal->tzp->no_hwmon = false;
- ret = thermal_add_hwmon_sysfs(thermal);
- if (ret)
- return ret;
-
- return 0;
+ return thermal_add_hwmon_sysfs(thermal);
}
static struct platform_driver bcm2711_thermal_driver = {
diff --git a/drivers/thermal/broadcom/bcm2835_thermal.c b/drivers/thermal/broadcom/bcm2835_thermal.c
index 3199977f1e73..2c67841a1115 100644
--- a/drivers/thermal/broadcom/bcm2835_thermal.c
+++ b/drivers/thermal/broadcom/bcm2835_thermal.c
@@ -88,9 +88,9 @@ static int bcm2835_thermal_temp2adc(int temp, int offset, int slope)
return temp;
}
-static int bcm2835_thermal_get_temp(void *d, int *temp)
+static int bcm2835_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct bcm2835_thermal_data *data = d;
+ struct bcm2835_thermal_data *data = tz->devdata;
u32 val = readl(data->regs + BCM2835_TS_TSENSSTAT);
if (!(val & BCM2835_TS_TSENSSTAT_VALID))
@@ -135,7 +135,7 @@ static void bcm2835_thermal_debugfs(struct platform_device *pdev)
debugfs_create_regset32("regset", 0444, data->debugfsdir, regset);
}
-static const struct thermal_zone_of_device_ops bcm2835_thermal_ops = {
+static const struct thermal_zone_device_ops bcm2835_thermal_ops = {
.get_temp = bcm2835_thermal_get_temp,
};
@@ -184,7 +184,6 @@ static int bcm2835_thermal_probe(struct platform_device *pdev)
data->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->regs)) {
err = PTR_ERR(data->regs);
- dev_err(&pdev->dev, "Could not get registers: %d\n", err);
return err;
}
@@ -207,8 +206,8 @@ static int bcm2835_thermal_probe(struct platform_device *pdev)
data->clk, rate);
/* register of thermal sensor and get info from DT */
- tz = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
- &bcm2835_thermal_ops);
+ tz = devm_thermal_of_zone_register(&pdev->dev, 0, data,
+ &bcm2835_thermal_ops);
if (IS_ERR(tz)) {
err = PTR_ERR(tz);
dev_err(&pdev->dev,
@@ -278,7 +277,7 @@ static int bcm2835_thermal_probe(struct platform_device *pdev)
return 0;
err_tz:
- thermal_zone_of_sensor_unregister(&pdev->dev, tz);
+ thermal_of_zone_unregister(tz);
err_clk:
clk_disable_unprepare(data->clk);
@@ -291,7 +290,7 @@ static int bcm2835_thermal_remove(struct platform_device *pdev)
struct thermal_zone_device *tz = data->tz;
debugfs_remove_recursive(data->debugfsdir);
- thermal_zone_of_sensor_unregister(&pdev->dev, tz);
+ thermal_of_zone_unregister(tz);
clk_disable_unprepare(data->clk);
return 0;
diff --git a/drivers/thermal/broadcom/brcmstb_thermal.c b/drivers/thermal/broadcom/brcmstb_thermal.c
index 8df5edef1ded..c79c6cfdd74d 100644
--- a/drivers/thermal/broadcom/brcmstb_thermal.c
+++ b/drivers/thermal/broadcom/brcmstb_thermal.c
@@ -105,7 +105,7 @@ static struct avs_tmon_trip avs_tmon_trips[] = {
struct brcmstb_thermal_params {
unsigned int offset;
unsigned int mult;
- const struct thermal_zone_of_device_ops *of_ops;
+ const struct thermal_zone_device_ops *of_ops;
};
struct brcmstb_thermal_priv {
@@ -150,9 +150,9 @@ static inline u32 avs_tmon_temp_to_code(struct brcmstb_thermal_priv *priv,
return (u32)((offset - temp) / mult);
}
-static int brcmstb_get_temp(void *data, int *temp)
+static int brcmstb_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct brcmstb_thermal_priv *priv = data;
+ struct brcmstb_thermal_priv *priv = tz->devdata;
u32 val;
long t;
@@ -260,9 +260,9 @@ static irqreturn_t brcmstb_tmon_irq_thread(int irq, void *data)
return IRQ_HANDLED;
}
-static int brcmstb_set_trips(void *data, int low, int high)
+static int brcmstb_set_trips(struct thermal_zone_device *tz, int low, int high)
{
- struct brcmstb_thermal_priv *priv = data;
+ struct brcmstb_thermal_priv *priv = tz->devdata;
dev_dbg(priv->dev, "set trips %d <--> %d\n", low, high);
@@ -288,7 +288,7 @@ static int brcmstb_set_trips(void *data, int low, int high)
return 0;
}
-static const struct thermal_zone_of_device_ops brcmstb_16nm_of_ops = {
+static const struct thermal_zone_device_ops brcmstb_16nm_of_ops = {
.get_temp = brcmstb_get_temp,
};
@@ -298,7 +298,7 @@ static const struct brcmstb_thermal_params brcmstb_16nm_params = {
.of_ops = &brcmstb_16nm_of_ops,
};
-static const struct thermal_zone_of_device_ops brcmstb_28nm_of_ops = {
+static const struct thermal_zone_device_ops brcmstb_28nm_of_ops = {
.get_temp = brcmstb_get_temp,
.set_trips = brcmstb_set_trips,
};
@@ -318,7 +318,7 @@ MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table);
static int brcmstb_thermal_probe(struct platform_device *pdev)
{
- const struct thermal_zone_of_device_ops *of_ops;
+ const struct thermal_zone_device_ops *of_ops;
struct thermal_zone_device *thermal;
struct brcmstb_thermal_priv *priv;
struct resource *res;
@@ -341,8 +341,8 @@ static int brcmstb_thermal_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, priv);
of_ops = priv->temp_params->of_ops;
- thermal = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv,
- of_ops);
+ thermal = devm_thermal_of_zone_register(&pdev->dev, 0, priv,
+ of_ops);
if (IS_ERR(thermal)) {
ret = PTR_ERR(thermal);
dev_err(&pdev->dev, "could not register sensor: %d\n", ret);
@@ -351,7 +351,7 @@ static int brcmstb_thermal_probe(struct platform_device *pdev)
priv->thermal = thermal;
- irq = platform_get_irq(pdev, 0);
+ irq = platform_get_irq_optional(pdev, 0);
if (irq >= 0) {
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
brcmstb_tmon_irq_thread,
diff --git a/drivers/thermal/broadcom/ns-thermal.c b/drivers/thermal/broadcom/ns-thermal.c
index c9468ba9d449..07a8a3f49bd0 100644
--- a/drivers/thermal/broadcom/ns-thermal.c
+++ b/drivers/thermal/broadcom/ns-thermal.c
@@ -14,19 +14,14 @@
#define PVTMON_CONTROL0_SEL_TEST_MODE 0x0000000e
#define PVTMON_STATUS 0x08
-struct ns_thermal {
- struct thermal_zone_device *tz;
- void __iomem *pvtmon;
-};
-
-static int ns_thermal_get_temp(void *data, int *temp)
+static int ns_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct ns_thermal *ns_thermal = data;
- int offset = thermal_zone_get_offset(ns_thermal->tz);
- int slope = thermal_zone_get_slope(ns_thermal->tz);
+ void __iomem *pvtmon = tz->devdata;
+ int offset = thermal_zone_get_offset(tz);
+ int slope = thermal_zone_get_slope(tz);
u32 val;
- val = readl(ns_thermal->pvtmon + PVTMON_CONTROL0);
+ val = readl(pvtmon + PVTMON_CONTROL0);
if ((val & PVTMON_CONTROL0_SEL_MASK) != PVTMON_CONTROL0_SEL_TEMP_MONITOR) {
/* Clear current mode selection */
val &= ~PVTMON_CONTROL0_SEL_MASK;
@@ -34,50 +29,47 @@ static int ns_thermal_get_temp(void *data, int *temp)
/* Set temp monitor mode (it's the default actually) */
val |= PVTMON_CONTROL0_SEL_TEMP_MONITOR;
- writel(val, ns_thermal->pvtmon + PVTMON_CONTROL0);
+ writel(val, pvtmon + PVTMON_CONTROL0);
}
- val = readl(ns_thermal->pvtmon + PVTMON_STATUS);
+ val = readl(pvtmon + PVTMON_STATUS);
*temp = slope * val + offset;
return 0;
}
-static const struct thermal_zone_of_device_ops ns_thermal_ops = {
+static const struct thermal_zone_device_ops ns_thermal_ops = {
.get_temp = ns_thermal_get_temp,
};
static int ns_thermal_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct ns_thermal *ns_thermal;
-
- ns_thermal = devm_kzalloc(dev, sizeof(*ns_thermal), GFP_KERNEL);
- if (!ns_thermal)
- return -ENOMEM;
+ struct thermal_zone_device *tz;
+ void __iomem *pvtmon;
- ns_thermal->pvtmon = of_iomap(dev_of_node(dev), 0);
- if (WARN_ON(!ns_thermal->pvtmon))
+ pvtmon = of_iomap(dev_of_node(dev), 0);
+ if (WARN_ON(!pvtmon))
return -ENOENT;
- ns_thermal->tz = devm_thermal_zone_of_sensor_register(dev, 0,
- ns_thermal,
- &ns_thermal_ops);
- if (IS_ERR(ns_thermal->tz)) {
- iounmap(ns_thermal->pvtmon);
- return PTR_ERR(ns_thermal->tz);
+ tz = devm_thermal_of_zone_register(dev, 0,
+ pvtmon,
+ &ns_thermal_ops);
+ if (IS_ERR(tz)) {
+ iounmap(pvtmon);
+ return PTR_ERR(tz);
}
- platform_set_drvdata(pdev, ns_thermal);
+ platform_set_drvdata(pdev, pvtmon);
return 0;
}
static int ns_thermal_remove(struct platform_device *pdev)
{
- struct ns_thermal *ns_thermal = platform_get_drvdata(pdev);
+ void __iomem *pvtmon = platform_get_drvdata(pdev);
- iounmap(ns_thermal->pvtmon);
+ iounmap(pvtmon);
return 0;
}
diff --git a/drivers/thermal/broadcom/sr-thermal.c b/drivers/thermal/broadcom/sr-thermal.c
index 475ce2900771..2b93502543ff 100644
--- a/drivers/thermal/broadcom/sr-thermal.c
+++ b/drivers/thermal/broadcom/sr-thermal.c
@@ -19,7 +19,6 @@
#define SR_TMON_MAX_LIST 6
struct sr_tmon {
- struct thermal_zone_device *tz;
unsigned int crit_temp;
unsigned int tmon_id;
struct sr_thermal *priv;
@@ -31,9 +30,9 @@ struct sr_thermal {
struct sr_tmon tmon[SR_TMON_MAX_LIST];
};
-static int sr_get_temp(void *data, int *temp)
+static int sr_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct sr_tmon *tmon = data;
+ struct sr_tmon *tmon = tz->devdata;
struct sr_thermal *sr_thermal = tmon->priv;
*temp = readl(sr_thermal->regs + SR_TMON_TEMP_BASE(tmon->tmon_id));
@@ -41,13 +40,14 @@ static int sr_get_temp(void *data, int *temp)
return 0;
}
-static const struct thermal_zone_of_device_ops sr_tz_ops = {
+static const struct thermal_zone_device_ops sr_tz_ops = {
.get_temp = sr_get_temp,
};
static int sr_thermal_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ struct thermal_zone_device *tz;
struct sr_thermal *sr_thermal;
struct sr_tmon *tmon;
struct resource *res;
@@ -60,6 +60,9 @@ static int sr_thermal_probe(struct platform_device *pdev)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+
sr_thermal->regs = (void __iomem *)devm_memremap(&pdev->dev, res->start,
resource_size(res),
MEMREMAP_WB);
@@ -81,10 +84,10 @@ static int sr_thermal_probe(struct platform_device *pdev)
writel(0, sr_thermal->regs + SR_TMON_TEMP_BASE(i));
tmon->tmon_id = i;
tmon->priv = sr_thermal;
- tmon->tz = devm_thermal_zone_of_sensor_register(dev, i, tmon,
- &sr_tz_ops);
- if (IS_ERR(tmon->tz))
- return PTR_ERR(tmon->tz);
+ tz = devm_thermal_of_zone_register(dev, i, tmon,
+ &sr_tz_ops);
+ if (IS_ERR(tz))
+ return PTR_ERR(tz);
dev_dbg(dev, "thermal sensor %d registered\n", i);
}
diff --git a/drivers/thermal/clock_cooling.c b/drivers/thermal/clock_cooling.c
deleted file mode 100644
index 7cb3ae4b44ee..000000000000
--- a/drivers/thermal/clock_cooling.c
+++ /dev/null
@@ -1,444 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * drivers/thermal/clock_cooling.c
- *
- * Copyright (C) 2014 Eduardo Valentin <edubezval@gmail.com>
- *
- * Copyright (C) 2013 Texas Instruments Inc.
- * Contact: Eduardo Valentin <eduardo.valentin@ti.com>
- *
- * Highly based on cpufreq_cooling.c.
- * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
- * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
- */
-#include <linux/clk.h>
-#include <linux/cpufreq.h>
-#include <linux/device.h>
-#include <linux/err.h>
-#include <linux/idr.h>
-#include <linux/mutex.h>
-#include <linux/pm_opp.h>
-#include <linux/slab.h>
-#include <linux/thermal.h>
-#include <linux/clock_cooling.h>
-
-/**
- * struct clock_cooling_device - data for cooling device with clock
- * @id: unique integer value corresponding to each clock_cooling_device
- * registered.
- * @dev: struct device pointer to the device being used to cool off using
- * clock frequencies.
- * @cdev: thermal_cooling_device pointer to keep track of the
- * registered cooling device.
- * @clk_rate_change_nb: reference to notifier block used to receive clock
- * rate changes.
- * @freq_table: frequency table used to keep track of available frequencies.
- * @clock_state: integer value representing the current state of clock
- * cooling devices.
- * @clock_val: integer value representing the absolute value of the clipped
- * frequency.
- * @clk: struct clk reference used to enforce clock limits.
- * @lock: mutex lock to protect this struct.
- *
- * This structure is required for keeping information of each
- * clock_cooling_device registered. In order to prevent corruption of this a
- * mutex @lock is used.
- */
-struct clock_cooling_device {
- int id;
- struct device *dev;
- struct thermal_cooling_device *cdev;
- struct notifier_block clk_rate_change_nb;
- struct cpufreq_frequency_table *freq_table;
- unsigned long clock_state;
- unsigned long clock_val;
- struct clk *clk;
- struct mutex lock; /* lock to protect the content of this struct */
-};
-#define to_clock_cooling_device(x) \
- container_of(x, struct clock_cooling_device, clk_rate_change_nb)
-static DEFINE_IDA(clock_ida);
-
-/* Below code defines functions to be used for clock as cooling device */
-
-enum clock_cooling_property {
- GET_LEVEL,
- GET_FREQ,
- GET_MAXL,
-};
-
-/**
- * clock_cooling_get_property - fetch a property of interest for a give cpu.
- * @ccdev: clock cooling device reference
- * @input: query parameter
- * @output: query return
- * @property: type of query (frequency, level, max level)
- *
- * This is the common function to
- * 1. get maximum clock cooling states
- * 2. translate frequency to cooling state
- * 3. translate cooling state to frequency
- * Note that the code may be not in good shape
- * but it is written in this way in order to:
- * a) reduce duplicate code as most of the code can be shared.
- * b) make sure the logic is consistent when translating between
- * cooling states and frequencies.
- *
- * Return: 0 on success, -EINVAL when invalid parameters are passed.
- */
-static int clock_cooling_get_property(struct clock_cooling_device *ccdev,
- unsigned long input,
- unsigned long *output,
- enum clock_cooling_property property)
-{
- int i;
- unsigned long max_level = 0, level = 0;
- unsigned int freq = CPUFREQ_ENTRY_INVALID;
- int descend = -1;
- struct cpufreq_frequency_table *pos, *table = ccdev->freq_table;
-
- if (!output)
- return -EINVAL;
-
- if (!table)
- return -EINVAL;
-
- cpufreq_for_each_valid_entry(pos, table) {
- /* ignore duplicate entry */
- if (freq == pos->frequency)
- continue;
-
- /* get the frequency order */
- if (freq != CPUFREQ_ENTRY_INVALID && descend == -1)
- descend = freq > pos->frequency;
-
- freq = pos->frequency;
- max_level++;
- }
-
- /* No valid cpu frequency entry */
- if (max_level == 0)
- return -EINVAL;
-
- /* max_level is an index, not a counter */
- max_level--;
-
- /* get max level */
- if (property == GET_MAXL) {
- *output = max_level;
- return 0;
- }
-
- if (property == GET_FREQ)
- level = descend ? input : (max_level - input);
-
- i = 0;
- cpufreq_for_each_valid_entry(pos, table) {
- /* ignore duplicate entry */
- if (freq == pos->frequency)
- continue;
-
- /* now we have a valid frequency entry */
- freq = pos->frequency;
-
- if (property == GET_LEVEL && (unsigned int)input == freq) {
- /* get level by frequency */
- *output = descend ? i : (max_level - i);
- return 0;
- }
- if (property == GET_FREQ && level == i) {
- /* get frequency by level */
- *output = freq;
- return 0;
- }
- i++;
- }
-
- return -EINVAL;
-}
-
-/**
- * clock_cooling_get_level - return the cooling level of given clock cooling.
- * @cdev: reference of a thermal cooling device of used as clock cooling device
- * @freq: the frequency of interest
- *
- * This function will match the cooling level corresponding to the
- * requested @freq and return it.
- *
- * Return: The matched cooling level on success or THERMAL_CSTATE_INVALID
- * otherwise.
- */
-unsigned long clock_cooling_get_level(struct thermal_cooling_device *cdev,
- unsigned long freq)
-{
- struct clock_cooling_device *ccdev = cdev->devdata;
- unsigned long val;
-
- if (clock_cooling_get_property(ccdev, (unsigned long)freq, &val,
- GET_LEVEL))
- return THERMAL_CSTATE_INVALID;
-
- return val;
-}
-EXPORT_SYMBOL_GPL(clock_cooling_get_level);
-
-/**
- * clock_cooling_get_frequency - get the absolute value of frequency from level.
- * @ccdev: clock cooling device reference
- * @level: cooling level
- *
- * This function matches cooling level with frequency. Based on a cooling level
- * of frequency, equals cooling state of cpu cooling device, it will return
- * the corresponding frequency.
- * e.g level=0 --> 1st MAX FREQ, level=1 ---> 2nd MAX FREQ, .... etc
- *
- * Return: 0 on error, the corresponding frequency otherwise.
- */
-static unsigned long
-clock_cooling_get_frequency(struct clock_cooling_device *ccdev,
- unsigned long level)
-{
- int ret = 0;
- unsigned long freq;
-
- ret = clock_cooling_get_property(ccdev, level, &freq, GET_FREQ);
- if (ret)
- return 0;
-
- return freq;
-}
-
-/**
- * clock_cooling_apply - function to apply frequency clipping.
- * @ccdev: clock_cooling_device pointer containing frequency clipping data.
- * @cooling_state: value of the cooling state.
- *
- * Function used to make sure the clock layer is aware of current thermal
- * limits. The limits are applied by updating the clock rate in case it is
- * higher than the corresponding frequency based on the requested cooling_state.
- *
- * Return: 0 on success, an error code otherwise (-EINVAL in case wrong
- * cooling state).
- */
-static int clock_cooling_apply(struct clock_cooling_device *ccdev,
- unsigned long cooling_state)
-{
- unsigned long clip_freq, cur_freq;
- int ret = 0;
-
- /* Here we write the clipping */
- /* Check if the old cooling action is same as new cooling action */
- if (ccdev->clock_state == cooling_state)
- return 0;
-
- clip_freq = clock_cooling_get_frequency(ccdev, cooling_state);
- if (!clip_freq)
- return -EINVAL;
-
- cur_freq = clk_get_rate(ccdev->clk);
-
- mutex_lock(&ccdev->lock);
- ccdev->clock_state = cooling_state;
- ccdev->clock_val = clip_freq;
- /* enforce clock level */
- if (cur_freq > clip_freq)
- ret = clk_set_rate(ccdev->clk, clip_freq);
- mutex_unlock(&ccdev->lock);
-
- return ret;
-}
-
-/**
- * clock_cooling_clock_notifier - notifier callback on clock rate changes.
- * @nb: struct notifier_block * with callback info.
- * @event: value showing clock event for which this function invoked.
- * @data: callback-specific data
- *
- * Callback to hijack the notification on clock transition.
- * Every time there is a clock change, we intercept all pre change events
- * and block the transition in case the new rate infringes thermal limits.
- *
- * Return: NOTIFY_DONE (success) or NOTIFY_BAD (new_rate > thermal limit).
- */
-static int clock_cooling_clock_notifier(struct notifier_block *nb,
- unsigned long event, void *data)
-{
- struct clk_notifier_data *ndata = data;
- struct clock_cooling_device *ccdev = to_clock_cooling_device(nb);
-
- switch (event) {
- case PRE_RATE_CHANGE:
- /*
- * checks on current state
- * TODO: current method is not best we can find as it
- * allows possibly voltage transitions, in case DVFS
- * layer is also hijacking clock pre notifications.
- */
- if (ndata->new_rate > ccdev->clock_val)
- return NOTIFY_BAD;
- /* fall through */
- case POST_RATE_CHANGE:
- case ABORT_RATE_CHANGE:
- default:
- return NOTIFY_DONE;
- }
-}
-
-/* clock cooling device thermal callback functions are defined below */
-
-/**
- * clock_cooling_get_max_state - callback function to get the max cooling state.
- * @cdev: thermal cooling device pointer.
- * @state: fill this variable with the max cooling state.
- *
- * Callback for the thermal cooling device to return the clock
- * max cooling state.
- *
- * Return: 0 on success, an error code otherwise.
- */
-static int clock_cooling_get_max_state(struct thermal_cooling_device *cdev,
- unsigned long *state)
-{
- struct clock_cooling_device *ccdev = cdev->devdata;
- unsigned long count = 0;
- int ret;
-
- ret = clock_cooling_get_property(ccdev, 0, &count, GET_MAXL);
- if (!ret)
- *state = count;
-
- return ret;
-}
-
-/**
- * clock_cooling_get_cur_state - function to get the current cooling state.
- * @cdev: thermal cooling device pointer.
- * @state: fill this variable with the current cooling state.
- *
- * Callback for the thermal cooling device to return the clock
- * current cooling state.
- *
- * Return: 0 (success)
- */
-static int clock_cooling_get_cur_state(struct thermal_cooling_device *cdev,
- unsigned long *state)
-{
- struct clock_cooling_device *ccdev = cdev->devdata;
-
- *state = ccdev->clock_state;
-
- return 0;
-}
-
-/**
- * clock_cooling_set_cur_state - function to set the current cooling state.
- * @cdev: thermal cooling device pointer.
- * @state: set this variable to the current cooling state.
- *
- * Callback for the thermal cooling device to change the clock cooling
- * current cooling state.
- *
- * Return: 0 on success, an error code otherwise.
- */
-static int clock_cooling_set_cur_state(struct thermal_cooling_device *cdev,
- unsigned long state)
-{
- struct clock_cooling_device *clock_device = cdev->devdata;
-
- return clock_cooling_apply(clock_device, state);
-}
-
-/* Bind clock callbacks to thermal cooling device ops */
-static struct thermal_cooling_device_ops const clock_cooling_ops = {
- .get_max_state = clock_cooling_get_max_state,
- .get_cur_state = clock_cooling_get_cur_state,
- .set_cur_state = clock_cooling_set_cur_state,
-};
-
-/**
- * clock_cooling_register - function to create clock cooling device.
- * @dev: struct device pointer to the device used as clock cooling device.
- * @clock_name: string containing the clock used as cooling mechanism.
- *
- * This interface function registers the clock cooling device with the name
- * "thermal-clock-%x". The cooling device is based on clock frequencies.
- * The struct device is assumed to be capable of DVFS transitions.
- * The OPP layer is used to fetch and fill the available frequencies for
- * the referred device. The ordered frequency table is used to control
- * the clock cooling device cooling states and to limit clock transitions
- * based on the cooling state requested by the thermal framework.
- *
- * Return: a valid struct thermal_cooling_device pointer on success,
- * on failure, it returns a corresponding ERR_PTR().
- */
-struct thermal_cooling_device *
-clock_cooling_register(struct device *dev, const char *clock_name)
-{
- struct thermal_cooling_device *cdev;
- struct clock_cooling_device *ccdev = NULL;
- char dev_name[THERMAL_NAME_LENGTH];
- int ret = 0;
-
- ccdev = devm_kzalloc(dev, sizeof(*ccdev), GFP_KERNEL);
- if (!ccdev)
- return ERR_PTR(-ENOMEM);
-
- mutex_init(&ccdev->lock);
- ccdev->dev = dev;
- ccdev->clk = devm_clk_get(dev, clock_name);
- if (IS_ERR(ccdev->clk))
- return ERR_CAST(ccdev->clk);
-
- ret = ida_simple_get(&clock_ida, 0, 0, GFP_KERNEL);
- if (ret < 0)
- return ERR_PTR(ret);
- ccdev->id = ret;
-
- snprintf(dev_name, sizeof(dev_name), "thermal-clock-%d", ccdev->id);
-
- cdev = thermal_cooling_device_register(dev_name, ccdev,
- &clock_cooling_ops);
- if (IS_ERR(cdev)) {
- ida_simple_remove(&clock_ida, ccdev->id);
- return ERR_PTR(-EINVAL);
- }
- ccdev->cdev = cdev;
- ccdev->clk_rate_change_nb.notifier_call = clock_cooling_clock_notifier;
-
- /* Assuming someone has already filled the opp table for this device */
- ret = dev_pm_opp_init_cpufreq_table(dev, &ccdev->freq_table);
- if (ret) {
- ida_simple_remove(&clock_ida, ccdev->id);
- return ERR_PTR(ret);
- }
- ccdev->clock_state = 0;
- ccdev->clock_val = clock_cooling_get_frequency(ccdev, 0);
-
- clk_notifier_register(ccdev->clk, &ccdev->clk_rate_change_nb);
-
- return cdev;
-}
-EXPORT_SYMBOL_GPL(clock_cooling_register);
-
-/**
- * clock_cooling_unregister - function to remove clock cooling device.
- * @cdev: thermal cooling device pointer.
- *
- * This interface function unregisters the "thermal-clock-%x" cooling device.
- */
-void clock_cooling_unregister(struct thermal_cooling_device *cdev)
-{
- struct clock_cooling_device *ccdev;
-
- if (!cdev)
- return;
-
- ccdev = cdev->devdata;
-
- clk_notifier_unregister(ccdev->clk, &ccdev->clk_rate_change_nb);
- dev_pm_opp_free_cpufreq_table(ccdev->dev, &ccdev->freq_table);
-
- thermal_cooling_device_unregister(ccdev->cdev);
- ida_simple_remove(&clock_ida, ccdev->id);
-}
-EXPORT_SYMBOL_GPL(clock_cooling_unregister);
diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c
index fe83d7a210d4..9f8b438fcf8f 100644
--- a/drivers/thermal/cpufreq_cooling.c
+++ b/drivers/thermal/cpufreq_cooling.c
@@ -10,17 +10,18 @@
* Viresh Kumar <viresh.kumar@linaro.org>
*
*/
-#include <linux/module.h>
-#include <linux/thermal.h>
+#include <linux/cpu.h>
#include <linux/cpufreq.h>
+#include <linux/cpu_cooling.h>
+#include <linux/device.h>
+#include <linux/energy_model.h>
#include <linux/err.h>
-#include <linux/idr.h>
+#include <linux/export.h>
#include <linux/pm_opp.h>
#include <linux/pm_qos.h>
#include <linux/slab.h>
-#include <linux/cpu.h>
-#include <linux/cpu_cooling.h>
-#include <linux/energy_model.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
#include <trace/events/thermal.h>
@@ -50,8 +51,6 @@ struct time_in_idle {
/**
* struct cpufreq_cooling_device - data for cooling device with cpufreq
- * @id: unique integer value corresponding to each cpufreq_cooling_device
- * registered.
* @last_load: load measured by the latest call to cpufreq_get_requested_power()
* @cpufreq_state: integer value representing the current state of cpufreq
* cooling devices.
@@ -61,7 +60,7 @@ struct time_in_idle {
* @cdev: thermal_cooling_device pointer to keep track of the
* registered cooling device.
* @policy: cpufreq policy.
- * @node: list_head to link all cpufreq_cooling_device together.
+ * @cooling_ops: cpufreq callbacks to thermal cooling device ops
* @idle_time: idle time stats
* @qos_req: PM QoS contraint to apply
*
@@ -69,21 +68,18 @@ struct time_in_idle {
* cpufreq_cooling_device.
*/
struct cpufreq_cooling_device {
- int id;
u32 last_load;
unsigned int cpufreq_state;
unsigned int max_level;
struct em_perf_domain *em;
struct cpufreq_policy *policy;
- struct list_head node;
+ struct thermal_cooling_device_ops cooling_ops;
+#ifndef CONFIG_SMP
struct time_in_idle *idle_time;
+#endif
struct freq_qos_request qos_req;
};
-static DEFINE_IDA(cpufreq_ida);
-static DEFINE_MUTEX(cooling_list_lock);
-static LIST_HEAD(cpufreq_cdev_list);
-
#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
/**
* get_level: Find the level for a particular frequency
@@ -108,6 +104,7 @@ static unsigned long get_level(struct cpufreq_cooling_device *cpufreq_cdev,
static u32 cpu_freq_to_power(struct cpufreq_cooling_device *cpufreq_cdev,
u32 freq)
{
+ unsigned long power_mw;
int i;
for (i = cpufreq_cdev->max_level - 1; i >= 0; i--) {
@@ -115,31 +112,47 @@ static u32 cpu_freq_to_power(struct cpufreq_cooling_device *cpufreq_cdev,
break;
}
- return cpufreq_cdev->em->table[i + 1].power;
+ power_mw = cpufreq_cdev->em->table[i + 1].power;
+ power_mw /= MICROWATT_PER_MILLIWATT;
+
+ return power_mw;
}
static u32 cpu_power_to_freq(struct cpufreq_cooling_device *cpufreq_cdev,
u32 power)
{
+ unsigned long em_power_mw;
int i;
- for (i = cpufreq_cdev->max_level - 1; i >= 0; i--) {
- if (power > cpufreq_cdev->em->table[i].power)
+ for (i = cpufreq_cdev->max_level; i > 0; i--) {
+ /* Convert EM power to milli-Watts to make safe comparison */
+ em_power_mw = cpufreq_cdev->em->table[i].power;
+ em_power_mw /= MICROWATT_PER_MILLIWATT;
+ if (power >= em_power_mw)
break;
}
- return cpufreq_cdev->em->table[i + 1].frequency;
+ return cpufreq_cdev->em->table[i].frequency;
}
/**
- * get_load() - get load for a cpu since last updated
- * @cpufreq_cdev: &struct cpufreq_cooling_device for this cpu
- * @cpu: cpu number
- * @cpu_idx: index of the cpu in time_in_idle*
+ * get_load() - get load for a cpu
+ * @cpufreq_cdev: struct cpufreq_cooling_device for the cpu
+ * @cpu: cpu number
+ * @cpu_idx: index of the cpu in time_in_idle array
*
* Return: The average load of cpu @cpu in percentage since this
* function was last called.
*/
+#ifdef CONFIG_SMP
+static u32 get_load(struct cpufreq_cooling_device *cpufreq_cdev, int cpu,
+ int cpu_idx)
+{
+ unsigned long util = sched_cpu_util(cpu);
+
+ return (util * 100) / arch_scale_cpu_capacity(cpu);
+}
+#else /* !CONFIG_SMP */
static u32 get_load(struct cpufreq_cooling_device *cpufreq_cdev, int cpu,
int cpu_idx)
{
@@ -161,6 +174,7 @@ static u32 get_load(struct cpufreq_cooling_device *cpufreq_cdev, int cpu,
return load;
}
+#endif /* CONFIG_SMP */
/**
* get_dynamic_power() - calculate the dynamic power
@@ -182,7 +196,6 @@ static u32 get_dynamic_power(struct cpufreq_cooling_device *cpufreq_cdev,
/**
* cpufreq_get_requested_power() - get the current power
* @cdev: &thermal_cooling_device pointer
- * @tz: a valid thermal zone device pointer
* @power: pointer in which to store the resulting power
*
* Calculate the current power consumption of the cpus in milliwatts
@@ -200,10 +213,9 @@ static u32 get_dynamic_power(struct cpufreq_cooling_device *cpufreq_cdev,
* complex code may be needed if experiments show that it's not
* accurate enough.
*
- * Return: 0 on success, -E* if getting the static power failed.
+ * Return: 0 on success, this function doesn't fail.
*/
static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
- struct thermal_zone_device *tz,
u32 *power)
{
unsigned long freq;
@@ -211,16 +223,9 @@ static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
u32 total_load = 0;
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
struct cpufreq_policy *policy = cpufreq_cdev->policy;
- u32 *load_cpu = NULL;
freq = cpufreq_quick_get(policy->cpu);
- if (trace_thermal_power_cpu_get_power_enabled()) {
- u32 ncpus = cpumask_weight(policy->related_cpus);
-
- load_cpu = kcalloc(ncpus, sizeof(*load_cpu), GFP_KERNEL);
- }
-
for_each_cpu(cpu, policy->related_cpus) {
u32 load;
@@ -230,22 +235,13 @@ static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
load = 0;
total_load += load;
- if (load_cpu)
- load_cpu[i] = load;
-
- i++;
}
cpufreq_cdev->last_load = total_load;
*power = get_dynamic_power(cpufreq_cdev, freq);
- if (load_cpu) {
- trace_thermal_power_cpu_get_power(policy->related_cpus, freq,
- load_cpu, i, *power);
-
- kfree(load_cpu);
- }
+ trace_thermal_power_cpu_get_power_simple(policy->cpu, *power);
return 0;
}
@@ -253,7 +249,6 @@ static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
/**
* cpufreq_state2power() - convert a cpu cdev state to power consumed
* @cdev: &thermal_cooling_device pointer
- * @tz: a valid thermal zone device pointer
* @state: cooling device state to be converted
* @power: pointer in which to store the resulting power
*
@@ -261,19 +256,17 @@ static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
* milliwatts assuming 100% load. Store the calculated power in
* @power.
*
- * Return: 0 on success, -EINVAL if the cooling device state could not
- * be converted into a frequency or other -E* if there was an error
- * when calculating the static power.
+ * Return: 0 on success, -EINVAL if the cooling device state is bigger
+ * than maximum allowed.
*/
static int cpufreq_state2power(struct thermal_cooling_device *cdev,
- struct thermal_zone_device *tz,
unsigned long state, u32 *power)
{
unsigned int freq, num_cpus, idx;
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
/* Request state should be less than max_level */
- if (WARN_ON(state > cpufreq_cdev->max_level))
+ if (state > cpufreq_cdev->max_level)
return -EINVAL;
num_cpus = cpumask_weight(cpufreq_cdev->policy->cpus);
@@ -288,26 +281,20 @@ static int cpufreq_state2power(struct thermal_cooling_device *cdev,
/**
* cpufreq_power2state() - convert power to a cooling device state
* @cdev: &thermal_cooling_device pointer
- * @tz: a valid thermal zone device pointer
* @power: power in milliwatts to be converted
* @state: pointer in which to store the resulting state
*
* Calculate a cooling device state for the cpus described by @cdev
* that would allow them to consume at most @power mW and store it in
* @state. Note that this calculation depends on external factors
- * such as the cpu load or the current static power. Calling this
- * function with the same power as input can yield different cooling
- * device states depending on those external factors.
- *
- * Return: 0 on success, -ENODEV if no cpus are online or -EINVAL if
- * the calculated frequency could not be converted to a valid state.
- * The latter should not happen unless the frequencies available to
- * cpufreq have changed since the initialization of the cpu cooling
- * device.
+ * such as the CPUs load. Calling this function with the same power
+ * as input can yield different cooling device states depending on those
+ * external factors.
+ *
+ * Return: 0 on success, this function doesn't fail.
*/
static int cpufreq_power2state(struct thermal_cooling_device *cdev,
- struct thermal_zone_device *tz, u32 power,
- unsigned long *state)
+ u32 power, unsigned long *state)
{
unsigned int target_freq;
u32 last_load, normalised_power;
@@ -329,22 +316,22 @@ static inline bool em_is_sane(struct cpufreq_cooling_device *cpufreq_cdev,
struct cpufreq_policy *policy;
unsigned int nr_levels;
- if (!em)
+ if (!em || em_is_artificial(em))
return false;
policy = cpufreq_cdev->policy;
- if (!cpumask_equal(policy->related_cpus, to_cpumask(em->cpus))) {
+ if (!cpumask_equal(policy->related_cpus, em_span_cpus(em))) {
pr_err("The span of pd %*pbl is misaligned with cpufreq policy %*pbl\n",
- cpumask_pr_args(to_cpumask(em->cpus)),
+ cpumask_pr_args(em_span_cpus(em)),
cpumask_pr_args(policy->related_cpus));
return false;
}
nr_levels = cpufreq_cdev->max_level + 1;
- if (em->nr_cap_states != nr_levels) {
- pr_err("The number of cap states in pd %*pbl (%u) doesn't match the number of cooling levels (%u)\n",
- cpumask_pr_args(to_cpumask(em->cpus)),
- em->nr_cap_states, nr_levels);
+ if (em_pd_nr_perf_states(em) != nr_levels) {
+ pr_err("The number of performance states in pd %*pbl (%u) doesn't match the number of cooling levels (%u)\n",
+ cpumask_pr_args(em_span_cpus(em)),
+ em_pd_nr_perf_states(em), nr_levels);
return false;
}
@@ -352,6 +339,36 @@ static inline bool em_is_sane(struct cpufreq_cooling_device *cpufreq_cdev,
}
#endif /* CONFIG_THERMAL_GOV_POWER_ALLOCATOR */
+#ifdef CONFIG_SMP
+static inline int allocate_idle_time(struct cpufreq_cooling_device *cpufreq_cdev)
+{
+ return 0;
+}
+
+static inline void free_idle_time(struct cpufreq_cooling_device *cpufreq_cdev)
+{
+}
+#else
+static int allocate_idle_time(struct cpufreq_cooling_device *cpufreq_cdev)
+{
+ unsigned int num_cpus = cpumask_weight(cpufreq_cdev->policy->related_cpus);
+
+ cpufreq_cdev->idle_time = kcalloc(num_cpus,
+ sizeof(*cpufreq_cdev->idle_time),
+ GFP_KERNEL);
+ if (!cpufreq_cdev->idle_time)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void free_idle_time(struct cpufreq_cooling_device *cpufreq_cdev)
+{
+ kfree(cpufreq_cdev->idle_time);
+ cpufreq_cdev->idle_time = NULL;
+}
+#endif /* CONFIG_SMP */
+
static unsigned int get_state_freq(struct cpufreq_cooling_device *cpufreq_cdev,
unsigned long state)
{
@@ -386,7 +403,7 @@ static unsigned int get_state_freq(struct cpufreq_cooling_device *cpufreq_cdev,
* Callback for the thermal cooling device to return the cpufreq
* max cooling state.
*
- * Return: 0 on success, an error code otherwise.
+ * Return: 0 on success, this function doesn't fail.
*/
static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
@@ -405,7 +422,7 @@ static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
* Callback for the thermal cooling device to return the cpufreq
* current cooling state.
*
- * Return: 0 on success, an error code otherwise.
+ * Return: 0 on success, this function doesn't fail.
*/
static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
@@ -431,38 +448,40 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
+ struct cpumask *cpus;
+ unsigned int frequency;
+ int ret;
/* Request state should be less than max_level */
- if (WARN_ON(state > cpufreq_cdev->max_level))
+ if (state > cpufreq_cdev->max_level)
return -EINVAL;
/* Check if the old cooling action is same as new cooling action */
if (cpufreq_cdev->cpufreq_state == state)
return 0;
- cpufreq_cdev->cpufreq_state = state;
-
- return freq_qos_update_request(&cpufreq_cdev->qos_req,
- get_state_freq(cpufreq_cdev, state));
-}
+ frequency = get_state_freq(cpufreq_cdev, state);
-/* Bind cpufreq callbacks to thermal cooling device ops */
+ ret = freq_qos_update_request(&cpufreq_cdev->qos_req, frequency);
+ if (ret >= 0) {
+ cpufreq_cdev->cpufreq_state = state;
+ cpus = cpufreq_cdev->policy->related_cpus;
+ arch_update_thermal_pressure(cpus, frequency);
+ ret = 0;
+ }
-static struct thermal_cooling_device_ops cpufreq_cooling_ops = {
- .get_max_state = cpufreq_get_max_state,
- .get_cur_state = cpufreq_get_cur_state,
- .set_cur_state = cpufreq_set_cur_state,
-};
+ return ret;
+}
/**
* __cpufreq_cooling_register - helper function to create cpufreq cooling device
- * @np: a valid struct device_node to the cooling device device tree node
+ * @np: a valid struct device_node to the cooling device tree node
* @policy: cpufreq policy
* Normally this should be same as cpufreq policy->related_cpus.
* @em: Energy Model of the cpufreq policy
*
* This interface function registers the cpufreq cooling device with the name
- * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ * "cpufreq-%s". This API can support multiple instances of cpufreq
* cooling devices. It also gives the opportunity to link the cooling device
* with a device tree node, in order to bind it via the thermal DT code.
*
@@ -476,11 +495,16 @@ __cpufreq_cooling_register(struct device_node *np,
{
struct thermal_cooling_device *cdev;
struct cpufreq_cooling_device *cpufreq_cdev;
- char dev_name[THERMAL_NAME_LENGTH];
- unsigned int i, num_cpus;
+ unsigned int i;
struct device *dev;
int ret;
struct thermal_cooling_device_ops *cooling_ops;
+ char *name;
+
+ if (IS_ERR_OR_NULL(policy)) {
+ pr_err("%s: cpufreq policy isn't valid: %p\n", __func__, policy);
+ return ERR_PTR(-EINVAL);
+ }
dev = get_cpu_device(policy->cpu);
if (unlikely(!dev)) {
@@ -488,12 +512,6 @@ __cpufreq_cooling_register(struct device_node *np,
return ERR_PTR(-ENODEV);
}
-
- if (IS_ERR_OR_NULL(policy)) {
- pr_err("%s: cpufreq policy isn't valid: %p\n", __func__, policy);
- return ERR_PTR(-EINVAL);
- }
-
i = cpufreq_table_count_valid_entries(policy);
if (!i) {
pr_debug("%s: CPUFreq table not found or has no valid entries\n",
@@ -506,29 +524,20 @@ __cpufreq_cooling_register(struct device_node *np,
return ERR_PTR(-ENOMEM);
cpufreq_cdev->policy = policy;
- num_cpus = cpumask_weight(policy->related_cpus);
- cpufreq_cdev->idle_time = kcalloc(num_cpus,
- sizeof(*cpufreq_cdev->idle_time),
- GFP_KERNEL);
- if (!cpufreq_cdev->idle_time) {
- cdev = ERR_PTR(-ENOMEM);
+
+ ret = allocate_idle_time(cpufreq_cdev);
+ if (ret) {
+ cdev = ERR_PTR(ret);
goto free_cdev;
}
/* max_level is an index, not a counter */
cpufreq_cdev->max_level = i - 1;
- ret = ida_simple_get(&cpufreq_ida, 0, 0, GFP_KERNEL);
- if (ret < 0) {
- cdev = ERR_PTR(ret);
- goto free_idle_time;
- }
- cpufreq_cdev->id = ret;
-
- snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d",
- cpufreq_cdev->id);
-
- cooling_ops = &cpufreq_cooling_ops;
+ cooling_ops = &cpufreq_cdev->cooling_ops;
+ cooling_ops->get_max_state = cpufreq_get_max_state;
+ cooling_ops->get_cur_state = cpufreq_get_cur_state;
+ cooling_ops->set_cur_state = cpufreq_set_cur_state;
#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
if (em_is_sane(cpufreq_cdev, em)) {
@@ -542,7 +551,7 @@ __cpufreq_cooling_register(struct device_node *np,
pr_err("%s: unsorted frequency tables are not supported\n",
__func__);
cdev = ERR_PTR(-EINVAL);
- goto remove_ida;
+ goto free_idle_time;
}
ret = freq_qos_add_request(&policy->constraints,
@@ -552,26 +561,27 @@ __cpufreq_cooling_register(struct device_node *np,
pr_err("%s: Failed to add freq constraint (%d)\n", __func__,
ret);
cdev = ERR_PTR(ret);
- goto remove_ida;
+ goto free_idle_time;
}
- cdev = thermal_of_cooling_device_register(np, dev_name, cpufreq_cdev,
+ cdev = ERR_PTR(-ENOMEM);
+ name = kasprintf(GFP_KERNEL, "cpufreq-%s", dev_name(dev));
+ if (!name)
+ goto remove_qos_req;
+
+ cdev = thermal_of_cooling_device_register(np, name, cpufreq_cdev,
cooling_ops);
+ kfree(name);
+
if (IS_ERR(cdev))
goto remove_qos_req;
- mutex_lock(&cooling_list_lock);
- list_add(&cpufreq_cdev->node, &cpufreq_cdev_list);
- mutex_unlock(&cooling_list_lock);
-
return cdev;
remove_qos_req:
freq_qos_remove_request(&cpufreq_cdev->qos_req);
-remove_ida:
- ida_simple_remove(&cpufreq_ida, cpufreq_cdev->id);
free_idle_time:
- kfree(cpufreq_cdev->idle_time);
+ free_idle_time(cpufreq_cdev);
free_cdev:
kfree(cpufreq_cdev);
return cdev;
@@ -582,8 +592,8 @@ free_cdev:
* @policy: cpufreq policy
*
* This interface function registers the cpufreq cooling device with the name
- * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
- * cooling devices.
+ * "cpufreq-%s". 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().
@@ -600,17 +610,14 @@ EXPORT_SYMBOL_GPL(cpufreq_cooling_register);
* @policy: cpufreq policy
*
* 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.
+ * "cpufreq-%s". 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.
*
* Using this function, the cooling device will implement the power
- * extensions by using a simple cpu power model. The cpus must have
+ * extensions by using the Energy Model (if present). The cpus must have
* registered their OPPs using the OPP library.
*
- * It also takes into account, if property present in policy CPU node, the
- * static power consumed by the cpu.
- *
* Return: a valid struct thermal_cooling_device pointer on success,
* and NULL on failure.
*/
@@ -646,7 +653,7 @@ EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register);
* cpufreq_cooling_unregister - function to remove cpufreq cooling device.
* @cdev: thermal cooling device pointer.
*
- * This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+ * This interface function unregisters the "cpufreq-%x" cooling device.
*/
void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
{
@@ -657,14 +664,9 @@ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
cpufreq_cdev = cdev->devdata;
- mutex_lock(&cooling_list_lock);
- list_del(&cpufreq_cdev->node);
- mutex_unlock(&cooling_list_lock);
-
thermal_cooling_device_unregister(cdev);
freq_qos_remove_request(&cpufreq_cdev->qos_req);
- ida_simple_remove(&cpufreq_ida, cpufreq_cdev->id);
- kfree(cpufreq_cdev->idle_time);
+ free_idle_time(cpufreq_cdev);
kfree(cpufreq_cdev);
}
EXPORT_SYMBOL_GPL(cpufreq_cooling_unregister);
diff --git a/drivers/thermal/cpuidle_cooling.c b/drivers/thermal/cpuidle_cooling.c
index 0bb843246f59..4f41102e8b16 100644
--- a/drivers/thermal/cpuidle_cooling.c
+++ b/drivers/thermal/cpuidle_cooling.c
@@ -5,11 +5,14 @@
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
*
*/
+#define pr_fmt(fmt) "cpuidle cooling: " fmt
+
#include <linux/cpu_cooling.h>
#include <linux/cpuidle.h>
+#include <linux/device.h>
#include <linux/err.h>
#include <linux/idle_inject.h>
-#include <linux/idr.h>
+#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/thermal.h>
@@ -23,11 +26,9 @@ struct cpuidle_cooling_device {
unsigned long state;
};
-static DEFINE_IDA(cpuidle_ida);
-
/**
* cpuidle_cooling_runtime - Running time computation
- * @idle_duration_us: the idle cooling device
+ * @idle_duration_us: CPU idle time to inject in microseconds
* @state: a percentile based number
*
* The running duration is computed from the idle injection duration
@@ -154,24 +155,28 @@ static struct thermal_cooling_device_ops cpuidle_cooling_ops = {
};
/**
- * cpuidle_of_cooling_register - Idle cooling device initialization function
+ * __cpuidle_cooling_register: register the cooling device
* @drv: a cpuidle driver structure pointer
- * @np: a node pointer to a device tree cooling device node
+ * @np: a device node structure pointer used for the thermal binding
*
- * This function is in charge of creating a cooling device per cpuidle
- * driver and register it to thermal framework.
+ * This function is in charge of allocating the cpuidle cooling device
+ * structure, the idle injection, initialize them and register the
+ * cooling device to the thermal framework.
*
- * Return: zero on success, or negative value corresponding to the
- * error detected in the underlying subsystems.
+ * Return: zero on success, a negative value returned by one of the
+ * underlying subsystem in case of error
*/
-int cpuidle_of_cooling_register(struct device_node *np,
- struct cpuidle_driver *drv)
+static int __cpuidle_cooling_register(struct device_node *np,
+ struct cpuidle_driver *drv)
{
struct idle_inject_device *ii_dev;
struct cpuidle_cooling_device *idle_cdev;
struct thermal_cooling_device *cdev;
- char dev_name[THERMAL_NAME_LENGTH];
- int id, ret;
+ struct device *dev;
+ unsigned int idle_duration_us = TICK_USEC;
+ unsigned int latency_us = UINT_MAX;
+ char *name;
+ int ret;
idle_cdev = kzalloc(sizeof(*idle_cdev), GFP_KERNEL);
if (!idle_cdev) {
@@ -179,37 +184,46 @@ int cpuidle_of_cooling_register(struct device_node *np,
goto out;
}
- id = ida_simple_get(&cpuidle_ida, 0, 0, GFP_KERNEL);
- if (id < 0) {
- ret = id;
- goto out_kfree;
- }
-
ii_dev = idle_inject_register(drv->cpumask);
if (!ii_dev) {
ret = -EINVAL;
- goto out_id;
+ goto out_kfree;
}
- idle_inject_set_duration(ii_dev, TICK_USEC, TICK_USEC);
+ of_property_read_u32(np, "duration-us", &idle_duration_us);
+ of_property_read_u32(np, "exit-latency-us", &latency_us);
+
+ idle_inject_set_duration(ii_dev, TICK_USEC, idle_duration_us);
+ idle_inject_set_latency(ii_dev, latency_us);
idle_cdev->ii_dev = ii_dev;
- snprintf(dev_name, sizeof(dev_name), "thermal-idle-%d", id);
+ dev = get_cpu_device(cpumask_first(drv->cpumask));
+
+ name = kasprintf(GFP_KERNEL, "idle-%s", dev_name(dev));
+ if (!name) {
+ ret = -ENOMEM;
+ goto out_unregister;
+ }
- cdev = thermal_of_cooling_device_register(np, dev_name, idle_cdev,
+ cdev = thermal_of_cooling_device_register(np, name, idle_cdev,
&cpuidle_cooling_ops);
if (IS_ERR(cdev)) {
ret = PTR_ERR(cdev);
- goto out_unregister;
+ goto out_kfree_name;
}
+ pr_debug("%s: Idle injection set with idle duration=%u, latency=%u\n",
+ name, idle_duration_us, latency_us);
+
+ kfree(name);
+
return 0;
+out_kfree_name:
+ kfree(name);
out_unregister:
idle_inject_unregister(ii_dev);
-out_id:
- ida_simple_remove(&cpuidle_ida, id);
out_kfree:
kfree(idle_cdev);
out:
@@ -221,12 +235,38 @@ out:
* @drv: a cpuidle driver structure pointer
*
* This function is in charge of creating a cooling device per cpuidle
- * driver and register it to thermal framework.
+ * driver and register it to the thermal framework.
*
* Return: zero on success, or negative value corresponding to the
* error detected in the underlying subsystems.
*/
-int cpuidle_cooling_register(struct cpuidle_driver *drv)
+void cpuidle_cooling_register(struct cpuidle_driver *drv)
{
- return cpuidle_of_cooling_register(NULL, drv);
+ struct device_node *cooling_node;
+ struct device_node *cpu_node;
+ int cpu, ret;
+
+ for_each_cpu(cpu, drv->cpumask) {
+
+ cpu_node = of_cpu_device_node_get(cpu);
+
+ cooling_node = of_get_child_by_name(cpu_node, "thermal-idle");
+
+ of_node_put(cpu_node);
+
+ if (!cooling_node) {
+ pr_debug("'thermal-idle' node not found for cpu%d\n", cpu);
+ continue;
+ }
+
+ ret = __cpuidle_cooling_register(cooling_node, drv);
+
+ of_node_put(cooling_node);
+
+ if (ret) {
+ pr_err("Failed to register the cpuidle cooling device" \
+ "for cpu%d: %d\n", cpu, ret);
+ break;
+ }
+ }
}
diff --git a/drivers/thermal/da9062-thermal.c b/drivers/thermal/da9062-thermal.c
index c32709badeda..7dcfde7a9f2c 100644
--- a/drivers/thermal/da9062-thermal.c
+++ b/drivers/thermal/da9062-thermal.c
@@ -49,7 +49,6 @@ struct da9062_thermal {
struct da9062 *hw;
struct delayed_work work;
struct thermal_zone_device *zone;
- enum thermal_device_mode mode;
struct mutex lock; /* protection for da9062_thermal temperature */
int temperature;
int irq;
@@ -96,7 +95,7 @@ static void da9062_thermal_poll_on(struct work_struct *work)
thermal_zone_device_update(thermal->zone,
THERMAL_EVENT_UNSPECIFIED);
- delay = msecs_to_jiffies(thermal->zone->passive_delay);
+ delay = thermal->zone->passive_delay_jiffies;
queue_delayed_work(system_freezable_wq, &thermal->work, delay);
return;
}
@@ -121,14 +120,6 @@ static irqreturn_t da9062_thermal_irq_handler(int irq, void *data)
return IRQ_HANDLED;
}
-static int da9062_thermal_get_mode(struct thermal_zone_device *z,
- enum thermal_device_mode *mode)
-{
- struct da9062_thermal *thermal = z->devdata;
- *mode = thermal->mode;
- return 0;
-}
-
static int da9062_thermal_get_trip_type(struct thermal_zone_device *z,
int trip,
enum thermal_trip_type *type)
@@ -181,7 +172,6 @@ static int da9062_thermal_get_temp(struct thermal_zone_device *z,
static struct thermal_zone_device_ops da9062_thermal_ops = {
.get_temp = da9062_thermal_get_temp,
- .get_mode = da9062_thermal_get_mode,
.get_trip_type = da9062_thermal_get_trip_type,
.get_trip_temp = da9062_thermal_get_trip_temp,
};
@@ -233,7 +223,6 @@ static int da9062_thermal_probe(struct platform_device *pdev)
thermal->config = match->data;
thermal->hw = chip;
- thermal->mode = THERMAL_DEVICE_ENABLED;
thermal->dev = &pdev->dev;
INIT_DELAYED_WORK(&thermal->work, da9062_thermal_poll_on);
@@ -248,16 +237,20 @@ static int da9062_thermal_probe(struct platform_device *pdev)
ret = PTR_ERR(thermal->zone);
goto err;
}
+ ret = thermal_zone_device_enable(thermal->zone);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot enable thermal zone device\n");
+ goto err_zone;
+ }
dev_dbg(&pdev->dev,
"TJUNC temperature polling period set at %d ms\n",
- thermal->zone->passive_delay);
+ jiffies_to_msecs(thermal->zone->passive_delay_jiffies));
ret = platform_get_irq_byname(pdev, "THERMAL");
- if (ret < 0) {
- dev_err(&pdev->dev, "Failed to get platform IRQ.\n");
+ if (ret < 0)
goto err_zone;
- }
+
thermal->irq = ret;
ret = request_threaded_irq(thermal->irq, NULL,
diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c
index 21d4d6e6409a..cb10e280681f 100644
--- a/drivers/thermal/db8500_thermal.c
+++ b/drivers/thermal/db8500_thermal.c
@@ -53,15 +53,14 @@ static const unsigned long db8500_thermal_points[] = {
struct db8500_thermal_zone {
struct thermal_zone_device *tz;
- enum thermal_trend trend;
unsigned long interpolated_temp;
unsigned int cur_index;
};
/* Callback to get current temperature */
-static int db8500_thermal_get_temp(void *data, int *temp)
+static int db8500_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct db8500_thermal_zone *th = data;
+ struct db8500_thermal_zone *th = tz->devdata;
/*
* TODO: There is no PRCMU interface to get temperature data currently,
@@ -73,24 +72,12 @@ static int db8500_thermal_get_temp(void *data, int *temp)
return 0;
}
-/* Callback to get temperature changing trend */
-static int db8500_thermal_get_trend(void *data, int trip, enum thermal_trend *trend)
-{
- struct db8500_thermal_zone *th = data;
-
- *trend = th->trend;
-
- return 0;
-}
-
-static struct thermal_zone_of_device_ops thdev_ops = {
+static const struct thermal_zone_device_ops thdev_ops = {
.get_temp = db8500_thermal_get_temp,
- .get_trend = db8500_thermal_get_trend,
};
static void db8500_thermal_update_config(struct db8500_thermal_zone *th,
unsigned int idx,
- enum thermal_trend trend,
unsigned long next_low,
unsigned long next_high)
{
@@ -98,7 +85,6 @@ static void db8500_thermal_update_config(struct db8500_thermal_zone *th,
th->cur_index = idx;
th->interpolated_temp = (next_low + next_high)/2;
- th->trend = trend;
/*
* The PRCMU accept absolute temperatures in celsius so divide
@@ -127,8 +113,7 @@ static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
}
idx -= 1;
- db8500_thermal_update_config(th, idx, THERMAL_TREND_DROPPING,
- next_low, next_high);
+ db8500_thermal_update_config(th, idx, next_low, next_high);
dev_dbg(&th->tz->device,
"PRCMU set max %ld, min %ld\n", next_high, next_low);
@@ -149,8 +134,7 @@ static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
next_low = db8500_thermal_points[idx];
idx += 1;
- db8500_thermal_update_config(th, idx, THERMAL_TREND_RAISING,
- next_low, next_high);
+ db8500_thermal_update_config(th, idx, next_low, next_high);
dev_dbg(&th->tz->device,
"PRCMU set max %ld, min %ld\n", next_high, next_low);
@@ -174,10 +158,8 @@ static int db8500_thermal_probe(struct platform_device *pdev)
return -ENOMEM;
low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
- if (low_irq < 0) {
- dev_err(dev, "Get IRQ_HOTMON_LOW failed\n");
+ if (low_irq < 0)
return low_irq;
- }
ret = devm_request_threaded_irq(dev, low_irq, NULL,
prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
@@ -188,10 +170,8 @@ static int db8500_thermal_probe(struct platform_device *pdev)
}
high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
- if (high_irq < 0) {
- dev_err(dev, "Get IRQ_HOTMON_HIGH failed\n");
+ if (high_irq < 0)
return high_irq;
- }
ret = devm_request_threaded_irq(dev, high_irq, NULL,
prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
@@ -202,7 +182,7 @@ static int db8500_thermal_probe(struct platform_device *pdev)
}
/* register of thermal sensor and get info from DT */
- th->tz = devm_thermal_zone_of_sensor_register(dev, 0, th, &thdev_ops);
+ th->tz = devm_thermal_of_zone_register(dev, 0, th, &thdev_ops);
if (IS_ERR(th->tz)) {
dev_err(dev, "register thermal zone sensor failed\n");
return PTR_ERR(th->tz);
@@ -210,8 +190,7 @@ static int db8500_thermal_probe(struct platform_device *pdev)
dev_info(dev, "thermal zone sensor registered\n");
/* Start measuring at the lowest point */
- db8500_thermal_update_config(th, 0, THERMAL_TREND_STABLE,
- PRCMU_DEFAULT_LOW_TEMP,
+ db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP,
db8500_thermal_points[0]);
platform_set_drvdata(pdev, th);
@@ -232,8 +211,7 @@ static int db8500_thermal_resume(struct platform_device *pdev)
struct db8500_thermal_zone *th = platform_get_drvdata(pdev);
/* Resume and start measuring at the lowest point */
- db8500_thermal_update_config(th, 0, THERMAL_TREND_STABLE,
- PRCMU_DEFAULT_LOW_TEMP,
+ db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP,
db8500_thermal_points[0]);
return 0;
diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c
index a87d4fa031c8..24b474925cd6 100644
--- a/drivers/thermal/devfreq_cooling.c
+++ b/drivers/thermal/devfreq_cooling.c
@@ -1,18 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* devfreq_cooling: Thermal cooling device implementation for devices using
* devfreq
*
* Copyright (C) 2014-2015 ARM Limited
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * This program is distributed "as is" WITHOUT ANY WARRANTY of any
- * kind, whether express or implied; without even the implied warranty
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
* TODO:
* - If OPPs are added or removed after devfreq cooling has
* registered, the devfreq cooling won't react to it.
@@ -20,101 +12,61 @@
#include <linux/devfreq.h>
#include <linux/devfreq_cooling.h>
+#include <linux/energy_model.h>
#include <linux/export.h>
-#include <linux/idr.h>
#include <linux/slab.h>
#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
#include <linux/thermal.h>
+#include <linux/units.h>
#include <trace/events/thermal.h>
-#define SCALE_ERROR_MITIGATION 100
-
-static DEFINE_IDA(devfreq_ida);
+#define SCALE_ERROR_MITIGATION 100
/**
* struct devfreq_cooling_device - Devfreq cooling device
- * @id: unique integer value corresponding to each
* devfreq_cooling_device registered.
* @cdev: Pointer to associated thermal cooling device.
+ * @cooling_ops: devfreq callbacks to thermal cooling device ops
* @devfreq: Pointer to associated devfreq device.
* @cooling_state: Current cooling state.
- * @power_table: Pointer to table with maximum power draw for each
- * cooling state. State is the index into the table, and
- * the power is in mW.
* @freq_table: Pointer to a table with the frequencies sorted in descending
* order. You can index the table by cooling device state
- * @freq_table_size: Size of the @freq_table and @power_table
- * @power_ops: Pointer to devfreq_cooling_power, used to generate the
- * @power_table.
+ * @max_state: It is the last index, that is, one less than the number of the
+ * OPPs
+ * @power_ops: Pointer to devfreq_cooling_power, a more precised model.
* @res_util: Resource utilization scaling factor for the power.
* It is multiplied by 100 to minimize the error. It is used
* for estimation of the power budget instead of using
- * 'utilization' (which is 'busy_time / 'total_time').
- * The 'res_util' range is from 100 to (power_table[state] * 100)
- * for the corresponding 'state'.
+ * 'utilization' (which is 'busy_time' / 'total_time').
+ * The 'res_util' range is from 100 to power * 100 for the
+ * corresponding 'state'.
* @capped_state: index to cooling state with in dynamic power budget
+ * @req_max_freq: PM QoS request for limiting the maximum frequency
+ * of the devfreq device.
+ * @em_pd: Energy Model for the associated Devfreq device
*/
struct devfreq_cooling_device {
- int id;
struct thermal_cooling_device *cdev;
+ struct thermal_cooling_device_ops cooling_ops;
struct devfreq *devfreq;
unsigned long cooling_state;
- u32 *power_table;
u32 *freq_table;
- size_t freq_table_size;
+ size_t max_state;
struct devfreq_cooling_power *power_ops;
u32 res_util;
int capped_state;
+ struct dev_pm_qos_request req_max_freq;
+ struct em_perf_domain *em_pd;
};
-/**
- * partition_enable_opps() - disable all opps above a given state
- * @dfc: Pointer to devfreq we are operating on
- * @cdev_state: cooling device state we're setting
- *
- * Go through the OPPs of the device, enabling all OPPs until
- * @cdev_state and disabling those frequencies above it.
- */
-static int partition_enable_opps(struct devfreq_cooling_device *dfc,
- unsigned long cdev_state)
-{
- int i;
- struct device *dev = dfc->devfreq->dev.parent;
-
- for (i = 0; i < dfc->freq_table_size; i++) {
- struct dev_pm_opp *opp;
- int ret = 0;
- unsigned int freq = dfc->freq_table[i];
- bool want_enable = i >= cdev_state ? true : false;
-
- opp = dev_pm_opp_find_freq_exact(dev, freq, !want_enable);
-
- if (PTR_ERR(opp) == -ERANGE)
- continue;
- else if (IS_ERR(opp))
- return PTR_ERR(opp);
-
- dev_pm_opp_put(opp);
-
- if (want_enable)
- ret = dev_pm_opp_enable(dev, freq);
- else
- ret = dev_pm_opp_disable(dev, freq);
-
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
static int devfreq_cooling_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct devfreq_cooling_device *dfc = cdev->devdata;
- *state = dfc->freq_table_size - 1;
+ *state = dfc->max_state;
return 0;
}
@@ -135,19 +87,26 @@ static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
struct devfreq_cooling_device *dfc = cdev->devdata;
struct devfreq *df = dfc->devfreq;
struct device *dev = df->dev.parent;
- int ret;
+ unsigned long freq;
+ int perf_idx;
if (state == dfc->cooling_state)
return 0;
dev_dbg(dev, "Setting cooling state %lu\n", state);
- if (state >= dfc->freq_table_size)
+ if (state > dfc->max_state)
return -EINVAL;
- ret = partition_enable_opps(dfc, state);
- if (ret)
- return ret;
+ if (dfc->em_pd) {
+ perf_idx = dfc->max_state - state;
+ freq = dfc->em_pd->table[perf_idx].frequency * 1000;
+ } else {
+ freq = dfc->freq_table[state];
+ }
+
+ dev_pm_qos_update_request(&dfc->req_max_freq,
+ DIV_ROUND_UP(freq, HZ_PER_KHZ));
dfc->cooling_state = state;
@@ -155,24 +114,23 @@ static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
}
/**
- * freq_get_state() - get the cooling state corresponding to a frequency
- * @dfc: Pointer to devfreq cooling device
- * @freq: frequency in Hz
+ * get_perf_idx() - get the performance index corresponding to a frequency
+ * @em_pd: Pointer to device's Energy Model
+ * @freq: frequency in kHz
*
- * Return: the cooling state associated with the @freq, or
- * THERMAL_CSTATE_INVALID if it wasn't found.
+ * Return: the performance index associated with the @freq, or
+ * -EINVAL if it wasn't found.
*/
-static unsigned long
-freq_get_state(struct devfreq_cooling_device *dfc, unsigned long freq)
+static int get_perf_idx(struct em_perf_domain *em_pd, unsigned long freq)
{
int i;
- for (i = 0; i < dfc->freq_table_size; i++) {
- if (dfc->freq_table[i] == freq)
+ for (i = 0; i < em_pd->nr_perf_states; i++) {
+ if (em_pd->table[i].frequency == freq)
return i;
}
- return THERMAL_CSTATE_INVALID;
+ return -EINVAL;
}
static unsigned long get_voltage(struct devfreq *df, unsigned long freq)
@@ -203,95 +161,38 @@ static unsigned long get_voltage(struct devfreq *df, unsigned long freq)
return voltage;
}
-/**
- * get_static_power() - calculate the static power
- * @dfc: Pointer to devfreq cooling device
- * @freq: Frequency in Hz
- *
- * Calculate the static power in milliwatts using the supplied
- * get_static_power(). The current voltage is calculated using the
- * OPP library. If no get_static_power() was supplied, assume the
- * static power is negligible.
- */
-static unsigned long
-get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq)
-{
- struct devfreq *df = dfc->devfreq;
- unsigned long voltage;
-
- if (!dfc->power_ops->get_static_power)
- return 0;
-
- voltage = get_voltage(df, freq);
-
- if (voltage == 0)
- return 0;
-
- return dfc->power_ops->get_static_power(df, voltage);
-}
-
-/**
- * get_dynamic_power - calculate the dynamic power
- * @dfc: Pointer to devfreq cooling device
- * @freq: Frequency in Hz
- * @voltage: Voltage in millivolts
- *
- * Calculate the dynamic power in milliwatts consumed by the device at
- * frequency @freq and voltage @voltage. If the get_dynamic_power()
- * was supplied as part of the devfreq_cooling_power struct, then that
- * function is used. Otherwise, a simple power model (Pdyn = Coeff *
- * Voltage^2 * Frequency) is used.
- */
-static unsigned long
-get_dynamic_power(struct devfreq_cooling_device *dfc, unsigned long freq,
- unsigned long voltage)
+static void _normalize_load(struct devfreq_dev_status *status)
{
- u64 power;
- u32 freq_mhz;
- struct devfreq_cooling_power *dfc_power = dfc->power_ops;
-
- if (dfc_power->get_dynamic_power)
- return dfc_power->get_dynamic_power(dfc->devfreq, freq,
- voltage);
-
- freq_mhz = freq / 1000000;
- power = (u64)dfc_power->dyn_power_coeff * freq_mhz * voltage * voltage;
- do_div(power, 1000000000);
-
- return power;
-}
+ if (status->total_time > 0xfffff) {
+ status->total_time >>= 10;
+ status->busy_time >>= 10;
+ }
+ status->busy_time <<= 10;
+ status->busy_time /= status->total_time ? : 1;
-static inline unsigned long get_total_power(struct devfreq_cooling_device *dfc,
- unsigned long freq,
- unsigned long voltage)
-{
- return get_static_power(dfc, freq) + get_dynamic_power(dfc, freq,
- voltage);
+ status->busy_time = status->busy_time ? : 1;
+ status->total_time = 1024;
}
-
static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cdev,
- struct thermal_zone_device *tz,
u32 *power)
{
struct devfreq_cooling_device *dfc = cdev->devdata;
struct devfreq *df = dfc->devfreq;
- struct devfreq_dev_status *status = &df->last_status;
+ struct devfreq_dev_status status;
unsigned long state;
- unsigned long freq = status->current_frequency;
+ unsigned long freq;
unsigned long voltage;
- u32 dyn_power = 0;
- u32 static_power = 0;
- int res;
-
- state = freq_get_state(dfc, freq);
- if (state == THERMAL_CSTATE_INVALID) {
- res = -EAGAIN;
- goto fail;
- }
+ int res, perf_idx;
+
+ mutex_lock(&df->lock);
+ status = df->last_status;
+ mutex_unlock(&df->lock);
- if (dfc->power_ops->get_real_power) {
+ freq = status.current_frequency;
+
+ if (dfc->power_ops && dfc->power_ops->get_real_power) {
voltage = get_voltage(df, freq);
if (voltage == 0) {
res = -EINVAL;
@@ -301,7 +202,11 @@ static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cd
res = dfc->power_ops->get_real_power(df, power, freq, voltage);
if (!res) {
state = dfc->capped_state;
- dfc->res_util = dfc->power_table[state];
+
+ /* Convert EM power into milli-Watts first */
+ dfc->res_util = dfc->em_pd->table[state].power;
+ dfc->res_util /= MICROWATT_PER_MILLIWATT;
+
dfc->res_util *= SCALE_ERROR_MITIGATION;
if (*power > 1)
@@ -310,19 +215,24 @@ static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cd
goto fail;
}
} else {
- dyn_power = dfc->power_table[state];
+ /* Energy Model frequencies are in kHz */
+ perf_idx = get_perf_idx(dfc->em_pd, freq / 1000);
+ if (perf_idx < 0) {
+ res = -EAGAIN;
+ goto fail;
+ }
- /* Scale dynamic power for utilization */
- dyn_power *= status->busy_time;
- dyn_power /= status->total_time;
- /* Get static power */
- static_power = get_static_power(dfc, freq);
+ _normalize_load(&status);
- *power = dyn_power + static_power;
+ /* Convert EM power into milli-Watts first */
+ *power = dfc->em_pd->table[perf_idx].power;
+ *power /= MICROWATT_PER_MILLIWATT;
+ /* Scale power for utilization */
+ *power *= status.busy_time;
+ *power >>= 10;
}
- trace_thermal_power_devfreq_get_power(cdev, status, freq, dyn_power,
- static_power, *power);
+ trace_thermal_power_devfreq_get_power(cdev, &status, freq, *power);
return 0;
fail:
@@ -332,159 +242,105 @@ fail:
}
static int devfreq_cooling_state2power(struct thermal_cooling_device *cdev,
- struct thermal_zone_device *tz,
- unsigned long state,
- u32 *power)
+ unsigned long state, u32 *power)
{
struct devfreq_cooling_device *dfc = cdev->devdata;
- unsigned long freq;
- u32 static_power;
+ int perf_idx;
- if (state >= dfc->freq_table_size)
+ if (state > dfc->max_state)
return -EINVAL;
- freq = dfc->freq_table[state];
- static_power = get_static_power(dfc, freq);
+ perf_idx = dfc->max_state - state;
+ *power = dfc->em_pd->table[perf_idx].power;
+ *power /= MICROWATT_PER_MILLIWATT;
- *power = dfc->power_table[state] + static_power;
return 0;
}
static int devfreq_cooling_power2state(struct thermal_cooling_device *cdev,
- struct thermal_zone_device *tz,
u32 power, unsigned long *state)
{
struct devfreq_cooling_device *dfc = cdev->devdata;
struct devfreq *df = dfc->devfreq;
- struct devfreq_dev_status *status = &df->last_status;
- unsigned long freq = status->current_frequency;
- unsigned long busy_time;
- s32 dyn_power;
- u32 static_power;
+ struct devfreq_dev_status status;
+ unsigned long freq, em_power_mw;
s32 est_power;
int i;
- if (dfc->power_ops->get_real_power) {
+ mutex_lock(&df->lock);
+ status = df->last_status;
+ mutex_unlock(&df->lock);
+
+ freq = status.current_frequency;
+
+ if (dfc->power_ops && dfc->power_ops->get_real_power) {
/* Scale for resource utilization */
est_power = power * dfc->res_util;
est_power /= SCALE_ERROR_MITIGATION;
} else {
- static_power = get_static_power(dfc, freq);
-
- dyn_power = power - static_power;
- dyn_power = dyn_power > 0 ? dyn_power : 0;
-
/* Scale dynamic power for utilization */
- busy_time = status->busy_time ?: 1;
- est_power = (dyn_power * status->total_time) / busy_time;
+ _normalize_load(&status);
+ est_power = power << 10;
+ est_power /= status.busy_time;
}
/*
* Find the first cooling state that is within the power
- * budget for dynamic power.
+ * budget. The EM power table is sorted ascending.
*/
- for (i = 0; i < dfc->freq_table_size - 1; i++)
- if (est_power >= dfc->power_table[i])
+ for (i = dfc->max_state; i > 0; i--) {
+ /* Convert EM power to milli-Watts to make safe comparison */
+ em_power_mw = dfc->em_pd->table[i].power;
+ em_power_mw /= MICROWATT_PER_MILLIWATT;
+ if (est_power >= em_power_mw)
break;
+ }
+
+ *state = dfc->max_state - i;
+ dfc->capped_state = *state;
- *state = i;
- dfc->capped_state = i;
trace_thermal_power_devfreq_limit(cdev, freq, *state, power);
return 0;
}
-static struct thermal_cooling_device_ops devfreq_cooling_ops = {
- .get_max_state = devfreq_cooling_get_max_state,
- .get_cur_state = devfreq_cooling_get_cur_state,
- .set_cur_state = devfreq_cooling_set_cur_state,
-};
-
/**
- * devfreq_cooling_gen_tables() - Generate power and freq tables.
- * @dfc: Pointer to devfreq cooling device.
+ * devfreq_cooling_gen_tables() - Generate frequency table.
+ * @dfc: Pointer to devfreq cooling device.
+ * @num_opps: Number of OPPs
*
- * Generate power and frequency tables: the power table hold the
- * device's maximum power usage at each cooling state (OPP). The
- * static and dynamic power using the appropriate voltage and
- * frequency for the state, is acquired from the struct
- * devfreq_cooling_power, and summed to make the maximum power draw.
- *
- * The frequency table holds the frequencies in descending order.
- * That way its indexed by cooling device state.
- *
- * The tables are malloced, and pointers put in dfc. They must be
- * freed when unregistering the devfreq cooling device.
+ * Generate frequency table which holds the frequencies in descending
+ * order. That way its indexed by cooling device state. This is for
+ * compatibility with drivers which do not register Energy Model.
*
* Return: 0 on success, negative error code on failure.
*/
-static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc)
+static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc,
+ int num_opps)
{
struct devfreq *df = dfc->devfreq;
struct device *dev = df->dev.parent;
- int ret, num_opps;
unsigned long freq;
- u32 *power_table = NULL;
- u32 *freq_table;
int i;
- num_opps = dev_pm_opp_get_opp_count(dev);
-
- if (dfc->power_ops) {
- power_table = kcalloc(num_opps, sizeof(*power_table),
- GFP_KERNEL);
- if (!power_table)
- return -ENOMEM;
- }
-
- freq_table = kcalloc(num_opps, sizeof(*freq_table),
+ dfc->freq_table = kcalloc(num_opps, sizeof(*dfc->freq_table),
GFP_KERNEL);
- if (!freq_table) {
- ret = -ENOMEM;
- goto free_power_table;
- }
+ if (!dfc->freq_table)
+ return -ENOMEM;
for (i = 0, freq = ULONG_MAX; i < num_opps; i++, freq--) {
- unsigned long power, voltage;
struct dev_pm_opp *opp;
opp = dev_pm_opp_find_freq_floor(dev, &freq);
if (IS_ERR(opp)) {
- ret = PTR_ERR(opp);
- goto free_tables;
+ kfree(dfc->freq_table);
+ return PTR_ERR(opp);
}
- voltage = dev_pm_opp_get_voltage(opp) / 1000; /* mV */
dev_pm_opp_put(opp);
-
- if (dfc->power_ops) {
- if (dfc->power_ops->get_real_power)
- power = get_total_power(dfc, freq, voltage);
- else
- power = get_dynamic_power(dfc, freq, voltage);
-
- dev_dbg(dev, "Power table: %lu MHz @ %lu mV: %lu = %lu mW\n",
- freq / 1000000, voltage, power, power);
-
- power_table[i] = power;
- }
-
- freq_table[i] = freq;
+ dfc->freq_table[i] = freq;
}
- if (dfc->power_ops)
- dfc->power_table = power_table;
-
- dfc->freq_table = freq_table;
- dfc->freq_table_size = num_opps;
-
return 0;
-
-free_tables:
- kfree(freq_table);
-free_power_table:
- kfree(power_table);
-
- return ret;
}
/**
@@ -507,9 +363,13 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
struct devfreq_cooling_power *dfc_power)
{
struct thermal_cooling_device *cdev;
+ struct device *dev = df->dev.parent;
struct devfreq_cooling_device *dfc;
- char dev_name[THERMAL_NAME_LENGTH];
- int err;
+ struct em_perf_domain *em;
+ struct thermal_cooling_device_ops *ops;
+ char *name;
+ int err, num_opps;
+
dfc = kzalloc(sizeof(*dfc), GFP_KERNEL);
if (!dfc)
@@ -517,44 +377,70 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
dfc->devfreq = df;
- if (dfc_power) {
- dfc->power_ops = dfc_power;
+ ops = &dfc->cooling_ops;
+ ops->get_max_state = devfreq_cooling_get_max_state;
+ ops->get_cur_state = devfreq_cooling_get_cur_state;
+ ops->set_cur_state = devfreq_cooling_set_cur_state;
- devfreq_cooling_ops.get_requested_power =
+ em = em_pd_get(dev);
+ if (em && !em_is_artificial(em)) {
+ dfc->em_pd = em;
+ ops->get_requested_power =
devfreq_cooling_get_requested_power;
- devfreq_cooling_ops.state2power = devfreq_cooling_state2power;
- devfreq_cooling_ops.power2state = devfreq_cooling_power2state;
+ ops->state2power = devfreq_cooling_state2power;
+ ops->power2state = devfreq_cooling_power2state;
+
+ dfc->power_ops = dfc_power;
+
+ num_opps = em_pd_nr_perf_states(dfc->em_pd);
+ } else {
+ /* Backward compatibility for drivers which do not use IPA */
+ dev_dbg(dev, "missing proper EM for cooling device\n");
+
+ num_opps = dev_pm_opp_get_opp_count(dev);
+
+ err = devfreq_cooling_gen_tables(dfc, num_opps);
+ if (err)
+ goto free_dfc;
}
- err = devfreq_cooling_gen_tables(dfc);
- if (err)
+ if (num_opps <= 0) {
+ err = -EINVAL;
goto free_dfc;
+ }
- err = ida_simple_get(&devfreq_ida, 0, 0, GFP_KERNEL);
+ /* max_state is an index, not a counter */
+ dfc->max_state = num_opps - 1;
+
+ err = dev_pm_qos_add_request(dev, &dfc->req_max_freq,
+ DEV_PM_QOS_MAX_FREQUENCY,
+ PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
if (err < 0)
- goto free_tables;
- dfc->id = err;
+ goto free_table;
+
+ err = -ENOMEM;
+ name = kasprintf(GFP_KERNEL, "devfreq-%s", dev_name(dev));
+ if (!name)
+ goto remove_qos_req;
- snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d", dfc->id);
+ cdev = thermal_of_cooling_device_register(np, name, dfc, ops);
+ kfree(name);
- cdev = thermal_of_cooling_device_register(np, dev_name, dfc,
- &devfreq_cooling_ops);
if (IS_ERR(cdev)) {
err = PTR_ERR(cdev);
- dev_err(df->dev.parent,
+ dev_err(dev,
"Failed to register devfreq cooling device (%d)\n",
err);
- goto release_ida;
+ goto remove_qos_req;
}
dfc->cdev = cdev;
return cdev;
-release_ida:
- ida_simple_remove(&devfreq_ida, dfc->id);
-free_tables:
- kfree(dfc->power_table);
+remove_qos_req:
+ dev_pm_qos_remove_request(&dfc->req_max_freq);
+free_table:
kfree(dfc->freq_table);
free_dfc:
kfree(dfc);
@@ -587,23 +473,72 @@ struct thermal_cooling_device *devfreq_cooling_register(struct devfreq *df)
EXPORT_SYMBOL_GPL(devfreq_cooling_register);
/**
+ * devfreq_cooling_em_register() - Register devfreq cooling device with
+ * power information and automatically register Energy Model (EM)
+ * @df: Pointer to devfreq device.
+ * @dfc_power: Pointer to devfreq_cooling_power.
+ *
+ * Register a devfreq cooling device and automatically register EM. The
+ * available OPPs must be registered for the device.
+ *
+ * If @dfc_power is provided, the cooling device is registered with the
+ * power extensions. It is using the simple Energy Model which requires
+ * "dynamic-power-coefficient" a devicetree property. To not break drivers
+ * which miss that DT property, the function won't bail out when the EM
+ * registration failed. The cooling device will be registered if everything
+ * else is OK.
+ */
+struct thermal_cooling_device *
+devfreq_cooling_em_register(struct devfreq *df,
+ struct devfreq_cooling_power *dfc_power)
+{
+ struct thermal_cooling_device *cdev;
+ struct device *dev;
+ int ret;
+
+ if (IS_ERR_OR_NULL(df))
+ return ERR_PTR(-EINVAL);
+
+ dev = df->dev.parent;
+
+ ret = dev_pm_opp_of_register_em(dev, NULL);
+ if (ret)
+ dev_dbg(dev, "Unable to register EM for devfreq cooling device (%d)\n",
+ ret);
+
+ cdev = of_devfreq_cooling_register_power(dev->of_node, df, dfc_power);
+
+ if (IS_ERR_OR_NULL(cdev))
+ em_dev_unregister_perf_domain(dev);
+
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(devfreq_cooling_em_register);
+
+/**
* devfreq_cooling_unregister() - Unregister devfreq cooling device.
* @cdev: Pointer to devfreq cooling device to unregister.
+ *
+ * Unregisters devfreq cooling device and related Energy Model if it was
+ * present.
*/
void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
{
struct devfreq_cooling_device *dfc;
+ struct device *dev;
- if (!cdev)
+ if (IS_ERR_OR_NULL(cdev))
return;
dfc = cdev->devdata;
+ dev = dfc->devfreq->dev.parent;
thermal_cooling_device_unregister(dfc->cdev);
- ida_simple_remove(&devfreq_ida, dfc->id);
- kfree(dfc->power_table);
- kfree(dfc->freq_table);
+ dev_pm_qos_remove_request(&dfc->req_max_freq);
+
+ em_dev_unregister_perf_domain(dev);
+ kfree(dfc->freq_table);
kfree(dfc);
}
EXPORT_SYMBOL_GPL(devfreq_cooling_unregister);
diff --git a/drivers/thermal/dove_thermal.c b/drivers/thermal/dove_thermal.c
index 75901ced4a62..73182eb94bc0 100644
--- a/drivers/thermal/dove_thermal.c
+++ b/drivers/thermal/dove_thermal.c
@@ -153,6 +153,12 @@ static int dove_thermal_probe(struct platform_device *pdev)
return PTR_ERR(thermal);
}
+ ret = thermal_zone_device_enable(thermal);
+ if (ret) {
+ thermal_zone_device_unregister(thermal);
+ return ret;
+ }
+
platform_set_drvdata(pdev, thermal);
return 0;
diff --git a/drivers/thermal/gov_bang_bang.c b/drivers/thermal/gov_bang_bang.c
index 991a1c54296d..a08bbe33be96 100644
--- a/drivers/thermal/gov_bang_bang.c
+++ b/drivers/thermal/gov_bang_bang.c
@@ -31,8 +31,6 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
trip, trip_temp, tz->temperature,
trip_hyst);
- mutex_lock(&tz->lock);
-
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip)
continue;
@@ -65,8 +63,6 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
instance->cdev->updated = false; /* cdev needs update */
mutex_unlock(&instance->cdev->lock);
}
-
- mutex_unlock(&tz->lock);
}
/**
@@ -100,15 +96,13 @@ static int bang_bang_control(struct thermal_zone_device *tz, int trip)
{
struct thermal_instance *instance;
- thermal_zone_trip_update(tz, trip);
+ lockdep_assert_held(&tz->lock);
- mutex_lock(&tz->lock);
+ thermal_zone_trip_update(tz, trip);
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
thermal_cdev_update(instance->cdev);
- mutex_unlock(&tz->lock);
-
return 0;
}
diff --git a/drivers/thermal/fair_share.c b/drivers/thermal/gov_fair_share.c
index aaa07180ab48..a4ee4661e9cc 100644
--- a/drivers/thermal/fair_share.c
+++ b/drivers/thermal/gov_fair_share.c
@@ -25,10 +25,10 @@ static int get_trip_level(struct thermal_zone_device *tz)
int trip_temp;
enum thermal_trip_type trip_type;
- if (tz->trips == 0 || !tz->ops->get_trip_temp)
+ if (tz->num_trips == 0 || !tz->ops->get_trip_temp)
return 0;
- for (count = 0; count < tz->trips; count++) {
+ for (count = 0; count < tz->num_trips; count++) {
tz->ops->get_trip_temp(tz, count, &trip_temp);
if (tz->temperature < trip_temp)
break;
@@ -53,7 +53,7 @@ static long get_target_state(struct thermal_zone_device *tz,
cdev->ops->get_max_state(cdev, &max_state);
- return (long)(percentage * level * max_state) / (100 * tz->trips);
+ return (long)(percentage * level * max_state) / (100 * tz->num_trips);
}
/**
@@ -82,6 +82,8 @@ static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
int total_instance = 0;
int cur_trip_level = get_trip_level(tz);
+ lockdep_assert_held(&tz->lock);
+
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip)
continue;
@@ -105,11 +107,11 @@ static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
instance->target = get_target_state(tz, cdev, percentage,
cur_trip_level);
- mutex_lock(&instance->cdev->lock);
- instance->cdev->updated = false;
- mutex_unlock(&instance->cdev->lock);
- thermal_cdev_update(cdev);
+ mutex_lock(&cdev->lock);
+ __thermal_cdev_update(cdev);
+ mutex_unlock(&cdev->lock);
}
+
return 0;
}
diff --git a/drivers/thermal/power_allocator.c b/drivers/thermal/gov_power_allocator.c
index 44636475b2a3..2d1aeaba38a8 100644
--- a/drivers/thermal/power_allocator.c
+++ b/drivers/thermal/gov_power_allocator.c
@@ -1,16 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
/*
* A power allocator to manage temperature
*
* Copyright (C) 2014 ARM Ltd.
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * This program is distributed "as is" WITHOUT ANY WARRANTY of any
- * kind, whether express or implied; 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) "Power allocator: " fmt
@@ -70,6 +63,8 @@ static inline s64 div_frac(s64 x, s64 y)
* @trip_max_desired_temperature: last passive trip point of the thermal
* zone. The temperature we are
* controlling for.
+ * @sustainable_power: Sustainable power (heat) that this thermal zone can
+ * dissipate
*/
struct power_allocator_params {
bool allocated_tzp;
@@ -77,6 +72,7 @@ struct power_allocator_params {
s32 prev_err;
int trip_switch_on;
int trip_max_desired_temperature;
+ u32 sustainable_power;
};
/**
@@ -103,7 +99,10 @@ static u32 estimate_sustainable_power(struct thermal_zone_device *tz)
if (instance->trip != params->trip_max_desired_temperature)
continue;
- if (power_actor_get_min_power(cdev, tz, &min_power))
+ if (!cdev_is_power_actor(cdev))
+ continue;
+
+ if (cdev->ops->state2power(cdev, instance->upper, &min_power))
continue;
sustainable_power += min_power;
@@ -118,26 +117,18 @@ static u32 estimate_sustainable_power(struct thermal_zone_device *tz)
* @sustainable_power: sustainable power for the thermal zone
* @trip_switch_on: trip point number for the switch on temperature
* @control_temp: target temperature for the power allocator governor
- * @force: whether to force the update of the constants
*
* This function is used to update the estimation of the PID
* controller constants in struct thermal_zone_parameters.
- * Sustainable power is provided in case it was estimated. The
- * estimated sustainable_power should not be stored in the
- * thermal_zone_parameters so it has to be passed explicitly to this
- * function.
- *
- * If @force is not set, the values in the thermal zone's parameters
- * are preserved if they are not zero. If @force is set, the values
- * in thermal zone's parameters are overwritten.
*/
static void estimate_pid_constants(struct thermal_zone_device *tz,
u32 sustainable_power, int trip_switch_on,
- int control_temp, bool force)
+ int control_temp)
{
int ret;
int switch_on_temp;
u32 temperature_threshold;
+ s32 k_i;
ret = tz->ops->get_trip_temp(tz, trip_switch_on, &switch_on_temp);
if (ret)
@@ -155,16 +146,15 @@ static void estimate_pid_constants(struct thermal_zone_device *tz,
if (!temperature_threshold)
return;
- if (!tz->tzp->k_po || force)
- tz->tzp->k_po = int_to_frac(sustainable_power) /
- temperature_threshold;
+ tz->tzp->k_po = int_to_frac(sustainable_power) /
+ temperature_threshold;
+
+ tz->tzp->k_pu = int_to_frac(2 * sustainable_power) /
+ temperature_threshold;
- if (!tz->tzp->k_pu || force)
- tz->tzp->k_pu = int_to_frac(2 * sustainable_power) /
- temperature_threshold;
+ k_i = tz->tzp->k_pu / 10;
+ tz->tzp->k_i = k_i > 0 ? k_i : 1;
- if (!tz->tzp->k_i || force)
- tz->tzp->k_i = int_to_frac(10) / 1000;
/*
* The default for k_d and integral_cutoff is 0, so we can
* leave them as they are.
@@ -172,6 +162,41 @@ static void estimate_pid_constants(struct thermal_zone_device *tz,
}
/**
+ * get_sustainable_power() - Get the right sustainable power
+ * @tz: thermal zone for which to estimate the constants
+ * @params: parameters for the power allocator governor
+ * @control_temp: target temperature for the power allocator governor
+ *
+ * This function is used for getting the proper sustainable power value based
+ * on variables which might be updated by the user sysfs interface. If that
+ * happen the new value is going to be estimated and updated. It is also used
+ * after thermal zone binding, where the initial values where set to 0.
+ */
+static u32 get_sustainable_power(struct thermal_zone_device *tz,
+ struct power_allocator_params *params,
+ int control_temp)
+{
+ u32 sustainable_power;
+
+ if (!tz->tzp->sustainable_power)
+ sustainable_power = estimate_sustainable_power(tz);
+ else
+ sustainable_power = tz->tzp->sustainable_power;
+
+ /* Check if it's init value 0 or there was update via sysfs */
+ if (sustainable_power != params->sustainable_power) {
+ estimate_pid_constants(tz, sustainable_power,
+ params->trip_switch_on, control_temp);
+
+ /* Do the estimation only once and make available in sysfs */
+ tz->tzp->sustainable_power = sustainable_power;
+ params->sustainable_power = sustainable_power;
+ }
+
+ return sustainable_power;
+}
+
+/**
* pid_controller() - PID controller
* @tz: thermal zone we are operating in
* @control_temp: the target temperature in millicelsius
@@ -200,14 +225,7 @@ static u32 pid_controller(struct thermal_zone_device *tz,
max_power_frac = int_to_frac(max_allocatable_power);
- if (tz->tzp->sustainable_power) {
- sustainable_power = tz->tzp->sustainable_power;
- } else {
- sustainable_power = estimate_sustainable_power(tz);
- estimate_pid_constants(tz, sustainable_power,
- params->trip_switch_on, control_temp,
- true);
- }
+ sustainable_power = get_sustainable_power(tz, params, control_temp);
err = control_temp - tz->temperature;
err = int_to_frac(err);
@@ -240,7 +258,7 @@ static u32 pid_controller(struct thermal_zone_device *tz,
* power being applied, slowing down the controller)
*/
d = mul_frac(tz->tzp->k_d, err - params->prev_err);
- d = div_frac(d, tz->passive_delay);
+ d = div_frac(d, jiffies_to_msecs(tz->passive_delay_jiffies));
params->prev_err = err;
power_range = p + i + d;
@@ -259,6 +277,37 @@ static u32 pid_controller(struct thermal_zone_device *tz,
}
/**
+ * power_actor_set_power() - limit the maximum power a cooling device consumes
+ * @cdev: pointer to &thermal_cooling_device
+ * @instance: thermal instance to update
+ * @power: the power in milliwatts
+ *
+ * Set the cooling device to consume at most @power milliwatts. The limit is
+ * expected to be a cap at the maximum power consumption.
+ *
+ * Return: 0 on success, -EINVAL if the cooling device does not
+ * implement the power actor API or -E* for other failures.
+ */
+static int
+power_actor_set_power(struct thermal_cooling_device *cdev,
+ struct thermal_instance *instance, u32 power)
+{
+ unsigned long state;
+ int ret;
+
+ ret = cdev->ops->power2state(cdev, power, &state);
+ if (ret)
+ return ret;
+
+ instance->target = clamp_val(state, instance->lower, instance->upper);
+ mutex_lock(&cdev->lock);
+ __thermal_cdev_update(cdev);
+ mutex_unlock(&cdev->lock);
+
+ return 0;
+}
+
+/**
* divvy_up_power() - divvy the allocated power between the actors
* @req_power: each actor's requested power
* @max_power: each actor's maximum available power
@@ -324,9 +373,11 @@ static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors,
*/
extra_power = min(extra_power, capped_extra_power);
if (capped_extra_power > 0)
- for (i = 0; i < num_actors; i++)
- granted_power[i] += (extra_actor_power[i] *
- extra_power) / capped_extra_power;
+ for (i = 0; i < num_actors; i++) {
+ u64 extra_range = (u64)extra_actor_power[i] * extra_power;
+ granted_power[i] += DIV_ROUND_CLOSEST_ULL(extra_range,
+ capped_extra_power);
+ }
}
static int allocate_power(struct thermal_zone_device *tz,
@@ -341,8 +392,6 @@ static int allocate_power(struct thermal_zone_device *tz,
int i, num_actors, total_weight, ret = 0;
int trip_max_desired_temperature = params->trip_max_desired_temperature;
- mutex_lock(&tz->lock);
-
num_actors = 0;
total_weight = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
@@ -353,10 +402,8 @@ static int allocate_power(struct thermal_zone_device *tz,
}
}
- if (!num_actors) {
- ret = -ENODEV;
- goto unlock;
- }
+ if (!num_actors)
+ return -ENODEV;
/*
* We need to allocate five arrays of the same size:
@@ -370,10 +417,8 @@ static int allocate_power(struct thermal_zone_device *tz,
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*extra_actor_power));
BUILD_BUG_ON(sizeof(*req_power) != sizeof(*weighted_req_power));
req_power = kcalloc(num_actors * 5, sizeof(*req_power), GFP_KERNEL);
- if (!req_power) {
- ret = -ENOMEM;
- goto unlock;
- }
+ if (!req_power)
+ return -ENOMEM;
max_power = &req_power[num_actors];
granted_power = &req_power[2 * num_actors];
@@ -395,7 +440,7 @@ static int allocate_power(struct thermal_zone_device *tz,
if (!cdev_is_power_actor(cdev))
continue;
- if (cdev->ops->get_requested_power(cdev, tz, &req_power[i]))
+ if (cdev->ops->get_requested_power(cdev, &req_power[i]))
continue;
if (!total_weight)
@@ -405,7 +450,8 @@ static int allocate_power(struct thermal_zone_device *tz,
weighted_req_power[i] = frac_to_int(weight * req_power[i]);
- if (power_actor_get_max_power(cdev, tz, &max_power[i]))
+ if (cdev->ops->state2power(cdev, instance->lower,
+ &max_power[i]))
continue;
total_req_power += req_power[i];
@@ -444,8 +490,6 @@ static int allocate_power(struct thermal_zone_device *tz,
control_temp - tz->temperature);
kfree(req_power);
-unlock:
- mutex_unlock(&tz->lock);
return ret;
}
@@ -475,7 +519,7 @@ static void get_governor_trips(struct thermal_zone_device *tz,
last_active = INVALID_TRIP;
last_passive = INVALID_TRIP;
- for (i = 0; i < tz->trips; i++) {
+ for (i = 0; i < tz->num_trips; i++) {
enum thermal_trip_type type;
int ret;
@@ -518,24 +562,61 @@ static void reset_pid_controller(struct power_allocator_params *params)
params->prev_err = 0;
}
-static void allow_maximum_power(struct thermal_zone_device *tz)
+static void allow_maximum_power(struct thermal_zone_device *tz, bool update)
{
struct thermal_instance *instance;
struct power_allocator_params *params = tz->governor_data;
+ u32 req_power;
- mutex_lock(&tz->lock);
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ struct thermal_cooling_device *cdev = instance->cdev;
+
if ((instance->trip != params->trip_max_desired_temperature) ||
(!cdev_is_power_actor(instance->cdev)))
continue;
instance->target = 0;
mutex_lock(&instance->cdev->lock);
- instance->cdev->updated = false;
+ /*
+ * Call for updating the cooling devices local stats and avoid
+ * periods of dozen of seconds when those have not been
+ * maintained.
+ */
+ cdev->ops->get_requested_power(cdev, &req_power);
+
+ if (update)
+ __thermal_cdev_update(instance->cdev);
+
mutex_unlock(&instance->cdev->lock);
- thermal_cdev_update(instance->cdev);
}
- mutex_unlock(&tz->lock);
+}
+
+/**
+ * check_power_actors() - Check all cooling devices and warn when they are
+ * not power actors
+ * @tz: thermal zone to operate on
+ *
+ * Check all cooling devices in the @tz and warn every time they are missing
+ * power actor API. The warning should help to investigate the issue, which
+ * could be e.g. lack of Energy Model for a given device.
+ *
+ * Return: 0 on success, -EINVAL if any cooling device does not implement
+ * the power actor API.
+ */
+static int check_power_actors(struct thermal_zone_device *tz)
+{
+ struct thermal_instance *instance;
+ int ret = 0;
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (!cdev_is_power_actor(instance->cdev)) {
+ dev_warn(&tz->device, "power_allocator: %s is not a power actor\n",
+ instance->cdev->type);
+ ret = -EINVAL;
+ }
+ }
+
+ return ret;
}
/**
@@ -545,7 +626,8 @@ static void allow_maximum_power(struct thermal_zone_device *tz)
* Initialize the PID controller parameters and bind it to the thermal
* zone.
*
- * Return: 0 on success, or -ENOMEM if we ran out of memory.
+ * Return: 0 on success, or -ENOMEM if we ran out of memory, or -EINVAL
+ * when there are unsupported cooling devices in the @tz.
*/
static int power_allocator_bind(struct thermal_zone_device *tz)
{
@@ -553,6 +635,10 @@ static int power_allocator_bind(struct thermal_zone_device *tz)
struct power_allocator_params *params;
int control_temp;
+ ret = check_power_actors(tz);
+ if (ret)
+ return ret;
+
params = kzalloc(sizeof(*params), GFP_KERNEL);
if (!params)
return -ENOMEM;
@@ -572,14 +658,14 @@ static int power_allocator_bind(struct thermal_zone_device *tz)
get_governor_trips(tz, params);
- if (tz->trips > 0) {
+ if (tz->num_trips > 0) {
ret = tz->ops->get_trip_temp(tz,
params->trip_max_desired_temperature,
&control_temp);
if (!ret)
estimate_pid_constants(tz, tz->tzp->sustainable_power,
params->trip_switch_on,
- control_temp, false);
+ control_temp);
}
reset_pid_controller(params);
@@ -614,6 +700,9 @@ static int power_allocator_throttle(struct thermal_zone_device *tz, int trip)
int ret;
int switch_on_temp, control_temp;
struct power_allocator_params *params = tz->governor_data;
+ bool update;
+
+ lockdep_assert_held(&tz->lock);
/*
* We get called for every trip point but we only need to do
@@ -625,9 +714,10 @@ static int power_allocator_throttle(struct thermal_zone_device *tz, int trip)
ret = tz->ops->get_trip_temp(tz, params->trip_switch_on,
&switch_on_temp);
if (!ret && (tz->temperature < switch_on_temp)) {
+ update = (tz->last_temperature >= switch_on_temp);
tz->passive = 0;
reset_pid_controller(params);
- allow_maximum_power(tz);
+ allow_maximum_power(tz, update);
return 0;
}
diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/gov_step_wise.c
index 2ae7198d3067..cdd3354bc27f 100644
--- a/drivers/thermal/step_wise.c
+++ b/drivers/thermal/gov_step_wise.c
@@ -11,6 +11,7 @@
*/
#include <linux/thermal.h>
+#include <linux/minmax.h>
#include <trace/events/thermal.h>
#include "thermal_core.h"
@@ -52,10 +53,7 @@ static unsigned long get_target_state(struct thermal_instance *instance,
if (!instance->initialized) {
if (throttle) {
- next_target = (cur_state + 1) >= instance->upper ?
- instance->upper :
- ((cur_state + 1) < instance->lower ?
- instance->lower : (cur_state + 1));
+ next_target = clamp((cur_state + 1), instance->lower, instance->upper);
} else {
next_target = THERMAL_NO_TARGET;
}
@@ -66,35 +64,19 @@ static unsigned long get_target_state(struct thermal_instance *instance,
switch (trend) {
case THERMAL_TREND_RAISING:
if (throttle) {
- next_target = cur_state < instance->upper ?
- (cur_state + 1) : instance->upper;
- if (next_target < instance->lower)
- next_target = instance->lower;
+ next_target = clamp((cur_state + 1), instance->lower, instance->upper);
}
break;
- case THERMAL_TREND_RAISE_FULL:
- if (throttle)
- next_target = instance->upper;
- break;
case THERMAL_TREND_DROPPING:
if (cur_state <= instance->lower) {
if (!throttle)
next_target = THERMAL_NO_TARGET;
} else {
if (!throttle) {
- next_target = cur_state - 1;
- if (next_target > instance->upper)
- next_target = instance->upper;
+ next_target = clamp((cur_state - 1), instance->lower, instance->upper);
}
}
break;
- case THERMAL_TREND_DROP_FULL:
- if (cur_state == instance->lower) {
- if (!throttle)
- next_target = THERMAL_NO_TARGET;
- } else
- next_target = instance->lower;
- break;
default:
break;
}
@@ -109,7 +91,7 @@ static void update_passive_instance(struct thermal_zone_device *tz,
* If value is +1, activate a passive instance.
* If value is -1, deactivate a passive instance.
*/
- if (type == THERMAL_TRIP_PASSIVE || type == THERMAL_TRIPS_NONE)
+ if (type == THERMAL_TRIP_PASSIVE)
tz->passive += value;
}
@@ -122,13 +104,8 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
bool throttle = false;
int old_target;
- if (trip == THERMAL_TRIPS_NONE) {
- trip_temp = tz->forced_passive;
- trip_type = THERMAL_TRIPS_NONE;
- } else {
- tz->ops->get_trip_temp(tz, trip, &trip_temp);
- tz->ops->get_trip_type(tz, trip, &trip_type);
- }
+ tz->ops->get_trip_temp(tz, trip, &trip_temp);
+ tz->ops->get_trip_type(tz, trip, &trip_type);
trend = get_tz_trend(tz, trip);
@@ -140,8 +117,6 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n",
trip, trip_type, trip_temp, trend, throttle);
- mutex_lock(&tz->lock);
-
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip)
continue;
@@ -168,8 +143,6 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
instance->cdev->updated = false; /* cdev needs update */
mutex_unlock(&instance->cdev->lock);
}
-
- mutex_unlock(&tz->lock);
}
/**
@@ -187,18 +160,13 @@ static int step_wise_throttle(struct thermal_zone_device *tz, int trip)
{
struct thermal_instance *instance;
- thermal_zone_trip_update(tz, trip);
-
- if (tz->forced_passive)
- thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE);
+ lockdep_assert_held(&tz->lock);
- mutex_lock(&tz->lock);
+ thermal_zone_trip_update(tz, trip);
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
thermal_cdev_update(instance->cdev);
- mutex_unlock(&tz->lock);
-
return 0;
}
diff --git a/drivers/thermal/user_space.c b/drivers/thermal/gov_user_space.c
index 293cffd9c8ad..8bc1c22aaf03 100644
--- a/drivers/thermal/user_space.c
+++ b/drivers/thermal/gov_user_space.c
@@ -10,11 +10,18 @@
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
-#include <linux/thermal.h>
#include <linux/slab.h>
+#include <linux/thermal.h>
#include "thermal_core.h"
+static int user_space_bind(struct thermal_zone_device *tz)
+{
+ pr_info_once("Consider using thermal netlink events interface\n");
+
+ return 0;
+}
+
/**
* notify_user_space - Notifies user space about thermal events
* @tz: thermal_zone_device
@@ -27,7 +34,8 @@ static int notify_user_space(struct thermal_zone_device *tz, int trip)
char *thermal_prop[5];
int i;
- mutex_lock(&tz->lock);
+ lockdep_assert_held(&tz->lock);
+
thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", tz->type);
thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", tz->temperature);
thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=%d", trip);
@@ -36,12 +44,13 @@ static int notify_user_space(struct thermal_zone_device *tz, int trip)
kobject_uevent_env(&tz->device.kobj, KOBJ_CHANGE, thermal_prop);
for (i = 0; i < 4; ++i)
kfree(thermal_prop[i]);
- mutex_unlock(&tz->lock);
+
return 0;
}
static struct thermal_governor thermal_gov_user_space = {
.name = "user_space",
.throttle = notify_user_space,
+ .bind_to_tz = user_space_bind,
};
THERMAL_GOVERNOR_DECLARE(thermal_gov_user_space);
diff --git a/drivers/thermal/hisi_thermal.c b/drivers/thermal/hisi_thermal.c
index 2d26ae80e202..d6974db7aaf7 100644
--- a/drivers/thermal/hisi_thermal.c
+++ b/drivers/thermal/hisi_thermal.c
@@ -1,20 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
- * Hisilicon thermal sensor driver
+ * HiSilicon thermal sensor driver
*
- * Copyright (c) 2014-2015 Hisilicon Limited.
+ * Copyright (c) 2014-2015 HiSilicon Limited.
* Copyright (c) 2014-2015 Linaro Limited.
*
* Xinwei Kong <kong.kongxinwei@hisilicon.com>
* Leo Yan <leo.yan@linaro.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * This program is distributed "as is" WITHOUT ANY WARRANTY of any
- * kind, whether express or implied; without even the implied warranty
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
*/
#include <linux/cpufreq.h>
@@ -442,9 +434,9 @@ static int hi3660_thermal_probe(struct hisi_thermal_data *data)
return 0;
}
-static int hisi_thermal_get_temp(void *__data, int *temp)
+static int hisi_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct hisi_thermal_sensor *sensor = __data;
+ struct hisi_thermal_sensor *sensor = tz->devdata;
struct hisi_thermal_data *data = sensor->data;
*temp = data->ops->get_temp(sensor);
@@ -455,7 +447,7 @@ static int hisi_thermal_get_temp(void *__data, int *temp)
return 0;
}
-static const struct thermal_zone_of_device_ops hisi_of_thermal_ops = {
+static const struct thermal_zone_device_ops hisi_of_thermal_ops = {
.get_temp = hisi_thermal_get_temp,
};
@@ -467,7 +459,7 @@ static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev)
data->ops->irq_handler(sensor);
- hisi_thermal_get_temp(sensor, &temp);
+ temp = data->ops->get_temp(sensor);
if (temp >= sensor->thres_temp) {
dev_crit(&data->pdev->dev,
@@ -492,9 +484,9 @@ static int hisi_thermal_register_sensor(struct platform_device *pdev,
int ret, i;
const struct thermal_trip *trip;
- sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
- sensor->id, sensor,
- &hisi_of_thermal_ops);
+ sensor->tzd = devm_thermal_of_zone_register(&pdev->dev,
+ sensor->id, sensor,
+ &hisi_of_thermal_ops);
if (IS_ERR(sensor->tzd)) {
ret = PTR_ERR(sensor->tzd);
sensor->tzd = NULL;
@@ -549,8 +541,10 @@ static void hisi_thermal_toggle_sensor(struct hisi_thermal_sensor *sensor,
{
struct thermal_zone_device *tzd = sensor->tzd;
- tzd->ops->set_mode(tzd,
- on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED);
+ if (on)
+ thermal_zone_device_enable(tzd);
+ else
+ thermal_zone_device_disable(tzd);
}
static int hisi_thermal_probe(struct platform_device *pdev)
@@ -570,10 +564,8 @@ static int hisi_thermal_probe(struct platform_device *pdev)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
data->regs = devm_ioremap_resource(dev, res);
- if (IS_ERR(data->regs)) {
- dev_err(dev, "failed to get io address\n");
+ if (IS_ERR(data->regs))
return PTR_ERR(data->regs);
- }
ret = data->ops->probe(data);
if (ret)
@@ -629,7 +621,6 @@ static int hisi_thermal_remove(struct platform_device *pdev)
return 0;
}
-#ifdef CONFIG_PM_SLEEP
static int hisi_thermal_suspend(struct device *dev)
{
struct hisi_thermal_data *data = dev_get_drvdata(dev);
@@ -651,15 +642,14 @@ static int hisi_thermal_resume(struct device *dev)
return ret;
}
-#endif
-static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
+static DEFINE_SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
hisi_thermal_suspend, hisi_thermal_resume);
static struct platform_driver hisi_thermal_driver = {
.driver = {
.name = "hisi_thermal",
- .pm = &hisi_thermal_pm_ops,
+ .pm = pm_sleep_ptr(&hisi_thermal_pm_ops),
.of_match_table = of_hisi_thermal_match,
},
.probe = hisi_thermal_probe,
@@ -670,5 +660,5 @@ module_platform_driver(hisi_thermal_driver);
MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>");
MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>");
-MODULE_DESCRIPTION("Hisilicon thermal driver");
+MODULE_DESCRIPTION("HiSilicon thermal driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/imx8mm_thermal.c b/drivers/thermal/imx8mm_thermal.c
new file mode 100644
index 000000000000..e2c2673025a7
--- /dev/null
+++ b/drivers/thermal/imx8mm_thermal.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 NXP.
+ *
+ * Author: Anson Huang <Anson.Huang@nxp.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#include "thermal_core.h"
+
+#define TER 0x0 /* TMU enable */
+#define TPS 0x4
+#define TRITSR 0x20 /* TMU immediate temp */
+
+#define TER_ADC_PD BIT(30)
+#define TER_EN BIT(31)
+#define TRITSR_TEMP0_VAL_MASK 0xff
+#define TRITSR_TEMP1_VAL_MASK 0xff0000
+
+#define PROBE_SEL_ALL GENMASK(31, 30)
+
+#define probe_status_offset(x) (30 + x)
+#define SIGN_BIT BIT(7)
+#define TEMP_VAL_MASK GENMASK(6, 0)
+
+#define VER1_TEMP_LOW_LIMIT 10000
+#define VER2_TEMP_LOW_LIMIT -40000
+#define VER2_TEMP_HIGH_LIMIT 125000
+
+#define TMU_VER1 0x1
+#define TMU_VER2 0x2
+
+struct thermal_soc_data {
+ u32 num_sensors;
+ u32 version;
+ int (*get_temp)(void *, int *);
+};
+
+struct tmu_sensor {
+ struct imx8mm_tmu *priv;
+ u32 hw_id;
+ struct thermal_zone_device *tzd;
+};
+
+struct imx8mm_tmu {
+ void __iomem *base;
+ struct clk *clk;
+ const struct thermal_soc_data *socdata;
+ struct tmu_sensor sensors[];
+};
+
+static int imx8mm_tmu_get_temp(void *data, int *temp)
+{
+ struct tmu_sensor *sensor = data;
+ struct imx8mm_tmu *tmu = sensor->priv;
+ u32 val;
+
+ val = readl_relaxed(tmu->base + TRITSR) & TRITSR_TEMP0_VAL_MASK;
+ *temp = val * 1000;
+ if (*temp < VER1_TEMP_LOW_LIMIT)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int imx8mp_tmu_get_temp(void *data, int *temp)
+{
+ struct tmu_sensor *sensor = data;
+ struct imx8mm_tmu *tmu = sensor->priv;
+ unsigned long val;
+ bool ready;
+
+ val = readl_relaxed(tmu->base + TRITSR);
+ ready = test_bit(probe_status_offset(sensor->hw_id), &val);
+ if (!ready)
+ return -EAGAIN;
+
+ val = sensor->hw_id ? FIELD_GET(TRITSR_TEMP1_VAL_MASK, val) :
+ FIELD_GET(TRITSR_TEMP0_VAL_MASK, val);
+ if (val & SIGN_BIT) /* negative */
+ val = (~(val & TEMP_VAL_MASK) + 1);
+
+ *temp = val * 1000;
+ if (*temp < VER2_TEMP_LOW_LIMIT || *temp > VER2_TEMP_HIGH_LIMIT)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int tmu_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct tmu_sensor *sensor = tz->devdata;
+ struct imx8mm_tmu *tmu = sensor->priv;
+
+ return tmu->socdata->get_temp(sensor, temp);
+}
+
+static const struct thermal_zone_device_ops tmu_tz_ops = {
+ .get_temp = tmu_get_temp,
+};
+
+static void imx8mm_tmu_enable(struct imx8mm_tmu *tmu, bool enable)
+{
+ u32 val;
+
+ val = readl_relaxed(tmu->base + TER);
+ val = enable ? (val | TER_EN) : (val & ~TER_EN);
+ if (tmu->socdata->version == TMU_VER2)
+ val = enable ? (val & ~TER_ADC_PD) : (val | TER_ADC_PD);
+ writel_relaxed(val, tmu->base + TER);
+}
+
+static void imx8mm_tmu_probe_sel_all(struct imx8mm_tmu *tmu)
+{
+ u32 val;
+
+ val = readl_relaxed(tmu->base + TPS);
+ val |= PROBE_SEL_ALL;
+ writel_relaxed(val, tmu->base + TPS);
+}
+
+static int imx8mm_tmu_probe(struct platform_device *pdev)
+{
+ const struct thermal_soc_data *data;
+ struct imx8mm_tmu *tmu;
+ int ret;
+ int i;
+
+ data = of_device_get_match_data(&pdev->dev);
+
+ tmu = devm_kzalloc(&pdev->dev, struct_size(tmu, sensors,
+ data->num_sensors), GFP_KERNEL);
+ if (!tmu)
+ return -ENOMEM;
+
+ tmu->socdata = data;
+
+ tmu->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(tmu->base))
+ return PTR_ERR(tmu->base);
+
+ tmu->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(tmu->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(tmu->clk),
+ "failed to get tmu clock\n");
+
+ ret = clk_prepare_enable(tmu->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable tmu clock: %d\n", ret);
+ return ret;
+ }
+
+ /* disable the monitor during initialization */
+ imx8mm_tmu_enable(tmu, false);
+
+ for (i = 0; i < data->num_sensors; i++) {
+ tmu->sensors[i].priv = tmu;
+ tmu->sensors[i].tzd =
+ devm_thermal_of_zone_register(&pdev->dev, i,
+ &tmu->sensors[i],
+ &tmu_tz_ops);
+ if (IS_ERR(tmu->sensors[i].tzd)) {
+ ret = PTR_ERR(tmu->sensors[i].tzd);
+ dev_err(&pdev->dev,
+ "failed to register thermal zone sensor[%d]: %d\n",
+ i, ret);
+ goto disable_clk;
+ }
+ tmu->sensors[i].hw_id = i;
+ }
+
+ platform_set_drvdata(pdev, tmu);
+
+ /* enable all the probes for V2 TMU */
+ if (tmu->socdata->version == TMU_VER2)
+ imx8mm_tmu_probe_sel_all(tmu);
+
+ /* enable the monitor */
+ imx8mm_tmu_enable(tmu, true);
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(tmu->clk);
+ return ret;
+}
+
+static int imx8mm_tmu_remove(struct platform_device *pdev)
+{
+ struct imx8mm_tmu *tmu = platform_get_drvdata(pdev);
+
+ /* disable TMU */
+ imx8mm_tmu_enable(tmu, false);
+
+ clk_disable_unprepare(tmu->clk);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct thermal_soc_data imx8mm_tmu_data = {
+ .num_sensors = 1,
+ .version = TMU_VER1,
+ .get_temp = imx8mm_tmu_get_temp,
+};
+
+static struct thermal_soc_data imx8mp_tmu_data = {
+ .num_sensors = 2,
+ .version = TMU_VER2,
+ .get_temp = imx8mp_tmu_get_temp,
+};
+
+static const struct of_device_id imx8mm_tmu_table[] = {
+ { .compatible = "fsl,imx8mm-tmu", .data = &imx8mm_tmu_data, },
+ { .compatible = "fsl,imx8mp-tmu", .data = &imx8mp_tmu_data, },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx8mm_tmu_table);
+
+static struct platform_driver imx8mm_tmu = {
+ .driver = {
+ .name = "i.mx8mm_thermal",
+ .of_match_table = imx8mm_tmu_table,
+ },
+ .probe = imx8mm_tmu_probe,
+ .remove = imx8mm_tmu_remove,
+};
+module_platform_driver(imx8mm_tmu);
+
+MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
+MODULE_DESCRIPTION("i.MX8MM Thermal Monitor Unit driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/imx_sc_thermal.c b/drivers/thermal/imx_sc_thermal.c
new file mode 100644
index 000000000000..5d92b70a5d53
--- /dev/null
+++ b/drivers/thermal/imx_sc_thermal.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018-2020 NXP.
+ */
+
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include <linux/err.h>
+#include <linux/firmware/imx/sci.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "thermal_core.h"
+#include "thermal_hwmon.h"
+
+#define IMX_SC_MISC_FUNC_GET_TEMP 13
+
+static struct imx_sc_ipc *thermal_ipc_handle;
+
+struct imx_sc_sensor {
+ struct thermal_zone_device *tzd;
+ u32 resource_id;
+};
+
+struct req_get_temp {
+ u16 resource_id;
+ u8 type;
+} __packed __aligned(4);
+
+struct resp_get_temp {
+ s16 celsius;
+ s8 tenths;
+} __packed __aligned(4);
+
+struct imx_sc_msg_misc_get_temp {
+ struct imx_sc_rpc_msg hdr;
+ union {
+ struct req_get_temp req;
+ struct resp_get_temp resp;
+ } data;
+} __packed __aligned(4);
+
+static int imx_sc_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct imx_sc_msg_misc_get_temp msg;
+ struct imx_sc_rpc_msg *hdr = &msg.hdr;
+ struct imx_sc_sensor *sensor = tz->devdata;
+ int ret;
+
+ msg.data.req.resource_id = sensor->resource_id;
+ msg.data.req.type = IMX_SC_C_TEMP;
+
+ hdr->ver = IMX_SC_RPC_VERSION;
+ hdr->svc = IMX_SC_RPC_SVC_MISC;
+ hdr->func = IMX_SC_MISC_FUNC_GET_TEMP;
+ hdr->size = 2;
+
+ ret = imx_scu_call_rpc(thermal_ipc_handle, &msg, true);
+ if (ret) {
+ dev_err(&sensor->tzd->device, "read temp sensor %d failed, ret %d\n",
+ sensor->resource_id, ret);
+ return ret;
+ }
+
+ *temp = msg.data.resp.celsius * 1000 + msg.data.resp.tenths * 100;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops imx_sc_thermal_ops = {
+ .get_temp = imx_sc_thermal_get_temp,
+};
+
+static int imx_sc_thermal_probe(struct platform_device *pdev)
+{
+ struct imx_sc_sensor *sensor;
+ const int *resource_id;
+ int i, ret;
+
+ ret = imx_scu_get_handle(&thermal_ipc_handle);
+ if (ret)
+ return ret;
+
+ resource_id = of_device_get_match_data(&pdev->dev);
+ if (!resource_id)
+ return -EINVAL;
+
+ for (i = 0; resource_id[i] > 0; i++) {
+
+ sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ sensor->resource_id = resource_id[i];
+
+ sensor->tzd = devm_thermal_of_zone_register(&pdev->dev, sensor->resource_id,
+ sensor, &imx_sc_thermal_ops);
+ if (IS_ERR(sensor->tzd)) {
+ /*
+ * Save the error value before freeing the
+ * sensor pointer, otherwise we endup with a
+ * use-after-free error
+ */
+ ret = PTR_ERR(sensor->tzd);
+
+ devm_kfree(&pdev->dev, sensor);
+
+ /*
+ * The thermal framework notifies us there is
+ * no thermal zone description for such a
+ * sensor id
+ */
+ if (ret == -ENODEV)
+ continue;
+
+ dev_err(&pdev->dev, "failed to register thermal zone\n");
+ return ret;
+ }
+
+ if (devm_thermal_add_hwmon_sysfs(sensor->tzd))
+ dev_warn(&pdev->dev, "failed to add hwmon sysfs attributes\n");
+ }
+
+ return 0;
+}
+
+static int imx_sc_thermal_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static int imx_sc_sensors[] = { IMX_SC_R_SYSTEM, IMX_SC_R_PMIC_0, -1 };
+
+static const struct of_device_id imx_sc_thermal_table[] = {
+ { .compatible = "fsl,imx-sc-thermal", .data = imx_sc_sensors },
+ {}
+};
+MODULE_DEVICE_TABLE(of, imx_sc_thermal_table);
+
+static struct platform_driver imx_sc_thermal_driver = {
+ .probe = imx_sc_thermal_probe,
+ .remove = imx_sc_thermal_remove,
+ .driver = {
+ .name = "imx-sc-thermal",
+ .of_match_table = imx_sc_thermal_table,
+ },
+};
+module_platform_driver(imx_sc_thermal_driver);
+
+MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
+MODULE_DESCRIPTION("Thermal driver for NXP i.MX SoCs with system controller");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
index bb6754a5342c..16663373b682 100644
--- a/drivers/thermal/imx_thermal.c
+++ b/drivers/thermal/imx_thermal.c
@@ -3,25 +3,19 @@
// Copyright 2013 Freescale Semiconductor, Inc.
#include <linux/clk.h>
-#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/cpu_cooling.h>
#include <linux/delay.h>
-#include <linux/device.h>
-#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
-#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
-#include <linux/platform_device.h>
#include <linux/regmap.h>
-#include <linux/slab.h>
#include <linux/thermal.h>
-#include <linux/types.h>
#include <linux/nvmem-consumer.h>
+#include <linux/pm_runtime.h>
#define REG_SET 0x4
#define REG_CLR 0x8
@@ -201,10 +195,10 @@ static struct thermal_soc_data thermal_imx7d_data = {
};
struct imx_thermal_data {
+ struct device *dev;
struct cpufreq_policy *policy;
struct thermal_zone_device *tz;
struct thermal_cooling_device *cdev;
- enum thermal_device_mode mode;
struct regmap *tempmon;
u32 c1, c2; /* See formula in imx_init_calib() */
int temp_passive;
@@ -260,43 +254,15 @@ static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
const struct thermal_soc_data *soc_data = data->socdata;
struct regmap *map = data->tempmon;
unsigned int n_meas;
- bool wait;
u32 val;
+ int ret;
- if (data->mode == THERMAL_DEVICE_ENABLED) {
- /* Check if a measurement is currently in progress */
- regmap_read(map, soc_data->temp_data, &val);
- wait = !(val & soc_data->temp_valid_mask);
- } else {
- /*
- * Every time we measure the temperature, we will power on the
- * temperature sensor, enable measurements, take a reading,
- * disable measurements, power off the temperature sensor.
- */
- regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
- soc_data->power_down_mask);
- regmap_write(map, soc_data->sensor_ctrl + REG_SET,
- soc_data->measure_temp_mask);
-
- wait = true;
- }
-
- /*
- * According to the temp sensor designers, it may require up to ~17us
- * to complete a measurement.
- */
- if (wait)
- usleep_range(20, 50);
+ ret = pm_runtime_resume_and_get(data->dev);
+ if (ret < 0)
+ return ret;
regmap_read(map, soc_data->temp_data, &val);
- if (data->mode != THERMAL_DEVICE_ENABLED) {
- regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
- soc_data->measure_temp_mask);
- regmap_write(map, soc_data->sensor_ctrl + REG_SET,
- soc_data->power_down_mask);
- }
-
if ((val & soc_data->temp_valid_mask) == 0) {
dev_dbg(&tz->device, "temp measurement never finished\n");
return -EAGAIN;
@@ -335,47 +301,25 @@ static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
enable_irq(data->irq);
}
- return 0;
-}
-
-static int imx_get_mode(struct thermal_zone_device *tz,
- enum thermal_device_mode *mode)
-{
- struct imx_thermal_data *data = tz->devdata;
-
- *mode = data->mode;
+ pm_runtime_put(data->dev);
return 0;
}
-static int imx_set_mode(struct thermal_zone_device *tz,
- enum thermal_device_mode mode)
+static int imx_change_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode mode)
{
struct imx_thermal_data *data = tz->devdata;
- struct regmap *map = data->tempmon;
- const struct thermal_soc_data *soc_data = data->socdata;
if (mode == THERMAL_DEVICE_ENABLED) {
- tz->polling_delay = IMX_POLLING_DELAY;
- tz->passive_delay = IMX_PASSIVE_DELAY;
-
- regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
- soc_data->power_down_mask);
- regmap_write(map, soc_data->sensor_ctrl + REG_SET,
- soc_data->measure_temp_mask);
+ pm_runtime_get(data->dev);
if (!data->irq_enabled) {
data->irq_enabled = true;
enable_irq(data->irq);
}
} else {
- regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
- soc_data->measure_temp_mask);
- regmap_write(map, soc_data->sensor_ctrl + REG_SET,
- soc_data->power_down_mask);
-
- tz->polling_delay = 0;
- tz->passive_delay = 0;
+ pm_runtime_put(data->dev);
if (data->irq_enabled) {
disable_irq(data->irq);
@@ -383,9 +327,6 @@ static int imx_set_mode(struct thermal_zone_device *tz,
}
}
- data->mode = mode;
- thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
-
return 0;
}
@@ -419,6 +360,11 @@ static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
int temp)
{
struct imx_thermal_data *data = tz->devdata;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(data->dev);
+ if (ret < 0)
+ return ret;
/* do not allow changing critical threshold */
if (trip == IMX_TRIP_CRITICAL)
@@ -432,6 +378,8 @@ static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
imx_set_alarm_temp(data, temp);
+ pm_runtime_put(data->dev);
+
return 0;
}
@@ -474,8 +422,7 @@ static struct thermal_zone_device_ops imx_tz_ops = {
.bind = imx_bind,
.unbind = imx_unbind,
.get_temp = imx_get_temp,
- .get_mode = imx_get_mode,
- .set_mode = imx_set_mode,
+ .change_mode = imx_change_mode,
.get_trip_type = imx_get_trip_type,
.get_trip_temp = imx_get_trip_temp,
.get_crit_temp = imx_get_crit_temp,
@@ -656,7 +603,7 @@ MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
{
struct device_node *np;
- int ret;
+ int ret = 0;
data->policy = cpufreq_cpu_get(0);
if (!data->policy) {
@@ -671,11 +618,12 @@ static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
if (IS_ERR(data->cdev)) {
ret = PTR_ERR(data->cdev);
cpufreq_cpu_put(data->policy);
- return ret;
}
}
- return 0;
+ of_node_put(np);
+
+ return ret;
}
static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
@@ -707,6 +655,8 @@ static int imx_thermal_probe(struct platform_device *pdev)
if (!data)
return -ENOMEM;
+ data->dev = &pdev->dev;
+
map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
if (IS_ERR(map)) {
ret = PTR_ERR(map);
@@ -742,14 +692,9 @@ static int imx_thermal_probe(struct platform_device *pdev)
if (of_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) {
ret = imx_init_from_nvmem_cells(pdev);
- if (ret) {
- if (ret == -EPROBE_DEFER)
- return ret;
-
- dev_err(&pdev->dev, "failed to init from nvmem: %d\n",
- ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to init from nvmem\n");
} else {
ret = imx_init_from_tempmon_data(pdev);
if (ret) {
@@ -772,14 +717,9 @@ static int imx_thermal_probe(struct platform_device *pdev)
data->socdata->power_down_mask);
ret = imx_thermal_register_legacy_cooling(data);
- if (ret) {
- if (ret == -EPROBE_DEFER)
- return ret;
-
- dev_err(&pdev->dev,
- "failed to register cpufreq cooling device: %d\n", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to register cpufreq cooling device\n");
data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(data->thermal_clk)) {
@@ -836,9 +776,21 @@ static int imx_thermal_probe(struct platform_device *pdev)
data->socdata->power_down_mask);
regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
data->socdata->measure_temp_mask);
+ /* After power up, we need a delay before first access can be done. */
+ usleep_range(20, 50);
+
+ /* the core was configured and enabled just before */
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(data->dev);
+
+ ret = pm_runtime_resume_and_get(data->dev);
+ if (ret < 0)
+ goto disable_runtime_pm;
data->irq_enabled = true;
- data->mode = THERMAL_DEVICE_ENABLED;
+ ret = thermal_zone_device_enable(data->tz);
+ if (ret)
+ goto thermal_zone_unregister;
ret = devm_request_threaded_irq(&pdev->dev, data->irq,
imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
@@ -848,10 +800,15 @@ static int imx_thermal_probe(struct platform_device *pdev)
goto thermal_zone_unregister;
}
+ pm_runtime_put(data->dev);
+
return 0;
thermal_zone_unregister:
thermal_zone_device_unregister(data->tz);
+disable_runtime_pm:
+ pm_runtime_put_noidle(data->dev);
+ pm_runtime_disable(data->dev);
clk_disable:
clk_disable_unprepare(data->thermal_clk);
legacy_cleanup:
@@ -863,65 +820,104 @@ legacy_cleanup:
static int imx_thermal_remove(struct platform_device *pdev)
{
struct imx_thermal_data *data = platform_get_drvdata(pdev);
- struct regmap *map = data->tempmon;
- /* Disable measurements */
- regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
- data->socdata->power_down_mask);
- if (!IS_ERR(data->thermal_clk))
- clk_disable_unprepare(data->thermal_clk);
+ pm_runtime_put_noidle(data->dev);
+ pm_runtime_disable(data->dev);
thermal_zone_device_unregister(data->tz);
- cpufreq_cooling_unregister(data->cdev);
- cpufreq_cpu_put(data->policy);
+ imx_thermal_unregister_legacy_cooling(data);
return 0;
}
-#ifdef CONFIG_PM_SLEEP
-static int imx_thermal_suspend(struct device *dev)
+static int __maybe_unused imx_thermal_suspend(struct device *dev)
{
struct imx_thermal_data *data = dev_get_drvdata(dev);
- struct regmap *map = data->tempmon;
+ int ret;
/*
* 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.
+ * down. This is done in change_mode() operation called from
+ * thermal_zone_device_disable()
*/
- regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
- data->socdata->measure_temp_mask);
- regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
- data->socdata->power_down_mask);
- data->mode = THERMAL_DEVICE_DISABLED;
+ ret = thermal_zone_device_disable(data->tz);
+ if (ret)
+ return ret;
+
+ return pm_runtime_force_suspend(data->dev);
+}
+
+static int __maybe_unused imx_thermal_resume(struct device *dev)
+{
+ struct imx_thermal_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_force_resume(data->dev);
+ if (ret)
+ return ret;
+ /* Enabled thermal sensor after resume */
+ return thermal_zone_device_enable(data->tz);
+}
+
+static int __maybe_unused imx_thermal_runtime_suspend(struct device *dev)
+{
+ struct imx_thermal_data *data = dev_get_drvdata(dev);
+ const struct thermal_soc_data *socdata = data->socdata;
+ struct regmap *map = data->tempmon;
+ int ret;
+
+ ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
+ socdata->measure_temp_mask);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
+ socdata->power_down_mask);
+ if (ret)
+ return ret;
+
clk_disable_unprepare(data->thermal_clk);
return 0;
}
-static int imx_thermal_resume(struct device *dev)
+static int __maybe_unused imx_thermal_runtime_resume(struct device *dev)
{
struct imx_thermal_data *data = dev_get_drvdata(dev);
+ const struct thermal_soc_data *socdata = data->socdata;
struct regmap *map = data->tempmon;
int ret;
ret = clk_prepare_enable(data->thermal_clk);
if (ret)
return ret;
- /* Enabled thermal sensor after resume */
- regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
- data->socdata->power_down_mask);
- regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
- data->socdata->measure_temp_mask);
- data->mode = THERMAL_DEVICE_ENABLED;
+
+ ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
+ socdata->power_down_mask);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
+ socdata->measure_temp_mask);
+ if (ret)
+ return ret;
+
+ /*
+ * According to the temp sensor designers, it may require up to ~17us
+ * to complete a measurement.
+ */
+ usleep_range(20, 50);
return 0;
}
-#endif
-static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops,
- imx_thermal_suspend, imx_thermal_resume);
+static const struct dev_pm_ops imx_thermal_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(imx_thermal_suspend, imx_thermal_resume)
+ SET_RUNTIME_PM_OPS(imx_thermal_runtime_suspend,
+ imx_thermal_runtime_resume, NULL)
+};
static struct platform_driver imx_thermal = {
.driver = {
diff --git a/drivers/thermal/intel/Kconfig b/drivers/thermal/intel/Kconfig
index 8025b21f43fa..f0c845679250 100644
--- a/drivers/thermal/intel/Kconfig
+++ b/drivers/thermal/intel/Kconfig
@@ -8,6 +8,10 @@ config INTEL_POWERCLAMP
enforce idle time which results in more package C-state residency. The
user interface is exposed via generic thermal framework.
+config X86_THERMAL_VECTOR
+ def_bool y
+ depends on X86 && CPU_SUP_INTEL && X86_LOCAL_APIC
+
config X86_PKG_TEMP_THERMAL
tristate "X86 package temperature thermal driver"
depends on X86_THERMAL_VECTOR
@@ -75,3 +79,37 @@ config INTEL_PCH_THERMAL
Enable this to support thermal reporting on certain intel PCHs.
Thermal reporting device will provide temperature reading,
programmable trip points and other information.
+
+config INTEL_TCC_COOLING
+ tristate "Intel TCC offset cooling Driver"
+ depends on X86
+ help
+ Enable this to support system cooling by adjusting the effective TCC
+ activation temperature via the TCC Offset register, which is widely
+ supported on modern Intel platforms.
+ Note that, on different platforms, the behavior might be different
+ on how fast the setting takes effect, and how much the CPU frequency
+ is reduced.
+
+config INTEL_MENLOW
+ tristate "Thermal Management driver for Intel menlow platform"
+ depends on ACPI_THERMAL
+ help
+ ACPI thermal management enhancement driver on
+ Intel Menlow platform.
+
+ If unsure, say N.
+
+config INTEL_HFI_THERMAL
+ bool "Intel Hardware Feedback Interface"
+ depends on NET
+ depends on CPU_SUP_INTEL
+ depends on X86_THERMAL_VECTOR
+ select THERMAL_NETLINK
+ help
+ Select this option to enable the Hardware Feedback Interface. If
+ selected, hardware provides guidance to the operating system on
+ the performance and energy efficiency capabilities of each CPU.
+ These capabilities may change as a result of changes in the operating
+ conditions of the system such power and thermal limits. If selected,
+ the kernel relays updates in CPUs' capabilities to userspace.
diff --git a/drivers/thermal/intel/Makefile b/drivers/thermal/intel/Makefile
index 0d9736ced5d4..9a8d8054f316 100644
--- a/drivers/thermal/intel/Makefile
+++ b/drivers/thermal/intel/Makefile
@@ -10,3 +10,7 @@ obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL) += intel_quark_dts_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
obj-$(CONFIG_INTEL_BXT_PMIC_THERMAL) += intel_bxt_pmic_thermal.o
obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o
+obj-$(CONFIG_INTEL_TCC_COOLING) += intel_tcc_cooling.o
+obj-$(CONFIG_X86_THERMAL_VECTOR) += therm_throt.o
+obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o
+obj-$(CONFIG_INTEL_HFI_THERMAL) += intel_hfi.o
diff --git a/drivers/thermal/intel/int340x_thermal/Kconfig b/drivers/thermal/intel/int340x_thermal/Kconfig
index 797907542e43..5d046de96a5d 100644
--- a/drivers/thermal/intel/int340x_thermal/Kconfig
+++ b/drivers/thermal/intel/int340x_thermal/Kconfig
@@ -5,11 +5,12 @@
config INT340X_THERMAL
tristate "ACPI INT340X thermal drivers"
- depends on X86 && ACPI && PCI
+ depends on X86_64 && ACPI && PCI
select THERMAL_GOV_USER_SPACE
select ACPI_THERMAL_REL
select ACPI_FAN
select INTEL_SOC_DTS_IOSF_CORE
+ select PROC_THERMAL_MMIO_RAPL if POWERCAP
help
Newer laptops and tablets that use ACPI may have thermal sensors and
other devices with thermal control capabilities outside the core
@@ -41,9 +42,6 @@ config INT3406_THERMAL
power consumed by display device.
config PROC_THERMAL_MMIO_RAPL
- bool
- depends on 64BIT
- depends on POWERCAP
+ tristate
select INTEL_RAPL_CORE
- default y
endif
diff --git a/drivers/thermal/intel/int340x_thermal/Makefile b/drivers/thermal/intel/int340x_thermal/Makefile
index 287eb0a1476d..4e852ce4a5d5 100644
--- a/drivers/thermal/intel/int340x_thermal/Makefile
+++ b/drivers/thermal/intel/int340x_thermal/Makefile
@@ -4,5 +4,11 @@ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal_zone.o
obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o
+obj-$(CONFIG_INT340X_THERMAL) += int3401_thermal.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device_pci_legacy.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device_pci.o
+obj-$(CONFIG_PROC_THERMAL_MMIO_RAPL) += processor_thermal_rapl.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_rfim.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_mbox.o
obj-$(CONFIG_INT3406_THERMAL) += int3406_thermal.o
obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o
diff --git a/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c
index 7130e90773ed..01b80331eab6 100644
--- a/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c
+++ b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c
@@ -19,6 +19,7 @@
#include <linux/acpi.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
+#include <linux/fs.h>
#include "acpi_thermal_rel.h"
static acpi_handle acpi_thermal_rel_handle;
@@ -71,7 +72,6 @@ int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp,
int i;
int nr_bad_entries = 0;
struct trt *trts;
- struct acpi_device *adev;
union acpi_object *p;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer element = { 0, NULL };
@@ -111,12 +111,10 @@ int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp,
if (!create_dev)
continue;
- result = acpi_bus_get_device(trt->source, &adev);
- if (result)
+ if (!acpi_fetch_acpi_dev(trt->source))
pr_warn("Failed to get source ACPI device\n");
- result = acpi_bus_get_device(trt->target, &adev);
- if (result)
+ if (!acpi_fetch_acpi_dev(trt->target))
pr_warn("Failed to get target ACPI device\n");
}
@@ -148,7 +146,6 @@ int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp,
int i;
int nr_bad_entries = 0;
struct art *arts;
- struct acpi_device *adev;
union acpi_object *p;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer element = { 0, NULL };
@@ -190,16 +187,11 @@ int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp,
if (!create_dev)
continue;
- if (art->source) {
- result = acpi_bus_get_device(art->source, &adev);
- if (result)
- pr_warn("Failed to get source ACPI device\n");
- }
- if (art->target) {
- result = acpi_bus_get_device(art->target, &adev);
- if (result)
- pr_warn("Failed to get target ACPI device\n");
- }
+ if (!acpi_fetch_acpi_dev(art->source))
+ pr_warn("Failed to get source ACPI device\n");
+
+ if (!acpi_fetch_acpi_dev(art->target))
+ pr_warn("Failed to get target ACPI device\n");
}
*artp = arts;
@@ -249,8 +241,9 @@ static int fill_art(char __user *ubuf)
get_single_name(arts[i].source, art_user[i].source_device);
get_single_name(arts[i].target, art_user[i].target_device);
/* copy the rest int data in addition to source and target */
- memcpy(&art_user[i].weight, &arts[i].weight,
- sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2));
+ BUILD_BUG_ON(sizeof(art_user[i].data) !=
+ sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2));
+ memcpy(&art_user[i].data, &arts[i].data, sizeof(art_user[i].data));
}
if (copy_to_user(ubuf, art_user, art_len))
diff --git a/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h
index 58822575fd54..78d942477035 100644
--- a/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h
+++ b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h
@@ -17,17 +17,19 @@
struct art {
acpi_handle source;
acpi_handle target;
- u64 weight;
- u64 ac0_max;
- u64 ac1_max;
- u64 ac2_max;
- u64 ac3_max;
- u64 ac4_max;
- u64 ac5_max;
- u64 ac6_max;
- u64 ac7_max;
- u64 ac8_max;
- u64 ac9_max;
+ struct_group(data,
+ u64 weight;
+ u64 ac0_max;
+ u64 ac1_max;
+ u64 ac2_max;
+ u64 ac3_max;
+ u64 ac4_max;
+ u64 ac5_max;
+ u64 ac6_max;
+ u64 ac7_max;
+ u64 ac8_max;
+ u64 ac9_max;
+ );
} __packed;
struct trt {
@@ -47,17 +49,19 @@ union art_object {
struct {
char source_device[8]; /* ACPI single name */
char target_device[8]; /* ACPI single name */
- u64 weight;
- u64 ac0_max_level;
- u64 ac1_max_level;
- u64 ac2_max_level;
- u64 ac3_max_level;
- u64 ac4_max_level;
- u64 ac5_max_level;
- u64 ac6_max_level;
- u64 ac7_max_level;
- u64 ac8_max_level;
- u64 ac9_max_level;
+ struct_group(data,
+ u64 weight;
+ u64 ac0_max_level;
+ u64 ac1_max_level;
+ u64 ac2_max_level;
+ u64 ac3_max_level;
+ u64 ac4_max_level;
+ u64 ac5_max_level;
+ u64 ac6_max_level;
+ u64 ac7_max_level;
+ u64 ac8_max_level;
+ u64 ac9_max_level;
+ );
};
u64 __data[ACPI_NR_ART_ELEMENTS];
};
diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c
index efae0c02d898..db8a6f63657d 100644
--- a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c
+++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c
@@ -13,10 +13,12 @@
#include "acpi_thermal_rel.h"
#define INT3400_THERMAL_TABLE_CHANGED 0x83
+#define INT3400_ODVP_CHANGED 0x88
+#define INT3400_KEEP_ALIVE 0xA0
enum int3400_thermal_uuid {
+ INT3400_THERMAL_ACTIVE = 0,
INT3400_THERMAL_PASSIVE_1,
- INT3400_THERMAL_ACTIVE,
INT3400_THERMAL_CRITICAL,
INT3400_THERMAL_ADAPTIVE_PERFORMANCE,
INT3400_THERMAL_EMERGENCY_CALL_MODE,
@@ -29,8 +31,8 @@ enum int3400_thermal_uuid {
};
static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
- "42A441D6-AE6A-462b-A84B-4A8CE79027D3",
"3A95C389-E4B8-4629-A526-C52C88626BAE",
+ "42A441D6-AE6A-462b-A84B-4A8CE79027D3",
"97C68AE7-15FA-499c-B8C9-5DA81D606E0A",
"63BE270F-1C11-48FD-A6F7-3AF253FF3E2D",
"5349962F-71E6-431D-9AE8-0A635B710AEE",
@@ -41,17 +43,78 @@ static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
"BE84BABF-C4D4-403D-B495-3128FD44dAC1",
};
+struct odvp_attr;
+
struct int3400_thermal_priv {
struct acpi_device *adev;
+ struct platform_device *pdev;
struct thermal_zone_device *thermal;
- int mode;
int art_count;
struct art *arts;
int trt_count;
struct trt *trts;
- u8 uuid_bitmap;
+ u32 uuid_bitmap;
int rel_misc_dev_res;
int current_uuid_index;
+ char *data_vault;
+ int odvp_count;
+ int *odvp;
+ u32 os_uuid_mask;
+ struct odvp_attr *odvp_attrs;
+};
+
+static int evaluate_odvp(struct int3400_thermal_priv *priv);
+
+struct odvp_attr {
+ int odvp;
+ struct int3400_thermal_priv *priv;
+ struct device_attribute attr;
+};
+
+static ssize_t data_vault_read(struct file *file, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf, loff_t off, size_t count)
+{
+ memcpy(buf, attr->private + off, count);
+ return count;
+}
+
+static BIN_ATTR_RO(data_vault, 0);
+
+static struct bin_attribute *data_attributes[] = {
+ &bin_attr_data_vault,
+ NULL,
+};
+
+static ssize_t imok_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
+ acpi_status status;
+ int input, ret;
+
+ ret = kstrtouint(buf, 10, &input);
+ if (ret)
+ return ret;
+ status = acpi_execute_simple_method(priv->adev->handle, "IMOK", input);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return count;
+}
+
+static DEVICE_ATTR_WO(imok);
+
+static struct attribute *imok_attr[] = {
+ &dev_attr_imok.attr,
+ NULL
+};
+
+static const struct attribute_group imok_attribute_group = {
+ .attrs = imok_attr,
+};
+
+static const struct attribute_group data_attribute_group = {
+ .bin_attrs = data_attributes,
};
static ssize_t available_uuids_show(struct device *dev,
@@ -62,13 +125,15 @@ static ssize_t available_uuids_show(struct device *dev,
int i;
int length = 0;
+ if (!priv->uuid_bitmap)
+ return sprintf(buf, "UNKNOWN\n");
+
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
if (priv->uuid_bitmap & (1 << i))
- if (PAGE_SIZE - length > 0)
- length += snprintf(&buf[length],
- PAGE_SIZE - length,
- "%s\n",
- int3400_thermal_uuids[i]);
+ length += scnprintf(&buf[length],
+ PAGE_SIZE - length,
+ "%s\n",
+ int3400_thermal_uuids[i]);
}
return length;
@@ -78,12 +143,71 @@ static ssize_t current_uuid_show(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
+ int i, length = 0;
- if (priv->uuid_bitmap & (1 << priv->current_uuid_index))
+ if (priv->current_uuid_index > 0)
return sprintf(buf, "%s\n",
int3400_thermal_uuids[priv->current_uuid_index]);
- else
- return sprintf(buf, "INVALID\n");
+
+ for (i = 0; i <= INT3400_THERMAL_CRITICAL; i++) {
+ if (priv->os_uuid_mask & BIT(i))
+ length += scnprintf(&buf[length],
+ PAGE_SIZE - length,
+ "%s\n",
+ int3400_thermal_uuids[i]);
+ }
+
+ if (length)
+ return length;
+
+ return sprintf(buf, "INVALID\n");
+}
+
+static int int3400_thermal_run_osc(acpi_handle handle, char *uuid_str, int *enable)
+{
+ u32 ret, buf[2];
+ acpi_status status;
+ int result = 0;
+ struct acpi_osc_context context = {
+ .uuid_str = uuid_str,
+ .rev = 1,
+ .cap.length = 8,
+ .cap.pointer = buf,
+ };
+
+ buf[OSC_QUERY_DWORD] = 0;
+ buf[OSC_SUPPORT_DWORD] = *enable;
+
+ status = acpi_run_osc(handle, &context);
+ if (ACPI_SUCCESS(status)) {
+ ret = *((u32 *)(context.ret.pointer + 4));
+ if (ret != *enable)
+ result = -EPERM;
+
+ kfree(context.ret.pointer);
+ } else
+ result = -EPERM;
+
+ return result;
+}
+
+static int set_os_uuid_mask(struct int3400_thermal_priv *priv, u32 mask)
+{
+ int cap = 0;
+
+ /*
+ * Capability bits:
+ * Bit 0: set to 1 to indicate DPTF is active
+ * Bi1 1: set to 1 to active cooling is supported by user space daemon
+ * Bit 2: set to 1 to passive cooling is supported by user space daemon
+ * Bit 3: set to 1 to critical trip is handled by user space daemon
+ */
+ if (mask)
+ cap = (priv->os_uuid_mask << 1) | 0x01;
+
+ return int3400_thermal_run_osc(priv->adev->handle,
+ "b23ba85d-c8b7-3542-88de-8de2ffcfd698",
+ &cap);
}
static ssize_t current_uuid_store(struct device *dev,
@@ -91,18 +215,44 @@ static ssize_t current_uuid_store(struct device *dev,
const char *buf, size_t count)
{
struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
- int i;
+ int ret, i;
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
- if ((priv->uuid_bitmap & (1 << i)) &&
- !(strncmp(buf, int3400_thermal_uuids[i],
- sizeof(int3400_thermal_uuids[i]) - 1))) {
- priv->current_uuid_index = i;
- return count;
+ if (!strncmp(buf, int3400_thermal_uuids[i],
+ sizeof(int3400_thermal_uuids[i]) - 1)) {
+ /*
+ * If we have a list of supported UUIDs, make sure
+ * this one is supported.
+ */
+ if (priv->uuid_bitmap & BIT(i)) {
+ priv->current_uuid_index = i;
+ return count;
+ }
+
+ /*
+ * There is support of only 3 policies via the new
+ * _OSC to inform OS capability:
+ * INT3400_THERMAL_ACTIVE
+ * INT3400_THERMAL_PASSIVE_1
+ * INT3400_THERMAL_CRITICAL
+ */
+
+ if (i > INT3400_THERMAL_CRITICAL)
+ return -EINVAL;
+
+ priv->os_uuid_mask |= BIT(i);
+
+ break;
}
}
- return -EINVAL;
+ if (priv->os_uuid_mask) {
+ ret = set_os_uuid_mask(priv, priv->os_uuid_mask);
+ if (ret)
+ return ret;
+ }
+
+ return count;
}
static DEVICE_ATTR_RW(current_uuid);
@@ -165,33 +315,104 @@ end:
return result;
}
-static int int3400_thermal_run_osc(acpi_handle handle,
- enum int3400_thermal_uuid uuid, bool enable)
+static ssize_t odvp_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
- u32 ret, buf[2];
+ struct odvp_attr *odvp_attr;
+
+ odvp_attr = container_of(attr, struct odvp_attr, attr);
+
+ return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);
+}
+
+static void cleanup_odvp(struct int3400_thermal_priv *priv)
+{
+ int i;
+
+ if (priv->odvp_attrs) {
+ for (i = 0; i < priv->odvp_count; i++) {
+ sysfs_remove_file(&priv->pdev->dev.kobj,
+ &priv->odvp_attrs[i].attr.attr);
+ kfree(priv->odvp_attrs[i].attr.attr.name);
+ }
+ kfree(priv->odvp_attrs);
+ }
+ kfree(priv->odvp);
+ priv->odvp_count = 0;
+}
+
+static int evaluate_odvp(struct int3400_thermal_priv *priv)
+{
+ struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj = NULL;
acpi_status status;
- int result = 0;
- struct acpi_osc_context context = {
- .uuid_str = int3400_thermal_uuids[uuid],
- .rev = 1,
- .cap.length = 8,
- };
+ int i, ret;
- buf[OSC_QUERY_DWORD] = 0;
- buf[OSC_SUPPORT_DWORD] = enable;
+ status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp);
+ if (ACPI_FAILURE(status)) {
+ ret = -EINVAL;
+ goto out_err;
+ }
- context.cap.pointer = buf;
+ obj = odvp.pointer;
+ if (obj->type != ACPI_TYPE_PACKAGE) {
+ ret = -EINVAL;
+ goto out_err;
+ }
- status = acpi_run_osc(handle, &context);
- if (ACPI_SUCCESS(status)) {
- ret = *((u32 *)(context.ret.pointer + 4));
- if (ret != enable)
- result = -EPERM;
- } else
- result = -EPERM;
+ if (priv->odvp == NULL) {
+ priv->odvp_count = obj->package.count;
+ priv->odvp = kmalloc_array(priv->odvp_count, sizeof(int),
+ GFP_KERNEL);
+ if (!priv->odvp) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+ }
- kfree(context.ret.pointer);
- return result;
+ if (priv->odvp_attrs == NULL) {
+ priv->odvp_attrs = kcalloc(priv->odvp_count,
+ sizeof(struct odvp_attr),
+ GFP_KERNEL);
+ if (!priv->odvp_attrs) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+ for (i = 0; i < priv->odvp_count; i++) {
+ struct odvp_attr *odvp = &priv->odvp_attrs[i];
+
+ sysfs_attr_init(&odvp->attr.attr);
+ odvp->priv = priv;
+ odvp->odvp = i;
+ odvp->attr.attr.name = kasprintf(GFP_KERNEL,
+ "odvp%d", i);
+
+ if (!odvp->attr.attr.name) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+ odvp->attr.attr.mode = 0444;
+ odvp->attr.show = odvp_show;
+ odvp->attr.store = NULL;
+ ret = sysfs_create_file(&priv->pdev->dev.kobj,
+ &odvp->attr.attr);
+ if (ret)
+ goto out_err;
+ }
+ }
+
+ for (i = 0; i < obj->package.count; i++) {
+ if (obj->package.elements[i].type == ACPI_TYPE_INTEGER)
+ priv->odvp[i] = obj->package.elements[i].integer.value;
+ }
+
+ kfree(obj);
+ return 0;
+
+out_err:
+ cleanup_odvp(priv);
+ kfree(obj);
+ return ret;
}
static void int3400_notify(acpi_handle handle,
@@ -200,27 +421,37 @@ static void int3400_notify(acpi_handle handle,
{
struct int3400_thermal_priv *priv = data;
char *thermal_prop[5];
+ int therm_event;
if (!priv)
return;
switch (event) {
case INT3400_THERMAL_TABLE_CHANGED:
- thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s",
- priv->thermal->type);
- thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d",
- priv->thermal->temperature);
- thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=");
- thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d",
- THERMAL_TABLE_CHANGED);
- thermal_prop[4] = NULL;
- kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE,
- thermal_prop);
+ therm_event = THERMAL_TABLE_CHANGED;
+ break;
+ case INT3400_KEEP_ALIVE:
+ therm_event = THERMAL_EVENT_KEEP_ALIVE;
+ break;
+ case INT3400_ODVP_CHANGED:
+ evaluate_odvp(priv);
+ therm_event = THERMAL_DEVICE_POWER_CAPABILITY_CHANGED;
break;
default:
/* Ignore unknown notification codes sent to INT3400 device */
- break;
+ return;
}
+
+ thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", priv->thermal->type);
+ thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", priv->thermal->temperature);
+ thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=");
+ thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", therm_event);
+ thermal_prop[4] = NULL;
+ kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE, thermal_prop);
+ kfree(thermal_prop[0]);
+ kfree(thermal_prop[1]);
+ kfree(thermal_prop[2]);
+ kfree(thermal_prop[3]);
}
static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
@@ -230,47 +461,46 @@ static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
return 0;
}
-static int int3400_thermal_get_mode(struct thermal_zone_device *thermal,
- enum thermal_device_mode *mode)
+static int int3400_thermal_change_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
{
struct int3400_thermal_priv *priv = thermal->devdata;
+ int result = 0;
if (!priv)
return -EINVAL;
- *mode = priv->mode;
+ if (mode != thermal->mode) {
+ int enabled;
- return 0;
-}
-
-static int int3400_thermal_set_mode(struct thermal_zone_device *thermal,
- enum thermal_device_mode mode)
-{
- struct int3400_thermal_priv *priv = thermal->devdata;
- bool enable;
- int result = 0;
+ enabled = mode == THERMAL_DEVICE_ENABLED;
- if (!priv)
- return -EINVAL;
+ if (priv->os_uuid_mask) {
+ if (!enabled) {
+ priv->os_uuid_mask = 0;
+ result = set_os_uuid_mask(priv, priv->os_uuid_mask);
+ }
+ goto eval_odvp;
+ }
- if (mode == THERMAL_DEVICE_ENABLED)
- enable = true;
- else if (mode == THERMAL_DEVICE_DISABLED)
- enable = false;
- else
- return -EINVAL;
+ if (priv->current_uuid_index < 0 ||
+ priv->current_uuid_index >= INT3400_THERMAL_MAXIMUM_UUID)
+ return -EINVAL;
- if (enable != priv->mode) {
- priv->mode = enable;
result = int3400_thermal_run_osc(priv->adev->handle,
- priv->current_uuid_index,
- enable);
+ int3400_thermal_uuids[priv->current_uuid_index],
+ &enabled);
}
+
+eval_odvp:
+ evaluate_odvp(priv);
+
return result;
}
static struct thermal_zone_device_ops int3400_thermal_ops = {
.get_temp = int3400_thermal_get_temp,
+ .change_mode = int3400_thermal_change_mode,
};
static struct thermal_zone_params int3400_thermal_params = {
@@ -278,6 +508,34 @@ static struct thermal_zone_params int3400_thermal_params = {
.no_hwmon = true,
};
+static void int3400_setup_gddv(struct int3400_thermal_priv *priv)
+{
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+
+ status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL,
+ &buffer);
+ if (ACPI_FAILURE(status) || !buffer.length)
+ return;
+
+ obj = buffer.pointer;
+ if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1
+ || obj->package.elements[0].type != ACPI_TYPE_BUFFER)
+ goto out_free;
+
+ priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,
+ obj->package.elements[0].buffer.length,
+ GFP_KERNEL);
+ if (ZERO_OR_NULL_PTR(priv->data_vault))
+ goto out_free;
+
+ bin_attr_data_vault.private = priv->data_vault;
+ bin_attr_data_vault.size = obj->package.elements[0].buffer.length;
+out_free:
+ kfree(buffer.pointer);
+}
+
static int int3400_thermal_probe(struct platform_device *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
@@ -291,12 +549,17 @@ static int int3400_thermal_probe(struct platform_device *pdev)
if (!priv)
return -ENOMEM;
+ priv->pdev = pdev;
priv->adev = adev;
result = int3400_thermal_get_uuids(priv);
- if (result)
+
+ /* Missing IDSP isn't fatal */
+ if (result && result != -ENODEV)
goto free_priv;
+ priv->current_uuid_index = -1;
+
result = acpi_parse_art(priv->adev->handle, &priv->art_count,
&priv->arts, true);
if (result)
@@ -309,8 +572,9 @@ static int int3400_thermal_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, priv);
- int3400_thermal_ops.get_mode = int3400_thermal_get_mode;
- int3400_thermal_ops.set_mode = int3400_thermal_set_mode;
+ int3400_setup_gddv(priv);
+
+ evaluate_odvp(priv);
priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
priv, &int3400_thermal_ops,
@@ -327,6 +591,19 @@ static int int3400_thermal_probe(struct platform_device *pdev)
if (result)
goto free_rel_misc;
+ if (acpi_has_method(priv->adev->handle, "IMOK")) {
+ result = sysfs_create_group(&pdev->dev.kobj, &imok_attribute_group);
+ if (result)
+ goto free_imok;
+ }
+
+ if (!ZERO_OR_NULL_PTR(priv->data_vault)) {
+ result = sysfs_create_group(&pdev->dev.kobj,
+ &data_attribute_group);
+ if (result)
+ goto free_uuid;
+ }
+
result = acpi_install_notify_handler(
priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
(void *)priv);
@@ -336,7 +613,15 @@ static int int3400_thermal_probe(struct platform_device *pdev)
return 0;
free_sysfs:
+ cleanup_odvp(priv);
+ if (!ZERO_OR_NULL_PTR(priv->data_vault)) {
+ sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
+ kfree(priv->data_vault);
+ }
+free_uuid:
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
+free_imok:
+ sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
free_rel_misc:
if (!priv->rel_misc_dev_res)
acpi_thermal_rel_misc_device_remove(priv->adev->handle);
@@ -357,11 +642,17 @@ static int int3400_thermal_remove(struct platform_device *pdev)
priv->adev->handle, ACPI_DEVICE_NOTIFY,
int3400_notify);
+ cleanup_odvp(priv);
+
if (!priv->rel_misc_dev_res)
acpi_thermal_rel_misc_device_remove(priv->adev->handle);
+ if (!ZERO_OR_NULL_PTR(priv->data_vault))
+ sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
+ sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
thermal_zone_device_unregister(priv->thermal);
+ kfree(priv->data_vault);
kfree(priv->trts);
kfree(priv->arts);
kfree(priv);
@@ -369,8 +660,11 @@ static int int3400_thermal_remove(struct platform_device *pdev)
}
static const struct acpi_device_id int3400_thermal_match[] = {
- {"INT1040", 0},
{"INT3400", 0},
+ {"INTC1040", 0},
+ {"INTC1041", 0},
+ {"INTC1042", 0},
+ {"INTC10A0", 0},
{}
};
diff --git a/drivers/thermal/intel/int340x_thermal/int3401_thermal.c b/drivers/thermal/intel/int340x_thermal/int3401_thermal.c
new file mode 100644
index 000000000000..217786fba185
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/int3401_thermal.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * INT3401 processor thermal device
+ * Copyright (c) 2020, Intel Corporation.
+ */
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#include "int340x_thermal_zone.h"
+#include "processor_thermal_device.h"
+
+static const struct acpi_device_id int3401_device_ids[] = {
+ {"INT3401", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, int3401_device_ids);
+
+static int int3401_add(struct platform_device *pdev)
+{
+ struct proc_thermal_device *proc_priv;
+ int ret;
+
+ proc_priv = devm_kzalloc(&pdev->dev, sizeof(*proc_priv), GFP_KERNEL);
+ if (!proc_priv)
+ return -ENOMEM;
+
+ ret = proc_thermal_add(&pdev->dev, proc_priv);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, proc_priv);
+
+ return ret;
+}
+
+static int int3401_remove(struct platform_device *pdev)
+{
+ proc_thermal_remove(platform_get_drvdata(pdev));
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int int3401_thermal_suspend(struct device *dev)
+{
+ return proc_thermal_suspend(dev);
+}
+static int int3401_thermal_resume(struct device *dev)
+{
+ return proc_thermal_resume(dev);
+}
+#else
+#define int3401_thermal_suspend NULL
+#define int3401_thermal_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(int3401_proc_thermal_pm, int3401_thermal_suspend,
+ int3401_thermal_resume);
+
+static struct platform_driver int3401_driver = {
+ .probe = int3401_add,
+ .remove = int3401_remove,
+ .driver = {
+ .name = "int3401 thermal",
+ .acpi_match_table = int3401_device_ids,
+ .pm = &int3401_proc_thermal_pm,
+ },
+};
+
+static int __init proc_thermal_init(void)
+{
+ return platform_driver_register(&int3401_driver);
+}
+
+static void __exit proc_thermal_exit(void)
+{
+ platform_driver_unregister(&int3401_driver);
+}
+
+module_init(proc_thermal_init);
+module_exit(proc_thermal_exit);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
index aeece1e136a5..71d084c4c456 100644
--- a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
+++ b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
@@ -74,7 +74,7 @@ static void int3403_notify(acpi_handle handle,
THERMAL_TRIP_CHANGED);
break;
default:
- dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event);
+ dev_dbg(&priv->pdev->dev, "Unsupported event [0x%x]\n", event);
break;
}
}
@@ -282,8 +282,11 @@ static int int3403_remove(struct platform_device *pdev)
}
static const struct acpi_device_id int3403_device_ids[] = {
- {"INT1043", 0},
{"INT3403", 0},
+ {"INTC1043", 0},
+ {"INTC1046", 0},
+ {"INTC1062", 0},
+ {"INTC10A1", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, int3403_device_ids);
diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
index 432213272f1e..62c0aa5d0783 100644
--- a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
+++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
@@ -146,12 +146,18 @@ static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone,
return 0;
}
+static void int340x_thermal_critical(struct thermal_zone_device *zone)
+{
+ dev_dbg(&zone->device, "%s: critical temperature reached\n", zone->type);
+}
+
static struct thermal_zone_device_ops int340x_thermal_zone_ops = {
.get_temp = int340x_thermal_get_zone_temp,
.get_trip_temp = int340x_thermal_get_trip_temp,
.get_trip_type = int340x_thermal_get_trip_type,
.set_trip_temp = int340x_thermal_set_trip_temp,
.get_trip_hyst = int340x_thermal_get_trip_hyst,
+ .critical = int340x_thermal_critical,
};
static int int340x_thermal_get_trip_config(acpi_handle handle, char *name,
@@ -231,6 +237,8 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
if (ACPI_FAILURE(status))
trip_cnt = 0;
else {
+ int i;
+
int34x_thermal_zone->aux_trips =
kcalloc(trip_cnt,
sizeof(*int34x_thermal_zone->aux_trips),
@@ -241,6 +249,8 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
}
trip_mask = BIT(trip_cnt) - 1;
int34x_thermal_zone->aux_trip_nr = trip_cnt;
+ for (i = 0; i < trip_cnt; ++i)
+ int34x_thermal_zone->aux_trips[i] = THERMAL_TEMP_INVALID;
}
trip_cnt = int340x_thermal_read_trips(int34x_thermal_zone);
@@ -259,9 +269,14 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
ret = PTR_ERR(int34x_thermal_zone->zone);
goto err_thermal_zone;
}
+ ret = thermal_zone_device_enable(int34x_thermal_zone->zone);
+ if (ret)
+ goto err_enable;
return int34x_thermal_zone;
+err_enable:
+ thermal_zone_device_unregister(int34x_thermal_zone->zone);
err_thermal_zone:
acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table);
kfree(int34x_thermal_zone->aux_trips);
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
index b1fd34516e28..a8d98f1bd6c6 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
@@ -3,87 +3,17 @@
* processor_thermal_device.c
* Copyright (c) 2014, Intel Corporation.
*/
+#include <linux/acpi.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/pci.h>
-#include <linux/interrupt.h>
-#include <linux/platform_device.h>
-#include <linux/acpi.h>
#include <linux/thermal.h>
-#include <linux/cpuhotplug.h>
-#include <linux/intel_rapl.h>
#include "int340x_thermal_zone.h"
+#include "processor_thermal_device.h"
#include "../intel_soc_dts_iosf.h"
-/* Broadwell-U/HSB thermal reporting device */
-#define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603
-#define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03
-
-/* Skylake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903
-
-/* CannonLake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_CNL_THERMAL 0x5a03
-#define PCI_DEVICE_ID_PROC_CFL_THERMAL 0x3E83
-
-/* Braswell thermal reporting device */
-#define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC
-
-/* Broxton thermal reporting device */
-#define PCI_DEVICE_ID_PROC_BXT0_THERMAL 0x0A8C
-#define PCI_DEVICE_ID_PROC_BXT1_THERMAL 0x1A8C
-#define PCI_DEVICE_ID_PROC_BXTX_THERMAL 0x4A8C
-#define PCI_DEVICE_ID_PROC_BXTP_THERMAL 0x5A8C
-
-/* GeminiLake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_GLK_THERMAL 0x318C
-
-/* IceLake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_ICL_THERMAL 0x8a03
-
-/* JasperLake thermal reporting device */
-#define PCI_DEVICE_ID_PROC_JSL_THERMAL 0x4503
-
#define DRV_NAME "proc_thermal"
-struct power_config {
- u32 index;
- u32 min_uw;
- u32 max_uw;
- u32 tmin_us;
- u32 tmax_us;
- u32 step_uw;
-};
-
-struct proc_thermal_device {
- struct device *dev;
- struct acpi_device *adev;
- struct power_config power_limits[2];
- struct int34x_thermal_zone *int340x_zone;
- struct intel_soc_dts_sensors *soc_dts;
- void __iomem *mmio_base;
-};
-
-enum proc_thermal_emum_mode_type {
- PROC_THERMAL_NONE,
- PROC_THERMAL_PCI,
- PROC_THERMAL_PLATFORM_DEV
-};
-
-struct rapl_mmio_regs {
- u64 reg_unit;
- u64 regs[RAPL_DOMAIN_MAX][RAPL_DOMAIN_REG_MAX];
- int limits[RAPL_DOMAIN_MAX];
-};
-
-/*
- * We can have only one type of enumeration, PCI or Platform,
- * not both. So we don't need instance specific data.
- */
-static enum proc_thermal_emum_mode_type proc_thermal_emum_mode =
- PROC_THERMAL_NONE;
-
#define POWER_LIMIT_SHOW(index, suffix) \
static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \
struct device_attribute *attr, \
@@ -91,11 +21,6 @@ static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \
{ \
struct proc_thermal_device *proc_dev = dev_get_drvdata(dev); \
\
- if (proc_thermal_emum_mode == PROC_THERMAL_NONE) { \
- dev_warn(dev, "Attempted to get power limit before device was initialized!\n"); \
- return 0; \
- } \
- \
return sprintf(buf, "%lu\n",\
(unsigned long)proc_dev->power_limits[index].suffix * 1000); \
}
@@ -143,8 +68,7 @@ static const struct attribute_group power_limit_attribute_group = {
.name = "power_limits"
};
-static ssize_t tcc_offset_degree_celsius_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static int tcc_get_offset(void)
{
u64 val;
int err;
@@ -153,24 +77,39 @@ static ssize_t tcc_offset_degree_celsius_show(struct device *dev,
if (err)
return err;
- val = (val >> 24) & 0xff;
- return sprintf(buf, "%d\n", (int)val);
+ return (val >> 24) & 0x3f;
+}
+
+static ssize_t tcc_offset_degree_celsius_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int tcc;
+
+ tcc = tcc_get_offset();
+ if (tcc < 0)
+ return tcc;
+
+ return sprintf(buf, "%d\n", tcc);
}
-static int tcc_offset_update(int tcc)
+static int tcc_offset_update(unsigned int tcc)
{
u64 val;
int err;
- if (!tcc)
+ if (tcc > 63)
return -EINVAL;
err = rdmsrl_safe(MSR_IA32_TEMPERATURE_TARGET, &val);
if (err)
return err;
- val &= ~GENMASK_ULL(31, 24);
- val |= (tcc & 0xff) << 24;
+ if (val & BIT(31))
+ return -EPERM;
+
+ val &= ~GENMASK_ULL(29, 24);
+ val |= (tcc & 0x3f) << 24;
err = wrmsrl_safe(MSR_IA32_TEMPERATURE_TARGET, val);
if (err)
@@ -179,14 +118,13 @@ static int tcc_offset_update(int tcc)
return 0;
}
-static int tcc_offset_save;
-
static ssize_t tcc_offset_degree_celsius_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
+ unsigned int tcc;
u64 val;
- int tcc, err;
+ int err;
err = rdmsrl_safe(MSR_PLATFORM_INFO, &val);
if (err)
@@ -195,15 +133,13 @@ static ssize_t tcc_offset_degree_celsius_store(struct device *dev,
if (!(val & BIT(30)))
return -EACCES;
- if (kstrtoint(buf, 0, &tcc))
+ if (kstrtouint(buf, 0, &tcc))
return -EINVAL;
err = tcc_offset_update(tcc);
if (err)
return err;
- tcc_offset_save = tcc;
-
return count;
}
@@ -344,11 +280,8 @@ static void proc_thermal_notify(acpi_handle handle, u32 event, void *data)
}
}
-
-static int proc_thermal_add(struct device *dev,
- struct proc_thermal_device **priv)
+int proc_thermal_add(struct device *dev, struct proc_thermal_device *proc_priv)
{
- struct proc_thermal_device *proc_priv;
struct acpi_device *adev;
acpi_status status;
unsigned long long tmp;
@@ -359,13 +292,8 @@ static int proc_thermal_add(struct device *dev,
if (!adev)
return -ENODEV;
- proc_priv = devm_kzalloc(dev, sizeof(*proc_priv), GFP_KERNEL);
- if (!proc_priv)
- return -ENOMEM;
-
proc_priv->dev = dev;
proc_priv->adev = adev;
- *priv = proc_priv;
ret = proc_thermal_read_ppcc(proc_priv);
if (ret)
@@ -391,15 +319,29 @@ static int proc_thermal_add(struct device *dev,
if (ret)
goto remove_zone;
+ ret = sysfs_create_file(&dev->kobj, &dev_attr_tcc_offset_degree_celsius.attr);
+ if (ret)
+ goto remove_notify;
+
+ ret = sysfs_create_group(&dev->kobj, &power_limit_attribute_group);
+ if (ret) {
+ sysfs_remove_file(&dev->kobj, &dev_attr_tcc_offset_degree_celsius.attr);
+ goto remove_notify;
+ }
+
return 0;
+remove_notify:
+ acpi_remove_notify_handler(adev->handle,
+ ACPI_DEVICE_NOTIFY, proc_thermal_notify);
remove_zone:
int340x_thermal_zone_remove(proc_priv->int340x_zone);
return ret;
}
+EXPORT_SYMBOL_GPL(proc_thermal_add);
-static void proc_thermal_remove(struct proc_thermal_device *proc_priv)
+void proc_thermal_remove(struct proc_thermal_device *proc_priv)
{
acpi_remove_notify_handler(proc_priv->adev->handle,
ACPI_DEVICE_NOTIFY, proc_thermal_notify);
@@ -408,134 +350,41 @@ static void proc_thermal_remove(struct proc_thermal_device *proc_priv)
sysfs_remove_group(&proc_priv->dev->kobj,
&power_limit_attribute_group);
}
+EXPORT_SYMBOL_GPL(proc_thermal_remove);
-static int int3401_add(struct platform_device *pdev)
-{
- struct proc_thermal_device *proc_priv;
- int ret;
-
- if (proc_thermal_emum_mode == PROC_THERMAL_PCI) {
- dev_err(&pdev->dev, "error: enumerated as PCI dev\n");
- return -ENODEV;
- }
-
- ret = proc_thermal_add(&pdev->dev, &proc_priv);
- if (ret)
- return ret;
-
- platform_set_drvdata(pdev, proc_priv);
- proc_thermal_emum_mode = PROC_THERMAL_PLATFORM_DEV;
+static int tcc_offset_save = -1;
- dev_info(&pdev->dev, "Creating sysfs group for PROC_THERMAL_PLATFORM_DEV\n");
-
- ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_tcc_offset_degree_celsius.attr);
- if (ret)
- return ret;
-
- ret = sysfs_create_group(&pdev->dev.kobj, &power_limit_attribute_group);
- if (ret)
- sysfs_remove_file(&pdev->dev.kobj, &dev_attr_tcc_offset_degree_celsius.attr);
-
- return ret;
-}
-
-static int int3401_remove(struct platform_device *pdev)
+int proc_thermal_suspend(struct device *dev)
{
- proc_thermal_remove(platform_get_drvdata(pdev));
+ tcc_offset_save = tcc_get_offset();
+ if (tcc_offset_save < 0)
+ dev_warn(dev, "failed to save offset (%d)\n", tcc_offset_save);
return 0;
}
+EXPORT_SYMBOL_GPL(proc_thermal_suspend);
-static irqreturn_t proc_thermal_pci_msi_irq(int irq, void *devid)
+int proc_thermal_resume(struct device *dev)
{
- struct proc_thermal_device *proc_priv;
- struct pci_dev *pdev = devid;
-
- proc_priv = pci_get_drvdata(pdev);
-
- intel_soc_dts_iosf_interrupt_handler(proc_priv->soc_dts);
-
- return IRQ_HANDLED;
-}
-
-#ifdef CONFIG_PROC_THERMAL_MMIO_RAPL
-
-#define MCHBAR 0
-
-/* RAPL Support via MMIO interface */
-static struct rapl_if_priv rapl_mmio_priv;
-
-static int rapl_mmio_cpu_online(unsigned int cpu)
-{
- struct rapl_package *rp;
+ struct proc_thermal_device *proc_dev;
- /* mmio rapl supports package 0 only for now */
- if (topology_physical_package_id(cpu))
- return 0;
+ proc_dev = dev_get_drvdata(dev);
+ proc_thermal_read_ppcc(proc_dev);
- rp = rapl_find_package_domain(cpu, &rapl_mmio_priv);
- if (!rp) {
- rp = rapl_add_package(cpu, &rapl_mmio_priv);
- if (IS_ERR(rp))
- return PTR_ERR(rp);
- }
- cpumask_set_cpu(cpu, &rp->cpumask);
- return 0;
-}
+ /* Do not update if saving failed */
+ if (tcc_offset_save >= 0)
+ tcc_offset_update(tcc_offset_save);
-static int rapl_mmio_cpu_down_prep(unsigned int cpu)
-{
- struct rapl_package *rp;
- int lead_cpu;
-
- rp = rapl_find_package_domain(cpu, &rapl_mmio_priv);
- if (!rp)
- return 0;
-
- cpumask_clear_cpu(cpu, &rp->cpumask);
- lead_cpu = cpumask_first(&rp->cpumask);
- if (lead_cpu >= nr_cpu_ids)
- rapl_remove_package(rp);
- else if (rp->lead_cpu == cpu)
- rp->lead_cpu = lead_cpu;
return 0;
}
+EXPORT_SYMBOL_GPL(proc_thermal_resume);
-static int rapl_mmio_read_raw(int cpu, struct reg_action *ra)
-{
- if (!ra->reg)
- return -EINVAL;
-
- ra->value = readq((void __iomem *)ra->reg);
- ra->value &= ra->mask;
- return 0;
-}
-
-static int rapl_mmio_write_raw(int cpu, struct reg_action *ra)
-{
- u64 val;
-
- if (!ra->reg)
- return -EINVAL;
-
- val = readq((void __iomem *)ra->reg);
- val &= ~ra->mask;
- val |= ra->value;
- writeq(val, (void __iomem *)ra->reg);
- return 0;
-}
+#define MCHBAR 0
-static int proc_thermal_rapl_add(struct pci_dev *pdev,
- struct proc_thermal_device *proc_priv,
- struct rapl_mmio_regs *rapl_regs)
+static int proc_thermal_set_mmio_base(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
{
- enum rapl_domain_reg_id reg;
- enum rapl_domain_type domain;
int ret;
- if (!rapl_regs)
- return 0;
-
ret = pcim_iomap_regions(pdev, 1 << MCHBAR, DRV_NAME);
if (ret) {
dev_err(&pdev->dev, "cannot reserve PCI memory region\n");
@@ -544,240 +393,72 @@ static int proc_thermal_rapl_add(struct pci_dev *pdev,
proc_priv->mmio_base = pcim_iomap_table(pdev)[MCHBAR];
- for (domain = RAPL_DOMAIN_PACKAGE; domain < RAPL_DOMAIN_MAX; domain++) {
- for (reg = RAPL_DOMAIN_REG_LIMIT; reg < RAPL_DOMAIN_REG_MAX; reg++)
- if (rapl_regs->regs[domain][reg])
- rapl_mmio_priv.regs[domain][reg] =
- (u64)proc_priv->mmio_base +
- rapl_regs->regs[domain][reg];
- rapl_mmio_priv.limits[domain] = rapl_regs->limits[domain];
- }
- rapl_mmio_priv.reg_unit = (u64)proc_priv->mmio_base + rapl_regs->reg_unit;
-
- rapl_mmio_priv.read_raw = rapl_mmio_read_raw;
- rapl_mmio_priv.write_raw = rapl_mmio_write_raw;
-
- rapl_mmio_priv.control_type = powercap_register_control_type(NULL, "intel-rapl-mmio", NULL);
- if (IS_ERR(rapl_mmio_priv.control_type)) {
- pr_debug("failed to register powercap control_type.\n");
- return PTR_ERR(rapl_mmio_priv.control_type);
- }
-
- ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "powercap/rapl:online",
- rapl_mmio_cpu_online, rapl_mmio_cpu_down_prep);
- if (ret < 0) {
- powercap_unregister_control_type(rapl_mmio_priv.control_type);
- rapl_mmio_priv.control_type = NULL;
- return ret;
- }
- rapl_mmio_priv.pcap_rapl_online = ret;
-
return 0;
}
-static void proc_thermal_rapl_remove(void)
+int proc_thermal_mmio_add(struct pci_dev *pdev,
+ struct proc_thermal_device *proc_priv,
+ kernel_ulong_t feature_mask)
{
- if (IS_ERR_OR_NULL(rapl_mmio_priv.control_type))
- return;
-
- cpuhp_remove_state(rapl_mmio_priv.pcap_rapl_online);
- powercap_unregister_control_type(rapl_mmio_priv.control_type);
-}
-
-static const struct rapl_mmio_regs rapl_mmio_hsw = {
- .reg_unit = 0x5938,
- .regs[RAPL_DOMAIN_PACKAGE] = { 0x59a0, 0x593c, 0x58f0, 0, 0x5930},
- .regs[RAPL_DOMAIN_DRAM] = { 0x58e0, 0x58e8, 0x58ec, 0, 0},
- .limits[RAPL_DOMAIN_PACKAGE] = 2,
- .limits[RAPL_DOMAIN_DRAM] = 2,
-};
-
-#else
-
-static int proc_thermal_rapl_add(struct pci_dev *pdev,
- struct proc_thermal_device *proc_priv,
- struct rapl_mmio_regs *rapl_regs)
-{
- return 0;
-}
-static void proc_thermal_rapl_remove(void) {}
-static const struct rapl_mmio_regs rapl_mmio_hsw;
-
-#endif /* CONFIG_MMIO_RAPL */
-
-static int proc_thermal_pci_probe(struct pci_dev *pdev,
- const struct pci_device_id *id)
-{
- struct proc_thermal_device *proc_priv;
int ret;
- if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) {
- dev_err(&pdev->dev, "error: enumerated as platform dev\n");
- return -ENODEV;
- }
+ proc_priv->mmio_feature_mask = feature_mask;
- ret = pcim_enable_device(pdev);
- if (ret < 0) {
- dev_err(&pdev->dev, "error: could not enable device\n");
- return ret;
+ if (feature_mask) {
+ ret = proc_thermal_set_mmio_base(pdev, proc_priv);
+ if (ret)
+ return ret;
}
- ret = proc_thermal_add(&pdev->dev, &proc_priv);
- if (ret)
- return ret;
-
- ret = proc_thermal_rapl_add(pdev, proc_priv,
- (struct rapl_mmio_regs *)id->driver_data);
- if (ret) {
- dev_err(&pdev->dev, "failed to add RAPL MMIO interface\n");
- proc_thermal_remove(proc_priv);
- return ret;
+ if (feature_mask & PROC_THERMAL_FEATURE_RAPL) {
+ ret = proc_thermal_rapl_add(pdev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add RAPL MMIO interface\n");
+ return ret;
+ }
}
- pci_set_drvdata(pdev, proc_priv);
- proc_thermal_emum_mode = PROC_THERMAL_PCI;
-
- if (pdev->device == PCI_DEVICE_ID_PROC_BSW_THERMAL) {
- /*
- * Enumerate additional DTS sensors available via IOSF.
- * But we are not treating as a failure condition, if
- * there are no aux DTSs enabled or fails. This driver
- * already exposes sensors, which can be accessed via
- * ACPI/MSR. So we don't want to fail for auxiliary DTSs.
- */
- proc_priv->soc_dts = intel_soc_dts_iosf_init(
- INTEL_SOC_DTS_INTERRUPT_MSI, 2, 0);
-
- if (!IS_ERR(proc_priv->soc_dts) && pdev->irq) {
- ret = pci_enable_msi(pdev);
- if (!ret) {
- ret = request_threaded_irq(pdev->irq, NULL,
- proc_thermal_pci_msi_irq,
- IRQF_ONESHOT, "proc_thermal",
- pdev);
- if (ret) {
- intel_soc_dts_iosf_exit(
- proc_priv->soc_dts);
- pci_disable_msi(pdev);
- proc_priv->soc_dts = NULL;
- }
- }
- } else
- dev_err(&pdev->dev, "No auxiliary DTSs enabled\n");
+ if (feature_mask & PROC_THERMAL_FEATURE_FIVR ||
+ feature_mask & PROC_THERMAL_FEATURE_DVFS) {
+ ret = proc_thermal_rfim_add(pdev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add RFIM interface\n");
+ goto err_rem_rapl;
+ }
}
- dev_info(&pdev->dev, "Creating sysfs group for PROC_THERMAL_PCI\n");
-
- ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_tcc_offset_degree_celsius.attr);
- if (ret)
- return ret;
-
- ret = sysfs_create_group(&pdev->dev.kobj, &power_limit_attribute_group);
- if (ret)
- sysfs_remove_file(&pdev->dev.kobj, &dev_attr_tcc_offset_degree_celsius.attr);
-
- return ret;
-}
-
-static void proc_thermal_pci_remove(struct pci_dev *pdev)
-{
- struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
-
- if (proc_priv->soc_dts) {
- intel_soc_dts_iosf_exit(proc_priv->soc_dts);
- if (pdev->irq) {
- free_irq(pdev->irq, pdev);
- pci_disable_msi(pdev);
+ if (feature_mask & PROC_THERMAL_FEATURE_MBOX) {
+ ret = proc_thermal_mbox_add(pdev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add MBOX interface\n");
+ goto err_rem_rfim;
}
}
- proc_thermal_rapl_remove();
- proc_thermal_remove(proc_priv);
-}
-
-#ifdef CONFIG_PM_SLEEP
-static int proc_thermal_resume(struct device *dev)
-{
- struct proc_thermal_device *proc_dev;
-
- proc_dev = dev_get_drvdata(dev);
- proc_thermal_read_ppcc(proc_dev);
-
- tcc_offset_update(tcc_offset_save);
return 0;
-}
-#else
-#define proc_thermal_resume NULL
-#endif
-
-static SIMPLE_DEV_PM_OPS(proc_thermal_pm, NULL, proc_thermal_resume);
-
-static const struct pci_device_id proc_thermal_pci_ids[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_SKL_THERMAL),
- .driver_data = (kernel_ulong_t)&rapl_mmio_hsw, },
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT0_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT1_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTX_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTP_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CNL_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CFL_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_GLK_THERMAL)},
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_ICL_THERMAL),
- .driver_data = (kernel_ulong_t)&rapl_mmio_hsw, },
- { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_JSL_THERMAL)},
- { 0, },
-};
-
-MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids);
-
-static struct pci_driver proc_thermal_pci_driver = {
- .name = DRV_NAME,
- .probe = proc_thermal_pci_probe,
- .remove = proc_thermal_pci_remove,
- .id_table = proc_thermal_pci_ids,
- .driver.pm = &proc_thermal_pm,
-};
-
-static const struct acpi_device_id int3401_device_ids[] = {
- {"INT3401", 0},
- {"", 0},
-};
-MODULE_DEVICE_TABLE(acpi, int3401_device_ids);
-
-static struct platform_driver int3401_driver = {
- .probe = int3401_add,
- .remove = int3401_remove,
- .driver = {
- .name = "int3401 thermal",
- .acpi_match_table = int3401_device_ids,
- .pm = &proc_thermal_pm,
- },
-};
-static int __init proc_thermal_init(void)
-{
- int ret;
-
- ret = platform_driver_register(&int3401_driver);
- if (ret)
- return ret;
-
- ret = pci_register_driver(&proc_thermal_pci_driver);
+err_rem_rfim:
+ proc_thermal_rfim_remove(pdev);
+err_rem_rapl:
+ proc_thermal_rapl_remove();
return ret;
}
+EXPORT_SYMBOL_GPL(proc_thermal_mmio_add);
-static void __exit proc_thermal_exit(void)
+void proc_thermal_mmio_remove(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
{
- platform_driver_unregister(&int3401_driver);
- pci_unregister_driver(&proc_thermal_pci_driver);
-}
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_RAPL)
+ proc_thermal_rapl_remove();
-module_init(proc_thermal_init);
-module_exit(proc_thermal_exit);
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR ||
+ proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS)
+ proc_thermal_rfim_remove(pdev);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_MBOX)
+ proc_thermal_mbox_remove(pdev);
+}
+EXPORT_SYMBOL_GPL(proc_thermal_mmio_remove);
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h
new file mode 100644
index 000000000000..7d52fcff4937
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * processor_thermal_device.h
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __PROCESSOR_THERMAL_DEVICE_H__
+#define __PROCESSOR_THERMAL_DEVICE_H__
+
+#include <linux/intel_rapl.h>
+
+#define PCI_DEVICE_ID_INTEL_ADL_THERMAL 0x461d
+#define PCI_DEVICE_ID_INTEL_BDW_THERMAL 0x1603
+#define PCI_DEVICE_ID_INTEL_BSW_THERMAL 0x22DC
+
+#define PCI_DEVICE_ID_INTEL_BXT0_THERMAL 0x0A8C
+#define PCI_DEVICE_ID_INTEL_BXT1_THERMAL 0x1A8C
+#define PCI_DEVICE_ID_INTEL_BXTX_THERMAL 0x4A8C
+#define PCI_DEVICE_ID_INTEL_BXTP_THERMAL 0x5A8C
+
+#define PCI_DEVICE_ID_INTEL_CNL_THERMAL 0x5a03
+#define PCI_DEVICE_ID_INTEL_CFL_THERMAL 0x3E83
+#define PCI_DEVICE_ID_INTEL_GLK_THERMAL 0x318C
+#define PCI_DEVICE_ID_INTEL_HSB_THERMAL 0x0A03
+#define PCI_DEVICE_ID_INTEL_ICL_THERMAL 0x8a03
+#define PCI_DEVICE_ID_INTEL_JSL_THERMAL 0x4E03
+#define PCI_DEVICE_ID_INTEL_MTLP_THERMAL 0x7D03
+#define PCI_DEVICE_ID_INTEL_RPL_THERMAL 0xA71D
+#define PCI_DEVICE_ID_INTEL_SKL_THERMAL 0x1903
+#define PCI_DEVICE_ID_INTEL_TGL_THERMAL 0x9A03
+
+struct power_config {
+ u32 index;
+ u32 min_uw;
+ u32 max_uw;
+ u32 tmin_us;
+ u32 tmax_us;
+ u32 step_uw;
+};
+
+struct proc_thermal_device {
+ struct device *dev;
+ struct acpi_device *adev;
+ struct power_config power_limits[2];
+ struct int34x_thermal_zone *int340x_zone;
+ struct intel_soc_dts_sensors *soc_dts;
+ u32 mmio_feature_mask;
+ void __iomem *mmio_base;
+ void *priv_data;
+};
+
+struct rapl_mmio_regs {
+ u64 reg_unit;
+ u64 regs[RAPL_DOMAIN_MAX][RAPL_DOMAIN_REG_MAX];
+ int limits[RAPL_DOMAIN_MAX];
+};
+
+#define PROC_THERMAL_FEATURE_NONE 0x00
+#define PROC_THERMAL_FEATURE_RAPL 0x01
+#define PROC_THERMAL_FEATURE_FIVR 0x02
+#define PROC_THERMAL_FEATURE_DVFS 0x04
+#define PROC_THERMAL_FEATURE_MBOX 0x08
+
+#if IS_ENABLED(CONFIG_PROC_THERMAL_MMIO_RAPL)
+int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_rapl_remove(void);
+#else
+static int __maybe_unused proc_thermal_rapl_add(struct pci_dev *pdev,
+ struct proc_thermal_device *proc_priv)
+{
+ return 0;
+}
+
+static void __maybe_unused proc_thermal_rapl_remove(void)
+{
+}
+#endif
+
+int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_rfim_remove(struct pci_dev *pdev);
+
+int proc_thermal_mbox_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_mbox_remove(struct pci_dev *pdev);
+
+int processor_thermal_send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp);
+int processor_thermal_send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data);
+int proc_thermal_add(struct device *dev, struct proc_thermal_device *priv);
+void proc_thermal_remove(struct proc_thermal_device *proc_priv);
+int proc_thermal_suspend(struct device *dev);
+int proc_thermal_resume(struct device *dev);
+int proc_thermal_mmio_add(struct pci_dev *pdev,
+ struct proc_thermal_device *proc_priv,
+ kernel_ulong_t feature_mask);
+void proc_thermal_mmio_remove(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+#endif
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c
new file mode 100644
index 000000000000..bf1b1cdfade4
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Processor thermal device for newer processors
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/thermal.h>
+
+#include "int340x_thermal_zone.h"
+#include "processor_thermal_device.h"
+
+#define DRV_NAME "proc_thermal_pci"
+
+struct proc_thermal_pci {
+ struct pci_dev *pdev;
+ struct proc_thermal_device *proc_priv;
+ struct thermal_zone_device *tzone;
+ struct delayed_work work;
+ int stored_thres;
+ int no_legacy;
+};
+
+enum proc_thermal_mmio_type {
+ PROC_THERMAL_MMIO_TJMAX,
+ PROC_THERMAL_MMIO_PP0_TEMP,
+ PROC_THERMAL_MMIO_PP1_TEMP,
+ PROC_THERMAL_MMIO_PKG_TEMP,
+ PROC_THERMAL_MMIO_THRES_0,
+ PROC_THERMAL_MMIO_THRES_1,
+ PROC_THERMAL_MMIO_INT_ENABLE_0,
+ PROC_THERMAL_MMIO_INT_ENABLE_1,
+ PROC_THERMAL_MMIO_INT_STATUS_0,
+ PROC_THERMAL_MMIO_INT_STATUS_1,
+ PROC_THERMAL_MMIO_MAX
+};
+
+struct proc_thermal_mmio_info {
+ enum proc_thermal_mmio_type mmio_type;
+ u64 mmio_addr;
+ u64 shift;
+ u64 mask;
+};
+
+static struct proc_thermal_mmio_info proc_thermal_mmio_info[] = {
+ { PROC_THERMAL_MMIO_TJMAX, 0x599c, 16, 0xff },
+ { PROC_THERMAL_MMIO_PP0_TEMP, 0x597c, 0, 0xff },
+ { PROC_THERMAL_MMIO_PP1_TEMP, 0x5980, 0, 0xff },
+ { PROC_THERMAL_MMIO_PKG_TEMP, 0x5978, 0, 0xff },
+ { PROC_THERMAL_MMIO_THRES_0, 0x5820, 8, 0x7F },
+ { PROC_THERMAL_MMIO_THRES_1, 0x5820, 16, 0x7F },
+ { PROC_THERMAL_MMIO_INT_ENABLE_0, 0x5820, 15, 0x01 },
+ { PROC_THERMAL_MMIO_INT_ENABLE_1, 0x5820, 23, 0x01 },
+ { PROC_THERMAL_MMIO_INT_STATUS_0, 0x7200, 6, 0x01 },
+ { PROC_THERMAL_MMIO_INT_STATUS_1, 0x7200, 8, 0x01 },
+};
+
+#define B0D4_THERMAL_NOTIFY_DELAY 1000
+static int notify_delay_ms = B0D4_THERMAL_NOTIFY_DELAY;
+
+static void proc_thermal_mmio_read(struct proc_thermal_pci *pci_info,
+ enum proc_thermal_mmio_type type,
+ u32 *value)
+{
+ *value = ioread32(((u8 __iomem *)pci_info->proc_priv->mmio_base +
+ proc_thermal_mmio_info[type].mmio_addr));
+ *value >>= proc_thermal_mmio_info[type].shift;
+ *value &= proc_thermal_mmio_info[type].mask;
+}
+
+static void proc_thermal_mmio_write(struct proc_thermal_pci *pci_info,
+ enum proc_thermal_mmio_type type,
+ u32 value)
+{
+ u32 current_val;
+ u32 mask;
+
+ current_val = ioread32(((u8 __iomem *)pci_info->proc_priv->mmio_base +
+ proc_thermal_mmio_info[type].mmio_addr));
+ mask = proc_thermal_mmio_info[type].mask << proc_thermal_mmio_info[type].shift;
+ current_val &= ~mask;
+
+ value &= proc_thermal_mmio_info[type].mask;
+ value <<= proc_thermal_mmio_info[type].shift;
+
+ current_val |= value;
+ iowrite32(current_val, ((u8 __iomem *)pci_info->proc_priv->mmio_base +
+ proc_thermal_mmio_info[type].mmio_addr));
+}
+
+/*
+ * To avoid sending two many messages to user space, we have 1 second delay.
+ * On interrupt we are disabling interrupt and enabling after 1 second.
+ * This workload function is delayed by 1 second.
+ */
+static void proc_thermal_threshold_work_fn(struct work_struct *work)
+{
+ struct delayed_work *delayed_work = to_delayed_work(work);
+ struct proc_thermal_pci *pci_info = container_of(delayed_work,
+ struct proc_thermal_pci, work);
+ struct thermal_zone_device *tzone = pci_info->tzone;
+
+ if (tzone)
+ thermal_zone_device_update(tzone, THERMAL_TRIP_VIOLATED);
+
+ /* Enable interrupt flag */
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 1);
+}
+
+static void pkg_thermal_schedule_work(struct delayed_work *work)
+{
+ unsigned long ms = msecs_to_jiffies(notify_delay_ms);
+
+ schedule_delayed_work(work, ms);
+}
+
+static irqreturn_t proc_thermal_irq_handler(int irq, void *devid)
+{
+ struct proc_thermal_pci *pci_info = devid;
+ u32 status;
+
+ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_INT_STATUS_0, &status);
+
+ /* Disable enable interrupt flag */
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 0);
+ pci_write_config_byte(pci_info->pdev, 0xdc, 0x01);
+
+ pkg_thermal_schedule_work(&pci_info->work);
+
+ return IRQ_HANDLED;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ struct proc_thermal_pci *pci_info = tzd->devdata;
+ u32 _temp;
+
+ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_PKG_TEMP, &_temp);
+ *temp = (unsigned long)_temp * 1000;
+
+ return 0;
+}
+
+static int sys_get_trip_temp(struct thermal_zone_device *tzd,
+ int trip, int *temp)
+{
+ struct proc_thermal_pci *pci_info = tzd->devdata;
+ u32 _temp;
+
+ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_THRES_0, &_temp);
+ if (!_temp) {
+ *temp = THERMAL_TEMP_INVALID;
+ } else {
+ int tjmax;
+
+ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_TJMAX, &tjmax);
+ _temp = tjmax - _temp;
+ *temp = (unsigned long)_temp * 1000;
+ }
+
+ return 0;
+}
+
+static int sys_get_trip_type(struct thermal_zone_device *tzd, int trip,
+ enum thermal_trip_type *type)
+{
+ *type = THERMAL_TRIP_PASSIVE;
+
+ return 0;
+}
+
+static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
+{
+ struct proc_thermal_pci *pci_info = tzd->devdata;
+ int tjmax, _temp;
+
+ if (temp <= 0) {
+ cancel_delayed_work_sync(&pci_info->work);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 0);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_THRES_0, 0);
+ thermal_zone_device_disable(tzd);
+ pci_info->stored_thres = 0;
+ return 0;
+ }
+
+ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_TJMAX, &tjmax);
+ _temp = tjmax - (temp / 1000);
+ if (_temp < 0)
+ return -EINVAL;
+
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_THRES_0, _temp);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 1);
+
+ thermal_zone_device_enable(tzd);
+ pci_info->stored_thres = temp;
+
+ 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 struct thermal_zone_params tzone_params = {
+ .governor_name = "user_space",
+ .no_hwmon = true,
+};
+
+static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct proc_thermal_device *proc_priv;
+ struct proc_thermal_pci *pci_info;
+ int irq_flag = 0, irq, ret;
+
+ proc_priv = devm_kzalloc(&pdev->dev, sizeof(*proc_priv), GFP_KERNEL);
+ if (!proc_priv)
+ return -ENOMEM;
+
+ pci_info = devm_kzalloc(&pdev->dev, sizeof(*pci_info), GFP_KERNEL);
+ if (!pci_info)
+ return -ENOMEM;
+
+ pci_info->pdev = pdev;
+ ret = pcim_enable_device(pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "error: could not enable device\n");
+ return ret;
+ }
+
+ pci_set_master(pdev);
+
+ INIT_DELAYED_WORK(&pci_info->work, proc_thermal_threshold_work_fn);
+
+ ret = proc_thermal_add(&pdev->dev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "error: proc_thermal_add, will continue\n");
+ pci_info->no_legacy = 1;
+ }
+
+ proc_priv->priv_data = pci_info;
+ pci_info->proc_priv = proc_priv;
+ pci_set_drvdata(pdev, proc_priv);
+
+ ret = proc_thermal_mmio_add(pdev, proc_priv, id->driver_data);
+ if (ret)
+ goto err_ret_thermal;
+
+ pci_info->tzone = thermal_zone_device_register("TCPU_PCI", 1, 1, pci_info,
+ &tzone_ops,
+ &tzone_params, 0, 0);
+ if (IS_ERR(pci_info->tzone)) {
+ ret = PTR_ERR(pci_info->tzone);
+ goto err_ret_mmio;
+ }
+
+ /* request and enable interrupt */
+ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to allocate vectors!\n");
+ goto err_ret_tzone;
+ }
+ if (!pdev->msi_enabled && !pdev->msix_enabled)
+ irq_flag = IRQF_SHARED;
+
+ irq = pci_irq_vector(pdev, 0);
+ ret = devm_request_threaded_irq(&pdev->dev, irq,
+ proc_thermal_irq_handler, NULL,
+ irq_flag, KBUILD_MODNAME, pci_info);
+ if (ret) {
+ dev_err(&pdev->dev, "Request IRQ %d failed\n", pdev->irq);
+ goto err_free_vectors;
+ }
+
+ return 0;
+
+err_free_vectors:
+ pci_free_irq_vectors(pdev);
+err_ret_tzone:
+ thermal_zone_device_unregister(pci_info->tzone);
+err_ret_mmio:
+ proc_thermal_mmio_remove(pdev, proc_priv);
+err_ret_thermal:
+ if (!pci_info->no_legacy)
+ proc_thermal_remove(proc_priv);
+ pci_disable_device(pdev);
+
+ return ret;
+}
+
+static void proc_thermal_pci_remove(struct pci_dev *pdev)
+{
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+ struct proc_thermal_pci *pci_info = proc_priv->priv_data;
+
+ cancel_delayed_work_sync(&pci_info->work);
+
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_THRES_0, 0);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 0);
+
+ devm_free_irq(&pdev->dev, pdev->irq, pci_info);
+ pci_free_irq_vectors(pdev);
+
+ thermal_zone_device_unregister(pci_info->tzone);
+ proc_thermal_mmio_remove(pdev, pci_info->proc_priv);
+ if (!pci_info->no_legacy)
+ proc_thermal_remove(proc_priv);
+ pci_disable_device(pdev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int proc_thermal_pci_suspend(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct proc_thermal_device *proc_priv;
+ struct proc_thermal_pci *pci_info;
+
+ proc_priv = pci_get_drvdata(pdev);
+ pci_info = proc_priv->priv_data;
+
+ if (!pci_info->no_legacy)
+ return proc_thermal_suspend(dev);
+
+ return 0;
+}
+static int proc_thermal_pci_resume(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct proc_thermal_device *proc_priv;
+ struct proc_thermal_pci *pci_info;
+
+ proc_priv = pci_get_drvdata(pdev);
+ pci_info = proc_priv->priv_data;
+
+ if (pci_info->stored_thres) {
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_THRES_0,
+ pci_info->stored_thres / 1000);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 1);
+ }
+
+ if (!pci_info->no_legacy)
+ return proc_thermal_resume(dev);
+
+ return 0;
+}
+#else
+#define proc_thermal_pci_suspend NULL
+#define proc_thermal_pci_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(proc_thermal_pci_pm, proc_thermal_pci_suspend,
+ proc_thermal_pci_resume);
+
+static const struct pci_device_id proc_thermal_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, ADL_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_MBOX) },
+ { PCI_DEVICE_DATA(INTEL, MTLP_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_MBOX) },
+ { PCI_DEVICE_DATA(INTEL, RPL_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_MBOX) },
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids);
+
+static struct pci_driver proc_thermal_pci_driver = {
+ .name = DRV_NAME,
+ .probe = proc_thermal_pci_probe,
+ .remove = proc_thermal_pci_remove,
+ .id_table = proc_thermal_pci_ids,
+ .driver.pm = &proc_thermal_pci_pm,
+};
+
+module_pci_driver(proc_thermal_pci_driver);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c
new file mode 100644
index 000000000000..09e032f822f3
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * B0D4 processor thermal device
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/thermal.h>
+
+#include "int340x_thermal_zone.h"
+#include "processor_thermal_device.h"
+#include "../intel_soc_dts_iosf.h"
+
+#define DRV_NAME "proc_thermal"
+
+static irqreturn_t proc_thermal_pci_msi_irq(int irq, void *devid)
+{
+ struct proc_thermal_device *proc_priv;
+ struct pci_dev *pdev = devid;
+
+ proc_priv = pci_get_drvdata(pdev);
+
+ intel_soc_dts_iosf_interrupt_handler(proc_priv->soc_dts);
+
+ return IRQ_HANDLED;
+}
+
+static int proc_thermal_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct proc_thermal_device *proc_priv;
+ int ret;
+
+ ret = pcim_enable_device(pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "error: could not enable device\n");
+ return ret;
+ }
+
+ proc_priv = devm_kzalloc(&pdev->dev, sizeof(*proc_priv), GFP_KERNEL);
+ if (!proc_priv)
+ return -ENOMEM;
+
+ ret = proc_thermal_add(&pdev->dev, proc_priv);
+ if (ret)
+ return ret;
+
+ pci_set_drvdata(pdev, proc_priv);
+
+ if (pdev->device == PCI_DEVICE_ID_INTEL_BSW_THERMAL) {
+ /*
+ * Enumerate additional DTS sensors available via IOSF.
+ * But we are not treating as a failure condition, if
+ * there are no aux DTSs enabled or fails. This driver
+ * already exposes sensors, which can be accessed via
+ * ACPI/MSR. So we don't want to fail for auxiliary DTSs.
+ */
+ proc_priv->soc_dts = intel_soc_dts_iosf_init(
+ INTEL_SOC_DTS_INTERRUPT_MSI, 2, 0);
+
+ if (!IS_ERR(proc_priv->soc_dts) && pdev->irq) {
+ ret = pci_enable_msi(pdev);
+ if (!ret) {
+ ret = request_threaded_irq(pdev->irq, NULL,
+ proc_thermal_pci_msi_irq,
+ IRQF_ONESHOT, "proc_thermal",
+ pdev);
+ if (ret) {
+ intel_soc_dts_iosf_exit(
+ proc_priv->soc_dts);
+ pci_disable_msi(pdev);
+ proc_priv->soc_dts = NULL;
+ }
+ }
+ } else
+ dev_err(&pdev->dev, "No auxiliary DTSs enabled\n");
+ } else {
+
+ }
+
+ ret = proc_thermal_mmio_add(pdev, proc_priv, id->driver_data);
+ if (ret) {
+ proc_thermal_remove(proc_priv);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void proc_thermal_pci_remove(struct pci_dev *pdev)
+{
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+
+ if (proc_priv->soc_dts) {
+ intel_soc_dts_iosf_exit(proc_priv->soc_dts);
+ if (pdev->irq) {
+ free_irq(pdev->irq, pdev);
+ pci_disable_msi(pdev);
+ }
+ }
+
+ proc_thermal_mmio_remove(pdev, proc_priv);
+ proc_thermal_remove(proc_priv);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int proc_thermal_pci_suspend(struct device *dev)
+{
+ return proc_thermal_suspend(dev);
+}
+static int proc_thermal_pci_resume(struct device *dev)
+{
+ return proc_thermal_resume(dev);
+}
+#else
+#define proc_thermal_pci_suspend NULL
+#define proc_thermal_pci_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(proc_thermal_pci_pm, proc_thermal_pci_suspend,
+ proc_thermal_pci_resume);
+
+static const struct pci_device_id proc_thermal_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, BDW_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BSW_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXT0_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXT1_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXTX_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXTP_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, CNL_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, CFL_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, GLK_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, HSB_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, ICL_THERMAL, PROC_THERMAL_FEATURE_RAPL) },
+ { PCI_DEVICE_DATA(INTEL, JSL_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, SKL_THERMAL, PROC_THERMAL_FEATURE_RAPL) },
+ { PCI_DEVICE_DATA(INTEL, TGL_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_MBOX) },
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids);
+
+static struct pci_driver proc_thermal_pci_driver = {
+ .name = DRV_NAME,
+ .probe = proc_thermal_pci_probe,
+ .remove = proc_thermal_pci_remove,
+ .id_table = proc_thermal_pci_ids,
+ .driver.pm = &proc_thermal_pci_pm,
+};
+
+module_pci_driver(proc_thermal_pci_driver);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c
new file mode 100644
index 000000000000..0b89a4340ff4
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device mailbox driver for Workload type hints
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include "processor_thermal_device.h"
+
+#define MBOX_CMD_WORKLOAD_TYPE_READ 0x0E
+#define MBOX_CMD_WORKLOAD_TYPE_WRITE 0x0F
+
+#define MBOX_OFFSET_DATA 0x5810
+#define MBOX_OFFSET_INTERFACE 0x5818
+
+#define MBOX_BUSY_BIT 31
+#define MBOX_RETRY_COUNT 100
+
+#define MBOX_DATA_BIT_VALID 31
+#define MBOX_DATA_BIT_AC_DC 30
+
+static DEFINE_MUTEX(mbox_lock);
+
+static int wait_for_mbox_ready(struct proc_thermal_device *proc_priv)
+{
+ u32 retries, data;
+ int ret;
+
+ /* Poll for rb bit == 0 */
+ retries = MBOX_RETRY_COUNT;
+ do {
+ data = readl(proc_priv->mmio_base + MBOX_OFFSET_INTERFACE);
+ if (data & BIT_ULL(MBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ continue;
+ }
+ ret = 0;
+ break;
+ } while (--retries);
+
+ return ret;
+}
+
+static int send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data)
+{
+ struct proc_thermal_device *proc_priv;
+ u32 reg_data;
+ int ret;
+
+ proc_priv = pci_get_drvdata(pdev);
+
+ mutex_lock(&mbox_lock);
+
+ ret = wait_for_mbox_ready(proc_priv);
+ if (ret)
+ goto unlock_mbox;
+
+ writel(data, (proc_priv->mmio_base + MBOX_OFFSET_DATA));
+ /* Write command register */
+ reg_data = BIT_ULL(MBOX_BUSY_BIT) | id;
+ writel(reg_data, (proc_priv->mmio_base + MBOX_OFFSET_INTERFACE));
+
+ ret = wait_for_mbox_ready(proc_priv);
+
+unlock_mbox:
+ mutex_unlock(&mbox_lock);
+ return ret;
+}
+
+static int send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp)
+{
+ struct proc_thermal_device *proc_priv;
+ u32 reg_data;
+ int ret;
+
+ proc_priv = pci_get_drvdata(pdev);
+
+ mutex_lock(&mbox_lock);
+
+ ret = wait_for_mbox_ready(proc_priv);
+ if (ret)
+ goto unlock_mbox;
+
+ /* Write command register */
+ reg_data = BIT_ULL(MBOX_BUSY_BIT) | id;
+ writel(reg_data, (proc_priv->mmio_base + MBOX_OFFSET_INTERFACE));
+
+ ret = wait_for_mbox_ready(proc_priv);
+ if (ret)
+ goto unlock_mbox;
+
+ if (id == MBOX_CMD_WORKLOAD_TYPE_READ)
+ *resp = readl(proc_priv->mmio_base + MBOX_OFFSET_DATA);
+ else
+ *resp = readq(proc_priv->mmio_base + MBOX_OFFSET_DATA);
+
+unlock_mbox:
+ mutex_unlock(&mbox_lock);
+ return ret;
+}
+
+int processor_thermal_send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp)
+{
+ return send_mbox_read_cmd(pdev, id, resp);
+}
+EXPORT_SYMBOL_NS_GPL(processor_thermal_send_mbox_read_cmd, INT340X_THERMAL);
+
+int processor_thermal_send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data)
+{
+ return send_mbox_write_cmd(pdev, id, data);
+}
+EXPORT_SYMBOL_NS_GPL(processor_thermal_send_mbox_write_cmd, INT340X_THERMAL);
+
+/* List of workload types */
+static const char * const workload_types[] = {
+ "none",
+ "idle",
+ "semi_active",
+ "bursty",
+ "sustained",
+ "battery_life",
+ NULL
+};
+
+static ssize_t workload_available_types_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i = 0;
+ int ret = 0;
+
+ while (workload_types[i] != NULL)
+ ret += sprintf(&buf[ret], "%s ", workload_types[i++]);
+
+ ret += sprintf(&buf[ret], "\n");
+
+ return ret;
+}
+
+static DEVICE_ATTR_RO(workload_available_types);
+
+static ssize_t workload_type_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ char str_preference[15];
+ u32 data = 0;
+ ssize_t ret;
+
+ ret = sscanf(buf, "%14s", str_preference);
+ if (ret != 1)
+ return -EINVAL;
+
+ ret = match_string(workload_types, -1, str_preference);
+ if (ret < 0)
+ return ret;
+
+ ret &= 0xff;
+
+ if (ret)
+ data = BIT(MBOX_DATA_BIT_VALID) | BIT(MBOX_DATA_BIT_AC_DC);
+
+ data |= ret;
+
+ ret = send_mbox_write_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_WRITE, data);
+ if (ret)
+ return false;
+
+ return count;
+}
+
+static ssize_t workload_type_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u64 cmd_resp;
+ int ret;
+
+ ret = send_mbox_read_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_READ, &cmd_resp);
+ if (ret)
+ return false;
+
+ cmd_resp &= 0xff;
+
+ if (cmd_resp > ARRAY_SIZE(workload_types) - 1)
+ return -EINVAL;
+
+ return sprintf(buf, "%s\n", workload_types[cmd_resp]);
+}
+
+static DEVICE_ATTR_RW(workload_type);
+
+static struct attribute *workload_req_attrs[] = {
+ &dev_attr_workload_available_types.attr,
+ &dev_attr_workload_type.attr,
+ NULL
+};
+
+static const struct attribute_group workload_req_attribute_group = {
+ .attrs = workload_req_attrs,
+ .name = "workload_request"
+};
+
+static bool workload_req_created;
+
+int proc_thermal_mbox_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ u64 cmd_resp;
+ int ret;
+
+ /* Check if there is a mailbox support, if fails return success */
+ ret = send_mbox_read_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_READ, &cmd_resp);
+ if (ret)
+ return 0;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &workload_req_attribute_group);
+ if (ret)
+ return ret;
+
+ workload_req_created = true;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_mbox_add);
+
+void proc_thermal_mbox_remove(struct pci_dev *pdev)
+{
+ if (workload_req_created)
+ sysfs_remove_group(&pdev->dev.kobj, &workload_req_attribute_group);
+
+ workload_req_created = false;
+
+}
+EXPORT_SYMBOL_GPL(proc_thermal_mbox_remove);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c
new file mode 100644
index 000000000000..a205221ec8df
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device RFIM control
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+static struct rapl_if_priv rapl_mmio_priv;
+
+static const struct rapl_mmio_regs rapl_mmio_default = {
+ .reg_unit = 0x5938,
+ .regs[RAPL_DOMAIN_PACKAGE] = { 0x59a0, 0x593c, 0x58f0, 0, 0x5930},
+ .regs[RAPL_DOMAIN_DRAM] = { 0x58e0, 0x58e8, 0x58ec, 0, 0},
+ .limits[RAPL_DOMAIN_PACKAGE] = 2,
+ .limits[RAPL_DOMAIN_DRAM] = 2,
+};
+
+static int rapl_mmio_cpu_online(unsigned int cpu)
+{
+ struct rapl_package *rp;
+
+ /* mmio rapl supports package 0 only for now */
+ if (topology_physical_package_id(cpu))
+ return 0;
+
+ rp = rapl_find_package_domain(cpu, &rapl_mmio_priv);
+ if (!rp) {
+ rp = rapl_add_package(cpu, &rapl_mmio_priv);
+ if (IS_ERR(rp))
+ return PTR_ERR(rp);
+ }
+ cpumask_set_cpu(cpu, &rp->cpumask);
+ return 0;
+}
+
+static int rapl_mmio_cpu_down_prep(unsigned int cpu)
+{
+ struct rapl_package *rp;
+ int lead_cpu;
+
+ rp = rapl_find_package_domain(cpu, &rapl_mmio_priv);
+ if (!rp)
+ return 0;
+
+ cpumask_clear_cpu(cpu, &rp->cpumask);
+ lead_cpu = cpumask_first(&rp->cpumask);
+ if (lead_cpu >= nr_cpu_ids)
+ rapl_remove_package(rp);
+ else if (rp->lead_cpu == cpu)
+ rp->lead_cpu = lead_cpu;
+ return 0;
+}
+
+static int rapl_mmio_read_raw(int cpu, struct reg_action *ra)
+{
+ if (!ra->reg)
+ return -EINVAL;
+
+ ra->value = readq((void __iomem *)ra->reg);
+ ra->value &= ra->mask;
+ return 0;
+}
+
+static int rapl_mmio_write_raw(int cpu, struct reg_action *ra)
+{
+ u64 val;
+
+ if (!ra->reg)
+ return -EINVAL;
+
+ val = readq((void __iomem *)ra->reg);
+ val &= ~ra->mask;
+ val |= ra->value;
+ writeq(val, (void __iomem *)ra->reg);
+ return 0;
+}
+
+int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ const struct rapl_mmio_regs *rapl_regs = &rapl_mmio_default;
+ enum rapl_domain_reg_id reg;
+ enum rapl_domain_type domain;
+ int ret;
+
+ if (!rapl_regs)
+ return 0;
+
+ for (domain = RAPL_DOMAIN_PACKAGE; domain < RAPL_DOMAIN_MAX; domain++) {
+ for (reg = RAPL_DOMAIN_REG_LIMIT; reg < RAPL_DOMAIN_REG_MAX; reg++)
+ if (rapl_regs->regs[domain][reg])
+ rapl_mmio_priv.regs[domain][reg] =
+ (u64)proc_priv->mmio_base +
+ rapl_regs->regs[domain][reg];
+ rapl_mmio_priv.limits[domain] = rapl_regs->limits[domain];
+ }
+ rapl_mmio_priv.reg_unit = (u64)proc_priv->mmio_base + rapl_regs->reg_unit;
+
+ rapl_mmio_priv.read_raw = rapl_mmio_read_raw;
+ rapl_mmio_priv.write_raw = rapl_mmio_write_raw;
+
+ rapl_mmio_priv.control_type = powercap_register_control_type(NULL, "intel-rapl-mmio", NULL);
+ if (IS_ERR(rapl_mmio_priv.control_type)) {
+ pr_debug("failed to register powercap control_type.\n");
+ return PTR_ERR(rapl_mmio_priv.control_type);
+ }
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "powercap/rapl:online",
+ rapl_mmio_cpu_online, rapl_mmio_cpu_down_prep);
+ if (ret < 0) {
+ powercap_unregister_control_type(rapl_mmio_priv.control_type);
+ rapl_mmio_priv.control_type = NULL;
+ return ret;
+ }
+ rapl_mmio_priv.pcap_rapl_online = ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rapl_add);
+
+void proc_thermal_rapl_remove(void)
+{
+ if (IS_ERR_OR_NULL(rapl_mmio_priv.control_type))
+ return;
+
+ cpuhp_remove_state(rapl_mmio_priv.pcap_rapl_online);
+ powercap_unregister_control_type(rapl_mmio_priv.control_type);
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rapl_remove);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c
new file mode 100644
index 000000000000..8c42e7662033
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device RFIM control
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+MODULE_IMPORT_NS(INT340X_THERMAL);
+
+struct mmio_reg {
+ int read_only;
+ u32 offset;
+ int bits;
+ u16 mask;
+ u16 shift;
+};
+
+/* These will represent sysfs attribute names */
+static const char * const fivr_strings[] = {
+ "vco_ref_code_lo",
+ "vco_ref_code_hi",
+ "spread_spectrum_pct",
+ "spread_spectrum_clk_enable",
+ "rfi_vco_ref_code",
+ "fivr_fffc_rev",
+ NULL
+};
+
+static const struct mmio_reg tgl_fivr_mmio_regs[] = {
+ { 0, 0x5A18, 3, 0x7, 11}, /* vco_ref_code_lo */
+ { 0, 0x5A18, 8, 0xFF, 16}, /* vco_ref_code_hi */
+ { 0, 0x5A08, 8, 0xFF, 0}, /* spread_spectrum_pct */
+ { 0, 0x5A08, 1, 0x1, 8}, /* spread_spectrum_clk_enable */
+ { 1, 0x5A10, 12, 0xFFF, 0}, /* rfi_vco_ref_code */
+ { 1, 0x5A14, 2, 0x3, 1}, /* fivr_fffc_rev */
+};
+
+/* These will represent sysfs attribute names */
+static const char * const dvfs_strings[] = {
+ "rfi_restriction_run_busy",
+ "rfi_restriction_err_code",
+ "rfi_restriction_data_rate",
+ "rfi_restriction_data_rate_base",
+ "ddr_data_rate_point_0",
+ "ddr_data_rate_point_1",
+ "ddr_data_rate_point_2",
+ "ddr_data_rate_point_3",
+ "rfi_disable",
+ NULL
+};
+
+static const struct mmio_reg adl_dvfs_mmio_regs[] = {
+ { 0, 0x5A38, 1, 0x1, 31}, /* rfi_restriction_run_busy */
+ { 0, 0x5A38, 7, 0x7F, 24}, /* rfi_restriction_err_code */
+ { 0, 0x5A38, 8, 0xFF, 16}, /* rfi_restriction_data_rate */
+ { 0, 0x5A38, 16, 0xFFFF, 0}, /* rfi_restriction_data_rate_base */
+ { 0, 0x5A30, 10, 0x3FF, 0}, /* ddr_data_rate_point_0 */
+ { 0, 0x5A30, 10, 0x3FF, 10}, /* ddr_data_rate_point_1 */
+ { 0, 0x5A30, 10, 0x3FF, 20}, /* ddr_data_rate_point_2 */
+ { 0, 0x5A30, 10, 0x3FF, 30}, /* ddr_data_rate_point_3 */
+ { 0, 0x5A40, 1, 0x1, 0}, /* rfi_disable */
+};
+
+#define RFIM_SHOW(suffix, table)\
+static ssize_t suffix##_show(struct device *dev,\
+ struct device_attribute *attr,\
+ char *buf)\
+{\
+ struct proc_thermal_device *proc_priv;\
+ struct pci_dev *pdev = to_pci_dev(dev);\
+ const struct mmio_reg *mmio_regs;\
+ const char **match_strs;\
+ u32 reg_val;\
+ int ret;\
+\
+ proc_priv = pci_get_drvdata(pdev);\
+ if (table) {\
+ match_strs = (const char **)dvfs_strings;\
+ mmio_regs = adl_dvfs_mmio_regs;\
+ } else { \
+ match_strs = (const char **)fivr_strings;\
+ mmio_regs = tgl_fivr_mmio_regs;\
+ } \
+ \
+ ret = match_string(match_strs, -1, attr->attr.name);\
+ if (ret < 0)\
+ return ret;\
+ reg_val = readl((void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
+ ret = (reg_val >> mmio_regs[ret].shift) & mmio_regs[ret].mask;\
+ return sprintf(buf, "%u\n", ret);\
+}
+
+#define RFIM_STORE(suffix, table)\
+static ssize_t suffix##_store(struct device *dev,\
+ struct device_attribute *attr,\
+ const char *buf, size_t count)\
+{\
+ struct proc_thermal_device *proc_priv;\
+ struct pci_dev *pdev = to_pci_dev(dev);\
+ unsigned int input;\
+ const char **match_strs;\
+ const struct mmio_reg *mmio_regs;\
+ int ret, err;\
+ u32 reg_val;\
+ u32 mask;\
+\
+ proc_priv = pci_get_drvdata(pdev);\
+ if (table) {\
+ match_strs = (const char **)dvfs_strings;\
+ mmio_regs = adl_dvfs_mmio_regs;\
+ } else { \
+ match_strs = (const char **)fivr_strings;\
+ mmio_regs = tgl_fivr_mmio_regs;\
+ } \
+ \
+ ret = match_string(match_strs, -1, attr->attr.name);\
+ if (ret < 0)\
+ return ret;\
+ if (mmio_regs[ret].read_only)\
+ return -EPERM;\
+ err = kstrtouint(buf, 10, &input);\
+ if (err)\
+ return err;\
+ mask = GENMASK(mmio_regs[ret].shift + mmio_regs[ret].bits - 1, mmio_regs[ret].shift);\
+ reg_val = readl((void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
+ reg_val &= ~mask;\
+ reg_val |= (input << mmio_regs[ret].shift);\
+ writel(reg_val, (void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
+ return count;\
+}
+
+RFIM_SHOW(vco_ref_code_lo, 0)
+RFIM_SHOW(vco_ref_code_hi, 0)
+RFIM_SHOW(spread_spectrum_pct, 0)
+RFIM_SHOW(spread_spectrum_clk_enable, 0)
+RFIM_SHOW(rfi_vco_ref_code, 0)
+RFIM_SHOW(fivr_fffc_rev, 0)
+
+RFIM_STORE(vco_ref_code_lo, 0)
+RFIM_STORE(vco_ref_code_hi, 0)
+RFIM_STORE(spread_spectrum_pct, 0)
+RFIM_STORE(spread_spectrum_clk_enable, 0)
+RFIM_STORE(rfi_vco_ref_code, 0)
+RFIM_STORE(fivr_fffc_rev, 0)
+
+static DEVICE_ATTR_RW(vco_ref_code_lo);
+static DEVICE_ATTR_RW(vco_ref_code_hi);
+static DEVICE_ATTR_RW(spread_spectrum_pct);
+static DEVICE_ATTR_RW(spread_spectrum_clk_enable);
+static DEVICE_ATTR_RW(rfi_vco_ref_code);
+static DEVICE_ATTR_RW(fivr_fffc_rev);
+
+static struct attribute *fivr_attrs[] = {
+ &dev_attr_vco_ref_code_lo.attr,
+ &dev_attr_vco_ref_code_hi.attr,
+ &dev_attr_spread_spectrum_pct.attr,
+ &dev_attr_spread_spectrum_clk_enable.attr,
+ &dev_attr_rfi_vco_ref_code.attr,
+ &dev_attr_fivr_fffc_rev.attr,
+ NULL
+};
+
+static const struct attribute_group fivr_attribute_group = {
+ .attrs = fivr_attrs,
+ .name = "fivr"
+};
+
+RFIM_SHOW(rfi_restriction_run_busy, 1)
+RFIM_SHOW(rfi_restriction_err_code, 1)
+RFIM_SHOW(rfi_restriction_data_rate, 1)
+RFIM_SHOW(ddr_data_rate_point_0, 1)
+RFIM_SHOW(ddr_data_rate_point_1, 1)
+RFIM_SHOW(ddr_data_rate_point_2, 1)
+RFIM_SHOW(ddr_data_rate_point_3, 1)
+RFIM_SHOW(rfi_disable, 1)
+
+RFIM_STORE(rfi_restriction_run_busy, 1)
+RFIM_STORE(rfi_restriction_err_code, 1)
+RFIM_STORE(rfi_restriction_data_rate, 1)
+RFIM_STORE(rfi_disable, 1)
+
+static DEVICE_ATTR_RW(rfi_restriction_run_busy);
+static DEVICE_ATTR_RW(rfi_restriction_err_code);
+static DEVICE_ATTR_RW(rfi_restriction_data_rate);
+static DEVICE_ATTR_RO(ddr_data_rate_point_0);
+static DEVICE_ATTR_RO(ddr_data_rate_point_1);
+static DEVICE_ATTR_RO(ddr_data_rate_point_2);
+static DEVICE_ATTR_RO(ddr_data_rate_point_3);
+static DEVICE_ATTR_RW(rfi_disable);
+
+static ssize_t rfi_restriction_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u16 id = 0x0008;
+ u32 input;
+ int ret;
+
+ ret = kstrtou32(buf, 10, &input);
+ if (ret)
+ return ret;
+
+ ret = processor_thermal_send_mbox_write_cmd(to_pci_dev(dev), id, input);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t rfi_restriction_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u16 id = 0x0007;
+ u64 resp;
+ int ret;
+
+ ret = processor_thermal_send_mbox_read_cmd(to_pci_dev(dev), id, &resp);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%llu\n", resp);
+}
+
+static ssize_t ddr_data_rate_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u16 id = 0x0107;
+ u64 resp;
+ int ret;
+
+ ret = processor_thermal_send_mbox_read_cmd(to_pci_dev(dev), id, &resp);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%llu\n", resp);
+}
+
+static DEVICE_ATTR_RW(rfi_restriction);
+static DEVICE_ATTR_RO(ddr_data_rate);
+
+static struct attribute *dvfs_attrs[] = {
+ &dev_attr_rfi_restriction_run_busy.attr,
+ &dev_attr_rfi_restriction_err_code.attr,
+ &dev_attr_rfi_restriction_data_rate.attr,
+ &dev_attr_ddr_data_rate_point_0.attr,
+ &dev_attr_ddr_data_rate_point_1.attr,
+ &dev_attr_ddr_data_rate_point_2.attr,
+ &dev_attr_ddr_data_rate_point_3.attr,
+ &dev_attr_rfi_disable.attr,
+ &dev_attr_ddr_data_rate.attr,
+ &dev_attr_rfi_restriction.attr,
+ NULL
+};
+
+static const struct attribute_group dvfs_attribute_group = {
+ .attrs = dvfs_attrs,
+ .name = "dvfs"
+};
+
+int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ int ret;
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) {
+ ret = sysfs_create_group(&pdev->dev.kobj, &fivr_attribute_group);
+ if (ret)
+ return ret;
+ }
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS) {
+ ret = sysfs_create_group(&pdev->dev.kobj, &dvfs_attribute_group);
+ if (ret && proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) {
+ sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rfim_add);
+
+void proc_thermal_rfim_remove(struct pci_dev *pdev)
+{
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR)
+ sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS)
+ sysfs_remove_group(&pdev->dev.kobj, &dvfs_attribute_group);
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rfim_remove);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/intel_hfi.c b/drivers/thermal/intel/intel_hfi.c
new file mode 100644
index 000000000000..a0640f762dc5
--- /dev/null
+++ b/drivers/thermal/intel/intel_hfi.c
@@ -0,0 +1,567 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hardware Feedback Interface Driver
+ *
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Aubrey Li <aubrey.li@linux.intel.com>
+ * Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
+ *
+ *
+ * The Hardware Feedback Interface provides a performance and energy efficiency
+ * capability information for each CPU in the system. Depending on the processor
+ * model, hardware may periodically update these capabilities as a result of
+ * changes in the operating conditions (e.g., power limits or thermal
+ * constraints). On other processor models, there is a single HFI update
+ * at boot.
+ *
+ * This file provides functionality to process HFI updates and relay these
+ * updates to userspace.
+ */
+
+#define pr_fmt(fmt) "intel-hfi: " fmt
+
+#include <linux/bitops.h>
+#include <linux/cpufeature.h>
+#include <linux/cpumask.h>
+#include <linux/gfp.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/math.h>
+#include <linux/mutex.h>
+#include <linux/percpu-defs.h>
+#include <linux/printk.h>
+#include <linux/processor.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/topology.h>
+#include <linux/workqueue.h>
+
+#include <asm/msr.h>
+
+#include "../thermal_core.h"
+#include "intel_hfi.h"
+
+#define THERM_STATUS_CLEAR_PKG_MASK (BIT(1) | BIT(3) | BIT(5) | BIT(7) | \
+ BIT(9) | BIT(11) | BIT(26))
+
+/* Hardware Feedback Interface MSR configuration bits */
+#define HW_FEEDBACK_PTR_VALID_BIT BIT(0)
+#define HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT BIT(0)
+
+/* CPUID detection and enumeration definitions for HFI */
+
+#define CPUID_HFI_LEAF 6
+
+union hfi_capabilities {
+ struct {
+ u8 performance:1;
+ u8 energy_efficiency:1;
+ u8 __reserved:6;
+ } split;
+ u8 bits;
+};
+
+union cpuid6_edx {
+ struct {
+ union hfi_capabilities capabilities;
+ u32 table_pages:4;
+ u32 __reserved:4;
+ s32 index:16;
+ } split;
+ u32 full;
+};
+
+/**
+ * struct hfi_cpu_data - HFI capabilities per CPU
+ * @perf_cap: Performance capability
+ * @ee_cap: Energy efficiency capability
+ *
+ * Capabilities of a logical processor in the HFI table. These capabilities are
+ * unitless.
+ */
+struct hfi_cpu_data {
+ u8 perf_cap;
+ u8 ee_cap;
+} __packed;
+
+/**
+ * struct hfi_hdr - Header of the HFI table
+ * @perf_updated: Hardware updated performance capabilities
+ * @ee_updated: Hardware updated energy efficiency capabilities
+ *
+ * Properties of the data in an HFI table.
+ */
+struct hfi_hdr {
+ u8 perf_updated;
+ u8 ee_updated;
+} __packed;
+
+/**
+ * struct hfi_instance - Representation of an HFI instance (i.e., a table)
+ * @local_table: Base of the local copy of the HFI table
+ * @timestamp: Timestamp of the last update of the local table.
+ * Located at the base of the local table.
+ * @hdr: Base address of the header of the local table
+ * @data: Base address of the data of the local table
+ * @cpus: CPUs represented in this HFI table instance
+ * @hw_table: Pointer to the HFI table of this instance
+ * @update_work: Delayed work to process HFI updates
+ * @table_lock: Lock to protect acceses to the table of this instance
+ * @event_lock: Lock to process HFI interrupts
+ *
+ * A set of parameters to parse and navigate a specific HFI table.
+ */
+struct hfi_instance {
+ union {
+ void *local_table;
+ u64 *timestamp;
+ };
+ void *hdr;
+ void *data;
+ cpumask_var_t cpus;
+ void *hw_table;
+ struct delayed_work update_work;
+ raw_spinlock_t table_lock;
+ raw_spinlock_t event_lock;
+};
+
+/**
+ * struct hfi_features - Supported HFI features
+ * @nr_table_pages: Size of the HFI table in 4KB pages
+ * @cpu_stride: Stride size to locate the capability data of a logical
+ * processor within the table (i.e., row stride)
+ * @hdr_size: Size of the table header
+ *
+ * Parameters and supported features that are common to all HFI instances
+ */
+struct hfi_features {
+ unsigned int nr_table_pages;
+ unsigned int cpu_stride;
+ unsigned int hdr_size;
+};
+
+/**
+ * struct hfi_cpu_info - Per-CPU attributes to consume HFI data
+ * @index: Row of this CPU in its HFI table
+ * @hfi_instance: Attributes of the HFI table to which this CPU belongs
+ *
+ * Parameters to link a logical processor to an HFI table and a row within it.
+ */
+struct hfi_cpu_info {
+ s16 index;
+ struct hfi_instance *hfi_instance;
+};
+
+static DEFINE_PER_CPU(struct hfi_cpu_info, hfi_cpu_info) = { .index = -1 };
+
+static int max_hfi_instances;
+static struct hfi_instance *hfi_instances;
+
+static struct hfi_features hfi_features;
+static DEFINE_MUTEX(hfi_instance_lock);
+
+static struct workqueue_struct *hfi_updates_wq;
+#define HFI_UPDATE_INTERVAL HZ
+#define HFI_MAX_THERM_NOTIFY_COUNT 16
+
+static void get_hfi_caps(struct hfi_instance *hfi_instance,
+ struct thermal_genl_cpu_caps *cpu_caps)
+{
+ int cpu, i = 0;
+
+ raw_spin_lock_irq(&hfi_instance->table_lock);
+ for_each_cpu(cpu, hfi_instance->cpus) {
+ struct hfi_cpu_data *caps;
+ s16 index;
+
+ index = per_cpu(hfi_cpu_info, cpu).index;
+ caps = hfi_instance->data + index * hfi_features.cpu_stride;
+ cpu_caps[i].cpu = cpu;
+
+ /*
+ * Scale performance and energy efficiency to
+ * the [0, 1023] interval that thermal netlink uses.
+ */
+ cpu_caps[i].performance = caps->perf_cap << 2;
+ cpu_caps[i].efficiency = caps->ee_cap << 2;
+
+ ++i;
+ }
+ raw_spin_unlock_irq(&hfi_instance->table_lock);
+}
+
+/*
+ * Call update_capabilities() when there are changes in the HFI table.
+ */
+static void update_capabilities(struct hfi_instance *hfi_instance)
+{
+ struct thermal_genl_cpu_caps *cpu_caps;
+ int i = 0, cpu_count;
+
+ /* CPUs may come online/offline while processing an HFI update. */
+ mutex_lock(&hfi_instance_lock);
+
+ cpu_count = cpumask_weight(hfi_instance->cpus);
+
+ /* No CPUs to report in this hfi_instance. */
+ if (!cpu_count)
+ goto out;
+
+ cpu_caps = kcalloc(cpu_count, sizeof(*cpu_caps), GFP_KERNEL);
+ if (!cpu_caps)
+ goto out;
+
+ get_hfi_caps(hfi_instance, cpu_caps);
+
+ if (cpu_count < HFI_MAX_THERM_NOTIFY_COUNT)
+ goto last_cmd;
+
+ /* Process complete chunks of HFI_MAX_THERM_NOTIFY_COUNT capabilities. */
+ for (i = 0;
+ (i + HFI_MAX_THERM_NOTIFY_COUNT) <= cpu_count;
+ i += HFI_MAX_THERM_NOTIFY_COUNT)
+ thermal_genl_cpu_capability_event(HFI_MAX_THERM_NOTIFY_COUNT,
+ &cpu_caps[i]);
+
+ cpu_count = cpu_count - i;
+
+last_cmd:
+ /* Process the remaining capabilities if any. */
+ if (cpu_count)
+ thermal_genl_cpu_capability_event(cpu_count, &cpu_caps[i]);
+
+ kfree(cpu_caps);
+out:
+ mutex_unlock(&hfi_instance_lock);
+}
+
+static void hfi_update_work_fn(struct work_struct *work)
+{
+ struct hfi_instance *hfi_instance;
+
+ hfi_instance = container_of(to_delayed_work(work), struct hfi_instance,
+ update_work);
+
+ update_capabilities(hfi_instance);
+}
+
+void intel_hfi_process_event(__u64 pkg_therm_status_msr_val)
+{
+ struct hfi_instance *hfi_instance;
+ int cpu = smp_processor_id();
+ struct hfi_cpu_info *info;
+ u64 new_timestamp;
+
+ if (!pkg_therm_status_msr_val)
+ return;
+
+ info = &per_cpu(hfi_cpu_info, cpu);
+ if (!info)
+ return;
+
+ /*
+ * A CPU is linked to its HFI instance before the thermal vector in the
+ * local APIC is unmasked. Hence, info->hfi_instance cannot be NULL
+ * when receiving an HFI event.
+ */
+ hfi_instance = info->hfi_instance;
+ if (unlikely(!hfi_instance)) {
+ pr_debug("Received event on CPU %d but instance was null", cpu);
+ return;
+ }
+
+ /*
+ * On most systems, all CPUs in the package receive a package-level
+ * thermal interrupt when there is an HFI update. It is sufficient to
+ * let a single CPU to acknowledge the update and queue work to
+ * process it. The remaining CPUs can resume their work.
+ */
+ if (!raw_spin_trylock(&hfi_instance->event_lock))
+ return;
+
+ /* Skip duplicated updates. */
+ new_timestamp = *(u64 *)hfi_instance->hw_table;
+ if (*hfi_instance->timestamp == new_timestamp) {
+ raw_spin_unlock(&hfi_instance->event_lock);
+ return;
+ }
+
+ raw_spin_lock(&hfi_instance->table_lock);
+
+ /*
+ * Copy the updated table into our local copy. This includes the new
+ * timestamp.
+ */
+ memcpy(hfi_instance->local_table, hfi_instance->hw_table,
+ hfi_features.nr_table_pages << PAGE_SHIFT);
+
+ raw_spin_unlock(&hfi_instance->table_lock);
+ raw_spin_unlock(&hfi_instance->event_lock);
+
+ /*
+ * Let hardware know that we are done reading the HFI table and it is
+ * free to update it again.
+ */
+ pkg_therm_status_msr_val &= THERM_STATUS_CLEAR_PKG_MASK &
+ ~PACKAGE_THERM_STATUS_HFI_UPDATED;
+ wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, pkg_therm_status_msr_val);
+
+ queue_delayed_work(hfi_updates_wq, &hfi_instance->update_work,
+ HFI_UPDATE_INTERVAL);
+}
+
+static void init_hfi_cpu_index(struct hfi_cpu_info *info)
+{
+ union cpuid6_edx edx;
+
+ /* Do not re-read @cpu's index if it has already been initialized. */
+ if (info->index > -1)
+ return;
+
+ edx.full = cpuid_edx(CPUID_HFI_LEAF);
+ info->index = edx.split.index;
+}
+
+/*
+ * The format of the HFI table depends on the number of capabilities that the
+ * hardware supports. Keep a data structure to navigate the table.
+ */
+static void init_hfi_instance(struct hfi_instance *hfi_instance)
+{
+ /* The HFI header is below the time-stamp. */
+ hfi_instance->hdr = hfi_instance->local_table +
+ sizeof(*hfi_instance->timestamp);
+
+ /* The HFI data starts below the header. */
+ hfi_instance->data = hfi_instance->hdr + hfi_features.hdr_size;
+}
+
+/**
+ * intel_hfi_online() - Enable HFI on @cpu
+ * @cpu: CPU in which the HFI will be enabled
+ *
+ * Enable the HFI to be used in @cpu. The HFI is enabled at the die/package
+ * level. The first CPU in the die/package to come online does the full HFI
+ * initialization. Subsequent CPUs will just link themselves to the HFI
+ * instance of their die/package.
+ *
+ * This function is called before enabling the thermal vector in the local APIC
+ * in order to ensure that @cpu has an associated HFI instance when it receives
+ * an HFI event.
+ */
+void intel_hfi_online(unsigned int cpu)
+{
+ struct hfi_instance *hfi_instance;
+ struct hfi_cpu_info *info;
+ phys_addr_t hw_table_pa;
+ u64 msr_val;
+ u16 die_id;
+
+ /* Nothing to do if hfi_instances are missing. */
+ if (!hfi_instances)
+ return;
+
+ /*
+ * Link @cpu to the HFI instance of its package/die. It does not
+ * matter whether the instance has been initialized.
+ */
+ info = &per_cpu(hfi_cpu_info, cpu);
+ die_id = topology_logical_die_id(cpu);
+ hfi_instance = info->hfi_instance;
+ if (!hfi_instance) {
+ if (die_id < 0 || die_id >= max_hfi_instances)
+ return;
+
+ hfi_instance = &hfi_instances[die_id];
+ info->hfi_instance = hfi_instance;
+ }
+
+ init_hfi_cpu_index(info);
+
+ /*
+ * Now check if the HFI instance of the package/die of @cpu has been
+ * initialized (by checking its header). In such case, all we have to
+ * do is to add @cpu to this instance's cpumask.
+ */
+ mutex_lock(&hfi_instance_lock);
+ if (hfi_instance->hdr) {
+ cpumask_set_cpu(cpu, hfi_instance->cpus);
+ goto unlock;
+ }
+
+ /*
+ * Hardware is programmed with the physical address of the first page
+ * frame of the table. Hence, the allocated memory must be page-aligned.
+ */
+ hfi_instance->hw_table = alloc_pages_exact(hfi_features.nr_table_pages,
+ GFP_KERNEL | __GFP_ZERO);
+ if (!hfi_instance->hw_table)
+ goto unlock;
+
+ hw_table_pa = virt_to_phys(hfi_instance->hw_table);
+
+ /*
+ * Allocate memory to keep a local copy of the table that
+ * hardware generates.
+ */
+ hfi_instance->local_table = kzalloc(hfi_features.nr_table_pages << PAGE_SHIFT,
+ GFP_KERNEL);
+ if (!hfi_instance->local_table)
+ goto free_hw_table;
+
+ /*
+ * Program the address of the feedback table of this die/package. On
+ * some processors, hardware remembers the old address of the HFI table
+ * even after having been reprogrammed and re-enabled. Thus, do not free
+ * the pages allocated for the table or reprogram the hardware with a
+ * new base address. Namely, program the hardware only once.
+ */
+ msr_val = hw_table_pa | HW_FEEDBACK_PTR_VALID_BIT;
+ wrmsrl(MSR_IA32_HW_FEEDBACK_PTR, msr_val);
+
+ init_hfi_instance(hfi_instance);
+
+ INIT_DELAYED_WORK(&hfi_instance->update_work, hfi_update_work_fn);
+ raw_spin_lock_init(&hfi_instance->table_lock);
+ raw_spin_lock_init(&hfi_instance->event_lock);
+
+ cpumask_set_cpu(cpu, hfi_instance->cpus);
+
+ /*
+ * Enable the hardware feedback interface and never disable it. See
+ * comment on programming the address of the table.
+ */
+ rdmsrl(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
+ msr_val |= HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT;
+ wrmsrl(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
+
+unlock:
+ mutex_unlock(&hfi_instance_lock);
+ return;
+
+free_hw_table:
+ free_pages_exact(hfi_instance->hw_table, hfi_features.nr_table_pages);
+ goto unlock;
+}
+
+/**
+ * intel_hfi_offline() - Disable HFI on @cpu
+ * @cpu: CPU in which the HFI will be disabled
+ *
+ * Remove @cpu from those covered by its HFI instance.
+ *
+ * On some processors, hardware remembers previous programming settings even
+ * after being reprogrammed. Thus, keep HFI enabled even if all CPUs in the
+ * die/package of @cpu are offline. See note in intel_hfi_online().
+ */
+void intel_hfi_offline(unsigned int cpu)
+{
+ struct hfi_cpu_info *info = &per_cpu(hfi_cpu_info, cpu);
+ struct hfi_instance *hfi_instance;
+
+ /*
+ * Check if @cpu as an associated, initialized (i.e., with a non-NULL
+ * header). Also, HFI instances are only initialized if X86_FEATURE_HFI
+ * is present.
+ */
+ hfi_instance = info->hfi_instance;
+ if (!hfi_instance)
+ return;
+
+ if (!hfi_instance->hdr)
+ return;
+
+ mutex_lock(&hfi_instance_lock);
+ cpumask_clear_cpu(cpu, hfi_instance->cpus);
+ mutex_unlock(&hfi_instance_lock);
+}
+
+static __init int hfi_parse_features(void)
+{
+ unsigned int nr_capabilities;
+ union cpuid6_edx edx;
+
+ if (!boot_cpu_has(X86_FEATURE_HFI))
+ return -ENODEV;
+
+ /*
+ * If we are here we know that CPUID_HFI_LEAF exists. Parse the
+ * supported capabilities and the size of the HFI table.
+ */
+ edx.full = cpuid_edx(CPUID_HFI_LEAF);
+
+ if (!edx.split.capabilities.split.performance) {
+ pr_debug("Performance reporting not supported! Not using HFI\n");
+ return -ENODEV;
+ }
+
+ /*
+ * The number of supported capabilities determines the number of
+ * columns in the HFI table. Exclude the reserved bits.
+ */
+ edx.split.capabilities.split.__reserved = 0;
+ nr_capabilities = hweight8(edx.split.capabilities.bits);
+
+ /* The number of 4KB pages required by the table */
+ hfi_features.nr_table_pages = edx.split.table_pages + 1;
+
+ /*
+ * The header contains change indications for each supported feature.
+ * The size of the table header is rounded up to be a multiple of 8
+ * bytes.
+ */
+ hfi_features.hdr_size = DIV_ROUND_UP(nr_capabilities, 8) * 8;
+
+ /*
+ * Data of each logical processor is also rounded up to be a multiple
+ * of 8 bytes.
+ */
+ hfi_features.cpu_stride = DIV_ROUND_UP(nr_capabilities, 8) * 8;
+
+ return 0;
+}
+
+void __init intel_hfi_init(void)
+{
+ struct hfi_instance *hfi_instance;
+ int i, j;
+
+ if (hfi_parse_features())
+ return;
+
+ /* There is one HFI instance per die/package. */
+ max_hfi_instances = topology_max_packages() *
+ topology_max_die_per_package();
+
+ /*
+ * This allocation may fail. CPU hotplug callbacks must check
+ * for a null pointer.
+ */
+ hfi_instances = kcalloc(max_hfi_instances, sizeof(*hfi_instances),
+ GFP_KERNEL);
+ if (!hfi_instances)
+ return;
+
+ for (i = 0; i < max_hfi_instances; i++) {
+ hfi_instance = &hfi_instances[i];
+ if (!zalloc_cpumask_var(&hfi_instance->cpus, GFP_KERNEL))
+ goto err_nomem;
+ }
+
+ hfi_updates_wq = create_singlethread_workqueue("hfi-updates");
+ if (!hfi_updates_wq)
+ goto err_nomem;
+
+ return;
+
+err_nomem:
+ for (j = 0; j < i; ++j) {
+ hfi_instance = &hfi_instances[j];
+ free_cpumask_var(hfi_instance->cpus);
+ }
+
+ kfree(hfi_instances);
+ hfi_instances = NULL;
+}
diff --git a/drivers/thermal/intel/intel_hfi.h b/drivers/thermal/intel/intel_hfi.h
new file mode 100644
index 000000000000..325aa78b745c
--- /dev/null
+++ b/drivers/thermal/intel/intel_hfi.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INTEL_HFI_H
+#define _INTEL_HFI_H
+
+#if defined(CONFIG_INTEL_HFI_THERMAL)
+void __init intel_hfi_init(void);
+void intel_hfi_online(unsigned int cpu);
+void intel_hfi_offline(unsigned int cpu);
+void intel_hfi_process_event(__u64 pkg_therm_status_msr_val);
+#else
+static inline void intel_hfi_init(void) { }
+static inline void intel_hfi_online(unsigned int cpu) { }
+static inline void intel_hfi_offline(unsigned int cpu) { }
+static inline void intel_hfi_process_event(__u64 pkg_therm_status_msr_val) { }
+#endif /* CONFIG_INTEL_HFI_THERMAL */
+
+#endif /* _INTEL_HFI_H */
diff --git a/drivers/thermal/intel/intel_menlow.c b/drivers/thermal/intel/intel_menlow.c
new file mode 100644
index 000000000000..101d7e791a13
--- /dev/null
+++ b/drivers/thermal/intel/intel_menlow.c
@@ -0,0 +1,523 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel menlow Driver for thermal management extension
+ *
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ *
+ * This driver creates the sys I/F for programming the sensors.
+ * It also implements the driver for intel menlow memory controller (hardware
+ * id is INT0002) which makes use of the platform specific ACPI methods
+ * to get/set bandwidth.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+MODULE_AUTHOR("Thomas Sujith");
+MODULE_AUTHOR("Zhang Rui");
+MODULE_DESCRIPTION("Intel Menlow platform specific driver");
+MODULE_LICENSE("GPL v2");
+
+/*
+ * Memory controller device control
+ */
+
+#define MEMORY_GET_BANDWIDTH "GTHS"
+#define MEMORY_SET_BANDWIDTH "STHS"
+#define MEMORY_ARG_CUR_BANDWIDTH 1
+#define MEMORY_ARG_MAX_BANDWIDTH 0
+
+static void intel_menlow_unregister_sensor(void);
+
+/*
+ * GTHS returning 'n' would mean that [0,n-1] states are supported
+ * In that case max_cstate would be n-1
+ * GTHS returning '0' would mean that no bandwidth control states are supported
+ */
+static int memory_get_max_bandwidth(struct thermal_cooling_device *cdev,
+ unsigned long *max_state)
+{
+ struct acpi_device *device = cdev->devdata;
+ acpi_handle handle = device->handle;
+ unsigned long long value;
+ struct acpi_object_list arg_list;
+ union acpi_object arg;
+ acpi_status status = AE_OK;
+
+ arg_list.count = 1;
+ arg_list.pointer = &arg;
+ arg.type = ACPI_TYPE_INTEGER;
+ arg.integer.value = MEMORY_ARG_MAX_BANDWIDTH;
+ status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH,
+ &arg_list, &value);
+ if (ACPI_FAILURE(status))
+ return -EFAULT;
+
+ if (!value)
+ return -EINVAL;
+
+ *max_state = value - 1;
+ return 0;
+}
+
+static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev,
+ unsigned long *value)
+{
+ struct acpi_device *device = cdev->devdata;
+ acpi_handle handle = device->handle;
+ unsigned long long result;
+ struct acpi_object_list arg_list;
+ union acpi_object arg;
+ acpi_status status = AE_OK;
+
+ arg_list.count = 1;
+ arg_list.pointer = &arg;
+ arg.type = ACPI_TYPE_INTEGER;
+ arg.integer.value = MEMORY_ARG_CUR_BANDWIDTH;
+ status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH,
+ &arg_list, &result);
+ if (ACPI_FAILURE(status))
+ return -EFAULT;
+
+ *value = result;
+ return 0;
+}
+
+static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct acpi_device *device = cdev->devdata;
+ acpi_handle handle = device->handle;
+ struct acpi_object_list arg_list;
+ union acpi_object arg;
+ acpi_status status;
+ unsigned long long temp;
+ unsigned long max_state;
+
+ if (memory_get_max_bandwidth(cdev, &max_state))
+ return -EFAULT;
+
+ if (state > max_state)
+ return -EINVAL;
+
+ arg_list.count = 1;
+ arg_list.pointer = &arg;
+ arg.type = ACPI_TYPE_INTEGER;
+ arg.integer.value = state;
+
+ status =
+ acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list,
+ &temp);
+
+ pr_info("Bandwidth value was %ld: status is %d\n", state, status);
+ if (ACPI_FAILURE(status))
+ return -EFAULT;
+
+ return 0;
+}
+
+static const struct thermal_cooling_device_ops memory_cooling_ops = {
+ .get_max_state = memory_get_max_bandwidth,
+ .get_cur_state = memory_get_cur_bandwidth,
+ .set_cur_state = memory_set_cur_bandwidth,
+};
+
+/*
+ * Memory Device Management
+ */
+static int intel_menlow_memory_add(struct acpi_device *device)
+{
+ int result = -ENODEV;
+ struct thermal_cooling_device *cdev;
+
+ if (!device)
+ return -EINVAL;
+
+ if (!acpi_has_method(device->handle, MEMORY_GET_BANDWIDTH))
+ goto end;
+
+ if (!acpi_has_method(device->handle, MEMORY_SET_BANDWIDTH))
+ goto end;
+
+ cdev = thermal_cooling_device_register("Memory controller", device,
+ &memory_cooling_ops);
+ if (IS_ERR(cdev)) {
+ result = PTR_ERR(cdev);
+ goto end;
+ }
+
+ device->driver_data = cdev;
+ result = sysfs_create_link(&device->dev.kobj,
+ &cdev->device.kobj, "thermal_cooling");
+ if (result)
+ goto unregister;
+
+ result = sysfs_create_link(&cdev->device.kobj,
+ &device->dev.kobj, "device");
+ if (result) {
+ sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
+ goto unregister;
+ }
+
+ end:
+ return result;
+
+ unregister:
+ thermal_cooling_device_unregister(cdev);
+ return result;
+
+}
+
+static int intel_menlow_memory_remove(struct acpi_device *device)
+{
+ struct thermal_cooling_device *cdev;
+
+ if (!device)
+ return -EINVAL;
+
+ cdev = acpi_driver_data(device);
+ if (!cdev)
+ return -EINVAL;
+
+ sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
+ sysfs_remove_link(&cdev->device.kobj, "device");
+ thermal_cooling_device_unregister(cdev);
+
+ return 0;
+}
+
+static const struct acpi_device_id intel_menlow_memory_ids[] = {
+ {"INT0002", 0},
+ {"", 0},
+};
+
+static struct acpi_driver intel_menlow_memory_driver = {
+ .name = "intel_menlow_thermal_control",
+ .ids = intel_menlow_memory_ids,
+ .ops = {
+ .add = intel_menlow_memory_add,
+ .remove = intel_menlow_memory_remove,
+ },
+};
+
+/*
+ * Sensor control on menlow platform
+ */
+
+#define THERMAL_AUX0 0
+#define THERMAL_AUX1 1
+#define GET_AUX0 "GAX0"
+#define GET_AUX1 "GAX1"
+#define SET_AUX0 "SAX0"
+#define SET_AUX1 "SAX1"
+
+struct intel_menlow_attribute {
+ struct device_attribute attr;
+ struct device *device;
+ acpi_handle handle;
+ struct list_head node;
+};
+
+static LIST_HEAD(intel_menlow_attr_list);
+static DEFINE_MUTEX(intel_menlow_attr_lock);
+
+/*
+ * sensor_get_auxtrip - get the current auxtrip value from sensor
+ * @name: Thermalzone name
+ * @auxtype : AUX0/AUX1
+ * @buf: syfs buffer
+ */
+static int sensor_get_auxtrip(acpi_handle handle, int index,
+ unsigned long long *value)
+{
+ acpi_status status;
+
+ if ((index != 0 && index != 1) || !value)
+ return -EINVAL;
+
+ status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0,
+ NULL, value);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return 0;
+}
+
+/*
+ * sensor_set_auxtrip - set the new auxtrip value to sensor
+ * @name: Thermalzone name
+ * @auxtype : AUX0/AUX1
+ * @buf: syfs buffer
+ */
+static int sensor_set_auxtrip(acpi_handle handle, int index, int value)
+{
+ acpi_status status;
+ union acpi_object arg = {
+ ACPI_TYPE_INTEGER
+ };
+ struct acpi_object_list args = {
+ 1, &arg
+ };
+ unsigned long long temp;
+
+ if (index != 0 && index != 1)
+ return -EINVAL;
+
+ status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1,
+ NULL, &temp);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+ if ((index && value < temp) || (!index && value > temp))
+ return -EINVAL;
+
+ arg.integer.value = value;
+ status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0,
+ &args, &temp);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ /* do we need to check the return value of SAX0/SAX1 ? */
+
+ return 0;
+}
+
+#define to_intel_menlow_attr(_attr) \
+ container_of(_attr, struct intel_menlow_attribute, attr)
+
+static ssize_t aux_show(struct device *dev, struct device_attribute *dev_attr,
+ char *buf, int idx)
+{
+ struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+ unsigned long long value;
+ int result;
+
+ result = sensor_get_auxtrip(attr->handle, idx, &value);
+ if (result)
+ return result;
+
+ return sprintf(buf, "%lu", deci_kelvin_to_celsius(value));
+}
+
+static ssize_t aux0_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ return aux_show(dev, dev_attr, buf, 0);
+}
+
+static ssize_t aux1_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ return aux_show(dev, dev_attr, buf, 1);
+}
+
+static ssize_t aux_store(struct device *dev, struct device_attribute *dev_attr,
+ const char *buf, size_t count, int idx)
+{
+ struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+ int value;
+ int result;
+
+ /*Sanity check; should be a positive integer */
+ if (!sscanf(buf, "%d", &value))
+ return -EINVAL;
+
+ if (value < 0)
+ return -EINVAL;
+
+ result = sensor_set_auxtrip(attr->handle, idx,
+ celsius_to_deci_kelvin(value));
+ return result ? result : count;
+}
+
+static ssize_t aux0_store(struct device *dev,
+ struct device_attribute *dev_attr,
+ const char *buf, size_t count)
+{
+ return aux_store(dev, dev_attr, buf, count, 0);
+}
+
+static ssize_t aux1_store(struct device *dev,
+ struct device_attribute *dev_attr,
+ const char *buf, size_t count)
+{
+ return aux_store(dev, dev_attr, buf, count, 1);
+}
+
+/* BIOS can enable/disable the thermal user application in dabney platform */
+#define BIOS_ENABLED "\\_TZ.GSTS"
+static ssize_t bios_enabled_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ acpi_status status;
+ unsigned long long bios_enabled;
+
+ status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled");
+}
+
+static int intel_menlow_add_one_attribute(char *name, umode_t mode, void *show,
+ void *store, struct device *dev,
+ acpi_handle handle)
+{
+ struct intel_menlow_attribute *attr;
+ int result;
+
+ attr = kzalloc(sizeof(struct intel_menlow_attribute), GFP_KERNEL);
+ if (!attr)
+ return -ENOMEM;
+
+ sysfs_attr_init(&attr->attr.attr); /* That is consistent naming :D */
+ attr->attr.attr.name = name;
+ attr->attr.attr.mode = mode;
+ attr->attr.show = show;
+ attr->attr.store = store;
+ attr->device = dev;
+ attr->handle = handle;
+
+ result = device_create_file(dev, &attr->attr);
+ if (result) {
+ kfree(attr);
+ return result;
+ }
+
+ mutex_lock(&intel_menlow_attr_lock);
+ list_add_tail(&attr->node, &intel_menlow_attr_list);
+ mutex_unlock(&intel_menlow_attr_lock);
+
+ return 0;
+}
+
+static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl,
+ void *context, void **rv)
+{
+ acpi_status status;
+ acpi_handle dummy;
+ struct thermal_zone_device *thermal;
+ int result;
+
+ result = acpi_bus_get_private_data(handle, (void **)&thermal);
+ if (result)
+ return 0;
+
+ /* _TZ must have the AUX0/1 methods */
+ status = acpi_get_handle(handle, GET_AUX0, &dummy);
+ if (ACPI_FAILURE(status))
+ return (status == AE_NOT_FOUND) ? AE_OK : status;
+
+ status = acpi_get_handle(handle, SET_AUX0, &dummy);
+ if (ACPI_FAILURE(status))
+ return (status == AE_NOT_FOUND) ? AE_OK : status;
+
+ result = intel_menlow_add_one_attribute("aux0", 0644,
+ aux0_show, aux0_store,
+ &thermal->device, handle);
+ if (result)
+ return AE_ERROR;
+
+ status = acpi_get_handle(handle, GET_AUX1, &dummy);
+ if (ACPI_FAILURE(status))
+ goto aux1_not_found;
+
+ status = acpi_get_handle(handle, SET_AUX1, &dummy);
+ if (ACPI_FAILURE(status))
+ goto aux1_not_found;
+
+ result = intel_menlow_add_one_attribute("aux1", 0644,
+ aux1_show, aux1_store,
+ &thermal->device, handle);
+ if (result) {
+ intel_menlow_unregister_sensor();
+ return AE_ERROR;
+ }
+
+ /*
+ * create the "dabney_enabled" attribute which means the user app
+ * should be loaded or not
+ */
+
+ result = intel_menlow_add_one_attribute("bios_enabled", 0444,
+ bios_enabled_show, NULL,
+ &thermal->device, handle);
+ if (result) {
+ intel_menlow_unregister_sensor();
+ return AE_ERROR;
+ }
+
+ return AE_OK;
+
+ aux1_not_found:
+ if (status == AE_NOT_FOUND)
+ return AE_OK;
+
+ intel_menlow_unregister_sensor();
+ return status;
+}
+
+static void intel_menlow_unregister_sensor(void)
+{
+ struct intel_menlow_attribute *pos, *next;
+
+ mutex_lock(&intel_menlow_attr_lock);
+ list_for_each_entry_safe(pos, next, &intel_menlow_attr_list, node) {
+ list_del(&pos->node);
+ device_remove_file(pos->device, &pos->attr);
+ kfree(pos);
+ }
+ mutex_unlock(&intel_menlow_attr_lock);
+
+ return;
+}
+
+static int __init intel_menlow_module_init(void)
+{
+ int result = -ENODEV;
+ acpi_status status;
+ unsigned long long enable;
+
+ if (acpi_disabled)
+ return result;
+
+ /* Looking for the \_TZ.GSTS method */
+ status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &enable);
+ if (ACPI_FAILURE(status) || !enable)
+ return -ENODEV;
+
+ /* Looking for ACPI device MEM0 with hardware id INT0002 */
+ result = acpi_bus_register_driver(&intel_menlow_memory_driver);
+ if (result)
+ return result;
+
+ /* Looking for sensors in each ACPI thermal zone */
+ status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT,
+ ACPI_UINT32_MAX,
+ intel_menlow_register_sensor, NULL, NULL, NULL);
+ if (ACPI_FAILURE(status)) {
+ acpi_bus_unregister_driver(&intel_menlow_memory_driver);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void __exit intel_menlow_module_exit(void)
+{
+ acpi_bus_unregister_driver(&intel_menlow_memory_driver);
+ intel_menlow_unregister_sensor();
+}
+
+module_init(intel_menlow_module_init);
+module_exit(intel_menlow_module_exit);
diff --git a/drivers/thermal/intel/intel_pch_thermal.c b/drivers/thermal/intel/intel_pch_thermal.c
index 56401fd4708d..dabf11a687a1 100644
--- a/drivers/thermal/intel/intel_pch_thermal.c
+++ b/drivers/thermal/intel/intel_pch_thermal.c
@@ -7,14 +7,16 @@
* Tushar Dave <tushar.n.dave@intel.com>
*/
+#include <linux/acpi.h>
+#include <linux/delay.h>
#include <linux/module.h>
-#include <linux/types.h>
#include <linux/init.h>
#include <linux/pci.h>
-#include <linux/acpi.h>
+#include <linux/pm.h>
+#include <linux/suspend.h>
#include <linux/thermal.h>
+#include <linux/types.h>
#include <linux/units.h>
-#include <linux/pm.h>
/* Intel PCH thermal Device IDs */
#define PCH_THERMAL_DID_HSW_1 0x9C24 /* Haswell PCH */
@@ -24,7 +26,9 @@
#define PCH_THERMAL_DID_SKL_H 0xA131 /* Skylake PCH 100 series */
#define PCH_THERMAL_DID_CNL 0x9Df9 /* CNL PCH */
#define PCH_THERMAL_DID_CNL_H 0xA379 /* CNL-H PCH */
+#define PCH_THERMAL_DID_CNL_LP 0x02F9 /* CNL-LP PCH */
#define PCH_THERMAL_DID_CML_H 0X06F9 /* CML-H PCH */
+#define PCH_THERMAL_DID_LWB 0xA1B1 /* Lewisburg PCH */
/* Wildcat Point-LP PCH Thermal registers */
#define WPT_TEMP 0x0000 /* Temperature */
@@ -34,6 +38,7 @@
#define WPT_TSREL 0x0A /* Thermal Sensor Report Enable and Lock */
#define WPT_TSMIC 0x0C /* Thermal Sensor SMI Control */
#define WPT_CTT 0x0010 /* Catastrophic Trip Point */
+#define WPT_TSPM 0x001C /* Thermal Sensor Power Management */
#define WPT_TAHV 0x0014 /* Thermal Alert High Value */
#define WPT_TALV 0x0018 /* Thermal Alert Low Value */
#define WPT_TL 0x00000040 /* Throttle Value */
@@ -54,6 +59,22 @@
#define WPT_TL_T1L 0x1ff00000 /* T1 Level */
#define WPT_TL_TTEN 0x20000000 /* TT Enable */
+/* Resolution of 1/2 degree C and an offset of -50C */
+#define PCH_TEMP_OFFSET (-50)
+#define GET_WPT_TEMP(x) ((x) * MILLIDEGREE_PER_DEGREE / 2 + WPT_TEMP_OFFSET)
+#define WPT_TEMP_OFFSET (PCH_TEMP_OFFSET * MILLIDEGREE_PER_DEGREE)
+#define GET_PCH_TEMP(x) (((x) / 2) + PCH_TEMP_OFFSET)
+
+/* Amount of time for each cooling delay, 100ms by default for now */
+static unsigned int delay_timeout = 100;
+module_param(delay_timeout, int, 0644);
+MODULE_PARM_DESC(delay_timeout, "amount of time delay for each iteration.");
+
+/* Number of iterations for cooling delay, 600 counts by default for now */
+static unsigned int delay_cnt = 600;
+module_param(delay_cnt, int, 0644);
+MODULE_PARM_DESC(delay_cnt, "total number of iterations for time delay.");
+
static char driver_name[] = "Intel PCH thermal driver";
struct pch_thermal_device {
@@ -146,8 +167,7 @@ read_trips:
trip_temp = readw(ptd->hw_base + WPT_CTT);
trip_temp &= 0x1FF;
if (trip_temp) {
- /* Resolution of 1/2 degree C and an offset of -50C */
- ptd->crt_temp = trip_temp * 1000 / 2 - 50000;
+ ptd->crt_temp = GET_WPT_TEMP(trip_temp);
ptd->crt_trip_id = 0;
++(*nr_trips);
}
@@ -156,8 +176,7 @@ read_trips:
trip_temp = readw(ptd->hw_base + WPT_PHL);
trip_temp &= 0x1FF;
if (trip_temp) {
- /* Resolution of 1/2 degree C and an offset of -50C */
- ptd->hot_temp = trip_temp * 1000 / 2 - 50000;
+ ptd->hot_temp = GET_WPT_TEMP(trip_temp);
ptd->hot_trip_id = *nr_trips;
++(*nr_trips);
}
@@ -169,26 +188,75 @@ read_trips:
static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp)
{
- u16 wpt_temp;
-
- wpt_temp = WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP);
-
- /* Resolution of 1/2 degree C and an offset of -50C */
- *temp = (wpt_temp * 1000 / 2 - 50000);
+ *temp = GET_WPT_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
return 0;
}
+/* Cool the PCH when it's overheat in .suspend_noirq phase */
static int pch_wpt_suspend(struct pch_thermal_device *ptd)
{
u8 tsel;
+ int pch_delay_cnt = 0;
+ u16 pch_thr_temp, pch_cur_temp;
- if (ptd->bios_enabled)
+ /* Shutdown the thermal sensor if it is not enabled by BIOS */
+ if (!ptd->bios_enabled) {
+ tsel = readb(ptd->hw_base + WPT_TSEL);
+ writeb(tsel & 0xFE, ptd->hw_base + WPT_TSEL);
return 0;
+ }
- tsel = readb(ptd->hw_base + WPT_TSEL);
+ /* Do not check temperature if it is not s2idle */
+ if (pm_suspend_via_firmware())
+ return 0;
+
+ /* Get the PCH temperature threshold value */
+ pch_thr_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TSPM));
- writeb(tsel & 0xFE, ptd->hw_base + WPT_TSEL);
+ /* Get the PCH current temperature value */
+ pch_cur_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
+
+ /*
+ * If current PCH temperature is higher than configured PCH threshold
+ * value, run some delay loop with sleep to let the current temperature
+ * go down below the threshold value which helps to allow system enter
+ * lower power S0ix suspend state. Even after delay loop if PCH current
+ * temperature stays above threshold, notify the warning message
+ * which helps to indentify the reason why S0ix entry was rejected.
+ */
+ while (pch_delay_cnt < delay_cnt) {
+ if (pch_cur_temp < pch_thr_temp)
+ break;
+
+ if (pm_wakeup_pending()) {
+ dev_warn(&ptd->pdev->dev, "Wakeup event detected, abort cooling\n");
+ return 0;
+ }
+
+ pch_delay_cnt++;
+ dev_dbg(&ptd->pdev->dev,
+ "CPU-PCH current temp [%dC] higher than the threshold temp [%dC], sleep %d times for %d ms duration\n",
+ pch_cur_temp, pch_thr_temp, pch_delay_cnt, delay_timeout);
+ msleep(delay_timeout);
+ /* Read the PCH current temperature for next cycle. */
+ pch_cur_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
+ }
+
+ if (pch_cur_temp >= pch_thr_temp)
+ dev_warn(&ptd->pdev->dev,
+ "CPU-PCH is hot [%dC] after %d ms delay. S0ix might fail\n",
+ pch_cur_temp, pch_delay_cnt * delay_timeout);
+ else {
+ if (pch_delay_cnt)
+ dev_info(&ptd->pdev->dev,
+ "CPU-PCH is cool [%dC] after %d ms delay\n",
+ pch_cur_temp, pch_delay_cnt * delay_timeout);
+ else
+ dev_info(&ptd->pdev->dev,
+ "CPU-PCH is cool [%dC]\n",
+ pch_cur_temp);
+ }
return 0;
}
@@ -263,10 +331,16 @@ static int pch_get_trip_temp(struct thermal_zone_device *tzd, int trip, int *tem
return 0;
}
+static void pch_critical(struct thermal_zone_device *tzd)
+{
+ dev_dbg(&tzd->device, "%s: critical temperature reached\n", tzd->type);
+}
+
static struct thermal_zone_device_ops tzd_ops = {
.get_temp = pch_thermal_get_temp,
.get_trip_type = pch_get_trip_type,
.get_trip_temp = pch_get_trip_temp,
+ .critical = pch_critical,
};
enum board_ids {
@@ -275,6 +349,7 @@ enum board_ids {
board_skl,
board_cnl,
board_cml,
+ board_lwb,
};
static const struct board_info {
@@ -300,7 +375,11 @@ static const struct board_info {
[board_cml] = {
.name = "pch_cometlake",
.ops = &pch_dev_ops_wpt,
- }
+ },
+ [board_lwb] = {
+ .name = "pch_lewisburg",
+ .ops = &pch_dev_ops_wpt,
+ },
};
static int intel_pch_thermal_probe(struct pci_dev *pdev,
@@ -352,9 +431,14 @@ static int intel_pch_thermal_probe(struct pci_dev *pdev,
err = PTR_ERR(ptd->tzd);
goto error_cleanup;
}
+ err = thermal_zone_device_enable(ptd->tzd);
+ if (err)
+ goto err_unregister;
return 0;
+err_unregister:
+ thermal_zone_device_unregister(ptd->tzd);
error_cleanup:
iounmap(ptd->hw_base);
error_release:
@@ -376,7 +460,7 @@ static void intel_pch_thermal_remove(struct pci_dev *pdev)
pci_disable_device(pdev);
}
-static int intel_pch_thermal_suspend(struct device *device)
+static int intel_pch_thermal_suspend_noirq(struct device *device)
{
struct pch_thermal_device *ptd = dev_get_drvdata(device);
@@ -405,14 +489,18 @@ static const struct pci_device_id intel_pch_thermal_id[] = {
.driver_data = board_cnl, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H),
.driver_data = board_cnl, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_LP),
+ .driver_data = board_cnl, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CML_H),
.driver_data = board_cml, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_LWB),
+ .driver_data = board_lwb, },
{ 0, },
};
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
static const struct dev_pm_ops intel_pch_pm_ops = {
- .suspend = intel_pch_thermal_suspend,
+ .suspend_noirq = intel_pch_thermal_suspend_noirq,
.resume = intel_pch_thermal_resume,
};
diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c
index 53216dcbe173..b80e25ec1261 100644
--- a/drivers/thermal/intel/intel_powerclamp.c
+++ b/drivers/thermal/intel/intel_powerclamp.c
@@ -62,17 +62,13 @@ static struct dentry *debug_dir;
static unsigned int set_target_ratio;
static unsigned int current_ratio;
static bool should_skip;
-static bool reduce_irq;
-static atomic_t idle_wakeup_counter;
+
static unsigned int control_cpu; /* The cpu assigned to collect stat and update
* control parameters. default to BSP but BSP
* can be offlined.
*/
static bool clamping;
-static const struct sched_param sparam = {
- .sched_priority = MAX_USER_RT_PRIO / 2,
-};
struct powerclamp_worker_data {
struct kthread_worker *worker;
struct kthread_work balancing_work;
@@ -288,9 +284,6 @@ static unsigned int get_compensation(int ratio)
cal_data[ratio + 1].steady_comp) / 3;
}
- /* REVISIT: simple penalty of double idle injection */
- if (reduce_irq)
- comp = ratio;
/* do not exceed limit */
if (comp + ratio >= MAX_TARGET_RATIO)
comp = MAX_TARGET_RATIO - ratio - 1;
@@ -304,13 +297,9 @@ static void adjust_compensation(int target_ratio, unsigned int win)
struct powerclamp_calibration_data *d = &cal_data[target_ratio];
/*
- * adjust compensations if confidence level has not been reached or
- * there are too many wakeups during the last idle injection period, we
- * cannot trust the data for compensation.
+ * adjust compensations if confidence level has not been reached.
*/
- if (d->confidence >= CONFIDENCE_OK ||
- atomic_read(&idle_wakeup_counter) >
- win * num_online_cpus())
+ if (d->confidence >= CONFIDENCE_OK)
return;
delta = set_target_ratio - current_ratio;
@@ -350,14 +339,7 @@ static bool powerclamp_adjust_controls(unsigned int target_ratio,
tsc_last = tsc_now;
adjust_compensation(target_ratio, win);
- /*
- * too many external interrupts, set flag such
- * that we can take measure later.
- */
- reduce_irq = atomic_read(&idle_wakeup_counter) >=
- 2 * win * num_online_cpus();
- atomic_set(&idle_wakeup_counter, 0);
/* if we are above target+guard, skip */
return set_target_ratio + guard <= current_ratio;
}
@@ -488,7 +470,7 @@ static void start_power_clamp_worker(unsigned long cpu)
w_data->cpu = cpu;
w_data->clamping = true;
set_bit(cpu, cpu_clamping_mask);
- sched_setscheduler(worker->task, SCHED_FIFO, &sparam);
+ sched_set_fifo(worker->task);
kthread_init_work(&w_data->balancing_work, clamp_balancing_func);
kthread_init_delayed_work(&w_data->idle_injection_work,
clamp_idle_injection_func);
@@ -531,12 +513,10 @@ static int start_power_clamp(void)
set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1);
/* prevent cpu hotplug */
- get_online_cpus();
+ cpus_read_lock();
/* prefer BSP */
- control_cpu = 0;
- if (!cpu_online(control_cpu))
- control_cpu = smp_processor_id();
+ control_cpu = cpumask_first(cpu_online_mask);
clamping = true;
schedule_delayed_work(&poll_pkg_cstate_work, 0);
@@ -545,7 +525,7 @@ static int start_power_clamp(void)
for_each_online_cpu(cpu) {
start_power_clamp_worker(cpu);
}
- put_online_cpus();
+ cpus_read_unlock();
return 0;
}
@@ -559,12 +539,9 @@ static void end_power_clamp(void)
* stop faster.
*/
clamping = false;
- if (bitmap_weight(cpu_clamping_mask, num_possible_cpus())) {
- for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) {
- pr_debug("clamping worker for cpu %d alive, destroy\n",
- i);
- stop_power_clamp_worker(i);
- }
+ for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) {
+ pr_debug("clamping worker for cpu %d alive, destroy\n", i);
+ stop_power_clamp_worker(i);
}
}
@@ -644,14 +621,14 @@ exit_set:
}
/* bind to generic thermal layer as cooling device*/
-static struct thermal_cooling_device_ops powerclamp_cooling_ops = {
+static const struct thermal_cooling_device_ops powerclamp_cooling_ops = {
.get_max_state = powerclamp_get_max_state,
.get_cur_state = powerclamp_get_cur_state,
.set_cur_state = powerclamp_set_cur_state,
};
static const struct x86_cpu_id __initconst intel_powerclamp_ids[] = {
- { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_MWAIT },
+ X86_MATCH_VENDOR_FEATURE(INTEL, X86_FEATURE_MWAIT, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids);
@@ -708,10 +685,8 @@ static enum cpuhp_state hp_state;
static int __init powerclamp_init(void)
{
int retval;
- int bitmap_size;
- bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long);
- cpu_clamping_mask = kzalloc(bitmap_size, GFP_KERNEL);
+ cpu_clamping_mask = bitmap_zalloc(num_possible_cpus(), GFP_KERNEL);
if (!cpu_clamping_mask)
return -ENOMEM;
@@ -756,7 +731,7 @@ exit_free_thread:
exit_unregister:
cpuhp_remove_state_nocalls(hp_state);
exit_free:
- kfree(cpu_clamping_mask);
+ bitmap_free(cpu_clamping_mask);
return retval;
}
module_init(powerclamp_init);
@@ -767,7 +742,7 @@ static void __exit powerclamp_exit(void)
cpuhp_remove_state_nocalls(hp_state);
free_percpu(worker_data);
thermal_cooling_device_unregister(cooling_dev);
- kfree(cpu_clamping_mask);
+ bitmap_free(cpu_clamping_mask);
cancel_delayed_work_sync(&poll_pkg_cstate_work);
debugfs_remove_recursive(debug_dir);
diff --git a/drivers/thermal/intel/intel_quark_dts_thermal.c b/drivers/thermal/intel/intel_quark_dts_thermal.c
index 5d33b350da1c..3eafc6b0e6c3 100644
--- a/drivers/thermal/intel/intel_quark_dts_thermal.c
+++ b/drivers/thermal/intel/intel_quark_dts_thermal.c
@@ -64,9 +64,6 @@
#include <asm/cpu_device_id.h>
#include <asm/iosf_mbi.h>
-#define X86_FAMILY_QUARK 0x5
-#define X86_MODEL_QUARK_X1000 0x9
-
/* DTS reset is programmed via QRK_MBI_UNIT_SOC */
#define QRK_DTS_REG_OFFSET_RESET 0x34
#define QRK_DTS_RESET_BIT BIT(0)
@@ -106,7 +103,6 @@ struct soc_sensor_entry {
bool locked;
u32 store_ptps;
u32 store_dts_enable;
- enum thermal_device_mode mode;
struct thermal_zone_device *tzone;
};
@@ -130,10 +126,8 @@ static int soc_dts_enable(struct thermal_zone_device *tzd)
if (ret)
return ret;
- if (out & QRK_DTS_ENABLE_BIT) {
- aux_entry->mode = THERMAL_DEVICE_ENABLED;
+ if (out & QRK_DTS_ENABLE_BIT)
return 0;
- }
if (!aux_entry->locked) {
out |= QRK_DTS_ENABLE_BIT;
@@ -141,10 +135,7 @@ static int soc_dts_enable(struct thermal_zone_device *tzd)
QRK_DTS_REG_OFFSET_ENABLE, out);
if (ret)
return ret;
-
- aux_entry->mode = THERMAL_DEVICE_ENABLED;
} else {
- aux_entry->mode = THERMAL_DEVICE_DISABLED;
pr_info("DTS is locked. Cannot enable DTS\n");
ret = -EPERM;
}
@@ -163,10 +154,8 @@ static int soc_dts_disable(struct thermal_zone_device *tzd)
if (ret)
return ret;
- if (!(out & QRK_DTS_ENABLE_BIT)) {
- aux_entry->mode = THERMAL_DEVICE_DISABLED;
+ if (!(out & QRK_DTS_ENABLE_BIT))
return 0;
- }
if (!aux_entry->locked) {
out &= ~QRK_DTS_ENABLE_BIT;
@@ -175,10 +164,7 @@ static int soc_dts_disable(struct thermal_zone_device *tzd)
if (ret)
return ret;
-
- aux_entry->mode = THERMAL_DEVICE_DISABLED;
} else {
- aux_entry->mode = THERMAL_DEVICE_ENABLED;
pr_info("DTS is locked. Cannot disable DTS\n");
ret = -EPERM;
}
@@ -312,16 +298,8 @@ static int sys_get_curr_temp(struct thermal_zone_device *tzd,
return 0;
}
-static int sys_get_mode(struct thermal_zone_device *tzd,
- enum thermal_device_mode *mode)
-{
- struct soc_sensor_entry *aux_entry = tzd->devdata;
- *mode = aux_entry->mode;
- return 0;
-}
-
-static int sys_set_mode(struct thermal_zone_device *tzd,
- enum thermal_device_mode mode)
+static int sys_change_mode(struct thermal_zone_device *tzd,
+ enum thermal_device_mode mode)
{
int ret;
@@ -341,8 +319,7 @@ static struct thermal_zone_device_ops tzone_ops = {
.get_trip_type = sys_get_trip_type,
.set_trip_temp = sys_set_trip_temp,
.get_crit_temp = sys_get_crit_temp,
- .get_mode = sys_get_mode,
- .set_mode = sys_set_mode,
+ .change_mode = sys_change_mode,
};
static void free_soc_dts(struct soc_sensor_entry *aux_entry)
@@ -417,9 +394,7 @@ static struct soc_sensor_entry *alloc_soc_dts(void)
goto err_ret;
}
- mutex_lock(&dts_update_mutex);
- err = soc_dts_enable(aux_entry->tzone);
- mutex_unlock(&dts_update_mutex);
+ err = thermal_zone_device_enable(aux_entry->tzone);
if (err)
goto err_aux_status;
@@ -433,7 +408,7 @@ err_ret:
}
static const struct x86_cpu_id qrk_thermal_ids[] __initconst = {
- { X86_VENDOR_INTEL, X86_FAMILY_QUARK, X86_MODEL_QUARK_X1000 },
+ X86_MATCH_VENDOR_FAM_MODEL(INTEL, 5, INTEL_FAM5_QUARK_X1000, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, qrk_thermal_ids);
diff --git a/drivers/thermal/intel/intel_soc_dts_iosf.c b/drivers/thermal/intel/intel_soc_dts_iosf.c
index f75271b669c6..342b0bb5a56d 100644
--- a/drivers/thermal/intel/intel_soc_dts_iosf.c
+++ b/drivers/thermal/intel/intel_soc_dts_iosf.c
@@ -329,6 +329,9 @@ static int add_dts_thermal_zone(int id, struct intel_soc_dts_sensor_entry *dts,
ret = PTR_ERR(dts->tzone);
goto err_ret;
}
+ ret = thermal_zone_device_enable(dts->tzone);
+ if (ret)
+ goto err_enable;
ret = soc_dts_enable(id);
if (ret)
@@ -347,13 +350,14 @@ int intel_soc_dts_iosf_add_read_only_critical_trip(
int i, j;
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
- for (j = 0; j < sensors->soc_dts[i].trip_count; ++j) {
- if (!(sensors->soc_dts[i].trip_mask & BIT(j))) {
- return update_trip_temp(&sensors->soc_dts[i], j,
- sensors->tj_max - critical_offset,
- THERMAL_TRIP_CRITICAL);
- }
- }
+ struct intel_soc_dts_sensor_entry *entry = &sensors->soc_dts[i];
+ int temp = sensors->tj_max - critical_offset;
+ unsigned long count = entry->trip_count;
+ unsigned long mask = entry->trip_mask;
+
+ j = find_first_zero_bit(&mask, count);
+ if (j < count)
+ return update_trip_temp(entry, j, temp, THERMAL_TRIP_CRITICAL);
}
return -EINVAL;
diff --git a/drivers/thermal/intel/intel_soc_dts_thermal.c b/drivers/thermal/intel/intel_soc_dts_thermal.c
index f4be9c14e5d9..92e5c19d03f6 100644
--- a/drivers/thermal/intel/intel_soc_dts_thermal.c
+++ b/drivers/thermal/intel/intel_soc_dts_thermal.c
@@ -36,8 +36,7 @@ static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data)
}
static const struct x86_cpu_id soc_thermal_ids[] = {
- { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT, 0,
- BYT_SOC_DTS_APIC_IRQ},
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT, BYT_SOC_DTS_APIC_IRQ),
{}
};
MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids);
diff --git a/drivers/thermal/intel/intel_tcc_cooling.c b/drivers/thermal/intel/intel_tcc_cooling.c
new file mode 100644
index 000000000000..95adac427b6f
--- /dev/null
+++ b/drivers/thermal/intel/intel_tcc_cooling.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cooling device driver that activates the processor throttling by
+ * programming the TCC Offset register.
+ * Copyright (c) 2021, Intel Corporation.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <asm/cpu_device_id.h>
+
+#define TCC_SHIFT 24
+#define TCC_MASK (0x3fULL<<24)
+#define TCC_PROGRAMMABLE BIT(30)
+
+static struct thermal_cooling_device *tcc_cdev;
+
+static int tcc_get_max_state(struct thermal_cooling_device *cdev, unsigned long
+ *state)
+{
+ *state = TCC_MASK >> TCC_SHIFT;
+ return 0;
+}
+
+static int tcc_offset_update(int tcc)
+{
+ u64 val;
+ int err;
+
+ err = rdmsrl_safe(MSR_IA32_TEMPERATURE_TARGET, &val);
+ if (err)
+ return err;
+
+ val &= ~TCC_MASK;
+ val |= tcc << TCC_SHIFT;
+
+ err = wrmsrl_safe(MSR_IA32_TEMPERATURE_TARGET, val);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int tcc_get_cur_state(struct thermal_cooling_device *cdev, unsigned long
+ *state)
+{
+ u64 val;
+ int err;
+
+ err = rdmsrl_safe(MSR_IA32_TEMPERATURE_TARGET, &val);
+ if (err)
+ return err;
+
+ *state = (val & TCC_MASK) >> TCC_SHIFT;
+ return 0;
+}
+
+static int tcc_set_cur_state(struct thermal_cooling_device *cdev, unsigned long
+ state)
+{
+ return tcc_offset_update(state);
+}
+
+static const struct thermal_cooling_device_ops tcc_cooling_ops = {
+ .get_max_state = tcc_get_max_state,
+ .get_cur_state = tcc_get_cur_state,
+ .set_cur_state = tcc_set_cur_state,
+};
+
+static const struct x86_cpu_id tcc_ids[] __initconst = {
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ICELAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_L, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_N, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_P, NULL),
+ {}
+};
+
+MODULE_DEVICE_TABLE(x86cpu, tcc_ids);
+
+static int __init tcc_cooling_init(void)
+{
+ int ret;
+ u64 val;
+ const struct x86_cpu_id *id;
+
+ int err;
+
+ id = x86_match_cpu(tcc_ids);
+ if (!id)
+ return -ENODEV;
+
+ err = rdmsrl_safe(MSR_PLATFORM_INFO, &val);
+ if (err)
+ return err;
+
+ if (!(val & TCC_PROGRAMMABLE))
+ return -ENODEV;
+
+ pr_info("Programmable TCC Offset detected\n");
+
+ tcc_cdev =
+ thermal_cooling_device_register("TCC Offset", NULL,
+ &tcc_cooling_ops);
+ if (IS_ERR(tcc_cdev)) {
+ ret = PTR_ERR(tcc_cdev);
+ return ret;
+ }
+ return 0;
+}
+
+module_init(tcc_cooling_init)
+
+static void __exit tcc_cooling_exit(void)
+{
+ thermal_cooling_device_unregister(tcc_cdev);
+}
+
+module_exit(tcc_cooling_exit)
+
+MODULE_DESCRIPTION("TCC offset cooling device Driver");
+MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c
new file mode 100644
index 000000000000..8352083b87c7
--- /dev/null
+++ b/drivers/thermal/intel/therm_throt.c
@@ -0,0 +1,752 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Thermal throttle event support code (such as syslog messaging and rate
+ * limiting) that was factored out from x86_64 (mce_intel.c) and i386 (p4.c).
+ *
+ * This allows consistent reporting of CPU thermal throttle events.
+ *
+ * Maintains a counter in /sys that keeps track of the number of thermal
+ * events, such that the user knows how bad the thermal problem might be
+ * (since the logging to syslog is rate limited).
+ *
+ * Author: Dmitriy Zavin (dmitriyz@google.com)
+ *
+ * Credits: Adapted from Zwane Mwaikambo's original code in mce_intel.c.
+ * Inspired by Ross Biro's and Al Borchers' counter code.
+ */
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/percpu.h>
+#include <linux/export.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/smp.h>
+#include <linux/cpu.h>
+
+#include <asm/processor.h>
+#include <asm/thermal.h>
+#include <asm/traps.h>
+#include <asm/apic.h>
+#include <asm/irq.h>
+#include <asm/msr.h>
+
+#include "intel_hfi.h"
+#include "thermal_interrupt.h"
+
+/* How long to wait between reporting thermal events */
+#define CHECK_INTERVAL (300 * HZ)
+
+#define THERMAL_THROTTLING_EVENT 0
+#define POWER_LIMIT_EVENT 1
+
+/**
+ * struct _thermal_state - Represent the current thermal event state
+ * @next_check: Stores the next timestamp, when it is allowed
+ * to log the next warning message.
+ * @last_interrupt_time: Stores the timestamp for the last threshold
+ * high event.
+ * @therm_work: Delayed workqueue structure
+ * @count: Stores the current running count for thermal
+ * or power threshold interrupts.
+ * @last_count: Stores the previous running count for thermal
+ * or power threshold interrupts.
+ * @max_time_ms: This shows the maximum amount of time CPU was
+ * in throttled state for a single thermal
+ * threshold high to low state.
+ * @total_time_ms: This is a cumulative time during which CPU was
+ * in the throttled state.
+ * @rate_control_active: Set when a throttling message is logged.
+ * This is used for the purpose of rate-control.
+ * @new_event: Stores the last high/low status of the
+ * THERM_STATUS_PROCHOT or
+ * THERM_STATUS_POWER_LIMIT.
+ * @level: Stores whether this _thermal_state instance is
+ * for a CORE level or for PACKAGE level.
+ * @sample_index: Index for storing the next sample in the buffer
+ * temp_samples[].
+ * @sample_count: Total number of samples collected in the buffer
+ * temp_samples[].
+ * @average: The last moving average of temperature samples
+ * @baseline_temp: Temperature at which thermal threshold high
+ * interrupt was generated.
+ * @temp_samples: Storage for temperature samples to calculate
+ * moving average.
+ *
+ * This structure is used to represent data related to thermal state for a CPU.
+ * There is a separate storage for core and package level for each CPU.
+ */
+struct _thermal_state {
+ u64 next_check;
+ u64 last_interrupt_time;
+ struct delayed_work therm_work;
+ unsigned long count;
+ unsigned long last_count;
+ unsigned long max_time_ms;
+ unsigned long total_time_ms;
+ bool rate_control_active;
+ bool new_event;
+ u8 level;
+ u8 sample_index;
+ u8 sample_count;
+ u8 average;
+ u8 baseline_temp;
+ u8 temp_samples[3];
+};
+
+struct thermal_state {
+ struct _thermal_state core_throttle;
+ struct _thermal_state core_power_limit;
+ struct _thermal_state package_throttle;
+ struct _thermal_state package_power_limit;
+ struct _thermal_state core_thresh0;
+ struct _thermal_state core_thresh1;
+ struct _thermal_state pkg_thresh0;
+ struct _thermal_state pkg_thresh1;
+};
+
+/* Callback to handle core threshold interrupts */
+int (*platform_thermal_notify)(__u64 msr_val);
+EXPORT_SYMBOL(platform_thermal_notify);
+
+/* Callback to handle core package threshold_interrupts */
+int (*platform_thermal_package_notify)(__u64 msr_val);
+EXPORT_SYMBOL_GPL(platform_thermal_package_notify);
+
+/* Callback support of rate control, return true, if
+ * callback has rate control */
+bool (*platform_thermal_package_rate_control)(void);
+EXPORT_SYMBOL_GPL(platform_thermal_package_rate_control);
+
+
+static DEFINE_PER_CPU(struct thermal_state, thermal_state);
+
+static atomic_t therm_throt_en = ATOMIC_INIT(0);
+
+static u32 lvtthmr_init __read_mostly;
+
+#ifdef CONFIG_SYSFS
+#define define_therm_throt_device_one_ro(_name) \
+ static DEVICE_ATTR(_name, 0444, \
+ therm_throt_device_show_##_name, \
+ NULL) \
+
+#define define_therm_throt_device_show_func(event, name) \
+ \
+static ssize_t therm_throt_device_show_##event##_##name( \
+ struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ unsigned int cpu = dev->id; \
+ ssize_t ret; \
+ \
+ preempt_disable(); /* CPU hotplug */ \
+ if (cpu_online(cpu)) { \
+ ret = sprintf(buf, "%lu\n", \
+ per_cpu(thermal_state, cpu).event.name); \
+ } else \
+ ret = 0; \
+ preempt_enable(); \
+ \
+ return ret; \
+}
+
+define_therm_throt_device_show_func(core_throttle, count);
+define_therm_throt_device_one_ro(core_throttle_count);
+
+define_therm_throt_device_show_func(core_power_limit, count);
+define_therm_throt_device_one_ro(core_power_limit_count);
+
+define_therm_throt_device_show_func(package_throttle, count);
+define_therm_throt_device_one_ro(package_throttle_count);
+
+define_therm_throt_device_show_func(package_power_limit, count);
+define_therm_throt_device_one_ro(package_power_limit_count);
+
+define_therm_throt_device_show_func(core_throttle, max_time_ms);
+define_therm_throt_device_one_ro(core_throttle_max_time_ms);
+
+define_therm_throt_device_show_func(package_throttle, max_time_ms);
+define_therm_throt_device_one_ro(package_throttle_max_time_ms);
+
+define_therm_throt_device_show_func(core_throttle, total_time_ms);
+define_therm_throt_device_one_ro(core_throttle_total_time_ms);
+
+define_therm_throt_device_show_func(package_throttle, total_time_ms);
+define_therm_throt_device_one_ro(package_throttle_total_time_ms);
+
+static struct attribute *thermal_throttle_attrs[] = {
+ &dev_attr_core_throttle_count.attr,
+ &dev_attr_core_throttle_max_time_ms.attr,
+ &dev_attr_core_throttle_total_time_ms.attr,
+ NULL
+};
+
+static const struct attribute_group thermal_attr_group = {
+ .attrs = thermal_throttle_attrs,
+ .name = "thermal_throttle"
+};
+#endif /* CONFIG_SYSFS */
+
+#define CORE_LEVEL 0
+#define PACKAGE_LEVEL 1
+
+#define THERM_THROT_POLL_INTERVAL HZ
+#define THERM_STATUS_PROCHOT_LOG BIT(1)
+
+#define THERM_STATUS_CLEAR_CORE_MASK (BIT(1) | BIT(3) | BIT(5) | BIT(7) | BIT(9) | BIT(11) | BIT(13) | BIT(15))
+#define THERM_STATUS_CLEAR_PKG_MASK (BIT(1) | BIT(3) | BIT(5) | BIT(7) | BIT(9) | BIT(11))
+
+static void clear_therm_status_log(int level)
+{
+ int msr;
+ u64 mask, msr_val;
+
+ if (level == CORE_LEVEL) {
+ msr = MSR_IA32_THERM_STATUS;
+ mask = THERM_STATUS_CLEAR_CORE_MASK;
+ } else {
+ msr = MSR_IA32_PACKAGE_THERM_STATUS;
+ mask = THERM_STATUS_CLEAR_PKG_MASK;
+ }
+
+ rdmsrl(msr, msr_val);
+ msr_val &= mask;
+ wrmsrl(msr, msr_val & ~THERM_STATUS_PROCHOT_LOG);
+}
+
+static void get_therm_status(int level, bool *proc_hot, u8 *temp)
+{
+ int msr;
+ u64 msr_val;
+
+ if (level == CORE_LEVEL)
+ msr = MSR_IA32_THERM_STATUS;
+ else
+ msr = MSR_IA32_PACKAGE_THERM_STATUS;
+
+ rdmsrl(msr, msr_val);
+ if (msr_val & THERM_STATUS_PROCHOT_LOG)
+ *proc_hot = true;
+ else
+ *proc_hot = false;
+
+ *temp = (msr_val >> 16) & 0x7F;
+}
+
+static void __maybe_unused throttle_active_work(struct work_struct *work)
+{
+ struct _thermal_state *state = container_of(to_delayed_work(work),
+ struct _thermal_state, therm_work);
+ unsigned int i, avg, this_cpu = smp_processor_id();
+ u64 now = get_jiffies_64();
+ bool hot;
+ u8 temp;
+
+ get_therm_status(state->level, &hot, &temp);
+ /* temperature value is offset from the max so lesser means hotter */
+ if (!hot && temp > state->baseline_temp) {
+ if (state->rate_control_active)
+ pr_info("CPU%d: %s temperature/speed normal (total events = %lu)\n",
+ this_cpu,
+ state->level == CORE_LEVEL ? "Core" : "Package",
+ state->count);
+
+ state->rate_control_active = false;
+ return;
+ }
+
+ if (time_before64(now, state->next_check) &&
+ state->rate_control_active)
+ goto re_arm;
+
+ state->next_check = now + CHECK_INTERVAL;
+
+ if (state->count != state->last_count) {
+ /* There was one new thermal interrupt */
+ state->last_count = state->count;
+ state->average = 0;
+ state->sample_count = 0;
+ state->sample_index = 0;
+ }
+
+ state->temp_samples[state->sample_index] = temp;
+ state->sample_count++;
+ state->sample_index = (state->sample_index + 1) % ARRAY_SIZE(state->temp_samples);
+ if (state->sample_count < ARRAY_SIZE(state->temp_samples))
+ goto re_arm;
+
+ avg = 0;
+ for (i = 0; i < ARRAY_SIZE(state->temp_samples); ++i)
+ avg += state->temp_samples[i];
+
+ avg /= ARRAY_SIZE(state->temp_samples);
+
+ if (state->average > avg) {
+ pr_warn("CPU%d: %s temperature is above threshold, cpu clock is throttled (total events = %lu)\n",
+ this_cpu,
+ state->level == CORE_LEVEL ? "Core" : "Package",
+ state->count);
+ state->rate_control_active = true;
+ }
+
+ state->average = avg;
+
+re_arm:
+ clear_therm_status_log(state->level);
+ schedule_delayed_work_on(this_cpu, &state->therm_work, THERM_THROT_POLL_INTERVAL);
+}
+
+/***
+ * therm_throt_process - Process thermal throttling event from interrupt
+ * @curr: Whether the condition is current or not (boolean), since the
+ * thermal interrupt normally gets called both when the thermal
+ * event begins and once the event has ended.
+ *
+ * This function is called by the thermal interrupt after the
+ * IRQ has been acknowledged.
+ *
+ * It will take care of rate limiting and printing messages to the syslog.
+ */
+static void therm_throt_process(bool new_event, int event, int level)
+{
+ struct _thermal_state *state;
+ unsigned int this_cpu = smp_processor_id();
+ bool old_event;
+ u64 now;
+ struct thermal_state *pstate = &per_cpu(thermal_state, this_cpu);
+
+ now = get_jiffies_64();
+ if (level == CORE_LEVEL) {
+ if (event == THERMAL_THROTTLING_EVENT)
+ state = &pstate->core_throttle;
+ else if (event == POWER_LIMIT_EVENT)
+ state = &pstate->core_power_limit;
+ else
+ return;
+ } else if (level == PACKAGE_LEVEL) {
+ if (event == THERMAL_THROTTLING_EVENT)
+ state = &pstate->package_throttle;
+ else if (event == POWER_LIMIT_EVENT)
+ state = &pstate->package_power_limit;
+ else
+ return;
+ } else
+ return;
+
+ old_event = state->new_event;
+ state->new_event = new_event;
+
+ if (new_event)
+ state->count++;
+
+ if (event != THERMAL_THROTTLING_EVENT)
+ return;
+
+ if (new_event && !state->last_interrupt_time) {
+ bool hot;
+ u8 temp;
+
+ get_therm_status(state->level, &hot, &temp);
+ /*
+ * Ignore short temperature spike as the system is not close
+ * to PROCHOT. 10C offset is large enough to ignore. It is
+ * already dropped from the high threshold temperature.
+ */
+ if (temp > 10)
+ return;
+
+ state->baseline_temp = temp;
+ state->last_interrupt_time = now;
+ schedule_delayed_work_on(this_cpu, &state->therm_work, THERM_THROT_POLL_INTERVAL);
+ } else if (old_event && state->last_interrupt_time) {
+ unsigned long throttle_time;
+
+ throttle_time = jiffies_delta_to_msecs(now - state->last_interrupt_time);
+ if (throttle_time > state->max_time_ms)
+ state->max_time_ms = throttle_time;
+ state->total_time_ms += throttle_time;
+ state->last_interrupt_time = 0;
+ }
+}
+
+static int thresh_event_valid(int level, int event)
+{
+ struct _thermal_state *state;
+ unsigned int this_cpu = smp_processor_id();
+ struct thermal_state *pstate = &per_cpu(thermal_state, this_cpu);
+ u64 now = get_jiffies_64();
+
+ if (level == PACKAGE_LEVEL)
+ state = (event == 0) ? &pstate->pkg_thresh0 :
+ &pstate->pkg_thresh1;
+ else
+ state = (event == 0) ? &pstate->core_thresh0 :
+ &pstate->core_thresh1;
+
+ if (time_before64(now, state->next_check))
+ return 0;
+
+ state->next_check = now + CHECK_INTERVAL;
+
+ return 1;
+}
+
+static bool int_pln_enable;
+static int __init int_pln_enable_setup(char *s)
+{
+ int_pln_enable = true;
+
+ return 1;
+}
+__setup("int_pln_enable", int_pln_enable_setup);
+
+#ifdef CONFIG_SYSFS
+/* Add/Remove thermal_throttle interface for CPU device: */
+static int thermal_throttle_add_dev(struct device *dev, unsigned int cpu)
+{
+ int err;
+ struct cpuinfo_x86 *c = &cpu_data(cpu);
+
+ err = sysfs_create_group(&dev->kobj, &thermal_attr_group);
+ if (err)
+ return err;
+
+ if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable) {
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_core_power_limit_count.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+ }
+
+ if (cpu_has(c, X86_FEATURE_PTS)) {
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_package_throttle_count.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_package_throttle_max_time_ms.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_package_throttle_total_time_ms.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+
+ if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable) {
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_package_power_limit_count.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+ }
+ }
+
+ return 0;
+
+del_group:
+ sysfs_remove_group(&dev->kobj, &thermal_attr_group);
+
+ return err;
+}
+
+static void thermal_throttle_remove_dev(struct device *dev)
+{
+ sysfs_remove_group(&dev->kobj, &thermal_attr_group);
+}
+
+/* Get notified when a cpu comes on/off. Be hotplug friendly. */
+static int thermal_throttle_online(unsigned int cpu)
+{
+ struct thermal_state *state = &per_cpu(thermal_state, cpu);
+ struct device *dev = get_cpu_device(cpu);
+ u32 l;
+
+ state->package_throttle.level = PACKAGE_LEVEL;
+ state->core_throttle.level = CORE_LEVEL;
+
+ INIT_DELAYED_WORK(&state->package_throttle.therm_work, throttle_active_work);
+ INIT_DELAYED_WORK(&state->core_throttle.therm_work, throttle_active_work);
+
+ /*
+ * The first CPU coming online will enable the HFI. Usually this causes
+ * hardware to issue an HFI thermal interrupt. Such interrupt will reach
+ * the CPU once we enable the thermal vector in the local APIC.
+ */
+ intel_hfi_online(cpu);
+
+ /* Unmask the thermal vector after the above workqueues are initialized. */
+ l = apic_read(APIC_LVTTHMR);
+ apic_write(APIC_LVTTHMR, l & ~APIC_LVT_MASKED);
+
+ return thermal_throttle_add_dev(dev, cpu);
+}
+
+static int thermal_throttle_offline(unsigned int cpu)
+{
+ struct thermal_state *state = &per_cpu(thermal_state, cpu);
+ struct device *dev = get_cpu_device(cpu);
+ u32 l;
+
+ /* Mask the thermal vector before draining evtl. pending work */
+ l = apic_read(APIC_LVTTHMR);
+ apic_write(APIC_LVTTHMR, l | APIC_LVT_MASKED);
+
+ intel_hfi_offline(cpu);
+
+ cancel_delayed_work_sync(&state->package_throttle.therm_work);
+ cancel_delayed_work_sync(&state->core_throttle.therm_work);
+
+ state->package_throttle.rate_control_active = false;
+ state->core_throttle.rate_control_active = false;
+
+ thermal_throttle_remove_dev(dev);
+ return 0;
+}
+
+static __init int thermal_throttle_init_device(void)
+{
+ int ret;
+
+ if (!atomic_read(&therm_throt_en))
+ return 0;
+
+ intel_hfi_init();
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/therm:online",
+ thermal_throttle_online,
+ thermal_throttle_offline);
+ return ret < 0 ? ret : 0;
+}
+device_initcall(thermal_throttle_init_device);
+
+#endif /* CONFIG_SYSFS */
+
+static void notify_package_thresholds(__u64 msr_val)
+{
+ bool notify_thres_0 = false;
+ bool notify_thres_1 = false;
+
+ if (!platform_thermal_package_notify)
+ return;
+
+ /* lower threshold check */
+ if (msr_val & THERM_LOG_THRESHOLD0)
+ notify_thres_0 = true;
+ /* higher threshold check */
+ if (msr_val & THERM_LOG_THRESHOLD1)
+ notify_thres_1 = true;
+
+ if (!notify_thres_0 && !notify_thres_1)
+ return;
+
+ if (platform_thermal_package_rate_control &&
+ platform_thermal_package_rate_control()) {
+ /* Rate control is implemented in callback */
+ platform_thermal_package_notify(msr_val);
+ return;
+ }
+
+ /* lower threshold reached */
+ if (notify_thres_0 && thresh_event_valid(PACKAGE_LEVEL, 0))
+ platform_thermal_package_notify(msr_val);
+ /* higher threshold reached */
+ if (notify_thres_1 && thresh_event_valid(PACKAGE_LEVEL, 1))
+ platform_thermal_package_notify(msr_val);
+}
+
+static void notify_thresholds(__u64 msr_val)
+{
+ /* check whether the interrupt handler is defined;
+ * otherwise simply return
+ */
+ if (!platform_thermal_notify)
+ return;
+
+ /* lower threshold reached */
+ if ((msr_val & THERM_LOG_THRESHOLD0) &&
+ thresh_event_valid(CORE_LEVEL, 0))
+ platform_thermal_notify(msr_val);
+ /* higher threshold reached */
+ if ((msr_val & THERM_LOG_THRESHOLD1) &&
+ thresh_event_valid(CORE_LEVEL, 1))
+ platform_thermal_notify(msr_val);
+}
+
+void __weak notify_hwp_interrupt(void)
+{
+ wrmsrl_safe(MSR_HWP_STATUS, 0);
+}
+
+/* Thermal transition interrupt handler */
+void intel_thermal_interrupt(void)
+{
+ __u64 msr_val;
+
+ if (static_cpu_has(X86_FEATURE_HWP))
+ notify_hwp_interrupt();
+
+ rdmsrl(MSR_IA32_THERM_STATUS, msr_val);
+
+ /* Check for violation of core thermal thresholds*/
+ notify_thresholds(msr_val);
+
+ therm_throt_process(msr_val & THERM_STATUS_PROCHOT,
+ THERMAL_THROTTLING_EVENT,
+ CORE_LEVEL);
+
+ if (this_cpu_has(X86_FEATURE_PLN) && int_pln_enable)
+ therm_throt_process(msr_val & THERM_STATUS_POWER_LIMIT,
+ POWER_LIMIT_EVENT,
+ CORE_LEVEL);
+
+ if (this_cpu_has(X86_FEATURE_PTS)) {
+ rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
+ /* check violations of package thermal thresholds */
+ notify_package_thresholds(msr_val);
+ therm_throt_process(msr_val & PACKAGE_THERM_STATUS_PROCHOT,
+ THERMAL_THROTTLING_EVENT,
+ PACKAGE_LEVEL);
+ if (this_cpu_has(X86_FEATURE_PLN) && int_pln_enable)
+ therm_throt_process(msr_val &
+ PACKAGE_THERM_STATUS_POWER_LIMIT,
+ POWER_LIMIT_EVENT,
+ PACKAGE_LEVEL);
+
+ if (this_cpu_has(X86_FEATURE_HFI))
+ intel_hfi_process_event(msr_val &
+ PACKAGE_THERM_STATUS_HFI_UPDATED);
+ }
+}
+
+/* Thermal monitoring depends on APIC, ACPI and clock modulation */
+static int intel_thermal_supported(struct cpuinfo_x86 *c)
+{
+ if (!boot_cpu_has(X86_FEATURE_APIC))
+ return 0;
+ if (!cpu_has(c, X86_FEATURE_ACPI) || !cpu_has(c, X86_FEATURE_ACC))
+ return 0;
+ return 1;
+}
+
+bool x86_thermal_enabled(void)
+{
+ return atomic_read(&therm_throt_en);
+}
+
+void __init therm_lvt_init(void)
+{
+ /*
+ * This function is only called on boot CPU. Save the init thermal
+ * LVT value on BSP and use that value to restore APs' thermal LVT
+ * entry BIOS programmed later
+ */
+ if (intel_thermal_supported(&boot_cpu_data))
+ lvtthmr_init = apic_read(APIC_LVTTHMR);
+}
+
+void intel_init_thermal(struct cpuinfo_x86 *c)
+{
+ unsigned int cpu = smp_processor_id();
+ int tm2 = 0;
+ u32 l, h;
+
+ if (!intel_thermal_supported(c))
+ return;
+
+ /*
+ * First check if its enabled already, in which case there might
+ * be some SMM goo which handles it, so we can't even put a handler
+ * since it might be delivered via SMI already:
+ */
+ rdmsr(MSR_IA32_MISC_ENABLE, l, h);
+
+ h = lvtthmr_init;
+ /*
+ * The initial value of thermal LVT entries on all APs always reads
+ * 0x10000 because APs are woken up by BSP issuing INIT-SIPI-SIPI
+ * sequence to them and LVT registers are reset to 0s except for
+ * the mask bits which are set to 1s when APs receive INIT IPI.
+ * If BIOS takes over the thermal interrupt and sets its interrupt
+ * delivery mode to SMI (not fixed), it restores the value that the
+ * BIOS has programmed on AP based on BSP's info we saved since BIOS
+ * is always setting the same value for all threads/cores.
+ */
+ if ((h & APIC_DM_FIXED_MASK) != APIC_DM_FIXED)
+ apic_write(APIC_LVTTHMR, lvtthmr_init);
+
+
+ if ((l & MSR_IA32_MISC_ENABLE_TM1) && (h & APIC_DM_SMI)) {
+ if (system_state == SYSTEM_BOOTING)
+ pr_debug("CPU%d: Thermal monitoring handled by SMI\n", cpu);
+ return;
+ }
+
+ /* early Pentium M models use different method for enabling TM2 */
+ if (cpu_has(c, X86_FEATURE_TM2)) {
+ if (c->x86 == 6 && (c->x86_model == 9 || c->x86_model == 13)) {
+ rdmsr(MSR_THERM2_CTL, l, h);
+ if (l & MSR_THERM2_CTL_TM_SELECT)
+ tm2 = 1;
+ } else if (l & MSR_IA32_MISC_ENABLE_TM2)
+ tm2 = 1;
+ }
+
+ /* We'll mask the thermal vector in the lapic till we're ready: */
+ h = THERMAL_APIC_VECTOR | APIC_DM_FIXED | APIC_LVT_MASKED;
+ apic_write(APIC_LVTTHMR, h);
+
+ rdmsr(MSR_IA32_THERM_INTERRUPT, l, h);
+ if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable)
+ wrmsr(MSR_IA32_THERM_INTERRUPT,
+ (l | (THERM_INT_LOW_ENABLE
+ | THERM_INT_HIGH_ENABLE)) & ~THERM_INT_PLN_ENABLE, h);
+ else if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable)
+ wrmsr(MSR_IA32_THERM_INTERRUPT,
+ l | (THERM_INT_LOW_ENABLE
+ | THERM_INT_HIGH_ENABLE | THERM_INT_PLN_ENABLE), h);
+ else
+ wrmsr(MSR_IA32_THERM_INTERRUPT,
+ l | (THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE), h);
+
+ if (cpu_has(c, X86_FEATURE_PTS)) {
+ rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
+ if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable)
+ wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
+ (l | (PACKAGE_THERM_INT_LOW_ENABLE
+ | PACKAGE_THERM_INT_HIGH_ENABLE))
+ & ~PACKAGE_THERM_INT_PLN_ENABLE, h);
+ else if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable)
+ wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
+ l | (PACKAGE_THERM_INT_LOW_ENABLE
+ | PACKAGE_THERM_INT_HIGH_ENABLE
+ | PACKAGE_THERM_INT_PLN_ENABLE), h);
+ else
+ wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
+ l | (PACKAGE_THERM_INT_LOW_ENABLE
+ | PACKAGE_THERM_INT_HIGH_ENABLE), h);
+
+ if (cpu_has(c, X86_FEATURE_HFI)) {
+ rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
+ wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
+ l | PACKAGE_THERM_INT_HFI_ENABLE, h);
+ }
+ }
+
+ rdmsr(MSR_IA32_MISC_ENABLE, l, h);
+ wrmsr(MSR_IA32_MISC_ENABLE, l | MSR_IA32_MISC_ENABLE_TM1, h);
+
+ pr_info_once("CPU0: Thermal monitoring enabled (%s)\n",
+ tm2 ? "TM2" : "TM1");
+
+ /* enable thermal throttle processing */
+ atomic_set(&therm_throt_en, 1);
+}
diff --git a/drivers/thermal/intel/thermal_interrupt.h b/drivers/thermal/intel/thermal_interrupt.h
new file mode 100644
index 000000000000..01e7bed2ffc7
--- /dev/null
+++ b/drivers/thermal/intel/thermal_interrupt.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INTEL_THERMAL_INTERRUPT_H
+#define _INTEL_THERMAL_INTERRUPT_H
+
+/* Interrupt Handler for package thermal thresholds */
+extern int (*platform_thermal_package_notify)(__u64 msr_val);
+
+/* Interrupt Handler for core thermal thresholds */
+extern int (*platform_thermal_notify)(__u64 msr_val);
+
+/* Callback support of rate control, return true, if
+ * callback has rate control */
+extern bool (*platform_thermal_package_rate_control)(void);
+
+/* Handle HWP interrupt */
+extern void notify_hwp_interrupt(void);
+
+#endif /* _INTEL_THERMAL_INTERRUPT_H */
diff --git a/drivers/thermal/intel/x86_pkg_temp_thermal.c b/drivers/thermal/intel/x86_pkg_temp_thermal.c
index ddb4a973c698..a0e234fce71a 100644
--- a/drivers/thermal/intel/x86_pkg_temp_thermal.c
+++ b/drivers/thermal/intel/x86_pkg_temp_thermal.c
@@ -17,8 +17,10 @@
#include <linux/pm.h>
#include <linux/thermal.h>
#include <linux/debugfs.h>
+
#include <asm/cpu_device_id.h>
-#include <asm/mce.h>
+
+#include "thermal_interrupt.h"
/*
* Rate control delay: Idea is to introduce denounce effect
@@ -63,7 +65,7 @@ static int max_id __read_mostly;
/* Array of zone pointers */
static struct zone_device **zones;
/* Serializes interrupt notification, work and hotplug */
-static DEFINE_SPINLOCK(pkg_temp_lock);
+static DEFINE_RAW_SPINLOCK(pkg_temp_lock);
/* Protects zone operation in the work function against hotplug removal */
static DEFINE_MUTEX(thermal_zone_mutex);
@@ -103,7 +105,7 @@ static struct zone_device *pkg_temp_thermal_get_dev(unsigned int cpu)
}
/*
-* tj-max is is interesting because threshold is set relative to this
+* tj-max is interesting because threshold is set relative to this
* temperature.
*/
static int get_tj_max(int cpu, u32 *tj_max)
@@ -164,7 +166,7 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzd,
if (thres_reg_value)
*temp = zonedev->tj_max - thres_reg_value * 1000;
else
- *temp = 0;
+ *temp = THERMAL_TEMP_INVALID;
pr_debug("sys_get_trip_temp %d\n", *temp);
return 0;
@@ -266,12 +268,12 @@ static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work)
u64 msr_val, wr_val;
mutex_lock(&thermal_zone_mutex);
- spin_lock_irq(&pkg_temp_lock);
+ raw_spin_lock_irq(&pkg_temp_lock);
++pkg_work_cnt;
zonedev = pkg_temp_thermal_get_dev(cpu);
if (!zonedev) {
- spin_unlock_irq(&pkg_temp_lock);
+ raw_spin_unlock_irq(&pkg_temp_lock);
mutex_unlock(&thermal_zone_mutex);
return;
}
@@ -285,7 +287,7 @@ static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work)
}
enable_pkg_thres_interrupt();
- spin_unlock_irq(&pkg_temp_lock);
+ raw_spin_unlock_irq(&pkg_temp_lock);
/*
* If tzone is not NULL, then thermal_zone_mutex will prevent the
@@ -310,7 +312,7 @@ static int pkg_thermal_notify(u64 msr_val)
struct zone_device *zonedev;
unsigned long flags;
- spin_lock_irqsave(&pkg_temp_lock, flags);
+ raw_spin_lock_irqsave(&pkg_temp_lock, flags);
++pkg_interrupt_cnt;
disable_pkg_thres_interrupt();
@@ -322,7 +324,7 @@ static int pkg_thermal_notify(u64 msr_val)
pkg_thermal_schedule_work(zonedev->cpu, &zonedev->work);
}
- spin_unlock_irqrestore(&pkg_temp_lock, flags);
+ raw_spin_unlock_irqrestore(&pkg_temp_lock, flags);
return 0;
}
@@ -363,14 +365,20 @@ static int pkg_temp_thermal_device_add(unsigned int cpu)
kfree(zonedev);
return err;
}
+ err = thermal_zone_device_enable(zonedev->tzone);
+ if (err) {
+ thermal_zone_device_unregister(zonedev->tzone);
+ kfree(zonedev);
+ return err;
+ }
/* Store MSR value for package thermal interrupt, to restore at exit */
rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, zonedev->msr_pkg_therm_low,
zonedev->msr_pkg_therm_high);
cpumask_set_cpu(cpu, &zonedev->cpumask);
- spin_lock_irq(&pkg_temp_lock);
+ raw_spin_lock_irq(&pkg_temp_lock);
zones[id] = zonedev;
- spin_unlock_irq(&pkg_temp_lock);
+ raw_spin_unlock_irq(&pkg_temp_lock);
return 0;
}
@@ -407,7 +415,7 @@ static int pkg_thermal_cpu_offline(unsigned int cpu)
}
/* Protect against work and interrupts */
- spin_lock_irq(&pkg_temp_lock);
+ raw_spin_lock_irq(&pkg_temp_lock);
/*
* Check whether this cpu was the current target and store the new
@@ -439,9 +447,9 @@ static int pkg_thermal_cpu_offline(unsigned int cpu)
* To cancel the work we need to drop the lock, otherwise
* we might deadlock if the work needs to be flushed.
*/
- spin_unlock_irq(&pkg_temp_lock);
+ raw_spin_unlock_irq(&pkg_temp_lock);
cancel_delayed_work_sync(&zonedev->work);
- spin_lock_irq(&pkg_temp_lock);
+ raw_spin_lock_irq(&pkg_temp_lock);
/*
* If this is not the last cpu in the package and the work
* did not run after we dropped the lock above, then we
@@ -452,7 +460,7 @@ static int pkg_thermal_cpu_offline(unsigned int cpu)
pkg_thermal_schedule_work(target, &zonedev->work);
}
- spin_unlock_irq(&pkg_temp_lock);
+ raw_spin_unlock_irq(&pkg_temp_lock);
/* Final cleanup if this is the last cpu */
if (lastcpu)
@@ -478,7 +486,7 @@ static int pkg_thermal_cpu_online(unsigned int cpu)
}
static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = {
- { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_PTS },
+ X86_MATCH_VENDOR_FEATURE(INTEL, X86_FEATURE_PTS, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids);
diff --git a/drivers/thermal/k3_bandgap.c b/drivers/thermal/k3_bandgap.c
new file mode 100644
index 000000000000..22c9bcb899c3
--- /dev/null
+++ b/drivers/thermal/k3_bandgap.c
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI Bandgap temperature sensor driver for K3 SoC Family
+ *
+ * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+
+#include "thermal_hwmon.h"
+
+#define K3_VTM_DEVINFO_PWR0_OFFSET 0x4
+#define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0
+#define K3_VTM_TMPSENS0_CTRL_OFFSET 0x80
+#define K3_VTM_REGS_PER_TS 0x10
+#define K3_VTM_TS_STAT_DTEMP_MASK 0x3ff
+#define K3_VTM_TMPSENS_CTRL_CBIASSEL BIT(0)
+#define K3_VTM_TMPSENS_CTRL_SOC BIT(5)
+#define K3_VTM_TMPSENS_CTRL_CLRZ BIT(6)
+#define K3_VTM_TMPSENS_CTRL_CLKON_REQ BIT(7)
+
+#define K3_VTM_ADC_BEGIN_VAL 540
+#define K3_VTM_ADC_END_VAL 944
+
+static const int k3_adc_to_temp[] = {
+ -40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200,
+ -37800, -37400, -37000, -36600, -36200, -35800, -35300, -34700, -34200,
+ -33800, -33400, -33000, -32600, -32200, -31800, -31400, -31000, -30600,
+ -30200, -29800, -29400, -29000, -28600, -28200, -27700, -27100, -26600,
+ -26200, -25800, -25400, -25000, -24600, -24200, -23800, -23400, -23000,
+ -22600, -22200, -21800, -21400, -21000, -20500, -19900, -19400, -19000,
+ -18600, -18200, -17800, -17400, -17000, -16600, -16200, -15800, -15400,
+ -15000, -14600, -14200, -13800, -13400, -13000, -12500, -11900, -11400,
+ -11000, -10600, -10200, -9800, -9400, -9000, -8600, -8200, -7800, -7400,
+ -7000, -6600, -6200, -5800, -5400, -5000, -4500, -3900, -3400, -3000,
+ -2600, -2200, -1800, -1400, -1000, -600, -200, 200, 600, 1000, 1400,
+ 1800, 2200, 2600, 3000, 3400, 3900, 4500, 5000, 5400, 5800, 6200, 6600,
+ 7000, 7400, 7800, 8200, 8600, 9000, 9400, 9800, 10200, 10600, 11000,
+ 11400, 11800, 12200, 12700, 13300, 13800, 14200, 14600, 15000, 15400,
+ 15800, 16200, 16600, 17000, 17400, 17800, 18200, 18600, 19000, 19400,
+ 19800, 20200, 20600, 21000, 21400, 21900, 22500, 23000, 23400, 23800,
+ 24200, 24600, 25000, 25400, 25800, 26200, 26600, 27000, 27400, 27800,
+ 28200, 28600, 29000, 29400, 29800, 30200, 30600, 31000, 31400, 31900,
+ 32500, 33000, 33400, 33800, 34200, 34600, 35000, 35400, 35800, 36200,
+ 36600, 37000, 37400, 37800, 38200, 38600, 39000, 39400, 39800, 40200,
+ 40600, 41000, 41400, 41800, 42200, 42600, 43100, 43700, 44200, 44600,
+ 45000, 45400, 45800, 46200, 46600, 47000, 47400, 47800, 48200, 48600,
+ 49000, 49400, 49800, 50200, 50600, 51000, 51400, 51800, 52200, 52600,
+ 53000, 53400, 53800, 54200, 54600, 55000, 55400, 55900, 56500, 57000,
+ 57400, 57800, 58200, 58600, 59000, 59400, 59800, 60200, 60600, 61000,
+ 61400, 61800, 62200, 62600, 63000, 63400, 63800, 64200, 64600, 65000,
+ 65400, 65800, 66200, 66600, 67000, 67400, 67800, 68200, 68600, 69000,
+ 69400, 69800, 70200, 70600, 71000, 71500, 72100, 72600, 73000, 73400,
+ 73800, 74200, 74600, 75000, 75400, 75800, 76200, 76600, 77000, 77400,
+ 77800, 78200, 78600, 79000, 79400, 79800, 80200, 80600, 81000, 81400,
+ 81800, 82200, 82600, 83000, 83400, 83800, 84200, 84600, 85000, 85400,
+ 85800, 86200, 86600, 87000, 87400, 87800, 88200, 88600, 89000, 89400,
+ 89800, 90200, 90600, 91000, 91400, 91800, 92200, 92600, 93000, 93400,
+ 93800, 94200, 94600, 95000, 95400, 95800, 96200, 96600, 97000, 97500,
+ 98100, 98600, 99000, 99400, 99800, 100200, 100600, 101000, 101400,
+ 101800, 102200, 102600, 103000, 103400, 103800, 104200, 104600, 105000,
+ 105400, 105800, 106200, 106600, 107000, 107400, 107800, 108200, 108600,
+ 109000, 109400, 109800, 110200, 110600, 111000, 111400, 111800, 112200,
+ 112600, 113000, 113400, 113800, 114200, 114600, 115000, 115400, 115800,
+ 116200, 116600, 117000, 117400, 117800, 118200, 118600, 119000, 119400,
+ 119800, 120200, 120600, 121000, 121400, 121800, 122200, 122600, 123000,
+ 123400, 123800, 124200, 124600, 124900, 125000,
+};
+
+struct k3_bandgap {
+ void __iomem *base;
+ const struct k3_bandgap_data *conf;
+};
+
+/* common data structures */
+struct k3_thermal_data {
+ struct thermal_zone_device *tzd;
+ struct k3_bandgap *bgp;
+ int sensor_id;
+ u32 ctrl_offset;
+ u32 stat_offset;
+};
+
+static unsigned int vtm_get_best_value(unsigned int s0, unsigned int s1,
+ unsigned int s2)
+{
+ int d01 = abs(s0 - s1);
+ int d02 = abs(s0 - s2);
+ int d12 = abs(s1 - s2);
+
+ if (d01 <= d02 && d01 <= d12)
+ return (s0 + s1) / 2;
+
+ if (d02 <= d01 && d02 <= d12)
+ return (s0 + s2) / 2;
+
+ return (s1 + s2) / 2;
+}
+
+static int k3_bgp_read_temp(struct k3_thermal_data *devdata,
+ int *temp)
+{
+ struct k3_bandgap *bgp;
+ unsigned int dtemp, s0, s1, s2;
+
+ bgp = devdata->bgp;
+
+ /*
+ * Errata is applicable for am654 pg 1.0 silicon. There
+ * is a variation of the order for 8-10 degree centigrade.
+ * Work around that by getting the average of two closest
+ * readings out of three readings everytime we want to
+ * report temperatures.
+ *
+ * Errata workaround.
+ */
+ s0 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ s1 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ s2 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ dtemp = vtm_get_best_value(s0, s1, s2);
+
+ if (dtemp < K3_VTM_ADC_BEGIN_VAL || dtemp > K3_VTM_ADC_END_VAL)
+ return -EINVAL;
+
+ *temp = k3_adc_to_temp[dtemp - K3_VTM_ADC_BEGIN_VAL];
+
+ return 0;
+}
+
+static int k3_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct k3_thermal_data *data = tz->devdata;
+ int ret = 0;
+
+ ret = k3_bgp_read_temp(data, temp);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops k3_of_thermal_ops = {
+ .get_temp = k3_thermal_get_temp,
+};
+
+static const struct of_device_id of_k3_bandgap_match[];
+
+static int k3_bandgap_probe(struct platform_device *pdev)
+{
+ int ret = 0, cnt, val, id;
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ struct k3_bandgap *bgp;
+ struct k3_thermal_data *data;
+
+ if (ARRAY_SIZE(k3_adc_to_temp) != (K3_VTM_ADC_END_VAL + 1 -
+ K3_VTM_ADC_BEGIN_VAL))
+ return -EINVAL;
+
+ bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL);
+ if (!bgp)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ bgp->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(bgp->base))
+ return PTR_ERR(bgp->base);
+
+ pm_runtime_enable(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ pm_runtime_disable(dev);
+ return ret;
+ }
+
+ /* Get the sensor count in the VTM */
+ val = readl(bgp->base + K3_VTM_DEVINFO_PWR0_OFFSET);
+ cnt = val & K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK;
+ cnt >>= __ffs(K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK);
+
+ data = devm_kcalloc(dev, cnt, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Register the thermal sensors */
+ for (id = 0; id < cnt; id++) {
+ data[id].sensor_id = id;
+ data[id].bgp = bgp;
+ data[id].ctrl_offset = K3_VTM_TMPSENS0_CTRL_OFFSET +
+ id * K3_VTM_REGS_PER_TS;
+ data[id].stat_offset = data[id].ctrl_offset + 0x8;
+
+ val = readl(data[id].bgp->base + data[id].ctrl_offset);
+ val |= (K3_VTM_TMPSENS_CTRL_SOC |
+ K3_VTM_TMPSENS_CTRL_CLRZ |
+ K3_VTM_TMPSENS_CTRL_CLKON_REQ);
+ val &= ~K3_VTM_TMPSENS_CTRL_CBIASSEL;
+ writel(val, data[id].bgp->base + data[id].ctrl_offset);
+
+ data[id].tzd =
+ devm_thermal_of_zone_register(dev, id,
+ &data[id],
+ &k3_of_thermal_ops);
+ if (IS_ERR(data[id].tzd)) {
+ dev_err(dev, "thermal zone device is NULL\n");
+ ret = PTR_ERR(data[id].tzd);
+ goto err_alloc;
+ }
+
+ if (devm_thermal_add_hwmon_sysfs(data[id].tzd))
+ dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
+ }
+
+ platform_set_drvdata(pdev, bgp);
+
+ return 0;
+
+err_alloc:
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static int k3_bandgap_remove(struct platform_device *pdev)
+{
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id of_k3_bandgap_match[] = {
+ {
+ .compatible = "ti,am654-vtm",
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, of_k3_bandgap_match);
+
+static struct platform_driver k3_bandgap_sensor_driver = {
+ .probe = k3_bandgap_probe,
+ .remove = k3_bandgap_remove,
+ .driver = {
+ .name = "k3-soc-thermal",
+ .of_match_table = of_k3_bandgap_match,
+ },
+};
+
+module_platform_driver(k3_bandgap_sensor_driver);
+
+MODULE_DESCRIPTION("K3 bandgap temperature sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>");
diff --git a/drivers/thermal/k3_j72xx_bandgap.c b/drivers/thermal/k3_j72xx_bandgap.c
new file mode 100644
index 000000000000..16b6bcf1bf4f
--- /dev/null
+++ b/drivers/thermal/k3_j72xx_bandgap.c
@@ -0,0 +1,565 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI Bandgap temperature sensor driver for J72XX SoC Family
+ *
+ * Copyright (C) 2021 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/err.h>
+#include <linux/types.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/thermal.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#define K3_VTM_DEVINFO_PWR0_OFFSET 0x4
+#define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0
+#define K3_VTM_TMPSENS0_CTRL_OFFSET 0x300
+#define K3_VTM_MISC_CTRL_OFFSET 0xc
+#define K3_VTM_TMPSENS_STAT_OFFSET 0x8
+#define K3_VTM_ANYMAXT_OUTRG_ALERT_EN 0x1
+#define K3_VTM_MISC_CTRL2_OFFSET 0x10
+#define K3_VTM_TS_STAT_DTEMP_MASK 0x3ff
+#define K3_VTM_MAX_NUM_TS 8
+#define K3_VTM_TMPSENS_CTRL_SOC BIT(5)
+#define K3_VTM_TMPSENS_CTRL_CLRZ BIT(6)
+#define K3_VTM_TMPSENS_CTRL_CLKON_REQ BIT(7)
+#define K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN BIT(11)
+
+#define K3_VTM_CORRECTION_TEMP_CNT 3
+
+#define MINUS40CREF 5
+#define PLUS30CREF 253
+#define PLUS125CREF 730
+#define PLUS150CREF 940
+
+#define TABLE_SIZE 1024
+#define MAX_TEMP 123000
+#define COOL_DOWN_TEMP 105000
+
+#define FACTORS_REDUCTION 13
+static int *derived_table;
+
+static int compute_value(int index, const s64 *factors, int nr_factors,
+ int reduction)
+{
+ s64 value = 0;
+ int i;
+
+ for (i = 0; i < nr_factors; i++)
+ value += factors[i] * int_pow(index, i);
+
+ return (int)div64_s64(value, int_pow(10, reduction));
+}
+
+static void init_table(int factors_size, int *table, const s64 *factors)
+{
+ int i;
+
+ for (i = 0; i < TABLE_SIZE; i++)
+ table[i] = compute_value(i, factors, factors_size,
+ FACTORS_REDUCTION);
+}
+
+/**
+ * struct err_values - structure containing error/reference values
+ * @refs: reference error values for -40C, 30C, 125C & 150C
+ * @errs: Actual error values for -40C, 30C, 125C & 150C read from the efuse
+ */
+struct err_values {
+ int refs[4];
+ int errs[4];
+};
+
+static void create_table_segments(struct err_values *err_vals, int seg,
+ int *ref_table)
+{
+ int m = 0, c, num, den, i, err, idx1, idx2, err1, err2, ref1, ref2;
+
+ if (seg == 0)
+ idx1 = 0;
+ else
+ idx1 = err_vals->refs[seg];
+
+ idx2 = err_vals->refs[seg + 1];
+ err1 = err_vals->errs[seg];
+ err2 = err_vals->errs[seg + 1];
+ ref1 = err_vals->refs[seg];
+ ref2 = err_vals->refs[seg + 1];
+
+ /*
+ * Calculate the slope with adc values read from the register
+ * as the y-axis param and err in adc value as x-axis param
+ */
+ num = ref2 - ref1;
+ den = err2 - err1;
+ if (den)
+ m = num / den;
+ c = ref2 - m * err2;
+
+ /*
+ * Take care of divide by zero error if error values are same
+ * Or when the slope is 0
+ */
+ if (den != 0 && m != 0) {
+ for (i = idx1; i <= idx2; i++) {
+ err = (i - c) / m;
+ if (((i + err) < 0) || ((i + err) >= TABLE_SIZE))
+ continue;
+ derived_table[i] = ref_table[i + err];
+ }
+ } else { /* Constant error take care of divide by zero */
+ for (i = idx1; i <= idx2; i++) {
+ if (((i + err1) < 0) || ((i + err1) >= TABLE_SIZE))
+ continue;
+ derived_table[i] = ref_table[i + err1];
+ }
+ }
+}
+
+static int prep_lookup_table(struct err_values *err_vals, int *ref_table)
+{
+ int inc, i, seg;
+
+ /*
+ * Fill up the lookup table under 3 segments
+ * region -40C to +30C
+ * region +30C to +125C
+ * region +125C to +150C
+ */
+ for (seg = 0; seg < 3; seg++)
+ create_table_segments(err_vals, seg, ref_table);
+
+ /* Get to the first valid temperature */
+ i = 0;
+ while (!derived_table[i])
+ i++;
+
+ /*
+ * Get to the last zero index and back fill the temperature for
+ * sake of continuity
+ */
+ if (i) {
+ /* 300 milli celsius steps */
+ while (i--)
+ derived_table[i] = derived_table[i + 1] - 300;
+ }
+
+ /*
+ * Fill the last trailing 0s which are unfilled with increments of
+ * 100 milli celsius till 1023 code
+ */
+ i = TABLE_SIZE - 1;
+ while (!derived_table[i])
+ i--;
+
+ i++;
+ inc = 1;
+ while (i < TABLE_SIZE) {
+ derived_table[i] = derived_table[i - 1] + inc * 100;
+ i++;
+ }
+
+ return 0;
+}
+
+struct k3_thermal_data;
+
+struct k3_j72xx_bandgap {
+ struct device *dev;
+ void __iomem *base;
+ void __iomem *cfg2_base;
+ void __iomem *fuse_base;
+ struct k3_thermal_data *ts_data[K3_VTM_MAX_NUM_TS];
+};
+
+/* common data structures */
+struct k3_thermal_data {
+ struct k3_j72xx_bandgap *bgp;
+ u32 ctrl_offset;
+ u32 stat_offset;
+};
+
+static int two_cmp(int tmp, int mask)
+{
+ tmp = ~(tmp);
+ tmp &= mask;
+ tmp += 1;
+
+ /* Return negative value */
+ return (0 - tmp);
+}
+
+static unsigned int vtm_get_best_value(unsigned int s0, unsigned int s1,
+ unsigned int s2)
+{
+ int d01 = abs(s0 - s1);
+ int d02 = abs(s0 - s2);
+ int d12 = abs(s1 - s2);
+
+ if (d01 <= d02 && d01 <= d12)
+ return (s0 + s1) / 2;
+
+ if (d02 <= d01 && d02 <= d12)
+ return (s0 + s2) / 2;
+
+ return (s1 + s2) / 2;
+}
+
+static inline int k3_bgp_read_temp(struct k3_thermal_data *devdata,
+ int *temp)
+{
+ struct k3_j72xx_bandgap *bgp;
+ unsigned int dtemp, s0, s1, s2;
+
+ bgp = devdata->bgp;
+ /*
+ * Errata is applicable for am654 pg 1.0 silicon/J7ES. There
+ * is a variation of the order for certain degree centigrade on AM654.
+ * Work around that by getting the average of two closest
+ * readings out of three readings everytime we want to
+ * report temperatures.
+ *
+ * Errata workaround.
+ */
+ s0 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ s1 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ s2 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ dtemp = vtm_get_best_value(s0, s1, s2);
+
+ if (dtemp < 0 || dtemp >= TABLE_SIZE)
+ return -EINVAL;
+
+ *temp = derived_table[dtemp];
+
+ return 0;
+}
+
+/* Get temperature callback function for thermal zone */
+static int k3_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct k3_thermal_data *data = tz->devdata;
+ int ret = 0;
+
+ ret = k3_bgp_read_temp(data, temp);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops k3_of_thermal_ops = {
+ .get_temp = k3_thermal_get_temp,
+};
+
+static int k3_j72xx_bandgap_temp_to_adc_code(int temp)
+{
+ int low = 0, high = TABLE_SIZE - 1, mid;
+
+ if (temp > 160000 || temp < -50000)
+ return -EINVAL;
+
+ /* Binary search to find the adc code */
+ while (low < (high - 1)) {
+ mid = (low + high) / 2;
+ if (temp <= derived_table[mid])
+ high = mid;
+ else
+ low = mid;
+ }
+
+ return mid;
+}
+
+static void get_efuse_values(int id, struct k3_thermal_data *data, int *err,
+ struct k3_j72xx_bandgap *bgp)
+{
+ int i, tmp, pow;
+ int ct_offsets[5][K3_VTM_CORRECTION_TEMP_CNT] = {
+ { 0x0, 0x8, 0x4 },
+ { 0x0, 0x8, 0x4 },
+ { 0x0, -1, 0x4 },
+ { 0x0, 0xC, -1 },
+ { 0x0, 0xc, 0x8 }
+ };
+ int ct_bm[5][K3_VTM_CORRECTION_TEMP_CNT] = {
+ { 0x3f, 0x1fe000, 0x1ff },
+ { 0xfc0, 0x1fe000, 0x3fe00 },
+ { 0x3f000, 0x7f800000, 0x7fc0000 },
+ { 0xfc0000, 0x1fe0, 0x1f800000 },
+ { 0x3f000000, 0x1fe000, 0x1ff0 }
+ };
+
+ for (i = 0; i < 3; i++) {
+ /* Extract the offset value using bit-mask */
+ if (ct_offsets[id][i] == -1 && i == 1) {
+ /* 25C offset Case of Sensor 2 split between 2 regs */
+ tmp = (readl(bgp->fuse_base + 0x8) & 0xE0000000) >> (29);
+ tmp |= ((readl(bgp->fuse_base + 0xC) & 0x1F) << 3);
+ pow = tmp & 0x80;
+ } else if (ct_offsets[id][i] == -1 && i == 2) {
+ /* 125C Case of Sensor 3 split between 2 regs */
+ tmp = (readl(bgp->fuse_base + 0x4) & 0xF8000000) >> (27);
+ tmp |= ((readl(bgp->fuse_base + 0x8) & 0xF) << 5);
+ pow = tmp & 0x100;
+ } else {
+ tmp = readl(bgp->fuse_base + ct_offsets[id][i]);
+ tmp &= ct_bm[id][i];
+ tmp = tmp >> __ffs(ct_bm[id][i]);
+
+ /* Obtain the sign bit pow*/
+ pow = ct_bm[id][i] >> __ffs(ct_bm[id][i]);
+ pow += 1;
+ pow /= 2;
+ }
+
+ /* Check for negative value */
+ if (tmp & pow) {
+ /* 2's complement value */
+ tmp = two_cmp(tmp, ct_bm[id][i] >> __ffs(ct_bm[id][i]));
+ }
+ err[i] = tmp;
+ }
+
+ /* Err value for 150C is set to 0 */
+ err[i] = 0;
+}
+
+static void print_look_up_table(struct device *dev, int *ref_table)
+{
+ int i;
+
+ dev_dbg(dev, "The contents of derived array\n");
+ dev_dbg(dev, "Code Temperature\n");
+ for (i = 0; i < TABLE_SIZE; i++)
+ dev_dbg(dev, "%d %d %d\n", i, derived_table[i], ref_table[i]);
+}
+
+struct k3_j72xx_bandgap_data {
+ unsigned int has_errata_i2128;
+};
+
+static int k3_j72xx_bandgap_probe(struct platform_device *pdev)
+{
+ int ret = 0, cnt, val, id;
+ int high_max, low_temp;
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ struct k3_j72xx_bandgap *bgp;
+ struct k3_thermal_data *data;
+ int workaround_needed = 0;
+ const struct k3_j72xx_bandgap_data *driver_data;
+ struct thermal_zone_device *ti_thermal;
+ int *ref_table;
+ struct err_values err_vals;
+
+ const s64 golden_factors[] = {
+ -490019999999999936,
+ 3251200000000000,
+ -1705800000000,
+ 603730000,
+ -92627,
+ };
+
+ const s64 pvt_wa_factors[] = {
+ -415230000000000000,
+ 3126600000000000,
+ -1157800000000,
+ };
+
+ bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL);
+ if (!bgp)
+ return -ENOMEM;
+
+ bgp->dev = dev;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ bgp->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(bgp->base))
+ return PTR_ERR(bgp->base);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ bgp->cfg2_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(bgp->cfg2_base))
+ return PTR_ERR(bgp->cfg2_base);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ bgp->fuse_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(bgp->fuse_base))
+ return PTR_ERR(bgp->fuse_base);
+
+ driver_data = of_device_get_match_data(dev);
+ if (driver_data)
+ workaround_needed = driver_data->has_errata_i2128;
+
+ pm_runtime_enable(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ pm_runtime_disable(dev);
+ return ret;
+ }
+
+ /* Get the sensor count in the VTM */
+ val = readl(bgp->base + K3_VTM_DEVINFO_PWR0_OFFSET);
+ cnt = val & K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK;
+ cnt >>= __ffs(K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK);
+
+ data = devm_kcalloc(bgp->dev, cnt, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ ref_table = kzalloc(sizeof(*ref_table) * TABLE_SIZE, GFP_KERNEL);
+ if (!ref_table) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ derived_table = devm_kzalloc(bgp->dev, sizeof(*derived_table) * TABLE_SIZE,
+ GFP_KERNEL);
+ if (!derived_table) {
+ ret = -ENOMEM;
+ goto err_free_ref_table;
+ }
+
+ /* Workaround not needed if bit30/bit31 is set even for J721e */
+ if (workaround_needed && (readl(bgp->fuse_base + 0x0) & 0xc0000000) == 0xc0000000)
+ workaround_needed = false;
+
+ dev_dbg(bgp->dev, "Work around %sneeded\n",
+ workaround_needed ? "not " : "");
+
+ if (!workaround_needed)
+ init_table(5, ref_table, golden_factors);
+ else
+ init_table(3, ref_table, pvt_wa_factors);
+
+ /* Register the thermal sensors */
+ for (id = 0; id < cnt; id++) {
+ data[id].bgp = bgp;
+ data[id].ctrl_offset = K3_VTM_TMPSENS0_CTRL_OFFSET + id * 0x20;
+ data[id].stat_offset = data[id].ctrl_offset +
+ K3_VTM_TMPSENS_STAT_OFFSET;
+
+ if (workaround_needed) {
+ /* ref adc values for -40C, 30C & 125C respectively */
+ err_vals.refs[0] = MINUS40CREF;
+ err_vals.refs[1] = PLUS30CREF;
+ err_vals.refs[2] = PLUS125CREF;
+ err_vals.refs[3] = PLUS150CREF;
+ get_efuse_values(id, &data[id], err_vals.errs, bgp);
+ }
+
+ if (id == 0 && workaround_needed)
+ prep_lookup_table(&err_vals, ref_table);
+ else if (id == 0 && !workaround_needed)
+ memcpy(derived_table, ref_table, TABLE_SIZE * 4);
+
+ val = readl(data[id].bgp->cfg2_base + data[id].ctrl_offset);
+ val |= (K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN |
+ K3_VTM_TMPSENS_CTRL_SOC |
+ K3_VTM_TMPSENS_CTRL_CLRZ | BIT(4));
+ writel(val, data[id].bgp->cfg2_base + data[id].ctrl_offset);
+
+ bgp->ts_data[id] = &data[id];
+ ti_thermal = devm_thermal_of_zone_register(bgp->dev, id, &data[id],
+ &k3_of_thermal_ops);
+ if (IS_ERR(ti_thermal)) {
+ dev_err(bgp->dev, "thermal zone device is NULL\n");
+ ret = PTR_ERR(ti_thermal);
+ goto err_free_ref_table;
+ }
+ }
+
+ /*
+ * Program TSHUT thresholds
+ * Step 1: set the thresholds to ~123C and 105C WKUP_VTM_MISC_CTRL2
+ * Step 2: WKUP_VTM_TMPSENS_CTRL_j set the MAXT_OUTRG_EN bit
+ * This is already taken care as per of init
+ * Step 3: WKUP_VTM_MISC_CTRL set the ANYMAXT_OUTRG_ALERT_EN bit
+ */
+ high_max = k3_j72xx_bandgap_temp_to_adc_code(MAX_TEMP);
+ low_temp = k3_j72xx_bandgap_temp_to_adc_code(COOL_DOWN_TEMP);
+
+ writel((low_temp << 16) | high_max, data[0].bgp->cfg2_base +
+ K3_VTM_MISC_CTRL2_OFFSET);
+ mdelay(100);
+ writel(K3_VTM_ANYMAXT_OUTRG_ALERT_EN, data[0].bgp->cfg2_base +
+ K3_VTM_MISC_CTRL_OFFSET);
+
+ platform_set_drvdata(pdev, bgp);
+
+ print_look_up_table(dev, ref_table);
+ /*
+ * Now that the derived_table has the appropriate look up values
+ * Free up the ref_table
+ */
+ kfree(ref_table);
+
+ return 0;
+
+err_free_ref_table:
+ kfree(ref_table);
+
+err_alloc:
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static int k3_j72xx_bandgap_remove(struct platform_device *pdev)
+{
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct k3_j72xx_bandgap_data k3_j72xx_bandgap_j721e_data = {
+ .has_errata_i2128 = 1,
+};
+
+static const struct k3_j72xx_bandgap_data k3_j72xx_bandgap_j7200_data = {
+ .has_errata_i2128 = 0,
+};
+
+static const struct of_device_id of_k3_j72xx_bandgap_match[] = {
+ {
+ .compatible = "ti,j721e-vtm",
+ .data = &k3_j72xx_bandgap_j721e_data,
+ },
+ {
+ .compatible = "ti,j7200-vtm",
+ .data = &k3_j72xx_bandgap_j7200_data,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, of_k3_j72xx_bandgap_match);
+
+static struct platform_driver k3_j72xx_bandgap_sensor_driver = {
+ .probe = k3_j72xx_bandgap_probe,
+ .remove = k3_j72xx_bandgap_remove,
+ .driver = {
+ .name = "k3-j72xx-soc-thermal",
+ .of_match_table = of_k3_j72xx_bandgap_match,
+ },
+};
+
+module_platform_driver(k3_j72xx_bandgap_sensor_driver);
+
+MODULE_DESCRIPTION("K3 bandgap temperature sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>");
diff --git a/drivers/thermal/khadas_mcu_fan.c b/drivers/thermal/khadas_mcu_fan.c
new file mode 100644
index 000000000000..d35e5313bea4
--- /dev/null
+++ b/drivers/thermal/khadas_mcu_fan.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Khadas MCU Controlled FAN driver
+ *
+ * Copyright (C) 2020 BayLibre SAS
+ * Author(s): Neil Armstrong <narmstrong@baylibre.com>
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/khadas-mcu.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/thermal.h>
+
+#define MAX_LEVEL 3
+
+struct khadas_mcu_fan_ctx {
+ struct khadas_mcu *mcu;
+ unsigned int level;
+ struct thermal_cooling_device *cdev;
+};
+
+static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
+ unsigned int level)
+{
+ int ret;
+
+ ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
+ level);
+ if (ret)
+ return ret;
+
+ ctx->level = level;
+
+ return 0;
+}
+
+static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ *state = MAX_LEVEL;
+
+ return 0;
+}
+
+static int khadas_mcu_fan_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+ *state = ctx->level;
+
+ return 0;
+}
+
+static int
+khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+ if (state > MAX_LEVEL)
+ return -EINVAL;
+
+ if (state == ctx->level)
+ return 0;
+
+ return khadas_mcu_fan_set_level(ctx, state);
+}
+
+static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
+ .get_max_state = khadas_mcu_fan_get_max_state,
+ .get_cur_state = khadas_mcu_fan_get_cur_state,
+ .set_cur_state = khadas_mcu_fan_set_cur_state,
+};
+
+static int khadas_mcu_fan_probe(struct platform_device *pdev)
+{
+ struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
+ struct thermal_cooling_device *cdev;
+ struct device *dev = &pdev->dev;
+ struct khadas_mcu_fan_ctx *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ ctx->mcu = mcu;
+ platform_set_drvdata(pdev, ctx);
+
+ cdev = devm_thermal_of_cooling_device_register(dev->parent,
+ dev->parent->of_node, "khadas-mcu-fan", ctx,
+ &khadas_mcu_fan_cooling_ops);
+ if (IS_ERR(cdev)) {
+ ret = PTR_ERR(cdev);
+ dev_err(dev, "Failed to register khadas-mcu-fan as cooling device: %d\n",
+ ret);
+ return ret;
+ }
+ ctx->cdev = cdev;
+
+ return 0;
+}
+
+static void khadas_mcu_fan_shutdown(struct platform_device *pdev)
+{
+ struct khadas_mcu_fan_ctx *ctx = platform_get_drvdata(pdev);
+
+ khadas_mcu_fan_set_level(ctx, 0);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int khadas_mcu_fan_suspend(struct device *dev)
+{
+ struct khadas_mcu_fan_ctx *ctx = dev_get_drvdata(dev);
+ unsigned int level_save = ctx->level;
+ int ret;
+
+ ret = khadas_mcu_fan_set_level(ctx, 0);
+ if (ret)
+ return ret;
+
+ ctx->level = level_save;
+
+ return 0;
+}
+
+static int khadas_mcu_fan_resume(struct device *dev)
+{
+ struct khadas_mcu_fan_ctx *ctx = dev_get_drvdata(dev);
+
+ return khadas_mcu_fan_set_level(ctx, ctx->level);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(khadas_mcu_fan_pm, khadas_mcu_fan_suspend,
+ khadas_mcu_fan_resume);
+
+static const struct platform_device_id khadas_mcu_fan_id_table[] = {
+ { .name = "khadas-mcu-fan-ctrl", },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, khadas_mcu_fan_id_table);
+
+static struct platform_driver khadas_mcu_fan_driver = {
+ .probe = khadas_mcu_fan_probe,
+ .shutdown = khadas_mcu_fan_shutdown,
+ .driver = {
+ .name = "khadas-mcu-fan-ctrl",
+ .pm = &khadas_mcu_fan_pm,
+ },
+ .id_table = khadas_mcu_fan_id_table,
+};
+
+module_platform_driver(khadas_mcu_fan_driver);
+
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Khadas MCU FAN driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/kirkwood_thermal.c b/drivers/thermal/kirkwood_thermal.c
index 189b675cf14d..7fb6e476c82a 100644
--- a/drivers/thermal/kirkwood_thermal.c
+++ b/drivers/thermal/kirkwood_thermal.c
@@ -65,6 +65,7 @@ static int kirkwood_thermal_probe(struct platform_device *pdev)
struct thermal_zone_device *thermal = NULL;
struct kirkwood_thermal_priv *priv;
struct resource *res;
+ int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -82,6 +83,12 @@ static int kirkwood_thermal_probe(struct platform_device *pdev)
"Failed to register thermal zone device\n");
return PTR_ERR(thermal);
}
+ ret = thermal_zone_device_enable(thermal);
+ if (ret) {
+ thermal_zone_device_unregister(thermal);
+ dev_err(&pdev->dev, "Failed to enable thermal zone device\n");
+ return ret;
+ }
platform_set_drvdata(pdev, thermal);
diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c
index 82d06c7411eb..6451a55eb582 100644
--- a/drivers/thermal/max77620_thermal.c
+++ b/drivers/thermal/max77620_thermal.c
@@ -44,9 +44,9 @@ struct max77620_therm_info {
* Return 0 on success otherwise error number to show reason of failure.
*/
-static int max77620_thermal_read_temp(void *data, int *temp)
+static int max77620_thermal_read_temp(struct thermal_zone_device *tz, int *temp)
{
- struct max77620_therm_info *mtherm = data;
+ struct max77620_therm_info *mtherm = tz->devdata;
unsigned int val;
int ret;
@@ -66,7 +66,7 @@ static int max77620_thermal_read_temp(void *data, int *temp)
return 0;
}
-static const struct thermal_zone_of_device_ops max77620_thermal_ops = {
+static const struct thermal_zone_device_ops max77620_thermal_ops = {
.get_temp = max77620_thermal_read_temp,
};
@@ -114,7 +114,7 @@ static int max77620_thermal_probe(struct platform_device *pdev)
*/
device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent);
- mtherm->tz_device = devm_thermal_zone_of_sensor_register(&pdev->dev, 0,
+ mtherm->tz_device = devm_thermal_of_zone_register(&pdev->dev, 0,
mtherm, &max77620_thermal_ops);
if (IS_ERR(mtherm->tz_device)) {
ret = PTR_ERR(mtherm->tz_device);
diff --git a/drivers/thermal/mtk_thermal.c b/drivers/thermal/mtk_thermal.c
index 76e30603d4d5..8440692e3890 100644
--- a/drivers/thermal/mtk_thermal.c
+++ b/drivers/thermal/mtk_thermal.c
@@ -23,6 +23,8 @@
#include <linux/reset.h>
#include <linux/types.h>
+#include "thermal_hwmon.h"
+
/* AUXADC Registers */
#define AUXADC_CON1_SET_V 0x008
#define AUXADC_CON1_CLR_V 0x00c
@@ -38,6 +40,7 @@
#define TEMP_MONIDET0 0x014
#define TEMP_MONIDET1 0x018
#define TEMP_MSRCTL0 0x038
+#define TEMP_MSRCTL1 0x03c
#define TEMP_AHBPOLL 0x040
#define TEMP_AHBTO 0x044
#define TEMP_ADCPNP0 0x048
@@ -120,18 +123,32 @@
* MT2701 has 3 sensors and needs 3 VTS calibration data.
* MT2712 has 4 sensors and needs 4 VTS calibration data.
*/
-#define CALIB_BUF0_VALID BIT(0)
-#define CALIB_BUF1_ADC_GE(x) (((x) >> 22) & 0x3ff)
-#define CALIB_BUF0_VTS_TS1(x) (((x) >> 17) & 0x1ff)
-#define CALIB_BUF0_VTS_TS2(x) (((x) >> 8) & 0x1ff)
-#define CALIB_BUF1_VTS_TS3(x) (((x) >> 0) & 0x1ff)
-#define CALIB_BUF2_VTS_TS4(x) (((x) >> 23) & 0x1ff)
-#define CALIB_BUF2_VTS_TS5(x) (((x) >> 5) & 0x1ff)
-#define CALIB_BUF2_VTS_TSABB(x) (((x) >> 14) & 0x1ff)
-#define CALIB_BUF0_DEGC_CALI(x) (((x) >> 1) & 0x3f)
-#define CALIB_BUF0_O_SLOPE(x) (((x) >> 26) & 0x3f)
-#define CALIB_BUF0_O_SLOPE_SIGN(x) (((x) >> 7) & 0x1)
-#define CALIB_BUF1_ID(x) (((x) >> 9) & 0x1)
+#define CALIB_BUF0_VALID_V1 BIT(0)
+#define CALIB_BUF1_ADC_GE_V1(x) (((x) >> 22) & 0x3ff)
+#define CALIB_BUF0_VTS_TS1_V1(x) (((x) >> 17) & 0x1ff)
+#define CALIB_BUF0_VTS_TS2_V1(x) (((x) >> 8) & 0x1ff)
+#define CALIB_BUF1_VTS_TS3_V1(x) (((x) >> 0) & 0x1ff)
+#define CALIB_BUF2_VTS_TS4_V1(x) (((x) >> 23) & 0x1ff)
+#define CALIB_BUF2_VTS_TS5_V1(x) (((x) >> 5) & 0x1ff)
+#define CALIB_BUF2_VTS_TSABB_V1(x) (((x) >> 14) & 0x1ff)
+#define CALIB_BUF0_DEGC_CALI_V1(x) (((x) >> 1) & 0x3f)
+#define CALIB_BUF0_O_SLOPE_V1(x) (((x) >> 26) & 0x3f)
+#define CALIB_BUF0_O_SLOPE_SIGN_V1(x) (((x) >> 7) & 0x1)
+#define CALIB_BUF1_ID_V1(x) (((x) >> 9) & 0x1)
+
+/*
+ * Layout of the fuses providing the calibration data
+ * These macros could be used for MT7622.
+ */
+#define CALIB_BUF0_ADC_OE_V2(x) (((x) >> 22) & 0x3ff)
+#define CALIB_BUF0_ADC_GE_V2(x) (((x) >> 12) & 0x3ff)
+#define CALIB_BUF0_DEGC_CALI_V2(x) (((x) >> 6) & 0x3f)
+#define CALIB_BUF0_O_SLOPE_V2(x) (((x) >> 0) & 0x3f)
+#define CALIB_BUF1_VTS_TS1_V2(x) (((x) >> 23) & 0x1ff)
+#define CALIB_BUF1_VTS_TS2_V2(x) (((x) >> 14) & 0x1ff)
+#define CALIB_BUF1_VTS_TSABB_V2(x) (((x) >> 5) & 0x1ff)
+#define CALIB_BUF1_VALID_V2(x) (((x) >> 4) & 0x1)
+#define CALIB_BUF1_O_SLOPE_SIGN_V2(x) (((x) >> 3) & 0x1)
enum {
VTS1,
@@ -143,6 +160,11 @@ enum {
MAX_NUM_VTS,
};
+enum mtk_thermal_version {
+ MTK_THERMAL_V1 = 1,
+ MTK_THERMAL_V2,
+};
+
/* MT2701 thermal sensors */
#define MT2701_TS1 0
#define MT2701_TS2 1
@@ -211,6 +233,9 @@ enum {
/* The total number of temperature sensors in the MT8183 */
#define MT8183_NUM_SENSORS 6
+/* The number of banks in the MT8183 */
+#define MT8183_NUM_ZONES 1
+
/* The number of sensing points per bank */
#define MT8183_NUM_SENSORS_PER_ZONE 6
@@ -245,6 +270,7 @@ struct mtk_thermal_data {
const int *controller_offset;
bool need_switch_bank;
struct thermal_bank_cfg bank_data[MAX_NUM_ZONES];
+ enum mtk_thermal_version version;
};
struct mtk_thermal {
@@ -258,8 +284,10 @@ struct mtk_thermal {
/* Calibration values */
s32 adc_ge;
+ s32 adc_oe;
s32 degc_cali;
s32 o_slope;
+ s32 o_slope_sign;
s32 vts[MAX_NUM_VTS];
const struct mtk_thermal_data *conf;
@@ -398,6 +426,7 @@ static const struct mtk_thermal_data mt8173_thermal_data = {
.msr = mt8173_msr,
.adcpnp = mt8173_adcpnp,
.sensor_mux_values = mt8173_mux_values,
+ .version = MTK_THERMAL_V1,
};
/*
@@ -428,6 +457,7 @@ static const struct mtk_thermal_data mt2701_thermal_data = {
.msr = mt2701_msr,
.adcpnp = mt2701_adcpnp,
.sensor_mux_values = mt2701_mux_values,
+ .version = MTK_THERMAL_V1,
};
/*
@@ -458,6 +488,7 @@ static const struct mtk_thermal_data mt2712_thermal_data = {
.msr = mt2712_msr,
.adcpnp = mt2712_adcpnp,
.sensor_mux_values = mt2712_mux_values,
+ .version = MTK_THERMAL_V1,
};
/*
@@ -482,6 +513,7 @@ static const struct mtk_thermal_data mt7622_thermal_data = {
.msr = mt7622_msr,
.adcpnp = mt7622_adcpnp,
.sensor_mux_values = mt7622_mux_values,
+ .version = MTK_THERMAL_V2,
};
/*
@@ -497,7 +529,7 @@ static const struct mtk_thermal_data mt7622_thermal_data = {
*/
static const struct mtk_thermal_data mt8183_thermal_data = {
.auxadc_channel = MT8183_TEMP_AUXADC_CHANNEL,
- .num_banks = MT8183_NUM_SENSORS_PER_ZONE,
+ .num_banks = MT8183_NUM_ZONES,
.num_sensors = MT8183_NUM_SENSORS,
.vts_index = mt8183_vts_index,
.cali_val = MT8183_CALIBRATION,
@@ -514,6 +546,7 @@ static const struct mtk_thermal_data mt8183_thermal_data = {
.msr = mt8183_msr,
.adcpnp = mt8183_adcpnp,
.sensor_mux_values = mt8183_mux_values,
+ .version = MTK_THERMAL_V1,
};
/**
@@ -525,7 +558,7 @@ static const struct mtk_thermal_data mt8183_thermal_data = {
* This converts the raw ADC value to mcelsius using the SoC specific
* calibration constants
*/
-static int raw_to_mcelsius(struct mtk_thermal *mt, int sensno, s32 raw)
+static int raw_to_mcelsius_v1(struct mtk_thermal *mt, int sensno, s32 raw)
{
s32 tmp;
@@ -540,6 +573,36 @@ static int raw_to_mcelsius(struct mtk_thermal *mt, int sensno, s32 raw)
return mt->degc_cali * 500 - tmp;
}
+static int raw_to_mcelsius_v2(struct mtk_thermal *mt, int sensno, s32 raw)
+{
+ s32 format_1;
+ s32 format_2;
+ s32 g_oe;
+ s32 g_gain;
+ s32 g_x_roomt;
+ s32 tmp;
+
+ if (raw == 0)
+ return 0;
+
+ raw &= 0xfff;
+ g_gain = 10000 + (((mt->adc_ge - 512) * 10000) >> 12);
+ g_oe = mt->adc_oe - 512;
+ format_1 = mt->vts[VTS2] + 3105 - g_oe;
+ format_2 = (mt->degc_cali * 10) >> 1;
+ g_x_roomt = (((format_1 * 10000) >> 12) * 10000) / g_gain;
+
+ tmp = (((((raw - g_oe) * 10000) >> 12) * 10000) / g_gain) - g_x_roomt;
+ tmp = tmp * 10 * 100 / 11;
+
+ if (mt->o_slope_sign == 0)
+ tmp = tmp / (165 - mt->o_slope);
+ else
+ tmp = tmp / (165 + mt->o_slope);
+
+ return (format_2 - tmp) * 100;
+}
+
/**
* mtk_thermal_get_bank - get bank
* @bank: The bank
@@ -591,12 +654,15 @@ static int mtk_thermal_bank_temperature(struct mtk_thermal_bank *bank)
u32 raw;
for (i = 0; i < conf->bank_data[bank->id].num_sensors; i++) {
- raw = readl(mt->thermal_base +
- conf->msr[conf->bank_data[bank->id].sensors[i]]);
-
- temp = raw_to_mcelsius(mt,
- conf->bank_data[bank->id].sensors[i],
- raw);
+ raw = readl(mt->thermal_base + conf->msr[i]);
+
+ if (mt->conf->version == MTK_THERMAL_V1) {
+ temp = raw_to_mcelsius_v1(
+ mt, conf->bank_data[bank->id].sensors[i], raw);
+ } else {
+ temp = raw_to_mcelsius_v2(
+ mt, conf->bank_data[bank->id].sensors[i], raw);
+ }
/*
* The first read of a sensor often contains very high bogus
@@ -613,9 +679,9 @@ static int mtk_thermal_bank_temperature(struct mtk_thermal_bank *bank)
return max;
}
-static int mtk_read_temp(void *data, int *temperature)
+static int mtk_read_temp(struct thermal_zone_device *tz, int *temperature)
{
- struct mtk_thermal *mt = data;
+ struct mtk_thermal *mt = tz->devdata;
int i;
int tempmax = INT_MIN;
@@ -634,7 +700,7 @@ static int mtk_read_temp(void *data, int *temperature)
return 0;
}
-static const struct thermal_zone_of_device_ops mtk_thermal_ops = {
+static const struct thermal_zone_device_ops mtk_thermal_ops = {
.get_temp = mtk_read_temp,
};
@@ -698,9 +764,11 @@ static void mtk_thermal_init_bank(struct mtk_thermal *mt, int num,
writel(auxadc_phys_base + AUXADC_CON1_CLR_V,
controller_base + TEMP_ADCMUXADDR);
- /* AHB address for pnp sensor mux selection */
- writel(apmixed_phys_base + APMIXED_SYS_TS_CON1,
- controller_base + TEMP_PNPMUXADDR);
+ if (mt->conf->version == MTK_THERMAL_V1) {
+ /* AHB address for pnp sensor mux selection */
+ writel(apmixed_phys_base + APMIXED_SYS_TS_CON1,
+ controller_base + TEMP_PNPMUXADDR);
+ }
/* AHB value for auxadc enable */
writel(BIT(conf->auxadc_channel), controller_base + TEMP_ADCEN);
@@ -733,8 +801,7 @@ static void mtk_thermal_init_bank(struct mtk_thermal *mt, int num,
for (i = 0; i < conf->bank_data[num].num_sensors; i++)
writel(conf->sensor_mux_values[conf->bank_data[num].sensors[i]],
- mt->thermal_base +
- conf->adcpnp[conf->bank_data[num].sensors[i]]);
+ mt->thermal_base + conf->adcpnp[i]);
writel((1 << conf->bank_data[num].num_sensors) - 1,
controller_base + TEMP_MONCTL0);
@@ -758,6 +825,68 @@ static u64 of_get_phys_base(struct device_node *np)
return of_translate_address(np, regaddr_p);
}
+static int mtk_thermal_extract_efuse_v1(struct mtk_thermal *mt, u32 *buf)
+{
+ int i;
+
+ if (!(buf[0] & CALIB_BUF0_VALID_V1))
+ return -EINVAL;
+
+ mt->adc_ge = CALIB_BUF1_ADC_GE_V1(buf[1]);
+
+ for (i = 0; i < mt->conf->num_sensors; i++) {
+ switch (mt->conf->vts_index[i]) {
+ case VTS1:
+ mt->vts[VTS1] = CALIB_BUF0_VTS_TS1_V1(buf[0]);
+ break;
+ case VTS2:
+ mt->vts[VTS2] = CALIB_BUF0_VTS_TS2_V1(buf[0]);
+ break;
+ case VTS3:
+ mt->vts[VTS3] = CALIB_BUF1_VTS_TS3_V1(buf[1]);
+ break;
+ case VTS4:
+ mt->vts[VTS4] = CALIB_BUF2_VTS_TS4_V1(buf[2]);
+ break;
+ case VTS5:
+ mt->vts[VTS5] = CALIB_BUF2_VTS_TS5_V1(buf[2]);
+ break;
+ case VTSABB:
+ mt->vts[VTSABB] =
+ CALIB_BUF2_VTS_TSABB_V1(buf[2]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ mt->degc_cali = CALIB_BUF0_DEGC_CALI_V1(buf[0]);
+ if (CALIB_BUF1_ID_V1(buf[1]) &
+ CALIB_BUF0_O_SLOPE_SIGN_V1(buf[0]))
+ mt->o_slope = -CALIB_BUF0_O_SLOPE_V1(buf[0]);
+ else
+ mt->o_slope = CALIB_BUF0_O_SLOPE_V1(buf[0]);
+
+ return 0;
+}
+
+static int mtk_thermal_extract_efuse_v2(struct mtk_thermal *mt, u32 *buf)
+{
+ if (!CALIB_BUF1_VALID_V2(buf[1]))
+ return -EINVAL;
+
+ mt->adc_oe = CALIB_BUF0_ADC_OE_V2(buf[0]);
+ mt->adc_ge = CALIB_BUF0_ADC_GE_V2(buf[0]);
+ mt->degc_cali = CALIB_BUF0_DEGC_CALI_V2(buf[0]);
+ mt->o_slope = CALIB_BUF0_O_SLOPE_V2(buf[0]);
+ mt->vts[VTS1] = CALIB_BUF1_VTS_TS1_V2(buf[1]);
+ mt->vts[VTS2] = CALIB_BUF1_VTS_TS2_V2(buf[1]);
+ mt->vts[VTSABB] = CALIB_BUF1_VTS_TSABB_V2(buf[1]);
+ mt->o_slope_sign = CALIB_BUF1_O_SLOPE_SIGN_V2(buf[1]);
+
+ return 0;
+}
+
static int mtk_thermal_get_calibration_data(struct device *dev,
struct mtk_thermal *mt)
{
@@ -793,42 +922,14 @@ static int mtk_thermal_get_calibration_data(struct device *dev,
goto out;
}
- if (buf[0] & CALIB_BUF0_VALID) {
- mt->adc_ge = CALIB_BUF1_ADC_GE(buf[1]);
-
- for (i = 0; i < mt->conf->num_sensors; i++) {
- switch (mt->conf->vts_index[i]) {
- case VTS1:
- mt->vts[VTS1] = CALIB_BUF0_VTS_TS1(buf[0]);
- break;
- case VTS2:
- mt->vts[VTS2] = CALIB_BUF0_VTS_TS2(buf[0]);
- break;
- case VTS3:
- mt->vts[VTS3] = CALIB_BUF1_VTS_TS3(buf[1]);
- break;
- case VTS4:
- mt->vts[VTS4] = CALIB_BUF2_VTS_TS4(buf[2]);
- break;
- case VTS5:
- mt->vts[VTS5] = CALIB_BUF2_VTS_TS5(buf[2]);
- break;
- case VTSABB:
- mt->vts[VTSABB] = CALIB_BUF2_VTS_TSABB(buf[2]);
- break;
- default:
- break;
- }
- }
+ if (mt->conf->version == MTK_THERMAL_V1)
+ ret = mtk_thermal_extract_efuse_v1(mt, buf);
+ else
+ ret = mtk_thermal_extract_efuse_v2(mt, buf);
- mt->degc_cali = CALIB_BUF0_DEGC_CALI(buf[0]);
- if (CALIB_BUF1_ID(buf[1]) &
- CALIB_BUF0_O_SLOPE_SIGN(buf[0]))
- mt->o_slope = -CALIB_BUF0_O_SLOPE(buf[0]);
- else
- mt->o_slope = CALIB_BUF0_O_SLOPE(buf[0]);
- } else {
+ if (ret) {
dev_info(dev, "Device not calibrated, using default calibration values\n");
+ ret = 0;
}
out:
@@ -862,6 +963,28 @@ static const struct of_device_id mtk_thermal_of_match[] = {
};
MODULE_DEVICE_TABLE(of, mtk_thermal_of_match);
+static void mtk_thermal_turn_on_buffer(void __iomem *apmixed_base)
+{
+ int tmp;
+
+ tmp = readl(apmixed_base + APMIXED_SYS_TS_CON1);
+ tmp &= ~(0x37);
+ tmp |= 0x1;
+ writel(tmp, apmixed_base + APMIXED_SYS_TS_CON1);
+ udelay(200);
+}
+
+static void mtk_thermal_release_periodic_ts(struct mtk_thermal *mt,
+ void __iomem *auxadc_base)
+{
+ int tmp;
+
+ writel(0x800, auxadc_base + AUXADC_CON1_SET_V);
+ writel(0x1, mt->thermal_base + TEMP_MONCTL0);
+ tmp = readl(mt->thermal_base + TEMP_MSRCTL1);
+ writel((tmp & (~0x10e)), mt->thermal_base + TEMP_MSRCTL1);
+}
+
static int mtk_thermal_probe(struct platform_device *pdev)
{
int ret, i, ctrl_id;
@@ -870,6 +993,7 @@ static int mtk_thermal_probe(struct platform_device *pdev)
struct resource *res;
u64 auxadc_phys_base, apmixed_phys_base;
struct thermal_zone_device *tzdev;
+ void __iomem *apmixed_base, *auxadc_base;
mt = devm_kzalloc(&pdev->dev, sizeof(*mt), GFP_KERNEL);
if (!mt)
@@ -904,6 +1028,7 @@ static int mtk_thermal_probe(struct platform_device *pdev)
return -ENODEV;
}
+ auxadc_base = of_iomap(auxadc, 0);
auxadc_phys_base = of_get_phys_base(auxadc);
of_node_put(auxadc);
@@ -919,6 +1044,7 @@ static int mtk_thermal_probe(struct platform_device *pdev)
return -ENODEV;
}
+ apmixed_base = of_iomap(apmixedsys, 0);
apmixed_phys_base = of_get_phys_base(apmixedsys);
of_node_put(apmixedsys);
@@ -928,7 +1054,7 @@ static int mtk_thermal_probe(struct platform_device *pdev)
return -EINVAL;
}
- ret = device_reset(&pdev->dev);
+ ret = device_reset_optional(&pdev->dev);
if (ret)
return ret;
@@ -944,6 +1070,11 @@ static int mtk_thermal_probe(struct platform_device *pdev)
goto err_disable_clk_auxadc;
}
+ if (mt->conf->version == MTK_THERMAL_V2) {
+ mtk_thermal_turn_on_buffer(apmixed_base);
+ mtk_thermal_release_periodic_ts(mt, auxadc_base);
+ }
+
for (ctrl_id = 0; ctrl_id < mt->conf->num_controller ; ctrl_id++)
for (i = 0; i < mt->conf->num_banks; i++)
mtk_thermal_init_bank(mt, i, apmixed_phys_base,
@@ -951,13 +1082,17 @@ static int mtk_thermal_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, mt);
- tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, mt,
- &mtk_thermal_ops);
+ tzdev = devm_thermal_of_zone_register(&pdev->dev, 0, mt,
+ &mtk_thermal_ops);
if (IS_ERR(tzdev)) {
ret = PTR_ERR(tzdev);
goto err_disable_clk_peri_therm;
}
+ ret = devm_thermal_add_hwmon_sysfs(tzdev);
+ if (ret)
+ dev_warn(&pdev->dev, "error in thermal_add_hwmon_sysfs");
+
return 0;
err_disable_clk_peri_therm:
diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c
deleted file mode 100644
index ef0baa954ff0..000000000000
--- a/drivers/thermal/of-thermal.c
+++ /dev/null
@@ -1,1121 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * of-thermal.c - Generic Thermal Management device tree support.
- *
- * Copyright (C) 2013 Texas Instruments
- * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#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_cooling_bind_param - a cooling device for a trip point
- * @cooling_device: a pointer to identify the referred cooling device
- * @min: minimum cooling state used at this trip point
- * @max: maximum cooling state used at this trip point
- */
-
-struct __thermal_cooling_bind_param {
- struct device_node *cooling_device;
- unsigned long min;
- unsigned long max;
-};
-
-/**
- * struct __thermal_bind_param - a match between trip and cooling device
- * @tcbp: a pointer to an array of cooling devices
- * @count: number of elements in array
- * @trip_id: the trip point index
- * @usage: the percentage (from 0 to 100) of cooling contribution
- */
-
-struct __thermal_bind_params {
- struct __thermal_cooling_bind_param *tcbp;
- unsigned int count;
- unsigned int trip_id;
- unsigned int usage;
-};
-
-/**
- * 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
- * @slope: slope of the temperature adjustment curve
- * @offset: offset of the temperature adjustment curve
- * @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
- * @ops: set of callbacks to handle the thermal zone based on DT
- */
-
-struct __thermal_zone {
- enum thermal_device_mode mode;
- int passive_delay;
- int polling_delay;
- int slope;
- int offset;
-
- /* trip data */
- int ntrips;
- struct thermal_trip *trips;
-
- /* cooling binding data */
- int num_tbps;
- struct __thermal_bind_params *tbps;
-
- /* sensor interface */
- void *sensor_data;
- const struct thermal_zone_of_device_ops *ops;
-};
-
-/*** DT thermal zone device callbacks ***/
-
-static int of_thermal_get_temp(struct thermal_zone_device *tz,
- int *temp)
-{
- struct __thermal_zone *data = tz->devdata;
-
- if (!data->ops->get_temp)
- return -EINVAL;
-
- return data->ops->get_temp(data->sensor_data, temp);
-}
-
-static int of_thermal_set_trips(struct thermal_zone_device *tz,
- int low, int high)
-{
- struct __thermal_zone *data = tz->devdata;
-
- if (!data->ops || !data->ops->set_trips)
- return -EINVAL;
-
- return data->ops->set_trips(data->sensor_data, low, high);
-}
-
-/**
- * of_thermal_get_ntrips - function to export number of available trip
- * points.
- * @tz: pointer to a thermal zone
- *
- * This function is a globally visible wrapper to get number of trip points
- * stored in the local struct __thermal_zone
- *
- * Return: number of available trip points, -ENODEV when data not available
- */
-int of_thermal_get_ntrips(struct thermal_zone_device *tz)
-{
- struct __thermal_zone *data = tz->devdata;
-
- if (!data || IS_ERR(data))
- return -ENODEV;
-
- return data->ntrips;
-}
-EXPORT_SYMBOL_GPL(of_thermal_get_ntrips);
-
-/**
- * of_thermal_is_trip_valid - function to check if trip point is valid
- *
- * @tz: pointer to a thermal zone
- * @trip: trip point to evaluate
- *
- * This function is responsible for checking if passed trip point is valid
- *
- * Return: true if trip point is valid, false otherwise
- */
-bool of_thermal_is_trip_valid(struct thermal_zone_device *tz, int trip)
-{
- struct __thermal_zone *data = tz->devdata;
-
- if (!data || trip >= data->ntrips || trip < 0)
- return false;
-
- return true;
-}
-EXPORT_SYMBOL_GPL(of_thermal_is_trip_valid);
-
-/**
- * of_thermal_get_trip_points - function to get access to a globally exported
- * trip points
- *
- * @tz: pointer to a thermal zone
- *
- * This function provides a pointer to trip points table
- *
- * Return: pointer to trip points table, NULL otherwise
- */
-const struct thermal_trip *
-of_thermal_get_trip_points(struct thermal_zone_device *tz)
-{
- struct __thermal_zone *data = tz->devdata;
-
- if (!data)
- return NULL;
-
- return data->trips;
-}
-EXPORT_SYMBOL_GPL(of_thermal_get_trip_points);
-
-/**
- * of_thermal_set_emul_temp - function to set emulated temperature
- *
- * @tz: pointer to a thermal zone
- * @temp: temperature to set
- *
- * This function gives the ability to set emulated value of temperature,
- * which is handy for debugging
- *
- * Return: zero on success, error code otherwise
- */
-static int of_thermal_set_emul_temp(struct thermal_zone_device *tz,
- int temp)
-{
- struct __thermal_zone *data = tz->devdata;
-
- return data->ops->set_emul_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;
-
- if (!data->ops->get_trend)
- return -EINVAL;
-
- return data->ops->get_trend(data->sensor_data, trip, trend);
-}
-
-static int of_thermal_bind(struct thermal_zone_device *thermal,
- struct thermal_cooling_device *cdev)
-{
- struct __thermal_zone *data = thermal->devdata;
- struct __thermal_bind_params *tbp;
- struct __thermal_cooling_bind_param *tcbp;
- int i, j;
-
- if (!data || IS_ERR(data))
- return -ENODEV;
-
- /* find where to bind */
- for (i = 0; i < data->num_tbps; i++) {
- tbp = data->tbps + i;
-
- for (j = 0; j < tbp->count; j++) {
- tcbp = tbp->tcbp + j;
-
- if (tcbp->cooling_device == cdev->np) {
- int ret;
-
- ret = thermal_zone_bind_cooling_device(thermal,
- tbp->trip_id, cdev,
- tcbp->max,
- tcbp->min,
- tbp->usage);
- 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;
- struct __thermal_bind_params *tbp;
- struct __thermal_cooling_bind_param *tcbp;
- int i, j;
-
- if (!data || IS_ERR(data))
- return -ENODEV;
-
- /* find where to unbind */
- for (i = 0; i < data->num_tbps; i++) {
- tbp = data->tbps + i;
-
- for (j = 0; j < tbp->count; j++) {
- tcbp = tbp->tcbp + j;
-
- if (tcbp->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;
- tz->passive_delay = data->passive_delay;
- } else {
- tz->polling_delay = 0;
- tz->passive_delay = 0;
- }
-
- mutex_unlock(&tz->lock);
-
- data->mode = mode;
- thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
-
- 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,
- int *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,
- int temp)
-{
- struct __thermal_zone *data = tz->devdata;
-
- if (trip >= data->ntrips || trip < 0)
- return -EDOM;
-
- if (data->ops->set_trip_temp) {
- int ret;
-
- ret = data->ops->set_trip_temp(data->sensor_data, trip, temp);
- if (ret)
- return ret;
- }
-
- /* 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,
- int *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,
- int 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,
- int *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,
- const struct thermal_zone_of_device_ops *ops)
-{
- 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;
-
- if (!ops)
- return ERR_PTR(-EINVAL);
-
- mutex_lock(&tzd->lock);
- tz->ops = ops;
- tz->sensor_data = data;
-
- tzd->ops->get_temp = of_thermal_get_temp;
- tzd->ops->get_trend = of_thermal_get_trend;
-
- /*
- * The thermal zone core will calculate the window if they have set the
- * optional set_trips pointer.
- */
- if (ops->set_trips)
- tzd->ops->set_trips = of_thermal_set_trips;
-
- if (ops->set_emul_temp)
- tzd->ops->set_emul_temp = of_thermal_set_emul_temp;
-
- 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.
- * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp.
- *
- * 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,
- const struct thermal_zone_of_device_ops *ops)
-{
- struct device_node *np, *child, *sensor_np;
- struct thermal_zone_device *tzd = ERR_PTR(-ENODEV);
-
- np = of_find_node_by_name(NULL, "thermal-zones");
- if (!np)
- return ERR_PTR(-ENODEV);
-
- if (!dev || !dev->of_node) {
- of_node_put(np);
- return ERR_PTR(-ENODEV);
- }
-
- sensor_np = of_node_get(dev->of_node);
-
- for_each_available_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,
- "%pOFn: too many cells in sensor specifier %d\n",
- sensor_specs.np, sensor_specs.args_count);
- } else {
- id = 0;
- }
-
- if (sensor_specs.np == sensor_np && id == sensor_id) {
- tzd = thermal_zone_of_add_sensor(child, sensor_np,
- data, ops);
- if (!IS_ERR(tzd))
- tzd->ops->set_mode(tzd, THERMAL_DEVICE_ENABLED);
-
- of_node_put(sensor_specs.np);
- of_node_put(child);
- goto exit;
- }
- of_node_put(sensor_specs.np);
- }
-exit:
- of_node_put(sensor_np);
- of_node_put(np);
-
- return tzd;
-}
-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;
- tzd->ops->set_emul_temp = NULL;
-
- tz->ops = NULL;
- tz->sensor_data = NULL;
- mutex_unlock(&tzd->lock);
-}
-EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_unregister);
-
-static void devm_thermal_zone_of_sensor_release(struct device *dev, void *res)
-{
- thermal_zone_of_sensor_unregister(dev,
- *(struct thermal_zone_device **)res);
-}
-
-static int devm_thermal_zone_of_sensor_match(struct device *dev, void *res,
- void *data)
-{
- struct thermal_zone_device **r = res;
-
- if (WARN_ON(!r || !*r))
- return 0;
-
- return *r == data;
-}
-
-/**
- * devm_thermal_zone_of_sensor_register - Resource managed version of
- * thermal_zone_of_sensor_register()
- * @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.
- * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp.
- *
- * Refer thermal_zone_of_sensor_register() for more details.
- *
- * 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.
- * Registered thermal_zone_device device will automatically be
- * released when device is unbounded.
- */
-struct thermal_zone_device *devm_thermal_zone_of_sensor_register(
- struct device *dev, int sensor_id,
- void *data, const struct thermal_zone_of_device_ops *ops)
-{
- struct thermal_zone_device **ptr, *tzd;
-
- ptr = devres_alloc(devm_thermal_zone_of_sensor_release, sizeof(*ptr),
- GFP_KERNEL);
- if (!ptr)
- return ERR_PTR(-ENOMEM);
-
- tzd = thermal_zone_of_sensor_register(dev, sensor_id, data, ops);
- if (IS_ERR(tzd)) {
- devres_free(ptr);
- return tzd;
- }
-
- *ptr = tzd;
- devres_add(dev, ptr);
-
- return tzd;
-}
-EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_register);
-
-/**
- * devm_thermal_zone_of_sensor_unregister - Resource managed version of
- * thermal_zone_of_sensor_unregister().
- * @dev: Device for which which resource was allocated.
- * @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 devm_thermal_zone_of_sensor_register()
- * API. It will also silent the zone by remove the .get_temp() and .get_trend()
- * thermal zone device callbacks.
- * Normally this function will not need to be called and the resource
- * management code will ensure that the resource is freed.
- */
-void devm_thermal_zone_of_sensor_unregister(struct device *dev,
- struct thermal_zone_device *tzd)
-{
- WARN_ON(devres_release(dev, devm_thermal_zone_of_sensor_release,
- devm_thermal_zone_of_sensor_match, tzd));
-}
-EXPORT_SYMBOL_GPL(devm_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 __thermal_cooling_bind_param *__tcbp;
- struct device_node *trip;
- int ret, i, count;
- u32 prop;
-
- /* Default weight. Usage is optional */
- __tbp->usage = THERMAL_WEIGHT_DEFAULT;
- 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;
- }
-
- count = of_count_phandle_with_args(np, "cooling-device",
- "#cooling-cells");
- if (!count) {
- pr_err("Add a cooling_device property with at least one device\n");
- goto end;
- }
-
- __tcbp = kcalloc(count, sizeof(*__tcbp), GFP_KERNEL);
- if (!__tcbp)
- goto end;
-
- for (i = 0; i < count; i++) {
- ret = of_parse_phandle_with_args(np, "cooling-device",
- "#cooling-cells", i, &cooling_spec);
- if (ret < 0) {
- pr_err("Invalid cooling-device entry\n");
- goto free_tcbp;
- }
-
- __tcbp[i].cooling_device = cooling_spec.np;
-
- if (cooling_spec.args_count >= 2) { /* at least min and max */
- __tcbp[i].min = cooling_spec.args[0];
- __tcbp[i].max = cooling_spec.args[1];
- } else {
- pr_err("wrong reference to cooling device, missing limits\n");
- }
- }
-
- __tbp->tcbp = __tcbp;
- __tbp->count = count;
-
- goto end;
-
-free_tcbp:
- for (i = i - 1; i >= 0; i--)
- of_node_put(__tcbp[i].cooling_device);
- kfree(__tcbp);
-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;
- of_node_get(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
- *
- * 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
-__init *thermal_of_build_thermal_zone(struct device_node *np)
-{
- struct device_node *child = NULL, *gchild;
- struct __thermal_zone *tz;
- int ret, i;
- u32 prop, coef[2];
-
- 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("%pOFn: missing polling-delay-passive property\n", np);
- goto free_tz;
- }
- tz->passive_delay = prop;
-
- ret = of_property_read_u32(np, "polling-delay", &prop);
- if (ret < 0) {
- pr_err("%pOFn: missing polling-delay property\n", np);
- goto free_tz;
- }
- tz->polling_delay = prop;
-
- /*
- * REVIST: for now, the thermal framework supports only
- * one sensor per thermal zone. Thus, we are considering
- * only the first two values as slope and offset.
- */
- ret = of_property_read_u32_array(np, "coefficients", coef, 2);
- if (ret == 0) {
- tz->slope = coef[0];
- tz->offset = coef[1];
- } else {
- tz->slope = 1;
- tz->offset = 0;
- }
-
- /* 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 = kcalloc(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 = kcalloc(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:
- for (i = i - 1; i >= 0; i--) {
- struct __thermal_bind_params *tbp = tz->tbps + i;
- int j;
-
- for (j = 0; j < tbp->count; j++)
- of_node_put(tbp->tcbp[j].cooling_device);
-
- kfree(tbp->tcbp);
- }
-
- kfree(tz->tbps);
-free_trips:
- for (i = 0; i < tz->ntrips; i++)
- of_node_put(tz->trips[i].np);
- kfree(tz->trips);
- of_node_put(gchild);
-free_tz:
- kfree(tz);
- of_node_put(child);
-
- return ERR_PTR(ret);
-}
-
-static __init void of_thermal_free_zone(struct __thermal_zone *tz)
-{
- struct __thermal_bind_params *tbp;
- int i, j;
-
- for (i = 0; i < tz->num_tbps; i++) {
- tbp = tz->tbps + i;
-
- for (j = 0; j < tbp->count; j++)
- of_node_put(tbp->tcbp[j].cooling_device);
-
- kfree(tbp->tcbp);
- }
-
- kfree(tz->tbps);
- for (i = 0; i < tz->ntrips; i++)
- of_node_put(tz->trips[i].np);
- kfree(tz->trips);
- kfree(tz);
-}
-
-/**
- * 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.
- *
- */
-static __init void of_thermal_destroy_zones(void)
-{
- struct device_node *np, *child;
-
- np = of_find_node_by_name(NULL, "thermal-zones");
- if (!np) {
- pr_debug("unable to find thermal zones\n");
- return;
- }
-
- for_each_available_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);
- }
- of_node_put(np);
-}
-
-/**
- * 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_available_child_of_node(np, child) {
- struct thermal_zone_device *zone;
- struct thermal_zone_params *tzp;
- int i, mask = 0;
- u32 prop;
-
- tz = thermal_of_build_thermal_zone(child);
- if (IS_ERR(tz)) {
- pr_err("failed to build thermal zone %pOFn: %ld\n",
- child,
- 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;
-
- if (!of_property_read_u32(child, "sustainable-power", &prop))
- tzp->sustainable_power = prop;
-
- for (i = 0; i < tz->ntrips; i++)
- mask |= 1 << i;
-
- /* these two are left for temperature drivers to use */
- tzp->slope = tz->slope;
- tzp->offset = tz->offset;
-
- zone = thermal_zone_device_register(child->name, tz->ntrips,
- mask, tz,
- ops, tzp,
- tz->passive_delay,
- tz->polling_delay);
- if (IS_ERR(zone)) {
- pr_err("Failed to build %pOFn zone %ld\n", child,
- PTR_ERR(zone));
- kfree(tzp);
- kfree(ops);
- of_thermal_free_zone(tz);
- /* attempting to build remaining zones still */
- }
- }
- of_node_put(np);
-
- return 0;
-
-exit_free:
- of_node_put(child);
- of_node_put(np);
- of_thermal_free_zone(tz);
-
- /* no memory available, so free what we have built */
- of_thermal_destroy_zones();
-
- return -ENOMEM;
-}
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
index aa9c1d80fae4..2c7f3f9a26eb 100644
--- a/drivers/thermal/qcom/Kconfig
+++ b/drivers/thermal/qcom/Kconfig
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config QCOM_TSENS
tristate "Qualcomm TSENS Temperature Alarm"
- depends on QCOM_QFPROM
+ depends on NVMEM_QCOM_QFPROM
depends on ARCH_QCOM || COMPILE_TEST
help
This enables the thermal sysfs driver for the TSENS device. It shows
@@ -10,6 +10,17 @@ config QCOM_TSENS
Also able to set threshold temperature for both hot and cold and update
when a threshold is reached.
+config QCOM_SPMI_ADC_TM5
+ tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5"
+ depends on OF && SPMI && IIO
+ select REGMAP_SPMI
+ select QCOM_VADC_COMMON
+ help
+ This enables the thermal driver for the ADC thermal monitoring
+ device. It shows up as a thermal zone with multiple trip points.
+ Thermal client sets threshold temperature for both warm and cool and
+ gets updated when a threshold is reached.
+
config QCOM_SPMI_TEMP_ALARM
tristate "Qualcomm SPMI PMIC Temperature Alarm"
depends on OF && SPMI && IIO
@@ -20,3 +31,13 @@ config QCOM_SPMI_TEMP_ALARM
trip points. The temperature reported by the thermal sensor reflects the
real time die temperature if an ADC is present or an estimate of the
temperature based upon the over temperature stage value.
+
+config QCOM_LMH
+ tristate "Qualcomm Limits Management Hardware"
+ depends on ARCH_QCOM && QCOM_SCM
+ help
+ This enables initialization of Qualcomm limits management
+ hardware(LMh). LMh allows for hardware-enforced mitigation for cpus based on
+ input from temperature and current sensors. On many newer Qualcomm SoCs
+ LMh is configured in the firmware and this feature need not be enabled.
+ However, on certain SoCs like sdm845 LMh has to be configured from kernel.
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
index 7c8dc6e36693..0fa2512042e7 100644
--- a/drivers/thermal/qcom/Makefile
+++ b/drivers/thermal/qcom/Makefile
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
-qcom_tsens-y += tsens.o tsens-common.o tsens-v0_1.o \
- tsens-8960.o tsens-v2.o tsens-v1.o
+qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
+ tsens-8960.o
+obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
+obj-$(CONFIG_QCOM_LMH) += lmh.o
diff --git a/drivers/thermal/qcom/lmh.c b/drivers/thermal/qcom/lmh.c
new file mode 100644
index 000000000000..d3d9b9fa49e8
--- /dev/null
+++ b/drivers/thermal/qcom/lmh.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (C) 2021, Linaro Limited. All rights reserved.
+ */
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/qcom_scm.h>
+
+#define LMH_NODE_DCVS 0x44435653
+#define LMH_CLUSTER0_NODE_ID 0x6370302D
+#define LMH_CLUSTER1_NODE_ID 0x6370312D
+
+#define LMH_SUB_FN_THERMAL 0x54484D4C
+#define LMH_SUB_FN_CRNT 0x43524E54
+#define LMH_SUB_FN_REL 0x52454C00
+#define LMH_SUB_FN_BCL 0x42434C00
+
+#define LMH_ALGO_MODE_ENABLE 0x454E424C
+#define LMH_TH_HI_THRESHOLD 0x48494748
+#define LMH_TH_LOW_THRESHOLD 0x4C4F5700
+#define LMH_TH_ARM_THRESHOLD 0x41524D00
+
+#define LMH_REG_DCVS_INTR_CLR 0x8
+
+#define LMH_ENABLE_ALGOS 1
+
+struct lmh_hw_data {
+ void __iomem *base;
+ struct irq_domain *domain;
+ int irq;
+};
+
+static irqreturn_t lmh_handle_irq(int hw_irq, void *data)
+{
+ struct lmh_hw_data *lmh_data = data;
+ int irq = irq_find_mapping(lmh_data->domain, 0);
+
+ /* Call the cpufreq driver to handle the interrupt */
+ if (irq)
+ generic_handle_irq(irq);
+
+ return 0;
+}
+
+static void lmh_enable_interrupt(struct irq_data *d)
+{
+ struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
+
+ /* Clear the existing interrupt */
+ writel(0xff, lmh_data->base + LMH_REG_DCVS_INTR_CLR);
+ enable_irq(lmh_data->irq);
+}
+
+static void lmh_disable_interrupt(struct irq_data *d)
+{
+ struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
+
+ disable_irq_nosync(lmh_data->irq);
+}
+
+static struct irq_chip lmh_irq_chip = {
+ .name = "lmh",
+ .irq_enable = lmh_enable_interrupt,
+ .irq_disable = lmh_disable_interrupt
+};
+
+static int lmh_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
+{
+ struct lmh_hw_data *lmh_data = d->host_data;
+
+ irq_set_chip_and_handler(irq, &lmh_irq_chip, handle_simple_irq);
+ irq_set_chip_data(irq, lmh_data);
+
+ return 0;
+}
+
+static const struct irq_domain_ops lmh_irq_ops = {
+ .map = lmh_irq_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static int lmh_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *cpu_node;
+ struct lmh_hw_data *lmh_data;
+ int temp_low, temp_high, temp_arm, cpu_id, ret;
+ unsigned int enable_alg;
+ u32 node_id;
+
+ lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL);
+ if (!lmh_data)
+ return -ENOMEM;
+
+ lmh_data->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(lmh_data->base))
+ return PTR_ERR(lmh_data->base);
+
+ cpu_node = of_parse_phandle(np, "cpus", 0);
+ if (!cpu_node)
+ return -EINVAL;
+ cpu_id = of_cpu_node_to_id(cpu_node);
+ of_node_put(cpu_node);
+
+ ret = of_property_read_u32(np, "qcom,lmh-temp-high-millicelsius", &temp_high);
+ if (ret) {
+ dev_err(dev, "missing qcom,lmh-temp-high-millicelsius property\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "qcom,lmh-temp-low-millicelsius", &temp_low);
+ if (ret) {
+ dev_err(dev, "missing qcom,lmh-temp-low-millicelsius property\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "qcom,lmh-temp-arm-millicelsius", &temp_arm);
+ if (ret) {
+ dev_err(dev, "missing qcom,lmh-temp-arm-millicelsius property\n");
+ return ret;
+ }
+
+ /*
+ * Only sdm845 has lmh hardware currently enabled from hlos. If this is needed
+ * for other platforms, revisit this to check if the <cpu-id, node-id> should be part
+ * of a dt match table.
+ */
+ if (cpu_id == 0) {
+ node_id = LMH_CLUSTER0_NODE_ID;
+ } else if (cpu_id == 4) {
+ node_id = LMH_CLUSTER1_NODE_ID;
+ } else {
+ dev_err(dev, "Wrong CPU id associated with LMh node\n");
+ return -EINVAL;
+ }
+
+ if (!qcom_scm_lmh_dcvsh_available())
+ return -EINVAL;
+
+ enable_alg = (uintptr_t)of_device_get_match_data(dev);
+
+ if (enable_alg) {
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_CRNT, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret)
+ dev_err(dev, "Error %d enabling current subfunction\n", ret);
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_REL, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret)
+ dev_err(dev, "Error %d enabling reliability subfunction\n", ret);
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_BCL, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret)
+ dev_err(dev, "Error %d enabling BCL subfunction\n", ret);
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error %d enabling thermal subfunction\n", ret);
+ return ret;
+ }
+
+ ret = qcom_scm_lmh_profile_change(0x1);
+ if (ret) {
+ dev_err(dev, "Error %d changing profile\n", ret);
+ return ret;
+ }
+ }
+
+ /* Set default thermal trips */
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_ARM_THRESHOLD, temp_arm,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
+ return ret;
+ }
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_HI_THRESHOLD, temp_high,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error setting thermal HI threshold%d\n", ret);
+ return ret;
+ }
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_LOW_THRESHOLD, temp_low,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
+ return ret;
+ }
+
+ lmh_data->irq = platform_get_irq(pdev, 0);
+ lmh_data->domain = irq_domain_add_linear(np, 1, &lmh_irq_ops, lmh_data);
+ if (!lmh_data->domain) {
+ dev_err(dev, "Error adding irq_domain\n");
+ return -EINVAL;
+ }
+
+ /* Disable the irq and let cpufreq enable it when ready to handle the interrupt */
+ irq_set_status_flags(lmh_data->irq, IRQ_NOAUTOEN);
+ ret = devm_request_irq(dev, lmh_data->irq, lmh_handle_irq,
+ IRQF_ONESHOT | IRQF_NO_SUSPEND,
+ "lmh-irq", lmh_data);
+ if (ret) {
+ dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq);
+ irq_domain_remove(lmh_data->domain);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id lmh_table[] = {
+ { .compatible = "qcom,sc8180x-lmh", },
+ { .compatible = "qcom,sdm845-lmh", .data = (void *)LMH_ENABLE_ALGOS},
+ { .compatible = "qcom,sm8150-lmh", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lmh_table);
+
+static struct platform_driver lmh_driver = {
+ .probe = lmh_probe,
+ .driver = {
+ .name = "qcom-lmh",
+ .of_match_table = lmh_table,
+ .suppress_bind_attrs = true,
+ },
+};
+module_platform_driver(lmh_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("QCOM LMh driver");
diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
new file mode 100644
index 000000000000..1b2c43eab27d
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
@@ -0,0 +1,1081 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 Linaro Limited
+ *
+ * Based on original driver:
+ * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
+ *
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iio/adc/qcom-vadc-common.h>
+#include <linux/iio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include <asm-generic/unaligned.h>
+
+#include "../thermal_hwmon.h"
+
+/*
+ * Thermal monitoring block consists of 8 (ADC_TM5_NUM_CHANNELS) channels. Each
+ * channel is programmed to use one of ADC channels for voltage comparison.
+ * Voltages are programmed using ADC codes, so we have to convert temp to
+ * voltage and then to ADC code value.
+ *
+ * Configuration of TM channels must match configuration of corresponding ADC
+ * channels.
+ */
+
+#define ADC5_MAX_CHANNEL 0xc0
+#define ADC_TM5_NUM_CHANNELS 8
+
+#define ADC_TM5_STATUS_LOW 0x0a
+
+#define ADC_TM5_STATUS_HIGH 0x0b
+
+#define ADC_TM5_NUM_BTM 0x0f
+
+#define ADC_TM5_ADC_DIG_PARAM 0x42
+
+#define ADC_TM5_FAST_AVG_CTL (ADC_TM5_ADC_DIG_PARAM + 1)
+#define ADC_TM5_FAST_AVG_EN BIT(7)
+
+#define ADC_TM5_MEAS_INTERVAL_CTL (ADC_TM5_ADC_DIG_PARAM + 2)
+#define ADC_TM5_TIMER1 3 /* 3.9ms */
+
+#define ADC_TM5_MEAS_INTERVAL_CTL2 (ADC_TM5_ADC_DIG_PARAM + 3)
+#define ADC_TM5_MEAS_INTERVAL_CTL2_MASK 0xf0
+#define ADC_TM5_TIMER2 10 /* 1 second */
+#define ADC_TM5_MEAS_INTERVAL_CTL3_MASK 0xf
+#define ADC_TM5_TIMER3 4 /* 4 second */
+
+#define ADC_TM_EN_CTL1 0x46
+#define ADC_TM_EN BIT(7)
+#define ADC_TM_CONV_REQ 0x47
+#define ADC_TM_CONV_REQ_EN BIT(7)
+
+#define ADC_TM5_M_CHAN_BASE 0x60
+
+#define ADC_TM5_M_ADC_CH_SEL_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 0)
+#define ADC_TM5_M_LOW_THR0(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 1)
+#define ADC_TM5_M_LOW_THR1(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 2)
+#define ADC_TM5_M_HIGH_THR0(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 3)
+#define ADC_TM5_M_HIGH_THR1(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 4)
+#define ADC_TM5_M_MEAS_INTERVAL_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 5)
+#define ADC_TM5_M_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 6)
+#define ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK 0xf
+#define ADC_TM5_M_CTL_CAL_SEL_MASK 0x30
+#define ADC_TM5_M_CTL_CAL_VAL 0x40
+#define ADC_TM5_M_EN(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 7)
+#define ADC_TM5_M_MEAS_EN BIT(7)
+#define ADC_TM5_M_HIGH_THR_INT_EN BIT(1)
+#define ADC_TM5_M_LOW_THR_INT_EN BIT(0)
+
+#define ADC_TM_GEN2_STATUS1 0x08
+#define ADC_TM_GEN2_STATUS_LOW_SET 0x09
+#define ADC_TM_GEN2_STATUS_LOW_CLR 0x0a
+#define ADC_TM_GEN2_STATUS_HIGH_SET 0x0b
+#define ADC_TM_GEN2_STATUS_HIGH_CLR 0x0c
+
+#define ADC_TM_GEN2_CFG_HS_SET 0x0d
+#define ADC_TM_GEN2_CFG_HS_FLAG BIT(0)
+#define ADC_TM_GEN2_CFG_HS_CLR 0x0e
+
+#define ADC_TM_GEN2_SID 0x40
+
+#define ADC_TM_GEN2_CH_CTL 0x41
+#define ADC_TM_GEN2_TM_CH_SEL GENMASK(7, 5)
+#define ADC_TM_GEN2_MEAS_INT_SEL GENMASK(3, 2)
+
+#define ADC_TM_GEN2_ADC_DIG_PARAM 0x42
+#define ADC_TM_GEN2_CTL_CAL_SEL GENMASK(5, 4)
+#define ADC_TM_GEN2_CTL_DEC_RATIO_MASK GENMASK(3, 2)
+
+#define ADC_TM_GEN2_FAST_AVG_CTL 0x43
+#define ADC_TM_GEN2_FAST_AVG_EN BIT(7)
+
+#define ADC_TM_GEN2_ADC_CH_SEL_CTL 0x44
+
+#define ADC_TM_GEN2_DELAY_CTL 0x45
+#define ADC_TM_GEN2_HW_SETTLE_DELAY GENMASK(3, 0)
+
+#define ADC_TM_GEN2_EN_CTL1 0x46
+#define ADC_TM_GEN2_EN BIT(7)
+
+#define ADC_TM_GEN2_CONV_REQ 0x47
+#define ADC_TM_GEN2_CONV_REQ_EN BIT(7)
+
+#define ADC_TM_GEN2_LOW_THR0 0x49
+#define ADC_TM_GEN2_LOW_THR1 0x4a
+#define ADC_TM_GEN2_HIGH_THR0 0x4b
+#define ADC_TM_GEN2_HIGH_THR1 0x4c
+#define ADC_TM_GEN2_LOWER_MASK(n) ((n) & GENMASK(7, 0))
+#define ADC_TM_GEN2_UPPER_MASK(n) (((n) & GENMASK(15, 8)) >> 8)
+
+#define ADC_TM_GEN2_MEAS_IRQ_EN 0x4d
+#define ADC_TM_GEN2_MEAS_EN BIT(7)
+#define ADC_TM5_GEN2_HIGH_THR_INT_EN BIT(1)
+#define ADC_TM5_GEN2_LOW_THR_INT_EN BIT(0)
+
+#define ADC_TM_GEN2_MEAS_INT_LSB 0x50
+#define ADC_TM_GEN2_MEAS_INT_MSB 0x51
+#define ADC_TM_GEN2_MEAS_INT_MODE 0x52
+
+#define ADC_TM_GEN2_Mn_DATA0(n) ((n * 2) + 0xa0)
+#define ADC_TM_GEN2_Mn_DATA1(n) ((n * 2) + 0xa1)
+#define ADC_TM_GEN2_DATA_SHIFT 8
+
+enum adc5_timer_select {
+ ADC5_TIMER_SEL_1 = 0,
+ ADC5_TIMER_SEL_2,
+ ADC5_TIMER_SEL_3,
+ ADC5_TIMER_SEL_NONE,
+};
+
+enum adc5_gen {
+ ADC_TM5,
+ ADC_TM_HC,
+ ADC_TM5_GEN2,
+ ADC_TM5_MAX
+};
+
+enum adc_tm5_cal_method {
+ ADC_TM5_NO_CAL = 0,
+ ADC_TM5_RATIOMETRIC_CAL,
+ ADC_TM5_ABSOLUTE_CAL
+};
+
+enum adc_tm_gen2_time_select {
+ MEAS_INT_50MS = 0,
+ MEAS_INT_100MS,
+ MEAS_INT_1S,
+ MEAS_INT_SET,
+ MEAS_INT_NONE,
+};
+
+struct adc_tm5_chip;
+struct adc_tm5_channel;
+
+struct adc_tm5_data {
+ const u32 full_scale_code_volt;
+ unsigned int *decimation;
+ unsigned int *hw_settle;
+ int (*disable_channel)(struct adc_tm5_channel *channel);
+ int (*configure)(struct adc_tm5_channel *channel, int low, int high);
+ irqreturn_t (*isr)(int irq, void *data);
+ int (*init)(struct adc_tm5_chip *chip);
+ char *irq_name;
+ int gen;
+};
+
+/**
+ * struct adc_tm5_channel - ADC Thermal Monitoring channel data.
+ * @channel: channel number.
+ * @adc_channel: corresponding ADC channel number.
+ * @cal_method: calibration method.
+ * @prescale: channel scaling performed on the input signal.
+ * @hw_settle_time: the time between AMUX being configured and the
+ * start of conversion.
+ * @decimation: sampling rate supported for the channel.
+ * @avg_samples: ability to provide single result from the ADC
+ * that is an average of multiple measurements.
+ * @high_thr_en: channel upper voltage threshold enable state.
+ * @low_thr_en: channel lower voltage threshold enable state.
+ * @meas_en: recurring measurement enable state
+ * @iio: IIO channel instance used by this channel.
+ * @chip: ADC TM chip instance.
+ * @tzd: thermal zone device used by this channel.
+ */
+struct adc_tm5_channel {
+ unsigned int channel;
+ unsigned int adc_channel;
+ enum adc_tm5_cal_method cal_method;
+ unsigned int prescale;
+ unsigned int hw_settle_time;
+ unsigned int decimation; /* For Gen2 ADC_TM */
+ unsigned int avg_samples; /* For Gen2 ADC_TM */
+ bool high_thr_en; /* For Gen2 ADC_TM */
+ bool low_thr_en; /* For Gen2 ADC_TM */
+ bool meas_en; /* For Gen2 ADC_TM */
+ struct iio_channel *iio;
+ struct adc_tm5_chip *chip;
+ struct thermal_zone_device *tzd;
+};
+
+/**
+ * struct adc_tm5_chip - ADC Thermal Monitoring properties
+ * @regmap: SPMI ADC5 Thermal Monitoring peripheral register map field.
+ * @dev: SPMI ADC5 device.
+ * @data: software configuration data.
+ * @channels: array of ADC TM channel data.
+ * @nchannels: amount of channels defined/allocated
+ * @decimation: sampling rate supported for the channel.
+ * Applies to all channels, used only on Gen1 ADC_TM.
+ * @avg_samples: ability to provide single result from the ADC
+ * that is an average of multiple measurements. Applies to all
+ * channels, used only on Gen1 ADC_TM.
+ * @base: base address of TM registers.
+ * @adc_mutex_lock: ADC_TM mutex lock, used only on Gen2 ADC_TM.
+ * It is used to ensure only one ADC channel configuration
+ * is done at a time using the shared set of configuration
+ * registers.
+ */
+struct adc_tm5_chip {
+ struct regmap *regmap;
+ struct device *dev;
+ const struct adc_tm5_data *data;
+ struct adc_tm5_channel *channels;
+ unsigned int nchannels;
+ unsigned int decimation;
+ unsigned int avg_samples;
+ u16 base;
+ struct mutex adc_mutex_lock;
+};
+
+static int adc_tm5_read(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len)
+{
+ return regmap_bulk_read(adc_tm->regmap, adc_tm->base + offset, data, len);
+}
+
+static int adc_tm5_write(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len)
+{
+ return regmap_bulk_write(adc_tm->regmap, adc_tm->base + offset, data, len);
+}
+
+static int adc_tm5_reg_update(struct adc_tm5_chip *adc_tm, u16 offset, u8 mask, u8 val)
+{
+ return regmap_write_bits(adc_tm->regmap, adc_tm->base + offset, mask, val);
+}
+
+static irqreturn_t adc_tm5_isr(int irq, void *data)
+{
+ struct adc_tm5_chip *chip = data;
+ u8 status_low, status_high, ctl;
+ int ret, i;
+
+ ret = adc_tm5_read(chip, ADC_TM5_STATUS_LOW, &status_low, sizeof(status_low));
+ if (unlikely(ret)) {
+ dev_err(chip->dev, "read status low failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_read(chip, ADC_TM5_STATUS_HIGH, &status_high, sizeof(status_high));
+ if (unlikely(ret)) {
+ dev_err(chip->dev, "read status high failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ bool upper_set = false, lower_set = false;
+ unsigned int ch = chip->channels[i].channel;
+
+ /* No TZD, we warned at the boot time */
+ if (!chip->channels[i].tzd)
+ continue;
+
+ ret = adc_tm5_read(chip, ADC_TM5_M_EN(ch), &ctl, sizeof(ctl));
+ if (unlikely(ret)) {
+ dev_err(chip->dev, "ctl read failed: %d, channel %d\n", ret, i);
+ continue;
+ }
+
+ if (!(ctl & ADC_TM5_M_MEAS_EN))
+ continue;
+
+ lower_set = (status_low & BIT(ch)) &&
+ (ctl & ADC_TM5_M_LOW_THR_INT_EN);
+
+ upper_set = (status_high & BIT(ch)) &&
+ (ctl & ADC_TM5_M_HIGH_THR_INT_EN);
+
+ if (upper_set || lower_set)
+ thermal_zone_device_update(chip->channels[i].tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t adc_tm5_gen2_isr(int irq, void *data)
+{
+ struct adc_tm5_chip *chip = data;
+ u8 status_low, status_high;
+ int ret, i;
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low));
+ if (ret) {
+ dev_err(chip->dev, "read status_low failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high));
+ if (ret) {
+ dev_err(chip->dev, "read status_high failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low));
+ if (ret < 0) {
+ dev_err(chip->dev, "clear status low failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high));
+ if (ret < 0) {
+ dev_err(chip->dev, "clear status high failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ bool upper_set = false, lower_set = false;
+ unsigned int ch = chip->channels[i].channel;
+
+ /* No TZD, we warned at the boot time */
+ if (!chip->channels[i].tzd)
+ continue;
+
+ if (!chip->channels[i].meas_en)
+ continue;
+
+ lower_set = (status_low & BIT(ch)) &&
+ (chip->channels[i].low_thr_en);
+
+ upper_set = (status_high & BIT(ch)) &&
+ (chip->channels[i].high_thr_en);
+
+ if (upper_set || lower_set)
+ thermal_zone_device_update(chip->channels[i].tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int adc_tm5_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct adc_tm5_channel *channel = tz->devdata;
+ int ret;
+
+ if (!channel || !channel->iio)
+ return -EINVAL;
+
+ ret = iio_read_channel_processed(channel->iio, temp);
+ if (ret < 0)
+ return ret;
+
+ if (ret != IIO_VAL_INT)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int adc_tm5_disable_channel(struct adc_tm5_channel *channel)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ unsigned int reg = ADC_TM5_M_EN(channel->channel);
+
+ return adc_tm5_reg_update(chip, reg,
+ ADC_TM5_M_MEAS_EN |
+ ADC_TM5_M_HIGH_THR_INT_EN |
+ ADC_TM5_M_LOW_THR_INT_EN,
+ 0);
+}
+
+#define ADC_TM_GEN2_POLL_DELAY_MIN_US 100
+#define ADC_TM_GEN2_POLL_DELAY_MAX_US 110
+#define ADC_TM_GEN2_POLL_RETRY_COUNT 3
+
+static int32_t adc_tm5_gen2_conv_req(struct adc_tm5_chip *chip)
+{
+ int ret;
+ u8 data;
+ unsigned int count;
+
+ data = ADC_TM_GEN2_EN;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_EN_CTL1, &data, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm enable failed with %d\n", ret);
+ return ret;
+ }
+
+ data = ADC_TM_GEN2_CFG_HS_FLAG;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_CFG_HS_SET, &data, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm handshake failed with %d\n", ret);
+ return ret;
+ }
+
+ data = ADC_TM_GEN2_CONV_REQ_EN;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_CONV_REQ, &data, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm request conversion failed with %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * SW sets a handshake bit and waits for PBS to clear it
+ * before the next conversion request can be queued.
+ */
+
+ for (count = 0; count < ADC_TM_GEN2_POLL_RETRY_COUNT; count++) {
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_CFG_HS_SET, &data, sizeof(data));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm read failed with %d\n", ret);
+ return ret;
+ }
+
+ if (!(data & ADC_TM_GEN2_CFG_HS_FLAG))
+ return ret;
+ usleep_range(ADC_TM_GEN2_POLL_DELAY_MIN_US,
+ ADC_TM_GEN2_POLL_DELAY_MAX_US);
+ }
+
+ dev_err(chip->dev, "adc-tm conversion request handshake timed out\n");
+
+ return -ETIMEDOUT;
+}
+
+static int adc_tm5_gen2_disable_channel(struct adc_tm5_channel *channel)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ int ret;
+ u8 val;
+
+ mutex_lock(&chip->adc_mutex_lock);
+
+ channel->meas_en = false;
+ channel->high_thr_en = false;
+ channel->low_thr_en = false;
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_CH_CTL, &val, sizeof(val));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm block read failed with %d\n", ret);
+ goto disable_fail;
+ }
+
+ val &= ~ADC_TM_GEN2_TM_CH_SEL;
+ val |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel);
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_CH_CTL, &val, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm channel disable failed with %d\n", ret);
+ goto disable_fail;
+ }
+
+ val = 0;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_MEAS_IRQ_EN, &val, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm interrupt disable failed with %d\n", ret);
+ goto disable_fail;
+ }
+
+
+ ret = adc_tm5_gen2_conv_req(channel->chip);
+ if (ret < 0)
+ dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret);
+
+disable_fail:
+ mutex_unlock(&chip->adc_mutex_lock);
+ return ret;
+}
+
+static int adc_tm5_enable(struct adc_tm5_chip *chip)
+{
+ int ret;
+ u8 data;
+
+ data = ADC_TM_EN;
+ ret = adc_tm5_write(chip, ADC_TM_EN_CTL1, &data, sizeof(data));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm enable failed\n");
+ return ret;
+ }
+
+ data = ADC_TM_CONV_REQ_EN;
+ ret = adc_tm5_write(chip, ADC_TM_CONV_REQ, &data, sizeof(data));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm request conversion failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int adc_tm5_configure(struct adc_tm5_channel *channel, int low, int high)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ u8 buf[8];
+ u16 reg = ADC_TM5_M_ADC_CH_SEL_CTL(channel->channel);
+ int ret;
+
+ ret = adc_tm5_read(chip, reg, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "channel %d params read failed: %d\n", channel->channel, ret);
+ return ret;
+ }
+
+ buf[0] = channel->adc_channel;
+
+ /* High temperature corresponds to low voltage threshold */
+ if (high != INT_MAX) {
+ u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
+ chip->data->full_scale_code_volt, high);
+
+ put_unaligned_le16(adc_code, &buf[1]);
+ buf[7] |= ADC_TM5_M_LOW_THR_INT_EN;
+ } else {
+ buf[7] &= ~ADC_TM5_M_LOW_THR_INT_EN;
+ }
+
+ /* Low temperature corresponds to high voltage threshold */
+ if (low != -INT_MAX) {
+ u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
+ chip->data->full_scale_code_volt, low);
+
+ put_unaligned_le16(adc_code, &buf[3]);
+ buf[7] |= ADC_TM5_M_HIGH_THR_INT_EN;
+ } else {
+ buf[7] &= ~ADC_TM5_M_HIGH_THR_INT_EN;
+ }
+
+ buf[5] = ADC5_TIMER_SEL_2;
+
+ /* Set calibration select, hw_settle delay */
+ buf[6] &= ~ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK;
+ buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK, channel->hw_settle_time);
+ buf[6] &= ~ADC_TM5_M_CTL_CAL_SEL_MASK;
+ buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_CAL_SEL_MASK, channel->cal_method);
+
+ buf[7] |= ADC_TM5_M_MEAS_EN;
+
+ ret = adc_tm5_write(chip, reg, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "channel %d params write failed: %d\n", channel->channel, ret);
+ return ret;
+ }
+
+ return adc_tm5_enable(chip);
+}
+
+static int adc_tm5_gen2_configure(struct adc_tm5_channel *channel, int low, int high)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ int ret;
+ u8 buf[14];
+ u16 adc_code;
+
+ mutex_lock(&chip->adc_mutex_lock);
+
+ channel->meas_en = true;
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_SID, buf, sizeof(buf));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm block read failed with %d\n", ret);
+ goto config_fail;
+ }
+
+ /* Set SID from virtual channel number */
+ buf[0] = channel->adc_channel >> 8;
+
+ /* Set TM channel number used and measurement interval */
+ buf[1] &= ~ADC_TM_GEN2_TM_CH_SEL;
+ buf[1] |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel);
+ buf[1] &= ~ADC_TM_GEN2_MEAS_INT_SEL;
+ buf[1] |= FIELD_PREP(ADC_TM_GEN2_MEAS_INT_SEL, MEAS_INT_1S);
+
+ buf[2] &= ~ADC_TM_GEN2_CTL_DEC_RATIO_MASK;
+ buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_DEC_RATIO_MASK, channel->decimation);
+ buf[2] &= ~ADC_TM_GEN2_CTL_CAL_SEL;
+ buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_CAL_SEL, channel->cal_method);
+
+ buf[3] = channel->avg_samples | ADC_TM_GEN2_FAST_AVG_EN;
+
+ buf[4] = channel->adc_channel & 0xff;
+
+ buf[5] = channel->hw_settle_time & ADC_TM_GEN2_HW_SETTLE_DELAY;
+
+ /* High temperature corresponds to low voltage threshold */
+ if (high != INT_MAX) {
+ channel->low_thr_en = true;
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(high);
+ put_unaligned_le16(adc_code, &buf[9]);
+ } else {
+ channel->low_thr_en = false;
+ }
+
+ /* Low temperature corresponds to high voltage threshold */
+ if (low != -INT_MAX) {
+ channel->high_thr_en = true;
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(low);
+ put_unaligned_le16(adc_code, &buf[11]);
+ } else {
+ channel->high_thr_en = false;
+ }
+
+ buf[13] = ADC_TM_GEN2_MEAS_EN;
+ if (channel->high_thr_en)
+ buf[13] |= ADC_TM5_GEN2_HIGH_THR_INT_EN;
+ if (channel->low_thr_en)
+ buf[13] |= ADC_TM5_GEN2_LOW_THR_INT_EN;
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_SID, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "channel %d params write failed: %d\n", channel->channel, ret);
+ goto config_fail;
+ }
+
+ ret = adc_tm5_gen2_conv_req(channel->chip);
+ if (ret < 0)
+ dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret);
+
+config_fail:
+ mutex_unlock(&chip->adc_mutex_lock);
+ return ret;
+}
+
+static int adc_tm5_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct adc_tm5_channel *channel = tz->devdata;
+ struct adc_tm5_chip *chip;
+ int ret;
+
+ if (!channel)
+ return -EINVAL;
+
+ chip = channel->chip;
+ dev_dbg(chip->dev, "%d:low(mdegC):%d, high(mdegC):%d\n",
+ channel->channel, low, high);
+
+ if (high == INT_MAX && low <= -INT_MAX)
+ ret = chip->data->disable_channel(channel);
+ else
+ ret = chip->data->configure(channel, low, high);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops adc_tm5_thermal_ops = {
+ .get_temp = adc_tm5_get_temp,
+ .set_trips = adc_tm5_set_trips,
+};
+
+static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm)
+{
+ unsigned int i;
+ struct thermal_zone_device *tzd;
+
+ for (i = 0; i < adc_tm->nchannels; i++) {
+ adc_tm->channels[i].chip = adc_tm;
+ tzd = devm_thermal_of_zone_register(adc_tm->dev,
+ adc_tm->channels[i].channel,
+ &adc_tm->channels[i],
+ &adc_tm5_thermal_ops);
+ if (IS_ERR(tzd)) {
+ if (PTR_ERR(tzd) == -ENODEV) {
+ dev_warn(adc_tm->dev, "thermal sensor on channel %d is not used\n",
+ adc_tm->channels[i].channel);
+ continue;
+ }
+
+ dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n",
+ adc_tm->channels[i].channel, PTR_ERR(tzd));
+ return PTR_ERR(tzd);
+ }
+ adc_tm->channels[i].tzd = tzd;
+ if (devm_thermal_add_hwmon_sysfs(tzd))
+ dev_warn(adc_tm->dev,
+ "Failed to add hwmon sysfs attributes\n");
+ }
+
+ return 0;
+}
+
+static int adc_tm_hc_init(struct adc_tm5_chip *chip)
+{
+ unsigned int i;
+ u8 buf[2];
+ int ret;
+
+ for (i = 0; i < chip->nchannels; i++) {
+ if (chip->channels[i].channel >= ADC_TM5_NUM_CHANNELS) {
+ dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
+ return -EINVAL;
+ }
+ }
+
+ buf[0] = chip->decimation;
+ buf[1] = chip->avg_samples | ADC_TM5_FAST_AVG_EN;
+
+ ret = adc_tm5_write(chip, ADC_TM5_ADC_DIG_PARAM, buf, sizeof(buf));
+ if (ret)
+ dev_err(chip->dev, "block write failed: %d\n", ret);
+
+ return ret;
+}
+
+static int adc_tm5_init(struct adc_tm5_chip *chip)
+{
+ u8 buf[4], channels_available;
+ int ret;
+ unsigned int i;
+
+ ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM,
+ &channels_available, sizeof(channels_available));
+ if (ret) {
+ dev_err(chip->dev, "read failed for BTM channels\n");
+ return ret;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ if (chip->channels[i].channel >= channels_available) {
+ dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
+ return -EINVAL;
+ }
+ }
+
+ buf[0] = chip->decimation;
+ buf[1] = chip->avg_samples | ADC_TM5_FAST_AVG_EN;
+ buf[2] = ADC_TM5_TIMER1;
+ buf[3] = FIELD_PREP(ADC_TM5_MEAS_INTERVAL_CTL2_MASK, ADC_TM5_TIMER2) |
+ FIELD_PREP(ADC_TM5_MEAS_INTERVAL_CTL3_MASK, ADC_TM5_TIMER3);
+
+ ret = adc_tm5_write(chip, ADC_TM5_ADC_DIG_PARAM, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "block write failed: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int adc_tm5_gen2_init(struct adc_tm5_chip *chip)
+{
+ u8 channels_available;
+ int ret;
+ unsigned int i;
+
+ ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM,
+ &channels_available, sizeof(channels_available));
+ if (ret) {
+ dev_err(chip->dev, "read failed for BTM channels\n");
+ return ret;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ if (chip->channels[i].channel >= channels_available) {
+ dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
+ return -EINVAL;
+ }
+ }
+
+ mutex_init(&chip->adc_mutex_lock);
+
+ return ret;
+}
+
+static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm,
+ struct adc_tm5_channel *channel,
+ struct device_node *node)
+{
+ const char *name = node->name;
+ u32 chan, value, adc_channel, varr[2];
+ int ret;
+ struct device *dev = adc_tm->dev;
+ struct of_phandle_args args;
+
+ ret = of_property_read_u32(node, "reg", &chan);
+ if (ret) {
+ dev_err(dev, "%s: invalid channel number %d\n", name, ret);
+ return ret;
+ }
+
+ if (chan >= ADC_TM5_NUM_CHANNELS) {
+ dev_err(dev, "%s: channel number too big: %d\n", name, chan);
+ return -EINVAL;
+ }
+
+ channel->channel = chan;
+
+ /*
+ * We are tied to PMIC's ADC controller, which always use single
+ * argument for channel number. So don't bother parsing
+ * #io-channel-cells, just enforce cell_count = 1.
+ */
+ ret = of_parse_phandle_with_fixed_args(node, "io-channels", 1, 0, &args);
+ if (ret < 0) {
+ dev_err(dev, "%s: error parsing ADC channel number %d: %d\n", name, chan, ret);
+ return ret;
+ }
+ of_node_put(args.np);
+
+ if (args.args_count != 1) {
+ dev_err(dev, "%s: invalid args count for ADC channel %d\n", name, chan);
+ return -EINVAL;
+ }
+
+ adc_channel = args.args[0];
+ if (adc_tm->data->gen == ADC_TM5_GEN2)
+ adc_channel &= 0xff;
+
+ if (adc_channel >= ADC5_MAX_CHANNEL) {
+ dev_err(dev, "%s: invalid ADC channel number %d\n", name, chan);
+ return -EINVAL;
+ }
+ channel->adc_channel = args.args[0];
+
+ channel->iio = devm_fwnode_iio_channel_get_by_name(adc_tm->dev,
+ of_fwnode_handle(node), NULL);
+ if (IS_ERR(channel->iio)) {
+ ret = PTR_ERR(channel->iio);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "%s: error getting channel: %d\n", name, ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
+ if (!ret) {
+ ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]);
+ if (ret < 0) {
+ dev_err(dev, "%s: invalid pre-scaling <%d %d>\n",
+ name, varr[0], varr[1]);
+ return ret;
+ }
+ channel->prescale = ret;
+ } else {
+ /* 1:1 prescale is index 0 */
+ channel->prescale = 0;
+ }
+
+ ret = of_property_read_u32(node, "qcom,hw-settle-time-us", &value);
+ if (!ret) {
+ ret = qcom_adc5_hw_settle_time_from_dt(value, adc_tm->data->hw_settle);
+ if (ret < 0) {
+ dev_err(dev, "%s invalid hw-settle-time-us %d us\n",
+ name, value);
+ return ret;
+ }
+ channel->hw_settle_time = ret;
+ } else {
+ channel->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
+ }
+
+ if (of_property_read_bool(node, "qcom,ratiometric"))
+ channel->cal_method = ADC_TM5_RATIOMETRIC_CAL;
+ else
+ channel->cal_method = ADC_TM5_ABSOLUTE_CAL;
+
+ if (adc_tm->data->gen == ADC_TM5_GEN2) {
+ ret = of_property_read_u32(node, "qcom,decimation", &value);
+ if (!ret) {
+ ret = qcom_adc5_decimation_from_dt(value, adc_tm->data->decimation);
+ if (ret < 0) {
+ dev_err(dev, "invalid decimation %d\n", value);
+ return ret;
+ }
+ channel->decimation = ret;
+ } else {
+ channel->decimation = ADC5_DECIMATION_DEFAULT;
+ }
+
+ ret = of_property_read_u32(node, "qcom,avg-samples", &value);
+ if (!ret) {
+ ret = qcom_adc5_avg_samples_from_dt(value);
+ if (ret < 0) {
+ dev_err(dev, "invalid avg-samples %d\n", value);
+ return ret;
+ }
+ channel->avg_samples = ret;
+ } else {
+ channel->avg_samples = VADC_DEF_AVG_SAMPLES;
+ }
+ }
+
+ return 0;
+}
+
+static const struct adc_tm5_data adc_tm5_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .decimation = (unsigned int []) { 250, 420, 840 },
+ .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700,
+ 1000, 2000, 4000, 8000, 16000, 32000,
+ 64000, 128000 },
+ .disable_channel = adc_tm5_disable_channel,
+ .configure = adc_tm5_configure,
+ .isr = adc_tm5_isr,
+ .init = adc_tm5_init,
+ .irq_name = "pm-adc-tm5",
+ .gen = ADC_TM5,
+};
+
+static const struct adc_tm5_data adc_tm_hc_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .decimation = (unsigned int []) { 256, 512, 1024 },
+ .hw_settle = (unsigned int []) { 0, 100, 200, 300, 400, 500, 600, 700,
+ 1000, 2000, 4000, 6000, 8000, 10000 },
+ .disable_channel = adc_tm5_disable_channel,
+ .configure = adc_tm5_configure,
+ .isr = adc_tm5_isr,
+ .init = adc_tm_hc_init,
+ .irq_name = "pm-adc-tm5",
+ .gen = ADC_TM_HC,
+};
+
+static const struct adc_tm5_data adc_tm5_gen2_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .decimation = (unsigned int []) { 85, 340, 1360 },
+ .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700,
+ 1000, 2000, 4000, 8000, 16000, 32000,
+ 64000, 128000 },
+ .disable_channel = adc_tm5_gen2_disable_channel,
+ .configure = adc_tm5_gen2_configure,
+ .isr = adc_tm5_gen2_isr,
+ .init = adc_tm5_gen2_init,
+ .irq_name = "pm-adc-tm5-gen2",
+ .gen = ADC_TM5_GEN2,
+};
+
+static int adc_tm5_get_dt_data(struct adc_tm5_chip *adc_tm, struct device_node *node)
+{
+ struct adc_tm5_channel *channels;
+ struct device_node *child;
+ u32 value;
+ int ret;
+ struct device *dev = adc_tm->dev;
+
+ adc_tm->nchannels = of_get_available_child_count(node);
+ if (!adc_tm->nchannels)
+ return -EINVAL;
+
+ adc_tm->channels = devm_kcalloc(dev, adc_tm->nchannels,
+ sizeof(*adc_tm->channels), GFP_KERNEL);
+ if (!adc_tm->channels)
+ return -ENOMEM;
+
+ channels = adc_tm->channels;
+
+ adc_tm->data = of_device_get_match_data(dev);
+ if (!adc_tm->data)
+ adc_tm->data = &adc_tm5_data_pmic;
+
+ ret = of_property_read_u32(node, "qcom,decimation", &value);
+ if (!ret) {
+ ret = qcom_adc5_decimation_from_dt(value, adc_tm->data->decimation);
+ if (ret < 0) {
+ dev_err(dev, "invalid decimation %d\n", value);
+ return ret;
+ }
+ adc_tm->decimation = ret;
+ } else {
+ adc_tm->decimation = ADC5_DECIMATION_DEFAULT;
+ }
+
+ ret = of_property_read_u32(node, "qcom,avg-samples", &value);
+ if (!ret) {
+ ret = qcom_adc5_avg_samples_from_dt(value);
+ if (ret < 0) {
+ dev_err(dev, "invalid avg-samples %d\n", value);
+ return ret;
+ }
+ adc_tm->avg_samples = ret;
+ } else {
+ adc_tm->avg_samples = VADC_DEF_AVG_SAMPLES;
+ }
+
+ for_each_available_child_of_node(node, child) {
+ ret = adc_tm5_get_dt_channel_data(adc_tm, channels, child);
+ if (ret) {
+ of_node_put(child);
+ return ret;
+ }
+
+ channels++;
+ }
+
+ return 0;
+}
+
+static int adc_tm5_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct adc_tm5_chip *adc_tm;
+ struct regmap *regmap;
+ int ret, irq;
+ u32 reg;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ ret = of_property_read_u32(node, "reg", &reg);
+ if (ret)
+ return ret;
+
+ adc_tm = devm_kzalloc(&pdev->dev, sizeof(*adc_tm), GFP_KERNEL);
+ if (!adc_tm)
+ return -ENOMEM;
+
+ adc_tm->regmap = regmap;
+ adc_tm->dev = dev;
+ adc_tm->base = reg;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = adc_tm5_get_dt_data(adc_tm, node);
+ if (ret) {
+ dev_err(dev, "get dt data failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = adc_tm->data->init(adc_tm);
+ if (ret) {
+ dev_err(dev, "adc-tm init failed\n");
+ return ret;
+ }
+
+ ret = adc_tm5_register_tzd(adc_tm);
+ if (ret) {
+ dev_err(dev, "tzd register failed\n");
+ return ret;
+ }
+
+ return devm_request_threaded_irq(dev, irq, NULL, adc_tm->data->isr,
+ IRQF_ONESHOT, adc_tm->data->irq_name, adc_tm);
+}
+
+static const struct of_device_id adc_tm5_match_table[] = {
+ {
+ .compatible = "qcom,spmi-adc-tm5",
+ .data = &adc_tm5_data_pmic,
+ },
+ {
+ .compatible = "qcom,spmi-adc-tm-hc",
+ .data = &adc_tm_hc_data_pmic,
+ },
+ {
+ .compatible = "qcom,spmi-adc-tm5-gen2",
+ .data = &adc_tm5_gen2_data_pmic,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adc_tm5_match_table);
+
+static struct platform_driver adc_tm5_driver = {
+ .driver = {
+ .name = "qcom-spmi-adc-tm5",
+ .of_match_table = adc_tm5_match_table,
+ },
+ .probe = adc_tm5_probe,
+};
+module_platform_driver(adc_tm5_driver);
+
+MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
index bf7bae42c141..be785ab37e53 100644
--- a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
+++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Copyright (c) 2011-2015, 2017, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2011-2015, 2017, 2020, The Linux Foundation. All rights reserved.
*/
#include <linux/bitops.h>
@@ -16,7 +16,9 @@
#include <linux/thermal.h>
#include "../thermal_core.h"
+#include "../thermal_hwmon.h"
+#define QPNP_TM_REG_DIG_MAJOR 0x01
#define QPNP_TM_REG_TYPE 0x04
#define QPNP_TM_REG_SUBTYPE 0x05
#define QPNP_TM_REG_STATUS 0x08
@@ -38,26 +40,30 @@
#define ALARM_CTRL_FORCE_ENABLE BIT(7)
-/*
- * Trip point values based on threshold control
- * 0 = {105 C, 125 C, 145 C}
- * 1 = {110 C, 130 C, 150 C}
- * 2 = {115 C, 135 C, 155 C}
- * 3 = {120 C, 140 C, 160 C}
-*/
-#define TEMP_STAGE_STEP 20000 /* Stage step: 20.000 C */
-#define TEMP_STAGE_HYSTERESIS 2000
+#define THRESH_COUNT 4
+#define STAGE_COUNT 3
-#define TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */
-#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */
+/* Over-temperature trip point values in mC */
+static const long temp_map_gen1[THRESH_COUNT][STAGE_COUNT] = {
+ { 105000, 125000, 145000 },
+ { 110000, 130000, 150000 },
+ { 115000, 135000, 155000 },
+ { 120000, 140000, 160000 },
+};
+
+static const long temp_map_gen2_v1[THRESH_COUNT][STAGE_COUNT] = {
+ { 90000, 110000, 140000 },
+ { 95000, 115000, 145000 },
+ { 100000, 120000, 150000 },
+ { 105000, 125000, 155000 },
+};
+
+#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */
#define THRESH_MIN 0
#define THRESH_MAX 3
-/* Stage 2 Threshold Min: 125 C */
-#define STAGE2_THRESHOLD_MIN 125000
-/* Stage 2 Threshold Max: 140 C */
-#define STAGE2_THRESHOLD_MAX 140000
+#define TEMP_STAGE_HYSTERESIS 2000
/* Temperature in Milli Celsius reported during stage 0 if no ADC is present */
#define DEFAULT_TEMP 37000
@@ -77,6 +83,7 @@ struct qpnp_tm_chip {
bool initialized;
struct iio_channel *adc;
+ const long (*temp_map)[THRESH_COUNT][STAGE_COUNT];
};
/* This array maps from GEN2 alarm state to GEN1 alarm stage */
@@ -101,6 +108,23 @@ static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data)
}
/**
+ * qpnp_tm_decode_temp() - return temperature in mC corresponding to the
+ * specified over-temperature stage
+ * @chip: Pointer to the qpnp_tm chip
+ * @stage: Over-temperature stage
+ *
+ * Return: temperature in mC
+ */
+static long qpnp_tm_decode_temp(struct qpnp_tm_chip *chip, unsigned int stage)
+{
+ if (!chip->temp_map || chip->thresh >= THRESH_COUNT || stage == 0 ||
+ stage > STAGE_COUNT)
+ return 0;
+
+ return (*chip->temp_map)[chip->thresh][stage - 1];
+}
+
+/**
* qpnp_tm_get_temp_stage() - return over-temperature stage
* @chip: Pointer to the qpnp_tm chip
*
@@ -149,14 +173,12 @@ static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
if (stage_new > stage_old) {
/* increasing stage, use lower bound */
- chip->temp = (stage_new - 1) * TEMP_STAGE_STEP +
- chip->thresh * TEMP_THRESH_STEP +
- TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+ chip->temp = qpnp_tm_decode_temp(chip, stage_new)
+ + TEMP_STAGE_HYSTERESIS;
} else if (stage_new < stage_old) {
/* decreasing stage, use upper bound */
- chip->temp = stage_new * TEMP_STAGE_STEP +
- chip->thresh * TEMP_THRESH_STEP -
- TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+ chip->temp = qpnp_tm_decode_temp(chip, stage_new + 1)
+ - TEMP_STAGE_HYSTERESIS;
}
chip->stage = stage;
@@ -164,9 +186,9 @@ static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
return 0;
}
-static int qpnp_tm_get_temp(void *data, int *temp)
+static int qpnp_tm_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct qpnp_tm_chip *chip = data;
+ struct qpnp_tm_chip *chip = tz->devdata;
int ret, mili_celsius;
if (!temp)
@@ -191,7 +213,7 @@ static int qpnp_tm_get_temp(void *data, int *temp)
chip->temp = mili_celsius;
}
- *temp = chip->temp < 0 ? 0 : chip->temp;
+ *temp = chip->temp;
return 0;
}
@@ -199,26 +221,28 @@ static int qpnp_tm_get_temp(void *data, int *temp)
static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip,
int temp)
{
- u8 reg;
+ long stage2_threshold_min = (*chip->temp_map)[THRESH_MIN][1];
+ long stage2_threshold_max = (*chip->temp_map)[THRESH_MAX][1];
bool disable_s2_shutdown = false;
+ u8 reg;
WARN_ON(!mutex_is_locked(&chip->lock));
/*
* Default: S2 and S3 shutdown enabled, thresholds at
- * 105C/125C/145C, monitoring at 25Hz
+ * lowest threshold set, monitoring at 25Hz
*/
reg = SHUTDOWN_CTRL1_RATE_25HZ;
if (temp == THERMAL_TEMP_INVALID ||
- temp < STAGE2_THRESHOLD_MIN) {
+ temp < stage2_threshold_min) {
chip->thresh = THRESH_MIN;
goto skip;
}
- if (temp <= STAGE2_THRESHOLD_MAX) {
+ if (temp <= stage2_threshold_max) {
chip->thresh = THRESH_MAX -
- ((STAGE2_THRESHOLD_MAX - temp) /
+ ((stage2_threshold_max - temp) /
TEMP_THRESH_STEP);
disable_s2_shutdown = true;
} else {
@@ -239,9 +263,9 @@ skip:
return qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg);
}
-static int qpnp_tm_set_trip_temp(void *data, int trip, int temp)
+static int qpnp_tm_set_trip_temp(struct thermal_zone_device *tz, int trip, int temp)
{
- struct qpnp_tm_chip *chip = data;
+ struct qpnp_tm_chip *chip = tz->devdata;
const struct thermal_trip *trip_points;
int ret;
@@ -259,7 +283,7 @@ static int qpnp_tm_set_trip_temp(void *data, int trip, int temp)
return ret;
}
-static const struct thermal_zone_of_device_ops qpnp_tm_sensor_ops = {
+static const struct thermal_zone_device_ops qpnp_tm_sensor_ops = {
.get_temp = qpnp_tm_get_temp,
.set_trip_temp = qpnp_tm_set_trip_temp,
};
@@ -326,9 +350,7 @@ static int qpnp_tm_init(struct qpnp_tm_chip *chip)
? chip->stage : alarm_state_map[chip->stage];
if (stage)
- chip->temp = chip->thresh * TEMP_THRESH_STEP +
- (stage - 1) * TEMP_STAGE_STEP +
- TEMP_THRESH_MIN;
+ chip->temp = qpnp_tm_decode_temp(chip, stage);
crit_temp = qpnp_tm_get_critical_trip_temp(chip);
ret = qpnp_tm_update_critical_trip_temp(chip, crit_temp);
@@ -350,7 +372,7 @@ static int qpnp_tm_probe(struct platform_device *pdev)
{
struct qpnp_tm_chip *chip;
struct device_node *node;
- u8 type, subtype;
+ u8 type, subtype, dig_major;
u32 res;
int ret, irq;
@@ -400,6 +422,12 @@ static int qpnp_tm_probe(struct platform_device *pdev)
return ret;
}
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_DIG_MAJOR, &dig_major);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not read dig_major\n");
+ return ret;
+ }
+
if (type != QPNP_TM_TYPE || (subtype != QPNP_TM_SUBTYPE_GEN1
&& subtype != QPNP_TM_SUBTYPE_GEN2)) {
dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n",
@@ -408,13 +436,17 @@ static int qpnp_tm_probe(struct platform_device *pdev)
}
chip->subtype = subtype;
+ if (subtype == QPNP_TM_SUBTYPE_GEN2 && dig_major >= 1)
+ chip->temp_map = &temp_map_gen2_v1;
+ else
+ chip->temp_map = &temp_map_gen1;
/*
* Register the sensor before initializing the hardware to be able to
* read the trip points. get_temp() returns the default temperature
* before the hardware initialization is completed.
*/
- chip->tz_dev = devm_thermal_zone_of_sensor_register(
+ chip->tz_dev = devm_thermal_of_zone_register(
&pdev->dev, 0, chip, &qpnp_tm_sensor_ops);
if (IS_ERR(chip->tz_dev)) {
dev_err(&pdev->dev, "failed to register sensor\n");
@@ -427,6 +459,10 @@ static int qpnp_tm_probe(struct platform_device *pdev)
return ret;
}
+ if (devm_thermal_add_hwmon_sysfs(chip->tz_dev))
+ dev_warn(&pdev->dev,
+ "Failed to add hwmon sysfs attributes\n");
+
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qpnp_tm_isr,
IRQF_ONESHOT, node->name, chip);
if (ret < 0)
diff --git a/drivers/thermal/qcom/tsens-8960.c b/drivers/thermal/qcom/tsens-8960.c
index fb77acb8d13b..67c1748cdf73 100644
--- a/drivers/thermal/qcom/tsens-8960.c
+++ b/drivers/thermal/qcom/tsens-8960.c
@@ -10,8 +10,6 @@
#include <linux/thermal.h>
#include "tsens.h"
-#define CAL_MDEGC 30000
-
#define CONFIG_ADDR 0x3640
#define CONFIG_ADDR_8660 0x3620
/* CONFIG_ADDR bitmasks */
@@ -21,40 +19,38 @@
#define CONFIG_SHIFT_8660 28
#define CONFIG_MASK_8660 (3 << CONFIG_SHIFT_8660)
-#define STATUS_CNTL_ADDR_8064 0x3660
#define CNTL_ADDR 0x3620
/* CNTL_ADDR bitmasks */
#define EN BIT(0)
#define SW_RST BIT(1)
-#define SENSOR0_EN BIT(3)
+
+#define MEASURE_PERIOD BIT(18)
#define SLP_CLK_ENA BIT(26)
#define SLP_CLK_ENA_8660 BIT(24)
-#define MEASURE_PERIOD 1
#define SENSOR0_SHIFT 3
-/* INT_STATUS_ADDR bitmasks */
-#define MIN_STATUS_MASK BIT(0)
-#define LOWER_STATUS_CLR BIT(1)
-#define UPPER_STATUS_CLR BIT(2)
-#define MAX_STATUS_MASK BIT(3)
-
#define THRESHOLD_ADDR 0x3624
-/* THRESHOLD_ADDR bitmasks */
-#define THRESHOLD_MAX_LIMIT_SHIFT 24
-#define THRESHOLD_MIN_LIMIT_SHIFT 16
-#define THRESHOLD_UPPER_LIMIT_SHIFT 8
-#define THRESHOLD_LOWER_LIMIT_SHIFT 0
-
-/* Initial temperature threshold values */
-#define LOWER_LIMIT_TH 0x50
-#define UPPER_LIMIT_TH 0xdf
-#define MIN_LIMIT_TH 0x0
-#define MAX_LIMIT_TH 0xff
-
-#define S0_STATUS_ADDR 0x3628
+
#define INT_STATUS_ADDR 0x363c
-#define TRDY_MASK BIT(7)
-#define TIMEOUT_US 100
+
+#define S0_STATUS_OFF 0x3628
+#define S1_STATUS_OFF 0x362c
+#define S2_STATUS_OFF 0x3630
+#define S3_STATUS_OFF 0x3634
+#define S4_STATUS_OFF 0x3638
+#define S5_STATUS_OFF 0x3664 /* Sensors 5-10 found on apq8064/msm8960 */
+#define S6_STATUS_OFF 0x3668
+#define S7_STATUS_OFF 0x366c
+#define S8_STATUS_OFF 0x3670
+#define S9_STATUS_OFF 0x3674
+#define S10_STATUS_OFF 0x3678
+
+/* Original slope - 350 to compensate mC to C inaccuracy */
+static u32 tsens_msm8960_slope[] = {
+ 826, 826, 804, 826,
+ 761, 782, 782, 849,
+ 782, 849, 782
+ };
static int suspend_8960(struct tsens_priv *priv)
{
@@ -115,17 +111,34 @@ static int resume_8960(struct tsens_priv *priv)
static int enable_8960(struct tsens_priv *priv, int id)
{
int ret;
- u32 reg, mask;
+ u32 reg, mask = BIT(id);
ret = regmap_read(priv->tm_map, CNTL_ADDR, &reg);
if (ret)
return ret;
- mask = BIT(id + SENSOR0_SHIFT);
+ /* HARDWARE BUG:
+ * On platforms with more than 6 sensors, all remaining sensors
+ * must be enabled together, otherwise undefined results are expected.
+ * (Sensor 6-7 disabled, Sensor 3 disabled...) In the original driver,
+ * all the sensors are enabled in one step hence this bug is not
+ * triggered.
+ */
+ if (id > 5)
+ mask = GENMASK(10, 6);
+
+ mask <<= SENSOR0_SHIFT;
+
+ /* Sensors already enabled. Skip. */
+ if ((reg & mask) == mask)
+ return 0;
+
ret = regmap_write(priv->tm_map, CNTL_ADDR, reg | SW_RST);
if (ret)
return ret;
+ reg |= MEASURE_PERIOD;
+
if (priv->num_sensors > 1)
reg |= mask | SLP_CLK_ENA | EN;
else
@@ -162,63 +175,11 @@ static void disable_8960(struct tsens_priv *priv)
regmap_write(priv->tm_map, CNTL_ADDR, reg_cntl);
}
-static int init_8960(struct tsens_priv *priv)
-{
- int ret, i;
- u32 reg_cntl;
-
- priv->tm_map = dev_get_regmap(priv->dev, NULL);
- if (!priv->tm_map)
- return -ENODEV;
-
- /*
- * The status registers for each sensor are discontiguous
- * because some SoCs have 5 sensors while others have more
- * but the control registers stay in the same place, i.e
- * directly after the first 5 status registers.
- */
- for (i = 0; i < priv->num_sensors; i++) {
- if (i >= 5)
- priv->sensor[i].status = S0_STATUS_ADDR + 40;
- priv->sensor[i].status += i * 4;
- }
-
- reg_cntl = SW_RST;
- ret = regmap_update_bits(priv->tm_map, CNTL_ADDR, SW_RST, reg_cntl);
- if (ret)
- return ret;
-
- if (priv->num_sensors > 1) {
- reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18);
- reg_cntl &= ~SW_RST;
- ret = regmap_update_bits(priv->tm_map, CONFIG_ADDR,
- CONFIG_MASK, CONFIG);
- } else {
- reg_cntl |= SLP_CLK_ENA_8660 | (MEASURE_PERIOD << 16);
- reg_cntl &= ~CONFIG_MASK_8660;
- reg_cntl |= CONFIG_8660 << CONFIG_SHIFT_8660;
- }
-
- reg_cntl |= GENMASK(priv->num_sensors - 1, 0) << SENSOR0_SHIFT;
- ret = regmap_write(priv->tm_map, CNTL_ADDR, reg_cntl);
- if (ret)
- return ret;
-
- reg_cntl |= EN;
- ret = regmap_write(priv->tm_map, CNTL_ADDR, reg_cntl);
- if (ret)
- return ret;
-
- return 0;
-}
-
static int calibrate_8960(struct tsens_priv *priv)
{
int i;
char *data;
-
- ssize_t num_read = priv->num_sensors;
- struct tsens_sensor *s = priv->sensor;
+ u32 p1[11];
data = qfprom_read(priv->dev, "calib");
if (IS_ERR(data))
@@ -226,60 +187,96 @@ static int calibrate_8960(struct tsens_priv *priv)
if (IS_ERR(data))
return PTR_ERR(data);
- for (i = 0; i < num_read; i++, s++)
- s->offset = data[i];
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = data[i];
+ priv->sensor[i].slope = tsens_msm8960_slope[i];
+ }
+
+ compute_intercept_slope(priv, p1, NULL, ONE_PT_CALIB);
kfree(data);
return 0;
}
-/* Temperature on y axis and ADC-code on x-axis */
-static inline int code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
-{
- int slope, offset;
-
- slope = thermal_zone_get_slope(s->tzd);
- offset = CAL_MDEGC - slope * s->offset;
-
- return adc_code * slope + offset;
-}
-
-static int get_temp_8960(struct tsens_sensor *s, int *temp)
-{
- int ret;
- u32 code, trdy;
- struct tsens_priv *priv = s->priv;
- unsigned long timeout;
-
- timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
- do {
- ret = regmap_read(priv->tm_map, INT_STATUS_ADDR, &trdy);
- if (ret)
- return ret;
- if (!(trdy & TRDY_MASK))
- continue;
- ret = regmap_read(priv->tm_map, s->status, &code);
- if (ret)
- return ret;
- *temp = code_to_mdegC(code, s);
- return 0;
- } while (time_before(jiffies, timeout));
-
- return -ETIMEDOUT;
-}
+static const struct reg_field tsens_8960_regfields[MAX_REGFIELDS] = {
+ /* ----- SROT ------ */
+ /* No VERSION information */
+
+ /* CNTL */
+ [TSENS_EN] = REG_FIELD(CNTL_ADDR, 0, 0),
+ [TSENS_SW_RST] = REG_FIELD(CNTL_ADDR, 1, 1),
+ /* 8960 has 5 sensors, 8660 has 11, we only handle 5 */
+ [SENSOR_EN] = REG_FIELD(CNTL_ADDR, 3, 7),
+
+ /* ----- TM ------ */
+ /* INTERRUPT ENABLE */
+ /* NO INTERRUPT ENABLE */
+
+ /* Single UPPER/LOWER TEMPERATURE THRESHOLD for all sensors */
+ [LOW_THRESH_0] = REG_FIELD(THRESHOLD_ADDR, 0, 7),
+ [UP_THRESH_0] = REG_FIELD(THRESHOLD_ADDR, 8, 15),
+ /* MIN_THRESH_0 and MAX_THRESH_0 are not present in the regfield
+ * Recycle CRIT_THRESH_0 and 1 to set the required regs to hardcoded temp
+ * MIN_THRESH_0 -> CRIT_THRESH_1
+ * MAX_THRESH_0 -> CRIT_THRESH_0
+ */
+ [CRIT_THRESH_1] = REG_FIELD(THRESHOLD_ADDR, 16, 23),
+ [CRIT_THRESH_0] = REG_FIELD(THRESHOLD_ADDR, 24, 31),
+
+ /* UPPER/LOWER INTERRUPT [CLEAR/STATUS] */
+ /* 1 == clear, 0 == normal operation */
+ [LOW_INT_CLEAR_0] = REG_FIELD(CNTL_ADDR, 9, 9),
+ [UP_INT_CLEAR_0] = REG_FIELD(CNTL_ADDR, 10, 10),
+
+ /* NO CRITICAL INTERRUPT SUPPORT on 8960 */
+
+ /* Sn_STATUS */
+ [LAST_TEMP_0] = REG_FIELD(S0_STATUS_OFF, 0, 7),
+ [LAST_TEMP_1] = REG_FIELD(S1_STATUS_OFF, 0, 7),
+ [LAST_TEMP_2] = REG_FIELD(S2_STATUS_OFF, 0, 7),
+ [LAST_TEMP_3] = REG_FIELD(S3_STATUS_OFF, 0, 7),
+ [LAST_TEMP_4] = REG_FIELD(S4_STATUS_OFF, 0, 7),
+ [LAST_TEMP_5] = REG_FIELD(S5_STATUS_OFF, 0, 7),
+ [LAST_TEMP_6] = REG_FIELD(S6_STATUS_OFF, 0, 7),
+ [LAST_TEMP_7] = REG_FIELD(S7_STATUS_OFF, 0, 7),
+ [LAST_TEMP_8] = REG_FIELD(S8_STATUS_OFF, 0, 7),
+ [LAST_TEMP_9] = REG_FIELD(S9_STATUS_OFF, 0, 7),
+ [LAST_TEMP_10] = REG_FIELD(S10_STATUS_OFF, 0, 7),
+
+ /* No VALID field on 8960 */
+ /* TSENS_INT_STATUS bits: 1 == threshold violated */
+ [MIN_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 0, 0),
+ [LOWER_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 1, 1),
+ [UPPER_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 2, 2),
+ /* No CRITICAL field on 8960 */
+ [MAX_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 3, 3),
+
+ /* TRDY: 1=ready, 0=in progress */
+ [TRDY] = REG_FIELD(INT_STATUS_ADDR, 7, 7),
+};
static const struct tsens_ops ops_8960 = {
- .init = init_8960,
+ .init = init_common,
.calibrate = calibrate_8960,
- .get_temp = get_temp_8960,
+ .get_temp = get_temp_common,
.enable = enable_8960,
.disable = disable_8960,
.suspend = suspend_8960,
.resume = resume_8960,
};
-const struct tsens_plat_data data_8960 = {
+static struct tsens_features tsens_8960_feat = {
+ .ver_major = VER_0,
+ .crit_int = 0,
+ .adc = 1,
+ .srot_split = 0,
+ .max_sensors = 11,
+};
+
+struct tsens_plat_data data_8960 = {
.num_sensors = 11,
.ops = &ops_8960,
+ .feat = &tsens_8960_feat,
+ .fields = tsens_8960_regfields,
};
diff --git a/drivers/thermal/qcom/tsens-common.c b/drivers/thermal/qcom/tsens-common.c
deleted file mode 100644
index c8d57ee0a5bb..000000000000
--- a/drivers/thermal/qcom/tsens-common.c
+++ /dev/null
@@ -1,695 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (c) 2015, The Linux Foundation. All rights reserved.
- */
-
-#include <linux/debugfs.h>
-#include <linux/err.h>
-#include <linux/io.h>
-#include <linux/nvmem-consumer.h>
-#include <linux/of_address.h>
-#include <linux/of_platform.h>
-#include <linux/platform_device.h>
-#include <linux/regmap.h>
-#include "tsens.h"
-
-/**
- * struct tsens_irq_data - IRQ status and temperature violations
- * @up_viol: upper threshold violated
- * @up_thresh: upper threshold temperature value
- * @up_irq_mask: mask register for upper threshold irqs
- * @up_irq_clear: clear register for uppper threshold irqs
- * @low_viol: lower threshold violated
- * @low_thresh: lower threshold temperature value
- * @low_irq_mask: mask register for lower threshold irqs
- * @low_irq_clear: clear register for lower threshold irqs
- *
- * Structure containing data about temperature threshold settings and
- * irq status if they were violated.
- */
-struct tsens_irq_data {
- u32 up_viol;
- int up_thresh;
- u32 up_irq_mask;
- u32 up_irq_clear;
- u32 low_viol;
- int low_thresh;
- u32 low_irq_mask;
- u32 low_irq_clear;
-};
-
-char *qfprom_read(struct device *dev, const char *cname)
-{
- struct nvmem_cell *cell;
- ssize_t data;
- char *ret;
-
- cell = nvmem_cell_get(dev, cname);
- if (IS_ERR(cell))
- return ERR_CAST(cell);
-
- ret = nvmem_cell_read(cell, &data);
- nvmem_cell_put(cell);
-
- return ret;
-}
-
-/*
- * Use this function on devices where slope and offset calculations
- * depend on calibration data read from qfprom. On others the slope
- * and offset values are derived from tz->tzp->slope and tz->tzp->offset
- * resp.
- */
-void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
- u32 *p2, u32 mode)
-{
- int i;
- int num, den;
-
- for (i = 0; i < priv->num_sensors; i++) {
- dev_dbg(priv->dev,
- "%s: sensor%d - data_point1:%#x data_point2:%#x\n",
- __func__, i, p1[i], p2[i]);
-
- priv->sensor[i].slope = SLOPE_DEFAULT;
- if (mode == TWO_PT_CALIB) {
- /*
- * slope (m) = adc_code2 - adc_code1 (y2 - y1)/
- * temp_120_degc - temp_30_degc (x2 - x1)
- */
- num = p2[i] - p1[i];
- num *= SLOPE_FACTOR;
- den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
- priv->sensor[i].slope = num / den;
- }
-
- priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
- (CAL_DEGC_PT1 *
- priv->sensor[i].slope);
- dev_dbg(priv->dev, "%s: offset:%d\n", __func__, priv->sensor[i].offset);
- }
-}
-
-static inline u32 degc_to_code(int degc, const struct tsens_sensor *s)
-{
- u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR);
-
- pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc);
- return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE);
-}
-
-static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
-{
- int degc, num, den;
-
- num = (adc_code * SLOPE_FACTOR) - s->offset;
- den = s->slope;
-
- if (num > 0)
- degc = num + (den / 2);
- else if (num < 0)
- degc = num - (den / 2);
- else
- degc = num;
-
- degc /= den;
-
- return degc;
-}
-
-/**
- * tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
- * @s: Pointer to sensor struct
- * @field: Index into regmap_field array pointing to temperature data
- *
- * This function handles temperature returned in ADC code or deciCelsius
- * depending on IP version.
- *
- * Return: Temperature in milliCelsius on success, a negative errno will
- * be returned in error cases
- */
-static int tsens_hw_to_mC(struct tsens_sensor *s, int field)
-{
- struct tsens_priv *priv = s->priv;
- u32 resolution;
- u32 temp = 0;
- int ret;
-
- resolution = priv->fields[LAST_TEMP_0].msb -
- priv->fields[LAST_TEMP_0].lsb;
-
- ret = regmap_field_read(priv->rf[field], &temp);
- if (ret)
- return ret;
-
- /* Convert temperature from ADC code to milliCelsius */
- if (priv->feat->adc)
- return code_to_degc(temp, s) * 1000;
-
- /* deciCelsius -> milliCelsius along with sign extension */
- return sign_extend32(temp, resolution) * 100;
-}
-
-/**
- * tsens_mC_to_hw - Convert temperature to hardware register value
- * @s: Pointer to sensor struct
- * @temp: temperature in milliCelsius to be programmed to hardware
- *
- * This function outputs the value to be written to hardware in ADC code
- * or deciCelsius depending on IP version.
- *
- * Return: ADC code or temperature in deciCelsius.
- */
-static int tsens_mC_to_hw(struct tsens_sensor *s, int temp)
-{
- struct tsens_priv *priv = s->priv;
-
- /* milliC to adc code */
- if (priv->feat->adc)
- return degc_to_code(temp / 1000, s);
-
- /* milliC to deciC */
- return temp / 100;
-}
-
-static inline enum tsens_ver tsens_version(struct tsens_priv *priv)
-{
- return priv->feat->ver_major;
-}
-
-static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
- enum tsens_irq_type irq_type, bool enable)
-{
- u32 index = 0;
-
- switch (irq_type) {
- case UPPER:
- index = UP_INT_CLEAR_0 + hw_id;
- break;
- case LOWER:
- index = LOW_INT_CLEAR_0 + hw_id;
- break;
- }
- regmap_field_write(priv->rf[index], enable ? 0 : 1);
-}
-
-static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
- enum tsens_irq_type irq_type, bool enable)
-{
- u32 index_mask = 0, index_clear = 0;
-
- /*
- * To enable the interrupt flag for a sensor:
- * - clear the mask bit
- * To disable the interrupt flag for a sensor:
- * - Mask further interrupts for this sensor
- * - Write 1 followed by 0 to clear the interrupt
- */
- switch (irq_type) {
- case UPPER:
- index_mask = UP_INT_MASK_0 + hw_id;
- index_clear = UP_INT_CLEAR_0 + hw_id;
- break;
- case LOWER:
- index_mask = LOW_INT_MASK_0 + hw_id;
- index_clear = LOW_INT_CLEAR_0 + hw_id;
- break;
- }
-
- if (enable) {
- regmap_field_write(priv->rf[index_mask], 0);
- } else {
- regmap_field_write(priv->rf[index_mask], 1);
- regmap_field_write(priv->rf[index_clear], 1);
- regmap_field_write(priv->rf[index_clear], 0);
- }
-}
-
-/**
- * tsens_set_interrupt - Set state of an interrupt
- * @priv: Pointer to tsens controller private data
- * @hw_id: Hardware ID aka. sensor number
- * @irq_type: irq_type from enum tsens_irq_type
- * @enable: false = disable, true = enable
- *
- * Call IP-specific function to set state of an interrupt
- *
- * Return: void
- */
-static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
- enum tsens_irq_type irq_type, bool enable)
-{
- dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
- irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
- enable ? "en" : "dis");
- if (tsens_version(priv) > VER_1_X)
- tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
- else
- tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
-}
-
-/**
- * tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
- * @priv: Pointer to tsens controller private data
- * @hw_id: Hardware ID aka. sensor number
- * @d: Pointer to irq state data
- *
- * Return: 0 if threshold was not violated, 1 if it was violated and negative
- * errno in case of errors
- */
-static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id,
- struct tsens_irq_data *d)
-{
- int ret;
-
- ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol);
- if (ret)
- return ret;
- ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol);
- if (ret)
- return ret;
- if (d->up_viol || d->low_viol)
- return 1;
-
- return 0;
-}
-
-static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
- struct tsens_sensor *s, struct tsens_irq_data *d)
-{
- int ret;
-
- ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear);
- if (ret)
- return ret;
- ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear);
- if (ret)
- return ret;
- if (tsens_version(priv) > VER_1_X) {
- ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
- if (ret)
- return ret;
- ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask);
- if (ret)
- return ret;
- } else {
- /* No mask register on older TSENS */
- d->up_irq_mask = 0;
- d->low_irq_mask = 0;
- }
-
- d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
- d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
-
- dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u) | clr(%u|%u) | mask(%u|%u)\n",
- hw_id, __func__, (d->up_viol || d->low_viol) ? "(V)" : "",
- d->low_viol, d->up_viol, d->low_irq_clear, d->up_irq_clear,
- d->low_irq_mask, d->up_irq_mask);
- dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d)\n", hw_id, __func__,
- (d->up_viol || d->low_viol) ? "(violation)" : "",
- d->low_thresh, d->up_thresh);
-
- return 0;
-}
-
-static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
-{
- if (ver > VER_1_X)
- return mask & (1 << hw_id);
-
- /* v1, v0.1 don't have a irq mask register */
- return 0;
-}
-
-/**
- * tsens_irq_thread - Threaded interrupt handler for uplow interrupts
- * @irq: irq number
- * @data: tsens controller private data
- *
- * Check all sensors to find ones that violated their threshold limits. If the
- * temperature is still outside the limits, call thermal_zone_device_update() to
- * update the thresholds, else re-enable the interrupts.
- *
- * The level-triggered interrupt might deassert if the temperature returned to
- * within the threshold limits by the time the handler got scheduled. We
- * consider the irq to have been handled in that case.
- *
- * Return: IRQ_HANDLED
- */
-irqreturn_t tsens_irq_thread(int irq, void *data)
-{
- struct tsens_priv *priv = data;
- struct tsens_irq_data d;
- bool enable = true, disable = false;
- unsigned long flags;
- int temp, ret, i;
-
- for (i = 0; i < priv->num_sensors; i++) {
- bool trigger = false;
- struct tsens_sensor *s = &priv->sensor[i];
- u32 hw_id = s->hw_id;
-
- if (IS_ERR(priv->sensor[i].tzd))
- continue;
- if (!tsens_threshold_violated(priv, hw_id, &d))
- continue;
- ret = get_temp_tsens_valid(s, &temp);
- if (ret) {
- dev_err(priv->dev, "[%u] %s: error reading sensor\n", hw_id, __func__);
- continue;
- }
-
- spin_lock_irqsave(&priv->ul_lock, flags);
-
- tsens_read_irq_state(priv, hw_id, s, &d);
-
- if (d.up_viol &&
- !masked_irq(hw_id, d.up_irq_mask, tsens_version(priv))) {
- tsens_set_interrupt(priv, hw_id, UPPER, disable);
- if (d.up_thresh > temp) {
- dev_dbg(priv->dev, "[%u] %s: re-arm upper\n",
- priv->sensor[i].hw_id, __func__);
- tsens_set_interrupt(priv, hw_id, UPPER, enable);
- } else {
- trigger = true;
- /* Keep irq masked */
- }
- } else if (d.low_viol &&
- !masked_irq(hw_id, d.low_irq_mask, tsens_version(priv))) {
- tsens_set_interrupt(priv, hw_id, LOWER, disable);
- if (d.low_thresh < temp) {
- dev_dbg(priv->dev, "[%u] %s: re-arm low\n",
- priv->sensor[i].hw_id, __func__);
- tsens_set_interrupt(priv, hw_id, LOWER, enable);
- } else {
- trigger = true;
- /* Keep irq masked */
- }
- }
-
- spin_unlock_irqrestore(&priv->ul_lock, flags);
-
- if (trigger) {
- dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n",
- hw_id, __func__, temp);
- thermal_zone_device_update(priv->sensor[i].tzd,
- THERMAL_EVENT_UNSPECIFIED);
- } else {
- dev_dbg(priv->dev, "[%u] %s: no violation: %d\n",
- hw_id, __func__, temp);
- }
- }
-
- return IRQ_HANDLED;
-}
-
-int tsens_set_trips(void *_sensor, int low, int high)
-{
- struct tsens_sensor *s = _sensor;
- struct tsens_priv *priv = s->priv;
- struct device *dev = priv->dev;
- struct tsens_irq_data d;
- unsigned long flags;
- int high_val, low_val, cl_high, cl_low;
- u32 hw_id = s->hw_id;
-
- dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
- hw_id, __func__, low, high);
-
- cl_high = clamp_val(high, -40000, 120000);
- cl_low = clamp_val(low, -40000, 120000);
-
- high_val = tsens_mC_to_hw(s, cl_high);
- low_val = tsens_mC_to_hw(s, cl_low);
-
- spin_lock_irqsave(&priv->ul_lock, flags);
-
- tsens_read_irq_state(priv, hw_id, s, &d);
-
- /* Write the new thresholds and clear the status */
- regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val);
- regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val);
- tsens_set_interrupt(priv, hw_id, LOWER, true);
- tsens_set_interrupt(priv, hw_id, UPPER, true);
-
- spin_unlock_irqrestore(&priv->ul_lock, flags);
-
- dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
- s->hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
-
- return 0;
-}
-
-int tsens_enable_irq(struct tsens_priv *priv)
-{
- int ret;
- int val = tsens_version(priv) > VER_1_X ? 7 : 1;
-
- ret = regmap_field_write(priv->rf[INT_EN], val);
- if (ret < 0)
- dev_err(priv->dev, "%s: failed to enable interrupts\n", __func__);
-
- return ret;
-}
-
-void tsens_disable_irq(struct tsens_priv *priv)
-{
- regmap_field_write(priv->rf[INT_EN], 0);
-}
-
-int get_temp_tsens_valid(struct tsens_sensor *s, int *temp)
-{
- struct tsens_priv *priv = s->priv;
- int hw_id = s->hw_id;
- u32 temp_idx = LAST_TEMP_0 + hw_id;
- u32 valid_idx = VALID_0 + hw_id;
- u32 valid;
- int ret;
-
- ret = regmap_field_read(priv->rf[valid_idx], &valid);
- if (ret)
- return ret;
- while (!valid) {
- /* Valid bit is 0 for 6 AHB clock cycles.
- * At 19.2MHz, 1 AHB clock is ~60ns.
- * We should enter this loop very, very rarely.
- */
- ndelay(400);
- ret = regmap_field_read(priv->rf[valid_idx], &valid);
- if (ret)
- return ret;
- }
-
- /* Valid bit is set, OK to read the temperature */
- *temp = tsens_hw_to_mC(s, temp_idx);
-
- return 0;
-}
-
-int get_temp_common(struct tsens_sensor *s, int *temp)
-{
- struct tsens_priv *priv = s->priv;
- int hw_id = s->hw_id;
- int last_temp = 0, ret;
-
- ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp);
- if (ret)
- return ret;
-
- *temp = code_to_degc(last_temp, s) * 1000;
-
- return 0;
-}
-
-#ifdef CONFIG_DEBUG_FS
-static int dbg_sensors_show(struct seq_file *s, void *data)
-{
- struct platform_device *pdev = s->private;
- struct tsens_priv *priv = platform_get_drvdata(pdev);
- int i;
-
- seq_printf(s, "max: %2d\nnum: %2d\n\n",
- priv->feat->max_sensors, priv->num_sensors);
-
- seq_puts(s, " id slope offset\n--------------------------\n");
- for (i = 0; i < priv->num_sensors; i++) {
- seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id,
- priv->sensor[i].slope, priv->sensor[i].offset);
- }
-
- return 0;
-}
-
-static int dbg_version_show(struct seq_file *s, void *data)
-{
- struct platform_device *pdev = s->private;
- struct tsens_priv *priv = platform_get_drvdata(pdev);
- u32 maj_ver, min_ver, step_ver;
- int ret;
-
- if (tsens_version(priv) > VER_0_1) {
- ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver);
- if (ret)
- return ret;
- ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver);
- if (ret)
- return ret;
- ret = regmap_field_read(priv->rf[VER_STEP], &step_ver);
- if (ret)
- return ret;
- seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver);
- } else {
- seq_puts(s, "0.1.0\n");
- }
-
- return 0;
-}
-
-DEFINE_SHOW_ATTRIBUTE(dbg_version);
-DEFINE_SHOW_ATTRIBUTE(dbg_sensors);
-
-static void tsens_debug_init(struct platform_device *pdev)
-{
- struct tsens_priv *priv = platform_get_drvdata(pdev);
- struct dentry *root, *file;
-
- root = debugfs_lookup("tsens", NULL);
- if (!root)
- priv->debug_root = debugfs_create_dir("tsens", NULL);
- else
- priv->debug_root = root;
-
- file = debugfs_lookup("version", priv->debug_root);
- if (!file)
- debugfs_create_file("version", 0444, priv->debug_root,
- pdev, &dbg_version_fops);
-
- /* A directory for each instance of the TSENS IP */
- priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root);
- debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops);
-}
-#else
-static inline void tsens_debug_init(struct platform_device *pdev) {}
-#endif
-
-static const struct regmap_config tsens_config = {
- .name = "tm",
- .reg_bits = 32,
- .val_bits = 32,
- .reg_stride = 4,
-};
-
-static const struct regmap_config tsens_srot_config = {
- .name = "srot",
- .reg_bits = 32,
- .val_bits = 32,
- .reg_stride = 4,
-};
-
-int __init init_common(struct tsens_priv *priv)
-{
- void __iomem *tm_base, *srot_base;
- struct device *dev = priv->dev;
- struct resource *res;
- u32 enabled;
- int ret, i, j;
- struct platform_device *op = of_find_device_by_node(priv->dev->of_node);
-
- if (!op)
- return -EINVAL;
-
- if (op->num_resources > 1) {
- /* DT with separate SROT and TM address space */
- priv->tm_offset = 0;
- res = platform_get_resource(op, IORESOURCE_MEM, 1);
- srot_base = devm_ioremap_resource(&op->dev, res);
- if (IS_ERR(srot_base)) {
- ret = PTR_ERR(srot_base);
- goto err_put_device;
- }
-
- priv->srot_map = devm_regmap_init_mmio(dev, srot_base,
- &tsens_srot_config);
- if (IS_ERR(priv->srot_map)) {
- ret = PTR_ERR(priv->srot_map);
- goto err_put_device;
- }
- } else {
- /* old DTs where SROT and TM were in a contiguous 2K block */
- priv->tm_offset = 0x1000;
- }
-
- res = platform_get_resource(op, IORESOURCE_MEM, 0);
- tm_base = devm_ioremap_resource(&op->dev, res);
- if (IS_ERR(tm_base)) {
- ret = PTR_ERR(tm_base);
- goto err_put_device;
- }
-
- priv->tm_map = devm_regmap_init_mmio(dev, tm_base, &tsens_config);
- if (IS_ERR(priv->tm_map)) {
- ret = PTR_ERR(priv->tm_map);
- goto err_put_device;
- }
-
- if (tsens_version(priv) > VER_0_1) {
- for (i = VER_MAJOR; i <= VER_STEP; i++) {
- priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map,
- priv->fields[i]);
- if (IS_ERR(priv->rf[i]))
- return PTR_ERR(priv->rf[i]);
- }
- }
-
- priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
- priv->fields[TSENS_EN]);
- if (IS_ERR(priv->rf[TSENS_EN])) {
- ret = PTR_ERR(priv->rf[TSENS_EN]);
- goto err_put_device;
- }
- ret = regmap_field_read(priv->rf[TSENS_EN], &enabled);
- if (ret)
- goto err_put_device;
- if (!enabled) {
- dev_err(dev, "%s: device not enabled\n", __func__);
- ret = -ENODEV;
- goto err_put_device;
- }
-
- priv->rf[SENSOR_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
- priv->fields[SENSOR_EN]);
- if (IS_ERR(priv->rf[SENSOR_EN])) {
- ret = PTR_ERR(priv->rf[SENSOR_EN]);
- goto err_put_device;
- }
- priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map,
- priv->fields[INT_EN]);
- if (IS_ERR(priv->rf[INT_EN])) {
- ret = PTR_ERR(priv->rf[INT_EN]);
- goto err_put_device;
- }
-
- /* This loop might need changes if enum regfield_ids is reordered */
- for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) {
- for (i = 0; i < priv->feat->max_sensors; i++) {
- int idx = j + i;
-
- priv->rf[idx] = devm_regmap_field_alloc(dev, priv->tm_map,
- priv->fields[idx]);
- if (IS_ERR(priv->rf[idx])) {
- ret = PTR_ERR(priv->rf[idx]);
- goto err_put_device;
- }
- }
- }
-
- spin_lock_init(&priv->ul_lock);
- tsens_enable_irq(priv);
- tsens_debug_init(op);
-
- return 0;
-
-err_put_device:
- put_device(&op->dev);
- return ret;
-}
diff --git a/drivers/thermal/qcom/tsens-v0_1.c b/drivers/thermal/qcom/tsens-v0_1.c
index 4b8dd6de02ce..327f37202c69 100644
--- a/drivers/thermal/qcom/tsens-v0_1.c
+++ b/drivers/thermal/qcom/tsens-v0_1.c
@@ -48,6 +48,63 @@
#define MSM8916_CAL_SEL_MASK 0xe0000000
#define MSM8916_CAL_SEL_SHIFT 29
+/* eeprom layout data for 8939 */
+#define MSM8939_BASE0_MASK 0x000000ff
+#define MSM8939_BASE1_MASK 0xff000000
+#define MSM8939_BASE0_SHIFT 0
+#define MSM8939_BASE1_SHIFT 24
+
+#define MSM8939_S0_P1_MASK 0x000001f8
+#define MSM8939_S1_P1_MASK 0x001f8000
+#define MSM8939_S2_P1_MASK_0_4 0xf8000000
+#define MSM8939_S2_P1_MASK_5 0x00000001
+#define MSM8939_S3_P1_MASK 0x00001f80
+#define MSM8939_S4_P1_MASK 0x01f80000
+#define MSM8939_S5_P1_MASK 0x00003f00
+#define MSM8939_S6_P1_MASK 0x03f00000
+#define MSM8939_S7_P1_MASK 0x0000003f
+#define MSM8939_S8_P1_MASK 0x0003f000
+#define MSM8939_S9_P1_MASK 0x07e00000
+
+#define MSM8939_S0_P2_MASK 0x00007e00
+#define MSM8939_S1_P2_MASK 0x07e00000
+#define MSM8939_S2_P2_MASK 0x0000007e
+#define MSM8939_S3_P2_MASK 0x0007e000
+#define MSM8939_S4_P2_MASK 0x7e000000
+#define MSM8939_S5_P2_MASK 0x000fc000
+#define MSM8939_S6_P2_MASK 0xfc000000
+#define MSM8939_S7_P2_MASK 0x00000fc0
+#define MSM8939_S8_P2_MASK 0x00fc0000
+#define MSM8939_S9_P2_MASK_0_4 0xf8000000
+#define MSM8939_S9_P2_MASK_5 0x00002000
+
+#define MSM8939_S0_P1_SHIFT 3
+#define MSM8939_S1_P1_SHIFT 15
+#define MSM8939_S2_P1_SHIFT_0_4 27
+#define MSM8939_S2_P1_SHIFT_5 0
+#define MSM8939_S3_P1_SHIFT 7
+#define MSM8939_S4_P1_SHIFT 19
+#define MSM8939_S5_P1_SHIFT 8
+#define MSM8939_S6_P1_SHIFT 20
+#define MSM8939_S7_P1_SHIFT 0
+#define MSM8939_S8_P1_SHIFT 12
+#define MSM8939_S9_P1_SHIFT 21
+
+#define MSM8939_S0_P2_SHIFT 9
+#define MSM8939_S1_P2_SHIFT 21
+#define MSM8939_S2_P2_SHIFT 1
+#define MSM8939_S3_P2_SHIFT 13
+#define MSM8939_S4_P2_SHIFT 25
+#define MSM8939_S5_P2_SHIFT 14
+#define MSM8939_S6_P2_SHIFT 26
+#define MSM8939_S7_P2_SHIFT 6
+#define MSM8939_S8_P2_SHIFT 18
+#define MSM8939_S9_P2_SHIFT_0_4 27
+#define MSM8939_S9_P2_SHIFT_5 13
+
+#define MSM8939_CAL_SEL_MASK 0x7
+#define MSM8939_CAL_SEL_SHIFT 0
+
/* eeprom layout data for 8974 */
#define BASE1_MASK 0xff
#define S0_P1_MASK 0x3f00
@@ -133,6 +190,39 @@
#define BIT_APPEND 0x3
+/* eeprom layout data for mdm9607 */
+#define MDM9607_BASE0_MASK 0x000000ff
+#define MDM9607_BASE1_MASK 0x000ff000
+#define MDM9607_BASE0_SHIFT 0
+#define MDM9607_BASE1_SHIFT 12
+
+#define MDM9607_S0_P1_MASK 0x00003f00
+#define MDM9607_S1_P1_MASK 0x03f00000
+#define MDM9607_S2_P1_MASK 0x0000003f
+#define MDM9607_S3_P1_MASK 0x0003f000
+#define MDM9607_S4_P1_MASK 0x0000003f
+
+#define MDM9607_S0_P2_MASK 0x000fc000
+#define MDM9607_S1_P2_MASK 0xfc000000
+#define MDM9607_S2_P2_MASK 0x00000fc0
+#define MDM9607_S3_P2_MASK 0x00fc0000
+#define MDM9607_S4_P2_MASK 0x00000fc0
+
+#define MDM9607_S0_P1_SHIFT 8
+#define MDM9607_S1_P1_SHIFT 20
+#define MDM9607_S2_P1_SHIFT 0
+#define MDM9607_S3_P1_SHIFT 12
+#define MDM9607_S4_P1_SHIFT 0
+
+#define MDM9607_S0_P2_SHIFT 14
+#define MDM9607_S1_P2_SHIFT 26
+#define MDM9607_S2_P2_SHIFT 6
+#define MDM9607_S3_P2_SHIFT 18
+#define MDM9607_S4_P2_SHIFT 6
+
+#define MDM9607_CAL_SEL_MASK 0x00700000
+#define MDM9607_CAL_SEL_SHIFT 20
+
static int calibrate_8916(struct tsens_priv *priv)
{
int base0 = 0, base1 = 0, i;
@@ -163,7 +253,7 @@ static int calibrate_8916(struct tsens_priv *priv)
p2[4] = (qfprom_cdata[1] & MSM8916_S4_P2_MASK) >> MSM8916_S4_P2_SHIFT;
for (i = 0; i < priv->num_sensors; i++)
p2[i] = ((base1 + p2[i]) << 3);
- /* Fall through */
+ fallthrough;
case ONE_PT_CALIB2:
base0 = (qfprom_cdata[0] & MSM8916_BASE0_MASK);
p1[0] = (qfprom_cdata[0] & MSM8916_S0_P1_MASK) >> MSM8916_S0_P1_SHIFT;
@@ -189,6 +279,76 @@ static int calibrate_8916(struct tsens_priv *priv)
return 0;
}
+static int calibrate_8939(struct tsens_priv *priv)
+{
+ int base0 = 0, base1 = 0, i;
+ u32 p1[10], p2[10];
+ int mode = 0;
+ u32 *qfprom_cdata;
+ u32 cdata[6];
+
+ qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
+
+ /* Mapping between qfprom nvmem and calibration data */
+ cdata[0] = qfprom_cdata[12];
+ cdata[1] = qfprom_cdata[13];
+ cdata[2] = qfprom_cdata[0];
+ cdata[3] = qfprom_cdata[1];
+ cdata[4] = qfprom_cdata[22];
+ cdata[5] = qfprom_cdata[21];
+
+ mode = (cdata[0] & MSM8939_CAL_SEL_MASK) >> MSM8939_CAL_SEL_SHIFT;
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base1 = (cdata[3] & MSM8939_BASE1_MASK) >> MSM8939_BASE1_SHIFT;
+ p2[0] = (cdata[0] & MSM8939_S0_P2_MASK) >> MSM8939_S0_P2_SHIFT;
+ p2[1] = (cdata[0] & MSM8939_S1_P2_MASK) >> MSM8939_S1_P2_SHIFT;
+ p2[2] = (cdata[1] & MSM8939_S2_P2_MASK) >> MSM8939_S2_P2_SHIFT;
+ p2[3] = (cdata[1] & MSM8939_S3_P2_MASK) >> MSM8939_S3_P2_SHIFT;
+ p2[4] = (cdata[1] & MSM8939_S4_P2_MASK) >> MSM8939_S4_P2_SHIFT;
+ p2[5] = (cdata[2] & MSM8939_S5_P2_MASK) >> MSM8939_S5_P2_SHIFT;
+ p2[6] = (cdata[2] & MSM8939_S6_P2_MASK) >> MSM8939_S6_P2_SHIFT;
+ p2[7] = (cdata[3] & MSM8939_S7_P2_MASK) >> MSM8939_S7_P2_SHIFT;
+ p2[8] = (cdata[3] & MSM8939_S8_P2_MASK) >> MSM8939_S8_P2_SHIFT;
+ p2[9] = (cdata[4] & MSM8939_S9_P2_MASK_0_4) >> MSM8939_S9_P2_SHIFT_0_4;
+ p2[9] |= ((cdata[5] & MSM8939_S9_P2_MASK_5) >> MSM8939_S9_P2_SHIFT_5) << 5;
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = (base1 + p2[i]) << 2;
+ fallthrough;
+ case ONE_PT_CALIB2:
+ base0 = (cdata[2] & MSM8939_BASE0_MASK) >> MSM8939_BASE0_SHIFT;
+ p1[0] = (cdata[0] & MSM8939_S0_P1_MASK) >> MSM8939_S0_P1_SHIFT;
+ p1[1] = (cdata[0] & MSM8939_S1_P1_MASK) >> MSM8939_S1_P1_SHIFT;
+ p1[2] = (cdata[0] & MSM8939_S2_P1_MASK_0_4) >> MSM8939_S2_P1_SHIFT_0_4;
+ p1[2] |= ((cdata[1] & MSM8939_S2_P1_MASK_5) >> MSM8939_S2_P1_SHIFT_5) << 5;
+ p1[3] = (cdata[1] & MSM8939_S3_P1_MASK) >> MSM8939_S3_P1_SHIFT;
+ p1[4] = (cdata[1] & MSM8939_S4_P1_MASK) >> MSM8939_S4_P1_SHIFT;
+ p1[5] = (cdata[2] & MSM8939_S5_P1_MASK) >> MSM8939_S5_P1_SHIFT;
+ p1[6] = (cdata[2] & MSM8939_S6_P1_MASK) >> MSM8939_S6_P1_SHIFT;
+ p1[7] = (cdata[3] & MSM8939_S7_P1_MASK) >> MSM8939_S7_P1_SHIFT;
+ p1[8] = (cdata[3] & MSM8939_S8_P1_MASK) >> MSM8939_S8_P1_SHIFT;
+ p1[9] = (cdata[4] & MSM8939_S9_P1_MASK) >> MSM8939_S9_P1_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = ((base0) + p1[i]) << 2;
+ break;
+ default:
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ break;
+ }
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(qfprom_cdata);
+
+ return 0;
+}
+
static int calibrate_8974(struct tsens_priv *priv)
{
int base1 = 0, base2 = 0, i;
@@ -228,7 +388,7 @@ static int calibrate_8974(struct tsens_priv *priv)
p2[8] = (calib[5] & S8_P2_BKP_MASK) >> S8_P2_BKP_SHIFT;
p2[9] = (calib[5] & S9_P2_BKP_MASK) >> S9_P2_BKP_SHIFT;
p2[10] = (calib[5] & S10_P2_BKP_MASK) >> S10_P2_BKP_SHIFT;
- /* Fall through */
+ fallthrough;
case ONE_PT_CALIB:
case ONE_PT_CALIB2:
base1 = bkp[0] & BASE1_MASK;
@@ -263,7 +423,7 @@ static int calibrate_8974(struct tsens_priv *priv)
p2[8] = (calib[4] & S8_P2_MASK) >> S8_P2_SHIFT;
p2[9] = (calib[4] & S9_P2_MASK) >> S9_P2_SHIFT;
p2[10] = (calib[4] & S10_P2_MASK) >> S10_P2_SHIFT;
- /* Fall through */
+ fallthrough;
case ONE_PT_CALIB:
case ONE_PT_CALIB2:
base1 = calib[0] & BASE1_MASK;
@@ -293,7 +453,7 @@ static int calibrate_8974(struct tsens_priv *priv)
p2[i] <<= 2;
p2[i] |= BIT_APPEND;
}
- /* Fall through */
+ fallthrough;
case ONE_PT_CALIB2:
for (i = 0; i < priv->num_sensors; i++) {
p1[i] += base1;
@@ -325,9 +485,58 @@ static int calibrate_8974(struct tsens_priv *priv)
return 0;
}
-/* v0.1: 8916, 8974 */
+static int calibrate_9607(struct tsens_priv *priv)
+{
+ int base, i;
+ u32 p1[5], p2[5];
+ int mode = 0;
+ u32 *qfprom_cdata;
+
+ qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
-static const struct tsens_features tsens_v0_1_feat = {
+ mode = (qfprom_cdata[2] & MDM9607_CAL_SEL_MASK) >> MDM9607_CAL_SEL_SHIFT;
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base = (qfprom_cdata[2] & MDM9607_BASE1_MASK) >> MDM9607_BASE1_SHIFT;
+ p2[0] = (qfprom_cdata[0] & MDM9607_S0_P2_MASK) >> MDM9607_S0_P2_SHIFT;
+ p2[1] = (qfprom_cdata[0] & MDM9607_S1_P2_MASK) >> MDM9607_S1_P2_SHIFT;
+ p2[2] = (qfprom_cdata[1] & MDM9607_S2_P2_MASK) >> MDM9607_S2_P2_SHIFT;
+ p2[3] = (qfprom_cdata[1] & MDM9607_S3_P2_MASK) >> MDM9607_S3_P2_SHIFT;
+ p2[4] = (qfprom_cdata[2] & MDM9607_S4_P2_MASK) >> MDM9607_S4_P2_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = ((base + p2[i]) << 2);
+ fallthrough;
+ case ONE_PT_CALIB2:
+ base = (qfprom_cdata[0] & MDM9607_BASE0_MASK);
+ p1[0] = (qfprom_cdata[0] & MDM9607_S0_P1_MASK) >> MDM9607_S0_P1_SHIFT;
+ p1[1] = (qfprom_cdata[0] & MDM9607_S1_P1_MASK) >> MDM9607_S1_P1_SHIFT;
+ p1[2] = (qfprom_cdata[1] & MDM9607_S2_P1_MASK) >> MDM9607_S2_P1_SHIFT;
+ p1[3] = (qfprom_cdata[1] & MDM9607_S3_P1_MASK) >> MDM9607_S3_P1_SHIFT;
+ p1[4] = (qfprom_cdata[2] & MDM9607_S4_P1_MASK) >> MDM9607_S4_P1_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = ((base + p1[i]) << 2);
+ break;
+ default:
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ break;
+ }
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(qfprom_cdata);
+
+ return 0;
+}
+
+/* v0.1: 8916, 8939, 8974, 9607 */
+
+static struct tsens_features tsens_v0_1_feat = {
.ver_major = VER_0_1,
.crit_int = 0,
.adc = 1,
@@ -377,7 +586,7 @@ static const struct tsens_ops ops_8916 = {
.get_temp = get_temp_common,
};
-const struct tsens_plat_data data_8916 = {
+struct tsens_plat_data data_8916 = {
.num_sensors = 5,
.ops = &ops_8916,
.hw_ids = (unsigned int []){0, 1, 2, 4, 5 },
@@ -386,15 +595,44 @@ const struct tsens_plat_data data_8916 = {
.fields = tsens_v0_1_regfields,
};
+static const struct tsens_ops ops_8939 = {
+ .init = init_common,
+ .calibrate = calibrate_8939,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_8939 = {
+ .num_sensors = 10,
+ .ops = &ops_8939,
+ .hw_ids = (unsigned int []){ 0, 1, 2, 3, 5, 6, 7, 8, 9, 10 },
+
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
+
static const struct tsens_ops ops_8974 = {
.init = init_common,
.calibrate = calibrate_8974,
.get_temp = get_temp_common,
};
-const struct tsens_plat_data data_8974 = {
+struct tsens_plat_data data_8974 = {
.num_sensors = 11,
.ops = &ops_8974,
.feat = &tsens_v0_1_feat,
.fields = tsens_v0_1_regfields,
};
+
+static const struct tsens_ops ops_9607 = {
+ .init = init_common,
+ .calibrate = calibrate_9607,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_9607 = {
+ .num_sensors = 5,
+ .ops = &ops_9607,
+ .hw_ids = (unsigned int []){ 0, 1, 2, 3, 4 },
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
diff --git a/drivers/thermal/qcom/tsens-v1.c b/drivers/thermal/qcom/tsens-v1.c
index bd2ddb684a45..573e261ccca7 100644
--- a/drivers/thermal/qcom/tsens-v1.c
+++ b/drivers/thermal/qcom/tsens-v1.c
@@ -202,7 +202,7 @@ static int calibrate_v1(struct tsens_priv *priv)
p2[9] = (qfprom_cdata[3] & S9_P2_MASK) >> S9_P2_SHIFT;
for (i = 0; i < priv->num_sensors; i++)
p2[i] = ((base1 + p2[i]) << 2);
- /* Fall through */
+ fallthrough;
case ONE_PT_CALIB2:
base0 = (qfprom_cdata[4] & BASE0_MASK) >> BASE0_SHIFT;
p1[0] = (qfprom_cdata[0] & S0_P1_MASK) >> S0_P1_SHIFT;
@@ -263,7 +263,7 @@ static int calibrate_8976(struct tsens_priv *priv)
for (i = 0; i < priv->num_sensors; i++)
p2[i] = ((base1 + p2[i]) << 2);
- /* Fall through */
+ fallthrough;
case ONE_PT_CALIB2:
base0 = qfprom_cdata[0] & MSM8976_BASE0_MASK;
p1[0] = (qfprom_cdata[0] & MSM8976_S0_P1_MASK) >> MSM8976_S0_P1_SHIFT;
@@ -299,7 +299,7 @@ static int calibrate_8976(struct tsens_priv *priv)
/* v1.x: msm8956,8976,qcs404,405 */
-static const struct tsens_features tsens_v1_feat = {
+static struct tsens_features tsens_v1_feat = {
.ver_major = VER_1_X,
.crit_int = 0,
.adc = 1,
@@ -368,7 +368,7 @@ static const struct tsens_ops ops_generic_v1 = {
.get_temp = get_temp_tsens_valid,
};
-const struct tsens_plat_data data_tsens_v1 = {
+struct tsens_plat_data data_tsens_v1 = {
.ops = &ops_generic_v1,
.feat = &tsens_v1_feat,
.fields = tsens_v1_regfields,
@@ -380,11 +380,11 @@ static const struct tsens_ops ops_8976 = {
.get_temp = get_temp_tsens_valid,
};
-/* Valid for both MSM8956 and MSM8976. Sensor ID 3 is unused. */
-const struct tsens_plat_data data_8976 = {
+/* Valid for both MSM8956 and MSM8976. */
+struct tsens_plat_data data_8976 = {
.num_sensors = 11,
.ops = &ops_8976,
- .hw_ids = (unsigned int[]){0, 1, 2, 4, 5, 6, 7, 8, 9, 10},
+ .hw_ids = (unsigned int[]){0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
.feat = &tsens_v1_feat,
.fields = tsens_v1_regfields,
};
diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
index a4d15e1abfdd..b293ed32174b 100644
--- a/drivers/thermal/qcom/tsens-v2.c
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -24,10 +24,11 @@
#define TM_Sn_CRITICAL_THRESHOLD_OFF 0x0060
#define TM_Sn_STATUS_OFF 0x00a0
#define TM_TRDY_OFF 0x00e4
+#define TM_WDOG_LOG_OFF 0x013c
/* v2.x: 8996, 8998, sdm845 */
-static const struct tsens_features tsens_v2_feat = {
+static struct tsens_features tsens_v2_feat = {
.ver_major = VER_2_X,
.crit_int = 1,
.adc = 0,
@@ -51,8 +52,9 @@ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
[INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 2),
/* TEMPERATURE THRESHOLDS */
- REG_FIELD_FOR_EACH_SENSOR16(LOW_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 0, 11),
- REG_FIELD_FOR_EACH_SENSOR16(UP_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 12, 23),
+ REG_FIELD_FOR_EACH_SENSOR16(LOW_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 0, 11),
+ REG_FIELD_FOR_EACH_SENSOR16(UP_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 12, 23),
+ REG_FIELD_FOR_EACH_SENSOR16(CRIT_THRESH, TM_Sn_CRITICAL_THRESHOLD_OFF, 0, 11),
/* INTERRUPTS [CLEAR/STATUS/MASK] */
REG_FIELD_SPLIT_BITS_0_15(LOW_INT_STATUS, TM_UPPER_LOWER_INT_STATUS_OFF),
@@ -61,6 +63,18 @@ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
REG_FIELD_SPLIT_BITS_16_31(UP_INT_STATUS, TM_UPPER_LOWER_INT_STATUS_OFF),
REG_FIELD_SPLIT_BITS_16_31(UP_INT_CLEAR, TM_UPPER_LOWER_INT_CLEAR_OFF),
REG_FIELD_SPLIT_BITS_16_31(UP_INT_MASK, TM_UPPER_LOWER_INT_MASK_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(CRIT_INT_STATUS, TM_CRITICAL_INT_STATUS_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(CRIT_INT_CLEAR, TM_CRITICAL_INT_CLEAR_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(CRIT_INT_MASK, TM_CRITICAL_INT_MASK_OFF),
+
+ /* WATCHDOG on v2.3 or later */
+ [WDOG_BARK_STATUS] = REG_FIELD(TM_CRITICAL_INT_STATUS_OFF, 31, 31),
+ [WDOG_BARK_CLEAR] = REG_FIELD(TM_CRITICAL_INT_CLEAR_OFF, 31, 31),
+ [WDOG_BARK_MASK] = REG_FIELD(TM_CRITICAL_INT_MASK_OFF, 31, 31),
+ [CC_MON_STATUS] = REG_FIELD(TM_CRITICAL_INT_STATUS_OFF, 30, 30),
+ [CC_MON_CLEAR] = REG_FIELD(TM_CRITICAL_INT_CLEAR_OFF, 30, 30),
+ [CC_MON_MASK] = REG_FIELD(TM_CRITICAL_INT_MASK_OFF, 30, 30),
+ [WDOG_BARK_COUNT] = REG_FIELD(TM_WDOG_LOG_OFF, 0, 7),
/* Sn_STATUS */
REG_FIELD_FOR_EACH_SENSOR16(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 11),
@@ -81,14 +95,14 @@ static const struct tsens_ops ops_generic_v2 = {
.get_temp = get_temp_tsens_valid,
};
-const struct tsens_plat_data data_tsens_v2 = {
+struct tsens_plat_data data_tsens_v2 = {
.ops = &ops_generic_v2,
.feat = &tsens_v2_feat,
.fields = tsens_v2_regfields,
};
/* Kept around for backward compatibility with old msm8996.dtsi */
-const struct tsens_plat_data data_8996 = {
+struct tsens_plat_data data_8996 = {
.num_sensors = 13,
.ops = &ops_generic_v2,
.feat = &tsens_v2_feat,
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
index 0e7cf5236932..b1b10005fb28 100644
--- a/drivers/thermal/qcom/tsens.c
+++ b/drivers/thermal/qcom/tsens.c
@@ -1,36 +1,936 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2019, 2020, Linaro Ltd.
*/
#include <linux/debugfs.h>
#include <linux/err.h>
+#include <linux/io.h>
#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
#include <linux/of.h>
+#include <linux/of_address.h>
#include <linux/of_platform.h>
+#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/thermal.h>
+#include "../thermal_hwmon.h"
#include "tsens.h"
-static int tsens_get_temp(void *data, int *temp)
+/**
+ * struct tsens_irq_data - IRQ status and temperature violations
+ * @up_viol: upper threshold violated
+ * @up_thresh: upper threshold temperature value
+ * @up_irq_mask: mask register for upper threshold irqs
+ * @up_irq_clear: clear register for uppper threshold irqs
+ * @low_viol: lower threshold violated
+ * @low_thresh: lower threshold temperature value
+ * @low_irq_mask: mask register for lower threshold irqs
+ * @low_irq_clear: clear register for lower threshold irqs
+ * @crit_viol: critical threshold violated
+ * @crit_thresh: critical threshold temperature value
+ * @crit_irq_mask: mask register for critical threshold irqs
+ * @crit_irq_clear: clear register for critical threshold irqs
+ *
+ * Structure containing data about temperature threshold settings and
+ * irq status if they were violated.
+ */
+struct tsens_irq_data {
+ u32 up_viol;
+ int up_thresh;
+ u32 up_irq_mask;
+ u32 up_irq_clear;
+ u32 low_viol;
+ int low_thresh;
+ u32 low_irq_mask;
+ u32 low_irq_clear;
+ u32 crit_viol;
+ u32 crit_thresh;
+ u32 crit_irq_mask;
+ u32 crit_irq_clear;
+};
+
+char *qfprom_read(struct device *dev, const char *cname)
+{
+ struct nvmem_cell *cell;
+ ssize_t data;
+ char *ret;
+
+ cell = nvmem_cell_get(dev, cname);
+ if (IS_ERR(cell))
+ return ERR_CAST(cell);
+
+ ret = nvmem_cell_read(cell, &data);
+ nvmem_cell_put(cell);
+
+ return ret;
+}
+
+/*
+ * Use this function on devices where slope and offset calculations
+ * depend on calibration data read from qfprom. On others the slope
+ * and offset values are derived from tz->tzp->slope and tz->tzp->offset
+ * resp.
+ */
+void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
+ u32 *p2, u32 mode)
+{
+ int i;
+ int num, den;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ dev_dbg(priv->dev,
+ "%s: sensor%d - data_point1:%#x data_point2:%#x\n",
+ __func__, i, p1[i], p2[i]);
+
+ if (!priv->sensor[i].slope)
+ priv->sensor[i].slope = SLOPE_DEFAULT;
+ if (mode == TWO_PT_CALIB) {
+ /*
+ * slope (m) = adc_code2 - adc_code1 (y2 - y1)/
+ * temp_120_degc - temp_30_degc (x2 - x1)
+ */
+ num = p2[i] - p1[i];
+ num *= SLOPE_FACTOR;
+ den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
+ priv->sensor[i].slope = num / den;
+ }
+
+ priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
+ (CAL_DEGC_PT1 *
+ priv->sensor[i].slope);
+ dev_dbg(priv->dev, "%s: offset:%d\n", __func__,
+ priv->sensor[i].offset);
+ }
+}
+
+static inline u32 degc_to_code(int degc, const struct tsens_sensor *s)
+{
+ u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR);
+
+ pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc);
+ return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE);
+}
+
+static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
+{
+ int degc, num, den;
+
+ num = (adc_code * SLOPE_FACTOR) - s->offset;
+ den = s->slope;
+
+ if (num > 0)
+ degc = num + (den / 2);
+ else if (num < 0)
+ degc = num - (den / 2);
+ else
+ degc = num;
+
+ degc /= den;
+
+ return degc;
+}
+
+/**
+ * tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
+ * @s: Pointer to sensor struct
+ * @field: Index into regmap_field array pointing to temperature data
+ *
+ * This function handles temperature returned in ADC code or deciCelsius
+ * depending on IP version.
+ *
+ * Return: Temperature in milliCelsius on success, a negative errno will
+ * be returned in error cases
+ */
+static int tsens_hw_to_mC(const struct tsens_sensor *s, int field)
{
- struct tsens_sensor *s = data;
struct tsens_priv *priv = s->priv;
+ u32 resolution;
+ u32 temp = 0;
+ int ret;
- return priv->ops->get_temp(s, temp);
+ resolution = priv->fields[LAST_TEMP_0].msb -
+ priv->fields[LAST_TEMP_0].lsb;
+
+ ret = regmap_field_read(priv->rf[field], &temp);
+ if (ret)
+ return ret;
+
+ /* Convert temperature from ADC code to milliCelsius */
+ if (priv->feat->adc)
+ return code_to_degc(temp, s) * 1000;
+
+ /* deciCelsius -> milliCelsius along with sign extension */
+ return sign_extend32(temp, resolution) * 100;
+}
+
+/**
+ * tsens_mC_to_hw - Convert temperature to hardware register value
+ * @s: Pointer to sensor struct
+ * @temp: temperature in milliCelsius to be programmed to hardware
+ *
+ * This function outputs the value to be written to hardware in ADC code
+ * or deciCelsius depending on IP version.
+ *
+ * Return: ADC code or temperature in deciCelsius.
+ */
+static int tsens_mC_to_hw(const struct tsens_sensor *s, int temp)
+{
+ struct tsens_priv *priv = s->priv;
+
+ /* milliC to adc code */
+ if (priv->feat->adc)
+ return degc_to_code(temp / 1000, s);
+
+ /* milliC to deciC */
+ return temp / 100;
+}
+
+static inline enum tsens_ver tsens_version(struct tsens_priv *priv)
+{
+ return priv->feat->ver_major;
+}
+
+static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
+ enum tsens_irq_type irq_type, bool enable)
+{
+ u32 index = 0;
+
+ switch (irq_type) {
+ case UPPER:
+ index = UP_INT_CLEAR_0 + hw_id;
+ break;
+ case LOWER:
+ index = LOW_INT_CLEAR_0 + hw_id;
+ break;
+ case CRITICAL:
+ /* No critical interrupts before v2 */
+ return;
+ }
+ regmap_field_write(priv->rf[index], enable ? 0 : 1);
+}
+
+static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
+ enum tsens_irq_type irq_type, bool enable)
+{
+ u32 index_mask = 0, index_clear = 0;
+
+ /*
+ * To enable the interrupt flag for a sensor:
+ * - clear the mask bit
+ * To disable the interrupt flag for a sensor:
+ * - Mask further interrupts for this sensor
+ * - Write 1 followed by 0 to clear the interrupt
+ */
+ switch (irq_type) {
+ case UPPER:
+ index_mask = UP_INT_MASK_0 + hw_id;
+ index_clear = UP_INT_CLEAR_0 + hw_id;
+ break;
+ case LOWER:
+ index_mask = LOW_INT_MASK_0 + hw_id;
+ index_clear = LOW_INT_CLEAR_0 + hw_id;
+ break;
+ case CRITICAL:
+ index_mask = CRIT_INT_MASK_0 + hw_id;
+ index_clear = CRIT_INT_CLEAR_0 + hw_id;
+ break;
+ }
+
+ if (enable) {
+ regmap_field_write(priv->rf[index_mask], 0);
+ } else {
+ regmap_field_write(priv->rf[index_mask], 1);
+ regmap_field_write(priv->rf[index_clear], 1);
+ regmap_field_write(priv->rf[index_clear], 0);
+ }
+}
+
+/**
+ * tsens_set_interrupt - Set state of an interrupt
+ * @priv: Pointer to tsens controller private data
+ * @hw_id: Hardware ID aka. sensor number
+ * @irq_type: irq_type from enum tsens_irq_type
+ * @enable: false = disable, true = enable
+ *
+ * Call IP-specific function to set state of an interrupt
+ *
+ * Return: void
+ */
+static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
+ enum tsens_irq_type irq_type, bool enable)
+{
+ dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
+ irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
+ enable ? "en" : "dis");
+ if (tsens_version(priv) > VER_1_X)
+ tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
+ else
+ tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
+}
+
+/**
+ * tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
+ * @priv: Pointer to tsens controller private data
+ * @hw_id: Hardware ID aka. sensor number
+ * @d: Pointer to irq state data
+ *
+ * Return: 0 if threshold was not violated, 1 if it was violated and negative
+ * errno in case of errors
+ */
+static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id,
+ struct tsens_irq_data *d)
+{
+ int ret;
+
+ ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol);
+ if (ret)
+ return ret;
+
+ if (priv->feat->crit_int) {
+ ret = regmap_field_read(priv->rf[CRITICAL_STATUS_0 + hw_id],
+ &d->crit_viol);
+ if (ret)
+ return ret;
+ }
+
+ if (d->up_viol || d->low_viol || d->crit_viol)
+ return 1;
+
+ return 0;
+}
+
+static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
+ const struct tsens_sensor *s,
+ struct tsens_irq_data *d)
+{
+ int ret;
+
+ ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear);
+ if (ret)
+ return ret;
+ if (tsens_version(priv) > VER_1_X) {
+ ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id],
+ &d->crit_irq_clear);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id],
+ &d->crit_irq_mask);
+ if (ret)
+ return ret;
+
+ d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id);
+ } else {
+ /* No mask register on older TSENS */
+ d->up_irq_mask = 0;
+ d->low_irq_mask = 0;
+ d->crit_irq_clear = 0;
+ d->crit_irq_mask = 0;
+ d->crit_thresh = 0;
+ }
+
+ d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
+ d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
+
+ dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n",
+ hw_id, __func__,
+ (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
+ d->low_viol, d->up_viol, d->crit_viol,
+ d->low_irq_clear, d->up_irq_clear, d->crit_irq_clear,
+ d->low_irq_mask, d->up_irq_mask, d->crit_irq_mask);
+ dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d:%d)\n", hw_id, __func__,
+ (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
+ d->low_thresh, d->up_thresh, d->crit_thresh);
+
+ return 0;
+}
+
+static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
+{
+ if (ver > VER_1_X)
+ return mask & (1 << hw_id);
+
+ /* v1, v0.1 don't have a irq mask register */
+ return 0;
+}
+
+/**
+ * tsens_critical_irq_thread() - Threaded handler for critical interrupts
+ * @irq: irq number
+ * @data: tsens controller private data
+ *
+ * Check FSM watchdog bark status and clear if needed.
+ * Check all sensors to find ones that violated their critical threshold limits.
+ * Clear and then re-enable the interrupt.
+ *
+ * The level-triggered interrupt might deassert if the temperature returned to
+ * within the threshold limits by the time the handler got scheduled. We
+ * consider the irq to have been handled in that case.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t tsens_critical_irq_thread(int irq, void *data)
+{
+ struct tsens_priv *priv = data;
+ struct tsens_irq_data d;
+ int temp, ret, i;
+ u32 wdog_status, wdog_count;
+
+ if (priv->feat->has_watchdog) {
+ ret = regmap_field_read(priv->rf[WDOG_BARK_STATUS],
+ &wdog_status);
+ if (ret)
+ return ret;
+
+ if (wdog_status) {
+ /* Clear WDOG interrupt */
+ regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 1);
+ regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 0);
+ ret = regmap_field_read(priv->rf[WDOG_BARK_COUNT],
+ &wdog_count);
+ if (ret)
+ return ret;
+ if (wdog_count)
+ dev_dbg(priv->dev, "%s: watchdog count: %d\n",
+ __func__, wdog_count);
+
+ /* Fall through to handle critical interrupts if any */
+ }
+ }
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ const struct tsens_sensor *s = &priv->sensor[i];
+ u32 hw_id = s->hw_id;
+
+ if (!s->tzd)
+ continue;
+ if (!tsens_threshold_violated(priv, hw_id, &d))
+ continue;
+ ret = get_temp_tsens_valid(s, &temp);
+ if (ret) {
+ dev_err(priv->dev, "[%u] %s: error reading sensor\n",
+ hw_id, __func__);
+ continue;
+ }
+
+ tsens_read_irq_state(priv, hw_id, s, &d);
+ if (d.crit_viol &&
+ !masked_irq(hw_id, d.crit_irq_mask, tsens_version(priv))) {
+ /* Mask critical interrupts, unused on Linux */
+ tsens_set_interrupt(priv, hw_id, CRITICAL, false);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * tsens_irq_thread - Threaded interrupt handler for uplow interrupts
+ * @irq: irq number
+ * @data: tsens controller private data
+ *
+ * Check all sensors to find ones that violated their threshold limits. If the
+ * temperature is still outside the limits, call thermal_zone_device_update() to
+ * update the thresholds, else re-enable the interrupts.
+ *
+ * The level-triggered interrupt might deassert if the temperature returned to
+ * within the threshold limits by the time the handler got scheduled. We
+ * consider the irq to have been handled in that case.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t tsens_irq_thread(int irq, void *data)
+{
+ struct tsens_priv *priv = data;
+ struct tsens_irq_data d;
+ bool enable = true, disable = false;
+ unsigned long flags;
+ int temp, ret, i;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ bool trigger = false;
+ const struct tsens_sensor *s = &priv->sensor[i];
+ u32 hw_id = s->hw_id;
+
+ if (!s->tzd)
+ continue;
+ if (!tsens_threshold_violated(priv, hw_id, &d))
+ continue;
+ ret = get_temp_tsens_valid(s, &temp);
+ if (ret) {
+ dev_err(priv->dev, "[%u] %s: error reading sensor\n",
+ hw_id, __func__);
+ continue;
+ }
+
+ spin_lock_irqsave(&priv->ul_lock, flags);
+
+ tsens_read_irq_state(priv, hw_id, s, &d);
+
+ if (d.up_viol &&
+ !masked_irq(hw_id, d.up_irq_mask, tsens_version(priv))) {
+ tsens_set_interrupt(priv, hw_id, UPPER, disable);
+ if (d.up_thresh > temp) {
+ dev_dbg(priv->dev, "[%u] %s: re-arm upper\n",
+ hw_id, __func__);
+ tsens_set_interrupt(priv, hw_id, UPPER, enable);
+ } else {
+ trigger = true;
+ /* Keep irq masked */
+ }
+ } else if (d.low_viol &&
+ !masked_irq(hw_id, d.low_irq_mask, tsens_version(priv))) {
+ tsens_set_interrupt(priv, hw_id, LOWER, disable);
+ if (d.low_thresh < temp) {
+ dev_dbg(priv->dev, "[%u] %s: re-arm low\n",
+ hw_id, __func__);
+ tsens_set_interrupt(priv, hw_id, LOWER, enable);
+ } else {
+ trigger = true;
+ /* Keep irq masked */
+ }
+ }
+
+ spin_unlock_irqrestore(&priv->ul_lock, flags);
+
+ if (trigger) {
+ dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n",
+ hw_id, __func__, temp);
+ thermal_zone_device_update(s->tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ } else {
+ dev_dbg(priv->dev, "[%u] %s: no violation: %d\n",
+ hw_id, __func__, temp);
+ }
+
+ if (tsens_version(priv) < VER_0_1) {
+ /* Constraint: There is only 1 interrupt control register for all
+ * 11 temperature sensor. So monitoring more than 1 sensor based
+ * on interrupts will yield inconsistent result. To overcome this
+ * issue we will monitor only sensor 0 which is the master sensor.
+ */
+ break;
+ }
+ }
+
+ return IRQ_HANDLED;
}
-static int tsens_get_trend(void *data, int trip, enum thermal_trend *trend)
+static int tsens_set_trips(struct thermal_zone_device *tz, int low, int high)
{
- struct tsens_sensor *s = data;
+ struct tsens_sensor *s = tz->devdata;
struct tsens_priv *priv = s->priv;
+ struct device *dev = priv->dev;
+ struct tsens_irq_data d;
+ unsigned long flags;
+ int high_val, low_val, cl_high, cl_low;
+ u32 hw_id = s->hw_id;
- if (priv->ops->get_trend)
- return priv->ops->get_trend(s, trend);
+ if (tsens_version(priv) < VER_0_1) {
+ /* Pre v0.1 IP had a single register for each type of interrupt
+ * and thresholds
+ */
+ hw_id = 0;
+ }
+
+ dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
+ hw_id, __func__, low, high);
+
+ cl_high = clamp_val(high, -40000, 120000);
+ cl_low = clamp_val(low, -40000, 120000);
+
+ high_val = tsens_mC_to_hw(s, cl_high);
+ low_val = tsens_mC_to_hw(s, cl_low);
+
+ spin_lock_irqsave(&priv->ul_lock, flags);
+
+ tsens_read_irq_state(priv, hw_id, s, &d);
+
+ /* Write the new thresholds and clear the status */
+ regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val);
+ regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val);
+ tsens_set_interrupt(priv, hw_id, LOWER, true);
+ tsens_set_interrupt(priv, hw_id, UPPER, true);
+
+ spin_unlock_irqrestore(&priv->ul_lock, flags);
+
+ dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
+ hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
+
+ return 0;
+}
+
+static int tsens_enable_irq(struct tsens_priv *priv)
+{
+ int ret;
+ int val = tsens_version(priv) > VER_1_X ? 7 : 1;
+
+ ret = regmap_field_write(priv->rf[INT_EN], val);
+ if (ret < 0)
+ dev_err(priv->dev, "%s: failed to enable interrupts\n",
+ __func__);
- return -ENOTSUPP;
+ return ret;
+}
+
+static void tsens_disable_irq(struct tsens_priv *priv)
+{
+ regmap_field_write(priv->rf[INT_EN], 0);
+}
+
+int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
+{
+ struct tsens_priv *priv = s->priv;
+ int hw_id = s->hw_id;
+ u32 temp_idx = LAST_TEMP_0 + hw_id;
+ u32 valid_idx = VALID_0 + hw_id;
+ u32 valid;
+ int ret;
+
+ /* VER_0 doesn't have VALID bit */
+ if (tsens_version(priv) == VER_0)
+ goto get_temp;
+
+ /* Valid bit is 0 for 6 AHB clock cycles.
+ * At 19.2MHz, 1 AHB clock is ~60ns.
+ * We should enter this loop very, very rarely.
+ * Wait 1 us since it's the min of poll_timeout macro.
+ * Old value was 400 ns.
+ */
+ ret = regmap_field_read_poll_timeout(priv->rf[valid_idx], valid,
+ valid, 1, 20 * USEC_PER_MSEC);
+ if (ret)
+ return ret;
+
+get_temp:
+ /* Valid bit is set, OK to read the temperature */
+ *temp = tsens_hw_to_mC(s, temp_idx);
+
+ return 0;
+}
+
+int get_temp_common(const struct tsens_sensor *s, int *temp)
+{
+ struct tsens_priv *priv = s->priv;
+ int hw_id = s->hw_id;
+ int last_temp = 0, ret, trdy;
+ unsigned long timeout;
+
+ timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
+ do {
+ if (tsens_version(priv) == VER_0) {
+ ret = regmap_field_read(priv->rf[TRDY], &trdy);
+ if (ret)
+ return ret;
+ if (!trdy)
+ continue;
+ }
+
+ ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp);
+ if (ret)
+ return ret;
+
+ *temp = code_to_degc(last_temp, s) * 1000;
+
+ return 0;
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int dbg_sensors_show(struct seq_file *s, void *data)
+{
+ struct platform_device *pdev = s->private;
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+ int i;
+
+ seq_printf(s, "max: %2d\nnum: %2d\n\n",
+ priv->feat->max_sensors, priv->num_sensors);
+
+ seq_puts(s, " id slope offset\n--------------------------\n");
+ for (i = 0; i < priv->num_sensors; i++) {
+ seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id,
+ priv->sensor[i].slope, priv->sensor[i].offset);
+ }
+
+ return 0;
+}
+
+static int dbg_version_show(struct seq_file *s, void *data)
+{
+ struct platform_device *pdev = s->private;
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+ u32 maj_ver, min_ver, step_ver;
+ int ret;
+
+ if (tsens_version(priv) > VER_0_1) {
+ ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[VER_STEP], &step_ver);
+ if (ret)
+ return ret;
+ seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver);
+ } else {
+ seq_puts(s, "0.1.0\n");
+ }
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(dbg_version);
+DEFINE_SHOW_ATTRIBUTE(dbg_sensors);
+
+static void tsens_debug_init(struct platform_device *pdev)
+{
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+ struct dentry *root, *file;
+
+ root = debugfs_lookup("tsens", NULL);
+ if (!root)
+ priv->debug_root = debugfs_create_dir("tsens", NULL);
+ else
+ priv->debug_root = root;
+
+ file = debugfs_lookup("version", priv->debug_root);
+ if (!file)
+ debugfs_create_file("version", 0444, priv->debug_root,
+ pdev, &dbg_version_fops);
+
+ /* A directory for each instance of the TSENS IP */
+ priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root);
+ debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops);
+}
+#else
+static inline void tsens_debug_init(struct platform_device *pdev) {}
+#endif
+
+static const struct regmap_config tsens_config = {
+ .name = "tm",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static const struct regmap_config tsens_srot_config = {
+ .name = "srot",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+int __init init_common(struct tsens_priv *priv)
+{
+ void __iomem *tm_base, *srot_base;
+ struct device *dev = priv->dev;
+ u32 ver_minor;
+ struct resource *res;
+ u32 enabled;
+ int ret, i, j;
+ struct platform_device *op = of_find_device_by_node(priv->dev->of_node);
+
+ if (!op)
+ return -EINVAL;
+
+ if (op->num_resources > 1) {
+ /* DT with separate SROT and TM address space */
+ priv->tm_offset = 0;
+ res = platform_get_resource(op, IORESOURCE_MEM, 1);
+ srot_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(srot_base)) {
+ ret = PTR_ERR(srot_base);
+ goto err_put_device;
+ }
+
+ priv->srot_map = devm_regmap_init_mmio(dev, srot_base,
+ &tsens_srot_config);
+ if (IS_ERR(priv->srot_map)) {
+ ret = PTR_ERR(priv->srot_map);
+ goto err_put_device;
+ }
+ } else {
+ /* old DTs where SROT and TM were in a contiguous 2K block */
+ priv->tm_offset = 0x1000;
+ }
+
+ if (tsens_version(priv) >= VER_0_1) {
+ res = platform_get_resource(op, IORESOURCE_MEM, 0);
+ tm_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(tm_base)) {
+ ret = PTR_ERR(tm_base);
+ goto err_put_device;
+ }
+
+ priv->tm_map = devm_regmap_init_mmio(dev, tm_base, &tsens_config);
+ } else { /* VER_0 share the same gcc regs using a syscon */
+ struct device *parent = priv->dev->parent;
+
+ if (parent)
+ priv->tm_map = syscon_node_to_regmap(parent->of_node);
+ }
+
+ if (IS_ERR_OR_NULL(priv->tm_map)) {
+ if (!priv->tm_map)
+ ret = -ENODEV;
+ else
+ ret = PTR_ERR(priv->tm_map);
+ goto err_put_device;
+ }
+
+ /* VER_0 have only tm_map */
+ if (!priv->srot_map)
+ priv->srot_map = priv->tm_map;
+
+ if (tsens_version(priv) > VER_0_1) {
+ for (i = VER_MAJOR; i <= VER_STEP; i++) {
+ priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[i]);
+ if (IS_ERR(priv->rf[i])) {
+ ret = PTR_ERR(priv->rf[i]);
+ goto err_put_device;
+ }
+ }
+ ret = regmap_field_read(priv->rf[VER_MINOR], &ver_minor);
+ if (ret)
+ goto err_put_device;
+ }
+
+ priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[TSENS_EN]);
+ if (IS_ERR(priv->rf[TSENS_EN])) {
+ ret = PTR_ERR(priv->rf[TSENS_EN]);
+ goto err_put_device;
+ }
+ /* in VER_0 TSENS need to be explicitly enabled */
+ if (tsens_version(priv) == VER_0)
+ regmap_field_write(priv->rf[TSENS_EN], 1);
+
+ ret = regmap_field_read(priv->rf[TSENS_EN], &enabled);
+ if (ret)
+ goto err_put_device;
+ if (!enabled) {
+ dev_err(dev, "%s: device not enabled\n", __func__);
+ ret = -ENODEV;
+ goto err_put_device;
+ }
+
+ priv->rf[SENSOR_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[SENSOR_EN]);
+ if (IS_ERR(priv->rf[SENSOR_EN])) {
+ ret = PTR_ERR(priv->rf[SENSOR_EN]);
+ goto err_put_device;
+ }
+ priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map,
+ priv->fields[INT_EN]);
+ if (IS_ERR(priv->rf[INT_EN])) {
+ ret = PTR_ERR(priv->rf[INT_EN]);
+ goto err_put_device;
+ }
+
+ priv->rf[TSENS_SW_RST] =
+ devm_regmap_field_alloc(dev, priv->srot_map, priv->fields[TSENS_SW_RST]);
+ if (IS_ERR(priv->rf[TSENS_SW_RST])) {
+ ret = PTR_ERR(priv->rf[TSENS_SW_RST]);
+ goto err_put_device;
+ }
+
+ priv->rf[TRDY] = devm_regmap_field_alloc(dev, priv->tm_map, priv->fields[TRDY]);
+ if (IS_ERR(priv->rf[TRDY])) {
+ ret = PTR_ERR(priv->rf[TRDY]);
+ goto err_put_device;
+ }
+
+ /* This loop might need changes if enum regfield_ids is reordered */
+ for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) {
+ for (i = 0; i < priv->feat->max_sensors; i++) {
+ int idx = j + i;
+
+ priv->rf[idx] = devm_regmap_field_alloc(dev,
+ priv->tm_map,
+ priv->fields[idx]);
+ if (IS_ERR(priv->rf[idx])) {
+ ret = PTR_ERR(priv->rf[idx]);
+ goto err_put_device;
+ }
+ }
+ }
+
+ if (priv->feat->crit_int || tsens_version(priv) < VER_0_1) {
+ /* Loop might need changes if enum regfield_ids is reordered */
+ for (j = CRITICAL_STATUS_0; j <= CRIT_THRESH_15; j += 16) {
+ for (i = 0; i < priv->feat->max_sensors; i++) {
+ int idx = j + i;
+
+ priv->rf[idx] =
+ devm_regmap_field_alloc(dev,
+ priv->tm_map,
+ priv->fields[idx]);
+ if (IS_ERR(priv->rf[idx])) {
+ ret = PTR_ERR(priv->rf[idx]);
+ goto err_put_device;
+ }
+ }
+ }
+ }
+
+ if (tsens_version(priv) > VER_1_X && ver_minor > 2) {
+ /* Watchdog is present only on v2.3+ */
+ priv->feat->has_watchdog = 1;
+ for (i = WDOG_BARK_STATUS; i <= CC_MON_MASK; i++) {
+ priv->rf[i] = devm_regmap_field_alloc(dev, priv->tm_map,
+ priv->fields[i]);
+ if (IS_ERR(priv->rf[i])) {
+ ret = PTR_ERR(priv->rf[i]);
+ goto err_put_device;
+ }
+ }
+ /*
+ * Watchdog is already enabled, unmask the bark.
+ * Disable cycle completion monitoring
+ */
+ regmap_field_write(priv->rf[WDOG_BARK_MASK], 0);
+ regmap_field_write(priv->rf[CC_MON_MASK], 1);
+ }
+
+ spin_lock_init(&priv->ul_lock);
+
+ /* VER_0 interrupt doesn't need to be enabled */
+ if (tsens_version(priv) >= VER_0_1)
+ tsens_enable_irq(priv);
+
+ tsens_debug_init(op);
+
+err_put_device:
+ put_device(&op->dev);
+ return ret;
+}
+
+static int tsens_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct tsens_sensor *s = tz->devdata;
+ struct tsens_priv *priv = s->priv;
+
+ return priv->ops->get_temp(s, temp);
}
static int __maybe_unused tsens_suspend(struct device *dev)
@@ -57,9 +957,21 @@ static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
static const struct of_device_id tsens_table[] = {
{
+ .compatible = "qcom,ipq8064-tsens",
+ .data = &data_8960,
+ }, {
+ .compatible = "qcom,mdm9607-tsens",
+ .data = &data_9607,
+ }, {
.compatible = "qcom,msm8916-tsens",
.data = &data_8916,
}, {
+ .compatible = "qcom,msm8939-tsens",
+ .data = &data_8939,
+ }, {
+ .compatible = "qcom,msm8960-tsens",
+ .data = &data_8960,
+ }, {
.compatible = "qcom,msm8974-tsens",
.data = &data_8974,
}, {
@@ -79,56 +991,94 @@ static const struct of_device_id tsens_table[] = {
};
MODULE_DEVICE_TABLE(of, tsens_table);
-static const struct thermal_zone_of_device_ops tsens_of_ops = {
+static const struct thermal_zone_device_ops tsens_of_ops = {
.get_temp = tsens_get_temp,
- .get_trend = tsens_get_trend,
.set_trips = tsens_set_trips,
};
+static int tsens_register_irq(struct tsens_priv *priv, char *irqname,
+ irq_handler_t thread_fn)
+{
+ struct platform_device *pdev;
+ int ret, irq;
+
+ pdev = of_find_device_by_node(priv->dev->of_node);
+ if (!pdev)
+ return -ENODEV;
+
+ irq = platform_get_irq_byname(pdev, irqname);
+ if (irq < 0) {
+ ret = irq;
+ /* For old DTs with no IRQ defined */
+ if (irq == -ENXIO)
+ ret = 0;
+ } else {
+ /* VER_0 interrupt is TRIGGER_RISING, VER_0_1 and up is ONESHOT */
+ if (tsens_version(priv) == VER_0)
+ ret = devm_request_threaded_irq(&pdev->dev, irq,
+ thread_fn, NULL,
+ IRQF_TRIGGER_RISING,
+ dev_name(&pdev->dev),
+ priv);
+ else
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ thread_fn, IRQF_ONESHOT,
+ dev_name(&pdev->dev),
+ priv);
+
+ if (ret)
+ dev_err(&pdev->dev, "%s: failed to get irq\n",
+ __func__);
+ else
+ enable_irq_wake(irq);
+ }
+
+ put_device(&pdev->dev);
+ return ret;
+}
+
static int tsens_register(struct tsens_priv *priv)
{
- int i, ret, irq;
+ int i, ret;
struct thermal_zone_device *tzd;
- struct platform_device *pdev;
for (i = 0; i < priv->num_sensors; i++) {
priv->sensor[i].priv = priv;
- tzd = devm_thermal_zone_of_sensor_register(priv->dev, priv->sensor[i].hw_id,
- &priv->sensor[i],
- &tsens_of_ops);
+ tzd = devm_thermal_of_zone_register(priv->dev, priv->sensor[i].hw_id,
+ &priv->sensor[i],
+ &tsens_of_ops);
if (IS_ERR(tzd))
continue;
priv->sensor[i].tzd = tzd;
if (priv->ops->enable)
priv->ops->enable(priv, i);
+
+ if (devm_thermal_add_hwmon_sysfs(tzd))
+ dev_warn(priv->dev,
+ "Failed to add hwmon sysfs attributes\n");
}
- pdev = of_find_device_by_node(priv->dev->of_node);
- if (!pdev)
- return -ENODEV;
+ /* VER_0 require to set MIN and MAX THRESH
+ * These 2 regs are set using the:
+ * - CRIT_THRESH_0 for MAX THRESH hardcoded to 120°C
+ * - CRIT_THRESH_1 for MIN THRESH hardcoded to 0°C
+ */
+ if (tsens_version(priv) < VER_0_1) {
+ regmap_field_write(priv->rf[CRIT_THRESH_0],
+ tsens_mC_to_hw(priv->sensor, 120000));
- irq = platform_get_irq_byname(pdev, "uplow");
- if (irq < 0) {
- ret = irq;
- /* For old DTs with no IRQ defined */
- if (irq == -ENXIO)
- ret = 0;
- goto err_put_device;
+ regmap_field_write(priv->rf[CRIT_THRESH_1],
+ tsens_mC_to_hw(priv->sensor, 0));
}
- ret = devm_request_threaded_irq(&pdev->dev, irq,
- NULL, tsens_irq_thread,
- IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
- dev_name(&pdev->dev), priv);
- if (ret) {
- dev_err(&pdev->dev, "%s: failed to get irq\n", __func__);
- goto err_put_device;
- }
+ ret = tsens_register_irq(priv, "uplow", tsens_irq_thread);
+ if (ret < 0)
+ return ret;
- enable_irq_wake(irq);
+ if (priv->feat->crit_int)
+ ret = tsens_register_irq(priv, "critical",
+ tsens_critical_irq_thread);
-err_put_device:
- put_device(&pdev->dev);
return ret;
}
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
index e24a865fbc34..ba05c8233356 100644
--- a/drivers/thermal/qcom/tsens.h
+++ b/drivers/thermal/qcom/tsens.h
@@ -13,6 +13,7 @@
#define CAL_DEGC_PT2 120
#define SLOPE_FACTOR 1000
#define SLOPE_DEFAULT 3200
+#define TIMEOUT_US 100
#define THRESHOLD_MAX_ADC_CODE 0x3ff
#define THRESHOLD_MIN_ADC_CODE 0x0
@@ -23,8 +24,10 @@
struct tsens_priv;
+/* IP version numbers in ascending order */
enum tsens_ver {
- VER_0_1 = 0,
+ VER_0 = 0,
+ VER_0_1,
VER_1_X,
VER_2_X,
};
@@ -32,6 +35,7 @@ enum tsens_ver {
enum tsens_irq_type {
LOWER,
UPPER,
+ CRITICAL,
};
/**
@@ -61,19 +65,17 @@ struct tsens_sensor {
* @disable: Function to disable the tsens device
* @suspend: Function to suspend the tsens device
* @resume: Function to resume the tsens device
- * @get_trend: Function to get the thermal/temp trend
*/
struct tsens_ops {
/* mandatory callbacks */
int (*init)(struct tsens_priv *priv);
int (*calibrate)(struct tsens_priv *priv);
- int (*get_temp)(struct tsens_sensor *s, int *temp);
+ int (*get_temp)(const struct tsens_sensor *s, int *temp);
/* optional callbacks */
int (*enable)(struct tsens_priv *priv, int i);
void (*disable)(struct tsens_priv *priv);
int (*suspend)(struct tsens_priv *priv);
int (*resume)(struct tsens_priv *priv);
- int (*get_trend)(struct tsens_sensor *s, enum thermal_trend *trend);
};
#define REG_FIELD_FOR_EACH_SENSOR11(_name, _offset, _startbit, _stopbit) \
@@ -374,6 +376,82 @@ enum regfield_ids {
CRITICAL_STATUS_13,
CRITICAL_STATUS_14,
CRITICAL_STATUS_15,
+ CRIT_INT_STATUS_0, /* CRITICAL interrupt status */
+ CRIT_INT_STATUS_1,
+ CRIT_INT_STATUS_2,
+ CRIT_INT_STATUS_3,
+ CRIT_INT_STATUS_4,
+ CRIT_INT_STATUS_5,
+ CRIT_INT_STATUS_6,
+ CRIT_INT_STATUS_7,
+ CRIT_INT_STATUS_8,
+ CRIT_INT_STATUS_9,
+ CRIT_INT_STATUS_10,
+ CRIT_INT_STATUS_11,
+ CRIT_INT_STATUS_12,
+ CRIT_INT_STATUS_13,
+ CRIT_INT_STATUS_14,
+ CRIT_INT_STATUS_15,
+ CRIT_INT_CLEAR_0, /* CRITICAL interrupt clear */
+ CRIT_INT_CLEAR_1,
+ CRIT_INT_CLEAR_2,
+ CRIT_INT_CLEAR_3,
+ CRIT_INT_CLEAR_4,
+ CRIT_INT_CLEAR_5,
+ CRIT_INT_CLEAR_6,
+ CRIT_INT_CLEAR_7,
+ CRIT_INT_CLEAR_8,
+ CRIT_INT_CLEAR_9,
+ CRIT_INT_CLEAR_10,
+ CRIT_INT_CLEAR_11,
+ CRIT_INT_CLEAR_12,
+ CRIT_INT_CLEAR_13,
+ CRIT_INT_CLEAR_14,
+ CRIT_INT_CLEAR_15,
+ CRIT_INT_MASK_0, /* CRITICAL interrupt mask */
+ CRIT_INT_MASK_1,
+ CRIT_INT_MASK_2,
+ CRIT_INT_MASK_3,
+ CRIT_INT_MASK_4,
+ CRIT_INT_MASK_5,
+ CRIT_INT_MASK_6,
+ CRIT_INT_MASK_7,
+ CRIT_INT_MASK_8,
+ CRIT_INT_MASK_9,
+ CRIT_INT_MASK_10,
+ CRIT_INT_MASK_11,
+ CRIT_INT_MASK_12,
+ CRIT_INT_MASK_13,
+ CRIT_INT_MASK_14,
+ CRIT_INT_MASK_15,
+ CRIT_THRESH_0, /* CRITICAL threshold values */
+ CRIT_THRESH_1,
+ CRIT_THRESH_2,
+ CRIT_THRESH_3,
+ CRIT_THRESH_4,
+ CRIT_THRESH_5,
+ CRIT_THRESH_6,
+ CRIT_THRESH_7,
+ CRIT_THRESH_8,
+ CRIT_THRESH_9,
+ CRIT_THRESH_10,
+ CRIT_THRESH_11,
+ CRIT_THRESH_12,
+ CRIT_THRESH_13,
+ CRIT_THRESH_14,
+ CRIT_THRESH_15,
+
+ /* WATCHDOG */
+ WDOG_BARK_STATUS,
+ WDOG_BARK_CLEAR,
+ WDOG_BARK_MASK,
+ WDOG_BARK_COUNT,
+
+ /* CYCLE COMPLETION MONITOR */
+ CC_MON_STATUS,
+ CC_MON_CLEAR,
+ CC_MON_MASK,
+
MIN_STATUS_0, /* MIN threshold violated */
MIN_STATUS_1,
MIN_STATUS_2,
@@ -418,6 +496,7 @@ enum regfield_ids {
* @adc: do the sensors only output adc code (instead of temperature)?
* @srot_split: does the IP neatly splits the register space into SROT and TM,
* with SROT only being available to secure boot firmware?
+ * @has_watchdog: does this IP support watchdog functionality?
* @max_sensors: maximum sensors supported by this version of the IP
*/
struct tsens_features {
@@ -425,6 +504,7 @@ struct tsens_features {
unsigned int crit_int:1;
unsigned int adc:1;
unsigned int srot_split:1;
+ unsigned int has_watchdog:1;
unsigned int max_sensors;
};
@@ -440,12 +520,14 @@ struct tsens_plat_data {
const u32 num_sensors;
const struct tsens_ops *ops;
unsigned int *hw_ids;
- const struct tsens_features *feat;
+ struct tsens_features *feat;
const struct reg_field *fields;
};
/**
* struct tsens_context - Registers to be saved/restored across a context loss
+ * @threshold: Threshold register value
+ * @control: Control register value
*/
struct tsens_context {
int threshold;
@@ -460,6 +542,8 @@ struct tsens_context {
* @srot_map: pointer to SROT register address space
* @tm_offset: deal with old device trees that don't address TM and SROT
* address space separately
+ * @ul_lock: lock while processing upper/lower threshold interrupts
+ * @crit_lock: lock while processing critical threshold interrupts
* @rf: array of regmap_fields used to store value of the field
* @ctx: registers to be saved and restored during suspend/resume
* @feat: features of the IP
@@ -481,36 +565,32 @@ struct tsens_priv {
struct regmap_field *rf[MAX_REGFIELDS];
struct tsens_context ctx;
- const struct tsens_features *feat;
+ struct tsens_features *feat;
const struct reg_field *fields;
const struct tsens_ops *ops;
struct dentry *debug_root;
struct dentry *debug;
- struct tsens_sensor sensor[0];
+ struct tsens_sensor sensor[];
};
char *qfprom_read(struct device *dev, const char *cname);
void compute_intercept_slope(struct tsens_priv *priv, u32 *pt1, u32 *pt2, u32 mode);
int init_common(struct tsens_priv *priv);
-int get_temp_tsens_valid(struct tsens_sensor *s, int *temp);
-int get_temp_common(struct tsens_sensor *s, int *temp);
-int tsens_enable_irq(struct tsens_priv *priv);
-void tsens_disable_irq(struct tsens_priv *priv);
-int tsens_set_trips(void *_sensor, int low, int high);
-irqreturn_t tsens_irq_thread(int irq, void *data);
+int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp);
+int get_temp_common(const struct tsens_sensor *s, int *temp);
/* TSENS target */
-extern const struct tsens_plat_data data_8960;
+extern struct tsens_plat_data data_8960;
/* TSENS v0.1 targets */
-extern const struct tsens_plat_data data_8916, data_8974;
+extern struct tsens_plat_data data_8916, data_8939, data_8974, data_9607;
/* TSENS v1 targets */
-extern const struct tsens_plat_data data_tsens_v1, data_8976;
+extern struct tsens_plat_data data_tsens_v1, data_8976;
/* TSENS v2 targets */
-extern const struct tsens_plat_data data_8996, data_tsens_v2;
+extern struct tsens_plat_data data_8996, data_tsens_v2;
#endif /* __QCOM_TSENS_H__ */
diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c
index 874bc46e6c73..d111e218f362 100644
--- a/drivers/thermal/qoriq_thermal.c
+++ b/drivers/thermal/qoriq_thermal.c
@@ -3,15 +3,15 @@
// Copyright 2016 Freescale Semiconductor, Inc.
#include <linux/clk.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/io.h>
+#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_address.h>
+#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/sizes.h>
#include <linux/thermal.h>
+#include <linux/units.h>
#include "thermal_core.h"
#include "thermal_hwmon.h"
@@ -24,6 +24,7 @@
#define TMTMIR_DEFAULT 0x0000000f
#define TIER_DISABLE 0x0
#define TEUMR0_V2 0x51009c00
+#define TMSARA_V2 0xe
#define TMU_VER1 0x1
#define TMU_VER2 0x2
@@ -51,6 +52,9 @@
* Site Register
*/
#define TRITSR_V BIT(31)
+#define REGS_V2_TMSAR(n) (0x304 + 16 * (n)) /* TMU monitoring
+ * site adjustment register
+ */
#define REGS_TTRnCR(n) (0xf10 + 4 * (n)) /* Temperature Range n
* Control Register
*/
@@ -78,20 +82,29 @@ static struct qoriq_tmu_data *qoriq_sensor_to_data(struct qoriq_sensor *s)
return container_of(s, struct qoriq_tmu_data, sensor[s->id]);
}
-static int tmu_get_temp(void *p, int *temp)
+static int tmu_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct qoriq_sensor *qsensor = p;
+ struct qoriq_sensor *qsensor = tz->devdata;
struct qoriq_tmu_data *qdata = qoriq_sensor_to_data(qsensor);
u32 val;
/*
* REGS_TRITSR(id) has the following layout:
*
+ * For TMU Rev1:
* 31 ... 7 6 5 4 3 2 1 0
* V TEMP
*
* Where V bit signifies if the measurement is ready and is
* within sensor range. TEMP is an 8 bit value representing
- * temperature in C.
+ * temperature in Celsius.
+
+ * For TMU Rev2:
+ * 31 ... 8 7 6 5 4 3 2 1 0
+ * V TEMP
+ *
+ * Where V bit signifies if the measurement is ready and is
+ * within sensor range. TEMP is an 9 bit value representing
+ * temperature in KelVin.
*/
if (regmap_read_poll_timeout(qdata->regmap,
REGS_TRITSR(qsensor->id),
@@ -101,12 +114,15 @@ static int tmu_get_temp(void *p, int *temp)
10 * USEC_PER_MSEC))
return -ENODATA;
- *temp = (val & 0xff) * 1000;
+ if (qdata->ver == TMU_VER1)
+ *temp = (val & GENMASK(7, 0)) * MILLIDEGREE_PER_DEGREE;
+ else
+ *temp = kelvin_to_millicelsius(val & GENMASK(8, 0));
return 0;
}
-static const struct thermal_zone_of_device_ops tmu_tz_ops = {
+static const struct thermal_zone_device_ops tmu_tz_ops = {
.get_temp = tmu_get_temp,
};
@@ -130,9 +146,9 @@ static int qoriq_tmu_register_tmu_zone(struct device *dev,
sensor->id = id;
- tzd = devm_thermal_zone_of_sensor_register(dev, id,
- sensor,
- &tmu_tz_ops);
+ tzd = devm_thermal_of_zone_register(dev, id,
+ sensor,
+ &tmu_tz_ops);
ret = PTR_ERR_OR_ZERO(tzd);
if (ret) {
if (ret == -ENODEV)
@@ -193,6 +209,8 @@ static int qoriq_tmu_calibration(struct device *dev,
static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
{
+ int i;
+
/* Disable interrupt, using polling instead */
regmap_write(data->regmap, REGS_TIER, TIER_DISABLE);
@@ -203,6 +221,8 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
} else {
regmap_write(data->regmap, REGS_V2_TMTMIR, TMTMIR_DEFAULT);
regmap_write(data->regmap, REGS_V2_TEUMR(0), TEUMR0_V2);
+ for (i = 0; i < SITES_MAX; i++)
+ regmap_write(data->regmap, REGS_V2_TMSAR(i), TMSARA_V2);
}
/* Disable monitoring */
@@ -213,6 +233,7 @@ static const struct regmap_range qoriq_yes_ranges[] = {
regmap_reg_range(REGS_TMR, REGS_TSCFGR),
regmap_reg_range(REGS_TTRnCR(0), REGS_TTRnCR(3)),
regmap_reg_range(REGS_V2_TEUMR(0), REGS_V2_TEUMR(2)),
+ regmap_reg_range(REGS_V2_TMSAR(0), REGS_V2_TMSAR(15)),
regmap_reg_range(REGS_IPBRR(0), REGS_IPBRR(1)),
/* Read only registers below */
regmap_reg_range(REGS_TRITSR(0), REGS_TRITSR(15)),
@@ -228,6 +249,14 @@ static const struct regmap_access_table qoriq_rd_table = {
.n_yes_ranges = ARRAY_SIZE(qoriq_yes_ranges),
};
+static void qoriq_tmu_action(void *p)
+{
+ struct qoriq_tmu_data *data = p;
+
+ regmap_write(data->regmap, REGS_TMR, TMR_DISABLE);
+ clk_disable_unprepare(data->clk);
+}
+
static int qoriq_tmu_probe(struct platform_device *pdev)
{
int ret;
@@ -278,6 +307,10 @@ static int qoriq_tmu_probe(struct platform_device *pdev)
return ret;
}
+ ret = devm_add_action_or_reset(dev, qoriq_tmu_action, data);
+ if (ret)
+ return ret;
+
/* version register offset at: 0xbf8 on both v1 and v2 */
ret = regmap_read(data->regmap, REGS_IPBRR(0), &ver);
if (ret) {
@@ -290,35 +323,17 @@ static int qoriq_tmu_probe(struct platform_device *pdev)
ret = qoriq_tmu_calibration(dev, data); /* TMU calibration */
if (ret < 0)
- goto err;
+ return ret;
ret = qoriq_tmu_register_tmu_zone(dev, data);
if (ret < 0) {
dev_err(dev, "Failed to register sensors\n");
- ret = -ENODEV;
- goto err;
+ return ret;
}
platform_set_drvdata(pdev, data);
return 0;
-
-err:
- clk_disable_unprepare(data->clk);
-
- return ret;
-}
-
-static int qoriq_tmu_remove(struct platform_device *pdev)
-{
- struct qoriq_tmu_data *data = platform_get_drvdata(pdev);
-
- /* Disable monitoring */
- regmap_write(data->regmap, REGS_TMR, TMR_DISABLE);
-
- clk_disable_unprepare(data->clk);
-
- return 0;
}
static int __maybe_unused qoriq_tmu_suspend(struct device *dev)
@@ -365,7 +380,6 @@ static struct platform_driver qoriq_tmu = {
.of_match_table = qoriq_tmu_match,
},
.probe = qoriq_tmu_probe,
- .remove = qoriq_tmu_remove,
};
module_platform_driver(qoriq_tmu);
diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c
index 72877bdc072d..4c1c6f89aa2f 100644
--- a/drivers/thermal/rcar_gen3_thermal.c
+++ b/drivers/thermal/rcar_gen3_thermal.c
@@ -34,6 +34,10 @@
#define REG_GEN3_THCODE1 0x50
#define REG_GEN3_THCODE2 0x54
#define REG_GEN3_THCODE3 0x58
+#define REG_GEN3_PTAT1 0x5c
+#define REG_GEN3_PTAT2 0x60
+#define REG_GEN3_PTAT3 0x64
+#define REG_GEN3_THSCP 0x68
/* IRQ{STR,MSK,EN} bits */
#define IRQ_TEMP1 BIT(0)
@@ -55,19 +59,15 @@
#define THCTR_PONM BIT(6)
#define THCTR_THSST BIT(0)
+/* THSCP bits */
+#define THSCP_COR_PARA_VLD (BIT(15) | BIT(14))
+
#define CTEMP_MASK 0xFFF
#define MCELSIUS(temp) ((temp) * 1000)
#define GEN3_FUSE_MASK 0xFFF
-#define TSC_MAX_NUM 3
-
-/* default THCODE values if FUSEs are missing */
-static const int thcode[TSC_MAX_NUM][3] = {
- { 3397, 2800, 2221 },
- { 3393, 2795, 2216 },
- { 3389, 2805, 2237 },
-};
+#define TSC_MAX_NUM 5
/* Structure for thermal temperature calculation */
struct equation_coefs {
@@ -81,16 +81,15 @@ struct rcar_gen3_thermal_tsc {
void __iomem *base;
struct thermal_zone_device *zone;
struct equation_coefs coef;
- int low;
- int high;
int tj_t;
- int id; /* thermal channel id */
+ int thcode[3];
};
struct rcar_gen3_thermal_priv {
struct rcar_gen3_thermal_tsc *tscs[TSC_MAX_NUM];
unsigned int num_tscs;
void (*thermal_init)(struct rcar_gen3_thermal_tsc *tsc);
+ int ptat[3];
};
static inline u32 rcar_gen3_thermal_read(struct rcar_gen3_thermal_tsc *tsc,
@@ -133,8 +132,8 @@ static inline void rcar_gen3_thermal_write(struct rcar_gen3_thermal_tsc *tsc,
/* no idea where these constants come from */
#define TJ_3 -41
-static void rcar_gen3_thermal_calc_coefs(struct rcar_gen3_thermal_tsc *tsc,
- int *ptat, const int *thcode,
+static void rcar_gen3_thermal_calc_coefs(struct rcar_gen3_thermal_priv *priv,
+ struct rcar_gen3_thermal_tsc *tsc,
int ths_tj_1)
{
/* TODO: Find documentation and document constant calculation formula */
@@ -143,16 +142,16 @@ static void rcar_gen3_thermal_calc_coefs(struct rcar_gen3_thermal_tsc *tsc,
* Division is not scaled in BSP and if scaled it might overflow
* the dividend (4095 * 4095 << 14 > INT_MAX) so keep it unscaled
*/
- tsc->tj_t = (FIXPT_INT((ptat[1] - ptat[2]) * 157)
- / (ptat[0] - ptat[2])) + FIXPT_INT(TJ_3);
+ tsc->tj_t = (FIXPT_INT((priv->ptat[1] - priv->ptat[2]) * (ths_tj_1 - TJ_3))
+ / (priv->ptat[0] - priv->ptat[2])) + FIXPT_INT(TJ_3);
- tsc->coef.a1 = FIXPT_DIV(FIXPT_INT(thcode[1] - thcode[2]),
+ tsc->coef.a1 = FIXPT_DIV(FIXPT_INT(tsc->thcode[1] - tsc->thcode[2]),
tsc->tj_t - FIXPT_INT(TJ_3));
- tsc->coef.b1 = FIXPT_INT(thcode[2]) - tsc->coef.a1 * TJ_3;
+ tsc->coef.b1 = FIXPT_INT(tsc->thcode[2]) - tsc->coef.a1 * TJ_3;
- tsc->coef.a2 = FIXPT_DIV(FIXPT_INT(thcode[1] - thcode[0]),
+ tsc->coef.a2 = FIXPT_DIV(FIXPT_INT(tsc->thcode[1] - tsc->thcode[0]),
tsc->tj_t - FIXPT_INT(ths_tj_1));
- tsc->coef.b2 = FIXPT_INT(thcode[0]) - tsc->coef.a2 * ths_tj_1;
+ tsc->coef.b2 = FIXPT_INT(tsc->thcode[0]) - tsc->coef.a2 * ths_tj_1;
}
static int rcar_gen3_thermal_round(int temp)
@@ -165,16 +164,16 @@ static int rcar_gen3_thermal_round(int temp)
return result * RCAR3_THERMAL_GRAN;
}
-static int rcar_gen3_thermal_get_temp(void *devdata, int *temp)
+static int rcar_gen3_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct rcar_gen3_thermal_tsc *tsc = devdata;
+ struct rcar_gen3_thermal_tsc *tsc = tz->devdata;
int mcelsius, val;
- u32 reg;
+ int reg;
/* Read register and convert to mili Celsius */
reg = rcar_gen3_thermal_read(tsc, REG_GEN3_TEMP) & CTEMP_MASK;
- if (reg <= thcode[tsc->id][1])
+ if (reg <= tsc->thcode[1])
val = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b1,
tsc->coef.a1);
else
@@ -204,44 +203,38 @@ static int rcar_gen3_thermal_mcelsius_to_temp(struct rcar_gen3_thermal_tsc *tsc,
return INT_FIXPT(val);
}
-static int rcar_gen3_thermal_set_trips(void *devdata, int low, int high)
+static int rcar_gen3_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
{
- struct rcar_gen3_thermal_tsc *tsc = devdata;
-
- low = clamp_val(low, -40000, 120000);
- high = clamp_val(high, -40000, 120000);
+ struct rcar_gen3_thermal_tsc *tsc = tz->devdata;
+ u32 irqmsk = 0;
- rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP1,
- rcar_gen3_thermal_mcelsius_to_temp(tsc, low));
+ if (low != -INT_MAX) {
+ irqmsk |= IRQ_TEMPD1;
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP1,
+ rcar_gen3_thermal_mcelsius_to_temp(tsc, low));
+ }
- rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP2,
- rcar_gen3_thermal_mcelsius_to_temp(tsc, high));
+ if (high != INT_MAX) {
+ irqmsk |= IRQ_TEMP2;
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP2,
+ rcar_gen3_thermal_mcelsius_to_temp(tsc, high));
+ }
- tsc->low = low;
- tsc->high = high;
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, irqmsk);
return 0;
}
-static const struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = {
+static struct thermal_zone_device_ops rcar_gen3_tz_of_ops = {
.get_temp = rcar_gen3_thermal_get_temp,
.set_trips = rcar_gen3_thermal_set_trips,
};
-static void rcar_thermal_irq_set(struct rcar_gen3_thermal_priv *priv, bool on)
-{
- unsigned int i;
- u32 val = on ? IRQ_TEMPD1 | IRQ_TEMP2 : 0;
-
- for (i = 0; i < priv->num_tscs; i++)
- rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQMSK, val);
-}
-
static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data)
{
struct rcar_gen3_thermal_priv *priv = data;
+ unsigned int i;
u32 status;
- int i;
for (i = 0; i < priv->num_tscs; i++) {
status = rcar_gen3_thermal_read(priv->tscs[i], REG_GEN3_IRQSTR);
@@ -259,6 +252,64 @@ static const struct soc_device_attribute r8a7795es1[] = {
{ /* sentinel */ }
};
+static bool rcar_gen3_thermal_read_fuses(struct rcar_gen3_thermal_priv *priv)
+{
+ unsigned int i;
+ u32 thscp;
+
+ /* If fuses are not set, fallback to pseudo values. */
+ thscp = rcar_gen3_thermal_read(priv->tscs[0], REG_GEN3_THSCP);
+ if ((thscp & THSCP_COR_PARA_VLD) != THSCP_COR_PARA_VLD) {
+ /* Default THCODE values in case FUSEs are not set. */
+ static const int thcodes[TSC_MAX_NUM][3] = {
+ { 3397, 2800, 2221 },
+ { 3393, 2795, 2216 },
+ { 3389, 2805, 2237 },
+ { 3415, 2694, 2195 },
+ { 3356, 2724, 2244 },
+ };
+
+ priv->ptat[0] = 2631;
+ priv->ptat[1] = 1509;
+ priv->ptat[2] = 435;
+
+ for (i = 0; i < priv->num_tscs; i++) {
+ struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
+
+ tsc->thcode[0] = thcodes[i][0];
+ tsc->thcode[1] = thcodes[i][1];
+ tsc->thcode[2] = thcodes[i][2];
+ }
+
+ return false;
+ }
+
+ /*
+ * Set the pseudo calibration points with fused values.
+ * PTAT is shared between all TSCs but only fused for the first
+ * TSC while THCODEs are fused for each TSC.
+ */
+ priv->ptat[0] = rcar_gen3_thermal_read(priv->tscs[0], REG_GEN3_PTAT1) &
+ GEN3_FUSE_MASK;
+ priv->ptat[1] = rcar_gen3_thermal_read(priv->tscs[0], REG_GEN3_PTAT2) &
+ GEN3_FUSE_MASK;
+ priv->ptat[2] = rcar_gen3_thermal_read(priv->tscs[0], REG_GEN3_PTAT3) &
+ GEN3_FUSE_MASK;
+
+ for (i = 0; i < priv->num_tscs; i++) {
+ struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
+
+ tsc->thcode[0] = rcar_gen3_thermal_read(tsc, REG_GEN3_THCODE1) &
+ GEN3_FUSE_MASK;
+ tsc->thcode[1] = rcar_gen3_thermal_read(tsc, REG_GEN3_THCODE2) &
+ GEN3_FUSE_MASK;
+ tsc->thcode[2] = rcar_gen3_thermal_read(tsc, REG_GEN3_THCODE3) &
+ GEN3_FUSE_MASK;
+ }
+
+ return true;
+}
+
static void rcar_gen3_thermal_init_r8a7795es1(struct rcar_gen3_thermal_tsc *tsc)
{
rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, CTSR_THBGR);
@@ -270,7 +321,9 @@ static void rcar_gen3_thermal_init_r8a7795es1(struct rcar_gen3_thermal_tsc *tsc)
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F);
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
- rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN, IRQ_TEMPD1 | IRQ_TEMP2);
+ if (tsc->zone->ops->set_trips)
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN,
+ IRQ_TEMPD1 | IRQ_TEMP2);
rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR,
CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN);
@@ -296,7 +349,9 @@ static void rcar_gen3_thermal_init(struct rcar_gen3_thermal_tsc *tsc)
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0);
rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
- rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN, IRQ_TEMPD1 | IRQ_TEMP2);
+ if (tsc->zone->ops->set_trips)
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN,
+ IRQ_TEMPD1 | IRQ_TEMP2);
reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR);
reg_val |= THCTR_THSST;
@@ -317,6 +372,10 @@ static const struct of_device_id rcar_gen3_thermal_dt_ids[] = {
.data = &rcar_gen3_ths_tj_1,
},
{
+ .compatible = "renesas,r8a774e1-thermal",
+ .data = &rcar_gen3_ths_tj_1,
+ },
+ {
.compatible = "renesas,r8a7795-thermal",
.data = &rcar_gen3_ths_tj_1,
},
@@ -325,6 +384,10 @@ static const struct of_device_id rcar_gen3_thermal_dt_ids[] = {
.data = &rcar_gen3_ths_tj_1_m3_w,
},
{
+ .compatible = "renesas,r8a77961-thermal",
+ .data = &rcar_gen3_ths_tj_1_m3_w,
+ },
+ {
.compatible = "renesas,r8a77965-thermal",
.data = &rcar_gen3_ths_tj_1,
},
@@ -332,6 +395,14 @@ static const struct of_device_id rcar_gen3_thermal_dt_ids[] = {
.compatible = "renesas,r8a77980-thermal",
.data = &rcar_gen3_ths_tj_1,
},
+ {
+ .compatible = "renesas,r8a779a0-thermal",
+ .data = &rcar_gen3_ths_tj_1,
+ },
+ {
+ .compatible = "renesas,r8a779f0-thermal",
+ .data = &rcar_gen3_ths_tj_1,
+ },
{},
};
MODULE_DEVICE_TABLE(of, rcar_gen3_thermal_dt_ids);
@@ -339,9 +410,6 @@ MODULE_DEVICE_TABLE(of, rcar_gen3_thermal_dt_ids);
static int rcar_gen3_thermal_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
-
- rcar_thermal_irq_set(priv, false);
pm_runtime_put(dev);
pm_runtime_disable(dev);
@@ -356,37 +424,16 @@ static void rcar_gen3_hwmon_action(void *data)
thermal_remove_hwmon_sysfs(zone);
}
-static int rcar_gen3_thermal_probe(struct platform_device *pdev)
+static int rcar_gen3_thermal_request_irqs(struct rcar_gen3_thermal_priv *priv,
+ struct platform_device *pdev)
{
- struct rcar_gen3_thermal_priv *priv;
struct device *dev = &pdev->dev;
- const int *rcar_gen3_ths_tj_1 = of_device_get_match_data(dev);
- struct resource *res;
- struct thermal_zone_device *zone;
- int ret, irq, i;
+ unsigned int i;
char *irqname;
+ int ret, irq;
- /* default values if FUSEs are missing */
- /* TODO: Read values from hardware on supported platforms */
- int ptat[3] = { 2631, 1509, 435 };
-
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- priv->thermal_init = rcar_gen3_thermal_init;
- if (soc_device_match(r8a7795es1))
- priv->thermal_init = rcar_gen3_thermal_init_r8a7795es1;
-
- platform_set_drvdata(pdev, priv);
-
- /*
- * Request 2 (of the 3 possible) IRQs, the driver only needs to
- * to trigger on the low and high trip points of the current
- * temp window at this point.
- */
for (i = 0; i < 2; i++) {
- irq = platform_get_irq(pdev, i);
+ irq = platform_get_irq_optional(pdev, i);
if (irq < 0)
return irq;
@@ -402,6 +449,32 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
return ret;
}
+ return 0;
+}
+
+static int rcar_gen3_thermal_probe(struct platform_device *pdev)
+{
+ struct rcar_gen3_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ const int *ths_tj_1 = of_device_get_match_data(dev);
+ struct resource *res;
+ struct thermal_zone_device *zone;
+ unsigned int i;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->thermal_init = rcar_gen3_thermal_init;
+ if (soc_device_match(r8a7795es1))
+ priv->thermal_init = rcar_gen3_thermal_init_r8a7795es1;
+
+ platform_set_drvdata(pdev, priv);
+
+ if (rcar_gen3_thermal_request_irqs(priv, pdev))
+ rcar_gen3_tz_of_ops.set_trips = NULL;
+
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
@@ -423,49 +496,51 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
ret = PTR_ERR(tsc->base);
goto error_unregister;
}
- tsc->id = i;
priv->tscs[i] = tsc;
+ }
- priv->thermal_init(tsc);
- rcar_gen3_thermal_calc_coefs(tsc, ptat, thcode[i],
- *rcar_gen3_ths_tj_1);
+ priv->num_tscs = i;
- zone = devm_thermal_zone_of_sensor_register(dev, i, tsc,
- &rcar_gen3_tz_of_ops);
+ if (!rcar_gen3_thermal_read_fuses(priv))
+ dev_info(dev, "No calibration values fused, fallback to driver values\n");
+
+ for (i = 0; i < priv->num_tscs; i++) {
+ struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
+
+ zone = devm_thermal_of_zone_register(dev, i, tsc,
+ &rcar_gen3_tz_of_ops);
if (IS_ERR(zone)) {
- dev_err(dev, "Can't register thermal zone\n");
+ dev_err(dev, "Sensor %u: Can't register thermal zone\n", i);
ret = PTR_ERR(zone);
goto error_unregister;
}
tsc->zone = zone;
+ priv->thermal_init(tsc);
+ rcar_gen3_thermal_calc_coefs(priv, tsc, *ths_tj_1);
+
tsc->zone->tzp->no_hwmon = false;
ret = thermal_add_hwmon_sysfs(tsc->zone);
if (ret)
goto error_unregister;
ret = devm_add_action_or_reset(dev, rcar_gen3_hwmon_action, zone);
- if (ret) {
+ if (ret)
goto error_unregister;
- }
ret = of_thermal_get_ntrips(tsc->zone);
if (ret < 0)
goto error_unregister;
- dev_info(dev, "TSC%d: Loaded %d trip points\n", i, ret);
+ dev_info(dev, "Sensor %u: Loaded %d trip points\n", i, ret);
}
- priv->num_tscs = i;
-
if (!priv->num_tscs) {
ret = -ENODEV;
goto error_unregister;
}
- rcar_thermal_irq_set(priv, true);
-
return 0;
error_unregister:
@@ -474,15 +549,6 @@ error_unregister:
return ret;
}
-static int __maybe_unused rcar_gen3_thermal_suspend(struct device *dev)
-{
- struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
-
- rcar_thermal_irq_set(priv, false);
-
- return 0;
-}
-
static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev)
{
struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
@@ -490,17 +556,18 @@ static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev)
for (i = 0; i < priv->num_tscs; i++) {
struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
+ struct thermal_zone_device *zone = tsc->zone;
priv->thermal_init(tsc);
- rcar_gen3_thermal_set_trips(tsc, tsc->low, tsc->high);
+ if (zone->ops->set_trips)
+ rcar_gen3_thermal_set_trips(zone, zone->prev_low_trip,
+ zone->prev_high_trip);
}
- rcar_thermal_irq_set(priv, true);
-
return 0;
}
-static SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops, rcar_gen3_thermal_suspend,
+static SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops, NULL,
rcar_gen3_thermal_resume);
static struct platform_driver rcar_gen3_thermal_driver = {
diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c
index 8f1aafa2044e..61c2b8855cb8 100644
--- a/drivers/thermal/rcar_thermal.c
+++ b/drivers/thermal/rcar_thermal.c
@@ -95,7 +95,6 @@ struct rcar_thermal_priv {
struct mutex lock;
struct list_head list;
int id;
- u32 ctemp;
};
#define rcar_thermal_for_each_priv(pos, common) \
@@ -199,9 +198,8 @@ static void _rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg,
static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
{
struct device *dev = rcar_priv_to_dev(priv);
- int i;
- u32 ctemp, old, new;
- int ret = -EINVAL;
+ int old, new, ctemp = -EINVAL;
+ unsigned int i;
mutex_lock(&priv->lock);
@@ -211,7 +209,6 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
*/
rcar_thermal_bset(priv, THSCR, CPCTL, CPCTL);
- ctemp = 0;
old = ~0;
for (i = 0; i < 128; i++) {
/*
@@ -229,7 +226,7 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
old = new;
}
- if (!ctemp) {
+ if (ctemp < 0) {
dev_err(dev, "thermal sensor was broken\n");
goto err_out_unlock;
}
@@ -247,48 +244,33 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
((ctemp - 1) << 0)));
}
- dev_dbg(dev, "thermal%d %d -> %d\n", priv->id, priv->ctemp, ctemp);
-
- priv->ctemp = ctemp;
- ret = 0;
err_out_unlock:
mutex_unlock(&priv->lock);
- return ret;
+
+ return ctemp;
}
static int rcar_thermal_get_current_temp(struct rcar_thermal_priv *priv,
int *temp)
{
- int tmp;
- int ret;
+ int ctemp;
- ret = rcar_thermal_update_temp(priv);
- if (ret < 0)
- return ret;
-
- mutex_lock(&priv->lock);
- if (priv->chip->ctemp_bands == 1)
- tmp = MCELSIUS((priv->ctemp * 5) - 65);
- else if (priv->ctemp < 24)
- tmp = MCELSIUS(((priv->ctemp * 55) - 720) / 10);
- else
- tmp = MCELSIUS((priv->ctemp * 5) - 60);
- mutex_unlock(&priv->lock);
+ ctemp = rcar_thermal_update_temp(priv);
+ if (ctemp < 0)
+ return ctemp;
/* Guaranteed operating range is -45C to 125C. */
- *temp = tmp;
+ if (priv->chip->ctemp_bands == 1)
+ *temp = MCELSIUS((ctemp * 5) - 65);
+ else if (ctemp < 24)
+ *temp = MCELSIUS(((ctemp * 55) - 720) / 10);
+ else
+ *temp = MCELSIUS((ctemp * 5) - 60);
return 0;
}
-static int rcar_thermal_of_get_temp(void *data, int *temp)
-{
- struct rcar_thermal_priv *priv = data;
-
- return rcar_thermal_get_current_temp(priv, temp);
-}
-
static int rcar_thermal_get_temp(struct thermal_zone_device *zone, int *temp)
{
struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
@@ -334,33 +316,14 @@ static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone,
return 0;
}
-static int rcar_thermal_notify(struct thermal_zone_device *zone,
- int trip, enum thermal_trip_type type)
-{
- struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
- struct device *dev = rcar_priv_to_dev(priv);
-
- switch (type) {
- case THERMAL_TRIP_CRITICAL:
- /* FIXME */
- dev_warn(dev, "Thermal reached to critical temperature\n");
- break;
- default:
- break;
- }
-
- return 0;
-}
-
-static const struct thermal_zone_of_device_ops rcar_thermal_zone_of_ops = {
- .get_temp = rcar_thermal_of_get_temp,
+static const struct thermal_zone_device_ops rcar_thermal_zone_of_ops = {
+ .get_temp = rcar_thermal_get_temp,
};
static struct thermal_zone_device_ops rcar_thermal_zone_ops = {
.get_temp = rcar_thermal_get_temp,
.get_trip_type = rcar_thermal_get_trip_type,
.get_trip_temp = rcar_thermal_get_trip_temp,
- .notify = rcar_thermal_notify,
};
/*
@@ -387,28 +350,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;
- int cctemp, nctemp;
int ret;
priv = container_of(work, struct rcar_thermal_priv, work.work);
- ret = rcar_thermal_get_current_temp(priv, &cctemp);
- if (ret < 0)
- return;
-
ret = rcar_thermal_update_temp(priv);
if (ret < 0)
return;
rcar_thermal_irq_enable(priv);
- ret = rcar_thermal_get_current_temp(priv, &nctemp);
- if (ret < 0)
- return;
-
- if (nctemp != cctemp)
- thermal_zone_device_update(priv->zone,
- THERMAL_EVENT_UNSPECIFIED);
+ thermal_zone_device_update(priv->zone, THERMAL_EVENT_UNSPECIFIED);
}
static u32 rcar_thermal_had_changed(struct rcar_thermal_priv *priv, u32 status)
@@ -431,16 +383,15 @@ static irqreturn_t rcar_thermal_irq(int irq, void *data)
{
struct rcar_thermal_common *common = data;
struct rcar_thermal_priv *priv;
- unsigned long flags;
u32 status, mask;
- spin_lock_irqsave(&common->lock, flags);
+ spin_lock(&common->lock);
mask = rcar_thermal_common_read(common, INTMSK);
status = rcar_thermal_common_read(common, STR);
rcar_thermal_common_write(common, STR, 0x000F0F0F & mask);
- spin_unlock_irqrestore(&common->lock, flags);
+ spin_unlock(&common->lock);
status = status & ~mask;
@@ -487,7 +438,7 @@ static int rcar_thermal_probe(struct platform_device *pdev)
struct rcar_thermal_common *common;
struct rcar_thermal_priv *priv;
struct device *dev = &pdev->dev;
- struct resource *res, *irq;
+ struct resource *res;
const struct rcar_thermal_chip *chip = of_device_get_match_data(dev);
int mres = 0;
int i;
@@ -509,9 +460,16 @@ static int rcar_thermal_probe(struct platform_device *pdev)
pm_runtime_get_sync(dev);
for (i = 0; i < chip->nirqs; i++) {
- irq = platform_get_resource(pdev, IORESOURCE_IRQ, i);
- if (!irq)
- continue;
+ int irq;
+
+ ret = platform_get_irq_optional(pdev, i);
+ if (ret < 0 && ret != -ENXIO)
+ goto error_unregister;
+ if (ret > 0)
+ irq = ret;
+ else
+ break;
+
if (!common->base) {
/*
* platform has IRQ support.
@@ -521,13 +479,15 @@ static int rcar_thermal_probe(struct platform_device *pdev)
res = platform_get_resource(pdev, IORESOURCE_MEM,
mres++);
common->base = devm_ioremap_resource(dev, res);
- if (IS_ERR(common->base))
- return PTR_ERR(common->base);
+ if (IS_ERR(common->base)) {
+ ret = PTR_ERR(common->base);
+ goto error_unregister;
+ }
idle = 0; /* polling delay is not needed */
}
- ret = devm_request_irq(dev, irq->start, rcar_thermal_irq,
+ ret = devm_request_irq(dev, irq, rcar_thermal_irq,
IRQF_SHARED, dev_name(dev), common);
if (ret) {
dev_err(dev, "irq request failed\n ");
@@ -566,16 +526,23 @@ static int rcar_thermal_probe(struct platform_device *pdev)
if (ret < 0)
goto error_unregister;
- if (chip->use_of_thermal)
- priv->zone = devm_thermal_zone_of_sensor_register(
+ if (chip->use_of_thermal) {
+ priv->zone = devm_thermal_of_zone_register(
dev, i, priv,
&rcar_thermal_zone_of_ops);
- else
+ } else {
priv->zone = thermal_zone_device_register(
"rcar_thermal",
1, 0, priv,
&rcar_thermal_zone_ops, NULL, 0,
idle);
+
+ ret = thermal_zone_device_enable(priv->zone);
+ if (ret) {
+ thermal_zone_device_unregister(priv->zone);
+ priv->zone = ERR_PTR(ret);
+ }
+ }
if (IS_ERR(priv->zone)) {
dev_err(dev, "can't register thermal zone\n");
ret = PTR_ERR(priv->zone);
diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c
index 7c1a8bccdcba..819e059cde71 100644
--- a/drivers/thermal/rockchip_thermal.c
+++ b/drivers/thermal/rockchip_thermal.c
@@ -211,7 +211,11 @@ struct rockchip_thermal_data {
#define TSADCV3_AUTO_PERIOD_TIME 1875 /* 2.5ms */
#define TSADCV3_AUTO_PERIOD_HT_TIME 1875 /* 2.5ms */
+#define TSADCV5_AUTO_PERIOD_TIME 1622 /* 2.5ms */
+#define TSADCV5_AUTO_PERIOD_HT_TIME 1622 /* 2.5ms */
+
#define TSADCV2_USER_INTER_PD_SOC 0x340 /* 13 clocks */
+#define TSADCV5_USER_INTER_PD_SOC 0xfc0 /* 97us, at least 90us */
#define GRF_SARADC_TESTBIT 0x0e644
#define GRF_TSADC_TESTBIT_L 0x0e648
@@ -219,6 +223,12 @@ struct rockchip_thermal_data {
#define PX30_GRF_SOC_CON2 0x0408
+#define RK3568_GRF_TSADC_CON 0x0600
+#define RK3568_GRF_TSADC_ANA_REG0 (0x10001 << 0)
+#define RK3568_GRF_TSADC_ANA_REG1 (0x10001 << 1)
+#define RK3568_GRF_TSADC_ANA_REG2 (0x10001 << 2)
+#define RK3568_GRF_TSADC_TSEN (0x10001 << 8)
+
#define GRF_SARADC_TESTBIT_ON (0x10001 << 2)
#define GRF_TSADC_TESTBIT_H_ON (0x10001 << 2)
#define GRF_TSADC_VCM_EN_L (0x10001 << 7)
@@ -474,6 +484,45 @@ static const struct tsadc_table rk3399_code_table[] = {
{TSADCV3_DATA_MASK, 125000},
};
+static const struct tsadc_table rk3568_code_table[] = {
+ {0, -40000},
+ {1584, -40000},
+ {1620, -35000},
+ {1652, -30000},
+ {1688, -25000},
+ {1720, -20000},
+ {1756, -15000},
+ {1788, -10000},
+ {1824, -5000},
+ {1856, 0},
+ {1892, 5000},
+ {1924, 10000},
+ {1956, 15000},
+ {1992, 20000},
+ {2024, 25000},
+ {2060, 30000},
+ {2092, 35000},
+ {2128, 40000},
+ {2160, 45000},
+ {2196, 50000},
+ {2228, 55000},
+ {2264, 60000},
+ {2300, 65000},
+ {2332, 70000},
+ {2368, 75000},
+ {2400, 80000},
+ {2436, 85000},
+ {2468, 90000},
+ {2500, 95000},
+ {2536, 100000},
+ {2572, 105000},
+ {2604, 110000},
+ {2636, 115000},
+ {2672, 120000},
+ {2704, 125000},
+ {TSADCV2_DATA_MASK, 125000},
+};
+
static u32 rk_tsadcv2_temp_to_code(const struct chip_tsadc_table *table,
int temp)
{
@@ -701,6 +750,49 @@ static void rk_tsadcv4_initialize(struct regmap *grf, void __iomem *regs,
regmap_write(grf, PX30_GRF_SOC_CON2, GRF_CON_TSADC_CH_INV);
}
+static void rk_tsadcv7_initialize(struct regmap *grf, void __iomem *regs,
+ enum tshut_polarity tshut_polarity)
+{
+ writel_relaxed(TSADCV5_USER_INTER_PD_SOC, regs + TSADCV2_USER_CON);
+ writel_relaxed(TSADCV5_AUTO_PERIOD_TIME, regs + TSADCV2_AUTO_PERIOD);
+ writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_INT_DEBOUNCE);
+ writel_relaxed(TSADCV5_AUTO_PERIOD_HT_TIME,
+ regs + TSADCV2_AUTO_PERIOD_HT);
+ writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE);
+
+ if (tshut_polarity == TSHUT_HIGH_ACTIVE)
+ writel_relaxed(0U | TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
+ regs + TSADCV2_AUTO_CON);
+ else
+ writel_relaxed(0U & ~TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
+ regs + TSADCV2_AUTO_CON);
+
+ /*
+ * The general register file will is optional
+ * and might not be available.
+ */
+ if (!IS_ERR(grf)) {
+ regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_TSEN);
+ /*
+ * RK3568 TRM, section 18.5. requires a delay no less
+ * than 10us between the rising edge of tsadc_tsen_en
+ * and the rising edge of tsadc_ana_reg_0/1/2.
+ */
+ udelay(15);
+ regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_ANA_REG0);
+ regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_ANA_REG1);
+ regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_ANA_REG2);
+
+ /*
+ * RK3568 TRM, section 18.5. requires a delay no less
+ * than 90us after the rising edge of tsadc_ana_reg_0/1/2.
+ */
+ usleep_range(100, 200);
+ }
+}
+
static void rk_tsadcv2_irq_ack(void __iomem *regs)
{
u32 val;
@@ -1027,6 +1119,31 @@ static const struct rockchip_tsadc_chip rk3399_tsadc_data = {
},
};
+static const struct rockchip_tsadc_chip rk3568_tsadc_data = {
+ .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */
+ .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */
+ .chn_num = 2, /* two channels for tsadc */
+
+ .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
+ .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
+ .tshut_temp = 95000,
+
+ .initialize = rk_tsadcv7_initialize,
+ .irq_ack = rk_tsadcv3_irq_ack,
+ .control = rk_tsadcv3_control,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+
+ .table = {
+ .id = rk3568_code_table,
+ .length = ARRAY_SIZE(rk3568_code_table),
+ .data_mask = TSADCV2_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
static const struct of_device_id of_rockchip_thermal_match[] = {
{ .compatible = "rockchip,px30-tsadc",
.data = (void *)&px30_tsadc_data,
@@ -1059,6 +1176,10 @@ static const struct of_device_id of_rockchip_thermal_match[] = {
.compatible = "rockchip,rk3399-tsadc",
.data = (void *)&rk3399_tsadc_data,
},
+ {
+ .compatible = "rockchip,rk3568-tsadc",
+ .data = (void *)&rk3568_tsadc_data,
+ },
{ /* end */ },
};
MODULE_DEVICE_TABLE(of, of_rockchip_thermal_match);
@@ -1068,8 +1189,10 @@ rockchip_thermal_toggle_sensor(struct rockchip_thermal_sensor *sensor, bool on)
{
struct thermal_zone_device *tzd = sensor->tzd;
- tzd->ops->set_mode(tzd,
- on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED);
+ if (on)
+ thermal_zone_device_enable(tzd);
+ else
+ thermal_zone_device_disable(tzd);
}
static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev)
@@ -1088,9 +1211,9 @@ static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev)
return IRQ_HANDLED;
}
-static int rockchip_thermal_set_trips(void *_sensor, int low, int high)
+static int rockchip_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
{
- struct rockchip_thermal_sensor *sensor = _sensor;
+ struct rockchip_thermal_sensor *sensor = tz->devdata;
struct rockchip_thermal_data *thermal = sensor->thermal;
const struct rockchip_tsadc_chip *tsadc = thermal->chip;
@@ -1101,9 +1224,9 @@ static int rockchip_thermal_set_trips(void *_sensor, int low, int high)
sensor->id, thermal->regs, high);
}
-static int rockchip_thermal_get_temp(void *_sensor, int *out_temp)
+static int rockchip_thermal_get_temp(struct thermal_zone_device *tz, int *out_temp)
{
- struct rockchip_thermal_sensor *sensor = _sensor;
+ struct rockchip_thermal_sensor *sensor = tz->devdata;
struct rockchip_thermal_data *thermal = sensor->thermal;
const struct rockchip_tsadc_chip *tsadc = sensor->thermal->chip;
int retval;
@@ -1116,7 +1239,7 @@ static int rockchip_thermal_get_temp(void *_sensor, int *out_temp)
return retval;
}
-static const struct thermal_zone_of_device_ops rockchip_of_thermal_ops = {
+static const struct thermal_zone_device_ops rockchip_of_thermal_ops = {
.get_temp = rockchip_thermal_get_temp,
.set_trips = rockchip_thermal_set_trips,
};
@@ -1203,8 +1326,8 @@ rockchip_thermal_register_sensor(struct platform_device *pdev,
sensor->thermal = thermal;
sensor->id = id;
- sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, id,
- sensor, &rockchip_of_thermal_ops);
+ sensor->tzd = devm_thermal_of_zone_register(&pdev->dev, id, sensor,
+ &rockchip_of_thermal_ops);
if (IS_ERR(sensor->tzd)) {
error = PTR_ERR(sensor->tzd);
dev_err(&pdev->dev, "failed to register sensor %d: %d\n",
@@ -1241,10 +1364,8 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
return -ENXIO;
irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
- dev_err(&pdev->dev, "no irq resource?\n");
+ if (irq < 0)
return -EINVAL;
- }
thermal = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_thermal_data),
GFP_KERNEL);
@@ -1262,7 +1383,7 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
if (IS_ERR(thermal->regs))
return PTR_ERR(thermal->regs);
- thermal->reset = devm_reset_control_get(&pdev->dev, "tsadc-apb");
+ thermal->reset = devm_reset_control_array_get(&pdev->dev, false, false);
if (IS_ERR(thermal->reset)) {
error = PTR_ERR(thermal->reset);
dev_err(&pdev->dev, "failed to get tsadc reset: %d\n", error);
diff --git a/drivers/thermal/rzg2l_thermal.c b/drivers/thermal/rzg2l_thermal.c
new file mode 100644
index 000000000000..2e0649f38506
--- /dev/null
+++ b/drivers/thermal/rzg2l_thermal.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G2L TSU Thermal Sensor Driver
+ *
+ * Copyright (C) 2021 Renesas Electronics Corporation
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "thermal_hwmon.h"
+
+#define CTEMP_MASK 0xFFF
+
+/* default calibration values, if FUSE values are missing */
+#define SW_CALIB0_VAL 3148
+#define SW_CALIB1_VAL 503
+
+/* Register offsets */
+#define TSU_SM 0x00
+#define TSU_ST 0x04
+#define TSU_SAD 0x0C
+#define TSU_SS 0x10
+
+#define OTPTSUTRIM_REG(n) (0x18 + ((n) * 0x4))
+#define OTPTSUTRIM_EN_MASK BIT(31)
+#define OTPTSUTRIM_MASK GENMASK(11, 0)
+
+/* Sensor Mode Register(TSU_SM) */
+#define TSU_SM_EN_TS BIT(0)
+#define TSU_SM_ADC_EN_TS BIT(1)
+#define TSU_SM_NORMAL_MODE (TSU_SM_EN_TS | TSU_SM_ADC_EN_TS)
+
+/* TSU_ST bits */
+#define TSU_ST_START BIT(0)
+
+#define TSU_SS_CONV_RUNNING BIT(0)
+
+#define TS_CODE_AVE_SCALE(x) ((x) * 1000000)
+#define MCELSIUS(temp) ((temp) * MILLIDEGREE_PER_DEGREE)
+#define TS_CODE_CAP_TIMES 8 /* Total number of ADC data samples */
+
+#define RZG2L_THERMAL_GRAN 500 /* milli Celsius */
+#define RZG2L_TSU_SS_TIMEOUT_US 1000
+
+#define CURVATURE_CORRECTION_CONST 13
+
+struct rzg2l_thermal_priv {
+ struct device *dev;
+ void __iomem *base;
+ struct thermal_zone_device *zone;
+ struct reset_control *rstc;
+ u32 calib0, calib1;
+};
+
+static inline u32 rzg2l_thermal_read(struct rzg2l_thermal_priv *priv, u32 reg)
+{
+ return ioread32(priv->base + reg);
+}
+
+static inline void rzg2l_thermal_write(struct rzg2l_thermal_priv *priv, u32 reg,
+ u32 data)
+{
+ iowrite32(data, priv->base + reg);
+}
+
+static int rzg2l_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct rzg2l_thermal_priv *priv = tz->devdata;
+ u32 result = 0, dsensor, ts_code_ave;
+ int val, i;
+
+ for (i = 0; i < TS_CODE_CAP_TIMES ; i++) {
+ /*
+ * TSU repeats measurement at 20 microseconds intervals and
+ * automatically updates the results of measurement. As per
+ * the HW manual for measuring temperature we need to read 8
+ * values consecutively and then take the average.
+ * ts_code_ave = (ts_code[0] + ⋯ + ts_code[7]) / 8
+ */
+ result += rzg2l_thermal_read(priv, TSU_SAD) & CTEMP_MASK;
+ usleep_range(20, 30);
+ }
+
+ ts_code_ave = result / TS_CODE_CAP_TIMES;
+
+ /*
+ * Calculate actual sensor value by applying curvature correction formula
+ * dsensor = ts_code_ave / (1 + ts_code_ave * 0.000013). Here we are doing
+ * integer calculation by scaling all the values by 1000000.
+ */
+ dsensor = TS_CODE_AVE_SCALE(ts_code_ave) /
+ (TS_CODE_AVE_SCALE(1) + (ts_code_ave * CURVATURE_CORRECTION_CONST));
+
+ /*
+ * The temperature Tj is calculated by the formula
+ * Tj = (dsensor − calib1) * 165/ (calib0 − calib1) − 40
+ * where calib0 and calib1 are the calibration values.
+ */
+ val = ((dsensor - priv->calib1) * (MCELSIUS(165) /
+ (priv->calib0 - priv->calib1))) - MCELSIUS(40);
+
+ *temp = roundup(val, RZG2L_THERMAL_GRAN);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops rzg2l_tz_of_ops = {
+ .get_temp = rzg2l_thermal_get_temp,
+};
+
+static int rzg2l_thermal_init(struct rzg2l_thermal_priv *priv)
+{
+ u32 reg_val;
+
+ rzg2l_thermal_write(priv, TSU_SM, TSU_SM_NORMAL_MODE);
+ rzg2l_thermal_write(priv, TSU_ST, 0);
+
+ /*
+ * Before setting the START bit, TSU should be in normal operating
+ * mode. As per the HW manual, it will take 60 µs to place the TSU
+ * into normal operating mode.
+ */
+ usleep_range(60, 80);
+
+ reg_val = rzg2l_thermal_read(priv, TSU_ST);
+ reg_val |= TSU_ST_START;
+ rzg2l_thermal_write(priv, TSU_ST, reg_val);
+
+ return readl_poll_timeout(priv->base + TSU_SS, reg_val,
+ reg_val == TSU_SS_CONV_RUNNING, 50,
+ RZG2L_TSU_SS_TIMEOUT_US);
+}
+
+static void rzg2l_thermal_reset_assert_pm_disable_put(struct platform_device *pdev)
+{
+ struct rzg2l_thermal_priv *priv = dev_get_drvdata(&pdev->dev);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ reset_control_assert(priv->rstc);
+}
+
+static int rzg2l_thermal_remove(struct platform_device *pdev)
+{
+ struct rzg2l_thermal_priv *priv = dev_get_drvdata(&pdev->dev);
+
+ thermal_remove_hwmon_sysfs(priv->zone);
+ rzg2l_thermal_reset_assert_pm_disable_put(pdev);
+
+ return 0;
+}
+
+static int rzg2l_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *zone;
+ struct rzg2l_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->dev = dev;
+ priv->rstc = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(priv->rstc))
+ return dev_err_probe(dev, PTR_ERR(priv->rstc),
+ "failed to get cpg reset");
+
+ ret = reset_control_deassert(priv->rstc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to deassert");
+
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+
+ priv->calib0 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0));
+ if (priv->calib0 & OTPTSUTRIM_EN_MASK)
+ priv->calib0 &= OTPTSUTRIM_MASK;
+ else
+ priv->calib0 = SW_CALIB0_VAL;
+
+ priv->calib1 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(1));
+ if (priv->calib1 & OTPTSUTRIM_EN_MASK)
+ priv->calib1 &= OTPTSUTRIM_MASK;
+ else
+ priv->calib1 = SW_CALIB1_VAL;
+
+ platform_set_drvdata(pdev, priv);
+ ret = rzg2l_thermal_init(priv);
+ if (ret) {
+ dev_err(dev, "Failed to start TSU");
+ goto err;
+ }
+
+ zone = devm_thermal_of_zone_register(dev, 0, priv,
+ &rzg2l_tz_of_ops);
+ if (IS_ERR(zone)) {
+ dev_err(dev, "Can't register thermal zone");
+ ret = PTR_ERR(zone);
+ goto err;
+ }
+
+ priv->zone = zone;
+ priv->zone->tzp->no_hwmon = false;
+ ret = thermal_add_hwmon_sysfs(priv->zone);
+ if (ret)
+ goto err;
+
+ dev_dbg(dev, "TSU probed with %s calibration values",
+ rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0)) ? "hw" : "sw");
+
+ return 0;
+
+err:
+ rzg2l_thermal_reset_assert_pm_disable_put(pdev);
+ return ret;
+}
+
+static const struct of_device_id rzg2l_thermal_dt_ids[] = {
+ { .compatible = "renesas,rzg2l-tsu", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rzg2l_thermal_dt_ids);
+
+static struct platform_driver rzg2l_thermal_driver = {
+ .driver = {
+ .name = "rzg2l_thermal",
+ .of_match_table = rzg2l_thermal_dt_ids,
+ },
+ .probe = rzg2l_thermal_probe,
+ .remove = rzg2l_thermal_remove,
+};
+module_platform_driver(rzg2l_thermal_driver);
+
+MODULE_DESCRIPTION("Renesas RZ/G2L TSU Thermal Sensor Driver");
+MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c
index fd4a17812f33..51874d0a284c 100644
--- a/drivers/thermal/samsung/exynos_tmu.c
+++ b/drivers/thermal/samsung/exynos_tmu.c
@@ -650,9 +650,9 @@ static void exynos7_tmu_control(struct platform_device *pdev, bool on)
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
}
-static int exynos_get_temp(void *p, int *temp)
+static int exynos_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct exynos_tmu_data *data = p;
+ struct exynos_tmu_data *data = tz->devdata;
int value, ret = 0;
if (!data || !data->tmu_read)
@@ -728,9 +728,9 @@ static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data,
writel(val, data->base + emul_con);
}
-static int exynos_tmu_set_emulation(void *drv_data, int temp)
+static int exynos_tmu_set_emulation(struct thermal_zone_device *tz, int temp)
{
- struct exynos_tmu_data *data = drv_data;
+ struct exynos_tmu_data *data = tz->devdata;
int ret = -EINVAL;
if (data->soc == SOC_ARCH_EXYNOS4210)
@@ -750,7 +750,7 @@ out:
}
#else
#define exynos4412_tmu_set_emulation NULL
-static int exynos_tmu_set_emulation(void *drv_data, int temp)
+static int exynos_tmu_set_emulation(struct thermal_zone_device *tz, int temp)
{ return -EINVAL; }
#endif /* CONFIG_THERMAL_EMULATION */
@@ -997,7 +997,7 @@ static int exynos_map_dt_data(struct platform_device *pdev)
return 0;
}
-static const struct thermal_zone_of_device_ops exynos_sensor_ops = {
+static const struct thermal_zone_device_ops exynos_sensor_ops = {
.get_temp = exynos_get_temp,
.set_emul_temp = exynos_tmu_set_emulation,
};
@@ -1073,6 +1073,7 @@ static int exynos_tmu_probe(struct platform_device *pdev)
data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk");
if (IS_ERR(data->sclk)) {
dev_err(&pdev->dev, "Failed to get sclk\n");
+ ret = PTR_ERR(data->sclk);
goto err_clk;
} else {
ret = clk_prepare_enable(data->sclk);
@@ -1090,32 +1091,32 @@ static int exynos_tmu_probe(struct platform_device *pdev)
* data->tzd must be registered before calling exynos_tmu_initialize(),
* requesting irq and calling exynos_tmu_control().
*/
- data->tzd = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
- &exynos_sensor_ops);
+ data->tzd = devm_thermal_of_zone_register(&pdev->dev, 0, data,
+ &exynos_sensor_ops);
if (IS_ERR(data->tzd)) {
ret = PTR_ERR(data->tzd);
- dev_err(&pdev->dev, "Failed to register sensor: %d\n", ret);
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Failed to register sensor: %d\n",
+ ret);
goto err_sclk;
}
ret = exynos_tmu_initialize(pdev);
if (ret) {
dev_err(&pdev->dev, "Failed to initialize TMU\n");
- goto err_thermal;
+ goto err_sclk;
}
ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data);
if (ret) {
dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
- goto err_thermal;
+ goto err_sclk;
}
exynos_tmu_control(pdev, true);
return 0;
-err_thermal:
- thermal_zone_of_sensor_unregister(&pdev->dev, data->tzd);
err_sclk:
clk_disable_unprepare(data->sclk);
err_clk:
@@ -1133,9 +1134,7 @@ err_sensor:
static int exynos_tmu_remove(struct platform_device *pdev)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
- struct thermal_zone_device *tzd = data->tzd;
- thermal_zone_of_sensor_unregister(&pdev->dev, tzd);
exynos_tmu_control(pdev, false);
clk_disable_unprepare(data->sclk);
diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c
index f68f581fd669..ee33ed692e4f 100644
--- a/drivers/thermal/spear_thermal.c
+++ b/drivers/thermal/spear_thermal.c
@@ -131,6 +131,11 @@ static int spear_thermal_probe(struct platform_device *pdev)
ret = PTR_ERR(spear_thermal);
goto disable_clk;
}
+ ret = thermal_zone_device_enable(spear_thermal);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot enable thermal zone\n");
+ goto unregister_tzd;
+ }
platform_set_drvdata(pdev, spear_thermal);
@@ -139,6 +144,8 @@ static int spear_thermal_probe(struct platform_device *pdev)
return 0;
+unregister_tzd:
+ thermal_zone_device_unregister(spear_thermal);
disable_clk:
clk_disable(stdev->clk);
diff --git a/drivers/thermal/sprd_thermal.c b/drivers/thermal/sprd_thermal.c
new file mode 100644
index 000000000000..ac884514f116
--- /dev/null
+++ b/drivers/thermal/sprd_thermal.c
@@ -0,0 +1,558 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2020 Spreadtrum Communications Inc.
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define SPRD_THM_CTL 0x0
+#define SPRD_THM_INT_EN 0x4
+#define SPRD_THM_INT_STS 0x8
+#define SPRD_THM_INT_RAW_STS 0xc
+#define SPRD_THM_DET_PERIOD 0x10
+#define SPRD_THM_INT_CLR 0x14
+#define SPRD_THM_INT_CLR_ST 0x18
+#define SPRD_THM_MON_PERIOD 0x4c
+#define SPRD_THM_MON_CTL 0x50
+#define SPRD_THM_INTERNAL_STS1 0x54
+#define SPRD_THM_RAW_READ_MSK 0x3ff
+
+#define SPRD_THM_OFFSET(id) ((id) * 0x4)
+#define SPRD_THM_TEMP(id) (SPRD_THM_OFFSET(id) + 0x5c)
+#define SPRD_THM_THRES(id) (SPRD_THM_OFFSET(id) + 0x2c)
+
+#define SPRD_THM_SEN(id) BIT((id) + 2)
+#define SPRD_THM_SEN_OVERHEAT_EN(id) BIT((id) + 8)
+#define SPRD_THM_SEN_OVERHEAT_ALARM_EN(id) BIT((id) + 0)
+
+/* bits definitions for register THM_CTL */
+#define SPRD_THM_SET_RDY_ST BIT(13)
+#define SPRD_THM_SET_RDY BIT(12)
+#define SPRD_THM_MON_EN BIT(1)
+#define SPRD_THM_EN BIT(0)
+
+/* bits definitions for register THM_INT_CTL */
+#define SPRD_THM_BIT_INT_EN BIT(26)
+#define SPRD_THM_OVERHEAT_EN BIT(25)
+#define SPRD_THM_OTP_TRIP_SHIFT 10
+
+/* bits definitions for register SPRD_THM_INTERNAL_STS1 */
+#define SPRD_THM_TEMPER_RDY BIT(0)
+
+#define SPRD_THM_DET_PERIOD_DATA 0x800
+#define SPRD_THM_DET_PERIOD_MASK GENMASK(19, 0)
+#define SPRD_THM_MON_MODE 0x7
+#define SPRD_THM_MON_MODE_MASK GENMASK(3, 0)
+#define SPRD_THM_MON_PERIOD_DATA 0x10
+#define SPRD_THM_MON_PERIOD_MASK GENMASK(15, 0)
+#define SPRD_THM_THRES_MASK GENMASK(19, 0)
+#define SPRD_THM_INT_CLR_MASK GENMASK(24, 0)
+
+/* thermal sensor calibration parameters */
+#define SPRD_THM_TEMP_LOW -40000
+#define SPRD_THM_TEMP_HIGH 120000
+#define SPRD_THM_OTP_TEMP 120000
+#define SPRD_THM_HOT_TEMP 75000
+#define SPRD_THM_RAW_DATA_LOW 0
+#define SPRD_THM_RAW_DATA_HIGH 1000
+#define SPRD_THM_SEN_NUM 8
+#define SPRD_THM_DT_OFFSET 24
+#define SPRD_THM_RATION_OFFSET 17
+#define SPRD_THM_RATION_SIGN 16
+
+#define SPRD_THM_RDYST_POLLING_TIME 10
+#define SPRD_THM_RDYST_TIMEOUT 700
+#define SPRD_THM_TEMP_READY_POLL_TIME 10000
+#define SPRD_THM_TEMP_READY_TIMEOUT 600000
+#define SPRD_THM_MAX_SENSOR 8
+
+struct sprd_thermal_sensor {
+ struct thermal_zone_device *tzd;
+ struct sprd_thermal_data *data;
+ struct device *dev;
+ int cal_slope;
+ int cal_offset;
+ int id;
+};
+
+struct sprd_thermal_data {
+ const struct sprd_thm_variant_data *var_data;
+ struct sprd_thermal_sensor *sensor[SPRD_THM_MAX_SENSOR];
+ struct clk *clk;
+ void __iomem *base;
+ u32 ratio_off;
+ int ratio_sign;
+ int nr_sensors;
+};
+
+/*
+ * The conversion between ADC and temperature is based on linear relationship,
+ * and use idea_k to specify the slope and ideal_b to specify the offset.
+ *
+ * Since different Spreadtrum SoCs have different ideal_k and ideal_b,
+ * we should save ideal_k and ideal_b in the device data structure.
+ */
+struct sprd_thm_variant_data {
+ u32 ideal_k;
+ u32 ideal_b;
+};
+
+static const struct sprd_thm_variant_data ums512_data = {
+ .ideal_k = 262,
+ .ideal_b = 66400,
+};
+
+static inline void sprd_thm_update_bits(void __iomem *reg, u32 mask, u32 val)
+{
+ u32 tmp, orig;
+
+ orig = readl(reg);
+ tmp = orig & ~mask;
+ tmp |= val & mask;
+ writel(tmp, reg);
+}
+
+static int sprd_thm_cal_read(struct device_node *np, const char *cell_id,
+ u32 *val)
+{
+ struct nvmem_cell *cell;
+ void *buf;
+ size_t len;
+
+ cell = of_nvmem_cell_get(np, cell_id);
+ if (IS_ERR(cell))
+ return PTR_ERR(cell);
+
+ buf = nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ if (len > sizeof(u32)) {
+ kfree(buf);
+ return -EINVAL;
+ }
+
+ memcpy(val, buf, len);
+
+ kfree(buf);
+ return 0;
+}
+
+static int sprd_thm_sensor_calibration(struct device_node *np,
+ struct sprd_thermal_data *thm,
+ struct sprd_thermal_sensor *sen)
+{
+ int ret;
+ /*
+ * According to thermal datasheet, the default calibration offset is 64,
+ * and the default ratio is 1000.
+ */
+ int dt_offset = 64, ratio = 1000;
+
+ ret = sprd_thm_cal_read(np, "sen_delta_cal", &dt_offset);
+ if (ret)
+ return ret;
+
+ ratio += thm->ratio_sign * thm->ratio_off;
+
+ /*
+ * According to the ideal slope K and ideal offset B, combined with
+ * calibration value of thermal from efuse, then calibrate the real
+ * slope k and offset b:
+ * k_cal = (k * ratio) / 1000.
+ * b_cal = b + (dt_offset - 64) * 500.
+ */
+ sen->cal_slope = (thm->var_data->ideal_k * ratio) / 1000;
+ sen->cal_offset = thm->var_data->ideal_b + (dt_offset - 128) * 250;
+
+ return 0;
+}
+
+static int sprd_thm_rawdata_to_temp(struct sprd_thermal_sensor *sen,
+ u32 rawdata)
+{
+ clamp(rawdata, (u32)SPRD_THM_RAW_DATA_LOW, (u32)SPRD_THM_RAW_DATA_HIGH);
+
+ /*
+ * According to the thermal datasheet, the formula of converting
+ * adc value to the temperature value should be:
+ * T_final = k_cal * x - b_cal.
+ */
+ return sen->cal_slope * rawdata - sen->cal_offset;
+}
+
+static int sprd_thm_temp_to_rawdata(int temp, struct sprd_thermal_sensor *sen)
+{
+ u32 val;
+
+ clamp(temp, (int)SPRD_THM_TEMP_LOW, (int)SPRD_THM_TEMP_HIGH);
+
+ /*
+ * According to the thermal datasheet, the formula of converting
+ * adc value to the temperature value should be:
+ * T_final = k_cal * x - b_cal.
+ */
+ val = (temp + sen->cal_offset) / sen->cal_slope;
+
+ return clamp(val, val, (u32)(SPRD_THM_RAW_DATA_HIGH - 1));
+}
+
+static int sprd_thm_read_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct sprd_thermal_sensor *sen = tz->devdata;
+ u32 data;
+
+ data = readl(sen->data->base + SPRD_THM_TEMP(sen->id)) &
+ SPRD_THM_RAW_READ_MSK;
+
+ *temp = sprd_thm_rawdata_to_temp(sen, data);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops sprd_thm_ops = {
+ .get_temp = sprd_thm_read_temp,
+};
+
+static int sprd_thm_poll_ready_status(struct sprd_thermal_data *thm)
+{
+ u32 val;
+ int ret;
+
+ /*
+ * Wait for thermal ready status before configuring thermal parameters.
+ */
+ ret = readl_poll_timeout(thm->base + SPRD_THM_CTL, val,
+ !(val & SPRD_THM_SET_RDY_ST),
+ SPRD_THM_RDYST_POLLING_TIME,
+ SPRD_THM_RDYST_TIMEOUT);
+ if (ret)
+ return ret;
+
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL, SPRD_THM_MON_EN,
+ SPRD_THM_MON_EN);
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL, SPRD_THM_SET_RDY,
+ SPRD_THM_SET_RDY);
+ return 0;
+}
+
+static int sprd_thm_wait_temp_ready(struct sprd_thermal_data *thm)
+{
+ u32 val;
+
+ /* Wait for first temperature data ready before reading temperature */
+ return readl_poll_timeout(thm->base + SPRD_THM_INTERNAL_STS1, val,
+ !(val & SPRD_THM_TEMPER_RDY),
+ SPRD_THM_TEMP_READY_POLL_TIME,
+ SPRD_THM_TEMP_READY_TIMEOUT);
+}
+
+static int sprd_thm_set_ready(struct sprd_thermal_data *thm)
+{
+ int ret;
+
+ ret = sprd_thm_poll_ready_status(thm);
+ if (ret)
+ return ret;
+
+ /*
+ * Clear interrupt status, enable thermal interrupt and enable thermal.
+ *
+ * The SPRD thermal controller integrates a hardware interrupt signal,
+ * which means if the temperature is overheat, it will generate an
+ * interrupt and notify the event to PMIC automatically to shutdown the
+ * system. So here we should enable the interrupt bits, though we have
+ * not registered an irq handler.
+ */
+ writel(SPRD_THM_INT_CLR_MASK, thm->base + SPRD_THM_INT_CLR);
+ sprd_thm_update_bits(thm->base + SPRD_THM_INT_EN,
+ SPRD_THM_BIT_INT_EN, SPRD_THM_BIT_INT_EN);
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_EN, SPRD_THM_EN);
+ return 0;
+}
+
+static void sprd_thm_sensor_init(struct sprd_thermal_data *thm,
+ struct sprd_thermal_sensor *sen)
+{
+ u32 otp_rawdata, hot_rawdata;
+
+ otp_rawdata = sprd_thm_temp_to_rawdata(SPRD_THM_OTP_TEMP, sen);
+ hot_rawdata = sprd_thm_temp_to_rawdata(SPRD_THM_HOT_TEMP, sen);
+
+ /* Enable the sensor' overheat temperature protection interrupt */
+ sprd_thm_update_bits(thm->base + SPRD_THM_INT_EN,
+ SPRD_THM_SEN_OVERHEAT_ALARM_EN(sen->id),
+ SPRD_THM_SEN_OVERHEAT_ALARM_EN(sen->id));
+
+ /* Set the sensor' overheat and hot threshold temperature */
+ sprd_thm_update_bits(thm->base + SPRD_THM_THRES(sen->id),
+ SPRD_THM_THRES_MASK,
+ (otp_rawdata << SPRD_THM_OTP_TRIP_SHIFT) |
+ hot_rawdata);
+
+ /* Enable the corresponding sensor */
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL, SPRD_THM_SEN(sen->id),
+ SPRD_THM_SEN(sen->id));
+}
+
+static void sprd_thm_para_config(struct sprd_thermal_data *thm)
+{
+ /* Set the period of two valid temperature detection action */
+ sprd_thm_update_bits(thm->base + SPRD_THM_DET_PERIOD,
+ SPRD_THM_DET_PERIOD_MASK, SPRD_THM_DET_PERIOD);
+
+ /* Set the sensors' monitor mode */
+ sprd_thm_update_bits(thm->base + SPRD_THM_MON_CTL,
+ SPRD_THM_MON_MODE_MASK, SPRD_THM_MON_MODE);
+
+ /* Set the sensors' monitor period */
+ sprd_thm_update_bits(thm->base + SPRD_THM_MON_PERIOD,
+ SPRD_THM_MON_PERIOD_MASK, SPRD_THM_MON_PERIOD);
+}
+
+static void sprd_thm_toggle_sensor(struct sprd_thermal_sensor *sen, bool on)
+{
+ struct thermal_zone_device *tzd = sen->tzd;
+
+ if (on)
+ thermal_zone_device_enable(tzd);
+ else
+ thermal_zone_device_disable(tzd);
+}
+
+static int sprd_thm_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *sen_child;
+ struct sprd_thermal_data *thm;
+ struct sprd_thermal_sensor *sen;
+ const struct sprd_thm_variant_data *pdata;
+ int ret, i;
+ u32 val;
+
+ pdata = of_device_get_match_data(&pdev->dev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "No matching driver data found\n");
+ return -EINVAL;
+ }
+
+ thm = devm_kzalloc(&pdev->dev, sizeof(*thm), GFP_KERNEL);
+ if (!thm)
+ return -ENOMEM;
+
+ thm->var_data = pdata;
+ thm->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(thm->base))
+ return PTR_ERR(thm->base);
+
+ thm->nr_sensors = of_get_child_count(np);
+ if (thm->nr_sensors == 0 || thm->nr_sensors > SPRD_THM_MAX_SENSOR) {
+ dev_err(&pdev->dev, "incorrect sensor count\n");
+ return -EINVAL;
+ }
+
+ thm->clk = devm_clk_get(&pdev->dev, "enable");
+ if (IS_ERR(thm->clk)) {
+ dev_err(&pdev->dev, "failed to get enable clock\n");
+ return PTR_ERR(thm->clk);
+ }
+
+ ret = clk_prepare_enable(thm->clk);
+ if (ret)
+ return ret;
+
+ sprd_thm_para_config(thm);
+
+ ret = sprd_thm_cal_read(np, "thm_sign_cal", &val);
+ if (ret)
+ goto disable_clk;
+
+ if (val > 0)
+ thm->ratio_sign = -1;
+ else
+ thm->ratio_sign = 1;
+
+ ret = sprd_thm_cal_read(np, "thm_ratio_cal", &thm->ratio_off);
+ if (ret)
+ goto disable_clk;
+
+ for_each_child_of_node(np, sen_child) {
+ sen = devm_kzalloc(&pdev->dev, sizeof(*sen), GFP_KERNEL);
+ if (!sen) {
+ ret = -ENOMEM;
+ goto of_put;
+ }
+
+ sen->data = thm;
+ sen->dev = &pdev->dev;
+
+ ret = of_property_read_u32(sen_child, "reg", &sen->id);
+ if (ret) {
+ dev_err(&pdev->dev, "get sensor reg failed");
+ goto of_put;
+ }
+
+ ret = sprd_thm_sensor_calibration(sen_child, thm, sen);
+ if (ret) {
+ dev_err(&pdev->dev, "efuse cal analysis failed");
+ goto of_put;
+ }
+
+ sprd_thm_sensor_init(thm, sen);
+
+ sen->tzd = devm_thermal_of_zone_register(sen->dev,
+ sen->id,
+ sen,
+ &sprd_thm_ops);
+ if (IS_ERR(sen->tzd)) {
+ dev_err(&pdev->dev, "register thermal zone failed %d\n",
+ sen->id);
+ ret = PTR_ERR(sen->tzd);
+ goto of_put;
+ }
+
+ thm->sensor[sen->id] = sen;
+ }
+ /* sen_child set to NULL at this point */
+
+ ret = sprd_thm_set_ready(thm);
+ if (ret)
+ goto of_put;
+
+ ret = sprd_thm_wait_temp_ready(thm);
+ if (ret)
+ goto of_put;
+
+ for (i = 0; i < thm->nr_sensors; i++)
+ sprd_thm_toggle_sensor(thm->sensor[i], true);
+
+ platform_set_drvdata(pdev, thm);
+ return 0;
+
+of_put:
+ of_node_put(sen_child);
+disable_clk:
+ clk_disable_unprepare(thm->clk);
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void sprd_thm_hw_suspend(struct sprd_thermal_data *thm)
+{
+ int i;
+
+ for (i = 0; i < thm->nr_sensors; i++) {
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_SEN(thm->sensor[i]->id), 0);
+ }
+
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_EN, 0x0);
+}
+
+static int sprd_thm_suspend(struct device *dev)
+{
+ struct sprd_thermal_data *thm = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < thm->nr_sensors; i++)
+ sprd_thm_toggle_sensor(thm->sensor[i], false);
+
+ sprd_thm_hw_suspend(thm);
+ clk_disable_unprepare(thm->clk);
+
+ return 0;
+}
+
+static int sprd_thm_hw_resume(struct sprd_thermal_data *thm)
+{
+ int ret, i;
+
+ for (i = 0; i < thm->nr_sensors; i++) {
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_SEN(thm->sensor[i]->id),
+ SPRD_THM_SEN(thm->sensor[i]->id));
+ }
+
+ ret = sprd_thm_poll_ready_status(thm);
+ if (ret)
+ return ret;
+
+ writel(SPRD_THM_INT_CLR_MASK, thm->base + SPRD_THM_INT_CLR);
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_EN, SPRD_THM_EN);
+ return sprd_thm_wait_temp_ready(thm);
+}
+
+static int sprd_thm_resume(struct device *dev)
+{
+ struct sprd_thermal_data *thm = dev_get_drvdata(dev);
+ int ret, i;
+
+ ret = clk_prepare_enable(thm->clk);
+ if (ret)
+ return ret;
+
+ ret = sprd_thm_hw_resume(thm);
+ if (ret)
+ goto disable_clk;
+
+ for (i = 0; i < thm->nr_sensors; i++)
+ sprd_thm_toggle_sensor(thm->sensor[i], true);
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(thm->clk);
+ return ret;
+}
+#endif
+
+static int sprd_thm_remove(struct platform_device *pdev)
+{
+ struct sprd_thermal_data *thm = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < thm->nr_sensors; i++) {
+ sprd_thm_toggle_sensor(thm->sensor[i], false);
+ devm_thermal_of_zone_unregister(&pdev->dev,
+ thm->sensor[i]->tzd);
+ }
+
+ clk_disable_unprepare(thm->clk);
+ return 0;
+}
+
+static const struct of_device_id sprd_thermal_of_match[] = {
+ { .compatible = "sprd,ums512-thermal", .data = &ums512_data },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sprd_thermal_of_match);
+
+static const struct dev_pm_ops sprd_thermal_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(sprd_thm_suspend, sprd_thm_resume)
+};
+
+static struct platform_driver sprd_thermal_driver = {
+ .probe = sprd_thm_probe,
+ .remove = sprd_thm_remove,
+ .driver = {
+ .name = "sprd-thermal",
+ .pm = &sprd_thermal_pm_ops,
+ .of_match_table = sprd_thermal_of_match,
+ },
+};
+
+module_platform_driver(sprd_thermal_driver);
+
+MODULE_AUTHOR("Freeman Liu <freeman.liu@unisoc.com>");
+MODULE_DESCRIPTION("Spreadtrum thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/st/Kconfig b/drivers/thermal/st/Kconfig
index 3c3b695cc3e9..58ece381956b 100644
--- a/drivers/thermal/st/Kconfig
+++ b/drivers/thermal/st/Kconfig
@@ -23,5 +23,5 @@ config STM32_THERMAL
help
Support for thermal framework on STMicroelectronics STM32 series of
SoCs. This thermal driver allows to access to general thermal framework
- functionalities and to acces to SoC sensor functionalities. This
+ functionalities and to access to SoC sensor functionalities. This
configuration is fully dependent of MACH_STM32MP157.
diff --git a/drivers/thermal/st/st_thermal.c b/drivers/thermal/st/st_thermal.c
index b928ca6a289b..1276b95604fe 100644
--- a/drivers/thermal/st/st_thermal.c
+++ b/drivers/thermal/st/st_thermal.c
@@ -246,11 +246,16 @@ int st_thermal_register(struct platform_device *pdev,
ret = PTR_ERR(sensor->thermal_dev);
goto sensor_off;
}
+ ret = thermal_zone_device_enable(sensor->thermal_dev);
+ if (ret)
+ goto tzd_unregister;
platform_set_drvdata(pdev, sensor);
return 0;
+tzd_unregister:
+ thermal_zone_device_unregister(sensor->thermal_dev);
sensor_off:
st_thermal_sensor_off(sensor);
diff --git a/drivers/thermal/st/st_thermal_memmap.c b/drivers/thermal/st/st_thermal_memmap.c
index a824b78dabf8..d68596c40be9 100644
--- a/drivers/thermal/st/st_thermal_memmap.c
+++ b/drivers/thermal/st/st_thermal_memmap.c
@@ -94,10 +94,8 @@ static int st_mmap_register_enable_irq(struct st_thermal_sensor *sensor)
int ret;
sensor->irq = platform_get_irq(pdev, 0);
- if (sensor->irq < 0) {
- dev_err(dev, "failed to register IRQ\n");
+ if (sensor->irq < 0)
return sensor->irq;
- }
ret = devm_request_threaded_irq(dev, sensor->irq,
NULL, st_mmap_thermal_trip_handler,
@@ -121,19 +119,10 @@ static int st_mmap_regmap_init(struct st_thermal_sensor *sensor)
{
struct device *dev = sensor->dev;
struct platform_device *pdev = to_platform_device(dev);
- struct resource *res;
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
- dev_err(dev, "no memory resources defined\n");
- return -ENODEV;
- }
- sensor->mmio_base = devm_ioremap_resource(dev, res);
- if (IS_ERR(sensor->mmio_base)) {
- dev_err(dev, "failed to remap IO\n");
+ sensor->mmio_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(sensor->mmio_base))
return PTR_ERR(sensor->mmio_base);
- }
sensor->regmap = devm_regmap_init_mmio(dev, sensor->mmio_base,
&st_416mpe_regmap_config);
diff --git a/drivers/thermal/st/stm_thermal.c b/drivers/thermal/st/stm_thermal.c
index ad9e3bf8fdf6..78feb802a87d 100644
--- a/drivers/thermal/st/stm_thermal.c
+++ b/drivers/thermal/st/stm_thermal.c
@@ -302,9 +302,9 @@ static int stm_disable_irq(struct stm_thermal_sensor *sensor)
return 0;
}
-static int stm_thermal_set_trips(void *data, int low, int high)
+static int stm_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
{
- struct stm_thermal_sensor *sensor = data;
+ struct stm_thermal_sensor *sensor = tz->devdata;
u32 itr1, th;
int ret;
@@ -350,9 +350,9 @@ static int stm_thermal_set_trips(void *data, int low, int high)
}
/* Callback to get temperature from HW */
-static int stm_thermal_get_temp(void *data, int *temp)
+static int stm_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct stm_thermal_sensor *sensor = data;
+ struct stm_thermal_sensor *sensor = tz->devdata;
u32 periods;
int freqM, ret;
@@ -385,10 +385,8 @@ static int stm_register_irq(struct stm_thermal_sensor *sensor)
int ret;
sensor->irq = platform_get_irq(pdev, 0);
- if (sensor->irq < 0) {
- dev_err(dev, "%s: Unable to find IRQ\n", __func__);
+ if (sensor->irq < 0)
return sensor->irq;
- }
ret = devm_request_threaded_irq(dev, sensor->irq,
NULL,
@@ -448,14 +446,9 @@ thermal_unprepare:
#ifdef CONFIG_PM_SLEEP
static int stm_thermal_suspend(struct device *dev)
{
- int ret;
struct stm_thermal_sensor *sensor = dev_get_drvdata(dev);
- ret = stm_thermal_sensor_off(sensor);
- if (ret)
- return ret;
-
- return 0;
+ return stm_thermal_sensor_off(sensor);
}
static int stm_thermal_resume(struct device *dev)
@@ -478,9 +471,10 @@ static int stm_thermal_resume(struct device *dev)
}
#endif /* CONFIG_PM_SLEEP */
-SIMPLE_DEV_PM_OPS(stm_thermal_pm_ops, stm_thermal_suspend, stm_thermal_resume);
+static SIMPLE_DEV_PM_OPS(stm_thermal_pm_ops,
+ stm_thermal_suspend, stm_thermal_resume);
-static const struct thermal_zone_of_device_ops stm_tz_ops = {
+static const struct thermal_zone_device_ops stm_tz_ops = {
.get_temp = stm_thermal_get_temp,
.set_trips = stm_thermal_set_trips,
};
@@ -545,9 +539,9 @@ static int stm_thermal_probe(struct platform_device *pdev)
return ret;
}
- sensor->th_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0,
- sensor,
- &stm_tz_ops);
+ sensor->th_dev = devm_thermal_of_zone_register(&pdev->dev, 0,
+ sensor,
+ &stm_tz_ops);
if (IS_ERR(sensor->th_dev)) {
dev_err(&pdev->dev, "%s: thermal zone sensor registering KO\n",
@@ -578,7 +572,6 @@ static int stm_thermal_probe(struct platform_device *pdev)
return 0;
err_tz:
- thermal_zone_of_sensor_unregister(&pdev->dev, sensor->th_dev);
return ret;
}
@@ -588,7 +581,6 @@ static int stm_thermal_remove(struct platform_device *pdev)
stm_thermal_sensor_off(sensor);
thermal_remove_hwmon_sysfs(sensor->th_dev);
- thermal_zone_of_sensor_unregister(&pdev->dev, sensor->th_dev);
return 0;
}
diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c
index 74d73be16496..e64d06d1328c 100644
--- a/drivers/thermal/sun8i_thermal.c
+++ b/drivers/thermal/sun8i_thermal.c
@@ -8,6 +8,7 @@
* Based on the work of Josef Gajdusek <atx@atx.name>
*/
+#include <linux/bitmap.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/interrupt.h>
@@ -74,7 +75,7 @@ struct ths_thermal_chip {
int (*calibrate)(struct ths_device *tmdev,
u16 *caldata, int callen);
int (*init)(struct ths_device *tmdev);
- int (*irq_ack)(struct ths_device *tmdev);
+ unsigned long (*irq_ack)(struct ths_device *tmdev);
int (*calc_temp)(struct ths_device *tmdev,
int id, int reg);
};
@@ -107,9 +108,9 @@ static int sun50i_h5_calc_temp(struct ths_device *tmdev,
return -1590 * reg / 10 + 276000;
}
-static int sun8i_ths_get_temp(void *data, int *temp)
+static int sun8i_ths_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct tsensor *s = data;
+ struct tsensor *s = tz->devdata;
struct ths_device *tmdev = s->tmdev;
int val = 0;
@@ -134,7 +135,7 @@ static int sun8i_ths_get_temp(void *data, int *temp)
return 0;
}
-static const struct thermal_zone_of_device_ops ths_ops = {
+static const struct thermal_zone_device_ops ths_ops = {
.get_temp = sun8i_ths_get_temp,
};
@@ -146,9 +147,10 @@ static const struct regmap_config config = {
.max_register = 0xfc,
};
-static int sun8i_h3_irq_ack(struct ths_device *tmdev)
+static unsigned long sun8i_h3_irq_ack(struct ths_device *tmdev)
{
- int i, state, ret = 0;
+ unsigned long irq_bitmap = 0;
+ int i, state;
regmap_read(tmdev->regmap, SUN8I_THS_IS, &state);
@@ -156,16 +158,17 @@ static int sun8i_h3_irq_ack(struct ths_device *tmdev)
if (state & SUN8I_THS_DATA_IRQ_STS(i)) {
regmap_write(tmdev->regmap, SUN8I_THS_IS,
SUN8I_THS_DATA_IRQ_STS(i));
- ret |= BIT(i);
+ bitmap_set(&irq_bitmap, i, 1);
}
}
- return ret;
+ return irq_bitmap;
}
-static int sun50i_h6_irq_ack(struct ths_device *tmdev)
+static unsigned long sun50i_h6_irq_ack(struct ths_device *tmdev)
{
- int i, state, ret = 0;
+ unsigned long irq_bitmap = 0;
+ int i, state;
regmap_read(tmdev->regmap, SUN50I_H6_THS_DIS, &state);
@@ -173,24 +176,22 @@ static int sun50i_h6_irq_ack(struct ths_device *tmdev)
if (state & SUN50I_H6_THS_DATA_IRQ_STS(i)) {
regmap_write(tmdev->regmap, SUN50I_H6_THS_DIS,
SUN50I_H6_THS_DATA_IRQ_STS(i));
- ret |= BIT(i);
+ bitmap_set(&irq_bitmap, i, 1);
}
}
- return ret;
+ return irq_bitmap;
}
static irqreturn_t sun8i_irq_thread(int irq, void *data)
{
struct ths_device *tmdev = data;
- int i, state;
-
- state = tmdev->chip->irq_ack(tmdev);
+ unsigned long irq_bitmap = tmdev->chip->irq_ack(tmdev);
+ int i;
- for (i = 0; i < tmdev->chip->sensor_num; i++) {
- if (state & BIT(i))
- thermal_zone_device_update(tmdev->sensor[i].tzd,
- THERMAL_EVENT_UNSPECIFIED);
+ for_each_set_bit(i, &irq_bitmap, tmdev->chip->sensor_num) {
+ thermal_zone_device_update(tmdev->sensor[i].tzd,
+ THERMAL_EVENT_UNSPECIFIED);
}
return IRQ_HANDLED;
@@ -236,7 +237,7 @@ static int sun50i_h6_ths_calibrate(struct ths_device *tmdev,
* The calibration data on the H6 is the ambient temperature and
* sensor values that are filled during the factory test stage.
*
- * The unit of stored FT temperature is 0.1 degreee celusis.
+ * The unit of stored FT temperature is 0.1 degree celsius.
*
* We need to calculate a delta between measured and caluclated
* register values and this will become a calibration offset.
@@ -244,7 +245,7 @@ static int sun50i_h6_ths_calibrate(struct ths_device *tmdev,
ft_temp = (caldata[0] & FT_TEMP_MASK) * 100;
for (i = 0; i < tmdev->chip->sensor_num; i++) {
- int sensor_reg = caldata[i + 1];
+ int sensor_reg = caldata[i + 1] & TEMP_CALIB_MASK;
int cdata, offset;
int sensor_temp = tmdev->chip->calc_temp(tmdev, i, sensor_reg);
@@ -299,7 +300,7 @@ static int sun8i_ths_calibrate(struct ths_device *tmdev)
* or 0x8xx, so they won't be away from the default value
* for a lot.
*
- * So here we do not return error if the calibartion data is
+ * So here we do not return error if the calibration data is
* not available, except the probe needs deferring.
*/
goto out;
@@ -417,7 +418,7 @@ static int sun8i_h3_thermal_init(struct ths_device *tmdev)
}
/*
- * Without this undocummented value, the returned temperatures would
+ * Without this undocumented value, the returned temperatures would
* be higher than real ones by about 20C.
*/
#define SUN50I_H6_CTRL0_UNK 0x0000002f
@@ -467,10 +468,10 @@ static int sun8i_ths_register(struct ths_device *tmdev)
tmdev->sensor[i].tmdev = tmdev;
tmdev->sensor[i].id = i;
tmdev->sensor[i].tzd =
- devm_thermal_zone_of_sensor_register(tmdev->dev,
- i,
- &tmdev->sensor[i],
- &ths_ops);
+ devm_thermal_of_zone_register(tmdev->dev,
+ i,
+ &tmdev->sensor[i],
+ &ths_ops);
if (IS_ERR(tmdev->sensor[i].tzd))
return PTR_ERR(tmdev->sensor[i].tzd);
@@ -590,6 +591,19 @@ static const struct ths_thermal_chip sun50i_a64_ths = {
.calc_temp = sun8i_ths_calc_temp,
};
+static const struct ths_thermal_chip sun50i_a100_ths = {
+ .sensor_num = 3,
+ .has_bus_clk_reset = true,
+ .ft_deviation = 8000,
+ .offset = 187744,
+ .scale = 672,
+ .temp_data_base = SUN50I_H6_THS_TEMP_DATA,
+ .calibrate = sun50i_h6_ths_calibrate,
+ .init = sun50i_h6_thermal_init,
+ .irq_ack = sun50i_h6_irq_ack,
+ .calc_temp = sun8i_ths_calc_temp,
+};
+
static const struct ths_thermal_chip sun50i_h5_ths = {
.sensor_num = 2,
.has_mod_clk = true,
@@ -619,6 +633,7 @@ static const struct of_device_id of_ths_match[] = {
{ .compatible = "allwinner,sun8i-h3-ths", .data = &sun8i_h3_ths },
{ .compatible = "allwinner,sun8i-r40-ths", .data = &sun8i_r40_ths },
{ .compatible = "allwinner,sun50i-a64-ths", .data = &sun50i_a64_ths },
+ { .compatible = "allwinner,sun50i-a100-ths", .data = &sun50i_a100_ths },
{ .compatible = "allwinner,sun50i-h5-ths", .data = &sun50i_h5_ths },
{ .compatible = "allwinner,sun50i-h6-ths", .data = &sun50i_h6_ths },
{ /* sentinel */ },
diff --git a/drivers/thermal/tango_thermal.c b/drivers/thermal/tango_thermal.c
deleted file mode 100644
index 304b461e12aa..000000000000
--- a/drivers/thermal/tango_thermal.c
+++ /dev/null
@@ -1,126 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-#include <linux/io.h>
-#include <linux/delay.h>
-#include <linux/module.h>
-#include <linux/thermal.h>
-#include <linux/platform_device.h>
-
-/*
- * According to a data sheet draft, "this temperature sensor uses a bandgap
- * type of circuit to compare a voltage which has a negative temperature
- * coefficient with a voltage that is proportional to absolute temperature.
- * A resistor bank allows 41 different temperature thresholds to be selected
- * and the logic output will then indicate whether the actual die temperature
- * lies above or below the selected threshold."
- */
-
-#define TEMPSI_CMD 0
-#define TEMPSI_RES 4
-#define TEMPSI_CFG 8
-
-#define CMD_OFF 0
-#define CMD_ON 1
-#define CMD_READ 2
-
-#define IDX_MIN 15
-#define IDX_MAX 40
-
-struct tango_thermal_priv {
- void __iomem *base;
- int thresh_idx;
-};
-
-static bool temp_above_thresh(void __iomem *base, int thresh_idx)
-{
- writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD);
- usleep_range(10, 20);
- writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD);
-
- return readl(base + TEMPSI_RES);
-}
-
-static int tango_get_temp(void *arg, int *res)
-{
- struct tango_thermal_priv *priv = arg;
- int idx = priv->thresh_idx;
-
- if (temp_above_thresh(priv->base, idx)) {
- /* Search upward by incrementing thresh_idx */
- while (idx < IDX_MAX && temp_above_thresh(priv->base, ++idx))
- cpu_relax();
- idx = idx - 1; /* always return lower bound */
- } else {
- /* Search downward by decrementing thresh_idx */
- while (idx > IDX_MIN && !temp_above_thresh(priv->base, --idx))
- cpu_relax();
- }
-
- *res = (idx * 9 / 2 - 38) * 1000; /* millidegrees Celsius */
- priv->thresh_idx = idx;
-
- return 0;
-}
-
-static const struct thermal_zone_of_device_ops ops = {
- .get_temp = tango_get_temp,
-};
-
-static void tango_thermal_init(struct tango_thermal_priv *priv)
-{
- writel(0, priv->base + TEMPSI_CFG);
- writel(CMD_ON, priv->base + TEMPSI_CMD);
-}
-
-static int tango_thermal_probe(struct platform_device *pdev)
-{
- struct resource *res;
- struct tango_thermal_priv *priv;
- struct thermal_zone_device *tzdev;
-
- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- priv->base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(priv->base))
- return PTR_ERR(priv->base);
-
- platform_set_drvdata(pdev, priv);
- priv->thresh_idx = IDX_MIN;
- tango_thermal_init(priv);
-
- tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv, &ops);
- return PTR_ERR_OR_ZERO(tzdev);
-}
-
-static int __maybe_unused tango_thermal_resume(struct device *dev)
-{
- tango_thermal_init(dev_get_drvdata(dev));
- return 0;
-}
-
-static SIMPLE_DEV_PM_OPS(tango_thermal_pm, NULL, tango_thermal_resume);
-
-static const struct of_device_id tango_sensor_ids[] = {
- {
- .compatible = "sigma,smp8758-thermal",
- },
- { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, tango_sensor_ids);
-
-static struct platform_driver tango_thermal_driver = {
- .probe = tango_thermal_probe,
- .driver = {
- .name = "tango-thermal",
- .of_match_table = tango_sensor_ids,
- .pm = &tango_thermal_pm,
- },
-};
-
-module_platform_driver(tango_thermal_driver);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Sigma Designs");
-MODULE_DESCRIPTION("Tango temperature sensor");
diff --git a/drivers/thermal/tegra/Kconfig b/drivers/thermal/tegra/Kconfig
index 46c2215867cd..cfa41d87a794 100644
--- a/drivers/thermal/tegra/Kconfig
+++ b/drivers/thermal/tegra/Kconfig
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "NVIDIA Tegra thermal drivers"
-depends on ARCH_TEGRA
+depends on ARCH_TEGRA || COMPILE_TEST
config TEGRA_SOCTHERM
tristate "Tegra SOCTHERM thermal management"
@@ -18,4 +18,11 @@ config TEGRA_BPMP_THERMAL
Enable this option for support for sensing system temperature of NVIDIA
Tegra systems-on-chip with the BPMP coprocessor (Tegra186).
+config TEGRA30_TSENSOR
+ tristate "Tegra30 Thermal Sensor"
+ depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST
+ help
+ Enable this option to support thermal management of NVIDIA Tegra30
+ system-on-chip.
+
endmenu
diff --git a/drivers/thermal/tegra/Makefile b/drivers/thermal/tegra/Makefile
index 0f2b66edf0d2..eb27d194c583 100644
--- a/drivers/thermal/tegra/Makefile
+++ b/drivers/thermal/tegra/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TEGRA_SOCTHERM) += tegra-soctherm.o
obj-$(CONFIG_TEGRA_BPMP_THERMAL) += tegra-bpmp-thermal.o
+obj-$(CONFIG_TEGRA30_TSENSOR) += tegra30-tsensor.o
tegra-soctherm-y := soctherm.o soctherm-fuse.o
tegra-soctherm-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124-soctherm.o
diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c
index 66e0639da4bf..1efe470f31e9 100644
--- a/drivers/thermal/tegra/soctherm.c
+++ b/drivers/thermal/tegra/soctherm.c
@@ -421,9 +421,9 @@ static int translate_temp(u16 val)
return t;
}
-static int tegra_thermctl_get_temp(void *data, int *out_temp)
+static int tegra_thermctl_get_temp(struct thermal_zone_device *tz, int *out_temp)
{
- struct tegra_thermctl_zone *zone = data;
+ struct tegra_thermctl_zone *zone = tz->devdata;
u32 val;
val = readl(zone->reg);
@@ -450,8 +450,8 @@ static int enforce_temp_range(struct device *dev, int trip_temp)
temp = clamp_val(trip_temp, min_low_temp, max_high_temp);
if (temp != trip_temp)
- dev_info(dev, "soctherm: trip temperature %d forced to %d\n",
- trip_temp, temp);
+ dev_dbg(dev, "soctherm: trip temperature %d forced to %d\n",
+ trip_temp, temp);
return temp;
}
@@ -582,10 +582,9 @@ static int tsensor_group_thermtrip_get(struct tegra_soctherm *ts, int id)
return temp;
}
-static int tegra_thermctl_set_trip_temp(void *data, int trip, int temp)
+static int tegra_thermctl_set_trip_temp(struct thermal_zone_device *tz, int trip, int temp)
{
- struct tegra_thermctl_zone *zone = data;
- struct thermal_zone_device *tz = zone->tz;
+ struct tegra_thermctl_zone *zone = tz->devdata;
struct tegra_soctherm *ts = zone->ts;
const struct tegra_tsensor_group *sg = zone->sg;
struct device *dev = zone->dev;
@@ -633,37 +632,6 @@ static int tegra_thermctl_set_trip_temp(void *data, int trip, int temp)
return 0;
}
-static int tegra_thermctl_get_trend(void *data, int trip,
- enum thermal_trend *trend)
-{
- struct tegra_thermctl_zone *zone = data;
- struct thermal_zone_device *tz = zone->tz;
- int trip_temp, temp, last_temp, ret;
-
- if (!tz)
- return -EINVAL;
-
- ret = tz->ops->get_trip_temp(zone->tz, trip, &trip_temp);
- if (ret)
- return ret;
-
- temp = READ_ONCE(tz->temperature);
- last_temp = READ_ONCE(tz->last_temperature);
-
- if (temp > trip_temp) {
- if (temp >= last_temp)
- *trend = THERMAL_TREND_RAISING;
- else
- *trend = THERMAL_TREND_STABLE;
- } else if (temp < trip_temp) {
- *trend = THERMAL_TREND_DROPPING;
- } else {
- *trend = THERMAL_TREND_STABLE;
- }
-
- return 0;
-}
-
static void thermal_irq_enable(struct tegra_thermctl_zone *zn)
{
u32 r;
@@ -688,9 +656,9 @@ static void thermal_irq_disable(struct tegra_thermctl_zone *zn)
mutex_unlock(&zn->ts->thermctl_lock);
}
-static int tegra_thermctl_set_trips(void *data, int lo, int hi)
+static int tegra_thermctl_set_trips(struct thermal_zone_device *tz, int lo, int hi)
{
- struct tegra_thermctl_zone *zone = data;
+ struct tegra_thermctl_zone *zone = tz->devdata;
u32 r;
thermal_irq_disable(zone);
@@ -713,10 +681,9 @@ static int tegra_thermctl_set_trips(void *data, int lo, int hi)
return 0;
}
-static const struct thermal_zone_of_device_ops tegra_of_thermal_ops = {
+static const struct thermal_zone_device_ops tegra_of_thermal_ops = {
.get_temp = tegra_thermctl_get_temp,
.set_trip_temp = tegra_thermctl_set_trip_temp,
- .get_trend = tegra_thermctl_get_trend,
.set_trips = tegra_thermctl_set_trips,
};
@@ -2118,7 +2085,6 @@ static int tegra_soctherm_probe(struct platform_device *pdev)
struct tegra_soctherm *tegra;
struct thermal_zone_device *z;
struct tsensor_shared_calib shared_calib;
- struct resource *res;
struct tegra_soctherm_soc *soc;
unsigned int i;
int err;
@@ -2140,26 +2106,20 @@ static int tegra_soctherm_probe(struct platform_device *pdev)
tegra->soc = soc;
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
- "soctherm-reg");
- tegra->regs = devm_ioremap_resource(&pdev->dev, res);
+ tegra->regs = devm_platform_ioremap_resource_byname(pdev, "soctherm-reg");
if (IS_ERR(tegra->regs)) {
dev_err(&pdev->dev, "can't get soctherm registers");
return PTR_ERR(tegra->regs);
}
if (!tegra->soc->use_ccroc) {
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
- "car-reg");
- tegra->clk_regs = devm_ioremap_resource(&pdev->dev, res);
+ tegra->clk_regs = devm_platform_ioremap_resource_byname(pdev, "car-reg");
if (IS_ERR(tegra->clk_regs)) {
dev_err(&pdev->dev, "can't get car clk registers");
return PTR_ERR(tegra->clk_regs);
}
} else {
- res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
- "ccroc-reg");
- tegra->ccroc_regs = devm_ioremap_resource(&pdev->dev, res);
+ tegra->ccroc_regs = devm_platform_ioremap_resource_byname(pdev, "ccroc-reg");
if (IS_ERR(tegra->ccroc_regs)) {
dev_err(&pdev->dev, "can't get ccroc registers");
return PTR_ERR(tegra->ccroc_regs);
@@ -2195,7 +2155,7 @@ static int tegra_soctherm_probe(struct platform_device *pdev)
if (err)
return err;
- /* calculate tsensor calibaration data */
+ /* calculate tsensor calibration data */
for (i = 0; i < soc->num_tsensors; ++i) {
err = tegra_calc_tsensor_calib(&soc->tsensors[i],
&shared_calib,
@@ -2233,9 +2193,9 @@ static int tegra_soctherm_probe(struct platform_device *pdev)
zone->sg = soc->ttgs[i];
zone->ts = tegra;
- z = devm_thermal_zone_of_sensor_register(&pdev->dev,
- soc->ttgs[i]->id, zone,
- &tegra_of_thermal_ops);
+ z = devm_thermal_of_zone_register(&pdev->dev,
+ soc->ttgs[i]->id, zone,
+ &tegra_of_thermal_ops);
if (IS_ERR(z)) {
err = PTR_ERR(z);
dev_err(&pdev->dev, "failed to register sensor: %d\n",
diff --git a/drivers/thermal/tegra/tegra-bpmp-thermal.c b/drivers/thermal/tegra/tegra-bpmp-thermal.c
index 94f1da1dcd69..eb84f0b9dc7c 100644
--- a/drivers/thermal/tegra/tegra-bpmp-thermal.c
+++ b/drivers/thermal/tegra/tegra-bpmp-thermal.c
@@ -30,9 +30,9 @@ struct tegra_bpmp_thermal {
struct tegra_bpmp_thermal_zone **zones;
};
-static int tegra_bpmp_thermal_get_temp(void *data, int *out_temp)
+static int __tegra_bpmp_thermal_get_temp(struct tegra_bpmp_thermal_zone *zone,
+ int *out_temp)
{
- struct tegra_bpmp_thermal_zone *zone = data;
struct mrq_thermal_host_to_bpmp_request req;
union mrq_thermal_bpmp_to_host_response reply;
struct tegra_bpmp_message msg;
@@ -52,17 +52,25 @@ static int tegra_bpmp_thermal_get_temp(void *data, int *out_temp)
err = tegra_bpmp_transfer(zone->tegra->bpmp, &msg);
if (err)
return err;
+ if (msg.rx.ret)
+ return -EINVAL;
*out_temp = reply.get_temp.temp;
return 0;
}
-static int tegra_bpmp_thermal_set_trips(void *data, int low, int high)
+static int tegra_bpmp_thermal_get_temp(struct thermal_zone_device *tz, int *out_temp)
{
- struct tegra_bpmp_thermal_zone *zone = data;
+ return __tegra_bpmp_thermal_get_temp(tz->devdata, out_temp);
+}
+
+static int tegra_bpmp_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct tegra_bpmp_thermal_zone *zone = tz->devdata;
struct mrq_thermal_host_to_bpmp_request req;
struct tegra_bpmp_message msg;
+ int err;
memset(&req, 0, sizeof(req));
req.type = CMD_THERMAL_SET_TRIP;
@@ -76,7 +84,13 @@ static int tegra_bpmp_thermal_set_trips(void *data, int low, int high)
msg.tx.data = &req;
msg.tx.size = sizeof(req);
- return tegra_bpmp_transfer(zone->tegra->bpmp, &msg);
+ err = tegra_bpmp_transfer(zone->tegra->bpmp, &msg);
+ if (err)
+ return err;
+ if (msg.rx.ret)
+ return -EINVAL;
+
+ return 0;
}
static void tz_device_update_work_fn(struct work_struct *work)
@@ -140,13 +154,15 @@ static int tegra_bpmp_thermal_get_num_zones(struct tegra_bpmp *bpmp,
err = tegra_bpmp_transfer(bpmp, &msg);
if (err)
return err;
+ if (msg.rx.ret)
+ return -EINVAL;
*num_zones = reply.get_num_zones.num;
return 0;
}
-static const struct thermal_zone_of_device_ops tegra_bpmp_of_thermal_ops = {
+static const struct thermal_zone_device_ops tegra_bpmp_of_thermal_ops = {
.get_temp = tegra_bpmp_thermal_get_temp,
.set_trips = tegra_bpmp_thermal_set_trips,
};
@@ -189,13 +205,13 @@ static int tegra_bpmp_thermal_probe(struct platform_device *pdev)
zone->idx = i;
zone->tegra = tegra;
- err = tegra_bpmp_thermal_get_temp(zone, &temp);
+ err = __tegra_bpmp_thermal_get_temp(zone, &temp);
if (err < 0) {
devm_kfree(&pdev->dev, zone);
continue;
}
- tzd = devm_thermal_zone_of_sensor_register(
+ tzd = devm_thermal_of_zone_register(
&pdev->dev, i, zone, &tegra_bpmp_of_thermal_ops);
if (IS_ERR(tzd)) {
if (PTR_ERR(tzd) == -EPROBE_DEFER)
diff --git a/drivers/thermal/tegra/tegra30-tsensor.c b/drivers/thermal/tegra/tegra30-tsensor.c
new file mode 100644
index 000000000000..c34501287e96
--- /dev/null
+++ b/drivers/thermal/tegra/tegra30-tsensor.c
@@ -0,0 +1,673 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tegra30 SoC Thermal Sensor driver
+ *
+ * Based on downstream HWMON driver from NVIDIA.
+ * Copyright (C) 2011 NVIDIA Corporation
+ *
+ * Author: Dmitry Osipenko <digetx@gmail.com>
+ * Copyright (C) 2021 GRATE-DRIVER project
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "../thermal_core.h"
+#include "../thermal_hwmon.h"
+
+#define TSENSOR_SENSOR0_CONFIG0 0x0
+#define TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP BIT(0)
+#define TSENSOR_SENSOR0_CONFIG0_HW_FREQ_DIV_EN BIT(1)
+#define TSENSOR_SENSOR0_CONFIG0_THERMAL_RST_EN BIT(2)
+#define TSENSOR_SENSOR0_CONFIG0_DVFS_EN BIT(3)
+#define TSENSOR_SENSOR0_CONFIG0_INTR_OVERFLOW_EN BIT(4)
+#define TSENSOR_SENSOR0_CONFIG0_INTR_HW_FREQ_DIV_EN BIT(5)
+#define TSENSOR_SENSOR0_CONFIG0_INTR_THERMAL_RST_EN BIT(6)
+#define TSENSOR_SENSOR0_CONFIG0_M GENMASK(23, 8)
+#define TSENSOR_SENSOR0_CONFIG0_N GENMASK(31, 24)
+
+#define TSENSOR_SENSOR0_CONFIG1 0x8
+#define TSENSOR_SENSOR0_CONFIG1_TH1 GENMASK(15, 0)
+#define TSENSOR_SENSOR0_CONFIG1_TH2 GENMASK(31, 16)
+
+#define TSENSOR_SENSOR0_CONFIG2 0xc
+#define TSENSOR_SENSOR0_CONFIG2_TH3 GENMASK(15, 0)
+
+#define TSENSOR_SENSOR0_STATUS0 0x18
+#define TSENSOR_SENSOR0_STATUS0_STATE GENMASK(2, 0)
+#define TSENSOR_SENSOR0_STATUS0_INTR BIT(8)
+#define TSENSOR_SENSOR0_STATUS0_CURRENT_VALID BIT(9)
+
+#define TSENSOR_SENSOR0_TS_STATUS1 0x1c
+#define TSENSOR_SENSOR0_TS_STATUS1_CURRENT_COUNT GENMASK(31, 16)
+
+#define TEGRA30_FUSE_TEST_PROG_VER 0x28
+
+#define TEGRA30_FUSE_TSENSOR_CALIB 0x98
+#define TEGRA30_FUSE_TSENSOR_CALIB_LOW GENMASK(15, 0)
+#define TEGRA30_FUSE_TSENSOR_CALIB_HIGH GENMASK(31, 16)
+
+#define TEGRA30_FUSE_SPARE_BIT 0x144
+
+struct tegra_tsensor;
+
+struct tegra_tsensor_calibration_data {
+ int a, b, m, n, p, r;
+};
+
+struct tegra_tsensor_channel {
+ void __iomem *regs;
+ unsigned int id;
+ struct tegra_tsensor *ts;
+ struct thermal_zone_device *tzd;
+};
+
+struct tegra_tsensor {
+ void __iomem *regs;
+ bool swap_channels;
+ struct clk *clk;
+ struct device *dev;
+ struct reset_control *rst;
+ struct tegra_tsensor_channel ch[2];
+ struct tegra_tsensor_calibration_data calib;
+};
+
+static int tegra_tsensor_hw_enable(const struct tegra_tsensor *ts)
+{
+ u32 val;
+ int err;
+
+ err = reset_control_assert(ts->rst);
+ if (err) {
+ dev_err(ts->dev, "failed to assert hardware reset: %d\n", err);
+ return err;
+ }
+
+ err = clk_prepare_enable(ts->clk);
+ if (err) {
+ dev_err(ts->dev, "failed to enable clock: %d\n", err);
+ return err;
+ }
+
+ fsleep(1000);
+
+ err = reset_control_deassert(ts->rst);
+ if (err) {
+ dev_err(ts->dev, "failed to deassert hardware reset: %d\n", err);
+ goto disable_clk;
+ }
+
+ /*
+ * Sensors are enabled after reset by default, but not gauging
+ * until clock counter is programmed.
+ *
+ * M: number of reference clock pulses after which every
+ * temperature / voltage measurement is made
+ *
+ * N: number of reference clock counts for which the counter runs
+ */
+ val = FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_M, 12500);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_N, 255);
+
+ /* apply the same configuration to both channels */
+ writel_relaxed(val, ts->regs + 0x40 + TSENSOR_SENSOR0_CONFIG0);
+ writel_relaxed(val, ts->regs + 0x80 + TSENSOR_SENSOR0_CONFIG0);
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(ts->clk);
+
+ return err;
+}
+
+static int tegra_tsensor_hw_disable(const struct tegra_tsensor *ts)
+{
+ int err;
+
+ err = reset_control_assert(ts->rst);
+ if (err) {
+ dev_err(ts->dev, "failed to assert hardware reset: %d\n", err);
+ return err;
+ }
+
+ clk_disable_unprepare(ts->clk);
+
+ return 0;
+}
+
+static void devm_tegra_tsensor_hw_disable(void *data)
+{
+ const struct tegra_tsensor *ts = data;
+
+ tegra_tsensor_hw_disable(ts);
+}
+
+static int tegra_tsensor_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ const struct tegra_tsensor_channel *tsc = tz->devdata;
+ const struct tegra_tsensor *ts = tsc->ts;
+ int err, c1, c2, c3, c4, counter;
+ u32 val;
+
+ /*
+ * Counter will be invalid if hardware is misprogrammed or not enough
+ * time passed since the time when sensor was enabled.
+ */
+ err = readl_relaxed_poll_timeout(tsc->regs + TSENSOR_SENSOR0_STATUS0, val,
+ val & TSENSOR_SENSOR0_STATUS0_CURRENT_VALID,
+ 21 * USEC_PER_MSEC,
+ 21 * USEC_PER_MSEC * 50);
+ if (err) {
+ dev_err_once(ts->dev, "ch%u: counter invalid\n", tsc->id);
+ return err;
+ }
+
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_TS_STATUS1);
+ counter = FIELD_GET(TSENSOR_SENSOR0_TS_STATUS1_CURRENT_COUNT, val);
+
+ /*
+ * This shouldn't happen with a valid counter status, nevertheless
+ * lets verify the value since it's in a separate (from status)
+ * register.
+ */
+ if (counter == 0xffff) {
+ dev_err_once(ts->dev, "ch%u: counter overflow\n", tsc->id);
+ return -EINVAL;
+ }
+
+ /*
+ * temperature = a * counter + b
+ * temperature = m * (temperature ^ 2) + n * temperature + p
+ */
+ c1 = DIV_ROUND_CLOSEST(ts->calib.a * counter + ts->calib.b, 1000000);
+ c1 = c1 ?: 1;
+ c2 = DIV_ROUND_CLOSEST(ts->calib.p, c1);
+ c3 = c1 * ts->calib.m;
+ c4 = ts->calib.n;
+
+ *temp = DIV_ROUND_CLOSEST(c1 * (c2 + c3 + c4), 1000);
+
+ return 0;
+}
+
+static int tegra_tsensor_temp_to_counter(const struct tegra_tsensor *ts, int temp)
+{
+ int c1, c2;
+
+ c1 = DIV_ROUND_CLOSEST(ts->calib.p - temp * 1000, ts->calib.m);
+ c2 = -ts->calib.r - int_sqrt(ts->calib.r * ts->calib.r - c1);
+
+ return DIV_ROUND_CLOSEST(c2 * 1000000 - ts->calib.b, ts->calib.a);
+}
+
+static int tegra_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ const struct tegra_tsensor_channel *tsc = tz->devdata;
+ const struct tegra_tsensor *ts = tsc->ts;
+ u32 val;
+
+ /*
+ * TSENSOR doesn't trigger interrupt on the "low" temperature breach,
+ * hence bail out if high temperature is unspecified.
+ */
+ if (high == INT_MAX)
+ return 0;
+
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG1);
+ val &= ~TSENSOR_SENSOR0_CONFIG1_TH1;
+
+ high = tegra_tsensor_temp_to_counter(ts, high);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG1_TH1, high);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG1);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops ops = {
+ .get_temp = tegra_tsensor_get_temp,
+ .set_trips = tegra_tsensor_set_trips,
+};
+
+static bool
+tegra_tsensor_handle_channel_interrupt(const struct tegra_tsensor *ts,
+ unsigned int id)
+{
+ const struct tegra_tsensor_channel *tsc = &ts->ch[id];
+ u32 val;
+
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_STATUS0);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_STATUS0);
+
+ if (FIELD_GET(TSENSOR_SENSOR0_STATUS0_STATE, val) == 5)
+ dev_err_ratelimited(ts->dev, "ch%u: counter overflowed\n", id);
+
+ if (!FIELD_GET(TSENSOR_SENSOR0_STATUS0_INTR, val))
+ return false;
+
+ thermal_zone_device_update(tsc->tzd, THERMAL_EVENT_UNSPECIFIED);
+
+ return true;
+}
+
+static irqreturn_t tegra_tsensor_isr(int irq, void *data)
+{
+ const struct tegra_tsensor *ts = data;
+ bool handled = false;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++)
+ handled |= tegra_tsensor_handle_channel_interrupt(ts, i);
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int tegra_tsensor_disable_hw_channel(const struct tegra_tsensor *ts,
+ unsigned int id)
+{
+ const struct tegra_tsensor_channel *tsc = &ts->ch[id];
+ struct thermal_zone_device *tzd = tsc->tzd;
+ u32 val;
+ int err;
+
+ if (!tzd)
+ goto stop_channel;
+
+ err = thermal_zone_device_disable(tzd);
+ if (err) {
+ dev_err(ts->dev, "ch%u: failed to disable zone: %d\n", id, err);
+ return err;
+ }
+
+stop_channel:
+ /* stop channel gracefully */
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP, 1);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+
+ return 0;
+}
+
+static void tegra_tsensor_get_hw_channel_trips(struct thermal_zone_device *tzd,
+ int *hot_trip, int *crit_trip)
+{
+ unsigned int i;
+
+ /*
+ * 90C is the maximal critical temperature of all Tegra30 SoC variants,
+ * use it for the default trip if unspecified in a device-tree.
+ */
+ *hot_trip = 85000;
+ *crit_trip = 90000;
+
+ for (i = 0; i < tzd->num_trips; i++) {
+ enum thermal_trip_type type;
+ int trip_temp;
+
+ tzd->ops->get_trip_temp(tzd, i, &trip_temp);
+ tzd->ops->get_trip_type(tzd, i, &type);
+
+ if (type == THERMAL_TRIP_HOT)
+ *hot_trip = trip_temp;
+
+ if (type == THERMAL_TRIP_CRITICAL)
+ *crit_trip = trip_temp;
+ }
+
+ /* clamp hardware trips to the calibration limits */
+ *hot_trip = clamp(*hot_trip, 25000, 90000);
+
+ /*
+ * Kernel will perform a normal system shut down if it will
+ * see that critical temperature is breached, hence set the
+ * hardware limit by 5C higher in order to allow system to
+ * shut down gracefully before sending signal to the Power
+ * Management controller.
+ */
+ *crit_trip = clamp(*crit_trip + 5000, 25000, 90000);
+}
+
+static int tegra_tsensor_enable_hw_channel(const struct tegra_tsensor *ts,
+ unsigned int id)
+{
+ const struct tegra_tsensor_channel *tsc = &ts->ch[id];
+ struct thermal_zone_device *tzd = tsc->tzd;
+ int err, hot_trip = 0, crit_trip = 0;
+ u32 val;
+
+ if (!tzd) {
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+ val &= ~TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP;
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+
+ return 0;
+ }
+
+ tegra_tsensor_get_hw_channel_trips(tzd, &hot_trip, &crit_trip);
+
+ /* prevent potential racing with tegra_tsensor_set_trips() */
+ mutex_lock(&tzd->lock);
+
+ dev_info_once(ts->dev, "ch%u: PMC emergency shutdown trip set to %dC\n",
+ id, DIV_ROUND_CLOSEST(crit_trip, 1000));
+
+ hot_trip = tegra_tsensor_temp_to_counter(ts, hot_trip);
+ crit_trip = tegra_tsensor_temp_to_counter(ts, crit_trip);
+
+ /* program LEVEL2 counter threshold */
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG1);
+ val &= ~TSENSOR_SENSOR0_CONFIG1_TH2;
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG1_TH2, hot_trip);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG1);
+
+ /* program LEVEL3 counter threshold */
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG2);
+ val &= ~TSENSOR_SENSOR0_CONFIG2_TH3;
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG2_TH3, crit_trip);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG2);
+
+ /*
+ * Enable sensor, emergency shutdown, interrupts for level 1/2/3
+ * breaches and counter overflow condition.
+ *
+ * Disable DIV2 throttle for now since we need to figure out how
+ * to integrate it properly with the thermal framework.
+ *
+ * Thermal levels supported by hardware:
+ *
+ * Level 0 = cold
+ * Level 1 = passive cooling (cpufreq DVFS)
+ * Level 2 = passive cooling assisted by hardware (DIV2)
+ * Level 3 = emergency shutdown assisted by hardware (PMC)
+ */
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+ val &= ~TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP;
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_DVFS_EN, 1);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_HW_FREQ_DIV_EN, 0);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_THERMAL_RST_EN, 1);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_OVERFLOW_EN, 1);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_HW_FREQ_DIV_EN, 1);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_THERMAL_RST_EN, 1);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+
+ mutex_unlock(&tzd->lock);
+
+ err = thermal_zone_device_enable(tzd);
+ if (err) {
+ dev_err(ts->dev, "ch%u: failed to enable zone: %d\n", id, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static bool tegra_tsensor_fuse_read_spare(unsigned int spare)
+{
+ u32 val = 0;
+
+ tegra_fuse_readl(TEGRA30_FUSE_SPARE_BIT + spare * 4, &val);
+
+ return !!val;
+}
+
+static int tegra_tsensor_nvmem_setup(struct tegra_tsensor *ts)
+{
+ u32 i, ate_ver = 0, cal = 0, t1_25C = 0, t2_90C = 0;
+ int err, c1_25C, c2_90C;
+
+ err = tegra_fuse_readl(TEGRA30_FUSE_TEST_PROG_VER, &ate_ver);
+ if (err) {
+ dev_err_probe(ts->dev, err, "failed to get ATE version\n");
+ return err;
+ }
+
+ if (ate_ver < 8) {
+ dev_info(ts->dev, "unsupported ATE version: %u\n", ate_ver);
+ return -ENODEV;
+ }
+
+ /*
+ * We have two TSENSOR channels in a two different spots on SoC.
+ * Second channel provides more accurate data on older SoC versions,
+ * use it as a primary channel.
+ */
+ if (ate_ver <= 21) {
+ dev_info_once(ts->dev,
+ "older ATE version detected, channels remapped\n");
+ ts->swap_channels = true;
+ }
+
+ err = tegra_fuse_readl(TEGRA30_FUSE_TSENSOR_CALIB, &cal);
+ if (err) {
+ dev_err(ts->dev, "failed to get calibration data: %d\n", err);
+ return err;
+ }
+
+ /* get calibrated counter values for 25C/90C thresholds */
+ c1_25C = FIELD_GET(TEGRA30_FUSE_TSENSOR_CALIB_LOW, cal);
+ c2_90C = FIELD_GET(TEGRA30_FUSE_TSENSOR_CALIB_HIGH, cal);
+
+ /* and calibrated temperatures corresponding to the counter values */
+ for (i = 0; i < 7; i++) {
+ t1_25C |= tegra_tsensor_fuse_read_spare(14 + i) << i;
+ t1_25C |= tegra_tsensor_fuse_read_spare(21 + i) << i;
+
+ t2_90C |= tegra_tsensor_fuse_read_spare(0 + i) << i;
+ t2_90C |= tegra_tsensor_fuse_read_spare(7 + i) << i;
+ }
+
+ if (c2_90C - c1_25C <= t2_90C - t1_25C) {
+ dev_err(ts->dev, "invalid calibration data: %d %d %u %u\n",
+ c2_90C, c1_25C, t2_90C, t1_25C);
+ return -EINVAL;
+ }
+
+ /* all calibration coefficients are premultiplied by 1000000 */
+
+ ts->calib.a = DIV_ROUND_CLOSEST((t2_90C - t1_25C) * 1000000,
+ (c2_90C - c1_25C));
+
+ ts->calib.b = t1_25C * 1000000 - ts->calib.a * c1_25C;
+
+ if (tegra_sku_info.revision == TEGRA_REVISION_A01) {
+ ts->calib.m = -2775;
+ ts->calib.n = 1338811;
+ ts->calib.p = -7300000;
+ } else {
+ ts->calib.m = -3512;
+ ts->calib.n = 1528943;
+ ts->calib.p = -11100000;
+ }
+
+ /* except the coefficient of a reduced quadratic equation */
+ ts->calib.r = DIV_ROUND_CLOSEST(ts->calib.n, ts->calib.m * 2);
+
+ dev_info_once(ts->dev,
+ "calibration: %d %d %u %u ATE ver: %u SoC rev: %u\n",
+ c2_90C, c1_25C, t2_90C, t1_25C, ate_ver,
+ tegra_sku_info.revision);
+
+ return 0;
+}
+
+static int tegra_tsensor_register_channel(struct tegra_tsensor *ts,
+ unsigned int id)
+{
+ struct tegra_tsensor_channel *tsc = &ts->ch[id];
+ unsigned int hw_id = ts->swap_channels ? !id : id;
+
+ tsc->ts = ts;
+ tsc->id = id;
+ tsc->regs = ts->regs + 0x40 * (hw_id + 1);
+
+ tsc->tzd = devm_thermal_of_zone_register(ts->dev, id, tsc, &ops);
+ if (IS_ERR(tsc->tzd)) {
+ if (PTR_ERR(tsc->tzd) != -ENODEV)
+ return dev_err_probe(ts->dev, PTR_ERR(tsc->tzd),
+ "failed to register thermal zone\n");
+
+ /*
+ * It's okay if sensor isn't assigned to any thermal zone
+ * in a device-tree.
+ */
+ tsc->tzd = NULL;
+ return 0;
+ }
+
+ if (devm_thermal_add_hwmon_sysfs(tsc->tzd))
+ dev_warn(ts->dev, "failed to add hwmon sysfs attributes\n");
+
+ return 0;
+}
+
+static int tegra_tsensor_probe(struct platform_device *pdev)
+{
+ struct tegra_tsensor *ts;
+ unsigned int i;
+ int err, irq;
+
+ ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ts->dev = &pdev->dev;
+ platform_set_drvdata(pdev, ts);
+
+ ts->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ts->regs))
+ return PTR_ERR(ts->regs);
+
+ ts->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ts->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(ts->clk),
+ "failed to get clock\n");
+
+ ts->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(ts->rst))
+ return dev_err_probe(&pdev->dev, PTR_ERR(ts->rst),
+ "failed to get reset control\n");
+
+ err = tegra_tsensor_nvmem_setup(ts);
+ if (err)
+ return err;
+
+ err = tegra_tsensor_hw_enable(ts);
+ if (err)
+ return err;
+
+ err = devm_add_action_or_reset(&pdev->dev,
+ devm_tegra_tsensor_hw_disable,
+ ts);
+ if (err)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
+ err = tegra_tsensor_register_channel(ts, i);
+ if (err)
+ return err;
+ }
+
+ err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ tegra_tsensor_isr, IRQF_ONESHOT,
+ "tegra_tsensor", ts);
+ if (err)
+ return dev_err_probe(&pdev->dev, err,
+ "failed to request interrupt\n");
+
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
+ err = tegra_tsensor_enable_hw_channel(ts, i);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused tegra_tsensor_suspend(struct device *dev)
+{
+ struct tegra_tsensor *ts = dev_get_drvdata(dev);
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
+ err = tegra_tsensor_disable_hw_channel(ts, i);
+ if (err)
+ goto enable_channel;
+ }
+
+ err = tegra_tsensor_hw_disable(ts);
+ if (err)
+ goto enable_channel;
+
+ return 0;
+
+enable_channel:
+ while (i--)
+ tegra_tsensor_enable_hw_channel(ts, i);
+
+ return err;
+}
+
+static int __maybe_unused tegra_tsensor_resume(struct device *dev)
+{
+ struct tegra_tsensor *ts = dev_get_drvdata(dev);
+ unsigned int i;
+ int err;
+
+ err = tegra_tsensor_hw_enable(ts);
+ if (err)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
+ err = tegra_tsensor_enable_hw_channel(ts, i);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra_tsensor_pm_ops = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_tsensor_suspend,
+ tegra_tsensor_resume)
+};
+
+static const struct of_device_id tegra_tsensor_of_match[] = {
+ { .compatible = "nvidia,tegra30-tsensor", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra_tsensor_of_match);
+
+static struct platform_driver tegra_tsensor_driver = {
+ .probe = tegra_tsensor_probe,
+ .driver = {
+ .name = "tegra30-tsensor",
+ .of_match_table = tegra_tsensor_of_match,
+ .pm = &tegra_tsensor_pm_ops,
+ },
+};
+module_platform_driver(tegra_tsensor_driver);
+
+MODULE_DESCRIPTION("NVIDIA Tegra30 Thermal Sensor driver");
+MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/thermal-generic-adc.c b/drivers/thermal/thermal-generic-adc.c
index 73665c3ccfe0..323e273e3298 100644
--- a/drivers/thermal/thermal-generic-adc.c
+++ b/drivers/thermal/thermal-generic-adc.c
@@ -52,9 +52,9 @@ static int gadc_thermal_adc_to_temp(struct gadc_thermal_info *gti, int val)
return temp;
}
-static int gadc_thermal_get_temp(void *data, int *temp)
+static int gadc_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct gadc_thermal_info *gti = data;
+ struct gadc_thermal_info *gti = tz->devdata;
int val;
int ret;
@@ -68,7 +68,7 @@ static int gadc_thermal_get_temp(void *data, int *temp)
return 0;
}
-static const struct thermal_zone_of_device_ops gadc_thermal_ops = {
+static const struct thermal_zone_device_ops gadc_thermal_ops = {
.get_temp = gadc_thermal_get_temp,
};
@@ -143,8 +143,8 @@ static int gadc_thermal_probe(struct platform_device *pdev)
gti->dev = &pdev->dev;
platform_set_drvdata(pdev, gti);
- gti->tz_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, gti,
- &gadc_thermal_ops);
+ gti->tz_dev = devm_thermal_of_zone_register(&pdev->dev, 0, gti,
+ &gadc_thermal_ops);
if (IS_ERR(gti->tz_dev)) {
ret = PTR_ERR(gti->tz_dev);
if (ret != -EPROBE_DEFER)
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
index 9a321dc548c8..117eeaf7dd24 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -9,9 +9,9 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/module.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/export.h>
#include <linux/slab.h>
#include <linux/kdev_t.h>
#include <linux/idr.h>
@@ -27,10 +27,6 @@
#include "thermal_core.h"
#include "thermal_hwmon.h"
-MODULE_AUTHOR("Zhang Rui");
-MODULE_DESCRIPTION("Generic thermal management sysfs support");
-MODULE_LICENSE("GPL v2");
-
static DEFINE_IDA(thermal_tz_ida);
static DEFINE_IDA(thermal_cdev_ida);
@@ -40,10 +36,8 @@ static LIST_HEAD(thermal_governor_list);
static DEFINE_MUTEX(thermal_list_lock);
static DEFINE_MUTEX(thermal_governor_lock);
-static DEFINE_MUTEX(poweroff_lock);
static atomic_t in_suspend;
-static bool power_off_triggered;
static struct thermal_governor *def_governor;
@@ -219,6 +213,8 @@ exit:
mutex_unlock(&tz->lock);
mutex_unlock(&thermal_governor_lock);
+ thermal_notify_tz_gov_change(tz->id, policy);
+
return ret;
}
@@ -226,15 +222,14 @@ int thermal_build_list_of_policies(char *buf)
{
struct thermal_governor *pos;
ssize_t count = 0;
- ssize_t size = PAGE_SIZE;
mutex_lock(&thermal_governor_lock);
list_for_each_entry(pos, &thermal_governor_list, governor_list) {
- size = PAGE_SIZE - count;
- count += scnprintf(buf + count, size, "%s ", pos->name);
+ count += scnprintf(buf + count, PAGE_SIZE - count, "%s ",
+ pos->name);
}
- count += scnprintf(buf + count, size, "\n");
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
mutex_unlock(&thermal_governor_lock);
@@ -291,32 +286,23 @@ static int __init thermal_register_governors(void)
* - Critical trip point will cause a system shutdown.
*/
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
- int delay)
+ unsigned long delay)
{
- if (delay > 1000)
- mod_delayed_work(system_freezable_power_efficient_wq,
- &tz->poll_queue,
- round_jiffies(msecs_to_jiffies(delay)));
- else if (delay)
+ if (delay)
mod_delayed_work(system_freezable_power_efficient_wq,
- &tz->poll_queue,
- msecs_to_jiffies(delay));
+ &tz->poll_queue, delay);
else
cancel_delayed_work(&tz->poll_queue);
}
static void monitor_thermal_zone(struct thermal_zone_device *tz)
{
- mutex_lock(&tz->lock);
-
- if (tz->passive)
- thermal_zone_device_set_polling(tz, tz->passive_delay);
- else if (tz->polling_delay)
- thermal_zone_device_set_polling(tz, tz->polling_delay);
- else
+ if (tz->mode != THERMAL_DEVICE_ENABLED)
thermal_zone_device_set_polling(tz, 0);
-
- mutex_unlock(&tz->lock);
+ else if (tz->passive)
+ thermal_zone_device_set_polling(tz, tz->passive_delay_jiffies);
+ else if (tz->polling_delay_jiffies)
+ thermal_zone_device_set_polling(tz, tz->polling_delay_jiffies);
}
static void handle_non_critical_trips(struct thermal_zone_device *tz, int trip)
@@ -325,114 +311,72 @@ static void handle_non_critical_trips(struct thermal_zone_device *tz, int trip)
def_governor->throttle(tz, trip);
}
-/**
- * thermal_emergency_poweroff_func - emergency poweroff work after a known delay
- * @work: work_struct associated with the emergency poweroff function
- *
- * This function is called in very critical situations to force
- * a kernel poweroff after a configurable timeout value.
- */
-static void thermal_emergency_poweroff_func(struct work_struct *work)
+void thermal_zone_device_critical(struct thermal_zone_device *tz)
{
/*
- * We have reached here after the emergency thermal shutdown
- * Waiting period has expired. This means orderly_poweroff has
- * not been able to shut off the system for some reason.
- * Try to shut down the system immediately using kernel_power_off
- * if populated
- */
- WARN(1, "Attempting kernel_power_off: Temperature too high\n");
- kernel_power_off();
-
- /*
- * Worst of the worst case trigger emergency restart
+ * poweroff_delay_ms must be a carefully profiled positive value.
+ * Its a must for forced_emergency_poweroff_work to be scheduled.
*/
- WARN(1, "Attempting emergency_restart: Temperature too high\n");
- emergency_restart();
-}
+ int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS;
-static DECLARE_DELAYED_WORK(thermal_emergency_poweroff_work,
- thermal_emergency_poweroff_func);
+ dev_emerg(&tz->device, "%s: critical temperature reached, "
+ "shutting down\n", tz->type);
-/**
- * thermal_emergency_poweroff - Trigger an emergency system poweroff
- *
- * This may be called from any critical situation to trigger a system shutdown
- * after a known period of time. By default this is not scheduled.
- */
-static void thermal_emergency_poweroff(void)
-{
- int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS;
- /*
- * poweroff_delay_ms must be a carefully profiled positive value.
- * Its a must for thermal_emergency_poweroff_work to be scheduled
- */
- if (poweroff_delay_ms <= 0)
- return;
- schedule_delayed_work(&thermal_emergency_poweroff_work,
- msecs_to_jiffies(poweroff_delay_ms));
+ hw_protection_shutdown("Temperature too high", poweroff_delay_ms);
}
+EXPORT_SYMBOL(thermal_zone_device_critical);
static void handle_critical_trips(struct thermal_zone_device *tz,
- int trip, enum thermal_trip_type trip_type)
+ int trip, int trip_temp, enum thermal_trip_type trip_type)
{
- int trip_temp;
-
- tz->ops->get_trip_temp(tz, trip, &trip_temp);
-
/* If we have not crossed the trip_temp, we do not care. */
if (trip_temp <= 0 || tz->temperature < trip_temp)
return;
trace_thermal_zone_trip(tz, trip, trip_type);
- if (tz->ops->notify)
- tz->ops->notify(tz, trip, trip_type);
-
- if (trip_type == THERMAL_TRIP_CRITICAL) {
- dev_emerg(&tz->device,
- "critical temperature reached (%d C), shutting down\n",
- tz->temperature / 1000);
- mutex_lock(&poweroff_lock);
- if (!power_off_triggered) {
- /*
- * Queue a backup emergency shutdown in the event of
- * orderly_poweroff failure
- */
- thermal_emergency_poweroff();
- orderly_poweroff(true);
- power_off_triggered = true;
- }
- mutex_unlock(&poweroff_lock);
- }
+ if (trip_type == THERMAL_TRIP_HOT && tz->ops->hot)
+ tz->ops->hot(tz);
+ else if (trip_type == THERMAL_TRIP_CRITICAL)
+ tz->ops->critical(tz);
}
static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
{
enum thermal_trip_type type;
+ int trip_temp, hyst = 0;
/* Ignore disabled trip points */
if (test_bit(trip, &tz->trips_disabled))
return;
+ tz->ops->get_trip_temp(tz, trip, &trip_temp);
tz->ops->get_trip_type(tz, trip, &type);
+ if (tz->ops->get_trip_hyst)
+ tz->ops->get_trip_hyst(tz, trip, &hyst);
+
+ if (tz->last_temperature != THERMAL_TEMP_INVALID) {
+ if (tz->last_temperature < trip_temp &&
+ tz->temperature >= trip_temp)
+ thermal_notify_tz_trip_up(tz->id, trip,
+ tz->temperature);
+ if (tz->last_temperature >= trip_temp &&
+ tz->temperature < (trip_temp - hyst))
+ thermal_notify_tz_trip_down(tz->id, trip,
+ tz->temperature);
+ }
if (type == THERMAL_TRIP_CRITICAL || type == THERMAL_TRIP_HOT)
- handle_critical_trips(tz, trip, type);
+ handle_critical_trips(tz, trip, trip_temp, type);
else
handle_non_critical_trips(tz, trip);
- /*
- * Alright, we handled this trip successfully.
- * So, start monitoring again.
- */
- monitor_thermal_zone(tz);
}
static void update_temperature(struct thermal_zone_device *tz)
{
int temp, ret;
- ret = thermal_zone_get_temp(tz, &temp);
+ ret = __thermal_zone_get_temp(tz, &temp);
if (ret) {
if (ret != -EAGAIN)
dev_warn(&tz->device,
@@ -441,32 +385,73 @@ static void update_temperature(struct thermal_zone_device *tz)
return;
}
- mutex_lock(&tz->lock);
tz->last_temperature = tz->temperature;
tz->temperature = temp;
- mutex_unlock(&tz->lock);
trace_thermal_temperature(tz);
- if (tz->last_temperature == THERMAL_TEMP_INVALID)
- dev_dbg(&tz->device, "last_temperature N/A, current_temperature=%d\n",
- tz->temperature);
- else
- dev_dbg(&tz->device, "last_temperature=%d, current_temperature=%d\n",
- tz->last_temperature, tz->temperature);
+
+ thermal_genl_sampling_temp(tz->id, temp);
}
static void thermal_zone_device_init(struct thermal_zone_device *tz)
{
struct thermal_instance *pos;
tz->temperature = THERMAL_TEMP_INVALID;
+ tz->prev_low_trip = -INT_MAX;
+ tz->prev_high_trip = INT_MAX;
list_for_each_entry(pos, &tz->thermal_instances, tz_node)
pos->initialized = false;
}
-static void thermal_zone_device_reset(struct thermal_zone_device *tz)
+static int thermal_zone_device_set_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode mode)
{
- tz->passive = 0;
- thermal_zone_device_init(tz);
+ int ret = 0;
+
+ mutex_lock(&tz->lock);
+
+ /* do nothing if mode isn't changing */
+ if (mode == tz->mode) {
+ mutex_unlock(&tz->lock);
+
+ return ret;
+ }
+
+ if (tz->ops->change_mode)
+ ret = tz->ops->change_mode(tz, mode);
+
+ if (!ret)
+ tz->mode = mode;
+
+ mutex_unlock(&tz->lock);
+
+ thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+
+ if (mode == THERMAL_DEVICE_ENABLED)
+ thermal_notify_tz_enable(tz->id);
+ else
+ thermal_notify_tz_disable(tz->id);
+
+ return ret;
+}
+
+int thermal_zone_device_enable(struct thermal_zone_device *tz)
+{
+ return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_ENABLED);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_enable);
+
+int thermal_zone_device_disable(struct thermal_zone_device *tz)
+{
+ return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_DISABLED);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_disable);
+
+int thermal_zone_device_is_enabled(struct thermal_zone_device *tz)
+{
+ lockdep_assert_held(&tz->lock);
+
+ return tz->mode == THERMAL_DEVICE_ENABLED;
}
void thermal_zone_device_update(struct thermal_zone_device *tz,
@@ -477,37 +462,29 @@ void thermal_zone_device_update(struct thermal_zone_device *tz,
if (atomic_read(&in_suspend))
return;
- if (!tz->ops->get_temp)
+ if (WARN_ONCE(!tz->ops->get_temp, "'%s' must not be called without "
+ "'get_temp' ops set\n", __func__))
return;
+ mutex_lock(&tz->lock);
+
+ if (!thermal_zone_device_is_enabled(tz))
+ goto out;
+
update_temperature(tz);
- thermal_zone_set_trips(tz);
+ __thermal_zone_set_trips(tz);
tz->notify_event = event;
- for (count = 0; count < tz->trips; count++)
+ for (count = 0; count < tz->num_trips; count++)
handle_thermal_trip(tz, count);
-}
-EXPORT_SYMBOL_GPL(thermal_zone_device_update);
-/**
- * thermal_notify_framework - Sensor drivers use this API to notify framework
- * @tz: thermal zone device
- * @trip: indicates which trip point has been crossed
- *
- * This function handles the trip events from sensor drivers. It starts
- * throttling the cooling devices according to the policy configured.
- * For CRITICAL and HOT trip points, this notifies the respective drivers,
- * and does actual throttling for other trip points i.e ACTIVE and PASSIVE.
- * The throttling policy is based on the configured platform data; if no
- * platform data is provided, this uses the step_wise throttling policy.
- */
-void thermal_notify_framework(struct thermal_zone_device *tz, int trip)
-{
- handle_thermal_trip(tz, trip);
+ monitor_thermal_zone(tz);
+out:
+ mutex_unlock(&tz->lock);
}
-EXPORT_SYMBOL_GPL(thermal_notify_framework);
+EXPORT_SYMBOL_GPL(thermal_zone_device_update);
static void thermal_zone_device_check(struct work_struct *work)
{
@@ -517,131 +494,71 @@ static void thermal_zone_device_check(struct work_struct *work)
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
}
-/*
- * Power actor section: interface to power actors to estimate power
- *
- * Set of functions used to interact to cooling devices that know
- * how to estimate their devices power consumption.
- */
-
-/**
- * power_actor_get_max_power() - get the maximum power that a cdev can consume
- * @cdev: pointer to &thermal_cooling_device
- * @tz: a valid thermal zone device pointer
- * @max_power: pointer in which to store the maximum power
- *
- * Calculate the maximum power consumption in milliwats that the
- * cooling device can currently consume and store it in @max_power.
- *
- * Return: 0 on success, -EINVAL if @cdev doesn't support the
- * power_actor API or -E* on other error.
- */
-int power_actor_get_max_power(struct thermal_cooling_device *cdev,
- struct thermal_zone_device *tz, u32 *max_power)
-{
- if (!cdev_is_power_actor(cdev))
- return -EINVAL;
-
- return cdev->ops->state2power(cdev, tz, 0, max_power);
-}
-
-/**
- * power_actor_get_min_power() - get the mainimum power that a cdev can consume
- * @cdev: pointer to &thermal_cooling_device
- * @tz: a valid thermal zone device pointer
- * @min_power: pointer in which to store the minimum power
- *
- * Calculate the minimum power consumption in milliwatts that the
- * cooling device can currently consume and store it in @min_power.
- *
- * Return: 0 on success, -EINVAL if @cdev doesn't support the
- * power_actor API or -E* on other error.
- */
-int power_actor_get_min_power(struct thermal_cooling_device *cdev,
- struct thermal_zone_device *tz, u32 *min_power)
+int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *),
+ void *data)
{
- unsigned long max_state;
- int ret;
-
- if (!cdev_is_power_actor(cdev))
- return -EINVAL;
+ struct thermal_governor *gov;
+ int ret = 0;
- ret = cdev->ops->get_max_state(cdev, &max_state);
- if (ret)
- return ret;
+ mutex_lock(&thermal_governor_lock);
+ list_for_each_entry(gov, &thermal_governor_list, governor_list) {
+ ret = cb(gov, data);
+ if (ret)
+ break;
+ }
+ mutex_unlock(&thermal_governor_lock);
- return cdev->ops->state2power(cdev, tz, max_state, min_power);
+ return ret;
}
-/**
- * power_actor_set_power() - limit the maximum power a cooling device consumes
- * @cdev: pointer to &thermal_cooling_device
- * @instance: thermal instance to update
- * @power: the power in milliwatts
- *
- * Set the cooling device to consume at most @power milliwatts. The limit is
- * expected to be a cap at the maximum power consumption.
- *
- * Return: 0 on success, -EINVAL if the cooling device does not
- * implement the power actor API or -E* for other failures.
- */
-int power_actor_set_power(struct thermal_cooling_device *cdev,
- struct thermal_instance *instance, u32 power)
+int for_each_thermal_cooling_device(int (*cb)(struct thermal_cooling_device *,
+ void *), void *data)
{
- unsigned long state;
- int ret;
-
- if (!cdev_is_power_actor(cdev))
- return -EINVAL;
-
- ret = cdev->ops->power2state(cdev, instance->tz, power, &state);
- if (ret)
- return ret;
+ struct thermal_cooling_device *cdev;
+ int ret = 0;
- instance->target = state;
- mutex_lock(&cdev->lock);
- cdev->updated = false;
- mutex_unlock(&cdev->lock);
- thermal_cdev_update(cdev);
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(cdev, &thermal_cdev_list, node) {
+ ret = cb(cdev, data);
+ if (ret)
+ break;
+ }
+ mutex_unlock(&thermal_list_lock);
- return 0;
+ return ret;
}
-void thermal_zone_device_rebind_exception(struct thermal_zone_device *tz,
- const char *cdev_type, size_t size)
+int for_each_thermal_zone(int (*cb)(struct thermal_zone_device *, void *),
+ void *data)
{
- struct thermal_cooling_device *cdev = NULL;
+ struct thermal_zone_device *tz;
+ int ret = 0;
mutex_lock(&thermal_list_lock);
- list_for_each_entry(cdev, &thermal_cdev_list, node) {
- /* skip non matching cdevs */
- if (strncmp(cdev_type, cdev->type, size))
- continue;
-
- /* re binding the exception matching the type pattern */
- thermal_zone_bind_cooling_device(tz, THERMAL_TRIPS_NONE, cdev,
- THERMAL_NO_LIMIT,
- THERMAL_NO_LIMIT,
- THERMAL_WEIGHT_DEFAULT);
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ ret = cb(tz, data);
+ if (ret)
+ break;
}
mutex_unlock(&thermal_list_lock);
+
+ return ret;
}
-void thermal_zone_device_unbind_exception(struct thermal_zone_device *tz,
- const char *cdev_type, size_t size)
+struct thermal_zone_device *thermal_zone_get_by_id(int id)
{
- struct thermal_cooling_device *cdev = NULL;
+ struct thermal_zone_device *tz, *match = NULL;
mutex_lock(&thermal_list_lock);
- list_for_each_entry(cdev, &thermal_cdev_list, node) {
- /* skip non matching cdevs */
- if (strncmp(cdev_type, cdev->type, size))
- continue;
- /* unbinding the exception matching the type pattern */
- thermal_zone_unbind_cooling_device(tz, THERMAL_TRIPS_NONE,
- cdev);
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ if (tz->id == id) {
+ match = tz;
+ break;
+ }
}
mutex_unlock(&thermal_list_lock);
+
+ return match;
}
/*
@@ -689,7 +606,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
unsigned long max_state;
int result, ret;
- if (trip >= tz->trips || (trip < 0 && trip != THERMAL_TRIPS_NONE))
+ if (trip >= tz->num_trips || trip < 0)
return -EINVAL;
list_for_each_entry(pos1, &thermal_tz_list, node) {
@@ -726,7 +643,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
dev->target = THERMAL_NO_TARGET;
dev->weight = weight;
- result = ida_simple_get(&tz->ida, 0, 0, GFP_KERNEL);
+ result = ida_alloc(&tz->ida, GFP_KERNEL);
if (result < 0)
goto free_mem;
@@ -780,7 +697,7 @@ remove_trip_file:
remove_symbol_link:
sysfs_remove_link(&tz->device.kobj, dev->name);
release_ida:
- ida_simple_remove(&tz->ida, dev->id);
+ ida_free(&tz->ida, dev->id);
free_mem:
kfree(dev);
return result;
@@ -827,7 +744,7 @@ unbind:
device_remove_file(&tz->device, &pos->weight_attr);
device_remove_file(&tz->device, &pos->attr);
sysfs_remove_link(&tz->device.kobj, pos->name);
- ida_simple_remove(&tz->ida, pos->id);
+ ida_free(&tz->ida, pos->id);
kfree(pos);
return 0;
}
@@ -870,7 +787,7 @@ static void __bind(struct thermal_zone_device *tz, int mask,
{
int i, ret;
- for (i = 0; i < tz->trips; i++) {
+ for (i = 0; i < tz->num_trips; i++) {
if (mask & (1 << i)) {
unsigned long upper, lower;
@@ -950,10 +867,7 @@ __thermal_cooling_device_register(struct device_node *np,
{
struct thermal_cooling_device *cdev;
struct thermal_zone_device *pos = NULL;
- int result;
-
- if (type && strlen(type) >= THERMAL_NAME_LENGTH)
- return ERR_PTR(-EINVAL);
+ int id, ret;
if (!ops || !ops->get_max_state || !ops->get_cur_state ||
!ops->set_cur_state)
@@ -963,14 +877,22 @@ __thermal_cooling_device_register(struct device_node *np,
if (!cdev)
return ERR_PTR(-ENOMEM);
- result = ida_simple_get(&thermal_cdev_ida, 0, 0, GFP_KERNEL);
- if (result < 0) {
- kfree(cdev);
- return ERR_PTR(result);
+ ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL);
+ if (ret < 0)
+ goto out_kfree_cdev;
+ cdev->id = ret;
+ id = ret;
+
+ ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
+ if (ret)
+ goto out_ida_remove;
+
+ cdev->type = kstrdup(type ? type : "", GFP_KERNEL);
+ if (!cdev->type) {
+ ret = -ENOMEM;
+ goto out_ida_remove;
}
- cdev->id = result;
- strlcpy(cdev->type, type ? : "", sizeof(cdev->type));
mutex_init(&cdev->lock);
INIT_LIST_HEAD(&cdev->thermal_instances);
cdev->np = np;
@@ -979,13 +901,9 @@ __thermal_cooling_device_register(struct device_node *np,
cdev->device.class = &thermal_class;
cdev->devdata = devdata;
thermal_cooling_device_setup_sysfs(cdev);
- dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
- result = device_register(&cdev->device);
- if (result) {
- ida_simple_remove(&thermal_cdev_ida, cdev->id);
- put_device(&cdev->device);
- return ERR_PTR(result);
- }
+ ret = device_register(&cdev->device);
+ if (ret)
+ goto out_kfree_type;
/* Add 'this' new cdev to the global cdev list */
mutex_lock(&thermal_list_lock);
@@ -1003,6 +921,17 @@ __thermal_cooling_device_register(struct device_node *np,
mutex_unlock(&thermal_list_lock);
return cdev;
+
+out_kfree_type:
+ thermal_cooling_device_destroy_sysfs(cdev);
+ kfree(cdev->type);
+ put_device(&cdev->device);
+ cdev = NULL;
+out_ida_remove:
+ ida_free(&thermal_cdev_ida, id);
+out_kfree_cdev:
+ kfree(cdev);
+ return ERR_PTR(ret);
}
/**
@@ -1104,7 +1033,7 @@ static void __unbind(struct thermal_zone_device *tz, int mask,
{
int i;
- for (i = 0; i < tz->trips; i++)
+ for (i = 0; i < tz->num_trips; i++)
if (mask & (1 << i))
thermal_zone_unbind_cooling_device(tz, i, cdev);
}
@@ -1158,9 +1087,10 @@ void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
mutex_unlock(&thermal_list_lock);
- ida_simple_remove(&thermal_cdev_ida, cdev->id);
+ ida_free(&thermal_cdev_ida, cdev->id);
device_del(&cdev->device);
thermal_cooling_device_destroy_sysfs(cdev);
+ kfree(cdev->type);
put_device(&cdev->device);
}
EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
@@ -1205,10 +1135,18 @@ exit:
mutex_unlock(&thermal_list_lock);
}
+static void thermal_set_delay_jiffies(unsigned long *delay_jiffies, int delay_ms)
+{
+ *delay_jiffies = msecs_to_jiffies(delay_ms);
+ if (delay_ms > 1000)
+ *delay_jiffies = round_jiffies(*delay_jiffies);
+}
+
/**
- * thermal_zone_device_register() - register a new thermal zone device
+ * thermal_zone_device_register_with_trips() - register a new thermal zone device
* @type: the thermal zone device type
- * @trips: the number of trip points the thermal zone support
+ * @trips: a pointer to an array of thermal trips
+ * @num_trips: the number of trip points the thermal zone support
* @mask: a bit string indicating the writeablility of trip points
* @devdata: private device data
* @ops: standard thermal zone device callbacks
@@ -1230,10 +1168,10 @@ exit:
* IS_ERR*() helpers.
*/
struct thermal_zone_device *
-thermal_zone_device_register(const char *type, int trips, int mask,
- void *devdata, struct thermal_zone_device_ops *ops,
- struct thermal_zone_params *tzp, int passive_delay,
- int polling_delay)
+thermal_zone_device_register_with_trips(const char *type, struct thermal_trip *trips, int num_trips, int mask,
+ void *devdata, struct thermal_zone_device_ops *ops,
+ struct thermal_zone_params *tzp, int passive_delay,
+ int polling_delay)
{
struct thermal_zone_device *tz;
enum thermal_trip_type trip_type;
@@ -1244,27 +1182,40 @@ thermal_zone_device_register(const char *type, int trips, int mask,
struct thermal_governor *governor;
if (!type || strlen(type) == 0) {
- pr_err("Error: No thermal zone type defined\n");
+ pr_err("No thermal zone type defined\n");
return ERR_PTR(-EINVAL);
}
- if (type && strlen(type) >= THERMAL_NAME_LENGTH) {
- pr_err("Error: Thermal zone name (%s) too long, should be under %d chars\n",
+ if (strlen(type) >= THERMAL_NAME_LENGTH) {
+ pr_err("Thermal zone name (%s) too long, should be under %d chars\n",
type, THERMAL_NAME_LENGTH);
return ERR_PTR(-EINVAL);
}
- if (trips > THERMAL_MAX_TRIPS || trips < 0 || mask >> trips) {
- pr_err("Error: Incorrect number of thermal trips\n");
+ /*
+ * Max trip count can't exceed 31 as the "mask >> num_trips" condition.
+ * For example, shifting by 32 will result in compiler warning:
+ * warning: right shift count >= width of type [-Wshift-count- overflow]
+ *
+ * Also "mask >> num_trips" will always be true with 32 bit shift.
+ * E.g. mask = 0x80000000 for trip id 31 to be RW. Then
+ * mask >> 32 = 0x80000000
+ * This will result in failure for the below condition.
+ *
+ * Check will be true when the bit 31 of the mask is set.
+ * 32 bit shift will cause overflow of 4 byte integer.
+ */
+ if (num_trips > (BITS_PER_TYPE(int) - 1) || num_trips < 0 || mask >> num_trips) {
+ pr_err("Incorrect number of thermal trips\n");
return ERR_PTR(-EINVAL);
}
if (!ops) {
- pr_err("Error: Thermal zone device ops not defined\n");
+ pr_err("Thermal zone device ops not defined\n");
return ERR_PTR(-EINVAL);
}
- if (trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp))
+ if (num_trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp))
return ERR_PTR(-EINVAL);
tz = kzalloc(sizeof(*tz), GFP_KERNEL);
@@ -1274,21 +1225,31 @@ thermal_zone_device_register(const char *type, int trips, int mask,
INIT_LIST_HEAD(&tz->thermal_instances);
ida_init(&tz->ida);
mutex_init(&tz->lock);
- id = ida_simple_get(&thermal_tz_ida, 0, 0, GFP_KERNEL);
+ id = ida_alloc(&thermal_tz_ida, GFP_KERNEL);
if (id < 0) {
result = id;
goto free_tz;
}
tz->id = id;
- strlcpy(tz->type, type, sizeof(tz->type));
+ strscpy(tz->type, type, sizeof(tz->type));
+
+ result = dev_set_name(&tz->device, "thermal_zone%d", tz->id);
+ if (result)
+ goto remove_id;
+
+ if (!ops->critical)
+ ops->critical = thermal_zone_device_critical;
+
tz->ops = ops;
tz->tzp = tzp;
tz->device.class = &thermal_class;
tz->devdata = devdata;
tz->trips = trips;
- tz->passive_delay = passive_delay;
- tz->polling_delay = polling_delay;
+ tz->num_trips = num_trips;
+
+ thermal_set_delay_jiffies(&tz->passive_delay_jiffies, passive_delay);
+ thermal_set_delay_jiffies(&tz->polling_delay_jiffies, polling_delay);
/* sys I/F */
/* Add nodes that are always present via .groups */
@@ -1299,18 +1260,14 @@ thermal_zone_device_register(const char *type, int trips, int mask,
/* A new thermal zone needs to be updated anyway. */
atomic_set(&tz->need_update, 1);
- dev_set_name(&tz->device, "thermal_zone%d", tz->id);
result = device_register(&tz->device);
if (result)
goto release_device;
- for (count = 0; count < trips; count++) {
- if (tz->ops->get_trip_type(tz, count, &trip_type))
- set_bit(count, &tz->trips_disabled);
- if (tz->ops->get_trip_temp(tz, count, &trip_temp))
- set_bit(count, &tz->trips_disabled);
- /* Check for bogus trip points */
- if (trip_temp == 0)
+ for (count = 0; count < num_trips; count++) {
+ if (tz->ops->get_trip_type(tz, count, &trip_type) ||
+ tz->ops->get_trip_temp(tz, count, &trip_temp) ||
+ !trip_temp)
set_bit(count, &tz->trips_disabled);
}
@@ -1345,11 +1302,13 @@ thermal_zone_device_register(const char *type, int trips, int mask,
INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
- thermal_zone_device_reset(tz);
+ thermal_zone_device_init(tz);
/* Update the new thermal zone and mark it as already updated. */
if (atomic_cmpxchg(&tz->need_update, 1, 0))
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+ thermal_notify_tz_create(tz->id, tz->type);
+
return tz;
unregister:
@@ -1358,20 +1317,31 @@ release_device:
put_device(&tz->device);
tz = NULL;
remove_id:
- ida_simple_remove(&thermal_tz_ida, id);
+ ida_free(&thermal_tz_ida, id);
free_tz:
kfree(tz);
return ERR_PTR(result);
}
+EXPORT_SYMBOL_GPL(thermal_zone_device_register_with_trips);
+
+struct thermal_zone_device *thermal_zone_device_register(const char *type, int ntrips, int mask,
+ void *devdata, struct thermal_zone_device_ops *ops,
+ struct thermal_zone_params *tzp, int passive_delay,
+ int polling_delay)
+{
+ return thermal_zone_device_register_with_trips(type, NULL, ntrips, mask,
+ devdata, ops, tzp,
+ passive_delay, polling_delay);
+}
EXPORT_SYMBOL_GPL(thermal_zone_device_register);
/**
- * thermal_device_unregister - removes the registered thermal zone device
+ * thermal_zone_device_unregister - removes the registered thermal zone device
* @tz: the thermal zone device to remove
*/
void thermal_zone_device_unregister(struct thermal_zone_device *tz)
{
- int i;
+ int i, tz_id;
const struct thermal_zone_params *tzp;
struct thermal_cooling_device *cdev;
struct thermal_zone_device *pos = NULL;
@@ -1380,6 +1350,7 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
return;
tzp = tz->tzp;
+ tz_id = tz->id;
mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node)
@@ -1417,10 +1388,12 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
thermal_set_governor(tz, NULL);
thermal_remove_hwmon_sysfs(tz);
- ida_simple_remove(&thermal_tz_ida, tz->id);
+ ida_free(&thermal_tz_ida, tz->id);
ida_destroy(&tz->ida);
mutex_destroy(&tz->lock);
device_unregister(&tz->device);
+
+ thermal_notify_tz_delete(tz_id);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
@@ -1466,7 +1439,6 @@ static int thermal_pm_notify(struct notifier_block *nb,
unsigned long mode, void *_unused)
{
struct thermal_zone_device *tz;
- enum thermal_device_mode tz_mode;
switch (mode) {
case PM_HIBERNATION_PREPARE:
@@ -1479,13 +1451,6 @@ static int thermal_pm_notify(struct notifier_block *nb,
case PM_POST_SUSPEND:
atomic_set(&in_suspend, 0);
list_for_each_entry(tz, &thermal_tz_list, node) {
- tz_mode = THERMAL_DEVICE_ENABLED;
- if (tz->ops->get_mode)
- tz->ops->get_mode(tz, &tz_mode);
-
- if (tz_mode == THERMAL_DEVICE_DISABLED)
- continue;
-
thermal_zone_device_init(tz);
thermal_zone_device_update(tz,
THERMAL_EVENT_UNSPECIFIED);
@@ -1505,7 +1470,10 @@ static int __init thermal_init(void)
{
int result;
- mutex_init(&poweroff_lock);
+ result = thermal_netlink_init();
+ if (result)
+ goto error;
+
result = thermal_register_governors();
if (result)
goto error;
@@ -1514,10 +1482,6 @@ static int __init thermal_init(void)
if (result)
goto unregister_governors;
- result = of_parse_thermal_zones();
- if (result)
- goto unregister_class;
-
result = register_pm_notifier(&thermal_pm_nb);
if (result)
pr_warn("Thermal: Can not register suspend notifier, return %d\n",
@@ -1525,8 +1489,6 @@ static int __init thermal_init(void)
return 0;
-unregister_class:
- class_unregister(&thermal_class);
unregister_governors:
thermal_unregister_governors();
error:
@@ -1534,7 +1496,6 @@ error:
ida_destroy(&thermal_cdev_ida);
mutex_destroy(&thermal_list_lock);
mutex_destroy(&thermal_governor_lock);
- mutex_destroy(&poweroff_lock);
return result;
}
-core_initcall(thermal_init);
+postcore_initcall(thermal_init);
diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h
index a9bf00e91d64..1571917bd3c8 100644
--- a/drivers/thermal/thermal_core.h
+++ b/drivers/thermal/thermal_core.h
@@ -12,6 +12,19 @@
#include <linux/device.h>
#include <linux/thermal.h>
+#include "thermal_netlink.h"
+
+/* Default Thermal Governor */
+#if defined(CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE)
+#define DEFAULT_THERMAL_GOVERNOR "step_wise"
+#elif defined(CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE)
+#define DEFAULT_THERMAL_GOVERNOR "fair_share"
+#elif defined(CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE)
+#define DEFAULT_THERMAL_GOVERNOR "user_space"
+#elif defined(CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR)
+#define DEFAULT_THERMAL_GOVERNOR "power_allocator"
+#endif
+
/* Initial state of a cooling device during binding */
#define THERMAL_NO_TARGET -1UL
@@ -21,7 +34,7 @@ extern struct thermal_governor *__governor_thermal_table_end[];
#define THERMAL_TABLE_ENTRY(table, name) \
static typeof(name) *__thermal_table_entry_##name \
- __used __section(__##table##_thermal_table) = &name
+ __used __section("__" #table "_thermal_table") = &name
#define THERMAL_GOVERNOR_DECLARE(name) THERMAL_TABLE_ENTRY(governor, name)
@@ -30,6 +43,38 @@ extern struct thermal_governor *__governor_thermal_table_end[];
__governor < __governor_thermal_table_end; \
__governor++)
+int for_each_thermal_zone(int (*cb)(struct thermal_zone_device *, void *),
+ void *);
+
+int for_each_thermal_cooling_device(int (*cb)(struct thermal_cooling_device *,
+ void *), void *);
+
+int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *),
+ void *thermal_governor);
+
+struct thermal_zone_device *thermal_zone_get_by_id(int id);
+
+struct thermal_attr {
+ struct device_attribute attr;
+ char name[THERMAL_NAME_LENGTH];
+};
+
+static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev)
+{
+ return cdev->ops->get_requested_power && cdev->ops->state2power &&
+ cdev->ops->power2state;
+}
+
+void thermal_cdev_update(struct thermal_cooling_device *);
+void __thermal_cdev_update(struct thermal_cooling_device *cdev);
+
+int get_tz_trend(struct thermal_zone_device *tz, int trip);
+
+struct thermal_instance *
+get_thermal_instance(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev,
+ int trip);
+
/*
* This structure is used to describe the behavior of
* a certain cooling device on a certain trip point
@@ -62,13 +107,14 @@ struct thermal_instance {
int thermal_register_governor(struct thermal_governor *);
void thermal_unregister_governor(struct thermal_governor *);
-void thermal_zone_device_rebind_exception(struct thermal_zone_device *,
- const char *, size_t);
-void thermal_zone_device_unbind_exception(struct thermal_zone_device *,
- const char *, size_t);
int thermal_zone_device_set_policy(struct thermal_zone_device *, char *);
int thermal_build_list_of_policies(char *buf);
+/* Helpers */
+void thermal_zone_set_trips(struct thermal_zone_device *tz);
+void __thermal_zone_set_trips(struct thermal_zone_device *tz);
+int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp);
+
/* sysfs I/F */
int thermal_zone_create_device_groups(struct thermal_zone_device *, int);
void thermal_zone_destroy_device_groups(struct thermal_zone_device *);
@@ -91,13 +137,11 @@ thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
/* device tree support */
#ifdef CONFIG_THERMAL_OF
-int of_parse_thermal_zones(void);
int of_thermal_get_ntrips(struct thermal_zone_device *);
bool of_thermal_is_trip_valid(struct thermal_zone_device *, int);
const struct thermal_trip *
of_thermal_get_trip_points(struct thermal_zone_device *);
#else
-static inline int of_parse_thermal_zones(void) { return 0; }
static inline int of_thermal_get_ntrips(struct thermal_zone_device *tz)
{
return 0;
@@ -114,4 +158,6 @@ of_thermal_get_trip_points(struct thermal_zone_device *tz)
}
#endif
+int thermal_zone_device_is_enabled(struct thermal_zone_device *tz);
+
#endif /* __THERMAL_CORE_H__ */
diff --git a/drivers/thermal/thermal_helpers.c b/drivers/thermal/thermal_helpers.c
index 2ba756af76b7..c65cdce8f856 100644
--- a/drivers/thermal/thermal_helpers.c
+++ b/drivers/thermal/thermal_helpers.c
@@ -12,11 +12,12 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/export.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/sysfs.h>
#include <trace/events/thermal.h>
@@ -38,7 +39,6 @@ int get_tz_trend(struct thermal_zone_device *tz, int trip)
return trend;
}
-EXPORT_SYMBOL(get_tz_trend);
struct thermal_instance *
get_thermal_instance(struct thermal_zone_device *tz,
@@ -64,32 +64,22 @@ get_thermal_instance(struct thermal_zone_device *tz,
}
EXPORT_SYMBOL(get_thermal_instance);
-/**
- * thermal_zone_get_temp() - returns the temperature of a thermal zone
- * @tz: a valid pointer to a struct thermal_zone_device
- * @temp: a valid pointer to where to store the resulting temperature.
- *
- * When a valid thermal zone reference is passed, it will fetch its
- * temperature and fill @temp.
- *
- * Return: On success returns 0, an error code otherwise
- */
-int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
+int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
{
int ret = -EINVAL;
int count;
int crit_temp = INT_MAX;
enum thermal_trip_type type;
- if (!tz || IS_ERR(tz) || !tz->ops->get_temp)
- goto exit;
+ lockdep_assert_held(&tz->lock);
- mutex_lock(&tz->lock);
+ if (!tz || IS_ERR(tz) || !tz->ops->get_temp)
+ return -EINVAL;
ret = tz->ops->get_temp(tz, temp);
if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
- for (count = 0; count < tz->trips; count++) {
+ for (count = 0; count < tz->num_trips; count++) {
ret = tz->ops->get_trip_type(tz, count, &type);
if (!ret && type == THERMAL_TRIP_CRITICAL) {
ret = tz->ops->get_trip_temp(tz, count,
@@ -107,25 +97,44 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
*temp = tz->emul_temperature;
}
+ return ret;
+}
+
+/**
+ * thermal_zone_get_temp() - returns the temperature of a thermal zone
+ * @tz: a valid pointer to a struct thermal_zone_device
+ * @temp: a valid pointer to where to store the resulting temperature.
+ *
+ * When a valid thermal zone reference is passed, it will fetch its
+ * temperature and fill @temp.
+ *
+ * Return: On success returns 0, an error code otherwise
+ */
+int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ int ret;
+
+ mutex_lock(&tz->lock);
+ ret = __thermal_zone_get_temp(tz, temp);
mutex_unlock(&tz->lock);
-exit:
+
return ret;
}
EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
-void thermal_zone_set_trips(struct thermal_zone_device *tz)
+void __thermal_zone_set_trips(struct thermal_zone_device *tz)
{
int low = -INT_MAX;
int high = INT_MAX;
int trip_temp, hysteresis;
int i, ret;
- mutex_lock(&tz->lock);
+ lockdep_assert_held(&tz->lock);
if (!tz->ops->set_trips || !tz->ops->get_trip_hyst)
- goto exit;
+ return;
- for (i = 0; i < tz->trips; i++) {
+ for (i = 0; i < tz->num_trips; i++) {
int trip_low;
tz->ops->get_trip_temp(tz, i, &trip_temp);
@@ -142,7 +151,7 @@ void thermal_zone_set_trips(struct thermal_zone_device *tz)
/* No need to change trip points */
if (tz->prev_low_trip == low && tz->prev_high_trip == high)
- goto exit;
+ return;
tz->prev_low_trip = low;
tz->prev_high_trip = high;
@@ -157,24 +166,42 @@ void thermal_zone_set_trips(struct thermal_zone_device *tz)
ret = tz->ops->set_trips(tz, low, high);
if (ret)
dev_err(&tz->device, "Failed to set trips: %d\n", ret);
+}
-exit:
+/**
+ * thermal_zone_set_trips - Computes the next trip points for the driver
+ * @tz: a pointer to a thermal zone device structure
+ *
+ * The function computes the next temperature boundaries by browsing
+ * the trip points. The result is the closer low and high trip points
+ * to the current temperature. These values are passed to the backend
+ * driver to let it set its own notification mechanism (usually an
+ * interrupt).
+ *
+ * It does not return a value
+ */
+void thermal_zone_set_trips(struct thermal_zone_device *tz)
+{
+ mutex_lock(&tz->lock);
+ __thermal_zone_set_trips(tz);
mutex_unlock(&tz->lock);
}
-EXPORT_SYMBOL_GPL(thermal_zone_set_trips);
-void thermal_cdev_update(struct thermal_cooling_device *cdev)
+static void thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev,
+ int target)
+{
+ if (cdev->ops->set_cur_state(cdev, target))
+ return;
+
+ thermal_notify_cdev_state_update(cdev->id, target);
+ thermal_cooling_device_stats_update(cdev, target);
+}
+
+void __thermal_cdev_update(struct thermal_cooling_device *cdev)
{
struct thermal_instance *instance;
unsigned long target = 0;
- mutex_lock(&cdev->lock);
- /* cooling device is updated*/
- if (cdev->updated) {
- mutex_unlock(&cdev->lock);
- return;
- }
-
/* 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",
@@ -185,15 +212,27 @@ void thermal_cdev_update(struct thermal_cooling_device *cdev)
target = instance->target;
}
- if (!cdev->ops->set_cur_state(cdev, target))
- thermal_cooling_device_stats_update(cdev, target);
+ thermal_cdev_set_cur_state(cdev, target);
- cdev->updated = true;
- mutex_unlock(&cdev->lock);
trace_cdev_update(cdev, target);
dev_dbg(&cdev->device, "set to state %lu\n", target);
}
-EXPORT_SYMBOL(thermal_cdev_update);
+
+/**
+ * thermal_cdev_update - update cooling device state if needed
+ * @cdev: pointer to struct thermal_cooling_device
+ *
+ * Update the cooling device state if there is a need.
+ */
+void thermal_cdev_update(struct thermal_cooling_device *cdev)
+{
+ mutex_lock(&cdev->lock);
+ if (!cdev->updated) {
+ __thermal_cdev_update(cdev);
+ cdev->updated = true;
+ }
+ mutex_unlock(&cdev->lock);
+}
/**
* thermal_zone_get_slope - return the slope attribute of the thermal zone
diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c
index c8d2620f2e42..f53f4ceb6a5d 100644
--- a/drivers/thermal/thermal_hwmon.c
+++ b/drivers/thermal/thermal_hwmon.c
@@ -10,10 +10,12 @@
* Copyright (C) 2013 Texas Instruments
* Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
*/
+#include <linux/err.h>
+#include <linux/export.h>
#include <linux/hwmon.h>
-#include <linux/thermal.h>
#include <linux/slab.h>
-#include <linux/err.h>
+#include <linux/thermal.h>
+
#include "thermal_hwmon.h"
/* hwmon sys I/F */
@@ -145,10 +147,10 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
return -ENOMEM;
INIT_LIST_HEAD(&hwmon->tz_list);
- strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
+ strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
strreplace(hwmon->type, '-', '_');
- hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type,
- hwmon, NULL, NULL);
+ hwmon->device = hwmon_device_register_for_thermal(&tz->device,
+ hwmon->type, hwmon);
if (IS_ERR(hwmon->device)) {
result = PTR_ERR(hwmon->device);
goto free_mem;
@@ -204,8 +206,7 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
if (new_hwmon_device)
hwmon_device_unregister(hwmon->device);
free_mem:
- if (new_hwmon_device)
- kfree(hwmon);
+ kfree(hwmon);
return result;
}
@@ -276,3 +277,5 @@ int devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
return ret;
}
EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs);
+
+MODULE_IMPORT_NS(HWMON_THERMAL);
diff --git a/drivers/thermal/thermal_mmio.c b/drivers/thermal/thermal_mmio.c
index d0bdf1ea3331..39c921415989 100644
--- a/drivers/thermal/thermal_mmio.c
+++ b/drivers/thermal/thermal_mmio.c
@@ -20,11 +20,10 @@ static u32 thermal_mmio_readb(void __iomem *mmio_base)
return readb(mmio_base);
}
-static int thermal_mmio_get_temperature(void *private, int *temp)
+static int thermal_mmio_get_temperature(struct thermal_zone_device *tz, int *temp)
{
int t;
- struct thermal_mmio *sensor =
- (struct thermal_mmio *)private;
+ struct thermal_mmio *sensor = tz->devdata;
t = sensor->read_mmio(sensor->mmio_base) & sensor->mask;
t *= sensor->factor;
@@ -34,7 +33,7 @@ static int thermal_mmio_get_temperature(void *private, int *temp)
return 0;
}
-static struct thermal_zone_of_device_ops thermal_mmio_ops = {
+static const struct thermal_zone_device_ops thermal_mmio_ops = {
.get_temp = thermal_mmio_get_temperature,
};
@@ -54,11 +53,8 @@ static int thermal_mmio_probe(struct platform_device *pdev)
resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
sensor->mmio_base = devm_ioremap_resource(&pdev->dev, resource);
- if (IS_ERR(sensor->mmio_base)) {
- dev_err(&pdev->dev, "failed to ioremap memory (%ld)\n",
- PTR_ERR(sensor->mmio_base));
+ if (IS_ERR(sensor->mmio_base))
return PTR_ERR(sensor->mmio_base);
- }
sensor_init_func = device_get_match_data(&pdev->dev);
if (sensor_init_func) {
@@ -71,10 +67,10 @@ static int thermal_mmio_probe(struct platform_device *pdev)
}
}
- thermal_zone = devm_thermal_zone_of_sensor_register(&pdev->dev,
- 0,
- sensor,
- &thermal_mmio_ops);
+ thermal_zone = devm_thermal_of_zone_register(&pdev->dev,
+ 0,
+ sensor,
+ &thermal_mmio_ops);
if (IS_ERR(thermal_zone)) {
dev_err(&pdev->dev,
"failed to register sensor (%ld)\n",
@@ -82,7 +78,7 @@ static int thermal_mmio_probe(struct platform_device *pdev)
return PTR_ERR(thermal_zone);
}
- thermal_mmio_get_temperature(sensor, &temperature);
+ thermal_mmio_get_temperature(thermal_zone, &temperature);
dev_info(&pdev->dev,
"thermal mmio sensor %s registered, current temperature: %d\n",
pdev->name, temperature);
@@ -110,7 +106,7 @@ static struct platform_driver thermal_mmio_driver = {
.probe = thermal_mmio_probe,
.driver = {
.name = "thermal-mmio",
- .of_match_table = of_match_ptr(thermal_mmio_id_table),
+ .of_match_table = thermal_mmio_id_table,
},
};
diff --git a/drivers/thermal/thermal_netlink.c b/drivers/thermal/thermal_netlink.c
new file mode 100644
index 000000000000..e2d78a996b5f
--- /dev/null
+++ b/drivers/thermal/thermal_netlink.c
@@ -0,0 +1,704 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 Linaro Limited
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ * Generic netlink for thermal management framework
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <net/genetlink.h>
+#include <uapi/linux/thermal.h>
+
+#include "thermal_core.h"
+
+static const struct genl_multicast_group thermal_genl_mcgrps[] = {
+ { .name = THERMAL_GENL_SAMPLING_GROUP_NAME, },
+ { .name = THERMAL_GENL_EVENT_GROUP_NAME, },
+};
+
+static const struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = {
+ /* Thermal zone */
+ [THERMAL_GENL_ATTR_TZ] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_TZ_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TEMP] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_TZ_TRIP_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP_TEMP] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP_TYPE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP_HYST] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_MODE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_NAME] = { .type = NLA_STRING,
+ .len = THERMAL_NAME_LENGTH },
+ /* Governor(s) */
+ [THERMAL_GENL_ATTR_TZ_GOV] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_TZ_GOV_NAME] = { .type = NLA_STRING,
+ .len = THERMAL_NAME_LENGTH },
+ /* Cooling devices */
+ [THERMAL_GENL_ATTR_CDEV] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_CDEV_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CDEV_CUR_STATE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CDEV_MAX_STATE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CDEV_NAME] = { .type = NLA_STRING,
+ .len = THERMAL_NAME_LENGTH },
+ /* CPU capabilities */
+ [THERMAL_GENL_ATTR_CPU_CAPABILITY] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_CPU_CAPABILITY_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY] = { .type = NLA_U32 },
+};
+
+struct param {
+ struct nlattr **attrs;
+ struct sk_buff *msg;
+ const char *name;
+ int tz_id;
+ int cdev_id;
+ int trip_id;
+ int trip_temp;
+ int trip_type;
+ int trip_hyst;
+ int temp;
+ int cdev_state;
+ int cdev_max_state;
+ struct thermal_genl_cpu_caps *cpu_capabilities;
+ int cpu_capabilities_count;
+};
+
+typedef int (*cb_t)(struct param *);
+
+static struct genl_family thermal_gnl_family;
+
+/************************** Sampling encoding *******************************/
+
+int thermal_genl_sampling_temp(int id, int temp)
+{
+ struct sk_buff *skb;
+ void *hdr;
+
+ skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(skb, 0, 0, &thermal_gnl_family, 0,
+ THERMAL_GENL_SAMPLING_TEMP);
+ if (!hdr)
+ goto out_free;
+
+ if (nla_put_u32(skb, THERMAL_GENL_ATTR_TZ_ID, id))
+ goto out_cancel;
+
+ if (nla_put_u32(skb, THERMAL_GENL_ATTR_TZ_TEMP, temp))
+ goto out_cancel;
+
+ genlmsg_end(skb, hdr);
+
+ genlmsg_multicast(&thermal_gnl_family, skb, 0, 0, GFP_KERNEL);
+
+ return 0;
+out_cancel:
+ genlmsg_cancel(skb, hdr);
+out_free:
+ nlmsg_free(skb);
+
+ return -EMSGSIZE;
+}
+
+/**************************** Event encoding *********************************/
+
+static int thermal_genl_event_tz_create(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
+ nla_put_string(p->msg, THERMAL_GENL_ATTR_TZ_NAME, p->name))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_tz(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_tz_trip_up(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_ID, p->trip_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TEMP, p->temp))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_tz_trip_add(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_ID, p->trip_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_TYPE, p->trip_type) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_TEMP, p->trip_temp) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_HYST, p->trip_hyst))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_tz_trip_delete(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_ID, p->trip_id))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_cdev_add(struct param *p)
+{
+ if (nla_put_string(p->msg, THERMAL_GENL_ATTR_CDEV_NAME,
+ p->name) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID,
+ p->cdev_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_MAX_STATE,
+ p->cdev_max_state))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_cdev_delete(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID, p->cdev_id))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_cdev_state_update(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID,
+ p->cdev_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_CUR_STATE,
+ p->cdev_state))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_gov_change(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
+ nla_put_string(p->msg, THERMAL_GENL_ATTR_GOV_NAME, p->name))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_cpu_capability_change(struct param *p)
+{
+ struct thermal_genl_cpu_caps *cpu_cap = p->cpu_capabilities;
+ struct sk_buff *msg = p->msg;
+ struct nlattr *start_cap;
+ int i;
+
+ start_cap = nla_nest_start(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY);
+ if (!start_cap)
+ return -EMSGSIZE;
+
+ for (i = 0; i < p->cpu_capabilities_count; ++i) {
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_ID,
+ cpu_cap->cpu))
+ goto out_cancel_nest;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE,
+ cpu_cap->performance))
+ goto out_cancel_nest;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY,
+ cpu_cap->efficiency))
+ goto out_cancel_nest;
+
+ ++cpu_cap;
+ }
+
+ nla_nest_end(msg, start_cap);
+
+ return 0;
+out_cancel_nest:
+ nla_nest_cancel(msg, start_cap);
+
+ return -EMSGSIZE;
+}
+
+int thermal_genl_event_tz_delete(struct param *p)
+ __attribute__((alias("thermal_genl_event_tz")));
+
+int thermal_genl_event_tz_enable(struct param *p)
+ __attribute__((alias("thermal_genl_event_tz")));
+
+int thermal_genl_event_tz_disable(struct param *p)
+ __attribute__((alias("thermal_genl_event_tz")));
+
+int thermal_genl_event_tz_trip_down(struct param *p)
+ __attribute__((alias("thermal_genl_event_tz_trip_up")));
+
+int thermal_genl_event_tz_trip_change(struct param *p)
+ __attribute__((alias("thermal_genl_event_tz_trip_add")));
+
+static cb_t event_cb[] = {
+ [THERMAL_GENL_EVENT_TZ_CREATE] = thermal_genl_event_tz_create,
+ [THERMAL_GENL_EVENT_TZ_DELETE] = thermal_genl_event_tz_delete,
+ [THERMAL_GENL_EVENT_TZ_ENABLE] = thermal_genl_event_tz_enable,
+ [THERMAL_GENL_EVENT_TZ_DISABLE] = thermal_genl_event_tz_disable,
+ [THERMAL_GENL_EVENT_TZ_TRIP_UP] = thermal_genl_event_tz_trip_up,
+ [THERMAL_GENL_EVENT_TZ_TRIP_DOWN] = thermal_genl_event_tz_trip_down,
+ [THERMAL_GENL_EVENT_TZ_TRIP_CHANGE] = thermal_genl_event_tz_trip_change,
+ [THERMAL_GENL_EVENT_TZ_TRIP_ADD] = thermal_genl_event_tz_trip_add,
+ [THERMAL_GENL_EVENT_TZ_TRIP_DELETE] = thermal_genl_event_tz_trip_delete,
+ [THERMAL_GENL_EVENT_CDEV_ADD] = thermal_genl_event_cdev_add,
+ [THERMAL_GENL_EVENT_CDEV_DELETE] = thermal_genl_event_cdev_delete,
+ [THERMAL_GENL_EVENT_CDEV_STATE_UPDATE] = thermal_genl_event_cdev_state_update,
+ [THERMAL_GENL_EVENT_TZ_GOV_CHANGE] = thermal_genl_event_gov_change,
+ [THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE] = thermal_genl_event_cpu_capability_change,
+};
+
+/*
+ * Generic netlink event encoding
+ */
+static int thermal_genl_send_event(enum thermal_genl_event event,
+ struct param *p)
+{
+ struct sk_buff *msg;
+ int ret = -EMSGSIZE;
+ void *hdr;
+
+ msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+ p->msg = msg;
+
+ hdr = genlmsg_put(msg, 0, 0, &thermal_gnl_family, 0, event);
+ if (!hdr)
+ goto out_free_msg;
+
+ ret = event_cb[event](p);
+ if (ret)
+ goto out_cancel_msg;
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_multicast(&thermal_gnl_family, msg, 0, 1, GFP_KERNEL);
+
+ return 0;
+
+out_cancel_msg:
+ genlmsg_cancel(msg, hdr);
+out_free_msg:
+ nlmsg_free(msg);
+
+ return ret;
+}
+
+int thermal_notify_tz_create(int tz_id, const char *name)
+{
+ struct param p = { .tz_id = tz_id, .name = name };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_CREATE, &p);
+}
+
+int thermal_notify_tz_delete(int tz_id)
+{
+ struct param p = { .tz_id = tz_id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DELETE, &p);
+}
+
+int thermal_notify_tz_enable(int tz_id)
+{
+ struct param p = { .tz_id = tz_id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_ENABLE, &p);
+}
+
+int thermal_notify_tz_disable(int tz_id)
+{
+ struct param p = { .tz_id = tz_id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DISABLE, &p);
+}
+
+int thermal_notify_tz_trip_down(int tz_id, int trip_id, int temp)
+{
+ struct param p = { .tz_id = tz_id, .trip_id = trip_id, .temp = temp };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_DOWN, &p);
+}
+
+int thermal_notify_tz_trip_up(int tz_id, int trip_id, int temp)
+{
+ struct param p = { .tz_id = tz_id, .trip_id = trip_id, .temp = temp };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_UP, &p);
+}
+
+int thermal_notify_tz_trip_add(int tz_id, int trip_id, int trip_type,
+ int trip_temp, int trip_hyst)
+{
+ struct param p = { .tz_id = tz_id, .trip_id = trip_id,
+ .trip_type = trip_type, .trip_temp = trip_temp,
+ .trip_hyst = trip_hyst };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_ADD, &p);
+}
+
+int thermal_notify_tz_trip_delete(int tz_id, int trip_id)
+{
+ struct param p = { .tz_id = tz_id, .trip_id = trip_id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_DELETE, &p);
+}
+
+int thermal_notify_tz_trip_change(int tz_id, int trip_id, int trip_type,
+ int trip_temp, int trip_hyst)
+{
+ struct param p = { .tz_id = tz_id, .trip_id = trip_id,
+ .trip_type = trip_type, .trip_temp = trip_temp,
+ .trip_hyst = trip_hyst };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_CHANGE, &p);
+}
+
+int thermal_notify_cdev_state_update(int cdev_id, int cdev_state)
+{
+ struct param p = { .cdev_id = cdev_id, .cdev_state = cdev_state };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_STATE_UPDATE, &p);
+}
+
+int thermal_notify_cdev_add(int cdev_id, const char *name, int cdev_max_state)
+{
+ struct param p = { .cdev_id = cdev_id, .name = name,
+ .cdev_max_state = cdev_max_state };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_ADD, &p);
+}
+
+int thermal_notify_cdev_delete(int cdev_id)
+{
+ struct param p = { .cdev_id = cdev_id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_DELETE, &p);
+}
+
+int thermal_notify_tz_gov_change(int tz_id, const char *name)
+{
+ struct param p = { .tz_id = tz_id, .name = name };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_GOV_CHANGE, &p);
+}
+
+int thermal_genl_cpu_capability_event(int count,
+ struct thermal_genl_cpu_caps *caps)
+{
+ struct param p = { .cpu_capabilities_count = count, .cpu_capabilities = caps };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE, &p);
+}
+EXPORT_SYMBOL_GPL(thermal_genl_cpu_capability_event);
+
+/*************************** Command encoding ********************************/
+
+static int __thermal_genl_cmd_tz_get_id(struct thermal_zone_device *tz,
+ void *data)
+{
+ struct sk_buff *msg = data;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, tz->id) ||
+ nla_put_string(msg, THERMAL_GENL_ATTR_TZ_NAME, tz->type))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_cmd_tz_get_id(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ struct nlattr *start_tz;
+ int ret;
+
+ start_tz = nla_nest_start(msg, THERMAL_GENL_ATTR_TZ);
+ if (!start_tz)
+ return -EMSGSIZE;
+
+ ret = for_each_thermal_zone(__thermal_genl_cmd_tz_get_id, msg);
+ if (ret)
+ goto out_cancel_nest;
+
+ nla_nest_end(msg, start_tz);
+
+ return 0;
+
+out_cancel_nest:
+ nla_nest_cancel(msg, start_tz);
+
+ return ret;
+}
+
+static int thermal_genl_cmd_tz_get_trip(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ struct thermal_zone_device *tz;
+ struct nlattr *start_trip;
+ int i, id;
+
+ if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])
+ return -EINVAL;
+
+ id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+
+ tz = thermal_zone_get_by_id(id);
+ if (!tz)
+ return -EINVAL;
+
+ start_trip = nla_nest_start(msg, THERMAL_GENL_ATTR_TZ_TRIP);
+ if (!start_trip)
+ return -EMSGSIZE;
+
+ mutex_lock(&tz->lock);
+
+ for (i = 0; i < tz->num_trips; i++) {
+
+ enum thermal_trip_type type;
+ int temp, hyst = 0;
+
+ tz->ops->get_trip_type(tz, i, &type);
+ tz->ops->get_trip_temp(tz, i, &temp);
+ if (tz->ops->get_trip_hyst)
+ tz->ops->get_trip_hyst(tz, i, &hyst);
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_ID, i) ||
+ nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TYPE, type) ||
+ nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TEMP, temp) ||
+ nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_HYST, hyst))
+ goto out_cancel_nest;
+ }
+
+ mutex_unlock(&tz->lock);
+
+ nla_nest_end(msg, start_trip);
+
+ return 0;
+
+out_cancel_nest:
+ mutex_unlock(&tz->lock);
+
+ return -EMSGSIZE;
+}
+
+static int thermal_genl_cmd_tz_get_temp(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ struct thermal_zone_device *tz;
+ int temp, ret, id;
+
+ if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])
+ return -EINVAL;
+
+ id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+
+ tz = thermal_zone_get_by_id(id);
+ if (!tz)
+ return -EINVAL;
+
+ ret = thermal_zone_get_temp(tz, &temp);
+ if (ret)
+ return ret;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id) ||
+ nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TEMP, temp))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_cmd_tz_get_gov(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ struct thermal_zone_device *tz;
+ int id, ret = 0;
+
+ if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])
+ return -EINVAL;
+
+ id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+
+ tz = thermal_zone_get_by_id(id);
+ if (!tz)
+ return -EINVAL;
+
+ mutex_lock(&tz->lock);
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id) ||
+ nla_put_string(msg, THERMAL_GENL_ATTR_TZ_GOV_NAME,
+ tz->governor->name))
+ ret = -EMSGSIZE;
+
+ mutex_unlock(&tz->lock);
+
+ return ret;
+}
+
+static int __thermal_genl_cmd_cdev_get(struct thermal_cooling_device *cdev,
+ void *data)
+{
+ struct sk_buff *msg = data;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_CDEV_ID, cdev->id))
+ return -EMSGSIZE;
+
+ if (nla_put_string(msg, THERMAL_GENL_ATTR_CDEV_NAME, cdev->type))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_cmd_cdev_get(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ struct nlattr *start_cdev;
+ int ret;
+
+ start_cdev = nla_nest_start(msg, THERMAL_GENL_ATTR_CDEV);
+ if (!start_cdev)
+ return -EMSGSIZE;
+
+ ret = for_each_thermal_cooling_device(__thermal_genl_cmd_cdev_get, msg);
+ if (ret)
+ goto out_cancel_nest;
+
+ nla_nest_end(msg, start_cdev);
+
+ return 0;
+out_cancel_nest:
+ nla_nest_cancel(msg, start_cdev);
+
+ return ret;
+}
+
+static cb_t cmd_cb[] = {
+ [THERMAL_GENL_CMD_TZ_GET_ID] = thermal_genl_cmd_tz_get_id,
+ [THERMAL_GENL_CMD_TZ_GET_TRIP] = thermal_genl_cmd_tz_get_trip,
+ [THERMAL_GENL_CMD_TZ_GET_TEMP] = thermal_genl_cmd_tz_get_temp,
+ [THERMAL_GENL_CMD_TZ_GET_GOV] = thermal_genl_cmd_tz_get_gov,
+ [THERMAL_GENL_CMD_CDEV_GET] = thermal_genl_cmd_cdev_get,
+};
+
+static int thermal_genl_cmd_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct param p = { .msg = skb };
+ const struct genl_dumpit_info *info = genl_dumpit_info(cb);
+ int cmd = info->op.cmd;
+ int ret;
+ void *hdr;
+
+ hdr = genlmsg_put(skb, 0, 0, &thermal_gnl_family, 0, cmd);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ ret = cmd_cb[cmd](&p);
+ if (ret)
+ goto out_cancel_msg;
+
+ genlmsg_end(skb, hdr);
+
+ return 0;
+
+out_cancel_msg:
+ genlmsg_cancel(skb, hdr);
+
+ return ret;
+}
+
+static int thermal_genl_cmd_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct param p = { .attrs = info->attrs };
+ struct sk_buff *msg;
+ void *hdr;
+ int cmd = info->genlhdr->cmd;
+ int ret = -EMSGSIZE;
+
+ msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+ p.msg = msg;
+
+ hdr = genlmsg_put_reply(msg, info, &thermal_gnl_family, 0, cmd);
+ if (!hdr)
+ goto out_free_msg;
+
+ ret = cmd_cb[cmd](&p);
+ if (ret)
+ goto out_cancel_msg;
+
+ genlmsg_end(msg, hdr);
+
+ return genlmsg_reply(msg, info);
+
+out_cancel_msg:
+ genlmsg_cancel(msg, hdr);
+out_free_msg:
+ nlmsg_free(msg);
+
+ return ret;
+}
+
+static const struct genl_small_ops thermal_genl_ops[] = {
+ {
+ .cmd = THERMAL_GENL_CMD_TZ_GET_ID,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .dumpit = thermal_genl_cmd_dumpit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_TZ_GET_TRIP,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_TZ_GET_TEMP,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_TZ_GET_GOV,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_CDEV_GET,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .dumpit = thermal_genl_cmd_dumpit,
+ },
+};
+
+static struct genl_family thermal_gnl_family __ro_after_init = {
+ .hdrsize = 0,
+ .name = THERMAL_GENL_FAMILY_NAME,
+ .version = THERMAL_GENL_VERSION,
+ .maxattr = THERMAL_GENL_ATTR_MAX,
+ .policy = thermal_genl_policy,
+ .small_ops = thermal_genl_ops,
+ .n_small_ops = ARRAY_SIZE(thermal_genl_ops),
+ .resv_start_op = THERMAL_GENL_CMD_CDEV_GET + 1,
+ .mcgrps = thermal_genl_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(thermal_genl_mcgrps),
+};
+
+int __init thermal_netlink_init(void)
+{
+ return genl_register_family(&thermal_gnl_family);
+}
diff --git a/drivers/thermal/thermal_netlink.h b/drivers/thermal/thermal_netlink.h
new file mode 100644
index 000000000000..1052f523188d
--- /dev/null
+++ b/drivers/thermal/thermal_netlink.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Linaro Ltd 2020
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ */
+
+struct thermal_genl_cpu_caps {
+ int cpu;
+ int performance;
+ int efficiency;
+};
+
+/* Netlink notification function */
+#ifdef CONFIG_THERMAL_NETLINK
+int __init thermal_netlink_init(void);
+int thermal_notify_tz_create(int tz_id, const char *name);
+int thermal_notify_tz_delete(int tz_id);
+int thermal_notify_tz_enable(int tz_id);
+int thermal_notify_tz_disable(int tz_id);
+int thermal_notify_tz_trip_down(int tz_id, int id, int temp);
+int thermal_notify_tz_trip_up(int tz_id, int id, int temp);
+int thermal_notify_tz_trip_delete(int tz_id, int id);
+int thermal_notify_tz_trip_add(int tz_id, int id, int type,
+ int temp, int hyst);
+int thermal_notify_tz_trip_change(int tz_id, int id, int type,
+ int temp, int hyst);
+int thermal_notify_cdev_state_update(int cdev_id, int state);
+int thermal_notify_cdev_add(int cdev_id, const char *name, int max_state);
+int thermal_notify_cdev_delete(int cdev_id);
+int thermal_notify_tz_gov_change(int tz_id, const char *name);
+int thermal_genl_sampling_temp(int id, int temp);
+int thermal_genl_cpu_capability_event(int count,
+ struct thermal_genl_cpu_caps *caps);
+#else
+static inline int thermal_netlink_init(void)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_create(int tz_id, const char *name)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_delete(int tz_id)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_enable(int tz_id)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_disable(int tz_id)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_trip_down(int tz_id, int id, int temp)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_trip_up(int tz_id, int id, int temp)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_trip_delete(int tz_id, int id)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_trip_add(int tz_id, int id, int type,
+ int temp, int hyst)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_trip_change(int tz_id, int id, int type,
+ int temp, int hyst)
+{
+ return 0;
+}
+
+static inline int thermal_notify_cdev_state_update(int cdev_id, int state)
+{
+ return 0;
+}
+
+static inline int thermal_notify_cdev_add(int cdev_id, const char *name,
+ int max_state)
+{
+ return 0;
+}
+
+static inline int thermal_notify_cdev_delete(int cdev_id)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_gov_change(int tz_id, const char *name)
+{
+ return 0;
+}
+
+static inline int thermal_genl_sampling_temp(int id, int temp)
+{
+ return 0;
+}
+
+static inline int thermal_genl_cpu_capability_event(int count, struct thermal_genl_cpu_caps *caps)
+{
+ return 0;
+}
+
+#endif /* CONFIG_THERMAL_NETLINK */
diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c
new file mode 100644
index 000000000000..d4b6335ace15
--- /dev/null
+++ b/drivers/thermal/thermal_of.c
@@ -0,0 +1,734 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * of-thermal.c - Generic Thermal Management device tree support.
+ *
+ * Copyright (C) 2013 Texas Instruments
+ * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/string.h>
+
+#include "thermal_core.h"
+
+/**
+ * of_thermal_get_ntrips - function to export number of available trip
+ * points.
+ * @tz: pointer to a thermal zone
+ *
+ * This function is a globally visible wrapper to get number of trip points
+ * stored in the local struct __thermal_zone
+ *
+ * Return: number of available trip points, -ENODEV when data not available
+ */
+int of_thermal_get_ntrips(struct thermal_zone_device *tz)
+{
+ return tz->num_trips;
+}
+EXPORT_SYMBOL_GPL(of_thermal_get_ntrips);
+
+/**
+ * of_thermal_is_trip_valid - function to check if trip point is valid
+ *
+ * @tz: pointer to a thermal zone
+ * @trip: trip point to evaluate
+ *
+ * This function is responsible for checking if passed trip point is valid
+ *
+ * Return: true if trip point is valid, false otherwise
+ */
+bool of_thermal_is_trip_valid(struct thermal_zone_device *tz, int trip)
+{
+ if (trip >= tz->num_trips || trip < 0)
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(of_thermal_is_trip_valid);
+
+/**
+ * of_thermal_get_trip_points - function to get access to a globally exported
+ * trip points
+ *
+ * @tz: pointer to a thermal zone
+ *
+ * This function provides a pointer to trip points table
+ *
+ * Return: pointer to trip points table, NULL otherwise
+ */
+const struct thermal_trip *
+of_thermal_get_trip_points(struct thermal_zone_device *tz)
+{
+ return tz->trips;
+}
+EXPORT_SYMBOL_GPL(of_thermal_get_trip_points);
+
+static int of_thermal_get_trip_type(struct thermal_zone_device *tz, int trip,
+ enum thermal_trip_type *type)
+{
+ if (trip >= tz->num_trips || trip < 0)
+ return -EDOM;
+
+ *type = tz->trips[trip].type;
+
+ return 0;
+}
+
+static int of_thermal_get_trip_temp(struct thermal_zone_device *tz, int trip,
+ int *temp)
+{
+ if (trip >= tz->num_trips || trip < 0)
+ return -EDOM;
+
+ *temp = tz->trips[trip].temperature;
+
+ return 0;
+}
+
+static int of_thermal_get_trip_hyst(struct thermal_zone_device *tz, int trip,
+ int *hyst)
+{
+ if (trip >= tz->num_trips || trip < 0)
+ return -EDOM;
+
+ *hyst = tz->trips[trip].hysteresis;
+
+ return 0;
+}
+
+static int of_thermal_set_trip_hyst(struct thermal_zone_device *tz, int trip,
+ int hyst)
+{
+ if (trip >= tz->num_trips || trip < 0)
+ return -EDOM;
+
+ /* thermal framework should take care of data->mask & (1 << trip) */
+ tz->trips[trip].hysteresis = hyst;
+
+ return 0;
+}
+
+static int of_thermal_get_crit_temp(struct thermal_zone_device *tz,
+ int *temp)
+{
+ int i;
+
+ for (i = 0; i < tz->num_trips; i++)
+ if (tz->trips[i].type == THERMAL_TRIP_CRITICAL) {
+ *temp = tz->trips[i].temperature;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/*** functions parsing device tree nodes ***/
+
+static int of_find_trip_id(struct device_node *np, struct device_node *trip)
+{
+ struct device_node *trips;
+ struct device_node *t;
+ int i = 0;
+
+ trips = of_get_child_by_name(np, "trips");
+ if (!trips) {
+ pr_err("Failed to find 'trips' node\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Find the trip id point associated with the cooling device map
+ */
+ for_each_child_of_node(trips, t) {
+
+ if (t == trip)
+ goto out;
+ i++;
+ }
+
+ i = -ENXIO;
+out:
+ of_node_put(trips);
+
+ return i;
+}
+
+/*
+ * 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;
+}
+
+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;
+ }
+
+ return 0;
+}
+
+static struct thermal_trip *thermal_of_trips_init(struct device_node *np, int *ntrips)
+{
+ struct thermal_trip *tt;
+ struct device_node *trips, *trip;
+ int ret, count;
+
+ trips = of_get_child_by_name(np, "trips");
+ if (!trips) {
+ pr_err("Failed to find 'trips' node\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ count = of_get_child_count(trips);
+ if (!count) {
+ pr_err("No trip point defined\n");
+ ret = -EINVAL;
+ goto out_of_node_put;
+ }
+
+ tt = kzalloc(sizeof(*tt) * count, GFP_KERNEL);
+ if (!tt) {
+ ret = -ENOMEM;
+ goto out_of_node_put;
+ }
+
+ *ntrips = count;
+
+ count = 0;
+ for_each_child_of_node(trips, trip) {
+ ret = thermal_of_populate_trip(trip, &tt[count++]);
+ if (ret)
+ goto out_kfree;
+ }
+
+ of_node_put(trips);
+
+ return tt;
+
+out_kfree:
+ kfree(tt);
+ *ntrips = 0;
+out_of_node_put:
+ of_node_put(trips);
+
+ return ERR_PTR(ret);
+}
+
+static struct device_node *of_thermal_zone_find(struct device_node *sensor, int id)
+{
+ struct device_node *np, *tz;
+ struct of_phandle_args sensor_specs;
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np) {
+ pr_debug("No thermal zones description\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ /*
+ * Search for each thermal zone, a defined sensor
+ * corresponding to the one passed as parameter
+ */
+ for_each_available_child_of_node(np, tz) {
+
+ int count, i;
+
+ count = of_count_phandle_with_args(tz, "thermal-sensors",
+ "#thermal-sensor-cells");
+ if (count <= 0) {
+ pr_err("%pOFn: missing thermal sensor\n", tz);
+ tz = ERR_PTR(-EINVAL);
+ goto out;
+ }
+
+ for (i = 0; i < count; i++) {
+
+ int ret;
+
+ ret = of_parse_phandle_with_args(tz, "thermal-sensors",
+ "#thermal-sensor-cells",
+ i, &sensor_specs);
+ if (ret < 0) {
+ pr_err("%pOFn: Failed to read thermal-sensors cells: %d\n", tz, ret);
+ tz = ERR_PTR(ret);
+ goto out;
+ }
+
+ if ((sensor == sensor_specs.np) && id == (sensor_specs.args_count ?
+ sensor_specs.args[0] : 0)) {
+ pr_debug("sensor %pOFn id=%d belongs to %pOFn\n", sensor, id, tz);
+ goto out;
+ }
+ }
+ }
+ tz = ERR_PTR(-ENODEV);
+out:
+ of_node_put(np);
+ return tz;
+}
+
+static int thermal_of_monitor_init(struct device_node *np, int *delay, int *pdelay)
+{
+ int ret;
+
+ ret = of_property_read_u32(np, "polling-delay-passive", pdelay);
+ if (ret < 0) {
+ pr_err("%pOFn: missing polling-delay-passive property\n", np);
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "polling-delay", delay);
+ if (ret < 0) {
+ pr_err("%pOFn: missing polling-delay property\n", np);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct thermal_zone_params *thermal_of_parameters_init(struct device_node *np)
+{
+ struct thermal_zone_params *tzp;
+ int coef[2];
+ int ncoef = ARRAY_SIZE(coef);
+ int prop, ret;
+
+ tzp = kzalloc(sizeof(*tzp), GFP_KERNEL);
+ if (!tzp)
+ return ERR_PTR(-ENOMEM);
+
+ tzp->no_hwmon = true;
+
+ if (!of_property_read_u32(np, "sustainable-power", &prop))
+ tzp->sustainable_power = prop;
+
+ /*
+ * For now, the thermal framework supports only one sensor per
+ * thermal zone. Thus, we are considering only the first two
+ * values as slope and offset.
+ */
+ ret = of_property_read_u32_array(np, "coefficients", coef, ncoef);
+ if (ret) {
+ coef[0] = 1;
+ coef[1] = 0;
+ }
+
+ tzp->slope = coef[0];
+ tzp->offset = coef[1];
+
+ return tzp;
+}
+
+static struct device_node *thermal_of_zone_get_by_name(struct thermal_zone_device *tz)
+{
+ struct device_node *np, *tz_np;
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np)
+ return ERR_PTR(-ENODEV);
+
+ tz_np = of_get_child_by_name(np, tz->type);
+
+ of_node_put(np);
+
+ if (!tz_np)
+ return ERR_PTR(-ENODEV);
+
+ return tz_np;
+}
+
+static int __thermal_of_unbind(struct device_node *map_np, int index, int trip_id,
+ struct thermal_zone_device *tz, struct thermal_cooling_device *cdev)
+{
+ struct of_phandle_args cooling_spec;
+ int ret;
+
+ ret = of_parse_phandle_with_args(map_np, "cooling-device", "#cooling-cells",
+ index, &cooling_spec);
+
+ of_node_put(cooling_spec.np);
+
+ if (ret < 0) {
+ pr_err("Invalid cooling-device entry\n");
+ return ret;
+ }
+
+ if (cooling_spec.args_count < 2) {
+ pr_err("wrong reference to cooling device, missing limits\n");
+ return -EINVAL;
+ }
+
+ if (cooling_spec.np != cdev->np)
+ return 0;
+
+ ret = thermal_zone_unbind_cooling_device(tz, trip_id, cdev);
+ if (ret)
+ pr_err("Failed to unbind '%s' with '%s': %d\n", tz->type, cdev->type, ret);
+
+ return ret;
+}
+
+static int __thermal_of_bind(struct device_node *map_np, int index, int trip_id,
+ struct thermal_zone_device *tz, struct thermal_cooling_device *cdev)
+{
+ struct of_phandle_args cooling_spec;
+ int ret, weight = THERMAL_WEIGHT_DEFAULT;
+
+ of_property_read_u32(map_np, "contribution", &weight);
+
+ ret = of_parse_phandle_with_args(map_np, "cooling-device", "#cooling-cells",
+ index, &cooling_spec);
+
+ of_node_put(cooling_spec.np);
+
+ if (ret < 0) {
+ pr_err("Invalid cooling-device entry\n");
+ return ret;
+ }
+
+ if (cooling_spec.args_count < 2) {
+ pr_err("wrong reference to cooling device, missing limits\n");
+ return -EINVAL;
+ }
+
+ if (cooling_spec.np != cdev->np)
+ return 0;
+
+ ret = thermal_zone_bind_cooling_device(tz, trip_id, cdev, cooling_spec.args[1],
+ cooling_spec.args[0],
+ weight);
+ if (ret)
+ pr_err("Failed to bind '%s' with '%s': %d\n", tz->type, cdev->type, ret);
+
+ return ret;
+}
+
+static int thermal_of_for_each_cooling_device(struct device_node *tz_np, struct device_node *map_np,
+ struct thermal_zone_device *tz, struct thermal_cooling_device *cdev,
+ int (*action)(struct device_node *, int, int,
+ struct thermal_zone_device *, struct thermal_cooling_device *))
+{
+ struct device_node *tr_np;
+ int count, i, trip_id;
+
+ tr_np = of_parse_phandle(map_np, "trip", 0);
+ if (!tr_np)
+ return -ENODEV;
+
+ trip_id = of_find_trip_id(tz_np, tr_np);
+ if (trip_id < 0)
+ return trip_id;
+
+ count = of_count_phandle_with_args(map_np, "cooling-device", "#cooling-cells");
+ if (count <= 0) {
+ pr_err("Add a cooling_device property with at least one device\n");
+ return -ENOENT;
+ }
+
+ /*
+ * At this point, we don't want to bail out when there is an
+ * error, we will try to bind/unbind as many as possible
+ * cooling devices
+ */
+ for (i = 0; i < count; i++)
+ action(map_np, i, trip_id, tz, cdev);
+
+ return 0;
+}
+
+static int thermal_of_for_each_cooling_maps(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev,
+ int (*action)(struct device_node *, int, int,
+ struct thermal_zone_device *, struct thermal_cooling_device *))
+{
+ struct device_node *tz_np, *cm_np, *child;
+ int ret = 0;
+
+ tz_np = thermal_of_zone_get_by_name(tz);
+ if (IS_ERR(tz_np)) {
+ pr_err("Failed to get node tz by name\n");
+ return PTR_ERR(tz_np);
+ }
+
+ cm_np = of_get_child_by_name(tz_np, "cooling-maps");
+ if (!cm_np)
+ goto out;
+
+ for_each_child_of_node(cm_np, child) {
+ ret = thermal_of_for_each_cooling_device(tz_np, child, tz, cdev, action);
+ if (ret)
+ break;
+ }
+
+ of_node_put(cm_np);
+out:
+ of_node_put(tz_np);
+
+ return ret;
+}
+
+static int thermal_of_bind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
+{
+ return thermal_of_for_each_cooling_maps(tz, cdev, __thermal_of_bind);
+}
+
+static int thermal_of_unbind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
+{
+ return thermal_of_for_each_cooling_maps(tz, cdev, __thermal_of_unbind);
+}
+
+/**
+ * thermal_of_zone_unregister - Cleanup the specific allocated ressources
+ *
+ * This function disables the thermal zone and frees the different
+ * ressources allocated specific to the thermal OF.
+ *
+ * @tz: a pointer to the thermal zone structure
+ */
+void thermal_of_zone_unregister(struct thermal_zone_device *tz)
+{
+ struct thermal_trip *trips = tz->trips;
+ struct thermal_zone_params *tzp = tz->tzp;
+ struct thermal_zone_device_ops *ops = tz->ops;
+
+ thermal_zone_device_disable(tz);
+ thermal_zone_device_unregister(tz);
+ kfree(trips);
+ kfree(tzp);
+ kfree(ops);
+}
+EXPORT_SYMBOL_GPL(thermal_of_zone_unregister);
+
+/**
+ * thermal_of_zone_register - Register a thermal zone with device node
+ * sensor
+ *
+ * The thermal_of_zone_register() parses a device tree given a device
+ * node sensor and identifier. It searches for the thermal zone
+ * associated to the couple sensor/id and retrieves all the thermal
+ * zone properties and registers new thermal zone with those
+ * properties.
+ *
+ * @sensor: A device node pointer corresponding to the sensor in the device tree
+ * @id: An integer as sensor identifier
+ * @data: A private data to be stored in the thermal zone dedicated private area
+ * @ops: A set of thermal sensor ops
+ *
+ * Return: a valid thermal zone structure pointer on success.
+ * - EINVAL: if the device tree thermal description is malformed
+ * - ENOMEM: if one structure can not be allocated
+ * - Other negative errors are returned by the underlying called functions
+ */
+struct thermal_zone_device *thermal_of_zone_register(struct device_node *sensor, int id, void *data,
+ const struct thermal_zone_device_ops *ops)
+{
+ struct thermal_zone_device *tz;
+ struct thermal_trip *trips;
+ struct thermal_zone_params *tzp;
+ struct thermal_zone_device_ops *of_ops;
+ struct device_node *np;
+ int delay, pdelay;
+ int ntrips, mask;
+ int ret;
+
+ of_ops = kmemdup(ops, sizeof(*ops), GFP_KERNEL);
+ if (!of_ops)
+ return ERR_PTR(-ENOMEM);
+
+ np = of_thermal_zone_find(sensor, id);
+ if (IS_ERR(np)) {
+ if (PTR_ERR(np) != -ENODEV)
+ pr_err("Failed to find thermal zone for %pOFn id=%d\n", sensor, id);
+ return ERR_CAST(np);
+ }
+
+ trips = thermal_of_trips_init(np, &ntrips);
+ if (IS_ERR(trips)) {
+ pr_err("Failed to find trip points for %pOFn id=%d\n", sensor, id);
+ return ERR_CAST(trips);
+ }
+
+ ret = thermal_of_monitor_init(np, &delay, &pdelay);
+ if (ret) {
+ pr_err("Failed to initialize monitoring delays from %pOFn\n", np);
+ goto out_kfree_trips;
+ }
+
+ tzp = thermal_of_parameters_init(np);
+ if (IS_ERR(tzp)) {
+ ret = PTR_ERR(tzp);
+ pr_err("Failed to initialize parameter from %pOFn: %d\n", np, ret);
+ goto out_kfree_trips;
+ }
+
+ of_ops->get_trip_type = of_ops->get_trip_type ? : of_thermal_get_trip_type;
+ of_ops->get_trip_temp = of_ops->get_trip_temp ? : of_thermal_get_trip_temp;
+ of_ops->get_trip_hyst = of_ops->get_trip_hyst ? : of_thermal_get_trip_hyst;
+ of_ops->set_trip_hyst = of_ops->set_trip_hyst ? : of_thermal_set_trip_hyst;
+ of_ops->get_crit_temp = of_ops->get_crit_temp ? : of_thermal_get_crit_temp;
+ of_ops->bind = thermal_of_bind;
+ of_ops->unbind = thermal_of_unbind;
+
+ mask = GENMASK_ULL((ntrips) - 1, 0);
+
+ tz = thermal_zone_device_register_with_trips(np->name, trips, ntrips,
+ mask, data, of_ops, tzp,
+ pdelay, delay);
+ if (IS_ERR(tz)) {
+ ret = PTR_ERR(tz);
+ pr_err("Failed to register thermal zone %pOFn: %d\n", np, ret);
+ goto out_kfree_tzp;
+ }
+
+ ret = thermal_zone_device_enable(tz);
+ if (ret) {
+ pr_err("Failed to enabled thermal zone '%s', id=%d: %d\n",
+ tz->type, tz->id, ret);
+ thermal_of_zone_unregister(tz);
+ return ERR_PTR(ret);
+ }
+
+ return tz;
+
+out_kfree_tzp:
+ kfree(tzp);
+out_kfree_trips:
+ kfree(trips);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(thermal_of_zone_register);
+
+static void devm_thermal_of_zone_release(struct device *dev, void *res)
+{
+ thermal_of_zone_unregister(*(struct thermal_zone_device **)res);
+}
+
+static int devm_thermal_of_zone_match(struct device *dev, void *res,
+ void *data)
+{
+ struct thermal_zone_device **r = res;
+
+ if (WARN_ON(!r || !*r))
+ return 0;
+
+ return *r == data;
+}
+
+/**
+ * devm_thermal_of_zone_register - register a thermal tied with the sensor life cycle
+ *
+ * This function is the device version of the thermal_of_zone_register() function.
+ *
+ * @dev: a device structure pointer to sensor to be tied with the thermal zone OF life cycle
+ * @sensor_id: the sensor identifier
+ * @data: a pointer to a private data to be stored in the thermal zone 'devdata' field
+ * @ops: a pointer to the ops structure associated with the sensor
+ */
+struct thermal_zone_device *devm_thermal_of_zone_register(struct device *dev, int sensor_id, void *data,
+ const struct thermal_zone_device_ops *ops)
+{
+ struct thermal_zone_device **ptr, *tzd;
+
+ ptr = devres_alloc(devm_thermal_of_zone_release, sizeof(*ptr),
+ GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ tzd = thermal_of_zone_register(dev->of_node, sensor_id, data, ops);
+ if (IS_ERR(tzd)) {
+ devres_free(ptr);
+ return tzd;
+ }
+
+ *ptr = tzd;
+ devres_add(dev, ptr);
+
+ return tzd;
+}
+EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register);
+
+/**
+ * devm_thermal_of_zone_unregister - Resource managed version of
+ * thermal_of_zone_unregister().
+ * @dev: Device for which which resource was allocated.
+ * @tz: a pointer to struct thermal_zone where the sensor is registered.
+ *
+ * This function removes the sensor callbacks and private data from the
+ * thermal zone device registered with devm_thermal_zone_of_sensor_register()
+ * API. It will also silent the zone by remove the .get_temp() and .get_trend()
+ * thermal zone device callbacks.
+ * Normally this function will not need to be called and the resource
+ * management code will ensure that the resource is freed.
+ */
+void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_device *tz)
+{
+ WARN_ON(devres_release(dev, devm_thermal_of_zone_release,
+ devm_thermal_of_zone_match, tz));
+}
+EXPORT_SYMBOL_GPL(devm_thermal_of_zone_unregister);
diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c
index aa99edb4dff7..ec495c7dff03 100644
--- a/drivers/thermal/thermal_sysfs.c
+++ b/drivers/thermal/thermal_sysfs.c
@@ -49,18 +49,13 @@ static ssize_t
mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
- enum thermal_device_mode mode;
- int result;
-
- if (!tz->ops->get_mode)
- return -EPERM;
+ int enabled;
- result = tz->ops->get_mode(tz, &mode);
- if (result)
- return result;
+ mutex_lock(&tz->lock);
+ enabled = thermal_zone_device_is_enabled(tz);
+ mutex_unlock(&tz->lock);
- return sprintf(buf, "%s\n", mode == THERMAL_DEVICE_ENABLED ? "enabled"
- : "disabled");
+ return sprintf(buf, "%s\n", enabled ? "enabled" : "disabled");
}
static ssize_t
@@ -70,13 +65,10 @@ mode_store(struct device *dev, struct device_attribute *attr,
struct thermal_zone_device *tz = to_thermal_zone(dev);
int result;
- if (!tz->ops->set_mode)
- return -EPERM;
-
if (!strncmp(buf, "enabled", sizeof("enabled") - 1))
- result = tz->ops->set_mode(tz, THERMAL_DEVICE_ENABLED);
+ result = thermal_zone_device_enable(tz);
else if (!strncmp(buf, "disabled", sizeof("disabled") - 1))
- result = tz->ops->set_mode(tz, THERMAL_DEVICE_DISABLED);
+ result = thermal_zone_device_disable(tz);
else
result = -EINVAL;
@@ -124,9 +116,10 @@ trip_point_temp_store(struct device *dev, struct device_attribute *attr,
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
int trip, ret;
- int temperature;
+ int temperature, hyst = 0;
+ enum thermal_trip_type type;
- if (!tz->ops->set_trip_temp)
+ if (!tz->ops->set_trip_temp && !tz->trips)
return -EPERM;
if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip) != 1)
@@ -135,10 +128,27 @@ trip_point_temp_store(struct device *dev, struct device_attribute *attr,
if (kstrtoint(buf, 10, &temperature))
return -EINVAL;
- ret = tz->ops->set_trip_temp(tz, trip, temperature);
+ if (tz->ops->set_trip_temp) {
+ ret = tz->ops->set_trip_temp(tz, trip, temperature);
+ if (ret)
+ return ret;
+ }
+
+ if (tz->trips)
+ tz->trips[trip].temperature = temperature;
+
+ if (tz->ops->get_trip_hyst) {
+ ret = tz->ops->get_trip_hyst(tz, trip, &hyst);
+ if (ret)
+ return ret;
+ }
+
+ ret = tz->ops->get_trip_type(tz, trip, &type);
if (ret)
return ret;
+ thermal_notify_tz_trip_change(tz->id, trip, type, temperature, hyst);
+
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
return count;
@@ -216,49 +226,6 @@ trip_point_hyst_show(struct device *dev, struct device_attribute *attr,
}
static ssize_t
-passive_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct thermal_zone_device *tz = to_thermal_zone(dev);
- int state;
-
- if (sscanf(buf, "%d\n", &state) != 1)
- return -EINVAL;
-
- /* sanity check: values below 1000 millicelcius don't make sense
- * and can cause the system to go into a thermal heart attack
- */
- if (state && state < 1000)
- return -EINVAL;
-
- if (state && !tz->forced_passive) {
- if (!tz->passive_delay)
- tz->passive_delay = 1000;
- thermal_zone_device_rebind_exception(tz, "Processor",
- sizeof("Processor"));
- } else if (!state && tz->forced_passive) {
- tz->passive_delay = 0;
- thermal_zone_device_unbind_exception(tz, "Processor",
- sizeof("Processor"));
- }
-
- tz->forced_passive = state;
-
- thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
-
- return count;
-}
-
-static ssize_t
-passive_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct thermal_zone_device *tz = to_thermal_zone(dev);
-
- return sprintf(buf, "%d\n", tz->forced_passive);
-}
-
-static ssize_t
policy_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
@@ -402,7 +369,6 @@ static DEVICE_ATTR_RW(sustainable_power);
/* These thermal zone device attributes are created based on conditions */
static DEVICE_ATTR_RW(mode);
-static DEVICE_ATTR_RW(passive);
/* These attributes are unconditionally added to a thermal zone */
static struct attribute *thermal_zone_dev_attrs[] = {
@@ -424,75 +390,22 @@ static struct attribute *thermal_zone_dev_attrs[] = {
NULL,
};
-static struct attribute_group thermal_zone_attribute_group = {
+static const struct attribute_group thermal_zone_attribute_group = {
.attrs = thermal_zone_dev_attrs,
};
-/* We expose mode only if .get_mode is present */
static struct attribute *thermal_zone_mode_attrs[] = {
&dev_attr_mode.attr,
NULL,
};
-static umode_t thermal_zone_mode_is_visible(struct kobject *kobj,
- struct attribute *attr,
- int attrno)
-{
- struct device *dev = container_of(kobj, struct device, kobj);
- struct thermal_zone_device *tz;
-
- tz = container_of(dev, struct thermal_zone_device, device);
-
- if (tz->ops->get_mode)
- return attr->mode;
-
- return 0;
-}
-
-static struct attribute_group thermal_zone_mode_attribute_group = {
+static const struct attribute_group thermal_zone_mode_attribute_group = {
.attrs = thermal_zone_mode_attrs,
- .is_visible = thermal_zone_mode_is_visible,
-};
-
-/* We expose passive only if passive trips are present */
-static struct attribute *thermal_zone_passive_attrs[] = {
- &dev_attr_passive.attr,
- NULL,
-};
-
-static umode_t thermal_zone_passive_is_visible(struct kobject *kobj,
- struct attribute *attr,
- int attrno)
-{
- struct device *dev = container_of(kobj, struct device, kobj);
- struct thermal_zone_device *tz;
- enum thermal_trip_type trip_type;
- int count, passive = 0;
-
- tz = container_of(dev, struct thermal_zone_device, device);
-
- for (count = 0; count < tz->trips && !passive; count++) {
- tz->ops->get_trip_type(tz, count, &trip_type);
-
- if (trip_type == THERMAL_TRIP_PASSIVE)
- passive = 1;
- }
-
- if (!passive)
- return attr->mode;
-
- return 0;
-}
-
-static struct attribute_group thermal_zone_passive_attribute_group = {
- .attrs = thermal_zone_passive_attrs,
- .is_visible = thermal_zone_passive_is_visible,
};
static const struct attribute_group *thermal_zone_attribute_groups[] = {
&thermal_zone_attribute_group,
&thermal_zone_mode_attribute_group,
- &thermal_zone_passive_attribute_group,
/* This is not NULL terminated as we create the group dynamically */
};
@@ -512,15 +425,15 @@ static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
int indx;
/* This function works only for zones with at least one trip */
- if (tz->trips <= 0)
+ if (tz->num_trips <= 0)
return -EINVAL;
- tz->trip_type_attrs = kcalloc(tz->trips, sizeof(*tz->trip_type_attrs),
+ tz->trip_type_attrs = kcalloc(tz->num_trips, sizeof(*tz->trip_type_attrs),
GFP_KERNEL);
if (!tz->trip_type_attrs)
return -ENOMEM;
- tz->trip_temp_attrs = kcalloc(tz->trips, sizeof(*tz->trip_temp_attrs),
+ tz->trip_temp_attrs = kcalloc(tz->num_trips, sizeof(*tz->trip_temp_attrs),
GFP_KERNEL);
if (!tz->trip_temp_attrs) {
kfree(tz->trip_type_attrs);
@@ -528,7 +441,7 @@ static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
}
if (tz->ops->get_trip_hyst) {
- tz->trip_hyst_attrs = kcalloc(tz->trips,
+ tz->trip_hyst_attrs = kcalloc(tz->num_trips,
sizeof(*tz->trip_hyst_attrs),
GFP_KERNEL);
if (!tz->trip_hyst_attrs) {
@@ -538,7 +451,7 @@ static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
}
}
- attrs = kcalloc(tz->trips * 3 + 1, sizeof(*attrs), GFP_KERNEL);
+ attrs = kcalloc(tz->num_trips * 3 + 1, sizeof(*attrs), GFP_KERNEL);
if (!attrs) {
kfree(tz->trip_type_attrs);
kfree(tz->trip_temp_attrs);
@@ -547,7 +460,7 @@ static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
return -ENOMEM;
}
- for (indx = 0; indx < tz->trips; indx++) {
+ for (indx = 0; indx < tz->num_trips; indx++) {
/* create trip type attribute */
snprintf(tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH,
"trip_point_%d_type", indx);
@@ -574,7 +487,7 @@ static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
tz->trip_temp_attrs[indx].attr.store =
trip_point_temp_store;
}
- attrs[indx + tz->trips] = &tz->trip_temp_attrs[indx].attr.attr;
+ attrs[indx + tz->num_trips] = &tz->trip_temp_attrs[indx].attr.attr;
/* create Optional trip hyst attribute */
if (!tz->ops->get_trip_hyst)
@@ -592,10 +505,10 @@ static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
tz->trip_hyst_attrs[indx].attr.store =
trip_point_hyst_store;
}
- attrs[indx + tz->trips * 2] =
+ attrs[indx + tz->num_trips * 2] =
&tz->trip_hyst_attrs[indx].attr.attr;
}
- attrs[tz->trips * 3] = NULL;
+ attrs[tz->num_trips * 3] = NULL;
tz->trips_attribute_group.attrs = attrs;
@@ -636,7 +549,7 @@ int thermal_zone_create_device_groups(struct thermal_zone_device *tz,
for (i = 0; i < size - 2; i++)
groups[i] = thermal_zone_attribute_groups[i];
- if (tz->trips) {
+ if (tz->num_trips) {
result = create_trip_attrs(tz, mask);
if (result) {
kfree(groups);
@@ -657,7 +570,7 @@ void thermal_zone_destroy_device_groups(struct thermal_zone_device *tz)
if (!tz)
return;
- if (tz->trips)
+ if (tz->num_trips)
destroy_trip_attrs(tz);
kfree(tz->device.groups);
@@ -770,6 +683,9 @@ void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
{
struct cooling_dev_stats *stats = cdev->stats;
+ if (!stats)
+ return;
+
spin_lock(&stats->lock);
if (stats->state == new_state)
@@ -906,12 +822,13 @@ static const struct attribute_group cooling_device_stats_attr_group = {
static void cooling_device_stats_setup(struct thermal_cooling_device *cdev)
{
+ const struct attribute_group *stats_attr_group = NULL;
struct cooling_dev_stats *stats;
unsigned long states;
int var;
if (cdev->ops->get_max_state(cdev, &states))
- return;
+ goto out;
states++; /* Total number of states is highest state + 1 */
@@ -921,7 +838,7 @@ static void cooling_device_stats_setup(struct thermal_cooling_device *cdev)
stats = kzalloc(var, GFP_KERNEL);
if (!stats)
- return;
+ goto out;
stats->time_in_state = (ktime_t *)(stats + 1);
stats->trans_table = (unsigned int *)(stats->time_in_state + states);
@@ -931,9 +848,12 @@ static void cooling_device_stats_setup(struct thermal_cooling_device *cdev)
spin_lock_init(&stats->lock);
+ stats_attr_group = &cooling_device_stats_attr_group;
+
+out:
/* Fill the empty slot left in cooling_device_attr_groups */
var = ARRAY_SIZE(cooling_device_attr_groups) - 2;
- cooling_device_attr_groups[var] = &cooling_device_stats_attr_group;
+ cooling_device_attr_groups[var] = stats_attr_group;
}
static void cooling_device_stats_destroy(struct thermal_cooling_device *cdev)
@@ -971,10 +891,7 @@ trip_point_show(struct device *dev, struct device_attribute *attr, char *buf)
instance =
container_of(attr, struct thermal_instance, attr);
- if (instance->trip == THERMAL_TRIPS_NONE)
- return sprintf(buf, "-1\n");
- else
- return sprintf(buf, "%d\n", instance->trip);
+ return sprintf(buf, "%d\n", instance->trip);
}
ssize_t
diff --git a/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c
index 63b02bfb2adf..b4ef7340ac9b 100644
--- a/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c
+++ b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c
@@ -24,7 +24,7 @@ omap4430_mpu_temp_sensor_registers = {
.bgap_dtemp_mask = OMAP4430_BGAP_TEMP_SENSOR_DTEMP_MASK,
.bgap_mode_ctrl = OMAP4430_TEMP_SENSOR_CTRL_OFFSET,
- .mode_ctrl_mask = OMAP4430_SINGLE_MODE_MASK,
+ .mode_ctrl_mask = OMAP4430_CONTINUOUS_MODE_MASK,
.bgap_efuse = OMAP4430_FUSE_OPP_BGAP,
};
@@ -37,27 +37,29 @@ static struct temp_sensor_data omap4430_mpu_temp_sensor_data = {
/*
* Temperature values in milli degree celsius
- * ADC code values from 530 to 923
+ * ADC code values from 13 to 107, see TRM
+ * "18.4.10.2.3 ADC Codes Versus Temperature".
*/
static const int
omap4430_adc_to_temp[OMAP4430_ADC_END_VALUE - OMAP4430_ADC_START_VALUE + 1] = {
- -38000, -35000, -34000, -32000, -30000, -28000, -26000, -24000, -22000,
- -20000, -18000, -17000, -15000, -13000, -12000, -10000, -8000, -6000,
- -5000, -3000, -1000, 0, 2000, 3000, 5000, 6000, 8000, 10000, 12000,
- 13000, 15000, 17000, 19000, 21000, 23000, 25000, 27000, 28000, 30000,
- 32000, 33000, 35000, 37000, 38000, 40000, 42000, 43000, 45000, 47000,
- 48000, 50000, 52000, 53000, 55000, 57000, 58000, 60000, 62000, 64000,
- 66000, 68000, 70000, 71000, 73000, 75000, 77000, 78000, 80000, 82000,
- 83000, 85000, 87000, 88000, 90000, 92000, 93000, 95000, 97000, 98000,
- 100000, 102000, 103000, 105000, 107000, 109000, 111000, 113000, 115000,
- 117000, 118000, 120000, 122000, 123000,
+ -40000, -38000, -35000, -34000, -32000, -30000, -28000, -26000, -24000,
+ -22000, -20000, -18500, -17000, -15000, -13500, -12000, -10000, -8000,
+ -6500, -5000, -3500, -1500, 0, 2000, 3500, 5000, 6500, 8500, 10000,
+ 12000, 13500, 15000, 17000, 19000, 21000, 23000, 25000, 27000, 28500,
+ 30000, 32000, 33500, 35000, 37000, 38500, 40000, 42000, 43500, 45000,
+ 47000, 48500, 50000, 52000, 53500, 55000, 57000, 58500, 60000, 62000,
+ 64000, 66000, 68000, 70000, 71500, 73500, 75000, 77000, 78500, 80000,
+ 82000, 83500, 85000, 87000, 88500, 90000, 92000, 93500, 95000, 97000,
+ 98500, 100000, 102000, 103500, 105000, 107000, 109000, 111000, 113000,
+ 115000, 117000, 118500, 120000, 122000, 123500, 125000,
};
/* OMAP4430 data */
const struct ti_bandgap_data omap4430_data = {
.features = TI_BANDGAP_FEATURE_MODE_CONFIG |
TI_BANDGAP_FEATURE_CLK_CTRL |
- TI_BANDGAP_FEATURE_POWER_SWITCH,
+ TI_BANDGAP_FEATURE_POWER_SWITCH |
+ TI_BANDGAP_FEATURE_CONT_MODE_ONLY,
.fclock_name = "bandgap_fclk",
.div_ck_name = "bandgap_fclk",
.conv_table = omap4430_adc_to_temp,
@@ -95,7 +97,7 @@ omap4460_mpu_temp_sensor_registers = {
.mask_cold_mask = OMAP4460_MASK_COLD_MASK,
.bgap_mode_ctrl = OMAP4460_BGAP_CTRL_OFFSET,
- .mode_ctrl_mask = OMAP4460_SINGLE_MODE_MASK,
+ .mode_ctrl_mask = OMAP4460_CONTINUOUS_MODE_MASK,
.bgap_counter = OMAP4460_BGAP_COUNTER_OFFSET,
.counter_mask = OMAP4460_COUNTER_MASK,
diff --git a/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h
index a453ff8eb313..c63f439e01d6 100644
--- a/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h
+++ b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h
@@ -40,7 +40,7 @@
/* OMAP4430.TEMP_SENSOR bits */
#define OMAP4430_BGAP_TEMPSOFF_MASK BIT(12)
#define OMAP4430_BGAP_TSHUT_MASK BIT(11)
-#define OMAP4430_SINGLE_MODE_MASK BIT(10)
+#define OMAP4430_CONTINUOUS_MODE_MASK BIT(10)
#define OMAP4430_BGAP_TEMP_SENSOR_SOC_MASK BIT(9)
#define OMAP4430_BGAP_TEMP_SENSOR_EOCZ_MASK BIT(8)
#define OMAP4430_BGAP_TEMP_SENSOR_DTEMP_MASK (0xff << 0)
@@ -53,9 +53,13 @@
* and thresholds for OMAP4430.
*/
-/* ADC conversion table limits */
-#define OMAP4430_ADC_START_VALUE 0
-#define OMAP4430_ADC_END_VALUE 127
+/*
+ * ADC conversion table limits. Ignore values outside the TRM listed
+ * range to avoid bogus thermal shutdowns. See omap4430 TRM chapter
+ * "18.4.10.2.3 ADC Codes Versus Temperature".
+ */
+#define OMAP4430_ADC_START_VALUE 13
+#define OMAP4430_ADC_END_VALUE 107
/* bandgap clock limits (no control on 4430) */
#define OMAP4430_MAX_FREQ 32768
#define OMAP4430_MIN_FREQ 32768
@@ -109,7 +113,7 @@
#define OMAP4460_BGAP_TEMP_SENSOR_DTEMP_MASK (0x3ff << 0)
/* OMAP4460.BANDGAP_CTRL bits */
-#define OMAP4460_SINGLE_MODE_MASK BIT(31)
+#define OMAP4460_CONTINUOUS_MODE_MASK BIT(31)
#define OMAP4460_MASK_HOT_MASK BIT(1)
#define OMAP4460_MASK_COLD_MASK BIT(0)
diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c
index 2fa78f738568..67050a1a5b07 100644
--- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c
+++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c
@@ -9,27 +9,37 @@
* Eduardo Valentin <eduardo.valentin@ti.com>
*/
-#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/cpu_pm.h>
+#include <linux/device.h>
+#include <linux/err.h>
#include <linux/export.h>
+#include <linux/gpio/consumer.h>
#include <linux/init.h>
-#include <linux/kernel.h>
#include <linux/interrupt.h>
-#include <linux/clk.h>
-#include <linux/gpio.h>
-#include <linux/platform_device.h>
-#include <linux/err.h>
-#include <linux/types.h>
-#include <linux/spinlock.h>
-#include <linux/reboot.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
#include <linux/of_device.h>
-#include <linux/of_platform.h>
#include <linux/of_irq.h>
-#include <linux/of_gpio.h>
-#include <linux/io.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/reboot.h>
+#include <linux/spinlock.h>
+#include <linux/sys_soc.h>
+#include <linux/types.h>
#include "ti-bandgap.h"
static int ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id);
+#ifdef CONFIG_PM_SLEEP
+static int bandgap_omap_cpu_notifier(struct notifier_block *nb,
+ unsigned long cmd, void *v);
+#endif
/*** Helper functions to access registers and their bitfields ***/
@@ -216,7 +226,7 @@ static irqreturn_t ti_bandgap_talert_irq_handler(int irq, void *data)
/*
* One TALERT interrupt: Two sources
* If the interrupt is due to t_hot then mask t_hot and
- * and unmask t_cold else mask t_cold and unmask t_hot
+ * unmask t_cold else mask t_cold and unmask t_hot
*/
if (t_hot) {
ctrl &= ~tsr->mask_hot_mask;
@@ -592,35 +602,40 @@ void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id)
static int
ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id)
{
- u32 counter = 1000;
- struct temp_sensor_registers *tsr;
-
- /* Select single conversion mode */
- if (TI_BANDGAP_HAS(bgp, MODE_CONFIG))
- RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 0);
+ struct temp_sensor_registers *tsr = bgp->conf->sensors[id].registers;
+ void __iomem *temp_sensor_ctrl = bgp->base + tsr->temp_sensor_ctrl;
+ int error;
+ u32 val;
+
+ /* Select continuous or single conversion mode */
+ if (TI_BANDGAP_HAS(bgp, MODE_CONFIG)) {
+ if (TI_BANDGAP_HAS(bgp, CONT_MODE_ONLY))
+ RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 1);
+ else
+ RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 0);
+ }
- /* Start of Conversion = 1 */
- RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 1);
+ /* Set Start of Conversion if available */
+ if (tsr->bgap_soc_mask) {
+ RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 1);
- /* Wait for EOCZ going up */
- tsr = bgp->conf->sensors[id].registers;
+ /* Wait for EOCZ going up */
+ error = readl_poll_timeout_atomic(temp_sensor_ctrl, val,
+ val & tsr->bgap_eocz_mask,
+ 1, 1000);
+ if (error)
+ dev_warn(bgp->dev, "eocz timed out waiting high\n");
- while (--counter) {
- if (ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) &
- tsr->bgap_eocz_mask)
- break;
+ /* Clear Start of Conversion if available */
+ RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 0);
}
- /* Start of Conversion = 0 */
- RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 0);
-
- /* Wait for EOCZ going down */
- counter = 1000;
- while (--counter) {
- if (!(ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) &
- tsr->bgap_eocz_mask))
- break;
- }
+ /* Wait for EOCZ going down, always needed even if no bgap_soc_mask */
+ error = readl_poll_timeout_atomic(temp_sensor_ctrl, val,
+ !(val & tsr->bgap_eocz_mask),
+ 1, 1500);
+ if (error)
+ dev_warn(bgp->dev, "eocz timed out waiting low\n");
return 0;
}
@@ -743,33 +758,19 @@ exit:
static int ti_bandgap_tshut_init(struct ti_bandgap *bgp,
struct platform_device *pdev)
{
- int gpio_nr = bgp->tshut_gpio;
int status;
- /* Request for gpio_86 line */
- status = gpio_request(gpio_nr, "tshut");
- if (status < 0) {
- dev_err(bgp->dev, "Could not request for TSHUT GPIO:%i\n", 86);
- return status;
- }
- status = gpio_direction_input(gpio_nr);
- if (status) {
- dev_err(bgp->dev, "Cannot set input TSHUT GPIO %d\n", gpio_nr);
- return status;
- }
-
- status = request_irq(gpio_to_irq(gpio_nr), ti_bandgap_tshut_irq_handler,
+ status = request_irq(gpiod_to_irq(bgp->tshut_gpiod),
+ ti_bandgap_tshut_irq_handler,
IRQF_TRIGGER_RISING, "tshut", NULL);
- if (status) {
- gpio_free(gpio_nr);
+ if (status)
dev_err(bgp->dev, "request irq failed for TSHUT");
- }
return 0;
}
/**
- * ti_bandgap_alert_init() - setup and initialize talert handling
+ * ti_bandgap_talert_init() - setup and initialize talert handling
* @bgp: pointer to struct ti_bandgap
* @pdev: pointer to device struct platform_device
*
@@ -787,10 +788,9 @@ static int ti_bandgap_talert_init(struct ti_bandgap *bgp,
int ret;
bgp->irq = platform_get_irq(pdev, 0);
- if (bgp->irq < 0) {
- dev_err(&pdev->dev, "get_irq failed\n");
+ if (bgp->irq < 0)
return bgp->irq;
- }
+
ret = request_threaded_irq(bgp->irq, NULL,
ti_bandgap_talert_irq_handler,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
@@ -860,17 +860,27 @@ static struct ti_bandgap *ti_bandgap_build(struct platform_device *pdev)
} while (res);
if (TI_BANDGAP_HAS(bgp, TSHUT)) {
- bgp->tshut_gpio = of_get_gpio(node, 0);
- if (!gpio_is_valid(bgp->tshut_gpio)) {
- dev_err(&pdev->dev, "invalid gpio for tshut (%d)\n",
- bgp->tshut_gpio);
- return ERR_PTR(-EINVAL);
+ bgp->tshut_gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_IN);
+ if (IS_ERR(bgp->tshut_gpiod)) {
+ dev_err(&pdev->dev, "invalid gpio for tshut\n");
+ return ERR_CAST(bgp->tshut_gpiod);
}
}
return bgp;
}
+/*
+ * List of SoCs on which the CPU PM notifier can cause erros on the DTEMP
+ * readout.
+ * Enabled notifier on these machines results in erroneous, random values which
+ * could trigger unexpected thermal shutdown.
+ */
+static const struct soc_device_attribute soc_no_cpu_notifier[] = {
+ { .machine = "OMAP4430" },
+ { /* sentinel */ },
+};
+
/*** Device driver call backs ***/
static
@@ -1025,6 +1035,12 @@ int ti_bandgap_probe(struct platform_device *pdev)
}
}
+#ifdef CONFIG_PM_SLEEP
+ bgp->nb.notifier_call = bandgap_omap_cpu_notifier;
+ if (!soc_device_match(soc_no_cpu_notifier))
+ cpu_pm_register_notifier(&bgp->nb);
+#endif
+
return 0;
remove_last_cooling:
@@ -1046,10 +1062,8 @@ put_clks:
put_fclock:
clk_put(bgp->fclock);
free_irqs:
- if (TI_BANDGAP_HAS(bgp, TSHUT)) {
- free_irq(gpio_to_irq(bgp->tshut_gpio), NULL);
- gpio_free(bgp->tshut_gpio);
- }
+ if (TI_BANDGAP_HAS(bgp, TSHUT))
+ free_irq(gpiod_to_irq(bgp->tshut_gpiod), NULL);
return ret;
}
@@ -1060,7 +1074,10 @@ int ti_bandgap_remove(struct platform_device *pdev)
struct ti_bandgap *bgp = platform_get_drvdata(pdev);
int i;
- /* First thing is to remove sensor interfaces */
+ if (!soc_device_match(soc_no_cpu_notifier))
+ cpu_pm_unregister_notifier(&bgp->nb);
+
+ /* Remove sensor interfaces */
for (i = 0; i < bgp->conf->sensor_count; i++) {
if (bgp->conf->sensors[i].unregister_cooling)
bgp->conf->sensors[i].unregister_cooling(bgp, i);
@@ -1079,10 +1096,8 @@ int ti_bandgap_remove(struct platform_device *pdev)
if (TI_BANDGAP_HAS(bgp, TALERT))
free_irq(bgp->irq, bgp);
- if (TI_BANDGAP_HAS(bgp, TSHUT)) {
- free_irq(gpio_to_irq(bgp->tshut_gpio), NULL);
- gpio_free(bgp->tshut_gpio);
- }
+ if (TI_BANDGAP_HAS(bgp, TSHUT))
+ free_irq(gpiod_to_irq(bgp->tshut_gpiod), NULL);
return 0;
}
@@ -1127,14 +1142,10 @@ static int ti_bandgap_restore_ctxt(struct ti_bandgap *bgp)
for (i = 0; i < bgp->conf->sensor_count; i++) {
struct temp_sensor_registers *tsr;
struct temp_sensor_regval *rval;
- u32 val = 0;
rval = &bgp->regval[i];
tsr = bgp->conf->sensors[i].registers;
- if (TI_BANDGAP_HAS(bgp, COUNTER))
- val = ti_bandgap_readl(bgp, tsr->bgap_counter);
-
if (TI_BANDGAP_HAS(bgp, TSHUT_CONFIG))
ti_bandgap_writel(bgp, rval->tshut_threshold,
tsr->tshut_threshold);
@@ -1171,9 +1182,43 @@ static int ti_bandgap_suspend(struct device *dev)
if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
clk_disable_unprepare(bgp->fclock);
+ bgp->is_suspended = true;
+
return err;
}
+static int bandgap_omap_cpu_notifier(struct notifier_block *nb,
+ unsigned long cmd, void *v)
+{
+ struct ti_bandgap *bgp;
+
+ bgp = container_of(nb, struct ti_bandgap, nb);
+
+ spin_lock(&bgp->lock);
+ switch (cmd) {
+ case CPU_CLUSTER_PM_ENTER:
+ if (bgp->is_suspended)
+ break;
+ ti_bandgap_save_ctxt(bgp);
+ ti_bandgap_power(bgp, false);
+ if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
+ clk_disable(bgp->fclock);
+ break;
+ case CPU_CLUSTER_PM_ENTER_FAILED:
+ case CPU_CLUSTER_PM_EXIT:
+ if (bgp->is_suspended)
+ break;
+ if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
+ clk_enable(bgp->fclock);
+ ti_bandgap_power(bgp, true);
+ ti_bandgap_restore_ctxt(bgp);
+ break;
+ }
+ spin_unlock(&bgp->lock);
+
+ return NOTIFY_OK;
+}
+
static int ti_bandgap_resume(struct device *dev)
{
struct ti_bandgap *bgp = dev_get_drvdata(dev);
@@ -1182,6 +1227,7 @@ static int ti_bandgap_resume(struct device *dev)
clk_prepare_enable(bgp->fclock);
ti_bandgap_power(bgp, true);
+ bgp->is_suspended = false;
return ti_bandgap_restore_ctxt(bgp);
}
diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.h b/drivers/thermal/ti-soc-thermal/ti-bandgap.h
index bb9b0f7faf99..1f4bbaf31675 100644
--- a/drivers/thermal/ti-soc-thermal/ti-bandgap.h
+++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.h
@@ -12,6 +12,12 @@
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/err.h>
+#include <linux/cpu_pm.h>
+#include <linux/device.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm.h>
+
+struct gpio_desc;
/**
* DOC: bandgap driver data structure
@@ -199,8 +205,10 @@ struct ti_bandgap {
struct clk *div_clk;
spinlock_t lock; /* shields this struct */
int irq;
- int tshut_gpio;
+ struct gpio_desc *tshut_gpiod;
u32 clk_rate;
+ struct notifier_block nb;
+ unsigned int is_suspended:1;
};
/**
@@ -272,6 +280,7 @@ struct ti_temp_sensor {
* has Errata 814
* TI_BANDGAP_FEATURE_UNRELIABLE - used when the sensor readings are too
* inaccurate.
+ * TI_BANDGAP_FEATURE_CONT_MODE_ONLY - used when single mode hangs the sensor
* TI_BANDGAP_HAS(b, f) - macro to check if a bandgap device is capable of a
* specific feature (above) or not. Return non-zero, if yes.
*/
@@ -287,6 +296,7 @@ struct ti_temp_sensor {
#define TI_BANDGAP_FEATURE_HISTORY_BUFFER BIT(9)
#define TI_BANDGAP_FEATURE_ERRATA_814 BIT(10)
#define TI_BANDGAP_FEATURE_UNRELIABLE BIT(11)
+#define TI_BANDGAP_FEATURE_CONT_MODE_ONLY BIT(12)
#define TI_BANDGAP_HAS(b, f) \
((b)->conf->features & TI_BANDGAP_FEATURE_ ## f)
diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
index d3e959d01606..8a9055bd376e 100644
--- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
+++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
@@ -21,6 +21,7 @@
#include "ti-thermal.h"
#include "ti-bandgap.h"
+#include "../thermal_hwmon.h"
/* common data structures */
struct ti_thermal_data {
@@ -64,10 +65,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(void *devdata, int *temp)
+static inline int __ti_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct thermal_zone_device *pcb_tz = NULL;
- struct ti_thermal_data *data = devdata;
+ struct ti_thermal_data *data = tz->devdata;
struct ti_bandgap *bgp;
const struct ti_temp_sensor *s;
int ret, tmp, slope, constant;
@@ -84,8 +85,8 @@ static inline int __ti_thermal_get_temp(void *devdata, int *temp)
return ret;
/* Default constants */
- slope = thermal_zone_get_slope(data->ti_thermal);
- constant = thermal_zone_get_offset(data->ti_thermal);
+ slope = thermal_zone_get_slope(tz);
+ constant = thermal_zone_get_offset(tz);
pcb_tz = data->pcb_tz;
/* In case pcb zone is available, use the extrapolation rule with it */
@@ -106,17 +107,9 @@ static inline int __ti_thermal_get_temp(void *devdata, int *temp)
return ret;
}
-static inline int ti_thermal_get_temp(struct thermal_zone_device *thermal,
- int *temp)
+static int __ti_thermal_get_trend(struct thermal_zone_device *tz, int trip, enum thermal_trend *trend)
{
- struct ti_thermal_data *data = thermal->devdata;
-
- return __ti_thermal_get_temp(data, temp);
-}
-
-static int __ti_thermal_get_trend(void *p, int trip, enum thermal_trend *trend)
-{
- struct ti_thermal_data *data = p;
+ struct ti_thermal_data *data = tz->devdata;
struct ti_bandgap *bgp;
int id, tr, ret = 0;
@@ -137,7 +130,7 @@ static int __ti_thermal_get_trend(void *p, int trip, enum thermal_trend *trend)
return 0;
}
-static const struct thermal_zone_of_device_ops ti_of_thermal_ops = {
+static const struct thermal_zone_device_ops ti_of_thermal_ops = {
.get_temp = __ti_thermal_get_temp,
.get_trend = __ti_thermal_get_trend,
};
@@ -166,26 +159,31 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id,
char *domain)
{
struct ti_thermal_data *data;
+ int interval;
data = ti_bandgap_get_sensor_data(bgp, id);
- if (!data || IS_ERR(data))
+ if (IS_ERR_OR_NULL(data))
data = ti_thermal_build_data(bgp, id);
if (!data)
return -EINVAL;
/* in case this is specified by DT */
- data->ti_thermal = devm_thermal_zone_of_sensor_register(bgp->dev, id,
+ data->ti_thermal = devm_thermal_of_zone_register(bgp->dev, id,
data, &ti_of_thermal_ops);
if (IS_ERR(data->ti_thermal)) {
dev_err(bgp->dev, "thermal zone device is NULL\n");
return PTR_ERR(data->ti_thermal);
}
+ interval = jiffies_to_msecs(data->ti_thermal->polling_delay_jiffies);
+
ti_bandgap_set_sensor_data(bgp, id, data);
- ti_bandgap_write_update_interval(bgp, data->sensor_id,
- data->ti_thermal->polling_delay);
+ ti_bandgap_write_update_interval(bgp, data->sensor_id, interval);
+
+ if (devm_thermal_add_hwmon_sysfs(data->ti_thermal))
+ dev_warn(bgp->dev, "failed to add hwmon sysfs attributes\n");
return 0;
}
@@ -196,7 +194,7 @@ int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id)
data = ti_bandgap_get_sensor_data(bgp, id);
- if (data && data->ti_thermal) {
+ if (!IS_ERR_OR_NULL(data) && data->ti_thermal) {
if (data->our_zone)
thermal_zone_device_unregister(data->ti_thermal);
}
@@ -262,7 +260,7 @@ int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id)
data = ti_bandgap_get_sensor_data(bgp, id);
- if (data) {
+ if (!IS_ERR_OR_NULL(data)) {
cpufreq_cooling_unregister(data->cool_dev);
if (data->policy)
cpufreq_cpu_put(data->policy);
diff --git a/drivers/thermal/uniphier_thermal.c b/drivers/thermal/uniphier_thermal.c
index bba2284412d3..4111d99ef50e 100644
--- a/drivers/thermal/uniphier_thermal.c
+++ b/drivers/thermal/uniphier_thermal.c
@@ -187,9 +187,9 @@ static void uniphier_tm_disable_sensor(struct uniphier_tm_dev *tdev)
usleep_range(1000, 2000); /* The spec note says at least 1ms */
}
-static int uniphier_tm_get_temp(void *data, int *out_temp)
+static int uniphier_tm_get_temp(struct thermal_zone_device *tz, int *out_temp)
{
- struct uniphier_tm_dev *tdev = data;
+ struct uniphier_tm_dev *tdev = tz->devdata;
struct regmap *map = tdev->regmap;
int ret;
u32 temp;
@@ -204,7 +204,7 @@ static int uniphier_tm_get_temp(void *data, int *out_temp)
return 0;
}
-static const struct thermal_zone_of_device_ops uniphier_of_thermal_ops = {
+static const struct thermal_zone_device_ops uniphier_of_thermal_ops = {
.get_temp = uniphier_tm_get_temp,
};
@@ -289,8 +289,8 @@ static int uniphier_tm_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, tdev);
- tdev->tz_dev = devm_thermal_zone_of_sensor_register(dev, 0, tdev,
- &uniphier_of_thermal_ops);
+ tdev->tz_dev = devm_thermal_of_zone_register(dev, 0, tdev,
+ &uniphier_of_thermal_ops);
if (IS_ERR(tdev->tz_dev)) {
dev_err(dev, "failed to register sensor device\n");
return PTR_ERR(tdev->tz_dev);
@@ -358,6 +358,10 @@ static const struct of_device_id uniphier_tm_dt_ids[] = {
.compatible = "socionext,uniphier-pxs3-thermal",
.data = &uniphier_ld20_tm_data,
},
+ {
+ .compatible = "socionext,uniphier-nx1-thermal",
+ .data = &uniphier_ld20_tm_data,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, uniphier_tm_dt_ids);
diff --git a/drivers/thermal/zx2967_thermal.c b/drivers/thermal/zx2967_thermal.c
deleted file mode 100644
index 8e3a2d3c2f9a..000000000000
--- a/drivers/thermal/zx2967_thermal.c
+++ /dev/null
@@ -1,256 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * ZTE's zx2967 family thermal sensor driver
- *
- * Copyright (C) 2017 ZTE Ltd.
- *
- * Author: Baoyou Xie <baoyou.xie@linaro.org>
- */
-
-#include <linux/clk.h>
-#include <linux/device.h>
-#include <linux/err.h>
-#include <linux/iopoll.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/thermal.h>
-
-/* Power Mode: 0->low 1->high */
-#define ZX2967_THERMAL_POWER_MODE 0
-#define ZX2967_POWER_MODE_LOW 0
-#define ZX2967_POWER_MODE_HIGH 1
-
-/* DCF Control Register */
-#define ZX2967_THERMAL_DCF 0x4
-#define ZX2967_DCF_EN BIT(1)
-#define ZX2967_DCF_FREEZE BIT(0)
-
-/* Selection Register */
-#define ZX2967_THERMAL_SEL 0x8
-
-/* Control Register */
-#define ZX2967_THERMAL_CTRL 0x10
-
-#define ZX2967_THERMAL_READY BIT(12)
-#define ZX2967_THERMAL_TEMP_MASK GENMASK(11, 0)
-#define ZX2967_THERMAL_ID_MASK 0x18
-#define ZX2967_THERMAL_ID 0x10
-
-#define ZX2967_GET_TEMP_TIMEOUT_US (100 * 1024)
-
-/**
- * struct zx2967_thermal_priv - zx2967 thermal sensor private structure
- * @tzd: struct thermal_zone_device where the sensor is registered
- * @lock: prevents read sensor in parallel
- * @clk_topcrm: topcrm clk structure
- * @clk_apb: apb clk structure
- * @regs: pointer to base address of the thermal sensor
- * @dev: struct device pointer
- */
-
-struct zx2967_thermal_priv {
- struct thermal_zone_device *tzd;
- struct mutex lock;
- struct clk *clk_topcrm;
- struct clk *clk_apb;
- void __iomem *regs;
- struct device *dev;
-};
-
-static int zx2967_thermal_get_temp(void *data, int *temp)
-{
- void __iomem *regs;
- struct zx2967_thermal_priv *priv = data;
- u32 val;
- int ret;
-
- if (!priv->tzd)
- return -EAGAIN;
-
- regs = priv->regs;
- mutex_lock(&priv->lock);
- writel_relaxed(ZX2967_POWER_MODE_LOW,
- regs + ZX2967_THERMAL_POWER_MODE);
- writel_relaxed(ZX2967_DCF_EN, regs + ZX2967_THERMAL_DCF);
-
- val = readl_relaxed(regs + ZX2967_THERMAL_SEL);
- val &= ~ZX2967_THERMAL_ID_MASK;
- val |= ZX2967_THERMAL_ID;
- writel_relaxed(val, regs + ZX2967_THERMAL_SEL);
-
- /*
- * Must wait for a while, surely it's a bit odd.
- * otherwise temperature value we got has a few deviation, even if
- * the THERMAL_READY bit is set.
- */
- usleep_range(100, 300);
- ret = readx_poll_timeout(readl, regs + ZX2967_THERMAL_CTRL,
- val, val & ZX2967_THERMAL_READY, 300,
- ZX2967_GET_TEMP_TIMEOUT_US);
- if (ret) {
- dev_err(priv->dev, "Thermal sensor data timeout\n");
- goto unlock;
- }
-
- writel_relaxed(ZX2967_DCF_FREEZE | ZX2967_DCF_EN,
- regs + ZX2967_THERMAL_DCF);
- val = readl_relaxed(regs + ZX2967_THERMAL_CTRL)
- & ZX2967_THERMAL_TEMP_MASK;
- writel_relaxed(ZX2967_POWER_MODE_HIGH,
- regs + ZX2967_THERMAL_POWER_MODE);
-
- /*
- * Calculate temperature
- * In dts, slope is multiplied by 1000.
- */
- *temp = DIV_ROUND_CLOSEST(((s32)val + priv->tzd->tzp->offset) * 1000,
- priv->tzd->tzp->slope);
-
-unlock:
- mutex_unlock(&priv->lock);
- return ret;
-}
-
-static const struct thermal_zone_of_device_ops zx2967_of_thermal_ops = {
- .get_temp = zx2967_thermal_get_temp,
-};
-
-static int zx2967_thermal_probe(struct platform_device *pdev)
-{
- struct zx2967_thermal_priv *priv;
- struct resource *res;
- int ret;
-
- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- priv->regs = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(priv->regs))
- return PTR_ERR(priv->regs);
-
- priv->clk_topcrm = devm_clk_get(&pdev->dev, "topcrm");
- if (IS_ERR(priv->clk_topcrm)) {
- ret = PTR_ERR(priv->clk_topcrm);
- dev_err(&pdev->dev, "failed to get topcrm clock: %d\n", ret);
- return ret;
- }
-
- ret = clk_prepare_enable(priv->clk_topcrm);
- if (ret) {
- dev_err(&pdev->dev, "failed to enable topcrm clock: %d\n",
- ret);
- return ret;
- }
-
- priv->clk_apb = devm_clk_get(&pdev->dev, "apb");
- if (IS_ERR(priv->clk_apb)) {
- ret = PTR_ERR(priv->clk_apb);
- dev_err(&pdev->dev, "failed to get apb clock: %d\n", ret);
- goto disable_clk_topcrm;
- }
-
- ret = clk_prepare_enable(priv->clk_apb);
- if (ret) {
- dev_err(&pdev->dev, "failed to enable apb clock: %d\n",
- ret);
- goto disable_clk_topcrm;
- }
-
- mutex_init(&priv->lock);
- priv->tzd = thermal_zone_of_sensor_register(&pdev->dev,
- 0, priv, &zx2967_of_thermal_ops);
-
- if (IS_ERR(priv->tzd)) {
- ret = PTR_ERR(priv->tzd);
- dev_err(&pdev->dev, "failed to register sensor: %d\n", ret);
- goto disable_clk_all;
- }
-
- if (priv->tzd->tzp->slope == 0) {
- thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd);
- dev_err(&pdev->dev, "coefficients of sensor is invalid\n");
- ret = -EINVAL;
- goto disable_clk_all;
- }
-
- priv->dev = &pdev->dev;
- platform_set_drvdata(pdev, priv);
-
- return 0;
-
-disable_clk_all:
- clk_disable_unprepare(priv->clk_apb);
-disable_clk_topcrm:
- clk_disable_unprepare(priv->clk_topcrm);
- return ret;
-}
-
-static int zx2967_thermal_exit(struct platform_device *pdev)
-{
- struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev);
-
- thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd);
- clk_disable_unprepare(priv->clk_topcrm);
- clk_disable_unprepare(priv->clk_apb);
-
- return 0;
-}
-
-static const struct of_device_id zx2967_thermal_id_table[] = {
- { .compatible = "zte,zx296718-thermal" },
- {}
-};
-MODULE_DEVICE_TABLE(of, zx2967_thermal_id_table);
-
-#ifdef CONFIG_PM_SLEEP
-static int zx2967_thermal_suspend(struct device *dev)
-{
- struct zx2967_thermal_priv *priv = dev_get_drvdata(dev);
-
- if (priv && priv->clk_topcrm)
- clk_disable_unprepare(priv->clk_topcrm);
-
- if (priv && priv->clk_apb)
- clk_disable_unprepare(priv->clk_apb);
-
- return 0;
-}
-
-static int zx2967_thermal_resume(struct device *dev)
-{
- struct zx2967_thermal_priv *priv = dev_get_drvdata(dev);
- int error;
-
- error = clk_prepare_enable(priv->clk_topcrm);
- if (error)
- return error;
-
- error = clk_prepare_enable(priv->clk_apb);
- if (error) {
- clk_disable_unprepare(priv->clk_topcrm);
- return error;
- }
-
- return 0;
-}
-#endif
-
-static SIMPLE_DEV_PM_OPS(zx2967_thermal_pm_ops,
- zx2967_thermal_suspend, zx2967_thermal_resume);
-
-static struct platform_driver zx2967_thermal_driver = {
- .probe = zx2967_thermal_probe,
- .remove = zx2967_thermal_exit,
- .driver = {
- .name = "zx2967_thermal",
- .of_match_table = zx2967_thermal_id_table,
- .pm = &zx2967_thermal_pm_ops,
- },
-};
-module_platform_driver(zx2967_thermal_driver);
-
-MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>");
-MODULE_DESCRIPTION("ZTE zx2967 thermal driver");
-MODULE_LICENSE("GPL v2");