summaryrefslogtreecommitdiff
path: root/drivers/thermal
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thermal')
-rw-r--r--drivers/thermal/Kconfig103
-rw-r--r--drivers/thermal/Makefile16
-rw-r--r--drivers/thermal/airoha_thermal.c489
-rw-r--r--drivers/thermal/amlogic_thermal.c55
-rw-r--r--drivers/thermal/armada_thermal.c20
-rw-r--r--drivers/thermal/broadcom/bcm2711_thermal.c2
-rw-r--r--drivers/thermal/broadcom/bcm2835_thermal.c62
-rw-r--r--drivers/thermal/broadcom/brcmstb_thermal.c29
-rw-r--r--drivers/thermal/broadcom/ns-thermal.c4
-rw-r--r--drivers/thermal/broadcom/sr-thermal.c1
-rw-r--r--drivers/thermal/cpufreq_cooling.c50
-rw-r--r--drivers/thermal/cpuidle_cooling.c4
-rw-r--r--drivers/thermal/da9062-thermal.c11
-rw-r--r--drivers/thermal/db8500_thermal.c2
-rw-r--r--drivers/thermal/devfreq_cooling.c51
-rw-r--r--drivers/thermal/dove_thermal.c10
-rw-r--r--drivers/thermal/gov_bang_bang.c161
-rw-r--r--drivers/thermal/gov_fair_share.c113
-rw-r--r--drivers/thermal/gov_power_allocator.c553
-rw-r--r--drivers/thermal/gov_step_wise.c153
-rw-r--r--drivers/thermal/gov_user_space.c16
-rw-r--r--drivers/thermal/hisi_thermal.c46
-rw-r--r--drivers/thermal/imx8mm_thermal.c13
-rw-r--r--drivers/thermal/imx91_thermal.c384
-rw-r--r--drivers/thermal/imx_sc_thermal.c4
-rw-r--r--drivers/thermal/imx_thermal.c105
-rw-r--r--drivers/thermal/intel/Kconfig11
-rw-r--r--drivers/thermal/intel/int340x_thermal/Kconfig7
-rw-r--r--drivers/thermal/intel/int340x_thermal/Makefile5
-rw-r--r--drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c8
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3400_thermal.c101
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3401_thermal.c17
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3402_thermal.c7
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3403_thermal.c19
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3406_thermal.c3
-rw-r--r--drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c122
-rw-r--r--drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h2
-rw-r--r--drivers/thermal/intel/int340x_thermal/platform_temperature_control.c313
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device.c136
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device.h48
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c257
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c5
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c182
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_power_floor.c127
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c89
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c137
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c284
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c256
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_wt_req.c137
-rw-r--r--drivers/thermal/intel/intel_hfi.c248
-rw-r--r--drivers/thermal/intel/intel_pch_thermal.c33
-rw-r--r--drivers/thermal/intel/intel_powerclamp.c42
-rw-r--r--drivers/thermal/intel/intel_quark_dts_thermal.c64
-rw-r--r--drivers/thermal/intel/intel_soc_dts_iosf.c203
-rw-r--r--drivers/thermal/intel/intel_soc_dts_iosf.h15
-rw-r--r--drivers/thermal/intel/intel_soc_dts_thermal.c19
-rw-r--r--drivers/thermal/intel/intel_tcc.c195
-rw-r--r--drivers/thermal/intel/intel_tcc_cooling.c39
-rw-r--r--drivers/thermal/intel/therm_throt.c10
-rw-r--r--drivers/thermal/intel/x86_pkg_temp_thermal.c62
-rw-r--r--drivers/thermal/k3_bandgap.c8
-rw-r--r--drivers/thermal/k3_j72xx_bandgap.c123
-rw-r--r--drivers/thermal/kirkwood_thermal.c10
-rw-r--r--drivers/thermal/loongson2_thermal.c218
-rw-r--r--drivers/thermal/max77620_thermal.c10
-rw-r--r--drivers/thermal/mediatek/auxadc_thermal.c8
-rw-r--r--drivers/thermal/mediatek/lvts_thermal.c925
-rw-r--r--drivers/thermal/pcie_cooling.c80
-rw-r--r--drivers/thermal/qcom/Kconfig3
-rw-r--r--drivers/thermal/qcom/lmh.c16
-rw-r--r--drivers/thermal/qcom/qcom-spmi-adc-tm5.c19
-rw-r--r--drivers/thermal/qcom/qcom-spmi-temp-alarm.c611
-rw-r--r--drivers/thermal/qcom/tsens-v0_1.c12
-rw-r--r--drivers/thermal/qcom/tsens-v1.c83
-rw-r--r--drivers/thermal/qcom/tsens-v2.c179
-rw-r--r--drivers/thermal/qcom/tsens.c79
-rw-r--r--drivers/thermal/qcom/tsens.h16
-rw-r--r--drivers/thermal/qoriq_thermal.c69
-rw-r--r--drivers/thermal/renesas/Kconfig43
-rw-r--r--drivers/thermal/renesas/Makefile7
-rw-r--r--drivers/thermal/renesas/rcar_gen3_thermal.c (renamed from drivers/thermal/rcar_gen3_thermal.c)361
-rw-r--r--drivers/thermal/renesas/rcar_thermal.c (renamed from drivers/thermal/rcar_thermal.c)22
-rw-r--r--drivers/thermal/renesas/rzg2l_thermal.c (renamed from drivers/thermal/rzg2l_thermal.c)8
-rw-r--r--drivers/thermal/renesas/rzg3e_thermal.c547
-rw-r--r--drivers/thermal/renesas/rzg3s_thermal.c272
-rw-r--r--drivers/thermal/rockchip_thermal.c306
-rw-r--r--drivers/thermal/samsung/exynos_tmu.c575
-rw-r--r--drivers/thermal/spear_thermal.c10
-rw-r--r--drivers/thermal/sprd_thermal.c19
-rw-r--r--drivers/thermal/st/st_thermal.c34
-rw-r--r--drivers/thermal/st/st_thermal.h18
-rw-r--r--drivers/thermal/st/st_thermal_memmap.c16
-rw-r--r--drivers/thermal/st/stm_thermal.c14
-rw-r--r--drivers/thermal/sun8i_thermal.c161
-rw-r--r--drivers/thermal/tegra/Makefile1
-rw-r--r--drivers/thermal/tegra/soctherm-fuse.c18
-rw-r--r--drivers/thermal/tegra/soctherm.c86
-rw-r--r--drivers/thermal/tegra/soctherm.h11
-rw-r--r--drivers/thermal/tegra/tegra-bpmp-thermal.c56
-rw-r--r--drivers/thermal/tegra/tegra114-soctherm.c209
-rw-r--r--drivers/thermal/tegra/tegra124-soctherm.c4
-rw-r--r--drivers/thermal/tegra/tegra132-soctherm.c4
-rw-r--r--drivers/thermal/tegra/tegra210-soctherm.c4
-rw-r--r--drivers/thermal/tegra/tegra30-tsensor.c59
-rw-r--r--drivers/thermal/testing/Makefile7
-rw-r--r--drivers/thermal/testing/command.c211
-rw-r--r--drivers/thermal/testing/thermal_testing.h11
-rw-r--r--drivers/thermal/testing/zone.c448
-rw-r--r--drivers/thermal/thermal-generic-adc.c83
-rw-r--r--drivers/thermal/thermal_acpi.c117
-rw-r--r--drivers/thermal/thermal_core.c1301
-rw-r--r--drivers/thermal/thermal_core.h202
-rw-r--r--drivers/thermal/thermal_debugfs.c968
-rw-r--r--drivers/thermal/thermal_debugfs.h30
-rw-r--r--drivers/thermal/thermal_helpers.c116
-rw-r--r--drivers/thermal/thermal_hwmon.c16
-rw-r--r--drivers/thermal/thermal_netlink.c445
-rw-r--r--drivers/thermal/thermal_netlink.h119
-rw-r--r--drivers/thermal/thermal_of.c305
-rw-r--r--drivers/thermal/thermal_sysfs.c391
-rw-r--r--drivers/thermal/thermal_thresholds.c244
-rw-r--r--drivers/thermal/thermal_thresholds.h19
-rw-r--r--drivers/thermal/thermal_trace.h12
-rw-r--r--drivers/thermal/thermal_trace_ipa.h52
-rw-r--r--drivers/thermal/thermal_trip.c170
-rw-r--r--drivers/thermal/ti-soc-thermal/dra752-bandgap.h4
-rw-r--r--drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h8
-rw-r--r--drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h4
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-bandgap.c6
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-bandgap.h4
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-thermal-common.c4
-rw-r--r--drivers/thermal/uniphier_thermal.c44
132 files changed, 12226 insertions, 4109 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 19a4b33cb564..b10080d61860 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -33,6 +33,22 @@ config THERMAL_STATISTICS
If in doubt, say N.
+config THERMAL_DEBUGFS
+ bool "Thermal subsystem debug support"
+ depends on DEBUG_FS
+ help
+ Say Y to allow the thermal subsystem to collect diagnostic
+ information that can be accessed via debugfs.
+
+config THERMAL_CORE_TESTING
+ tristate "Thermal core testing facility"
+ depends on DEBUG_FS
+ help
+ Say Y to add a debugfs-based thermal core testing facility.
+ It allows test thermal zones to be created and populated
+ with trip points in order to exercise the thermal core
+ functionality in a controlled way.
+
config THERMAL_EMERGENCY_POWEROFF_DELAY_MS
int "Emergency poweroff delay in milli-seconds"
default 0
@@ -76,21 +92,6 @@ config THERMAL_OF
Say 'Y' here if you need to build thermal infrastructure
based on device tree.
-config THERMAL_ACPI
- depends on ACPI
- bool
-
-config THERMAL_WRITABLE_TRIPS
- bool "Enable writable trip points"
- help
- This option allows the system integrator to choose whether
- trip temperatures can be changed from userspace. The
- writable trips need to be specified when setting up the
- thermal zone but the choice here takes precedence.
-
- Say 'Y' here if you would like to allow userspace tools to
- change trip temperatures.
-
choice
prompt "Default Thermal governor"
default THERMAL_DEFAULT_GOV_STEP_WISE
@@ -219,6 +220,15 @@ config DEVFREQ_THERMAL
If you want this support, you should say Y here.
+config PCIE_THERMAL
+ bool "PCIe cooling support"
+ depends on PCIEPORTBUS
+ help
+ This implements PCIe cooling mechanism through bandwidth reduction
+ for PCIe devices.
+
+ If you want this support, you should say Y here.
+
config THERMAL_EMULATION
bool "Thermal emulation mode support"
help
@@ -247,7 +257,7 @@ config HISI_THERMAL
depends on ARCH_HISI || COMPILE_TEST
depends on HAS_IOMEM
depends on OF
- default y
+ default ARCH_HISI
help
Enable this to plug hisilicon's thermal sensor driver into the Linux
thermal framework. cpufreq is used as the cooling device to throttle
@@ -286,6 +296,16 @@ config IMX8MM_THERMAL
cpufreq is used as the cooling device to throttle CPUs when the passive
trip is crossed.
+config IMX91_THERMAL
+ tristate "Temperature sensor driver for NXP i.MX91 SoC"
+ depends on ARCH_MXC || COMPILE_TEST
+ depends on OF
+ help
+ Include one sensor and six comparators. Each of them compares the
+ temperature value (from the sensor) against the programmable
+ threshold values. The direction of the comparison is configurable
+ (greater / lesser than).
+
config K3_THERMAL
tristate "Texas Instruments K3 thermal support"
depends on ARCH_K3 || COMPILE_TEST
@@ -317,6 +337,15 @@ config QORIQ_THERMAL
cpufreq is used as the cooling device to throttle CPUs when the
passive trip is crossed.
+config AIROHA_THERMAL
+ tristate "Airoha thermal sensor driver"
+ depends on ARCH_AIROHA || COMPILE_TEST
+ depends on MFD_SYSCON
+ depends on OF
+ help
+ Enable this to plug the Airoha thermal sensor driver into the Linux
+ thermal framework.
+
config SPEAR_THERMAL
tristate "SPEAr thermal sensor driver"
depends on PLAT_SPEAR || COMPILE_TEST
@@ -351,32 +380,6 @@ config ROCKCHIP_THERMAL
trip point. Cpufreq is used as the cooling device and will throttle
CPUs when the Temperature crosses the passive trip point.
-config RCAR_THERMAL
- tristate "Renesas R-Car thermal driver"
- depends on ARCH_RENESAS || COMPILE_TEST
- depends on HAS_IOMEM
- help
- Enable this to plug the R-Car thermal sensor driver into the Linux
- thermal framework.
-
-config RCAR_GEN3_THERMAL
- 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 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
tristate "Temperature sensor on Marvell Kirkwood SoCs"
depends on MACH_KIRKWOOD || COMPILE_TEST
@@ -463,10 +466,12 @@ source "drivers/thermal/samsung/Kconfig"
endmenu
menu "STMicroelectronics thermal drivers"
-depends on (ARCH_STI || ARCH_STM32) && OF
+depends on (ARCH_STI || ARCH_STM32) && THERMAL_OF
source "drivers/thermal/st/Kconfig"
endmenu
+source "drivers/thermal/renesas/Kconfig"
+
source "drivers/thermal/tegra/Kconfig"
config GENERIC_ADC_THERMAL
@@ -510,4 +515,16 @@ config KHADAS_MCU_FAN_THERMAL
If you say yes here you get support for the FAN controlled
by the Microcontroller found on the Khadas VIM boards.
+config LOONGSON2_THERMAL
+ tristate "Loongson-2 SoC series thermal driver"
+ depends on LOONGARCH || COMPILE_TEST
+ depends on OF
+ help
+ Support for Thermal driver found on Loongson-2 SoC series platforms.
+ The thermal driver realizes get_temp and set_trips function, which
+ are used to obtain the temperature of the current node and set the
+ temperature range to trigger the interrupt. When the input temperature
+ is higher than the high temperature threshold or lower than the low
+ temperature threshold, the interrupt will occur.
+
endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 058664bc3ec0..bb21e7ea7fc6 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -6,14 +6,16 @@ CFLAGS_thermal_core.o := -I$(src)
obj-$(CONFIG_THERMAL) += thermal_sys.o
thermal_sys-y += thermal_core.o thermal_sysfs.o
thermal_sys-y += thermal_trip.o thermal_helpers.o
+thermal_sys-y += thermal_thresholds.o
# netlink interface to manage the thermal framework
thermal_sys-$(CONFIG_THERMAL_NETLINK) += thermal_netlink.o
+thermal_sys-$(CONFIG_THERMAL_DEBUGFS) += thermal_debugfs.o
+
# interface to/from other layers providing sensors
thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o
thermal_sys-$(CONFIG_THERMAL_OF) += thermal_of.o
-thermal_sys-$(CONFIG_THERMAL_ACPI) += thermal_acpi.o
# governors
CFLAGS_gov_power_allocator.o := -I$(src)
@@ -30,24 +32,26 @@ thermal_sys-$(CONFIG_CPU_IDLE_THERMAL) += cpuidle_cooling.o
# devfreq cooling
thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
+thermal_sys-$(CONFIG_PCIE_THERMAL) += pcie_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
+obj-$(CONFIG_AIROHA_THERMAL) += airoha_thermal.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
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-y += renesas/
obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o
obj-y += samsung/
-obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o
+obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o
obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o
obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o
obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o
obj-$(CONFIG_IMX_SC_THERMAL) += imx_sc_thermal.o
obj-$(CONFIG_IMX8MM_THERMAL) += imx8mm_thermal.o
+obj-$(CONFIG_IMX91_THERMAL) += imx91_thermal.o
obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o
obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o
obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o
@@ -63,3 +67,5 @@ 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
+obj-$(CONFIG_LOONGSON2_THERMAL) += loongson2_thermal.o
+obj-$(CONFIG_THERMAL_CORE_TESTING) += testing/
diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c
new file mode 100644
index 000000000000..b9fd6bfc88e5
--- /dev/null
+++ b/drivers/thermal/airoha_thermal.c
@@ -0,0 +1,489 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/module.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+/* SCU regs */
+#define EN7581_PLLRG_PROTECT 0x268
+#define EN7581_PWD_TADC 0x2ec
+#define EN7581_MUX_TADC GENMASK(3, 1)
+#define EN7581_DOUT_TADC 0x2f8
+#define EN7581_DOUT_TADC_MASK GENMASK(15, 0)
+
+/* PTP_THERMAL regs */
+#define EN7581_TEMPMONCTL0 0x800
+#define EN7581_SENSE3_EN BIT(3)
+#define EN7581_SENSE2_EN BIT(2)
+#define EN7581_SENSE1_EN BIT(1)
+#define EN7581_SENSE0_EN BIT(0)
+#define EN7581_TEMPMONCTL1 0x804
+/* period unit calculated in BUS clock * 256 scaling-up */
+#define EN7581_PERIOD_UNIT GENMASK(9, 0)
+#define EN7581_TEMPMONCTL2 0x808
+#define EN7581_FILT_INTERVAL GENMASK(25, 16)
+#define EN7581_SEN_INTERVAL GENMASK(9, 0)
+#define EN7581_TEMPMONINT 0x80C
+#define EN7581_STAGE3_INT_EN BIT(31)
+#define EN7581_STAGE2_INT_EN BIT(30)
+#define EN7581_STAGE1_INT_EN BIT(29)
+#define EN7581_FILTER_INT_EN_3 BIT(28)
+#define EN7581_IMMD_INT_EN3 BIT(27)
+#define EN7581_NOHOTINTEN3 BIT(26)
+#define EN7581_HOFSINTEN3 BIT(25)
+#define EN7581_LOFSINTEN3 BIT(24)
+#define EN7581_HINTEN3 BIT(23)
+#define EN7581_CINTEN3 BIT(22)
+#define EN7581_FILTER_INT_EN_2 BIT(21)
+#define EN7581_FILTER_INT_EN_1 BIT(20)
+#define EN7581_FILTER_INT_EN_0 BIT(19)
+#define EN7581_IMMD_INT_EN2 BIT(18)
+#define EN7581_IMMD_INT_EN1 BIT(17)
+#define EN7581_IMMD_INT_EN0 BIT(16)
+#define EN7581_TIME_OUT_INT_EN BIT(15)
+#define EN7581_NOHOTINTEN2 BIT(14)
+#define EN7581_HOFSINTEN2 BIT(13)
+#define EN7581_LOFSINTEN2 BIT(12)
+#define EN7581_HINTEN2 BIT(11)
+#define EN7581_CINTEN2 BIT(10)
+#define EN7581_NOHOTINTEN1 BIT(9)
+#define EN7581_HOFSINTEN1 BIT(8)
+#define EN7581_LOFSINTEN1 BIT(7)
+#define EN7581_HINTEN1 BIT(6)
+#define EN7581_CINTEN1 BIT(5)
+#define EN7581_NOHOTINTEN0 BIT(4)
+/* Similar to COLD and HOT also these seems to be swapped in documentation */
+#define EN7581_LOFSINTEN0 BIT(3) /* In documentation: BIT(2) */
+#define EN7581_HOFSINTEN0 BIT(2) /* In documentation: BIT(3) */
+/* It seems documentation have these swapped as the HW
+ * - Fire BIT(1) when lower than EN7581_COLD_THRE
+ * - Fire BIT(0) and BIT(5) when higher than EN7581_HOT2NORMAL_THRE or
+ * EN7581_HOT_THRE
+ */
+#define EN7581_CINTEN0 BIT(1) /* In documentation: BIT(0) */
+#define EN7581_HINTEN0 BIT(0) /* In documentation: BIT(1) */
+#define EN7581_TEMPMONINTSTS 0x810
+#define EN7581_STAGE3_INT_STAT BIT(31)
+#define EN7581_STAGE2_INT_STAT BIT(30)
+#define EN7581_STAGE1_INT_STAT BIT(29)
+#define EN7581_FILTER_INT_STAT_3 BIT(28)
+#define EN7581_IMMD_INT_STS3 BIT(27)
+#define EN7581_NOHOTINTSTS3 BIT(26)
+#define EN7581_HOFSINTSTS3 BIT(25)
+#define EN7581_LOFSINTSTS3 BIT(24)
+#define EN7581_HINTSTS3 BIT(23)
+#define EN7581_CINTSTS3 BIT(22)
+#define EN7581_FILTER_INT_STAT_2 BIT(21)
+#define EN7581_FILTER_INT_STAT_1 BIT(20)
+#define EN7581_FILTER_INT_STAT_0 BIT(19)
+#define EN7581_IMMD_INT_STS2 BIT(18)
+#define EN7581_IMMD_INT_STS1 BIT(17)
+#define EN7581_IMMD_INT_STS0 BIT(16)
+#define EN7581_TIME_OUT_INT_STAT BIT(15)
+#define EN7581_NOHOTINTSTS2 BIT(14)
+#define EN7581_HOFSINTSTS2 BIT(13)
+#define EN7581_LOFSINTSTS2 BIT(12)
+#define EN7581_HINTSTS2 BIT(11)
+#define EN7581_CINTSTS2 BIT(10)
+#define EN7581_NOHOTINTSTS1 BIT(9)
+#define EN7581_HOFSINTSTS1 BIT(8)
+#define EN7581_LOFSINTSTS1 BIT(7)
+#define EN7581_HINTSTS1 BIT(6)
+#define EN7581_CINTSTS1 BIT(5)
+#define EN7581_NOHOTINTSTS0 BIT(4)
+/* Similar to COLD and HOT also these seems to be swapped in documentation */
+#define EN7581_LOFSINTSTS0 BIT(3) /* In documentation: BIT(2) */
+#define EN7581_HOFSINTSTS0 BIT(2) /* In documentation: BIT(3) */
+/* It seems documentation have these swapped as the HW
+ * - Fire BIT(1) when lower than EN7581_COLD_THRE
+ * - Fire BIT(0) and BIT(5) when higher than EN7581_HOT2NORMAL_THRE or
+ * EN7581_HOT_THRE
+ *
+ * To clear things, we swap the define but we keep them documented here.
+ */
+#define EN7581_CINTSTS0 BIT(1) /* In documentation: BIT(0) */
+#define EN7581_HINTSTS0 BIT(0) /* In documentation: BIT(1)*/
+/* Monitor will take the bigger threshold between HOT2NORMAL and HOT
+ * and will fire both HOT2NORMAL and HOT interrupt when higher than the 2
+ *
+ * It has also been observed that not setting HOT2NORMAL makes the monitor
+ * treat COLD threshold as HOT2NORMAL.
+ */
+#define EN7581_TEMPH2NTHRE 0x824
+/* It seems HOT2NORMAL is actually NORMAL2HOT */
+#define EN7581_HOT2NORMAL_THRE GENMASK(11, 0)
+#define EN7581_TEMPHTHRE 0x828
+#define EN7581_HOT_THRE GENMASK(11, 0)
+/* Monitor will use this as HOT2NORMAL (fire interrupt when lower than...)*/
+#define EN7581_TEMPCTHRE 0x82c
+#define EN7581_COLD_THRE GENMASK(11, 0)
+/* Also LOW and HIGH offset register are swapped */
+#define EN7581_TEMPOFFSETL 0x830 /* In documentation: 0x834 */
+#define EN7581_LOW_OFFSET GENMASK(11, 0)
+#define EN7581_TEMPOFFSETH 0x834 /* In documentation: 0x830 */
+#define EN7581_HIGH_OFFSET GENMASK(11, 0)
+#define EN7581_TEMPMSRCTL0 0x838
+#define EN7581_MSRCTL3 GENMASK(11, 9)
+#define EN7581_MSRCTL2 GENMASK(8, 6)
+#define EN7581_MSRCTL1 GENMASK(5, 3)
+#define EN7581_MSRCTL0 GENMASK(2, 0)
+#define EN7581_TEMPADCVALIDADDR 0x878
+#define EN7581_ADC_VALID_ADDR GENMASK(31, 0)
+#define EN7581_TEMPADCVOLTADDR 0x87c
+#define EN7581_ADC_VOLT_ADDR GENMASK(31, 0)
+#define EN7581_TEMPRDCTRL 0x880
+/*
+ * NOTICE: AHB have this set to 0 by default. Means that
+ * the same addr is used for ADC volt and valid reading.
+ * In such case, VALID ADDR is used and volt addr is ignored.
+ */
+#define EN7581_RD_CTRL_DIFF BIT(0)
+#define EN7581_TEMPADCVALIDMASK 0x884
+#define EN7581_ADV_RD_VALID_POLARITY BIT(5)
+#define EN7581_ADV_RD_VALID_POS GENMASK(4, 0)
+#define EN7581_TEMPADCVOLTAGESHIFT 0x888
+#define EN7581_ADC_VOLTAGE_SHIFT GENMASK(4, 0)
+/*
+ * Same values for each CTL.
+ * Can operate in:
+ * - 1 sample
+ * - 2 sample and make average of them
+ * - 4,6,10,16 sample, drop max and min and make average of them
+ */
+#define EN7581_MSRCTL_1SAMPLE 0x0
+#define EN7581_MSRCTL_AVG2SAMPLE 0x1
+#define EN7581_MSRCTL_4SAMPLE_MAX_MIX_AVG2 0x2
+#define EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4 0x3
+#define EN7581_MSRCTL_10SAMPLE_MAX_MIX_AVG8 0x4
+#define EN7581_MSRCTL_18SAMPLE_MAX_MIX_AVG16 0x5
+#define EN7581_TEMPAHBPOLL 0x840
+#define EN7581_ADC_POLL_INTVL GENMASK(31, 0)
+/* PTPSPARE0,2 reg are used to store efuse info for calibrated temp offset */
+#define EN7581_EFUSE_TEMP_OFFSET_REG 0xf20 /* PTPSPARE0 */
+#define EN7581_EFUSE_TEMP_OFFSET GENMASK(31, 16)
+#define EN7581_PTPSPARE1 0xf24 /* PTPSPARE1 */
+#define EN7581_EFUSE_TEMP_CPU_SENSOR_REG 0xf28 /* PTPSPARE2 */
+
+#define EN7581_SLOPE_X100_DIO_DEFAULT 5645
+#define EN7581_SLOPE_X100_DIO_AVS 5645
+
+#define EN7581_INIT_TEMP_CPK_X10 300
+#define EN7581_INIT_TEMP_FTK_X10 620
+#define EN7581_INIT_TEMP_NONK_X10 550
+
+#define EN7581_SCU_THERMAL_PROTECT_KEY 0x12
+#define EN7581_SCU_THERMAL_MUX_DIODE1 0x7
+
+/* Convert temp to raw value as read from ADC ((((temp / 100) - init) * slope) / 1000) + offset */
+#define TEMP_TO_RAW(priv, temp) ((((((temp) / 100) - (priv)->init_temp) * \
+ (priv)->default_slope) / 1000) + \
+ (priv)->default_offset)
+
+/* Convert raw to temp ((((temp - offset) * 1000) / slope + init) * 100) */
+#define RAW_TO_TEMP(priv, raw) (((((raw) - (priv)->default_offset) * 1000) / \
+ (priv)->default_slope + \
+ (priv)->init_temp) * 100)
+
+#define AIROHA_MAX_SAMPLES 6
+
+struct airoha_thermal_priv {
+ void __iomem *base;
+ struct regmap *chip_scu;
+ struct resource scu_adc_res;
+
+ struct thermal_zone_device *tz;
+ int init_temp;
+ int default_slope;
+ int default_offset;
+};
+
+static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv)
+{
+ u32 val;
+
+ regmap_read(priv->chip_scu, EN7581_DOUT_TADC, &val);
+ return FIELD_GET(EN7581_DOUT_TADC_MASK, val);
+}
+
+static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv)
+{
+ u32 adc_mux, pllrg;
+
+ /* Save PLLRG current value */
+ regmap_read(priv->chip_scu, EN7581_PLLRG_PROTECT, &pllrg);
+
+ /* Give access to thermal regs */
+ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, EN7581_SCU_THERMAL_PROTECT_KEY);
+ adc_mux = FIELD_PREP(EN7581_MUX_TADC, EN7581_SCU_THERMAL_MUX_DIODE1);
+ regmap_write(priv->chip_scu, EN7581_PWD_TADC, adc_mux);
+
+ /* Restore PLLRG value on exit */
+ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg);
+}
+
+static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+ int min_value, max_value, avg_value, value;
+ int i;
+
+ avg_value = 0;
+ min_value = INT_MAX;
+ max_value = INT_MIN;
+
+ for (i = 0; i < AIROHA_MAX_SAMPLES; i++) {
+ value = airoha_get_thermal_ADC(priv);
+ min_value = min(value, min_value);
+ max_value = max(value, max_value);
+ avg_value += value;
+ }
+
+ /* Drop min and max and average for the remaining sample */
+ avg_value -= (min_value + max_value);
+ avg_value /= AIROHA_MAX_SAMPLES - 2;
+
+ *temp = RAW_TO_TEMP(priv, avg_value);
+ return 0;
+}
+
+static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low,
+ int high)
+{
+ struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+ bool enable_monitor = false;
+
+ if (high != INT_MAX) {
+ /* Validate high and clamp it a supported value */
+ high = clamp_t(int, high, RAW_TO_TEMP(priv, 0),
+ RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK)));
+
+ /* We offset the high temp of 1°C to trigger correct event */
+ writel(TEMP_TO_RAW(priv, high) >> 4,
+ priv->base + EN7581_TEMPOFFSETH);
+
+ enable_monitor = true;
+ }
+
+ if (low != -INT_MAX) {
+ /* Validate low and clamp it to a supported value */
+ low = clamp_t(int, high, RAW_TO_TEMP(priv, 0),
+ RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK)));
+
+ /* We offset the low temp of 1°C to trigger correct event */
+ writel(TEMP_TO_RAW(priv, low) >> 4,
+ priv->base + EN7581_TEMPOFFSETL);
+
+ enable_monitor = true;
+ }
+
+ /* Enable sensor 0 monitor after trip are set */
+ if (enable_monitor)
+ writel(EN7581_SENSE0_EN, priv->base + EN7581_TEMPMONCTL0);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops thdev_ops = {
+ .get_temp = airoha_thermal_get_temp,
+ .set_trips = airoha_thermal_set_trips,
+};
+
+static irqreturn_t airoha_thermal_irq(int irq, void *data)
+{
+ struct airoha_thermal_priv *priv = data;
+ enum thermal_notify_event event;
+ bool update = false;
+ u32 status;
+
+ status = readl(priv->base + EN7581_TEMPMONINTSTS);
+ switch (status & (EN7581_HOFSINTSTS0 | EN7581_LOFSINTSTS0)) {
+ case EN7581_HOFSINTSTS0:
+ event = THERMAL_TRIP_VIOLATED;
+ update = true;
+ break;
+ case EN7581_LOFSINTSTS0:
+ event = THERMAL_EVENT_UNSPECIFIED;
+ update = true;
+ break;
+ default:
+ /* Should be impossible as we enable only these Interrupt */
+ break;
+ }
+
+ /* Reset Interrupt */
+ writel(status, priv->base + EN7581_TEMPMONINTSTS);
+
+ if (update)
+ thermal_zone_device_update(priv->tz, event);
+
+ return IRQ_HANDLED;
+}
+
+static void airoha_thermal_setup_adc_val(struct device *dev,
+ struct airoha_thermal_priv *priv)
+{
+ u32 efuse_calib_info, cpu_sensor;
+
+ /* Setup thermal sensor to ADC mode and setup the mux to DIODE1 */
+ airoha_init_thermal_ADC_mode(priv);
+ /* sleep 10 ms for ADC to enable */
+ usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC);
+
+ efuse_calib_info = readl(priv->base + EN7581_EFUSE_TEMP_OFFSET_REG);
+ if (efuse_calib_info) {
+ priv->default_offset = FIELD_GET(EN7581_EFUSE_TEMP_OFFSET, efuse_calib_info);
+ /* Different slope are applied if the sensor is used for CPU or for package */
+ cpu_sensor = readl(priv->base + EN7581_EFUSE_TEMP_CPU_SENSOR_REG);
+ if (cpu_sensor) {
+ priv->default_slope = EN7581_SLOPE_X100_DIO_DEFAULT;
+ priv->init_temp = EN7581_INIT_TEMP_FTK_X10;
+ } else {
+ priv->default_slope = EN7581_SLOPE_X100_DIO_AVS;
+ priv->init_temp = EN7581_INIT_TEMP_CPK_X10;
+ }
+ } else {
+ priv->default_offset = airoha_get_thermal_ADC(priv);
+ priv->default_slope = EN7581_SLOPE_X100_DIO_DEFAULT;
+ priv->init_temp = EN7581_INIT_TEMP_NONK_X10;
+ dev_info(dev, "missing thermal calibration EFUSE, using non calibrated value\n");
+ }
+}
+
+static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv)
+{
+ /* Set measure mode */
+ writel(FIELD_PREP(EN7581_MSRCTL0, EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4),
+ priv->base + EN7581_TEMPMSRCTL0);
+
+ /*
+ * Configure ADC valid reading addr
+ * The AHB temp monitor system doesn't have direct access to the
+ * thermal sensor. It does instead work by providing various
+ * addresses to configure how to access and setup an ADC for the
+ * sensor. EN7581 supports only one sensor hence the
+ * implementation is greatly simplified but the AHB supports
+ * up to 4 different sensors from the same ADC that can be
+ * switched by tuning the ADC mux or writing address.
+ *
+ * We set valid instead of volt as we don't enable valid/volt
+ * split reading and AHB read valid addr in such case.
+ */
+ writel(priv->scu_adc_res.start + EN7581_DOUT_TADC,
+ priv->base + EN7581_TEMPADCVALIDADDR);
+
+ /*
+ * Configure valid bit on a fake value of bit 16. The ADC outputs
+ * max of 2 bytes for voltage.
+ */
+ writel(FIELD_PREP(EN7581_ADV_RD_VALID_POS, 16),
+ priv->base + EN7581_TEMPADCVALIDMASK);
+
+ /*
+ * AHB supports max 12 bytes for ADC voltage. Shift the read
+ * value 4 bit to the right. Precision lost by this is minimal
+ * in the order of half a °C and is acceptable in the context
+ * of triggering interrupt in critical condition.
+ */
+ writel(FIELD_PREP(EN7581_ADC_VOLTAGE_SHIFT, 4),
+ priv->base + EN7581_TEMPADCVOLTAGESHIFT);
+
+ /* BUS clock is 300MHz counting unit is 3 * 68.64 * 256 = 52.715us */
+ writel(FIELD_PREP(EN7581_PERIOD_UNIT, 3),
+ priv->base + EN7581_TEMPMONCTL1);
+
+ /*
+ * filt interval is 1 * 52.715us = 52.715us,
+ * sen interval is 379 * 52.715us = 19.97ms
+ */
+ writel(FIELD_PREP(EN7581_FILT_INTERVAL, 1) |
+ FIELD_PREP(EN7581_FILT_INTERVAL, 379),
+ priv->base + EN7581_TEMPMONCTL2);
+
+ /* AHB poll is set to 146 * 68.64 = 10.02us */
+ writel(FIELD_PREP(EN7581_ADC_POLL_INTVL, 146),
+ priv->base + EN7581_TEMPAHBPOLL);
+}
+
+static int airoha_thermal_probe(struct platform_device *pdev)
+{
+ struct airoha_thermal_priv *priv;
+ struct device_node *chip_scu_np;
+ struct device *dev = &pdev->dev;
+ int irq, 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);
+
+ chip_scu_np = of_parse_phandle(dev->of_node, "airoha,chip-scu", 0);
+ if (!chip_scu_np)
+ return -EINVAL;
+
+ priv->chip_scu = syscon_node_to_regmap(chip_scu_np);
+ if (IS_ERR(priv->chip_scu))
+ return PTR_ERR(priv->chip_scu);
+
+ of_address_to_resource(chip_scu_np, 0, &priv->scu_adc_res);
+ of_node_put(chip_scu_np);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ airoha_thermal_irq, IRQF_ONESHOT,
+ pdev->name, priv);
+ if (ret) {
+ dev_err(dev, "Can't get interrupt working.\n");
+ return ret;
+ }
+
+ airoha_thermal_setup_monitor(priv);
+ airoha_thermal_setup_adc_val(dev, priv);
+
+ /* register of thermal sensor and get info from DT */
+ priv->tz = devm_thermal_of_zone_register(dev, 0, priv, &thdev_ops);
+ if (IS_ERR(priv->tz)) {
+ dev_err(dev, "register thermal zone sensor failed\n");
+ return PTR_ERR(priv->tz);
+ }
+
+ platform_set_drvdata(pdev, priv);
+
+ /* Enable LOW and HIGH interrupt */
+ writel(EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0,
+ priv->base + EN7581_TEMPMONINT);
+
+ return 0;
+}
+
+static const struct of_device_id airoha_thermal_match[] = {
+ { .compatible = "airoha,en7581-thermal" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, airoha_thermal_match);
+
+static struct platform_driver airoha_thermal_driver = {
+ .driver = {
+ .name = "airoha-thermal",
+ .of_match_table = airoha_thermal_match,
+ },
+ .probe = airoha_thermal_probe,
+};
+
+module_platform_driver(airoha_thermal_driver);
+
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("Airoha thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c
index 756b218880a7..5448d772db12 100644
--- a/drivers/thermal/amlogic_thermal.c
+++ b/drivers/thermal/amlogic_thermal.c
@@ -7,10 +7,10 @@
*
* Register value to celsius temperature formulas:
* Read_Val m * U
- * U = ---------, Uptat = ---------
+ * U = ---------, uptat = ---------
* 2^16 1 + n * U
*
- * Temperature = A * ( Uptat + u_efuse / 2^16 )- B
+ * Temperature = A * ( uptat + u_efuse / 2^16 )- B
*
* A B m n : calibration parameters
* u_efuse : fused calibration value, it's a signed 16 bits value
@@ -22,8 +22,6 @@
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_address.h>
-#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/thermal.h>
@@ -114,7 +112,7 @@ static int amlogic_thermal_code_to_millicelsius(struct amlogic_thermal *pdata,
const struct amlogic_thermal_soc_calib_data *param =
pdata->data->calibration_parameters;
int temp;
- s64 factor, Uptat, uefuse;
+ s64 factor, uptat, uefuse;
uefuse = pdata->trim_info & TSENSOR_TRIM_SIGN_MASK ?
~(pdata->trim_info & TSENSOR_TRIM_TEMP_MASK) + 1 :
@@ -123,12 +121,12 @@ static int amlogic_thermal_code_to_millicelsius(struct amlogic_thermal *pdata,
factor = param->n * temp_code;
factor = div_s64(factor, 100);
- Uptat = temp_code * param->m;
- Uptat = div_s64(Uptat, 100);
- Uptat = Uptat * BIT(16);
- Uptat = div_s64(Uptat, BIT(16) + factor);
+ uptat = temp_code * param->m;
+ uptat = div_s64(uptat, 100);
+ uptat = uptat * BIT(16);
+ uptat = div_s64(uptat, BIT(16) + factor);
- temp = (Uptat + uefuse) * param->A;
+ temp = (uptat + uefuse) * param->A;
temp = div_s64(temp, BIT(16));
temp = (temp - param->B) * 100;
@@ -169,13 +167,11 @@ static int amlogic_thermal_enable(struct amlogic_thermal *data)
return 0;
}
-static int amlogic_thermal_disable(struct amlogic_thermal *data)
+static void amlogic_thermal_disable(struct amlogic_thermal *data)
{
regmap_update_bits(data->regmap, TSENSOR_CFG_REG1,
TSENSOR_CFG_REG1_ENABLE, 0);
clk_disable_unprepare(data->clk);
-
- return 0;
}
static int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
@@ -224,6 +220,12 @@ static const struct amlogic_thermal_data amlogic_thermal_g12a_ddr_param = {
.regmap_config = &amlogic_thermal_regmap_config_g12a,
};
+static const struct amlogic_thermal_data amlogic_thermal_a1_cpu_param = {
+ .u_efuse_off = 0x114,
+ .calibration_parameters = &amlogic_thermal_g12a,
+ .regmap_config = &amlogic_thermal_regmap_config_g12a,
+};
+
static const struct of_device_id of_amlogic_thermal_match[] = {
{
.compatible = "amlogic,g12a-ddr-thermal",
@@ -233,6 +235,10 @@ static const struct of_device_id of_amlogic_thermal_match[] = {
.compatible = "amlogic,g12a-cpu-thermal",
.data = &amlogic_thermal_g12a_cpu_param,
},
+ {
+ .compatible = "amlogic,a1-cpu-thermal",
+ .data = &amlogic_thermal_a1_cpu_param,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_amlogic_thermal_match);
@@ -293,38 +299,41 @@ static int amlogic_thermal_probe(struct platform_device *pdev)
return ret;
}
-static int amlogic_thermal_remove(struct platform_device *pdev)
+static void amlogic_thermal_remove(struct platform_device *pdev)
{
struct amlogic_thermal *data = platform_get_drvdata(pdev);
- return amlogic_thermal_disable(data);
+ amlogic_thermal_disable(data);
}
-static int __maybe_unused amlogic_thermal_suspend(struct device *dev)
+static int amlogic_thermal_suspend(struct device *dev)
{
struct amlogic_thermal *data = dev_get_drvdata(dev);
- return amlogic_thermal_disable(data);
+ amlogic_thermal_disable(data);
+
+ return 0;
}
-static int __maybe_unused amlogic_thermal_resume(struct device *dev)
+static int amlogic_thermal_resume(struct device *dev)
{
struct amlogic_thermal *data = dev_get_drvdata(dev);
return amlogic_thermal_enable(data);
}
-static SIMPLE_DEV_PM_OPS(amlogic_thermal_pm_ops,
- amlogic_thermal_suspend, amlogic_thermal_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(amlogic_thermal_pm_ops,
+ amlogic_thermal_suspend,
+ amlogic_thermal_resume);
static struct platform_driver amlogic_thermal_driver = {
.driver = {
.name = "amlogic_thermal",
- .pm = &amlogic_thermal_pm_ops,
+ .pm = pm_ptr(&amlogic_thermal_pm_ops),
.of_match_table = of_amlogic_thermal_match,
},
- .probe = amlogic_thermal_probe,
- .remove = amlogic_thermal_remove,
+ .probe = amlogic_thermal_probe,
+ .remove = amlogic_thermal_remove,
};
module_platform_driver(amlogic_thermal_driver);
diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c
index 9f6dc4fc9112..c2fbdb534f61 100644
--- a/drivers/thermal/armada_thermal.c
+++ b/drivers/thermal/armada_thermal.c
@@ -408,7 +408,7 @@ static int armada_get_temp_legacy(struct thermal_zone_device *thermal,
return ret;
}
-static struct thermal_zone_device_ops legacy_ops = {
+static const struct thermal_zone_device_ops legacy_ops = {
.get_temp = armada_get_temp_legacy,
};
@@ -763,7 +763,6 @@ static void armada_set_sane_name(struct platform_device *pdev,
struct armada_thermal_priv *priv)
{
const char *name = dev_name(&pdev->dev);
- char *insane_char;
if (strlen(name) > THERMAL_NAME_LENGTH) {
/*
@@ -781,12 +780,8 @@ static void armada_set_sane_name(struct platform_device *pdev,
/* Save the name locally */
strscpy(priv->zone_name, name, THERMAL_NAME_LENGTH);
- /* Then check there are no '-' or hwmon core will complain */
- do {
- insane_char = strpbrk(priv->zone_name, "-");
- if (insane_char)
- *insane_char = '_';
- } while (insane_char);
+ /* Then ensure there are no '-' or hwmon core will complain */
+ strreplace(priv->zone_name, '-', '_');
}
/*
@@ -876,8 +871,9 @@ static int armada_thermal_probe(struct platform_device *pdev)
/* Wait the sensors to be valid */
armada_wait_sensor_validity(priv);
- tz = thermal_zone_device_register(priv->zone_name, 0, 0, priv,
- &legacy_ops, NULL, 0, 0);
+ tz = thermal_tripless_zone_device_register(priv->zone_name,
+ priv, &legacy_ops,
+ NULL);
if (IS_ERR(tz)) {
dev_err(&pdev->dev,
"Failed to register thermal zone device\n");
@@ -964,14 +960,12 @@ static int armada_thermal_probe(struct platform_device *pdev)
return 0;
}
-static int armada_thermal_exit(struct platform_device *pdev)
+static void armada_thermal_exit(struct platform_device *pdev)
{
struct armada_drvdata *drvdata = platform_get_drvdata(pdev);
if (drvdata->type == LEGACY)
thermal_zone_device_unregister(drvdata->data.tz);
-
- return 0;
}
static struct platform_driver armada_thermal_driver = {
diff --git a/drivers/thermal/broadcom/bcm2711_thermal.c b/drivers/thermal/broadcom/bcm2711_thermal.c
index c243a76a3471..03ac2d02e9d4 100644
--- a/drivers/thermal/broadcom/bcm2711_thermal.c
+++ b/drivers/thermal/broadcom/bcm2711_thermal.c
@@ -15,8 +15,8 @@
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
-#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/thermal.h>
diff --git a/drivers/thermal/broadcom/bcm2835_thermal.c b/drivers/thermal/broadcom/bcm2835_thermal.c
index 3acc9288b310..685a5aee5e0d 100644
--- a/drivers/thermal/broadcom/bcm2835_thermal.c
+++ b/drivers/thermal/broadcom/bcm2835_thermal.c
@@ -163,6 +163,7 @@ MODULE_DEVICE_TABLE(of, bcm2835_thermal_of_match_table);
static int bcm2835_thermal_probe(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
const struct of_device_id *match;
struct thermal_zone_device *tz;
struct bcm2835_thermal_data *data;
@@ -170,12 +171,11 @@ static int bcm2835_thermal_probe(struct platform_device *pdev)
u32 val;
unsigned long rate;
- data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- match = of_match_device(bcm2835_thermal_of_match_table,
- &pdev->dev);
+ match = of_match_device(bcm2835_thermal_of_match_table, dev);
if (!match)
return -EINVAL;
@@ -185,34 +185,20 @@ static int bcm2835_thermal_probe(struct platform_device *pdev)
return err;
}
- data->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(data->clk)) {
- err = PTR_ERR(data->clk);
- if (err != -EPROBE_DEFER)
- dev_err(&pdev->dev, "Could not get clk: %d\n", err);
- return err;
- }
-
- err = clk_prepare_enable(data->clk);
- if (err)
- return err;
+ data->clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(data->clk))
+ return dev_err_probe(dev, PTR_ERR(data->clk), "Could not get clk\n");
rate = clk_get_rate(data->clk);
if ((rate < 1920000) || (rate > 5000000))
- dev_warn(&pdev->dev,
- "Clock %pCn running at %lu Hz is outside of the recommended range: 1.92 to 5MHz\n",
+ dev_warn(dev,
+ "Clock %pC running at %lu Hz is outside of the recommended range: 1.92 to 5MHz\n",
data->clk, rate);
/* register of thermal sensor and get info from DT */
- 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,
- "Failed to register the thermal device: %d\n",
- err);
- goto err_clk;
- }
+ tz = devm_thermal_of_zone_register(dev, 0, data, &bcm2835_thermal_ops);
+ if (IS_ERR(tz))
+ return dev_err_probe(dev, PTR_ERR(tz), "Failed to register the thermal device\n");
/*
* right now the FW does set up the HW-block, so we are not
@@ -222,8 +208,7 @@ static int bcm2835_thermal_probe(struct platform_device *pdev)
*/
val = readl(data->regs + BCM2835_TS_TSENSCTL);
if (!(val & BCM2835_TS_TSENSCTL_RSTB)) {
- struct thermal_trip trip;
- int offset, slope;
+ int offset, slope, crit_temp;
slope = thermal_zone_get_slope(tz);
offset = thermal_zone_get_offset(tz);
@@ -231,12 +216,10 @@ static int bcm2835_thermal_probe(struct platform_device *pdev)
* For now we deal only with critical, otherwise
* would need to iterate
*/
- err = thermal_zone_get_trip(tz, 0, &trip);
+ err = thermal_zone_get_crit_temp(tz, &crit_temp);
if (err < 0) {
- dev_err(&pdev->dev,
- "Not able to read trip_temp: %d\n",
- err);
- goto err_tz;
+ dev_err(dev, "Not able to read trip_temp: %d\n", err);
+ return err;
}
/* set bandgap reference voltage and enable voltage regulator */
@@ -248,7 +231,7 @@ static int bcm2835_thermal_probe(struct platform_device *pdev)
val |= (0xFE << BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT);
/* trip_adc value from info */
- val |= bcm2835_thermal_temp2adc(trip.temperature,
+ val |= bcm2835_thermal_temp2adc(crit_temp,
offset,
slope)
<< BCM2835_TS_TSENSCTL_THOLD_SHIFT;
@@ -269,27 +252,18 @@ static int bcm2835_thermal_probe(struct platform_device *pdev)
*/
err = thermal_add_hwmon_sysfs(tz);
if (err)
- goto err_tz;
+ return err;
bcm2835_thermal_debugfs(pdev);
return 0;
-err_tz:
- devm_thermal_of_zone_unregister(&pdev->dev, tz);
-err_clk:
- clk_disable_unprepare(data->clk);
-
- return err;
}
-static int bcm2835_thermal_remove(struct platform_device *pdev)
+static void bcm2835_thermal_remove(struct platform_device *pdev)
{
struct bcm2835_thermal_data *data = platform_get_drvdata(pdev);
debugfs_remove_recursive(data->debugfsdir);
- clk_disable_unprepare(data->clk);
-
- return 0;
}
static struct platform_driver bcm2835_thermal_driver = {
diff --git a/drivers/thermal/broadcom/brcmstb_thermal.c b/drivers/thermal/broadcom/brcmstb_thermal.c
index 72d1dbe60b8f..f46f2ddc174e 100644
--- a/drivers/thermal/broadcom/brcmstb_thermal.c
+++ b/drivers/thermal/broadcom/brcmstb_thermal.c
@@ -17,8 +17,8 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
-#include <linux/of_device.h>
#include <linux/thermal.h>
#define AVS_TMON_STATUS 0x00
@@ -286,14 +286,20 @@ static int brcmstb_set_trips(struct thermal_zone_device *tz, int low, int high)
return 0;
}
-static const struct thermal_zone_device_ops brcmstb_16nm_of_ops = {
+static const struct thermal_zone_device_ops brcmstb_of_ops = {
.get_temp = brcmstb_get_temp,
};
+static const struct brcmstb_thermal_params brcmstb_8nm_params = {
+ .offset = 418670,
+ .mult = 509,
+ .of_ops = &brcmstb_of_ops,
+};
+
static const struct brcmstb_thermal_params brcmstb_16nm_params = {
.offset = 457829,
.mult = 557,
- .of_ops = &brcmstb_16nm_of_ops,
+ .of_ops = &brcmstb_of_ops,
};
static const struct thermal_zone_device_ops brcmstb_28nm_of_ops = {
@@ -308,6 +314,7 @@ static const struct brcmstb_thermal_params brcmstb_28nm_params = {
};
static const struct of_device_id brcmstb_thermal_id_table[] = {
+ { .compatible = "brcm,avs-tmon-bcm74110", .data = &brcmstb_8nm_params },
{ .compatible = "brcm,avs-tmon-bcm7216", .data = &brcmstb_16nm_params },
{ .compatible = "brcm,avs-tmon", .data = &brcmstb_28nm_params },
{},
@@ -334,16 +341,13 @@ static int brcmstb_thermal_probe(struct platform_device *pdev)
return PTR_ERR(priv->tmon_base);
priv->dev = &pdev->dev;
- platform_set_drvdata(pdev, priv);
of_ops = priv->temp_params->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);
- return ret;
- }
+ if (IS_ERR(thermal))
+ return dev_err_probe(&pdev->dev, PTR_ERR(thermal),
+ "could not register sensor\n");
priv->thermal = thermal;
@@ -353,10 +357,9 @@ static int brcmstb_thermal_probe(struct platform_device *pdev)
brcmstb_tmon_irq_thread,
IRQF_ONESHOT,
DRV_NAME, priv);
- if (ret < 0) {
- dev_err(&pdev->dev, "could not request IRQ: %d\n", ret);
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "could not request IRQ\n");
}
dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n");
diff --git a/drivers/thermal/broadcom/ns-thermal.c b/drivers/thermal/broadcom/ns-thermal.c
index d255aa879fc0..8b5b32f749ee 100644
--- a/drivers/thermal/broadcom/ns-thermal.c
+++ b/drivers/thermal/broadcom/ns-thermal.c
@@ -65,13 +65,11 @@ static int ns_thermal_probe(struct platform_device *pdev)
return 0;
}
-static int ns_thermal_remove(struct platform_device *pdev)
+static void ns_thermal_remove(struct platform_device *pdev)
{
void __iomem *pvtmon = platform_get_drvdata(pdev);
iounmap(pvtmon);
-
- return 0;
}
static const struct of_device_id ns_thermal_of_match[] = {
diff --git a/drivers/thermal/broadcom/sr-thermal.c b/drivers/thermal/broadcom/sr-thermal.c
index 747915890022..9a29dfd4c7fe 100644
--- a/drivers/thermal/broadcom/sr-thermal.c
+++ b/drivers/thermal/broadcom/sr-thermal.c
@@ -91,7 +91,6 @@ static int sr_thermal_probe(struct platform_device *pdev)
dev_dbg(dev, "thermal sensor %d registered\n", i);
}
- platform_set_drvdata(pdev, sr_thermal);
return 0;
}
diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c
index e2cc7bd30862..6b7ab1814c12 100644
--- a/drivers/thermal/cpufreq_cooling.c
+++ b/drivers/thermal/cpufreq_cooling.c
@@ -57,8 +57,6 @@ struct time_in_idle {
* @max_level: maximum cooling level. One less than total number of valid
* cpufreq frequencies.
* @em: Reference on the Energy Model of the device
- * @cdev: thermal_cooling_device pointer to keep track of the
- * registered cooling device.
* @policy: cpufreq policy.
* @cooling_ops: cpufreq callbacks to thermal cooling device ops
* @idle_time: idle time stats
@@ -91,12 +89,16 @@ struct cpufreq_cooling_device {
static unsigned long get_level(struct cpufreq_cooling_device *cpufreq_cdev,
unsigned int freq)
{
+ struct em_perf_state *table;
int i;
+ rcu_read_lock();
+ table = em_perf_state_from_pd(cpufreq_cdev->em);
for (i = cpufreq_cdev->max_level - 1; i >= 0; i--) {
- if (freq > cpufreq_cdev->em->table[i].frequency)
+ if (freq > table[i].frequency)
break;
}
+ rcu_read_unlock();
return cpufreq_cdev->max_level - i - 1;
}
@@ -104,16 +106,20 @@ 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)
{
+ struct em_perf_state *table;
unsigned long power_mw;
int i;
+ rcu_read_lock();
+ table = em_perf_state_from_pd(cpufreq_cdev->em);
for (i = cpufreq_cdev->max_level - 1; i >= 0; i--) {
- if (freq > cpufreq_cdev->em->table[i].frequency)
+ if (freq > table[i].frequency)
break;
}
- power_mw = cpufreq_cdev->em->table[i + 1].power;
+ power_mw = table[i + 1].power;
power_mw /= MICROWATT_PER_MILLIWATT;
+ rcu_read_unlock();
return power_mw;
}
@@ -121,18 +127,24 @@ static u32 cpu_freq_to_power(struct cpufreq_cooling_device *cpufreq_cdev,
static u32 cpu_power_to_freq(struct cpufreq_cooling_device *cpufreq_cdev,
u32 power)
{
+ struct em_perf_state *table;
unsigned long em_power_mw;
+ u32 freq;
int i;
+ rcu_read_lock();
+ table = em_perf_state_from_pd(cpufreq_cdev->em);
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 = table[i].power;
em_power_mw /= MICROWATT_PER_MILLIWATT;
if (power >= em_power_mw)
break;
}
+ freq = table[i].frequency;
+ rcu_read_unlock();
- return cpufreq_cdev->em->table[i].frequency;
+ return freq;
}
/**
@@ -262,8 +274,9 @@ static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
static int cpufreq_state2power(struct thermal_cooling_device *cdev,
unsigned long state, u32 *power)
{
- unsigned int freq, num_cpus, idx;
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
+ unsigned int freq, num_cpus, idx;
+ struct em_perf_state *table;
/* Request state should be less than max_level */
if (state > cpufreq_cdev->max_level)
@@ -272,7 +285,12 @@ static int cpufreq_state2power(struct thermal_cooling_device *cdev,
num_cpus = cpumask_weight(cpufreq_cdev->policy->cpus);
idx = cpufreq_cdev->max_level - state;
- freq = cpufreq_cdev->em->table[idx].frequency;
+
+ rcu_read_lock();
+ table = em_perf_state_from_pd(cpufreq_cdev->em);
+ freq = table[idx].frequency;
+ rcu_read_unlock();
+
*power = cpu_freq_to_power(cpufreq_cdev, freq) * num_cpus;
return 0;
@@ -378,8 +396,17 @@ static unsigned int get_state_freq(struct cpufreq_cooling_device *cpufreq_cdev,
#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
/* Use the Energy Model table if available */
if (cpufreq_cdev->em) {
+ struct em_perf_state *table;
+ unsigned int freq;
+
idx = cpufreq_cdev->max_level - state;
- return cpufreq_cdev->em->table[idx].frequency;
+
+ rcu_read_lock();
+ table = em_perf_state_from_pd(cpufreq_cdev->em);
+ freq = table[idx].frequency;
+ rcu_read_unlock();
+
+ return freq;
}
#endif
@@ -448,7 +475,6 @@ 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;
@@ -465,8 +491,6 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
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;
}
diff --git a/drivers/thermal/cpuidle_cooling.c b/drivers/thermal/cpuidle_cooling.c
index 69f4c0a8dfcc..f678c1281862 100644
--- a/drivers/thermal/cpuidle_cooling.c
+++ b/drivers/thermal/cpuidle_cooling.c
@@ -66,7 +66,7 @@ static unsigned int cpuidle_cooling_runtime(unsigned int idle_duration_us,
* @state : a pointer to the state variable to be filled
*
* The function always returns 100 as the injection ratio. It is
- * percentile based for consistency accross different platforms.
+ * percentile based for consistency across different platforms.
*
* Return: The function can not fail, it is always zero
*/
@@ -146,7 +146,7 @@ static int cpuidle_cooling_set_cur_state(struct thermal_cooling_device *cdev,
return 0;
}
-/**
+/*
* cpuidle_cooling_ops - thermal cooling device ops
*/
static struct thermal_cooling_device_ops cpuidle_cooling_ops = {
diff --git a/drivers/thermal/da9062-thermal.c b/drivers/thermal/da9062-thermal.c
index 2d31b1f73423..a8d4b766ba21 100644
--- a/drivers/thermal/da9062-thermal.c
+++ b/drivers/thermal/da9062-thermal.c
@@ -137,7 +137,7 @@ static int da9062_thermal_get_temp(struct thermal_zone_device *z,
return 0;
}
-static struct thermal_zone_device_ops da9062_thermal_ops = {
+static const struct thermal_zone_device_ops da9062_thermal_ops = {
.get_temp = da9062_thermal_get_temp,
};
@@ -197,7 +197,7 @@ static int da9062_thermal_probe(struct platform_device *pdev)
mutex_init(&thermal->lock);
thermal->zone = thermal_zone_device_register_with_trips(thermal->config->name,
- trips, ARRAY_SIZE(trips), 0, thermal,
+ trips, ARRAY_SIZE(trips), thermal,
&da9062_thermal_ops, NULL, pp_tmp,
0);
if (IS_ERR(thermal->zone)) {
@@ -239,22 +239,21 @@ err:
return ret;
}
-static int da9062_thermal_remove(struct platform_device *pdev)
+static void da9062_thermal_remove(struct platform_device *pdev)
{
struct da9062_thermal *thermal = platform_get_drvdata(pdev);
free_irq(thermal->irq, thermal);
cancel_delayed_work_sync(&thermal->work);
thermal_zone_device_unregister(thermal->zone);
- return 0;
}
static struct platform_driver da9062_thermal_driver = {
.probe = da9062_thermal_probe,
.remove = da9062_thermal_remove,
.driver = {
- .name = "da9062-thermal",
- .of_match_table = da9062_compatible_reg_id_table,
+ .name = "da9062-thermal",
+ .of_match_table = da9062_compatible_reg_id_table,
},
};
diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c
index fca5c2c93bf9..576f88b6a1b3 100644
--- a/drivers/thermal/db8500_thermal.c
+++ b/drivers/thermal/db8500_thermal.c
@@ -229,7 +229,7 @@ MODULE_DEVICE_TABLE(of, db8500_thermal_match);
static struct platform_driver db8500_thermal_driver = {
.driver = {
.name = "db8500-thermal",
- .of_match_table = of_match_ptr(db8500_thermal_match),
+ .of_match_table = db8500_thermal_match,
},
.probe = db8500_thermal_probe,
.suspend = db8500_thermal_suspend,
diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c
index 262e62ab6cf2..8fd7cf1932cd 100644
--- a/drivers/thermal/devfreq_cooling.c
+++ b/drivers/thermal/devfreq_cooling.c
@@ -87,6 +87,7 @@ 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;
+ struct em_perf_state *table;
unsigned long freq;
int perf_idx;
@@ -100,7 +101,11 @@ static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
if (dfc->em_pd) {
perf_idx = dfc->max_state - state;
- freq = dfc->em_pd->table[perf_idx].frequency * 1000;
+
+ rcu_read_lock();
+ table = em_perf_state_from_pd(dfc->em_pd);
+ freq = table[perf_idx].frequency * 1000;
+ rcu_read_unlock();
} else {
freq = dfc->freq_table[state];
}
@@ -123,14 +128,21 @@ static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
*/
static int get_perf_idx(struct em_perf_domain *em_pd, unsigned long freq)
{
- int i;
+ struct em_perf_state *table;
+ int i, idx = -EINVAL;
+ rcu_read_lock();
+ table = em_perf_state_from_pd(em_pd);
for (i = 0; i < em_pd->nr_perf_states; i++) {
- if (em_pd->table[i].frequency == freq)
- return i;
+ if (table[i].frequency != freq)
+ continue;
+
+ idx = i;
+ break;
}
+ rcu_read_unlock();
- return -EINVAL;
+ return idx;
}
static unsigned long get_voltage(struct devfreq *df, unsigned long freq)
@@ -181,6 +193,7 @@ static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cd
struct devfreq_cooling_device *dfc = cdev->devdata;
struct devfreq *df = dfc->devfreq;
struct devfreq_dev_status status;
+ struct em_perf_state *table;
unsigned long state;
unsigned long freq;
unsigned long voltage;
@@ -201,10 +214,14 @@ 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;
+ state = dfc->max_state - dfc->capped_state;
/* Convert EM power into milli-Watts first */
- dfc->res_util = dfc->em_pd->table[state].power;
+ rcu_read_lock();
+ table = em_perf_state_from_pd(dfc->em_pd);
+ dfc->res_util = table[state].power;
+ rcu_read_unlock();
+
dfc->res_util /= MICROWATT_PER_MILLIWATT;
dfc->res_util *= SCALE_ERROR_MITIGATION;
@@ -225,7 +242,11 @@ static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cd
_normalize_load(&status);
/* Convert EM power into milli-Watts first */
- *power = dfc->em_pd->table[perf_idx].power;
+ rcu_read_lock();
+ table = em_perf_state_from_pd(dfc->em_pd);
+ *power = table[perf_idx].power;
+ rcu_read_unlock();
+
*power /= MICROWATT_PER_MILLIWATT;
/* Scale power for utilization */
*power *= status.busy_time;
@@ -245,13 +266,19 @@ static int devfreq_cooling_state2power(struct thermal_cooling_device *cdev,
unsigned long state, u32 *power)
{
struct devfreq_cooling_device *dfc = cdev->devdata;
+ struct em_perf_state *table;
int perf_idx;
if (state > dfc->max_state)
return -EINVAL;
perf_idx = dfc->max_state - state;
- *power = dfc->em_pd->table[perf_idx].power;
+
+ rcu_read_lock();
+ table = em_perf_state_from_pd(dfc->em_pd);
+ *power = table[perf_idx].power;
+ rcu_read_unlock();
+
*power /= MICROWATT_PER_MILLIWATT;
return 0;
@@ -264,6 +291,7 @@ static int devfreq_cooling_power2state(struct thermal_cooling_device *cdev,
struct devfreq *df = dfc->devfreq;
struct devfreq_dev_status status;
unsigned long freq, em_power_mw;
+ struct em_perf_state *table;
s32 est_power;
int i;
@@ -288,13 +316,16 @@ static int devfreq_cooling_power2state(struct thermal_cooling_device *cdev,
* Find the first cooling state that is within the power
* budget. The EM power table is sorted ascending.
*/
+ rcu_read_lock();
+ table = em_perf_state_from_pd(dfc->em_pd);
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 = table[i].power;
em_power_mw /= MICROWATT_PER_MILLIWATT;
if (est_power >= em_power_mw)
break;
}
+ rcu_read_unlock();
*state = dfc->max_state - i;
dfc->capped_state = *state;
diff --git a/drivers/thermal/dove_thermal.c b/drivers/thermal/dove_thermal.c
index 9954040d1d2c..723bc72f0626 100644
--- a/drivers/thermal/dove_thermal.c
+++ b/drivers/thermal/dove_thermal.c
@@ -106,7 +106,7 @@ static int dove_get_temp(struct thermal_zone_device *thermal,
return 0;
}
-static struct thermal_zone_device_ops ops = {
+static const struct thermal_zone_device_ops ops = {
.get_temp = dove_get_temp,
};
@@ -139,8 +139,8 @@ static int dove_thermal_probe(struct platform_device *pdev)
return ret;
}
- thermal = thermal_zone_device_register("dove_thermal", 0, 0,
- priv, &ops, NULL, 0, 0);
+ thermal = thermal_tripless_zone_device_register("dove_thermal", priv,
+ &ops, NULL);
if (IS_ERR(thermal)) {
dev_err(&pdev->dev,
"Failed to register thermal zone device\n");
@@ -158,14 +158,12 @@ static int dove_thermal_probe(struct platform_device *pdev)
return 0;
}
-static int dove_thermal_exit(struct platform_device *pdev)
+static void dove_thermal_exit(struct platform_device *pdev)
{
struct thermal_zone_device *dove_thermal =
platform_get_drvdata(pdev);
thermal_zone_device_unregister(dove_thermal);
-
- return 0;
}
MODULE_DEVICE_TABLE(of, dove_thermal_id_table);
diff --git a/drivers/thermal/gov_bang_bang.c b/drivers/thermal/gov_bang_bang.c
index 1b121066521f..51951967d67f 100644
--- a/drivers/thermal/gov_bang_bang.c
+++ b/drivers/thermal/gov_bang_bang.c
@@ -7,72 +7,6 @@
* Based on step_wise.c with following Copyrights:
* Copyright (C) 2012 Intel Corp
* Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
- */
-
-#include <linux/thermal.h>
-
-#include "thermal_core.h"
-
-static int thermal_zone_trip_update(struct thermal_zone_device *tz, int trip_id)
-{
- struct thermal_trip trip;
- struct thermal_instance *instance;
- int ret;
-
- ret = __thermal_zone_get_trip(tz, trip_id, &trip);
- if (ret) {
- pr_warn_once("Failed to retrieve trip point %d\n", trip_id);
- return ret;
- }
-
- if (!trip.hysteresis)
- dev_info_once(&tz->device,
- "Zero hysteresis value for thermal zone %s\n", tz->type);
-
- dev_dbg(&tz->device, "Trip%d[temp=%d]:temp=%d:hyst=%d\n",
- trip_id, trip.temperature, tz->temperature,
- trip.hysteresis);
-
- list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
- if (instance->trip != trip_id)
- continue;
-
- /* in case fan is in initial state, switch the fan off */
- if (instance->target == THERMAL_NO_TARGET)
- instance->target = 0;
-
- /* in case fan is neither on nor off set the fan to active */
- if (instance->target != 0 && instance->target != 1) {
- pr_warn("Thermal instance %s controlled by bang-bang has unexpected state: %ld\n",
- instance->name, instance->target);
- instance->target = 1;
- }
-
- /*
- * enable fan when temperature exceeds trip_temp and disable
- * the fan in case it falls below trip_temp minus hysteresis
- */
- if (instance->target == 0 && tz->temperature >= trip.temperature)
- instance->target = 1;
- else if (instance->target == 1 &&
- tz->temperature <= trip.temperature - trip.hysteresis)
- instance->target = 0;
-
- dev_dbg(&instance->cdev->device, "target=%d\n",
- (int)instance->target);
-
- mutex_lock(&instance->cdev->lock);
- instance->cdev->updated = false; /* cdev needs update */
- mutex_unlock(&instance->cdev->lock);
- }
-
- return 0;
-}
-
-/**
- * bang_bang_control - controls devices associated with the given zone
- * @tz: thermal_zone_device
- * @trip: the trip point
*
* Regulation Logic: a two point regulation, deliver cooling state depending
* on the previous state shown in this diagram:
@@ -94,27 +28,102 @@ static int thermal_zone_trip_update(struct thermal_zone_device *tz, int trip_id)
* gets turned on.
* * In case the fan is running, temperature must fall below
* (trip_temp - hyst) so that the fan gets turned off again.
- *
*/
-static int bang_bang_control(struct thermal_zone_device *tz, int trip)
+
+#include <linux/thermal.h>
+
+#include "thermal_core.h"
+
+static void bang_bang_set_instance_target(struct thermal_instance *instance,
+ unsigned int target)
{
+ if (instance->target != 0 && instance->target != 1 &&
+ instance->target != THERMAL_NO_TARGET)
+ pr_debug("Unexpected state %ld of thermal instance %s in bang-bang\n",
+ instance->target, instance->name);
+
+ /*
+ * Enable the fan when the trip is crossed on the way up and disable it
+ * when the trip is crossed on the way down.
+ */
+ instance->target = target;
+ instance->initialized = true;
+
+ dev_dbg(&instance->cdev->device, "target=%ld\n", instance->target);
+
+ thermal_cdev_update_nocheck(instance->cdev);
+}
+
+/**
+ * bang_bang_trip_crossed - controls devices associated with the given zone
+ * @tz: thermal_zone_device
+ * @trip: the trip point
+ * @upward: whether or not the trip has been crossed on the way up
+ */
+static void bang_bang_trip_crossed(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ bool upward)
+{
+ const struct thermal_trip_desc *td = trip_to_trip_desc(trip);
struct thermal_instance *instance;
- int ret;
lockdep_assert_held(&tz->lock);
- ret = thermal_zone_trip_update(tz, trip);
- if (ret)
- return ret;
+ dev_dbg(&tz->device, "Trip%d[temp=%d]:temp=%d:hyst=%d\n",
+ thermal_zone_trip_id(tz, trip), trip->temperature,
+ tz->temperature, trip->hysteresis);
+
+ list_for_each_entry(instance, &td->thermal_instances, trip_node)
+ bang_bang_set_instance_target(instance, upward);
+}
+
+static void bang_bang_manage(struct thermal_zone_device *tz)
+{
+ const struct thermal_trip_desc *td;
+ struct thermal_instance *instance;
- list_for_each_entry(instance, &tz->thermal_instances, tz_node)
- thermal_cdev_update(instance->cdev);
+ /* If the code below has run already, nothing needs to be done. */
+ if (tz->governor_data)
+ return;
- return 0;
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
+ bool turn_on;
+
+ if (trip->temperature == THERMAL_TEMP_INVALID ||
+ trip->type == THERMAL_TRIP_CRITICAL ||
+ trip->type == THERMAL_TRIP_HOT)
+ continue;
+
+ /*
+ * Adjust the target states for uninitialized thermal instances
+ * to the thermal zone temperature and the trip point threshold.
+ */
+ turn_on = tz->temperature >= td->threshold;
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ if (!instance->initialized)
+ bang_bang_set_instance_target(instance, turn_on);
+ }
+ }
+
+ tz->governor_data = (void *)true;
+}
+
+static void bang_bang_update_tz(struct thermal_zone_device *tz,
+ enum thermal_notify_event reason)
+{
+ /*
+ * Let bang_bang_manage() know that it needs to walk trips after binding
+ * a new cdev and after system resume.
+ */
+ if (reason == THERMAL_TZ_BIND_CDEV || reason == THERMAL_TZ_RESUME)
+ tz->governor_data = NULL;
}
static struct thermal_governor thermal_gov_bang_bang = {
.name = "bang_bang",
- .throttle = bang_bang_control,
+ .trip_crossed = bang_bang_trip_crossed,
+ .manage = bang_bang_manage,
+ .update_tz = bang_bang_update_tz,
};
THERMAL_GOVERNOR_DECLARE(thermal_gov_bang_bang);
diff --git a/drivers/thermal/gov_fair_share.c b/drivers/thermal/gov_fair_share.c
index 03c2daeb6ee8..4643be4f941d 100644
--- a/drivers/thermal/gov_fair_share.c
+++ b/drivers/thermal/gov_fair_share.c
@@ -15,98 +15,105 @@
#include "thermal_core.h"
-/**
- * get_trip_level: - obtains the current trip level for a zone
- * @tz: thermal zone device
- */
static int get_trip_level(struct thermal_zone_device *tz)
{
- struct thermal_trip trip;
- int count;
+ const struct thermal_trip_desc *level_td = NULL;
+ const struct thermal_trip_desc *td;
+ int trip_level = -1;
+
+ for_each_trip_desc(tz, td) {
+ if (td->threshold > tz->temperature)
+ continue;
+
+ trip_level++;
- for (count = 0; count < tz->num_trips; count++) {
- __thermal_zone_get_trip(tz, count, &trip);
- if (tz->temperature < trip.temperature)
- break;
+ if (!level_td || td->threshold > level_td->threshold)
+ level_td = td;
}
- /*
- * count > 0 only if temperature is greater than first trip
- * point, in which case, trip_point = count - 1
- */
- if (count > 0)
- trace_thermal_zone_trip(tz, count - 1, trip.type);
+ /* Bail out if the temperature is not greater than any trips. */
+ if (trip_level < 0)
+ return 0;
- return count;
-}
+ trace_thermal_zone_trip(tz, thermal_zone_trip_id(tz, &level_td->trip),
+ level_td->trip.type);
-static long get_target_state(struct thermal_zone_device *tz,
- struct thermal_cooling_device *cdev, int percentage, int level)
-{
- return (long)(percentage * level * cdev->max_state) / (100 * tz->num_trips);
+ return trip_level;
}
/**
* fair_share_throttle - throttles devices associated with the given zone
* @tz: thermal_zone_device
- * @trip: trip point index
+ * @td: trip point descriptor
+ * @trip_level: number of trips crossed by the zone temperature
*
* Throttling Logic: This uses three parameters to calculate the new
* throttle state of the cooling devices associated with the given zone.
*
* Parameters used for Throttling:
* P1. max_state: Maximum throttle state exposed by the cooling device.
- * P2. percentage[i]/100:
+ * P2. weight[i]/total_weight:
* How 'effective' the 'i'th device is, in cooling the given zone.
- * P3. cur_trip_level/max_no_of_trips:
+ * P3. trip_level/max_no_of_trips:
* This describes the extent to which the devices should be throttled.
* We do not want to throttle too much when we trip a lower temperature,
* whereas the throttling is at full swing if we trip critical levels.
- * (Heavily assumes the trip points are in ascending order)
* new_state of cooling device = P3 * P2 * P1
*/
-static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
+static void fair_share_throttle(struct thermal_zone_device *tz,
+ const struct thermal_trip_desc *td,
+ int trip_level)
{
struct thermal_instance *instance;
int total_weight = 0;
- 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;
+ int nr_instances = 0;
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
total_weight += instance->weight;
- total_instance++;
+ nr_instances++;
}
- list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
- int percentage;
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
struct thermal_cooling_device *cdev = instance->cdev;
+ u64 dividend;
+ u32 divisor;
+
+ dividend = trip_level;
+ dividend *= cdev->max_state;
+ divisor = tz->num_trips;
+ if (total_weight) {
+ dividend *= instance->weight;
+ divisor *= total_weight;
+ } else {
+ divisor *= nr_instances;
+ }
+ instance->target = div_u64(dividend, divisor);
+
+ thermal_cdev_update_nocheck(cdev);
+ }
+}
- if (instance->trip != trip)
- continue;
+static void fair_share_manage(struct thermal_zone_device *tz)
+{
+ int trip_level = get_trip_level(tz);
+ const struct thermal_trip_desc *td;
- if (!total_weight)
- percentage = 100 / total_instance;
- else
- percentage = (instance->weight * 100) / total_weight;
+ lockdep_assert_held(&tz->lock);
- instance->target = get_target_state(tz, cdev, percentage,
- cur_trip_level);
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
- mutex_lock(&cdev->lock);
- __thermal_cdev_update(cdev);
- mutex_unlock(&cdev->lock);
- }
+ if (trip->temperature == THERMAL_TEMP_INVALID ||
+ trip->type == THERMAL_TRIP_CRITICAL ||
+ trip->type == THERMAL_TRIP_HOT)
+ continue;
- return 0;
+ fair_share_throttle(tz, td, trip_level);
+ }
}
static struct thermal_governor thermal_gov_fair_share = {
- .name = "fair_share",
- .throttle = fair_share_throttle,
+ .name = "fair_share",
+ .manage = fair_share_manage,
};
THERMAL_GOVERNOR_DECLARE(thermal_gov_fair_share);
diff --git a/drivers/thermal/gov_power_allocator.c b/drivers/thermal/gov_power_allocator.c
index 8642f1096b91..0d9f636c80f4 100644
--- a/drivers/thermal/gov_power_allocator.c
+++ b/drivers/thermal/gov_power_allocator.c
@@ -16,8 +16,6 @@
#include "thermal_core.h"
-#define INVALID_TRIP -1
-
#define FRAC_BITS 10
#define int_to_frac(x) ((x) << FRAC_BITS)
#define frac_to_int(x) ((x) >> FRAC_BITS)
@@ -49,31 +47,61 @@ static inline s64 div_frac(s64 x, s64 y)
}
/**
+ * struct power_actor - internal power information for power actor
+ * @req_power: requested power value (not weighted)
+ * @max_power: max allocatable power for this actor
+ * @granted_power: granted power for this actor
+ * @extra_actor_power: extra power that this actor can receive
+ * @weighted_req_power: weighted requested power as input to IPA
+ */
+struct power_actor {
+ u32 req_power;
+ u32 max_power;
+ u32 granted_power;
+ u32 extra_actor_power;
+ u32 weighted_req_power;
+};
+
+/**
* struct power_allocator_params - parameters for the power allocator governor
* @allocated_tzp: whether we have allocated tzp for this thermal zone and
* it needs to be freed on unbind
+ * @update_cdevs: whether or not update cdevs on the next run
* @err_integral: accumulated error in the PID controller.
* @prev_err: error in the previous iteration of the PID controller.
* Used to calculate the derivative term.
+ * @sustainable_power: Sustainable power (heat) that this thermal zone can
+ * dissipate
* @trip_switch_on: first passive trip point of the thermal zone. The
* governor switches on when this trip point is crossed.
* If the thermal zone only has one passive trip point,
- * @trip_switch_on should be INVALID_TRIP.
- * @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
+ * @trip_switch_on should be NULL.
+ * @trip_max: last passive trip point of the thermal zone. The
+ * temperature we are controlling for.
+ * @total_weight: Sum of all thermal instances weights
+ * @num_actors: number of cooling devices supporting IPA callbacks
+ * @buffer_size: internal buffer size, to avoid runtime re-calculation
+ * @power: buffer for all power actors internal power information
*/
struct power_allocator_params {
bool allocated_tzp;
+ bool update_cdevs;
s64 err_integral;
s32 prev_err;
- int trip_switch_on;
- int trip_max_desired_temperature;
u32 sustainable_power;
+ const struct thermal_trip *trip_switch_on;
+ const struct thermal_trip *trip_max;
+ int total_weight;
+ unsigned int num_actors;
+ unsigned int buffer_size;
+ struct power_actor *power;
};
+static bool power_actor_is_valid(struct thermal_instance *instance)
+{
+ return cdev_is_power_actor(instance->cdev);
+}
+
/**
* estimate_sustainable_power() - Estimate the sustainable power of a thermal zone
* @tz: thermal zone we are operating in
@@ -87,20 +115,18 @@ struct power_allocator_params {
*/
static u32 estimate_sustainable_power(struct thermal_zone_device *tz)
{
- u32 sustainable_power = 0;
- struct thermal_instance *instance;
struct power_allocator_params *params = tz->governor_data;
+ const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
+ struct thermal_cooling_device *cdev;
+ struct thermal_instance *instance;
+ u32 sustainable_power = 0;
+ u32 min_power;
- list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
- struct thermal_cooling_device *cdev = instance->cdev;
- u32 min_power;
-
- if (instance->trip != params->trip_max_desired_temperature)
- continue;
-
- if (!cdev_is_power_actor(cdev))
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ if (!power_actor_is_valid(instance))
continue;
+ cdev = instance->cdev;
if (cdev->ops->state2power(cdev, instance->upper, &min_power))
continue;
@@ -114,24 +140,22 @@ static u32 estimate_sustainable_power(struct thermal_zone_device *tz)
* estimate_pid_constants() - Estimate the constants for the PID controller
* @tz: thermal zone for which to estimate the constants
* @sustainable_power: sustainable power for the thermal zone
- * @trip_switch_on: trip point number for the switch on temperature
+ * @trip_switch_on: trip point for the switch on temperature
* @control_temp: target temperature for the power allocator governor
*
* This function is used to update the estimation of the PID
* controller constants in struct thermal_zone_parameters.
*/
static void estimate_pid_constants(struct thermal_zone_device *tz,
- u32 sustainable_power, int trip_switch_on,
+ u32 sustainable_power,
+ const struct thermal_trip *trip_switch_on,
int control_temp)
{
- struct thermal_trip trip;
u32 temperature_threshold = control_temp;
- int ret;
s32 k_i;
- ret = __thermal_zone_get_trip(tz, trip_switch_on, &trip);
- if (!ret)
- temperature_threshold -= trip.temperature;
+ if (trip_switch_on)
+ temperature_threshold -= trip_switch_on->temperature;
/*
* estimate_pid_constants() tries to find appropriate default
@@ -216,10 +240,10 @@ static u32 pid_controller(struct thermal_zone_device *tz,
int control_temp,
u32 max_allocatable_power)
{
+ struct power_allocator_params *params = tz->governor_data;
s64 p, i, d, power_range;
s32 err, max_power_frac;
u32 sustainable_power;
- struct power_allocator_params *params = tz->governor_data;
max_power_frac = int_to_frac(max_allocatable_power);
@@ -298,24 +322,18 @@ power_actor_set_power(struct thermal_cooling_device *cdev,
return ret;
instance->target = clamp_val(state, instance->lower, instance->upper);
- mutex_lock(&cdev->lock);
- __thermal_cdev_update(cdev);
- mutex_unlock(&cdev->lock);
+
+ thermal_cdev_update_nocheck(cdev);
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
- * @num_actors: size of the @req_power, @max_power and @granted_power's array
- * @total_req_power: sum of @req_power
+ * @power: buffer for all power actors internal power information
+ * @num_actors: number of power actors in this thermal zone
+ * @total_req_power: sum of all weighted requested power for all actors
* @power_range: total allocated power
- * @granted_power: output array: each actor's granted power
- * @extra_actor_power: an appropriately sized array to be used in the
- * function as temporary storage of the extra power given
- * to the actors
*
* This function divides the total allocated power (@power_range)
* fairly between the actors. It first tries to give each actor a
@@ -328,41 +346,45 @@ power_actor_set_power(struct thermal_cooling_device *cdev,
* If any actor received more than their maximum power, then that
* surplus is re-divvied among the actors based on how far they are
* from their respective maximums.
- *
- * Granted power for each actor is written to @granted_power, which
- * should've been allocated by the calling function.
*/
-static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors,
- u32 total_req_power, u32 power_range,
- u32 *granted_power, u32 *extra_actor_power)
+static void divvy_up_power(struct power_actor *power, int num_actors,
+ u32 total_req_power, u32 power_range)
{
- u32 extra_power, capped_extra_power;
+ u32 capped_extra_power = 0;
+ u32 extra_power = 0;
int i;
- /*
- * Prevent division by 0 if none of the actors request power.
- */
- if (!total_req_power)
- total_req_power = 1;
+ if (!total_req_power) {
+ /*
+ * Nobody requested anything, just give everybody
+ * the maximum power
+ */
+ for (i = 0; i < num_actors; i++) {
+ struct power_actor *pa = &power[i];
+
+ pa->granted_power = pa->max_power;
+ }
+
+ return;
+ }
- capped_extra_power = 0;
- extra_power = 0;
for (i = 0; i < num_actors; i++) {
- u64 req_range = (u64)req_power[i] * power_range;
+ struct power_actor *pa = &power[i];
+ u64 req_range = (u64)pa->weighted_req_power * power_range;
- granted_power[i] = DIV_ROUND_CLOSEST_ULL(req_range,
- total_req_power);
+ pa->granted_power = DIV_ROUND_CLOSEST_ULL(req_range,
+ total_req_power);
- if (granted_power[i] > max_power[i]) {
- extra_power += granted_power[i] - max_power[i];
- granted_power[i] = max_power[i];
+ if (pa->granted_power > pa->max_power) {
+ extra_power += pa->granted_power - pa->max_power;
+ pa->granted_power = pa->max_power;
}
- extra_actor_power[i] = max_power[i] - granted_power[i];
- capped_extra_power += extra_actor_power[i];
+ pa->extra_actor_power = pa->max_power - pa->granted_power;
+ capped_extra_power += pa->extra_actor_power;
}
- if (!extra_power)
+ if (!extra_power || !capped_extra_power)
return;
/*
@@ -370,130 +392,98 @@ static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors,
* how far they are from the max
*/
extra_power = min(extra_power, capped_extra_power);
- if (capped_extra_power > 0)
- 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);
- }
+
+ for (i = 0; i < num_actors; i++) {
+ struct power_actor *pa = &power[i];
+ u64 extra_range = pa->extra_actor_power;
+
+ extra_range *= extra_power;
+ pa->granted_power += DIV_ROUND_CLOSEST_ULL(extra_range,
+ capped_extra_power);
+ }
}
-static int allocate_power(struct thermal_zone_device *tz,
- int control_temp)
+static void allocate_power(struct thermal_zone_device *tz, int control_temp)
{
- struct thermal_instance *instance;
struct power_allocator_params *params = tz->governor_data;
- u32 *req_power, *max_power, *granted_power, *extra_actor_power;
- u32 *weighted_req_power;
- u32 total_req_power, max_allocatable_power, total_weighted_req_power;
- u32 total_granted_power, power_range;
- int i, num_actors, total_weight, ret = 0;
- int trip_max_desired_temperature = params->trip_max_desired_temperature;
-
- num_actors = 0;
- total_weight = 0;
- list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
- if ((instance->trip == trip_max_desired_temperature) &&
- cdev_is_power_actor(instance->cdev)) {
- num_actors++;
- total_weight += instance->weight;
- }
- }
+ const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
+ unsigned int num_actors = params->num_actors;
+ struct power_actor *power = params->power;
+ struct thermal_cooling_device *cdev;
+ struct thermal_instance *instance;
+ u32 total_weighted_req_power = 0;
+ u32 max_allocatable_power = 0;
+ u32 total_granted_power = 0;
+ u32 total_req_power = 0;
+ u32 power_range, weight;
+ int i = 0, ret;
if (!num_actors)
- return -ENODEV;
-
- /*
- * We need to allocate five arrays of the same size:
- * req_power, max_power, granted_power, extra_actor_power and
- * weighted_req_power. They are going to be needed until this
- * function returns. Allocate them all in one go to simplify
- * the allocation and deallocation logic.
- */
- BUILD_BUG_ON(sizeof(*req_power) != sizeof(*max_power));
- BUILD_BUG_ON(sizeof(*req_power) != sizeof(*granted_power));
- 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)
- return -ENOMEM;
+ return;
- max_power = &req_power[num_actors];
- granted_power = &req_power[2 * num_actors];
- extra_actor_power = &req_power[3 * num_actors];
- weighted_req_power = &req_power[4 * num_actors];
+ /* Clean all buffers for new power estimations */
+ memset(power, 0, params->buffer_size);
- i = 0;
- total_weighted_req_power = 0;
- total_req_power = 0;
- max_allocatable_power = 0;
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ struct power_actor *pa = &power[i];
- list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
- int weight;
- struct thermal_cooling_device *cdev = instance->cdev;
-
- if (instance->trip != trip_max_desired_temperature)
+ if (!power_actor_is_valid(instance))
continue;
- if (!cdev_is_power_actor(cdev))
- continue;
+ cdev = instance->cdev;
- if (cdev->ops->get_requested_power(cdev, &req_power[i]))
+ ret = cdev->ops->get_requested_power(cdev, &pa->req_power);
+ if (ret)
continue;
- if (!total_weight)
+ if (!params->total_weight)
weight = 1 << FRAC_BITS;
else
weight = instance->weight;
- weighted_req_power[i] = frac_to_int(weight * req_power[i]);
+ pa->weighted_req_power = frac_to_int(weight * pa->req_power);
- if (cdev->ops->state2power(cdev, instance->lower,
- &max_power[i]))
+ ret = cdev->ops->state2power(cdev, instance->lower,
+ &pa->max_power);
+ if (ret)
continue;
- total_req_power += req_power[i];
- max_allocatable_power += max_power[i];
- total_weighted_req_power += weighted_req_power[i];
+ total_req_power += pa->req_power;
+ max_allocatable_power += pa->max_power;
+ total_weighted_req_power += pa->weighted_req_power;
i++;
}
power_range = pid_controller(tz, control_temp, max_allocatable_power);
- divvy_up_power(weighted_req_power, max_power, num_actors,
- total_weighted_req_power, power_range, granted_power,
- extra_actor_power);
+ divvy_up_power(power, num_actors, total_weighted_req_power,
+ power_range);
- total_granted_power = 0;
i = 0;
- list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
- if (instance->trip != trip_max_desired_temperature)
- continue;
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ struct power_actor *pa = &power[i];
- if (!cdev_is_power_actor(instance->cdev))
+ if (!power_actor_is_valid(instance))
continue;
power_actor_set_power(instance->cdev, instance,
- granted_power[i]);
- total_granted_power += granted_power[i];
+ pa->granted_power);
+ total_granted_power += pa->granted_power;
+ trace_thermal_power_actor(tz, i, pa->req_power,
+ pa->granted_power);
i++;
}
- trace_thermal_power_allocator(tz, req_power, total_req_power,
- granted_power, total_granted_power,
+ trace_thermal_power_allocator(tz, total_req_power, total_granted_power,
num_actors, power_range,
max_allocatable_power, tz->temperature,
control_temp - tz->temperature);
-
- kfree(req_power);
-
- return ret;
}
/**
- * get_governor_trips() - get the number of the two trip points that are key for this governor
+ * get_governor_trips() - get the two trip points that are key for this governor
* @tz: thermal zone to operate on
* @params: pointer to private data for this governor
*
@@ -510,47 +500,39 @@ static int allocate_power(struct thermal_zone_device *tz,
static void get_governor_trips(struct thermal_zone_device *tz,
struct power_allocator_params *params)
{
- int i, last_active, last_passive;
- bool found_first_passive;
-
- found_first_passive = false;
- last_active = INVALID_TRIP;
- last_passive = INVALID_TRIP;
-
- for (i = 0; i < tz->num_trips; i++) {
- struct thermal_trip trip;
- int ret;
-
- ret = __thermal_zone_get_trip(tz, i, &trip);
- if (ret) {
- dev_warn(&tz->device,
- "Failed to get trip point %d type: %d\n", i,
- ret);
- continue;
- }
-
- if (trip.type == THERMAL_TRIP_PASSIVE) {
- if (!found_first_passive) {
- params->trip_switch_on = i;
- found_first_passive = true;
- } else {
- last_passive = i;
+ const struct thermal_trip *first_passive = NULL;
+ const struct thermal_trip *last_passive = NULL;
+ const struct thermal_trip *last_active = NULL;
+ const struct thermal_trip_desc *td;
+
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
+
+ switch (trip->type) {
+ case THERMAL_TRIP_PASSIVE:
+ if (!first_passive) {
+ first_passive = trip;
+ break;
}
- } else if (trip.type == THERMAL_TRIP_ACTIVE) {
- last_active = i;
- } else {
+ last_passive = trip;
+ break;
+ case THERMAL_TRIP_ACTIVE:
+ last_active = trip;
+ break;
+ default:
break;
}
}
- if (last_passive != INVALID_TRIP) {
- params->trip_max_desired_temperature = last_passive;
- } else if (found_first_passive) {
- params->trip_max_desired_temperature = params->trip_switch_on;
- params->trip_switch_on = INVALID_TRIP;
+ if (last_passive) {
+ params->trip_switch_on = first_passive;
+ params->trip_max = last_passive;
+ } else if (first_passive) {
+ params->trip_switch_on = NULL;
+ params->trip_max = first_passive;
} else {
- params->trip_switch_on = INVALID_TRIP;
- params->trip_max_desired_temperature = last_active;
+ params->trip_switch_on = NULL;
+ params->trip_max = last_active;
}
}
@@ -560,32 +542,32 @@ static void reset_pid_controller(struct power_allocator_params *params)
params->prev_err = 0;
}
-static void allow_maximum_power(struct thermal_zone_device *tz, bool update)
+static void allow_maximum_power(struct thermal_zone_device *tz)
{
- struct thermal_instance *instance;
struct power_allocator_params *params = tz->governor_data;
+ const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
+ struct thermal_cooling_device *cdev;
+ struct thermal_instance *instance;
u32 req_power;
- 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)))
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ if (!power_actor_is_valid(instance))
continue;
- instance->target = 0;
- mutex_lock(&instance->cdev->lock);
- /*
- * 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);
+ cdev = instance->cdev;
- mutex_unlock(&instance->cdev->lock);
+ instance->target = 0;
+ scoped_guard(cooling_dev, cdev) {
+ /*
+ * 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 (params->update_cdevs)
+ __thermal_cdev_update(cdev);
+ }
}
}
@@ -593,30 +575,115 @@ static void allow_maximum_power(struct thermal_zone_device *tz, bool update)
* check_power_actors() - Check all cooling devices and warn when they are
* not power actors
* @tz: thermal zone to operate on
+ * @params: power allocator private data
*
* 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.
+ * If all of the cooling devices currently attached to @tz implement the power
+ * actor API, return the number of them (which may be 0, because some cooling
+ * devices may be attached later). Otherwise, return -EINVAL.
*/
-static int check_power_actors(struct thermal_zone_device *tz)
+static int check_power_actors(struct thermal_zone_device *tz,
+ struct power_allocator_params *params)
{
+ const struct thermal_trip_desc *td;
struct thermal_instance *instance;
int ret = 0;
- list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (!params->trip_max)
+ return 0;
+
+ td = trip_to_trip_desc(params->trip_max);
+
+ list_for_each_entry(instance, &td->thermal_instances, trip_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 -EINVAL;
}
+ ret++;
}
return ret;
}
+static int allocate_actors_buffer(struct power_allocator_params *params,
+ int num_actors)
+{
+ int ret;
+
+ kfree(params->power);
+
+ /* There might be no cooling devices yet. */
+ if (!num_actors) {
+ ret = 0;
+ goto clean_state;
+ }
+
+ params->power = kcalloc(num_actors, sizeof(struct power_actor),
+ GFP_KERNEL);
+ if (!params->power) {
+ ret = -ENOMEM;
+ goto clean_state;
+ }
+
+ params->num_actors = num_actors;
+ params->buffer_size = num_actors * sizeof(struct power_actor);
+
+ return 0;
+
+clean_state:
+ params->num_actors = 0;
+ params->buffer_size = 0;
+ params->power = NULL;
+ return ret;
+}
+
+static void power_allocator_update_weight(struct power_allocator_params *params)
+{
+ const struct thermal_trip_desc *td;
+ struct thermal_instance *instance;
+
+ if (!params->trip_max)
+ return;
+
+ td = trip_to_trip_desc(params->trip_max);
+
+ params->total_weight = 0;
+ list_for_each_entry(instance, &td->thermal_instances, trip_node)
+ if (power_actor_is_valid(instance))
+ params->total_weight += instance->weight;
+}
+
+static void power_allocator_update_tz(struct thermal_zone_device *tz,
+ enum thermal_notify_event reason)
+{
+ struct power_allocator_params *params = tz->governor_data;
+ const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
+ struct thermal_instance *instance;
+ int num_actors = 0;
+
+ switch (reason) {
+ case THERMAL_TZ_BIND_CDEV:
+ case THERMAL_TZ_UNBIND_CDEV:
+ list_for_each_entry(instance, &td->thermal_instances, trip_node)
+ if (power_actor_is_valid(instance))
+ num_actors++;
+
+ if (num_actors != params->num_actors)
+ allocate_actors_buffer(params, num_actors);
+
+ fallthrough;
+ case THERMAL_INSTANCE_WEIGHT_CHANGED:
+ power_allocator_update_weight(params);
+ break;
+ default:
+ break;
+ }
+}
+
/**
* power_allocator_bind() - bind the power_allocator governor to a thermal zone
* @tz: thermal zone to bind it to
@@ -629,18 +696,29 @@ static int check_power_actors(struct thermal_zone_device *tz)
*/
static int power_allocator_bind(struct thermal_zone_device *tz)
{
- int ret;
struct power_allocator_params *params;
- struct thermal_trip trip;
-
- ret = check_power_actors(tz);
- if (ret)
- return ret;
+ int ret;
params = kzalloc(sizeof(*params), GFP_KERNEL);
if (!params)
return -ENOMEM;
+ get_governor_trips(tz, params);
+
+ ret = check_power_actors(tz, params);
+ if (ret < 0) {
+ dev_warn(&tz->device, "power_allocator: binding failed\n");
+ kfree(params);
+ return ret;
+ }
+
+ ret = allocate_actors_buffer(params, ret);
+ if (ret) {
+ dev_warn(&tz->device, "power_allocator: allocation failed\n");
+ kfree(params);
+ return ret;
+ }
+
if (!tz->tzp) {
tz->tzp = kzalloc(sizeof(*tz->tzp), GFP_KERNEL);
if (!tz->tzp) {
@@ -653,25 +731,24 @@ static int power_allocator_bind(struct thermal_zone_device *tz)
if (!tz->tzp->sustainable_power)
dev_warn(&tz->device, "power_allocator: sustainable_power will be estimated\n");
+ else
+ params->sustainable_power = tz->tzp->sustainable_power;
- get_governor_trips(tz, params);
-
- if (tz->num_trips > 0) {
- ret = __thermal_zone_get_trip(tz, params->trip_max_desired_temperature,
- &trip);
- if (!ret)
- estimate_pid_constants(tz, tz->tzp->sustainable_power,
- params->trip_switch_on,
- trip.temperature);
- }
+ if (params->trip_max)
+ estimate_pid_constants(tz, tz->tzp->sustainable_power,
+ params->trip_switch_on,
+ params->trip_max->temperature);
reset_pid_controller(params);
tz->governor_data = params;
+ power_allocator_update_weight(params);
+
return 0;
free_params:
+ kfree(params->power);
kfree(params);
return ret;
@@ -688,51 +765,37 @@ static void power_allocator_unbind(struct thermal_zone_device *tz)
tz->tzp = NULL;
}
+ kfree(params->power);
kfree(tz->governor_data);
tz->governor_data = NULL;
}
-static int power_allocator_throttle(struct thermal_zone_device *tz, int trip_id)
+static void power_allocator_manage(struct thermal_zone_device *tz)
{
struct power_allocator_params *params = tz->governor_data;
- struct thermal_trip trip;
- int ret;
- bool update;
+ const struct thermal_trip *trip = params->trip_switch_on;
lockdep_assert_held(&tz->lock);
- /*
- * We get called for every trip point but we only need to do
- * our calculations once
- */
- if (trip_id != params->trip_max_desired_temperature)
- return 0;
-
- ret = __thermal_zone_get_trip(tz, params->trip_switch_on, &trip);
- if (!ret && (tz->temperature < trip.temperature)) {
- update = (tz->last_temperature >= trip.temperature);
- tz->passive = 0;
+ if (trip && tz->temperature < trip->temperature) {
reset_pid_controller(params);
- allow_maximum_power(tz, update);
- return 0;
+ allow_maximum_power(tz);
+ params->update_cdevs = false;
+ return;
}
- tz->passive = 1;
-
- ret = __thermal_zone_get_trip(tz, params->trip_max_desired_temperature, &trip);
- if (ret) {
- dev_warn(&tz->device, "Failed to get the maximum desired temperature: %d\n",
- ret);
- return ret;
- }
+ if (!params->trip_max)
+ return;
- return allocate_power(tz, trip.temperature);
+ allocate_power(tz, params->trip_max->temperature);
+ params->update_cdevs = true;
}
static struct thermal_governor thermal_gov_power_allocator = {
.name = "power_allocator",
.bind_to_tz = power_allocator_bind,
.unbind_from_tz = power_allocator_unbind,
- .throttle = power_allocator_throttle,
+ .manage = power_allocator_manage,
+ .update_tz = power_allocator_update_tz,
};
THERMAL_GOVERNOR_DECLARE(thermal_gov_power_allocator);
diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c
index 1050fb4d94c2..ea277c466d8d 100644
--- a/drivers/thermal/gov_step_wise.c
+++ b/drivers/thermal/gov_step_wise.c
@@ -20,11 +20,13 @@
* If the temperature is higher than a trip point,
* a. if the trend is THERMAL_TREND_RAISING, use higher cooling
* state for this trip point
- * b. if the trend is THERMAL_TREND_DROPPING, do nothing
+ * b. if the trend is THERMAL_TREND_DROPPING, use a lower cooling state
+ * for this trip point, but keep the cooling state above the applicable
+ * minimum
* If the temperature is lower than a trip point,
* a. if the trend is THERMAL_TREND_RAISING, do nothing
- * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
- * state for this trip point, if the cooling state already
+ * b. if the trend is THERMAL_TREND_DROPPING, use the minimum applicable
+ * cooling state for this trip point, or if the cooling state already
* equals lower limit, deactivate the thermal instance
*/
static unsigned long get_target_state(struct thermal_instance *instance,
@@ -32,7 +34,6 @@ static unsigned long get_target_state(struct thermal_instance *instance,
{
struct thermal_cooling_device *cdev = instance->cdev;
unsigned long cur_state;
- unsigned long next_target;
/*
* We keep this instance the way it is by default.
@@ -40,120 +41,112 @@ static unsigned long get_target_state(struct thermal_instance *instance,
* cdev in use to determine the next_target.
*/
cdev->ops->get_cur_state(cdev, &cur_state);
- next_target = instance->target;
dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state);
if (!instance->initialized) {
- if (throttle) {
- next_target = clamp((cur_state + 1), instance->lower, instance->upper);
- } else {
- next_target = THERMAL_NO_TARGET;
- }
+ if (throttle)
+ return clamp(cur_state + 1, instance->lower, instance->upper);
- return next_target;
+ return THERMAL_NO_TARGET;
}
if (throttle) {
if (trend == THERMAL_TREND_RAISING)
- next_target = clamp((cur_state + 1), instance->lower, instance->upper);
- } else {
- if (trend == THERMAL_TREND_DROPPING) {
- if (cur_state <= instance->lower)
- next_target = THERMAL_NO_TARGET;
- else
- next_target = clamp((cur_state - 1), instance->lower, instance->upper);
- }
+ return clamp(cur_state + 1, instance->lower, instance->upper);
+
+ /*
+ * If the zone temperature is falling, the cooling level can
+ * be reduced, but it should still be above the lower state of
+ * the given thermal instance to pull the temperature further
+ * down.
+ */
+ if (trend == THERMAL_TREND_DROPPING)
+ return clamp(cur_state - 1,
+ min(instance->lower + 1, instance->upper),
+ instance->upper);
+ } else if (trend == THERMAL_TREND_DROPPING) {
+ if (cur_state <= instance->lower)
+ return THERMAL_NO_TARGET;
+
+ /*
+ * If 'throttle' is false, no mitigation is necessary, so
+ * request the lower state for this instance.
+ */
+ return instance->lower;
}
- return next_target;
-}
-
-static void update_passive_instance(struct thermal_zone_device *tz,
- enum thermal_trip_type type, int value)
-{
- /*
- * If value is +1, activate a passive instance.
- * If value is -1, deactivate a passive instance.
- */
- if (type == THERMAL_TRIP_PASSIVE)
- tz->passive += value;
+ return instance->target;
}
-static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip_id)
+static void thermal_zone_trip_update(struct thermal_zone_device *tz,
+ const struct thermal_trip_desc *td,
+ int trip_threshold)
{
- enum thermal_trend trend;
+ bool throttle = tz->temperature >= trip_threshold;
+ const struct thermal_trip *trip = &td->trip;
+ enum thermal_trend trend = get_tz_trend(tz, trip);
+ int trip_id = thermal_zone_trip_id(tz, trip);
struct thermal_instance *instance;
- struct thermal_trip trip;
- bool throttle = false;
- int old_target;
-
- __thermal_zone_get_trip(tz, trip_id, &trip);
-
- trend = get_tz_trend(tz, trip_id);
- if (tz->temperature >= trip.temperature) {
- throttle = true;
- trace_thermal_zone_trip(tz, trip_id, trip.type);
- }
+ if (throttle)
+ trace_thermal_zone_trip(tz, trip_id, trip->type);
dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n",
- trip_id, trip.type, trip.temperature, trend, throttle);
+ trip_id, trip->type, trip_threshold, trend, throttle);
- list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
- if (instance->trip != trip_id)
- continue;
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ int old_target;
old_target = instance->target;
instance->target = get_target_state(instance, trend, throttle);
- dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n",
- old_target, (int)instance->target);
+
+ dev_dbg(&instance->cdev->device, "old_target=%d, target=%ld\n",
+ old_target, instance->target);
if (instance->initialized && old_target == instance->target)
continue;
- /* Activate a passive thermal instance */
- if (old_target == THERMAL_NO_TARGET &&
- instance->target != THERMAL_NO_TARGET)
- update_passive_instance(tz, trip.type, 1);
- /* Deactivate a passive thermal instance */
- else if (old_target != THERMAL_NO_TARGET &&
- instance->target == THERMAL_NO_TARGET)
- update_passive_instance(tz, trip.type, -1);
-
instance->initialized = true;
- mutex_lock(&instance->cdev->lock);
- instance->cdev->updated = false; /* cdev needs update */
- mutex_unlock(&instance->cdev->lock);
+
+ scoped_guard(cooling_dev, instance->cdev) {
+ instance->cdev->updated = false; /* cdev needs update */
+ }
}
}
-/**
- * step_wise_throttle - throttles devices associated with the given zone
- * @tz: thermal_zone_device
- * @trip: trip point index
- *
- * Throttling Logic: This uses the trend of the thermal zone to throttle.
- * If the thermal zone is 'heating up' this throttles all the cooling
- * devices associated with the zone and its particular trip point, by one
- * step. If the zone is 'cooling down' it brings back the performance of
- * the devices by one step.
- */
-static int step_wise_throttle(struct thermal_zone_device *tz, int trip)
+static void step_wise_manage(struct thermal_zone_device *tz)
{
+ const struct thermal_trip_desc *td;
struct thermal_instance *instance;
lockdep_assert_held(&tz->lock);
- thermal_zone_trip_update(tz, trip);
+ /*
+ * Throttling Logic: Use the trend of the thermal zone to throttle.
+ * If the thermal zone is 'heating up', throttle all of the cooling
+ * devices associated with each trip point by one step. If the zone
+ * is 'cooling down', it brings back the performance of the devices
+ * by one step.
+ */
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
- list_for_each_entry(instance, &tz->thermal_instances, tz_node)
- thermal_cdev_update(instance->cdev);
+ if (trip->temperature == THERMAL_TEMP_INVALID ||
+ trip->type == THERMAL_TRIP_CRITICAL ||
+ trip->type == THERMAL_TRIP_HOT)
+ continue;
- return 0;
+ thermal_zone_trip_update(tz, td, td->threshold);
+ }
+
+ for_each_trip_desc(tz, td) {
+ list_for_each_entry(instance, &td->thermal_instances, trip_node)
+ thermal_cdev_update(instance->cdev);
+ }
}
static struct thermal_governor thermal_gov_step_wise = {
- .name = "step_wise",
- .throttle = step_wise_throttle,
+ .name = "step_wise",
+ .manage = step_wise_manage,
};
THERMAL_GOVERNOR_DECLARE(thermal_gov_step_wise);
diff --git a/drivers/thermal/gov_user_space.c b/drivers/thermal/gov_user_space.c
index 8bc1c22aaf03..ef95cf7d65ef 100644
--- a/drivers/thermal/gov_user_space.c
+++ b/drivers/thermal/gov_user_space.c
@@ -23,13 +23,16 @@ static int user_space_bind(struct thermal_zone_device *tz)
}
/**
- * notify_user_space - Notifies user space about thermal events
+ * user_space_trip_crossed - Notify user space about trip crossing events
* @tz: thermal_zone_device
- * @trip: trip point index
+ * @trip: trip point
+ * @upward: whether or not the trip has been crossed on the way up
*
* This function notifies the user space through UEvents.
*/
-static int notify_user_space(struct thermal_zone_device *tz, int trip)
+static void user_space_trip_crossed(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ bool upward)
{
char *thermal_prop[5];
int i;
@@ -38,19 +41,18 @@ static int notify_user_space(struct thermal_zone_device *tz, int trip)
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);
+ thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=%d",
+ thermal_zone_trip_id(tz, trip));
thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", tz->notify_event);
thermal_prop[4] = NULL;
kobject_uevent_env(&tz->device.kobj, KOBJ_CHANGE, thermal_prop);
for (i = 0; i < 4; ++i)
kfree(thermal_prop[i]);
-
- return 0;
}
static struct thermal_governor thermal_gov_user_space = {
.name = "user_space",
- .throttle = notify_user_space,
+ .trip_crossed = user_space_trip_crossed,
.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 3f09ef8be41a..4307161533a7 100644
--- a/drivers/thermal/hisi_thermal.c
+++ b/drivers/thermal/hisi_thermal.c
@@ -13,9 +13,9 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/io.h>
-#include <linux/of_device.h>
#include <linux/thermal.h>
#define HI6220_TEMP0_LAG (0x0)
@@ -388,15 +388,10 @@ static int hi6220_thermal_probe(struct hisi_thermal_data *data)
{
struct platform_device *pdev = data->pdev;
struct device *dev = &pdev->dev;
- int ret;
data->clk = devm_clk_get(dev, "thermal_clk");
- if (IS_ERR(data->clk)) {
- ret = PTR_ERR(data->clk);
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to get thermal clk: %d\n", ret);
- return ret;
- }
+ if (IS_ERR(data->clk))
+ return dev_err_probe(dev, PTR_ERR(data->clk), "failed to get thermal clk\n");
data->sensor = devm_kzalloc(dev, sizeof(*data->sensor), GFP_KERNEL);
if (!data->sensor)
@@ -417,8 +412,8 @@ static int hi3660_thermal_probe(struct hisi_thermal_data *data)
data->nr_sensors = 1;
- data->sensor = devm_kzalloc(dev, sizeof(*data->sensor) *
- data->nr_sensors, GFP_KERNEL);
+ data->sensor = devm_kcalloc(dev, data->nr_sensors,
+ sizeof(*data->sensor), GFP_KERNEL);
if (!data->sensor)
return -ENOMEM;
@@ -470,11 +465,22 @@ static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev)
return IRQ_HANDLED;
}
+static int hisi_trip_walk_cb(struct thermal_trip *trip, void *arg)
+{
+ struct hisi_thermal_sensor *sensor = arg;
+
+ if (trip->type != THERMAL_TRIP_PASSIVE)
+ return 0;
+
+ sensor->thres_temp = trip->temperature;
+ /* Return nonzero to terminate the search. */
+ return 1;
+}
+
static int hisi_thermal_register_sensor(struct platform_device *pdev,
struct hisi_thermal_sensor *sensor)
{
- int ret, i;
- struct thermal_trip trip;
+ int ret;
sensor->tzd = devm_thermal_of_zone_register(&pdev->dev,
sensor->id, sensor,
@@ -487,15 +493,7 @@ static int hisi_thermal_register_sensor(struct platform_device *pdev,
return ret;
}
- for (i = 0; i < thermal_zone_get_num_trips(sensor->tzd); i++) {
-
- thermal_zone_get_trip(sensor->tzd, i, &trip);
-
- if (trip.type == THERMAL_TRIP_PASSIVE) {
- sensor->thres_temp = trip.temperature;
- break;
- }
- }
+ thermal_zone_for_each_trip(sensor->tzd, hisi_trip_walk_cb, sensor);
return 0;
}
@@ -597,7 +595,7 @@ static int hisi_thermal_probe(struct platform_device *pdev)
return 0;
}
-static int hisi_thermal_remove(struct platform_device *pdev)
+static void hisi_thermal_remove(struct platform_device *pdev)
{
struct hisi_thermal_data *data = platform_get_drvdata(pdev);
int i;
@@ -608,8 +606,6 @@ static int hisi_thermal_remove(struct platform_device *pdev)
hisi_thermal_toggle_sensor(sensor, false);
data->ops->disable_sensor(sensor);
}
-
- return 0;
}
static int hisi_thermal_suspend(struct device *dev)
@@ -641,7 +637,7 @@ static struct platform_driver hisi_thermal_driver = {
.driver = {
.name = "hisi_thermal",
.pm = pm_sleep_ptr(&hisi_thermal_pm_ops),
- .of_match_table = of_hisi_thermal_match,
+ .of_match_table = of_hisi_thermal_match,
},
.probe = hisi_thermal_probe,
.remove = hisi_thermal_remove,
diff --git a/drivers/thermal/imx8mm_thermal.c b/drivers/thermal/imx8mm_thermal.c
index d4b40869c7d7..719d71f5b235 100644
--- a/drivers/thermal/imx8mm_thermal.c
+++ b/drivers/thermal/imx8mm_thermal.c
@@ -12,7 +12,6 @@
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
-#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/thermal.h>
@@ -79,7 +78,7 @@
struct thermal_soc_data {
u32 num_sensors;
u32 version;
- int (*get_temp)(void *, int *);
+ int (*get_temp)(void *data, int *temp);
};
struct tmu_sensor {
@@ -179,10 +178,8 @@ static int imx8mm_tmu_probe_set_calib_v1(struct platform_device *pdev,
int ret;
ret = nvmem_cell_read_u32(&pdev->dev, "calib", &ana0);
- if (ret) {
- dev_warn(dev, "Failed to read OCOTP nvmem cell (%d).\n", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read OCOTP nvmem cell\n");
writel(FIELD_PREP(TASR_BUF_VREF_MASK,
FIELD_GET(ANA0_BUF_VREF_MASK, ana0)) |
@@ -366,7 +363,7 @@ disable_clk:
return ret;
}
-static int imx8mm_tmu_remove(struct platform_device *pdev)
+static void imx8mm_tmu_remove(struct platform_device *pdev)
{
struct imx8mm_tmu *tmu = platform_get_drvdata(pdev);
@@ -375,8 +372,6 @@ static int imx8mm_tmu_remove(struct platform_device *pdev)
clk_disable_unprepare(tmu->clk);
platform_set_drvdata(pdev, NULL);
-
- return 0;
}
static struct thermal_soc_data imx8mm_tmu_data = {
diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c
new file mode 100644
index 000000000000..9b20be03d6de
--- /dev/null
+++ b/drivers/thermal/imx91_thermal.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 NXP.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#define REG_SET 0x4
+#define REG_CLR 0x8
+#define REG_TOG 0xc
+
+#define IMX91_TMU_CTRL0 0x0
+#define IMX91_TMU_CTRL0_THR1_IE BIT(9)
+#define IMX91_TMU_CTRL0_THR1_MASK GENMASK(3, 2)
+#define IMX91_TMU_CTRL0_CLR_FLT1 BIT(21)
+
+#define IMX91_TMU_THR_MODE_LE 0
+#define IMX91_TMU_THR_MODE_GE 1
+
+#define IMX91_TMU_STAT0 0x10
+#define IMX91_TMU_STAT0_THR1_IF BIT(9)
+#define IMX91_TMU_STAT0_THR1_STAT BIT(13)
+#define IMX91_TMU_STAT0_DRDY0_IF_MASK BIT(16)
+
+#define IMX91_TMU_DATA0 0x20
+
+#define IMX91_TMU_CTRL1 0x200
+#define IMX91_TMU_CTRL1_EN BIT(31)
+#define IMX91_TMU_CTRL1_START BIT(30)
+#define IMX91_TMU_CTRL1_STOP BIT(29)
+#define IMX91_TMU_CTRL1_RES_MASK GENMASK(19, 18)
+#define IMX91_TMU_CTRL1_MEAS_MODE_MASK GENMASK(25, 24)
+#define IMX91_TMU_CTRL1_MEAS_MODE_SINGLE 0
+#define IMX91_TMU_CTRL1_MEAS_MODE_CONTINUES 1
+#define IMX91_TMU_CTRL1_MEAS_MODE_PERIODIC 2
+
+#define IMX91_TMU_THR_CTRL01 0x30
+#define IMX91_TMU_THR_CTRL01_THR1_MASK GENMASK(31, 16)
+
+#define IMX91_TMU_REF_DIV 0x280
+#define IMX91_TMU_DIV_EN BIT(31)
+#define IMX91_TMU_DIV_MASK GENMASK(23, 16)
+#define IMX91_TMU_DIV_MAX 255
+
+#define IMX91_TMU_PUD_ST_CTRL 0x2b0
+#define IMX91_TMU_PUDL_MASK GENMASK(23, 16)
+
+#define IMX91_TMU_TRIM1 0x2e0
+#define IMX91_TMU_TRIM2 0x2f0
+
+#define IMX91_TMU_TEMP_LOW_LIMIT -40000
+#define IMX91_TMU_TEMP_HIGH_LIMIT 125000
+
+#define IMX91_TMU_DEFAULT_TRIM1_CONFIG 0xb561bc2d
+#define IMX91_TMU_DEFAULT_TRIM2_CONFIG 0x65d4
+
+#define IMX91_TMU_PERIOD_CTRL 0x270
+#define IMX91_TMU_PERIOD_CTRL_MEAS_MASK GENMASK(23, 0)
+
+#define IMX91_TMP_FRAC 64
+
+struct imx91_tmu {
+ void __iomem *base;
+ struct clk *clk;
+ struct device *dev;
+ struct thermal_zone_device *tzd;
+};
+
+static void imx91_tmu_start(struct imx91_tmu *tmu, bool start)
+{
+ u32 val = start ? IMX91_TMU_CTRL1_START : IMX91_TMU_CTRL1_STOP;
+
+ writel_relaxed(val, tmu->base + IMX91_TMU_CTRL1 + REG_SET);
+}
+
+static void imx91_tmu_enable(struct imx91_tmu *tmu, bool enable)
+{
+ u32 reg = IMX91_TMU_CTRL1;
+
+ reg += enable ? REG_SET : REG_CLR;
+
+ writel_relaxed(IMX91_TMU_CTRL1_EN, tmu->base + reg);
+}
+
+static int imx91_tmu_to_mcelsius(int x)
+{
+ return x * MILLIDEGREE_PER_DEGREE / IMX91_TMP_FRAC;
+}
+
+static int imx91_tmu_from_mcelsius(int x)
+{
+ return x * IMX91_TMP_FRAC / MILLIDEGREE_PER_DEGREE;
+}
+
+static int imx91_tmu_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct imx91_tmu *tmu = thermal_zone_device_priv(tz);
+ s16 data;
+
+ /* DATA0 is 16bit signed number */
+ data = readw_relaxed(tmu->base + IMX91_TMU_DATA0);
+ *temp = imx91_tmu_to_mcelsius(data);
+
+ return 0;
+}
+
+static int imx91_tmu_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct imx91_tmu *tmu = thermal_zone_device_priv(tz);
+ int val;
+
+ if (high >= IMX91_TMU_TEMP_HIGH_LIMIT)
+ return -EINVAL;
+
+ writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR);
+
+ /* Comparator1 for temperature threshold */
+ writel_relaxed(IMX91_TMU_THR_CTRL01_THR1_MASK, tmu->base + IMX91_TMU_THR_CTRL01 + REG_CLR);
+ val = FIELD_PREP(IMX91_TMU_THR_CTRL01_THR1_MASK, imx91_tmu_from_mcelsius(high));
+
+ writel_relaxed(val, tmu->base + IMX91_TMU_THR_CTRL01 + REG_SET);
+
+ writel_relaxed(IMX91_TMU_STAT0_THR1_IF, tmu->base + IMX91_TMU_STAT0 + REG_CLR);
+
+ writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_SET);
+
+ return 0;
+}
+
+static int imx91_init_from_nvmem_cells(struct imx91_tmu *tmu)
+{
+ struct device *dev = tmu->dev;
+ u32 trim1, trim2;
+ int ret;
+
+ ret = nvmem_cell_read_u32(dev, "trim1", &trim1);
+ if (ret)
+ return ret;
+
+ ret = nvmem_cell_read_u32(dev, "trim2", &trim2);
+ if (ret)
+ return ret;
+
+ if (trim1 == 0 || trim2 == 0)
+ return -EINVAL;
+
+ writel_relaxed(trim1, tmu->base + IMX91_TMU_TRIM1);
+ writel_relaxed(trim2, tmu->base + IMX91_TMU_TRIM2);
+
+ return 0;
+}
+
+static void imx91_tmu_action_remove(void *data)
+{
+ struct imx91_tmu *tmu = data;
+
+ /* disable tmu */
+ imx91_tmu_enable(tmu, false);
+}
+
+static irqreturn_t imx91_tmu_alarm_irq(int irq, void *data)
+{
+ struct imx91_tmu *tmu = data;
+ u32 val;
+
+ val = readl_relaxed(tmu->base + IMX91_TMU_STAT0);
+
+ /* Check if comparison interrupt occurred */
+ if (val & IMX91_TMU_STAT0_THR1_IF) {
+ /* Clear irq flag and disable interrupt until reconfigured */
+ writel(IMX91_TMU_STAT0_THR1_IF, tmu->base + IMX91_TMU_STAT0 + REG_CLR);
+ writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR);
+
+ return IRQ_WAKE_THREAD;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t imx91_tmu_alarm_irq_thread(int irq, void *data)
+{
+ struct imx91_tmu *tmu = data;
+
+ thermal_zone_device_update(tmu->tzd, THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+static int imx91_tmu_change_mode(struct thermal_zone_device *tz, enum thermal_device_mode mode)
+{
+ struct imx91_tmu *tmu = thermal_zone_device_priv(tz);
+ int ret;
+
+ if (mode == THERMAL_DEVICE_ENABLED) {
+ ret = pm_runtime_get(tmu->dev);
+ if (ret < 0)
+ return ret;
+
+ writel_relaxed(IMX91_TMU_CTRL0_THR1_IE | IMX91_TMU_CTRL0_THR1_MASK,
+ tmu->base + IMX91_TMU_CTRL0 + REG_CLR);
+
+ writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL0_THR1_MASK, IMX91_TMU_THR_MODE_GE),
+ tmu->base + IMX91_TMU_CTRL0 + REG_SET);
+ imx91_tmu_start(tmu, true);
+ } else {
+ writel_relaxed(IMX91_TMU_CTRL0_THR1_IE, tmu->base + IMX91_TMU_CTRL0 + REG_CLR);
+ imx91_tmu_start(tmu, false);
+ pm_runtime_put(tmu->dev);
+ }
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops tmu_tz_ops = {
+ .get_temp = imx91_tmu_get_temp,
+ .change_mode = imx91_tmu_change_mode,
+ .set_trips = imx91_tmu_set_trips,
+};
+
+static int imx91_tmu_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imx91_tmu *tmu;
+ unsigned long rate;
+ int irq, ret;
+ u32 div;
+
+ tmu = devm_kzalloc(dev, sizeof(struct imx91_tmu), GFP_KERNEL);
+ if (!tmu)
+ return -ENOMEM;
+
+ tmu->dev = dev;
+
+ tmu->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(tmu->base))
+ return dev_err_probe(dev, PTR_ERR(tmu->base), "failed to get io resource");
+
+ tmu->clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(tmu->clk))
+ return dev_err_probe(dev, PTR_ERR(tmu->clk), "failed to get tmu clock\n");
+
+ platform_set_drvdata(pdev, tmu);
+
+ /* disable the monitor during initialization */
+ imx91_tmu_enable(tmu, false);
+ imx91_tmu_start(tmu, false);
+
+ ret = imx91_init_from_nvmem_cells(tmu);
+ if (ret) {
+ dev_warn(dev, "can't get trim value, use default settings\n");
+
+ writel_relaxed(IMX91_TMU_DEFAULT_TRIM1_CONFIG, tmu->base + IMX91_TMU_TRIM1);
+ writel_relaxed(IMX91_TMU_DEFAULT_TRIM2_CONFIG, tmu->base + IMX91_TMU_TRIM2);
+ }
+
+ /* The typical conv clk is 4MHz, the output freq is 'rate / (div + 1)' */
+ rate = clk_get_rate(tmu->clk);
+ div = (rate / (4 * HZ_PER_MHZ)) - 1;
+ if (div > IMX91_TMU_DIV_MAX)
+ return dev_err_probe(dev, -EINVAL, "clock divider exceed hardware limitation");
+
+ /* Set divider value and enable divider */
+ writel_relaxed(IMX91_TMU_DIV_EN | FIELD_PREP(IMX91_TMU_DIV_MASK, div),
+ tmu->base + IMX91_TMU_REF_DIV);
+
+ /* Set max power up delay: 'Tpud(ms) = 0xFF * 1000 / 4000000' */
+ writel_relaxed(FIELD_PREP(IMX91_TMU_PUDL_MASK, 100U), tmu->base + IMX91_TMU_PUD_ST_CTRL);
+
+ /*
+ * Set resolution mode
+ * 00b - Conversion time = 0.59325 ms
+ * 01b - Conversion time = 1.10525 ms
+ * 10b - Conversion time = 2.12925 ms
+ * 11b - Conversion time = 4.17725 ms
+ */
+ writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_RES_MASK, 0x3),
+ tmu->base + IMX91_TMU_CTRL1 + REG_CLR);
+ writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_RES_MASK, 0x1),
+ tmu->base + IMX91_TMU_CTRL1 + REG_SET);
+
+ writel_relaxed(IMX91_TMU_CTRL1_MEAS_MODE_MASK, tmu->base + IMX91_TMU_CTRL1 + REG_CLR);
+ writel_relaxed(FIELD_PREP(IMX91_TMU_CTRL1_MEAS_MODE_MASK,
+ IMX91_TMU_CTRL1_MEAS_MODE_PERIODIC),
+ tmu->base + IMX91_TMU_CTRL1 + REG_SET);
+
+ /*
+ * Set Periodic Measurement Frequency to 25Hz:
+ * tMEAS_FREQ = tCONV_CLK * PERIOD_CTRL[MEAS_FREQ]
+ */
+ writel_relaxed(FIELD_PREP(IMX91_TMU_PERIOD_CTRL_MEAS_MASK, 4 * HZ_PER_MHZ / 25),
+ tmu->base + IMX91_TMU_PERIOD_CTRL);
+
+ imx91_tmu_enable(tmu, true);
+ ret = devm_add_action(dev, imx91_tmu_action_remove, tmu);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failure to add action imx91_tmu_action_remove()\n");
+
+ pm_runtime_set_active(dev);
+ pm_runtime_get_noresume(dev);
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ tmu->tzd = devm_thermal_of_zone_register(dev, 0, tmu, &tmu_tz_ops);
+ if (IS_ERR(tmu->tzd))
+ return dev_err_probe(dev, PTR_ERR(tmu->tzd),
+ "failed to register thermal zone sensor\n");
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(dev, irq, imx91_tmu_alarm_irq,
+ imx91_tmu_alarm_irq_thread,
+ IRQF_ONESHOT, "imx91_thermal", tmu);
+
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to request alarm irq\n");
+
+ pm_runtime_put(dev);
+
+ return 0;
+}
+
+static int imx91_tmu_runtime_suspend(struct device *dev)
+{
+ struct imx91_tmu *tmu = dev_get_drvdata(dev);
+
+ /* disable tmu */
+ imx91_tmu_enable(tmu, false);
+
+ clk_disable_unprepare(tmu->clk);
+
+ return 0;
+}
+
+static int imx91_tmu_runtime_resume(struct device *dev)
+{
+ struct imx91_tmu *tmu = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(tmu->clk);
+ if (ret)
+ return ret;
+
+ imx91_tmu_enable(tmu, true);
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(imx91_tmu_pm_ops, imx91_tmu_runtime_suspend,
+ imx91_tmu_runtime_resume, NULL);
+
+static const struct of_device_id imx91_tmu_table[] = {
+ { .compatible = "fsl,imx91-tmu", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx91_tmu_table);
+
+static struct platform_driver imx91_tmu = {
+ .driver = {
+ .name = "imx91_thermal",
+ .pm = pm_ptr(&imx91_tmu_pm_ops),
+ .of_match_table = imx91_tmu_table,
+ },
+ .probe = imx91_tmu_probe,
+};
+module_platform_driver(imx91_tmu);
+
+MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
+MODULE_DESCRIPTION("i.MX91 Thermal Monitor Unit driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/imx_sc_thermal.c b/drivers/thermal/imx_sc_thermal.c
index 8d6b4ef23746..88558ce58880 100644
--- a/drivers/thermal/imx_sc_thermal.c
+++ b/drivers/thermal/imx_sc_thermal.c
@@ -8,7 +8,6 @@
#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>
@@ -112,8 +111,7 @@ static int imx_sc_thermal_probe(struct platform_device *pdev)
if (ret == -ENODEV)
continue;
- dev_err(&pdev->dev, "failed to register thermal zone\n");
- return ret;
+ return dev_err_probe(&pdev->dev, ret, "failed to register thermal zone\n");
}
devm_thermal_add_hwmon_sysfs(&pdev->dev, sensor->tzd);
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
index a94ec0a0c9dd..38c993d1bcb3 100644
--- a/drivers/thermal/imx_thermal.c
+++ b/drivers/thermal/imx_thermal.c
@@ -11,7 +11,7 @@
#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/thermal.h>
#include <linux/nvmem-consumer.h>
@@ -115,7 +115,8 @@ struct thermal_soc_data {
};
static struct thermal_trip trips[] = {
- [IMX_TRIP_PASSIVE] = { .type = THERMAL_TRIP_PASSIVE },
+ [IMX_TRIP_PASSIVE] = { .type = THERMAL_TRIP_PASSIVE,
+ .flags = THERMAL_TRIP_FLAG_RW_TEMP },
[IMX_TRIP_CRITICAL] = { .type = THERMAL_TRIP_CRITICAL },
};
@@ -330,54 +331,38 @@ static int imx_change_mode(struct thermal_zone_device *tz,
return 0;
}
-static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip_id,
- int temp)
+static int imx_set_trip_temp(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip, int temp)
{
struct imx_thermal_data *data = thermal_zone_device_priv(tz);
- struct thermal_trip trip;
int ret;
ret = pm_runtime_resume_and_get(data->dev);
if (ret < 0)
return ret;
- ret = __thermal_zone_get_trip(tz, trip_id, &trip);
- if (ret)
- return ret;
-
- /* do not allow changing critical threshold */
- if (trip.type == THERMAL_TRIP_CRITICAL)
- return -EPERM;
-
/* do not allow passive to be set higher than critical */
if (temp < 0 || temp > trips[IMX_TRIP_CRITICAL].temperature)
return -EINVAL;
imx_set_alarm_temp(data, temp);
+ trips[IMX_TRIP_PASSIVE].temperature = temp;
pm_runtime_put(data->dev);
return 0;
}
-static int imx_bind(struct thermal_zone_device *tz,
- struct thermal_cooling_device *cdev)
+static bool imx_should_bind(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ struct thermal_cooling_device *cdev,
+ struct cooling_spec *c)
{
- return thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
- THERMAL_NO_LIMIT,
- THERMAL_NO_LIMIT,
- THERMAL_WEIGHT_DEFAULT);
+ return trip->type == THERMAL_TRIP_PASSIVE;
}
-static int imx_unbind(struct thermal_zone_device *tz,
- struct thermal_cooling_device *cdev)
-{
- return thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
-}
-
-static struct thermal_zone_device_ops imx_tz_ops = {
- .bind = imx_bind,
- .unbind = imx_unbind,
+static const struct thermal_zone_device_ops imx_tz_ops = {
+ .should_bind = imx_should_bind,
.get_temp = imx_get_temp,
.change_mode = imx_change_mode,
.set_trip_temp = imx_set_trip_temp,
@@ -599,28 +584,29 @@ static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data
static int imx_thermal_probe(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
struct imx_thermal_data *data;
struct regmap *map;
int measure_freq;
int ret;
- data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- data->dev = &pdev->dev;
+ data->dev = dev;
- map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
+ map = syscon_regmap_lookup_by_phandle(dev->of_node, "fsl,tempmon");
if (IS_ERR(map)) {
ret = PTR_ERR(map);
- dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
+ dev_err(dev, "failed to get tempmon regmap: %d\n", ret);
return ret;
}
data->tempmon = map;
- data->socdata = of_device_get_match_data(&pdev->dev);
+ data->socdata = of_device_get_match_data(dev);
if (!data->socdata) {
- dev_err(&pdev->dev, "no device match found\n");
+ dev_err(dev, "no device match found\n");
return -ENODEV;
}
@@ -643,15 +629,15 @@ static int imx_thermal_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, data);
- if (of_property_present(pdev->dev.of_node, "nvmem-cells")) {
+ if (of_property_present(dev->of_node, "nvmem-cells")) {
ret = imx_init_from_nvmem_cells(pdev);
if (ret)
- return dev_err_probe(&pdev->dev, ret,
+ return dev_err_probe(dev, ret,
"failed to init from nvmem\n");
} else {
ret = imx_init_from_tempmon_data(pdev);
if (ret) {
- dev_err(&pdev->dev, "failed to init from fsl,tempmon-data\n");
+ dev_err(dev, "failed to init from fsl,tempmon-data\n");
return ret;
}
}
@@ -671,15 +657,12 @@ static int imx_thermal_probe(struct platform_device *pdev)
ret = imx_thermal_register_legacy_cooling(data);
if (ret)
- return dev_err_probe(&pdev->dev, ret,
+ return dev_err_probe(dev, ret,
"failed to register cpufreq cooling device\n");
- data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
+ data->thermal_clk = devm_clk_get(dev, NULL);
if (IS_ERR(data->thermal_clk)) {
- ret = PTR_ERR(data->thermal_clk);
- if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev,
- "failed to get thermal clk: %d\n", ret);
+ ret = dev_err_probe(dev, PTR_ERR(data->thermal_clk), "failed to get thermal clk\n");
goto legacy_cleanup;
}
@@ -692,25 +675,25 @@ static int imx_thermal_probe(struct platform_device *pdev)
*/
ret = clk_prepare_enable(data->thermal_clk);
if (ret) {
- dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
+ dev_err(dev, "failed to enable thermal clk: %d\n", ret);
goto legacy_cleanup;
}
data->tz = thermal_zone_device_register_with_trips("imx_thermal_zone",
trips,
ARRAY_SIZE(trips),
- BIT(IMX_TRIP_PASSIVE), data,
+ data,
&imx_tz_ops, NULL,
IMX_PASSIVE_DELAY,
IMX_POLLING_DELAY);
if (IS_ERR(data->tz)) {
ret = PTR_ERR(data->tz);
- dev_err(&pdev->dev,
- "failed to register thermal zone device %d\n", ret);
+ dev_err(dev, "failed to register thermal zone device %d\n",
+ ret);
goto clk_disable;
}
- dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
+ dev_info(dev, "%s CPU temperature grade - max:%dC"
" critical:%dC passive:%dC\n", data->temp_grade,
data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000,
trips[IMX_TRIP_PASSIVE].temperature / 1000);
@@ -734,7 +717,7 @@ static int imx_thermal_probe(struct platform_device *pdev)
usleep_range(20, 50);
/* the core was configured and enabled just before */
- pm_runtime_set_active(&pdev->dev);
+ pm_runtime_set_active(dev);
pm_runtime_enable(data->dev);
ret = pm_runtime_resume_and_get(data->dev);
@@ -746,11 +729,11 @@ static int imx_thermal_probe(struct platform_device *pdev)
if (ret)
goto thermal_zone_unregister;
- ret = devm_request_threaded_irq(&pdev->dev, data->irq,
+ ret = devm_request_threaded_irq(dev, data->irq,
imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
0, "imx_thermal", data);
if (ret < 0) {
- dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
+ dev_err(dev, "failed to request alarm irq: %d\n", ret);
goto thermal_zone_unregister;
}
@@ -771,7 +754,7 @@ legacy_cleanup:
return ret;
}
-static int imx_thermal_remove(struct platform_device *pdev)
+static void imx_thermal_remove(struct platform_device *pdev)
{
struct imx_thermal_data *data = platform_get_drvdata(pdev);
@@ -780,11 +763,9 @@ static int imx_thermal_remove(struct platform_device *pdev)
thermal_zone_device_unregister(data->tz);
imx_thermal_unregister_legacy_cooling(data);
-
- return 0;
}
-static int __maybe_unused imx_thermal_suspend(struct device *dev)
+static int imx_thermal_suspend(struct device *dev)
{
struct imx_thermal_data *data = dev_get_drvdata(dev);
int ret;
@@ -803,7 +784,7 @@ static int __maybe_unused imx_thermal_suspend(struct device *dev)
return pm_runtime_force_suspend(data->dev);
}
-static int __maybe_unused imx_thermal_resume(struct device *dev)
+static int imx_thermal_resume(struct device *dev)
{
struct imx_thermal_data *data = dev_get_drvdata(dev);
int ret;
@@ -815,7 +796,7 @@ static int __maybe_unused imx_thermal_resume(struct device *dev)
return thermal_zone_device_enable(data->tz);
}
-static int __maybe_unused imx_thermal_runtime_suspend(struct device *dev)
+static int imx_thermal_runtime_suspend(struct device *dev)
{
struct imx_thermal_data *data = dev_get_drvdata(dev);
const struct thermal_soc_data *socdata = data->socdata;
@@ -837,7 +818,7 @@ static int __maybe_unused imx_thermal_runtime_suspend(struct device *dev)
return 0;
}
-static int __maybe_unused imx_thermal_runtime_resume(struct device *dev)
+static int imx_thermal_runtime_resume(struct device *dev)
{
struct imx_thermal_data *data = dev_get_drvdata(dev);
const struct thermal_soc_data *socdata = data->socdata;
@@ -868,15 +849,15 @@ static int __maybe_unused imx_thermal_runtime_resume(struct device *dev)
}
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)
+ SYSTEM_SLEEP_PM_OPS(imx_thermal_suspend, imx_thermal_resume)
+ RUNTIME_PM_OPS(imx_thermal_runtime_suspend,
+ imx_thermal_runtime_resume, NULL)
};
static struct platform_driver imx_thermal = {
.driver = {
.name = "imx_thermal",
- .pm = &imx_thermal_pm_ops,
+ .pm = pm_ptr(&imx_thermal_pm_ops),
.of_match_table = of_imx_thermal_match,
},
.probe = imx_thermal_probe,
diff --git a/drivers/thermal/intel/Kconfig b/drivers/thermal/intel/Kconfig
index ecd7e07eece0..347c59bc87d6 100644
--- a/drivers/thermal/intel/Kconfig
+++ b/drivers/thermal/intel/Kconfig
@@ -21,9 +21,8 @@ config INTEL_TCC
config X86_PKG_TEMP_THERMAL
tristate "X86 package temperature thermal driver"
- depends on X86_THERMAL_VECTOR
- select THERMAL_GOV_USER_SPACE
- select THERMAL_WRITABLE_TRIPS
+ depends on X86_THERMAL_VECTOR && NET
+ select THERMAL_NETLINK
select INTEL_TCC
default m
help
@@ -45,9 +44,9 @@ config INTEL_SOC_DTS_IOSF_CORE
config INTEL_SOC_DTS_THERMAL
tristate "Intel SoCs DTS thermal driver"
- depends on X86 && PCI && ACPI
+ depends on X86_64 && PCI && ACPI && NET
+ select INT340X_THERMAL
select INTEL_SOC_DTS_IOSF_CORE
- select THERMAL_WRITABLE_TRIPS
help
Enable this to register Intel SoCs (e.g. Bay Trail) platform digital
temperature sensor (DTS). These SoCs have two additional DTSs in
@@ -85,7 +84,7 @@ config INTEL_BXT_PMIC_THERMAL
config INTEL_PCH_THERMAL
tristate "Intel PCH Thermal Reporting Driver"
depends on X86 && PCI
- select THERMAL_ACPI if ACPI
+ select ACPI_THERMAL_LIB if ACPI
help
Enable this to support thermal reporting on certain intel PCHs.
Thermal reporting device will provide temperature reading,
diff --git a/drivers/thermal/intel/int340x_thermal/Kconfig b/drivers/thermal/intel/int340x_thermal/Kconfig
index 300ea53e9b33..4ced7bdcd62c 100644
--- a/drivers/thermal/intel/int340x_thermal/Kconfig
+++ b/drivers/thermal/intel/int340x_thermal/Kconfig
@@ -5,13 +5,14 @@
config INT340X_THERMAL
tristate "ACPI INT340X thermal drivers"
- depends on X86_64 && ACPI && PCI
- select THERMAL_GOV_USER_SPACE
+ depends on X86_64 && ACPI && PCI && NET
+ select THERMAL_NETLINK
select ACPI_THERMAL_REL
select ACPI_FAN
- select THERMAL_ACPI
+ select ACPI_THERMAL_LIB
select INTEL_SOC_DTS_IOSF_CORE
select INTEL_TCC
+ select ACPI_PLATFORM_PROFILE
select PROC_THERMAL_MMIO_RAPL if POWERCAP
help
Newer laptops and tablets that use ACPI may have thermal sensors and
diff --git a/drivers/thermal/intel/int340x_thermal/Makefile b/drivers/thermal/intel/int340x_thermal/Makefile
index 4e852ce4a5d5..436be34b21a9 100644
--- a/drivers/thermal/intel/int340x_thermal/Makefile
+++ b/drivers/thermal/intel/int340x_thermal/Makefile
@@ -9,6 +9,11 @@ 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) += platform_temperature_control.o
obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_mbox.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_wt_req.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_wt_hint.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_power_floor.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_soc_slider.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 dc519a665c18..ce5d53be108b 100644
--- a/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c
+++ b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c
@@ -220,9 +220,6 @@ static int acpi_parse_psvt(acpi_handle handle, int *psvt_count, struct psvt **ps
int i, result = 0;
struct psvt *psvts;
- if (!acpi_has_method(handle, "PSVT"))
- return -ENODEV;
-
status = acpi_evaluate_object(handle, "PSVT", NULL, &buffer);
if (ACPI_FAILURE(status))
return -ENODEV;
@@ -309,7 +306,7 @@ static int acpi_parse_psvt(acpi_handle handle, int *psvt_count, struct psvt **ps
if (knob->type == ACPI_TYPE_STRING) {
memset(&psvt->limit, 0, sizeof(u64));
- strncpy(psvt->limit.string, psvt_ptr->limit.str_ptr, knob->string.length);
+ strscpy(psvt->limit.string, psvt_ptr->limit.str_ptr, ACPI_LIMIT_STR_MAX_LEN);
} else {
psvt->limit.integer = psvt_ptr->limit.integer;
}
@@ -468,7 +465,7 @@ static int fill_psvt(char __user *ubuf)
psvt_user[i].unlimit_coeff = psvts[i].unlimit_coeff;
psvt_user[i].control_knob_type = psvts[i].control_knob_type;
if (psvt_user[i].control_knob_type == ACPI_TYPE_STRING)
- strncpy(psvt_user[i].limit.string, psvts[i].limit.string,
+ strscpy(psvt_user[i].limit.string, psvts[i].limit.string,
ACPI_LIMIT_STR_MAX_LEN);
else
psvt_user[i].limit.integer = psvts[i].limit.integer;
@@ -564,7 +561,6 @@ static const struct file_operations acpi_thermal_rel_fops = {
.open = acpi_thermal_rel_open,
.release = acpi_thermal_rel_release,
.unlocked_ioctl = acpi_thermal_rel_ioctl,
- .llseek = no_llseek,
};
static struct miscdevice acpi_thermal_rel_misc_device = {
diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c
index 5e1164226ada..41d3bc3ed8a2 100644
--- a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c
+++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c
@@ -15,6 +15,9 @@
#define INT3400_THERMAL_TABLE_CHANGED 0x83
#define INT3400_ODVP_CHANGED 0x88
#define INT3400_KEEP_ALIVE 0xA0
+#define INT3400_FAKE_TEMP (20 * 1000) /* faked temp sensor with 20C */
+/* UUID prefix length for comparison - sufficient for all UUIDs */
+#define INT3400_UUID_PREFIX_LEN 7
enum int3400_thermal_uuid {
INT3400_THERMAL_ACTIVE = 0,
@@ -72,19 +75,7 @@ struct odvp_attr {
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 BIN_ATTR_SIMPLE_RO(data_vault);
static ssize_t imok_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
@@ -114,10 +105,6 @@ 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,
struct device_attribute *attr,
char *buf)
@@ -127,7 +114,7 @@ static ssize_t available_uuids_show(struct device *dev,
int length = 0;
if (!priv->uuid_bitmap)
- return sprintf(buf, "UNKNOWN\n");
+ return sysfs_emit(buf, "UNKNOWN\n");
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
if (priv->uuid_bitmap & (1 << i))
@@ -143,8 +130,8 @@ static ssize_t current_uuid_show(struct device *dev,
struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
int i, length = 0;
- if (priv->current_uuid_index > 0)
- return sprintf(buf, "%s\n",
+ if (priv->current_uuid_index >= 0)
+ return sysfs_emit(buf, "%s\n",
int3400_thermal_uuids[priv->current_uuid_index]);
for (i = 0; i <= INT3400_THERMAL_CRITICAL; i++) {
@@ -155,7 +142,7 @@ static ssize_t current_uuid_show(struct device *dev,
if (length)
return length;
- return sprintf(buf, "INVALID\n");
+ return sysfs_emit(buf, "INVALID\n");
}
static int int3400_thermal_run_osc(acpi_handle handle, char *uuid_str, int *enable)
@@ -214,7 +201,7 @@ static ssize_t current_uuid_store(struct device *dev,
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
if (!strncmp(buf, int3400_thermal_uuids[i],
- sizeof(int3400_thermal_uuids[i]) - 1)) {
+ INT3400_UUID_PREFIX_LEN)) {
/*
* If we have a list of supported UUIDs, make sure
* this one is supported.
@@ -355,7 +342,7 @@ static ssize_t odvp_show(struct device *dev, struct device_attribute *attr,
odvp_attr = container_of(attr, struct odvp_attr, attr);
- return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);
+ return sysfs_emit(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);
}
static void cleanup_odvp(struct int3400_thermal_priv *priv)
@@ -453,6 +440,7 @@ static void int3400_notify(acpi_handle handle,
void *data)
{
struct int3400_thermal_priv *priv = data;
+ struct device *dev;
char *thermal_prop[5];
int therm_event;
@@ -475,12 +463,14 @@ static void int3400_notify(acpi_handle handle,
return;
}
- thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", priv->thermal->type);
- thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", priv->thermal->temperature);
+ dev = thermal_zone_device(priv->thermal);
+
+ thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", thermal_zone_device_type(priv->thermal));
+ thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", INT3400_FAKE_TEMP);
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);
+ kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, thermal_prop);
kfree(thermal_prop[0]);
kfree(thermal_prop[1]);
kfree(thermal_prop[2]);
@@ -490,7 +480,7 @@ static void int3400_notify(acpi_handle handle,
static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
int *temp)
{
- *temp = 20 * 1000; /* faked temp sensor with 20C */
+ *temp = INT3400_FAKE_TEMP;
return 0;
}
@@ -499,45 +489,40 @@ static int int3400_thermal_change_mode(struct thermal_zone_device *thermal,
{
struct int3400_thermal_priv *priv = thermal_zone_device_priv(thermal);
int result = 0;
+ int enabled;
if (!priv)
return -EINVAL;
- if (mode != thermal->mode) {
- int enabled;
+ enabled = mode == THERMAL_DEVICE_ENABLED;
- enabled = mode == THERMAL_DEVICE_ENABLED;
-
- 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 (priv->os_uuid_mask) {
+ if (!enabled) {
+ priv->os_uuid_mask = 0;
+ result = set_os_uuid_mask(priv, priv->os_uuid_mask);
}
-
- if (priv->current_uuid_index < 0 ||
- priv->current_uuid_index >= INT3400_THERMAL_MAXIMUM_UUID)
- return -EINVAL;
-
- result = int3400_thermal_run_osc(priv->adev->handle,
- int3400_thermal_uuids[priv->current_uuid_index],
- &enabled);
+ goto eval_odvp;
}
+ if (priv->current_uuid_index < 0 ||
+ priv->current_uuid_index >= INT3400_THERMAL_MAXIMUM_UUID)
+ return -EINVAL;
+
+ result = int3400_thermal_run_osc(priv->adev->handle,
+ int3400_thermal_uuids[priv->current_uuid_index],
+ &enabled);
eval_odvp:
evaluate_odvp(priv);
return result;
}
-static struct thermal_zone_device_ops int3400_thermal_ops = {
+static const 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 = {
- .governor_name = "user_space",
.no_hwmon = true,
};
@@ -578,7 +563,7 @@ static int int3400_thermal_probe(struct platform_device *pdev)
if (!adev)
return -ENODEV;
- priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL);
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
@@ -609,9 +594,9 @@ static int int3400_thermal_probe(struct platform_device *pdev)
evaluate_odvp(priv);
- priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
- priv, &int3400_thermal_ops,
- &int3400_thermal_params, 0, 0);
+ priv->thermal = thermal_tripless_zone_device_register("INT3400 Thermal", priv,
+ &int3400_thermal_ops,
+ &int3400_thermal_params);
if (IS_ERR(priv->thermal)) {
result = PTR_ERR(priv->thermal);
goto free_art_trt;
@@ -631,8 +616,7 @@ static int int3400_thermal_probe(struct platform_device *pdev)
}
if (!ZERO_OR_NULL_PTR(priv->data_vault)) {
- result = sysfs_create_group(&pdev->dev.kobj,
- &data_attribute_group);
+ result = device_create_bin_file(&pdev->dev, &bin_attr_data_vault);
if (result)
goto free_uuid;
}
@@ -655,7 +639,7 @@ free_notify:
free_sysfs:
cleanup_odvp(priv);
if (!ZERO_OR_NULL_PTR(priv->data_vault)) {
- sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
+ device_remove_bin_file(&pdev->dev, &bin_attr_data_vault);
kfree(priv->data_vault);
}
free_uuid:
@@ -674,7 +658,7 @@ free_priv:
return result;
}
-static int int3400_thermal_remove(struct platform_device *pdev)
+static void int3400_thermal_remove(struct platform_device *pdev)
{
struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
@@ -690,7 +674,7 @@ static int int3400_thermal_remove(struct platform_device *pdev)
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);
+ device_remove_bin_file(&pdev->dev, &bin_attr_data_vault);
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
thermal_zone_device_unregister(priv->thermal);
@@ -698,7 +682,6 @@ static int int3400_thermal_remove(struct platform_device *pdev)
kfree(priv->trts);
kfree(priv->arts);
kfree(priv);
- return 0;
}
static const struct acpi_device_id int3400_thermal_match[] = {
@@ -706,7 +689,11 @@ static const struct acpi_device_id int3400_thermal_match[] = {
{"INTC1040", 0},
{"INTC1041", 0},
{"INTC1042", 0},
+ {"INTC1068", 0},
{"INTC10A0", 0},
+ {"INTC10D4", 0},
+ {"INTC10FC", 0},
+ {"INTC10F3", 0},
{}
};
diff --git a/drivers/thermal/intel/int340x_thermal/int3401_thermal.c b/drivers/thermal/intel/int340x_thermal/int3401_thermal.c
index 217786fba185..96d6277a5a8c 100644
--- a/drivers/thermal/intel/int340x_thermal/int3401_thermal.c
+++ b/drivers/thermal/intel/int340x_thermal/int3401_thermal.c
@@ -36,11 +36,9 @@ static int int3401_add(struct platform_device *pdev)
return ret;
}
-static int int3401_remove(struct platform_device *pdev)
+static void int3401_remove(struct platform_device *pdev)
{
proc_thermal_remove(platform_get_drvdata(pdev));
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
@@ -70,18 +68,7 @@ static struct platform_driver int3401_driver = {
},
};
-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_platform_driver(int3401_driver);
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
diff --git a/drivers/thermal/intel/int340x_thermal/int3402_thermal.c b/drivers/thermal/intel/int340x_thermal/int3402_thermal.c
index 43fa351e2b9e..57b90005888a 100644
--- a/drivers/thermal/intel/int340x_thermal/int3402_thermal.c
+++ b/drivers/thermal/intel/int340x_thermal/int3402_thermal.c
@@ -45,6 +45,9 @@ static int int3402_thermal_probe(struct platform_device *pdev)
struct int3402_thermal_data *d;
int ret;
+ if (!adev)
+ return -ENODEV;
+
if (!acpi_has_method(adev->handle, "_TMP"))
return -ENODEV;
@@ -71,15 +74,13 @@ static int int3402_thermal_probe(struct platform_device *pdev)
return 0;
}
-static int int3402_thermal_remove(struct platform_device *pdev)
+static void int3402_thermal_remove(struct platform_device *pdev)
{
struct int3402_thermal_data *d = platform_get_drvdata(pdev);
acpi_remove_notify_handler(d->handle,
ACPI_DEVICE_NOTIFY, int3402_notify);
int340x_thermal_zone_remove(d->int340x_zone);
-
- return 0;
}
static const struct acpi_device_id int3402_thermal_match[] = {
diff --git a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
index e418d270bc76..264c9bc8e645 100644
--- a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
+++ b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
@@ -25,17 +25,6 @@ struct int3403_sensor {
struct int34x_thermal_zone *int340x_zone;
};
-struct int3403_performance_state {
- u64 performance;
- u64 power;
- u64 latency;
- u64 linear;
- u64 control;
- u64 raw_performace;
- char *raw_unit;
- int reserved;
-};
-
struct int3403_cdev {
struct thermal_cooling_device *cdev;
unsigned long max_state;
@@ -262,7 +251,7 @@ err:
return result;
}
-static int int3403_remove(struct platform_device *pdev)
+static void int3403_remove(struct platform_device *pdev)
{
struct int3403_priv *priv = platform_get_drvdata(pdev);
@@ -277,8 +266,6 @@ static int int3403_remove(struct platform_device *pdev)
default:
break;
}
-
- return 0;
}
static const struct acpi_device_id int3403_device_ids[] = {
@@ -286,7 +273,11 @@ static const struct acpi_device_id int3403_device_ids[] = {
{"INTC1043", 0},
{"INTC1046", 0},
{"INTC1062", 0},
+ {"INTC1069", 0},
{"INTC10A1", 0},
+ {"INTC10D5", 0},
+ {"INTC10FD", 0},
+ {"INTC10F4", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, int3403_device_ids);
diff --git a/drivers/thermal/intel/int340x_thermal/int3406_thermal.c b/drivers/thermal/intel/int340x_thermal/int3406_thermal.c
index f5e42fc2acc0..e21fcbccf4ba 100644
--- a/drivers/thermal/intel/int340x_thermal/int3406_thermal.c
+++ b/drivers/thermal/intel/int340x_thermal/int3406_thermal.c
@@ -178,13 +178,12 @@ err:
return -ENODEV;
}
-static int int3406_thermal_remove(struct platform_device *pdev)
+static void int3406_thermal_remove(struct platform_device *pdev)
{
struct int3406_thermal_data *d = platform_get_drvdata(pdev);
thermal_cooling_device_unregister(d->cooling_dev);
kfree(d->br);
- return 0;
}
static const struct acpi_device_id int3406_thermal_match[] = {
diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
index 89cf007146ea..3d9efe69d562 100644
--- a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
+++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
@@ -39,13 +39,14 @@ static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone,
}
static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
- int trip, int temp)
+ const struct thermal_trip *trip, int temp)
{
struct int34x_thermal_zone *d = thermal_zone_device_priv(zone);
- char name[] = {'P', 'A', 'T', '0' + trip, '\0'};
+ unsigned int trip_index = THERMAL_TRIP_PRIV_TO_INT(trip->priv);
+ char name[] = {'P', 'A', 'T', '0' + trip_index, '\0'};
acpi_status status;
- if (trip > 9)
+ if (trip_index > 9)
return -EINVAL;
status = acpi_execute_simple_method(d->adev->handle, name,
@@ -58,15 +59,10 @@ static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
static void int340x_thermal_critical(struct thermal_zone_device *zone)
{
- dev_dbg(&zone->device, "%s: critical temperature reached\n", zone->type);
+ dev_dbg(thermal_zone_device(zone), "%s: critical temperature reached\n",
+ thermal_zone_device_type(zone));
}
-static struct thermal_zone_device_ops int340x_thermal_zone_ops = {
- .get_temp = int340x_thermal_get_zone_temp,
- .set_trip_temp = int340x_thermal_set_trip_temp,
- .critical = int340x_thermal_critical,
-};
-
static int int340x_thermal_read_trips(struct acpi_device *zone_adev,
struct thermal_trip *zone_trips,
int trip_cnt)
@@ -101,6 +97,7 @@ static int int340x_thermal_read_trips(struct acpi_device *zone_adev,
break;
zone_trips[trip_cnt].type = THERMAL_TRIP_ACTIVE;
+ zone_trips[trip_cnt].priv = THERMAL_INT_TO_TRIP_PRIV(i);
trip_cnt++;
}
@@ -108,18 +105,21 @@ static int int340x_thermal_read_trips(struct acpi_device *zone_adev,
}
static struct thermal_zone_params int340x_thermal_params = {
- .governor_name = "user_space",
.no_hwmon = true,
};
struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
int (*get_temp) (struct thermal_zone_device *, int *))
{
+ const struct thermal_zone_device_ops zone_ops = {
+ .set_trip_temp = int340x_thermal_set_trip_temp,
+ .critical = int340x_thermal_critical,
+ .get_temp = get_temp ? get_temp : int340x_thermal_get_zone_temp,
+ };
struct int34x_thermal_zone *int34x_zone;
struct thermal_trip *zone_trips;
unsigned long long trip_cnt = 0;
unsigned long long hyst;
- int trip_mask = 0;
acpi_status status;
int i, ret;
@@ -129,24 +129,12 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
int34x_zone->adev = adev;
- int34x_zone->ops = kmemdup(&int340x_thermal_zone_ops,
- sizeof(int340x_thermal_zone_ops), GFP_KERNEL);
- if (!int34x_zone->ops) {
- ret = -ENOMEM;
- goto err_ops_alloc;
- }
-
- if (get_temp)
- int34x_zone->ops->get_temp = get_temp;
-
status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt);
- if (ACPI_SUCCESS(status)) {
+ if (ACPI_SUCCESS(status))
int34x_zone->aux_trip_nr = trip_cnt;
- trip_mask = BIT(trip_cnt) - 1;
- }
- zone_trips = kzalloc(sizeof(*zone_trips) * (trip_cnt + INT340X_THERMAL_MAX_TRIP_COUNT),
- GFP_KERNEL);
+ zone_trips = kcalloc(trip_cnt + INT340X_THERMAL_MAX_TRIP_COUNT,
+ sizeof(*zone_trips), GFP_KERNEL);
if (!zone_trips) {
ret = -ENOMEM;
goto err_trips_alloc;
@@ -155,6 +143,8 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
for (i = 0; i < trip_cnt; i++) {
zone_trips[i].type = THERMAL_TRIP_PASSIVE;
zone_trips[i].temperature = THERMAL_TEMP_INVALID;
+ zone_trips[i].flags = THERMAL_TRIP_FLAG_RW_TEMP;
+ zone_trips[i].priv = THERMAL_INT_TO_TRIP_PRIV(i);
}
trip_cnt = int340x_thermal_read_trips(adev, zone_trips, trip_cnt);
@@ -168,17 +158,17 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
for (i = 0; i < trip_cnt; ++i)
zone_trips[i].hysteresis = hyst;
- int34x_zone->trips = zone_trips;
-
int34x_zone->lpat_table = acpi_lpat_get_conversion_table(adev->handle);
int34x_zone->zone = thermal_zone_device_register_with_trips(
acpi_device_bid(adev),
zone_trips, trip_cnt,
- trip_mask, int34x_zone,
- int34x_zone->ops,
+ int34x_zone,
+ &zone_ops,
&int340x_thermal_params,
0, 0);
+ kfree(zone_trips);
+
if (IS_ERR(int34x_zone->zone)) {
ret = PTR_ERR(int34x_zone->zone);
goto err_thermal_zone;
@@ -192,11 +182,8 @@ struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
err_enable:
thermal_zone_device_unregister(int34x_zone->zone);
err_thermal_zone:
- kfree(int34x_zone->trips);
acpi_lpat_free_conversion_table(int34x_zone->lpat_table);
err_trips_alloc:
- kfree(int34x_zone->ops);
-err_ops_alloc:
kfree(int34x_zone);
return ERR_PTR(ret);
}
@@ -206,51 +193,46 @@ void int340x_thermal_zone_remove(struct int34x_thermal_zone *int34x_zone)
{
thermal_zone_device_unregister(int34x_zone->zone);
acpi_lpat_free_conversion_table(int34x_zone->lpat_table);
- kfree(int34x_zone->trips);
- kfree(int34x_zone->ops);
kfree(int34x_zone);
}
EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove);
-void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone)
+static int int340x_update_one_trip(struct thermal_trip *trip, void *arg)
{
+ struct int34x_thermal_zone *int34x_zone = arg;
struct acpi_device *zone_adev = int34x_zone->adev;
- struct thermal_trip *zone_trips = int34x_zone->trips;
- int trip_cnt = int34x_zone->zone->num_trips;
- int act_trip_nr = 0;
- int i;
-
- mutex_lock(&int34x_zone->zone->lock);
+ int temp, err;
+
+ switch (trip->type) {
+ case THERMAL_TRIP_CRITICAL:
+ err = thermal_acpi_critical_trip_temp(zone_adev, &temp);
+ break;
+ case THERMAL_TRIP_HOT:
+ err = thermal_acpi_hot_trip_temp(zone_adev, &temp);
+ break;
+ case THERMAL_TRIP_PASSIVE:
+ err = thermal_acpi_passive_trip_temp(zone_adev, &temp);
+ break;
+ case THERMAL_TRIP_ACTIVE:
+ err = thermal_acpi_active_trip_temp(zone_adev,
+ THERMAL_TRIP_PRIV_TO_INT(trip->priv),
+ &temp);
+ break;
+ default:
+ err = -ENODEV;
+ }
+ if (err)
+ temp = THERMAL_TEMP_INVALID;
- for (i = int34x_zone->aux_trip_nr; i < trip_cnt; i++) {
- int temp, err;
+ thermal_zone_set_trip_temp(int34x_zone->zone, trip, temp);
- switch (zone_trips[i].type) {
- case THERMAL_TRIP_CRITICAL:
- err = thermal_acpi_critical_trip_temp(zone_adev, &temp);
- break;
- case THERMAL_TRIP_HOT:
- err = thermal_acpi_hot_trip_temp(zone_adev, &temp);
- break;
- case THERMAL_TRIP_PASSIVE:
- err = thermal_acpi_passive_trip_temp(zone_adev, &temp);
- break;
- case THERMAL_TRIP_ACTIVE:
- err = thermal_acpi_active_trip_temp(zone_adev, act_trip_nr++,
- &temp);
- break;
- default:
- err = -ENODEV;
- }
- if (err) {
- zone_trips[i].temperature = THERMAL_TEMP_INVALID;
- continue;
- }
-
- zone_trips[i].temperature = temp;
- }
+ return 0;
+}
- mutex_unlock(&int34x_zone->zone->lock);
+void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone)
+{
+ thermal_zone_for_each_trip(int34x_zone->zone, int340x_update_one_trip,
+ int34x_zone);
}
EXPORT_SYMBOL_GPL(int340x_thermal_update_trips);
diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h
index e0df6271facc..d504e271009a 100644
--- a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h
+++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h
@@ -20,10 +20,8 @@ struct active_trip {
struct int34x_thermal_zone {
struct acpi_device *adev;
- struct thermal_trip *trips;
int aux_trip_nr;
struct thermal_zone_device *zone;
- struct thermal_zone_device_ops *ops;
void *priv_data;
struct acpi_lpat_conversion_table *lpat_table;
};
diff --git a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c
new file mode 100644
index 000000000000..0ccc72c93499
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device platform temperature controls
+ * Copyright (c) 2025, Intel Corporation.
+ */
+
+/*
+ * Platform temperature controls hardware interface
+ *
+ * The hardware control interface is via MMIO offsets in the processor
+ * thermal device MMIO space. There are three instances of MMIO registers.
+ * All registers are 64 bit wide with RW access.
+ *
+ * Name: PLATFORM_TEMPERATURE_CONTROL
+ * Offsets: 0x5B20, 0x5B28, 0x5B30
+ *
+ * Bits Description
+ * 7:0 TARGET_TEMP : Target temperature limit to which the control
+ * mechanism is regulating. Units: 0.5C.
+ * 8:8 ENABLE: Read current enable status of the feature or enable
+ * feature.
+ * 11:9 GAIN: Sets the aggressiveness of control loop from 0 to 7
+ * 7 graceful, favors performance at the expense of temperature
+ * overshoots
+ * 0 aggressive, favors tight regulation over performance
+ * 12:12 TEMPERATURE_OVERRIDE_EN
+ * When set, hardware will use TEMPERATURE_OVERRIDE values instead
+ * of reading from corresponding sensor.
+ * 15:13 RESERVED
+ * 23:16 MIN_PERFORMANCE_LEVEL: Minimum Performance level below which the
+ * there will be no throttling. 0 - all levels of throttling allowed
+ * including survivability actions. 255 - no throttling allowed.
+ * 31:24 TEMPERATURE_OVERRIDE: Allows SW to override the input temperature.
+ * hardware will use this value instead of the sensor temperature.
+ * Units: 0.5C.
+ * 63:32 RESERVED
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+struct mmio_reg {
+ int bits;
+ u16 mask;
+ u16 shift;
+ u16 units;
+};
+
+#define MAX_ATTR_GROUP_NAME_LEN 32
+#define PTC_MAX_ATTRS 4
+
+struct ptc_data {
+ u32 offset;
+ struct pci_dev *pdev;
+ struct attribute_group ptc_attr_group;
+ struct attribute *ptc_attrs[PTC_MAX_ATTRS];
+ struct device_attribute temperature_target_attr;
+ struct device_attribute enable_attr;
+ struct device_attribute thermal_tolerance_attr;
+ char group_name[MAX_ATTR_GROUP_NAME_LEN];
+};
+
+static const struct mmio_reg ptc_mmio_regs[] = {
+ { 8, 0xFF, 0, 500}, /* temperature_target, units 0.5C*/
+ { 1, 0x01, 8, 0}, /* enable */
+ { 3, 0x7, 9, 0}, /* gain */
+ { 8, 0xFF, 16, 0}, /* min_performance_level */
+ { 1, 0x1, 12, 0}, /* temperature_override_enable */
+ { 8, 0xFF, 24, 500}, /* temperature_override, units 0.5C */
+};
+
+#define PTC_MAX_INSTANCES 3
+
+/* Unique offset for each PTC instance */
+static u32 ptc_offsets[PTC_MAX_INSTANCES] = {0x5B20, 0x5B28, 0x5B30};
+
+/* These will represent sysfs attribute names */
+static const char * const ptc_strings[] = {
+ "temperature_target",
+ "enable",
+ "thermal_tolerance",
+ NULL
+};
+
+/* Lock to protect concurrent read/write and read-modify-write */
+static DEFINE_MUTEX(ptc_lock);
+
+static ssize_t ptc_mmio_show(struct ptc_data *data, struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct proc_thermal_device *proc_priv;
+ const struct mmio_reg *mmio_regs;
+ int ret, units;
+ u64 reg_val;
+
+ proc_priv = pci_get_drvdata(pdev);
+ mmio_regs = ptc_mmio_regs;
+ ret = match_string(ptc_strings, -1, attr->attr.name);
+ if (ret < 0)
+ return ret;
+
+ units = mmio_regs[ret].units;
+
+ guard(mutex)(&ptc_lock);
+
+ reg_val = readq((void __iomem *) (proc_priv->mmio_base + data->offset));
+ ret = (reg_val >> mmio_regs[ret].shift) & mmio_regs[ret].mask;
+ if (units)
+ ret *= units;
+
+ return sysfs_emit(buf, "%d\n", ret);
+}
+
+#define PTC_SHOW(suffix)\
+static ssize_t suffix##_show(struct device *dev,\
+ struct device_attribute *attr,\
+ char *buf)\
+{\
+ struct ptc_data *data = container_of(attr, struct ptc_data, suffix##_attr);\
+ return ptc_mmio_show(data, dev, attr, buf);\
+}
+
+static void ptc_mmio_write(struct pci_dev *pdev, u32 offset, int index, u32 value)
+{
+ struct proc_thermal_device *proc_priv;
+ u64 mask, reg_val;
+
+ proc_priv = pci_get_drvdata(pdev);
+
+ mask = GENMASK_ULL(ptc_mmio_regs[index].shift + ptc_mmio_regs[index].bits - 1,
+ ptc_mmio_regs[index].shift);
+
+ guard(mutex)(&ptc_lock);
+
+ reg_val = readq((void __iomem *) (proc_priv->mmio_base + offset));
+ reg_val &= ~mask;
+ reg_val |= (value << ptc_mmio_regs[index].shift);
+ writeq(reg_val, (void __iomem *) (proc_priv->mmio_base + offset));
+}
+
+static int ptc_store(struct ptc_data *data, struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ unsigned int input;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &input);
+ if (ret)
+ return ret;
+
+ ret = match_string(ptc_strings, -1, attr->attr.name);
+ if (ret < 0)
+ return ret;
+
+ if (ptc_mmio_regs[ret].units)
+ input /= ptc_mmio_regs[ret].units;
+
+ if (input > ptc_mmio_regs[ret].mask)
+ return -EINVAL;
+
+ ptc_mmio_write(pdev, data->offset, ret, input);
+
+ return count;
+}
+
+#define PTC_STORE(suffix)\
+static ssize_t suffix##_store(struct device *dev,\
+ struct device_attribute *attr,\
+ const char *buf, size_t count)\
+{\
+ struct ptc_data *data = container_of(attr, struct ptc_data, suffix##_attr);\
+ return ptc_store(data, dev, attr, buf, count);\
+}
+
+PTC_SHOW(temperature_target);
+PTC_STORE(temperature_target);
+PTC_SHOW(enable);
+PTC_STORE(enable);
+PTC_SHOW(thermal_tolerance);
+PTC_STORE(thermal_tolerance);
+
+#define ptc_init_attribute(_name)\
+ do {\
+ sysfs_attr_init(&data->_name##_attr.attr);\
+ data->_name##_attr.show = _name##_show;\
+ data->_name##_attr.store = _name##_store;\
+ data->_name##_attr.attr.name = #_name;\
+ data->_name##_attr.attr.mode = 0644;\
+ } while (0)
+
+static int ptc_create_groups(struct pci_dev *pdev, int instance, struct ptc_data *data)
+{
+ int ret, index = 0;
+
+ ptc_init_attribute(temperature_target);
+ ptc_init_attribute(enable);
+ ptc_init_attribute(thermal_tolerance);
+
+ data->ptc_attrs[index++] = &data->temperature_target_attr.attr;
+ data->ptc_attrs[index++] = &data->enable_attr.attr;
+ data->ptc_attrs[index++] = &data->thermal_tolerance_attr.attr;
+ data->ptc_attrs[index] = NULL;
+
+ snprintf(data->group_name, MAX_ATTR_GROUP_NAME_LEN,
+ "ptc_%d_control", instance);
+ data->ptc_attr_group.name = data->group_name;
+ data->ptc_attr_group.attrs = data->ptc_attrs;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &data->ptc_attr_group);
+
+ return ret;
+}
+
+static struct ptc_data ptc_instance[PTC_MAX_INSTANCES];
+static struct dentry *ptc_debugfs;
+
+#define PTC_TEMP_OVERRIDE_ENABLE_INDEX 4
+#define PTC_TEMP_OVERRIDE_INDEX 5
+
+static ssize_t ptc_temperature_write(struct file *file, const char __user *data,
+ size_t count, loff_t *ppos)
+{
+ struct ptc_data *ptc_instance = file->private_data;
+ struct pci_dev *pdev = ptc_instance->pdev;
+ char buf[32];
+ ssize_t len;
+ u32 value;
+
+ len = min(count, sizeof(buf) - 1);
+ if (copy_from_user(buf, data, len))
+ return -EFAULT;
+
+ buf[len] = '\0';
+ if (kstrtouint(buf, 0, &value))
+ return -EINVAL;
+
+ if (ptc_mmio_regs[PTC_TEMP_OVERRIDE_INDEX].units)
+ value /= ptc_mmio_regs[PTC_TEMP_OVERRIDE_INDEX].units;
+
+ if (value > ptc_mmio_regs[PTC_TEMP_OVERRIDE_INDEX].mask)
+ return -EINVAL;
+
+ if (!value) {
+ ptc_mmio_write(pdev, ptc_instance->offset, PTC_TEMP_OVERRIDE_ENABLE_INDEX, 0);
+ } else {
+ ptc_mmio_write(pdev, ptc_instance->offset, PTC_TEMP_OVERRIDE_INDEX, value);
+ ptc_mmio_write(pdev, ptc_instance->offset, PTC_TEMP_OVERRIDE_ENABLE_INDEX, 1);
+ }
+
+ return count;
+}
+
+static const struct file_operations ptc_fops = {
+ .open = simple_open,
+ .write = ptc_temperature_write,
+ .llseek = generic_file_llseek,
+};
+
+static void ptc_create_debugfs(void)
+{
+ ptc_debugfs = debugfs_create_dir("platform_temperature_control", NULL);
+
+ debugfs_create_file("temperature_0", 0200, ptc_debugfs, &ptc_instance[0], &ptc_fops);
+ debugfs_create_file("temperature_1", 0200, ptc_debugfs, &ptc_instance[1], &ptc_fops);
+ debugfs_create_file("temperature_2", 0200, ptc_debugfs, &ptc_instance[2], &ptc_fops);
+}
+
+static void ptc_delete_debugfs(void)
+{
+ debugfs_remove_recursive(ptc_debugfs);
+}
+
+int proc_thermal_ptc_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_PTC) {
+ int i;
+
+ for (i = 0; i < PTC_MAX_INSTANCES; i++) {
+ ptc_instance[i].offset = ptc_offsets[i];
+ ptc_instance[i].pdev = pdev;
+ ptc_create_groups(pdev, i, &ptc_instance[i]);
+ }
+
+ ptc_create_debugfs();
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_ptc_add);
+
+void proc_thermal_ptc_remove(struct pci_dev *pdev)
+{
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_PTC) {
+ int i;
+
+ for (i = 0; i < PTC_MAX_INSTANCES; i++)
+ sysfs_remove_group(&pdev->dev.kobj, &ptc_instance[i].ptc_attr_group);
+
+ ptc_delete_debugfs();
+ }
+}
+EXPORT_SYMBOL_GPL(proc_thermal_ptc_remove);
+
+MODULE_IMPORT_NS("INT340X_THERMAL");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Processor Thermal PTC Interface");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
index 3ca0a2f5937f..48e7849d4816 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
@@ -9,6 +9,7 @@
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/thermal.h>
+#include <asm/msr.h>
#include "int340x_thermal_zone.h"
#include "processor_thermal_device.h"
#include "../intel_soc_dts_iosf.h"
@@ -26,6 +27,48 @@ static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \
(unsigned long)proc_dev->power_limits[index].suffix * 1000); \
}
+static ssize_t power_floor_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct proc_thermal_device *proc_dev = dev_get_drvdata(dev);
+ int ret;
+
+ ret = proc_thermal_read_power_floor_status(proc_dev);
+
+ return sysfs_emit(buf, "%d\n", ret);
+}
+
+static ssize_t power_floor_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct proc_thermal_device *proc_dev = dev_get_drvdata(dev);
+ bool ret;
+
+ ret = proc_thermal_power_floor_get_state(proc_dev);
+
+ return sysfs_emit(buf, "%d\n", ret);
+}
+
+static ssize_t power_floor_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct proc_thermal_device *proc_dev = dev_get_drvdata(dev);
+ u8 state;
+ int ret;
+
+ if (kstrtou8(buf, 0, &state))
+ return -EINVAL;
+
+ ret = proc_thermal_power_floor_set_state(proc_dev, !!state);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
POWER_LIMIT_SHOW(0, min_uw)
POWER_LIMIT_SHOW(0, max_uw)
POWER_LIMIT_SHOW(0, step_uw)
@@ -50,6 +93,9 @@ static DEVICE_ATTR_RO(power_limit_1_step_uw);
static DEVICE_ATTR_RO(power_limit_1_tmin_us);
static DEVICE_ATTR_RO(power_limit_1_tmax_us);
+static DEVICE_ATTR_RO(power_floor_status);
+static DEVICE_ATTR_RW(power_floor_enable);
+
static struct attribute *power_limit_attrs[] = {
&dev_attr_power_limit_0_min_uw.attr,
&dev_attr_power_limit_1_min_uw.attr,
@@ -61,12 +107,30 @@ static struct attribute *power_limit_attrs[] = {
&dev_attr_power_limit_1_tmin_us.attr,
&dev_attr_power_limit_0_tmax_us.attr,
&dev_attr_power_limit_1_tmax_us.attr,
+ &dev_attr_power_floor_status.attr,
+ &dev_attr_power_floor_enable.attr,
NULL
};
+static umode_t power_limit_attr_visible(struct kobject *kobj, struct attribute *attr, int unused)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct proc_thermal_device *proc_dev;
+
+ if (attr != &dev_attr_power_floor_status.attr && attr != &dev_attr_power_floor_enable.attr)
+ return attr->mode;
+
+ proc_dev = dev_get_drvdata(dev);
+ if (!proc_dev || !(proc_dev->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR))
+ return 0;
+
+ return attr->mode;
+}
+
static const struct attribute_group power_limit_attribute_group = {
.attrs = power_limit_attrs,
- .name = "power_limits"
+ .name = "power_limits",
+ .is_visible = power_limit_attr_visible,
};
static ssize_t tcc_offset_degree_celsius_show(struct device *dev,
@@ -90,7 +154,7 @@ static ssize_t tcc_offset_degree_celsius_store(struct device *dev,
u64 val;
int err;
- err = rdmsrl_safe(MSR_PLATFORM_INFO, &val);
+ err = rdmsrq_safe(MSR_PLATFORM_INFO, &val);
if (err)
return err;
@@ -113,14 +177,14 @@ static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone,
int *temp)
{
int cpu;
- int curr_temp;
+ int curr_temp, ret;
*temp = 0;
for_each_online_cpu(cpu) {
- curr_temp = intel_tcc_get_temp(cpu, false);
- if (curr_temp < 0)
- return curr_temp;
+ ret = intel_tcc_get_temp(cpu, &curr_temp, false);
+ if (ret < 0)
+ return ret;
if (!*temp || curr_temp > *temp)
*temp = curr_temp;
}
@@ -274,10 +338,17 @@ static int tcc_offset_save = -1;
int proc_thermal_suspend(struct device *dev)
{
+ struct proc_thermal_device *proc_dev;
+
tcc_offset_save = intel_tcc_get_offset(-1);
if (tcc_offset_save < 0)
dev_warn(dev, "failed to save offset (%d)\n", tcc_offset_save);
+ proc_dev = dev_get_drvdata(dev);
+
+ if (proc_dev->mmio_feature_mask & PROC_THERMAL_FEATURE_SOC_POWER_SLIDER)
+ proc_thermal_soc_power_slider_suspend(proc_dev);
+
return 0;
}
EXPORT_SYMBOL_GPL(proc_thermal_suspend);
@@ -293,6 +364,9 @@ int proc_thermal_resume(struct device *dev)
if (tcc_offset_save >= 0)
intel_tcc_set_offset(-1, tcc_offset_save);
+ if (proc_dev->mmio_feature_mask & PROC_THERMAL_FEATURE_SOC_POWER_SLIDER)
+ proc_thermal_soc_power_slider_resume(proc_dev);
+
return 0;
}
EXPORT_SYMBOL_GPL(proc_thermal_resume);
@@ -336,28 +410,54 @@ int proc_thermal_mmio_add(struct pci_dev *pdev,
}
}
+ if (feature_mask & PROC_THERMAL_FEATURE_PTC) {
+ ret = proc_thermal_ptc_add(pdev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add PTC MMIO interface\n");
+ goto err_rem_rapl;
+ }
+ }
+
if (feature_mask & PROC_THERMAL_FEATURE_FIVR ||
feature_mask & PROC_THERMAL_FEATURE_DVFS ||
feature_mask & PROC_THERMAL_FEATURE_DLVR) {
ret = proc_thermal_rfim_add(pdev, proc_priv);
if (ret) {
dev_err(&pdev->dev, "failed to add RFIM interface\n");
- goto err_rem_rapl;
+ goto err_rem_ptc;
}
}
- if (feature_mask & PROC_THERMAL_FEATURE_MBOX) {
- ret = proc_thermal_mbox_add(pdev, proc_priv);
+ if (feature_mask & PROC_THERMAL_FEATURE_WT_REQ) {
+ ret = proc_thermal_wt_req_add(pdev, proc_priv);
if (ret) {
dev_err(&pdev->dev, "failed to add MBOX interface\n");
goto err_rem_rfim;
}
+ } else if (feature_mask & PROC_THERMAL_FEATURE_WT_HINT) {
+ ret = proc_thermal_wt_hint_add(pdev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add WT Hint\n");
+ goto err_rem_rfim;
+ }
+ }
+
+ if (feature_mask & PROC_THERMAL_FEATURE_SOC_POWER_SLIDER) {
+ ret = proc_thermal_soc_power_slider_add(pdev, proc_priv);
+ if (ret) {
+ dev_info(&pdev->dev, "failed to add soc power efficiency slider\n");
+ goto err_rem_wlt;
+ }
}
return 0;
+err_rem_wlt:
+ proc_thermal_wt_hint_remove(pdev);
err_rem_rfim:
proc_thermal_rfim_remove(pdev);
+err_rem_ptc:
+ proc_thermal_ptc_remove(pdev);
err_rem_rapl:
proc_thermal_rapl_remove();
@@ -370,16 +470,26 @@ void proc_thermal_mmio_remove(struct pci_dev *pdev, struct proc_thermal_device *
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_RAPL)
proc_thermal_rapl_remove();
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_PTC)
+ proc_thermal_ptc_remove(pdev);
+
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR ||
- proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS)
+ proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS ||
+ proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR)
proc_thermal_rfim_remove(pdev);
- if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_MBOX)
- proc_thermal_mbox_remove(pdev);
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR)
+ proc_thermal_power_floor_set_state(proc_priv, false);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_REQ)
+ proc_thermal_wt_req_remove(pdev);
+ else if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_HINT)
+ proc_thermal_wt_hint_remove(pdev);
}
EXPORT_SYMBOL_GPL(proc_thermal_mmio_remove);
-MODULE_IMPORT_NS(INTEL_TCC);
+MODULE_IMPORT_NS("INTEL_TCC");
+MODULE_IMPORT_NS("INT340X_THERMAL");
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.h b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h
index 7acaa8f1b896..b79937a386ec 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h
@@ -10,6 +10,7 @@
#include <linux/intel_rapl.h>
#define PCI_DEVICE_ID_INTEL_ADL_THERMAL 0x461d
+#define PCI_DEVICE_ID_INTEL_ARL_S_THERMAL 0xAD03
#define PCI_DEVICE_ID_INTEL_BDW_THERMAL 0x1603
#define PCI_DEVICE_ID_INTEL_BSW_THERMAL 0x22DC
@@ -24,10 +25,15 @@
#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_LNLM_THERMAL 0x641D
#define PCI_DEVICE_ID_INTEL_MTLP_THERMAL 0x7D03
+#define PCI_DEVICE_ID_INTEL_NVL_H_THERMAL 0xD703
+#define PCI_DEVICE_ID_INTEL_NVL_S_THERMAL 0xAD03
#define PCI_DEVICE_ID_INTEL_RPL_THERMAL 0xA71D
#define PCI_DEVICE_ID_INTEL_SKL_THERMAL 0x1903
#define PCI_DEVICE_ID_INTEL_TGL_THERMAL 0x9A03
+#define PCI_DEVICE_ID_INTEL_PTL_THERMAL 0xB01D
+#define PCI_DEVICE_ID_INTEL_WCL_THERMAL 0xFD1D
struct power_config {
u32 index;
@@ -59,8 +65,13 @@ struct rapl_mmio_regs {
#define PROC_THERMAL_FEATURE_RAPL 0x01
#define PROC_THERMAL_FEATURE_FIVR 0x02
#define PROC_THERMAL_FEATURE_DVFS 0x04
-#define PROC_THERMAL_FEATURE_MBOX 0x08
+#define PROC_THERMAL_FEATURE_WT_REQ 0x08
#define PROC_THERMAL_FEATURE_DLVR 0x10
+#define PROC_THERMAL_FEATURE_WT_HINT 0x20
+#define PROC_THERMAL_FEATURE_POWER_FLOOR 0x40
+#define PROC_THERMAL_FEATURE_MSI_SUPPORT 0x80
+#define PROC_THERMAL_FEATURE_PTC 0x100
+#define PROC_THERMAL_FEATURE_SOC_POWER_SLIDER 0x200
#if IS_ENABLED(CONFIG_PROC_THERMAL_MMIO_RAPL)
int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
@@ -80,17 +91,48 @@ static void __maybe_unused proc_thermal_rapl_remove(void)
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 proc_thermal_wt_req_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_wt_req_remove(struct pci_dev *pdev);
+
+#define MBOX_CMD_WORKLOAD_TYPE_READ 0x0E
+#define MBOX_CMD_WORKLOAD_TYPE_WRITE 0x0F
+
+#define MBOX_DATA_BIT_AC_DC 30
+#define MBOX_DATA_BIT_VALID 31
+
+#define SOC_WT_RES_INT_STATUS_OFFSET 0x5B18
+#define SOC_WT_RES_INT_STATUS_MASK GENMASK_ULL(3, 2)
+
+int proc_thermal_read_power_floor_status(struct proc_thermal_device *proc_priv);
+int proc_thermal_power_floor_set_state(struct proc_thermal_device *proc_priv, bool enable);
+bool proc_thermal_power_floor_get_state(struct proc_thermal_device *proc_priv);
+void proc_thermal_power_floor_intr_callback(struct pci_dev *pdev,
+ struct proc_thermal_device *proc_priv);
+bool proc_thermal_check_power_floor_intr(struct proc_thermal_device *proc_priv);
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 processor_thermal_mbox_interrupt_config(struct pci_dev *pdev, bool enable, int enable_bit,
+ int time_window);
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_wt_hint_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_wt_hint_remove(struct pci_dev *pdev);
+void proc_thermal_wt_intr_callback(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+bool proc_thermal_check_wt_intr(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);
+int proc_thermal_ptc_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_ptc_remove(struct pci_dev *pdev);
+
+int proc_thermal_soc_power_slider_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_soc_power_slider_suspend(struct proc_thermal_device *proc_priv);
+void proc_thermal_soc_power_slider_resume(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
index 0d1e98007270..0d4dcc66e097 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c
@@ -15,6 +15,11 @@
#define DRV_NAME "proc_thermal_pci"
+static bool use_msi;
+module_param(use_msi, bool, 0644);
+MODULE_PARM_DESC(use_msi,
+ "Use PCI MSI based interrupts for processor thermal device.");
+
struct proc_thermal_pci {
struct pci_dev *pdev;
struct proc_thermal_device *proc_priv;
@@ -58,6 +63,18 @@ static struct proc_thermal_mmio_info proc_thermal_mmio_info[] = {
{ PROC_THERMAL_MMIO_INT_STATUS_1, 0x7200, 8, 0x01 },
};
+/* List of supported MSI IDs (sources) */
+enum proc_thermal_msi_ids {
+ PKG_THERMAL,
+ DDR_THERMAL,
+ THERM_POWER_FLOOR,
+ WORKLOAD_CHANGE,
+ MSI_THERMAL_MAX
+};
+
+/* Stores IRQ associated with a MSI ID */
+static int proc_thermal_msi_map[MSI_THERMAL_MAX];
+
#define B0D4_THERMAL_NOTIFY_DELAY 1000
static int notify_delay_ms = B0D4_THERMAL_NOTIFY_DELAY;
@@ -117,20 +134,84 @@ static void pkg_thermal_schedule_work(struct delayed_work *work)
schedule_delayed_work(work, ms);
}
+static void proc_thermal_clear_soc_int_status(struct proc_thermal_device *proc_priv)
+{
+ u64 status;
+
+ if (!(proc_priv->mmio_feature_mask &
+ (PROC_THERMAL_FEATURE_WT_HINT | PROC_THERMAL_FEATURE_POWER_FLOOR)))
+ return;
+
+ status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+ writeq(status & ~SOC_WT_RES_INT_STATUS_MASK,
+ proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+}
+
+static irqreturn_t proc_thermal_irq_thread_handler(int irq, void *devid)
+{
+ struct proc_thermal_pci *pci_info = devid;
+
+ proc_thermal_wt_intr_callback(pci_info->pdev, pci_info->proc_priv);
+ proc_thermal_power_floor_intr_callback(pci_info->pdev, pci_info->proc_priv);
+ proc_thermal_clear_soc_int_status(pci_info->proc_priv);
+
+ return IRQ_HANDLED;
+}
+
+static int proc_thermal_match_msi_irq(int irq)
+{
+ int i;
+
+ if (!use_msi)
+ goto msi_fail;
+
+ for (i = 0; i < MSI_THERMAL_MAX; i++) {
+ if (proc_thermal_msi_map[i] == irq)
+ return i;
+ }
+
+msi_fail:
+ return -EOPNOTSUPP;
+}
+
static irqreturn_t proc_thermal_irq_handler(int irq, void *devid)
{
struct proc_thermal_pci *pci_info = devid;
+ struct proc_thermal_device *proc_priv;
+ int ret = IRQ_NONE, msi_id;
u32 status;
+ proc_priv = pci_info->proc_priv;
+
+ msi_id = proc_thermal_match_msi_irq(irq);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_HINT) {
+ if (msi_id == WORKLOAD_CHANGE || proc_thermal_check_wt_intr(pci_info->proc_priv))
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR) {
+ if (msi_id == THERM_POWER_FLOOR ||
+ proc_thermal_check_power_floor_intr(pci_info->proc_priv))
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ /*
+ * Since now there are two sources of interrupts: one from thermal threshold
+ * and another from workload hint, add a check if there was really a threshold
+ * interrupt before scheduling work function for thermal threshold.
+ */
proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_INT_STATUS_0, &status);
+ if (msi_id == PKG_THERMAL || status) {
+ /* Disable enable interrupt flag */
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 0);
+ pkg_thermal_schedule_work(&pci_info->work);
+ ret = IRQ_HANDLED;
+ }
- /* 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;
+ return ret;
}
static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
@@ -144,7 +225,8 @@ static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
return 0;
}
-static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
+static int sys_set_trip_temp(struct thermal_zone_device *tzd,
+ const struct thermal_trip *trip, int temp)
{
struct proc_thermal_pci *pci_info = thermal_zone_device_priv(tzd);
int tjmax, _temp;
@@ -184,24 +266,74 @@ static int get_trip_temp(struct proc_thermal_pci *pci_info)
return temp;
}
-static struct thermal_trip psv_trip = {
- .type = THERMAL_TRIP_PASSIVE,
-};
-
-static struct thermal_zone_device_ops tzone_ops = {
+static const struct thermal_zone_device_ops tzone_ops = {
.get_temp = sys_get_curr_temp,
.set_trip_temp = sys_set_trip_temp,
};
static struct thermal_zone_params tzone_params = {
- .governor_name = "user_space",
.no_hwmon = true,
};
+static bool msi_irq;
+
+static void proc_thermal_free_msi(struct pci_dev *pdev, struct proc_thermal_pci *pci_info)
+{
+ int i;
+
+ for (i = 0; i < MSI_THERMAL_MAX; i++) {
+ if (proc_thermal_msi_map[i])
+ devm_free_irq(&pdev->dev, proc_thermal_msi_map[i], pci_info);
+ }
+
+ pci_free_irq_vectors(pdev);
+}
+
+static int proc_thermal_setup_msi(struct pci_dev *pdev, struct proc_thermal_pci *pci_info)
+{
+ int ret, i, irq, count;
+
+ count = pci_alloc_irq_vectors(pdev, 1, MSI_THERMAL_MAX, PCI_IRQ_MSI | PCI_IRQ_MSIX);
+ if (count < 0) {
+ dev_err(&pdev->dev, "Failed to allocate vectors!\n");
+ return count;
+ }
+
+ dev_info(&pdev->dev, "msi enabled:%d msix enabled:%d\n", pdev->msi_enabled,
+ pdev->msix_enabled);
+
+ for (i = 0; i < count; i++) {
+ irq = pci_irq_vector(pdev, i);
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler,
+ proc_thermal_irq_thread_handler,
+ 0, KBUILD_MODNAME, pci_info);
+ if (ret) {
+ dev_err(&pdev->dev, "Request IRQ %d failed\n", irq);
+ goto err_free_msi_vectors;
+ }
+
+ proc_thermal_msi_map[i] = irq;
+ }
+
+ msi_irq = true;
+
+ return 0;
+
+err_free_msi_vectors:
+ proc_thermal_free_msi(pdev, pci_info);
+
+ return ret;
+}
+
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;
+ struct thermal_trip psv_trip = {
+ .type = THERMAL_TRIP_PASSIVE,
+ .flags = THERMAL_TRIP_FLAG_RW_TEMP,
+ };
int irq_flag = 0, irq, ret;
proc_priv = devm_kzalloc(&pdev->dev, sizeof(*proc_priv), GFP_KERNEL);
@@ -223,47 +355,49 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_
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;
+ return ret;
+
+ 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;
+ }
psv_trip.temperature = get_trip_temp(pci_info);
pci_info->tzone = thermal_zone_device_register_with_trips("TCPU_PCI", &psv_trip,
- 1, 1, pci_info,
+ 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;
+ goto err_del_legacy;
}
- /* 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;
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_MSI_SUPPORT)
+ use_msi = true;
- 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;
+ if (use_msi) {
+ ret = proc_thermal_setup_msi(pdev, pci_info);
+ if (ret)
+ goto err_ret_tzone;
+ } else {
+ irq_flag = IRQF_SHARED;
+ irq = pdev->irq;
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler,
+ proc_thermal_irq_thread_handler, irq_flag,
+ KBUILD_MODNAME, pci_info);
+ if (ret) {
+ dev_err(&pdev->dev, "Request IRQ %d failed\n", pdev->irq);
+ goto err_ret_tzone;
+ }
}
ret = thermal_zone_device_enable(pci_info->tzone);
@@ -273,15 +407,14 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_
return 0;
err_free_vectors:
- pci_free_irq_vectors(pdev);
+ if (msi_irq)
+ proc_thermal_free_msi(pdev, pci_info);
err_ret_tzone:
thermal_zone_device_unregister(pci_info->tzone);
-err_ret_mmio:
- proc_thermal_mmio_remove(pdev, proc_priv);
-err_ret_thermal:
+err_del_legacy:
if (!pci_info->no_legacy)
proc_thermal_remove(proc_priv);
- pci_disable_device(pdev);
+ proc_thermal_mmio_remove(pdev, proc_priv);
return ret;
}
@@ -296,14 +429,13 @@ static void proc_thermal_pci_remove(struct pci_dev *pdev)
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);
+ if (msi_irq)
+ proc_thermal_free_msi(pdev, pci_info);
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
@@ -350,9 +482,38 @@ 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 | PROC_THERMAL_FEATURE_DLVR) },
- { PCI_DEVICE_DATA(INTEL, RPL_THERMAL, PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_MBOX) },
+ { PCI_DEVICE_DATA(INTEL, ADL_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_WT_REQ) },
+ { PCI_DEVICE_DATA(INTEL, LNLM_THERMAL, PROC_THERMAL_FEATURE_MSI_SUPPORT |
+ PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_DLVR | PROC_THERMAL_FEATURE_DVFS |
+ PROC_THERMAL_FEATURE_WT_HINT | PROC_THERMAL_FEATURE_POWER_FLOOR |
+ PROC_THERMAL_FEATURE_PTC) },
+ { PCI_DEVICE_DATA(INTEL, MTLP_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_DLVR |
+ PROC_THERMAL_FEATURE_WT_HINT | PROC_THERMAL_FEATURE_POWER_FLOOR) },
+ { PCI_DEVICE_DATA(INTEL, ARL_S_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_DLVR | PROC_THERMAL_FEATURE_WT_HINT) },
+ { PCI_DEVICE_DATA(INTEL, RPL_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_WT_REQ) },
+ { PCI_DEVICE_DATA(INTEL, PTL_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_DLVR | PROC_THERMAL_FEATURE_DVFS |
+ PROC_THERMAL_FEATURE_MSI_SUPPORT | PROC_THERMAL_FEATURE_WT_HINT |
+ PROC_THERMAL_FEATURE_POWER_FLOOR | PROC_THERMAL_FEATURE_PTC |
+ PROC_THERMAL_FEATURE_SOC_POWER_SLIDER) },
+ { PCI_DEVICE_DATA(INTEL, WCL_THERMAL, PROC_THERMAL_FEATURE_MSI_SUPPORT |
+ PROC_THERMAL_FEATURE_RAPL | PROC_THERMAL_FEATURE_DLVR |
+ PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_WT_HINT |
+ PROC_THERMAL_FEATURE_POWER_FLOOR | PROC_THERMAL_FEATURE_PTC) },
+ { PCI_DEVICE_DATA(INTEL, NVL_H_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_DLVR | PROC_THERMAL_FEATURE_DVFS |
+ PROC_THERMAL_FEATURE_MSI_SUPPORT | PROC_THERMAL_FEATURE_WT_HINT |
+ PROC_THERMAL_FEATURE_POWER_FLOOR | PROC_THERMAL_FEATURE_PTC |
+ PROC_THERMAL_FEATURE_SOC_POWER_SLIDER) },
+ { PCI_DEVICE_DATA(INTEL, NVL_S_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_DLVR | PROC_THERMAL_FEATURE_DVFS |
+ PROC_THERMAL_FEATURE_MSI_SUPPORT | PROC_THERMAL_FEATURE_WT_HINT |
+ PROC_THERMAL_FEATURE_POWER_FLOOR | PROC_THERMAL_FEATURE_PTC |
+ PROC_THERMAL_FEATURE_SOC_POWER_SLIDER) },
{ },
};
@@ -368,6 +529,8 @@ static struct pci_driver proc_thermal_pci_driver = {
module_pci_driver(proc_thermal_pci_driver);
+MODULE_IMPORT_NS("INT340X_THERMAL");
+
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
index 09e032f822f3..ccfdd2f9d973 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c
@@ -59,7 +59,7 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev,
* 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);
+ INTEL_SOC_DTS_INTERRUPT_MSI, false, 0);
if (!IS_ERR(proc_priv->soc_dts) && pdev->irq) {
ret = pci_enable_msi(pdev);
@@ -137,7 +137,8 @@ static const struct pci_device_id proc_thermal_pci_ids[] = {
{ 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) },
+ { PCI_DEVICE_DATA(INTEL, TGL_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_WT_REQ) },
{ },
};
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c
index 0b89a4340ff4..b1d531ef440f 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c
@@ -10,18 +10,12 @@
#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)
@@ -51,23 +45,16 @@ static int send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 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;
+ return ret;
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;
+ return wait_for_mbox_ready(proc_priv);
}
static int send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp)
@@ -77,12 +64,9 @@ static int send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp)
int ret;
proc_priv = pci_get_drvdata(pdev);
-
- mutex_lock(&mbox_lock);
-
ret = wait_for_mbox_ready(proc_priv);
if (ret)
- goto unlock_mbox;
+ return ret;
/* Write command register */
reg_data = BIT_ULL(MBOX_BUSY_BIT) | id;
@@ -90,152 +74,86 @@ static int send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp)
ret = wait_for_mbox_ready(proc_priv);
if (ret)
- goto unlock_mbox;
+ return ret;
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;
+ return 0;
}
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++]);
+ int ret;
- ret += sprintf(&buf[ret], "\n");
+ mutex_lock(&mbox_lock);
+ ret = send_mbox_read_cmd(pdev, id, resp);
+ mutex_unlock(&mbox_lock);
return ret;
}
+EXPORT_SYMBOL_NS_GPL(processor_thermal_send_mbox_read_cmd, "INT340X_THERMAL");
-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)
+int processor_thermal_send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data)
{
- 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;
+ int ret;
- ret = send_mbox_write_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_WRITE, data);
- if (ret)
- return false;
+ mutex_lock(&mbox_lock);
+ ret = send_mbox_write_cmd(pdev, id, data);
+ mutex_unlock(&mbox_lock);
- return count;
+ return ret;
}
+EXPORT_SYMBOL_NS_GPL(processor_thermal_send_mbox_write_cmd, "INT340X_THERMAL");
+
+#define MBOX_CAMARILLO_RD_INTR_CONFIG 0x1E
+#define MBOX_CAMARILLO_WR_INTR_CONFIG 0x1F
+#define WLT_TW_MASK GENMASK_ULL(30, 24)
+#define SOC_PREDICTION_TW_SHIFT 24
-static ssize_t workload_type_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+int processor_thermal_mbox_interrupt_config(struct pci_dev *pdev, bool enable,
+ int enable_bit, int time_window)
{
- struct pci_dev *pdev = to_pci_dev(dev);
- u64 cmd_resp;
+ u64 data;
int ret;
- ret = send_mbox_read_cmd(pdev, MBOX_CMD_WORKLOAD_TYPE_READ, &cmd_resp);
- if (ret)
- return false;
-
- cmd_resp &= 0xff;
+ if (!pdev)
+ return -ENODEV;
- 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);
+ mutex_lock(&mbox_lock);
-static struct attribute *workload_req_attrs[] = {
- &dev_attr_workload_available_types.attr,
- &dev_attr_workload_type.attr,
- NULL
-};
+ /* Do read modify write for MBOX_CAMARILLO_RD_INTR_CONFIG */
-static const struct attribute_group workload_req_attribute_group = {
- .attrs = workload_req_attrs,
- .name = "workload_request"
-};
+ ret = send_mbox_read_cmd(pdev, MBOX_CAMARILLO_RD_INTR_CONFIG, &data);
+ if (ret) {
+ dev_err(&pdev->dev, "MBOX_CAMARILLO_RD_INTR_CONFIG failed\n");
+ goto unlock;
+ }
-static bool workload_req_created;
+ if (time_window >= 0) {
+ data &= ~WLT_TW_MASK;
-int proc_thermal_mbox_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
-{
- u64 cmd_resp;
- int ret;
+ /* Program notification delay */
+ data |= ((u64)time_window << SOC_PREDICTION_TW_SHIFT) & WLT_TW_MASK;
+ }
- /* 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;
+ if (enable)
+ data |= BIT(enable_bit);
+ else
+ data &= ~BIT(enable_bit);
- ret = sysfs_create_group(&pdev->dev.kobj, &workload_req_attribute_group);
+ ret = send_mbox_write_cmd(pdev, MBOX_CAMARILLO_WR_INTR_CONFIG, data);
if (ret)
- return ret;
-
- workload_req_created = true;
+ dev_err(&pdev->dev, "MBOX_CAMARILLO_WR_INTR_CONFIG failed\n");
- 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;
+unlock:
+ mutex_unlock(&mbox_lock);
+ return ret;
}
-EXPORT_SYMBOL_GPL(proc_thermal_mbox_remove);
+EXPORT_SYMBOL_NS_GPL(processor_thermal_mbox_interrupt_config, "INT340X_THERMAL");
MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Processor Thermal Mail Box Interface");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_power_floor.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_power_floor.c
new file mode 100644
index 000000000000..25cdbb6d91a6
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_power_floor.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Processor thermal device module for registering and processing
+ * power floor. When the hardware reduces the power to the minimum
+ * possible, the power floor is notified via an interrupt.
+ *
+ * Operation:
+ * When user space enables power floor reporting:
+ * - Use mailbox to:
+ * Enable processor thermal device interrupt
+ *
+ * - Current status of power floor is read from offset 0x5B18
+ * bit 39.
+ *
+ * Two interface functions are provided to call when there is a
+ * thermal device interrupt:
+ * - proc_thermal_power_floor_intr():
+ * Check if the interrupt is for change in power floor.
+ * Called from interrupt context.
+ *
+ * - proc_thermal_power_floor_intr_callback():
+ * Callback for interrupt processing in thread context. This involves
+ * sending notification to user space that there is a change in the
+ * power floor status.
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+#define SOC_POWER_FLOOR_STATUS BIT(39)
+#define SOC_POWER_FLOOR_SHIFT 39
+
+#define SOC_POWER_FLOOR_INT_ENABLE_BIT 31
+#define SOC_POWER_FLOOR_INT_ACTIVE BIT(3)
+
+int proc_thermal_read_power_floor_status(struct proc_thermal_device *proc_priv)
+{
+ u64 status = 0;
+
+ status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+ return (status & SOC_POWER_FLOOR_STATUS) >> SOC_POWER_FLOOR_SHIFT;
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_read_power_floor_status, "INT340X_THERMAL");
+
+static bool enable_state;
+static DEFINE_MUTEX(pf_lock);
+
+int proc_thermal_power_floor_set_state(struct proc_thermal_device *proc_priv, bool enable)
+{
+ int ret = 0;
+
+ mutex_lock(&pf_lock);
+ if (enable_state == enable)
+ goto pf_unlock;
+
+ /*
+ * Time window parameter is not applicable to power floor interrupt configuration.
+ * Hence use -1 for time window.
+ */
+ ret = processor_thermal_mbox_interrupt_config(to_pci_dev(proc_priv->dev), enable,
+ SOC_POWER_FLOOR_INT_ENABLE_BIT, -1);
+ if (!ret)
+ enable_state = enable;
+
+pf_unlock:
+ mutex_unlock(&pf_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_power_floor_set_state, "INT340X_THERMAL");
+
+bool proc_thermal_power_floor_get_state(struct proc_thermal_device *proc_priv)
+{
+ return enable_state;
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_power_floor_get_state, "INT340X_THERMAL");
+
+/**
+ * proc_thermal_check_power_floor_intr() - Check power floor interrupt.
+ * @proc_priv: Processor thermal device instance.
+ *
+ * Callback to check if the interrupt for power floor is active.
+ *
+ * Context: Called from interrupt context.
+ *
+ * Return: true if power floor is active, false when not active.
+ */
+bool proc_thermal_check_power_floor_intr(struct proc_thermal_device *proc_priv)
+{
+ u64 int_status;
+
+ int_status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+ return !!(int_status & SOC_POWER_FLOOR_INT_ACTIVE);
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_check_power_floor_intr, "INT340X_THERMAL");
+
+/**
+ * proc_thermal_power_floor_intr_callback() - Process power floor notification
+ * @pdev: PCI device instance
+ * @proc_priv: Processor thermal device instance.
+ *
+ * Check if the power floor interrupt is active, if active send notification to
+ * user space for the attribute "power_limits", so that user can read the attribute
+ * and take action.
+ *
+ * Context: Called from interrupt thread context.
+ *
+ * Return: None.
+ */
+void proc_thermal_power_floor_intr_callback(struct pci_dev *pdev,
+ struct proc_thermal_device *proc_priv)
+{
+ u64 status;
+
+ status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+ if (!(status & SOC_POWER_FLOOR_INT_ACTIVE))
+ return;
+
+ sysfs_notify(&pdev->dev.kobj, "power_limits", "power_floor_status");
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_power_floor_intr_callback, "INT340X_THERMAL");
+
+MODULE_IMPORT_NS("INT340X_THERMAL");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Processor Thermal power floor notification Interface");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c
index 013f1633f082..bf51a17c5be6 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c
@@ -13,54 +13,18 @@ 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_PACKAGE] = { 0x59a0, 0x593c, 0x58f0, 0, 0x5930, 0x59b0},
.regs[RAPL_DOMAIN_DRAM] = { 0x58e0, 0x58e8, 0x58ec, 0, 0},
- .limits[RAPL_DOMAIN_PACKAGE] = BIT(POWER_LIMIT2),
+ .limits[RAPL_DOMAIN_PACKAGE] = BIT(POWER_LIMIT2) | BIT(POWER_LIMIT4),
.limits[RAPL_DOMAIN_DRAM] = BIT(POWER_LIMIT2),
};
-static int rapl_mmio_cpu_online(unsigned int cpu)
+static int rapl_mmio_read_raw(int cpu, struct reg_action *ra, bool atomic)
{
- 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, true);
- if (!rp) {
- rp = rapl_add_package(cpu, &rapl_mmio_priv, true);
- 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, true);
- 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)
+ if (!ra->reg.mmio)
return -EINVAL;
- ra->value = readq((void __iomem *)ra->reg);
+ ra->value = readq(ra->reg.mmio);
ra->value &= ra->mask;
return 0;
}
@@ -69,19 +33,20 @@ static int rapl_mmio_write_raw(int cpu, struct reg_action *ra)
{
u64 val;
- if (!ra->reg)
+ if (!ra->reg.mmio)
return -EINVAL;
- val = readq((void __iomem *)ra->reg);
+ val = readq(ra->reg.mmio);
val &= ~ra->mask;
val |= ra->value;
- writeq(val, (void __iomem *)ra->reg);
+ writeq(val, ra->reg.mmio);
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;
+ struct rapl_package *rp;
enum rapl_domain_reg_id reg;
enum rapl_domain_type domain;
int ret;
@@ -92,13 +57,13 @@ int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc
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_mmio_priv.regs[domain][reg].mmio =
+ proc_priv->mmio_base +
rapl_regs->regs[domain][reg];
rapl_mmio_priv.limits[domain] = rapl_regs->limits[domain];
}
rapl_mmio_priv.type = RAPL_IF_MMIO;
- rapl_mmio_priv.reg_unit = (u64)proc_priv->mmio_base + rapl_regs->reg_unit;
+ rapl_mmio_priv.reg_unit.mmio = 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;
@@ -109,27 +74,41 @@ int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc
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;
+ /* Register a RAPL package device for package 0 which is always online */
+ rp = rapl_find_package_domain(0, &rapl_mmio_priv, false);
+ if (rp) {
+ ret = -EEXIST;
+ goto err;
+ }
+
+ rp = rapl_add_package(0, &rapl_mmio_priv, false);
+ if (IS_ERR(rp)) {
+ ret = PTR_ERR(rp);
+ goto err;
}
- rapl_mmio_priv.pcap_rapl_online = ret;
return 0;
+
+err:
+ powercap_unregister_control_type(rapl_mmio_priv.control_type);
+ rapl_mmio_priv.control_type = NULL;
+ return ret;
}
EXPORT_SYMBOL_GPL(proc_thermal_rapl_add);
void proc_thermal_rapl_remove(void)
{
+ struct rapl_package *rp;
+
if (IS_ERR_OR_NULL(rapl_mmio_priv.control_type))
return;
- cpuhp_remove_state(rapl_mmio_priv.pcap_rapl_online);
+ rp = rapl_find_package_domain(0, &rapl_mmio_priv, false);
+ if (rp)
+ rapl_remove_package(rp);
powercap_unregister_control_type(rapl_mmio_priv.control_type);
}
EXPORT_SYMBOL_GPL(proc_thermal_rapl_remove);
MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("RAPL interface using MMIO");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c
index 546b70434004..589a3a71f0c4 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c
@@ -9,7 +9,7 @@
#include <linux/pci.h>
#include "processor_thermal_device.h"
-MODULE_IMPORT_NS(INT340X_THERMAL);
+MODULE_IMPORT_NS("INT340X_THERMAL");
struct mmio_reg {
int read_only;
@@ -19,6 +19,12 @@ struct mmio_reg {
u16 shift;
};
+struct mapping_table {
+ const char *attr_name;
+ const u32 value;
+ const char *mapped_str;
+};
+
/* These will represent sysfs attribute names */
static const char * const fivr_strings[] = {
"vco_ref_code_lo",
@@ -62,6 +68,89 @@ static const struct mmio_reg dlvr_mmio_regs[] = {
{ 1, 0x15A10, 1, 0x1, 16}, /* dlvr_pll_busy */
};
+static const struct mmio_reg lnl_dlvr_mmio_regs[] = {
+ { 0, 0x5A08, 5, 0x1F, 0}, /* dlvr_spread_spectrum_pct */
+ { 0, 0x5A08, 1, 0x1, 5}, /* dlvr_control_mode */
+ { 0, 0x5A08, 1, 0x1, 6}, /* dlvr_control_lock */
+ { 0, 0x5A08, 1, 0x1, 7}, /* dlvr_rfim_enable */
+ { 0, 0x5A08, 2, 0x3, 8}, /* dlvr_freq_select */
+ { 1, 0x5A10, 2, 0x3, 30}, /* dlvr_hardware_rev */
+ { 1, 0x5A10, 2, 0x3, 0}, /* dlvr_freq_mhz */
+ { 1, 0x5A10, 1, 0x1, 23}, /* dlvr_pll_busy */
+};
+
+static const struct mapping_table lnl_dlvr_mapping[] = {
+ {"dlvr_freq_select", 0, "2227.2"},
+ {"dlvr_freq_select", 1, "2140"},
+ {"dlvr_freq_mhz", 0, "2227.2"},
+ {"dlvr_freq_mhz", 1, "2140"},
+ {NULL, 0, NULL},
+};
+
+static const struct mmio_reg nvl_dlvr_mmio_regs[] = {
+ { 0, 0x19208, 5, 0x1F, 0}, /* dlvr_spread_spectrum_pct */
+ { 0, 0x19208, 1, 0x1, 5}, /* dlvr_control_mode */
+ { 0, 0x19208, 1, 0x1, 6}, /* dlvr_control_lock */
+ { 0, 0x19208, 1, 0x1, 7}, /* dlvr_rfim_enable */
+ { 0, 0x19208, 12, 0xFFF, 8}, /* dlvr_freq_select */
+ { 1, 0x19210, 2, 0x3, 30}, /* dlvr_hardware_rev */
+ { 1, 0x19210, 16, 0xFFFF, 0}, /* dlvr_freq_mhz */
+ { 1, 0x19210, 1, 0x1, 16}, /* dlvr_pll_busy */
+};
+
+static int match_mapping_table(const struct mapping_table *table, const char *attr_name,
+ bool match_int_value, const u32 value, const char *value_str,
+ char **result_str, u32 *result_int)
+{
+ bool attr_matched = false;
+ int i = 0;
+
+ if (!table)
+ return -EOPNOTSUPP;
+
+ while (table[i].attr_name) {
+ if (strncmp(table[i].attr_name, attr_name, strlen(attr_name)))
+ goto match_next;
+
+ attr_matched = true;
+
+ if (match_int_value) {
+ if (table[i].value != value)
+ goto match_next;
+
+ *result_str = (char *)table[i].mapped_str;
+ return 0;
+ }
+
+ if (strncmp(table[i].mapped_str, value_str, strlen(table[i].mapped_str)))
+ goto match_next;
+
+ *result_int = table[i].value;
+
+ return 0;
+match_next:
+ i++;
+ }
+
+ /* If attribute name is matched, then the user space value is invalid */
+ if (attr_matched)
+ return -EINVAL;
+
+ return -EOPNOTSUPP;
+}
+
+static int get_mapped_string(const struct mapping_table *table, const char *attr_name,
+ u32 value, char **result)
+{
+ return match_mapping_table(table, attr_name, true, value, NULL, result, NULL);
+}
+
+static int get_mapped_value(const struct mapping_table *table, const char *attr_name,
+ const char *value, unsigned int *result)
+{
+ return match_mapping_table(table, attr_name, false, 0, value, NULL, result);
+}
+
/* These will represent sysfs attribute names */
static const char * const dvfs_strings[] = {
"rfi_restriction_run_busy",
@@ -88,17 +177,22 @@ static const struct mmio_reg adl_dvfs_mmio_regs[] = {
{ 0, 0x5A40, 1, 0x1, 0}, /* rfi_disable */
};
+static const struct mapping_table *dlvr_mapping;
+static const struct mmio_reg *dlvr_mmio_regs_table;
+
#define RFIM_SHOW(suffix, table)\
static ssize_t suffix##_show(struct device *dev,\
struct device_attribute *attr,\
char *buf)\
{\
+ const struct mmio_reg *mmio_regs = dlvr_mmio_regs_table;\
+ const struct mapping_table *mapping = dlvr_mapping;\
struct proc_thermal_device *proc_priv;\
struct pci_dev *pdev = to_pci_dev(dev);\
- const struct mmio_reg *mmio_regs;\
const char **match_strs;\
+ int ret, err;\
u32 reg_val;\
- int ret;\
+ char *str;\
\
proc_priv = pci_get_drvdata(pdev);\
if (table == 1) {\
@@ -106,7 +200,6 @@ static ssize_t suffix##_show(struct device *dev,\
mmio_regs = adl_dvfs_mmio_regs;\
} else if (table == 2) { \
match_strs = (const char **)dlvr_strings;\
- mmio_regs = dlvr_mmio_regs;\
} else {\
match_strs = (const char **)fivr_strings;\
mmio_regs = tgl_fivr_mmio_regs;\
@@ -116,7 +209,12 @@ static ssize_t suffix##_show(struct device *dev,\
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);\
+ err = get_mapped_string(mapping, attr->attr.name, ret, &str);\
+ if (!err)\
+ return sprintf(buf, "%s\n", str);\
+ if (err == -EOPNOTSUPP)\
+ return sprintf(buf, "%u\n", ret);\
+ return err;\
}
#define RFIM_STORE(suffix, table)\
@@ -124,11 +222,12 @@ static ssize_t suffix##_store(struct device *dev,\
struct device_attribute *attr,\
const char *buf, size_t count)\
{\
+ const struct mmio_reg *mmio_regs = dlvr_mmio_regs_table;\
+ const struct mapping_table *mapping = dlvr_mapping;\
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;\
@@ -139,7 +238,6 @@ static ssize_t suffix##_store(struct device *dev,\
mmio_regs = adl_dvfs_mmio_regs;\
} else if (table == 2) { \
match_strs = (const char **)dlvr_strings;\
- mmio_regs = dlvr_mmio_regs;\
} else {\
match_strs = (const char **)fivr_strings;\
mmio_regs = tgl_fivr_mmio_regs;\
@@ -150,9 +248,14 @@ static ssize_t suffix##_store(struct device *dev,\
return ret;\
if (mmio_regs[ret].read_only)\
return -EPERM;\
- err = kstrtouint(buf, 10, &input);\
- if (err)\
+ err = get_mapped_value(mapping, attr->attr.name, buf, &input);\
+ if (err == -EINVAL)\
return err;\
+ if (err == -EOPNOTSUPP) {\
+ 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;\
@@ -347,6 +450,21 @@ int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc
}
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR) {
+ switch (pdev->device) {
+ case PCI_DEVICE_ID_INTEL_LNLM_THERMAL:
+ case PCI_DEVICE_ID_INTEL_PTL_THERMAL:
+ case PCI_DEVICE_ID_INTEL_WCL_THERMAL:
+ dlvr_mmio_regs_table = lnl_dlvr_mmio_regs;
+ dlvr_mapping = lnl_dlvr_mapping;
+ break;
+ case PCI_DEVICE_ID_INTEL_NVL_H_THERMAL:
+ case PCI_DEVICE_ID_INTEL_NVL_S_THERMAL:
+ dlvr_mmio_regs_table = nvl_dlvr_mmio_regs;
+ break;
+ default:
+ dlvr_mmio_regs_table = dlvr_mmio_regs;
+ break;
+ }
ret = sysfs_create_group(&pdev->dev.kobj, &dlvr_attribute_group);
if (ret)
return ret;
@@ -384,3 +502,4 @@ void proc_thermal_rfim_remove(struct pci_dev *pdev)
EXPORT_SYMBOL_GPL(proc_thermal_rfim_remove);
MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Processor Thermal RFIM Interface");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
new file mode 100644
index 000000000000..49ff3bae7271
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Processor Thermal Device Interface for Reading and Writing
+ * SoC Power Slider Values from User Space.
+ *
+ * Operation:
+ * The SOC_EFFICIENCY_SLIDER_0_0_0_MCHBAR register is accessed
+ * using the MMIO (Memory-Mapped I/O) interface with an MMIO offset of 0x5B38.
+ * Although this register is 64 bits wide, only bits 7:0 are used,
+ * and the other bits remain unchanged.
+ *
+ * Bit definitions
+ *
+ * Bits 2:0 (Slider value):
+ * The SoC optimizer slider value indicates the system wide energy performance
+ * hint. The slider has no specific units and ranges from 0 (highest
+ * performance) to 6 (highest energy efficiency). Value of 7 is reserved.
+ * Bits 3 : Reserved
+ * Bits 6:4 (Offset)
+ * Offset allows the SoC to automatically switch slider position in range
+ * [slider value (bits 2:0) + offset] to improve power efficiency based on
+ * internal SoC algorithms.
+ * Bit 7 (Enable):
+ * If this bit is set, the SoC Optimization sliders will be processed by the
+ * SoC firmware.
+ *
+ * Copyright (c) 2025, Intel Corporation.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/pci.h>
+#include <linux/platform_profile.h>
+#include "processor_thermal_device.h"
+
+#define SOC_POWER_SLIDER_OFFSET 0x5B38
+
+enum power_slider_preference {
+ SOC_POWER_SLIDER_PERFORMANCE,
+ SOC_POWER_SLIDER_BALANCE,
+ SOC_POWER_SLIDER_POWERSAVE,
+};
+
+#define SOC_SLIDER_VALUE_MINIMUM 0x00
+#define SOC_SLIDER_VALUE_BALANCE 0x03
+#define SOC_SLIDER_VALUE_MAXIMUM 0x06
+
+#define SLIDER_MASK GENMASK_ULL(2, 0)
+#define SLIDER_ENABLE_BIT 7
+
+static u8 slider_values[] = {
+ [SOC_POWER_SLIDER_PERFORMANCE] = SOC_SLIDER_VALUE_MINIMUM,
+ [SOC_POWER_SLIDER_BALANCE] = SOC_SLIDER_VALUE_BALANCE,
+ [SOC_POWER_SLIDER_POWERSAVE] = SOC_SLIDER_VALUE_MAXIMUM,
+};
+
+/* Lock to protect module param updates */
+static DEFINE_MUTEX(slider_param_lock);
+
+static int slider_balanced_param = SOC_SLIDER_VALUE_BALANCE;
+
+static int slider_def_balance_set(const char *arg, const struct kernel_param *kp)
+{
+ u8 slider_val;
+ int ret;
+
+ guard(mutex)(&slider_param_lock);
+
+ ret = kstrtou8(arg, 16, &slider_val);
+ if (!ret) {
+ if (slider_val <= slider_values[SOC_POWER_SLIDER_PERFORMANCE] ||
+ slider_val >= slider_values[SOC_POWER_SLIDER_POWERSAVE])
+ return -EINVAL;
+
+ slider_balanced_param = slider_val;
+ }
+
+ return ret;
+}
+
+static int slider_def_balance_get(char *buf, const struct kernel_param *kp)
+{
+ guard(mutex)(&slider_param_lock);
+ return sysfs_emit(buf, "%02x\n", slider_values[SOC_POWER_SLIDER_BALANCE]);
+}
+
+static const struct kernel_param_ops slider_def_balance_ops = {
+ .set = slider_def_balance_set,
+ .get = slider_def_balance_get,
+};
+
+module_param_cb(slider_balance, &slider_def_balance_ops, NULL, 0644);
+MODULE_PARM_DESC(slider_balance, "Set slider default value for balance");
+
+static u8 slider_offset;
+
+static int slider_def_offset_set(const char *arg, const struct kernel_param *kp)
+{
+ u8 offset;
+ int ret;
+
+ guard(mutex)(&slider_param_lock);
+
+ ret = kstrtou8(arg, 16, &offset);
+ if (!ret) {
+ if (offset > SOC_SLIDER_VALUE_MAXIMUM)
+ return -EINVAL;
+
+ slider_offset = offset;
+ }
+
+ return ret;
+}
+
+static int slider_def_offset_get(char *buf, const struct kernel_param *kp)
+{
+ guard(mutex)(&slider_param_lock);
+ return sysfs_emit(buf, "%02x\n", slider_offset);
+}
+
+static const struct kernel_param_ops slider_offset_ops = {
+ .set = slider_def_offset_set,
+ .get = slider_def_offset_get,
+};
+
+/*
+ * To enhance power efficiency dynamically, the firmware can optionally
+ * auto-adjust the slider value based on the current workload. This
+ * adjustment is controlled by the "slider_offset" module parameter.
+ * This offset permits the firmware to increase the slider value
+ * up to and including "SoC slider + slider offset,".
+ */
+module_param_cb(slider_offset, &slider_offset_ops, NULL, 0644);
+MODULE_PARM_DESC(slider_offset, "Set slider offset");
+
+/* Convert from platform power profile option to SoC slider value */
+static int convert_profile_to_power_slider(enum platform_profile_option profile)
+{
+ switch (profile) {
+ case PLATFORM_PROFILE_LOW_POWER:
+ return slider_values[SOC_POWER_SLIDER_POWERSAVE];
+ case PLATFORM_PROFILE_BALANCED:
+ return slider_values[SOC_POWER_SLIDER_BALANCE];
+ case PLATFORM_PROFILE_PERFORMANCE:
+ return slider_values[SOC_POWER_SLIDER_PERFORMANCE];
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+/* Convert to platform power profile option from SoC slider values */
+static int convert_power_slider_to_profile(u8 slider)
+{
+ if (slider == slider_values[SOC_POWER_SLIDER_PERFORMANCE])
+ return PLATFORM_PROFILE_PERFORMANCE;
+ if (slider == slider_values[SOC_POWER_SLIDER_BALANCE])
+ return PLATFORM_PROFILE_BALANCED;
+ if (slider == slider_values[SOC_POWER_SLIDER_POWERSAVE])
+ return PLATFORM_PROFILE_LOW_POWER;
+
+ return -EOPNOTSUPP;
+}
+
+static inline u64 read_soc_slider(struct proc_thermal_device *proc_priv)
+{
+ return readq(proc_priv->mmio_base + SOC_POWER_SLIDER_OFFSET);
+}
+
+static inline void write_soc_slider(struct proc_thermal_device *proc_priv, u64 val)
+{
+ writeq(val, proc_priv->mmio_base + SOC_POWER_SLIDER_OFFSET);
+}
+
+#define SLIDER_OFFSET_MASK GENMASK_ULL(6, 4)
+
+static void set_soc_power_profile(struct proc_thermal_device *proc_priv, int slider)
+{
+ u64 val;
+
+ val = read_soc_slider(proc_priv);
+ val &= ~SLIDER_MASK;
+ val |= FIELD_PREP(SLIDER_MASK, slider) | BIT(SLIDER_ENABLE_BIT);
+
+ /* Set the slider offset from module params */
+ val &= ~SLIDER_OFFSET_MASK;
+ val |= FIELD_PREP(SLIDER_OFFSET_MASK, slider_offset);
+
+ write_soc_slider(proc_priv, val);
+}
+
+/* profile get/set callbacks are called with a profile lock, so no need for local locks */
+
+static int power_slider_platform_profile_set(struct device *dev,
+ enum platform_profile_option profile)
+{
+ struct proc_thermal_device *proc_priv;
+ int slider;
+
+ proc_priv = dev_get_drvdata(dev);
+ if (!proc_priv)
+ return -EOPNOTSUPP;
+
+ guard(mutex)(&slider_param_lock);
+
+ slider_values[SOC_POWER_SLIDER_BALANCE] = slider_balanced_param;
+
+ slider = convert_profile_to_power_slider(profile);
+ if (slider < 0)
+ return slider;
+
+ set_soc_power_profile(proc_priv, slider);
+
+ return 0;
+}
+
+static int power_slider_platform_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
+{
+ struct proc_thermal_device *proc_priv;
+ int slider, ret;
+ u64 val;
+
+ proc_priv = dev_get_drvdata(dev);
+ if (!proc_priv)
+ return -EOPNOTSUPP;
+
+ val = read_soc_slider(proc_priv);
+ slider = FIELD_GET(SLIDER_MASK, val);
+
+ ret = convert_power_slider_to_profile(slider);
+ if (ret < 0)
+ return ret;
+
+ *profile = ret;
+
+ return 0;
+}
+
+static int power_slider_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+ set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+
+ return 0;
+}
+
+static const struct platform_profile_ops power_slider_platform_profile_ops = {
+ .probe = power_slider_platform_profile_probe,
+ .profile_get = power_slider_platform_profile_get,
+ .profile_set = power_slider_platform_profile_set,
+};
+
+int proc_thermal_soc_power_slider_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ struct device *ppdev;
+
+ set_soc_power_profile(proc_priv, slider_values[SOC_POWER_SLIDER_BALANCE]);
+
+ ppdev = devm_platform_profile_register(&pdev->dev, "SoC Power Slider", proc_priv,
+ &power_slider_platform_profile_ops);
+
+ return PTR_ERR_OR_ZERO(ppdev);
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_add, "INT340X_THERMAL");
+
+static u64 soc_slider_save;
+
+void proc_thermal_soc_power_slider_suspend(struct proc_thermal_device *proc_priv)
+{
+ soc_slider_save = read_soc_slider(proc_priv);
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_suspend, "INT340X_THERMAL");
+
+void proc_thermal_soc_power_slider_resume(struct proc_thermal_device *proc_priv)
+{
+ write_soc_slider(proc_priv, soc_slider_save);
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_resume, "INT340X_THERMAL");
+
+MODULE_IMPORT_NS("INT340X_THERMAL");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Processor Thermal Power Slider Interface");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c
new file mode 100644
index 000000000000..68e8391af8f4
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device interface for reading workload type hints
+ * from the user space. The hints are provided by the firmware.
+ *
+ * Operation:
+ * When user space enables workload type prediction:
+ * - Use mailbox to:
+ * Configure notification delay
+ * Enable processor thermal device interrupt
+ *
+ * - The predicted workload type can be read from MMIO:
+ * Offset 0x5B18 shows if there was an interrupt
+ * active for change in workload type and also
+ * predicted workload type.
+ *
+ * Two interface functions are provided to call when there is a
+ * thermal device interrupt:
+ * - proc_thermal_check_wt_intr():
+ * Check if the interrupt is for change in workload type. Called from
+ * interrupt context.
+ *
+ * - proc_thermal_wt_intr_callback():
+ * Callback for interrupt processing in thread context. This involves
+ * sending notification to user space that there is a change in the
+ * workload type.
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+#define SOC_WT GENMASK_ULL(47, 40)
+
+#define SOC_WT_PREDICTION_INT_ENABLE_BIT 23
+
+#define SOC_WT_PREDICTION_INT_ACTIVE BIT(2)
+
+/*
+ * Closest possible to 1 Second is 1024 ms with programmed time delay
+ * of 0x0A.
+ */
+static u8 notify_delay = 0x0A;
+static u16 notify_delay_ms = 1024;
+
+static DEFINE_MUTEX(wt_lock);
+static u8 wt_enable;
+
+/* Show current predicted workload type index */
+static ssize_t workload_type_index_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct proc_thermal_device *proc_priv;
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u64 status = 0;
+ int wt;
+
+ mutex_lock(&wt_lock);
+ if (!wt_enable) {
+ mutex_unlock(&wt_lock);
+ return -ENODATA;
+ }
+
+ proc_priv = pci_get_drvdata(pdev);
+
+ status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+
+ mutex_unlock(&wt_lock);
+
+ wt = FIELD_GET(SOC_WT, status);
+
+ return sysfs_emit(buf, "%d\n", wt);
+}
+
+static DEVICE_ATTR_RO(workload_type_index);
+
+static ssize_t workload_hint_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%d\n", wt_enable);
+}
+
+static ssize_t workload_hint_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u8 mode;
+ int ret;
+
+ if (kstrtou8(buf, 10, &mode) || mode > 1)
+ return -EINVAL;
+
+ mutex_lock(&wt_lock);
+
+ if (mode)
+ ret = processor_thermal_mbox_interrupt_config(pdev, true,
+ SOC_WT_PREDICTION_INT_ENABLE_BIT,
+ notify_delay);
+ else
+ ret = processor_thermal_mbox_interrupt_config(pdev, false,
+ SOC_WT_PREDICTION_INT_ENABLE_BIT, 0);
+
+ if (ret)
+ goto ret_enable_store;
+
+ ret = size;
+ wt_enable = mode;
+
+ret_enable_store:
+ mutex_unlock(&wt_lock);
+
+ return ret;
+}
+
+static DEVICE_ATTR_RW(workload_hint_enable);
+
+static ssize_t notification_delay_ms_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%u\n", notify_delay_ms);
+}
+
+static ssize_t notification_delay_ms_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u16 new_tw;
+ int ret;
+ u8 tm;
+
+ /*
+ * Time window register value:
+ * Formula: (1 + x/4) * power(2,y)
+ * x = 2 msbs, that is [30:29] y = 5 [28:24]
+ * in INTR_CONFIG register.
+ * The result will be in milli seconds.
+ * Here, just keep x = 0, and just change y.
+ * First round up the user value to power of 2 and
+ * then take log2, to get "y" value to program.
+ */
+ ret = kstrtou16(buf, 10, &new_tw);
+ if (ret)
+ return ret;
+
+ if (!new_tw)
+ return -EINVAL;
+
+ new_tw = roundup_pow_of_two(new_tw);
+ tm = ilog2(new_tw);
+ if (tm > 31)
+ return -EINVAL;
+
+ mutex_lock(&wt_lock);
+
+ /* If the workload hint was already enabled, then update with the new delay */
+ if (wt_enable)
+ ret = processor_thermal_mbox_interrupt_config(pdev, true,
+ SOC_WT_PREDICTION_INT_ENABLE_BIT,
+ tm);
+
+ if (!ret) {
+ ret = size;
+ notify_delay = tm;
+ notify_delay_ms = new_tw;
+ }
+
+ mutex_unlock(&wt_lock);
+
+ return ret;
+}
+
+static DEVICE_ATTR_RW(notification_delay_ms);
+
+static struct attribute *workload_hint_attrs[] = {
+ &dev_attr_workload_type_index.attr,
+ &dev_attr_workload_hint_enable.attr,
+ &dev_attr_notification_delay_ms.attr,
+ NULL
+};
+
+static const struct attribute_group workload_hint_attribute_group = {
+ .attrs = workload_hint_attrs,
+ .name = "workload_hint"
+};
+
+/*
+ * Callback to check if the interrupt for prediction is active.
+ * Caution: Called from the interrupt context.
+ */
+bool proc_thermal_check_wt_intr(struct proc_thermal_device *proc_priv)
+{
+ u64 int_status;
+
+ int_status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+ if (int_status & SOC_WT_PREDICTION_INT_ACTIVE)
+ return true;
+
+ return false;
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_check_wt_intr, "INT340X_THERMAL");
+
+/* Callback to notify user space */
+void proc_thermal_wt_intr_callback(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ u64 status;
+
+ status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+ if (!(status & SOC_WT_PREDICTION_INT_ACTIVE))
+ return;
+
+ sysfs_notify(&pdev->dev.kobj, "workload_hint", "workload_type_index");
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_intr_callback, "INT340X_THERMAL");
+
+static bool workload_hint_created;
+
+int proc_thermal_wt_hint_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ int ret;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &workload_hint_attribute_group);
+ if (ret)
+ return ret;
+
+ workload_hint_created = true;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_add, "INT340X_THERMAL");
+
+void proc_thermal_wt_hint_remove(struct pci_dev *pdev)
+{
+ mutex_lock(&wt_lock);
+ if (wt_enable)
+ processor_thermal_mbox_interrupt_config(pdev, false,
+ SOC_WT_PREDICTION_INT_ENABLE_BIT,
+ 0);
+ mutex_unlock(&wt_lock);
+
+ if (workload_hint_created)
+ sysfs_remove_group(&pdev->dev.kobj, &workload_hint_attribute_group);
+
+ workload_hint_created = false;
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_remove, "INT340X_THERMAL");
+
+MODULE_IMPORT_NS("INT340X_THERMAL");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Processor Thermal Work Load type hint Interface");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_req.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_req.c
new file mode 100644
index 000000000000..b95810f4a011
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_req.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device for Workload type hints
+ * update from user space
+ *
+ * Copyright (c) 2020-2023, Intel Corporation.
+ */
+
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+/* 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 = processor_thermal_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 = processor_thermal_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_wt_req_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 = processor_thermal_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_wt_req_add);
+
+void proc_thermal_wt_req_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_wt_req_remove);
+
+MODULE_IMPORT_NS("INT340X_THERMAL");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Processor Thermal Work Load type request Interface");
diff --git a/drivers/thermal/intel/intel_hfi.c b/drivers/thermal/intel/intel_hfi.c
index c69db6c90869..8a2f441cd2ec 100644
--- a/drivers/thermal/intel/intel_hfi.c
+++ b/drivers/thermal/intel/intel_hfi.c
@@ -24,6 +24,7 @@
#include <linux/bitops.h>
#include <linux/cpufeature.h>
#include <linux/cpumask.h>
+#include <linux/delay.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/kernel.h>
@@ -34,7 +35,9 @@
#include <linux/processor.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
+#include <linux/suspend.h>
#include <linux/string.h>
+#include <linux/syscore_ops.h>
#include <linux/topology.h>
#include <linux/workqueue.h>
@@ -156,14 +159,15 @@ struct hfi_cpu_info {
static DEFINE_PER_CPU(struct hfi_cpu_info, hfi_cpu_info) = { .index = -1 };
static int max_hfi_instances;
+static int hfi_clients_nr;
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
+#define HFI_UPDATE_DELAY_MS 100
+#define HFI_THERMNL_CAPS_PER_EVENT 64
static void get_hfi_caps(struct hfi_instance *hfi_instance,
struct thermal_genl_cpu_caps *cpu_caps)
@@ -214,14 +218,14 @@ static void update_capabilities(struct hfi_instance *hfi_instance)
get_hfi_caps(hfi_instance, cpu_caps);
- if (cpu_count < HFI_MAX_THERM_NOTIFY_COUNT)
+ if (cpu_count < HFI_THERMNL_CAPS_PER_EVENT)
goto last_cmd;
- /* Process complete chunks of HFI_MAX_THERM_NOTIFY_COUNT capabilities. */
+ /* Process complete chunks of HFI_THERMNL_CAPS_PER_EVENT 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,
+ (i + HFI_THERMNL_CAPS_PER_EVENT) <= cpu_count;
+ i += HFI_THERMNL_CAPS_PER_EVENT)
+ thermal_genl_cpu_capability_event(HFI_THERMNL_CAPS_PER_EVENT,
&cpu_caps[i]);
cpu_count = cpu_count - i;
@@ -280,7 +284,7 @@ void intel_hfi_process_event(__u64 pkg_therm_status_msr_val)
if (!raw_spin_trylock(&hfi_instance->event_lock))
return;
- rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr);
+ rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr);
hfi = msr & PACKAGE_THERM_STATUS_HFI_UPDATED;
if (!hfi) {
raw_spin_unlock(&hfi_instance->event_lock);
@@ -318,7 +322,7 @@ void intel_hfi_process_event(__u64 pkg_therm_status_msr_val)
raw_spin_unlock(&hfi_instance->event_lock);
queue_delayed_work(hfi_updates_wq, &hfi_instance->update_work,
- HFI_UPDATE_INTERVAL);
+ msecs_to_jiffies(HFI_UPDATE_DELAY_MS));
}
static void init_hfi_cpu_index(struct hfi_cpu_info *info)
@@ -347,14 +351,60 @@ static void init_hfi_instance(struct hfi_instance *hfi_instance)
hfi_instance->data = hfi_instance->hdr + hfi_features.hdr_size;
}
+/* Caller must hold hfi_instance_lock. */
+static void hfi_enable(void)
+{
+ u64 msr_val;
+
+ rdmsrq(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
+ msr_val |= HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT;
+ wrmsrq(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
+}
+
+static void hfi_set_hw_table(struct hfi_instance *hfi_instance)
+{
+ phys_addr_t hw_table_pa;
+ u64 msr_val;
+
+ hw_table_pa = virt_to_phys(hfi_instance->hw_table);
+ msr_val = hw_table_pa | HW_FEEDBACK_PTR_VALID_BIT;
+ wrmsrq(MSR_IA32_HW_FEEDBACK_PTR, msr_val);
+}
+
+/* Caller must hold hfi_instance_lock. */
+static void hfi_disable(void)
+{
+ u64 msr_val;
+ int i;
+
+ rdmsrq(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
+ msr_val &= ~HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT;
+ wrmsrq(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
+
+ /*
+ * Wait for hardware to acknowledge the disabling of HFI. Some
+ * processors may not do it. Wait for ~2ms. This is a reasonable
+ * time for hardware to complete any pending actions on the HFI
+ * memory.
+ */
+ for (i = 0; i < 2000; i++) {
+ rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
+ if (msr_val & PACKAGE_THERM_STATUS_HFI_UPDATED)
+ break;
+
+ udelay(1);
+ cpu_relax();
+ }
+}
+
/**
* 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
+ * Enable the HFI to be used in @cpu. The HFI is enabled at the package
+ * level. The first CPU in the package to come online does the full HFI
* initialization. Subsequent CPUs will just link themselves to the HFI
- * instance of their die/package.
+ * instance of their 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
@@ -364,53 +414,52 @@ 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;
+ u16 pkg_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
+ * Link @cpu to the HFI instance of its package. It does not
* matter whether the instance has been initialized.
*/
info = &per_cpu(hfi_cpu_info, cpu);
- die_id = topology_logical_die_id(cpu);
+ pkg_id = topology_logical_package_id(cpu);
hfi_instance = info->hfi_instance;
if (!hfi_instance) {
- if (die_id >= max_hfi_instances)
+ if (pkg_id >= max_hfi_instances)
return;
- hfi_instance = &hfi_instances[die_id];
+ hfi_instance = &hfi_instances[pkg_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
+ * Now check if the HFI instance of the package 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.
+ * do is to add @cpu to this instance's cpumask and enable the instance
+ * if needed.
*/
mutex_lock(&hfi_instance_lock);
- if (hfi_instance->hdr) {
- cpumask_set_cpu(cpu, hfi_instance->cpus);
- goto unlock;
- }
+ if (hfi_instance->hdr)
+ goto enable;
/*
* Hardware is programmed with the physical address of the first page
* frame of the table. Hence, the allocated memory must be page-aligned.
+ *
+ * Some processors do not forget the initial address of the HFI table
+ * even after having been reprogrammed. Keep using the same pages. Do
+ * not free them.
*/
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.
@@ -420,31 +469,23 @@ void intel_hfi_online(unsigned int cpu)
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);
+enable:
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.
+ * Enable this HFI instance if this is its first online CPU and
+ * there are user-space clients of thermal events.
*/
- rdmsrl(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
- msr_val |= HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT;
- wrmsrl(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
+ if (cpumask_weight(hfi_instance->cpus) == 1 && hfi_clients_nr > 0) {
+ hfi_set_hw_table(hfi_instance);
+ hfi_enable();
+ }
unlock:
mutex_unlock(&hfi_instance_lock);
@@ -463,7 +504,7 @@ free_hw_table:
*
* 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().
+ * package of @cpu are offline. See note in intel_hfi_online().
*/
void intel_hfi_offline(unsigned int cpu)
{
@@ -484,6 +525,10 @@ void intel_hfi_offline(unsigned int cpu)
mutex_lock(&hfi_instance_lock);
cpumask_clear_cpu(cpu, hfi_instance->cpus);
+
+ if (!cpumask_weight(hfi_instance->cpus))
+ hfi_disable();
+
mutex_unlock(&hfi_instance_lock);
}
@@ -532,6 +577,99 @@ static __init int hfi_parse_features(void)
return 0;
}
+/*
+ * If concurrency is not prevented by other means, the HFI enable/disable
+ * routines must be called under hfi_instance_lock."
+ */
+static void hfi_enable_instance(void *ptr)
+{
+ hfi_set_hw_table(ptr);
+ hfi_enable();
+}
+
+static void hfi_disable_instance(void *ptr)
+{
+ hfi_disable();
+}
+
+static void hfi_syscore_resume(void *data)
+{
+ /* This code runs only on the boot CPU. */
+ struct hfi_cpu_info *info = &per_cpu(hfi_cpu_info, 0);
+ struct hfi_instance *hfi_instance = info->hfi_instance;
+
+ /* No locking needed. There is no concurrency with CPU online. */
+ if (hfi_clients_nr > 0)
+ hfi_enable_instance(hfi_instance);
+}
+
+static int hfi_syscore_suspend(void *data)
+{
+ /* No locking needed. There is no concurrency with CPU offline. */
+ hfi_disable();
+
+ return 0;
+}
+
+static const struct syscore_ops hfi_pm_ops = {
+ .resume = hfi_syscore_resume,
+ .suspend = hfi_syscore_suspend,
+};
+
+static struct syscore hfi_pm = {
+ .ops = &hfi_pm_ops,
+};
+
+static int hfi_thermal_notify(struct notifier_block *nb, unsigned long state,
+ void *_notify)
+{
+ struct thermal_genl_notify *notify = _notify;
+ struct hfi_instance *hfi_instance;
+ smp_call_func_t func = NULL;
+ unsigned int cpu;
+ int i;
+
+ if (notify->mcgrp != THERMAL_GENL_EVENT_GROUP)
+ return NOTIFY_DONE;
+
+ if (state != THERMAL_NOTIFY_BIND && state != THERMAL_NOTIFY_UNBIND)
+ return NOTIFY_DONE;
+
+ mutex_lock(&hfi_instance_lock);
+
+ switch (state) {
+ case THERMAL_NOTIFY_BIND:
+ if (++hfi_clients_nr == 1)
+ func = hfi_enable_instance;
+ break;
+ case THERMAL_NOTIFY_UNBIND:
+ if (--hfi_clients_nr == 0)
+ func = hfi_disable_instance;
+ break;
+ }
+
+ if (!func)
+ goto out;
+
+ for (i = 0; i < max_hfi_instances; i++) {
+ hfi_instance = &hfi_instances[i];
+ if (cpumask_empty(hfi_instance->cpus))
+ continue;
+
+ cpu = cpumask_any(hfi_instance->cpus);
+ smp_call_function_single(cpu, func, hfi_instance, true);
+ }
+
+out:
+ mutex_unlock(&hfi_instance_lock);
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block hfi_thermal_nb = {
+ .notifier_call = hfi_thermal_notify,
+};
+
void __init intel_hfi_init(void)
{
struct hfi_instance *hfi_instance;
@@ -540,9 +678,13 @@ void __init intel_hfi_init(void)
if (hfi_parse_features())
return;
- /* There is one HFI instance per die/package. */
- max_hfi_instances = topology_max_packages() *
- topology_max_die_per_package();
+ /*
+ * Note: HFI resources are managed at the physical package scope.
+ * There could be platforms that enumerate packages as Linux dies.
+ * Special handling would be needed if this happens on an HFI-capable
+ * platform.
+ */
+ max_hfi_instances = topology_max_packages();
/*
* This allocation may fail. CPU hotplug callbacks must check
@@ -563,8 +705,22 @@ void __init intel_hfi_init(void)
if (!hfi_updates_wq)
goto err_nomem;
+ /*
+ * Both thermal core and Intel HFI can not be build as modules.
+ * As kernel build-in drivers they are initialized before user-space
+ * starts, hence we can not miss BIND/UNBIND events when applications
+ * add/remove thermal multicast group to/from a netlink socket.
+ */
+ if (thermal_genl_register_notifier(&hfi_thermal_nb))
+ goto err_nl_notif;
+
+ register_syscore(&hfi_pm);
+
return;
+err_nl_notif:
+ destroy_workqueue(hfi_updates_wq);
+
err_nomem:
for (j = 0; j < i; ++j) {
hfi_instance = &hfi_instances[j];
diff --git a/drivers/thermal/intel/intel_pch_thermal.c b/drivers/thermal/intel/intel_pch_thermal.c
index b3905e34c507..fc326985796c 100644
--- a/drivers/thermal/intel/intel_pch_thermal.c
+++ b/drivers/thermal/intel/intel_pch_thermal.c
@@ -84,7 +84,6 @@ struct pch_thermal_device {
void __iomem *hw_base;
struct pci_dev *pdev;
struct thermal_zone_device *tzd;
- struct thermal_trip trips[PCH_MAX_TRIPS];
bool bios_enabled;
};
@@ -94,7 +93,8 @@ struct pch_thermal_device {
* passive trip temperature using _PSV method. There is no specific
* passive temperature setting in MMIO interface of this PCI device.
*/
-static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, int trip)
+static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd,
+ struct thermal_trip *trip)
{
struct acpi_device *adev;
int temp;
@@ -106,12 +106,13 @@ static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, int trip)
if (thermal_acpi_passive_trip_temp(adev, &temp) || temp <= 0)
return 0;
- ptd->trips[trip].type = THERMAL_TRIP_PASSIVE;
- ptd->trips[trip].temperature = temp;
+ trip->type = THERMAL_TRIP_PASSIVE;
+ trip->temperature = temp;
return 1;
}
#else
-static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd, int trip)
+static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd,
+ struct thermal_trip *trip)
{
return 0;
}
@@ -131,7 +132,7 @@ static void pch_critical(struct thermal_zone_device *tzd)
thermal_zone_device_type(tzd));
}
-static struct thermal_zone_device_ops tzd_ops = {
+static const struct thermal_zone_device_ops tzd_ops = {
.get_temp = pch_thermal_get_temp,
.critical = pch_critical,
};
@@ -159,6 +160,7 @@ static const char *board_names[] = {
static int intel_pch_thermal_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
+ struct thermal_trip ptd_trips[PCH_MAX_TRIPS] = { 0 };
enum pch_board_ids board_id = id->driver_data;
struct pch_thermal_device *ptd;
int nr_trips = 0;
@@ -220,22 +222,22 @@ read_trips:
trip_temp = readw(ptd->hw_base + WPT_CTT);
trip_temp &= 0x1FF;
if (trip_temp) {
- ptd->trips[nr_trips].temperature = GET_WPT_TEMP(trip_temp);
- ptd->trips[nr_trips++].type = THERMAL_TRIP_CRITICAL;
+ ptd_trips[nr_trips].temperature = GET_WPT_TEMP(trip_temp);
+ ptd_trips[nr_trips++].type = THERMAL_TRIP_CRITICAL;
}
trip_temp = readw(ptd->hw_base + WPT_PHL);
trip_temp &= 0x1FF;
if (trip_temp) {
- ptd->trips[nr_trips].temperature = GET_WPT_TEMP(trip_temp);
- ptd->trips[nr_trips++].type = THERMAL_TRIP_HOT;
+ ptd_trips[nr_trips].temperature = GET_WPT_TEMP(trip_temp);
+ ptd_trips[nr_trips++].type = THERMAL_TRIP_HOT;
}
- nr_trips += pch_wpt_add_acpi_psv_trip(ptd, nr_trips);
+ nr_trips += pch_wpt_add_acpi_psv_trip(ptd, &ptd_trips[nr_trips]);
ptd->tzd = thermal_zone_device_register_with_trips(board_names[board_id],
- ptd->trips, nr_trips,
- 0, ptd, &tzd_ops,
+ ptd_trips, nr_trips,
+ ptd, &tzd_ops,
NULL, 0, 0);
if (IS_ERR(ptd->tzd)) {
dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
@@ -296,6 +298,11 @@ static int intel_pch_thermal_suspend_noirq(struct device *device)
/* Get the PCH current temperature value */
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 current temp [%dC] higher than the threshold temp [%dC], S0ix might fail. Start cooling...\n",
+ pch_cur_temp, pch_thr_temp);
+
/*
* If current PCH temperature is higher than configured PCH threshold
* value, run some delay loop with sleep to let the current temperature
diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c
index 36243a3972fd..9a4cec000910 100644
--- a/drivers/thermal/intel/intel_powerclamp.c
+++ b/drivers/thermal/intel/intel_powerclamp.c
@@ -49,7 +49,6 @@
*/
#define DEFAULT_DURATION_JIFFIES (6)
-static unsigned int target_mwait;
static struct dentry *debug_dir;
static bool poll_pkg_cstate_enable;
@@ -256,7 +255,7 @@ skip_limit_set:
static const struct kernel_param_ops max_idle_ops = {
.set = max_idle_set,
- .get = param_get_int,
+ .get = param_get_byte,
};
module_param_cb(max_idle, &max_idle_ops, &max_idle, 0644);
@@ -312,34 +311,6 @@ MODULE_PARM_DESC(window_size, "sliding window in number of clamping cycles\n"
"\twindow size results in slower response time but more smooth\n"
"\tclamping results. default to 2.");
-static void find_target_mwait(void)
-{
- unsigned int eax, ebx, ecx, edx;
- unsigned int highest_cstate = 0;
- unsigned int highest_subcstate = 0;
- int i;
-
- if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF)
- return;
-
- cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &edx);
-
- if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) ||
- !(ecx & CPUID5_ECX_INTERRUPT_BREAK))
- return;
-
- edx >>= MWAIT_SUBSTATE_SIZE;
- for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) {
- if (edx & MWAIT_SUBSTATE_MASK) {
- highest_cstate = i;
- highest_subcstate = edx & MWAIT_SUBSTATE_MASK;
- }
- }
- target_mwait = (highest_cstate << MWAIT_SUBSTATE_SIZE) |
- (highest_subcstate - 1);
-
-}
-
struct pkg_cstate_info {
bool skip;
int msr_index;
@@ -369,7 +340,7 @@ static bool has_pkg_state_counter(void)
/* check if any one of the counter msrs exists */
while (info->msr_index) {
- if (!rdmsrl_safe(info->msr_index, &val))
+ if (!rdmsrq_safe(info->msr_index, &val))
return true;
info++;
}
@@ -385,7 +356,7 @@ static u64 pkg_state_counter(void)
while (info->msr_index) {
if (!info->skip) {
- if (!rdmsrl_safe(info->msr_index, &val))
+ if (!rdmsrq_safe(info->msr_index, &val))
count += val;
else
info->skip = true;
@@ -616,7 +587,7 @@ static int powerclamp_idle_injection_register(void)
poll_pkg_cstate_enable = false;
if (cpumask_equal(cpu_present_mask, idle_injection_cpu_mask)) {
ii_dev = idle_inject_register_full(idle_injection_cpu_mask, idle_inject_update);
- if (topology_max_packages() == 1 && topology_max_die_per_package() == 1)
+ if (topology_max_packages() == 1 && topology_max_dies_per_package() == 1)
poll_pkg_cstate_enable = true;
} else {
ii_dev = idle_inject_register(idle_injection_cpu_mask);
@@ -759,9 +730,6 @@ static int __init powerclamp_probe(void)
return -ENODEV;
}
- /* find the deepest mwait value */
- find_target_mwait();
-
return 0;
}
@@ -841,7 +809,7 @@ static void __exit powerclamp_exit(void)
}
module_exit(powerclamp_exit);
-MODULE_IMPORT_NS(IDLE_INJECT);
+MODULE_IMPORT_NS("IDLE_INJECT");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
diff --git a/drivers/thermal/intel/intel_quark_dts_thermal.c b/drivers/thermal/intel/intel_quark_dts_thermal.c
index 646ca8bd40a9..89498eb29a89 100644
--- a/drivers/thermal/intel/intel_quark_dts_thermal.c
+++ b/drivers/thermal/intel/intel_quark_dts_thermal.c
@@ -93,10 +93,6 @@
/* Quark DTS has 2 trip points: hot & catastrophic */
#define QRK_MAX_DTS_TRIPS 2
-/* If DTS not locked, all trip points are configurable */
-#define QRK_DTS_WR_MASK_SET 0x3
-/* If DTS locked, all trip points are not configurable */
-#define QRK_DTS_WR_MASK_CLR 0
#define DEFAULT_POLL_DELAY 2000
@@ -105,7 +101,6 @@ struct soc_sensor_entry {
u32 store_ptps;
u32 store_dts_enable;
struct thermal_zone_device *tzone;
- struct thermal_trip trips[QRK_MAX_DTS_TRIPS];
};
static struct soc_sensor_entry *soc_dts;
@@ -200,7 +195,7 @@ static int get_trip_temp(int trip)
}
static int update_trip_temp(struct soc_sensor_entry *aux_entry,
- int trip, int temp)
+ int trip_index, int temp)
{
u32 out;
u32 temp_out;
@@ -235,9 +230,9 @@ static int update_trip_temp(struct soc_sensor_entry *aux_entry,
*/
temp_out = temp + QRK_DTS_TEMP_BASE;
out = (store_ptps & ~(QRK_DTS_MASK_TP_THRES <<
- (trip * QRK_DTS_SHIFT_TP)));
+ (trip_index * QRK_DTS_SHIFT_TP)));
out |= (temp_out & QRK_DTS_MASK_TP_THRES) <<
- (trip * QRK_DTS_SHIFT_TP);
+ (trip_index * QRK_DTS_SHIFT_TP);
ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
QRK_DTS_REG_OFFSET_PTPS, out);
@@ -247,10 +242,26 @@ failed:
return ret;
}
-static inline int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
- int temp)
+static inline int sys_set_trip_temp(struct thermal_zone_device *tzd,
+ const struct thermal_trip *trip,
+ int temp)
{
- return update_trip_temp(thermal_zone_device_priv(tzd), trip, temp);
+ unsigned int trip_index;
+
+ switch (trip->type) {
+ case THERMAL_TRIP_HOT:
+ trip_index = QRK_DTS_ID_TP_HOT;
+ break;
+
+ case THERMAL_TRIP_CRITICAL:
+ trip_index = QRK_DTS_ID_TP_CRITICAL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return update_trip_temp(thermal_zone_device_priv(tzd), trip_index, temp);
}
static int sys_get_curr_temp(struct thermal_zone_device *tzd,
@@ -293,7 +304,7 @@ static int sys_change_mode(struct thermal_zone_device *tzd,
return ret;
}
-static struct thermal_zone_device_ops tzone_ops = {
+static const struct thermal_zone_device_ops tzone_ops = {
.get_temp = sys_get_curr_temp,
.set_trip_temp = sys_set_trip_temp,
.change_mode = sys_change_mode,
@@ -320,10 +331,10 @@ static void free_soc_dts(struct soc_sensor_entry *aux_entry)
static struct soc_sensor_entry *alloc_soc_dts(void)
{
+ struct thermal_trip trips[QRK_MAX_DTS_TRIPS] = { 0 };
struct soc_sensor_entry *aux_entry;
int err;
u32 out;
- int wr_mask;
aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL);
if (!aux_entry) {
@@ -337,13 +348,7 @@ static struct soc_sensor_entry *alloc_soc_dts(void)
if (err)
goto err_ret;
- if (out & QRK_DTS_LOCK_BIT) {
- aux_entry->locked = true;
- wr_mask = QRK_DTS_WR_MASK_CLR;
- } else {
- aux_entry->locked = false;
- wr_mask = QRK_DTS_WR_MASK_SET;
- }
+ aux_entry->locked = !!(out & QRK_DTS_LOCK_BIT);
/* Store DTS default state if DTS registers are not locked */
if (!aux_entry->locked) {
@@ -360,19 +365,22 @@ static struct soc_sensor_entry *alloc_soc_dts(void)
&aux_entry->store_ptps);
if (err)
goto err_ret;
+
+ trips[QRK_DTS_ID_TP_CRITICAL].flags |= THERMAL_TRIP_FLAG_RW_TEMP;
+ trips[QRK_DTS_ID_TP_HOT].flags |= THERMAL_TRIP_FLAG_RW_TEMP;
}
- aux_entry->trips[QRK_DTS_ID_TP_CRITICAL].temperature = get_trip_temp(QRK_DTS_ID_TP_CRITICAL);
- aux_entry->trips[QRK_DTS_ID_TP_CRITICAL].type = THERMAL_TRIP_CRITICAL;
+ trips[QRK_DTS_ID_TP_CRITICAL].temperature = get_trip_temp(QRK_DTS_ID_TP_CRITICAL);
+ trips[QRK_DTS_ID_TP_CRITICAL].type = THERMAL_TRIP_CRITICAL;
- aux_entry->trips[QRK_DTS_ID_TP_HOT].temperature = get_trip_temp(QRK_DTS_ID_TP_HOT);
- aux_entry->trips[QRK_DTS_ID_TP_HOT].type = THERMAL_TRIP_HOT;
+ trips[QRK_DTS_ID_TP_HOT].temperature = get_trip_temp(QRK_DTS_ID_TP_HOT);
+ trips[QRK_DTS_ID_TP_HOT].type = THERMAL_TRIP_HOT;
aux_entry->tzone = thermal_zone_device_register_with_trips("quark_dts",
- aux_entry->trips,
+ trips,
QRK_MAX_DTS_TRIPS,
- wr_mask,
- aux_entry, &tzone_ops,
+ aux_entry,
+ &tzone_ops,
NULL, 0, polling_delay);
if (IS_ERR(aux_entry->tzone)) {
err = PTR_ERR(aux_entry->tzone);
@@ -393,7 +401,7 @@ err_ret:
}
static const struct x86_cpu_id qrk_thermal_ids[] __initconst = {
- X86_MATCH_VENDOR_FAM_MODEL(INTEL, 5, INTEL_FAM5_QUARK_X1000, NULL),
+ X86_MATCH_VFM(INTEL_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 db97499f4f0a..ea87439fe7a9 100644
--- a/drivers/thermal/intel/intel_soc_dts_iosf.c
+++ b/drivers/thermal/intel/intel_soc_dts_iosf.c
@@ -37,44 +37,11 @@
/* DTS encoding for TJ MAX temperature */
#define SOC_DTS_TJMAX_ENCODING 0x7F
-/* Only 2 out of 4 is allowed for OSPM */
-#define SOC_MAX_DTS_TRIPS 2
-
/* Mask for two trips in status bits */
#define SOC_DTS_TRIP_MASK 0x03
-/* DTS0 and DTS 1 */
-#define SOC_MAX_DTS_SENSORS 2
-
-static int sys_get_trip_temp(struct thermal_zone_device *tzd, int trip,
- int *temp)
-{
- int status;
- u32 out;
- struct intel_soc_dts_sensor_entry *dts;
- struct intel_soc_dts_sensors *sensors;
-
- dts = thermal_zone_device_priv(tzd);
- sensors = dts->sensors;
- mutex_lock(&sensors->dts_update_lock);
- status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
- SOC_DTS_OFFSET_PTPS, &out);
- mutex_unlock(&sensors->dts_update_lock);
- if (status)
- return status;
-
- out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING;
- if (!out)
- *temp = 0;
- else
- *temp = sensors->tj_max - out * 1000;
-
- return 0;
-}
-
-static int update_trip_temp(struct intel_soc_dts_sensor_entry *dts,
- int thres_index, int temp,
- enum thermal_trip_type trip_type)
+static int update_trip_temp(struct intel_soc_dts_sensors *sensors,
+ int thres_index, int temp)
{
int status;
u32 temp_out;
@@ -85,7 +52,6 @@ static int update_trip_temp(struct intel_soc_dts_sensor_entry *dts,
u32 store_te_out;
u32 te_out;
u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE;
- struct intel_soc_dts_sensors *sensors = dts->sensors;
if (sensors->intr_type == INTEL_SOC_DTS_INTERRUPT_MSI)
int_enable_bit |= SOC_DTS_TE_MSI_ENABLE;
@@ -148,8 +114,6 @@ static int update_trip_temp(struct intel_soc_dts_sensor_entry *dts,
if (status)
goto err_restore_te_out;
- dts->trip_types[thres_index] = trip_type;
-
return 0;
err_restore_te_out:
iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
@@ -165,34 +129,25 @@ err_restore_ptps:
return status;
}
-static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
+static int sys_set_trip_temp(struct thermal_zone_device *tzd,
+ const struct thermal_trip *trip,
int temp)
{
struct intel_soc_dts_sensor_entry *dts = thermal_zone_device_priv(tzd);
struct intel_soc_dts_sensors *sensors = dts->sensors;
+ unsigned int trip_index = THERMAL_TRIP_PRIV_TO_INT(trip->priv);
int status;
if (temp > sensors->tj_max)
return -EINVAL;
mutex_lock(&sensors->dts_update_lock);
- status = update_trip_temp(dts, trip, temp,
- dts->trip_types[trip]);
+ status = update_trip_temp(sensors, trip_index, temp);
mutex_unlock(&sensors->dts_update_lock);
return status;
}
-static int sys_get_trip_type(struct thermal_zone_device *tzd,
- int trip, enum thermal_trip_type *type)
-{
- struct intel_soc_dts_sensor_entry *dts = thermal_zone_device_priv(tzd);
-
- *type = dts->trip_types[trip];
-
- return 0;
-}
-
static int sys_get_curr_temp(struct thermal_zone_device *tzd,
int *temp)
{
@@ -215,10 +170,8 @@ static int sys_get_curr_temp(struct thermal_zone_device *tzd,
return 0;
}
-static struct thermal_zone_device_ops tzone_ops = {
+static const 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,
};
@@ -245,25 +198,16 @@ static int soc_dts_enable(int id)
static void remove_dts_thermal_zone(struct intel_soc_dts_sensor_entry *dts)
{
- if (dts) {
- iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
- SOC_DTS_OFFSET_ENABLE, dts->store_status);
- thermal_zone_device_unregister(dts->tzone);
- }
+ iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_ENABLE, dts->store_status);
+ thermal_zone_device_unregister(dts->tzone);
}
static int add_dts_thermal_zone(int id, struct intel_soc_dts_sensor_entry *dts,
- bool notification_support, int trip_cnt,
- int read_only_trip_cnt)
+ struct thermal_trip *trips)
{
char name[10];
- unsigned long trip;
- int trip_count = 0;
- int trip_mask = 0;
- int writable_trip_cnt = 0;
- unsigned long ptps;
u32 store_ptps;
- unsigned long i;
int ret;
/* Store status to restor on exit */
@@ -273,30 +217,23 @@ static int add_dts_thermal_zone(int id, struct intel_soc_dts_sensor_entry *dts,
goto err_ret;
dts->id = id;
- if (notification_support) {
- trip_count = min(SOC_MAX_DTS_TRIPS, trip_cnt);
- writable_trip_cnt = trip_count - read_only_trip_cnt;
- trip_mask = GENMASK(writable_trip_cnt - 1, 0);
- }
/* Check if the writable trip we provide is not used by BIOS */
ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_PTPS, &store_ptps);
- if (ret)
- trip_mask = 0;
- else {
- ptps = store_ptps;
- for_each_set_clump8(i, trip, &ptps, writable_trip_cnt * 8)
- trip_mask &= ~BIT(i / 8);
+ if (!ret) {
+ int i;
+
+ for (i = 0; i <= 1; i++) {
+ if (store_ptps & (0xFFU << i * 8))
+ trips[i].flags &= ~THERMAL_TRIP_FLAG_RW_TEMP;
+ }
}
- dts->trip_mask = trip_mask;
- dts->trip_count = trip_count;
snprintf(name, sizeof(name), "soc_dts%d", id);
- dts->tzone = thermal_zone_device_register(name,
- trip_count,
- trip_mask,
- dts, &tzone_ops,
- NULL, 0, 0);
+ dts->tzone = thermal_zone_device_register_with_trips(name, trips,
+ SOC_MAX_DTS_TRIPS,
+ dts, &tzone_ops,
+ NULL, 0, 0);
if (IS_ERR(dts->tzone)) {
ret = PTR_ERR(dts->tzone);
goto err_ret;
@@ -316,26 +253,6 @@ err_ret:
return ret;
}
-int intel_soc_dts_iosf_add_read_only_critical_trip(
- struct intel_soc_dts_sensors *sensors, int critical_offset)
-{
- int i, j;
-
- for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
- 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;
-}
-EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_add_read_only_critical_trip);
-
void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors)
{
u32 sticky_out;
@@ -371,12 +288,27 @@ void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors)
}
EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_interrupt_handler);
-struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
- enum intel_soc_dts_interrupt_type intr_type, int trip_count,
- int read_only_trip_count)
+static void dts_trips_reset(struct intel_soc_dts_sensors *sensors, int dts_index)
{
+ update_trip_temp(sensors, 0, 0);
+ update_trip_temp(sensors, 1, 0);
+}
+
+static void set_trip(struct thermal_trip *trip, enum thermal_trip_type type,
+ u8 flags, int temp, unsigned int index)
+{
+ trip->type = type;
+ trip->flags = flags;
+ trip->temperature = temp;
+ trip->priv = THERMAL_INT_TO_TRIP_PRIV(index);
+}
+
+struct intel_soc_dts_sensors *
+intel_soc_dts_iosf_init(enum intel_soc_dts_interrupt_type intr_type,
+ bool critical_trip, int crit_offset)
+{
+ struct thermal_trip trips[SOC_MAX_DTS_SENSORS][SOC_MAX_DTS_TRIPS] = { 0 };
struct intel_soc_dts_sensors *sensors;
- bool notification;
int tj_max;
int ret;
int i;
@@ -384,9 +316,6 @@ struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
if (!iosf_mbi_available())
return ERR_PTR(-ENODEV);
- if (!trip_count || read_only_trip_count > trip_count)
- return ERR_PTR(-EINVAL);
-
tj_max = intel_tcc_get_tjmax(-1);
if (tj_max < 0)
return ERR_PTR(tj_max);
@@ -399,37 +328,49 @@ struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
mutex_init(&sensors->dts_update_lock);
sensors->intr_type = intr_type;
sensors->tj_max = tj_max * 1000;
- if (intr_type == INTEL_SOC_DTS_INTERRUPT_NONE)
- notification = false;
- else
- notification = true;
+
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ int temp;
+
sensors->soc_dts[i].sensors = sensors;
- ret = add_dts_thermal_zone(i, &sensors->soc_dts[i],
- notification, trip_count,
- read_only_trip_count);
+
+ set_trip(&trips[i][0], THERMAL_TRIP_PASSIVE,
+ THERMAL_TRIP_FLAG_RW_TEMP, 0, 0);
+
+ ret = update_trip_temp(sensors, 0, 0);
if (ret)
- goto err_free;
- }
+ goto err_reset_trips;
+
+ if (critical_trip) {
+ temp = sensors->tj_max - crit_offset;
+ set_trip(&trips[i][1], THERMAL_TRIP_CRITICAL, 0, temp, 1);
+ } else {
+ set_trip(&trips[i][1], THERMAL_TRIP_PASSIVE,
+ THERMAL_TRIP_FLAG_RW_TEMP, 0, 1);
+ temp = 0;
+ }
- for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
- ret = update_trip_temp(&sensors->soc_dts[i], 0, 0,
- THERMAL_TRIP_PASSIVE);
+ ret = update_trip_temp(sensors, 1, temp);
if (ret)
- goto err_remove_zone;
+ goto err_reset_trips;
+ }
- ret = update_trip_temp(&sensors->soc_dts[i], 1, 0,
- THERMAL_TRIP_PASSIVE);
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ ret = add_dts_thermal_zone(i, &sensors->soc_dts[i], trips[i]);
if (ret)
goto err_remove_zone;
}
return sensors;
+
err_remove_zone:
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i)
remove_dts_thermal_zone(&sensors->soc_dts[i]);
-err_free:
+err_reset_trips:
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; i++)
+ dts_trips_reset(sensors, i);
+
kfree(sensors);
return ERR_PTR(ret);
}
@@ -440,13 +381,13 @@ void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors)
int i;
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
- update_trip_temp(&sensors->soc_dts[i], 0, 0, 0);
- update_trip_temp(&sensors->soc_dts[i], 1, 0, 0);
remove_dts_thermal_zone(&sensors->soc_dts[i]);
+ dts_trips_reset(sensors, i);
}
kfree(sensors);
}
EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_exit);
-MODULE_IMPORT_NS(INTEL_TCC);
+MODULE_IMPORT_NS("INTEL_TCC");
MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("SoC DTS driver using side band interface");
diff --git a/drivers/thermal/intel/intel_soc_dts_iosf.h b/drivers/thermal/intel/intel_soc_dts_iosf.h
index c54945748200..44eee844ab3c 100644
--- a/drivers/thermal/intel/intel_soc_dts_iosf.h
+++ b/drivers/thermal/intel/intel_soc_dts_iosf.h
@@ -12,6 +12,9 @@
/* DTS0 and DTS 1 */
#define SOC_MAX_DTS_SENSORS 2
+/* Only 2 out of 4 is allowed for OSPM */
+#define SOC_MAX_DTS_TRIPS 2
+
enum intel_soc_dts_interrupt_type {
INTEL_SOC_DTS_INTERRUPT_NONE,
INTEL_SOC_DTS_INTERRUPT_APIC,
@@ -25,9 +28,6 @@ struct intel_soc_dts_sensors;
struct intel_soc_dts_sensor_entry {
int id;
u32 store_status;
- u32 trip_mask;
- u32 trip_count;
- enum thermal_trip_type trip_types[2];
struct thermal_zone_device *tzone;
struct intel_soc_dts_sensors *sensors;
};
@@ -40,12 +40,11 @@ struct intel_soc_dts_sensors {
struct intel_soc_dts_sensor_entry soc_dts[SOC_MAX_DTS_SENSORS];
};
-struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
- enum intel_soc_dts_interrupt_type intr_type, int trip_count,
- int read_only_trip_count);
+
+struct intel_soc_dts_sensors *
+intel_soc_dts_iosf_init(enum intel_soc_dts_interrupt_type intr_type,
+ bool critical_trip, int crit_offset);
void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors);
void intel_soc_dts_iosf_interrupt_handler(
struct intel_soc_dts_sensors *sensors);
-int intel_soc_dts_iosf_add_read_only_critical_trip(
- struct intel_soc_dts_sensors *sensors, int critical_offset);
#endif
diff --git a/drivers/thermal/intel/intel_soc_dts_thermal.c b/drivers/thermal/intel/intel_soc_dts_thermal.c
index 92e5c19d03f6..718c6326eaf4 100644
--- a/drivers/thermal/intel/intel_soc_dts_thermal.c
+++ b/drivers/thermal/intel/intel_soc_dts_thermal.c
@@ -36,7 +36,7 @@ static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data)
}
static const struct x86_cpu_id soc_thermal_ids[] = {
- X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT, BYT_SOC_DTS_APIC_IRQ),
+ X86_MATCH_VFM(INTEL_ATOM_SILVERMONT, BYT_SOC_DTS_APIC_IRQ),
{}
};
MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids);
@@ -51,7 +51,8 @@ static int __init intel_soc_thermal_init(void)
return -ENODEV;
/* Create a zone with 2 trips with marked as read only */
- soc_dts = intel_soc_dts_iosf_init(INTEL_SOC_DTS_INTERRUPT_APIC, 2, 1);
+ soc_dts = intel_soc_dts_iosf_init(INTEL_SOC_DTS_INTERRUPT_APIC, true,
+ crit_offset);
if (IS_ERR(soc_dts)) {
err = PTR_ERR(soc_dts);
return err;
@@ -88,21 +89,7 @@ static int __init intel_soc_thermal_init(void)
}
}
- err = intel_soc_dts_iosf_add_read_only_critical_trip(soc_dts,
- crit_offset);
- if (err)
- goto error_trips;
-
return 0;
-
-error_trips:
- if (soc_dts_thres_irq) {
- free_irq(soc_dts_thres_irq, soc_dts);
- acpi_unregister_gsi(soc_dts_thres_gsi);
- }
- intel_soc_dts_iosf_exit(soc_dts);
-
- return err;
}
static void __exit intel_soc_thermal_exit(void)
diff --git a/drivers/thermal/intel/intel_tcc.c b/drivers/thermal/intel/intel_tcc.c
index 2e5c741c41ca..b2a615aea7c1 100644
--- a/drivers/thermal/intel/intel_tcc.c
+++ b/drivers/thermal/intel/intel_tcc.c
@@ -6,9 +6,171 @@
#include <linux/errno.h>
#include <linux/intel_tcc.h>
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
#include <asm/msr.h>
/**
+ * struct temp_masks - Bitmasks for temperature readings
+ * @tcc_offset: TCC offset in MSR_TEMPERATURE_TARGET
+ * @digital_readout: Digital readout in MSR_IA32_THERM_STATUS
+ * @pkg_digital_readout: Digital readout in MSR_IA32_PACKAGE_THERM_STATUS
+ *
+ * Bitmasks to extract the fields of the MSR_TEMPERATURE and IA32_[PACKAGE]_
+ * THERM_STATUS registers for different processor models.
+ *
+ * The bitmask of TjMax is not included in this structure. It is always 0xff.
+ */
+struct temp_masks {
+ u32 tcc_offset;
+ u32 digital_readout;
+ u32 pkg_digital_readout;
+};
+
+#define TCC_MODEL_TEMP_MASKS(model, _tcc_offset, _digital_readout, \
+ _pkg_digital_readout) \
+ static const struct temp_masks temp_##model __initconst = { \
+ .tcc_offset = _tcc_offset, \
+ .digital_readout = _digital_readout, \
+ .pkg_digital_readout = _pkg_digital_readout \
+ }
+
+TCC_MODEL_TEMP_MASKS(nehalem, 0, 0x7f, 0x7f);
+TCC_MODEL_TEMP_MASKS(haswell_x, 0xf, 0x7f, 0x7f);
+TCC_MODEL_TEMP_MASKS(broadwell, 0x3f, 0x7f, 0x7f);
+TCC_MODEL_TEMP_MASKS(goldmont, 0x7f, 0x7f, 0x7f);
+TCC_MODEL_TEMP_MASKS(tigerlake, 0x3f, 0xff, 0xff);
+TCC_MODEL_TEMP_MASKS(sapphirerapids, 0x3f, 0x7f, 0xff);
+
+/* Use these masks for processors not included in @tcc_cpu_ids. */
+static struct temp_masks intel_tcc_temp_masks __ro_after_init = {
+ .tcc_offset = 0x7f,
+ .digital_readout = 0xff,
+ .pkg_digital_readout = 0xff,
+};
+
+static const struct x86_cpu_id intel_tcc_cpu_ids[] __initconst = {
+ X86_MATCH_VFM(INTEL_CORE_YONAH, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_CORE2_MEROM, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_CORE2_MEROM_L, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_CORE2_PENRYN, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_CORE2_DUNNINGTON, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_NEHALEM, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_NEHALEM_G, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_NEHALEM_EP, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_NEHALEM_EX, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_WESTMERE, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_WESTMERE_EP, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_WESTMERE_EX, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_SANDYBRIDGE, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_SANDYBRIDGE_X, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_IVYBRIDGE, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_IVYBRIDGE_X, &temp_haswell_x),
+ X86_MATCH_VFM(INTEL_HASWELL, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_HASWELL_X, &temp_haswell_x),
+ X86_MATCH_VFM(INTEL_HASWELL_L, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_HASWELL_G, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_BROADWELL, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_BROADWELL_G, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_BROADWELL_X, &temp_haswell_x),
+ X86_MATCH_VFM(INTEL_BROADWELL_D, &temp_haswell_x),
+ X86_MATCH_VFM(INTEL_SKYLAKE_L, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_SKYLAKE, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_SKYLAKE_X, &temp_haswell_x),
+ X86_MATCH_VFM(INTEL_KABYLAKE_L, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_KABYLAKE, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_COMETLAKE, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_COMETLAKE_L, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_CANNONLAKE_L, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ICELAKE_X, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ICELAKE_D, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ICELAKE, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ICELAKE_L, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ICELAKE_NNPI, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ROCKETLAKE, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_TIGERLAKE_L, &temp_tigerlake),
+ X86_MATCH_VFM(INTEL_TIGERLAKE, &temp_tigerlake),
+ X86_MATCH_VFM(INTEL_SAPPHIRERAPIDS_X, &temp_sapphirerapids),
+ X86_MATCH_VFM(INTEL_EMERALDRAPIDS_X, &temp_sapphirerapids),
+ X86_MATCH_VFM(INTEL_LAKEFIELD, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ALDERLAKE, &temp_tigerlake),
+ X86_MATCH_VFM(INTEL_ALDERLAKE_L, &temp_tigerlake),
+ X86_MATCH_VFM(INTEL_RAPTORLAKE, &temp_tigerlake),
+ X86_MATCH_VFM(INTEL_RAPTORLAKE_P, &temp_tigerlake),
+ X86_MATCH_VFM(INTEL_RAPTORLAKE_S, &temp_tigerlake),
+ X86_MATCH_VFM(INTEL_ATOM_BONNELL, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_ATOM_BONNELL_MID, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_ATOM_SALTWELL, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_ATOM_SALTWELL_MID, &temp_nehalem),
+ X86_MATCH_VFM(INTEL_ATOM_SILVERMONT, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ATOM_SILVERMONT_D, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ATOM_SILVERMONT_MID, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ATOM_AIRMONT, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ATOM_SILVERMONT_MID2, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ATOM_AIRMONT_NP, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ATOM_GOLDMONT, &temp_goldmont),
+ X86_MATCH_VFM(INTEL_ATOM_GOLDMONT_D, &temp_goldmont),
+ X86_MATCH_VFM(INTEL_ATOM_GOLDMONT_PLUS, &temp_goldmont),
+ X86_MATCH_VFM(INTEL_ATOM_TREMONT_D, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ATOM_TREMONT, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ATOM_TREMONT_L, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_ATOM_GRACEMONT, &temp_tigerlake),
+ X86_MATCH_VFM(INTEL_XEON_PHI_KNL, &temp_broadwell),
+ X86_MATCH_VFM(INTEL_XEON_PHI_KNM, &temp_broadwell),
+ {}
+};
+
+static int __init intel_tcc_init(void)
+{
+ const struct x86_cpu_id *id;
+
+ id = x86_match_cpu(intel_tcc_cpu_ids);
+ if (id)
+ memcpy(&intel_tcc_temp_masks, (const void *)id->driver_data,
+ sizeof(intel_tcc_temp_masks));
+
+ return 0;
+}
+/*
+ * Use subsys_initcall to ensure temperature bitmasks are initialized before
+ * the drivers that use this library.
+ */
+subsys_initcall(intel_tcc_init);
+
+/**
+ * intel_tcc_get_offset_mask() - Returns the bitmask to read TCC offset
+ *
+ * Get the model-specific bitmask to extract TCC_OFFSET from the MSR
+ * TEMPERATURE_TARGET register. If the mask is 0, it means the processor does
+ * not support TCC offset.
+ *
+ * Return: The model-specific bitmask for TCC offset.
+ */
+u32 intel_tcc_get_offset_mask(void)
+{
+ return intel_tcc_temp_masks.tcc_offset;
+}
+EXPORT_SYMBOL_NS(intel_tcc_get_offset_mask, "INTEL_TCC");
+
+/**
+ * get_temp_mask() - Returns the model-specific bitmask for temperature
+ *
+ * @pkg: true: Package Thermal Sensor. false: Core Thermal Sensor.
+ *
+ * Get the model-specific bitmask to extract the temperature reading from the
+ * MSR_IA32_[PACKAGE]_THERM_STATUS register.
+ *
+ * Callers must check if the thermal status registers are supported.
+ *
+ * Return: The model-specific bitmask for temperature reading
+ */
+static u32 get_temp_mask(bool pkg)
+{
+ return pkg ? intel_tcc_temp_masks.pkg_digital_readout :
+ intel_tcc_temp_masks.digital_readout;
+}
+
+/**
* intel_tcc_get_tjmax() - returns the default TCC activation Temperature
* @cpu: cpu that the MSR should be run on, nagative value means any cpu.
*
@@ -33,7 +195,7 @@ int intel_tcc_get_tjmax(int cpu)
return val ? val : -ENODATA;
}
-EXPORT_SYMBOL_NS_GPL(intel_tcc_get_tjmax, INTEL_TCC);
+EXPORT_SYMBOL_NS_GPL(intel_tcc_get_tjmax, "INTEL_TCC");
/**
* intel_tcc_get_offset() - returns the TCC Offset value to Tjmax
@@ -56,9 +218,9 @@ int intel_tcc_get_offset(int cpu)
if (err)
return err;
- return (low >> 24) & 0x3f;
+ return (low >> 24) & intel_tcc_temp_masks.tcc_offset;
}
-EXPORT_SYMBOL_NS_GPL(intel_tcc_get_offset, INTEL_TCC);
+EXPORT_SYMBOL_NS_GPL(intel_tcc_get_offset, "INTEL_TCC");
/**
* intel_tcc_set_offset() - set the TCC offset value to Tjmax
@@ -76,7 +238,10 @@ int intel_tcc_set_offset(int cpu, int offset)
u32 low, high;
int err;
- if (offset < 0 || offset > 0x3f)
+ if (!intel_tcc_temp_masks.tcc_offset)
+ return -ENODEV;
+
+ if (offset < 0 || offset > intel_tcc_temp_masks.tcc_offset)
return -EINVAL;
if (cpu < 0)
@@ -90,7 +255,7 @@ int intel_tcc_set_offset(int cpu, int offset)
if (low & BIT(31))
return -EPERM;
- low &= ~(0x3f << 24);
+ low &= ~(intel_tcc_temp_masks.tcc_offset << 24);
low |= offset << 24;
if (cpu < 0)
@@ -98,23 +263,24 @@ int intel_tcc_set_offset(int cpu, int offset)
else
return wrmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, low, high);
}
-EXPORT_SYMBOL_NS_GPL(intel_tcc_set_offset, INTEL_TCC);
+EXPORT_SYMBOL_NS_GPL(intel_tcc_set_offset, "INTEL_TCC");
/**
* intel_tcc_get_temp() - returns the current temperature
* @cpu: cpu that the MSR should be run on, nagative value means any cpu.
+ * @temp: pointer to the memory for saving cpu temperature.
* @pkg: true: Package Thermal Sensor. false: Core Thermal Sensor.
*
* Get the current temperature returned by the CPU core/package level
* thermal sensor, in degrees C.
*
- * Return: Temperature in degrees C on success, negative error code otherwise.
+ * Return: 0 on success, negative error code otherwise.
*/
-int intel_tcc_get_temp(int cpu, bool pkg)
+int intel_tcc_get_temp(int cpu, int *temp, bool pkg)
{
- u32 low, high;
u32 msr = pkg ? MSR_IA32_PACKAGE_THERM_STATUS : MSR_IA32_THERM_STATUS;
- int tjmax, temp, err;
+ u32 low, high, mask;
+ int tjmax, err;
tjmax = intel_tcc_get_tjmax(cpu);
if (tjmax < 0)
@@ -131,9 +297,10 @@ int intel_tcc_get_temp(int cpu, bool pkg)
if (!(low & BIT(31)))
return -ENODATA;
- temp = tjmax - ((low >> 16) & 0x7f);
+ mask = get_temp_mask(pkg);
+
+ *temp = tjmax - ((low >> 16) & mask);
- /* Do not allow negative CPU temperature */
- return temp >= 0 ? temp : -ENODATA;
+ return 0;
}
-EXPORT_SYMBOL_NS_GPL(intel_tcc_get_temp, INTEL_TCC);
+EXPORT_SYMBOL_NS_GPL(intel_tcc_get_temp, "INTEL_TCC");
diff --git a/drivers/thermal/intel/intel_tcc_cooling.c b/drivers/thermal/intel/intel_tcc_cooling.c
index e95f799454fe..f352ecafbedf 100644
--- a/drivers/thermal/intel/intel_tcc_cooling.c
+++ b/drivers/thermal/intel/intel_tcc_cooling.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/thermal.h>
#include <asm/cpu_device_id.h>
+#include <asm/msr.h>
#define TCC_PROGRAMMABLE BIT(30)
#define TCC_LOCKED BIT(31)
@@ -20,7 +21,7 @@ static struct thermal_cooling_device *tcc_cdev;
static int tcc_get_max_state(struct thermal_cooling_device *cdev, unsigned long
*state)
{
- *state = 0x3f;
+ *state = intel_tcc_get_offset_mask();
return 0;
}
@@ -49,21 +50,21 @@ static const struct thermal_cooling_device_ops tcc_cooling_ops = {
};
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),
- X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_S, NULL),
+ X86_MATCH_VFM(INTEL_SKYLAKE, NULL),
+ X86_MATCH_VFM(INTEL_SKYLAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_KABYLAKE, NULL),
+ X86_MATCH_VFM(INTEL_KABYLAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_ICELAKE, NULL),
+ X86_MATCH_VFM(INTEL_ICELAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_TIGERLAKE, NULL),
+ X86_MATCH_VFM(INTEL_TIGERLAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_COMETLAKE, NULL),
+ X86_MATCH_VFM(INTEL_ALDERLAKE, NULL),
+ X86_MATCH_VFM(INTEL_ALDERLAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_ATOM_GRACEMONT, NULL),
+ X86_MATCH_VFM(INTEL_RAPTORLAKE, NULL),
+ X86_MATCH_VFM(INTEL_RAPTORLAKE_P, NULL),
+ X86_MATCH_VFM(INTEL_RAPTORLAKE_S, NULL),
{}
};
@@ -81,14 +82,14 @@ static int __init tcc_cooling_init(void)
if (!id)
return -ENODEV;
- err = rdmsrl_safe(MSR_PLATFORM_INFO, &val);
+ err = rdmsrq_safe(MSR_PLATFORM_INFO, &val);
if (err)
return err;
if (!(val & TCC_PROGRAMMABLE))
return -ENODEV;
- err = rdmsrl_safe(MSR_IA32_TEMPERATURE_TARGET, &val);
+ err = rdmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, &val);
if (err)
return err;
@@ -118,7 +119,7 @@ static void __exit tcc_cooling_exit(void)
module_exit(tcc_cooling_exit)
-MODULE_IMPORT_NS(INTEL_TCC);
+MODULE_IMPORT_NS("INTEL_TCC");
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
index e69868e868eb..debc94e2dc16 100644
--- a/drivers/thermal/intel/therm_throt.c
+++ b/drivers/thermal/intel/therm_throt.c
@@ -273,7 +273,7 @@ void thermal_clear_package_intr_status(int level, u64 bit_mask)
}
msr_val &= ~bit_mask;
- wrmsrl(msr, msr_val);
+ wrmsrq(msr, msr_val);
}
EXPORT_SYMBOL_GPL(thermal_clear_package_intr_status);
@@ -287,7 +287,7 @@ static void get_therm_status(int level, bool *proc_hot, u8 *temp)
else
msr = MSR_IA32_PACKAGE_THERM_STATUS;
- rdmsrl(msr, msr_val);
+ rdmsrq(msr, msr_val);
if (msr_val & THERM_STATUS_PROCHOT_LOG)
*proc_hot = true;
else
@@ -643,7 +643,7 @@ static void notify_thresholds(__u64 msr_val)
void __weak notify_hwp_interrupt(void)
{
- wrmsrl_safe(MSR_HWP_STATUS, 0);
+ wrmsrq_safe(MSR_HWP_STATUS, 0);
}
/* Thermal transition interrupt handler */
@@ -654,7 +654,7 @@ void intel_thermal_interrupt(void)
if (static_cpu_has(X86_FEATURE_HWP))
notify_hwp_interrupt();
- rdmsrl(MSR_IA32_THERM_STATUS, msr_val);
+ rdmsrq(MSR_IA32_THERM_STATUS, msr_val);
/* Check for violation of core thermal thresholds*/
notify_thresholds(msr_val);
@@ -669,7 +669,7 @@ void intel_thermal_interrupt(void)
CORE_LEVEL);
if (this_cpu_has(X86_FEATURE_PTS)) {
- rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
+ rdmsrq(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,
diff --git a/drivers/thermal/intel/x86_pkg_temp_thermal.c b/drivers/thermal/intel/x86_pkg_temp_thermal.c
index 11a7f8108bbb..3fc679b6f11b 100644
--- a/drivers/thermal/intel/x86_pkg_temp_thermal.c
+++ b/drivers/thermal/intel/x86_pkg_temp_thermal.c
@@ -20,6 +20,7 @@
#include <linux/debugfs.h>
#include <asm/cpu_device_id.h>
+#include <asm/msr.h>
#include "thermal_interrupt.h"
@@ -53,7 +54,6 @@ struct zone_device {
u32 msr_pkg_therm_high;
struct delayed_work work;
struct thermal_zone_device *tzone;
- struct thermal_trip *trips;
struct cpumask cpumask;
};
@@ -108,11 +108,11 @@ static struct zone_device *pkg_temp_thermal_get_dev(unsigned int cpu)
static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
{
struct zone_device *zonedev = thermal_zone_device_priv(tzd);
- int val;
+ int val, ret;
- val = intel_tcc_get_temp(zonedev->cpu, true);
- if (val < 0)
- return val;
+ ret = intel_tcc_get_temp(zonedev->cpu, &val, true);
+ if (ret < 0)
+ return ret;
*temp = val * 1000;
pr_debug("sys_get_curr_temp %d\n", *temp);
@@ -120,9 +120,11 @@ static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
}
static int
-sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
+sys_set_trip_temp(struct thermal_zone_device *tzd,
+ const struct thermal_trip *trip, int temp)
{
struct zone_device *zonedev = thermal_zone_device_priv(tzd);
+ unsigned int trip_index = THERMAL_TRIP_PRIV_TO_INT(trip->priv);
u32 l, h, mask, shift, intr;
int tj_max, val, ret;
@@ -133,7 +135,7 @@ sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
val = (tj_max - temp)/1000;
- if (trip >= MAX_NUMBER_OF_TRIPS || val < 0 || val > 0x7f)
+ if (trip_index >= MAX_NUMBER_OF_TRIPS || val < 0 || val > 0x7f)
return -EINVAL;
ret = rdmsr_on_cpu(zonedev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
@@ -141,7 +143,7 @@ sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
if (ret < 0)
return ret;
- if (trip) {
+ if (trip_index) {
mask = THERM_MASK_THRESHOLD1;
shift = THERM_SHIFT_THRESHOLD1;
intr = THERM_INT_THRESHOLD1_ENABLE;
@@ -167,7 +169,7 @@ sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
}
/* Thermal zone callback registry */
-static struct thermal_zone_device_ops tzone_ops = {
+static const struct thermal_zone_device_ops tzone_ops = {
.get_temp = sys_get_curr_temp,
.set_trip_temp = sys_set_trip_temp,
};
@@ -268,17 +270,13 @@ static int pkg_thermal_notify(u64 msr_val)
return 0;
}
-static struct thermal_trip *pkg_temp_thermal_trips_init(int cpu, int tj_max, int num_trips)
+static int pkg_temp_thermal_trips_init(int cpu, int tj_max,
+ struct thermal_trip *trips, int num_trips)
{
- struct thermal_trip *trips;
unsigned long thres_reg_value;
u32 mask, shift, eax, edx;
int ret, i;
- trips = kzalloc(sizeof(*trips) * num_trips, GFP_KERNEL);
- if (!trips)
- return ERR_PTR(-ENOMEM);
-
for (i = 0; i < num_trips; i++) {
if (i) {
@@ -291,10 +289,8 @@ static struct thermal_trip *pkg_temp_thermal_trips_init(int cpu, int tj_max, int
ret = rdmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
&eax, &edx);
- if (ret < 0) {
- kfree(trips);
- return ERR_PTR(ret);
- }
+ if (ret < 0)
+ return ret;
thres_reg_value = (eax & mask) >> shift;
@@ -302,16 +298,19 @@ static struct thermal_trip *pkg_temp_thermal_trips_init(int cpu, int tj_max, int
tj_max - thres_reg_value * 1000 : THERMAL_TEMP_INVALID;
trips[i].type = THERMAL_TRIP_PASSIVE;
+ trips[i].flags |= THERMAL_TRIP_FLAG_RW_TEMP;
+ trips[i].priv = THERMAL_INT_TO_TRIP_PRIV(i);
pr_debug("%s: cpu=%d, trip=%d, temp=%d\n",
__func__, cpu, i, trips[i].temperature);
}
- return trips;
+ return 0;
}
static int pkg_temp_thermal_device_add(unsigned int cpu)
{
+ struct thermal_trip trips[MAX_NUMBER_OF_TRIPS] = { 0 };
int id = topology_logical_die_id(cpu);
u32 eax, ebx, ecx, edx;
struct zone_device *zonedev;
@@ -331,26 +330,24 @@ static int pkg_temp_thermal_device_add(unsigned int cpu)
tj_max = intel_tcc_get_tjmax(cpu);
if (tj_max < 0)
return tj_max;
+ tj_max *= 1000;
zonedev = kzalloc(sizeof(*zonedev), GFP_KERNEL);
if (!zonedev)
return -ENOMEM;
- zonedev->trips = pkg_temp_thermal_trips_init(cpu, tj_max, thres_count);
- if (IS_ERR(zonedev->trips)) {
- err = PTR_ERR(zonedev->trips);
+ err = pkg_temp_thermal_trips_init(cpu, tj_max, trips, thres_count);
+ if (err)
goto out_kfree_zonedev;
- }
INIT_DELAYED_WORK(&zonedev->work, pkg_temp_thermal_threshold_work_fn);
zonedev->cpu = cpu;
zonedev->tzone = thermal_zone_device_register_with_trips("x86_pkg_temp",
- zonedev->trips, thres_count,
- (thres_count == MAX_NUMBER_OF_TRIPS) ? 0x03 : 0x01,
+ trips, thres_count,
zonedev, &tzone_ops, &pkg_temp_tz_params, 0, 0);
if (IS_ERR(zonedev->tzone)) {
err = PTR_ERR(zonedev->tzone);
- goto out_kfree_trips;
+ goto out_kfree_zonedev;
}
err = thermal_zone_device_enable(zonedev->tzone);
if (err)
@@ -369,8 +366,6 @@ static int pkg_temp_thermal_device_add(unsigned int cpu)
out_unregister_tz:
thermal_zone_device_unregister(zonedev->tzone);
-out_kfree_trips:
- kfree(zonedev->trips);
out_kfree_zonedev:
kfree(zonedev);
return err;
@@ -457,10 +452,9 @@ static int pkg_thermal_cpu_offline(unsigned int cpu)
raw_spin_unlock_irq(&pkg_temp_lock);
/* Final cleanup if this is the last cpu */
- if (lastcpu) {
- kfree(zonedev->trips);
+ if (lastcpu)
kfree(zonedev);
- }
+
return 0;
}
@@ -494,7 +488,7 @@ static int __init pkg_temp_thermal_init(void)
if (!x86_match_cpu(pkg_temp_thermal_ids))
return -ENODEV;
- max_id = topology_max_packages() * topology_max_die_per_package();
+ max_id = topology_max_packages() * topology_max_dies_per_package();
zones = kcalloc(max_id, sizeof(struct zone_device *),
GFP_KERNEL);
if (!zones)
@@ -532,7 +526,7 @@ static void __exit pkg_temp_thermal_exit(void)
}
module_exit(pkg_temp_thermal_exit)
-MODULE_IMPORT_NS(INTEL_TCC);
+MODULE_IMPORT_NS("INTEL_TCC");
MODULE_DESCRIPTION("X86 PKG TEMP Thermal Driver");
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/k3_bandgap.c b/drivers/thermal/k3_bandgap.c
index 1c3e590157ec..678d6ed711b5 100644
--- a/drivers/thermal/k3_bandgap.c
+++ b/drivers/thermal/k3_bandgap.c
@@ -11,7 +11,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_platform.h>
+#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/thermal.h>
#include <linux/types.h>
@@ -78,7 +78,6 @@ static const int k3_adc_to_temp[] = {
struct k3_bandgap {
void __iomem *base;
- const struct k3_bandgap_data *conf;
};
/* common data structures */
@@ -225,7 +224,6 @@ static int k3_bandgap_probe(struct platform_device *pdev)
devm_thermal_add_hwmon_sysfs(dev, data[id].tzd);
}
- platform_set_drvdata(pdev, bgp);
return 0;
@@ -236,12 +234,10 @@ err_alloc:
return ret;
}
-static int k3_bandgap_remove(struct platform_device *pdev)
+static void 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[] = {
diff --git a/drivers/thermal/k3_j72xx_bandgap.c b/drivers/thermal/k3_j72xx_bandgap.c
index 5be1f09eeb2c..d9ec3bf19496 100644
--- a/drivers/thermal/k3_j72xx_bandgap.c
+++ b/drivers/thermal/k3_j72xx_bandgap.c
@@ -10,16 +10,18 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/platform_device.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>
+#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 0x300
@@ -178,6 +180,7 @@ struct k3_j72xx_bandgap {
void __iomem *base;
void __iomem *cfg2_base;
struct k3_thermal_data *ts_data[K3_VTM_MAX_NUM_TS];
+ int cnt;
};
/* common data structures */
@@ -237,7 +240,7 @@ static inline int k3_bgp_read_temp(struct k3_thermal_data *devdata,
K3_VTM_TS_STAT_DTEMP_MASK;
dtemp = vtm_get_best_value(s0, s1, s2);
- if (dtemp < 0 || dtemp >= TABLE_SIZE)
+ if (dtemp >= TABLE_SIZE)
return -EINVAL;
*temp = derived_table[dtemp];
@@ -338,24 +341,52 @@ static void print_look_up_table(struct device *dev, int *ref_table)
dev_dbg(dev, "%d %d %d\n", i, derived_table[i], ref_table[i]);
}
+static void k3_j72xx_bandgap_init_hw(struct k3_j72xx_bandgap *bgp)
+{
+ struct k3_thermal_data *data;
+ int id, high_max, low_temp;
+ u32 val;
+
+ for (id = 0; id < bgp->cnt; id++) {
+ data = bgp->ts_data[id];
+ val = readl(bgp->cfg2_base + data->ctrl_offset);
+ val |= (K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN |
+ K3_VTM_TMPSENS_CTRL_SOC |
+ K3_VTM_TMPSENS_CTRL_CLRZ | BIT(4));
+ writel(val, bgp->cfg2_base + data->ctrl_offset);
+ }
+
+ /*
+ * 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, bgp->cfg2_base + K3_VTM_MISC_CTRL2_OFFSET);
+ writel(K3_VTM_ANYMAXT_OUTRG_ALERT_EN, bgp->cfg2_base + K3_VTM_MISC_CTRL_OFFSET);
+}
+
struct k3_j72xx_bandgap_data {
const bool 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;
+ const struct k3_j72xx_bandgap_data *driver_data;
+ struct thermal_zone_device *ti_thermal;
struct device *dev = &pdev->dev;
+ bool workaround_needed = false;
struct k3_j72xx_bandgap *bgp;
struct k3_thermal_data *data;
- bool workaround_needed = false;
- const struct k3_j72xx_bandgap_data *driver_data;
- struct thermal_zone_device *ti_thermal;
- int *ref_table;
struct err_values err_vals;
void __iomem *fuse_base;
+ int ret = 0, val, id;
+ struct resource *res;
+ int *ref_table;
const s64 golden_factors[] = {
-490019999999999936,
@@ -422,22 +453,22 @@ static int k3_j72xx_bandgap_probe(struct platform_device *pdev)
/* 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);
+ bgp->cnt = val & K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK;
+ bgp->cnt >>= __ffs(K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK);
- data = devm_kcalloc(bgp->dev, cnt, sizeof(*data), GFP_KERNEL);
+ data = devm_kcalloc(bgp->dev, bgp->cnt, sizeof(*data), GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto err_alloc;
}
- ref_table = kzalloc(sizeof(*ref_table) * TABLE_SIZE, GFP_KERNEL);
+ ref_table = kcalloc(TABLE_SIZE, sizeof(*ref_table), GFP_KERNEL);
if (!ref_table) {
ret = -ENOMEM;
goto err_alloc;
}
- derived_table = devm_kzalloc(bgp->dev, sizeof(*derived_table) * TABLE_SIZE,
+ derived_table = devm_kcalloc(bgp->dev, TABLE_SIZE, sizeof(*derived_table),
GFP_KERNEL);
if (!derived_table) {
ret = -ENOMEM;
@@ -449,8 +480,8 @@ static int k3_j72xx_bandgap_probe(struct platform_device *pdev)
else
init_table(3, ref_table, pvt_wa_factors);
- /* Register the thermal sensors */
- for (id = 0; id < cnt; id++) {
+ /* Precompute the derived table & fill each thermal sensor struct */
+ for (id = 0; id < bgp->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 +
@@ -470,13 +501,13 @@ static int k3_j72xx_bandgap_probe(struct platform_device *pdev)
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];
+ }
+
+ k3_j72xx_bandgap_init_hw(bgp);
+
+ /* Register the thermal sensors */
+ for (id = 0; id < bgp->cnt; id++) {
ti_thermal = devm_thermal_of_zone_register(bgp->dev, id, &data[id],
&k3_of_thermal_ops);
if (IS_ERR(ti_thermal)) {
@@ -484,23 +515,9 @@ static int k3_j72xx_bandgap_probe(struct platform_device *pdev)
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);
+ devm_thermal_add_hwmon_sysfs(bgp->dev, ti_thermal);
+ }
platform_set_drvdata(pdev, bgp);
@@ -523,14 +540,41 @@ err_alloc:
return ret;
}
-static int k3_j72xx_bandgap_remove(struct platform_device *pdev)
+static void k3_j72xx_bandgap_remove(struct platform_device *pdev)
{
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
+}
+static int k3_j72xx_bandgap_suspend(struct device *dev)
+{
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
return 0;
}
+static int k3_j72xx_bandgap_resume(struct device *dev)
+{
+ struct k3_j72xx_bandgap *bgp = dev_get_drvdata(dev);
+ int ret;
+
+ pm_runtime_enable(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ pm_runtime_disable(dev);
+ return ret;
+ }
+
+ k3_j72xx_bandgap_init_hw(bgp);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(k3_j72xx_bandgap_pm_ops,
+ k3_j72xx_bandgap_suspend,
+ k3_j72xx_bandgap_resume);
+
static const struct k3_j72xx_bandgap_data k3_j72xx_bandgap_j721e_data = {
.has_errata_i2128 = true,
};
@@ -558,6 +602,7 @@ static struct platform_driver k3_j72xx_bandgap_sensor_driver = {
.driver = {
.name = "k3-j72xx-soc-thermal",
.of_match_table = of_k3_j72xx_bandgap_match,
+ .pm = pm_sleep_ptr(&k3_j72xx_bandgap_pm_ops),
},
};
diff --git a/drivers/thermal/kirkwood_thermal.c b/drivers/thermal/kirkwood_thermal.c
index 668747bd86ef..4619e090f756 100644
--- a/drivers/thermal/kirkwood_thermal.c
+++ b/drivers/thermal/kirkwood_thermal.c
@@ -48,7 +48,7 @@ static int kirkwood_get_temp(struct thermal_zone_device *thermal,
return 0;
}
-static struct thermal_zone_device_ops ops = {
+static const struct thermal_zone_device_ops ops = {
.get_temp = kirkwood_get_temp,
};
@@ -71,8 +71,8 @@ static int kirkwood_thermal_probe(struct platform_device *pdev)
if (IS_ERR(priv->sensor))
return PTR_ERR(priv->sensor);
- thermal = thermal_zone_device_register("kirkwood_thermal", 0, 0,
- priv, &ops, NULL, 0, 0);
+ thermal = thermal_tripless_zone_device_register("kirkwood_thermal",
+ priv, &ops, NULL);
if (IS_ERR(thermal)) {
dev_err(&pdev->dev,
"Failed to register thermal zone device\n");
@@ -90,14 +90,12 @@ static int kirkwood_thermal_probe(struct platform_device *pdev)
return 0;
}
-static int kirkwood_thermal_exit(struct platform_device *pdev)
+static void kirkwood_thermal_exit(struct platform_device *pdev)
{
struct thermal_zone_device *kirkwood_thermal =
platform_get_drvdata(pdev);
thermal_zone_device_unregister(kirkwood_thermal);
-
- return 0;
}
MODULE_DEVICE_TABLE(of, kirkwood_thermal_id_table);
diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
new file mode 100644
index 000000000000..ea4dd2fb1f47
--- /dev/null
+++ b/drivers/thermal/loongson2_thermal.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Author: zhanghongchen <zhanghongchen@loongson.cn>
+ * Yinbo Zhu <zhuyinbo@loongson.cn>
+ * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "thermal_hwmon.h"
+
+#define LOONGSON2_MAX_SENSOR_SEL_NUM 3
+
+#define LOONGSON2_THSENS_CTRL_HI_REG 0x0
+#define LOONGSON2_THSENS_CTRL_LOW_REG 0x8
+#define LOONGSON2_THSENS_STATUS_REG 0x10
+#define LOONGSON2_THSENS_OUT_REG 0x14
+
+#define LOONGSON2_THSENS_INT_LO BIT(0)
+#define LOONGSON2_THSENS_INT_HIGH BIT(1)
+#define LOONGSON2_THSENS_INT_EN (LOONGSON2_THSENS_INT_LO | \
+ LOONGSON2_THSENS_INT_HIGH)
+#define LOONGSON2_THSENS_OUT_MASK 0xFF
+
+/*
+ * This flag is used to indicate the temperature reading
+ * method of the Loongson-2K2000
+ */
+#define LS2K2000_THSENS_OUT_FLAG BIT(0)
+
+struct loongson2_thermal_chip_data {
+ unsigned int thermal_sensor_sel;
+ unsigned int flags;
+};
+
+struct loongson2_thermal_data {
+ void __iomem *ctrl_reg;
+ void __iomem *temp_reg;
+ const struct loongson2_thermal_chip_data *chip_data;
+};
+
+static void loongson2_set_ctrl_regs(struct loongson2_thermal_data *data,
+ int ctrl_data, bool low, bool enable)
+{
+ int reg_ctrl = 0;
+ int reg_off = data->chip_data->thermal_sensor_sel * 2;
+ int ctrl_reg = low ? LOONGSON2_THSENS_CTRL_LOW_REG : LOONGSON2_THSENS_CTRL_HI_REG;
+
+ reg_ctrl = ctrl_data + HECTO;
+ reg_ctrl |= enable ? 0x100 : 0;
+ writew(reg_ctrl, data->ctrl_reg + ctrl_reg + reg_off);
+}
+
+static int loongson2_thermal_set(struct loongson2_thermal_data *data,
+ int low, int high, bool enable)
+{
+ /* Set low temperature threshold */
+ loongson2_set_ctrl_regs(data, clamp(-40, low, high), true, enable);
+
+ /* Set high temperature threshold */
+ loongson2_set_ctrl_regs(data, clamp(125, low, high), false, enable);
+
+ return 0;
+}
+
+static int loongson2_2k1000_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ int val;
+ struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
+
+ val = readl(data->ctrl_reg + LOONGSON2_THSENS_OUT_REG);
+ *temp = ((val & LOONGSON2_THSENS_OUT_MASK) - HECTO) * KILO;
+
+ return 0;
+}
+
+static int loongson2_2k2000_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ int val;
+ struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
+
+ val = readl(data->temp_reg);
+ *temp = ((val & 0xffff) * 820 / 0x4000 - 311) * KILO;
+
+ return 0;
+}
+
+static irqreturn_t loongson2_thermal_irq_thread(int irq, void *dev)
+{
+ struct thermal_zone_device *tzd = dev;
+ struct loongson2_thermal_data *data = thermal_zone_device_priv(tzd);
+
+ writeb(LOONGSON2_THSENS_INT_EN, data->ctrl_reg + LOONGSON2_THSENS_STATUS_REG);
+
+ thermal_zone_device_update(tzd, THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
+
+ return loongson2_thermal_set(data, low/MILLI, high/MILLI, true);
+}
+
+static const struct thermal_zone_device_ops loongson2_2k1000_of_thermal_ops = {
+ .get_temp = loongson2_2k1000_get_temp,
+ .set_trips = loongson2_thermal_set_trips,
+};
+
+static const struct thermal_zone_device_ops loongson2_2k2000_of_thermal_ops = {
+ .get_temp = loongson2_2k2000_get_temp,
+ .set_trips = loongson2_thermal_set_trips,
+};
+
+static int loongson2_thermal_probe(struct platform_device *pdev)
+{
+ const struct thermal_zone_device_ops *thermal_ops;
+ struct device *dev = &pdev->dev;
+ struct loongson2_thermal_data *data;
+ struct thermal_zone_device *tzd;
+ int ret, irq, i;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->chip_data = device_get_match_data(dev);
+
+ data->ctrl_reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(data->ctrl_reg))
+ return PTR_ERR(data->ctrl_reg);
+
+ /* The temperature output register is separate for Loongson-2K2000 */
+ if (data->chip_data->flags & LS2K2000_THSENS_OUT_FLAG) {
+ data->temp_reg = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(data->temp_reg))
+ return PTR_ERR(data->temp_reg);
+
+ thermal_ops = &loongson2_2k2000_of_thermal_ops;
+ } else {
+ thermal_ops = &loongson2_2k1000_of_thermal_ops;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ writeb(LOONGSON2_THSENS_INT_EN, data->ctrl_reg + LOONGSON2_THSENS_STATUS_REG);
+
+ loongson2_thermal_set(data, 0, 0, false);
+
+ for (i = 0; i <= LOONGSON2_MAX_SENSOR_SEL_NUM; i++) {
+ tzd = devm_thermal_of_zone_register(dev, i, data, thermal_ops);
+
+ if (!IS_ERR(tzd))
+ break;
+
+ if (PTR_ERR(tzd) != -ENODEV)
+ continue;
+
+ return dev_err_probe(dev, PTR_ERR(tzd), "failed to register");
+ }
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, loongson2_thermal_irq_thread,
+ IRQF_ONESHOT, "loongson2_thermal", tzd);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to request alarm irq\n");
+
+ devm_thermal_add_hwmon_sysfs(dev, tzd);
+
+ return 0;
+}
+
+static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k1000_data = {
+ .thermal_sensor_sel = 0,
+ .flags = 0,
+};
+
+static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k2000_data = {
+ .thermal_sensor_sel = 0,
+ .flags = LS2K2000_THSENS_OUT_FLAG,
+};
+
+static const struct of_device_id of_loongson2_thermal_match[] = {
+ {
+ .compatible = "loongson,ls2k1000-thermal",
+ .data = &loongson2_thermal_ls2k1000_data,
+ },
+ {
+ .compatible = "loongson,ls2k2000-thermal",
+ .data = &loongson2_thermal_ls2k2000_data,
+ },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_loongson2_thermal_match);
+
+static struct platform_driver loongson2_thermal_driver = {
+ .driver = {
+ .name = "loongson2_thermal",
+ .of_match_table = of_loongson2_thermal_match,
+ },
+ .probe = loongson2_thermal_probe,
+};
+module_platform_driver(loongson2_thermal_driver);
+
+MODULE_DESCRIPTION("Loongson2 thermal driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c
index 61c7622d9945..85a12e98d6dc 100644
--- a/drivers/thermal/max77620_thermal.c
+++ b/drivers/thermal/max77620_thermal.c
@@ -114,12 +114,8 @@ static int max77620_thermal_probe(struct platform_device *pdev)
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);
- dev_err(&pdev->dev, "Failed to register thermal zone: %d\n",
- ret);
- return ret;
- }
+ if (IS_ERR(mtherm->tz_device))
+ return PTR_ERR(mtherm->tz_device);
ret = devm_request_threaded_irq(&pdev->dev, mtherm->irq_tjalarm1, NULL,
max77620_thermal_irq,
@@ -139,8 +135,6 @@ static int max77620_thermal_probe(struct platform_device *pdev)
return ret;
}
- platform_set_drvdata(pdev, mtherm);
-
return 0;
}
diff --git a/drivers/thermal/mediatek/auxadc_thermal.c b/drivers/thermal/mediatek/auxadc_thermal.c
index f59d36de20a0..9ee2e7283435 100644
--- a/drivers/thermal/mediatek/auxadc_thermal.c
+++ b/drivers/thermal/mediatek/auxadc_thermal.c
@@ -15,7 +15,6 @@
#include <linux/nvmem-consumer.h>
#include <linux/of.h>
#include <linux/of_address.h>
-#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/io.h>
@@ -691,6 +690,9 @@ static const struct mtk_thermal_data mt7986_thermal_data = {
.adcpnp = mt7986_adcpnp,
.sensor_mux_values = mt7986_mux_values,
.version = MTK_THERMAL_V3,
+ .apmixed_buffer_ctl_reg = APMIXED_SYS_TS_CON1,
+ .apmixed_buffer_ctl_mask = GENMASK(31, 6) | BIT(3),
+ .apmixed_buffer_ctl_set = BIT(0),
};
static bool mtk_thermal_temp_is_valid(int temp)
@@ -1268,7 +1270,7 @@ static int mtk_thermal_probe(struct platform_device *pdev)
mtk_thermal_turn_on_buffer(mt, apmixed_base);
- if (mt->conf->version != MTK_THERMAL_V2)
+ if (mt->conf->version != MTK_THERMAL_V1)
mtk_thermal_release_periodic_ts(mt, auxadc_base);
if (mt->conf->version == MTK_THERMAL_V1)
@@ -1283,8 +1285,6 @@ static int mtk_thermal_probe(struct platform_device *pdev)
mtk_thermal_init_bank(mt, i, apmixed_phys_base,
auxadc_phys_base, ctrl_id);
- platform_set_drvdata(pdev, mt);
-
tzdev = devm_thermal_of_zone_register(&pdev->dev, 0, mt,
&mtk_thermal_ops);
if (IS_ERR(tzdev))
diff --git a/drivers/thermal/mediatek/lvts_thermal.c b/drivers/thermal/mediatek/lvts_thermal.c
index b693fac2d677..ab55b20cda47 100644
--- a/drivers/thermal/mediatek/lvts_thermal.c
+++ b/drivers/thermal/mediatek/lvts_thermal.c
@@ -13,7 +13,7 @@
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/nvmem-consumer.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/thermal.h>
@@ -58,14 +58,22 @@
#define LVTS_PROTTC(__base) (__base + 0x00CC)
#define LVTS_CLKEN(__base) (__base + 0x00E4)
-#define LVTS_PERIOD_UNIT ((118 * 1000) / (256 * 38))
-#define LVTS_GROUP_INTERVAL 1
-#define LVTS_FILTER_INTERVAL 1
-#define LVTS_SENSOR_INTERVAL 1
-#define LVTS_HW_FILTER 0x2
+#define LVTS_PERIOD_UNIT 0
+#define LVTS_GROUP_INTERVAL 0
+#define LVTS_FILTER_INTERVAL 0
+#define LVTS_SENSOR_INTERVAL 0
+#define LVTS_HW_FILTER 0x0
#define LVTS_TSSEL_CONF 0x13121110
#define LVTS_CALSCALE_CONF 0x300
-#define LVTS_MONINT_CONF 0x9FBF7BDE
+
+#define LVTS_MONINT_OFFSET_HIGH_INTEN_SENSOR0 BIT(3)
+#define LVTS_MONINT_OFFSET_HIGH_INTEN_SENSOR1 BIT(8)
+#define LVTS_MONINT_OFFSET_HIGH_INTEN_SENSOR2 BIT(13)
+#define LVTS_MONINT_OFFSET_HIGH_INTEN_SENSOR3 BIT(25)
+#define LVTS_MONINT_OFFSET_LOW_INTEN_SENSOR0 BIT(2)
+#define LVTS_MONINT_OFFSET_LOW_INTEN_SENSOR1 BIT(7)
+#define LVTS_MONINT_OFFSET_LOW_INTEN_SENSOR2 BIT(12)
+#define LVTS_MONINT_OFFSET_LOW_INTEN_SENSOR3 BIT(24)
#define LVTS_INT_SENSOR0 0x0009001F
#define LVTS_INT_SENSOR1 0x001203E0
@@ -75,33 +83,57 @@
#define LVTS_SENSOR_MAX 4
#define LVTS_GOLDEN_TEMP_MAX 62
#define LVTS_GOLDEN_TEMP_DEFAULT 50
-#define LVTS_COEFF_A -250460
-#define LVTS_COEFF_B 250460
+#define LVTS_COEFF_A_MT8195 -250460
+#define LVTS_COEFF_B_MT8195 250460
+#define LVTS_COEFF_A_MT7988 -204650
+#define LVTS_COEFF_B_MT7988 204650
#define LVTS_MSR_IMMEDIATE_MODE 0
#define LVTS_MSR_FILTERED_MODE 1
-#define LVTS_HW_SHUTDOWN_MT8195 105000
+#define LVTS_MSR_READ_TIMEOUT_US 400
+#define LVTS_MSR_READ_WAIT_US (LVTS_MSR_READ_TIMEOUT_US / 2)
+
+#define LVTS_MINIMUM_THRESHOLD 20000
static int golden_temp = LVTS_GOLDEN_TEMP_DEFAULT;
-static int coeff_b = LVTS_COEFF_B;
+static int golden_temp_offset;
struct lvts_sensor_data {
int dt_id;
+ u8 cal_offsets[3];
};
struct lvts_ctrl_data {
struct lvts_sensor_data lvts_sensor[LVTS_SENSOR_MAX];
- int cal_offset[LVTS_SENSOR_MAX];
- int hw_tshut_temp;
- int num_lvts_sensor;
+ u8 valid_sensor_mask;
int offset;
int mode;
};
+#define VALID_SENSOR_MAP(s0, s1, s2, s3) \
+ .valid_sensor_mask = (((s0) ? BIT(0) : 0) | \
+ ((s1) ? BIT(1) : 0) | \
+ ((s2) ? BIT(2) : 0) | \
+ ((s3) ? BIT(3) : 0))
+
+#define lvts_for_each_valid_sensor(i, lvts_ctrl) \
+ for ((i) = 0; (i) < LVTS_SENSOR_MAX; (i)++) \
+ if (!((lvts_ctrl)->valid_sensor_mask & BIT(i))) \
+ continue; \
+ else
+
struct lvts_data {
const struct lvts_ctrl_data *lvts_ctrl;
+ const u32 *conn_cmd;
+ const u32 *init_cmd;
int num_lvts_ctrl;
+ int num_conn_cmd;
+ int num_init_cmd;
+ int temp_factor;
+ int temp_offset;
+ int gt_calib_bit_offset;
+ unsigned int def_calibration;
};
struct lvts_sensor {
@@ -110,15 +142,19 @@ struct lvts_sensor {
void __iomem *base;
int id;
int dt_id;
+ int low_thresh;
+ int high_thresh;
};
struct lvts_ctrl {
struct lvts_sensor sensors[LVTS_SENSOR_MAX];
+ const struct lvts_data *lvts_data;
u32 calibration[LVTS_SENSOR_MAX];
- u32 hw_tshut_raw_temp;
- int num_lvts_sensor;
+ u8 valid_sensor_mask;
int mode;
void __iomem *base;
+ int low_thresh;
+ int high_thresh;
};
struct lvts_domain {
@@ -181,6 +217,13 @@ static const struct debugfs_reg32 lvts_regs[] = {
LVTS_DEBUG_FS_REGS(LVTS_CLKEN),
};
+static void lvts_debugfs_exit(void *data)
+{
+ struct lvts_domain *lvts_td = data;
+
+ debugfs_remove_recursive(lvts_td->dom_dentry);
+}
+
static int lvts_debugfs_init(struct device *dev, struct lvts_domain *lvts_td)
{
struct debugfs_regset32 *regset;
@@ -190,7 +233,7 @@ static int lvts_debugfs_init(struct device *dev, struct lvts_domain *lvts_td)
int i;
lvts_td->dom_dentry = debugfs_create_dir(dev_name(dev), NULL);
- if (!lvts_td->dom_dentry)
+ if (IS_ERR(lvts_td->dom_dentry))
return 0;
for (i = 0; i < lvts_td->num_lvts_ctrl; i++) {
@@ -199,7 +242,7 @@ static int lvts_debugfs_init(struct device *dev, struct lvts_domain *lvts_td)
sprintf(name, "controller%d", i);
dentry = debugfs_create_dir(name, lvts_td->dom_dentry);
- if (!dentry)
+ if (IS_ERR(dentry))
continue;
regset = devm_kzalloc(dev, sizeof(*regset), GFP_KERNEL);
@@ -213,12 +256,7 @@ static int lvts_debugfs_init(struct device *dev, struct lvts_domain *lvts_td)
debugfs_create_regset32("registers", 0400, dentry, regset);
}
- return 0;
-}
-
-static void lvts_debugfs_exit(struct lvts_domain *lvts_td)
-{
- debugfs_remove_recursive(lvts_td->dom_dentry);
+ return devm_add_action_or_reset(dev, lvts_debugfs_exit, lvts_td);
}
#else
@@ -229,25 +267,23 @@ static inline int lvts_debugfs_init(struct device *dev,
return 0;
}
-static void lvts_debugfs_exit(struct lvts_domain *lvts_td) { }
-
#endif
-static int lvts_raw_to_temp(u32 raw_temp)
+static int lvts_raw_to_temp(u32 raw_temp, int temp_factor)
{
int temperature;
- temperature = ((s64)(raw_temp & 0xFFFF) * LVTS_COEFF_A) >> 14;
- temperature += coeff_b;
+ temperature = ((s64)(raw_temp & 0xFFFF) * temp_factor) >> 14;
+ temperature += golden_temp_offset;
return temperature;
}
-static u32 lvts_temp_to_raw(int temperature)
+static u32 lvts_temp_to_raw(int temperature, int temp_factor)
{
- u32 raw_temp = ((s64)(coeff_b - temperature)) << 14;
+ u32 raw_temp = ((s64)(golden_temp_offset - temperature)) << 14;
- raw_temp = div_s64(raw_temp, -LVTS_COEFF_A);
+ raw_temp = div_s64(raw_temp, -temp_factor);
return raw_temp;
}
@@ -255,8 +291,12 @@ static u32 lvts_temp_to_raw(int temperature)
static int lvts_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct lvts_sensor *lvts_sensor = thermal_zone_device_priv(tz);
+ struct lvts_ctrl *lvts_ctrl = container_of(lvts_sensor, struct lvts_ctrl,
+ sensors[lvts_sensor->id]);
+ const struct lvts_data *lvts_data = lvts_ctrl->lvts_data;
void __iomem *msr = lvts_sensor->msr;
u32 value;
+ int rc;
/*
* Measurement registers:
@@ -269,7 +309,8 @@ static int lvts_get_temp(struct thermal_zone_device *tz, int *temp)
* 16 : Valid temperature
* 15-0 : Raw temperature
*/
- value = readl(msr);
+ rc = readl_poll_timeout(msr, value, value & BIT(16),
+ LVTS_MSR_READ_WAIT_US, LVTS_MSR_READ_TIMEOUT_US);
/*
* As the thermal zone temperature will read before the
@@ -282,40 +323,113 @@ static int lvts_get_temp(struct thermal_zone_device *tz, int *temp)
* functionning temperature and directly jump to a system
* shutdown.
*/
- if (!(value & BIT(16)))
+ if (rc)
return -EAGAIN;
- *temp = lvts_raw_to_temp(value & 0xFFFF);
+ *temp = lvts_raw_to_temp(value & 0xFFFF, lvts_data->temp_factor);
return 0;
}
+static void lvts_update_irq_mask(struct lvts_ctrl *lvts_ctrl)
+{
+ static const u32 high_offset_inten_masks[] = {
+ LVTS_MONINT_OFFSET_HIGH_INTEN_SENSOR0,
+ LVTS_MONINT_OFFSET_HIGH_INTEN_SENSOR1,
+ LVTS_MONINT_OFFSET_HIGH_INTEN_SENSOR2,
+ LVTS_MONINT_OFFSET_HIGH_INTEN_SENSOR3,
+ };
+ static const u32 low_offset_inten_masks[] = {
+ LVTS_MONINT_OFFSET_LOW_INTEN_SENSOR0,
+ LVTS_MONINT_OFFSET_LOW_INTEN_SENSOR1,
+ LVTS_MONINT_OFFSET_LOW_INTEN_SENSOR2,
+ LVTS_MONINT_OFFSET_LOW_INTEN_SENSOR3,
+ };
+ u32 value = 0;
+ int i;
+
+ value = readl(LVTS_MONINT(lvts_ctrl->base));
+
+ lvts_for_each_valid_sensor(i, lvts_ctrl) {
+ if (lvts_ctrl->sensors[i].high_thresh == lvts_ctrl->high_thresh
+ && lvts_ctrl->sensors[i].low_thresh == lvts_ctrl->low_thresh) {
+ /*
+ * The minimum threshold needs to be configured in the
+ * OFFSETL register to get working interrupts, but we
+ * don't actually want to generate interrupts when
+ * crossing it.
+ */
+ if (lvts_ctrl->low_thresh == -INT_MAX) {
+ value &= ~low_offset_inten_masks[i];
+ value |= high_offset_inten_masks[i];
+ } else {
+ value |= low_offset_inten_masks[i] | high_offset_inten_masks[i];
+ }
+ } else {
+ value &= ~(low_offset_inten_masks[i] | high_offset_inten_masks[i]);
+ }
+ }
+
+ writel(value, LVTS_MONINT(lvts_ctrl->base));
+}
+
+static bool lvts_should_update_thresh(struct lvts_ctrl *lvts_ctrl, int high)
+{
+ int i;
+
+ if (high > lvts_ctrl->high_thresh)
+ return true;
+
+ lvts_for_each_valid_sensor(i, lvts_ctrl)
+ if (lvts_ctrl->sensors[i].high_thresh == lvts_ctrl->high_thresh
+ && lvts_ctrl->sensors[i].low_thresh == lvts_ctrl->low_thresh)
+ return false;
+
+ return true;
+}
+
static int lvts_set_trips(struct thermal_zone_device *tz, int low, int high)
{
struct lvts_sensor *lvts_sensor = thermal_zone_device_priv(tz);
+ struct lvts_ctrl *lvts_ctrl = container_of(lvts_sensor, struct lvts_ctrl,
+ sensors[lvts_sensor->id]);
+ const struct lvts_data *lvts_data = lvts_ctrl->lvts_data;
void __iomem *base = lvts_sensor->base;
- u32 raw_low = lvts_temp_to_raw(low);
- u32 raw_high = lvts_temp_to_raw(high);
+ u32 raw_low = lvts_temp_to_raw(low != -INT_MAX ? low : LVTS_MINIMUM_THRESHOLD,
+ lvts_data->temp_factor);
+ u32 raw_high = lvts_temp_to_raw(high, lvts_data->temp_factor);
+ bool should_update_thresh;
+
+ lvts_sensor->low_thresh = low;
+ lvts_sensor->high_thresh = high;
+
+ should_update_thresh = lvts_should_update_thresh(lvts_ctrl, high);
+ if (should_update_thresh) {
+ lvts_ctrl->high_thresh = high;
+ lvts_ctrl->low_thresh = low;
+ }
+ lvts_update_irq_mask(lvts_ctrl);
+
+ if (!should_update_thresh)
+ return 0;
/*
- * Hot to normal temperature threshold
+ * Low offset temperature threshold
*
- * LVTS_H2NTHRE
+ * LVTS_OFFSETL
*
* Bits:
*
* 14-0 : Raw temperature for threshold
*/
- if (low != -INT_MAX) {
- pr_debug("%s: Setting low limit temperature interrupt: %d\n",
- thermal_zone_device_type(tz), low);
- writel(raw_low, LVTS_H2NTHRE(base));
- }
+ pr_debug("%s: Setting low limit temperature interrupt: %d\n",
+ thermal_zone_device_type(tz), low);
+ writel(raw_low, LVTS_OFFSETL(base));
/*
- * Hot temperature threshold
+ * High offset temperature threshold
*
- * LVTS_HTHRE
+ * LVTS_OFFSETH
*
* Bits:
*
@@ -323,7 +437,7 @@ static int lvts_set_trips(struct thermal_zone_device *tz, int low, int high)
*/
pr_debug("%s: Setting high limit temperature interrupt: %d\n",
thermal_zone_device_type(tz), high);
- writel(raw_high, LVTS_HTHRE(base));
+ writel(raw_high, LVTS_OFFSETH(base));
return 0;
}
@@ -332,7 +446,7 @@ static irqreturn_t lvts_ctrl_irq_handler(struct lvts_ctrl *lvts_ctrl)
{
irqreturn_t iret = IRQ_NONE;
u32 value;
- u32 masks[] = {
+ static const u32 masks[] = {
LVTS_INT_SENSOR0,
LVTS_INT_SENSOR1,
LVTS_INT_SENSOR2,
@@ -451,7 +565,7 @@ static irqreturn_t lvts_irq_handler(int irq, void *data)
for (i = 0; i < lvts_td->num_lvts_ctrl; i++) {
- aux = lvts_ctrl_irq_handler(lvts_td->lvts_ctrl);
+ aux = lvts_ctrl_irq_handler(&lvts_td->lvts_ctrl[i]);
if (aux != IRQ_HANDLED)
continue;
@@ -461,7 +575,7 @@ static irqreturn_t lvts_irq_handler(int irq, void *data)
return iret;
}
-static struct thermal_zone_device_ops lvts_ops = {
+static const struct thermal_zone_device_ops lvts_ops = {
.get_temp = lvts_get_temp,
.set_trips = lvts_set_trips,
};
@@ -470,6 +584,7 @@ static int lvts_sensor_init(struct device *dev, struct lvts_ctrl *lvts_ctrl,
const struct lvts_ctrl_data *lvts_ctrl_data)
{
struct lvts_sensor *lvts_sensor = lvts_ctrl->sensors;
+
void __iomem *msr_regs[] = {
LVTS_MSR0(lvts_ctrl->base),
LVTS_MSR1(lvts_ctrl->base),
@@ -486,7 +601,7 @@ static int lvts_sensor_init(struct device *dev, struct lvts_ctrl *lvts_ctrl,
int i;
- for (i = 0; i < lvts_ctrl_data->num_lvts_sensor; i++) {
+ lvts_for_each_valid_sensor(i, lvts_ctrl_data) {
int dt_id = lvts_ctrl_data->lvts_sensor[i].dt_id;
@@ -521,9 +636,12 @@ static int lvts_sensor_init(struct device *dev, struct lvts_ctrl *lvts_ctrl,
*/
lvts_sensor[i].msr = lvts_ctrl_data->mode == LVTS_MSR_IMMEDIATE_MODE ?
imm_regs[i] : msr_regs[i];
- };
- lvts_ctrl->num_lvts_sensor = lvts_ctrl_data->num_lvts_sensor;
+ lvts_sensor[i].low_thresh = INT_MIN;
+ lvts_sensor[i].high_thresh = INT_MIN;
+ }
+
+ lvts_ctrl->valid_sensor_mask = lvts_ctrl_data->valid_sensor_mask;
return 0;
}
@@ -532,7 +650,34 @@ static int lvts_sensor_init(struct device *dev, struct lvts_ctrl *lvts_ctrl,
* The efuse blob values follows the sensor enumeration per thermal
* controller. The decoding of the stream is as follow:
*
- * stream index map for MCU Domain :
+ * MT8192 :
+ * Stream index map for MCU Domain mt8192 :
+ *
+ * <-----mcu-tc#0-----> <-----sensor#0-----> <-----sensor#1----->
+ * 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 | 0x08 | 0x09 | 0x0A | 0x0B
+ *
+ * <-----sensor#2-----> <-----sensor#3----->
+ * 0x0C | 0x0D | 0x0E | 0x0F | 0x10 | 0x11 | 0x12 | 0x13
+ *
+ * <-----sensor#4-----> <-----sensor#5-----> <-----sensor#6-----> <-----sensor#7----->
+ * 0x14 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 | 0x1A | 0x1B | 0x1C | 0x1D | 0x1E | 0x1F | 0x20 | 0x21 | 0x22 | 0x23
+ *
+ * Stream index map for AP Domain mt8192 :
+ *
+ * <-----sensor#0-----> <-----sensor#1----->
+ * 0x24 | 0x25 | 0x26 | 0x27 | 0x28 | 0x29 | 0x2A | 0x2B
+ *
+ * <-----sensor#2-----> <-----sensor#3----->
+ * 0x2C | 0x2D | 0x2E | 0x2F | 0x30 | 0x31 | 0x32 | 0x33
+ *
+ * <-----sensor#4-----> <-----sensor#5----->
+ * 0x34 | 0x35 | 0x36 | 0x37 | 0x38 | 0x39 | 0x3A | 0x3B
+ *
+ * <-----sensor#6-----> <-----sensor#7-----> <-----sensor#8----->
+ * 0x3C | 0x3D | 0x3E | 0x3F | 0x40 | 0x41 | 0x42 | 0x43 | 0x44 | 0x45 | 0x46 | 0x47
+ *
+ * MT8195 :
+ * Stream index map for MCU Domain mt8195 :
*
* <-----mcu-tc#0-----> <-----sensor#0-----> <-----sensor#1----->
* 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 | 0x08 | 0x09
@@ -543,7 +688,7 @@ static int lvts_sensor_init(struct device *dev, struct lvts_ctrl *lvts_ctrl,
* <-----mcu-tc#2-----> <-----sensor#4-----> <-----sensor#5-----> <-----sensor#6-----> <-----sensor#7----->
* 0x13 | 0x14 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 | 0x1A | 0x1B | 0x1C | 0x1D | 0x1E | 0x1F | 0x20 | 0x21
*
- * stream index map for AP Domain :
+ * Stream index map for AP Domain mt8195 :
*
* <-----ap--tc#0-----> <-----sensor#0-----> <-----sensor#1----->
* 0x22 | 0x23 | 0x24 | 0x25 | 0x26 | 0x27 | 0x28 | 0x29 | 0x2A
@@ -557,18 +702,42 @@ static int lvts_sensor_init(struct device *dev, struct lvts_ctrl *lvts_ctrl,
* <-----ap--tc#3-----> <-----sensor#7-----> <-----sensor#8----->
* 0x40 | 0x41 | 0x42 | 0x43 | 0x44 | 0x45 | 0x46 | 0x47 | 0x48
*
- * The data description gives the offset of the calibration data in
- * this bytes stream for each sensor.
+ * Note: In some cases, values don't strictly follow a little endian ordering.
+ * The data description gives byte offsets constituting each calibration value
+ * for each sensor.
*/
static int lvts_calibration_init(struct device *dev, struct lvts_ctrl *lvts_ctrl,
const struct lvts_ctrl_data *lvts_ctrl_data,
- u8 *efuse_calibration)
+ u8 *efuse_calibration,
+ size_t calib_len)
{
int i;
+ u32 gt;
- for (i = 0; i < lvts_ctrl_data->num_lvts_sensor; i++)
- memcpy(&lvts_ctrl->calibration[i],
- efuse_calibration + lvts_ctrl_data->cal_offset[i], 2);
+ /* A zero value for gt means that device has invalid efuse data */
+ gt = (((u32 *)efuse_calibration)[0] >> lvts_ctrl->lvts_data->gt_calib_bit_offset) & 0xff;
+
+ lvts_for_each_valid_sensor(i, lvts_ctrl_data) {
+ const struct lvts_sensor_data *sensor =
+ &lvts_ctrl_data->lvts_sensor[i];
+
+ if (sensor->cal_offsets[0] >= calib_len ||
+ sensor->cal_offsets[1] >= calib_len ||
+ sensor->cal_offsets[2] >= calib_len)
+ return -EINVAL;
+
+ if (gt) {
+ lvts_ctrl->calibration[i] =
+ (efuse_calibration[sensor->cal_offsets[0]] << 0) +
+ (efuse_calibration[sensor->cal_offsets[1]] << 8) +
+ (efuse_calibration[sensor->cal_offsets[2]] << 16);
+ } else if (lvts_ctrl->lvts_data->def_calibration) {
+ lvts_ctrl->calibration[i] = lvts_ctrl->lvts_data->def_calibration;
+ } else {
+ dev_err(dev, "efuse contains invalid calibration data and no default given.\n");
+ return -ENODATA;
+ }
+ }
return 0;
}
@@ -608,8 +777,10 @@ static int lvts_calibration_read(struct device *dev, struct lvts_domain *lvts_td
lvts_td->calib = devm_krealloc(dev, lvts_td->calib,
lvts_td->calib_len + len, GFP_KERNEL);
- if (!lvts_td->calib)
+ if (!lvts_td->calib) {
+ kfree(efuse);
return -ENOMEM;
+ }
memcpy(lvts_td->calib + lvts_td->calib_len, efuse, len);
@@ -621,16 +792,24 @@ static int lvts_calibration_read(struct device *dev, struct lvts_domain *lvts_td
return 0;
}
-static int lvts_golden_temp_init(struct device *dev, u32 *value)
+static int lvts_golden_temp_init(struct device *dev, u8 *calib,
+ const struct lvts_data *lvts_data)
{
u32 gt;
- gt = (*value) >> 24;
+ /*
+ * The golden temp information is contained in the first 32-bit
+ * word of efuse data at a specific bit offset.
+ */
+ gt = (((u32 *)calib)[0] >> lvts_data->gt_calib_bit_offset) & 0xff;
+ /* A zero value for gt means that device has invalid efuse data */
if (gt && gt < LVTS_GOLDEN_TEMP_MAX)
golden_temp = gt;
- coeff_b = golden_temp * 500 + LVTS_COEFF_B;
+ golden_temp_offset = golden_temp * 500 + lvts_data->temp_offset;
+
+ dev_info(dev, "%sgolden temp=%d\n", gt ? "" : "fake ", golden_temp);
return 0;
}
@@ -649,11 +828,7 @@ static int lvts_ctrl_init(struct device *dev, struct lvts_domain *lvts_td,
if (ret)
return ret;
- /*
- * The golden temp information is contained in the first chunk
- * of efuse data.
- */
- ret = lvts_golden_temp_init(dev, (u32 *)lvts_td->calib);
+ ret = lvts_golden_temp_init(dev, lvts_td->calib, lvts_data);
if (ret)
return ret;
@@ -664,6 +839,7 @@ static int lvts_ctrl_init(struct device *dev, struct lvts_domain *lvts_td,
for (i = 0; i < lvts_data->num_lvts_ctrl; i++) {
lvts_ctrl[i].base = lvts_td->base + lvts_data->lvts_ctrl[i].offset;
+ lvts_ctrl[i].lvts_data = lvts_data;
ret = lvts_sensor_init(dev, &lvts_ctrl[i],
&lvts_data->lvts_ctrl[i]);
@@ -672,7 +848,8 @@ static int lvts_ctrl_init(struct device *dev, struct lvts_domain *lvts_td,
ret = lvts_calibration_init(dev, &lvts_ctrl[i],
&lvts_data->lvts_ctrl[i],
- lvts_td->calib);
+ lvts_td->calib,
+ lvts_td->calib_len);
if (ret)
return ret;
@@ -682,12 +859,8 @@ static int lvts_ctrl_init(struct device *dev, struct lvts_domain *lvts_td,
*/
lvts_ctrl[i].mode = lvts_data->lvts_ctrl[i].mode;
- /*
- * The temperature to raw temperature must be done
- * after initializing the calibration.
- */
- lvts_ctrl[i].hw_tshut_raw_temp =
- lvts_temp_to_raw(lvts_data->lvts_ctrl[i].hw_tshut_temp);
+ lvts_ctrl[i].low_thresh = INT_MIN;
+ lvts_ctrl[i].high_thresh = INT_MIN;
}
/*
@@ -701,13 +874,39 @@ static int lvts_ctrl_init(struct device *dev, struct lvts_domain *lvts_td,
return 0;
}
+static void lvts_ctrl_monitor_enable(struct device *dev, struct lvts_ctrl *lvts_ctrl, bool enable)
+{
+ /*
+ * Bitmaps to enable each sensor on filtered mode in the MONCTL0
+ * register.
+ */
+ static const u8 sensor_filt_bitmap[] = { BIT(0), BIT(1), BIT(2), BIT(3) };
+ u32 sensor_map = 0;
+ int i;
+
+ if (lvts_ctrl->mode != LVTS_MSR_FILTERED_MODE)
+ return;
+
+ if (enable) {
+ lvts_for_each_valid_sensor(i, lvts_ctrl)
+ sensor_map |= sensor_filt_bitmap[i];
+ }
+
+ /*
+ * Bits:
+ * 9: Single point access flow
+ * 0-3: Enable sensing point 0-3
+ */
+ writel(sensor_map | BIT(9), LVTS_MONCTL0(lvts_ctrl->base));
+}
+
/*
* At this point the configuration register is the only place in the
* driver where we write multiple values. Per hardware constraint,
* each write in the configuration register must be separated by a
* delay of 2 us.
*/
-static void lvts_write_config(struct lvts_ctrl *lvts_ctrl, u32 *cmds, int nr_cmds)
+static void lvts_write_config(struct lvts_ctrl *lvts_ctrl, const u32 *cmds, int nr_cmds)
{
int i;
@@ -734,7 +933,6 @@ static int lvts_irq_init(struct lvts_ctrl *lvts_ctrl)
* 10 : Selected sensor with bits 19-18
* 11 : Reserved
*/
- writel(BIT(16), LVTS_PROTCTL(lvts_ctrl->base));
/*
* LVTS_PROTTA : Stage 1 temperature threshold
@@ -747,8 +945,8 @@ static int lvts_irq_init(struct lvts_ctrl *lvts_ctrl)
*
* writel(0x0, LVTS_PROTTA(lvts_ctrl->base));
* writel(0x0, LVTS_PROTTB(lvts_ctrl->base));
+ * writel(0x0, LVTS_PROTTC(lvts_ctrl->base));
*/
- writel(lvts_ctrl->hw_tshut_raw_temp, LVTS_PROTTC(lvts_ctrl->base));
/*
* LVTS_MONINT : Interrupt configuration register
@@ -756,7 +954,7 @@ static int lvts_irq_init(struct lvts_ctrl *lvts_ctrl)
* The LVTS_MONINT register layout is the same as the LVTS_MONINTSTS
* register, except we set the bits to enable the interrupt.
*/
- writel(LVTS_MONINT_CONF, LVTS_MONINT(lvts_ctrl->base));
+ writel(0, LVTS_MONINT(lvts_ctrl->base));
return 0;
}
@@ -791,9 +989,10 @@ static int lvts_ctrl_set_enable(struct lvts_ctrl *lvts_ctrl, int enable)
static int lvts_ctrl_connect(struct device *dev, struct lvts_ctrl *lvts_ctrl)
{
- u32 id, cmds[] = { 0xC103FFFF, 0xC502FF55 };
+ const struct lvts_data *lvts_data = lvts_ctrl->lvts_data;
+ u32 id;
- lvts_write_config(lvts_ctrl, cmds, ARRAY_SIZE(cmds));
+ lvts_write_config(lvts_ctrl, lvts_data->conn_cmd, lvts_data->num_conn_cmd);
/*
* LVTS_ID : Get ID and status of the thermal controller
@@ -812,17 +1011,9 @@ static int lvts_ctrl_connect(struct device *dev, struct lvts_ctrl *lvts_ctrl)
static int lvts_ctrl_initialize(struct device *dev, struct lvts_ctrl *lvts_ctrl)
{
- /*
- * Write device mask: 0xC1030000
- */
- u32 cmds[] = {
- 0xC1030E01, 0xC1030CFC, 0xC1030A8C, 0xC103098D, 0xC10308F1,
- 0xC10307A6, 0xC10306B8, 0xC1030500, 0xC1030420, 0xC1030300,
- 0xC1030030, 0xC10300F6, 0xC1030050, 0xC1030060, 0xC10300AC,
- 0xC10300FC, 0xC103009D, 0xC10300F1, 0xC10300E1
- };
+ const struct lvts_data *lvts_data = lvts_ctrl->lvts_data;
- lvts_write_config(lvts_ctrl, cmds, ARRAY_SIZE(cmds));
+ lvts_write_config(lvts_ctrl, lvts_data->init_cmd, lvts_data->num_init_cmd);
return 0;
}
@@ -897,24 +1088,6 @@ static int lvts_ctrl_configure(struct device *dev, struct lvts_ctrl *lvts_ctrl)
writel(value, LVTS_MSRCTL0(lvts_ctrl->base));
/*
- * LVTS_MSRCTL1 : Measurement control
- *
- * Bits:
- *
- * 9: Ignore MSRCTL0 config and do immediate measurement on sensor3
- * 6: Ignore MSRCTL0 config and do immediate measurement on sensor2
- * 5: Ignore MSRCTL0 config and do immediate measurement on sensor1
- * 4: Ignore MSRCTL0 config and do immediate measurement on sensor0
- *
- * That configuration will ignore the filtering and the delays
- * introduced below in MONCTL1 and MONCTL2
- */
- if (lvts_ctrl->mode == LVTS_MSR_IMMEDIATE_MODE) {
- value = BIT(9) | BIT(6) | BIT(5) | BIT(4);
- writel(value, LVTS_MSRCTL1(lvts_ctrl->base));
- }
-
- /*
* LVTS_MONCTL1 : Period unit and group interval configuration
*
* The clock source of LVTS thermal controller is 26MHz.
@@ -979,8 +1152,17 @@ static int lvts_ctrl_start(struct device *dev, struct lvts_ctrl *lvts_ctrl)
struct thermal_zone_device *tz;
u32 sensor_map = 0;
int i;
+ /*
+ * Bitmaps to enable each sensor on immediate and filtered modes, as
+ * described in MSRCTL1 and MONCTL0 registers below, respectively.
+ */
+ u32 sensor_imm_bitmap[] = { BIT(4), BIT(5), BIT(6), BIT(9) };
+ u32 sensor_filt_bitmap[] = { BIT(0), BIT(1), BIT(2), BIT(3) };
+
+ u32 *sensor_bitmap = lvts_ctrl->mode == LVTS_MSR_IMMEDIATE_MODE ?
+ sensor_imm_bitmap : sensor_filt_bitmap;
- for (i = 0; i < lvts_ctrl->num_lvts_sensor; i++) {
+ lvts_for_each_valid_sensor(i, lvts_ctrl) {
int dt_id = lvts_sensors[i].dt_id;
@@ -1016,20 +1198,38 @@ static int lvts_ctrl_start(struct device *dev, struct lvts_ctrl *lvts_ctrl)
* map, so we can enable the temperature monitoring in
* the hardware thermal controller.
*/
- sensor_map |= BIT(i);
+ sensor_map |= sensor_bitmap[i];
}
/*
- * Bits:
- * 9: Single point access flow
- * 0-3: Enable sensing point 0-3
- *
* The initialization of the thermal zones give us
* which sensor point to enable. If any thermal zone
* was not described in the device tree, it won't be
* enabled here in the sensor map.
*/
- writel(sensor_map | BIT(9), LVTS_MONCTL0(lvts_ctrl->base));
+ if (lvts_ctrl->mode == LVTS_MSR_IMMEDIATE_MODE) {
+ /*
+ * LVTS_MSRCTL1 : Measurement control
+ *
+ * Bits:
+ *
+ * 9: Ignore MSRCTL0 config and do immediate measurement on sensor3
+ * 6: Ignore MSRCTL0 config and do immediate measurement on sensor2
+ * 5: Ignore MSRCTL0 config and do immediate measurement on sensor1
+ * 4: Ignore MSRCTL0 config and do immediate measurement on sensor0
+ *
+ * That configuration will ignore the filtering and the delays
+ * introduced in MONCTL1 and MONCTL2
+ */
+ writel(sensor_map, LVTS_MSRCTL1(lvts_ctrl->base));
+ } else {
+ /*
+ * Bits:
+ * 9: Single point access flow
+ * 0-3: Enable sensing point 0-3
+ */
+ writel(sensor_map | BIT(9), LVTS_MONCTL0(lvts_ctrl->base));
+ }
return 0;
}
@@ -1119,6 +1319,8 @@ static int lvts_probe(struct platform_device *pdev)
return -ENOMEM;
lvts_data = of_device_get_match_data(dev);
+ if (!lvts_data)
+ return -ENODEV;
lvts_td->clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(lvts_td->clk))
@@ -1138,7 +1340,9 @@ static int lvts_probe(struct platform_device *pdev)
irq = platform_get_irq(pdev, 0);
if (irq < 0)
- return dev_err_probe(dev, irq, "No irq resource\n");
+ return irq;
+
+ golden_temp_offset = lvts_data->temp_offset;
ret = lvts_domain_init(dev, lvts_td, lvts_data);
if (ret)
@@ -1158,7 +1362,7 @@ static int lvts_probe(struct platform_device *pdev)
return 0;
}
-static int lvts_remove(struct platform_device *pdev)
+static void lvts_remove(struct platform_device *pdev)
{
struct lvts_domain *lvts_td;
int i;
@@ -1167,114 +1371,515 @@ static int lvts_remove(struct platform_device *pdev)
for (i = 0; i < lvts_td->num_lvts_ctrl; i++)
lvts_ctrl_set_enable(&lvts_td->lvts_ctrl[i], false);
+}
+
+static const struct lvts_ctrl_data mt7988_lvts_ap_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT7988_CPU_0,
+ .cal_offsets = { 0x00, 0x01, 0x02 } },
+ { .dt_id = MT7988_CPU_1,
+ .cal_offsets = { 0x04, 0x05, 0x06 } },
+ { .dt_id = MT7988_ETH2P5G_0,
+ .cal_offsets = { 0x08, 0x09, 0x0a } },
+ { .dt_id = MT7988_ETH2P5G_1,
+ .cal_offsets = { 0x0c, 0x0d, 0x0e } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x0,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT7988_TOPS_0,
+ .cal_offsets = { 0x14, 0x15, 0x16 } },
+ { .dt_id = MT7988_TOPS_1,
+ .cal_offsets = { 0x18, 0x19, 0x1a } },
+ { .dt_id = MT7988_ETHWARP_0,
+ .cal_offsets = { 0x1c, 0x1d, 0x1e } },
+ { .dt_id = MT7988_ETHWARP_1,
+ .cal_offsets = { 0x20, 0x21, 0x22 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x100,
+ }
+};
+
+static int lvts_suspend(struct device *dev)
+{
+ struct lvts_domain *lvts_td;
+ int i;
+
+ lvts_td = dev_get_drvdata(dev);
+
+ for (i = 0; i < lvts_td->num_lvts_ctrl; i++) {
+ lvts_ctrl_monitor_enable(dev, &lvts_td->lvts_ctrl[i], false);
+ usleep_range(100, 200);
+ lvts_ctrl_set_enable(&lvts_td->lvts_ctrl[i], false);
+ }
- lvts_debugfs_exit(lvts_td);
+ clk_disable_unprepare(lvts_td->clk);
return 0;
}
+static int lvts_resume(struct device *dev)
+{
+ struct lvts_domain *lvts_td;
+ int i, ret;
+
+ lvts_td = dev_get_drvdata(dev);
+
+ ret = clk_prepare_enable(lvts_td->clk);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < lvts_td->num_lvts_ctrl; i++) {
+ lvts_ctrl_set_enable(&lvts_td->lvts_ctrl[i], true);
+ usleep_range(100, 200);
+ lvts_ctrl_monitor_enable(dev, &lvts_td->lvts_ctrl[i], true);
+ }
+
+ return 0;
+}
+
+static const u32 default_conn_cmds[] = { 0xC103FFFF, 0xC502FF55 };
+static const u32 mt7988_conn_cmds[] = { 0xC103FFFF, 0xC502FC55 };
+
+/*
+ * Write device mask: 0xC1030000
+ */
+static const u32 default_init_cmds[] = {
+ 0xC1030E01, 0xC1030CFC, 0xC1030A8C, 0xC103098D, 0xC10308F1,
+ 0xC10307A6, 0xC10306B8, 0xC1030500, 0xC1030420, 0xC1030300,
+ 0xC1030030, 0xC10300F6, 0xC1030050, 0xC1030060, 0xC10300AC,
+ 0xC10300FC, 0xC103009D, 0xC10300F1, 0xC10300E1
+};
+
+static const u32 mt7988_init_cmds[] = {
+ 0xC1030300, 0xC1030420, 0xC1030500, 0xC10307A6, 0xC1030CFC,
+ 0xC1030A8C, 0xC103098D, 0xC10308F1, 0xC1030B04, 0xC1030E01,
+ 0xC10306B8
+};
+
+/*
+ * The MT8186 calibration data is stored as packed 3-byte little-endian
+ * values using a weird layout that makes sense only when viewed as a 32-bit
+ * hexadecimal word dump. Let's suppose SxBy where x = sensor number and
+ * y = byte number where the LSB is y=0. We then have:
+ *
+ * [S0B2-S0B1-S0B0-S1B2] [S1B1-S1B0-S2B2-S2B1] [S2B0-S3B2-S3B1-S3B0]
+ *
+ * However, when considering a byte stream, those appear as follows:
+ *
+ * [S1B2] [S0B0[ [S0B1] [S0B2] [S2B1] [S2B2] [S1B0] [S1B1] [S3B0] [S3B1] [S3B2] [S2B0]
+ *
+ * Hence the rather confusing offsets provided below.
+ */
+static const struct lvts_ctrl_data mt8186_lvts_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8186_LITTLE_CPU0,
+ .cal_offsets = { 5, 6, 7 } },
+ { .dt_id = MT8186_LITTLE_CPU1,
+ .cal_offsets = { 10, 11, 4 } },
+ { .dt_id = MT8186_LITTLE_CPU2,
+ .cal_offsets = { 15, 8, 9 } },
+ { .dt_id = MT8186_CAM,
+ .cal_offsets = { 12, 13, 14 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x0,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8186_BIG_CPU0,
+ .cal_offsets = { 22, 23, 16 } },
+ { .dt_id = MT8186_BIG_CPU1,
+ .cal_offsets = { 27, 20, 21 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x100,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8186_NNA,
+ .cal_offsets = { 29, 30, 31 } },
+ { .dt_id = MT8186_ADSP,
+ .cal_offsets = { 34, 35, 28 } },
+ { .dt_id = MT8186_GPU,
+ .cal_offsets = { 39, 32, 33 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 0),
+ .offset = 0x200,
+ }
+};
+
+static const struct lvts_ctrl_data mt8188_lvts_mcu_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8188_MCU_LITTLE_CPU0,
+ .cal_offsets = { 22, 23, 24 } },
+ { .dt_id = MT8188_MCU_LITTLE_CPU1,
+ .cal_offsets = { 25, 26, 27 } },
+ { .dt_id = MT8188_MCU_LITTLE_CPU2,
+ .cal_offsets = { 28, 29, 30 } },
+ { .dt_id = MT8188_MCU_LITTLE_CPU3,
+ .cal_offsets = { 31, 32, 33 } },
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x0,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8188_MCU_BIG_CPU0,
+ .cal_offsets = { 34, 35, 36 } },
+ { .dt_id = MT8188_MCU_BIG_CPU1,
+ .cal_offsets = { 37, 38, 39 } },
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x100,
+ }
+};
+
+static const struct lvts_ctrl_data mt8188_lvts_ap_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+
+ { /* unused */ },
+ { .dt_id = MT8188_AP_APU,
+ .cal_offsets = { 40, 41, 42 } },
+ },
+ VALID_SENSOR_MAP(0, 1, 0, 0),
+ .offset = 0x0,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8188_AP_GPU0,
+ .cal_offsets = { 43, 44, 45 } },
+ { .dt_id = MT8188_AP_GPU1,
+ .cal_offsets = { 46, 47, 48 } },
+ { .dt_id = MT8188_AP_ADSP,
+ .cal_offsets = { 49, 50, 51 } },
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 0),
+ .offset = 0x100,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8188_AP_VDO,
+ .cal_offsets = { 52, 53, 54 } },
+ { .dt_id = MT8188_AP_INFRA,
+ .cal_offsets = { 55, 56, 57 } },
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x200,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8188_AP_CAM1,
+ .cal_offsets = { 58, 59, 60 } },
+ { .dt_id = MT8188_AP_CAM2,
+ .cal_offsets = { 61, 62, 63 } },
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x300,
+ }
+};
+
+static const struct lvts_ctrl_data mt8192_lvts_mcu_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8192_MCU_BIG_CPU0,
+ .cal_offsets = { 0x04, 0x05, 0x06 } },
+ { .dt_id = MT8192_MCU_BIG_CPU1,
+ .cal_offsets = { 0x08, 0x09, 0x0a } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x0,
+ .mode = LVTS_MSR_FILTERED_MODE,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8192_MCU_BIG_CPU2,
+ .cal_offsets = { 0x0c, 0x0d, 0x0e } },
+ { .dt_id = MT8192_MCU_BIG_CPU3,
+ .cal_offsets = { 0x10, 0x11, 0x12 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x100,
+ .mode = LVTS_MSR_FILTERED_MODE,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8192_MCU_LITTLE_CPU0,
+ .cal_offsets = { 0x14, 0x15, 0x16 } },
+ { .dt_id = MT8192_MCU_LITTLE_CPU1,
+ .cal_offsets = { 0x18, 0x19, 0x1a } },
+ { .dt_id = MT8192_MCU_LITTLE_CPU2,
+ .cal_offsets = { 0x1c, 0x1d, 0x1e } },
+ { .dt_id = MT8192_MCU_LITTLE_CPU3,
+ .cal_offsets = { 0x20, 0x21, 0x22 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x200,
+ .mode = LVTS_MSR_FILTERED_MODE,
+ }
+};
+
+static const struct lvts_ctrl_data mt8192_lvts_ap_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8192_AP_VPU0,
+ .cal_offsets = { 0x24, 0x25, 0x26 } },
+ { .dt_id = MT8192_AP_VPU1,
+ .cal_offsets = { 0x28, 0x29, 0x2a } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x0,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8192_AP_GPU0,
+ .cal_offsets = { 0x2c, 0x2d, 0x2e } },
+ { .dt_id = MT8192_AP_GPU1,
+ .cal_offsets = { 0x30, 0x31, 0x32 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x100,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8192_AP_INFRA,
+ .cal_offsets = { 0x34, 0x35, 0x36 } },
+ { .dt_id = MT8192_AP_CAM,
+ .cal_offsets = { 0x38, 0x39, 0x3a } },
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x200,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8192_AP_MD0,
+ .cal_offsets = { 0x3c, 0x3d, 0x3e } },
+ { .dt_id = MT8192_AP_MD1,
+ .cal_offsets = { 0x40, 0x41, 0x42 } },
+ { .dt_id = MT8192_AP_MD2,
+ .cal_offsets = { 0x44, 0x45, 0x46 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 0),
+ .offset = 0x300,
+ }
+};
+
static const struct lvts_ctrl_data mt8195_lvts_mcu_data_ctrl[] = {
{
- .cal_offset = { 0x04, 0x07 },
.lvts_sensor = {
- { .dt_id = MT8195_MCU_BIG_CPU0 },
- { .dt_id = MT8195_MCU_BIG_CPU1 }
+ { .dt_id = MT8195_MCU_BIG_CPU0,
+ .cal_offsets = { 0x04, 0x05, 0x06 } },
+ { .dt_id = MT8195_MCU_BIG_CPU1,
+ .cal_offsets = { 0x07, 0x08, 0x09 } }
},
- .num_lvts_sensor = 2,
+ VALID_SENSOR_MAP(1, 1, 0, 0),
.offset = 0x0,
- .hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
},
{
- .cal_offset = { 0x0d, 0x10 },
.lvts_sensor = {
- { .dt_id = MT8195_MCU_BIG_CPU2 },
- { .dt_id = MT8195_MCU_BIG_CPU3 }
+ { .dt_id = MT8195_MCU_BIG_CPU2,
+ .cal_offsets = { 0x0d, 0x0e, 0x0f } },
+ { .dt_id = MT8195_MCU_BIG_CPU3,
+ .cal_offsets = { 0x10, 0x11, 0x12 } }
},
- .num_lvts_sensor = 2,
+ VALID_SENSOR_MAP(1, 1, 0, 0),
.offset = 0x100,
- .hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
},
{
- .cal_offset = { 0x16, 0x19, 0x1c, 0x1f },
.lvts_sensor = {
- { .dt_id = MT8195_MCU_LITTLE_CPU0 },
- { .dt_id = MT8195_MCU_LITTLE_CPU1 },
- { .dt_id = MT8195_MCU_LITTLE_CPU2 },
- { .dt_id = MT8195_MCU_LITTLE_CPU3 }
+ { .dt_id = MT8195_MCU_LITTLE_CPU0,
+ .cal_offsets = { 0x16, 0x17, 0x18 } },
+ { .dt_id = MT8195_MCU_LITTLE_CPU1,
+ .cal_offsets = { 0x19, 0x1a, 0x1b } },
+ { .dt_id = MT8195_MCU_LITTLE_CPU2,
+ .cal_offsets = { 0x1c, 0x1d, 0x1e } },
+ { .dt_id = MT8195_MCU_LITTLE_CPU3,
+ .cal_offsets = { 0x1f, 0x20, 0x21 } }
},
- .num_lvts_sensor = 4,
+ VALID_SENSOR_MAP(1, 1, 1, 1),
.offset = 0x200,
- .hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
}
};
static const struct lvts_ctrl_data mt8195_lvts_ap_data_ctrl[] = {
- {
- .cal_offset = { 0x25, 0x28 },
+ {
.lvts_sensor = {
- { .dt_id = MT8195_AP_VPU0 },
- { .dt_id = MT8195_AP_VPU1 }
+ { .dt_id = MT8195_AP_VPU0,
+ .cal_offsets = { 0x25, 0x26, 0x27 } },
+ { .dt_id = MT8195_AP_VPU1,
+ .cal_offsets = { 0x28, 0x29, 0x2a } }
},
- .num_lvts_sensor = 2,
+ VALID_SENSOR_MAP(1, 1, 0, 0),
.offset = 0x0,
- .hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
},
{
- .cal_offset = { 0x2e, 0x31 },
.lvts_sensor = {
- { .dt_id = MT8195_AP_GPU0 },
- { .dt_id = MT8195_AP_GPU1 }
+ { .dt_id = MT8195_AP_GPU0,
+ .cal_offsets = { 0x2e, 0x2f, 0x30 } },
+ { .dt_id = MT8195_AP_GPU1,
+ .cal_offsets = { 0x31, 0x32, 0x33 } }
},
- .num_lvts_sensor = 2,
+ VALID_SENSOR_MAP(1, 1, 0, 0),
.offset = 0x100,
- .hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
},
{
- .cal_offset = { 0x37, 0x3a, 0x3d },
.lvts_sensor = {
- { .dt_id = MT8195_AP_VDEC },
- { .dt_id = MT8195_AP_IMG },
- { .dt_id = MT8195_AP_INFRA },
+ { .dt_id = MT8195_AP_VDEC,
+ .cal_offsets = { 0x37, 0x38, 0x39 } },
+ { .dt_id = MT8195_AP_IMG,
+ .cal_offsets = { 0x3a, 0x3b, 0x3c } },
+ { .dt_id = MT8195_AP_INFRA,
+ .cal_offsets = { 0x3d, 0x3e, 0x3f } }
},
- .num_lvts_sensor = 3,
+ VALID_SENSOR_MAP(1, 1, 1, 0),
.offset = 0x200,
- .hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
},
{
- .cal_offset = { 0x43, 0x46 },
.lvts_sensor = {
- { .dt_id = MT8195_AP_CAM0 },
- { .dt_id = MT8195_AP_CAM1 }
+ { .dt_id = MT8195_AP_CAM0,
+ .cal_offsets = { 0x43, 0x44, 0x45 } },
+ { .dt_id = MT8195_AP_CAM1,
+ .cal_offsets = { 0x46, 0x47, 0x48 } }
},
- .num_lvts_sensor = 2,
+ VALID_SENSOR_MAP(1, 1, 0, 0),
.offset = 0x300,
- .hw_tshut_temp = LVTS_HW_SHUTDOWN_MT8195,
}
};
+static const struct lvts_data mt7988_lvts_ap_data = {
+ .lvts_ctrl = mt7988_lvts_ap_data_ctrl,
+ .conn_cmd = mt7988_conn_cmds,
+ .init_cmd = mt7988_init_cmds,
+ .num_lvts_ctrl = ARRAY_SIZE(mt7988_lvts_ap_data_ctrl),
+ .num_conn_cmd = ARRAY_SIZE(mt7988_conn_cmds),
+ .num_init_cmd = ARRAY_SIZE(mt7988_init_cmds),
+ .temp_factor = LVTS_COEFF_A_MT7988,
+ .temp_offset = LVTS_COEFF_B_MT7988,
+ .gt_calib_bit_offset = 24,
+};
+
+static const struct lvts_data mt8186_lvts_data = {
+ .lvts_ctrl = mt8186_lvts_data_ctrl,
+ .conn_cmd = default_conn_cmds,
+ .init_cmd = default_init_cmds,
+ .num_lvts_ctrl = ARRAY_SIZE(mt8186_lvts_data_ctrl),
+ .num_conn_cmd = ARRAY_SIZE(default_conn_cmds),
+ .num_init_cmd = ARRAY_SIZE(default_init_cmds),
+ .temp_factor = LVTS_COEFF_A_MT7988,
+ .temp_offset = LVTS_COEFF_B_MT7988,
+ .gt_calib_bit_offset = 24,
+ .def_calibration = 19000,
+};
+
+static const struct lvts_data mt8188_lvts_mcu_data = {
+ .lvts_ctrl = mt8188_lvts_mcu_data_ctrl,
+ .conn_cmd = default_conn_cmds,
+ .init_cmd = default_init_cmds,
+ .num_lvts_ctrl = ARRAY_SIZE(mt8188_lvts_mcu_data_ctrl),
+ .num_conn_cmd = ARRAY_SIZE(default_conn_cmds),
+ .num_init_cmd = ARRAY_SIZE(default_init_cmds),
+ .temp_factor = LVTS_COEFF_A_MT8195,
+ .temp_offset = LVTS_COEFF_B_MT8195,
+ .gt_calib_bit_offset = 20,
+ .def_calibration = 35000,
+};
+
+static const struct lvts_data mt8188_lvts_ap_data = {
+ .lvts_ctrl = mt8188_lvts_ap_data_ctrl,
+ .conn_cmd = default_conn_cmds,
+ .init_cmd = default_init_cmds,
+ .num_lvts_ctrl = ARRAY_SIZE(mt8188_lvts_ap_data_ctrl),
+ .num_conn_cmd = ARRAY_SIZE(default_conn_cmds),
+ .num_init_cmd = ARRAY_SIZE(default_init_cmds),
+ .temp_factor = LVTS_COEFF_A_MT8195,
+ .temp_offset = LVTS_COEFF_B_MT8195,
+ .gt_calib_bit_offset = 20,
+ .def_calibration = 35000,
+};
+
+static const struct lvts_data mt8192_lvts_mcu_data = {
+ .lvts_ctrl = mt8192_lvts_mcu_data_ctrl,
+ .conn_cmd = default_conn_cmds,
+ .init_cmd = default_init_cmds,
+ .num_lvts_ctrl = ARRAY_SIZE(mt8192_lvts_mcu_data_ctrl),
+ .num_conn_cmd = ARRAY_SIZE(default_conn_cmds),
+ .num_init_cmd = ARRAY_SIZE(default_init_cmds),
+ .temp_factor = LVTS_COEFF_A_MT8195,
+ .temp_offset = LVTS_COEFF_B_MT8195,
+ .gt_calib_bit_offset = 24,
+ .def_calibration = 35000,
+};
+
+static const struct lvts_data mt8192_lvts_ap_data = {
+ .lvts_ctrl = mt8192_lvts_ap_data_ctrl,
+ .conn_cmd = default_conn_cmds,
+ .init_cmd = default_init_cmds,
+ .num_lvts_ctrl = ARRAY_SIZE(mt8192_lvts_ap_data_ctrl),
+ .num_conn_cmd = ARRAY_SIZE(default_conn_cmds),
+ .num_init_cmd = ARRAY_SIZE(default_init_cmds),
+ .temp_factor = LVTS_COEFF_A_MT8195,
+ .temp_offset = LVTS_COEFF_B_MT8195,
+ .gt_calib_bit_offset = 24,
+ .def_calibration = 35000,
+};
+
static const struct lvts_data mt8195_lvts_mcu_data = {
.lvts_ctrl = mt8195_lvts_mcu_data_ctrl,
+ .conn_cmd = default_conn_cmds,
+ .init_cmd = default_init_cmds,
.num_lvts_ctrl = ARRAY_SIZE(mt8195_lvts_mcu_data_ctrl),
+ .num_conn_cmd = ARRAY_SIZE(default_conn_cmds),
+ .num_init_cmd = ARRAY_SIZE(default_init_cmds),
+ .temp_factor = LVTS_COEFF_A_MT8195,
+ .temp_offset = LVTS_COEFF_B_MT8195,
+ .gt_calib_bit_offset = 24,
+ .def_calibration = 35000,
};
static const struct lvts_data mt8195_lvts_ap_data = {
.lvts_ctrl = mt8195_lvts_ap_data_ctrl,
+ .conn_cmd = default_conn_cmds,
+ .init_cmd = default_init_cmds,
.num_lvts_ctrl = ARRAY_SIZE(mt8195_lvts_ap_data_ctrl),
+ .num_conn_cmd = ARRAY_SIZE(default_conn_cmds),
+ .num_init_cmd = ARRAY_SIZE(default_init_cmds),
+ .temp_factor = LVTS_COEFF_A_MT8195,
+ .temp_offset = LVTS_COEFF_B_MT8195,
+ .gt_calib_bit_offset = 24,
+ .def_calibration = 35000,
};
static const struct of_device_id lvts_of_match[] = {
+ { .compatible = "mediatek,mt7988-lvts-ap", .data = &mt7988_lvts_ap_data },
+ { .compatible = "mediatek,mt8186-lvts", .data = &mt8186_lvts_data },
+ { .compatible = "mediatek,mt8188-lvts-mcu", .data = &mt8188_lvts_mcu_data },
+ { .compatible = "mediatek,mt8188-lvts-ap", .data = &mt8188_lvts_ap_data },
+ { .compatible = "mediatek,mt8192-lvts-mcu", .data = &mt8192_lvts_mcu_data },
+ { .compatible = "mediatek,mt8192-lvts-ap", .data = &mt8192_lvts_ap_data },
{ .compatible = "mediatek,mt8195-lvts-mcu", .data = &mt8195_lvts_mcu_data },
{ .compatible = "mediatek,mt8195-lvts-ap", .data = &mt8195_lvts_ap_data },
{},
};
MODULE_DEVICE_TABLE(of, lvts_of_match);
+static const struct dev_pm_ops lvts_pm_ops = {
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(lvts_suspend, lvts_resume)
+};
+
static struct platform_driver lvts_driver = {
.probe = lvts_probe,
.remove = lvts_remove,
.driver = {
.name = "mtk-lvts-thermal",
.of_match_table = lvts_of_match,
+ .pm = &lvts_pm_ops,
},
};
module_platform_driver(lvts_driver);
diff --git a/drivers/thermal/pcie_cooling.c b/drivers/thermal/pcie_cooling.c
new file mode 100644
index 000000000000..a876d64f1582
--- /dev/null
+++ b/drivers/thermal/pcie_cooling.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCIe cooling device
+ *
+ * Copyright (C) 2023-2024 Intel Corporation
+ */
+
+#include <linux/build_bug.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pci-bwctrl.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/thermal.h>
+
+#define COOLING_DEV_TYPE_PREFIX "PCIe_Port_Link_Speed_"
+
+static int pcie_cooling_get_max_level(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+ struct pci_dev *port = cdev->devdata;
+
+ /* cooling state 0 is same as the maximum PCIe speed */
+ *state = port->subordinate->max_bus_speed - PCIE_SPEED_2_5GT;
+
+ return 0;
+}
+
+static int pcie_cooling_get_cur_level(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+ struct pci_dev *port = cdev->devdata;
+
+ /* cooling state 0 is same as the maximum PCIe speed */
+ *state = cdev->max_state - (port->subordinate->cur_bus_speed - PCIE_SPEED_2_5GT);
+
+ return 0;
+}
+
+static int pcie_cooling_set_cur_level(struct thermal_cooling_device *cdev, unsigned long state)
+{
+ struct pci_dev *port = cdev->devdata;
+ enum pci_bus_speed speed;
+
+ /* cooling state 0 is same as the maximum PCIe speed */
+ speed = (cdev->max_state - state) + PCIE_SPEED_2_5GT;
+
+ return pcie_set_target_speed(port, speed, true);
+}
+
+static struct thermal_cooling_device_ops pcie_cooling_ops = {
+ .get_max_state = pcie_cooling_get_max_level,
+ .get_cur_state = pcie_cooling_get_cur_level,
+ .set_cur_state = pcie_cooling_set_cur_level,
+};
+
+struct thermal_cooling_device *pcie_cooling_device_register(struct pci_dev *port)
+{
+ char *name __free(kfree) =
+ kasprintf(GFP_KERNEL, COOLING_DEV_TYPE_PREFIX "%s", pci_name(port));
+ if (!name)
+ return ERR_PTR(-ENOMEM);
+
+ return thermal_cooling_device_register(name, port, &pcie_cooling_ops);
+}
+
+void pcie_cooling_device_unregister(struct thermal_cooling_device *cdev)
+{
+ thermal_cooling_device_unregister(cdev);
+}
+
+/* For bus_speed <-> state arithmetic */
+static_assert(PCIE_SPEED_2_5GT + 1 == PCIE_SPEED_5_0GT);
+static_assert(PCIE_SPEED_5_0GT + 1 == PCIE_SPEED_8_0GT);
+static_assert(PCIE_SPEED_8_0GT + 1 == PCIE_SPEED_16_0GT);
+static_assert(PCIE_SPEED_16_0GT + 1 == PCIE_SPEED_32_0GT);
+static_assert(PCIE_SPEED_32_0GT + 1 == PCIE_SPEED_64_0GT);
+
+MODULE_AUTHOR("Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>");
+MODULE_DESCRIPTION("PCIe cooling driver");
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
index 2c7f3f9a26eb..a6bb01082ec6 100644
--- a/drivers/thermal/qcom/Kconfig
+++ b/drivers/thermal/qcom/Kconfig
@@ -34,7 +34,8 @@ config QCOM_SPMI_TEMP_ALARM
config QCOM_LMH
tristate "Qualcomm Limits Management Hardware"
- depends on ARCH_QCOM && QCOM_SCM
+ depends on ARCH_QCOM || COMPILE_TEST
+ select QCOM_SCM
help
This enables initialization of Qualcomm limits management
hardware(LMh). LMh allows for hardware-enforced mitigation for cpus based on
diff --git a/drivers/thermal/qcom/lmh.c b/drivers/thermal/qcom/lmh.c
index f6edb12ec004..ddadcfada513 100644
--- a/drivers/thermal/qcom/lmh.c
+++ b/drivers/thermal/qcom/lmh.c
@@ -5,6 +5,8 @@
*/
#include <linux/module.h>
#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
#include <linux/irqdomain.h>
#include <linux/err.h>
#include <linux/platform_device.h>
@@ -73,7 +75,14 @@ static struct irq_chip lmh_irq_chip = {
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;
+ static struct lock_class_key lmh_lock_key;
+ static struct lock_class_key lmh_request_key;
+ /*
+ * This lock class tells lockdep that GPIO irqs are in a different
+ * category than their parents, so it won't report false recursion.
+ */
+ irq_set_lockdep_class(irq, &lmh_lock_key, &lmh_request_key);
irq_set_chip_and_handler(irq, &lmh_irq_chip, handle_simple_irq);
irq_set_chip_data(irq, lmh_data);
@@ -95,6 +104,9 @@ static int lmh_probe(struct platform_device *pdev)
unsigned int enable_alg;
u32 node_id;
+ if (!qcom_scm_is_available())
+ return -EPROBE_DEFER;
+
lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL);
if (!lmh_data)
return -ENOMEM;
@@ -194,12 +206,12 @@ static int lmh_probe(struct platform_device *pdev)
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);
+ dev_err(dev, "Error setting thermal LOW 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);
+ lmh_data->domain = irq_domain_create_linear(dev_fwnode(dev), 1, &lmh_irq_ops, lmh_data);
if (!lmh_data->domain) {
dev_err(dev, "Error adding irq_domain\n");
return -EINVAL;
diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
index 5ddc39b2be32..d7f2e6ca92c2 100644
--- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
+++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
@@ -14,12 +14,11 @@
#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/unaligned.h>
+#include <linux/unaligned.h>
#include "../thermal_hwmon.h"
@@ -830,12 +829,9 @@ static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm,
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;
- }
+ if (IS_ERR(channel->iio))
+ return dev_err_probe(dev, PTR_ERR(channel->iio), "%s: error getting channel\n",
+ name);
ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
if (!ret) {
@@ -942,7 +938,6 @@ static const struct adc_tm5_data adc_tm5_gen2_data_pmic = {
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;
@@ -986,12 +981,10 @@ static int adc_tm5_get_dt_data(struct adc_tm5_chip *adc_tm, struct device_node *
adc_tm->avg_samples = VADC_DEF_AVG_SAMPLES;
}
- for_each_available_child_of_node(node, child) {
+ for_each_available_child_of_node_scoped(node, child) {
ret = adc_tm5_get_dt_channel_data(adc_tm, channels, child);
- if (ret) {
- of_node_put(child);
+ if (ret)
return ret;
- }
channels++;
}
diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
index 0e8ebfcd84c5..f39ca0ddd17b 100644
--- a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
+++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2011-2015, 2017, 2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
+#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/err.h>
@@ -10,38 +12,57 @@
#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 "../thermal_hwmon.h"
+#define QPNP_TM_REG_DIG_MINOR 0x00
#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
+#define QPNP_TM_REG_IRQ_STATUS 0x10
#define QPNP_TM_REG_SHUTDOWN_CTRL1 0x40
#define QPNP_TM_REG_ALARM_CTRL 0x46
+/* TEMP_DAC_STGx registers are only present for TEMP_GEN2 v2.0 */
+#define QPNP_TM_REG_TEMP_DAC_STG1 0x47
+#define QPNP_TM_REG_TEMP_DAC_STG2 0x48
+#define QPNP_TM_REG_TEMP_DAC_STG3 0x49
+#define QPNP_TM_REG_LITE_TEMP_CFG1 0x50
+#define QPNP_TM_REG_LITE_TEMP_CFG2 0x51
+
#define QPNP_TM_TYPE 0x09
#define QPNP_TM_SUBTYPE_GEN1 0x08
#define QPNP_TM_SUBTYPE_GEN2 0x09
+#define QPNP_TM_SUBTYPE_LITE 0xC0
#define STATUS_GEN1_STAGE_MASK GENMASK(1, 0)
#define STATUS_GEN2_STATE_MASK GENMASK(6, 4)
-#define STATUS_GEN2_STATE_SHIFT 4
-#define SHUTDOWN_CTRL1_OVERRIDE_S2 BIT(6)
+/* IRQ status only needed for TEMP_ALARM_LITE */
+#define IRQ_STATUS_MASK BIT(0)
+
+#define SHUTDOWN_CTRL1_OVERRIDE_STAGE2 BIT(6)
#define SHUTDOWN_CTRL1_THRESHOLD_MASK GENMASK(1, 0)
#define SHUTDOWN_CTRL1_RATE_25HZ BIT(3)
#define ALARM_CTRL_FORCE_ENABLE BIT(7)
+#define LITE_TEMP_CFG_THRESHOLD_MASK GENMASK(3, 2)
+
#define THRESH_COUNT 4
#define STAGE_COUNT 3
+enum overtemp_stage {
+ STAGE1 = 0,
+ STAGE2,
+ STAGE3,
+};
+
/* Over-temperature trip point values in mC */
static const long temp_map_gen1[THRESH_COUNT][STAGE_COUNT] = {
{ 105000, 125000, 145000 },
@@ -64,25 +85,68 @@ static const long temp_map_gen2_v1[THRESH_COUNT][STAGE_COUNT] = {
#define TEMP_STAGE_HYSTERESIS 2000
+/*
+ * For TEMP_GEN2 v2.0, TEMP_DAC_STG1/2/3 registers are used to set the threshold
+ * for each stage independently.
+ * TEMP_DAC_STG* = 0 --> 80 C
+ * Each 8 step increase in TEMP_DAC_STG* value corresponds to 5 C (5000 mC).
+ */
+#define TEMP_DAC_MIN 80000
+#define TEMP_DAC_SCALE_NUM 8
+#define TEMP_DAC_SCALE_DEN 5000
+
+#define TEMP_DAC_TEMP_TO_REG(temp) \
+ (((temp) - TEMP_DAC_MIN) * TEMP_DAC_SCALE_NUM / TEMP_DAC_SCALE_DEN)
+#define TEMP_DAC_REG_TO_TEMP(reg) \
+ (TEMP_DAC_MIN + (reg) * TEMP_DAC_SCALE_DEN / TEMP_DAC_SCALE_NUM)
+
+static const long temp_dac_max[STAGE_COUNT] = {
+ 119375, 159375, 159375
+};
+
+/*
+ * TEMP_ALARM_LITE has two stages: warning and shutdown with independently
+ * configured threshold temperatures.
+ */
+
+static const long temp_lite_warning_map[THRESH_COUNT] = {
+ 115000, 125000, 135000, 145000
+};
+
+static const long temp_lite_shutdown_map[THRESH_COUNT] = {
+ 135000, 145000, 160000, 175000
+};
+
/* Temperature in Milli Celsius reported during stage 0 if no ADC is present */
#define DEFAULT_TEMP 37000
+struct qpnp_tm_chip;
+
+struct spmi_temp_alarm_data {
+ const struct thermal_zone_device_ops *ops;
+ const long (*temp_map)[THRESH_COUNT][STAGE_COUNT];
+ int (*sync_thresholds)(struct qpnp_tm_chip *chip);
+ int (*get_temp_stage)(struct qpnp_tm_chip *chip);
+ int (*configure_trip_temps)(struct qpnp_tm_chip *chip);
+};
+
struct qpnp_tm_chip {
struct regmap *map;
struct device *dev;
struct thermal_zone_device *tz_dev;
+ const struct spmi_temp_alarm_data *data;
unsigned int subtype;
long temp;
- unsigned int thresh;
unsigned int stage;
- unsigned int prev_stage;
unsigned int base;
+ unsigned int ntrips;
/* protects .thresh, .stage and chip registers */
struct mutex lock;
bool initialized;
+ bool require_stage2_shutdown;
+ long temp_thresh_map[STAGE_COUNT];
struct iio_channel *adc;
- const long (*temp_map)[THRESH_COUNT][STAGE_COUNT];
};
/* This array maps from GEN2 alarm state to GEN1 alarm stage */
@@ -116,34 +180,66 @@ static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data)
*/
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)
+ if (stage == 0 || stage > STAGE_COUNT)
return 0;
- return (*chip->temp_map)[chip->thresh][stage - 1];
+ return chip->temp_thresh_map[stage - 1];
}
/**
- * qpnp_tm_get_temp_stage() - return over-temperature stage
+ * qpnp_tm_gen1_get_temp_stage() - return over-temperature stage
* @chip: Pointer to the qpnp_tm chip
*
- * Return: stage (GEN1) or state (GEN2) on success, or errno on failure.
+ * Return: stage on success, or errno on failure.
*/
-static int qpnp_tm_get_temp_stage(struct qpnp_tm_chip *chip)
+static int qpnp_tm_gen1_get_temp_stage(struct qpnp_tm_chip *chip)
{
int ret;
- u8 reg = 0;
+ u8 reg;
ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, &reg);
if (ret < 0)
return ret;
- if (chip->subtype == QPNP_TM_SUBTYPE_GEN1)
- ret = reg & STATUS_GEN1_STAGE_MASK;
- else
- ret = (reg & STATUS_GEN2_STATE_MASK) >> STATUS_GEN2_STATE_SHIFT;
+ return FIELD_GET(STATUS_GEN1_STAGE_MASK, reg);
+}
- return ret;
+/**
+ * qpnp_tm_gen2_get_temp_stage() - return over-temperature stage
+ * @chip: Pointer to the qpnp_tm chip
+ *
+ * Return: stage on success, or errno on failure.
+ */
+static int qpnp_tm_gen2_get_temp_stage(struct qpnp_tm_chip *chip)
+{
+ int ret;
+ u8 reg;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, &reg);
+ if (ret < 0)
+ return ret;
+
+ ret = FIELD_GET(STATUS_GEN2_STATE_MASK, reg);
+
+ return alarm_state_map[ret];
+}
+
+/**
+ * qpnp_tm_lite_get_temp_stage() - return over-temperature stage
+ * @chip: Pointer to the qpnp_tm chip
+ *
+ * Return: alarm interrupt state on success, or errno on failure.
+ */
+static int qpnp_tm_lite_get_temp_stage(struct qpnp_tm_chip *chip)
+{
+ u8 reg = 0;
+ int ret;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_IRQ_STATUS, &reg);
+ if (ret < 0)
+ return ret;
+
+ return FIELD_GET(IRQ_STATUS_MASK, reg);
}
/*
@@ -152,23 +248,16 @@ static int qpnp_tm_get_temp_stage(struct qpnp_tm_chip *chip)
*/
static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
{
- unsigned int stage, stage_new, stage_old;
+ unsigned int stage_new, stage_old;
int ret;
WARN_ON(!mutex_is_locked(&chip->lock));
- ret = qpnp_tm_get_temp_stage(chip);
+ ret = chip->data->get_temp_stage(chip);
if (ret < 0)
return ret;
- stage = ret;
-
- if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) {
- stage_new = stage;
- stage_old = chip->stage;
- } else {
- stage_new = alarm_state_map[stage];
- stage_old = alarm_state_map[chip->stage];
- }
+ stage_new = ret;
+ stage_old = chip->stage;
if (stage_new > stage_old) {
/* increasing stage, use lower bound */
@@ -180,7 +269,7 @@ static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
- TEMP_STAGE_HYSTERESIS;
}
- chip->stage = stage;
+ chip->stage = stage_new;
return 0;
}
@@ -220,35 +309,35 @@ static int qpnp_tm_get_temp(struct thermal_zone_device *tz, int *temp)
static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip,
int temp)
{
- 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;
+ long stage2_threshold_min = (*chip->data->temp_map)[THRESH_MIN][STAGE2];
+ long stage2_threshold_max = (*chip->data->temp_map)[THRESH_MAX][STAGE2];
+ bool disable_stage2_shutdown = false;
+ u8 reg, threshold;
WARN_ON(!mutex_is_locked(&chip->lock));
/*
- * Default: S2 and S3 shutdown enabled, thresholds at
+ * Default: Stage 2 and Stage 3 shutdown enabled, thresholds at
* lowest threshold set, monitoring at 25Hz
*/
reg = SHUTDOWN_CTRL1_RATE_25HZ;
if (temp == THERMAL_TEMP_INVALID ||
temp < stage2_threshold_min) {
- chip->thresh = THRESH_MIN;
+ threshold = THRESH_MIN;
goto skip;
}
if (temp <= stage2_threshold_max) {
- chip->thresh = THRESH_MAX -
+ threshold = THRESH_MAX -
((stage2_threshold_max - temp) /
TEMP_THRESH_STEP);
- disable_s2_shutdown = true;
+ disable_stage2_shutdown = true;
} else {
- chip->thresh = THRESH_MAX;
+ threshold = THRESH_MAX;
if (chip->adc)
- disable_s2_shutdown = true;
+ disable_stage2_shutdown = true;
else
dev_warn(chip->dev,
"No ADC is configured and critical temperature %d mC is above the maximum stage 2 threshold of %ld mC! Configuring stage 2 shutdown at %ld mC.\n",
@@ -256,24 +345,22 @@ static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip,
}
skip:
- reg |= chip->thresh;
- if (disable_s2_shutdown)
- reg |= SHUTDOWN_CTRL1_OVERRIDE_S2;
+ memcpy(chip->temp_thresh_map, chip->data->temp_map[threshold],
+ sizeof(chip->temp_thresh_map));
+ reg |= threshold;
+ if (disable_stage2_shutdown && !chip->require_stage2_shutdown)
+ reg |= SHUTDOWN_CTRL1_OVERRIDE_STAGE2;
return qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg);
}
-static int qpnp_tm_set_trip_temp(struct thermal_zone_device *tz, int trip_id, int temp)
+static int qpnp_tm_set_trip_temp(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip, int temp)
{
struct qpnp_tm_chip *chip = thermal_zone_device_priv(tz);
- struct thermal_trip trip;
int ret;
- ret = __thermal_zone_get_trip(chip->tz_dev, trip_id, &trip);
- if (ret)
- return ret;
-
- if (trip.type != THERMAL_TRIP_CRITICAL)
+ if (trip->type != THERMAL_TRIP_CRITICAL)
return 0;
mutex_lock(&chip->lock);
@@ -288,6 +375,146 @@ static const struct thermal_zone_device_ops qpnp_tm_sensor_ops = {
.set_trip_temp = qpnp_tm_set_trip_temp,
};
+static int qpnp_tm_gen2_rev2_set_temp_thresh(struct qpnp_tm_chip *chip, unsigned int trip, int temp)
+{
+ int ret, temp_cfg;
+ u8 reg;
+
+ WARN_ON(!mutex_is_locked(&chip->lock));
+
+ if (trip >= STAGE_COUNT) {
+ dev_err(chip->dev, "invalid TEMP_DAC trip = %d\n", trip);
+ return -EINVAL;
+ } else if (temp < TEMP_DAC_MIN || temp > temp_dac_max[trip]) {
+ dev_err(chip->dev, "invalid TEMP_DAC temp = %d\n", temp);
+ return -EINVAL;
+ }
+
+ reg = TEMP_DAC_TEMP_TO_REG(temp);
+ temp_cfg = TEMP_DAC_REG_TO_TEMP(reg);
+
+ ret = qpnp_tm_write(chip, QPNP_TM_REG_TEMP_DAC_STG1 + trip, reg);
+ if (ret < 0) {
+ dev_err(chip->dev, "TEMP_DAC_STG write failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ chip->temp_thresh_map[trip] = temp_cfg;
+
+ return 0;
+}
+
+static int qpnp_tm_gen2_rev2_set_trip_temp(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip, int temp)
+{
+ unsigned int trip_index = THERMAL_TRIP_PRIV_TO_INT(trip->priv);
+ struct qpnp_tm_chip *chip = thermal_zone_device_priv(tz);
+ int ret;
+
+ mutex_lock(&chip->lock);
+ ret = qpnp_tm_gen2_rev2_set_temp_thresh(chip, trip_index, temp);
+ mutex_unlock(&chip->lock);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops qpnp_tm_gen2_rev2_sensor_ops = {
+ .get_temp = qpnp_tm_get_temp,
+ .set_trip_temp = qpnp_tm_gen2_rev2_set_trip_temp,
+};
+
+static int qpnp_tm_lite_set_temp_thresh(struct qpnp_tm_chip *chip, unsigned int trip, int temp)
+{
+ int ret, temp_cfg, i;
+ const long *temp_map;
+ u8 reg, thresh;
+ u16 addr;
+
+ WARN_ON(!mutex_is_locked(&chip->lock));
+
+ if (trip >= STAGE_COUNT) {
+ dev_err(chip->dev, "invalid TEMP_LITE trip = %d\n", trip);
+ return -EINVAL;
+ }
+
+ switch (trip) {
+ case 0:
+ temp_map = temp_lite_warning_map;
+ addr = QPNP_TM_REG_LITE_TEMP_CFG1;
+ break;
+ case 1:
+ /*
+ * The second trip point is purely in software to facilitate
+ * a controlled shutdown after the warning threshold is crossed
+ * but before the automatic hardware shutdown threshold is
+ * crossed.
+ */
+ return 0;
+ case 2:
+ temp_map = temp_lite_shutdown_map;
+ addr = QPNP_TM_REG_LITE_TEMP_CFG2;
+ break;
+ default:
+ return 0;
+ }
+
+ if (temp < temp_map[THRESH_MIN] || temp > temp_map[THRESH_MAX]) {
+ dev_err(chip->dev, "invalid TEMP_LITE temp = %d\n", temp);
+ return -EINVAL;
+ }
+
+ thresh = 0;
+ temp_cfg = temp_map[thresh];
+ for (i = THRESH_MAX; i >= THRESH_MIN; i--) {
+ if (temp >= temp_map[i]) {
+ thresh = i;
+ temp_cfg = temp_map[i];
+ break;
+ }
+ }
+
+ if (temp_cfg == chip->temp_thresh_map[trip])
+ return 0;
+
+ ret = qpnp_tm_read(chip, addr, &reg);
+ if (ret < 0) {
+ dev_err(chip->dev, "LITE_TEMP_CFG read failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ reg &= ~LITE_TEMP_CFG_THRESHOLD_MASK;
+ reg |= FIELD_PREP(LITE_TEMP_CFG_THRESHOLD_MASK, thresh);
+
+ ret = qpnp_tm_write(chip, addr, reg);
+ if (ret < 0) {
+ dev_err(chip->dev, "LITE_TEMP_CFG write failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ chip->temp_thresh_map[trip] = temp_cfg;
+
+ return 0;
+}
+
+static int qpnp_tm_lite_set_trip_temp(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip, int temp)
+{
+ unsigned int trip_index = THERMAL_TRIP_PRIV_TO_INT(trip->priv);
+ struct qpnp_tm_chip *chip = thermal_zone_device_priv(tz);
+ int ret;
+
+ mutex_lock(&chip->lock);
+ ret = qpnp_tm_lite_set_temp_thresh(chip, trip_index, temp);
+ mutex_unlock(&chip->lock);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops qpnp_tm_lite_sensor_ops = {
+ .get_temp = qpnp_tm_get_temp,
+ .set_trip_temp = qpnp_tm_lite_set_trip_temp,
+};
+
static irqreturn_t qpnp_tm_isr(int irq, void *data)
{
struct qpnp_tm_chip *chip = data;
@@ -297,65 +524,227 @@ static irqreturn_t qpnp_tm_isr(int irq, void *data)
return IRQ_HANDLED;
}
-static int qpnp_tm_get_critical_trip_temp(struct qpnp_tm_chip *chip)
+/* Read the hardware default stage threshold temperatures */
+static int qpnp_tm_sync_thresholds(struct qpnp_tm_chip *chip)
{
- struct thermal_trip trip;
- int i, ret;
+ u8 reg, threshold;
+ int ret;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, &reg);
+ if (ret < 0)
+ return ret;
- for (i = 0; i < thermal_zone_get_num_trips(chip->tz_dev); i++) {
+ threshold = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+ memcpy(chip->temp_thresh_map, chip->data->temp_map[threshold],
+ sizeof(chip->temp_thresh_map));
- ret = thermal_zone_get_trip(chip->tz_dev, i, &trip);
- if (ret)
- continue;
+ return ret;
+}
- if (trip.type == THERMAL_TRIP_CRITICAL)
- return trip.temperature;
- }
+static int qpnp_tm_configure_trip_temp(struct qpnp_tm_chip *chip)
+{
+ int crit_temp, ret;
+
+ ret = thermal_zone_get_crit_temp(chip->tz_dev, &crit_temp);
+ if (ret)
+ crit_temp = THERMAL_TEMP_INVALID;
+
+ mutex_lock(&chip->lock);
+ ret = qpnp_tm_update_critical_trip_temp(chip, crit_temp);
+ mutex_unlock(&chip->lock);
- return THERMAL_TEMP_INVALID;
+ return ret;
}
-/*
- * This function initializes the internal temp value based on only the
- * current thermal stage and threshold. Setup threshold control and
- * disable shutdown override.
- */
-static int qpnp_tm_init(struct qpnp_tm_chip *chip)
+/* Configure TEMP_DAC registers based on DT thermal_zone trips */
+static int qpnp_tm_gen2_rev2_configure_trip_temps_cb(struct thermal_trip *trip, void *data)
{
- unsigned int stage;
+ struct qpnp_tm_chip *chip = data;
int ret;
+
+ mutex_lock(&chip->lock);
+ trip->priv = THERMAL_INT_TO_TRIP_PRIV(chip->ntrips);
+ ret = qpnp_tm_gen2_rev2_set_temp_thresh(chip, chip->ntrips, trip->temperature);
+ chip->ntrips++;
+ mutex_unlock(&chip->lock);
+
+ return ret;
+}
+
+static int qpnp_tm_gen2_rev2_configure_trip_temps(struct qpnp_tm_chip *chip)
+{
+ int ret, i;
+
+ ret = thermal_zone_for_each_trip(chip->tz_dev,
+ qpnp_tm_gen2_rev2_configure_trip_temps_cb, chip);
+ if (ret < 0)
+ return ret;
+
+ /* Verify that trips are strictly increasing. */
+ for (i = 1; i < STAGE_COUNT; i++) {
+ if (chip->temp_thresh_map[i] <= chip->temp_thresh_map[i - 1]) {
+ dev_err(chip->dev, "Threshold %d=%ld <= threshold %d=%ld\n",
+ i, chip->temp_thresh_map[i], i - 1,
+ chip->temp_thresh_map[i - 1]);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* Read the hardware default TEMP_DAC stage threshold temperatures */
+static int qpnp_tm_gen2_rev2_sync_thresholds(struct qpnp_tm_chip *chip)
+{
+ int ret, i;
u8 reg = 0;
- int crit_temp;
+
+ for (i = 0; i < STAGE_COUNT; i++) {
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_TEMP_DAC_STG1 + i, &reg);
+ if (ret < 0)
+ return ret;
+
+ chip->temp_thresh_map[i] = TEMP_DAC_REG_TO_TEMP(reg);
+ }
+
+ return 0;
+}
+
+/* Configure TEMP_LITE registers based on DT thermal_zone trips */
+static int qpnp_tm_lite_configure_trip_temps_cb(struct thermal_trip *trip, void *data)
+{
+ struct qpnp_tm_chip *chip = data;
+ int ret;
mutex_lock(&chip->lock);
+ trip->priv = THERMAL_INT_TO_TRIP_PRIV(chip->ntrips);
+ ret = qpnp_tm_lite_set_temp_thresh(chip, chip->ntrips, trip->temperature);
+ chip->ntrips++;
+ mutex_unlock(&chip->lock);
- ret = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, &reg);
+ return ret;
+}
+
+static int qpnp_tm_lite_configure_trip_temps(struct qpnp_tm_chip *chip)
+{
+ int ret;
+
+ ret = thermal_zone_for_each_trip(chip->tz_dev, qpnp_tm_lite_configure_trip_temps_cb, chip);
if (ret < 0)
- goto out;
+ return ret;
- chip->thresh = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK;
- chip->temp = DEFAULT_TEMP;
+ /* Verify that trips are strictly increasing. */
+ if (chip->temp_thresh_map[2] <= chip->temp_thresh_map[0]) {
+ dev_err(chip->dev, "Threshold 2=%ld <= threshold 0=%ld\n",
+ chip->temp_thresh_map[2], chip->temp_thresh_map[0]);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Read the hardware default TEMP_LITE stage threshold temperatures */
+static int qpnp_tm_lite_sync_thresholds(struct qpnp_tm_chip *chip)
+{
+ int ret, thresh;
+ u8 reg = 0;
- ret = qpnp_tm_get_temp_stage(chip);
+ /*
+ * Store the warning trip temp in temp_thresh_map[0] and the shutdown trip
+ * temp in temp_thresh_map[2]. The second trip point is purely in software
+ * to facilitate a controlled shutdown after the warning threshold is
+ * crossed but before the automatic hardware shutdown threshold is
+ * crossed. Thus, there is no register to read for the second trip
+ * point.
+ */
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_LITE_TEMP_CFG1, &reg);
if (ret < 0)
- goto out;
- chip->stage = ret;
+ return ret;
+
+ thresh = FIELD_GET(LITE_TEMP_CFG_THRESHOLD_MASK, reg);
+ chip->temp_thresh_map[0] = temp_lite_warning_map[thresh];
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_LITE_TEMP_CFG2, &reg);
+ if (ret < 0)
+ return ret;
- stage = chip->subtype == QPNP_TM_SUBTYPE_GEN1
- ? chip->stage : alarm_state_map[chip->stage];
+ thresh = FIELD_GET(LITE_TEMP_CFG_THRESHOLD_MASK, reg);
+ chip->temp_thresh_map[2] = temp_lite_shutdown_map[thresh];
- if (stage)
- chip->temp = qpnp_tm_decode_temp(chip, stage);
+ return 0;
+}
- mutex_unlock(&chip->lock);
+static const struct spmi_temp_alarm_data spmi_temp_alarm_data = {
+ .ops = &qpnp_tm_sensor_ops,
+ .temp_map = &temp_map_gen1,
+ .sync_thresholds = qpnp_tm_sync_thresholds,
+ .configure_trip_temps = qpnp_tm_configure_trip_temp,
+ .get_temp_stage = qpnp_tm_gen1_get_temp_stage,
+};
- crit_temp = qpnp_tm_get_critical_trip_temp(chip);
+static const struct spmi_temp_alarm_data spmi_temp_alarm_gen2_data = {
+ .ops = &qpnp_tm_sensor_ops,
+ .temp_map = &temp_map_gen1,
+ .sync_thresholds = qpnp_tm_sync_thresholds,
+ .configure_trip_temps = qpnp_tm_configure_trip_temp,
+ .get_temp_stage = qpnp_tm_gen2_get_temp_stage,
+};
- mutex_lock(&chip->lock);
+static const struct spmi_temp_alarm_data spmi_temp_alarm_gen2_rev1_data = {
+ .ops = &qpnp_tm_sensor_ops,
+ .temp_map = &temp_map_gen2_v1,
+ .sync_thresholds = qpnp_tm_sync_thresholds,
+ .configure_trip_temps = qpnp_tm_configure_trip_temp,
+ .get_temp_stage = qpnp_tm_gen2_get_temp_stage,
+};
- ret = qpnp_tm_update_critical_trip_temp(chip, crit_temp);
+static const struct spmi_temp_alarm_data spmi_temp_alarm_gen2_rev2_data = {
+ .ops = &qpnp_tm_gen2_rev2_sensor_ops,
+ .sync_thresholds = qpnp_tm_gen2_rev2_sync_thresholds,
+ .configure_trip_temps = qpnp_tm_gen2_rev2_configure_trip_temps,
+ .get_temp_stage = qpnp_tm_gen2_get_temp_stage,
+};
+
+static const struct spmi_temp_alarm_data spmi_temp_alarm_lite_data = {
+ .ops = &qpnp_tm_lite_sensor_ops,
+ .sync_thresholds = qpnp_tm_lite_sync_thresholds,
+ .configure_trip_temps = qpnp_tm_lite_configure_trip_temps,
+ .get_temp_stage = qpnp_tm_lite_get_temp_stage,
+};
+
+/*
+ * This function initializes the internal temp value based on only the
+ * current thermal stage and threshold.
+ */
+static int qpnp_tm_threshold_init(struct qpnp_tm_chip *chip)
+{
+ int ret;
+
+ ret = chip->data->sync_thresholds(chip);
if (ret < 0)
- goto out;
+ return ret;
+
+ ret = chip->data->get_temp_stage(chip);
+ if (ret < 0)
+ return ret;
+ chip->stage = ret;
+ chip->temp = DEFAULT_TEMP;
+
+ if (chip->stage)
+ chip->temp = qpnp_tm_decode_temp(chip, chip->stage);
+
+ return ret;
+}
+
+/* This function initializes threshold control and disables shutdown override. */
+static int qpnp_tm_init(struct qpnp_tm_chip *chip)
+{
+ int ret;
+ u8 reg;
+
+ ret = chip->data->configure_trip_temps(chip);
+ if (ret < 0)
+ return ret;
/* Enable the thermal alarm PMIC module in always-on mode. */
reg = ALARM_CTRL_FORCE_ENABLE;
@@ -363,8 +752,6 @@ static int qpnp_tm_init(struct qpnp_tm_chip *chip)
chip->initialized = true;
-out:
- mutex_unlock(&chip->lock);
return ret;
}
@@ -372,8 +759,8 @@ static int qpnp_tm_probe(struct platform_device *pdev)
{
struct qpnp_tm_chip *chip;
struct device_node *node;
- u8 type, subtype, dig_major;
- u32 res;
+ u8 type, subtype, dig_major, dig_minor;
+ u32 res, dig_revision;
int ret, irq;
node = pdev->dev.of_node;
@@ -382,7 +769,6 @@ static int qpnp_tm_probe(struct platform_device *pdev)
if (!chip)
return -ENOMEM;
- dev_set_drvdata(&pdev->dev, chip);
chip->dev = &pdev->dev;
mutex_init(&chip->lock);
@@ -425,18 +811,53 @@ static int qpnp_tm_probe(struct platform_device *pdev)
return dev_err_probe(&pdev->dev, ret,
"could not read dig_major\n");
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_DIG_MINOR, &dig_minor);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "could not read dig_minor\n");
+
if (type != QPNP_TM_TYPE || (subtype != QPNP_TM_SUBTYPE_GEN1
- && subtype != QPNP_TM_SUBTYPE_GEN2)) {
+ && subtype != QPNP_TM_SUBTYPE_GEN2
+ && subtype != QPNP_TM_SUBTYPE_LITE)) {
dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n",
type, subtype);
return -ENODEV;
}
chip->subtype = subtype;
- if (subtype == QPNP_TM_SUBTYPE_GEN2 && dig_major >= 1)
- chip->temp_map = &temp_map_gen2_v1;
+ if (subtype == QPNP_TM_SUBTYPE_GEN1)
+ chip->data = &spmi_temp_alarm_data;
+ else if (subtype == QPNP_TM_SUBTYPE_GEN2 && dig_major == 0)
+ chip->data = &spmi_temp_alarm_gen2_data;
+ else if (subtype == QPNP_TM_SUBTYPE_GEN2 && dig_major == 1)
+ chip->data = &spmi_temp_alarm_gen2_rev1_data;
+ else if (subtype == QPNP_TM_SUBTYPE_GEN2 && dig_major >= 2)
+ chip->data = &spmi_temp_alarm_gen2_rev2_data;
+ else if (subtype == QPNP_TM_SUBTYPE_LITE)
+ chip->data = &spmi_temp_alarm_lite_data;
else
- chip->temp_map = &temp_map_gen1;
+ return -ENODEV;
+
+ if (chip->subtype == QPNP_TM_SUBTYPE_GEN2) {
+ dig_revision = (dig_major << 8) | dig_minor;
+ /*
+ * Check if stage 2 automatic partial shutdown must remain
+ * enabled to avoid potential repeated faults upon reaching
+ * over-temperature stage 3.
+ */
+ switch (dig_revision) {
+ case 0x0001:
+ case 0x0002:
+ case 0x0100:
+ case 0x0101:
+ chip->require_stage2_shutdown = true;
+ break;
+ }
+ }
+
+ ret = qpnp_tm_threshold_init(chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "threshold init failed\n");
/*
* Register the sensor before initializing the hardware to be able to
@@ -444,7 +865,7 @@ static int qpnp_tm_probe(struct platform_device *pdev)
* before the hardware initialization is completed.
*/
chip->tz_dev = devm_thermal_of_zone_register(
- &pdev->dev, 0, chip, &qpnp_tm_sensor_ops);
+ &pdev->dev, 0, chip, chip->data->ops);
if (IS_ERR(chip->tz_dev))
return dev_err_probe(&pdev->dev, PTR_ERR(chip->tz_dev),
"failed to register sensor\n");
diff --git a/drivers/thermal/qcom/tsens-v0_1.c b/drivers/thermal/qcom/tsens-v0_1.c
index a941b4241b0a..32d2d3e33287 100644
--- a/drivers/thermal/qcom/tsens-v0_1.c
+++ b/drivers/thermal/qcom/tsens-v0_1.c
@@ -23,7 +23,7 @@
#define BIT_APPEND 0x3
-struct tsens_legacy_calibration_format tsens_8916_nvmem = {
+static struct tsens_legacy_calibration_format tsens_8916_nvmem = {
.base_len = 7,
.base_shift = 3,
.sp_len = 5,
@@ -39,7 +39,7 @@ struct tsens_legacy_calibration_format tsens_8916_nvmem = {
},
};
-struct tsens_legacy_calibration_format tsens_8974_nvmem = {
+static struct tsens_legacy_calibration_format tsens_8974_nvmem = {
.base_len = 8,
.base_shift = 2,
.sp_len = 6,
@@ -61,7 +61,7 @@ struct tsens_legacy_calibration_format tsens_8974_nvmem = {
},
};
-struct tsens_legacy_calibration_format tsens_8974_backup_nvmem = {
+static struct tsens_legacy_calibration_format tsens_8974_backup_nvmem = {
.base_len = 8,
.base_shift = 2,
.sp_len = 6,
@@ -325,12 +325,6 @@ static const struct reg_field tsens_v0_1_regfields[MAX_REGFIELDS] = {
[TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
};
-static const struct tsens_ops ops_v0_1 = {
- .init = init_common,
- .calibrate = tsens_calibrate_common,
- .get_temp = get_temp_common,
-};
-
static const struct tsens_ops ops_8226 = {
.init = init_8226,
.calibrate = tsens_calibrate_common,
diff --git a/drivers/thermal/qcom/tsens-v1.c b/drivers/thermal/qcom/tsens-v1.c
index 51322430f1fe..faa5d00788ca 100644
--- a/drivers/thermal/qcom/tsens-v1.c
+++ b/drivers/thermal/qcom/tsens-v1.c
@@ -21,7 +21,7 @@
#define TM_HIGH_LOW_INT_STATUS_OFF 0x0088
#define TM_HIGH_LOW_Sn_INT_THRESHOLD_OFF 0x0090
-struct tsens_legacy_calibration_format tsens_qcs404_nvmem = {
+static struct tsens_legacy_calibration_format tsens_qcs404_nvmem = {
.base_len = 8,
.base_shift = 2,
.sp_len = 6,
@@ -79,6 +79,17 @@ static struct tsens_features tsens_v1_feat = {
.trip_max_temp = 120000,
};
+static struct tsens_features tsens_v1_no_rpm_feat = {
+ .ver_major = VER_1_X_NO_RPM,
+ .crit_int = 0,
+ .combo_int = 0,
+ .adc = 1,
+ .srot_split = 1,
+ .max_sensors = 11,
+ .trip_min_temp = -40000,
+ .trip_max_temp = 120000,
+};
+
static const struct reg_field tsens_v1_regfields[MAX_REGFIELDS] = {
/* ----- SROT ------ */
/* VERSION */
@@ -150,6 +161,43 @@ static int __init init_8956(struct tsens_priv *priv) {
return init_common(priv);
}
+static int __init init_tsens_v1_no_rpm(struct tsens_priv *priv)
+{
+ int i, ret;
+ u32 mask = 0;
+
+ ret = init_common(priv);
+ if (ret < 0) {
+ dev_err(priv->dev, "Init common failed %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_field_write(priv->rf[TSENS_SW_RST], 1);
+ if (ret) {
+ dev_err(priv->dev, "Reset failed\n");
+ return ret;
+ }
+
+ for (i = 0; i < priv->num_sensors; i++)
+ mask |= BIT(priv->sensor[i].hw_id);
+
+ ret = regmap_field_update_bits(priv->rf[SENSOR_EN], mask, mask);
+ if (ret) {
+ dev_err(priv->dev, "Sensor Enable failed\n");
+ return ret;
+ }
+
+ ret = regmap_field_write(priv->rf[TSENS_EN], 1);
+ if (ret) {
+ dev_err(priv->dev, "Enable failed\n");
+ return ret;
+ }
+
+ ret = regmap_field_write(priv->rf[TSENS_SW_RST], 0);
+
+ return ret;
+}
+
static const struct tsens_ops ops_generic_v1 = {
.init = init_common,
.calibrate = calibrate_v1,
@@ -162,6 +210,19 @@ struct tsens_plat_data data_tsens_v1 = {
.fields = tsens_v1_regfields,
};
+static const struct tsens_ops ops_common = {
+ .init = init_common,
+ .calibrate = tsens_calibrate_common,
+ .get_temp = get_temp_tsens_valid,
+};
+
+struct tsens_plat_data data_8937 = {
+ .num_sensors = 11,
+ .ops = &ops_common,
+ .feat = &tsens_v1_feat,
+ .fields = tsens_v1_regfields,
+};
+
static const struct tsens_ops ops_8956 = {
.init = init_8956,
.calibrate = tsens_calibrate_common,
@@ -175,15 +236,23 @@ struct tsens_plat_data data_8956 = {
.fields = tsens_v1_regfields,
};
-static const struct tsens_ops ops_8976 = {
- .init = init_common,
+struct tsens_plat_data data_8976 = {
+ .num_sensors = 11,
+ .ops = &ops_common,
+ .feat = &tsens_v1_feat,
+ .fields = tsens_v1_regfields,
+};
+
+static const struct tsens_ops ops_ipq5018 = {
+ .init = init_tsens_v1_no_rpm,
.calibrate = tsens_calibrate_common,
.get_temp = get_temp_tsens_valid,
};
-struct tsens_plat_data data_8976 = {
- .num_sensors = 11,
- .ops = &ops_8976,
- .feat = &tsens_v1_feat,
+const struct tsens_plat_data data_ipq5018 = {
+ .num_sensors = 5,
+ .ops = &ops_ipq5018,
+ .hw_ids = (unsigned int []){0, 1, 2, 3, 4},
+ .feat = &tsens_v1_no_rpm_feat,
.fields = tsens_v1_regfields,
};
diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
index 29a61d2d6ca3..8d9698ea3ec4 100644
--- a/drivers/thermal/qcom/tsens-v2.c
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -4,13 +4,32 @@
* Copyright (c) 2018, Linaro Limited
*/
+#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/nvmem-consumer.h>
#include <linux/regmap.h>
#include "tsens.h"
/* ----- SROT ------ */
#define SROT_HW_VER_OFF 0x0000
#define SROT_CTRL_OFF 0x0004
+#define SROT_MEASURE_PERIOD 0x0008
+#define SROT_Sn_CONVERSION 0x0060
+#define V2_SHIFT_DEFAULT 0x0003
+#define V2_SLOPE_DEFAULT 0x0cd0
+#define V2_CZERO_DEFAULT 0x016a
+#define ONE_PT_SLOPE 0x0cd0
+#define TWO_PT_SHIFTED_GAIN 921600
+#define ONE_PT_CZERO_CONST 94
+#define SW_RST_DEASSERT 0x0
+#define SW_RST_ASSERT 0x1
+#define MEASURE_PERIOD_2mSEC 0x1
+#define RESULT_FORMAT_TEMP 0x1
+#define TSENS_ENABLE 0x1
+#define SENSOR_CONVERSION(n) (((n) * 4) + SROT_Sn_CONVERSION)
+#define CONVERSION_SHIFT_MASK GENMASK(24, 23)
+#define CONVERSION_SLOPE_MASK GENMASK(22, 10)
+#define CONVERSION_CZERO_MASK GENMASK(9, 0)
/* ----- TM ------ */
#define TM_INT_EN_OFF 0x0004
@@ -50,6 +69,17 @@ static struct tsens_features ipq8074_feat = {
.trip_max_temp = 204000,
};
+static struct tsens_features ipq5332_feat = {
+ .ver_major = VER_2_X_NO_RPM,
+ .crit_int = 1,
+ .combo_int = 1,
+ .adc = 0,
+ .srot_split = 1,
+ .max_sensors = 16,
+ .trip_min_temp = 0,
+ .trip_max_temp = 204000,
+};
+
static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
/* ----- SROT ------ */
/* VERSION */
@@ -59,6 +89,10 @@ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
/* CTRL_OFF */
[TSENS_EN] = REG_FIELD(SROT_CTRL_OFF, 0, 0),
[TSENS_SW_RST] = REG_FIELD(SROT_CTRL_OFF, 1, 1),
+ [SENSOR_EN] = REG_FIELD(SROT_CTRL_OFF, 3, 18),
+ [CODE_OR_TEMP] = REG_FIELD(SROT_CTRL_OFF, 21, 21),
+
+ [MAIN_MEASURE_PERIOD] = REG_FIELD(SROT_MEASURE_PERIOD, 0, 7),
/* ----- TM ------ */
/* INTERRUPT ENABLE */
@@ -104,9 +138,132 @@ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
[TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
};
+static int tsens_v2_calibrate_sensor(struct device *dev, struct tsens_sensor *sensor,
+ struct regmap *map, u32 mode, u32 base0, u32 base1)
+{
+ u32 shift = V2_SHIFT_DEFAULT;
+ u32 slope = V2_SLOPE_DEFAULT;
+ u32 czero = V2_CZERO_DEFAULT;
+ char name[20];
+ u32 val;
+ int ret;
+
+ /* Read offset value */
+ ret = snprintf(name, sizeof(name), "tsens_sens%d_off", sensor->hw_id);
+ if (ret < 0)
+ return ret;
+
+ ret = nvmem_cell_read_variable_le_u32(dev, name, &sensor->offset);
+ if (ret)
+ return ret;
+
+ /* Based on calib mode, program SHIFT, SLOPE and CZERO */
+ switch (mode) {
+ case TWO_PT_CALIB:
+ slope = (TWO_PT_SHIFTED_GAIN / (base1 - base0));
+
+ czero = (base0 + sensor->offset - ((base1 - base0) / 3));
+
+ break;
+ case ONE_PT_CALIB2:
+ czero = base0 + sensor->offset - ONE_PT_CZERO_CONST;
+
+ slope = ONE_PT_SLOPE;
+
+ break;
+ default:
+ dev_dbg(dev, "calibrationless mode\n");
+ }
+
+ val = FIELD_PREP(CONVERSION_SHIFT_MASK, shift) |
+ FIELD_PREP(CONVERSION_SLOPE_MASK, slope) |
+ FIELD_PREP(CONVERSION_CZERO_MASK, czero);
+
+ regmap_write(map, SENSOR_CONVERSION(sensor->hw_id), val);
+
+ return 0;
+}
+
+static int tsens_v2_calibration(struct tsens_priv *priv)
+{
+ struct device *dev = priv->dev;
+ u32 mode, base0, base1;
+ int i, ret;
+
+ if (priv->num_sensors > MAX_SENSORS)
+ return -EINVAL;
+
+ ret = nvmem_cell_read_variable_le_u32(priv->dev, "mode", &mode);
+ if (ret == -ENOENT)
+ dev_warn(priv->dev, "Calibration data not present in DT\n");
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ ret = nvmem_cell_read_variable_le_u32(priv->dev, "base0", &base0);
+ if (ret < 0)
+ return ret;
+
+ ret = nvmem_cell_read_variable_le_u32(priv->dev, "base1", &base1);
+ if (ret < 0)
+ return ret;
+
+ /* Calibrate each sensor */
+ for (i = 0; i < priv->num_sensors; i++) {
+ ret = tsens_v2_calibrate_sensor(dev, &priv->sensor[i], priv->srot_map,
+ mode, base0, base1);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __init init_tsens_v2_no_rpm(struct tsens_priv *priv)
+{
+ struct device *dev = priv->dev;
+ int i, ret;
+ u32 val = 0;
+
+ ret = init_common(priv);
+ if (ret < 0)
+ return ret;
+
+ priv->rf[CODE_OR_TEMP] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[CODE_OR_TEMP]);
+ if (IS_ERR(priv->rf[CODE_OR_TEMP]))
+ return PTR_ERR(priv->rf[CODE_OR_TEMP]);
+
+ priv->rf[MAIN_MEASURE_PERIOD] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[MAIN_MEASURE_PERIOD]);
+ if (IS_ERR(priv->rf[MAIN_MEASURE_PERIOD]))
+ return PTR_ERR(priv->rf[MAIN_MEASURE_PERIOD]);
+
+ regmap_field_write(priv->rf[TSENS_SW_RST], SW_RST_ASSERT);
+
+ regmap_field_write(priv->rf[MAIN_MEASURE_PERIOD], MEASURE_PERIOD_2mSEC);
+
+ /* Enable available sensors */
+ for (i = 0; i < priv->num_sensors; i++)
+ val |= 1 << priv->sensor[i].hw_id;
+
+ regmap_field_write(priv->rf[SENSOR_EN], val);
+
+ /* Select temperature format, unit is deci-Celsius */
+ regmap_field_write(priv->rf[CODE_OR_TEMP], RESULT_FORMAT_TEMP);
+
+ regmap_field_write(priv->rf[TSENS_SW_RST], SW_RST_DEASSERT);
+
+ regmap_field_write(priv->rf[TSENS_EN], TSENS_ENABLE);
+
+ return 0;
+}
+
static const struct tsens_ops ops_generic_v2 = {
.init = init_common,
.get_temp = get_temp_tsens_valid,
+ .resume = tsens_resume_common,
};
struct tsens_plat_data data_tsens_v2 = {
@@ -121,6 +278,28 @@ struct tsens_plat_data data_ipq8074 = {
.fields = tsens_v2_regfields,
};
+static const struct tsens_ops ops_ipq5332 = {
+ .init = init_tsens_v2_no_rpm,
+ .get_temp = get_temp_tsens_valid,
+ .calibrate = tsens_v2_calibration,
+};
+
+const struct tsens_plat_data data_ipq5332 = {
+ .num_sensors = 5,
+ .ops = &ops_ipq5332,
+ .hw_ids = (unsigned int []){11, 12, 13, 14, 15},
+ .feat = &ipq5332_feat,
+ .fields = tsens_v2_regfields,
+};
+
+const struct tsens_plat_data data_ipq5424 = {
+ .num_sensors = 7,
+ .ops = &ops_ipq5332,
+ .hw_ids = (unsigned int []){9, 10, 11, 12, 13, 14, 15},
+ .feat = &ipq5332_feat,
+ .fields = tsens_v2_regfields,
+};
+
/* Kept around for backward compatibility with old msm8996.dtsi */
struct tsens_plat_data data_8996 = {
.num_sensors = 13,
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
index 98c356acfe98..a2422ebee816 100644
--- a/drivers/thermal/qcom/tsens.c
+++ b/drivers/thermal/qcom/tsens.c
@@ -17,6 +17,7 @@
#include <linux/pm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
+#include <linux/suspend.h>
#include <linux/thermal.h>
#include "../thermal_hwmon.h"
#include "tsens.h"
@@ -264,7 +265,7 @@ void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
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]);
+ __func__, i, p1[i], p2 ? p2[i] : 0);
if (!priv->sensor[i].slope)
priv->sensor[i].slope = SLOPE_DEFAULT;
@@ -446,7 +447,7 @@ static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
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)
+ if (tsens_version(priv) >= VER_2_X)
tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
else
tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
@@ -498,7 +499,7 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
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) {
+ if (tsens_version(priv) >= VER_2_X) {
ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
if (ret)
return ret;
@@ -542,7 +543,7 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
{
- if (ver > VER_1_X)
+ if (ver >= VER_2_X)
return mask & (1 << hw_id);
/* v1, v0.1 don't have a irq mask register */
@@ -732,7 +733,7 @@ static int tsens_set_trips(struct thermal_zone_device *tz, int low, int high)
static int tsens_enable_irq(struct tsens_priv *priv)
{
int ret;
- int val = tsens_version(priv) > VER_1_X ? 7 : 1;
+ int val = tsens_version(priv) >= VER_2_X ? 7 : 1;
ret = regmap_field_write(priv->rf[INT_EN], val);
if (ret < 0)
@@ -975,9 +976,15 @@ int __init init_common(struct tsens_priv *priv)
if (ret)
goto err_put_device;
if (!enabled) {
- dev_err(dev, "%s: device not enabled\n", __func__);
- ret = -ENODEV;
- goto err_put_device;
+ switch (tsens_version(priv)) {
+ case VER_1_X_NO_RPM:
+ case VER_2_X_NO_RPM:
+ break;
+ default:
+ 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,
@@ -1039,7 +1046,7 @@ int __init init_common(struct tsens_priv *priv)
}
}
- if (tsens_version(priv) > VER_1_X && ver_minor > 2) {
+ if (tsens_version(priv) >= VER_2_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++) {
@@ -1101,6 +1108,15 @@ static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
static const struct of_device_id tsens_table[] = {
{
+ .compatible = "qcom,ipq5018-tsens",
+ .data = &data_ipq5018,
+ }, {
+ .compatible = "qcom,ipq5332-tsens",
+ .data = &data_ipq5332,
+ }, {
+ .compatible = "qcom,ipq5424-tsens",
+ .data = &data_ipq5424,
+ }, {
.compatible = "qcom,ipq8064-tsens",
.data = &data_8960,
}, {
@@ -1119,6 +1135,9 @@ static const struct of_device_id tsens_table[] = {
.compatible = "qcom,msm8916-tsens",
.data = &data_8916,
}, {
+ .compatible = "qcom,msm8937-tsens",
+ .data = &data_8937,
+ }, {
.compatible = "qcom,msm8939-tsens",
.data = &data_8939,
}, {
@@ -1193,6 +1212,36 @@ static int tsens_register_irq(struct tsens_priv *priv, char *irqname,
return ret;
}
+#ifdef CONFIG_SUSPEND
+static int tsens_reinit(struct tsens_priv *priv)
+{
+ if (tsens_version(priv) >= VER_2_X) {
+ /*
+ * Re-enable the watchdog, unmask the bark.
+ * Disable cycle completion monitoring
+ */
+ if (priv->feat->has_watchdog) {
+ regmap_field_write(priv->rf[WDOG_BARK_MASK], 0);
+ regmap_field_write(priv->rf[CC_MON_MASK], 1);
+ }
+
+ /* Re-enable interrupts */
+ tsens_enable_irq(priv);
+ }
+
+ return 0;
+}
+
+int tsens_resume_common(struct tsens_priv *priv)
+{
+ if (pm_suspend_target_state == PM_SUSPEND_MEM)
+ tsens_reinit(priv);
+
+ return 0;
+}
+
+#endif /* !CONFIG_SUSPEND */
+
static int tsens_register(struct tsens_priv *priv)
{
int i, ret;
@@ -1305,11 +1354,9 @@ static int tsens_probe(struct platform_device *pdev)
if (priv->ops->calibrate) {
ret = priv->ops->calibrate(priv);
- if (ret < 0) {
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "%s: calibration failed\n", __func__);
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%s: calibration failed\n",
+ __func__);
}
ret = tsens_register(priv);
@@ -1319,7 +1366,7 @@ static int tsens_probe(struct platform_device *pdev)
return ret;
}
-static int tsens_remove(struct platform_device *pdev)
+static void tsens_remove(struct platform_device *pdev)
{
struct tsens_priv *priv = platform_get_drvdata(pdev);
@@ -1327,8 +1374,6 @@ static int tsens_remove(struct platform_device *pdev)
tsens_disable_irq(priv);
if (priv->ops->disable)
priv->ops->disable(priv);
-
- return 0;
}
static struct platform_driver tsens_driver = {
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
index 2805de1c6827..2a7afa4c899b 100644
--- a/drivers/thermal/qcom/tsens.h
+++ b/drivers/thermal/qcom/tsens.h
@@ -34,7 +34,9 @@ enum tsens_ver {
VER_0 = 0,
VER_0_1,
VER_1_X,
+ VER_1_X_NO_RPM,
VER_2_X,
+ VER_2_X_NO_RPM,
};
enum tsens_irq_type {
@@ -168,6 +170,7 @@ enum regfield_ids {
TSENS_SW_RST,
SENSOR_EN,
CODE_OR_TEMP,
+ MAIN_MEASURE_PERIOD,
/* ----- TM ------ */
/* TRDY */
@@ -585,7 +588,7 @@ struct tsens_priv {
struct dentry *debug_root;
struct dentry *debug;
- struct tsens_sensor sensor[];
+ struct tsens_sensor sensor[] __counted_by(num_sensors);
};
/**
@@ -634,6 +637,11 @@ void compute_intercept_slope(struct tsens_priv *priv, u32 *pt1, u32 *pt2, u32 mo
int init_common(struct tsens_priv *priv);
int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp);
int get_temp_common(const struct tsens_sensor *s, int *temp);
+#ifdef CONFIG_SUSPEND
+int tsens_resume_common(struct tsens_priv *priv);
+#else
+#define tsens_resume_common NULL
+#endif
/* TSENS target */
extern struct tsens_plat_data data_8960;
@@ -642,9 +650,13 @@ extern struct tsens_plat_data data_8960;
extern struct tsens_plat_data data_8226, data_8909, data_8916, data_8939, data_8974, data_9607;
/* TSENS v1 targets */
-extern struct tsens_plat_data data_tsens_v1, data_8976, data_8956;
+extern struct tsens_plat_data data_tsens_v1, data_8937, data_8976, data_8956;
+
+/* TSENS v1 with no RPM targets */
+extern const struct tsens_plat_data data_ipq5018;
/* TSENS v2 targets */
extern struct tsens_plat_data data_8996, data_ipq8074, data_tsens_v2;
+extern const struct tsens_plat_data data_ipq5332, data_ipq5424;
#endif /* __QCOM_TSENS_H__ */
diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c
index ccc2eea7f9f5..01b58be0dcc6 100644
--- a/drivers/thermal/qoriq_thermal.c
+++ b/drivers/thermal/qoriq_thermal.c
@@ -18,6 +18,7 @@
#define SITES_MAX 16
#define TMR_DISABLE 0x0
#define TMR_ME 0x80000000
+#define TMR_CMD BIT(29)
#define TMR_ALPF 0x0c000000
#define TMR_ALPF_V2 0x03000000
#define TMTMIR_DEFAULT 0x0000000f
@@ -57,6 +58,9 @@
#define REGS_TTRnCR(n) (0xf10 + 4 * (n)) /* Temperature Range n
* Control Register
*/
+#define NUM_TTRCR_V1 4
+#define NUM_TTRCR_MAX 16
+
#define REGS_IPBRR(n) (0xbf8 + 4 * (n)) /* IP Block Revision
* Register n
*/
@@ -71,6 +75,7 @@ struct qoriq_sensor {
struct qoriq_tmu_data {
int ver;
+ u32 ttrcr[NUM_TTRCR_MAX];
struct regmap *regmap;
struct clk *clk;
struct qoriq_sensor sensor[SITES_MAX];
@@ -182,17 +187,17 @@ static int qoriq_tmu_calibration(struct device *dev,
struct qoriq_tmu_data *data)
{
int i, val, len;
- u32 range[4];
const u32 *calibration;
struct device_node *np = dev->of_node;
len = of_property_count_u32_elems(np, "fsl,tmu-range");
- if (len < 0 || len > 4) {
+ if (len < 0 || (data->ver == TMU_VER1 && len > NUM_TTRCR_V1) ||
+ (data->ver > TMU_VER1 && len > NUM_TTRCR_MAX)) {
dev_err(dev, "invalid range data.\n");
return len;
}
- val = of_property_read_u32_array(np, "fsl,tmu-range", range, len);
+ val = of_property_read_u32_array(np, "fsl,tmu-range", data->ttrcr, len);
if (val != 0) {
dev_err(dev, "failed to read range data.\n");
return val;
@@ -200,7 +205,7 @@ static int qoriq_tmu_calibration(struct device *dev,
/* Init temperature range registers */
for (i = 0; i < len; i++)
- regmap_write(data->regmap, REGS_TTRnCR(i), range[i]);
+ regmap_write(data->regmap, REGS_TTRnCR(i), data->ttrcr[i]);
calibration = of_get_property(np, "fsl,tmu-calibration", &len);
if (calibration == NULL || len % 8) {
@@ -261,7 +266,6 @@ 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)
@@ -292,38 +296,27 @@ static int qoriq_tmu_probe(struct platform_device *pdev)
base = devm_platform_ioremap_resource(pdev, 0);
ret = PTR_ERR_OR_ZERO(base);
- if (ret) {
- dev_err(dev, "Failed to get memory region\n");
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get memory region\n");
data->regmap = devm_regmap_init_mmio(dev, base, &regmap_config);
ret = PTR_ERR_OR_ZERO(data->regmap);
- if (ret) {
- dev_err(dev, "Failed to init regmap (%d)\n", ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init regmap\n");
- data->clk = devm_clk_get_optional(dev, NULL);
+ data->clk = devm_clk_get_optional_enabled(dev, NULL);
if (IS_ERR(data->clk))
return PTR_ERR(data->clk);
- ret = clk_prepare_enable(data->clk);
- if (ret) {
- dev_err(dev, "Failed to enable clock\n");
- 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) {
- dev_err(&pdev->dev, "Failed to read IP block version\n");
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read IP block version\n");
+
data->ver = (ver >> 8) & 0xff;
qoriq_tmu_init_device(data); /* TMU initialization */
@@ -333,17 +326,15 @@ static int qoriq_tmu_probe(struct platform_device *pdev)
return ret;
ret = qoriq_tmu_register_tmu_zone(dev, data);
- if (ret < 0) {
- dev_err(dev, "Failed to register sensors\n");
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to register sensors\n");
platform_set_drvdata(pdev, data);
return 0;
}
-static int __maybe_unused qoriq_tmu_suspend(struct device *dev)
+static int qoriq_tmu_suspend(struct device *dev)
{
struct qoriq_tmu_data *data = dev_get_drvdata(dev);
int ret;
@@ -352,12 +343,18 @@ static int __maybe_unused qoriq_tmu_suspend(struct device *dev)
if (ret)
return ret;
+ if (data->ver > TMU_VER1) {
+ ret = regmap_set_bits(data->regmap, REGS_TMR, TMR_CMD);
+ if (ret)
+ return ret;
+ }
+
clk_disable_unprepare(data->clk);
return 0;
}
-static int __maybe_unused qoriq_tmu_resume(struct device *dev)
+static int qoriq_tmu_resume(struct device *dev)
{
int ret;
struct qoriq_tmu_data *data = dev_get_drvdata(dev);
@@ -366,12 +363,18 @@ static int __maybe_unused qoriq_tmu_resume(struct device *dev)
if (ret)
return ret;
+ if (data->ver > TMU_VER1) {
+ ret = regmap_clear_bits(data->regmap, REGS_TMR, TMR_CMD);
+ if (ret)
+ return ret;
+ }
+
/* Enable monitoring */
return regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, TMR_ME);
}
-static SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops,
- qoriq_tmu_suspend, qoriq_tmu_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops,
+ qoriq_tmu_suspend, qoriq_tmu_resume);
static const struct of_device_id qoriq_tmu_match[] = {
{ .compatible = "fsl,qoriq-tmu", },
@@ -383,7 +386,7 @@ MODULE_DEVICE_TABLE(of, qoriq_tmu_match);
static struct platform_driver qoriq_tmu = {
.driver = {
.name = "qoriq_thermal",
- .pm = &qoriq_tmu_pm_ops,
+ .pm = pm_sleep_ptr(&qoriq_tmu_pm_ops),
.of_match_table = qoriq_tmu_match,
},
.probe = qoriq_tmu_probe,
diff --git a/drivers/thermal/renesas/Kconfig b/drivers/thermal/renesas/Kconfig
new file mode 100644
index 000000000000..5735c8728a31
--- /dev/null
+++ b/drivers/thermal/renesas/Kconfig
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config RCAR_THERMAL
+ tristate "Renesas R-Car thermal driver"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ Enable this to plug the R-Car thermal sensor driver into the Linux
+ thermal framework.
+
+config RCAR_GEN3_THERMAL
+ tristate "Renesas R-Car Gen3/Gen4 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/Gen4 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 RZG3E_THERMAL
+ tristate "Renesas RZ/G3E thermal driver"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ help
+ Enable this to plug the RZ/G3E thermal sensor driver into the Linux
+ thermal framework.
+
+config RZG3S_THERMAL
+ tristate "Renesas RZ/G3S thermal driver"
+ depends on ARCH_R9A08G045 || COMPILE_TEST
+ depends on OF && IIO && RZG2L_ADC
+ help
+ Enable this to plug the RZ/G3S thermal sensor driver into the Linux
+ thermal framework.
diff --git a/drivers/thermal/renesas/Makefile b/drivers/thermal/renesas/Makefile
new file mode 100644
index 000000000000..8f5ae9af277c
--- /dev/null
+++ b/drivers/thermal/renesas/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_RCAR_GEN3_THERMAL) += rcar_gen3_thermal.o
+obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
+obj-$(CONFIG_RZG2L_THERMAL) += rzg2l_thermal.o
+obj-$(CONFIG_RZG3E_THERMAL) += rzg3e_thermal.o
+obj-$(CONFIG_RZG3S_THERMAL) += rzg3s_thermal.o
diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/renesas/rcar_gen3_thermal.c
index 9029d01e029b..94804816e9e1 100644
--- a/drivers/thermal/rcar_gen3_thermal.c
+++ b/drivers/thermal/renesas/rcar_gen3_thermal.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * R-Car Gen3 THS thermal sensor driver
+ * R-Car Gen3, Gen4 and RZ/G2 THS thermal sensor driver
* Based on rcar_thermal.c and work from Hien Dang and Khiem Nguyen.
*
* Copyright (C) 2016 Renesas Electronics Corporation.
@@ -11,21 +11,21 @@
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/thermal.h>
-#include "thermal_hwmon.h"
+#include "../thermal_hwmon.h"
/* Register offsets */
#define REG_GEN3_IRQSTR 0x04
#define REG_GEN3_IRQMSK 0x08
-#define REG_GEN3_IRQCTL 0x0C
+#define REG_GEN3_IRQCTL 0x0c
#define REG_GEN3_IRQEN 0x10
#define REG_GEN3_IRQTEMP1 0x14
#define REG_GEN3_IRQTEMP2 0x18
-#define REG_GEN3_IRQTEMP3 0x1C
+#define REG_GEN3_IRQTEMP3 0x1c
#define REG_GEN3_THCTR 0x20
#define REG_GEN3_TEMP 0x28
#define REG_GEN3_THCODE1 0x50
@@ -38,9 +38,9 @@
#define REG_GEN4_THSFMON00 0x180
#define REG_GEN4_THSFMON01 0x184
#define REG_GEN4_THSFMON02 0x188
-#define REG_GEN4_THSFMON15 0x1BC
-#define REG_GEN4_THSFMON16 0x1C0
-#define REG_GEN4_THSFMON17 0x1C4
+#define REG_GEN4_THSFMON15 0x1bc
+#define REG_GEN4_THSFMON16 0x1c0
+#define REG_GEN4_THSFMON17 0x1c4
/* IRQ{STR,MSK,EN} bits */
#define IRQ_TEMP1 BIT(0)
@@ -57,34 +57,49 @@
/* THSCP bits */
#define THSCP_COR_PARA_VLD (BIT(15) | BIT(14))
-#define CTEMP_MASK 0xFFF
+#define CTEMP_MASK 0xfff
#define MCELSIUS(temp) ((temp) * 1000)
-#define GEN3_FUSE_MASK 0xFFF
-#define GEN4_FUSE_MASK 0xFFF
+#define GEN3_FUSE_MASK 0xfff
+#define GEN4_FUSE_MASK 0xfff
#define TSC_MAX_NUM 5
-/* Structure for thermal temperature calculation */
-struct equation_coefs {
- int a1;
- int b1;
- int a2;
- int b2;
+struct rcar_gen3_thermal_priv;
+
+struct rcar_gen3_thermal_fuse_info {
+ u32 ptat[3];
+ u32 thcode[3];
+ u32 mask;
};
-struct rcar_gen3_thermal_priv;
+struct rcar_gen3_thermal_fuse_default {
+ u32 ptat[3];
+ u32 thcodes[TSC_MAX_NUM][3];
+};
struct rcar_thermal_info {
- int ths_tj_1;
- void (*read_fuses)(struct rcar_gen3_thermal_priv *priv);
+ int scale;
+ int adj_below;
+ int adj_above;
+ const struct rcar_gen3_thermal_fuse_info *fuses;
+ const struct rcar_gen3_thermal_fuse_default *fuse_defaults;
+};
+
+struct equation_set_coef {
+ int a;
+ int b;
};
struct rcar_gen3_thermal_tsc {
+ struct rcar_gen3_thermal_priv *priv;
void __iomem *base;
struct thermal_zone_device *zone;
- struct equation_coefs coef;
- int tj_t;
+ /* Different coefficients are used depending on a threshold. */
+ struct {
+ struct equation_set_coef below;
+ struct equation_set_coef above;
+ } coef;
int thcode[3];
};
@@ -93,6 +108,7 @@ struct rcar_gen3_thermal_priv {
struct thermal_zone_device_ops ops;
unsigned int num_tscs;
int ptat[3];
+ int tj_t;
const struct rcar_thermal_info *info;
};
@@ -111,84 +127,75 @@ static inline void rcar_gen3_thermal_write(struct rcar_gen3_thermal_tsc *tsc,
/*
* Linear approximation for temperature
*
- * [reg] = [temp] * a + b => [temp] = ([reg] - b) / a
+ * [temp] = ((thadj - [reg]) * a) / b + adj
+ * [reg] = thadj - ([temp] - adj) * b / a
*
* The constants a and b are calculated using two triplets of int values PTAT
* and THCODE. PTAT and THCODE can either be read from hardware or use hard
- * coded values from driver. The formula to calculate a and b are taken from
- * BSP and sparsely documented and understood.
+ * coded values from the driver. The formula to calculate a and b are taken from
+ * the datasheet. Different calculations are needed for a and b depending on
+ * if the input variables ([temp] or [reg]) are above or below a threshold. The
+ * threshold is also calculated from PTAT and THCODE using formulas from the
+ * datasheet.
+ *
+ * The constant thadj is one of the THCODE values, which one to use depends on
+ * the threshold and input value.
*
- * Examining the linear formula and the formula used to calculate constants a
- * and b while knowing that the span for PTAT and THCODE values are between
- * 0x000 and 0xfff the largest integer possible is 0xfff * 0xfff == 0xffe001.
- * Integer also needs to be signed so that leaves 7 bits for binary
- * fixed point scaling.
+ * The constants adj is taken verbatim from the datasheet. Two values exists,
+ * which one to use depends on the input value and the calculated threshold.
+ * Furthermore different SoC models supported by the driver have different sets
+ * of values. The values for each model are stored in the device match data.
*/
-#define FIXPT_SHIFT 7
-#define FIXPT_INT(_x) ((_x) << FIXPT_SHIFT)
-#define INT_FIXPT(_x) ((_x) >> FIXPT_SHIFT)
-#define FIXPT_DIV(_a, _b) DIV_ROUND_CLOSEST(((_a) << FIXPT_SHIFT), (_b))
-#define FIXPT_TO_MCELSIUS(_x) ((_x) * 1000 >> FIXPT_SHIFT)
-
-#define RCAR3_THERMAL_GRAN 500 /* mili Celsius */
-
-/* no idea where these constants come from */
-#define TJ_3 -41
-
-static void rcar_gen3_thermal_calc_coefs(struct rcar_gen3_thermal_priv *priv,
- struct rcar_gen3_thermal_tsc *tsc,
- int ths_tj_1)
+static void rcar_gen3_thermal_shared_coefs(struct rcar_gen3_thermal_priv *priv)
{
- /* TODO: Find documentation and document constant calculation formula */
-
- /*
- * 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((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(tsc->thcode[1] - tsc->thcode[2]),
- tsc->tj_t - FIXPT_INT(TJ_3));
- tsc->coef.b1 = FIXPT_INT(tsc->thcode[2]) - tsc->coef.a1 * TJ_3;
-
- 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(tsc->thcode[0]) - tsc->coef.a2 * ths_tj_1;
+ priv->tj_t =
+ DIV_ROUND_CLOSEST((priv->ptat[1] - priv->ptat[2]) * priv->info->scale,
+ priv->ptat[0] - priv->ptat[2])
+ + priv->info->adj_below;
}
-
-static int rcar_gen3_thermal_round(int temp)
+static void rcar_gen3_thermal_tsc_coefs(struct rcar_gen3_thermal_priv *priv,
+ struct rcar_gen3_thermal_tsc *tsc)
{
- int result, round_offs;
+ tsc->coef.below.a = priv->info->scale * (priv->ptat[2] - priv->ptat[1]);
+ tsc->coef.above.a = priv->info->scale * (priv->ptat[0] - priv->ptat[1]);
- round_offs = temp >= 0 ? RCAR3_THERMAL_GRAN / 2 :
- -RCAR3_THERMAL_GRAN / 2;
- result = (temp + round_offs) / RCAR3_THERMAL_GRAN;
- return result * RCAR3_THERMAL_GRAN;
+ tsc->coef.below.b = (priv->ptat[2] - priv->ptat[0]) * (tsc->thcode[2] - tsc->thcode[1]);
+ tsc->coef.above.b = (priv->ptat[0] - priv->ptat[2]) * (tsc->thcode[1] - tsc->thcode[0]);
}
static int rcar_gen3_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct rcar_gen3_thermal_tsc *tsc = thermal_zone_device_priv(tz);
- int mcelsius, val;
- int reg;
+ struct rcar_gen3_thermal_priv *priv = tsc->priv;
+ const struct equation_set_coef *coef;
+ int adj, decicelsius, reg, thcode;
- /* Read register and convert to mili Celsius */
+ /* Read register and convert to millidegree Celsius */
reg = rcar_gen3_thermal_read(tsc, REG_GEN3_TEMP) & CTEMP_MASK;
- if (reg <= tsc->thcode[1])
- val = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b1,
- tsc->coef.a1);
- else
- val = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b2,
- tsc->coef.a2);
- mcelsius = FIXPT_TO_MCELSIUS(val);
+ if (reg < tsc->thcode[1]) {
+ adj = priv->info->adj_below;
+ coef = &tsc->coef.below;
+ thcode = tsc->thcode[2];
+ } else {
+ adj = priv->info->adj_above;
+ coef = &tsc->coef.above;
+ thcode = tsc->thcode[0];
+ }
+
+ /*
+ * The dividend can't be grown as it might overflow, instead shorten the
+ * divisor to convert to decidegree Celsius. If we convert after the
+ * division precision is lost as we will scale up from whole degrees
+ * Celsius.
+ */
+ decicelsius = DIV_ROUND_CLOSEST(coef->a * (thcode - reg), coef->b / 10);
/* Guaranteed operating range is -40C to 125C. */
- /* Round value to device granularity setting */
- *temp = rcar_gen3_thermal_round(mcelsius);
+ /* Reporting is done in millidegree Celsius */
+ *temp = decicelsius * 100 + adj * 1000;
return 0;
}
@@ -196,15 +203,22 @@ static int rcar_gen3_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
static int rcar_gen3_thermal_mcelsius_to_temp(struct rcar_gen3_thermal_tsc *tsc,
int mcelsius)
{
- int celsius, val;
+ struct rcar_gen3_thermal_priv *priv = tsc->priv;
+ const struct equation_set_coef *coef;
+ int adj, celsius, thcode;
celsius = DIV_ROUND_CLOSEST(mcelsius, 1000);
- if (celsius <= INT_FIXPT(tsc->tj_t))
- val = celsius * tsc->coef.a1 + tsc->coef.b1;
- else
- val = celsius * tsc->coef.a2 + tsc->coef.b2;
+ if (celsius < priv->tj_t) {
+ coef = &tsc->coef.below;
+ adj = priv->info->adj_below;
+ thcode = tsc->thcode[2];
+ } else {
+ coef = &tsc->coef.above;
+ adj = priv->info->adj_above;
+ thcode = tsc->thcode[0];
+ }
- return INT_FIXPT(val);
+ return thcode - DIV_ROUND_CLOSEST((celsius - adj) * coef->b, coef->a);
}
static int rcar_gen3_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
@@ -251,96 +265,62 @@ static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data)
return IRQ_HANDLED;
}
-static void rcar_gen3_thermal_read_fuses_gen3(struct rcar_gen3_thermal_priv *priv)
-{
- unsigned int i;
-
- /*
- * 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;
- }
-}
-
-static void rcar_gen3_thermal_read_fuses_gen4(struct rcar_gen3_thermal_priv *priv)
+static void rcar_gen3_thermal_fetch_fuses(struct rcar_gen3_thermal_priv *priv)
{
- unsigned int i;
+ const struct rcar_gen3_thermal_fuse_info *fuses = priv->info->fuses;
/*
* 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_GEN4_THSFMON16) &
- GEN4_FUSE_MASK;
- priv->ptat[1] = rcar_gen3_thermal_read(priv->tscs[0], REG_GEN4_THSFMON17) &
- GEN4_FUSE_MASK;
- priv->ptat[2] = rcar_gen3_thermal_read(priv->tscs[0], REG_GEN4_THSFMON15) &
- GEN4_FUSE_MASK;
-
- for (i = 0; i < priv->num_tscs; i++) {
+ priv->ptat[0] = rcar_gen3_thermal_read(priv->tscs[0], fuses->ptat[0])
+ & fuses->mask;
+ priv->ptat[1] = rcar_gen3_thermal_read(priv->tscs[0], fuses->ptat[1])
+ & fuses->mask;
+ priv->ptat[2] = rcar_gen3_thermal_read(priv->tscs[0], fuses->ptat[2])
+ & fuses->mask;
+
+ for (unsigned int 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_GEN4_THSFMON01) &
- GEN4_FUSE_MASK;
- tsc->thcode[1] = rcar_gen3_thermal_read(tsc, REG_GEN4_THSFMON02) &
- GEN4_FUSE_MASK;
- tsc->thcode[2] = rcar_gen3_thermal_read(tsc, REG_GEN4_THSFMON00) &
- GEN4_FUSE_MASK;
+ tsc->thcode[0] = rcar_gen3_thermal_read(tsc, fuses->thcode[0])
+ & fuses->mask;
+ tsc->thcode[1] = rcar_gen3_thermal_read(tsc, fuses->thcode[1])
+ & fuses->mask;
+ tsc->thcode[2] = rcar_gen3_thermal_read(tsc, fuses->thcode[2])
+ & fuses->mask;
}
}
static bool rcar_gen3_thermal_read_fuses(struct rcar_gen3_thermal_priv *priv)
{
+ const struct rcar_gen3_thermal_fuse_default *fuse_defaults = priv->info->fuse_defaults;
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 (!priv->info->read_fuses ||
+ if (!priv->info->fuses ||
(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;
+ priv->ptat[0] = fuse_defaults->ptat[0];
+ priv->ptat[1] = fuse_defaults->ptat[1];
+ priv->ptat[2] = fuse_defaults->ptat[2];
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];
+ tsc->thcode[0] = fuse_defaults->thcodes[i][0];
+ tsc->thcode[1] = fuse_defaults->thcodes[i][1];
+ tsc->thcode[2] = fuse_defaults->thcodes[i][2];
}
return false;
}
- priv->info->read_fuses(priv);
+ rcar_gen3_thermal_fetch_fuses(priv);
+
return true;
}
@@ -368,19 +348,69 @@ static void rcar_gen3_thermal_init(struct rcar_gen3_thermal_priv *priv,
usleep_range(1000, 2000);
}
+static const struct rcar_gen3_thermal_fuse_info rcar_gen3_thermal_fuse_info_gen3 = {
+ .ptat = { REG_GEN3_PTAT1, REG_GEN3_PTAT2, REG_GEN3_PTAT3 },
+ .thcode = { REG_GEN3_THCODE1, REG_GEN3_THCODE2, REG_GEN3_THCODE3 },
+ .mask = GEN3_FUSE_MASK,
+};
+
+static const struct rcar_gen3_thermal_fuse_info rcar_gen3_thermal_fuse_info_gen4 = {
+ .ptat = { REG_GEN4_THSFMON16, REG_GEN4_THSFMON17, REG_GEN4_THSFMON15 },
+ .thcode = { REG_GEN4_THSFMON01, REG_GEN4_THSFMON02, REG_GEN4_THSFMON00 },
+ .mask = GEN4_FUSE_MASK,
+};
+
+static const struct rcar_gen3_thermal_fuse_default rcar_gen3_thermal_fuse_default_info_gen3 = {
+ .ptat = { 2631, 1509, 435 },
+ .thcodes = {
+ { 3397, 2800, 2221 },
+ { 3393, 2795, 2216 },
+ { 3389, 2805, 2237 },
+ { 3415, 2694, 2195 },
+ { 3356, 2724, 2244 },
+ },
+};
+
+static const struct rcar_gen3_thermal_fuse_default rcar_gen3_thermal_fuse_default_info_gen4 = {
+ .ptat = { 3274, 2164, 985 },
+ .thcodes = { /* All four THS units share the same trimming */
+ { 3218, 2617, 1980 },
+ { 3218, 2617, 1980 },
+ { 3218, 2617, 1980 },
+ { 3218, 2617, 1980 },
+ }
+};
+
static const struct rcar_thermal_info rcar_m3w_thermal_info = {
- .ths_tj_1 = 116,
- .read_fuses = rcar_gen3_thermal_read_fuses_gen3,
+ .scale = 157,
+ .adj_below = -41,
+ .adj_above = 116,
+ .fuses = &rcar_gen3_thermal_fuse_info_gen3,
+ .fuse_defaults = &rcar_gen3_thermal_fuse_default_info_gen3,
};
static const struct rcar_thermal_info rcar_gen3_thermal_info = {
- .ths_tj_1 = 126,
- .read_fuses = rcar_gen3_thermal_read_fuses_gen3,
+ .scale = 167,
+ .adj_below = -41,
+ .adj_above = 126,
+ .fuses = &rcar_gen3_thermal_fuse_info_gen3,
+ .fuse_defaults = &rcar_gen3_thermal_fuse_default_info_gen3,
+};
+
+static const struct rcar_thermal_info rcar_s4_thermal_info = {
+ .scale = 167,
+ .adj_below = -41,
+ .adj_above = 126,
+ .fuses = &rcar_gen3_thermal_fuse_info_gen4,
+ .fuse_defaults = &rcar_gen3_thermal_fuse_default_info_gen3,
};
static const struct rcar_thermal_info rcar_gen4_thermal_info = {
- .ths_tj_1 = 126,
- .read_fuses = rcar_gen3_thermal_read_fuses_gen4,
+ .scale = 167,
+ .adj_below = -41,
+ .adj_above = 126,
+ .fuses = &rcar_gen3_thermal_fuse_info_gen4,
+ .fuse_defaults = &rcar_gen3_thermal_fuse_default_info_gen4,
};
static const struct of_device_id rcar_gen3_thermal_dt_ids[] = {
@@ -422,24 +452,26 @@ static const struct of_device_id rcar_gen3_thermal_dt_ids[] = {
},
{
.compatible = "renesas,r8a779f0-thermal",
- .data = &rcar_gen4_thermal_info,
+ .data = &rcar_s4_thermal_info,
},
{
.compatible = "renesas,r8a779g0-thermal",
.data = &rcar_gen4_thermal_info,
},
+ {
+ .compatible = "renesas,r8a779h0-thermal",
+ .data = &rcar_gen4_thermal_info,
+ },
{},
};
MODULE_DEVICE_TABLE(of, rcar_gen3_thermal_dt_ids);
-static int rcar_gen3_thermal_remove(struct platform_device *pdev)
+static void rcar_gen3_thermal_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
pm_runtime_put(dev);
pm_runtime_disable(dev);
-
- return 0;
}
static void rcar_gen3_hwmon_action(void *data)
@@ -514,6 +546,7 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
goto error_unregister;
}
+ tsc->priv = priv;
tsc->base = devm_ioremap_resource(dev, res);
if (IS_ERR(tsc->base)) {
ret = PTR_ERR(tsc->base);
@@ -528,11 +561,13 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
if (!rcar_gen3_thermal_read_fuses(priv))
dev_info(dev, "No calibration values fused, fallback to driver values\n");
+ rcar_gen3_thermal_shared_coefs(priv);
+
for (i = 0; i < priv->num_tscs; i++) {
struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
rcar_gen3_thermal_init(priv, tsc);
- rcar_gen3_thermal_calc_coefs(priv, tsc, priv->info->ths_tj_1);
+ rcar_gen3_thermal_tsc_coefs(priv, tsc);
zone = devm_thermal_of_zone_register(dev, i, tsc, &priv->ops);
if (IS_ERR(zone)) {
@@ -550,11 +585,7 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev)
if (ret)
goto error_unregister;
- ret = thermal_zone_get_num_trips(tsc->zone);
- if (ret < 0)
- goto error_unregister;
-
- dev_info(dev, "Sensor %u: Loaded %d trip points\n", i, ret);
+ dev_info(dev, "Sensor %u: Loaded\n", i);
}
if (!priv->num_tscs) {
@@ -570,7 +601,7 @@ error_unregister:
return ret;
}
-static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev)
+static int rcar_gen3_thermal_resume(struct device *dev)
{
struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
unsigned int i;
@@ -584,13 +615,13 @@ static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev)
return 0;
}
-static SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops, NULL,
- rcar_gen3_thermal_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops, NULL,
+ rcar_gen3_thermal_resume);
static struct platform_driver rcar_gen3_thermal_driver = {
.driver = {
.name = "rcar_gen3_thermal",
- .pm = &rcar_gen3_thermal_pm_ops,
+ .pm = pm_sleep_ptr(&rcar_gen3_thermal_pm_ops),
.of_match_table = rcar_gen3_thermal_dt_ids,
},
.probe = rcar_gen3_thermal_probe,
diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/renesas/rcar_thermal.c
index b8571f7090aa..6e5dcac5d47a 100644
--- a/drivers/thermal/rcar_thermal.c
+++ b/drivers/thermal/renesas/rcar_thermal.c
@@ -11,7 +11,7 @@
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reboot.h>
@@ -19,7 +19,7 @@
#include <linux/spinlock.h>
#include <linux/thermal.h>
-#include "thermal_hwmon.h"
+#include "../thermal_hwmon.h"
#define IDLE_INTERVAL 5000
@@ -277,7 +277,7 @@ static int rcar_thermal_get_temp(struct thermal_zone_device *zone, int *temp)
return rcar_thermal_get_current_temp(priv, temp);
}
-static struct thermal_zone_device_ops rcar_thermal_zone_ops = {
+static const struct thermal_zone_device_ops rcar_thermal_zone_ops = {
.get_temp = rcar_thermal_get_temp,
};
@@ -371,7 +371,7 @@ static irqreturn_t rcar_thermal_irq(int irq, void *data)
/*
* platform functions
*/
-static int rcar_thermal_remove(struct platform_device *pdev)
+static void rcar_thermal_remove(struct platform_device *pdev)
{
struct rcar_thermal_common *common = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
@@ -388,8 +388,6 @@ static int rcar_thermal_remove(struct platform_device *pdev)
pm_runtime_put(dev);
pm_runtime_disable(dev);
-
- return 0;
}
static int rcar_thermal_probe(struct platform_device *pdev)
@@ -449,7 +447,7 @@ static int rcar_thermal_probe(struct platform_device *pdev)
ret = devm_request_irq(dev, irq, rcar_thermal_irq,
IRQF_SHARED, dev_name(dev), common);
if (ret) {
- dev_err(dev, "irq request failed\n ");
+ dev_err(dev, "irq request failed\n");
goto error_unregister;
}
@@ -491,7 +489,7 @@ static int rcar_thermal_probe(struct platform_device *pdev)
&rcar_thermal_zone_ops);
} else {
priv->zone = thermal_zone_device_register_with_trips(
- "rcar_thermal", trips, ARRAY_SIZE(trips), 0, priv,
+ "rcar_thermal", trips, ARRAY_SIZE(trips), priv,
&rcar_thermal_zone_ops, NULL, 0,
idle);
@@ -536,7 +534,6 @@ error_unregister:
return ret;
}
-#ifdef CONFIG_PM_SLEEP
static int rcar_thermal_suspend(struct device *dev)
{
struct rcar_thermal_common *common = dev_get_drvdata(dev);
@@ -569,15 +566,14 @@ static int rcar_thermal_resume(struct device *dev)
return 0;
}
-#endif
-static SIMPLE_DEV_PM_OPS(rcar_thermal_pm_ops, rcar_thermal_suspend,
- rcar_thermal_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(rcar_thermal_pm_ops, rcar_thermal_suspend,
+ rcar_thermal_resume);
static struct platform_driver rcar_thermal_driver = {
.driver = {
.name = "rcar_thermal",
- .pm = &rcar_thermal_pm_ops,
+ .pm = pm_sleep_ptr(&rcar_thermal_pm_ops),
.of_match_table = rcar_thermal_dt_ids,
},
.probe = rcar_thermal_probe,
diff --git a/drivers/thermal/rzg2l_thermal.c b/drivers/thermal/renesas/rzg2l_thermal.c
index b56981f85306..b588be628640 100644
--- a/drivers/thermal/rzg2l_thermal.c
+++ b/drivers/thermal/renesas/rzg2l_thermal.c
@@ -9,15 +9,15 @@
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/math.h>
+#include <linux/mod_devicetable.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"
+#include "../thermal_hwmon.h"
#define CTEMP_MASK 0xFFF
@@ -150,14 +150,12 @@ static void rzg2l_thermal_reset_assert_pm_disable_put(struct platform_device *pd
reset_control_assert(priv->rstc);
}
-static int rzg2l_thermal_remove(struct platform_device *pdev)
+static void 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)
diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c
new file mode 100644
index 000000000000..e66d73ca6752
--- /dev/null
+++ b/drivers/thermal/renesas/rzg3e_thermal.c
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G3E TSU Temperature Sensor Unit
+ *
+ * Copyright (C) 2025 Renesas Electronics Corporation
+ */
+#include <linux/clk.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "../thermal_hwmon.h"
+
+/* TSU Register offsets and bits */
+#define TSU_SSUSR 0x00
+#define TSU_SSUSR_EN_TS BIT(0)
+#define TSU_SSUSR_ADC_PD_TS BIT(1)
+#define TSU_SSUSR_SOC_TS_EN BIT(2)
+
+#define TSU_STRGR 0x04
+#define TSU_STRGR_ADST BIT(0)
+
+#define TSU_SOSR1 0x08
+#define TSU_SOSR1_ADCT_8 0x03
+#define TSU_SOSR1_ADCS BIT(4)
+#define TSU_SOSR1_OUTSEL BIT(9)
+
+#define TSU_SCRR 0x10
+#define TSU_SCRR_OUT12BIT_TS GENMASK(11, 0)
+
+#define TSU_SSR 0x14
+#define TSU_SSR_CONV BIT(0)
+
+#define TSU_CMSR 0x18
+#define TSU_CMSR_CMPEN BIT(0)
+
+#define TSU_LLSR 0x1C
+#define TSU_ULSR 0x20
+
+#define TSU_SISR 0x30
+#define TSU_SISR_ADF BIT(0)
+#define TSU_SISR_CMPF BIT(1)
+
+#define TSU_SIER 0x34
+#define TSU_SIER_CMPIE BIT(1)
+
+#define TSU_SICR 0x38
+#define TSU_SICR_ADCLR BIT(0)
+#define TSU_SICR_CMPCLR BIT(1)
+
+/* Temperature calculation constants from datasheet */
+#define TSU_TEMP_D (-41)
+#define TSU_TEMP_E 126
+#define TSU_CODE_MAX 0xFFF
+
+/* Timing specifications from datasheet */
+#define TSU_POWERUP_TIME_US 120 /* 120T at 1MHz sensor clock per datasheet */
+#define TSU_CONV_TIME_US 50 /* Per sample conversion time */
+#define TSU_POLL_DELAY_US 10 /* Polling interval */
+#define TSU_MIN_CLOCK_RATE 24000000 /* TSU_PCLK minimum 24MHz */
+
+/**
+ * struct rzg3e_thermal_priv - RZ/G3E TSU private data
+ * @base: TSU register base
+ * @dev: device pointer
+ * @syscon: regmap for calibration values
+ * @zone: thermal zone device
+ * @rstc: reset control
+ * @trmval0: calibration value 0 (b)
+ * @trmval1: calibration value 1 (c)
+ * @trim_offset: offset for trim registers in syscon
+ * @lock: protects hardware access during conversions
+ */
+struct rzg3e_thermal_priv {
+ void __iomem *base;
+ struct device *dev;
+ struct regmap *syscon;
+ struct thermal_zone_device *zone;
+ struct reset_control *rstc;
+ u16 trmval0;
+ u16 trmval1;
+ u32 trim_offset;
+ struct mutex lock;
+};
+
+static int rzg3e_thermal_power_on(struct rzg3e_thermal_priv *priv)
+{
+ u32 val;
+ int ret;
+
+ /* Clear any pending interrupts */
+ writel(TSU_SICR_ADCLR | TSU_SICR_CMPCLR, priv->base + TSU_SICR);
+
+ /* Disable all interrupts during setup */
+ writel(0, priv->base + TSU_SIER);
+
+ /*
+ * Power-on sequence per datasheet 7.11.9.1:
+ * SOC_TS_EN must be set at same time or before EN_TS and ADC_PD_TS
+ */
+ val = TSU_SSUSR_SOC_TS_EN | TSU_SSUSR_EN_TS;
+ writel(val, priv->base + TSU_SSUSR);
+
+ /* Wait for sensor stabilization per datasheet 7.11.7.1 */
+ usleep_range(TSU_POWERUP_TIME_US, TSU_POWERUP_TIME_US + 10);
+
+ /* Configure for average mode with 8 samples */
+ val = TSU_SOSR1_OUTSEL | TSU_SOSR1_ADCT_8;
+ writel(val, priv->base + TSU_SOSR1);
+
+ /* Ensure we're in single scan mode (default) */
+ val = readl(priv->base + TSU_SOSR1);
+ if (val & TSU_SOSR1_ADCS) {
+ dev_err(priv->dev, "Invalid scan mode setting\n");
+ return -EINVAL;
+ }
+
+ /* Wait for any ongoing conversion to complete */
+ ret = readl_poll_timeout(priv->base + TSU_SSR, val,
+ !(val & TSU_SSR_CONV),
+ TSU_POLL_DELAY_US,
+ USEC_PER_MSEC);
+ if (ret) {
+ dev_err(priv->dev, "Timeout waiting for conversion\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rzg3e_thermal_power_off(struct rzg3e_thermal_priv *priv)
+{
+ /* Disable all interrupts */
+ writel(0, priv->base + TSU_SIER);
+
+ /* Clear pending interrupts */
+ writel(TSU_SICR_ADCLR | TSU_SICR_CMPCLR, priv->base + TSU_SICR);
+
+ /* Power down sequence per datasheet */
+ writel(TSU_SSUSR_ADC_PD_TS, priv->base + TSU_SSUSR);
+}
+
+/*
+ * Convert 12-bit sensor code to temperature in millicelsius
+ * Formula from datasheet 7.11.7.8:
+ * T(°C) = ((e - d) / (c - b)) * (a - b) + d
+ * where: a = sensor code, b = trmval0, c = trmval1, d = -41, e = 126
+ */
+static int rzg3e_thermal_code_to_temp(struct rzg3e_thermal_priv *priv, u16 code)
+{
+ int temp_e_mc = TSU_TEMP_E * MILLIDEGREE_PER_DEGREE;
+ int temp_d_mc = TSU_TEMP_D * MILLIDEGREE_PER_DEGREE;
+ s64 numerator, denominator;
+ int temp_mc;
+
+ numerator = (temp_e_mc - temp_d_mc) * (s64)(code - priv->trmval0);
+ denominator = priv->trmval1 - priv->trmval0;
+
+ temp_mc = div64_s64(numerator, denominator) + temp_d_mc;
+
+ return clamp(temp_mc, temp_d_mc, temp_e_mc);
+}
+
+/*
+ * Convert temperature in millicelsius to 12-bit sensor code
+ * Formula from datasheet 7.11.7.9 (inverse of above)
+ */
+static u16 rzg3e_thermal_temp_to_code(struct rzg3e_thermal_priv *priv, int temp_mc)
+{
+ int temp_e_mc = TSU_TEMP_E * MILLIDEGREE_PER_DEGREE;
+ int temp_d_mc = TSU_TEMP_D * MILLIDEGREE_PER_DEGREE;
+ s64 numerator, denominator;
+ s64 code;
+
+ numerator = (temp_mc - temp_d_mc) * (priv->trmval1 - priv->trmval0);
+ denominator = temp_e_mc - temp_d_mc;
+
+ code = div64_s64(numerator, denominator) + priv->trmval0;
+
+ return clamp_val(code, 0, TSU_CODE_MAX);
+}
+
+static int rzg3e_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct rzg3e_thermal_priv *priv = thermal_zone_device_priv(tz);
+ u32 status, code;
+ int ret, timeout;
+
+ ret = pm_runtime_resume_and_get(priv->dev);
+ if (ret < 0)
+ return ret;
+
+ guard(mutex)(&priv->lock);
+
+ /* Clear any previous conversion status */
+ writel(TSU_SICR_ADCLR, priv->base + TSU_SICR);
+
+ /* Start single conversion */
+ writel(TSU_STRGR_ADST, priv->base + TSU_STRGR);
+
+ /* Wait for conversion completion - 8 samples at ~50us each */
+ timeout = TSU_CONV_TIME_US * 8 * 2; /* Double for margin */
+ ret = readl_poll_timeout(priv->base + TSU_SISR, status,
+ status & TSU_SISR_ADF,
+ TSU_POLL_DELAY_US, timeout);
+ if (ret) {
+ dev_err(priv->dev, "Conversion timeout (status=0x%08x)\n", status);
+ goto out;
+ }
+
+ /* Read the averaged result and clear the complete flag */
+ code = readl(priv->base + TSU_SCRR) & TSU_SCRR_OUT12BIT_TS;
+ writel(TSU_SICR_ADCLR, priv->base + TSU_SICR);
+
+ /* Convert to temperature */
+ *temp = rzg3e_thermal_code_to_temp(priv, code);
+
+ dev_dbg(priv->dev, "temp=%d mC (%d.%03d°C), code=0x%03x\n",
+ *temp, *temp / 1000, abs(*temp) % 1000, code);
+
+out:
+ pm_runtime_mark_last_busy(priv->dev);
+ pm_runtime_put_autosuspend(priv->dev);
+ return ret;
+}
+
+static int rzg3e_thermal_set_trips(struct thermal_zone_device *tz,
+ int low, int high)
+{
+ struct rzg3e_thermal_priv *priv = thermal_zone_device_priv(tz);
+ u16 low_code, high_code;
+ u32 val;
+ int ret;
+
+ /* Hardware requires low < high */
+ if (low >= high)
+ return -EINVAL;
+
+ ret = pm_runtime_resume_and_get(priv->dev);
+ if (ret < 0)
+ return ret;
+
+ guard(mutex)(&priv->lock);
+
+ /* Convert temperatures to codes */
+ low_code = rzg3e_thermal_temp_to_code(priv, low);
+ high_code = rzg3e_thermal_temp_to_code(priv, high);
+
+ dev_dbg(priv->dev, "set_trips: low=%d high=%d (codes: 0x%03x/0x%03x)\n",
+ low, high, low_code, high_code);
+
+ /* Disable comparison during reconfiguration */
+ writel(0, priv->base + TSU_SIER);
+ writel(0, priv->base + TSU_CMSR);
+
+ /* Clear any pending comparison interrupts */
+ writel(TSU_SICR_CMPCLR, priv->base + TSU_SICR);
+
+ /* Set trip points */
+ writel(low_code, priv->base + TSU_LLSR);
+ writel(high_code, priv->base + TSU_ULSR);
+
+ /*
+ * Ensure OUTSEL is set for comparison per datasheet 7.11.7.4
+ * Comparison uses averaged data
+ */
+ val = readl(priv->base + TSU_SOSR1);
+ val |= TSU_SOSR1_OUTSEL;
+ writel(val, priv->base + TSU_SOSR1);
+
+ /* Enable comparison with "out of range" mode (CMPCOND=0) */
+ writel(TSU_CMSR_CMPEN, priv->base + TSU_CMSR);
+
+ /* Unmask compare IRQ and start a conversion to evaluate window */
+ writel(TSU_SIER_CMPIE, priv->base + TSU_SIER);
+ writel(TSU_STRGR_ADST, priv->base + TSU_STRGR);
+
+ pm_runtime_mark_last_busy(priv->dev);
+ pm_runtime_put_autosuspend(priv->dev);
+
+ return 0;
+}
+
+static irqreturn_t rzg3e_thermal_irq_thread(int irq, void *data)
+{
+ struct rzg3e_thermal_priv *priv = data;
+
+ dev_dbg(priv->dev, "Temperature threshold crossed\n");
+
+ /* Notify thermal framework to re-evaluate trip points */
+ thermal_zone_device_update(priv->zone, THERMAL_TRIP_VIOLATED);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rzg3e_thermal_irq(int irq, void *data)
+{
+ struct rzg3e_thermal_priv *priv = data;
+ u32 status;
+
+ status = readl(priv->base + TSU_SISR);
+
+ /* Check if comparison interrupt occurred */
+ if (status & TSU_SISR_CMPF) {
+ /* Clear irq flag and disable interrupt until reconfigured */
+ writel(TSU_SICR_CMPCLR, priv->base + TSU_SICR);
+ writel(0, priv->base + TSU_SIER);
+
+ return IRQ_WAKE_THREAD;
+ }
+
+ return IRQ_NONE;
+}
+
+static const struct thermal_zone_device_ops rzg3e_tz_ops = {
+ .get_temp = rzg3e_thermal_get_temp,
+ .set_trips = rzg3e_thermal_set_trips,
+};
+
+static int rzg3e_thermal_get_calibration(struct rzg3e_thermal_priv *priv)
+{
+ u32 val;
+ int ret;
+
+ /* Read calibration values from syscon */
+ ret = regmap_read(priv->syscon, priv->trim_offset, &val);
+ if (ret)
+ return ret;
+ priv->trmval0 = val & GENMASK(11, 0);
+
+ ret = regmap_read(priv->syscon, priv->trim_offset + 4, &val);
+ if (ret)
+ return ret;
+ priv->trmval1 = val & GENMASK(11, 0);
+
+ /* Validate calibration data */
+ if (!priv->trmval0 || !priv->trmval1 ||
+ priv->trmval0 == priv->trmval1 ||
+ priv->trmval0 == 0xFFF || priv->trmval1 == 0xFFF) {
+ dev_err(priv->dev, "Invalid calibration: b=0x%03x, c=0x%03x\n",
+ priv->trmval0, priv->trmval1);
+ return -EINVAL;
+ }
+
+ dev_dbg(priv->dev, "Calibration: b=0x%03x (%u), c=0x%03x (%u)\n",
+ priv->trmval0, priv->trmval0, priv->trmval1, priv->trmval1);
+
+ return 0;
+}
+
+static int rzg3e_thermal_parse_dt(struct rzg3e_thermal_priv *priv)
+{
+ struct device_node *np = priv->dev->of_node;
+ u32 offset;
+
+ priv->syscon = syscon_regmap_lookup_by_phandle_args(np, "renesas,tsu-trim", 1, &offset);
+ if (IS_ERR(priv->syscon))
+ return dev_err_probe(priv->dev, PTR_ERR(priv->syscon),
+ "Failed to parse renesas,tsu-trim\n");
+
+ priv->trim_offset = offset;
+ return 0;
+}
+
+static int rzg3e_thermal_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rzg3e_thermal_priv *priv;
+ struct clk *clk;
+ int irq, ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ ret = devm_mutex_init(dev, &priv->lock);
+ if (ret)
+ return ret;
+ platform_set_drvdata(pdev, priv);
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ /* Parse device tree for trim register info */
+ ret = rzg3e_thermal_parse_dt(priv);
+ if (ret)
+ return ret;
+
+ /* Get clock to verify frequency - clock is managed by power domain */
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "Failed to get clock\n");
+
+ if (clk_get_rate(clk) < TSU_MIN_CLOCK_RATE)
+ return dev_err_probe(dev, -EINVAL,
+ "Clock rate %lu Hz too low (min %u Hz)\n",
+ clk_get_rate(clk), TSU_MIN_CLOCK_RATE);
+
+ priv->rstc = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(priv->rstc))
+ return dev_err_probe(dev, PTR_ERR(priv->rstc),
+ "Failed to get/deassert reset control\n");
+
+ /* Get calibration data */
+ ret = rzg3e_thermal_get_calibration(priv);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get valid calibration data\n");
+
+ /* Get comparison interrupt */
+ irq = platform_get_irq_byname(pdev, "adcmpi");
+ if (irq < 0)
+ return irq;
+
+ /* Enable runtime PM */
+ pm_runtime_set_autosuspend_delay(dev, 1000);
+ pm_runtime_use_autosuspend(dev);
+ devm_pm_runtime_enable(dev);
+
+ /* Initial hardware setup */
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Runtime resume failed\n");
+
+ /* Register thermal zone - this will trigger DT parsing */
+ priv->zone = devm_thermal_of_zone_register(dev, 0, priv, &rzg3e_tz_ops);
+ if (IS_ERR(priv->zone)) {
+ ret = PTR_ERR(priv->zone);
+ dev_err(dev, "Failed to register thermal zone: %d\n", ret);
+ goto err_pm_put;
+ }
+
+ /* Request threaded IRQ for comparison interrupt */
+ ret = devm_request_threaded_irq(dev, irq, rzg3e_thermal_irq,
+ rzg3e_thermal_irq_thread,
+ IRQF_ONESHOT, "rzg3e_thermal", priv);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ: %d\n", ret);
+ goto err_pm_put;
+ }
+
+ /* Add hwmon sysfs interface */
+ ret = devm_thermal_add_hwmon_sysfs(dev, priv->zone);
+ if (ret)
+ dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ dev_info(dev, "RZ/G3E thermal sensor registered\n");
+
+ return 0;
+
+err_pm_put:
+ pm_runtime_put_sync(dev);
+ return ret;
+}
+
+static int rzg3e_thermal_runtime_suspend(struct device *dev)
+{
+ struct rzg3e_thermal_priv *priv = dev_get_drvdata(dev);
+
+ rzg3e_thermal_power_off(priv);
+ return 0;
+}
+
+static int rzg3e_thermal_runtime_resume(struct device *dev)
+{
+ struct rzg3e_thermal_priv *priv = dev_get_drvdata(dev);
+
+ return rzg3e_thermal_power_on(priv);
+}
+
+static int rzg3e_thermal_suspend(struct device *dev)
+{
+ struct rzg3e_thermal_priv *priv = dev_get_drvdata(dev);
+
+ /* If device is active, power it off */
+ if (pm_runtime_active(dev))
+ rzg3e_thermal_power_off(priv);
+
+ /* Assert reset to ensure clean state after resume */
+ reset_control_assert(priv->rstc);
+
+ return 0;
+}
+
+static int rzg3e_thermal_resume(struct device *dev)
+{
+ struct rzg3e_thermal_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ /* Deassert reset */
+ ret = reset_control_deassert(priv->rstc);
+ if (ret) {
+ dev_err(dev, "Failed to deassert reset: %d\n", ret);
+ return ret;
+ }
+
+ /* If device was active before suspend, power it back on */
+ if (pm_runtime_active(dev))
+ return rzg3e_thermal_power_on(priv);
+
+ return 0;
+}
+
+static const struct dev_pm_ops rzg3e_thermal_pm_ops = {
+ RUNTIME_PM_OPS(rzg3e_thermal_runtime_suspend,
+ rzg3e_thermal_runtime_resume, NULL)
+ SYSTEM_SLEEP_PM_OPS(rzg3e_thermal_suspend, rzg3e_thermal_resume)
+};
+
+static const struct of_device_id rzg3e_thermal_dt_ids[] = {
+ { .compatible = "renesas,r9a09g047-tsu" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rzg3e_thermal_dt_ids);
+
+static struct platform_driver rzg3e_thermal_driver = {
+ .driver = {
+ .name = "rzg3e_thermal",
+ .of_match_table = rzg3e_thermal_dt_ids,
+ .pm = pm_ptr(&rzg3e_thermal_pm_ops),
+ },
+ .probe = rzg3e_thermal_probe,
+};
+module_platform_driver(rzg3e_thermal_driver);
+
+MODULE_DESCRIPTION("Renesas RZ/G3E TSU Thermal Sensor Driver");
+MODULE_AUTHOR("John Madieu <john.madieu.xa@bp.renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c
new file mode 100644
index 000000000000..e25e36c99a88
--- /dev/null
+++ b/drivers/thermal/renesas/rzg3s_thermal.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G3S TSU Thermal Sensor Driver
+ *
+ * Copyright (C) 2024 Renesas Electronics Corporation
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/iio/consumer.h>
+#include <linux/io.h>
+#include <linux/module.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 TSU_SM 0x0
+#define TSU_SM_EN BIT(0)
+#define TSU_SM_OE BIT(1)
+#define OTPTSUTRIM_REG(n) (0x18 + (n) * 0x4)
+#define OTPTSUTRIM_EN_MASK BIT(31)
+#define OTPTSUTRIM_MASK GENMASK(11, 0)
+
+#define TSU_READ_STEPS 8
+
+/* Default calibration values, if FUSE values are missing. */
+#define SW_CALIB0_VAL 1297
+#define SW_CALIB1_VAL 751
+
+#define MCELSIUS(temp) ((temp) * MILLIDEGREE_PER_DEGREE)
+
+/**
+ * struct rzg3s_thermal_priv - RZ/G3S thermal private data structure
+ * @base: TSU base address
+ * @dev: device pointer
+ * @tz: thermal zone pointer
+ * @rstc: reset control
+ * @channel: IIO channel to read the TSU
+ * @mode: current device mode
+ * @calib0: calibration value
+ * @calib1: calibration value
+ */
+struct rzg3s_thermal_priv {
+ void __iomem *base;
+ struct device *dev;
+ struct thermal_zone_device *tz;
+ struct reset_control *rstc;
+ struct iio_channel *channel;
+ enum thermal_device_mode mode;
+ u16 calib0;
+ u16 calib1;
+};
+
+static int rzg3s_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct rzg3s_thermal_priv *priv = thermal_zone_device_priv(tz);
+ int ts_code_ave = 0;
+
+ if (priv->mode != THERMAL_DEVICE_ENABLED)
+ return -EAGAIN;
+
+ for (u8 i = 0; i < TSU_READ_STEPS; i++) {
+ int ret, val;
+
+ ret = iio_read_channel_raw(priv->channel, &val);
+ if (ret < 0)
+ return ret;
+
+ ts_code_ave += val;
+ /*
+ * According to the HW manual (Rev.1.10, section 40.4.4 Procedure for Measuring
+ * the Temperature) we need to wait here at leat 3us.
+ */
+ usleep_range(5, 10);
+ }
+
+ ts_code_ave = DIV_ROUND_CLOSEST(MCELSIUS(ts_code_ave), TSU_READ_STEPS);
+
+ /*
+ * According to the HW manual (Rev.1.10, section 40.4.4 Procedure for Measuring the
+ * Temperature) the computation formula is as follows:
+ *
+ * Tj = (ts_code_ave - priv->calib1) * 165 / (priv->calib0 - priv->calib1) - 40
+ *
+ * Convert everything to milli Celsius before applying the formula to avoid
+ * losing precision.
+ */
+
+ *temp = div_s64((s64)(ts_code_ave - MCELSIUS(priv->calib1)) * MCELSIUS(165),
+ MCELSIUS(priv->calib0 - priv->calib1)) - MCELSIUS(40);
+
+ /* Report it in milli degrees Celsius and round it up to 0.5 degrees Celsius. */
+ *temp = roundup(*temp, 500);
+
+ return 0;
+}
+
+static void rzg3s_thermal_set_mode(struct rzg3s_thermal_priv *priv,
+ enum thermal_device_mode mode)
+{
+ struct device *dev = priv->dev;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return;
+
+ if (mode == THERMAL_DEVICE_DISABLED) {
+ writel(0, priv->base + TSU_SM);
+ } else {
+ writel(TSU_SM_EN, priv->base + TSU_SM);
+ /*
+ * According to the HW manual (Rev.1.10, section 40.4.1 Procedure for
+ * Starting the TSU) we need to wait here 30us or more.
+ */
+ usleep_range(30, 40);
+
+ writel(TSU_SM_OE | TSU_SM_EN, priv->base + TSU_SM);
+ /*
+ * According to the HW manual (Rev.1.10, section 40.4.1 Procedure for
+ * Starting the TSU) we need to wait here 50us or more.
+ */
+ usleep_range(50, 60);
+ }
+
+ pm_runtime_put_autosuspend(dev);
+}
+
+static int rzg3s_thermal_change_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode mode)
+{
+ struct rzg3s_thermal_priv *priv = thermal_zone_device_priv(tz);
+
+ if (priv->mode == mode)
+ return 0;
+
+ rzg3s_thermal_set_mode(priv, mode);
+ priv->mode = mode;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops rzg3s_tz_of_ops = {
+ .get_temp = rzg3s_thermal_get_temp,
+ .change_mode = rzg3s_thermal_change_mode,
+};
+
+static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv)
+{
+ struct device *dev = priv->dev;
+ u32 val;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ val = readl(priv->base + OTPTSUTRIM_REG(0));
+ if (val & OTPTSUTRIM_EN_MASK)
+ priv->calib0 = FIELD_GET(OTPTSUTRIM_MASK, val);
+ else
+ priv->calib0 = SW_CALIB0_VAL;
+
+ val = readl(priv->base + OTPTSUTRIM_REG(1));
+ if (val & OTPTSUTRIM_EN_MASK)
+ priv->calib1 = FIELD_GET(OTPTSUTRIM_MASK, val);
+ else
+ priv->calib1 = SW_CALIB1_VAL;
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+static int rzg3s_thermal_probe(struct platform_device *pdev)
+{
+ struct rzg3s_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->channel = devm_iio_channel_get(dev, "tsu");
+ if (IS_ERR(priv->channel))
+ return dev_err_probe(dev, PTR_ERR(priv->channel), "Failed to get IIO channel!\n");
+
+ priv->rstc = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(priv->rstc))
+ return dev_err_probe(dev, PTR_ERR(priv->rstc), "Failed to get reset!\n");
+
+ priv->dev = dev;
+ priv->mode = THERMAL_DEVICE_DISABLED;
+ platform_set_drvdata(pdev, priv);
+
+ pm_runtime_set_autosuspend_delay(dev, 300);
+ pm_runtime_use_autosuspend(dev);
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM!\n");
+
+ ret = rzg3s_thermal_read_calib(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read calibration data!\n");
+
+ priv->tz = devm_thermal_of_zone_register(dev, 0, priv, &rzg3s_tz_of_ops);
+ if (IS_ERR(priv->tz))
+ return dev_err_probe(dev, PTR_ERR(priv->tz), "Failed to register thermal zone!\n");
+
+ ret = devm_thermal_add_hwmon_sysfs(dev, priv->tz);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add hwmon sysfs!\n");
+
+ return 0;
+}
+
+static int rzg3s_thermal_suspend(struct device *dev)
+{
+ struct rzg3s_thermal_priv *priv = dev_get_drvdata(dev);
+
+ rzg3s_thermal_set_mode(priv, THERMAL_DEVICE_DISABLED);
+
+ return reset_control_assert(priv->rstc);
+}
+
+static int rzg3s_thermal_resume(struct device *dev)
+{
+ struct rzg3s_thermal_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = reset_control_deassert(priv->rstc);
+ if (ret)
+ return ret;
+
+ if (priv->mode != THERMAL_DEVICE_DISABLED)
+ rzg3s_thermal_set_mode(priv, priv->mode);
+
+ return 0;
+}
+
+static const struct dev_pm_ops rzg3s_thermal_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(rzg3s_thermal_suspend, rzg3s_thermal_resume)
+};
+
+static const struct of_device_id rzg3s_thermal_dt_ids[] = {
+ { .compatible = "renesas,r9a08g045-tsu" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rzg3s_thermal_dt_ids);
+
+static struct platform_driver rzg3s_thermal_driver = {
+ .driver = {
+ .name = "rzg3s-thermal",
+ .of_match_table = rzg3s_thermal_dt_ids,
+ .pm = pm_ptr(&rzg3s_thermal_pm_ops),
+ },
+ .probe = rzg3s_thermal_probe,
+};
+module_platform_driver(rzg3s_thermal_driver);
+
+MODULE_DESCRIPTION("Renesas RZ/G3S Thermal Sensor Unit Driver");
+MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c
index 77231a9d28ff..c49ddf70f86e 100644
--- a/drivers/thermal/rockchip_thermal.c
+++ b/drivers/thermal/rockchip_thermal.c
@@ -9,6 +9,7 @@
#include <linux/interrupt.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_irq.h>
@@ -69,16 +70,19 @@ struct chip_tsadc_table {
* struct rockchip_tsadc_chip - hold the private data of tsadc chip
* @chn_offset: the channel offset of the first channel
* @chn_num: the channel number of tsadc chip
- * @tshut_temp: the hardware-controlled shutdown temperature value
+ * @trim_slope: used to convert the trim code to a temperature in millicelsius
+ * @tshut_temp: the hardware-controlled shutdown temperature value, with no trim
* @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO)
* @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
+ * @grf_required: true, if a GRF is required for proper functionality
* @initialize: SoC special initialize tsadc controller method
* @irq_ack: clear the interrupt
* @control: enable/disable method for the tsadc controller
- * @get_temp: get the temperature
+ * @get_temp: get the raw temperature, unadjusted by trim
* @set_alarm_temp: set the high temperature interrupt
* @set_tshut_temp: set the hardware-controlled shutdown temperature
* @set_tshut_mode: set the hardware-controlled shutdown mode
+ * @get_trim_code: convert a hardware temperature code to one adjusted for by trim
* @table: the chip-specific conversion table
*/
struct rockchip_tsadc_chip {
@@ -86,11 +90,17 @@ struct rockchip_tsadc_chip {
int chn_offset;
int chn_num;
+ /* Used to convert trim code to trim temp */
+ int trim_slope;
+
/* The hardware-controlled tshut property */
int tshut_temp;
enum tshut_mode tshut_mode;
enum tshut_polarity tshut_polarity;
+ /* GRF availability */
+ bool grf_required;
+
/* Chip-wide methods */
void (*initialize)(struct regmap *grf,
void __iomem *reg, enum tshut_polarity p);
@@ -105,6 +115,8 @@ struct rockchip_tsadc_chip {
int (*set_tshut_temp)(const struct chip_tsadc_table *table,
int chn, void __iomem *reg, int temp);
void (*set_tshut_mode)(int chn, void __iomem *reg, enum tshut_mode m);
+ int (*get_trim_code)(const struct chip_tsadc_table *table,
+ int code, int trim_base, int trim_base_frac);
/* Per-table methods */
struct chip_tsadc_table table;
@@ -114,12 +126,16 @@ struct rockchip_tsadc_chip {
* struct rockchip_thermal_sensor - hold the information of thermal sensor
* @thermal: pointer to the platform/configuration data
* @tzd: pointer to a thermal zone
+ * @of_node: pointer to the device_node representing this sensor, if any
* @id: identifier of the thermal sensor
+ * @trim_temp: per-sensor trim temperature value
*/
struct rockchip_thermal_sensor {
struct rockchip_thermal_data *thermal;
struct thermal_zone_device *tzd;
+ struct device_node *of_node;
int id;
+ int trim_temp;
};
/**
@@ -132,7 +148,11 @@ struct rockchip_thermal_sensor {
* @pclk: the advanced peripherals bus clock
* @grf: the general register file will be used to do static set by software
* @regs: the base address of tsadc controller
- * @tshut_temp: the hardware-controlled shutdown temperature value
+ * @trim_base: major component of sensor trim value, in Celsius
+ * @trim_base_frac: minor component of sensor trim value, in Decicelsius
+ * @trim: fallback thermal trim value for each channel
+ * @tshut_temp: the hardware-controlled shutdown temperature value, with no trim
+ * @trim_temp: the fallback trim temperature for the whole sensor
* @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO)
* @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
*/
@@ -149,7 +169,12 @@ struct rockchip_thermal_data {
struct regmap *grf;
void __iomem *regs;
+ int trim_base;
+ int trim_base_frac;
+ int trim;
+
int tshut_temp;
+ int trim_temp;
enum tshut_mode tshut_mode;
enum tshut_polarity tshut_polarity;
};
@@ -249,6 +274,9 @@ struct rockchip_thermal_data {
#define GRF_CON_TSADC_CH_INV (0x10001 << 1)
+
+#define RK_MAX_TEMP (180000)
+
/**
* struct tsadc_table - code to temperature conversion table
* @code: the value of adc channel
@@ -386,6 +414,7 @@ static const struct tsadc_table rk3328_code_table[] = {
{296, -40000},
{304, -35000},
{313, -30000},
+ {322, -25000},
{331, -20000},
{340, -15000},
{349, -10000},
@@ -1044,7 +1073,7 @@ static void rk_tsadcv2_tshut_mode(int chn, void __iomem *regs,
writel_relaxed(val, regs + TSADCV2_INT_EN);
}
-static void rk_tsadcv3_tshut_mode(int chn, void __iomem *regs,
+static void rk_tsadcv4_tshut_mode(int chn, void __iomem *regs,
enum tshut_mode mode)
{
u32 val_gpio, val_cru;
@@ -1060,14 +1089,22 @@ static void rk_tsadcv3_tshut_mode(int chn, void __iomem *regs,
writel_relaxed(val_cru, regs + TSADCV3_HSHUT_CRU_INT_EN);
}
+static int rk_tsadcv2_get_trim_code(const struct chip_tsadc_table *table,
+ int code, int trim_base, int trim_base_frac)
+{
+ int temp = trim_base * 1000 + trim_base_frac * 100;
+ u32 base_code = rk_tsadcv2_temp_to_code(table, temp);
+
+ return code - base_code;
+}
+
static const struct rockchip_tsadc_chip px30_tsadc_data = {
/* cpu, gpu */
.chn_offset = 0,
.chn_num = 2, /* 2 channels for tsadc */
-
+ .grf_required = true,
.tshut_mode = TSHUT_MODE_CRU, /* default TSHUT via CRU */
.tshut_temp = 95000,
-
.initialize = rk_tsadcv4_initialize,
.irq_ack = rk_tsadcv3_irq_ack,
.control = rk_tsadcv3_control,
@@ -1075,7 +1112,6 @@ static const struct rockchip_tsadc_chip px30_tsadc_data = {
.set_alarm_temp = rk_tsadcv2_alarm_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
-
.table = {
.id = rk3328_code_table,
.length = ARRAY_SIZE(rk3328_code_table),
@@ -1088,11 +1124,10 @@ static const struct rockchip_tsadc_chip rv1108_tsadc_data = {
/* cpu */
.chn_offset = 0,
.chn_num = 1, /* one channel for tsadc */
-
+ .grf_required = false,
.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_tsadcv2_initialize,
.irq_ack = rk_tsadcv3_irq_ack,
.control = rk_tsadcv3_control,
@@ -1100,7 +1135,6 @@ static const struct rockchip_tsadc_chip rv1108_tsadc_data = {
.set_alarm_temp = rk_tsadcv2_alarm_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
-
.table = {
.id = rv1108_table,
.length = ARRAY_SIZE(rv1108_table),
@@ -1113,11 +1147,10 @@ static const struct rockchip_tsadc_chip rk3228_tsadc_data = {
/* cpu */
.chn_offset = 0,
.chn_num = 1, /* one channel for tsadc */
-
+ .grf_required = false,
.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_tsadcv2_initialize,
.irq_ack = rk_tsadcv3_irq_ack,
.control = rk_tsadcv3_control,
@@ -1125,7 +1158,6 @@ static const struct rockchip_tsadc_chip rk3228_tsadc_data = {
.set_alarm_temp = rk_tsadcv2_alarm_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
-
.table = {
.id = rk3228_code_table,
.length = ARRAY_SIZE(rk3228_code_table),
@@ -1138,11 +1170,10 @@ static const struct rockchip_tsadc_chip rk3288_tsadc_data = {
/* cpu, gpu */
.chn_offset = 1,
.chn_num = 2, /* two channels for tsadc */
-
+ .grf_required = false,
.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_tsadcv2_initialize,
.irq_ack = rk_tsadcv2_irq_ack,
.control = rk_tsadcv2_control,
@@ -1150,7 +1181,6 @@ static const struct rockchip_tsadc_chip rk3288_tsadc_data = {
.set_alarm_temp = rk_tsadcv2_alarm_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
-
.table = {
.id = rk3288_code_table,
.length = ARRAY_SIZE(rk3288_code_table),
@@ -1163,10 +1193,9 @@ static const struct rockchip_tsadc_chip rk3328_tsadc_data = {
/* cpu */
.chn_offset = 0,
.chn_num = 1, /* one channels for tsadc */
-
+ .grf_required = false,
.tshut_mode = TSHUT_MODE_CRU, /* default TSHUT via CRU */
.tshut_temp = 95000,
-
.initialize = rk_tsadcv2_initialize,
.irq_ack = rk_tsadcv3_irq_ack,
.control = rk_tsadcv3_control,
@@ -1174,7 +1203,6 @@ static const struct rockchip_tsadc_chip rk3328_tsadc_data = {
.set_alarm_temp = rk_tsadcv2_alarm_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
-
.table = {
.id = rk3328_code_table,
.length = ARRAY_SIZE(rk3328_code_table),
@@ -1187,11 +1215,10 @@ static const struct rockchip_tsadc_chip rk3366_tsadc_data = {
/* cpu, gpu */
.chn_offset = 0,
.chn_num = 2, /* two channels for tsadc */
-
+ .grf_required = true,
.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_tsadcv3_initialize,
.irq_ack = rk_tsadcv3_irq_ack,
.control = rk_tsadcv3_control,
@@ -1199,7 +1226,6 @@ static const struct rockchip_tsadc_chip rk3366_tsadc_data = {
.set_alarm_temp = rk_tsadcv2_alarm_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
-
.table = {
.id = rk3228_code_table,
.length = ARRAY_SIZE(rk3228_code_table),
@@ -1212,11 +1238,10 @@ static const struct rockchip_tsadc_chip rk3368_tsadc_data = {
/* cpu, gpu */
.chn_offset = 0,
.chn_num = 2, /* two channels for tsadc */
-
+ .grf_required = false,
.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_tsadcv2_initialize,
.irq_ack = rk_tsadcv2_irq_ack,
.control = rk_tsadcv2_control,
@@ -1224,7 +1249,6 @@ static const struct rockchip_tsadc_chip rk3368_tsadc_data = {
.set_alarm_temp = rk_tsadcv2_alarm_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
-
.table = {
.id = rk3368_code_table,
.length = ARRAY_SIZE(rk3368_code_table),
@@ -1237,11 +1261,10 @@ static const struct rockchip_tsadc_chip rk3399_tsadc_data = {
/* cpu, gpu */
.chn_offset = 0,
.chn_num = 2, /* two channels for tsadc */
-
+ .grf_required = true,
.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_tsadcv3_initialize,
.irq_ack = rk_tsadcv3_irq_ack,
.control = rk_tsadcv3_control,
@@ -1249,7 +1272,6 @@ static const struct rockchip_tsadc_chip rk3399_tsadc_data = {
.set_alarm_temp = rk_tsadcv2_alarm_temp,
.set_tshut_temp = rk_tsadcv2_tshut_temp,
.set_tshut_mode = rk_tsadcv2_tshut_mode,
-
.table = {
.id = rk3399_code_table,
.length = ARRAY_SIZE(rk3399_code_table),
@@ -1262,11 +1284,10 @@ static const struct rockchip_tsadc_chip rk3568_tsadc_data = {
/* cpu, gpu */
.chn_offset = 0,
.chn_num = 2, /* two channels for tsadc */
-
+ .grf_required = true,
.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,
@@ -1274,7 +1295,6 @@ static const struct rockchip_tsadc_chip rk3568_tsadc_data = {
.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),
@@ -1283,10 +1303,36 @@ static const struct rockchip_tsadc_chip rk3568_tsadc_data = {
},
};
+static const struct rockchip_tsadc_chip rk3576_tsadc_data = {
+ /* top, big_core, little_core, ddr, npu, gpu */
+ .chn_offset = 0,
+ .chn_num = 6, /* six channels for tsadc */
+ .grf_required = false,
+ .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_tsadcv8_initialize,
+ .irq_ack = rk_tsadcv4_irq_ack,
+ .control = rk_tsadcv4_control,
+ .get_temp = rk_tsadcv4_get_temp,
+ .set_alarm_temp = rk_tsadcv3_alarm_temp,
+ .set_tshut_temp = rk_tsadcv3_tshut_temp,
+ .set_tshut_mode = rk_tsadcv4_tshut_mode,
+ .get_trim_code = rk_tsadcv2_get_trim_code,
+ .trim_slope = 923,
+ .table = {
+ .id = rk3588_code_table,
+ .length = ARRAY_SIZE(rk3588_code_table),
+ .data_mask = TSADCV4_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
static const struct rockchip_tsadc_chip rk3588_tsadc_data = {
/* top, big_core0, big_core1, little_core, center, gpu, npu */
.chn_offset = 0,
.chn_num = 7, /* seven channels for tsadc */
+ .grf_required = false,
.tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
.tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
.tshut_temp = 95000,
@@ -1296,7 +1342,7 @@ static const struct rockchip_tsadc_chip rk3588_tsadc_data = {
.get_temp = rk_tsadcv4_get_temp,
.set_alarm_temp = rk_tsadcv3_alarm_temp,
.set_tshut_temp = rk_tsadcv3_tshut_temp,
- .set_tshut_mode = rk_tsadcv3_tshut_mode,
+ .set_tshut_mode = rk_tsadcv4_tshut_mode,
.table = {
.id = rk3588_code_table,
.length = ARRAY_SIZE(rk3588_code_table),
@@ -1342,6 +1388,10 @@ static const struct of_device_id of_rockchip_thermal_match[] = {
.data = (void *)&rk3568_tsadc_data,
},
{
+ .compatible = "rockchip,rk3576-tsadc",
+ .data = (void *)&rk3576_tsadc_data,
+ },
+ {
.compatible = "rockchip,rk3588-tsadc",
.data = (void *)&rk3588_tsadc_data,
},
@@ -1386,7 +1436,7 @@ static int rockchip_thermal_set_trips(struct thermal_zone_device *tz, int low, i
__func__, sensor->id, low, high);
return tsadc->set_alarm_temp(&tsadc->table,
- sensor->id, thermal->regs, high);
+ sensor->id, thermal->regs, high + sensor->trim_temp);
}
static int rockchip_thermal_get_temp(struct thermal_zone_device *tz, int *out_temp)
@@ -1398,6 +1448,8 @@ static int rockchip_thermal_get_temp(struct thermal_zone_device *tz, int *out_te
retval = tsadc->get_temp(&tsadc->table,
sensor->id, thermal->regs, out_temp);
+ *out_temp -= sensor->trim_temp;
+
return retval;
}
@@ -1406,6 +1458,104 @@ static const struct thermal_zone_device_ops rockchip_of_thermal_ops = {
.set_trips = rockchip_thermal_set_trips,
};
+/**
+ * rockchip_get_efuse_value - read an OTP cell from a device node
+ * @np: pointer to the device node with the nvmem-cells property
+ * @cell_name: name of cell that should be read
+ * @value: pointer to where the read value will be placed
+ *
+ * Return: Negative errno on failure, during which *value will not be touched,
+ * or 0 on success.
+ */
+static int rockchip_get_efuse_value(struct device_node *np, const char *cell_name,
+ int *value)
+{
+ struct nvmem_cell *cell;
+ int ret = 0;
+ size_t len;
+ u8 *buf;
+ int i;
+
+ cell = of_nvmem_cell_get(np, cell_name);
+ 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(*value)) {
+ ret = -ERANGE;
+ goto exit;
+ }
+
+ /* Copy with implicit endian conversion */
+ *value = 0;
+ for (i = 0; i < len; i++)
+ *value |= (int) buf[i] << (8 * i);
+
+exit:
+ kfree(buf);
+ return ret;
+}
+
+static int rockchip_get_trim_configuration(struct device *dev, struct device_node *np,
+ struct rockchip_thermal_data *thermal)
+{
+ const struct rockchip_tsadc_chip *tsadc = thermal->chip;
+ int trim_base = 0, trim_base_frac = 0, trim = 0;
+ int trim_code;
+ int ret;
+
+ thermal->trim_base = 0;
+ thermal->trim_base_frac = 0;
+ thermal->trim = 0;
+
+ if (!tsadc->get_trim_code)
+ return 0;
+
+ ret = rockchip_get_efuse_value(np, "trim_base", &trim_base);
+ if (ret < 0) {
+ if (ret == -ENOENT) {
+ trim_base = 30;
+ dev_dbg(dev, "trim_base is absent, defaulting to 30\n");
+ } else {
+ dev_err(dev, "failed reading nvmem value of trim_base: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ }
+ ret = rockchip_get_efuse_value(np, "trim_base_frac", &trim_base_frac);
+ if (ret < 0) {
+ if (ret == -ENOENT) {
+ dev_dbg(dev, "trim_base_frac is absent, defaulting to 0\n");
+ } else {
+ dev_err(dev, "failed reading nvmem value of trim_base_frac: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ }
+ thermal->trim_base = trim_base;
+ thermal->trim_base_frac = trim_base_frac;
+
+ /*
+ * If the tsadc node contains the trim property, then it is used in the
+ * absence of per-channel trim values
+ */
+ if (!rockchip_get_efuse_value(np, "trim", &trim))
+ thermal->trim = trim;
+ if (trim) {
+ trim_code = tsadc->get_trim_code(&tsadc->table, trim,
+ trim_base, trim_base_frac);
+ thermal->trim_temp = thermal->chip->trim_slope * trim_code;
+ }
+
+ return 0;
+}
+
static int rockchip_configure_from_dt(struct device *dev,
struct device_node *np,
struct rockchip_thermal_data *thermal)
@@ -1459,12 +1609,12 @@ static int rockchip_configure_from_dt(struct device *dev,
return -EINVAL;
}
- /* The tsadc wont to handle the error in here since some SoCs didn't
- * need this property.
- */
thermal->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
- if (IS_ERR(thermal->grf))
- dev_warn(dev, "Missing rockchip,grf property\n");
+ if (IS_ERR(thermal->grf) && thermal->chip->grf_required)
+ return dev_err_probe(dev, PTR_ERR(thermal->grf),
+ "Missing rockchip,grf property\n");
+
+ rockchip_get_trim_configuration(dev, np, thermal);
return 0;
}
@@ -1476,23 +1626,50 @@ rockchip_thermal_register_sensor(struct platform_device *pdev,
int id)
{
const struct rockchip_tsadc_chip *tsadc = thermal->chip;
+ struct device *dev = &pdev->dev;
+ int trim = thermal->trim;
+ int trim_code, tshut_temp;
+ int trim_temp = 0;
int error;
+ if (thermal->trim_temp)
+ trim_temp = thermal->trim_temp;
+
+ if (tsadc->get_trim_code && sensor->of_node) {
+ error = rockchip_get_efuse_value(sensor->of_node, "trim", &trim);
+ if (error < 0 && error != -ENOENT) {
+ dev_err(dev, "failed reading trim of sensor %d: %pe\n",
+ id, ERR_PTR(error));
+ return error;
+ }
+ if (trim) {
+ trim_code = tsadc->get_trim_code(&tsadc->table, trim,
+ thermal->trim_base,
+ thermal->trim_base_frac);
+ trim_temp = thermal->chip->trim_slope * trim_code;
+ }
+ }
+
+ sensor->trim_temp = trim_temp;
+
+ dev_dbg(dev, "trim of sensor %d is %d\n", id, sensor->trim_temp);
+
+ tshut_temp = min(thermal->tshut_temp + sensor->trim_temp, RK_MAX_TEMP);
+
tsadc->set_tshut_mode(id, thermal->regs, thermal->tshut_mode);
- error = tsadc->set_tshut_temp(&tsadc->table, id, thermal->regs,
- thermal->tshut_temp);
+ error = tsadc->set_tshut_temp(&tsadc->table, id, thermal->regs, tshut_temp);
if (error)
- dev_err(&pdev->dev, "%s: invalid tshut=%d, error=%d\n",
- __func__, thermal->tshut_temp, error);
+ dev_err(dev, "%s: invalid tshut=%d, error=%d\n",
+ __func__, tshut_temp, error);
sensor->thermal = thermal;
sensor->id = id;
- sensor->tzd = devm_thermal_of_zone_register(&pdev->dev, id, sensor,
+ sensor->tzd = devm_thermal_of_zone_register(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",
+ dev_err(dev, "failed to register sensor %d: %d\n",
id, error);
return error;
}
@@ -1515,9 +1692,11 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct rockchip_thermal_data *thermal;
+ struct device_node *child;
int irq;
int i;
int error;
+ u32 chn;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
@@ -1568,6 +1747,18 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
thermal->chip->initialize(thermal->grf, thermal->regs,
thermal->tshut_polarity);
+ for_each_available_child_of_node(np, child) {
+ if (!of_property_read_u32(child, "reg", &chn)) {
+ if (chn < thermal->chip->chn_num)
+ thermal->sensors[chn].of_node = child;
+ else
+ dev_warn(&pdev->dev,
+ "sensor address (%d) too large, ignoring its trim\n",
+ chn);
+ }
+
+ }
+
for (i = 0; i < thermal->chip->chn_num; i++) {
error = rockchip_thermal_register_sensor(pdev, thermal,
&thermal->sensors[i],
@@ -1601,7 +1792,7 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
return 0;
}
-static int rockchip_thermal_remove(struct platform_device *pdev)
+static void rockchip_thermal_remove(struct platform_device *pdev)
{
struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev);
int i;
@@ -1614,8 +1805,6 @@ static int rockchip_thermal_remove(struct platform_device *pdev)
}
thermal->chip->control(thermal->regs, false);
-
- return 0;
}
static int __maybe_unused rockchip_thermal_suspend(struct device *dev)
@@ -1639,8 +1828,11 @@ static int __maybe_unused rockchip_thermal_suspend(struct device *dev)
static int __maybe_unused rockchip_thermal_resume(struct device *dev)
{
struct rockchip_thermal_data *thermal = dev_get_drvdata(dev);
- int i;
+ const struct rockchip_tsadc_chip *tsadc = thermal->chip;
+ struct rockchip_thermal_sensor *sensor;
+ int tshut_temp;
int error;
+ int i;
error = clk_enable(thermal->clk);
if (error)
@@ -1654,21 +1846,23 @@ static int __maybe_unused rockchip_thermal_resume(struct device *dev)
rockchip_thermal_reset_controller(thermal->reset);
- thermal->chip->initialize(thermal->grf, thermal->regs,
- thermal->tshut_polarity);
+ tsadc->initialize(thermal->grf, thermal->regs, thermal->tshut_polarity);
for (i = 0; i < thermal->chip->chn_num; i++) {
- int id = thermal->sensors[i].id;
+ sensor = &thermal->sensors[i];
+
+ tshut_temp = min(thermal->tshut_temp + sensor->trim_temp,
+ RK_MAX_TEMP);
- thermal->chip->set_tshut_mode(id, thermal->regs,
+ tsadc->set_tshut_mode(sensor->id, thermal->regs,
thermal->tshut_mode);
- error = thermal->chip->set_tshut_temp(&thermal->chip->table,
- id, thermal->regs,
- thermal->tshut_temp);
+ error = tsadc->set_tshut_temp(&thermal->chip->table,
+ sensor->id, thermal->regs,
+ tshut_temp);
if (error)
dev_err(dev, "%s: invalid tshut=%d, error=%d\n",
- __func__, thermal->tshut_temp, error);
+ __func__, tshut_temp, error);
}
thermal->chip->control(thermal->regs, true);
diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c
index 45e5c840d130..47a99b3c5395 100644
--- a/drivers/thermal/samsung/exynos_tmu.c
+++ b/drivers/thermal/samsung/exynos_tmu.c
@@ -15,7 +15,7 @@
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
@@ -138,12 +138,10 @@ enum soc_type {
/**
* struct exynos_tmu_data : A structure to hold the private data of the TMU
* driver
- * @id: identifier of the one instance of the TMU controller.
* @base: base address of the single instance of the TMU controller.
* @base_second: base address of the common registers of the TMU controller.
* @irq: irq number of the TMU controller.
* @soc: id of the SOC type.
- * @irq_work: pointer to the irq work structure.
* @lock: lock to implement synchronization.
* @clk: pointer to the clock structure.
* @clk_sec: pointer to the clock structure for accessing the base_second.
@@ -159,13 +157,13 @@ enum soc_type {
* @reference_voltage: reference voltage of amplifier
* in the positive-TC generator block
* 0 < reference_voltage <= 31
- * @regulator: pointer to the TMU regulator structure.
- * @reg_conf: pointer to structure to register with core thermal.
* @tzd: pointer to thermal_zone_device structure
- * @ntrip: number of supported trip points.
* @enabled: current status of TMU device
- * @tmu_set_trip_temp: SoC specific method to set trip (rising threshold)
- * @tmu_set_trip_hyst: SoC specific to set hysteresis (falling threshold)
+ * @tmu_set_low_temp: SoC specific method to set trip (falling threshold)
+ * @tmu_set_high_temp: SoC specific method to set trip (rising threshold)
+ * @tmu_set_crit_temp: SoC specific method to set critical temperature
+ * @tmu_disable_low: SoC specific method to disable an interrupt (falling threshold)
+ * @tmu_disable_high: SoC specific method to disable an interrupt (rising threshold)
* @tmu_initialize: SoC specific TMU initialization method
* @tmu_control: SoC specific TMU control method
* @tmu_read: SoC specific TMU temperature read method
@@ -173,12 +171,10 @@ enum soc_type {
* @tmu_clear_irqs: SoC specific TMU interrupts clearing method
*/
struct exynos_tmu_data {
- int id;
void __iomem *base;
void __iomem *base_second;
int irq;
enum soc_type soc;
- struct work_struct irq_work;
struct mutex lock;
struct clk *clk, *clk_sec, *sclk;
u32 cal_type;
@@ -188,15 +184,14 @@ struct exynos_tmu_data {
u16 temp_error1, temp_error2;
u8 gain;
u8 reference_voltage;
- struct regulator *regulator;
struct thermal_zone_device *tzd;
- unsigned int ntrip;
bool enabled;
- void (*tmu_set_trip_temp)(struct exynos_tmu_data *data, int trip,
- u8 temp);
- void (*tmu_set_trip_hyst)(struct exynos_tmu_data *data, int trip,
- u8 temp, u8 hyst);
+ void (*tmu_set_low_temp)(struct exynos_tmu_data *data, u8 temp);
+ void (*tmu_set_high_temp)(struct exynos_tmu_data *data, u8 temp);
+ void (*tmu_set_crit_temp)(struct exynos_tmu_data *data, u8 temp);
+ void (*tmu_disable_low)(struct exynos_tmu_data *data);
+ void (*tmu_disable_high)(struct exynos_tmu_data *data);
void (*tmu_initialize)(struct platform_device *pdev);
void (*tmu_control)(struct platform_device *pdev, bool on);
int (*tmu_read)(struct exynos_tmu_data *data);
@@ -258,25 +253,8 @@ static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info)
static int exynos_tmu_initialize(struct platform_device *pdev)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
- struct thermal_zone_device *tzd = data->tzd;
- int num_trips = thermal_zone_get_num_trips(tzd);
unsigned int status;
- int ret = 0, temp;
-
- ret = thermal_zone_get_crit_temp(tzd, &temp);
- if (ret && data->soc != SOC_ARCH_EXYNOS5433) { /* FIXME */
- dev_err(&pdev->dev,
- "No CRITICAL trip point defined in device tree!\n");
- goto out;
- }
-
- if (num_trips > data->ntrip) {
- dev_info(&pdev->dev,
- "More trip points than supported by this TMU.\n");
- dev_info(&pdev->dev,
- "%d trip points should be configured in polling mode.\n",
- num_trips - data->ntrip);
- }
+ int ret = 0;
mutex_lock(&data->lock);
clk_enable(data->clk);
@@ -287,34 +265,44 @@ static int exynos_tmu_initialize(struct platform_device *pdev)
if (!status) {
ret = -EBUSY;
} else {
- int i, ntrips =
- min_t(int, num_trips, data->ntrip);
-
data->tmu_initialize(pdev);
+ data->tmu_clear_irqs(data);
+ }
- /* Write temperature code for rising and falling threshold */
- for (i = 0; i < ntrips; i++) {
+ if (!IS_ERR(data->clk_sec))
+ clk_disable(data->clk_sec);
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
- struct thermal_trip trip;
+ return ret;
+}
- ret = thermal_zone_get_trip(tzd, i, &trip);
- if (ret)
- goto err;
+static int exynos_thermal_zone_configure(struct platform_device *pdev)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ struct thermal_zone_device *tzd = data->tzd;
+ int ret, temp;
- data->tmu_set_trip_temp(data, i, trip.temperature / MCELSIUS);
- data->tmu_set_trip_hyst(data, i, trip.temperature / MCELSIUS,
- trip.hysteresis / MCELSIUS);
- }
+ ret = thermal_zone_get_crit_temp(tzd, &temp);
+ if (ret) {
+ /* FIXME: Remove this special case */
+ if (data->soc == SOC_ARCH_EXYNOS5433)
+ return 0;
- data->tmu_clear_irqs(data);
+ dev_err(&pdev->dev,
+ "No CRITICAL trip point defined in device tree!\n");
+ return ret;
}
-err:
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+
+ data->tmu_set_crit_temp(data, temp / MCELSIUS);
+
clk_disable(data->clk);
mutex_unlock(&data->lock);
- if (!IS_ERR(data->clk_sec))
- clk_disable(data->clk_sec);
-out:
- return ret;
+
+ return 0;
}
static u32 get_con_reg(struct exynos_tmu_data *data, u32 con)
@@ -347,30 +335,74 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on)
mutex_unlock(&data->lock);
}
-static void exynos4210_tmu_set_trip_temp(struct exynos_tmu_data *data,
- int trip_id, u8 temp)
+static void exynos_tmu_update_bit(struct exynos_tmu_data *data, int reg_off,
+ int bit_off, bool enable)
{
- struct thermal_trip trip;
- u8 ref, th_code;
+ u32 interrupt_en;
- if (thermal_zone_get_trip(data->tzd, 0, &trip))
- return;
+ interrupt_en = readl(data->base + reg_off);
+ if (enable)
+ interrupt_en |= BIT(bit_off);
+ else
+ interrupt_en &= ~BIT(bit_off);
+ writel(interrupt_en, data->base + reg_off);
+}
- ref = trip.temperature / MCELSIUS;
+static void exynos_tmu_update_temp(struct exynos_tmu_data *data, int reg_off,
+ int bit_off, u8 temp)
+{
+ u16 tmu_temp_mask;
+ u32 th;
- if (trip_id == 0) {
- th_code = temp_to_code(data, ref);
- writeb(th_code, data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
- }
+ tmu_temp_mask =
+ (data->soc == SOC_ARCH_EXYNOS7) ? EXYNOS7_TMU_TEMP_MASK
+ : EXYNOS_TMU_TEMP_MASK;
- temp -= ref;
- writeb(temp, data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + trip_id * 4);
+ th = readl(data->base + reg_off);
+ th &= ~(tmu_temp_mask << bit_off);
+ th |= temp_to_code(data, temp) << bit_off;
+ writel(th, data->base + reg_off);
}
-/* failing thresholds are not supported on Exynos4210 */
-static void exynos4210_tmu_set_trip_hyst(struct exynos_tmu_data *data,
- int trip, u8 temp, u8 hyst)
+static void exynos4210_tmu_set_low_temp(struct exynos_tmu_data *data, u8 temp)
{
+ /*
+ * Failing thresholds are not supported on Exynos 4210.
+ * We use polling instead.
+ */
+}
+
+static void exynos4210_tmu_set_high_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ temp = temp_to_code(data, temp);
+ writeb(temp, data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + 4);
+ exynos_tmu_update_bit(data, EXYNOS_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_RISE0_SHIFT + 4, true);
+}
+
+static void exynos4210_tmu_disable_low(struct exynos_tmu_data *data)
+{
+ /* Again, this is handled by polling. */
+}
+
+static void exynos4210_tmu_disable_high(struct exynos_tmu_data *data)
+{
+ exynos_tmu_update_bit(data, EXYNOS_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_RISE0_SHIFT + 4, false);
+}
+
+static void exynos4210_tmu_set_crit_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ /*
+ * Hardware critical temperature handling is not supported on Exynos 4210.
+ * We still set the critical temperature threshold, but this is only to
+ * make sure it is handled as soon as possible. It is just a normal interrupt.
+ */
+
+ temp = temp_to_code(data, temp);
+ writeb(temp, data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + 12);
+ exynos_tmu_update_bit(data, EXYNOS_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_RISE0_SHIFT + 12, true);
}
static void exynos4210_tmu_initialize(struct platform_device *pdev)
@@ -378,35 +410,35 @@ static void exynos4210_tmu_initialize(struct platform_device *pdev)
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
sanitize_temp_error(data, readl(data->base + EXYNOS_TMU_REG_TRIMINFO));
+
+ writeb(0, data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
}
-static void exynos4412_tmu_set_trip_temp(struct exynos_tmu_data *data,
- int trip, u8 temp)
+static void exynos4412_tmu_set_low_temp(struct exynos_tmu_data *data, u8 temp)
{
- u32 th, con;
-
- th = readl(data->base + EXYNOS_THD_TEMP_RISE);
- th &= ~(0xff << 8 * trip);
- th |= temp_to_code(data, temp) << 8 * trip;
- writel(th, data->base + EXYNOS_THD_TEMP_RISE);
+ exynos_tmu_update_temp(data, EXYNOS_THD_TEMP_FALL, 0, temp);
+ exynos_tmu_update_bit(data, EXYNOS_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_FALL0_SHIFT, true);
+}
- if (trip == 3) {
- con = readl(data->base + EXYNOS_TMU_REG_CONTROL);
- con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT);
- writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
- }
+static void exynos4412_tmu_set_high_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ exynos_tmu_update_temp(data, EXYNOS_THD_TEMP_RISE, 8, temp);
+ exynos_tmu_update_bit(data, EXYNOS_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_RISE0_SHIFT + 4, true);
}
-static void exynos4412_tmu_set_trip_hyst(struct exynos_tmu_data *data,
- int trip, u8 temp, u8 hyst)
+static void exynos4412_tmu_disable_low(struct exynos_tmu_data *data)
{
- u32 th;
+ exynos_tmu_update_bit(data, EXYNOS_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_FALL0_SHIFT, false);
+}
- th = readl(data->base + EXYNOS_THD_TEMP_FALL);
- th &= ~(0xff << 8 * trip);
- if (hyst)
- th |= temp_to_code(data, temp - hyst) << 8 * trip;
- writel(th, data->base + EXYNOS_THD_TEMP_FALL);
+static void exynos4412_tmu_set_crit_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ exynos_tmu_update_temp(data, EXYNOS_THD_TEMP_RISE, 24, temp);
+ exynos_tmu_update_bit(data, EXYNOS_TMU_REG_CONTROL,
+ EXYNOS_TMU_THERM_TRIP_EN_SHIFT, true);
}
static void exynos4412_tmu_initialize(struct platform_device *pdev)
@@ -436,44 +468,39 @@ static void exynos4412_tmu_initialize(struct platform_device *pdev)
sanitize_temp_error(data, trim_info);
}
-static void exynos5433_tmu_set_trip_temp(struct exynos_tmu_data *data,
- int trip, u8 temp)
+static void exynos5433_tmu_set_low_temp(struct exynos_tmu_data *data, u8 temp)
{
- unsigned int reg_off, j;
- u32 th;
-
- if (trip > 3) {
- reg_off = EXYNOS5433_THD_TEMP_RISE7_4;
- j = trip - 4;
- } else {
- reg_off = EXYNOS5433_THD_TEMP_RISE3_0;
- j = trip;
- }
+ exynos_tmu_update_temp(data, EXYNOS5433_THD_TEMP_FALL3_0, 0, temp);
+ exynos_tmu_update_bit(data, EXYNOS5433_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_FALL0_SHIFT, true);
+}
- th = readl(data->base + reg_off);
- th &= ~(0xff << j * 8);
- th |= (temp_to_code(data, temp) << j * 8);
- writel(th, data->base + reg_off);
+static void exynos5433_tmu_set_high_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ exynos_tmu_update_temp(data, EXYNOS5433_THD_TEMP_RISE3_0, 8, temp);
+ exynos_tmu_update_bit(data, EXYNOS5433_TMU_REG_INTEN,
+ EXYNOS7_TMU_INTEN_RISE0_SHIFT + 1, true);
}
-static void exynos5433_tmu_set_trip_hyst(struct exynos_tmu_data *data,
- int trip, u8 temp, u8 hyst)
+static void exynos5433_tmu_disable_low(struct exynos_tmu_data *data)
{
- unsigned int reg_off, j;
- u32 th;
+ exynos_tmu_update_bit(data, EXYNOS5433_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_FALL0_SHIFT, false);
+}
- if (trip > 3) {
- reg_off = EXYNOS5433_THD_TEMP_FALL7_4;
- j = trip - 4;
- } else {
- reg_off = EXYNOS5433_THD_TEMP_FALL3_0;
- j = trip;
- }
+static void exynos5433_tmu_disable_high(struct exynos_tmu_data *data)
+{
+ exynos_tmu_update_bit(data, EXYNOS5433_TMU_REG_INTEN,
+ EXYNOS7_TMU_INTEN_RISE0_SHIFT + 1, false);
+}
- th = readl(data->base + reg_off);
- th &= ~(0xff << j * 8);
- th |= (temp_to_code(data, temp - hyst) << j * 8);
- writel(th, data->base + reg_off);
+static void exynos5433_tmu_set_crit_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ exynos_tmu_update_temp(data, EXYNOS5433_THD_TEMP_RISE7_4, 24, temp);
+ exynos_tmu_update_bit(data, EXYNOS_TMU_REG_CONTROL,
+ EXYNOS_TMU_THERM_TRIP_EN_SHIFT, true);
+ exynos_tmu_update_bit(data, EXYNOS5433_TMU_REG_INTEN,
+ EXYNOS7_TMU_INTEN_RISE0_SHIFT + 7, true);
}
static void exynos5433_tmu_initialize(struct platform_device *pdev)
@@ -509,34 +536,41 @@ static void exynos5433_tmu_initialize(struct platform_device *pdev)
cal_type ? 2 : 1);
}
-static void exynos7_tmu_set_trip_temp(struct exynos_tmu_data *data,
- int trip, u8 temp)
+static void exynos7_tmu_set_low_temp(struct exynos_tmu_data *data, u8 temp)
{
- unsigned int reg_off, bit_off;
- u32 th;
-
- reg_off = ((7 - trip) / 2) * 4;
- bit_off = ((8 - trip) % 2);
+ exynos_tmu_update_temp(data, EXYNOS7_THD_TEMP_FALL7_6 + 12, 0, temp);
+ exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_FALL0_SHIFT + 0, true);
+}
- th = readl(data->base + EXYNOS7_THD_TEMP_RISE7_6 + reg_off);
- th &= ~(EXYNOS7_TMU_TEMP_MASK << (16 * bit_off));
- th |= temp_to_code(data, temp) << (16 * bit_off);
- writel(th, data->base + EXYNOS7_THD_TEMP_RISE7_6 + reg_off);
+static void exynos7_tmu_set_high_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ exynos_tmu_update_temp(data, EXYNOS7_THD_TEMP_RISE7_6 + 12, 16, temp);
+ exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+ EXYNOS7_TMU_INTEN_RISE0_SHIFT + 1, true);
}
-static void exynos7_tmu_set_trip_hyst(struct exynos_tmu_data *data,
- int trip, u8 temp, u8 hyst)
+static void exynos7_tmu_disable_low(struct exynos_tmu_data *data)
{
- unsigned int reg_off, bit_off;
- u32 th;
+ exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_FALL0_SHIFT + 0, false);
+}
- reg_off = ((7 - trip) / 2) * 4;
- bit_off = ((8 - trip) % 2);
+static void exynos7_tmu_disable_high(struct exynos_tmu_data *data)
+{
+ exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+ EXYNOS7_TMU_INTEN_RISE0_SHIFT + 1, false);
+}
- th = readl(data->base + EXYNOS7_THD_TEMP_FALL7_6 + reg_off);
- th &= ~(EXYNOS7_TMU_TEMP_MASK << (16 * bit_off));
- th |= temp_to_code(data, temp - hyst) << (16 * bit_off);
- writel(th, data->base + EXYNOS7_THD_TEMP_FALL7_6 + reg_off);
+static void exynos7_tmu_set_crit_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ /*
+ * Like Exynos 4210, Exynos 7 does not seem to support critical temperature
+ * handling in hardware. Again, we still set a separate interrupt for it.
+ */
+ exynos_tmu_update_temp(data, EXYNOS7_THD_TEMP_RISE7_6 + 0, 16, temp);
+ exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+ EXYNOS7_TMU_INTEN_RISE0_SHIFT + 7, true);
}
static void exynos7_tmu_initialize(struct platform_device *pdev)
@@ -551,95 +585,51 @@ static void exynos7_tmu_initialize(struct platform_device *pdev)
static void exynos4210_tmu_control(struct platform_device *pdev, bool on)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
- struct thermal_zone_device *tz = data->tzd;
- struct thermal_trip trip;
- unsigned int con, interrupt_en = 0, i;
+ unsigned int con;
con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
- if (on) {
- for (i = 0; i < data->ntrip; i++) {
- if (thermal_zone_get_trip(tz, i, &trip))
- continue;
-
- interrupt_en |=
- (1 << (EXYNOS_TMU_INTEN_RISE0_SHIFT + i * 4));
- }
-
- if (data->soc != SOC_ARCH_EXYNOS4210)
- interrupt_en |=
- interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
-
- con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
- } else {
- con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
- }
+ if (on)
+ con |= BIT(EXYNOS_TMU_CORE_EN_SHIFT);
+ else
+ con &= ~BIT(EXYNOS_TMU_CORE_EN_SHIFT);
- writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN);
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
}
static void exynos5433_tmu_control(struct platform_device *pdev, bool on)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
- struct thermal_zone_device *tz = data->tzd;
- struct thermal_trip trip;
- unsigned int con, interrupt_en = 0, pd_det_en, i;
+ unsigned int con, pd_det_en;
con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
- if (on) {
- for (i = 0; i < data->ntrip; i++) {
- if (thermal_zone_get_trip(tz, i, &trip))
- continue;
-
- interrupt_en |=
- (1 << (EXYNOS7_TMU_INTEN_RISE0_SHIFT + i));
- }
-
- interrupt_en |=
- interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
-
- con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
- } else
- con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
+ if (on)
+ con |= BIT(EXYNOS_TMU_CORE_EN_SHIFT);
+ else
+ con &= ~BIT(EXYNOS_TMU_CORE_EN_SHIFT);
pd_det_en = on ? EXYNOS5433_PD_DET_EN : 0;
writel(pd_det_en, data->base + EXYNOS5433_TMU_PD_DET_EN);
- writel(interrupt_en, data->base + EXYNOS5433_TMU_REG_INTEN);
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
}
static void exynos7_tmu_control(struct platform_device *pdev, bool on)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
- struct thermal_zone_device *tz = data->tzd;
- struct thermal_trip trip;
- unsigned int con, interrupt_en = 0, i;
+ unsigned int con;
con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
if (on) {
- for (i = 0; i < data->ntrip; i++) {
- if (thermal_zone_get_trip(tz, i, &trip))
- continue;
-
- interrupt_en |=
- (1 << (EXYNOS7_TMU_INTEN_RISE0_SHIFT + i));
- }
-
- interrupt_en |=
- interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
-
- con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
- con |= (1 << EXYNOS7_PD_DET_EN_SHIFT);
+ con |= BIT(EXYNOS_TMU_CORE_EN_SHIFT);
+ con |= BIT(EXYNOS7_PD_DET_EN_SHIFT);
} else {
- con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
- con &= ~(1 << EXYNOS7_PD_DET_EN_SHIFT);
+ con &= ~BIT(EXYNOS_TMU_CORE_EN_SHIFT);
+ con &= ~BIT(EXYNOS7_PD_DET_EN_SHIFT);
}
- writel(interrupt_en, data->base + EXYNOS7_TMU_REG_INTEN);
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
}
@@ -766,10 +756,9 @@ static int exynos7_tmu_read(struct exynos_tmu_data *data)
EXYNOS7_TMU_TEMP_MASK;
}
-static void exynos_tmu_work(struct work_struct *work)
+static irqreturn_t exynos_tmu_threaded_irq(int irq, void *id)
{
- struct exynos_tmu_data *data = container_of(work,
- struct exynos_tmu_data, irq_work);
+ struct exynos_tmu_data *data = id;
thermal_zone_device_update(data->tzd, THERMAL_EVENT_UNSPECIFIED);
@@ -781,7 +770,8 @@ static void exynos_tmu_work(struct work_struct *work)
clk_disable(data->clk);
mutex_unlock(&data->lock);
- enable_irq(data->irq);
+
+ return IRQ_HANDLED;
}
static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
@@ -815,16 +805,6 @@ static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
writel(val_irq, data->base + tmu_intclear);
}
-static irqreturn_t exynos_tmu_irq(int irq, void *id)
-{
- struct exynos_tmu_data *data = id;
-
- disable_irq_nosync(irq);
- schedule_work(&data->irq_work);
-
- return IRQ_HANDLED;
-}
-
static const struct of_device_id exynos_tmu_match[] = {
{
.compatible = "samsung,exynos3250-tmu",
@@ -866,10 +846,6 @@ static int exynos_map_dt_data(struct platform_device *pdev)
if (!data || !pdev->dev.of_node)
return -ENODEV;
- data->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl");
- if (data->id < 0)
- data->id = 0;
-
data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
if (data->irq <= 0) {
dev_err(&pdev->dev, "failed to get IRQ\n");
@@ -887,17 +863,19 @@ static int exynos_map_dt_data(struct platform_device *pdev)
return -EADDRNOTAVAIL;
}
- data->soc = (enum soc_type)of_device_get_match_data(&pdev->dev);
+ data->soc = (uintptr_t)of_device_get_match_data(&pdev->dev);
switch (data->soc) {
case SOC_ARCH_EXYNOS4210:
- data->tmu_set_trip_temp = exynos4210_tmu_set_trip_temp;
- data->tmu_set_trip_hyst = exynos4210_tmu_set_trip_hyst;
+ data->tmu_set_low_temp = exynos4210_tmu_set_low_temp;
+ data->tmu_set_high_temp = exynos4210_tmu_set_high_temp;
+ data->tmu_disable_low = exynos4210_tmu_disable_low;
+ data->tmu_disable_high = exynos4210_tmu_disable_high;
+ data->tmu_set_crit_temp = exynos4210_tmu_set_crit_temp;
data->tmu_initialize = exynos4210_tmu_initialize;
data->tmu_control = exynos4210_tmu_control;
data->tmu_read = exynos4210_tmu_read;
data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
- data->ntrip = 4;
data->gain = 15;
data->reference_voltage = 7;
data->efuse_value = 55;
@@ -910,14 +888,16 @@ static int exynos_map_dt_data(struct platform_device *pdev)
case SOC_ARCH_EXYNOS5260:
case SOC_ARCH_EXYNOS5420:
case SOC_ARCH_EXYNOS5420_TRIMINFO:
- data->tmu_set_trip_temp = exynos4412_tmu_set_trip_temp;
- data->tmu_set_trip_hyst = exynos4412_tmu_set_trip_hyst;
+ data->tmu_set_low_temp = exynos4412_tmu_set_low_temp;
+ data->tmu_set_high_temp = exynos4412_tmu_set_high_temp;
+ data->tmu_disable_low = exynos4412_tmu_disable_low;
+ data->tmu_disable_high = exynos4210_tmu_disable_high;
+ data->tmu_set_crit_temp = exynos4412_tmu_set_crit_temp;
data->tmu_initialize = exynos4412_tmu_initialize;
data->tmu_control = exynos4210_tmu_control;
data->tmu_read = exynos4412_tmu_read;
data->tmu_set_emulation = exynos4412_tmu_set_emulation;
data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
- data->ntrip = 4;
data->gain = 8;
data->reference_voltage = 16;
data->efuse_value = 55;
@@ -929,14 +909,16 @@ static int exynos_map_dt_data(struct platform_device *pdev)
data->max_efuse_value = 100;
break;
case SOC_ARCH_EXYNOS5433:
- data->tmu_set_trip_temp = exynos5433_tmu_set_trip_temp;
- data->tmu_set_trip_hyst = exynos5433_tmu_set_trip_hyst;
+ data->tmu_set_low_temp = exynos5433_tmu_set_low_temp;
+ data->tmu_set_high_temp = exynos5433_tmu_set_high_temp;
+ data->tmu_disable_low = exynos5433_tmu_disable_low;
+ data->tmu_disable_high = exynos5433_tmu_disable_high;
+ data->tmu_set_crit_temp = exynos5433_tmu_set_crit_temp;
data->tmu_initialize = exynos5433_tmu_initialize;
data->tmu_control = exynos5433_tmu_control;
data->tmu_read = exynos4412_tmu_read;
data->tmu_set_emulation = exynos4412_tmu_set_emulation;
data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
- data->ntrip = 8;
data->gain = 8;
if (res.start == EXYNOS5433_G3D_BASE)
data->reference_voltage = 23;
@@ -947,14 +929,16 @@ static int exynos_map_dt_data(struct platform_device *pdev)
data->max_efuse_value = 150;
break;
case SOC_ARCH_EXYNOS7:
- data->tmu_set_trip_temp = exynos7_tmu_set_trip_temp;
- data->tmu_set_trip_hyst = exynos7_tmu_set_trip_hyst;
+ data->tmu_set_low_temp = exynos7_tmu_set_low_temp;
+ data->tmu_set_high_temp = exynos7_tmu_set_high_temp;
+ data->tmu_disable_low = exynos7_tmu_disable_low;
+ data->tmu_disable_high = exynos7_tmu_disable_high;
+ data->tmu_set_crit_temp = exynos7_tmu_set_crit_temp;
data->tmu_initialize = exynos7_tmu_initialize;
data->tmu_control = exynos7_tmu_control;
data->tmu_read = exynos7_tmu_read;
data->tmu_set_emulation = exynos4412_tmu_set_emulation;
data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
- data->ntrip = 8;
data->gain = 9;
data->reference_voltage = 17;
data->efuse_value = 75;
@@ -990,18 +974,41 @@ static int exynos_map_dt_data(struct platform_device *pdev)
return 0;
}
+static int exynos_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct exynos_tmu_data *data = thermal_zone_device_priv(tz);
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+
+ if (low > INT_MIN)
+ data->tmu_set_low_temp(data, low / MCELSIUS);
+ else
+ data->tmu_disable_low(data);
+ if (high < INT_MAX)
+ data->tmu_set_high_temp(data, high / MCELSIUS);
+ else
+ data->tmu_disable_high(data);
+
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+
+ return 0;
+}
+
static const struct thermal_zone_device_ops exynos_sensor_ops = {
.get_temp = exynos_get_temp,
.set_emul_temp = exynos_tmu_set_emulation,
+ .set_trips = exynos_set_trips,
};
static int exynos_tmu_probe(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
struct exynos_tmu_data *data;
int ret;
- data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data),
- GFP_KERNEL);
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
@@ -1013,65 +1020,56 @@ static int exynos_tmu_probe(struct platform_device *pdev)
* TODO: Add regulator as an SOC feature, so that regulator enable
* is a compulsory call.
*/
- data->regulator = devm_regulator_get_optional(&pdev->dev, "vtmu");
- if (!IS_ERR(data->regulator)) {
- ret = regulator_enable(data->regulator);
- if (ret) {
- dev_err(&pdev->dev, "failed to enable vtmu\n");
- return ret;
- }
- } else {
- if (PTR_ERR(data->regulator) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
- dev_info(&pdev->dev, "Regulator node (vtmu) not found\n");
+ ret = devm_regulator_get_enable_optional(dev, "vtmu");
+ switch (ret) {
+ case 0:
+ case -ENODEV:
+ break;
+ case -EPROBE_DEFER:
+ return -EPROBE_DEFER;
+ default:
+ dev_err(dev, "Failed to get enabled regulator: %d\n", ret);
+ return ret;
}
ret = exynos_map_dt_data(pdev);
if (ret)
- goto err_sensor;
-
- INIT_WORK(&data->irq_work, exynos_tmu_work);
+ return ret;
- data->clk = devm_clk_get(&pdev->dev, "tmu_apbif");
- if (IS_ERR(data->clk)) {
- dev_err(&pdev->dev, "Failed to get clock\n");
- ret = PTR_ERR(data->clk);
- goto err_sensor;
- }
+ data->clk = devm_clk_get(dev, "tmu_apbif");
+ if (IS_ERR(data->clk))
+ return dev_err_probe(dev, PTR_ERR(data->clk), "Failed to get clock\n");
- data->clk_sec = devm_clk_get(&pdev->dev, "tmu_triminfo_apbif");
+ data->clk_sec = devm_clk_get(dev, "tmu_triminfo_apbif");
if (IS_ERR(data->clk_sec)) {
- if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) {
- dev_err(&pdev->dev, "Failed to get triminfo clock\n");
- ret = PTR_ERR(data->clk_sec);
- goto err_sensor;
- }
+ if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO)
+ return dev_err_probe(dev, PTR_ERR(data->clk_sec),
+ "Failed to get triminfo clock\n");
} else {
ret = clk_prepare(data->clk_sec);
if (ret) {
- dev_err(&pdev->dev, "Failed to get clock\n");
- goto err_sensor;
+ dev_err(dev, "Failed to get clock\n");
+ return ret;
}
}
ret = clk_prepare(data->clk);
if (ret) {
- dev_err(&pdev->dev, "Failed to get clock\n");
+ dev_err(dev, "Failed to get clock\n");
goto err_clk_sec;
}
switch (data->soc) {
case SOC_ARCH_EXYNOS5433:
case SOC_ARCH_EXYNOS7:
- data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk");
+ data->sclk = devm_clk_get(dev, "tmu_sclk");
if (IS_ERR(data->sclk)) {
- dev_err(&pdev->dev, "Failed to get sclk\n");
- ret = PTR_ERR(data->sclk);
+ ret = dev_err_probe(dev, PTR_ERR(data->sclk), "Failed to get sclk\n");
goto err_clk;
} else {
ret = clk_prepare_enable(data->sclk);
if (ret) {
- dev_err(&pdev->dev, "Failed to enable sclk\n");
+ dev_err(dev, "Failed to enable sclk\n");
goto err_clk;
}
}
@@ -1080,30 +1078,32 @@ static int exynos_tmu_probe(struct platform_device *pdev)
break;
}
- /*
- * data->tzd must be registered before calling exynos_tmu_initialize(),
- * requesting irq and calling exynos_tmu_control().
- */
- data->tzd = devm_thermal_of_zone_register(&pdev->dev, 0, data,
+ ret = exynos_tmu_initialize(pdev);
+ if (ret) {
+ dev_err(dev, "Failed to initialize TMU\n");
+ goto err_sclk;
+ }
+
+ data->tzd = devm_thermal_of_zone_register(dev, 0, data,
&exynos_sensor_ops);
if (IS_ERR(data->tzd)) {
- ret = PTR_ERR(data->tzd);
- if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev, "Failed to register sensor: %d\n",
- ret);
+ ret = dev_err_probe(dev, PTR_ERR(data->tzd), "Failed to register sensor\n");
goto err_sclk;
}
- ret = exynos_tmu_initialize(pdev);
+ ret = exynos_thermal_zone_configure(pdev);
if (ret) {
- dev_err(&pdev->dev, "Failed to initialize TMU\n");
+ dev_err(dev, "Failed to configure the thermal zone\n");
goto err_sclk;
}
- ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
- IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data);
+ ret = devm_request_threaded_irq(dev, data->irq, NULL,
+ exynos_tmu_threaded_irq,
+ IRQF_TRIGGER_RISING
+ | IRQF_SHARED | IRQF_ONESHOT,
+ dev_name(dev), data);
if (ret) {
- dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
+ dev_err(dev, "Failed to request irq: %d\n", data->irq);
goto err_sclk;
}
@@ -1117,14 +1117,10 @@ err_clk:
err_clk_sec:
if (!IS_ERR(data->clk_sec))
clk_unprepare(data->clk_sec);
-err_sensor:
- if (!IS_ERR(data->regulator))
- regulator_disable(data->regulator);
-
return ret;
}
-static int exynos_tmu_remove(struct platform_device *pdev)
+static void exynos_tmu_remove(struct platform_device *pdev)
{
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
@@ -1134,11 +1130,6 @@ static int exynos_tmu_remove(struct platform_device *pdev)
clk_unprepare(data->clk);
if (!IS_ERR(data->clk_sec))
clk_unprepare(data->clk_sec);
-
- if (!IS_ERR(data->regulator))
- regulator_disable(data->regulator);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
@@ -1173,7 +1164,7 @@ static struct platform_driver exynos_tmu_driver = {
.of_match_table = exynos_tmu_match,
},
.probe = exynos_tmu_probe,
- .remove = exynos_tmu_remove,
+ .remove = exynos_tmu_remove,
};
module_platform_driver(exynos_tmu_driver);
diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c
index 6e78616a576e..603dadcd3df5 100644
--- a/drivers/thermal/spear_thermal.c
+++ b/drivers/thermal/spear_thermal.c
@@ -41,7 +41,7 @@ static inline int thermal_get_temp(struct thermal_zone_device *thermal,
return 0;
}
-static struct thermal_zone_device_ops ops = {
+static const struct thermal_zone_device_ops ops = {
.get_temp = thermal_get_temp,
};
@@ -122,8 +122,8 @@ static int spear_thermal_probe(struct platform_device *pdev)
stdev->flags = val;
writel_relaxed(stdev->flags, stdev->thermal_base);
- spear_thermal = thermal_zone_device_register("spear_thermal", 0, 0,
- stdev, &ops, NULL, 0, 0);
+ spear_thermal = thermal_tripless_zone_device_register("spear_thermal",
+ stdev, &ops, NULL);
if (IS_ERR(spear_thermal)) {
dev_err(&pdev->dev, "thermal zone device is NULL\n");
ret = PTR_ERR(spear_thermal);
@@ -150,7 +150,7 @@ disable_clk:
return ret;
}
-static int spear_thermal_exit(struct platform_device *pdev)
+static void spear_thermal_exit(struct platform_device *pdev)
{
unsigned int actual_mask = 0;
struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev);
@@ -163,8 +163,6 @@ static int spear_thermal_exit(struct platform_device *pdev)
writel_relaxed(actual_mask & ~stdev->flags, stdev->thermal_base);
clk_disable(stdev->clk);
-
- return 0;
}
static const struct of_device_id spear_thermal_id_table[] = {
diff --git a/drivers/thermal/sprd_thermal.c b/drivers/thermal/sprd_thermal.c
index 2fb90fdad76e..e546067c9621 100644
--- a/drivers/thermal/sprd_thermal.c
+++ b/drivers/thermal/sprd_thermal.c
@@ -6,7 +6,7 @@
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/thermal.h>
@@ -359,21 +359,17 @@ static int sprd_thm_probe(struct platform_device *pdev)
return -EINVAL;
}
- thm->clk = devm_clk_get(&pdev->dev, "enable");
+ thm->clk = devm_clk_get_enabled(&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;
+ return ret;
if (val > 0)
thm->ratio_sign = -1;
@@ -382,7 +378,7 @@ static int sprd_thm_probe(struct platform_device *pdev)
ret = sprd_thm_cal_read(np, "thm_ratio_cal", &thm->ratio_off);
if (ret)
- goto disable_clk;
+ return ret;
for_each_child_of_node(np, sen_child) {
sen = devm_kzalloc(&pdev->dev, sizeof(*sen), GFP_KERNEL);
@@ -439,8 +435,6 @@ static int sprd_thm_probe(struct platform_device *pdev)
of_put:
of_node_put(sen_child);
-disable_clk:
- clk_disable_unprepare(thm->clk);
return ret;
}
@@ -516,7 +510,7 @@ disable_clk:
}
#endif
-static int sprd_thm_remove(struct platform_device *pdev)
+static void sprd_thm_remove(struct platform_device *pdev)
{
struct sprd_thermal_data *thm = platform_get_drvdata(pdev);
int i;
@@ -526,9 +520,6 @@ static int sprd_thm_remove(struct platform_device *pdev)
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[] = {
diff --git a/drivers/thermal/st/st_thermal.c b/drivers/thermal/st/st_thermal.c
index 0d6249b36609..1470ca519def 100644
--- a/drivers/thermal/st/st_thermal.c
+++ b/drivers/thermal/st/st_thermal.c
@@ -12,6 +12,7 @@
#include <linux/of_device.h>
#include "st_thermal.h"
+#include "../thermal_hwmon.h"
/* The Thermal Framework expects millidegrees */
#define mcelsius(temp) ((temp) * 1000)
@@ -131,12 +132,10 @@ static int st_thermal_get_temp(struct thermal_zone_device *th, int *temperature)
return 0;
}
-static struct thermal_zone_device_ops st_tz_ops = {
+static const struct thermal_zone_device_ops st_tz_ops = {
.get_temp = st_thermal_get_temp,
};
-static struct thermal_trip trip;
-
int st_thermal_register(struct platform_device *pdev,
const struct of_device_id *st_thermal_of_match)
{
@@ -145,7 +144,6 @@ int st_thermal_register(struct platform_device *pdev,
struct device_node *np = dev->of_node;
const struct of_device_id *match;
- int polling_delay;
int ret;
if (!np) {
@@ -197,29 +195,24 @@ int st_thermal_register(struct platform_device *pdev,
if (ret)
goto sensor_off;
- polling_delay = sensor->ops->register_enable_irq ? 0 : 1000;
-
- trip.temperature = sensor->cdata->crit_temp;
- trip.type = THERMAL_TRIP_CRITICAL;
-
sensor->thermal_dev =
- thermal_zone_device_register_with_trips(dev_name(dev), &trip, 1, 0, sensor,
- &st_tz_ops, NULL, 0, polling_delay);
+ devm_thermal_of_zone_register(dev, 0, sensor, &st_tz_ops);
if (IS_ERR(sensor->thermal_dev)) {
- dev_err(dev, "failed to register thermal zone device\n");
+ dev_err(dev, "failed to register thermal of zone\n");
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);
+ /*
+ * devm_thermal_of_zone_register() doesn't enable hwmon by default
+ * Enable it here
+ */
+ devm_thermal_add_hwmon_sysfs(dev, sensor->thermal_dev);
+
return 0;
-tzd_unregister:
- thermal_zone_device_unregister(sensor->thermal_dev);
sensor_off:
st_thermal_sensor_off(sensor);
@@ -232,11 +225,11 @@ void st_thermal_unregister(struct platform_device *pdev)
struct st_thermal_sensor *sensor = platform_get_drvdata(pdev);
st_thermal_sensor_off(sensor);
- thermal_zone_device_unregister(sensor->thermal_dev);
+ thermal_remove_hwmon_sysfs(sensor->thermal_dev);
+ devm_thermal_of_zone_unregister(sensor->dev, sensor->thermal_dev);
}
EXPORT_SYMBOL_GPL(st_thermal_unregister);
-#ifdef CONFIG_PM_SLEEP
static int st_thermal_suspend(struct device *dev)
{
struct st_thermal_sensor *sensor = dev_get_drvdata(dev);
@@ -265,9 +258,8 @@ static int st_thermal_resume(struct device *dev)
return 0;
}
-#endif
-SIMPLE_DEV_PM_OPS(st_thermal_pm_ops, st_thermal_suspend, st_thermal_resume);
+DEFINE_SIMPLE_DEV_PM_OPS(st_thermal_pm_ops, st_thermal_suspend, st_thermal_resume);
EXPORT_SYMBOL_GPL(st_thermal_pm_ops);
MODULE_AUTHOR("STMicroelectronics (R&D) Limited <ajitpal.singh@st.com>");
diff --git a/drivers/thermal/st/st_thermal.h b/drivers/thermal/st/st_thermal.h
index 75a84e6ec6a7..8639d9165c9b 100644
--- a/drivers/thermal/st/st_thermal.h
+++ b/drivers/thermal/st/st_thermal.h
@@ -38,10 +38,10 @@ struct st_thermal_sensor;
*
* @power_ctrl: Function for powering on/off a sensor. Clock to the
* sensor is also controlled from this function.
- * @alloc_regfields: Allocate regmap register fields, specific to a sensor.
- * @do_memmap_regmap: Memory map the thermal register space and init regmap
+ * @alloc_regfields: Allocate regmap register fields, specific to a sensor.
+ * @do_memmap_regmap: Memory map the thermal register space and init regmap
* instance or find regmap instance.
- * @register_irq: Register an interrupt handler for a sensor.
+ * @register_irq: Register an interrupt handler for a sensor.
*/
struct st_thermal_sensor_ops {
int (*power_ctrl)(struct st_thermal_sensor *, enum st_thermal_power_state);
@@ -56,15 +56,15 @@ struct st_thermal_sensor_ops {
*
* @reg_fields: Pointer to the regfields array for a sensor.
* @sys_compat: Pointer to the syscon node compatible string.
- * @ops: Pointer to private thermal ops for a sensor.
- * @calibration_val: Default calibration value to be written to the DCORRECT
+ * @ops: Pointer to private thermal ops for a sensor.
+ * @calibration_val: Default calibration value to be written to the DCORRECT
* register field for a sensor.
- * @temp_adjust_val: Value to be added/subtracted from the data read from
+ * @temp_adjust_val: Value to be added/subtracted from the data read from
* the sensor. If value needs to be added please provide a
* positive value and if it is to be subtracted please
- * provide a negative value.
- * @crit_temp: The temperature beyond which the SoC should be shutdown
- * to prevent damage.
+ * provide a negative value.
+ * @crit_temp: The temperature beyond which the SoC should be shutdown
+ * to prevent damage.
*/
struct st_thermal_compat_data {
char *sys_compat;
diff --git a/drivers/thermal/st/st_thermal_memmap.c b/drivers/thermal/st/st_thermal_memmap.c
index e8cfa83b724a..8f76e50ea567 100644
--- a/drivers/thermal/st/st_thermal_memmap.c
+++ b/drivers/thermal/st/st_thermal_memmap.c
@@ -27,7 +27,7 @@ static const struct reg_field st_mmap_thermal_regfields[MAX_REGFIELDS] = {
* written simultaneously for powering on and off the temperature
* sensor. regmap_update_bits() will be used to update the register.
*/
- [INT_THRESH_HI] = REG_FIELD(STIH416_MPE_INT_THRESH, 0, 7),
+ [INT_THRESH_HI] = REG_FIELD(STIH416_MPE_INT_THRESH, 0, 7),
[DCORRECT] = REG_FIELD(STIH416_MPE_CONF, 5, 9),
[OVERFLOW] = REG_FIELD(STIH416_MPE_STATUS, 9, 9),
[DATA] = REG_FIELD(STIH416_MPE_STATUS, 11, 18),
@@ -142,15 +142,6 @@ static const struct st_thermal_sensor_ops st_mmap_sensor_ops = {
.enable_irq = st_mmap_enable_irq,
};
-/* Compatible device data stih416 mpe thermal sensor */
-static const struct st_thermal_compat_data st_416mpe_cdata = {
- .reg_fields = st_mmap_thermal_regfields,
- .ops = &st_mmap_sensor_ops,
- .calibration_val = 14,
- .temp_adjust_val = -95,
- .crit_temp = 120,
-};
-
/* Compatible device data stih407 thermal sensor */
static const struct st_thermal_compat_data st_407_cdata = {
.reg_fields = st_mmap_thermal_regfields,
@@ -161,7 +152,6 @@ static const struct st_thermal_compat_data st_407_cdata = {
};
static const struct of_device_id st_mmap_thermal_of_match[] = {
- { .compatible = "st,stih416-mpe-thermal", .data = &st_416mpe_cdata },
{ .compatible = "st,stih407-thermal", .data = &st_407_cdata },
{ /* sentinel */ }
};
@@ -180,11 +170,11 @@ static void st_mmap_remove(struct platform_device *pdev)
static struct platform_driver st_mmap_thermal_driver = {
.driver = {
.name = "st_thermal_mmap",
- .pm = &st_thermal_pm_ops,
+ .pm = pm_sleep_ptr(&st_thermal_pm_ops),
.of_match_table = st_mmap_thermal_of_match,
},
.probe = st_mmap_probe,
- .remove_new = st_mmap_remove,
+ .remove = st_mmap_remove,
};
module_platform_driver(st_mmap_thermal_driver);
diff --git a/drivers/thermal/st/stm_thermal.c b/drivers/thermal/st/stm_thermal.c
index 903fcf1763f1..6e90eb9f414d 100644
--- a/drivers/thermal/st/stm_thermal.c
+++ b/drivers/thermal/st/stm_thermal.c
@@ -14,8 +14,6 @@
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_address.h>
-#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/thermal.h>
@@ -442,7 +440,6 @@ thermal_unprepare:
return ret;
}
-#ifdef CONFIG_PM_SLEEP
static int stm_thermal_suspend(struct device *dev)
{
struct stm_thermal_sensor *sensor = dev_get_drvdata(dev);
@@ -468,10 +465,9 @@ static int stm_thermal_resume(struct device *dev)
return 0;
}
-#endif /* CONFIG_PM_SLEEP */
-static SIMPLE_DEV_PM_OPS(stm_thermal_pm_ops,
- stm_thermal_suspend, stm_thermal_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(stm_thermal_pm_ops,
+ stm_thermal_suspend, stm_thermal_resume);
static const struct thermal_zone_device_ops stm_tz_ops = {
.get_temp = stm_thermal_get_temp,
@@ -571,20 +567,18 @@ err_tz:
return ret;
}
-static int stm_thermal_remove(struct platform_device *pdev)
+static void stm_thermal_remove(struct platform_device *pdev)
{
struct stm_thermal_sensor *sensor = platform_get_drvdata(pdev);
stm_thermal_sensor_off(sensor);
thermal_remove_hwmon_sysfs(sensor->th_dev);
-
- return 0;
}
static struct platform_driver stm_thermal_driver = {
.driver = {
.name = "stm_thermal",
- .pm = &stm_thermal_pm_ops,
+ .pm = pm_sleep_ptr(&stm_thermal_pm_ops),
.of_match_table = stm_thermal_of_match,
},
.probe = stm_thermal_probe,
diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c
index 195f3c5d0b38..22674790629a 100644
--- a/drivers/thermal/sun8i_thermal.c
+++ b/drivers/thermal/sun8i_thermal.c
@@ -9,12 +9,14 @@
*/
#include <linux/bitmap.h>
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
@@ -50,14 +52,13 @@
#define SUN8I_THS_CTRL2_T_ACQ1(x) ((GENMASK(15, 0) & (x)) << 16)
#define SUN8I_THS_DATA_IRQ_STS(x) BIT(x + 8)
-#define SUN50I_THS_CTRL0_T_ACQ(x) ((GENMASK(15, 0) & (x)) << 16)
+#define SUN50I_THS_CTRL0_T_ACQ(x) (GENMASK(15, 0) & ((x) - 1))
+#define SUN50I_THS_CTRL0_T_SAMPLE_PER(x) ((GENMASK(15, 0) & ((x) - 1)) << 16)
#define SUN50I_THS_FILTER_EN BIT(2)
#define SUN50I_THS_FILTER_TYPE(x) (GENMASK(1, 0) & (x))
#define SUN50I_H6_THS_PC_TEMP_PERIOD(x) ((GENMASK(19, 0) & (x)) << 12)
#define SUN50I_H6_THS_DATA_IRQ_STS(x) BIT(x)
-/* millidegree celsius */
-
struct tsensor {
struct ths_device *tmdev;
struct thermal_zone_device *tzd;
@@ -67,6 +68,7 @@ struct tsensor {
struct ths_thermal_chip {
bool has_mod_clk;
bool has_bus_clk_reset;
+ bool needs_sram;
int sensor_num;
int offset;
int scale;
@@ -84,12 +86,16 @@ struct ths_device {
const struct ths_thermal_chip *chip;
struct device *dev;
struct regmap *regmap;
+ struct regmap_field *sram_regmap_field;
struct reset_control *reset;
struct clk *bus_clk;
struct clk *mod_clk;
struct tsensor sensor[MAX_SENSOR_NUM];
};
+/* The H616 needs to have a bit 16 in the SRAM control register cleared. */
+static const struct reg_field sun8i_ths_sram_reg_field = REG_FIELD(0x0, 16, 16);
+
/* Temp Unit: millidegree Celsius */
static int sun8i_ths_calc_temp(struct ths_device *tmdev,
int id, int reg)
@@ -190,6 +196,9 @@ static irqreturn_t sun8i_irq_thread(int irq, void *data)
int i;
for_each_set_bit(i, &irq_bitmap, tmdev->chip->sensor_num) {
+ /* We allow some zones to not register. */
+ if (IS_ERR(tmdev->sensor[i].tzd))
+ continue;
thermal_zone_device_update(tmdev->sensor[i].tzd,
THERMAL_EVENT_UNSPECIFIED);
}
@@ -223,16 +232,21 @@ static int sun50i_h6_ths_calibrate(struct ths_device *tmdev,
struct device *dev = tmdev->dev;
int i, ft_temp;
- if (!caldata[0] || callen < 2 + 2 * tmdev->chip->sensor_num)
+ if (!caldata[0])
return -EINVAL;
/*
* efuse layout:
*
- * 0 11 16 32
- * +-------+-------+-------+
- * |temp| |sensor0|sensor1|
- * +-------+-------+-------+
+ * 0 11 16 27 32 43 48 57
+ * +----------+-----------+-----------+-----------+
+ * | temp | |sensor0| |sensor1| |sensor2| |
+ * +----------+-----------+-----------+-----------+
+ * ^ ^ ^
+ * | | |
+ * | | sensor3[11:8]
+ * | sensor3[7:4]
+ * sensor3[3:0]
*
* The calibration data on the H6 is the ambient temperature and
* sensor values that are filled during the factory test stage.
@@ -245,9 +259,16 @@ 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] & TEMP_CALIB_MASK;
- int cdata, offset;
- int sensor_temp = tmdev->chip->calc_temp(tmdev, i, sensor_reg);
+ int sensor_reg, sensor_temp, cdata, offset;
+
+ if (i == 3)
+ sensor_reg = (caldata[1] >> 12)
+ | ((caldata[2] >> 12) << 4)
+ | ((caldata[3] >> 12) << 8);
+ else
+ sensor_reg = caldata[i + 1] & TEMP_CALIB_MASK;
+
+ sensor_temp = tmdev->chip->calc_temp(tmdev, i, sensor_reg);
/*
* Calibration data is CALIBRATE_DEFAULT - (calculated
@@ -286,7 +307,7 @@ static int sun8i_ths_calibrate(struct ths_device *tmdev)
size_t callen;
int ret = 0;
- calcell = devm_nvmem_cell_get(dev, "calibration");
+ calcell = nvmem_cell_get(dev, "calibration");
if (IS_ERR(calcell)) {
if (PTR_ERR(calcell) == -EPROBE_DEFER)
return -EPROBE_DEFER;
@@ -316,6 +337,8 @@ static int sun8i_ths_calibrate(struct ths_device *tmdev)
kfree(caldata);
out:
+ if (!IS_ERR(calcell))
+ nvmem_cell_put(calcell);
return ret;
}
@@ -324,6 +347,32 @@ static void sun8i_ths_reset_control_assert(void *data)
reset_control_assert(data);
}
+static struct regmap *sun8i_ths_get_sram_regmap(struct device_node *node)
+{
+ struct platform_device *sram_pdev;
+ struct regmap *regmap = NULL;
+
+ struct device_node *sram_node __free(device_node) =
+ of_parse_phandle(node, "allwinner,sram", 0);
+ if (!sram_node)
+ return ERR_PTR(-ENODEV);
+
+ sram_pdev = of_find_device_by_node(sram_node);
+ if (!sram_pdev) {
+ /* platform device might not be probed yet */
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ /* If no regmap is found then the other device driver is at fault */
+ regmap = dev_get_regmap(&sram_pdev->dev, NULL);
+ if (!regmap)
+ regmap = ERR_PTR(-EINVAL);
+
+ platform_device_put(sram_pdev);
+
+ return regmap;
+}
+
static int sun8i_ths_resource_init(struct ths_device *tmdev)
{
struct device *dev = tmdev->dev;
@@ -368,6 +417,19 @@ static int sun8i_ths_resource_init(struct ths_device *tmdev)
if (ret)
return ret;
+ if (tmdev->chip->needs_sram) {
+ struct regmap *regmap;
+
+ regmap = sun8i_ths_get_sram_regmap(dev->of_node);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+ tmdev->sram_regmap_field = devm_regmap_field_alloc(dev,
+ regmap,
+ sun8i_ths_sram_reg_field);
+ if (IS_ERR(tmdev->sram_regmap_field))
+ return PTR_ERR(tmdev->sram_regmap_field);
+ }
+
ret = sun8i_ths_calibrate(tmdev);
if (ret)
return ret;
@@ -410,25 +472,31 @@ static int sun8i_h3_thermal_init(struct ths_device *tmdev)
return 0;
}
-/*
- * Without this undocumented value, the returned temperatures would
- * be higher than real ones by about 20C.
- */
-#define SUN50I_H6_CTRL0_UNK 0x0000002f
-
static int sun50i_h6_thermal_init(struct ths_device *tmdev)
{
int val;
+ /* The H616 needs to have a bit in the SRAM control register cleared. */
+ if (tmdev->sram_regmap_field)
+ regmap_field_write(tmdev->sram_regmap_field, 0);
+
/*
- * T_acq = 20us
- * clkin = 24MHz
- *
- * x = T_acq * clkin - 1
- * = 479
+ * The manual recommends an overall sample frequency of 50 KHz (20us,
+ * 480 cycles at 24 MHz), which provides plenty of time for both the
+ * acquisition time (>24 cycles) and the actual conversion time
+ * (>14 cycles).
+ * The lower half of the CTRL register holds the "acquire time", in
+ * clock cycles, which the manual recommends to be 2us:
+ * 24MHz * 2us = 48 cycles.
+ * The high half of THS_CTRL encodes the sample frequency, in clock
+ * cycles: 24MHz * 20us = 480 cycles.
+ * This is explained in the H616 manual, but apparently wrongly
+ * described in the H6 manual, although the BSP code does the same
+ * for both SoCs.
*/
regmap_write(tmdev->regmap, SUN50I_THS_CTRL0,
- SUN50I_H6_CTRL0_UNK | SUN50I_THS_CTRL0_T_ACQ(479));
+ SUN50I_THS_CTRL0_T_ACQ(48) |
+ SUN50I_THS_CTRL0_T_SAMPLE_PER(480));
/* average over 4 samples */
regmap_write(tmdev->regmap, SUN50I_H6_THS_MFC,
SUN50I_THS_FILTER_EN |
@@ -465,8 +533,17 @@ static int sun8i_ths_register(struct ths_device *tmdev)
i,
&tmdev->sensor[i],
&ths_ops);
- if (IS_ERR(tmdev->sensor[i].tzd))
- return PTR_ERR(tmdev->sensor[i].tzd);
+
+ /*
+ * If an individual zone fails to register for reasons
+ * other than probe deferral (eg, a bad DT) then carry
+ * on, other zones might register successfully.
+ */
+ if (IS_ERR(tmdev->sensor[i].tzd)) {
+ if (PTR_ERR(tmdev->sensor[i].tzd) == -EPROBE_DEFER)
+ return PTR_ERR(tmdev->sensor[i].tzd);
+ continue;
+ }
devm_thermal_add_hwmon_sysfs(tmdev->dev, tmdev->sensor[i].tzd);
}
@@ -489,8 +566,6 @@ static int sun8i_ths_probe(struct platform_device *pdev)
if (!tmdev->chip)
return -EINVAL;
- platform_set_drvdata(pdev, tmdev);
-
ret = sun8i_ths_resource_init(tmdev);
if (ret)
return ret;
@@ -608,6 +683,32 @@ static const struct ths_thermal_chip sun50i_h6_ths = {
.calc_temp = sun8i_ths_calc_temp,
};
+static const struct ths_thermal_chip sun20i_d1_ths = {
+ .sensor_num = 1,
+ .has_bus_clk_reset = true,
+ .offset = 188552,
+ .scale = 673,
+ .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_h616_ths = {
+ .sensor_num = 4,
+ .has_bus_clk_reset = true,
+ .needs_sram = true,
+ .ft_deviation = 8000,
+ .offset = 263655,
+ .scale = 810,
+ .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 of_device_id of_ths_match[] = {
{ .compatible = "allwinner,sun8i-a83t-ths", .data = &sun8i_a83t_ths },
{ .compatible = "allwinner,sun8i-h3-ths", .data = &sun8i_h3_ths },
@@ -616,6 +717,8 @@ static const struct of_device_id of_ths_match[] = {
{ .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 },
+ { .compatible = "allwinner,sun20i-d1-ths", .data = &sun20i_d1_ths },
+ { .compatible = "allwinner,sun50i-h616-ths", .data = &sun50i_h616_ths },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, of_ths_match);
diff --git a/drivers/thermal/tegra/Makefile b/drivers/thermal/tegra/Makefile
index eb27d194c583..9b3e91f7fb97 100644
--- a/drivers/thermal/tegra/Makefile
+++ b/drivers/thermal/tegra/Makefile
@@ -4,6 +4,7 @@ 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_114_SOC) += tegra114-soctherm.o
tegra-soctherm-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124-soctherm.o
tegra-soctherm-$(CONFIG_ARCH_TEGRA_132_SOC) += tegra132-soctherm.o
tegra-soctherm-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210-soctherm.o
diff --git a/drivers/thermal/tegra/soctherm-fuse.c b/drivers/thermal/tegra/soctherm-fuse.c
index 190f95280e0b..8d37cd8c9122 100644
--- a/drivers/thermal/tegra/soctherm-fuse.c
+++ b/drivers/thermal/tegra/soctherm-fuse.c
@@ -9,15 +9,12 @@
#include "soctherm.h"
-#define NOMINAL_CALIB_FT 105
#define NOMINAL_CALIB_CP 25
#define FUSE_TSENSOR_CALIB_CP_TS_BASE_MASK 0x1fff
#define FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK (0x1fff << 13)
#define FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT 13
-#define FUSE_TSENSOR_COMMON 0x180
-
/*
* Tegra210: Layout of bits in FUSE_TSENSOR_COMMON:
* 3 2 1 0
@@ -26,7 +23,7 @@
* | BASE_FT | BASE_CP | SHFT_FT | SHIFT_CP |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
- * Tegra12x, etc:
+ * Tegra124:
* In chips prior to Tegra210, this fuse was incorrectly sized as 26 bits,
* and didn't hold SHIFT_CP in [31:26]. Therefore these missing six bits
* were obtained via the FUSE_SPARE_REALIGNMENT_REG register [5:0].
@@ -44,6 +41,13 @@
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |---------------------------------------------------| SHIFT_CP |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Tegra114: Layout of bits in FUSE_TSENSOR_COMMON aka FUSE_VSENSOR_CALIB:
+ * 3 2 1 0
+ * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | SHFT_FT | BASE_FT | SHIFT_CP | BASE_CP |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
#define CALIB_COEFFICIENT 1000000LL
@@ -77,7 +81,7 @@ int tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse,
s32 shifted_cp, shifted_ft;
int err;
- err = tegra_fuse_readl(FUSE_TSENSOR_COMMON, &val);
+ err = tegra_fuse_readl(tfuse->fuse_common_reg, &val);
if (err)
return err;
@@ -96,10 +100,12 @@ int tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse,
return err;
}
+ shifted_cp = (val & tfuse->fuse_shift_cp_mask) >>
+ tfuse->fuse_shift_cp_shift;
shifted_cp = sign_extend32(val, 5);
shared->actual_temp_cp = 2 * NOMINAL_CALIB_CP + shifted_cp;
- shared->actual_temp_ft = 2 * NOMINAL_CALIB_FT + shifted_ft;
+ shared->actual_temp_ft = 2 * tfuse->nominal_calib_ft + shifted_ft;
return 0;
}
diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c
index ea66cba09e56..5d26b52beaba 100644
--- a/drivers/thermal/tegra/soctherm.c
+++ b/drivers/thermal/tegra/soctherm.c
@@ -31,6 +31,7 @@
#include <linux/reset.h>
#include <linux/thermal.h>
+#include <dt-bindings/thermal/tegra114-soctherm.h>
#include <dt-bindings/thermal/tegra124-soctherm.h>
#include "../thermal_core.h"
@@ -357,6 +358,12 @@ struct soctherm_oc_irq_chip_data {
static struct soctherm_oc_irq_chip_data soc_irq_cdata;
+/* Ensure that TEGRA114_* and TEGRA124_* counterparts are equal */
+static_assert(TEGRA114_SOCTHERM_SENSOR_CPU == TEGRA124_SOCTHERM_SENSOR_CPU);
+static_assert(TEGRA114_SOCTHERM_SENSOR_MEM == TEGRA124_SOCTHERM_SENSOR_MEM);
+static_assert(TEGRA114_SOCTHERM_SENSOR_GPU == TEGRA124_SOCTHERM_SENSOR_GPU);
+static_assert(TEGRA114_SOCTHERM_SENSOR_PLLX == TEGRA124_SOCTHERM_SENSOR_PLLX);
+
/**
* ccroc_writel() - writes a value to a CCROC register
* @ts: pointer to a struct tegra_soctherm
@@ -582,23 +589,18 @@ static int tsensor_group_thermtrip_get(struct tegra_soctherm *ts, int id)
return temp;
}
-static int tegra_thermctl_set_trip_temp(struct thermal_zone_device *tz, int trip_id, int temp)
+static int tegra_thermctl_set_trip_temp(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip, int temp)
{
struct tegra_thermctl_zone *zone = thermal_zone_device_priv(tz);
struct tegra_soctherm *ts = zone->ts;
- struct thermal_trip trip;
const struct tegra_tsensor_group *sg = zone->sg;
struct device *dev = zone->dev;
- int ret;
if (!tz)
return -EINVAL;
- ret = __thermal_zone_get_trip(tz, trip_id, &trip);
- if (ret)
- return ret;
-
- if (trip.type == THERMAL_TRIP_CRITICAL) {
+ if (trip->type == THERMAL_TRIP_CRITICAL) {
/*
* If thermtrips property is set in DT,
* doesn't need to program critical type trip to HW,
@@ -609,7 +611,7 @@ static int tegra_thermctl_set_trip_temp(struct thermal_zone_device *tz, int trip
else
return 0;
- } else if (trip.type == THERMAL_TRIP_HOT) {
+ } else if (trip->type == THERMAL_TRIP_HOT) {
int i;
for (i = 0; i < THROTTLE_SIZE; i++) {
@@ -620,7 +622,7 @@ static int tegra_thermctl_set_trip_temp(struct thermal_zone_device *tz, int trip
continue;
cdev = ts->throt_cfgs[i].cdev;
- if (get_thermal_instance(tz, cdev, trip_id))
+ if (thermal_trip_is_bound_to_cdev(tz, trip, cdev))
stc = find_throttle_cfg_by_name(ts, cdev->type);
else
continue;
@@ -687,24 +689,25 @@ static const struct thermal_zone_device_ops tegra_of_thermal_ops = {
.set_trips = tegra_thermctl_set_trips,
};
-static int get_hot_temp(struct thermal_zone_device *tz, int *trip_id, int *temp)
+static int get_hot_trip_cb(struct thermal_trip *trip, void *arg)
{
- int i, ret;
- struct thermal_trip trip;
+ const struct thermal_trip **trip_ret = arg;
- for (i = 0; i < thermal_zone_get_num_trips(tz); i++) {
+ if (trip->type != THERMAL_TRIP_HOT)
+ return 0;
- ret = thermal_zone_get_trip(tz, i, &trip);
- if (ret)
- return -EINVAL;
+ *trip_ret = trip;
+ /* Return nonzero to terminate the search. */
+ return 1;
+}
- if (trip.type == THERMAL_TRIP_HOT) {
- *trip_id = i;
- return 0;
- }
- }
+static const struct thermal_trip *get_hot_trip(struct thermal_zone_device *tz)
+{
+ const struct thermal_trip *trip = NULL;
- return -EINVAL;
+ thermal_zone_for_each_trip(tz, get_hot_trip_cb, &trip);
+
+ return trip;
}
/**
@@ -736,8 +739,9 @@ static int tegra_soctherm_set_hwtrips(struct device *dev,
struct thermal_zone_device *tz)
{
struct tegra_soctherm *ts = dev_get_drvdata(dev);
+ const struct thermal_trip *hot_trip;
struct soctherm_throt_cfg *stc;
- int i, trip, temperature, ret;
+ int i, temperature, ret;
/* Get thermtrips. If missing, try to get critical trips. */
temperature = tsensor_group_thermtrip_get(ts, sg->id);
@@ -754,8 +758,8 @@ static int tegra_soctherm_set_hwtrips(struct device *dev,
dev_info(dev, "thermtrip: will shut down when %s reaches %d mC\n",
sg->name, temperature);
- ret = get_hot_temp(tz, &trip, &temperature);
- if (ret) {
+ hot_trip = get_hot_trip(tz);
+ if (!hot_trip) {
dev_info(dev, "throttrip: %s: missing hot temperature\n",
sg->name);
return 0;
@@ -768,7 +772,7 @@ static int tegra_soctherm_set_hwtrips(struct device *dev,
continue;
cdev = ts->throt_cfgs[i].cdev;
- if (get_thermal_instance(tz, cdev, trip))
+ if (thermal_trip_is_bound_to_cdev(tz, hot_trip, cdev))
stc = find_throttle_cfg_by_name(ts, cdev->type);
else
continue;
@@ -1209,7 +1213,7 @@ static const struct irq_domain_ops soctherm_oc_domain_ops = {
/**
* soctherm_oc_int_init() - Initial enabling of the over
* current interrupts
- * @np: The devicetree node for soctherm
+ * @fwnode: The devicetree node for soctherm
* @num_irqs: The number of new interrupt requests
*
* Sets the over current interrupt request chip data
@@ -1218,7 +1222,7 @@ static const struct irq_domain_ops soctherm_oc_domain_ops = {
* -ENOMEM (out of memory), or irq_base if the function failed to
* allocate the irqs
*/
-static int soctherm_oc_int_init(struct device_node *np, int num_irqs)
+static int soctherm_oc_int_init(struct fwnode_handle *fwnode, int num_irqs)
{
if (!num_irqs) {
pr_info("%s(): OC interrupts are not enabled\n", __func__);
@@ -1237,10 +1241,8 @@ static int soctherm_oc_int_init(struct device_node *np, int num_irqs)
soc_irq_cdata.irq_chip.irq_set_type = soctherm_oc_irq_set_type;
soc_irq_cdata.irq_chip.irq_set_wake = NULL;
- soc_irq_cdata.domain = irq_domain_add_linear(np, num_irqs,
- &soctherm_oc_domain_ops,
- &soc_irq_cdata);
-
+ soc_irq_cdata.domain = irq_domain_create_linear(fwnode, num_irqs, &soctherm_oc_domain_ops,
+ &soc_irq_cdata);
if (!soc_irq_cdata.domain) {
pr_err("%s: Failed to create IRQ domain\n", __func__);
return -ENOMEM;
@@ -1654,7 +1656,7 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct tegra_soctherm *ts = dev_get_drvdata(dev);
- struct device_node *np_stc, *np_stcc;
+ struct device_node *np_stc;
const char *name;
int i;
@@ -1671,7 +1673,7 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev)
return;
}
- for_each_child_of_node(np_stc, np_stcc) {
+ for_each_child_of_node_scoped(np_stc, np_stcc) {
struct soctherm_throt_cfg *stc;
struct thermal_cooling_device *tcd;
int err;
@@ -1686,7 +1688,6 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev)
if (stc->init) {
dev_err(dev, "throttle-cfg: %s: redefined!\n", name);
- of_node_put(np_stcc);
break;
}
@@ -1972,10 +1973,9 @@ static void tegra_soctherm_throttle(struct device *dev)
static int soctherm_interrupts_init(struct platform_device *pdev,
struct tegra_soctherm *tegra)
{
- struct device_node *np = pdev->dev.of_node;
int ret;
- ret = soctherm_oc_int_init(np, TEGRA_SOC_OC_IRQ_MAX);
+ ret = soctherm_oc_int_init(dev_fwnode(&pdev->dev), TEGRA_SOC_OC_IRQ_MAX);
if (ret < 0) {
dev_err(&pdev->dev, "soctherm_oc_int_init failed\n");
return ret;
@@ -2052,6 +2052,12 @@ static void soctherm_init(struct platform_device *pdev)
}
static const struct of_device_id tegra_soctherm_of_match[] = {
+#ifdef CONFIG_ARCH_TEGRA_114_SOC
+ {
+ .compatible = "nvidia,tegra114-soctherm",
+ .data = &tegra114_soctherm,
+ },
+#endif
#ifdef CONFIG_ARCH_TEGRA_124_SOC
{
.compatible = "nvidia,tegra124-soctherm",
@@ -2219,15 +2225,13 @@ disable_clocks:
return err;
}
-static int tegra_soctherm_remove(struct platform_device *pdev)
+static void tegra_soctherm_remove(struct platform_device *pdev)
{
struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
debugfs_remove_recursive(tegra->debugfs_dir);
soctherm_clk_enable(pdev, false);
-
- return 0;
}
static int __maybe_unused soctherm_suspend(struct device *dev)
diff --git a/drivers/thermal/tegra/soctherm.h b/drivers/thermal/tegra/soctherm.h
index 70501e73d586..aa4af9268b05 100644
--- a/drivers/thermal/tegra/soctherm.h
+++ b/drivers/thermal/tegra/soctherm.h
@@ -56,6 +56,9 @@
#define SENSOR_TEMP2_MEM_TEMP_MASK (0xffff << 16)
#define SENSOR_TEMP2_PLLX_TEMP_MASK 0xffff
+#define FUSE_VSENSOR_CALIB 0x08c
+#define FUSE_TSENSOR_COMMON 0x180
+
/**
* struct tegra_tsensor_group - SOC_THERM sensor group data
* @name: short name of the temperature sensor group
@@ -109,9 +112,11 @@ struct tsensor_group_thermtrips {
struct tegra_soctherm_fuse {
u32 fuse_base_cp_mask, fuse_base_cp_shift;
+ u32 fuse_shift_cp_mask, fuse_shift_cp_shift;
u32 fuse_base_ft_mask, fuse_base_ft_shift;
u32 fuse_shift_ft_mask, fuse_shift_ft_shift;
- u32 fuse_spare_realignment;
+ u32 fuse_common_reg, fuse_spare_realignment;
+ u32 nominal_calib_ft;
};
struct tsensor_shared_calib {
@@ -137,6 +142,10 @@ int tegra_calc_tsensor_calib(const struct tegra_tsensor *sensor,
const struct tsensor_shared_calib *shared,
u32 *calib);
+#ifdef CONFIG_ARCH_TEGRA_114_SOC
+extern const struct tegra_soctherm_soc tegra114_soctherm;
+#endif
+
#ifdef CONFIG_ARCH_TEGRA_124_SOC
extern const struct tegra_soctherm_soc tegra124_soctherm;
#endif
diff --git a/drivers/thermal/tegra/tegra-bpmp-thermal.c b/drivers/thermal/tegra/tegra-bpmp-thermal.c
index a2879d624945..997d77ce30d9 100644
--- a/drivers/thermal/tegra/tegra-bpmp-thermal.c
+++ b/drivers/thermal/tegra/tegra-bpmp-thermal.c
@@ -167,19 +167,69 @@ static int tegra_bpmp_thermal_get_num_zones(struct tegra_bpmp *bpmp,
return 0;
}
+static int tegra_bpmp_thermal_trips_supported(struct tegra_bpmp *bpmp, bool *supported)
+{
+ struct mrq_thermal_host_to_bpmp_request req;
+ union mrq_thermal_bpmp_to_host_response reply;
+ struct tegra_bpmp_message msg;
+ int err;
+
+ memset(&req, 0, sizeof(req));
+ req.type = CMD_THERMAL_QUERY_ABI;
+ req.query_abi.type = CMD_THERMAL_SET_TRIP;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.mrq = MRQ_THERMAL;
+ msg.tx.data = &req;
+ msg.tx.size = sizeof(req);
+ msg.rx.data = &reply;
+ msg.rx.size = sizeof(reply);
+
+ err = tegra_bpmp_transfer(bpmp, &msg);
+ if (err)
+ return err;
+
+ if (msg.rx.ret == 0) {
+ *supported = true;
+ return 0;
+ } else if (msg.rx.ret == -BPMP_ENODEV) {
+ *supported = false;
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+}
+
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,
};
+static const struct thermal_zone_device_ops tegra_bpmp_of_thermal_ops_notrips = {
+ .get_temp = tegra_bpmp_thermal_get_temp,
+};
+
static int tegra_bpmp_thermal_probe(struct platform_device *pdev)
{
struct tegra_bpmp *bpmp = dev_get_drvdata(pdev->dev.parent);
+ const struct thermal_zone_device_ops *thermal_ops;
struct tegra_bpmp_thermal *tegra;
struct thermal_zone_device *tzd;
unsigned int i, max_num_zones;
+ bool supported;
int err;
+ err = tegra_bpmp_thermal_trips_supported(bpmp, &supported);
+ if (err) {
+ dev_err(&pdev->dev, "failed to determine if trip points are supported\n");
+ return err;
+ }
+
+ if (supported)
+ thermal_ops = &tegra_bpmp_of_thermal_ops;
+ else
+ thermal_ops = &tegra_bpmp_of_thermal_ops_notrips;
+
tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
if (!tegra)
return -ENOMEM;
@@ -222,7 +272,7 @@ static int tegra_bpmp_thermal_probe(struct platform_device *pdev)
}
tzd = devm_thermal_of_zone_register(
- &pdev->dev, i, zone, &tegra_bpmp_of_thermal_ops);
+ &pdev->dev, i, zone, thermal_ops);
if (IS_ERR(tzd)) {
if (PTR_ERR(tzd) == -EPROBE_DEFER)
return -EPROBE_DEFER;
@@ -250,13 +300,11 @@ static int tegra_bpmp_thermal_probe(struct platform_device *pdev)
return 0;
}
-static int tegra_bpmp_thermal_remove(struct platform_device *pdev)
+static void tegra_bpmp_thermal_remove(struct platform_device *pdev)
{
struct tegra_bpmp_thermal *tegra = platform_get_drvdata(pdev);
tegra_bpmp_free_mrq(tegra->bpmp, MRQ_THERMAL, tegra);
-
- return 0;
}
static const struct of_device_id tegra_bpmp_thermal_of_match[] = {
diff --git a/drivers/thermal/tegra/tegra114-soctherm.c b/drivers/thermal/tegra/tegra114-soctherm.c
new file mode 100644
index 000000000000..688104f28052
--- /dev/null
+++ b/drivers/thermal/tegra/tegra114-soctherm.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2024, Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <dt-bindings/thermal/tegra114-soctherm.h>
+
+#include "soctherm.h"
+
+#define TEGRA114_THERMTRIP_ANY_EN_MASK (0x1 << 28)
+#define TEGRA114_THERMTRIP_MEM_EN_MASK (0x1 << 27)
+#define TEGRA114_THERMTRIP_GPU_EN_MASK (0x1 << 26)
+#define TEGRA114_THERMTRIP_CPU_EN_MASK (0x1 << 25)
+#define TEGRA114_THERMTRIP_TSENSE_EN_MASK (0x1 << 24)
+#define TEGRA114_THERMTRIP_GPUMEM_THRESH_MASK (0xff << 16)
+#define TEGRA114_THERMTRIP_CPU_THRESH_MASK (0xff << 8)
+#define TEGRA114_THERMTRIP_TSENSE_THRESH_MASK 0xff
+
+#define TEGRA114_THERMCTL_LVL0_UP_THRESH_MASK (0xff << 17)
+#define TEGRA114_THERMCTL_LVL0_DN_THRESH_MASK (0xff << 9)
+
+#define TEGRA114_THRESH_GRAIN 1000
+#define TEGRA114_BPTT 8
+
+static const struct tegra_tsensor_configuration tegra114_tsensor_config = {
+ .tall = 16300,
+ .tiddq_en = 1,
+ .ten_count = 1,
+ .tsample = 163,
+ .tsample_ate = 655,
+};
+
+static const struct tegra_tsensor_group tegra114_tsensor_group_cpu = {
+ .id = TEGRA114_SOCTHERM_SENSOR_CPU,
+ .name = "cpu",
+ .sensor_temp_offset = SENSOR_TEMP1,
+ .sensor_temp_mask = SENSOR_TEMP1_CPU_TEMP_MASK,
+ .pdiv = 10,
+ .pdiv_ate = 10,
+ .pdiv_mask = SENSOR_PDIV_CPU_MASK,
+ .pllx_hotspot_diff = 6,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_CPU_MASK,
+ .thermtrip_any_en_mask = TEGRA114_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA114_THERMTRIP_CPU_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA114_THERMTRIP_CPU_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_CPU_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_CPU,
+ .thermctl_lvl0_up_thresh_mask = TEGRA114_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA114_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra114_tsensor_group_gpu = {
+ .id = TEGRA114_SOCTHERM_SENSOR_GPU,
+ .name = "gpu",
+ .sensor_temp_offset = SENSOR_TEMP1,
+ .sensor_temp_mask = SENSOR_TEMP1_GPU_TEMP_MASK,
+ .pdiv = 10,
+ .pdiv_ate = 10,
+ .pdiv_mask = SENSOR_PDIV_GPU_MASK,
+ .pllx_hotspot_diff = 6,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_GPU_MASK,
+ .thermtrip_any_en_mask = TEGRA114_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA114_THERMTRIP_GPU_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA114_THERMTRIP_GPUMEM_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_GPU_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_GPU,
+ .thermctl_lvl0_up_thresh_mask = TEGRA114_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA114_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra114_tsensor_group_pll = {
+ .id = TEGRA114_SOCTHERM_SENSOR_PLLX,
+ .name = "pll",
+ .sensor_temp_offset = SENSOR_TEMP2,
+ .sensor_temp_mask = SENSOR_TEMP2_PLLX_TEMP_MASK,
+ .pdiv = 10,
+ .pdiv_ate = 10,
+ .pdiv_mask = SENSOR_PDIV_PLLX_MASK,
+ .thermtrip_any_en_mask = TEGRA114_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA114_THERMTRIP_TSENSE_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA114_THERMTRIP_TSENSE_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_TSENSE_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_TSENSE,
+ .thermctl_lvl0_up_thresh_mask = TEGRA114_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA114_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra114_tsensor_group_mem = {
+ .id = TEGRA114_SOCTHERM_SENSOR_MEM,
+ .name = "mem",
+ .sensor_temp_offset = SENSOR_TEMP2,
+ .sensor_temp_mask = SENSOR_TEMP2_MEM_TEMP_MASK,
+ .pdiv = 10,
+ .pdiv_ate = 10,
+ .pdiv_mask = SENSOR_PDIV_MEM_MASK,
+ .pllx_hotspot_diff = 0,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_MEM_MASK,
+ .thermtrip_any_en_mask = TEGRA114_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA114_THERMTRIP_MEM_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA114_THERMTRIP_GPUMEM_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_MEM_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_MEM,
+ .thermctl_lvl0_up_thresh_mask = TEGRA114_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA114_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group *tegra114_tsensor_groups[] = {
+ &tegra114_tsensor_group_cpu,
+ &tegra114_tsensor_group_gpu,
+ &tegra114_tsensor_group_pll,
+ &tegra114_tsensor_group_mem,
+};
+
+static const struct tegra_tsensor tegra114_tsensors[] = {
+ {
+ .name = "cpu0",
+ .base = 0xc0,
+ .config = &tegra114_tsensor_config,
+ .calib_fuse_offset = 0x098,
+ .fuse_corr_alpha = 1196400,
+ .fuse_corr_beta = -13600000,
+ .group = &tegra114_tsensor_group_cpu,
+ }, {
+ .name = "cpu1",
+ .base = 0xe0,
+ .config = &tegra114_tsensor_config,
+ .calib_fuse_offset = 0x084,
+ .fuse_corr_alpha = 1196400,
+ .fuse_corr_beta = -13600000,
+ .group = &tegra114_tsensor_group_cpu,
+ }, {
+ .name = "cpu2",
+ .base = 0x100,
+ .config = &tegra114_tsensor_config,
+ .calib_fuse_offset = 0x088,
+ .fuse_corr_alpha = 1196400,
+ .fuse_corr_beta = -13600000,
+ .group = &tegra114_tsensor_group_cpu,
+ }, {
+ .name = "cpu3",
+ .base = 0x120,
+ .config = &tegra114_tsensor_config,
+ .calib_fuse_offset = 0x12c,
+ .fuse_corr_alpha = 1196400,
+ .fuse_corr_beta = -13600000,
+ .group = &tegra114_tsensor_group_cpu,
+ }, {
+ .name = "mem0",
+ .base = 0x140,
+ .config = &tegra114_tsensor_config,
+ .calib_fuse_offset = 0x158,
+ .fuse_corr_alpha = 1000000,
+ .fuse_corr_beta = 0,
+ .group = &tegra114_tsensor_group_mem,
+ }, {
+ .name = "mem1",
+ .base = 0x160,
+ .config = &tegra114_tsensor_config,
+ .calib_fuse_offset = 0x15c,
+ .fuse_corr_alpha = 1000000,
+ .fuse_corr_beta = 0,
+ .group = &tegra114_tsensor_group_mem,
+ }, {
+ .name = "gpu",
+ .base = 0x180,
+ .config = &tegra114_tsensor_config,
+ .calib_fuse_offset = 0x154,
+ .fuse_corr_alpha = 1124500,
+ .fuse_corr_beta = -9793100,
+ .group = &tegra114_tsensor_group_gpu,
+ }, {
+ .name = "pllx",
+ .base = 0x1a0,
+ .config = &tegra114_tsensor_config,
+ .calib_fuse_offset = 0x160,
+ .fuse_corr_alpha = 1224200,
+ .fuse_corr_beta = -14665000,
+ .group = &tegra114_tsensor_group_pll,
+ },
+};
+
+static const struct tegra_soctherm_fuse tegra114_soctherm_fuse = {
+ .fuse_base_cp_mask = 0x3ff,
+ .fuse_base_cp_shift = 0,
+ .fuse_shift_cp_mask = 0x3f << 10,
+ .fuse_shift_cp_shift = 10,
+ .fuse_base_ft_mask = 0x7ff << 16,
+ .fuse_base_ft_shift = 16,
+ .fuse_shift_ft_mask = 0x1f << 27,
+ .fuse_shift_ft_shift = 27,
+ .fuse_common_reg = FUSE_VSENSOR_CALIB,
+ .fuse_spare_realignment = 0,
+ .nominal_calib_ft = 90,
+};
+
+const struct tegra_soctherm_soc tegra114_soctherm = {
+ .tsensors = tegra114_tsensors,
+ .num_tsensors = ARRAY_SIZE(tegra114_tsensors),
+ .ttgs = tegra114_tsensor_groups,
+ .num_ttgs = ARRAY_SIZE(tegra114_tsensor_groups),
+ .tfuse = &tegra114_soctherm_fuse,
+ .thresh_grain = TEGRA114_THRESH_GRAIN,
+ .bptt = TEGRA114_BPTT,
+ .use_ccroc = false,
+};
diff --git a/drivers/thermal/tegra/tegra124-soctherm.c b/drivers/thermal/tegra/tegra124-soctherm.c
index 20ad27f4d1a1..d86acff1b234 100644
--- a/drivers/thermal/tegra/tegra124-soctherm.c
+++ b/drivers/thermal/tegra/tegra124-soctherm.c
@@ -200,11 +200,15 @@ static const struct tegra_tsensor tegra124_tsensors[] = {
static const struct tegra_soctherm_fuse tegra124_soctherm_fuse = {
.fuse_base_cp_mask = 0x3ff,
.fuse_base_cp_shift = 0,
+ .fuse_shift_cp_mask = 0x3f,
+ .fuse_shift_cp_shift = 0,
.fuse_base_ft_mask = 0x7ff << 10,
.fuse_base_ft_shift = 10,
.fuse_shift_ft_mask = 0x1f << 21,
.fuse_shift_ft_shift = 21,
+ .fuse_common_reg = FUSE_TSENSOR_COMMON,
.fuse_spare_realignment = 0x1fc,
+ .nominal_calib_ft = 105,
};
const struct tegra_soctherm_soc tegra124_soctherm = {
diff --git a/drivers/thermal/tegra/tegra132-soctherm.c b/drivers/thermal/tegra/tegra132-soctherm.c
index b76308fdad9e..64c0363b9717 100644
--- a/drivers/thermal/tegra/tegra132-soctherm.c
+++ b/drivers/thermal/tegra/tegra132-soctherm.c
@@ -200,11 +200,15 @@ static struct tegra_tsensor tegra132_tsensors[] = {
static const struct tegra_soctherm_fuse tegra132_soctherm_fuse = {
.fuse_base_cp_mask = 0x3ff,
.fuse_base_cp_shift = 0,
+ .fuse_shift_cp_mask = 0x3f,
+ .fuse_shift_cp_shift = 0,
.fuse_base_ft_mask = 0x7ff << 10,
.fuse_base_ft_shift = 10,
.fuse_shift_ft_mask = 0x1f << 21,
.fuse_shift_ft_shift = 21,
+ .fuse_common_reg = FUSE_TSENSOR_COMMON,
.fuse_spare_realignment = 0x1fc,
+ .nominal_calib_ft = 105,
};
const struct tegra_soctherm_soc tegra132_soctherm = {
diff --git a/drivers/thermal/tegra/tegra210-soctherm.c b/drivers/thermal/tegra/tegra210-soctherm.c
index d0ff793f18c5..f6e1493f0202 100644
--- a/drivers/thermal/tegra/tegra210-soctherm.c
+++ b/drivers/thermal/tegra/tegra210-soctherm.c
@@ -201,11 +201,15 @@ static const struct tegra_tsensor tegra210_tsensors[] = {
static const struct tegra_soctherm_fuse tegra210_soctherm_fuse = {
.fuse_base_cp_mask = 0x3ff << 11,
.fuse_base_cp_shift = 11,
+ .fuse_shift_cp_mask = 0x3f,
+ .fuse_shift_cp_shift = 0,
.fuse_base_ft_mask = 0x7ff << 21,
.fuse_base_ft_shift = 21,
.fuse_shift_ft_mask = 0x1f << 6,
.fuse_shift_ft_shift = 6,
+ .fuse_common_reg = FUSE_TSENSOR_COMMON,
.fuse_spare_realignment = 0,
+ .nominal_calib_ft = 105,
};
static struct tsensor_group_thermtrips tegra210_tsensor_thermtrips[] = {
diff --git a/drivers/thermal/tegra/tegra30-tsensor.c b/drivers/thermal/tegra/tegra30-tsensor.c
index c243e9d76d3c..6245f6b97f43 100644
--- a/drivers/thermal/tegra/tegra30-tsensor.c
+++ b/drivers/thermal/tegra/tegra30-tsensor.c
@@ -18,7 +18,7 @@
#include <linux/iopoll.h>
#include <linux/math.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/reset.h>
@@ -303,33 +303,37 @@ stop_channel:
return 0;
}
-static void tegra_tsensor_get_hw_channel_trips(struct thermal_zone_device *tzd,
- int *hot_trip, int *crit_trip)
+struct trip_temps {
+ int hot_trip;
+ int crit_trip;
+};
+
+static int tegra_tsensor_get_trips_cb(struct thermal_trip *trip, void *arg)
{
- unsigned int i;
+ struct trip_temps *temps = arg;
+
+ if (trip->type == THERMAL_TRIP_HOT)
+ temps->hot_trip = trip->temperature;
+ else if (trip->type == THERMAL_TRIP_CRITICAL)
+ temps->crit_trip = trip->temperature;
+
+ return 0;
+}
+static void tegra_tsensor_get_hw_channel_trips(struct thermal_zone_device *tzd,
+ struct trip_temps *temps)
+{
/*
* 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 < thermal_zone_get_num_trips(tzd); i++) {
-
- struct thermal_trip trip;
+ temps->hot_trip = 85000;
+ temps->crit_trip = 90000;
- thermal_zone_get_trip(tzd, i, &trip);
-
- if (trip.type == THERMAL_TRIP_HOT)
- *hot_trip = trip.temperature;
-
- if (trip.type == THERMAL_TRIP_CRITICAL)
- *crit_trip = trip.temperature;
- }
+ thermal_zone_for_each_trip(tzd, tegra_tsensor_get_trips_cb, temps);
/* clamp hardware trips to the calibration limits */
- *hot_trip = clamp(*hot_trip, 25000, 90000);
+ temps->hot_trip = clamp(temps->hot_trip, 25000, 90000);
/*
* Kernel will perform a normal system shut down if it will
@@ -338,7 +342,7 @@ static void tegra_tsensor_get_hw_channel_trips(struct thermal_zone_device *tzd,
* shut down gracefully before sending signal to the Power
* Management controller.
*/
- *crit_trip = clamp(*crit_trip + 5000, 25000, 90000);
+ temps->crit_trip = clamp(temps->crit_trip + 5000, 25000, 90000);
}
static int tegra_tsensor_enable_hw_channel(const struct tegra_tsensor *ts,
@@ -346,7 +350,8 @@ static int tegra_tsensor_enable_hw_channel(const struct tegra_tsensor *ts,
{
const struct tegra_tsensor_channel *tsc = &ts->ch[id];
struct thermal_zone_device *tzd = tsc->tzd;
- int err, hot_trip = 0, crit_trip = 0;
+ struct trip_temps temps = { 0 };
+ int err;
u32 val;
if (!tzd) {
@@ -357,24 +362,24 @@ static int tegra_tsensor_enable_hw_channel(const struct tegra_tsensor *ts,
return 0;
}
- tegra_tsensor_get_hw_channel_trips(tzd, &hot_trip, &crit_trip);
+ tegra_tsensor_get_hw_channel_trips(tzd, &temps);
dev_info_once(ts->dev, "ch%u: PMC emergency shutdown trip set to %dC\n",
- id, DIV_ROUND_CLOSEST(crit_trip, 1000));
+ id, DIV_ROUND_CLOSEST(temps.crit_trip, 1000));
- hot_trip = tegra_tsensor_temp_to_counter(ts, hot_trip);
- crit_trip = tegra_tsensor_temp_to_counter(ts, crit_trip);
+ temps.hot_trip = tegra_tsensor_temp_to_counter(ts, temps.hot_trip);
+ temps.crit_trip = tegra_tsensor_temp_to_counter(ts, temps.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);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG1_TH2, temps.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);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG2_TH3, temps.crit_trip);
writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG2);
/*
diff --git a/drivers/thermal/testing/Makefile b/drivers/thermal/testing/Makefile
new file mode 100644
index 000000000000..ede9678efbce
--- /dev/null
+++ b/drivers/thermal/testing/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Thermal core testing facility.
+
+obj-$(CONFIG_THERMAL_CORE_TESTING) += thermal-testing.o
+
+thermal-testing-y := command.o zone.o
diff --git a/drivers/thermal/testing/command.c b/drivers/thermal/testing/command.c
new file mode 100644
index 000000000000..1159ecea57e7
--- /dev/null
+++ b/drivers/thermal/testing/command.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2024, Intel Corporation
+ *
+ * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+ *
+ * Thermal subsystem testing facility.
+ *
+ * This facility allows the thermal core functionality to be exercised in a
+ * controlled way in order to verify its behavior.
+ *
+ * It resides in the "thermal-testing" directory under the debugfs root and
+ * starts with a single file called "command" which can be written a string
+ * representing a thermal testing facility command.
+ *
+ * The currently supported commands are listed in the tt_commands enum below.
+ *
+ * The "addtz" command causes a new test thermal zone template to be created,
+ * for example:
+ *
+ * # echo addtz > /sys/kernel/debug/thermal-testing/command
+ *
+ * That template will be represented as a subdirectory in the "thermal-testing"
+ * directory, for example
+ *
+ * # ls /sys/kernel/debug/thermal-testing/
+ * command tz0
+ *
+ * The thermal zone template can be populated with trip points with the help of
+ * the "tzaddtrip" command, for example:
+ *
+ * # echo tzaddtrip:0 > /sys/kernel/debug/thermal-testing/command
+ *
+ * which causes a trip point template to be added to the test thermal zone
+ * template 0 (represented by the tz0 subdirectory in "thermal-testing").
+ *
+ * # ls /sys/kernel/debug/thermal-testing/tz0
+ * init_temp temp trip_0_temp trip_0_hyst
+ *
+ * The temperature of a trip point template is initially THERMAL_TEMP_INVALID
+ * and its hysteresis is initially 0. They can be adjusted by writing to the
+ * "trip_x_temp" and "trip_x_hyst" files correspoinding to that trip point
+ * template, respectively.
+ *
+ * The initial temperature of a thermal zone based on a template can be set by
+ * writing to the "init_temp" file in its directory under "thermal-testing", for
+ * example:
+ *
+ * echo 50000 > /sys/kernel/debug/thermal-testing/tz0/init_temp
+ *
+ * When ready, "tzreg" command can be used for registering and enabling a
+ * thermal zone based on a given template with the thermal core, for example
+ *
+ * # echo tzreg:0 > /sys/kernel/debug/thermal-testing/command
+ *
+ * In this case, test thermal zone template 0 is used for registering a new
+ * thermal zone and the set of trip point templates associated with it is used
+ * for populating the new thermal zone's trip points table. The type of the new
+ * thermal zone is "test_tz".
+ *
+ * The temperature and hysteresis of all of the trip points in that new thermal
+ * zone are adjustable via sysfs, so they can be updated at any time.
+ *
+ * The current temperature of the new thermal zone can be set by writing to the
+ * "temp" file in the corresponding thermal zone template's directory under
+ * "thermal-testing", for example
+ *
+ * echo 10000 > /sys/kernel/debug/thermal-testing/tz0/temp
+ *
+ * which will also trigger a temperature update for this zone in the thermal
+ * core, including checking its trip points, sending notifications to user space
+ * if any of them have been crossed and so on.
+ *
+ * When it is not needed any more, a test thermal zone template can be deleted
+ * with the help of the "deltz" command, for example
+ *
+ * # echo deltz:0 > /sys/kernel/debug/thermal-testing/command
+ *
+ * which will also unregister the thermal zone based on it, if present.
+ */
+
+#define pr_fmt(fmt) "thermal-testing: " fmt
+
+#include <linux/debugfs.h>
+#include <linux/module.h>
+
+#include "thermal_testing.h"
+
+struct dentry *d_testing;
+
+#define TT_COMMAND_SIZE 16
+
+enum tt_commands {
+ TT_CMD_ADDTZ,
+ TT_CMD_DELTZ,
+ TT_CMD_TZADDTRIP,
+ TT_CMD_TZREG,
+ TT_CMD_TZUNREG,
+};
+
+static const char *tt_command_strings[] = {
+ [TT_CMD_ADDTZ] = "addtz",
+ [TT_CMD_DELTZ] = "deltz",
+ [TT_CMD_TZADDTRIP] = "tzaddtrip",
+ [TT_CMD_TZREG] = "tzreg",
+ [TT_CMD_TZUNREG] = "tzunreg",
+};
+
+static int tt_command_exec(int index, const char *arg)
+{
+ int ret;
+
+ switch (index) {
+ case TT_CMD_ADDTZ:
+ ret = tt_add_tz();
+ break;
+
+ case TT_CMD_DELTZ:
+ ret = tt_del_tz(arg);
+ break;
+
+ case TT_CMD_TZADDTRIP:
+ ret = tt_zone_add_trip(arg);
+ break;
+
+ case TT_CMD_TZREG:
+ ret = tt_zone_reg(arg);
+ break;
+
+ case TT_CMD_TZUNREG:
+ ret = tt_zone_unreg(arg);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static ssize_t tt_command_process(char *s)
+{
+ char *arg;
+ int i;
+
+ strim(s);
+
+ arg = strchr(s, ':');
+ if (arg) {
+ *arg = '\0';
+ arg++;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tt_command_strings); i++) {
+ if (!strcmp(s, tt_command_strings[i]))
+ return tt_command_exec(i, arg);
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t tt_command_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ char buf[TT_COMMAND_SIZE];
+ ssize_t ret;
+
+ if (*ppos)
+ return -EINVAL;
+
+ if (count > TT_COMMAND_SIZE - 1)
+ return -E2BIG;
+
+ if (copy_from_user(buf, user_buf, count))
+ return -EFAULT;
+ buf[count] = '\0';
+
+ ret = tt_command_process(buf);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static const struct file_operations tt_command_fops = {
+ .write = tt_command_write,
+ .open = simple_open,
+ .llseek = default_llseek,
+};
+
+static int __init thermal_testing_init(void)
+{
+ d_testing = debugfs_create_dir("thermal-testing", NULL);
+ if (!IS_ERR(d_testing))
+ debugfs_create_file("command", 0200, d_testing, NULL,
+ &tt_command_fops);
+
+ return 0;
+}
+module_init(thermal_testing_init);
+
+static void __exit thermal_testing_exit(void)
+{
+ debugfs_remove(d_testing);
+ tt_zone_cleanup();
+}
+module_exit(thermal_testing_exit);
+
+MODULE_DESCRIPTION("Thermal core testing facility");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/testing/thermal_testing.h b/drivers/thermal/testing/thermal_testing.h
new file mode 100644
index 000000000000..c790a32aae4e
--- /dev/null
+++ b/drivers/thermal/testing/thermal_testing.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+extern struct dentry *d_testing;
+
+int tt_add_tz(void);
+int tt_del_tz(const char *arg);
+int tt_zone_add_trip(const char *arg);
+int tt_zone_reg(const char *arg);
+int tt_zone_unreg(const char *arg);
+
+void tt_zone_cleanup(void);
diff --git a/drivers/thermal/testing/zone.c b/drivers/thermal/testing/zone.c
new file mode 100644
index 000000000000..c12c405225bb
--- /dev/null
+++ b/drivers/thermal/testing/zone.c
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2024, Intel Corporation
+ *
+ * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+ *
+ * Thermal zone tempalates handling for thermal core testing.
+ */
+
+#define pr_fmt(fmt) "thermal-testing: " fmt
+
+#include <linux/debugfs.h>
+#include <linux/idr.h>
+#include <linux/list.h>
+#include <linux/thermal.h>
+#include <linux/workqueue.h>
+
+#include "thermal_testing.h"
+
+#define TT_MAX_FILE_NAME_LENGTH 16
+
+/**
+ * struct tt_thermal_zone - Testing thermal zone template
+ *
+ * Represents a template of a thermal zone that can be used for registering
+ * a test thermal zone with the thermal core.
+ *
+ * @list_node: Node in the list of all testing thermal zone templates.
+ * @trips: List of trip point templates for this thermal zone template.
+ * @d_tt_zone: Directory in debugfs representing this template.
+ * @tz: Test thermal zone based on this template, if present.
+ * @lock: Mutex for synchronizing changes of this template.
+ * @ida: IDA for trip point IDs.
+ * @id: The ID of this template for the debugfs interface.
+ * @temp: Temperature value.
+ * @tz_temp: Current thermal zone temperature (after registration).
+ * @num_trips: Number of trip points in the @trips list.
+ * @refcount: Reference counter for usage and removal synchronization.
+ */
+struct tt_thermal_zone {
+ struct list_head list_node;
+ struct list_head trips;
+ struct dentry *d_tt_zone;
+ struct thermal_zone_device *tz;
+ struct mutex lock;
+ struct ida ida;
+ int id;
+ int temp;
+ int tz_temp;
+ unsigned int num_trips;
+ unsigned int refcount;
+};
+
+DEFINE_GUARD(tt_zone, struct tt_thermal_zone *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
+
+/**
+ * struct tt_trip - Testing trip point template
+ *
+ * Represents a template of a trip point to be used for populating a trip point
+ * during the registration of a thermal zone based on a given zone template.
+ *
+ * @list_node: Node in the list of all trip templates in the zone template.
+ * @trip: Trip point data to use for thernal zone registration.
+ * @id: The ID of this trip template for the debugfs interface.
+ */
+struct tt_trip {
+ struct list_head list_node;
+ struct thermal_trip trip;
+ int id;
+};
+
+/*
+ * It is both questionable and potentially problematic from the sychnronization
+ * perspective to attempt to manipulate debugfs from within a debugfs file
+ * "write" operation, so auxiliary work items are used for that. The majority
+ * of zone-related command functions have a part that runs from a workqueue and
+ * make changes in debugs, among other things.
+ */
+struct tt_work {
+ struct work_struct work;
+ struct tt_thermal_zone *tt_zone;
+ struct tt_trip *tt_trip;
+};
+
+static inline struct tt_work *tt_work_of_work(struct work_struct *work)
+{
+ return container_of(work, struct tt_work, work);
+}
+
+static LIST_HEAD(tt_thermal_zones);
+static DEFINE_IDA(tt_thermal_zones_ida);
+static DEFINE_MUTEX(tt_thermal_zones_lock);
+
+static int tt_int_get(void *data, u64 *val)
+{
+ *val = *(int *)data;
+ return 0;
+}
+static int tt_int_set(void *data, u64 val)
+{
+ if ((int)val < THERMAL_TEMP_INVALID)
+ return -EINVAL;
+
+ *(int *)data = val;
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_int_attr, tt_int_get, tt_int_set, "%lld\n");
+DEFINE_DEBUGFS_ATTRIBUTE(tt_unsigned_int_attr, tt_int_get, tt_int_set, "%llu\n");
+
+static int tt_zone_tz_temp_get(void *data, u64 *val)
+{
+ struct tt_thermal_zone *tt_zone = data;
+
+ guard(tt_zone)(tt_zone);
+
+ if (!tt_zone->tz)
+ return -EBUSY;
+
+ *val = tt_zone->tz_temp;
+
+ return 0;
+}
+static int tt_zone_tz_temp_set(void *data, u64 val)
+{
+ struct tt_thermal_zone *tt_zone = data;
+
+ guard(tt_zone)(tt_zone);
+
+ if (!tt_zone->tz)
+ return -EBUSY;
+
+ WRITE_ONCE(tt_zone->tz_temp, val);
+ thermal_zone_device_update(tt_zone->tz, THERMAL_EVENT_TEMP_SAMPLE);
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(tt_zone_tz_temp_attr, tt_zone_tz_temp_get,
+ tt_zone_tz_temp_set, "%lld\n");
+
+static void tt_zone_free_trips(struct tt_thermal_zone *tt_zone)
+{
+ struct tt_trip *tt_trip, *aux;
+
+ list_for_each_entry_safe(tt_trip, aux, &tt_zone->trips, list_node) {
+ list_del(&tt_trip->list_node);
+ ida_free(&tt_zone->ida, tt_trip->id);
+ kfree(tt_trip);
+ }
+}
+
+static void tt_zone_free(struct tt_thermal_zone *tt_zone)
+{
+ tt_zone_free_trips(tt_zone);
+ ida_free(&tt_thermal_zones_ida, tt_zone->id);
+ ida_destroy(&tt_zone->ida);
+ kfree(tt_zone);
+}
+
+static void tt_add_tz_work_fn(struct work_struct *work)
+{
+ struct tt_work *tt_work = tt_work_of_work(work);
+ struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
+ char f_name[TT_MAX_FILE_NAME_LENGTH];
+
+ kfree(tt_work);
+
+ snprintf(f_name, TT_MAX_FILE_NAME_LENGTH, "tz%d", tt_zone->id);
+ tt_zone->d_tt_zone = debugfs_create_dir(f_name, d_testing);
+ if (IS_ERR(tt_zone->d_tt_zone)) {
+ tt_zone_free(tt_zone);
+ return;
+ }
+
+ debugfs_create_file_unsafe("temp", 0600, tt_zone->d_tt_zone, tt_zone,
+ &tt_zone_tz_temp_attr);
+
+ debugfs_create_file_unsafe("init_temp", 0600, tt_zone->d_tt_zone,
+ &tt_zone->temp, &tt_int_attr);
+
+ guard(mutex)(&tt_thermal_zones_lock);
+
+ list_add_tail(&tt_zone->list_node, &tt_thermal_zones);
+}
+
+int tt_add_tz(void)
+{
+ int ret;
+
+ struct tt_thermal_zone *tt_zone __free(kfree) = kzalloc(sizeof(*tt_zone),
+ GFP_KERNEL);
+ if (!tt_zone)
+ return -ENOMEM;
+
+ struct tt_work *tt_work __free(kfree) = kzalloc(sizeof(*tt_work), GFP_KERNEL);
+ if (!tt_work)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&tt_zone->trips);
+ mutex_init(&tt_zone->lock);
+ ida_init(&tt_zone->ida);
+ tt_zone->temp = THERMAL_TEMP_INVALID;
+
+ ret = ida_alloc(&tt_thermal_zones_ida, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ tt_zone->id = ret;
+
+ INIT_WORK(&tt_work->work, tt_add_tz_work_fn);
+ tt_work->tt_zone = no_free_ptr(tt_zone);
+ schedule_work(&(no_free_ptr(tt_work)->work));
+
+ return 0;
+}
+
+static void tt_del_tz_work_fn(struct work_struct *work)
+{
+ struct tt_work *tt_work = tt_work_of_work(work);
+ struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
+
+ kfree(tt_work);
+
+ debugfs_remove(tt_zone->d_tt_zone);
+ tt_zone_free(tt_zone);
+}
+
+static void tt_zone_unregister_tz(struct tt_thermal_zone *tt_zone)
+{
+ guard(tt_zone)(tt_zone);
+
+ if (tt_zone->tz) {
+ thermal_zone_device_unregister(tt_zone->tz);
+ tt_zone->tz = NULL;
+ }
+}
+
+int tt_del_tz(const char *arg)
+{
+ struct tt_thermal_zone *tt_zone, *aux;
+ int ret;
+ int id;
+
+ ret = sscanf(arg, "%d", &id);
+ if (ret != 1)
+ return -EINVAL;
+
+ struct tt_work *tt_work __free(kfree) = kzalloc(sizeof(*tt_work), GFP_KERNEL);
+ if (!tt_work)
+ return -ENOMEM;
+
+ guard(mutex)(&tt_thermal_zones_lock);
+
+ ret = -EINVAL;
+ list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
+ if (tt_zone->id == id) {
+ if (tt_zone->refcount) {
+ ret = -EBUSY;
+ } else {
+ list_del(&tt_zone->list_node);
+ ret = 0;
+ }
+ break;
+ }
+ }
+
+ if (ret)
+ return ret;
+
+ tt_zone_unregister_tz(tt_zone);
+
+ INIT_WORK(&tt_work->work, tt_del_tz_work_fn);
+ tt_work->tt_zone = tt_zone;
+ schedule_work(&(no_free_ptr(tt_work)->work));
+
+ return 0;
+}
+
+static struct tt_thermal_zone *tt_get_tt_zone(const char *arg)
+{
+ struct tt_thermal_zone *tt_zone;
+ int ret, id;
+
+ ret = sscanf(arg, "%d", &id);
+ if (ret != 1)
+ return ERR_PTR(-EINVAL);
+
+ guard(mutex)(&tt_thermal_zones_lock);
+
+ list_for_each_entry(tt_zone, &tt_thermal_zones, list_node) {
+ if (tt_zone->id == id) {
+ tt_zone->refcount++;
+ return tt_zone;
+ }
+ }
+
+ return ERR_PTR(-EINVAL);
+}
+
+static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone)
+{
+ guard(mutex)(&tt_thermal_zones_lock);
+
+ tt_zone->refcount--;
+}
+
+DEFINE_FREE(put_tt_zone, struct tt_thermal_zone *,
+ if (!IS_ERR_OR_NULL(_T)) tt_put_tt_zone(_T))
+
+static void tt_zone_add_trip_work_fn(struct work_struct *work)
+{
+ struct tt_work *tt_work = tt_work_of_work(work);
+ struct tt_thermal_zone *tt_zone = tt_work->tt_zone;
+ struct tt_trip *tt_trip = tt_work->tt_trip;
+ char d_name[TT_MAX_FILE_NAME_LENGTH];
+
+ kfree(tt_work);
+
+ snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_temp", tt_trip->id);
+ debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
+ &tt_trip->trip.temperature, &tt_int_attr);
+
+ snprintf(d_name, TT_MAX_FILE_NAME_LENGTH, "trip_%d_hyst", tt_trip->id);
+ debugfs_create_file_unsafe(d_name, 0600, tt_zone->d_tt_zone,
+ &tt_trip->trip.hysteresis, &tt_unsigned_int_attr);
+
+ tt_put_tt_zone(tt_zone);
+}
+
+int tt_zone_add_trip(const char *arg)
+{
+ int id;
+
+ struct tt_work *tt_work __free(kfree) = kzalloc(sizeof(*tt_work), GFP_KERNEL);
+ if (!tt_work)
+ return -ENOMEM;
+
+ struct tt_trip *tt_trip __free(kfree) = kzalloc(sizeof(*tt_trip), GFP_KERNEL);
+ if (!tt_trip)
+ return -ENOMEM;
+
+ struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
+ if (IS_ERR(tt_zone))
+ return PTR_ERR(tt_zone);
+
+ id = ida_alloc(&tt_zone->ida, GFP_KERNEL);
+ if (id < 0)
+ return id;
+
+ tt_trip->trip.type = THERMAL_TRIP_ACTIVE;
+ tt_trip->trip.temperature = THERMAL_TEMP_INVALID;
+ tt_trip->trip.flags = THERMAL_TRIP_FLAG_RW;
+ tt_trip->id = id;
+
+ guard(tt_zone)(tt_zone);
+
+ list_add_tail(&tt_trip->list_node, &tt_zone->trips);
+ tt_zone->num_trips++;
+
+ INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn);
+ tt_work->tt_zone = no_free_ptr(tt_zone);
+ tt_work->tt_trip = no_free_ptr(tt_trip);
+ schedule_work(&(no_free_ptr(tt_work)->work));
+
+ return 0;
+}
+
+static int tt_zone_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct tt_thermal_zone *tt_zone = thermal_zone_device_priv(tz);
+
+ *temp = READ_ONCE(tt_zone->tz_temp);
+
+ if (*temp < THERMAL_TEMP_INVALID)
+ return -ENODATA;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops tt_zone_ops = {
+ .get_temp = tt_zone_get_temp,
+};
+
+static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone)
+{
+ struct thermal_zone_device *tz;
+ struct tt_trip *tt_trip;
+ int i;
+
+ guard(tt_zone)(tt_zone);
+
+ if (tt_zone->tz)
+ return -EINVAL;
+
+ struct thermal_trip *trips __free(kfree) = kcalloc(tt_zone->num_trips,
+ sizeof(*trips), GFP_KERNEL);
+ if (!trips)
+ return -ENOMEM;
+
+ i = 0;
+ list_for_each_entry(tt_trip, &tt_zone->trips, list_node)
+ trips[i++] = tt_trip->trip;
+
+ tt_zone->tz_temp = tt_zone->temp;
+
+ tz = thermal_zone_device_register_with_trips("test_tz", trips, i, tt_zone,
+ &tt_zone_ops, NULL, 0, 0);
+ if (IS_ERR(tz))
+ return PTR_ERR(tz);
+
+ tt_zone->tz = tz;
+
+ thermal_zone_device_enable(tz);
+
+ return 0;
+}
+
+int tt_zone_reg(const char *arg)
+{
+ struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
+ if (IS_ERR(tt_zone))
+ return PTR_ERR(tt_zone);
+
+ return tt_zone_register_tz(tt_zone);
+}
+
+int tt_zone_unreg(const char *arg)
+{
+ struct tt_thermal_zone *tt_zone __free(put_tt_zone) = tt_get_tt_zone(arg);
+ if (IS_ERR(tt_zone))
+ return PTR_ERR(tt_zone);
+
+ tt_zone_unregister_tz(tt_zone);
+
+ return 0;
+}
+
+void tt_zone_cleanup(void)
+{
+ struct tt_thermal_zone *tt_zone, *aux;
+
+ list_for_each_entry_safe(tt_zone, aux, &tt_thermal_zones, list_node) {
+ tt_zone_unregister_tz(tt_zone);
+
+ list_del(&tt_zone->list_node);
+
+ tt_zone_free(tt_zone);
+ }
+}
diff --git a/drivers/thermal/thermal-generic-adc.c b/drivers/thermal/thermal-generic-adc.c
index f4f1a04f8c0f..7c844589b153 100644
--- a/drivers/thermal/thermal-generic-adc.c
+++ b/drivers/thermal/thermal-generic-adc.c
@@ -7,6 +7,7 @@
* Author: Laxman Dewangan <ldewangan@nvidia.com>
*/
#include <linux/iio/consumer.h>
+#include <linux/iio/iio.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@@ -73,6 +74,58 @@ static const struct thermal_zone_device_ops gadc_thermal_ops = {
.get_temp = gadc_thermal_get_temp,
};
+static const struct iio_chan_spec gadc_thermal_iio_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ }
+};
+
+static int gadc_thermal_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct gadc_thermal_info *gtinfo = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ ret = gadc_thermal_get_temp(gtinfo->tz_dev, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info gadc_thermal_iio_info = {
+ .read_raw = gadc_thermal_read_raw,
+};
+
+static int gadc_iio_register(struct device *dev, struct gadc_thermal_info *gti)
+{
+ struct gadc_thermal_info *gtinfo;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*gtinfo));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ gtinfo = iio_priv(indio_dev);
+ memcpy(gtinfo, gti, sizeof(*gtinfo));
+
+ indio_dev->name = dev_name(dev);
+ indio_dev->info = &gadc_thermal_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = gadc_thermal_iio_channels;
+ indio_dev->num_channels = ARRAY_SIZE(gadc_thermal_iio_channels);
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
static int gadc_thermal_read_linear_lookup_table(struct device *dev,
struct gadc_thermal_info *gti)
{
@@ -117,47 +170,43 @@ static int gadc_thermal_read_linear_lookup_table(struct device *dev,
static int gadc_thermal_probe(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
struct gadc_thermal_info *gti;
int ret;
- if (!pdev->dev.of_node) {
- dev_err(&pdev->dev, "Only DT based supported\n");
+ if (!dev->of_node) {
+ dev_err(dev, "Only DT based supported\n");
return -ENODEV;
}
- gti = devm_kzalloc(&pdev->dev, sizeof(*gti), GFP_KERNEL);
+ gti = devm_kzalloc(dev, sizeof(*gti), GFP_KERNEL);
if (!gti)
return -ENOMEM;
- gti->channel = devm_iio_channel_get(&pdev->dev, "sensor-channel");
- if (IS_ERR(gti->channel)) {
- ret = PTR_ERR(gti->channel);
- if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev, "IIO channel not found: %d\n", ret);
- return ret;
- }
+ gti->channel = devm_iio_channel_get(dev, "sensor-channel");
+ if (IS_ERR(gti->channel))
+ return dev_err_probe(dev, PTR_ERR(gti->channel), "IIO channel not found\n");
- ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti);
+ ret = gadc_thermal_read_linear_lookup_table(dev, gti);
if (ret < 0)
return ret;
- gti->dev = &pdev->dev;
- platform_set_drvdata(pdev, gti);
+ gti->dev = dev;
- gti->tz_dev = devm_thermal_of_zone_register(&pdev->dev, 0, gti,
+ gti->tz_dev = devm_thermal_of_zone_register(dev, 0, gti,
&gadc_thermal_ops);
if (IS_ERR(gti->tz_dev)) {
ret = PTR_ERR(gti->tz_dev);
if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev,
+ dev_err(dev,
"Thermal zone sensor register failed: %d\n",
ret);
return ret;
}
- devm_thermal_add_hwmon_sysfs(&pdev->dev, gti->tz_dev);
+ devm_thermal_add_hwmon_sysfs(dev, gti->tz_dev);
- return 0;
+ return gadc_iio_register(&pdev->dev, gti);
}
static const struct of_device_id of_adc_thermal_match[] = {
diff --git a/drivers/thermal/thermal_acpi.c b/drivers/thermal/thermal_acpi.c
deleted file mode 100644
index 0e5698818f69..000000000000
--- a/drivers/thermal/thermal_acpi.c
+++ /dev/null
@@ -1,117 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright 2023 Linaro Limited
- * Copyright 2023 Intel Corporation
- *
- * Library routines for populating a generic thermal trip point structure
- * with data obtained by evaluating a specific object in the ACPI Namespace.
- */
-#include <linux/acpi.h>
-#include <linux/units.h>
-
-#include "thermal_core.h"
-
-/*
- * Minimum temperature for full military grade is 218°K (-55°C) and
- * max temperature is 448°K (175°C). We can consider those values as
- * the boundaries for the [trips] temperature returned by the
- * firmware. Any values out of these boundaries may be considered
- * bogus and we can assume the firmware has no data to provide.
- */
-#define TEMP_MIN_DECIK 2180
-#define TEMP_MAX_DECIK 4480
-
-static int thermal_acpi_trip_temp(struct acpi_device *adev, char *obj_name,
- int *ret_temp)
-{
- unsigned long long temp;
- acpi_status status;
-
- status = acpi_evaluate_integer(adev->handle, obj_name, NULL, &temp);
- if (ACPI_FAILURE(status)) {
- acpi_handle_debug(adev->handle, "%s evaluation failed\n", obj_name);
- return -ENODATA;
- }
-
- if (temp >= TEMP_MIN_DECIK && temp <= TEMP_MAX_DECIK) {
- *ret_temp = deci_kelvin_to_millicelsius(temp);
- } else {
- acpi_handle_debug(adev->handle, "%s result %llu out of range\n",
- obj_name, temp);
- *ret_temp = THERMAL_TEMP_INVALID;
- }
-
- return 0;
-}
-
-/**
- * thermal_acpi_active_trip_temp - Retrieve active trip point temperature
- * @adev: Target thermal zone ACPI device object.
- * @id: Active cooling level (0 - 9).
- * @ret_temp: Address to store the retrieved temperature value on success.
- *
- * Evaluate the _ACx object for the thermal zone represented by @adev to obtain
- * the temperature of the active cooling trip point corresponding to the active
- * cooling level given by @id.
- *
- * Return 0 on success or a negative error value on failure.
- */
-int thermal_acpi_active_trip_temp(struct acpi_device *adev, int id, int *ret_temp)
-{
- char obj_name[] = {'_', 'A', 'C', '0' + id, '\0'};
-
- if (id < 0 || id > 9)
- return -EINVAL;
-
- return thermal_acpi_trip_temp(adev, obj_name, ret_temp);
-}
-EXPORT_SYMBOL_GPL(thermal_acpi_active_trip_temp);
-
-/**
- * thermal_acpi_passive_trip_temp - Retrieve passive trip point temperature
- * @adev: Target thermal zone ACPI device object.
- * @ret_temp: Address to store the retrieved temperature value on success.
- *
- * Evaluate the _PSV object for the thermal zone represented by @adev to obtain
- * the temperature of the passive cooling trip point.
- *
- * Return 0 on success or -ENODATA on failure.
- */
-int thermal_acpi_passive_trip_temp(struct acpi_device *adev, int *ret_temp)
-{
- return thermal_acpi_trip_temp(adev, "_PSV", ret_temp);
-}
-EXPORT_SYMBOL_GPL(thermal_acpi_passive_trip_temp);
-
-/**
- * thermal_acpi_hot_trip_temp - Retrieve hot trip point temperature
- * @adev: Target thermal zone ACPI device object.
- * @ret_temp: Address to store the retrieved temperature value on success.
- *
- * Evaluate the _HOT object for the thermal zone represented by @adev to obtain
- * the temperature of the trip point at which the system is expected to be put
- * into the S4 sleep state.
- *
- * Return 0 on success or -ENODATA on failure.
- */
-int thermal_acpi_hot_trip_temp(struct acpi_device *adev, int *ret_temp)
-{
- return thermal_acpi_trip_temp(adev, "_HOT", ret_temp);
-}
-EXPORT_SYMBOL_GPL(thermal_acpi_hot_trip_temp);
-
-/**
- * thermal_acpi_critical_trip_temp - Retrieve critical trip point temperature
- * @adev: Target thermal zone ACPI device object.
- * @ret_temp: Address to store the retrieved temperature value on success.
- *
- * Evaluate the _CRT object for the thermal zone represented by @adev to obtain
- * the temperature of the critical cooling trip point.
- *
- * Return 0 on success or -ENODATA on failure.
- */
-int thermal_acpi_critical_trip_temp(struct acpi_device *adev, int *ret_temp)
-{
- return thermal_acpi_trip_temp(adev, "_CRT", ret_temp);
-}
-EXPORT_SYMBOL_GPL(thermal_acpi_critical_trip_temp);
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
index cc2b5e81c620..17ca5c082643 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -37,10 +37,10 @@ static LIST_HEAD(thermal_governor_list);
static DEFINE_MUTEX(thermal_list_lock);
static DEFINE_MUTEX(thermal_governor_lock);
-static atomic_t in_suspend;
-
static struct thermal_governor *def_governor;
+static bool thermal_pm_suspended;
+
/*
* Governor section: set of functions to handle thermal governors
*
@@ -123,7 +123,7 @@ int thermal_register_governor(struct thermal_governor *governor)
if (!governor)
return -EINVAL;
- mutex_lock(&thermal_governor_lock);
+ guard(mutex)(&thermal_governor_lock);
err = -EBUSY;
if (!__find_governor(governor->name)) {
@@ -139,7 +139,7 @@ int thermal_register_governor(struct thermal_governor *governor)
def_governor = governor;
}
- mutex_lock(&thermal_list_lock);
+ guard(mutex)(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node) {
/*
@@ -162,9 +162,6 @@ int thermal_register_governor(struct thermal_governor *governor)
}
}
- mutex_unlock(&thermal_list_lock);
- mutex_unlock(&thermal_governor_lock);
-
return err;
}
@@ -175,23 +172,20 @@ void thermal_unregister_governor(struct thermal_governor *governor)
if (!governor)
return;
- mutex_lock(&thermal_governor_lock);
+ guard(mutex)(&thermal_governor_lock);
if (!__find_governor(governor->name))
- goto exit;
+ return;
- mutex_lock(&thermal_list_lock);
+ list_del(&governor->governor_list);
+
+ guard(mutex)(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node) {
if (!strncasecmp(pos->governor->name, governor->name,
THERMAL_NAME_LENGTH))
thermal_set_governor(pos, NULL);
}
-
- mutex_unlock(&thermal_list_lock);
- list_del(&governor->governor_list);
-exit:
- mutex_unlock(&thermal_governor_lock);
}
int thermal_zone_device_set_policy(struct thermal_zone_device *tz,
@@ -200,23 +194,14 @@ int thermal_zone_device_set_policy(struct thermal_zone_device *tz,
struct thermal_governor *gov;
int ret = -EINVAL;
- mutex_lock(&thermal_governor_lock);
- mutex_lock(&tz->lock);
-
- if (!device_is_registered(&tz->device))
- goto exit;
+ guard(mutex)(&thermal_governor_lock);
+ guard(thermal_zone)(tz);
gov = __find_governor(strim(policy));
- if (!gov)
- goto exit;
-
- ret = thermal_set_governor(tz, gov);
+ if (gov)
+ ret = thermal_set_governor(tz, gov);
-exit:
- mutex_unlock(&tz->lock);
- mutex_unlock(&thermal_governor_lock);
-
- thermal_notify_tz_gov_change(tz->id, policy);
+ thermal_notify_tz_gov_change(tz, policy);
return ret;
}
@@ -226,15 +211,13 @@ int thermal_build_list_of_policies(char *buf)
struct thermal_governor *pos;
ssize_t count = 0;
- mutex_lock(&thermal_governor_lock);
+ guard(mutex)(&thermal_governor_lock);
list_for_each_entry(pos, &thermal_governor_list, governor_list) {
count += sysfs_emit_at(buf, count, "%s ", pos->name);
}
count += sysfs_emit_at(buf, count, "\n");
- mutex_unlock(&thermal_governor_lock);
-
return count;
}
@@ -276,9 +259,46 @@ static int __init thermal_register_governors(void)
return ret;
}
+static int __thermal_zone_device_set_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode mode)
+{
+ if (tz->ops.change_mode) {
+ int ret;
+
+ ret = tz->ops.change_mode(tz, mode);
+ if (ret)
+ return ret;
+ }
+
+ tz->mode = mode;
+
+ return 0;
+}
+
+static void thermal_zone_broken_disable(struct thermal_zone_device *tz)
+{
+ struct thermal_trip_desc *td;
+
+ dev_err(&tz->device, "Unable to get temperature, disabling!\n");
+ /*
+ * This function only runs for enabled thermal zones, so no need to
+ * check for the current mode.
+ */
+ __thermal_zone_device_set_mode(tz, THERMAL_DEVICE_DISABLED);
+ thermal_notify_tz_disable(tz);
+
+ for_each_trip_desc(tz, td) {
+ if (td->trip.type == THERMAL_TRIP_CRITICAL &&
+ td->trip.temperature > THERMAL_TEMP_INVALID) {
+ dev_crit(&tz->device,
+ "Disabled thermal zone with critical trip point\n");
+ return;
+ }
+ }
+}
+
/*
* Zone update section: main control loop applied to each zone while monitoring
- *
* in polling mode. The monitoring is done using a workqueue.
* Same update may be done on a zone by calling thermal_zone_device_update().
*
@@ -290,181 +310,375 @@ static int __init thermal_register_governors(void)
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
unsigned long delay)
{
- if (delay)
- mod_delayed_work(system_freezable_power_efficient_wq,
- &tz->poll_queue, delay);
- else
- cancel_delayed_work(&tz->poll_queue);
+ if (delay > HZ)
+ delay = round_jiffies_relative(delay);
+
+ mod_delayed_work(system_freezable_power_efficient_wq, &tz->poll_queue, delay);
+}
+
+static void thermal_zone_recheck(struct thermal_zone_device *tz, int error)
+{
+ if (error == -EAGAIN) {
+ thermal_zone_device_set_polling(tz, THERMAL_RECHECK_DELAY);
+ return;
+ }
+
+ /*
+ * Print the message once to reduce log noise. It will be followed by
+ * another one if the temperature cannot be determined after multiple
+ * attempts.
+ */
+ if (tz->recheck_delay_jiffies == THERMAL_RECHECK_DELAY)
+ dev_info(&tz->device, "Temperature check failed (%d)\n", error);
+
+ thermal_zone_device_set_polling(tz, tz->recheck_delay_jiffies);
+
+ tz->recheck_delay_jiffies += max(tz->recheck_delay_jiffies >> 1, 1ULL);
+ if (tz->recheck_delay_jiffies > THERMAL_MAX_RECHECK_DELAY) {
+ thermal_zone_broken_disable(tz);
+ /*
+ * Restore the original recheck delay value to allow the thermal
+ * zone to try to recover when it is reenabled by user space.
+ */
+ tz->recheck_delay_jiffies = THERMAL_RECHECK_DELAY;
+ }
}
static void monitor_thermal_zone(struct thermal_zone_device *tz)
{
- if (tz->mode != THERMAL_DEVICE_ENABLED)
- thermal_zone_device_set_polling(tz, 0);
- else if (tz->passive)
+ if (tz->passive > 0 && tz->passive_delay_jiffies)
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)
+static struct thermal_governor *thermal_get_tz_governor(struct thermal_zone_device *tz)
{
- tz->governor ? tz->governor->throttle(tz, trip) :
- def_governor->throttle(tz, trip);
+ if (tz->governor)
+ return tz->governor;
+
+ return def_governor;
}
-void thermal_zone_device_critical(struct thermal_zone_device *tz)
+void thermal_governor_update_tz(struct thermal_zone_device *tz,
+ enum thermal_notify_event reason)
+{
+ if (!tz->governor || !tz->governor->update_tz)
+ return;
+
+ tz->governor->update_tz(tz, reason);
+}
+
+static void thermal_zone_device_halt(struct thermal_zone_device *tz,
+ enum hw_protection_action action)
{
/*
* poweroff_delay_ms must be a carefully profiled positive value.
* Its a must for forced_emergency_poweroff_work to be scheduled.
*/
int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS;
+ const char *msg = "Temperature too high";
- dev_emerg(&tz->device, "%s: critical temperature reached, "
- "shutting down\n", tz->type);
+ dev_emerg(&tz->device, "%s: critical temperature reached\n", tz->type);
- hw_protection_shutdown("Temperature too high", poweroff_delay_ms);
+ __hw_protection_trigger(msg, poweroff_delay_ms, action);
+}
+
+void thermal_zone_device_critical(struct thermal_zone_device *tz)
+{
+ thermal_zone_device_halt(tz, HWPROT_ACT_DEFAULT);
}
EXPORT_SYMBOL(thermal_zone_device_critical);
+void thermal_zone_device_critical_shutdown(struct thermal_zone_device *tz)
+{
+ thermal_zone_device_halt(tz, HWPROT_ACT_SHUTDOWN);
+}
+
+void thermal_zone_device_critical_reboot(struct thermal_zone_device *tz)
+{
+ thermal_zone_device_halt(tz, HWPROT_ACT_REBOOT);
+}
+
static void handle_critical_trips(struct thermal_zone_device *tz,
- int trip, int trip_temp, enum thermal_trip_type trip_type)
+ const struct thermal_trip *trip)
{
- /* 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, thermal_zone_trip_id(tz, trip), trip->type);
+
+ if (trip->type == THERMAL_TRIP_CRITICAL)
+ tz->ops.critical(tz);
+ else if (tz->ops.hot)
+ tz->ops.hot(tz);
+}
- trace_thermal_zone_trip(tz, trip, trip_type);
+static void move_trip_to_sorted_list(struct thermal_trip_desc *td,
+ struct list_head *list)
+{
+ struct thermal_trip_desc *entry;
- if (trip_type == THERMAL_TRIP_HOT && tz->ops->hot)
- tz->ops->hot(tz);
- else if (trip_type == THERMAL_TRIP_CRITICAL)
- tz->ops->critical(tz);
+ /*
+ * Delete upfront and then add to make relocation within the same list
+ * work.
+ */
+ list_del(&td->list_node);
+
+ /* Assume that the new entry is likely to be the last one. */
+ list_for_each_entry_reverse(entry, list, list_node) {
+ if (entry->threshold <= td->threshold) {
+ list_add(&td->list_node, &entry->list_node);
+ return;
+ }
+ }
+ list_add(&td->list_node, list);
}
-static void handle_thermal_trip(struct thermal_zone_device *tz, int trip_id)
+static void move_to_trips_high(struct thermal_zone_device *tz,
+ struct thermal_trip_desc *td)
{
- struct thermal_trip trip;
+ td->threshold = td->trip.temperature;
+ move_trip_to_sorted_list(td, &tz->trips_high);
+}
- /* Ignore disabled trip points */
- if (test_bit(trip_id, &tz->trips_disabled))
+static void move_to_trips_reached(struct thermal_zone_device *tz,
+ struct thermal_trip_desc *td)
+{
+ td->threshold = td->trip.temperature - td->trip.hysteresis;
+ move_trip_to_sorted_list(td, &tz->trips_reached);
+}
+
+static void move_to_trips_invalid(struct thermal_zone_device *tz,
+ struct thermal_trip_desc *td)
+{
+ td->threshold = INT_MAX;
+ list_move(&td->list_node, &tz->trips_invalid);
+}
+
+static void thermal_governor_trip_crossed(struct thermal_governor *governor,
+ struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ bool upward)
+{
+ if (trip->type == THERMAL_TRIP_HOT || trip->type == THERMAL_TRIP_CRITICAL)
return;
- __thermal_zone_get_trip(tz, trip_id, &trip);
-
- if (tz->last_temperature != THERMAL_TEMP_INVALID) {
- if (tz->last_temperature < trip.temperature &&
- tz->temperature >= trip.temperature)
- thermal_notify_tz_trip_up(tz->id, trip_id,
- tz->temperature);
- if (tz->last_temperature >= trip.temperature &&
- tz->temperature < (trip.temperature - trip.hysteresis))
- thermal_notify_tz_trip_down(tz->id, trip_id,
- tz->temperature);
+ if (governor->trip_crossed)
+ governor->trip_crossed(tz, trip, upward);
+}
+
+static void thermal_trip_crossed(struct thermal_zone_device *tz,
+ struct thermal_trip_desc *td,
+ struct thermal_governor *governor,
+ bool upward)
+{
+ const struct thermal_trip *trip = &td->trip;
+
+ if (upward) {
+ if (trip->type == THERMAL_TRIP_PASSIVE)
+ tz->passive++;
+ else if (trip->type == THERMAL_TRIP_CRITICAL ||
+ trip->type == THERMAL_TRIP_HOT)
+ handle_critical_trips(tz, trip);
+
+ thermal_notify_tz_trip_up(tz, trip);
+ thermal_debug_tz_trip_up(tz, trip);
+ } else {
+ if (trip->type == THERMAL_TRIP_PASSIVE) {
+ tz->passive--;
+ WARN_ON(tz->passive < 0);
+ }
+ thermal_notify_tz_trip_down(tz, trip);
+ thermal_debug_tz_trip_down(tz, trip);
}
+ thermal_governor_trip_crossed(governor, tz, trip, upward);
+}
- if (trip.type == THERMAL_TRIP_CRITICAL || trip.type == THERMAL_TRIP_HOT)
- handle_critical_trips(tz, trip_id, trip.temperature, trip.type);
- else
- handle_non_critical_trips(tz, trip_id);
+void thermal_zone_set_trip_hyst(struct thermal_zone_device *tz,
+ struct thermal_trip *trip, int hyst)
+{
+ struct thermal_trip_desc *td = trip_to_trip_desc(trip);
+
+ WRITE_ONCE(trip->hysteresis, hyst);
+ thermal_notify_tz_trip_change(tz, trip);
+ /*
+ * If the zone temperature is above or at the trip tmperature, the trip
+ * is in the trips_reached list and its threshold is equal to its low
+ * temperature. It needs to stay in that list, but its threshold needs
+ * to be updated and the list ordering may need to be restored.
+ */
+ if (tz->temperature >= td->threshold)
+ move_to_trips_reached(tz, td);
}
-static void update_temperature(struct thermal_zone_device *tz)
+void thermal_zone_set_trip_temp(struct thermal_zone_device *tz,
+ struct thermal_trip *trip, int temp)
{
- int temp, ret;
+ struct thermal_trip_desc *td = trip_to_trip_desc(trip);
+ int old_temp = trip->temperature;
- ret = __thermal_zone_get_temp(tz, &temp);
- if (ret) {
- if (ret != -EAGAIN)
- dev_warn(&tz->device,
- "failed to read out thermal zone (%d)\n",
- ret);
+ if (old_temp == temp)
+ return;
+
+ WRITE_ONCE(trip->temperature, temp);
+ thermal_notify_tz_trip_change(tz, trip);
+
+ if (old_temp == THERMAL_TEMP_INVALID) {
+ /*
+ * The trip was invalid before the change, so move it to the
+ * trips_high list regardless of the new temperature value
+ * because there is no mitigation under way for it. If a
+ * mitigation needs to be started, the trip will be moved to the
+ * trips_reached list later.
+ */
+ move_to_trips_high(tz, td);
return;
}
- tz->last_temperature = tz->temperature;
- tz->temperature = temp;
+ if (temp == THERMAL_TEMP_INVALID) {
+ /*
+ * If the trip is in the trips_reached list, mitigation is under
+ * way for it and it needs to be stopped because the trip is
+ * effectively going away.
+ */
+ if (tz->temperature >= td->threshold)
+ thermal_trip_crossed(tz, td, thermal_get_tz_governor(tz), false);
- trace_thermal_temperature(tz);
+ move_to_trips_invalid(tz, td);
+ return;
+ }
- thermal_genl_sampling_temp(tz->id, temp);
+ /*
+ * The trip stays on its current list, but its threshold needs to be
+ * updated due to the temperature change and the list ordering may need
+ * to be restored.
+ */
+ if (tz->temperature >= td->threshold)
+ move_to_trips_reached(tz, td);
+ else
+ move_to_trips_high(tz, td);
}
+EXPORT_SYMBOL_GPL(thermal_zone_set_trip_temp);
-static void thermal_zone_device_init(struct thermal_zone_device *tz)
+static void thermal_zone_handle_trips(struct thermal_zone_device *tz,
+ struct thermal_governor *governor,
+ int *low, int *high)
{
- 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;
+ struct thermal_trip_desc *td, *next;
+ LIST_HEAD(way_down_list);
+
+ /* Check the trips that were below or at the zone temperature. */
+ list_for_each_entry_safe_reverse(td, next, &tz->trips_reached, list_node) {
+ if (td->threshold <= tz->temperature)
+ break;
+
+ thermal_trip_crossed(tz, td, governor, false);
+ /*
+ * The current trips_high list needs to be processed before
+ * adding new entries to it, so put them on a temporary list.
+ */
+ list_move(&td->list_node, &way_down_list);
+ }
+ /* Check the trips that were previously above the zone temperature. */
+ list_for_each_entry_safe(td, next, &tz->trips_high, list_node) {
+ if (td->threshold > tz->temperature)
+ break;
+
+ thermal_trip_crossed(tz, td, governor, true);
+ move_to_trips_reached(tz, td);
+ }
+ /* Move all of the trips from the temporary list to trips_high. */
+ list_for_each_entry_safe(td, next, &way_down_list, list_node)
+ move_to_trips_high(tz, td);
+
+ if (!list_empty(&tz->trips_reached)) {
+ td = list_last_entry(&tz->trips_reached,
+ struct thermal_trip_desc, list_node);
+ /*
+ * Set the "low" value below the current trip threshold in case
+ * the zone temperature is at that threshold and stays there,
+ * which would trigger a new interrupt immediately in vain.
+ */
+ *low = td->threshold - 1;
+ }
+ if (!list_empty(&tz->trips_high)) {
+ td = list_first_entry(&tz->trips_high,
+ struct thermal_trip_desc, list_node);
+ *high = td->threshold;
+ }
}
void __thermal_zone_device_update(struct thermal_zone_device *tz,
enum thermal_notify_event event)
{
- int count;
+ struct thermal_governor *governor = thermal_get_tz_governor(tz);
+ int low = -INT_MAX, high = INT_MAX;
+ int temp, ret;
- if (atomic_read(&in_suspend))
+ if (tz->state != TZ_STATE_READY || tz->mode != THERMAL_DEVICE_ENABLED)
return;
- if (WARN_ONCE(!tz->ops->get_temp,
- "'%s' must not be called without 'get_temp' ops set\n",
- __func__))
+ ret = __thermal_zone_get_temp(tz, &temp);
+ if (ret) {
+ thermal_zone_recheck(tz, ret);
return;
+ } else if (temp <= THERMAL_TEMP_INVALID) {
+ /*
+ * Special case: No valid temperature value is available, but
+ * the zone owner does not want the core to do anything about
+ * it. Continue regular zone polling if needed, so that this
+ * function can be called again, but skip everything else.
+ */
+ goto monitor;
+ }
- if (!thermal_zone_device_is_enabled(tz))
- return;
+ tz->recheck_delay_jiffies = THERMAL_RECHECK_DELAY;
- update_temperature(tz);
+ tz->last_temperature = tz->temperature;
+ tz->temperature = temp;
- __thermal_zone_set_trips(tz);
+ trace_thermal_temperature(tz);
+
+ thermal_genl_sampling_temp(tz->id, temp);
tz->notify_event = event;
- for (count = 0; count < tz->num_trips; count++)
- handle_thermal_trip(tz, count);
+ thermal_zone_handle_trips(tz, governor, &low, &high);
+
+ thermal_thresholds_handle(tz, &low, &high);
+
+ thermal_zone_set_trips(tz, low, high);
+
+ if (governor->manage)
+ governor->manage(tz);
+ thermal_debug_update_trip_stats(tz);
+
+monitor:
monitor_thermal_zone(tz);
}
static int thermal_zone_device_set_mode(struct thermal_zone_device *tz,
enum thermal_device_mode mode)
{
- int ret = 0;
+ int ret;
- mutex_lock(&tz->lock);
+ guard(thermal_zone)(tz);
/* do nothing if mode isn't changing */
- if (mode == tz->mode) {
- mutex_unlock(&tz->lock);
+ if (mode == tz->mode)
+ return 0;
+ ret = __thermal_zone_device_set_mode(tz, mode);
+ if (ret)
return ret;
- }
-
- if (!device_is_registered(&tz->device)) {
- mutex_unlock(&tz->lock);
-
- return -ENODEV;
- }
-
- if (tz->ops->change_mode)
- ret = tz->ops->change_mode(tz, mode);
-
- if (!ret)
- tz->mode = mode;
__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
- mutex_unlock(&tz->lock);
-
if (mode == THERMAL_DEVICE_ENABLED)
- thermal_notify_tz_enable(tz->id);
+ thermal_notify_tz_enable(tz);
else
- thermal_notify_tz_disable(tz->id);
+ thermal_notify_tz_disable(tz);
- return ret;
+ return 0;
}
int thermal_zone_device_enable(struct thermal_zone_device *tz)
@@ -479,96 +693,89 @@ int thermal_zone_device_disable(struct thermal_zone_device *tz)
}
EXPORT_SYMBOL_GPL(thermal_zone_device_disable);
-int thermal_zone_device_is_enabled(struct thermal_zone_device *tz)
+static bool thermal_zone_is_present(struct thermal_zone_device *tz)
{
- lockdep_assert_held(&tz->lock);
-
- return tz->mode == THERMAL_DEVICE_ENABLED;
+ return !list_empty(&tz->node);
}
void thermal_zone_device_update(struct thermal_zone_device *tz,
enum thermal_notify_event event)
{
- mutex_lock(&tz->lock);
- if (device_is_registered(&tz->device))
+ guard(thermal_zone)(tz);
+
+ if (thermal_zone_is_present(tz))
__thermal_zone_device_update(tz, event);
- mutex_unlock(&tz->lock);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_update);
-static void thermal_zone_device_check(struct work_struct *work)
-{
- struct thermal_zone_device *tz = container_of(work, struct
- thermal_zone_device,
- poll_queue.work);
- thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
-}
-
int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *),
void *data)
{
struct thermal_governor *gov;
- int ret = 0;
- mutex_lock(&thermal_governor_lock);
+ guard(mutex)(&thermal_governor_lock);
+
list_for_each_entry(gov, &thermal_governor_list, governor_list) {
+ int ret;
+
ret = cb(gov, data);
if (ret)
- break;
+ return ret;
}
- mutex_unlock(&thermal_governor_lock);
- return ret;
+ return 0;
}
int for_each_thermal_cooling_device(int (*cb)(struct thermal_cooling_device *,
void *), void *data)
{
struct thermal_cooling_device *cdev;
- int ret = 0;
- mutex_lock(&thermal_list_lock);
+ guard(mutex)(&thermal_list_lock);
+
list_for_each_entry(cdev, &thermal_cdev_list, node) {
+ int ret;
+
ret = cb(cdev, data);
if (ret)
- break;
+ return ret;
}
- mutex_unlock(&thermal_list_lock);
- return ret;
+ return 0;
}
int for_each_thermal_zone(int (*cb)(struct thermal_zone_device *, void *),
void *data)
{
struct thermal_zone_device *tz;
- int ret = 0;
- mutex_lock(&thermal_list_lock);
+ guard(mutex)(&thermal_list_lock);
+
list_for_each_entry(tz, &thermal_tz_list, node) {
+ int ret;
+
ret = cb(tz, data);
if (ret)
- break;
+ return ret;
}
- mutex_unlock(&thermal_list_lock);
- return ret;
+ return 0;
}
struct thermal_zone_device *thermal_zone_get_by_id(int id)
{
- struct thermal_zone_device *tz, *match = NULL;
+ struct thermal_zone_device *tz;
+
+ guard(mutex)(&thermal_list_lock);
- mutex_lock(&thermal_list_lock);
list_for_each_entry(tz, &thermal_tz_list, node) {
if (tz->id == id) {
- match = tz;
- break;
+ get_device(&tz->device);
+ return tz;
}
}
- mutex_unlock(&thermal_list_lock);
- return match;
+ return NULL;
}
/*
@@ -581,21 +788,32 @@ struct thermal_zone_device *thermal_zone_get_by_id(int id)
* binding, and unbinding.
*/
+static int thermal_instance_add(struct thermal_instance *new_instance,
+ struct thermal_cooling_device *cdev,
+ struct thermal_trip_desc *td)
+{
+ struct thermal_instance *instance;
+
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ if (instance->cdev == cdev)
+ return -EEXIST;
+ }
+
+ list_add_tail(&new_instance->trip_node, &td->thermal_instances);
+
+ guard(cooling_dev)(cdev);
+
+ list_add_tail(&new_instance->cdev_node, &cdev->thermal_instances);
+
+ return 0;
+}
+
/**
- * thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone
+ * thermal_bind_cdev_to_trip - bind a cooling device to a thermal zone
* @tz: pointer to struct thermal_zone_device
- * @trip: indicates which trip point the cooling devices is
- * associated with in this thermal zone.
+ * @td: descriptor of the trip point to bind @cdev to
* @cdev: pointer to struct thermal_cooling_device
- * @upper: the Maximum cooling state for this trip point.
- * THERMAL_NO_LIMIT means no upper limit,
- * and the cooling device can be in max_state.
- * @lower: the Minimum cooling state can be used for this trip point.
- * THERMAL_NO_LIMIT means no lower limit,
- * and the cooling device can be in cooling state 0.
- * @weight: The weight of the cooling device to be bound to the
- * thermal zone. Use THERMAL_WEIGHT_DEFAULT for the
- * default value
+ * @cool_spec: cooling specification for the trip point and @cdev
*
* This interface function bind a thermal cooling device to the certain trip
* point of a thermal zone device.
@@ -603,58 +821,40 @@ struct thermal_zone_device *thermal_zone_get_by_id(int id)
*
* Return: 0 on success, the proper error value otherwise.
*/
-int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
- int trip,
+static int thermal_bind_cdev_to_trip(struct thermal_zone_device *tz,
+ struct thermal_trip_desc *td,
struct thermal_cooling_device *cdev,
- unsigned long upper, unsigned long lower,
- unsigned int weight)
+ struct cooling_spec *cool_spec)
{
struct thermal_instance *dev;
- struct thermal_instance *pos;
- struct thermal_zone_device *pos1;
- struct thermal_cooling_device *pos2;
bool upper_no_limit;
int result;
- if (trip >= tz->num_trips || trip < 0)
- return -EINVAL;
-
- list_for_each_entry(pos1, &thermal_tz_list, node) {
- if (pos1 == tz)
- break;
- }
- list_for_each_entry(pos2, &thermal_cdev_list, node) {
- if (pos2 == cdev)
- break;
- }
-
- if (tz != pos1 || cdev != pos2)
- return -EINVAL;
-
/* lower default 0, upper default max_state */
- lower = lower == THERMAL_NO_LIMIT ? 0 : lower;
+ if (cool_spec->lower == THERMAL_NO_LIMIT)
+ cool_spec->lower = 0;
- if (upper == THERMAL_NO_LIMIT) {
- upper = cdev->max_state;
+ if (cool_spec->upper == THERMAL_NO_LIMIT) {
+ cool_spec->upper = cdev->max_state;
upper_no_limit = true;
} else {
upper_no_limit = false;
}
- if (lower > upper || upper > cdev->max_state)
+ if (cool_spec->lower > cool_spec->upper || cool_spec->upper > cdev->max_state)
return -EINVAL;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
- dev->tz = tz;
+
dev->cdev = cdev;
- dev->trip = trip;
- dev->upper = upper;
+ dev->trip = &td->trip;
+ dev->upper = cool_spec->upper;
dev->upper_no_limit = upper_no_limit;
- dev->lower = lower;
+ dev->lower = cool_spec->lower;
dev->target = THERMAL_NO_TARGET;
- dev->weight = weight;
+ dev->weight = cool_spec->weight;
result = ida_alloc(&tz->ida, GFP_KERNEL);
if (result < 0)
@@ -667,7 +867,8 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
if (result)
goto release_ida;
- sprintf(dev->attr_name, "cdev%d_trip_point", dev->id);
+ snprintf(dev->attr_name, sizeof(dev->attr_name), "cdev%d_trip_point",
+ dev->id);
sysfs_attr_init(&dev->attr.attr);
dev->attr.attr.name = dev->attr_name;
dev->attr.attr.mode = 0444;
@@ -676,7 +877,8 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
if (result)
goto remove_symbol_link;
- sprintf(dev->weight_attr_name, "cdev%d_weight", dev->id);
+ snprintf(dev->weight_attr_name, sizeof(dev->weight_attr_name),
+ "cdev%d_weight", dev->id);
sysfs_attr_init(&dev->weight_attr.attr);
dev->weight_attr.attr.name = dev->weight_attr_name;
dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO;
@@ -686,24 +888,15 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
if (result)
goto remove_trip_file;
- mutex_lock(&tz->lock);
- mutex_lock(&cdev->lock);
- list_for_each_entry(pos, &tz->thermal_instances, tz_node)
- if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
- result = -EEXIST;
- break;
- }
- if (!result) {
- list_add_tail(&dev->tz_node, &tz->thermal_instances);
- list_add_tail(&dev->cdev_node, &cdev->thermal_instances);
- atomic_set(&tz->need_update, 1);
- }
- mutex_unlock(&cdev->lock);
- mutex_unlock(&tz->lock);
+ result = thermal_instance_add(dev, cdev, td);
+ if (result)
+ goto remove_weight_file;
- if (!result)
- return 0;
+ thermal_governor_update_tz(tz, THERMAL_TZ_BIND_CDEV);
+
+ return 0;
+remove_weight_file:
device_remove_file(&tz->device, &dev->weight_attr);
remove_trip_file:
device_remove_file(&tz->device, &dev->attr);
@@ -715,53 +908,50 @@ free_mem:
kfree(dev);
return result;
}
-EXPORT_SYMBOL_GPL(thermal_zone_bind_cooling_device);
+
+static void thermal_instance_delete(struct thermal_instance *instance)
+{
+ list_del(&instance->trip_node);
+
+ guard(cooling_dev)(instance->cdev);
+
+ list_del(&instance->cdev_node);
+}
/**
- * thermal_zone_unbind_cooling_device() - unbind a cooling device from a
- * thermal zone.
+ * thermal_unbind_cdev_from_trip - unbind a cooling device from a thermal zone.
* @tz: pointer to a struct thermal_zone_device.
- * @trip: indicates which trip point the cooling devices is
- * associated with in this thermal zone.
+ * @td: descriptor of the trip point to unbind @cdev from
* @cdev: pointer to a struct thermal_cooling_device.
*
* This interface function unbind a thermal cooling device from the certain
* trip point of a thermal zone device.
* This function is usually called in the thermal zone device .unbind callback.
- *
- * Return: 0 on success, the proper error value otherwise.
*/
-int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
- int trip,
- struct thermal_cooling_device *cdev)
+static void thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz,
+ struct thermal_trip_desc *td,
+ struct thermal_cooling_device *cdev)
{
struct thermal_instance *pos, *next;
- mutex_lock(&tz->lock);
- mutex_lock(&cdev->lock);
- list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) {
- if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
- list_del(&pos->tz_node);
- list_del(&pos->cdev_node);
- mutex_unlock(&cdev->lock);
- mutex_unlock(&tz->lock);
+ list_for_each_entry_safe(pos, next, &td->thermal_instances, trip_node) {
+ if (pos->cdev == cdev) {
+ thermal_instance_delete(pos);
goto unbind;
}
}
- mutex_unlock(&cdev->lock);
- mutex_unlock(&tz->lock);
- return -ENODEV;
+ return;
unbind:
+ thermal_governor_update_tz(tz, THERMAL_TZ_UNBIND_CDEV);
+
device_remove_file(&tz->device, &pos->weight_attr);
device_remove_file(&tz->device, &pos->attr);
sysfs_remove_link(&tz->device.kobj, pos->name);
ida_free(&tz->ida, pos->id);
kfree(pos);
- return 0;
}
-EXPORT_SYMBOL_GPL(thermal_zone_unbind_cooling_device);
static void thermal_release(struct device *dev)
{
@@ -773,12 +963,12 @@ static void thermal_release(struct device *dev)
tz = to_thermal_zone(dev);
thermal_zone_destroy_device_groups(tz);
mutex_destroy(&tz->lock);
- kfree(tz);
+ complete(&tz->removal);
} else if (!strncmp(dev_name(dev), "cooling_device",
sizeof("cooling_device") - 1)) {
cdev = to_cooling_device(dev);
thermal_cooling_device_destroy_sysfs(cdev);
- kfree(cdev->type);
+ kfree_const(cdev->type);
ida_free(&thermal_cdev_ida, cdev->id);
kfree(cdev);
}
@@ -788,24 +978,64 @@ static struct class *thermal_class;
static inline
void print_bind_err_msg(struct thermal_zone_device *tz,
+ const struct thermal_trip_desc *td,
struct thermal_cooling_device *cdev, int ret)
{
- dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
- tz->type, cdev->type, ret);
+ dev_err(&tz->device, "binding cdev %s to trip %d failed: %d\n",
+ cdev->type, thermal_zone_trip_id(tz, &td->trip), ret);
}
-static void bind_cdev(struct thermal_cooling_device *cdev)
+static bool __thermal_zone_cdev_bind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
{
- int ret;
- struct thermal_zone_device *pos = NULL;
+ struct thermal_trip_desc *td;
+ bool update_tz = false;
- list_for_each_entry(pos, &thermal_tz_list, node) {
- if (pos->ops->bind) {
- ret = pos->ops->bind(pos, cdev);
- if (ret)
- print_bind_err_msg(pos, cdev, ret);
+ if (!tz->ops.should_bind)
+ return false;
+
+ for_each_trip_desc(tz, td) {
+ struct cooling_spec c = {
+ .upper = THERMAL_NO_LIMIT,
+ .lower = THERMAL_NO_LIMIT,
+ .weight = THERMAL_WEIGHT_DEFAULT
+ };
+ int ret;
+
+ if (!tz->ops.should_bind(tz, &td->trip, cdev, &c))
+ continue;
+
+ ret = thermal_bind_cdev_to_trip(tz, td, cdev, &c);
+ if (ret) {
+ print_bind_err_msg(tz, td, cdev, ret);
+ continue;
}
+
+ update_tz = true;
}
+
+ return update_tz;
+}
+
+static void thermal_zone_cdev_bind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
+{
+ guard(thermal_zone)(tz);
+
+ if (__thermal_zone_cdev_bind(tz, cdev))
+ __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+}
+
+static void thermal_cooling_device_init_complete(struct thermal_cooling_device *cdev)
+{
+ struct thermal_zone_device *tz;
+
+ guard(mutex)(&thermal_list_lock);
+
+ list_add(&cdev->node, &thermal_cdev_list);
+
+ list_for_each_entry(tz, &thermal_tz_list, node)
+ thermal_zone_cdev_bind(tz, cdev);
}
/**
@@ -830,7 +1060,7 @@ __thermal_cooling_device_register(struct device_node *np,
const struct thermal_cooling_device_ops *ops)
{
struct thermal_cooling_device *cdev;
- struct thermal_zone_device *pos = NULL;
+ unsigned long current_state;
int id, ret;
if (!ops || !ops->get_max_state || !ops->get_cur_state ||
@@ -850,7 +1080,7 @@ __thermal_cooling_device_register(struct device_node *np,
cdev->id = ret;
id = ret;
- cdev->type = kstrdup(type ? type : "", GFP_KERNEL);
+ cdev->type = kstrdup_const(type ? type : "", GFP_KERNEL);
if (!cdev->type) {
ret = -ENOMEM;
goto out_ida_remove;
@@ -868,6 +1098,18 @@ __thermal_cooling_device_register(struct device_node *np,
if (ret)
goto out_cdev_type;
+ /*
+ * The cooling device's current state is only needed for debug
+ * initialization below, so a failure to get it does not cause
+ * the entire cooling device initialization to fail. However,
+ * the debug will not work for the device if its initial state
+ * cannot be determined and drivers are responsible for ensuring
+ * that this will not happen.
+ */
+ ret = cdev->ops->get_cur_state(cdev, &current_state);
+ if (ret)
+ current_state = ULONG_MAX;
+
thermal_cooling_device_setup_sysfs(cdev);
ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
@@ -881,27 +1123,17 @@ __thermal_cooling_device_register(struct device_node *np,
return ERR_PTR(ret);
}
- /* Add 'this' new cdev to the global cdev list */
- mutex_lock(&thermal_list_lock);
+ if (current_state <= cdev->max_state)
+ thermal_debug_cdev_add(cdev, current_state);
- list_add(&cdev->node, &thermal_cdev_list);
-
- /* Update binding information for 'this' new cdev */
- bind_cdev(cdev);
-
- list_for_each_entry(pos, &thermal_tz_list, node)
- if (atomic_cmpxchg(&pos->need_update, 1, 0))
- thermal_zone_device_update(pos,
- THERMAL_EVENT_UNSPECIFIED);
-
- mutex_unlock(&thermal_list_lock);
+ thermal_cooling_device_init_complete(cdev);
return cdev;
out_cooling_dev:
thermal_cooling_device_destroy_sysfs(cdev);
out_cdev_type:
- kfree(cdev->type);
+ kfree_const(cdev->type);
out_ida_remove:
ida_free(&thermal_cdev_ida, id);
out_kfree_cdev:
@@ -980,7 +1212,7 @@ static void thermal_cooling_device_release(struct device *dev, void *res)
struct thermal_cooling_device *
devm_thermal_of_cooling_device_register(struct device *dev,
struct device_node *np,
- char *type, void *devdata,
+ const char *type, void *devdata,
const struct thermal_cooling_device_ops *ops)
{
struct thermal_cooling_device **ptr, *tcd;
@@ -1037,19 +1269,19 @@ void thermal_cooling_device_update(struct thermal_cooling_device *cdev)
* Hold thermal_list_lock throughout the update to prevent the device
* from going away while being updated.
*/
- mutex_lock(&thermal_list_lock);
+ guard(mutex)(&thermal_list_lock);
if (!thermal_cooling_device_present(cdev))
- goto unlock_list;
+ return;
/*
* Update under the cdev lock to prevent the state from being set beyond
* the new limit concurrently.
*/
- mutex_lock(&cdev->lock);
+ guard(cooling_dev)(cdev);
if (cdev->ops->get_max_state(cdev, &cdev->max_state))
- goto unlock;
+ return;
thermal_cooling_device_stats_reinit(cdev);
@@ -1076,111 +1308,172 @@ void thermal_cooling_device_update(struct thermal_cooling_device *cdev)
}
if (cdev->ops->get_cur_state(cdev, &state) || state > cdev->max_state)
- goto unlock;
+ return;
thermal_cooling_device_stats_update(cdev, state);
+}
+EXPORT_SYMBOL_GPL(thermal_cooling_device_update);
-unlock:
- mutex_unlock(&cdev->lock);
+static void __thermal_zone_cdev_unbind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
+{
+ struct thermal_trip_desc *td;
-unlock_list:
- mutex_unlock(&thermal_list_lock);
+ for_each_trip_desc(tz, td)
+ thermal_unbind_cdev_from_trip(tz, td, cdev);
+}
+
+static void thermal_zone_cdev_unbind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
+{
+ guard(thermal_zone)(tz);
+
+ __thermal_zone_cdev_unbind(tz, cdev);
+}
+
+static bool thermal_cooling_device_exit(struct thermal_cooling_device *cdev)
+{
+ struct thermal_zone_device *tz;
+
+ guard(mutex)(&thermal_list_lock);
+
+ if (!thermal_cooling_device_present(cdev))
+ return false;
+
+ list_del(&cdev->node);
+
+ list_for_each_entry(tz, &thermal_tz_list, node)
+ thermal_zone_cdev_unbind(tz, cdev);
+
+ return true;
}
-EXPORT_SYMBOL_GPL(thermal_cooling_device_update);
/**
- * thermal_cooling_device_unregister - removes a thermal cooling device
- * @cdev: the thermal cooling device to remove.
- *
- * thermal_cooling_device_unregister() must be called when a registered
- * thermal cooling device is no longer needed.
+ * thermal_cooling_device_unregister() - removes a thermal cooling device
+ * @cdev: Thermal cooling device to remove.
*/
void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
{
- struct thermal_zone_device *tz;
-
if (!cdev)
return;
- mutex_lock(&thermal_list_lock);
+ thermal_debug_cdev_remove(cdev);
- if (!thermal_cooling_device_present(cdev)) {
- mutex_unlock(&thermal_list_lock);
- return;
- }
+ if (thermal_cooling_device_exit(cdev))
+ device_unregister(&cdev->device);
+}
+EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
- list_del(&cdev->node);
+int thermal_zone_get_crit_temp(struct thermal_zone_device *tz, int *temp)
+{
+ const struct thermal_trip_desc *td;
+ int ret = -EINVAL;
- /* Unbind all thermal zones associated with 'this' cdev */
- list_for_each_entry(tz, &thermal_tz_list, node) {
- if (tz->ops->unbind)
- tz->ops->unbind(tz, cdev);
+ if (tz->ops.get_crit_temp)
+ return tz->ops.get_crit_temp(tz, temp);
+
+ guard(thermal_zone)(tz);
+
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
+
+ if (trip->type == THERMAL_TRIP_CRITICAL) {
+ *temp = trip->temperature;
+ ret = 0;
+ break;
+ }
}
- mutex_unlock(&thermal_list_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_get_crit_temp);
- device_unregister(&cdev->device);
+static void thermal_zone_device_check(struct work_struct *work)
+{
+ struct thermal_zone_device *tz = container_of(work, struct
+ thermal_zone_device,
+ poll_queue.work);
+ thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
}
-EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
-static void bind_tz(struct thermal_zone_device *tz)
+static void thermal_zone_device_init(struct thermal_zone_device *tz)
{
- int ret;
- struct thermal_cooling_device *pos = NULL;
+ struct thermal_trip_desc *td, *next;
- if (!tz->ops->bind)
- return;
+ INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
- mutex_lock(&thermal_list_lock);
+ tz->temperature = THERMAL_TEMP_INIT;
+ tz->passive = 0;
+ tz->prev_low_trip = -INT_MAX;
+ tz->prev_high_trip = INT_MAX;
+ for_each_trip_desc(tz, td) {
+ struct thermal_instance *instance;
- list_for_each_entry(pos, &thermal_cdev_list, node) {
- ret = tz->ops->bind(tz, pos);
- if (ret)
- print_bind_err_msg(tz, pos, ret);
+ list_for_each_entry(instance, &td->thermal_instances, trip_node)
+ instance->initialized = false;
+ }
+ /*
+ * At this point, all valid trips need to be moved to trips_high so that
+ * mitigation can be started if the zone temperature is above them.
+ */
+ list_for_each_entry_safe(td, next, &tz->trips_invalid, list_node) {
+ if (td->trip.temperature != THERMAL_TEMP_INVALID)
+ move_to_trips_high(tz, td);
+ }
+ /* The trips_reached list may not be empty during system resume. */
+ list_for_each_entry_safe(td, next, &tz->trips_reached, list_node) {
+ if (td->trip.temperature == THERMAL_TEMP_INVALID)
+ move_to_trips_invalid(tz, td);
+ else
+ move_to_trips_high(tz, td);
}
-
- mutex_unlock(&thermal_list_lock);
}
-static void thermal_set_delay_jiffies(unsigned long *delay_jiffies, int delay_ms)
+static int thermal_zone_init_governor(struct thermal_zone_device *tz)
{
- *delay_jiffies = msecs_to_jiffies(delay_ms);
- if (delay_ms > 1000)
- *delay_jiffies = round_jiffies(*delay_jiffies);
+ struct thermal_governor *governor;
+
+ guard(mutex)(&thermal_governor_lock);
+
+ if (tz->tzp)
+ governor = __find_governor(tz->tzp->governor_name);
+ else
+ governor = def_governor;
+
+ return thermal_set_governor(tz, governor);
}
-int thermal_zone_get_crit_temp(struct thermal_zone_device *tz, int *temp)
+static void thermal_zone_init_complete(struct thermal_zone_device *tz)
{
- int i, ret = -EINVAL;
+ struct thermal_cooling_device *cdev;
- if (tz->ops->get_crit_temp)
- return tz->ops->get_crit_temp(tz, temp);
+ guard(mutex)(&thermal_list_lock);
- if (!tz->trips)
- return -EINVAL;
+ list_add_tail(&tz->node, &thermal_tz_list);
- mutex_lock(&tz->lock);
+ guard(thermal_zone)(tz);
- for (i = 0; i < tz->num_trips; i++) {
- if (tz->trips[i].type == THERMAL_TRIP_CRITICAL) {
- *temp = tz->trips[i].temperature;
- ret = 0;
- break;
- }
- }
+ /* Bind cooling devices for this zone. */
+ list_for_each_entry(cdev, &thermal_cdev_list, node)
+ __thermal_zone_cdev_bind(tz, cdev);
- mutex_unlock(&tz->lock);
+ tz->state &= ~TZ_STATE_FLAG_INIT;
+ /*
+ * If system suspend or resume is in progress at this point, the
+ * new thermal zone needs to be marked as suspended because
+ * thermal_pm_notify() has run already.
+ */
+ if (thermal_pm_suspended)
+ tz->state |= TZ_STATE_FLAG_SUSPENDED;
- return ret;
+ __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
}
-EXPORT_SYMBOL_GPL(thermal_zone_get_crit_temp);
/**
* thermal_zone_device_register_with_trips() - register a new thermal zone device
* @type: the thermal zone device type
* @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
* @tzp: thermal zone platform parameters
@@ -1201,16 +1494,19 @@ EXPORT_SYMBOL_GPL(thermal_zone_get_crit_temp);
* IS_ERR*() helpers.
*/
struct thermal_zone_device *
-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,
- const struct thermal_zone_params *tzp, int passive_delay,
- int polling_delay)
+thermal_zone_device_register_with_trips(const char *type,
+ const struct thermal_trip *trips,
+ int num_trips, void *devdata,
+ const struct thermal_zone_device_ops *ops,
+ const struct thermal_zone_params *tzp,
+ unsigned int passive_delay,
+ unsigned int polling_delay)
{
+ const struct thermal_trip *trip = trips;
struct thermal_zone_device *tz;
+ struct thermal_trip_desc *td;
int id;
int result;
- int count;
- struct thermal_governor *governor;
if (!type || strlen(type) == 0) {
pr_err("No thermal zone type defined\n");
@@ -1223,36 +1519,26 @@ thermal_zone_device_register_with_trips(const char *type, struct thermal_trip *t
return ERR_PTR(-EINVAL);
}
- /*
- * 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) {
+ if (num_trips < 0) {
pr_err("Incorrect number of thermal trips\n");
return ERR_PTR(-EINVAL);
}
- if (!ops) {
- pr_err("Thermal zone device ops not defined\n");
+ if (!ops || !ops->get_temp) {
+ pr_err("Thermal zone device ops not defined or invalid\n");
return ERR_PTR(-EINVAL);
}
- if (num_trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp) && !trips)
+ if (num_trips > 0 && !trips)
+ return ERR_PTR(-EINVAL);
+
+ if (polling_delay && passive_delay > polling_delay)
return ERR_PTR(-EINVAL);
if (!thermal_class)
return ERR_PTR(-ENODEV);
- tz = kzalloc(sizeof(*tz), GFP_KERNEL);
+ tz = kzalloc(struct_size(tz, trips, num_trips), GFP_KERNEL);
if (!tz)
return ERR_PTR(-ENOMEM);
@@ -1264,9 +1550,14 @@ thermal_zone_device_register_with_trips(const char *type, struct thermal_trip *t
}
}
- INIT_LIST_HEAD(&tz->thermal_instances);
+ INIT_LIST_HEAD(&tz->node);
+ INIT_LIST_HEAD(&tz->trips_high);
+ INIT_LIST_HEAD(&tz->trips_reached);
+ INIT_LIST_HEAD(&tz->trips_invalid);
ida_init(&tz->ida);
mutex_init(&tz->lock);
+ init_completion(&tz->removal);
+ init_completion(&tz->resume);
id = ida_alloc(&thermal_tz_ida, GFP_KERNEL);
if (id < 0) {
result = id;
@@ -1276,89 +1567,75 @@ thermal_zone_device_register_with_trips(const char *type, struct thermal_trip *t
tz->id = id;
strscpy(tz->type, type, sizeof(tz->type));
- if (!ops->critical)
- ops->critical = thermal_zone_device_critical;
+ tz->ops = *ops;
+ if (!tz->ops.critical)
+ tz->ops.critical = thermal_zone_device_critical;
- tz->ops = ops;
tz->device.class = thermal_class;
tz->devdata = devdata;
- tz->trips = trips;
tz->num_trips = num_trips;
+ for_each_trip_desc(tz, td) {
+ td->trip = *trip++;
+ INIT_LIST_HEAD(&td->thermal_instances);
+ INIT_LIST_HEAD(&td->list_node);
+ /*
+ * Mark all thresholds as invalid to start with even though
+ * this only matters for the trips that start as invalid and
+ * become valid later.
+ */
+ move_to_trips_invalid(tz, td);
+ }
- thermal_set_delay_jiffies(&tz->passive_delay_jiffies, passive_delay);
- thermal_set_delay_jiffies(&tz->polling_delay_jiffies, polling_delay);
+ tz->polling_delay_jiffies = msecs_to_jiffies(polling_delay);
+ tz->passive_delay_jiffies = msecs_to_jiffies(passive_delay);
+ tz->recheck_delay_jiffies = THERMAL_RECHECK_DELAY;
- /* sys I/F */
- /* Add nodes that are always present via .groups */
- result = thermal_zone_create_device_groups(tz, mask);
+ tz->state = TZ_STATE_FLAG_INIT;
+
+ result = dev_set_name(&tz->device, "thermal_zone%d", tz->id);
if (result)
goto remove_id;
- /* A new thermal zone needs to be updated anyway. */
- atomic_set(&tz->need_update, 1);
+ thermal_zone_device_init(tz);
- result = dev_set_name(&tz->device, "thermal_zone%d", tz->id);
- if (result) {
- thermal_zone_destroy_device_groups(tz);
+ result = thermal_zone_init_governor(tz);
+ if (result)
goto remove_id;
- }
+
+ /* sys I/F */
+ /* Add nodes that are always present via .groups */
+ result = thermal_zone_create_device_groups(tz);
+ if (result)
+ goto remove_id;
+
result = device_register(&tz->device);
if (result)
goto release_device;
- for (count = 0; count < num_trips; count++) {
- struct thermal_trip trip;
-
- result = thermal_zone_get_trip(tz, count, &trip);
- if (result || !trip.temperature)
- set_bit(count, &tz->trips_disabled);
- }
-
- /* Update 'this' zone's governor information */
- mutex_lock(&thermal_governor_lock);
-
- if (tz->tzp)
- governor = __find_governor(tz->tzp->governor_name);
- else
- governor = def_governor;
-
- result = thermal_set_governor(tz, governor);
- if (result) {
- mutex_unlock(&thermal_governor_lock);
- goto unregister;
- }
-
- mutex_unlock(&thermal_governor_lock);
-
if (!tz->tzp || !tz->tzp->no_hwmon) {
result = thermal_add_hwmon_sysfs(tz);
if (result)
goto unregister;
}
- mutex_lock(&thermal_list_lock);
- list_add_tail(&tz->node, &thermal_tz_list);
- mutex_unlock(&thermal_list_lock);
+ result = thermal_thresholds_init(tz);
+ if (result)
+ goto remove_hwmon;
- /* Bind cooling devices for this zone */
- bind_tz(tz);
+ thermal_zone_init_complete(tz);
- INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
+ thermal_notify_tz_create(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);
+ thermal_debug_tz_add(tz);
return tz;
+remove_hwmon:
+ thermal_remove_hwmon_sysfs(tz);
unregister:
device_del(&tz->device);
release_device:
put_device(&tz->device);
- tz = NULL;
remove_id:
ida_free(&thermal_tz_ida, id);
free_tzp:
@@ -1369,16 +1646,16 @@ free_tz:
}
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,
- const struct thermal_zone_params *tzp, int passive_delay,
- int polling_delay)
+struct thermal_zone_device *thermal_tripless_zone_device_register(
+ const char *type,
+ void *devdata,
+ const struct thermal_zone_device_ops *ops,
+ const struct thermal_zone_params *tzp)
{
- return thermal_zone_device_register_with_trips(type, NULL, ntrips, mask,
- devdata, ops, tzp,
- passive_delay, polling_delay);
+ return thermal_zone_device_register_with_trips(type, NULL, 0, devdata,
+ ops, tzp, 0, 0);
}
-EXPORT_SYMBOL_GPL(thermal_zone_device_register);
+EXPORT_SYMBOL_GPL(thermal_tripless_zone_device_register);
void *thermal_zone_device_priv(struct thermal_zone_device *tzd)
{
@@ -1404,56 +1681,58 @@ struct device *thermal_zone_device(struct thermal_zone_device *tzd)
}
EXPORT_SYMBOL_GPL(thermal_zone_device);
+static bool thermal_zone_exit(struct thermal_zone_device *tz)
+{
+ struct thermal_cooling_device *cdev;
+
+ guard(mutex)(&thermal_list_lock);
+
+ if (list_empty(&tz->node))
+ return false;
+
+ guard(thermal_zone)(tz);
+
+ tz->state |= TZ_STATE_FLAG_EXIT;
+ list_del_init(&tz->node);
+
+ /* Unbind all cdevs associated with this thermal zone. */
+ list_for_each_entry(cdev, &thermal_cdev_list, node)
+ __thermal_zone_cdev_unbind(tz, cdev);
+
+ return true;
+}
+
/**
* 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 tz_id;
- struct thermal_cooling_device *cdev;
- struct thermal_zone_device *pos = NULL;
-
if (!tz)
return;
- tz_id = tz->id;
+ thermal_debug_tz_remove(tz);
- mutex_lock(&thermal_list_lock);
- list_for_each_entry(pos, &thermal_tz_list, node)
- if (pos == tz)
- break;
- if (pos != tz) {
- /* thermal zone device not found */
- mutex_unlock(&thermal_list_lock);
+ if (!thermal_zone_exit(tz))
return;
- }
- list_del(&tz->node);
-
- /* Unbind all cdevs associated with 'this' thermal zone */
- list_for_each_entry(cdev, &thermal_cdev_list, node)
- if (tz->ops->unbind)
- tz->ops->unbind(tz, cdev);
-
- mutex_unlock(&thermal_list_lock);
cancel_delayed_work_sync(&tz->poll_queue);
thermal_set_governor(tz, NULL);
+ thermal_thresholds_exit(tz);
thermal_remove_hwmon_sysfs(tz);
ida_free(&thermal_tz_ida, tz->id);
ida_destroy(&tz->ida);
- mutex_lock(&tz->lock);
device_del(&tz->device);
- mutex_unlock(&tz->lock);
-
- kfree(tz->tzp);
-
put_device(&tz->device);
- thermal_notify_tz_delete(tz_id);
+ thermal_notify_tz_delete(tz);
+
+ wait_for_completion(&tz->removal);
+ kfree(tz->tzp);
+ kfree(tz);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
@@ -1473,48 +1752,118 @@ struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name)
unsigned int found = 0;
if (!name)
- goto exit;
+ return ERR_PTR(-EINVAL);
+
+ guard(mutex)(&thermal_list_lock);
- mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node)
if (!strncasecmp(name, pos->type, THERMAL_NAME_LENGTH)) {
found++;
ref = pos;
}
- mutex_unlock(&thermal_list_lock);
- /* nothing has been found, thus an error code for it */
- if (found == 0)
- ref = ERR_PTR(-ENODEV);
- else if (found > 1)
- /* Success only when an unique zone is found */
- ref = ERR_PTR(-EEXIST);
+ if (!found)
+ return ERR_PTR(-ENODEV);
+
+ /* Success only when one zone is found. */
+ if (found > 1)
+ return ERR_PTR(-EEXIST);
-exit:
return ref;
}
EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
-static int thermal_pm_notify(struct notifier_block *nb,
- unsigned long mode, void *_unused)
+static void thermal_zone_device_resume(struct work_struct *work)
+{
+ struct thermal_zone_device *tz;
+
+ tz = container_of(work, struct thermal_zone_device, poll_queue.work);
+
+ guard(thermal_zone)(tz);
+
+ tz->state &= ~(TZ_STATE_FLAG_SUSPENDED | TZ_STATE_FLAG_RESUMING);
+
+ thermal_debug_tz_resume(tz);
+ thermal_zone_device_init(tz);
+ thermal_governor_update_tz(tz, THERMAL_TZ_RESUME);
+ __thermal_zone_device_update(tz, THERMAL_TZ_RESUME);
+
+ complete(&tz->resume);
+}
+
+static void thermal_zone_pm_prepare(struct thermal_zone_device *tz)
+{
+ guard(thermal_zone)(tz);
+
+ if (tz->state & TZ_STATE_FLAG_RESUMING) {
+ /*
+ * thermal_zone_device_resume() queued up for this zone has not
+ * acquired the lock yet, so release it to let the function run
+ * and wait util it has done the work.
+ */
+ scoped_guard(thermal_zone_reverse, tz) {
+ wait_for_completion(&tz->resume);
+ }
+ }
+
+ tz->state |= TZ_STATE_FLAG_SUSPENDED;
+}
+
+static void thermal_pm_notify_prepare(void)
{
struct thermal_zone_device *tz;
+ guard(mutex)(&thermal_list_lock);
+
+ thermal_pm_suspended = true;
+
+ list_for_each_entry(tz, &thermal_tz_list, node)
+ thermal_zone_pm_prepare(tz);
+}
+
+static void thermal_zone_pm_complete(struct thermal_zone_device *tz)
+{
+ guard(thermal_zone)(tz);
+
+ cancel_delayed_work(&tz->poll_queue);
+
+ reinit_completion(&tz->resume);
+ tz->state |= TZ_STATE_FLAG_RESUMING;
+
+ /*
+ * Replace the work function with the resume one, which will restore the
+ * original work function and schedule the polling work if needed.
+ */
+ INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_resume);
+ /* Queue up the work without a delay. */
+ mod_delayed_work(system_freezable_power_efficient_wq, &tz->poll_queue, 0);
+}
+
+static void thermal_pm_notify_complete(void)
+{
+ struct thermal_zone_device *tz;
+
+ guard(mutex)(&thermal_list_lock);
+
+ thermal_pm_suspended = false;
+
+ list_for_each_entry(tz, &thermal_tz_list, node)
+ thermal_zone_pm_complete(tz);
+}
+
+static int thermal_pm_notify(struct notifier_block *nb,
+ unsigned long mode, void *_unused)
+{
switch (mode) {
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
case PM_SUSPEND_PREPARE:
- atomic_set(&in_suspend, 1);
+ thermal_pm_notify_prepare();
break;
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
case PM_POST_SUSPEND:
- atomic_set(&in_suspend, 0);
- list_for_each_entry(tz, &thermal_tz_list, node) {
- thermal_zone_device_init(tz);
- thermal_zone_device_update(tz,
- THERMAL_EVENT_UNSPECIFIED);
- }
+ thermal_pm_notify_complete();
break;
default:
break;
@@ -1524,12 +1873,20 @@ static int thermal_pm_notify(struct notifier_block *nb,
static struct notifier_block thermal_pm_nb = {
.notifier_call = thermal_pm_notify,
+ /*
+ * Run at the lowest priority to avoid interference between the thermal
+ * zone resume work items spawned by thermal_pm_notify() and the other
+ * PM notifiers.
+ */
+ .priority = INT_MIN,
};
static int __init thermal_init(void)
{
int result;
+ thermal_debug_init();
+
result = thermal_netlink_init();
if (result)
goto error;
diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h
index 17c1bbed734d..bdadd141aa24 100644
--- a/drivers/thermal/thermal_core.h
+++ b/drivers/thermal/thermal_core.h
@@ -9,10 +9,164 @@
#ifndef __THERMAL_CORE_H__
#define __THERMAL_CORE_H__
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/thermal.h>
#include "thermal_netlink.h"
+#include "thermal_thresholds.h"
+#include "thermal_debugfs.h"
+
+struct thermal_attr {
+ struct device_attribute attr;
+ char name[THERMAL_NAME_LENGTH];
+};
+
+struct thermal_trip_attrs {
+ struct thermal_attr type;
+ struct thermal_attr temp;
+ struct thermal_attr hyst;
+};
+
+struct thermal_trip_desc {
+ struct thermal_trip trip;
+ struct thermal_trip_attrs trip_attrs;
+ struct list_head list_node;
+ struct list_head thermal_instances;
+ int threshold;
+};
+
+/**
+ * struct thermal_governor - structure that holds thermal governor information
+ * @name: name of the governor
+ * @bind_to_tz: callback called when binding to a thermal zone. If it
+ * returns 0, the governor is bound to the thermal zone,
+ * otherwise it fails.
+ * @unbind_from_tz: callback called when a governor is unbound from a
+ * thermal zone.
+ * @trip_crossed: called for trip points that have just been crossed
+ * @manage: called on thermal zone temperature updates
+ * @update_tz: callback called when thermal zone internals have changed, e.g.
+ * thermal cooling instance was added/removed
+ * @governor_list: node in thermal_governor_list (in thermal_core.c)
+ */
+struct thermal_governor {
+ const char *name;
+ int (*bind_to_tz)(struct thermal_zone_device *tz);
+ void (*unbind_from_tz)(struct thermal_zone_device *tz);
+ void (*trip_crossed)(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ bool upward);
+ void (*manage)(struct thermal_zone_device *tz);
+ void (*update_tz)(struct thermal_zone_device *tz,
+ enum thermal_notify_event reason);
+ struct list_head governor_list;
+};
+
+#define TZ_STATE_FLAG_SUSPENDED BIT(0)
+#define TZ_STATE_FLAG_RESUMING BIT(1)
+#define TZ_STATE_FLAG_INIT BIT(2)
+#define TZ_STATE_FLAG_EXIT BIT(3)
+
+#define TZ_STATE_READY 0
+
+/**
+ * struct thermal_zone_device - structure for a thermal zone
+ * @id: unique id number for each thermal zone
+ * @type: the thermal zone device type
+ * @device: &struct device for this thermal zone
+ * @removal: removal completion
+ * @resume: resume completion
+ * @trips_high: trips above the current zone temperature
+ * @trips_reached: trips below or at the current zone temperature
+ * @trips_invalid: trips with invalid temperature
+ * @mode: current mode of this thermal zone
+ * @devdata: private pointer for device private data
+ * @num_trips: number of trip points the thermal zone supports
+ * @passive_delay_jiffies: number of jiffies to wait between polls when
+ * performing passive cooling.
+ * @polling_delay_jiffies: number of jiffies to wait between polls when
+ * checking whether trip points have been crossed (0 for
+ * interrupt driven systems)
+ * @recheck_delay_jiffies: delay after a failed attempt to determine the zone
+ * temperature before trying again
+ * @temperature: current temperature. This is only for core code,
+ * drivers should use thermal_zone_get_temp() to get the
+ * current temperature
+ * @last_temperature: previous temperature read
+ * @emul_temperature: emulated temperature when using CONFIG_THERMAL_EMULATION
+ * @passive: 1 if you've crossed a passive trip point, 0 otherwise.
+ * @prev_low_trip: the low current temperature if you've crossed a passive
+ trip point.
+ * @prev_high_trip: the above current temperature if you've crossed a
+ passive trip point.
+ * @ops: operations this &thermal_zone_device supports
+ * @tzp: thermal zone parameters
+ * @governor: pointer to the governor for this thermal zone
+ * @governor_data: private pointer for governor data
+ * @ida: &struct ida to generate unique id for this zone's cooling
+ * devices
+ * @lock: lock to protect thermal_instances list
+ * @node: node in thermal_tz_list (in thermal_core.c)
+ * @poll_queue: delayed work for polling
+ * @notify_event: Last notification event
+ * @state: current state of the thermal zone
+ * @trips: array of struct thermal_trip objects
+ */
+struct thermal_zone_device {
+ int id;
+ char type[THERMAL_NAME_LENGTH];
+ struct device device;
+ struct completion removal;
+ struct completion resume;
+ struct attribute_group trips_attribute_group;
+ struct list_head trips_high;
+ struct list_head trips_reached;
+ struct list_head trips_invalid;
+ enum thermal_device_mode mode;
+ void *devdata;
+ int num_trips;
+ unsigned long passive_delay_jiffies;
+ unsigned long polling_delay_jiffies;
+ unsigned long recheck_delay_jiffies;
+ int temperature;
+ int last_temperature;
+ int emul_temperature;
+ int passive;
+ int prev_low_trip;
+ int prev_high_trip;
+ struct thermal_zone_device_ops ops;
+ struct thermal_zone_params *tzp;
+ struct thermal_governor *governor;
+ void *governor_data;
+ struct ida ida;
+ struct mutex lock;
+ struct list_head node;
+ struct delayed_work poll_queue;
+ enum thermal_notify_event notify_event;
+ u8 state;
+#ifdef CONFIG_THERMAL_DEBUGFS
+ struct thermal_debugfs *debugfs;
+#endif
+ struct list_head user_thresholds;
+ struct thermal_trip_desc trips[] __counted_by(num_trips);
+};
+
+DEFINE_GUARD(thermal_zone, struct thermal_zone_device *, mutex_lock(&_T->lock),
+ mutex_unlock(&_T->lock))
+
+DEFINE_GUARD(thermal_zone_reverse, struct thermal_zone_device *,
+ mutex_unlock(&_T->lock), mutex_lock(&_T->lock))
+
+/* Initial thermal zone temperature. */
+#define THERMAL_TEMP_INIT INT_MIN
+
+/*
+ * Default and maximum delay after a failed thermal zone temperature check
+ * before attempting to check it again (in jiffies).
+ */
+#define THERMAL_RECHECK_DELAY msecs_to_jiffies(250)
+#define THERMAL_MAX_RECHECK_DELAY (120 * HZ)
/* Default Thermal Governor */
#if defined(CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE)
@@ -54,16 +208,10 @@ int for_each_thermal_cooling_device(int (*cb)(struct thermal_cooling_device *,
int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *),
void *thermal_governor);
-int __for_each_thermal_trip(struct thermal_zone_device *,
- int (*cb)(struct thermal_trip *, void *),
- void *);
-
struct thermal_zone_device *thermal_zone_get_by_id(int id);
-struct thermal_attr {
- struct device_attribute attr;
- char name[THERMAL_NAME_LENGTH];
-};
+DEFINE_CLASS(thermal_zone_get_by_id, struct thermal_zone_device *,
+ if (_T) put_device(&_T->device), thermal_zone_get_by_id(id), int id)
static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev)
{
@@ -72,14 +220,10 @@ static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev)
}
void thermal_cdev_update(struct thermal_cooling_device *);
+void thermal_cdev_update_nocheck(struct thermal_cooling_device *cdev);
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);
+int get_tz_trend(struct thermal_zone_device *tz, const struct thermal_trip *trip);
/*
* This structure is used to describe the behavior of
@@ -89,9 +233,8 @@ get_thermal_instance(struct thermal_zone_device *tz,
struct thermal_instance {
int id;
char name[THERMAL_NAME_LENGTH];
- struct thermal_zone_device *tz;
struct thermal_cooling_device *cdev;
- int trip;
+ const struct thermal_trip *trip;
bool initialized;
unsigned long upper; /* Highest cooling state for this trip point */
unsigned long lower; /* Lowest cooling state for this trip point */
@@ -100,7 +243,7 @@ struct thermal_instance {
struct device_attribute attr;
char weight_attr_name[THERMAL_NAME_LENGTH];
struct device_attribute weight_attr;
- struct list_head tz_node; /* node in tz->thermal_instances */
+ struct list_head trip_node; /* node in trip->thermal_instances */
struct list_head cdev_node; /* node in cdev->thermal_instances */
unsigned int weight; /* The weight of the cooling device */
bool upper_no_limit;
@@ -118,15 +261,29 @@ int thermal_zone_device_set_policy(struct thermal_zone_device *, char *);
int thermal_build_list_of_policies(char *buf);
void __thermal_zone_device_update(struct thermal_zone_device *tz,
enum thermal_notify_event event);
+void thermal_zone_device_critical_reboot(struct thermal_zone_device *tz);
+void thermal_zone_device_critical_shutdown(struct thermal_zone_device *tz);
+void thermal_governor_update_tz(struct thermal_zone_device *tz,
+ enum thermal_notify_event reason);
/* Helpers */
-void __thermal_zone_set_trips(struct thermal_zone_device *tz);
-int __thermal_zone_get_trip(struct thermal_zone_device *tz, int trip_id,
- struct thermal_trip *trip);
+#define for_each_trip_desc(__tz, __td) \
+ for (__td = __tz->trips; __td - __tz->trips < __tz->num_trips; __td++)
+
+#define trip_to_trip_desc(__trip) \
+ container_of(__trip, struct thermal_trip_desc, trip)
+
+const char *thermal_trip_type_name(enum thermal_trip_type trip_type);
+
+void thermal_zone_set_trips(struct thermal_zone_device *tz, int low, int high);
+int thermal_zone_trip_id(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip);
int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp);
+void thermal_zone_set_trip_hyst(struct thermal_zone_device *tz,
+ struct thermal_trip *trip, int hyst);
/* sysfs I/F */
-int thermal_zone_create_device_groups(struct thermal_zone_device *, int);
+int thermal_zone_create_device_groups(struct thermal_zone_device *tz);
void thermal_zone_destroy_device_groups(struct thermal_zone_device *);
void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *);
void thermal_cooling_device_destroy_sysfs(struct thermal_cooling_device *cdev);
@@ -146,7 +303,4 @@ thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
unsigned long new_state) {}
#endif /* CONFIG_THERMAL_STATISTICS */
-/* device tree support */
-int thermal_zone_device_is_enabled(struct thermal_zone_device *tz);
-
#endif /* __THERMAL_CORE_H__ */
diff --git a/drivers/thermal/thermal_debugfs.c b/drivers/thermal/thermal_debugfs.c
new file mode 100644
index 000000000000..11d34f2a3d9f
--- /dev/null
+++ b/drivers/thermal/thermal_debugfs.c
@@ -0,0 +1,968 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2023 Linaro Limited
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ * Thermal subsystem debug support
+ */
+#include <linux/debugfs.h>
+#include <linux/ktime.h>
+#include <linux/list.h>
+#include <linux/minmax.h>
+#include <linux/mutex.h>
+#include <linux/thermal.h>
+
+#include "thermal_core.h"
+
+static struct dentry *d_root;
+static struct dentry *d_cdev;
+static struct dentry *d_tz;
+
+/*
+ * Length of the string containing the thermal zone id or the cooling
+ * device id, including the ending nul character. We can reasonably
+ * assume there won't be more than 256 thermal zones as the maximum
+ * observed today is around 32.
+ */
+#define IDSLENGTH 4
+
+/*
+ * The cooling device transition list is stored in a hash table where
+ * the size is CDEVSTATS_HASH_SIZE. The majority of cooling devices
+ * have dozen of states but some can have much more, so a hash table
+ * is more adequate in this case, because the cost of browsing the entire
+ * list when storing the transitions may not be negligible.
+ */
+#define CDEVSTATS_HASH_SIZE 16
+
+/**
+ * struct cdev_debugfs - per cooling device statistics structure
+ * A cooling device can have a high number of states. Showing the
+ * transitions on a matrix based representation can be overkill given
+ * most of the transitions won't happen and we end up with a matrix
+ * filled with zero. Instead, we show the transitions which actually
+ * happened.
+ *
+ * Every transition updates the current_state and the timestamp. The
+ * transitions and the durations are stored in lists.
+ *
+ * @total: the number of transitions for this cooling device
+ * @current_state: the current cooling device state
+ * @timestamp: the state change timestamp
+ * @transitions: an array of lists containing the state transitions
+ * @durations: an array of lists containing the residencies of each state
+ */
+struct cdev_debugfs {
+ u32 total;
+ int current_state;
+ ktime_t timestamp;
+ struct list_head transitions[CDEVSTATS_HASH_SIZE];
+ struct list_head durations[CDEVSTATS_HASH_SIZE];
+};
+
+/**
+ * struct cdev_record - Common structure for cooling device entry
+ *
+ * The following common structure allows to store the information
+ * related to the transitions and to the state residencies. They are
+ * identified with a id which is associated to a value. It is used as
+ * nodes for the "transitions" and "durations" above.
+ *
+ * @node: node to insert the structure in a list
+ * @id: identifier of the value which can be a state or a transition
+ * @residency: a ktime_t representing a state residency duration
+ * @count: a number of occurrences
+ */
+struct cdev_record {
+ struct list_head node;
+ int id;
+ union {
+ ktime_t residency;
+ u64 count;
+ };
+};
+
+/**
+ * struct trip_stats - Thermal trip statistics
+ *
+ * The trip_stats structure has the relevant information to show the
+ * statistics related to temperature going above a trip point.
+ *
+ * @timestamp: the trip crossing timestamp
+ * @duration: total time when the zone temperature was above the trip point
+ * @trip_temp: trip temperature at mitigation start
+ * @trip_hyst: trip hysteresis at mitigation start
+ * @count: the number of times the zone temperature was above the trip point
+ * @min: minimum recorded temperature above the trip point
+ * @avg: average temperature above the trip point
+ */
+struct trip_stats {
+ ktime_t timestamp;
+ ktime_t duration;
+ int trip_temp;
+ int trip_hyst;
+ int count;
+ int min;
+ int avg;
+};
+
+/**
+ * struct tz_episode - A mitigation episode information
+ *
+ * The tz_episode structure describes a mitigation episode. A
+ * mitigation episode begins the trip point with the lower temperature
+ * is crossed the way up and ends when it is crossed the way
+ * down. During this episode we can have multiple trip points crossed
+ * the way up and down if there are multiple trip described in the
+ * firmware after the lowest temperature trip point.
+ *
+ * @timestamp: first trip point crossed the way up
+ * @duration: total duration of the mitigation episode
+ * @node: a list element to be added to the list of tz events
+ * @max_temp: maximum zone temperature during this episode
+ * @trip_stats: per trip point statistics, flexible array
+ */
+struct tz_episode {
+ ktime_t timestamp;
+ ktime_t duration;
+ struct list_head node;
+ int max_temp;
+ struct trip_stats trip_stats[];
+};
+
+/**
+ * struct tz_debugfs - Store all mitigation episodes for a thermal zone
+ *
+ * The tz_debugfs structure contains the list of the mitigation
+ * episodes and has to track which trip point has been crossed in
+ * order to handle correctly nested trip point mitigation episodes.
+ *
+ * We keep the history of the trip point crossed in an array and as we
+ * can go back and forth inside this history, eg. trip 0,1,2,1,2,1,0,
+ * we keep track of the current position in the history array.
+ *
+ * @tz_episodes: a list of thermal mitigation episodes
+ * @tz: thermal zone this object belongs to
+ * @trips_crossed: an array of trip points crossed by id
+ * @nr_trips: the number of trip points currently being crossed
+ */
+struct tz_debugfs {
+ struct list_head tz_episodes;
+ struct thermal_zone_device *tz;
+ int *trips_crossed;
+ int nr_trips;
+};
+
+/**
+ * struct thermal_debugfs - High level structure for a thermal object in debugfs
+ *
+ * The thermal_debugfs structure is the common structure used by the
+ * cooling device or the thermal zone to store the statistics.
+ *
+ * @d_top: top directory of the thermal object directory
+ * @lock: per object lock to protect the internals
+ *
+ * @cdev_dbg: a cooling device debug structure
+ * @tz_dbg: a thermal zone debug structure
+ */
+struct thermal_debugfs {
+ struct dentry *d_top;
+ struct mutex lock;
+ union {
+ struct cdev_debugfs cdev_dbg;
+ struct tz_debugfs tz_dbg;
+ };
+};
+
+void thermal_debug_init(void)
+{
+ d_root = debugfs_create_dir("thermal", NULL);
+ if (IS_ERR(d_root))
+ return;
+
+ d_cdev = debugfs_create_dir("cooling_devices", d_root);
+ if (IS_ERR(d_cdev))
+ return;
+
+ d_tz = debugfs_create_dir("thermal_zones", d_root);
+}
+
+static struct thermal_debugfs *thermal_debugfs_add_id(struct dentry *d, int id)
+{
+ struct thermal_debugfs *thermal_dbg;
+ char ids[IDSLENGTH];
+
+ thermal_dbg = kzalloc(sizeof(*thermal_dbg), GFP_KERNEL);
+ if (!thermal_dbg)
+ return NULL;
+
+ mutex_init(&thermal_dbg->lock);
+
+ snprintf(ids, IDSLENGTH, "%d", id);
+
+ thermal_dbg->d_top = debugfs_create_dir(ids, d);
+ if (IS_ERR(thermal_dbg->d_top)) {
+ kfree(thermal_dbg);
+ return NULL;
+ }
+
+ return thermal_dbg;
+}
+
+static void thermal_debugfs_remove_id(struct thermal_debugfs *thermal_dbg)
+{
+ if (!thermal_dbg)
+ return;
+
+ debugfs_remove(thermal_dbg->d_top);
+
+ kfree(thermal_dbg);
+}
+
+static struct cdev_record *
+thermal_debugfs_cdev_record_alloc(struct thermal_debugfs *thermal_dbg,
+ struct list_head *lists, int id)
+{
+ struct cdev_record *cdev_record;
+
+ cdev_record = kzalloc(sizeof(*cdev_record), GFP_KERNEL);
+ if (!cdev_record)
+ return NULL;
+
+ cdev_record->id = id;
+ INIT_LIST_HEAD(&cdev_record->node);
+ list_add_tail(&cdev_record->node,
+ &lists[cdev_record->id % CDEVSTATS_HASH_SIZE]);
+
+ return cdev_record;
+}
+
+static struct cdev_record *
+thermal_debugfs_cdev_record_find(struct thermal_debugfs *thermal_dbg,
+ struct list_head *lists, int id)
+{
+ struct cdev_record *entry;
+
+ list_for_each_entry(entry, &lists[id % CDEVSTATS_HASH_SIZE], node)
+ if (entry->id == id)
+ return entry;
+
+ return NULL;
+}
+
+static struct cdev_record *
+thermal_debugfs_cdev_record_get(struct thermal_debugfs *thermal_dbg,
+ struct list_head *lists, int id)
+{
+ struct cdev_record *cdev_record;
+
+ cdev_record = thermal_debugfs_cdev_record_find(thermal_dbg, lists, id);
+ if (cdev_record)
+ return cdev_record;
+
+ return thermal_debugfs_cdev_record_alloc(thermal_dbg, lists, id);
+}
+
+static void thermal_debugfs_cdev_clear(struct cdev_debugfs *cdev_dbg)
+{
+ int i;
+ struct cdev_record *entry, *tmp;
+
+ for (i = 0; i < CDEVSTATS_HASH_SIZE; i++) {
+
+ list_for_each_entry_safe(entry, tmp,
+ &cdev_dbg->transitions[i], node) {
+ list_del(&entry->node);
+ kfree(entry);
+ }
+
+ list_for_each_entry_safe(entry, tmp,
+ &cdev_dbg->durations[i], node) {
+ list_del(&entry->node);
+ kfree(entry);
+ }
+ }
+
+ cdev_dbg->total = 0;
+}
+
+static void *cdev_seq_start(struct seq_file *s, loff_t *pos)
+{
+ struct thermal_debugfs *thermal_dbg = s->private;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ return (*pos < CDEVSTATS_HASH_SIZE) ? pos : NULL;
+}
+
+static void *cdev_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+ (*pos)++;
+
+ return (*pos < CDEVSTATS_HASH_SIZE) ? pos : NULL;
+}
+
+static void cdev_seq_stop(struct seq_file *s, void *v)
+{
+ struct thermal_debugfs *thermal_dbg = s->private;
+
+ mutex_unlock(&thermal_dbg->lock);
+}
+
+static int cdev_tt_seq_show(struct seq_file *s, void *v)
+{
+ struct thermal_debugfs *thermal_dbg = s->private;
+ struct cdev_debugfs *cdev_dbg = &thermal_dbg->cdev_dbg;
+ struct list_head *transitions = cdev_dbg->transitions;
+ struct cdev_record *entry;
+ int i = *(loff_t *)v;
+
+ if (!i)
+ seq_puts(s, "Transition\tOccurrences\n");
+
+ list_for_each_entry(entry, &transitions[i], node) {
+ /*
+ * Assuming maximum cdev states is 1024, the longer
+ * string for a transition would be "1024->1024\0"
+ */
+ char buffer[11];
+
+ snprintf(buffer, ARRAY_SIZE(buffer), "%d->%d",
+ entry->id >> 16, entry->id & 0xFFFF);
+
+ seq_printf(s, "%-10s\t%-10llu\n", buffer, entry->count);
+ }
+
+ return 0;
+}
+
+static const struct seq_operations tt_sops = {
+ .start = cdev_seq_start,
+ .next = cdev_seq_next,
+ .stop = cdev_seq_stop,
+ .show = cdev_tt_seq_show,
+};
+
+DEFINE_SEQ_ATTRIBUTE(tt);
+
+static int cdev_dt_seq_show(struct seq_file *s, void *v)
+{
+ struct thermal_debugfs *thermal_dbg = s->private;
+ struct cdev_debugfs *cdev_dbg = &thermal_dbg->cdev_dbg;
+ struct list_head *durations = cdev_dbg->durations;
+ struct cdev_record *entry;
+ int i = *(loff_t *)v;
+
+ if (!i)
+ seq_puts(s, "State\tResidency\n");
+
+ list_for_each_entry(entry, &durations[i], node) {
+ s64 duration = ktime_to_ms(entry->residency);
+
+ if (entry->id == cdev_dbg->current_state)
+ duration += ktime_ms_delta(ktime_get(),
+ cdev_dbg->timestamp);
+
+ seq_printf(s, "%-5d\t%-10llu\n", entry->id, duration);
+ }
+
+ return 0;
+}
+
+static const struct seq_operations dt_sops = {
+ .start = cdev_seq_start,
+ .next = cdev_seq_next,
+ .stop = cdev_seq_stop,
+ .show = cdev_dt_seq_show,
+};
+
+DEFINE_SEQ_ATTRIBUTE(dt);
+
+static int cdev_clear_set(void *data, u64 val)
+{
+ struct thermal_debugfs *thermal_dbg = data;
+
+ if (!val)
+ return -EINVAL;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ thermal_debugfs_cdev_clear(&thermal_dbg->cdev_dbg);
+
+ mutex_unlock(&thermal_dbg->lock);
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(cdev_clear_fops, NULL, cdev_clear_set, "%llu\n");
+
+/**
+ * thermal_debug_cdev_state_update - Update a cooling device state change
+ *
+ * Computes a transition and the duration of the previous state residency.
+ *
+ * @cdev : a pointer to a cooling device
+ * @new_state: an integer corresponding to the new cooling device state
+ */
+void thermal_debug_cdev_state_update(const struct thermal_cooling_device *cdev,
+ int new_state)
+{
+ struct thermal_debugfs *thermal_dbg = cdev->debugfs;
+ struct cdev_debugfs *cdev_dbg;
+ struct cdev_record *cdev_record;
+ int transition, old_state;
+
+ if (!thermal_dbg || (thermal_dbg->cdev_dbg.current_state == new_state))
+ return;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ cdev_dbg = &thermal_dbg->cdev_dbg;
+
+ old_state = cdev_dbg->current_state;
+
+ /*
+ * Get the old state information in the durations list. If
+ * this one does not exist, a new allocated one will be
+ * returned. Recompute the total duration in the old state and
+ * get a new timestamp for the new state.
+ */
+ cdev_record = thermal_debugfs_cdev_record_get(thermal_dbg,
+ cdev_dbg->durations,
+ old_state);
+ if (cdev_record) {
+ ktime_t now = ktime_get();
+ ktime_t delta = ktime_sub(now, cdev_dbg->timestamp);
+ cdev_record->residency = ktime_add(cdev_record->residency, delta);
+ cdev_dbg->timestamp = now;
+ }
+
+ cdev_dbg->current_state = new_state;
+
+ /*
+ * Create a record for the new state if it is not there, so its
+ * duration will be printed by cdev_dt_seq_show() as expected if it
+ * runs before the next state transition.
+ */
+ thermal_debugfs_cdev_record_get(thermal_dbg, cdev_dbg->durations, new_state);
+
+ transition = (old_state << 16) | new_state;
+
+ /*
+ * Get the transition in the transitions list. If this one
+ * does not exist, a new allocated one will be returned.
+ * Increment the occurrence of this transition which is stored
+ * in the value field.
+ */
+ cdev_record = thermal_debugfs_cdev_record_get(thermal_dbg,
+ cdev_dbg->transitions,
+ transition);
+ if (cdev_record)
+ cdev_record->count++;
+
+ cdev_dbg->total++;
+
+ mutex_unlock(&thermal_dbg->lock);
+}
+
+/**
+ * thermal_debug_cdev_add - Add a cooling device debugfs entry
+ *
+ * Allocates a cooling device object for debug, initializes the
+ * statistics and create the entries in sysfs.
+ * @cdev: a pointer to a cooling device
+ * @state: current state of the cooling device
+ */
+void thermal_debug_cdev_add(struct thermal_cooling_device *cdev, int state)
+{
+ struct thermal_debugfs *thermal_dbg;
+ struct cdev_debugfs *cdev_dbg;
+ int i;
+
+ thermal_dbg = thermal_debugfs_add_id(d_cdev, cdev->id);
+ if (!thermal_dbg)
+ return;
+
+ cdev_dbg = &thermal_dbg->cdev_dbg;
+
+ for (i = 0; i < CDEVSTATS_HASH_SIZE; i++) {
+ INIT_LIST_HEAD(&cdev_dbg->transitions[i]);
+ INIT_LIST_HEAD(&cdev_dbg->durations[i]);
+ }
+
+ cdev_dbg->current_state = state;
+ cdev_dbg->timestamp = ktime_get();
+
+ /*
+ * Create a record for the initial cooling device state, so its
+ * duration will be printed by cdev_dt_seq_show() as expected if it
+ * runs before the first state transition.
+ */
+ thermal_debugfs_cdev_record_get(thermal_dbg, cdev_dbg->durations, state);
+
+ debugfs_create_file("trans_table", 0400, thermal_dbg->d_top,
+ thermal_dbg, &tt_fops);
+
+ debugfs_create_file("time_in_state_ms", 0400, thermal_dbg->d_top,
+ thermal_dbg, &dt_fops);
+
+ debugfs_create_file("clear", 0200, thermal_dbg->d_top,
+ thermal_dbg, &cdev_clear_fops);
+
+ debugfs_create_u32("total_trans", 0400, thermal_dbg->d_top,
+ &cdev_dbg->total);
+
+ cdev->debugfs = thermal_dbg;
+}
+
+static struct thermal_debugfs *thermal_debug_cdev_clear(struct thermal_cooling_device *cdev)
+{
+ struct thermal_debugfs *thermal_dbg;
+
+ guard(cooling_dev)(cdev);
+
+ thermal_dbg = cdev->debugfs;
+ if (thermal_dbg)
+ cdev->debugfs = NULL;
+
+ return thermal_dbg;
+}
+
+/**
+ * thermal_debug_cdev_remove - Remove a cooling device debugfs entry
+ *
+ * Frees the statistics memory data and remove the debugfs entry
+ *
+ * @cdev: a pointer to a cooling device
+ */
+void thermal_debug_cdev_remove(struct thermal_cooling_device *cdev)
+{
+ struct thermal_debugfs *thermal_dbg;
+
+ thermal_dbg = thermal_debug_cdev_clear(cdev);
+ if (!thermal_dbg)
+ return;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ thermal_debugfs_cdev_clear(&thermal_dbg->cdev_dbg);
+
+ mutex_unlock(&thermal_dbg->lock);
+
+ thermal_debugfs_remove_id(thermal_dbg);
+}
+
+static struct tz_episode *thermal_debugfs_tz_event_alloc(struct thermal_zone_device *tz,
+ ktime_t now)
+{
+ struct tz_episode *tze;
+ int i;
+
+ tze = kzalloc(struct_size(tze, trip_stats, tz->num_trips), GFP_KERNEL);
+ if (!tze)
+ return NULL;
+
+ INIT_LIST_HEAD(&tze->node);
+ tze->timestamp = now;
+ tze->duration = KTIME_MIN;
+ tze->max_temp = INT_MIN;
+
+ for (i = 0; i < tz->num_trips; i++) {
+ tze->trip_stats[i].trip_temp = THERMAL_TEMP_INVALID;
+ tze->trip_stats[i].min = INT_MAX;
+ }
+
+ return tze;
+}
+
+void thermal_debug_tz_trip_up(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
+{
+ struct thermal_debugfs *thermal_dbg = tz->debugfs;
+ int trip_id = thermal_zone_trip_id(tz, trip);
+ ktime_t now = ktime_get();
+ struct trip_stats *trip_stats;
+ struct tz_debugfs *tz_dbg;
+ struct tz_episode *tze;
+
+ if (!thermal_dbg)
+ return;
+
+ tz_dbg = &thermal_dbg->tz_dbg;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ /*
+ * The mitigation is starting. A mitigation can contain
+ * several episodes where each of them is related to a
+ * temperature crossing a trip point. The episodes are
+ * nested. That means when the temperature is crossing the
+ * first trip point, the duration begins to be measured. If
+ * the temperature continues to increase and reaches the
+ * second trip point, the duration of the first trip must be
+ * also accumulated.
+ *
+ * eg.
+ *
+ * temp
+ * ^
+ * | --------
+ * trip 2 / \ ------
+ * | /| |\ /| |\
+ * trip 1 / | | `---- | | \
+ * | /| | | | | |\
+ * trip 0 / | | | | | | \
+ * | /| | | | | | | |\
+ * | / | | | | | | | | `--
+ * | / | | | | | | | |
+ * |----- | | | | | | | |
+ * | | | | | | | | |
+ * --------|-|-|--------|--------|------|-|-|------------------> time
+ * | | |<--t2-->| |<-t2'>| | |
+ * | | | |
+ * | |<------------t1------------>| |
+ * | |
+ * |<-------------t0--------------->|
+ *
+ */
+ if (!tz_dbg->nr_trips) {
+ tze = thermal_debugfs_tz_event_alloc(tz, now);
+ if (!tze)
+ goto unlock;
+
+ list_add(&tze->node, &tz_dbg->tz_episodes);
+ }
+
+ /*
+ * Each time a trip point is crossed the way up, the trip_id
+ * is stored in the trip_crossed array and the nr_trips is
+ * incremented. A nr_trips equal to zero means we are entering
+ * a mitigation episode.
+ *
+ * The trip ids may not be in the ascending order but the
+ * result in the array trips_crossed will be in the ascending
+ * temperature order. The function detecting when a trip point
+ * is crossed the way down will handle the very rare case when
+ * the trip points may have been reordered during this
+ * mitigation episode.
+ */
+ tz_dbg->trips_crossed[tz_dbg->nr_trips++] = trip_id;
+
+ tze = list_first_entry(&tz_dbg->tz_episodes, struct tz_episode, node);
+ trip_stats = &tze->trip_stats[trip_id];
+ trip_stats->trip_temp = trip->temperature;
+ trip_stats->trip_hyst = trip->hysteresis;
+ trip_stats->timestamp = now;
+
+unlock:
+ mutex_unlock(&thermal_dbg->lock);
+}
+
+static void tz_episode_close_trip(struct tz_episode *tze, int trip_id, ktime_t now)
+{
+ struct trip_stats *trip_stats = &tze->trip_stats[trip_id];
+ ktime_t delta = ktime_sub(now, trip_stats->timestamp);
+
+ trip_stats->duration = ktime_add(delta, trip_stats->duration);
+ /* Mark the end of mitigation for this trip point. */
+ trip_stats->timestamp = KTIME_MAX;
+}
+
+void thermal_debug_tz_trip_down(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
+{
+ struct thermal_debugfs *thermal_dbg = tz->debugfs;
+ int trip_id = thermal_zone_trip_id(tz, trip);
+ ktime_t now = ktime_get();
+ struct tz_episode *tze;
+ struct tz_debugfs *tz_dbg;
+ int i;
+
+ if (!thermal_dbg)
+ return;
+
+ tz_dbg = &thermal_dbg->tz_dbg;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ /*
+ * The temperature crosses the way down but there was not
+ * mitigation detected before. That may happen when the
+ * temperature is greater than a trip point when registering a
+ * thermal zone, which is a common use case as the kernel has
+ * no mitigation mechanism yet at boot time.
+ */
+ if (!tz_dbg->nr_trips)
+ goto out;
+
+ for (i = tz_dbg->nr_trips - 1; i >= 0; i--) {
+ if (tz_dbg->trips_crossed[i] == trip_id)
+ break;
+ }
+
+ if (i < 0)
+ goto out;
+
+ tz_dbg->nr_trips--;
+
+ if (i < tz_dbg->nr_trips)
+ tz_dbg->trips_crossed[i] = tz_dbg->trips_crossed[tz_dbg->nr_trips];
+
+ tze = list_first_entry(&tz_dbg->tz_episodes, struct tz_episode, node);
+
+ tz_episode_close_trip(tze, trip_id, now);
+
+ /*
+ * This event closes the mitigation as we are crossing the
+ * last trip point the way down.
+ */
+ if (!tz_dbg->nr_trips)
+ tze->duration = ktime_sub(now, tze->timestamp);
+
+out:
+ mutex_unlock(&thermal_dbg->lock);
+}
+
+void thermal_debug_update_trip_stats(struct thermal_zone_device *tz)
+{
+ struct thermal_debugfs *thermal_dbg = tz->debugfs;
+ struct tz_debugfs *tz_dbg;
+ struct tz_episode *tze;
+ int i;
+
+ if (!thermal_dbg)
+ return;
+
+ tz_dbg = &thermal_dbg->tz_dbg;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ if (!tz_dbg->nr_trips)
+ goto out;
+
+ tze = list_first_entry(&tz_dbg->tz_episodes, struct tz_episode, node);
+
+ if (tz->temperature > tze->max_temp)
+ tze->max_temp = tz->temperature;
+
+ for (i = 0; i < tz_dbg->nr_trips; i++) {
+ int trip_id = tz_dbg->trips_crossed[i];
+ struct trip_stats *trip_stats = &tze->trip_stats[trip_id];
+
+ trip_stats->min = min(trip_stats->min, tz->temperature);
+ trip_stats->avg += (tz->temperature - trip_stats->avg) /
+ ++trip_stats->count;
+ }
+out:
+ mutex_unlock(&thermal_dbg->lock);
+}
+
+static void *tze_seq_start(struct seq_file *s, loff_t *pos)
+{
+ struct thermal_debugfs *thermal_dbg = s->private;
+ struct tz_debugfs *tz_dbg = &thermal_dbg->tz_dbg;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ return seq_list_start(&tz_dbg->tz_episodes, *pos);
+}
+
+static void *tze_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+ struct thermal_debugfs *thermal_dbg = s->private;
+ struct tz_debugfs *tz_dbg = &thermal_dbg->tz_dbg;
+
+ return seq_list_next(v, &tz_dbg->tz_episodes, pos);
+}
+
+static void tze_seq_stop(struct seq_file *s, void *v)
+{
+ struct thermal_debugfs *thermal_dbg = s->private;
+
+ mutex_unlock(&thermal_dbg->lock);
+}
+
+static int tze_seq_show(struct seq_file *s, void *v)
+{
+ struct thermal_debugfs *thermal_dbg = s->private;
+ struct thermal_zone_device *tz = thermal_dbg->tz_dbg.tz;
+ struct thermal_trip_desc *td;
+ struct tz_episode *tze;
+ u64 duration_ms;
+ int trip_id;
+ char c;
+
+ tze = list_entry((struct list_head *)v, struct tz_episode, node);
+
+ if (tze->duration == KTIME_MIN) {
+ /* Mitigation in progress. */
+ duration_ms = ktime_to_ms(ktime_sub(ktime_get(), tze->timestamp));
+ c = '>';
+ } else {
+ duration_ms = ktime_to_ms(tze->duration);
+ c = '=';
+ }
+
+ seq_printf(s, ",-Mitigation at %llums, duration%c%llums, max. temp=%dm°C\n",
+ ktime_to_ms(tze->timestamp), c, duration_ms, tze->max_temp);
+
+ seq_printf(s, "| trip | type | temp(m°C) | hyst(m°C) | duration(ms) | avg(m°C) | min(m°C) |\n");
+
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
+ struct trip_stats *trip_stats;
+
+ /*
+ * There is no possible mitigation happening at the
+ * critical trip point, so the stats will be always
+ * zero, skip this trip point
+ */
+ if (trip->type == THERMAL_TRIP_CRITICAL)
+ continue;
+
+ trip_id = thermal_zone_trip_id(tz, trip);
+ trip_stats = &tze->trip_stats[trip_id];
+
+ /* Skip trips without any stats. */
+ if (trip_stats->trip_temp == THERMAL_TEMP_INVALID)
+ continue;
+
+ if (trip_stats->timestamp != KTIME_MAX) {
+ /* Mitigation in progress. */
+ ktime_t delta = ktime_sub(ktime_get(),
+ trip_stats->timestamp);
+
+ delta = ktime_add(delta, trip_stats->duration);
+ duration_ms = ktime_to_ms(delta);
+ c = '>';
+ } else {
+ duration_ms = ktime_to_ms(trip_stats->duration);
+ c = ' ';
+ }
+
+ seq_printf(s, "| %*d | %*s | %*d | %*d | %c%*lld | %*d | %*d |\n",
+ 4 , trip_id,
+ 8, thermal_trip_type_name(trip->type),
+ 9, trip_stats->trip_temp,
+ 9, trip_stats->trip_hyst,
+ c, 11, duration_ms,
+ 9, trip_stats->avg,
+ 9, trip_stats->min);
+ }
+
+ return 0;
+}
+
+static const struct seq_operations tze_sops = {
+ .start = tze_seq_start,
+ .next = tze_seq_next,
+ .stop = tze_seq_stop,
+ .show = tze_seq_show,
+};
+
+DEFINE_SEQ_ATTRIBUTE(tze);
+
+void thermal_debug_tz_add(struct thermal_zone_device *tz)
+{
+ struct thermal_debugfs *thermal_dbg;
+ struct tz_debugfs *tz_dbg;
+
+ thermal_dbg = thermal_debugfs_add_id(d_tz, tz->id);
+ if (!thermal_dbg)
+ return;
+
+ tz_dbg = &thermal_dbg->tz_dbg;
+
+ tz_dbg->tz = tz;
+
+ tz_dbg->trips_crossed = kcalloc(tz->num_trips, sizeof(int), GFP_KERNEL);
+ if (!tz_dbg->trips_crossed) {
+ thermal_debugfs_remove_id(thermal_dbg);
+ return;
+ }
+
+ INIT_LIST_HEAD(&tz_dbg->tz_episodes);
+
+ debugfs_create_file("mitigations", 0400, thermal_dbg->d_top,
+ thermal_dbg, &tze_fops);
+
+ tz->debugfs = thermal_dbg;
+}
+
+static struct thermal_debugfs *thermal_debug_tz_clear(struct thermal_zone_device *tz)
+{
+ struct thermal_debugfs *thermal_dbg;
+
+ guard(thermal_zone)(tz);
+
+ thermal_dbg = tz->debugfs;
+ if (thermal_dbg)
+ tz->debugfs = NULL;
+
+ return thermal_dbg;
+}
+
+void thermal_debug_tz_remove(struct thermal_zone_device *tz)
+{
+ struct thermal_debugfs *thermal_dbg;
+ struct tz_episode *tze, *tmp;
+ struct tz_debugfs *tz_dbg;
+ int *trips_crossed;
+
+ thermal_dbg = thermal_debug_tz_clear(tz);
+ if (!thermal_dbg)
+ return;
+
+ tz_dbg = &thermal_dbg->tz_dbg;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ trips_crossed = tz_dbg->trips_crossed;
+
+ list_for_each_entry_safe(tze, tmp, &tz_dbg->tz_episodes, node) {
+ list_del(&tze->node);
+ kfree(tze);
+ }
+
+ mutex_unlock(&thermal_dbg->lock);
+
+ thermal_debugfs_remove_id(thermal_dbg);
+ kfree(trips_crossed);
+}
+
+void thermal_debug_tz_resume(struct thermal_zone_device *tz)
+{
+ struct thermal_debugfs *thermal_dbg = tz->debugfs;
+ ktime_t now = ktime_get();
+ struct tz_debugfs *tz_dbg;
+ struct tz_episode *tze;
+ int i;
+
+ if (!thermal_dbg)
+ return;
+
+ mutex_lock(&thermal_dbg->lock);
+
+ tz_dbg = &thermal_dbg->tz_dbg;
+
+ if (!tz_dbg->nr_trips)
+ goto out;
+
+ /*
+ * A mitigation episode was in progress before the preceding system
+ * suspend transition, so close it because the zone handling is starting
+ * over from scratch.
+ */
+ tze = list_first_entry(&tz_dbg->tz_episodes, struct tz_episode, node);
+
+ for (i = 0; i < tz_dbg->nr_trips; i++)
+ tz_episode_close_trip(tze, tz_dbg->trips_crossed[i], now);
+
+ tze->duration = ktime_sub(now, tze->timestamp);
+
+ tz_dbg->nr_trips = 0;
+
+out:
+ mutex_unlock(&thermal_dbg->lock);
+}
diff --git a/drivers/thermal/thermal_debugfs.h b/drivers/thermal/thermal_debugfs.h
new file mode 100644
index 000000000000..1c957ce2ec8f
--- /dev/null
+++ b/drivers/thermal/thermal_debugfs.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifdef CONFIG_THERMAL_DEBUGFS
+void thermal_debug_init(void);
+void thermal_debug_cdev_add(struct thermal_cooling_device *cdev, int state);
+void thermal_debug_cdev_remove(struct thermal_cooling_device *cdev);
+void thermal_debug_cdev_state_update(const struct thermal_cooling_device *cdev, int state);
+void thermal_debug_tz_add(struct thermal_zone_device *tz);
+void thermal_debug_tz_remove(struct thermal_zone_device *tz);
+void thermal_debug_tz_resume(struct thermal_zone_device *tz);
+void thermal_debug_tz_trip_up(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip);
+void thermal_debug_tz_trip_down(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip);
+void thermal_debug_update_trip_stats(struct thermal_zone_device *tz);
+#else
+static inline void thermal_debug_init(void) {}
+static inline void thermal_debug_cdev_add(struct thermal_cooling_device *cdev, int state) {}
+static inline void thermal_debug_cdev_remove(struct thermal_cooling_device *cdev) {}
+static inline void thermal_debug_cdev_state_update(const struct thermal_cooling_device *cdev,
+ int state) {}
+static inline void thermal_debug_tz_add(struct thermal_zone_device *tz) {}
+static inline void thermal_debug_tz_remove(struct thermal_zone_device *tz) {}
+static inline void thermal_debug_tz_resume(struct thermal_zone_device *tz) {}
+static inline void thermal_debug_tz_trip_up(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip) {};
+static inline void thermal_debug_tz_trip_down(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip) {}
+static inline void thermal_debug_update_trip_stats(struct thermal_zone_device *tz) {}
+#endif /* CONFIG_THERMAL_DEBUGFS */
diff --git a/drivers/thermal/thermal_helpers.c b/drivers/thermal/thermal_helpers.c
index cfba0965a22d..b1152ad7acc9 100644
--- a/drivers/thermal/thermal_helpers.c
+++ b/drivers/thermal/thermal_helpers.c
@@ -22,12 +22,12 @@
#include "thermal_core.h"
#include "thermal_trace.h"
-int get_tz_trend(struct thermal_zone_device *tz, int trip)
+int get_tz_trend(struct thermal_zone_device *tz, const struct thermal_trip *trip)
{
enum thermal_trend trend;
- if (tz->emul_temperature || !tz->ops->get_trend ||
- tz->ops->get_trend(tz, trip, &trend)) {
+ if (tz->emul_temperature || !tz->ops.get_trend ||
+ tz->ops.get_trend(tz, trip, &trend)) {
if (tz->temperature > tz->last_temperature)
trend = THERMAL_TREND_RAISING;
else if (tz->temperature < tz->last_temperature)
@@ -39,29 +39,31 @@ int get_tz_trend(struct thermal_zone_device *tz, int trip)
return trend;
}
-struct thermal_instance *
-get_thermal_instance(struct thermal_zone_device *tz,
- struct thermal_cooling_device *cdev, int trip)
+static bool thermal_instance_present(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev,
+ const struct thermal_trip *trip)
{
- struct thermal_instance *pos = NULL;
- struct thermal_instance *target_instance = NULL;
+ const struct thermal_trip_desc *td = trip_to_trip_desc(trip);
+ struct thermal_instance *ti;
- mutex_lock(&tz->lock);
- mutex_lock(&cdev->lock);
-
- list_for_each_entry(pos, &tz->thermal_instances, tz_node) {
- if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
- target_instance = pos;
- break;
- }
+ list_for_each_entry(ti, &td->thermal_instances, trip_node) {
+ if (ti->cdev == cdev)
+ return true;
}
- mutex_unlock(&cdev->lock);
- mutex_unlock(&tz->lock);
+ return false;
+}
+
+bool thermal_trip_is_bound_to_cdev(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ struct thermal_cooling_device *cdev)
+{
+ guard(thermal_zone)(tz);
+ guard(cooling_dev)(cdev);
- return target_instance;
+ return thermal_instance_present(tz, cdev, trip);
}
-EXPORT_SYMBOL(get_thermal_instance);
+EXPORT_SYMBOL_GPL(thermal_trip_is_bound_to_cdev);
/**
* __thermal_zone_get_temp() - returns the temperature of a thermal zone
@@ -72,27 +74,27 @@ EXPORT_SYMBOL(get_thermal_instance);
* temperature and fill @temp.
*
* Both tz and tz->ops must be valid pointers when calling this function,
- * and the tz->ops->get_temp callback must be provided.
+ * and the tz->ops.get_temp callback must be provided.
* The function must be called under tz->lock.
*
* Return: On success returns 0, an error code otherwise
*/
int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
{
- int ret = -EINVAL;
- int count;
+ const struct thermal_trip_desc *td;
int crit_temp = INT_MAX;
- struct thermal_trip trip;
+ int ret = -EINVAL;
lockdep_assert_held(&tz->lock);
- ret = tz->ops->get_temp(tz, temp);
+ ret = tz->ops.get_temp(tz, temp);
if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
- for (count = 0; count < tz->num_trips; count++) {
- ret = __thermal_zone_get_trip(tz, count, &trip);
- if (!ret && trip.type == THERMAL_TRIP_CRITICAL) {
- crit_temp = trip.temperature;
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
+
+ if (trip->type == THERMAL_TRIP_CRITICAL) {
+ crit_temp = trip->temperature;
break;
}
}
@@ -129,33 +131,36 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
if (IS_ERR_OR_NULL(tz))
return -EINVAL;
- mutex_lock(&tz->lock);
-
- if (!tz->ops->get_temp) {
- ret = -EINVAL;
- goto unlock;
- }
+ guard(thermal_zone)(tz);
- if (device_is_registered(&tz->device))
- ret = __thermal_zone_get_temp(tz, temp);
- else
- ret = -ENODEV;
+ if (!tz->ops.get_temp)
+ return -EINVAL;
-unlock:
- mutex_unlock(&tz->lock);
+ ret = __thermal_zone_get_temp(tz, temp);
+ if (!ret && *temp <= THERMAL_TEMP_INVALID)
+ return -ENODATA;
return ret;
}
EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
-static void thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev,
- int target)
+static int thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev, int state)
{
- if (cdev->ops->set_cur_state(cdev, target))
- return;
+ int ret;
- thermal_notify_cdev_state_update(cdev->id, target);
- thermal_cooling_device_stats_update(cdev, target);
+ /*
+ * No check is needed for the ops->set_cur_state as the
+ * registering function checked the ops are correctly set
+ */
+ ret = cdev->ops->set_cur_state(cdev, state);
+ if (ret)
+ return ret;
+
+ thermal_notify_cdev_state_update(cdev, state);
+ thermal_cooling_device_stats_update(cdev, state);
+ thermal_debug_cdev_state_update(cdev, state);
+
+ return 0;
}
void __thermal_cdev_update(struct thermal_cooling_device *cdev)
@@ -165,8 +170,6 @@ void __thermal_cdev_update(struct thermal_cooling_device *cdev)
/* Make sure cdev enters the deepest cooling state */
list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
- dev_dbg(&cdev->device, "zone%d->target=%lu\n",
- instance->tz->id, instance->target);
if (instance->target == THERMAL_NO_TARGET)
continue;
if (instance->target > target)
@@ -187,12 +190,23 @@ void __thermal_cdev_update(struct thermal_cooling_device *cdev)
*/
void thermal_cdev_update(struct thermal_cooling_device *cdev)
{
- mutex_lock(&cdev->lock);
+ guard(cooling_dev)(cdev);
+
if (!cdev->updated) {
__thermal_cdev_update(cdev);
cdev->updated = true;
}
- mutex_unlock(&cdev->lock);
+}
+
+/**
+ * thermal_cdev_update_nocheck() - Unconditionally update cooling device state
+ * @cdev: Target cooling device.
+ */
+void thermal_cdev_update_nocheck(struct thermal_cooling_device *cdev)
+{
+ guard(cooling_dev)(cdev);
+
+ __thermal_cdev_update(cdev);
}
/**
diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c
index c3ae44659b81..64cc3ab949fe 100644
--- a/drivers/thermal/thermal_hwmon.c
+++ b/drivers/thermal/thermal_hwmon.c
@@ -78,15 +78,9 @@ temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
int temperature;
int ret;
- mutex_lock(&tz->lock);
-
- if (device_is_registered(&tz->device))
- ret = tz->ops->get_crit_temp(tz, &temperature);
- else
- ret = -ENODEV;
-
- mutex_unlock(&tz->lock);
+ guard(thermal_zone)(tz);
+ ret = tz->ops.get_crit_temp(tz, &temperature);
if (ret)
return ret;
@@ -102,7 +96,7 @@ thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
mutex_lock(&thermal_hwmon_list_lock);
list_for_each_entry(hwmon, &thermal_hwmon_list, node) {
- strcpy(type, tz->type);
+ strscpy(type, tz->type);
strreplace(type, '-', '_');
if (!strcmp(hwmon->type, type)) {
mutex_unlock(&thermal_hwmon_list_lock);
@@ -135,7 +129,7 @@ thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz)
{
int temp;
- return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp);
+ return tz->ops.get_crit_temp && !tz->ops.get_crit_temp(tz, &temp);
}
int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
@@ -290,4 +284,4 @@ int devm_thermal_add_hwmon_sysfs(struct device *dev, struct thermal_zone_device
}
EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs);
-MODULE_IMPORT_NS(HWMON_THERMAL);
+MODULE_IMPORT_NS("HWMON_THERMAL");
diff --git a/drivers/thermal/thermal_netlink.c b/drivers/thermal/thermal_netlink.c
index 08bc46c3ec7b..315a76b01f6a 100644
--- a/drivers/thermal/thermal_netlink.c
+++ b/drivers/thermal/thermal_netlink.c
@@ -7,15 +7,17 @@
* Generic netlink for thermal management framework
*/
#include <linux/module.h>
+#include <linux/notifier.h>
#include <linux/kernel.h>
+#include <net/sock.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, },
+ [THERMAL_GENL_SAMPLING_GROUP] = { .name = THERMAL_GENL_SAMPLING_GROUP_NAME, },
+ [THERMAL_GENL_EVENT_GROUP] = { .name = THERMAL_GENL_EVENT_GROUP_NAME, },
};
static const struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = {
@@ -48,6 +50,11 @@ static const struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] =
[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 },
+
+ /* Thresholds */
+ [THERMAL_GENL_ATTR_THRESHOLD] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_THRESHOLD_TEMP] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_THRESHOLD_DIRECTION] = { .type = NLA_U32 },
};
struct param {
@@ -61,6 +68,8 @@ struct param {
int trip_type;
int trip_hyst;
int temp;
+ int prev_temp;
+ int direction;
int cdev_state;
int cdev_max_state;
struct thermal_genl_cpu_caps *cpu_capabilities;
@@ -69,7 +78,13 @@ struct param {
typedef int (*cb_t)(struct param *);
-static struct genl_family thermal_gnl_family;
+static struct genl_family thermal_genl_family;
+static BLOCKING_NOTIFIER_HEAD(thermal_genl_chain);
+
+static int thermal_group_has_listeners(enum thermal_genl_multicast_groups group)
+{
+ return genl_has_listeners(&thermal_genl_family, &init_net, group);
+}
/************************** Sampling encoding *******************************/
@@ -78,11 +93,14 @@ int thermal_genl_sampling_temp(int id, int temp)
struct sk_buff *skb;
void *hdr;
+ if (!thermal_group_has_listeners(THERMAL_GENL_SAMPLING_GROUP))
+ return 0;
+
skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (!skb)
return -ENOMEM;
- hdr = genlmsg_put(skb, 0, 0, &thermal_gnl_family, 0,
+ hdr = genlmsg_put(skb, 0, 0, &thermal_genl_family, 0,
THERMAL_GENL_SAMPLING_TEMP);
if (!hdr)
goto out_free;
@@ -95,7 +113,7 @@ int thermal_genl_sampling_temp(int id, int temp)
genlmsg_end(skb, hdr);
- genlmsg_multicast(&thermal_gnl_family, skb, 0, 0, GFP_KERNEL);
+ genlmsg_multicast(&thermal_genl_family, skb, 0, THERMAL_GENL_SAMPLING_GROUP, GFP_KERNEL);
return 0;
out_cancel:
@@ -135,7 +153,7 @@ static int thermal_genl_event_tz_trip_up(struct param *p)
return 0;
}
-static int thermal_genl_event_tz_trip_add(struct param *p)
+static int thermal_genl_event_tz_trip_change(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) ||
@@ -147,15 +165,6 @@ static int thermal_genl_event_tz_trip_add(struct param *p)
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,
@@ -233,6 +242,34 @@ out_cancel_nest:
return -EMSGSIZE;
}
+static int thermal_genl_event_threshold_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_THRESHOLD_TEMP, p->temp) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_THRESHOLD_DIRECTION, p->direction))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_threshold_flush(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_threshold_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_PREV_TEMP, p->prev_temp) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TEMP, p->temp))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
int thermal_genl_event_tz_delete(struct param *p)
__attribute__((alias("thermal_genl_event_tz")));
@@ -245,8 +282,11 @@ int thermal_genl_event_tz_disable(struct param *p)
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")));
+int thermal_genl_event_threshold_delete(struct param *p)
+ __attribute__((alias("thermal_genl_event_threshold_add")));
+
+int thermal_genl_event_threshold_down(struct param *p)
+ __attribute__((alias("thermal_genl_event_threshold_up")));
static cb_t event_cb[] = {
[THERMAL_GENL_EVENT_TZ_CREATE] = thermal_genl_event_tz_create,
@@ -256,13 +296,16 @@ static cb_t event_cb[] = {
[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,
+ [THERMAL_GENL_EVENT_THRESHOLD_ADD] = thermal_genl_event_threshold_add,
+ [THERMAL_GENL_EVENT_THRESHOLD_DELETE] = thermal_genl_event_threshold_delete,
+ [THERMAL_GENL_EVENT_THRESHOLD_FLUSH] = thermal_genl_event_threshold_flush,
+ [THERMAL_GENL_EVENT_THRESHOLD_DOWN] = thermal_genl_event_threshold_down,
+ [THERMAL_GENL_EVENT_THRESHOLD_UP] = thermal_genl_event_threshold_up,
};
/*
@@ -275,12 +318,15 @@ static int thermal_genl_send_event(enum thermal_genl_event event,
int ret = -EMSGSIZE;
void *hdr;
+ if (!thermal_group_has_listeners(THERMAL_GENL_EVENT_GROUP))
+ return 0;
+
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);
+ hdr = genlmsg_put(msg, 0, 0, &thermal_genl_family, 0, event);
if (!hdr)
goto out_free_msg;
@@ -290,7 +336,7 @@ static int thermal_genl_send_event(enum thermal_genl_event event,
genlmsg_end(msg, hdr);
- genlmsg_multicast(&thermal_gnl_family, msg, 0, 1, GFP_KERNEL);
+ genlmsg_multicast(&thermal_genl_family, msg, 0, THERMAL_GENL_EVENT_GROUP, GFP_KERNEL);
return 0;
@@ -302,100 +348,93 @@ out_free_msg:
return ret;
}
-int thermal_notify_tz_create(int tz_id, const char *name)
+int thermal_notify_tz_create(const struct thermal_zone_device *tz)
{
- struct param p = { .tz_id = tz_id, .name = name };
+ struct param p = { .tz_id = tz->id, .name = tz->type };
return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_CREATE, &p);
}
-int thermal_notify_tz_delete(int tz_id)
+int thermal_notify_tz_delete(const struct thermal_zone_device *tz)
{
- struct param p = { .tz_id = 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)
+int thermal_notify_tz_enable(const struct thermal_zone_device *tz)
{
- struct param p = { .tz_id = 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)
+int thermal_notify_tz_disable(const struct thermal_zone_device *tz)
{
- struct param p = { .tz_id = 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)
+int thermal_notify_tz_trip_down(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
{
- struct param p = { .tz_id = tz_id, .trip_id = trip_id, .temp = temp };
+ struct param p = { .tz_id = tz->id,
+ .trip_id = thermal_zone_trip_id(tz, trip),
+ .temp = tz->temperature };
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)
+int thermal_notify_tz_trip_up(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
{
- struct param p = { .tz_id = tz_id, .trip_id = trip_id, .temp = temp };
+ struct param p = { .tz_id = tz->id,
+ .trip_id = thermal_zone_trip_id(tz, trip),
+ .temp = tz->temperature };
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)
+int thermal_notify_tz_trip_change(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
{
- 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 };
+ struct param p = { .tz_id = tz->id,
+ .trip_id = thermal_zone_trip_id(tz, trip),
+ .trip_type = trip->type,
+ .trip_temp = trip->temperature,
+ .trip_hyst = trip->hysteresis };
return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_CHANGE, &p);
}
-int thermal_notify_cdev_state_update(int cdev_id, int cdev_state)
+int thermal_notify_cdev_state_update(const struct thermal_cooling_device *cdev,
+ int state)
{
- struct param p = { .cdev_id = cdev_id, .cdev_state = cdev_state };
+ struct param p = { .cdev_id = cdev->id, .cdev_state = 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)
+int thermal_notify_cdev_add(const struct thermal_cooling_device *cdev)
{
- struct param p = { .cdev_id = cdev_id, .name = name,
- .cdev_max_state = cdev_max_state };
+ struct param p = { .cdev_id = cdev->id, .name = cdev->type,
+ .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)
+int thermal_notify_cdev_delete(const struct thermal_cooling_device *cdev)
{
- struct param p = { .cdev_id = 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)
+int thermal_notify_tz_gov_change(const struct thermal_zone_device *tz,
+ const char *name)
{
- struct param p = { .tz_id = tz_id, .name = name };
+ struct param p = { .tz_id = tz->id, .name = name };
return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_GOV_CHANGE, &p);
}
@@ -409,6 +448,43 @@ int thermal_genl_cpu_capability_event(int count,
}
EXPORT_SYMBOL_GPL(thermal_genl_cpu_capability_event);
+int thermal_notify_threshold_add(const struct thermal_zone_device *tz,
+ int temperature, int direction)
+{
+ struct param p = { .tz_id = tz->id, .temp = temperature, .direction = direction };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_ADD, &p);
+}
+
+int thermal_notify_threshold_delete(const struct thermal_zone_device *tz,
+ int temperature, int direction)
+{
+ struct param p = { .tz_id = tz->id, .temp = temperature, .direction = direction };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_DELETE, &p);
+}
+
+int thermal_notify_threshold_flush(const struct thermal_zone_device *tz)
+{
+ struct param p = { .tz_id = tz->id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_FLUSH, &p);
+}
+
+int thermal_notify_threshold_down(const struct thermal_zone_device *tz)
+{
+ struct param p = { .tz_id = tz->id, .temp = tz->temperature, .prev_temp = tz->last_temperature };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_DOWN, &p);
+}
+
+int thermal_notify_threshold_up(const struct thermal_zone_device *tz)
+{
+ struct param p = { .tz_id = tz->id, .temp = tz->temperature, .prev_temp = tz->last_temperature };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_UP, &p);
+}
+
/*************************** Command encoding ********************************/
static int __thermal_genl_cmd_tz_get_id(struct thermal_zone_device *tz,
@@ -450,17 +526,16 @@ out_cancel_nest:
static int thermal_genl_cmd_tz_get_trip(struct param *p)
{
struct sk_buff *msg = p->msg;
- struct thermal_zone_device *tz;
+ const struct thermal_trip_desc *td;
struct nlattr *start_trip;
- struct thermal_trip trip;
- int ret, i, id;
+ int 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);
+ CLASS(thermal_zone_get_by_id, tz)(id);
if (!tz)
return -EINVAL;
@@ -468,37 +543,27 @@ static int thermal_genl_cmd_tz_get_trip(struct param *p)
if (!start_trip)
return -EMSGSIZE;
- mutex_lock(&tz->lock);
+ guard(thermal_zone)(tz);
- for (i = 0; i < tz->num_trips; i++) {
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
- ret = __thermal_zone_get_trip(tz, i, &trip);
- if (ret)
- goto out_cancel_nest;
-
- if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_ID, i) ||
- nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TYPE, trip.type) ||
- nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TEMP, trip.temperature) ||
- nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_HYST, trip.hysteresis))
- goto out_cancel_nest;
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_ID,
+ thermal_zone_trip_id(tz, trip)) ||
+ nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TYPE, trip->type) ||
+ nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TEMP, trip->temperature) ||
+ nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_HYST, trip->hysteresis))
+ return -EMSGSIZE;
}
- 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])
@@ -506,7 +571,7 @@ static int thermal_genl_cmd_tz_get_temp(struct param *p)
id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
- tz = thermal_zone_get_by_id(id);
+ CLASS(thermal_zone_get_by_id, tz)(id);
if (!tz)
return -EINVAL;
@@ -524,28 +589,25 @@ static int thermal_genl_cmd_tz_get_temp(struct param *p)
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;
+ int 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);
+ CLASS(thermal_zone_get_by_id, tz)(id);
if (!tz)
return -EINVAL;
- mutex_lock(&tz->lock);
+ guard(thermal_zone)(tz);
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 -EMSGSIZE;
- return ret;
+ return 0;
}
static int __thermal_genl_cmd_cdev_get(struct thermal_cooling_device *cdev,
@@ -585,12 +647,128 @@ out_cancel_nest:
return ret;
}
+static int __thermal_genl_cmd_threshold_get(struct user_threshold *threshold, void *arg)
+{
+ struct sk_buff *msg = arg;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_THRESHOLD_TEMP, threshold->temperature) ||
+ nla_put_u32(msg, THERMAL_GENL_ATTR_THRESHOLD_DIRECTION, threshold->direction))
+ return -1;
+
+ return 0;
+}
+
+static int thermal_genl_cmd_threshold_get(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ struct nlattr *start_trip;
+ int id, ret;
+
+ if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])
+ return -EINVAL;
+
+ id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+
+ CLASS(thermal_zone_get_by_id, tz)(id);
+ if (!tz)
+ return -EINVAL;
+
+ start_trip = nla_nest_start(msg, THERMAL_GENL_ATTR_THRESHOLD);
+ if (!start_trip)
+ return -EMSGSIZE;
+
+ ret = thermal_thresholds_for_each(tz, __thermal_genl_cmd_threshold_get, msg);
+ if (ret)
+ return -EMSGSIZE;
+
+ nla_nest_end(msg, start_trip);
+
+ return 0;
+}
+
+static int thermal_genl_cmd_threshold_add(struct param *p)
+{
+ int id, temp, direction;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID] ||
+ !p->attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP] ||
+ !p->attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION])
+ return -EINVAL;
+
+ id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+ temp = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP]);
+ direction = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION]);
+
+ CLASS(thermal_zone_get_by_id, tz)(id);
+ if (!tz)
+ return -EINVAL;
+
+ guard(thermal_zone)(tz);
+
+ return thermal_thresholds_add(tz, temp, direction);
+}
+
+static int thermal_genl_cmd_threshold_delete(struct param *p)
+{
+ int id, temp, direction;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID] ||
+ !p->attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP] ||
+ !p->attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION])
+ return -EINVAL;
+
+ id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+ temp = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP]);
+ direction = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION]);
+
+ CLASS(thermal_zone_get_by_id, tz)(id);
+ if (!tz)
+ return -EINVAL;
+
+ guard(thermal_zone)(tz);
+
+ return thermal_thresholds_delete(tz, temp, direction);
+}
+
+static int thermal_genl_cmd_threshold_flush(struct param *p)
+{
+ int id;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])
+ return -EINVAL;
+
+ id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+
+ CLASS(thermal_zone_get_by_id, tz)(id);
+ if (!tz)
+ return -EINVAL;
+
+ guard(thermal_zone)(tz);
+
+ thermal_thresholds_flush(tz);
+
+ return 0;
+}
+
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,
+ [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,
+ [THERMAL_GENL_CMD_THRESHOLD_GET] = thermal_genl_cmd_threshold_get,
+ [THERMAL_GENL_CMD_THRESHOLD_ADD] = thermal_genl_cmd_threshold_add,
+ [THERMAL_GENL_CMD_THRESHOLD_DELETE] = thermal_genl_cmd_threshold_delete,
+ [THERMAL_GENL_CMD_THRESHOLD_FLUSH] = thermal_genl_cmd_threshold_flush,
};
static int thermal_genl_cmd_dumpit(struct sk_buff *skb,
@@ -602,7 +780,7 @@ static int thermal_genl_cmd_dumpit(struct sk_buff *skb,
int ret;
void *hdr;
- hdr = genlmsg_put(skb, 0, 0, &thermal_gnl_family, 0, cmd);
+ hdr = genlmsg_put(skb, 0, 0, &thermal_genl_family, 0, cmd);
if (!hdr)
return -EMSGSIZE;
@@ -634,7 +812,7 @@ static int thermal_genl_cmd_doit(struct sk_buff *skb,
return -ENOMEM;
p.msg = msg;
- hdr = genlmsg_put_reply(msg, info, &thermal_gnl_family, 0, cmd);
+ hdr = genlmsg_put_reply(msg, info, &thermal_genl_family, 0, cmd);
if (!hdr)
goto out_free_msg;
@@ -654,6 +832,27 @@ out_free_msg:
return ret;
}
+static int thermal_genl_bind(int mcgrp)
+{
+ struct thermal_genl_notify n = { .mcgrp = mcgrp };
+
+ if (WARN_ON_ONCE(mcgrp > THERMAL_GENL_MAX_GROUP))
+ return -EINVAL;
+
+ blocking_notifier_call_chain(&thermal_genl_chain, THERMAL_NOTIFY_BIND, &n);
+ return 0;
+}
+
+static void thermal_genl_unbind(int mcgrp)
+{
+ struct thermal_genl_notify n = { .mcgrp = mcgrp };
+
+ if (WARN_ON_ONCE(mcgrp > THERMAL_GENL_MAX_GROUP))
+ return;
+
+ blocking_notifier_call_chain(&thermal_genl_chain, THERMAL_NOTIFY_UNBIND, &n);
+}
+
static const struct genl_small_ops thermal_genl_ops[] = {
{
.cmd = THERMAL_GENL_CMD_TZ_GET_ID,
@@ -680,27 +879,59 @@ static const struct genl_small_ops thermal_genl_ops[] = {
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.dumpit = thermal_genl_cmd_dumpit,
},
+ {
+ .cmd = THERMAL_GENL_CMD_THRESHOLD_GET,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_THRESHOLD_ADD,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_THRESHOLD_DELETE,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_THRESHOLD_FLUSH,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
};
-static struct genl_family thermal_gnl_family __ro_after_init = {
+static struct genl_family thermal_genl_family __ro_after_init = {
.hdrsize = 0,
.name = THERMAL_GENL_FAMILY_NAME,
.version = THERMAL_GENL_VERSION,
.maxattr = THERMAL_GENL_ATTR_MAX,
.policy = thermal_genl_policy,
+ .bind = thermal_genl_bind,
+ .unbind = thermal_genl_unbind,
.small_ops = thermal_genl_ops,
.n_small_ops = ARRAY_SIZE(thermal_genl_ops),
- .resv_start_op = THERMAL_GENL_CMD_CDEV_GET + 1,
+ .resv_start_op = __THERMAL_GENL_CMD_MAX,
.mcgrps = thermal_genl_mcgrps,
.n_mcgrps = ARRAY_SIZE(thermal_genl_mcgrps),
};
+int thermal_genl_register_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&thermal_genl_chain, nb);
+}
+
+int thermal_genl_unregister_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&thermal_genl_chain, nb);
+}
+
int __init thermal_netlink_init(void)
{
- return genl_register_family(&thermal_gnl_family);
+ return genl_register_family(&thermal_genl_family);
}
void __init thermal_netlink_exit(void)
{
- genl_unregister_family(&thermal_gnl_family);
+ genl_unregister_family(&thermal_genl_family);
}
diff --git a/drivers/thermal/thermal_netlink.h b/drivers/thermal/thermal_netlink.h
index 0a9987c3bc57..075e9ae85f3d 100644
--- a/drivers/thermal/thermal_netlink.h
+++ b/drivers/thermal/thermal_netlink.h
@@ -10,98 +10,128 @@ struct thermal_genl_cpu_caps {
int efficiency;
};
+enum thermal_genl_multicast_groups {
+ THERMAL_GENL_SAMPLING_GROUP = 0,
+ THERMAL_GENL_EVENT_GROUP = 1,
+ THERMAL_GENL_MAX_GROUP = THERMAL_GENL_EVENT_GROUP,
+};
+
+#define THERMAL_NOTIFY_BIND 0
+#define THERMAL_NOTIFY_UNBIND 1
+
+struct thermal_genl_notify {
+ int mcgrp;
+};
+
+struct thermal_zone_device;
+struct thermal_trip;
+struct thermal_cooling_device;
+
/* Netlink notification function */
#ifdef CONFIG_THERMAL_NETLINK
int __init thermal_netlink_init(void);
void __init thermal_netlink_exit(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_register_notifier(struct notifier_block *nb);
+int thermal_genl_unregister_notifier(struct notifier_block *nb);
+
+int thermal_notify_tz_create(const struct thermal_zone_device *tz);
+int thermal_notify_tz_delete(const struct thermal_zone_device *tz);
+int thermal_notify_tz_enable(const struct thermal_zone_device *tz);
+int thermal_notify_tz_disable(const struct thermal_zone_device *tz);
+int thermal_notify_tz_trip_down(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip);
+int thermal_notify_tz_trip_up(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip);
+int thermal_notify_tz_trip_change(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip);
+int thermal_notify_cdev_state_update(const struct thermal_cooling_device *cdev,
+ int state);
+int thermal_notify_cdev_add(const struct thermal_cooling_device *cdev);
+int thermal_notify_cdev_delete(const struct thermal_cooling_device *cdev);
+int thermal_notify_tz_gov_change(const struct thermal_zone_device *tz,
+ 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);
+int thermal_notify_threshold_add(const struct thermal_zone_device *tz,
+ int temperature, int direction);
+int thermal_notify_threshold_delete(const struct thermal_zone_device *tz,
+ int temperature, int direction);
+int thermal_notify_threshold_flush(const struct thermal_zone_device *tz);
+int thermal_notify_threshold_down(const struct thermal_zone_device *tz);
+int thermal_notify_threshold_up(const struct thermal_zone_device *tz);
#else
static inline int thermal_netlink_init(void)
{
return 0;
}
-static inline int thermal_notify_tz_create(int tz_id, const char *name)
+static inline int thermal_notify_tz_create(const struct thermal_zone_device *tz)
{
return 0;
}
-static inline int thermal_notify_tz_delete(int tz_id)
+static inline int thermal_genl_register_notifier(struct notifier_block *nb)
{
return 0;
}
-static inline int thermal_notify_tz_enable(int tz_id)
+static inline int thermal_genl_unregister_notifier(struct notifier_block *nb)
{
return 0;
}
-static inline int thermal_notify_tz_disable(int tz_id)
+static inline int thermal_notify_tz_delete(const struct thermal_zone_device *tz)
{
return 0;
}
-static inline int thermal_notify_tz_trip_down(int tz_id, int id, int temp)
+static inline int thermal_notify_tz_enable(const struct thermal_zone_device *tz)
{
return 0;
}
-static inline int thermal_notify_tz_trip_up(int tz_id, int id, int temp)
+static inline int thermal_notify_tz_disable(const struct thermal_zone_device *tz)
{
return 0;
}
-static inline int thermal_notify_tz_trip_delete(int tz_id, int id)
+static inline int thermal_notify_tz_trip_down(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
{
return 0;
}
-static inline int thermal_notify_tz_trip_add(int tz_id, int id, int type,
- int temp, int hyst)
+static inline int thermal_notify_tz_trip_up(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
{
return 0;
}
-static inline int thermal_notify_tz_trip_change(int tz_id, int id, int type,
- int temp, int hyst)
+static inline int thermal_notify_tz_trip_change(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
{
return 0;
}
-static inline int thermal_notify_cdev_state_update(int cdev_id, int state)
+static inline int thermal_notify_cdev_state_update(const struct thermal_cooling_device *cdev,
+ int state)
{
return 0;
}
-static inline int thermal_notify_cdev_add(int cdev_id, const char *name,
- int max_state)
+static inline int thermal_notify_cdev_add(const struct thermal_cooling_device *cdev)
{
return 0;
}
-static inline int thermal_notify_cdev_delete(int cdev_id)
+static inline int thermal_notify_cdev_delete(const struct thermal_cooling_device *cdev)
{
return 0;
}
-static inline int thermal_notify_tz_gov_change(int tz_id, const char *name)
+static inline int thermal_notify_tz_gov_change(const struct thermal_zone_device *tz,
+ const char *name)
{
return 0;
}
@@ -116,6 +146,33 @@ static inline int thermal_genl_cpu_capability_event(int count, struct thermal_ge
return 0;
}
+static inline int thermal_notify_threshold_add(const struct thermal_zone_device *tz,
+ int temperature, int direction)
+{
+ return 0;
+}
+
+static inline int thermal_notify_threshold_delete(const struct thermal_zone_device *tz,
+ int temperature, int direction)
+{
+ return 0;
+}
+
+static inline int thermal_notify_threshold_flush(const struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
+static inline int thermal_notify_threshold_down(const struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
+static inline int thermal_notify_threshold_up(const struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
static inline void __init thermal_netlink_exit(void) {}
#endif /* CONFIG_THERMAL_NETLINK */
diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c
index bc07ae1c284c..1a51a4d240ff 100644
--- a/drivers/thermal/thermal_of.c
+++ b/drivers/thermal/thermal_of.c
@@ -10,8 +10,7 @@
#include <linux/err.h>
#include <linux/export.h>
-#include <linux/of_device.h>
-#include <linux/of_platform.h>
+#include <linux/of.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include <linux/types.h>
@@ -21,35 +20,6 @@
/*** 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.
@@ -116,62 +86,48 @@ static int thermal_of_populate_trip(struct device_node *np,
return ret;
}
+ trip->flags = THERMAL_TRIP_FLAG_RW_TEMP;
+
+ trip->priv = np;
+
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);
- }
+ *ntrips = 0;
+
+ struct device_node *trips __free(device_node) = of_get_child_by_name(np, "trips");
+ if (!trips)
+ return NULL;
count = of_get_child_count(trips);
- if (!count) {
- pr_err("No trip point defined\n");
- ret = -EINVAL;
- goto out_of_node_put;
- }
+ if (!count)
+ return NULL;
- tt = kzalloc(sizeof(*tt) * count, GFP_KERNEL);
- if (!tt) {
- ret = -ENOMEM;
- goto out_of_node_put;
- }
-
- *ntrips = count;
+ struct thermal_trip *tt __free(kfree) = kcalloc(count, sizeof(*tt), GFP_KERNEL);
+ if (!tt)
+ return ERR_PTR(-ENOMEM);
count = 0;
- for_each_child_of_node(trips, trip) {
+ for_each_child_of_node_scoped(trips, trip) {
ret = thermal_of_populate_trip(trip, &tt[count++]);
if (ret)
- goto out_kfree;
+ return ERR_PTR(ret);
}
- of_node_put(trips);
-
- return tt;
-
-out_kfree:
- kfree(tt);
- *ntrips = 0;
-out_of_node_put:
- of_node_put(trips);
+ *ntrips = count;
- return ERR_PTR(ret);
+ return no_free_ptr(tt);
}
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");
+ struct device_node *np __free(device_node) = of_find_node_by_name(NULL, "thermal-zones");
if (!np) {
pr_debug("No thermal zones description\n");
return ERR_PTR(-ENODEV);
@@ -181,42 +137,39 @@ static struct device_node *of_thermal_zone_find(struct device_node *sensor, int
* Search for each thermal zone, a defined sensor
* corresponding to the one passed as parameter
*/
- for_each_available_child_of_node(np, tz) {
+ for_each_available_child_of_node_scoped(np, child) {
int count, i;
- count = of_count_phandle_with_args(tz, "thermal-sensors",
+ count = of_count_phandle_with_args(child, "thermal-sensors",
"#thermal-sensor-cells");
if (count <= 0) {
- pr_err("%pOFn: missing thermal sensor\n", tz);
- tz = ERR_PTR(-EINVAL);
- goto out;
+ pr_err("%pOFn: missing thermal sensor\n", child);
+ return ERR_PTR(-EINVAL);
}
for (i = 0; i < count; i++) {
int ret;
- ret = of_parse_phandle_with_args(tz, "thermal-sensors",
+ ret = of_parse_phandle_with_args(child, "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;
+ pr_err("%pOFn: Failed to read thermal-sensors cells: %d\n", child, ret);
+ return ERR_PTR(ret);
}
+ of_node_put(sensor_specs.np);
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;
+ pr_debug("sensor %pOFn id=%d belongs to %pOFn\n", sensor, id, child);
+ return no_free_ptr(child);
}
}
}
- tz = ERR_PTR(-ENODEV);
-out:
- of_node_put(np);
- return tz;
+
+ return ERR_PTR(-ENODEV);
}
static int thermal_of_monitor_init(struct device_node *np, int *delay, int *pdelay)
@@ -224,14 +177,18 @@ static int thermal_of_monitor_init(struct device_node *np, int *delay, int *pdel
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);
+ if (ret == -EINVAL) {
+ *pdelay = 0;
+ } else if (ret < 0) {
+ pr_err("%pOFn: Couldn't get polling-delay-passive: %d\n", np, ret);
return ret;
}
ret = of_property_read_u32(np, "polling-delay", delay);
- if (ret < 0) {
- pr_err("%pOFn: missing polling-delay property\n", np);
+ if (ret == -EINVAL) {
+ *delay = 0;
+ } else if (ret < 0) {
+ pr_err("%pOFn: Couldn't get polling-delay: %d\n", np, ret);
return ret;
}
@@ -283,39 +240,9 @@ static struct device_node *thermal_of_zone_get_by_name(struct thermal_zone_devic
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)
+static bool thermal_of_get_cooling_spec(struct device_node *map_np, int index,
+ struct thermal_cooling_device *cdev,
+ struct cooling_spec *c)
{
struct of_phandle_args cooling_spec;
int ret, weight = THERMAL_WEIGHT_DEFAULT;
@@ -325,104 +252,82 @@ static int __thermal_of_bind(struct device_node *map_np, int index, int trip_id,
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;
+ return false;
}
+ of_node_put(cooling_spec.np);
+
if (cooling_spec.args_count < 2) {
pr_err("wrong reference to cooling device, missing limits\n");
- return -EINVAL;
+ return false;
}
if (cooling_spec.np != cdev->np)
- return 0;
+ return false;
- 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);
+ c->lower = cooling_spec.args[0];
+ c->upper = cooling_spec.args[1];
+ c->weight = weight;
- return ret;
+ return true;
}
-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 *))
+static bool thermal_of_cm_lookup(struct device_node *cm_np,
+ const struct thermal_trip *trip,
+ struct thermal_cooling_device *cdev,
+ struct cooling_spec *c)
{
- struct device_node *tr_np;
- int count, i, trip_id;
+ for_each_child_of_node_scoped(cm_np, child) {
+ struct device_node *tr_np;
+ int count, i;
- tr_np = of_parse_phandle(map_np, "trip", 0);
- if (!tr_np)
- return -ENODEV;
+ tr_np = of_parse_phandle(child, "trip", 0);
+ if (tr_np != trip->priv)
+ continue;
- trip_id = of_find_trip_id(tz_np, tr_np);
- if (trip_id < 0)
- return trip_id;
+ /* The trip has been found, look up the cdev. */
+ count = of_count_phandle_with_args(child, "cooling-device",
+ "#cooling-cells");
+ if (count <= 0)
+ pr_err("Add a cooling_device property with at least one device\n");
- 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;
+ for (i = 0; i < count; i++) {
+ if (thermal_of_get_cooling_spec(child, i, cdev, c))
+ return true;
+ }
}
- /*
- * 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;
+ return false;
}
-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 *))
+static bool thermal_of_should_bind(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ struct thermal_cooling_device *cdev,
+ struct cooling_spec *c)
{
- struct device_node *tz_np, *cm_np, *child;
- int ret = 0;
+ struct device_node *tz_np, *cm_np;
+ bool result = false;
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);
+ return false;
}
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;
- }
+ /* Look up the trip and the cdev in the cooling maps. */
+ result = thermal_of_cm_lookup(cm_np, trip, cdev, c);
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);
+ return result;
}
/**
@@ -435,13 +340,8 @@ static int thermal_of_unbind(struct thermal_zone_device *tz,
*/
static void thermal_of_zone_unregister(struct thermal_zone_device *tz)
{
- struct thermal_trip *trips = tz->trips;
- struct thermal_zone_device_ops *ops = tz->ops;
-
thermal_zone_device_disable(tz);
thermal_zone_device_unregister(tz);
- kfree(trips);
- kfree(ops);
}
/**
@@ -460,41 +360,40 @@ static void thermal_of_zone_unregister(struct thermal_zone_device *tz)
* @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
+ * - 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
*/
static 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_ops of_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;
+ const char *action;
int delay, pdelay;
- int ntrips, mask;
+ int ntrips;
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);
- ret = PTR_ERR(np);
- goto out_kfree_of_ops;
+ 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);
+ pr_err("Failed to parse trip points for %pOFn id=%d\n", sensor, id);
ret = PTR_ERR(trips);
- goto out_kfree_of_ops;
+ goto out_of_node_put;
}
+ if (!trips)
+ pr_info("No trip points found for %pOFn id=%d\n", sensor, id);
+
ret = thermal_of_monitor_init(np, &delay, &pdelay);
if (ret) {
pr_err("Failed to initialize monitoring delays from %pOFn\n", np);
@@ -503,13 +402,18 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node *
thermal_of_parameters_init(np, &tzp);
- of_ops->bind = thermal_of_bind;
- of_ops->unbind = thermal_of_unbind;
+ of_ops.should_bind = thermal_of_should_bind;
- mask = GENMASK_ULL((ntrips) - 1, 0);
+ ret = of_property_read_string(np, "critical-action", &action);
+ if (!ret && !of_ops.critical) {
+ if (!strcasecmp(action, "reboot"))
+ of_ops.critical = thermal_zone_device_critical_reboot;
+ else if (!strcasecmp(action, "shutdown"))
+ of_ops.critical = thermal_zone_device_critical_shutdown;
+ }
tz = thermal_zone_device_register_with_trips(np->name, trips, ntrips,
- mask, data, of_ops, &tzp,
+ data, &of_ops, &tzp,
pdelay, delay);
if (IS_ERR(tz)) {
ret = PTR_ERR(tz);
@@ -517,6 +421,9 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node *
goto out_kfree_trips;
}
+ of_node_put(np);
+ kfree(trips);
+
ret = thermal_zone_device_enable(tz);
if (ret) {
pr_err("Failed to enabled thermal zone '%s', id=%d: %d\n",
@@ -529,8 +436,8 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node *
out_kfree_trips:
kfree(trips);
-out_kfree_of_ops:
- kfree(of_ops);
+out_of_node_put:
+ of_node_put(np);
return ERR_PTR(ret);
}
diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c
index 6c20c9f90a05..d80612506a33 100644
--- a/drivers/thermal/thermal_sysfs.c
+++ b/drivers/thermal/thermal_sysfs.c
@@ -12,6 +12,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/container_of.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/err.h>
@@ -39,23 +40,26 @@ temp_show(struct device *dev, struct device_attribute *attr, char *buf)
ret = thermal_zone_get_temp(tz, &temperature);
- if (ret)
- return ret;
+ if (!ret)
+ return sprintf(buf, "%d\n", temperature);
+
+ if (ret == -EAGAIN)
+ return -ENODATA;
- return sprintf(buf, "%d\n", temperature);
+ return ret;
}
static ssize_t
mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
- int enabled;
- mutex_lock(&tz->lock);
- enabled = thermal_zone_device_is_enabled(tz);
- mutex_unlock(&tz->lock);
+ guard(thermal_zone)(tz);
+
+ if (tz->mode == THERMAL_DEVICE_ENABLED)
+ return sprintf(buf, "enabled\n");
- return sprintf(buf, "%s\n", enabled ? "enabled" : "disabled");
+ return sprintf(buf, "disabled\n");
}
static ssize_t
@@ -78,155 +82,113 @@ mode_store(struct device *dev, struct device_attribute *attr,
return count;
}
+#define thermal_trip_of_attr(_ptr_, _attr_) \
+ ({ \
+ struct thermal_trip_desc *td; \
+ \
+ td = container_of(_ptr_, struct thermal_trip_desc, \
+ trip_attrs._attr_.attr); \
+ &td->trip; \
+ })
+
static ssize_t
trip_point_type_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- struct thermal_zone_device *tz = to_thermal_zone(dev);
- struct thermal_trip trip;
- int trip_id, result;
-
- if (sscanf(attr->attr.name, "trip_point_%d_type", &trip_id) != 1)
- return -EINVAL;
-
- mutex_lock(&tz->lock);
-
- if (device_is_registered(dev))
- result = __thermal_zone_get_trip(tz, trip_id, &trip);
- else
- result = -ENODEV;
+ struct thermal_trip *trip = thermal_trip_of_attr(attr, type);
- mutex_unlock(&tz->lock);
-
- if (result)
- return result;
-
- switch (trip.type) {
- case THERMAL_TRIP_CRITICAL:
- return sprintf(buf, "critical\n");
- case THERMAL_TRIP_HOT:
- return sprintf(buf, "hot\n");
- case THERMAL_TRIP_PASSIVE:
- return sprintf(buf, "passive\n");
- case THERMAL_TRIP_ACTIVE:
- return sprintf(buf, "active\n");
- default:
- return sprintf(buf, "unknown\n");
- }
+ return sprintf(buf, "%s\n", thermal_trip_type_name(trip->type));
}
static ssize_t
trip_point_temp_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct thermal_trip *trip = thermal_trip_of_attr(attr, temp);
struct thermal_zone_device *tz = to_thermal_zone(dev);
- struct thermal_trip trip;
- int trip_id, ret;
+ int temp;
- if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip_id) != 1)
+ if (kstrtoint(buf, 10, &temp))
return -EINVAL;
- mutex_lock(&tz->lock);
+ guard(thermal_zone)(tz);
- if (!device_is_registered(dev)) {
- ret = -ENODEV;
- goto unlock;
+ if (temp == trip->temperature)
+ return count;
+
+ /* Arrange the condition to avoid integer overflows. */
+ if (temp != THERMAL_TEMP_INVALID &&
+ temp <= trip->hysteresis + THERMAL_TEMP_INVALID)
+ return -EINVAL;
+
+ if (tz->ops.set_trip_temp) {
+ int ret;
+
+ ret = tz->ops.set_trip_temp(tz, trip, temp);
+ if (ret)
+ return ret;
}
- ret = __thermal_zone_get_trip(tz, trip_id, &trip);
- if (ret)
- goto unlock;
+ thermal_zone_set_trip_temp(tz, trip, temp);
- ret = kstrtoint(buf, 10, &trip.temperature);
- if (ret)
- goto unlock;
+ __thermal_zone_device_update(tz, THERMAL_TRIP_CHANGED);
- ret = thermal_zone_set_trip(tz, trip_id, &trip);
-unlock:
- mutex_unlock(&tz->lock);
-
- return ret ? ret : count;
+ return count;
}
static ssize_t
trip_point_temp_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- struct thermal_zone_device *tz = to_thermal_zone(dev);
- struct thermal_trip trip;
- int trip_id, ret;
-
- if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip_id) != 1)
- return -EINVAL;
-
- mutex_lock(&tz->lock);
+ struct thermal_trip *trip = thermal_trip_of_attr(attr, temp);
- if (device_is_registered(dev))
- ret = __thermal_zone_get_trip(tz, trip_id, &trip);
- else
- ret = -ENODEV;
-
- mutex_unlock(&tz->lock);
-
- if (ret)
- return ret;
-
- return sprintf(buf, "%d\n", trip.temperature);
+ return sprintf(buf, "%d\n", READ_ONCE(trip->temperature));
}
static ssize_t
trip_point_hyst_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct thermal_trip *trip = thermal_trip_of_attr(attr, hyst);
struct thermal_zone_device *tz = to_thermal_zone(dev);
- struct thermal_trip trip;
- int trip_id, ret;
+ int hyst;
- if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip_id) != 1)
+ if (kstrtoint(buf, 10, &hyst) || hyst < 0)
return -EINVAL;
- if (kstrtoint(buf, 10, &trip.hysteresis))
- return -EINVAL;
+ guard(thermal_zone)(tz);
- mutex_lock(&tz->lock);
+ if (hyst == trip->hysteresis)
+ return count;
- if (!device_is_registered(dev)) {
- ret = -ENODEV;
- goto unlock;
+ /*
+ * Allow the hysteresis to be updated when the temperature is invalid
+ * to allow user space to avoid having to adjust hysteresis after a
+ * valid temperature has been set, but in that case just change the
+ * value and do nothing else.
+ */
+ if (trip->temperature == THERMAL_TEMP_INVALID) {
+ WRITE_ONCE(trip->hysteresis, hyst);
+ return count;
}
- ret = __thermal_zone_get_trip(tz, trip_id, &trip);
- if (ret)
- goto unlock;
-
- ret = thermal_zone_set_trip(tz, trip_id, &trip);
-unlock:
- mutex_unlock(&tz->lock);
+ if (trip->temperature - hyst <= THERMAL_TEMP_INVALID)
+ return -EINVAL;
+
+ thermal_zone_set_trip_hyst(tz, trip, hyst);
+
+ __thermal_zone_device_update(tz, THERMAL_TRIP_CHANGED);
- return ret ? ret : count;
+ return count;
}
static ssize_t
trip_point_hyst_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- struct thermal_zone_device *tz = to_thermal_zone(dev);
- struct thermal_trip trip;
- int trip_id, ret;
-
- if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip_id) != 1)
- return -EINVAL;
-
- mutex_lock(&tz->lock);
+ struct thermal_trip *trip = thermal_trip_of_attr(attr, hyst);
- if (device_is_registered(dev))
- ret = __thermal_zone_get_trip(tz, trip_id, &trip);
- else
- ret = -ENODEV;
-
- mutex_unlock(&tz->lock);
-
- return ret ? ret : sprintf(buf, "%d\n", trip.hysteresis);
+ return sprintf(buf, "%d\n", READ_ONCE(trip->hysteresis));
}
static ssize_t
@@ -267,31 +229,26 @@ emul_temp_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
- int ret = 0;
int temperature;
if (kstrtoint(buf, 10, &temperature))
return -EINVAL;
- mutex_lock(&tz->lock);
+ guard(thermal_zone)(tz);
- if (!device_is_registered(dev)) {
- ret = -ENODEV;
- goto unlock;
- }
+ if (tz->ops.set_emul_temp) {
+ int ret;
- if (!tz->ops->set_emul_temp)
+ ret = tz->ops.set_emul_temp(tz, temperature);
+ if (ret)
+ return ret;
+ } else {
tz->emul_temperature = temperature;
- else
- ret = tz->ops->set_emul_temp(tz, temperature);
-
- if (!ret)
- __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+ }
-unlock:
- mutex_unlock(&tz->lock);
+ __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
- return ret ? ret : count;
+ return count;
}
static DEVICE_ATTR_WO(emul_temp);
#endif
@@ -423,95 +380,63 @@ static const struct attribute_group *thermal_zone_attribute_groups[] = {
/**
* create_trip_attrs() - create attributes for trip points
* @tz: the thermal zone device
- * @mask: Writeable trip point bitmap.
*
* helper function to instantiate sysfs entries for every trip
* point and its properties of a struct thermal_zone_device.
*
* Return: 0 on success, the proper error value otherwise.
*/
-static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
+static int create_trip_attrs(struct thermal_zone_device *tz)
{
+ struct thermal_trip_desc *td;
struct attribute **attrs;
- int indx;
-
- /* This function works only for zones with at least one trip */
- if (tz->num_trips <= 0)
- return -EINVAL;
-
- 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->num_trips, sizeof(*tz->trip_temp_attrs),
- GFP_KERNEL);
- if (!tz->trip_temp_attrs) {
- kfree(tz->trip_type_attrs);
- return -ENOMEM;
- }
-
- tz->trip_hyst_attrs = kcalloc(tz->num_trips,
- sizeof(*tz->trip_hyst_attrs),
- GFP_KERNEL);
- if (!tz->trip_hyst_attrs) {
- kfree(tz->trip_type_attrs);
- kfree(tz->trip_temp_attrs);
- return -ENOMEM;
- }
+ int i;
attrs = kcalloc(tz->num_trips * 3 + 1, sizeof(*attrs), GFP_KERNEL);
- if (!attrs) {
- kfree(tz->trip_type_attrs);
- kfree(tz->trip_temp_attrs);
- kfree(tz->trip_hyst_attrs);
+ if (!attrs)
return -ENOMEM;
- }
- for (indx = 0; indx < tz->num_trips; indx++) {
+ i = 0;
+ for_each_trip_desc(tz, td) {
+ struct thermal_trip_attrs *trip_attrs = &td->trip_attrs;
+
/* create trip type attribute */
- snprintf(tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH,
- "trip_point_%d_type", indx);
+ snprintf(trip_attrs->type.name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_type", i);
- sysfs_attr_init(&tz->trip_type_attrs[indx].attr.attr);
- tz->trip_type_attrs[indx].attr.attr.name =
- tz->trip_type_attrs[indx].name;
- tz->trip_type_attrs[indx].attr.attr.mode = S_IRUGO;
- tz->trip_type_attrs[indx].attr.show = trip_point_type_show;
- attrs[indx] = &tz->trip_type_attrs[indx].attr.attr;
+ sysfs_attr_init(&trip_attrs->type.attr.attr);
+ trip_attrs->type.attr.attr.name = trip_attrs->type.name;
+ trip_attrs->type.attr.attr.mode = S_IRUGO;
+ trip_attrs->type.attr.show = trip_point_type_show;
+ attrs[i] = &trip_attrs->type.attr.attr;
/* create trip temp attribute */
- snprintf(tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH,
- "trip_point_%d_temp", indx);
-
- sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr);
- tz->trip_temp_attrs[indx].attr.attr.name =
- tz->trip_temp_attrs[indx].name;
- tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO;
- tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show;
- if (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) &&
- mask & (1 << indx)) {
- tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;
- tz->trip_temp_attrs[indx].attr.store =
- trip_point_temp_store;
+ snprintf(trip_attrs->temp.name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_temp", i);
+
+ sysfs_attr_init(&trip_attrs->temp.attr.attr);
+ trip_attrs->temp.attr.attr.name = trip_attrs->temp.name;
+ trip_attrs->temp.attr.attr.mode = S_IRUGO;
+ trip_attrs->temp.attr.show = trip_point_temp_show;
+ if (td->trip.flags & THERMAL_TRIP_FLAG_RW_TEMP) {
+ trip_attrs->temp.attr.attr.mode |= S_IWUSR;
+ trip_attrs->temp.attr.store = trip_point_temp_store;
}
- attrs[indx + tz->num_trips] = &tz->trip_temp_attrs[indx].attr.attr;
-
- snprintf(tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH,
- "trip_point_%d_hyst", indx);
-
- sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr);
- tz->trip_hyst_attrs[indx].attr.attr.name =
- tz->trip_hyst_attrs[indx].name;
- tz->trip_hyst_attrs[indx].attr.attr.mode = S_IRUGO;
- tz->trip_hyst_attrs[indx].attr.show = trip_point_hyst_show;
- if (tz->ops->set_trip_hyst) {
- tz->trip_hyst_attrs[indx].attr.attr.mode |= S_IWUSR;
- tz->trip_hyst_attrs[indx].attr.store =
- trip_point_hyst_store;
+ attrs[i + tz->num_trips] = &trip_attrs->temp.attr.attr;
+
+ snprintf(trip_attrs->hyst.name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_hyst", i);
+
+ sysfs_attr_init(&trip_attrs->hyst.attr.attr);
+ trip_attrs->hyst.attr.attr.name = trip_attrs->hyst.name;
+ trip_attrs->hyst.attr.attr.mode = S_IRUGO;
+ trip_attrs->hyst.attr.show = trip_point_hyst_show;
+ if (td->trip.flags & THERMAL_TRIP_FLAG_RW_HYST) {
+ trip_attrs->hyst.attr.attr.mode |= S_IWUSR;
+ trip_attrs->hyst.attr.store = trip_point_hyst_store;
}
- attrs[indx + tz->num_trips * 2] =
- &tz->trip_hyst_attrs[indx].attr.attr;
+ attrs[i + 2 * tz->num_trips] = &trip_attrs->hyst.attr.attr;
+ i++;
}
attrs[tz->num_trips * 3] = NULL;
@@ -528,17 +453,11 @@ static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
*/
static void destroy_trip_attrs(struct thermal_zone_device *tz)
{
- if (!tz)
- return;
-
- kfree(tz->trip_type_attrs);
- kfree(tz->trip_temp_attrs);
- kfree(tz->trip_hyst_attrs);
- kfree(tz->trips_attribute_group.attrs);
+ if (tz)
+ kfree(tz->trips_attribute_group.attrs);
}
-int thermal_zone_create_device_groups(struct thermal_zone_device *tz,
- int mask)
+int thermal_zone_create_device_groups(struct thermal_zone_device *tz)
{
const struct attribute_group **groups;
int i, size, result;
@@ -554,7 +473,7 @@ int thermal_zone_create_device_groups(struct thermal_zone_device *tz,
groups[i] = thermal_zone_attribute_groups[i];
if (tz->num_trips) {
- result = create_trip_attrs(tz, mask);
+ result = create_trip_attrs(tz);
if (result) {
kfree(groups);
@@ -628,14 +547,15 @@ cur_state_store(struct device *dev, struct device_attribute *attr,
if (state > cdev->max_state)
return -EINVAL;
- mutex_lock(&cdev->lock);
+ guard(cooling_dev)(cdev);
result = cdev->ops->set_cur_state(cdev, state);
- if (!result)
- thermal_cooling_device_stats_update(cdev, state);
+ if (result)
+ return result;
+
+ thermal_cooling_device_stats_update(cdev, state);
- mutex_unlock(&cdev->lock);
- return result ? result : count;
+ return count;
}
static struct device_attribute
@@ -709,21 +629,18 @@ static ssize_t total_trans_show(struct device *dev,
{
struct thermal_cooling_device *cdev = to_cooling_device(dev);
struct cooling_dev_stats *stats;
- int ret = 0;
+ int ret;
- mutex_lock(&cdev->lock);
+ guard(cooling_dev)(cdev);
stats = cdev->stats;
if (!stats)
- goto unlock;
+ return 0;
spin_lock(&stats->lock);
ret = sprintf(buf, "%u\n", stats->total_trans);
spin_unlock(&stats->lock);
-unlock:
- mutex_unlock(&cdev->lock);
-
return ret;
}
@@ -736,11 +653,11 @@ time_in_state_ms_show(struct device *dev, struct device_attribute *attr,
ssize_t len = 0;
int i;
- mutex_lock(&cdev->lock);
+ guard(cooling_dev)(cdev);
stats = cdev->stats;
if (!stats)
- goto unlock;
+ return 0;
spin_lock(&stats->lock);
@@ -752,9 +669,6 @@ time_in_state_ms_show(struct device *dev, struct device_attribute *attr,
}
spin_unlock(&stats->lock);
-unlock:
- mutex_unlock(&cdev->lock);
-
return len;
}
@@ -766,11 +680,11 @@ reset_store(struct device *dev, struct device_attribute *attr, const char *buf,
struct cooling_dev_stats *stats;
int i, states;
- mutex_lock(&cdev->lock);
+ guard(cooling_dev)(cdev);
stats = cdev->stats;
if (!stats)
- goto unlock;
+ return count;
states = cdev->max_state + 1;
@@ -786,9 +700,6 @@ reset_store(struct device *dev, struct device_attribute *attr, const char *buf,
spin_unlock(&stats->lock);
-unlock:
- mutex_unlock(&cdev->lock);
-
return count;
}
@@ -800,13 +711,11 @@ static ssize_t trans_table_show(struct device *dev,
ssize_t len = 0;
int i, j;
- mutex_lock(&cdev->lock);
+ guard(cooling_dev)(cdev);
stats = cdev->stats;
- if (!stats) {
- len = -ENODATA;
- goto unlock;
- }
+ if (!stats)
+ return -ENODATA;
len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n");
len += snprintf(buf + len, PAGE_SIZE - len, " : ");
@@ -815,10 +724,8 @@ static ssize_t trans_table_show(struct device *dev,
break;
len += snprintf(buf + len, PAGE_SIZE - len, "state%2u ", i);
}
- if (len >= PAGE_SIZE) {
- len = PAGE_SIZE;
- goto unlock;
- }
+ if (len >= PAGE_SIZE)
+ return PAGE_SIZE;
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
@@ -844,9 +751,6 @@ static ssize_t trans_table_show(struct device *dev,
len = -EFBIG;
}
-unlock:
- mutex_unlock(&cdev->lock);
-
return len;
}
@@ -937,12 +841,12 @@ void thermal_cooling_device_stats_reinit(struct thermal_cooling_device *cdev)
ssize_t
trip_point_show(struct device *dev, struct device_attribute *attr, char *buf)
{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
struct thermal_instance *instance;
- instance =
- container_of(attr, struct thermal_instance, attr);
+ instance = container_of(attr, struct thermal_instance, attr);
- return sprintf(buf, "%d\n", instance->trip);
+ return sprintf(buf, "%d\n", thermal_zone_trip_id(tz, instance->trip));
}
ssize_t
@@ -958,6 +862,7 @@ weight_show(struct device *dev, struct device_attribute *attr, char *buf)
ssize_t weight_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
struct thermal_instance *instance;
int ret, weight;
@@ -966,7 +871,13 @@ ssize_t weight_store(struct device *dev, struct device_attribute *attr,
return ret;
instance = container_of(attr, struct thermal_instance, weight_attr);
+
+ /* Don't race with governors using the 'weight' value */
+ guard(thermal_zone)(tz);
+
instance->weight = weight;
+ thermal_governor_update_tz(tz, THERMAL_INSTANCE_WEIGHT_CHANGED);
+
return count;
}
diff --git a/drivers/thermal/thermal_thresholds.c b/drivers/thermal/thermal_thresholds.c
new file mode 100644
index 000000000000..38f5fd0e8930
--- /dev/null
+++ b/drivers/thermal/thermal_thresholds.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2024 Linaro Limited
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ * Thermal thresholds
+ */
+#include <linux/list.h>
+#include <linux/list_sort.h>
+#include <linux/slab.h>
+
+#include "thermal_core.h"
+#include "thermal_thresholds.h"
+
+int thermal_thresholds_init(struct thermal_zone_device *tz)
+{
+ INIT_LIST_HEAD(&tz->user_thresholds);
+
+ return 0;
+}
+
+static void __thermal_thresholds_flush(struct thermal_zone_device *tz)
+{
+ struct list_head *thresholds = &tz->user_thresholds;
+ struct user_threshold *entry, *tmp;
+
+ list_for_each_entry_safe(entry, tmp, thresholds, list_node) {
+ list_del(&entry->list_node);
+ kfree(entry);
+ }
+}
+
+void thermal_thresholds_flush(struct thermal_zone_device *tz)
+{
+ lockdep_assert_held(&tz->lock);
+
+ __thermal_thresholds_flush(tz);
+
+ thermal_notify_threshold_flush(tz);
+
+ __thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS);
+}
+
+void thermal_thresholds_exit(struct thermal_zone_device *tz)
+{
+ __thermal_thresholds_flush(tz);
+}
+
+static int __thermal_thresholds_cmp(void *data,
+ const struct list_head *l1,
+ const struct list_head *l2)
+{
+ struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node);
+ struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node);
+
+ return t1->temperature - t2->temperature;
+}
+
+static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds,
+ int temperature)
+{
+ struct user_threshold *t;
+
+ list_for_each_entry(t, thresholds, list_node)
+ if (t->temperature == temperature)
+ return t;
+
+ return NULL;
+}
+
+static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
+ int last_temperature)
+{
+ struct user_threshold *t;
+
+ list_for_each_entry(t, thresholds, list_node) {
+
+ if (!(t->direction & THERMAL_THRESHOLD_WAY_UP))
+ continue;
+
+ if (temperature >= t->temperature &&
+ last_temperature < t->temperature)
+ return true;
+ }
+
+ return false;
+}
+
+static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
+ int last_temperature)
+{
+ struct user_threshold *t;
+
+ list_for_each_entry_reverse(t, thresholds, list_node) {
+
+ if (!(t->direction & THERMAL_THRESHOLD_WAY_DOWN))
+ continue;
+
+ if (temperature <= t->temperature &&
+ last_temperature > t->temperature)
+ return true;
+ }
+
+ return false;
+}
+
+static void thermal_threshold_find_boundaries(struct list_head *thresholds, int temperature,
+ int *low, int *high)
+{
+ struct user_threshold *t;
+
+ list_for_each_entry(t, thresholds, list_node) {
+ if (temperature < t->temperature &&
+ (t->direction & THERMAL_THRESHOLD_WAY_UP) &&
+ *high > t->temperature)
+ *high = t->temperature;
+ }
+
+ list_for_each_entry_reverse(t, thresholds, list_node) {
+ if (temperature > t->temperature &&
+ (t->direction & THERMAL_THRESHOLD_WAY_DOWN) &&
+ *low < t->temperature)
+ *low = t->temperature;
+ }
+}
+
+void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
+{
+ struct list_head *thresholds = &tz->user_thresholds;
+
+ int temperature = tz->temperature;
+ int last_temperature = tz->last_temperature;
+
+ lockdep_assert_held(&tz->lock);
+
+ thermal_threshold_find_boundaries(thresholds, temperature, low, high);
+
+ /*
+ * We need a second update in order to detect a threshold being crossed
+ */
+ if (last_temperature == THERMAL_TEMP_INVALID)
+ return;
+
+ /*
+ * The temperature is stable, so obviously we can not have
+ * crossed a threshold.
+ */
+ if (last_temperature == temperature)
+ return;
+
+ /*
+ * Since last update the temperature:
+ * - increased : thresholds are crossed the way up
+ * - decreased : thresholds are crossed the way down
+ */
+ if (temperature > last_temperature) {
+ if (thermal_thresholds_handle_raising(thresholds,
+ temperature, last_temperature))
+ thermal_notify_threshold_up(tz);
+ } else {
+ if (thermal_thresholds_handle_dropping(thresholds,
+ temperature, last_temperature))
+ thermal_notify_threshold_down(tz);
+ }
+}
+
+int thermal_thresholds_add(struct thermal_zone_device *tz,
+ int temperature, int direction)
+{
+ struct list_head *thresholds = &tz->user_thresholds;
+ struct user_threshold *t;
+
+ lockdep_assert_held(&tz->lock);
+
+ t = __thermal_thresholds_find(thresholds, temperature);
+ if (t) {
+ if (t->direction == direction)
+ return -EEXIST;
+
+ t->direction |= direction;
+ } else {
+
+ t = kmalloc(sizeof(*t), GFP_KERNEL);
+ if (!t)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&t->list_node);
+ t->temperature = temperature;
+ t->direction = direction;
+ list_add(&t->list_node, thresholds);
+ list_sort(NULL, thresholds, __thermal_thresholds_cmp);
+ }
+
+ thermal_notify_threshold_add(tz, temperature, direction);
+
+ __thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
+
+ return 0;
+}
+
+int thermal_thresholds_delete(struct thermal_zone_device *tz,
+ int temperature, int direction)
+{
+ struct list_head *thresholds = &tz->user_thresholds;
+ struct user_threshold *t;
+
+ lockdep_assert_held(&tz->lock);
+
+ t = __thermal_thresholds_find(thresholds, temperature);
+ if (!t)
+ return -ENOENT;
+
+ if (t->direction == direction) {
+ list_del(&t->list_node);
+ kfree(t);
+ } else {
+ t->direction &= ~direction;
+ }
+
+ thermal_notify_threshold_delete(tz, temperature, direction);
+
+ __thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
+
+ return 0;
+}
+
+int thermal_thresholds_for_each(struct thermal_zone_device *tz,
+ int (*cb)(struct user_threshold *, void *arg), void *arg)
+{
+ struct list_head *thresholds = &tz->user_thresholds;
+ struct user_threshold *entry;
+ int ret;
+
+ guard(thermal_zone)(tz);
+
+ list_for_each_entry(entry, thresholds, list_node) {
+ ret = cb(entry, arg);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/drivers/thermal/thermal_thresholds.h b/drivers/thermal/thermal_thresholds.h
new file mode 100644
index 000000000000..cb372659a20d
--- /dev/null
+++ b/drivers/thermal/thermal_thresholds.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __THERMAL_THRESHOLDS_H__
+#define __THERMAL_THRESHOLDS_H__
+
+struct user_threshold {
+ struct list_head list_node;
+ int temperature;
+ int direction;
+};
+
+int thermal_thresholds_init(struct thermal_zone_device *tz);
+void thermal_thresholds_exit(struct thermal_zone_device *tz);
+void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high);
+void thermal_thresholds_flush(struct thermal_zone_device *tz);
+int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction);
+int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction);
+int thermal_thresholds_for_each(struct thermal_zone_device *tz,
+ int (*cb)(struct user_threshold *, void *arg), void *arg);
+#endif
diff --git a/drivers/thermal/thermal_trace.h b/drivers/thermal/thermal_trace.h
index 459c8ce6cf3b..df8f4edd6068 100644
--- a/drivers/thermal/thermal_trace.h
+++ b/drivers/thermal/thermal_trace.h
@@ -9,6 +9,8 @@
#include <linux/thermal.h>
#include <linux/tracepoint.h>
+#include "thermal_core.h"
+
TRACE_DEFINE_ENUM(THERMAL_TRIP_CRITICAL);
TRACE_DEFINE_ENUM(THERMAL_TRIP_HOT);
TRACE_DEFINE_ENUM(THERMAL_TRIP_PASSIVE);
@@ -35,7 +37,7 @@ TRACE_EVENT(thermal_temperature,
),
TP_fast_assign(
- __assign_str(thermal_zone, tz->type);
+ __assign_str(thermal_zone);
__entry->id = tz->id;
__entry->temp_prev = tz->last_temperature;
__entry->temp = tz->temperature;
@@ -58,7 +60,7 @@ TRACE_EVENT(cdev_update,
),
TP_fast_assign(
- __assign_str(type, cdev->type);
+ __assign_str(type);
__entry->target = target;
),
@@ -80,7 +82,7 @@ TRACE_EVENT(thermal_zone_trip,
),
TP_fast_assign(
- __assign_str(thermal_zone, tz->type);
+ __assign_str(thermal_zone);
__entry->id = tz->id;
__entry->trip = trip;
__entry->trip_type = trip_type;
@@ -154,7 +156,7 @@ TRACE_EVENT(thermal_power_devfreq_get_power,
),
TP_fast_assign(
- __assign_str(type, cdev->type);
+ __assign_str(type);
__entry->freq = freq;
__entry->busy_time = status->busy_time;
__entry->total_time = status->total_time;
@@ -182,7 +184,7 @@ TRACE_EVENT(thermal_power_devfreq_limit,
),
TP_fast_assign(
- __assign_str(type, cdev->type);
+ __assign_str(type);
__entry->freq = freq;
__entry->cdev_state = cdev_state;
__entry->power = power;
diff --git a/drivers/thermal/thermal_trace_ipa.h b/drivers/thermal/thermal_trace_ipa.h
index 84568db5421b..a82821eebc88 100644
--- a/drivers/thermal/thermal_trace_ipa.h
+++ b/drivers/thermal/thermal_trace_ipa.h
@@ -7,20 +7,17 @@
#include <linux/tracepoint.h>
+#include "thermal_core.h"
+
TRACE_EVENT(thermal_power_allocator,
- TP_PROTO(struct thermal_zone_device *tz, u32 *req_power,
- u32 total_req_power, u32 *granted_power,
- u32 total_granted_power, size_t num_actors,
- u32 power_range, u32 max_allocatable_power,
- int current_temp, s32 delta_temp),
- TP_ARGS(tz, req_power, total_req_power, granted_power,
- total_granted_power, num_actors, power_range,
- max_allocatable_power, current_temp, delta_temp),
+ TP_PROTO(struct thermal_zone_device *tz, u32 total_req_power,
+ u32 total_granted_power, int num_actors, u32 power_range,
+ u32 max_allocatable_power, int current_temp, s32 delta_temp),
+ TP_ARGS(tz, total_req_power, total_granted_power, num_actors,
+ power_range, max_allocatable_power, current_temp, delta_temp),
TP_STRUCT__entry(
__field(int, tz_id )
- __dynamic_array(u32, req_power, num_actors )
__field(u32, total_req_power )
- __dynamic_array(u32, granted_power, num_actors)
__field(u32, total_granted_power )
__field(size_t, num_actors )
__field(u32, power_range )
@@ -30,11 +27,7 @@ TRACE_EVENT(thermal_power_allocator,
),
TP_fast_assign(
__entry->tz_id = tz->id;
- memcpy(__get_dynamic_array(req_power), req_power,
- num_actors * sizeof(*req_power));
__entry->total_req_power = total_req_power;
- memcpy(__get_dynamic_array(granted_power), granted_power,
- num_actors * sizeof(*granted_power));
__entry->total_granted_power = total_granted_power;
__entry->num_actors = num_actors;
__entry->power_range = power_range;
@@ -43,18 +36,35 @@ TRACE_EVENT(thermal_power_allocator,
__entry->delta_temp = delta_temp;
),
- TP_printk("thermal_zone_id=%d req_power={%s} total_req_power=%u granted_power={%s} total_granted_power=%u power_range=%u max_allocatable_power=%u current_temperature=%d delta_temperature=%d",
- __entry->tz_id,
- __print_array(__get_dynamic_array(req_power),
- __entry->num_actors, 4),
- __entry->total_req_power,
- __print_array(__get_dynamic_array(granted_power),
- __entry->num_actors, 4),
+ TP_printk("thermal_zone_id=%d total_req_power=%u total_granted_power=%u power_range=%u max_allocatable_power=%u current_temperature=%d delta_temperature=%d",
+ __entry->tz_id, __entry->total_req_power,
__entry->total_granted_power, __entry->power_range,
__entry->max_allocatable_power, __entry->current_temp,
__entry->delta_temp)
);
+TRACE_EVENT(thermal_power_actor,
+ TP_PROTO(struct thermal_zone_device *tz, int actor_id, u32 req_power,
+ u32 granted_power),
+ TP_ARGS(tz, actor_id, req_power, granted_power),
+ TP_STRUCT__entry(
+ __field(int, tz_id)
+ __field(int, actor_id)
+ __field(u32, req_power)
+ __field(u32, granted_power)
+ ),
+ TP_fast_assign(
+ __entry->tz_id = tz->id;
+ __entry->actor_id = actor_id;
+ __entry->req_power = req_power;
+ __entry->granted_power = granted_power;
+ ),
+
+ TP_printk("thermal_zone_id=%d actor_id=%d req_power=%u granted_power=%u",
+ __entry->tz_id, __entry->actor_id, __entry->req_power,
+ __entry->granted_power)
+);
+
TRACE_EVENT(thermal_power_allocator_pid,
TP_PROTO(struct thermal_zone_device *tz, s32 err, s32 err_integral,
s64 p, s64 i, s64 d, s32 output),
diff --git a/drivers/thermal/thermal_trip.c b/drivers/thermal/thermal_trip.c
index 907f3a4d7bc8..4b8238468b53 100644
--- a/drivers/thermal/thermal_trip.c
+++ b/drivers/thermal/thermal_trip.c
@@ -9,78 +9,57 @@
*/
#include "thermal_core.h"
-int __for_each_thermal_trip(struct thermal_zone_device *tz,
- int (*cb)(struct thermal_trip *, void *),
- void *data)
+static const char *trip_type_names[] = {
+ [THERMAL_TRIP_ACTIVE] = "active",
+ [THERMAL_TRIP_PASSIVE] = "passive",
+ [THERMAL_TRIP_HOT] = "hot",
+ [THERMAL_TRIP_CRITICAL] = "critical",
+};
+
+const char *thermal_trip_type_name(enum thermal_trip_type trip_type)
{
- int i, ret;
- struct thermal_trip trip;
+ if (trip_type < THERMAL_TRIP_ACTIVE || trip_type > THERMAL_TRIP_CRITICAL)
+ return "unknown";
- lockdep_assert_held(&tz->lock);
-
- for (i = 0; i < tz->num_trips; i++) {
+ return trip_type_names[trip_type];
+}
- ret = __thermal_zone_get_trip(tz, i, &trip);
- if (ret)
- return ret;
+int for_each_thermal_trip(struct thermal_zone_device *tz,
+ int (*cb)(struct thermal_trip *, void *),
+ void *data)
+{
+ struct thermal_trip_desc *td;
+ int ret;
- ret = cb(&trip, data);
+ for_each_trip_desc(tz, td) {
+ ret = cb(&td->trip, data);
if (ret)
return ret;
}
return 0;
}
+EXPORT_SYMBOL_GPL(for_each_thermal_trip);
-int thermal_zone_get_num_trips(struct thermal_zone_device *tz)
+int thermal_zone_for_each_trip(struct thermal_zone_device *tz,
+ int (*cb)(struct thermal_trip *, void *),
+ void *data)
{
- return tz->num_trips;
+ guard(thermal_zone)(tz);
+
+ return for_each_thermal_trip(tz, cb, data);
}
-EXPORT_SYMBOL_GPL(thermal_zone_get_num_trips);
+EXPORT_SYMBOL_GPL(thermal_zone_for_each_trip);
-/**
- * __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).
- *
- * This function must be called with tz->lock held. Both tz and tz->ops
- * must be valid pointers.
- *
- * It does not return a value
- */
-void __thermal_zone_set_trips(struct thermal_zone_device *tz)
+void thermal_zone_set_trips(struct thermal_zone_device *tz, int low, int high)
{
- struct thermal_trip trip;
- int low = -INT_MAX, high = INT_MAX;
- int i, ret;
+ int ret;
lockdep_assert_held(&tz->lock);
- if (!tz->ops->set_trips)
+ if (!tz->ops.set_trips)
return;
- for (i = 0; i < tz->num_trips; i++) {
- int trip_low;
-
- ret = __thermal_zone_get_trip(tz, i , &trip);
- if (ret)
- return;
-
- trip_low = trip.temperature - trip.hysteresis;
-
- if (trip_low < tz->temperature && trip_low > low)
- low = trip_low;
-
- if (trip.temperature > tz->temperature &&
- trip.temperature < high)
- high = trip.temperature;
- }
-
/* No need to change trip points */
if (tz->prev_low_trip == low && tz->prev_high_trip == high)
return;
@@ -95,88 +74,17 @@ void __thermal_zone_set_trips(struct thermal_zone_device *tz)
* Set a temperature window. When this window is left the driver
* must inform the thermal core via thermal_zone_device_update.
*/
- ret = tz->ops->set_trips(tz, low, high);
+ ret = tz->ops.set_trips(tz, low, high);
if (ret)
dev_err(&tz->device, "Failed to set trips: %d\n", ret);
}
-int __thermal_zone_get_trip(struct thermal_zone_device *tz, int trip_id,
- struct thermal_trip *trip)
-{
- int ret;
-
- if (!tz || trip_id < 0 || trip_id >= tz->num_trips || !trip)
- return -EINVAL;
-
- if (tz->trips) {
- *trip = tz->trips[trip_id];
- return 0;
- }
-
- if (tz->ops->get_trip_hyst) {
- ret = tz->ops->get_trip_hyst(tz, trip_id, &trip->hysteresis);
- if (ret)
- return ret;
- } else {
- trip->hysteresis = 0;
- }
-
- ret = tz->ops->get_trip_temp(tz, trip_id, &trip->temperature);
- if (ret)
- return ret;
-
- return tz->ops->get_trip_type(tz, trip_id, &trip->type);
-}
-EXPORT_SYMBOL_GPL(__thermal_zone_get_trip);
-
-int thermal_zone_get_trip(struct thermal_zone_device *tz, int trip_id,
- struct thermal_trip *trip)
+int thermal_zone_trip_id(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
{
- int ret;
-
- mutex_lock(&tz->lock);
- ret = __thermal_zone_get_trip(tz, trip_id, trip);
- mutex_unlock(&tz->lock);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(thermal_zone_get_trip);
-
-int thermal_zone_set_trip(struct thermal_zone_device *tz, int trip_id,
- const struct thermal_trip *trip)
-{
- struct thermal_trip t;
- int ret;
-
- if (!tz->ops->set_trip_temp && !tz->ops->set_trip_hyst && !tz->trips)
- return -EINVAL;
-
- ret = __thermal_zone_get_trip(tz, trip_id, &t);
- if (ret)
- return ret;
-
- if (t.type != trip->type)
- return -EINVAL;
-
- if (t.temperature != trip->temperature && tz->ops->set_trip_temp) {
- ret = tz->ops->set_trip_temp(tz, trip_id, trip->temperature);
- if (ret)
- return ret;
- }
-
- if (t.hysteresis != trip->hysteresis && tz->ops->set_trip_hyst) {
- ret = tz->ops->set_trip_hyst(tz, trip_id, trip->hysteresis);
- if (ret)
- return ret;
- }
-
- if (tz->trips && (t.temperature != trip->temperature || t.hysteresis != trip->hysteresis))
- tz->trips[trip_id] = *trip;
-
- thermal_notify_tz_trip_change(tz->id, trip_id, trip->type,
- trip->temperature, trip->hysteresis);
-
- __thermal_zone_device_update(tz, THERMAL_TRIP_CHANGED);
-
- return 0;
+ /*
+ * Assume the trip to be located within the bounds of the thermal
+ * zone's trips[] table.
+ */
+ return trip_to_trip_desc(trip) - tz->trips;
}
diff --git a/drivers/thermal/ti-soc-thermal/dra752-bandgap.h b/drivers/thermal/ti-soc-thermal/dra752-bandgap.h
index d1b5b699cf23..1402b8c44c6b 100644
--- a/drivers/thermal/ti-soc-thermal/dra752-bandgap.h
+++ b/drivers/thermal/ti-soc-thermal/dra752-bandgap.h
@@ -74,7 +74,7 @@
/**
* Register bitfields for DRA752
*
- * All the macros bellow define the required bits for
+ * All the macros below define the required bits for
* controlling temperature on DRA752. Bit defines are
* grouped by register.
*/
@@ -125,7 +125,7 @@
/**
* Temperature limits and thresholds for DRA752
*
- * All the macros bellow are definitions for handling the
+ * All the macros below are definitions for handling the
* ADC conversions and representation of temperature limits
* and thresholds for DRA752. Definitions are grouped
* by temperature domain.
diff --git a/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h
index c63f439e01d6..3963f1badfc9 100644
--- a/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h
+++ b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h
@@ -32,7 +32,7 @@
/**
* Register and bit definitions for OMAP4430
*
- * All the macros bellow define the required bits for
+ * All the macros below define the required bits for
* controlling temperature on OMAP4430. Bit defines are
* grouped by register.
*/
@@ -48,7 +48,7 @@
/**
* Temperature limits and thresholds for OMAP4430
*
- * All the macros bellow are definitions for handling the
+ * All the macros below are definitions for handling the
* ADC conversions and representation of temperature limits
* and thresholds for OMAP4430.
*/
@@ -102,7 +102,7 @@
/**
* Register bitfields for OMAP4460
*
- * All the macros bellow define the required bits for
+ * All the macros below define the required bits for
* controlling temperature on OMAP4460. Bit defines are
* grouped by register.
*/
@@ -135,7 +135,7 @@
/**
* Temperature limits and thresholds for OMAP4460
*
- * All the macros bellow are definitions for handling the
+ * All the macros below are definitions for handling the
* ADC conversions and representation of temperature limits
* and thresholds for OMAP4460.
*/
diff --git a/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h b/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h
index 3880e667ea96..b70084b8013a 100644
--- a/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h
+++ b/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h
@@ -56,7 +56,7 @@
/**
* Register bitfields for OMAP5430
*
- * All the macros bellow define the required bits for
+ * All the macros below define the required bits for
* controlling temperature on OMAP5430. Bit defines are
* grouped by register.
*/
@@ -101,7 +101,7 @@
/**
* Temperature limits and thresholds for OMAP5430
*
- * All the macros bellow are definitions for handling the
+ * All the macros below are definitions for handling the
* ADC conversions and representation of temperature limits
* and thresholds for OMAP5430. Definitions are grouped
* by temperature domain.
diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c
index a1c9a1530183..ba43399d0b38 100644
--- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c
+++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c
@@ -314,7 +314,7 @@ int ti_bandgap_adc_to_mcelsius(struct ti_bandgap *bgp, int adc_val, int *t)
*/
static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id)
{
- if (!bgp || IS_ERR(bgp)) {
+ if (IS_ERR_OR_NULL(bgp)) {
pr_err("%s: invalid bandgap pointer\n", __func__);
return -EINVAL;
}
@@ -1069,7 +1069,7 @@ free_irqs:
}
static
-int ti_bandgap_remove(struct platform_device *pdev)
+void ti_bandgap_remove(struct platform_device *pdev)
{
struct ti_bandgap *bgp = platform_get_drvdata(pdev);
int i;
@@ -1098,8 +1098,6 @@ int ti_bandgap_remove(struct platform_device *pdev)
if (TI_BANDGAP_HAS(bgp, TSHUT))
free_irq(gpiod_to_irq(bgp->tshut_gpiod), NULL);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.h b/drivers/thermal/ti-soc-thermal/ti-bandgap.h
index 1f4bbaf31675..46263c1da8b6 100644
--- a/drivers/thermal/ti-soc-thermal/ti-bandgap.h
+++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.h
@@ -336,10 +336,6 @@ struct ti_bandgap_data {
struct ti_temp_sensor sensors[];
};
-int ti_bandgap_read_thot(struct ti_bandgap *bgp, int id, int *thot);
-int ti_bandgap_write_thot(struct ti_bandgap *bgp, int id, int val);
-int ti_bandgap_read_tcold(struct ti_bandgap *bgp, int id, int *tcold);
-int ti_bandgap_write_tcold(struct ti_bandgap *bgp, int id, int val);
int ti_bandgap_read_update_interval(struct ti_bandgap *bgp, int id,
int *interval);
int ti_bandgap_write_update_interval(struct ti_bandgap *bgp, int id,
diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
index d414a4b7a94a..0cf0826b805a 100644
--- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
+++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
@@ -109,7 +109,9 @@ static inline int __ti_thermal_get_temp(struct thermal_zone_device *tz, int *tem
return ret;
}
-static int __ti_thermal_get_trend(struct thermal_zone_device *tz, int trip, enum thermal_trend *trend)
+static int __ti_thermal_get_trend(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ enum thermal_trend *trend)
{
struct ti_thermal_data *data = thermal_zone_device_priv(tz);
struct ti_bandgap *bgp;
diff --git a/drivers/thermal/uniphier_thermal.c b/drivers/thermal/uniphier_thermal.c
index aef6119cc004..1a04294effea 100644
--- a/drivers/thermal/uniphier_thermal.c
+++ b/drivers/thermal/uniphier_thermal.c
@@ -12,7 +12,6 @@
#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/thermal.h>
@@ -240,13 +239,34 @@ static irqreturn_t uniphier_tm_alarm_irq_thread(int irq, void *_tdev)
return IRQ_HANDLED;
}
+struct trip_walk_data {
+ struct uniphier_tm_dev *tdev;
+ int crit_temp;
+ int index;
+};
+
+static int uniphier_tm_trip_walk_cb(struct thermal_trip *trip, void *arg)
+{
+ struct trip_walk_data *twd = arg;
+
+ if (trip->type == THERMAL_TRIP_CRITICAL &&
+ trip->temperature < twd->crit_temp)
+ twd->crit_temp = trip->temperature;
+
+ uniphier_tm_set_alert(twd->tdev, twd->index, trip->temperature);
+ twd->tdev->alert_en[twd->index++] = true;
+
+ return 0;
+}
+
static int uniphier_tm_probe(struct platform_device *pdev)
{
+ struct trip_walk_data twd = { .crit_temp = INT_MAX, .index = 0 };
struct device *dev = &pdev->dev;
struct regmap *regmap;
struct device_node *parent;
struct uniphier_tm_dev *tdev;
- int i, ret, irq, crit_temp = INT_MAX;
+ int ret, irq;
tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
if (!tdev)
@@ -294,20 +314,10 @@ static int uniphier_tm_probe(struct platform_device *pdev)
}
/* set alert temperatures */
- for (i = 0; i < thermal_zone_get_num_trips(tdev->tz_dev); i++) {
- struct thermal_trip trip;
-
- ret = thermal_zone_get_trip(tdev->tz_dev, i, &trip);
- if (ret)
- return ret;
+ twd.tdev = tdev;
+ thermal_zone_for_each_trip(tdev->tz_dev, uniphier_tm_trip_walk_cb, &twd);
- if (trip.type == THERMAL_TRIP_CRITICAL &&
- trip.temperature < crit_temp)
- crit_temp = trip.temperature;
- uniphier_tm_set_alert(tdev, i, trip.temperature);
- tdev->alert_en[i] = true;
- }
- if (crit_temp > CRITICAL_TEMP_LIMIT) {
+ if (twd.crit_temp > CRITICAL_TEMP_LIMIT) {
dev_err(dev, "critical trip is over limit(>%d), or not set\n",
CRITICAL_TEMP_LIMIT);
return -EINVAL;
@@ -318,14 +328,12 @@ static int uniphier_tm_probe(struct platform_device *pdev)
return 0;
}
-static int uniphier_tm_remove(struct platform_device *pdev)
+static void uniphier_tm_remove(struct platform_device *pdev)
{
struct uniphier_tm_dev *tdev = platform_get_drvdata(pdev);
/* disable sensor */
uniphier_tm_disable_sensor(tdev);
-
- return 0;
}
static const struct uniphier_tm_soc_data uniphier_pxs2_tm_data = {