summaryrefslogtreecommitdiffstats
path: root/drivers/thermal
diff options
context:
space:
mode:
authorSteven Rostedt <rostedt@goodmis.org>2026-09-02 09:55:01 -0400
committerSteven Rostedt <rostedt@goodmis.org>2026-09-02 11:08:21 -0400
commit4617721c502b2ddaa4e324e86da4997edf738fa5 (patch)
tree350aa83b47da0b574a814795392b20b0bed83cbc /drivers/thermal
downloadlinux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz
linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/thermal')
-rw-r--r--drivers/thermal/Kconfig533
-rw-r--r--drivers/thermal/Makefile72
-rw-r--r--drivers/thermal/airoha_thermal.c733
-rw-r--r--drivers/thermal/amlogic_thermal.c395
-rw-r--r--drivers/thermal/armada_thermal.c969
-rw-r--r--drivers/thermal/broadcom/Kconfig41
-rw-r--r--drivers/thermal/broadcom/Makefile6
-rw-r--r--drivers/thermal/broadcom/bcm2711_thermal.c115
-rw-r--r--drivers/thermal/broadcom/bcm2835_thermal.c277
-rw-r--r--drivers/thermal/broadcom/brcmstb_thermal.c378
-rw-r--r--drivers/thermal/broadcom/ns-thermal.c93
-rw-r--r--drivers/thermal/broadcom/sr-thermal.c115
-rw-r--r--drivers/thermal/cpufreq_cooling.c695
-rw-r--r--drivers/thermal/cpuidle_cooling.c270
-rw-r--r--drivers/thermal/da9062-thermal.c265
-rw-r--r--drivers/thermal/db8500_thermal.c239
-rw-r--r--drivers/thermal/devfreq_cooling.c576
-rw-r--r--drivers/thermal/dove_thermal.c184
-rw-r--r--drivers/thermal/gov_bang_bang.c129
-rw-r--r--drivers/thermal/gov_fair_share.c119
-rw-r--r--drivers/thermal/gov_power_allocator.c800
-rw-r--r--drivers/thermal/gov_step_wise.c152
-rw-r--r--drivers/thermal/gov_user_space.c58
-rw-r--r--drivers/thermal/hisi_thermal.c649
-rw-r--r--drivers/thermal/imx8mm_thermal.c408
-rw-r--r--drivers/thermal/imx91_thermal.c388
-rw-r--r--drivers/thermal/imx_sc_thermal.c146
-rw-r--r--drivers/thermal/imx_thermal.c874
-rw-r--r--drivers/thermal/intel/Kconfig117
-rw-r--r--drivers/thermal/intel/Makefile16
-rw-r--r--drivers/thermal/intel/int340x_thermal/Kconfig50
-rw-r--r--drivers/thermal/intel/int340x_thermal/Makefile19
-rw-r--r--drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c591
-rw-r--r--drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h146
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3400_thermal.c715
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3401_thermal.c75
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3402_thermal.c105
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3403_thermal.c298
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3406_thermal.c208
-rw-r--r--drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c242
-rw-r--r--drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h53
-rw-r--r--drivers/thermal/intel/int340x_thermal/platform_temperature_control.c314
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device.c496
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device.h138
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c533
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c159
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c159
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_power_floor.c127
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c195
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c523
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c290
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c297
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_wt_req.c138
-rw-r--r--drivers/thermal/intel/intel_bxt_pmic_thermal.c288
-rw-r--r--drivers/thermal/intel/intel_hfi.c732
-rw-r--r--drivers/thermal/intel/intel_hfi.h17
-rw-r--r--drivers/thermal/intel/intel_pch_thermal.c407
-rw-r--r--drivers/thermal/intel/intel_powerclamp.c816
-rw-r--r--drivers/thermal/intel/intel_quark_dts_thermal.c431
-rw-r--r--drivers/thermal/intel/intel_soc_dts_iosf.c393
-rw-r--r--drivers/thermal/intel/intel_soc_dts_iosf.h50
-rw-r--r--drivers/thermal/intel/intel_soc_dts_thermal.c109
-rw-r--r--drivers/thermal/intel/intel_tcc.c307
-rw-r--r--drivers/thermal/intel/intel_tcc_cooling.c129
-rw-r--r--drivers/thermal/intel/therm_throt.c1088
-rw-r--r--drivers/thermal/intel/thermal_interrupt.h24
-rw-r--r--drivers/thermal/intel/x86_pkg_temp_thermal.c532
-rw-r--r--drivers/thermal/k3_bandgap.c264
-rw-r--r--drivers/thermal/k3_j72xx_bandgap.c613
-rw-r--r--drivers/thermal/khadas_mcu_fan.c162
-rw-r--r--drivers/thermal/kirkwood_thermal.c116
-rw-r--r--drivers/thermal/loongson2_thermal.c217
-rw-r--r--drivers/thermal/max77620_thermal.c156
-rw-r--r--drivers/thermal/mediatek/Kconfig37
-rw-r--r--drivers/thermal/mediatek/Makefile2
-rw-r--r--drivers/thermal/mediatek/auxadc_thermal.c1316
-rw-r--r--drivers/thermal/mediatek/lvts_thermal.c2220
-rw-r--r--drivers/thermal/pcie_cooling.c80
-rw-r--r--drivers/thermal/qcom/Kconfig67
-rw-r--r--drivers/thermal/qcom/Makefile10
-rw-r--r--drivers/thermal/qcom/lmh.c252
-rw-r--r--drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c434
-rw-r--r--drivers/thermal/qcom/qcom-spmi-adc-tm5.c1069
-rw-r--r--drivers/thermal/qcom/qcom-spmi-mbg-tm.c257
-rw-r--r--drivers/thermal/qcom/qcom-spmi-temp-alarm.c907
-rw-r--r--drivers/thermal/qcom/tsens-8960.c285
-rw-r--r--drivers/thermal/qcom/tsens-v0_1.c408
-rw-r--r--drivers/thermal/qcom/tsens-v1.c258
-rw-r--r--drivers/thermal/qcom/tsens-v2.c316
-rw-r--r--drivers/thermal/qcom/tsens.c1478
-rw-r--r--drivers/thermal/qcom/tsens.h684
-rw-r--r--drivers/thermal/qoriq_thermal.c470
-rw-r--r--drivers/thermal/renesas/Kconfig43
-rw-r--r--drivers/thermal/renesas/Makefile7
-rw-r--r--drivers/thermal/renesas/rcar_gen3_thermal.c634
-rw-r--r--drivers/thermal/renesas/rcar_thermal.c579
-rw-r--r--drivers/thermal/renesas/rzg2l_thermal.c248
-rw-r--r--drivers/thermal/renesas/rzg3e_thermal.c569
-rw-r--r--drivers/thermal/renesas/rzg3s_thermal.c273
-rw-r--r--drivers/thermal/rockchip_thermal.c1895
-rw-r--r--drivers/thermal/samsung/Kconfig12
-rw-r--r--drivers/thermal/samsung/Makefile6
-rw-r--r--drivers/thermal/samsung/exynos_tmu.c1173
-rw-r--r--drivers/thermal/spacemit/Kconfig19
-rw-r--r--drivers/thermal/spacemit/Makefile3
-rw-r--r--drivers/thermal/spacemit/k1_tsensor.c326
-rw-r--r--drivers/thermal/spear_thermal.c188
-rw-r--r--drivers/thermal/sprd_thermal.c549
-rw-r--r--drivers/thermal/st/Kconfig23
-rw-r--r--drivers/thermal/st/Makefile4
-rw-r--r--drivers/thermal/st/st_thermal.c267
-rw-r--r--drivers/thermal/st/st_thermal.h100
-rw-r--r--drivers/thermal/st/st_thermal_memmap.c182
-rw-r--r--drivers/thermal/st/stm_thermal.c589
-rw-r--r--drivers/thermal/sun8i_thermal.c735
-rw-r--r--drivers/thermal/tegra/Kconfig28
-rw-r--r--drivers/thermal/tegra/Makefile10
-rw-r--r--drivers/thermal/tegra/soctherm-fuse.c166
-rw-r--r--drivers/thermal/tegra/soctherm.c2290
-rw-r--r--drivers/thermal/tegra/soctherm.h162
-rw-r--r--drivers/thermal/tegra/tegra-bpmp-thermal.c328
-rw-r--r--drivers/thermal/tegra/tegra114-soctherm.c209
-rw-r--r--drivers/thermal/tegra/tegra124-soctherm.c223
-rw-r--r--drivers/thermal/tegra/tegra132-soctherm.c223
-rw-r--r--drivers/thermal/tegra/tegra210-soctherm.c232
-rw-r--r--drivers/thermal/tegra/tegra30-tsensor.c677
-rw-r--r--drivers/thermal/testing/Makefile7
-rw-r--r--drivers/thermal/testing/command.c251
-rw-r--r--drivers/thermal/testing/thermal_testing.h19
-rw-r--r--drivers/thermal/testing/zone.c446
-rw-r--r--drivers/thermal/thermal-fw.c12
-rw-r--r--drivers/thermal/thermal-generic-adc.c231
-rw-r--r--drivers/thermal/thermal_core.c1831
-rw-r--r--drivers/thermal/thermal_core.h312
-rw-r--r--drivers/thermal/thermal_debugfs.c968
-rw-r--r--drivers/thermal/thermal_debugfs.h30
-rw-r--r--drivers/thermal/thermal_fw.c97
-rw-r--r--drivers/thermal/thermal_helpers.c240
-rw-r--r--drivers/thermal/thermal_hwmon.c272
-rw-r--r--drivers/thermal/thermal_hwmon.h41
-rw-r--r--drivers/thermal/thermal_mmio.c115
-rw-r--r--drivers/thermal/thermal_netlink.c937
-rw-r--r--drivers/thermal/thermal_netlink.h178
-rw-r--r--drivers/thermal/thermal_of.c652
-rw-r--r--drivers/thermal/thermal_sysfs.c879
-rw-r--r--drivers/thermal/thermal_thresholds.c244
-rw-r--r--drivers/thermal/thermal_thresholds.h19
-rw-r--r--drivers/thermal/thermal_trace.h207
-rw-r--r--drivers/thermal/thermal_trace_ipa.h104
-rw-r--r--drivers/thermal/thermal_trip.c90
-rw-r--r--drivers/thermal/ti-soc-thermal/Kconfig73
-rw-r--r--drivers/thermal/ti-soc-thermal/Makefile8
-rw-r--r--drivers/thermal/ti-soc-thermal/TODO12
-rw-r--r--drivers/thermal/ti-soc-thermal/dra752-bandgap.h179
-rw-r--r--drivers/thermal/ti-soc-thermal/dra752-thermal-data.c372
-rw-r--r--drivers/thermal/ti-soc-thermal/omap3-thermal-data.c158
-rw-r--r--drivers/thermal/ti-soc-thermal/omap4-thermal-data.c244
-rw-r--r--drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h155
-rw-r--r--drivers/thermal/ti-soc-thermal/omap5-thermal-data.c303
-rw-r--r--drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h145
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-bandgap.c1297
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-bandgap.h378
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-thermal-common.c271
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-thermal.h78
-rw-r--r--drivers/thermal/uniphier_thermal.c384
165 files changed, 58295 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
new file mode 100644
index 000000000..810eecced
--- /dev/null
+++ b/drivers/thermal/Kconfig
@@ -0,0 +1,533 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Generic thermal drivers configuration
+#
+
+menuconfig THERMAL
+ bool "Thermal drivers"
+ help
+ Thermal drivers offer a generic mechanism for
+ thermal management. Usually it's made up of one or more thermal
+ zones and cooling devices.
+ Each thermal zone contains its own temperature, trip points,
+ and cooling devices.
+ All platforms with ACPI or Open Firmware thermal support can use
+ this driver.
+ If you want this support, you should say Y here.
+
+if THERMAL
+
+config THERMAL_NETLINK
+ bool "Thermal netlink management"
+ depends on NET
+ help
+ The thermal framework has a netlink interface to do thermal
+ zones discovery, temperature readings and events such as
+ trip point crossed, cooling device update or governor
+ change. It is recommended to enable the feature.
+
+config THERMAL_STATISTICS
+ bool "Thermal state transition statistics"
+ help
+ Export thermal state transition statistics information through sysfs.
+
+ 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
+ help
+ Thermal subsystem will issue a graceful shutdown when
+ critical temperatures are reached using orderly_poweroff(). In
+ case of failure of an orderly_poweroff(), the thermal emergency
+ poweroff kicks in after a delay has elapsed and shuts down the system.
+ This config is number of milliseconds to delay before emergency
+ poweroff kicks in. Similarly to the critical trip point,
+ the delay should be carefully profiled so as to give adequate
+ time for orderly_poweroff() to finish on regular execution.
+ If set to 0 emergency poweroff will not be supported.
+
+ In doubt, leave as 0.
+
+config THERMAL_HWMON
+ bool
+ prompt "Expose thermal sensors as hwmon device"
+ depends on HWMON=y || HWMON=THERMAL
+ default y
+ help
+ In case a sensor is registered with the thermal
+ framework, this option will also register it
+ as a hwmon. The sensor will then have the common
+ hwmon sysfs interface.
+
+ Say 'Y' here if you want all thermal sensors to
+ have hwmon sysfs interface too.
+
+config THERMAL_OF
+ bool
+ prompt "APIs to parse thermal data out of device tree"
+ depends on OF
+ default y
+ help
+ This options provides helpers to add the support to
+ read and parse thermal data definitions out of the
+ device tree blob.
+
+ Say 'Y' here if you need to build thermal infrastructure
+ based on device tree.
+
+choice
+ prompt "Default Thermal governor"
+ default THERMAL_DEFAULT_GOV_STEP_WISE
+ help
+ This option sets which thermal governor shall be loaded at
+ startup. If in doubt, select 'step_wise'.
+
+config THERMAL_DEFAULT_GOV_STEP_WISE
+ bool "step_wise"
+ select THERMAL_GOV_STEP_WISE
+ help
+ Use the step_wise governor as default. This throttles the
+ devices one step at a time.
+
+config THERMAL_DEFAULT_GOV_FAIR_SHARE
+ bool "fair_share"
+ select THERMAL_GOV_FAIR_SHARE
+ help
+ Use the fair_share governor as default. This throttles the
+ devices based on their 'contribution' to a zone. The
+ contribution should be provided through platform data.
+
+config THERMAL_DEFAULT_GOV_USER_SPACE
+ bool "user_space"
+ select THERMAL_GOV_USER_SPACE
+ help
+ The Userspace governor allows to get trip point crossed
+ notification from the kernel via uevents. It is recommended
+ to use the netlink interface instead which gives richer
+ information about the thermal framework events.
+
+config THERMAL_DEFAULT_GOV_POWER_ALLOCATOR
+ bool "power_allocator"
+ depends on THERMAL_GOV_POWER_ALLOCATOR
+ help
+ Select this if you want to control temperature based on
+ system and device power allocation. This governor can only
+ operate on cooling devices that implement the power API.
+
+config THERMAL_DEFAULT_GOV_BANG_BANG
+ bool "bang_bang"
+ depends on THERMAL_GOV_BANG_BANG
+ help
+ Use the bang_bang governor as default. This throttles the
+ devices one step at the time, taking into account the trip
+ point hysteresis.
+
+endchoice
+
+config THERMAL_GOV_FAIR_SHARE
+ bool "Fair-share thermal governor"
+ help
+ Enable this to manage platform thermals using fair-share governor.
+
+config THERMAL_GOV_STEP_WISE
+ bool "Step_wise thermal governor"
+ help
+ Enable this to manage platform thermals using a simple linear
+ governor.
+
+config THERMAL_GOV_BANG_BANG
+ bool "Bang Bang thermal governor"
+ default n
+ help
+ Enable this to manage platform thermals using bang bang governor.
+
+ Say 'Y' here if you want to use two point temperature regulation
+ used for fans without throttling. Some fan drivers depend on this
+ governor to be enabled (e.g. acerhdf).
+
+config THERMAL_GOV_USER_SPACE
+ bool "User_space thermal governor"
+ help
+ Enable this to let the user space manage the platform thermals.
+
+config THERMAL_GOV_POWER_ALLOCATOR
+ bool "Power allocator thermal governor"
+ depends on ENERGY_MODEL
+ help
+ Enable this to manage platform thermals by dynamically
+ allocating and limiting power to devices.
+
+config CPU_THERMAL
+ bool "Generic cpu cooling support"
+ depends on THERMAL_OF
+ help
+ Enable the CPU cooling features. If the system has no active
+ cooling device available, this option allows to use the CPU
+ as a cooling device.
+
+if CPU_THERMAL
+
+config CPU_FREQ_THERMAL
+ bool "CPU frequency cooling device"
+ depends on CPU_FREQ
+ default y
+ help
+ This implements the generic cpu cooling mechanism through frequency
+ reduction. An ACPI version of this already exists
+ (drivers/acpi/processor_thermal.c).
+ This will be useful for platforms using the generic thermal interface
+ and not the ACPI interface.
+
+config CPU_IDLE_THERMAL
+ bool "CPU idle cooling device"
+ depends on IDLE_INJECT
+ help
+ This implements the CPU cooling mechanism through
+ idle injection. This will throttle the CPU by injecting
+ idle cycle.
+endif
+
+config DEVFREQ_THERMAL
+ bool "Generic device cooling support"
+ depends on PM_DEVFREQ
+ depends on PM_OPP
+ help
+ This implements the generic devfreq cooling mechanism through
+ frequency reduction for devices using devfreq.
+
+ This will throttle the device by limiting the maximum allowed DVFS
+ frequency corresponding to the cooling level.
+
+ In order to use the power extensions of the cooling device,
+ devfreq should use the simple_ondemand governor.
+
+ 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
+ Enable this option to make a emul_temp sysfs node in thermal zone
+ directory to support temperature emulation. With emulation sysfs node,
+ user can manually input temperature and test the different trip
+ threshold behaviour for simulation purpose.
+
+ WARNING: Be careful while enabling this option on production systems,
+ because userland can easily disable the thermal policy by simply
+ flooding this sysfs node with low temperature values.
+
+config THERMAL_MMIO
+ tristate "Generic Thermal MMIO driver"
+ depends on OF
+ depends on HAS_IOMEM
+ help
+ This option enables the generic thermal MMIO driver that will use
+ memory-mapped reads to get the temperature. Any HW/System that
+ allows temperature reading by a single memory-mapped reading, be it
+ register or shared memory, is a potential candidate to work with this
+ driver.
+
+config HISI_THERMAL
+ tristate "Hisilicon thermal driver"
+ depends on ARCH_HISI || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ 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
+ CPUs when the passive trip is crossed.
+
+config IMX_THERMAL
+ tristate "Temperature sensor driver for Freescale i.MX SoCs"
+ depends on ARCH_MXC || COMPILE_TEST
+ depends on NVMEM || !NVMEM
+ depends on MFD_SYSCON
+ depends on OF
+ help
+ Support for Temperature Monitor (TEMPMON) found on Freescale i.MX SoCs.
+ It supports one critical trip point and one passive trip point. The
+ cpufreq is used as the cooling device to throttle CPUs when the
+ passive trip is crossed.
+
+config IMX_SC_THERMAL
+ tristate "Temperature sensor driver for NXP i.MX SoCs with System Controller"
+ depends on IMX_SCU
+ depends on OF
+ help
+ Support for Temperature Monitor (TEMPMON) found on NXP i.MX SoCs with
+ system controller inside, Linux kernel has to communicate with system
+ controller via MU (message unit) IPC to get temperature from thermal
+ sensor. It supports one critical trip point and one
+ passive trip point for each thermal sensor.
+
+config IMX8MM_THERMAL
+ tristate "Temperature sensor driver for Freescale i.MX8MM SoC"
+ depends on ARCH_MXC || COMPILE_TEST
+ depends on OF
+ help
+ Support for Thermal Monitoring Unit (TMU) found on Freescale i.MX8MM SoC.
+ It supports one critical trip point and one passive trip point. The
+ cpufreq is used as the cooling device to throttle CPUs when the passive
+ trip is crossed.
+
+config 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
+ help
+ If you say yes here you get thermal support for the Texas Instruments
+ K3 SoC family. The current chip supported is:
+ - AM654
+
+ This includes temperature reading functionality.
+
+config MAX77620_THERMAL
+ tristate "Temperature sensor driver for Maxim MAX77620 PMIC"
+ depends on MFD_MAX77620
+ depends on OF
+ help
+ Support for die junction temperature warning alarm for Maxim
+ Semiconductor PMIC MAX77620 device. Device generates two alarm
+ interrupts when PMIC die temperature cross the threshold of
+ 120 degC and 140 degC.
+
+config QORIQ_THERMAL
+ tristate "QorIQ Thermal Monitoring Unit"
+ depends on THERMAL_OF && HAS_IOMEM
+ depends on PPC_E500MC || SOC_LS1021A || ARCH_LAYERSCAPE || (ARCH_MXC && ARM64) || COMPILE_TEST
+ select REGMAP_MMIO
+ help
+ Support for Thermal Monitoring Unit (TMU) found on QorIQ platforms.
+ It supports one critical trip point and one passive trip point. The
+ 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
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ Enable this to plug the SPEAr thermal sensor driver into the Linux
+ thermal framework.
+
+config SUN8I_THERMAL
+ tristate "Allwinner sun8i thermal driver"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on NVMEM
+ depends on OF
+ depends on RESET_CONTROLLER
+ help
+ Support for the sun8i thermal sensor driver into the Linux thermal
+ framework.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sun8i-thermal.
+
+config ROCKCHIP_THERMAL
+ tristate "Rockchip thermal driver"
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ depends on RESET_CONTROLLER
+ depends on HAS_IOMEM
+ help
+ Rockchip thermal driver provides support for Temperature sensor
+ ADC (TS-ADC) found on Rockchip SoCs. It supports one critical
+ trip point. Cpufreq is used as the cooling device and will throttle
+ CPUs when the Temperature crosses the passive trip point.
+
+config KIRKWOOD_THERMAL
+ tristate "Temperature sensor on Marvell Kirkwood SoCs"
+ depends on MACH_KIRKWOOD || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ Support for the Kirkwood thermal sensor driver into the Linux thermal
+ framework. Only kirkwood 88F6282 and 88F6283 have this sensor.
+
+config DOVE_THERMAL
+ tristate "Temperature sensor on Marvell Dove SoCs"
+ depends on ARCH_DOVE || MACH_DOVE || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ Support for the Dove thermal sensor driver in the Linux thermal
+ framework.
+
+config DB8500_THERMAL
+ tristate "DB8500 thermal management"
+ depends on MFD_DB8500_PRCMU && OF
+ default y
+ help
+ Adds DB8500 thermal management implementation according to the thermal
+ management framework. A thermal zone with several trip points will be
+ created. Cooling devices can be bound to the trip points to cool this
+ thermal zone if trip points reached.
+
+config ARMADA_THERMAL
+ tristate "Marvell EBU Armada SoCs thermal management"
+ depends on ARCH_MVEBU || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ Enable this option if you want to have support for thermal management
+ controller present in Marvell EBU Armada SoCs (370,375,XP,38x,7K,8K).
+
+config DA9062_THERMAL
+ tristate "DA9062/DA9061 Dialog Semiconductor thermal driver"
+ depends on MFD_DA9062 || COMPILE_TEST
+ depends on OF
+ help
+ Enable this for the Dialog Semiconductor thermal sensor driver.
+ This will report PMIC junction over-temperature for one thermal trip
+ zone.
+ Compatible with the DA9062 and DA9061 PMICs.
+
+menu "Mediatek thermal drivers"
+depends on ARCH_MEDIATEK || COMPILE_TEST
+source "drivers/thermal/mediatek/Kconfig"
+endmenu
+
+config AMLOGIC_THERMAL
+ tristate "Amlogic Thermal Support"
+ default ARCH_MESON
+ depends on OF && ARCH_MESON
+ depends on MESON_SM
+ help
+ If you say yes here you get support for Amlogic Thermal
+ for G12 SoC Family.
+
+ This driver can also be built as a module. If so, the module will
+ be called amlogic_thermal.
+
+menu "Intel thermal drivers"
+depends on X86 || X86_INTEL_QUARK || COMPILE_TEST
+source "drivers/thermal/intel/Kconfig"
+endmenu
+
+menu "Broadcom thermal drivers"
+depends on ARCH_BCM || ARCH_BRCMSTB || ARCH_BCM2835 || ARCH_BCM_IPROC || \
+ COMPILE_TEST
+source "drivers/thermal/broadcom/Kconfig"
+endmenu
+
+menu "Texas Instruments thermal drivers"
+depends on ARCH_HAS_BANDGAP || COMPILE_TEST
+depends on HAS_IOMEM
+source "drivers/thermal/ti-soc-thermal/Kconfig"
+endmenu
+
+menu "Samsung thermal drivers"
+depends on ARCH_EXYNOS || COMPILE_TEST
+source "drivers/thermal/samsung/Kconfig"
+endmenu
+
+menu "STMicroelectronics thermal drivers"
+depends on (ARCH_STI || ARCH_STM32) && THERMAL_OF
+source "drivers/thermal/st/Kconfig"
+endmenu
+
+source "drivers/thermal/renesas/Kconfig"
+
+source "drivers/thermal/spacemit/Kconfig"
+
+source "drivers/thermal/tegra/Kconfig"
+
+config GENERIC_ADC_THERMAL
+ tristate "Generic ADC based thermal sensor"
+ depends on IIO
+ help
+ This enabled a thermal sysfs driver for the temperature sensor
+ which is connected to the General Purpose ADC. The ADC channel
+ is read via IIO framework and the channel information is provided
+ to this driver. This driver reports the temperature by reading ADC
+ channel and converts it to temperature based on lookup table.
+
+menu "Qualcomm thermal drivers"
+depends on (ARCH_QCOM && OF) || COMPILE_TEST
+source "drivers/thermal/qcom/Kconfig"
+endmenu
+
+config UNIPHIER_THERMAL
+ tristate "Socionext UniPhier thermal driver"
+ depends on ARCH_UNIPHIER || COMPILE_TEST
+ depends on THERMAL_OF && MFD_SYSCON
+ help
+ Enable this to plug in UniPhier on-chip PVT thermal driver into the
+ thermal framework. The driver supports CPU thermal zone temperature
+ reporting and a couple of trip points.
+
+config SPRD_THERMAL
+ tristate "Temperature sensor on Spreadtrum SoCs"
+ depends on ARCH_SPRD || COMPILE_TEST
+ help
+ Support for the Spreadtrum thermal sensor driver in the Linux thermal
+ framework.
+
+config KHADAS_MCU_FAN_THERMAL
+ tristate "Khadas MCU controller FAN cooling support"
+ depends on OF
+ depends on MFD_KHADAS_MCU
+ select MFD_CORE
+ select REGMAP
+ help
+ If you say yes here you get support for the FAN controlled
+ by the Microcontroller found on the Khadas VIM boards.
+
+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
new file mode 100644
index 000000000..3b249195c
--- /dev/null
+++ b/drivers/thermal/Makefile
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for sensor chip drivers.
+#
+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
+
+# governors
+CFLAGS_gov_power_allocator.o := -I$(src)
+thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += gov_fair_share.o
+thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG) += gov_bang_bang.o
+thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += gov_step_wise.o
+thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += gov_user_space.o
+thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += gov_power_allocator.o
+
+# cpufreq cooling
+thermal_sys-$(CONFIG_CPU_FREQ_THERMAL) += cpufreq_cooling.o
+thermal_sys-$(CONFIG_CPU_IDLE_THERMAL) += cpuidle_cooling.o
+
+# 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-y += renesas/
+obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o
+obj-y += samsung/
+obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o
+obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o
+obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o
+obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o
+obj-$(CONFIG_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
+obj-y += intel/
+obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
+obj-y += st/
+obj-y += qcom/
+obj-y += tegra/
+obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
+obj-y += mediatek/
+obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
+obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o
+obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o
+obj-y += spacemit/
+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 000000000..77b3a1af2
--- /dev/null
+++ b/drivers/thermal/airoha_thermal.c
@@ -0,0 +1,733 @@
+// 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)
+
+#define AN7583_MUX_SENSOR 0x2a0
+#define AN7583_LOAD_ADJ GENMASK(3, 2)
+#define AN7583_MUX_TADC 0x2e4
+#define AN7583_MUX_TADC_MASK GENMASK(3, 1)
+#define AN7583_DOUT_TADC 0x2f0
+
+/* 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
+
+#define AN7583_SCU_THERMAL_PROTECT_KEY 0x80
+#define AN7583_NUM_SENSOR 3
+
+#define AIROHA_THERMAL_NO_MUX_SENSOR -1
+
+/* 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
+
+/*
+ * AN7583 supports all these ADC mux but the original driver
+ * always checked temp with the AN7583_BGP_TEMP_SENSOR.
+ * Assume using the other sensor temperature is invalid and
+ * always read from AN7583_BGP_TEMP_SENSOR.
+ *
+ * On top of this it's defined that AN7583 supports 3
+ * sensor: AN7583_BGP_TEMP_SENSOR, AN7583_GBE_TEMP_SENSOR,
+ * AN7583_CPU_TEMP_SENSOR.
+ *
+ * Provide the ADC mux for reference.
+ */
+enum an7583_thermal_adc_mux {
+ AN7583_BGP_TEMP_SENSOR,
+ AN7583_PAD_AVS,
+ AN7583_CORE_POWER,
+ AN7583_AVSDAC_OUT,
+ AN7583_VCM,
+ AN7583_GBE_TEMP_SENSOR,
+ AN7583_CPU_TEMP_SENSOR,
+
+ AN7583_ADC_MUX_MAX,
+};
+
+enum an7583_thermal_diode_mux {
+ AN7583_D0_TADC,
+ AN7583_ZERO_TADC,
+ AN7583_D1_TADC,
+};
+
+enum airoha_thermal_chip_scu_field {
+ AIROHA_THERMAL_DOUT_TADC,
+ AIROHA_THERMAL_MUX_SENSOR,
+ AIROHA_THERMAL_MUX_TADC,
+
+ /* keep last */
+ AIROHA_THERMAL_FIELD_MAX,
+};
+
+struct airoha_thermal_priv {
+ struct regmap *map;
+ struct regmap *chip_scu;
+ struct regmap_field *chip_scu_fields[AIROHA_THERMAL_FIELD_MAX];
+ struct resource scu_adc_res;
+
+ u32 pllrg_protect;
+ int current_adc;
+
+ struct thermal_zone_device *tz;
+ int init_temp;
+ int default_slope;
+ int default_offset;
+};
+
+struct airoha_thermal_soc_data {
+ u32 pllrg_protect;
+
+ const struct thermal_zone_device_ops *thdev_ops;
+ int (*probe)(struct platform_device *pdev,
+ struct airoha_thermal_priv *priv);
+ int (*post_probe)(struct platform_device *pdev);
+};
+
+static const unsigned int an7583_thermal_coeff[AN7583_ADC_MUX_MAX] = {
+ [AN7583_BGP_TEMP_SENSOR] = 973,
+ [AN7583_GBE_TEMP_SENSOR] = 995,
+ [AN7583_CPU_TEMP_SENSOR] = 1035,
+};
+
+static const unsigned int an7583_thermal_slope[AN7583_ADC_MUX_MAX] = {
+ [AN7583_BGP_TEMP_SENSOR] = 7440,
+ [AN7583_GBE_TEMP_SENSOR] = 7620,
+ [AN7583_CPU_TEMP_SENSOR] = 8390,
+};
+
+static const unsigned int an7583_thermal_offset[AN7583_ADC_MUX_MAX] = {
+ [AN7583_BGP_TEMP_SENSOR] = 294,
+ [AN7583_GBE_TEMP_SENSOR] = 298,
+ [AN7583_CPU_TEMP_SENSOR] = 344,
+};
+
+static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv)
+{
+ u32 val;
+
+ regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_DOUT_TADC],
+ &val);
+ return val;
+}
+
+static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv,
+ int tdac_idx, int sensor_idx)
+{
+ u32 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,
+ priv->pllrg_protect);
+
+ /*
+ * Configure Thermal Sensor mux to sensor_idx.
+ * (if not supported, sensor_idx is AIROHA_THERMAL_NO_MUX_SENSOR)
+ */
+ if (sensor_idx != AIROHA_THERMAL_NO_MUX_SENSOR)
+ regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR],
+ sensor_idx);
+
+ /* Configure Thermal ADC mux to tdac_idx */
+ if (priv->current_adc != tdac_idx) {
+ regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC],
+ tdac_idx);
+ priv->current_adc = tdac_idx;
+ }
+
+ /* Restore PLLRG value on exit */
+ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg);
+
+ /* Sleep 10 ms for Thermal ADC to enable */
+ usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC);
+}
+
+static int en7581_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 en7581_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 */
+ regmap_write(priv->map, EN7581_TEMPOFFSETH,
+ TEMP_TO_RAW(priv, high) >> 4);
+
+ enable_monitor = true;
+ }
+
+ if (low != -INT_MAX) {
+ /* Validate low and clamp it to a supported value */
+ low = clamp_t(int, low, 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 */
+ regmap_write(priv->map, EN7581_TEMPOFFSETL,
+ TEMP_TO_RAW(priv, low) >> 4);
+
+ enable_monitor = true;
+ }
+
+ /* Enable sensor 0 monitor after trip are set */
+ if (enable_monitor)
+ regmap_write(priv->map, EN7581_TEMPMONCTL0, EN7581_SENSE0_EN);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops en7581_thdev_ops = {
+ .get_temp = en7581_thermal_get_temp,
+ .set_trips = en7581_thermal_set_trips,
+};
+
+static irqreturn_t en7581_thermal_irq(int irq, void *data)
+{
+ struct airoha_thermal_priv *priv = data;
+ enum thermal_notify_event event;
+ bool update = false;
+ u32 status = 0;
+
+ regmap_read(priv->map, EN7581_TEMPMONINTSTS, &status);
+ 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 */
+ regmap_write(priv->map, EN7581_TEMPMONINTSTS, status);
+
+ if (update)
+ thermal_zone_device_update(priv->tz, event);
+
+ return IRQ_HANDLED;
+}
+
+static void en7581_thermal_setup_adc_val(struct device *dev,
+ struct airoha_thermal_priv *priv)
+{
+ u32 efuse_calib_info = 0;
+ u32 cpu_sensor = 0;
+
+ /* Setup Thermal Sensor to ADC mode and setup the mux to DIODE1 */
+ airoha_set_thermal_mux(priv, EN7581_SCU_THERMAL_MUX_DIODE1,
+ AIROHA_THERMAL_NO_MUX_SENSOR);
+
+ regmap_read(priv->map, EN7581_EFUSE_TEMP_OFFSET_REG, &efuse_calib_info);
+ 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 */
+ regmap_read(priv->map, EN7581_EFUSE_TEMP_CPU_SENSOR_REG, &cpu_sensor);
+ 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 en7581_thermal_setup_monitor(struct airoha_thermal_priv *priv)
+{
+ /* Set measure mode */
+ regmap_write(priv->map, EN7581_TEMPMSRCTL0,
+ FIELD_PREP(EN7581_MSRCTL0, EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4));
+
+ /*
+ * 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.
+ */
+ regmap_write(priv->map, EN7581_TEMPADCVALIDADDR,
+ priv->scu_adc_res.start + EN7581_DOUT_TADC);
+
+ /*
+ * Configure valid bit on a fake value of bit 16. The ADC outputs
+ * max of 2 bytes for voltage.
+ */
+ regmap_write(priv->map, EN7581_TEMPADCVALIDMASK,
+ FIELD_PREP(EN7581_ADV_RD_VALID_POS, 16));
+
+ /*
+ * 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.
+ */
+ regmap_write(priv->map, EN7581_TEMPADCVOLTAGESHIFT,
+ FIELD_PREP(EN7581_ADC_VOLTAGE_SHIFT, 4));
+
+ /* BUS clock is 300MHz counting unit is 3 * 68.64 * 256 = 52.715us */
+ regmap_write(priv->map, EN7581_TEMPMONCTL1,
+ FIELD_PREP(EN7581_PERIOD_UNIT, 3));
+
+ /*
+ * filt interval is 1 * 52.715us = 52.715us,
+ * sen interval is 379 * 52.715us = 19.97ms
+ */
+ regmap_write(priv->map, EN7581_TEMPMONCTL2,
+ FIELD_PREP(EN7581_FILT_INTERVAL, 1) |
+ FIELD_PREP(EN7581_SEN_INTERVAL, 379));
+
+ /* AHB poll is set to 146 * 68.64 = 10.02us */
+ regmap_write(priv->map, EN7581_TEMPAHBPOLL,
+ FIELD_PREP(EN7581_ADC_POLL_INTVL, 146));
+}
+
+static const struct regmap_config en7581_thermal_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+};
+
+static const struct reg_field en7581_chip_scu_fields[AIROHA_THERMAL_FIELD_MAX] = {
+ [AIROHA_THERMAL_DOUT_TADC] = REG_FIELD(EN7581_DOUT_TADC, 0, 15),
+ [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(EN7581_PWD_TADC, 1, 3),
+};
+
+static int en7581_thermal_probe(struct platform_device *pdev,
+ struct airoha_thermal_priv *priv)
+{
+ struct device_node *chip_scu_np;
+ struct device *dev = &pdev->dev;
+ void __iomem *base;
+ int i, irq, ret;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->map = devm_regmap_init_mmio(dev, base,
+ &en7581_thermal_regmap_config);
+ if (IS_ERR(priv->map))
+ return PTR_ERR(priv->map);
+
+ 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);
+
+ for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) {
+ struct regmap_field *field;
+
+ /* Skip registering MUX_SENSOR field as not supported */
+ if (i == AIROHA_THERMAL_MUX_SENSOR)
+ continue;
+
+ field = devm_regmap_field_alloc(dev, priv->chip_scu,
+ en7581_chip_scu_fields[i]);
+ if (IS_ERR(field)) {
+ of_node_put(chip_scu_np);
+ return PTR_ERR(field);
+ }
+
+ priv->chip_scu_fields[i] = field;
+ }
+
+ 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,
+ en7581_thermal_irq, IRQF_ONESHOT,
+ pdev->name, priv);
+ if (ret)
+ return ret;
+
+ en7581_thermal_setup_monitor(priv);
+ en7581_thermal_setup_adc_val(dev, priv);
+
+ return 0;
+}
+
+static int en7581_thermal_post_probe(struct platform_device *pdev)
+{
+ struct airoha_thermal_priv *priv = platform_get_drvdata(pdev);
+
+ /* Enable LOW and HIGH interrupt (if supported) */
+ regmap_write(priv->map, EN7581_TEMPMONINT,
+ EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0);
+
+ return 0;
+}
+
+static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+ int sensor_idx;
+ int delta_diode, delta_gain;
+ int coeff, slope, offset;
+
+ int diode_zero, diode_d0, diode_d1;
+
+ /* Always read sensor AN7583_BGP_TEMP_SENSOR */
+ sensor_idx = AN7583_BGP_TEMP_SENSOR;
+
+ coeff = an7583_thermal_coeff[sensor_idx];
+ slope = an7583_thermal_slope[sensor_idx];
+ offset = an7583_thermal_offset[sensor_idx];
+
+ airoha_set_thermal_mux(priv, AN7583_ZERO_TADC, sensor_idx);
+ diode_zero = airoha_get_thermal_ADC(priv);
+ airoha_set_thermal_mux(priv, AN7583_D0_TADC, sensor_idx);
+ diode_d0 = airoha_get_thermal_ADC(priv);
+ airoha_set_thermal_mux(priv, AN7583_D1_TADC, sensor_idx);
+ diode_d1 = airoha_get_thermal_ADC(priv);
+
+ delta_diode = diode_d1 - diode_d0;
+ delta_gain = (delta_diode * coeff) / 100 + (diode_zero - diode_d1);
+ if (!delta_gain)
+ return -EINVAL;
+
+ *temp = (slope * delta_diode * 10) / delta_gain - offset * 10;
+ *temp *= 100;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops an7583_tz_ops = {
+ .get_temp = an7583_thermal_get_temp,
+};
+
+static const struct reg_field an7583_chip_scu_fields[AIROHA_THERMAL_FIELD_MAX] = {
+ [AIROHA_THERMAL_DOUT_TADC] = REG_FIELD(AN7583_DOUT_TADC, 0, 31),
+ [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(AN7583_MUX_TADC, 1, 3),
+ [AIROHA_THERMAL_MUX_SENSOR] = REG_FIELD(AN7583_MUX_SENSOR, 2, 3),
+};
+
+static int an7583_thermal_probe(struct platform_device *pdev,
+ struct airoha_thermal_priv *priv)
+{
+ struct device *dev = &pdev->dev;
+ int i;
+
+ priv->chip_scu = device_node_to_regmap(dev->of_node);
+ if (IS_ERR(priv->chip_scu))
+ return PTR_ERR(priv->chip_scu);
+
+ for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) {
+ struct regmap_field *field;
+
+ field = devm_regmap_field_alloc(dev, priv->chip_scu,
+ an7583_chip_scu_fields[i]);
+ if (IS_ERR(field))
+ return PTR_ERR(field);
+
+ priv->chip_scu_fields[i] = field;
+ }
+
+ return 0;
+}
+
+static int airoha_thermal_probe(struct platform_device *pdev)
+{
+ const struct airoha_thermal_soc_data *soc_data;
+ struct airoha_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ soc_data = device_get_match_data(dev);
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pllrg_protect = soc_data->pllrg_protect;
+ priv->current_adc = -1;
+
+ if (!soc_data->probe)
+ return -EINVAL;
+
+ ret = soc_data->probe(pdev, priv);
+ if (ret)
+ return ret;
+
+ /* register of thermal sensor and get info from DT */
+ priv->tz = devm_thermal_of_zone_register(dev, 0, priv,
+ soc_data->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);
+
+ return soc_data->post_probe ? soc_data->post_probe(pdev) : 0;
+}
+
+static const struct airoha_thermal_soc_data en7581_data = {
+ .pllrg_protect = EN7581_SCU_THERMAL_PROTECT_KEY,
+ .thdev_ops = &en7581_thdev_ops,
+ .probe = &en7581_thermal_probe,
+ .post_probe = &en7581_thermal_post_probe,
+};
+
+static const struct airoha_thermal_soc_data an7583_data = {
+ .pllrg_protect = AN7583_SCU_THERMAL_PROTECT_KEY,
+ .thdev_ops = &an7583_tz_ops,
+ .probe = &an7583_thermal_probe,
+};
+
+static const struct of_device_id airoha_thermal_match[] = {
+ { .compatible = "airoha,en7581-thermal", .data = &en7581_data },
+ { .compatible = "airoha,an7583-chip-scu", .data = &an7583_data },
+ {},
+};
+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
new file mode 100644
index 000000000..a0b530624
--- /dev/null
+++ b/drivers/thermal/amlogic_thermal.c
@@ -0,0 +1,395 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Amlogic Thermal Sensor Driver
+ *
+ * Copyright (C) 2017 Huan Biao <huan.biao@amlogic.com>
+ * Copyright (C) 2019 Guillaume La Roque <glaroque@baylibre.com>
+ *
+ * Register value to celsius temperature formulas:
+ * Read_Val m * U
+ * U = ---------, uptat = ---------
+ * 2^16 1 + n * U
+ *
+ * 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
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include <linux/firmware/meson/meson_sm.h>
+
+#include "thermal_hwmon.h"
+
+#define TSENSOR_CFG_REG1 0x4
+ #define TSENSOR_CFG_REG1_RSET_VBG BIT(12)
+ #define TSENSOR_CFG_REG1_RSET_ADC BIT(11)
+ #define TSENSOR_CFG_REG1_VCM_EN BIT(10)
+ #define TSENSOR_CFG_REG1_VBG_EN BIT(9)
+ #define TSENSOR_CFG_REG1_OUT_CTL BIT(6)
+ #define TSENSOR_CFG_REG1_FILTER_EN BIT(5)
+ #define TSENSOR_CFG_REG1_DEM_EN BIT(3)
+ #define TSENSOR_CFG_REG1_CH_SEL GENMASK(1, 0)
+ #define TSENSOR_CFG_REG1_ENABLE \
+ (TSENSOR_CFG_REG1_FILTER_EN | \
+ TSENSOR_CFG_REG1_VCM_EN | \
+ TSENSOR_CFG_REG1_VBG_EN | \
+ TSENSOR_CFG_REG1_DEM_EN | \
+ TSENSOR_CFG_REG1_CH_SEL)
+
+#define TSENSOR_STAT0 0x40
+
+#define TSENSOR_STAT9 0x64
+
+#define TSENSOR_READ_TEMP_MASK GENMASK(15, 0)
+#define TSENSOR_TEMP_MASK GENMASK(11, 0)
+
+#define TSENSOR_TRIM_SIGN_MASK BIT(15)
+#define TSENSOR_TRIM_TEMP_MASK GENMASK(14, 0)
+#define TSENSOR_TRIM_VERSION_MASK GENMASK(31, 24)
+
+#define TSENSOR_TRIM_VERSION(_version) \
+ FIELD_GET(TSENSOR_TRIM_VERSION_MASK, _version)
+
+#define TSENSOR_TRIM_CALIB_VALID_MASK (GENMASK(3, 2) | BIT(7))
+
+#define TSENSOR_CALIB_OFFSET 1
+#define TSENSOR_CALIB_SHIFT 4
+
+/**
+ * struct amlogic_thermal_soc_calib_data
+ * @A: calibration parameters
+ * @B: calibration parameters
+ * @m: calibration parameters
+ * @n: calibration parameters
+ *
+ * This structure is required for configuration of amlogic thermal driver.
+ */
+struct amlogic_thermal_soc_calib_data {
+ int A;
+ int B;
+ int m;
+ int n;
+};
+
+/**
+ * struct amlogic_thermal_data
+ * @u_efuse_off: register offset to read fused calibration value
+ * @calibration_parameters: calibration parameters structure pointer
+ * @regmap_config: regmap config for the device
+ * @use_sm: read data from secure monitor instead of efuse
+ * This structure is required for configuration of amlogic thermal driver.
+ */
+struct amlogic_thermal_data {
+ int u_efuse_off;
+ const struct amlogic_thermal_soc_calib_data *calibration_parameters;
+ const struct regmap_config *regmap_config;
+ bool use_sm;
+};
+
+struct amlogic_thermal {
+ struct platform_device *pdev;
+ const struct amlogic_thermal_data *data;
+ struct regmap *regmap;
+ struct regmap *sec_ao_map;
+ struct clk *clk;
+ struct thermal_zone_device *tzd;
+ u32 trim_info;
+ struct meson_sm_firmware *sm_fw;
+ u32 tsensor_id;
+};
+
+/*
+ * Calculate a temperature value from a temperature code.
+ * The unit of the temperature is degree milliCelsius.
+ */
+static int amlogic_thermal_code_to_millicelsius(struct amlogic_thermal *pdata,
+ int temp_code)
+{
+ const struct amlogic_thermal_soc_calib_data *param =
+ pdata->data->calibration_parameters;
+ int temp;
+ s64 factor, uptat, uefuse;
+
+ uefuse = pdata->trim_info & TSENSOR_TRIM_SIGN_MASK ?
+ ~(pdata->trim_info & TSENSOR_TRIM_TEMP_MASK) + 1 :
+ (pdata->trim_info & TSENSOR_TRIM_TEMP_MASK);
+
+ 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);
+
+ temp = (uptat + uefuse) * param->A;
+ temp = div_s64(temp, BIT(16));
+ temp = (temp - param->B) * 100;
+
+ return temp;
+}
+
+static int amlogic_thermal_enable(struct amlogic_thermal *data)
+{
+ int ret;
+
+ ret = clk_prepare_enable(data->clk);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(data->regmap, TSENSOR_CFG_REG1,
+ TSENSOR_CFG_REG1_ENABLE, TSENSOR_CFG_REG1_ENABLE);
+
+ return 0;
+}
+
+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);
+}
+
+static int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ unsigned int tval;
+ struct amlogic_thermal *pdata = thermal_zone_device_priv(tz);
+
+ if (!pdata)
+ return -EINVAL;
+
+ regmap_read(pdata->regmap, TSENSOR_STAT0, &tval);
+ *temp =
+ amlogic_thermal_code_to_millicelsius(pdata,
+ tval & TSENSOR_READ_TEMP_MASK);
+
+ return 0;
+}
+
+static int amlogic_thermal_probe_sm(struct platform_device *pdev,
+ struct amlogic_thermal *pdata)
+{
+ struct device *dev = &pdev->dev;
+ struct of_phandle_args ph_args;
+ int ret;
+
+ ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+ "amlogic,secure-monitor",
+ 1, 0, &ph_args);
+ if (ret)
+ return ret;
+
+ if (!ph_args.np) {
+ dev_err(dev, "Failed to parse secure monitor phandle\n");
+ return -ENODEV;
+ }
+
+ pdata->sm_fw = meson_sm_get(ph_args.np);
+ of_node_put(ph_args.np);
+ if (!pdata->sm_fw) {
+ dev_err(dev, "Failed to get secure monitor firmware\n");
+ return -EPROBE_DEFER;
+ }
+
+ pdata->tsensor_id = ph_args.args[0];
+
+ return meson_sm_get_thermal_calib(pdata->sm_fw,
+ &pdata->trim_info,
+ pdata->tsensor_id);
+}
+
+static int amlogic_thermal_probe_syscon(struct platform_device *pdev,
+ struct amlogic_thermal *pdata)
+{
+ struct device *dev = &pdev->dev;
+ int ver;
+
+ pdata->sec_ao_map =
+ syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "amlogic,ao-secure");
+ if (IS_ERR(pdata->sec_ao_map)) {
+ dev_err(dev, "syscon regmap lookup failed.\n");
+ return PTR_ERR(pdata->sec_ao_map);
+ }
+
+ regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off,
+ &pdata->trim_info);
+
+ ver = TSENSOR_TRIM_VERSION(pdata->trim_info);
+
+ if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) {
+ dev_err(&pdata->pdev->dev,
+ "tsensor thermal calibration not supported: 0x%x!\n",
+ ver);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops amlogic_thermal_ops = {
+ .get_temp = amlogic_thermal_get_temp,
+};
+
+static const struct regmap_config amlogic_thermal_regmap_config_g12a = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = TSENSOR_STAT9,
+};
+
+static const struct amlogic_thermal_soc_calib_data amlogic_thermal_g12a = {
+ .A = 9411,
+ .B = 3159,
+ .m = 424,
+ .n = 324,
+};
+
+static const struct amlogic_thermal_data amlogic_thermal_g12a_cpu_param = {
+ .u_efuse_off = 0x128,
+ .calibration_parameters = &amlogic_thermal_g12a,
+ .regmap_config = &amlogic_thermal_regmap_config_g12a,
+};
+
+static const struct amlogic_thermal_data amlogic_thermal_g12a_ddr_param = {
+ .u_efuse_off = 0xf0,
+ .calibration_parameters = &amlogic_thermal_g12a,
+ .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 amlogic_thermal_data amlogic_thermal_t7_param = {
+ .use_sm = true,
+ .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",
+ .data = &amlogic_thermal_g12a_ddr_param,
+ },
+ {
+ .compatible = "amlogic,g12a-cpu-thermal",
+ .data = &amlogic_thermal_g12a_cpu_param,
+ },
+ {
+ .compatible = "amlogic,a1-cpu-thermal",
+ .data = &amlogic_thermal_a1_cpu_param,
+ },
+ {
+ .compatible = "amlogic,t7-thermal",
+ .data = &amlogic_thermal_t7_param,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_amlogic_thermal_match);
+
+static int amlogic_thermal_probe(struct platform_device *pdev)
+{
+ struct amlogic_thermal *pdata;
+ struct device *dev = &pdev->dev;
+ void __iomem *base;
+ int ret;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ pdata->data = of_device_get_match_data(dev);
+ pdata->pdev = pdev;
+ platform_set_drvdata(pdev, pdata);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ pdata->regmap = devm_regmap_init_mmio(dev, base,
+ pdata->data->regmap_config);
+ if (IS_ERR(pdata->regmap))
+ return PTR_ERR(pdata->regmap);
+
+ pdata->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(pdata->clk))
+ return dev_err_probe(dev, PTR_ERR(pdata->clk), "failed to get clock\n");
+
+ if (pdata->data->use_sm)
+ ret = amlogic_thermal_probe_sm(pdev, pdata);
+ else
+ ret = amlogic_thermal_probe_syscon(pdev, pdata);
+ if (ret)
+ return ret;
+
+ pdata->tzd = devm_thermal_of_zone_register(&pdev->dev,
+ 0,
+ pdata,
+ &amlogic_thermal_ops);
+ if (IS_ERR(pdata->tzd)) {
+ ret = PTR_ERR(pdata->tzd);
+ dev_err(dev, "Failed to register tsensor: %d\n", ret);
+ return ret;
+ }
+
+ devm_thermal_add_hwmon_sysfs(&pdev->dev, pdata->tzd);
+
+ ret = amlogic_thermal_enable(pdata);
+
+ return ret;
+}
+
+static void amlogic_thermal_remove(struct platform_device *pdev)
+{
+ struct amlogic_thermal *data = platform_get_drvdata(pdev);
+
+ amlogic_thermal_disable(data);
+}
+
+static int amlogic_thermal_suspend(struct device *dev)
+{
+ struct amlogic_thermal *data = dev_get_drvdata(dev);
+
+ amlogic_thermal_disable(data);
+
+ return 0;
+}
+
+static int amlogic_thermal_resume(struct device *dev)
+{
+ struct amlogic_thermal *data = dev_get_drvdata(dev);
+
+ return amlogic_thermal_enable(data);
+}
+
+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 = pm_ptr(&amlogic_thermal_pm_ops),
+ .of_match_table = of_amlogic_thermal_match,
+ },
+ .probe = amlogic_thermal_probe,
+ .remove = amlogic_thermal_remove,
+};
+
+module_platform_driver(amlogic_thermal_driver);
+
+MODULE_AUTHOR("Guillaume La Roque <glaroque@baylibre.com>");
+MODULE_DESCRIPTION("Amlogic thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c
new file mode 100644
index 000000000..ff63b4f2b
--- /dev/null
+++ b/drivers/thermal/armada_thermal.c
@@ -0,0 +1,969 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Marvell EBU Armada SoCs thermal sensor driver
+ *
+ * Copyright (C) 2013 Marvell
+ */
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/thermal.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/interrupt.h>
+
+/* Thermal Manager Control and Status Register */
+#define PMU_TDC0_SW_RST_MASK BIT(1)
+#define PMU_TM_DISABLE_MASK BIT(0)
+#define PMU_TDC0_REF_CAL_CNT_MASK GENMASK(19, 11)
+#define PMU_TDC0_OTF_CAL_MASK BIT(30)
+#define PMU_TDC0_START_CAL_MASK BIT(25)
+
+#define A375_UNIT_CONTROL_MASK GENMASK(29, 27)
+#define A375_READOUT_INVERT BIT(15)
+#define A375_HW_RESETn BIT(8)
+
+/* Errata fields */
+#define CONTROL0_TSEN_TC_TRIM_MASK GENMASK(2, 0)
+#define CONTROL0_TSEN_TC_TRIM_VAL 0x3
+
+#define CONTROL0_TSEN_START BIT(0)
+#define CONTROL0_TSEN_RESET BIT(1)
+#define CONTROL0_TSEN_ENABLE BIT(2)
+#define CONTROL0_TSEN_AVG_BYPASS BIT(6)
+#define CONTROL0_TSEN_CHAN_MASK GENMASK(16, 13)
+#define CONTROL0_TSEN_OSR_MASK GENMASK(25, 24)
+#define CONTROL0_TSEN_OSR_MAX FIELD_MAX(CONTROL0_TSEN_OSR_MASK)
+#define CONTROL0_TSEN_MODE_MASK GENMASK(31, 30)
+#define CONTROL0_TSEN_MODE_EXTERNAL 0x2
+
+#define CONTROL1_TSEN_AVG_MASK GENMASK(2, 0)
+#define CONTROL1_EXT_TSEN_SW_RESET BIT(7)
+#define CONTROL1_EXT_TSEN_HW_RESETn BIT(8)
+#define CONTROL1_TSEN_INT_EN BIT(25)
+#define CONTROL1_TSEN_SELECT_MASK GENMASK(22, 21)
+
+#define STATUS_POLL_PERIOD_US 1000
+#define STATUS_POLL_TIMEOUT_US 100000
+#define OVERHEAT_INT_POLL_DELAY_MS 1000
+
+struct armada_thermal_data;
+
+/* Marvell EBU Thermal Sensor Dev Structure */
+struct armada_thermal_priv {
+ struct device *dev;
+ struct regmap *syscon;
+ char zone_name[THERMAL_NAME_LENGTH];
+ /* serialize temperature reads/updates */
+ struct mutex update_lock;
+ struct armada_thermal_data *data;
+ struct thermal_zone_device *overheat_sensor;
+ int interrupt_source;
+ int current_channel;
+ long current_threshold;
+ long current_hysteresis;
+};
+
+struct armada_thermal_data {
+ /* Initialize the thermal IC */
+ void (*init)(struct platform_device *pdev,
+ struct armada_thermal_priv *priv);
+
+ /* Formula coeficients: temp = (b - m * reg) / div */
+ s64 coef_b;
+ s64 coef_m;
+ u32 coef_div;
+ bool inverted;
+ bool signed_sample;
+
+ /* Register shift and mask to access the sensor temperature */
+ unsigned int temp_shift;
+ unsigned int temp_mask;
+ unsigned int thresh_shift;
+ unsigned int hyst_shift;
+ unsigned int hyst_mask;
+ u32 is_valid_bit;
+
+ /* Syscon access */
+ unsigned int syscon_control0_off;
+ unsigned int syscon_control1_off;
+ unsigned int syscon_status_off;
+ unsigned int dfx_irq_cause_off;
+ unsigned int dfx_irq_mask_off;
+ unsigned int dfx_overheat_irq;
+ unsigned int dfx_server_irq_mask_off;
+ unsigned int dfx_server_irq_en;
+
+ /* One sensor is in the thermal IC, the others are in the CPUs if any */
+ unsigned int cpu_nr;
+};
+
+struct armada_drvdata {
+ enum drvtype {
+ LEGACY,
+ SYSCON
+ } type;
+ union {
+ struct armada_thermal_priv *priv;
+ struct thermal_zone_device *tz;
+ } data;
+};
+
+/*
+ * struct armada_thermal_sensor - hold the information of one thermal sensor
+ * @thermal: pointer to the local private structure
+ * @tzd: pointer to the thermal zone device
+ * @id: identifier of the thermal sensor
+ */
+struct armada_thermal_sensor {
+ struct armada_thermal_priv *priv;
+ int id;
+};
+
+static void armadaxp_init(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ u32 reg;
+
+ regmap_read(priv->syscon, data->syscon_control1_off, &reg);
+ FIELD_MODIFY(PMU_TDC0_OTF_CAL_MASK, &reg, 1);
+
+ /* Reference calibration value */
+ FIELD_MODIFY(PMU_TDC0_REF_CAL_CNT_MASK, &reg, 0xf1);
+
+ /* Reset the sensor */
+ FIELD_MODIFY(PMU_TDC0_SW_RST_MASK, &reg, 1);
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+
+ FIELD_MODIFY(PMU_TDC0_SW_RST_MASK, &reg, 0);
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+
+ /* Enable the sensor */
+ regmap_read(priv->syscon, data->syscon_status_off, &reg);
+ FIELD_MODIFY(PMU_TM_DISABLE_MASK, &reg, 0);
+ regmap_write(priv->syscon, data->syscon_status_off, reg);
+}
+
+static void armada370_init(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ u32 reg;
+
+ regmap_read(priv->syscon, data->syscon_control1_off, &reg);
+ FIELD_MODIFY(PMU_TDC0_OTF_CAL_MASK, &reg, 1);
+
+ /* Reference calibration value */
+ FIELD_MODIFY(PMU_TDC0_REF_CAL_CNT_MASK, &reg, 0xf1);
+
+ /* Reset the sensor */
+ FIELD_MODIFY(PMU_TDC0_START_CAL_MASK, &reg, 0);
+
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+
+ msleep(10);
+}
+
+static void armada375_init(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ u32 reg;
+
+ regmap_read(priv->syscon, data->syscon_control1_off, &reg);
+ FIELD_MODIFY(A375_UNIT_CONTROL_MASK, &reg, 0);
+ FIELD_MODIFY(A375_READOUT_INVERT, &reg, 0);
+ FIELD_MODIFY(A375_HW_RESETn, &reg, 0);
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+
+ msleep(20);
+
+ FIELD_MODIFY(A375_HW_RESETn, &reg, 1);
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+
+ msleep(50);
+}
+
+static int armada_wait_sensor_validity(struct armada_thermal_priv *priv)
+{
+ u32 reg;
+
+ return regmap_read_poll_timeout(priv->syscon,
+ priv->data->syscon_status_off, reg,
+ reg & priv->data->is_valid_bit,
+ STATUS_POLL_PERIOD_US,
+ STATUS_POLL_TIMEOUT_US);
+}
+
+static void armada380_init(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ u32 reg;
+
+ /* Disable the HW/SW reset */
+ regmap_read(priv->syscon, data->syscon_control1_off, &reg);
+ FIELD_MODIFY(CONTROL1_EXT_TSEN_HW_RESETn, &reg, 1);
+ FIELD_MODIFY(CONTROL1_EXT_TSEN_SW_RESET, &reg, 0);
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+
+ /* Set Tsen Tc Trim to correct default value (errata #132698) */
+ regmap_read(priv->syscon, data->syscon_control0_off, &reg);
+ FIELD_MODIFY(CONTROL0_TSEN_TC_TRIM_MASK, &reg, CONTROL0_TSEN_TC_TRIM_VAL);
+ regmap_write(priv->syscon, data->syscon_control0_off, reg);
+}
+
+static void armada_ap80x_init(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ u32 reg;
+
+ regmap_read(priv->syscon, data->syscon_control0_off, &reg);
+ FIELD_MODIFY(CONTROL0_TSEN_RESET, &reg, 0);
+ FIELD_MODIFY(CONTROL0_TSEN_START, &reg, 1);
+ FIELD_MODIFY(CONTROL0_TSEN_ENABLE, &reg, 1);
+
+ /* Sample every ~2ms */
+ FIELD_MODIFY(CONTROL0_TSEN_OSR_MASK, &reg, CONTROL0_TSEN_OSR_MAX);
+
+ /* Enable average (2 samples by default) */
+ FIELD_MODIFY(CONTROL0_TSEN_AVG_BYPASS, &reg, 0);
+
+ regmap_write(priv->syscon, data->syscon_control0_off, reg);
+}
+
+static void armada_cp110_init(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ u32 reg;
+
+ armada380_init(pdev, priv);
+
+ /* Sample every ~2ms */
+ regmap_read(priv->syscon, data->syscon_control0_off, &reg);
+ FIELD_MODIFY(CONTROL0_TSEN_OSR_MASK, &reg, CONTROL0_TSEN_OSR_MAX);
+ regmap_write(priv->syscon, data->syscon_control0_off, reg);
+
+ /* Average the output value over 2^1 = 2 samples */
+ regmap_read(priv->syscon, data->syscon_control1_off, &reg);
+ FIELD_MODIFY(CONTROL1_TSEN_AVG_MASK, &reg, 1);
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+}
+
+static bool armada_is_valid(struct armada_thermal_priv *priv)
+{
+ u32 reg;
+
+ if (!priv->data->is_valid_bit)
+ return true;
+
+ regmap_read(priv->syscon, priv->data->syscon_status_off, &reg);
+
+ return reg & priv->data->is_valid_bit;
+}
+
+static void armada_enable_overheat_interrupt(struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ u32 reg;
+
+ /* Clear DFX temperature IRQ cause */
+ regmap_read(priv->syscon, data->dfx_irq_cause_off, &reg);
+
+ /* Enable DFX Temperature IRQ */
+ regmap_read(priv->syscon, data->dfx_irq_mask_off, &reg);
+ reg |= data->dfx_overheat_irq;
+ regmap_write(priv->syscon, data->dfx_irq_mask_off, reg);
+
+ /* Enable DFX server IRQ */
+ regmap_read(priv->syscon, data->dfx_server_irq_mask_off, &reg);
+ reg |= data->dfx_server_irq_en;
+ regmap_write(priv->syscon, data->dfx_server_irq_mask_off, reg);
+
+ /* Enable overheat interrupt */
+ regmap_read(priv->syscon, data->syscon_control1_off, &reg);
+ reg |= CONTROL1_TSEN_INT_EN;
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+}
+
+static void __maybe_unused
+armada_disable_overheat_interrupt(struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ u32 reg;
+
+ regmap_read(priv->syscon, data->syscon_control1_off, &reg);
+ FIELD_MODIFY(CONTROL1_TSEN_INT_EN, &reg, 0);
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+}
+
+/* There is currently no board with more than one sensor per channel */
+static int armada_select_channel(struct armada_thermal_priv *priv, int channel)
+{
+ struct armada_thermal_data *data = priv->data;
+ u32 ctrl0;
+
+ if (channel < 0 || channel > priv->data->cpu_nr)
+ return -EINVAL;
+
+ if (priv->current_channel == channel)
+ return 0;
+
+ /* Stop the measurements */
+ regmap_read(priv->syscon, data->syscon_control0_off, &ctrl0);
+ FIELD_MODIFY(CONTROL0_TSEN_START, &ctrl0, 0);
+ regmap_write(priv->syscon, data->syscon_control0_off, ctrl0);
+
+ /* Other channels are external and should be selected accordingly */
+ if (channel) {
+ /* Change the mode to external */
+ FIELD_MODIFY(CONTROL0_TSEN_MODE_MASK, &ctrl0, CONTROL0_TSEN_MODE_EXTERNAL);
+ /* Select the sensor */
+ FIELD_MODIFY(CONTROL0_TSEN_CHAN_MASK, &ctrl0, channel - 1);
+ } else {
+ /* Reset the mode, internal sensor will be automatically selected */
+ FIELD_MODIFY(CONTROL0_TSEN_MODE_MASK, &ctrl0, 0);
+ }
+
+ /* Actually set the mode/channel */
+ regmap_write(priv->syscon, data->syscon_control0_off, ctrl0);
+ priv->current_channel = channel;
+
+ /* Re-start the measurements */
+ FIELD_MODIFY(CONTROL0_TSEN_START, &ctrl0, 1);
+ regmap_write(priv->syscon, data->syscon_control0_off, ctrl0);
+
+ /*
+ * The IP has a latency of ~15ms, so after updating the selected source,
+ * we must absolutely wait for the sensor validity bit to ensure we read
+ * actual data.
+ */
+ if (armada_wait_sensor_validity(priv))
+ return -EIO;
+
+ return 0;
+}
+
+static int armada_read_sensor(struct armada_thermal_priv *priv, int *temp)
+{
+ u32 reg, div;
+ s64 sample, b, m;
+
+ regmap_read(priv->syscon, priv->data->syscon_status_off, &reg);
+ reg = (reg >> priv->data->temp_shift) & priv->data->temp_mask;
+ if (priv->data->signed_sample)
+ /* The most significant bit is the sign bit */
+ sample = sign_extend32(reg, fls(priv->data->temp_mask) - 1);
+ else
+ sample = reg;
+
+ /* Get formula coeficients */
+ b = priv->data->coef_b;
+ m = priv->data->coef_m;
+ div = priv->data->coef_div;
+
+ if (priv->data->inverted)
+ *temp = div_s64((m * sample) - b, div);
+ else
+ *temp = div_s64(b - (m * sample), div);
+
+ return 0;
+}
+
+static int armada_get_temp_legacy(struct thermal_zone_device *thermal,
+ int *temp)
+{
+ struct armada_thermal_priv *priv = thermal_zone_device_priv(thermal);
+ int ret;
+
+ /* Valid check */
+ if (!armada_is_valid(priv))
+ return -EIO;
+
+ /* Do the actual reading */
+ ret = armada_read_sensor(priv, temp);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops legacy_ops = {
+ .get_temp = armada_get_temp_legacy,
+};
+
+static int armada_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct armada_thermal_sensor *sensor = thermal_zone_device_priv(tz);
+ struct armada_thermal_priv *priv = sensor->priv;
+ int ret;
+
+ mutex_lock(&priv->update_lock);
+
+ /* Select the desired channel */
+ ret = armada_select_channel(priv, sensor->id);
+ if (ret)
+ goto unlock_mutex;
+
+ /* Do the actual reading */
+ ret = armada_read_sensor(priv, temp);
+ if (ret)
+ goto unlock_mutex;
+
+ /*
+ * Select back the interrupt source channel from which a potential
+ * critical trip point has been set.
+ */
+ ret = armada_select_channel(priv, priv->interrupt_source);
+
+unlock_mutex:
+ mutex_unlock(&priv->update_lock);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops of_ops = {
+ .get_temp = armada_get_temp,
+};
+
+static unsigned int armada_mc_to_reg_temp(struct armada_thermal_data *data,
+ unsigned int temp_mc)
+{
+ s64 b = data->coef_b;
+ s64 m = data->coef_m;
+ s64 div = data->coef_div;
+ unsigned int sample;
+
+ if (data->inverted)
+ sample = div_s64(((temp_mc * div) + b), m);
+ else
+ sample = div_s64((b - (temp_mc * div)), m);
+
+ return sample & data->temp_mask;
+}
+
+/*
+ * The documentation states:
+ * high/low watermark = threshold +/- 0.4761 * 2^(hysteresis + 2)
+ * which is the mathematical derivation for:
+ * 0x0 <=> 1.9°C, 0x1 <=> 3.8°C, 0x2 <=> 7.6°C, 0x3 <=> 15.2°C
+ */
+static unsigned int hyst_levels_mc[] = {1900, 3800, 7600, 15200};
+
+static unsigned int armada_mc_to_reg_hyst(struct armada_thermal_data *data,
+ unsigned int hyst_mc)
+{
+ int i;
+
+ /*
+ * We will always take the smallest possible hysteresis to avoid risking
+ * the hardware integrity by enlarging the threshold by +8°C in the
+ * worst case.
+ */
+ for (i = ARRAY_SIZE(hyst_levels_mc) - 1; i > 0; i--)
+ if (hyst_mc >= hyst_levels_mc[i])
+ break;
+
+ return i & data->hyst_mask;
+}
+
+static void armada_set_overheat_thresholds(struct armada_thermal_priv *priv,
+ int thresh_mc, int hyst_mc)
+{
+ struct armada_thermal_data *data = priv->data;
+ unsigned int threshold = armada_mc_to_reg_temp(data, thresh_mc);
+ unsigned int hysteresis = armada_mc_to_reg_hyst(data, hyst_mc);
+ u32 ctrl1;
+
+ regmap_read(priv->syscon, data->syscon_control1_off, &ctrl1);
+
+ /* Set Threshold */
+ if (thresh_mc >= 0) {
+ ctrl1 &= ~(data->temp_mask << data->thresh_shift);
+ ctrl1 |= threshold << data->thresh_shift;
+ priv->current_threshold = thresh_mc;
+ }
+
+ /* Set Hysteresis */
+ if (hyst_mc >= 0) {
+ ctrl1 &= ~(data->hyst_mask << data->hyst_shift);
+ ctrl1 |= hysteresis << data->hyst_shift;
+ priv->current_hysteresis = hyst_mc;
+ }
+
+ regmap_write(priv->syscon, data->syscon_control1_off, ctrl1);
+}
+
+static irqreturn_t armada_overheat_isr(int irq, void *blob)
+{
+ /*
+ * Disable the IRQ and continue in thread context (thermal core
+ * notification and temperature monitoring).
+ */
+ disable_irq_nosync(irq);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t armada_overheat_isr_thread(int irq, void *blob)
+{
+ struct armada_thermal_priv *priv = blob;
+ int low_threshold = priv->current_threshold - priv->current_hysteresis;
+ int temperature;
+ u32 dummy;
+ int ret;
+
+ /* Notify the core in thread context */
+ thermal_zone_device_update(priv->overheat_sensor,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ /*
+ * The overheat interrupt must be cleared by reading the DFX interrupt
+ * cause _after_ the temperature has fallen down to the low threshold.
+ * Otherwise future interrupts might not be served.
+ */
+ do {
+ msleep(OVERHEAT_INT_POLL_DELAY_MS);
+ mutex_lock(&priv->update_lock);
+ ret = armada_read_sensor(priv, &temperature);
+ mutex_unlock(&priv->update_lock);
+ if (ret)
+ goto enable_irq;
+ } while (temperature >= low_threshold);
+
+ regmap_read(priv->syscon, priv->data->dfx_irq_cause_off, &dummy);
+
+ /* Notify the thermal core that the temperature is acceptable again */
+ thermal_zone_device_update(priv->overheat_sensor,
+ THERMAL_EVENT_UNSPECIFIED);
+
+enable_irq:
+ enable_irq(irq);
+
+ return IRQ_HANDLED;
+}
+
+static const struct armada_thermal_data armadaxp_data = {
+ .init = armadaxp_init,
+ .temp_shift = 10,
+ .temp_mask = 0x1ff,
+ .coef_b = 3153000000ULL,
+ .coef_m = 10000000ULL,
+ .coef_div = 13825,
+ .syscon_status_off = 0xb0,
+ .syscon_control1_off = 0x2d0,
+};
+
+static const struct armada_thermal_data armada370_data = {
+ .init = armada370_init,
+ .is_valid_bit = BIT(9),
+ .temp_shift = 10,
+ .temp_mask = 0x1ff,
+ .coef_b = 3153000000ULL,
+ .coef_m = 10000000ULL,
+ .coef_div = 13825,
+ .syscon_status_off = 0x0,
+ .syscon_control1_off = 0x4,
+};
+
+static const struct armada_thermal_data armada375_data = {
+ .init = armada375_init,
+ .is_valid_bit = BIT(10),
+ .temp_shift = 0,
+ .temp_mask = 0x1ff,
+ .coef_b = 3171900000ULL,
+ .coef_m = 10000000ULL,
+ .coef_div = 13616,
+ .syscon_status_off = 0x78,
+ .syscon_control0_off = 0x7c,
+ .syscon_control1_off = 0x80,
+};
+
+static const struct armada_thermal_data armada380_data = {
+ .init = armada380_init,
+ .is_valid_bit = BIT(10),
+ .temp_shift = 0,
+ .temp_mask = 0x3ff,
+ .coef_b = 1172499100ULL,
+ .coef_m = 2000096ULL,
+ .coef_div = 4201,
+ .inverted = true,
+ .syscon_control0_off = 0x70,
+ .syscon_control1_off = 0x74,
+ .syscon_status_off = 0x78,
+};
+
+static const struct armada_thermal_data armada_ap806_data = {
+ .init = armada_ap80x_init,
+ .is_valid_bit = BIT(16),
+ .temp_shift = 0,
+ .temp_mask = 0x3ff,
+ .thresh_shift = 3,
+ .hyst_shift = 19,
+ .hyst_mask = 0x3,
+ .coef_b = -150000LL,
+ .coef_m = 423ULL,
+ .coef_div = 1,
+ .inverted = true,
+ .signed_sample = true,
+ .syscon_control0_off = 0x84,
+ .syscon_control1_off = 0x88,
+ .syscon_status_off = 0x8C,
+ .dfx_irq_cause_off = 0x108,
+ .dfx_irq_mask_off = 0x10C,
+ .dfx_overheat_irq = BIT(22),
+ .dfx_server_irq_mask_off = 0x104,
+ .dfx_server_irq_en = BIT(1),
+ .cpu_nr = 4,
+};
+
+static const struct armada_thermal_data armada_ap807_data = {
+ .init = armada_ap80x_init,
+ .is_valid_bit = BIT(16),
+ .temp_shift = 0,
+ .temp_mask = 0x3ff,
+ .thresh_shift = 3,
+ .hyst_shift = 19,
+ .hyst_mask = 0x3,
+ .coef_b = -128900LL,
+ .coef_m = 394ULL,
+ .coef_div = 1,
+ .inverted = true,
+ .signed_sample = true,
+ .syscon_control0_off = 0x84,
+ .syscon_control1_off = 0x88,
+ .syscon_status_off = 0x8C,
+ .dfx_irq_cause_off = 0x108,
+ .dfx_irq_mask_off = 0x10C,
+ .dfx_overheat_irq = BIT(22),
+ .dfx_server_irq_mask_off = 0x104,
+ .dfx_server_irq_en = BIT(1),
+ .cpu_nr = 4,
+};
+
+static const struct armada_thermal_data armada_cp110_data = {
+ .init = armada_cp110_init,
+ .is_valid_bit = BIT(10),
+ .temp_shift = 0,
+ .temp_mask = 0x3ff,
+ .thresh_shift = 16,
+ .hyst_shift = 26,
+ .hyst_mask = 0x3,
+ .coef_b = 1172499100ULL,
+ .coef_m = 2000096ULL,
+ .coef_div = 4201,
+ .inverted = true,
+ .syscon_control0_off = 0x70,
+ .syscon_control1_off = 0x74,
+ .syscon_status_off = 0x78,
+ .dfx_irq_cause_off = 0x108,
+ .dfx_irq_mask_off = 0x10C,
+ .dfx_overheat_irq = BIT(20),
+ .dfx_server_irq_mask_off = 0x104,
+ .dfx_server_irq_en = BIT(1),
+};
+
+static const struct of_device_id armada_thermal_id_table[] = {
+ {
+ .compatible = "marvell,armadaxp-thermal",
+ .data = &armadaxp_data,
+ },
+ {
+ .compatible = "marvell,armada370-thermal",
+ .data = &armada370_data,
+ },
+ {
+ .compatible = "marvell,armada375-thermal",
+ .data = &armada375_data,
+ },
+ {
+ .compatible = "marvell,armada380-thermal",
+ .data = &armada380_data,
+ },
+ {
+ .compatible = "marvell,armada-ap806-thermal",
+ .data = &armada_ap806_data,
+ },
+ {
+ .compatible = "marvell,armada-ap807-thermal",
+ .data = &armada_ap807_data,
+ },
+ {
+ .compatible = "marvell,armada-cp110-thermal",
+ .data = &armada_cp110_data,
+ },
+ {
+ /* sentinel */
+ },
+};
+MODULE_DEVICE_TABLE(of, armada_thermal_id_table);
+
+static const struct regmap_config armada_thermal_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+};
+
+static int armada_thermal_probe_legacy(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ void __iomem *base;
+
+ /* First memory region points towards the status register */
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ /*
+ * Fix up from the old individual DT register specification to
+ * cover all the registers. We do this by adjusting the ioremap()
+ * result, which should be fine as ioremap() deals with pages.
+ * However, validate that we do not cross a page boundary while
+ * making this adjustment.
+ */
+ if (((unsigned long)base & ~PAGE_MASK) < data->syscon_status_off)
+ return -EINVAL;
+ base -= data->syscon_status_off;
+
+ priv->syscon = devm_regmap_init_mmio(&pdev->dev, base,
+ &armada_thermal_regmap_config);
+ return PTR_ERR_OR_ZERO(priv->syscon);
+}
+
+static int armada_thermal_probe_syscon(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ priv->syscon = syscon_node_to_regmap(pdev->dev.parent->of_node);
+ return PTR_ERR_OR_ZERO(priv->syscon);
+}
+
+static void armada_set_sane_name(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ const char *name = dev_name(&pdev->dev);
+
+ if (strlen(name) > THERMAL_NAME_LENGTH) {
+ /*
+ * When inside a system controller, the device name has the
+ * form: f06f8000.system-controller:ap-thermal so stripping
+ * after the ':' should give us a shorter but meaningful name.
+ */
+ name = strrchr(name, ':');
+ if (!name)
+ name = "armada_thermal";
+ else
+ name++;
+ }
+
+ /* Save the name locally */
+ strscpy(priv->zone_name, name, THERMAL_NAME_LENGTH);
+
+ /* Then ensure there are no '-' or hwmon core will complain */
+ strreplace(priv->zone_name, '-', '_');
+}
+
+/*
+ * The IP can manage to trigger interrupts on overheat situation from all the
+ * sensors. However, the interrupt source changes along with the last selected
+ * source (ie. the last read sensor), which is an inconsistent behavior. Avoid
+ * possible glitches by always selecting back only one channel (arbitrarily: the
+ * first in the DT which has a critical trip point). We also disable sensor
+ * switch during overheat situations.
+ */
+static int armada_configure_overheat_int(struct armada_thermal_priv *priv,
+ struct thermal_zone_device *tz,
+ int sensor_id)
+{
+ /* Retrieve the critical trip point to enable the overheat interrupt */
+ int temperature;
+ int ret;
+
+ ret = thermal_zone_get_crit_temp(tz, &temperature);
+ if (ret)
+ return ret;
+
+ ret = armada_select_channel(priv, sensor_id);
+ if (ret)
+ return ret;
+
+ /*
+ * A critical temperature does not have a hysteresis
+ */
+ armada_set_overheat_thresholds(priv, temperature, 0);
+ priv->overheat_sensor = tz;
+ priv->interrupt_source = sensor_id;
+ armada_enable_overheat_interrupt(priv);
+
+ return 0;
+}
+
+static int armada_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *tz;
+ struct armada_thermal_sensor *sensor;
+ struct armada_drvdata *drvdata;
+ const struct of_device_id *match;
+ struct armada_thermal_priv *priv;
+ int sensor_id, irq;
+ int ret;
+
+ match = of_match_device(armada_thermal_id_table, &pdev->dev);
+ if (!match)
+ return -ENODEV;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ priv->dev = &pdev->dev;
+ priv->data = (struct armada_thermal_data *)match->data;
+
+ mutex_init(&priv->update_lock);
+
+ /*
+ * Legacy DT bindings only described "control1" register (also referred
+ * as "control MSB" on old documentation). Then, bindings moved to cover
+ * "control0/control LSB" and "control1/control MSB" registers within
+ * the same resource, which was then of size 8 instead of 4.
+ *
+ * The logic of defining sporadic registers is broken. For instance, it
+ * blocked the addition of the overheat interrupt feature that needed
+ * another resource somewhere else in the same memory area. One solution
+ * is to define an overall system controller and put the thermal node
+ * into it, which requires the use of regmaps across all the driver.
+ */
+ if (IS_ERR(syscon_node_to_regmap(pdev->dev.parent->of_node))) {
+ /* Ensure device name is correct for the thermal core */
+ armada_set_sane_name(pdev, priv);
+
+ ret = armada_thermal_probe_legacy(pdev, priv);
+ if (ret)
+ return ret;
+
+ priv->data->init(pdev, priv);
+
+ /* Wait the sensors to be valid */
+ armada_wait_sensor_validity(priv);
+
+ 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");
+ return PTR_ERR(tz);
+ }
+
+ ret = thermal_zone_device_enable(tz);
+ if (ret) {
+ thermal_zone_device_unregister(tz);
+ return ret;
+ }
+
+ drvdata->type = LEGACY;
+ drvdata->data.tz = tz;
+ platform_set_drvdata(pdev, drvdata);
+
+ return 0;
+ }
+
+ ret = armada_thermal_probe_syscon(pdev, priv);
+ if (ret)
+ return ret;
+
+ priv->current_channel = -1;
+ priv->data->init(pdev, priv);
+ drvdata->type = SYSCON;
+ drvdata->data.priv = priv;
+ platform_set_drvdata(pdev, drvdata);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq == -EPROBE_DEFER)
+ return irq;
+
+ /* The overheat interrupt feature is not mandatory */
+ if (irq > 0) {
+ ret = devm_request_threaded_irq(&pdev->dev, irq,
+ armada_overheat_isr,
+ armada_overheat_isr_thread,
+ 0, NULL, priv);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * There is one channel for the IC and one per CPU (if any), each
+ * channel has one sensor.
+ */
+ for (sensor_id = 0; sensor_id <= priv->data->cpu_nr; sensor_id++) {
+ sensor = devm_kzalloc(&pdev->dev,
+ sizeof(struct armada_thermal_sensor),
+ GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ /* Register the sensor */
+ sensor->priv = priv;
+ sensor->id = sensor_id;
+ tz = devm_thermal_of_zone_register(&pdev->dev,
+ sensor->id, sensor,
+ &of_ops);
+ if (IS_ERR(tz)) {
+ dev_info(&pdev->dev, "Thermal sensor %d unavailable\n",
+ sensor_id);
+ devm_kfree(&pdev->dev, sensor);
+ continue;
+ }
+
+ /*
+ * The first channel that has a critical trip point registered
+ * in the DT will serve as interrupt source. Others possible
+ * critical trip points will simply be ignored by the driver.
+ */
+ if (irq > 0 && !priv->overheat_sensor)
+ armada_configure_overheat_int(priv, tz, sensor->id);
+ }
+
+ /* Just complain if no overheat interrupt was set up */
+ if (!priv->overheat_sensor)
+ dev_warn(&pdev->dev, "Overheat interrupt not available\n");
+
+ return 0;
+}
+
+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);
+}
+
+static struct platform_driver armada_thermal_driver = {
+ .probe = armada_thermal_probe,
+ .remove = armada_thermal_exit,
+ .driver = {
+ .name = "armada_thermal",
+ .of_match_table = armada_thermal_id_table,
+ },
+};
+
+module_platform_driver(armada_thermal_driver);
+
+MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>");
+MODULE_DESCRIPTION("Marvell EBU Armada SoCs thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/broadcom/Kconfig b/drivers/thermal/broadcom/Kconfig
new file mode 100644
index 000000000..061f1db6e
--- /dev/null
+++ b/drivers/thermal/broadcom/Kconfig
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config BCM2711_THERMAL
+ tristate "Broadcom AVS RO thermal sensor driver"
+ depends on ARCH_BCM2835 || COMPILE_TEST
+ depends on THERMAL_OF && MFD_SYSCON
+ help
+ Support for thermal sensors on Broadcom BCM2711 SoCs.
+
+config BCM2835_THERMAL
+ tristate "Thermal sensors on bcm2835 SoC"
+ depends on ARCH_BCM2835 || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on THERMAL_OF
+ help
+ Support for thermal sensors on Broadcom bcm2835 SoCs.
+
+config BRCMSTB_THERMAL
+ tristate "Broadcom STB AVS TMON thermal driver"
+ depends on ARCH_BRCMSTB || COMPILE_TEST
+ help
+ Enable this driver if you have a Broadcom STB SoC and would like
+ thermal framework support.
+
+config BCM_NS_THERMAL
+ tristate "Northstar thermal driver"
+ depends on ARCH_BCM_IPROC || COMPILE_TEST
+ default y if ARCH_BCM_IPROC
+ help
+ Support for the Northstar and Northstar Plus family of SoCs (e.g.
+ BCM4708, BCM4709, BCM5301x, BCM95852X, etc). It contains DMU (Device
+ Management Unit) block with a thermal sensor that allows checking CPU
+ temperature.
+
+config BCM_SR_THERMAL
+ tristate "Stingray thermal driver"
+ depends on ARCH_BCM_IPROC || COMPILE_TEST
+ default ARCH_BCM_IPROC
+ help
+ Support for the Stingray family of SoCs. Its different blocks like
+ iHost, CRMU and NITRO has thermal sensor that allows checking its
+ temperature.
diff --git a/drivers/thermal/broadcom/Makefile b/drivers/thermal/broadcom/Makefile
new file mode 100644
index 000000000..c917b243d
--- /dev/null
+++ b/drivers/thermal/broadcom/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_BCM2711_THERMAL) += bcm2711_thermal.o
+obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o
+obj-$(CONFIG_BRCMSTB_THERMAL) += brcmstb_thermal.o
+obj-$(CONFIG_BCM_NS_THERMAL) += ns-thermal.o
+obj-$(CONFIG_BCM_SR_THERMAL) += sr-thermal.o
diff --git a/drivers/thermal/broadcom/bcm2711_thermal.c b/drivers/thermal/broadcom/bcm2711_thermal.c
new file mode 100644
index 000000000..03ac2d02e
--- /dev/null
+++ b/drivers/thermal/broadcom/bcm2711_thermal.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Broadcom AVS RO thermal sensor driver
+ *
+ * based on brcmstb_thermal
+ *
+ * Copyright (C) 2020 Stefan Wahren
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+#include "../thermal_hwmon.h"
+
+#define AVS_RO_TEMP_STATUS 0x200
+#define AVS_RO_TEMP_STATUS_VALID_MSK (BIT(16) | BIT(10))
+#define AVS_RO_TEMP_STATUS_DATA_MSK GENMASK(9, 0)
+
+struct bcm2711_thermal_priv {
+ struct regmap *regmap;
+ struct thermal_zone_device *thermal;
+};
+
+static int bcm2711_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct bcm2711_thermal_priv *priv = thermal_zone_device_priv(tz);
+ int slope = thermal_zone_get_slope(tz);
+ int offset = thermal_zone_get_offset(tz);
+ u32 val;
+ int ret;
+
+ ret = regmap_read(priv->regmap, AVS_RO_TEMP_STATUS, &val);
+ if (ret)
+ return ret;
+
+ if (!(val & AVS_RO_TEMP_STATUS_VALID_MSK))
+ return -EIO;
+
+ val &= AVS_RO_TEMP_STATUS_DATA_MSK;
+
+ /* Convert a HW code to a temperature reading (millidegree celsius) */
+ *temp = slope * val + offset;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops bcm2711_thermal_of_ops = {
+ .get_temp = bcm2711_get_temp,
+};
+
+static const struct of_device_id bcm2711_thermal_id_table[] = {
+ { .compatible = "brcm,bcm2711-thermal" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bcm2711_thermal_id_table);
+
+static int bcm2711_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *thermal;
+ struct bcm2711_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct device_node *parent;
+ struct regmap *regmap;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* get regmap from syscon node */
+ parent = of_get_parent(dev->of_node); /* parent should be syscon node */
+ regmap = syscon_node_to_regmap(parent);
+ of_node_put(parent);
+ if (IS_ERR(regmap)) {
+ ret = PTR_ERR(regmap);
+ dev_err(dev, "failed to get regmap: %d\n", ret);
+ return ret;
+ }
+ priv->regmap = regmap;
+
+ thermal = devm_thermal_of_zone_register(dev, 0, priv,
+ &bcm2711_thermal_of_ops);
+ if (IS_ERR(thermal)) {
+ ret = PTR_ERR(thermal);
+ dev_err(dev, "could not register sensor: %d\n", ret);
+ return ret;
+ }
+
+ priv->thermal = thermal;
+
+ return thermal_add_hwmon_sysfs(thermal);
+}
+
+static struct platform_driver bcm2711_thermal_driver = {
+ .probe = bcm2711_thermal_probe,
+ .driver = {
+ .name = "bcm2711_thermal",
+ .of_match_table = bcm2711_thermal_id_table,
+ },
+};
+module_platform_driver(bcm2711_thermal_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Stefan Wahren");
+MODULE_DESCRIPTION("Broadcom AVS RO thermal sensor driver");
diff --git a/drivers/thermal/broadcom/bcm2835_thermal.c b/drivers/thermal/broadcom/bcm2835_thermal.c
new file mode 100644
index 000000000..c5105dfc6
--- /dev/null
+++ b/drivers/thermal/broadcom/bcm2835_thermal.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Broadcom BCM2835 SoC temperature sensor
+ *
+ * Copyright (C) 2016 Martin Sperl
+ */
+
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/minmax.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>
+
+#include "../thermal_hwmon.h"
+
+#define BCM2835_TS_TSENSCTL 0x00
+#define BCM2835_TS_TSENSSTAT 0x04
+
+#define BCM2835_TS_TSENSCTL_PRWDW BIT(0)
+#define BCM2835_TS_TSENSCTL_RSTB BIT(1)
+
+/*
+ * bandgap reference voltage in 6 mV increments
+ * 000b = 1178 mV, 001b = 1184 mV, ... 111b = 1220 mV
+ */
+#define BCM2835_TS_TSENSCTL_CTRL_BITS 3
+#define BCM2835_TS_TSENSCTL_CTRL_SHIFT 2
+#define BCM2835_TS_TSENSCTL_CTRL_MASK \
+ GENMASK(BCM2835_TS_TSENSCTL_CTRL_BITS + \
+ BCM2835_TS_TSENSCTL_CTRL_SHIFT - 1, \
+ BCM2835_TS_TSENSCTL_CTRL_SHIFT)
+#define BCM2835_TS_TSENSCTL_CTRL_DEFAULT 1
+#define BCM2835_TS_TSENSCTL_EN_INT BIT(5)
+#define BCM2835_TS_TSENSCTL_DIRECT BIT(6)
+#define BCM2835_TS_TSENSCTL_CLR_INT BIT(7)
+#define BCM2835_TS_TSENSCTL_THOLD_SHIFT 8
+#define BCM2835_TS_TSENSCTL_THOLD_BITS 10
+#define BCM2835_TS_TSENSCTL_THOLD_MASK \
+ GENMASK(BCM2835_TS_TSENSCTL_THOLD_BITS + \
+ BCM2835_TS_TSENSCTL_THOLD_SHIFT - 1, \
+ BCM2835_TS_TSENSCTL_THOLD_SHIFT)
+/*
+ * time how long the block to be asserted in reset
+ * which based on a clock counter (TSENS clock assumed)
+ */
+#define BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT 18
+#define BCM2835_TS_TSENSCTL_RSTDELAY_BITS 8
+#define BCM2835_TS_TSENSCTL_REGULEN BIT(26)
+
+#define BCM2835_TS_TSENSSTAT_DATA_BITS 10
+#define BCM2835_TS_TSENSSTAT_DATA_SHIFT 0
+#define BCM2835_TS_TSENSSTAT_DATA_MASK \
+ GENMASK(BCM2835_TS_TSENSSTAT_DATA_BITS + \
+ BCM2835_TS_TSENSSTAT_DATA_SHIFT - 1, \
+ BCM2835_TS_TSENSSTAT_DATA_SHIFT)
+#define BCM2835_TS_TSENSSTAT_VALID BIT(10)
+#define BCM2835_TS_TSENSSTAT_INTERRUPT BIT(11)
+
+struct bcm2835_thermal_data {
+ struct thermal_zone_device *tz;
+ void __iomem *regs;
+ struct clk *clk;
+ struct dentry *debugfsdir;
+};
+
+static int bcm2835_thermal_adc2temp(u32 adc, int offset, int slope)
+{
+ return offset + slope * adc;
+}
+
+static int bcm2835_thermal_temp2adc(int temp, int offset, int slope)
+{
+ temp -= offset;
+ temp /= slope;
+
+ return clamp(temp, 0, (int)BIT(BCM2835_TS_TSENSSTAT_DATA_BITS) - 1);
+}
+
+static int bcm2835_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct bcm2835_thermal_data *data = thermal_zone_device_priv(tz);
+ u32 val = readl(data->regs + BCM2835_TS_TSENSSTAT);
+
+ if (!(val & BCM2835_TS_TSENSSTAT_VALID))
+ return -EIO;
+
+ val &= BCM2835_TS_TSENSSTAT_DATA_MASK;
+
+ *temp = bcm2835_thermal_adc2temp(
+ val,
+ thermal_zone_get_offset(data->tz),
+ thermal_zone_get_slope(data->tz));
+
+ return 0;
+}
+
+static const struct debugfs_reg32 bcm2835_thermal_regs[] = {
+ {
+ .name = "ctl",
+ .offset = 0
+ },
+ {
+ .name = "stat",
+ .offset = 4
+ }
+};
+
+static void bcm2835_thermal_debugfs(struct platform_device *pdev)
+{
+ struct bcm2835_thermal_data *data = platform_get_drvdata(pdev);
+ struct debugfs_regset32 *regset;
+
+ data->debugfsdir = debugfs_create_dir("bcm2835_thermal", NULL);
+
+ regset = devm_kzalloc(&pdev->dev, sizeof(*regset), GFP_KERNEL);
+ if (!regset)
+ return;
+
+ regset->regs = bcm2835_thermal_regs;
+ regset->nregs = ARRAY_SIZE(bcm2835_thermal_regs);
+ regset->base = data->regs;
+
+ debugfs_create_regset32("regset", 0444, data->debugfsdir, regset);
+}
+
+static const struct thermal_zone_device_ops bcm2835_thermal_ops = {
+ .get_temp = bcm2835_thermal_get_temp,
+};
+
+/*
+ * Note: as per Raspberry Foundation FAQ
+ * (https://www.raspberrypi.org/help/faqs/#performanceOperatingTemperature)
+ * the recommended temperature range for the SoC -40C to +85C
+ * so the trip limit is set to 80C.
+ * this applies to all the BCM283X SoC
+ */
+
+static const struct of_device_id bcm2835_thermal_of_match_table[] = {
+ {
+ .compatible = "brcm,bcm2835-thermal",
+ },
+ {
+ .compatible = "brcm,bcm2836-thermal",
+ },
+ {
+ .compatible = "brcm,bcm2837-thermal",
+ },
+ {},
+};
+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;
+ int err = 0;
+ u32 val;
+ unsigned long rate;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ match = of_match_device(bcm2835_thermal_of_match_table, dev);
+ if (!match)
+ return -EINVAL;
+
+ data->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(data->regs)) {
+ err = PTR_ERR(data->regs);
+ 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(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(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
+ * touching the configuration registers.
+ * But if the HW is not enabled, then set it up
+ * using "sane" values used by the firmware right now.
+ */
+ val = readl(data->regs + BCM2835_TS_TSENSCTL);
+ if (!(val & BCM2835_TS_TSENSCTL_RSTB)) {
+ int offset, slope, crit_temp;
+
+ slope = thermal_zone_get_slope(tz);
+ offset = thermal_zone_get_offset(tz);
+ /*
+ * For now we deal only with critical, otherwise
+ * would need to iterate
+ */
+ err = thermal_zone_get_crit_temp(tz, &crit_temp);
+ if (err < 0) {
+ dev_err(dev, "Not able to read trip_temp: %d\n", err);
+ return err;
+ }
+
+ /* set bandgap reference voltage and enable voltage regulator */
+ val = (BCM2835_TS_TSENSCTL_CTRL_DEFAULT <<
+ BCM2835_TS_TSENSCTL_CTRL_SHIFT) |
+ BCM2835_TS_TSENSCTL_REGULEN;
+
+ /* use the recommended reset duration */
+ val |= (0xFE << BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT);
+
+ /* trip_adc value from info */
+ val |= bcm2835_thermal_temp2adc(crit_temp,
+ offset,
+ slope)
+ << BCM2835_TS_TSENSCTL_THOLD_SHIFT;
+
+ /* write the value back to the register as 2 steps */
+ writel(val, data->regs + BCM2835_TS_TSENSCTL);
+ val |= BCM2835_TS_TSENSCTL_RSTB;
+ writel(val, data->regs + BCM2835_TS_TSENSCTL);
+ }
+
+ data->tz = tz;
+
+ platform_set_drvdata(pdev, data);
+
+ /*
+ * Thermal_zone doesn't enable hwmon as default,
+ * enable it here
+ */
+ err = thermal_add_hwmon_sysfs(tz);
+ if (err)
+ return err;
+
+ bcm2835_thermal_debugfs(pdev);
+
+ return 0;
+}
+
+static void bcm2835_thermal_remove(struct platform_device *pdev)
+{
+ struct bcm2835_thermal_data *data = platform_get_drvdata(pdev);
+
+ debugfs_remove_recursive(data->debugfsdir);
+}
+
+static struct platform_driver bcm2835_thermal_driver = {
+ .probe = bcm2835_thermal_probe,
+ .remove = bcm2835_thermal_remove,
+ .driver = {
+ .name = "bcm2835_thermal",
+ .of_match_table = bcm2835_thermal_of_match_table,
+ },
+};
+module_platform_driver(bcm2835_thermal_driver);
+
+MODULE_AUTHOR("Martin Sperl");
+MODULE_DESCRIPTION("Thermal driver for bcm2835 chip");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/broadcom/brcmstb_thermal.c b/drivers/thermal/broadcom/brcmstb_thermal.c
new file mode 100644
index 000000000..5e23f9e00
--- /dev/null
+++ b/drivers/thermal/broadcom/brcmstb_thermal.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Broadcom STB AVS TMON thermal sensor driver
+ *
+ * Copyright (c) 2015-2017 Broadcom
+ */
+
+#define DRV_NAME "brcmstb_thermal"
+
+#define pr_fmt(fmt) DRV_NAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/irqreturn.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#define AVS_TMON_STATUS 0x00
+ #define AVS_TMON_STATUS_valid_msk BIT(11)
+ #define AVS_TMON_STATUS_data_msk GENMASK(10, 1)
+ #define AVS_TMON_STATUS_data_shift 1
+
+#define AVS_TMON_EN_OVERTEMP_RESET 0x04
+ #define AVS_TMON_EN_OVERTEMP_RESET_msk BIT(0)
+
+#define AVS_TMON_RESET_THRESH 0x08
+ #define AVS_TMON_RESET_THRESH_msk GENMASK(10, 1)
+ #define AVS_TMON_RESET_THRESH_shift 1
+
+#define AVS_TMON_INT_IDLE_TIME 0x10
+
+#define AVS_TMON_EN_TEMP_INT_SRCS 0x14
+ #define AVS_TMON_EN_TEMP_INT_SRCS_high BIT(1)
+ #define AVS_TMON_EN_TEMP_INT_SRCS_low BIT(0)
+
+#define AVS_TMON_INT_THRESH 0x18
+ #define AVS_TMON_INT_THRESH_high_msk GENMASK(26, 17)
+ #define AVS_TMON_INT_THRESH_high_shift 17
+ #define AVS_TMON_INT_THRESH_low_msk GENMASK(10, 1)
+ #define AVS_TMON_INT_THRESH_low_shift 1
+
+#define AVS_TMON_TEMP_INT_CODE 0x1c
+#define AVS_TMON_TP_TEST_ENABLE 0x20
+
+/* Default coefficients */
+#define AVS_TMON_TEMP_SLOPE 487
+#define AVS_TMON_TEMP_OFFSET 410040
+
+/* HW related temperature constants */
+#define AVS_TMON_TEMP_MAX 0x3ff
+#define AVS_TMON_TEMP_MIN -88161
+#define AVS_TMON_TEMP_MASK AVS_TMON_TEMP_MAX
+
+enum avs_tmon_trip_type {
+ TMON_TRIP_TYPE_LOW = 0,
+ TMON_TRIP_TYPE_HIGH,
+ TMON_TRIP_TYPE_RESET,
+ TMON_TRIP_TYPE_MAX,
+};
+
+struct avs_tmon_trip {
+ /* HW bit to enable the trip */
+ u32 enable_offs;
+ u32 enable_mask;
+
+ /* HW field to read the trip temperature */
+ u32 reg_offs;
+ u32 reg_msk;
+ int reg_shift;
+};
+
+static struct avs_tmon_trip avs_tmon_trips[] = {
+ /* Trips when temperature is below threshold */
+ [TMON_TRIP_TYPE_LOW] = {
+ .enable_offs = AVS_TMON_EN_TEMP_INT_SRCS,
+ .enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_low,
+ .reg_offs = AVS_TMON_INT_THRESH,
+ .reg_msk = AVS_TMON_INT_THRESH_low_msk,
+ .reg_shift = AVS_TMON_INT_THRESH_low_shift,
+ },
+ /* Trips when temperature is above threshold */
+ [TMON_TRIP_TYPE_HIGH] = {
+ .enable_offs = AVS_TMON_EN_TEMP_INT_SRCS,
+ .enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_high,
+ .reg_offs = AVS_TMON_INT_THRESH,
+ .reg_msk = AVS_TMON_INT_THRESH_high_msk,
+ .reg_shift = AVS_TMON_INT_THRESH_high_shift,
+ },
+ /* Automatically resets chip when above threshold */
+ [TMON_TRIP_TYPE_RESET] = {
+ .enable_offs = AVS_TMON_EN_OVERTEMP_RESET,
+ .enable_mask = AVS_TMON_EN_OVERTEMP_RESET_msk,
+ .reg_offs = AVS_TMON_RESET_THRESH,
+ .reg_msk = AVS_TMON_RESET_THRESH_msk,
+ .reg_shift = AVS_TMON_RESET_THRESH_shift,
+ },
+};
+
+struct brcmstb_thermal_params {
+ unsigned int offset;
+ unsigned int mult;
+ const struct thermal_zone_device_ops *of_ops;
+};
+
+struct brcmstb_thermal_priv {
+ void __iomem *tmon_base;
+ struct device *dev;
+ struct thermal_zone_device *thermal;
+ /* Process specific thermal parameters used for calculations */
+ const struct brcmstb_thermal_params *temp_params;
+};
+
+/* Convert a HW code to a temperature reading (millidegree celsius) */
+static inline int avs_tmon_code_to_temp(struct brcmstb_thermal_priv *priv,
+ u32 code)
+{
+ int offset = priv->temp_params->offset;
+ int mult = priv->temp_params->mult;
+
+ return (offset - (int)((code & AVS_TMON_TEMP_MASK) * mult));
+}
+
+/*
+ * Convert a temperature value (millidegree celsius) to a HW code
+ *
+ * @temp: temperature to convert
+ * @low: if true, round toward the low side
+ */
+static inline u32 avs_tmon_temp_to_code(struct brcmstb_thermal_priv *priv,
+ int temp, bool low)
+{
+ int offset = priv->temp_params->offset;
+ int mult = priv->temp_params->mult;
+
+ if (temp < AVS_TMON_TEMP_MIN)
+ return AVS_TMON_TEMP_MAX; /* Maximum code value */
+
+ if (temp >= offset)
+ return 0; /* Minimum code value */
+
+ if (low)
+ return (u32)(DIV_ROUND_UP(offset - temp, mult));
+ else
+ return (u32)((offset - temp) / mult);
+}
+
+static int brcmstb_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct brcmstb_thermal_priv *priv = thermal_zone_device_priv(tz);
+ u32 val;
+ int t;
+
+ val = __raw_readl(priv->tmon_base + AVS_TMON_STATUS);
+
+ if (!(val & AVS_TMON_STATUS_valid_msk))
+ return -EIO;
+
+ val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift;
+
+ t = avs_tmon_code_to_temp(priv, val);
+ *temp = max(0, t);
+
+ return 0;
+}
+
+static void avs_tmon_trip_enable(struct brcmstb_thermal_priv *priv,
+ enum avs_tmon_trip_type type, int en)
+{
+ struct avs_tmon_trip *trip = &avs_tmon_trips[type];
+ u32 val = __raw_readl(priv->tmon_base + trip->enable_offs);
+
+ dev_dbg(priv->dev, "%sable trip, type %d\n", en ? "en" : "dis", type);
+
+ if (en)
+ val |= trip->enable_mask;
+ else
+ val &= ~trip->enable_mask;
+
+ __raw_writel(val, priv->tmon_base + trip->enable_offs);
+}
+
+static int avs_tmon_get_trip_temp(struct brcmstb_thermal_priv *priv,
+ enum avs_tmon_trip_type type)
+{
+ struct avs_tmon_trip *trip = &avs_tmon_trips[type];
+ u32 val = __raw_readl(priv->tmon_base + trip->reg_offs);
+
+ val &= trip->reg_msk;
+ val >>= trip->reg_shift;
+
+ return avs_tmon_code_to_temp(priv, val);
+}
+
+static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv *priv,
+ enum avs_tmon_trip_type type,
+ int temp)
+{
+ struct avs_tmon_trip *trip = &avs_tmon_trips[type];
+ u32 val, orig;
+
+ dev_dbg(priv->dev, "set temp %d to %d\n", type, temp);
+
+ /* round toward low temp for the low interrupt */
+ val = avs_tmon_temp_to_code(priv, temp,
+ type == TMON_TRIP_TYPE_LOW);
+
+ val <<= trip->reg_shift;
+ val &= trip->reg_msk;
+
+ orig = __raw_readl(priv->tmon_base + trip->reg_offs);
+ orig &= ~trip->reg_msk;
+ orig |= val;
+ __raw_writel(orig, priv->tmon_base + trip->reg_offs);
+}
+
+static int avs_tmon_get_intr_temp(struct brcmstb_thermal_priv *priv)
+{
+ u32 val;
+
+ val = __raw_readl(priv->tmon_base + AVS_TMON_TEMP_INT_CODE);
+ return avs_tmon_code_to_temp(priv, val);
+}
+
+static irqreturn_t brcmstb_tmon_irq_thread(int irq, void *data)
+{
+ struct brcmstb_thermal_priv *priv = data;
+ int low, high, intr;
+
+ low = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_LOW);
+ high = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_HIGH);
+ intr = avs_tmon_get_intr_temp(priv);
+
+ dev_dbg(priv->dev, "low/intr/high: %d/%d/%d\n",
+ low, intr, high);
+
+ /* Disable high-temp until next threshold shift */
+ if (intr >= high)
+ avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);
+ /* Disable low-temp until next threshold shift */
+ if (intr <= low)
+ avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);
+
+ /*
+ * Notify using the interrupt temperature, in case the temperature
+ * changes before it can next be read out
+ */
+ thermal_zone_device_update(priv->thermal, intr);
+
+ return IRQ_HANDLED;
+}
+
+static int brcmstb_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct brcmstb_thermal_priv *priv = thermal_zone_device_priv(tz);
+
+ dev_dbg(priv->dev, "set trips %d <--> %d\n", low, high);
+
+ /*
+ * Disable low-temp if "low" is too small. As per thermal framework
+ * API, we use -INT_MAX rather than INT_MIN.
+ */
+ if (low <= -INT_MAX) {
+ avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);
+ } else {
+ avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_LOW, low);
+ avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 1);
+ }
+
+ /* Disable high-temp if "high" is too big. */
+ if (high == INT_MAX) {
+ avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);
+ } else {
+ avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_HIGH, high);
+ avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 1);
+ }
+
+ return 0;
+}
+
+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_of_ops,
+};
+
+static const struct thermal_zone_device_ops brcmstb_28nm_of_ops = {
+ .get_temp = brcmstb_get_temp,
+ .set_trips = brcmstb_set_trips,
+};
+
+static const struct brcmstb_thermal_params brcmstb_28nm_params = {
+ .offset = 410040,
+ .mult = 487,
+ .of_ops = &brcmstb_28nm_of_ops,
+};
+
+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 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table);
+
+static int brcmstb_thermal_probe(struct platform_device *pdev)
+{
+ const struct thermal_zone_device_ops *of_ops;
+ struct thermal_zone_device *thermal;
+ struct brcmstb_thermal_priv *priv;
+ int irq, ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->temp_params = of_device_get_match_data(&pdev->dev);
+ if (!priv->temp_params)
+ return -EINVAL;
+
+ priv->tmon_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(priv->tmon_base))
+ return PTR_ERR(priv->tmon_base);
+
+ priv->dev = &pdev->dev;
+ of_ops = priv->temp_params->of_ops;
+
+ thermal = devm_thermal_of_zone_register(&pdev->dev, 0, priv,
+ of_ops);
+ if (IS_ERR(thermal))
+ return dev_err_probe(&pdev->dev, PTR_ERR(thermal),
+ "could not register sensor\n");
+
+ priv->thermal = thermal;
+
+ irq = platform_get_irq_optional(pdev, 0);
+ if (irq >= 0) {
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ brcmstb_tmon_irq_thread,
+ IRQF_ONESHOT,
+ DRV_NAME, priv);
+ if (ret < 0)
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n");
+
+ return 0;
+}
+
+static struct platform_driver brcmstb_thermal_driver = {
+ .probe = brcmstb_thermal_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = brcmstb_thermal_id_table,
+ },
+};
+module_platform_driver(brcmstb_thermal_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Brian Norris");
+MODULE_DESCRIPTION("Broadcom STB AVS TMON thermal driver");
diff --git a/drivers/thermal/broadcom/ns-thermal.c b/drivers/thermal/broadcom/ns-thermal.c
new file mode 100644
index 000000000..8b5b32f74
--- /dev/null
+++ b/drivers/thermal/broadcom/ns-thermal.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017 Rafał Miłecki <rafal@milecki.pl>
+ */
+
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#define PVTMON_CONTROL0 0x00
+#define PVTMON_CONTROL0_SEL_MASK 0x0000000e
+#define PVTMON_CONTROL0_SEL_TEMP_MONITOR 0x00000000
+#define PVTMON_CONTROL0_SEL_TEST_MODE 0x0000000e
+#define PVTMON_STATUS 0x08
+
+static int ns_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ void __iomem *pvtmon = thermal_zone_device_priv(tz);
+ int offset = thermal_zone_get_offset(tz);
+ int slope = thermal_zone_get_slope(tz);
+ u32 val;
+
+ val = readl(pvtmon + PVTMON_CONTROL0);
+ if ((val & PVTMON_CONTROL0_SEL_MASK) != PVTMON_CONTROL0_SEL_TEMP_MONITOR) {
+ /* Clear current mode selection */
+ val &= ~PVTMON_CONTROL0_SEL_MASK;
+
+ /* Set temp monitor mode (it's the default actually) */
+ val |= PVTMON_CONTROL0_SEL_TEMP_MONITOR;
+
+ writel(val, pvtmon + PVTMON_CONTROL0);
+ }
+
+ val = readl(pvtmon + PVTMON_STATUS);
+ *temp = slope * val + offset;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops ns_thermal_ops = {
+ .get_temp = ns_thermal_get_temp,
+};
+
+static int ns_thermal_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct thermal_zone_device *tz;
+ void __iomem *pvtmon;
+
+ pvtmon = of_iomap(dev_of_node(dev), 0);
+ if (WARN_ON(!pvtmon))
+ return -ENOENT;
+
+ tz = devm_thermal_of_zone_register(dev, 0,
+ pvtmon,
+ &ns_thermal_ops);
+ if (IS_ERR(tz)) {
+ iounmap(pvtmon);
+ return PTR_ERR(tz);
+ }
+
+ platform_set_drvdata(pdev, pvtmon);
+
+ return 0;
+}
+
+static void ns_thermal_remove(struct platform_device *pdev)
+{
+ void __iomem *pvtmon = platform_get_drvdata(pdev);
+
+ iounmap(pvtmon);
+}
+
+static const struct of_device_id ns_thermal_of_match[] = {
+ { .compatible = "brcm,ns-thermal", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ns_thermal_of_match);
+
+static struct platform_driver ns_thermal_driver = {
+ .probe = ns_thermal_probe,
+ .remove = ns_thermal_remove,
+ .driver = {
+ .name = "ns-thermal",
+ .of_match_table = ns_thermal_of_match,
+ },
+};
+module_platform_driver(ns_thermal_driver);
+
+MODULE_AUTHOR("Rafał Miłecki <rafal@milecki.pl>");
+MODULE_DESCRIPTION("Northstar thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/broadcom/sr-thermal.c b/drivers/thermal/broadcom/sr-thermal.c
new file mode 100644
index 000000000..9a29dfd4c
--- /dev/null
+++ b/drivers/thermal/broadcom/sr-thermal.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Broadcom
+ */
+
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+/*
+ * In stingray thermal IO memory,
+ * Total Number of available TMONs MASK is at offset 0
+ * temperature registers BASE is at 4 byte offset.
+ * Each TMON temperature register size is 4.
+ */
+#define SR_TMON_TEMP_BASE(id) ((id) * 0x4)
+
+#define SR_TMON_MAX_LIST 6
+
+struct sr_tmon {
+ unsigned int crit_temp;
+ unsigned int tmon_id;
+ struct sr_thermal *priv;
+};
+
+struct sr_thermal {
+ void __iomem *regs;
+ unsigned int max_crit_temp;
+ struct sr_tmon tmon[SR_TMON_MAX_LIST];
+};
+
+static int sr_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct sr_tmon *tmon = thermal_zone_device_priv(tz);
+ struct sr_thermal *sr_thermal = tmon->priv;
+
+ *temp = readl(sr_thermal->regs + SR_TMON_TEMP_BASE(tmon->tmon_id));
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops sr_tz_ops = {
+ .get_temp = sr_get_temp,
+};
+
+static int sr_thermal_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct thermal_zone_device *tz;
+ struct sr_thermal *sr_thermal;
+ struct sr_tmon *tmon;
+ struct resource *res;
+ u32 sr_tmon_list = 0;
+ unsigned int i;
+ int ret;
+
+ sr_thermal = devm_kzalloc(dev, sizeof(*sr_thermal), GFP_KERNEL);
+ if (!sr_thermal)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+
+ sr_thermal->regs = (void __iomem *)devm_memremap(&pdev->dev, res->start,
+ resource_size(res),
+ MEMREMAP_WB);
+ if (IS_ERR(sr_thermal->regs)) {
+ dev_err(dev, "failed to get io address\n");
+ return PTR_ERR(sr_thermal->regs);
+ }
+
+ ret = device_property_read_u32(dev, "brcm,tmon-mask", &sr_tmon_list);
+ if (ret)
+ return ret;
+
+ tmon = sr_thermal->tmon;
+ for (i = 0; i < SR_TMON_MAX_LIST; i++, tmon++) {
+ if (!(sr_tmon_list & BIT(i)))
+ continue;
+
+ /* Flush temperature registers */
+ writel(0, sr_thermal->regs + SR_TMON_TEMP_BASE(i));
+ tmon->tmon_id = i;
+ tmon->priv = sr_thermal;
+ tz = devm_thermal_of_zone_register(dev, i, tmon,
+ &sr_tz_ops);
+ if (IS_ERR(tz))
+ return PTR_ERR(tz);
+
+ dev_dbg(dev, "thermal sensor %d registered\n", i);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id sr_thermal_of_match[] = {
+ { .compatible = "brcm,sr-thermal", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sr_thermal_of_match);
+
+static struct platform_driver sr_thermal_driver = {
+ .probe = sr_thermal_probe,
+ .driver = {
+ .name = "sr-thermal",
+ .of_match_table = sr_thermal_of_match,
+ },
+};
+module_platform_driver(sr_thermal_driver);
+
+MODULE_AUTHOR("Pramod Kumar <pramod.kumar@broadcom.com>");
+MODULE_DESCRIPTION("Stingray thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c
new file mode 100644
index 000000000..768859a7a
--- /dev/null
+++ b/drivers/thermal/cpufreq_cooling.c
@@ -0,0 +1,695 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/drivers/thermal/cpufreq_cooling.c
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *
+ * Copyright (C) 2012-2018 Linaro Limited.
+ *
+ * Authors: Amit Daniel <amit.kachhap@linaro.org>
+ * Viresh Kumar <viresh.kumar@linaro.org>
+ *
+ */
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu_cooling.h>
+#include <linux/device.h>
+#include <linux/energy_model.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "thermal_trace.h"
+
+/*
+ * Cooling state <-> CPUFreq frequency
+ *
+ * Cooling states are translated to frequencies throughout this driver and this
+ * is the relation between them.
+ *
+ * Highest cooling state corresponds to lowest possible frequency.
+ *
+ * i.e.
+ * level 0 --> 1st Max Freq
+ * level 1 --> 2nd Max Freq
+ * ...
+ */
+
+/**
+ * struct time_in_idle - Idle time stats
+ * @time: previous reading of the absolute time that this cpu was idle
+ * @timestamp: wall time of the last invocation of get_cpu_idle_time_us()
+ */
+struct time_in_idle {
+ u64 time;
+ u64 timestamp;
+};
+
+/**
+ * struct cpufreq_cooling_device - data for cooling device with cpufreq
+ * @last_load: load measured by the latest call to cpufreq_get_requested_power()
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ * cooling devices.
+ * @max_level: maximum cooling level. One less than total number of valid
+ * cpufreq frequencies.
+ * @em: Reference on the Energy Model of the device
+ * @policy: cpufreq policy.
+ * @cooling_ops: cpufreq callbacks to thermal cooling device ops
+ * @idle_time: idle time stats
+ * @qos_req: PM QoS contraint to apply
+ *
+ * This structure is required for keeping information of each registered
+ * cpufreq_cooling_device.
+ */
+struct cpufreq_cooling_device {
+ u32 last_load;
+ unsigned int cpufreq_state;
+ unsigned int max_level;
+ struct em_perf_domain *em;
+ struct cpufreq_policy *policy;
+ struct thermal_cooling_device_ops cooling_ops;
+#ifndef CONFIG_SMP
+ struct time_in_idle *idle_time;
+#endif
+ struct freq_qos_request qos_req;
+};
+
+#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
+/**
+ * get_level: Find the level for a particular frequency
+ * @cpufreq_cdev: cpufreq_cdev for which the property is required
+ * @freq: Frequency
+ *
+ * Return: level corresponding to the frequency.
+ */
+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 > table[i].frequency)
+ break;
+ }
+ rcu_read_unlock();
+
+ return cpufreq_cdev->max_level - i - 1;
+}
+
+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 > table[i].frequency)
+ break;
+ }
+
+ power_mw = table[i + 1].power;
+ power_mw /= MICROWATT_PER_MILLIWATT;
+ rcu_read_unlock();
+
+ return power_mw;
+}
+
+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 = table[i].power;
+ em_power_mw /= MICROWATT_PER_MILLIWATT;
+ if (power >= em_power_mw)
+ break;
+ }
+ freq = table[i].frequency;
+ rcu_read_unlock();
+
+ return freq;
+}
+
+/**
+ * get_load() - get load for a cpu
+ * @cpufreq_cdev: struct cpufreq_cooling_device for the cpu
+ * @cpu: cpu number
+ * @cpu_idx: index of the cpu in time_in_idle array
+ *
+ * Return: The average load of cpu @cpu in percentage since this
+ * function was last called.
+ */
+#ifdef CONFIG_SMP
+static u32 get_load(struct cpufreq_cooling_device *cpufreq_cdev, int cpu,
+ int cpu_idx)
+{
+ unsigned long util = sched_cpu_util(cpu);
+
+ return (util * 100) / arch_scale_cpu_capacity(cpu);
+}
+#else /* !CONFIG_SMP */
+static u32 get_load(struct cpufreq_cooling_device *cpufreq_cdev, int cpu,
+ int cpu_idx)
+{
+ u32 load;
+ u64 now, now_idle, delta_time, delta_idle;
+ struct time_in_idle *idle_time = &cpufreq_cdev->idle_time[cpu_idx];
+
+ now_idle = get_cpu_idle_time(cpu, &now, 0);
+ delta_idle = now_idle - idle_time->time;
+ delta_time = now - idle_time->timestamp;
+
+ if (delta_time <= delta_idle)
+ load = 0;
+ else
+ load = div64_u64(100 * (delta_time - delta_idle), delta_time);
+
+ idle_time->time = now_idle;
+ idle_time->timestamp = now;
+
+ return load;
+}
+#endif /* CONFIG_SMP */
+
+/**
+ * get_dynamic_power() - calculate the dynamic power
+ * @cpufreq_cdev: &cpufreq_cooling_device for this cdev
+ * @freq: current frequency
+ *
+ * Return: the dynamic power consumed by the cpus described by
+ * @cpufreq_cdev.
+ */
+static u32 get_dynamic_power(struct cpufreq_cooling_device *cpufreq_cdev,
+ unsigned long freq)
+{
+ u32 raw_cpu_power;
+
+ raw_cpu_power = cpu_freq_to_power(cpufreq_cdev, freq);
+ return (raw_cpu_power * cpufreq_cdev->last_load) / 100;
+}
+
+/**
+ * cpufreq_get_requested_power() - get the current power
+ * @cdev: &thermal_cooling_device pointer
+ * @power: pointer in which to store the resulting power
+ *
+ * Calculate the current power consumption of the cpus in milliwatts
+ * and store it in @power. This function should actually calculate
+ * the requested power, but it's hard to get the frequency that
+ * cpufreq would have assigned if there were no thermal limits.
+ * Instead, we calculate the current power on the assumption that the
+ * immediate future will look like the immediate past.
+ *
+ * We use the current frequency and the average load since this
+ * function was last called. In reality, there could have been
+ * multiple opps since this function was last called and that affects
+ * the load calculation. While it's not perfectly accurate, this
+ * simplification is good enough and works. REVISIT this, as more
+ * complex code may be needed if experiments show that it's not
+ * accurate enough.
+ *
+ * Return: 0 on success, this function doesn't fail.
+ */
+static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
+ u32 *power)
+{
+ unsigned long freq;
+ int i = 0, cpu;
+ u32 total_load = 0;
+ struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
+ struct cpufreq_policy *policy = cpufreq_cdev->policy;
+
+ freq = cpufreq_quick_get(policy->cpu);
+
+ for_each_cpu(cpu, policy->related_cpus) {
+ u32 load;
+
+ if (cpu_online(cpu))
+ load = get_load(cpufreq_cdev, cpu, i);
+ else
+ load = 0;
+
+ total_load += load;
+ }
+
+ cpufreq_cdev->last_load = total_load;
+
+ *power = get_dynamic_power(cpufreq_cdev, freq);
+
+ trace_thermal_power_cpu_get_power_simple(policy->cpu, *power);
+
+ return 0;
+}
+
+/**
+ * cpufreq_state2power() - convert a cpu cdev state to power consumed
+ * @cdev: &thermal_cooling_device pointer
+ * @state: cooling device state to be converted
+ * @power: pointer in which to store the resulting power
+ *
+ * Convert cooling device state @state into power consumption in
+ * milliwatts assuming 100% load. Store the calculated power in
+ * @power.
+ *
+ * Return: 0 on success, -EINVAL if the cooling device state is bigger
+ * than maximum allowed.
+ */
+static int cpufreq_state2power(struct thermal_cooling_device *cdev,
+ unsigned long state, u32 *power)
+{
+ 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)
+ return -EINVAL;
+
+ num_cpus = cpumask_weight(cpufreq_cdev->policy->cpus);
+
+ idx = cpufreq_cdev->max_level - state;
+
+ 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;
+}
+
+/**
+ * cpufreq_power2state() - convert power to a cooling device state
+ * @cdev: &thermal_cooling_device pointer
+ * @power: power in milliwatts to be converted
+ * @state: pointer in which to store the resulting state
+ *
+ * Calculate a cooling device state for the cpus described by @cdev
+ * that would allow them to consume at most @power mW and store it in
+ * @state. Note that this calculation depends on external factors
+ * such as the CPUs load. Calling this function with the same power
+ * as input can yield different cooling device states depending on those
+ * external factors.
+ *
+ * Return: 0 on success, this function doesn't fail.
+ */
+static int cpufreq_power2state(struct thermal_cooling_device *cdev,
+ u32 power, unsigned long *state)
+{
+ unsigned int target_freq;
+ u32 last_load, normalised_power;
+ struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
+ struct cpufreq_policy *policy = cpufreq_cdev->policy;
+
+ last_load = cpufreq_cdev->last_load ?: 1;
+ normalised_power = (power * 100) / last_load;
+ target_freq = cpu_power_to_freq(cpufreq_cdev, normalised_power);
+
+ *state = get_level(cpufreq_cdev, target_freq);
+ trace_thermal_power_cpu_limit(policy->related_cpus, target_freq, *state,
+ power);
+ return 0;
+}
+
+static inline bool em_is_sane(struct cpufreq_cooling_device *cpufreq_cdev,
+ struct em_perf_domain *em) {
+ struct cpufreq_policy *policy;
+ unsigned int nr_levels;
+
+ if (!em || em_is_artificial(em))
+ return false;
+
+ policy = cpufreq_cdev->policy;
+ if (!cpumask_equal(policy->related_cpus, em_span_cpus(em))) {
+ pr_err("The span of pd %*pbl is misaligned with cpufreq policy %*pbl\n",
+ cpumask_pr_args(em_span_cpus(em)),
+ cpumask_pr_args(policy->related_cpus));
+ return false;
+ }
+
+ nr_levels = cpufreq_cdev->max_level + 1;
+ if (em_pd_nr_perf_states(em) != nr_levels) {
+ pr_err("The number of performance states in pd %*pbl (%u) doesn't match the number of cooling levels (%u)\n",
+ cpumask_pr_args(em_span_cpus(em)),
+ em_pd_nr_perf_states(em), nr_levels);
+ return false;
+ }
+
+ return true;
+}
+#endif /* CONFIG_THERMAL_GOV_POWER_ALLOCATOR */
+
+#ifdef CONFIG_SMP
+static inline int allocate_idle_time(struct cpufreq_cooling_device *cpufreq_cdev)
+{
+ return 0;
+}
+
+static inline void free_idle_time(struct cpufreq_cooling_device *cpufreq_cdev)
+{
+}
+#else
+static int allocate_idle_time(struct cpufreq_cooling_device *cpufreq_cdev)
+{
+ unsigned int num_cpus = cpumask_weight(cpufreq_cdev->policy->related_cpus);
+
+ cpufreq_cdev->idle_time = kzalloc_objs(*cpufreq_cdev->idle_time,
+ num_cpus);
+ if (!cpufreq_cdev->idle_time)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void free_idle_time(struct cpufreq_cooling_device *cpufreq_cdev)
+{
+ kfree(cpufreq_cdev->idle_time);
+ cpufreq_cdev->idle_time = NULL;
+}
+#endif /* CONFIG_SMP */
+
+static unsigned int get_state_freq(struct cpufreq_cooling_device *cpufreq_cdev,
+ unsigned long state)
+{
+ struct cpufreq_policy *policy;
+ unsigned long idx;
+
+#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;
+
+ rcu_read_lock();
+ table = em_perf_state_from_pd(cpufreq_cdev->em);
+ freq = table[idx].frequency;
+ rcu_read_unlock();
+
+ return freq;
+ }
+#endif
+
+ /* Otherwise, fallback on the CPUFreq table */
+ policy = cpufreq_cdev->policy;
+ if (policy->freq_table_sorted == CPUFREQ_TABLE_SORTED_ASCENDING)
+ idx = cpufreq_cdev->max_level - state;
+ else
+ idx = state;
+
+ return policy->freq_table[idx].frequency;
+}
+
+/* cpufreq cooling device callback functions are defined below */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ *
+ * Callback for the thermal cooling device to return the cpufreq
+ * max cooling state.
+ *
+ * Return: 0 on success, this function doesn't fail.
+ */
+static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
+
+ *state = cpufreq_cdev->max_level;
+ return 0;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling state.
+ *
+ * Callback for the thermal cooling device to return the cpufreq
+ * current cooling state.
+ *
+ * Return: 0 on success, this function doesn't fail.
+ */
+static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
+
+ *state = cpufreq_cdev->cpufreq_state;
+
+ return 0;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ *
+ * Callback for the thermal cooling device to change the cpufreq
+ * current cooling state.
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
+ unsigned int frequency;
+ int ret;
+
+ /* Request state should be less than max_level */
+ if (state > cpufreq_cdev->max_level)
+ return -EINVAL;
+
+ /* Check if the old cooling action is same as new cooling action */
+ if (cpufreq_cdev->cpufreq_state == state)
+ return 0;
+
+ frequency = get_state_freq(cpufreq_cdev, state);
+
+ ret = freq_qos_update_request(&cpufreq_cdev->qos_req, frequency);
+ if (ret >= 0) {
+ cpufreq_cdev->cpufreq_state = state;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/**
+ * __cpufreq_cooling_register - helper function to create cpufreq cooling device
+ * @np: a valid struct device_node to the cooling device tree node
+ * @policy: cpufreq policy
+ * Normally this should be same as cpufreq policy->related_cpus.
+ * @em: Energy Model of the cpufreq policy
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "cpufreq-%s". This API can support multiple instances of cpufreq
+ * cooling devices. It also gives the opportunity to link the cooling device
+ * with a device tree node, in order to bind it via the thermal DT code.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+static struct thermal_cooling_device *
+__cpufreq_cooling_register(struct device_node *np,
+ struct cpufreq_policy *policy,
+ struct em_perf_domain *em)
+{
+ struct thermal_cooling_device *cdev;
+ struct cpufreq_cooling_device *cpufreq_cdev;
+ unsigned int i;
+ struct device *dev;
+ int ret;
+ struct thermal_cooling_device_ops *cooling_ops;
+ char *name;
+
+ if (IS_ERR_OR_NULL(policy)) {
+ pr_err("%s: cpufreq policy isn't valid: %p\n", __func__, policy);
+ return ERR_PTR(-EINVAL);
+ }
+
+ dev = get_cpu_device(policy->cpu);
+ if (unlikely(!dev)) {
+ pr_warn("No cpu device for cpu %d\n", policy->cpu);
+ return ERR_PTR(-ENODEV);
+ }
+
+ i = cpufreq_table_count_valid_entries(policy);
+ if (!i) {
+ pr_debug("%s: CPUFreq table not found or has no valid entries\n",
+ __func__);
+ return ERR_PTR(-ENODEV);
+ }
+
+ cpufreq_cdev = kzalloc_obj(*cpufreq_cdev);
+ if (!cpufreq_cdev)
+ return ERR_PTR(-ENOMEM);
+
+ cpufreq_cdev->policy = policy;
+
+ ret = allocate_idle_time(cpufreq_cdev);
+ if (ret) {
+ cdev = ERR_PTR(ret);
+ goto free_cdev;
+ }
+
+ /* max_level is an index, not a counter */
+ cpufreq_cdev->max_level = i - 1;
+
+ cooling_ops = &cpufreq_cdev->cooling_ops;
+ cooling_ops->get_max_state = cpufreq_get_max_state;
+ cooling_ops->get_cur_state = cpufreq_get_cur_state;
+ cooling_ops->set_cur_state = cpufreq_set_cur_state;
+
+#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
+ if (em_is_sane(cpufreq_cdev, em)) {
+ cpufreq_cdev->em = em;
+ cooling_ops->get_requested_power = cpufreq_get_requested_power;
+ cooling_ops->state2power = cpufreq_state2power;
+ cooling_ops->power2state = cpufreq_power2state;
+ } else
+#endif
+ if (policy->freq_table_sorted == CPUFREQ_TABLE_UNSORTED) {
+ pr_err("%s: unsorted frequency tables are not supported\n",
+ __func__);
+ cdev = ERR_PTR(-EINVAL);
+ goto free_idle_time;
+ }
+
+ ret = freq_qos_add_request(&policy->constraints,
+ &cpufreq_cdev->qos_req, FREQ_QOS_MAX,
+ get_state_freq(cpufreq_cdev, 0));
+ if (ret < 0) {
+ pr_err("%s: Failed to add freq constraint (%d)\n", __func__,
+ ret);
+ cdev = ERR_PTR(ret);
+ goto free_idle_time;
+ }
+
+ cdev = ERR_PTR(-ENOMEM);
+ name = kasprintf(GFP_KERNEL, "cpufreq-%s", dev_name(dev));
+ if (!name)
+ goto remove_qos_req;
+
+ cdev = thermal_of_cooling_device_register(np, 0, name, cpufreq_cdev,
+ cooling_ops);
+ kfree(name);
+
+ if (IS_ERR(cdev))
+ goto remove_qos_req;
+
+ return cdev;
+
+remove_qos_req:
+ freq_qos_remove_request(&cpufreq_cdev->qos_req);
+free_idle_time:
+ free_idle_time(cpufreq_cdev);
+free_cdev:
+ kfree(cpufreq_cdev);
+ return cdev;
+}
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @policy: cpufreq policy
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "cpufreq-%s". This API can support multiple instances of cpufreq cooling
+ * devices.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+cpufreq_cooling_register(struct cpufreq_policy *policy)
+{
+ return __cpufreq_cooling_register(NULL, policy, NULL);
+}
+EXPORT_SYMBOL_GPL(cpufreq_cooling_register);
+
+/**
+ * of_cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @policy: cpufreq policy
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "cpufreq-%s". This API can support multiple instances of cpufreq cooling
+ * devices. Using this API, the cpufreq cooling device will be linked to the
+ * device tree node provided.
+ *
+ * Using this function, the cooling device will implement the power
+ * extensions by using the Energy Model (if present). The cpus must have
+ * registered their OPPs using the OPP library.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * and NULL on failure.
+ */
+struct thermal_cooling_device *
+of_cpufreq_cooling_register(struct cpufreq_policy *policy)
+{
+ struct device_node *np = of_get_cpu_node(policy->cpu, NULL);
+ struct thermal_cooling_device *cdev = NULL;
+
+ if (!np) {
+ pr_err("cpufreq_cooling: OF node not available for cpu%d\n",
+ policy->cpu);
+ return NULL;
+ }
+
+ if (of_property_present(np, "#cooling-cells")) {
+ struct em_perf_domain *em = em_cpu_get(policy->cpu);
+
+ cdev = __cpufreq_cooling_register(np, policy, em);
+ if (IS_ERR(cdev)) {
+ pr_err("cpufreq_cooling: cpu%d failed to register as cooling device: %ld\n",
+ policy->cpu, PTR_ERR(cdev));
+ cdev = NULL;
+ }
+ }
+
+ of_node_put(np);
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ *
+ * This interface function unregisters the "cpufreq-%x" cooling device.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct cpufreq_cooling_device *cpufreq_cdev;
+
+ if (!cdev)
+ return;
+
+ cpufreq_cdev = cdev->devdata;
+
+ thermal_cooling_device_unregister(cdev);
+ freq_qos_remove_request(&cpufreq_cdev->qos_req);
+ free_idle_time(cpufreq_cdev);
+ kfree(cpufreq_cdev);
+}
+EXPORT_SYMBOL_GPL(cpufreq_cooling_unregister);
diff --git a/drivers/thermal/cpuidle_cooling.c b/drivers/thermal/cpuidle_cooling.c
new file mode 100644
index 000000000..bbd2e91cf
--- /dev/null
+++ b/drivers/thermal/cpuidle_cooling.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Linaro Limited.
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#define pr_fmt(fmt) "cpuidle cooling: " fmt
+
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+#include <linux/cpuidle.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/idle_inject.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+/**
+ * struct cpuidle_cooling_device - data for the idle cooling device
+ * @ii_dev: an atomic to keep track of the last task exiting the idle cycle
+ * @state: a normalized integer giving the state of the cooling device
+ */
+struct cpuidle_cooling_device {
+ struct idle_inject_device *ii_dev;
+ unsigned long state;
+};
+
+/**
+ * cpuidle_cooling_runtime - Running time computation
+ * @idle_duration_us: CPU idle time to inject in microseconds
+ * @state: a percentile based number
+ *
+ * The running duration is computed from the idle injection duration
+ * which is fixed. If we reach 100% of idle injection ratio, that
+ * means the running duration is zero. If we have a 50% ratio
+ * injection, that means we have equal duration for idle and for
+ * running duration.
+ *
+ * The formula is deduced as follows:
+ *
+ * running = idle x ((100 / ratio) - 1)
+ *
+ * For precision purpose for integer math, we use the following:
+ *
+ * running = (idle x 100) / ratio - idle
+ *
+ * For example, if we have an injected duration of 50%, then we end up
+ * with 10ms of idle injection and 10ms of running duration.
+ *
+ * Return: An unsigned int for a usec based runtime duration.
+ */
+static unsigned int cpuidle_cooling_runtime(unsigned int idle_duration_us,
+ unsigned long state)
+{
+ if (!state)
+ return 0;
+
+ return ((idle_duration_us * 100) / state) - idle_duration_us;
+}
+
+/**
+ * cpuidle_cooling_get_max_state - Get the maximum state
+ * @cdev : the thermal cooling device
+ * @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 across different platforms.
+ *
+ * Return: The function can not fail, it is always zero
+ */
+static int cpuidle_cooling_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ /*
+ * Depending on the configuration or the hardware, the running
+ * cycle and the idle cycle could be different. We want to
+ * unify that to an 0..100 interval, so the set state
+ * interface will be the same whatever the platform is.
+ *
+ * The state 100% will make the cluster 100% ... idle. A 0%
+ * injection ratio means no idle injection at all and 50%
+ * means for 10ms of idle injection, we have 10ms of running
+ * time.
+ */
+ *state = 100;
+
+ return 0;
+}
+
+/**
+ * cpuidle_cooling_get_cur_state - Get the current cooling state
+ * @cdev: the thermal cooling device
+ * @state: a pointer to the state
+ *
+ * The function just copies the state value from the private thermal
+ * cooling device structure, the mapping is 1 <-> 1.
+ *
+ * Return: The function can not fail, it is always zero
+ */
+static int cpuidle_cooling_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct cpuidle_cooling_device *idle_cdev = cdev->devdata;
+
+ *state = idle_cdev->state;
+
+ return 0;
+}
+
+/**
+ * cpuidle_cooling_set_cur_state - Set the current cooling state
+ * @cdev: the thermal cooling device
+ * @state: the target state
+ *
+ * The function checks first if we are initiating the mitigation which
+ * in turn wakes up all the idle injection tasks belonging to the idle
+ * cooling device. In any case, it updates the internal state for the
+ * cooling device.
+ *
+ * Return: The function can not fail, it is always zero
+ */
+static int cpuidle_cooling_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct cpuidle_cooling_device *idle_cdev = cdev->devdata;
+ struct idle_inject_device *ii_dev = idle_cdev->ii_dev;
+ unsigned long current_state = idle_cdev->state;
+ unsigned int runtime_us, idle_duration_us;
+
+ idle_cdev->state = state;
+
+ idle_inject_get_duration(ii_dev, &runtime_us, &idle_duration_us);
+
+ runtime_us = cpuidle_cooling_runtime(idle_duration_us, state);
+
+ idle_inject_set_duration(ii_dev, runtime_us, idle_duration_us);
+
+ if (current_state == 0 && state > 0) {
+ idle_inject_start(ii_dev);
+ } else if (current_state > 0 && !state) {
+ idle_inject_stop(ii_dev);
+ }
+
+ return 0;
+}
+
+/*
+ * cpuidle_cooling_ops - thermal cooling device ops
+ */
+static struct thermal_cooling_device_ops cpuidle_cooling_ops = {
+ .get_max_state = cpuidle_cooling_get_max_state,
+ .get_cur_state = cpuidle_cooling_get_cur_state,
+ .set_cur_state = cpuidle_cooling_set_cur_state,
+};
+
+/**
+ * __cpuidle_cooling_register: register the cooling device
+ * @drv: a cpuidle driver structure pointer
+ * @np: a device node structure pointer used for the thermal binding
+ *
+ * This function is in charge of allocating the cpuidle cooling device
+ * structure, the idle injection, initialize them and register the
+ * cooling device to the thermal framework.
+ *
+ * Return: zero on success, a negative value returned by one of the
+ * underlying subsystem in case of error
+ */
+static int __cpuidle_cooling_register(struct device_node *np,
+ struct cpuidle_driver *drv)
+{
+ struct idle_inject_device *ii_dev;
+ struct cpuidle_cooling_device *idle_cdev;
+ struct thermal_cooling_device *cdev;
+ struct device *dev;
+ unsigned int idle_duration_us = TICK_USEC;
+ unsigned int latency_us = UINT_MAX;
+ char *name;
+ int ret;
+
+ idle_cdev = kzalloc_obj(*idle_cdev);
+ if (!idle_cdev) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ii_dev = idle_inject_register(drv->cpumask);
+ if (!ii_dev) {
+ ret = -EINVAL;
+ goto out_kfree;
+ }
+
+ of_property_read_u32(np, "duration-us", &idle_duration_us);
+ of_property_read_u32(np, "exit-latency-us", &latency_us);
+
+ idle_inject_set_duration(ii_dev, TICK_USEC, idle_duration_us);
+ idle_inject_set_latency(ii_dev, latency_us);
+
+ idle_cdev->ii_dev = ii_dev;
+
+ dev = get_cpu_device(cpumask_first(drv->cpumask));
+
+ name = kasprintf(GFP_KERNEL, "idle-%s", dev_name(dev));
+ if (!name) {
+ ret = -ENOMEM;
+ goto out_unregister;
+ }
+
+ cdev = thermal_of_cooling_device_register(np, 0, name, idle_cdev,
+ &cpuidle_cooling_ops);
+ if (IS_ERR(cdev)) {
+ ret = PTR_ERR(cdev);
+ goto out_kfree_name;
+ }
+
+ pr_debug("%s: Idle injection set with idle duration=%u, latency=%u\n",
+ name, idle_duration_us, latency_us);
+
+ kfree(name);
+
+ return 0;
+
+out_kfree_name:
+ kfree(name);
+out_unregister:
+ idle_inject_unregister(ii_dev);
+out_kfree:
+ kfree(idle_cdev);
+out:
+ return ret;
+}
+
+/**
+ * cpuidle_cooling_register - Idle cooling device initialization function
+ * @drv: a cpuidle driver structure pointer
+ *
+ * This function is in charge of creating a cooling device per cpuidle
+ * driver and register it to the thermal framework.
+ */
+void cpuidle_cooling_register(struct cpuidle_driver *drv)
+{
+ struct device_node *cooling_node;
+ struct device_node *cpu_node;
+ int cpu, ret;
+
+ for_each_cpu(cpu, drv->cpumask) {
+
+ cpu_node = of_cpu_device_node_get(cpu);
+
+ cooling_node = of_get_child_by_name(cpu_node, "thermal-idle");
+
+ of_node_put(cpu_node);
+
+ if (!cooling_node) {
+ pr_debug("'thermal-idle' node not found for cpu%d\n", cpu);
+ continue;
+ }
+
+ ret = __cpuidle_cooling_register(cooling_node, drv);
+
+ of_node_put(cooling_node);
+
+ if (ret) {
+ pr_err("Failed to register the cpuidle cooling device" \
+ "for cpu%d: %d\n", cpu, ret);
+ break;
+ }
+ }
+}
diff --git a/drivers/thermal/da9062-thermal.c b/drivers/thermal/da9062-thermal.c
new file mode 100644
index 000000000..a8d4b766b
--- /dev/null
+++ b/drivers/thermal/da9062-thermal.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Thermal device driver for DA9062 and DA9061
+ * Copyright (C) 2017 Dialog Semiconductor
+ */
+
+/* When over-temperature is reached, an interrupt from the device will be
+ * triggered. Following this event the interrupt will be disabled and
+ * periodic transmission of uevents (HOT trip point) should define the
+ * first level of temperature supervision. It is expected that any final
+ * implementation of the thermal driver will include a .notify() function
+ * to implement these uevents to userspace.
+ *
+ * These uevents are intended to indicate non-invasive temperature control
+ * of the system, where the necessary measures for cooling are the
+ * responsibility of the host software. Once the temperature falls again,
+ * the IRQ is re-enabled so the start of a new over-temperature event can
+ * be detected without constant software monitoring.
+ */
+
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/da9062/core.h>
+#include <linux/mfd/da9062/registers.h>
+
+/* Minimum, maximum and default polling millisecond periods are provided
+ * here as an example. It is expected that any final implementation to also
+ * include a modification of these settings to match the required
+ * application.
+ */
+#define DA9062_DEFAULT_POLLING_MS_PERIOD 3000
+#define DA9062_MAX_POLLING_MS_PERIOD 10000
+#define DA9062_MIN_POLLING_MS_PERIOD 1000
+
+#define DA9062_MILLI_CELSIUS(t) ((t) * 1000)
+
+static unsigned int pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD;
+
+struct da9062_thermal_config {
+ const char *name;
+};
+
+struct da9062_thermal {
+ struct da9062 *hw;
+ struct delayed_work work;
+ struct thermal_zone_device *zone;
+ struct mutex lock; /* protection for da9062_thermal temperature */
+ int temperature;
+ int irq;
+ const struct da9062_thermal_config *config;
+ struct device *dev;
+};
+
+static void da9062_thermal_poll_on(struct work_struct *work)
+{
+ struct da9062_thermal *thermal = container_of(work,
+ struct da9062_thermal,
+ work.work);
+ unsigned long delay;
+ unsigned int val;
+ int ret;
+
+ /* clear E_TEMP */
+ ret = regmap_write(thermal->hw->regmap,
+ DA9062AA_EVENT_B,
+ DA9062AA_E_TEMP_MASK);
+ if (ret < 0) {
+ dev_err(thermal->dev,
+ "Cannot clear the TJUNC temperature status\n");
+ goto err_enable_irq;
+ }
+
+ /* Now read E_TEMP again: it is acting like a status bit.
+ * If over-temperature, then this status will be true.
+ * If not over-temperature, this status will be false.
+ */
+ ret = regmap_read(thermal->hw->regmap,
+ DA9062AA_EVENT_B,
+ &val);
+ if (ret < 0) {
+ dev_err(thermal->dev,
+ "Cannot check the TJUNC temperature status\n");
+ goto err_enable_irq;
+ }
+
+ if (val & DA9062AA_E_TEMP_MASK) {
+ mutex_lock(&thermal->lock);
+ thermal->temperature = DA9062_MILLI_CELSIUS(125);
+ mutex_unlock(&thermal->lock);
+ thermal_zone_device_update(thermal->zone,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ /*
+ * pp_tmp is between 1s and 10s, so we can round the jiffies
+ */
+ delay = round_jiffies(msecs_to_jiffies(pp_tmp));
+ queue_delayed_work(system_freezable_wq, &thermal->work, delay);
+ return;
+ }
+
+ mutex_lock(&thermal->lock);
+ thermal->temperature = DA9062_MILLI_CELSIUS(0);
+ mutex_unlock(&thermal->lock);
+ thermal_zone_device_update(thermal->zone,
+ THERMAL_EVENT_UNSPECIFIED);
+
+err_enable_irq:
+ enable_irq(thermal->irq);
+}
+
+static irqreturn_t da9062_thermal_irq_handler(int irq, void *data)
+{
+ struct da9062_thermal *thermal = data;
+
+ disable_irq_nosync(thermal->irq);
+ queue_delayed_work(system_freezable_wq, &thermal->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static int da9062_thermal_get_temp(struct thermal_zone_device *z,
+ int *temp)
+{
+ struct da9062_thermal *thermal = thermal_zone_device_priv(z);
+
+ mutex_lock(&thermal->lock);
+ *temp = thermal->temperature;
+ mutex_unlock(&thermal->lock);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops da9062_thermal_ops = {
+ .get_temp = da9062_thermal_get_temp,
+};
+
+static struct thermal_trip trips[] = {
+ { .temperature = DA9062_MILLI_CELSIUS(125), .type = THERMAL_TRIP_HOT },
+};
+
+static const struct da9062_thermal_config da9062_config = {
+ .name = "da9062-thermal",
+};
+
+static const struct of_device_id da9062_compatible_reg_id_table[] = {
+ { .compatible = "dlg,da9062-thermal", .data = &da9062_config },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, da9062_compatible_reg_id_table);
+
+static int da9062_thermal_probe(struct platform_device *pdev)
+{
+ struct da9062 *chip = dev_get_drvdata(pdev->dev.parent);
+ struct da9062_thermal *thermal;
+ const struct of_device_id *match;
+ int ret = 0;
+
+ match = of_match_node(da9062_compatible_reg_id_table,
+ pdev->dev.of_node);
+ if (!match)
+ return -ENXIO;
+
+ if (pdev->dev.of_node) {
+ if (!of_property_read_u32(pdev->dev.of_node,
+ "polling-delay-passive",
+ &pp_tmp)) {
+ if (pp_tmp < DA9062_MIN_POLLING_MS_PERIOD ||
+ pp_tmp > DA9062_MAX_POLLING_MS_PERIOD) {
+ dev_warn(&pdev->dev,
+ "Out-of-range polling period %d ms\n",
+ pp_tmp);
+ pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD;
+ }
+ }
+ }
+
+ thermal = devm_kzalloc(&pdev->dev, sizeof(struct da9062_thermal),
+ GFP_KERNEL);
+ if (!thermal) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ thermal->config = match->data;
+ thermal->hw = chip;
+ thermal->dev = &pdev->dev;
+
+ INIT_DELAYED_WORK(&thermal->work, da9062_thermal_poll_on);
+ mutex_init(&thermal->lock);
+
+ thermal->zone = thermal_zone_device_register_with_trips(thermal->config->name,
+ trips, ARRAY_SIZE(trips), thermal,
+ &da9062_thermal_ops, NULL, pp_tmp,
+ 0);
+ if (IS_ERR(thermal->zone)) {
+ dev_err(&pdev->dev, "Cannot register thermal zone device\n");
+ ret = PTR_ERR(thermal->zone);
+ goto err;
+ }
+ ret = thermal_zone_device_enable(thermal->zone);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot enable thermal zone device\n");
+ goto err_zone;
+ }
+
+ dev_dbg(&pdev->dev,
+ "TJUNC temperature polling period set at %d ms\n", pp_tmp);
+
+ ret = platform_get_irq_byname(pdev, "THERMAL");
+ if (ret < 0)
+ goto err_zone;
+
+ thermal->irq = ret;
+
+ ret = request_threaded_irq(thermal->irq, NULL,
+ da9062_thermal_irq_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "THERMAL", thermal);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to request thermal device IRQ.\n");
+ goto err_zone;
+ }
+
+ platform_set_drvdata(pdev, thermal);
+ return 0;
+
+err_zone:
+ thermal_zone_device_unregister(thermal->zone);
+err:
+ return ret;
+}
+
+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);
+}
+
+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,
+ },
+};
+
+module_platform_driver(da9062_thermal_driver);
+
+MODULE_AUTHOR("Steve Twiss");
+MODULE_DESCRIPTION("Thermal TJUNC device driver for Dialog DA9062 and DA9061");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9062-thermal");
diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c
new file mode 100644
index 000000000..46005b476
--- /dev/null
+++ b/drivers/thermal/db8500_thermal.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * db8500_thermal.c - DB8500 Thermal Management Implementation
+ *
+ * Copyright (C) 2012 ST-Ericsson
+ * Copyright (C) 2012-2019 Linaro Ltd.
+ *
+ * Authors: Hongbo Zhang, Linus Walleij
+ */
+
+#include <linux/cpu_cooling.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/db8500-prcmu.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF
+#define PRCMU_DEFAULT_LOW_TEMP 0
+
+/**
+ * db8500_thermal_points - the interpolation points that trigger
+ * interrupts
+ */
+static const unsigned long db8500_thermal_points[] = {
+ 15000,
+ 20000,
+ 25000,
+ 30000,
+ 35000,
+ 40000,
+ 45000,
+ 50000,
+ 55000,
+ 60000,
+ 65000,
+ 70000,
+ 75000,
+ 80000,
+ /*
+ * This is where things start to get really bad for the
+ * SoC and the thermal zones should be set up to trigger
+ * critical temperature at 85000 mC so we don't get above
+ * this point.
+ */
+ 85000,
+ 90000,
+ 95000,
+ 100000,
+};
+
+struct db8500_thermal_zone {
+ struct thermal_zone_device *tz;
+ struct device *dev;
+ unsigned long interpolated_temp;
+ unsigned int cur_index;
+};
+
+/* Callback to get current temperature */
+static int db8500_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct db8500_thermal_zone *th = thermal_zone_device_priv(tz);
+
+ /*
+ * TODO: There is no PRCMU interface to get temperature data currently,
+ * so a pseudo temperature is returned , it works for thermal framework
+ * and this will be fixed when the PRCMU interface is available.
+ */
+ *temp = th->interpolated_temp;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops thdev_ops = {
+ .get_temp = db8500_thermal_get_temp,
+};
+
+static void db8500_thermal_update_config(struct db8500_thermal_zone *th,
+ unsigned int idx,
+ unsigned long next_low,
+ unsigned long next_high)
+{
+ db8500_prcmu_stop_temp_sense();
+
+ th->cur_index = idx;
+ th->interpolated_temp = (next_low + next_high)/2;
+
+ /*
+ * The PRCMU accept absolute temperatures in celsius so divide
+ * down the millicelsius with 1000
+ */
+ db8500_prcmu_config_hotmon((u8)(next_low / 1000), (u8)(next_high / 1000));
+ db8500_prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
+}
+
+static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
+{
+ struct db8500_thermal_zone *th = irq_data;
+ unsigned int idx = th->cur_index;
+ unsigned long next_low, next_high;
+
+ if (idx == 0)
+ /* Meaningless for thermal management, ignoring it */
+ return IRQ_HANDLED;
+
+ if (idx == 1) {
+ next_high = db8500_thermal_points[0];
+ next_low = PRCMU_DEFAULT_LOW_TEMP;
+ } else {
+ next_high = db8500_thermal_points[idx - 1];
+ next_low = db8500_thermal_points[idx - 2];
+ }
+ idx -= 1;
+
+ db8500_thermal_update_config(th, idx, next_low, next_high);
+ dev_dbg(th->dev,
+ "PRCMU set max %ld, min %ld\n", next_high, next_low);
+
+ thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
+{
+ struct db8500_thermal_zone *th = irq_data;
+ unsigned int idx = th->cur_index;
+ unsigned long next_low, next_high;
+ int num_points = ARRAY_SIZE(db8500_thermal_points);
+
+ if (idx < num_points - 1) {
+ next_high = db8500_thermal_points[idx+1];
+ next_low = db8500_thermal_points[idx];
+ idx += 1;
+
+ db8500_thermal_update_config(th, idx, next_low, next_high);
+
+ dev_dbg(th->dev,
+ "PRCMU set max %ld, min %ld\n", next_high, next_low);
+ } else if (idx == num_points - 1)
+ /* So we roof out 1 degree over the max point */
+ th->interpolated_temp = db8500_thermal_points[idx] + 1;
+
+ thermal_zone_device_update(th->tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+static int db8500_thermal_probe(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *th = NULL;
+ struct device *dev = &pdev->dev;
+ int low_irq, high_irq, ret = 0;
+
+ th = devm_kzalloc(dev, sizeof(*th), GFP_KERNEL);
+ if (!th)
+ return -ENOMEM;
+
+ th->dev = dev;
+
+ low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
+ if (low_irq < 0)
+ return low_irq;
+
+ ret = devm_request_threaded_irq(dev, low_irq, NULL,
+ prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ "dbx500_temp_low", th);
+ if (ret < 0)
+ return ret;
+
+ high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
+ if (high_irq < 0)
+ return high_irq;
+
+ ret = devm_request_threaded_irq(dev, high_irq, NULL,
+ prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ "dbx500_temp_high", th);
+ if (ret < 0)
+ return ret;
+
+ /* register of thermal sensor and get info from DT */
+ th->tz = devm_thermal_of_zone_register(dev, 0, th, &thdev_ops);
+ if (IS_ERR(th->tz)) {
+ dev_err(dev, "register thermal zone sensor failed\n");
+ return PTR_ERR(th->tz);
+ }
+ dev_info(dev, "thermal zone sensor registered\n");
+
+ /* Start measuring at the lowest point */
+ db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP,
+ db8500_thermal_points[0]);
+
+ platform_set_drvdata(pdev, th);
+
+ return 0;
+}
+
+static int db8500_thermal_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ db8500_prcmu_stop_temp_sense();
+
+ return 0;
+}
+
+static int db8500_thermal_resume(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *th = platform_get_drvdata(pdev);
+
+ /* Resume and start measuring at the lowest point */
+ db8500_thermal_update_config(th, 0, PRCMU_DEFAULT_LOW_TEMP,
+ db8500_thermal_points[0]);
+
+ return 0;
+}
+
+static const struct of_device_id db8500_thermal_match[] = {
+ { .compatible = "stericsson,db8500-thermal" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, db8500_thermal_match);
+
+static struct platform_driver db8500_thermal_driver = {
+ .driver = {
+ .name = "db8500-thermal",
+ .of_match_table = db8500_thermal_match,
+ },
+ .probe = db8500_thermal_probe,
+ .suspend = db8500_thermal_suspend,
+ .resume = db8500_thermal_resume,
+};
+
+module_platform_driver(db8500_thermal_driver);
+
+MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
+MODULE_DESCRIPTION("DB8500 thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c
new file mode 100644
index 000000000..0330a8112
--- /dev/null
+++ b/drivers/thermal/devfreq_cooling.c
@@ -0,0 +1,576 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * devfreq_cooling: Thermal cooling device implementation for devices using
+ * devfreq
+ *
+ * Copyright (C) 2014-2015 ARM Limited
+ *
+ * TODO:
+ * - If OPPs are added or removed after devfreq cooling has
+ * registered, the devfreq cooling won't react to it.
+ */
+
+#include <linux/devfreq.h>
+#include <linux/devfreq_cooling.h>
+#include <linux/energy_model.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "thermal_trace.h"
+
+#define SCALE_ERROR_MITIGATION 100
+
+/**
+ * struct devfreq_cooling_device - Devfreq cooling device
+ * devfreq_cooling_device registered.
+ * @cdev: Pointer to associated thermal cooling device.
+ * @cooling_ops: devfreq callbacks to thermal cooling device ops
+ * @devfreq: Pointer to associated devfreq device.
+ * @cooling_state: Current cooling state.
+ * @freq_table: Pointer to a table with the frequencies sorted in descending
+ * order. You can index the table by cooling device state
+ * @max_state: It is the last index, that is, one less than the number of the
+ * OPPs
+ * @power_ops: Pointer to devfreq_cooling_power, a more precised model.
+ * @res_util: Resource utilization scaling factor for the power.
+ * It is multiplied by 100 to minimize the error. It is used
+ * for estimation of the power budget instead of using
+ * 'utilization' (which is 'busy_time' / 'total_time').
+ * The 'res_util' range is from 100 to power * 100 for the
+ * corresponding 'state'.
+ * @capped_state: index to cooling state with in dynamic power budget
+ * @req_max_freq: PM QoS request for limiting the maximum frequency
+ * of the devfreq device.
+ * @em_pd: Energy Model for the associated Devfreq device
+ */
+struct devfreq_cooling_device {
+ struct thermal_cooling_device *cdev;
+ struct thermal_cooling_device_ops cooling_ops;
+ struct devfreq *devfreq;
+ unsigned long cooling_state;
+ u32 *freq_table;
+ size_t max_state;
+ struct devfreq_cooling_power *power_ops;
+ u32 res_util;
+ int capped_state;
+ struct dev_pm_qos_request req_max_freq;
+ struct em_perf_domain *em_pd;
+};
+
+static int devfreq_cooling_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+
+ *state = dfc->max_state;
+
+ return 0;
+}
+
+static int devfreq_cooling_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+
+ *state = dfc->cooling_state;
+
+ return 0;
+}
+
+static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ 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;
+
+ if (state == dfc->cooling_state)
+ return 0;
+
+ dev_dbg(dev, "Setting cooling state %lu\n", state);
+
+ if (state > dfc->max_state)
+ return -EINVAL;
+
+ if (dfc->em_pd) {
+ perf_idx = dfc->max_state - state;
+
+ 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];
+ }
+
+ dev_pm_qos_update_request(&dfc->req_max_freq,
+ DIV_ROUND_UP(freq, HZ_PER_KHZ));
+
+ dfc->cooling_state = state;
+
+ return 0;
+}
+
+/**
+ * get_perf_idx() - get the performance index corresponding to a frequency
+ * @em_pd: Pointer to device's Energy Model
+ * @freq: frequency in kHz
+ *
+ * Return: the performance index associated with the @freq, or
+ * -EINVAL if it wasn't found.
+ */
+static int get_perf_idx(struct em_perf_domain *em_pd, unsigned long freq)
+{
+ 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 (table[i].frequency != freq)
+ continue;
+
+ idx = i;
+ break;
+ }
+ rcu_read_unlock();
+
+ return idx;
+}
+
+static unsigned long get_voltage(struct devfreq *df, unsigned long freq)
+{
+ struct device *dev = df->dev.parent;
+ unsigned long voltage;
+ struct dev_pm_opp *opp;
+
+ opp = dev_pm_opp_find_freq_exact(dev, freq, true);
+ if (PTR_ERR(opp) == -ERANGE)
+ opp = dev_pm_opp_find_freq_exact(dev, freq, false);
+
+ if (IS_ERR(opp)) {
+ dev_err_ratelimited(dev, "Failed to find OPP for frequency %lu: %ld\n",
+ freq, PTR_ERR(opp));
+ return 0;
+ }
+
+ voltage = dev_pm_opp_get_voltage(opp) / 1000; /* mV */
+ dev_pm_opp_put(opp);
+
+ if (voltage == 0) {
+ dev_err_ratelimited(dev,
+ "Failed to get voltage for frequency %lu\n",
+ freq);
+ }
+
+ return voltage;
+}
+
+static void _normalize_load(struct devfreq_dev_status *status)
+{
+ if (status->total_time > 0xfffff) {
+ status->total_time >>= 10;
+ status->busy_time >>= 10;
+ }
+
+ status->busy_time <<= 10;
+ status->busy_time /= status->total_time ? : 1;
+
+ status->busy_time = status->busy_time ? : 1;
+ status->total_time = 1024;
+}
+
+static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cdev,
+ u32 *power)
+{
+ 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;
+ int res, perf_idx;
+
+ mutex_lock(&df->lock);
+ status = df->last_status;
+ mutex_unlock(&df->lock);
+
+ freq = status.current_frequency;
+
+ if (dfc->power_ops && dfc->power_ops->get_real_power) {
+ voltage = get_voltage(df, freq);
+ if (voltage == 0) {
+ res = -EINVAL;
+ goto fail;
+ }
+
+ res = dfc->power_ops->get_real_power(df, power, freq, voltage);
+ if (!res) {
+ state = dfc->max_state - dfc->capped_state;
+
+ /* Convert EM power into milli-Watts first */
+ 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;
+
+ if (*power > 1)
+ dfc->res_util /= *power;
+ } else {
+ goto fail;
+ }
+ } else {
+ /* Energy Model frequencies are in kHz */
+ perf_idx = get_perf_idx(dfc->em_pd, freq / 1000);
+ if (perf_idx < 0) {
+ res = -EAGAIN;
+ goto fail;
+ }
+
+ _normalize_load(&status);
+
+ /* Convert EM power into milli-Watts first */
+ 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;
+ *power >>= 10;
+ }
+
+ trace_thermal_power_devfreq_get_power(cdev, &status, freq, *power);
+
+ return 0;
+fail:
+ /* It is safe to set max in this case */
+ dfc->res_util = SCALE_ERROR_MITIGATION;
+ return res;
+}
+
+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;
+
+ 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;
+}
+
+static int devfreq_cooling_power2state(struct thermal_cooling_device *cdev,
+ u32 power, unsigned long *state)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+ 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;
+
+ mutex_lock(&df->lock);
+ status = df->last_status;
+ mutex_unlock(&df->lock);
+
+ freq = status.current_frequency;
+
+ if (dfc->power_ops && dfc->power_ops->get_real_power) {
+ /* Scale for resource utilization */
+ est_power = power * dfc->res_util;
+ est_power /= SCALE_ERROR_MITIGATION;
+ } else {
+ /* Scale dynamic power for utilization */
+ _normalize_load(&status);
+ est_power = power << 10;
+ est_power /= status.busy_time;
+ }
+
+ /*
+ * 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 = 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;
+
+ trace_thermal_power_devfreq_limit(cdev, freq, *state, power);
+ return 0;
+}
+
+/**
+ * devfreq_cooling_gen_tables() - Generate frequency table.
+ * @dfc: Pointer to devfreq cooling device.
+ * @num_opps: Number of OPPs
+ *
+ * Generate frequency table which holds the frequencies in descending
+ * order. That way its indexed by cooling device state. This is for
+ * compatibility with drivers which do not register Energy Model.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc,
+ int num_opps)
+{
+ struct devfreq *df = dfc->devfreq;
+ struct device *dev = df->dev.parent;
+ unsigned long freq;
+ int i;
+
+ dfc->freq_table = kcalloc(num_opps, sizeof(*dfc->freq_table),
+ GFP_KERNEL);
+ if (!dfc->freq_table)
+ return -ENOMEM;
+
+ for (i = 0, freq = ULONG_MAX; i < num_opps; i++, freq--) {
+ struct dev_pm_opp *opp;
+
+ opp = dev_pm_opp_find_freq_floor(dev, &freq);
+ if (IS_ERR(opp)) {
+ kfree(dfc->freq_table);
+ return PTR_ERR(opp);
+ }
+
+ dev_pm_opp_put(opp);
+ dfc->freq_table[i] = freq;
+ }
+
+ return 0;
+}
+
+/**
+ * of_devfreq_cooling_register_power() - Register devfreq cooling device,
+ * with OF and power information.
+ * @np: Pointer to OF device_node.
+ * @df: Pointer to devfreq device.
+ * @dfc_power: Pointer to devfreq_cooling_power.
+ *
+ * Register a devfreq cooling device. The available OPPs must be
+ * registered on the device.
+ *
+ * If @dfc_power is provided, the cooling device is registered with the
+ * power extensions. For the power extensions to work correctly,
+ * devfreq should use the simple_ondemand governor, other governors
+ * are not currently supported.
+ */
+struct thermal_cooling_device *
+of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
+ struct devfreq_cooling_power *dfc_power)
+{
+ struct thermal_cooling_device *cdev;
+ struct device *dev = df->dev.parent;
+ struct devfreq_cooling_device *dfc;
+ struct em_perf_domain *em;
+ struct thermal_cooling_device_ops *ops;
+ char *name;
+ int err, num_opps;
+
+
+ dfc = kzalloc_obj(*dfc);
+ if (!dfc)
+ return ERR_PTR(-ENOMEM);
+
+ dfc->devfreq = df;
+
+ ops = &dfc->cooling_ops;
+ ops->get_max_state = devfreq_cooling_get_max_state;
+ ops->get_cur_state = devfreq_cooling_get_cur_state;
+ ops->set_cur_state = devfreq_cooling_set_cur_state;
+
+ em = em_pd_get(dev);
+ if (em && !em_is_artificial(em)) {
+ dfc->em_pd = em;
+ ops->get_requested_power =
+ devfreq_cooling_get_requested_power;
+ ops->state2power = devfreq_cooling_state2power;
+ ops->power2state = devfreq_cooling_power2state;
+
+ dfc->power_ops = dfc_power;
+
+ num_opps = em_pd_nr_perf_states(dfc->em_pd);
+ } else {
+ /* Backward compatibility for drivers which do not use IPA */
+ dev_dbg(dev, "missing proper EM for cooling device\n");
+
+ num_opps = dev_pm_opp_get_opp_count(dev);
+
+ err = devfreq_cooling_gen_tables(dfc, num_opps);
+ if (err)
+ goto free_dfc;
+ }
+
+ if (num_opps <= 0) {
+ err = -EINVAL;
+ goto free_dfc;
+ }
+
+ /* max_state is an index, not a counter */
+ dfc->max_state = num_opps - 1;
+
+ err = dev_pm_qos_add_request(dev, &dfc->req_max_freq,
+ DEV_PM_QOS_MAX_FREQUENCY,
+ PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
+ if (err < 0)
+ goto free_table;
+
+ err = -ENOMEM;
+ name = kasprintf(GFP_KERNEL, "devfreq-%s", dev_name(dev));
+ if (!name)
+ goto remove_qos_req;
+
+ cdev = thermal_of_cooling_device_register(np, 0, name, dfc, ops);
+ kfree(name);
+
+ if (IS_ERR(cdev)) {
+ err = PTR_ERR(cdev);
+ dev_err(dev,
+ "Failed to register devfreq cooling device (%d)\n",
+ err);
+ goto remove_qos_req;
+ }
+
+ dfc->cdev = cdev;
+
+ return cdev;
+
+remove_qos_req:
+ dev_pm_qos_remove_request(&dfc->req_max_freq);
+free_table:
+ if (!dfc->em_pd)
+ kfree(dfc->freq_table);
+free_dfc:
+ kfree(dfc);
+
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(of_devfreq_cooling_register_power);
+
+/**
+ * of_devfreq_cooling_register() - Register devfreq cooling device,
+ * with OF information.
+ * @np: Pointer to OF device_node.
+ * @df: Pointer to devfreq device.
+ */
+struct thermal_cooling_device *
+of_devfreq_cooling_register(struct device_node *np, struct devfreq *df)
+{
+ return of_devfreq_cooling_register_power(np, df, NULL);
+}
+EXPORT_SYMBOL_GPL(of_devfreq_cooling_register);
+
+/**
+ * devfreq_cooling_register() - Register devfreq cooling device.
+ * @df: Pointer to devfreq device.
+ */
+struct thermal_cooling_device *devfreq_cooling_register(struct devfreq *df)
+{
+ return of_devfreq_cooling_register(NULL, df);
+}
+EXPORT_SYMBOL_GPL(devfreq_cooling_register);
+
+/**
+ * devfreq_cooling_em_register() - Register devfreq cooling device with
+ * power information and automatically register Energy Model (EM)
+ * @df: Pointer to devfreq device.
+ * @dfc_power: Pointer to devfreq_cooling_power.
+ *
+ * Register a devfreq cooling device and automatically register EM. The
+ * available OPPs must be registered for the device.
+ *
+ * If @dfc_power is provided, the cooling device is registered with the
+ * power extensions. It is using the simple Energy Model which requires
+ * "dynamic-power-coefficient" a devicetree property. To not break drivers
+ * which miss that DT property, the function won't bail out when the EM
+ * registration failed. The cooling device will be registered if everything
+ * else is OK.
+ */
+struct thermal_cooling_device *
+devfreq_cooling_em_register(struct devfreq *df,
+ struct devfreq_cooling_power *dfc_power)
+{
+ struct thermal_cooling_device *cdev;
+ struct device *dev;
+ int ret;
+
+ if (IS_ERR_OR_NULL(df))
+ return ERR_PTR(-EINVAL);
+
+ dev = df->dev.parent;
+
+ ret = dev_pm_opp_of_register_em(dev, NULL);
+ if (ret)
+ dev_dbg(dev, "Unable to register EM for devfreq cooling device (%d)\n",
+ ret);
+
+ cdev = of_devfreq_cooling_register_power(dev->of_node, df, dfc_power);
+
+ if (IS_ERR_OR_NULL(cdev))
+ em_dev_unregister_perf_domain(dev);
+
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(devfreq_cooling_em_register);
+
+/**
+ * devfreq_cooling_unregister() - Unregister devfreq cooling device.
+ * @cdev: Pointer to devfreq cooling device to unregister.
+ *
+ * Unregisters devfreq cooling device and related Energy Model if it was
+ * present.
+ */
+void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct devfreq_cooling_device *dfc;
+ struct device *dev;
+
+ if (IS_ERR_OR_NULL(cdev))
+ return;
+
+ dfc = cdev->devdata;
+ dev = dfc->devfreq->dev.parent;
+
+ thermal_cooling_device_unregister(dfc->cdev);
+ dev_pm_qos_remove_request(&dfc->req_max_freq);
+
+ em_dev_unregister_perf_domain(dev);
+
+ kfree(dfc->freq_table);
+ kfree(dfc);
+}
+EXPORT_SYMBOL_GPL(devfreq_cooling_unregister);
diff --git a/drivers/thermal/dove_thermal.c b/drivers/thermal/dove_thermal.c
new file mode 100644
index 000000000..723bc72f0
--- /dev/null
+++ b/drivers/thermal/dove_thermal.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Dove thermal sensor driver
+ *
+ * Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch>
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#define DOVE_THERMAL_TEMP_OFFSET 1
+#define DOVE_THERMAL_TEMP_MASK 0x1FF
+
+/* Dove Thermal Manager Control and Status Register */
+#define PMU_TM_DISABLE_OFFS 0
+#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS)
+
+/* Dove Theraml Diode Control 0 Register */
+#define PMU_TDC0_SW_RST_MASK (0x1 << 1)
+#define PMU_TDC0_SEL_VCAL_OFFS 5
+#define PMU_TDC0_SEL_VCAL_MASK (0x3 << PMU_TDC0_SEL_VCAL_OFFS)
+#define PMU_TDC0_REF_CAL_CNT_OFFS 11
+#define PMU_TDC0_REF_CAL_CNT_MASK (0x1FF << PMU_TDC0_REF_CAL_CNT_OFFS)
+#define PMU_TDC0_AVG_NUM_OFFS 25
+#define PMU_TDC0_AVG_NUM_MASK (0x7 << PMU_TDC0_AVG_NUM_OFFS)
+
+/* Dove Thermal Diode Control 1 Register */
+#define PMU_TEMP_DIOD_CTRL1_REG 0x04
+#define PMU_TDC1_TEMP_VALID_MASK (0x1 << 10)
+
+/* Dove Thermal Sensor Dev Structure */
+struct dove_thermal_priv {
+ void __iomem *sensor;
+ void __iomem *control;
+};
+
+static int dove_init_sensor(const struct dove_thermal_priv *priv)
+{
+ u32 reg;
+ u32 i;
+
+ /* Configure the Diode Control Register #0 */
+ reg = readl_relaxed(priv->control);
+
+ /* Use average of 2 */
+ reg &= ~PMU_TDC0_AVG_NUM_MASK;
+ reg |= (0x1 << PMU_TDC0_AVG_NUM_OFFS);
+
+ /* Reference calibration value */
+ reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
+ reg |= (0x0F1 << PMU_TDC0_REF_CAL_CNT_OFFS);
+
+ /* Set the high level reference for calibration */
+ reg &= ~PMU_TDC0_SEL_VCAL_MASK;
+ reg |= (0x2 << PMU_TDC0_SEL_VCAL_OFFS);
+ writel(reg, priv->control);
+
+ /* Reset the sensor */
+ reg = readl_relaxed(priv->control);
+ writel((reg | PMU_TDC0_SW_RST_MASK), priv->control);
+ writel(reg, priv->control);
+
+ /* Enable the sensor */
+ reg = readl_relaxed(priv->sensor);
+ reg &= ~PMU_TM_DISABLE_MASK;
+ writel(reg, priv->sensor);
+
+ /* Poll the sensor for the first reading */
+ for (i = 0; i < 1000000; i++) {
+ reg = readl_relaxed(priv->sensor);
+ if (reg & DOVE_THERMAL_TEMP_MASK)
+ break;
+ }
+
+ if (i == 1000000)
+ return -EIO;
+
+ return 0;
+}
+
+static int dove_get_temp(struct thermal_zone_device *thermal,
+ int *temp)
+{
+ unsigned long reg;
+ struct dove_thermal_priv *priv = thermal_zone_device_priv(thermal);
+
+ /* Valid check */
+ reg = readl_relaxed(priv->control + PMU_TEMP_DIOD_CTRL1_REG);
+ if ((reg & PMU_TDC1_TEMP_VALID_MASK) == 0x0)
+ return -EIO;
+
+ /*
+ * Calculate temperature. According to Marvell internal
+ * documentation the formula for this is:
+ * Celsius = (322-reg)/1.3625
+ */
+ reg = readl_relaxed(priv->sensor);
+ reg = (reg >> DOVE_THERMAL_TEMP_OFFSET) & DOVE_THERMAL_TEMP_MASK;
+ *temp = ((3220000000UL - (10000000UL * reg)) / 13625);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops ops = {
+ .get_temp = dove_get_temp,
+};
+
+static const struct of_device_id dove_thermal_id_table[] = {
+ { .compatible = "marvell,dove-thermal" },
+ {}
+};
+
+static int dove_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *thermal = NULL;
+ struct dove_thermal_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->sensor = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(priv->sensor))
+ return PTR_ERR(priv->sensor);
+
+ priv->control = devm_platform_get_and_ioremap_resource(pdev, 1, NULL);
+ if (IS_ERR(priv->control))
+ return PTR_ERR(priv->control);
+
+ ret = dove_init_sensor(priv);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to initialize sensor\n");
+ return ret;
+ }
+
+ thermal = thermal_tripless_zone_device_register("dove_thermal", priv,
+ &ops, NULL);
+ if (IS_ERR(thermal)) {
+ dev_err(&pdev->dev,
+ "Failed to register thermal zone device\n");
+ return PTR_ERR(thermal);
+ }
+
+ ret = thermal_zone_device_enable(thermal);
+ if (ret) {
+ thermal_zone_device_unregister(thermal);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, thermal);
+
+ return 0;
+}
+
+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);
+}
+
+MODULE_DEVICE_TABLE(of, dove_thermal_id_table);
+
+static struct platform_driver dove_thermal_driver = {
+ .probe = dove_thermal_probe,
+ .remove = dove_thermal_exit,
+ .driver = {
+ .name = "dove_thermal",
+ .of_match_table = dove_thermal_id_table,
+ },
+};
+
+module_platform_driver(dove_thermal_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_DESCRIPTION("Dove thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/gov_bang_bang.c b/drivers/thermal/gov_bang_bang.c
new file mode 100644
index 000000000..51951967d
--- /dev/null
+++ b/drivers/thermal/gov_bang_bang.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * gov_bang_bang.c - A simple thermal throttling governor using hysteresis
+ *
+ * Copyright (C) 2014 Peter Kaestle <peter@piie.net>
+ *
+ * Based on step_wise.c with following Copyrights:
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * Regulation Logic: a two point regulation, deliver cooling state depending
+ * on the previous state shown in this diagram:
+ *
+ * Fan: OFF ON
+ *
+ * |
+ * |
+ * trip_temp: +---->+
+ * | | ^
+ * | | |
+ * | | Temperature
+ * (trip_temp - hyst): +<----+
+ * |
+ * |
+ * |
+ *
+ * * If the fan is not running and temperature exceeds trip_temp, the fan
+ * gets turned on.
+ * * In case the fan is running, temperature must fall below
+ * (trip_temp - hyst) so that the fan gets turned off again.
+ */
+
+#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;
+
+ lockdep_assert_held(&tz->lock);
+
+ 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;
+
+ /* If the code below has run already, nothing needs to be done. */
+ if (tz->governor_data)
+ return;
+
+ 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",
+ .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
new file mode 100644
index 000000000..4643be4f9
--- /dev/null
+++ b/drivers/thermal/gov_fair_share.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * fair_share.c - A simple weight based Thermal governor
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/thermal.h>
+#include "thermal_trace.h"
+
+#include "thermal_core.h"
+
+static int get_trip_level(struct thermal_zone_device *tz)
+{
+ 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++;
+
+ if (!level_td || td->threshold > level_td->threshold)
+ level_td = td;
+ }
+
+ /* Bail out if the temperature is not greater than any trips. */
+ if (trip_level < 0)
+ return 0;
+
+ trace_thermal_zone_trip(tz, thermal_zone_trip_id(tz, &level_td->trip),
+ level_td->trip.type);
+
+ return trip_level;
+}
+
+/**
+ * fair_share_throttle - throttles devices associated with the given zone
+ * @tz: thermal_zone_device
+ * @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. weight[i]/total_weight:
+ * How 'effective' the 'i'th device is, in cooling the given zone.
+ * 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.
+ * new_state of cooling device = P3 * P2 * P1
+ */
+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 nr_instances = 0;
+
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ total_weight += instance->weight;
+ nr_instances++;
+ }
+
+ 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);
+ }
+}
+
+static void fair_share_manage(struct thermal_zone_device *tz)
+{
+ int trip_level = get_trip_level(tz);
+ const struct thermal_trip_desc *td;
+
+ lockdep_assert_held(&tz->lock);
+
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
+
+ if (trip->temperature == THERMAL_TEMP_INVALID ||
+ trip->type == THERMAL_TRIP_CRITICAL ||
+ trip->type == THERMAL_TRIP_HOT)
+ continue;
+
+ fair_share_throttle(tz, td, trip_level);
+ }
+}
+
+static struct thermal_governor thermal_gov_fair_share = {
+ .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
new file mode 100644
index 000000000..37f2e22a9
--- /dev/null
+++ b/drivers/thermal/gov_power_allocator.c
@@ -0,0 +1,800 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A power allocator to manage temperature
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ */
+
+#define pr_fmt(fmt) "Power allocator: " fmt
+
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define CREATE_TRACE_POINTS
+#include "thermal_trace_ipa.h"
+
+#include "thermal_core.h"
+
+#define FRAC_BITS 10
+#define int_to_frac(x) ((x) << FRAC_BITS)
+#define frac_to_int(x) ((x) >> FRAC_BITS)
+
+/**
+ * mul_frac() - multiply two fixed-point numbers
+ * @x: first multiplicand
+ * @y: second multiplicand
+ *
+ * Return: the result of multiplying two fixed-point numbers. The
+ * result is also a fixed-point number.
+ */
+static inline s64 mul_frac(s64 x, s64 y)
+{
+ return (x * y) >> FRAC_BITS;
+}
+
+/**
+ * div_frac() - divide two fixed-point numbers
+ * @x: the dividend
+ * @y: the divisor
+ *
+ * Return: the result of dividing two fixed-point numbers. The
+ * result is also a fixed-point number.
+ */
+static inline s64 div_frac(s64 x, s64 y)
+{
+ return div_s64(x << FRAC_BITS, 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 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;
+ 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
+ *
+ * For thermal zones that don't provide a sustainable_power in their
+ * thermal_zone_params, estimate one. Calculate it using the minimum
+ * power of all the cooling devices as that gives a valid value that
+ * can give some degree of functionality. For optimal performance of
+ * this governor, provide a sustainable_power in the thermal zone's
+ * thermal_zone_params.
+ */
+static u32 estimate_sustainable_power(struct thermal_zone_device *tz)
+{
+ 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, &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;
+
+ sustainable_power += min_power;
+ }
+
+ return sustainable_power;
+}
+
+/**
+ * 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 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,
+ const struct thermal_trip *trip_switch_on,
+ int control_temp)
+{
+ u32 temperature_threshold = control_temp;
+ s32 k_i;
+
+ if (trip_switch_on)
+ temperature_threshold -= trip_switch_on->temperature;
+
+ /*
+ * estimate_pid_constants() tries to find appropriate default
+ * values for thermal zones that don't provide them. If a
+ * system integrator has configured a thermal zone with two
+ * passive trip points at the same temperature, that person
+ * hasn't put any effort to set up the thermal zone properly
+ * so just give up.
+ */
+ if (!temperature_threshold)
+ return;
+
+ tz->tzp->k_po = int_to_frac(sustainable_power) /
+ temperature_threshold;
+
+ tz->tzp->k_pu = int_to_frac(2 * sustainable_power) /
+ temperature_threshold;
+
+ k_i = tz->tzp->k_pu / 10;
+ tz->tzp->k_i = k_i > 0 ? k_i : 1;
+
+ /*
+ * The default for k_d and integral_cutoff is 0, so we can
+ * leave them as they are.
+ */
+}
+
+/**
+ * get_sustainable_power() - Get the right sustainable power
+ * @tz: thermal zone for which to estimate the constants
+ * @params: parameters for the power allocator governor
+ * @control_temp: target temperature for the power allocator governor
+ *
+ * This function is used for getting the proper sustainable power value based
+ * on variables which might be updated by the user sysfs interface. If that
+ * happen the new value is going to be estimated and updated. It is also used
+ * after thermal zone binding, where the initial values where set to 0.
+ */
+static u32 get_sustainable_power(struct thermal_zone_device *tz,
+ struct power_allocator_params *params,
+ int control_temp)
+{
+ u32 sustainable_power;
+
+ if (!tz->tzp->sustainable_power)
+ sustainable_power = estimate_sustainable_power(tz);
+ else
+ sustainable_power = tz->tzp->sustainable_power;
+
+ /* Check if it's init value 0 or there was update via sysfs */
+ if (sustainable_power != params->sustainable_power) {
+ estimate_pid_constants(tz, sustainable_power,
+ params->trip_switch_on, control_temp);
+
+ /* Do the estimation only once and make available in sysfs */
+ tz->tzp->sustainable_power = sustainable_power;
+ params->sustainable_power = sustainable_power;
+ }
+
+ return sustainable_power;
+}
+
+/**
+ * pid_controller() - PID controller
+ * @tz: thermal zone we are operating in
+ * @control_temp: the target temperature in millicelsius
+ * @max_allocatable_power: maximum allocatable power for this thermal zone
+ *
+ * This PID controller increases the available power budget so that the
+ * temperature of the thermal zone gets as close as possible to
+ * @control_temp and limits the power if it exceeds it. k_po is the
+ * proportional term when we are overshooting, k_pu is the
+ * proportional term when we are undershooting. integral_cutoff is a
+ * threshold below which we stop accumulating the error. The
+ * accumulated error is only valid if the requested power will make
+ * the system warmer. If the system is mostly idle, there's no point
+ * in accumulating positive error.
+ *
+ * Return: The power budget for the next period.
+ */
+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;
+
+ max_power_frac = int_to_frac(max_allocatable_power);
+
+ sustainable_power = get_sustainable_power(tz, params, control_temp);
+
+ err = control_temp - tz->temperature;
+ err = int_to_frac(err);
+
+ /* Calculate the proportional term */
+ p = mul_frac(err < 0 ? tz->tzp->k_po : tz->tzp->k_pu, err);
+
+ /*
+ * Calculate the integral term
+ *
+ * if the error is less than cut off allow integration (but
+ * the integral is limited to max power)
+ */
+ i = mul_frac(tz->tzp->k_i, params->err_integral);
+
+ if (err < int_to_frac(tz->tzp->integral_cutoff)) {
+ s64 i_next = i + mul_frac(tz->tzp->k_i, err);
+
+ if (abs(i_next) < max_power_frac) {
+ i = i_next;
+ params->err_integral += err;
+ }
+ }
+
+ /*
+ * Calculate the derivative term
+ *
+ * We do err - prev_err, so with a positive k_d, a decreasing
+ * error (i.e. driving closer to the line) results in less
+ * power being applied, slowing down the controller)
+ */
+ d = mul_frac(tz->tzp->k_d, err - params->prev_err);
+ d = div_frac(d, jiffies_to_msecs(tz->passive_delay_jiffies));
+ params->prev_err = err;
+
+ power_range = p + i + d;
+
+ /* feed-forward the known sustainable dissipatable power */
+ power_range = sustainable_power + frac_to_int(power_range);
+
+ power_range = clamp(power_range, (s64)0, (s64)max_allocatable_power);
+
+ trace_thermal_power_allocator_pid(tz, frac_to_int(err),
+ frac_to_int(params->err_integral),
+ frac_to_int(p), frac_to_int(i),
+ frac_to_int(d), power_range);
+
+ return power_range;
+}
+
+/**
+ * power_actor_set_power() - limit the maximum power a cooling device consumes
+ * @cdev: pointer to &thermal_cooling_device
+ * @instance: thermal instance to update
+ * @power: the power in milliwatts
+ *
+ * Set the cooling device to consume at most @power milliwatts. The limit is
+ * expected to be a cap at the maximum power consumption.
+ *
+ * Return: 0 on success, -EINVAL if the cooling device does not
+ * implement the power actor API or -E* for other failures.
+ */
+static int
+power_actor_set_power(struct thermal_cooling_device *cdev,
+ struct thermal_instance *instance, u32 power)
+{
+ unsigned long state;
+ int ret;
+
+ ret = cdev->ops->power2state(cdev, power, &state);
+ if (ret)
+ return ret;
+
+ instance->target = clamp_val(state, instance->lower, instance->upper);
+
+ thermal_cdev_update_nocheck(cdev);
+
+ return 0;
+}
+
+/**
+ * divvy_up_power() - divvy the allocated power between the actors
+ * @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
+ *
+ * This function divides the total allocated power (@power_range)
+ * fairly between the actors. It first tries to give each actor a
+ * share of the @power_range according to how much power it requested
+ * compared to the rest of the actors. For example, if only one actor
+ * requests power, then it receives all the @power_range. If
+ * three actors each requests 1mW, each receives a third of the
+ * @power_range.
+ *
+ * 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.
+ */
+static void divvy_up_power(struct power_actor *power, int num_actors,
+ u32 total_req_power, u32 power_range)
+{
+ u32 capped_extra_power = 0;
+ u32 extra_power = 0;
+ int i;
+
+ 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;
+ }
+
+ for (i = 0; i < num_actors; i++) {
+ struct power_actor *pa = &power[i];
+ u64 req_range = (u64)pa->weighted_req_power * power_range;
+
+ pa->granted_power = DIV_ROUND_CLOSEST_ULL(req_range,
+ total_req_power);
+
+ if (pa->granted_power > pa->max_power) {
+ extra_power += pa->granted_power - pa->max_power;
+ pa->granted_power = pa->max_power;
+ }
+
+ pa->extra_actor_power = pa->max_power - pa->granted_power;
+ capped_extra_power += pa->extra_actor_power;
+ }
+
+ if (!extra_power || !capped_extra_power)
+ return;
+
+ /*
+ * Re-divvy the reclaimed extra among actors based on
+ * how far they are from the max
+ */
+ extra_power = min(extra_power, 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 void allocate_power(struct thermal_zone_device *tz, int control_temp)
+{
+ struct power_allocator_params *params = tz->governor_data;
+ 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;
+
+ /* Clean all buffers for new power estimations */
+ memset(power, 0, params->buffer_size);
+
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ struct power_actor *pa = &power[i];
+
+ if (!power_actor_is_valid(instance))
+ continue;
+
+ cdev = instance->cdev;
+
+ ret = cdev->ops->get_requested_power(cdev, &pa->req_power);
+ if (ret)
+ continue;
+
+ if (!params->total_weight)
+ weight = 1 << FRAC_BITS;
+ else
+ weight = instance->weight;
+
+ pa->weighted_req_power = frac_to_int(weight * pa->req_power);
+
+ ret = cdev->ops->state2power(cdev, instance->lower,
+ &pa->max_power);
+ if (ret)
+ continue;
+
+ 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(power, num_actors, total_weighted_req_power,
+ power_range);
+
+ i = 0;
+ list_for_each_entry(instance, &td->thermal_instances, trip_node) {
+ struct power_actor *pa = &power[i];
+
+ if (!power_actor_is_valid(instance))
+ continue;
+
+ power_actor_set_power(instance->cdev, instance,
+ 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, total_req_power, total_granted_power,
+ num_actors, power_range,
+ max_allocatable_power, tz->temperature,
+ control_temp - tz->temperature);
+}
+
+/**
+ * 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
+ *
+ * The power allocator governor works optimally with two trips points:
+ * a "switch on" trip point and a "maximum desired temperature". These
+ * are defined as the first and last passive trip points.
+ *
+ * If there is only one trip point, then that's considered to be the
+ * "maximum desired temperature" trip point and the governor is always
+ * on. If there are no passive or active trip points, then the
+ * governor won't do anything. In fact, its throttle function
+ * won't be called at all.
+ */
+static void get_governor_trips(struct thermal_zone_device *tz,
+ struct power_allocator_params *params)
+{
+ 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;
+ }
+ last_passive = trip;
+ break;
+ case THERMAL_TRIP_ACTIVE:
+ last_active = trip;
+ break;
+ default:
+ break;
+ }
+ }
+
+ 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 = NULL;
+ params->trip_max = last_active;
+ }
+}
+
+static void reset_pid_controller(struct power_allocator_params *params)
+{
+ params->err_integral = 0;
+ params->prev_err = 0;
+}
+
+static void allow_maximum_power(struct thermal_zone_device *tz)
+{
+ 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, &td->thermal_instances, trip_node) {
+ if (!power_actor_is_valid(instance))
+ continue;
+
+ cdev = instance->cdev;
+
+ 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);
+ }
+ }
+}
+
+/**
+ * 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.
+ *
+ * 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,
+ struct power_allocator_params *params)
+{
+ const struct thermal_trip_desc *td;
+ struct thermal_instance *instance;
+ int ret = 0;
+
+ 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);
+ 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 = kzalloc_objs(struct power_actor, num_actors);
+ 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
+ *
+ * Initialize the PID controller parameters and bind it to the thermal
+ * zone.
+ *
+ * Return: 0 on success, or -ENOMEM if we ran out of memory, or -EINVAL
+ * when there are unsupported cooling devices in the @tz.
+ */
+static int power_allocator_bind(struct thermal_zone_device *tz)
+{
+ struct power_allocator_params *params;
+ int ret;
+
+ params = kzalloc_obj(*params);
+ 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_obj(*tz->tzp);
+ if (!tz->tzp) {
+ ret = -ENOMEM;
+ goto free_params;
+ }
+
+ params->allocated_tzp = true;
+ }
+
+ 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;
+
+ 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;
+}
+
+static void power_allocator_unbind(struct thermal_zone_device *tz)
+{
+ struct power_allocator_params *params = tz->governor_data;
+
+ dev_dbg(&tz->device, "Unbinding from thermal zone %d\n", tz->id);
+
+ if (params->allocated_tzp) {
+ kfree(tz->tzp);
+ tz->tzp = NULL;
+ }
+
+ kfree(params->power);
+ kfree(tz->governor_data);
+ tz->governor_data = NULL;
+}
+
+static void power_allocator_manage(struct thermal_zone_device *tz)
+{
+ struct power_allocator_params *params = tz->governor_data;
+ const struct thermal_trip *trip = params->trip_switch_on;
+
+ lockdep_assert_held(&tz->lock);
+
+ if (trip && tz->temperature < trip->temperature) {
+ reset_pid_controller(params);
+ allow_maximum_power(tz);
+ params->update_cdevs = false;
+ return;
+ }
+
+ if (!params->trip_max)
+ return;
+
+ 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,
+ .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
new file mode 100644
index 000000000..ea277c466
--- /dev/null
+++ b/drivers/thermal/gov_step_wise.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * step_wise.c - A step-by-step Thermal throttling governor
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/thermal.h>
+#include <linux/minmax.h>
+#include "thermal_trace.h"
+
+#include "thermal_core.h"
+
+/*
+ * If the temperature is higher than a trip point,
+ * a. if the trend is THERMAL_TREND_RAISING, use higher cooling
+ * state for this trip point
+ * b. if the trend is THERMAL_TREND_DROPPING, use 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 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,
+ enum thermal_trend trend, bool throttle)
+{
+ struct thermal_cooling_device *cdev = instance->cdev;
+ unsigned long cur_state;
+
+ /*
+ * We keep this instance the way it is by default.
+ * Otherwise, we use the current state of the
+ * cdev in use to determine the next_target.
+ */
+ cdev->ops->get_cur_state(cdev, &cur_state);
+ dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state);
+
+ if (!instance->initialized) {
+ if (throttle)
+ return clamp(cur_state + 1, instance->lower, instance->upper);
+
+ return THERMAL_NO_TARGET;
+ }
+
+ if (throttle) {
+ if (trend == THERMAL_TREND_RAISING)
+ 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 instance->target;
+}
+
+static void thermal_zone_trip_update(struct thermal_zone_device *tz,
+ const struct thermal_trip_desc *td,
+ int trip_threshold)
+{
+ 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;
+
+ 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_threshold, trend, throttle);
+
+ 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=%ld\n",
+ old_target, instance->target);
+
+ if (instance->initialized && old_target == instance->target)
+ continue;
+
+ instance->initialized = true;
+
+ scoped_guard(cooling_dev, instance->cdev) {
+ instance->cdev->updated = false; /* cdev needs update */
+ }
+ }
+}
+
+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);
+
+ /*
+ * 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;
+
+ if (trip->temperature == THERMAL_TEMP_INVALID ||
+ trip->type == THERMAL_TRIP_CRITICAL ||
+ trip->type == THERMAL_TRIP_HOT)
+ continue;
+
+ 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",
+ .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
new file mode 100644
index 000000000..ef95cf7d6
--- /dev/null
+++ b/drivers/thermal/gov_user_space.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * user_space.c - A simple user space Thermal events notifier
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "thermal_core.h"
+
+static int user_space_bind(struct thermal_zone_device *tz)
+{
+ pr_info_once("Consider using thermal netlink events interface\n");
+
+ return 0;
+}
+
+/**
+ * user_space_trip_crossed - Notify user space about trip crossing events
+ * @tz: thermal_zone_device
+ * @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 void user_space_trip_crossed(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ bool upward)
+{
+ char *thermal_prop[5];
+ int i;
+
+ lockdep_assert_held(&tz->lock);
+
+ thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", tz->type);
+ thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", tz->temperature);
+ thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=%d",
+ 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]);
+}
+
+static struct thermal_governor thermal_gov_user_space = {
+ .name = "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
new file mode 100644
index 000000000..17ed0c5b7
--- /dev/null
+++ b/drivers/thermal/hisi_thermal.c
@@ -0,0 +1,649 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HiSilicon thermal sensor driver
+ *
+ * Copyright (c) 2014-2015 HiSilicon Limited.
+ * Copyright (c) 2014-2015 Linaro Limited.
+ *
+ * Xinwei Kong <kong.kongxinwei@hisilicon.com>
+ * Leo Yan <leo.yan@linaro.org>
+ */
+
+#include <linux/cpufreq.h>
+#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/thermal.h>
+
+#define HI6220_TEMP0_LAG (0x0)
+#define HI6220_TEMP0_TH (0x4)
+#define HI6220_TEMP0_RST_TH (0x8)
+#define HI6220_TEMP0_CFG (0xC)
+#define HI6220_TEMP0_CFG_SS_MSK (0xF000)
+#define HI6220_TEMP0_CFG_HDAK_MSK (0x30)
+#define HI6220_TEMP0_EN (0x10)
+#define HI6220_TEMP0_INT_EN (0x14)
+#define HI6220_TEMP0_INT_CLR (0x18)
+#define HI6220_TEMP0_RST_MSK (0x1C)
+#define HI6220_TEMP0_VALUE (0x28)
+
+#define HI3660_OFFSET(chan) ((chan) * 0x40)
+#define HI3660_TEMP(chan) (HI3660_OFFSET(chan) + 0x1C)
+#define HI3660_TH(chan) (HI3660_OFFSET(chan) + 0x20)
+#define HI3660_LAG(chan) (HI3660_OFFSET(chan) + 0x28)
+#define HI3660_INT_EN(chan) (HI3660_OFFSET(chan) + 0x2C)
+#define HI3660_INT_CLR(chan) (HI3660_OFFSET(chan) + 0x30)
+
+#define HI6220_TEMP_BASE (-60000)
+#define HI6220_TEMP_RESET (100000)
+#define HI6220_TEMP_STEP (785)
+#define HI6220_TEMP_LAG (3500)
+
+#define HI3660_TEMP_BASE (-63780)
+#define HI3660_TEMP_STEP (205)
+#define HI3660_TEMP_LAG (4000)
+
+#define HI6220_CLUSTER0_SENSOR 2
+#define HI6220_CLUSTER1_SENSOR 1
+
+#define HI3660_LITTLE_SENSOR 0
+#define HI3660_BIG_SENSOR 1
+#define HI3660_G3D_SENSOR 2
+#define HI3660_MODEM_SENSOR 3
+
+struct hisi_thermal_data;
+
+struct hisi_thermal_sensor {
+ struct hisi_thermal_data *data;
+ struct thermal_zone_device *tzd;
+ const char *irq_name;
+ uint32_t id;
+ uint32_t thres_temp;
+};
+
+struct hisi_thermal_ops {
+ int (*get_temp)(struct hisi_thermal_sensor *sensor);
+ int (*enable_sensor)(struct hisi_thermal_sensor *sensor);
+ int (*disable_sensor)(struct hisi_thermal_sensor *sensor);
+ int (*irq_handler)(struct hisi_thermal_sensor *sensor);
+ int (*probe)(struct hisi_thermal_data *data);
+};
+
+struct hisi_thermal_data {
+ const struct hisi_thermal_ops *ops;
+ struct hisi_thermal_sensor *sensor;
+ struct platform_device *pdev;
+ struct clk *clk;
+ void __iomem *regs;
+ int nr_sensors;
+};
+
+/*
+ * The temperature computation on the tsensor is as follow:
+ * Unit: millidegree Celsius
+ * Step: 200/255 (0.7843)
+ * Temperature base: -60°C
+ *
+ * The register is programmed in temperature steps, every step is 785
+ * millidegree and begins at -60 000 m°C
+ *
+ * The temperature from the steps:
+ *
+ * Temp = TempBase + (steps x 785)
+ *
+ * and the steps from the temperature:
+ *
+ * steps = (Temp - TempBase) / 785
+ *
+ */
+static inline int hi6220_thermal_step_to_temp(int step)
+{
+ return HI6220_TEMP_BASE + (step * HI6220_TEMP_STEP);
+}
+
+static inline int hi6220_thermal_temp_to_step(int temp)
+{
+ return DIV_ROUND_UP(temp - HI6220_TEMP_BASE, HI6220_TEMP_STEP);
+}
+
+/*
+ * for Hi3660,
+ * Step: 189/922 (0.205)
+ * Temperature base: -63.780°C
+ *
+ * The register is programmed in temperature steps, every step is 205
+ * millidegree and begins at -63 780 m°C
+ */
+static inline int hi3660_thermal_step_to_temp(int step)
+{
+ return HI3660_TEMP_BASE + step * HI3660_TEMP_STEP;
+}
+
+static inline int hi3660_thermal_temp_to_step(int temp)
+{
+ return DIV_ROUND_UP(temp - HI3660_TEMP_BASE, HI3660_TEMP_STEP);
+}
+
+/*
+ * The lag register contains 5 bits encoding the temperature in steps.
+ *
+ * Each time the temperature crosses the threshold boundary, an
+ * interrupt is raised. It could be when the temperature is going
+ * above the threshold or below. However, if the temperature is
+ * fluctuating around this value due to the load, we can receive
+ * several interrupts which may not desired.
+ *
+ * We can setup a temperature representing the delta between the
+ * threshold and the current temperature when the temperature is
+ * decreasing.
+ *
+ * For instance: the lag register is 5°C, the threshold is 65°C, when
+ * the temperature reaches 65°C an interrupt is raised and when the
+ * temperature decrease to 65°C - 5°C another interrupt is raised.
+ *
+ * A very short lag can lead to an interrupt storm, a long lag
+ * increase the latency to react to the temperature changes. In our
+ * case, that is not really a problem as we are polling the
+ * temperature.
+ *
+ * [0:4] : lag register
+ *
+ * The temperature is coded in steps, cf. HI6220_TEMP_STEP.
+ *
+ * Min : 0x00 : 0.0 °C
+ * Max : 0x1F : 24.3 °C
+ *
+ * The 'value' parameter is in milliCelsius.
+ */
+static inline void hi6220_thermal_set_lag(void __iomem *addr, int value)
+{
+ writel(DIV_ROUND_UP(value, HI6220_TEMP_STEP) & 0x1F,
+ addr + HI6220_TEMP0_LAG);
+}
+
+static inline void hi6220_thermal_alarm_clear(void __iomem *addr, int value)
+{
+ writel(value, addr + HI6220_TEMP0_INT_CLR);
+}
+
+static inline void hi6220_thermal_alarm_enable(void __iomem *addr, int value)
+{
+ writel(value, addr + HI6220_TEMP0_INT_EN);
+}
+
+static inline void hi6220_thermal_alarm_set(void __iomem *addr, int temp)
+{
+ writel(hi6220_thermal_temp_to_step(temp) | 0x0FFFFFF00,
+ addr + HI6220_TEMP0_TH);
+}
+
+static inline void hi6220_thermal_reset_set(void __iomem *addr, int temp)
+{
+ writel(hi6220_thermal_temp_to_step(temp), addr + HI6220_TEMP0_RST_TH);
+}
+
+static inline void hi6220_thermal_reset_enable(void __iomem *addr, int value)
+{
+ writel(value, addr + HI6220_TEMP0_RST_MSK);
+}
+
+static inline void hi6220_thermal_enable(void __iomem *addr, int value)
+{
+ writel(value, addr + HI6220_TEMP0_EN);
+}
+
+static inline int hi6220_thermal_get_temperature(void __iomem *addr)
+{
+ return hi6220_thermal_step_to_temp(readl(addr + HI6220_TEMP0_VALUE));
+}
+
+/*
+ * [0:6] lag register
+ *
+ * The temperature is coded in steps, cf. HI3660_TEMP_STEP.
+ *
+ * Min : 0x00 : 0.0 °C
+ * Max : 0x7F : 26.0 °C
+ *
+ */
+static inline void hi3660_thermal_set_lag(void __iomem *addr,
+ int id, int value)
+{
+ writel(DIV_ROUND_UP(value, HI3660_TEMP_STEP) & 0x7F,
+ addr + HI3660_LAG(id));
+}
+
+static inline void hi3660_thermal_alarm_clear(void __iomem *addr,
+ int id, int value)
+{
+ writel(value, addr + HI3660_INT_CLR(id));
+}
+
+static inline void hi3660_thermal_alarm_enable(void __iomem *addr,
+ int id, int value)
+{
+ writel(value, addr + HI3660_INT_EN(id));
+}
+
+static inline void hi3660_thermal_alarm_set(void __iomem *addr,
+ int id, int value)
+{
+ writel(value, addr + HI3660_TH(id));
+}
+
+static inline int hi3660_thermal_get_temperature(void __iomem *addr, int id)
+{
+ return hi3660_thermal_step_to_temp(readl(addr + HI3660_TEMP(id)));
+}
+
+/*
+ * Temperature configuration register - Sensor selection
+ *
+ * Bits [19:12]
+ *
+ * 0x0: local sensor (default)
+ * 0x1: remote sensor 1 (ACPU cluster 1)
+ * 0x2: remote sensor 2 (ACPU cluster 0)
+ * 0x3: remote sensor 3 (G3D)
+ */
+static inline void hi6220_thermal_sensor_select(void __iomem *addr, int sensor)
+{
+ writel((readl(addr + HI6220_TEMP0_CFG) & ~HI6220_TEMP0_CFG_SS_MSK) |
+ (sensor << 12), addr + HI6220_TEMP0_CFG);
+}
+
+/*
+ * Temperature configuration register - Hdak conversion polling interval
+ *
+ * Bits [5:4]
+ *
+ * 0x0 : 0.768 ms
+ * 0x1 : 6.144 ms
+ * 0x2 : 49.152 ms
+ * 0x3 : 393.216 ms
+ */
+static inline void hi6220_thermal_hdak_set(void __iomem *addr, int value)
+{
+ writel((readl(addr + HI6220_TEMP0_CFG) & ~HI6220_TEMP0_CFG_HDAK_MSK) |
+ (value << 4), addr + HI6220_TEMP0_CFG);
+}
+
+static int hi6220_thermal_irq_handler(struct hisi_thermal_sensor *sensor)
+{
+ struct hisi_thermal_data *data = sensor->data;
+
+ hi6220_thermal_alarm_clear(data->regs, 1);
+ return 0;
+}
+
+static int hi3660_thermal_irq_handler(struct hisi_thermal_sensor *sensor)
+{
+ struct hisi_thermal_data *data = sensor->data;
+
+ hi3660_thermal_alarm_clear(data->regs, sensor->id, 1);
+ return 0;
+}
+
+static int hi6220_thermal_get_temp(struct hisi_thermal_sensor *sensor)
+{
+ struct hisi_thermal_data *data = sensor->data;
+
+ return hi6220_thermal_get_temperature(data->regs);
+}
+
+static int hi3660_thermal_get_temp(struct hisi_thermal_sensor *sensor)
+{
+ struct hisi_thermal_data *data = sensor->data;
+
+ return hi3660_thermal_get_temperature(data->regs, sensor->id);
+}
+
+static int hi6220_thermal_disable_sensor(struct hisi_thermal_sensor *sensor)
+{
+ struct hisi_thermal_data *data = sensor->data;
+
+ /* disable sensor module */
+ hi6220_thermal_enable(data->regs, 0);
+ hi6220_thermal_alarm_enable(data->regs, 0);
+ hi6220_thermal_reset_enable(data->regs, 0);
+
+ clk_disable_unprepare(data->clk);
+
+ return 0;
+}
+
+static int hi3660_thermal_disable_sensor(struct hisi_thermal_sensor *sensor)
+{
+ struct hisi_thermal_data *data = sensor->data;
+
+ /* disable sensor module */
+ hi3660_thermal_alarm_enable(data->regs, sensor->id, 0);
+ return 0;
+}
+
+static int hi6220_thermal_enable_sensor(struct hisi_thermal_sensor *sensor)
+{
+ struct hisi_thermal_data *data = sensor->data;
+ int ret;
+
+ /* enable clock for tsensor */
+ ret = clk_prepare_enable(data->clk);
+ if (ret)
+ return ret;
+
+ /* disable module firstly */
+ hi6220_thermal_reset_enable(data->regs, 0);
+ hi6220_thermal_enable(data->regs, 0);
+
+ /* select sensor id */
+ hi6220_thermal_sensor_select(data->regs, sensor->id);
+
+ /* setting the hdak time */
+ hi6220_thermal_hdak_set(data->regs, 0);
+
+ /* setting lag value between current temp and the threshold */
+ hi6220_thermal_set_lag(data->regs, HI6220_TEMP_LAG);
+
+ /* enable for interrupt */
+ hi6220_thermal_alarm_set(data->regs, sensor->thres_temp);
+
+ hi6220_thermal_reset_set(data->regs, HI6220_TEMP_RESET);
+
+ /* enable module */
+ hi6220_thermal_reset_enable(data->regs, 1);
+ hi6220_thermal_enable(data->regs, 1);
+
+ hi6220_thermal_alarm_clear(data->regs, 0);
+ hi6220_thermal_alarm_enable(data->regs, 1);
+
+ return 0;
+}
+
+static int hi3660_thermal_enable_sensor(struct hisi_thermal_sensor *sensor)
+{
+ unsigned int value;
+ struct hisi_thermal_data *data = sensor->data;
+
+ /* disable interrupt */
+ hi3660_thermal_alarm_enable(data->regs, sensor->id, 0);
+
+ /* setting lag value between current temp and the threshold */
+ hi3660_thermal_set_lag(data->regs, sensor->id, HI3660_TEMP_LAG);
+
+ /* set interrupt threshold */
+ value = hi3660_thermal_temp_to_step(sensor->thres_temp);
+ hi3660_thermal_alarm_set(data->regs, sensor->id, value);
+
+ /* enable interrupt */
+ hi3660_thermal_alarm_clear(data->regs, sensor->id, 1);
+ hi3660_thermal_alarm_enable(data->regs, sensor->id, 1);
+
+ return 0;
+}
+
+static int hi6220_thermal_probe(struct hisi_thermal_data *data)
+{
+ struct platform_device *pdev = data->pdev;
+ struct device *dev = &pdev->dev;
+
+ data->clk = devm_clk_get(dev, "thermal_clk");
+ 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)
+ return -ENOMEM;
+
+ data->sensor[0].id = HI6220_CLUSTER0_SENSOR;
+ data->sensor[0].irq_name = "tsensor_intr";
+ data->sensor[0].data = data;
+ data->nr_sensors = 1;
+
+ return 0;
+}
+
+static int hi3660_thermal_probe(struct hisi_thermal_data *data)
+{
+ struct platform_device *pdev = data->pdev;
+ struct device *dev = &pdev->dev;
+
+ data->nr_sensors = 1;
+
+ data->sensor = devm_kcalloc(dev, data->nr_sensors,
+ sizeof(*data->sensor), GFP_KERNEL);
+ if (!data->sensor)
+ return -ENOMEM;
+
+ data->sensor[0].id = HI3660_BIG_SENSOR;
+ data->sensor[0].irq_name = "tsensor_a73";
+ data->sensor[0].data = data;
+
+ return 0;
+}
+
+static int hisi_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct hisi_thermal_sensor *sensor = thermal_zone_device_priv(tz);
+ struct hisi_thermal_data *data = sensor->data;
+
+ *temp = data->ops->get_temp(sensor);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops hisi_of_thermal_ops = {
+ .get_temp = hisi_thermal_get_temp,
+};
+
+static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev)
+{
+ struct hisi_thermal_sensor *sensor = dev;
+ struct hisi_thermal_data *data = sensor->data;
+ int temp = 0;
+
+ data->ops->irq_handler(sensor);
+
+ temp = data->ops->get_temp(sensor);
+
+ if (temp >= sensor->thres_temp) {
+ dev_crit(&data->pdev->dev,
+ "sensor <%d> THERMAL ALARM: %d > %d\n",
+ sensor->id, temp, sensor->thres_temp);
+
+ thermal_zone_device_update(sensor->tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ } else {
+ dev_crit(&data->pdev->dev,
+ "sensor <%d> THERMAL ALARM stopped: %d < %d\n",
+ sensor->id, temp, sensor->thres_temp);
+ }
+
+ 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;
+
+ sensor->tzd = devm_thermal_of_zone_register(&pdev->dev,
+ sensor->id, sensor,
+ &hisi_of_thermal_ops);
+ if (IS_ERR(sensor->tzd)) {
+ ret = PTR_ERR(sensor->tzd);
+ sensor->tzd = NULL;
+ dev_err(&pdev->dev, "failed to register sensor id %d: %d\n",
+ sensor->id, ret);
+ return ret;
+ }
+
+ thermal_zone_for_each_trip(sensor->tzd, hisi_trip_walk_cb, sensor);
+
+ return 0;
+}
+
+static const struct hisi_thermal_ops hi6220_ops = {
+ .get_temp = hi6220_thermal_get_temp,
+ .enable_sensor = hi6220_thermal_enable_sensor,
+ .disable_sensor = hi6220_thermal_disable_sensor,
+ .irq_handler = hi6220_thermal_irq_handler,
+ .probe = hi6220_thermal_probe,
+};
+
+static const struct hisi_thermal_ops hi3660_ops = {
+ .get_temp = hi3660_thermal_get_temp,
+ .enable_sensor = hi3660_thermal_enable_sensor,
+ .disable_sensor = hi3660_thermal_disable_sensor,
+ .irq_handler = hi3660_thermal_irq_handler,
+ .probe = hi3660_thermal_probe,
+};
+
+static const struct of_device_id of_hisi_thermal_match[] = {
+ {
+ .compatible = "hisilicon,tsensor",
+ .data = &hi6220_ops,
+ },
+ {
+ .compatible = "hisilicon,hi3660-tsensor",
+ .data = &hi3660_ops,
+ },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_hisi_thermal_match);
+
+static void hisi_thermal_toggle_sensor(struct hisi_thermal_sensor *sensor,
+ bool on)
+{
+ struct thermal_zone_device *tzd = sensor->tzd;
+
+ if (on)
+ thermal_zone_device_enable(tzd);
+ else
+ thermal_zone_device_disable(tzd);
+}
+
+static int hisi_thermal_probe(struct platform_device *pdev)
+{
+ struct hisi_thermal_data *data;
+ struct device *dev = &pdev->dev;
+ int i, ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->pdev = pdev;
+ platform_set_drvdata(pdev, data);
+ data->ops = of_device_get_match_data(dev);
+
+ data->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(data->regs))
+ return PTR_ERR(data->regs);
+
+ ret = data->ops->probe(data);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < data->nr_sensors; i++) {
+ struct hisi_thermal_sensor *sensor = &data->sensor[i];
+
+ ret = hisi_thermal_register_sensor(pdev, sensor);
+ if (ret) {
+ dev_err(dev, "failed to register thermal sensor: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, ret, NULL,
+ hisi_thermal_alarm_irq_thread,
+ IRQF_ONESHOT, sensor->irq_name,
+ sensor);
+ if (ret < 0)
+ return ret;
+
+ ret = data->ops->enable_sensor(sensor);
+ if (ret) {
+ dev_err(dev, "Failed to setup the sensor: %d\n", ret);
+ return ret;
+ }
+
+ hisi_thermal_toggle_sensor(sensor, true);
+ }
+
+ return 0;
+}
+
+static void hisi_thermal_remove(struct platform_device *pdev)
+{
+ struct hisi_thermal_data *data = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < data->nr_sensors; i++) {
+ struct hisi_thermal_sensor *sensor = &data->sensor[i];
+
+ hisi_thermal_toggle_sensor(sensor, false);
+ data->ops->disable_sensor(sensor);
+ }
+}
+
+static int hisi_thermal_suspend(struct device *dev)
+{
+ struct hisi_thermal_data *data = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < data->nr_sensors; i++)
+ data->ops->disable_sensor(&data->sensor[i]);
+
+ return 0;
+}
+
+static int hisi_thermal_resume(struct device *dev)
+{
+ struct hisi_thermal_data *data = dev_get_drvdata(dev);
+ int i, ret = 0;
+
+ for (i = 0; i < data->nr_sensors; i++)
+ ret |= data->ops->enable_sensor(&data->sensor[i]);
+
+ return ret;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
+ hisi_thermal_suspend, hisi_thermal_resume);
+
+static struct platform_driver hisi_thermal_driver = {
+ .driver = {
+ .name = "hisi_thermal",
+ .pm = pm_sleep_ptr(&hisi_thermal_pm_ops),
+ .of_match_table = of_hisi_thermal_match,
+ },
+ .probe = hisi_thermal_probe,
+ .remove = hisi_thermal_remove,
+};
+
+module_platform_driver(hisi_thermal_driver);
+
+MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>");
+MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>");
+MODULE_DESCRIPTION("HiSilicon thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/imx8mm_thermal.c b/drivers/thermal/imx8mm_thermal.c
new file mode 100644
index 000000000..719d71f5b
--- /dev/null
+++ b/drivers/thermal/imx8mm_thermal.c
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 NXP.
+ *
+ * Author: Anson Huang <Anson.Huang@nxp.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "thermal_hwmon.h"
+
+#define TER 0x0 /* TMU enable */
+#define TPS 0x4
+#define TRITSR 0x20 /* TMU immediate temp */
+/* TMU calibration data registers */
+#define TASR 0x28
+#define TASR_BUF_SLOPE_MASK GENMASK(19, 16)
+#define TASR_BUF_VREF_MASK GENMASK(4, 0) /* TMU_V1 */
+#define TASR_BUF_VERF_SEL_MASK GENMASK(1, 0) /* TMU_V2 */
+#define TCALIV(n) (0x30 + ((n) * 4))
+#define TCALIV_EN BIT(31)
+#define TCALIV_HR_MASK GENMASK(23, 16) /* TMU_V1 */
+#define TCALIV_RT_MASK GENMASK(7, 0) /* TMU_V1 */
+#define TCALIV_SNSR105C_MASK GENMASK(27, 16) /* TMU_V2 */
+#define TCALIV_SNSR25C_MASK GENMASK(11, 0) /* TMU_V2 */
+#define TRIM 0x3c
+#define TRIM_BJT_CUR_MASK GENMASK(23, 20)
+#define TRIM_BGR_MASK GENMASK(31, 28)
+#define TRIM_VLSB_MASK GENMASK(15, 12)
+#define TRIM_EN_CH BIT(7)
+
+#define TER_ADC_PD BIT(30)
+#define TER_EN BIT(31)
+#define TRITSR_TEMP0_VAL_MASK GENMASK(7, 0)
+#define TRITSR_TEMP1_VAL_MASK GENMASK(23, 16)
+
+#define PROBE_SEL_ALL GENMASK(31, 30)
+
+#define probe_status_offset(x) (30 + x)
+#define SIGN_BIT BIT(7)
+#define TEMP_VAL_MASK GENMASK(6, 0)
+
+/* TMU OCOTP calibration data bitfields */
+#define ANA0_EN BIT(25)
+#define ANA0_BUF_VREF_MASK GENMASK(24, 20)
+#define ANA0_BUF_SLOPE_MASK GENMASK(19, 16)
+#define ANA0_HR_MASK GENMASK(15, 8)
+#define ANA0_RT_MASK GENMASK(7, 0)
+#define TRIM2_VLSB_MASK GENMASK(23, 20)
+#define TRIM2_BGR_MASK GENMASK(19, 16)
+#define TRIM2_BJT_CUR_MASK GENMASK(15, 12)
+#define TRIM2_BUF_SLOP_SEL_MASK GENMASK(11, 8)
+#define TRIM2_BUF_VERF_SEL_MASK GENMASK(7, 6)
+#define TRIM3_TCA25_0_LSB_MASK GENMASK(31, 28)
+#define TRIM3_TCA40_0_MASK GENMASK(27, 16)
+#define TRIM4_TCA40_1_MASK GENMASK(31, 20)
+#define TRIM4_TCA105_0_MASK GENMASK(19, 8)
+#define TRIM4_TCA25_0_MSB_MASK GENMASK(7, 0)
+#define TRIM5_TCA105_1_MASK GENMASK(23, 12)
+#define TRIM5_TCA25_1_MASK GENMASK(11, 0)
+
+#define VER1_TEMP_LOW_LIMIT 10000
+#define VER2_TEMP_LOW_LIMIT -40000
+#define VER2_TEMP_HIGH_LIMIT 125000
+
+#define TMU_VER1 0x1
+#define TMU_VER2 0x2
+
+struct thermal_soc_data {
+ u32 num_sensors;
+ u32 version;
+ int (*get_temp)(void *data, int *temp);
+};
+
+struct tmu_sensor {
+ struct imx8mm_tmu *priv;
+ u32 hw_id;
+ struct thermal_zone_device *tzd;
+};
+
+struct imx8mm_tmu {
+ void __iomem *base;
+ struct clk *clk;
+ const struct thermal_soc_data *socdata;
+ struct tmu_sensor sensors[];
+};
+
+static int imx8mm_tmu_get_temp(void *data, int *temp)
+{
+ struct tmu_sensor *sensor = data;
+ struct imx8mm_tmu *tmu = sensor->priv;
+ u32 val;
+
+ val = readl_relaxed(tmu->base + TRITSR) & TRITSR_TEMP0_VAL_MASK;
+
+ /*
+ * Do not validate against the V bit (bit 31) due to errata
+ * ERR051272: TMU: Bit 31 of registers TMU_TSCR/TMU_TRITSR/TMU_TRATSR invalid
+ */
+
+ *temp = val * 1000;
+ if (*temp < VER1_TEMP_LOW_LIMIT || *temp > VER2_TEMP_HIGH_LIMIT)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int imx8mp_tmu_get_temp(void *data, int *temp)
+{
+ struct tmu_sensor *sensor = data;
+ struct imx8mm_tmu *tmu = sensor->priv;
+ unsigned long val;
+ bool ready;
+
+ val = readl_relaxed(tmu->base + TRITSR);
+ ready = test_bit(probe_status_offset(sensor->hw_id), &val);
+ if (!ready)
+ return -EAGAIN;
+
+ val = sensor->hw_id ? FIELD_GET(TRITSR_TEMP1_VAL_MASK, val) :
+ FIELD_GET(TRITSR_TEMP0_VAL_MASK, val);
+ if (val & SIGN_BIT) /* negative */
+ val = (~(val & TEMP_VAL_MASK) + 1);
+
+ *temp = val * 1000;
+ if (*temp < VER2_TEMP_LOW_LIMIT || *temp > VER2_TEMP_HIGH_LIMIT)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int tmu_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct tmu_sensor *sensor = thermal_zone_device_priv(tz);
+ struct imx8mm_tmu *tmu = sensor->priv;
+
+ return tmu->socdata->get_temp(sensor, temp);
+}
+
+static const struct thermal_zone_device_ops tmu_tz_ops = {
+ .get_temp = tmu_get_temp,
+};
+
+static void imx8mm_tmu_enable(struct imx8mm_tmu *tmu, bool enable)
+{
+ u32 val;
+
+ val = readl_relaxed(tmu->base + TER);
+ val = enable ? (val | TER_EN) : (val & ~TER_EN);
+ if (tmu->socdata->version == TMU_VER2)
+ val = enable ? (val & ~TER_ADC_PD) : (val | TER_ADC_PD);
+ writel_relaxed(val, tmu->base + TER);
+}
+
+static void imx8mm_tmu_probe_sel_all(struct imx8mm_tmu *tmu)
+{
+ u32 val;
+
+ val = readl_relaxed(tmu->base + TPS);
+ val |= PROBE_SEL_ALL;
+ writel_relaxed(val, tmu->base + TPS);
+}
+
+static int imx8mm_tmu_probe_set_calib_v1(struct platform_device *pdev,
+ struct imx8mm_tmu *tmu)
+{
+ struct device *dev = &pdev->dev;
+ u32 ana0;
+ int ret;
+
+ ret = nvmem_cell_read_u32(&pdev->dev, "calib", &ana0);
+ 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)) |
+ FIELD_PREP(TASR_BUF_SLOPE_MASK,
+ FIELD_GET(ANA0_BUF_SLOPE_MASK, ana0)),
+ tmu->base + TASR);
+
+ writel(FIELD_PREP(TCALIV_RT_MASK, FIELD_GET(ANA0_RT_MASK, ana0)) |
+ FIELD_PREP(TCALIV_HR_MASK, FIELD_GET(ANA0_HR_MASK, ana0)) |
+ ((ana0 & ANA0_EN) ? TCALIV_EN : 0),
+ tmu->base + TCALIV(0));
+
+ return 0;
+}
+
+static int imx8mm_tmu_probe_set_calib_v2(struct platform_device *pdev,
+ struct imx8mm_tmu *tmu)
+{
+ struct device *dev = &pdev->dev;
+ struct nvmem_cell *cell;
+ u32 trim[4] = { 0 };
+ size_t len;
+ void *buf;
+
+ cell = nvmem_cell_get(dev, "calib");
+ 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);
+
+ memcpy(trim, buf, min(len, sizeof(trim)));
+ kfree(buf);
+
+ if (len != 16) {
+ dev_err(dev,
+ "OCOTP nvmem cell length is %zu, must be 16.\n", len);
+ return -EINVAL;
+ }
+
+ /* Blank sample hardware */
+ if (!trim[0] && !trim[1] && !trim[2] && !trim[3]) {
+ /* Use a default 25C binary codes */
+ writel(FIELD_PREP(TCALIV_SNSR25C_MASK, 0x63c),
+ tmu->base + TCALIV(0));
+ writel(FIELD_PREP(TCALIV_SNSR25C_MASK, 0x63c),
+ tmu->base + TCALIV(1));
+ return 0;
+ }
+
+ writel(FIELD_PREP(TASR_BUF_VERF_SEL_MASK,
+ FIELD_GET(TRIM2_BUF_VERF_SEL_MASK, trim[0])) |
+ FIELD_PREP(TASR_BUF_SLOPE_MASK,
+ FIELD_GET(TRIM2_BUF_SLOP_SEL_MASK, trim[0])),
+ tmu->base + TASR);
+
+ writel(FIELD_PREP(TRIM_BJT_CUR_MASK,
+ FIELD_GET(TRIM2_BJT_CUR_MASK, trim[0])) |
+ FIELD_PREP(TRIM_BGR_MASK, FIELD_GET(TRIM2_BGR_MASK, trim[0])) |
+ FIELD_PREP(TRIM_VLSB_MASK, FIELD_GET(TRIM2_VLSB_MASK, trim[0])) |
+ TRIM_EN_CH,
+ tmu->base + TRIM);
+
+ writel(FIELD_PREP(TCALIV_SNSR25C_MASK,
+ FIELD_GET(TRIM3_TCA25_0_LSB_MASK, trim[1]) |
+ (FIELD_GET(TRIM4_TCA25_0_MSB_MASK, trim[2]) << 4)) |
+ FIELD_PREP(TCALIV_SNSR105C_MASK,
+ FIELD_GET(TRIM4_TCA105_0_MASK, trim[2])),
+ tmu->base + TCALIV(0));
+
+ writel(FIELD_PREP(TCALIV_SNSR25C_MASK,
+ FIELD_GET(TRIM5_TCA25_1_MASK, trim[3])) |
+ FIELD_PREP(TCALIV_SNSR105C_MASK,
+ FIELD_GET(TRIM5_TCA105_1_MASK, trim[3])),
+ tmu->base + TCALIV(1));
+
+ writel(FIELD_PREP(TCALIV_SNSR25C_MASK,
+ FIELD_GET(TRIM3_TCA40_0_MASK, trim[1])) |
+ FIELD_PREP(TCALIV_SNSR105C_MASK,
+ FIELD_GET(TRIM4_TCA40_1_MASK, trim[2])),
+ tmu->base + TCALIV(2));
+
+ return 0;
+}
+
+static int imx8mm_tmu_probe_set_calib(struct platform_device *pdev,
+ struct imx8mm_tmu *tmu)
+{
+ struct device *dev = &pdev->dev;
+
+ /*
+ * Lack of calibration data OCOTP reference is not considered
+ * fatal to retain compatibility with old DTs. It is however
+ * strongly recommended to update such old DTs to get correct
+ * temperature compensation values for each SoC.
+ */
+ if (!of_property_present(pdev->dev.of_node, "nvmem-cells")) {
+ dev_warn(dev,
+ "No OCOTP nvmem reference found, SoC-specific calibration not loaded. Please update your DT.\n");
+ return 0;
+ }
+
+ if (tmu->socdata->version == TMU_VER1)
+ return imx8mm_tmu_probe_set_calib_v1(pdev, tmu);
+
+ return imx8mm_tmu_probe_set_calib_v2(pdev, tmu);
+}
+
+static int imx8mm_tmu_probe(struct platform_device *pdev)
+{
+ const struct thermal_soc_data *data;
+ struct imx8mm_tmu *tmu;
+ int ret;
+ int i;
+
+ data = of_device_get_match_data(&pdev->dev);
+
+ tmu = devm_kzalloc(&pdev->dev, struct_size(tmu, sensors,
+ data->num_sensors), GFP_KERNEL);
+ if (!tmu)
+ return -ENOMEM;
+
+ tmu->socdata = data;
+
+ tmu->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(tmu->base))
+ return PTR_ERR(tmu->base);
+
+ tmu->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(tmu->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(tmu->clk),
+ "failed to get tmu clock\n");
+
+ ret = clk_prepare_enable(tmu->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable tmu clock: %d\n", ret);
+ return ret;
+ }
+
+ /* disable the monitor during initialization */
+ imx8mm_tmu_enable(tmu, false);
+
+ for (i = 0; i < data->num_sensors; i++) {
+ tmu->sensors[i].priv = tmu;
+ tmu->sensors[i].tzd =
+ devm_thermal_of_zone_register(&pdev->dev, i,
+ &tmu->sensors[i],
+ &tmu_tz_ops);
+ if (IS_ERR(tmu->sensors[i].tzd)) {
+ ret = PTR_ERR(tmu->sensors[i].tzd);
+ dev_err(&pdev->dev,
+ "failed to register thermal zone sensor[%d]: %d\n",
+ i, ret);
+ goto disable_clk;
+ }
+ tmu->sensors[i].hw_id = i;
+
+ devm_thermal_add_hwmon_sysfs(&pdev->dev, tmu->sensors[i].tzd);
+ }
+
+ platform_set_drvdata(pdev, tmu);
+
+ ret = imx8mm_tmu_probe_set_calib(pdev, tmu);
+ if (ret)
+ goto disable_clk;
+
+ /* enable all the probes for V2 TMU */
+ if (tmu->socdata->version == TMU_VER2)
+ imx8mm_tmu_probe_sel_all(tmu);
+
+ /* enable the monitor */
+ imx8mm_tmu_enable(tmu, true);
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(tmu->clk);
+ return ret;
+}
+
+static void imx8mm_tmu_remove(struct platform_device *pdev)
+{
+ struct imx8mm_tmu *tmu = platform_get_drvdata(pdev);
+
+ /* disable TMU */
+ imx8mm_tmu_enable(tmu, false);
+
+ clk_disable_unprepare(tmu->clk);
+ platform_set_drvdata(pdev, NULL);
+}
+
+static struct thermal_soc_data imx8mm_tmu_data = {
+ .num_sensors = 1,
+ .version = TMU_VER1,
+ .get_temp = imx8mm_tmu_get_temp,
+};
+
+static struct thermal_soc_data imx8mp_tmu_data = {
+ .num_sensors = 2,
+ .version = TMU_VER2,
+ .get_temp = imx8mp_tmu_get_temp,
+};
+
+static const struct of_device_id imx8mm_tmu_table[] = {
+ { .compatible = "fsl,imx8mm-tmu", .data = &imx8mm_tmu_data, },
+ { .compatible = "fsl,imx8mp-tmu", .data = &imx8mp_tmu_data, },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx8mm_tmu_table);
+
+static struct platform_driver imx8mm_tmu = {
+ .driver = {
+ .name = "i.mx8mm_thermal",
+ .of_match_table = imx8mm_tmu_table,
+ },
+ .probe = imx8mm_tmu_probe,
+ .remove = imx8mm_tmu_remove,
+};
+module_platform_driver(imx8mm_tmu);
+
+MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
+MODULE_DESCRIPTION("i.MX8MM Thermal Monitor Unit driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c
new file mode 100644
index 000000000..274eee303
--- /dev/null
+++ b/drivers/thermal/imx91_thermal.c
@@ -0,0 +1,388 @@
+// 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>
+
+#include "thermal_hwmon.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");
+
+ devm_thermal_add_hwmon_sysfs(dev, tmu->tzd);
+
+ 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 ret;
+
+ 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
new file mode 100644
index 000000000..88558ce58
--- /dev/null
+++ b/drivers/thermal/imx_sc_thermal.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018-2020 NXP.
+ */
+
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include <linux/err.h>
+#include <linux/firmware/imx/sci.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "thermal_hwmon.h"
+
+#define IMX_SC_MISC_FUNC_GET_TEMP 13
+
+static struct imx_sc_ipc *thermal_ipc_handle;
+
+struct imx_sc_sensor {
+ struct thermal_zone_device *tzd;
+ u32 resource_id;
+};
+
+struct req_get_temp {
+ u16 resource_id;
+ u8 type;
+} __packed __aligned(4);
+
+struct resp_get_temp {
+ s16 celsius;
+ s8 tenths;
+} __packed __aligned(4);
+
+struct imx_sc_msg_misc_get_temp {
+ struct imx_sc_rpc_msg hdr;
+ union {
+ struct req_get_temp req;
+ struct resp_get_temp resp;
+ } data;
+} __packed __aligned(4);
+
+static int imx_sc_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct imx_sc_msg_misc_get_temp msg;
+ struct imx_sc_rpc_msg *hdr = &msg.hdr;
+ struct imx_sc_sensor *sensor = thermal_zone_device_priv(tz);
+ int ret;
+
+ msg.data.req.resource_id = sensor->resource_id;
+ msg.data.req.type = IMX_SC_C_TEMP;
+
+ hdr->ver = IMX_SC_RPC_VERSION;
+ hdr->svc = IMX_SC_RPC_SVC_MISC;
+ hdr->func = IMX_SC_MISC_FUNC_GET_TEMP;
+ hdr->size = 2;
+
+ ret = imx_scu_call_rpc(thermal_ipc_handle, &msg, true);
+ if (ret)
+ return ret;
+
+ *temp = msg.data.resp.celsius * 1000 + msg.data.resp.tenths * 100;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops imx_sc_thermal_ops = {
+ .get_temp = imx_sc_thermal_get_temp,
+};
+
+static int imx_sc_thermal_probe(struct platform_device *pdev)
+{
+ struct imx_sc_sensor *sensor;
+ const int *resource_id;
+ int i, ret;
+
+ ret = imx_scu_get_handle(&thermal_ipc_handle);
+ if (ret)
+ return ret;
+
+ resource_id = of_device_get_match_data(&pdev->dev);
+ if (!resource_id)
+ return -EINVAL;
+
+ for (i = 0; resource_id[i] >= 0; i++) {
+
+ sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ sensor->resource_id = resource_id[i];
+
+ sensor->tzd = devm_thermal_of_zone_register(&pdev->dev, sensor->resource_id,
+ sensor, &imx_sc_thermal_ops);
+ if (IS_ERR(sensor->tzd)) {
+ /*
+ * Save the error value before freeing the
+ * sensor pointer, otherwise we endup with a
+ * use-after-free error
+ */
+ ret = PTR_ERR(sensor->tzd);
+
+ devm_kfree(&pdev->dev, sensor);
+
+ /*
+ * The thermal framework notifies us there is
+ * no thermal zone description for such a
+ * sensor id
+ */
+ if (ret == -ENODEV)
+ continue;
+
+ return dev_err_probe(&pdev->dev, ret, "failed to register thermal zone\n");
+ }
+
+ devm_thermal_add_hwmon_sysfs(&pdev->dev, sensor->tzd);
+ }
+
+ return 0;
+}
+
+static const int imx_sc_sensors[] = {
+ IMX_SC_R_SYSTEM, IMX_SC_R_PMIC_0,
+ IMX_SC_R_AP_0, IMX_SC_R_AP_1,
+ IMX_SC_R_GPU_0_PID0, IMX_SC_R_GPU_1_PID0,
+ IMX_SC_R_DRC_0, -1 };
+
+static const struct of_device_id imx_sc_thermal_table[] = {
+ { .compatible = "fsl,imx-sc-thermal", .data = imx_sc_sensors },
+ {}
+};
+MODULE_DEVICE_TABLE(of, imx_sc_thermal_table);
+
+static struct platform_driver imx_sc_thermal_driver = {
+ .probe = imx_sc_thermal_probe,
+ .driver = {
+ .name = "imx-sc-thermal",
+ .of_match_table = imx_sc_thermal_table,
+ },
+};
+module_platform_driver(imx_sc_thermal_driver);
+
+MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
+MODULE_DESCRIPTION("Thermal driver for NXP i.MX SoCs with system controller");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
new file mode 100644
index 000000000..887d38154
--- /dev/null
+++ b/drivers/thermal/imx_thermal.c
@@ -0,0 +1,874 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2013 Freescale Semiconductor, Inc.
+
+#include <linux/clk.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu_cooling.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/pm_runtime.h>
+
+#define REG_SET 0x4
+#define REG_CLR 0x8
+#define REG_TOG 0xc
+
+/* i.MX6 specific */
+#define IMX6_MISC0 0x0150
+#define IMX6_MISC0_REFTOP_SELBIASOFF (1 << 3)
+#define IMX6_MISC1 0x0160
+#define IMX6_MISC1_IRQ_TEMPHIGH (1 << 29)
+/* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */
+#define IMX6_MISC1_IRQ_TEMPLOW (1 << 28)
+#define IMX6_MISC1_IRQ_TEMPPANIC (1 << 27)
+
+#define IMX6_TEMPSENSE0 0x0180
+#define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT 20
+#define IMX6_TEMPSENSE0_ALARM_VALUE_MASK (0xfff << 20)
+#define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT 8
+#define IMX6_TEMPSENSE0_TEMP_CNT_MASK (0xfff << 8)
+#define IMX6_TEMPSENSE0_FINISHED (1 << 2)
+#define IMX6_TEMPSENSE0_MEASURE_TEMP (1 << 1)
+#define IMX6_TEMPSENSE0_POWER_DOWN (1 << 0)
+
+#define IMX6_TEMPSENSE1 0x0190
+#define IMX6_TEMPSENSE1_MEASURE_FREQ 0xffff
+#define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT 0
+
+#define OCOTP_MEM0 0x0480
+#define OCOTP_ANA1 0x04e0
+
+/* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */
+#define IMX6_TEMPSENSE2 0x0290
+#define IMX6_TEMPSENSE2_LOW_VALUE_SHIFT 0
+#define IMX6_TEMPSENSE2_LOW_VALUE_MASK 0xfff
+#define IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT 16
+#define IMX6_TEMPSENSE2_PANIC_VALUE_MASK 0xfff0000
+
+/* i.MX7 specific */
+#define IMX7_ANADIG_DIGPROG 0x800
+#define IMX7_TEMPSENSE0 0x300
+#define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT 18
+#define IMX7_TEMPSENSE0_PANIC_ALARM_MASK (0x1ff << 18)
+#define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT 9
+#define IMX7_TEMPSENSE0_HIGH_ALARM_MASK (0x1ff << 9)
+#define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT 0
+#define IMX7_TEMPSENSE0_LOW_ALARM_MASK 0x1ff
+
+#define IMX7_TEMPSENSE1 0x310
+#define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT 16
+#define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK (0xffff << 16)
+#define IMX7_TEMPSENSE1_FINISHED (1 << 11)
+#define IMX7_TEMPSENSE1_MEASURE_TEMP (1 << 10)
+#define IMX7_TEMPSENSE1_POWER_DOWN (1 << 9)
+#define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT 0
+#define IMX7_TEMPSENSE1_TEMP_VALUE_MASK 0x1ff
+
+/* The driver supports 1 passive trip point and 1 critical trip point */
+enum imx_thermal_trip {
+ IMX_TRIP_PASSIVE,
+ IMX_TRIP_CRITICAL,
+};
+
+#define IMX_POLLING_DELAY 2000 /* millisecond */
+#define IMX_PASSIVE_DELAY 1000
+
+#define TEMPMON_IMX6Q 1
+#define TEMPMON_IMX6SX 2
+#define TEMPMON_IMX7D 3
+
+struct thermal_soc_data {
+ u32 version;
+
+ u32 sensor_ctrl;
+ u32 power_down_mask;
+ u32 measure_temp_mask;
+
+ u32 measure_freq_ctrl;
+ u32 measure_freq_mask;
+ u32 measure_freq_shift;
+
+ u32 temp_data;
+ u32 temp_value_mask;
+ u32 temp_value_shift;
+ u32 temp_valid_mask;
+
+ u32 panic_alarm_ctrl;
+ u32 panic_alarm_mask;
+ u32 panic_alarm_shift;
+
+ u32 high_alarm_ctrl;
+ u32 high_alarm_mask;
+ u32 high_alarm_shift;
+
+ u32 low_alarm_ctrl;
+ u32 low_alarm_mask;
+ u32 low_alarm_shift;
+};
+
+static struct thermal_trip trips[] = {
+ [IMX_TRIP_PASSIVE] = { .type = THERMAL_TRIP_PASSIVE,
+ .flags = THERMAL_TRIP_FLAG_RW_TEMP },
+ [IMX_TRIP_CRITICAL] = { .type = THERMAL_TRIP_CRITICAL },
+};
+
+static struct thermal_soc_data thermal_imx6q_data = {
+ .version = TEMPMON_IMX6Q,
+
+ .sensor_ctrl = IMX6_TEMPSENSE0,
+ .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
+ .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
+
+ .measure_freq_ctrl = IMX6_TEMPSENSE1,
+ .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
+ .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
+
+ .temp_data = IMX6_TEMPSENSE0,
+ .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
+ .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
+ .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
+
+ .high_alarm_ctrl = IMX6_TEMPSENSE0,
+ .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
+ .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
+};
+
+static struct thermal_soc_data thermal_imx6sx_data = {
+ .version = TEMPMON_IMX6SX,
+
+ .sensor_ctrl = IMX6_TEMPSENSE0,
+ .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN,
+ .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP,
+
+ .measure_freq_ctrl = IMX6_TEMPSENSE1,
+ .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT,
+ .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ,
+
+ .temp_data = IMX6_TEMPSENSE0,
+ .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK,
+ .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT,
+ .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED,
+
+ .high_alarm_ctrl = IMX6_TEMPSENSE0,
+ .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK,
+ .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT,
+
+ .panic_alarm_ctrl = IMX6_TEMPSENSE2,
+ .panic_alarm_mask = IMX6_TEMPSENSE2_PANIC_VALUE_MASK,
+ .panic_alarm_shift = IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT,
+
+ .low_alarm_ctrl = IMX6_TEMPSENSE2,
+ .low_alarm_mask = IMX6_TEMPSENSE2_LOW_VALUE_MASK,
+ .low_alarm_shift = IMX6_TEMPSENSE2_LOW_VALUE_SHIFT,
+};
+
+static struct thermal_soc_data thermal_imx7d_data = {
+ .version = TEMPMON_IMX7D,
+
+ .sensor_ctrl = IMX7_TEMPSENSE1,
+ .power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN,
+ .measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP,
+
+ .measure_freq_ctrl = IMX7_TEMPSENSE1,
+ .measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT,
+ .measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK,
+
+ .temp_data = IMX7_TEMPSENSE1,
+ .temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK,
+ .temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT,
+ .temp_valid_mask = IMX7_TEMPSENSE1_FINISHED,
+
+ .panic_alarm_ctrl = IMX7_TEMPSENSE1,
+ .panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK,
+ .panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT,
+
+ .high_alarm_ctrl = IMX7_TEMPSENSE0,
+ .high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK,
+ .high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT,
+
+ .low_alarm_ctrl = IMX7_TEMPSENSE0,
+ .low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK,
+ .low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT,
+};
+
+struct imx_thermal_data {
+ struct device *dev;
+ struct cpufreq_policy *policy;
+ struct thermal_zone_device *tz;
+ struct thermal_cooling_device *cdev;
+ struct regmap *tempmon;
+ u32 c1, c2; /* See formula in imx_init_calib() */
+ int temp_max;
+ int alarm_temp;
+ int last_temp;
+ bool irq_enabled;
+ int irq;
+ struct clk *thermal_clk;
+ const struct thermal_soc_data *socdata;
+ const char *temp_grade;
+};
+
+static void imx_set_panic_temp(struct imx_thermal_data *data,
+ int panic_temp)
+{
+ const struct thermal_soc_data *soc_data = data->socdata;
+ struct regmap *map = data->tempmon;
+ int critical_value;
+
+ critical_value = (data->c2 - panic_temp) / data->c1;
+
+ regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR,
+ soc_data->panic_alarm_mask);
+ regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET,
+ critical_value << soc_data->panic_alarm_shift);
+}
+
+static void imx_set_alarm_temp(struct imx_thermal_data *data,
+ int alarm_temp)
+{
+ struct regmap *map = data->tempmon;
+ const struct thermal_soc_data *soc_data = data->socdata;
+ int alarm_value;
+
+ data->alarm_temp = alarm_temp;
+
+ if (data->socdata->version == TEMPMON_IMX7D)
+ alarm_value = alarm_temp / 1000 + data->c1 - 25;
+ else
+ alarm_value = (data->c2 - alarm_temp) / data->c1;
+
+ regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR,
+ soc_data->high_alarm_mask);
+ regmap_write(map, soc_data->high_alarm_ctrl + REG_SET,
+ alarm_value << soc_data->high_alarm_shift);
+}
+
+static int imx_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct imx_thermal_data *data = thermal_zone_device_priv(tz);
+ const struct thermal_soc_data *soc_data = data->socdata;
+ struct regmap *map = data->tempmon;
+ unsigned int n_meas;
+ u32 val;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(data->dev);
+ if (ret < 0)
+ return ret;
+
+ regmap_read(map, soc_data->temp_data, &val);
+
+ if ((val & soc_data->temp_valid_mask) == 0)
+ return -EAGAIN;
+
+ n_meas = (val & soc_data->temp_value_mask)
+ >> soc_data->temp_value_shift;
+
+ /* See imx_init_calib() for formula derivation */
+ if (data->socdata->version == TEMPMON_IMX7D)
+ *temp = (n_meas - data->c1 + 25) * 1000;
+ else
+ *temp = data->c2 - n_meas * data->c1;
+
+ /* Update alarm value to next higher trip point for TEMPMON_IMX6Q */
+ if (data->socdata->version == TEMPMON_IMX6Q) {
+ if (data->alarm_temp == trips[IMX_TRIP_PASSIVE].temperature &&
+ *temp >= trips[IMX_TRIP_PASSIVE].temperature)
+ imx_set_alarm_temp(data, trips[IMX_TRIP_CRITICAL].temperature);
+ if (data->alarm_temp == trips[IMX_TRIP_CRITICAL].temperature &&
+ *temp < trips[IMX_TRIP_PASSIVE].temperature) {
+ imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature);
+ dev_dbg(data->dev, "thermal alarm off: T < %d\n",
+ data->alarm_temp / 1000);
+ }
+ }
+
+ if (*temp != data->last_temp) {
+ dev_dbg(data->dev, "millicelsius: %d\n", *temp);
+ data->last_temp = *temp;
+ }
+
+ /* Reenable alarm IRQ if temperature below alarm temperature */
+ if (!data->irq_enabled && *temp < data->alarm_temp) {
+ data->irq_enabled = true;
+ enable_irq(data->irq);
+ }
+
+ pm_runtime_put(data->dev);
+
+ return 0;
+}
+
+static int imx_change_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode mode)
+{
+ struct imx_thermal_data *data = thermal_zone_device_priv(tz);
+
+ if (mode == THERMAL_DEVICE_ENABLED) {
+ pm_runtime_get(data->dev);
+
+ if (!data->irq_enabled) {
+ data->irq_enabled = true;
+ enable_irq(data->irq);
+ }
+ } else {
+ pm_runtime_put(data->dev);
+
+ if (data->irq_enabled) {
+ disable_irq(data->irq);
+ data->irq_enabled = false;
+ }
+ }
+
+ return 0;
+}
+
+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);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(data->dev);
+ if (ret < 0)
+ return ret;
+
+ /* 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 bool imx_should_bind(struct thermal_zone_device *tz,
+ const struct thermal_trip *trip,
+ struct thermal_cooling_device *cdev,
+ struct cooling_spec *c)
+{
+ return trip->type == THERMAL_TRIP_PASSIVE;
+}
+
+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,
+};
+
+static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1)
+{
+ struct imx_thermal_data *data = platform_get_drvdata(pdev);
+ int n1;
+ u64 temp64;
+
+ if (ocotp_ana1 == 0 || ocotp_ana1 == ~0) {
+ dev_err(&pdev->dev, "invalid sensor calibration data\n");
+ return -EINVAL;
+ }
+
+ /*
+ * On i.MX7D, we only use the calibration data at 25C to get the temp,
+ * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C.
+ */
+ if (data->socdata->version == TEMPMON_IMX7D) {
+ data->c1 = (ocotp_ana1 >> 9) & 0x1ff;
+ return 0;
+ }
+
+ /*
+ * The sensor is calibrated at 25 °C (aka T1) and the value measured
+ * (aka N1) at this temperature is provided in bits [31:20] in the
+ * i.MX's OCOTP value ANA1.
+ * To find the actual temperature T, the following formula has to be used
+ * when reading value n from the sensor:
+ *
+ * T = T1 + (N - N1) / (0.4148468 - 0.0015423 * N1) °C + 3.580661 °C
+ * = [T1' - N1 / (0.4148468 - 0.0015423 * N1) °C] + N / (0.4148468 - 0.0015423 * N1) °C
+ * = [T1' + N1 / (0.0015423 * N1 - 0.4148468) °C] - N / (0.0015423 * N1 - 0.4148468) °C
+ * = c2 - c1 * N
+ *
+ * with
+ *
+ * T1' = 28.580661 °C
+ * c1 = 1 / (0.0015423 * N1 - 0.4297157) °C
+ * c2 = T1' + N1 / (0.0015423 * N1 - 0.4148468) °C
+ * = T1' + N1 * c1
+ */
+ n1 = ocotp_ana1 >> 20;
+
+ temp64 = 10000000; /* use 10^7 as fixed point constant for values in formula */
+ temp64 *= 1000; /* to get result in °mC */
+ do_div(temp64, 15423 * n1 - 4148468);
+ data->c1 = temp64;
+ data->c2 = n1 * data->c1 + 28581;
+
+ return 0;
+}
+
+static void imx_init_temp_grade(struct platform_device *pdev, u32 ocotp_mem0)
+{
+ struct imx_thermal_data *data = platform_get_drvdata(pdev);
+
+ /* The maximum die temp is specified by the Temperature Grade */
+ switch ((ocotp_mem0 >> 6) & 0x3) {
+ case 0: /* Commercial (0 to 95 °C) */
+ data->temp_grade = "Commercial";
+ data->temp_max = 95000;
+ break;
+ case 1: /* Extended Commercial (-20 °C to 105 °C) */
+ data->temp_grade = "Extended Commercial";
+ data->temp_max = 105000;
+ break;
+ case 2: /* Industrial (-40 °C to 105 °C) */
+ data->temp_grade = "Industrial";
+ data->temp_max = 105000;
+ break;
+ case 3: /* Automotive (-40 °C to 125 °C) */
+ data->temp_grade = "Automotive";
+ data->temp_max = 125000;
+ break;
+ }
+
+ /*
+ * Set the critical trip point at 5 °C under max
+ * Set the passive trip point at 10 °C under max (changeable via sysfs)
+ */
+ trips[IMX_TRIP_PASSIVE].temperature = data->temp_max - (1000 * 10);
+ trips[IMX_TRIP_CRITICAL].temperature = data->temp_max - (1000 * 5);
+}
+
+static int imx_init_from_tempmon_data(struct platform_device *pdev)
+{
+ struct regmap *map;
+ int ret;
+ u32 val;
+
+ map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "fsl,tempmon-data");
+ if (IS_ERR(map)) {
+ ret = PTR_ERR(map);
+ dev_err(&pdev->dev, "failed to get sensor regmap: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_read(map, OCOTP_ANA1, &val);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
+ return ret;
+ }
+ ret = imx_init_calib(pdev, val);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, OCOTP_MEM0, &val);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read sensor data: %d\n", ret);
+ return ret;
+ }
+ imx_init_temp_grade(pdev, val);
+
+ return 0;
+}
+
+static int imx_init_from_nvmem_cells(struct platform_device *pdev)
+{
+ int ret;
+ u32 val;
+
+ ret = nvmem_cell_read_u32(&pdev->dev, "calib", &val);
+ if (ret)
+ return ret;
+
+ ret = imx_init_calib(pdev, val);
+ if (ret)
+ return ret;
+
+ ret = nvmem_cell_read_u32(&pdev->dev, "temp_grade", &val);
+ if (ret)
+ return ret;
+ imx_init_temp_grade(pdev, val);
+
+ return 0;
+}
+
+static irqreturn_t imx_thermal_alarm_irq(int irq, void *dev)
+{
+ struct imx_thermal_data *data = dev;
+
+ disable_irq_nosync(irq);
+ data->irq_enabled = false;
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev)
+{
+ struct imx_thermal_data *data = dev;
+
+ dev_dbg(data->dev, "THERMAL ALARM: T > %d\n", data->alarm_temp / 1000);
+
+ thermal_zone_device_update(data->tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+static const struct of_device_id of_imx_thermal_match[] = {
+ { .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, },
+ { .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, },
+ { .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
+
+#ifdef CONFIG_CPU_FREQ
+/*
+ * Create cooling device in case no #cooling-cells property is available in
+ * CPU node
+ */
+static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
+{
+ struct device_node *np;
+ int ret = 0;
+
+ data->policy = cpufreq_cpu_get(0);
+ if (!data->policy) {
+ pr_debug("%s: CPUFreq policy not found\n", __func__);
+ return -EPROBE_DEFER;
+ }
+
+ np = of_get_cpu_node(data->policy->cpu, NULL);
+
+ if (!np || !of_property_present(np, "#cooling-cells")) {
+ data->cdev = cpufreq_cooling_register(data->policy);
+ if (IS_ERR(data->cdev)) {
+ ret = PTR_ERR(data->cdev);
+ cpufreq_cpu_put(data->policy);
+ }
+ }
+
+ of_node_put(np);
+
+ return ret;
+}
+
+static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
+{
+ cpufreq_cooling_unregister(data->cdev);
+ cpufreq_cpu_put(data->policy);
+}
+
+#else
+
+static inline int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
+{
+ return 0;
+}
+
+static inline void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
+{
+}
+#endif
+
+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(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+
+ map = syscon_regmap_lookup_by_phandle(dev->of_node, "fsl,tempmon");
+ if (IS_ERR(map)) {
+ ret = PTR_ERR(map);
+ dev_err(dev, "failed to get tempmon regmap: %d\n", ret);
+ return ret;
+ }
+ data->tempmon = map;
+
+ data->socdata = of_device_get_match_data(dev);
+ if (!data->socdata) {
+ dev_err(dev, "no device match found\n");
+ return -ENODEV;
+ }
+
+ /* make sure the IRQ flag is clear before enabling irq on i.MX6SX */
+ if (data->socdata->version == TEMPMON_IMX6SX) {
+ regmap_write(map, IMX6_MISC1 + REG_CLR,
+ IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW
+ | IMX6_MISC1_IRQ_TEMPPANIC);
+ /*
+ * reset value of LOW ALARM is incorrect, set it to lowest
+ * value to avoid false trigger of low alarm.
+ */
+ regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET,
+ data->socdata->low_alarm_mask);
+ }
+
+ data->irq = platform_get_irq(pdev, 0);
+ if (data->irq < 0)
+ return data->irq;
+
+ platform_set_drvdata(pdev, data);
+
+ if (of_property_present(dev->of_node, "nvmem-cells")) {
+ ret = imx_init_from_nvmem_cells(pdev);
+ if (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(dev, "failed to init from fsl,tempmon-data\n");
+ return ret;
+ }
+ }
+
+ /* Make sure sensor is in known good state for measurements */
+ regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
+ data->socdata->power_down_mask);
+ regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
+ data->socdata->measure_temp_mask);
+ regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
+ data->socdata->measure_freq_mask);
+ if (data->socdata->version != TEMPMON_IMX7D)
+ regmap_write(map, IMX6_MISC0 + REG_SET,
+ IMX6_MISC0_REFTOP_SELBIASOFF);
+ regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
+ data->socdata->power_down_mask);
+
+ ret = imx_thermal_register_legacy_cooling(data);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register cpufreq cooling device\n");
+
+ data->thermal_clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(data->thermal_clk)) {
+ ret = dev_err_probe(dev, PTR_ERR(data->thermal_clk), "failed to get thermal clk\n");
+ goto legacy_cleanup;
+ }
+
+ /*
+ * Thermal sensor needs clk on to get correct value, normally
+ * we should enable its clk before taking measurement and disable
+ * clk after measurement is done, but if alarm function is enabled,
+ * hardware will auto measure the temperature periodically, so we
+ * need to keep the clk always on for alarm function.
+ */
+ ret = clk_prepare_enable(data->thermal_clk);
+ if (ret) {
+ dev_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),
+ data,
+ &imx_tz_ops, NULL,
+ IMX_PASSIVE_DELAY,
+ IMX_POLLING_DELAY);
+ if (IS_ERR(data->tz)) {
+ ret = PTR_ERR(data->tz);
+ dev_err(dev, "failed to register thermal zone device %d\n",
+ ret);
+ goto clk_disable;
+ }
+
+ 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);
+
+ /* Enable measurements at ~ 10 Hz */
+ regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR,
+ data->socdata->measure_freq_mask);
+ measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */
+ regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET,
+ measure_freq << data->socdata->measure_freq_shift);
+ imx_set_alarm_temp(data, trips[IMX_TRIP_PASSIVE].temperature);
+
+ if (data->socdata->version == TEMPMON_IMX6SX)
+ imx_set_panic_temp(data, trips[IMX_TRIP_CRITICAL].temperature);
+
+ regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
+ data->socdata->power_down_mask);
+ regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
+ data->socdata->measure_temp_mask);
+ /* After power up, we need a delay before first access can be done. */
+ usleep_range(20, 50);
+
+ /* the core was configured and enabled just before */
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(data->dev);
+
+ ret = pm_runtime_resume_and_get(data->dev);
+ if (ret < 0)
+ goto disable_runtime_pm;
+
+ data->irq_enabled = true;
+ ret = thermal_zone_device_enable(data->tz);
+ if (ret)
+ goto thermal_zone_unregister;
+
+ ret = devm_request_threaded_irq(dev, data->irq,
+ imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
+ 0, "imx_thermal", data);
+ if (ret < 0)
+ goto thermal_zone_unregister;
+
+ pm_runtime_put(data->dev);
+
+ return 0;
+
+thermal_zone_unregister:
+ thermal_zone_device_unregister(data->tz);
+disable_runtime_pm:
+ pm_runtime_put_noidle(data->dev);
+ pm_runtime_disable(data->dev);
+clk_disable:
+ clk_disable_unprepare(data->thermal_clk);
+legacy_cleanup:
+ imx_thermal_unregister_legacy_cooling(data);
+
+ return ret;
+}
+
+static void imx_thermal_remove(struct platform_device *pdev)
+{
+ struct imx_thermal_data *data = platform_get_drvdata(pdev);
+
+ pm_runtime_put_noidle(data->dev);
+ pm_runtime_disable(data->dev);
+
+ thermal_zone_device_unregister(data->tz);
+ imx_thermal_unregister_legacy_cooling(data);
+}
+
+static int imx_thermal_suspend(struct device *dev)
+{
+ struct imx_thermal_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ /*
+ * Need to disable thermal sensor, otherwise, when thermal core
+ * try to get temperature before thermal sensor resume, a wrong
+ * temperature will be read as the thermal sensor is powered
+ * down. This is done in change_mode() operation called from
+ * thermal_zone_device_disable()
+ */
+ ret = thermal_zone_device_disable(data->tz);
+ if (ret)
+ return ret;
+
+ return pm_runtime_force_suspend(data->dev);
+}
+
+static int imx_thermal_resume(struct device *dev)
+{
+ struct imx_thermal_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_force_resume(data->dev);
+ if (ret)
+ return ret;
+ /* Enabled thermal sensor after resume */
+ return thermal_zone_device_enable(data->tz);
+}
+
+static int imx_thermal_runtime_suspend(struct device *dev)
+{
+ struct imx_thermal_data *data = dev_get_drvdata(dev);
+ const struct thermal_soc_data *socdata = data->socdata;
+ struct regmap *map = data->tempmon;
+ int ret;
+
+ ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
+ socdata->measure_temp_mask);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
+ socdata->power_down_mask);
+ if (ret)
+ return ret;
+
+ clk_disable_unprepare(data->thermal_clk);
+
+ return 0;
+}
+
+static int imx_thermal_runtime_resume(struct device *dev)
+{
+ struct imx_thermal_data *data = dev_get_drvdata(dev);
+ const struct thermal_soc_data *socdata = data->socdata;
+ struct regmap *map = data->tempmon;
+ int ret;
+
+ ret = clk_prepare_enable(data->thermal_clk);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR,
+ socdata->power_down_mask);
+ if (ret)
+ goto disable_clk;
+
+ ret = regmap_write(map, socdata->sensor_ctrl + REG_SET,
+ socdata->measure_temp_mask);
+ if (ret)
+ goto disable_clk;
+
+ /*
+ * According to the temp sensor designers, it may require up to ~17us
+ * to complete a measurement.
+ */
+ usleep_range(20, 50);
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(data->thermal_clk);
+
+ return ret;
+}
+
+static const struct dev_pm_ops imx_thermal_pm_ops = {
+ 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 = pm_ptr(&imx_thermal_pm_ops),
+ .of_match_table = of_imx_thermal_match,
+ },
+ .probe = imx_thermal_probe,
+ .remove = imx_thermal_remove,
+};
+module_platform_driver(imx_thermal);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Thermal driver for Freescale i.MX SoCs");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-thermal");
diff --git a/drivers/thermal/intel/Kconfig b/drivers/thermal/intel/Kconfig
new file mode 100644
index 000000000..347c59bc8
--- /dev/null
+++ b/drivers/thermal/intel/Kconfig
@@ -0,0 +1,117 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config INTEL_POWERCLAMP
+ tristate "Intel PowerClamp idle injection driver"
+ depends on X86
+ depends on CPU_SUP_INTEL
+ depends on CPU_IDLE
+ select POWERCAP
+ select IDLE_INJECT
+ help
+ Enable this to enable Intel PowerClamp idle injection driver. This
+ enforce idle time which results in more package C-state residency. The
+ user interface is exposed via generic thermal framework.
+
+config X86_THERMAL_VECTOR
+ def_bool y
+ depends on X86 && CPU_SUP_INTEL && X86_LOCAL_APIC
+
+config INTEL_TCC
+ bool
+ depends on X86
+
+config X86_PKG_TEMP_THERMAL
+ tristate "X86 package temperature thermal driver"
+ depends on X86_THERMAL_VECTOR && NET
+ select THERMAL_NETLINK
+ select INTEL_TCC
+ default m
+ help
+ Enable this to register CPU digital sensor for package temperature as
+ thermal zone. Each package will have its own thermal zone. There are
+ two trip points which can be set by user to get notifications via thermal
+ notification methods.
+
+config INTEL_SOC_DTS_IOSF_CORE
+ tristate
+ depends on X86 && PCI
+ select IOSF_MBI
+ select INTEL_TCC
+ help
+ This is becoming a common feature for Intel SoCs to expose the additional
+ digital temperature sensors (DTSs) using side band interface (IOSF). This
+ implements the common set of helper functions to register, get temperature
+ and get/set thresholds on DTSs.
+
+config INTEL_SOC_DTS_THERMAL
+ tristate "Intel SoCs DTS thermal driver"
+ depends on X86_64 && PCI && ACPI && NET
+ select INT340X_THERMAL
+ select INTEL_SOC_DTS_IOSF_CORE
+ help
+ Enable this to register Intel SoCs (e.g. Bay Trail) platform digital
+ temperature sensor (DTS). These SoCs have two additional DTSs in
+ addition to DTSs on CPU cores. Each DTS will be registered as a
+ thermal zone. There are two trip points. One of the trip point can
+ be set by user mode programs to get notifications via Linux thermal
+ notification methods.The other trip is a critical trip point, which
+ was set by the driver based on the TJ MAX temperature.
+
+config INTEL_QUARK_DTS_THERMAL
+ tristate "Intel Quark DTS thermal driver"
+ depends on X86_INTEL_QUARK
+ help
+ Enable this to register Intel Quark SoC (e.g. X1000) platform digital
+ temperature sensor (DTS). For X1000 SoC, it has one on-die DTS.
+ The DTS will be registered as a thermal zone. There are two trip points:
+ hot & critical. The critical trip point default value is set by
+ underlying BIOS/Firmware.
+
+menu "ACPI INT340X thermal drivers"
+source "drivers/thermal/intel/int340x_thermal/Kconfig"
+endmenu
+
+config INTEL_BXT_PMIC_THERMAL
+ tristate "Intel Broxton PMIC thermal driver"
+ depends on X86 && INTEL_SOC_PMIC_BXTWC
+ select REGMAP
+ help
+ Select this driver for Intel Broxton PMIC with ADC channels monitoring
+ system temperature measurements and alerts.
+ This driver is used for monitoring the ADC channels of PMIC and handles
+ the alert trip point interrupts and notifies the thermal framework with
+ the trip point and temperature details of the zone.
+
+config INTEL_PCH_THERMAL
+ tristate "Intel PCH Thermal Reporting Driver"
+ depends on X86 && PCI
+ select ACPI_THERMAL_LIB if ACPI
+ help
+ Enable this to support thermal reporting on certain intel PCHs.
+ Thermal reporting device will provide temperature reading,
+ programmable trip points and other information.
+
+config INTEL_TCC_COOLING
+ tristate "Intel TCC offset cooling Driver"
+ depends on X86
+ select INTEL_TCC
+ help
+ Enable this to support system cooling by adjusting the effective TCC
+ activation temperature via the TCC Offset register, which is widely
+ supported on modern Intel platforms.
+ Note that, on different platforms, the behavior might be different
+ on how fast the setting takes effect, and how much the CPU frequency
+ is reduced.
+
+config INTEL_HFI_THERMAL
+ bool "Intel Hardware Feedback Interface"
+ depends on NET
+ depends on CPU_SUP_INTEL
+ depends on X86_THERMAL_VECTOR
+ select THERMAL_NETLINK
+ help
+ Select this option to enable the Hardware Feedback Interface. If
+ selected, hardware provides guidance to the operating system on
+ the performance and energy efficiency capabilities of each CPU.
+ These capabilities may change as a result of changes in the operating
+ conditions of the system such power and thermal limits. If selected,
+ the kernel relays updates in CPUs' capabilities to userspace.
diff --git a/drivers/thermal/intel/Makefile b/drivers/thermal/intel/Makefile
new file mode 100644
index 000000000..182b34113
--- /dev/null
+++ b/drivers/thermal/intel/Makefile
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for various Intel thermal drivers.
+
+obj-$(CONFIG_INTEL_TCC) += intel_tcc.o
+obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
+obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o
+obj-$(CONFIG_INTEL_SOC_DTS_IOSF_CORE) += intel_soc_dts_iosf.o
+obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o
+obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL) += intel_quark_dts_thermal.o
+obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
+obj-$(CONFIG_INTEL_BXT_PMIC_THERMAL) += intel_bxt_pmic_thermal.o
+obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o
+obj-$(CONFIG_INTEL_TCC_COOLING) += intel_tcc_cooling.o
+obj-$(CONFIG_X86_THERMAL_VECTOR) += therm_throt.o
+obj-$(CONFIG_INTEL_HFI_THERMAL) += intel_hfi.o
diff --git a/drivers/thermal/intel/int340x_thermal/Kconfig b/drivers/thermal/intel/int340x_thermal/Kconfig
new file mode 100644
index 000000000..4ced7bdcd
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/Kconfig
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# ACPI INT340x thermal drivers configuration
+#
+
+config INT340X_THERMAL
+ tristate "ACPI INT340X thermal drivers"
+ depends on X86_64 && ACPI && PCI && NET
+ select THERMAL_NETLINK
+ select ACPI_THERMAL_REL
+ select ACPI_FAN
+ 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
+ other devices with thermal control capabilities outside the core
+ CPU/SOC, for thermal safety reasons.
+ They are exposed for the OS to use via the INT3400 ACPI device object
+ as the master, and INT3401~INT340B ACPI device objects as the slaves.
+ Enable this to expose the temperature information and cooling ability
+ from these objects to userspace via the normal thermal framework.
+ This means that a wide range of applications and GUI widgets can show
+ the information to the user or use this information for making
+ decisions. For example, the Intel Thermal Daemon can use this
+ information to allow the user to select his laptop to run without
+ turning on the fans.
+
+config ACPI_THERMAL_REL
+ tristate
+ depends on ACPI
+
+if INT340X_THERMAL
+
+config INT3406_THERMAL
+ tristate "ACPI INT3406 display thermal driver"
+ depends on ACPI_VIDEO
+ help
+ The display thermal device represents the LED/LCD display panel
+ that may or may not include touch support. The main function of
+ the display thermal device is to allow control of the display
+ brightness in order to address a thermal condition or to reduce
+ power consumed by display device.
+
+config PROC_THERMAL_MMIO_RAPL
+ tristate
+ select INTEL_RAPL_CORE
+endif
diff --git a/drivers/thermal/intel/int340x_thermal/Makefile b/drivers/thermal/intel/int340x_thermal/Makefile
new file mode 100644
index 000000000..436be34b2
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/Makefile
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o
+obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal_zone.o
+obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o
+obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o
+obj-$(CONFIG_INT340X_THERMAL) += int3401_thermal.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device_pci_legacy.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device_pci.o
+obj-$(CONFIG_PROC_THERMAL_MMIO_RAPL) += processor_thermal_rapl.o
+obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_rfim.o
+obj-$(CONFIG_INT340X_THERMAL) += 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
new file mode 100644
index 000000000..0a548fdc4
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.c
@@ -0,0 +1,591 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* acpi_thermal_rel.c driver for exporting ACPI thermal relationship
+ *
+ * Copyright (c) 2014 Intel Corp
+ */
+
+/*
+ * Two functionalities included:
+ * 1. Export _TRT, _ART, via misc device interface to the userspace.
+ * 2. Provide parsing result to kernel drivers
+ *
+ */
+#include <linux/init.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+#include <linux/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include "acpi_thermal_rel.h"
+
+static acpi_handle acpi_thermal_rel_handle;
+static DEFINE_SPINLOCK(acpi_thermal_rel_chrdev_lock);
+static int acpi_thermal_rel_chrdev_count; /* #times opened */
+static int acpi_thermal_rel_chrdev_exclu; /* already open exclusive? */
+
+static int acpi_thermal_rel_open(struct inode *inode, struct file *file)
+{
+ spin_lock(&acpi_thermal_rel_chrdev_lock);
+ if (acpi_thermal_rel_chrdev_exclu ||
+ (acpi_thermal_rel_chrdev_count && (file->f_flags & O_EXCL))) {
+ spin_unlock(&acpi_thermal_rel_chrdev_lock);
+ return -EBUSY;
+ }
+
+ if (file->f_flags & O_EXCL)
+ acpi_thermal_rel_chrdev_exclu = 1;
+ acpi_thermal_rel_chrdev_count++;
+
+ spin_unlock(&acpi_thermal_rel_chrdev_lock);
+
+ return nonseekable_open(inode, file);
+}
+
+static int acpi_thermal_rel_release(struct inode *inode, struct file *file)
+{
+ spin_lock(&acpi_thermal_rel_chrdev_lock);
+ acpi_thermal_rel_chrdev_count--;
+ acpi_thermal_rel_chrdev_exclu = 0;
+ spin_unlock(&acpi_thermal_rel_chrdev_lock);
+
+ return 0;
+}
+
+/**
+ * acpi_parse_trt - Thermal Relationship Table _TRT for passive cooling
+ *
+ * @handle: ACPI handle of the device contains _TRT
+ * @trt_count: the number of valid entries resulted from parsing _TRT
+ * @trtp: pointer to pointer of array of _TRT entries in parsing result
+ * @create_dev: whether to create platform devices for target and source
+ *
+ */
+int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp,
+ bool create_dev)
+{
+ acpi_status status;
+ int result = 0;
+ int i;
+ int nr_bad_entries = 0;
+ struct trt *trts;
+ union acpi_object *p;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ struct acpi_buffer element = { 0, NULL };
+ struct acpi_buffer trt_format = { sizeof("RRNNNNNN"), "RRNNNNNN" };
+
+ status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ p = buffer.pointer;
+ if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
+ pr_err("Invalid _TRT data\n");
+ result = -EFAULT;
+ goto end;
+ }
+
+ *trt_count = p->package.count;
+ trts = kzalloc_objs(struct trt, *trt_count);
+ if (!trts) {
+ result = -ENOMEM;
+ goto end;
+ }
+
+ for (i = 0; i < *trt_count; i++) {
+ struct trt *trt = &trts[i - nr_bad_entries];
+
+ element.length = sizeof(struct trt);
+ element.pointer = trt;
+
+ status = acpi_extract_package(&(p->package.elements[i]),
+ &trt_format, &element);
+ if (ACPI_FAILURE(status)) {
+ nr_bad_entries++;
+ pr_warn("_TRT package %d is invalid, ignored\n", i);
+ continue;
+ }
+ if (!create_dev)
+ continue;
+
+ if (!acpi_fetch_acpi_dev(trt->source))
+ pr_warn("Failed to get source ACPI device\n");
+
+ if (!acpi_fetch_acpi_dev(trt->target))
+ pr_warn("Failed to get target ACPI device\n");
+ }
+
+ result = 0;
+
+ *trtp = trts;
+ /* don't count bad entries */
+ *trt_count -= nr_bad_entries;
+end:
+ kfree(buffer.pointer);
+ return result;
+}
+EXPORT_SYMBOL(acpi_parse_trt);
+
+/**
+ * acpi_parse_art - Parse Active Relationship Table _ART
+ *
+ * @handle: ACPI handle of the device contains _ART
+ * @art_count: the number of valid entries resulted from parsing _ART
+ * @artp: pointer to pointer of array of art entries in parsing result
+ * @create_dev: whether to create platform devices for target and source
+ *
+ */
+int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp,
+ bool create_dev)
+{
+ acpi_status status;
+ int result = 0;
+ int i;
+ int nr_bad_entries = 0;
+ struct art *arts;
+ union acpi_object *p;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ struct acpi_buffer element = { 0, NULL };
+ struct acpi_buffer art_format = {
+ sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" };
+
+ status = acpi_evaluate_object(handle, "_ART", NULL, &buffer);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ p = buffer.pointer;
+ if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
+ pr_err("Invalid _ART data\n");
+ result = -EFAULT;
+ goto end;
+ }
+
+ /* ignore p->package.elements[0], as this is _ART Revision field */
+ *art_count = p->package.count - 1;
+ arts = kzalloc_objs(struct art, *art_count);
+ if (!arts) {
+ result = -ENOMEM;
+ goto end;
+ }
+
+ for (i = 0; i < *art_count; i++) {
+ struct art *art = &arts[i - nr_bad_entries];
+
+ element.length = sizeof(struct art);
+ element.pointer = art;
+
+ status = acpi_extract_package(&(p->package.elements[i + 1]),
+ &art_format, &element);
+ if (ACPI_FAILURE(status)) {
+ pr_warn("_ART package %d is invalid, ignored", i);
+ nr_bad_entries++;
+ continue;
+ }
+ if (!create_dev)
+ continue;
+
+ if (!acpi_fetch_acpi_dev(art->source))
+ pr_warn("Failed to get source ACPI device\n");
+
+ if (!acpi_fetch_acpi_dev(art->target))
+ pr_warn("Failed to get target ACPI device\n");
+ }
+
+ *artp = arts;
+ /* don't count bad entries */
+ *art_count -= nr_bad_entries;
+end:
+ kfree(buffer.pointer);
+ return result;
+}
+EXPORT_SYMBOL(acpi_parse_art);
+
+/*
+ * acpi_parse_psvt - Passive Table (PSVT) for passive cooling
+ *
+ * @handle: ACPI handle of the device which contains PSVT
+ * @psvt_count: the number of valid entries resulted from parsing PSVT
+ * @psvtp: pointer to array of psvt entries
+ *
+ */
+static int acpi_parse_psvt(acpi_handle handle, int *psvt_count, struct psvt **psvtp)
+{
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ int nr_bad_entries = 0, revision = 0;
+ union acpi_object *p;
+ acpi_status status;
+ int i, result = 0;
+ struct psvt *psvts;
+
+ status = acpi_evaluate_object(handle, "PSVT", NULL, &buffer);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ p = buffer.pointer;
+ if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
+ result = -EFAULT;
+ goto end;
+ }
+
+ /* first package is the revision number */
+ if (p->package.count > 0) {
+ union acpi_object *prev = &(p->package.elements[0]);
+
+ if (prev->type == ACPI_TYPE_INTEGER)
+ revision = (int)prev->integer.value;
+ } else {
+ result = -EFAULT;
+ goto end;
+ }
+
+ /* Support only version 2 */
+ if (revision != 2) {
+ result = -EFAULT;
+ goto end;
+ }
+
+ *psvt_count = p->package.count - 1;
+ if (!*psvt_count) {
+ result = -EFAULT;
+ goto end;
+ }
+
+ psvts = kzalloc_objs(*psvts, *psvt_count);
+ if (!psvts) {
+ result = -ENOMEM;
+ goto end;
+ }
+
+ /* Start index is 1 because the first package is the revision number */
+ for (i = 1; i < p->package.count; i++) {
+ struct acpi_buffer psvt_int_format = { sizeof("RRNNNNNNNNNN"), "RRNNNNNNNNNN" };
+ struct acpi_buffer psvt_str_format = { sizeof("RRNNNNNSNNNN"), "RRNNNNNSNNNN" };
+ union acpi_object *package = &(p->package.elements[i]);
+ struct psvt *psvt = &psvts[i - 1 - nr_bad_entries];
+ struct acpi_buffer *psvt_format = &psvt_int_format;
+ struct acpi_buffer element = { 0, NULL };
+ union acpi_object *knob;
+ struct acpi_device *res;
+ struct psvt *psvt_ptr;
+
+ element.length = ACPI_ALLOCATE_BUFFER;
+ element.pointer = NULL;
+
+ if (package->package.count >= ACPI_NR_PSVT_ELEMENTS) {
+ knob = &(package->package.elements[ACPI_PSVT_CONTROL_KNOB]);
+ } else {
+ nr_bad_entries++;
+ pr_info("PSVT package %d is invalid, ignored\n", i);
+ continue;
+ }
+
+ if (knob->type == ACPI_TYPE_STRING) {
+ psvt_format = &psvt_str_format;
+ if (knob->string.length > ACPI_LIMIT_STR_MAX_LEN - 1) {
+ pr_info("PSVT package %d limit string len exceeds max\n", i);
+ knob->string.length = ACPI_LIMIT_STR_MAX_LEN - 1;
+ }
+ }
+
+ status = acpi_extract_package(&(p->package.elements[i]), psvt_format, &element);
+ if (ACPI_FAILURE(status)) {
+ nr_bad_entries++;
+ pr_info("PSVT package %d is invalid, ignored\n", i);
+ continue;
+ }
+
+ psvt_ptr = (struct psvt *)element.pointer;
+
+ memcpy(psvt, psvt_ptr, sizeof(*psvt));
+
+ /* The limit element can be string or U64 */
+ psvt->control_knob_type = (u64)knob->type;
+
+ if (knob->type == ACPI_TYPE_STRING) {
+ memset(&psvt->limit, 0, sizeof(u64));
+ strscpy(psvt->limit.string, psvt_ptr->limit.str_ptr, ACPI_LIMIT_STR_MAX_LEN);
+ } else {
+ psvt->limit.integer = psvt_ptr->limit.integer;
+ }
+
+ kfree(element.pointer);
+
+ res = acpi_fetch_acpi_dev(psvt->source);
+ if (!res) {
+ nr_bad_entries++;
+ pr_info("Failed to get source ACPI device\n");
+ continue;
+ }
+
+ res = acpi_fetch_acpi_dev(psvt->target);
+ if (!res) {
+ nr_bad_entries++;
+ pr_info("Failed to get target ACPI device\n");
+ continue;
+ }
+ }
+
+ /* don't count bad entries */
+ *psvt_count -= nr_bad_entries;
+
+ if (!*psvt_count) {
+ result = -EFAULT;
+ kfree(psvts);
+ goto end;
+ }
+
+ *psvtp = psvts;
+
+ return 0;
+
+end:
+ kfree(buffer.pointer);
+ return result;
+}
+
+/* get device name from acpi handle */
+static void get_single_name(acpi_handle handle, char *name)
+{
+ struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER};
+
+ if (ACPI_FAILURE(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)))
+ pr_warn("Failed to get device name from acpi handle\n");
+ else {
+ memcpy(name, buffer.pointer, ACPI_NAMESEG_SIZE);
+ kfree(buffer.pointer);
+ }
+}
+
+static int fill_art(char __user *ubuf)
+{
+ int i;
+ int ret;
+ int count;
+ int art_len;
+ struct art *arts = NULL;
+ union art_object *art_user;
+
+ ret = acpi_parse_art(acpi_thermal_rel_handle, &count, &arts, false);
+ if (ret)
+ goto free_art;
+ art_len = count * sizeof(union art_object);
+ art_user = kzalloc(art_len, GFP_KERNEL);
+ if (!art_user) {
+ ret = -ENOMEM;
+ goto free_art;
+ }
+ /* now fill in user art data */
+ for (i = 0; i < count; i++) {
+ /* userspace art needs device name instead of acpi reference */
+ get_single_name(arts[i].source, art_user[i].source_device);
+ get_single_name(arts[i].target, art_user[i].target_device);
+ /* copy the rest int data in addition to source and target */
+ BUILD_BUG_ON(sizeof(art_user[i].data) !=
+ sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2));
+ memcpy(&art_user[i].data, &arts[i].data, sizeof(art_user[i].data));
+ }
+
+ if (copy_to_user(ubuf, art_user, art_len))
+ ret = -EFAULT;
+ kfree(art_user);
+free_art:
+ kfree(arts);
+ return ret;
+}
+
+static int fill_trt(char __user *ubuf)
+{
+ int i;
+ int ret;
+ int count;
+ int trt_len;
+ struct trt *trts = NULL;
+ union trt_object *trt_user;
+
+ ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, &trts, false);
+ if (ret)
+ goto free_trt;
+ trt_len = count * sizeof(union trt_object);
+ trt_user = kzalloc(trt_len, GFP_KERNEL);
+ if (!trt_user) {
+ ret = -ENOMEM;
+ goto free_trt;
+ }
+ /* now fill in user trt data */
+ for (i = 0; i < count; i++) {
+ /* userspace trt needs device name instead of acpi reference */
+ get_single_name(trts[i].source, trt_user[i].source_device);
+ get_single_name(trts[i].target, trt_user[i].target_device);
+ trt_user[i].sample_period = trts[i].sample_period;
+ trt_user[i].influence = trts[i].influence;
+ }
+
+ if (copy_to_user(ubuf, trt_user, trt_len))
+ ret = -EFAULT;
+ kfree(trt_user);
+free_trt:
+ kfree(trts);
+ return ret;
+}
+
+static int fill_psvt(char __user *ubuf)
+{
+ int i, ret, count, psvt_len;
+ union psvt_object *psvt_user;
+ struct psvt *psvts;
+
+ ret = acpi_parse_psvt(acpi_thermal_rel_handle, &count, &psvts);
+ if (ret)
+ return ret;
+
+ psvt_len = count * sizeof(*psvt_user);
+
+ psvt_user = kzalloc(psvt_len, GFP_KERNEL);
+ if (!psvt_user) {
+ ret = -ENOMEM;
+ goto free_psvt;
+ }
+
+ /* now fill in user psvt data */
+ for (i = 0; i < count; i++) {
+ /* userspace psvt needs device name instead of acpi reference */
+ get_single_name(psvts[i].source, psvt_user[i].source_device);
+ get_single_name(psvts[i].target, psvt_user[i].target_device);
+
+ psvt_user[i].priority = psvts[i].priority;
+ psvt_user[i].sample_period = psvts[i].sample_period;
+ psvt_user[i].passive_temp = psvts[i].passive_temp;
+ psvt_user[i].source_domain = psvts[i].source_domain;
+ psvt_user[i].control_knob = psvts[i].control_knob;
+ psvt_user[i].step_size = psvts[i].step_size;
+ psvt_user[i].limit_coeff = psvts[i].limit_coeff;
+ 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)
+ 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;
+
+ }
+
+ if (copy_to_user(ubuf, psvt_user, psvt_len))
+ ret = -EFAULT;
+
+ kfree(psvt_user);
+
+free_psvt:
+ kfree(psvts);
+ return ret;
+}
+
+static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd,
+ unsigned long __arg)
+{
+ int ret = 0;
+ unsigned long length = 0;
+ int count = 0;
+ char __user *arg = (void __user *)__arg;
+ struct trt *trts = NULL;
+ struct art *arts = NULL;
+ struct psvt *psvts;
+
+ switch (cmd) {
+ case ACPI_THERMAL_GET_TRT_COUNT:
+ ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,
+ &trts, false);
+ kfree(trts);
+ if (!ret)
+ return put_user(count, (unsigned long __user *)__arg);
+ return ret;
+ case ACPI_THERMAL_GET_TRT_LEN:
+ ret = acpi_parse_trt(acpi_thermal_rel_handle, &count,
+ &trts, false);
+ kfree(trts);
+ length = count * sizeof(union trt_object);
+ if (!ret)
+ return put_user(length, (unsigned long __user *)__arg);
+ return ret;
+ case ACPI_THERMAL_GET_TRT:
+ return fill_trt(arg);
+ case ACPI_THERMAL_GET_ART_COUNT:
+ ret = acpi_parse_art(acpi_thermal_rel_handle, &count,
+ &arts, false);
+ kfree(arts);
+ if (!ret)
+ return put_user(count, (unsigned long __user *)__arg);
+ return ret;
+ case ACPI_THERMAL_GET_ART_LEN:
+ ret = acpi_parse_art(acpi_thermal_rel_handle, &count,
+ &arts, false);
+ kfree(arts);
+ length = count * sizeof(union art_object);
+ if (!ret)
+ return put_user(length, (unsigned long __user *)__arg);
+ return ret;
+
+ case ACPI_THERMAL_GET_ART:
+ return fill_art(arg);
+
+ case ACPI_THERMAL_GET_PSVT_COUNT:
+ ret = acpi_parse_psvt(acpi_thermal_rel_handle, &count, &psvts);
+ if (!ret) {
+ kfree(psvts);
+ return put_user(count, (unsigned long __user *)__arg);
+ }
+ return ret;
+
+ case ACPI_THERMAL_GET_PSVT_LEN:
+ /* total length of the data retrieved (count * PSVT entry size) */
+ ret = acpi_parse_psvt(acpi_thermal_rel_handle, &count, &psvts);
+ length = count * sizeof(union psvt_object);
+ if (!ret) {
+ kfree(psvts);
+ return put_user(length, (unsigned long __user *)__arg);
+ }
+ return ret;
+
+ case ACPI_THERMAL_GET_PSVT:
+ return fill_psvt(arg);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations acpi_thermal_rel_fops = {
+ .owner = THIS_MODULE,
+ .open = acpi_thermal_rel_open,
+ .release = acpi_thermal_rel_release,
+ .unlocked_ioctl = acpi_thermal_rel_ioctl,
+};
+
+static struct miscdevice acpi_thermal_rel_misc_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ "acpi_thermal_rel",
+ &acpi_thermal_rel_fops
+};
+
+int acpi_thermal_rel_misc_device_add(acpi_handle handle)
+{
+ acpi_thermal_rel_handle = handle;
+
+ return misc_register(&acpi_thermal_rel_misc_device);
+}
+EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add);
+
+int acpi_thermal_rel_misc_device_remove(acpi_handle handle)
+{
+ misc_deregister(&acpi_thermal_rel_misc_device);
+
+ return 0;
+}
+EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove);
+
+MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
+MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com");
+MODULE_DESCRIPTION("Intel acpi thermal rel misc dev driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h
new file mode 100644
index 000000000..ac376d8f9
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/acpi_thermal_rel.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ACPI_ACPI_THERMAL_H
+#define __ACPI_ACPI_THERMAL_H
+
+#include <asm/ioctl.h>
+
+#define ACPI_THERMAL_MAGIC 's'
+
+#define ACPI_THERMAL_GET_TRT_LEN _IOR(ACPI_THERMAL_MAGIC, 1, unsigned long)
+#define ACPI_THERMAL_GET_ART_LEN _IOR(ACPI_THERMAL_MAGIC, 2, unsigned long)
+#define ACPI_THERMAL_GET_TRT_COUNT _IOR(ACPI_THERMAL_MAGIC, 3, unsigned long)
+#define ACPI_THERMAL_GET_ART_COUNT _IOR(ACPI_THERMAL_MAGIC, 4, unsigned long)
+
+#define ACPI_THERMAL_GET_TRT _IOR(ACPI_THERMAL_MAGIC, 5, unsigned long)
+#define ACPI_THERMAL_GET_ART _IOR(ACPI_THERMAL_MAGIC, 6, unsigned long)
+
+/*
+ * ACPI_THERMAL_GET_PSVT_COUNT = Number of PSVT entries
+ * ACPI_THERMAL_GET_PSVT_LEN = Total return data size (PSVT count x each
+ * PSVT entry size)
+ * ACPI_THERMAL_GET_PSVT = Get the data as an array of psvt_objects
+ */
+#define ACPI_THERMAL_GET_PSVT_LEN _IOR(ACPI_THERMAL_MAGIC, 7, unsigned long)
+#define ACPI_THERMAL_GET_PSVT_COUNT _IOR(ACPI_THERMAL_MAGIC, 8, unsigned long)
+#define ACPI_THERMAL_GET_PSVT _IOR(ACPI_THERMAL_MAGIC, 9, unsigned long)
+
+struct art {
+ acpi_handle source;
+ acpi_handle target;
+ struct_group(data,
+ u64 weight;
+ u64 ac0_max;
+ u64 ac1_max;
+ u64 ac2_max;
+ u64 ac3_max;
+ u64 ac4_max;
+ u64 ac5_max;
+ u64 ac6_max;
+ u64 ac7_max;
+ u64 ac8_max;
+ u64 ac9_max;
+ );
+} __packed;
+
+struct trt {
+ acpi_handle source;
+ acpi_handle target;
+ u64 influence;
+ u64 sample_period;
+ u64 reserved1;
+ u64 reserved2;
+ u64 reserved3;
+ u64 reserved4;
+} __packed;
+
+#define ACPI_NR_PSVT_ELEMENTS 12
+#define ACPI_PSVT_CONTROL_KNOB 7
+#define ACPI_LIMIT_STR_MAX_LEN 8
+
+struct psvt {
+ acpi_handle source;
+ acpi_handle target;
+ u64 priority;
+ u64 sample_period;
+ u64 passive_temp;
+ u64 source_domain;
+ u64 control_knob;
+ union {
+ /* For limit_type = ACPI_TYPE_INTEGER */
+ u64 integer;
+ /* For limit_type = ACPI_TYPE_STRING */
+ char string[ACPI_LIMIT_STR_MAX_LEN];
+ char *str_ptr;
+ } limit;
+ u64 step_size;
+ u64 limit_coeff;
+ u64 unlimit_coeff;
+ /* Spec calls this field reserved, so we borrow it for type info */
+ u64 control_knob_type; /* ACPI_TYPE_STRING or ACPI_TYPE_INTEGER */
+} __packed;
+
+#define ACPI_NR_ART_ELEMENTS 13
+/* for usrspace */
+union art_object {
+ struct {
+ char source_device[8]; /* ACPI single name */
+ char target_device[8]; /* ACPI single name */
+ struct_group(data,
+ u64 weight;
+ u64 ac0_max_level;
+ u64 ac1_max_level;
+ u64 ac2_max_level;
+ u64 ac3_max_level;
+ u64 ac4_max_level;
+ u64 ac5_max_level;
+ u64 ac6_max_level;
+ u64 ac7_max_level;
+ u64 ac8_max_level;
+ u64 ac9_max_level;
+ );
+ };
+ u64 __data[ACPI_NR_ART_ELEMENTS];
+};
+
+union trt_object {
+ struct {
+ char source_device[8]; /* ACPI single name */
+ char target_device[8]; /* ACPI single name */
+ u64 influence;
+ u64 sample_period;
+ u64 reserved[4];
+ };
+ u64 __data[8];
+};
+
+union psvt_object {
+ struct {
+ char source_device[8];
+ char target_device[8];
+ u64 priority;
+ u64 sample_period;
+ u64 passive_temp;
+ u64 source_domain;
+ u64 control_knob;
+ union {
+ u64 integer;
+ char string[ACPI_LIMIT_STR_MAX_LEN];
+ } limit;
+ u64 step_size;
+ u64 limit_coeff;
+ u64 unlimit_coeff;
+ u64 control_knob_type;
+ };
+ u64 __data[ACPI_NR_PSVT_ELEMENTS];
+};
+
+#ifdef __KERNEL__
+int acpi_thermal_rel_misc_device_add(acpi_handle handle);
+int acpi_thermal_rel_misc_device_remove(acpi_handle handle);
+int acpi_parse_art(acpi_handle handle, int *art_count, struct art **arts,
+ bool create_dev);
+int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trts,
+ bool create_dev);
+#endif
+
+#endif /* __ACPI_ACPI_THERMAL_H */
diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c
new file mode 100644
index 000000000..5d70301d4
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c
@@ -0,0 +1,715 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * INT3400 thermal driver
+ *
+ * Copyright (C) 2014, Intel Corporation
+ * Authors: Zhang Rui <rui.zhang@intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#include "acpi_thermal_rel.h"
+
+#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,
+ INT3400_THERMAL_PASSIVE_1,
+ INT3400_THERMAL_CRITICAL,
+ INT3400_THERMAL_ADAPTIVE_PERFORMANCE,
+ INT3400_THERMAL_EMERGENCY_CALL_MODE,
+ INT3400_THERMAL_PASSIVE_2,
+ INT3400_THERMAL_POWER_BOSS,
+ INT3400_THERMAL_VIRTUAL_SENSOR,
+ INT3400_THERMAL_COOLING_MODE,
+ INT3400_THERMAL_HARDWARE_DUTY_CYCLING,
+ INT3400_THERMAL_MAXIMUM_UUID,
+};
+
+static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
+ "3A95C389-E4B8-4629-A526-C52C88626BAE",
+ "42A441D6-AE6A-462b-A84B-4A8CE79027D3",
+ "97C68AE7-15FA-499c-B8C9-5DA81D606E0A",
+ "63BE270F-1C11-48FD-A6F7-3AF253FF3E2D",
+ "5349962F-71E6-431D-9AE8-0A635B710AEE",
+ "9E04115A-AE87-4D1C-9500-0F3E340BFE75",
+ "F5A35014-C209-46A4-993A-EB56DE7530A1",
+ "6ED722A7-9240-48A5-B479-31EEF723D7CF",
+ "16CAF1B7-DD38-40ED-B1C1-1B8A1913D531",
+ "BE84BABF-C4D4-403D-B495-3128FD44dAC1",
+};
+
+struct odvp_attr;
+
+struct int3400_thermal_priv {
+ struct acpi_device *adev;
+ struct platform_device *pdev;
+ struct thermal_zone_device *thermal;
+ int art_count;
+ struct art *arts;
+ int trt_count;
+ struct trt *trts;
+ u32 uuid_bitmap;
+ int rel_misc_dev_res;
+ int current_uuid_index;
+ char *data_vault;
+ int odvp_count;
+ int *odvp;
+ u32 os_uuid_mask;
+ int production_mode;
+ struct odvp_attr *odvp_attrs;
+};
+
+static int evaluate_odvp(struct int3400_thermal_priv *priv);
+
+struct odvp_attr {
+ int odvp;
+ struct int3400_thermal_priv *priv;
+ struct device_attribute attr;
+};
+
+static BIN_ATTR_SIMPLE_RO(data_vault);
+
+static ssize_t imok_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
+ acpi_status status;
+ int input, ret;
+
+ ret = kstrtouint(buf, 10, &input);
+ if (ret)
+ return ret;
+ status = acpi_execute_simple_method(priv->adev->handle, "IMOK", input);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return count;
+}
+
+static DEVICE_ATTR_WO(imok);
+
+static struct attribute *imok_attr[] = {
+ &dev_attr_imok.attr,
+ NULL
+};
+
+static const struct attribute_group imok_attribute_group = {
+ .attrs = imok_attr,
+};
+
+static ssize_t available_uuids_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
+ int i;
+ int length = 0;
+
+ if (!priv->uuid_bitmap)
+ return sysfs_emit(buf, "UNKNOWN\n");
+
+ for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
+ if (priv->uuid_bitmap & (1 << i))
+ length += sysfs_emit_at(buf, length, "%s\n", int3400_thermal_uuids[i]);
+ }
+
+ return length;
+}
+
+static ssize_t current_uuid_show(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
+ int i, length = 0;
+
+ if (priv->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++) {
+ if (priv->os_uuid_mask & BIT(i))
+ length += sysfs_emit_at(buf, length, "%s\n", int3400_thermal_uuids[i]);
+ }
+
+ if (length)
+ return length;
+
+ return sysfs_emit(buf, "INVALID\n");
+}
+
+static int int3400_thermal_run_osc(acpi_handle handle, char *uuid_str, int *enable)
+{
+ u32 ret, buf[2];
+ acpi_status status;
+ int result = 0;
+ struct acpi_osc_context context = {
+ .uuid_str = uuid_str,
+ .rev = 1,
+ .cap.length = 8,
+ .cap.pointer = buf,
+ };
+
+ buf[OSC_QUERY_DWORD] = 0;
+ buf[OSC_SUPPORT_DWORD] = *enable;
+
+ status = acpi_run_osc(handle, &context);
+ if (ACPI_SUCCESS(status)) {
+ ret = *((u32 *)(context.ret.pointer + 4));
+ if (ret != *enable)
+ result = -EPERM;
+
+ kfree(context.ret.pointer);
+ } else
+ result = -EPERM;
+
+ return result;
+}
+
+static int set_os_uuid_mask(struct int3400_thermal_priv *priv, u32 mask)
+{
+ int cap = 0;
+
+ /*
+ * Capability bits:
+ * Bit 0: set to 1 to indicate DPTF is active
+ * Bi1 1: set to 1 to active cooling is supported by user space daemon
+ * Bit 2: set to 1 to passive cooling is supported by user space daemon
+ * Bit 3: set to 1 to critical trip is handled by user space daemon
+ */
+ if (mask)
+ cap = (priv->os_uuid_mask << 1) | 0x01;
+
+ return int3400_thermal_run_osc(priv->adev->handle,
+ "b23ba85d-c8b7-3542-88de-8de2ffcfd698",
+ &cap);
+}
+
+static ssize_t current_uuid_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
+ int ret, i;
+
+ for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
+ if (!strncmp(buf, int3400_thermal_uuids[i],
+ INT3400_UUID_PREFIX_LEN)) {
+ /*
+ * If we have a list of supported UUIDs, make sure
+ * this one is supported.
+ */
+ if (priv->uuid_bitmap & BIT(i)) {
+ priv->current_uuid_index = i;
+ return count;
+ }
+
+ /*
+ * There is support of only 3 policies via the new
+ * _OSC to inform OS capability:
+ * INT3400_THERMAL_ACTIVE
+ * INT3400_THERMAL_PASSIVE_1
+ * INT3400_THERMAL_CRITICAL
+ */
+
+ if (i > INT3400_THERMAL_CRITICAL)
+ return -EINVAL;
+
+ priv->os_uuid_mask |= BIT(i);
+
+ break;
+ }
+ }
+
+ if (priv->os_uuid_mask) {
+ ret = set_os_uuid_mask(priv, priv->os_uuid_mask);
+ if (ret)
+ return ret;
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(current_uuid);
+static DEVICE_ATTR_RO(available_uuids);
+static struct attribute *uuid_attrs[] = {
+ &dev_attr_available_uuids.attr,
+ &dev_attr_current_uuid.attr,
+ NULL
+};
+
+static const struct attribute_group uuid_attribute_group = {
+ .attrs = uuid_attrs,
+ .name = "uuids"
+};
+
+static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv)
+{
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL};
+ union acpi_object *obja, *objb;
+ int i, j;
+ int result = 0;
+ acpi_status status;
+
+ status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ obja = (union acpi_object *)buf.pointer;
+ if (obja->type != ACPI_TYPE_PACKAGE) {
+ result = -EINVAL;
+ goto end;
+ }
+
+ for (i = 0; i < obja->package.count; i++) {
+ objb = &obja->package.elements[i];
+ if (objb->type != ACPI_TYPE_BUFFER) {
+ result = -EINVAL;
+ goto end;
+ }
+
+ /* UUID must be 16 bytes */
+ if (objb->buffer.length != 16) {
+ result = -EINVAL;
+ goto end;
+ }
+
+ for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) {
+ guid_t guid;
+
+ guid_parse(int3400_thermal_uuids[j], &guid);
+ if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) {
+ priv->uuid_bitmap |= (1 << j);
+ break;
+ }
+ }
+ }
+
+end:
+ kfree(buf.pointer);
+ return result;
+}
+
+static ssize_t production_mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", priv->production_mode);
+}
+
+static DEVICE_ATTR_RO(production_mode);
+
+static int production_mode_init(struct int3400_thermal_priv *priv)
+{
+ unsigned long long mode;
+ acpi_status status;
+ int ret;
+
+ priv->production_mode = -1;
+
+ status = acpi_evaluate_integer(priv->adev->handle, "DCFG", NULL, &mode);
+ /* If the method is not present, this is not an error */
+ if (ACPI_FAILURE(status))
+ return 0;
+
+ ret = sysfs_create_file(&priv->pdev->dev.kobj, &dev_attr_production_mode.attr);
+ if (ret)
+ return ret;
+
+ priv->production_mode = mode;
+
+ return 0;
+}
+
+static void production_mode_exit(struct int3400_thermal_priv *priv)
+{
+ if (priv->production_mode >= 0)
+ sysfs_remove_file(&priv->pdev->dev.kobj, &dev_attr_production_mode.attr);
+}
+
+static ssize_t odvp_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct odvp_attr *odvp_attr;
+
+ odvp_attr = container_of(attr, struct odvp_attr, attr);
+
+ return sysfs_emit(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);
+}
+
+static void cleanup_odvp(struct int3400_thermal_priv *priv)
+{
+ int i;
+
+ if (priv->odvp_attrs) {
+ for (i = 0; i < priv->odvp_count; i++) {
+ sysfs_remove_file(&priv->pdev->dev.kobj,
+ &priv->odvp_attrs[i].attr.attr);
+ kfree(priv->odvp_attrs[i].attr.attr.name);
+ }
+ kfree(priv->odvp_attrs);
+ priv->odvp_attrs = NULL;
+ }
+ kfree(priv->odvp);
+ priv->odvp = NULL;
+ priv->odvp_count = 0;
+}
+
+static int evaluate_odvp(struct int3400_thermal_priv *priv)
+{
+ struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj = NULL;
+ acpi_status status;
+ int i, ret;
+
+ status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp);
+ if (ACPI_FAILURE(status)) {
+ ret = -EINVAL;
+ goto out_err;
+ }
+
+ obj = odvp.pointer;
+ if (obj->type != ACPI_TYPE_PACKAGE) {
+ ret = -EINVAL;
+ goto out_err;
+ }
+
+ if (priv->odvp == NULL) {
+ priv->odvp_count = obj->package.count;
+ priv->odvp = kmalloc_objs(int, priv->odvp_count);
+ if (!priv->odvp) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+ }
+
+ if (priv->odvp_attrs == NULL) {
+ priv->odvp_attrs = kzalloc_objs(struct odvp_attr,
+ priv->odvp_count);
+ if (!priv->odvp_attrs) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+ for (i = 0; i < priv->odvp_count; i++) {
+ struct odvp_attr *odvp = &priv->odvp_attrs[i];
+
+ sysfs_attr_init(&odvp->attr.attr);
+ odvp->priv = priv;
+ odvp->odvp = i;
+ odvp->attr.attr.name = kasprintf(GFP_KERNEL,
+ "odvp%d", i);
+
+ if (!odvp->attr.attr.name) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+ odvp->attr.attr.mode = 0444;
+ odvp->attr.show = odvp_show;
+ odvp->attr.store = NULL;
+ ret = sysfs_create_file(&priv->pdev->dev.kobj,
+ &odvp->attr.attr);
+ if (ret)
+ goto out_err;
+ }
+ }
+
+ for (i = 0; i < obj->package.count; i++) {
+ if (obj->package.elements[i].type == ACPI_TYPE_INTEGER)
+ priv->odvp[i] = obj->package.elements[i].integer.value;
+ }
+
+ kfree(obj);
+ return 0;
+
+out_err:
+ cleanup_odvp(priv);
+ kfree(obj);
+ return ret;
+}
+
+static void int3400_notify(acpi_handle handle,
+ u32 event,
+ void *data)
+{
+ struct int3400_thermal_priv *priv = data;
+ struct device *dev;
+ char *thermal_prop[5];
+ int therm_event;
+
+ if (!priv)
+ return;
+
+ switch (event) {
+ case INT3400_THERMAL_TABLE_CHANGED:
+ therm_event = THERMAL_TABLE_CHANGED;
+ break;
+ case INT3400_KEEP_ALIVE:
+ therm_event = THERMAL_EVENT_KEEP_ALIVE;
+ break;
+ case INT3400_ODVP_CHANGED:
+ evaluate_odvp(priv);
+ therm_event = THERMAL_DEVICE_POWER_CAPABILITY_CHANGED;
+ break;
+ default:
+ /* Ignore unknown notification codes sent to INT3400 device */
+ return;
+ }
+
+ 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(&dev->kobj, KOBJ_CHANGE, thermal_prop);
+ kfree(thermal_prop[0]);
+ kfree(thermal_prop[1]);
+ kfree(thermal_prop[2]);
+ kfree(thermal_prop[3]);
+}
+
+static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
+ int *temp)
+{
+ *temp = INT3400_FAKE_TEMP;
+ return 0;
+}
+
+static int int3400_thermal_change_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ struct int3400_thermal_priv *priv = thermal_zone_device_priv(thermal);
+ int result = 0;
+ int enabled;
+
+ if (!priv)
+ return -EINVAL;
+
+ 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->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 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 = {
+ .no_hwmon = true,
+};
+
+static void int3400_setup_gddv(struct int3400_thermal_priv *priv)
+{
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+
+ status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL,
+ &buffer);
+ if (ACPI_FAILURE(status) || !buffer.length)
+ return;
+
+ obj = buffer.pointer;
+ if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1
+ || obj->package.elements[0].type != ACPI_TYPE_BUFFER)
+ goto out_free;
+
+ priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,
+ obj->package.elements[0].buffer.length,
+ GFP_KERNEL);
+ if (ZERO_OR_NULL_PTR(priv->data_vault))
+ goto out_free;
+
+ bin_attr_data_vault.private = priv->data_vault;
+ bin_attr_data_vault.size = obj->package.elements[0].buffer.length;
+out_free:
+ kfree(buffer.pointer);
+}
+
+static int int3400_thermal_probe(struct platform_device *pdev)
+{
+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+ struct int3400_thermal_priv *priv;
+ int result;
+
+ if (!adev)
+ return -ENODEV;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+ priv->adev = adev;
+
+ result = int3400_thermal_get_uuids(priv);
+
+ /* Missing IDSP isn't fatal */
+ if (result && result != -ENODEV)
+ goto free_priv;
+
+ priv->current_uuid_index = -1;
+
+ result = acpi_parse_art(priv->adev->handle, &priv->art_count,
+ &priv->arts, true);
+ if (result)
+ dev_dbg(&pdev->dev, "_ART table parsing error\n");
+
+ result = acpi_parse_trt(priv->adev->handle, &priv->trt_count,
+ &priv->trts, true);
+ if (result)
+ dev_dbg(&pdev->dev, "_TRT table parsing error\n");
+
+ platform_set_drvdata(pdev, priv);
+
+ int3400_setup_gddv(priv);
+
+ evaluate_odvp(priv);
+
+ 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;
+ }
+
+ priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(
+ priv->adev->handle);
+
+ result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group);
+ if (result)
+ goto free_rel_misc;
+
+ if (acpi_has_method(priv->adev->handle, "IMOK")) {
+ result = sysfs_create_group(&pdev->dev.kobj, &imok_attribute_group);
+ if (result)
+ goto free_imok;
+ }
+
+ if (!ZERO_OR_NULL_PTR(priv->data_vault)) {
+ result = device_create_bin_file(&pdev->dev, &bin_attr_data_vault);
+ if (result)
+ goto free_uuid;
+ }
+
+ result = acpi_install_notify_handler(
+ priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
+ (void *)priv);
+ if (result)
+ goto free_sysfs;
+
+ result = production_mode_init(priv);
+ if (result)
+ goto free_notify;
+
+ return 0;
+
+free_notify:
+ acpi_remove_notify_handler(priv->adev->handle, ACPI_DEVICE_NOTIFY,
+ int3400_notify);
+free_sysfs:
+ if (!ZERO_OR_NULL_PTR(priv->data_vault)) {
+ device_remove_bin_file(&pdev->dev, &bin_attr_data_vault);
+ kfree(priv->data_vault);
+ }
+free_uuid:
+ sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
+free_imok:
+ sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
+free_rel_misc:
+ if (!priv->rel_misc_dev_res)
+ acpi_thermal_rel_misc_device_remove(priv->adev->handle);
+ thermal_zone_device_unregister(priv->thermal);
+free_art_trt:
+ cleanup_odvp(priv);
+ kfree(priv->trts);
+ kfree(priv->arts);
+free_priv:
+ kfree(priv);
+ return result;
+}
+
+static void int3400_thermal_remove(struct platform_device *pdev)
+{
+ struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
+
+ production_mode_exit(priv);
+
+ acpi_remove_notify_handler(
+ priv->adev->handle, ACPI_DEVICE_NOTIFY,
+ int3400_notify);
+
+ cleanup_odvp(priv);
+
+ if (!priv->rel_misc_dev_res)
+ acpi_thermal_rel_misc_device_remove(priv->adev->handle);
+
+ if (!ZERO_OR_NULL_PTR(priv->data_vault))
+ 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);
+ kfree(priv->data_vault);
+ kfree(priv->trts);
+ kfree(priv->arts);
+ kfree(priv);
+}
+
+static const struct acpi_device_id int3400_thermal_match[] = {
+ {"INT3400", 0},
+ {"INTC1040", 0},
+ {"INTC1041", 0},
+ {"INTC1042", 0},
+ {"INTC1068", 0},
+ {"INTC10A0", 0},
+ {"INTC10D4", 0},
+ {"INTC10FC", 0},
+ {"INTC10F3", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(acpi, int3400_thermal_match);
+
+static struct platform_driver int3400_thermal_driver = {
+ .probe = int3400_thermal_probe,
+ .remove = int3400_thermal_remove,
+ .driver = {
+ .name = "int3400 thermal",
+ .acpi_match_table = ACPI_PTR(int3400_thermal_match),
+ },
+};
+
+module_platform_driver(int3400_thermal_driver);
+
+MODULE_DESCRIPTION("INT3400 Thermal driver");
+MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/intel/int340x_thermal/int3401_thermal.c b/drivers/thermal/intel/int340x_thermal/int3401_thermal.c
new file mode 100644
index 000000000..96d6277a5
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/int3401_thermal.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * INT3401 processor thermal device
+ * Copyright (c) 2020, Intel Corporation.
+ */
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#include "int340x_thermal_zone.h"
+#include "processor_thermal_device.h"
+
+static const struct acpi_device_id int3401_device_ids[] = {
+ {"INT3401", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, int3401_device_ids);
+
+static int int3401_add(struct platform_device *pdev)
+{
+ struct proc_thermal_device *proc_priv;
+ int ret;
+
+ proc_priv = devm_kzalloc(&pdev->dev, sizeof(*proc_priv), GFP_KERNEL);
+ if (!proc_priv)
+ return -ENOMEM;
+
+ ret = proc_thermal_add(&pdev->dev, proc_priv);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, proc_priv);
+
+ return ret;
+}
+
+static void int3401_remove(struct platform_device *pdev)
+{
+ proc_thermal_remove(platform_get_drvdata(pdev));
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int int3401_thermal_suspend(struct device *dev)
+{
+ return proc_thermal_suspend(dev);
+}
+static int int3401_thermal_resume(struct device *dev)
+{
+ return proc_thermal_resume(dev);
+}
+#else
+#define int3401_thermal_suspend NULL
+#define int3401_thermal_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(int3401_proc_thermal_pm, int3401_thermal_suspend,
+ int3401_thermal_resume);
+
+static struct platform_driver int3401_driver = {
+ .probe = int3401_add,
+ .remove = int3401_remove,
+ .driver = {
+ .name = "int3401 thermal",
+ .acpi_match_table = int3401_device_ids,
+ .pm = &int3401_proc_thermal_pm,
+ },
+};
+
+module_platform_driver(int3401_driver);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/int3402_thermal.c b/drivers/thermal/intel/int340x_thermal/int3402_thermal.c
new file mode 100644
index 000000000..57b900058
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/int3402_thermal.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * INT3402 thermal driver for memory temperature reporting
+ *
+ * Copyright (C) 2014, Intel Corporation
+ * Authors: Aaron Lu <aaron.lu@intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#include "int340x_thermal_zone.h"
+
+#define INT3402_PERF_CHANGED_EVENT 0x80
+#define INT3402_THERMAL_EVENT 0x90
+
+struct int3402_thermal_data {
+ acpi_handle *handle;
+ struct int34x_thermal_zone *int340x_zone;
+};
+
+static void int3402_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct int3402_thermal_data *priv = data;
+
+ if (!priv)
+ return;
+
+ switch (event) {
+ case INT3402_PERF_CHANGED_EVENT:
+ break;
+ case INT3402_THERMAL_EVENT:
+ int340x_thermal_zone_device_update(priv->int340x_zone,
+ THERMAL_TRIP_VIOLATED);
+ break;
+ default:
+ break;
+ }
+}
+
+static int int3402_thermal_probe(struct platform_device *pdev)
+{
+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+ struct int3402_thermal_data *d;
+ int ret;
+
+ if (!adev)
+ return -ENODEV;
+
+ if (!acpi_has_method(adev->handle, "_TMP"))
+ return -ENODEV;
+
+ d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
+ if (!d)
+ return -ENOMEM;
+
+ d->int340x_zone = int340x_thermal_zone_add(adev, NULL);
+ if (IS_ERR(d->int340x_zone))
+ return PTR_ERR(d->int340x_zone);
+
+ ret = acpi_install_notify_handler(adev->handle,
+ ACPI_DEVICE_NOTIFY,
+ int3402_notify,
+ d);
+ if (ret) {
+ int340x_thermal_zone_remove(d->int340x_zone);
+ return ret;
+ }
+
+ d->handle = adev->handle;
+ platform_set_drvdata(pdev, d);
+
+ return 0;
+}
+
+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);
+}
+
+static const struct acpi_device_id int3402_thermal_match[] = {
+ {"INT3402", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(acpi, int3402_thermal_match);
+
+static struct platform_driver int3402_thermal_driver = {
+ .probe = int3402_thermal_probe,
+ .remove = int3402_thermal_remove,
+ .driver = {
+ .name = "int3402 thermal",
+ .acpi_match_table = int3402_thermal_match,
+ },
+};
+
+module_platform_driver(int3402_thermal_driver);
+
+MODULE_DESCRIPTION("INT3402 Thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
new file mode 100644
index 000000000..264c9bc8e
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ACPI INT3403 thermal driver
+ * Copyright (c) 2013, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include "int340x_thermal_zone.h"
+
+#define INT3403_TYPE_SENSOR 0x03
+#define INT3403_TYPE_CHARGER 0x0B
+#define INT3403_TYPE_BATTERY 0x0C
+#define INT3403_PERF_CHANGED_EVENT 0x80
+#define INT3403_PERF_TRIP_POINT_CHANGED 0x81
+#define INT3403_THERMAL_EVENT 0x90
+
+/* Preserved structure for future expandbility */
+struct int3403_sensor {
+ struct int34x_thermal_zone *int340x_zone;
+};
+
+struct int3403_cdev {
+ struct thermal_cooling_device *cdev;
+ unsigned long max_state;
+};
+
+struct int3403_priv {
+ struct platform_device *pdev;
+ struct acpi_device *adev;
+ unsigned long long type;
+ void *priv;
+};
+
+static void int3403_notify(acpi_handle handle,
+ u32 event, void *data)
+{
+ struct int3403_priv *priv = data;
+ struct int3403_sensor *obj;
+
+ if (!priv)
+ return;
+
+ obj = priv->priv;
+ if (priv->type != INT3403_TYPE_SENSOR || !obj)
+ return;
+
+ switch (event) {
+ case INT3403_PERF_CHANGED_EVENT:
+ break;
+ case INT3403_THERMAL_EVENT:
+ int340x_thermal_zone_device_update(obj->int340x_zone,
+ THERMAL_TRIP_VIOLATED);
+ break;
+ case INT3403_PERF_TRIP_POINT_CHANGED:
+ int340x_thermal_update_trips(obj->int340x_zone);
+ int340x_thermal_zone_device_update(obj->int340x_zone,
+ THERMAL_TRIP_CHANGED);
+ break;
+ default:
+ dev_dbg(&priv->pdev->dev, "Unsupported event [0x%x]\n", event);
+ break;
+ }
+}
+
+static int int3403_sensor_add(struct int3403_priv *priv)
+{
+ int result = 0;
+ struct int3403_sensor *obj;
+
+ obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return -ENOMEM;
+
+ priv->priv = obj;
+
+ obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL);
+ if (IS_ERR(obj->int340x_zone))
+ return PTR_ERR(obj->int340x_zone);
+
+ result = acpi_install_notify_handler(priv->adev->handle,
+ ACPI_DEVICE_NOTIFY, int3403_notify,
+ (void *)priv);
+ if (result)
+ goto err_free_obj;
+
+ return 0;
+
+ err_free_obj:
+ int340x_thermal_zone_remove(obj->int340x_zone);
+ return result;
+}
+
+static int int3403_sensor_remove(struct int3403_priv *priv)
+{
+ struct int3403_sensor *obj = priv->priv;
+
+ acpi_remove_notify_handler(priv->adev->handle,
+ ACPI_DEVICE_NOTIFY, int3403_notify);
+ int340x_thermal_zone_remove(obj->int340x_zone);
+
+ return 0;
+}
+
+/* INT3403 Cooling devices */
+static int int3403_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct int3403_priv *priv = cdev->devdata;
+ struct int3403_cdev *obj = priv->priv;
+
+ *state = obj->max_state;
+ return 0;
+}
+
+static int int3403_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct int3403_priv *priv = cdev->devdata;
+ unsigned long long level;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level);
+ if (ACPI_SUCCESS(status)) {
+ *state = level;
+ return 0;
+ } else
+ return -EINVAL;
+}
+
+static int
+int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
+{
+ struct int3403_priv *priv = cdev->devdata;
+ acpi_status status;
+
+ status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state);
+ if (ACPI_SUCCESS(status))
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static const struct thermal_cooling_device_ops int3403_cooling_ops = {
+ .get_max_state = int3403_get_max_state,
+ .get_cur_state = int3403_get_cur_state,
+ .set_cur_state = int3403_set_cur_state,
+};
+
+static int int3403_cdev_add(struct int3403_priv *priv)
+{
+ int result = 0;
+ acpi_status status;
+ struct int3403_cdev *obj;
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *p;
+
+ obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return -ENOMEM;
+
+ status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ p = buf.pointer;
+ if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
+ pr_warn("Invalid PPSS data\n");
+ kfree(buf.pointer);
+ return -EFAULT;
+ }
+
+ priv->priv = obj;
+ obj->max_state = p->package.count - 1;
+ obj->cdev =
+ thermal_cooling_device_register(acpi_device_bid(priv->adev),
+ priv, &int3403_cooling_ops);
+ if (IS_ERR(obj->cdev))
+ result = PTR_ERR(obj->cdev);
+
+ kfree(buf.pointer);
+ /* TODO: add ACPI notification support */
+
+ return result;
+}
+
+static int int3403_cdev_remove(struct int3403_priv *priv)
+{
+ struct int3403_cdev *obj = priv->priv;
+
+ thermal_cooling_device_unregister(obj->cdev);
+ return 0;
+}
+
+static int int3403_add(struct platform_device *pdev)
+{
+ struct int3403_priv *priv;
+ int result = 0;
+ unsigned long long tmp;
+ acpi_status status;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+ priv->adev = ACPI_COMPANION(&(pdev->dev));
+ if (!priv->adev) {
+ result = -EINVAL;
+ goto err;
+ }
+
+
+ status = acpi_evaluate_integer(priv->adev->handle, "_TMP",
+ NULL, &tmp);
+ if (ACPI_FAILURE(status)) {
+ status = acpi_evaluate_integer(priv->adev->handle, "PTYP",
+ NULL, &priv->type);
+ if (ACPI_FAILURE(status)) {
+ result = -EINVAL;
+ goto err;
+ }
+ } else {
+ priv->type = INT3403_TYPE_SENSOR;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ switch (priv->type) {
+ case INT3403_TYPE_SENSOR:
+ result = int3403_sensor_add(priv);
+ break;
+ case INT3403_TYPE_CHARGER:
+ case INT3403_TYPE_BATTERY:
+ result = int3403_cdev_add(priv);
+ break;
+ default:
+ result = -EINVAL;
+ }
+
+ if (result)
+ goto err;
+ return result;
+
+err:
+ return result;
+}
+
+static void int3403_remove(struct platform_device *pdev)
+{
+ struct int3403_priv *priv = platform_get_drvdata(pdev);
+
+ switch (priv->type) {
+ case INT3403_TYPE_SENSOR:
+ int3403_sensor_remove(priv);
+ break;
+ case INT3403_TYPE_CHARGER:
+ case INT3403_TYPE_BATTERY:
+ int3403_cdev_remove(priv);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct acpi_device_id int3403_device_ids[] = {
+ {"INT3403", 0},
+ {"INTC1043", 0},
+ {"INTC1046", 0},
+ {"INTC1062", 0},
+ {"INTC1069", 0},
+ {"INTC10A1", 0},
+ {"INTC10D5", 0},
+ {"INTC10FD", 0},
+ {"INTC10F4", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, int3403_device_ids);
+
+static struct platform_driver int3403_driver = {
+ .probe = int3403_add,
+ .remove = int3403_remove,
+ .driver = {
+ .name = "int3403 thermal",
+ .acpi_match_table = int3403_device_ids,
+ },
+};
+
+module_platform_driver(int3403_driver);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ACPI INT3403 thermal driver");
diff --git a/drivers/thermal/intel/int340x_thermal/int3406_thermal.c b/drivers/thermal/intel/int340x_thermal/int3406_thermal.c
new file mode 100644
index 000000000..e21fcbccf
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/int3406_thermal.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * INT3406 thermal driver for display participant device
+ *
+ * Copyright (C) 2016, Intel Corporation
+ * Authors: Aaron Lu <aaron.lu@intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/backlight.h>
+#include <linux/thermal.h>
+#include <acpi/video.h>
+
+#define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80
+
+struct int3406_thermal_data {
+ int upper_limit;
+ int lower_limit;
+ acpi_handle handle;
+ struct acpi_video_device_brightness *br;
+ struct backlight_device *raw_bd;
+ struct thermal_cooling_device *cooling_dev;
+};
+
+/*
+ * According to the ACPI spec,
+ * "Each brightness level is represented by a number between 0 and 100,
+ * and can be thought of as a percentage. For example, 50 can be 50%
+ * power consumption or 50% brightness, as defined by the OEM."
+ *
+ * As int3406 device uses this value to communicate with the native
+ * graphics driver, we make the assumption that it represents
+ * the percentage of brightness only
+ */
+#define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100)
+#define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness)
+
+static int
+int3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev,
+ unsigned long *state)
+{
+ struct int3406_thermal_data *d = cooling_dev->devdata;
+
+ *state = d->upper_limit - d->lower_limit;
+ return 0;
+}
+
+static int
+int3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev,
+ unsigned long state)
+{
+ struct int3406_thermal_data *d = cooling_dev->devdata;
+ int acpi_level, raw_level;
+
+ if (state > d->upper_limit - d->lower_limit)
+ return -EINVAL;
+
+ acpi_level = d->br->levels[d->upper_limit - state];
+
+ raw_level = ACPI_TO_RAW(acpi_level, d);
+
+ return backlight_device_set_brightness(d->raw_bd, raw_level);
+}
+
+static int
+int3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev,
+ unsigned long *state)
+{
+ struct int3406_thermal_data *d = cooling_dev->devdata;
+ int acpi_level;
+ int index;
+
+ acpi_level = RAW_TO_ACPI(d->raw_bd->props.brightness, d);
+
+ /*
+ * There is no 1:1 mapping between the firmware interface level
+ * with the raw interface level, we will have to find one that is
+ * right above it.
+ */
+ for (index = d->lower_limit; index < d->upper_limit; index++) {
+ if (acpi_level <= d->br->levels[index])
+ break;
+ }
+
+ *state = d->upper_limit - index;
+ return 0;
+}
+
+static const struct thermal_cooling_device_ops video_cooling_ops = {
+ .get_max_state = int3406_thermal_get_max_state,
+ .get_cur_state = int3406_thermal_get_cur_state,
+ .set_cur_state = int3406_thermal_set_cur_state,
+};
+
+static int int3406_thermal_get_index(int *array, int nr, int value)
+{
+ int i;
+
+ for (i = 2; i < nr; i++) {
+ if (array[i] == value)
+ break;
+ }
+ return i == nr ? -ENOENT : i;
+}
+
+static void int3406_thermal_get_limit(struct int3406_thermal_data *d)
+{
+ acpi_status status;
+ unsigned long long lower_limit, upper_limit;
+
+ status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit);
+ if (ACPI_SUCCESS(status))
+ d->lower_limit = int3406_thermal_get_index(d->br->levels,
+ d->br->count, lower_limit);
+
+ status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit);
+ if (ACPI_SUCCESS(status))
+ d->upper_limit = int3406_thermal_get_index(d->br->levels,
+ d->br->count, upper_limit);
+
+ /* lower_limit and upper_limit should be always set */
+ d->lower_limit = d->lower_limit > 0 ? d->lower_limit : 2;
+ d->upper_limit = d->upper_limit > 0 ? d->upper_limit : d->br->count - 1;
+}
+
+static void int3406_notify(acpi_handle handle, u32 event, void *data)
+{
+ if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED)
+ int3406_thermal_get_limit(data);
+}
+
+static int int3406_thermal_probe(struct platform_device *pdev)
+{
+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+ struct int3406_thermal_data *d;
+ struct backlight_device *bd;
+ int ret;
+
+ if (!ACPI_HANDLE(&pdev->dev))
+ return -ENODEV;
+
+ d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
+ if (!d)
+ return -ENOMEM;
+ d->handle = ACPI_HANDLE(&pdev->dev);
+
+ bd = backlight_device_get_by_type(BACKLIGHT_RAW);
+ if (!bd)
+ return -ENODEV;
+ d->raw_bd = bd;
+
+ ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br, NULL);
+ if (ret)
+ return ret;
+
+ int3406_thermal_get_limit(d);
+
+ d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev),
+ d, &video_cooling_ops);
+ if (IS_ERR(d->cooling_dev))
+ goto err;
+
+ ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
+ int3406_notify, d);
+ if (ret)
+ goto err_cdev;
+
+ platform_set_drvdata(pdev, d);
+
+ return 0;
+
+err_cdev:
+ thermal_cooling_device_unregister(d->cooling_dev);
+err:
+ kfree(d->br);
+ return -ENODEV;
+}
+
+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);
+}
+
+static const struct acpi_device_id int3406_thermal_match[] = {
+ {"INT3406", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(acpi, int3406_thermal_match);
+
+static struct platform_driver int3406_thermal_driver = {
+ .probe = int3406_thermal_probe,
+ .remove = int3406_thermal_remove,
+ .driver = {
+ .name = "int3406 thermal",
+ .acpi_match_table = int3406_thermal_match,
+ },
+};
+
+module_platform_driver(int3406_thermal_driver);
+
+MODULE_DESCRIPTION("INT3406 Thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
new file mode 100644
index 000000000..965700ee6
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * int340x_thermal_zone.c
+ * Copyright (c) 2015, Intel Corporation.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+#include "int340x_thermal_zone.h"
+
+static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone,
+ int *temp)
+{
+ struct int34x_thermal_zone *d = thermal_zone_device_priv(zone);
+ unsigned long long tmp;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(d->adev->handle, "_TMP", NULL, &tmp);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ if (d->lpat_table) {
+ int conv_temp;
+
+ conv_temp = acpi_lpat_raw_to_temp(d->lpat_table, (int)tmp);
+ if (conv_temp < 0)
+ return conv_temp;
+
+ *temp = conv_temp * 10;
+ } else {
+ /* _TMP returns the temperature in tenths of degrees Kelvin */
+ *temp = deci_kelvin_to_millicelsius(tmp);
+ }
+
+ return 0;
+}
+
+static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
+ const struct thermal_trip *trip, int temp)
+{
+ struct int34x_thermal_zone *d = thermal_zone_device_priv(zone);
+ 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_index > 9)
+ return -EINVAL;
+
+ status = acpi_execute_simple_method(d->adev->handle, name,
+ millicelsius_to_deci_kelvin(temp));
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ return 0;
+}
+
+static void int340x_thermal_critical(struct thermal_zone_device *zone)
+{
+ dev_dbg(thermal_zone_device(zone), "%s: critical temperature reached\n",
+ thermal_zone_device_type(zone));
+}
+
+static int int340x_thermal_read_trips(struct acpi_device *zone_adev,
+ struct thermal_trip *zone_trips,
+ int trip_cnt)
+{
+ int i, ret;
+
+ ret = thermal_acpi_critical_trip_temp(zone_adev,
+ &zone_trips[trip_cnt].temperature);
+ if (!ret) {
+ zone_trips[trip_cnt].type = THERMAL_TRIP_CRITICAL;
+ trip_cnt++;
+ }
+
+ ret = thermal_acpi_hot_trip_temp(zone_adev,
+ &zone_trips[trip_cnt].temperature);
+ if (!ret) {
+ zone_trips[trip_cnt].type = THERMAL_TRIP_HOT;
+ trip_cnt++;
+ }
+
+ ret = thermal_acpi_passive_trip_temp(zone_adev,
+ &zone_trips[trip_cnt].temperature);
+ if (!ret) {
+ zone_trips[trip_cnt].type = THERMAL_TRIP_PASSIVE;
+ trip_cnt++;
+ }
+
+ for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
+ ret = thermal_acpi_active_trip_temp(zone_adev, i,
+ &zone_trips[trip_cnt].temperature);
+ if (ret)
+ break;
+
+ zone_trips[trip_cnt].type = THERMAL_TRIP_ACTIVE;
+ zone_trips[trip_cnt].priv = THERMAL_INT_TO_TRIP_PRIV(i);
+ trip_cnt++;
+ }
+
+ return trip_cnt;
+}
+
+static struct thermal_zone_params int340x_thermal_params = {
+ .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;
+ acpi_status status;
+ int i, ret;
+
+ int34x_zone = kzalloc_obj(*int34x_zone);
+ if (!int34x_zone)
+ return ERR_PTR(-ENOMEM);
+
+ int34x_zone->adev = adev;
+
+ status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt);
+ if (ACPI_SUCCESS(status))
+ int34x_zone->aux_trip_nr = trip_cnt;
+
+ zone_trips = kzalloc_objs(*zone_trips,
+ trip_cnt + INT340X_THERMAL_MAX_TRIP_COUNT);
+ if (!zone_trips) {
+ ret = -ENOMEM;
+ goto err_trips_alloc;
+ }
+
+ 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);
+
+ status = acpi_evaluate_integer(adev->handle, "GTSH", NULL, &hyst);
+ if (ACPI_SUCCESS(status))
+ hyst *= 100;
+ else
+ hyst = 0;
+
+ for (i = 0; i < trip_cnt; ++i)
+ zone_trips[i].hysteresis = hyst;
+
+ 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,
+ 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;
+ }
+ ret = thermal_zone_device_enable(int34x_zone->zone);
+ if (ret)
+ goto err_enable;
+
+ return int34x_zone;
+
+err_enable:
+ thermal_zone_device_unregister(int34x_zone->zone);
+err_thermal_zone:
+ acpi_lpat_free_conversion_table(int34x_zone->lpat_table);
+err_trips_alloc:
+ kfree(int34x_zone);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(int340x_thermal_zone_add);
+
+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);
+}
+EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove);
+
+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;
+ 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;
+
+ thermal_zone_set_trip_temp(int34x_zone->zone, trip, temp);
+
+ return 0;
+}
+
+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);
+
+MODULE_AUTHOR("Aaron Lu <aaron.lu@intel.com>");
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_DESCRIPTION("Intel INT340x common thermal zone handler");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h
new file mode 100644
index 000000000..d504e2710
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * int340x_thermal_zone.h
+ * Copyright (c) 2015, Intel Corporation.
+ */
+
+#ifndef __INT340X_THERMAL_ZONE_H__
+#define __INT340X_THERMAL_ZONE_H__
+
+#include <acpi/acpi_lpat.h>
+
+#define INT340X_THERMAL_MAX_ACT_TRIP_COUNT 10
+#define INT340X_THERMAL_MAX_TRIP_COUNT INT340X_THERMAL_MAX_ACT_TRIP_COUNT + 3
+
+struct active_trip {
+ int temp;
+ int id;
+ bool valid;
+};
+
+struct int34x_thermal_zone {
+ struct acpi_device *adev;
+ int aux_trip_nr;
+ struct thermal_zone_device *zone;
+ void *priv_data;
+ struct acpi_lpat_conversion_table *lpat_table;
+};
+
+struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *,
+ int (*get_temp) (struct thermal_zone_device *, int *));
+void int340x_thermal_zone_remove(struct int34x_thermal_zone *);
+void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone);
+
+static inline void int340x_thermal_zone_set_priv_data(
+ struct int34x_thermal_zone *tzone, void *priv_data)
+{
+ tzone->priv_data = priv_data;
+}
+
+static inline void *int340x_thermal_zone_get_priv_data(
+ struct int34x_thermal_zone *tzone)
+{
+ return tzone->priv_data;
+}
+
+static inline void int340x_thermal_zone_device_update(
+ struct int34x_thermal_zone *tzone,
+ enum thermal_notify_event event)
+{
+ thermal_zone_device_update(tzone->zone, event);
+}
+
+#endif
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 000000000..43cb809c5
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c
@@ -0,0 +1,314 @@
+// 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 |= ((u64)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;
+ u32 value;
+ int ret;
+
+ ret = kstrtou32_from_user(data, count, 0, &value);
+ if (unlikely(ret))
+ return ret;
+
+ 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, ret;
+
+ for (i = 0; i < PTC_MAX_INSTANCES; i++) {
+ ptc_instance[i].offset = ptc_offsets[i];
+ ptc_instance[i].pdev = pdev;
+ ret = ptc_create_groups(pdev, i, &ptc_instance[i]);
+ if (ret) {
+ while (i--)
+ sysfs_remove_group(&pdev->dev.kobj,
+ &ptc_instance[i].ptc_attr_group);
+ return ret;
+ }
+ }
+
+ 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
new file mode 100644
index 000000000..f80dbe2ca
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor_thermal_device.c
+ * Copyright (c) 2014, Intel Corporation.
+ */
+#include <linux/acpi.h>
+#include <linux/intel_tcc.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sysfs.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"
+
+#define DRV_NAME "proc_thermal"
+
+#define POWER_LIMIT_SHOW(index, suffix) \
+static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct proc_thermal_device *proc_dev = dev_get_drvdata(dev); \
+ \
+ return sysfs_emit(buf, "%lu\n",\
+ (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)
+POWER_LIMIT_SHOW(0, tmin_us)
+POWER_LIMIT_SHOW(0, tmax_us)
+
+POWER_LIMIT_SHOW(1, min_uw)
+POWER_LIMIT_SHOW(1, max_uw)
+POWER_LIMIT_SHOW(1, step_uw)
+POWER_LIMIT_SHOW(1, tmin_us)
+POWER_LIMIT_SHOW(1, tmax_us)
+
+static DEVICE_ATTR_RO(power_limit_0_min_uw);
+static DEVICE_ATTR_RO(power_limit_0_max_uw);
+static DEVICE_ATTR_RO(power_limit_0_step_uw);
+static DEVICE_ATTR_RO(power_limit_0_tmin_us);
+static DEVICE_ATTR_RO(power_limit_0_tmax_us);
+
+static DEVICE_ATTR_RO(power_limit_1_min_uw);
+static DEVICE_ATTR_RO(power_limit_1_max_uw);
+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,
+ &dev_attr_power_limit_0_max_uw.attr,
+ &dev_attr_power_limit_1_max_uw.attr,
+ &dev_attr_power_limit_0_step_uw.attr,
+ &dev_attr_power_limit_1_step_uw.attr,
+ &dev_attr_power_limit_0_tmin_us.attr,
+ &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",
+ .is_visible = power_limit_attr_visible,
+};
+
+static ssize_t tcc_offset_degree_celsius_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int offset;
+
+ offset = intel_tcc_get_offset(-1);
+ if (offset < 0)
+ return offset;
+
+ return sysfs_emit(buf, "%d\n", offset);
+}
+
+static ssize_t tcc_offset_degree_celsius_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ unsigned int tcc;
+ u64 val;
+ int err;
+
+ err = rdmsrq_safe(MSR_PLATFORM_INFO, &val);
+ if (err)
+ return err;
+
+ if (!(val & BIT(30)))
+ return -EACCES;
+
+ if (kstrtouint(buf, 0, &tcc))
+ return -EINVAL;
+
+ err = intel_tcc_set_offset(-1, tcc);
+ if (err)
+ return err;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(tcc_offset_degree_celsius);
+
+static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone,
+ int *temp)
+{
+ int cpu;
+ int curr_temp, ret;
+
+ *temp = 0;
+
+ for_each_online_cpu(cpu) {
+ ret = intel_tcc_get_temp(cpu, &curr_temp, false);
+ if (ret < 0)
+ return ret;
+ if (!*temp || curr_temp > *temp)
+ *temp = curr_temp;
+ }
+
+ *temp *= 1000;
+
+ return 0;
+}
+
+static int proc_thermal_read_ppcc(struct proc_thermal_device *proc_priv)
+{
+ int i;
+ acpi_status status;
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *elements, *ppcc;
+ union acpi_object *p;
+ int ret = 0;
+
+ status = acpi_evaluate_object(proc_priv->adev->handle, "PPCC",
+ NULL, &buf);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ p = buf.pointer;
+ if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
+ dev_err(proc_priv->dev, "Invalid PPCC data\n");
+ ret = -EFAULT;
+ goto free_buffer;
+ }
+
+ if (!p->package.count) {
+ dev_err(proc_priv->dev, "Invalid PPCC package size\n");
+ ret = -EFAULT;
+ goto free_buffer;
+ }
+
+ for (i = 0; i < min((int)p->package.count - 1, 2); ++i) {
+ elements = &(p->package.elements[i+1]);
+ if (elements->type != ACPI_TYPE_PACKAGE ||
+ elements->package.count != 6) {
+ ret = -EFAULT;
+ goto free_buffer;
+ }
+ ppcc = elements->package.elements;
+ proc_priv->power_limits[i].index = ppcc[0].integer.value;
+ proc_priv->power_limits[i].min_uw = ppcc[1].integer.value;
+ proc_priv->power_limits[i].max_uw = ppcc[2].integer.value;
+ proc_priv->power_limits[i].tmin_us = ppcc[3].integer.value;
+ proc_priv->power_limits[i].tmax_us = ppcc[4].integer.value;
+ proc_priv->power_limits[i].step_uw = ppcc[5].integer.value;
+ }
+
+free_buffer:
+ kfree(buf.pointer);
+
+ return ret;
+}
+
+#define PROC_POWER_CAPABILITY_CHANGED 0x83
+static void proc_thermal_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct proc_thermal_device *proc_priv = data;
+
+ if (!proc_priv)
+ return;
+
+ switch (event) {
+ case PROC_POWER_CAPABILITY_CHANGED:
+ proc_thermal_read_ppcc(proc_priv);
+ int340x_thermal_zone_device_update(proc_priv->int340x_zone,
+ THERMAL_DEVICE_POWER_CAPABILITY_CHANGED);
+ break;
+ default:
+ dev_dbg(proc_priv->dev, "Unsupported event [0x%x]\n", event);
+ break;
+ }
+}
+
+int proc_thermal_add(struct device *dev, struct proc_thermal_device *proc_priv)
+{
+ struct acpi_device *adev;
+ acpi_status status;
+ unsigned long long tmp;
+ int (*get_temp) (struct thermal_zone_device *, int *) = NULL;
+ int ret;
+
+ adev = ACPI_COMPANION(dev);
+ if (!adev)
+ return -ENODEV;
+
+ proc_priv->dev = dev;
+ proc_priv->adev = adev;
+
+ ret = proc_thermal_read_ppcc(proc_priv);
+ if (ret)
+ return ret;
+
+ status = acpi_evaluate_integer(adev->handle, "_TMP", NULL, &tmp);
+ if (ACPI_FAILURE(status)) {
+ /* there is no _TMP method, add local method */
+ if (intel_tcc_get_tjmax(-1) > 0)
+ get_temp = proc_thermal_get_zone_temp;
+ }
+
+ proc_priv->int340x_zone = int340x_thermal_zone_add(adev, get_temp);
+ if (IS_ERR(proc_priv->int340x_zone)) {
+ return PTR_ERR(proc_priv->int340x_zone);
+ } else
+ ret = 0;
+
+ ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
+ proc_thermal_notify,
+ (void *)proc_priv);
+ if (ret)
+ goto remove_zone;
+
+ ret = sysfs_create_file(&dev->kobj, &dev_attr_tcc_offset_degree_celsius.attr);
+ if (ret)
+ goto remove_notify;
+
+ ret = sysfs_create_group(&dev->kobj, &power_limit_attribute_group);
+ if (ret) {
+ sysfs_remove_file(&dev->kobj, &dev_attr_tcc_offset_degree_celsius.attr);
+ goto remove_notify;
+ }
+
+ return 0;
+
+remove_notify:
+ acpi_remove_notify_handler(adev->handle,
+ ACPI_DEVICE_NOTIFY, proc_thermal_notify);
+remove_zone:
+ int340x_thermal_zone_remove(proc_priv->int340x_zone);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_add);
+
+void proc_thermal_remove(struct proc_thermal_device *proc_priv)
+{
+ acpi_remove_notify_handler(proc_priv->adev->handle,
+ ACPI_DEVICE_NOTIFY, proc_thermal_notify);
+ int340x_thermal_zone_remove(proc_priv->int340x_zone);
+ sysfs_remove_file(&proc_priv->dev->kobj, &dev_attr_tcc_offset_degree_celsius.attr);
+ sysfs_remove_group(&proc_priv->dev->kobj,
+ &power_limit_attribute_group);
+}
+EXPORT_SYMBOL_GPL(proc_thermal_remove);
+
+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);
+
+int proc_thermal_resume(struct device *dev)
+{
+ struct proc_thermal_device *proc_dev;
+
+ proc_dev = dev_get_drvdata(dev);
+ proc_thermal_read_ppcc(proc_dev);
+
+ /* Do not update if saving failed */
+ 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);
+
+#define MCHBAR 0
+
+static int proc_thermal_set_mmio_base(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ int ret;
+
+ ret = pcim_iomap_regions(pdev, 1 << MCHBAR, DRV_NAME);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot reserve PCI memory region\n");
+ return -ENOMEM;
+ }
+
+ proc_priv->mmio_base = pcim_iomap_table(pdev)[MCHBAR];
+
+ return 0;
+}
+
+int proc_thermal_mmio_add(struct pci_dev *pdev,
+ struct proc_thermal_device *proc_priv,
+ kernel_ulong_t feature_mask)
+{
+ int ret;
+
+ proc_priv->mmio_feature_mask = feature_mask;
+
+ if (feature_mask) {
+ ret = proc_thermal_set_mmio_base(pdev, proc_priv);
+ if (ret)
+ return ret;
+ }
+
+ if (feature_mask & PROC_THERMAL_FEATURE_RAPL) {
+ ret = proc_thermal_rapl_add(pdev, proc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add RAPL MMIO interface\n");
+ return ret;
+ }
+ }
+
+ 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_ptc;
+ }
+ }
+
+ 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();
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_mmio_add);
+
+void proc_thermal_mmio_remove(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ 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_DLVR)
+ proc_thermal_rfim_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("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
new file mode 100644
index 000000000..b79937a38
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * processor_thermal_device.h
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#ifndef __PROCESSOR_THERMAL_DEVICE_H__
+#define __PROCESSOR_THERMAL_DEVICE_H__
+
+#include <linux/intel_rapl.h>
+
+#define PCI_DEVICE_ID_INTEL_ADL_THERMAL 0x461d
+#define PCI_DEVICE_ID_INTEL_ARL_S_THERMAL 0xAD03
+#define PCI_DEVICE_ID_INTEL_BDW_THERMAL 0x1603
+#define PCI_DEVICE_ID_INTEL_BSW_THERMAL 0x22DC
+
+#define PCI_DEVICE_ID_INTEL_BXT0_THERMAL 0x0A8C
+#define PCI_DEVICE_ID_INTEL_BXT1_THERMAL 0x1A8C
+#define PCI_DEVICE_ID_INTEL_BXTX_THERMAL 0x4A8C
+#define PCI_DEVICE_ID_INTEL_BXTP_THERMAL 0x5A8C
+
+#define PCI_DEVICE_ID_INTEL_CNL_THERMAL 0x5a03
+#define PCI_DEVICE_ID_INTEL_CFL_THERMAL 0x3E83
+#define PCI_DEVICE_ID_INTEL_GLK_THERMAL 0x318C
+#define PCI_DEVICE_ID_INTEL_HSB_THERMAL 0x0A03
+#define PCI_DEVICE_ID_INTEL_ICL_THERMAL 0x8a03
+#define PCI_DEVICE_ID_INTEL_JSL_THERMAL 0x4E03
+#define PCI_DEVICE_ID_INTEL_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;
+ u32 min_uw;
+ u32 max_uw;
+ u32 tmin_us;
+ u32 tmax_us;
+ u32 step_uw;
+};
+
+struct proc_thermal_device {
+ struct device *dev;
+ struct acpi_device *adev;
+ struct power_config power_limits[2];
+ struct int34x_thermal_zone *int340x_zone;
+ struct intel_soc_dts_sensors *soc_dts;
+ u32 mmio_feature_mask;
+ void __iomem *mmio_base;
+ void *priv_data;
+};
+
+struct rapl_mmio_regs {
+ u64 reg_unit;
+ u64 regs[RAPL_DOMAIN_MAX][RAPL_DOMAIN_REG_MAX];
+ int limits[RAPL_DOMAIN_MAX];
+};
+
+#define PROC_THERMAL_FEATURE_NONE 0x00
+#define PROC_THERMAL_FEATURE_RAPL 0x01
+#define PROC_THERMAL_FEATURE_FIVR 0x02
+#define PROC_THERMAL_FEATURE_DVFS 0x04
+#define PROC_THERMAL_FEATURE_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);
+void proc_thermal_rapl_remove(void);
+#else
+static int __maybe_unused proc_thermal_rapl_add(struct pci_dev *pdev,
+ struct proc_thermal_device *proc_priv)
+{
+ return 0;
+}
+
+static void __maybe_unused proc_thermal_rapl_remove(void)
+{
+}
+#endif
+
+int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
+void proc_thermal_rfim_remove(struct pci_dev *pdev);
+
+int proc_thermal_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
new file mode 100644
index 000000000..c5131423e
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c
@@ -0,0 +1,533 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Processor thermal device for newer processors
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/thermal.h>
+
+#include "int340x_thermal_zone.h"
+#include "processor_thermal_device.h"
+
+#define DRV_NAME "proc_thermal_pci"
+
+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;
+ struct thermal_zone_device *tzone;
+ struct delayed_work work;
+ int stored_thres;
+ int no_legacy;
+};
+
+enum proc_thermal_mmio_type {
+ PROC_THERMAL_MMIO_TJMAX,
+ PROC_THERMAL_MMIO_PP0_TEMP,
+ PROC_THERMAL_MMIO_PP1_TEMP,
+ PROC_THERMAL_MMIO_PKG_TEMP,
+ PROC_THERMAL_MMIO_THRES_0,
+ PROC_THERMAL_MMIO_THRES_1,
+ PROC_THERMAL_MMIO_INT_ENABLE_0,
+ PROC_THERMAL_MMIO_INT_ENABLE_1,
+ PROC_THERMAL_MMIO_INT_STATUS_0,
+ PROC_THERMAL_MMIO_INT_STATUS_1,
+ PROC_THERMAL_MMIO_MAX
+};
+
+struct proc_thermal_mmio_info {
+ enum proc_thermal_mmio_type mmio_type;
+ u64 mmio_addr;
+ u64 shift;
+ u64 mask;
+};
+
+static struct proc_thermal_mmio_info proc_thermal_mmio_info[] = {
+ { PROC_THERMAL_MMIO_TJMAX, 0x599c, 16, 0xff },
+ { PROC_THERMAL_MMIO_PP0_TEMP, 0x597c, 0, 0xff },
+ { PROC_THERMAL_MMIO_PP1_TEMP, 0x5980, 0, 0xff },
+ { PROC_THERMAL_MMIO_PKG_TEMP, 0x5978, 0, 0xff },
+ { PROC_THERMAL_MMIO_THRES_0, 0x5820, 8, 0x7F },
+ { PROC_THERMAL_MMIO_THRES_1, 0x5820, 16, 0x7F },
+ { PROC_THERMAL_MMIO_INT_ENABLE_0, 0x5820, 15, 0x01 },
+ { PROC_THERMAL_MMIO_INT_ENABLE_1, 0x5820, 23, 0x01 },
+ { PROC_THERMAL_MMIO_INT_STATUS_0, 0x7200, 6, 0x01 },
+ { PROC_THERMAL_MMIO_INT_STATUS_1, 0x7200, 8, 0x01 },
+};
+
+/* 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;
+
+static void proc_thermal_mmio_read(struct proc_thermal_pci *pci_info,
+ enum proc_thermal_mmio_type type,
+ u32 *value)
+{
+ *value = ioread32(((u8 __iomem *)pci_info->proc_priv->mmio_base +
+ proc_thermal_mmio_info[type].mmio_addr));
+ *value >>= proc_thermal_mmio_info[type].shift;
+ *value &= proc_thermal_mmio_info[type].mask;
+}
+
+static void proc_thermal_mmio_write(struct proc_thermal_pci *pci_info,
+ enum proc_thermal_mmio_type type,
+ u32 value)
+{
+ u32 current_val;
+ u32 mask;
+
+ current_val = ioread32(((u8 __iomem *)pci_info->proc_priv->mmio_base +
+ proc_thermal_mmio_info[type].mmio_addr));
+ mask = proc_thermal_mmio_info[type].mask << proc_thermal_mmio_info[type].shift;
+ current_val &= ~mask;
+
+ value &= proc_thermal_mmio_info[type].mask;
+ value <<= proc_thermal_mmio_info[type].shift;
+
+ current_val |= value;
+ iowrite32(current_val, ((u8 __iomem *)pci_info->proc_priv->mmio_base +
+ proc_thermal_mmio_info[type].mmio_addr));
+}
+
+/*
+ * To avoid sending two many messages to user space, we have 1 second delay.
+ * On interrupt we are disabling interrupt and enabling after 1 second.
+ * This workload function is delayed by 1 second.
+ */
+static void proc_thermal_threshold_work_fn(struct work_struct *work)
+{
+ struct delayed_work *delayed_work = to_delayed_work(work);
+ struct proc_thermal_pci *pci_info = container_of(delayed_work,
+ struct proc_thermal_pci, work);
+ struct thermal_zone_device *tzone = pci_info->tzone;
+
+ if (tzone)
+ thermal_zone_device_update(tzone, THERMAL_TRIP_VIOLATED);
+
+ /* Enable interrupt flag */
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 1);
+}
+
+static void pkg_thermal_schedule_work(struct delayed_work *work)
+{
+ unsigned long ms = msecs_to_jiffies(notify_delay_ms);
+
+ schedule_delayed_work(work, ms);
+}
+
+static 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;
+ }
+
+ pci_write_config_byte(pci_info->pdev, 0xdc, 0x01);
+
+ return ret;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ struct proc_thermal_pci *pci_info = thermal_zone_device_priv(tzd);
+ u32 _temp;
+
+ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_PKG_TEMP, &_temp);
+ *temp = (unsigned long)_temp * 1000;
+
+ return 0;
+}
+
+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;
+
+ if (temp <= 0) {
+ cancel_delayed_work_sync(&pci_info->work);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 0);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_THRES_0, 0);
+ pci_info->stored_thres = 0;
+ return 0;
+ }
+
+ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_TJMAX, &tjmax);
+ _temp = tjmax - (temp / 1000);
+ if (_temp < 0)
+ return -EINVAL;
+
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_THRES_0, _temp);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 1);
+
+ pci_info->stored_thres = temp;
+
+ return 0;
+}
+
+static int get_trip_temp(struct proc_thermal_pci *pci_info)
+{
+ int temp, tjmax;
+
+ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_THRES_0, &temp);
+ if (!temp)
+ return THERMAL_TEMP_INVALID;
+
+ proc_thermal_mmio_read(pci_info, PROC_THERMAL_MMIO_TJMAX, &tjmax);
+ temp = (tjmax - temp) * 1000;
+
+ return temp;
+}
+
+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 = {
+ .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)
+ 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);
+ if (!proc_priv)
+ return -ENOMEM;
+
+ pci_info = devm_kzalloc(&pdev->dev, sizeof(*pci_info), GFP_KERNEL);
+ if (!pci_info)
+ return -ENOMEM;
+
+ pci_info->pdev = pdev;
+ ret = pcim_enable_device(pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "error: could not enable device\n");
+ return ret;
+ }
+
+ pci_set_master(pdev);
+
+ INIT_DELAYED_WORK(&pci_info->work, proc_thermal_threshold_work_fn);
+
+ 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)
+ 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, pci_info,
+ &tzone_ops,
+ &tzone_params, 0, 0);
+ if (IS_ERR(pci_info->tzone)) {
+ ret = PTR_ERR(pci_info->tzone);
+ goto err_del_legacy;
+ }
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_MSI_SUPPORT)
+ use_msi = true;
+
+ 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)
+ goto err_ret_tzone;
+ }
+
+ ret = thermal_zone_device_enable(pci_info->tzone);
+ if (ret)
+ goto err_free_vectors;
+
+ return 0;
+
+err_free_vectors:
+ if (msi_irq)
+ proc_thermal_free_msi(pdev, pci_info);
+err_ret_tzone:
+ thermal_zone_device_unregister(pci_info->tzone);
+err_del_legacy:
+ if (!pci_info->no_legacy)
+ proc_thermal_remove(proc_priv);
+ proc_thermal_mmio_remove(pdev, proc_priv);
+
+ return ret;
+}
+
+static void proc_thermal_pci_remove(struct pci_dev *pdev)
+{
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+ struct proc_thermal_pci *pci_info = proc_priv->priv_data;
+
+ cancel_delayed_work_sync(&pci_info->work);
+
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_THRES_0, 0);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 0);
+
+ 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);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int proc_thermal_pci_suspend(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct proc_thermal_device *proc_priv;
+ struct proc_thermal_pci *pci_info;
+
+ proc_priv = pci_get_drvdata(pdev);
+ pci_info = proc_priv->priv_data;
+
+ if (!pci_info->no_legacy)
+ return proc_thermal_suspend(dev);
+
+ return 0;
+}
+static int proc_thermal_pci_resume(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct proc_thermal_device *proc_priv;
+ struct proc_thermal_pci *pci_info;
+
+ proc_priv = pci_get_drvdata(pdev);
+ pci_info = proc_priv->priv_data;
+
+ if (pci_info->stored_thres) {
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_THRES_0,
+ pci_info->stored_thres / 1000);
+ proc_thermal_mmio_write(pci_info, PROC_THERMAL_MMIO_INT_ENABLE_0, 1);
+ }
+
+ if (!pci_info->no_legacy)
+ return proc_thermal_resume(dev);
+
+ return 0;
+}
+#else
+#define proc_thermal_pci_suspend NULL
+#define proc_thermal_pci_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(proc_thermal_pci_pm, proc_thermal_pci_suspend,
+ proc_thermal_pci_resume);
+
+static const struct pci_device_id proc_thermal_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, ADL_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_DVFS | PROC_THERMAL_FEATURE_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 |
+ PROC_THERMAL_FEATURE_SOC_POWER_SLIDER) },
+ { 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) },
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids);
+
+static struct pci_driver proc_thermal_pci_driver = {
+ .name = DRV_NAME,
+ .probe = proc_thermal_pci_probe,
+ .remove = proc_thermal_pci_remove,
+ .id_table = proc_thermal_pci_ids,
+ .driver.pm = &proc_thermal_pci_pm,
+};
+
+module_pci_driver(proc_thermal_pci_driver);
+
+MODULE_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
new file mode 100644
index 000000000..ccfdd2f9d
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci_legacy.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * B0D4 processor thermal device
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/thermal.h>
+
+#include "int340x_thermal_zone.h"
+#include "processor_thermal_device.h"
+#include "../intel_soc_dts_iosf.h"
+
+#define DRV_NAME "proc_thermal"
+
+static irqreturn_t proc_thermal_pci_msi_irq(int irq, void *devid)
+{
+ struct proc_thermal_device *proc_priv;
+ struct pci_dev *pdev = devid;
+
+ proc_priv = pci_get_drvdata(pdev);
+
+ intel_soc_dts_iosf_interrupt_handler(proc_priv->soc_dts);
+
+ return IRQ_HANDLED;
+}
+
+static int proc_thermal_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct proc_thermal_device *proc_priv;
+ int ret;
+
+ ret = pcim_enable_device(pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "error: could not enable device\n");
+ return ret;
+ }
+
+ proc_priv = devm_kzalloc(&pdev->dev, sizeof(*proc_priv), GFP_KERNEL);
+ if (!proc_priv)
+ return -ENOMEM;
+
+ ret = proc_thermal_add(&pdev->dev, proc_priv);
+ if (ret)
+ return ret;
+
+ pci_set_drvdata(pdev, proc_priv);
+
+ if (pdev->device == PCI_DEVICE_ID_INTEL_BSW_THERMAL) {
+ /*
+ * Enumerate additional DTS sensors available via IOSF.
+ * But we are not treating as a failure condition, if
+ * there are no aux DTSs enabled or fails. This driver
+ * already exposes sensors, which can be accessed via
+ * ACPI/MSR. So we don't want to fail for auxiliary DTSs.
+ */
+ proc_priv->soc_dts = intel_soc_dts_iosf_init(
+ INTEL_SOC_DTS_INTERRUPT_MSI, false, 0);
+
+ if (!IS_ERR(proc_priv->soc_dts) && pdev->irq) {
+ ret = pci_enable_msi(pdev);
+ if (!ret) {
+ ret = request_threaded_irq(pdev->irq, NULL,
+ proc_thermal_pci_msi_irq,
+ IRQF_ONESHOT, "proc_thermal",
+ pdev);
+ if (ret) {
+ intel_soc_dts_iosf_exit(
+ proc_priv->soc_dts);
+ pci_disable_msi(pdev);
+ proc_priv->soc_dts = NULL;
+ }
+ }
+ } else
+ dev_err(&pdev->dev, "No auxiliary DTSs enabled\n");
+ } else {
+
+ }
+
+ ret = proc_thermal_mmio_add(pdev, proc_priv, id->driver_data);
+ if (ret) {
+ proc_thermal_remove(proc_priv);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void proc_thermal_pci_remove(struct pci_dev *pdev)
+{
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+
+ if (proc_priv->soc_dts) {
+ intel_soc_dts_iosf_exit(proc_priv->soc_dts);
+ if (pdev->irq) {
+ free_irq(pdev->irq, pdev);
+ pci_disable_msi(pdev);
+ }
+ }
+
+ proc_thermal_mmio_remove(pdev, proc_priv);
+ proc_thermal_remove(proc_priv);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int proc_thermal_pci_suspend(struct device *dev)
+{
+ return proc_thermal_suspend(dev);
+}
+static int proc_thermal_pci_resume(struct device *dev)
+{
+ return proc_thermal_resume(dev);
+}
+#else
+#define proc_thermal_pci_suspend NULL
+#define proc_thermal_pci_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(proc_thermal_pci_pm, proc_thermal_pci_suspend,
+ proc_thermal_pci_resume);
+
+static const struct pci_device_id proc_thermal_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, BDW_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BSW_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXT0_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXT1_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXTX_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, BXTP_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, CNL_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, CFL_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, GLK_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, HSB_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, ICL_THERMAL, PROC_THERMAL_FEATURE_RAPL) },
+ { PCI_DEVICE_DATA(INTEL, JSL_THERMAL, 0) },
+ { PCI_DEVICE_DATA(INTEL, SKL_THERMAL, PROC_THERMAL_FEATURE_RAPL) },
+ { PCI_DEVICE_DATA(INTEL, TGL_THERMAL, PROC_THERMAL_FEATURE_RAPL |
+ PROC_THERMAL_FEATURE_FIVR | PROC_THERMAL_FEATURE_WT_REQ) },
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids);
+
+static struct pci_driver proc_thermal_pci_driver = {
+ .name = DRV_NAME,
+ .probe = proc_thermal_pci_probe,
+ .remove = proc_thermal_pci_remove,
+ .id_table = proc_thermal_pci_ids,
+ .driver.pm = &proc_thermal_pci_pm,
+};
+
+module_pci_driver(proc_thermal_pci_driver);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c
new file mode 100644
index 000000000..b1d531ef4
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_mbox.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device mailbox driver for Workload type hints
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include "processor_thermal_device.h"
+
+#define MBOX_OFFSET_DATA 0x5810
+#define MBOX_OFFSET_INTERFACE 0x5818
+
+#define MBOX_BUSY_BIT 31
+#define MBOX_RETRY_COUNT 100
+
+static DEFINE_MUTEX(mbox_lock);
+
+static int wait_for_mbox_ready(struct proc_thermal_device *proc_priv)
+{
+ u32 retries, data;
+ int ret;
+
+ /* Poll for rb bit == 0 */
+ retries = MBOX_RETRY_COUNT;
+ do {
+ data = readl(proc_priv->mmio_base + MBOX_OFFSET_INTERFACE);
+ if (data & BIT_ULL(MBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ continue;
+ }
+ ret = 0;
+ break;
+ } while (--retries);
+
+ return ret;
+}
+
+static int send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data)
+{
+ struct proc_thermal_device *proc_priv;
+ u32 reg_data;
+ int ret;
+
+ proc_priv = pci_get_drvdata(pdev);
+ ret = wait_for_mbox_ready(proc_priv);
+ if (ret)
+ 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));
+
+ return wait_for_mbox_ready(proc_priv);
+}
+
+static int send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp)
+{
+ struct proc_thermal_device *proc_priv;
+ u32 reg_data;
+ int ret;
+
+ proc_priv = pci_get_drvdata(pdev);
+ ret = wait_for_mbox_ready(proc_priv);
+ if (ret)
+ return ret;
+
+ /* Write command register */
+ reg_data = BIT_ULL(MBOX_BUSY_BIT) | id;
+ writel(reg_data, (proc_priv->mmio_base + MBOX_OFFSET_INTERFACE));
+
+ ret = wait_for_mbox_ready(proc_priv);
+ if (ret)
+ 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);
+
+ return 0;
+}
+
+int processor_thermal_send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp)
+{
+ int ret;
+
+ 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");
+
+int processor_thermal_send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data)
+{
+ int ret;
+
+ mutex_lock(&mbox_lock);
+ ret = send_mbox_write_cmd(pdev, id, data);
+ mutex_unlock(&mbox_lock);
+
+ 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
+
+int processor_thermal_mbox_interrupt_config(struct pci_dev *pdev, bool enable,
+ int enable_bit, int time_window)
+{
+ u64 data;
+ int ret;
+
+ if (!pdev)
+ return -ENODEV;
+
+ mutex_lock(&mbox_lock);
+
+ /* Do read modify write for MBOX_CAMARILLO_RD_INTR_CONFIG */
+
+ 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;
+ }
+
+ if (time_window >= 0) {
+ data &= ~WLT_TW_MASK;
+
+ /* Program notification delay */
+ data |= ((u64)time_window << SOC_PREDICTION_TW_SHIFT) & WLT_TW_MASK;
+ }
+
+ if (enable)
+ data |= BIT(enable_bit);
+ else
+ data &= ~BIT(enable_bit);
+
+ ret = send_mbox_write_cmd(pdev, MBOX_CAMARILLO_WR_INTR_CONFIG, data);
+ if (ret)
+ dev_err(&pdev->dev, "MBOX_CAMARILLO_WR_INTR_CONFIG failed\n");
+
+unlock:
+ mutex_unlock(&mbox_lock);
+
+ return ret;
+}
+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 000000000..25cdbb6d9
--- /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
new file mode 100644
index 000000000..f8b9745c1
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_rapl.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device RFIM control
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+static struct rapl_if_priv rapl_mmio_priv;
+
+/* bitmasks for RAPL MSRs, used by primitive access functions */
+#define MMIO_ENERGY_STATUS_MASK GENMASK(31, 0)
+
+#define MMIO_POWER_LIMIT1_MASK GENMASK(14, 0)
+#define MMIO_POWER_LIMIT1_ENABLE BIT(15)
+#define MMIO_POWER_LIMIT1_CLAMP BIT(16)
+
+#define MMIO_POWER_LIMIT2_MASK GENMASK_ULL(46, 32)
+#define MMIO_POWER_LIMIT2_ENABLE BIT_ULL(47)
+#define MMIO_POWER_LIMIT2_CLAMP BIT_ULL(48)
+
+#define MMIO_POWER_LOW_LOCK BIT(31)
+#define MMIO_POWER_HIGH_LOCK BIT_ULL(63)
+
+#define MMIO_POWER_LIMIT4_MASK GENMASK(12, 0)
+
+#define MMIO_TIME_WINDOW1_MASK GENMASK_ULL(23, 17)
+#define MMIO_TIME_WINDOW2_MASK GENMASK_ULL(55, 49)
+
+#define MMIO_POWER_INFO_MAX_MASK GENMASK_ULL(46, 32)
+#define MMIO_POWER_INFO_MIN_MASK GENMASK_ULL(30, 16)
+#define MMIO_POWER_INFO_MAX_TIME_WIN_MASK GENMASK_ULL(53, 48)
+#define MMIO_POWER_INFO_THERMAL_SPEC_MASK GENMASK(14, 0)
+
+#define MMIO_PERF_STATUS_THROTTLE_TIME_MASK GENMASK(31, 0)
+#define MMIO_PP_POLICY_MASK GENMASK(4, 0)
+
+/* RAPL primitives for MMIO I/F */
+static struct rapl_primitive_info rpi_mmio[NR_RAPL_PRIMITIVES] = {
+ /* name, mask, shift, msr index, unit divisor */
+ [POWER_LIMIT1] = PRIMITIVE_INFO_INIT(POWER_LIMIT1, MMIO_POWER_LIMIT1_MASK, 0,
+ RAPL_DOMAIN_REG_LIMIT, POWER_UNIT, 0),
+ [POWER_LIMIT2] = PRIMITIVE_INFO_INIT(POWER_LIMIT2, MMIO_POWER_LIMIT2_MASK, 32,
+ RAPL_DOMAIN_REG_LIMIT, POWER_UNIT, 0),
+ [POWER_LIMIT4] = PRIMITIVE_INFO_INIT(POWER_LIMIT4, MMIO_POWER_LIMIT4_MASK, 0,
+ RAPL_DOMAIN_REG_PL4, POWER_UNIT, 0),
+ [ENERGY_COUNTER] = PRIMITIVE_INFO_INIT(ENERGY_COUNTER, MMIO_ENERGY_STATUS_MASK, 0,
+ RAPL_DOMAIN_REG_STATUS, ENERGY_UNIT, 0),
+ [FW_LOCK] = PRIMITIVE_INFO_INIT(FW_LOCK, MMIO_POWER_LOW_LOCK, 31,
+ RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
+ [FW_HIGH_LOCK] = PRIMITIVE_INFO_INIT(FW_LOCK, MMIO_POWER_HIGH_LOCK, 63,
+ RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
+ [PL1_ENABLE] = PRIMITIVE_INFO_INIT(PL1_ENABLE, MMIO_POWER_LIMIT1_ENABLE, 15,
+ RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
+ [PL1_CLAMP] = PRIMITIVE_INFO_INIT(PL1_CLAMP, MMIO_POWER_LIMIT1_CLAMP, 16,
+ RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
+ [PL2_ENABLE] = PRIMITIVE_INFO_INIT(PL2_ENABLE, MMIO_POWER_LIMIT2_ENABLE, 47,
+ RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
+ [PL2_CLAMP] = PRIMITIVE_INFO_INIT(PL2_CLAMP, MMIO_POWER_LIMIT2_CLAMP, 48,
+ RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
+ [TIME_WINDOW1] = PRIMITIVE_INFO_INIT(TIME_WINDOW1, MMIO_TIME_WINDOW1_MASK, 17,
+ RAPL_DOMAIN_REG_LIMIT, TIME_UNIT, 0),
+ [TIME_WINDOW2] = PRIMITIVE_INFO_INIT(TIME_WINDOW2, MMIO_TIME_WINDOW2_MASK, 49,
+ RAPL_DOMAIN_REG_LIMIT, TIME_UNIT, 0),
+ [THERMAL_SPEC_POWER] = PRIMITIVE_INFO_INIT(THERMAL_SPEC_POWER,
+ MMIO_POWER_INFO_THERMAL_SPEC_MASK, 0,
+ RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0),
+ [MAX_POWER] = PRIMITIVE_INFO_INIT(MAX_POWER, MMIO_POWER_INFO_MAX_MASK, 32,
+ RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0),
+ [MIN_POWER] = PRIMITIVE_INFO_INIT(MIN_POWER, MMIO_POWER_INFO_MIN_MASK, 16,
+ RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0),
+ [MAX_TIME_WINDOW] = PRIMITIVE_INFO_INIT(MAX_TIME_WINDOW,
+ MMIO_POWER_INFO_MAX_TIME_WIN_MASK, 48,
+ RAPL_DOMAIN_REG_INFO, TIME_UNIT, 0),
+ [THROTTLED_TIME] = PRIMITIVE_INFO_INIT(THROTTLED_TIME,
+ MMIO_PERF_STATUS_THROTTLE_TIME_MASK, 0,
+ RAPL_DOMAIN_REG_PERF, TIME_UNIT, 0),
+ [PRIORITY_LEVEL] = PRIMITIVE_INFO_INIT(PRIORITY_LEVEL, MMIO_PP_POLICY_MASK, 0,
+ RAPL_DOMAIN_REG_POLICY, ARBITRARY_UNIT, 0),
+};
+
+static const struct rapl_mmio_regs rapl_mmio_default = {
+ .reg_unit = 0x5938,
+ .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) | BIT(POWER_LIMIT4),
+ .limits[RAPL_DOMAIN_DRAM] = BIT(POWER_LIMIT2),
+};
+
+static const struct rapl_defaults rapl_defaults_mmio = {
+ .floor_freq_reg_addr = 0,
+ .check_unit = rapl_default_check_unit,
+ .set_floor_freq = rapl_default_set_floor_freq,
+ .compute_time_window = rapl_default_compute_time_window,
+};
+
+static int rapl_mmio_read_raw(int cpu, struct reg_action *ra, bool atomic)
+{
+ if (!ra->reg.mmio)
+ return -EINVAL;
+
+ ra->value = readq(ra->reg.mmio);
+ ra->value &= ra->mask;
+ return 0;
+}
+
+static int rapl_mmio_write_raw(int cpu, struct reg_action *ra)
+{
+ u64 val;
+
+ if (!ra->reg.mmio)
+ return -EINVAL;
+
+ val = readq(ra->reg.mmio);
+ val &= ~ra->mask;
+ val |= ra->value;
+ 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;
+
+ if (!rapl_regs)
+ return 0;
+
+ for (domain = RAPL_DOMAIN_PACKAGE; domain < RAPL_DOMAIN_MAX; domain++) {
+ for (reg = RAPL_DOMAIN_REG_LIMIT; reg < RAPL_DOMAIN_REG_MAX; reg++)
+ if (rapl_regs->regs[domain][reg])
+ rapl_mmio_priv.regs[domain][reg].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.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;
+ rapl_mmio_priv.defaults = &rapl_defaults_mmio;
+ rapl_mmio_priv.rpi = rpi_mmio;
+
+ rapl_mmio_priv.control_type = powercap_register_control_type(NULL, "intel-rapl-mmio", NULL);
+ if (IS_ERR(rapl_mmio_priv.control_type)) {
+ pr_debug("failed to register powercap control_type.\n");
+ return PTR_ERR(rapl_mmio_priv.control_type);
+ }
+
+ /* 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;
+ }
+
+ 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;
+
+ 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_IMPORT_NS("INTEL_RAPL");
+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
new file mode 100644
index 000000000..962797561
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c
@@ -0,0 +1,523 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device RFIM control
+ * Copyright (c) 2020, Intel Corporation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sysfs.h>
+#include "processor_thermal_device.h"
+
+MODULE_IMPORT_NS("INT340X_THERMAL");
+
+struct mmio_reg {
+ int read_only;
+ u32 offset;
+ int bits;
+ u16 mask;
+ u16 shift;
+};
+
+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",
+ "vco_ref_code_hi",
+ "spread_spectrum_pct",
+ "spread_spectrum_clk_enable",
+ "rfi_vco_ref_code",
+ "fivr_fffc_rev",
+ NULL
+};
+
+static const struct mmio_reg tgl_fivr_mmio_regs[] = {
+ { 0, 0x5A18, 3, 0x7, 11}, /* vco_ref_code_lo */
+ { 0, 0x5A18, 8, 0xFF, 16}, /* vco_ref_code_hi */
+ { 0, 0x5A08, 8, 0xFF, 0}, /* spread_spectrum_pct */
+ { 0, 0x5A08, 1, 0x1, 8}, /* spread_spectrum_clk_enable */
+ { 1, 0x5A10, 12, 0xFFF, 0}, /* rfi_vco_ref_code */
+ { 1, 0x5A14, 2, 0x3, 1}, /* fivr_fffc_rev */
+};
+
+static const char * const dlvr_strings[] = {
+ "dlvr_spread_spectrum_pct",
+ "dlvr_control_mode",
+ "dlvr_control_lock",
+ "dlvr_rfim_enable",
+ "dlvr_freq_select",
+ "dlvr_hardware_rev",
+ "dlvr_freq_mhz",
+ "dlvr_pll_busy",
+ NULL
+};
+
+static const struct mmio_reg dlvr_mmio_regs[] = {
+ { 0, 0x15A08, 5, 0x1F, 0}, /* dlvr_spread_spectrum_pct */
+ { 0, 0x15A08, 1, 0x1, 5}, /* dlvr_control_mode */
+ { 0, 0x15A08, 1, 0x1, 6}, /* dlvr_control_lock */
+ { 0, 0x15A08, 1, 0x1, 7}, /* dlvr_rfim_enable */
+ { 0, 0x15A08, 12, 0xFFF, 8}, /* dlvr_freq_select */
+ { 1, 0x15A10, 2, 0x3, 30}, /* dlvr_hardware_rev */
+ { 1, 0x15A10, 16, 0xFFFF, 0}, /* dlvr_freq_mhz */
+ { 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",
+ "rfi_restriction_err_code",
+ "rfi_restriction_data_rate",
+ "rfi_restriction_data_rate_base",
+ "ddr_data_rate_point_0",
+ "ddr_data_rate_point_1",
+ "ddr_data_rate_point_2",
+ "ddr_data_rate_point_3",
+ "rfi_disable",
+ NULL
+};
+
+static const struct mmio_reg adl_dvfs_mmio_regs[] = {
+ { 0, 0x5A38, 1, 0x1, 31}, /* rfi_restriction_run_busy */
+ { 0, 0x5A38, 7, 0x7F, 24}, /* rfi_restriction_err_code */
+ { 0, 0x5A38, 8, 0xFF, 16}, /* rfi_restriction_data_rate */
+ { 0, 0x5A38, 16, 0xFFFF, 0}, /* rfi_restriction_data_rate_base */
+ { 0, 0x5A30, 10, 0x3FF, 0}, /* ddr_data_rate_point_0 */
+ { 0, 0x5A30, 10, 0x3FF, 10}, /* ddr_data_rate_point_1 */
+ { 0, 0x5A30, 10, 0x3FF, 20}, /* ddr_data_rate_point_2 */
+ { 0, 0x5A30, 10, 0x3FF, 30}, /* ddr_data_rate_point_3 */
+ { 0, 0x5A40, 1, 0x1, 0}, /* rfi_disable */
+};
+
+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 char **match_strs;\
+ int ret, err;\
+ u32 reg_val;\
+ char *str;\
+\
+ proc_priv = pci_get_drvdata(pdev);\
+ if (table == 1) {\
+ match_strs = (const char **)dvfs_strings;\
+ mmio_regs = adl_dvfs_mmio_regs;\
+ } else if (table == 2) { \
+ match_strs = (const char **)dlvr_strings;\
+ } else {\
+ match_strs = (const char **)fivr_strings;\
+ mmio_regs = tgl_fivr_mmio_regs;\
+ } \
+ ret = match_string(match_strs, -1, attr->attr.name);\
+ if (ret < 0)\
+ return ret;\
+ reg_val = readl((void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
+ ret = (reg_val >> mmio_regs[ret].shift) & mmio_regs[ret].mask;\
+ err = get_mapped_string(mapping, attr->attr.name, ret, &str);\
+ if (!err)\
+ return sysfs_emit(buf, "%s\n", str);\
+ if (err == -EOPNOTSUPP)\
+ return sysfs_emit(buf, "%u\n", ret);\
+ return err;\
+}
+
+#define RFIM_STORE(suffix, table)\
+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;\
+ int ret, err;\
+ u32 reg_val;\
+ u32 mask;\
+\
+ proc_priv = pci_get_drvdata(pdev);\
+ if (table == 1) {\
+ match_strs = (const char **)dvfs_strings;\
+ mmio_regs = adl_dvfs_mmio_regs;\
+ } else if (table == 2) { \
+ match_strs = (const char **)dlvr_strings;\
+ } else {\
+ match_strs = (const char **)fivr_strings;\
+ mmio_regs = tgl_fivr_mmio_regs;\
+ } \
+ \
+ ret = match_string(match_strs, -1, attr->attr.name);\
+ if (ret < 0)\
+ return ret;\
+ if (mmio_regs[ret].read_only)\
+ return -EPERM;\
+ err = 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;\
+ reg_val |= (input << mmio_regs[ret].shift);\
+ writel(reg_val, (void __iomem *) (proc_priv->mmio_base + mmio_regs[ret].offset));\
+ return count;\
+}
+
+RFIM_SHOW(vco_ref_code_lo, 0)
+RFIM_SHOW(vco_ref_code_hi, 0)
+RFIM_SHOW(spread_spectrum_pct, 0)
+RFIM_SHOW(spread_spectrum_clk_enable, 0)
+RFIM_SHOW(rfi_vco_ref_code, 0)
+RFIM_SHOW(fivr_fffc_rev, 0)
+
+RFIM_STORE(vco_ref_code_lo, 0)
+RFIM_STORE(vco_ref_code_hi, 0)
+RFIM_STORE(spread_spectrum_pct, 0)
+RFIM_STORE(spread_spectrum_clk_enable, 0)
+RFIM_STORE(rfi_vco_ref_code, 0)
+RFIM_STORE(fivr_fffc_rev, 0)
+
+RFIM_SHOW(dlvr_spread_spectrum_pct, 2)
+RFIM_SHOW(dlvr_control_mode, 2)
+RFIM_SHOW(dlvr_control_lock, 2)
+RFIM_SHOW(dlvr_hardware_rev, 2)
+RFIM_SHOW(dlvr_freq_mhz, 2)
+RFIM_SHOW(dlvr_pll_busy, 2)
+RFIM_SHOW(dlvr_freq_select, 2)
+RFIM_SHOW(dlvr_rfim_enable, 2)
+
+RFIM_STORE(dlvr_spread_spectrum_pct, 2)
+RFIM_STORE(dlvr_rfim_enable, 2)
+RFIM_STORE(dlvr_freq_select, 2)
+RFIM_STORE(dlvr_control_mode, 2)
+RFIM_STORE(dlvr_control_lock, 2)
+
+static DEVICE_ATTR_RW(dlvr_spread_spectrum_pct);
+static DEVICE_ATTR_RW(dlvr_control_mode);
+static DEVICE_ATTR_RW(dlvr_control_lock);
+static DEVICE_ATTR_RW(dlvr_freq_select);
+static DEVICE_ATTR_RO(dlvr_hardware_rev);
+static DEVICE_ATTR_RO(dlvr_freq_mhz);
+static DEVICE_ATTR_RO(dlvr_pll_busy);
+static DEVICE_ATTR_RW(dlvr_rfim_enable);
+
+static struct attribute *dlvr_attrs[] = {
+ &dev_attr_dlvr_spread_spectrum_pct.attr,
+ &dev_attr_dlvr_control_mode.attr,
+ &dev_attr_dlvr_control_lock.attr,
+ &dev_attr_dlvr_freq_select.attr,
+ &dev_attr_dlvr_hardware_rev.attr,
+ &dev_attr_dlvr_freq_mhz.attr,
+ &dev_attr_dlvr_pll_busy.attr,
+ &dev_attr_dlvr_rfim_enable.attr,
+ NULL
+};
+
+static const struct attribute_group dlvr_attribute_group = {
+ .attrs = dlvr_attrs,
+ .name = "dlvr"
+};
+
+static DEVICE_ATTR_RW(vco_ref_code_lo);
+static DEVICE_ATTR_RW(vco_ref_code_hi);
+static DEVICE_ATTR_RW(spread_spectrum_pct);
+static DEVICE_ATTR_RW(spread_spectrum_clk_enable);
+static DEVICE_ATTR_RW(rfi_vco_ref_code);
+static DEVICE_ATTR_RW(fivr_fffc_rev);
+
+static struct attribute *fivr_attrs[] = {
+ &dev_attr_vco_ref_code_lo.attr,
+ &dev_attr_vco_ref_code_hi.attr,
+ &dev_attr_spread_spectrum_pct.attr,
+ &dev_attr_spread_spectrum_clk_enable.attr,
+ &dev_attr_rfi_vco_ref_code.attr,
+ &dev_attr_fivr_fffc_rev.attr,
+ NULL
+};
+
+static const struct attribute_group fivr_attribute_group = {
+ .attrs = fivr_attrs,
+ .name = "fivr"
+};
+
+RFIM_SHOW(rfi_restriction_run_busy, 1)
+RFIM_SHOW(rfi_restriction_err_code, 1)
+RFIM_SHOW(rfi_restriction_data_rate, 1)
+RFIM_SHOW(rfi_restriction_data_rate_base, 1)
+RFIM_SHOW(ddr_data_rate_point_0, 1)
+RFIM_SHOW(ddr_data_rate_point_1, 1)
+RFIM_SHOW(ddr_data_rate_point_2, 1)
+RFIM_SHOW(ddr_data_rate_point_3, 1)
+RFIM_SHOW(rfi_disable, 1)
+
+RFIM_STORE(rfi_restriction_run_busy, 1)
+RFIM_STORE(rfi_restriction_err_code, 1)
+RFIM_STORE(rfi_restriction_data_rate, 1)
+RFIM_STORE(rfi_restriction_data_rate_base, 1)
+RFIM_STORE(rfi_disable, 1)
+
+static DEVICE_ATTR_RW(rfi_restriction_run_busy);
+static DEVICE_ATTR_RW(rfi_restriction_err_code);
+static DEVICE_ATTR_RW(rfi_restriction_data_rate);
+static DEVICE_ATTR_RW(rfi_restriction_data_rate_base);
+static DEVICE_ATTR_RO(ddr_data_rate_point_0);
+static DEVICE_ATTR_RO(ddr_data_rate_point_1);
+static DEVICE_ATTR_RO(ddr_data_rate_point_2);
+static DEVICE_ATTR_RO(ddr_data_rate_point_3);
+static DEVICE_ATTR_RW(rfi_disable);
+
+static ssize_t rfi_restriction_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u16 id = 0x0008;
+ u32 input;
+ int ret;
+
+ ret = kstrtou32(buf, 10, &input);
+ if (ret)
+ return ret;
+
+ ret = processor_thermal_send_mbox_write_cmd(to_pci_dev(dev), id, input);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t rfi_restriction_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u16 id = 0x0007;
+ u64 resp;
+ int ret;
+
+ ret = processor_thermal_send_mbox_read_cmd(to_pci_dev(dev), id, &resp);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%llu\n", resp);
+}
+
+ /* ddr_data_rate */
+static const struct mmio_reg nvl_ddr_data_rate_reg = { 1, 0xE0, 10, 0x3FF, 2};
+
+static const struct mmio_reg *ddr_data_rate_reg;
+
+static ssize_t ddr_data_rate_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u64 resp;
+
+ if (ddr_data_rate_reg) {
+ u16 reg_val;
+
+ pci_read_config_word(to_pci_dev(dev), ddr_data_rate_reg->offset, &reg_val);
+ resp = (reg_val >> ddr_data_rate_reg->shift) & ddr_data_rate_reg->mask;
+ resp = (resp * 3333) / 100;
+ } else {
+ const u16 id = 0x0107;
+ int ret;
+
+ ret = processor_thermal_send_mbox_read_cmd(to_pci_dev(dev), id, &resp);
+ if (ret)
+ return ret;
+ }
+
+ return sysfs_emit(buf, "%llu\n", resp);
+}
+
+static DEVICE_ATTR_RW(rfi_restriction);
+static DEVICE_ATTR_RO(ddr_data_rate);
+
+static struct attribute *dvfs_attrs[] = {
+ &dev_attr_rfi_restriction_run_busy.attr,
+ &dev_attr_rfi_restriction_err_code.attr,
+ &dev_attr_rfi_restriction_data_rate.attr,
+ &dev_attr_rfi_restriction_data_rate_base.attr,
+ &dev_attr_ddr_data_rate_point_0.attr,
+ &dev_attr_ddr_data_rate_point_1.attr,
+ &dev_attr_ddr_data_rate_point_2.attr,
+ &dev_attr_ddr_data_rate_point_3.attr,
+ &dev_attr_rfi_disable.attr,
+ &dev_attr_ddr_data_rate.attr,
+ &dev_attr_rfi_restriction.attr,
+ NULL
+};
+
+static const struct attribute_group dvfs_attribute_group = {
+ .attrs = dvfs_attrs,
+ .name = "dvfs"
+};
+
+int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ int ret;
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) {
+ ret = sysfs_create_group(&pdev->dev.kobj, &fivr_attribute_group);
+ if (ret)
+ return ret;
+ }
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_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;
+ ddr_data_rate_reg = &nvl_ddr_data_rate_reg;
+ break;
+ default:
+ dlvr_mmio_regs_table = dlvr_mmio_regs;
+ break;
+ }
+ ret = sysfs_create_group(&pdev->dev.kobj, &dlvr_attribute_group);
+ if (ret) {
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR)
+ sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group);
+ return ret;
+ }
+ }
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS) {
+ ret = sysfs_create_group(&pdev->dev.kobj, &dvfs_attribute_group);
+ if (ret) {
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR)
+ sysfs_remove_group(&pdev->dev.kobj, &dlvr_attribute_group);
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR)
+ sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rfim_add);
+
+void proc_thermal_rfim_remove(struct pci_dev *pdev)
+{
+ struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR)
+ sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR)
+ sysfs_remove_group(&pdev->dev.kobj, &dlvr_attribute_group);
+
+ if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS)
+ sysfs_remove_group(&pdev->dev.kobj, &dvfs_attribute_group);
+}
+EXPORT_SYMBOL_GPL(proc_thermal_rfim_remove);
+
+MODULE_LICENSE("GPL v2");
+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 000000000..91f291627
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
@@ -0,0 +1,290 @@
+// 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)
+{
+ u8 offset;
+ u64 val;
+
+ val = read_soc_slider(proc_priv);
+ val &= ~SLIDER_MASK;
+ val |= FIELD_PREP(SLIDER_MASK, slider) | BIT(SLIDER_ENABLE_BIT);
+
+ if (slider == SOC_SLIDER_VALUE_MINIMUM || slider == SOC_SLIDER_VALUE_MAXIMUM)
+ offset = 0;
+ else
+ offset = slider_offset;
+
+ /* Set the slider offset from module params */
+ val &= ~SLIDER_OFFSET_MASK;
+ val |= FIELD_PREP(SLIDER_OFFSET_MASK, 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 000000000..f8c33e8e5
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c
@@ -0,0 +1,297 @@
+// 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_SLOW_PREDICTION_INT_ENABLE_BIT 22
+#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;
+static u8 wt_slow_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 && !wt_slow_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(struct device *dev, u8 enable_bit, u8 *status,
+ 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,
+ enable_bit,
+ notify_delay);
+ else
+ ret = processor_thermal_mbox_interrupt_config(pdev, false,
+ enable_bit, 0);
+
+ if (ret)
+ goto ret_enable_store;
+
+ ret = size;
+ *status = mode;
+
+ret_enable_store:
+ mutex_unlock(&wt_lock);
+
+ return ret;
+}
+
+static ssize_t workload_hint_enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ return workload_hint_enable(dev, SOC_WT_PREDICTION_INT_ENABLE_BIT, &wt_enable,
+ attr, buf, size);
+}
+static DEVICE_ATTR_RW(workload_hint_enable);
+
+static ssize_t workload_slow_hint_enable_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%d\n", wt_slow_enable);
+}
+
+static ssize_t workload_slow_hint_enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ return workload_hint_enable(dev, SOC_WT_SLOW_PREDICTION_INT_ENABLE_BIT, &wt_slow_enable,
+ attr, buf, size);
+}
+static DEVICE_ATTR_RW(workload_slow_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 umode_t workload_hint_attr_visible(struct kobject *kobj, struct attribute *attr, int unused)
+{
+ if (attr != &dev_attr_workload_slow_hint_enable.attr)
+ return attr->mode;
+
+ switch (to_pci_dev(kobj_to_dev(kobj))->device) {
+ case PCI_DEVICE_ID_INTEL_LNLM_THERMAL:
+ case PCI_DEVICE_ID_INTEL_MTLP_THERMAL:
+ case PCI_DEVICE_ID_INTEL_ARL_S_THERMAL:
+ return 0;
+ default:
+ break;
+ }
+
+ return attr->mode;
+}
+
+static struct attribute *workload_hint_attrs[] = {
+ &dev_attr_workload_type_index.attr,
+ &dev_attr_workload_hint_enable.attr,
+ &dev_attr_workload_slow_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",
+ .is_visible = workload_hint_attr_visible
+};
+
+/*
+ * 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 000000000..2372f5202
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_req.c
@@ -0,0 +1,138 @@
+// 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 <linux/sysfs.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 += sysfs_emit_at(buf, ret, "%s ", workload_types[i++]);
+
+ ret += sysfs_emit_at(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 sysfs_emit(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_bxt_pmic_thermal.c b/drivers/thermal/intel/intel_bxt_pmic_thermal.c
new file mode 100644
index 000000000..aeaefbbd5
--- /dev/null
+++ b/drivers/thermal/intel/intel_bxt_pmic_thermal.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Broxton PMIC thermal driver
+ *
+ * Copyright (C) 2016 Intel Corporation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/mfd/intel_soc_pmic.h>
+
+#define BXTWC_THRM0IRQ 0x4E04
+#define BXTWC_THRM1IRQ 0x4E05
+#define BXTWC_THRM2IRQ 0x4E06
+#define BXTWC_MTHRM0IRQ 0x4E12
+#define BXTWC_MTHRM1IRQ 0x4E13
+#define BXTWC_MTHRM2IRQ 0x4E14
+#define BXTWC_STHRM0IRQ 0x4F19
+#define BXTWC_STHRM1IRQ 0x4F1A
+#define BXTWC_STHRM2IRQ 0x4F1B
+
+struct trip_config_map {
+ u16 irq_reg;
+ u16 irq_en;
+ u16 evt_stat;
+ u8 irq_mask;
+ u8 irq_en_mask;
+ u8 evt_mask;
+ u8 trip_num;
+};
+
+struct thermal_irq_map {
+ char handle[20];
+ int num_trips;
+ const struct trip_config_map *trip_config;
+};
+
+struct pmic_thermal_data {
+ const struct thermal_irq_map *maps;
+ int num_maps;
+};
+
+static const struct trip_config_map bxtwc_str0_trip_config[] = {
+ {
+ .irq_reg = BXTWC_THRM0IRQ,
+ .irq_mask = 0x01,
+ .irq_en = BXTWC_MTHRM0IRQ,
+ .irq_en_mask = 0x01,
+ .evt_stat = BXTWC_STHRM0IRQ,
+ .evt_mask = 0x01,
+ .trip_num = 0
+ },
+ {
+ .irq_reg = BXTWC_THRM0IRQ,
+ .irq_mask = 0x10,
+ .irq_en = BXTWC_MTHRM0IRQ,
+ .irq_en_mask = 0x10,
+ .evt_stat = BXTWC_STHRM0IRQ,
+ .evt_mask = 0x10,
+ .trip_num = 1
+ }
+};
+
+static const struct trip_config_map bxtwc_str1_trip_config[] = {
+ {
+ .irq_reg = BXTWC_THRM0IRQ,
+ .irq_mask = 0x02,
+ .irq_en = BXTWC_MTHRM0IRQ,
+ .irq_en_mask = 0x02,
+ .evt_stat = BXTWC_STHRM0IRQ,
+ .evt_mask = 0x02,
+ .trip_num = 0
+ },
+ {
+ .irq_reg = BXTWC_THRM0IRQ,
+ .irq_mask = 0x20,
+ .irq_en = BXTWC_MTHRM0IRQ,
+ .irq_en_mask = 0x20,
+ .evt_stat = BXTWC_STHRM0IRQ,
+ .evt_mask = 0x20,
+ .trip_num = 1
+ },
+};
+
+static const struct trip_config_map bxtwc_str2_trip_config[] = {
+ {
+ .irq_reg = BXTWC_THRM0IRQ,
+ .irq_mask = 0x04,
+ .irq_en = BXTWC_MTHRM0IRQ,
+ .irq_en_mask = 0x04,
+ .evt_stat = BXTWC_STHRM0IRQ,
+ .evt_mask = 0x04,
+ .trip_num = 0
+ },
+ {
+ .irq_reg = BXTWC_THRM0IRQ,
+ .irq_mask = 0x40,
+ .irq_en = BXTWC_MTHRM0IRQ,
+ .irq_en_mask = 0x40,
+ .evt_stat = BXTWC_STHRM0IRQ,
+ .evt_mask = 0x40,
+ .trip_num = 1
+ },
+};
+
+static const struct trip_config_map bxtwc_str3_trip_config[] = {
+ {
+ .irq_reg = BXTWC_THRM2IRQ,
+ .irq_mask = 0x10,
+ .irq_en = BXTWC_MTHRM2IRQ,
+ .irq_en_mask = 0x10,
+ .evt_stat = BXTWC_STHRM2IRQ,
+ .evt_mask = 0x10,
+ .trip_num = 0
+ },
+};
+
+static const struct thermal_irq_map bxtwc_thermal_irq_map[] = {
+ {
+ .handle = "STR0",
+ .trip_config = bxtwc_str0_trip_config,
+ .num_trips = ARRAY_SIZE(bxtwc_str0_trip_config),
+ },
+ {
+ .handle = "STR1",
+ .trip_config = bxtwc_str1_trip_config,
+ .num_trips = ARRAY_SIZE(bxtwc_str1_trip_config),
+ },
+ {
+ .handle = "STR2",
+ .trip_config = bxtwc_str2_trip_config,
+ .num_trips = ARRAY_SIZE(bxtwc_str2_trip_config),
+ },
+ {
+ .handle = "STR3",
+ .trip_config = bxtwc_str3_trip_config,
+ .num_trips = ARRAY_SIZE(bxtwc_str3_trip_config),
+ },
+};
+
+static const struct pmic_thermal_data bxtwc_thermal_data = {
+ .maps = bxtwc_thermal_irq_map,
+ .num_maps = ARRAY_SIZE(bxtwc_thermal_irq_map),
+};
+
+static irqreturn_t pmic_thermal_irq_handler(int irq, void *data)
+{
+ struct platform_device *pdev = data;
+ struct thermal_zone_device *tzd;
+ struct pmic_thermal_data *td;
+ struct intel_soc_pmic *pmic;
+ struct regmap *regmap;
+ u8 reg_val, mask, irq_stat;
+ u16 reg, evt_stat_reg;
+ int i, j, ret;
+
+ pmic = dev_get_drvdata(pdev->dev.parent);
+ regmap = pmic->regmap;
+ td = (struct pmic_thermal_data *)
+ platform_get_device_id(pdev)->driver_data;
+
+ /* Resolve thermal irqs */
+ for (i = 0; i < td->num_maps; i++) {
+ for (j = 0; j < td->maps[i].num_trips; j++) {
+ reg = td->maps[i].trip_config[j].irq_reg;
+ mask = td->maps[i].trip_config[j].irq_mask;
+ /*
+ * Read the irq register to resolve whether the
+ * interrupt was triggered for this sensor
+ */
+ if (regmap_read(regmap, reg, &ret))
+ return IRQ_HANDLED;
+
+ reg_val = (u8)ret;
+ irq_stat = ((u8)ret & mask);
+
+ if (!irq_stat)
+ continue;
+
+ /*
+ * Read the status register to find out what
+ * event occurred i.e a high or a low
+ */
+ evt_stat_reg = td->maps[i].trip_config[j].evt_stat;
+ if (regmap_read(regmap, evt_stat_reg, &ret))
+ return IRQ_HANDLED;
+
+ tzd = thermal_zone_get_zone_by_name(td->maps[i].handle);
+ if (!IS_ERR(tzd))
+ thermal_zone_device_update(tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ /* Clear the appropriate irq */
+ regmap_write(regmap, reg, reg_val & mask);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int pmic_thermal_probe(struct platform_device *pdev)
+{
+ struct regmap_irq_chip_data *regmap_irq_chip;
+ struct pmic_thermal_data *thermal_data;
+ int ret, irq, virq, i, j, pmic_irq_count;
+ struct intel_soc_pmic *pmic;
+ struct regmap *regmap;
+ struct device *dev;
+ u16 reg;
+ u8 mask;
+
+ dev = &pdev->dev;
+ pmic = dev_get_drvdata(pdev->dev.parent);
+ if (!pmic) {
+ dev_err(dev, "Failed to get struct intel_soc_pmic pointer\n");
+ return -ENODEV;
+ }
+
+ thermal_data = (struct pmic_thermal_data *)
+ platform_get_device_id(pdev)->driver_data;
+ if (!thermal_data) {
+ dev_err(dev, "No thermal data initialized!!\n");
+ return -ENODEV;
+ }
+
+ regmap = pmic->regmap;
+ regmap_irq_chip = pmic->irq_chip_data;
+
+ pmic_irq_count = 0;
+ while ((irq = platform_get_irq(pdev, pmic_irq_count)) != -ENXIO) {
+ virq = regmap_irq_get_virq(regmap_irq_chip, irq);
+ if (virq < 0) {
+ dev_err(dev, "failed to get virq by irq %d\n", irq);
+ return virq;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, virq,
+ NULL, pmic_thermal_irq_handler,
+ IRQF_ONESHOT, "pmic_thermal", pdev);
+
+ if (ret)
+ return ret;
+ pmic_irq_count++;
+ }
+
+ /* Enable thermal interrupts */
+ for (i = 0; i < thermal_data->num_maps; i++) {
+ for (j = 0; j < thermal_data->maps[i].num_trips; j++) {
+ reg = thermal_data->maps[i].trip_config[j].irq_en;
+ mask = thermal_data->maps[i].trip_config[j].irq_en_mask;
+ ret = regmap_update_bits(regmap, reg, mask, 0x00);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct platform_device_id pmic_thermal_id_table[] = {
+ {
+ .name = "bxt_wcove_thermal",
+ .driver_data = (kernel_ulong_t)&bxtwc_thermal_data,
+ },
+ {},
+};
+
+static struct platform_driver pmic_thermal_driver = {
+ .probe = pmic_thermal_probe,
+ .driver = {
+ .name = "pmic_thermal",
+ },
+ .id_table = pmic_thermal_id_table,
+};
+
+MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table);
+module_platform_driver(pmic_thermal_driver);
+
+MODULE_AUTHOR("Yegnesh S Iyer <yegnesh.s.iyer@intel.com>");
+MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/intel_hfi.c b/drivers/thermal/intel/intel_hfi.c
new file mode 100644
index 000000000..3273b8fe3
--- /dev/null
+++ b/drivers/thermal/intel/intel_hfi.c
@@ -0,0 +1,732 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hardware Feedback Interface Driver
+ *
+ * Copyright (c) 2021, Intel Corporation.
+ *
+ * Authors: Aubrey Li <aubrey.li@linux.intel.com>
+ * Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
+ *
+ *
+ * The Hardware Feedback Interface provides a performance and energy efficiency
+ * capability information for each CPU in the system. Depending on the processor
+ * model, hardware may periodically update these capabilities as a result of
+ * changes in the operating conditions (e.g., power limits or thermal
+ * constraints). On other processor models, there is a single HFI update
+ * at boot.
+ *
+ * This file provides functionality to process HFI updates and relay these
+ * updates to userspace.
+ */
+
+#define pr_fmt(fmt) "intel-hfi: " fmt
+
+#include <linux/bitops.h>
+#include <linux/cpufeature.h>
+#include <linux/cpumask.h>
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/math.h>
+#include <linux/mutex.h>
+#include <linux/percpu-defs.h>
+#include <linux/printk.h>
+#include <linux/processor.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/suspend.h>
+#include <linux/string.h>
+#include <linux/syscore_ops.h>
+#include <linux/topology.h>
+#include <linux/workqueue.h>
+
+#include <asm/cpuid/api.h>
+#include <asm/msr.h>
+
+#include "intel_hfi.h"
+#include "thermal_interrupt.h"
+
+#include "../thermal_netlink.h"
+
+/* Hardware Feedback Interface MSR configuration bits */
+#define HW_FEEDBACK_PTR_VALID_BIT BIT(0)
+#define HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT BIT(0)
+
+/* CPUID detection and enumeration definitions for HFI */
+
+#define CPUID_HFI_LEAF 6
+
+union hfi_capabilities {
+ struct {
+ u8 performance:1;
+ u8 energy_efficiency:1;
+ u8 __reserved:6;
+ } split;
+ u8 bits;
+};
+
+union cpuid6_edx {
+ struct {
+ union hfi_capabilities capabilities;
+ u32 table_pages:4;
+ u32 __reserved:4;
+ s32 index:16;
+ } split;
+ u32 full;
+};
+
+/**
+ * struct hfi_cpu_data - HFI capabilities per CPU
+ * @perf_cap: Performance capability
+ * @ee_cap: Energy efficiency capability
+ *
+ * Capabilities of a logical processor in the HFI table. These capabilities are
+ * unitless.
+ */
+struct hfi_cpu_data {
+ u8 perf_cap;
+ u8 ee_cap;
+} __packed;
+
+/**
+ * struct hfi_hdr - Header of the HFI table
+ * @perf_updated: Hardware updated performance capabilities
+ * @ee_updated: Hardware updated energy efficiency capabilities
+ *
+ * Properties of the data in an HFI table.
+ */
+struct hfi_hdr {
+ u8 perf_updated;
+ u8 ee_updated;
+} __packed;
+
+/**
+ * struct hfi_instance - Representation of an HFI instance (i.e., a table)
+ * @local_table: Base of the local copy of the HFI table
+ * @timestamp: Timestamp of the last update of the local table.
+ * Located at the base of the local table.
+ * @hdr: Base address of the header of the local table
+ * @data: Base address of the data of the local table
+ * @cpus: CPUs represented in this HFI table instance
+ * @hw_table: Pointer to the HFI table of this instance
+ * @update_work: Delayed work to process HFI updates
+ * @table_lock: Lock to protect acceses to the table of this instance
+ * @event_lock: Lock to process HFI interrupts
+ *
+ * A set of parameters to parse and navigate a specific HFI table.
+ */
+struct hfi_instance {
+ union {
+ void *local_table;
+ u64 *timestamp;
+ };
+ void *hdr;
+ void *data;
+ cpumask_var_t cpus;
+ void *hw_table;
+ struct delayed_work update_work;
+ raw_spinlock_t table_lock;
+ raw_spinlock_t event_lock;
+};
+
+/**
+ * struct hfi_features - Supported HFI features
+ * @nr_table_pages: Size of the HFI table in 4KB pages
+ * @cpu_stride: Stride size to locate the capability data of a logical
+ * processor within the table (i.e., row stride)
+ * @hdr_size: Size of the table header
+ *
+ * Parameters and supported features that are common to all HFI instances
+ */
+struct hfi_features {
+ size_t nr_table_pages;
+ unsigned int cpu_stride;
+ unsigned int hdr_size;
+};
+
+/**
+ * struct hfi_cpu_info - Per-CPU attributes to consume HFI data
+ * @index: Row of this CPU in its HFI table
+ * @hfi_instance: Attributes of the HFI table to which this CPU belongs
+ *
+ * Parameters to link a logical processor to an HFI table and a row within it.
+ */
+struct hfi_cpu_info {
+ s16 index;
+ struct hfi_instance *hfi_instance;
+};
+
+static DEFINE_PER_CPU(struct hfi_cpu_info, hfi_cpu_info) = { .index = -1 };
+
+static int max_hfi_instances;
+static 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_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)
+{
+ int cpu, i = 0;
+
+ raw_spin_lock_irq(&hfi_instance->table_lock);
+ for_each_cpu(cpu, hfi_instance->cpus) {
+ struct hfi_cpu_data *caps;
+ s16 index;
+
+ index = per_cpu(hfi_cpu_info, cpu).index;
+ caps = hfi_instance->data + index * hfi_features.cpu_stride;
+ cpu_caps[i].cpu = cpu;
+
+ /*
+ * Scale performance and energy efficiency to
+ * the [0, 1023] interval that thermal netlink uses.
+ */
+ cpu_caps[i].performance = caps->perf_cap << 2;
+ cpu_caps[i].efficiency = caps->ee_cap << 2;
+
+ ++i;
+ }
+ raw_spin_unlock_irq(&hfi_instance->table_lock);
+}
+
+/*
+ * Call update_capabilities() when there are changes in the HFI table.
+ */
+static void update_capabilities(struct hfi_instance *hfi_instance)
+{
+ struct thermal_genl_cpu_caps *cpu_caps;
+ int i = 0, cpu_count;
+
+ /* CPUs may come online/offline while processing an HFI update. */
+ mutex_lock(&hfi_instance_lock);
+
+ cpu_count = cpumask_weight(hfi_instance->cpus);
+
+ /* No CPUs to report in this hfi_instance. */
+ if (!cpu_count)
+ goto out;
+
+ cpu_caps = kzalloc_objs(*cpu_caps, cpu_count);
+ if (!cpu_caps)
+ goto out;
+
+ get_hfi_caps(hfi_instance, cpu_caps);
+
+ if (cpu_count < HFI_THERMNL_CAPS_PER_EVENT)
+ goto last_cmd;
+
+ /* Process complete chunks of HFI_THERMNL_CAPS_PER_EVENT capabilities. */
+ for (i = 0;
+ (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;
+
+last_cmd:
+ /* Process the remaining capabilities if any. */
+ if (cpu_count)
+ thermal_genl_cpu_capability_event(cpu_count, &cpu_caps[i]);
+
+ kfree(cpu_caps);
+out:
+ mutex_unlock(&hfi_instance_lock);
+}
+
+static void hfi_update_work_fn(struct work_struct *work)
+{
+ struct hfi_instance *hfi_instance;
+
+ hfi_instance = container_of(to_delayed_work(work), struct hfi_instance,
+ update_work);
+
+ update_capabilities(hfi_instance);
+}
+
+void intel_hfi_process_event(__u64 pkg_therm_status_msr_val)
+{
+ struct hfi_instance *hfi_instance;
+ int cpu = smp_processor_id();
+ struct hfi_cpu_info *info;
+ u64 new_timestamp, msr, hfi;
+
+ if (!pkg_therm_status_msr_val)
+ return;
+
+ info = &per_cpu(hfi_cpu_info, cpu);
+ if (!info)
+ return;
+
+ /*
+ * A CPU is linked to its HFI instance before the thermal vector in the
+ * local APIC is unmasked. Hence, info->hfi_instance cannot be NULL
+ * when receiving an HFI event.
+ */
+ hfi_instance = info->hfi_instance;
+ if (unlikely(!hfi_instance)) {
+ pr_debug("Received event on CPU %d but instance was null", cpu);
+ return;
+ }
+
+ /*
+ * On most systems, all CPUs in the package receive a package-level
+ * thermal interrupt when there is an HFI update. It is sufficient to
+ * let a single CPU to acknowledge the update and queue work to
+ * process it. The remaining CPUs can resume their work.
+ */
+ if (!raw_spin_trylock(&hfi_instance->event_lock))
+ return;
+
+ rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr);
+ hfi = msr & PACKAGE_THERM_STATUS_HFI_UPDATED;
+ if (!hfi) {
+ raw_spin_unlock(&hfi_instance->event_lock);
+ return;
+ }
+
+ /*
+ * Ack duplicate update. Since there is an active HFI
+ * status from HW, it must be a new event, not a case
+ * where a lagging CPU entered the locked region.
+ */
+ new_timestamp = *(u64 *)hfi_instance->hw_table;
+ if (*hfi_instance->timestamp == new_timestamp) {
+ thermal_clear_package_intr_status(PACKAGE_LEVEL, PACKAGE_THERM_STATUS_HFI_UPDATED);
+ raw_spin_unlock(&hfi_instance->event_lock);
+ return;
+ }
+
+ raw_spin_lock(&hfi_instance->table_lock);
+
+ /*
+ * Copy the updated table into our local copy. This includes the new
+ * timestamp.
+ */
+ memcpy(hfi_instance->local_table, hfi_instance->hw_table,
+ hfi_features.nr_table_pages << PAGE_SHIFT);
+
+ /*
+ * Let hardware know that we are done reading the HFI table and it is
+ * free to update it again.
+ */
+ thermal_clear_package_intr_status(PACKAGE_LEVEL, PACKAGE_THERM_STATUS_HFI_UPDATED);
+
+ raw_spin_unlock(&hfi_instance->table_lock);
+ raw_spin_unlock(&hfi_instance->event_lock);
+
+ queue_delayed_work(hfi_updates_wq, &hfi_instance->update_work,
+ msecs_to_jiffies(HFI_UPDATE_DELAY_MS));
+}
+
+static void init_hfi_cpu_index(struct hfi_cpu_info *info)
+{
+ union cpuid6_edx edx;
+
+ /* Do not re-read @cpu's index if it has already been initialized. */
+ if (info->index > -1)
+ return;
+
+ edx.full = cpuid_edx(CPUID_HFI_LEAF);
+ info->index = edx.split.index;
+}
+
+/*
+ * The format of the HFI table depends on the number of capabilities that the
+ * hardware supports. Keep a data structure to navigate the table.
+ */
+static void init_hfi_instance(struct hfi_instance *hfi_instance)
+{
+ /* The HFI header is below the time-stamp. */
+ hfi_instance->hdr = hfi_instance->local_table +
+ sizeof(*hfi_instance->timestamp);
+
+ /* The HFI data starts below the header. */
+ hfi_instance->data = hfi_instance->hdr + hfi_features.hdr_size;
+}
+
+/* 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 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 package.
+ *
+ * This function is called before enabling the thermal vector in the local APIC
+ * in order to ensure that @cpu has an associated HFI instance when it receives
+ * an HFI event.
+ */
+void intel_hfi_online(unsigned int cpu)
+{
+ struct hfi_instance *hfi_instance;
+ struct hfi_cpu_info *info;
+ u16 pkg_id;
+
+ /* Nothing to do if hfi_instances are missing. */
+ if (!hfi_instances)
+ return;
+
+ /*
+ * 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);
+ pkg_id = topology_logical_package_id(cpu);
+ hfi_instance = info->hfi_instance;
+ if (!hfi_instance) {
+ if (pkg_id >= max_hfi_instances)
+ return;
+
+ 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 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 and enable the instance
+ * if needed.
+ */
+ mutex_lock(&hfi_instance_lock);
+ 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;
+
+ /*
+ * Allocate memory to keep a local copy of the table that
+ * hardware generates.
+ */
+ hfi_instance->local_table = kzalloc(hfi_features.nr_table_pages << PAGE_SHIFT,
+ GFP_KERNEL);
+ if (!hfi_instance->local_table)
+ goto free_hw_table;
+
+ 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 this HFI instance if this is its first online CPU and
+ * there are user-space clients of thermal events.
+ */
+ 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);
+ return;
+
+free_hw_table:
+ free_pages_exact(hfi_instance->hw_table, hfi_features.nr_table_pages);
+ goto unlock;
+}
+
+/**
+ * intel_hfi_offline() - Disable HFI on @cpu
+ * @cpu: CPU in which the HFI will be disabled
+ *
+ * Remove @cpu from those covered by its HFI instance.
+ *
+ * On some processors, hardware remembers previous programming settings even
+ * after being reprogrammed. Thus, keep HFI enabled even if all CPUs in the
+ * package of @cpu are offline. See note in intel_hfi_online().
+ */
+void intel_hfi_offline(unsigned int cpu)
+{
+ struct hfi_cpu_info *info = &per_cpu(hfi_cpu_info, cpu);
+ struct hfi_instance *hfi_instance;
+
+ /*
+ * Check if @cpu as an associated, initialized (i.e., with a non-NULL
+ * header). Also, HFI instances are only initialized if X86_FEATURE_HFI
+ * is present.
+ */
+ hfi_instance = info->hfi_instance;
+ if (!hfi_instance)
+ return;
+
+ if (!hfi_instance->hdr)
+ return;
+
+ mutex_lock(&hfi_instance_lock);
+ cpumask_clear_cpu(cpu, hfi_instance->cpus);
+
+ if (cpumask_empty(hfi_instance->cpus))
+ hfi_disable();
+
+ mutex_unlock(&hfi_instance_lock);
+}
+
+static __init int hfi_parse_features(void)
+{
+ unsigned int nr_capabilities;
+ union cpuid6_edx edx;
+
+ if (!boot_cpu_has(X86_FEATURE_HFI))
+ return -ENODEV;
+
+ /*
+ * If we are here we know that CPUID_HFI_LEAF exists. Parse the
+ * supported capabilities and the size of the HFI table.
+ */
+ edx.full = cpuid_edx(CPUID_HFI_LEAF);
+
+ if (!edx.split.capabilities.split.performance) {
+ pr_debug("Performance reporting not supported! Not using HFI\n");
+ return -ENODEV;
+ }
+
+ /*
+ * The number of supported capabilities determines the number of
+ * columns in the HFI table. Exclude the reserved bits.
+ */
+ edx.split.capabilities.split.__reserved = 0;
+ nr_capabilities = hweight8(edx.split.capabilities.bits);
+
+ /* The number of 4KB pages required by the table */
+ hfi_features.nr_table_pages = edx.split.table_pages + 1;
+
+ /*
+ * The header contains change indications for each supported feature.
+ * The size of the table header is rounded up to be a multiple of 8
+ * bytes.
+ */
+ hfi_features.hdr_size = DIV_ROUND_UP(nr_capabilities, 8) * 8;
+
+ /*
+ * Data of each logical processor is also rounded up to be a multiple
+ * of 8 bytes.
+ */
+ hfi_features.cpu_stride = DIV_ROUND_UP(nr_capabilities, 8) * 8;
+
+ return 0;
+}
+
+/*
+ * 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;
+ int i, j;
+
+ if (hfi_parse_features())
+ return;
+
+ /*
+ * 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
+ * for a null pointer.
+ */
+ hfi_instances = kzalloc_objs(*hfi_instances, max_hfi_instances);
+ if (!hfi_instances)
+ return;
+
+ for (i = 0; i < max_hfi_instances; i++) {
+ hfi_instance = &hfi_instances[i];
+ if (!zalloc_cpumask_var(&hfi_instance->cpus, GFP_KERNEL))
+ goto err_nomem;
+ }
+
+ hfi_updates_wq = create_singlethread_workqueue("hfi-updates");
+ if (!hfi_updates_wq)
+ goto err_nomem;
+
+ /*
+ * 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];
+ free_cpumask_var(hfi_instance->cpus);
+ }
+
+ kfree(hfi_instances);
+ hfi_instances = NULL;
+}
diff --git a/drivers/thermal/intel/intel_hfi.h b/drivers/thermal/intel/intel_hfi.h
new file mode 100644
index 000000000..325aa78b7
--- /dev/null
+++ b/drivers/thermal/intel/intel_hfi.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INTEL_HFI_H
+#define _INTEL_HFI_H
+
+#if defined(CONFIG_INTEL_HFI_THERMAL)
+void __init intel_hfi_init(void);
+void intel_hfi_online(unsigned int cpu);
+void intel_hfi_offline(unsigned int cpu);
+void intel_hfi_process_event(__u64 pkg_therm_status_msr_val);
+#else
+static inline void intel_hfi_init(void) { }
+static inline void intel_hfi_online(unsigned int cpu) { }
+static inline void intel_hfi_offline(unsigned int cpu) { }
+static inline void intel_hfi_process_event(__u64 pkg_therm_status_msr_val) { }
+#endif /* CONFIG_INTEL_HFI_THERMAL */
+
+#endif /* _INTEL_HFI_H */
diff --git a/drivers/thermal/intel/intel_pch_thermal.c b/drivers/thermal/intel/intel_pch_thermal.c
new file mode 100644
index 000000000..52e71af67
--- /dev/null
+++ b/drivers/thermal/intel/intel_pch_thermal.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* intel_pch_thermal.c - Intel PCH Thermal driver
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * Authors:
+ * Tushar Dave <tushar.n.dave@intel.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+#include <linux/suspend.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+/* Intel PCH thermal Device IDs */
+#define PCH_THERMAL_DID_HSW_1 0x9C24 /* Haswell PCH */
+#define PCH_THERMAL_DID_HSW_2 0x8C24 /* Haswell PCH */
+#define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */
+#define PCH_THERMAL_DID_SKL 0x9D31 /* Skylake PCH */
+#define PCH_THERMAL_DID_SKL_H 0xA131 /* Skylake PCH 100 series */
+#define PCH_THERMAL_DID_CNL 0x9Df9 /* CNL PCH */
+#define PCH_THERMAL_DID_CNL_H 0xA379 /* CNL-H PCH */
+#define PCH_THERMAL_DID_CNL_LP 0x02F9 /* CNL-LP PCH */
+#define PCH_THERMAL_DID_CML_H 0X06F9 /* CML-H PCH */
+#define PCH_THERMAL_DID_LWB 0xA1B1 /* Lewisburg PCH */
+#define PCH_THERMAL_DID_WBG 0x8D24 /* Wellsburg PCH */
+
+/* Wildcat Point-LP PCH Thermal registers */
+#define WPT_TEMP 0x0000 /* Temperature */
+#define WPT_TSC 0x04 /* Thermal Sensor Control */
+#define WPT_TSS 0x06 /* Thermal Sensor Status */
+#define WPT_TSEL 0x08 /* Thermal Sensor Enable and Lock */
+#define WPT_TSREL 0x0A /* Thermal Sensor Report Enable and Lock */
+#define WPT_TSMIC 0x0C /* Thermal Sensor SMI Control */
+#define WPT_CTT 0x0010 /* Catastrophic Trip Point */
+#define WPT_TSPM 0x001C /* Thermal Sensor Power Management */
+#define WPT_TAHV 0x0014 /* Thermal Alert High Value */
+#define WPT_TALV 0x0018 /* Thermal Alert Low Value */
+#define WPT_TL 0x00000040 /* Throttle Value */
+#define WPT_PHL 0x0060 /* PCH Hot Level */
+#define WPT_PHLC 0x62 /* PHL Control */
+#define WPT_TAS 0x80 /* Thermal Alert Status */
+#define WPT_TSPIEN 0x82 /* PCI Interrupt Event Enables */
+#define WPT_TSGPEN 0x84 /* General Purpose Event Enables */
+
+/* Wildcat Point-LP PCH Thermal Register bit definitions */
+#define WPT_TEMP_TSR 0x01ff /* Temp TS Reading */
+#define WPT_TSC_CPDE 0x01 /* Catastrophic Power-Down Enable */
+#define WPT_TSS_TSDSS 0x10 /* Thermal Sensor Dynamic Shutdown Status */
+#define WPT_TSS_GPES 0x08 /* GPE status */
+#define WPT_TSEL_ETS 0x01 /* Enable TS */
+#define WPT_TSEL_PLDB 0x80 /* TSEL Policy Lock-Down Bit */
+#define WPT_TL_TOL 0x000001FF /* T0 Level */
+#define WPT_TL_T1L 0x1ff00000 /* T1 Level */
+#define WPT_TL_TTEN 0x20000000 /* TT Enable */
+
+/* Resolution of 1/2 degree C and an offset of -50C */
+#define PCH_TEMP_OFFSET (-50)
+#define GET_WPT_TEMP(x) ((x) * MILLIDEGREE_PER_DEGREE / 2 + WPT_TEMP_OFFSET)
+#define WPT_TEMP_OFFSET (PCH_TEMP_OFFSET * MILLIDEGREE_PER_DEGREE)
+#define GET_PCH_TEMP(x) (((x) / 2) + PCH_TEMP_OFFSET)
+
+#define PCH_MAX_TRIPS 3 /* critical, hot, passive */
+
+/* Amount of time for each cooling delay, 100ms by default for now */
+static unsigned int delay_timeout = 100;
+module_param(delay_timeout, int, 0644);
+MODULE_PARM_DESC(delay_timeout, "amount of time delay for each iteration.");
+
+/* Number of iterations for cooling delay, 600 counts by default for now */
+static unsigned int delay_cnt = 600;
+module_param(delay_cnt, int, 0644);
+MODULE_PARM_DESC(delay_cnt, "total number of iterations for time delay.");
+
+static char driver_name[] = "Intel PCH thermal driver";
+
+struct pch_thermal_device {
+ void __iomem *hw_base;
+ struct pci_dev *pdev;
+ struct thermal_zone_device *tzd;
+ bool bios_enabled;
+};
+
+#ifdef CONFIG_ACPI
+/*
+ * On some platforms, there is a companion ACPI device, which adds
+ * 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,
+ struct thermal_trip *trip)
+{
+ struct acpi_device *adev;
+ int temp;
+
+ adev = ACPI_COMPANION(&ptd->pdev->dev);
+ if (!adev)
+ return 0;
+
+ if (thermal_acpi_passive_trip_temp(adev, &temp) || temp <= 0)
+ return 0;
+
+ trip->type = THERMAL_TRIP_PASSIVE;
+ trip->temperature = temp;
+ return 1;
+}
+#else
+static int pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd,
+ struct thermal_trip *trip)
+{
+ return 0;
+}
+#endif
+
+static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ struct pch_thermal_device *ptd = thermal_zone_device_priv(tzd);
+
+ *temp = GET_WPT_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
+ return 0;
+}
+
+static void pch_critical(struct thermal_zone_device *tzd)
+{
+ dev_dbg(thermal_zone_device(tzd), "%s: critical temperature reached\n",
+ thermal_zone_device_type(tzd));
+}
+
+static const struct thermal_zone_device_ops tzd_ops = {
+ .get_temp = pch_thermal_get_temp,
+ .critical = pch_critical,
+};
+
+enum pch_board_ids {
+ PCH_BOARD_HSW = 0,
+ PCH_BOARD_WPT,
+ PCH_BOARD_SKL,
+ PCH_BOARD_CNL,
+ PCH_BOARD_CML,
+ PCH_BOARD_LWB,
+ PCH_BOARD_WBG,
+};
+
+static const char *board_names[] = {
+ [PCH_BOARD_HSW] = "pch_haswell",
+ [PCH_BOARD_WPT] = "pch_wildcat_point",
+ [PCH_BOARD_SKL] = "pch_skylake",
+ [PCH_BOARD_CNL] = "pch_cannonlake",
+ [PCH_BOARD_CML] = "pch_cometlake",
+ [PCH_BOARD_LWB] = "pch_lewisburg",
+ [PCH_BOARD_WBG] = "pch_wellsburg",
+};
+
+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;
+ u16 trip_temp;
+ u8 tsel;
+ int err;
+
+ ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL);
+ if (!ptd)
+ return -ENOMEM;
+
+ pci_set_drvdata(pdev, ptd);
+ ptd->pdev = pdev;
+
+ err = pci_enable_device(pdev);
+ if (err) {
+ dev_err(&pdev->dev, "failed to enable pci device\n");
+ return err;
+ }
+
+ err = pci_request_regions(pdev, driver_name);
+ if (err) {
+ dev_err(&pdev->dev, "failed to request pci region\n");
+ goto error_disable;
+ }
+
+ ptd->hw_base = pci_ioremap_bar(pdev, 0);
+ if (!ptd->hw_base) {
+ err = -ENOMEM;
+ dev_err(&pdev->dev, "failed to map mem base\n");
+ goto error_release;
+ }
+
+ /* Check if BIOS has already enabled thermal sensor */
+ if (WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL)) {
+ ptd->bios_enabled = true;
+ goto read_trips;
+ }
+
+ tsel = readb(ptd->hw_base + WPT_TSEL);
+ /*
+ * When TSEL's Policy Lock-Down bit is 1, TSEL become RO.
+ * If so, thermal sensor cannot enable. Bail out.
+ */
+ if (tsel & WPT_TSEL_PLDB) {
+ dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
+ err = -ENODEV;
+ goto error_cleanup;
+ }
+
+ writeb(tsel|WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL);
+ if (!(WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL))) {
+ dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
+ err = -ENODEV;
+ goto error_cleanup;
+ }
+
+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;
+ }
+
+ 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;
+ }
+
+ 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,
+ ptd, &tzd_ops,
+ NULL, 0, 0);
+ if (IS_ERR(ptd->tzd)) {
+ dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
+ board_names[board_id]);
+ err = PTR_ERR(ptd->tzd);
+ goto error_cleanup;
+ }
+ err = thermal_zone_device_enable(ptd->tzd);
+ if (err)
+ goto err_unregister;
+
+ return 0;
+
+err_unregister:
+ thermal_zone_device_unregister(ptd->tzd);
+error_cleanup:
+ iounmap(ptd->hw_base);
+error_release:
+ pci_release_regions(pdev);
+error_disable:
+ pci_disable_device(pdev);
+ dev_err(&pdev->dev, "pci device failed to probe\n");
+ return err;
+}
+
+static void intel_pch_thermal_remove(struct pci_dev *pdev)
+{
+ struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(ptd->tzd);
+ iounmap(ptd->hw_base);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+}
+
+static int intel_pch_thermal_suspend_noirq(struct device *device)
+{
+ struct pch_thermal_device *ptd = dev_get_drvdata(device);
+ u16 pch_thr_temp, pch_cur_temp;
+ int pch_delay_cnt = 0;
+ u8 tsel;
+
+ /* Shutdown the thermal sensor if it is not enabled by BIOS */
+ if (!ptd->bios_enabled) {
+ tsel = readb(ptd->hw_base + WPT_TSEL);
+ writeb(tsel & 0xFE, ptd->hw_base + WPT_TSEL);
+ return 0;
+ }
+
+ /* Do not check temperature if it is not s2idle */
+ if (pm_suspend_via_firmware())
+ return 0;
+
+ /* Get the PCH temperature threshold value */
+ pch_thr_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TSPM));
+
+ /* 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
+ * go down below the threshold value which helps to allow system enter
+ * lower power S0ix suspend state. Even after delay loop if PCH current
+ * temperature stays above threshold, notify the warning message
+ * which helps to indentify the reason why S0ix entry was rejected.
+ */
+ while (pch_delay_cnt < delay_cnt) {
+ if (pch_cur_temp < pch_thr_temp)
+ break;
+
+ if (pm_wakeup_pending()) {
+ dev_warn(&ptd->pdev->dev, "Wakeup event detected, abort cooling\n");
+ return 0;
+ }
+
+ pch_delay_cnt++;
+ dev_dbg(&ptd->pdev->dev,
+ "CPU-PCH current temp [%dC] higher than the threshold temp [%dC], sleep %d times for %d ms duration\n",
+ pch_cur_temp, pch_thr_temp, pch_delay_cnt, delay_timeout);
+ msleep(delay_timeout);
+ /* Read the PCH current temperature for next cycle. */
+ pch_cur_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
+ }
+
+ if (pch_cur_temp >= pch_thr_temp)
+ dev_warn(&ptd->pdev->dev,
+ "CPU-PCH is hot [%dC] after %d ms delay. S0ix might fail\n",
+ pch_cur_temp, pch_delay_cnt * delay_timeout);
+ else {
+ if (pch_delay_cnt)
+ dev_info(&ptd->pdev->dev,
+ "CPU-PCH is cool [%dC] after %d ms delay\n",
+ pch_cur_temp, pch_delay_cnt * delay_timeout);
+ else
+ dev_info(&ptd->pdev->dev,
+ "CPU-PCH is cool [%dC]\n",
+ pch_cur_temp);
+ }
+
+ return 0;
+}
+
+static int intel_pch_thermal_resume(struct device *device)
+{
+ struct pch_thermal_device *ptd = dev_get_drvdata(device);
+ u8 tsel;
+
+ if (ptd->bios_enabled)
+ return 0;
+
+ tsel = readb(ptd->hw_base + WPT_TSEL);
+
+ writeb(tsel | WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL);
+
+ return 0;
+}
+
+static const struct pci_device_id intel_pch_thermal_id[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1),
+ .driver_data = PCH_BOARD_HSW, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2),
+ .driver_data = PCH_BOARD_HSW, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT),
+ .driver_data = PCH_BOARD_WPT, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL),
+ .driver_data = PCH_BOARD_SKL, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL_H),
+ .driver_data = PCH_BOARD_SKL, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL),
+ .driver_data = PCH_BOARD_CNL, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H),
+ .driver_data = PCH_BOARD_CNL, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_LP),
+ .driver_data = PCH_BOARD_CNL, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CML_H),
+ .driver_data = PCH_BOARD_CML, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_LWB),
+ .driver_data = PCH_BOARD_LWB, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WBG),
+ .driver_data = PCH_BOARD_WBG, },
+ { 0, },
+};
+MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
+
+static const struct dev_pm_ops intel_pch_pm_ops = {
+ .suspend_noirq = intel_pch_thermal_suspend_noirq,
+ .resume = intel_pch_thermal_resume,
+};
+
+static struct pci_driver intel_pch_thermal_driver = {
+ .name = "intel_pch_thermal",
+ .id_table = intel_pch_thermal_id,
+ .probe = intel_pch_thermal_probe,
+ .remove = intel_pch_thermal_remove,
+ .driver.pm = &intel_pch_pm_ops,
+};
+
+module_pci_driver(intel_pch_thermal_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel PCH Thermal driver");
diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c
new file mode 100644
index 000000000..bd7fd98dc
--- /dev/null
+++ b/drivers/thermal/intel/intel_powerclamp.c
@@ -0,0 +1,816 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * intel_powerclamp.c - package c-state idle injection
+ *
+ * Copyright (c) 2012-2023, Intel Corporation.
+ *
+ * Authors:
+ * Arjan van de Ven <arjan@linux.intel.com>
+ * Jacob Pan <jacob.jun.pan@linux.intel.com>
+ *
+ * TODO:
+ * 1. better handle wakeup from external interrupts, currently a fixed
+ * compensation is added to clamping duration when excessive amount
+ * of wakeups are observed during idle time. the reason is that in
+ * case of external interrupts without need for ack, clamping down
+ * cpu in non-irq context does not reduce irq. for majority of the
+ * cases, clamping down cpu does help reduce irq as well, we should
+ * be able to differentiate the two cases and give a quantitative
+ * solution for the irqs that we can control. perhaps based on
+ * get_cpu_iowait_time_us()
+ *
+ * 2. synchronization with other hw blocks
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/cpu.h>
+#include <linux/thermal.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/idle_inject.h>
+
+#include <asm/msr.h>
+#include <asm/mwait.h>
+#include <asm/cpu_device_id.h>
+
+#define MAX_TARGET_RATIO (100U)
+/* For each undisturbed clamping period (no extra wake ups during idle time),
+ * we increment the confidence counter for the given target ratio.
+ * CONFIDENCE_OK defines the level where runtime calibration results are
+ * valid.
+ */
+#define CONFIDENCE_OK (3)
+/* Default idle injection duration, driver adjust sleep time to meet target
+ * idle ratio. Similar to frequency modulation.
+ */
+#define DEFAULT_DURATION_JIFFIES (6)
+
+static struct dentry *debug_dir;
+static bool poll_pkg_cstate_enable;
+
+/* Idle ratio observed using package C-state counters */
+static unsigned int current_ratio;
+
+/* Skip the idle injection till set to true */
+static bool should_skip;
+
+struct powerclamp_data {
+ unsigned int cpu;
+ unsigned int count;
+ unsigned int guard;
+ unsigned int window_size_now;
+ unsigned int target_ratio;
+ bool clamping;
+};
+
+static struct powerclamp_data powerclamp_data;
+
+static struct thermal_cooling_device *cooling_dev;
+
+static DEFINE_MUTEX(powerclamp_lock);
+
+/* This duration is in microseconds */
+static unsigned int duration;
+static unsigned int pkg_cstate_ratio_cur;
+static unsigned int window_size;
+
+static int duration_set(const char *arg, const struct kernel_param *kp)
+{
+ int ret = 0;
+ unsigned long new_duration;
+
+ ret = kstrtoul(arg, 10, &new_duration);
+ if (ret)
+ goto exit;
+ if (new_duration > 25 || new_duration < 6) {
+ pr_err("Out of recommended range %lu, between 6-25ms\n",
+ new_duration);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ mutex_lock(&powerclamp_lock);
+ duration = clamp(new_duration, 6ul, 25ul) * 1000;
+ mutex_unlock(&powerclamp_lock);
+exit:
+
+ return ret;
+}
+
+static int duration_get(char *buf, const struct kernel_param *kp)
+{
+ int ret;
+
+ mutex_lock(&powerclamp_lock);
+ ret = sysfs_emit(buf, "%d\n", duration / 1000);
+ mutex_unlock(&powerclamp_lock);
+
+ return ret;
+}
+
+static const struct kernel_param_ops duration_ops = {
+ .set = duration_set,
+ .get = duration_get,
+};
+
+module_param_cb(duration, &duration_ops, NULL, 0644);
+MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec.");
+
+#define DEFAULT_MAX_IDLE 50
+#define MAX_ALL_CPU_IDLE 75
+
+static u8 max_idle = DEFAULT_MAX_IDLE;
+
+static cpumask_var_t idle_injection_cpu_mask;
+
+static int allocate_copy_idle_injection_mask(const struct cpumask *copy_mask)
+{
+ if (cpumask_available(idle_injection_cpu_mask))
+ goto copy_mask;
+
+ /* This mask is allocated only one time and freed during module exit */
+ if (!alloc_cpumask_var(&idle_injection_cpu_mask, GFP_KERNEL))
+ return -ENOMEM;
+
+copy_mask:
+ cpumask_copy(idle_injection_cpu_mask, copy_mask);
+
+ return 0;
+}
+
+/* Return true if the cpumask and idle percent combination is invalid */
+static bool check_invalid(cpumask_var_t mask, u8 idle)
+{
+ if (cpumask_equal(cpu_present_mask, mask) && idle > MAX_ALL_CPU_IDLE)
+ return true;
+
+ return false;
+}
+
+static int cpumask_set(const char *arg, const struct kernel_param *kp)
+{
+ cpumask_var_t new_mask;
+ int ret;
+
+ mutex_lock(&powerclamp_lock);
+
+ /* Can't set mask when cooling device is in use */
+ if (powerclamp_data.clamping) {
+ ret = -EAGAIN;
+ goto skip_cpumask_set;
+ }
+
+ ret = alloc_cpumask_var(&new_mask, GFP_KERNEL);
+ if (!ret)
+ goto skip_cpumask_set;
+
+ ret = bitmap_parse(arg, strlen(arg), cpumask_bits(new_mask),
+ nr_cpumask_bits);
+ if (ret)
+ goto free_cpumask_set;
+
+ if (cpumask_empty(new_mask) || check_invalid(new_mask, max_idle)) {
+ ret = -EINVAL;
+ goto free_cpumask_set;
+ }
+
+ /*
+ * When module parameters are passed from kernel command line
+ * during insmod, the module parameter callback is called
+ * before powerclamp_init(), so we can't assume that some
+ * cpumask can be allocated and copied before here. Also
+ * in this case this cpumask is used as the default mask.
+ */
+ ret = allocate_copy_idle_injection_mask(new_mask);
+
+free_cpumask_set:
+ free_cpumask_var(new_mask);
+skip_cpumask_set:
+ mutex_unlock(&powerclamp_lock);
+
+ return ret;
+}
+
+static int cpumask_get(char *buf, const struct kernel_param *kp)
+{
+ if (!cpumask_available(idle_injection_cpu_mask))
+ return -ENODEV;
+
+ return sysfs_emit(buf, "%*pb\n", cpumask_pr_args(idle_injection_cpu_mask));
+}
+
+static const struct kernel_param_ops cpumask_ops = {
+ .set = cpumask_set,
+ .get = cpumask_get,
+};
+
+module_param_cb(cpumask, &cpumask_ops, NULL, 0644);
+MODULE_PARM_DESC(cpumask, "Mask of CPUs to use for idle injection.");
+
+static int max_idle_set(const char *arg, const struct kernel_param *kp)
+{
+ u8 new_max_idle;
+ int ret = 0;
+
+ mutex_lock(&powerclamp_lock);
+
+ /* Can't set mask when cooling device is in use */
+ if (powerclamp_data.clamping) {
+ ret = -EAGAIN;
+ goto skip_limit_set;
+ }
+
+ ret = kstrtou8(arg, 10, &new_max_idle);
+ if (ret)
+ goto skip_limit_set;
+
+ if (new_max_idle > MAX_TARGET_RATIO) {
+ ret = -EINVAL;
+ goto skip_limit_set;
+ }
+
+ if (!cpumask_available(idle_injection_cpu_mask)) {
+ ret = allocate_copy_idle_injection_mask(cpu_present_mask);
+ if (ret)
+ goto skip_limit_set;
+ }
+
+ if (check_invalid(idle_injection_cpu_mask, new_max_idle)) {
+ ret = -EINVAL;
+ goto skip_limit_set;
+ }
+
+ max_idle = new_max_idle;
+
+skip_limit_set:
+ mutex_unlock(&powerclamp_lock);
+
+ return ret;
+}
+
+static const struct kernel_param_ops max_idle_ops = {
+ .set = max_idle_set,
+ .get = param_get_byte,
+};
+
+module_param_cb(max_idle, &max_idle_ops, &max_idle, 0644);
+MODULE_PARM_DESC(max_idle, "maximum injected idle time to the total CPU time ratio in percent range:1-100");
+
+struct powerclamp_calibration_data {
+ unsigned long confidence; /* used for calibration, basically a counter
+ * gets incremented each time a clamping
+ * period is completed without extra wakeups
+ * once that counter is reached given level,
+ * compensation is deemed usable.
+ */
+ unsigned long steady_comp; /* steady state compensation used when
+ * no extra wakeups occurred.
+ */
+ unsigned long dynamic_comp; /* compensate excessive wakeup from idle
+ * mostly from external interrupts.
+ */
+};
+
+static struct powerclamp_calibration_data cal_data[MAX_TARGET_RATIO];
+
+static int window_size_set(const char *arg, const struct kernel_param *kp)
+{
+ int ret = 0;
+ unsigned long new_window_size;
+
+ ret = kstrtoul(arg, 10, &new_window_size);
+ if (ret)
+ goto exit_win;
+ if (new_window_size > 10 || new_window_size < 2) {
+ pr_err("Out of recommended window size %lu, between 2-10\n",
+ new_window_size);
+ ret = -EINVAL;
+ }
+
+ window_size = clamp(new_window_size, 2ul, 10ul);
+ smp_mb();
+
+exit_win:
+
+ return ret;
+}
+
+static const struct kernel_param_ops window_size_ops = {
+ .set = window_size_set,
+ .get = param_get_int,
+};
+
+module_param_cb(window_size, &window_size_ops, &window_size, 0644);
+MODULE_PARM_DESC(window_size, "sliding window in number of clamping cycles\n"
+ "\tpowerclamp controls idle ratio within this window. larger\n"
+ "\twindow size results in slower response time but more smooth\n"
+ "\tclamping results. default to 2.");
+
+struct pkg_cstate_info {
+ bool skip;
+ int msr_index;
+ int cstate_id;
+};
+
+#define PKG_CSTATE_INIT(id) { \
+ .msr_index = MSR_PKG_C##id##_RESIDENCY, \
+ .cstate_id = id \
+ }
+
+static struct pkg_cstate_info pkg_cstates[] = {
+ PKG_CSTATE_INIT(2),
+ PKG_CSTATE_INIT(3),
+ PKG_CSTATE_INIT(6),
+ PKG_CSTATE_INIT(7),
+ PKG_CSTATE_INIT(8),
+ PKG_CSTATE_INIT(9),
+ PKG_CSTATE_INIT(10),
+ {NULL},
+};
+
+static bool has_pkg_state_counter(void)
+{
+ u64 val;
+ struct pkg_cstate_info *info = pkg_cstates;
+
+ /* check if any one of the counter msrs exists */
+ while (info->msr_index) {
+ if (!rdmsrq_safe(info->msr_index, &val))
+ return true;
+ info++;
+ }
+
+ return false;
+}
+
+static u64 pkg_state_counter(void)
+{
+ u64 val;
+ u64 count = 0;
+ struct pkg_cstate_info *info = pkg_cstates;
+
+ while (info->msr_index) {
+ if (!info->skip) {
+ if (!rdmsrq_safe(info->msr_index, &val))
+ count += val;
+ else
+ info->skip = true;
+ }
+ info++;
+ }
+
+ return count;
+}
+
+static unsigned int get_compensation(int ratio)
+{
+ unsigned int comp = 0;
+
+ if (!poll_pkg_cstate_enable)
+ return 0;
+
+ /* we only use compensation if all adjacent ones are good */
+ if (ratio == 1 &&
+ cal_data[ratio].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio + 1].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio + 2].confidence >= CONFIDENCE_OK) {
+ comp = (cal_data[ratio].steady_comp +
+ cal_data[ratio + 1].steady_comp +
+ cal_data[ratio + 2].steady_comp) / 3;
+ } else if (ratio == MAX_TARGET_RATIO - 1 &&
+ cal_data[ratio].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio - 1].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio - 2].confidence >= CONFIDENCE_OK) {
+ comp = (cal_data[ratio].steady_comp +
+ cal_data[ratio - 1].steady_comp +
+ cal_data[ratio - 2].steady_comp) / 3;
+ } else if (cal_data[ratio].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio - 1].confidence >= CONFIDENCE_OK &&
+ cal_data[ratio + 1].confidence >= CONFIDENCE_OK) {
+ comp = (cal_data[ratio].steady_comp +
+ cal_data[ratio - 1].steady_comp +
+ cal_data[ratio + 1].steady_comp) / 3;
+ }
+
+ /* do not exceed limit */
+ if (comp + ratio >= MAX_TARGET_RATIO)
+ comp = MAX_TARGET_RATIO - ratio - 1;
+
+ return comp;
+}
+
+static void adjust_compensation(int target_ratio, unsigned int win)
+{
+ int delta;
+ struct powerclamp_calibration_data *d = &cal_data[target_ratio];
+
+ /*
+ * adjust compensations if confidence level has not been reached.
+ */
+ if (d->confidence >= CONFIDENCE_OK)
+ return;
+
+ delta = powerclamp_data.target_ratio - current_ratio;
+ /* filter out bad data */
+ if (delta >= 0 && delta <= (1+target_ratio/10)) {
+ if (d->steady_comp)
+ d->steady_comp =
+ roundup(delta+d->steady_comp, 2)/2;
+ else
+ d->steady_comp = delta;
+ d->confidence++;
+ }
+}
+
+static bool powerclamp_adjust_controls(unsigned int target_ratio,
+ unsigned int guard, unsigned int win)
+{
+ static u64 msr_last, tsc_last;
+ u64 msr_now, tsc_now;
+ u64 val64;
+
+ /* check result for the last window */
+ msr_now = pkg_state_counter();
+ tsc_now = rdtsc();
+
+ /* calculate pkg cstate vs tsc ratio */
+ if (!msr_last || !tsc_last)
+ current_ratio = 1;
+ else if (tsc_now-tsc_last) {
+ val64 = 100*(msr_now-msr_last);
+ do_div(val64, (tsc_now-tsc_last));
+ current_ratio = val64;
+ }
+
+ /* update record */
+ msr_last = msr_now;
+ tsc_last = tsc_now;
+
+ adjust_compensation(target_ratio, win);
+
+ /* if we are above target+guard, skip */
+ return powerclamp_data.target_ratio + guard <= current_ratio;
+}
+
+/*
+ * This function calculates runtime from the current target ratio.
+ * This function gets called under powerclamp_lock.
+ */
+static unsigned int get_run_time(void)
+{
+ unsigned int compensated_ratio;
+ unsigned int runtime;
+
+ /*
+ * make sure user selected ratio does not take effect until
+ * the next round. adjust target_ratio if user has changed
+ * target such that we can converge quickly.
+ */
+ powerclamp_data.guard = 1 + powerclamp_data.target_ratio / 20;
+ powerclamp_data.window_size_now = window_size;
+
+ /*
+ * systems may have different ability to enter package level
+ * c-states, thus we need to compensate the injected idle ratio
+ * to achieve the actual target reported by the HW.
+ */
+ compensated_ratio = powerclamp_data.target_ratio +
+ get_compensation(powerclamp_data.target_ratio);
+ if (compensated_ratio <= 0)
+ compensated_ratio = 1;
+
+ runtime = duration * 100 / compensated_ratio - duration;
+
+ return runtime;
+}
+
+/*
+ * 1 HZ polling while clamping is active, useful for userspace
+ * to monitor actual idle ratio.
+ */
+static void poll_pkg_cstate(struct work_struct *dummy);
+static DECLARE_DELAYED_WORK(poll_pkg_cstate_work, poll_pkg_cstate);
+static void poll_pkg_cstate(struct work_struct *dummy)
+{
+ static u64 msr_last;
+ static u64 tsc_last;
+
+ u64 msr_now;
+ u64 tsc_now;
+ u64 val64;
+
+ msr_now = pkg_state_counter();
+ tsc_now = rdtsc();
+
+ /* calculate pkg cstate vs tsc ratio */
+ if (!msr_last || !tsc_last)
+ pkg_cstate_ratio_cur = 1;
+ else {
+ if (tsc_now - tsc_last) {
+ val64 = 100 * (msr_now - msr_last);
+ do_div(val64, (tsc_now - tsc_last));
+ pkg_cstate_ratio_cur = val64;
+ }
+ }
+
+ /* update record */
+ msr_last = msr_now;
+ tsc_last = tsc_now;
+
+ mutex_lock(&powerclamp_lock);
+ if (powerclamp_data.clamping)
+ schedule_delayed_work(&poll_pkg_cstate_work, HZ);
+ mutex_unlock(&powerclamp_lock);
+}
+
+static struct idle_inject_device *ii_dev;
+
+/*
+ * This function is called from idle injection core on timer expiry
+ * for the run duration. This allows powerclamp to readjust or skip
+ * injecting idle for this cycle.
+ */
+static bool idle_inject_update(void)
+{
+ bool update = false;
+
+ /* We can't sleep in this callback */
+ if (!mutex_trylock(&powerclamp_lock))
+ return true;
+
+ if (!(powerclamp_data.count % powerclamp_data.window_size_now)) {
+
+ should_skip = powerclamp_adjust_controls(powerclamp_data.target_ratio,
+ powerclamp_data.guard,
+ powerclamp_data.window_size_now);
+ update = true;
+ }
+
+ if (update) {
+ unsigned int runtime = get_run_time();
+
+ idle_inject_set_duration(ii_dev, runtime, duration);
+ }
+
+ powerclamp_data.count++;
+
+ mutex_unlock(&powerclamp_lock);
+
+ if (should_skip)
+ return false;
+
+ return true;
+}
+
+/* This function starts idle injection by calling idle_inject_start() */
+static void trigger_idle_injection(void)
+{
+ unsigned int runtime = get_run_time();
+
+ idle_inject_set_duration(ii_dev, runtime, duration);
+ idle_inject_start(ii_dev);
+ powerclamp_data.clamping = true;
+}
+
+/*
+ * This function is called from start_power_clamp() to register
+ * CPUS with powercap idle injection register and set default
+ * idle duration and latency.
+ */
+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_dies_per_package() == 1)
+ poll_pkg_cstate_enable = true;
+ } else {
+ ii_dev = idle_inject_register(idle_injection_cpu_mask);
+ }
+
+ if (!ii_dev) {
+ pr_err("powerclamp: idle_inject_register failed\n");
+ return -EAGAIN;
+ }
+
+ idle_inject_set_duration(ii_dev, TICK_USEC, duration);
+ idle_inject_set_latency(ii_dev, UINT_MAX);
+
+ return 0;
+}
+
+/*
+ * This function is called from end_power_clamp() to stop idle injection
+ * and unregister CPUS from powercap idle injection core.
+ */
+static void remove_idle_injection(void)
+{
+ if (!powerclamp_data.clamping)
+ return;
+
+ powerclamp_data.clamping = false;
+ idle_inject_stop(ii_dev);
+}
+
+/*
+ * This function is called when user change the cooling device
+ * state from zero to some other value.
+ */
+static int start_power_clamp(void)
+{
+ int ret;
+
+ ret = powerclamp_idle_injection_register();
+ if (!ret) {
+ trigger_idle_injection();
+ if (poll_pkg_cstate_enable)
+ schedule_delayed_work(&poll_pkg_cstate_work, 0);
+ }
+
+ return ret;
+}
+
+/*
+ * This function is called when user change the cooling device
+ * state from non zero value zero.
+ */
+static void end_power_clamp(void)
+{
+ if (powerclamp_data.clamping) {
+ remove_idle_injection();
+ idle_inject_unregister(ii_dev);
+ }
+}
+
+static int powerclamp_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ *state = MAX_TARGET_RATIO;
+
+ return 0;
+}
+
+static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ mutex_lock(&powerclamp_lock);
+ *state = powerclamp_data.target_ratio;
+ mutex_unlock(&powerclamp_lock);
+
+ return 0;
+}
+
+static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long new_target_ratio)
+{
+ int ret = 0;
+
+ mutex_lock(&powerclamp_lock);
+
+ new_target_ratio = clamp(new_target_ratio, 0UL,
+ (unsigned long) (max_idle - 1));
+
+ if (powerclamp_data.target_ratio == new_target_ratio)
+ goto exit_set;
+
+ if (!powerclamp_data.target_ratio && new_target_ratio > 0) {
+ pr_info("Start idle injection to reduce power\n");
+ powerclamp_data.target_ratio = new_target_ratio;
+ ret = start_power_clamp();
+ if (ret)
+ powerclamp_data.target_ratio = 0;
+ goto exit_set;
+ } else if (powerclamp_data.target_ratio > 0 && new_target_ratio == 0) {
+ pr_info("Stop forced idle injection\n");
+ end_power_clamp();
+ powerclamp_data.target_ratio = 0;
+ } else /* adjust currently running */ {
+ unsigned int runtime;
+
+ powerclamp_data.target_ratio = new_target_ratio;
+ runtime = get_run_time();
+ idle_inject_set_duration(ii_dev, runtime, duration);
+ }
+
+exit_set:
+ mutex_unlock(&powerclamp_lock);
+
+ return ret;
+}
+
+/* bind to generic thermal layer as cooling device*/
+static const struct thermal_cooling_device_ops powerclamp_cooling_ops = {
+ .get_max_state = powerclamp_get_max_state,
+ .get_cur_state = powerclamp_get_cur_state,
+ .set_cur_state = powerclamp_set_cur_state,
+};
+
+static const struct x86_cpu_id __initconst intel_powerclamp_ids[] = {
+ X86_MATCH_VENDOR_FEATURE(INTEL, X86_FEATURE_MWAIT, NULL),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids);
+
+static int __init powerclamp_probe(void)
+{
+
+ if (!x86_match_cpu(intel_powerclamp_ids)) {
+ pr_err("CPU does not support MWAIT\n");
+ return -ENODEV;
+ }
+
+ /* The goal for idle time alignment is to achieve package cstate. */
+ if (!has_pkg_state_counter()) {
+ pr_info("No package C-state available\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int powerclamp_debug_show(struct seq_file *m, void *unused)
+{
+ int i = 0;
+
+ seq_printf(m, "pct confidence steady dynamic (compensation)\n");
+ for (i = 0; i < MAX_TARGET_RATIO; i++) {
+ seq_printf(m, "%d\t%lu\t%lu\t%lu\n",
+ i,
+ cal_data[i].confidence,
+ cal_data[i].steady_comp,
+ cal_data[i].dynamic_comp);
+ }
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(powerclamp_debug);
+
+static inline void powerclamp_create_debug_files(void)
+{
+ debug_dir = debugfs_create_dir("intel_powerclamp", NULL);
+
+ debugfs_create_file("powerclamp_calib", S_IRUGO, debug_dir, cal_data,
+ &powerclamp_debug_fops);
+}
+
+static int __init powerclamp_init(void)
+{
+ int retval;
+
+ /* probe cpu features and ids here */
+ retval = powerclamp_probe();
+ if (retval)
+ return retval;
+
+ mutex_lock(&powerclamp_lock);
+ if (!cpumask_available(idle_injection_cpu_mask))
+ retval = allocate_copy_idle_injection_mask(cpu_present_mask);
+ mutex_unlock(&powerclamp_lock);
+
+ if (retval)
+ return retval;
+
+ /* set default limit, maybe adjusted during runtime based on feedback */
+ window_size = 2;
+
+ cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL,
+ &powerclamp_cooling_ops);
+ if (IS_ERR(cooling_dev))
+ return -ENODEV;
+
+ if (!duration)
+ duration = jiffies_to_usecs(DEFAULT_DURATION_JIFFIES);
+
+ powerclamp_create_debug_files();
+
+ return 0;
+}
+module_init(powerclamp_init);
+
+static void __exit powerclamp_exit(void)
+{
+ mutex_lock(&powerclamp_lock);
+ end_power_clamp();
+ mutex_unlock(&powerclamp_lock);
+
+ thermal_cooling_device_unregister(cooling_dev);
+
+ cancel_delayed_work_sync(&poll_pkg_cstate_work);
+ debugfs_remove_recursive(debug_dir);
+
+ if (cpumask_available(idle_injection_cpu_mask))
+ free_cpumask_var(idle_injection_cpu_mask);
+}
+module_exit(powerclamp_exit);
+
+MODULE_IMPORT_NS("IDLE_INJECT");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
+MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
+MODULE_DESCRIPTION("Package Level C-state Idle Injection for Intel CPUs");
diff --git a/drivers/thermal/intel/intel_quark_dts_thermal.c b/drivers/thermal/intel/intel_quark_dts_thermal.c
new file mode 100644
index 000000000..15476699b
--- /dev/null
+++ b/drivers/thermal/intel/intel_quark_dts_thermal.c
@@ -0,0 +1,431 @@
+/*
+ * intel_quark_dts_thermal.c
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2015 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * Contact Information:
+ * Ong Boon Leong <boon.leong.ong@intel.com>
+ * Intel Malaysia, Penang
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2015 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Quark DTS thermal driver is implemented by referencing
+ * intel_soc_dts_thermal.c.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/thermal.h>
+#include <asm/cpu_device_id.h>
+#include <asm/iosf_mbi.h>
+
+/* DTS reset is programmed via QRK_MBI_UNIT_SOC */
+#define QRK_DTS_REG_OFFSET_RESET 0x34
+#define QRK_DTS_RESET_BIT BIT(0)
+
+/* DTS enable is programmed via QRK_MBI_UNIT_RMU */
+#define QRK_DTS_REG_OFFSET_ENABLE 0xB0
+#define QRK_DTS_ENABLE_BIT BIT(15)
+
+/* Temperature Register is read via QRK_MBI_UNIT_RMU */
+#define QRK_DTS_REG_OFFSET_TEMP 0xB1
+#define QRK_DTS_MASK_TEMP 0xFF
+#define QRK_DTS_OFFSET_TEMP 0
+#define QRK_DTS_OFFSET_REL_TEMP 16
+#define QRK_DTS_TEMP_BASE 50
+
+/* Programmable Trip Point Register is configured via QRK_MBI_UNIT_RMU */
+#define QRK_DTS_REG_OFFSET_PTPS 0xB2
+#define QRK_DTS_MASK_TP_THRES 0xFF
+#define QRK_DTS_SHIFT_TP 8
+#define QRK_DTS_ID_TP_CRITICAL 0
+#define QRK_DTS_ID_TP_HOT 1
+#define QRK_DTS_SAFE_TP_THRES 105
+
+/* Thermal Sensor Register Lock */
+#define QRK_DTS_REG_OFFSET_LOCK 0x71
+#define QRK_DTS_LOCK_BIT BIT(5)
+
+/* Quark DTS has 2 trip points: hot & catastrophic */
+#define QRK_MAX_DTS_TRIPS 2
+
+#define DEFAULT_POLL_DELAY 2000
+
+struct soc_sensor_entry {
+ bool locked;
+ u32 store_ptps;
+ u32 store_dts_enable;
+ struct thermal_zone_device *tzone;
+};
+
+static struct soc_sensor_entry *soc_dts;
+
+static int polling_delay = DEFAULT_POLL_DELAY;
+module_param(polling_delay, int, 0644);
+MODULE_PARM_DESC(polling_delay,
+ "Polling interval for checking trip points (in milliseconds)");
+
+static DEFINE_MUTEX(dts_update_mutex);
+
+static int soc_dts_enable(struct thermal_zone_device *tzd)
+{
+ u32 out;
+ struct soc_sensor_entry *aux_entry = thermal_zone_device_priv(tzd);
+ int ret;
+
+ ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
+ QRK_DTS_REG_OFFSET_ENABLE, &out);
+ if (ret)
+ return ret;
+
+ if (out & QRK_DTS_ENABLE_BIT)
+ return 0;
+
+ if (!aux_entry->locked) {
+ out |= QRK_DTS_ENABLE_BIT;
+ ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
+ QRK_DTS_REG_OFFSET_ENABLE, out);
+ if (ret)
+ return ret;
+ } else {
+ pr_info("DTS is locked. Cannot enable DTS\n");
+ ret = -EPERM;
+ }
+
+ return ret;
+}
+
+static int soc_dts_disable(struct thermal_zone_device *tzd)
+{
+ u32 out;
+ struct soc_sensor_entry *aux_entry = thermal_zone_device_priv(tzd);
+ int ret;
+
+ ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
+ QRK_DTS_REG_OFFSET_ENABLE, &out);
+ if (ret)
+ return ret;
+
+ if (!(out & QRK_DTS_ENABLE_BIT))
+ return 0;
+
+ if (!aux_entry->locked) {
+ out &= ~QRK_DTS_ENABLE_BIT;
+ ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
+ QRK_DTS_REG_OFFSET_ENABLE, out);
+
+ if (ret)
+ return ret;
+ } else {
+ pr_info("DTS is locked. Cannot disable DTS\n");
+ ret = -EPERM;
+ }
+
+ return ret;
+}
+
+static int get_trip_temp(int trip)
+{
+ int status, temp;
+ u32 out;
+
+ mutex_lock(&dts_update_mutex);
+ status = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
+ QRK_DTS_REG_OFFSET_PTPS, &out);
+ mutex_unlock(&dts_update_mutex);
+
+ if (status)
+ return THERMAL_TEMP_INVALID;
+
+ /*
+ * Thermal Sensor Programmable Trip Point Register has 8-bit
+ * fields for critical (catastrophic) and hot set trip point
+ * thresholds. The threshold value is always offset by its
+ * temperature base (50 degree Celsius).
+ */
+ temp = (out >> (trip * QRK_DTS_SHIFT_TP)) & QRK_DTS_MASK_TP_THRES;
+ temp -= QRK_DTS_TEMP_BASE;
+
+ return temp;
+}
+
+static int update_trip_temp(struct soc_sensor_entry *aux_entry,
+ int trip_index, int temp)
+{
+ u32 out;
+ u32 temp_out;
+ u32 store_ptps;
+ int ret;
+
+ mutex_lock(&dts_update_mutex);
+ if (aux_entry->locked) {
+ ret = -EPERM;
+ goto failed;
+ }
+
+ ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
+ QRK_DTS_REG_OFFSET_PTPS, &store_ptps);
+ if (ret)
+ goto failed;
+
+ /*
+ * Protection against unsafe trip point thresdhold value.
+ * As Quark X1000 data-sheet does not provide any recommendation
+ * regarding the safe trip point threshold value to use, we choose
+ * the safe value according to the threshold value set by UEFI BIOS.
+ */
+ if (temp > QRK_DTS_SAFE_TP_THRES)
+ temp = QRK_DTS_SAFE_TP_THRES;
+
+ /*
+ * Thermal Sensor Programmable Trip Point Register has 8-bit
+ * fields for critical (catastrophic) and hot set trip point
+ * thresholds. The threshold value is always offset by its
+ * temperature base (50 degree Celsius).
+ */
+ temp_out = temp + QRK_DTS_TEMP_BASE;
+ out = (store_ptps & ~(QRK_DTS_MASK_TP_THRES <<
+ (trip_index * QRK_DTS_SHIFT_TP)));
+ out |= (temp_out & QRK_DTS_MASK_TP_THRES) <<
+ (trip_index * QRK_DTS_SHIFT_TP);
+
+ ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
+ QRK_DTS_REG_OFFSET_PTPS, out);
+
+failed:
+ mutex_unlock(&dts_update_mutex);
+ return ret;
+}
+
+static inline int sys_set_trip_temp(struct thermal_zone_device *tzd,
+ const struct thermal_trip *trip,
+ int 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,
+ int *temp)
+{
+ u32 out;
+ int ret;
+
+ mutex_lock(&dts_update_mutex);
+ ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
+ QRK_DTS_REG_OFFSET_TEMP, &out);
+ mutex_unlock(&dts_update_mutex);
+
+ if (ret)
+ return ret;
+
+ /*
+ * Thermal Sensor Temperature Register has 8-bit field
+ * for temperature value (offset by temperature base
+ * 50 degree Celsius).
+ */
+ out = (out >> QRK_DTS_OFFSET_TEMP) & QRK_DTS_MASK_TEMP;
+ *temp = out - QRK_DTS_TEMP_BASE;
+
+ return 0;
+}
+
+static int sys_change_mode(struct thermal_zone_device *tzd,
+ enum thermal_device_mode mode)
+{
+ int ret;
+
+ mutex_lock(&dts_update_mutex);
+ if (mode == THERMAL_DEVICE_ENABLED)
+ ret = soc_dts_enable(tzd);
+ else
+ ret = soc_dts_disable(tzd);
+ mutex_unlock(&dts_update_mutex);
+
+ return ret;
+}
+
+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,
+};
+
+static void free_soc_dts(struct soc_sensor_entry *aux_entry)
+{
+ if (aux_entry) {
+ if (!aux_entry->locked) {
+ mutex_lock(&dts_update_mutex);
+ iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
+ QRK_DTS_REG_OFFSET_ENABLE,
+ aux_entry->store_dts_enable);
+
+ iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
+ QRK_DTS_REG_OFFSET_PTPS,
+ aux_entry->store_ptps);
+ mutex_unlock(&dts_update_mutex);
+ }
+ thermal_zone_device_unregister(aux_entry->tzone);
+ kfree(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;
+
+ aux_entry = kzalloc_obj(*aux_entry);
+ if (!aux_entry) {
+ err = -ENOMEM;
+ return ERR_PTR(-ENOMEM);
+ }
+
+ /* Check if DTS register is locked */
+ err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
+ QRK_DTS_REG_OFFSET_LOCK, &out);
+ if (err)
+ goto err_ret;
+
+ aux_entry->locked = !!(out & QRK_DTS_LOCK_BIT);
+
+ /* Store DTS default state if DTS registers are not locked */
+ if (!aux_entry->locked) {
+ /* Store DTS default enable for restore on exit */
+ err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
+ QRK_DTS_REG_OFFSET_ENABLE,
+ &aux_entry->store_dts_enable);
+ if (err)
+ goto err_ret;
+
+ /* Store DTS default PTPS register for restore on exit */
+ err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
+ QRK_DTS_REG_OFFSET_PTPS,
+ &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;
+ }
+
+ 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;
+
+ 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",
+ trips,
+ QRK_MAX_DTS_TRIPS,
+ aux_entry,
+ &tzone_ops,
+ NULL, 0, polling_delay);
+ if (IS_ERR(aux_entry->tzone)) {
+ err = PTR_ERR(aux_entry->tzone);
+ goto err_ret;
+ }
+
+ err = thermal_zone_device_enable(aux_entry->tzone);
+ if (err)
+ goto err_aux_status;
+
+ return aux_entry;
+
+err_aux_status:
+ thermal_zone_device_unregister(aux_entry->tzone);
+err_ret:
+ kfree(aux_entry);
+ return ERR_PTR(err);
+}
+
+static const struct x86_cpu_id qrk_thermal_ids[] __initconst = {
+ X86_MATCH_VFM(INTEL_QUARK_X1000, NULL),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, qrk_thermal_ids);
+
+static int __init intel_quark_thermal_init(void)
+{
+ if (!x86_match_cpu(qrk_thermal_ids) || !iosf_mbi_available())
+ return -ENODEV;
+
+ soc_dts = alloc_soc_dts();
+ if (IS_ERR(soc_dts))
+ return PTR_ERR(soc_dts);
+
+ return 0;
+}
+
+static void __exit intel_quark_thermal_exit(void)
+{
+ free_soc_dts(soc_dts);
+}
+
+module_init(intel_quark_thermal_init)
+module_exit(intel_quark_thermal_exit)
+
+MODULE_DESCRIPTION("Intel Quark DTS Thermal Driver");
+MODULE_AUTHOR("Ong Boon Leong <boon.leong.ong@intel.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/thermal/intel/intel_soc_dts_iosf.c b/drivers/thermal/intel/intel_soc_dts_iosf.c
new file mode 100644
index 000000000..937738783
--- /dev/null
+++ b/drivers/thermal/intel/intel_soc_dts_iosf.c
@@ -0,0 +1,393 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * intel_soc_dts_iosf.c
+ * Copyright (c) 2015, Intel Corporation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/intel_tcc.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <asm/iosf_mbi.h>
+#include "intel_soc_dts_iosf.h"
+
+#define SOC_DTS_OFFSET_ENABLE 0xB0
+#define SOC_DTS_OFFSET_TEMP 0xB1
+
+#define SOC_DTS_OFFSET_PTPS 0xB2
+#define SOC_DTS_OFFSET_PTTS 0xB3
+#define SOC_DTS_OFFSET_PTTSS 0xB4
+#define SOC_DTS_OFFSET_PTMC 0x80
+#define SOC_DTS_TE_AUX0 0xB5
+#define SOC_DTS_TE_AUX1 0xB6
+
+#define SOC_DTS_AUX0_ENABLE_BIT BIT(0)
+#define SOC_DTS_AUX1_ENABLE_BIT BIT(1)
+#define SOC_DTS_CPU_MODULE0_ENABLE_BIT BIT(16)
+#define SOC_DTS_CPU_MODULE1_ENABLE_BIT BIT(17)
+#define SOC_DTS_TE_SCI_ENABLE BIT(9)
+#define SOC_DTS_TE_SMI_ENABLE BIT(10)
+#define SOC_DTS_TE_MSI_ENABLE BIT(11)
+#define SOC_DTS_TE_APICA_ENABLE BIT(14)
+#define SOC_DTS_PTMC_APIC_DEASSERT_BIT BIT(4)
+
+/* DTS encoding for TJ MAX temperature */
+#define SOC_DTS_TJMAX_ENCODING 0x7F
+
+/* Mask for two trips in status bits */
+#define SOC_DTS_TRIP_MASK 0x03
+
+static int update_trip_temp(struct intel_soc_dts_sensors *sensors,
+ int thres_index, int temp)
+{
+ int status;
+ u32 temp_out;
+ u32 out;
+ unsigned long update_ptps;
+ u32 store_ptps;
+ u32 store_ptmc;
+ u32 store_te_out;
+ u32 te_out;
+ u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE;
+
+ if (sensors->intr_type == INTEL_SOC_DTS_INTERRUPT_MSI)
+ int_enable_bit |= SOC_DTS_TE_MSI_ENABLE;
+
+ temp_out = (sensors->tj_max - temp) / 1000;
+
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_PTPS, &store_ptps);
+ if (status)
+ return status;
+
+ update_ptps = store_ptps;
+ bitmap_set_value8(&update_ptps, temp_out & 0xFF, thres_index * 8);
+ out = update_ptps;
+
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_PTPS, out);
+ if (status)
+ return status;
+
+ pr_debug("update_trip_temp PTPS = %x\n", out);
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_PTMC, &out);
+ if (status)
+ goto err_restore_ptps;
+
+ store_ptmc = out;
+
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_TE_AUX0 + thres_index,
+ &te_out);
+ if (status)
+ goto err_restore_ptmc;
+
+ store_te_out = te_out;
+ /* Enable for CPU module 0 and module 1 */
+ out |= (SOC_DTS_CPU_MODULE0_ENABLE_BIT |
+ SOC_DTS_CPU_MODULE1_ENABLE_BIT);
+ if (temp) {
+ if (thres_index)
+ out |= SOC_DTS_AUX1_ENABLE_BIT;
+ else
+ out |= SOC_DTS_AUX0_ENABLE_BIT;
+ te_out |= int_enable_bit;
+ } else {
+ if (thres_index)
+ out &= ~SOC_DTS_AUX1_ENABLE_BIT;
+ else
+ out &= ~SOC_DTS_AUX0_ENABLE_BIT;
+ te_out &= ~int_enable_bit;
+ }
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_PTMC, out);
+ if (status)
+ goto err_restore_te_out;
+
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_TE_AUX0 + thres_index,
+ te_out);
+ if (status)
+ goto err_restore_te_out;
+
+ return 0;
+err_restore_te_out:
+ iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_PTMC, store_te_out);
+err_restore_ptmc:
+ iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_PTMC, store_ptmc);
+err_restore_ptps:
+ iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_PTPS, store_ptps);
+ /* Nothing we can do if restore fails */
+
+ return status;
+}
+
+static int sys_set_trip_temp(struct thermal_zone_device *tzd,
+ 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(sensors, trip_index, temp);
+ mutex_unlock(&sensors->dts_update_lock);
+
+ return status;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd,
+ int *temp)
+{
+ int status;
+ u32 out;
+ struct intel_soc_dts_sensor_entry *dts = thermal_zone_device_priv(tzd);
+ struct intel_soc_dts_sensors *sensors;
+ unsigned long raw;
+
+ sensors = dts->sensors;
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_TEMP, &out);
+ if (status)
+ return status;
+
+ raw = out;
+ out = bitmap_get_value8(&raw, dts->id * 8) - SOC_DTS_TJMAX_ENCODING;
+ *temp = sensors->tj_max - out * 1000;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops tzone_ops = {
+ .get_temp = sys_get_curr_temp,
+ .set_trip_temp = sys_set_trip_temp,
+};
+
+static int soc_dts_enable(int id)
+{
+ u32 out;
+ int ret;
+
+ ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_ENABLE, &out);
+ if (ret)
+ return ret;
+
+ if (!(out & BIT(id))) {
+ out |= BIT(id);
+ ret = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_ENABLE, out);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static void remove_dts_thermal_zone(struct intel_soc_dts_sensor_entry *dts)
+{
+ 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,
+ struct thermal_trip *trips)
+{
+ char name[10];
+ u32 store_ptps;
+ int ret;
+
+ /* Store status to restor on exit */
+ ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_ENABLE, &dts->store_status);
+ if (ret)
+ goto err_ret;
+
+ dts->id = id;
+
+ /* 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) {
+ int i;
+
+ for (i = 0; i <= 1; i++) {
+ if (store_ptps & (0xFFU << i * 8))
+ trips[i].flags &= ~THERMAL_TRIP_FLAG_RW_TEMP;
+ }
+ }
+ snprintf(name, sizeof(name), "soc_dts%d", id);
+ 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;
+ }
+ ret = thermal_zone_device_enable(dts->tzone);
+ if (ret)
+ goto err_enable;
+
+ ret = soc_dts_enable(id);
+ if (ret)
+ goto err_enable;
+
+ return 0;
+err_enable:
+ thermal_zone_device_unregister(dts->tzone);
+err_ret:
+ return ret;
+}
+
+void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors)
+{
+ u32 sticky_out;
+ int status;
+ u32 ptmc_out;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sensors->intr_notify_lock, flags);
+
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_PTMC, &ptmc_out);
+ ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT;
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_PTMC, ptmc_out);
+
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_PTTSS, &sticky_out);
+ pr_debug("status %d PTTSS %x\n", status, sticky_out);
+ if (sticky_out & SOC_DTS_TRIP_MASK) {
+ int i;
+ /* reset sticky bit */
+ status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_PTTSS, sticky_out);
+ spin_unlock_irqrestore(&sensors->intr_notify_lock, flags);
+
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ pr_debug("TZD update for zone %d\n", i);
+ thermal_zone_device_update(sensors->soc_dts[i].tzone,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+ } else
+ spin_unlock_irqrestore(&sensors->intr_notify_lock, flags);
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_interrupt_handler);
+
+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;
+ int tj_max;
+ int ret;
+ int i;
+
+ if (!iosf_mbi_available())
+ return ERR_PTR(-ENODEV);
+
+ tj_max = intel_tcc_get_tjmax(-1);
+ if (tj_max < 0)
+ return ERR_PTR(tj_max);
+
+ sensors = kzalloc_obj(*sensors);
+ if (!sensors)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_init(&sensors->intr_notify_lock);
+ mutex_init(&sensors->dts_update_lock);
+ sensors->intr_type = intr_type;
+ sensors->tj_max = tj_max * 1000;
+
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ int temp;
+
+ sensors->soc_dts[i].sensors = sensors;
+
+ 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_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;
+ }
+
+ ret = update_trip_temp(sensors, 1, temp);
+ if (ret)
+ goto err_reset_trips;
+ }
+
+ 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_reset_trips:
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; i++)
+ dts_trips_reset(sensors, i);
+
+ kfree(sensors);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_init);
+
+void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors)
+{
+ int i;
+
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ 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_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
new file mode 100644
index 000000000..44eee844a
--- /dev/null
+++ b/drivers/thermal/intel/intel_soc_dts_iosf.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * intel_soc_dts_iosf.h
+ * Copyright (c) 2015, Intel Corporation.
+ */
+
+#ifndef _INTEL_SOC_DTS_IOSF_CORE_H
+#define _INTEL_SOC_DTS_IOSF_CORE_H
+
+#include <linux/thermal.h>
+
+/* 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,
+ INTEL_SOC_DTS_INTERRUPT_MSI,
+ INTEL_SOC_DTS_INTERRUPT_SCI,
+ INTEL_SOC_DTS_INTERRUPT_SMI,
+};
+
+struct intel_soc_dts_sensors;
+
+struct intel_soc_dts_sensor_entry {
+ int id;
+ u32 store_status;
+ struct thermal_zone_device *tzone;
+ struct intel_soc_dts_sensors *sensors;
+};
+
+struct intel_soc_dts_sensors {
+ u32 tj_max;
+ spinlock_t intr_notify_lock;
+ struct mutex dts_update_lock;
+ enum intel_soc_dts_interrupt_type intr_type;
+ 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,
+ 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);
+#endif
diff --git a/drivers/thermal/intel/intel_soc_dts_thermal.c b/drivers/thermal/intel/intel_soc_dts_thermal.c
new file mode 100644
index 000000000..718c6326e
--- /dev/null
+++ b/drivers/thermal/intel/intel_soc_dts_thermal.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * intel_soc_dts_thermal.c
+ * Copyright (c) 2014, Intel Corporation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+#include "intel_soc_dts_iosf.h"
+
+#define CRITICAL_OFFSET_FROM_TJ_MAX 5000
+
+static int crit_offset = CRITICAL_OFFSET_FROM_TJ_MAX;
+module_param(crit_offset, int, 0644);
+MODULE_PARM_DESC(crit_offset,
+ "Critical Temperature offset from tj max in millidegree Celsius.");
+
+/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */
+#define BYT_SOC_DTS_APIC_IRQ 86
+
+static int soc_dts_thres_gsi;
+static int soc_dts_thres_irq;
+static struct intel_soc_dts_sensors *soc_dts;
+
+static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data)
+{
+ pr_debug("proc_thermal_interrupt\n");
+ intel_soc_dts_iosf_interrupt_handler(soc_dts);
+
+ return IRQ_HANDLED;
+}
+
+static const struct x86_cpu_id soc_thermal_ids[] = {
+ X86_MATCH_VFM(INTEL_ATOM_SILVERMONT, BYT_SOC_DTS_APIC_IRQ),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids);
+
+static int __init intel_soc_thermal_init(void)
+{
+ int err = 0;
+ const struct x86_cpu_id *match_cpu;
+
+ match_cpu = x86_match_cpu(soc_thermal_ids);
+ if (!match_cpu)
+ 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, true,
+ crit_offset);
+ if (IS_ERR(soc_dts)) {
+ err = PTR_ERR(soc_dts);
+ return err;
+ }
+
+ soc_dts_thres_gsi = (int)match_cpu->driver_data;
+ if (soc_dts_thres_gsi) {
+ /*
+ * Note the flags here MUST match the firmware defaults, rather
+ * then the request_irq flags, otherwise we get an EBUSY error.
+ */
+ soc_dts_thres_irq = acpi_register_gsi(NULL, soc_dts_thres_gsi,
+ ACPI_LEVEL_SENSITIVE,
+ ACPI_ACTIVE_LOW);
+ if (soc_dts_thres_irq < 0) {
+ pr_warn("intel_soc_dts: Could not get IRQ for GSI %d, err %d\n",
+ soc_dts_thres_gsi, soc_dts_thres_irq);
+ soc_dts_thres_irq = 0;
+ }
+ }
+
+ if (soc_dts_thres_irq) {
+ err = request_threaded_irq(soc_dts_thres_irq, NULL,
+ soc_irq_thread_fn,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "soc_dts", soc_dts);
+ if (err) {
+ /*
+ * Do not just error out because the user space thermal
+ * daemon such as DPTF may use polling instead of being
+ * interrupt driven.
+ */
+ pr_warn("request_threaded_irq ret %d\n", err);
+ }
+ }
+
+ return 0;
+}
+
+static void __exit intel_soc_thermal_exit(void)
+{
+ 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);
+}
+
+module_init(intel_soc_thermal_init)
+module_exit(intel_soc_thermal_exit)
+
+MODULE_DESCRIPTION("Intel SoC DTS Thermal Driver");
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel/intel_tcc.c b/drivers/thermal/intel/intel_tcc.c
new file mode 100644
index 000000000..d1fa3c63d
--- /dev/null
+++ b/drivers/thermal/intel/intel_tcc.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * intel_tcc.c - Library for Intel TCC (thermal control circuitry) MSR access
+ * Copyright (c) 2022, Intel Corporation.
+ */
+
+#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, negative value means any cpu.
+ *
+ * Get the TjMax value, which is the default thermal throttling or TCC
+ * activation temperature in degrees C.
+ *
+ * Return: Tjmax value in degrees C on success, negative error code otherwise.
+ */
+int intel_tcc_get_tjmax(int cpu)
+{
+ struct msr msrval;
+ int val, err;
+
+ if (cpu < 0)
+ err = rdmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, &msrval.q);
+ else
+ err = rdmsrq_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &msrval.q);
+ if (err)
+ return err;
+
+ val = (msrval.l >> 16) & 0xff;
+
+ return val ? val : -ENODATA;
+}
+EXPORT_SYMBOL_NS_GPL(intel_tcc_get_tjmax, "INTEL_TCC");
+
+/**
+ * intel_tcc_get_offset() - returns the TCC Offset value to Tjmax
+ * @cpu: cpu that the MSR should be run on, negative value means any cpu.
+ *
+ * Get the TCC offset value to Tjmax. The effective thermal throttling or TCC
+ * activation temperature equals "Tjmax" - "TCC Offset", in degrees C.
+ *
+ * Return: Tcc offset value in degrees C on success, negative error code otherwise.
+ */
+int intel_tcc_get_offset(int cpu)
+{
+ struct msr val;
+ int err;
+
+ if (cpu < 0)
+ err = rdmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, &val.q);
+ else
+ err = rdmsrq_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &val.q);
+ if (err)
+ return err;
+
+ return (val.l >> 24) & intel_tcc_temp_masks.tcc_offset;
+}
+EXPORT_SYMBOL_NS_GPL(intel_tcc_get_offset, "INTEL_TCC");
+
+/**
+ * intel_tcc_set_offset() - set the TCC offset value to Tjmax
+ * @cpu: cpu that the MSR should be run on, negative value means any cpu.
+ * @offset: TCC offset value in degree C
+ *
+ * Set the TCC Offset value to Tjmax. The effective thermal throttling or TCC
+ * activation temperature equals "Tjmax" - "TCC Offset", in degree C.
+ *
+ * Return: On success returns 0, negative error code otherwise.
+ */
+
+int intel_tcc_set_offset(int cpu, int offset)
+{
+ struct msr val;
+ int err;
+
+ if (!intel_tcc_temp_masks.tcc_offset)
+ return -ENODEV;
+
+ if (offset < 0 || offset > intel_tcc_temp_masks.tcc_offset)
+ return -EINVAL;
+
+ if (cpu < 0)
+ err = rdmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, &val.q);
+ else
+ err = rdmsrq_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &val.q);
+ if (err)
+ return err;
+
+ /* MSR Locked */
+ if (val.l & BIT(31))
+ return -EPERM;
+
+ val.l &= ~(intel_tcc_temp_masks.tcc_offset << 24);
+ val.l |= offset << 24;
+
+ if (cpu < 0)
+ return wrmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, val.q);
+ else
+ return wrmsrq_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, val.q);
+}
+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, negative 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: 0 on success, negative error code otherwise.
+ */
+int intel_tcc_get_temp(int cpu, int *temp, bool pkg)
+{
+ u32 msr = pkg ? MSR_IA32_PACKAGE_THERM_STATUS : MSR_IA32_THERM_STATUS;
+ u32 mask;
+ struct msr val;
+ int tjmax, err;
+
+ tjmax = intel_tcc_get_tjmax(cpu);
+ if (tjmax < 0)
+ return tjmax;
+
+ if (cpu < 0)
+ err = rdmsrq_safe(msr, &val.q);
+ else
+ err = rdmsrq_safe_on_cpu(cpu, msr, &val.q);
+ if (err)
+ return err;
+
+ /* Temperature is beyond the valid thermal sensor range */
+ if (!(val.l & BIT(31)))
+ return -ENODATA;
+
+ mask = get_temp_mask(pkg);
+
+ *temp = tjmax - ((val.l >> 16) & mask);
+
+ return 0;
+}
+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
new file mode 100644
index 000000000..52ea4f7fc
--- /dev/null
+++ b/drivers/thermal/intel/intel_tcc_cooling.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cooling device driver that activates the processor throttling by
+ * programming the TCC Offset register.
+ * Copyright (c) 2021, Intel Corporation.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/intel_tcc.h>
+#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)
+
+static struct thermal_cooling_device *tcc_cdev;
+
+static int tcc_get_max_state(struct thermal_cooling_device *cdev, unsigned long
+ *state)
+{
+ *state = intel_tcc_get_offset_mask();
+ return 0;
+}
+
+static int tcc_get_cur_state(struct thermal_cooling_device *cdev, unsigned long
+ *state)
+{
+ int offset = intel_tcc_get_offset(-1);
+
+ if (offset < 0)
+ return offset;
+
+ *state = offset;
+ return 0;
+}
+
+static int tcc_set_cur_state(struct thermal_cooling_device *cdev, unsigned long
+ state)
+{
+ return intel_tcc_set_offset(-1, (int)state);
+}
+
+static const struct thermal_cooling_device_ops tcc_cooling_ops = {
+ .get_max_state = tcc_get_max_state,
+ .get_cur_state = tcc_get_cur_state,
+ .set_cur_state = tcc_set_cur_state,
+};
+
+static const struct x86_cpu_id tcc_ids[] __initconst = {
+ X86_MATCH_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),
+ X86_MATCH_VFM(INTEL_ARROWLAKE, NULL),
+ X86_MATCH_VFM(INTEL_ARROWLAKE_U, NULL),
+ X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL),
+ X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_NOVALAKE, NULL),
+ X86_MATCH_VFM(INTEL_NOVALAKE_L, NULL),
+ {}
+};
+
+MODULE_DEVICE_TABLE(x86cpu, tcc_ids);
+
+static int __init tcc_cooling_init(void)
+{
+ u64 val;
+ const struct x86_cpu_id *id;
+ int err;
+
+ id = x86_match_cpu(tcc_ids);
+ if (!id)
+ return -ENODEV;
+
+ err = rdmsrq_safe(MSR_PLATFORM_INFO, &val);
+ if (err)
+ return err;
+
+ if (!(val & TCC_PROGRAMMABLE))
+ return -ENODEV;
+
+ err = rdmsrq_safe(MSR_IA32_TEMPERATURE_TARGET, &val);
+ if (err)
+ return err;
+
+ if (val & TCC_LOCKED) {
+ pr_info("TCC Offset locked\n");
+ return -ENODEV;
+ }
+
+ pr_info("Programmable TCC Offset detected\n");
+
+ tcc_cdev =
+ thermal_cooling_device_register("TCC Offset", NULL,
+ &tcc_cooling_ops);
+ if (IS_ERR(tcc_cdev))
+ return PTR_ERR(tcc_cdev);
+
+ return 0;
+}
+
+module_init(tcc_cooling_init)
+
+static void __exit tcc_cooling_exit(void)
+{
+ thermal_cooling_device_unregister(tcc_cdev);
+}
+
+module_exit(tcc_cooling_exit)
+
+MODULE_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
new file mode 100644
index 000000000..4d4e81601
--- /dev/null
+++ b/drivers/thermal/intel/therm_throt.c
@@ -0,0 +1,1088 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Thermal throttle event support code (such as syslog messaging and rate
+ * limiting) that was factored out from x86_64 (mce_intel.c) and i386 (p4.c).
+ *
+ * This allows consistent reporting of CPU thermal throttle events.
+ *
+ * Maintains a counter in /sys that keeps track of the number of thermal
+ * events, such that the user knows how bad the thermal problem might be
+ * (since the logging to syslog is rate limited).
+ *
+ * Author: Dmitriy Zavin (dmitriyz@google.com)
+ *
+ * Credits: Adapted from Zwane Mwaikambo's original code in mce_intel.c.
+ * Inspired by Ross Biro's and Al Borchers' counter code.
+ */
+#include <linux/syscore_ops.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/percpu.h>
+#include <linux/export.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/smp.h>
+#include <linux/sysfs.h>
+#include <linux/cpu.h>
+
+#include <asm/processor.h>
+#include <asm/thermal.h>
+#include <asm/traps.h>
+#include <asm/apic.h>
+#include <asm/irq.h>
+#include <asm/msr.h>
+
+#include "intel_hfi.h"
+#include "thermal_interrupt.h"
+
+/* How long to wait between reporting thermal events */
+#define CHECK_INTERVAL (300 * HZ)
+
+#define THERMAL_THROTTLING_EVENT 0
+#define POWER_LIMIT_EVENT 1
+
+/**
+ * struct _thermal_state - Represent the current thermal event state
+ * @next_check: Stores the next timestamp, when it is allowed
+ * to log the next warning message.
+ * @last_interrupt_time: Stores the timestamp for the last threshold
+ * high event.
+ * @therm_work: Delayed workqueue structure
+ * @count: Stores the current running count for thermal
+ * or power threshold interrupts.
+ * @last_count: Stores the previous running count for thermal
+ * or power threshold interrupts.
+ * @max_time_ms: This shows the maximum amount of time CPU was
+ * in throttled state for a single thermal
+ * threshold high to low state.
+ * @total_time_ms: This is a cumulative time during which CPU was
+ * in the throttled state.
+ * @rate_control_active: Set when a throttling message is logged.
+ * This is used for the purpose of rate-control.
+ * @new_event: Stores the last high/low status of the
+ * THERM_STATUS_PROCHOT or
+ * THERM_STATUS_POWER_LIMIT.
+ * @level: Stores whether this _thermal_state instance is
+ * for a CORE level or for PACKAGE level.
+ * @sample_index: Index for storing the next sample in the buffer
+ * temp_samples[].
+ * @sample_count: Total number of samples collected in the buffer
+ * temp_samples[].
+ * @average: The last moving average of temperature samples
+ * @baseline_temp: Temperature at which thermal threshold high
+ * interrupt was generated.
+ * @temp_samples: Storage for temperature samples to calculate
+ * moving average.
+ *
+ * This structure is used to represent data related to thermal state for a CPU.
+ * There is a separate storage for core and package level for each CPU.
+ */
+struct _thermal_state {
+ u64 next_check;
+ u64 last_interrupt_time;
+ struct delayed_work therm_work;
+ unsigned long count;
+ unsigned long last_count;
+ unsigned long max_time_ms;
+ unsigned long total_time_ms;
+ bool rate_control_active;
+ bool new_event;
+ u8 level;
+ u8 sample_index;
+ u8 sample_count;
+ u8 average;
+ u8 baseline_temp;
+ u8 temp_samples[3];
+};
+
+struct thermal_state {
+ struct _thermal_state core_throttle;
+ struct _thermal_state core_power_limit;
+ struct _thermal_state package_throttle;
+ struct _thermal_state package_power_limit;
+ struct _thermal_state core_thresh0;
+ struct _thermal_state core_thresh1;
+ struct _thermal_state pkg_thresh0;
+ struct _thermal_state pkg_thresh1;
+};
+
+/* Callback to handle core threshold interrupts */
+int (*platform_thermal_notify)(__u64 msr_val);
+EXPORT_SYMBOL(platform_thermal_notify);
+
+/* Callback to handle core package threshold_interrupts */
+int (*platform_thermal_package_notify)(__u64 msr_val);
+EXPORT_SYMBOL_GPL(platform_thermal_package_notify);
+
+/* Callback support of rate control, return true, if
+ * callback has rate control */
+bool (*platform_thermal_package_rate_control)(void);
+EXPORT_SYMBOL_GPL(platform_thermal_package_rate_control);
+
+
+static DEFINE_PER_CPU(struct thermal_state, thermal_state);
+
+static atomic_t therm_throt_en = ATOMIC_INIT(0);
+
+static u32 lvtthmr_init __read_mostly;
+
+#ifdef CONFIG_SYSFS
+#define define_therm_throt_device_one_ro(_name) \
+ static DEVICE_ATTR(_name, 0444, \
+ therm_throt_device_show_##_name, \
+ NULL) \
+
+#define define_therm_throt_device_show_func(event, name) \
+ \
+static ssize_t therm_throt_device_show_##event##_##name( \
+ struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ unsigned int cpu = dev->id; \
+ ssize_t ret; \
+ \
+ preempt_disable(); /* CPU hotplug */ \
+ if (cpu_online(cpu)) { \
+ ret = sysfs_emit(buf, "%lu\n", \
+ per_cpu(thermal_state, cpu).event.name); \
+ } else \
+ ret = 0; \
+ preempt_enable(); \
+ \
+ return ret; \
+}
+
+define_therm_throt_device_show_func(core_throttle, count);
+define_therm_throt_device_one_ro(core_throttle_count);
+
+define_therm_throt_device_show_func(core_power_limit, count);
+define_therm_throt_device_one_ro(core_power_limit_count);
+
+define_therm_throt_device_show_func(package_throttle, count);
+define_therm_throt_device_one_ro(package_throttle_count);
+
+define_therm_throt_device_show_func(package_power_limit, count);
+define_therm_throt_device_one_ro(package_power_limit_count);
+
+define_therm_throt_device_show_func(core_throttle, max_time_ms);
+define_therm_throt_device_one_ro(core_throttle_max_time_ms);
+
+define_therm_throt_device_show_func(package_throttle, max_time_ms);
+define_therm_throt_device_one_ro(package_throttle_max_time_ms);
+
+define_therm_throt_device_show_func(core_throttle, total_time_ms);
+define_therm_throt_device_one_ro(core_throttle_total_time_ms);
+
+define_therm_throt_device_show_func(package_throttle, total_time_ms);
+define_therm_throt_device_one_ro(package_throttle_total_time_ms);
+
+static struct attribute *thermal_throttle_attrs[] = {
+ &dev_attr_core_throttle_count.attr,
+ &dev_attr_core_throttle_max_time_ms.attr,
+ &dev_attr_core_throttle_total_time_ms.attr,
+ NULL
+};
+
+static const struct attribute_group thermal_attr_group = {
+ .attrs = thermal_throttle_attrs,
+ .name = "thermal_throttle"
+};
+#endif /* CONFIG_SYSFS */
+
+#define THERM_THROT_POLL_INTERVAL HZ
+#define THERM_STATUS_PROCHOT_LOG BIT(1)
+
+static u64 therm_intr_core_clear_mask;
+static u64 therm_intr_pkg_clear_mask;
+
+static void thermal_intr_init_core_clear_mask(void)
+{
+ if (therm_intr_core_clear_mask)
+ return;
+
+ /*
+ * Reference: Intel SDM Volume 4
+ * "Table 2-2. IA-32 Architectural MSRs", MSR 0x19C
+ * IA32_THERM_STATUS.
+ */
+
+ /*
+ * Bit 1, 3, 5: CPUID.01H:EDX[22] = 1. This driver will not
+ * enable interrupts, when 0 as it checks for X86_FEATURE_ACPI.
+ */
+ therm_intr_core_clear_mask = (BIT(1) | BIT(3) | BIT(5));
+
+ /*
+ * Bit 7 and 9: Thermal Threshold #1 and #2 log
+ * If CPUID.01H:ECX[8] = 1
+ */
+ if (boot_cpu_has(X86_FEATURE_TM2))
+ therm_intr_core_clear_mask |= (BIT(7) | BIT(9));
+
+ /* Bit 11: Power Limitation log (R/WC0) If CPUID.06H:EAX[4] = 1 */
+ if (boot_cpu_has(X86_FEATURE_PLN))
+ therm_intr_core_clear_mask |= BIT(11);
+
+ /*
+ * Bit 13: Current Limit log (R/WC0) If CPUID.06H:EAX[7] = 1
+ * Bit 15: Cross Domain Limit log (R/WC0) If CPUID.06H:EAX[7] = 1
+ */
+ if (boot_cpu_has(X86_FEATURE_HWP))
+ therm_intr_core_clear_mask |= (BIT(13) | BIT(15));
+}
+
+static void thermal_intr_init_pkg_clear_mask(void)
+{
+ if (therm_intr_pkg_clear_mask)
+ return;
+
+ /*
+ * Reference: Intel SDM Volume 4
+ * "Table 2-2. IA-32 Architectural MSRs", MSR 0x1B1
+ * IA32_PACKAGE_THERM_STATUS.
+ */
+
+ /* All bits except BITs 25 and 26 depend on CPUID.06H: EAX[6] = 1 */
+ if (boot_cpu_has(X86_FEATURE_PTS))
+ therm_intr_pkg_clear_mask = (BIT(1) | BIT(3) | BIT(5) | BIT(7) | BIT(9) | BIT(11));
+
+ /*
+ * Intel SDM Volume 1: Thermal and Power Management Leaf
+ * Bit 26: CPUID.06H: EAX[19] = 1
+ */
+ if (boot_cpu_has(X86_FEATURE_HFI))
+ therm_intr_pkg_clear_mask |= BIT(26);
+
+ /*
+ * Intel SDM Volume 1: Thermal and Power Management Leaf
+ * Bit 25: CPUID.06H: EAX[24] = 1
+ */
+ if (boot_cpu_has(X86_FEATURE_DPTI))
+ therm_intr_pkg_clear_mask |= BIT(25);
+}
+
+/*
+ * Clear the bits in package thermal status register for bit = 1
+ * in bitmask
+ */
+void thermal_clear_package_intr_status(int level, u64 bit_mask)
+{
+ u64 msr_val;
+ int msr;
+
+ if (level == CORE_LEVEL) {
+ msr = MSR_IA32_THERM_STATUS;
+ msr_val = therm_intr_core_clear_mask;
+ } else {
+ msr = MSR_IA32_PACKAGE_THERM_STATUS;
+ msr_val = therm_intr_pkg_clear_mask;
+ }
+
+ msr_val &= ~bit_mask;
+ wrmsrq(msr, msr_val);
+}
+EXPORT_SYMBOL_GPL(thermal_clear_package_intr_status);
+
+static void get_therm_status(int level, bool *proc_hot, u8 *temp)
+{
+ int msr;
+ u64 msr_val;
+
+ if (level == CORE_LEVEL)
+ msr = MSR_IA32_THERM_STATUS;
+ else
+ msr = MSR_IA32_PACKAGE_THERM_STATUS;
+
+ rdmsrq(msr, msr_val);
+ if (msr_val & THERM_STATUS_PROCHOT_LOG)
+ *proc_hot = true;
+ else
+ *proc_hot = false;
+
+ *temp = (msr_val >> 16) & 0x7F;
+}
+
+static void __maybe_unused throttle_active_work(struct work_struct *work)
+{
+ struct _thermal_state *state = container_of(to_delayed_work(work),
+ struct _thermal_state, therm_work);
+ unsigned int i, avg, this_cpu = smp_processor_id();
+ u64 now = get_jiffies_64();
+ bool hot;
+ u8 temp;
+
+ get_therm_status(state->level, &hot, &temp);
+ /* temperature value is offset from the max so lesser means hotter */
+ if (!hot && temp > state->baseline_temp) {
+ if (state->rate_control_active)
+ pr_info("CPU%d: %s temperature/speed normal (total events = %lu)\n",
+ this_cpu,
+ state->level == CORE_LEVEL ? "Core" : "Package",
+ state->count);
+
+ state->rate_control_active = false;
+ return;
+ }
+
+ if (time_before64(now, state->next_check) &&
+ state->rate_control_active)
+ goto re_arm;
+
+ state->next_check = now + CHECK_INTERVAL;
+
+ if (state->count != state->last_count) {
+ /* There was one new thermal interrupt */
+ state->last_count = state->count;
+ state->average = 0;
+ state->sample_count = 0;
+ state->sample_index = 0;
+ }
+
+ state->temp_samples[state->sample_index] = temp;
+ state->sample_count++;
+ state->sample_index = (state->sample_index + 1) % ARRAY_SIZE(state->temp_samples);
+ if (state->sample_count < ARRAY_SIZE(state->temp_samples))
+ goto re_arm;
+
+ avg = 0;
+ for (i = 0; i < ARRAY_SIZE(state->temp_samples); ++i)
+ avg += state->temp_samples[i];
+
+ avg /= ARRAY_SIZE(state->temp_samples);
+
+ if (state->average > avg) {
+ pr_warn("CPU%d: %s temperature is above threshold, cpu clock is throttled (total events = %lu)\n",
+ this_cpu,
+ state->level == CORE_LEVEL ? "Core" : "Package",
+ state->count);
+ state->rate_control_active = true;
+ }
+
+ state->average = avg;
+
+re_arm:
+ thermal_clear_package_intr_status(state->level, THERM_STATUS_PROCHOT_LOG);
+ schedule_delayed_work_on(this_cpu, &state->therm_work, THERM_THROT_POLL_INTERVAL);
+}
+
+/***
+ * therm_throt_process - Process thermal throttling event from interrupt
+ * @curr: Whether the condition is current or not (boolean), since the
+ * thermal interrupt normally gets called both when the thermal
+ * event begins and once the event has ended.
+ *
+ * This function is called by the thermal interrupt after the
+ * IRQ has been acknowledged.
+ *
+ * It will take care of rate limiting and printing messages to the syslog.
+ */
+static void therm_throt_process(bool new_event, int event, int level)
+{
+ struct _thermal_state *state;
+ unsigned int this_cpu = smp_processor_id();
+ bool old_event;
+ u64 now;
+ struct thermal_state *pstate = &per_cpu(thermal_state, this_cpu);
+
+ now = get_jiffies_64();
+ if (level == CORE_LEVEL) {
+ if (event == THERMAL_THROTTLING_EVENT)
+ state = &pstate->core_throttle;
+ else if (event == POWER_LIMIT_EVENT)
+ state = &pstate->core_power_limit;
+ else
+ return;
+ } else if (level == PACKAGE_LEVEL) {
+ if (event == THERMAL_THROTTLING_EVENT)
+ state = &pstate->package_throttle;
+ else if (event == POWER_LIMIT_EVENT)
+ state = &pstate->package_power_limit;
+ else
+ return;
+ } else
+ return;
+
+ old_event = state->new_event;
+ state->new_event = new_event;
+
+ if (new_event)
+ state->count++;
+
+ if (event != THERMAL_THROTTLING_EVENT)
+ return;
+
+ if (new_event && !state->last_interrupt_time) {
+ bool hot;
+ u8 temp;
+
+ get_therm_status(state->level, &hot, &temp);
+ /*
+ * Ignore short temperature spike as the system is not close
+ * to PROCHOT. 10C offset is large enough to ignore. It is
+ * already dropped from the high threshold temperature.
+ */
+ if (temp > 10)
+ return;
+
+ state->baseline_temp = temp;
+ state->last_interrupt_time = now;
+ schedule_delayed_work_on(this_cpu, &state->therm_work, THERM_THROT_POLL_INTERVAL);
+ } else if (old_event && state->last_interrupt_time) {
+ unsigned long throttle_time;
+
+ throttle_time = jiffies_delta_to_msecs(now - state->last_interrupt_time);
+ if (throttle_time > state->max_time_ms)
+ state->max_time_ms = throttle_time;
+ state->total_time_ms += throttle_time;
+ state->last_interrupt_time = 0;
+ }
+}
+
+static int thresh_event_valid(int level, int event)
+{
+ struct _thermal_state *state;
+ unsigned int this_cpu = smp_processor_id();
+ struct thermal_state *pstate = &per_cpu(thermal_state, this_cpu);
+ u64 now = get_jiffies_64();
+
+ if (level == PACKAGE_LEVEL)
+ state = (event == 0) ? &pstate->pkg_thresh0 :
+ &pstate->pkg_thresh1;
+ else
+ state = (event == 0) ? &pstate->core_thresh0 :
+ &pstate->core_thresh1;
+
+ if (time_before64(now, state->next_check))
+ return 0;
+
+ state->next_check = now + CHECK_INTERVAL;
+
+ return 1;
+}
+
+static bool int_pln_enable;
+static int __init int_pln_enable_setup(char *s)
+{
+ int_pln_enable = true;
+
+ return 1;
+}
+__setup("int_pln_enable", int_pln_enable_setup);
+
+#ifdef CONFIG_SYSFS
+/* Add/Remove thermal_throttle interface for CPU device: */
+static int thermal_throttle_add_dev(struct device *dev, unsigned int cpu)
+{
+ int err;
+ struct cpuinfo_x86 *c = &cpu_data(cpu);
+
+ err = sysfs_create_group(&dev->kobj, &thermal_attr_group);
+ if (err)
+ return err;
+
+ if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable) {
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_core_power_limit_count.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+ }
+
+ if (cpu_has(c, X86_FEATURE_PTS)) {
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_package_throttle_count.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_package_throttle_max_time_ms.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_package_throttle_total_time_ms.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+
+ if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable) {
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_package_power_limit_count.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+ }
+ }
+
+ return 0;
+
+del_group:
+ sysfs_remove_group(&dev->kobj, &thermal_attr_group);
+
+ return err;
+}
+
+static void thermal_throttle_remove_dev(struct device *dev)
+{
+ sysfs_remove_group(&dev->kobj, &thermal_attr_group);
+}
+
+static int check_directed_thermal_pkg_intr_ack(void)
+{
+ unsigned int count = 15000;
+ u64 msr_val;
+
+ /*
+ * Hardware acknowledges the directed interrupt setup in 10ms or less.
+ * Wait 15ms to be safe.
+ */
+ do {
+ rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
+ udelay(1);
+ } while (!(msr_val & PACKAGE_THERM_STATUS_DPTI_ACK) && --count);
+
+ if (!count)
+ return -ETIMEDOUT;
+
+ thermal_clear_package_intr_status(PACKAGE_LEVEL,
+ PACKAGE_THERM_STATUS_DPTI_ACK);
+
+ return 0;
+}
+
+static void config_directed_thermal_pkg_intr(void *info)
+{
+ bool enable = *((bool *)info);
+ u64 msr_val;
+
+ rdmsrq(MSR_IA32_THERM_INTERRUPT, msr_val);
+
+ if (enable)
+ msr_val |= THERM_INT_DPTI_ENABLE;
+ else
+ msr_val &= ~THERM_INT_DPTI_ENABLE;
+
+ wrmsrq(MSR_IA32_THERM_INTERRUPT, msr_val);
+}
+
+/*
+ * Accessed from CPU hotplug callbacks and from code that runs while CPU
+ * hotplug is inactive: the init and cleanup paths as well as syscore callbacks.
+ * No extra locking needed.
+ */
+static unsigned int *directed_intr_handler_cpus;
+
+static bool directed_thermal_pkg_intr_supported(void)
+{
+ if (!boot_cpu_has(X86_FEATURE_DPTI))
+ return false;
+
+ if (!directed_intr_handler_cpus)
+ return false;
+
+ return true;
+}
+
+/*
+ * Must be called with cpu_hotplug_lock held to prevent CPUs from going offline
+ * while iterating through packages and interrupts must be enabled to avoid
+ * deadlocks in SMP function calls. The syscore shutdown callback also calls
+ * this function, but runs with CPU hotplug disabled (and interrupts enabled).
+ */
+static void disable_directed_thermal_pkg_intr_all(void)
+{
+ bool enable = false;
+ int i;
+
+ if (!directed_thermal_pkg_intr_supported())
+ return;
+
+ for (i = 0; i < topology_max_packages(); i++) {
+ if (directed_intr_handler_cpus[i] == nr_cpu_ids)
+ continue;
+
+ smp_call_function_single(directed_intr_handler_cpus[i],
+ config_directed_thermal_pkg_intr,
+ &enable, true);
+ }
+}
+
+static int enable_directed_thermal_pkg_intr(unsigned int cpu)
+{
+ bool enable = true;
+ u16 pkg_id;
+
+ if (!directed_thermal_pkg_intr_supported())
+ return 0;
+
+ pkg_id = topology_logical_package_id(cpu);
+ if (pkg_id >= topology_max_packages())
+ return -EINVAL;
+
+ /* Another CPU in this package already handles the directed interrupt. */
+ if (directed_intr_handler_cpus[pkg_id] != nr_cpu_ids)
+ return 0;
+
+ thermal_clear_package_intr_status(PACKAGE_LEVEL,
+ PACKAGE_THERM_STATUS_DPTI_ACK);
+
+ config_directed_thermal_pkg_intr(&enable);
+ if (!check_directed_thermal_pkg_intr_ack()) {
+ directed_intr_handler_cpus[pkg_id] = cpu;
+ return 0;
+ }
+
+ /*
+ * A failure indicates faulty hardware. Roll back completely so that
+ * no other CPU tries. This is especially important during boot as all
+ * CPUs may come online and would otherwise keep trying.
+ */
+ enable = false;
+ config_directed_thermal_pkg_intr(&enable);
+
+ return -ETIMEDOUT;
+}
+
+static void disable_directed_thermal_pkg_intr(unsigned int cpu)
+{
+ unsigned int new_cpu;
+ bool enable;
+ u16 pkg_id;
+
+ if (!directed_thermal_pkg_intr_supported())
+ return;
+
+ pkg_id = topology_logical_package_id(cpu);
+ if (pkg_id >= topology_max_packages())
+ return;
+
+ /* Not the CPU handling the directed interrupt. */
+ if (directed_intr_handler_cpus[pkg_id] != cpu)
+ return;
+
+ /*
+ * The package-level interrupt must remain directed after this CPU goes
+ * offline.
+ */
+ new_cpu = cpumask_any_but(topology_core_cpumask(cpu), cpu);
+ if (new_cpu < nr_cpu_ids) {
+ enable = true;
+ thermal_clear_package_intr_status(PACKAGE_LEVEL,
+ PACKAGE_THERM_STATUS_DPTI_ACK);
+
+ /*
+ * We are here via CPU hotplug. Since we are holding the
+ * cpu_hotplug_lock, @new_cpu cannot go offline and interrupts
+ * are enabled, so the SMP function call is safe.
+ *
+ * The syscore suspend callback runs with interrupts disabled,
+ * but it does not reach this path because all the secondary
+ * CPUs are offline.
+ */
+ smp_call_function_single(new_cpu, config_directed_thermal_pkg_intr,
+ &enable, true);
+ }
+
+ /*
+ * If hardware does not acknowledge the directed interrupt setup on
+ * @new_cpu, disable the redirection. Since no other CPU is configured
+ * to receive the package-level interrupt, all CPUs in the package will
+ * receive it.
+ */
+ enable = false;
+ if (new_cpu < nr_cpu_ids && check_directed_thermal_pkg_intr_ack()) {
+ smp_call_function_single(new_cpu, config_directed_thermal_pkg_intr,
+ &enable, true);
+
+ pr_warn_once("Failed to redirect package thermal interrupt from CPU%u to CPU%u; reverting to broadcast.\n",
+ cpu, new_cpu);
+
+ new_cpu = nr_cpu_ids;
+ }
+
+ /*
+ * Clear the directed interrupt on @cpu. Hardware acknowledgment can be
+ * ignored since @cpu is going offline.
+ */
+ config_directed_thermal_pkg_intr(&enable);
+
+ directed_intr_handler_cpus[pkg_id] = (new_cpu < nr_cpu_ids) ? new_cpu : nr_cpu_ids;
+}
+
+/*
+ * CPU0 may be handling the directed interrupt, but the CPU hotplug callbacks
+ * are not called for CPU0 during suspend and resume.
+ */
+static void directed_pkg_intr_syscore_resume(void *data)
+{
+ /*
+ * We can't do anything to handle errors. If direction fails for CPU0,
+ * another CPU will take over or disable direction entirely during CPU
+ * hotplug.
+ */
+ enable_directed_thermal_pkg_intr(0);
+}
+
+static int directed_pkg_intr_syscore_suspend(void *data)
+{
+ disable_directed_thermal_pkg_intr(0);
+
+ return 0;
+}
+
+static void directed_pkg_intr_syscore_shutdown(void *data)
+{
+ disable_directed_thermal_pkg_intr_all();
+}
+
+static const struct syscore_ops directed_pkg_intr_pm_ops = {
+ .resume = directed_pkg_intr_syscore_resume,
+ .suspend = directed_pkg_intr_syscore_suspend,
+ .shutdown = directed_pkg_intr_syscore_shutdown,
+};
+
+static struct syscore directed_pkg_intr_pm = {
+ .ops = &directed_pkg_intr_pm_ops,
+};
+
+static __init void init_directed_pkg_intr(void)
+{
+ int i;
+
+ if (!boot_cpu_has(X86_FEATURE_DPTI))
+ return;
+
+ directed_intr_handler_cpus = kmalloc_array(topology_max_packages(),
+ sizeof(*directed_intr_handler_cpus),
+ GFP_KERNEL);
+ if (!directed_intr_handler_cpus)
+ return;
+
+ for (i = 0; i < topology_max_packages(); i++)
+ directed_intr_handler_cpus[i] = nr_cpu_ids;
+
+ register_syscore(&directed_pkg_intr_pm);
+}
+
+static void cleanup_directed_pkg_thermal_intr(void)
+{
+ if (!directed_thermal_pkg_intr_supported())
+ return;
+
+ unregister_syscore(&directed_pkg_intr_pm);
+ disable_directed_thermal_pkg_intr_all();
+ kfree(directed_intr_handler_cpus);
+ directed_intr_handler_cpus = NULL;
+}
+
+/* Get notified when a cpu comes on/off. Be hotplug friendly. */
+static int thermal_throttle_online(unsigned int cpu)
+{
+ struct thermal_state *state = &per_cpu(thermal_state, cpu);
+ struct device *dev = get_cpu_device(cpu);
+ int err;
+ u32 l;
+
+ err = thermal_throttle_add_dev(dev, cpu);
+ if (err)
+ return err;
+
+ state->package_throttle.level = PACKAGE_LEVEL;
+ state->core_throttle.level = CORE_LEVEL;
+
+ INIT_DELAYED_WORK(&state->package_throttle.therm_work, throttle_active_work);
+ INIT_DELAYED_WORK(&state->core_throttle.therm_work, throttle_active_work);
+
+ /*
+ * The first CPU coming online will enable the HFI. Usually this causes
+ * hardware to issue an HFI thermal interrupt. Such interrupt will reach
+ * the CPU once we enable the thermal vector in the local APIC.
+ */
+ intel_hfi_online(cpu);
+
+ if (enable_directed_thermal_pkg_intr(cpu)) {
+ pr_info_once("Failed to direct package thermal interrupts. All CPUs will receive it.\n");
+ cleanup_directed_pkg_thermal_intr();
+ }
+
+ /* Unmask the thermal vector after the above workqueues are initialized. */
+ l = apic_read(APIC_LVTTHMR);
+ apic_write(APIC_LVTTHMR, l & ~APIC_LVT_MASKED);
+
+ return err;
+}
+
+static int thermal_throttle_offline(unsigned int cpu)
+{
+ struct thermal_state *state = &per_cpu(thermal_state, cpu);
+ struct device *dev = get_cpu_device(cpu);
+ u32 l;
+
+ /* Mask the thermal vector before draining evtl. pending work */
+ l = apic_read(APIC_LVTTHMR);
+ apic_write(APIC_LVTTHMR, l | APIC_LVT_MASKED);
+
+ disable_directed_thermal_pkg_intr(cpu);
+
+ intel_hfi_offline(cpu);
+
+ cancel_delayed_work_sync(&state->package_throttle.therm_work);
+ cancel_delayed_work_sync(&state->core_throttle.therm_work);
+
+ state->package_throttle.rate_control_active = false;
+ state->core_throttle.rate_control_active = false;
+
+ thermal_throttle_remove_dev(dev);
+ return 0;
+}
+
+static __init int thermal_throttle_init_device(void)
+{
+ int ret;
+
+ if (!atomic_read(&therm_throt_en))
+ return 0;
+
+ init_directed_pkg_intr();
+
+ intel_hfi_init();
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/therm:online",
+ thermal_throttle_online,
+ thermal_throttle_offline);
+ if (ret >= 0)
+ return 0;
+
+ cleanup_directed_pkg_thermal_intr();
+
+ return ret;
+}
+device_initcall(thermal_throttle_init_device);
+
+#endif /* CONFIG_SYSFS */
+
+static void notify_package_thresholds(__u64 msr_val)
+{
+ bool notify_thres_0 = false;
+ bool notify_thres_1 = false;
+
+ if (!platform_thermal_package_notify)
+ return;
+
+ /* lower threshold check */
+ if (msr_val & THERM_LOG_THRESHOLD0)
+ notify_thres_0 = true;
+ /* higher threshold check */
+ if (msr_val & THERM_LOG_THRESHOLD1)
+ notify_thres_1 = true;
+
+ if (!notify_thres_0 && !notify_thres_1)
+ return;
+
+ if (platform_thermal_package_rate_control &&
+ platform_thermal_package_rate_control()) {
+ /* Rate control is implemented in callback */
+ platform_thermal_package_notify(msr_val);
+ return;
+ }
+
+ /* lower threshold reached */
+ if (notify_thres_0 && thresh_event_valid(PACKAGE_LEVEL, 0))
+ platform_thermal_package_notify(msr_val);
+ /* higher threshold reached */
+ if (notify_thres_1 && thresh_event_valid(PACKAGE_LEVEL, 1))
+ platform_thermal_package_notify(msr_val);
+}
+
+static void notify_thresholds(__u64 msr_val)
+{
+ /* check whether the interrupt handler is defined;
+ * otherwise simply return
+ */
+ if (!platform_thermal_notify)
+ return;
+
+ /* lower threshold reached */
+ if ((msr_val & THERM_LOG_THRESHOLD0) &&
+ thresh_event_valid(CORE_LEVEL, 0))
+ platform_thermal_notify(msr_val);
+ /* higher threshold reached */
+ if ((msr_val & THERM_LOG_THRESHOLD1) &&
+ thresh_event_valid(CORE_LEVEL, 1))
+ platform_thermal_notify(msr_val);
+}
+
+void __weak notify_hwp_interrupt(void)
+{
+ wrmsrq_safe(MSR_HWP_STATUS, 0);
+}
+
+/* Thermal transition interrupt handler */
+void intel_thermal_interrupt(void)
+{
+ __u64 msr_val;
+
+ if (cpu_feature_enabled(X86_FEATURE_HWP))
+ notify_hwp_interrupt();
+
+ rdmsrq(MSR_IA32_THERM_STATUS, msr_val);
+
+ /* Check for violation of core thermal thresholds*/
+ notify_thresholds(msr_val);
+
+ therm_throt_process(msr_val & THERM_STATUS_PROCHOT,
+ THERMAL_THROTTLING_EVENT,
+ CORE_LEVEL);
+
+ if (this_cpu_has(X86_FEATURE_PLN) && int_pln_enable)
+ therm_throt_process(msr_val & THERM_STATUS_POWER_LIMIT,
+ POWER_LIMIT_EVENT,
+ CORE_LEVEL);
+
+ if (this_cpu_has(X86_FEATURE_PTS)) {
+ 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,
+ THERMAL_THROTTLING_EVENT,
+ PACKAGE_LEVEL);
+ if (this_cpu_has(X86_FEATURE_PLN) && int_pln_enable)
+ therm_throt_process(msr_val &
+ PACKAGE_THERM_STATUS_POWER_LIMIT,
+ POWER_LIMIT_EVENT,
+ PACKAGE_LEVEL);
+
+ if (this_cpu_has(X86_FEATURE_HFI))
+ intel_hfi_process_event(msr_val &
+ PACKAGE_THERM_STATUS_HFI_UPDATED);
+ }
+}
+
+/* Thermal monitoring depends on APIC, ACPI and clock modulation */
+static int intel_thermal_supported(struct cpuinfo_x86 *c)
+{
+ if (!boot_cpu_has(X86_FEATURE_APIC))
+ return 0;
+ if (!cpu_has(c, X86_FEATURE_ACPI) || !cpu_has(c, X86_FEATURE_ACC))
+ return 0;
+ return 1;
+}
+
+bool x86_thermal_enabled(void)
+{
+ return atomic_read(&therm_throt_en);
+}
+
+void __init therm_lvt_init(void)
+{
+ /*
+ * This function is only called on boot CPU. Save the init thermal
+ * LVT value on BSP and use that value to restore APs' thermal LVT
+ * entry BIOS programmed later
+ */
+ if (intel_thermal_supported(&boot_cpu_data))
+ lvtthmr_init = apic_read(APIC_LVTTHMR);
+}
+
+void intel_init_thermal(struct cpuinfo_x86 *c)
+{
+ unsigned int cpu = smp_processor_id();
+ struct msr val;
+ int tm2 = 0;
+
+ if (!intel_thermal_supported(c))
+ return;
+
+ /*
+ * First check if its enabled already, in which case there might
+ * be some SMM goo which handles it, so we can't even put a handler
+ * since it might be delivered via SMI already:
+ */
+ rdmsrq(MSR_IA32_MISC_ENABLE, val.q);
+
+ val.h = lvtthmr_init;
+ /*
+ * The initial value of thermal LVT entries on all APs always reads
+ * 0x10000 because APs are woken up by BSP issuing INIT-SIPI-SIPI
+ * sequence to them and LVT registers are reset to 0s except for
+ * the mask bits which are set to 1s when APs receive INIT IPI.
+ * If BIOS takes over the thermal interrupt and sets its interrupt
+ * delivery mode to SMI (not fixed), it restores the value that the
+ * BIOS has programmed on AP based on BSP's info we saved since BIOS
+ * is always setting the same value for all threads/cores.
+ */
+ if ((val.h & APIC_DM_FIXED_MASK) != APIC_DM_FIXED)
+ apic_write(APIC_LVTTHMR, lvtthmr_init);
+
+
+ if ((val.l & MSR_IA32_MISC_ENABLE_TM1) && (val.h & APIC_DM_SMI)) {
+ if (system_state == SYSTEM_BOOTING)
+ pr_debug("CPU%d: Thermal monitoring handled by SMI\n", cpu);
+ return;
+ }
+
+ /* early Pentium M models use different method for enabling TM2 */
+ if (cpu_has(c, X86_FEATURE_TM2)) {
+ if (c->x86 == 6 && (c->x86_model == 9 || c->x86_model == 13)) {
+ rdmsrq(MSR_THERM2_CTL, val.q);
+ if (val.l & MSR_THERM2_CTL_TM_SELECT)
+ tm2 = 1;
+ } else if (val.l & MSR_IA32_MISC_ENABLE_TM2)
+ tm2 = 1;
+ }
+
+ /* We'll mask the thermal vector in the lapic till we're ready: */
+ val.h = THERMAL_APIC_VECTOR | APIC_DM_FIXED | APIC_LVT_MASKED;
+ apic_write(APIC_LVTTHMR, val.h);
+
+ thermal_intr_init_core_clear_mask();
+ thermal_intr_init_pkg_clear_mask();
+
+ rdmsrq(MSR_IA32_THERM_INTERRUPT, val.q);
+ if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable) {
+ val.l |= THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE;
+ val.l &= ~THERM_INT_PLN_ENABLE;
+ } else if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable)
+ val.l |= THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE |
+ THERM_INT_PLN_ENABLE;
+ else
+ val.l |= THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE;
+ wrmsrq(MSR_IA32_THERM_INTERRUPT, val.q);
+
+ if (cpu_has(c, X86_FEATURE_PTS)) {
+ rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+ if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable) {
+ val.l |= PACKAGE_THERM_INT_LOW_ENABLE |
+ PACKAGE_THERM_INT_HIGH_ENABLE;
+ val.l &= ~PACKAGE_THERM_INT_PLN_ENABLE;
+ } else if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable)
+ val.l |= PACKAGE_THERM_INT_LOW_ENABLE |
+ PACKAGE_THERM_INT_HIGH_ENABLE |
+ PACKAGE_THERM_INT_PLN_ENABLE;
+ else
+ val.l |= PACKAGE_THERM_INT_LOW_ENABLE |
+ PACKAGE_THERM_INT_HIGH_ENABLE;
+ wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+
+ if (cpu_has(c, X86_FEATURE_HFI)) {
+ rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+ wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT,
+ val.q | PACKAGE_THERM_INT_HFI_ENABLE);
+ }
+ }
+
+ rdmsrq(MSR_IA32_MISC_ENABLE, val.q);
+ wrmsrq(MSR_IA32_MISC_ENABLE, val.q | MSR_IA32_MISC_ENABLE_TM1);
+
+ pr_info_once("CPU0: Thermal monitoring enabled (%s)\n",
+ tm2 ? "TM2" : "TM1");
+
+ /* enable thermal throttle processing */
+ atomic_set(&therm_throt_en, 1);
+}
diff --git a/drivers/thermal/intel/thermal_interrupt.h b/drivers/thermal/intel/thermal_interrupt.h
new file mode 100644
index 000000000..01dfd4cdb
--- /dev/null
+++ b/drivers/thermal/intel/thermal_interrupt.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INTEL_THERMAL_INTERRUPT_H
+#define _INTEL_THERMAL_INTERRUPT_H
+
+#define CORE_LEVEL 0
+#define PACKAGE_LEVEL 1
+
+/* Interrupt Handler for package thermal thresholds */
+extern int (*platform_thermal_package_notify)(__u64 msr_val);
+
+/* Interrupt Handler for core thermal thresholds */
+extern int (*platform_thermal_notify)(__u64 msr_val);
+
+/* Callback support of rate control, return true, if
+ * callback has rate control */
+extern bool (*platform_thermal_package_rate_control)(void);
+
+/* Handle HWP interrupt */
+extern void notify_hwp_interrupt(void);
+
+/* Common function to clear Package thermal status register */
+extern void thermal_clear_package_intr_status(int level, u64 bit_mask);
+
+#endif /* _INTEL_THERMAL_INTERRUPT_H */
diff --git a/drivers/thermal/intel/x86_pkg_temp_thermal.c b/drivers/thermal/intel/x86_pkg_temp_thermal.c
new file mode 100644
index 000000000..43fd5bdf1
--- /dev/null
+++ b/drivers/thermal/intel/x86_pkg_temp_thermal.c
@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * x86_pkg_temp_thermal driver
+ * Copyright (c) 2013, Intel Corporation.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/intel_tcc.h>
+#include <linux/err.h>
+#include <linux/param.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/cpu.h>
+#include <linux/smp.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/thermal.h>
+#include <linux/debugfs.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/cpuid/api.h>
+#include <asm/msr.h>
+
+#include "thermal_interrupt.h"
+
+/*
+* Rate control delay: Idea is to introduce denounce effect
+* This should be long enough to avoid reduce events, when
+* threshold is set to a temperature, which is constantly
+* violated, but at the short enough to take any action.
+* The action can be remove threshold or change it to next
+* interesting setting. Based on experiments, in around
+* every 5 seconds under load will give us a significant
+* temperature change.
+*/
+#define PKG_TEMP_THERMAL_NOTIFY_DELAY 5000
+static int notify_delay_ms = PKG_TEMP_THERMAL_NOTIFY_DELAY;
+module_param(notify_delay_ms, int, 0644);
+MODULE_PARM_DESC(notify_delay_ms,
+ "User space notification delay in milli seconds.");
+
+/* Number of trip points in thermal zone. Currently it can't
+* be more than 2. MSR can allow setting and getting notifications
+* for only 2 thresholds. This define enforces this, if there
+* is some wrong values returned by cpuid for number of thresholds.
+*/
+#define MAX_NUMBER_OF_TRIPS 2
+
+struct zone_device {
+ int cpu;
+ bool work_scheduled;
+ u64 msr_pkg_therm;
+ struct delayed_work work;
+ struct thermal_zone_device *tzone;
+ struct cpumask cpumask;
+};
+
+static struct thermal_zone_params pkg_temp_tz_params = {
+ .no_hwmon = true,
+};
+
+/* Keep track of how many zone pointers we allocated in init() */
+static int max_id __read_mostly;
+/* Array of zone pointers */
+static struct zone_device **zones;
+/* Serializes interrupt notification, work and hotplug */
+static DEFINE_RAW_SPINLOCK(pkg_temp_lock);
+/* Protects zone operation in the work function against hotplug removal */
+static DEFINE_MUTEX(thermal_zone_mutex);
+
+/* The dynamically assigned cpu hotplug state for module_exit() */
+static enum cpuhp_state pkg_thermal_hp_state __read_mostly;
+
+/* Debug counters to show using debugfs */
+static struct dentry *debugfs;
+static unsigned int pkg_interrupt_cnt;
+static unsigned int pkg_work_cnt;
+
+static void pkg_temp_debugfs_init(void)
+{
+ debugfs = debugfs_create_dir("pkg_temp_thermal", NULL);
+
+ debugfs_create_u32("pkg_thres_interrupt", S_IRUGO, debugfs,
+ &pkg_interrupt_cnt);
+ debugfs_create_u32("pkg_thres_work", S_IRUGO, debugfs,
+ &pkg_work_cnt);
+}
+
+/*
+ * Protection:
+ *
+ * - cpu hotplug: Read serialized by cpu hotplug lock
+ * Write must hold pkg_temp_lock
+ *
+ * - Other callsites: Must hold pkg_temp_lock
+ */
+static struct zone_device *pkg_temp_thermal_get_dev(unsigned int cpu)
+{
+ int id = topology_logical_die_id(cpu);
+
+ if (id >= 0 && id < max_id)
+ return zones[id];
+ return NULL;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ struct zone_device *zonedev = thermal_zone_device_priv(tzd);
+ int val, ret;
+
+ 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);
+ return 0;
+}
+
+static int
+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 mask, shift, intr;
+ int tj_max, val, ret;
+ struct msr v;
+
+ if (temp == THERMAL_TEMP_INVALID)
+ temp = 0;
+
+ tj_max = intel_tcc_get_tjmax(zonedev->cpu);
+ if (tj_max < 0)
+ return tj_max;
+ tj_max *= 1000;
+
+ val = (tj_max - temp)/1000;
+
+ if (trip_index >= MAX_NUMBER_OF_TRIPS || val < 0 || val > 0x7f)
+ return -EINVAL;
+
+ ret = rdmsrq_on_cpu(zonedev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, &v.q);
+ if (ret < 0)
+ return ret;
+
+ if (trip_index) {
+ mask = THERM_MASK_THRESHOLD1;
+ shift = THERM_SHIFT_THRESHOLD1;
+ intr = THERM_INT_THRESHOLD1_ENABLE;
+ } else {
+ mask = THERM_MASK_THRESHOLD0;
+ shift = THERM_SHIFT_THRESHOLD0;
+ intr = THERM_INT_THRESHOLD0_ENABLE;
+ }
+ v.l &= ~mask;
+ /*
+ * When users space sets a trip temperature == 0, which is indication
+ * that, it is no longer interested in receiving notifications.
+ */
+ if (!temp) {
+ v.l &= ~intr;
+ } else {
+ v.l |= val << shift;
+ v.l |= intr;
+ }
+
+ return wrmsrq_on_cpu(zonedev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, v.q);
+}
+
+/* Thermal zone callback registry */
+static const struct thermal_zone_device_ops tzone_ops = {
+ .get_temp = sys_get_curr_temp,
+ .set_trip_temp = sys_set_trip_temp,
+};
+
+static bool pkg_thermal_rate_control(void)
+{
+ return true;
+}
+
+/* Enable threshold interrupt on local package/cpu */
+static inline void enable_pkg_thres_interrupt(void)
+{
+ u8 thres_0, thres_1;
+ struct msr val;
+
+ rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+ /* only enable/disable if it had valid threshold value */
+ thres_0 = (val.l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0;
+ thres_1 = (val.l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1;
+ if (thres_0)
+ val.l |= THERM_INT_THRESHOLD0_ENABLE;
+ if (thres_1)
+ val.l |= THERM_INT_THRESHOLD1_ENABLE;
+ wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+}
+
+/* Disable threshold interrupt on local package/cpu */
+static inline void disable_pkg_thres_interrupt(void)
+{
+ struct msr val;
+
+ rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+
+ val.l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE);
+ wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+}
+
+static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work)
+{
+ struct thermal_zone_device *tzone = NULL;
+ int cpu = smp_processor_id();
+ struct zone_device *zonedev;
+
+ mutex_lock(&thermal_zone_mutex);
+ raw_spin_lock_irq(&pkg_temp_lock);
+ ++pkg_work_cnt;
+
+ zonedev = pkg_temp_thermal_get_dev(cpu);
+ if (!zonedev) {
+ raw_spin_unlock_irq(&pkg_temp_lock);
+ mutex_unlock(&thermal_zone_mutex);
+ return;
+ }
+ zonedev->work_scheduled = false;
+
+ thermal_clear_package_intr_status(PACKAGE_LEVEL, THERM_LOG_THRESHOLD0 | THERM_LOG_THRESHOLD1);
+ tzone = zonedev->tzone;
+
+ enable_pkg_thres_interrupt();
+ raw_spin_unlock_irq(&pkg_temp_lock);
+
+ /*
+ * If tzone is not NULL, then thermal_zone_mutex will prevent the
+ * concurrent removal in the cpu offline callback.
+ */
+ if (tzone)
+ thermal_zone_device_update(tzone, THERMAL_EVENT_UNSPECIFIED);
+
+ mutex_unlock(&thermal_zone_mutex);
+}
+
+static void pkg_thermal_schedule_work(int cpu, struct delayed_work *work)
+{
+ unsigned long ms = msecs_to_jiffies(notify_delay_ms);
+
+ schedule_delayed_work_on(cpu, work, ms);
+}
+
+static int pkg_thermal_notify(u64 msr_val)
+{
+ int cpu = smp_processor_id();
+ struct zone_device *zonedev;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&pkg_temp_lock, flags);
+ ++pkg_interrupt_cnt;
+
+ disable_pkg_thres_interrupt();
+
+ /* Work is per package, so scheduling it once is enough. */
+ zonedev = pkg_temp_thermal_get_dev(cpu);
+ if (zonedev && !zonedev->work_scheduled) {
+ zonedev->work_scheduled = true;
+ pkg_thermal_schedule_work(zonedev->cpu, &zonedev->work);
+ }
+
+ raw_spin_unlock_irqrestore(&pkg_temp_lock, flags);
+ return 0;
+}
+
+static int pkg_temp_thermal_trips_init(int cpu, int tj_max,
+ struct thermal_trip *trips, int num_trips)
+{
+ unsigned long thres_reg_value;
+ u32 mask, shift;
+ struct msr val;
+ int ret, i;
+
+ for (i = 0; i < num_trips; i++) {
+
+ if (i) {
+ mask = THERM_MASK_THRESHOLD1;
+ shift = THERM_SHIFT_THRESHOLD1;
+ } else {
+ mask = THERM_MASK_THRESHOLD0;
+ shift = THERM_SHIFT_THRESHOLD0;
+ }
+
+ ret = rdmsrq_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, &val.q);
+ if (ret < 0)
+ return ret;
+
+ thres_reg_value = (val.l & mask) >> shift;
+
+ trips[i].temperature = thres_reg_value ?
+ 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 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;
+ int thres_count, err;
+ int tj_max;
+
+ if (id >= max_id)
+ return -ENOMEM;
+
+ cpuid(6, &eax, &ebx, &ecx, &edx);
+ thres_count = ebx & 0x07;
+ if (!thres_count)
+ return -ENODEV;
+
+ thres_count = clamp_val(thres_count, 0, MAX_NUMBER_OF_TRIPS);
+
+ tj_max = intel_tcc_get_tjmax(cpu);
+ if (tj_max < 0)
+ return tj_max;
+ tj_max *= 1000;
+
+ zonedev = kzalloc_obj(*zonedev);
+ if (!zonedev)
+ return -ENOMEM;
+
+ 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",
+ 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_zonedev;
+ }
+ err = thermal_zone_device_enable(zonedev->tzone);
+ if (err)
+ goto out_unregister_tz;
+
+ /* Store MSR value for package thermal interrupt, to restore at exit */
+ rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, zonedev->msr_pkg_therm);
+
+ cpumask_set_cpu(cpu, &zonedev->cpumask);
+ raw_spin_lock_irq(&pkg_temp_lock);
+ zones[id] = zonedev;
+ raw_spin_unlock_irq(&pkg_temp_lock);
+
+ return 0;
+
+out_unregister_tz:
+ thermal_zone_device_unregister(zonedev->tzone);
+out_kfree_zonedev:
+ kfree(zonedev);
+ return err;
+}
+
+static int pkg_thermal_cpu_offline(unsigned int cpu)
+{
+ struct zone_device *zonedev = pkg_temp_thermal_get_dev(cpu);
+ bool lastcpu, was_target;
+ int target;
+
+ if (!zonedev)
+ return 0;
+
+ target = cpumask_any_but(&zonedev->cpumask, cpu);
+ cpumask_clear_cpu(cpu, &zonedev->cpumask);
+ lastcpu = target >= nr_cpu_ids;
+ /*
+ * Remove the sysfs files, if this is the last cpu in the package
+ * before doing further cleanups.
+ */
+ if (lastcpu) {
+ struct thermal_zone_device *tzone = zonedev->tzone;
+
+ /*
+ * We must protect against a work function calling
+ * thermal_zone_update, after/while unregister. We null out
+ * the pointer under the zone mutex, so the worker function
+ * won't try to call.
+ */
+ mutex_lock(&thermal_zone_mutex);
+ zonedev->tzone = NULL;
+ mutex_unlock(&thermal_zone_mutex);
+
+ thermal_zone_device_unregister(tzone);
+ }
+
+ /* Protect against work and interrupts */
+ raw_spin_lock_irq(&pkg_temp_lock);
+
+ /*
+ * Check whether this cpu was the current target and store the new
+ * one. When we drop the lock, then the interrupt notify function
+ * will see the new target.
+ */
+ was_target = zonedev->cpu == cpu;
+ zonedev->cpu = target;
+
+ /*
+ * If this is the last CPU in the package remove the package
+ * reference from the array and restore the interrupt MSR. When we
+ * drop the lock neither the interrupt notify function nor the
+ * worker will see the package anymore.
+ */
+ if (lastcpu) {
+ zones[topology_logical_die_id(cpu)] = NULL;
+ /* After this point nothing touches the MSR anymore. */
+ wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT,
+ zonedev->msr_pkg_therm);
+ }
+
+ /*
+ * Check whether there is work scheduled and whether the work is
+ * targeted at the outgoing CPU.
+ */
+ if (zonedev->work_scheduled && was_target) {
+ /*
+ * To cancel the work we need to drop the lock, otherwise
+ * we might deadlock if the work needs to be flushed.
+ */
+ raw_spin_unlock_irq(&pkg_temp_lock);
+ cancel_delayed_work_sync(&zonedev->work);
+ raw_spin_lock_irq(&pkg_temp_lock);
+ /*
+ * If this is not the last cpu in the package and the work
+ * did not run after we dropped the lock above, then we
+ * need to reschedule the work, otherwise the interrupt
+ * stays disabled forever.
+ */
+ if (!lastcpu && zonedev->work_scheduled)
+ pkg_thermal_schedule_work(target, &zonedev->work);
+ }
+
+ raw_spin_unlock_irq(&pkg_temp_lock);
+
+ /* Final cleanup if this is the last cpu */
+ if (lastcpu)
+ kfree(zonedev);
+
+ return 0;
+}
+
+static int pkg_thermal_cpu_online(unsigned int cpu)
+{
+ struct zone_device *zonedev = pkg_temp_thermal_get_dev(cpu);
+ struct cpuinfo_x86 *c = &cpu_data(cpu);
+
+ /* Paranoia check */
+ if (!cpu_has(c, X86_FEATURE_DTHERM) || !cpu_has(c, X86_FEATURE_PTS))
+ return -ENODEV;
+
+ /* If the package exists, nothing to do */
+ if (zonedev) {
+ cpumask_set_cpu(cpu, &zonedev->cpumask);
+ return 0;
+ }
+ return pkg_temp_thermal_device_add(cpu);
+}
+
+static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = {
+ X86_MATCH_VENDOR_FEATURE(INTEL, X86_FEATURE_PTS, NULL),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids);
+
+static int __init pkg_temp_thermal_init(void)
+{
+ int ret;
+
+ if (!x86_match_cpu(pkg_temp_thermal_ids))
+ return -ENODEV;
+
+ max_id = topology_max_packages() * topology_max_dies_per_package();
+ zones = kzalloc_objs(struct zone_device *, max_id);
+ if (!zones)
+ return -ENOMEM;
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "thermal/x86_pkg:online",
+ pkg_thermal_cpu_online, pkg_thermal_cpu_offline);
+ if (ret < 0)
+ goto err;
+
+ /* Store the state for module exit */
+ pkg_thermal_hp_state = ret;
+
+ platform_thermal_package_notify = pkg_thermal_notify;
+ platform_thermal_package_rate_control = pkg_thermal_rate_control;
+
+ /* Don't care if it fails */
+ pkg_temp_debugfs_init();
+ return 0;
+
+err:
+ kfree(zones);
+ return ret;
+}
+module_init(pkg_temp_thermal_init)
+
+static void __exit pkg_temp_thermal_exit(void)
+{
+ platform_thermal_package_notify = NULL;
+ platform_thermal_package_rate_control = NULL;
+
+ cpuhp_remove_state(pkg_thermal_hp_state);
+ debugfs_remove_recursive(debugfs);
+ kfree(zones);
+}
+module_exit(pkg_temp_thermal_exit)
+
+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
new file mode 100644
index 000000000..678d6ed71
--- /dev/null
+++ b/drivers/thermal/k3_bandgap.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI Bandgap temperature sensor driver for K3 SoC Family
+ *
+ * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+
+#include "thermal_hwmon.h"
+
+#define K3_VTM_DEVINFO_PWR0_OFFSET 0x4
+#define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0
+#define K3_VTM_TMPSENS0_CTRL_OFFSET 0x80
+#define K3_VTM_REGS_PER_TS 0x10
+#define K3_VTM_TS_STAT_DTEMP_MASK 0x3ff
+#define K3_VTM_TMPSENS_CTRL_CBIASSEL BIT(0)
+#define K3_VTM_TMPSENS_CTRL_SOC BIT(5)
+#define K3_VTM_TMPSENS_CTRL_CLRZ BIT(6)
+#define K3_VTM_TMPSENS_CTRL_CLKON_REQ BIT(7)
+
+#define K3_VTM_ADC_BEGIN_VAL 540
+#define K3_VTM_ADC_END_VAL 944
+
+static const int k3_adc_to_temp[] = {
+ -40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200,
+ -37800, -37400, -37000, -36600, -36200, -35800, -35300, -34700, -34200,
+ -33800, -33400, -33000, -32600, -32200, -31800, -31400, -31000, -30600,
+ -30200, -29800, -29400, -29000, -28600, -28200, -27700, -27100, -26600,
+ -26200, -25800, -25400, -25000, -24600, -24200, -23800, -23400, -23000,
+ -22600, -22200, -21800, -21400, -21000, -20500, -19900, -19400, -19000,
+ -18600, -18200, -17800, -17400, -17000, -16600, -16200, -15800, -15400,
+ -15000, -14600, -14200, -13800, -13400, -13000, -12500, -11900, -11400,
+ -11000, -10600, -10200, -9800, -9400, -9000, -8600, -8200, -7800, -7400,
+ -7000, -6600, -6200, -5800, -5400, -5000, -4500, -3900, -3400, -3000,
+ -2600, -2200, -1800, -1400, -1000, -600, -200, 200, 600, 1000, 1400,
+ 1800, 2200, 2600, 3000, 3400, 3900, 4500, 5000, 5400, 5800, 6200, 6600,
+ 7000, 7400, 7800, 8200, 8600, 9000, 9400, 9800, 10200, 10600, 11000,
+ 11400, 11800, 12200, 12700, 13300, 13800, 14200, 14600, 15000, 15400,
+ 15800, 16200, 16600, 17000, 17400, 17800, 18200, 18600, 19000, 19400,
+ 19800, 20200, 20600, 21000, 21400, 21900, 22500, 23000, 23400, 23800,
+ 24200, 24600, 25000, 25400, 25800, 26200, 26600, 27000, 27400, 27800,
+ 28200, 28600, 29000, 29400, 29800, 30200, 30600, 31000, 31400, 31900,
+ 32500, 33000, 33400, 33800, 34200, 34600, 35000, 35400, 35800, 36200,
+ 36600, 37000, 37400, 37800, 38200, 38600, 39000, 39400, 39800, 40200,
+ 40600, 41000, 41400, 41800, 42200, 42600, 43100, 43700, 44200, 44600,
+ 45000, 45400, 45800, 46200, 46600, 47000, 47400, 47800, 48200, 48600,
+ 49000, 49400, 49800, 50200, 50600, 51000, 51400, 51800, 52200, 52600,
+ 53000, 53400, 53800, 54200, 54600, 55000, 55400, 55900, 56500, 57000,
+ 57400, 57800, 58200, 58600, 59000, 59400, 59800, 60200, 60600, 61000,
+ 61400, 61800, 62200, 62600, 63000, 63400, 63800, 64200, 64600, 65000,
+ 65400, 65800, 66200, 66600, 67000, 67400, 67800, 68200, 68600, 69000,
+ 69400, 69800, 70200, 70600, 71000, 71500, 72100, 72600, 73000, 73400,
+ 73800, 74200, 74600, 75000, 75400, 75800, 76200, 76600, 77000, 77400,
+ 77800, 78200, 78600, 79000, 79400, 79800, 80200, 80600, 81000, 81400,
+ 81800, 82200, 82600, 83000, 83400, 83800, 84200, 84600, 85000, 85400,
+ 85800, 86200, 86600, 87000, 87400, 87800, 88200, 88600, 89000, 89400,
+ 89800, 90200, 90600, 91000, 91400, 91800, 92200, 92600, 93000, 93400,
+ 93800, 94200, 94600, 95000, 95400, 95800, 96200, 96600, 97000, 97500,
+ 98100, 98600, 99000, 99400, 99800, 100200, 100600, 101000, 101400,
+ 101800, 102200, 102600, 103000, 103400, 103800, 104200, 104600, 105000,
+ 105400, 105800, 106200, 106600, 107000, 107400, 107800, 108200, 108600,
+ 109000, 109400, 109800, 110200, 110600, 111000, 111400, 111800, 112200,
+ 112600, 113000, 113400, 113800, 114200, 114600, 115000, 115400, 115800,
+ 116200, 116600, 117000, 117400, 117800, 118200, 118600, 119000, 119400,
+ 119800, 120200, 120600, 121000, 121400, 121800, 122200, 122600, 123000,
+ 123400, 123800, 124200, 124600, 124900, 125000,
+};
+
+struct k3_bandgap {
+ void __iomem *base;
+};
+
+/* common data structures */
+struct k3_thermal_data {
+ struct thermal_zone_device *tzd;
+ struct k3_bandgap *bgp;
+ int sensor_id;
+ u32 ctrl_offset;
+ u32 stat_offset;
+};
+
+static unsigned int vtm_get_best_value(unsigned int s0, unsigned int s1,
+ unsigned int s2)
+{
+ int d01 = abs(s0 - s1);
+ int d02 = abs(s0 - s2);
+ int d12 = abs(s1 - s2);
+
+ if (d01 <= d02 && d01 <= d12)
+ return (s0 + s1) / 2;
+
+ if (d02 <= d01 && d02 <= d12)
+ return (s0 + s2) / 2;
+
+ return (s1 + s2) / 2;
+}
+
+static int k3_bgp_read_temp(struct k3_thermal_data *devdata,
+ int *temp)
+{
+ struct k3_bandgap *bgp;
+ unsigned int dtemp, s0, s1, s2;
+
+ bgp = devdata->bgp;
+
+ /*
+ * Errata is applicable for am654 pg 1.0 silicon. There
+ * is a variation of the order for 8-10 degree centigrade.
+ * Work around that by getting the average of two closest
+ * readings out of three readings everytime we want to
+ * report temperatures.
+ *
+ * Errata workaround.
+ */
+ s0 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ s1 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ s2 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ dtemp = vtm_get_best_value(s0, s1, s2);
+
+ if (dtemp < K3_VTM_ADC_BEGIN_VAL || dtemp > K3_VTM_ADC_END_VAL)
+ return -EINVAL;
+
+ *temp = k3_adc_to_temp[dtemp - K3_VTM_ADC_BEGIN_VAL];
+
+ return 0;
+}
+
+static int k3_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct k3_thermal_data *data = thermal_zone_device_priv(tz);
+ int ret = 0;
+
+ ret = k3_bgp_read_temp(data, temp);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops k3_of_thermal_ops = {
+ .get_temp = k3_thermal_get_temp,
+};
+
+static const struct of_device_id of_k3_bandgap_match[];
+
+static int k3_bandgap_probe(struct platform_device *pdev)
+{
+ int ret = 0, cnt, val, id;
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ struct k3_bandgap *bgp;
+ struct k3_thermal_data *data;
+
+ if (ARRAY_SIZE(k3_adc_to_temp) != (K3_VTM_ADC_END_VAL + 1 -
+ K3_VTM_ADC_BEGIN_VAL))
+ return -EINVAL;
+
+ bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL);
+ if (!bgp)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ bgp->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(bgp->base))
+ return PTR_ERR(bgp->base);
+
+ pm_runtime_enable(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ pm_runtime_disable(dev);
+ return ret;
+ }
+
+ /* Get the sensor count in the VTM */
+ val = readl(bgp->base + K3_VTM_DEVINFO_PWR0_OFFSET);
+ cnt = val & K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK;
+ cnt >>= __ffs(K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK);
+
+ data = devm_kcalloc(dev, cnt, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Register the thermal sensors */
+ for (id = 0; id < cnt; id++) {
+ data[id].sensor_id = id;
+ data[id].bgp = bgp;
+ data[id].ctrl_offset = K3_VTM_TMPSENS0_CTRL_OFFSET +
+ id * K3_VTM_REGS_PER_TS;
+ data[id].stat_offset = data[id].ctrl_offset + 0x8;
+
+ val = readl(data[id].bgp->base + data[id].ctrl_offset);
+ val |= (K3_VTM_TMPSENS_CTRL_SOC |
+ K3_VTM_TMPSENS_CTRL_CLRZ |
+ K3_VTM_TMPSENS_CTRL_CLKON_REQ);
+ val &= ~K3_VTM_TMPSENS_CTRL_CBIASSEL;
+ writel(val, data[id].bgp->base + data[id].ctrl_offset);
+
+ data[id].tzd =
+ devm_thermal_of_zone_register(dev, id,
+ &data[id],
+ &k3_of_thermal_ops);
+ if (IS_ERR(data[id].tzd)) {
+ dev_err(dev, "thermal zone device is NULL\n");
+ ret = PTR_ERR(data[id].tzd);
+ goto err_alloc;
+ }
+
+ devm_thermal_add_hwmon_sysfs(dev, data[id].tzd);
+ }
+
+
+ return 0;
+
+err_alloc:
+ pm_runtime_put_sync(dev);
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static void k3_bandgap_remove(struct platform_device *pdev)
+{
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+}
+
+static const struct of_device_id of_k3_bandgap_match[] = {
+ {
+ .compatible = "ti,am654-vtm",
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, of_k3_bandgap_match);
+
+static struct platform_driver k3_bandgap_sensor_driver = {
+ .probe = k3_bandgap_probe,
+ .remove = k3_bandgap_remove,
+ .driver = {
+ .name = "k3-soc-thermal",
+ .of_match_table = of_k3_bandgap_match,
+ },
+};
+
+module_platform_driver(k3_bandgap_sensor_driver);
+
+MODULE_DESCRIPTION("K3 bandgap temperature sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>");
diff --git a/drivers/thermal/k3_j72xx_bandgap.c b/drivers/thermal/k3_j72xx_bandgap.c
new file mode 100644
index 000000000..6aba00d8b
--- /dev/null
+++ b/drivers/thermal/k3_j72xx_bandgap.c
@@ -0,0 +1,613 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI Bandgap temperature sensor driver for J72XX SoC Family
+ *
+ * Copyright (C) 2021 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/err.h>
+#include <linux/types.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
+#define K3_VTM_MISC_CTRL_OFFSET 0xc
+#define K3_VTM_TMPSENS_STAT_OFFSET 0x8
+#define K3_VTM_ANYMAXT_OUTRG_ALERT_EN 0x1
+#define K3_VTM_MISC_CTRL2_OFFSET 0x10
+#define K3_VTM_TS_STAT_DTEMP_MASK 0x3ff
+#define K3_VTM_MAX_NUM_TS 8
+#define K3_VTM_TMPSENS_CTRL_SOC BIT(5)
+#define K3_VTM_TMPSENS_CTRL_CLRZ BIT(6)
+#define K3_VTM_TMPSENS_CTRL_CLKON_REQ BIT(7)
+#define K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN BIT(11)
+
+#define K3_VTM_CORRECTION_TEMP_CNT 3
+
+#define MINUS40CREF 5
+#define PLUS30CREF 253
+#define PLUS125CREF 730
+#define PLUS150CREF 940
+
+#define TABLE_SIZE 1024
+#define MAX_TEMP 123000
+#define COOL_DOWN_TEMP 105000
+
+#define FACTORS_REDUCTION 13
+static int *derived_table;
+
+static int compute_value(int index, const s64 *factors, int nr_factors,
+ int reduction)
+{
+ s64 value = 0;
+ int i;
+
+ for (i = 0; i < nr_factors; i++)
+ value += factors[i] * int_pow(index, i);
+
+ return (int)div64_s64(value, int_pow(10, reduction));
+}
+
+static void init_table(int factors_size, int *table, const s64 *factors)
+{
+ int i;
+
+ for (i = 0; i < TABLE_SIZE; i++)
+ table[i] = compute_value(i, factors, factors_size,
+ FACTORS_REDUCTION);
+}
+
+/**
+ * struct err_values - structure containing error/reference values
+ * @refs: reference error values for -40C, 30C, 125C & 150C
+ * @errs: Actual error values for -40C, 30C, 125C & 150C read from the efuse
+ */
+struct err_values {
+ int refs[4];
+ int errs[4];
+};
+
+static void create_table_segments(struct err_values *err_vals, int seg,
+ int *ref_table)
+{
+ int m = 0, c, num, den, i, err, idx1, idx2, err1, err2, ref1, ref2;
+
+ if (seg == 0)
+ idx1 = 0;
+ else
+ idx1 = err_vals->refs[seg];
+
+ idx2 = err_vals->refs[seg + 1];
+ err1 = err_vals->errs[seg];
+ err2 = err_vals->errs[seg + 1];
+ ref1 = err_vals->refs[seg];
+ ref2 = err_vals->refs[seg + 1];
+
+ /*
+ * Calculate the slope with adc values read from the register
+ * as the y-axis param and err in adc value as x-axis param
+ */
+ num = ref2 - ref1;
+ den = err2 - err1;
+ if (den)
+ m = num / den;
+ c = ref2 - m * err2;
+
+ /*
+ * Take care of divide by zero error if error values are same
+ * Or when the slope is 0
+ */
+ if (den != 0 && m != 0) {
+ for (i = idx1; i <= idx2; i++) {
+ err = (i - c) / m;
+ if (((i + err) < 0) || ((i + err) >= TABLE_SIZE))
+ continue;
+ derived_table[i] = ref_table[i + err];
+ }
+ } else { /* Constant error take care of divide by zero */
+ for (i = idx1; i <= idx2; i++) {
+ if (((i + err1) < 0) || ((i + err1) >= TABLE_SIZE))
+ continue;
+ derived_table[i] = ref_table[i + err1];
+ }
+ }
+}
+
+static int prep_lookup_table(struct err_values *err_vals, int *ref_table)
+{
+ int inc, i, seg;
+
+ /*
+ * Fill up the lookup table under 3 segments
+ * region -40C to +30C
+ * region +30C to +125C
+ * region +125C to +150C
+ */
+ for (seg = 0; seg < 3; seg++)
+ create_table_segments(err_vals, seg, ref_table);
+
+ /* Get to the first valid temperature */
+ i = 0;
+ while (!derived_table[i])
+ i++;
+
+ /*
+ * Get to the last zero index and back fill the temperature for
+ * sake of continuity
+ */
+ if (i) {
+ /* 300 milli celsius steps */
+ while (i--)
+ derived_table[i] = derived_table[i + 1] - 300;
+ }
+
+ /*
+ * Fill the last trailing 0s which are unfilled with increments of
+ * 100 milli celsius till 1023 code
+ */
+ i = TABLE_SIZE - 1;
+ while (!derived_table[i])
+ i--;
+
+ i++;
+ inc = 1;
+ while (i < TABLE_SIZE) {
+ derived_table[i] = derived_table[i - 1] + inc * 100;
+ i++;
+ }
+
+ return 0;
+}
+
+struct k3_thermal_data;
+
+struct k3_j72xx_bandgap {
+ struct device *dev;
+ void __iomem *base;
+ void __iomem *cfg2_base;
+ struct k3_thermal_data *ts_data[K3_VTM_MAX_NUM_TS];
+ int cnt;
+};
+
+/* common data structures */
+struct k3_thermal_data {
+ struct k3_j72xx_bandgap *bgp;
+ u32 ctrl_offset;
+ u32 stat_offset;
+};
+
+static int two_cmp(int tmp, int mask)
+{
+ tmp = ~(tmp);
+ tmp &= mask;
+ tmp += 1;
+
+ /* Return negative value */
+ return (0 - tmp);
+}
+
+static unsigned int vtm_get_best_value(unsigned int s0, unsigned int s1,
+ unsigned int s2)
+{
+ int d01 = abs(s0 - s1);
+ int d02 = abs(s0 - s2);
+ int d12 = abs(s1 - s2);
+
+ if (d01 <= d02 && d01 <= d12)
+ return (s0 + s1) / 2;
+
+ if (d02 <= d01 && d02 <= d12)
+ return (s0 + s2) / 2;
+
+ return (s1 + s2) / 2;
+}
+
+static inline int k3_bgp_read_temp(struct k3_thermal_data *devdata,
+ int *temp)
+{
+ struct k3_j72xx_bandgap *bgp;
+ unsigned int dtemp, s0, s1, s2;
+
+ bgp = devdata->bgp;
+ /*
+ * Errata is applicable for am654 pg 1.0 silicon/J7ES. There
+ * is a variation of the order for certain degree centigrade on AM654.
+ * Work around that by getting the average of two closest
+ * readings out of three readings everytime we want to
+ * report temperatures.
+ *
+ * Errata workaround.
+ */
+ s0 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ s1 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ s2 = readl(bgp->base + devdata->stat_offset) &
+ K3_VTM_TS_STAT_DTEMP_MASK;
+ dtemp = vtm_get_best_value(s0, s1, s2);
+
+ if (dtemp >= TABLE_SIZE)
+ return -EINVAL;
+
+ *temp = derived_table[dtemp];
+
+ return 0;
+}
+
+/* Get temperature callback function for thermal zone */
+static int k3_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ return k3_bgp_read_temp(thermal_zone_device_priv(tz), temp);
+}
+
+static const struct thermal_zone_device_ops k3_of_thermal_ops = {
+ .get_temp = k3_thermal_get_temp,
+};
+
+static int k3_j72xx_bandgap_temp_to_adc_code(int temp)
+{
+ int low = 0, high = TABLE_SIZE - 1, mid;
+
+ if (temp > 160000 || temp < -50000)
+ return -EINVAL;
+
+ /* Binary search to find the adc code */
+ while (low < (high - 1)) {
+ mid = (low + high) / 2;
+ if (temp <= derived_table[mid])
+ high = mid;
+ else
+ low = mid;
+ }
+
+ return mid;
+}
+
+static void get_efuse_values(int id, struct k3_thermal_data *data, int *err,
+ void __iomem *fuse_base)
+{
+ int i, tmp, pow;
+ int ct_offsets[5][K3_VTM_CORRECTION_TEMP_CNT] = {
+ { 0x0, 0x8, 0x4 },
+ { 0x0, 0x8, 0x4 },
+ { 0x0, -1, 0x4 },
+ { 0x0, 0xC, -1 },
+ { 0x0, 0xc, 0x8 }
+ };
+ int ct_bm[5][K3_VTM_CORRECTION_TEMP_CNT] = {
+ { 0x3f, 0x1fe000, 0x1ff },
+ { 0xfc0, 0x1fe000, 0x3fe00 },
+ { 0x3f000, 0x7f800000, 0x7fc0000 },
+ { 0xfc0000, 0x1fe0, 0x1f800000 },
+ { 0x3f000000, 0x1fe000, 0x1ff0 }
+ };
+
+ for (i = 0; i < 3; i++) {
+ /* Extract the offset value using bit-mask */
+ if (ct_offsets[id][i] == -1 && i == 1) {
+ /* 25C offset Case of Sensor 2 split between 2 regs */
+ tmp = (readl(fuse_base + 0x8) & 0xE0000000) >> (29);
+ tmp |= ((readl(fuse_base + 0xC) & 0x1F) << 3);
+ pow = tmp & 0x80;
+ } else if (ct_offsets[id][i] == -1 && i == 2) {
+ /* 125C Case of Sensor 3 split between 2 regs */
+ tmp = (readl(fuse_base + 0x4) & 0xF8000000) >> (27);
+ tmp |= ((readl(fuse_base + 0x8) & 0xF) << 5);
+ pow = tmp & 0x100;
+ } else {
+ tmp = readl(fuse_base + ct_offsets[id][i]);
+ tmp &= ct_bm[id][i];
+ tmp = tmp >> __ffs(ct_bm[id][i]);
+
+ /* Obtain the sign bit pow*/
+ pow = ct_bm[id][i] >> __ffs(ct_bm[id][i]);
+ pow += 1;
+ pow /= 2;
+ }
+
+ /* Check for negative value */
+ if (tmp & pow) {
+ /* 2's complement value */
+ tmp = two_cmp(tmp, ct_bm[id][i] >> __ffs(ct_bm[id][i]));
+ }
+ err[i] = tmp;
+ }
+
+ /* Err value for 150C is set to 0 */
+ err[i] = 0;
+}
+
+static void print_look_up_table(struct device *dev, int *ref_table)
+{
+ int i;
+
+ dev_dbg(dev, "The contents of derived array\n");
+ dev_dbg(dev, "Code Temperature\n");
+ for (i = 0; i < TABLE_SIZE; i++)
+ dev_dbg(dev, "%d %d %d\n", i, derived_table[i], ref_table[i]);
+}
+
+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)
+{
+ 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;
+ 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,
+ 3251200000000000,
+ -1705800000000,
+ 603730000,
+ -92627,
+ };
+
+ const s64 pvt_wa_factors[] = {
+ -415230000000000000,
+ 3126600000000000,
+ -1157800000000,
+ };
+
+ bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL);
+ if (!bgp)
+ return -ENOMEM;
+
+ bgp->dev = dev;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ bgp->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(bgp->base))
+ return PTR_ERR(bgp->base);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ bgp->cfg2_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(bgp->cfg2_base))
+ return PTR_ERR(bgp->cfg2_base);
+
+ driver_data = of_device_get_match_data(dev);
+ if (driver_data)
+ workaround_needed = driver_data->has_errata_i2128;
+
+ /*
+ * Some of TI's J721E SoCs require a software trimming procedure
+ * for the temperature monitors to function properly. To determine
+ * if this particular SoC is NOT affected, both bits in the
+ * WKUP_SPARE_FUSE0[31:30] will be set (0xC0000000) indicating
+ * when software trimming should NOT be applied.
+ *
+ * https://www.ti.com/lit/er/sprz455c/sprz455c.pdf
+ */
+ if (workaround_needed) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ fuse_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(fuse_base))
+ return PTR_ERR(fuse_base);
+
+ if ((readl(fuse_base) & 0xc0000000) == 0xc0000000)
+ workaround_needed = false;
+ }
+
+ dev_dbg(bgp->dev, "Work around %sneeded\n",
+ workaround_needed ? "" : "not ");
+
+ pm_runtime_enable(dev);
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ pm_runtime_disable(dev);
+ return ret;
+ }
+
+ /* Get the sensor count in the VTM */
+ val = readl(bgp->base + K3_VTM_DEVINFO_PWR0_OFFSET);
+ 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, bgp->cnt, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ ref_table = kzalloc_objs(*ref_table, TABLE_SIZE);
+ if (!ref_table) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ derived_table = devm_kcalloc(bgp->dev, TABLE_SIZE, sizeof(*derived_table),
+ GFP_KERNEL);
+ if (!derived_table) {
+ ret = -ENOMEM;
+ goto err_free_ref_table;
+ }
+
+ if (!workaround_needed)
+ init_table(5, ref_table, golden_factors);
+ else
+ init_table(3, ref_table, pvt_wa_factors);
+
+ /* 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 +
+ K3_VTM_TMPSENS_STAT_OFFSET;
+
+ if (workaround_needed) {
+ /* ref adc values for -40C, 30C & 125C respectively */
+ err_vals.refs[0] = MINUS40CREF;
+ err_vals.refs[1] = PLUS30CREF;
+ err_vals.refs[2] = PLUS125CREF;
+ err_vals.refs[3] = PLUS150CREF;
+ get_efuse_values(id, &data[id], err_vals.errs, fuse_base);
+ }
+
+ if (id == 0 && workaround_needed)
+ prep_lookup_table(&err_vals, ref_table);
+ else if (id == 0 && !workaround_needed)
+ memcpy(derived_table, ref_table, TABLE_SIZE * 4);
+
+ 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)) {
+ dev_err(bgp->dev, "thermal zone device is NULL\n");
+ ret = PTR_ERR(ti_thermal);
+ goto err_free_ref_table;
+ }
+
+ devm_thermal_add_hwmon_sysfs(bgp->dev, ti_thermal);
+ }
+
+ platform_set_drvdata(pdev, bgp);
+
+ print_look_up_table(dev, ref_table);
+ /*
+ * Now that the derived_table has the appropriate look up values
+ * Free up the ref_table
+ */
+ kfree(ref_table);
+
+ return 0;
+
+err_free_ref_table:
+ kfree(ref_table);
+
+err_alloc:
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static 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,
+};
+
+static const struct k3_j72xx_bandgap_data k3_j72xx_bandgap_j7200_data = {
+ .has_errata_i2128 = false,
+};
+
+static const struct of_device_id of_k3_j72xx_bandgap_match[] = {
+ {
+ .compatible = "ti,j721e-vtm",
+ .data = &k3_j72xx_bandgap_j721e_data,
+ },
+ {
+ .compatible = "ti,j7200-vtm",
+ .data = &k3_j72xx_bandgap_j7200_data,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, of_k3_j72xx_bandgap_match);
+
+static struct platform_driver k3_j72xx_bandgap_sensor_driver = {
+ .probe = k3_j72xx_bandgap_probe,
+ .remove = k3_j72xx_bandgap_remove,
+ .driver = {
+ .name = "k3-j72xx-soc-thermal",
+ .of_match_table = of_k3_j72xx_bandgap_match,
+ .pm = pm_sleep_ptr(&k3_j72xx_bandgap_pm_ops),
+ },
+};
+
+module_platform_driver(k3_j72xx_bandgap_sensor_driver);
+
+MODULE_DESCRIPTION("K3 bandgap temperature sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>");
diff --git a/drivers/thermal/khadas_mcu_fan.c b/drivers/thermal/khadas_mcu_fan.c
new file mode 100644
index 000000000..21b3d0a71
--- /dev/null
+++ b/drivers/thermal/khadas_mcu_fan.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Khadas MCU Controlled FAN driver
+ *
+ * Copyright (C) 2020 BayLibre SAS
+ * Author(s): Neil Armstrong <narmstrong@baylibre.com>
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/khadas-mcu.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/thermal.h>
+
+#define MAX_LEVEL 3
+
+struct khadas_mcu_fan_ctx {
+ struct khadas_mcu *mcu;
+ unsigned int level;
+ struct thermal_cooling_device *cdev;
+};
+
+static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx,
+ unsigned int level)
+{
+ int ret;
+
+ ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG,
+ level);
+ if (ret)
+ return ret;
+
+ ctx->level = level;
+
+ return 0;
+}
+
+static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ *state = MAX_LEVEL;
+
+ return 0;
+}
+
+static int khadas_mcu_fan_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+ *state = ctx->level;
+
+ return 0;
+}
+
+static int
+khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct khadas_mcu_fan_ctx *ctx = cdev->devdata;
+
+ if (state > MAX_LEVEL)
+ return -EINVAL;
+
+ if (state == ctx->level)
+ return 0;
+
+ return khadas_mcu_fan_set_level(ctx, state);
+}
+
+static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = {
+ .get_max_state = khadas_mcu_fan_get_max_state,
+ .get_cur_state = khadas_mcu_fan_get_cur_state,
+ .set_cur_state = khadas_mcu_fan_set_cur_state,
+};
+
+static int khadas_mcu_fan_probe(struct platform_device *pdev)
+{
+ struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
+ struct thermal_cooling_device *cdev;
+ struct device *dev = &pdev->dev;
+ struct khadas_mcu_fan_ctx *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ ctx->mcu = mcu;
+ platform_set_drvdata(pdev, ctx);
+
+ cdev = devm_thermal_of_child_cooling_device_register(dev->parent,
+ dev->parent->of_node,
+ "khadas-mcu-fan", ctx,
+ &khadas_mcu_fan_cooling_ops);
+ if (IS_ERR(cdev)) {
+ ret = PTR_ERR(cdev);
+ dev_err(dev, "Failed to register khadas-mcu-fan as cooling device: %d\n",
+ ret);
+ return ret;
+ }
+ ctx->cdev = cdev;
+
+ return 0;
+}
+
+static void khadas_mcu_fan_shutdown(struct platform_device *pdev)
+{
+ struct khadas_mcu_fan_ctx *ctx = platform_get_drvdata(pdev);
+
+ khadas_mcu_fan_set_level(ctx, 0);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int khadas_mcu_fan_suspend(struct device *dev)
+{
+ struct khadas_mcu_fan_ctx *ctx = dev_get_drvdata(dev);
+ unsigned int level_save = ctx->level;
+ int ret;
+
+ ret = khadas_mcu_fan_set_level(ctx, 0);
+ if (ret)
+ return ret;
+
+ ctx->level = level_save;
+
+ return 0;
+}
+
+static int khadas_mcu_fan_resume(struct device *dev)
+{
+ struct khadas_mcu_fan_ctx *ctx = dev_get_drvdata(dev);
+
+ return khadas_mcu_fan_set_level(ctx, ctx->level);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(khadas_mcu_fan_pm, khadas_mcu_fan_suspend,
+ khadas_mcu_fan_resume);
+
+static const struct platform_device_id khadas_mcu_fan_id_table[] = {
+ { .name = "khadas-mcu-fan-ctrl", },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, khadas_mcu_fan_id_table);
+
+static struct platform_driver khadas_mcu_fan_driver = {
+ .probe = khadas_mcu_fan_probe,
+ .shutdown = khadas_mcu_fan_shutdown,
+ .driver = {
+ .name = "khadas-mcu-fan-ctrl",
+ .pm = &khadas_mcu_fan_pm,
+ },
+ .id_table = khadas_mcu_fan_id_table,
+};
+
+module_platform_driver(khadas_mcu_fan_driver);
+
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Khadas MCU FAN driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/kirkwood_thermal.c b/drivers/thermal/kirkwood_thermal.c
new file mode 100644
index 000000000..4619e090f
--- /dev/null
+++ b/drivers/thermal/kirkwood_thermal.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Kirkwood thermal sensor driver
+ *
+ * Copyright (C) 2012 Nobuhiro Iwamatsu <iwamatsu@nigauri.org>
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#define KIRKWOOD_THERMAL_VALID_OFFSET 9
+#define KIRKWOOD_THERMAL_VALID_MASK 0x1
+#define KIRKWOOD_THERMAL_TEMP_OFFSET 10
+#define KIRKWOOD_THERMAL_TEMP_MASK 0x1FF
+
+/* Kirkwood Thermal Sensor Dev Structure */
+struct kirkwood_thermal_priv {
+ void __iomem *sensor;
+};
+
+static int kirkwood_get_temp(struct thermal_zone_device *thermal,
+ int *temp)
+{
+ unsigned long reg;
+ struct kirkwood_thermal_priv *priv = thermal_zone_device_priv(thermal);
+
+ reg = readl_relaxed(priv->sensor);
+
+ /* Valid check */
+ if (!((reg >> KIRKWOOD_THERMAL_VALID_OFFSET) &
+ KIRKWOOD_THERMAL_VALID_MASK))
+ return -EIO;
+
+ /*
+ * Calculate temperature. According to Marvell internal
+ * documentation the formula for this is:
+ * Celsius = (322-reg)/1.3625
+ */
+ reg = (reg >> KIRKWOOD_THERMAL_TEMP_OFFSET) &
+ KIRKWOOD_THERMAL_TEMP_MASK;
+ *temp = ((3220000000UL - (10000000UL * reg)) / 13625);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops ops = {
+ .get_temp = kirkwood_get_temp,
+};
+
+static const struct of_device_id kirkwood_thermal_id_table[] = {
+ { .compatible = "marvell,kirkwood-thermal" },
+ {}
+};
+
+static int kirkwood_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *thermal = NULL;
+ struct kirkwood_thermal_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->sensor = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(priv->sensor))
+ return PTR_ERR(priv->sensor);
+
+ 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");
+ return PTR_ERR(thermal);
+ }
+ ret = thermal_zone_device_enable(thermal);
+ if (ret) {
+ thermal_zone_device_unregister(thermal);
+ dev_err(&pdev->dev, "Failed to enable thermal zone device\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, thermal);
+
+ return 0;
+}
+
+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);
+}
+
+MODULE_DEVICE_TABLE(of, kirkwood_thermal_id_table);
+
+static struct platform_driver kirkwood_thermal_driver = {
+ .probe = kirkwood_thermal_probe,
+ .remove = kirkwood_thermal_exit,
+ .driver = {
+ .name = "kirkwood_thermal",
+ .of_match_table = kirkwood_thermal_id_table,
+ },
+};
+
+module_platform_driver(kirkwood_thermal_driver);
+
+MODULE_AUTHOR("Nobuhiro Iwamatsu <iwamatsu@nigauri.org>");
+MODULE_DESCRIPTION("kirkwood thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
new file mode 100644
index 000000000..4d40fc706
--- /dev/null
+++ b/drivers/thermal/loongson2_thermal.c
@@ -0,0 +1,217 @@
+// 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/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 ret;
+
+ 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
new file mode 100644
index 000000000..f4a1535f4
--- /dev/null
+++ b/drivers/thermal/max77620_thermal.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Junction temperature thermal driver for Maxim Max77620.
+ *
+ * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ * Mallikarjun Kasoju <mkasoju@nvidia.com>
+ */
+
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max77620.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define MAX77620_NORMAL_OPERATING_TEMP 100000
+#define MAX77620_TJALARM1_TEMP 120000
+#define MAX77620_TJALARM2_TEMP 140000
+
+struct max77620_therm_info {
+ struct device *dev;
+ struct regmap *rmap;
+ struct thermal_zone_device *tz_device;
+ int irq_tjalarm1;
+ int irq_tjalarm2;
+};
+
+/**
+ * max77620_thermal_read_temp: Read PMIC die temperatue.
+ * @data: Device specific data.
+ * @temp: Temperature in millidegrees Celsius
+ *
+ * The actual temperature of PMIC die is not available from PMIC.
+ * PMIC only tells the status if it has crossed or not the threshold level
+ * of 120degC or 140degC.
+ * If threshold has not been crossed then assume die temperature as 100degC
+ * else 120degC or 140deG based on the PMIC die temp threshold status.
+ *
+ * Return 0 on success otherwise error number to show reason of failure.
+ */
+
+static int max77620_thermal_read_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct max77620_therm_info *mtherm = thermal_zone_device_priv(tz);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(mtherm->rmap, MAX77620_REG_STATLBT, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val & MAX77620_IRQ_TJALRM2_MASK)
+ *temp = MAX77620_TJALARM2_TEMP;
+ else if (val & MAX77620_IRQ_TJALRM1_MASK)
+ *temp = MAX77620_TJALARM1_TEMP;
+ else
+ *temp = MAX77620_NORMAL_OPERATING_TEMP;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops max77620_thermal_ops = {
+ .get_temp = max77620_thermal_read_temp,
+};
+
+static irqreturn_t max77620_thermal_irq(int irq, void *data)
+{
+ struct max77620_therm_info *mtherm = data;
+
+ if (irq == mtherm->irq_tjalarm1)
+ dev_warn(mtherm->dev, "Junction Temp Alarm1(120C) occurred\n");
+ else if (irq == mtherm->irq_tjalarm2)
+ dev_crit(mtherm->dev, "Junction Temp Alarm2(140C) occurred\n");
+
+ thermal_zone_device_update(mtherm->tz_device,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+static int max77620_thermal_probe(struct platform_device *pdev)
+{
+ struct max77620_therm_info *mtherm;
+ int ret;
+
+ mtherm = devm_kzalloc(&pdev->dev, sizeof(*mtherm), GFP_KERNEL);
+ if (!mtherm)
+ return -ENOMEM;
+
+ mtherm->irq_tjalarm1 = platform_get_irq(pdev, 0);
+ mtherm->irq_tjalarm2 = platform_get_irq(pdev, 1);
+ if ((mtherm->irq_tjalarm1 < 0) || (mtherm->irq_tjalarm2 < 0)) {
+ dev_err(&pdev->dev, "Alarm irq number not available\n");
+ return -EINVAL;
+ }
+
+ mtherm->dev = &pdev->dev;
+ mtherm->rmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!mtherm->rmap) {
+ dev_err(&pdev->dev, "Failed to get parent regmap\n");
+ return -ENODEV;
+ }
+
+ /*
+ * The reference taken to the parent's node which will be balanced on
+ * reprobe or on platform-device release.
+ */
+ device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent);
+
+ mtherm->tz_device = devm_thermal_of_zone_register(&pdev->dev, 0,
+ mtherm, &max77620_thermal_ops);
+ 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,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(&pdev->dev), mtherm);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_threaded_irq(&pdev->dev, mtherm->irq_tjalarm2, NULL,
+ max77620_thermal_irq,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(&pdev->dev), mtherm);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct platform_device_id max77620_thermal_devtype[] = {
+ { .name = "max77620-thermal", },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, max77620_thermal_devtype);
+
+static struct platform_driver max77620_thermal_driver = {
+ .driver = {
+ .name = "max77620-thermal",
+ },
+ .probe = max77620_thermal_probe,
+ .id_table = max77620_thermal_devtype,
+};
+
+module_platform_driver(max77620_thermal_driver);
+
+MODULE_DESCRIPTION("Max77620 Junction temperature Thermal driver");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_AUTHOR("Mallikarjun Kasoju <mkasoju@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/mediatek/Kconfig b/drivers/thermal/mediatek/Kconfig
new file mode 100644
index 000000000..d82c86d9b
--- /dev/null
+++ b/drivers/thermal/mediatek/Kconfig
@@ -0,0 +1,37 @@
+config MTK_THERMAL
+ tristate "MediaTek thermal drivers"
+ depends on THERMAL_OF
+ help
+ This is the option for MediaTek thermal software solutions.
+ Please enable corresponding options to get temperature
+ information from thermal sensors or turn on throttle
+ mechaisms for thermal mitigation.
+
+if MTK_THERMAL
+
+config MTK_SOC_THERMAL
+ tristate "AUXADC temperature sensor driver for MediaTek SoCs"
+ depends on HAS_IOMEM
+ help
+ Enable this option if you want to get SoC temperature
+ information for MediaTek platforms.
+ This driver configures thermal controllers to collect
+ temperature via AUXADC interface.
+
+config MTK_LVTS_THERMAL
+ tristate "LVTS Thermal Driver for MediaTek SoCs"
+ depends on HAS_IOMEM
+ help
+ Enable this option if you want to get SoC temperature
+ information for supported MediaTek platforms.
+ This driver configures LVTS (Low Voltage Thermal Sensor)
+ thermal controllers to collect temperatures via ASIF
+ (Analog Serial Interface).
+
+config MTK_LVTS_THERMAL_DEBUGFS
+ bool "LVTS thermal debugfs"
+ depends on MTK_LVTS_THERMAL && DEBUG_FS
+ help
+ Enable this option to debug the internals of the device driver.
+
+endif
diff --git a/drivers/thermal/mediatek/Makefile b/drivers/thermal/mediatek/Makefile
new file mode 100644
index 000000000..1c6daa1e6
--- /dev/null
+++ b/drivers/thermal/mediatek/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_MTK_SOC_THERMAL) += auxadc_thermal.o
+obj-$(CONFIG_MTK_LVTS_THERMAL) += lvts_thermal.o
diff --git a/drivers/thermal/mediatek/auxadc_thermal.c b/drivers/thermal/mediatek/auxadc_thermal.c
new file mode 100644
index 000000000..9ee2e7283
--- /dev/null
+++ b/drivers/thermal/mediatek/auxadc_thermal.c
@@ -0,0 +1,1316 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2015 MediaTek Inc.
+ * Author: Hanyi Wu <hanyi.wu@mediatek.com>
+ * Sascha Hauer <s.hauer@pengutronix.de>
+ * Dawei Chien <dawei.chien@mediatek.com>
+ * Louis Yu <louis.yu@mediatek.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/thermal.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+
+#include "../thermal_hwmon.h"
+
+/* AUXADC Registers */
+#define AUXADC_CON1_SET_V 0x008
+#define AUXADC_CON1_CLR_V 0x00c
+#define AUXADC_CON2_V 0x010
+#define AUXADC_DATA(channel) (0x14 + (channel) * 4)
+
+#define APMIXED_SYS_TS_CON0 0x600
+#define APMIXED_SYS_TS_CON1 0x604
+
+/* Thermal Controller Registers */
+#define TEMP_MONCTL0 0x000
+#define TEMP_MONCTL1 0x004
+#define TEMP_MONCTL2 0x008
+#define TEMP_MONIDET0 0x014
+#define TEMP_MONIDET1 0x018
+#define TEMP_MSRCTL0 0x038
+#define TEMP_MSRCTL1 0x03c
+#define TEMP_AHBPOLL 0x040
+#define TEMP_AHBTO 0x044
+#define TEMP_ADCPNP0 0x048
+#define TEMP_ADCPNP1 0x04c
+#define TEMP_ADCPNP2 0x050
+#define TEMP_ADCPNP3 0x0b4
+
+#define TEMP_ADCMUX 0x054
+#define TEMP_ADCEN 0x060
+#define TEMP_PNPMUXADDR 0x064
+#define TEMP_ADCMUXADDR 0x068
+#define TEMP_ADCENADDR 0x074
+#define TEMP_ADCVALIDADDR 0x078
+#define TEMP_ADCVOLTADDR 0x07c
+#define TEMP_RDCTRL 0x080
+#define TEMP_ADCVALIDMASK 0x084
+#define TEMP_ADCVOLTAGESHIFT 0x088
+#define TEMP_ADCWRITECTRL 0x08c
+#define TEMP_MSR0 0x090
+#define TEMP_MSR1 0x094
+#define TEMP_MSR2 0x098
+#define TEMP_MSR3 0x0B8
+
+#define TEMP_SPARE0 0x0f0
+
+#define TEMP_ADCPNP0_1 0x148
+#define TEMP_ADCPNP1_1 0x14c
+#define TEMP_ADCPNP2_1 0x150
+#define TEMP_MSR0_1 0x190
+#define TEMP_MSR1_1 0x194
+#define TEMP_MSR2_1 0x198
+#define TEMP_ADCPNP3_1 0x1b4
+#define TEMP_MSR3_1 0x1B8
+
+#define PTPCORESEL 0x400
+
+#define TEMP_MONCTL1_PERIOD_UNIT(x) ((x) & 0x3ff)
+
+#define TEMP_MONCTL2_FILTER_INTERVAL(x) (((x) & 0x3ff) << 16)
+#define TEMP_MONCTL2_SENSOR_INTERVAL(x) ((x) & 0x3ff)
+
+#define TEMP_AHBPOLL_ADC_POLL_INTERVAL(x) (x)
+
+#define TEMP_ADCWRITECTRL_ADC_PNP_WRITE BIT(0)
+#define TEMP_ADCWRITECTRL_ADC_MUX_WRITE BIT(1)
+
+#define TEMP_ADCVALIDMASK_VALID_HIGH BIT(5)
+#define TEMP_ADCVALIDMASK_VALID_POS(bit) (bit)
+
+/* MT8173 thermal sensors */
+#define MT8173_TS1 0
+#define MT8173_TS2 1
+#define MT8173_TS3 2
+#define MT8173_TS4 3
+#define MT8173_TSABB 4
+
+/* AUXADC channel 11 is used for the temperature sensors */
+#define MT8173_TEMP_AUXADC_CHANNEL 11
+
+/* The total number of temperature sensors in the MT8173 */
+#define MT8173_NUM_SENSORS 5
+
+/* The number of banks in the MT8173 */
+#define MT8173_NUM_ZONES 4
+
+/* The number of sensing points per bank */
+#define MT8173_NUM_SENSORS_PER_ZONE 4
+
+/* The number of controller in the MT8173 */
+#define MT8173_NUM_CONTROLLER 1
+
+/* The calibration coefficient of sensor */
+#define MT8173_CALIBRATION 165
+
+/* Valid temperatures range */
+#define MT8173_TEMP_MIN -20000
+#define MT8173_TEMP_MAX 150000
+
+/*
+ * Layout of the fuses providing the calibration data
+ * These macros could be used for MT8183, MT8173, MT2701, and MT2712.
+ * MT8183 has 6 sensors and needs 6 VTS calibration data.
+ * MT8173 has 5 sensors and needs 5 VTS calibration data.
+ * MT2701 has 3 sensors and needs 3 VTS calibration data.
+ * MT2712 has 4 sensors and needs 4 VTS calibration data.
+ */
+#define CALIB_BUF0_VALID_V1 BIT(0)
+#define CALIB_BUF1_ADC_GE_V1(x) (((x) >> 22) & 0x3ff)
+#define CALIB_BUF0_VTS_TS1_V1(x) (((x) >> 17) & 0x1ff)
+#define CALIB_BUF0_VTS_TS2_V1(x) (((x) >> 8) & 0x1ff)
+#define CALIB_BUF1_VTS_TS3_V1(x) (((x) >> 0) & 0x1ff)
+#define CALIB_BUF2_VTS_TS4_V1(x) (((x) >> 23) & 0x1ff)
+#define CALIB_BUF2_VTS_TS5_V1(x) (((x) >> 5) & 0x1ff)
+#define CALIB_BUF2_VTS_TSABB_V1(x) (((x) >> 14) & 0x1ff)
+#define CALIB_BUF0_DEGC_CALI_V1(x) (((x) >> 1) & 0x3f)
+#define CALIB_BUF0_O_SLOPE_V1(x) (((x) >> 26) & 0x3f)
+#define CALIB_BUF0_O_SLOPE_SIGN_V1(x) (((x) >> 7) & 0x1)
+#define CALIB_BUF1_ID_V1(x) (((x) >> 9) & 0x1)
+
+/*
+ * Layout of the fuses providing the calibration data
+ * These macros could be used for MT7622.
+ */
+#define CALIB_BUF0_ADC_OE_V2(x) (((x) >> 22) & 0x3ff)
+#define CALIB_BUF0_ADC_GE_V2(x) (((x) >> 12) & 0x3ff)
+#define CALIB_BUF0_DEGC_CALI_V2(x) (((x) >> 6) & 0x3f)
+#define CALIB_BUF0_O_SLOPE_V2(x) (((x) >> 0) & 0x3f)
+#define CALIB_BUF1_VTS_TS1_V2(x) (((x) >> 23) & 0x1ff)
+#define CALIB_BUF1_VTS_TS2_V2(x) (((x) >> 14) & 0x1ff)
+#define CALIB_BUF1_VTS_TSABB_V2(x) (((x) >> 5) & 0x1ff)
+#define CALIB_BUF1_VALID_V2(x) (((x) >> 4) & 0x1)
+#define CALIB_BUF1_O_SLOPE_SIGN_V2(x) (((x) >> 3) & 0x1)
+
+/*
+ * Layout of the fuses providing the calibration data
+ * These macros can be used for MT7981 and MT7986.
+ */
+#define CALIB_BUF0_ADC_GE_V3(x) (((x) >> 0) & 0x3ff)
+#define CALIB_BUF0_DEGC_CALI_V3(x) (((x) >> 20) & 0x3f)
+#define CALIB_BUF0_O_SLOPE_V3(x) (((x) >> 26) & 0x3f)
+#define CALIB_BUF1_VTS_TS1_V3(x) (((x) >> 0) & 0x1ff)
+#define CALIB_BUF1_VTS_TS2_V3(x) (((x) >> 21) & 0x1ff)
+#define CALIB_BUF1_VTS_TSABB_V3(x) (((x) >> 9) & 0x1ff)
+#define CALIB_BUF1_VALID_V3(x) (((x) >> 18) & 0x1)
+#define CALIB_BUF1_O_SLOPE_SIGN_V3(x) (((x) >> 19) & 0x1)
+#define CALIB_BUF1_ID_V3(x) (((x) >> 20) & 0x1)
+
+enum {
+ VTS1,
+ VTS2,
+ VTS3,
+ VTS4,
+ VTS5,
+ VTSABB,
+ MAX_NUM_VTS,
+};
+
+enum mtk_thermal_version {
+ MTK_THERMAL_V1 = 1,
+ MTK_THERMAL_V2,
+ MTK_THERMAL_V3,
+};
+
+/* MT2701 thermal sensors */
+#define MT2701_TS1 0
+#define MT2701_TS2 1
+#define MT2701_TSABB 2
+
+/* AUXADC channel 11 is used for the temperature sensors */
+#define MT2701_TEMP_AUXADC_CHANNEL 11
+
+/* The total number of temperature sensors in the MT2701 */
+#define MT2701_NUM_SENSORS 3
+
+/* The number of sensing points per bank */
+#define MT2701_NUM_SENSORS_PER_ZONE 3
+
+/* The number of controller in the MT2701 */
+#define MT2701_NUM_CONTROLLER 1
+
+/* The calibration coefficient of sensor */
+#define MT2701_CALIBRATION 165
+
+/* MT2712 thermal sensors */
+#define MT2712_TS1 0
+#define MT2712_TS2 1
+#define MT2712_TS3 2
+#define MT2712_TS4 3
+
+/* AUXADC channel 11 is used for the temperature sensors */
+#define MT2712_TEMP_AUXADC_CHANNEL 11
+
+/* The total number of temperature sensors in the MT2712 */
+#define MT2712_NUM_SENSORS 4
+
+/* The number of sensing points per bank */
+#define MT2712_NUM_SENSORS_PER_ZONE 4
+
+/* The number of controller in the MT2712 */
+#define MT2712_NUM_CONTROLLER 1
+
+/* The calibration coefficient of sensor */
+#define MT2712_CALIBRATION 165
+
+#define MT7622_TEMP_AUXADC_CHANNEL 11
+#define MT7622_NUM_SENSORS 1
+#define MT7622_NUM_ZONES 1
+#define MT7622_NUM_SENSORS_PER_ZONE 1
+#define MT7622_TS1 0
+#define MT7622_NUM_CONTROLLER 1
+
+/* The maximum number of banks */
+#define MAX_NUM_ZONES 8
+
+/* The calibration coefficient of sensor */
+#define MT7622_CALIBRATION 165
+
+/* MT8183 thermal sensors */
+#define MT8183_TS1 0
+#define MT8183_TS2 1
+#define MT8183_TS3 2
+#define MT8183_TS4 3
+#define MT8183_TS5 4
+#define MT8183_TSABB 5
+
+/* AUXADC channel is used for the temperature sensors */
+#define MT8183_TEMP_AUXADC_CHANNEL 11
+
+/* The total number of temperature sensors in the MT8183 */
+#define MT8183_NUM_SENSORS 6
+
+/* The number of banks in the MT8183 */
+#define MT8183_NUM_ZONES 1
+
+/* The number of sensing points per bank */
+#define MT8183_NUM_SENSORS_PER_ZONE 6
+
+/* The number of controller in the MT8183 */
+#define MT8183_NUM_CONTROLLER 2
+
+/* The calibration coefficient of sensor */
+#define MT8183_CALIBRATION 153
+
+/* AUXADC channel 11 is used for the temperature sensors */
+#define MT7986_TEMP_AUXADC_CHANNEL 11
+
+/* The total number of temperature sensors in the MT7986 */
+#define MT7986_NUM_SENSORS 1
+
+/* The number of banks in the MT7986 */
+#define MT7986_NUM_ZONES 1
+
+/* The number of sensing points per bank */
+#define MT7986_NUM_SENSORS_PER_ZONE 1
+
+/* MT7986 thermal sensors */
+#define MT7986_TS1 0
+
+/* The number of controller in the MT7986 */
+#define MT7986_NUM_CONTROLLER 1
+
+/* The calibration coefficient of sensor */
+#define MT7986_CALIBRATION 165
+
+/* MT8365 */
+#define MT8365_TEMP_AUXADC_CHANNEL 11
+#define MT8365_CALIBRATION 164
+#define MT8365_NUM_CONTROLLER 1
+#define MT8365_NUM_BANKS 1
+#define MT8365_NUM_SENSORS 3
+#define MT8365_NUM_SENSORS_PER_ZONE 3
+#define MT8365_TS1 0
+#define MT8365_TS2 1
+#define MT8365_TS3 2
+
+struct mtk_thermal;
+
+struct thermal_bank_cfg {
+ unsigned int num_sensors;
+ const int *sensors;
+};
+
+struct mtk_thermal_bank {
+ struct mtk_thermal *mt;
+ int id;
+};
+
+struct mtk_thermal_data {
+ s32 num_banks;
+ s32 num_sensors;
+ s32 auxadc_channel;
+ const int *vts_index;
+ const int *sensor_mux_values;
+ const int *msr;
+ const int *adcpnp;
+ const int cali_val;
+ const int num_controller;
+ const int *controller_offset;
+ bool need_switch_bank;
+ struct thermal_bank_cfg bank_data[MAX_NUM_ZONES];
+ enum mtk_thermal_version version;
+ u32 apmixed_buffer_ctl_reg;
+ u32 apmixed_buffer_ctl_mask;
+ u32 apmixed_buffer_ctl_set;
+};
+
+struct mtk_thermal {
+ struct device *dev;
+ void __iomem *thermal_base;
+
+ struct clk *clk_peri_therm;
+ struct clk *clk_auxadc;
+ /* lock: for getting and putting banks */
+ struct mutex lock;
+
+ /* Calibration values */
+ s32 adc_ge;
+ s32 adc_oe;
+ s32 degc_cali;
+ s32 o_slope;
+ s32 o_slope_sign;
+ s32 vts[MAX_NUM_VTS];
+
+ const struct mtk_thermal_data *conf;
+ struct mtk_thermal_bank banks[MAX_NUM_ZONES];
+
+ int (*raw_to_mcelsius)(struct mtk_thermal *mt, int sensno, s32 raw);
+};
+
+/* MT8183 thermal sensor data */
+static const int mt8183_bank_data[MT8183_NUM_SENSORS] = {
+ MT8183_TS1, MT8183_TS2, MT8183_TS3, MT8183_TS4, MT8183_TS5, MT8183_TSABB
+};
+
+static const int mt8183_msr[MT8183_NUM_SENSORS_PER_ZONE] = {
+ TEMP_MSR0_1, TEMP_MSR1_1, TEMP_MSR2_1, TEMP_MSR1, TEMP_MSR0, TEMP_MSR3_1
+};
+
+static const int mt8183_adcpnp[MT8183_NUM_SENSORS_PER_ZONE] = {
+ TEMP_ADCPNP0_1, TEMP_ADCPNP1_1, TEMP_ADCPNP2_1,
+ TEMP_ADCPNP1, TEMP_ADCPNP0, TEMP_ADCPNP3_1
+};
+
+static const int mt8183_mux_values[MT8183_NUM_SENSORS] = { 0, 1, 2, 3, 4, 0 };
+static const int mt8183_tc_offset[MT8183_NUM_CONTROLLER] = {0x0, 0x100};
+
+static const int mt8183_vts_index[MT8183_NUM_SENSORS] = {
+ VTS1, VTS2, VTS3, VTS4, VTS5, VTSABB
+};
+
+/* MT8173 thermal sensor data */
+static const int mt8173_bank_data[MT8173_NUM_ZONES][3] = {
+ { MT8173_TS2, MT8173_TS3 },
+ { MT8173_TS2, MT8173_TS4 },
+ { MT8173_TS1, MT8173_TS2, MT8173_TSABB },
+ { MT8173_TS2 },
+};
+
+static const int mt8173_msr[MT8173_NUM_SENSORS_PER_ZONE] = {
+ TEMP_MSR0, TEMP_MSR1, TEMP_MSR2, TEMP_MSR3
+};
+
+static const int mt8173_adcpnp[MT8173_NUM_SENSORS_PER_ZONE] = {
+ TEMP_ADCPNP0, TEMP_ADCPNP1, TEMP_ADCPNP2, TEMP_ADCPNP3
+};
+
+static const int mt8173_mux_values[MT8173_NUM_SENSORS] = { 0, 1, 2, 3, 16 };
+static const int mt8173_tc_offset[MT8173_NUM_CONTROLLER] = { 0x0, };
+
+static const int mt8173_vts_index[MT8173_NUM_SENSORS] = {
+ VTS1, VTS2, VTS3, VTS4, VTSABB
+};
+
+/* MT2701 thermal sensor data */
+static const int mt2701_bank_data[MT2701_NUM_SENSORS] = {
+ MT2701_TS1, MT2701_TS2, MT2701_TSABB
+};
+
+static const int mt2701_msr[MT2701_NUM_SENSORS_PER_ZONE] = {
+ TEMP_MSR0, TEMP_MSR1, TEMP_MSR2
+};
+
+static const int mt2701_adcpnp[MT2701_NUM_SENSORS_PER_ZONE] = {
+ TEMP_ADCPNP0, TEMP_ADCPNP1, TEMP_ADCPNP2
+};
+
+static const int mt2701_mux_values[MT2701_NUM_SENSORS] = { 0, 1, 16 };
+static const int mt2701_tc_offset[MT2701_NUM_CONTROLLER] = { 0x0, };
+
+static const int mt2701_vts_index[MT2701_NUM_SENSORS] = {
+ VTS1, VTS2, VTS3
+};
+
+/* MT2712 thermal sensor data */
+static const int mt2712_bank_data[MT2712_NUM_SENSORS] = {
+ MT2712_TS1, MT2712_TS2, MT2712_TS3, MT2712_TS4
+};
+
+static const int mt2712_msr[MT2712_NUM_SENSORS_PER_ZONE] = {
+ TEMP_MSR0, TEMP_MSR1, TEMP_MSR2, TEMP_MSR3
+};
+
+static const int mt2712_adcpnp[MT2712_NUM_SENSORS_PER_ZONE] = {
+ TEMP_ADCPNP0, TEMP_ADCPNP1, TEMP_ADCPNP2, TEMP_ADCPNP3
+};
+
+static const int mt2712_mux_values[MT2712_NUM_SENSORS] = { 0, 1, 2, 3 };
+static const int mt2712_tc_offset[MT2712_NUM_CONTROLLER] = { 0x0, };
+
+static const int mt2712_vts_index[MT2712_NUM_SENSORS] = {
+ VTS1, VTS2, VTS3, VTS4
+};
+
+/* MT7622 thermal sensor data */
+static const int mt7622_bank_data[MT7622_NUM_SENSORS] = { MT7622_TS1, };
+static const int mt7622_msr[MT7622_NUM_SENSORS_PER_ZONE] = { TEMP_MSR0, };
+static const int mt7622_adcpnp[MT7622_NUM_SENSORS_PER_ZONE] = { TEMP_ADCPNP0, };
+static const int mt7622_mux_values[MT7622_NUM_SENSORS] = { 0, };
+static const int mt7622_vts_index[MT7622_NUM_SENSORS] = { VTS1 };
+static const int mt7622_tc_offset[MT7622_NUM_CONTROLLER] = { 0x0, };
+
+/* MT7986 thermal sensor data */
+static const int mt7986_bank_data[MT7986_NUM_SENSORS] = { MT7986_TS1, };
+static const int mt7986_msr[MT7986_NUM_SENSORS_PER_ZONE] = { TEMP_MSR0, };
+static const int mt7986_adcpnp[MT7986_NUM_SENSORS_PER_ZONE] = { TEMP_ADCPNP0, };
+static const int mt7986_mux_values[MT7986_NUM_SENSORS] = { 0, };
+static const int mt7986_vts_index[MT7986_NUM_SENSORS] = { VTS1 };
+static const int mt7986_tc_offset[MT7986_NUM_CONTROLLER] = { 0x0, };
+
+/* MT8365 thermal sensor data */
+static const int mt8365_bank_data[MT8365_NUM_SENSORS] = {
+ MT8365_TS1, MT8365_TS2, MT8365_TS3
+};
+
+static const int mt8365_msr[MT8365_NUM_SENSORS_PER_ZONE] = {
+ TEMP_MSR0, TEMP_MSR1, TEMP_MSR2
+};
+
+static const int mt8365_adcpnp[MT8365_NUM_SENSORS_PER_ZONE] = {
+ TEMP_ADCPNP0, TEMP_ADCPNP1, TEMP_ADCPNP2
+};
+
+static const int mt8365_mux_values[MT8365_NUM_SENSORS] = { 0, 1, 2 };
+static const int mt8365_tc_offset[MT8365_NUM_CONTROLLER] = { 0 };
+
+static const int mt8365_vts_index[MT8365_NUM_SENSORS] = { VTS1, VTS2, VTS3 };
+
+/*
+ * The MT8173 thermal controller has four banks. Each bank can read up to
+ * four temperature sensors simultaneously. The MT8173 has a total of 5
+ * temperature sensors. We use each bank to measure a certain area of the
+ * SoC. Since TS2 is located centrally in the SoC it is influenced by multiple
+ * areas, hence is used in different banks.
+ *
+ * The thermal core only gets the maximum temperature of all banks, so
+ * the bank concept wouldn't be necessary here. However, the SVS (Smart
+ * Voltage Scaling) unit makes its decisions based on the same bank
+ * data, and this indeed needs the temperatures of the individual banks
+ * for making better decisions.
+ */
+static const struct mtk_thermal_data mt8173_thermal_data = {
+ .auxadc_channel = MT8173_TEMP_AUXADC_CHANNEL,
+ .num_banks = MT8173_NUM_ZONES,
+ .num_sensors = MT8173_NUM_SENSORS,
+ .vts_index = mt8173_vts_index,
+ .cali_val = MT8173_CALIBRATION,
+ .num_controller = MT8173_NUM_CONTROLLER,
+ .controller_offset = mt8173_tc_offset,
+ .need_switch_bank = true,
+ .bank_data = {
+ {
+ .num_sensors = 2,
+ .sensors = mt8173_bank_data[0],
+ }, {
+ .num_sensors = 2,
+ .sensors = mt8173_bank_data[1],
+ }, {
+ .num_sensors = 3,
+ .sensors = mt8173_bank_data[2],
+ }, {
+ .num_sensors = 1,
+ .sensors = mt8173_bank_data[3],
+ },
+ },
+ .msr = mt8173_msr,
+ .adcpnp = mt8173_adcpnp,
+ .sensor_mux_values = mt8173_mux_values,
+ .version = MTK_THERMAL_V1,
+};
+
+/*
+ * The MT2701 thermal controller has one bank, which can read up to
+ * three temperature sensors simultaneously. The MT2701 has a total of 3
+ * temperature sensors.
+ *
+ * The thermal core only gets the maximum temperature of this one bank,
+ * so the bank concept wouldn't be necessary here. However, the SVS (Smart
+ * Voltage Scaling) unit makes its decisions based on the same bank
+ * data.
+ */
+static const struct mtk_thermal_data mt2701_thermal_data = {
+ .auxadc_channel = MT2701_TEMP_AUXADC_CHANNEL,
+ .num_banks = 1,
+ .num_sensors = MT2701_NUM_SENSORS,
+ .vts_index = mt2701_vts_index,
+ .cali_val = MT2701_CALIBRATION,
+ .num_controller = MT2701_NUM_CONTROLLER,
+ .controller_offset = mt2701_tc_offset,
+ .need_switch_bank = true,
+ .bank_data = {
+ {
+ .num_sensors = 3,
+ .sensors = mt2701_bank_data,
+ },
+ },
+ .msr = mt2701_msr,
+ .adcpnp = mt2701_adcpnp,
+ .sensor_mux_values = mt2701_mux_values,
+ .version = MTK_THERMAL_V1,
+};
+
+/*
+ * The MT8365 thermal controller has one bank, which can read up to
+ * four temperature sensors simultaneously. The MT8365 has a total of 3
+ * temperature sensors.
+ *
+ * The thermal core only gets the maximum temperature of this one bank,
+ * so the bank concept wouldn't be necessary here. However, the SVS (Smart
+ * Voltage Scaling) unit makes its decisions based on the same bank
+ * data.
+ */
+static const struct mtk_thermal_data mt8365_thermal_data = {
+ .auxadc_channel = MT8365_TEMP_AUXADC_CHANNEL,
+ .num_banks = MT8365_NUM_BANKS,
+ .num_sensors = MT8365_NUM_SENSORS,
+ .vts_index = mt8365_vts_index,
+ .cali_val = MT8365_CALIBRATION,
+ .num_controller = MT8365_NUM_CONTROLLER,
+ .controller_offset = mt8365_tc_offset,
+ .need_switch_bank = false,
+ .bank_data = {
+ {
+ .num_sensors = MT8365_NUM_SENSORS,
+ .sensors = mt8365_bank_data
+ },
+ },
+ .msr = mt8365_msr,
+ .adcpnp = mt8365_adcpnp,
+ .sensor_mux_values = mt8365_mux_values,
+ .version = MTK_THERMAL_V1,
+ .apmixed_buffer_ctl_reg = APMIXED_SYS_TS_CON0,
+ .apmixed_buffer_ctl_mask = (u32) ~GENMASK(29, 28),
+ .apmixed_buffer_ctl_set = 0,
+};
+
+/*
+ * The MT2712 thermal controller has one bank, which can read up to
+ * four temperature sensors simultaneously. The MT2712 has a total of 4
+ * temperature sensors.
+ *
+ * The thermal core only gets the maximum temperature of this one bank,
+ * so the bank concept wouldn't be necessary here. However, the SVS (Smart
+ * Voltage Scaling) unit makes its decisions based on the same bank
+ * data.
+ */
+static const struct mtk_thermal_data mt2712_thermal_data = {
+ .auxadc_channel = MT2712_TEMP_AUXADC_CHANNEL,
+ .num_banks = 1,
+ .num_sensors = MT2712_NUM_SENSORS,
+ .vts_index = mt2712_vts_index,
+ .cali_val = MT2712_CALIBRATION,
+ .num_controller = MT2712_NUM_CONTROLLER,
+ .controller_offset = mt2712_tc_offset,
+ .need_switch_bank = true,
+ .bank_data = {
+ {
+ .num_sensors = 4,
+ .sensors = mt2712_bank_data,
+ },
+ },
+ .msr = mt2712_msr,
+ .adcpnp = mt2712_adcpnp,
+ .sensor_mux_values = mt2712_mux_values,
+ .version = MTK_THERMAL_V1,
+};
+
+/*
+ * MT7622 have only one sensing point which uses AUXADC Channel 11 for raw data
+ * access.
+ */
+static const struct mtk_thermal_data mt7622_thermal_data = {
+ .auxadc_channel = MT7622_TEMP_AUXADC_CHANNEL,
+ .num_banks = MT7622_NUM_ZONES,
+ .num_sensors = MT7622_NUM_SENSORS,
+ .vts_index = mt7622_vts_index,
+ .cali_val = MT7622_CALIBRATION,
+ .num_controller = MT7622_NUM_CONTROLLER,
+ .controller_offset = mt7622_tc_offset,
+ .need_switch_bank = true,
+ .bank_data = {
+ {
+ .num_sensors = 1,
+ .sensors = mt7622_bank_data,
+ },
+ },
+ .msr = mt7622_msr,
+ .adcpnp = mt7622_adcpnp,
+ .sensor_mux_values = mt7622_mux_values,
+ .version = MTK_THERMAL_V2,
+ .apmixed_buffer_ctl_reg = APMIXED_SYS_TS_CON1,
+ .apmixed_buffer_ctl_mask = GENMASK(31, 6) | BIT(3),
+ .apmixed_buffer_ctl_set = BIT(0),
+};
+
+/*
+ * The MT8183 thermal controller has one bank for the current SW framework.
+ * The MT8183 has a total of 6 temperature sensors.
+ * There are two thermal controller to control the six sensor.
+ * The first one bind 2 sensor, and the other bind 4 sensors.
+ * The thermal core only gets the maximum temperature of all sensor, so
+ * the bank concept wouldn't be necessary here. However, the SVS (Smart
+ * Voltage Scaling) unit makes its decisions based on the same bank
+ * data, and this indeed needs the temperatures of the individual banks
+ * for making better decisions.
+ */
+static const struct mtk_thermal_data mt8183_thermal_data = {
+ .auxadc_channel = MT8183_TEMP_AUXADC_CHANNEL,
+ .num_banks = MT8183_NUM_ZONES,
+ .num_sensors = MT8183_NUM_SENSORS,
+ .vts_index = mt8183_vts_index,
+ .cali_val = MT8183_CALIBRATION,
+ .num_controller = MT8183_NUM_CONTROLLER,
+ .controller_offset = mt8183_tc_offset,
+ .need_switch_bank = false,
+ .bank_data = {
+ {
+ .num_sensors = 6,
+ .sensors = mt8183_bank_data,
+ },
+ },
+
+ .msr = mt8183_msr,
+ .adcpnp = mt8183_adcpnp,
+ .sensor_mux_values = mt8183_mux_values,
+ .version = MTK_THERMAL_V1,
+};
+
+/*
+ * MT7986 uses AUXADC Channel 11 for raw data access.
+ */
+static const struct mtk_thermal_data mt7986_thermal_data = {
+ .auxadc_channel = MT7986_TEMP_AUXADC_CHANNEL,
+ .num_banks = MT7986_NUM_ZONES,
+ .num_sensors = MT7986_NUM_SENSORS,
+ .vts_index = mt7986_vts_index,
+ .cali_val = MT7986_CALIBRATION,
+ .num_controller = MT7986_NUM_CONTROLLER,
+ .controller_offset = mt7986_tc_offset,
+ .need_switch_bank = true,
+ .bank_data = {
+ {
+ .num_sensors = 1,
+ .sensors = mt7986_bank_data,
+ },
+ },
+ .msr = mt7986_msr,
+ .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)
+{
+ return (temp >= MT8173_TEMP_MIN) && (temp <= MT8173_TEMP_MAX);
+}
+
+/**
+ * raw_to_mcelsius_v1 - convert a raw ADC value to mcelsius
+ * @mt: The thermal controller
+ * @sensno: sensor number
+ * @raw: raw ADC value
+ *
+ * This converts the raw ADC value to mcelsius using the SoC specific
+ * calibration constants
+ */
+static int raw_to_mcelsius_v1(struct mtk_thermal *mt, int sensno, s32 raw)
+{
+ s32 tmp;
+
+ raw &= 0xfff;
+
+ tmp = 203450520 << 3;
+ tmp /= mt->conf->cali_val + mt->o_slope;
+ tmp /= 10000 + mt->adc_ge;
+ tmp *= raw - mt->vts[sensno] - 3350;
+ tmp >>= 3;
+
+ return mt->degc_cali * 500 - tmp;
+}
+
+static int raw_to_mcelsius_v2(struct mtk_thermal *mt, int sensno, s32 raw)
+{
+ s32 format_1;
+ s32 format_2;
+ s32 g_oe;
+ s32 g_gain;
+ s32 g_x_roomt;
+ s32 tmp;
+
+ if (raw == 0)
+ return 0;
+
+ raw &= 0xfff;
+ g_gain = 10000 + (((mt->adc_ge - 512) * 10000) >> 12);
+ g_oe = mt->adc_oe - 512;
+ format_1 = mt->vts[VTS2] + 3105 - g_oe;
+ format_2 = (mt->degc_cali * 10) >> 1;
+ g_x_roomt = (((format_1 * 10000) >> 12) * 10000) / g_gain;
+
+ tmp = (((((raw - g_oe) * 10000) >> 12) * 10000) / g_gain) - g_x_roomt;
+ tmp = tmp * 10 * 100 / 11;
+
+ if (mt->o_slope_sign == 0)
+ tmp = tmp / (165 - mt->o_slope);
+ else
+ tmp = tmp / (165 + mt->o_slope);
+
+ return (format_2 - tmp) * 100;
+}
+
+static int raw_to_mcelsius_v3(struct mtk_thermal *mt, int sensno, s32 raw)
+{
+ s32 tmp;
+
+ if (raw == 0)
+ return 0;
+
+ raw &= 0xfff;
+ tmp = 100000 * 15 / 16 * 10000;
+ tmp /= 4096 - 512 + mt->adc_ge;
+ tmp /= 1490;
+ tmp *= raw - mt->vts[sensno] - 2900;
+
+ return mt->degc_cali * 500 - tmp;
+}
+
+/**
+ * mtk_thermal_get_bank - get bank
+ * @bank: The bank
+ *
+ * The bank registers are banked, we have to select a bank in the
+ * PTPCORESEL register to access it.
+ */
+static void mtk_thermal_get_bank(struct mtk_thermal_bank *bank)
+{
+ struct mtk_thermal *mt = bank->mt;
+ u32 val;
+
+ if (mt->conf->need_switch_bank) {
+ mutex_lock(&mt->lock);
+
+ val = readl(mt->thermal_base + PTPCORESEL);
+ val &= ~0xf;
+ val |= bank->id;
+ writel(val, mt->thermal_base + PTPCORESEL);
+ }
+}
+
+/**
+ * mtk_thermal_put_bank - release bank
+ * @bank: The bank
+ *
+ * release a bank previously taken with mtk_thermal_get_bank,
+ */
+static void mtk_thermal_put_bank(struct mtk_thermal_bank *bank)
+{
+ struct mtk_thermal *mt = bank->mt;
+
+ if (mt->conf->need_switch_bank)
+ mutex_unlock(&mt->lock);
+}
+
+/**
+ * mtk_thermal_bank_temperature - get the temperature of a bank
+ * @bank: The bank
+ *
+ * The temperature of a bank is considered the maximum temperature of
+ * the sensors associated to the bank.
+ */
+static int mtk_thermal_bank_temperature(struct mtk_thermal_bank *bank)
+{
+ struct mtk_thermal *mt = bank->mt;
+ const struct mtk_thermal_data *conf = mt->conf;
+ int i, temp = INT_MIN, max = INT_MIN;
+ u32 raw;
+
+ for (i = 0; i < conf->bank_data[bank->id].num_sensors; i++) {
+ raw = readl(mt->thermal_base + conf->msr[i]);
+
+ temp = mt->raw_to_mcelsius(
+ mt, conf->bank_data[bank->id].sensors[i], raw);
+
+ /*
+ * Depending on the filt/sen intervals and ADC polling time,
+ * we may need up to 60 milliseconds after initialization: this
+ * will result in the first reading containing an out of range
+ * temperature value.
+ * Validate the reading to both address the aforementioned issue
+ * and to eventually avoid bogus readings during runtime in the
+ * event that the AUXADC gets unstable due to high EMI, etc.
+ */
+ if (!mtk_thermal_temp_is_valid(temp))
+ temp = THERMAL_TEMP_INVALID;
+
+ if (temp > max)
+ max = temp;
+ }
+
+ return max;
+}
+
+static int mtk_read_temp(struct thermal_zone_device *tz, int *temperature)
+{
+ struct mtk_thermal *mt = thermal_zone_device_priv(tz);
+ int i;
+ int tempmax = INT_MIN;
+
+ for (i = 0; i < mt->conf->num_banks; i++) {
+ struct mtk_thermal_bank *bank = &mt->banks[i];
+
+ mtk_thermal_get_bank(bank);
+
+ tempmax = max(tempmax, mtk_thermal_bank_temperature(bank));
+
+ mtk_thermal_put_bank(bank);
+ }
+
+ *temperature = tempmax;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops mtk_thermal_ops = {
+ .get_temp = mtk_read_temp,
+};
+
+static void mtk_thermal_init_bank(struct mtk_thermal *mt, int num,
+ u32 apmixed_phys_base, u32 auxadc_phys_base,
+ int ctrl_id)
+{
+ struct mtk_thermal_bank *bank = &mt->banks[num];
+ const struct mtk_thermal_data *conf = mt->conf;
+ int i;
+
+ int offset = mt->conf->controller_offset[ctrl_id];
+ void __iomem *controller_base = mt->thermal_base + offset;
+
+ bank->id = num;
+ bank->mt = mt;
+
+ mtk_thermal_get_bank(bank);
+
+ /* bus clock 66M counting unit is 12 * 15.15ns * 256 = 46.540us */
+ writel(TEMP_MONCTL1_PERIOD_UNIT(12), controller_base + TEMP_MONCTL1);
+
+ /*
+ * filt interval is 1 * 46.540us = 46.54us,
+ * sen interval is 429 * 46.540us = 19.96ms
+ */
+ writel(TEMP_MONCTL2_FILTER_INTERVAL(1) |
+ TEMP_MONCTL2_SENSOR_INTERVAL(429),
+ controller_base + TEMP_MONCTL2);
+
+ /* poll is set to 10u */
+ writel(TEMP_AHBPOLL_ADC_POLL_INTERVAL(768),
+ controller_base + TEMP_AHBPOLL);
+
+ /* temperature sampling control, 1 sample */
+ writel(0x0, controller_base + TEMP_MSRCTL0);
+
+ /* exceed this polling time, IRQ would be inserted */
+ writel(0xffffffff, controller_base + TEMP_AHBTO);
+
+ /* number of interrupts per event, 1 is enough */
+ writel(0x0, controller_base + TEMP_MONIDET0);
+ writel(0x0, controller_base + TEMP_MONIDET1);
+
+ /*
+ * The MT8173 thermal controller does not have its own ADC. Instead it
+ * uses AHB bus accesses to control the AUXADC. To do this the thermal
+ * controller has to be programmed with the physical addresses of the
+ * AUXADC registers and with the various bit positions in the AUXADC.
+ * Also the thermal controller controls a mux in the APMIXEDSYS register
+ * space.
+ */
+
+ /*
+ * this value will be stored to TEMP_PNPMUXADDR (TEMP_SPARE0)
+ * automatically by hw
+ */
+ writel(BIT(conf->auxadc_channel), controller_base + TEMP_ADCMUX);
+
+ /* AHB address for auxadc mux selection */
+ writel(auxadc_phys_base + AUXADC_CON1_CLR_V,
+ controller_base + TEMP_ADCMUXADDR);
+
+ if (mt->conf->version == MTK_THERMAL_V1) {
+ /* AHB address for pnp sensor mux selection */
+ writel(apmixed_phys_base + APMIXED_SYS_TS_CON1,
+ controller_base + TEMP_PNPMUXADDR);
+ }
+
+ /* AHB value for auxadc enable */
+ writel(BIT(conf->auxadc_channel), controller_base + TEMP_ADCEN);
+
+ /* AHB address for auxadc enable (channel 0 immediate mode selected) */
+ writel(auxadc_phys_base + AUXADC_CON1_SET_V,
+ controller_base + TEMP_ADCENADDR);
+
+ /* AHB address for auxadc valid bit */
+ writel(auxadc_phys_base + AUXADC_DATA(conf->auxadc_channel),
+ controller_base + TEMP_ADCVALIDADDR);
+
+ /* AHB address for auxadc voltage output */
+ writel(auxadc_phys_base + AUXADC_DATA(conf->auxadc_channel),
+ controller_base + TEMP_ADCVOLTADDR);
+
+ /* read valid & voltage are at the same register */
+ writel(0x0, controller_base + TEMP_RDCTRL);
+
+ /* indicate where the valid bit is */
+ writel(TEMP_ADCVALIDMASK_VALID_HIGH | TEMP_ADCVALIDMASK_VALID_POS(12),
+ controller_base + TEMP_ADCVALIDMASK);
+
+ /* no shift */
+ writel(0x0, controller_base + TEMP_ADCVOLTAGESHIFT);
+
+ /* enable auxadc mux write transaction */
+ writel(TEMP_ADCWRITECTRL_ADC_MUX_WRITE,
+ controller_base + TEMP_ADCWRITECTRL);
+
+ for (i = 0; i < conf->bank_data[num].num_sensors; i++)
+ writel(conf->sensor_mux_values[conf->bank_data[num].sensors[i]],
+ mt->thermal_base + conf->adcpnp[i]);
+
+ writel((1 << conf->bank_data[num].num_sensors) - 1,
+ controller_base + TEMP_MONCTL0);
+
+ writel(TEMP_ADCWRITECTRL_ADC_PNP_WRITE |
+ TEMP_ADCWRITECTRL_ADC_MUX_WRITE,
+ controller_base + TEMP_ADCWRITECTRL);
+
+ mtk_thermal_put_bank(bank);
+}
+
+static u64 of_get_phys_base(struct device_node *np)
+{
+ struct resource res;
+
+ if (of_address_to_resource(np, 0, &res))
+ return OF_BAD_ADDR;
+
+ return res.start;
+}
+
+static int mtk_thermal_extract_efuse_v1(struct mtk_thermal *mt, u32 *buf)
+{
+ int i;
+
+ if (!(buf[0] & CALIB_BUF0_VALID_V1))
+ return -EINVAL;
+
+ mt->adc_ge = CALIB_BUF1_ADC_GE_V1(buf[1]);
+
+ for (i = 0; i < mt->conf->num_sensors; i++) {
+ switch (mt->conf->vts_index[i]) {
+ case VTS1:
+ mt->vts[VTS1] = CALIB_BUF0_VTS_TS1_V1(buf[0]);
+ break;
+ case VTS2:
+ mt->vts[VTS2] = CALIB_BUF0_VTS_TS2_V1(buf[0]);
+ break;
+ case VTS3:
+ mt->vts[VTS3] = CALIB_BUF1_VTS_TS3_V1(buf[1]);
+ break;
+ case VTS4:
+ mt->vts[VTS4] = CALIB_BUF2_VTS_TS4_V1(buf[2]);
+ break;
+ case VTS5:
+ mt->vts[VTS5] = CALIB_BUF2_VTS_TS5_V1(buf[2]);
+ break;
+ case VTSABB:
+ mt->vts[VTSABB] =
+ CALIB_BUF2_VTS_TSABB_V1(buf[2]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ mt->degc_cali = CALIB_BUF0_DEGC_CALI_V1(buf[0]);
+ if (CALIB_BUF1_ID_V1(buf[1]) &
+ CALIB_BUF0_O_SLOPE_SIGN_V1(buf[0]))
+ mt->o_slope = -CALIB_BUF0_O_SLOPE_V1(buf[0]);
+ else
+ mt->o_slope = CALIB_BUF0_O_SLOPE_V1(buf[0]);
+
+ return 0;
+}
+
+static int mtk_thermal_extract_efuse_v2(struct mtk_thermal *mt, u32 *buf)
+{
+ if (!CALIB_BUF1_VALID_V2(buf[1]))
+ return -EINVAL;
+
+ mt->adc_oe = CALIB_BUF0_ADC_OE_V2(buf[0]);
+ mt->adc_ge = CALIB_BUF0_ADC_GE_V2(buf[0]);
+ mt->degc_cali = CALIB_BUF0_DEGC_CALI_V2(buf[0]);
+ mt->o_slope = CALIB_BUF0_O_SLOPE_V2(buf[0]);
+ mt->vts[VTS1] = CALIB_BUF1_VTS_TS1_V2(buf[1]);
+ mt->vts[VTS2] = CALIB_BUF1_VTS_TS2_V2(buf[1]);
+ mt->vts[VTSABB] = CALIB_BUF1_VTS_TSABB_V2(buf[1]);
+ mt->o_slope_sign = CALIB_BUF1_O_SLOPE_SIGN_V2(buf[1]);
+
+ return 0;
+}
+
+static int mtk_thermal_extract_efuse_v3(struct mtk_thermal *mt, u32 *buf)
+{
+ if (!CALIB_BUF1_VALID_V3(buf[1]))
+ return -EINVAL;
+
+ mt->adc_ge = CALIB_BUF0_ADC_GE_V3(buf[0]);
+ mt->degc_cali = CALIB_BUF0_DEGC_CALI_V3(buf[0]);
+ mt->o_slope = CALIB_BUF0_O_SLOPE_V3(buf[0]);
+ mt->vts[VTS1] = CALIB_BUF1_VTS_TS1_V3(buf[1]);
+ mt->vts[VTS2] = CALIB_BUF1_VTS_TS2_V3(buf[1]);
+ mt->vts[VTSABB] = CALIB_BUF1_VTS_TSABB_V3(buf[1]);
+ mt->o_slope_sign = CALIB_BUF1_O_SLOPE_SIGN_V3(buf[1]);
+
+ if (CALIB_BUF1_ID_V3(buf[1]) == 0)
+ mt->o_slope = 0;
+
+ return 0;
+}
+
+static int mtk_thermal_get_calibration_data(struct device *dev,
+ struct mtk_thermal *mt)
+{
+ struct nvmem_cell *cell;
+ u32 *buf;
+ size_t len;
+ int i, ret = 0;
+
+ /* Start with default values */
+ mt->adc_ge = 512;
+ mt->adc_oe = 512;
+ for (i = 0; i < mt->conf->num_sensors; i++)
+ mt->vts[i] = 260;
+ mt->degc_cali = 40;
+ mt->o_slope = 0;
+
+ cell = nvmem_cell_get(dev, "calibration-data");
+ if (IS_ERR(cell)) {
+ if (PTR_ERR(cell) == -EPROBE_DEFER)
+ return PTR_ERR(cell);
+ return 0;
+ }
+
+ buf = (u32 *)nvmem_cell_read(cell, &len);
+
+ nvmem_cell_put(cell);
+
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ if (len < 3 * sizeof(u32)) {
+ dev_warn(dev, "invalid calibration data\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ switch (mt->conf->version) {
+ case MTK_THERMAL_V1:
+ ret = mtk_thermal_extract_efuse_v1(mt, buf);
+ break;
+ case MTK_THERMAL_V2:
+ ret = mtk_thermal_extract_efuse_v2(mt, buf);
+ break;
+ case MTK_THERMAL_V3:
+ ret = mtk_thermal_extract_efuse_v3(mt, buf);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret) {
+ dev_info(dev, "Device not calibrated, using default calibration values\n");
+ ret = 0;
+ }
+
+out:
+ kfree(buf);
+
+ return ret;
+}
+
+static const struct of_device_id mtk_thermal_of_match[] = {
+ {
+ .compatible = "mediatek,mt8173-thermal",
+ .data = (void *)&mt8173_thermal_data,
+ },
+ {
+ .compatible = "mediatek,mt2701-thermal",
+ .data = (void *)&mt2701_thermal_data,
+ },
+ {
+ .compatible = "mediatek,mt2712-thermal",
+ .data = (void *)&mt2712_thermal_data,
+ },
+ {
+ .compatible = "mediatek,mt7622-thermal",
+ .data = (void *)&mt7622_thermal_data,
+ },
+ {
+ .compatible = "mediatek,mt7986-thermal",
+ .data = (void *)&mt7986_thermal_data,
+ },
+ {
+ .compatible = "mediatek,mt8183-thermal",
+ .data = (void *)&mt8183_thermal_data,
+ },
+ {
+ .compatible = "mediatek,mt8365-thermal",
+ .data = (void *)&mt8365_thermal_data,
+ }, {
+ },
+};
+MODULE_DEVICE_TABLE(of, mtk_thermal_of_match);
+
+static void mtk_thermal_turn_on_buffer(struct mtk_thermal *mt,
+ void __iomem *apmixed_base)
+{
+ u32 tmp;
+
+ if (!mt->conf->apmixed_buffer_ctl_reg)
+ return;
+
+ tmp = readl(apmixed_base + mt->conf->apmixed_buffer_ctl_reg);
+ tmp &= mt->conf->apmixed_buffer_ctl_mask;
+ tmp |= mt->conf->apmixed_buffer_ctl_set;
+ writel(tmp, apmixed_base + mt->conf->apmixed_buffer_ctl_reg);
+ udelay(200);
+}
+
+static void mtk_thermal_release_periodic_ts(struct mtk_thermal *mt,
+ void __iomem *auxadc_base)
+{
+ int tmp;
+
+ writel(0x800, auxadc_base + AUXADC_CON1_SET_V);
+ writel(0x1, mt->thermal_base + TEMP_MONCTL0);
+ tmp = readl(mt->thermal_base + TEMP_MSRCTL1);
+ writel((tmp & (~0x10e)), mt->thermal_base + TEMP_MSRCTL1);
+}
+
+static int mtk_thermal_probe(struct platform_device *pdev)
+{
+ int ret, i, ctrl_id;
+ struct device_node *auxadc, *apmixedsys, *np = pdev->dev.of_node;
+ struct mtk_thermal *mt;
+ u64 auxadc_phys_base, apmixed_phys_base;
+ struct thermal_zone_device *tzdev;
+ void __iomem *apmixed_base, *auxadc_base;
+
+ mt = devm_kzalloc(&pdev->dev, sizeof(*mt), GFP_KERNEL);
+ if (!mt)
+ return -ENOMEM;
+
+ mt->conf = of_device_get_match_data(&pdev->dev);
+
+ mt->thermal_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(mt->thermal_base))
+ return PTR_ERR(mt->thermal_base);
+
+ ret = mtk_thermal_get_calibration_data(&pdev->dev, mt);
+ if (ret)
+ return ret;
+
+ mutex_init(&mt->lock);
+
+ mt->dev = &pdev->dev;
+
+ auxadc = of_parse_phandle(np, "mediatek,auxadc", 0);
+ if (!auxadc) {
+ dev_err(&pdev->dev, "missing auxadc node\n");
+ return -ENODEV;
+ }
+
+ auxadc_base = of_iomap(auxadc, 0);
+ auxadc_phys_base = of_get_phys_base(auxadc);
+
+ of_node_put(auxadc);
+
+ if (auxadc_phys_base == OF_BAD_ADDR) {
+ dev_err(&pdev->dev, "Can't get auxadc phys address\n");
+ return -EINVAL;
+ }
+
+ apmixedsys = of_parse_phandle(np, "mediatek,apmixedsys", 0);
+ if (!apmixedsys) {
+ dev_err(&pdev->dev, "missing apmixedsys node\n");
+ return -ENODEV;
+ }
+
+ apmixed_base = of_iomap(apmixedsys, 0);
+ apmixed_phys_base = of_get_phys_base(apmixedsys);
+
+ of_node_put(apmixedsys);
+
+ if (apmixed_phys_base == OF_BAD_ADDR) {
+ dev_err(&pdev->dev, "Can't get auxadc phys address\n");
+ return -EINVAL;
+ }
+
+ ret = device_reset_optional(&pdev->dev);
+ if (ret)
+ return ret;
+
+ mt->clk_auxadc = devm_clk_get_enabled(&pdev->dev, "auxadc");
+ if (IS_ERR(mt->clk_auxadc)) {
+ ret = PTR_ERR(mt->clk_auxadc);
+ dev_err(&pdev->dev, "Can't enable auxadc clk: %d\n", ret);
+ return ret;
+ }
+
+ mt->clk_peri_therm = devm_clk_get_enabled(&pdev->dev, "therm");
+ if (IS_ERR(mt->clk_peri_therm)) {
+ ret = PTR_ERR(mt->clk_peri_therm);
+ dev_err(&pdev->dev, "Can't enable peri clk: %d\n", ret);
+ return ret;
+ }
+
+ mtk_thermal_turn_on_buffer(mt, apmixed_base);
+
+ if (mt->conf->version != MTK_THERMAL_V1)
+ mtk_thermal_release_periodic_ts(mt, auxadc_base);
+
+ if (mt->conf->version == MTK_THERMAL_V1)
+ mt->raw_to_mcelsius = raw_to_mcelsius_v1;
+ else if (mt->conf->version == MTK_THERMAL_V2)
+ mt->raw_to_mcelsius = raw_to_mcelsius_v2;
+ else
+ mt->raw_to_mcelsius = raw_to_mcelsius_v3;
+
+ for (ctrl_id = 0; ctrl_id < mt->conf->num_controller ; ctrl_id++)
+ for (i = 0; i < mt->conf->num_banks; i++)
+ mtk_thermal_init_bank(mt, i, apmixed_phys_base,
+ auxadc_phys_base, ctrl_id);
+
+ tzdev = devm_thermal_of_zone_register(&pdev->dev, 0, mt,
+ &mtk_thermal_ops);
+ if (IS_ERR(tzdev))
+ return PTR_ERR(tzdev);
+
+ ret = devm_thermal_add_hwmon_sysfs(&pdev->dev, tzdev);
+ if (ret)
+ dev_warn(&pdev->dev, "error in thermal_add_hwmon_sysfs");
+
+ return 0;
+}
+
+static struct platform_driver mtk_thermal_driver = {
+ .probe = mtk_thermal_probe,
+ .driver = {
+ .name = "mtk-thermal",
+ .of_match_table = mtk_thermal_of_match,
+ },
+};
+
+module_platform_driver(mtk_thermal_driver);
+
+MODULE_AUTHOR("Michael Kao <michael.kao@mediatek.com>");
+MODULE_AUTHOR("Louis Yu <louis.yu@mediatek.com>");
+MODULE_AUTHOR("Dawei Chien <dawei.chien@mediatek.com>");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_AUTHOR("Hanyi Wu <hanyi.wu@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/mediatek/lvts_thermal.c b/drivers/thermal/mediatek/lvts_thermal.c
new file mode 100644
index 000000000..52f99f3e6
--- /dev/null
+++ b/drivers/thermal/mediatek/lvts_thermal.c
@@ -0,0 +1,2220 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 MediaTek Inc.
+ * Author: Balsam CHIHI <bchihi@baylibre.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/debugfs.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/thermal.h>
+#include <dt-bindings/thermal/mediatek,lvts-thermal.h>
+
+#include "../thermal_hwmon.h"
+
+#define LVTS_MONCTL0(__base) (__base + 0x0000)
+#define LVTS_MONCTL1(__base) (__base + 0x0004)
+#define LVTS_MONCTL2(__base) (__base + 0x0008)
+#define LVTS_MONINT(__base) (__base + 0x000C)
+#define LVTS_MONINTSTS(__base) (__base + 0x0010)
+#define LVTS_MONIDET0(__base) (__base + 0x0014)
+#define LVTS_MONIDET1(__base) (__base + 0x0018)
+#define LVTS_MONIDET2(__base) (__base + 0x001C)
+#define LVTS_MONIDET3(__base) (__base + 0x0020)
+#define LVTS_H2NTHRE(__base) (__base + 0x0024)
+#define LVTS_HTHRE(__base) (__base + 0x0028)
+#define LVTS_OFFSETH(__base) (__base + 0x0030)
+#define LVTS_OFFSETL(__base) (__base + 0x0034)
+#define LVTS_MSRCTL0(__base) (__base + 0x0038)
+#define LVTS_MSRCTL1(__base) (__base + 0x003C)
+#define LVTS_TSSEL(__base) (__base + 0x0040)
+#define LVTS_CALSCALE(__base) (__base + 0x0048)
+#define LVTS_ID(__base) (__base + 0x004C)
+#define LVTS_CONFIG(__base) (__base + 0x0050)
+#define LVTS_EDATA00(__base) (__base + 0x0054)
+#define LVTS_EDATA01(__base) (__base + 0x0058)
+#define LVTS_EDATA02(__base) (__base + 0x005C)
+#define LVTS_EDATA03(__base) (__base + 0x0060)
+#define LVTS_MSROFT(__base) (__base + 0x006C)
+#define LVTS_ATP0(__base) (__base + 0x0070)
+#define LVTS_ATP1(__base) (__base + 0x0074)
+#define LVTS_ATP2(__base) (__base + 0x0078)
+#define LVTS_ATP3(__base) (__base + 0x007C)
+#define LVTS_MSR0(__base) (__base + 0x0090)
+#define LVTS_MSR1(__base) (__base + 0x0094)
+#define LVTS_MSR2(__base) (__base + 0x0098)
+#define LVTS_MSR3(__base) (__base + 0x009C)
+#define LVTS_IMMD0(__base) (__base + 0x00A0)
+#define LVTS_IMMD1(__base) (__base + 0x00A4)
+#define LVTS_IMMD2(__base) (__base + 0x00A8)
+#define LVTS_IMMD3(__base) (__base + 0x00AC)
+#define LVTS_PROTCTL(__base) (__base + 0x00C0)
+#define LVTS_PROTTA(__base) (__base + 0x00C4)
+#define LVTS_PROTTB(__base) (__base + 0x00C8)
+#define LVTS_PROTTC(__base) (__base + 0x00CC)
+#define LVTS_CLKEN(__base) (__base + 0x00E4)
+
+#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_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
+#define LVTS_INT_SENSOR2 0x00247C00
+#define LVTS_INT_SENSOR3 0x1FC00000
+
+#define LVTS_SENSOR_MAX 4
+#define LVTS_GOLDEN_TEMP_MAX 62
+#define LVTS_GOLDEN_TEMP_DEFAULT 50
+#define LVTS_COEFF_A_MT8195 -250460
+#define LVTS_COEFF_B_MT8195 250460
+#define LVTS_COEFF_A_MT7987 -204650
+#define LVTS_COEFF_B_MT7987 204650
+#define LVTS_COEFF_A_MT7988 -204650
+#define LVTS_COEFF_B_MT7988 204650
+#define LVTS_COEFF_A_MT8196 391460
+#define LVTS_COEFF_B_MT8196 -391460
+
+#define LVTS_MSR_OFFSET_MT8196 -984
+
+#define LVTS_MSR_READ_TIMEOUT_US 400
+#define LVTS_MSR_READ_WAIT_US (LVTS_MSR_READ_TIMEOUT_US / 2)
+
+#define LVTS_MINIMUM_THRESHOLD 20000
+
+#define LVTS_MAX_CAL_OFFSETS 3
+#define LVTS_NUM_CAL_OFFSETS_MT7988 3
+#define LVTS_NUM_CAL_OFFSETS_MT8196 2
+
+static int golden_temp = LVTS_GOLDEN_TEMP_DEFAULT;
+static int golden_temp_offset;
+
+enum lvts_msr_mode {
+ LVTS_MSR_IMMEDIATE_MODE,
+ LVTS_MSR_FILTERED_MODE,
+ LVTS_MSR_ATP_MODE,
+};
+
+struct lvts_sensor_data {
+ int dt_id;
+ u8 cal_offsets[LVTS_MAX_CAL_OFFSETS];
+};
+
+struct lvts_ctrl_data {
+ struct lvts_sensor_data lvts_sensor[LVTS_SENSOR_MAX];
+ u8 valid_sensor_mask;
+ int offset;
+ enum lvts_msr_mode 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_platform_ops {
+ int (*lvts_raw_to_temp)(u32 raw_temp, int temp_factor);
+ u32 (*lvts_temp_to_raw)(int temperature, int temp_factor);
+};
+
+struct lvts_data {
+ const struct lvts_ctrl_data *lvts_ctrl;
+ const struct lvts_platform_ops *ops;
+ const u32 *conn_cmd;
+ const u32 *init_cmd;
+ int num_cal_offsets;
+ 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;
+ u16 msr_offset;
+};
+
+struct lvts_sensor {
+ struct thermal_zone_device *tz;
+ void __iomem *msr;
+ 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];
+ u8 valid_sensor_mask;
+ int mode;
+ void __iomem *base;
+ int low_thresh;
+ int high_thresh;
+};
+
+struct lvts_domain {
+ struct lvts_ctrl *lvts_ctrl;
+ struct reset_control *reset;
+ struct clk *clk;
+ int num_lvts_ctrl;
+ void __iomem *base;
+ size_t calib_len;
+ u8 *calib;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dom_dentry;
+#endif
+};
+
+#ifdef CONFIG_MTK_LVTS_THERMAL_DEBUGFS
+
+#define LVTS_DEBUG_FS_REGS(__reg) \
+{ \
+ .name = __stringify(__reg), \
+ .offset = __reg(0), \
+}
+
+static const struct debugfs_reg32 lvts_regs[] = {
+ LVTS_DEBUG_FS_REGS(LVTS_MONCTL0),
+ LVTS_DEBUG_FS_REGS(LVTS_MONCTL1),
+ LVTS_DEBUG_FS_REGS(LVTS_MONCTL2),
+ LVTS_DEBUG_FS_REGS(LVTS_MONINT),
+ LVTS_DEBUG_FS_REGS(LVTS_MONINTSTS),
+ LVTS_DEBUG_FS_REGS(LVTS_MONIDET0),
+ LVTS_DEBUG_FS_REGS(LVTS_MONIDET1),
+ LVTS_DEBUG_FS_REGS(LVTS_MONIDET2),
+ LVTS_DEBUG_FS_REGS(LVTS_MONIDET3),
+ LVTS_DEBUG_FS_REGS(LVTS_H2NTHRE),
+ LVTS_DEBUG_FS_REGS(LVTS_HTHRE),
+ LVTS_DEBUG_FS_REGS(LVTS_OFFSETH),
+ LVTS_DEBUG_FS_REGS(LVTS_OFFSETL),
+ LVTS_DEBUG_FS_REGS(LVTS_MSRCTL0),
+ LVTS_DEBUG_FS_REGS(LVTS_MSRCTL1),
+ LVTS_DEBUG_FS_REGS(LVTS_TSSEL),
+ LVTS_DEBUG_FS_REGS(LVTS_CALSCALE),
+ LVTS_DEBUG_FS_REGS(LVTS_ID),
+ LVTS_DEBUG_FS_REGS(LVTS_CONFIG),
+ LVTS_DEBUG_FS_REGS(LVTS_EDATA00),
+ LVTS_DEBUG_FS_REGS(LVTS_EDATA01),
+ LVTS_DEBUG_FS_REGS(LVTS_EDATA02),
+ LVTS_DEBUG_FS_REGS(LVTS_EDATA03),
+ LVTS_DEBUG_FS_REGS(LVTS_MSROFT),
+ LVTS_DEBUG_FS_REGS(LVTS_ATP0),
+ LVTS_DEBUG_FS_REGS(LVTS_ATP1),
+ LVTS_DEBUG_FS_REGS(LVTS_ATP2),
+ LVTS_DEBUG_FS_REGS(LVTS_ATP3),
+ LVTS_DEBUG_FS_REGS(LVTS_MSR0),
+ LVTS_DEBUG_FS_REGS(LVTS_MSR1),
+ LVTS_DEBUG_FS_REGS(LVTS_MSR2),
+ LVTS_DEBUG_FS_REGS(LVTS_MSR3),
+ LVTS_DEBUG_FS_REGS(LVTS_IMMD0),
+ LVTS_DEBUG_FS_REGS(LVTS_IMMD1),
+ LVTS_DEBUG_FS_REGS(LVTS_IMMD2),
+ LVTS_DEBUG_FS_REGS(LVTS_IMMD3),
+ LVTS_DEBUG_FS_REGS(LVTS_PROTCTL),
+ LVTS_DEBUG_FS_REGS(LVTS_PROTTA),
+ LVTS_DEBUG_FS_REGS(LVTS_PROTTB),
+ LVTS_DEBUG_FS_REGS(LVTS_PROTTC),
+ 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;
+ struct lvts_ctrl *lvts_ctrl;
+ struct dentry *dentry;
+ char name[64];
+ int i;
+
+ lvts_td->dom_dentry = debugfs_create_dir(dev_name(dev), NULL);
+ if (IS_ERR(lvts_td->dom_dentry))
+ return 0;
+
+ for (i = 0; i < lvts_td->num_lvts_ctrl; i++) {
+
+ lvts_ctrl = &lvts_td->lvts_ctrl[i];
+
+ sprintf(name, "controller%d", i);
+ dentry = debugfs_create_dir(name, lvts_td->dom_dentry);
+ if (IS_ERR(dentry))
+ continue;
+
+ regset = devm_kzalloc(dev, sizeof(*regset), GFP_KERNEL);
+ if (!regset)
+ continue;
+
+ regset->base = lvts_ctrl->base;
+ regset->regs = lvts_regs;
+ regset->nregs = ARRAY_SIZE(lvts_regs);
+
+ debugfs_create_regset32("registers", 0400, dentry, regset);
+ }
+
+ return devm_add_action_or_reset(dev, lvts_debugfs_exit, lvts_td);
+}
+
+#else
+
+static inline int lvts_debugfs_init(struct device *dev,
+ struct lvts_domain *lvts_td)
+{
+ return 0;
+}
+
+#endif
+
+static int lvts_raw_to_temp(u32 raw_temp, const struct lvts_data *lvts_data)
+{
+ return lvts_data->ops->lvts_raw_to_temp(raw_temp & 0xFFFF, lvts_data->temp_factor);
+}
+
+static u32 lvts_temp_to_raw(int temperature, const struct lvts_data *lvts_data)
+{
+ return lvts_data->ops->lvts_temp_to_raw(temperature, lvts_data->temp_factor);
+}
+
+static int lvts_raw_to_temp_mt7988(u32 raw_temp, int temp_factor)
+{
+ int temperature;
+
+ temperature = ((s64)(raw_temp & 0xFFFF) * temp_factor) >> 14;
+ temperature += golden_temp_offset;
+
+ return temperature;
+}
+
+static u32 lvts_temp_to_raw_mt7988(int temperature, int temp_factor)
+{
+ u32 raw_temp = ((s64)(golden_temp_offset - temperature)) << 14;
+
+ raw_temp = div_s64(raw_temp, -temp_factor);
+
+ return raw_temp;
+}
+
+static u32 lvts_temp_to_raw_mt8196(int temperature, int temp_factor)
+{
+ u32 raw_temp;
+
+ raw_temp = temperature - golden_temp_offset;
+
+ return div_s64((s64)temp_factor << 14, raw_temp);
+}
+
+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:
+ *
+ * LVTS_MSR[0-3] / LVTS_IMMD[0-3]
+ *
+ * Bits:
+ *
+ * 32-17: Unused
+ * 16 : Valid temperature
+ * 15-0 : Raw temperature
+ */
+ 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
+ * hardware sensor is fully initialized, we have to check the
+ * validity of the temperature returned when reading the
+ * measurement register. The thermal controller will set the
+ * valid bit temperature only when it is totally initialized.
+ *
+ * Otherwise, we may end up with garbage values out of the
+ * functionning temperature and directly jump to a system
+ * shutdown.
+ */
+ if (rc)
+ return -EAGAIN;
+
+ *temp = lvts_raw_to_temp(value, lvts_data);
+
+ 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 != -INT_MAX ? low : LVTS_MINIMUM_THRESHOLD,
+ lvts_data);
+ u32 raw_high = lvts_temp_to_raw(high, lvts_data);
+ 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;
+
+ /*
+ * Low offset temperature threshold
+ *
+ * LVTS_OFFSETL
+ *
+ * Bits:
+ *
+ * 14-0 : Raw temperature for threshold
+ */
+ pr_debug("%s: Setting low limit temperature interrupt: %d\n",
+ thermal_zone_device_type(tz), low);
+ writel(raw_low, LVTS_OFFSETL(base));
+
+ /*
+ * High offset temperature threshold
+ *
+ * LVTS_OFFSETH
+ *
+ * Bits:
+ *
+ * 14-0 : Raw temperature for threshold
+ */
+ pr_debug("%s: Setting high limit temperature interrupt: %d\n",
+ thermal_zone_device_type(tz), high);
+ writel(raw_high, LVTS_OFFSETH(base));
+
+ return 0;
+}
+
+static irqreturn_t lvts_ctrl_irq_handler(struct lvts_ctrl *lvts_ctrl)
+{
+ irqreturn_t iret = IRQ_NONE;
+ u32 value;
+ static const u32 masks[] = {
+ LVTS_INT_SENSOR0,
+ LVTS_INT_SENSOR1,
+ LVTS_INT_SENSOR2,
+ LVTS_INT_SENSOR3
+ };
+ int i;
+
+ /*
+ * Interrupt monitoring status
+ *
+ * LVTS_MONINTST
+ *
+ * Bits:
+ *
+ * 31 : Interrupt for stage 3
+ * 30 : Interrupt for stage 2
+ * 29 : Interrupt for state 1
+ * 28 : Interrupt using filter on sensor 3
+ *
+ * 27 : Interrupt using immediate on sensor 3
+ * 26 : Interrupt normal to hot on sensor 3
+ * 25 : Interrupt high offset on sensor 3
+ * 24 : Interrupt low offset on sensor 3
+ *
+ * 23 : Interrupt hot threshold on sensor 3
+ * 22 : Interrupt cold threshold on sensor 3
+ * 21 : Interrupt using filter on sensor 2
+ * 20 : Interrupt using filter on sensor 1
+ *
+ * 19 : Interrupt using filter on sensor 0
+ * 18 : Interrupt using immediate on sensor 2
+ * 17 : Interrupt using immediate on sensor 1
+ * 16 : Interrupt using immediate on sensor 0
+ *
+ * 15 : Interrupt device access timeout interrupt
+ * 14 : Interrupt normal to hot on sensor 2
+ * 13 : Interrupt high offset interrupt on sensor 2
+ * 12 : Interrupt low offset interrupt on sensor 2
+ *
+ * 11 : Interrupt hot threshold on sensor 2
+ * 10 : Interrupt cold threshold on sensor 2
+ * 9 : Interrupt normal to hot on sensor 1
+ * 8 : Interrupt high offset interrupt on sensor 1
+ *
+ * 7 : Interrupt low offset interrupt on sensor 1
+ * 6 : Interrupt hot threshold on sensor 1
+ * 5 : Interrupt cold threshold on sensor 1
+ * 4 : Interrupt normal to hot on sensor 0
+ *
+ * 3 : Interrupt high offset interrupt on sensor 0
+ * 2 : Interrupt low offset interrupt on sensor 0
+ * 1 : Interrupt hot threshold on sensor 0
+ * 0 : Interrupt cold threshold on sensor 0
+ *
+ * We are interested in the sensor(s) responsible of the
+ * interrupt event. We update the thermal framework with the
+ * thermal zone associated with the sensor. The framework will
+ * take care of the rest whatever the kind of interrupt, we
+ * are only interested in which sensor raised the interrupt.
+ *
+ * sensor 3 interrupt: 0001 1111 1100 0000 0000 0000 0000 0000
+ * => 0x1FC00000
+ * sensor 2 interrupt: 0000 0000 0010 0100 0111 1100 0000 0000
+ * => 0x00247C00
+ * sensor 1 interrupt: 0000 0000 0001 0010 0000 0011 1110 0000
+ * => 0X001203E0
+ * sensor 0 interrupt: 0000 0000 0000 1001 0000 0000 0001 1111
+ * => 0x0009001F
+ */
+ value = readl(LVTS_MONINTSTS(lvts_ctrl->base));
+
+ /*
+ * Let's figure out which sensors raised the interrupt
+ *
+ * NOTE: the masks array must be ordered with the index
+ * corresponding to the sensor id eg. index=0, mask for
+ * sensor0.
+ */
+ for (i = 0; i < ARRAY_SIZE(masks); i++) {
+
+ if (!(value & masks[i]))
+ continue;
+
+ thermal_zone_device_update(lvts_ctrl->sensors[i].tz,
+ THERMAL_TRIP_VIOLATED);
+ iret = IRQ_HANDLED;
+ }
+
+ /*
+ * Write back to clear the interrupt status (W1C)
+ */
+ writel(value, LVTS_MONINTSTS(lvts_ctrl->base));
+
+ return iret;
+}
+
+/*
+ * Temperature interrupt handler. Even if the driver supports more
+ * interrupt modes, we use the interrupt when the temperature crosses
+ * the hot threshold the way up and the way down (modulo the
+ * hysteresis).
+ *
+ * Each thermal domain has a couple of interrupts, one for hardware
+ * reset and another one for all the thermal events happening on the
+ * different sensors.
+ *
+ * The interrupt is configured for thermal events when crossing the
+ * hot temperature limit. At each interrupt, we check in every
+ * controller if there is an interrupt pending.
+ */
+static irqreturn_t lvts_irq_handler(int irq, void *data)
+{
+ struct lvts_domain *lvts_td = data;
+ irqreturn_t aux, iret = IRQ_NONE;
+ int i;
+
+ for (i = 0; i < lvts_td->num_lvts_ctrl; i++) {
+
+ aux = lvts_ctrl_irq_handler(&lvts_td->lvts_ctrl[i]);
+ if (aux != IRQ_HANDLED)
+ continue;
+
+ iret = IRQ_HANDLED;
+ }
+
+ return iret;
+}
+
+static const struct thermal_zone_device_ops lvts_ops = {
+ .get_temp = lvts_get_temp,
+ .set_trips = lvts_set_trips,
+};
+
+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),
+ LVTS_MSR2(lvts_ctrl->base),
+ LVTS_MSR3(lvts_ctrl->base)
+ };
+
+ void __iomem *imm_regs[] = {
+ LVTS_IMMD0(lvts_ctrl->base),
+ LVTS_IMMD1(lvts_ctrl->base),
+ LVTS_IMMD2(lvts_ctrl->base),
+ LVTS_IMMD3(lvts_ctrl->base)
+ };
+
+ void __iomem *atp_regs[] = {
+ LVTS_ATP0(lvts_ctrl->base),
+ LVTS_ATP1(lvts_ctrl->base),
+ LVTS_ATP2(lvts_ctrl->base),
+ LVTS_ATP3(lvts_ctrl->base)
+ };
+
+ int i;
+
+ lvts_for_each_valid_sensor(i, lvts_ctrl_data) {
+
+ int dt_id = lvts_ctrl_data->lvts_sensor[i].dt_id;
+
+ /*
+ * At this point, we don't know which id matches which
+ * sensor. Let's set arbitrally the id from the index.
+ */
+ lvts_sensor[i].id = i;
+
+ /*
+ * The thermal zone registration will set the trip
+ * point interrupt in the thermal controller
+ * register. But this one will be reset in the
+ * initialization after. So we need to post pone the
+ * thermal zone creation after the controller is
+ * setup. For this reason, we store the device tree
+ * node id from the data in the sensor structure
+ */
+ lvts_sensor[i].dt_id = dt_id;
+
+ /*
+ * We assign the base address of the thermal
+ * controller as a back pointer. So it will be
+ * accessible from the different thermal framework ops
+ * as we pass the lvts_sensor pointer as thermal zone
+ * private data.
+ */
+ lvts_sensor[i].base = lvts_ctrl->base;
+
+ /*
+ * Each sensor has its own register address to read from.
+ */
+ switch (lvts_ctrl_data->mode) {
+ case LVTS_MSR_IMMEDIATE_MODE:
+ lvts_sensor[i].msr = imm_regs[i];
+ break;
+ case LVTS_MSR_FILTERED_MODE:
+ lvts_sensor[i].msr = msr_regs[i];
+ break;
+ case LVTS_MSR_ATP_MODE:
+ lvts_sensor[i].msr = atp_regs[i];
+ break;
+ default:
+ lvts_sensor[i].msr = imm_regs[i];
+ break;
+ }
+
+ 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;
+}
+
+static int lvts_decode_sensor_calibration(const struct lvts_sensor_data *sensor,
+ const u8 *efuse_calibration, u32 calib_len,
+ u8 num_offsets, u32 *calib)
+{
+ int i;
+ u32 calib_val = 0;
+
+ for (i = 0; i < num_offsets; i++) {
+ u8 offset = sensor->cal_offsets[i];
+
+ if (offset >= calib_len)
+ return -EINVAL;
+ // Pack each calibration byte into the correct position
+ calib_val |= efuse_calibration[offset] << (8 * i);
+ }
+
+ *calib = calib_val;
+ return 0;
+}
+
+/*
+ * The efuse blob values follows the sensor enumeration per thermal
+ * controller. The decoding of the stream is as follow:
+ *
+ * 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
+ *
+ * <-----mcu-tc#1-----> <-----sensor#2-----> <-----sensor#3----->
+ * 0x0A | 0x0B | 0x0C | 0x0D | 0x0E | 0x0F | 0x10 | 0x11 | 0x12
+ *
+ * <-----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 mt8195 :
+ *
+ * <-----ap--tc#0-----> <-----sensor#0-----> <-----sensor#1----->
+ * 0x22 | 0x23 | 0x24 | 0x25 | 0x26 | 0x27 | 0x28 | 0x29 | 0x2A
+ *
+ * <-----ap--tc#1-----> <-----sensor#2-----> <-----sensor#3----->
+ * 0x2B | 0x2C | 0x2D | 0x2E | 0x2F | 0x30 | 0x31 | 0x32 | 0x33
+ *
+ * <-----ap--tc#2-----> <-----sensor#4-----> <-----sensor#5-----> <-----sensor#6----->
+ * 0x34 | 0x35 | 0x36 | 0x37 | 0x38 | 0x39 | 0x3A | 0x3B | 0x3C | 0x3D | 0x3E | 0x3F
+ *
+ * <-----ap--tc#3-----> <-----sensor#7-----> <-----sensor#8----->
+ * 0x40 | 0x41 | 0x42 | 0x43 | 0x44 | 0x45 | 0x46 | 0x47 | 0x48
+ *
+ * MT8196 :
+ * Stream index map for MCU Domain mt8196 :
+ *
+ * <-sensor#1--> <-sensor#0--> <-sensor#3--> <-sensor#2-->
+ * 0x04 | 0x05 | 0x06 | 0x07 | 0x08 | 0x09 | 0x0A | 0x0B
+ *
+ * <-sensor#5--> <-sensor#4--> <-sensor#7--> <-sensor#6-->
+ * 0x0C | 0x0D | 0x0E | 0x0F | 0x10 | 0x11 | 0x12 | 0x13
+ *
+ * <-sensor#9--> <-sensor#8--> <-sensor#11-> <-sensor#10->
+ * 0x14 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 | 0x1A | 0X1B
+ *
+ * <-sensor#13-> <-sensor#12-> <-sensor#15-> <-sensor#14->
+ * 0x1C | 0x1D | 0x1E | 0x1F | 0x20 | 0x21 | 0x22 | 0x23
+ *
+ * Stream index map for APU Domain mt8196 :
+ *
+ * <-sensor#1--> <-sensor#0--> <-sensor#3--> <-sensor#2-->
+ * 0x24 | 0x25 | 0x26 | 0x27 | 0x28 | 0x29 | 0x2A | 0x2B
+ *
+ * Stream index map for GPU Domain mt8196 :
+ *
+ * <-sensor#1--> <-sensor#0-->
+ * 0x2C | 0x2D | 0x2E | 0x2F
+ *
+ * Stream index map for AP Domain mt8196 :
+ *
+ * <-sensor#1--> <-sensor#0--> <-sensor#3--> <-sensor#2-->
+ * 0x30 | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x36 | 0x37
+ *
+ * <-sensor#5--> <-sensor#4--> <-sensor#6--> <-sensor#7-->
+ * 0x38 | 0x39 | 0x3A | 0x3B | 0x3C | 0x3D | 0x3E | 0x3F
+ *
+ * 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,
+ size_t calib_len)
+{
+ const struct lvts_data *lvts_data = lvts_ctrl->lvts_data;
+ int i, ret;
+ u32 gt;
+
+ /* A zero value for gt means that device has invalid efuse data */
+ gt = (((u32 *)efuse_calibration)[0] >> 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];
+ u32 calib = 0;
+
+ ret = lvts_decode_sensor_calibration(sensor, efuse_calibration,
+ calib_len,
+ lvts_data->num_cal_offsets,
+ &calib);
+ if (ret)
+ return ret;
+
+ if (gt) {
+ lvts_ctrl->calibration[i] = calib;
+ if (lvts_ctrl->lvts_data->msr_offset)
+ lvts_ctrl->calibration[i] += lvts_ctrl->lvts_data->msr_offset;
+ } 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;
+}
+
+/*
+ * The efuse bytes stream can be split into different chunk of
+ * nvmems. This function reads and concatenate those into a single
+ * buffer so it can be read sequentially when initializing the
+ * calibration data.
+ */
+static int lvts_calibration_read(struct device *dev, struct lvts_domain *lvts_td,
+ const struct lvts_data *lvts_data)
+{
+ struct device_node *np = dev_of_node(dev);
+ struct nvmem_cell *cell;
+ struct property *prop;
+ const char *cell_name;
+
+ of_property_for_each_string(np, "nvmem-cell-names", prop, cell_name) {
+ size_t len;
+ u8 *efuse;
+
+ cell = of_nvmem_cell_get(np, cell_name);
+ if (IS_ERR(cell)) {
+ dev_err(dev, "Failed to get cell '%s'\n", cell_name);
+ return PTR_ERR(cell);
+ }
+
+ efuse = nvmem_cell_read(cell, &len);
+
+ nvmem_cell_put(cell);
+
+ if (IS_ERR(efuse)) {
+ dev_err(dev, "Failed to read cell '%s'\n", cell_name);
+ return PTR_ERR(efuse);
+ }
+
+ lvts_td->calib = devm_krealloc(dev, lvts_td->calib,
+ lvts_td->calib_len + len, GFP_KERNEL);
+ if (!lvts_td->calib) {
+ kfree(efuse);
+ return -ENOMEM;
+ }
+
+ memcpy(lvts_td->calib + lvts_td->calib_len, efuse, len);
+
+ lvts_td->calib_len += len;
+
+ kfree(efuse);
+ }
+
+ return 0;
+}
+
+static int lvts_golden_temp_init(struct device *dev, u8 *calib,
+ const struct lvts_data *lvts_data)
+{
+ u32 gt;
+
+ /*
+ * 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;
+
+ golden_temp_offset = golden_temp * 500 + lvts_data->temp_offset;
+
+ dev_info(dev, "%sgolden temp=%d\n", gt ? "" : "fake ", golden_temp);
+
+ return 0;
+}
+
+static int lvts_ctrl_init(struct device *dev, struct lvts_domain *lvts_td,
+ const struct lvts_data *lvts_data)
+{
+ size_t size = sizeof(*lvts_td->lvts_ctrl) * lvts_data->num_lvts_ctrl;
+ struct lvts_ctrl *lvts_ctrl;
+ int i, ret;
+
+ /*
+ * Create the calibration bytes stream from efuse data
+ */
+ ret = lvts_calibration_read(dev, lvts_td, lvts_data);
+ if (ret)
+ return ret;
+
+ ret = lvts_golden_temp_init(dev, lvts_td->calib, lvts_data);
+ if (ret)
+ return ret;
+
+ lvts_ctrl = devm_kzalloc(dev, size, GFP_KERNEL);
+ if (!lvts_ctrl)
+ return -ENOMEM;
+
+ 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]);
+ if (ret)
+ return ret;
+
+ ret = lvts_calibration_init(dev, &lvts_ctrl[i],
+ &lvts_data->lvts_ctrl[i],
+ lvts_td->calib,
+ lvts_td->calib_len);
+ if (ret)
+ return ret;
+
+ /*
+ * The mode the ctrl will use to read the temperature
+ * (filtered or immediate)
+ */
+ lvts_ctrl[i].mode = lvts_data->lvts_ctrl[i].mode;
+
+ lvts_ctrl[i].low_thresh = INT_MIN;
+ lvts_ctrl[i].high_thresh = INT_MIN;
+ }
+
+ /*
+ * We no longer need the efuse bytes stream, let's free it
+ */
+ devm_kfree(dev, lvts_td->calib);
+
+ lvts_td->lvts_ctrl = lvts_ctrl;
+ lvts_td->num_lvts_ctrl = lvts_data->num_lvts_ctrl;
+
+ 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_IMMEDIATE_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, const u32 *cmds, int nr_cmds)
+{
+ int i;
+
+ /*
+ * Configuration register
+ */
+ for (i = 0; i < nr_cmds; i++) {
+ writel(cmds[i], LVTS_CONFIG(lvts_ctrl->base));
+ usleep_range(2, 4);
+ }
+}
+
+static int lvts_irq_init(struct lvts_ctrl *lvts_ctrl)
+{
+ /*
+ * LVTS_PROTCTL : Thermal Protection Sensor Selection
+ *
+ * Bits:
+ *
+ * 19-18 : Sensor to base the protection on
+ * 17-16 : Strategy:
+ * 00 : Average of 4 sensors
+ * 01 : Max of 4 sensors
+ * 10 : Selected sensor with bits 19-18
+ * 11 : Reserved
+ */
+
+ /*
+ * LVTS_PROTTA : Stage 1 temperature threshold
+ * LVTS_PROTTB : Stage 2 temperature threshold
+ * LVTS_PROTTC : Stage 3 temperature threshold
+ *
+ * Bits:
+ *
+ * 14-0: Raw temperature threshold
+ *
+ * writel(0x0, LVTS_PROTTA(lvts_ctrl->base));
+ * writel(0x0, LVTS_PROTTB(lvts_ctrl->base));
+ * writel(0x0, LVTS_PROTTC(lvts_ctrl->base));
+ */
+
+ /*
+ * LVTS_MONINT : Interrupt configuration register
+ *
+ * The LVTS_MONINT register layout is the same as the LVTS_MONINTSTS
+ * register, except we set the bits to enable the interrupt.
+ */
+ writel(0, LVTS_MONINT(lvts_ctrl->base));
+
+ return 0;
+}
+
+static int lvts_domain_reset(struct device *dev, struct reset_control *reset)
+{
+ int ret;
+
+ ret = reset_control_assert(reset);
+ if (ret)
+ return ret;
+
+ return reset_control_deassert(reset);
+}
+
+/*
+ * Enable or disable the clocks of a specified thermal controller
+ */
+static int lvts_ctrl_set_enable(struct lvts_ctrl *lvts_ctrl, int enable)
+{
+ /*
+ * LVTS_CLKEN : Internal LVTS clock
+ *
+ * Bits:
+ *
+ * 0 : enable / disable clock
+ */
+ writel(enable, LVTS_CLKEN(lvts_ctrl->base));
+
+ return 0;
+}
+
+static int lvts_ctrl_connect(struct device *dev, struct lvts_ctrl *lvts_ctrl)
+{
+ const struct lvts_data *lvts_data = lvts_ctrl->lvts_data;
+ u32 id;
+
+ lvts_write_config(lvts_ctrl, lvts_data->conn_cmd, lvts_data->num_conn_cmd);
+
+ /*
+ * LVTS_ID : Get ID and status of the thermal controller
+ *
+ * Bits:
+ *
+ * 0-5 : thermal controller id
+ * 7 : thermal controller connection is valid
+ */
+ id = readl(LVTS_ID(lvts_ctrl->base));
+ if (!(id & BIT(7)))
+ return -EIO;
+
+ return 0;
+}
+
+static int lvts_ctrl_initialize(struct device *dev, struct lvts_ctrl *lvts_ctrl)
+{
+ const struct lvts_data *lvts_data = lvts_ctrl->lvts_data;
+
+ lvts_write_config(lvts_ctrl, lvts_data->init_cmd, lvts_data->num_init_cmd);
+
+ return 0;
+}
+
+static int lvts_ctrl_calibrate(struct device *dev, struct lvts_ctrl *lvts_ctrl)
+{
+ int i;
+ void __iomem *lvts_edata[] = {
+ LVTS_EDATA00(lvts_ctrl->base),
+ LVTS_EDATA01(lvts_ctrl->base),
+ LVTS_EDATA02(lvts_ctrl->base),
+ LVTS_EDATA03(lvts_ctrl->base)
+ };
+
+ /*
+ * LVTS_EDATA0X : Efuse calibration reference value for sensor X
+ *
+ * Bits:
+ *
+ * 20-0 : Efuse value for normalization data
+ */
+ for (i = 0; i < LVTS_SENSOR_MAX; i++)
+ writel(lvts_ctrl->calibration[i], lvts_edata[i]);
+
+ /* LVTS_MSROFT : Constant offset applied to MSR values
+ * for post-processing
+ *
+ * Bits:
+ *
+ * 20-0 : Constant data added to MSR values
+ */
+ if (lvts_ctrl->lvts_data->msr_offset)
+ writel(lvts_ctrl->lvts_data->msr_offset,
+ LVTS_MSROFT(lvts_ctrl->base));
+
+ return 0;
+}
+
+static int lvts_ctrl_configure(struct device *dev, struct lvts_ctrl *lvts_ctrl)
+{
+ u32 value;
+
+ /*
+ * LVTS_TSSEL : Sensing point index numbering
+ *
+ * Bits:
+ *
+ * 31-24: ADC Sense 3
+ * 23-16: ADC Sense 2
+ * 15-8 : ADC Sense 1
+ * 7-0 : ADC Sense 0
+ */
+ value = LVTS_TSSEL_CONF;
+ writel(value, LVTS_TSSEL(lvts_ctrl->base));
+
+ /*
+ * LVTS_CALSCALE : ADC voltage round
+ */
+ value = 0x300;
+ value = LVTS_CALSCALE_CONF;
+
+ /*
+ * LVTS_MSRCTL0 : Sensor filtering strategy
+ *
+ * Filters:
+ *
+ * 000 : One sample
+ * 001 : Avg 2 samples
+ * 010 : 4 samples, drop min and max, avg 2 samples
+ * 011 : 6 samples, drop min and max, avg 4 samples
+ * 100 : 10 samples, drop min and max, avg 8 samples
+ * 101 : 18 samples, drop min and max, avg 16 samples
+ *
+ * Bits:
+ *
+ * 0-2 : Sensor0 filter
+ * 3-5 : Sensor1 filter
+ * 6-8 : Sensor2 filter
+ * 9-11 : Sensor3 filter
+ */
+ value = LVTS_HW_FILTER << 9 | LVTS_HW_FILTER << 6 |
+ LVTS_HW_FILTER << 3 | LVTS_HW_FILTER;
+ writel(value, LVTS_MSRCTL0(lvts_ctrl->base));
+
+ /*
+ * LVTS_MONCTL1 : Period unit and group interval configuration
+ *
+ * The clock source of LVTS thermal controller is 26MHz.
+ *
+ * The period unit is a time base for all the interval delays
+ * specified in the registers. By default we use 12. The time
+ * conversion is done by multiplying by 256 and 1/26.10^6
+ *
+ * An interval delay multiplied by the period unit gives the
+ * duration in seconds.
+ *
+ * - Filter interval delay is a delay between two samples of
+ * the same sensor.
+ *
+ * - Sensor interval delay is a delay between two samples of
+ * different sensors.
+ *
+ * - Group interval delay is a delay between different rounds.
+ *
+ * For example:
+ * If Period unit = C, filter delay = 1, sensor delay = 2, group delay = 1,
+ * and two sensors, TS1 and TS2, are in a LVTS thermal controller
+ * and then
+ * Period unit time = C * 1/26M * 256 = 12 * 38.46ns * 256 = 118.149us
+ * Filter interval delay = 1 * Period unit = 118.149us
+ * Sensor interval delay = 2 * Period unit = 236.298us
+ * Group interval delay = 1 * Period unit = 118.149us
+ *
+ * TS1 TS1 ... TS1 TS2 TS2 ... TS2 TS1...
+ * <--> Filter interval delay
+ * <--> Sensor interval delay
+ * <--> Group interval delay
+ * Bits:
+ * 29 - 20 : Group interval
+ * 16 - 13 : Send a single interrupt when crossing the hot threshold (1)
+ * or an interrupt everytime the hot threshold is crossed (0)
+ * 9 - 0 : Period unit
+ *
+ */
+ value = LVTS_GROUP_INTERVAL << 20 | LVTS_PERIOD_UNIT;
+ writel(value, LVTS_MONCTL1(lvts_ctrl->base));
+
+ /*
+ * LVTS_MONCTL2 : Filtering and sensor interval
+ *
+ * Bits:
+ *
+ * 25-16 : Interval unit in PERIOD_UNIT between sample on
+ * the same sensor, filter interval
+ * 9-0 : Interval unit in PERIOD_UNIT between each sensor
+ *
+ */
+ value = LVTS_FILTER_INTERVAL << 16 | LVTS_SENSOR_INTERVAL;
+ writel(value, LVTS_MONCTL2(lvts_ctrl->base));
+
+ return lvts_irq_init(lvts_ctrl);
+}
+
+static int lvts_ctrl_start(struct device *dev, struct lvts_ctrl *lvts_ctrl)
+{
+ struct lvts_sensor *lvts_sensors = lvts_ctrl->sensors;
+ 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;
+
+ lvts_for_each_valid_sensor(i, lvts_ctrl) {
+
+ int dt_id = lvts_sensors[i].dt_id;
+
+ tz = devm_thermal_of_zone_register(dev, dt_id, &lvts_sensors[i],
+ &lvts_ops);
+ if (IS_ERR(tz)) {
+ /*
+ * This thermal zone is not described in the
+ * device tree. It is not an error from the
+ * thermal OF code POV, we just continue.
+ */
+ if (PTR_ERR(tz) == -ENODEV)
+ continue;
+
+ return PTR_ERR(tz);
+ }
+
+ devm_thermal_add_hwmon_sysfs(dev, tz);
+
+ /*
+ * The thermal zone pointer will be needed in the
+ * interrupt handler, we store it in the sensor
+ * structure. The thermal domain structure will be
+ * passed to the interrupt handler private data as the
+ * interrupt is shared for all the controller
+ * belonging to the thermal domain.
+ */
+ lvts_sensors[i].tz = tz;
+
+ /*
+ * This sensor was correctly associated with a thermal
+ * zone, let's set the corresponding bit in the sensor
+ * map, so we can enable the temperature monitoring in
+ * the hardware thermal controller.
+ */
+ sensor_map |= sensor_bitmap[i];
+ }
+
+ /*
+ * 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.
+ */
+ 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;
+}
+
+static int lvts_domain_init(struct device *dev, struct lvts_domain *lvts_td,
+ const struct lvts_data *lvts_data)
+{
+ struct lvts_ctrl *lvts_ctrl;
+ int i, ret;
+
+ ret = lvts_ctrl_init(dev, lvts_td, lvts_data);
+ if (ret)
+ return ret;
+
+ ret = lvts_domain_reset(dev, lvts_td->reset);
+ if (ret) {
+ dev_dbg(dev, "Failed to reset domain");
+ return ret;
+ }
+
+ for (i = 0; i < lvts_td->num_lvts_ctrl; i++) {
+
+ lvts_ctrl = &lvts_td->lvts_ctrl[i];
+
+ /*
+ * Initialization steps:
+ *
+ * - Enable the clock
+ * - Connect to the LVTS
+ * - Initialize the LVTS
+ * - Prepare the calibration data
+ * - Select monitored sensors
+ * [ Configure sampling ]
+ * [ Configure the interrupt ]
+ * - Start measurement
+ */
+ ret = lvts_ctrl_set_enable(lvts_ctrl, true);
+ if (ret) {
+ dev_dbg(dev, "Failed to enable LVTS clock");
+ return ret;
+ }
+
+ ret = lvts_ctrl_connect(dev, lvts_ctrl);
+ if (ret) {
+ dev_dbg(dev, "Failed to connect to LVTS controller");
+ return ret;
+ }
+
+ ret = lvts_ctrl_initialize(dev, lvts_ctrl);
+ if (ret) {
+ dev_dbg(dev, "Failed to initialize controller");
+ return ret;
+ }
+
+ ret = lvts_ctrl_calibrate(dev, lvts_ctrl);
+ if (ret) {
+ dev_dbg(dev, "Failed to calibrate controller");
+ return ret;
+ }
+
+ ret = lvts_ctrl_configure(dev, lvts_ctrl);
+ if (ret) {
+ dev_dbg(dev, "Failed to configure controller");
+ return ret;
+ }
+
+ ret = lvts_ctrl_start(dev, lvts_ctrl);
+ if (ret) {
+ dev_dbg(dev, "Failed to start controller");
+ return ret;
+ }
+ }
+
+ return lvts_debugfs_init(dev, lvts_td);
+}
+
+static int lvts_probe(struct platform_device *pdev)
+{
+ const struct lvts_data *lvts_data;
+ struct lvts_domain *lvts_td;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int irq, ret;
+
+ lvts_td = devm_kzalloc(dev, sizeof(*lvts_td), GFP_KERNEL);
+ if (!lvts_td)
+ 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))
+ return dev_err_probe(dev, PTR_ERR(lvts_td->clk), "Failed to retrieve clock\n");
+
+ res = platform_get_mem_or_io(pdev, 0);
+ if (!res)
+ return dev_err_probe(dev, (-ENXIO), "No IO resource\n");
+
+ lvts_td->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(lvts_td->base))
+ return dev_err_probe(dev, PTR_ERR(lvts_td->base), "Failed to map io resource\n");
+
+ /*
+ * Depending on the SoC+Firmware combination, the LVTS hardware may be
+ * may be actively used by one or even multiple concurrent MCUs!
+ * In this case, resetting it may produce either a severe slowdown of
+ * the entire system, or even a thermal protection AP reset, as some
+ * MCU(s) may be reading a very high or very low temperature while the
+ * LVTS is being reset.
+ *
+ * On those, don't fail if no reset is found as that may be omitted on
+ * purpose, but still check if there's one, because some board(s) may
+ * be running on a different bootchain with reduced firmwares or using
+ * firmwares with reduced functionality.
+ */
+ lvts_td->reset = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(lvts_td->reset))
+ return dev_err_probe(dev, PTR_ERR(lvts_td->reset), "Failed to get reset control\n");
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ golden_temp_offset = lvts_data->temp_offset;
+
+ ret = lvts_domain_init(dev, lvts_td, lvts_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to initialize the lvts domain\n");
+
+ /*
+ * At this point the LVTS is initialized and enabled. We can
+ * safely enable the interrupt.
+ */
+ ret = devm_request_threaded_irq(dev, irq, NULL, lvts_irq_handler,
+ IRQF_ONESHOT, dev_name(dev), lvts_td);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, lvts_td);
+
+ return 0;
+}
+
+static void lvts_remove(struct platform_device *pdev)
+{
+ struct lvts_domain *lvts_td;
+ int i;
+
+ lvts_td = platform_get_drvdata(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 mt7987_lvts_ap_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT7987_CPU,
+ .cal_offsets = { 0x04, 0x05, 0x06 } },
+ { .dt_id = MT7987_ETH2P5G,
+ .cal_offsets = { 0x08, 0x09, 0x0a } },
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x0,
+ .mode = LVTS_MSR_FILTERED_MODE,
+ },
+};
+
+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);
+ }
+
+ 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 mt7987_init_cmds[] = {
+ 0xC1030300, 0xC1030420, 0xC1030500, 0xC10307A6, 0xC10308C7,
+ 0xC103098D, 0xC1030C7C, 0xC1030AA8, 0xC10308CE, 0xC10308C7,
+ 0xC1030B04, 0xC1030E01, 0xC10306B8
+};
+
+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[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8195_MCU_BIG_CPU0,
+ .cal_offsets = { 0x04, 0x05, 0x06 } },
+ { .dt_id = MT8195_MCU_BIG_CPU1,
+ .cal_offsets = { 0x07, 0x08, 0x09 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x0,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8195_MCU_BIG_CPU2,
+ .cal_offsets = { 0x0d, 0x0e, 0x0f } },
+ { .dt_id = MT8195_MCU_BIG_CPU3,
+ .cal_offsets = { 0x10, 0x11, 0x12 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x100,
+ },
+ {
+ .lvts_sensor = {
+ { .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 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x200,
+ }
+};
+
+static const struct lvts_ctrl_data mt8195_lvts_ap_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8195_AP_VPU0,
+ .cal_offsets = { 0x25, 0x26, 0x27 } },
+ { .dt_id = MT8195_AP_VPU1,
+ .cal_offsets = { 0x28, 0x29, 0x2a } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x0,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8195_AP_GPU0,
+ .cal_offsets = { 0x2e, 0x2f, 0x30 } },
+ { .dt_id = MT8195_AP_GPU1,
+ .cal_offsets = { 0x31, 0x32, 0x33 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x100,
+ },
+ {
+ .lvts_sensor = {
+ { .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 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 0),
+ .offset = 0x200,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8195_AP_CAM0,
+ .cal_offsets = { 0x43, 0x44, 0x45 } },
+ { .dt_id = MT8195_AP_CAM1,
+ .cal_offsets = { 0x46, 0x47, 0x48 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 0, 0),
+ .offset = 0x300,
+ }
+};
+
+static const struct lvts_ctrl_data mt8196_lvts_mcu_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8196_MCU_MEDIUM_CPU6_0,
+ .cal_offsets = { 0x06, 0x07 } },
+ { .dt_id = MT8196_MCU_MEDIUM_CPU6_1,
+ .cal_offsets = { 0x04, 0x05 } },
+ { .dt_id = MT8196_MCU_DSU2,
+ .cal_offsets = { 0x0A, 0x0B } },
+ { .dt_id = MT8196_MCU_DSU3,
+ .cal_offsets = { 0x08, 0x09 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x0,
+ .mode = LVTS_MSR_ATP_MODE,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8196_MCU_LITTLE_CPU3,
+ .cal_offsets = { 0x0E, 0x0F } },
+ { .dt_id = MT8196_MCU_LITTLE_CPU0,
+ .cal_offsets = { 0x0C, 0x0D } },
+ { .dt_id = MT8196_MCU_LITTLE_CPU1,
+ .cal_offsets = { 0x12, 0x13 } },
+ { .dt_id = MT8196_MCU_LITTLE_CPU2,
+ .cal_offsets = { 0x10, 0x11 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x100,
+ .mode = LVTS_MSR_ATP_MODE,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8196_MCU_MEDIUM_CPU4_0,
+ .cal_offsets = { 0x16, 0x17 } },
+ { .dt_id = MT8196_MCU_MEDIUM_CPU4_1,
+ .cal_offsets = { 0x14, 0x15 } },
+ { .dt_id = MT8196_MCU_MEDIUM_CPU5_0,
+ .cal_offsets = { 0x1A, 0x1B } },
+ { .dt_id = MT8196_MCU_MEDIUM_CPU5_1,
+ .cal_offsets = { 0x18, 0x19 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x200,
+ .mode = LVTS_MSR_ATP_MODE,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8196_MCU_DSU0,
+ .cal_offsets = { 0x1E, 0x1F } },
+ { .dt_id = MT8196_MCU_DSU1,
+ .cal_offsets = { 0x1C, 0x1D } },
+ { .dt_id = MT8196_MCU_BIG_CPU7_0,
+ .cal_offsets = { 0x22, 0x23 } },
+ { .dt_id = MT8196_MCU_BIG_CPU7_1,
+ .cal_offsets = { 0x20, 0x21 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x300,
+ .mode = LVTS_MSR_ATP_MODE,
+ }
+};
+
+static const struct lvts_ctrl_data mt8196_lvts_ap_data_ctrl[] = {
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8196_AP_TOP0,
+ .cal_offsets = { 0x32, 0x33 } },
+ { .dt_id = MT8196_AP_TOP1,
+ .cal_offsets = { 0x30, 0x31 } },
+ { .dt_id = MT8196_AP_TOP2,
+ .cal_offsets = { 0x36, 0x37 } },
+ { .dt_id = MT8196_AP_TOP3,
+ .cal_offsets = { 0x34, 0x35 } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x0,
+ .mode = LVTS_MSR_ATP_MODE,
+ },
+ {
+ .lvts_sensor = {
+ { .dt_id = MT8196_AP_BOT0,
+ .cal_offsets = { 0x3A, 0x3B } },
+ { .dt_id = MT8196_AP_BOT1,
+ .cal_offsets = { 0x38, 0x39 } },
+ { .dt_id = MT8196_AP_BOT2,
+ .cal_offsets = { 0x3E, 0x3F } },
+ { .dt_id = MT8196_AP_BOT3,
+ .cal_offsets = { 0x3C, 0x3D } }
+ },
+ VALID_SENSOR_MAP(1, 1, 1, 1),
+ .offset = 0x100,
+ .mode = LVTS_MSR_ATP_MODE,
+ }
+};
+
+static const struct lvts_platform_ops lvts_platform_ops_mt7988 = {
+ .lvts_raw_to_temp = lvts_raw_to_temp_mt7988,
+ .lvts_temp_to_raw = lvts_temp_to_raw_mt7988,
+};
+
+static const struct lvts_platform_ops lvts_platform_ops_mt8196 = {
+ .lvts_raw_to_temp = lvts_raw_to_temp_mt7988,
+ .lvts_temp_to_raw = lvts_temp_to_raw_mt8196,
+};
+
+static const struct lvts_data mt7987_lvts_ap_data = {
+ .lvts_ctrl = mt7987_lvts_ap_data_ctrl,
+ .num_lvts_ctrl = ARRAY_SIZE(mt7987_lvts_ap_data_ctrl),
+ .conn_cmd = mt7988_conn_cmds,
+ .init_cmd = mt7987_init_cmds,
+ .num_conn_cmd = ARRAY_SIZE(mt7988_conn_cmds),
+ .num_init_cmd = ARRAY_SIZE(mt7987_init_cmds),
+ .temp_factor = LVTS_COEFF_A_MT7987,
+ .temp_offset = LVTS_COEFF_B_MT7987,
+ .gt_calib_bit_offset = 32,
+ .def_calibration = 19380,
+};
+
+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,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT7988,
+ .ops = &lvts_platform_ops_mt7988,
+};
+
+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,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT7988,
+ .ops = &lvts_platform_ops_mt7988,
+};
+
+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,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT7988,
+ .ops = &lvts_platform_ops_mt7988,
+};
+
+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,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT7988,
+ .ops = &lvts_platform_ops_mt7988,
+};
+
+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,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT7988,
+ .ops = &lvts_platform_ops_mt7988,
+};
+
+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,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT7988,
+ .ops = &lvts_platform_ops_mt7988,
+};
+
+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,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT7988,
+ .ops = &lvts_platform_ops_mt7988,
+};
+
+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,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT7988,
+ .ops = &lvts_platform_ops_mt7988,
+};
+
+static const struct lvts_data mt8196_lvts_mcu_data = {
+ .lvts_ctrl = mt8196_lvts_mcu_data_ctrl,
+ .num_lvts_ctrl = ARRAY_SIZE(mt8196_lvts_mcu_data_ctrl),
+ .temp_factor = LVTS_COEFF_A_MT8196,
+ .temp_offset = LVTS_COEFF_B_MT8196,
+ .gt_calib_bit_offset = 0,
+ .def_calibration = 14437,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT8196,
+ .msr_offset = LVTS_MSR_OFFSET_MT8196,
+ .ops = &lvts_platform_ops_mt8196,
+};
+
+static const struct lvts_data mt8196_lvts_ap_data = {
+ .lvts_ctrl = mt8196_lvts_ap_data_ctrl,
+ .num_lvts_ctrl = ARRAY_SIZE(mt8196_lvts_ap_data_ctrl),
+ .temp_factor = LVTS_COEFF_A_MT8196,
+ .temp_offset = LVTS_COEFF_B_MT8196,
+ .gt_calib_bit_offset = 0,
+ .def_calibration = 14437,
+ .num_cal_offsets = LVTS_NUM_CAL_OFFSETS_MT8196,
+ .msr_offset = LVTS_MSR_OFFSET_MT8196,
+ .ops = &lvts_platform_ops_mt8196,
+};
+
+static const struct of_device_id lvts_of_match[] = {
+ { .compatible = "mediatek,mt7987-lvts-ap", .data = &mt7987_lvts_ap_data },
+ { .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 },
+ { .compatible = "mediatek,mt8196-lvts-mcu", .data = &mt8196_lvts_mcu_data },
+ { .compatible = "mediatek,mt8196-lvts-ap", .data = &mt8196_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);
+
+MODULE_AUTHOR("Balsam CHIHI <bchihi@baylibre.com>");
+MODULE_DESCRIPTION("MediaTek LVTS Thermal Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/pcie_cooling.c b/drivers/thermal/pcie_cooling.c
new file mode 100644
index 000000000..a876d64f1
--- /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
new file mode 100644
index 000000000..a8cf7e258
--- /dev/null
+++ b/drivers/thermal/qcom/Kconfig
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config QCOM_TSENS
+ tristate "Qualcomm TSENS Temperature Alarm"
+ depends on NVMEM_QCOM_QFPROM
+ depends on ARCH_QCOM || COMPILE_TEST
+ help
+ This enables the thermal sysfs driver for the TSENS device. It shows
+ up in Sysfs as a thermal zone with multiple trip points. Disabling the
+ thermal zone device via the mode file results in disabling the sensor.
+ Also able to set threshold temperature for both hot and cold and update
+ when a threshold is reached.
+
+config QCOM_SPMI_ADC_TM5
+ tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5"
+ depends on OF && SPMI && IIO
+ select REGMAP_SPMI
+ select QCOM_VADC_COMMON
+ help
+ This enables the thermal driver for the ADC thermal monitoring
+ device. It shows up as a thermal zone with multiple trip points.
+ Thermal client sets threshold temperature for both warm and cool and
+ gets updated when a threshold is reached.
+
+config QCOM_SPMI_MBG_TM
+ tristate "Qualcomm SPMI PMIC MBG Temperature monitor"
+ depends on QCOM_SPMI_ADC5_GEN3
+ select REGMAP_SPMI
+ help
+ This enables Qualcomm PMIC MBG (Master Bandgap) thermal monitor.
+
+ The MBG block monitors PMIC die temperature in hardware and raises an
+ interrupt when the programmed threshold is crossed.
+
+ Temperature is read from the associated ADC channel, and threshold
+ interrupts are forwarded to the thermal framework as trip-violation
+ events. Current support handles a single LVL1 upper (hot) trip.
+
+config QCOM_SPMI_ADC_TM5_GEN3
+ tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3"
+ depends on QCOM_SPMI_ADC5_GEN3
+ help
+ This enables the auxiliary thermal driver for the ADC5 Gen3 thermal
+ monitoring device. It shows up as a thermal zone with multiple trip points.
+ Thermal client sets threshold temperature for both warm and cool and
+ gets updated when a threshold is reached.
+
+config QCOM_SPMI_TEMP_ALARM
+ tristate "Qualcomm SPMI PMIC Temperature Alarm"
+ depends on OF && SPMI && IIO
+ select REGMAP_SPMI
+ help
+ This enables a thermal sysfs driver for Qualcomm plug-and-play (QPNP)
+ PMIC devices. It shows up in sysfs as a thermal sensor with multiple
+ trip points. The temperature reported by the thermal sensor reflects the
+ real time die temperature if an ADC is present or an estimate of the
+ temperature based upon the over temperature stage value.
+
+config QCOM_LMH
+ tristate "Qualcomm Limits Management Hardware"
+ depends on ARCH_QCOM || 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
+ input from temperature and current sensors. On many newer Qualcomm SoCs
+ LMh is configured in the firmware and this feature need not be enabled.
+ However, on certain SoCs like sdm845 LMh has to be configured from kernel.
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
new file mode 100644
index 000000000..937ba0fe2
--- /dev/null
+++ b/drivers/thermal/qcom/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
+
+qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
+ tsens-8960.o
+obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o
+obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.o
+obj-$(CONFIG_QCOM_SPMI_MBG_TM) += qcom-spmi-mbg-tm.o
+obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
+obj-$(CONFIG_QCOM_LMH) += lmh.o
diff --git a/drivers/thermal/qcom/lmh.c b/drivers/thermal/qcom/lmh.c
new file mode 100644
index 000000000..99396b93e
--- /dev/null
+++ b/drivers/thermal/qcom/lmh.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (C) 2021, Linaro Limited. All rights reserved.
+ */
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/irqdomain.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+
+#define LMH_NODE_DCVS 0x44435653
+#define LMH_CLUSTER0_NODE_ID 0x6370302D
+#define LMH_CLUSTER1_NODE_ID 0x6370312D
+
+#define LMH_SUB_FN_THERMAL 0x54484D4C
+#define LMH_SUB_FN_CRNT 0x43524E54
+#define LMH_SUB_FN_REL 0x52454C00
+#define LMH_SUB_FN_BCL 0x42434C00
+
+#define LMH_ALGO_MODE_ENABLE 0x454E424C
+#define LMH_TH_HI_THRESHOLD 0x48494748
+#define LMH_TH_LOW_THRESHOLD 0x4C4F5700
+#define LMH_TH_ARM_THRESHOLD 0x41524D00
+
+#define LMH_REG_DCVS_INTR_CLR 0x8
+
+#define LMH_ENABLE_ALGOS 1
+
+struct lmh_hw_data {
+ void __iomem *base;
+ struct irq_domain *domain;
+ int irq;
+};
+
+static irqreturn_t lmh_handle_irq(int hw_irq, void *data)
+{
+ struct lmh_hw_data *lmh_data = data;
+ int irq = irq_find_mapping(lmh_data->domain, 0);
+
+ /* Call the cpufreq driver to handle the interrupt */
+ if (irq)
+ generic_handle_irq(irq);
+
+ return IRQ_HANDLED;
+}
+
+static void lmh_enable_interrupt(struct irq_data *d)
+{
+ struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
+
+ /* Clear the existing interrupt */
+ writel(0xff, lmh_data->base + LMH_REG_DCVS_INTR_CLR);
+ enable_irq(lmh_data->irq);
+}
+
+static void lmh_disable_interrupt(struct irq_data *d)
+{
+ struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
+
+ disable_irq_nosync(lmh_data->irq);
+}
+
+static struct irq_chip lmh_irq_chip = {
+ .name = "lmh",
+ .irq_enable = lmh_enable_interrupt,
+ .irq_disable = lmh_disable_interrupt
+};
+
+static int lmh_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
+{
+ struct lmh_hw_data *lmh_data = d->host_data;
+ 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);
+
+ return 0;
+}
+
+static const struct irq_domain_ops lmh_irq_ops = {
+ .map = lmh_irq_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static int lmh_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *cpu_node;
+ struct lmh_hw_data *lmh_data;
+ int temp_low, temp_high, temp_arm, cpu_id, ret;
+ unsigned int enable_alg;
+ u32 node_id;
+
+ if (!qcom_scm_is_available())
+ return -EPROBE_DEFER;
+
+ lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL);
+ if (!lmh_data)
+ return -ENOMEM;
+
+ lmh_data->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(lmh_data->base))
+ return PTR_ERR(lmh_data->base);
+
+ cpu_node = of_parse_phandle(np, "cpus", 0);
+ if (!cpu_node)
+ return -EINVAL;
+ cpu_id = of_cpu_node_to_id(cpu_node);
+ of_node_put(cpu_node);
+
+ ret = of_property_read_u32(np, "qcom,lmh-temp-high-millicelsius", &temp_high);
+ if (ret) {
+ dev_err(dev, "missing qcom,lmh-temp-high-millicelsius property\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "qcom,lmh-temp-low-millicelsius", &temp_low);
+ if (ret) {
+ dev_err(dev, "missing qcom,lmh-temp-low-millicelsius property\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "qcom,lmh-temp-arm-millicelsius", &temp_arm);
+ if (ret) {
+ dev_err(dev, "missing qcom,lmh-temp-arm-millicelsius property\n");
+ return ret;
+ }
+
+ /*
+ * Only sdm845 has lmh hardware currently enabled from hlos. If this is needed
+ * for other platforms, revisit this to check if the <cpu-id, node-id> should be part
+ * of a dt match table.
+ */
+ if (cpu_id == 0) {
+ node_id = LMH_CLUSTER0_NODE_ID;
+ } else if (cpu_id == 4) {
+ node_id = LMH_CLUSTER1_NODE_ID;
+ } else {
+ dev_err(dev, "Wrong CPU id associated with LMh node\n");
+ return -EINVAL;
+ }
+
+ if (!qcom_scm_lmh_dcvsh_available())
+ return -EINVAL;
+
+ enable_alg = (uintptr_t)of_device_get_match_data(dev);
+
+ if (enable_alg) {
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_CRNT, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret)
+ dev_err(dev, "Error %d enabling current subfunction\n", ret);
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_REL, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret)
+ dev_err(dev, "Error %d enabling reliability subfunction\n", ret);
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_BCL, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret)
+ dev_err(dev, "Error %d enabling BCL subfunction\n", ret);
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error %d enabling thermal subfunction\n", ret);
+ return ret;
+ }
+
+ ret = qcom_scm_lmh_profile_change(0x1);
+ if (ret) {
+ dev_err(dev, "Error %d changing profile\n", ret);
+ return ret;
+ }
+ }
+
+ /* Set default thermal trips */
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_ARM_THRESHOLD, temp_arm,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
+ return ret;
+ }
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_HI_THRESHOLD, temp_high,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error setting thermal HI threshold%d\n", ret);
+ return ret;
+ }
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_LOW_THRESHOLD, temp_low,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error setting thermal LOW threshold%d\n", ret);
+ return ret;
+ }
+
+ lmh_data->irq = platform_get_irq(pdev, 0);
+ 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;
+ }
+
+ /* Disable the irq and let cpufreq enable it when ready to handle the interrupt */
+ irq_set_status_flags(lmh_data->irq, IRQ_NOAUTOEN);
+ ret = devm_request_irq(dev, lmh_data->irq, lmh_handle_irq,
+ IRQF_NO_THREAD | IRQF_NO_SUSPEND,
+ "lmh-irq", lmh_data);
+ if (ret) {
+ irq_domain_remove(lmh_data->domain);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id lmh_table[] = {
+ { .compatible = "qcom,sc8180x-lmh", },
+ { .compatible = "qcom,sdm845-lmh", .data = (void *)LMH_ENABLE_ALGOS},
+ { .compatible = "qcom,sm8150-lmh", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lmh_table);
+
+static struct platform_driver lmh_driver = {
+ .probe = lmh_probe,
+ .driver = {
+ .name = "qcom-lmh",
+ .of_match_table = lmh_table,
+ .suppress_bind_attrs = true,
+ },
+};
+module_platform_driver(lmh_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("QCOM LMh driver");
diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
new file mode 100644
index 000000000..9cf552d36
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device/devres.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/iio/adc/qcom-adc5-gen3-common.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include "../thermal_hwmon.h"
+
+#define ADC_TM5_GEN3_CONFIG_REGS 12
+
+struct device;
+struct adc_tm5_gen3_chip;
+
+/**
+ * struct adc_tm5_gen3_channel_props - ADC_TM channel structure
+ * @common_props: structure with common ADC channel properties.
+ * @chip: ADC TM device.
+ * @tzd: pointer to thermal device corresponding to TM channel.
+ * @sdam_index: SDAM on which this TM channel lies.
+ * @timer: time period of recurring TM measurement.
+ * @tm_chan_index: TM channel number used.
+ * @high_thr_en: TM high threshold crossing detection enabled.
+ * @low_thr_en: TM low threshold crossing detection enabled.
+ */
+struct adc_tm5_gen3_channel_props {
+ struct adc5_channel_common_prop common_props;
+ struct adc_tm5_gen3_chip *chip;
+ struct thermal_zone_device *tzd;
+ unsigned int sdam_index;
+ unsigned int timer;
+ unsigned int tm_chan_index;
+ bool high_thr_en;
+ bool low_thr_en;
+};
+
+/**
+ * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure
+ * @dev_data: Top-level ADC device data.
+ * @chan_props: Array of ADC_TM channel structures.
+ * @dev: SPMI ADC5 Gen3 device.
+ * @nchannels: number of TM channels allocated
+ */
+struct adc_tm5_gen3_chip {
+ struct adc5_device_data *dev_data;
+ struct adc_tm5_gen3_channel_props *chan_props;
+ struct device *dev;
+ unsigned int nchannels;
+};
+
+DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *,
+ adc5_gen3_mutex_lock(_T->dev), adc5_gen3_mutex_unlock(_T->dev))
+
+static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq)
+{
+ for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) {
+ if (adc_tm5->dev_data->base[i].irq == irq)
+ return i;
+ }
+ return -ENOENT;
+}
+
+static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = dev_id;
+ int ret, sdam_num;
+ u8 tm_status[2];
+ u8 status, val;
+
+ sdam_num = get_sdam_from_irq(adc_tm5, irq);
+ if (sdam_num < 0)
+ return IRQ_NONE;
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1,
+ &status, sizeof(status));
+ if (ret)
+ return IRQ_NONE;
+
+ if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
+ val = ADC5_GEN3_CONV_ERR_CLR_REQ;
+ adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num,
+ ADC5_GEN3_CONV_ERR_CLR, &val, 1);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS,
+ tm_status, sizeof(tm_status));
+ if (ret)
+ return IRQ_NONE;
+
+ if (tm_status[0] || tm_status[1])
+ return IRQ_WAKE_THREAD;
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t adctm5_gen3_isr_thread(int irq, void *dev_id)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = dev_id;
+ u8 tm_status[2];
+ int sdam_index;
+
+ sdam_index = get_sdam_from_irq(adc_tm5, irq);
+ if (sdam_index < 0)
+ return IRQ_NONE;
+
+ scoped_guard(adc5_gen3, adc_tm5) {
+ int ret;
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS,
+ tm_status, sizeof(tm_status));
+ if (ret)
+ return IRQ_NONE;
+
+ ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index,
+ ADC5_GEN3_TM_HIGH_STS_CLR, tm_status,
+ sizeof(tm_status));
+ if (ret)
+ return IRQ_NONE;
+ }
+
+ for (int i = 0; i < adc_tm5->nchannels; i++) {
+ struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i];
+ int offset = chan_prop->tm_chan_index;
+ bool upper_set, lower_set;
+
+ if (chan_prop->sdam_index != sdam_index)
+ continue;
+
+ upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en);
+ lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en);
+
+ if (!(upper_set || lower_set))
+ continue;
+
+ thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
+ struct adc_tm5_gen3_chip *adc_tm5;
+
+ if (!prop || !prop->chip)
+ return -EINVAL;
+
+ adc_tm5 = prop->chip;
+
+ return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props, temp);
+}
+
+static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
+ int ret;
+ u8 val;
+
+ prop->high_thr_en = false;
+ prop->low_thr_en = false;
+
+ ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
+ if (ret)
+ return ret;
+
+ val = BIT(prop->tm_chan_index);
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_TM_LOW_STS_CLR, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ val = MEAS_INT_DISABLE;
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_TIMER_SEL, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ /* To indicate there is an actual conversion request */
+ val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_PERPH_CH, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ val = ADC5_GEN3_CONV_REQ_REQ;
+ return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_CONV_REQ, &val, sizeof(val));
+}
+
+static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop,
+ int low_temp, int high_temp)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
+ u8 buf[ADC_TM5_GEN3_CONFIG_REGS];
+ u8 conv_req;
+ u16 adc_code;
+ int ret;
+
+ ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
+ if (ret < 0)
+ return ret;
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_SID, buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ /* Write SID */
+ buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid);
+
+ /* Select TM channel and indicate there is an actual conversion request */
+ buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
+
+ buf[2] = prop->timer;
+
+ /* Digital param selection */
+ adc5_gen3_update_dig_param(&prop->common_props, &buf[3]);
+
+ /* Update fast average sample value */
+ buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK,
+ prop->common_props.avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN;
+
+ /* Select ADC channel */
+ buf[5] = prop->common_props.channel;
+
+ /* Select HW settle delay for channel */
+ buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK,
+ prop->common_props.hw_settle_time_us);
+
+ buf[7] = 0;
+
+ /* High temperature corresponds to low voltage threshold */
+ prop->low_thr_en = (high_temp != INT_MAX);
+ if (prop->low_thr_en) {
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp);
+ put_unaligned_le16(adc_code, &buf[8]);
+ buf[7] |= ADC5_GEN3_LOW_THR_INT_EN;
+ }
+
+ /* Low temperature corresponds to high voltage threshold */
+ prop->high_thr_en = (low_temp != -INT_MAX);
+ if (prop->high_thr_en) {
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp);
+ put_unaligned_le16(adc_code, &buf[10]);
+ buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN;
+ }
+
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID,
+ buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ conv_req = ADC5_GEN3_CONV_REQ_REQ;
+ return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req));
+}
+
+static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz,
+ int low_temp, int high_temp)
+{
+ struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
+ struct adc_tm5_gen3_chip *adc_tm5;
+
+ if (!prop || !prop->chip)
+ return -EINVAL;
+
+ adc_tm5 = prop->chip;
+
+ dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n",
+ prop->common_props.label, low_temp, high_temp);
+
+ guard(adc5_gen3)(adc_tm5);
+
+ return adc_tm5_gen3_configure(prop, low_temp, high_temp);
+}
+
+static const struct thermal_zone_device_ops adc_tm_ops = {
+ .get_temp = adc_tm5_gen3_get_temp,
+ .set_trips = adc_tm5_gen3_set_trip_temp,
+};
+
+static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5)
+{
+ struct thermal_zone_device *tzd;
+ unsigned int channel;
+ int ret;
+
+ for (int i = 0; i < adc_tm5->nchannels; i++) {
+ channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props);
+ tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel,
+ &adc_tm5->chan_props[i],
+ &adc_tm_ops);
+ if (IS_ERR(tzd)) {
+ if (PTR_ERR(tzd) == -ENODEV) {
+ dev_dbg(adc_tm5->dev,
+ "thermal sensor on channel %d is not used\n",
+ channel);
+ continue;
+ }
+ return PTR_ERR(tzd);
+ }
+ adc_tm5->chan_props[i].tzd = tzd;
+ ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void adc5_gen3_disable(void *data)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = data;
+
+ guard(adc5_gen3)(adc_tm5);
+
+ /* Disable all available TM channels */
+ for (int i = 0; i < adc_tm5->nchannels; i++)
+ adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]);
+}
+
+static int adc_tm5_probe(struct auxiliary_device *aux_dev,
+ const struct auxiliary_device_id *id)
+{
+ struct adc_tm5_gen3_chip *adc_tm5;
+ struct tm5_aux_dev_wrapper *aux_dev_wrapper;
+ struct device *dev = &aux_dev->dev;
+ int ret;
+
+ adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL);
+ if (!adc_tm5)
+ return -ENOMEM;
+
+ aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper, aux_dev);
+
+ adc_tm5->dev = dev;
+ adc_tm5->dev_data = aux_dev_wrapper->dev_data;
+ adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels;
+ adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels,
+ sizeof(*adc_tm5->chan_props), GFP_KERNEL);
+ if (!adc_tm5->chan_props)
+ return -ENOMEM;
+
+ for (int i = 0; i < adc_tm5->nchannels; i++) {
+ /*
+ * Since the first channel of the first SDAM is reserved for
+ * immediate ADC conversions, TM channel count must start from
+ * the channel just after it. The variable tm_count is used to
+ * calculate SDAM and TM channel index on that SDAM correctly
+ * for each TM channel.
+ */
+ int tm_count = i + 1;
+
+ adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i];
+ adc_tm5->chan_props[i].timer = MEAS_INT_1S;
+ adc_tm5->chan_props[i].sdam_index = tm_count / 8;
+ adc_tm5->chan_props[i].tm_chan_index = tm_count % 8;
+ adc_tm5->chan_props[i].chip = adc_tm5;
+ }
+
+ /*
+ * ADC_TM channels are enabled in the loop in adc_tm5_register_tzd() as
+ * part of the set_trips calls during thermal zone registration. This
+ * action is to disable them all in case of probe failure.
+ */
+ ret = devm_add_action(dev, adc5_gen3_disable, adc_tm5);
+ if (ret)
+ return ret;
+
+ ret = adc_tm5_register_tzd(adc_tm5);
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) {
+ u32 irq_flags = IRQF_ONESHOT;
+
+ /*
+ * First SDAM's interrupt is shared between main ADC driver and
+ * auxiliary TM driver, so its flags must include IRQF_SHARED.
+ * This is not needed for other SDAMs as they will be used only
+ * for TM functionality.
+ */
+ if (i == 0)
+ irq_flags |= IRQF_SHARED;
+
+ ret = devm_request_threaded_irq(dev,
+ adc_tm5->dev_data->base[i].irq,
+ adctm5_gen3_isr,
+ adctm5_gen3_isr_thread,
+ irq_flags,
+ adc_tm5->dev_data->base[i].irq_name,
+ adc_tm5);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = {
+ { .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3" },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table);
+
+static struct auxiliary_driver adctm5gen3_auxiliary_driver = {
+ .id_table = adctm5_auxiliary_id_table,
+ .probe = adc_tm5_probe,
+};
+module_auxiliary_driver(adctm5gen3_auxiliary_driver);
+
+MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3");
diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
new file mode 100644
index 000000000..af72db629
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
@@ -0,0 +1,1069 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 Linaro Limited
+ *
+ * Based on original driver:
+ * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
+ *
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iio/adc/qcom-vadc-common.h>
+#include <linux/iio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+#include <linux/unaligned.h>
+
+#include "../thermal_hwmon.h"
+
+/*
+ * Thermal monitoring block consists of 8 (ADC_TM5_NUM_CHANNELS) channels. Each
+ * channel is programmed to use one of ADC channels for voltage comparison.
+ * Voltages are programmed using ADC codes, so we have to convert temp to
+ * voltage and then to ADC code value.
+ *
+ * Configuration of TM channels must match configuration of corresponding ADC
+ * channels.
+ */
+
+#define ADC5_MAX_CHANNEL 0xc0
+#define ADC_TM5_NUM_CHANNELS 8
+
+#define ADC_TM5_STATUS_LOW 0x0a
+
+#define ADC_TM5_STATUS_HIGH 0x0b
+
+#define ADC_TM5_NUM_BTM 0x0f
+
+#define ADC_TM5_ADC_DIG_PARAM 0x42
+
+#define ADC_TM5_FAST_AVG_CTL (ADC_TM5_ADC_DIG_PARAM + 1)
+#define ADC_TM5_FAST_AVG_EN BIT(7)
+
+#define ADC_TM5_MEAS_INTERVAL_CTL (ADC_TM5_ADC_DIG_PARAM + 2)
+#define ADC_TM5_TIMER1 3 /* 3.9ms */
+
+#define ADC_TM5_MEAS_INTERVAL_CTL2 (ADC_TM5_ADC_DIG_PARAM + 3)
+#define ADC_TM5_MEAS_INTERVAL_CTL2_MASK 0xf0
+#define ADC_TM5_TIMER2 10 /* 1 second */
+#define ADC_TM5_MEAS_INTERVAL_CTL3_MASK 0xf
+#define ADC_TM5_TIMER3 4 /* 4 second */
+
+#define ADC_TM_EN_CTL1 0x46
+#define ADC_TM_EN BIT(7)
+#define ADC_TM_CONV_REQ 0x47
+#define ADC_TM_CONV_REQ_EN BIT(7)
+
+#define ADC_TM5_M_CHAN_BASE 0x60
+
+#define ADC_TM5_M_ADC_CH_SEL_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 0)
+#define ADC_TM5_M_LOW_THR0(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 1)
+#define ADC_TM5_M_LOW_THR1(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 2)
+#define ADC_TM5_M_HIGH_THR0(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 3)
+#define ADC_TM5_M_HIGH_THR1(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 4)
+#define ADC_TM5_M_MEAS_INTERVAL_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 5)
+#define ADC_TM5_M_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 6)
+#define ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK 0xf
+#define ADC_TM5_M_CTL_CAL_SEL_MASK 0x30
+#define ADC_TM5_M_CTL_CAL_VAL 0x40
+#define ADC_TM5_M_EN(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 7)
+#define ADC_TM5_M_MEAS_EN BIT(7)
+#define ADC_TM5_M_HIGH_THR_INT_EN BIT(1)
+#define ADC_TM5_M_LOW_THR_INT_EN BIT(0)
+
+#define ADC_TM_GEN2_STATUS1 0x08
+#define ADC_TM_GEN2_STATUS_LOW_SET 0x09
+#define ADC_TM_GEN2_STATUS_LOW_CLR 0x0a
+#define ADC_TM_GEN2_STATUS_HIGH_SET 0x0b
+#define ADC_TM_GEN2_STATUS_HIGH_CLR 0x0c
+
+#define ADC_TM_GEN2_CFG_HS_SET 0x0d
+#define ADC_TM_GEN2_CFG_HS_FLAG BIT(0)
+#define ADC_TM_GEN2_CFG_HS_CLR 0x0e
+
+#define ADC_TM_GEN2_SID 0x40
+
+#define ADC_TM_GEN2_CH_CTL 0x41
+#define ADC_TM_GEN2_TM_CH_SEL GENMASK(7, 5)
+#define ADC_TM_GEN2_MEAS_INT_SEL GENMASK(3, 2)
+
+#define ADC_TM_GEN2_ADC_DIG_PARAM 0x42
+#define ADC_TM_GEN2_CTL_CAL_SEL GENMASK(5, 4)
+#define ADC_TM_GEN2_CTL_DEC_RATIO_MASK GENMASK(3, 2)
+
+#define ADC_TM_GEN2_FAST_AVG_CTL 0x43
+#define ADC_TM_GEN2_FAST_AVG_EN BIT(7)
+
+#define ADC_TM_GEN2_ADC_CH_SEL_CTL 0x44
+
+#define ADC_TM_GEN2_DELAY_CTL 0x45
+#define ADC_TM_GEN2_HW_SETTLE_DELAY GENMASK(3, 0)
+
+#define ADC_TM_GEN2_EN_CTL1 0x46
+#define ADC_TM_GEN2_EN BIT(7)
+
+#define ADC_TM_GEN2_CONV_REQ 0x47
+#define ADC_TM_GEN2_CONV_REQ_EN BIT(7)
+
+#define ADC_TM_GEN2_LOW_THR0 0x49
+#define ADC_TM_GEN2_LOW_THR1 0x4a
+#define ADC_TM_GEN2_HIGH_THR0 0x4b
+#define ADC_TM_GEN2_HIGH_THR1 0x4c
+#define ADC_TM_GEN2_LOWER_MASK(n) ((n) & GENMASK(7, 0))
+#define ADC_TM_GEN2_UPPER_MASK(n) (((n) & GENMASK(15, 8)) >> 8)
+
+#define ADC_TM_GEN2_MEAS_IRQ_EN 0x4d
+#define ADC_TM_GEN2_MEAS_EN BIT(7)
+#define ADC_TM5_GEN2_HIGH_THR_INT_EN BIT(1)
+#define ADC_TM5_GEN2_LOW_THR_INT_EN BIT(0)
+
+#define ADC_TM_GEN2_MEAS_INT_LSB 0x50
+#define ADC_TM_GEN2_MEAS_INT_MSB 0x51
+#define ADC_TM_GEN2_MEAS_INT_MODE 0x52
+
+#define ADC_TM_GEN2_Mn_DATA0(n) ((n * 2) + 0xa0)
+#define ADC_TM_GEN2_Mn_DATA1(n) ((n * 2) + 0xa1)
+#define ADC_TM_GEN2_DATA_SHIFT 8
+
+enum adc5_timer_select {
+ ADC5_TIMER_SEL_1 = 0,
+ ADC5_TIMER_SEL_2,
+ ADC5_TIMER_SEL_3,
+ ADC5_TIMER_SEL_NONE,
+};
+
+enum adc5_gen {
+ ADC_TM5,
+ ADC_TM_HC,
+ ADC_TM5_GEN2,
+ ADC_TM5_MAX
+};
+
+enum adc_tm5_cal_method {
+ ADC_TM5_NO_CAL = 0,
+ ADC_TM5_RATIOMETRIC_CAL,
+ ADC_TM5_ABSOLUTE_CAL
+};
+
+enum adc_tm_gen2_time_select {
+ MEAS_INT_50MS = 0,
+ MEAS_INT_100MS,
+ MEAS_INT_1S,
+ MEAS_INT_SET,
+ MEAS_INT_NONE,
+};
+
+struct adc_tm5_chip;
+struct adc_tm5_channel;
+
+struct adc_tm5_data {
+ const u32 full_scale_code_volt;
+ unsigned int *decimation;
+ unsigned int *hw_settle;
+ int (*disable_channel)(struct adc_tm5_channel *channel);
+ int (*configure)(struct adc_tm5_channel *channel, int low, int high);
+ irqreturn_t (*isr)(int irq, void *data);
+ int (*init)(struct adc_tm5_chip *chip);
+ char *irq_name;
+ int gen;
+};
+
+/**
+ * struct adc_tm5_channel - ADC Thermal Monitoring channel data.
+ * @channel: channel number.
+ * @adc_channel: corresponding ADC channel number.
+ * @cal_method: calibration method.
+ * @prescale: channel scaling performed on the input signal.
+ * @hw_settle_time: the time between AMUX being configured and the
+ * start of conversion.
+ * @decimation: sampling rate supported for the channel.
+ * @avg_samples: ability to provide single result from the ADC
+ * that is an average of multiple measurements.
+ * @high_thr_en: channel upper voltage threshold enable state.
+ * @low_thr_en: channel lower voltage threshold enable state.
+ * @meas_en: recurring measurement enable state
+ * @iio: IIO channel instance used by this channel.
+ * @chip: ADC TM chip instance.
+ * @tzd: thermal zone device used by this channel.
+ */
+struct adc_tm5_channel {
+ unsigned int channel;
+ unsigned int adc_channel;
+ enum adc_tm5_cal_method cal_method;
+ unsigned int prescale;
+ unsigned int hw_settle_time;
+ unsigned int decimation; /* For Gen2 ADC_TM */
+ unsigned int avg_samples; /* For Gen2 ADC_TM */
+ bool high_thr_en; /* For Gen2 ADC_TM */
+ bool low_thr_en; /* For Gen2 ADC_TM */
+ bool meas_en; /* For Gen2 ADC_TM */
+ struct iio_channel *iio;
+ struct adc_tm5_chip *chip;
+ struct thermal_zone_device *tzd;
+};
+
+/**
+ * struct adc_tm5_chip - ADC Thermal Monitoring properties
+ * @regmap: SPMI ADC5 Thermal Monitoring peripheral register map field.
+ * @dev: SPMI ADC5 device.
+ * @data: software configuration data.
+ * @channels: array of ADC TM channel data.
+ * @nchannels: amount of channels defined/allocated
+ * @decimation: sampling rate supported for the channel.
+ * Applies to all channels, used only on Gen1 ADC_TM.
+ * @avg_samples: ability to provide single result from the ADC
+ * that is an average of multiple measurements. Applies to all
+ * channels, used only on Gen1 ADC_TM.
+ * @base: base address of TM registers.
+ * @adc_mutex_lock: ADC_TM mutex lock, used only on Gen2 ADC_TM.
+ * It is used to ensure only one ADC channel configuration
+ * is done at a time using the shared set of configuration
+ * registers.
+ */
+struct adc_tm5_chip {
+ struct regmap *regmap;
+ struct device *dev;
+ const struct adc_tm5_data *data;
+ struct adc_tm5_channel *channels;
+ unsigned int nchannels;
+ unsigned int decimation;
+ unsigned int avg_samples;
+ u16 base;
+ struct mutex adc_mutex_lock;
+};
+
+static int adc_tm5_read(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len)
+{
+ return regmap_bulk_read(adc_tm->regmap, adc_tm->base + offset, data, len);
+}
+
+static int adc_tm5_write(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len)
+{
+ return regmap_bulk_write(adc_tm->regmap, adc_tm->base + offset, data, len);
+}
+
+static int adc_tm5_reg_update(struct adc_tm5_chip *adc_tm, u16 offset, u8 mask, u8 val)
+{
+ return regmap_write_bits(adc_tm->regmap, adc_tm->base + offset, mask, val);
+}
+
+static irqreturn_t adc_tm5_isr(int irq, void *data)
+{
+ struct adc_tm5_chip *chip = data;
+ u8 status_low, status_high, ctl;
+ int ret, i;
+
+ ret = adc_tm5_read(chip, ADC_TM5_STATUS_LOW, &status_low, sizeof(status_low));
+ if (unlikely(ret)) {
+ dev_err(chip->dev, "read status low failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_read(chip, ADC_TM5_STATUS_HIGH, &status_high, sizeof(status_high));
+ if (unlikely(ret)) {
+ dev_err(chip->dev, "read status high failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ bool upper_set = false, lower_set = false;
+ unsigned int ch = chip->channels[i].channel;
+
+ /* No TZD, we warned at the boot time */
+ if (!chip->channels[i].tzd)
+ continue;
+
+ ret = adc_tm5_read(chip, ADC_TM5_M_EN(ch), &ctl, sizeof(ctl));
+ if (unlikely(ret)) {
+ dev_err(chip->dev, "ctl read failed: %d, channel %d\n", ret, i);
+ continue;
+ }
+
+ if (!(ctl & ADC_TM5_M_MEAS_EN))
+ continue;
+
+ lower_set = (status_low & BIT(ch)) &&
+ (ctl & ADC_TM5_M_LOW_THR_INT_EN);
+
+ upper_set = (status_high & BIT(ch)) &&
+ (ctl & ADC_TM5_M_HIGH_THR_INT_EN);
+
+ if (upper_set || lower_set)
+ thermal_zone_device_update(chip->channels[i].tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t adc_tm5_gen2_isr(int irq, void *data)
+{
+ struct adc_tm5_chip *chip = data;
+ u8 status_low, status_high;
+ int ret, i;
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low));
+ if (ret) {
+ dev_err(chip->dev, "read status_low failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high));
+ if (ret) {
+ dev_err(chip->dev, "read status_high failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low));
+ if (ret < 0) {
+ dev_err(chip->dev, "clear status low failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high));
+ if (ret < 0) {
+ dev_err(chip->dev, "clear status high failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ bool upper_set = false, lower_set = false;
+ unsigned int ch = chip->channels[i].channel;
+
+ /* No TZD, we warned at the boot time */
+ if (!chip->channels[i].tzd)
+ continue;
+
+ if (!chip->channels[i].meas_en)
+ continue;
+
+ lower_set = (status_low & BIT(ch)) &&
+ (chip->channels[i].low_thr_en);
+
+ upper_set = (status_high & BIT(ch)) &&
+ (chip->channels[i].high_thr_en);
+
+ if (upper_set || lower_set)
+ thermal_zone_device_update(chip->channels[i].tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int adc_tm5_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct adc_tm5_channel *channel = thermal_zone_device_priv(tz);
+ int ret;
+
+ if (!channel || !channel->iio)
+ return -EINVAL;
+
+ ret = iio_read_channel_processed(channel->iio, temp);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int adc_tm5_disable_channel(struct adc_tm5_channel *channel)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ unsigned int reg = ADC_TM5_M_EN(channel->channel);
+
+ return adc_tm5_reg_update(chip, reg,
+ ADC_TM5_M_MEAS_EN |
+ ADC_TM5_M_HIGH_THR_INT_EN |
+ ADC_TM5_M_LOW_THR_INT_EN,
+ 0);
+}
+
+#define ADC_TM_GEN2_POLL_DELAY_MIN_US 100
+#define ADC_TM_GEN2_POLL_DELAY_MAX_US 110
+#define ADC_TM_GEN2_POLL_RETRY_COUNT 3
+
+static int32_t adc_tm5_gen2_conv_req(struct adc_tm5_chip *chip)
+{
+ int ret;
+ u8 data;
+ unsigned int count;
+
+ data = ADC_TM_GEN2_EN;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_EN_CTL1, &data, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm enable failed with %d\n", ret);
+ return ret;
+ }
+
+ data = ADC_TM_GEN2_CFG_HS_FLAG;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_CFG_HS_SET, &data, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm handshake failed with %d\n", ret);
+ return ret;
+ }
+
+ data = ADC_TM_GEN2_CONV_REQ_EN;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_CONV_REQ, &data, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm request conversion failed with %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * SW sets a handshake bit and waits for PBS to clear it
+ * before the next conversion request can be queued.
+ */
+
+ for (count = 0; count < ADC_TM_GEN2_POLL_RETRY_COUNT; count++) {
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_CFG_HS_SET, &data, sizeof(data));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm read failed with %d\n", ret);
+ return ret;
+ }
+
+ if (!(data & ADC_TM_GEN2_CFG_HS_FLAG))
+ return ret;
+ usleep_range(ADC_TM_GEN2_POLL_DELAY_MIN_US,
+ ADC_TM_GEN2_POLL_DELAY_MAX_US);
+ }
+
+ dev_err(chip->dev, "adc-tm conversion request handshake timed out\n");
+
+ return -ETIMEDOUT;
+}
+
+static int adc_tm5_gen2_disable_channel(struct adc_tm5_channel *channel)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ int ret;
+ u8 val;
+
+ mutex_lock(&chip->adc_mutex_lock);
+
+ channel->meas_en = false;
+ channel->high_thr_en = false;
+ channel->low_thr_en = false;
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_CH_CTL, &val, sizeof(val));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm block read failed with %d\n", ret);
+ goto disable_fail;
+ }
+
+ val &= ~ADC_TM_GEN2_TM_CH_SEL;
+ val |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel);
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_CH_CTL, &val, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm channel disable failed with %d\n", ret);
+ goto disable_fail;
+ }
+
+ val = 0;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_MEAS_IRQ_EN, &val, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm interrupt disable failed with %d\n", ret);
+ goto disable_fail;
+ }
+
+
+ ret = adc_tm5_gen2_conv_req(channel->chip);
+ if (ret < 0)
+ dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret);
+
+disable_fail:
+ mutex_unlock(&chip->adc_mutex_lock);
+ return ret;
+}
+
+static int adc_tm5_enable(struct adc_tm5_chip *chip)
+{
+ int ret;
+ u8 data;
+
+ data = ADC_TM_EN;
+ ret = adc_tm5_write(chip, ADC_TM_EN_CTL1, &data, sizeof(data));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm enable failed\n");
+ return ret;
+ }
+
+ data = ADC_TM_CONV_REQ_EN;
+ ret = adc_tm5_write(chip, ADC_TM_CONV_REQ, &data, sizeof(data));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm request conversion failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int adc_tm5_configure(struct adc_tm5_channel *channel, int low, int high)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ u8 buf[8];
+ u16 reg = ADC_TM5_M_ADC_CH_SEL_CTL(channel->channel);
+ int ret;
+
+ ret = adc_tm5_read(chip, reg, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "channel %d params read failed: %d\n", channel->channel, ret);
+ return ret;
+ }
+
+ buf[0] = channel->adc_channel;
+
+ /* High temperature corresponds to low voltage threshold */
+ if (high != INT_MAX) {
+ u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
+ chip->data->full_scale_code_volt, high);
+
+ put_unaligned_le16(adc_code, &buf[1]);
+ buf[7] |= ADC_TM5_M_LOW_THR_INT_EN;
+ } else {
+ buf[7] &= ~ADC_TM5_M_LOW_THR_INT_EN;
+ }
+
+ /* Low temperature corresponds to high voltage threshold */
+ if (low != -INT_MAX) {
+ u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
+ chip->data->full_scale_code_volt, low);
+
+ put_unaligned_le16(adc_code, &buf[3]);
+ buf[7] |= ADC_TM5_M_HIGH_THR_INT_EN;
+ } else {
+ buf[7] &= ~ADC_TM5_M_HIGH_THR_INT_EN;
+ }
+
+ buf[5] = ADC5_TIMER_SEL_2;
+
+ /* Set calibration select, hw_settle delay */
+ buf[6] &= ~ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK;
+ buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK, channel->hw_settle_time);
+ buf[6] &= ~ADC_TM5_M_CTL_CAL_SEL_MASK;
+ buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_CAL_SEL_MASK, channel->cal_method);
+
+ buf[7] |= ADC_TM5_M_MEAS_EN;
+
+ ret = adc_tm5_write(chip, reg, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "channel %d params write failed: %d\n", channel->channel, ret);
+ return ret;
+ }
+
+ return adc_tm5_enable(chip);
+}
+
+static int adc_tm5_gen2_configure(struct adc_tm5_channel *channel, int low, int high)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ int ret;
+ u8 buf[14];
+ u16 adc_code;
+
+ mutex_lock(&chip->adc_mutex_lock);
+
+ channel->meas_en = true;
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_SID, buf, sizeof(buf));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm block read failed with %d\n", ret);
+ goto config_fail;
+ }
+
+ /* Set SID from virtual channel number */
+ buf[0] = channel->adc_channel >> 8;
+
+ /* Set TM channel number used and measurement interval */
+ buf[1] &= ~ADC_TM_GEN2_TM_CH_SEL;
+ buf[1] |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel);
+ buf[1] &= ~ADC_TM_GEN2_MEAS_INT_SEL;
+ buf[1] |= FIELD_PREP(ADC_TM_GEN2_MEAS_INT_SEL, MEAS_INT_1S);
+
+ buf[2] &= ~ADC_TM_GEN2_CTL_DEC_RATIO_MASK;
+ buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_DEC_RATIO_MASK, channel->decimation);
+ buf[2] &= ~ADC_TM_GEN2_CTL_CAL_SEL;
+ buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_CAL_SEL, channel->cal_method);
+
+ buf[3] = channel->avg_samples | ADC_TM_GEN2_FAST_AVG_EN;
+
+ buf[4] = channel->adc_channel & 0xff;
+
+ buf[5] = channel->hw_settle_time & ADC_TM_GEN2_HW_SETTLE_DELAY;
+
+ /* High temperature corresponds to low voltage threshold */
+ if (high != INT_MAX) {
+ channel->low_thr_en = true;
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(high);
+ put_unaligned_le16(adc_code, &buf[9]);
+ } else {
+ channel->low_thr_en = false;
+ }
+
+ /* Low temperature corresponds to high voltage threshold */
+ if (low != -INT_MAX) {
+ channel->high_thr_en = true;
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(low);
+ put_unaligned_le16(adc_code, &buf[11]);
+ } else {
+ channel->high_thr_en = false;
+ }
+
+ buf[13] = ADC_TM_GEN2_MEAS_EN;
+ if (channel->high_thr_en)
+ buf[13] |= ADC_TM5_GEN2_HIGH_THR_INT_EN;
+ if (channel->low_thr_en)
+ buf[13] |= ADC_TM5_GEN2_LOW_THR_INT_EN;
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_SID, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "channel %d params write failed: %d\n", channel->channel, ret);
+ goto config_fail;
+ }
+
+ ret = adc_tm5_gen2_conv_req(channel->chip);
+ if (ret < 0)
+ dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret);
+
+config_fail:
+ mutex_unlock(&chip->adc_mutex_lock);
+ return ret;
+}
+
+static int adc_tm5_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct adc_tm5_channel *channel = thermal_zone_device_priv(tz);
+ struct adc_tm5_chip *chip;
+ int ret;
+
+ if (!channel)
+ return -EINVAL;
+
+ chip = channel->chip;
+ dev_dbg(chip->dev, "%d:low(mdegC):%d, high(mdegC):%d\n",
+ channel->channel, low, high);
+
+ if (high == INT_MAX && low <= -INT_MAX)
+ ret = chip->data->disable_channel(channel);
+ else
+ ret = chip->data->configure(channel, low, high);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops adc_tm5_thermal_ops = {
+ .get_temp = adc_tm5_get_temp,
+ .set_trips = adc_tm5_set_trips,
+};
+
+static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm)
+{
+ unsigned int i;
+ struct thermal_zone_device *tzd;
+
+ for (i = 0; i < adc_tm->nchannels; i++) {
+ adc_tm->channels[i].chip = adc_tm;
+ tzd = devm_thermal_of_zone_register(adc_tm->dev,
+ adc_tm->channels[i].channel,
+ &adc_tm->channels[i],
+ &adc_tm5_thermal_ops);
+ if (IS_ERR(tzd)) {
+ if (PTR_ERR(tzd) == -ENODEV) {
+ dev_dbg(adc_tm->dev, "thermal sensor on channel %d is not used\n",
+ adc_tm->channels[i].channel);
+ continue;
+ }
+
+ dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n",
+ adc_tm->channels[i].channel, PTR_ERR(tzd));
+ return PTR_ERR(tzd);
+ }
+ adc_tm->channels[i].tzd = tzd;
+ devm_thermal_add_hwmon_sysfs(adc_tm->dev, tzd);
+ }
+
+ return 0;
+}
+
+static int adc_tm_hc_init(struct adc_tm5_chip *chip)
+{
+ unsigned int i;
+ u8 buf[2];
+ int ret;
+
+ for (i = 0; i < chip->nchannels; i++) {
+ if (chip->channels[i].channel >= ADC_TM5_NUM_CHANNELS) {
+ dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
+ return -EINVAL;
+ }
+ }
+
+ buf[0] = chip->decimation;
+ buf[1] = chip->avg_samples | ADC_TM5_FAST_AVG_EN;
+
+ ret = adc_tm5_write(chip, ADC_TM5_ADC_DIG_PARAM, buf, sizeof(buf));
+ if (ret)
+ dev_err(chip->dev, "block write failed: %d\n", ret);
+
+ return ret;
+}
+
+static int adc_tm5_init(struct adc_tm5_chip *chip)
+{
+ u8 buf[4], channels_available;
+ int ret;
+ unsigned int i;
+
+ ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM,
+ &channels_available, sizeof(channels_available));
+ if (ret) {
+ dev_err(chip->dev, "read failed for BTM channels\n");
+ return ret;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ if (chip->channels[i].channel >= channels_available) {
+ dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
+ return -EINVAL;
+ }
+ }
+
+ buf[0] = chip->decimation;
+ buf[1] = chip->avg_samples | ADC_TM5_FAST_AVG_EN;
+ buf[2] = ADC_TM5_TIMER1;
+ buf[3] = FIELD_PREP(ADC_TM5_MEAS_INTERVAL_CTL2_MASK, ADC_TM5_TIMER2) |
+ FIELD_PREP(ADC_TM5_MEAS_INTERVAL_CTL3_MASK, ADC_TM5_TIMER3);
+
+ ret = adc_tm5_write(chip, ADC_TM5_ADC_DIG_PARAM, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "block write failed: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int adc_tm5_gen2_init(struct adc_tm5_chip *chip)
+{
+ u8 channels_available;
+ int ret;
+ unsigned int i;
+
+ ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM,
+ &channels_available, sizeof(channels_available));
+ if (ret) {
+ dev_err(chip->dev, "read failed for BTM channels\n");
+ return ret;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ if (chip->channels[i].channel >= channels_available) {
+ dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
+ return -EINVAL;
+ }
+ }
+
+ mutex_init(&chip->adc_mutex_lock);
+
+ return ret;
+}
+
+static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm,
+ struct adc_tm5_channel *channel,
+ struct device_node *node)
+{
+ const char *name = node->name;
+ u32 chan, value, adc_channel, varr[2];
+ int ret;
+ struct device *dev = adc_tm->dev;
+ struct of_phandle_args args;
+
+ ret = of_property_read_u32(node, "reg", &chan);
+ if (ret) {
+ dev_err(dev, "%s: invalid channel number %d\n", name, ret);
+ return ret;
+ }
+
+ if (chan >= ADC_TM5_NUM_CHANNELS) {
+ dev_err(dev, "%s: channel number too big: %d\n", name, chan);
+ return -EINVAL;
+ }
+
+ channel->channel = chan;
+
+ /*
+ * We are tied to PMIC's ADC controller, which always use single
+ * argument for channel number. So don't bother parsing
+ * #io-channel-cells, just enforce cell_count = 1.
+ */
+ ret = of_parse_phandle_with_fixed_args(node, "io-channels", 1, 0, &args);
+ if (ret < 0) {
+ dev_err(dev, "%s: error parsing ADC channel number %d: %d\n", name, chan, ret);
+ return ret;
+ }
+ of_node_put(args.np);
+
+ if (args.args_count != 1) {
+ dev_err(dev, "%s: invalid args count for ADC channel %d\n", name, chan);
+ return -EINVAL;
+ }
+
+ adc_channel = args.args[0];
+ if (adc_tm->data->gen == ADC_TM5_GEN2)
+ adc_channel &= 0xff;
+
+ if (adc_channel >= ADC5_MAX_CHANNEL) {
+ dev_err(dev, "%s: invalid ADC channel number %d\n", name, chan);
+ return -EINVAL;
+ }
+ channel->adc_channel = args.args[0];
+
+ channel->iio = devm_fwnode_iio_channel_get_by_name(adc_tm->dev,
+ of_fwnode_handle(node), NULL);
+ if (IS_ERR(channel->iio))
+ 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) {
+ ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]);
+ if (ret < 0) {
+ dev_err(dev, "%s: invalid pre-scaling <%d %d>\n",
+ name, varr[0], varr[1]);
+ return ret;
+ }
+ channel->prescale = ret;
+ } else {
+ /* 1:1 prescale is index 0 */
+ channel->prescale = 0;
+ }
+
+ ret = of_property_read_u32(node, "qcom,hw-settle-time-us", &value);
+ if (!ret) {
+ ret = qcom_adc5_hw_settle_time_from_dt(value, adc_tm->data->hw_settle);
+ if (ret < 0) {
+ dev_err(dev, "%s invalid hw-settle-time-us %d us\n",
+ name, value);
+ return ret;
+ }
+ channel->hw_settle_time = ret;
+ } else {
+ channel->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
+ }
+
+ if (of_property_read_bool(node, "qcom,ratiometric"))
+ channel->cal_method = ADC_TM5_RATIOMETRIC_CAL;
+ else
+ channel->cal_method = ADC_TM5_ABSOLUTE_CAL;
+
+ if (adc_tm->data->gen == ADC_TM5_GEN2) {
+ ret = of_property_read_u32(node, "qcom,decimation", &value);
+ if (!ret) {
+ ret = qcom_adc5_decimation_from_dt(value, adc_tm->data->decimation);
+ if (ret < 0) {
+ dev_err(dev, "invalid decimation %d\n", value);
+ return ret;
+ }
+ channel->decimation = ret;
+ } else {
+ channel->decimation = ADC5_DECIMATION_DEFAULT;
+ }
+
+ ret = of_property_read_u32(node, "qcom,avg-samples", &value);
+ if (!ret) {
+ ret = qcom_adc5_avg_samples_from_dt(value);
+ if (ret < 0) {
+ dev_err(dev, "invalid avg-samples %d\n", value);
+ return ret;
+ }
+ channel->avg_samples = ret;
+ } else {
+ channel->avg_samples = VADC_DEF_AVG_SAMPLES;
+ }
+ }
+
+ return 0;
+}
+
+static const struct adc_tm5_data adc_tm5_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .decimation = (unsigned int []) { 250, 420, 840 },
+ .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700,
+ 1000, 2000, 4000, 8000, 16000, 32000,
+ 64000, 128000 },
+ .disable_channel = adc_tm5_disable_channel,
+ .configure = adc_tm5_configure,
+ .isr = adc_tm5_isr,
+ .init = adc_tm5_init,
+ .irq_name = "pm-adc-tm5",
+ .gen = ADC_TM5,
+};
+
+static const struct adc_tm5_data adc_tm_hc_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .decimation = (unsigned int []) { 256, 512, 1024 },
+ .hw_settle = (unsigned int []) { 0, 100, 200, 300, 400, 500, 600, 700,
+ 1000, 2000, 4000, 6000, 8000, 10000 },
+ .disable_channel = adc_tm5_disable_channel,
+ .configure = adc_tm5_configure,
+ .isr = adc_tm5_isr,
+ .init = adc_tm_hc_init,
+ .irq_name = "pm-adc-tm5",
+ .gen = ADC_TM_HC,
+};
+
+static const struct adc_tm5_data adc_tm5_gen2_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .decimation = (unsigned int []) { 85, 340, 1360 },
+ .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700,
+ 1000, 2000, 4000, 8000, 16000, 32000,
+ 64000, 128000 },
+ .disable_channel = adc_tm5_gen2_disable_channel,
+ .configure = adc_tm5_gen2_configure,
+ .isr = adc_tm5_gen2_isr,
+ .init = adc_tm5_gen2_init,
+ .irq_name = "pm-adc-tm5-gen2",
+ .gen = ADC_TM5_GEN2,
+};
+
+static int adc_tm5_get_dt_data(struct adc_tm5_chip *adc_tm, struct device_node *node)
+{
+ struct adc_tm5_channel *channels;
+ u32 value;
+ int ret;
+ struct device *dev = adc_tm->dev;
+
+ adc_tm->nchannels = of_get_available_child_count(node);
+ if (!adc_tm->nchannels)
+ return -EINVAL;
+
+ adc_tm->channels = devm_kcalloc(dev, adc_tm->nchannels,
+ sizeof(*adc_tm->channels), GFP_KERNEL);
+ if (!adc_tm->channels)
+ return -ENOMEM;
+
+ channels = adc_tm->channels;
+
+ adc_tm->data = of_device_get_match_data(dev);
+ if (!adc_tm->data)
+ adc_tm->data = &adc_tm5_data_pmic;
+
+ ret = of_property_read_u32(node, "qcom,decimation", &value);
+ if (!ret) {
+ ret = qcom_adc5_decimation_from_dt(value, adc_tm->data->decimation);
+ if (ret < 0) {
+ dev_err(dev, "invalid decimation %d\n", value);
+ return ret;
+ }
+ adc_tm->decimation = ret;
+ } else {
+ adc_tm->decimation = ADC5_DECIMATION_DEFAULT;
+ }
+
+ ret = of_property_read_u32(node, "qcom,avg-samples", &value);
+ if (!ret) {
+ ret = qcom_adc5_avg_samples_from_dt(value);
+ if (ret < 0) {
+ dev_err(dev, "invalid avg-samples %d\n", value);
+ return ret;
+ }
+ adc_tm->avg_samples = ret;
+ } else {
+ adc_tm->avg_samples = VADC_DEF_AVG_SAMPLES;
+ }
+
+ for_each_available_child_of_node_scoped(node, child) {
+ ret = adc_tm5_get_dt_channel_data(adc_tm, channels, child);
+ if (ret)
+ return ret;
+
+ channels++;
+ }
+
+ return 0;
+}
+
+static int adc_tm5_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct adc_tm5_chip *adc_tm;
+ struct regmap *regmap;
+ int ret, irq;
+ u32 reg;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ ret = of_property_read_u32(node, "reg", &reg);
+ if (ret)
+ return ret;
+
+ adc_tm = devm_kzalloc(&pdev->dev, sizeof(*adc_tm), GFP_KERNEL);
+ if (!adc_tm)
+ return -ENOMEM;
+
+ adc_tm->regmap = regmap;
+ adc_tm->dev = dev;
+ adc_tm->base = reg;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = adc_tm5_get_dt_data(adc_tm, node);
+ if (ret)
+ return dev_err_probe(dev, ret, "get dt data failed\n");
+
+ ret = adc_tm->data->init(adc_tm);
+ if (ret) {
+ dev_err(dev, "adc-tm init failed\n");
+ return ret;
+ }
+
+ ret = adc_tm5_register_tzd(adc_tm);
+ if (ret) {
+ dev_err(dev, "tzd register failed\n");
+ return ret;
+ }
+
+ return devm_request_threaded_irq(dev, irq, NULL, adc_tm->data->isr,
+ IRQF_ONESHOT, adc_tm->data->irq_name, adc_tm);
+}
+
+static const struct of_device_id adc_tm5_match_table[] = {
+ {
+ .compatible = "qcom,spmi-adc-tm5",
+ .data = &adc_tm5_data_pmic,
+ },
+ {
+ .compatible = "qcom,spmi-adc-tm-hc",
+ .data = &adc_tm_hc_data_pmic,
+ },
+ {
+ .compatible = "qcom,spmi-adc-tm5-gen2",
+ .data = &adc_tm5_gen2_data_pmic,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adc_tm5_match_table);
+
+static struct platform_driver adc_tm5_driver = {
+ .driver = {
+ .name = "qcom-spmi-adc-tm5",
+ .of_match_table = adc_tm5_match_table,
+ },
+ .probe = adc_tm5_probe,
+};
+module_platform_driver(adc_tm5_driver);
+
+MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_CONSUMER");
diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c
new file mode 100644
index 000000000..a05bb4446
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#include <linux/bitfield.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include <linux/iio/consumer.h>
+
+#define MBG_TEMP_MON2_FAULT_STATUS 0x50
+
+#define MON_FAULT_STATUS_MASK GENMASK(7, 4)
+#define MON_FAULT_LVL1_UPR 0x5
+
+#define MON2_LVL1_UP_THRESH 0x59
+
+#define MBG_TEMP_MON2_MISC_CFG 0x5f
+#define MON2_UP_THRESH_EN BIT(1)
+
+#define MBG_TEMP_STEP_MV 8
+#define MBG_TEMP_DEFAULT_TEMP_MV 600
+#define MBG_TEMP_CONSTANT 1000
+#define MBG_MIN_TRIP_TEMP 25000
+#define MBG_MAX_SUPPORTED_TEMP 160000
+
+/**
+ * struct mbg_tm_chip - MBG thermal monitor device data.
+ * @map: regmap for accessing MBG thermal registers.
+ * @dev: mbg_tm_chip device.
+ * @tz_dev: thermal zone device registered with the thermal framework.
+ * @lock: mbg_tm_chip lock for set trip temperature.
+ * @base: base register offset for this MBG instance
+ * @irq: interrupt line used to signal threshold events
+ * @last_temp: last measured temperature.
+ * @last_thres_crossed: indicates whether the last interrupt crossed a threshold
+ * @adc: IIO ADC channel used for temperature sensing
+ */
+struct mbg_tm_chip {
+ struct regmap *map;
+ struct device *dev;
+ struct thermal_zone_device *tz_dev;
+ struct mutex lock;
+ unsigned int base;
+ int irq;
+ int last_temp;
+ bool last_thres_crossed;
+ struct iio_channel *adc;
+};
+
+/**
+ * struct mbg_map_table - temperature to voltage mapping entry
+ * @min_temp: minimum temperature supported by this mapping entry
+ * @vtemp0: reference voltage or ADC code corresponding to the temperature
+ * @tc: temperature coefficient used for conversion calculations
+ */
+struct mbg_map_table {
+ int min_temp;
+ int vtemp0;
+ int tc;
+};
+
+static const struct mbg_map_table map_table[] = {
+ { -60000, 4337, 1967 },
+ { -40000, 4731, 1964 },
+ { -20000, 5124, 1957 },
+ { 0, 5515, 1949 },
+ { 20000, 5905, 1940 },
+ { 40000, 6293, 1930 },
+ { 60000, 6679, 1921 },
+ { 80000, 7064, 1910 },
+ { 100000, 7446, 1896 },
+ { 120000, 7825, 1878 },
+ { 140000, 8201, 1859 },
+};
+
+static int mbg_tm_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct mbg_tm_chip *chip = thermal_zone_device_priv(tz);
+ int ret, milli_celsius;
+
+ scoped_guard(mutex, &chip->lock) {
+ if (chip->last_thres_crossed) {
+ dev_dbg(chip->dev, "last_temp: %d\n", chip->last_temp);
+ chip->last_thres_crossed = false;
+ *temp = chip->last_temp;
+ return 0;
+ }
+ }
+
+ ret = iio_read_channel_processed(chip->adc, &milli_celsius);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to read iio channel with %d\n", ret);
+ return ret;
+ }
+
+ *temp = milli_celsius;
+
+ return 0;
+}
+
+static int temp_to_vtemp_mv(int temp)
+{
+ int idx, vtemp, tc = 0, t0 = 0, vtemp0 = 0;
+
+ for (idx = 0; idx < ARRAY_SIZE(map_table); idx++)
+ if (temp >= map_table[idx].min_temp &&
+ temp < (map_table[idx].min_temp + 20000)) {
+ tc = map_table[idx].tc;
+ t0 = map_table[idx].min_temp;
+ vtemp0 = map_table[idx].vtemp0;
+ break;
+ }
+
+ /*
+ * Formula to calculate vtemp(mV) from a given temp
+ * vtemp = (temp - minT) * tc + vtemp0
+ * tc, t0 and vtemp0 values are mentioned in the map_table array.
+ */
+ vtemp = ((temp - t0) * tc + vtemp0 * 100000) / 1000000;
+
+ /* step size is 8mV */
+ return abs(vtemp - MBG_TEMP_DEFAULT_TEMP_MV) / MBG_TEMP_STEP_MV;
+}
+
+static int mbg_tm_set_trip_temp(struct thermal_zone_device *tz, int low_temp,
+ int temp)
+{
+ struct mbg_tm_chip *chip = thermal_zone_device_priv(tz);
+ int ret = 0;
+
+ guard(mutex)(&chip->lock);
+
+ /* The HW has a limitation that the trip set must be above 25C */
+ if (temp > MBG_MIN_TRIP_TEMP && temp < MBG_MAX_SUPPORTED_TEMP) {
+ ret = regmap_write(chip->map, chip->base + MON2_LVL1_UP_THRESH,
+ temp_to_vtemp_mv(temp));
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_set_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG,
+ MON2_UP_THRESH_EN);
+ if (ret < 0)
+ return ret;
+ } else {
+ dev_err(chip->dev, "Set trip b/w 25C and 160C\n");
+ ret = regmap_clear_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG,
+ MON2_UP_THRESH_EN);
+ return -ERANGE;
+ }
+
+ /*
+ * Configure the last_temp one degree higher, to ensure the
+ * violated temp is returned to thermal framework when it reads
+ * temperature for the first time after the violation happens.
+ * This is needed to account for the inaccuracy in the conversion
+ * formula used which leads to the thermal framework setting back
+ * the same thresholds in case the temperature it reads does not
+ * show violation.
+ */
+ chip->last_temp = temp + MBG_TEMP_CONSTANT;
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops mbg_tm_ops = {
+ .get_temp = mbg_tm_get_temp,
+ .set_trips = mbg_tm_set_trip_temp,
+};
+
+static irqreturn_t mbg_tm_isr(int irq, void *data)
+{
+ struct mbg_tm_chip *chip = data;
+ int ret, val;
+
+ scoped_guard(mutex, &chip->lock) {
+ ret = regmap_read(chip->map, chip->base + MBG_TEMP_MON2_FAULT_STATUS, &val);
+ if (ret < 0)
+ return IRQ_HANDLED;
+ if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR)
+ chip->last_thres_crossed = true;
+ }
+
+ if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) {
+ dev_dbg(chip->dev, "Notifying Thermal, fault status=%d\n", val);
+ thermal_zone_device_update(chip->tz_dev, THERMAL_TRIP_VIOLATED);
+ } else {
+ dev_dbg(chip->dev, "Lvl1 upper threshold not violated, ignoring interrupt\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mbg_tm_probe(struct platform_device *pdev)
+{
+ struct mbg_tm_chip *chip;
+ struct device_node *node = pdev->dev.of_node;
+ u32 res;
+ int ret;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = &pdev->dev;
+
+ mutex_init(&chip->lock);
+
+ chip->map = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chip->map)
+ return -ENXIO;
+
+ ret = device_property_read_u32(chip->dev, "reg", &res);
+ if (ret < 0)
+ return dev_err_probe(chip->dev, ret, "Couldn't read reg property\n");
+
+ chip->base = res;
+
+ chip->irq = platform_get_irq(pdev, 0);
+ if (chip->irq < 0)
+ return dev_err_probe(chip->dev, chip->irq, "Failed to get irq\n");
+
+ chip->adc = devm_iio_channel_get(&pdev->dev, "thermal");
+ if (IS_ERR(chip->adc))
+ return dev_err_probe(chip->dev, PTR_ERR(chip->adc), "Failed to get adc channel\n");
+
+ chip->tz_dev = devm_thermal_of_zone_register(chip->dev, 0, chip, &mbg_tm_ops);
+ if (IS_ERR(chip->tz_dev))
+ return dev_err_probe(chip->dev, PTR_ERR(chip->tz_dev),
+ "Failed to register sensor\n");
+
+ return devm_request_threaded_irq(&pdev->dev, chip->irq, NULL, mbg_tm_isr, IRQF_ONESHOT,
+ node->name, chip);
+}
+
+static const struct of_device_id mbg_tm_match_table[] = {
+ { .compatible = "qcom,pm8775-mbg-tm" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mbg_tm_match_table);
+
+static struct platform_driver mbg_tm_driver = {
+ .driver = {
+ .name = "qcom-spmi-mbg-tm",
+ .of_match_table = mbg_tm_match_table,
+ },
+ .probe = mbg_tm_probe,
+};
+module_platform_driver(mbg_tm_driver);
+
+MODULE_DESCRIPTION("PMIC MBG Temperature monitor driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_CONSUMER");
diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
new file mode 100644
index 000000000..fb003ca96
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
@@ -0,0 +1,907 @@
+// 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>
+#include <linux/iio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.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)
+
+/* 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 },
+ { 110000, 130000, 150000 },
+ { 115000, 135000, 155000 },
+ { 120000, 140000, 160000 },
+};
+
+static const long temp_map_gen2_v1[THRESH_COUNT][STAGE_COUNT] = {
+ { 90000, 110000, 140000 },
+ { 95000, 115000, 145000 },
+ { 100000, 120000, 150000 },
+ { 105000, 125000, 155000 },
+};
+
+#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */
+
+#define THRESH_MIN 0
+#define THRESH_MAX 3
+
+#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 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;
+};
+
+/* This array maps from GEN2 alarm state to GEN1 alarm stage */
+static const unsigned int alarm_state_map[8] = {0, 1, 1, 2, 2, 3, 3, 3};
+
+static int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *data)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(chip->map, chip->base + addr, &val);
+ if (ret < 0)
+ return ret;
+
+ *data = val;
+ return 0;
+}
+
+static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data)
+{
+ return regmap_write(chip->map, chip->base + addr, data);
+}
+
+/**
+ * qpnp_tm_decode_temp() - return temperature in mC corresponding to the
+ * specified over-temperature stage
+ * @chip: Pointer to the qpnp_tm chip
+ * @stage: Over-temperature stage
+ *
+ * Return: temperature in mC
+ */
+static long qpnp_tm_decode_temp(struct qpnp_tm_chip *chip, unsigned int stage)
+{
+ if (stage == 0 || stage > STAGE_COUNT)
+ return 0;
+
+ return chip->temp_thresh_map[stage - 1];
+}
+
+/**
+ * qpnp_tm_gen1_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_gen1_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;
+
+ return FIELD_GET(STATUS_GEN1_STAGE_MASK, reg);
+}
+
+/**
+ * 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);
+}
+
+/*
+ * This function updates the internal temp value based on the
+ * current thermal stage and threshold as well as the previous stage
+ */
+static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
+{
+ unsigned int stage_new, stage_old;
+ int ret;
+
+ WARN_ON(!mutex_is_locked(&chip->lock));
+
+ ret = chip->data->get_temp_stage(chip);
+ if (ret < 0)
+ return ret;
+ stage_new = ret;
+ stage_old = chip->stage;
+
+ if (stage_new > stage_old) {
+ /* increasing stage, use lower bound */
+ chip->temp = qpnp_tm_decode_temp(chip, stage_new)
+ + TEMP_STAGE_HYSTERESIS;
+ } else if (stage_new < stage_old) {
+ /* decreasing stage, use upper bound */
+ chip->temp = qpnp_tm_decode_temp(chip, stage_new + 1)
+ - TEMP_STAGE_HYSTERESIS;
+ }
+
+ chip->stage = stage_new;
+
+ return 0;
+}
+
+static int qpnp_tm_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct qpnp_tm_chip *chip = thermal_zone_device_priv(tz);
+ int ret, mili_celsius;
+
+ if (!temp)
+ return -EINVAL;
+
+ if (!chip->initialized) {
+ *temp = DEFAULT_TEMP;
+ return 0;
+ }
+
+ if (!chip->adc) {
+ mutex_lock(&chip->lock);
+ ret = qpnp_tm_update_temp_no_adc(chip);
+ mutex_unlock(&chip->lock);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = iio_read_channel_processed(chip->adc, &mili_celsius);
+ if (ret < 0)
+ return ret;
+
+ chip->temp = mili_celsius;
+ }
+
+ *temp = chip->temp;
+
+ return 0;
+}
+
+static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip,
+ int temp)
+{
+ 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: 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) {
+ threshold = THRESH_MIN;
+ goto skip;
+ }
+
+ if (temp <= stage2_threshold_max) {
+ threshold = THRESH_MAX -
+ ((stage2_threshold_max - temp) /
+ TEMP_THRESH_STEP);
+ disable_stage2_shutdown = true;
+ } else {
+ threshold = THRESH_MAX;
+
+ if (chip->adc)
+ 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",
+ temp, stage2_threshold_max, stage2_threshold_max);
+ }
+
+skip:
+ 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,
+ const struct thermal_trip *trip, int temp)
+{
+ struct qpnp_tm_chip *chip = thermal_zone_device_priv(tz);
+ int ret;
+
+ if (trip->type != THERMAL_TRIP_CRITICAL)
+ return 0;
+
+ mutex_lock(&chip->lock);
+ ret = qpnp_tm_update_critical_trip_temp(chip, temp);
+ mutex_unlock(&chip->lock);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops qpnp_tm_sensor_ops = {
+ .get_temp = qpnp_tm_get_temp,
+ .set_trip_temp = qpnp_tm_set_trip_temp,
+};
+
+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;
+
+ thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+/* Read the hardware default stage threshold temperatures */
+static int qpnp_tm_sync_thresholds(struct qpnp_tm_chip *chip)
+{
+ u8 reg, threshold;
+ int ret;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, &reg);
+ if (ret < 0)
+ return ret;
+
+ threshold = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+ memcpy(chip->temp_thresh_map, chip->data->temp_map[threshold],
+ sizeof(chip->temp_thresh_map));
+
+ return ret;
+}
+
+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 ret;
+}
+
+/* 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)
+{
+ 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;
+
+ 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);
+
+ 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)
+ return ret;
+
+ /* 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;
+
+ /*
+ * 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)
+ 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;
+
+ thresh = FIELD_GET(LITE_TEMP_CFG_THRESHOLD_MASK, reg);
+ chip->temp_thresh_map[2] = temp_lite_shutdown_map[thresh];
+
+ return 0;
+}
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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)
+ 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;
+ ret = qpnp_tm_write(chip, QPNP_TM_REG_ALARM_CTRL, reg);
+
+ chip->initialized = true;
+
+ return ret;
+}
+
+static int qpnp_tm_probe(struct platform_device *pdev)
+{
+ struct qpnp_tm_chip *chip;
+ struct device_node *node;
+ u8 type, subtype, dig_major, dig_minor;
+ u32 res, dig_revision;
+ int ret, irq;
+
+ node = pdev->dev.of_node;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = &pdev->dev;
+
+ mutex_init(&chip->lock);
+
+ chip->map = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chip->map)
+ return -ENXIO;
+
+ ret = of_property_read_u32(node, "reg", &res);
+ if (ret < 0)
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ /* ADC based measurements are optional */
+ chip->adc = devm_iio_channel_get(&pdev->dev, "thermal");
+ if (IS_ERR(chip->adc)) {
+ ret = PTR_ERR(chip->adc);
+ chip->adc = NULL;
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ }
+
+ chip->base = res;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "could not read type\n");
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_SUBTYPE, &subtype);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "could not read subtype\n");
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_DIG_MAJOR, &dig_major);
+ if (ret < 0)
+ 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_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_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
+ 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
+ * read the trip points. get_temp() returns the default temperature
+ * before the hardware initialization is completed.
+ */
+ chip->tz_dev = devm_thermal_of_zone_register(
+ &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");
+
+ ret = qpnp_tm_init(chip);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "init failed\n");
+
+ devm_thermal_add_hwmon_sysfs(&pdev->dev, chip->tz_dev);
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qpnp_tm_isr,
+ IRQF_ONESHOT, node->name, chip);
+ if (ret < 0)
+ return ret;
+
+ thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED);
+
+ return 0;
+}
+
+static const struct of_device_id qpnp_tm_match_table[] = {
+ { .compatible = "qcom,spmi-temp-alarm" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qpnp_tm_match_table);
+
+static struct platform_driver qpnp_tm_driver = {
+ .driver = {
+ .name = "spmi-temp-alarm",
+ .of_match_table = qpnp_tm_match_table,
+ },
+ .probe = qpnp_tm_probe,
+};
+module_platform_driver(qpnp_tm_driver);
+
+MODULE_ALIAS("platform:spmi-temp-alarm");
+MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_CONSUMER");
diff --git a/drivers/thermal/qcom/tsens-8960.c b/drivers/thermal/qcom/tsens-8960.c
new file mode 100644
index 000000000..4585904fb
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-8960.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include "tsens.h"
+
+#define CONFIG_ADDR 0x3640
+#define CONFIG_ADDR_8660 0x3620
+/* CONFIG_ADDR bitmasks */
+#define CONFIG 0x9b
+#define CONFIG_MASK 0xf
+#define CONFIG_8660 1
+#define CONFIG_SHIFT_8660 28
+#define CONFIG_MASK_8660 (3 << CONFIG_SHIFT_8660)
+
+#define CNTL_ADDR 0x3620
+/* CNTL_ADDR bitmasks */
+#define EN BIT(0)
+#define SW_RST BIT(1)
+
+#define MEASURE_PERIOD BIT(18)
+#define SLP_CLK_ENA BIT(26)
+#define SLP_CLK_ENA_8660 BIT(24)
+#define SENSOR0_SHIFT 3
+
+#define THRESHOLD_ADDR 0x3624
+
+#define INT_STATUS_ADDR 0x363c
+
+#define S0_STATUS_OFF 0x3628
+#define S1_STATUS_OFF 0x362c
+#define S2_STATUS_OFF 0x3630
+#define S3_STATUS_OFF 0x3634
+#define S4_STATUS_OFF 0x3638
+#define S5_STATUS_OFF 0x3664 /* Sensors 5-10 found on apq8064/msm8960 */
+#define S6_STATUS_OFF 0x3668
+#define S7_STATUS_OFF 0x366c
+#define S8_STATUS_OFF 0x3670
+#define S9_STATUS_OFF 0x3674
+#define S10_STATUS_OFF 0x3678
+
+/* Original slope - 350 to compensate mC to C inaccuracy */
+static u32 tsens_msm8960_slope[] = {
+ 826, 826, 804, 826,
+ 761, 782, 782, 849,
+ 782, 849, 782
+ };
+
+static int suspend_8960(struct tsens_priv *priv)
+{
+ int ret;
+ unsigned int mask;
+ struct regmap *map = priv->tm_map;
+
+ ret = regmap_read(map, THRESHOLD_ADDR, &priv->ctx.threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, CNTL_ADDR, &priv->ctx.control);
+ if (ret)
+ return ret;
+
+ if (priv->num_sensors > 1)
+ mask = SLP_CLK_ENA | EN;
+ else
+ mask = SLP_CLK_ENA_8660 | EN;
+
+ ret = regmap_update_bits(map, CNTL_ADDR, mask, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int resume_8960(struct tsens_priv *priv)
+{
+ int ret;
+ struct regmap *map = priv->tm_map;
+
+ ret = regmap_update_bits(map, CNTL_ADDR, SW_RST, SW_RST);
+ if (ret)
+ return ret;
+
+ /*
+ * Separate CONFIG restore is not needed only for 8660 as
+ * config is part of CTRL Addr and its restored as such
+ */
+ if (priv->num_sensors > 1) {
+ ret = regmap_update_bits(map, CONFIG_ADDR, CONFIG_MASK, CONFIG);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(map, THRESHOLD_ADDR, priv->ctx.threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(map, CNTL_ADDR, priv->ctx.control);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int enable_8960(struct tsens_priv *priv, int id)
+{
+ int ret;
+ u32 reg, mask = BIT(id);
+
+ ret = regmap_read(priv->tm_map, CNTL_ADDR, &reg);
+ if (ret)
+ return ret;
+
+ /* HARDWARE BUG:
+ * On platforms with more than 6 sensors, all remaining sensors
+ * must be enabled together, otherwise undefined results are expected.
+ * (Sensor 6-7 disabled, Sensor 3 disabled...) In the original driver,
+ * all the sensors are enabled in one step hence this bug is not
+ * triggered.
+ */
+ if (id > 5)
+ mask = GENMASK(10, 6);
+
+ mask <<= SENSOR0_SHIFT;
+
+ /* Sensors already enabled. Skip. */
+ if ((reg & mask) == mask)
+ return 0;
+
+ ret = regmap_write(priv->tm_map, CNTL_ADDR, reg | SW_RST);
+ if (ret)
+ return ret;
+
+ reg |= MEASURE_PERIOD;
+
+ if (priv->num_sensors > 1)
+ reg |= mask | SLP_CLK_ENA | EN;
+ else
+ reg |= mask | SLP_CLK_ENA_8660 | EN;
+
+ ret = regmap_write(priv->tm_map, CNTL_ADDR, reg);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void disable_8960(struct tsens_priv *priv)
+{
+ int ret;
+ u32 reg_cntl;
+ u32 mask;
+
+ mask = GENMASK(priv->num_sensors - 1, 0);
+ mask <<= SENSOR0_SHIFT;
+ mask |= EN;
+
+ ret = regmap_read(priv->tm_map, CNTL_ADDR, &reg_cntl);
+ if (ret)
+ return;
+
+ reg_cntl &= ~mask;
+
+ if (priv->num_sensors > 1)
+ reg_cntl &= ~SLP_CLK_ENA;
+ else
+ reg_cntl &= ~SLP_CLK_ENA_8660;
+
+ regmap_write(priv->tm_map, CNTL_ADDR, reg_cntl);
+}
+
+static int calibrate_8960(struct tsens_priv *priv)
+{
+ int i;
+ char *data;
+ u32 p1[11];
+
+ data = qfprom_read(priv->dev, "calib");
+ if (IS_ERR(data))
+ data = qfprom_read(priv->dev, "calib_backup");
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = data[i];
+ priv->sensor[i].slope = tsens_msm8960_slope[i];
+ }
+
+ compute_intercept_slope(priv, p1, NULL, ONE_PT_CALIB);
+
+ kfree(data);
+
+ return 0;
+}
+
+static const struct reg_field tsens_8960_regfields[MAX_REGFIELDS] = {
+ /* ----- SROT ------ */
+ /* No VERSION information */
+
+ /* CNTL */
+ [TSENS_EN] = REG_FIELD(CNTL_ADDR, 0, 0),
+ [TSENS_SW_RST] = REG_FIELD(CNTL_ADDR, 1, 1),
+ /* 8960 has 5 sensors, 8660 has 11, we only handle 5 */
+ [SENSOR_EN] = REG_FIELD(CNTL_ADDR, 3, 7),
+
+ /* ----- TM ------ */
+ /* INTERRUPT ENABLE */
+ /* NO INTERRUPT ENABLE */
+
+ /* Single UPPER/LOWER TEMPERATURE THRESHOLD for all sensors */
+ [LOW_THRESH_0] = REG_FIELD(THRESHOLD_ADDR, 0, 7),
+ [UP_THRESH_0] = REG_FIELD(THRESHOLD_ADDR, 8, 15),
+ /* MIN_THRESH_0 and MAX_THRESH_0 are not present in the regfield
+ * Recycle CRIT_THRESH_0 and 1 to set the required regs to hardcoded temp
+ * MIN_THRESH_0 -> CRIT_THRESH_1
+ * MAX_THRESH_0 -> CRIT_THRESH_0
+ */
+ [CRIT_THRESH_1] = REG_FIELD(THRESHOLD_ADDR, 16, 23),
+ [CRIT_THRESH_0] = REG_FIELD(THRESHOLD_ADDR, 24, 31),
+
+ /* UPPER/LOWER INTERRUPT [CLEAR/STATUS] */
+ /* 1 == clear, 0 == normal operation */
+ [LOW_INT_CLEAR_0] = REG_FIELD(CNTL_ADDR, 9, 9),
+ [UP_INT_CLEAR_0] = REG_FIELD(CNTL_ADDR, 10, 10),
+
+ /* NO CRITICAL INTERRUPT SUPPORT on 8960 */
+
+ /* Sn_STATUS */
+ [LAST_TEMP_0] = REG_FIELD(S0_STATUS_OFF, 0, 7),
+ [LAST_TEMP_1] = REG_FIELD(S1_STATUS_OFF, 0, 7),
+ [LAST_TEMP_2] = REG_FIELD(S2_STATUS_OFF, 0, 7),
+ [LAST_TEMP_3] = REG_FIELD(S3_STATUS_OFF, 0, 7),
+ [LAST_TEMP_4] = REG_FIELD(S4_STATUS_OFF, 0, 7),
+ [LAST_TEMP_5] = REG_FIELD(S5_STATUS_OFF, 0, 7),
+ [LAST_TEMP_6] = REG_FIELD(S6_STATUS_OFF, 0, 7),
+ [LAST_TEMP_7] = REG_FIELD(S7_STATUS_OFF, 0, 7),
+ [LAST_TEMP_8] = REG_FIELD(S8_STATUS_OFF, 0, 7),
+ [LAST_TEMP_9] = REG_FIELD(S9_STATUS_OFF, 0, 7),
+ [LAST_TEMP_10] = REG_FIELD(S10_STATUS_OFF, 0, 7),
+
+ /* No VALID field on 8960 */
+ /* TSENS_INT_STATUS bits: 1 == threshold violated */
+ [MIN_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 0, 0),
+ [LOWER_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 1, 1),
+ [UPPER_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 2, 2),
+ /* No CRITICAL field on 8960 */
+ [MAX_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 3, 3),
+
+ /* TRDY: 1=ready, 0=in progress */
+ [TRDY] = REG_FIELD(INT_STATUS_ADDR, 7, 7),
+};
+
+static const struct tsens_ops ops_8960 = {
+ .init = init_common,
+ .calibrate = calibrate_8960,
+ .get_temp = get_temp_common,
+ .enable = enable_8960,
+ .disable = disable_8960,
+ .suspend = suspend_8960,
+ .resume = resume_8960,
+};
+
+static struct tsens_features tsens_8960_feat = {
+ .ver_major = VER_0,
+ .crit_int = 0,
+ .combo_int = 0,
+ .adc = 1,
+ .srot_split = 0,
+ .max_sensors = 11,
+ .trip_min_temp = -40000,
+ .trip_max_temp = 120000,
+};
+
+struct tsens_plat_data data_8960 = {
+ .num_sensors = 11,
+ .ops = &ops_8960,
+ .feat = &tsens_8960_feat,
+ .fields = tsens_8960_regfields,
+};
diff --git a/drivers/thermal/qcom/tsens-v0_1.c b/drivers/thermal/qcom/tsens-v0_1.c
new file mode 100644
index 000000000..32d2d3e33
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-v0_1.c
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/platform_device.h>
+#include "tsens.h"
+
+/* ----- SROT ------ */
+#define SROT_CTRL_OFF 0x0000
+
+/* ----- TM ------ */
+#define TM_INT_EN_OFF 0x0000
+#define TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF 0x0004
+#define TM_Sn_STATUS_OFF 0x0030
+#define TM_TRDY_OFF 0x005c
+
+/* extra data for 8974 */
+#define BKP_SEL 0x3
+#define BKP_REDUN_SEL 0xe0000000
+
+#define BIT_APPEND 0x3
+
+static struct tsens_legacy_calibration_format tsens_8916_nvmem = {
+ .base_len = 7,
+ .base_shift = 3,
+ .sp_len = 5,
+ .mode = { 0, 29, 1 },
+ .invalid = { 0, 31, 1 },
+ .base = { { 0, 0 }, { 1, 25 } },
+ .sp = {
+ { { 0, 7 }, { 0, 12 } },
+ { { 0, 17 }, { 0, 22 } },
+ { { 0, 27 }, { 1, 0 } },
+ { { 1, 5 }, { 1, 10 } },
+ { { 1, 15 }, { 1, 20 } },
+ },
+};
+
+static struct tsens_legacy_calibration_format tsens_8974_nvmem = {
+ .base_len = 8,
+ .base_shift = 2,
+ .sp_len = 6,
+ .mode = { 1, 30 },
+ .invalid = { 3, 30 },
+ .base = { { 0, 0 }, { 2, 12 } },
+ .sp = {
+ { { 0, 8 }, { 2, 20 } },
+ { { 0, 14 }, { 2, 26 } },
+ { { 0, 20 }, { 3, 0 } },
+ { { 0, 26 }, { 3, 6 } },
+ { { 1, 0 }, { 3, 12 } },
+ { { 1, 6 }, { 3, 18 } },
+ { { 1, 12 }, { 3, 24 } },
+ { { 1, 18 }, { 4, 0 } },
+ { { 1, 24 }, { 4, 6 } },
+ { { 2, 0 }, { 4, 12 } },
+ { { 2, 6 }, { 4, 18 } },
+ },
+};
+
+static struct tsens_legacy_calibration_format tsens_8974_backup_nvmem = {
+ .base_len = 8,
+ .base_shift = 2,
+ .sp_len = 6,
+ .mode = { 4, 30, 1 },
+ .invalid = { 5, 30, 1 },
+ .base = { { 0, 0 }, { 2, 18 } },
+ .sp = {
+ { { 0, 8 }, { 2, 26 } },
+ { { 0, 14 }, { 3, 0 } },
+ { { 0, 20 }, { 3, 6 } },
+ { { 0, 26 }, { 3, 12 } },
+ { { 1, 0 }, { 3, 18 } },
+ { { 1, 6 }, { 3, 24, 1 } },
+ { { 1, 12 }, { 4, 0, 1 } },
+ { { 1, 18 }, { 4, 6, 1 } },
+ { { 2, 0 }, { 4, 12, 1 } },
+ { { 2, 6 }, { 4, 18, 1 } },
+ { { 2, 12 }, { 4, 24, 1 } },
+ },
+};
+
+static int calibrate_8916(struct tsens_priv *priv)
+{
+ u32 p1[5], p2[5];
+ u32 *qfprom_cdata, *qfprom_csel;
+ int mode, ret;
+
+ ret = tsens_calibrate_nvmem(priv, 3);
+ if (!ret)
+ return 0;
+
+ qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
+
+ qfprom_csel = (u32 *)qfprom_read(priv->dev, "calib_sel");
+ if (IS_ERR(qfprom_csel)) {
+ kfree(qfprom_cdata);
+ return PTR_ERR(qfprom_csel);
+ }
+
+ mode = tsens_read_calibration_legacy(priv, &tsens_8916_nvmem,
+ p1, p2,
+ qfprom_cdata, qfprom_csel);
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(qfprom_cdata);
+ kfree(qfprom_csel);
+
+ return 0;
+}
+
+static void fixup_8974_points(int mode, u32 *p1, u32 *p2)
+{
+ int i;
+
+ if (mode == NO_PT_CALIB) {
+ p1[0] += 2;
+ p1[1] += 9;
+ p1[2] += 3;
+ p1[3] += 9;
+ p1[4] += 5;
+ p1[5] += 9;
+ p1[6] += 7;
+ p1[7] += 10;
+ p1[8] += 8;
+ p1[9] += 9;
+ p1[10] += 8;
+ } else {
+ for (i = 0; i < 11; i++) {
+ /*
+ * ONE_PT_CALIB requires using addition here instead of
+ * using OR operation.
+ */
+ p1[i] += BIT_APPEND;
+ p2[i] += BIT_APPEND;
+ }
+ }
+
+}
+
+static int calibrate_8974_nvmem(struct tsens_priv *priv)
+{
+ u32 p1[11], p2[11];
+ u32 backup;
+ int ret, mode;
+
+ ret = nvmem_cell_read_variable_le_u32(priv->dev, "use_backup", &backup);
+ if (ret == -ENOENT)
+ dev_warn(priv->dev, "Please migrate to separate nvmem cells for calibration data\n");
+ if (ret < 0)
+ return ret;
+
+ mode = tsens_read_calibration(priv, 2, p1, p2, backup == BKP_SEL);
+ if (mode < 0)
+ return mode;
+
+ fixup_8974_points(mode, p1, p2);
+
+ compute_intercept_slope(priv, p1, p2, mode);
+
+ return 0;
+}
+
+static int calibrate_8974(struct tsens_priv *priv)
+{
+ u32 p1[11], p2[11];
+ u32 *calib, *bkp;
+ u32 calib_redun_sel;
+ int mode, ret;
+
+ ret = calibrate_8974_nvmem(priv);
+ if (ret == 0)
+ return 0;
+
+ calib = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(calib))
+ return PTR_ERR(calib);
+
+ bkp = (u32 *)qfprom_read(priv->dev, "calib_backup");
+ if (IS_ERR(bkp)) {
+ kfree(calib);
+ return PTR_ERR(bkp);
+ }
+
+ calib_redun_sel = FIELD_GET(BKP_REDUN_SEL, bkp[1]);
+
+ if (calib_redun_sel == BKP_SEL)
+ mode = tsens_read_calibration_legacy(priv, &tsens_8974_backup_nvmem,
+ p1, p2,
+ bkp, calib);
+ else
+ mode = tsens_read_calibration_legacy(priv, &tsens_8974_nvmem,
+ p1, p2,
+ calib, NULL);
+
+ fixup_8974_points(mode, p1, p2);
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(calib);
+ kfree(bkp);
+
+ return 0;
+}
+
+static int __init init_8226(struct tsens_priv *priv)
+{
+ priv->sensor[0].slope = 2901;
+ priv->sensor[1].slope = 2846;
+ priv->sensor[2].slope = 3038;
+ priv->sensor[3].slope = 2955;
+ priv->sensor[4].slope = 2901;
+ priv->sensor[5].slope = 2846;
+
+ return init_common(priv);
+}
+
+static int __init init_8909(struct tsens_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < priv->num_sensors; ++i)
+ priv->sensor[i].slope = 3000;
+
+ priv->sensor[0].p1_calib_offset = 0;
+ priv->sensor[0].p2_calib_offset = 0;
+ priv->sensor[1].p1_calib_offset = -10;
+ priv->sensor[1].p2_calib_offset = -6;
+ priv->sensor[2].p1_calib_offset = 0;
+ priv->sensor[2].p2_calib_offset = 0;
+ priv->sensor[3].p1_calib_offset = -9;
+ priv->sensor[3].p2_calib_offset = -9;
+ priv->sensor[4].p1_calib_offset = -8;
+ priv->sensor[4].p2_calib_offset = -10;
+
+ return init_common(priv);
+}
+
+static int __init init_8939(struct tsens_priv *priv) {
+ priv->sensor[0].slope = 2911;
+ priv->sensor[1].slope = 2789;
+ priv->sensor[2].slope = 2906;
+ priv->sensor[3].slope = 2763;
+ priv->sensor[4].slope = 2922;
+ priv->sensor[5].slope = 2867;
+ priv->sensor[6].slope = 2833;
+ priv->sensor[7].slope = 2838;
+ priv->sensor[8].slope = 2840;
+ /* priv->sensor[9].slope = 2852; */
+
+ return init_common(priv);
+}
+
+static int __init init_9607(struct tsens_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < priv->num_sensors; ++i)
+ priv->sensor[i].slope = 3000;
+
+ priv->sensor[0].p1_calib_offset = 1;
+ priv->sensor[0].p2_calib_offset = 1;
+ priv->sensor[1].p1_calib_offset = -4;
+ priv->sensor[1].p2_calib_offset = -2;
+ priv->sensor[2].p1_calib_offset = 4;
+ priv->sensor[2].p2_calib_offset = 8;
+ priv->sensor[3].p1_calib_offset = -3;
+ priv->sensor[3].p2_calib_offset = -5;
+ priv->sensor[4].p1_calib_offset = -4;
+ priv->sensor[4].p2_calib_offset = -4;
+
+ return init_common(priv);
+}
+
+/* v0.1: 8226, 8909, 8916, 8939, 8974, 9607 */
+
+static struct tsens_features tsens_v0_1_feat = {
+ .ver_major = VER_0_1,
+ .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_v0_1_regfields[MAX_REGFIELDS] = {
+ /* ----- SROT ------ */
+ /* No VERSION information */
+
+ /* CTRL_OFFSET */
+ [TSENS_EN] = REG_FIELD(SROT_CTRL_OFF, 0, 0),
+ [TSENS_SW_RST] = REG_FIELD(SROT_CTRL_OFF, 1, 1),
+
+ /* ----- TM ------ */
+ /* INTERRUPT ENABLE */
+ [INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 0),
+
+ /* UPPER/LOWER TEMPERATURE THRESHOLDS */
+ REG_FIELD_FOR_EACH_SENSOR11(LOW_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 0, 9),
+ REG_FIELD_FOR_EACH_SENSOR11(UP_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 10, 19),
+
+ /* UPPER/LOWER INTERRUPTS [CLEAR/STATUS] */
+ REG_FIELD_FOR_EACH_SENSOR11(LOW_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 20, 20),
+ REG_FIELD_FOR_EACH_SENSOR11(UP_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 21, 21),
+
+ /* NO CRITICAL INTERRUPT SUPPORT on v0.1 */
+
+ /* Sn_STATUS */
+ REG_FIELD_FOR_EACH_SENSOR11(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 9),
+ /* No VALID field on v0.1 */
+ /* xxx_STATUS bits: 1 == threshold violated */
+ REG_FIELD_FOR_EACH_SENSOR11(MIN_STATUS, TM_Sn_STATUS_OFF, 10, 10),
+ REG_FIELD_FOR_EACH_SENSOR11(LOWER_STATUS, TM_Sn_STATUS_OFF, 11, 11),
+ REG_FIELD_FOR_EACH_SENSOR11(UPPER_STATUS, TM_Sn_STATUS_OFF, 12, 12),
+ /* No CRITICAL field on v0.1 */
+ REG_FIELD_FOR_EACH_SENSOR11(MAX_STATUS, TM_Sn_STATUS_OFF, 13, 13),
+
+ /* TRDY: 1=ready, 0=in progress */
+ [TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
+};
+
+static const struct tsens_ops ops_8226 = {
+ .init = init_8226,
+ .calibrate = tsens_calibrate_common,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_8226 = {
+ .num_sensors = 6,
+ .ops = &ops_8226,
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
+
+static const struct tsens_ops ops_8909 = {
+ .init = init_8909,
+ .calibrate = tsens_calibrate_common,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_8909 = {
+ .num_sensors = 5,
+ .ops = &ops_8909,
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
+
+static const struct tsens_ops ops_8916 = {
+ .init = init_common,
+ .calibrate = calibrate_8916,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_8916 = {
+ .num_sensors = 5,
+ .ops = &ops_8916,
+ .hw_ids = (unsigned int []){0, 1, 2, 4, 5 },
+
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
+
+static const struct tsens_ops ops_8939 = {
+ .init = init_8939,
+ .calibrate = tsens_calibrate_common,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_8939 = {
+ .num_sensors = 9,
+ .ops = &ops_8939,
+ .hw_ids = (unsigned int []){ 0, 1, 2, 3, 5, 6, 7, 8, 9, /* 10 */ },
+
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
+
+static const struct tsens_ops ops_8974 = {
+ .init = init_common,
+ .calibrate = calibrate_8974,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_8974 = {
+ .num_sensors = 11,
+ .ops = &ops_8974,
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
+
+static const struct tsens_ops ops_9607 = {
+ .init = init_9607,
+ .calibrate = tsens_calibrate_common,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_9607 = {
+ .num_sensors = 5,
+ .ops = &ops_9607,
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
diff --git a/drivers/thermal/qcom/tsens-v1.c b/drivers/thermal/qcom/tsens-v1.c
new file mode 100644
index 000000000..faa5d0078
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-v1.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019, Linaro Limited
+ */
+
+#include <linux/bitops.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include "tsens.h"
+
+/* ----- SROT ------ */
+#define SROT_HW_VER_OFF 0x0000
+#define SROT_CTRL_OFF 0x0004
+
+/* ----- TM ------ */
+#define TM_INT_EN_OFF 0x0000
+#define TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF 0x0004
+#define TM_Sn_STATUS_OFF 0x0044
+#define TM_TRDY_OFF 0x0084
+#define TM_HIGH_LOW_INT_STATUS_OFF 0x0088
+#define TM_HIGH_LOW_Sn_INT_THRESHOLD_OFF 0x0090
+
+static struct tsens_legacy_calibration_format tsens_qcs404_nvmem = {
+ .base_len = 8,
+ .base_shift = 2,
+ .sp_len = 6,
+ .mode = { 4, 0 },
+ .invalid = { 4, 2 },
+ .base = { { 4, 3 }, { 4, 11 } },
+ .sp = {
+ { { 0, 0 }, { 0, 6 } },
+ { { 0, 12 }, { 0, 18 } },
+ { { 0, 24 }, { 0, 30 } },
+ { { 1, 4 }, { 1, 10 } },
+ { { 1, 16 }, { 1, 22 } },
+ { { 2, 0 }, { 2, 6 } },
+ { { 2, 12 }, { 2, 18 } },
+ { { 2, 24 }, { 2, 30 } },
+ { { 3, 4 }, { 3, 10 } },
+ { { 3, 16 }, { 3, 22 } },
+ },
+};
+
+static int calibrate_v1(struct tsens_priv *priv)
+{
+ u32 p1[10], p2[10];
+ u32 *qfprom_cdata;
+ int mode, ret;
+
+ ret = tsens_calibrate_common(priv);
+ if (!ret)
+ return 0;
+
+ qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
+
+ mode = tsens_read_calibration_legacy(priv, &tsens_qcs404_nvmem,
+ p1, p2,
+ qfprom_cdata, NULL);
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(qfprom_cdata);
+
+ return 0;
+}
+
+/* v1.x: msm8956,8976,qcs404,405 */
+
+static struct tsens_features tsens_v1_feat = {
+ .ver_major = VER_1_X,
+ .crit_int = 0,
+ .combo_int = 0,
+ .adc = 1,
+ .srot_split = 1,
+ .max_sensors = 11,
+ .trip_min_temp = -40000,
+ .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 */
+ [VER_MAJOR] = REG_FIELD(SROT_HW_VER_OFF, 28, 31),
+ [VER_MINOR] = REG_FIELD(SROT_HW_VER_OFF, 16, 27),
+ [VER_STEP] = REG_FIELD(SROT_HW_VER_OFF, 0, 15),
+ /* CTRL_OFFSET */
+ [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, 13),
+
+ /* ----- TM ------ */
+ /* INTERRUPT ENABLE */
+ [INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 0),
+
+ /* UPPER/LOWER TEMPERATURE THRESHOLDS */
+ REG_FIELD_FOR_EACH_SENSOR11(LOW_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 0, 9),
+ REG_FIELD_FOR_EACH_SENSOR11(UP_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 10, 19),
+
+ /* UPPER/LOWER INTERRUPTS [CLEAR/STATUS] */
+ REG_FIELD_FOR_EACH_SENSOR11(LOW_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 20, 20),
+ REG_FIELD_FOR_EACH_SENSOR11(UP_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 21, 21),
+ [LOW_INT_STATUS_0] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 0, 0),
+ [LOW_INT_STATUS_1] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 1, 1),
+ [LOW_INT_STATUS_2] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 2, 2),
+ [LOW_INT_STATUS_3] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 3, 3),
+ [LOW_INT_STATUS_4] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 4, 4),
+ [LOW_INT_STATUS_5] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 5, 5),
+ [LOW_INT_STATUS_6] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 6, 6),
+ [LOW_INT_STATUS_7] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 7, 7),
+ [UP_INT_STATUS_0] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 8, 8),
+ [UP_INT_STATUS_1] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 9, 9),
+ [UP_INT_STATUS_2] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 10, 10),
+ [UP_INT_STATUS_3] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 11, 11),
+ [UP_INT_STATUS_4] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 12, 12),
+ [UP_INT_STATUS_5] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 13, 13),
+ [UP_INT_STATUS_6] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 14, 14),
+ [UP_INT_STATUS_7] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 15, 15),
+
+ /* NO CRITICAL INTERRUPT SUPPORT on v1 */
+
+ /* Sn_STATUS */
+ REG_FIELD_FOR_EACH_SENSOR11(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 9),
+ REG_FIELD_FOR_EACH_SENSOR11(VALID, TM_Sn_STATUS_OFF, 14, 14),
+ /* xxx_STATUS bits: 1 == threshold violated */
+ REG_FIELD_FOR_EACH_SENSOR11(MIN_STATUS, TM_Sn_STATUS_OFF, 10, 10),
+ REG_FIELD_FOR_EACH_SENSOR11(LOWER_STATUS, TM_Sn_STATUS_OFF, 11, 11),
+ REG_FIELD_FOR_EACH_SENSOR11(UPPER_STATUS, TM_Sn_STATUS_OFF, 12, 12),
+ /* No CRITICAL field on v1.x */
+ REG_FIELD_FOR_EACH_SENSOR11(MAX_STATUS, TM_Sn_STATUS_OFF, 13, 13),
+
+ /* TRDY: 1=ready, 0=in progress */
+ [TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
+};
+
+static int __init init_8956(struct tsens_priv *priv) {
+ priv->sensor[0].slope = 3313;
+ priv->sensor[1].slope = 3275;
+ priv->sensor[2].slope = 3320;
+ priv->sensor[3].slope = 3246;
+ priv->sensor[4].slope = 3279;
+ priv->sensor[5].slope = 3257;
+ priv->sensor[6].slope = 3234;
+ priv->sensor[7].slope = 3269;
+ priv->sensor[8].slope = 3255;
+ priv->sensor[9].slope = 3239;
+ priv->sensor[10].slope = 3286;
+
+ 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,
+ .get_temp = get_temp_tsens_valid,
+};
+
+struct tsens_plat_data data_tsens_v1 = {
+ .ops = &ops_generic_v1,
+ .feat = &tsens_v1_feat,
+ .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,
+ .get_temp = get_temp_tsens_valid,
+};
+
+struct tsens_plat_data data_8956 = {
+ .num_sensors = 11,
+ .ops = &ops_8956,
+ .feat = &tsens_v1_feat,
+ .fields = tsens_v1_regfields,
+};
+
+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,
+};
+
+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
new file mode 100644
index 000000000..2ee117aa9
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * 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
+#define TM_UPPER_LOWER_INT_STATUS_OFF 0x0008
+#define TM_UPPER_LOWER_INT_CLEAR_OFF 0x000c
+#define TM_UPPER_LOWER_INT_MASK_OFF 0x0010
+#define TM_CRITICAL_INT_STATUS_OFF 0x0014
+#define TM_CRITICAL_INT_CLEAR_OFF 0x0018
+#define TM_CRITICAL_INT_MASK_OFF 0x001c
+#define TM_Sn_UPPER_LOWER_THRESHOLD_OFF 0x0020
+#define TM_Sn_CRITICAL_THRESHOLD_OFF 0x0060
+#define TM_Sn_STATUS_OFF 0x00a0
+#define TM_TRDY_OFF 0x00e4
+#define TM_WDOG_LOG_OFF 0x013c
+
+/* v2.x: 8996, 8998, sdm845 */
+
+static struct tsens_features tsens_v2_feat = {
+ .ver_major = VER_2_X,
+ .crit_int = 1,
+ .combo_int = 0,
+ .adc = 0,
+ .srot_split = 1,
+ .max_sensors = 16,
+ .trip_min_temp = -40000,
+ .trip_max_temp = 120000,
+};
+
+static struct tsens_features ipq8074_feat = {
+ .ver_major = VER_2_X,
+ .crit_int = 1,
+ .combo_int = 1,
+ .adc = 0,
+ .srot_split = 1,
+ .max_sensors = 16,
+ .trip_min_temp = 0,
+ .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 */
+ [VER_MAJOR] = REG_FIELD(SROT_HW_VER_OFF, 28, 31),
+ [VER_MINOR] = REG_FIELD(SROT_HW_VER_OFF, 16, 27),
+ [VER_STEP] = REG_FIELD(SROT_HW_VER_OFF, 0, 15),
+ /* 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 */
+ /* v2 has separate enables for UPPER/LOWER/CRITICAL interrupts */
+ [INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 2),
+
+ /* TEMPERATURE THRESHOLDS */
+ REG_FIELD_FOR_EACH_SENSOR16(LOW_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 0, 11),
+ REG_FIELD_FOR_EACH_SENSOR16(UP_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 12, 23),
+ REG_FIELD_FOR_EACH_SENSOR16(CRIT_THRESH, TM_Sn_CRITICAL_THRESHOLD_OFF, 0, 11),
+
+ /* INTERRUPTS [CLEAR/STATUS/MASK] */
+ REG_FIELD_SPLIT_BITS_0_15(LOW_INT_STATUS, TM_UPPER_LOWER_INT_STATUS_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(LOW_INT_CLEAR, TM_UPPER_LOWER_INT_CLEAR_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(LOW_INT_MASK, TM_UPPER_LOWER_INT_MASK_OFF),
+ REG_FIELD_SPLIT_BITS_16_31(UP_INT_STATUS, TM_UPPER_LOWER_INT_STATUS_OFF),
+ REG_FIELD_SPLIT_BITS_16_31(UP_INT_CLEAR, TM_UPPER_LOWER_INT_CLEAR_OFF),
+ REG_FIELD_SPLIT_BITS_16_31(UP_INT_MASK, TM_UPPER_LOWER_INT_MASK_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(CRIT_INT_STATUS, TM_CRITICAL_INT_STATUS_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(CRIT_INT_CLEAR, TM_CRITICAL_INT_CLEAR_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(CRIT_INT_MASK, TM_CRITICAL_INT_MASK_OFF),
+
+ /* WATCHDOG on v2.3 or later */
+ [WDOG_BARK_STATUS] = REG_FIELD(TM_CRITICAL_INT_STATUS_OFF, 31, 31),
+ [WDOG_BARK_CLEAR] = REG_FIELD(TM_CRITICAL_INT_CLEAR_OFF, 31, 31),
+ [WDOG_BARK_MASK] = REG_FIELD(TM_CRITICAL_INT_MASK_OFF, 31, 31),
+ [CC_MON_STATUS] = REG_FIELD(TM_CRITICAL_INT_STATUS_OFF, 30, 30),
+ [CC_MON_CLEAR] = REG_FIELD(TM_CRITICAL_INT_CLEAR_OFF, 30, 30),
+ [CC_MON_MASK] = REG_FIELD(TM_CRITICAL_INT_MASK_OFF, 30, 30),
+ [WDOG_BARK_COUNT] = REG_FIELD(TM_WDOG_LOG_OFF, 0, 7),
+
+ /* Sn_STATUS */
+ REG_FIELD_FOR_EACH_SENSOR16(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 11),
+ REG_FIELD_FOR_EACH_SENSOR16(VALID, TM_Sn_STATUS_OFF, 21, 21),
+ /* xxx_STATUS bits: 1 == threshold violated */
+ REG_FIELD_FOR_EACH_SENSOR16(MIN_STATUS, TM_Sn_STATUS_OFF, 16, 16),
+ REG_FIELD_FOR_EACH_SENSOR16(LOWER_STATUS, TM_Sn_STATUS_OFF, 17, 17),
+ REG_FIELD_FOR_EACH_SENSOR16(UPPER_STATUS, TM_Sn_STATUS_OFF, 18, 18),
+ REG_FIELD_FOR_EACH_SENSOR16(CRITICAL_STATUS, TM_Sn_STATUS_OFF, 19, 19),
+ REG_FIELD_FOR_EACH_SENSOR16(MAX_STATUS, TM_Sn_STATUS_OFF, 20, 20),
+
+ /* TRDY: 1=ready, 0=in progress */
+ [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,
+};
+
+struct tsens_plat_data data_tsens_v2 = {
+ .ops = &ops_generic_v2,
+ .feat = &tsens_v2_feat,
+ .fields = tsens_v2_regfields,
+};
+
+struct tsens_plat_data data_ipq8074 = {
+ .ops = &ops_generic_v2,
+ .feat = &ipq8074_feat,
+ .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,
+ .ops = &ops_generic_v2,
+ .feat = &tsens_v2_feat,
+ .fields = tsens_v2_regfields,
+};
+
+/* Do not enable wakeup capable interrupts for automotive platforms */
+struct tsens_plat_data data_automotive_v2 = {
+ .ops = &ops_generic_v2,
+ .feat = &tsens_v2_feat,
+ .fields = tsens_v2_regfields,
+ .no_irq_wake = true,
+};
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
new file mode 100644
index 000000000..b5ec70201
--- /dev/null
+++ b/drivers/thermal/qcom/tsens.c
@@ -0,0 +1,1478 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2019, 2020, Linaro Ltd.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/thermal.h>
+#include "../thermal_hwmon.h"
+#include "tsens.h"
+
+/**
+ * struct tsens_irq_data - IRQ status and temperature violations
+ * @up_viol: upper threshold violated
+ * @up_thresh: upper threshold temperature value
+ * @up_irq_mask: mask register for upper threshold irqs
+ * @up_irq_clear: clear register for upper threshold irqs
+ * @low_viol: lower threshold violated
+ * @low_thresh: lower threshold temperature value
+ * @low_irq_mask: mask register for lower threshold irqs
+ * @low_irq_clear: clear register for lower threshold irqs
+ * @crit_viol: critical threshold violated
+ * @crit_thresh: critical threshold temperature value
+ * @crit_irq_mask: mask register for critical threshold irqs
+ * @crit_irq_clear: clear register for critical threshold irqs
+ *
+ * Structure containing data about temperature threshold settings and
+ * irq status if they were violated.
+ */
+struct tsens_irq_data {
+ u32 up_viol;
+ int up_thresh;
+ u32 up_irq_mask;
+ u32 up_irq_clear;
+ u32 low_viol;
+ int low_thresh;
+ u32 low_irq_mask;
+ u32 low_irq_clear;
+ u32 crit_viol;
+ u32 crit_thresh;
+ u32 crit_irq_mask;
+ u32 crit_irq_clear;
+};
+
+char *qfprom_read(struct device *dev, const char *cname)
+{
+ struct nvmem_cell *cell;
+ ssize_t data;
+ char *ret;
+
+ cell = nvmem_cell_get(dev, cname);
+ if (IS_ERR(cell))
+ return ERR_CAST(cell);
+
+ ret = nvmem_cell_read(cell, &data);
+ nvmem_cell_put(cell);
+
+ return ret;
+}
+
+int tsens_read_calibration(struct tsens_priv *priv, int shift, u32 *p1, u32 *p2, bool backup)
+{
+ u32 mode;
+ u32 base1, base2;
+ char name[] = "sXX_pY_backup"; /* s10_p1_backup */
+ int i, ret;
+
+ if (priv->num_sensors > MAX_SENSORS)
+ return -EINVAL;
+
+ ret = snprintf(name, sizeof(name), "mode%s", backup ? "_backup" : "");
+ if (ret < 0)
+ return ret;
+
+ ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &mode);
+ if (ret == -ENOENT)
+ dev_warn(priv->dev, "Please migrate to separate nvmem cells for calibration data\n");
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ ret = snprintf(name, sizeof(name), "base1%s", backup ? "_backup" : "");
+ if (ret < 0)
+ return ret;
+
+ ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &base1);
+ if (ret < 0)
+ return ret;
+
+ ret = snprintf(name, sizeof(name), "base2%s", backup ? "_backup" : "");
+ if (ret < 0)
+ return ret;
+
+ ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &base2);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ ret = snprintf(name, sizeof(name), "s%d_p1%s", priv->sensor[i].hw_id,
+ backup ? "_backup" : "");
+ if (ret < 0)
+ return ret;
+
+ ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &p1[i]);
+ if (ret)
+ return ret;
+
+ ret = snprintf(name, sizeof(name), "s%d_p2%s", priv->sensor[i].hw_id,
+ backup ? "_backup" : "");
+ if (ret < 0)
+ return ret;
+
+ ret = nvmem_cell_read_variable_le_u32(priv->dev, name, &p2[i]);
+ if (ret)
+ return ret;
+ }
+
+ switch (mode) {
+ case ONE_PT_CALIB:
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = p1[i] + (base1 << shift);
+ break;
+ case TWO_PT_CALIB:
+ case TWO_PT_CALIB_NO_OFFSET:
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = (p2[i] + base2) << shift;
+ fallthrough;
+ case ONE_PT_CALIB2:
+ case ONE_PT_CALIB2_NO_OFFSET:
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = (p1[i] + base1) << shift;
+ break;
+ default:
+ dev_dbg(priv->dev, "calibrationless mode\n");
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ }
+
+ /* Apply calibration offset workaround except for _NO_OFFSET modes */
+ switch (mode) {
+ case TWO_PT_CALIB:
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] += priv->sensor[i].p2_calib_offset;
+ fallthrough;
+ case ONE_PT_CALIB2:
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] += priv->sensor[i].p1_calib_offset;
+ break;
+ }
+
+ return mode;
+}
+
+int tsens_calibrate_nvmem(struct tsens_priv *priv, int shift)
+{
+ u32 p1[MAX_SENSORS], p2[MAX_SENSORS];
+ int mode;
+
+ mode = tsens_read_calibration(priv, shift, p1, p2, false);
+ if (mode < 0)
+ return mode;
+
+ compute_intercept_slope(priv, p1, p2, mode);
+
+ return 0;
+}
+
+int tsens_calibrate_common(struct tsens_priv *priv)
+{
+ return tsens_calibrate_nvmem(priv, 2);
+}
+
+static u32 tsens_read_cell(const struct tsens_single_value *cell, u8 len, u32 *data0, u32 *data1)
+{
+ u32 val;
+ u32 *data = cell->blob ? data1 : data0;
+
+ if (cell->shift + len <= 32) {
+ val = data[cell->idx] >> cell->shift;
+ } else {
+ u8 part = 32 - cell->shift;
+
+ val = data[cell->idx] >> cell->shift;
+ val |= data[cell->idx + 1] << part;
+ }
+
+ return val & ((1 << len) - 1);
+}
+
+int tsens_read_calibration_legacy(struct tsens_priv *priv,
+ const struct tsens_legacy_calibration_format *format,
+ u32 *p1, u32 *p2,
+ u32 *cdata0, u32 *cdata1)
+{
+ u32 mode, invalid;
+ u32 base1, base2;
+ int i;
+
+ mode = tsens_read_cell(&format->mode, 2, cdata0, cdata1);
+ invalid = tsens_read_cell(&format->invalid, 1, cdata0, cdata1);
+ if (invalid)
+ mode = NO_PT_CALIB;
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ base1 = tsens_read_cell(&format->base[0], format->base_len, cdata0, cdata1);
+ base2 = tsens_read_cell(&format->base[1], format->base_len, cdata0, cdata1);
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = tsens_read_cell(&format->sp[i][0], format->sp_len, cdata0, cdata1);
+ p2[i] = tsens_read_cell(&format->sp[i][1], format->sp_len, cdata0, cdata1);
+ }
+
+ switch (mode) {
+ case ONE_PT_CALIB:
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = p1[i] + (base1 << format->base_shift);
+ break;
+ case TWO_PT_CALIB:
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = (p2[i] + base2) << format->base_shift;
+ fallthrough;
+ case ONE_PT_CALIB2:
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = (p1[i] + base1) << format->base_shift;
+ break;
+ default:
+ dev_dbg(priv->dev, "calibrationless mode\n");
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ }
+
+ return mode;
+}
+
+/*
+ * Use this function on devices where slope and offset calculations
+ * depend on calibration data read from qfprom. On others the slope
+ * and offset values are derived from tz->tzp->slope and tz->tzp->offset
+ * resp.
+ */
+void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
+ u32 *p2, u32 mode)
+{
+ int i;
+ int num, den;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ dev_dbg(priv->dev,
+ "%s: sensor%d - data_point1:%#x data_point2:%#x\n",
+ __func__, i, p1[i], p2 ? p2[i] : 0);
+
+ if (!priv->sensor[i].slope)
+ priv->sensor[i].slope = SLOPE_DEFAULT;
+ if (mode == TWO_PT_CALIB || mode == TWO_PT_CALIB_NO_OFFSET) {
+ /*
+ * slope (m) = adc_code2 - adc_code1 (y2 - y1)/
+ * temp_120_degc - temp_30_degc (x2 - x1)
+ */
+ num = p2[i] - p1[i];
+ num *= SLOPE_FACTOR;
+ den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
+ priv->sensor[i].slope = num / den;
+ }
+
+ priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
+ (CAL_DEGC_PT1 *
+ priv->sensor[i].slope);
+ dev_dbg(priv->dev, "%s: offset:%d\n", __func__,
+ priv->sensor[i].offset);
+ }
+}
+
+static inline u32 degc_to_code(int degc, const struct tsens_sensor *s)
+{
+ u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR);
+
+ pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc);
+ return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE);
+}
+
+static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
+{
+ int degc, num, den;
+
+ num = (adc_code * SLOPE_FACTOR) - s->offset;
+ den = s->slope;
+
+ if (num > 0)
+ degc = num + (den / 2);
+ else if (num < 0)
+ degc = num - (den / 2);
+ else
+ degc = num;
+
+ degc /= den;
+
+ return degc;
+}
+
+/**
+ * tsens_read_temp - Retrieve temperature readings from the hardware.
+ * @s: Pointer to sensor struct
+ * @field: Index into regmap_field array pointing to temperature data
+ * @temp: temperature in deciCelsius to be read from hardware
+ *
+ * This function handles temperature returned in ADC code or deciCelsius
+ * depending on IP version.
+ *
+ * Return: 0 on success, a negative errno will be returned in error cases
+ */
+static int tsens_read_temp(const struct tsens_sensor *s, int field, int *temp)
+{
+ struct tsens_priv *priv = s->priv;
+ int temp_val[MAX_READ_RETRY] = {0};
+ u32 status;
+ int ret;
+ u32 last_temp_mask = GENMASK(priv->fields[LAST_TEMP_0].msb,
+ priv->fields[LAST_TEMP_0].lsb);
+ u32 valid_bit = priv->rf[VALID_0] ? BIT(priv->fields[VALID_0].lsb) : 0;
+
+ for (int i = 0; i < MAX_READ_RETRY; i++) {
+ ret = regmap_read(priv->tm_map, priv->fields[field].reg, &status);
+ if (ret)
+ return ret;
+
+ /* VER_0 doesn't have a VALID bit */
+ if (!valid_bit) {
+ *temp = status & last_temp_mask;
+ return 0;
+ }
+
+ temp_val[i] = status & last_temp_mask;
+
+ if (status & valid_bit) {
+ *temp = temp_val[i];
+ return 0;
+ }
+ }
+
+ /*
+ * As per the HW guidelines, if none of the attempts observe a
+ * valid sample, a stable fallback value must be returned. If the
+ * first and second samples match, the second value is returned;
+ * otherwise, if the second and third samples match, the third
+ * value is returned.
+ */
+ if (temp_val[0] == temp_val[1])
+ *temp = temp_val[1];
+ else if (temp_val[1] == temp_val[2])
+ *temp = temp_val[2];
+ else
+ return -EAGAIN;
+
+ return 0;
+}
+
+/**
+ * tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
+ * @s: Pointer to sensor struct
+ * @temp: temperature in milliCelsius to be read from hardware
+ *
+ * This function handles temperature returned in ADC code or deciCelsius
+ * depending on IP version.
+ *
+ * Return: Temperature in milliCelsius on success, a negative errno will
+ * be returned in error cases
+ */
+static int tsens_hw_to_mC(const struct tsens_sensor *s, int temp)
+{
+ struct tsens_priv *priv = s->priv;
+ u32 resolution;
+
+ resolution = priv->fields[LAST_TEMP_0].msb -
+ priv->fields[LAST_TEMP_0].lsb;
+
+ /* Convert temperature from ADC code to milliCelsius */
+ if (priv->feat->adc)
+ return code_to_degc(temp, s) * 1000;
+
+ /* deciCelsius -> milliCelsius along with sign extension */
+ return sign_extend32(temp, resolution) * 100;
+}
+
+/**
+ * tsens_mC_to_hw - Convert temperature to hardware register value
+ * @s: Pointer to sensor struct
+ * @temp: temperature in milliCelsius to be programmed to hardware
+ *
+ * This function outputs the value to be written to hardware in ADC code
+ * or deciCelsius depending on IP version.
+ *
+ * Return: ADC code or temperature in deciCelsius.
+ */
+static int tsens_mC_to_hw(const struct tsens_sensor *s, int temp)
+{
+ struct tsens_priv *priv = s->priv;
+
+ /* milliC to adc code */
+ if (priv->feat->adc)
+ return degc_to_code(temp / 1000, s);
+
+ /* milliC to deciC */
+ return temp / 100;
+}
+
+static inline enum tsens_ver tsens_version(struct tsens_priv *priv)
+{
+ return priv->feat->ver_major;
+}
+
+static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
+ enum tsens_irq_type irq_type, bool enable)
+{
+ u32 index = 0;
+
+ switch (irq_type) {
+ case UPPER:
+ index = UP_INT_CLEAR_0 + hw_id;
+ break;
+ case LOWER:
+ index = LOW_INT_CLEAR_0 + hw_id;
+ break;
+ case CRITICAL:
+ /* No critical interrupts before v2 */
+ return;
+ }
+ regmap_field_write(priv->rf[index], enable ? 0 : 1);
+}
+
+static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
+ enum tsens_irq_type irq_type, bool enable)
+{
+ u32 index_mask = 0, index_clear = 0;
+
+ /*
+ * To enable the interrupt flag for a sensor:
+ * - clear the mask bit
+ * To disable the interrupt flag for a sensor:
+ * - Mask further interrupts for this sensor
+ * - Write 1 followed by 0 to clear the interrupt
+ */
+ switch (irq_type) {
+ case UPPER:
+ index_mask = UP_INT_MASK_0 + hw_id;
+ index_clear = UP_INT_CLEAR_0 + hw_id;
+ break;
+ case LOWER:
+ index_mask = LOW_INT_MASK_0 + hw_id;
+ index_clear = LOW_INT_CLEAR_0 + hw_id;
+ break;
+ case CRITICAL:
+ index_mask = CRIT_INT_MASK_0 + hw_id;
+ index_clear = CRIT_INT_CLEAR_0 + hw_id;
+ break;
+ }
+
+ if (enable) {
+ regmap_field_write(priv->rf[index_mask], 0);
+ } else {
+ regmap_field_write(priv->rf[index_mask], 1);
+ regmap_field_write(priv->rf[index_clear], 1);
+ regmap_field_write(priv->rf[index_clear], 0);
+ }
+}
+
+/**
+ * tsens_set_interrupt - Set state of an interrupt
+ * @priv: Pointer to tsens controller private data
+ * @hw_id: Hardware ID aka. sensor number
+ * @irq_type: irq_type from enum tsens_irq_type
+ * @enable: false = disable, true = enable
+ *
+ * Call IP-specific function to set state of an interrupt
+ *
+ * Return: void
+ */
+static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
+ enum tsens_irq_type irq_type, bool enable)
+{
+ dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
+ irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
+ enable ? "en" : "dis");
+ if (tsens_version(priv) >= VER_2_X)
+ tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
+ else
+ tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
+}
+
+/**
+ * tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
+ * @priv: Pointer to tsens controller private data
+ * @hw_id: Hardware ID aka. sensor number
+ * @d: Pointer to irq state data
+ *
+ * Return: 0 if threshold was not violated, 1 if it was violated and negative
+ * errno in case of errors
+ */
+static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id,
+ struct tsens_irq_data *d)
+{
+ int ret;
+
+ ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol);
+ if (ret)
+ return ret;
+
+ if (priv->feat->crit_int) {
+ ret = regmap_field_read(priv->rf[CRITICAL_STATUS_0 + hw_id],
+ &d->crit_viol);
+ if (ret)
+ return ret;
+ }
+
+ if (d->up_viol || d->low_viol || d->crit_viol)
+ return 1;
+
+ return 0;
+}
+
+static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
+ const struct tsens_sensor *s,
+ struct tsens_irq_data *d)
+{
+ int ret;
+
+ ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear);
+ if (ret)
+ return ret;
+ if (tsens_version(priv) >= VER_2_X) {
+ ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id],
+ &d->crit_irq_clear);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id],
+ &d->crit_irq_mask);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[CRIT_THRESH_0 + hw_id], &d->crit_thresh);
+ if (ret)
+ return ret;
+ d->crit_thresh = tsens_hw_to_mC(s, d->crit_thresh);
+ } else {
+ /* No mask register on older TSENS */
+ d->up_irq_mask = 0;
+ d->low_irq_mask = 0;
+ d->crit_irq_clear = 0;
+ d->crit_irq_mask = 0;
+ d->crit_thresh = 0;
+ }
+
+ ret = regmap_field_read(priv->rf[UP_THRESH_0 + hw_id], &d->up_thresh);
+ if (ret)
+ return ret;
+
+ d->up_thresh = tsens_hw_to_mC(s, d->up_thresh);
+ ret = regmap_field_read(priv->rf[LOW_THRESH_0 + hw_id], &d->low_thresh);
+ if (ret)
+ return ret;
+
+ d->low_thresh = tsens_hw_to_mC(s, d->low_thresh);
+
+ dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n",
+ hw_id, __func__,
+ (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
+ d->low_viol, d->up_viol, d->crit_viol,
+ d->low_irq_clear, d->up_irq_clear, d->crit_irq_clear,
+ d->low_irq_mask, d->up_irq_mask, d->crit_irq_mask);
+ dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d:%d)\n", hw_id, __func__,
+ (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
+ d->low_thresh, d->up_thresh, d->crit_thresh);
+
+ return 0;
+}
+
+static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
+{
+ if (ver >= VER_2_X)
+ return mask & (1 << hw_id);
+
+ /* v1, v0.1 don't have a irq mask register */
+ return 0;
+}
+
+/**
+ * tsens_critical_irq_thread() - Threaded handler for critical interrupts
+ * @irq: irq number
+ * @data: tsens controller private data
+ *
+ * Check FSM watchdog bark status and clear if needed.
+ * Check all sensors to find ones that violated their critical threshold limits.
+ * Clear and then re-enable the interrupt.
+ *
+ * The level-triggered interrupt might deassert if the temperature returned to
+ * within the threshold limits by the time the handler got scheduled. We
+ * consider the irq to have been handled in that case.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t tsens_critical_irq_thread(int irq, void *data)
+{
+ struct tsens_priv *priv = data;
+ struct tsens_irq_data d;
+ int temp, ret, i;
+ u32 wdog_status, wdog_count;
+
+ if (priv->feat->has_watchdog) {
+ ret = regmap_field_read(priv->rf[WDOG_BARK_STATUS],
+ &wdog_status);
+ if (ret)
+ return ret;
+
+ if (wdog_status) {
+ /* Clear WDOG interrupt */
+ regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 1);
+ regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 0);
+ ret = regmap_field_read(priv->rf[WDOG_BARK_COUNT],
+ &wdog_count);
+ if (ret)
+ return ret;
+ if (wdog_count)
+ dev_dbg(priv->dev, "%s: watchdog count: %d\n",
+ __func__, wdog_count);
+
+ /* Fall through to handle critical interrupts if any */
+ }
+ }
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ const struct tsens_sensor *s = &priv->sensor[i];
+ u32 hw_id = s->hw_id;
+
+ if (!s->tzd)
+ continue;
+ if (!tsens_threshold_violated(priv, hw_id, &d))
+ continue;
+ ret = get_temp_tsens_valid(s, &temp);
+ if (ret) {
+ dev_err(priv->dev, "[%u] %s: error reading sensor\n",
+ hw_id, __func__);
+ continue;
+ }
+
+ tsens_read_irq_state(priv, hw_id, s, &d);
+ if (d.crit_viol &&
+ !masked_irq(hw_id, d.crit_irq_mask, tsens_version(priv))) {
+ /* Mask critical interrupts, unused on Linux */
+ tsens_set_interrupt(priv, hw_id, CRITICAL, false);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * tsens_irq_thread - Threaded interrupt handler for uplow interrupts
+ * @irq: irq number
+ * @data: tsens controller private data
+ *
+ * Check all sensors to find ones that violated their threshold limits. If the
+ * temperature is still outside the limits, call thermal_zone_device_update() to
+ * update the thresholds, else re-enable the interrupts.
+ *
+ * The level-triggered interrupt might deassert if the temperature returned to
+ * within the threshold limits by the time the handler got scheduled. We
+ * consider the irq to have been handled in that case.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t tsens_irq_thread(int irq, void *data)
+{
+ struct tsens_priv *priv = data;
+ struct tsens_irq_data d;
+ int i;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ const struct tsens_sensor *s = &priv->sensor[i];
+ u32 hw_id = s->hw_id;
+
+ if (!s->tzd)
+ continue;
+ if (!tsens_threshold_violated(priv, hw_id, &d))
+ continue;
+
+ thermal_zone_device_update(s->tzd, THERMAL_EVENT_UNSPECIFIED);
+
+ if (tsens_version(priv) < VER_0_1) {
+ /* Constraint: There is only 1 interrupt control register for all
+ * 11 temperature sensor. So monitoring more than 1 sensor based
+ * on interrupts will yield inconsistent result. To overcome this
+ * issue we will monitor only sensor 0 which is the master sensor.
+ */
+ break;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * tsens_combined_irq_thread() - Threaded interrupt handler for combined interrupts
+ * @irq: irq number
+ * @data: tsens controller private data
+ *
+ * Handle the combined interrupt as if it were 2 separate interrupts, so call the
+ * critical handler first and then the up/low one.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t tsens_combined_irq_thread(int irq, void *data)
+{
+ irqreturn_t ret;
+
+ ret = tsens_critical_irq_thread(irq, data);
+ if (ret != IRQ_HANDLED)
+ return ret;
+
+ return tsens_irq_thread(irq, data);
+}
+
+static int tsens_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct tsens_sensor *s = thermal_zone_device_priv(tz);
+ struct tsens_priv *priv = s->priv;
+ struct device *dev = priv->dev;
+ struct tsens_irq_data d;
+ unsigned long flags;
+ int high_val, low_val, cl_high, cl_low;
+ u32 hw_id = s->hw_id;
+
+ if (tsens_version(priv) < VER_0_1) {
+ /* Pre v0.1 IP had a single register for each type of interrupt
+ * and thresholds
+ */
+ hw_id = 0;
+ }
+
+ dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
+ hw_id, __func__, low, high);
+
+ cl_high = clamp_val(high, priv->feat->trip_min_temp, priv->feat->trip_max_temp);
+ cl_low = clamp_val(low, priv->feat->trip_min_temp, priv->feat->trip_max_temp);
+
+ high_val = tsens_mC_to_hw(s, cl_high);
+ low_val = tsens_mC_to_hw(s, cl_low);
+
+ spin_lock_irqsave(&priv->ul_lock, flags);
+
+ tsens_read_irq_state(priv, hw_id, s, &d);
+
+ /* Write the new thresholds and clear the status */
+ regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val);
+ regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val);
+ tsens_set_interrupt(priv, hw_id, LOWER, true);
+ tsens_set_interrupt(priv, hw_id, UPPER, true);
+
+ spin_unlock_irqrestore(&priv->ul_lock, flags);
+
+ dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
+ hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
+
+ return 0;
+}
+
+static int tsens_enable_irq(struct tsens_priv *priv)
+{
+ int ret;
+ int val = tsens_version(priv) >= VER_2_X ? 7 : 1;
+
+ ret = regmap_field_write(priv->rf[INT_EN], val);
+ if (ret < 0)
+ dev_err(priv->dev, "%s: failed to enable interrupts\n",
+ __func__);
+
+ return ret;
+}
+
+static void tsens_disable_irq(struct tsens_priv *priv)
+{
+ regmap_field_write(priv->rf[INT_EN], 0);
+}
+
+int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
+{
+ int hw_id = s->hw_id;
+ u32 temp_idx = LAST_TEMP_0 + hw_id;
+ int ret;
+
+ ret = tsens_read_temp(s, temp_idx, temp);
+ if (!ret)
+ *temp = tsens_hw_to_mC(s, *temp);
+
+ return ret;
+}
+
+int get_temp_common(const struct tsens_sensor *s, int *temp)
+{
+ struct tsens_priv *priv = s->priv;
+ int hw_id = s->hw_id;
+ int last_temp = 0, ret, trdy;
+ unsigned long timeout;
+
+ timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
+ do {
+ if (tsens_version(priv) == VER_0) {
+ ret = regmap_field_read(priv->rf[TRDY], &trdy);
+ if (ret)
+ return ret;
+ if (!trdy)
+ continue;
+ }
+
+ ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp);
+ if (ret)
+ return ret;
+
+ *temp = code_to_degc(last_temp, s) * 1000;
+
+ return 0;
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int dbg_sensors_show(struct seq_file *s, void *data)
+{
+ struct platform_device *pdev = s->private;
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+ int i;
+
+ seq_printf(s, "max: %2d\nnum: %2d\n\n",
+ priv->feat->max_sensors, priv->num_sensors);
+
+ seq_puts(s, " id slope offset\n--------------------------\n");
+ for (i = 0; i < priv->num_sensors; i++) {
+ seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id,
+ priv->sensor[i].slope, priv->sensor[i].offset);
+ }
+
+ return 0;
+}
+
+static int dbg_version_show(struct seq_file *s, void *data)
+{
+ struct platform_device *pdev = s->private;
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+ u32 maj_ver, min_ver, step_ver;
+ int ret;
+
+ if (tsens_version(priv) > VER_0_1) {
+ ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[VER_STEP], &step_ver);
+ if (ret)
+ return ret;
+ seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver);
+ } else {
+ seq_printf(s, "0.%d.0\n", priv->feat->ver_major);
+ }
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(dbg_version);
+DEFINE_SHOW_ATTRIBUTE(dbg_sensors);
+
+static void tsens_debug_init(struct platform_device *pdev)
+{
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+
+ priv->debug_root = debugfs_lookup("tsens", NULL);
+ if (!priv->debug_root)
+ priv->debug_root = debugfs_create_dir("tsens", NULL);
+
+ /* A directory for each instance of the TSENS IP */
+ priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root);
+ debugfs_create_file("version", 0444, priv->debug, pdev, &dbg_version_fops);
+ debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops);
+}
+#else
+static inline void tsens_debug_init(struct platform_device *pdev) {}
+#endif
+
+static const struct regmap_config tsens_config = {
+ .name = "tm",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static const struct regmap_config tsens_srot_config = {
+ .name = "srot",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+int __init init_common(struct tsens_priv *priv)
+{
+ void __iomem *tm_base, *srot_base;
+ struct device *dev = priv->dev;
+ u32 ver_minor;
+ struct resource *res;
+ u32 enabled;
+ int ret, i, j;
+ struct platform_device *op = of_find_device_by_node(priv->dev->of_node);
+
+ if (!op)
+ return -EINVAL;
+
+ if (op->num_resources > 1) {
+ /* DT with separate SROT and TM address space */
+ priv->tm_offset = 0;
+ res = platform_get_resource(op, IORESOURCE_MEM, 1);
+ srot_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(srot_base)) {
+ ret = PTR_ERR(srot_base);
+ goto err_put_device;
+ }
+
+ priv->srot_map = devm_regmap_init_mmio(dev, srot_base,
+ &tsens_srot_config);
+ if (IS_ERR(priv->srot_map)) {
+ ret = PTR_ERR(priv->srot_map);
+ goto err_put_device;
+ }
+ } else {
+ /* old DTs where SROT and TM were in a contiguous 2K block */
+ priv->tm_offset = 0x1000;
+ }
+
+ if (tsens_version(priv) >= VER_0_1) {
+ res = platform_get_resource(op, IORESOURCE_MEM, 0);
+ tm_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(tm_base)) {
+ ret = PTR_ERR(tm_base);
+ goto err_put_device;
+ }
+
+ priv->tm_map = devm_regmap_init_mmio(dev, tm_base, &tsens_config);
+ } else { /* VER_0 share the same gcc regs using a syscon */
+ struct device *parent = priv->dev->parent;
+
+ if (parent)
+ priv->tm_map = syscon_node_to_regmap(parent->of_node);
+ }
+
+ if (IS_ERR_OR_NULL(priv->tm_map)) {
+ if (!priv->tm_map)
+ ret = -ENODEV;
+ else
+ ret = PTR_ERR(priv->tm_map);
+ goto err_put_device;
+ }
+
+ /* VER_0 have only tm_map */
+ if (!priv->srot_map)
+ priv->srot_map = priv->tm_map;
+
+ if (tsens_version(priv) > VER_0_1) {
+ for (i = VER_MAJOR; i <= VER_STEP; i++) {
+ priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[i]);
+ if (IS_ERR(priv->rf[i])) {
+ ret = PTR_ERR(priv->rf[i]);
+ goto err_put_device;
+ }
+ }
+ ret = regmap_field_read(priv->rf[VER_MINOR], &ver_minor);
+ if (ret)
+ goto err_put_device;
+ }
+
+ priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[TSENS_EN]);
+ if (IS_ERR(priv->rf[TSENS_EN])) {
+ ret = PTR_ERR(priv->rf[TSENS_EN]);
+ goto err_put_device;
+ }
+ /* in VER_0 TSENS need to be explicitly enabled */
+ if (tsens_version(priv) == VER_0)
+ regmap_field_write(priv->rf[TSENS_EN], 1);
+
+ ret = regmap_field_read(priv->rf[TSENS_EN], &enabled);
+ if (ret)
+ goto err_put_device;
+ if (!enabled) {
+ 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,
+ priv->fields[SENSOR_EN]);
+ if (IS_ERR(priv->rf[SENSOR_EN])) {
+ ret = PTR_ERR(priv->rf[SENSOR_EN]);
+ goto err_put_device;
+ }
+ priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map,
+ priv->fields[INT_EN]);
+ if (IS_ERR(priv->rf[INT_EN])) {
+ ret = PTR_ERR(priv->rf[INT_EN]);
+ goto err_put_device;
+ }
+
+ priv->rf[TSENS_SW_RST] =
+ devm_regmap_field_alloc(dev, priv->srot_map, priv->fields[TSENS_SW_RST]);
+ if (IS_ERR(priv->rf[TSENS_SW_RST])) {
+ ret = PTR_ERR(priv->rf[TSENS_SW_RST]);
+ goto err_put_device;
+ }
+
+ priv->rf[TRDY] = devm_regmap_field_alloc(dev, priv->tm_map, priv->fields[TRDY]);
+ if (IS_ERR(priv->rf[TRDY])) {
+ ret = PTR_ERR(priv->rf[TRDY]);
+ goto err_put_device;
+ }
+
+ /* This loop might need changes if enum regfield_ids is reordered */
+ for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) {
+ for (i = 0; i < priv->feat->max_sensors; i++) {
+ int idx = j + i;
+
+ priv->rf[idx] = devm_regmap_field_alloc(dev,
+ priv->tm_map,
+ priv->fields[idx]);
+ if (IS_ERR(priv->rf[idx])) {
+ ret = PTR_ERR(priv->rf[idx]);
+ goto err_put_device;
+ }
+ }
+ }
+
+ if (priv->feat->crit_int || tsens_version(priv) < VER_0_1) {
+ /* Loop might need changes if enum regfield_ids is reordered */
+ for (j = CRITICAL_STATUS_0; j <= CRIT_THRESH_15; j += 16) {
+ for (i = 0; i < priv->feat->max_sensors; i++) {
+ int idx = j + i;
+
+ priv->rf[idx] =
+ devm_regmap_field_alloc(dev,
+ priv->tm_map,
+ priv->fields[idx]);
+ if (IS_ERR(priv->rf[idx])) {
+ ret = PTR_ERR(priv->rf[idx]);
+ goto err_put_device;
+ }
+ }
+ }
+ }
+
+ if (tsens_version(priv) >= VER_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++) {
+ priv->rf[i] = devm_regmap_field_alloc(dev, priv->tm_map,
+ priv->fields[i]);
+ if (IS_ERR(priv->rf[i])) {
+ ret = PTR_ERR(priv->rf[i]);
+ goto err_put_device;
+ }
+ }
+ /*
+ * Watchdog is already enabled, unmask the bark.
+ * Disable cycle completion monitoring
+ */
+ regmap_field_write(priv->rf[WDOG_BARK_MASK], 0);
+ regmap_field_write(priv->rf[CC_MON_MASK], 1);
+ }
+
+ spin_lock_init(&priv->ul_lock);
+
+ /* VER_0 interrupt doesn't need to be enabled */
+ if (tsens_version(priv) >= VER_0_1)
+ tsens_enable_irq(priv);
+
+err_put_device:
+ put_device(&op->dev);
+ return ret;
+}
+
+static int tsens_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct tsens_sensor *s = thermal_zone_device_priv(tz);
+ struct tsens_priv *priv = s->priv;
+
+ return priv->ops->get_temp(s, temp);
+}
+
+static int __maybe_unused tsens_suspend(struct device *dev)
+{
+ int ret = 0;
+ struct tsens_priv *priv = dev_get_drvdata(dev);
+
+ if (priv->ops && priv->ops->suspend) {
+ ret = priv->ops->suspend(priv);
+ if (ret)
+ return ret;
+ }
+
+ return tsens_suspend_common(priv);
+}
+
+static int __maybe_unused tsens_resume(struct device *dev)
+{
+ int ret = 0;
+ struct tsens_priv *priv = dev_get_drvdata(dev);
+
+ if (priv->ops && priv->ops->resume) {
+ ret = priv->ops->resume(priv);
+ if (ret)
+ return ret;
+ }
+
+ return tsens_resume_common(priv);
+}
+
+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,
+ }, {
+ .compatible = "qcom,ipq8074-tsens",
+ .data = &data_ipq8074,
+ }, {
+ .compatible = "qcom,mdm9607-tsens",
+ .data = &data_9607,
+ }, {
+ .compatible = "qcom,msm8226-tsens",
+ .data = &data_8226,
+ }, {
+ .compatible = "qcom,msm8909-tsens",
+ .data = &data_8909,
+ }, {
+ .compatible = "qcom,msm8916-tsens",
+ .data = &data_8916,
+ }, {
+ .compatible = "qcom,msm8937-tsens",
+ .data = &data_8937,
+ }, {
+ .compatible = "qcom,msm8939-tsens",
+ .data = &data_8939,
+ }, {
+ .compatible = "qcom,msm8956-tsens",
+ .data = &data_8956,
+ }, {
+ .compatible = "qcom,msm8960-tsens",
+ .data = &data_8960,
+ }, {
+ .compatible = "qcom,msm8974-tsens",
+ .data = &data_8974,
+ }, {
+ .compatible = "qcom,msm8976-tsens",
+ .data = &data_8976,
+ }, {
+ .compatible = "qcom,msm8996-tsens",
+ .data = &data_8996,
+ }, {
+ .compatible = "qcom,tsens-v1",
+ .data = &data_tsens_v1,
+ }, {
+ .compatible = "qcom,tsens-v2",
+ .data = &data_tsens_v2,
+ }, {
+ .compatible = "qcom,sa8775p-tsens",
+ .data = &data_automotive_v2,
+ }, {
+ .compatible = "qcom,sa8255p-tsens",
+ .data = &data_automotive_v2,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, tsens_table);
+
+static const struct thermal_zone_device_ops tsens_of_ops = {
+ .get_temp = tsens_get_temp,
+ .set_trips = tsens_set_trips,
+};
+
+static int tsens_register_irq(struct tsens_priv *priv, char *irqname,
+ irq_handler_t thread_fn, int *irq_num)
+{
+ struct platform_device *pdev;
+ int ret, irq;
+
+ pdev = of_find_device_by_node(priv->dev->of_node);
+ if (!pdev)
+ return -ENODEV;
+
+ irq = platform_get_irq_byname(pdev, irqname);
+ if (irq < 0) {
+ ret = irq;
+ /* For old DTs with no IRQ defined */
+ if (irq == -ENXIO)
+ ret = 0;
+ } else {
+ /* VER_0 interrupt is TRIGGER_RISING, VER_0_1 and up is ONESHOT */
+ if (tsens_version(priv) == VER_0)
+ ret = devm_request_threaded_irq(&pdev->dev, irq,
+ thread_fn, NULL,
+ IRQF_TRIGGER_RISING,
+ dev_name(&pdev->dev),
+ priv);
+ else
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ thread_fn, IRQF_ONESHOT,
+ dev_name(&pdev->dev),
+ priv);
+
+ if (!ret)
+ *irq_num = irq;
+ }
+
+ put_device(&pdev->dev);
+ 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_suspend_common(struct tsens_priv *priv)
+{
+ if (!device_may_wakeup(priv->dev))
+ return 0;
+
+ if (priv->feat->combo_int)
+ enable_irq_wake(priv->combined_irq);
+ else {
+ enable_irq_wake(priv->uplow_irq);
+ if (priv->feat->crit_int)
+ enable_irq_wake(priv->crit_irq);
+ }
+
+ return 0;
+}
+
+int tsens_resume_common(struct tsens_priv *priv)
+{
+ if (pm_suspend_target_state == PM_SUSPEND_MEM)
+ tsens_reinit(priv);
+
+ if (!device_may_wakeup(priv->dev))
+ return 0;
+
+ if (priv->feat->combo_int)
+ disable_irq_wake(priv->combined_irq);
+ else {
+ disable_irq_wake(priv->uplow_irq);
+ if (priv->feat->crit_int)
+ disable_irq_wake(priv->crit_irq);
+ }
+
+ return 0;
+}
+
+#endif /* !CONFIG_SUSPEND */
+
+static int tsens_register(struct tsens_priv *priv)
+{
+ int i, ret;
+ struct thermal_zone_device *tzd;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ priv->sensor[i].priv = priv;
+ tzd = devm_thermal_of_zone_register(priv->dev, priv->sensor[i].hw_id,
+ &priv->sensor[i],
+ &tsens_of_ops);
+ if (IS_ERR(tzd))
+ continue;
+ priv->sensor[i].tzd = tzd;
+ if (priv->ops->enable)
+ priv->ops->enable(priv, i);
+
+ devm_thermal_add_hwmon_sysfs(priv->dev, tzd);
+ }
+
+ /* VER_0 require to set MIN and MAX THRESH
+ * These 2 regs are set using the:
+ * - CRIT_THRESH_0 for MAX THRESH hardcoded to 120°C
+ * - CRIT_THRESH_1 for MIN THRESH hardcoded to 0°C
+ */
+ if (tsens_version(priv) < VER_0_1) {
+ regmap_field_write(priv->rf[CRIT_THRESH_0],
+ tsens_mC_to_hw(priv->sensor, 120000));
+
+ regmap_field_write(priv->rf[CRIT_THRESH_1],
+ tsens_mC_to_hw(priv->sensor, 0));
+ }
+
+ if (priv->feat->combo_int) {
+ ret = tsens_register_irq(priv, "combined",
+ tsens_combined_irq_thread, &priv->combined_irq);
+ } else {
+ ret = tsens_register_irq(priv, "uplow", tsens_irq_thread,
+ &priv->uplow_irq);
+ if (ret < 0)
+ return ret;
+
+ if (priv->feat->crit_int) {
+ ret = tsens_register_irq(priv, "critical",
+ tsens_critical_irq_thread,
+ &priv->crit_irq);
+ }
+ }
+
+ return ret;
+}
+
+static int tsens_probe(struct platform_device *pdev)
+{
+ int ret, i;
+ struct device *dev;
+ struct device_node *np;
+ struct tsens_priv *priv;
+ const struct tsens_plat_data *data;
+ const struct of_device_id *id;
+ u32 num_sensors;
+
+ if (pdev->dev.of_node)
+ dev = &pdev->dev;
+ else
+ dev = pdev->dev.parent;
+
+ np = dev->of_node;
+
+ id = of_match_node(tsens_table, np);
+ if (id)
+ data = id->data;
+ else
+ data = &data_8960;
+
+ num_sensors = data->num_sensors;
+
+ if (np)
+ of_property_read_u32(np, "#qcom,sensors", &num_sensors);
+
+ if (num_sensors <= 0) {
+ dev_err(dev, "%s: invalid number of sensors\n", __func__);
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(dev,
+ struct_size(priv, sensor, num_sensors),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->num_sensors = num_sensors;
+ priv->ops = data->ops;
+ for (i = 0; i < priv->num_sensors; i++) {
+ if (data->hw_ids)
+ priv->sensor[i].hw_id = data->hw_ids[i];
+ else
+ priv->sensor[i].hw_id = i;
+ }
+ priv->feat = data->feat;
+ priv->fields = data->fields;
+
+ platform_set_drvdata(pdev, priv);
+
+ device_init_wakeup(dev, !data->no_irq_wake);
+
+ if (!priv->ops || !priv->ops->init || !priv->ops->get_temp)
+ return -EINVAL;
+
+ ret = priv->ops->init(priv);
+ if (ret < 0) {
+ dev_err(dev, "%s: init failed\n", __func__);
+ return ret;
+ }
+
+ if (priv->ops->calibrate) {
+ ret = priv->ops->calibrate(priv);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%s: calibration failed\n",
+ __func__);
+ }
+
+ ret = tsens_register(priv);
+ if (!ret)
+ tsens_debug_init(pdev);
+
+ return ret;
+}
+
+static void tsens_remove(struct platform_device *pdev)
+{
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+
+ debugfs_remove_recursive(priv->debug_root);
+ tsens_disable_irq(priv);
+ if (priv->ops->disable)
+ priv->ops->disable(priv);
+}
+
+static struct platform_driver tsens_driver = {
+ .probe = tsens_probe,
+ .remove = tsens_remove,
+ .driver = {
+ .name = "qcom-tsens",
+ .pm = &tsens_pm_ops,
+ .of_match_table = tsens_table,
+ },
+};
+module_platform_driver(tsens_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("QCOM Temperature Sensor driver");
+MODULE_ALIAS("platform:qcom-tsens");
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
new file mode 100644
index 000000000..e8376accd
--- /dev/null
+++ b/drivers/thermal/qcom/tsens.h
@@ -0,0 +1,684 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef __QCOM_TSENS_H__
+#define __QCOM_TSENS_H__
+
+#define NO_PT_CALIB 0x0
+#define ONE_PT_CALIB 0x1
+#define ONE_PT_CALIB2 0x2
+#define TWO_PT_CALIB 0x3
+#define ONE_PT_CALIB2_NO_OFFSET 0x6
+#define TWO_PT_CALIB_NO_OFFSET 0x7
+#define CAL_DEGC_PT1 30
+#define CAL_DEGC_PT2 120
+#define SLOPE_FACTOR 1000
+#define SLOPE_DEFAULT 3200
+#define TIMEOUT_US 100
+#define THRESHOLD_MAX_ADC_CODE 0x3ff
+#define THRESHOLD_MIN_ADC_CODE 0x0
+
+#define MAX_SENSORS 16
+#define MAX_READ_RETRY 3
+
+#include <linux/interrupt.h>
+#include <linux/thermal.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+struct tsens_priv;
+
+/* IP version numbers in ascending order */
+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 {
+ LOWER,
+ UPPER,
+ CRITICAL,
+};
+
+/**
+ * struct tsens_sensor - data for each sensor connected to the tsens device
+ * @priv: tsens device instance that this sensor is connected to
+ * @tzd: pointer to the thermal zone that this sensor is in
+ * @offset: offset of temperature adjustment curve
+ * @hw_id: HW ID can be used in case of platform-specific IDs
+ * @slope: slope of temperature adjustment curve
+ * @status: 8960-specific variable to track 8960 and 8660 status register offset
+ */
+struct tsens_sensor {
+ struct tsens_priv *priv;
+ struct thermal_zone_device *tzd;
+ int offset;
+ unsigned int hw_id;
+ int slope;
+ u32 status;
+ int p1_calib_offset;
+ int p2_calib_offset;
+};
+
+/**
+ * struct tsens_ops - operations as supported by the tsens device
+ * @init: Function to initialize the tsens device
+ * @calibrate: Function to calibrate the tsens device
+ * @get_temp: Function which returns the temp in millidegC
+ * @enable: Function to enable (clocks/power) tsens device
+ * @disable: Function to disable the tsens device
+ * @suspend: Function to suspend the tsens device
+ * @resume: Function to resume the tsens device
+ */
+struct tsens_ops {
+ /* mandatory callbacks */
+ int (*init)(struct tsens_priv *priv);
+ int (*calibrate)(struct tsens_priv *priv);
+ int (*get_temp)(const struct tsens_sensor *s, int *temp);
+ /* optional callbacks */
+ int (*enable)(struct tsens_priv *priv, int i);
+ void (*disable)(struct tsens_priv *priv);
+ int (*suspend)(struct tsens_priv *priv);
+ int (*resume)(struct tsens_priv *priv);
+};
+
+#define REG_FIELD_FOR_EACH_SENSOR11(_name, _offset, _startbit, _stopbit) \
+ [_name##_##0] = REG_FIELD(_offset, _startbit, _stopbit), \
+ [_name##_##1] = REG_FIELD(_offset + 4, _startbit, _stopbit), \
+ [_name##_##2] = REG_FIELD(_offset + 8, _startbit, _stopbit), \
+ [_name##_##3] = REG_FIELD(_offset + 12, _startbit, _stopbit), \
+ [_name##_##4] = REG_FIELD(_offset + 16, _startbit, _stopbit), \
+ [_name##_##5] = REG_FIELD(_offset + 20, _startbit, _stopbit), \
+ [_name##_##6] = REG_FIELD(_offset + 24, _startbit, _stopbit), \
+ [_name##_##7] = REG_FIELD(_offset + 28, _startbit, _stopbit), \
+ [_name##_##8] = REG_FIELD(_offset + 32, _startbit, _stopbit), \
+ [_name##_##9] = REG_FIELD(_offset + 36, _startbit, _stopbit), \
+ [_name##_##10] = REG_FIELD(_offset + 40, _startbit, _stopbit)
+
+#define REG_FIELD_FOR_EACH_SENSOR16(_name, _offset, _startbit, _stopbit) \
+ [_name##_##0] = REG_FIELD(_offset, _startbit, _stopbit), \
+ [_name##_##1] = REG_FIELD(_offset + 4, _startbit, _stopbit), \
+ [_name##_##2] = REG_FIELD(_offset + 8, _startbit, _stopbit), \
+ [_name##_##3] = REG_FIELD(_offset + 12, _startbit, _stopbit), \
+ [_name##_##4] = REG_FIELD(_offset + 16, _startbit, _stopbit), \
+ [_name##_##5] = REG_FIELD(_offset + 20, _startbit, _stopbit), \
+ [_name##_##6] = REG_FIELD(_offset + 24, _startbit, _stopbit), \
+ [_name##_##7] = REG_FIELD(_offset + 28, _startbit, _stopbit), \
+ [_name##_##8] = REG_FIELD(_offset + 32, _startbit, _stopbit), \
+ [_name##_##9] = REG_FIELD(_offset + 36, _startbit, _stopbit), \
+ [_name##_##10] = REG_FIELD(_offset + 40, _startbit, _stopbit), \
+ [_name##_##11] = REG_FIELD(_offset + 44, _startbit, _stopbit), \
+ [_name##_##12] = REG_FIELD(_offset + 48, _startbit, _stopbit), \
+ [_name##_##13] = REG_FIELD(_offset + 52, _startbit, _stopbit), \
+ [_name##_##14] = REG_FIELD(_offset + 56, _startbit, _stopbit), \
+ [_name##_##15] = REG_FIELD(_offset + 60, _startbit, _stopbit)
+
+#define REG_FIELD_SPLIT_BITS_0_15(_name, _offset) \
+ [_name##_##0] = REG_FIELD(_offset, 0, 0), \
+ [_name##_##1] = REG_FIELD(_offset, 1, 1), \
+ [_name##_##2] = REG_FIELD(_offset, 2, 2), \
+ [_name##_##3] = REG_FIELD(_offset, 3, 3), \
+ [_name##_##4] = REG_FIELD(_offset, 4, 4), \
+ [_name##_##5] = REG_FIELD(_offset, 5, 5), \
+ [_name##_##6] = REG_FIELD(_offset, 6, 6), \
+ [_name##_##7] = REG_FIELD(_offset, 7, 7), \
+ [_name##_##8] = REG_FIELD(_offset, 8, 8), \
+ [_name##_##9] = REG_FIELD(_offset, 9, 9), \
+ [_name##_##10] = REG_FIELD(_offset, 10, 10), \
+ [_name##_##11] = REG_FIELD(_offset, 11, 11), \
+ [_name##_##12] = REG_FIELD(_offset, 12, 12), \
+ [_name##_##13] = REG_FIELD(_offset, 13, 13), \
+ [_name##_##14] = REG_FIELD(_offset, 14, 14), \
+ [_name##_##15] = REG_FIELD(_offset, 15, 15)
+
+#define REG_FIELD_SPLIT_BITS_16_31(_name, _offset) \
+ [_name##_##0] = REG_FIELD(_offset, 16, 16), \
+ [_name##_##1] = REG_FIELD(_offset, 17, 17), \
+ [_name##_##2] = REG_FIELD(_offset, 18, 18), \
+ [_name##_##3] = REG_FIELD(_offset, 19, 19), \
+ [_name##_##4] = REG_FIELD(_offset, 20, 20), \
+ [_name##_##5] = REG_FIELD(_offset, 21, 21), \
+ [_name##_##6] = REG_FIELD(_offset, 22, 22), \
+ [_name##_##7] = REG_FIELD(_offset, 23, 23), \
+ [_name##_##8] = REG_FIELD(_offset, 24, 24), \
+ [_name##_##9] = REG_FIELD(_offset, 25, 25), \
+ [_name##_##10] = REG_FIELD(_offset, 26, 26), \
+ [_name##_##11] = REG_FIELD(_offset, 27, 27), \
+ [_name##_##12] = REG_FIELD(_offset, 28, 28), \
+ [_name##_##13] = REG_FIELD(_offset, 29, 29), \
+ [_name##_##14] = REG_FIELD(_offset, 30, 30), \
+ [_name##_##15] = REG_FIELD(_offset, 31, 31)
+
+/*
+ * reg_field IDs to use as an index into an array
+ * If you change the order of the entries, check the devm_regmap_field_alloc()
+ * calls in init_common()
+ */
+enum regfield_ids {
+ /* ----- SROT ------ */
+ /* HW_VER */
+ VER_MAJOR,
+ VER_MINOR,
+ VER_STEP,
+ /* CTRL_OFFSET */
+ TSENS_EN,
+ TSENS_SW_RST,
+ SENSOR_EN,
+ CODE_OR_TEMP,
+ MAIN_MEASURE_PERIOD,
+
+ /* ----- TM ------ */
+ /* TRDY */
+ TRDY,
+ /* INTERRUPT ENABLE */
+ INT_EN, /* v2+ has separate enables for crit, upper and lower irq */
+ /* STATUS */
+ LAST_TEMP_0, /* Last temperature reading */
+ LAST_TEMP_1,
+ LAST_TEMP_2,
+ LAST_TEMP_3,
+ LAST_TEMP_4,
+ LAST_TEMP_5,
+ LAST_TEMP_6,
+ LAST_TEMP_7,
+ LAST_TEMP_8,
+ LAST_TEMP_9,
+ LAST_TEMP_10,
+ LAST_TEMP_11,
+ LAST_TEMP_12,
+ LAST_TEMP_13,
+ LAST_TEMP_14,
+ LAST_TEMP_15,
+ VALID_0, /* VALID reading or not */
+ VALID_1,
+ VALID_2,
+ VALID_3,
+ VALID_4,
+ VALID_5,
+ VALID_6,
+ VALID_7,
+ VALID_8,
+ VALID_9,
+ VALID_10,
+ VALID_11,
+ VALID_12,
+ VALID_13,
+ VALID_14,
+ VALID_15,
+ LOWER_STATUS_0, /* LOWER threshold violated */
+ LOWER_STATUS_1,
+ LOWER_STATUS_2,
+ LOWER_STATUS_3,
+ LOWER_STATUS_4,
+ LOWER_STATUS_5,
+ LOWER_STATUS_6,
+ LOWER_STATUS_7,
+ LOWER_STATUS_8,
+ LOWER_STATUS_9,
+ LOWER_STATUS_10,
+ LOWER_STATUS_11,
+ LOWER_STATUS_12,
+ LOWER_STATUS_13,
+ LOWER_STATUS_14,
+ LOWER_STATUS_15,
+ LOW_INT_STATUS_0, /* LOWER interrupt status */
+ LOW_INT_STATUS_1,
+ LOW_INT_STATUS_2,
+ LOW_INT_STATUS_3,
+ LOW_INT_STATUS_4,
+ LOW_INT_STATUS_5,
+ LOW_INT_STATUS_6,
+ LOW_INT_STATUS_7,
+ LOW_INT_STATUS_8,
+ LOW_INT_STATUS_9,
+ LOW_INT_STATUS_10,
+ LOW_INT_STATUS_11,
+ LOW_INT_STATUS_12,
+ LOW_INT_STATUS_13,
+ LOW_INT_STATUS_14,
+ LOW_INT_STATUS_15,
+ LOW_INT_CLEAR_0, /* LOWER interrupt clear */
+ LOW_INT_CLEAR_1,
+ LOW_INT_CLEAR_2,
+ LOW_INT_CLEAR_3,
+ LOW_INT_CLEAR_4,
+ LOW_INT_CLEAR_5,
+ LOW_INT_CLEAR_6,
+ LOW_INT_CLEAR_7,
+ LOW_INT_CLEAR_8,
+ LOW_INT_CLEAR_9,
+ LOW_INT_CLEAR_10,
+ LOW_INT_CLEAR_11,
+ LOW_INT_CLEAR_12,
+ LOW_INT_CLEAR_13,
+ LOW_INT_CLEAR_14,
+ LOW_INT_CLEAR_15,
+ LOW_INT_MASK_0, /* LOWER interrupt mask */
+ LOW_INT_MASK_1,
+ LOW_INT_MASK_2,
+ LOW_INT_MASK_3,
+ LOW_INT_MASK_4,
+ LOW_INT_MASK_5,
+ LOW_INT_MASK_6,
+ LOW_INT_MASK_7,
+ LOW_INT_MASK_8,
+ LOW_INT_MASK_9,
+ LOW_INT_MASK_10,
+ LOW_INT_MASK_11,
+ LOW_INT_MASK_12,
+ LOW_INT_MASK_13,
+ LOW_INT_MASK_14,
+ LOW_INT_MASK_15,
+ LOW_THRESH_0, /* LOWER threshold values */
+ LOW_THRESH_1,
+ LOW_THRESH_2,
+ LOW_THRESH_3,
+ LOW_THRESH_4,
+ LOW_THRESH_5,
+ LOW_THRESH_6,
+ LOW_THRESH_7,
+ LOW_THRESH_8,
+ LOW_THRESH_9,
+ LOW_THRESH_10,
+ LOW_THRESH_11,
+ LOW_THRESH_12,
+ LOW_THRESH_13,
+ LOW_THRESH_14,
+ LOW_THRESH_15,
+ UPPER_STATUS_0, /* UPPER threshold violated */
+ UPPER_STATUS_1,
+ UPPER_STATUS_2,
+ UPPER_STATUS_3,
+ UPPER_STATUS_4,
+ UPPER_STATUS_5,
+ UPPER_STATUS_6,
+ UPPER_STATUS_7,
+ UPPER_STATUS_8,
+ UPPER_STATUS_9,
+ UPPER_STATUS_10,
+ UPPER_STATUS_11,
+ UPPER_STATUS_12,
+ UPPER_STATUS_13,
+ UPPER_STATUS_14,
+ UPPER_STATUS_15,
+ UP_INT_STATUS_0, /* UPPER interrupt status */
+ UP_INT_STATUS_1,
+ UP_INT_STATUS_2,
+ UP_INT_STATUS_3,
+ UP_INT_STATUS_4,
+ UP_INT_STATUS_5,
+ UP_INT_STATUS_6,
+ UP_INT_STATUS_7,
+ UP_INT_STATUS_8,
+ UP_INT_STATUS_9,
+ UP_INT_STATUS_10,
+ UP_INT_STATUS_11,
+ UP_INT_STATUS_12,
+ UP_INT_STATUS_13,
+ UP_INT_STATUS_14,
+ UP_INT_STATUS_15,
+ UP_INT_CLEAR_0, /* UPPER interrupt clear */
+ UP_INT_CLEAR_1,
+ UP_INT_CLEAR_2,
+ UP_INT_CLEAR_3,
+ UP_INT_CLEAR_4,
+ UP_INT_CLEAR_5,
+ UP_INT_CLEAR_6,
+ UP_INT_CLEAR_7,
+ UP_INT_CLEAR_8,
+ UP_INT_CLEAR_9,
+ UP_INT_CLEAR_10,
+ UP_INT_CLEAR_11,
+ UP_INT_CLEAR_12,
+ UP_INT_CLEAR_13,
+ UP_INT_CLEAR_14,
+ UP_INT_CLEAR_15,
+ UP_INT_MASK_0, /* UPPER interrupt mask */
+ UP_INT_MASK_1,
+ UP_INT_MASK_2,
+ UP_INT_MASK_3,
+ UP_INT_MASK_4,
+ UP_INT_MASK_5,
+ UP_INT_MASK_6,
+ UP_INT_MASK_7,
+ UP_INT_MASK_8,
+ UP_INT_MASK_9,
+ UP_INT_MASK_10,
+ UP_INT_MASK_11,
+ UP_INT_MASK_12,
+ UP_INT_MASK_13,
+ UP_INT_MASK_14,
+ UP_INT_MASK_15,
+ UP_THRESH_0, /* UPPER threshold values */
+ UP_THRESH_1,
+ UP_THRESH_2,
+ UP_THRESH_3,
+ UP_THRESH_4,
+ UP_THRESH_5,
+ UP_THRESH_6,
+ UP_THRESH_7,
+ UP_THRESH_8,
+ UP_THRESH_9,
+ UP_THRESH_10,
+ UP_THRESH_11,
+ UP_THRESH_12,
+ UP_THRESH_13,
+ UP_THRESH_14,
+ UP_THRESH_15,
+ CRITICAL_STATUS_0, /* CRITICAL threshold violated */
+ CRITICAL_STATUS_1,
+ CRITICAL_STATUS_2,
+ CRITICAL_STATUS_3,
+ CRITICAL_STATUS_4,
+ CRITICAL_STATUS_5,
+ CRITICAL_STATUS_6,
+ CRITICAL_STATUS_7,
+ CRITICAL_STATUS_8,
+ CRITICAL_STATUS_9,
+ CRITICAL_STATUS_10,
+ CRITICAL_STATUS_11,
+ CRITICAL_STATUS_12,
+ CRITICAL_STATUS_13,
+ CRITICAL_STATUS_14,
+ CRITICAL_STATUS_15,
+ CRIT_INT_STATUS_0, /* CRITICAL interrupt status */
+ CRIT_INT_STATUS_1,
+ CRIT_INT_STATUS_2,
+ CRIT_INT_STATUS_3,
+ CRIT_INT_STATUS_4,
+ CRIT_INT_STATUS_5,
+ CRIT_INT_STATUS_6,
+ CRIT_INT_STATUS_7,
+ CRIT_INT_STATUS_8,
+ CRIT_INT_STATUS_9,
+ CRIT_INT_STATUS_10,
+ CRIT_INT_STATUS_11,
+ CRIT_INT_STATUS_12,
+ CRIT_INT_STATUS_13,
+ CRIT_INT_STATUS_14,
+ CRIT_INT_STATUS_15,
+ CRIT_INT_CLEAR_0, /* CRITICAL interrupt clear */
+ CRIT_INT_CLEAR_1,
+ CRIT_INT_CLEAR_2,
+ CRIT_INT_CLEAR_3,
+ CRIT_INT_CLEAR_4,
+ CRIT_INT_CLEAR_5,
+ CRIT_INT_CLEAR_6,
+ CRIT_INT_CLEAR_7,
+ CRIT_INT_CLEAR_8,
+ CRIT_INT_CLEAR_9,
+ CRIT_INT_CLEAR_10,
+ CRIT_INT_CLEAR_11,
+ CRIT_INT_CLEAR_12,
+ CRIT_INT_CLEAR_13,
+ CRIT_INT_CLEAR_14,
+ CRIT_INT_CLEAR_15,
+ CRIT_INT_MASK_0, /* CRITICAL interrupt mask */
+ CRIT_INT_MASK_1,
+ CRIT_INT_MASK_2,
+ CRIT_INT_MASK_3,
+ CRIT_INT_MASK_4,
+ CRIT_INT_MASK_5,
+ CRIT_INT_MASK_6,
+ CRIT_INT_MASK_7,
+ CRIT_INT_MASK_8,
+ CRIT_INT_MASK_9,
+ CRIT_INT_MASK_10,
+ CRIT_INT_MASK_11,
+ CRIT_INT_MASK_12,
+ CRIT_INT_MASK_13,
+ CRIT_INT_MASK_14,
+ CRIT_INT_MASK_15,
+ CRIT_THRESH_0, /* CRITICAL threshold values */
+ CRIT_THRESH_1,
+ CRIT_THRESH_2,
+ CRIT_THRESH_3,
+ CRIT_THRESH_4,
+ CRIT_THRESH_5,
+ CRIT_THRESH_6,
+ CRIT_THRESH_7,
+ CRIT_THRESH_8,
+ CRIT_THRESH_9,
+ CRIT_THRESH_10,
+ CRIT_THRESH_11,
+ CRIT_THRESH_12,
+ CRIT_THRESH_13,
+ CRIT_THRESH_14,
+ CRIT_THRESH_15,
+
+ /* WATCHDOG */
+ WDOG_BARK_STATUS,
+ WDOG_BARK_CLEAR,
+ WDOG_BARK_MASK,
+ WDOG_BARK_COUNT,
+
+ /* CYCLE COMPLETION MONITOR */
+ CC_MON_STATUS,
+ CC_MON_CLEAR,
+ CC_MON_MASK,
+
+ MIN_STATUS_0, /* MIN threshold violated */
+ MIN_STATUS_1,
+ MIN_STATUS_2,
+ MIN_STATUS_3,
+ MIN_STATUS_4,
+ MIN_STATUS_5,
+ MIN_STATUS_6,
+ MIN_STATUS_7,
+ MIN_STATUS_8,
+ MIN_STATUS_9,
+ MIN_STATUS_10,
+ MIN_STATUS_11,
+ MIN_STATUS_12,
+ MIN_STATUS_13,
+ MIN_STATUS_14,
+ MIN_STATUS_15,
+ MAX_STATUS_0, /* MAX threshold violated */
+ MAX_STATUS_1,
+ MAX_STATUS_2,
+ MAX_STATUS_3,
+ MAX_STATUS_4,
+ MAX_STATUS_5,
+ MAX_STATUS_6,
+ MAX_STATUS_7,
+ MAX_STATUS_8,
+ MAX_STATUS_9,
+ MAX_STATUS_10,
+ MAX_STATUS_11,
+ MAX_STATUS_12,
+ MAX_STATUS_13,
+ MAX_STATUS_14,
+ MAX_STATUS_15,
+
+ /* Keep last */
+ MAX_REGFIELDS
+};
+
+/**
+ * struct tsens_features - Features supported by the IP
+ * @ver_major: Major number of IP version
+ * @crit_int: does the IP support critical interrupts?
+ * @combo_int: does the IP use one IRQ for up, low and critical thresholds?
+ * @adc: do the sensors only output adc code (instead of temperature)?
+ * @srot_split: does the IP neatly splits the register space into SROT and TM,
+ * with SROT only being available to secure boot firmware?
+ * @has_watchdog: does this IP support watchdog functionality?
+ * @max_sensors: maximum sensors supported by this version of the IP
+ * @trip_min_temp: minimum trip temperature supported by this version of the IP
+ * @trip_max_temp: maximum trip temperature supported by this version of the IP
+ */
+struct tsens_features {
+ unsigned int ver_major;
+ unsigned int crit_int:1;
+ unsigned int combo_int:1;
+ unsigned int adc:1;
+ unsigned int srot_split:1;
+ unsigned int has_watchdog:1;
+ unsigned int max_sensors;
+ int trip_min_temp;
+ int trip_max_temp;
+};
+
+/**
+ * struct tsens_plat_data - tsens compile-time platform data
+ * @num_sensors: Number of sensors supported by platform
+ * @ops: operations the tsens instance supports
+ * @hw_ids: Subset of sensors ids supported by platform, if not the first n
+ * @feat: features of the IP
+ * @fields: bitfield locations
+ * @no_irq_wake: if set, TSENS interrupts will not be configured as wakeup sources
+ */
+struct tsens_plat_data {
+ const u32 num_sensors;
+ const struct tsens_ops *ops;
+ unsigned int *hw_ids;
+ struct tsens_features *feat;
+ const struct reg_field *fields;
+ bool no_irq_wake;
+};
+
+/**
+ * struct tsens_context - Registers to be saved/restored across a context loss
+ * @threshold: Threshold register value
+ * @control: Control register value
+ */
+struct tsens_context {
+ int threshold;
+ int control;
+};
+
+/**
+ * struct tsens_priv - private data for each instance of the tsens IP
+ * @dev: pointer to struct device
+ * @num_sensors: number of sensors enabled on this device
+ * @tm_map: pointer to TM register address space
+ * @srot_map: pointer to SROT register address space
+ * @tm_offset: deal with old device trees that don't address TM and SROT
+ * address space separately
+ * @ul_lock: lock while processing upper/lower threshold interrupts
+ * @crit_lock: lock while processing critical threshold interrupts
+ * @rf: array of regmap_fields used to store value of the field
+ * @ctx: registers to be saved and restored during suspend/resume
+ * @feat: features of the IP
+ * @fields: bitfield locations
+ * @ops: pointer to list of callbacks supported by this device
+ * @debug_root: pointer to debugfs dentry for all tsens
+ * @debug: pointer to debugfs dentry for tsens controller
+ * @uplow_irq: IRQ number for uplow (upper/lower) threshold interrupts
+ * @crit_irq: IRQ number for critical threshold interrupts
+ * @combined_irq: IRQ number for combined threshold interrupts
+ * @sensor: list of sensors attached to this device
+ */
+struct tsens_priv {
+ struct device *dev;
+ u32 num_sensors;
+ struct regmap *tm_map;
+ struct regmap *srot_map;
+ u32 tm_offset;
+
+ /* lock for upper/lower threshold interrupts */
+ spinlock_t ul_lock;
+
+ struct regmap_field *rf[MAX_REGFIELDS];
+ struct tsens_context ctx;
+ struct tsens_features *feat;
+ const struct reg_field *fields;
+ const struct tsens_ops *ops;
+
+ struct dentry *debug_root;
+ struct dentry *debug;
+
+ int uplow_irq;
+ int crit_irq;
+ int combined_irq;
+
+ struct tsens_sensor sensor[] __counted_by(num_sensors);
+};
+
+/**
+ * struct tsens_single_value - internal representation of a single field inside nvmem calibration data
+ * @idx: index into the u32 data array
+ * @shift: the shift of the first bit in the value
+ * @blob: index of the data blob to use for this cell
+ */
+struct tsens_single_value {
+ u8 idx;
+ u8 shift;
+ u8 blob;
+};
+
+/**
+ * struct tsens_legacy_calibration_format - description of calibration data used when parsing the legacy nvmem blob
+ * @base_len: the length of the base fields inside calibration data
+ * @base_shift: the shift to be applied to base data
+ * @sp_len: the length of the sN_pM fields inside calibration data
+ * @mode: descriptor of the calibration mode field
+ * @invalid: descriptor of the calibration mode invalid field
+ * @base: descriptors of the base0 and base1 fields
+ * @sp: descriptors of the sN_pM fields
+ */
+struct tsens_legacy_calibration_format {
+ unsigned int base_len;
+ unsigned int base_shift;
+ unsigned int sp_len;
+ /* just two bits */
+ struct tsens_single_value mode;
+ /* on all platforms except 8974 invalid is the third bit of what downstream calls 'mode' */
+ struct tsens_single_value invalid;
+ struct tsens_single_value base[2];
+ struct tsens_single_value sp[][2];
+};
+
+char *qfprom_read(struct device *dev, const char *cname);
+int tsens_read_calibration_legacy(struct tsens_priv *priv,
+ const struct tsens_legacy_calibration_format *format,
+ u32 *p1, u32 *p2,
+ u32 *cdata, u32 *csel);
+int tsens_read_calibration(struct tsens_priv *priv, int shift, u32 *p1, u32 *p2, bool backup);
+int tsens_calibrate_nvmem(struct tsens_priv *priv, int shift);
+int tsens_calibrate_common(struct tsens_priv *priv);
+void compute_intercept_slope(struct tsens_priv *priv, u32 *pt1, u32 *pt2, u32 mode);
+int init_common(struct tsens_priv *priv);
+int get_temp_tsens_valid(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);
+int tsens_suspend_common(struct tsens_priv *priv);
+#else
+static inline int tsens_resume_common(struct tsens_priv *priv)
+{
+ return 0;
+}
+
+static inline int tsens_suspend_common(struct tsens_priv *priv)
+{
+ return 0;
+}
+#endif
+
+/* TSENS target */
+extern struct tsens_plat_data data_8960;
+
+/* TSENS v0.1 targets */
+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_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;
+
+/* TSENS automotive targets */
+extern struct tsens_plat_data data_automotive_v2;
+
+#endif /* __QCOM_TSENS_H__ */
diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c
new file mode 100644
index 000000000..297724e81
--- /dev/null
+++ b/drivers/thermal/qoriq_thermal.c
@@ -0,0 +1,470 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2016 Freescale Semiconductor, Inc.
+// Copyright 2025 NXP
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/sizes.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "thermal_hwmon.h"
+
+#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
+#define TIER_DISABLE 0x0
+#define TEUMR0_V2 0x51009c00
+#define TEUMR0_V21 0x55000c00
+#define TMSARA_V2 0xe
+#define TMU_VER1 0x1
+#define TMU_VER2 0x2
+
+/* errata ID info define */
+#define TMU_ERR052243 BIT(0)
+
+#define REGS_TMR 0x000 /* Mode Register */
+#define TMR_DISABLE 0x0
+#define TMR_ME 0x80000000
+#define TMR_ALPF 0x0c000000
+
+#define REGS_TMTMIR 0x008 /* Temperature measurement interval Register */
+#define TMTMIR_DEFAULT 0x0000000f
+
+#define REGS_V2_TMSR 0x008 /* monitor site register */
+
+#define REGS_V2_TMTMIR 0x00c /* Temperature measurement interval Register */
+
+#define REGS_TIER 0x020 /* Interrupt Enable Register */
+#define TIER_DISABLE 0x0
+
+#define REGS_TIDR 0x24
+#define TEMP_RATE_IRQ_MASK GENMASK(25, 24)
+#define TMRTRCTR 0x70
+#define TMRTRCTR_EN BIT(31)
+#define TMRTRCTR_TEMP_MASK GENMASK(7, 0)
+#define TMFTRCTR 0x74
+#define TMFTRCTR_EN BIT(31)
+#define TMFTRCTR_TEMP_MASK GENMASK(7, 0)
+#define TEMP_RATE_THR_LVL 0x7
+
+#define REGS_TTCFGR 0x080 /* Temperature Configuration Register */
+#define REGS_TSCFGR 0x084 /* Sensor Configuration Register */
+
+#define REGS_TRITSR(n) (0x100 + 16 * (n)) /* Immediate Temperature
+ * Site Register
+ */
+#define TRITSR_V BIT(31)
+#define TRITSR_TP5 BIT(9)
+#define REGS_V2_TMSAR(n) (0x304 + 16 * (n)) /* TMU monitoring
+ * site adjustment register
+ */
+#define REGS_TTRnCR(n) (0xf10 + 4 * (n)) /* Temperature Range n
+ * Control Register
+ */
+#define NUM_TTRCR_V1 4
+#define NUM_TTRCR_MAX 16
+
+#define REGS_IPBRR(n) (0xbf8 + 4 * (n)) /* IP Block Revision
+ * Register n
+ */
+#define REGS_V2_TEUMR(n) (0xf00 + 4 * (n))
+
+/*
+ * Thermal zone data
+ */
+struct qoriq_sensor {
+ int id;
+};
+
+struct tmu_drvdata {
+ u32 teumr0;
+ u32 tmu_errata;
+};
+
+struct qoriq_tmu_data {
+ int ver;
+ u32 ttrcr[NUM_TTRCR_MAX];
+ struct regmap *regmap;
+ struct clk *clk;
+ struct qoriq_sensor sensor[SITES_MAX];
+ const struct tmu_drvdata *drvdata;
+};
+
+static inline bool qoriq_tmu_has_errata(const struct tmu_drvdata *drvdata,
+ u32 flag)
+{
+ return drvdata->tmu_errata & flag;
+}
+
+static struct qoriq_tmu_data *qoriq_sensor_to_data(struct qoriq_sensor *s)
+{
+ return container_of(s, struct qoriq_tmu_data, sensor[s->id]);
+}
+
+static int tmu_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct qoriq_sensor *qsensor = thermal_zone_device_priv(tz);
+ struct qoriq_tmu_data *qdata = qoriq_sensor_to_data(qsensor);
+ u32 val, tidr;
+ /*
+ * REGS_TRITSR(id) has the following layout:
+ *
+ * For TMU Rev1:
+ * 31 ... 7 6 5 4 3 2 1 0
+ * V TEMP
+ *
+ * Where V bit signifies if the measurement is ready and is
+ * within sensor range. TEMP is an 8 bit value representing
+ * temperature in Celsius.
+
+ * For TMU Rev2:
+ * 31 ... 8 7 6 5 4 3 2 1 0
+ * V TEMP
+ *
+ * Where V bit signifies if the measurement is ready and is
+ * within sensor range. TEMP is an 9 bit value representing
+ * temperature in KelVin.
+ */
+
+ regmap_read(qdata->regmap, REGS_TMR, &val);
+ if (!(val & TMR_ME))
+ return -EAGAIN;
+
+ if (regmap_read_poll_timeout(qdata->regmap,
+ REGS_TRITSR(qsensor->id),
+ val,
+ val & TRITSR_V,
+ USEC_PER_MSEC,
+ 10 * USEC_PER_MSEC))
+ return -ENODATA;
+
+ /*ERR052243: If a raising or falling edge happens, try later */
+ if (qoriq_tmu_has_errata(qdata->drvdata, TMU_ERR052243)) {
+ regmap_read(qdata->regmap, REGS_TIDR, &tidr);
+ if (tidr & TEMP_RATE_IRQ_MASK) {
+ regmap_write(qdata->regmap, REGS_TIDR, TEMP_RATE_IRQ_MASK);
+ return -EAGAIN;
+ }
+ }
+
+ if (qdata->ver == TMU_VER1) {
+ *temp = (val & GENMASK(7, 0)) * MILLIDEGREE_PER_DEGREE;
+ } else {
+ if (val & TRITSR_TP5)
+ *temp = milli_kelvin_to_millicelsius((val & GENMASK(8, 0)) *
+ MILLIDEGREE_PER_DEGREE + 500);
+ else
+ *temp = kelvin_to_millicelsius(val & GENMASK(8, 0));
+ }
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops tmu_tz_ops = {
+ .get_temp = tmu_get_temp,
+};
+
+static int qoriq_tmu_register_tmu_zone(struct device *dev,
+ struct qoriq_tmu_data *qdata)
+{
+ int id, sites = 0;
+
+ for (id = 0; id < SITES_MAX; id++) {
+ struct thermal_zone_device *tzd;
+ struct qoriq_sensor *sensor = &qdata->sensor[id];
+ int ret;
+
+ sensor->id = id;
+
+ tzd = devm_thermal_of_zone_register(dev, id,
+ sensor,
+ &tmu_tz_ops);
+ ret = PTR_ERR_OR_ZERO(tzd);
+ if (ret) {
+ if (ret == -ENODEV)
+ continue;
+
+ return ret;
+ }
+
+ if (qdata->ver == TMU_VER1)
+ sites |= 0x1 << (15 - id);
+ else
+ sites |= 0x1 << id;
+
+ devm_thermal_add_hwmon_sysfs(dev, tzd);
+ }
+
+ if (sites) {
+ if (qdata->ver == TMU_VER1) {
+ regmap_write(qdata->regmap, REGS_TMR, TMR_ME | TMR_ALPF | sites);
+ } else {
+ regmap_write(qdata->regmap, REGS_V2_TMSR, sites);
+ regmap_write(qdata->regmap, REGS_TMR, TMR_ME | TMR_ALPF_V2);
+ }
+ }
+
+ return 0;
+}
+
+static int qoriq_tmu_calibration(struct device *dev,
+ struct qoriq_tmu_data *data)
+{
+ int i, val, len;
+ const u32 *calibration;
+ struct device_node *np = dev->of_node;
+
+ len = of_property_count_u32_elems(np, "fsl,tmu-range");
+ 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", data->ttrcr, len);
+ if (val != 0) {
+ dev_err(dev, "failed to read range data.\n");
+ return val;
+ }
+
+ /* Init temperature range registers */
+ for (i = 0; i < len; 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) {
+ dev_err(dev, "invalid calibration data.\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < len; i += 8, calibration += 2) {
+ val = of_read_number(calibration, 1);
+ regmap_write(data->regmap, REGS_TTCFGR, val);
+ val = of_read_number(calibration + 1, 1);
+ regmap_write(data->regmap, REGS_TSCFGR, val);
+ }
+
+ return 0;
+}
+
+static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
+{
+ /* Disable interrupt, using polling instead */
+ regmap_write(data->regmap, REGS_TIER, TIER_DISABLE);
+
+ /* Set update_interval */
+
+ if (data->ver == TMU_VER1) {
+ regmap_write(data->regmap, REGS_TMTMIR, TMTMIR_DEFAULT);
+ } else {
+ regmap_write(data->regmap, REGS_V2_TMTMIR, TMTMIR_DEFAULT);
+ regmap_write(data->regmap, REGS_V2_TEUMR(0),
+ data->drvdata->teumr0);
+ }
+
+ /* ERR052243: Set the raising & falling edge monitor */
+ if (qoriq_tmu_has_errata(data->drvdata, TMU_ERR052243)) {
+ regmap_write(data->regmap, TMRTRCTR, TMRTRCTR_EN |
+ FIELD_PREP(TMRTRCTR_TEMP_MASK, TEMP_RATE_THR_LVL));
+ regmap_write(data->regmap, TMFTRCTR, TMFTRCTR_EN |
+ FIELD_PREP(TMFTRCTR_TEMP_MASK, TEMP_RATE_THR_LVL));
+
+ }
+ /* Disable monitoring */
+ regmap_write(data->regmap, REGS_TMR, TMR_DISABLE);
+}
+
+static const struct regmap_range qoriq_yes_ranges[] = {
+ regmap_reg_range(REGS_TMR, REGS_TSCFGR),
+ regmap_reg_range(REGS_TTRnCR(0), REGS_TTRnCR(15)),
+ regmap_reg_range(REGS_V2_TEUMR(0), REGS_V2_TEUMR(2)),
+ regmap_reg_range(REGS_V2_TMSAR(0), REGS_V2_TMSAR(15)),
+ regmap_reg_range(REGS_IPBRR(0), REGS_IPBRR(1)),
+ /* Read only registers below */
+ regmap_reg_range(REGS_TRITSR(0), REGS_TRITSR(15)),
+};
+
+static const struct regmap_access_table qoriq_wr_table = {
+ .yes_ranges = qoriq_yes_ranges,
+ .n_yes_ranges = ARRAY_SIZE(qoriq_yes_ranges) - 1,
+};
+
+static const struct regmap_access_table qoriq_rd_table = {
+ .yes_ranges = qoriq_yes_ranges,
+ .n_yes_ranges = ARRAY_SIZE(qoriq_yes_ranges),
+};
+
+static void qoriq_tmu_action(void *p)
+{
+ struct qoriq_tmu_data *data = p;
+
+ regmap_write(data->regmap, REGS_TMR, TMR_DISABLE);
+}
+
+static int qoriq_tmu_probe(struct platform_device *pdev)
+{
+ int ret;
+ u32 ver;
+ struct qoriq_tmu_data *data;
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ const bool little_endian = of_property_read_bool(np, "little-endian");
+ const enum regmap_endian format_endian =
+ little_endian ? REGMAP_ENDIAN_LITTLE : REGMAP_ENDIAN_BIG;
+ const struct regmap_config regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .rd_table = &qoriq_rd_table,
+ .wr_table = &qoriq_wr_table,
+ .val_format_endian = format_endian,
+ .max_register = SZ_4K,
+ };
+ void __iomem *base;
+
+ data = devm_kzalloc(dev, sizeof(struct qoriq_tmu_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ ret = PTR_ERR_OR_ZERO(base);
+ 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)
+ return dev_err_probe(dev, ret, "Failed to init regmap\n");
+
+ data->clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(data->clk))
+ return PTR_ERR(data->clk);
+
+ 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)
+ return dev_err_probe(dev, ret, "Failed to read IP block version\n");
+
+ data->ver = (ver >> 8) & 0xff;
+
+ data->drvdata = of_device_get_match_data(&pdev->dev);
+ if (!data->drvdata)
+ return dev_err_probe(dev, -EINVAL, "Failed to get match data\n");
+
+ qoriq_tmu_init_device(data); /* TMU initialization */
+
+ ret = qoriq_tmu_calibration(dev, data); /* TMU calibration */
+ if (ret < 0)
+ return ret;
+
+ ret = qoriq_tmu_register_tmu_zone(dev, data);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to register sensors\n");
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static int qoriq_tmu_suspend(struct device *dev)
+{
+ struct qoriq_tmu_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, 0);
+ 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 qoriq_tmu_resume(struct device *dev)
+{
+ int ret;
+ struct qoriq_tmu_data *data = dev_get_drvdata(dev);
+
+ ret = clk_prepare_enable(data->clk);
+ if (ret)
+ return ret;
+
+ if (data->ver > TMU_VER1) {
+ ret = regmap_clear_bits(data->regmap, REGS_TMR, TMR_CMD);
+ if (ret)
+ goto disable_clk;
+ }
+
+ /* Enable monitoring */
+ ret = regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, TMR_ME);
+ if (ret)
+ goto disable_clk;
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(data->clk);
+
+ return ret;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops,
+ qoriq_tmu_suspend, qoriq_tmu_resume);
+
+static const struct tmu_drvdata qoriq_tmu_data = {
+ .teumr0 = TEUMR0_V2,
+};
+
+static const struct tmu_drvdata imx8mq_tmu_data = {
+ .teumr0 = TEUMR0_V2,
+};
+
+static const struct tmu_drvdata imx93_data = {
+ .teumr0 = TEUMR0_V21,
+ .tmu_errata = TMU_ERR052243,
+};
+
+static const struct of_device_id qoriq_tmu_match[] = {
+ { .compatible = "fsl,qoriq-tmu", .data = &qoriq_tmu_data },
+ { .compatible = "fsl,imx8mq-tmu", .data = &imx8mq_tmu_data },
+ { .compatible = "fsl,imx93-tmu", .data = &imx93_data },
+ {},
+};
+MODULE_DEVICE_TABLE(of, qoriq_tmu_match);
+
+static struct platform_driver qoriq_tmu = {
+ .driver = {
+ .name = "qoriq_thermal",
+ .pm = pm_sleep_ptr(&qoriq_tmu_pm_ops),
+ .of_match_table = qoriq_tmu_match,
+ },
+ .probe = qoriq_tmu_probe,
+};
+module_platform_driver(qoriq_tmu);
+
+MODULE_AUTHOR("Jia Hongtao <hongtao.jia@nxp.com>");
+MODULE_DESCRIPTION("QorIQ Thermal Monitoring Unit driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/renesas/Kconfig b/drivers/thermal/renesas/Kconfig
new file mode 100644
index 000000000..5735c8728
--- /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 000000000..8f5ae9af2
--- /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/renesas/rcar_gen3_thermal.c b/drivers/thermal/renesas/rcar_gen3_thermal.c
new file mode 100644
index 000000000..94804816e
--- /dev/null
+++ b/drivers/thermal/renesas/rcar_gen3_thermal.c
@@ -0,0 +1,634 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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.
+ * Copyright (C) 2016 Sang Engineering
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/thermal.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_IRQEN 0x10
+#define REG_GEN3_IRQTEMP1 0x14
+#define REG_GEN3_IRQTEMP2 0x18
+#define REG_GEN3_IRQTEMP3 0x1c
+#define REG_GEN3_THCTR 0x20
+#define REG_GEN3_TEMP 0x28
+#define REG_GEN3_THCODE1 0x50
+#define REG_GEN3_THCODE2 0x54
+#define REG_GEN3_THCODE3 0x58
+#define REG_GEN3_PTAT1 0x5c
+#define REG_GEN3_PTAT2 0x60
+#define REG_GEN3_PTAT3 0x64
+#define REG_GEN3_THSCP 0x68
+#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
+
+/* IRQ{STR,MSK,EN} bits */
+#define IRQ_TEMP1 BIT(0)
+#define IRQ_TEMP2 BIT(1)
+#define IRQ_TEMP3 BIT(2)
+#define IRQ_TEMPD1 BIT(3)
+#define IRQ_TEMPD2 BIT(4)
+#define IRQ_TEMPD3 BIT(5)
+
+/* THCTR bits */
+#define THCTR_PONM BIT(6)
+#define THCTR_THSST BIT(0)
+
+/* THSCP bits */
+#define THSCP_COR_PARA_VLD (BIT(15) | BIT(14))
+
+#define CTEMP_MASK 0xfff
+
+#define MCELSIUS(temp) ((temp) * 1000)
+#define GEN3_FUSE_MASK 0xfff
+#define GEN4_FUSE_MASK 0xfff
+
+#define TSC_MAX_NUM 5
+
+struct rcar_gen3_thermal_priv;
+
+struct rcar_gen3_thermal_fuse_info {
+ u32 ptat[3];
+ u32 thcode[3];
+ u32 mask;
+};
+
+struct rcar_gen3_thermal_fuse_default {
+ u32 ptat[3];
+ u32 thcodes[TSC_MAX_NUM][3];
+};
+
+struct rcar_thermal_info {
+ 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;
+ /* Different coefficients are used depending on a threshold. */
+ struct {
+ struct equation_set_coef below;
+ struct equation_set_coef above;
+ } coef;
+ int thcode[3];
+};
+
+struct rcar_gen3_thermal_priv {
+ struct rcar_gen3_thermal_tsc *tscs[TSC_MAX_NUM];
+ struct thermal_zone_device_ops ops;
+ unsigned int num_tscs;
+ int ptat[3];
+ int tj_t;
+ const struct rcar_thermal_info *info;
+};
+
+static inline u32 rcar_gen3_thermal_read(struct rcar_gen3_thermal_tsc *tsc,
+ u32 reg)
+{
+ return ioread32(tsc->base + reg);
+}
+
+static inline void rcar_gen3_thermal_write(struct rcar_gen3_thermal_tsc *tsc,
+ u32 reg, u32 data)
+{
+ iowrite32(data, tsc->base + reg);
+}
+
+/*
+ * Linear approximation for temperature
+ *
+ * [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 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.
+ *
+ * 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.
+ */
+
+static void rcar_gen3_thermal_shared_coefs(struct rcar_gen3_thermal_priv *priv)
+{
+ 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 void rcar_gen3_thermal_tsc_coefs(struct rcar_gen3_thermal_priv *priv,
+ struct rcar_gen3_thermal_tsc *tsc)
+{
+ 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]);
+
+ 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);
+ struct rcar_gen3_thermal_priv *priv = tsc->priv;
+ const struct equation_set_coef *coef;
+ int adj, decicelsius, reg, thcode;
+
+ /* Read register and convert to millidegree Celsius */
+ reg = rcar_gen3_thermal_read(tsc, REG_GEN3_TEMP) & CTEMP_MASK;
+
+ 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. */
+
+ /* Reporting is done in millidegree Celsius */
+ *temp = decicelsius * 100 + adj * 1000;
+
+ return 0;
+}
+
+static int rcar_gen3_thermal_mcelsius_to_temp(struct rcar_gen3_thermal_tsc *tsc,
+ int mcelsius)
+{
+ 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 < 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 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)
+{
+ struct rcar_gen3_thermal_tsc *tsc = thermal_zone_device_priv(tz);
+ u32 irqmsk = 0;
+
+ if (low != -INT_MAX) {
+ irqmsk |= IRQ_TEMPD1;
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP1,
+ rcar_gen3_thermal_mcelsius_to_temp(tsc, low));
+ }
+
+ if (high != INT_MAX) {
+ irqmsk |= IRQ_TEMP2;
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP2,
+ rcar_gen3_thermal_mcelsius_to_temp(tsc, high));
+ }
+
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, irqmsk);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops rcar_gen3_tz_of_ops = {
+ .get_temp = rcar_gen3_thermal_get_temp,
+ .set_trips = rcar_gen3_thermal_set_trips,
+};
+
+static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data)
+{
+ struct rcar_gen3_thermal_priv *priv = data;
+ unsigned int i;
+ u32 status;
+
+ for (i = 0; i < priv->num_tscs; i++) {
+ status = rcar_gen3_thermal_read(priv->tscs[i], REG_GEN3_IRQSTR);
+ rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQSTR, 0);
+ if (status && priv->tscs[i]->zone)
+ thermal_zone_device_update(priv->tscs[i]->zone,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void rcar_gen3_thermal_fetch_fuses(struct rcar_gen3_thermal_priv *priv)
+{
+ 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], 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, 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->fuses ||
+ (thscp & THSCP_COR_PARA_VLD) != THSCP_COR_PARA_VLD) {
+ /* Default THCODE values in case FUSEs are not set. */
+ 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] = fuse_defaults->thcodes[i][0];
+ tsc->thcode[1] = fuse_defaults->thcodes[i][1];
+ tsc->thcode[2] = fuse_defaults->thcodes[i][2];
+ }
+
+ return false;
+ }
+
+ rcar_gen3_thermal_fetch_fuses(priv);
+
+ return true;
+}
+
+static void rcar_gen3_thermal_init(struct rcar_gen3_thermal_priv *priv,
+ struct rcar_gen3_thermal_tsc *tsc)
+{
+ u32 reg_val;
+
+ reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR);
+ reg_val &= ~THCTR_PONM;
+ rcar_gen3_thermal_write(tsc, REG_GEN3_THCTR, reg_val);
+
+ usleep_range(1000, 2000);
+
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0);
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
+ if (priv->ops.set_trips)
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN,
+ IRQ_TEMPD1 | IRQ_TEMP2);
+
+ reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR);
+ reg_val |= THCTR_THSST;
+ rcar_gen3_thermal_write(tsc, REG_GEN3_THCTR, reg_val);
+
+ 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 = {
+ .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 = {
+ .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 = {
+ .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[] = {
+ {
+ .compatible = "renesas,r8a774a1-thermal",
+ .data = &rcar_m3w_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a774b1-thermal",
+ .data = &rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a774e1-thermal",
+ .data = &rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a7795-thermal",
+ .data = &rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a7796-thermal",
+ .data = &rcar_m3w_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a77961-thermal",
+ .data = &rcar_m3w_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a77965-thermal",
+ .data = &rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a77980-thermal",
+ .data = &rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a779a0-thermal",
+ .data = &rcar_gen3_thermal_info,
+ },
+ {
+ .compatible = "renesas,r8a779f0-thermal",
+ .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 void rcar_gen3_thermal_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ pm_runtime_put(dev);
+ pm_runtime_disable(dev);
+}
+
+static void rcar_gen3_hwmon_action(void *data)
+{
+ struct thermal_zone_device *zone = data;
+
+ thermal_remove_hwmon_sysfs(zone);
+}
+
+static int rcar_gen3_thermal_request_irqs(struct rcar_gen3_thermal_priv *priv,
+ struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ unsigned int i;
+ char *irqname;
+ int ret, irq;
+
+ for (i = 0; i < 2; i++) {
+ irq = platform_get_irq_optional(pdev, i);
+ if (irq < 0)
+ return irq;
+
+ irqname = devm_kasprintf(dev, GFP_KERNEL, "%s:ch%d",
+ dev_name(dev), i);
+ if (!irqname)
+ return -ENOMEM;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ rcar_gen3_thermal_irq,
+ IRQF_ONESHOT, irqname, priv);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rcar_gen3_thermal_probe(struct platform_device *pdev)
+{
+ struct rcar_gen3_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct thermal_zone_device *zone;
+ unsigned int i;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->ops = rcar_gen3_tz_of_ops;
+
+ priv->info = of_device_get_match_data(dev);
+ platform_set_drvdata(pdev, priv);
+
+ if (rcar_gen3_thermal_request_irqs(priv, pdev))
+ priv->ops.set_trips = NULL;
+
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+
+ for (i = 0; i < TSC_MAX_NUM; i++) {
+ struct rcar_gen3_thermal_tsc *tsc;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ if (!res)
+ break;
+
+ tsc = devm_kzalloc(dev, sizeof(*tsc), GFP_KERNEL);
+ if (!tsc) {
+ ret = -ENOMEM;
+ goto error_unregister;
+ }
+
+ tsc->priv = priv;
+ tsc->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(tsc->base)) {
+ ret = PTR_ERR(tsc->base);
+ goto error_unregister;
+ }
+
+ priv->tscs[i] = tsc;
+ }
+
+ priv->num_tscs = i;
+
+ 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_tsc_coefs(priv, tsc);
+
+ zone = devm_thermal_of_zone_register(dev, i, tsc, &priv->ops);
+ if (IS_ERR(zone)) {
+ dev_err(dev, "Sensor %u: Can't register thermal zone\n", i);
+ ret = PTR_ERR(zone);
+ goto error_unregister;
+ }
+ tsc->zone = zone;
+
+ ret = thermal_add_hwmon_sysfs(tsc->zone);
+ if (ret)
+ goto error_unregister;
+
+ ret = devm_add_action_or_reset(dev, rcar_gen3_hwmon_action, zone);
+ if (ret)
+ goto error_unregister;
+
+ dev_info(dev, "Sensor %u: Loaded\n", i);
+ }
+
+ if (!priv->num_tscs) {
+ ret = -ENODEV;
+ goto error_unregister;
+ }
+
+ return 0;
+
+error_unregister:
+ rcar_gen3_thermal_remove(pdev);
+
+ return ret;
+}
+
+static int rcar_gen3_thermal_resume(struct device *dev)
+{
+ struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
+ unsigned int i;
+
+ for (i = 0; i < priv->num_tscs; i++) {
+ struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
+
+ rcar_gen3_thermal_init(priv, tsc);
+ }
+
+ return 0;
+}
+
+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 = pm_sleep_ptr(&rcar_gen3_thermal_pm_ops),
+ .of_match_table = rcar_gen3_thermal_dt_ids,
+ },
+ .probe = rcar_gen3_thermal_probe,
+ .remove = rcar_gen3_thermal_remove,
+};
+module_platform_driver(rcar_gen3_thermal_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("R-Car Gen3 THS thermal sensor driver");
+MODULE_AUTHOR("Wolfram Sang <wsa+renesas@sang-engineering.com>");
diff --git a/drivers/thermal/renesas/rcar_thermal.c b/drivers/thermal/renesas/rcar_thermal.c
new file mode 100644
index 000000000..9a9038671
--- /dev/null
+++ b/drivers/thermal/renesas/rcar_thermal.c
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * R-Car THS/TSC thermal sensor driver
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/thermal.h>
+
+#include "../thermal_hwmon.h"
+
+#define IDLE_INTERVAL 5000
+
+#define COMMON_STR 0x00
+#define COMMON_ENR 0x04
+#define COMMON_INTMSK 0x0c
+
+#define REG_POSNEG 0x20
+#define REG_FILONOFF 0x28
+#define REG_THSCR 0x2c
+#define REG_THSSR 0x30
+#define REG_INTCTRL 0x34
+
+/* THSCR */
+#define CPCTL (1 << 12)
+
+/* THSSR */
+#define CTEMP 0x3f
+
+struct rcar_thermal_common {
+ void __iomem *base;
+ struct device *dev;
+ struct list_head head;
+ spinlock_t lock;
+};
+
+struct rcar_thermal_chip {
+ unsigned int use_of_thermal : 1;
+ unsigned int has_filonoff : 1;
+ unsigned int irq_per_ch : 1;
+ unsigned int needs_suspend_resume : 1;
+ unsigned int nirqs;
+ unsigned int ctemp_bands;
+};
+
+static const struct rcar_thermal_chip rcar_thermal = {
+ .use_of_thermal = 0,
+ .has_filonoff = 1,
+ .irq_per_ch = 0,
+ .needs_suspend_resume = 0,
+ .nirqs = 1,
+ .ctemp_bands = 1,
+};
+
+static const struct rcar_thermal_chip rcar_gen2_thermal = {
+ .use_of_thermal = 1,
+ .has_filonoff = 1,
+ .irq_per_ch = 0,
+ .needs_suspend_resume = 0,
+ .nirqs = 1,
+ .ctemp_bands = 1,
+};
+
+static const struct rcar_thermal_chip rcar_gen3_thermal = {
+ .use_of_thermal = 1,
+ .has_filonoff = 0,
+ .irq_per_ch = 1,
+ .needs_suspend_resume = 1,
+ /*
+ * The Gen3 chip has 3 interrupts, but this driver uses only 2
+ * interrupts to detect a temperature change, rise or fall.
+ */
+ .nirqs = 2,
+ .ctemp_bands = 2,
+};
+
+struct rcar_thermal_priv {
+ void __iomem *base;
+ struct rcar_thermal_common *common;
+ struct thermal_zone_device *zone;
+ const struct rcar_thermal_chip *chip;
+ struct delayed_work work;
+ struct mutex lock;
+ struct list_head list;
+ int id;
+};
+
+#define rcar_thermal_for_each_priv(pos, common) \
+ list_for_each_entry(pos, &common->head, list)
+
+#define MCELSIUS(temp) ((temp) * 1000)
+#define rcar_priv_to_dev(priv) ((priv)->common->dev)
+#define rcar_has_irq_support(priv) ((priv)->common->base)
+#define rcar_id_to_shift(priv) ((priv)->id * 8)
+
+static const struct of_device_id rcar_thermal_dt_ids[] = {
+ {
+ .compatible = "renesas,rcar-thermal",
+ .data = &rcar_thermal,
+ },
+ {
+ .compatible = "renesas,rcar-gen2-thermal",
+ .data = &rcar_gen2_thermal,
+ },
+ {
+ .compatible = "renesas,thermal-r8a774c0",
+ .data = &rcar_gen3_thermal,
+ },
+ {
+ .compatible = "renesas,thermal-r8a77970",
+ .data = &rcar_gen3_thermal,
+ },
+ {
+ .compatible = "renesas,thermal-r8a77990",
+ .data = &rcar_gen3_thermal,
+ },
+ {
+ .compatible = "renesas,thermal-r8a77995",
+ .data = &rcar_gen3_thermal,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rcar_thermal_dt_ids);
+
+/*
+ * basic functions
+ */
+#define rcar_thermal_common_read(c, r) \
+ _rcar_thermal_common_read(c, COMMON_ ##r)
+static u32 _rcar_thermal_common_read(struct rcar_thermal_common *common,
+ u32 reg)
+{
+ return ioread32(common->base + reg);
+}
+
+#define rcar_thermal_common_write(c, r, d) \
+ _rcar_thermal_common_write(c, COMMON_ ##r, d)
+static void _rcar_thermal_common_write(struct rcar_thermal_common *common,
+ u32 reg, u32 data)
+{
+ iowrite32(data, common->base + reg);
+}
+
+#define rcar_thermal_common_bset(c, r, m, d) \
+ _rcar_thermal_common_bset(c, COMMON_ ##r, m, d)
+static void _rcar_thermal_common_bset(struct rcar_thermal_common *common,
+ u32 reg, u32 mask, u32 data)
+{
+ u32 val;
+
+ val = ioread32(common->base + reg);
+ val &= ~mask;
+ val |= (data & mask);
+ iowrite32(val, common->base + reg);
+}
+
+#define rcar_thermal_read(p, r) _rcar_thermal_read(p, REG_ ##r)
+static u32 _rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg)
+{
+ return ioread32(priv->base + reg);
+}
+
+#define rcar_thermal_write(p, r, d) _rcar_thermal_write(p, REG_ ##r, d)
+static void _rcar_thermal_write(struct rcar_thermal_priv *priv,
+ u32 reg, u32 data)
+{
+ iowrite32(data, priv->base + reg);
+}
+
+#define rcar_thermal_bset(p, r, m, d) _rcar_thermal_bset(p, REG_ ##r, m, d)
+static void _rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg,
+ u32 mask, u32 data)
+{
+ u32 val;
+
+ val = ioread32(priv->base + reg);
+ val &= ~mask;
+ val |= (data & mask);
+ iowrite32(val, priv->base + reg);
+}
+
+/*
+ * zone device functions
+ */
+static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
+{
+ struct device *dev = rcar_priv_to_dev(priv);
+ int old, new, ctemp = -EINVAL;
+ unsigned int i;
+
+ mutex_lock(&priv->lock);
+
+ /*
+ * TSC decides a value of CPTAP automatically,
+ * and this is the conditions which validate interrupt.
+ */
+ rcar_thermal_bset(priv, THSCR, CPCTL, CPCTL);
+
+ old = ~0;
+ for (i = 0; i < 128; i++) {
+ /*
+ * we need to wait 300us after changing comparator offset
+ * to get stable temperature.
+ * see "Usage Notes" on datasheet
+ */
+ usleep_range(300, 400);
+
+ new = rcar_thermal_read(priv, THSSR) & CTEMP;
+ if (new == old) {
+ ctemp = new;
+ break;
+ }
+ old = new;
+ }
+
+ if (ctemp < 0) {
+ dev_err(dev, "thermal sensor was broken\n");
+ goto err_out_unlock;
+ }
+
+ /*
+ * enable IRQ
+ */
+ if (rcar_has_irq_support(priv)) {
+ if (priv->chip->has_filonoff)
+ rcar_thermal_write(priv, FILONOFF, 0);
+
+ /* enable Rising/Falling edge interrupt */
+ rcar_thermal_write(priv, POSNEG, 0x1);
+ rcar_thermal_write(priv, INTCTRL, (((ctemp - 0) << 8) |
+ ((ctemp - 1) << 0)));
+ }
+
+err_out_unlock:
+ mutex_unlock(&priv->lock);
+
+ return ctemp;
+}
+
+static int rcar_thermal_get_current_temp(struct rcar_thermal_priv *priv,
+ int *temp)
+{
+ int ctemp;
+
+ ctemp = rcar_thermal_update_temp(priv);
+ if (ctemp < 0)
+ return ctemp;
+
+ /* Guaranteed operating range is -45C to 125C. */
+
+ if (priv->chip->ctemp_bands == 1)
+ *temp = MCELSIUS((ctemp * 5) - 65);
+ else if (ctemp < 24)
+ *temp = MCELSIUS(((ctemp * 55) - 720) / 10);
+ else
+ *temp = MCELSIUS((ctemp * 5) - 60);
+
+ return 0;
+}
+
+static int rcar_thermal_get_temp(struct thermal_zone_device *zone, int *temp)
+{
+ struct rcar_thermal_priv *priv = thermal_zone_device_priv(zone);
+
+ return rcar_thermal_get_current_temp(priv, temp);
+}
+
+static const struct thermal_zone_device_ops rcar_thermal_zone_ops = {
+ .get_temp = rcar_thermal_get_temp,
+};
+
+static struct thermal_trip trips[] = {
+ { .type = THERMAL_TRIP_CRITICAL, .temperature = 90000 }
+};
+
+/*
+ * interrupt
+ */
+#define rcar_thermal_irq_enable(p) _rcar_thermal_irq_ctrl(p, 1)
+#define rcar_thermal_irq_disable(p) _rcar_thermal_irq_ctrl(p, 0)
+static void _rcar_thermal_irq_ctrl(struct rcar_thermal_priv *priv, int enable)
+{
+ struct rcar_thermal_common *common = priv->common;
+ unsigned long flags;
+ u32 mask = 0x3 << rcar_id_to_shift(priv); /* enable Rising/Falling */
+
+ if (!rcar_has_irq_support(priv))
+ return;
+
+ spin_lock_irqsave(&common->lock, flags);
+
+ rcar_thermal_common_bset(common, INTMSK, mask, enable ? 0 : mask);
+
+ spin_unlock_irqrestore(&common->lock, flags);
+}
+
+static void rcar_thermal_work(struct work_struct *work)
+{
+ struct rcar_thermal_priv *priv;
+ int ret;
+
+ priv = container_of(work, struct rcar_thermal_priv, work.work);
+
+ ret = rcar_thermal_update_temp(priv);
+ if (ret < 0)
+ return;
+
+ rcar_thermal_irq_enable(priv);
+
+ thermal_zone_device_update(priv->zone, THERMAL_EVENT_UNSPECIFIED);
+}
+
+static u32 rcar_thermal_had_changed(struct rcar_thermal_priv *priv, u32 status)
+{
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ status = (status >> rcar_id_to_shift(priv)) & 0x3;
+
+ if (status) {
+ dev_dbg(dev, "thermal%d %s%s\n",
+ priv->id,
+ (status & 0x2) ? "Rising " : "",
+ (status & 0x1) ? "Falling" : "");
+ }
+
+ return status;
+}
+
+static irqreturn_t rcar_thermal_irq(int irq, void *data)
+{
+ struct rcar_thermal_common *common = data;
+ struct rcar_thermal_priv *priv;
+ u32 status, mask;
+
+ spin_lock(&common->lock);
+
+ mask = rcar_thermal_common_read(common, INTMSK);
+ status = rcar_thermal_common_read(common, STR);
+ rcar_thermal_common_write(common, STR, 0x000F0F0F & mask);
+
+ spin_unlock(&common->lock);
+
+ status = status & ~mask;
+
+ /*
+ * check the status
+ */
+ rcar_thermal_for_each_priv(priv, common) {
+ if (rcar_thermal_had_changed(priv, status)) {
+ rcar_thermal_irq_disable(priv);
+ queue_delayed_work(system_freezable_wq, &priv->work,
+ msecs_to_jiffies(300));
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * platform functions
+ */
+static void rcar_thermal_remove(struct platform_device *pdev)
+{
+ struct rcar_thermal_common *common = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct rcar_thermal_priv *priv;
+
+ rcar_thermal_for_each_priv(priv, common) {
+ rcar_thermal_irq_disable(priv);
+ cancel_delayed_work_sync(&priv->work);
+ if (priv->chip->use_of_thermal)
+ thermal_remove_hwmon_sysfs(priv->zone);
+ else
+ thermal_zone_device_unregister(priv->zone);
+ }
+
+ pm_runtime_put(dev);
+ pm_runtime_disable(dev);
+}
+
+static int rcar_thermal_probe(struct platform_device *pdev)
+{
+ struct rcar_thermal_common *common;
+ struct rcar_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ const struct rcar_thermal_chip *chip = of_device_get_match_data(dev);
+ int mres = 0;
+ int i;
+ int ret = -ENODEV;
+ int idle = IDLE_INTERVAL;
+ u32 enr_bits = 0;
+
+ common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL);
+ if (!common)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, common);
+
+ INIT_LIST_HEAD(&common->head);
+ spin_lock_init(&common->lock);
+ common->dev = dev;
+
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+
+ for (i = 0; i < chip->nirqs; i++) {
+ int irq;
+
+ ret = platform_get_irq_optional(pdev, i);
+ if (ret < 0 && ret != -ENXIO)
+ goto error_unregister;
+ if (ret > 0)
+ irq = ret;
+ else
+ break;
+
+ if (!common->base) {
+ /*
+ * platform has IRQ support.
+ * Then, driver uses common registers
+ * rcar_has_irq_support() will be enabled
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM,
+ mres++);
+ common->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(common->base)) {
+ ret = PTR_ERR(common->base);
+ goto error_unregister;
+ }
+
+ idle = 0; /* polling delay is not needed */
+ }
+
+ ret = devm_request_irq(dev, irq, rcar_thermal_irq,
+ IRQF_SHARED, dev_name(dev), common);
+ if (ret)
+ goto error_unregister;
+
+ /* update ENR bits */
+ if (chip->irq_per_ch)
+ enr_bits |= 1 << i;
+ }
+
+ for (i = 0;; i++) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, mres++);
+ if (!res)
+ break;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto error_unregister;
+ }
+
+ priv->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(priv->base)) {
+ ret = PTR_ERR(priv->base);
+ goto error_unregister;
+ }
+
+ priv->common = common;
+ priv->id = i;
+ priv->chip = chip;
+ mutex_init(&priv->lock);
+ INIT_LIST_HEAD(&priv->list);
+ INIT_DELAYED_WORK(&priv->work, rcar_thermal_work);
+ ret = rcar_thermal_update_temp(priv);
+ if (ret < 0)
+ goto error_unregister;
+
+ if (chip->use_of_thermal) {
+ priv->zone = devm_thermal_of_zone_register(
+ dev, i, priv,
+ &rcar_thermal_zone_ops);
+ } else {
+ priv->zone = thermal_zone_device_register_with_trips(
+ "rcar_thermal", trips, ARRAY_SIZE(trips), priv,
+ &rcar_thermal_zone_ops, NULL, 0,
+ idle);
+ }
+ if (IS_ERR(priv->zone)) {
+ dev_err(dev, "can't register thermal zone\n");
+ ret = PTR_ERR(priv->zone);
+ priv->zone = NULL;
+ goto error_unregister;
+ }
+
+ if (chip->use_of_thermal)
+ ret = thermal_add_hwmon_sysfs(priv->zone);
+ else
+ ret = thermal_zone_device_enable(priv->zone);
+ if (ret)
+ goto error_unregister;
+
+ rcar_thermal_irq_enable(priv);
+
+ list_move_tail(&priv->list, &common->head);
+
+ /* update ENR bits */
+ if (!chip->irq_per_ch)
+ enr_bits |= 3 << (i * 8);
+ }
+
+ if (common->base && enr_bits)
+ rcar_thermal_common_write(common, ENR, enr_bits);
+
+ dev_info(dev, "%d sensor probed\n", i);
+
+ return 0;
+
+error_unregister:
+ rcar_thermal_remove(pdev);
+
+ return ret;
+}
+
+static int rcar_thermal_suspend(struct device *dev)
+{
+ struct rcar_thermal_common *common = dev_get_drvdata(dev);
+ struct rcar_thermal_priv *priv = list_first_entry(&common->head,
+ typeof(*priv), list);
+
+ if (priv->chip->needs_suspend_resume) {
+ rcar_thermal_common_write(common, ENR, 0);
+ rcar_thermal_irq_disable(priv);
+ rcar_thermal_bset(priv, THSCR, CPCTL, 0);
+ }
+
+ return 0;
+}
+
+static int rcar_thermal_resume(struct device *dev)
+{
+ struct rcar_thermal_common *common = dev_get_drvdata(dev);
+ struct rcar_thermal_priv *priv = list_first_entry(&common->head,
+ typeof(*priv), list);
+ int ret;
+
+ if (priv->chip->needs_suspend_resume) {
+ ret = rcar_thermal_update_temp(priv);
+ if (ret < 0)
+ return ret;
+ rcar_thermal_irq_enable(priv);
+ rcar_thermal_common_write(common, ENR, 0x03);
+ }
+
+ return 0;
+}
+
+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 = pm_sleep_ptr(&rcar_thermal_pm_ops),
+ .of_match_table = rcar_thermal_dt_ids,
+ },
+ .probe = rcar_thermal_probe,
+ .remove = rcar_thermal_remove,
+};
+module_platform_driver(rcar_thermal_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("R-Car THS/TSC thermal sensor driver");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
diff --git a/drivers/thermal/renesas/rzg2l_thermal.c b/drivers/thermal/renesas/rzg2l_thermal.c
new file mode 100644
index 000000000..d9afd0619
--- /dev/null
+++ b/drivers/thermal/renesas/rzg2l_thermal.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G2L TSU Thermal Sensor Driver
+ *
+ * Copyright (C) 2021 Renesas Electronics Corporation
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "../thermal_hwmon.h"
+
+#define CTEMP_MASK 0xFFF
+
+/* default calibration values, if FUSE values are missing */
+#define SW_CALIB0_VAL 3148
+#define SW_CALIB1_VAL 503
+
+/* Register offsets */
+#define TSU_SM 0x00
+#define TSU_ST 0x04
+#define TSU_SAD 0x0C
+#define TSU_SS 0x10
+
+#define OTPTSUTRIM_REG(n) (0x18 + ((n) * 0x4))
+#define OTPTSUTRIM_EN_MASK BIT(31)
+#define OTPTSUTRIM_MASK GENMASK(11, 0)
+
+/* Sensor Mode Register(TSU_SM) */
+#define TSU_SM_EN_TS BIT(0)
+#define TSU_SM_ADC_EN_TS BIT(1)
+#define TSU_SM_NORMAL_MODE (TSU_SM_EN_TS | TSU_SM_ADC_EN_TS)
+
+/* TSU_ST bits */
+#define TSU_ST_START BIT(0)
+
+#define TSU_SS_CONV_RUNNING BIT(0)
+
+#define TS_CODE_AVE_SCALE(x) ((x) * 1000000)
+#define MCELSIUS(temp) ((temp) * MILLIDEGREE_PER_DEGREE)
+#define TS_CODE_CAP_TIMES 8 /* Total number of ADC data samples */
+
+#define RZG2L_THERMAL_GRAN 500 /* milli Celsius */
+#define RZG2L_TSU_SS_TIMEOUT_US 1000
+
+#define CURVATURE_CORRECTION_CONST 13
+
+struct rzg2l_thermal_priv {
+ struct device *dev;
+ void __iomem *base;
+ struct thermal_zone_device *zone;
+ struct reset_control *rstc;
+ u32 calib0, calib1;
+};
+
+static inline u32 rzg2l_thermal_read(struct rzg2l_thermal_priv *priv, u32 reg)
+{
+ return ioread32(priv->base + reg);
+}
+
+static inline void rzg2l_thermal_write(struct rzg2l_thermal_priv *priv, u32 reg,
+ u32 data)
+{
+ iowrite32(data, priv->base + reg);
+}
+
+static int rzg2l_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct rzg2l_thermal_priv *priv = thermal_zone_device_priv(tz);
+ u32 result = 0, dsensor, ts_code_ave;
+ int val, i;
+
+ for (i = 0; i < TS_CODE_CAP_TIMES ; i++) {
+ /*
+ * TSU repeats measurement at 20 microseconds intervals and
+ * automatically updates the results of measurement. As per
+ * the HW manual for measuring temperature we need to read 8
+ * values consecutively and then take the average.
+ * ts_code_ave = (ts_code[0] + ⋯ + ts_code[7]) / 8
+ */
+ result += rzg2l_thermal_read(priv, TSU_SAD) & CTEMP_MASK;
+ usleep_range(20, 30);
+ }
+
+ ts_code_ave = result / TS_CODE_CAP_TIMES;
+
+ /*
+ * Calculate actual sensor value by applying curvature correction formula
+ * dsensor = ts_code_ave / (1 + ts_code_ave * 0.000013). Here we are doing
+ * integer calculation by scaling all the values by 1000000.
+ */
+ dsensor = TS_CODE_AVE_SCALE(ts_code_ave) /
+ (TS_CODE_AVE_SCALE(1) + (ts_code_ave * CURVATURE_CORRECTION_CONST));
+
+ /*
+ * The temperature Tj is calculated by the formula
+ * Tj = (dsensor − calib1) * 165/ (calib0 − calib1) − 40
+ * where calib0 and calib1 are the calibration values.
+ */
+ val = ((dsensor - priv->calib1) * (MCELSIUS(165) /
+ (priv->calib0 - priv->calib1))) - MCELSIUS(40);
+
+ *temp = roundup(val, RZG2L_THERMAL_GRAN);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops rzg2l_tz_of_ops = {
+ .get_temp = rzg2l_thermal_get_temp,
+};
+
+static int rzg2l_thermal_init(struct rzg2l_thermal_priv *priv)
+{
+ u32 reg_val;
+
+ rzg2l_thermal_write(priv, TSU_SM, TSU_SM_NORMAL_MODE);
+ rzg2l_thermal_write(priv, TSU_ST, 0);
+
+ /*
+ * Before setting the START bit, TSU should be in normal operating
+ * mode. As per the HW manual, it will take 60 µs to place the TSU
+ * into normal operating mode.
+ */
+ usleep_range(60, 80);
+
+ reg_val = rzg2l_thermal_read(priv, TSU_ST);
+ reg_val |= TSU_ST_START;
+ rzg2l_thermal_write(priv, TSU_ST, reg_val);
+
+ return readl_poll_timeout(priv->base + TSU_SS, reg_val,
+ reg_val == TSU_SS_CONV_RUNNING, 50,
+ RZG2L_TSU_SS_TIMEOUT_US);
+}
+
+static void rzg2l_thermal_reset_assert_pm_disable_put(struct platform_device *pdev)
+{
+ struct rzg2l_thermal_priv *priv = dev_get_drvdata(&pdev->dev);
+
+ pm_runtime_put(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ reset_control_assert(priv->rstc);
+}
+
+static 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);
+}
+
+static int rzg2l_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *zone;
+ struct rzg2l_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->dev = dev;
+ priv->rstc = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(priv->rstc))
+ return dev_err_probe(dev, PTR_ERR(priv->rstc),
+ "failed to get cpg reset");
+
+ ret = reset_control_deassert(priv->rstc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to deassert");
+
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+
+ priv->calib0 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0));
+ if (priv->calib0 & OTPTSUTRIM_EN_MASK)
+ priv->calib0 &= OTPTSUTRIM_MASK;
+ else
+ priv->calib0 = SW_CALIB0_VAL;
+
+ priv->calib1 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(1));
+ if (priv->calib1 & OTPTSUTRIM_EN_MASK)
+ priv->calib1 &= OTPTSUTRIM_MASK;
+ else
+ priv->calib1 = SW_CALIB1_VAL;
+
+ platform_set_drvdata(pdev, priv);
+ ret = rzg2l_thermal_init(priv);
+ if (ret) {
+ dev_err(dev, "Failed to start TSU");
+ goto err;
+ }
+
+ zone = devm_thermal_of_zone_register(dev, 0, priv,
+ &rzg2l_tz_of_ops);
+ if (IS_ERR(zone)) {
+ dev_err(dev, "Can't register thermal zone");
+ ret = PTR_ERR(zone);
+ goto err;
+ }
+
+ priv->zone = zone;
+ ret = thermal_add_hwmon_sysfs(priv->zone);
+ if (ret)
+ goto err;
+
+ dev_dbg(dev, "TSU probed with %s calibration values",
+ rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0)) ? "hw" : "sw");
+
+ return 0;
+
+err:
+ rzg2l_thermal_reset_assert_pm_disable_put(pdev);
+ return ret;
+}
+
+static const struct of_device_id rzg2l_thermal_dt_ids[] = {
+ { .compatible = "renesas,rzg2l-tsu", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rzg2l_thermal_dt_ids);
+
+static struct platform_driver rzg2l_thermal_driver = {
+ .driver = {
+ .name = "rzg2l_thermal",
+ .of_match_table = rzg2l_thermal_dt_ids,
+ },
+ .probe = rzg2l_thermal_probe,
+ .remove = rzg2l_thermal_remove,
+};
+module_platform_driver(rzg2l_thermal_driver);
+
+MODULE_DESCRIPTION("Renesas RZ/G2L TSU Thermal Sensor Driver");
+MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c
new file mode 100644
index 000000000..c44f5b885
--- /dev/null
+++ b/drivers/thermal/renesas/rzg3e_thermal.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G3E TSU Temperature Sensor Unit
+ *
+ * Copyright (C) 2025 Renesas Electronics Corporation
+ */
+#include <linux/arm-smccc.h>
+#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_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 */
+
+#define RZ_SIP_SVC_GET_SYSTSU 0x82000022
+#define OTP_TSU_REG_ADR_TEMPHI 0x01DC
+#define OTP_TSU_REG_ADR_TEMPLO 0x01DD
+
+struct rzg3e_thermal_priv;
+
+struct rzg3e_thermal_info {
+ int (*get_trim)(struct rzg3e_thermal_priv *priv);
+ int temp_d_mc;
+ int temp_e_mc;
+};
+
+/**
+ * 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
+ * @info: chip type specific information
+ * @trmval0: calibration value 0 (b)
+ * @trmval1: calibration value 1 (c)
+ * @lock: protects hardware access during conversions
+ */
+struct rzg3e_thermal_priv {
+ void __iomem *base;
+ struct device *dev;
+ struct thermal_zone_device *zone;
+ struct reset_control *rstc;
+ const struct rzg3e_thermal_info *info;
+ u16 trmval0;
+ u16 trmval1;
+ 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)
+{
+ const struct rzg3e_thermal_info *info = priv->info;
+ s64 numerator, denominator;
+ int temp_mc;
+
+ numerator = (info->temp_e_mc - info->temp_d_mc) *
+ (s64)(code - priv->trmval0);
+ denominator = priv->trmval1 - priv->trmval0;
+
+ temp_mc = div64_s64(numerator, denominator) + info->temp_d_mc;
+
+ return clamp(temp_mc, info->temp_d_mc, info->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)
+{
+ const struct rzg3e_thermal_info *info = priv->info;
+ s64 numerator, denominator;
+ s64 code;
+
+ numerator = (temp_mc - info->temp_d_mc) * (priv->trmval1 - priv->trmval0);
+ denominator = info->temp_e_mc - info->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_syscon_trim(struct rzg3e_thermal_priv *priv)
+{
+ struct device_node *np = priv->dev->of_node;
+ struct regmap *syscon;
+ u32 offset;
+ int ret;
+ u32 val;
+
+ syscon = syscon_regmap_lookup_by_phandle_args(np, "renesas,tsu-trim", 1, &offset);
+ if (IS_ERR(syscon))
+ return dev_err_probe(priv->dev, PTR_ERR(syscon),
+ "Failed to parse renesas,tsu-trim\n");
+
+ /* Read calibration values from syscon */
+ ret = regmap_read(syscon, offset, &val);
+ if (ret)
+ return ret;
+ priv->trmval0 = val & TSU_CODE_MAX;
+
+ ret = regmap_read(syscon, offset + 4, &val);
+ if (ret)
+ return ret;
+ priv->trmval1 = val & TSU_CODE_MAX;
+
+ return 0;
+}
+
+static int rzg3e_thermal_get_smc_trim(struct rzg3e_thermal_priv *priv)
+{
+ struct arm_smccc_res local_res;
+
+ arm_smccc_smc(RZ_SIP_SVC_GET_SYSTSU, OTP_TSU_REG_ADR_TEMPLO,
+ 0, 0, 0, 0, 0, 0, &local_res);
+ priv->trmval0 = local_res.a0 & TSU_CODE_MAX;
+
+ arm_smccc_smc(RZ_SIP_SVC_GET_SYSTSU, OTP_TSU_REG_ADR_TEMPHI,
+ 0, 0, 0, 0, 0, 0, &local_res);
+ priv->trmval1 = local_res.a0 & TSU_CODE_MAX;
+
+ 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->info = device_get_match_data(dev);
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ ret = priv->info->get_trim(priv);
+ if (ret)
+ return ret;
+
+ if (!priv->trmval0 || !priv->trmval1 ||
+ priv->trmval0 == priv->trmval1 ||
+ priv->trmval0 == TSU_CODE_MAX || priv->trmval1 == TSU_CODE_MAX)
+ return dev_err_probe(priv->dev, -EINVAL,
+ "Invalid calibration: b=0x%03x, c=0x%03x\n",
+ priv->trmval0, priv->trmval1);
+
+ dev_dbg(priv->dev, "Calibration: b=0x%03x (%u), c=0x%03x (%u)\n",
+ priv->trmval0, priv->trmval0, priv->trmval1, priv->trmval1);
+
+ /* 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_optional_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 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)
+ 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 rzg3e_thermal_info rzg3e_thermal_info = {
+ .get_trim = rzg3e_thermal_get_syscon_trim,
+ .temp_d_mc = -41000,
+ .temp_e_mc = 126000,
+};
+
+static const struct rzg3e_thermal_info rzt2h_thermal_info = {
+ .get_trim = rzg3e_thermal_get_smc_trim,
+ .temp_d_mc = -40000,
+ .temp_e_mc = 125000,
+};
+
+static const struct of_device_id rzg3e_thermal_dt_ids[] = {
+ { .compatible = "renesas,r9a09g047-tsu", .data = &rzg3e_thermal_info },
+ { .compatible = "renesas,r9a09g077-tsu", .data = &rzt2h_thermal_info },
+ { /* 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 000000000..7ced8f76a
--- /dev/null
+++ b/drivers/thermal/renesas/rzg3s_thermal.c
@@ -0,0 +1,273 @@
+// 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");
+MODULE_IMPORT_NS("IIO_CONSUMER");
diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c
new file mode 100644
index 000000000..08891608b
--- /dev/null
+++ b/drivers/thermal/rockchip_thermal.c
@@ -0,0 +1,1895 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014-2016, Fuzhou Rockchip Electronics Co., Ltd
+ * Caesar Wang <wxt@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#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>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/thermal.h>
+#include <linux/mfd/syscon.h>
+#include <linux/pinctrl/consumer.h>
+
+/*
+ * If the temperature over a period of time High,
+ * the resulting TSHUT gave CRU module,let it reset the entire chip,
+ * or via GPIO give PMIC.
+ */
+enum tshut_mode {
+ TSHUT_MODE_CRU = 0,
+ TSHUT_MODE_GPIO,
+};
+
+/*
+ * The system Temperature Sensors tshut(tshut) polarity
+ * the bit 8 is tshut polarity.
+ * 0: low active, 1: high active
+ */
+enum tshut_polarity {
+ TSHUT_LOW_ACTIVE = 0,
+ TSHUT_HIGH_ACTIVE,
+};
+
+/*
+ * The conversion table has the adc value and temperature.
+ * ADC_DECREMENT: the adc value is of diminishing.(e.g. rk3288_code_table)
+ * ADC_INCREMENT: the adc value is incremental.(e.g. rk3368_code_table)
+ */
+enum adc_sort_mode {
+ ADC_DECREMENT = 0,
+ ADC_INCREMENT,
+};
+
+#include "thermal_hwmon.h"
+
+/**
+ * struct chip_tsadc_table - hold information about chip-specific differences
+ * @id: conversion table
+ * @length: size of conversion table
+ * @data_mask: mask to apply on data inputs
+ * @mode: sort mode of this adc variant (incrementing or decrementing)
+ */
+struct chip_tsadc_table {
+ const struct tsadc_table *id;
+ unsigned int length;
+ u32 data_mask;
+ enum adc_sort_mode mode;
+};
+
+/**
+ * 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
+ * @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 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 {
+ /* The sensor id of chip correspond to the ADC channel */
+ 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);
+ void (*irq_ack)(void __iomem *reg);
+ void (*control)(void __iomem *reg, bool on);
+
+ /* Per-sensor methods */
+ int (*get_temp)(const struct chip_tsadc_table *table,
+ int chn, void __iomem *reg, int *temp);
+ int (*set_alarm_temp)(const struct chip_tsadc_table *table,
+ int chn, void __iomem *reg, int temp);
+ 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;
+};
+
+/**
+ * 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;
+};
+
+/**
+ * struct rockchip_thermal_data - hold the private data of thermal driver
+ * @chip: pointer to the platform/configuration data
+ * @pdev: platform device of thermal
+ * @reset: the reset controller of tsadc
+ * @sensors: array of thermal sensors
+ * @clk: the controller clock is divided by the exteral 24MHz
+ * @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
+ * @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)
+ */
+struct rockchip_thermal_data {
+ const struct rockchip_tsadc_chip *chip;
+ struct platform_device *pdev;
+ struct reset_control *reset;
+
+ struct rockchip_thermal_sensor *sensors;
+
+ struct clk *clk;
+ struct clk *pclk;
+
+ 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;
+};
+
+/*
+ * TSADC Sensor Register description:
+ *
+ * TSADCV2_* are used for RK3288 SoCs, the other chips can reuse it.
+ * TSADCV3_* are used for newer SoCs than RK3288. (e.g: RK3228, RK3399)
+ *
+ */
+#define TSADCV2_USER_CON 0x00
+#define TSADCV2_AUTO_CON 0x04
+#define TSADCV2_INT_EN 0x08
+#define TSADCV2_INT_PD 0x0c
+#define TSADCV3_AUTO_SRC_CON 0x0c
+#define TSADCV3_HT_INT_EN 0x14
+#define TSADCV3_HSHUT_GPIO_INT_EN 0x18
+#define TSADCV3_HSHUT_CRU_INT_EN 0x1c
+#define TSADCV3_INT_PD 0x24
+#define TSADCV3_HSHUT_PD 0x28
+#define TSADCV2_DATA(chn) (0x20 + (chn) * 0x04)
+#define TSADCV2_COMP_INT(chn) (0x30 + (chn) * 0x04)
+#define TSADCV2_COMP_SHUT(chn) (0x40 + (chn) * 0x04)
+#define TSADCV3_DATA(chn) (0x2c + (chn) * 0x04)
+#define TSADCV3_COMP_INT(chn) (0x6c + (chn) * 0x04)
+#define TSADCV3_COMP_SHUT(chn) (0x10c + (chn) * 0x04)
+#define TSADCV2_HIGHT_INT_DEBOUNCE 0x60
+#define TSADCV2_HIGHT_TSHUT_DEBOUNCE 0x64
+#define TSADCV3_HIGHT_INT_DEBOUNCE 0x14c
+#define TSADCV3_HIGHT_TSHUT_DEBOUNCE 0x150
+#define TSADCV2_AUTO_PERIOD 0x68
+#define TSADCV2_AUTO_PERIOD_HT 0x6c
+#define TSADCV3_AUTO_PERIOD 0x154
+#define TSADCV3_AUTO_PERIOD_HT 0x158
+
+#define TSADCV2_AUTO_EN BIT(0)
+#define TSADCV2_AUTO_EN_MASK BIT(16)
+#define TSADCV2_AUTO_SRC_EN(chn) BIT(4 + (chn))
+#define TSADCV3_AUTO_SRC_EN(chn) BIT(chn)
+#define TSADCV3_AUTO_SRC_EN_MASK(chn) BIT(16 + chn)
+#define TSADCV2_AUTO_TSHUT_POLARITY_HIGH BIT(8)
+#define TSADCV2_AUTO_TSHUT_POLARITY_MASK BIT(24)
+
+#define TSADCV3_AUTO_Q_SEL_EN BIT(1)
+
+#define TSADCV2_INT_SRC_EN(chn) BIT(chn)
+#define TSADCV2_INT_SRC_EN_MASK(chn) BIT(16 + (chn))
+#define TSADCV2_SHUT_2GPIO_SRC_EN(chn) BIT(4 + (chn))
+#define TSADCV2_SHUT_2CRU_SRC_EN(chn) BIT(8 + (chn))
+
+#define TSADCV2_INT_PD_CLEAR_MASK ~BIT(8)
+#define TSADCV3_INT_PD_CLEAR_MASK ~BIT(16)
+#define TSADCV4_INT_PD_CLEAR_MASK 0xffffffff
+
+#define TSADCV2_DATA_MASK 0xfff
+#define TSADCV3_DATA_MASK 0x3ff
+#define TSADCV4_DATA_MASK 0x1ff
+
+#define TSADCV2_HIGHT_INT_DEBOUNCE_COUNT 4
+#define TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT 4
+#define TSADCV2_AUTO_PERIOD_TIME 250 /* 250ms */
+#define TSADCV2_AUTO_PERIOD_HT_TIME 50 /* 50ms */
+#define TSADCV3_AUTO_PERIOD_TIME 1875 /* 2.5ms */
+#define TSADCV3_AUTO_PERIOD_HT_TIME 1875 /* 2.5ms */
+
+#define TSADCV5_AUTO_PERIOD_TIME 1622 /* 2.5ms */
+#define TSADCV5_AUTO_PERIOD_HT_TIME 1622 /* 2.5ms */
+#define TSADCV6_AUTO_PERIOD_TIME 5000 /* 2.5ms */
+#define TSADCV6_AUTO_PERIOD_HT_TIME 5000 /* 2.5ms */
+
+#define TSADCV2_USER_INTER_PD_SOC 0x340 /* 13 clocks */
+#define TSADCV5_USER_INTER_PD_SOC 0xfc0 /* 97us, at least 90us */
+
+#define GRF_SARADC_TESTBIT 0x0e644
+#define GRF_TSADC_TESTBIT_L 0x0e648
+#define GRF_TSADC_TESTBIT_H 0x0e64c
+
+#define PX30_GRF_SOC_CON2 0x0408
+
+#define RK3568_GRF_TSADC_CON 0x0600
+#define RK3568_GRF_TSADC_ANA_REG0 (0x10001 << 0)
+#define RK3568_GRF_TSADC_ANA_REG1 (0x10001 << 1)
+#define RK3568_GRF_TSADC_ANA_REG2 (0x10001 << 2)
+#define RK3568_GRF_TSADC_TSEN (0x10001 << 8)
+
+#define RK3588_GRF0_TSADC_CON 0x0100
+
+#define RK3588_GRF0_TSADC_TRM (0xff0077 << 0)
+#define RK3588_GRF0_TSADC_SHUT_2CRU (0x30003 << 10)
+#define RK3588_GRF0_TSADC_SHUT_2GPIO (0x70007 << 12)
+
+#define GRF_SARADC_TESTBIT_ON (0x10001 << 2)
+#define GRF_TSADC_TESTBIT_H_ON (0x10001 << 2)
+#define GRF_TSADC_VCM_EN_L (0x10001 << 7)
+#define GRF_TSADC_VCM_EN_H (0x10001 << 7)
+
+#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
+ * @temp: the temperature
+ * Note:
+ * code to temperature mapping of the temperature sensor is a piece wise linear
+ * curve.Any temperature, code faling between to 2 give temperatures can be
+ * linearly interpolated.
+ * Code to Temperature mapping should be updated based on manufacturer results.
+ */
+struct tsadc_table {
+ u32 code;
+ int temp;
+};
+
+static const struct tsadc_table rv1108_table[] = {
+ {0, -40000},
+ {374, -40000},
+ {382, -35000},
+ {389, -30000},
+ {397, -25000},
+ {405, -20000},
+ {413, -15000},
+ {421, -10000},
+ {429, -5000},
+ {436, 0},
+ {444, 5000},
+ {452, 10000},
+ {460, 15000},
+ {468, 20000},
+ {476, 25000},
+ {483, 30000},
+ {491, 35000},
+ {499, 40000},
+ {507, 45000},
+ {515, 50000},
+ {523, 55000},
+ {531, 60000},
+ {539, 65000},
+ {547, 70000},
+ {555, 75000},
+ {562, 80000},
+ {570, 85000},
+ {578, 90000},
+ {586, 95000},
+ {594, 100000},
+ {602, 105000},
+ {610, 110000},
+ {618, 115000},
+ {626, 120000},
+ {634, 125000},
+ {TSADCV2_DATA_MASK, 125000},
+};
+
+static const struct tsadc_table rk3228_code_table[] = {
+ {0, -40000},
+ {588, -40000},
+ {593, -35000},
+ {598, -30000},
+ {603, -25000},
+ {608, -20000},
+ {613, -15000},
+ {618, -10000},
+ {623, -5000},
+ {629, 0},
+ {634, 5000},
+ {639, 10000},
+ {644, 15000},
+ {649, 20000},
+ {654, 25000},
+ {660, 30000},
+ {665, 35000},
+ {670, 40000},
+ {675, 45000},
+ {681, 50000},
+ {686, 55000},
+ {691, 60000},
+ {696, 65000},
+ {702, 70000},
+ {707, 75000},
+ {712, 80000},
+ {717, 85000},
+ {723, 90000},
+ {728, 95000},
+ {733, 100000},
+ {738, 105000},
+ {744, 110000},
+ {749, 115000},
+ {754, 120000},
+ {760, 125000},
+ {TSADCV2_DATA_MASK, 125000},
+};
+
+static const struct tsadc_table rk3288_code_table[] = {
+ {TSADCV2_DATA_MASK, -40000},
+ {3800, -40000},
+ {3792, -35000},
+ {3783, -30000},
+ {3774, -25000},
+ {3765, -20000},
+ {3756, -15000},
+ {3747, -10000},
+ {3737, -5000},
+ {3728, 0},
+ {3718, 5000},
+ {3708, 10000},
+ {3698, 15000},
+ {3688, 20000},
+ {3678, 25000},
+ {3667, 30000},
+ {3656, 35000},
+ {3645, 40000},
+ {3634, 45000},
+ {3623, 50000},
+ {3611, 55000},
+ {3600, 60000},
+ {3588, 65000},
+ {3575, 70000},
+ {3563, 75000},
+ {3550, 80000},
+ {3537, 85000},
+ {3524, 90000},
+ {3510, 95000},
+ {3496, 100000},
+ {3482, 105000},
+ {3467, 110000},
+ {3452, 115000},
+ {3437, 120000},
+ {3421, 125000},
+ {0, 125000},
+};
+
+static const struct tsadc_table rk3328_code_table[] = {
+ {0, -40000},
+ {296, -40000},
+ {304, -35000},
+ {313, -30000},
+ {322, -25000},
+ {331, -20000},
+ {340, -15000},
+ {349, -10000},
+ {359, -5000},
+ {368, 0},
+ {378, 5000},
+ {388, 10000},
+ {398, 15000},
+ {408, 20000},
+ {418, 25000},
+ {429, 30000},
+ {440, 35000},
+ {451, 40000},
+ {462, 45000},
+ {473, 50000},
+ {485, 55000},
+ {496, 60000},
+ {508, 65000},
+ {521, 70000},
+ {533, 75000},
+ {546, 80000},
+ {559, 85000},
+ {572, 90000},
+ {586, 95000},
+ {600, 100000},
+ {614, 105000},
+ {629, 110000},
+ {644, 115000},
+ {659, 120000},
+ {675, 125000},
+ {TSADCV2_DATA_MASK, 125000},
+};
+
+static const struct tsadc_table rk3368_code_table[] = {
+ {0, -40000},
+ {106, -40000},
+ {108, -35000},
+ {110, -30000},
+ {112, -25000},
+ {114, -20000},
+ {116, -15000},
+ {118, -10000},
+ {120, -5000},
+ {122, 0},
+ {124, 5000},
+ {126, 10000},
+ {128, 15000},
+ {130, 20000},
+ {132, 25000},
+ {134, 30000},
+ {136, 35000},
+ {138, 40000},
+ {140, 45000},
+ {142, 50000},
+ {144, 55000},
+ {146, 60000},
+ {148, 65000},
+ {150, 70000},
+ {152, 75000},
+ {154, 80000},
+ {156, 85000},
+ {158, 90000},
+ {160, 95000},
+ {162, 100000},
+ {163, 105000},
+ {165, 110000},
+ {167, 115000},
+ {169, 120000},
+ {171, 125000},
+ {TSADCV3_DATA_MASK, 125000},
+};
+
+static const struct tsadc_table rk3399_code_table[] = {
+ {0, -40000},
+ {402, -40000},
+ {410, -35000},
+ {419, -30000},
+ {427, -25000},
+ {436, -20000},
+ {444, -15000},
+ {453, -10000},
+ {461, -5000},
+ {470, 0},
+ {478, 5000},
+ {487, 10000},
+ {496, 15000},
+ {504, 20000},
+ {513, 25000},
+ {521, 30000},
+ {530, 35000},
+ {538, 40000},
+ {547, 45000},
+ {555, 50000},
+ {564, 55000},
+ {573, 60000},
+ {581, 65000},
+ {590, 70000},
+ {599, 75000},
+ {607, 80000},
+ {616, 85000},
+ {624, 90000},
+ {633, 95000},
+ {642, 100000},
+ {650, 105000},
+ {659, 110000},
+ {668, 115000},
+ {677, 120000},
+ {685, 125000},
+ {TSADCV3_DATA_MASK, 125000},
+};
+
+static const struct tsadc_table rk3568_code_table[] = {
+ {0, -40000},
+ {1584, -40000},
+ {1620, -35000},
+ {1652, -30000},
+ {1688, -25000},
+ {1720, -20000},
+ {1756, -15000},
+ {1788, -10000},
+ {1824, -5000},
+ {1856, 0},
+ {1892, 5000},
+ {1924, 10000},
+ {1956, 15000},
+ {1992, 20000},
+ {2024, 25000},
+ {2060, 30000},
+ {2092, 35000},
+ {2128, 40000},
+ {2160, 45000},
+ {2196, 50000},
+ {2228, 55000},
+ {2264, 60000},
+ {2300, 65000},
+ {2332, 70000},
+ {2368, 75000},
+ {2400, 80000},
+ {2436, 85000},
+ {2468, 90000},
+ {2500, 95000},
+ {2536, 100000},
+ {2572, 105000},
+ {2604, 110000},
+ {2636, 115000},
+ {2672, 120000},
+ {2704, 125000},
+ {TSADCV2_DATA_MASK, 125000},
+};
+
+static const struct tsadc_table rk3588_code_table[] = {
+ {0, -40000},
+ {215, -40000},
+ {285, 25000},
+ {350, 85000},
+ {395, 125000},
+ {TSADCV4_DATA_MASK, 125000},
+};
+
+static u32 rk_tsadcv2_temp_to_code(const struct chip_tsadc_table *table,
+ int temp)
+{
+ int high, low, mid;
+ unsigned long num;
+ unsigned int denom;
+ u32 error = table->data_mask;
+
+ low = 0;
+ high = (table->length - 1) - 1; /* ignore the last check for table */
+ mid = (high + low) / 2;
+
+ /* Return mask code data when the temp is over table range */
+ if (temp < table->id[low].temp || temp > table->id[high].temp)
+ goto exit;
+
+ while (low <= high) {
+ if (temp == table->id[mid].temp)
+ return table->id[mid].code;
+ else if (temp < table->id[mid].temp)
+ high = mid - 1;
+ else
+ low = mid + 1;
+ mid = (low + high) / 2;
+ }
+
+ /*
+ * The conversion code granularity provided by the table. Let's
+ * assume that the relationship between temperature and
+ * analog value between 2 table entries is linear and interpolate
+ * to produce less granular result.
+ */
+ num = abs(table->id[mid + 1].code - table->id[mid].code);
+ num *= temp - table->id[mid].temp;
+ denom = table->id[mid + 1].temp - table->id[mid].temp;
+
+ switch (table->mode) {
+ case ADC_DECREMENT:
+ return table->id[mid].code - (num / denom);
+ case ADC_INCREMENT:
+ return table->id[mid].code + (num / denom);
+ default:
+ pr_err("%s: unknown table mode: %d\n", __func__, table->mode);
+ return error;
+ }
+
+exit:
+ pr_err("%s: invalid temperature, temp=%d error=%d\n",
+ __func__, temp, error);
+ return error;
+}
+
+static int rk_tsadcv2_code_to_temp(const struct chip_tsadc_table *table,
+ u32 code, int *temp)
+{
+ unsigned int low = 1;
+ unsigned int high = table->length - 1;
+ unsigned int mid = (low + high) / 2;
+ unsigned int num;
+ unsigned long denom;
+
+ WARN_ON(table->length < 2);
+
+ switch (table->mode) {
+ case ADC_DECREMENT:
+ code &= table->data_mask;
+ if (code <= table->id[high].code)
+ return -EAGAIN; /* Incorrect reading */
+
+ while (low <= high) {
+ if (code >= table->id[mid].code &&
+ code < table->id[mid - 1].code)
+ break;
+ else if (code < table->id[mid].code)
+ low = mid + 1;
+ else
+ high = mid - 1;
+
+ mid = (low + high) / 2;
+ }
+ break;
+ case ADC_INCREMENT:
+ code &= table->data_mask;
+ if (code < table->id[low].code)
+ return -EAGAIN; /* Incorrect reading */
+
+ while (low <= high) {
+ if (code <= table->id[mid].code &&
+ code > table->id[mid - 1].code)
+ break;
+ else if (code > table->id[mid].code)
+ low = mid + 1;
+ else
+ high = mid - 1;
+
+ mid = (low + high) / 2;
+ }
+ break;
+ default:
+ pr_err("%s: unknown table mode: %d\n", __func__, table->mode);
+ return -EINVAL;
+ }
+
+ /*
+ * The 5C granularity provided by the table is too much. Let's
+ * assume that the relationship between sensor readings and
+ * temperature between 2 table entries is linear and interpolate
+ * to produce less granular result.
+ */
+ num = table->id[mid].temp - table->id[mid - 1].temp;
+ num *= abs(table->id[mid - 1].code - code);
+ denom = abs(table->id[mid - 1].code - table->id[mid].code);
+ *temp = table->id[mid - 1].temp + (num / denom);
+
+ return 0;
+}
+
+/**
+ * rk_tsadcv2_initialize - initialize TASDC Controller.
+ * @grf: the general register file will be used to do static set by software
+ * @regs: the base address of tsadc controller
+ * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
+ *
+ * (1) Set TSADC_V2_AUTO_PERIOD:
+ * Configure the interleave between every two accessing of
+ * TSADC in normal operation.
+ *
+ * (2) Set TSADCV2_AUTO_PERIOD_HT:
+ * Configure the interleave between every two accessing of
+ * TSADC after the temperature is higher than COM_SHUT or COM_INT.
+ *
+ * (3) Set TSADCV2_HIGH_INT_DEBOUNCE and TSADC_HIGHT_TSHUT_DEBOUNCE:
+ * If the temperature is higher than COMP_INT or COMP_SHUT for
+ * "debounce" times, TSADC controller will generate interrupt or TSHUT.
+ */
+static void rk_tsadcv2_initialize(struct regmap *grf, void __iomem *regs,
+ enum tshut_polarity tshut_polarity)
+{
+ if (tshut_polarity == TSHUT_HIGH_ACTIVE)
+ writel_relaxed(0U | TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
+ regs + TSADCV2_AUTO_CON);
+ else
+ writel_relaxed(0U & ~TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
+ regs + TSADCV2_AUTO_CON);
+
+ writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, regs + TSADCV2_AUTO_PERIOD);
+ writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_INT_DEBOUNCE);
+ writel_relaxed(TSADCV2_AUTO_PERIOD_HT_TIME,
+ regs + TSADCV2_AUTO_PERIOD_HT);
+ writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE);
+}
+
+/**
+ * rk_tsadcv3_initialize - initialize TASDC Controller.
+ * @grf: the general register file will be used to do static set by software
+ * @regs: the base address of tsadc controller
+ * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
+ *
+ * (1) The tsadc control power sequence.
+ *
+ * (2) Set TSADC_V2_AUTO_PERIOD:
+ * Configure the interleave between every two accessing of
+ * TSADC in normal operation.
+ *
+ * (2) Set TSADCV2_AUTO_PERIOD_HT:
+ * Configure the interleave between every two accessing of
+ * TSADC after the temperature is higher than COM_SHUT or COM_INT.
+ *
+ * (3) Set TSADCV2_HIGH_INT_DEBOUNCE and TSADC_HIGHT_TSHUT_DEBOUNCE:
+ * If the temperature is higher than COMP_INT or COMP_SHUT for
+ * "debounce" times, TSADC controller will generate interrupt or TSHUT.
+ */
+static void rk_tsadcv3_initialize(struct regmap *grf, void __iomem *regs,
+ enum tshut_polarity tshut_polarity)
+{
+ /* The tsadc control power sequence */
+ if (IS_ERR(grf)) {
+ /* Set interleave value to workround ic time sync issue */
+ writel_relaxed(TSADCV2_USER_INTER_PD_SOC, regs +
+ TSADCV2_USER_CON);
+
+ writel_relaxed(TSADCV2_AUTO_PERIOD_TIME,
+ regs + TSADCV2_AUTO_PERIOD);
+ writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_INT_DEBOUNCE);
+ writel_relaxed(TSADCV2_AUTO_PERIOD_HT_TIME,
+ regs + TSADCV2_AUTO_PERIOD_HT);
+ writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE);
+
+ } else {
+ /* Enable the voltage common mode feature */
+ regmap_write(grf, GRF_TSADC_TESTBIT_L, GRF_TSADC_VCM_EN_L);
+ regmap_write(grf, GRF_TSADC_TESTBIT_H, GRF_TSADC_VCM_EN_H);
+
+ usleep_range(15, 100); /* The spec note says at least 15 us */
+ regmap_write(grf, GRF_SARADC_TESTBIT, GRF_SARADC_TESTBIT_ON);
+ regmap_write(grf, GRF_TSADC_TESTBIT_H, GRF_TSADC_TESTBIT_H_ON);
+ usleep_range(90, 200); /* The spec note says at least 90 us */
+
+ writel_relaxed(TSADCV3_AUTO_PERIOD_TIME,
+ regs + TSADCV2_AUTO_PERIOD);
+ writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_INT_DEBOUNCE);
+ writel_relaxed(TSADCV3_AUTO_PERIOD_HT_TIME,
+ regs + TSADCV2_AUTO_PERIOD_HT);
+ writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE);
+ }
+
+ if (tshut_polarity == TSHUT_HIGH_ACTIVE)
+ writel_relaxed(0U | TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
+ regs + TSADCV2_AUTO_CON);
+ else
+ writel_relaxed(0U & ~TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
+ regs + TSADCV2_AUTO_CON);
+}
+
+static void rk_tsadcv4_initialize(struct regmap *grf, void __iomem *regs,
+ enum tshut_polarity tshut_polarity)
+{
+ rk_tsadcv2_initialize(grf, regs, tshut_polarity);
+ regmap_write(grf, PX30_GRF_SOC_CON2, GRF_CON_TSADC_CH_INV);
+}
+
+static void rk_tsadcv7_initialize(struct regmap *grf, void __iomem *regs,
+ enum tshut_polarity tshut_polarity)
+{
+ writel_relaxed(TSADCV5_USER_INTER_PD_SOC, regs + TSADCV2_USER_CON);
+ writel_relaxed(TSADCV5_AUTO_PERIOD_TIME, regs + TSADCV2_AUTO_PERIOD);
+ writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_INT_DEBOUNCE);
+ writel_relaxed(TSADCV5_AUTO_PERIOD_HT_TIME,
+ regs + TSADCV2_AUTO_PERIOD_HT);
+ writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT,
+ regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE);
+
+ if (tshut_polarity == TSHUT_HIGH_ACTIVE)
+ writel_relaxed(0U | TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
+ regs + TSADCV2_AUTO_CON);
+ else
+ writel_relaxed(0U & ~TSADCV2_AUTO_TSHUT_POLARITY_HIGH,
+ regs + TSADCV2_AUTO_CON);
+
+ /*
+ * The general register file will is optional
+ * and might not be available.
+ */
+ if (!IS_ERR(grf)) {
+ regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_TSEN);
+ /*
+ * RK3568 TRM, section 18.5. requires a delay no less
+ * than 10us between the rising edge of tsadc_tsen_en
+ * and the rising edge of tsadc_ana_reg_0/1/2.
+ */
+ udelay(15);
+ regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_ANA_REG0);
+ regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_ANA_REG1);
+ regmap_write(grf, RK3568_GRF_TSADC_CON, RK3568_GRF_TSADC_ANA_REG2);
+
+ /*
+ * RK3568 TRM, section 18.5. requires a delay no less
+ * than 90us after the rising edge of tsadc_ana_reg_0/1/2.
+ */
+ usleep_range(100, 200);
+ }
+}
+
+static void rk_tsadcv8_initialize(struct regmap *grf, void __iomem *regs,
+ enum tshut_polarity tshut_polarity)
+{
+ writel_relaxed(TSADCV6_AUTO_PERIOD_TIME, regs + TSADCV3_AUTO_PERIOD);
+ writel_relaxed(TSADCV6_AUTO_PERIOD_HT_TIME,
+ regs + TSADCV3_AUTO_PERIOD_HT);
+ writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT,
+ regs + TSADCV3_HIGHT_INT_DEBOUNCE);
+ writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT,
+ regs + TSADCV3_HIGHT_TSHUT_DEBOUNCE);
+ if (tshut_polarity == TSHUT_HIGH_ACTIVE)
+ writel_relaxed(TSADCV2_AUTO_TSHUT_POLARITY_HIGH |
+ TSADCV2_AUTO_TSHUT_POLARITY_MASK,
+ regs + TSADCV2_AUTO_CON);
+ else
+ writel_relaxed(TSADCV2_AUTO_TSHUT_POLARITY_MASK,
+ regs + TSADCV2_AUTO_CON);
+}
+
+static void rk_tsadcv2_irq_ack(void __iomem *regs)
+{
+ u32 val;
+
+ val = readl_relaxed(regs + TSADCV2_INT_PD);
+ writel_relaxed(val & TSADCV2_INT_PD_CLEAR_MASK, regs + TSADCV2_INT_PD);
+}
+
+static void rk_tsadcv3_irq_ack(void __iomem *regs)
+{
+ u32 val;
+
+ val = readl_relaxed(regs + TSADCV2_INT_PD);
+ writel_relaxed(val & TSADCV3_INT_PD_CLEAR_MASK, regs + TSADCV2_INT_PD);
+}
+
+static void rk_tsadcv4_irq_ack(void __iomem *regs)
+{
+ u32 val;
+
+ val = readl_relaxed(regs + TSADCV3_INT_PD);
+ writel_relaxed(val & TSADCV4_INT_PD_CLEAR_MASK, regs + TSADCV3_INT_PD);
+ val = readl_relaxed(regs + TSADCV3_HSHUT_PD);
+ writel_relaxed(val & TSADCV3_INT_PD_CLEAR_MASK,
+ regs + TSADCV3_HSHUT_PD);
+}
+
+static void rk_tsadcv2_control(void __iomem *regs, bool enable)
+{
+ u32 val;
+
+ val = readl_relaxed(regs + TSADCV2_AUTO_CON);
+ if (enable)
+ val |= TSADCV2_AUTO_EN;
+ else
+ val &= ~TSADCV2_AUTO_EN;
+
+ writel_relaxed(val, regs + TSADCV2_AUTO_CON);
+}
+
+/**
+ * rk_tsadcv3_control - the tsadc controller is enabled or disabled.
+ * @regs: the base address of tsadc controller
+ * @enable: boolean flag to enable the controller
+ *
+ * NOTE: TSADC controller works at auto mode, and some SoCs need set the
+ * tsadc_q_sel bit on TSADCV2_AUTO_CON[1]. The (1024 - tsadc_q) as output
+ * adc value if setting this bit to enable.
+ */
+static void rk_tsadcv3_control(void __iomem *regs, bool enable)
+{
+ u32 val;
+
+ val = readl_relaxed(regs + TSADCV2_AUTO_CON);
+ if (enable)
+ val |= TSADCV2_AUTO_EN | TSADCV3_AUTO_Q_SEL_EN;
+ else
+ val &= ~TSADCV2_AUTO_EN;
+
+ writel_relaxed(val, regs + TSADCV2_AUTO_CON);
+}
+
+static void rk_tsadcv4_control(void __iomem *regs, bool enable)
+{
+ u32 val;
+
+ if (enable)
+ val = TSADCV2_AUTO_EN | TSADCV2_AUTO_EN_MASK;
+ else
+ val = TSADCV2_AUTO_EN_MASK;
+
+ writel_relaxed(val, regs + TSADCV2_AUTO_CON);
+}
+
+static int rk_tsadcv2_get_temp(const struct chip_tsadc_table *table,
+ int chn, void __iomem *regs, int *temp)
+{
+ u32 val;
+
+ val = readl_relaxed(regs + TSADCV2_DATA(chn));
+
+ return rk_tsadcv2_code_to_temp(table, val, temp);
+}
+
+static int rk_tsadcv4_get_temp(const struct chip_tsadc_table *table,
+ int chn, void __iomem *regs, int *temp)
+{
+ u32 val;
+
+ val = readl_relaxed(regs + TSADCV3_DATA(chn));
+
+ return rk_tsadcv2_code_to_temp(table, val, temp);
+}
+
+static int rk_tsadcv2_alarm_temp(const struct chip_tsadc_table *table,
+ int chn, void __iomem *regs, int temp)
+{
+ u32 alarm_value;
+ u32 int_en, int_clr;
+
+ /*
+ * In some cases, some sensors didn't need the trip points, the
+ * set_trips will pass {-INT_MAX, INT_MAX} to trigger tsadc alarm
+ * in the end, ignore this case and disable the high temperature
+ * interrupt.
+ */
+ if (temp == INT_MAX) {
+ int_clr = readl_relaxed(regs + TSADCV2_INT_EN);
+ int_clr &= ~TSADCV2_INT_SRC_EN(chn);
+ writel_relaxed(int_clr, regs + TSADCV2_INT_EN);
+ return 0;
+ }
+
+ /* Make sure the value is valid */
+ alarm_value = rk_tsadcv2_temp_to_code(table, temp);
+ if (alarm_value == table->data_mask)
+ return -ERANGE;
+
+ writel_relaxed(alarm_value & table->data_mask,
+ regs + TSADCV2_COMP_INT(chn));
+
+ int_en = readl_relaxed(regs + TSADCV2_INT_EN);
+ int_en |= TSADCV2_INT_SRC_EN(chn);
+ writel_relaxed(int_en, regs + TSADCV2_INT_EN);
+
+ return 0;
+}
+
+static int rk_tsadcv3_alarm_temp(const struct chip_tsadc_table *table,
+ int chn, void __iomem *regs, int temp)
+{
+ u32 alarm_value;
+
+ /*
+ * In some cases, some sensors didn't need the trip points, the
+ * set_trips will pass {-INT_MAX, INT_MAX} to trigger tsadc alarm
+ * in the end, ignore this case and disable the high temperature
+ * interrupt.
+ */
+ if (temp == INT_MAX) {
+ writel_relaxed(TSADCV2_INT_SRC_EN_MASK(chn),
+ regs + TSADCV3_HT_INT_EN);
+ return 0;
+ }
+ /* Make sure the value is valid */
+ alarm_value = rk_tsadcv2_temp_to_code(table, temp);
+ if (alarm_value == table->data_mask)
+ return -ERANGE;
+ writel_relaxed(alarm_value & table->data_mask,
+ regs + TSADCV3_COMP_INT(chn));
+ writel_relaxed(TSADCV2_INT_SRC_EN(chn) | TSADCV2_INT_SRC_EN_MASK(chn),
+ regs + TSADCV3_HT_INT_EN);
+ return 0;
+}
+
+static int rk_tsadcv2_tshut_temp(const struct chip_tsadc_table *table,
+ int chn, void __iomem *regs, int temp)
+{
+ u32 tshut_value, val;
+
+ /* Make sure the value is valid */
+ tshut_value = rk_tsadcv2_temp_to_code(table, temp);
+ if (tshut_value == table->data_mask)
+ return -ERANGE;
+
+ writel_relaxed(tshut_value, regs + TSADCV2_COMP_SHUT(chn));
+
+ /* TSHUT will be valid */
+ val = readl_relaxed(regs + TSADCV2_AUTO_CON);
+ writel_relaxed(val | TSADCV2_AUTO_SRC_EN(chn), regs + TSADCV2_AUTO_CON);
+
+ return 0;
+}
+
+static int rk_tsadcv3_tshut_temp(const struct chip_tsadc_table *table,
+ int chn, void __iomem *regs, int temp)
+{
+ u32 tshut_value;
+
+ /* Make sure the value is valid */
+ tshut_value = rk_tsadcv2_temp_to_code(table, temp);
+ if (tshut_value == table->data_mask)
+ return -ERANGE;
+
+ writel_relaxed(tshut_value, regs + TSADCV3_COMP_SHUT(chn));
+
+ /* TSHUT will be valid */
+ writel_relaxed(TSADCV3_AUTO_SRC_EN(chn) | TSADCV3_AUTO_SRC_EN_MASK(chn),
+ regs + TSADCV3_AUTO_SRC_CON);
+
+ return 0;
+}
+
+static void rk_tsadcv2_tshut_mode(int chn, void __iomem *regs,
+ enum tshut_mode mode)
+{
+ u32 val;
+
+ val = readl_relaxed(regs + TSADCV2_INT_EN);
+ if (mode == TSHUT_MODE_GPIO) {
+ val &= ~TSADCV2_SHUT_2CRU_SRC_EN(chn);
+ val |= TSADCV2_SHUT_2GPIO_SRC_EN(chn);
+ } else {
+ val &= ~TSADCV2_SHUT_2GPIO_SRC_EN(chn);
+ val |= TSADCV2_SHUT_2CRU_SRC_EN(chn);
+ }
+
+ writel_relaxed(val, regs + TSADCV2_INT_EN);
+}
+
+static void rk_tsadcv4_tshut_mode(int chn, void __iomem *regs,
+ enum tshut_mode mode)
+{
+ u32 val_gpio, val_cru;
+
+ if (mode == TSHUT_MODE_GPIO) {
+ val_gpio = TSADCV2_INT_SRC_EN(chn) | TSADCV2_INT_SRC_EN_MASK(chn);
+ val_cru = TSADCV2_INT_SRC_EN_MASK(chn);
+ } else {
+ val_cru = TSADCV2_INT_SRC_EN(chn) | TSADCV2_INT_SRC_EN_MASK(chn);
+ val_gpio = TSADCV2_INT_SRC_EN_MASK(chn);
+ }
+ writel_relaxed(val_gpio, regs + TSADCV3_HSHUT_GPIO_INT_EN);
+ 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,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+ .table = {
+ .id = rk3328_code_table,
+ .length = ARRAY_SIZE(rk3328_code_table),
+ .data_mask = TSADCV2_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
+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,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+ .table = {
+ .id = rv1108_table,
+ .length = ARRAY_SIZE(rv1108_table),
+ .data_mask = TSADCV2_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
+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,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+ .table = {
+ .id = rk3228_code_table,
+ .length = ARRAY_SIZE(rk3228_code_table),
+ .data_mask = TSADCV3_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
+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,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+ .table = {
+ .id = rk3288_code_table,
+ .length = ARRAY_SIZE(rk3288_code_table),
+ .data_mask = TSADCV2_DATA_MASK,
+ .mode = ADC_DECREMENT,
+ },
+};
+
+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,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+ .table = {
+ .id = rk3328_code_table,
+ .length = ARRAY_SIZE(rk3328_code_table),
+ .data_mask = TSADCV2_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
+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,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+ .table = {
+ .id = rk3228_code_table,
+ .length = ARRAY_SIZE(rk3228_code_table),
+ .data_mask = TSADCV3_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
+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,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+ .table = {
+ .id = rk3368_code_table,
+ .length = ARRAY_SIZE(rk3368_code_table),
+ .data_mask = TSADCV3_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
+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,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+ .table = {
+ .id = rk3399_code_table,
+ .length = ARRAY_SIZE(rk3399_code_table),
+ .data_mask = TSADCV3_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
+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,
+ .get_temp = rk_tsadcv2_get_temp,
+ .set_alarm_temp = rk_tsadcv2_alarm_temp,
+ .set_tshut_temp = rk_tsadcv2_tshut_temp,
+ .set_tshut_mode = rk_tsadcv2_tshut_mode,
+ .table = {
+ .id = rk3568_code_table,
+ .length = ARRAY_SIZE(rk3568_code_table),
+ .data_mask = TSADCV2_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
+static const struct 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,
+ .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,
+ .table = {
+ .id = rk3588_code_table,
+ .length = ARRAY_SIZE(rk3588_code_table),
+ .data_mask = TSADCV4_DATA_MASK,
+ .mode = ADC_INCREMENT,
+ },
+};
+
+static const struct of_device_id of_rockchip_thermal_match[] = {
+ { .compatible = "rockchip,px30-tsadc",
+ .data = (void *)&px30_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rv1108-tsadc",
+ .data = (void *)&rv1108_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rk3228-tsadc",
+ .data = (void *)&rk3228_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rk3288-tsadc",
+ .data = (void *)&rk3288_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rk3328-tsadc",
+ .data = (void *)&rk3328_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rk3366-tsadc",
+ .data = (void *)&rk3366_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rk3368-tsadc",
+ .data = (void *)&rk3368_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rk3399-tsadc",
+ .data = (void *)&rk3399_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rk3568-tsadc",
+ .data = (void *)&rk3568_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rk3576-tsadc",
+ .data = (void *)&rk3576_tsadc_data,
+ },
+ {
+ .compatible = "rockchip,rk3588-tsadc",
+ .data = (void *)&rk3588_tsadc_data,
+ },
+ { /* end */ },
+};
+MODULE_DEVICE_TABLE(of, of_rockchip_thermal_match);
+
+static void
+rockchip_thermal_toggle_sensor(struct rockchip_thermal_sensor *sensor, bool on)
+{
+ struct thermal_zone_device *tzd = sensor->tzd;
+
+ if (on)
+ thermal_zone_device_enable(tzd);
+ else
+ thermal_zone_device_disable(tzd);
+}
+
+static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev)
+{
+ struct rockchip_thermal_data *thermal = dev;
+ int i;
+
+ dev_dbg(&thermal->pdev->dev, "thermal alarm\n");
+
+ thermal->chip->irq_ack(thermal->regs);
+
+ for (i = 0; i < thermal->chip->chn_num; i++)
+ thermal_zone_device_update(thermal->sensors[i].tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+static int rockchip_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct rockchip_thermal_sensor *sensor = thermal_zone_device_priv(tz);
+ struct rockchip_thermal_data *thermal = sensor->thermal;
+ const struct rockchip_tsadc_chip *tsadc = thermal->chip;
+
+ dev_dbg(&thermal->pdev->dev, "%s: sensor %d: low: %d, high %d\n",
+ __func__, sensor->id, low, high);
+
+ return tsadc->set_alarm_temp(&tsadc->table,
+ sensor->id, thermal->regs, high + sensor->trim_temp);
+}
+
+static int rockchip_thermal_get_temp(struct thermal_zone_device *tz, int *out_temp)
+{
+ struct rockchip_thermal_sensor *sensor = thermal_zone_device_priv(tz);
+ struct rockchip_thermal_data *thermal = sensor->thermal;
+ const struct rockchip_tsadc_chip *tsadc = sensor->thermal->chip;
+ int retval;
+
+ retval = tsadc->get_temp(&tsadc->table,
+ sensor->id, thermal->regs, out_temp);
+ *out_temp -= sensor->trim_temp;
+
+ return retval;
+}
+
+static const struct thermal_zone_device_ops rockchip_of_thermal_ops = {
+ .get_temp = rockchip_thermal_get_temp,
+ .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)
+{
+ u32 shut_temp, tshut_mode, tshut_polarity;
+
+ if (of_property_read_u32(np, "rockchip,hw-tshut-temp", &shut_temp)) {
+ dev_warn(dev,
+ "Missing tshut temp property, using default %d\n",
+ thermal->chip->tshut_temp);
+ thermal->tshut_temp = thermal->chip->tshut_temp;
+ } else {
+ if (shut_temp > INT_MAX) {
+ dev_err(dev, "Invalid tshut temperature specified: %d\n",
+ shut_temp);
+ return -ERANGE;
+ }
+ thermal->tshut_temp = shut_temp;
+ }
+
+ if (of_property_read_u32(np, "rockchip,hw-tshut-mode", &tshut_mode)) {
+ dev_warn(dev,
+ "Missing tshut mode property, using default (%s)\n",
+ thermal->chip->tshut_mode == TSHUT_MODE_GPIO ?
+ "gpio" : "cru");
+ thermal->tshut_mode = thermal->chip->tshut_mode;
+ } else {
+ thermal->tshut_mode = tshut_mode;
+ }
+
+ if (thermal->tshut_mode > 1) {
+ dev_err(dev, "Invalid tshut mode specified: %d\n",
+ thermal->tshut_mode);
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32(np, "rockchip,hw-tshut-polarity",
+ &tshut_polarity)) {
+ dev_warn(dev,
+ "Missing tshut-polarity property, using default (%s)\n",
+ thermal->chip->tshut_polarity == TSHUT_LOW_ACTIVE ?
+ "low" : "high");
+ thermal->tshut_polarity = thermal->chip->tshut_polarity;
+ } else {
+ thermal->tshut_polarity = tshut_polarity;
+ }
+
+ if (thermal->tshut_polarity > 1) {
+ dev_err(dev, "Invalid tshut-polarity specified: %d\n",
+ thermal->tshut_polarity);
+ return -EINVAL;
+ }
+
+ thermal->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ 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;
+}
+
+static int
+rockchip_thermal_register_sensor(struct platform_device *pdev,
+ struct rockchip_thermal_data *thermal,
+ struct rockchip_thermal_sensor *sensor,
+ 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, tshut_temp);
+ if (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(dev, id, sensor,
+ &rockchip_of_thermal_ops);
+ if (IS_ERR(sensor->tzd)) {
+ error = PTR_ERR(sensor->tzd);
+ dev_err(dev, "failed to register sensor %d: %d\n",
+ id, error);
+ return error;
+ }
+
+ return 0;
+}
+
+/**
+ * rockchip_thermal_reset_controller - Reset TSADC Controller, reset all tsadc registers.
+ * @reset: the reset controller of tsadc
+ */
+static void rockchip_thermal_reset_controller(struct reset_control *reset)
+{
+ reset_control_assert(reset);
+ usleep_range(10, 20);
+ reset_control_deassert(reset);
+}
+
+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)
+ return -EINVAL;
+
+ thermal = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_thermal_data),
+ GFP_KERNEL);
+ if (!thermal)
+ return -ENOMEM;
+
+ thermal->pdev = pdev;
+
+ thermal->chip = device_get_match_data(&pdev->dev);
+ if (!thermal->chip)
+ return -EINVAL;
+
+ thermal->sensors = devm_kcalloc(&pdev->dev, thermal->chip->chn_num,
+ sizeof(*thermal->sensors), GFP_KERNEL);
+ if (!thermal->sensors)
+ return -ENOMEM;
+
+ thermal->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(thermal->regs))
+ return PTR_ERR(thermal->regs);
+
+ thermal->reset = devm_reset_control_array_get_exclusive(&pdev->dev);
+ if (IS_ERR(thermal->reset))
+ return dev_err_probe(&pdev->dev, PTR_ERR(thermal->reset),
+ "failed to get tsadc reset.\n");
+
+ thermal->clk = devm_clk_get_enabled(&pdev->dev, "tsadc");
+ if (IS_ERR(thermal->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(thermal->clk),
+ "failed to get tsadc clock.\n");
+
+ thermal->pclk = devm_clk_get_enabled(&pdev->dev, "apb_pclk");
+ if (IS_ERR(thermal->pclk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(thermal->pclk),
+ "failed to get apb_pclk clock.\n");
+
+ rockchip_thermal_reset_controller(thermal->reset);
+
+ error = rockchip_configure_from_dt(&pdev->dev, np, thermal);
+ if (error)
+ return dev_err_probe(&pdev->dev, error,
+ "failed to parse device tree data\n");
+
+ 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],
+ thermal->chip->chn_offset + i);
+ if (error)
+ return dev_err_probe(&pdev->dev, error,
+ "failed to register sensor[%d].\n", i);
+ }
+
+ error = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ &rockchip_thermal_alarm_irq_thread,
+ IRQF_ONESHOT,
+ "rockchip_thermal", thermal);
+ if (error)
+ return error;
+
+ thermal->chip->control(thermal->regs, true);
+
+ for (i = 0; i < thermal->chip->chn_num; i++) {
+ rockchip_thermal_toggle_sensor(&thermal->sensors[i], true);
+ error = thermal_add_hwmon_sysfs(thermal->sensors[i].tzd);
+ if (error)
+ dev_warn(&pdev->dev,
+ "failed to register sensor %d with hwmon: %d\n",
+ i, error);
+ }
+
+ platform_set_drvdata(pdev, thermal);
+
+ return 0;
+}
+
+static void rockchip_thermal_remove(struct platform_device *pdev)
+{
+ struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < thermal->chip->chn_num; i++) {
+ struct rockchip_thermal_sensor *sensor = &thermal->sensors[i];
+
+ thermal_remove_hwmon_sysfs(sensor->tzd);
+ rockchip_thermal_toggle_sensor(sensor, false);
+ }
+
+ thermal->chip->control(thermal->regs, false);
+}
+
+static int __maybe_unused rockchip_thermal_suspend(struct device *dev)
+{
+ struct rockchip_thermal_data *thermal = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < thermal->chip->chn_num; i++)
+ rockchip_thermal_toggle_sensor(&thermal->sensors[i], false);
+
+ thermal->chip->control(thermal->regs, false);
+
+ clk_disable(thermal->pclk);
+ clk_disable(thermal->clk);
+
+ pinctrl_pm_select_sleep_state(dev);
+
+ return 0;
+}
+
+static int __maybe_unused rockchip_thermal_resume(struct device *dev)
+{
+ struct rockchip_thermal_data *thermal = dev_get_drvdata(dev);
+ 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)
+ return error;
+
+ error = clk_enable(thermal->pclk);
+ if (error) {
+ clk_disable(thermal->clk);
+ return error;
+ }
+
+ rockchip_thermal_reset_controller(thermal->reset);
+
+ tsadc->initialize(thermal->grf, thermal->regs, thermal->tshut_polarity);
+
+ for (i = 0; i < thermal->chip->chn_num; i++) {
+ sensor = &thermal->sensors[i];
+
+ tshut_temp = min(thermal->tshut_temp + sensor->trim_temp,
+ RK_MAX_TEMP);
+
+ tsadc->set_tshut_mode(sensor->id, thermal->regs,
+ thermal->tshut_mode);
+
+ 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__, tshut_temp, error);
+ }
+
+ thermal->chip->control(thermal->regs, true);
+
+ for (i = 0; i < thermal->chip->chn_num; i++)
+ rockchip_thermal_toggle_sensor(&thermal->sensors[i], true);
+
+ pinctrl_pm_select_default_state(dev);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rockchip_thermal_pm_ops,
+ rockchip_thermal_suspend, rockchip_thermal_resume);
+
+static struct platform_driver rockchip_thermal_driver = {
+ .driver = {
+ .name = "rockchip-thermal",
+ .pm = &rockchip_thermal_pm_ops,
+ .of_match_table = of_rockchip_thermal_match,
+ },
+ .probe = rockchip_thermal_probe,
+ .remove = rockchip_thermal_remove,
+};
+
+module_platform_driver(rockchip_thermal_driver);
+
+MODULE_DESCRIPTION("ROCKCHIP THERMAL Driver");
+MODULE_AUTHOR("Rockchip, Inc.");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:rockchip-thermal");
diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig
new file mode 100644
index 000000000..e1e8035d2
--- /dev/null
+++ b/drivers/thermal/samsung/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config EXYNOS_THERMAL
+ tristate "Exynos thermal management unit driver"
+ depends on THERMAL_OF
+ depends on HAS_IOMEM
+ default ARCH_EXYNOS
+ help
+ If you say yes here you get support for the TMU (Thermal Management
+ Unit) driver for Samsung Exynos series of SoCs. This driver initialises
+ the TMU, reports temperature and handles cooling action if defined.
+ This driver uses the Exynos core thermal APIs and TMU configuration
+ data from the supported SoCs.
diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile
new file mode 100644
index 000000000..f13940715
--- /dev/null
+++ b/drivers/thermal/samsung/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Samsung thermal specific Makefile
+#
+obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
+exynos_thermal-y := exynos_tmu.o
diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c
new file mode 100644
index 000000000..56717bb50
--- /dev/null
+++ b/drivers/thermal/samsung/exynos_tmu.c
@@ -0,0 +1,1173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * exynos_tmu.c - Samsung Exynos TMU (Thermal Management Unit)
+ *
+ * Copyright (C) 2014 Samsung Electronics
+ * Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
+ * Lukasz Majewski <l.majewski@samsung.com>
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Donggeun Kim <dg77.kim@samsung.com>
+ * Amit Daniel Kachhap <amit.kachhap@linaro.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/thermal.h>
+
+#include <dt-bindings/thermal/thermal_exynos.h>
+
+/* Exynos generic registers */
+#define EXYNOS_TMU_REG_TRIMINFO 0x0
+#define EXYNOS_TMU_REG_CONTROL 0x20
+#define EXYNOS_TMU_REG_STATUS 0x28
+#define EXYNOS_TMU_REG_CURRENT_TEMP 0x40
+#define EXYNOS_TMU_REG_INTEN 0x70
+#define EXYNOS_TMU_REG_INTSTAT 0x74
+#define EXYNOS_TMU_REG_INTCLEAR 0x78
+
+#define EXYNOS_TMU_TEMP_MASK 0xff
+#define EXYNOS_TMU_REF_VOLTAGE_SHIFT 24
+#define EXYNOS_TMU_REF_VOLTAGE_MASK 0x1f
+#define EXYNOS_TMU_BUF_SLOPE_SEL_MASK 0xf
+#define EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT 8
+#define EXYNOS_TMU_CORE_EN_SHIFT 0
+
+/* Exynos3250 specific registers */
+#define EXYNOS_TMU_TRIMINFO_CON1 0x10
+
+/* Exynos4210 specific registers */
+#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP 0x44
+#define EXYNOS4210_TMU_REG_TRIG_LEVEL0 0x50
+
+/* Exynos5250, Exynos4412, Exynos3250 specific registers */
+#define EXYNOS_TMU_TRIMINFO_CON2 0x14
+#define EXYNOS_THD_TEMP_RISE 0x50
+#define EXYNOS_THD_TEMP_FALL 0x54
+#define EXYNOS_EMUL_CON 0x80
+
+#define EXYNOS_TRIMINFO_RELOAD_ENABLE 1
+#define EXYNOS_TRIMINFO_25_SHIFT 0
+#define EXYNOS_TRIMINFO_85_SHIFT 8
+#define EXYNOS_TMU_TRIP_MODE_SHIFT 13
+#define EXYNOS_TMU_TRIP_MODE_MASK 0x7
+#define EXYNOS_TMU_THERM_TRIP_EN_SHIFT 12
+
+#define EXYNOS_TMU_INTEN_RISE0_SHIFT 0
+#define EXYNOS_TMU_INTEN_FALL0_SHIFT 16
+
+#define EXYNOS_EMUL_TIME 0x57F0
+#define EXYNOS_EMUL_TIME_MASK 0xffff
+#define EXYNOS_EMUL_TIME_SHIFT 16
+#define EXYNOS_EMUL_DATA_SHIFT 8
+#define EXYNOS_EMUL_DATA_MASK 0xFF
+#define EXYNOS_EMUL_ENABLE 0x1
+
+/* Exynos5260 specific */
+#define EXYNOS5260_TMU_REG_INTEN 0xC0
+#define EXYNOS5260_TMU_REG_INTSTAT 0xC4
+#define EXYNOS5260_TMU_REG_INTCLEAR 0xC8
+#define EXYNOS5260_EMUL_CON 0x100
+
+/* Exynos4412 specific */
+#define EXYNOS4412_MUX_ADDR_VALUE 6
+#define EXYNOS4412_MUX_ADDR_SHIFT 20
+
+/* Exynos5433 specific registers */
+#define EXYNOS5433_THD_TEMP_RISE3_0 0x050
+#define EXYNOS5433_THD_TEMP_RISE7_4 0x054
+#define EXYNOS5433_THD_TEMP_FALL3_0 0x060
+#define EXYNOS5433_THD_TEMP_FALL7_4 0x064
+#define EXYNOS5433_TMU_REG_INTEN 0x0c0
+#define EXYNOS5433_TMU_REG_INTPEND 0x0c8
+#define EXYNOS5433_TMU_EMUL_CON 0x110
+#define EXYNOS5433_TMU_PD_DET_EN 0x130
+
+#define EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT 16
+#define EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT 23
+#define EXYNOS5433_TRIMINFO_SENSOR_ID_MASK \
+ (0xf << EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT)
+#define EXYNOS5433_TRIMINFO_CALIB_SEL_MASK BIT(23)
+
+#define EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING 0
+#define EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING 1
+
+#define EXYNOS5433_PD_DET_EN 1
+
+#define EXYNOS5433_G3D_BASE 0x10070000
+
+/* Exynos7 specific registers */
+#define EXYNOS7_THD_TEMP_RISE7_6 0x50
+#define EXYNOS7_THD_TEMP_FALL7_6 0x60
+#define EXYNOS7_TMU_REG_INTEN 0x110
+#define EXYNOS7_TMU_REG_INTPEND 0x118
+#define EXYNOS7_TMU_REG_EMUL_CON 0x160
+
+#define EXYNOS7_TMU_TEMP_MASK 0x1ff
+#define EXYNOS7_PD_DET_EN_SHIFT 23
+#define EXYNOS7_TMU_INTEN_RISE0_SHIFT 0
+#define EXYNOS7_EMUL_DATA_SHIFT 7
+#define EXYNOS7_EMUL_DATA_MASK 0x1ff
+
+#define EXYNOS_FIRST_POINT_TRIM 25
+#define EXYNOS_SECOND_POINT_TRIM 85
+
+#define EXYNOS_NOISE_CANCEL_MODE 4
+
+#define MCELSIUS 1000
+
+enum soc_type {
+ SOC_ARCH_EXYNOS3250 = 1,
+ SOC_ARCH_EXYNOS4210,
+ SOC_ARCH_EXYNOS4412,
+ SOC_ARCH_EXYNOS5250,
+ SOC_ARCH_EXYNOS5260,
+ SOC_ARCH_EXYNOS5420,
+ SOC_ARCH_EXYNOS5420_TRIMINFO,
+ SOC_ARCH_EXYNOS5433,
+ SOC_ARCH_EXYNOS7,
+};
+
+/**
+ * struct exynos_tmu_data : A structure to hold the private data of the TMU
+ * driver
+ * @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.
+ * @lock: lock to implement synchronization.
+ * @clk: pointer to the clock structure.
+ * @clk_sec: pointer to the clock structure for accessing the base_second.
+ * @sclk: pointer to the clock structure for accessing the tmu special clk.
+ * @cal_type: calibration type for temperature
+ * @efuse_value: SoC defined fuse value
+ * @min_efuse_value: minimum valid trimming data
+ * @max_efuse_value: maximum valid trimming data
+ * @temp_error1: fused value of the first point trim.
+ * @temp_error2: fused value of the second point trim.
+ * @gain: gain of amplifier in the positive-TC generator block
+ * 0 < gain <= 15
+ * @reference_voltage: reference voltage of amplifier
+ * in the positive-TC generator block
+ * 0 < reference_voltage <= 31
+ * @tzd: pointer to thermal_zone_device structure
+ * @enabled: current status of TMU device
+ * @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
+ * @tmu_set_emulation: SoC specific TMU emulation setting method
+ * @tmu_clear_irqs: SoC specific TMU interrupts clearing method
+ */
+struct exynos_tmu_data {
+ void __iomem *base;
+ void __iomem *base_second;
+ int irq;
+ enum soc_type soc;
+ struct mutex lock;
+ struct clk *clk, *clk_sec, *sclk;
+ u32 cal_type;
+ u32 efuse_value;
+ u32 min_efuse_value;
+ u32 max_efuse_value;
+ u16 temp_error1, temp_error2;
+ u8 gain;
+ u8 reference_voltage;
+ struct thermal_zone_device *tzd;
+ bool enabled;
+
+ 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);
+ void (*tmu_set_emulation)(struct exynos_tmu_data *data, int temp);
+ void (*tmu_clear_irqs)(struct exynos_tmu_data *data);
+};
+
+/*
+ * TMU treats temperature as a mapped temperature code.
+ * The temperature is converted differently depending on the calibration type.
+ */
+static int temp_to_code(struct exynos_tmu_data *data, u8 temp)
+{
+ if (data->cal_type == TYPE_ONE_POINT_TRIMMING)
+ return temp + data->temp_error1 - EXYNOS_FIRST_POINT_TRIM;
+
+ return (temp - EXYNOS_FIRST_POINT_TRIM) *
+ (data->temp_error2 - data->temp_error1) /
+ (EXYNOS_SECOND_POINT_TRIM - EXYNOS_FIRST_POINT_TRIM) +
+ data->temp_error1;
+}
+
+/*
+ * Calculate a temperature value from a temperature code.
+ * The unit of the temperature is degree Celsius.
+ */
+static int code_to_temp(struct exynos_tmu_data *data, u16 temp_code)
+{
+ if (data->cal_type == TYPE_ONE_POINT_TRIMMING)
+ return temp_code - data->temp_error1 + EXYNOS_FIRST_POINT_TRIM;
+
+ return (temp_code - data->temp_error1) *
+ (EXYNOS_SECOND_POINT_TRIM - EXYNOS_FIRST_POINT_TRIM) /
+ (data->temp_error2 - data->temp_error1) +
+ EXYNOS_FIRST_POINT_TRIM;
+}
+
+static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info)
+{
+ u16 tmu_temp_mask =
+ (data->soc == SOC_ARCH_EXYNOS7) ? EXYNOS7_TMU_TEMP_MASK
+ : EXYNOS_TMU_TEMP_MASK;
+
+ data->temp_error1 = trim_info & tmu_temp_mask;
+ data->temp_error2 = ((trim_info >> EXYNOS_TRIMINFO_85_SHIFT) &
+ EXYNOS_TMU_TEMP_MASK);
+
+ if (!data->temp_error1 ||
+ (data->min_efuse_value > data->temp_error1) ||
+ (data->temp_error1 > data->max_efuse_value))
+ data->temp_error1 = data->efuse_value & EXYNOS_TMU_TEMP_MASK;
+
+ if (!data->temp_error2)
+ data->temp_error2 =
+ (data->efuse_value >> EXYNOS_TRIMINFO_85_SHIFT) &
+ EXYNOS_TMU_TEMP_MASK;
+}
+
+static int exynos_tmu_initialize(struct platform_device *pdev)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ unsigned int status;
+ int ret = 0;
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+ if (!IS_ERR(data->clk_sec))
+ clk_enable(data->clk_sec);
+
+ status = readb(data->base + EXYNOS_TMU_REG_STATUS);
+ if (!status) {
+ ret = -EBUSY;
+ } else {
+ data->tmu_initialize(pdev);
+ data->tmu_clear_irqs(data);
+ }
+
+ if (!IS_ERR(data->clk_sec))
+ clk_disable(data->clk_sec);
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+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;
+
+ ret = thermal_zone_get_crit_temp(tzd, &temp);
+ if (ret) {
+ /* FIXME: Remove this special case */
+ if (data->soc == SOC_ARCH_EXYNOS5433)
+ return 0;
+
+ dev_err(&pdev->dev,
+ "No CRITICAL trip point defined in device tree!\n");
+ return ret;
+ }
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+
+ data->tmu_set_crit_temp(data, temp / MCELSIUS);
+
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+
+ return 0;
+}
+
+static u32 get_con_reg(struct exynos_tmu_data *data, u32 con)
+{
+ if (data->soc == SOC_ARCH_EXYNOS4412 ||
+ data->soc == SOC_ARCH_EXYNOS3250)
+ con |= (EXYNOS4412_MUX_ADDR_VALUE << EXYNOS4412_MUX_ADDR_SHIFT);
+
+ con &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << EXYNOS_TMU_REF_VOLTAGE_SHIFT);
+ con |= data->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT;
+
+ con &= ~(EXYNOS_TMU_BUF_SLOPE_SEL_MASK << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT);
+ con |= (data->gain << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT);
+
+ con &= ~(EXYNOS_TMU_TRIP_MODE_MASK << EXYNOS_TMU_TRIP_MODE_SHIFT);
+ con |= (EXYNOS_NOISE_CANCEL_MODE << EXYNOS_TMU_TRIP_MODE_SHIFT);
+
+ return con;
+}
+
+static void exynos_tmu_control(struct platform_device *pdev, bool on)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+ data->tmu_control(pdev, on);
+ data->enabled = on;
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+}
+
+static void exynos_tmu_update_bit(struct exynos_tmu_data *data, int reg_off,
+ int bit_off, bool enable)
+{
+ u32 interrupt_en;
+
+ 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);
+}
+
+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;
+
+ tmu_temp_mask =
+ (data->soc == SOC_ARCH_EXYNOS7) ? EXYNOS7_TMU_TEMP_MASK
+ : EXYNOS_TMU_TEMP_MASK;
+
+ 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);
+}
+
+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)
+{
+ 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_low_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ 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);
+}
+
+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_disable_low(struct exynos_tmu_data *data)
+{
+ exynos_tmu_update_bit(data, EXYNOS_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_FALL0_SHIFT, false);
+}
+
+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)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ unsigned int trim_info, ctrl;
+
+ if (data->soc == SOC_ARCH_EXYNOS3250 ||
+ data->soc == SOC_ARCH_EXYNOS4412 ||
+ data->soc == SOC_ARCH_EXYNOS5250) {
+ if (data->soc == SOC_ARCH_EXYNOS3250) {
+ ctrl = readl(data->base + EXYNOS_TMU_TRIMINFO_CON1);
+ ctrl |= EXYNOS_TRIMINFO_RELOAD_ENABLE;
+ writel(ctrl, data->base + EXYNOS_TMU_TRIMINFO_CON1);
+ }
+ ctrl = readl(data->base + EXYNOS_TMU_TRIMINFO_CON2);
+ ctrl |= EXYNOS_TRIMINFO_RELOAD_ENABLE;
+ writel(ctrl, data->base + EXYNOS_TMU_TRIMINFO_CON2);
+ }
+
+ /* On exynos5420 the triminfo register is in the shared space */
+ if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO)
+ trim_info = readl(data->base_second + EXYNOS_TMU_REG_TRIMINFO);
+ else
+ trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+
+ sanitize_temp_error(data, trim_info);
+}
+
+static void exynos5433_tmu_set_low_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ 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);
+}
+
+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_disable_low(struct exynos_tmu_data *data)
+{
+ exynos_tmu_update_bit(data, EXYNOS5433_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_FALL0_SHIFT, false);
+}
+
+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);
+}
+
+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)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ unsigned int trim_info;
+ int sensor_id, cal_type;
+
+ trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+ sanitize_temp_error(data, trim_info);
+
+ /* Read the temperature sensor id */
+ sensor_id = (trim_info & EXYNOS5433_TRIMINFO_SENSOR_ID_MASK)
+ >> EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT;
+ dev_info(&pdev->dev, "Temperature sensor ID: 0x%x\n", sensor_id);
+
+ /* Read the calibration mode */
+ writel(trim_info, data->base + EXYNOS_TMU_REG_TRIMINFO);
+ cal_type = (trim_info & EXYNOS5433_TRIMINFO_CALIB_SEL_MASK)
+ >> EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT;
+
+ switch (cal_type) {
+ case EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING:
+ data->cal_type = TYPE_TWO_POINT_TRIMMING;
+ break;
+ case EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING:
+ default:
+ data->cal_type = TYPE_ONE_POINT_TRIMMING;
+ break;
+ }
+
+ dev_info(&pdev->dev, "Calibration type is %d-point calibration\n",
+ cal_type ? 2 : 1);
+}
+
+static void exynos7_tmu_set_low_temp(struct exynos_tmu_data *data, u8 temp)
+{
+ 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);
+}
+
+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_disable_low(struct exynos_tmu_data *data)
+{
+ exynos_tmu_update_bit(data, EXYNOS7_TMU_REG_INTEN,
+ EXYNOS_TMU_INTEN_FALL0_SHIFT + 0, false);
+}
+
+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);
+}
+
+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)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ unsigned int trim_info;
+
+ trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+ sanitize_temp_error(data, trim_info);
+}
+
+static void exynos4210_tmu_control(struct platform_device *pdev, bool on)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ unsigned int con;
+
+ con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
+
+ if (on)
+ con |= BIT(EXYNOS_TMU_CORE_EN_SHIFT);
+ else
+ con &= ~BIT(EXYNOS_TMU_CORE_EN_SHIFT);
+
+ 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);
+ unsigned int con, pd_det_en;
+
+ con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
+
+ 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(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);
+ unsigned int con;
+
+ con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
+
+ if (on) {
+ con |= BIT(EXYNOS_TMU_CORE_EN_SHIFT);
+ con |= BIT(EXYNOS7_PD_DET_EN_SHIFT);
+ } else {
+ con &= ~BIT(EXYNOS_TMU_CORE_EN_SHIFT);
+ con &= ~BIT(EXYNOS7_PD_DET_EN_SHIFT);
+ }
+
+ writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
+}
+
+static int exynos_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct exynos_tmu_data *data = thermal_zone_device_priv(tz);
+ int value, ret = 0;
+
+ if (!data || !data->tmu_read)
+ return -EINVAL;
+ else if (!data->enabled)
+ /*
+ * Called too early, probably
+ * from thermal_zone_of_sensor_register().
+ */
+ return -EAGAIN;
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+
+ value = data->tmu_read(data);
+ if (value < 0)
+ ret = value;
+ else
+ *temp = code_to_temp(data, value) * MCELSIUS;
+
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+#ifdef CONFIG_THERMAL_EMULATION
+static u32 get_emul_con_reg(struct exynos_tmu_data *data, unsigned int val,
+ int temp)
+{
+ if (temp) {
+ temp /= MCELSIUS;
+
+ val &= ~(EXYNOS_EMUL_TIME_MASK << EXYNOS_EMUL_TIME_SHIFT);
+ val |= (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT);
+ if (data->soc == SOC_ARCH_EXYNOS7) {
+ val &= ~(EXYNOS7_EMUL_DATA_MASK <<
+ EXYNOS7_EMUL_DATA_SHIFT);
+ val |= (temp_to_code(data, temp) <<
+ EXYNOS7_EMUL_DATA_SHIFT) |
+ EXYNOS_EMUL_ENABLE;
+ } else {
+ val &= ~(EXYNOS_EMUL_DATA_MASK <<
+ EXYNOS_EMUL_DATA_SHIFT);
+ val |= (temp_to_code(data, temp) <<
+ EXYNOS_EMUL_DATA_SHIFT) |
+ EXYNOS_EMUL_ENABLE;
+ }
+ } else {
+ val &= ~EXYNOS_EMUL_ENABLE;
+ }
+
+ return val;
+}
+
+static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data,
+ int temp)
+{
+ unsigned int val;
+ u32 emul_con;
+
+ if (data->soc == SOC_ARCH_EXYNOS5260)
+ emul_con = EXYNOS5260_EMUL_CON;
+ else if (data->soc == SOC_ARCH_EXYNOS5433)
+ emul_con = EXYNOS5433_TMU_EMUL_CON;
+ else if (data->soc == SOC_ARCH_EXYNOS7)
+ emul_con = EXYNOS7_TMU_REG_EMUL_CON;
+ else
+ emul_con = EXYNOS_EMUL_CON;
+
+ val = readl(data->base + emul_con);
+ val = get_emul_con_reg(data, val, temp);
+ writel(val, data->base + emul_con);
+}
+
+static int exynos_tmu_set_emulation(struct thermal_zone_device *tz, int temp)
+{
+ struct exynos_tmu_data *data = thermal_zone_device_priv(tz);
+ int ret = -EINVAL;
+
+ if (data->soc == SOC_ARCH_EXYNOS4210)
+ goto out;
+
+ if (temp && temp < MCELSIUS)
+ goto out;
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+ data->tmu_set_emulation(data, temp);
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+ return 0;
+out:
+ return ret;
+}
+#else
+#define exynos4412_tmu_set_emulation NULL
+static int exynos_tmu_set_emulation(struct thermal_zone_device *tz, int temp)
+ { return -EINVAL; }
+#endif /* CONFIG_THERMAL_EMULATION */
+
+static int exynos4210_tmu_read(struct exynos_tmu_data *data)
+{
+ int ret = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);
+
+ /* "temp_code" should range between 75 and 175 */
+ return (ret < 75 || ret > 175) ? -ENODATA : ret;
+}
+
+static int exynos4412_tmu_read(struct exynos_tmu_data *data)
+{
+ return readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);
+}
+
+static int exynos7_tmu_read(struct exynos_tmu_data *data)
+{
+ return readw(data->base + EXYNOS_TMU_REG_CURRENT_TEMP) &
+ EXYNOS7_TMU_TEMP_MASK;
+}
+
+static irqreturn_t exynos_tmu_threaded_irq(int irq, void *id)
+{
+ struct exynos_tmu_data *data = id;
+
+ thermal_zone_device_update(data->tzd, THERMAL_EVENT_UNSPECIFIED);
+
+ mutex_lock(&data->lock);
+ clk_enable(data->clk);
+
+ /* TODO: take action based on particular interrupt */
+ data->tmu_clear_irqs(data);
+
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+
+ return IRQ_HANDLED;
+}
+
+static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
+{
+ unsigned int val_irq;
+ u32 tmu_intstat, tmu_intclear;
+
+ if (data->soc == SOC_ARCH_EXYNOS5260) {
+ tmu_intstat = EXYNOS5260_TMU_REG_INTSTAT;
+ tmu_intclear = EXYNOS5260_TMU_REG_INTCLEAR;
+ } else if (data->soc == SOC_ARCH_EXYNOS7) {
+ tmu_intstat = EXYNOS7_TMU_REG_INTPEND;
+ tmu_intclear = EXYNOS7_TMU_REG_INTPEND;
+ } else if (data->soc == SOC_ARCH_EXYNOS5433) {
+ tmu_intstat = EXYNOS5433_TMU_REG_INTPEND;
+ tmu_intclear = EXYNOS5433_TMU_REG_INTPEND;
+ } else {
+ tmu_intstat = EXYNOS_TMU_REG_INTSTAT;
+ tmu_intclear = EXYNOS_TMU_REG_INTCLEAR;
+ }
+
+ val_irq = readl(data->base + tmu_intstat);
+ /*
+ * Clear the interrupts. Please note that the documentation for
+ * Exynos3250, Exynos4412, Exynos5250 and Exynos5260 incorrectly
+ * states that INTCLEAR register has a different placing of bits
+ * responsible for FALL IRQs than INTSTAT register. Exynos5420
+ * and Exynos5440 documentation is correct (Exynos4210 doesn't
+ * support FALL IRQs at all).
+ */
+ writel(val_irq, data->base + tmu_intclear);
+}
+
+static const struct of_device_id exynos_tmu_match[] = {
+ {
+ .compatible = "samsung,exynos3250-tmu",
+ .data = (const void *)SOC_ARCH_EXYNOS3250,
+ }, {
+ .compatible = "samsung,exynos4210-tmu",
+ .data = (const void *)SOC_ARCH_EXYNOS4210,
+ }, {
+ .compatible = "samsung,exynos4412-tmu",
+ .data = (const void *)SOC_ARCH_EXYNOS4412,
+ }, {
+ .compatible = "samsung,exynos5250-tmu",
+ .data = (const void *)SOC_ARCH_EXYNOS5250,
+ }, {
+ .compatible = "samsung,exynos5260-tmu",
+ .data = (const void *)SOC_ARCH_EXYNOS5260,
+ }, {
+ .compatible = "samsung,exynos5420-tmu",
+ .data = (const void *)SOC_ARCH_EXYNOS5420,
+ }, {
+ .compatible = "samsung,exynos5420-tmu-ext-triminfo",
+ .data = (const void *)SOC_ARCH_EXYNOS5420_TRIMINFO,
+ }, {
+ .compatible = "samsung,exynos5433-tmu",
+ .data = (const void *)SOC_ARCH_EXYNOS5433,
+ }, {
+ .compatible = "samsung,exynos7-tmu",
+ .data = (const void *)SOC_ARCH_EXYNOS7,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, exynos_tmu_match);
+
+static int exynos_map_dt_data(struct platform_device *pdev)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ struct resource res;
+
+ if (!data || !pdev->dev.of_node)
+ return -ENODEV;
+
+ data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
+ if (data->irq <= 0) {
+ dev_err(&pdev->dev, "failed to get IRQ\n");
+ return -ENODEV;
+ }
+
+ if (of_address_to_resource(pdev->dev.of_node, 0, &res)) {
+ dev_err(&pdev->dev, "failed to get Resource 0\n");
+ return -ENODEV;
+ }
+
+ data->base = devm_ioremap(&pdev->dev, res.start, resource_size(&res));
+ if (!data->base) {
+ dev_err(&pdev->dev, "Failed to ioremap memory\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ data->soc = (uintptr_t)of_device_get_match_data(&pdev->dev);
+
+ switch (data->soc) {
+ case SOC_ARCH_EXYNOS4210:
+ 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->gain = 15;
+ data->reference_voltage = 7;
+ data->efuse_value = 55;
+ data->min_efuse_value = 40;
+ data->max_efuse_value = 100;
+ break;
+ case SOC_ARCH_EXYNOS3250:
+ case SOC_ARCH_EXYNOS4412:
+ case SOC_ARCH_EXYNOS5250:
+ case SOC_ARCH_EXYNOS5260:
+ case SOC_ARCH_EXYNOS5420:
+ case SOC_ARCH_EXYNOS5420_TRIMINFO:
+ 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->gain = 8;
+ data->reference_voltage = 16;
+ data->efuse_value = 55;
+ if (data->soc != SOC_ARCH_EXYNOS5420 &&
+ data->soc != SOC_ARCH_EXYNOS5420_TRIMINFO)
+ data->min_efuse_value = 40;
+ else
+ data->min_efuse_value = 0;
+ data->max_efuse_value = 100;
+ break;
+ case SOC_ARCH_EXYNOS5433:
+ 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->gain = 8;
+ if (res.start == EXYNOS5433_G3D_BASE)
+ data->reference_voltage = 23;
+ else
+ data->reference_voltage = 16;
+ data->efuse_value = 75;
+ data->min_efuse_value = 40;
+ data->max_efuse_value = 150;
+ break;
+ case SOC_ARCH_EXYNOS7:
+ 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->gain = 9;
+ data->reference_voltage = 17;
+ data->efuse_value = 75;
+ data->min_efuse_value = 15;
+ data->max_efuse_value = 100;
+ break;
+ default:
+ dev_err(&pdev->dev, "Platform not supported\n");
+ return -EINVAL;
+ }
+
+ data->cal_type = TYPE_ONE_POINT_TRIMMING;
+
+ /*
+ * Check if the TMU shares some registers and then try to map the
+ * memory of common registers.
+ */
+ if (data->soc != SOC_ARCH_EXYNOS5420_TRIMINFO)
+ return 0;
+
+ if (of_address_to_resource(pdev->dev.of_node, 1, &res)) {
+ dev_err(&pdev->dev, "failed to get Resource 1\n");
+ return -ENODEV;
+ }
+
+ data->base_second = devm_ioremap(&pdev->dev, res.start,
+ resource_size(&res));
+ if (!data->base_second) {
+ dev_err(&pdev->dev, "Failed to ioremap memory\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int exynos_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(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, data);
+ mutex_init(&data->lock);
+
+ /*
+ * Try enabling the regulator if found
+ * TODO: Add regulator as an SOC feature, so that regulator enable
+ * is a compulsory call.
+ */
+ 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)
+ return ret;
+
+ 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(dev, "tmu_triminfo_apbif");
+ if (IS_ERR(data->clk_sec)) {
+ 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(dev, "Failed to get clock\n");
+ return ret;
+ }
+ }
+
+ ret = clk_prepare(data->clk);
+ if (ret) {
+ 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(dev, "tmu_sclk");
+ if (IS_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(dev, "Failed to enable sclk\n");
+ goto err_clk;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ 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 = dev_err_probe(dev, PTR_ERR(data->tzd), "Failed to register sensor\n");
+ goto err_sclk;
+ }
+
+ ret = exynos_thermal_zone_configure(pdev);
+ if (ret) {
+ dev_err(dev, "Failed to configure the thermal zone\n");
+ goto err_sclk;
+ }
+
+ 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)
+ goto err_sclk;
+
+ exynos_tmu_control(pdev, true);
+ return 0;
+
+err_sclk:
+ clk_disable_unprepare(data->sclk);
+err_clk:
+ clk_unprepare(data->clk);
+err_clk_sec:
+ if (!IS_ERR(data->clk_sec))
+ clk_unprepare(data->clk_sec);
+ return ret;
+}
+
+static void exynos_tmu_remove(struct platform_device *pdev)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+
+ exynos_tmu_control(pdev, false);
+
+ clk_disable_unprepare(data->sclk);
+ clk_unprepare(data->clk);
+ if (!IS_ERR(data->clk_sec))
+ clk_unprepare(data->clk_sec);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos_tmu_suspend(struct device *dev)
+{
+ exynos_tmu_control(to_platform_device(dev), false);
+
+ return 0;
+}
+
+static int exynos_tmu_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ exynos_tmu_initialize(pdev);
+ exynos_tmu_control(pdev, true);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(exynos_tmu_pm,
+ exynos_tmu_suspend, exynos_tmu_resume);
+#define EXYNOS_TMU_PM (&exynos_tmu_pm)
+#else
+#define EXYNOS_TMU_PM NULL
+#endif
+
+static struct platform_driver exynos_tmu_driver = {
+ .driver = {
+ .name = "exynos-tmu",
+ .pm = EXYNOS_TMU_PM,
+ .of_match_table = exynos_tmu_match,
+ },
+ .probe = exynos_tmu_probe,
+ .remove = exynos_tmu_remove,
+};
+
+module_platform_driver(exynos_tmu_driver);
+
+MODULE_DESCRIPTION("Exynos TMU Driver");
+MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:exynos-tmu");
diff --git a/drivers/thermal/spacemit/Kconfig b/drivers/thermal/spacemit/Kconfig
new file mode 100644
index 000000000..de7b5ece5
--- /dev/null
+++ b/drivers/thermal/spacemit/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "SpacemiT thermal drivers"
+depends on ARCH_SPACEMIT || COMPILE_TEST
+
+config SPACEMIT_K1_TSENSOR
+ tristate "SpacemiT K1 thermal sensor driver"
+ depends on THERMAL_OF
+ help
+ This driver provides support for the thermal sensor
+ integrated in the SpacemiT K1 SoC.
+
+ The thermal sensor monitors temperatures for five thermal zones:
+ soc, package, gpu, cluster0, and cluster1. It supports reporting
+ temperature values and handling high/low threshold interrupts.
+
+ Say Y here if you want to enable thermal monitoring on SpacemiT K1.
+ If compiled as a module, it will be called k1_tsensor.
+
+endmenu
diff --git a/drivers/thermal/spacemit/Makefile b/drivers/thermal/spacemit/Makefile
new file mode 100644
index 000000000..82b30741e
--- /dev/null
+++ b/drivers/thermal/spacemit/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_SPACEMIT_K1_TSENSOR) += k1_tsensor.o
diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c
new file mode 100644
index 000000000..ef91453e5
--- /dev/null
+++ b/drivers/thermal/spacemit/k1_tsensor.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Thermal sensor driver for SpacemiT K1 SoC
+ *
+ * Copyright (C) 2026 Shuwei Wu <shuwei.wu@mailbox.org>
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "../thermal_hwmon.h"
+
+#define K1_TSENSOR_PCTRL_REG 0x00
+#define K1_TSENSOR_PCTRL_ENABLE BIT(0)
+#define K1_TSENSOR_PCTRL_TEMP_MODE BIT(3)
+#define K1_TSENSOR_PCTRL_RAW_SEL BIT(7)
+
+#define K1_TSENSOR_PCTRL_CTUNE GENMASK(11, 8)
+#define K1_TSENSOR_PCTRL_SW_CTRL GENMASK(21, 18)
+#define K1_TSENSOR_PCTRL_HW_AUTO_MODE BIT(23)
+
+#define K1_TSENSOR_EN_REG 0x08
+#define K1_TSENSOR_EN_ALL GENMASK(MAX_SENSOR_NUMBER - 1, 0)
+
+#define K1_TSENSOR_TIME_REG 0x0C
+#define K1_TSENSOR_TIME_WAIT_REF_CNT GENMASK(3, 0)
+#define K1_TSENSOR_TIME_ADC_CNT_RST GENMASK(7, 4)
+#define K1_TSENSOR_TIME_FILTER_PERIOD GENMASK(21, 20)
+#define K1_TSENSOR_TIME_MASK GENMASK(23, 0)
+
+#define K1_TSENSOR_INT_CLR_REG 0x10
+#define K1_TSENSOR_INT_EN_REG 0x14
+#define K1_TSENSOR_INT_STA_REG 0x18
+
+#define K1_TSENSOR_INT_EN_MASK BIT(0)
+#define K1_TSENSOR_INT_MASK(x) (GENMASK(2, 1) << ((x) * 2))
+
+#define K1_TSENSOR_DATA_BASE_REG 0x20
+#define K1_TSENSOR_DATA_REG(x) (K1_TSENSOR_DATA_BASE_REG + ((x) / 2) * 4)
+#define K1_TSENSOR_DATA_LOW_MASK GENMASK(15, 0)
+#define K1_TSENSOR_DATA_HIGH_MASK GENMASK(31, 16)
+
+#define K1_TSENSOR_THRSH_BASE_REG 0x40
+#define K1_TSENSOR_THRSH_REG(x) (K1_TSENSOR_THRSH_BASE_REG + ((x) * 4))
+#define K1_TSENSOR_THRSH_LOW_MASK GENMASK(15, 0)
+#define K1_TSENSOR_THRSH_HIGH_MASK GENMASK(31, 16)
+
+#define MAX_SENSOR_NUMBER 5
+
+/* Hardware offset value required for temperature calculation */
+#define TEMPERATURE_OFFSET 278
+
+struct k1_tsensor_channel {
+ struct k1_tsensor *ts;
+ struct thermal_zone_device *tzd;
+ int id;
+};
+
+struct k1_tsensor {
+ void __iomem *base;
+ struct k1_tsensor_channel ch[MAX_SENSOR_NUMBER];
+};
+
+static void k1_tsensor_init(struct k1_tsensor *ts)
+{
+ u32 val;
+
+ /* Disable all the interrupts */
+ writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG);
+
+ /* Configure ADC sampling time and filter period */
+ val = readl(ts->base + K1_TSENSOR_TIME_REG);
+ val &= ~K1_TSENSOR_TIME_MASK;
+ val |= K1_TSENSOR_TIME_FILTER_PERIOD |
+ K1_TSENSOR_TIME_ADC_CNT_RST |
+ K1_TSENSOR_TIME_WAIT_REF_CNT;
+ writel(val, ts->base + K1_TSENSOR_TIME_REG);
+
+ /*
+ * Enable all sensors' auto mode, enable dither control,
+ * consecutive mode, and power up sensor.
+ */
+ val = readl(ts->base + K1_TSENSOR_PCTRL_REG);
+ val &= ~K1_TSENSOR_PCTRL_SW_CTRL;
+ val &= ~K1_TSENSOR_PCTRL_CTUNE;
+ val |= K1_TSENSOR_PCTRL_RAW_SEL |
+ K1_TSENSOR_PCTRL_TEMP_MODE |
+ K1_TSENSOR_PCTRL_HW_AUTO_MODE |
+ K1_TSENSOR_PCTRL_ENABLE;
+ writel(val, ts->base + K1_TSENSOR_PCTRL_REG);
+
+ /* Enable each sensor */
+ val = readl(ts->base + K1_TSENSOR_EN_REG);
+ val |= K1_TSENSOR_EN_ALL;
+ writel(val, ts->base + K1_TSENSOR_EN_REG);
+}
+
+static void k1_tsensor_enable_irq(struct k1_tsensor_channel *ch)
+{
+ struct k1_tsensor *ts = ch->ts;
+ u32 val;
+
+ val = readl(ts->base + K1_TSENSOR_INT_CLR_REG);
+ val |= K1_TSENSOR_INT_MASK(ch->id);
+ writel(val, ts->base + K1_TSENSOR_INT_CLR_REG);
+
+ val = readl(ts->base + K1_TSENSOR_INT_EN_REG);
+ val &= ~K1_TSENSOR_INT_MASK(ch->id);
+ writel(val, ts->base + K1_TSENSOR_INT_EN_REG);
+
+ /* Enable thermal interrupt */
+ val = readl(ts->base + K1_TSENSOR_INT_EN_REG);
+ val |= K1_TSENSOR_INT_EN_MASK;
+ writel(val, ts->base + K1_TSENSOR_INT_EN_REG);
+}
+
+/*
+ * The conversion formula used is:
+ * T(m°C) = (((raw_value & mask) >> shift) - TEMPERATURE_OFFSET) * 1000
+ */
+static int k1_tsensor_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz);
+ struct k1_tsensor *ts = ch->ts;
+ u32 val;
+
+ val = readl(ts->base + K1_TSENSOR_DATA_REG(ch->id));
+ if (ch->id % 2)
+ *temp = FIELD_GET(K1_TSENSOR_DATA_HIGH_MASK, val);
+ else
+ *temp = FIELD_GET(K1_TSENSOR_DATA_LOW_MASK, val);
+
+ *temp -= TEMPERATURE_OFFSET;
+ *temp *= 1000;
+
+ return 0;
+}
+
+/*
+ * For each sensor, the hardware threshold register is 32 bits:
+ * - Lower 16 bits [15:0] configure the low threshold temperature.
+ * - Upper 16 bits [31:16] configure the high threshold temperature.
+ */
+static int k1_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz);
+ struct k1_tsensor *ts = ch->ts;
+ u32 val;
+
+ low = clamp_val(low / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET,
+ FIELD_MAX(K1_TSENSOR_THRSH_LOW_MASK));
+ high = clamp_val(high / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET,
+ FIELD_MAX(K1_TSENSOR_THRSH_HIGH_MASK));
+ if (low >= high)
+ return -EINVAL;
+
+ val = readl(ts->base + K1_TSENSOR_THRSH_REG(ch->id));
+
+ val &= ~(K1_TSENSOR_THRSH_LOW_MASK | K1_TSENSOR_THRSH_HIGH_MASK);
+ val |= FIELD_PREP(K1_TSENSOR_THRSH_LOW_MASK, low);
+ val |= FIELD_PREP(K1_TSENSOR_THRSH_HIGH_MASK, high);
+
+ writel(val, ts->base + K1_TSENSOR_THRSH_REG(ch->id));
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops k1_tsensor_ops = {
+ .get_temp = k1_tsensor_get_temp,
+ .set_trips = k1_tsensor_set_trips,
+};
+
+static irqreturn_t k1_tsensor_irq_thread(int irq, void *data)
+{
+ struct k1_tsensor *ts = (struct k1_tsensor *)data;
+ int mask, status, i;
+
+ status = readl(ts->base + K1_TSENSOR_INT_STA_REG);
+
+ for (i = 0; i < MAX_SENSOR_NUMBER; i++) {
+ if (status & K1_TSENSOR_INT_MASK(i)) {
+ mask = readl(ts->base + K1_TSENSOR_INT_CLR_REG);
+ mask |= K1_TSENSOR_INT_MASK(i);
+ writel(mask, ts->base + K1_TSENSOR_INT_CLR_REG);
+ thermal_zone_device_update(ts->ch[i].tzd, THERMAL_EVENT_UNSPECIFIED);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void k1_tsensor_shutdown(struct k1_tsensor *ts)
+{
+ u32 val;
+
+ /* Disable all interrupts */
+ writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG);
+
+ /* Disable all sensors */
+ val = readl(ts->base + K1_TSENSOR_EN_REG);
+ val &= ~K1_TSENSOR_EN_ALL;
+ writel(val, ts->base + K1_TSENSOR_EN_REG);
+
+ /* Clear the sampling configuration set by k1_tsensor_init() */
+ val = readl(ts->base + K1_TSENSOR_TIME_REG);
+ val &= ~(K1_TSENSOR_TIME_FILTER_PERIOD |
+ K1_TSENSOR_TIME_ADC_CNT_RST |
+ K1_TSENSOR_TIME_WAIT_REF_CNT);
+ writel(val, ts->base + K1_TSENSOR_TIME_REG);
+
+ /* Clear the control bits configured by k1_tsensor_init() */
+ val = readl(ts->base + K1_TSENSOR_PCTRL_REG);
+ val &= ~(K1_TSENSOR_PCTRL_RAW_SEL |
+ K1_TSENSOR_PCTRL_TEMP_MODE |
+ K1_TSENSOR_PCTRL_HW_AUTO_MODE |
+ K1_TSENSOR_PCTRL_ENABLE);
+ writel(val, ts->base + K1_TSENSOR_PCTRL_REG);
+}
+
+static void k1_tsensor_shutdown_action(void *data)
+{
+ k1_tsensor_shutdown(data);
+}
+
+static int k1_tsensor_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct k1_tsensor *ts;
+ struct reset_control *reset;
+ struct clk *clk;
+ int i, irq, ret;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ts->base))
+ return dev_err_probe(dev, PTR_ERR(ts->base), "Failed to get reg\n");
+
+ reset = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(reset))
+ return dev_err_probe(dev, PTR_ERR(reset), "Failed to get/deassert reset control\n");
+
+ clk = devm_clk_get_enabled(dev, "core");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get core clock\n");
+
+ clk = devm_clk_get_enabled(dev, "bus");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get bus clock\n");
+
+ k1_tsensor_init(ts);
+
+ for (i = 0; i < MAX_SENSOR_NUMBER; ++i) {
+ ts->ch[i].id = i;
+ ts->ch[i].ts = ts;
+ ts->ch[i].tzd = devm_thermal_of_zone_register(dev, i, ts->ch + i, &k1_tsensor_ops);
+ if (IS_ERR(ts->ch[i].tzd)) {
+ ret = PTR_ERR(ts->ch[i].tzd);
+ goto err_shutdown;
+ }
+
+ /* Attach sysfs hwmon attributes for userspace monitoring */
+ ret = devm_thermal_add_hwmon_sysfs(dev, ts->ch[i].tzd);
+ if (ret)
+ dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ ret = irq;
+ goto err_shutdown;
+ }
+
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ k1_tsensor_irq_thread,
+ IRQF_ONESHOT, "k1_tsensor", ts);
+ if (ret < 0)
+ goto err_shutdown;
+
+ ret = devm_add_action_or_reset(dev, k1_tsensor_shutdown_action, ts);
+ if (ret)
+ return ret;
+
+ /* Enable interrupts only after all zones and the handler are ready */
+ for (i = 0; i < MAX_SENSOR_NUMBER; ++i)
+ k1_tsensor_enable_irq(ts->ch + i);
+
+ platform_set_drvdata(pdev, ts);
+
+ return 0;
+
+err_shutdown:
+ k1_tsensor_shutdown(ts);
+ return ret;
+}
+
+static const struct of_device_id k1_tsensor_dt_ids[] = {
+ { .compatible = "spacemit,k1-tsensor" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, k1_tsensor_dt_ids);
+
+static struct platform_driver k1_tsensor_driver = {
+ .driver = {
+ .name = "k1_tsensor",
+ .of_match_table = k1_tsensor_dt_ids,
+ },
+ .probe = k1_tsensor_probe,
+};
+module_platform_driver(k1_tsensor_driver);
+
+MODULE_DESCRIPTION("SpacemiT K1 Thermal Sensor Driver");
+MODULE_AUTHOR("Shuwei Wu <shuwei.wu@mailbox.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/spear_thermal.c b/drivers/thermal/spear_thermal.c
new file mode 100644
index 000000000..5e3e9c1f3
--- /dev/null
+++ b/drivers/thermal/spear_thermal.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SPEAr thermal driver.
+ *
+ * Copyright (C) 2011-2012 ST Microelectronics
+ * Author: Vincenzo Frascino <vincenzo.frascino@st.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+#define MD_FACTOR 1000
+
+/* SPEAr Thermal Sensor Dev Structure */
+struct spear_thermal_dev {
+ /* pointer to base address of the thermal sensor */
+ void __iomem *thermal_base;
+ /* clk structure */
+ struct clk *clk;
+ /* pointer to thermal flags */
+ unsigned int flags;
+};
+
+static inline int thermal_get_temp(struct thermal_zone_device *thermal,
+ int *temp)
+{
+ struct spear_thermal_dev *stdev = thermal_zone_device_priv(thermal);
+
+ /*
+ * Data are ready to be read after 628 usec from POWERDOWN signal
+ * (PDN) = 1
+ */
+ *temp = (readl_relaxed(stdev->thermal_base) & 0x7F) * MD_FACTOR;
+ return 0;
+}
+
+static const struct thermal_zone_device_ops ops = {
+ .get_temp = thermal_get_temp,
+};
+
+static int __maybe_unused spear_thermal_suspend(struct device *dev)
+{
+ struct thermal_zone_device *spear_thermal = dev_get_drvdata(dev);
+ struct spear_thermal_dev *stdev = thermal_zone_device_priv(spear_thermal);
+ unsigned int actual_mask = 0;
+
+ /* Disable SPEAr Thermal Sensor */
+ actual_mask = readl_relaxed(stdev->thermal_base);
+ writel_relaxed(actual_mask & ~stdev->flags, stdev->thermal_base);
+
+ clk_disable(stdev->clk);
+ dev_info(dev, "Suspended.\n");
+
+ return 0;
+}
+
+static int __maybe_unused spear_thermal_resume(struct device *dev)
+{
+ struct thermal_zone_device *spear_thermal = dev_get_drvdata(dev);
+ struct spear_thermal_dev *stdev = thermal_zone_device_priv(spear_thermal);
+ unsigned int actual_mask = 0;
+ int ret = 0;
+
+ ret = clk_enable(stdev->clk);
+ if (ret) {
+ dev_err(dev, "Can't enable clock\n");
+ return ret;
+ }
+
+ /* Enable SPEAr Thermal Sensor */
+ actual_mask = readl_relaxed(stdev->thermal_base);
+ writel_relaxed(actual_mask | stdev->flags, stdev->thermal_base);
+
+ dev_info(dev, "Resumed.\n");
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(spear_thermal_pm_ops, spear_thermal_suspend,
+ spear_thermal_resume);
+
+static int spear_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *spear_thermal = NULL;
+ struct spear_thermal_dev *stdev;
+ struct device_node *np = pdev->dev.of_node;
+ int ret = 0, val;
+
+ if (!np || of_property_read_u32(np, "st,thermal-flags", &val)) {
+ dev_err(&pdev->dev, "Failed: DT Pdata not passed\n");
+ return -EINVAL;
+ }
+
+ stdev = devm_kzalloc(&pdev->dev, sizeof(*stdev), GFP_KERNEL);
+ if (!stdev)
+ return -ENOMEM;
+
+ /* Enable thermal sensor */
+ stdev->thermal_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(stdev->thermal_base))
+ return PTR_ERR(stdev->thermal_base);
+
+ stdev->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(stdev->clk)) {
+ dev_err(&pdev->dev, "Can't get clock\n");
+ return PTR_ERR(stdev->clk);
+ }
+
+ ret = clk_enable(stdev->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't enable clock\n");
+ return ret;
+ }
+
+ stdev->flags = val;
+ writel_relaxed(stdev->flags, stdev->thermal_base);
+
+ spear_thermal = thermal_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);
+ goto disable_clk;
+ }
+ ret = thermal_zone_device_enable(spear_thermal);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot enable thermal zone\n");
+ goto unregister_tzd;
+ }
+
+ platform_set_drvdata(pdev, spear_thermal);
+
+ dev_info(&pdev->dev, "Thermal Sensor Loaded at: 0x%p.\n",
+ stdev->thermal_base);
+
+ return 0;
+
+unregister_tzd:
+ thermal_zone_device_unregister(spear_thermal);
+disable_clk:
+ clk_disable(stdev->clk);
+
+ return ret;
+}
+
+static void spear_thermal_exit(struct platform_device *pdev)
+{
+ unsigned int actual_mask = 0;
+ struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev);
+ struct spear_thermal_dev *stdev = thermal_zone_device_priv(spear_thermal);
+
+ thermal_zone_device_unregister(spear_thermal);
+
+ /* Disable SPEAr Thermal Sensor */
+ actual_mask = readl_relaxed(stdev->thermal_base);
+ writel_relaxed(actual_mask & ~stdev->flags, stdev->thermal_base);
+
+ clk_disable(stdev->clk);
+}
+
+static const struct of_device_id spear_thermal_id_table[] = {
+ { .compatible = "st,thermal-spear1340" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, spear_thermal_id_table);
+
+static struct platform_driver spear_thermal_driver = {
+ .probe = spear_thermal_probe,
+ .remove = spear_thermal_exit,
+ .driver = {
+ .name = "spear_thermal",
+ .pm = &spear_thermal_pm_ops,
+ .of_match_table = spear_thermal_id_table,
+ },
+};
+
+module_platform_driver(spear_thermal_driver);
+
+MODULE_AUTHOR("Vincenzo Frascino <vincenzo.frascino@st.com>");
+MODULE_DESCRIPTION("SPEAr thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/sprd_thermal.c b/drivers/thermal/sprd_thermal.c
new file mode 100644
index 000000000..d683fcb0f
--- /dev/null
+++ b/drivers/thermal/sprd_thermal.c
@@ -0,0 +1,549 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2020 Spreadtrum Communications Inc.
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define SPRD_THM_CTL 0x0
+#define SPRD_THM_INT_EN 0x4
+#define SPRD_THM_INT_STS 0x8
+#define SPRD_THM_INT_RAW_STS 0xc
+#define SPRD_THM_DET_PERIOD 0x10
+#define SPRD_THM_INT_CLR 0x14
+#define SPRD_THM_INT_CLR_ST 0x18
+#define SPRD_THM_MON_PERIOD 0x4c
+#define SPRD_THM_MON_CTL 0x50
+#define SPRD_THM_INTERNAL_STS1 0x54
+#define SPRD_THM_RAW_READ_MSK 0x3ff
+
+#define SPRD_THM_OFFSET(id) ((id) * 0x4)
+#define SPRD_THM_TEMP(id) (SPRD_THM_OFFSET(id) + 0x5c)
+#define SPRD_THM_THRES(id) (SPRD_THM_OFFSET(id) + 0x2c)
+
+#define SPRD_THM_SEN(id) BIT((id) + 2)
+#define SPRD_THM_SEN_OVERHEAT_EN(id) BIT((id) + 8)
+#define SPRD_THM_SEN_OVERHEAT_ALARM_EN(id) BIT((id) + 0)
+
+/* bits definitions for register THM_CTL */
+#define SPRD_THM_SET_RDY_ST BIT(13)
+#define SPRD_THM_SET_RDY BIT(12)
+#define SPRD_THM_MON_EN BIT(1)
+#define SPRD_THM_EN BIT(0)
+
+/* bits definitions for register THM_INT_CTL */
+#define SPRD_THM_BIT_INT_EN BIT(26)
+#define SPRD_THM_OVERHEAT_EN BIT(25)
+#define SPRD_THM_OTP_TRIP_SHIFT 10
+
+/* bits definitions for register SPRD_THM_INTERNAL_STS1 */
+#define SPRD_THM_TEMPER_RDY BIT(0)
+
+#define SPRD_THM_DET_PERIOD_DATA 0x800
+#define SPRD_THM_DET_PERIOD_MASK GENMASK(19, 0)
+#define SPRD_THM_MON_MODE 0x7
+#define SPRD_THM_MON_MODE_MASK GENMASK(3, 0)
+#define SPRD_THM_MON_PERIOD_DATA 0x10
+#define SPRD_THM_MON_PERIOD_MASK GENMASK(15, 0)
+#define SPRD_THM_THRES_MASK GENMASK(19, 0)
+#define SPRD_THM_INT_CLR_MASK GENMASK(24, 0)
+
+/* thermal sensor calibration parameters */
+#define SPRD_THM_TEMP_LOW -40000
+#define SPRD_THM_TEMP_HIGH 120000
+#define SPRD_THM_OTP_TEMP 120000
+#define SPRD_THM_HOT_TEMP 75000
+#define SPRD_THM_RAW_DATA_LOW 0
+#define SPRD_THM_RAW_DATA_HIGH 1000
+#define SPRD_THM_SEN_NUM 8
+#define SPRD_THM_DT_OFFSET 24
+#define SPRD_THM_RATION_OFFSET 17
+#define SPRD_THM_RATION_SIGN 16
+
+#define SPRD_THM_RDYST_POLLING_TIME 10
+#define SPRD_THM_RDYST_TIMEOUT 700
+#define SPRD_THM_TEMP_READY_POLL_TIME 10000
+#define SPRD_THM_TEMP_READY_TIMEOUT 600000
+#define SPRD_THM_MAX_SENSOR 8
+
+struct sprd_thermal_sensor {
+ struct thermal_zone_device *tzd;
+ struct sprd_thermal_data *data;
+ struct device *dev;
+ int cal_slope;
+ int cal_offset;
+ int id;
+};
+
+struct sprd_thermal_data {
+ const struct sprd_thm_variant_data *var_data;
+ struct sprd_thermal_sensor *sensor[SPRD_THM_MAX_SENSOR];
+ struct clk *clk;
+ void __iomem *base;
+ u32 ratio_off;
+ int ratio_sign;
+ int nr_sensors;
+};
+
+/*
+ * The conversion between ADC and temperature is based on linear relationship,
+ * and use idea_k to specify the slope and ideal_b to specify the offset.
+ *
+ * Since different Spreadtrum SoCs have different ideal_k and ideal_b,
+ * we should save ideal_k and ideal_b in the device data structure.
+ */
+struct sprd_thm_variant_data {
+ u32 ideal_k;
+ u32 ideal_b;
+};
+
+static const struct sprd_thm_variant_data ums512_data = {
+ .ideal_k = 262,
+ .ideal_b = 66400,
+};
+
+static inline void sprd_thm_update_bits(void __iomem *reg, u32 mask, u32 val)
+{
+ u32 tmp, orig;
+
+ orig = readl(reg);
+ tmp = orig & ~mask;
+ tmp |= val & mask;
+ writel(tmp, reg);
+}
+
+static int sprd_thm_cal_read(struct device_node *np, const char *cell_id,
+ u32 *val)
+{
+ struct nvmem_cell *cell;
+ void *buf;
+ size_t len;
+
+ cell = of_nvmem_cell_get(np, cell_id);
+ if (IS_ERR(cell))
+ return PTR_ERR(cell);
+
+ buf = nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ if (len > sizeof(u32)) {
+ kfree(buf);
+ return -EINVAL;
+ }
+
+ memcpy(val, buf, len);
+
+ kfree(buf);
+ return 0;
+}
+
+static int sprd_thm_sensor_calibration(struct device_node *np,
+ struct sprd_thermal_data *thm,
+ struct sprd_thermal_sensor *sen)
+{
+ int ret;
+ /*
+ * According to thermal datasheet, the default calibration offset is 64,
+ * and the default ratio is 1000.
+ */
+ int dt_offset = 64, ratio = 1000;
+
+ ret = sprd_thm_cal_read(np, "sen_delta_cal", &dt_offset);
+ if (ret)
+ return ret;
+
+ ratio += thm->ratio_sign * thm->ratio_off;
+
+ /*
+ * According to the ideal slope K and ideal offset B, combined with
+ * calibration value of thermal from efuse, then calibrate the real
+ * slope k and offset b:
+ * k_cal = (k * ratio) / 1000.
+ * b_cal = b + (dt_offset - 64) * 500.
+ */
+ sen->cal_slope = (thm->var_data->ideal_k * ratio) / 1000;
+ sen->cal_offset = thm->var_data->ideal_b + (dt_offset - 128) * 250;
+
+ return 0;
+}
+
+static int sprd_thm_rawdata_to_temp(struct sprd_thermal_sensor *sen,
+ u32 rawdata)
+{
+ rawdata = clamp(rawdata, SPRD_THM_RAW_DATA_LOW, SPRD_THM_RAW_DATA_HIGH);
+
+ /*
+ * According to the thermal datasheet, the formula of converting
+ * adc value to the temperature value should be:
+ * T_final = k_cal * x - b_cal.
+ */
+ return sen->cal_slope * rawdata - sen->cal_offset;
+}
+
+static int sprd_thm_temp_to_rawdata(int temp, struct sprd_thermal_sensor *sen)
+{
+ u32 val;
+
+ temp = clamp(temp, SPRD_THM_TEMP_LOW, SPRD_THM_TEMP_HIGH);
+
+ /*
+ * According to the thermal datasheet, the formula of converting
+ * adc value to the temperature value should be:
+ * T_final = k_cal * x - b_cal.
+ */
+ val = (temp + sen->cal_offset) / sen->cal_slope;
+
+ return min(val, SPRD_THM_RAW_DATA_HIGH - 1);
+}
+
+static int sprd_thm_read_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct sprd_thermal_sensor *sen = thermal_zone_device_priv(tz);
+ u32 data;
+
+ data = readl(sen->data->base + SPRD_THM_TEMP(sen->id)) &
+ SPRD_THM_RAW_READ_MSK;
+
+ *temp = sprd_thm_rawdata_to_temp(sen, data);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops sprd_thm_ops = {
+ .get_temp = sprd_thm_read_temp,
+};
+
+static int sprd_thm_poll_ready_status(struct sprd_thermal_data *thm)
+{
+ u32 val;
+ int ret;
+
+ /*
+ * Wait for thermal ready status before configuring thermal parameters.
+ */
+ ret = readl_poll_timeout(thm->base + SPRD_THM_CTL, val,
+ !(val & SPRD_THM_SET_RDY_ST),
+ SPRD_THM_RDYST_POLLING_TIME,
+ SPRD_THM_RDYST_TIMEOUT);
+ if (ret)
+ return ret;
+
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL, SPRD_THM_MON_EN,
+ SPRD_THM_MON_EN);
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL, SPRD_THM_SET_RDY,
+ SPRD_THM_SET_RDY);
+ return 0;
+}
+
+static int sprd_thm_wait_temp_ready(struct sprd_thermal_data *thm)
+{
+ u32 val;
+
+ /* Wait for first temperature data ready before reading temperature */
+ return readl_poll_timeout(thm->base + SPRD_THM_INTERNAL_STS1, val,
+ !(val & SPRD_THM_TEMPER_RDY),
+ SPRD_THM_TEMP_READY_POLL_TIME,
+ SPRD_THM_TEMP_READY_TIMEOUT);
+}
+
+static int sprd_thm_set_ready(struct sprd_thermal_data *thm)
+{
+ int ret;
+
+ ret = sprd_thm_poll_ready_status(thm);
+ if (ret)
+ return ret;
+
+ /*
+ * Clear interrupt status, enable thermal interrupt and enable thermal.
+ *
+ * The SPRD thermal controller integrates a hardware interrupt signal,
+ * which means if the temperature is overheat, it will generate an
+ * interrupt and notify the event to PMIC automatically to shutdown the
+ * system. So here we should enable the interrupt bits, though we have
+ * not registered an irq handler.
+ */
+ writel(SPRD_THM_INT_CLR_MASK, thm->base + SPRD_THM_INT_CLR);
+ sprd_thm_update_bits(thm->base + SPRD_THM_INT_EN,
+ SPRD_THM_BIT_INT_EN, SPRD_THM_BIT_INT_EN);
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_EN, SPRD_THM_EN);
+ return 0;
+}
+
+static void sprd_thm_sensor_init(struct sprd_thermal_data *thm,
+ struct sprd_thermal_sensor *sen)
+{
+ u32 otp_rawdata, hot_rawdata;
+
+ otp_rawdata = sprd_thm_temp_to_rawdata(SPRD_THM_OTP_TEMP, sen);
+ hot_rawdata = sprd_thm_temp_to_rawdata(SPRD_THM_HOT_TEMP, sen);
+
+ /* Enable the sensor' overheat temperature protection interrupt */
+ sprd_thm_update_bits(thm->base + SPRD_THM_INT_EN,
+ SPRD_THM_SEN_OVERHEAT_ALARM_EN(sen->id),
+ SPRD_THM_SEN_OVERHEAT_ALARM_EN(sen->id));
+
+ /* Set the sensor' overheat and hot threshold temperature */
+ sprd_thm_update_bits(thm->base + SPRD_THM_THRES(sen->id),
+ SPRD_THM_THRES_MASK,
+ (otp_rawdata << SPRD_THM_OTP_TRIP_SHIFT) |
+ hot_rawdata);
+
+ /* Enable the corresponding sensor */
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL, SPRD_THM_SEN(sen->id),
+ SPRD_THM_SEN(sen->id));
+}
+
+static void sprd_thm_para_config(struct sprd_thermal_data *thm)
+{
+ /* Set the period of two valid temperature detection action */
+ sprd_thm_update_bits(thm->base + SPRD_THM_DET_PERIOD,
+ SPRD_THM_DET_PERIOD_MASK, SPRD_THM_DET_PERIOD);
+
+ /* Set the sensors' monitor mode */
+ sprd_thm_update_bits(thm->base + SPRD_THM_MON_CTL,
+ SPRD_THM_MON_MODE_MASK, SPRD_THM_MON_MODE);
+
+ /* Set the sensors' monitor period */
+ sprd_thm_update_bits(thm->base + SPRD_THM_MON_PERIOD,
+ SPRD_THM_MON_PERIOD_MASK, SPRD_THM_MON_PERIOD);
+}
+
+static void sprd_thm_toggle_sensor(struct sprd_thermal_sensor *sen, bool on)
+{
+ struct thermal_zone_device *tzd = sen->tzd;
+
+ if (on)
+ thermal_zone_device_enable(tzd);
+ else
+ thermal_zone_device_disable(tzd);
+}
+
+static int sprd_thm_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *sen_child;
+ struct sprd_thermal_data *thm;
+ struct sprd_thermal_sensor *sen;
+ const struct sprd_thm_variant_data *pdata;
+ int ret, i;
+ u32 val;
+
+ pdata = of_device_get_match_data(&pdev->dev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "No matching driver data found\n");
+ return -EINVAL;
+ }
+
+ thm = devm_kzalloc(&pdev->dev, sizeof(*thm), GFP_KERNEL);
+ if (!thm)
+ return -ENOMEM;
+
+ thm->var_data = pdata;
+ thm->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(thm->base))
+ return PTR_ERR(thm->base);
+
+ thm->nr_sensors = of_get_child_count(np);
+ if (thm->nr_sensors == 0 || thm->nr_sensors > SPRD_THM_MAX_SENSOR) {
+ dev_err(&pdev->dev, "incorrect sensor count\n");
+ return -EINVAL;
+ }
+
+ thm->clk = devm_clk_get_enabled(&pdev->dev, "enable");
+ if (IS_ERR(thm->clk)) {
+ dev_err(&pdev->dev, "failed to get enable clock\n");
+ return PTR_ERR(thm->clk);
+ }
+
+ sprd_thm_para_config(thm);
+
+ ret = sprd_thm_cal_read(np, "thm_sign_cal", &val);
+ if (ret)
+ return ret;
+
+ if (val > 0)
+ thm->ratio_sign = -1;
+ else
+ thm->ratio_sign = 1;
+
+ ret = sprd_thm_cal_read(np, "thm_ratio_cal", &thm->ratio_off);
+ if (ret)
+ return ret;
+
+ for_each_child_of_node(np, sen_child) {
+ sen = devm_kzalloc(&pdev->dev, sizeof(*sen), GFP_KERNEL);
+ if (!sen) {
+ ret = -ENOMEM;
+ goto of_put;
+ }
+
+ sen->data = thm;
+ sen->dev = &pdev->dev;
+
+ ret = of_property_read_u32(sen_child, "reg", &sen->id);
+ if (ret) {
+ dev_err(&pdev->dev, "get sensor reg failed");
+ goto of_put;
+ }
+
+ ret = sprd_thm_sensor_calibration(sen_child, thm, sen);
+ if (ret) {
+ dev_err(&pdev->dev, "efuse cal analysis failed");
+ goto of_put;
+ }
+
+ sprd_thm_sensor_init(thm, sen);
+
+ sen->tzd = devm_thermal_of_zone_register(sen->dev,
+ sen->id,
+ sen,
+ &sprd_thm_ops);
+ if (IS_ERR(sen->tzd)) {
+ dev_err(&pdev->dev, "register thermal zone failed %d\n",
+ sen->id);
+ ret = PTR_ERR(sen->tzd);
+ goto of_put;
+ }
+
+ thm->sensor[sen->id] = sen;
+ }
+ /* sen_child set to NULL at this point */
+
+ ret = sprd_thm_set_ready(thm);
+ if (ret)
+ goto of_put;
+
+ ret = sprd_thm_wait_temp_ready(thm);
+ if (ret)
+ goto of_put;
+
+ for (i = 0; i < thm->nr_sensors; i++)
+ sprd_thm_toggle_sensor(thm->sensor[i], true);
+
+ platform_set_drvdata(pdev, thm);
+ return 0;
+
+of_put:
+ of_node_put(sen_child);
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void sprd_thm_hw_suspend(struct sprd_thermal_data *thm)
+{
+ int i;
+
+ for (i = 0; i < thm->nr_sensors; i++) {
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_SEN(thm->sensor[i]->id), 0);
+ }
+
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_EN, 0x0);
+}
+
+static int sprd_thm_suspend(struct device *dev)
+{
+ struct sprd_thermal_data *thm = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < thm->nr_sensors; i++)
+ sprd_thm_toggle_sensor(thm->sensor[i], false);
+
+ sprd_thm_hw_suspend(thm);
+ clk_disable_unprepare(thm->clk);
+
+ return 0;
+}
+
+static int sprd_thm_hw_resume(struct sprd_thermal_data *thm)
+{
+ int ret, i;
+
+ for (i = 0; i < thm->nr_sensors; i++) {
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_SEN(thm->sensor[i]->id),
+ SPRD_THM_SEN(thm->sensor[i]->id));
+ }
+
+ ret = sprd_thm_poll_ready_status(thm);
+ if (ret)
+ return ret;
+
+ writel(SPRD_THM_INT_CLR_MASK, thm->base + SPRD_THM_INT_CLR);
+ sprd_thm_update_bits(thm->base + SPRD_THM_CTL,
+ SPRD_THM_EN, SPRD_THM_EN);
+ return sprd_thm_wait_temp_ready(thm);
+}
+
+static int sprd_thm_resume(struct device *dev)
+{
+ struct sprd_thermal_data *thm = dev_get_drvdata(dev);
+ int ret, i;
+
+ ret = clk_prepare_enable(thm->clk);
+ if (ret)
+ return ret;
+
+ ret = sprd_thm_hw_resume(thm);
+ if (ret)
+ goto disable_clk;
+
+ for (i = 0; i < thm->nr_sensors; i++)
+ sprd_thm_toggle_sensor(thm->sensor[i], true);
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(thm->clk);
+ return ret;
+}
+#endif
+
+static void sprd_thm_remove(struct platform_device *pdev)
+{
+ struct sprd_thermal_data *thm = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < thm->nr_sensors; i++) {
+ sprd_thm_toggle_sensor(thm->sensor[i], false);
+ devm_thermal_of_zone_unregister(&pdev->dev,
+ thm->sensor[i]->tzd);
+ }
+}
+
+static const struct of_device_id sprd_thermal_of_match[] = {
+ { .compatible = "sprd,ums512-thermal", .data = &ums512_data },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sprd_thermal_of_match);
+
+static const struct dev_pm_ops sprd_thermal_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(sprd_thm_suspend, sprd_thm_resume)
+};
+
+static struct platform_driver sprd_thermal_driver = {
+ .probe = sprd_thm_probe,
+ .remove = sprd_thm_remove,
+ .driver = {
+ .name = "sprd-thermal",
+ .pm = &sprd_thermal_pm_ops,
+ .of_match_table = sprd_thermal_of_match,
+ },
+};
+
+module_platform_driver(sprd_thermal_driver);
+
+MODULE_AUTHOR("Freeman Liu <freeman.liu@unisoc.com>");
+MODULE_DESCRIPTION("Spreadtrum thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/st/Kconfig b/drivers/thermal/st/Kconfig
new file mode 100644
index 000000000..ecbdf4ef0
--- /dev/null
+++ b/drivers/thermal/st/Kconfig
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# STMicroelectronics thermal drivers configuration
+#
+
+config ST_THERMAL
+ tristate "Thermal sensors on STMicroelectronics STi series of SoCs"
+ help
+ Support for thermal sensors on STMicroelectronics STi series of SoCs.
+
+config ST_THERMAL_MEMMAP
+ select ST_THERMAL
+ tristate "STi series memory mapped access based thermal sensors"
+
+config STM32_THERMAL
+ tristate "Thermal framework support on STMicroelectronics STM32 series of SoCs"
+ depends on MACH_STM32MP157
+ default y
+ help
+ Support for thermal framework on STMicroelectronics STM32 series of
+ SoCs. This thermal driver allows to access to general thermal framework
+ functionalities and to access to SoC sensor functionalities. This
+ configuration is fully dependent of MACH_STM32MP157.
diff --git a/drivers/thermal/st/Makefile b/drivers/thermal/st/Makefile
new file mode 100644
index 000000000..9bb0342b7
--- /dev/null
+++ b/drivers/thermal/st/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_ST_THERMAL) := st_thermal.o
+obj-$(CONFIG_ST_THERMAL_MEMMAP) += st_thermal_memmap.o
+obj-$(CONFIG_STM32_THERMAL) += stm_thermal.o
diff --git a/drivers/thermal/st/st_thermal.c b/drivers/thermal/st/st_thermal.c
new file mode 100644
index 000000000..1470ca519
--- /dev/null
+++ b/drivers/thermal/st/st_thermal.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ST Thermal Sensor Driver core routines
+ * Author: Ajit Pal Singh <ajitpal.singh@st.com>
+ *
+ * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include "st_thermal.h"
+#include "../thermal_hwmon.h"
+
+/* The Thermal Framework expects millidegrees */
+#define mcelsius(temp) ((temp) * 1000)
+
+/*
+ * Function to allocate regfields which are common
+ * between syscfg and memory mapped based sensors
+ */
+static int st_thermal_alloc_regfields(struct st_thermal_sensor *sensor)
+{
+ struct device *dev = sensor->dev;
+ struct regmap *regmap = sensor->regmap;
+ const struct reg_field *reg_fields = sensor->cdata->reg_fields;
+
+ sensor->dcorrect = devm_regmap_field_alloc(dev, regmap,
+ reg_fields[DCORRECT]);
+
+ sensor->overflow = devm_regmap_field_alloc(dev, regmap,
+ reg_fields[OVERFLOW]);
+
+ sensor->temp_data = devm_regmap_field_alloc(dev, regmap,
+ reg_fields[DATA]);
+
+ if (IS_ERR(sensor->dcorrect) ||
+ IS_ERR(sensor->overflow) ||
+ IS_ERR(sensor->temp_data)) {
+ dev_err(dev, "failed to allocate common regfields\n");
+ return -EINVAL;
+ }
+
+ return sensor->ops->alloc_regfields(sensor);
+}
+
+static int st_thermal_sensor_on(struct st_thermal_sensor *sensor)
+{
+ int ret;
+ struct device *dev = sensor->dev;
+
+ ret = clk_prepare_enable(sensor->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable clk\n");
+ return ret;
+ }
+
+ ret = sensor->ops->power_ctrl(sensor, POWER_ON);
+ if (ret) {
+ dev_err(dev, "failed to power on sensor\n");
+ clk_disable_unprepare(sensor->clk);
+ }
+
+ return ret;
+}
+
+static int st_thermal_sensor_off(struct st_thermal_sensor *sensor)
+{
+ int ret;
+
+ ret = sensor->ops->power_ctrl(sensor, POWER_OFF);
+ if (ret)
+ return ret;
+
+ clk_disable_unprepare(sensor->clk);
+
+ return 0;
+}
+
+static int st_thermal_calibration(struct st_thermal_sensor *sensor)
+{
+ int ret;
+ unsigned int val;
+ struct device *dev = sensor->dev;
+
+ /* Check if sensor calibration data is already written */
+ ret = regmap_field_read(sensor->dcorrect, &val);
+ if (ret) {
+ dev_err(dev, "failed to read calibration data\n");
+ return ret;
+ }
+
+ if (!val) {
+ /*
+ * Sensor calibration value not set by bootloader,
+ * default calibration data to be used
+ */
+ ret = regmap_field_write(sensor->dcorrect,
+ sensor->cdata->calibration_val);
+ if (ret)
+ dev_err(dev, "failed to set calibration data\n");
+ }
+
+ return ret;
+}
+
+/* Callback to get temperature from HW*/
+static int st_thermal_get_temp(struct thermal_zone_device *th, int *temperature)
+{
+ struct st_thermal_sensor *sensor = thermal_zone_device_priv(th);
+ unsigned int temp;
+ unsigned int overflow;
+ int ret;
+
+ ret = regmap_field_read(sensor->overflow, &overflow);
+ if (ret)
+ return ret;
+ if (overflow)
+ return -EIO;
+
+ ret = regmap_field_read(sensor->temp_data, &temp);
+ if (ret)
+ return ret;
+
+ temp += sensor->cdata->temp_adjust_val;
+ temp = mcelsius(temp);
+
+ *temperature = temp;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops st_tz_ops = {
+ .get_temp = st_thermal_get_temp,
+};
+
+int st_thermal_register(struct platform_device *pdev,
+ const struct of_device_id *st_thermal_of_match)
+{
+ struct st_thermal_sensor *sensor;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct of_device_id *match;
+
+ int ret;
+
+ if (!np) {
+ dev_err(dev, "device tree node not found\n");
+ return -EINVAL;
+ }
+
+ sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ sensor->dev = dev;
+
+ match = of_match_device(st_thermal_of_match, dev);
+ if (!(match && match->data))
+ return -EINVAL;
+
+ sensor->cdata = match->data;
+ if (!sensor->cdata->ops)
+ return -EINVAL;
+
+ sensor->ops = sensor->cdata->ops;
+
+ ret = (sensor->ops->regmap_init)(sensor);
+ if (ret)
+ return ret;
+
+ ret = st_thermal_alloc_regfields(sensor);
+ if (ret)
+ return ret;
+
+ sensor->clk = devm_clk_get(dev, "thermal");
+ if (IS_ERR(sensor->clk)) {
+ dev_err(dev, "failed to fetch clock\n");
+ return PTR_ERR(sensor->clk);
+ }
+
+ if (sensor->ops->register_enable_irq) {
+ ret = sensor->ops->register_enable_irq(sensor);
+ if (ret)
+ return ret;
+ }
+
+ ret = st_thermal_sensor_on(sensor);
+ if (ret)
+ return ret;
+
+ ret = st_thermal_calibration(sensor);
+ if (ret)
+ goto sensor_off;
+
+ sensor->thermal_dev =
+ devm_thermal_of_zone_register(dev, 0, sensor, &st_tz_ops);
+ if (IS_ERR(sensor->thermal_dev)) {
+ dev_err(dev, "failed to register thermal of zone\n");
+ ret = PTR_ERR(sensor->thermal_dev);
+ goto sensor_off;
+ }
+
+ 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;
+
+sensor_off:
+ st_thermal_sensor_off(sensor);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(st_thermal_register);
+
+void st_thermal_unregister(struct platform_device *pdev)
+{
+ struct st_thermal_sensor *sensor = platform_get_drvdata(pdev);
+
+ st_thermal_sensor_off(sensor);
+ thermal_remove_hwmon_sysfs(sensor->thermal_dev);
+ devm_thermal_of_zone_unregister(sensor->dev, sensor->thermal_dev);
+}
+EXPORT_SYMBOL_GPL(st_thermal_unregister);
+
+static int st_thermal_suspend(struct device *dev)
+{
+ struct st_thermal_sensor *sensor = dev_get_drvdata(dev);
+
+ return st_thermal_sensor_off(sensor);
+}
+
+static int st_thermal_resume(struct device *dev)
+{
+ int ret;
+ struct st_thermal_sensor *sensor = dev_get_drvdata(dev);
+
+ ret = st_thermal_sensor_on(sensor);
+ if (ret)
+ return ret;
+
+ ret = st_thermal_calibration(sensor);
+ if (ret)
+ return ret;
+
+ if (sensor->ops->enable_irq) {
+ ret = sensor->ops->enable_irq(sensor);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+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>");
+MODULE_DESCRIPTION("STMicroelectronics STi SoC Thermal Sensor Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/st/st_thermal.h b/drivers/thermal/st/st_thermal.h
new file mode 100644
index 000000000..8639d9165
--- /dev/null
+++ b/drivers/thermal/st/st_thermal.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * ST Thermal Sensor Driver for STi series of SoCs
+ * Author: Ajit Pal Singh <ajitpal.singh@st.com>
+ *
+ * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited
+ */
+
+#ifndef __STI_THERMAL_SYSCFG_H
+#define __STI_THERMAL_SYSCFG_H
+
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+enum st_thermal_regfield_ids {
+ INT_THRESH_HI = 0, /* Top two regfield IDs are mutually exclusive */
+ TEMP_PWR = 0,
+ DCORRECT,
+ OVERFLOW,
+ DATA,
+ INT_ENABLE,
+
+ MAX_REGFIELDS
+};
+
+/* Thermal sensor power states */
+enum st_thermal_power_state {
+ POWER_OFF = 0,
+ POWER_ON
+};
+
+struct st_thermal_sensor;
+
+/**
+ * Description of private thermal sensor ops.
+ *
+ * @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
+ * instance or find regmap instance.
+ * @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);
+ int (*alloc_regfields)(struct st_thermal_sensor *);
+ int (*regmap_init)(struct st_thermal_sensor *);
+ int (*register_enable_irq)(struct st_thermal_sensor *);
+ int (*enable_irq)(struct st_thermal_sensor *);
+};
+
+/**
+ * Description of thermal driver compatible data.
+ *
+ * @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
+ * register field for a sensor.
+ * @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.
+ */
+struct st_thermal_compat_data {
+ char *sys_compat;
+ const struct reg_field *reg_fields;
+ const struct st_thermal_sensor_ops *ops;
+ unsigned int calibration_val;
+ int temp_adjust_val;
+ int crit_temp;
+};
+
+struct st_thermal_sensor {
+ struct device *dev;
+ struct thermal_zone_device *thermal_dev;
+ const struct st_thermal_sensor_ops *ops;
+ const struct st_thermal_compat_data *cdata;
+ struct clk *clk;
+ struct regmap *regmap;
+ struct regmap_field *pwr;
+ struct regmap_field *dcorrect;
+ struct regmap_field *overflow;
+ struct regmap_field *temp_data;
+ struct regmap_field *int_thresh_hi;
+ struct regmap_field *int_enable;
+ int irq;
+ void __iomem *mmio_base;
+};
+
+extern int st_thermal_register(struct platform_device *pdev,
+ const struct of_device_id *st_thermal_of_match);
+extern void st_thermal_unregister(struct platform_device *pdev);
+extern const struct dev_pm_ops st_thermal_pm_ops;
+
+#endif /* __STI_RESET_SYSCFG_H */
diff --git a/drivers/thermal/st/st_thermal_memmap.c b/drivers/thermal/st/st_thermal_memmap.c
new file mode 100644
index 000000000..e3dbe4df8
--- /dev/null
+++ b/drivers/thermal/st/st_thermal_memmap.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ST Thermal Sensor Driver for memory mapped sensors.
+ * Author: Ajit Pal Singh <ajitpal.singh@st.com>
+ *
+ * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited
+ */
+
+#include <linux/of.h>
+#include <linux/module.h>
+
+#include "st_thermal.h"
+
+#define STIH416_MPE_CONF 0x0
+#define STIH416_MPE_STATUS 0x4
+#define STIH416_MPE_INT_THRESH 0x8
+#define STIH416_MPE_INT_EN 0xC
+
+/* Power control bits for the memory mapped thermal sensor */
+#define THERMAL_PDN BIT(4)
+#define THERMAL_SRSTN BIT(10)
+
+static const struct reg_field st_mmap_thermal_regfields[MAX_REGFIELDS] = {
+ /*
+ * According to the STIH416 MPE temp sensor data sheet -
+ * the PDN (Power Down Bit) and SRSTN (Soft Reset Bit) need to be
+ * 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),
+ [DCORRECT] = REG_FIELD(STIH416_MPE_CONF, 5, 9),
+ [OVERFLOW] = REG_FIELD(STIH416_MPE_STATUS, 9, 9),
+ [DATA] = REG_FIELD(STIH416_MPE_STATUS, 11, 18),
+ [INT_ENABLE] = REG_FIELD(STIH416_MPE_INT_EN, 0, 0),
+};
+
+static irqreturn_t st_mmap_thermal_trip_handler(int irq, void *sdata)
+{
+ struct st_thermal_sensor *sensor = sdata;
+
+ thermal_zone_device_update(sensor->thermal_dev,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+/* Private ops for the Memory Mapped based thermal sensors */
+static int st_mmap_power_ctrl(struct st_thermal_sensor *sensor,
+ enum st_thermal_power_state power_state)
+{
+ const unsigned int mask = (THERMAL_PDN | THERMAL_SRSTN);
+ const unsigned int val = power_state ? mask : 0;
+
+ return regmap_update_bits(sensor->regmap, STIH416_MPE_CONF, mask, val);
+}
+
+static int st_mmap_alloc_regfields(struct st_thermal_sensor *sensor)
+{
+ struct device *dev = sensor->dev;
+ struct regmap *regmap = sensor->regmap;
+ const struct reg_field *reg_fields = sensor->cdata->reg_fields;
+
+ sensor->int_thresh_hi = devm_regmap_field_alloc(dev, regmap,
+ reg_fields[INT_THRESH_HI]);
+ sensor->int_enable = devm_regmap_field_alloc(dev, regmap,
+ reg_fields[INT_ENABLE]);
+
+ if (IS_ERR(sensor->int_thresh_hi) || IS_ERR(sensor->int_enable)) {
+ dev_err(dev, "failed to alloc mmap regfields\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int st_mmap_enable_irq(struct st_thermal_sensor *sensor)
+{
+ int ret;
+
+ /* Set upper critical threshold */
+ ret = regmap_field_write(sensor->int_thresh_hi,
+ sensor->cdata->crit_temp -
+ sensor->cdata->temp_adjust_val);
+ if (ret)
+ return ret;
+
+ return regmap_field_write(sensor->int_enable, 1);
+}
+
+static int st_mmap_register_enable_irq(struct st_thermal_sensor *sensor)
+{
+ struct device *dev = sensor->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ int ret;
+
+ sensor->irq = platform_get_irq(pdev, 0);
+ if (sensor->irq < 0)
+ return sensor->irq;
+
+ ret = devm_request_threaded_irq(dev, sensor->irq,
+ NULL, st_mmap_thermal_trip_handler,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ dev->driver->name, sensor);
+ if (ret)
+ return ret;
+
+ return st_mmap_enable_irq(sensor);
+}
+
+static const struct regmap_config st_416mpe_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int st_mmap_regmap_init(struct st_thermal_sensor *sensor)
+{
+ struct device *dev = sensor->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+
+ sensor->mmio_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(sensor->mmio_base))
+ return PTR_ERR(sensor->mmio_base);
+
+ sensor->regmap = devm_regmap_init_mmio(dev, sensor->mmio_base,
+ &st_416mpe_regmap_config);
+ if (IS_ERR(sensor->regmap)) {
+ dev_err(dev, "failed to initialise regmap\n");
+ return PTR_ERR(sensor->regmap);
+ }
+
+ return 0;
+}
+
+static const struct st_thermal_sensor_ops st_mmap_sensor_ops = {
+ .power_ctrl = st_mmap_power_ctrl,
+ .alloc_regfields = st_mmap_alloc_regfields,
+ .regmap_init = st_mmap_regmap_init,
+ .register_enable_irq = st_mmap_register_enable_irq,
+ .enable_irq = st_mmap_enable_irq,
+};
+
+/* Compatible device data stih407 thermal sensor */
+static const struct st_thermal_compat_data st_407_cdata = {
+ .reg_fields = st_mmap_thermal_regfields,
+ .ops = &st_mmap_sensor_ops,
+ .calibration_val = 16,
+ .temp_adjust_val = -95,
+ .crit_temp = 120,
+};
+
+static const struct of_device_id st_mmap_thermal_of_match[] = {
+ { .compatible = "st,stih407-thermal", .data = &st_407_cdata },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, st_mmap_thermal_of_match);
+
+static int st_mmap_probe(struct platform_device *pdev)
+{
+ return st_thermal_register(pdev, st_mmap_thermal_of_match);
+}
+
+static void st_mmap_remove(struct platform_device *pdev)
+{
+ st_thermal_unregister(pdev);
+}
+
+static struct platform_driver st_mmap_thermal_driver = {
+ .driver = {
+ .name = "st_thermal_mmap",
+ .pm = pm_sleep_ptr(&st_thermal_pm_ops),
+ .of_match_table = st_mmap_thermal_of_match,
+ },
+ .probe = st_mmap_probe,
+ .remove = st_mmap_remove,
+};
+
+module_platform_driver(st_mmap_thermal_driver);
+
+MODULE_AUTHOR("STMicroelectronics (R&D) Limited <ajitpal.singh@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics STi SoC Thermal Sensor Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/st/stm_thermal.c b/drivers/thermal/st/stm_thermal.c
new file mode 100644
index 000000000..3290da7ab
--- /dev/null
+++ b/drivers/thermal/st/stm_thermal.c
@@ -0,0 +1,589 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: David Hernandez Sanchez <david.hernandezsanchez@st.com> for
+ * STMicroelectronics.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "../thermal_hwmon.h"
+
+/* DTS register offsets */
+#define DTS_CFGR1_OFFSET 0x0
+#define DTS_T0VALR1_OFFSET 0x8
+#define DTS_RAMPVALR_OFFSET 0X10
+#define DTS_ITR1_OFFSET 0x14
+#define DTS_DR_OFFSET 0x1C
+#define DTS_SR_OFFSET 0x20
+#define DTS_ITENR_OFFSET 0x24
+#define DTS_ICIFR_OFFSET 0x28
+
+/* DTS_CFGR1 register mask definitions */
+#define HSREF_CLK_DIV_MASK GENMASK(30, 24)
+#define TS1_SMP_TIME_MASK GENMASK(19, 16)
+#define TS1_INTRIG_SEL_MASK GENMASK(11, 8)
+
+/* DTS_T0VALR1 register mask definitions */
+#define TS1_T0_MASK GENMASK(17, 16)
+#define TS1_FMT0_MASK GENMASK(15, 0)
+
+/* DTS_RAMPVALR register mask definitions */
+#define TS1_RAMP_COEFF_MASK GENMASK(15, 0)
+
+/* DTS_ITR1 register mask definitions */
+#define TS1_HITTHD_MASK GENMASK(31, 16)
+#define TS1_LITTHD_MASK GENMASK(15, 0)
+
+/* DTS_DR register mask definitions */
+#define TS1_MFREQ_MASK GENMASK(15, 0)
+
+/* DTS_ITENR register mask definitions */
+#define ITENR_MASK (GENMASK(2, 0) | GENMASK(6, 4))
+
+/* DTS_ICIFR register mask definitions */
+#define ICIFR_MASK (GENMASK(2, 0) | GENMASK(6, 4))
+
+/* Less significant bit position definitions */
+#define TS1_T0_POS 16
+#define TS1_HITTHD_POS 16
+#define TS1_LITTHD_POS 0
+#define HSREF_CLK_DIV_POS 24
+
+/* DTS_CFGR1 bit definitions */
+#define TS1_EN BIT(0)
+#define TS1_START BIT(4)
+#define REFCLK_SEL BIT(20)
+#define REFCLK_LSE REFCLK_SEL
+#define Q_MEAS_OPT BIT(21)
+#define CALIBRATION_CONTROL Q_MEAS_OPT
+
+/* DTS_SR bit definitions */
+#define TS_RDY BIT(15)
+/* Bit definitions below are common for DTS_SR, DTS_ITENR and DTS_CIFR */
+#define HIGH_THRESHOLD BIT(2)
+#define LOW_THRESHOLD BIT(1)
+
+/* Constants */
+#define ADJUST 100
+#define POLL_TIMEOUT 5000
+#define STARTUP_TIME 40
+#define TS1_T0_VAL0 30000 /* 30 celsius */
+#define TS1_T0_VAL1 130000 /* 130 celsius */
+#define NO_HW_TRIG 0
+#define SAMPLING_TIME 15
+
+struct stm_thermal_sensor {
+ struct device *dev;
+ struct thermal_zone_device *th_dev;
+ enum thermal_device_mode mode;
+ struct clk *clk;
+ unsigned int low_temp_enabled;
+ unsigned int high_temp_enabled;
+ int irq;
+ void __iomem *base;
+ int t0, fmt0, ramp_coeff;
+};
+
+static int stm_enable_irq(struct stm_thermal_sensor *sensor)
+{
+ u32 value;
+
+ dev_dbg(sensor->dev, "low:%d high:%d\n", sensor->low_temp_enabled,
+ sensor->high_temp_enabled);
+
+ /* Disable IT generation for low and high thresholds */
+ value = readl_relaxed(sensor->base + DTS_ITENR_OFFSET);
+ value &= ~(LOW_THRESHOLD | HIGH_THRESHOLD);
+
+ if (sensor->low_temp_enabled)
+ value |= HIGH_THRESHOLD;
+
+ if (sensor->high_temp_enabled)
+ value |= LOW_THRESHOLD;
+
+ /* Enable interrupts */
+ writel_relaxed(value, sensor->base + DTS_ITENR_OFFSET);
+
+ return 0;
+}
+
+static irqreturn_t stm_thermal_irq_handler(int irq, void *sdata)
+{
+ struct stm_thermal_sensor *sensor = sdata;
+
+ dev_dbg(sensor->dev, "sr:%d\n",
+ readl_relaxed(sensor->base + DTS_SR_OFFSET));
+
+ thermal_zone_device_update(sensor->th_dev, THERMAL_EVENT_UNSPECIFIED);
+
+ stm_enable_irq(sensor);
+
+ /* Acknoledge all DTS irqs */
+ writel_relaxed(ICIFR_MASK, sensor->base + DTS_ICIFR_OFFSET);
+
+ return IRQ_HANDLED;
+}
+
+static int stm_sensor_power_on(struct stm_thermal_sensor *sensor)
+{
+ int ret;
+ u32 value;
+
+ /* Enable sensor */
+ value = readl_relaxed(sensor->base + DTS_CFGR1_OFFSET);
+ value |= TS1_EN;
+ writel_relaxed(value, sensor->base + DTS_CFGR1_OFFSET);
+
+ /*
+ * The DTS block can be enabled by setting TSx_EN bit in
+ * DTS_CFGRx register. It requires a startup time of
+ * 40μs. Use 5 ms as arbitrary timeout.
+ */
+ ret = readl_poll_timeout(sensor->base + DTS_SR_OFFSET,
+ value, (value & TS_RDY),
+ STARTUP_TIME, POLL_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Start continuous measuring */
+ value = readl_relaxed(sensor->base +
+ DTS_CFGR1_OFFSET);
+ value |= TS1_START;
+ writel_relaxed(value, sensor->base +
+ DTS_CFGR1_OFFSET);
+
+ sensor->mode = THERMAL_DEVICE_ENABLED;
+
+ return 0;
+}
+
+static int stm_sensor_power_off(struct stm_thermal_sensor *sensor)
+{
+ u32 value;
+
+ sensor->mode = THERMAL_DEVICE_DISABLED;
+
+ /* Stop measuring */
+ value = readl_relaxed(sensor->base + DTS_CFGR1_OFFSET);
+ value &= ~TS1_START;
+ writel_relaxed(value, sensor->base + DTS_CFGR1_OFFSET);
+
+ /* Ensure stop is taken into account */
+ usleep_range(STARTUP_TIME, POLL_TIMEOUT);
+
+ /* Disable sensor */
+ value = readl_relaxed(sensor->base + DTS_CFGR1_OFFSET);
+ value &= ~TS1_EN;
+ writel_relaxed(value, sensor->base + DTS_CFGR1_OFFSET);
+
+ /* Ensure disable is taken into account */
+ return readl_poll_timeout(sensor->base + DTS_SR_OFFSET, value,
+ !(value & TS_RDY),
+ STARTUP_TIME, POLL_TIMEOUT);
+}
+
+static int stm_thermal_calibration(struct stm_thermal_sensor *sensor)
+{
+ u32 value, clk_freq;
+ u32 prescaler;
+
+ /* Figure out prescaler value for PCLK during calibration */
+ clk_freq = clk_get_rate(sensor->clk);
+ if (!clk_freq)
+ return -EINVAL;
+
+ prescaler = 0;
+ clk_freq /= HZ_PER_MHZ;
+ if (clk_freq) {
+ while (prescaler <= clk_freq)
+ prescaler++;
+ }
+
+ value = readl_relaxed(sensor->base + DTS_CFGR1_OFFSET);
+
+ /* Clear prescaler */
+ value &= ~HSREF_CLK_DIV_MASK;
+
+ /* Set prescaler. pclk_freq/prescaler < 1MHz */
+ value |= (prescaler << HSREF_CLK_DIV_POS);
+
+ /* Select PCLK as reference clock */
+ value &= ~REFCLK_SEL;
+
+ /* Set maximal sampling time for better precision */
+ value |= TS1_SMP_TIME_MASK;
+
+ /* Measure with calibration */
+ value &= ~CALIBRATION_CONTROL;
+
+ /* select trigger */
+ value &= ~TS1_INTRIG_SEL_MASK;
+ value |= NO_HW_TRIG;
+
+ writel_relaxed(value, sensor->base + DTS_CFGR1_OFFSET);
+
+ return 0;
+}
+
+/* Fill in DTS structure with factory sensor values */
+static int stm_thermal_read_factory_settings(struct stm_thermal_sensor *sensor)
+{
+ /* Retrieve engineering calibration temperature */
+ sensor->t0 = readl_relaxed(sensor->base + DTS_T0VALR1_OFFSET) &
+ TS1_T0_MASK;
+ if (!sensor->t0)
+ sensor->t0 = TS1_T0_VAL0;
+ else
+ sensor->t0 = TS1_T0_VAL1;
+
+ /* Retrieve fmt0 and put it on Hz */
+ sensor->fmt0 = ADJUST * (readl_relaxed(sensor->base +
+ DTS_T0VALR1_OFFSET) & TS1_FMT0_MASK);
+
+ /* Retrieve ramp coefficient */
+ sensor->ramp_coeff = readl_relaxed(sensor->base + DTS_RAMPVALR_OFFSET) &
+ TS1_RAMP_COEFF_MASK;
+
+ if (!sensor->fmt0 || !sensor->ramp_coeff) {
+ dev_err(sensor->dev, "%s: wrong setting\n", __func__);
+ return -EINVAL;
+ }
+
+ dev_dbg(sensor->dev, "%s: T0 = %doC, FMT0 = %dHz, RAMP_COEFF = %dHz/oC",
+ __func__, sensor->t0, sensor->fmt0, sensor->ramp_coeff);
+
+ return 0;
+}
+
+static int stm_thermal_calculate_threshold(struct stm_thermal_sensor *sensor,
+ int temp, u32 *th)
+{
+ int freqM;
+
+ /* Figure out the CLK_PTAT frequency for a given temperature */
+ freqM = ((temp - sensor->t0) * sensor->ramp_coeff) / 1000 +
+ sensor->fmt0;
+
+ /* Figure out the threshold sample number */
+ *th = clk_get_rate(sensor->clk) * SAMPLING_TIME / freqM;
+ if (!*th)
+ return -EINVAL;
+
+ dev_dbg(sensor->dev, "freqM=%d Hz, threshold=0x%x", freqM, *th);
+
+ return 0;
+}
+
+/* Disable temperature interrupt */
+static int stm_disable_irq(struct stm_thermal_sensor *sensor)
+{
+ u32 value;
+
+ /* Disable IT generation */
+ value = readl_relaxed(sensor->base + DTS_ITENR_OFFSET);
+ value &= ~ITENR_MASK;
+ writel_relaxed(value, sensor->base + DTS_ITENR_OFFSET);
+
+ return 0;
+}
+
+static int stm_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct stm_thermal_sensor *sensor = thermal_zone_device_priv(tz);
+ u32 itr1, th;
+ int ret;
+
+ dev_dbg(sensor->dev, "set trips %d <--> %d\n", low, high);
+
+ /* Erase threshold content */
+ itr1 = readl_relaxed(sensor->base + DTS_ITR1_OFFSET);
+ itr1 &= ~(TS1_LITTHD_MASK | TS1_HITTHD_MASK);
+
+ /*
+ * Disable low-temp if "low" is too small. As per thermal framework
+ * API, we use -INT_MAX rather than INT_MIN.
+ */
+
+ if (low > -INT_MAX) {
+ sensor->low_temp_enabled = 1;
+ /* add 0.5 of hysteresis due to measurement error */
+ ret = stm_thermal_calculate_threshold(sensor, low - 500, &th);
+ if (ret)
+ return ret;
+
+ itr1 |= (TS1_HITTHD_MASK & (th << TS1_HITTHD_POS));
+ } else {
+ sensor->low_temp_enabled = 0;
+ }
+
+ /* Disable high-temp if "high" is too big. */
+ if (high < INT_MAX) {
+ sensor->high_temp_enabled = 1;
+ ret = stm_thermal_calculate_threshold(sensor, high, &th);
+ if (ret)
+ return ret;
+
+ itr1 |= (TS1_LITTHD_MASK & (th << TS1_LITTHD_POS));
+ } else {
+ sensor->high_temp_enabled = 0;
+ }
+
+ /* Write new threshod values*/
+ writel_relaxed(itr1, sensor->base + DTS_ITR1_OFFSET);
+
+ return 0;
+}
+
+/* Callback to get temperature from HW */
+static int stm_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct stm_thermal_sensor *sensor = thermal_zone_device_priv(tz);
+ u32 periods;
+ int freqM, ret;
+
+ if (sensor->mode != THERMAL_DEVICE_ENABLED)
+ return -EAGAIN;
+
+ /* Retrieve the number of periods sampled */
+ ret = readl_relaxed_poll_timeout(sensor->base + DTS_DR_OFFSET, periods,
+ (periods & TS1_MFREQ_MASK),
+ STARTUP_TIME, POLL_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Figure out the CLK_PTAT frequency */
+ freqM = (clk_get_rate(sensor->clk) * SAMPLING_TIME) / periods;
+ if (!freqM)
+ return -EINVAL;
+
+ /* Figure out the temperature in mili celsius */
+ *temp = (freqM - sensor->fmt0) * 1000 / sensor->ramp_coeff + sensor->t0;
+
+ return 0;
+}
+
+/* Registers DTS irq to be visible by GIC */
+static int stm_register_irq(struct stm_thermal_sensor *sensor)
+{
+ struct device *dev = sensor->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ int ret;
+
+ sensor->irq = platform_get_irq(pdev, 0);
+ if (sensor->irq < 0)
+ return sensor->irq;
+
+ ret = devm_request_threaded_irq(dev, sensor->irq,
+ NULL,
+ stm_thermal_irq_handler,
+ IRQF_ONESHOT,
+ dev->driver->name, sensor);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "%s: thermal IRQ registered", __func__);
+
+ return 0;
+}
+
+static int stm_thermal_sensor_off(struct stm_thermal_sensor *sensor)
+{
+ int ret;
+
+ stm_disable_irq(sensor);
+
+ ret = stm_sensor_power_off(sensor);
+ if (ret)
+ return ret;
+
+ clk_disable_unprepare(sensor->clk);
+
+ return 0;
+}
+
+static int stm_thermal_prepare(struct stm_thermal_sensor *sensor)
+{
+ int ret;
+
+ ret = clk_prepare_enable(sensor->clk);
+ if (ret)
+ return ret;
+
+ ret = stm_thermal_read_factory_settings(sensor);
+ if (ret)
+ goto thermal_unprepare;
+
+ ret = stm_thermal_calibration(sensor);
+ if (ret)
+ goto thermal_unprepare;
+
+ return 0;
+
+thermal_unprepare:
+ clk_disable_unprepare(sensor->clk);
+
+ return ret;
+}
+
+static int stm_thermal_suspend(struct device *dev)
+{
+ struct stm_thermal_sensor *sensor = dev_get_drvdata(dev);
+
+ return stm_thermal_sensor_off(sensor);
+}
+
+static int stm_thermal_resume(struct device *dev)
+{
+ int ret;
+ struct stm_thermal_sensor *sensor = dev_get_drvdata(dev);
+
+ ret = stm_thermal_prepare(sensor);
+ if (ret)
+ return ret;
+
+ ret = stm_sensor_power_on(sensor);
+ if (ret)
+ return ret;
+
+ thermal_zone_device_update(sensor->th_dev, THERMAL_EVENT_UNSPECIFIED);
+ stm_enable_irq(sensor);
+
+ return 0;
+}
+
+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,
+ .set_trips = stm_thermal_set_trips,
+};
+
+static const struct of_device_id stm_thermal_of_match[] = {
+ { .compatible = "st,stm32-thermal"},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, stm_thermal_of_match);
+
+static int stm_thermal_probe(struct platform_device *pdev)
+{
+ struct stm_thermal_sensor *sensor;
+ void __iomem *base;
+ int ret;
+
+ if (!pdev->dev.of_node) {
+ dev_err(&pdev->dev, "%s: device tree node not found\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, sensor);
+
+ sensor->dev = &pdev->dev;
+
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ /* Populate sensor */
+ sensor->base = base;
+
+ sensor->clk = devm_clk_get(&pdev->dev, "pclk");
+ if (IS_ERR(sensor->clk)) {
+ dev_err(&pdev->dev, "%s: failed to fetch PCLK clock\n",
+ __func__);
+ return PTR_ERR(sensor->clk);
+ }
+
+ stm_disable_irq(sensor);
+
+ /* Clear irq flags */
+ writel_relaxed(ICIFR_MASK, sensor->base + DTS_ICIFR_OFFSET);
+
+ /* Configure and enable HW sensor */
+ ret = stm_thermal_prepare(sensor);
+ if (ret) {
+ dev_err(&pdev->dev, "Error prepare sensor: %d\n", ret);
+ return ret;
+ }
+
+ ret = stm_sensor_power_on(sensor);
+ if (ret) {
+ dev_err(&pdev->dev, "Error power on sensor: %d\n", ret);
+ return ret;
+ }
+
+ sensor->th_dev = devm_thermal_of_zone_register(&pdev->dev, 0,
+ sensor,
+ &stm_tz_ops);
+
+ if (IS_ERR(sensor->th_dev)) {
+ dev_err(&pdev->dev, "%s: thermal zone sensor registering KO\n",
+ __func__);
+ ret = PTR_ERR(sensor->th_dev);
+ return ret;
+ }
+
+ /* Register IRQ into GIC */
+ ret = stm_register_irq(sensor);
+ if (ret)
+ goto err_tz;
+
+ stm_enable_irq(sensor);
+
+ /*
+ * Thermal_zone doesn't enable hwmon as default,
+ * enable it here
+ */
+ ret = thermal_add_hwmon_sysfs(sensor->th_dev);
+ if (ret)
+ goto err_tz;
+
+ dev_info(&pdev->dev, "%s: Driver initialized successfully\n",
+ __func__);
+
+ return 0;
+
+err_tz:
+ return ret;
+}
+
+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);
+}
+
+static struct platform_driver stm_thermal_driver = {
+ .driver = {
+ .name = "stm_thermal",
+ .pm = pm_sleep_ptr(&stm_thermal_pm_ops),
+ .of_match_table = stm_thermal_of_match,
+ },
+ .probe = stm_thermal_probe,
+ .remove = stm_thermal_remove,
+};
+module_platform_driver(stm_thermal_driver);
+
+MODULE_DESCRIPTION("STMicroelectronics STM32 Thermal Sensor Driver");
+MODULE_AUTHOR("David Hernandez Sanchez <david.hernandezsanchez@st.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:stm_thermal");
diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c
new file mode 100644
index 000000000..284684137
--- /dev/null
+++ b/drivers/thermal/sun8i_thermal.c
@@ -0,0 +1,735 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Thermal sensor driver for Allwinner SOC
+ * Copyright (C) 2019 Yangtao Li
+ *
+ * Based on the work of Icenowy Zheng <icenowy@aosc.io>
+ * Based on the work of Ondrej Jirman <megous@megous.com>
+ * Based on the work of Josef Gajdusek <atx@atx.name>
+ */
+
+#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.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "thermal_hwmon.h"
+
+#define MAX_SENSOR_NUM 4
+
+#define FT_TEMP_MASK GENMASK(11, 0)
+#define TEMP_CALIB_MASK GENMASK(11, 0)
+#define CALIBRATE_DEFAULT 0x800
+
+#define SUN8I_THS_CTRL0 0x00
+#define SUN8I_THS_CTRL2 0x40
+#define SUN8I_THS_IC 0x44
+#define SUN8I_THS_IS 0x48
+#define SUN8I_THS_MFC 0x70
+#define SUN8I_THS_TEMP_CALIB 0x74
+#define SUN8I_THS_TEMP_DATA 0x80
+
+#define SUN50I_THS_CTRL0 0x00
+#define SUN50I_H6_THS_ENABLE 0x04
+#define SUN50I_H6_THS_PC 0x08
+#define SUN50I_H6_THS_DIC 0x10
+#define SUN50I_H6_THS_DIS 0x20
+#define SUN50I_H6_THS_MFC 0x30
+#define SUN50I_H6_THS_TEMP_CALIB 0xa0
+#define SUN50I_H6_THS_TEMP_DATA 0xc0
+
+#define SUN8I_THS_CTRL0_T_ACQ0(x) (GENMASK(15, 0) & (x))
+#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) - 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)
+
+struct tsensor {
+ struct ths_device *tmdev;
+ struct thermal_zone_device *tzd;
+ int id;
+};
+
+struct ths_thermal_chip {
+ bool has_mod_clk;
+ bool has_bus_clk_reset;
+ bool needs_sram;
+ int sensor_num;
+ int offset;
+ int scale;
+ int ft_deviation;
+ int temp_data_base;
+ int (*calibrate)(struct ths_device *tmdev,
+ u16 *caldata, int callen);
+ int (*init)(struct ths_device *tmdev);
+ unsigned long (*irq_ack)(struct ths_device *tmdev);
+ int (*calc_temp)(struct ths_device *tmdev,
+ int id, int reg);
+};
+
+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)
+{
+ return tmdev->chip->offset - (reg * tmdev->chip->scale / 10);
+}
+
+static int sun50i_h5_calc_temp(struct ths_device *tmdev,
+ int id, int reg)
+{
+ if (reg >= 0x500)
+ return -1191 * reg / 10 + 223000;
+ else if (!id)
+ return -1452 * reg / 10 + 259000;
+ else
+ return -1590 * reg / 10 + 276000;
+}
+
+static int sun8i_ths_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct tsensor *s = thermal_zone_device_priv(tz);
+ struct ths_device *tmdev = s->tmdev;
+ int val = 0;
+
+ regmap_read(tmdev->regmap, tmdev->chip->temp_data_base +
+ 0x4 * s->id, &val);
+
+ /* ths have no data yet */
+ if (!val)
+ return -EAGAIN;
+
+ *temp = tmdev->chip->calc_temp(tmdev, s->id, val);
+ /*
+ * According to the original sdk, there are some platforms(rarely)
+ * that add a fixed offset value after calculating the temperature
+ * value. We can't simply put it on the formula for calculating the
+ * temperature above, because the formula for calculating the
+ * temperature above is also used when the sensor is calibrated. If
+ * do this, the correct calibration formula is hard to know.
+ */
+ *temp += tmdev->chip->ft_deviation;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops ths_ops = {
+ .get_temp = sun8i_ths_get_temp,
+};
+
+static const struct regmap_config config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0xfc,
+};
+
+static unsigned long sun8i_h3_irq_ack(struct ths_device *tmdev)
+{
+ unsigned long irq_bitmap = 0;
+ int i, state;
+
+ regmap_read(tmdev->regmap, SUN8I_THS_IS, &state);
+
+ for (i = 0; i < tmdev->chip->sensor_num; i++) {
+ if (state & SUN8I_THS_DATA_IRQ_STS(i)) {
+ regmap_write(tmdev->regmap, SUN8I_THS_IS,
+ SUN8I_THS_DATA_IRQ_STS(i));
+ bitmap_set(&irq_bitmap, i, 1);
+ }
+ }
+
+ return irq_bitmap;
+}
+
+static unsigned long sun50i_h6_irq_ack(struct ths_device *tmdev)
+{
+ unsigned long irq_bitmap = 0;
+ int i, state;
+
+ regmap_read(tmdev->regmap, SUN50I_H6_THS_DIS, &state);
+
+ for (i = 0; i < tmdev->chip->sensor_num; i++) {
+ if (state & SUN50I_H6_THS_DATA_IRQ_STS(i)) {
+ regmap_write(tmdev->regmap, SUN50I_H6_THS_DIS,
+ SUN50I_H6_THS_DATA_IRQ_STS(i));
+ bitmap_set(&irq_bitmap, i, 1);
+ }
+ }
+
+ return irq_bitmap;
+}
+
+static irqreturn_t sun8i_irq_thread(int irq, void *data)
+{
+ struct ths_device *tmdev = data;
+ unsigned long irq_bitmap = tmdev->chip->irq_ack(tmdev);
+ 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);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int sun8i_h3_ths_calibrate(struct ths_device *tmdev,
+ u16 *caldata, int callen)
+{
+ int i;
+
+ if (!caldata[0] || callen < 2 * tmdev->chip->sensor_num)
+ return -EINVAL;
+
+ for (i = 0; i < tmdev->chip->sensor_num; i++) {
+ int offset = (i % 2) << 4;
+
+ regmap_update_bits(tmdev->regmap,
+ SUN8I_THS_TEMP_CALIB + (4 * (i >> 1)),
+ TEMP_CALIB_MASK << offset,
+ caldata[i] << offset);
+ }
+
+ return 0;
+}
+
+static int sun50i_h6_ths_calibrate(struct ths_device *tmdev,
+ u16 *caldata, int callen)
+{
+ struct device *dev = tmdev->dev;
+ int i, ft_temp;
+
+ if (!caldata[0])
+ return -EINVAL;
+
+ /*
+ * efuse layout:
+ *
+ * 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.
+ *
+ * The unit of stored FT temperature is 0.1 degree celsius.
+ *
+ * We need to calculate a delta between measured and caluclated
+ * register values and this will become a calibration offset.
+ */
+ ft_temp = (caldata[0] & FT_TEMP_MASK) * 100;
+
+ for (i = 0; i < tmdev->chip->sensor_num; i++) {
+ 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
+ * temperature from sensor reading at factory temperature
+ * minus actual factory temperature) * 14.88 (scale from
+ * temperature to register values)
+ */
+ cdata = CALIBRATE_DEFAULT -
+ ((sensor_temp - ft_temp) * 10 / tmdev->chip->scale);
+ if (cdata & ~TEMP_CALIB_MASK) {
+ /*
+ * Calibration value more than 12-bit, but calibration
+ * register is 12-bit. In this case, ths hardware can
+ * still work without calibration, although the data
+ * won't be so accurate.
+ */
+ dev_warn(dev, "sensor%d is not calibrated.\n", i);
+ continue;
+ }
+
+ offset = (i % 2) * 16;
+ regmap_update_bits(tmdev->regmap,
+ SUN50I_H6_THS_TEMP_CALIB + (i / 2 * 4),
+ TEMP_CALIB_MASK << offset,
+ cdata << offset);
+ }
+
+ return 0;
+}
+
+static int sun8i_ths_calibrate(struct ths_device *tmdev)
+{
+ struct nvmem_cell *calcell;
+ struct device *dev = tmdev->dev;
+ u16 *caldata;
+ size_t callen;
+ int ret = 0;
+
+ calcell = nvmem_cell_get(dev, "calibration");
+ if (IS_ERR(calcell)) {
+ if (PTR_ERR(calcell) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ /*
+ * Even if the external calibration data stored in sid is
+ * not accessible, the THS hardware can still work, although
+ * the data won't be so accurate.
+ *
+ * The default value of calibration register is 0x800 for
+ * every sensor, and the calibration value is usually 0x7xx
+ * or 0x8xx, so they won't be away from the default value
+ * for a lot.
+ *
+ * So here we do not return error if the calibration data is
+ * not available, except the probe needs deferring.
+ */
+ goto out;
+ }
+
+ caldata = nvmem_cell_read(calcell, &callen);
+ if (IS_ERR(caldata)) {
+ ret = PTR_ERR(caldata);
+ goto out;
+ }
+
+ tmdev->chip->calibrate(tmdev, caldata, callen);
+
+ kfree(caldata);
+out:
+ if (!IS_ERR(calcell))
+ nvmem_cell_put(calcell);
+ return ret;
+}
+
+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;
+ struct platform_device *pdev = to_platform_device(dev);
+ void __iomem *base;
+ int ret;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ tmdev->regmap = devm_regmap_init_mmio(dev, base, &config);
+ if (IS_ERR(tmdev->regmap))
+ return PTR_ERR(tmdev->regmap);
+
+ if (tmdev->chip->has_bus_clk_reset) {
+ tmdev->reset = devm_reset_control_get(dev, NULL);
+ if (IS_ERR(tmdev->reset))
+ return PTR_ERR(tmdev->reset);
+
+ ret = reset_control_deassert(tmdev->reset);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, sun8i_ths_reset_control_assert,
+ tmdev->reset);
+ if (ret)
+ return ret;
+
+ tmdev->bus_clk = devm_clk_get_enabled(&pdev->dev, "bus");
+ if (IS_ERR(tmdev->bus_clk))
+ return PTR_ERR(tmdev->bus_clk);
+ }
+
+ if (tmdev->chip->has_mod_clk) {
+ tmdev->mod_clk = devm_clk_get_enabled(&pdev->dev, "mod");
+ if (IS_ERR(tmdev->mod_clk))
+ return PTR_ERR(tmdev->mod_clk);
+ }
+
+ ret = clk_set_rate(tmdev->mod_clk, 24000000);
+ 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;
+
+ return 0;
+}
+
+static int sun8i_h3_thermal_init(struct ths_device *tmdev)
+{
+ int val;
+
+ /* average over 4 samples */
+ regmap_write(tmdev->regmap, SUN8I_THS_MFC,
+ SUN50I_THS_FILTER_EN |
+ SUN50I_THS_FILTER_TYPE(1));
+ /*
+ * clkin = 24MHz
+ * filter_samples = 4
+ * period = 0.25s
+ *
+ * x = period * clkin / 4096 / filter_samples - 1
+ * = 365
+ */
+ val = GENMASK(7 + tmdev->chip->sensor_num, 8);
+ regmap_write(tmdev->regmap, SUN8I_THS_IC,
+ SUN50I_H6_THS_PC_TEMP_PERIOD(365) | val);
+ /*
+ * T_acq = 20us
+ * clkin = 24MHz
+ *
+ * x = T_acq * clkin - 1
+ * = 479
+ */
+ regmap_write(tmdev->regmap, SUN8I_THS_CTRL0,
+ SUN8I_THS_CTRL0_T_ACQ0(479));
+ val = GENMASK(tmdev->chip->sensor_num - 1, 0);
+ regmap_write(tmdev->regmap, SUN8I_THS_CTRL2,
+ SUN8I_THS_CTRL2_T_ACQ1(479) | val);
+
+ return 0;
+}
+
+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);
+
+ /*
+ * 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_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 |
+ SUN50I_THS_FILTER_TYPE(1));
+ /*
+ * clkin = 24MHz
+ * filter_samples = 4
+ * period = 0.25s
+ *
+ * x = period * clkin / 4096 / filter_samples - 1
+ * = 365
+ */
+ regmap_write(tmdev->regmap, SUN50I_H6_THS_PC,
+ SUN50I_H6_THS_PC_TEMP_PERIOD(365));
+ /* enable sensor */
+ val = GENMASK(tmdev->chip->sensor_num - 1, 0);
+ regmap_write(tmdev->regmap, SUN50I_H6_THS_ENABLE, val);
+ /* thermal data interrupt enable */
+ val = GENMASK(tmdev->chip->sensor_num - 1, 0);
+ regmap_write(tmdev->regmap, SUN50I_H6_THS_DIC, val);
+
+ return 0;
+}
+
+static int sun8i_ths_register(struct ths_device *tmdev)
+{
+ int i;
+
+ for (i = 0; i < tmdev->chip->sensor_num; i++) {
+ tmdev->sensor[i].tmdev = tmdev;
+ tmdev->sensor[i].id = i;
+ tmdev->sensor[i].tzd =
+ devm_thermal_of_zone_register(tmdev->dev,
+ i,
+ &tmdev->sensor[i],
+ &ths_ops);
+
+ /*
+ * 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);
+ }
+
+ return 0;
+}
+
+static int sun8i_ths_probe(struct platform_device *pdev)
+{
+ struct ths_device *tmdev;
+ struct device *dev = &pdev->dev;
+ int ret, irq;
+
+ tmdev = devm_kzalloc(dev, sizeof(*tmdev), GFP_KERNEL);
+ if (!tmdev)
+ return -ENOMEM;
+
+ tmdev->dev = dev;
+ tmdev->chip = of_device_get_match_data(&pdev->dev);
+ if (!tmdev->chip)
+ return -EINVAL;
+
+ ret = sun8i_ths_resource_init(tmdev);
+ if (ret)
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = tmdev->chip->init(tmdev);
+ if (ret)
+ return ret;
+
+ ret = sun8i_ths_register(tmdev);
+ if (ret)
+ return ret;
+
+ /*
+ * Avoid entering the interrupt handler, the thermal device is not
+ * registered yet, we deffer the registration of the interrupt to
+ * the end.
+ */
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ sun8i_irq_thread,
+ IRQF_ONESHOT, "ths", tmdev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct ths_thermal_chip sun8i_a83t_ths = {
+ .sensor_num = 3,
+ .scale = 705,
+ .offset = 191668,
+ .temp_data_base = SUN8I_THS_TEMP_DATA,
+ .calibrate = sun8i_h3_ths_calibrate,
+ .init = sun8i_h3_thermal_init,
+ .irq_ack = sun8i_h3_irq_ack,
+ .calc_temp = sun8i_ths_calc_temp,
+};
+
+static const struct ths_thermal_chip sun8i_h3_ths = {
+ .sensor_num = 1,
+ .scale = 1211,
+ .offset = 217000,
+ .has_mod_clk = true,
+ .has_bus_clk_reset = true,
+ .temp_data_base = SUN8I_THS_TEMP_DATA,
+ .calibrate = sun8i_h3_ths_calibrate,
+ .init = sun8i_h3_thermal_init,
+ .irq_ack = sun8i_h3_irq_ack,
+ .calc_temp = sun8i_ths_calc_temp,
+};
+
+static const struct ths_thermal_chip sun8i_r40_ths = {
+ .sensor_num = 2,
+ .offset = 251086,
+ .scale = 1130,
+ .has_mod_clk = true,
+ .has_bus_clk_reset = true,
+ .temp_data_base = SUN8I_THS_TEMP_DATA,
+ .calibrate = sun8i_h3_ths_calibrate,
+ .init = sun8i_h3_thermal_init,
+ .irq_ack = sun8i_h3_irq_ack,
+ .calc_temp = sun8i_ths_calc_temp,
+};
+
+static const struct ths_thermal_chip sun50i_a64_ths = {
+ .sensor_num = 3,
+ .offset = 260890,
+ .scale = 1170,
+ .has_mod_clk = true,
+ .has_bus_clk_reset = true,
+ .temp_data_base = SUN8I_THS_TEMP_DATA,
+ .calibrate = sun8i_h3_ths_calibrate,
+ .init = sun8i_h3_thermal_init,
+ .irq_ack = sun8i_h3_irq_ack,
+ .calc_temp = sun8i_ths_calc_temp,
+};
+
+static const struct ths_thermal_chip sun50i_a100_ths = {
+ .sensor_num = 3,
+ .has_bus_clk_reset = true,
+ .ft_deviation = 8000,
+ .offset = 187744,
+ .scale = 672,
+ .temp_data_base = SUN50I_H6_THS_TEMP_DATA,
+ .calibrate = sun50i_h6_ths_calibrate,
+ .init = sun50i_h6_thermal_init,
+ .irq_ack = sun50i_h6_irq_ack,
+ .calc_temp = sun8i_ths_calc_temp,
+};
+
+static const struct ths_thermal_chip sun50i_h5_ths = {
+ .sensor_num = 2,
+ .has_mod_clk = true,
+ .has_bus_clk_reset = true,
+ .temp_data_base = SUN8I_THS_TEMP_DATA,
+ .calibrate = sun8i_h3_ths_calibrate,
+ .init = sun8i_h3_thermal_init,
+ .irq_ack = sun8i_h3_irq_ack,
+ .calc_temp = sun50i_h5_calc_temp,
+};
+
+static const struct ths_thermal_chip sun50i_h6_ths = {
+ .sensor_num = 2,
+ .has_bus_clk_reset = true,
+ .ft_deviation = 7000,
+ .offset = 187744,
+ .scale = 672,
+ .temp_data_base = SUN50I_H6_THS_TEMP_DATA,
+ .calibrate = sun50i_h6_ths_calibrate,
+ .init = sun50i_h6_thermal_init,
+ .irq_ack = sun50i_h6_irq_ack,
+ .calc_temp = sun8i_ths_calc_temp,
+};
+
+static const struct ths_thermal_chip 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 },
+ { .compatible = "allwinner,sun8i-r40-ths", .data = &sun8i_r40_ths },
+ { .compatible = "allwinner,sun50i-a64-ths", .data = &sun50i_a64_ths },
+ { .compatible = "allwinner,sun50i-a100-ths", .data = &sun50i_a100_ths },
+ { .compatible = "allwinner,sun50i-h5-ths", .data = &sun50i_h5_ths },
+ { .compatible = "allwinner,sun50i-h6-ths", .data = &sun50i_h6_ths },
+ { .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);
+
+static struct platform_driver ths_driver = {
+ .probe = sun8i_ths_probe,
+ .driver = {
+ .name = "sun8i-thermal",
+ .of_match_table = of_ths_match,
+ },
+};
+module_platform_driver(ths_driver);
+
+MODULE_DESCRIPTION("Thermal sensor driver for Allwinner SOC");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/tegra/Kconfig b/drivers/thermal/tegra/Kconfig
new file mode 100644
index 000000000..cfa41d87a
--- /dev/null
+++ b/drivers/thermal/tegra/Kconfig
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "NVIDIA Tegra thermal drivers"
+depends on ARCH_TEGRA || COMPILE_TEST
+
+config TEGRA_SOCTHERM
+ tristate "Tegra SOCTHERM thermal management"
+ help
+ Enable this option for integrated thermal management support on NVIDIA
+ Tegra systems-on-chip. The driver supports four thermal zones
+ (CPU, GPU, MEM, PLLX). Cooling devices can be bound to the thermal
+ zones to manage temperatures. This option is also required for the
+ emergency thermal reset (thermtrip) feature to function.
+
+config TEGRA_BPMP_THERMAL
+ tristate "Tegra BPMP thermal sensing"
+ depends on TEGRA_BPMP || COMPILE_TEST
+ help
+ Enable this option for support for sensing system temperature of NVIDIA
+ Tegra systems-on-chip with the BPMP coprocessor (Tegra186).
+
+config TEGRA30_TSENSOR
+ tristate "Tegra30 Thermal Sensor"
+ depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST
+ help
+ Enable this option to support thermal management of NVIDIA Tegra30
+ system-on-chip.
+
+endmenu
diff --git a/drivers/thermal/tegra/Makefile b/drivers/thermal/tegra/Makefile
new file mode 100644
index 000000000..9b3e91f7f
--- /dev/null
+++ b/drivers/thermal/tegra/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_TEGRA_SOCTHERM) += tegra-soctherm.o
+obj-$(CONFIG_TEGRA_BPMP_THERMAL) += tegra-bpmp-thermal.o
+obj-$(CONFIG_TEGRA30_TSENSOR) += tegra30-tsensor.o
+
+tegra-soctherm-y := soctherm.o soctherm-fuse.o
+tegra-soctherm-$(CONFIG_ARCH_TEGRA_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
new file mode 100644
index 000000000..8d37cd8c9
--- /dev/null
+++ b/drivers/thermal/tegra/soctherm-fuse.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <soc/tegra/fuse.h>
+
+#include "soctherm.h"
+
+#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
+
+/*
+ * Tegra210: Layout of bits in FUSE_TSENSOR_COMMON:
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | BASE_FT | BASE_CP | SHFT_FT | SHIFT_CP |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * 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].
+ *
+ * FUSE_TSENSOR_COMMON:
+ * 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 | BASE_CP |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * FUSE_SPARE_REALIGNMENT_REG:
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |---------------------------------------------------| 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
+
+/**
+ * div64_s64_precise() - wrapper for div64_s64()
+ * @a: the dividend
+ * @b: the divisor
+ *
+ * Implements division with fairly accurate rounding instead of truncation by
+ * shifting the dividend to the left by 16 so that the quotient has a
+ * much higher precision.
+ *
+ * Return: the quotient of a / b.
+ */
+static s64 div64_s64_precise(s64 a, s32 b)
+{
+ s64 r, al;
+
+ /* Scale up for increased precision division */
+ al = a << 16;
+
+ r = div64_s64(al * 2 + 1, 2 * b);
+ return r >> 16;
+}
+
+int tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse,
+ struct tsensor_shared_calib *shared)
+{
+ u32 val;
+ s32 shifted_cp, shifted_ft;
+ int err;
+
+ err = tegra_fuse_readl(tfuse->fuse_common_reg, &val);
+ if (err)
+ return err;
+
+ shared->base_cp = (val & tfuse->fuse_base_cp_mask) >>
+ tfuse->fuse_base_cp_shift;
+ shared->base_ft = (val & tfuse->fuse_base_ft_mask) >>
+ tfuse->fuse_base_ft_shift;
+
+ shifted_ft = (val & tfuse->fuse_shift_ft_mask) >>
+ tfuse->fuse_shift_ft_shift;
+ shifted_ft = sign_extend32(shifted_ft, 4);
+
+ if (tfuse->fuse_spare_realignment) {
+ err = tegra_fuse_readl(tfuse->fuse_spare_realignment, &val);
+ if (err)
+ 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 * tfuse->nominal_calib_ft + shifted_ft;
+
+ return 0;
+}
+
+int tegra_calc_tsensor_calib(const struct tegra_tsensor *sensor,
+ const struct tsensor_shared_calib *shared,
+ u32 *calibration)
+{
+ const struct tegra_tsensor_group *sensor_group;
+ u32 val, calib;
+ s32 actual_tsensor_ft, actual_tsensor_cp;
+ s32 delta_sens, delta_temp;
+ s32 mult, div;
+ s16 therma, thermb;
+ s64 temp;
+ int err;
+
+ sensor_group = sensor->group;
+
+ err = tegra_fuse_readl(sensor->calib_fuse_offset, &val);
+ if (err)
+ return err;
+
+ actual_tsensor_cp = (shared->base_cp * 64) + sign_extend32(val, 12);
+ val = (val & FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK) >>
+ FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT;
+ actual_tsensor_ft = (shared->base_ft * 32) + sign_extend32(val, 12);
+
+ delta_sens = actual_tsensor_ft - actual_tsensor_cp;
+ delta_temp = shared->actual_temp_ft - shared->actual_temp_cp;
+
+ mult = sensor_group->pdiv * sensor->config->tsample_ate;
+ div = sensor->config->tsample * sensor_group->pdiv_ate;
+
+ temp = (s64)delta_temp * (1LL << 13) * mult;
+ therma = div64_s64_precise(temp, (s64)delta_sens * div);
+
+ temp = ((s64)actual_tsensor_ft * shared->actual_temp_cp) -
+ ((s64)actual_tsensor_cp * shared->actual_temp_ft);
+ thermb = div64_s64_precise(temp, delta_sens);
+
+ temp = (s64)therma * sensor->fuse_corr_alpha;
+ therma = div64_s64_precise(temp, CALIB_COEFFICIENT);
+
+ temp = (s64)thermb * sensor->fuse_corr_alpha + sensor->fuse_corr_beta;
+ thermb = div64_s64_precise(temp, CALIB_COEFFICIENT);
+
+ calib = ((u16)therma << SENSOR_CONFIG2_THERMA_SHIFT) |
+ ((u16)thermb << SENSOR_CONFIG2_THERMB_SHIFT);
+
+ *calibration = calib;
+
+ return 0;
+}
+
+MODULE_AUTHOR("Wei Ni <wni@nvidia.com>");
+MODULE_DESCRIPTION("Tegra SOCTHERM fuse management");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c
new file mode 100644
index 000000000..f74acf13b
--- /dev/null
+++ b/drivers/thermal/tegra/soctherm.c
@@ -0,0 +1,2290 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014 - 2018, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author:
+ * Mikko Perttunen <mperttunen@nvidia.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/debugfs.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#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"
+#include "soctherm.h"
+
+#define SENSOR_CONFIG0 0
+#define SENSOR_CONFIG0_STOP BIT(0)
+#define SENSOR_CONFIG0_CPTR_OVER BIT(2)
+#define SENSOR_CONFIG0_OVER BIT(3)
+#define SENSOR_CONFIG0_TCALC_OVER BIT(4)
+#define SENSOR_CONFIG0_TALL_MASK (0xfffff << 8)
+#define SENSOR_CONFIG0_TALL_SHIFT 8
+
+#define SENSOR_CONFIG1 4
+#define SENSOR_CONFIG1_TSAMPLE_MASK 0x3ff
+#define SENSOR_CONFIG1_TSAMPLE_SHIFT 0
+#define SENSOR_CONFIG1_TIDDQ_EN_MASK (0x3f << 15)
+#define SENSOR_CONFIG1_TIDDQ_EN_SHIFT 15
+#define SENSOR_CONFIG1_TEN_COUNT_MASK (0x3f << 24)
+#define SENSOR_CONFIG1_TEN_COUNT_SHIFT 24
+#define SENSOR_CONFIG1_TEMP_ENABLE BIT(31)
+
+/*
+ * SENSOR_CONFIG2 is defined in soctherm.h
+ * because, it will be used by tegra_soctherm_fuse.c
+ */
+
+#define SENSOR_STATUS0 0xc
+#define SENSOR_STATUS0_VALID_MASK BIT(31)
+#define SENSOR_STATUS0_CAPTURE_MASK 0xffff
+
+#define SENSOR_STATUS1 0x10
+#define SENSOR_STATUS1_TEMP_VALID_MASK BIT(31)
+#define SENSOR_STATUS1_TEMP_MASK 0xffff
+
+#define READBACK_VALUE_MASK 0xff00
+#define READBACK_VALUE_SHIFT 8
+#define READBACK_ADD_HALF BIT(7)
+#define READBACK_NEGATE BIT(0)
+
+/*
+ * THERMCTL_LEVEL0_GROUP_CPU is defined in soctherm.h
+ * because it will be used by tegraxxx_soctherm.c
+ */
+#define THERMCTL_LVL0_CPU0_EN_MASK BIT(8)
+#define THERMCTL_LVL0_CPU0_CPU_THROT_MASK (0x3 << 5)
+#define THERMCTL_LVL0_CPU0_CPU_THROT_LIGHT 0x1
+#define THERMCTL_LVL0_CPU0_CPU_THROT_HEAVY 0x2
+#define THERMCTL_LVL0_CPU0_GPU_THROT_MASK (0x3 << 3)
+#define THERMCTL_LVL0_CPU0_GPU_THROT_LIGHT 0x1
+#define THERMCTL_LVL0_CPU0_GPU_THROT_HEAVY 0x2
+#define THERMCTL_LVL0_CPU0_MEM_THROT_MASK BIT(2)
+#define THERMCTL_LVL0_CPU0_STATUS_MASK 0x3
+
+#define THERMCTL_LVL0_UP_STATS 0x10
+#define THERMCTL_LVL0_DN_STATS 0x14
+
+#define THERMCTL_INTR_STATUS 0x84
+
+#define TH_INTR_MD0_MASK BIT(25)
+#define TH_INTR_MU0_MASK BIT(24)
+#define TH_INTR_GD0_MASK BIT(17)
+#define TH_INTR_GU0_MASK BIT(16)
+#define TH_INTR_CD0_MASK BIT(9)
+#define TH_INTR_CU0_MASK BIT(8)
+#define TH_INTR_PD0_MASK BIT(1)
+#define TH_INTR_PU0_MASK BIT(0)
+#define TH_INTR_IGNORE_MASK 0xFCFCFCFC
+
+#define THERMCTL_STATS_CTL 0x94
+#define STATS_CTL_CLR_DN 0x8
+#define STATS_CTL_EN_DN 0x4
+#define STATS_CTL_CLR_UP 0x2
+#define STATS_CTL_EN_UP 0x1
+
+#define OC1_CFG 0x310
+#define OC1_CFG_LONG_LATENCY_MASK BIT(6)
+#define OC1_CFG_HW_RESTORE_MASK BIT(5)
+#define OC1_CFG_PWR_GOOD_MASK_MASK BIT(4)
+#define OC1_CFG_THROTTLE_MODE_MASK (0x3 << 2)
+#define OC1_CFG_ALARM_POLARITY_MASK BIT(1)
+#define OC1_CFG_EN_THROTTLE_MASK BIT(0)
+
+#define OC1_CNT_THRESHOLD 0x314
+#define OC1_THROTTLE_PERIOD 0x318
+#define OC1_ALARM_COUNT 0x31c
+#define OC1_FILTER 0x320
+#define OC1_STATS 0x3a8
+
+#define OC_INTR_STATUS 0x39c
+#define OC_INTR_ENABLE 0x3a0
+#define OC_INTR_DISABLE 0x3a4
+#define OC_STATS_CTL 0x3c4
+#define OC_STATS_CTL_CLR_ALL 0x2
+#define OC_STATS_CTL_EN_ALL 0x1
+
+#define OC_INTR_OC1_MASK BIT(0)
+#define OC_INTR_OC2_MASK BIT(1)
+#define OC_INTR_OC3_MASK BIT(2)
+#define OC_INTR_OC4_MASK BIT(3)
+#define OC_INTR_OC5_MASK BIT(4)
+
+#define THROT_GLOBAL_CFG 0x400
+#define THROT_GLOBAL_ENB_MASK BIT(0)
+
+#define CPU_PSKIP_STATUS 0x418
+#define XPU_PSKIP_STATUS_M_MASK (0xff << 12)
+#define XPU_PSKIP_STATUS_N_MASK (0xff << 4)
+#define XPU_PSKIP_STATUS_SW_OVERRIDE_MASK BIT(1)
+#define XPU_PSKIP_STATUS_ENABLED_MASK BIT(0)
+
+#define THROT_PRIORITY_LOCK 0x424
+#define THROT_PRIORITY_LOCK_PRIORITY_MASK 0xff
+
+#define THROT_STATUS 0x428
+#define THROT_STATUS_BREACH_MASK BIT(12)
+#define THROT_STATUS_STATE_MASK (0xff << 4)
+#define THROT_STATUS_ENABLED_MASK BIT(0)
+
+#define THROT_PSKIP_CTRL_LITE_CPU 0x430
+#define THROT_PSKIP_CTRL_ENABLE_MASK BIT(31)
+#define THROT_PSKIP_CTRL_DIVIDEND_MASK (0xff << 8)
+#define THROT_PSKIP_CTRL_DIVISOR_MASK 0xff
+#define THROT_PSKIP_CTRL_VECT_GPU_MASK (0x7 << 16)
+#define THROT_PSKIP_CTRL_VECT_CPU_MASK (0x7 << 8)
+#define THROT_PSKIP_CTRL_VECT2_CPU_MASK 0x7
+
+#define THROT_VECT_NONE 0x0 /* 3'b000 */
+#define THROT_VECT_LOW 0x1 /* 3'b001 */
+#define THROT_VECT_MED 0x3 /* 3'b011 */
+#define THROT_VECT_HIGH 0x7 /* 3'b111 */
+
+#define THROT_PSKIP_RAMP_LITE_CPU 0x434
+#define THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK BIT(31)
+#define THROT_PSKIP_RAMP_DURATION_MASK (0xffff << 8)
+#define THROT_PSKIP_RAMP_STEP_MASK 0xff
+
+#define THROT_PRIORITY_LITE 0x444
+#define THROT_PRIORITY_LITE_PRIO_MASK 0xff
+
+#define THROT_DELAY_LITE 0x448
+#define THROT_DELAY_LITE_DELAY_MASK 0xff
+
+/* car register offsets needed for enabling HW throttling */
+#define CAR_SUPER_CCLKG_DIVIDER 0x36c
+#define CDIVG_USE_THERM_CONTROLS_MASK BIT(30)
+
+/* ccroc register offsets needed for enabling HW throttling for Tegra132 */
+#define CCROC_SUPER_CCLKG_DIVIDER 0x024
+
+#define CCROC_GLOBAL_CFG 0x148
+
+#define CCROC_THROT_PSKIP_RAMP_CPU 0x150
+#define CCROC_THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK BIT(31)
+#define CCROC_THROT_PSKIP_RAMP_DURATION_MASK (0xffff << 8)
+#define CCROC_THROT_PSKIP_RAMP_STEP_MASK 0xff
+
+#define CCROC_THROT_PSKIP_CTRL_CPU 0x154
+#define CCROC_THROT_PSKIP_CTRL_ENB_MASK BIT(31)
+#define CCROC_THROT_PSKIP_CTRL_DIVIDEND_MASK (0xff << 8)
+#define CCROC_THROT_PSKIP_CTRL_DIVISOR_MASK 0xff
+
+/* get val from register(r) mask bits(m) */
+#define REG_GET_MASK(r, m) (((r) & (m)) >> (ffs(m) - 1))
+/* set val(v) to mask bits(m) of register(r) */
+#define REG_SET_MASK(r, m, v) (((r) & ~(m)) | \
+ (((v) & (m >> (ffs(m) - 1))) << (ffs(m) - 1)))
+
+/* get dividend from the depth */
+#define THROT_DEPTH_DIVIDEND(depth) ((256 * (100 - (depth)) / 100) - 1)
+
+/* gk20a nv_therm interface N:3 Mapping. Levels defined in tegra124-soctherm.h
+ * level vector
+ * NONE 3'b000
+ * LOW 3'b001
+ * MED 3'b011
+ * HIGH 3'b111
+ */
+#define THROT_LEVEL_TO_DEPTH(level) ((0x1 << (level)) - 1)
+
+/* get THROT_PSKIP_xxx offset per LIGHT/HEAVY throt and CPU/GPU dev */
+#define THROT_OFFSET 0x30
+#define THROT_PSKIP_CTRL(throt, dev) (THROT_PSKIP_CTRL_LITE_CPU + \
+ (THROT_OFFSET * throt) + (8 * dev))
+#define THROT_PSKIP_RAMP(throt, dev) (THROT_PSKIP_RAMP_LITE_CPU + \
+ (THROT_OFFSET * throt) + (8 * dev))
+
+/* get THROT_xxx_CTRL offset per LIGHT/HEAVY throt */
+#define THROT_PRIORITY_CTRL(throt) (THROT_PRIORITY_LITE + \
+ (THROT_OFFSET * throt))
+#define THROT_DELAY_CTRL(throt) (THROT_DELAY_LITE + \
+ (THROT_OFFSET * throt))
+
+#define ALARM_OFFSET 0x14
+#define ALARM_CFG(throt) (OC1_CFG + \
+ (ALARM_OFFSET * (throt - THROTTLE_OC1)))
+
+#define ALARM_CNT_THRESHOLD(throt) (OC1_CNT_THRESHOLD + \
+ (ALARM_OFFSET * (throt - THROTTLE_OC1)))
+
+#define ALARM_THROTTLE_PERIOD(throt) (OC1_THROTTLE_PERIOD + \
+ (ALARM_OFFSET * (throt - THROTTLE_OC1)))
+
+#define ALARM_ALARM_COUNT(throt) (OC1_ALARM_COUNT + \
+ (ALARM_OFFSET * (throt - THROTTLE_OC1)))
+
+#define ALARM_FILTER(throt) (OC1_FILTER + \
+ (ALARM_OFFSET * (throt - THROTTLE_OC1)))
+
+#define ALARM_STATS(throt) (OC1_STATS + \
+ (4 * (throt - THROTTLE_OC1)))
+
+/* get CCROC_THROT_PSKIP_xxx offset per HIGH/MED/LOW vect*/
+#define CCROC_THROT_OFFSET 0x0c
+#define CCROC_THROT_PSKIP_CTRL_CPU_REG(vect) (CCROC_THROT_PSKIP_CTRL_CPU + \
+ (CCROC_THROT_OFFSET * vect))
+#define CCROC_THROT_PSKIP_RAMP_CPU_REG(vect) (CCROC_THROT_PSKIP_RAMP_CPU + \
+ (CCROC_THROT_OFFSET * vect))
+
+/* get THERMCTL_LEVELx offset per CPU/GPU/MEM/TSENSE rg and LEVEL0~3 lv */
+#define THERMCTL_LVL_REGS_SIZE 0x20
+#define THERMCTL_LVL_REG(rg, lv) ((rg) + ((lv) * THERMCTL_LVL_REGS_SIZE))
+
+#define OC_THROTTLE_MODE_DISABLED 0
+#define OC_THROTTLE_MODE_BRIEF 2
+
+static const int min_low_temp = -127000;
+static const int max_high_temp = 127000;
+
+enum soctherm_throttle_id {
+ THROTTLE_LIGHT = 0,
+ THROTTLE_HEAVY,
+ THROTTLE_OC1,
+ THROTTLE_OC2,
+ THROTTLE_OC3,
+ THROTTLE_OC4,
+ THROTTLE_OC5, /* OC5 is reserved */
+ THROTTLE_SIZE,
+};
+
+enum soctherm_oc_irq_id {
+ TEGRA_SOC_OC_IRQ_1,
+ TEGRA_SOC_OC_IRQ_2,
+ TEGRA_SOC_OC_IRQ_3,
+ TEGRA_SOC_OC_IRQ_4,
+ TEGRA_SOC_OC_IRQ_5,
+ TEGRA_SOC_OC_IRQ_MAX,
+};
+
+enum soctherm_throttle_dev_id {
+ THROTTLE_DEV_CPU = 0,
+ THROTTLE_DEV_GPU,
+ THROTTLE_DEV_SIZE,
+};
+
+static const char *const throt_names[] = {
+ [THROTTLE_LIGHT] = "light",
+ [THROTTLE_HEAVY] = "heavy",
+ [THROTTLE_OC1] = "oc1",
+ [THROTTLE_OC2] = "oc2",
+ [THROTTLE_OC3] = "oc3",
+ [THROTTLE_OC4] = "oc4",
+ [THROTTLE_OC5] = "oc5",
+};
+
+struct tegra_soctherm;
+struct tegra_thermctl_zone {
+ void __iomem *reg;
+ struct device *dev;
+ struct tegra_soctherm *ts;
+ struct thermal_zone_device *tz;
+ const struct tegra_tsensor_group *sg;
+};
+
+struct soctherm_oc_cfg {
+ u32 active_low;
+ u32 throt_period;
+ u32 alarm_cnt_thresh;
+ u32 alarm_filter;
+ u32 mode;
+ bool intr_en;
+};
+
+struct soctherm_throt_cfg {
+ const char *name;
+ unsigned int id;
+ u8 priority;
+ u8 cpu_throt_level;
+ u32 cpu_throt_depth;
+ u32 gpu_throt_level;
+ struct soctherm_oc_cfg oc_cfg;
+ struct thermal_cooling_device *cdev;
+ bool init;
+};
+
+struct tegra_soctherm {
+ struct reset_control *reset;
+ struct clk *clock_tsensor;
+ struct clk *clock_soctherm;
+ void __iomem *regs;
+ void __iomem *clk_regs;
+ void __iomem *ccroc_regs;
+
+ int thermal_irq;
+ int edp_irq;
+
+ u32 *calib;
+ struct thermal_zone_device **thermctl_tzs;
+ struct tegra_soctherm_soc *soc;
+
+ struct soctherm_throt_cfg throt_cfgs[THROTTLE_SIZE];
+
+ struct dentry *debugfs_dir;
+
+ struct mutex thermctl_lock;
+};
+
+struct soctherm_oc_irq_chip_data {
+ struct mutex irq_lock; /* serialize OC IRQs */
+ struct irq_chip irq_chip;
+ struct irq_domain *domain;
+ int irq_enable;
+};
+
+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
+ * @value: the value to write
+ * @reg: the register offset
+ *
+ * Writes @v to @reg. No return value.
+ */
+static inline void ccroc_writel(struct tegra_soctherm *ts, u32 value, u32 reg)
+{
+ writel(value, (ts->ccroc_regs + reg));
+}
+
+/**
+ * ccroc_readl() - reads specified register from CCROC IP block
+ * @ts: pointer to a struct tegra_soctherm
+ * @reg: register address to be read
+ *
+ * Return: the value of the register
+ */
+static inline u32 ccroc_readl(struct tegra_soctherm *ts, u32 reg)
+{
+ return readl(ts->ccroc_regs + reg);
+}
+
+static void enable_tsensor(struct tegra_soctherm *tegra, unsigned int i)
+{
+ const struct tegra_tsensor *sensor = &tegra->soc->tsensors[i];
+ void __iomem *base = tegra->regs + sensor->base;
+ unsigned int val;
+
+ val = sensor->config->tall << SENSOR_CONFIG0_TALL_SHIFT;
+ writel(val, base + SENSOR_CONFIG0);
+
+ val = (sensor->config->tsample - 1) << SENSOR_CONFIG1_TSAMPLE_SHIFT;
+ val |= sensor->config->tiddq_en << SENSOR_CONFIG1_TIDDQ_EN_SHIFT;
+ val |= sensor->config->ten_count << SENSOR_CONFIG1_TEN_COUNT_SHIFT;
+ val |= SENSOR_CONFIG1_TEMP_ENABLE;
+ writel(val, base + SENSOR_CONFIG1);
+
+ writel(tegra->calib[i], base + SENSOR_CONFIG2);
+}
+
+/*
+ * Translate from soctherm readback format to millicelsius.
+ * The soctherm readback format in bits is as follows:
+ * TTTTTTTT H______N
+ * where T's contain the temperature in Celsius,
+ * H denotes an addition of 0.5 Celsius and N denotes negation
+ * of the final value.
+ */
+static int translate_temp(u16 val)
+{
+ int t;
+
+ t = ((val & READBACK_VALUE_MASK) >> READBACK_VALUE_SHIFT) * 1000;
+ if (val & READBACK_ADD_HALF)
+ t += 500;
+ if (val & READBACK_NEGATE)
+ t *= -1;
+
+ return t;
+}
+
+static int tegra_thermctl_get_temp(struct thermal_zone_device *tz, int *out_temp)
+{
+ struct tegra_thermctl_zone *zone = thermal_zone_device_priv(tz);
+ u32 val;
+
+ val = readl(zone->reg);
+ val = REG_GET_MASK(val, zone->sg->sensor_temp_mask);
+ *out_temp = translate_temp(val);
+
+ return 0;
+}
+
+/**
+ * enforce_temp_range() - check and enforce temperature range [min, max]
+ * @dev: struct device * of the SOC_THERM instance
+ * @trip_temp: the trip temperature to check
+ *
+ * Checks and enforces the permitted temperature range that SOC_THERM
+ * HW can support This is
+ * done while taking care of precision.
+ *
+ * Return: The precision adjusted capped temperature in millicelsius.
+ */
+static int enforce_temp_range(struct device *dev, int trip_temp)
+{
+ int temp;
+
+ temp = clamp_val(trip_temp, min_low_temp, max_high_temp);
+ if (temp != trip_temp)
+ dev_dbg(dev, "soctherm: trip temperature %d forced to %d\n",
+ trip_temp, temp);
+ return temp;
+}
+
+/**
+ * thermtrip_program() - Configures the hardware to shut down the
+ * system if a given sensor group reaches a given temperature
+ * @dev: ptr to the struct device for the SOC_THERM IP block
+ * @sg: pointer to the sensor group to set the thermtrip temperature for
+ * @trip_temp: the temperature in millicelsius to trigger the thermal trip at
+ *
+ * Sets the thermal trip threshold of the given sensor group to be the
+ * @trip_temp. If this threshold is crossed, the hardware will shut
+ * down.
+ *
+ * Note that, although @trip_temp is specified in millicelsius, the
+ * hardware is programmed in degrees Celsius.
+ *
+ * Return: 0 upon success, or %-EINVAL upon failure.
+ */
+static int thermtrip_program(struct device *dev,
+ const struct tegra_tsensor_group *sg,
+ int trip_temp)
+{
+ struct tegra_soctherm *ts = dev_get_drvdata(dev);
+ int temp;
+ u32 r;
+
+ if (!sg || !sg->thermtrip_threshold_mask)
+ return -EINVAL;
+
+ temp = enforce_temp_range(dev, trip_temp) / ts->soc->thresh_grain;
+
+ r = readl(ts->regs + THERMCTL_THERMTRIP_CTL);
+ r = REG_SET_MASK(r, sg->thermtrip_threshold_mask, temp);
+ r = REG_SET_MASK(r, sg->thermtrip_enable_mask, 1);
+ r = REG_SET_MASK(r, sg->thermtrip_any_en_mask, 0);
+ writel(r, ts->regs + THERMCTL_THERMTRIP_CTL);
+
+ return 0;
+}
+
+/**
+ * throttrip_program() - Configures the hardware to throttle the
+ * pulse if a given sensor group reaches a given temperature
+ * @dev: ptr to the struct device for the SOC_THERM IP block
+ * @sg: pointer to the sensor group to set the thermtrip temperature for
+ * @stc: pointer to the throttle need to be triggered
+ * @trip_temp: the temperature in millicelsius to trigger the thermal trip at
+ *
+ * Sets the thermal trip threshold and throttle event of the given sensor
+ * group. If this threshold is crossed, the hardware will trigger the
+ * throttle.
+ *
+ * Note that, although @trip_temp is specified in millicelsius, the
+ * hardware is programmed in degrees Celsius.
+ *
+ * Return: 0 upon success, or %-EINVAL upon failure.
+ */
+static int throttrip_program(struct device *dev,
+ const struct tegra_tsensor_group *sg,
+ struct soctherm_throt_cfg *stc,
+ int trip_temp)
+{
+ struct tegra_soctherm *ts = dev_get_drvdata(dev);
+ int temp, cpu_throt, gpu_throt;
+ unsigned int throt;
+ u32 r, reg_off;
+
+ if (!sg || !stc || !stc->init)
+ return -EINVAL;
+
+ temp = enforce_temp_range(dev, trip_temp) / ts->soc->thresh_grain;
+
+ /* Hardcode LIGHT on LEVEL1 and HEAVY on LEVEL2 */
+ throt = stc->id;
+ reg_off = THERMCTL_LVL_REG(sg->thermctl_lvl0_offset, throt + 1);
+
+ if (throt == THROTTLE_LIGHT) {
+ cpu_throt = THERMCTL_LVL0_CPU0_CPU_THROT_LIGHT;
+ gpu_throt = THERMCTL_LVL0_CPU0_GPU_THROT_LIGHT;
+ } else {
+ cpu_throt = THERMCTL_LVL0_CPU0_CPU_THROT_HEAVY;
+ gpu_throt = THERMCTL_LVL0_CPU0_GPU_THROT_HEAVY;
+ if (throt != THROTTLE_HEAVY)
+ dev_warn(dev,
+ "invalid throt id %d - assuming HEAVY",
+ throt);
+ }
+
+ r = readl(ts->regs + reg_off);
+ r = REG_SET_MASK(r, sg->thermctl_lvl0_up_thresh_mask, temp);
+ r = REG_SET_MASK(r, sg->thermctl_lvl0_dn_thresh_mask, temp);
+ r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_CPU_THROT_MASK, cpu_throt);
+ r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_GPU_THROT_MASK, gpu_throt);
+ r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_EN_MASK, 1);
+ writel(r, ts->regs + reg_off);
+
+ return 0;
+}
+
+static struct soctherm_throt_cfg *
+find_throttle_cfg_by_name(struct tegra_soctherm *ts, const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; ts->throt_cfgs[i].name; i++)
+ if (!strcmp(ts->throt_cfgs[i].name, name))
+ return &ts->throt_cfgs[i];
+
+ return NULL;
+}
+
+static int tsensor_group_thermtrip_get(struct tegra_soctherm *ts, int id)
+{
+ int i, temp = min_low_temp;
+ struct tsensor_group_thermtrips *tt = ts->soc->thermtrips;
+
+ if (id >= TEGRA124_SOCTHERM_SENSOR_NUM)
+ return temp;
+
+ if (tt) {
+ for (i = 0; i < ts->soc->num_ttgs; i++) {
+ if (tt[i].id == id)
+ return tt[i].temp;
+ }
+ }
+
+ return 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;
+ const struct tegra_tsensor_group *sg = zone->sg;
+ struct device *dev = zone->dev;
+
+ if (!tz)
+ return -EINVAL;
+
+ if (trip->type == THERMAL_TRIP_CRITICAL) {
+ /*
+ * If thermtrips property is set in DT,
+ * doesn't need to program critical type trip to HW,
+ * if not, program critical trip to HW.
+ */
+ if (min_low_temp == tsensor_group_thermtrip_get(ts, sg->id))
+ return thermtrip_program(dev, sg, temp);
+ else
+ return 0;
+
+ } else if (trip->type == THERMAL_TRIP_HOT) {
+ int i;
+
+ for (i = 0; i < THROTTLE_SIZE; i++) {
+ struct thermal_cooling_device *cdev;
+ struct soctherm_throt_cfg *stc;
+
+ if (!ts->throt_cfgs[i].init)
+ continue;
+
+ cdev = ts->throt_cfgs[i].cdev;
+ if (thermal_trip_is_bound_to_cdev(tz, trip, cdev))
+ stc = find_throttle_cfg_by_name(ts, cdev->type);
+ else
+ continue;
+
+ return throttrip_program(dev, sg, stc, temp);
+ }
+ }
+
+ return 0;
+}
+
+static void thermal_irq_enable(struct tegra_thermctl_zone *zn)
+{
+ u32 r;
+
+ /* multiple zones could be handling and setting trips at once */
+ mutex_lock(&zn->ts->thermctl_lock);
+ r = readl(zn->ts->regs + THERMCTL_INTR_ENABLE);
+ r = REG_SET_MASK(r, zn->sg->thermctl_isr_mask, TH_INTR_UP_DN_EN);
+ writel(r, zn->ts->regs + THERMCTL_INTR_ENABLE);
+ mutex_unlock(&zn->ts->thermctl_lock);
+}
+
+static void thermal_irq_disable(struct tegra_thermctl_zone *zn)
+{
+ u32 r;
+
+ /* multiple zones could be handling and setting trips at once */
+ mutex_lock(&zn->ts->thermctl_lock);
+ r = readl(zn->ts->regs + THERMCTL_INTR_DISABLE);
+ r = REG_SET_MASK(r, zn->sg->thermctl_isr_mask, 0);
+ writel(r, zn->ts->regs + THERMCTL_INTR_DISABLE);
+ mutex_unlock(&zn->ts->thermctl_lock);
+}
+
+static int tegra_thermctl_set_trips(struct thermal_zone_device *tz, int lo, int hi)
+{
+ struct tegra_thermctl_zone *zone = thermal_zone_device_priv(tz);
+ u32 r;
+
+ thermal_irq_disable(zone);
+
+ r = readl(zone->ts->regs + zone->sg->thermctl_lvl0_offset);
+ r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_EN_MASK, 0);
+ writel(r, zone->ts->regs + zone->sg->thermctl_lvl0_offset);
+
+ lo = enforce_temp_range(zone->dev, lo) / zone->ts->soc->thresh_grain;
+ hi = enforce_temp_range(zone->dev, hi) / zone->ts->soc->thresh_grain;
+ dev_dbg(zone->dev, "%s hi:%d, lo:%d\n", __func__, hi, lo);
+
+ r = REG_SET_MASK(r, zone->sg->thermctl_lvl0_up_thresh_mask, hi);
+ r = REG_SET_MASK(r, zone->sg->thermctl_lvl0_dn_thresh_mask, lo);
+ r = REG_SET_MASK(r, THERMCTL_LVL0_CPU0_EN_MASK, 1);
+ writel(r, zone->ts->regs + zone->sg->thermctl_lvl0_offset);
+
+ thermal_irq_enable(zone);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops tegra_of_thermal_ops = {
+ .get_temp = tegra_thermctl_get_temp,
+ .set_trip_temp = tegra_thermctl_set_trip_temp,
+ .set_trips = tegra_thermctl_set_trips,
+};
+
+static int get_hot_trip_cb(struct thermal_trip *trip, void *arg)
+{
+ const struct thermal_trip **trip_ret = arg;
+
+ if (trip->type != THERMAL_TRIP_HOT)
+ return 0;
+
+ *trip_ret = trip;
+ /* Return nonzero to terminate the search. */
+ return 1;
+}
+
+static const struct thermal_trip *get_hot_trip(struct thermal_zone_device *tz)
+{
+ const struct thermal_trip *trip = NULL;
+
+ thermal_zone_for_each_trip(tz, get_hot_trip_cb, &trip);
+
+ return trip;
+}
+
+/**
+ * tegra_soctherm_set_hwtrips() - set HW trip point from DT data
+ * @dev: struct device * of the SOC_THERM instance
+ * @sg: pointer to the sensor group to set the thermtrip temperature for
+ * @tz: struct thermal_zone_device *
+ *
+ * Configure the SOC_THERM HW trip points, setting "THERMTRIP"
+ * "THROTTLE" trip points , using "thermtrips", "critical" or "hot"
+ * type trip_temp
+ * from thermal zone.
+ * After they have been configured, THERMTRIP or THROTTLE will take
+ * action when the configured SoC thermal sensor group reaches a
+ * certain temperature.
+ *
+ * Return: 0 upon success, or a negative error code on failure.
+ * "Success" does not mean that trips was enabled; it could also
+ * mean that no node was found in DT.
+ * THERMTRIP has been enabled successfully when a message similar to
+ * this one appears on the serial console:
+ * "thermtrip: will shut down when sensor group XXX reaches YYYYYY mC"
+ * THROTTLE has been enabled successfully when a message similar to
+ * this one appears on the serial console:
+ * ""throttrip: will throttle when sensor group XXX reaches YYYYYY mC"
+ */
+static int tegra_soctherm_set_hwtrips(struct device *dev,
+ const struct tegra_tsensor_group *sg,
+ 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, temperature, ret;
+
+ /* Get thermtrips. If missing, try to get critical trips. */
+ temperature = tsensor_group_thermtrip_get(ts, sg->id);
+ if (min_low_temp == temperature)
+ if (thermal_zone_get_crit_temp(tz, &temperature))
+ temperature = max_high_temp;
+
+ ret = thermtrip_program(dev, sg, temperature);
+ if (ret) {
+ dev_err(dev, "thermtrip: %s: error during enable\n", sg->name);
+ return ret;
+ }
+
+ dev_info(dev, "thermtrip: will shut down when %s reaches %d mC\n",
+ sg->name, temperature);
+
+ hot_trip = get_hot_trip(tz);
+ if (!hot_trip) {
+ dev_info(dev, "throttrip: %s: missing hot temperature\n",
+ sg->name);
+ return 0;
+ }
+
+ for (i = 0; i < THROTTLE_OC1; i++) {
+ struct thermal_cooling_device *cdev;
+
+ if (!ts->throt_cfgs[i].init)
+ continue;
+
+ cdev = ts->throt_cfgs[i].cdev;
+ if (thermal_trip_is_bound_to_cdev(tz, hot_trip, cdev))
+ stc = find_throttle_cfg_by_name(ts, cdev->type);
+ else
+ continue;
+
+ ret = throttrip_program(dev, sg, stc, temperature);
+ if (ret) {
+ dev_err(dev, "throttrip: %s: error during enable\n",
+ sg->name);
+ return ret;
+ }
+
+ dev_info(dev,
+ "throttrip: will throttle when %s reaches %d mC\n",
+ sg->name, temperature);
+ break;
+ }
+
+ if (i == THROTTLE_SIZE)
+ dev_info(dev, "throttrip: %s: missing throttle cdev\n",
+ sg->name);
+
+ return 0;
+}
+
+static irqreturn_t soctherm_thermal_isr(int irq, void *dev_id)
+{
+ struct tegra_soctherm *ts = dev_id;
+ u32 r;
+
+ /* Case for no lock:
+ * Although interrupts are enabled in set_trips, there is still no need
+ * to lock here because the interrupts are disabled before programming
+ * new trip points. Hence there cant be a interrupt on the same sensor.
+ * An interrupt can however occur on a sensor while trips are being
+ * programmed on a different one. This beign a LEVEL interrupt won't
+ * cause a new interrupt but this is taken care of by the re-reading of
+ * the STATUS register in the thread function.
+ */
+ r = readl(ts->regs + THERMCTL_INTR_STATUS);
+ writel(r, ts->regs + THERMCTL_INTR_DISABLE);
+
+ return IRQ_WAKE_THREAD;
+}
+
+/**
+ * soctherm_thermal_isr_thread() - Handles a thermal interrupt request
+ * @irq: The interrupt number being requested; not used
+ * @dev_id: Opaque pointer to tegra_soctherm;
+ *
+ * Clears the interrupt status register if there are expected
+ * interrupt bits set.
+ * The interrupt(s) are then handled by updating the corresponding
+ * thermal zones.
+ *
+ * An error is logged if any unexpected interrupt bits are set.
+ *
+ * Disabled interrupts are re-enabled.
+ *
+ * Return: %IRQ_HANDLED. Interrupt was handled and no further processing
+ * is needed.
+ */
+static irqreturn_t soctherm_thermal_isr_thread(int irq, void *dev_id)
+{
+ struct tegra_soctherm *ts = dev_id;
+ struct thermal_zone_device *tz;
+ u32 st, ex = 0, cp = 0, gp = 0, pl = 0, me = 0;
+
+ st = readl(ts->regs + THERMCTL_INTR_STATUS);
+
+ /* deliberately clear expected interrupts handled in SW */
+ cp |= st & TH_INTR_CD0_MASK;
+ cp |= st & TH_INTR_CU0_MASK;
+
+ gp |= st & TH_INTR_GD0_MASK;
+ gp |= st & TH_INTR_GU0_MASK;
+
+ pl |= st & TH_INTR_PD0_MASK;
+ pl |= st & TH_INTR_PU0_MASK;
+
+ me |= st & TH_INTR_MD0_MASK;
+ me |= st & TH_INTR_MU0_MASK;
+
+ ex |= cp | gp | pl | me;
+ if (ex) {
+ writel(ex, ts->regs + THERMCTL_INTR_STATUS);
+ st &= ~ex;
+
+ if (cp) {
+ tz = ts->thermctl_tzs[TEGRA124_SOCTHERM_SENSOR_CPU];
+ thermal_zone_device_update(tz,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ if (gp) {
+ tz = ts->thermctl_tzs[TEGRA124_SOCTHERM_SENSOR_GPU];
+ thermal_zone_device_update(tz,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ if (pl) {
+ tz = ts->thermctl_tzs[TEGRA124_SOCTHERM_SENSOR_PLLX];
+ thermal_zone_device_update(tz,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ if (me) {
+ tz = ts->thermctl_tzs[TEGRA124_SOCTHERM_SENSOR_MEM];
+ thermal_zone_device_update(tz,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+ }
+
+ /* deliberately ignore expected interrupts NOT handled in SW */
+ ex |= TH_INTR_IGNORE_MASK;
+ st &= ~ex;
+
+ if (st) {
+ /* Whine about any other unexpected INTR bits still set */
+ pr_err("soctherm: Ignored unexpected INTRs 0x%08x\n", st);
+ writel(st, ts->regs + THERMCTL_INTR_STATUS);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * soctherm_oc_intr_enable() - Enables the soctherm over-current interrupt
+ * @ts: pointer to a struct tegra_soctherm
+ * @alarm: The soctherm throttle id
+ * @enable: Flag indicating enable the soctherm over-current
+ * interrupt or disable it
+ *
+ * Enables a specific over-current pins @alarm to raise an interrupt if the flag
+ * is set and the alarm corresponds to OC1, OC2, OC3, or OC4.
+ */
+static void soctherm_oc_intr_enable(struct tegra_soctherm *ts,
+ enum soctherm_throttle_id alarm,
+ bool enable)
+{
+ u32 r;
+
+ if (!enable)
+ return;
+
+ r = readl(ts->regs + OC_INTR_ENABLE);
+ switch (alarm) {
+ case THROTTLE_OC1:
+ r = REG_SET_MASK(r, OC_INTR_OC1_MASK, 1);
+ break;
+ case THROTTLE_OC2:
+ r = REG_SET_MASK(r, OC_INTR_OC2_MASK, 1);
+ break;
+ case THROTTLE_OC3:
+ r = REG_SET_MASK(r, OC_INTR_OC3_MASK, 1);
+ break;
+ case THROTTLE_OC4:
+ r = REG_SET_MASK(r, OC_INTR_OC4_MASK, 1);
+ break;
+ default:
+ r = 0;
+ break;
+ }
+ writel(r, ts->regs + OC_INTR_ENABLE);
+}
+
+/**
+ * soctherm_handle_alarm() - Handles soctherm alarms
+ * @alarm: The soctherm throttle id
+ *
+ * "Handles" over-current alarms (OC1, OC2, OC3, and OC4) by printing
+ * a warning or informative message.
+ *
+ * Return: -EINVAL for @alarm = THROTTLE_OC3, otherwise 0 (success).
+ */
+static int soctherm_handle_alarm(enum soctherm_throttle_id alarm)
+{
+ int rv = -EINVAL;
+
+ switch (alarm) {
+ case THROTTLE_OC1:
+ pr_debug("soctherm: Successfully handled OC1 alarm\n");
+ rv = 0;
+ break;
+
+ case THROTTLE_OC2:
+ pr_debug("soctherm: Successfully handled OC2 alarm\n");
+ rv = 0;
+ break;
+
+ case THROTTLE_OC3:
+ pr_debug("soctherm: Successfully handled OC3 alarm\n");
+ rv = 0;
+ break;
+
+ case THROTTLE_OC4:
+ pr_debug("soctherm: Successfully handled OC4 alarm\n");
+ rv = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ if (rv)
+ pr_err("soctherm: ERROR in handling %s alarm\n",
+ throt_names[alarm]);
+
+ return rv;
+}
+
+/**
+ * soctherm_edp_isr_thread() - log an over-current interrupt request
+ * @irq: OC irq number. Currently not being used. See description
+ * @arg: a void pointer for callback, currently not being used
+ *
+ * Over-current events are handled in hardware. This function is called to log
+ * and handle any OC events that happened. Additionally, it checks every
+ * over-current interrupt registers for registers are set but
+ * was not expected (i.e. any discrepancy in interrupt status) by the function,
+ * the discrepancy will logged.
+ *
+ * Return: %IRQ_HANDLED
+ */
+static irqreturn_t soctherm_edp_isr_thread(int irq, void *arg)
+{
+ struct tegra_soctherm *ts = arg;
+ u32 st, ex, oc1, oc2, oc3, oc4;
+
+ st = readl(ts->regs + OC_INTR_STATUS);
+
+ /* deliberately clear expected interrupts handled in SW */
+ oc1 = st & OC_INTR_OC1_MASK;
+ oc2 = st & OC_INTR_OC2_MASK;
+ oc3 = st & OC_INTR_OC3_MASK;
+ oc4 = st & OC_INTR_OC4_MASK;
+ ex = oc1 | oc2 | oc3 | oc4;
+
+ pr_err("soctherm: OC ALARM 0x%08x\n", ex);
+ if (ex) {
+ writel(st, ts->regs + OC_INTR_STATUS);
+ st &= ~ex;
+
+ if (oc1 && !soctherm_handle_alarm(THROTTLE_OC1))
+ soctherm_oc_intr_enable(ts, THROTTLE_OC1, true);
+
+ if (oc2 && !soctherm_handle_alarm(THROTTLE_OC2))
+ soctherm_oc_intr_enable(ts, THROTTLE_OC2, true);
+
+ if (oc3 && !soctherm_handle_alarm(THROTTLE_OC3))
+ soctherm_oc_intr_enable(ts, THROTTLE_OC3, true);
+
+ if (oc4 && !soctherm_handle_alarm(THROTTLE_OC4))
+ soctherm_oc_intr_enable(ts, THROTTLE_OC4, true);
+
+ if (oc1 && soc_irq_cdata.irq_enable & BIT(0))
+ handle_nested_irq(
+ irq_find_mapping(soc_irq_cdata.domain, 0));
+
+ if (oc2 && soc_irq_cdata.irq_enable & BIT(1))
+ handle_nested_irq(
+ irq_find_mapping(soc_irq_cdata.domain, 1));
+
+ if (oc3 && soc_irq_cdata.irq_enable & BIT(2))
+ handle_nested_irq(
+ irq_find_mapping(soc_irq_cdata.domain, 2));
+
+ if (oc4 && soc_irq_cdata.irq_enable & BIT(3))
+ handle_nested_irq(
+ irq_find_mapping(soc_irq_cdata.domain, 3));
+ }
+
+ if (st) {
+ pr_err("soctherm: Ignored unexpected OC ALARM 0x%08x\n", st);
+ writel(st, ts->regs + OC_INTR_STATUS);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * soctherm_edp_isr() - Disables any active interrupts
+ * @irq: The interrupt request number
+ * @arg: Opaque pointer to an argument
+ *
+ * Writes to the OC_INTR_DISABLE register the over current interrupt status,
+ * masking any asserted interrupts. Doing this prevents the same interrupts
+ * from triggering this isr repeatedly. The thread woken by this isr will
+ * handle asserted interrupts and subsequently unmask/re-enable them.
+ *
+ * The OC_INTR_DISABLE register indicates which OC interrupts
+ * have been disabled.
+ *
+ * Return: %IRQ_WAKE_THREAD, handler requests to wake the handler thread
+ */
+static irqreturn_t soctherm_edp_isr(int irq, void *arg)
+{
+ struct tegra_soctherm *ts = arg;
+ u32 r;
+
+ if (!ts)
+ return IRQ_NONE;
+
+ r = readl(ts->regs + OC_INTR_STATUS);
+ writel(r, ts->regs + OC_INTR_DISABLE);
+
+ return IRQ_WAKE_THREAD;
+}
+
+/**
+ * soctherm_oc_irq_lock() - locks the over-current interrupt request
+ * @data: Interrupt request data
+ *
+ * Looks up the chip data from @data and locks the mutex associated with
+ * a particular over-current interrupt request.
+ */
+static void soctherm_oc_irq_lock(struct irq_data *data)
+{
+ struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data);
+
+ mutex_lock(&d->irq_lock);
+}
+
+/**
+ * soctherm_oc_irq_sync_unlock() - Unlocks the OC interrupt request
+ * @data: Interrupt request data
+ *
+ * Looks up the interrupt request data @data and unlocks the mutex associated
+ * with a particular over-current interrupt request.
+ */
+static void soctherm_oc_irq_sync_unlock(struct irq_data *data)
+{
+ struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data);
+
+ mutex_unlock(&d->irq_lock);
+}
+
+/**
+ * soctherm_oc_irq_enable() - Enables the SOC_THERM over-current interrupt queue
+ * @data: irq_data structure of the chip
+ *
+ * Sets the irq_enable bit of SOC_THERM allowing SOC_THERM
+ * to respond to over-current interrupts.
+ *
+ */
+static void soctherm_oc_irq_enable(struct irq_data *data)
+{
+ struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data);
+
+ d->irq_enable |= BIT(data->hwirq);
+}
+
+/**
+ * soctherm_oc_irq_disable() - Disables overcurrent interrupt requests
+ * @data: The interrupt request information
+ *
+ * Clears the interrupt request enable bit of the overcurrent
+ * interrupt request chip data.
+ *
+ * Return: Nothing is returned (void)
+ */
+static void soctherm_oc_irq_disable(struct irq_data *data)
+{
+ struct soctherm_oc_irq_chip_data *d = irq_data_get_irq_chip_data(data);
+
+ d->irq_enable &= ~BIT(data->hwirq);
+}
+
+static int soctherm_oc_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ return 0;
+}
+
+/**
+ * soctherm_oc_irq_map() - SOC_THERM interrupt request domain mapper
+ * @h: Interrupt request domain
+ * @virq: Virtual interrupt request number
+ * @hw: Hardware interrupt request number
+ *
+ * Mapping callback function for SOC_THERM's irq_domain. When a SOC_THERM
+ * interrupt request is called, the irq_domain takes the request's virtual
+ * request number (much like a virtual memory address) and maps it to a
+ * physical hardware request number.
+ *
+ * When a mapping doesn't already exist for a virtual request number, the
+ * irq_domain calls this function to associate the virtual request number with
+ * a hardware request number.
+ *
+ * Return: 0
+ */
+static int soctherm_oc_irq_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct soctherm_oc_irq_chip_data *data = h->host_data;
+
+ irq_set_chip_data(virq, data);
+ irq_set_chip(virq, &data->irq_chip);
+ irq_set_nested_thread(virq, 1);
+ return 0;
+}
+
+/**
+ * soctherm_irq_domain_xlate_twocell() - xlate for soctherm interrupts
+ * @d: Interrupt request domain
+ * @ctrlr: Controller device tree node
+ * @intspec: Array of u32s from DTs "interrupt" property
+ * @intsize: Number of values inside the intspec array
+ * @out_hwirq: HW IRQ value associated with this interrupt
+ * @out_type: The IRQ SENSE type for this interrupt.
+ *
+ * This Device Tree IRQ specifier translation function will translate a
+ * specific "interrupt" as defined by 2 DT values where the cell values map
+ * the hwirq number + 1 and linux irq flags. Since the output is the hwirq
+ * number, this function will subtract 1 from the value listed in DT.
+ *
+ * Return: 0
+ */
+static int soctherm_irq_domain_xlate_twocell(struct irq_domain *d,
+ struct device_node *ctrlr, const u32 *intspec, unsigned int intsize,
+ irq_hw_number_t *out_hwirq, unsigned int *out_type)
+{
+ if (WARN_ON(intsize < 2))
+ return -EINVAL;
+
+ /*
+ * The HW value is 1 index less than the DT IRQ values.
+ * i.e. OC4 goes to HW index 3.
+ */
+ *out_hwirq = intspec[0] - 1;
+ *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;
+ return 0;
+}
+
+static const struct irq_domain_ops soctherm_oc_domain_ops = {
+ .map = soctherm_oc_irq_map,
+ .xlate = soctherm_irq_domain_xlate_twocell,
+};
+
+/**
+ * soctherm_oc_int_init() - Initial enabling of the over
+ * current interrupts
+ * @fwnode: The devicetree node for soctherm
+ * @num_irqs: The number of new interrupt requests
+ *
+ * Sets the over current interrupt request chip data
+ *
+ * Return: 0 on success or if overcurrent interrupts are not enabled,
+ * -ENOMEM (out of memory), or irq_base if the function failed to
+ * allocate the 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__);
+ return 0;
+ }
+
+ mutex_init(&soc_irq_cdata.irq_lock);
+ soc_irq_cdata.irq_enable = 0;
+
+ soc_irq_cdata.irq_chip.name = "soc_therm_oc";
+ soc_irq_cdata.irq_chip.irq_bus_lock = soctherm_oc_irq_lock;
+ soc_irq_cdata.irq_chip.irq_bus_sync_unlock =
+ soctherm_oc_irq_sync_unlock;
+ soc_irq_cdata.irq_chip.irq_disable = soctherm_oc_irq_disable;
+ soc_irq_cdata.irq_chip.irq_enable = soctherm_oc_irq_enable;
+ 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_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;
+ }
+
+ pr_debug("%s(): OC interrupts enabled successful\n", __func__);
+ return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int regs_show(struct seq_file *s, void *data)
+{
+ struct platform_device *pdev = s->private;
+ struct tegra_soctherm *ts = platform_get_drvdata(pdev);
+ const struct tegra_tsensor *tsensors = ts->soc->tsensors;
+ const struct tegra_tsensor_group **ttgs = ts->soc->ttgs;
+ u32 r, state;
+ int i, level;
+
+ seq_puts(s, "-----TSENSE (convert HW)-----\n");
+
+ for (i = 0; i < ts->soc->num_tsensors; i++) {
+ r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG1);
+ state = REG_GET_MASK(r, SENSOR_CONFIG1_TEMP_ENABLE);
+
+ seq_printf(s, "%s: ", tsensors[i].name);
+ seq_printf(s, "En(%d) ", state);
+
+ if (!state) {
+ seq_puts(s, "\n");
+ continue;
+ }
+
+ state = REG_GET_MASK(r, SENSOR_CONFIG1_TIDDQ_EN_MASK);
+ seq_printf(s, "tiddq(%d) ", state);
+ state = REG_GET_MASK(r, SENSOR_CONFIG1_TEN_COUNT_MASK);
+ seq_printf(s, "ten_count(%d) ", state);
+ state = REG_GET_MASK(r, SENSOR_CONFIG1_TSAMPLE_MASK);
+ seq_printf(s, "tsample(%d) ", state + 1);
+
+ r = readl(ts->regs + tsensors[i].base + SENSOR_STATUS1);
+ state = REG_GET_MASK(r, SENSOR_STATUS1_TEMP_VALID_MASK);
+ seq_printf(s, "Temp(%d/", state);
+ state = REG_GET_MASK(r, SENSOR_STATUS1_TEMP_MASK);
+ seq_printf(s, "%d) ", translate_temp(state));
+
+ r = readl(ts->regs + tsensors[i].base + SENSOR_STATUS0);
+ state = REG_GET_MASK(r, SENSOR_STATUS0_VALID_MASK);
+ seq_printf(s, "Capture(%d/", state);
+ state = REG_GET_MASK(r, SENSOR_STATUS0_CAPTURE_MASK);
+ seq_printf(s, "%d) ", state);
+
+ r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG0);
+ state = REG_GET_MASK(r, SENSOR_CONFIG0_STOP);
+ seq_printf(s, "Stop(%d) ", state);
+ state = REG_GET_MASK(r, SENSOR_CONFIG0_TALL_MASK);
+ seq_printf(s, "Tall(%d) ", state);
+ state = REG_GET_MASK(r, SENSOR_CONFIG0_TCALC_OVER);
+ seq_printf(s, "Over(%d/", state);
+ state = REG_GET_MASK(r, SENSOR_CONFIG0_OVER);
+ seq_printf(s, "%d/", state);
+ state = REG_GET_MASK(r, SENSOR_CONFIG0_CPTR_OVER);
+ seq_printf(s, "%d) ", state);
+
+ r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG2);
+ state = REG_GET_MASK(r, SENSOR_CONFIG2_THERMA_MASK);
+ seq_printf(s, "Therm_A/B(%d/", state);
+ state = REG_GET_MASK(r, SENSOR_CONFIG2_THERMB_MASK);
+ seq_printf(s, "%d)\n", (s16)state);
+ }
+
+ r = readl(ts->regs + SENSOR_PDIV);
+ seq_printf(s, "PDIV: 0x%x\n", r);
+
+ r = readl(ts->regs + SENSOR_HOTSPOT_OFF);
+ seq_printf(s, "HOTSPOT: 0x%x\n", r);
+
+ seq_puts(s, "\n");
+ seq_puts(s, "-----SOC_THERM-----\n");
+
+ r = readl(ts->regs + SENSOR_TEMP1);
+ state = REG_GET_MASK(r, SENSOR_TEMP1_CPU_TEMP_MASK);
+ seq_printf(s, "Temperatures: CPU(%d) ", translate_temp(state));
+ state = REG_GET_MASK(r, SENSOR_TEMP1_GPU_TEMP_MASK);
+ seq_printf(s, " GPU(%d) ", translate_temp(state));
+ r = readl(ts->regs + SENSOR_TEMP2);
+ state = REG_GET_MASK(r, SENSOR_TEMP2_PLLX_TEMP_MASK);
+ seq_printf(s, " PLLX(%d) ", translate_temp(state));
+ state = REG_GET_MASK(r, SENSOR_TEMP2_MEM_TEMP_MASK);
+ seq_printf(s, " MEM(%d)\n", translate_temp(state));
+
+ for (i = 0; i < ts->soc->num_ttgs; i++) {
+ seq_printf(s, "%s:\n", ttgs[i]->name);
+ for (level = 0; level < 4; level++) {
+ s32 v;
+ u32 mask;
+ u16 off = ttgs[i]->thermctl_lvl0_offset;
+
+ r = readl(ts->regs + THERMCTL_LVL_REG(off, level));
+
+ mask = ttgs[i]->thermctl_lvl0_up_thresh_mask;
+ state = REG_GET_MASK(r, mask);
+ v = sign_extend32(state, ts->soc->bptt - 1);
+ v *= ts->soc->thresh_grain;
+ seq_printf(s, " %d: Up/Dn(%d /", level, v);
+
+ mask = ttgs[i]->thermctl_lvl0_dn_thresh_mask;
+ state = REG_GET_MASK(r, mask);
+ v = sign_extend32(state, ts->soc->bptt - 1);
+ v *= ts->soc->thresh_grain;
+ seq_printf(s, "%d ) ", v);
+
+ mask = THERMCTL_LVL0_CPU0_EN_MASK;
+ state = REG_GET_MASK(r, mask);
+ seq_printf(s, "En(%d) ", state);
+
+ mask = THERMCTL_LVL0_CPU0_CPU_THROT_MASK;
+ state = REG_GET_MASK(r, mask);
+ seq_puts(s, "CPU Throt");
+ if (!state)
+ seq_printf(s, "(%s) ", "none");
+ else if (state == THERMCTL_LVL0_CPU0_CPU_THROT_LIGHT)
+ seq_printf(s, "(%s) ", "L");
+ else if (state == THERMCTL_LVL0_CPU0_CPU_THROT_HEAVY)
+ seq_printf(s, "(%s) ", "H");
+ else
+ seq_printf(s, "(%s) ", "H+L");
+
+ mask = THERMCTL_LVL0_CPU0_GPU_THROT_MASK;
+ state = REG_GET_MASK(r, mask);
+ seq_puts(s, "GPU Throt");
+ if (!state)
+ seq_printf(s, "(%s) ", "none");
+ else if (state == THERMCTL_LVL0_CPU0_GPU_THROT_LIGHT)
+ seq_printf(s, "(%s) ", "L");
+ else if (state == THERMCTL_LVL0_CPU0_GPU_THROT_HEAVY)
+ seq_printf(s, "(%s) ", "H");
+ else
+ seq_printf(s, "(%s) ", "H+L");
+
+ mask = THERMCTL_LVL0_CPU0_STATUS_MASK;
+ state = REG_GET_MASK(r, mask);
+ seq_printf(s, "Status(%s)\n",
+ state == 0 ? "LO" :
+ state == 1 ? "In" :
+ state == 2 ? "Res" : "HI");
+ }
+ }
+
+ r = readl(ts->regs + THERMCTL_STATS_CTL);
+ seq_printf(s, "STATS: Up(%s) Dn(%s)\n",
+ r & STATS_CTL_EN_UP ? "En" : "--",
+ r & STATS_CTL_EN_DN ? "En" : "--");
+
+ for (level = 0; level < 4; level++) {
+ u16 off;
+
+ off = THERMCTL_LVL0_UP_STATS;
+ r = readl(ts->regs + THERMCTL_LVL_REG(off, level));
+ seq_printf(s, " Level_%d Up(%d) ", level, r);
+
+ off = THERMCTL_LVL0_DN_STATS;
+ r = readl(ts->regs + THERMCTL_LVL_REG(off, level));
+ seq_printf(s, "Dn(%d)\n", r);
+ }
+
+ r = readl(ts->regs + THERMCTL_THERMTRIP_CTL);
+ state = REG_GET_MASK(r, ttgs[0]->thermtrip_any_en_mask);
+ seq_printf(s, "Thermtrip Any En(%d)\n", state);
+ for (i = 0; i < ts->soc->num_ttgs; i++) {
+ state = REG_GET_MASK(r, ttgs[i]->thermtrip_enable_mask);
+ seq_printf(s, " %s En(%d) ", ttgs[i]->name, state);
+ state = REG_GET_MASK(r, ttgs[i]->thermtrip_threshold_mask);
+ state *= ts->soc->thresh_grain;
+ seq_printf(s, "Thresh(%d)\n", state);
+ }
+
+ r = readl(ts->regs + THROT_GLOBAL_CFG);
+ seq_puts(s, "\n");
+ seq_printf(s, "GLOBAL THROTTLE CONFIG: 0x%08x\n", r);
+
+ seq_puts(s, "---------------------------------------------------\n");
+ r = readl(ts->regs + THROT_STATUS);
+ state = REG_GET_MASK(r, THROT_STATUS_BREACH_MASK);
+ seq_printf(s, "THROT STATUS: breach(%d) ", state);
+ state = REG_GET_MASK(r, THROT_STATUS_STATE_MASK);
+ seq_printf(s, "state(%d) ", state);
+ state = REG_GET_MASK(r, THROT_STATUS_ENABLED_MASK);
+ seq_printf(s, "enabled(%d)\n", state);
+
+ r = readl(ts->regs + CPU_PSKIP_STATUS);
+ if (ts->soc->use_ccroc) {
+ state = REG_GET_MASK(r, XPU_PSKIP_STATUS_ENABLED_MASK);
+ seq_printf(s, "CPU PSKIP STATUS: enabled(%d)\n", state);
+ } else {
+ state = REG_GET_MASK(r, XPU_PSKIP_STATUS_M_MASK);
+ seq_printf(s, "CPU PSKIP STATUS: M(%d) ", state);
+ state = REG_GET_MASK(r, XPU_PSKIP_STATUS_N_MASK);
+ seq_printf(s, "N(%d) ", state);
+ state = REG_GET_MASK(r, XPU_PSKIP_STATUS_ENABLED_MASK);
+ seq_printf(s, "enabled(%d)\n", state);
+ }
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(regs);
+
+static void soctherm_debug_init(struct platform_device *pdev)
+{
+ struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
+ struct dentry *root;
+
+ root = debugfs_create_dir("soctherm", NULL);
+
+ tegra->debugfs_dir = root;
+
+ debugfs_create_file("reg_contents", 0644, root, pdev, &regs_fops);
+}
+#else
+static inline void soctherm_debug_init(struct platform_device *pdev) {}
+#endif
+
+static int soctherm_clk_enable(struct platform_device *pdev, bool enable)
+{
+ struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
+ int err;
+
+ if (!tegra->clock_soctherm || !tegra->clock_tsensor)
+ return -EINVAL;
+
+ reset_control_assert(tegra->reset);
+
+ if (enable) {
+ err = clk_prepare_enable(tegra->clock_soctherm);
+ if (err) {
+ reset_control_deassert(tegra->reset);
+ return err;
+ }
+
+ err = clk_prepare_enable(tegra->clock_tsensor);
+ if (err) {
+ clk_disable_unprepare(tegra->clock_soctherm);
+ reset_control_deassert(tegra->reset);
+ return err;
+ }
+ } else {
+ clk_disable_unprepare(tegra->clock_tsensor);
+ clk_disable_unprepare(tegra->clock_soctherm);
+ }
+
+ reset_control_deassert(tegra->reset);
+
+ return 0;
+}
+
+static void soctherm_clk_disable(void *data)
+{
+ struct platform_device *pdev = data;
+
+ soctherm_clk_enable(pdev, false);
+}
+
+static int throt_get_cdev_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *max_state)
+{
+ *max_state = 1;
+ return 0;
+}
+
+static int throt_get_cdev_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *cur_state)
+{
+ struct tegra_soctherm *ts = cdev->devdata;
+ u32 r;
+
+ r = readl(ts->regs + THROT_STATUS);
+ if (REG_GET_MASK(r, THROT_STATUS_STATE_MASK))
+ *cur_state = 1;
+ else
+ *cur_state = 0;
+
+ return 0;
+}
+
+static int throt_set_cdev_state(struct thermal_cooling_device *cdev,
+ unsigned long cur_state)
+{
+ return 0;
+}
+
+static const struct thermal_cooling_device_ops throt_cooling_ops = {
+ .get_max_state = throt_get_cdev_max_state,
+ .get_cur_state = throt_get_cdev_cur_state,
+ .set_cur_state = throt_set_cdev_state,
+};
+
+static int soctherm_thermtrips_parse(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra_soctherm *ts = dev_get_drvdata(dev);
+ struct tsensor_group_thermtrips *tt = ts->soc->thermtrips;
+ const int max_num_prop = ts->soc->num_ttgs * 2;
+ u32 *tlb;
+ int i, j, n, ret;
+
+ if (!tt)
+ return -ENOMEM;
+
+ n = of_property_count_u32_elems(dev->of_node, "nvidia,thermtrips");
+ if (n <= 0) {
+ dev_info(dev,
+ "missing thermtrips, will use critical trips as shut down temp\n");
+ return n;
+ }
+
+ n = min(max_num_prop, n);
+
+ tlb = devm_kcalloc(&pdev->dev, max_num_prop, sizeof(u32), GFP_KERNEL);
+ if (!tlb)
+ return -ENOMEM;
+ ret = of_property_read_u32_array(dev->of_node, "nvidia,thermtrips",
+ tlb, n);
+ if (ret) {
+ dev_err(dev, "invalid num ele: thermtrips:%d\n", ret);
+ return ret;
+ }
+
+ i = 0;
+ for (j = 0; j < n; j = j + 2) {
+ if (tlb[j] >= TEGRA124_SOCTHERM_SENSOR_NUM)
+ continue;
+
+ tt[i].id = tlb[j];
+ tt[i].temp = tlb[j + 1];
+ i++;
+ }
+
+ return 0;
+}
+
+static void soctherm_oc_cfg_parse(struct device *dev,
+ struct device_node *np_oc,
+ struct soctherm_throt_cfg *stc)
+{
+ u32 val;
+
+ if (of_property_read_bool(np_oc, "nvidia,polarity-active-low"))
+ stc->oc_cfg.active_low = 1;
+ else
+ stc->oc_cfg.active_low = 0;
+
+ if (!of_property_read_u32(np_oc, "nvidia,count-threshold", &val)) {
+ stc->oc_cfg.intr_en = 1;
+ stc->oc_cfg.alarm_cnt_thresh = val;
+ }
+
+ if (!of_property_read_u32(np_oc, "nvidia,throttle-period-us", &val))
+ stc->oc_cfg.throt_period = val;
+
+ if (!of_property_read_u32(np_oc, "nvidia,alarm-filter", &val))
+ stc->oc_cfg.alarm_filter = val;
+
+ /* BRIEF throttling by default, do not support STICKY */
+ stc->oc_cfg.mode = OC_THROTTLE_MODE_BRIEF;
+}
+
+static int soctherm_throt_cfg_parse(struct device *dev,
+ struct device_node *np,
+ struct soctherm_throt_cfg *stc)
+{
+ struct tegra_soctherm *ts = dev_get_drvdata(dev);
+ int ret;
+ u32 val;
+
+ ret = of_property_read_u32(np, "nvidia,priority", &val);
+ if (ret) {
+ dev_err(dev, "throttle-cfg: %s: invalid priority\n", stc->name);
+ return -EINVAL;
+ }
+ stc->priority = val;
+
+ ret = of_property_read_u32(np, ts->soc->use_ccroc ?
+ "nvidia,cpu-throt-level" :
+ "nvidia,cpu-throt-percent", &val);
+ if (!ret) {
+ if (ts->soc->use_ccroc &&
+ val <= TEGRA_SOCTHERM_THROT_LEVEL_HIGH)
+ stc->cpu_throt_level = val;
+ else if (!ts->soc->use_ccroc && val <= 100)
+ stc->cpu_throt_depth = val;
+ else
+ goto err;
+ } else {
+ goto err;
+ }
+
+ ret = of_property_read_u32(np, "nvidia,gpu-throt-level", &val);
+ if (!ret && val <= TEGRA_SOCTHERM_THROT_LEVEL_HIGH)
+ stc->gpu_throt_level = val;
+ else
+ goto err;
+
+ return 0;
+
+err:
+ dev_err(dev, "throttle-cfg: %s: no throt prop or invalid prop\n",
+ stc->name);
+ return -EINVAL;
+}
+
+/**
+ * soctherm_init_hw_throt_cdev() - Parse the HW throttle configurations
+ * and register them as cooling devices.
+ * @pdev: Pointer to platform_device struct
+ */
+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;
+ const char *name;
+ int i;
+
+ for (i = 0; i < THROTTLE_SIZE; i++) {
+ ts->throt_cfgs[i].name = throt_names[i];
+ ts->throt_cfgs[i].id = i;
+ ts->throt_cfgs[i].init = false;
+ }
+
+ np_stc = of_get_child_by_name(dev->of_node, "throttle-cfgs");
+ if (!np_stc) {
+ dev_info(dev,
+ "throttle-cfg: no throttle-cfgs - not enabling\n");
+ return;
+ }
+
+ for_each_child_of_node_scoped(np_stc, np_stcc) {
+ struct soctherm_throt_cfg *stc;
+ struct thermal_cooling_device *tcd;
+ int err;
+
+ name = np_stcc->name;
+ stc = find_throttle_cfg_by_name(ts, name);
+ if (!stc) {
+ dev_err(dev,
+ "throttle-cfg: could not find %s\n", name);
+ continue;
+ }
+
+ if (stc->init) {
+ dev_err(dev, "throttle-cfg: %s: redefined!\n", name);
+ break;
+ }
+
+ err = soctherm_throt_cfg_parse(dev, np_stcc, stc);
+ if (err)
+ continue;
+
+ if (stc->id >= THROTTLE_OC1) {
+ soctherm_oc_cfg_parse(dev, np_stcc, stc);
+ stc->init = true;
+ } else {
+
+ tcd = devm_thermal_of_child_cooling_device_register(dev, np_stcc,
+ (char *)name, ts,
+ &throt_cooling_ops);
+ if (IS_ERR_OR_NULL(tcd)) {
+ dev_err(dev,
+ "throttle-cfg: %s: failed to register cooling device\n",
+ name);
+ continue;
+ }
+ stc->cdev = tcd;
+ stc->init = true;
+ }
+
+ }
+
+ of_node_put(np_stc);
+}
+
+/**
+ * throttlectl_cpu_level_cfg() - programs CCROC NV_THERM level config
+ * @ts: pointer to a struct tegra_soctherm
+ * @level: describing the level LOW/MED/HIGH of throttling
+ *
+ * It's necessary to set up the CPU-local CCROC NV_THERM instance with
+ * the M/N values desired for each level. This function does this.
+ *
+ * This function pre-programs the CCROC NV_THERM levels in terms of
+ * pre-configured "Low", "Medium" or "Heavy" throttle levels which are
+ * mapped to THROT_LEVEL_LOW, THROT_LEVEL_MED and THROT_LEVEL_HVY.
+ */
+static void throttlectl_cpu_level_cfg(struct tegra_soctherm *ts, int level)
+{
+ u8 depth, dividend;
+ u32 r;
+
+ switch (level) {
+ case TEGRA_SOCTHERM_THROT_LEVEL_LOW:
+ depth = 50;
+ break;
+ case TEGRA_SOCTHERM_THROT_LEVEL_MED:
+ depth = 75;
+ break;
+ case TEGRA_SOCTHERM_THROT_LEVEL_HIGH:
+ depth = 80;
+ break;
+ case TEGRA_SOCTHERM_THROT_LEVEL_NONE:
+ return;
+ default:
+ return;
+ }
+
+ dividend = THROT_DEPTH_DIVIDEND(depth);
+
+ /* setup PSKIP in ccroc nv_therm registers */
+ r = ccroc_readl(ts, CCROC_THROT_PSKIP_RAMP_CPU_REG(level));
+ r = REG_SET_MASK(r, CCROC_THROT_PSKIP_RAMP_DURATION_MASK, 0xff);
+ r = REG_SET_MASK(r, CCROC_THROT_PSKIP_RAMP_STEP_MASK, 0xf);
+ ccroc_writel(ts, r, CCROC_THROT_PSKIP_RAMP_CPU_REG(level));
+
+ r = ccroc_readl(ts, CCROC_THROT_PSKIP_CTRL_CPU_REG(level));
+ r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_ENB_MASK, 1);
+ r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_DIVIDEND_MASK, dividend);
+ r = REG_SET_MASK(r, CCROC_THROT_PSKIP_CTRL_DIVISOR_MASK, 0xff);
+ ccroc_writel(ts, r, CCROC_THROT_PSKIP_CTRL_CPU_REG(level));
+}
+
+/**
+ * throttlectl_cpu_level_select() - program CPU pulse skipper config
+ * @ts: pointer to a struct tegra_soctherm
+ * @throt: the LIGHT/HEAVY of throttle event id
+ *
+ * Pulse skippers are used to throttle clock frequencies. This
+ * function programs the pulse skippers based on @throt and platform
+ * data. This function is used on SoCs which have CPU-local pulse
+ * skipper control, such as T13x. It programs soctherm's interface to
+ * Denver:CCROC NV_THERM in terms of Low, Medium and HIGH throttling
+ * vectors. PSKIP_BYPASS mode is set as required per HW spec.
+ */
+static void throttlectl_cpu_level_select(struct tegra_soctherm *ts,
+ enum soctherm_throttle_id throt)
+{
+ u32 r, throt_vect;
+
+ /* Denver:CCROC NV_THERM interface N:3 Mapping */
+ switch (ts->throt_cfgs[throt].cpu_throt_level) {
+ case TEGRA_SOCTHERM_THROT_LEVEL_LOW:
+ throt_vect = THROT_VECT_LOW;
+ break;
+ case TEGRA_SOCTHERM_THROT_LEVEL_MED:
+ throt_vect = THROT_VECT_MED;
+ break;
+ case TEGRA_SOCTHERM_THROT_LEVEL_HIGH:
+ throt_vect = THROT_VECT_HIGH;
+ break;
+ default:
+ throt_vect = THROT_VECT_NONE;
+ break;
+ }
+
+ r = readl(ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU));
+ r = REG_SET_MASK(r, THROT_PSKIP_CTRL_ENABLE_MASK, 1);
+ r = REG_SET_MASK(r, THROT_PSKIP_CTRL_VECT_CPU_MASK, throt_vect);
+ r = REG_SET_MASK(r, THROT_PSKIP_CTRL_VECT2_CPU_MASK, throt_vect);
+ writel(r, ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU));
+
+ /* bypass sequencer in soc_therm as it is programmed in ccroc */
+ r = REG_SET_MASK(0, THROT_PSKIP_RAMP_SEQ_BYPASS_MODE_MASK, 1);
+ writel(r, ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU));
+}
+
+/**
+ * throttlectl_cpu_mn() - program CPU pulse skipper configuration
+ * @ts: pointer to a struct tegra_soctherm
+ * @throt: the LIGHT/HEAVY of throttle event id
+ *
+ * Pulse skippers are used to throttle clock frequencies. This
+ * function programs the pulse skippers based on @throt and platform
+ * data. This function is used for CPUs that have "remote" pulse
+ * skipper control, e.g., the CPU pulse skipper is controlled by the
+ * SOC_THERM IP block. (SOC_THERM is located outside the CPU
+ * complex.)
+ */
+static void throttlectl_cpu_mn(struct tegra_soctherm *ts,
+ enum soctherm_throttle_id throt)
+{
+ u32 r;
+ int depth;
+ u8 dividend;
+
+ depth = ts->throt_cfgs[throt].cpu_throt_depth;
+ dividend = THROT_DEPTH_DIVIDEND(depth);
+
+ r = readl(ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU));
+ r = REG_SET_MASK(r, THROT_PSKIP_CTRL_ENABLE_MASK, 1);
+ r = REG_SET_MASK(r, THROT_PSKIP_CTRL_DIVIDEND_MASK, dividend);
+ r = REG_SET_MASK(r, THROT_PSKIP_CTRL_DIVISOR_MASK, 0xff);
+ writel(r, ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_CPU));
+
+ r = readl(ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU));
+ r = REG_SET_MASK(r, THROT_PSKIP_RAMP_DURATION_MASK, 0xff);
+ r = REG_SET_MASK(r, THROT_PSKIP_RAMP_STEP_MASK, 0xf);
+ writel(r, ts->regs + THROT_PSKIP_RAMP(throt, THROTTLE_DEV_CPU));
+}
+
+/**
+ * throttlectl_gpu_level_select() - selects throttling level for GPU
+ * @ts: pointer to a struct tegra_soctherm
+ * @throt: the LIGHT/HEAVY of throttle event id
+ *
+ * This function programs soctherm's interface to GK20a NV_THERM to select
+ * pre-configured "Low", "Medium" or "Heavy" throttle levels.
+ *
+ * Return: boolean true if HW was programmed
+ */
+static void throttlectl_gpu_level_select(struct tegra_soctherm *ts,
+ enum soctherm_throttle_id throt)
+{
+ u32 r, level, throt_vect;
+
+ level = ts->throt_cfgs[throt].gpu_throt_level;
+ throt_vect = THROT_LEVEL_TO_DEPTH(level);
+ r = readl(ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_GPU));
+ r = REG_SET_MASK(r, THROT_PSKIP_CTRL_ENABLE_MASK, 1);
+ r = REG_SET_MASK(r, THROT_PSKIP_CTRL_VECT_GPU_MASK, throt_vect);
+ writel(r, ts->regs + THROT_PSKIP_CTRL(throt, THROTTLE_DEV_GPU));
+}
+
+static int soctherm_oc_cfg_program(struct tegra_soctherm *ts,
+ enum soctherm_throttle_id throt)
+{
+ u32 r;
+ struct soctherm_oc_cfg *oc = &ts->throt_cfgs[throt].oc_cfg;
+
+ if (oc->mode == OC_THROTTLE_MODE_DISABLED)
+ return -EINVAL;
+
+ r = REG_SET_MASK(0, OC1_CFG_HW_RESTORE_MASK, 1);
+ r = REG_SET_MASK(r, OC1_CFG_THROTTLE_MODE_MASK, oc->mode);
+ r = REG_SET_MASK(r, OC1_CFG_ALARM_POLARITY_MASK, oc->active_low);
+ r = REG_SET_MASK(r, OC1_CFG_EN_THROTTLE_MASK, 1);
+ writel(r, ts->regs + ALARM_CFG(throt));
+ writel(oc->throt_period, ts->regs + ALARM_THROTTLE_PERIOD(throt));
+ writel(oc->alarm_cnt_thresh, ts->regs + ALARM_CNT_THRESHOLD(throt));
+ writel(oc->alarm_filter, ts->regs + ALARM_FILTER(throt));
+ soctherm_oc_intr_enable(ts, throt, oc->intr_en);
+
+ return 0;
+}
+
+/**
+ * soctherm_throttle_program() - programs pulse skippers' configuration
+ * @ts: pointer to a struct tegra_soctherm
+ * @throt: the LIGHT/HEAVY of the throttle event id.
+ *
+ * Pulse skippers are used to throttle clock frequencies.
+ * This function programs the pulse skippers.
+ */
+static void soctherm_throttle_program(struct tegra_soctherm *ts,
+ enum soctherm_throttle_id throt)
+{
+ u32 r;
+ struct soctherm_throt_cfg stc = ts->throt_cfgs[throt];
+
+ if (!stc.init)
+ return;
+
+ if ((throt >= THROTTLE_OC1) && (soctherm_oc_cfg_program(ts, throt)))
+ return;
+
+ /* Setup PSKIP parameters */
+ if (ts->soc->use_ccroc)
+ throttlectl_cpu_level_select(ts, throt);
+ else
+ throttlectl_cpu_mn(ts, throt);
+
+ throttlectl_gpu_level_select(ts, throt);
+
+ r = REG_SET_MASK(0, THROT_PRIORITY_LITE_PRIO_MASK, stc.priority);
+ writel(r, ts->regs + THROT_PRIORITY_CTRL(throt));
+
+ r = REG_SET_MASK(0, THROT_DELAY_LITE_DELAY_MASK, 0);
+ writel(r, ts->regs + THROT_DELAY_CTRL(throt));
+
+ r = readl(ts->regs + THROT_PRIORITY_LOCK);
+ r = REG_GET_MASK(r, THROT_PRIORITY_LOCK_PRIORITY_MASK);
+ if (r >= stc.priority)
+ return;
+ r = REG_SET_MASK(0, THROT_PRIORITY_LOCK_PRIORITY_MASK,
+ stc.priority);
+ writel(r, ts->regs + THROT_PRIORITY_LOCK);
+}
+
+static void tegra_soctherm_throttle(struct device *dev)
+{
+ struct tegra_soctherm *ts = dev_get_drvdata(dev);
+ u32 v;
+ int i;
+
+ /* configure LOW, MED and HIGH levels for CCROC NV_THERM */
+ if (ts->soc->use_ccroc) {
+ throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_LOW);
+ throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_MED);
+ throttlectl_cpu_level_cfg(ts, TEGRA_SOCTHERM_THROT_LEVEL_HIGH);
+ }
+
+ /* Thermal HW throttle programming */
+ for (i = 0; i < THROTTLE_SIZE; i++)
+ soctherm_throttle_program(ts, i);
+
+ v = REG_SET_MASK(0, THROT_GLOBAL_ENB_MASK, 1);
+ if (ts->soc->use_ccroc) {
+ ccroc_writel(ts, v, CCROC_GLOBAL_CFG);
+
+ v = ccroc_readl(ts, CCROC_SUPER_CCLKG_DIVIDER);
+ v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1);
+ ccroc_writel(ts, v, CCROC_SUPER_CCLKG_DIVIDER);
+ } else {
+ writel(v, ts->regs + THROT_GLOBAL_CFG);
+
+ v = readl(ts->clk_regs + CAR_SUPER_CCLKG_DIVIDER);
+ v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1);
+ writel(v, ts->clk_regs + CAR_SUPER_CCLKG_DIVIDER);
+ }
+
+ /* initialize stats collection */
+ v = STATS_CTL_CLR_DN | STATS_CTL_EN_DN |
+ STATS_CTL_CLR_UP | STATS_CTL_EN_UP;
+ writel(v, ts->regs + THERMCTL_STATS_CTL);
+}
+
+static int soctherm_interrupts_init(struct platform_device *pdev,
+ struct tegra_soctherm *tegra)
+{
+ int ret;
+
+ 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;
+ }
+
+ tegra->thermal_irq = platform_get_irq(pdev, 0);
+ if (tegra->thermal_irq < 0) {
+ dev_dbg(&pdev->dev, "get 'thermal_irq' failed.\n");
+ return 0;
+ }
+
+ tegra->edp_irq = platform_get_irq(pdev, 1);
+ if (tegra->edp_irq < 0) {
+ dev_dbg(&pdev->dev, "get 'edp_irq' failed.\n");
+ return 0;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev,
+ tegra->thermal_irq,
+ soctherm_thermal_isr,
+ soctherm_thermal_isr_thread,
+ IRQF_ONESHOT,
+ dev_name(&pdev->dev),
+ tegra);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_threaded_irq(&pdev->dev,
+ tegra->edp_irq,
+ soctherm_edp_isr,
+ soctherm_edp_isr_thread,
+ IRQF_ONESHOT,
+ "soctherm_edp",
+ tegra);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void soctherm_init(struct platform_device *pdev)
+{
+ struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
+ const struct tegra_tsensor_group **ttgs = tegra->soc->ttgs;
+ int i;
+ u32 pdiv, hotspot;
+
+ /* Initialize raw sensors */
+ for (i = 0; i < tegra->soc->num_tsensors; ++i)
+ enable_tsensor(tegra, i);
+
+ /* program pdiv and hotspot offsets per THERM */
+ pdiv = readl(tegra->regs + SENSOR_PDIV);
+ hotspot = readl(tegra->regs + SENSOR_HOTSPOT_OFF);
+ for (i = 0; i < tegra->soc->num_ttgs; ++i) {
+ pdiv = REG_SET_MASK(pdiv, ttgs[i]->pdiv_mask,
+ ttgs[i]->pdiv);
+ /* hotspot offset from PLLX, doesn't need to configure PLLX */
+ if (ttgs[i]->id == TEGRA124_SOCTHERM_SENSOR_PLLX)
+ continue;
+ hotspot = REG_SET_MASK(hotspot,
+ ttgs[i]->pllx_hotspot_mask,
+ ttgs[i]->pllx_hotspot_diff);
+ }
+ writel(pdiv, tegra->regs + SENSOR_PDIV);
+ writel(hotspot, tegra->regs + SENSOR_HOTSPOT_OFF);
+
+ /* Configure hw throttle */
+ tegra_soctherm_throttle(&pdev->dev);
+}
+
+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",
+ .data = &tegra124_soctherm,
+ },
+#endif
+#ifdef CONFIG_ARCH_TEGRA_132_SOC
+ {
+ .compatible = "nvidia,tegra132-soctherm",
+ .data = &tegra132_soctherm,
+ },
+#endif
+#ifdef CONFIG_ARCH_TEGRA_210_SOC
+ {
+ .compatible = "nvidia,tegra210-soctherm",
+ .data = &tegra210_soctherm,
+ },
+#endif
+ { },
+};
+MODULE_DEVICE_TABLE(of, tegra_soctherm_of_match);
+
+static int tegra_soctherm_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct tegra_soctherm *tegra;
+ struct thermal_zone_device *z;
+ struct tsensor_shared_calib shared_calib;
+ struct tegra_soctherm_soc *soc;
+ unsigned int i;
+ int err;
+
+ match = of_match_node(tegra_soctherm_of_match, pdev->dev.of_node);
+ if (!match)
+ return -ENODEV;
+
+ soc = (struct tegra_soctherm_soc *)match->data;
+ if (soc->num_ttgs > TEGRA124_SOCTHERM_SENSOR_NUM)
+ return -EINVAL;
+
+ tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
+ if (!tegra)
+ return -ENOMEM;
+
+ mutex_init(&tegra->thermctl_lock);
+ dev_set_drvdata(&pdev->dev, tegra);
+
+ tegra->soc = soc;
+
+ tegra->regs = devm_platform_ioremap_resource_byname(pdev, "soctherm-reg");
+ if (IS_ERR(tegra->regs)) {
+ dev_err(&pdev->dev, "can't get soctherm registers");
+ return PTR_ERR(tegra->regs);
+ }
+
+ if (!tegra->soc->use_ccroc) {
+ tegra->clk_regs = devm_platform_ioremap_resource_byname(pdev, "car-reg");
+ if (IS_ERR(tegra->clk_regs)) {
+ dev_err(&pdev->dev, "can't get car clk registers");
+ return PTR_ERR(tegra->clk_regs);
+ }
+ } else {
+ tegra->ccroc_regs = devm_platform_ioremap_resource_byname(pdev, "ccroc-reg");
+ if (IS_ERR(tegra->ccroc_regs)) {
+ dev_err(&pdev->dev, "can't get ccroc registers");
+ return PTR_ERR(tegra->ccroc_regs);
+ }
+ }
+
+ tegra->reset = devm_reset_control_get(&pdev->dev, "soctherm");
+ if (IS_ERR(tegra->reset)) {
+ dev_err(&pdev->dev, "can't get soctherm reset\n");
+ return PTR_ERR(tegra->reset);
+ }
+
+ tegra->clock_tsensor = devm_clk_get(&pdev->dev, "tsensor");
+ if (IS_ERR(tegra->clock_tsensor)) {
+ dev_err(&pdev->dev, "can't get tsensor clock\n");
+ return PTR_ERR(tegra->clock_tsensor);
+ }
+
+ tegra->clock_soctherm = devm_clk_get(&pdev->dev, "soctherm");
+ if (IS_ERR(tegra->clock_soctherm)) {
+ dev_err(&pdev->dev, "can't get soctherm clock\n");
+ return PTR_ERR(tegra->clock_soctherm);
+ }
+
+ tegra->calib = devm_kcalloc(&pdev->dev,
+ soc->num_tsensors, sizeof(u32),
+ GFP_KERNEL);
+ if (!tegra->calib)
+ return -ENOMEM;
+
+ /* calculate shared calibration data */
+ err = tegra_calc_shared_calib(soc->tfuse, &shared_calib);
+ if (err)
+ return err;
+
+ /* calculate tsensor calibration data */
+ for (i = 0; i < soc->num_tsensors; ++i) {
+ err = tegra_calc_tsensor_calib(&soc->tsensors[i],
+ &shared_calib,
+ &tegra->calib[i]);
+ if (err)
+ return err;
+ }
+
+ tegra->thermctl_tzs = devm_kcalloc(&pdev->dev,
+ soc->num_ttgs, sizeof(z),
+ GFP_KERNEL);
+ if (!tegra->thermctl_tzs)
+ return -ENOMEM;
+
+ err = soctherm_clk_enable(pdev, true);
+ if (err)
+ return err;
+
+ err = devm_add_action_or_reset(&pdev->dev, soctherm_clk_disable, pdev);
+ if (err)
+ return err;
+
+ soctherm_thermtrips_parse(pdev);
+
+ soctherm_init_hw_throt_cdev(pdev);
+
+ soctherm_init(pdev);
+
+ for (i = 0; i < soc->num_ttgs; ++i) {
+ struct tegra_thermctl_zone *zone =
+ devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL);
+ if (!zone)
+ return -ENOMEM;
+
+ zone->reg = tegra->regs + soc->ttgs[i]->sensor_temp_offset;
+ zone->dev = &pdev->dev;
+ zone->sg = soc->ttgs[i];
+ zone->ts = tegra;
+
+ z = devm_thermal_of_zone_register(&pdev->dev,
+ soc->ttgs[i]->id, zone,
+ &tegra_of_thermal_ops);
+ if (IS_ERR(z)) {
+ err = PTR_ERR(z);
+ dev_err(&pdev->dev, "failed to register sensor: %d\n",
+ err);
+ return err;
+ }
+
+ zone->tz = z;
+ tegra->thermctl_tzs[soc->ttgs[i]->id] = z;
+
+ /* Configure hw trip points */
+ err = tegra_soctherm_set_hwtrips(&pdev->dev, soc->ttgs[i], z);
+ if (err)
+ return err;
+ }
+
+ err = soctherm_interrupts_init(pdev, tegra);
+
+ soctherm_debug_init(pdev);
+
+ return 0;
+}
+
+static void tegra_soctherm_remove(struct platform_device *pdev)
+{
+ struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
+
+ debugfs_remove_recursive(tegra->debugfs_dir);
+}
+
+static int __maybe_unused soctherm_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ soctherm_clk_enable(pdev, false);
+
+ return 0;
+}
+
+static int __maybe_unused soctherm_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
+ struct tegra_soctherm_soc *soc = tegra->soc;
+ int err, i;
+
+ err = soctherm_clk_enable(pdev, true);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Resume failed: enable clocks failed\n");
+ return err;
+ }
+
+ soctherm_init(pdev);
+
+ for (i = 0; i < soc->num_ttgs; ++i) {
+ struct thermal_zone_device *tz;
+
+ tz = tegra->thermctl_tzs[soc->ttgs[i]->id];
+ err = tegra_soctherm_set_hwtrips(dev, soc->ttgs[i], tz);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Resume failed: set hwtrips failed\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(tegra_soctherm_pm, soctherm_suspend, soctherm_resume);
+
+static struct platform_driver tegra_soctherm_driver = {
+ .probe = tegra_soctherm_probe,
+ .remove = tegra_soctherm_remove,
+ .driver = {
+ .name = "tegra_soctherm",
+ .pm = &tegra_soctherm_pm,
+ .of_match_table = tegra_soctherm_of_match,
+ },
+};
+module_platform_driver(tegra_soctherm_driver);
+
+MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
+MODULE_DESCRIPTION("NVIDIA Tegra SOCTHERM thermal management driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/tegra/soctherm.h b/drivers/thermal/tegra/soctherm.h
new file mode 100644
index 000000000..aa4af9268
--- /dev/null
+++ b/drivers/thermal/tegra/soctherm.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __DRIVERS_THERMAL_TEGRA_SOCTHERM_H
+#define __DRIVERS_THERMAL_TEGRA_SOCTHERM_H
+
+#define THERMCTL_LEVEL0_GROUP_CPU 0x0
+#define THERMCTL_LEVEL0_GROUP_GPU 0x4
+#define THERMCTL_LEVEL0_GROUP_MEM 0x8
+#define THERMCTL_LEVEL0_GROUP_TSENSE 0xc
+
+#define SENSOR_CONFIG2 8
+#define SENSOR_CONFIG2_THERMA_MASK (0xffff << 16)
+#define SENSOR_CONFIG2_THERMA_SHIFT 16
+#define SENSOR_CONFIG2_THERMB_MASK 0xffff
+#define SENSOR_CONFIG2_THERMB_SHIFT 0
+
+#define THERMCTL_THERMTRIP_CTL 0x80
+/* BITs are defined in device file */
+
+#define THERMCTL_INTR_ENABLE 0x88
+#define THERMCTL_INTR_DISABLE 0x8c
+#define TH_INTR_UP_DN_EN 0x3
+#define THERM_IRQ_MEM_MASK (TH_INTR_UP_DN_EN << 24)
+#define THERM_IRQ_GPU_MASK (TH_INTR_UP_DN_EN << 16)
+#define THERM_IRQ_CPU_MASK (TH_INTR_UP_DN_EN << 8)
+#define THERM_IRQ_TSENSE_MASK (TH_INTR_UP_DN_EN << 0)
+
+#define SENSOR_PDIV 0x1c0
+#define SENSOR_PDIV_CPU_MASK (0xf << 12)
+#define SENSOR_PDIV_GPU_MASK (0xf << 8)
+#define SENSOR_PDIV_MEM_MASK (0xf << 4)
+#define SENSOR_PDIV_PLLX_MASK (0xf << 0)
+
+#define SENSOR_HOTSPOT_OFF 0x1c4
+#define SENSOR_HOTSPOT_CPU_MASK (0xff << 16)
+#define SENSOR_HOTSPOT_GPU_MASK (0xff << 8)
+#define SENSOR_HOTSPOT_MEM_MASK (0xff << 0)
+
+#define SENSOR_TEMP1 0x1c8
+#define SENSOR_TEMP1_CPU_TEMP_MASK (0xffff << 16)
+#define SENSOR_TEMP1_GPU_TEMP_MASK 0xffff
+#define SENSOR_TEMP2 0x1cc
+#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
+ * @id: numeric ID of the temperature sensor group
+ * @sensor_temp_offset: offset of the SENSOR_TEMP* register
+ * @sensor_temp_mask: bit mask for this sensor group in SENSOR_TEMP* register
+ * @pdiv: the sensor count post-divider to use during runtime
+ * @pdiv_ate: the sensor count post-divider used during automated test
+ * @pdiv_mask: register bitfield mask for the PDIV field for this sensor
+ * @pllx_hotspot_diff: hotspot offset from the PLLX sensor, must be 0 for
+ PLLX sensor group
+ * @pllx_hotspot_mask: register bitfield mask for the HOTSPOT field
+ */
+struct tegra_tsensor_group {
+ const char *name;
+ u8 id;
+ u16 sensor_temp_offset;
+ u32 sensor_temp_mask;
+ u32 pdiv, pdiv_ate, pdiv_mask;
+ u32 pllx_hotspot_diff, pllx_hotspot_mask;
+ u32 thermtrip_enable_mask;
+ u32 thermtrip_any_en_mask;
+ u32 thermtrip_threshold_mask;
+ u32 thermctl_isr_mask;
+ u16 thermctl_lvl0_offset;
+ u32 thermctl_lvl0_up_thresh_mask;
+ u32 thermctl_lvl0_dn_thresh_mask;
+};
+
+struct tegra_tsensor_configuration {
+ u32 tall, tiddq_en, ten_count, pdiv, pdiv_ate, tsample, tsample_ate;
+};
+
+struct tegra_tsensor {
+ const char *name;
+ const u32 base;
+ const struct tegra_tsensor_configuration *config;
+ const u32 calib_fuse_offset;
+ /*
+ * Correction values used to modify values read from
+ * calibration fuses
+ */
+ const s32 fuse_corr_alpha, fuse_corr_beta;
+ const struct tegra_tsensor_group *group;
+};
+
+struct tsensor_group_thermtrips {
+ u8 id;
+ u32 temp;
+};
+
+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_common_reg, fuse_spare_realignment;
+ u32 nominal_calib_ft;
+};
+
+struct tsensor_shared_calib {
+ u32 base_cp, base_ft;
+ u32 actual_temp_cp, actual_temp_ft;
+};
+
+struct tegra_soctherm_soc {
+ const struct tegra_tsensor *tsensors;
+ const unsigned int num_tsensors;
+ const struct tegra_tsensor_group **ttgs;
+ const unsigned int num_ttgs;
+ const struct tegra_soctherm_fuse *tfuse;
+ const int thresh_grain;
+ const unsigned int bptt;
+ const bool use_ccroc;
+ struct tsensor_group_thermtrips *thermtrips;
+};
+
+int tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse,
+ struct tsensor_shared_calib *shared);
+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
+
+#ifdef CONFIG_ARCH_TEGRA_132_SOC
+extern const struct tegra_soctherm_soc tegra132_soctherm;
+#endif
+
+#ifdef CONFIG_ARCH_TEGRA_210_SOC
+extern const struct tegra_soctherm_soc tegra210_soctherm;
+#endif
+
+#endif
+
diff --git a/drivers/thermal/tegra/tegra-bpmp-thermal.c b/drivers/thermal/tegra/tegra-bpmp-thermal.c
new file mode 100644
index 000000000..997d77ce3
--- /dev/null
+++ b/drivers/thermal/tegra/tegra-bpmp-thermal.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2015-2017, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author:
+ * Mikko Perttunen <mperttunen@nvidia.com>
+ * Aapo Vienamo <avienamo@nvidia.com>
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <linux/workqueue.h>
+
+#include <soc/tegra/bpmp.h>
+#include <soc/tegra/bpmp-abi.h>
+
+struct tegra_bpmp_thermal_zone {
+ struct tegra_bpmp_thermal *tegra;
+ struct thermal_zone_device *tzd;
+ struct work_struct tz_device_update_work;
+ unsigned int idx;
+};
+
+struct tegra_bpmp_thermal {
+ struct device *dev;
+ struct tegra_bpmp *bpmp;
+ unsigned int num_zones;
+ struct tegra_bpmp_thermal_zone **zones;
+};
+
+static int __tegra_bpmp_thermal_get_temp(struct tegra_bpmp_thermal_zone *zone,
+ int *out_temp)
+{
+ 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_GET_TEMP;
+ req.get_temp.zone = zone->idx;
+
+ 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(zone->tegra->bpmp, &msg);
+ if (err)
+ return err;
+ if (msg.rx.ret == -BPMP_EFAULT)
+ return -EAGAIN;
+ if (msg.rx.ret)
+ return -EINVAL;
+
+ *out_temp = reply.get_temp.temp;
+
+ return 0;
+}
+
+static int tegra_bpmp_thermal_get_temp(struct thermal_zone_device *tz, int *out_temp)
+{
+ struct tegra_bpmp_thermal_zone *zone = thermal_zone_device_priv(tz);
+
+ return __tegra_bpmp_thermal_get_temp(zone, out_temp);
+}
+
+static int tegra_bpmp_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct tegra_bpmp_thermal_zone *zone = thermal_zone_device_priv(tz);
+ struct mrq_thermal_host_to_bpmp_request req;
+ struct tegra_bpmp_message msg;
+ int err;
+
+ memset(&req, 0, sizeof(req));
+ req.type = CMD_THERMAL_SET_TRIP;
+ req.set_trip.zone = zone->idx;
+ req.set_trip.enabled = true;
+ req.set_trip.low = low;
+ req.set_trip.high = high;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.mrq = MRQ_THERMAL;
+ msg.tx.data = &req;
+ msg.tx.size = sizeof(req);
+
+ err = tegra_bpmp_transfer(zone->tegra->bpmp, &msg);
+ if (err)
+ return err;
+ if (msg.rx.ret)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void tz_device_update_work_fn(struct work_struct *work)
+{
+ struct tegra_bpmp_thermal_zone *zone;
+
+ zone = container_of(work, struct tegra_bpmp_thermal_zone,
+ tz_device_update_work);
+
+ thermal_zone_device_update(zone->tzd, THERMAL_TRIP_VIOLATED);
+}
+
+static void bpmp_mrq_thermal(unsigned int mrq, struct tegra_bpmp_channel *ch,
+ void *data)
+{
+ struct mrq_thermal_bpmp_to_host_request req;
+ struct tegra_bpmp_thermal *tegra = data;
+ size_t offset;
+ int i;
+
+ offset = offsetof(struct tegra_bpmp_mb_data, data);
+ iosys_map_memcpy_from(&req, &ch->ib, offset, sizeof(req));
+
+ if (req.type != CMD_THERMAL_HOST_TRIP_REACHED) {
+ dev_err(tegra->dev, "%s: invalid request type: %d\n", __func__, req.type);
+ tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0);
+ return;
+ }
+
+ for (i = 0; i < tegra->num_zones; ++i) {
+ if (tegra->zones[i]->idx != req.host_trip_reached.zone)
+ continue;
+
+ schedule_work(&tegra->zones[i]->tz_device_update_work);
+ tegra_bpmp_mrq_return(ch, 0, NULL, 0);
+ return;
+ }
+
+ dev_err(tegra->dev, "%s: invalid thermal zone: %d\n", __func__,
+ req.host_trip_reached.zone);
+ tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0);
+}
+
+static int tegra_bpmp_thermal_get_num_zones(struct tegra_bpmp *bpmp,
+ int *num_zones)
+{
+ 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_GET_NUM_ZONES;
+
+ 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)
+ return -EINVAL;
+
+ *num_zones = reply.get_num_zones.num;
+
+ 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;
+
+ tegra->dev = &pdev->dev;
+ tegra->bpmp = bpmp;
+
+ err = tegra_bpmp_thermal_get_num_zones(bpmp, &max_num_zones);
+ if (err) {
+ dev_err(&pdev->dev, "failed to get the number of zones: %d\n",
+ err);
+ return err;
+ }
+
+ tegra->zones = devm_kcalloc(&pdev->dev, max_num_zones,
+ sizeof(*tegra->zones), GFP_KERNEL);
+ if (!tegra->zones)
+ return -ENOMEM;
+
+ for (i = 0; i < max_num_zones; ++i) {
+ struct tegra_bpmp_thermal_zone *zone;
+ int temp;
+
+ zone = devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL);
+ if (!zone)
+ return -ENOMEM;
+
+ zone->idx = i;
+ zone->tegra = tegra;
+
+ err = __tegra_bpmp_thermal_get_temp(zone, &temp);
+
+ /*
+ * Sensors in powergated domains may temporarily fail to be read
+ * (-EAGAIN), but will become accessible when the domain is powered on.
+ */
+ if (err < 0 && err != -EAGAIN) {
+ devm_kfree(&pdev->dev, zone);
+ continue;
+ }
+
+ tzd = devm_thermal_of_zone_register(
+ &pdev->dev, i, zone, thermal_ops);
+ if (IS_ERR(tzd)) {
+ if (PTR_ERR(tzd) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ devm_kfree(&pdev->dev, zone);
+ continue;
+ }
+
+ zone->tzd = tzd;
+ INIT_WORK(&zone->tz_device_update_work,
+ tz_device_update_work_fn);
+
+ tegra->zones[tegra->num_zones++] = zone;
+ }
+
+ err = tegra_bpmp_request_mrq(bpmp, MRQ_THERMAL, bpmp_mrq_thermal,
+ tegra);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register mrq handler: %d\n",
+ err);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, tegra);
+
+ return 0;
+}
+
+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);
+}
+
+static const struct of_device_id tegra_bpmp_thermal_of_match[] = {
+ { .compatible = "nvidia,tegra186-bpmp-thermal" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tegra_bpmp_thermal_of_match);
+
+static struct platform_driver tegra_bpmp_thermal_driver = {
+ .probe = tegra_bpmp_thermal_probe,
+ .remove = tegra_bpmp_thermal_remove,
+ .driver = {
+ .name = "tegra-bpmp-thermal",
+ .of_match_table = tegra_bpmp_thermal_of_match,
+ },
+};
+module_platform_driver(tegra_bpmp_thermal_driver);
+
+MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
+MODULE_DESCRIPTION("NVIDIA Tegra BPMP thermal sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/tegra/tegra114-soctherm.c b/drivers/thermal/tegra/tegra114-soctherm.c
new file mode 100644
index 000000000..688104f28
--- /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
new file mode 100644
index 000000000..d86acff1b
--- /dev/null
+++ b/drivers/thermal/tegra/tegra124-soctherm.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <dt-bindings/thermal/tegra124-soctherm.h>
+
+#include "soctherm.h"
+
+#define TEGRA124_THERMTRIP_ANY_EN_MASK (0x1 << 28)
+#define TEGRA124_THERMTRIP_MEM_EN_MASK (0x1 << 27)
+#define TEGRA124_THERMTRIP_GPU_EN_MASK (0x1 << 26)
+#define TEGRA124_THERMTRIP_CPU_EN_MASK (0x1 << 25)
+#define TEGRA124_THERMTRIP_TSENSE_EN_MASK (0x1 << 24)
+#define TEGRA124_THERMTRIP_GPUMEM_THRESH_MASK (0xff << 16)
+#define TEGRA124_THERMTRIP_CPU_THRESH_MASK (0xff << 8)
+#define TEGRA124_THERMTRIP_TSENSE_THRESH_MASK 0xff
+
+#define TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK (0xff << 17)
+#define TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK (0xff << 9)
+
+#define TEGRA124_THRESH_GRAIN 1000
+#define TEGRA124_BPTT 8
+
+static const struct tegra_tsensor_configuration tegra124_tsensor_config = {
+ .tall = 16300,
+ .tiddq_en = 1,
+ .ten_count = 1,
+ .tsample = 120,
+ .tsample_ate = 480,
+};
+
+static const struct tegra_tsensor_group tegra124_tsensor_group_cpu = {
+ .id = TEGRA124_SOCTHERM_SENSOR_CPU,
+ .name = "cpu",
+ .sensor_temp_offset = SENSOR_TEMP1,
+ .sensor_temp_mask = SENSOR_TEMP1_CPU_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_CPU_MASK,
+ .pllx_hotspot_diff = 10,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_CPU_MASK,
+ .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA124_THERMTRIP_CPU_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA124_THERMTRIP_CPU_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_CPU_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_CPU,
+ .thermctl_lvl0_up_thresh_mask = TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra124_tsensor_group_gpu = {
+ .id = TEGRA124_SOCTHERM_SENSOR_GPU,
+ .name = "gpu",
+ .sensor_temp_offset = SENSOR_TEMP1,
+ .sensor_temp_mask = SENSOR_TEMP1_GPU_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_GPU_MASK,
+ .pllx_hotspot_diff = 5,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_GPU_MASK,
+ .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA124_THERMTRIP_GPU_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA124_THERMTRIP_GPUMEM_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_GPU_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_GPU,
+ .thermctl_lvl0_up_thresh_mask = TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra124_tsensor_group_pll = {
+ .id = TEGRA124_SOCTHERM_SENSOR_PLLX,
+ .name = "pll",
+ .sensor_temp_offset = SENSOR_TEMP2,
+ .sensor_temp_mask = SENSOR_TEMP2_PLLX_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_PLLX_MASK,
+ .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA124_THERMTRIP_TSENSE_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA124_THERMTRIP_TSENSE_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_TSENSE_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_TSENSE,
+ .thermctl_lvl0_up_thresh_mask = TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra124_tsensor_group_mem = {
+ .id = TEGRA124_SOCTHERM_SENSOR_MEM,
+ .name = "mem",
+ .sensor_temp_offset = SENSOR_TEMP2,
+ .sensor_temp_mask = SENSOR_TEMP2_MEM_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_MEM_MASK,
+ .pllx_hotspot_diff = 0,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_MEM_MASK,
+ .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA124_THERMTRIP_MEM_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA124_THERMTRIP_GPUMEM_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_MEM_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_MEM,
+ .thermctl_lvl0_up_thresh_mask = TEGRA124_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA124_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group *tegra124_tsensor_groups[] = {
+ &tegra124_tsensor_group_cpu,
+ &tegra124_tsensor_group_gpu,
+ &tegra124_tsensor_group_pll,
+ &tegra124_tsensor_group_mem,
+};
+
+static const struct tegra_tsensor tegra124_tsensors[] = {
+ {
+ .name = "cpu0",
+ .base = 0xc0,
+ .config = &tegra124_tsensor_config,
+ .calib_fuse_offset = 0x098,
+ .fuse_corr_alpha = 1135400,
+ .fuse_corr_beta = -6266900,
+ .group = &tegra124_tsensor_group_cpu,
+ }, {
+ .name = "cpu1",
+ .base = 0xe0,
+ .config = &tegra124_tsensor_config,
+ .calib_fuse_offset = 0x084,
+ .fuse_corr_alpha = 1122220,
+ .fuse_corr_beta = -5700700,
+ .group = &tegra124_tsensor_group_cpu,
+ }, {
+ .name = "cpu2",
+ .base = 0x100,
+ .config = &tegra124_tsensor_config,
+ .calib_fuse_offset = 0x088,
+ .fuse_corr_alpha = 1127000,
+ .fuse_corr_beta = -6768200,
+ .group = &tegra124_tsensor_group_cpu,
+ }, {
+ .name = "cpu3",
+ .base = 0x120,
+ .config = &tegra124_tsensor_config,
+ .calib_fuse_offset = 0x12c,
+ .fuse_corr_alpha = 1110900,
+ .fuse_corr_beta = -6232000,
+ .group = &tegra124_tsensor_group_cpu,
+ }, {
+ .name = "mem0",
+ .base = 0x140,
+ .config = &tegra124_tsensor_config,
+ .calib_fuse_offset = 0x158,
+ .fuse_corr_alpha = 1122300,
+ .fuse_corr_beta = -5936400,
+ .group = &tegra124_tsensor_group_mem,
+ }, {
+ .name = "mem1",
+ .base = 0x160,
+ .config = &tegra124_tsensor_config,
+ .calib_fuse_offset = 0x15c,
+ .fuse_corr_alpha = 1145700,
+ .fuse_corr_beta = -7124600,
+ .group = &tegra124_tsensor_group_mem,
+ }, {
+ .name = "gpu",
+ .base = 0x180,
+ .config = &tegra124_tsensor_config,
+ .calib_fuse_offset = 0x154,
+ .fuse_corr_alpha = 1120100,
+ .fuse_corr_beta = -6000500,
+ .group = &tegra124_tsensor_group_gpu,
+ }, {
+ .name = "pllx",
+ .base = 0x1a0,
+ .config = &tegra124_tsensor_config,
+ .calib_fuse_offset = 0x160,
+ .fuse_corr_alpha = 1106500,
+ .fuse_corr_beta = -6729300,
+ .group = &tegra124_tsensor_group_pll,
+ },
+};
+
+/*
+ * Mask/shift bits in FUSE_TSENSOR_COMMON and
+ * FUSE_TSENSOR_COMMON, which are described in
+ * tegra_soctherm_fuse.c
+ */
+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 = {
+ .tsensors = tegra124_tsensors,
+ .num_tsensors = ARRAY_SIZE(tegra124_tsensors),
+ .ttgs = tegra124_tsensor_groups,
+ .num_ttgs = ARRAY_SIZE(tegra124_tsensor_groups),
+ .tfuse = &tegra124_soctherm_fuse,
+ .thresh_grain = TEGRA124_THRESH_GRAIN,
+ .bptt = TEGRA124_BPTT,
+ .use_ccroc = false,
+};
diff --git a/drivers/thermal/tegra/tegra132-soctherm.c b/drivers/thermal/tegra/tegra132-soctherm.c
new file mode 100644
index 000000000..64c0363b9
--- /dev/null
+++ b/drivers/thermal/tegra/tegra132-soctherm.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <dt-bindings/thermal/tegra124-soctherm.h>
+
+#include "soctherm.h"
+
+#define TEGRA132_THERMTRIP_ANY_EN_MASK (0x1 << 28)
+#define TEGRA132_THERMTRIP_MEM_EN_MASK (0x1 << 27)
+#define TEGRA132_THERMTRIP_GPU_EN_MASK (0x1 << 26)
+#define TEGRA132_THERMTRIP_CPU_EN_MASK (0x1 << 25)
+#define TEGRA132_THERMTRIP_TSENSE_EN_MASK (0x1 << 24)
+#define TEGRA132_THERMTRIP_GPUMEM_THRESH_MASK (0xff << 16)
+#define TEGRA132_THERMTRIP_CPU_THRESH_MASK (0xff << 8)
+#define TEGRA132_THERMTRIP_TSENSE_THRESH_MASK 0xff
+
+#define TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK (0xff << 17)
+#define TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK (0xff << 9)
+
+#define TEGRA132_THRESH_GRAIN 1000
+#define TEGRA132_BPTT 8
+
+static const struct tegra_tsensor_configuration tegra132_tsensor_config = {
+ .tall = 16300,
+ .tiddq_en = 1,
+ .ten_count = 1,
+ .tsample = 120,
+ .tsample_ate = 480,
+};
+
+static const struct tegra_tsensor_group tegra132_tsensor_group_cpu = {
+ .id = TEGRA124_SOCTHERM_SENSOR_CPU,
+ .name = "cpu",
+ .sensor_temp_offset = SENSOR_TEMP1,
+ .sensor_temp_mask = SENSOR_TEMP1_CPU_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_CPU_MASK,
+ .pllx_hotspot_diff = 10,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_CPU_MASK,
+ .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA132_THERMTRIP_CPU_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA132_THERMTRIP_CPU_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_CPU_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_CPU,
+ .thermctl_lvl0_up_thresh_mask = TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra132_tsensor_group_gpu = {
+ .id = TEGRA124_SOCTHERM_SENSOR_GPU,
+ .name = "gpu",
+ .sensor_temp_offset = SENSOR_TEMP1,
+ .sensor_temp_mask = SENSOR_TEMP1_GPU_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_GPU_MASK,
+ .pllx_hotspot_diff = 5,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_GPU_MASK,
+ .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA132_THERMTRIP_GPU_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA132_THERMTRIP_GPUMEM_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_GPU_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_GPU,
+ .thermctl_lvl0_up_thresh_mask = TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra132_tsensor_group_pll = {
+ .id = TEGRA124_SOCTHERM_SENSOR_PLLX,
+ .name = "pll",
+ .sensor_temp_offset = SENSOR_TEMP2,
+ .sensor_temp_mask = SENSOR_TEMP2_PLLX_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_PLLX_MASK,
+ .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA132_THERMTRIP_TSENSE_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA132_THERMTRIP_TSENSE_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_TSENSE_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_TSENSE,
+ .thermctl_lvl0_up_thresh_mask = TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra132_tsensor_group_mem = {
+ .id = TEGRA124_SOCTHERM_SENSOR_MEM,
+ .name = "mem",
+ .sensor_temp_offset = SENSOR_TEMP2,
+ .sensor_temp_mask = SENSOR_TEMP2_MEM_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_MEM_MASK,
+ .pllx_hotspot_diff = 0,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_MEM_MASK,
+ .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA132_THERMTRIP_MEM_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA132_THERMTRIP_GPUMEM_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_MEM_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_MEM,
+ .thermctl_lvl0_up_thresh_mask = TEGRA132_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA132_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group *tegra132_tsensor_groups[] = {
+ &tegra132_tsensor_group_cpu,
+ &tegra132_tsensor_group_gpu,
+ &tegra132_tsensor_group_pll,
+ &tegra132_tsensor_group_mem,
+};
+
+static struct tegra_tsensor tegra132_tsensors[] = {
+ {
+ .name = "cpu0",
+ .base = 0xc0,
+ .config = &tegra132_tsensor_config,
+ .calib_fuse_offset = 0x098,
+ .fuse_corr_alpha = 1126600,
+ .fuse_corr_beta = -9433500,
+ .group = &tegra132_tsensor_group_cpu,
+ }, {
+ .name = "cpu1",
+ .base = 0xe0,
+ .config = &tegra132_tsensor_config,
+ .calib_fuse_offset = 0x084,
+ .fuse_corr_alpha = 1110800,
+ .fuse_corr_beta = -7383000,
+ .group = &tegra132_tsensor_group_cpu,
+ }, {
+ .name = "cpu2",
+ .base = 0x100,
+ .config = &tegra132_tsensor_config,
+ .calib_fuse_offset = 0x088,
+ .fuse_corr_alpha = 1113800,
+ .fuse_corr_beta = -6215200,
+ .group = &tegra132_tsensor_group_cpu,
+ }, {
+ .name = "cpu3",
+ .base = 0x120,
+ .config = &tegra132_tsensor_config,
+ .calib_fuse_offset = 0x12c,
+ .fuse_corr_alpha = 1129600,
+ .fuse_corr_beta = -8196100,
+ .group = &tegra132_tsensor_group_cpu,
+ }, {
+ .name = "mem0",
+ .base = 0x140,
+ .config = &tegra132_tsensor_config,
+ .calib_fuse_offset = 0x158,
+ .fuse_corr_alpha = 1132900,
+ .fuse_corr_beta = -6755300,
+ .group = &tegra132_tsensor_group_mem,
+ }, {
+ .name = "mem1",
+ .base = 0x160,
+ .config = &tegra132_tsensor_config,
+ .calib_fuse_offset = 0x15c,
+ .fuse_corr_alpha = 1142300,
+ .fuse_corr_beta = -7374200,
+ .group = &tegra132_tsensor_group_mem,
+ }, {
+ .name = "gpu",
+ .base = 0x180,
+ .config = &tegra132_tsensor_config,
+ .calib_fuse_offset = 0x154,
+ .fuse_corr_alpha = 1125100,
+ .fuse_corr_beta = -6350400,
+ .group = &tegra132_tsensor_group_gpu,
+ }, {
+ .name = "pllx",
+ .base = 0x1a0,
+ .config = &tegra132_tsensor_config,
+ .calib_fuse_offset = 0x160,
+ .fuse_corr_alpha = 1118100,
+ .fuse_corr_beta = -8208800,
+ .group = &tegra132_tsensor_group_pll,
+ },
+};
+
+/*
+ * Mask/shift bits in FUSE_TSENSOR_COMMON and
+ * FUSE_TSENSOR_COMMON, which are described in
+ * tegra_soctherm_fuse.c
+ */
+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 = {
+ .tsensors = tegra132_tsensors,
+ .num_tsensors = ARRAY_SIZE(tegra132_tsensors),
+ .ttgs = tegra132_tsensor_groups,
+ .num_ttgs = ARRAY_SIZE(tegra132_tsensor_groups),
+ .tfuse = &tegra132_soctherm_fuse,
+ .thresh_grain = TEGRA132_THRESH_GRAIN,
+ .bptt = TEGRA132_BPTT,
+ .use_ccroc = true,
+};
diff --git a/drivers/thermal/tegra/tegra210-soctherm.c b/drivers/thermal/tegra/tegra210-soctherm.c
new file mode 100644
index 000000000..f6e1493f0
--- /dev/null
+++ b/drivers/thermal/tegra/tegra210-soctherm.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <soc/tegra/fuse.h>
+
+#include <dt-bindings/thermal/tegra124-soctherm.h>
+
+#include "soctherm.h"
+
+#define TEGRA210_THERMTRIP_ANY_EN_MASK (0x1 << 31)
+#define TEGRA210_THERMTRIP_MEM_EN_MASK (0x1 << 30)
+#define TEGRA210_THERMTRIP_GPU_EN_MASK (0x1 << 29)
+#define TEGRA210_THERMTRIP_CPU_EN_MASK (0x1 << 28)
+#define TEGRA210_THERMTRIP_TSENSE_EN_MASK (0x1 << 27)
+#define TEGRA210_THERMTRIP_GPUMEM_THRESH_MASK (0x1ff << 18)
+#define TEGRA210_THERMTRIP_CPU_THRESH_MASK (0x1ff << 9)
+#define TEGRA210_THERMTRIP_TSENSE_THRESH_MASK 0x1ff
+
+#define TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK (0x1ff << 18)
+#define TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK (0x1ff << 9)
+
+#define TEGRA210_THRESH_GRAIN 500
+#define TEGRA210_BPTT 9
+
+static const struct tegra_tsensor_configuration tegra210_tsensor_config = {
+ .tall = 16300,
+ .tiddq_en = 1,
+ .ten_count = 1,
+ .tsample = 120,
+ .tsample_ate = 480,
+};
+
+static const struct tegra_tsensor_group tegra210_tsensor_group_cpu = {
+ .id = TEGRA124_SOCTHERM_SENSOR_CPU,
+ .name = "cpu",
+ .sensor_temp_offset = SENSOR_TEMP1,
+ .sensor_temp_mask = SENSOR_TEMP1_CPU_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_CPU_MASK,
+ .pllx_hotspot_diff = 10,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_CPU_MASK,
+ .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA210_THERMTRIP_CPU_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA210_THERMTRIP_CPU_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_CPU_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_CPU,
+ .thermctl_lvl0_up_thresh_mask = TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra210_tsensor_group_gpu = {
+ .id = TEGRA124_SOCTHERM_SENSOR_GPU,
+ .name = "gpu",
+ .sensor_temp_offset = SENSOR_TEMP1,
+ .sensor_temp_mask = SENSOR_TEMP1_GPU_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_GPU_MASK,
+ .pllx_hotspot_diff = 5,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_GPU_MASK,
+ .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA210_THERMTRIP_GPU_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA210_THERMTRIP_GPUMEM_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_GPU_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_GPU,
+ .thermctl_lvl0_up_thresh_mask = TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra210_tsensor_group_pll = {
+ .id = TEGRA124_SOCTHERM_SENSOR_PLLX,
+ .name = "pll",
+ .sensor_temp_offset = SENSOR_TEMP2,
+ .sensor_temp_mask = SENSOR_TEMP2_PLLX_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_PLLX_MASK,
+ .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA210_THERMTRIP_TSENSE_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA210_THERMTRIP_TSENSE_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_TSENSE_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_TSENSE,
+ .thermctl_lvl0_up_thresh_mask = TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group tegra210_tsensor_group_mem = {
+ .id = TEGRA124_SOCTHERM_SENSOR_MEM,
+ .name = "mem",
+ .sensor_temp_offset = SENSOR_TEMP2,
+ .sensor_temp_mask = SENSOR_TEMP2_MEM_TEMP_MASK,
+ .pdiv = 8,
+ .pdiv_ate = 8,
+ .pdiv_mask = SENSOR_PDIV_MEM_MASK,
+ .pllx_hotspot_diff = 0,
+ .pllx_hotspot_mask = SENSOR_HOTSPOT_MEM_MASK,
+ .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK,
+ .thermtrip_enable_mask = TEGRA210_THERMTRIP_MEM_EN_MASK,
+ .thermtrip_threshold_mask = TEGRA210_THERMTRIP_GPUMEM_THRESH_MASK,
+ .thermctl_isr_mask = THERM_IRQ_MEM_MASK,
+ .thermctl_lvl0_offset = THERMCTL_LEVEL0_GROUP_MEM,
+ .thermctl_lvl0_up_thresh_mask = TEGRA210_THERMCTL_LVL0_UP_THRESH_MASK,
+ .thermctl_lvl0_dn_thresh_mask = TEGRA210_THERMCTL_LVL0_DN_THRESH_MASK,
+};
+
+static const struct tegra_tsensor_group *tegra210_tsensor_groups[] = {
+ &tegra210_tsensor_group_cpu,
+ &tegra210_tsensor_group_gpu,
+ &tegra210_tsensor_group_pll,
+ &tegra210_tsensor_group_mem,
+};
+
+static const struct tegra_tsensor tegra210_tsensors[] = {
+ {
+ .name = "cpu0",
+ .base = 0xc0,
+ .config = &tegra210_tsensor_config,
+ .calib_fuse_offset = 0x098,
+ .fuse_corr_alpha = 1085000,
+ .fuse_corr_beta = 3244200,
+ .group = &tegra210_tsensor_group_cpu,
+ }, {
+ .name = "cpu1",
+ .base = 0xe0,
+ .config = &tegra210_tsensor_config,
+ .calib_fuse_offset = 0x084,
+ .fuse_corr_alpha = 1126200,
+ .fuse_corr_beta = -67500,
+ .group = &tegra210_tsensor_group_cpu,
+ }, {
+ .name = "cpu2",
+ .base = 0x100,
+ .config = &tegra210_tsensor_config,
+ .calib_fuse_offset = 0x088,
+ .fuse_corr_alpha = 1098400,
+ .fuse_corr_beta = 2251100,
+ .group = &tegra210_tsensor_group_cpu,
+ }, {
+ .name = "cpu3",
+ .base = 0x120,
+ .config = &tegra210_tsensor_config,
+ .calib_fuse_offset = 0x12c,
+ .fuse_corr_alpha = 1108000,
+ .fuse_corr_beta = 602700,
+ .group = &tegra210_tsensor_group_cpu,
+ }, {
+ .name = "mem0",
+ .base = 0x140,
+ .config = &tegra210_tsensor_config,
+ .calib_fuse_offset = 0x158,
+ .fuse_corr_alpha = 1069200,
+ .fuse_corr_beta = 3549900,
+ .group = &tegra210_tsensor_group_mem,
+ }, {
+ .name = "mem1",
+ .base = 0x160,
+ .config = &tegra210_tsensor_config,
+ .calib_fuse_offset = 0x15c,
+ .fuse_corr_alpha = 1173700,
+ .fuse_corr_beta = -6263600,
+ .group = &tegra210_tsensor_group_mem,
+ }, {
+ .name = "gpu",
+ .base = 0x180,
+ .config = &tegra210_tsensor_config,
+ .calib_fuse_offset = 0x154,
+ .fuse_corr_alpha = 1074300,
+ .fuse_corr_beta = 2734900,
+ .group = &tegra210_tsensor_group_gpu,
+ }, {
+ .name = "pllx",
+ .base = 0x1a0,
+ .config = &tegra210_tsensor_config,
+ .calib_fuse_offset = 0x160,
+ .fuse_corr_alpha = 1039700,
+ .fuse_corr_beta = 6829100,
+ .group = &tegra210_tsensor_group_pll,
+ },
+};
+
+/*
+ * Mask/shift bits in FUSE_TSENSOR_COMMON and
+ * FUSE_TSENSOR_COMMON, which are described in
+ * tegra_soctherm_fuse.c
+ */
+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[] = {
+ {.id = TEGRA124_SOCTHERM_SENSOR_NUM},
+ {.id = TEGRA124_SOCTHERM_SENSOR_NUM},
+ {.id = TEGRA124_SOCTHERM_SENSOR_NUM},
+ {.id = TEGRA124_SOCTHERM_SENSOR_NUM},
+};
+
+const struct tegra_soctherm_soc tegra210_soctherm = {
+ .tsensors = tegra210_tsensors,
+ .num_tsensors = ARRAY_SIZE(tegra210_tsensors),
+ .ttgs = tegra210_tsensor_groups,
+ .num_ttgs = ARRAY_SIZE(tegra210_tsensor_groups),
+ .tfuse = &tegra210_soctherm_fuse,
+ .thresh_grain = TEGRA210_THRESH_GRAIN,
+ .bptt = TEGRA210_BPTT,
+ .use_ccroc = false,
+ .thermtrips = tegra210_tsensor_thermtrips,
+};
diff --git a/drivers/thermal/tegra/tegra30-tsensor.c b/drivers/thermal/tegra/tegra30-tsensor.c
new file mode 100644
index 000000000..10a5ab1fe
--- /dev/null
+++ b/drivers/thermal/tegra/tegra30-tsensor.c
@@ -0,0 +1,677 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tegra30 SoC Thermal Sensor driver
+ *
+ * Based on downstream HWMON driver from NVIDIA.
+ * Copyright (C) 2011 NVIDIA Corporation
+ *
+ * Author: Dmitry Osipenko <digetx@gmail.com>
+ * Copyright (C) 2021 GRATE-DRIVER project
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "../thermal_hwmon.h"
+
+#define TSENSOR_SENSOR0_CONFIG0 0x0
+#define TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP BIT(0)
+#define TSENSOR_SENSOR0_CONFIG0_HW_FREQ_DIV_EN BIT(1)
+#define TSENSOR_SENSOR0_CONFIG0_THERMAL_RST_EN BIT(2)
+#define TSENSOR_SENSOR0_CONFIG0_DVFS_EN BIT(3)
+#define TSENSOR_SENSOR0_CONFIG0_INTR_OVERFLOW_EN BIT(4)
+#define TSENSOR_SENSOR0_CONFIG0_INTR_HW_FREQ_DIV_EN BIT(5)
+#define TSENSOR_SENSOR0_CONFIG0_INTR_THERMAL_RST_EN BIT(6)
+#define TSENSOR_SENSOR0_CONFIG0_M GENMASK(23, 8)
+#define TSENSOR_SENSOR0_CONFIG0_N GENMASK(31, 24)
+
+#define TSENSOR_SENSOR0_CONFIG1 0x8
+#define TSENSOR_SENSOR0_CONFIG1_TH1 GENMASK(15, 0)
+#define TSENSOR_SENSOR0_CONFIG1_TH2 GENMASK(31, 16)
+
+#define TSENSOR_SENSOR0_CONFIG2 0xc
+#define TSENSOR_SENSOR0_CONFIG2_TH3 GENMASK(15, 0)
+
+#define TSENSOR_SENSOR0_STATUS0 0x18
+#define TSENSOR_SENSOR0_STATUS0_STATE GENMASK(2, 0)
+#define TSENSOR_SENSOR0_STATUS0_INTR BIT(8)
+#define TSENSOR_SENSOR0_STATUS0_CURRENT_VALID BIT(9)
+
+#define TSENSOR_SENSOR0_TS_STATUS1 0x1c
+#define TSENSOR_SENSOR0_TS_STATUS1_CURRENT_COUNT GENMASK(31, 16)
+
+#define TEGRA30_FUSE_TEST_PROG_VER 0x28
+
+#define TEGRA30_FUSE_TSENSOR_CALIB 0x98
+#define TEGRA30_FUSE_TSENSOR_CALIB_LOW GENMASK(15, 0)
+#define TEGRA30_FUSE_TSENSOR_CALIB_HIGH GENMASK(31, 16)
+
+#define TEGRA30_FUSE_SPARE_BIT 0x144
+
+struct tegra_tsensor;
+
+struct tegra_tsensor_calibration_data {
+ int a, b, m, n, p, r;
+};
+
+struct tegra_tsensor_channel {
+ void __iomem *regs;
+ unsigned int id;
+ struct tegra_tsensor *ts;
+ struct thermal_zone_device *tzd;
+};
+
+struct tegra_tsensor {
+ void __iomem *regs;
+ bool swap_channels;
+ struct clk *clk;
+ struct device *dev;
+ struct reset_control *rst;
+ struct tegra_tsensor_channel ch[2];
+ struct tegra_tsensor_calibration_data calib;
+};
+
+static int tegra_tsensor_hw_enable(const struct tegra_tsensor *ts)
+{
+ u32 val;
+ int err;
+
+ err = reset_control_assert(ts->rst);
+ if (err) {
+ dev_err(ts->dev, "failed to assert hardware reset: %d\n", err);
+ return err;
+ }
+
+ err = clk_prepare_enable(ts->clk);
+ if (err) {
+ dev_err(ts->dev, "failed to enable clock: %d\n", err);
+ return err;
+ }
+
+ fsleep(1000);
+
+ err = reset_control_deassert(ts->rst);
+ if (err) {
+ dev_err(ts->dev, "failed to deassert hardware reset: %d\n", err);
+ goto disable_clk;
+ }
+
+ /*
+ * Sensors are enabled after reset by default, but not gauging
+ * until clock counter is programmed.
+ *
+ * M: number of reference clock pulses after which every
+ * temperature / voltage measurement is made
+ *
+ * N: number of reference clock counts for which the counter runs
+ */
+ val = FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_M, 12500);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_N, 255);
+
+ /* apply the same configuration to both channels */
+ writel_relaxed(val, ts->regs + 0x40 + TSENSOR_SENSOR0_CONFIG0);
+ writel_relaxed(val, ts->regs + 0x80 + TSENSOR_SENSOR0_CONFIG0);
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(ts->clk);
+
+ return err;
+}
+
+static int tegra_tsensor_hw_disable(const struct tegra_tsensor *ts)
+{
+ int err;
+
+ err = reset_control_assert(ts->rst);
+ if (err) {
+ dev_err(ts->dev, "failed to assert hardware reset: %d\n", err);
+ return err;
+ }
+
+ clk_disable_unprepare(ts->clk);
+
+ return 0;
+}
+
+static void devm_tegra_tsensor_hw_disable(void *data)
+{
+ const struct tegra_tsensor *ts = data;
+
+ tegra_tsensor_hw_disable(ts);
+}
+
+static int tegra_tsensor_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ const struct tegra_tsensor_channel *tsc = thermal_zone_device_priv(tz);
+ const struct tegra_tsensor *ts = tsc->ts;
+ int err, c1, c2, c3, c4, counter;
+ u32 val;
+
+ /*
+ * Counter will be invalid if hardware is misprogrammed or not enough
+ * time passed since the time when sensor was enabled.
+ */
+ err = readl_relaxed_poll_timeout(tsc->regs + TSENSOR_SENSOR0_STATUS0, val,
+ val & TSENSOR_SENSOR0_STATUS0_CURRENT_VALID,
+ 21 * USEC_PER_MSEC,
+ 21 * USEC_PER_MSEC * 50);
+ if (err) {
+ dev_err_once(ts->dev, "ch%u: counter invalid\n", tsc->id);
+ return err;
+ }
+
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_TS_STATUS1);
+ counter = FIELD_GET(TSENSOR_SENSOR0_TS_STATUS1_CURRENT_COUNT, val);
+
+ /*
+ * This shouldn't happen with a valid counter status, nevertheless
+ * lets verify the value since it's in a separate (from status)
+ * register.
+ */
+ if (counter == 0xffff) {
+ dev_err_once(ts->dev, "ch%u: counter overflow\n", tsc->id);
+ return -EINVAL;
+ }
+
+ /*
+ * temperature = a * counter + b
+ * temperature = m * (temperature ^ 2) + n * temperature + p
+ */
+ c1 = DIV_ROUND_CLOSEST(ts->calib.a * counter + ts->calib.b, 1000000);
+ c1 = c1 ?: 1;
+ c2 = DIV_ROUND_CLOSEST(ts->calib.p, c1);
+ c3 = c1 * ts->calib.m;
+ c4 = ts->calib.n;
+
+ *temp = DIV_ROUND_CLOSEST(c1 * (c2 + c3 + c4), 1000);
+
+ return 0;
+}
+
+static int tegra_tsensor_temp_to_counter(const struct tegra_tsensor *ts, int temp)
+{
+ int c1, c2;
+
+ c1 = DIV_ROUND_CLOSEST(ts->calib.p - temp * 1000, ts->calib.m);
+ c2 = -ts->calib.r - int_sqrt(ts->calib.r * ts->calib.r - c1);
+
+ return DIV_ROUND_CLOSEST(c2 * 1000000 - ts->calib.b, ts->calib.a);
+}
+
+static int tegra_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ const struct tegra_tsensor_channel *tsc = thermal_zone_device_priv(tz);
+ const struct tegra_tsensor *ts = tsc->ts;
+ u32 val;
+
+ /*
+ * TSENSOR doesn't trigger interrupt on the "low" temperature breach,
+ * hence bail out if high temperature is unspecified.
+ */
+ if (high == INT_MAX)
+ return 0;
+
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG1);
+ val &= ~TSENSOR_SENSOR0_CONFIG1_TH1;
+
+ high = tegra_tsensor_temp_to_counter(ts, high);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG1_TH1, high);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG1);
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops ops = {
+ .get_temp = tegra_tsensor_get_temp,
+ .set_trips = tegra_tsensor_set_trips,
+};
+
+static bool
+tegra_tsensor_handle_channel_interrupt(const struct tegra_tsensor *ts,
+ unsigned int id)
+{
+ const struct tegra_tsensor_channel *tsc = &ts->ch[id];
+ u32 val;
+
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_STATUS0);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_STATUS0);
+
+ if (FIELD_GET(TSENSOR_SENSOR0_STATUS0_STATE, val) == 5)
+ dev_err_ratelimited(ts->dev, "ch%u: counter overflowed\n", id);
+
+ if (!FIELD_GET(TSENSOR_SENSOR0_STATUS0_INTR, val))
+ return false;
+
+ thermal_zone_device_update(tsc->tzd, THERMAL_EVENT_UNSPECIFIED);
+
+ return true;
+}
+
+static irqreturn_t tegra_tsensor_isr(int irq, void *data)
+{
+ const struct tegra_tsensor *ts = data;
+ bool handled = false;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++)
+ handled |= tegra_tsensor_handle_channel_interrupt(ts, i);
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int tegra_tsensor_disable_hw_channel(const struct tegra_tsensor *ts,
+ unsigned int id)
+{
+ const struct tegra_tsensor_channel *tsc = &ts->ch[id];
+ struct thermal_zone_device *tzd = tsc->tzd;
+ u32 val;
+ int err;
+
+ if (!tzd)
+ goto stop_channel;
+
+ err = thermal_zone_device_disable(tzd);
+ if (err) {
+ dev_err(ts->dev, "ch%u: failed to disable zone: %d\n", id, err);
+ return err;
+ }
+
+stop_channel:
+ /* stop channel gracefully */
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP, 1);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+
+ return 0;
+}
+
+struct trip_temps {
+ int hot_trip;
+ int crit_trip;
+};
+
+static int tegra_tsensor_get_trips_cb(struct thermal_trip *trip, void *arg)
+{
+ 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.
+ */
+ temps->hot_trip = 85000;
+ temps->crit_trip = 90000;
+
+ thermal_zone_for_each_trip(tzd, tegra_tsensor_get_trips_cb, temps);
+
+ /* clamp hardware trips to the calibration limits */
+ temps->hot_trip = clamp(temps->hot_trip, 25000, 90000);
+
+ /*
+ * Kernel will perform a normal system shut down if it will
+ * see that critical temperature is breached, hence set the
+ * hardware limit by 5C higher in order to allow system to
+ * shut down gracefully before sending signal to the Power
+ * Management controller.
+ */
+ temps->crit_trip = clamp(temps->crit_trip + 5000, 25000, 90000);
+}
+
+static int tegra_tsensor_enable_hw_channel(const struct tegra_tsensor *ts,
+ unsigned int id)
+{
+ const struct tegra_tsensor_channel *tsc = &ts->ch[id];
+ struct thermal_zone_device *tzd = tsc->tzd;
+ struct trip_temps temps = { 0 };
+ int err;
+ u32 val;
+
+ if (!tzd) {
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+ val &= ~TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP;
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+
+ return 0;
+ }
+
+ tegra_tsensor_get_hw_channel_trips(tzd, &temps);
+
+ dev_info_once(ts->dev, "ch%u: PMC emergency shutdown trip set to %dC\n",
+ id, DIV_ROUND_CLOSEST(temps.crit_trip, 1000));
+
+ 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, 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, temps.crit_trip);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG2);
+
+ /*
+ * Enable sensor, emergency shutdown, interrupts for level 1/2/3
+ * breaches and counter overflow condition.
+ *
+ * Disable DIV2 throttle for now since we need to figure out how
+ * to integrate it properly with the thermal framework.
+ *
+ * Thermal levels supported by hardware:
+ *
+ * Level 0 = cold
+ * Level 1 = passive cooling (cpufreq DVFS)
+ * Level 2 = passive cooling assisted by hardware (DIV2)
+ * Level 3 = emergency shutdown assisted by hardware (PMC)
+ */
+ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+ val &= ~TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP;
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_DVFS_EN, 1);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_HW_FREQ_DIV_EN, 0);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_THERMAL_RST_EN, 1);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_OVERFLOW_EN, 1);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_HW_FREQ_DIV_EN, 1);
+ val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_THERMAL_RST_EN, 1);
+ writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0);
+
+ err = thermal_zone_device_enable(tzd);
+ if (err) {
+ dev_err(ts->dev, "ch%u: failed to enable zone: %d\n", id, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static bool tegra_tsensor_fuse_read_spare(unsigned int spare)
+{
+ u32 val = 0;
+
+ tegra_fuse_readl(TEGRA30_FUSE_SPARE_BIT + spare * 4, &val);
+
+ return !!val;
+}
+
+static int tegra_tsensor_nvmem_setup(struct tegra_tsensor *ts)
+{
+ u32 i, ate_ver = 0, cal = 0, t1_25C = 0, t2_90C = 0;
+ int err, c1_25C, c2_90C;
+
+ err = tegra_fuse_readl(TEGRA30_FUSE_TEST_PROG_VER, &ate_ver);
+ if (err) {
+ dev_err_probe(ts->dev, err, "failed to get ATE version\n");
+ return err;
+ }
+
+ if (ate_ver < 8) {
+ dev_info(ts->dev, "unsupported ATE version: %u\n", ate_ver);
+ return -ENODEV;
+ }
+
+ /*
+ * We have two TSENSOR channels in a two different spots on SoC.
+ * Second channel provides more accurate data on older SoC versions,
+ * use it as a primary channel.
+ */
+ if (ate_ver <= 21) {
+ dev_info_once(ts->dev,
+ "older ATE version detected, channels remapped\n");
+ ts->swap_channels = true;
+ }
+
+ err = tegra_fuse_readl(TEGRA30_FUSE_TSENSOR_CALIB, &cal);
+ if (err) {
+ dev_err(ts->dev, "failed to get calibration data: %d\n", err);
+ return err;
+ }
+
+ /* get calibrated counter values for 25C/90C thresholds */
+ c1_25C = FIELD_GET(TEGRA30_FUSE_TSENSOR_CALIB_LOW, cal);
+ c2_90C = FIELD_GET(TEGRA30_FUSE_TSENSOR_CALIB_HIGH, cal);
+
+ /* and calibrated temperatures corresponding to the counter values */
+ for (i = 0; i < 7; i++) {
+ t1_25C |= tegra_tsensor_fuse_read_spare(14 + i) << i;
+ t1_25C |= tegra_tsensor_fuse_read_spare(21 + i) << i;
+
+ t2_90C |= tegra_tsensor_fuse_read_spare(0 + i) << i;
+ t2_90C |= tegra_tsensor_fuse_read_spare(7 + i) << i;
+ }
+
+ if (c2_90C - c1_25C <= t2_90C - t1_25C) {
+ dev_err(ts->dev, "invalid calibration data: %d %d %u %u\n",
+ c2_90C, c1_25C, t2_90C, t1_25C);
+ return -EINVAL;
+ }
+
+ /* all calibration coefficients are premultiplied by 1000000 */
+
+ ts->calib.a = DIV_ROUND_CLOSEST((t2_90C - t1_25C) * 1000000,
+ (c2_90C - c1_25C));
+
+ ts->calib.b = t1_25C * 1000000 - ts->calib.a * c1_25C;
+
+ if (tegra_sku_info.revision == TEGRA_REVISION_A01) {
+ ts->calib.m = -2775;
+ ts->calib.n = 1338811;
+ ts->calib.p = -7300000;
+ } else {
+ ts->calib.m = -3512;
+ ts->calib.n = 1528943;
+ ts->calib.p = -11100000;
+ }
+
+ /* except the coefficient of a reduced quadratic equation */
+ ts->calib.r = DIV_ROUND_CLOSEST(ts->calib.n, ts->calib.m * 2);
+
+ dev_info_once(ts->dev,
+ "calibration: %d %d %u %u ATE ver: %u SoC rev: %u\n",
+ c2_90C, c1_25C, t2_90C, t1_25C, ate_ver,
+ tegra_sku_info.revision);
+
+ return 0;
+}
+
+static int tegra_tsensor_register_channel(struct tegra_tsensor *ts,
+ unsigned int id)
+{
+ struct tegra_tsensor_channel *tsc = &ts->ch[id];
+ unsigned int hw_id = ts->swap_channels ? !id : id;
+
+ tsc->ts = ts;
+ tsc->id = id;
+ tsc->regs = ts->regs + 0x40 * (hw_id + 1);
+
+ tsc->tzd = devm_thermal_of_zone_register(ts->dev, id, tsc, &ops);
+ if (IS_ERR(tsc->tzd)) {
+ if (PTR_ERR(tsc->tzd) != -ENODEV)
+ return dev_err_probe(ts->dev, PTR_ERR(tsc->tzd),
+ "failed to register thermal zone\n");
+
+ /*
+ * It's okay if sensor isn't assigned to any thermal zone
+ * in a device-tree.
+ */
+ tsc->tzd = NULL;
+ return 0;
+ }
+
+ devm_thermal_add_hwmon_sysfs(ts->dev, tsc->tzd);
+
+ return 0;
+}
+
+static int tegra_tsensor_probe(struct platform_device *pdev)
+{
+ struct tegra_tsensor *ts;
+ unsigned int i;
+ int err, irq;
+
+ ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ts->dev = &pdev->dev;
+ platform_set_drvdata(pdev, ts);
+
+ ts->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ts->regs))
+ return PTR_ERR(ts->regs);
+
+ ts->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ts->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(ts->clk),
+ "failed to get clock\n");
+
+ ts->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(ts->rst))
+ return dev_err_probe(&pdev->dev, PTR_ERR(ts->rst),
+ "failed to get reset control\n");
+
+ err = tegra_tsensor_nvmem_setup(ts);
+ if (err)
+ return err;
+
+ err = tegra_tsensor_hw_enable(ts);
+ if (err)
+ return err;
+
+ err = devm_add_action_or_reset(&pdev->dev,
+ devm_tegra_tsensor_hw_disable,
+ ts);
+ if (err)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
+ err = tegra_tsensor_register_channel(ts, i);
+ if (err)
+ return err;
+ }
+
+ /*
+ * Enable the channels before setting the interrupt so
+ * set_trips() can not be called while we are setting up the
+ * register TSENSOR_SENSOR0_CONFIG1. With this we close a
+ * potential race window where we are setting up the TH2 and
+ * the temperature hits TH1 resulting to an update of the
+ * TSENSOR_SENSOR0_CONFIG1 register in the ISR.
+ */
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
+ err = tegra_tsensor_enable_hw_channel(ts, i);
+ if (err)
+ return err;
+ }
+
+ err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ tegra_tsensor_isr, IRQF_ONESHOT,
+ "tegra_tsensor", ts);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int __maybe_unused tegra_tsensor_suspend(struct device *dev)
+{
+ struct tegra_tsensor *ts = dev_get_drvdata(dev);
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
+ err = tegra_tsensor_disable_hw_channel(ts, i);
+ if (err)
+ goto enable_channel;
+ }
+
+ err = tegra_tsensor_hw_disable(ts);
+ if (err)
+ goto enable_channel;
+
+ return 0;
+
+enable_channel:
+ while (i--)
+ tegra_tsensor_enable_hw_channel(ts, i);
+
+ return err;
+}
+
+static int __maybe_unused tegra_tsensor_resume(struct device *dev)
+{
+ struct tegra_tsensor *ts = dev_get_drvdata(dev);
+ unsigned int i;
+ int err;
+
+ err = tegra_tsensor_hw_enable(ts);
+ if (err)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(ts->ch); i++) {
+ err = tegra_tsensor_enable_hw_channel(ts, i);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra_tsensor_pm_ops = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_tsensor_suspend,
+ tegra_tsensor_resume)
+};
+
+static const struct of_device_id tegra_tsensor_of_match[] = {
+ { .compatible = "nvidia,tegra30-tsensor", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra_tsensor_of_match);
+
+static struct platform_driver tegra_tsensor_driver = {
+ .probe = tegra_tsensor_probe,
+ .driver = {
+ .name = "tegra30-tsensor",
+ .of_match_table = tegra_tsensor_of_match,
+ .pm = &tegra_tsensor_pm_ops,
+ },
+};
+module_platform_driver(tegra_tsensor_driver);
+
+MODULE_DESCRIPTION("NVIDIA Tegra30 Thermal Sensor driver");
+MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/testing/Makefile b/drivers/thermal/testing/Makefile
new file mode 100644
index 000000000..ede9678ef
--- /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 000000000..90e06c593
--- /dev/null
+++ b/drivers/thermal/testing/command.c
@@ -0,0 +1,251 @@
+// 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 workqueue_struct *tt_wq __ro_after_init;
+
+struct dentry *d_testing __ro_after_init;
+static struct dentry *d_command __ro_after_init;
+
+#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:
+ if (!arg || !*arg)
+ return -EINVAL;
+
+ ret = tt_del_tz(arg);
+ break;
+
+ case TT_CMD_TZADDTRIP:
+ if (!arg || !*arg)
+ return -EINVAL;
+
+ ret = tt_zone_add_trip(arg);
+ break;
+
+ case TT_CMD_TZREG:
+ if (!arg || !*arg)
+ return -EINVAL;
+
+ ret = tt_zone_reg(arg);
+ break;
+
+ case TT_CMD_TZUNREG:
+ if (!arg || !*arg)
+ return -EINVAL;
+
+ 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)
+{
+ int error;
+
+ tt_wq = alloc_workqueue("thermal_testing", WQ_UNBOUND, 0);
+ if (!tt_wq)
+ return -ENOMEM;
+
+ d_testing = debugfs_create_dir("thermal-testing", NULL);
+ if (IS_ERR(d_testing)) {
+ error = PTR_ERR(d_testing);
+ goto destroy_wq;
+ }
+
+ d_command = debugfs_create_file("command", 0200, d_testing, NULL, &tt_command_fops);
+ if (IS_ERR(d_command)) {
+ error = PTR_ERR(d_command);
+ goto remove_d_testing;
+ }
+
+ return 0;
+
+remove_d_testing:
+ debugfs_remove(d_testing);
+destroy_wq:
+ destroy_workqueue(tt_wq);
+ return error;
+}
+module_init(thermal_testing_init);
+
+static void __exit thermal_testing_exit(void)
+{
+ /* First, prevent new commands from being entered. */
+ debugfs_remove(d_command);
+ /* Flush commands in progress (if any). */
+ flush_workqueue(tt_wq);
+ destroy_workqueue(tt_wq);
+ /* Remove the directory structure and clean up. */
+ 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 000000000..5880c9a63
--- /dev/null
+++ b/drivers/thermal/testing/thermal_testing.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/workqueue.h>
+
+extern struct workqueue_struct *tt_wq;
+
+static inline void tt_queue_work(struct work_struct *work)
+{
+ queue_work(tt_wq, work);
+}
+
+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 000000000..6db26276d
--- /dev/null
+++ b/drivers/thermal/testing/zone.c
@@ -0,0 +1,446 @@
+// 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 "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_obj(*tt_zone);
+ if (!tt_zone)
+ return -ENOMEM;
+
+ struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);
+ 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);
+ tt_queue_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 = kstrtoint(arg, 10, &id);
+ if (ret < 0)
+ return ret;
+
+ struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);
+ 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;
+ tt_queue_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 = kstrtoint(arg, 10, &id);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ 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_obj(*tt_work);
+ if (!tt_work)
+ return -ENOMEM;
+
+ struct tt_trip *tt_trip __free(kfree) = kzalloc_obj(*tt_trip);
+ 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);
+ tt_queue_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) = kzalloc_objs(*trips,
+ tt_zone->num_trips);
+ 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-fw.c b/drivers/thermal/thermal-fw.c
new file mode 100644
index 000000000..47c557e32
--- /dev/null
+++ b/drivers/thermal/thermal-fw.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * of-thermal-testing.c - Generic Thermal Management device tree testing support
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#include <linux/platform_device.h>
+#include <linux/fwnode.h>
+#include <linux/module.h>
+
+
+
diff --git a/drivers/thermal/thermal-generic-adc.c b/drivers/thermal/thermal-generic-adc.c
new file mode 100644
index 000000000..cfdb8e674
--- /dev/null
+++ b/drivers/thermal/thermal-generic-adc.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Generic ADC thermal driver
+ *
+ * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved.
+ *
+ * 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>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "thermal_hwmon.h"
+
+struct gadc_thermal_info {
+ struct device *dev;
+ struct thermal_zone_device *tz_dev;
+ struct iio_channel *channel;
+ s32 *lookup_table;
+ int nlookup_table;
+};
+
+static int gadc_thermal_adc_to_temp(struct gadc_thermal_info *gti, int val)
+{
+ int temp, temp_hi, temp_lo, adc_hi, adc_lo;
+ int i;
+
+ if (!gti->lookup_table)
+ return val;
+
+ for (i = 0; i < gti->nlookup_table; i++) {
+ if (val >= gti->lookup_table[2 * i + 1])
+ break;
+ }
+
+ if (i == 0) {
+ temp = gti->lookup_table[0];
+ } else if (i >= gti->nlookup_table) {
+ temp = gti->lookup_table[2 * (gti->nlookup_table - 1)];
+ } else {
+ adc_hi = gti->lookup_table[2 * i - 1];
+ adc_lo = gti->lookup_table[2 * i + 1];
+
+ temp_hi = gti->lookup_table[2 * i - 2];
+ temp_lo = gti->lookup_table[2 * i];
+
+ temp = temp_hi + mult_frac(temp_lo - temp_hi, val - adc_hi,
+ adc_lo - adc_hi);
+ }
+
+ return temp;
+}
+
+static int gadc_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct gadc_thermal_info *gti = thermal_zone_device_priv(tz);
+ int val;
+ int ret;
+
+ ret = iio_read_channel_processed(gti->channel, &val);
+ if (ret < 0)
+ return ret;
+
+ *temp = gadc_thermal_adc_to_temp(gti, val);
+
+ return 0;
+}
+
+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)
+{
+ struct device_node *np = dev->of_node;
+ enum iio_chan_type chan_type;
+ int ntable;
+ int ret;
+
+ ntable = of_property_count_elems_of_size(np, "temperature-lookup-table",
+ sizeof(u32));
+ if (ntable <= 0) {
+ ret = iio_get_channel_type(gti->channel, &chan_type);
+ if (ret || chan_type != IIO_TEMP)
+ dev_notice(dev,
+ "no lookup table, assuming DAC channel returns milliCelcius\n");
+ return 0;
+ }
+
+ if (ntable % 2) {
+ dev_err(dev, "Pair of temperature vs ADC read value missing\n");
+ return -EINVAL;
+ }
+
+ gti->lookup_table = devm_kcalloc(dev,
+ ntable, sizeof(*gti->lookup_table),
+ GFP_KERNEL);
+ if (!gti->lookup_table)
+ return -ENOMEM;
+
+ ret = of_property_read_u32_array(np, "temperature-lookup-table",
+ (u32 *)gti->lookup_table, ntable);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read temperature lookup table: %d\n",
+ ret);
+ return ret;
+ }
+
+ gti->nlookup_table = ntable / 2;
+
+ return 0;
+}
+
+static int gadc_thermal_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gadc_thermal_info *gti;
+ int ret;
+
+ if (!dev->of_node) {
+ dev_err(dev, "Only DT based supported\n");
+ return -ENODEV;
+ }
+
+ gti = devm_kzalloc(dev, sizeof(*gti), GFP_KERNEL);
+ if (!gti)
+ return -ENOMEM;
+
+ 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(dev, gti);
+ if (ret < 0)
+ return ret;
+
+ gti->dev = dev;
+
+ 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(dev,
+ "Thermal zone sensor register failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ devm_thermal_add_hwmon_sysfs(dev, gti->tz_dev);
+
+ return gadc_iio_register(&pdev->dev, gti);
+}
+
+static const struct of_device_id of_adc_thermal_match[] = {
+ { .compatible = "generic-adc-thermal", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_adc_thermal_match);
+
+static struct platform_driver gadc_thermal_driver = {
+ .driver = {
+ .name = "generic-adc-thermal",
+ .of_match_table = of_adc_thermal_match,
+ },
+ .probe = gadc_thermal_probe,
+};
+
+module_platform_driver(gadc_thermal_driver);
+
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("IIO_CONSUMER");
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
new file mode 100644
index 000000000..82e2f0d8a
--- /dev/null
+++ b/drivers/thermal/thermal_core.c
@@ -0,0 +1,1831 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * thermal.c - Generic Thermal Management Sysfs support.
+ *
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/kdev_t.h>
+#include <linux/idr.h>
+#include <linux/thermal.h>
+#include <linux/reboot.h>
+#include <linux/string.h>
+#include <linux/of.h>
+#include <linux/suspend.h>
+
+#define CREATE_TRACE_POINTS
+#include "thermal_trace.h"
+
+#include "thermal_core.h"
+#include "thermal_hwmon.h"
+
+static DEFINE_IDA(thermal_tz_ida);
+static DEFINE_IDA(thermal_cdev_ida);
+
+static LIST_HEAD(thermal_tz_list);
+static LIST_HEAD(thermal_cdev_list);
+static LIST_HEAD(thermal_governor_list);
+
+static DEFINE_MUTEX(thermal_list_lock);
+static DEFINE_MUTEX(thermal_governor_lock);
+
+static struct thermal_governor *def_governor;
+
+static bool thermal_pm_suspended;
+
+static struct workqueue_struct *thermal_wq __ro_after_init;
+
+/*
+ * Governor section: set of functions to handle thermal governors
+ *
+ * Functions to help in the life cycle of thermal governors within
+ * the thermal core and by the thermal governor code.
+ */
+
+static struct thermal_governor *__find_governor(const char *name)
+{
+ struct thermal_governor *pos;
+
+ if (!name || !name[0])
+ return def_governor;
+
+ list_for_each_entry(pos, &thermal_governor_list, governor_list)
+ if (!strncasecmp(name, pos->name, THERMAL_NAME_LENGTH))
+ return pos;
+
+ return NULL;
+}
+
+/**
+ * bind_previous_governor() - bind the previous governor of the thermal zone
+ * @tz: a valid pointer to a struct thermal_zone_device
+ * @failed_gov_name: the name of the governor that failed to register
+ *
+ * Register the previous governor of the thermal zone after a new
+ * governor has failed to be bound.
+ */
+static void bind_previous_governor(struct thermal_zone_device *tz,
+ const char *failed_gov_name)
+{
+ if (tz->governor && tz->governor->bind_to_tz) {
+ if (tz->governor->bind_to_tz(tz)) {
+ dev_err(&tz->device,
+ "governor %s failed to bind and the previous one (%s) failed to bind again, thermal zone %s has no governor\n",
+ failed_gov_name, tz->governor->name, tz->type);
+ tz->governor = NULL;
+ }
+ }
+}
+
+/**
+ * thermal_set_governor() - Switch to another governor
+ * @tz: a valid pointer to a struct thermal_zone_device
+ * @new_gov: pointer to the new governor
+ *
+ * Change the governor of thermal zone @tz.
+ *
+ * Return: 0 on success, an error if the new governor's bind_to_tz() failed.
+ */
+static int thermal_set_governor(struct thermal_zone_device *tz,
+ struct thermal_governor *new_gov)
+{
+ int ret = 0;
+
+ if (tz->governor && tz->governor->unbind_from_tz)
+ tz->governor->unbind_from_tz(tz);
+
+ if (new_gov && new_gov->bind_to_tz) {
+ ret = new_gov->bind_to_tz(tz);
+ if (ret) {
+ bind_previous_governor(tz, new_gov->name);
+
+ return ret;
+ }
+ }
+
+ tz->governor = new_gov;
+
+ return ret;
+}
+
+static int __init thermal_register_governor(struct thermal_governor *governor)
+{
+
+ if (!governor)
+ return -EINVAL;
+
+ if (__find_governor(governor->name))
+ return -EBUSY;
+
+ list_add(&governor->governor_list, &thermal_governor_list);
+
+ if (strncmp(governor->name, DEFAULT_THERMAL_GOVERNOR, THERMAL_NAME_LENGTH))
+ return 0;
+
+ if (!def_governor)
+ def_governor = governor;
+
+ return 0;
+}
+
+int thermal_zone_device_set_policy(struct thermal_zone_device *tz,
+ char *policy)
+{
+ struct thermal_governor *gov;
+ int ret = -EINVAL;
+
+ guard(mutex)(&thermal_governor_lock);
+ guard(thermal_zone)(tz);
+
+ gov = __find_governor(strim(policy));
+ if (gov)
+ ret = thermal_set_governor(tz, gov);
+
+ thermal_notify_tz_gov_change(tz, policy);
+
+ return ret;
+}
+
+int thermal_build_list_of_policies(char *buf)
+{
+ struct thermal_governor *pos;
+ ssize_t count = 0;
+
+ 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");
+
+ return count;
+}
+
+static void __init thermal_unregister_governors(void)
+{
+ struct thermal_governor *gov, *pos;
+
+ guard(mutex)(&thermal_governor_lock);
+
+ list_for_each_entry_safe(gov, pos, &thermal_governor_list, governor_list)
+ list_del(&gov->governor_list);
+}
+
+static int __init thermal_register_governors(void)
+{
+ struct thermal_governor **governor;
+
+ guard(mutex)(&thermal_governor_lock);
+
+ for_each_governor_table(governor) {
+ int ret;
+
+ ret = thermal_register_governor(*governor);
+ if (ret) {
+ pr_err("Failed to register governor: '%s'",
+ (*governor)->name);
+ return ret;
+ }
+
+ pr_info("Registered thermal governor '%s'", (*governor)->name);
+ }
+
+ return 0;
+}
+
+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().
+ *
+ * An update means:
+ * - Non-critical trips will invoke the governor responsible for that zone;
+ * - Hot trips will produce a notification to userspace;
+ * - Critical trip point will cause a system shutdown.
+ */
+static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
+ unsigned long delay)
+{
+ if (delay > HZ)
+ delay = round_jiffies_relative(delay);
+
+ mod_delayed_work(thermal_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->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 struct thermal_governor *thermal_get_tz_governor(struct thermal_zone_device *tz)
+{
+ if (tz->governor)
+ return tz->governor;
+
+ return def_governor;
+}
+
+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\n", tz->type);
+
+ __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,
+ const struct thermal_trip *trip)
+{
+ 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);
+}
+
+static void move_trip_to_sorted_list(struct thermal_trip_desc *td,
+ struct list_head *list)
+{
+ struct thermal_trip_desc *entry;
+
+ /*
+ * 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 move_to_trips_high(struct thermal_zone_device *tz,
+ struct thermal_trip_desc *td)
+{
+ td->threshold = td->trip.temperature;
+ move_trip_to_sorted_list(td, &tz->trips_high);
+}
+
+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;
+
+ 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);
+}
+
+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 temperature, 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);
+}
+
+void thermal_zone_set_trip_temp(struct thermal_zone_device *tz,
+ struct thermal_trip *trip, int temp)
+{
+ struct thermal_trip_desc *td = trip_to_trip_desc(trip);
+ int old_temp = trip->temperature;
+
+ 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;
+ }
+
+ 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);
+
+ move_to_trips_invalid(tz, td);
+ return;
+ }
+
+ /*
+ * 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_handle_trips(struct thermal_zone_device *tz,
+ struct thermal_governor *governor,
+ int *low, int *high)
+{
+ 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)
+{
+ struct thermal_governor *governor = thermal_get_tz_governor(tz);
+ int low = -INT_MAX, high = INT_MAX;
+ int temp, ret;
+
+ if (tz->state != TZ_STATE_READY || tz->mode != THERMAL_DEVICE_ENABLED)
+ return;
+
+ 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;
+ }
+
+ tz->recheck_delay_jiffies = THERMAL_RECHECK_DELAY;
+
+ tz->last_temperature = tz->temperature;
+ tz->temperature = temp;
+
+ trace_thermal_temperature(tz);
+
+ thermal_genl_sampling_temp(tz->id, temp);
+
+ tz->notify_event = event;
+
+ 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;
+
+ guard(thermal_zone)(tz);
+
+ /* do nothing if mode isn't changing */
+ if (mode == tz->mode)
+ return 0;
+
+ ret = __thermal_zone_device_set_mode(tz, mode);
+ if (ret)
+ return ret;
+
+ __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+
+ if (mode == THERMAL_DEVICE_ENABLED)
+ thermal_notify_tz_enable(tz);
+ else
+ thermal_notify_tz_disable(tz);
+
+ return 0;
+}
+
+int thermal_zone_device_enable(struct thermal_zone_device *tz)
+{
+ return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_ENABLED);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_enable);
+
+int thermal_zone_device_disable(struct thermal_zone_device *tz)
+{
+ return thermal_zone_device_set_mode(tz, THERMAL_DEVICE_DISABLED);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_disable);
+
+void thermal_zone_device_update(struct thermal_zone_device *tz,
+ enum thermal_notify_event event)
+{
+ guard(thermal_zone)(tz);
+
+ __thermal_zone_device_update(tz, event);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_update);
+
+int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *),
+ void *data)
+{
+ struct thermal_governor *gov;
+
+ guard(mutex)(&thermal_governor_lock);
+
+ list_for_each_entry(gov, &thermal_governor_list, governor_list) {
+ int ret;
+
+ ret = cb(gov, data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int for_each_thermal_cooling_device(int (*cb)(struct thermal_cooling_device *,
+ void *), void *data)
+{
+ struct thermal_cooling_device *cdev;
+
+ guard(mutex)(&thermal_list_lock);
+
+ list_for_each_entry(cdev, &thermal_cdev_list, node) {
+ int ret;
+
+ ret = cb(cdev, data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int for_each_thermal_zone(int (*cb)(struct thermal_zone_device *, void *),
+ void *data)
+{
+ struct thermal_zone_device *tz;
+
+ guard(mutex)(&thermal_list_lock);
+
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ int ret;
+
+ ret = cb(tz, data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+struct thermal_zone_device *thermal_zone_get_by_id(int id)
+{
+ struct thermal_zone_device *tz;
+
+ guard(mutex)(&thermal_list_lock);
+
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ if (tz->id == id) {
+ get_device(&tz->device);
+ return tz;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Device management section: cooling devices, zones devices, and binding
+ *
+ * Set of functions provided by the thermal core for:
+ * - cooling devices lifecycle: registration, unregistration,
+ * binding, and unbinding.
+ * - thermal zone devices lifecycle: registration, unregistration,
+ * 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_bind_cdev_to_trip - bind a cooling device to a thermal zone
+ * @tz: pointer to struct thermal_zone_device
+ * @td: descriptor of the trip point to bind @cdev to
+ * @cdev: pointer to struct thermal_cooling_device
+ * @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.
+ * This function is usually called in the thermal zone device .bind callback.
+ *
+ * Return: 0 on success, the proper error value otherwise.
+ */
+static int thermal_bind_cdev_to_trip(struct thermal_zone_device *tz,
+ struct thermal_trip_desc *td,
+ struct thermal_cooling_device *cdev,
+ struct cooling_spec *cool_spec)
+{
+ struct thermal_instance *dev;
+ bool upper_no_limit;
+ int result;
+
+ /* lower default 0, upper default max_state */
+ if (cool_spec->lower == THERMAL_NO_LIMIT)
+ cool_spec->lower = 0;
+
+ if (cool_spec->upper == THERMAL_NO_LIMIT) {
+ cool_spec->upper = cdev->max_state;
+ upper_no_limit = true;
+ } else {
+ upper_no_limit = false;
+ }
+
+ if (cool_spec->lower > cool_spec->upper || cool_spec->upper > cdev->max_state)
+ return -EINVAL;
+
+ dev = kzalloc_obj(*dev);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->cdev = cdev;
+ dev->trip = &td->trip;
+ dev->upper = cool_spec->upper;
+ dev->upper_no_limit = upper_no_limit;
+ dev->lower = cool_spec->lower;
+ dev->target = THERMAL_NO_TARGET;
+ dev->weight = cool_spec->weight;
+
+ result = ida_alloc(&tz->ida, GFP_KERNEL);
+ if (result < 0)
+ goto free_mem;
+
+ dev->id = result;
+ snprintf(dev->name, sizeof(dev->name), "cdev%d", dev->id);
+ result =
+ sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
+ if (result)
+ goto release_ida;
+
+ 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;
+ dev->attr.show = trip_point_show;
+ result = device_create_file(&tz->device, &dev->attr);
+ if (result)
+ goto remove_symbol_link;
+
+ 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;
+ dev->weight_attr.show = weight_show;
+ dev->weight_attr.store = weight_store;
+ result = device_create_file(&tz->device, &dev->weight_attr);
+ if (result)
+ goto remove_trip_file;
+
+ result = thermal_instance_add(dev, cdev, td);
+ if (result)
+ goto remove_weight_file;
+
+ 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);
+remove_symbol_link:
+ sysfs_remove_link(&tz->device.kobj, dev->name);
+release_ida:
+ ida_free(&tz->ida, dev->id);
+free_mem:
+ kfree(dev);
+ return result;
+}
+
+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_unbind_cdev_from_trip - unbind a cooling device from a thermal zone.
+ * @tz: pointer to a struct thermal_zone_device.
+ * @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.
+ */
+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;
+
+ list_for_each_entry_safe(pos, next, &td->thermal_instances, trip_node) {
+ if (pos->cdev == cdev) {
+ thermal_instance_delete(pos);
+ goto unbind;
+ }
+ }
+
+ 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);
+}
+
+static const struct class thermal_class = {
+ .name = "thermal",
+};
+static bool thermal_class_unavailable __ro_after_init = true;
+
+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 cdev %s to trip %d failed: %d\n",
+ cdev->type, thermal_zone_trip_id(tz, &td->trip), ret);
+}
+
+static bool __thermal_zone_cdev_bind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
+{
+ struct thermal_trip_desc *td;
+ bool update_tz = false;
+
+ 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);
+}
+
+static void thermal_cdev_release(struct device *dev)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+
+ thermal_cooling_device_destroy_sysfs(cdev);
+ kfree_const(cdev->type);
+ ida_free(&thermal_cdev_ida, cdev->id);
+ kfree(cdev);
+}
+
+struct thermal_cooling_device *
+thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+ int ret;
+
+ if (!ops || !ops->get_max_state || !ops->get_cur_state ||
+ !ops->set_cur_state)
+ return ERR_PTR(-EINVAL);
+
+ if (thermal_class_unavailable)
+ return ERR_PTR(-ENODEV);
+
+ cdev = kzalloc_obj(*cdev);
+ if (!cdev)
+ return ERR_PTR(-ENOMEM);
+
+ cdev->ops = ops;
+
+ ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL);
+ if (ret < 0)
+ goto out_kfree_cdev;
+ cdev->id = ret;
+
+ cdev->type = kstrdup_const(type ? type : "", GFP_KERNEL);
+ if (!cdev->type) {
+ ret = -ENOMEM;
+ goto out_ida_remove;
+ }
+
+ return cdev;
+
+out_ida_remove:
+ ida_free(&thermal_cdev_ida, cdev->id);
+out_kfree_cdev:
+ kfree(cdev);
+ return ERR_PTR(ret);
+}
+
+int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata)
+{
+ unsigned long current_state;
+ int ret;
+
+ mutex_init(&cdev->lock);
+ INIT_LIST_HEAD(&cdev->thermal_instances);
+ cdev->updated = false;
+ cdev->device.class = &thermal_class;
+ cdev->device.release = thermal_cdev_release;
+ device_initialize(&cdev->device);
+ cdev->devdata = devdata;
+
+ ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
+ if (ret)
+ goto out_put_device;
+
+ ret = cdev->ops->get_max_state(cdev, &cdev->max_state);
+ if (ret)
+ goto out_put_device;
+
+ /*
+ * 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 = device_add(&cdev->device);
+ if (ret)
+ goto out_put_device;
+
+ if (current_state <= cdev->max_state)
+ thermal_debug_cdev_add(cdev, current_state);
+
+ thermal_cooling_device_init_complete(cdev);
+
+ return 0;
+
+out_put_device:
+ /*
+ * The device core will release the memory via
+ * thermal_release() after put_device() is called in the error
+ * path
+ */
+ put_device(&cdev->device);
+ return ret;
+}
+
+/**
+ * thermal_cooling_device_register() - register a new thermal cooling device
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+thermal_cooling_device_register(const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+ int ret;
+
+ cdev = thermal_cooling_device_alloc(type, ops);
+ if (IS_ERR(cdev))
+ return cdev;
+
+ ret = thermal_cooling_device_add(cdev, devdata);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(thermal_cooling_device_register);
+
+static void thermal_cooling_device_release(void *data)
+{
+ struct thermal_cooling_device *cdev = data;
+
+ thermal_cooling_device_unregister(cdev);
+}
+
+/**
+ * devm_thermal_cooling_device_register() - register a thermal cooling device
+ *
+ * @dev: a valid struct device pointer of a sensor device.
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This function will register a cooling device. This interface
+ * function adds a new thermal cooling device (fan/processor/...) to
+ * /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind
+ * itself to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+ int ret;
+
+ cdev = thermal_cooling_device_register(type, devdata, ops);
+ if (IS_ERR(cdev))
+ return cdev;
+
+ ret = devm_add_action_or_reset(dev, thermal_cooling_device_release, cdev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(devm_thermal_cooling_device_register);
+
+static bool thermal_cooling_device_present(struct thermal_cooling_device *cdev)
+{
+ struct thermal_cooling_device *pos = NULL;
+
+ list_for_each_entry(pos, &thermal_cdev_list, node) {
+ if (pos == cdev)
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * thermal_cooling_device_update - Update a cooling device object
+ * @cdev: Target cooling device.
+ *
+ * Update @cdev to reflect a change of the underlying hardware or platform.
+ *
+ * Must be called when the maximum cooling state of @cdev becomes invalid and so
+ * its .get_max_state() callback needs to be run to produce the new maximum
+ * cooling state value.
+ */
+void thermal_cooling_device_update(struct thermal_cooling_device *cdev)
+{
+ struct thermal_instance *ti;
+ unsigned long state;
+
+ if (IS_ERR_OR_NULL(cdev))
+ return;
+
+ /*
+ * Hold thermal_list_lock throughout the update to prevent the device
+ * from going away while being updated.
+ */
+ guard(mutex)(&thermal_list_lock);
+
+ if (!thermal_cooling_device_present(cdev))
+ return;
+
+ /*
+ * Update under the cdev lock to prevent the state from being set beyond
+ * the new limit concurrently.
+ */
+ guard(cooling_dev)(cdev);
+
+ if (cdev->ops->get_max_state(cdev, &cdev->max_state))
+ return;
+
+ thermal_cooling_device_stats_reinit(cdev);
+
+ list_for_each_entry(ti, &cdev->thermal_instances, cdev_node) {
+ if (ti->upper == cdev->max_state)
+ continue;
+
+ if (ti->upper < cdev->max_state) {
+ if (ti->upper_no_limit)
+ ti->upper = cdev->max_state;
+
+ continue;
+ }
+
+ ti->upper = cdev->max_state;
+ if (ti->lower > ti->upper)
+ ti->lower = ti->upper;
+
+ if (ti->target == THERMAL_NO_TARGET)
+ continue;
+
+ if (ti->target > ti->upper)
+ ti->target = ti->upper;
+ }
+
+ if (cdev->ops->get_cur_state(cdev, &state) || state > cdev->max_state)
+ return;
+
+ thermal_cooling_device_stats_update(cdev, state);
+}
+EXPORT_SYMBOL_GPL(thermal_cooling_device_update);
+
+static void __thermal_zone_cdev_unbind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
+{
+ struct thermal_trip_desc *td;
+
+ 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;
+}
+
+/**
+ * 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)
+{
+ if (!cdev)
+ return;
+
+ thermal_debug_cdev_remove(cdev);
+
+ if (thermal_cooling_device_exit(cdev))
+ device_unregister(&cdev->device);
+}
+EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
+
+int thermal_zone_get_crit_temp(struct thermal_zone_device *tz, int *temp)
+{
+ const struct thermal_trip_desc *td;
+ int ret = -EINVAL;
+
+ 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;
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_get_crit_temp);
+
+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);
+}
+
+static void thermal_zone_device_init(struct thermal_zone_device *tz)
+{
+ struct thermal_trip_desc *td, *next;
+
+ INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
+
+ 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(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);
+ }
+}
+
+static int thermal_zone_init_governor(struct thermal_zone_device *tz)
+{
+ 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);
+}
+
+static void thermal_zone_init_complete(struct thermal_zone_device *tz)
+{
+ struct thermal_cooling_device *cdev;
+
+ guard(mutex)(&thermal_list_lock);
+
+ list_add_tail(&tz->node, &thermal_tz_list);
+
+ guard(thermal_zone)(tz);
+
+ /* Bind cooling devices for this zone. */
+ list_for_each_entry(cdev, &thermal_cdev_list, node)
+ __thermal_zone_cdev_bind(tz, cdev);
+
+ 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;
+
+ __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+}
+
+static void thermal_zone_device_release(struct device *dev)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ thermal_zone_destroy_device_groups(tz);
+ thermal_set_governor(tz, NULL);
+ ida_destroy(&tz->ida);
+ mutex_destroy(&tz->lock);
+ complete(&tz->removal);
+}
+
+/**
+ * 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
+ * @devdata: private device data
+ * @ops: standard thermal zone device callbacks
+ * @tzp: thermal zone platform parameters
+ * @passive_delay: number of milliseconds to wait between polls when
+ * performing passive cooling
+ * @polling_delay: number of milliseconds to wait between polls when checking
+ * whether trip points have been crossed (0 for interrupt
+ * driven systems)
+ *
+ * This interface function adds a new thermal zone device (sensor) to
+ * /sys/class/thermal folder as thermal_zone[0-*]. It tries to bind all the
+ * thermal cooling devices registered at the same time.
+ * thermal_zone_device_unregister() must be called when the device is no
+ * longer needed. The passive cooling depends on the .get_trend() return value.
+ *
+ * Return: a pointer to the created struct thermal_zone_device or an
+ * in case of error, an ERR_PTR. Caller must check return value with
+ * IS_ERR*() helpers.
+ */
+struct thermal_zone_device *
+thermal_zone_device_register_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;
+ size_t type_len = 0;
+ int id;
+ int result;
+
+ if (type)
+ type_len = strnlen(type, THERMAL_NAME_LENGTH);
+
+ if (type_len == 0) {
+ pr_err("No thermal zone type defined\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (type_len == THERMAL_NAME_LENGTH) {
+ pr_err("Thermal zone name (%s) too long, should be under %d chars\n",
+ type, THERMAL_NAME_LENGTH);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (num_trips < 0) {
+ pr_err("Incorrect number of thermal trips\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (!ops || !ops->get_temp) {
+ pr_err("Thermal zone device ops not defined or invalid\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (num_trips > 0 && !trips)
+ return ERR_PTR(-EINVAL);
+
+ if (polling_delay && passive_delay > polling_delay)
+ return ERR_PTR(-EINVAL);
+
+ if (thermal_class_unavailable)
+ return ERR_PTR(-ENODEV);
+
+ tz = kzalloc_flex(*tz, trips, num_trips);
+ if (!tz)
+ return ERR_PTR(-ENOMEM);
+
+ if (tzp) {
+ tz->tzp = kmemdup(tzp, sizeof(*tzp), GFP_KERNEL);
+ if (!tz->tzp) {
+ result = -ENOMEM;
+ goto free_tz;
+ }
+ }
+
+ 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;
+ goto free_tzp;
+ }
+
+ tz->id = id;
+ strscpy(tz->type, type, sizeof(tz->type));
+
+ tz->ops = *ops;
+ if (!tz->ops.critical)
+ tz->ops.critical = thermal_zone_device_critical;
+
+ tz->device.class = &thermal_class;
+ tz->device.release = thermal_zone_device_release;
+ tz->devdata = devdata;
+ 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);
+ }
+
+ 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;
+
+ tz->state = TZ_STATE_FLAG_INIT;
+
+ result = dev_set_name(&tz->device, "thermal_zone%d", tz->id);
+ if (result)
+ goto remove_id;
+
+ thermal_zone_device_init(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) {
+ thermal_set_governor(tz, NULL);
+ goto remove_id;
+ }
+
+ result = device_register(&tz->device);
+ if (result)
+ goto release_device;
+
+ if (!tz->tzp || !tz->tzp->no_hwmon) {
+ result = thermal_add_hwmon_sysfs(tz);
+ if (result)
+ goto unregister;
+ }
+
+ result = thermal_thresholds_init(tz);
+ if (result)
+ goto remove_hwmon;
+
+ thermal_zone_init_complete(tz);
+
+ thermal_notify_tz_create(tz);
+
+ 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);
+ wait_for_completion(&tz->removal);
+remove_id:
+ ida_free(&thermal_tz_ida, id);
+free_tzp:
+ kfree(tz->tzp);
+free_tz:
+ kfree(tz);
+ return ERR_PTR(result);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_register_with_trips);
+
+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, 0, devdata,
+ ops, tzp, 0, 0);
+}
+EXPORT_SYMBOL_GPL(thermal_tripless_zone_device_register);
+
+void *thermal_zone_device_priv(struct thermal_zone_device *tzd)
+{
+ return tzd->devdata;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_priv);
+
+const char *thermal_zone_device_type(struct thermal_zone_device *tzd)
+{
+ return tzd->type;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_type);
+
+int thermal_zone_device_id(struct thermal_zone_device *tzd)
+{
+ return tzd->id;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_id);
+
+struct device *thermal_zone_device(struct thermal_zone_device *tzd)
+{
+ return &tzd->device;
+}
+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)
+{
+ if (!tz)
+ return;
+
+ thermal_debug_tz_remove(tz);
+
+ if (!thermal_zone_exit(tz))
+ return;
+
+ cancel_delayed_work_sync(&tz->poll_queue);
+
+ thermal_thresholds_exit(tz);
+ thermal_remove_hwmon_sysfs(tz);
+
+ device_del(&tz->device);
+ put_device(&tz->device);
+
+ thermal_notify_tz_delete(tz);
+
+ wait_for_completion(&tz->removal);
+
+ ida_free(&thermal_tz_ida, tz->id);
+
+ kfree(tz->tzp);
+ kfree(tz);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
+
+/**
+ * thermal_zone_get_zone_by_name() - search for a zone and returns its ref
+ * @name: thermal zone name to fetch the temperature
+ *
+ * When only one zone is found with the passed name, returns a reference to it.
+ *
+ * Return: On success returns a reference to an unique thermal zone with
+ * matching name equals to @name, an ERR_PTR otherwise (-EINVAL for invalid
+ * paramenters, -ENODEV for not found and -EEXIST for multiple matches).
+ */
+struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name)
+{
+ struct thermal_zone_device *pos = NULL, *ref = ERR_PTR(-EINVAL);
+ unsigned int found = 0;
+
+ if (!name)
+ return ERR_PTR(-EINVAL);
+
+ guard(mutex)(&thermal_list_lock);
+
+ list_for_each_entry(pos, &thermal_tz_list, node)
+ if (!strncasecmp(name, pos->type, THERMAL_NAME_LENGTH)) {
+ found++;
+ ref = pos;
+ }
+
+ if (!found)
+ return ERR_PTR(-ENODEV);
+
+ /* Success only when one zone is found. */
+ if (found > 1)
+ return ERR_PTR(-EEXIST);
+
+ return ref;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
+
+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);
+
+ /* If the thermal zone is going away, there's nothing to do. */
+ if (tz->state & TZ_STATE_FLAG_EXIT)
+ return;
+
+ 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;
+
+ /* Prevent new work from getting to the workqueue subsequently. */
+ cancel_delayed_work(&tz->poll_queue);
+}
+
+static void __thermal_pm_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);
+}
+
+void thermal_pm_prepare(void)
+{
+ if (thermal_class_unavailable)
+ return;
+
+ __thermal_pm_prepare();
+ /*
+ * Allow any leftover thermal work items already on the worqueue to
+ * complete so they don't get in the way later.
+ */
+ flush_workqueue(thermal_wq);
+}
+
+static void thermal_zone_pm_complete(struct thermal_zone_device *tz)
+{
+ guard(thermal_zone)(tz);
+
+ 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(thermal_wq, &tz->poll_queue, 0);
+}
+
+void thermal_pm_complete(void)
+{
+ struct thermal_zone_device *tz;
+
+ if (thermal_class_unavailable)
+ return;
+
+ 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 __init thermal_init(void)
+{
+ int result;
+
+ thermal_debug_init();
+
+ result = thermal_netlink_init();
+ if (result)
+ goto error;
+
+ thermal_wq = alloc_workqueue("thermal_events", WQ_UNBOUND, 0);
+ if (!thermal_wq) {
+ result = -ENOMEM;
+ goto unregister_netlink;
+ }
+
+ result = thermal_register_governors();
+ if (result)
+ goto unregister_governors;
+
+ result = class_register(&thermal_class);
+ if (result)
+ goto unregister_governors;
+
+ thermal_class_unavailable = false;
+
+ return 0;
+
+unregister_governors:
+ thermal_unregister_governors();
+ destroy_workqueue(thermal_wq);
+unregister_netlink:
+ thermal_netlink_exit();
+error:
+ mutex_destroy(&thermal_list_lock);
+ mutex_destroy(&thermal_governor_lock);
+ return result;
+}
+postcore_initcall(thermal_init);
diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h
new file mode 100644
index 000000000..e98b0aa5a
--- /dev/null
+++ b/drivers/thermal/thermal_core.h
@@ -0,0 +1,312 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * thermal_core.h
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Author: Durgadoss R <durgadoss.r@intel.com>
+ */
+
+#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_attribute_group: trip point sysfs attributes
+ * @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
+ * @debugfs: this thermal zone device's thermal zone debug info
+ * @user_thresholds: list of userspace thresholds for temp. limit notifications
+ * @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)
+#define DEFAULT_THERMAL_GOVERNOR "step_wise"
+#elif defined(CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE)
+#define DEFAULT_THERMAL_GOVERNOR "fair_share"
+#elif defined(CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE)
+#define DEFAULT_THERMAL_GOVERNOR "user_space"
+#elif defined(CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR)
+#define DEFAULT_THERMAL_GOVERNOR "power_allocator"
+#elif defined(CONFIG_THERMAL_DEFAULT_GOV_BANG_BANG)
+#define DEFAULT_THERMAL_GOVERNOR "bang_bang"
+#endif
+
+/* Initial state of a cooling device during binding */
+#define THERMAL_NO_TARGET -1UL
+
+/* Init section thermal table */
+extern struct thermal_governor *__governor_thermal_table[];
+extern struct thermal_governor *__governor_thermal_table_end[];
+
+#define THERMAL_TABLE_ENTRY(table, name) \
+ static typeof(name) *__thermal_table_entry_##name \
+ __used __section("__" #table "_thermal_table") = &name
+
+#define THERMAL_GOVERNOR_DECLARE(name) THERMAL_TABLE_ENTRY(governor, name)
+
+#define for_each_governor_table(__governor) \
+ for (__governor = __governor_thermal_table; \
+ __governor < __governor_thermal_table_end; \
+ __governor++)
+
+int for_each_thermal_zone(int (*cb)(struct thermal_zone_device *, void *),
+ void *);
+
+int for_each_thermal_cooling_device(int (*cb)(struct thermal_cooling_device *,
+ void *), void *);
+
+int for_each_thermal_governor(int (*cb)(struct thermal_governor *, void *),
+ void *thermal_governor);
+
+struct thermal_zone_device *thermal_zone_get_by_id(int id);
+
+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)
+{
+ return cdev->ops->get_requested_power && cdev->ops->state2power &&
+ cdev->ops->power2state;
+}
+
+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, const struct thermal_trip *trip);
+
+/*
+ * This structure is used to describe the behavior of
+ * a certain cooling device on a certain trip point
+ * in a certain thermal zone
+ */
+struct thermal_instance {
+ int id;
+ char name[THERMAL_NAME_LENGTH];
+ struct thermal_cooling_device *cdev;
+ 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 */
+ unsigned long target; /* expected cooling state */
+ char attr_name[THERMAL_NAME_LENGTH];
+ struct device_attribute attr;
+ char weight_attr_name[THERMAL_NAME_LENGTH];
+ struct device_attribute weight_attr;
+ 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;
+};
+
+#define to_thermal_zone(_dev) \
+ container_of(_dev, struct thermal_zone_device, device)
+
+#define to_cooling_device(_dev) \
+ container_of(_dev, struct thermal_cooling_device, device)
+
+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);
+
+struct thermal_cooling_device *
+thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops);
+
+int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata);
+
+/* Helpers */
+#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 *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);
+void thermal_cooling_device_stats_reinit(struct thermal_cooling_device *cdev);
+/* used only at binding time */
+ssize_t trip_point_show(struct device *, struct device_attribute *, char *);
+ssize_t weight_show(struct device *, struct device_attribute *, char *);
+ssize_t weight_store(struct device *, struct device_attribute *, const char *,
+ size_t);
+
+#ifdef CONFIG_THERMAL_STATISTICS
+void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
+ unsigned long new_state);
+#else
+static inline void
+thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
+ unsigned long new_state) {}
+#endif /* CONFIG_THERMAL_STATISTICS */
+
+#endif /* __THERMAL_CORE_H__ */
diff --git a/drivers/thermal/thermal_debugfs.c b/drivers/thermal/thermal_debugfs.c
new file mode 100644
index 000000000..da02d8f17
--- /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_obj(*thermal_dbg);
+ 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_obj(*cdev_record);
+ 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_flex(*tze, trip_stats, tz->num_trips);
+ 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_puts(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 = kzalloc_objs(int, tz->num_trips);
+ 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 000000000..1c957ce2e
--- /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_fw.c b/drivers/thermal/thermal_fw.c
new file mode 100644
index 000000000..e8bd2049c
--- /dev/null
+++ b/drivers/thermal/thermal_fw.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * thermal-fw.c - Thermal components creation from firmware description
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#include <linux/fwnode.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+/**
+ * thermal_fwnode_cooling_device_register() - register an thermal cooling device
+ * @np: a pointer to a device tree node.
+ * @of_index: a cooling device index in the cooling controller
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This function will register a cooling device with device tree node reference.
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+thermal_fwnode_cooling_device_register(struct fwnode_handle *fwnode, int fwn_index,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+
+ cdev = __thermal_cooling_device_register(type, devdata, ops);
+ if (IS_ERR(cdev))
+ return cdev;
+
+ cdev->np = (struct device_node *)fwnode;
+ cdev->of_index = fwn_index;
+ thermal_cooling_device_init_complete(cdev);
+
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(thermal_fwnode_cooling_device_register);
+
+static struct thermal_cooling_device *
+__devm_thermal_fwnode_cooling_device_register(struct device *dev, struct fwnode_handle *fwnode,
+ int fwn_index, const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device **ptr, *tcd;
+
+ ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr),
+ GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ tcd = thermal_fwnode_cooling_device_register(fwnode, fwn_index, type, devdata, ops);
+ if (IS_ERR(tcd)) {
+ devres_free(ptr);
+ return tcd;
+ }
+
+ *ptr = tcd;
+ devres_add(dev, ptr);
+
+ return tcd;
+}
+
+/**
+ * devm_thermal_fwnode_cooling_device_register() - register a thermal cooling device
+ * @dev: a valid struct device pointer of a sensor device.
+ * @fw_index: a cooling device index in the cooling controller
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This function will register a cooling device with a firmware node reference.
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+devm_thermal_fwnode_cooling_device_register(struct device *dev, int fwn_index,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return __devm_thermal_fwnode_cooling_device_register(dev, dev_fwnode(dev), fwn_index,
+ type, devdata, ops);
+}
+EXPORT_SYMBOL_GPL(devm_thermal_fwnode_cooling_device_register);
+
+
diff --git a/drivers/thermal/thermal_helpers.c b/drivers/thermal/thermal_helpers.c
new file mode 100644
index 000000000..b1152ad7a
--- /dev/null
+++ b/drivers/thermal/thermal_helpers.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * thermal_helpers.c - helper functions to handle thermal devices
+ *
+ * Copyright (C) 2016 Eduardo Valentin <edubezval@gmail.com>
+ *
+ * Highly based on original thermal_core.c
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+
+#include "thermal_core.h"
+#include "thermal_trace.h"
+
+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->temperature > tz->last_temperature)
+ trend = THERMAL_TREND_RAISING;
+ else if (tz->temperature < tz->last_temperature)
+ trend = THERMAL_TREND_DROPPING;
+ else
+ trend = THERMAL_TREND_STABLE;
+ }
+
+ return trend;
+}
+
+static bool thermal_instance_present(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev,
+ const struct thermal_trip *trip)
+{
+ const struct thermal_trip_desc *td = trip_to_trip_desc(trip);
+ struct thermal_instance *ti;
+
+ list_for_each_entry(ti, &td->thermal_instances, trip_node) {
+ if (ti->cdev == cdev)
+ return true;
+ }
+
+ 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 thermal_instance_present(tz, cdev, trip);
+}
+EXPORT_SYMBOL_GPL(thermal_trip_is_bound_to_cdev);
+
+/**
+ * __thermal_zone_get_temp() - returns the temperature of a thermal zone
+ * @tz: a valid pointer to a struct thermal_zone_device
+ * @temp: a valid pointer to where to store the resulting temperature.
+ *
+ * When a valid thermal zone reference is passed, it will fetch its
+ * temperature and fill @temp.
+ *
+ * Both tz and tz->ops must be valid pointers when calling this function,
+ * 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)
+{
+ const struct thermal_trip_desc *td;
+ int crit_temp = INT_MAX;
+ int ret = -EINVAL;
+
+ lockdep_assert_held(&tz->lock);
+
+ ret = tz->ops.get_temp(tz, temp);
+
+ if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_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;
+ }
+ }
+
+ /*
+ * Only allow emulating a temperature when the real temperature
+ * is below the critical temperature so that the emulation code
+ * cannot hide critical conditions.
+ */
+ if (!ret && *temp < crit_temp)
+ *temp = tz->emul_temperature;
+ }
+
+ if (ret)
+ dev_dbg(&tz->device, "Failed to get temperature: %d\n", ret);
+
+ return ret;
+}
+
+/**
+ * thermal_zone_get_temp() - returns the temperature of a thermal zone
+ * @tz: a valid pointer to a struct thermal_zone_device
+ * @temp: a valid pointer to where to store the resulting temperature.
+ *
+ * When a valid thermal zone reference is passed, it will fetch its
+ * temperature and fill @temp.
+ *
+ * Return: On success returns 0, an error code otherwise
+ */
+int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ int ret;
+
+ if (IS_ERR_OR_NULL(tz))
+ return -EINVAL;
+
+ guard(thermal_zone)(tz);
+
+ if (!tz->ops.get_temp)
+ return -EINVAL;
+
+ 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 int thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev, int state)
+{
+ int ret;
+
+ /*
+ * 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)
+{
+ struct thermal_instance *instance;
+ unsigned long target = 0;
+
+ /* Make sure cdev enters the deepest cooling state */
+ list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
+ if (instance->target == THERMAL_NO_TARGET)
+ continue;
+ if (instance->target > target)
+ target = instance->target;
+ }
+
+ thermal_cdev_set_cur_state(cdev, target);
+
+ trace_cdev_update(cdev, target);
+ dev_dbg(&cdev->device, "set to state %lu\n", target);
+}
+
+/**
+ * thermal_cdev_update - update cooling device state if needed
+ * @cdev: pointer to struct thermal_cooling_device
+ *
+ * Update the cooling device state if there is a need.
+ */
+void thermal_cdev_update(struct thermal_cooling_device *cdev)
+{
+ guard(cooling_dev)(cdev);
+
+ if (!cdev->updated) {
+ __thermal_cdev_update(cdev);
+ cdev->updated = true;
+ }
+}
+
+/**
+ * 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);
+}
+
+/**
+ * thermal_zone_get_slope - return the slope attribute of the thermal zone
+ * @tz: thermal zone device with the slope attribute
+ *
+ * Return: If the thermal zone device has a slope attribute, return it, else
+ * return 1.
+ */
+int thermal_zone_get_slope(struct thermal_zone_device *tz)
+{
+ if (tz && tz->tzp)
+ return tz->tzp->slope;
+ return 1;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_get_slope);
+
+/**
+ * thermal_zone_get_offset - return the offset attribute of the thermal zone
+ * @tz: thermal zone device with the offset attribute
+ *
+ * Return: If the thermal zone device has a offset attribute, return it, else
+ * return 0.
+ */
+int thermal_zone_get_offset(struct thermal_zone_device *tz)
+{
+ if (tz && tz->tzp)
+ return tz->tzp->offset;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_get_offset);
diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c
new file mode 100644
index 000000000..45ce21914
--- /dev/null
+++ b/drivers/thermal/thermal_hwmon.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * thermal_hwmon.c - Generic Thermal Management hwmon support.
+ *
+ * Code based on Intel thermal_core.c. Copyrights of the original code:
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ *
+ * Copyright (C) 2013 Texas Instruments
+ * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/hwmon.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "thermal_hwmon.h"
+#include "thermal_core.h"
+
+/* hwmon sys I/F */
+/* thermal zone devices with the same type share one hwmon device */
+struct thermal_hwmon_device {
+ char type[THERMAL_NAME_LENGTH];
+ struct device *device;
+ int count;
+ struct list_head tz_list;
+ struct list_head node;
+};
+
+struct thermal_hwmon_attr {
+ struct device_attribute attr;
+ char name[16];
+};
+
+/* one temperature input for each thermal zone */
+struct thermal_hwmon_temp {
+ struct list_head hwmon_node;
+ struct thermal_zone_device *tz;
+ struct thermal_hwmon_attr temp_input; /* hwmon sys attr */
+ struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */
+ bool temp_crit_present;
+};
+
+static LIST_HEAD(thermal_hwmon_list);
+
+static DEFINE_MUTEX(thermal_hwmon_list_lock);
+
+static ssize_t
+temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int temperature;
+ int ret;
+ struct thermal_hwmon_attr *hwmon_attr
+ = container_of(attr, struct thermal_hwmon_attr, attr);
+ struct thermal_hwmon_temp *temp
+ = container_of(hwmon_attr, struct thermal_hwmon_temp,
+ temp_input);
+ struct thermal_zone_device *tz = temp->tz;
+
+ ret = thermal_zone_get_temp(tz, &temperature);
+
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", temperature);
+}
+
+static ssize_t
+temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_hwmon_attr *hwmon_attr
+ = container_of(attr, struct thermal_hwmon_attr, attr);
+ struct thermal_hwmon_temp *temp
+ = container_of(hwmon_attr, struct thermal_hwmon_temp,
+ temp_crit);
+ struct thermal_zone_device *tz = temp->tz;
+ int temperature;
+ int ret;
+
+ guard(thermal_zone)(tz);
+
+ ret = tz->ops.get_crit_temp(tz, &temperature);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", temperature);
+}
+
+
+static struct thermal_hwmon_device *
+thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_device *hwmon;
+ char type[THERMAL_NAME_LENGTH];
+
+ list_for_each_entry(hwmon, &thermal_hwmon_list, node) {
+ strscpy(type, tz->type);
+ strreplace(type, '-', '_');
+ if (!strcmp(hwmon->type, type))
+ return hwmon;
+ }
+
+ return NULL;
+}
+
+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);
+}
+
+int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_device *hwmon;
+ struct thermal_hwmon_temp *temp;
+ int new_hwmon_device = 1;
+ int result = 0;
+
+ mutex_lock(&thermal_hwmon_list_lock);
+
+ hwmon = thermal_hwmon_lookup_by_type(tz);
+ if (hwmon) {
+ new_hwmon_device = 0;
+ goto register_sys_interface;
+ }
+
+ hwmon = kzalloc_obj(*hwmon);
+ if (!hwmon) {
+ result = -ENOMEM;
+ goto unlock;
+ }
+
+ INIT_LIST_HEAD(&hwmon->tz_list);
+ strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
+ strreplace(hwmon->type, '-', '_');
+ hwmon->device = hwmon_device_register_for_thermal(&tz->device,
+ hwmon->type, hwmon);
+ if (IS_ERR(hwmon->device)) {
+ result = PTR_ERR(hwmon->device);
+ goto free_mem;
+ }
+
+ register_sys_interface:
+ temp = kzalloc_obj(*temp);
+ if (!temp) {
+ result = -ENOMEM;
+ goto unregister_name;
+ }
+
+ temp->tz = tz;
+ hwmon->count++;
+
+ snprintf(temp->temp_input.name, sizeof(temp->temp_input.name),
+ "temp%d_input", hwmon->count);
+ temp->temp_input.attr.attr.name = temp->temp_input.name;
+ temp->temp_input.attr.attr.mode = 0444;
+ temp->temp_input.attr.show = temp_input_show;
+ sysfs_attr_init(&temp->temp_input.attr.attr);
+ result = device_create_file(hwmon->device, &temp->temp_input.attr);
+ if (result)
+ goto free_temp_mem;
+
+ if (thermal_zone_crit_temp_valid(tz)) {
+ snprintf(temp->temp_crit.name,
+ sizeof(temp->temp_crit.name),
+ "temp%d_crit", hwmon->count);
+ temp->temp_crit.attr.attr.name = temp->temp_crit.name;
+ temp->temp_crit.attr.attr.mode = 0444;
+ temp->temp_crit.attr.show = temp_crit_show;
+ sysfs_attr_init(&temp->temp_crit.attr.attr);
+ result = device_create_file(hwmon->device,
+ &temp->temp_crit.attr);
+ if (result)
+ goto unregister_input;
+
+ temp->temp_crit_present = true;
+ }
+
+ if (new_hwmon_device)
+ list_add_tail(&hwmon->node, &thermal_hwmon_list);
+ list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
+
+ goto unlock;
+
+unregister_input:
+ device_remove_file(hwmon->device, &temp->temp_input.attr);
+free_temp_mem:
+ kfree(temp);
+unregister_name:
+ if (new_hwmon_device)
+ hwmon_device_unregister(hwmon->device);
+free_mem:
+ if (new_hwmon_device)
+ kfree(hwmon);
+unlock:
+ mutex_unlock(&thermal_hwmon_list_lock);
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs);
+
+void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_temp *temp, *entry;
+ struct thermal_hwmon_device *hwmon;
+ bool unregister;
+
+ guard(mutex)(&thermal_hwmon_list_lock);
+
+ hwmon = thermal_hwmon_lookup_by_type(tz);
+ if (unlikely(!hwmon)) {
+ /* Should never happen... */
+ dev_dbg(&tz->device, "hwmon device lookup failed!\n");
+ return;
+ }
+
+ unregister = hwmon->device->parent == &tz->device;
+
+ list_for_each_entry_safe_reverse(temp, entry, &hwmon->tz_list, hwmon_node) {
+ if (!unregister && temp->tz != tz)
+ continue;
+
+ device_remove_file(hwmon->device, &temp->temp_input.attr);
+ if (temp->temp_crit_present)
+ device_remove_file(hwmon->device, &temp->temp_crit.attr);
+
+ list_del(&temp->hwmon_node);
+ kfree(temp);
+ }
+
+ if (unregister) {
+ list_del(&hwmon->node);
+ hwmon_device_unregister(hwmon->device);
+ kfree(hwmon);
+ }
+}
+EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);
+
+static void devm_thermal_hwmon_release(struct device *dev, void *res)
+{
+ thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res);
+}
+
+int devm_thermal_add_hwmon_sysfs(struct device *dev, struct thermal_zone_device *tz)
+{
+ struct thermal_zone_device **ptr;
+ int ret;
+
+ ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr),
+ GFP_KERNEL);
+ if (!ptr) {
+ dev_warn(dev, "Failed to allocate device resource data\n");
+ return -ENOMEM;
+ }
+
+ ret = thermal_add_hwmon_sysfs(tz);
+ if (ret) {
+ dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
+ devres_free(ptr);
+ return ret;
+ }
+
+ *ptr = tz;
+ devres_add(dev, ptr);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs);
+
+MODULE_IMPORT_NS("HWMON_THERMAL");
diff --git a/drivers/thermal/thermal_hwmon.h b/drivers/thermal/thermal_hwmon.h
new file mode 100644
index 000000000..b429f6e7a
--- /dev/null
+++ b/drivers/thermal/thermal_hwmon.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * thermal_hwmon.h - Generic Thermal Management hwmon support.
+ *
+ * Code based on Intel thermal_core.c. Copyrights of the original code:
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ *
+ * Copyright (C) 2013 Texas Instruments
+ * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+#ifndef __THERMAL_HWMON_H__
+#define __THERMAL_HWMON_H__
+
+#include <linux/thermal.h>
+
+#ifdef CONFIG_THERMAL_HWMON
+int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz);
+int devm_thermal_add_hwmon_sysfs(struct device *dev, struct thermal_zone_device *tz);
+void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz);
+#else
+static inline int
+thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
+static inline int
+devm_thermal_add_hwmon_sysfs(struct device *dev, struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
+static inline void
+thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+}
+#endif
+
+#endif /* __THERMAL_HWMON_H__ */
diff --git a/drivers/thermal/thermal_mmio.c b/drivers/thermal/thermal_mmio.c
new file mode 100644
index 000000000..6845756ad
--- /dev/null
+++ b/drivers/thermal/thermal_mmio.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+struct thermal_mmio {
+ void __iomem *mmio_base;
+ u32 (*read_mmio)(void __iomem *mmio_base);
+ u32 mask;
+ int factor;
+};
+
+static u32 thermal_mmio_readb(void __iomem *mmio_base)
+{
+ return readb(mmio_base);
+}
+
+static int thermal_mmio_get_temperature(struct thermal_zone_device *tz, int *temp)
+{
+ int t;
+ struct thermal_mmio *sensor = thermal_zone_device_priv(tz);
+
+ t = sensor->read_mmio(sensor->mmio_base) & sensor->mask;
+ t *= sensor->factor;
+
+ *temp = t;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops thermal_mmio_ops = {
+ .get_temp = thermal_mmio_get_temperature,
+};
+
+static int thermal_mmio_probe(struct platform_device *pdev)
+{
+ struct thermal_mmio *sensor;
+ int (*sensor_init_func)(struct platform_device *pdev,
+ struct thermal_mmio *sensor);
+ struct thermal_zone_device *thermal_zone;
+ int ret;
+ int temperature;
+
+ sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL);
+ if (!sensor)
+ return -ENOMEM;
+
+ sensor->mmio_base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
+ if (IS_ERR(sensor->mmio_base))
+ return PTR_ERR(sensor->mmio_base);
+
+ sensor_init_func = device_get_match_data(&pdev->dev);
+ if (sensor_init_func) {
+ ret = sensor_init_func(pdev, sensor);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to initialize sensor (%d)\n",
+ ret);
+ return ret;
+ }
+ }
+
+ thermal_zone = devm_thermal_of_zone_register(&pdev->dev,
+ 0,
+ sensor,
+ &thermal_mmio_ops);
+ if (IS_ERR(thermal_zone)) {
+ dev_err(&pdev->dev,
+ "failed to register sensor (%ld)\n",
+ PTR_ERR(thermal_zone));
+ return PTR_ERR(thermal_zone);
+ }
+
+ thermal_mmio_get_temperature(thermal_zone, &temperature);
+ dev_info(&pdev->dev,
+ "thermal mmio sensor %s registered, current temperature: %d\n",
+ pdev->name, temperature);
+
+ return 0;
+}
+
+static int al_thermal_init(struct platform_device *pdev,
+ struct thermal_mmio *sensor)
+{
+ sensor->read_mmio = thermal_mmio_readb;
+ sensor->mask = 0xff;
+ sensor->factor = 1000;
+
+ return 0;
+}
+
+static const struct of_device_id thermal_mmio_id_table[] = {
+ { .compatible = "amazon,al-thermal", .data = al_thermal_init},
+ {}
+};
+MODULE_DEVICE_TABLE(of, thermal_mmio_id_table);
+
+static struct platform_driver thermal_mmio_driver = {
+ .probe = thermal_mmio_probe,
+ .driver = {
+ .name = "thermal-mmio",
+ .of_match_table = thermal_mmio_id_table,
+ },
+};
+
+module_platform_driver(thermal_mmio_driver);
+
+MODULE_AUTHOR("Talel Shenhar <talel@amazon.com>");
+MODULE_DESCRIPTION("Thermal MMIO Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/thermal_netlink.c b/drivers/thermal/thermal_netlink.c
new file mode 100644
index 000000000..315a76b01
--- /dev/null
+++ b/drivers/thermal/thermal_netlink.c
@@ -0,0 +1,937 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 Linaro Limited
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ * Generic netlink for thermal management framework
+ */
+#include <linux/module.h>
+#include <linux/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[] = {
+ [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] = {
+ /* Thermal zone */
+ [THERMAL_GENL_ATTR_TZ] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_TZ_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TEMP] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_TZ_TRIP_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP_TEMP] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP_TYPE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP_HYST] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_MODE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_NAME] = { .type = NLA_STRING,
+ .len = THERMAL_NAME_LENGTH },
+ /* Governor(s) */
+ [THERMAL_GENL_ATTR_TZ_GOV] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_TZ_GOV_NAME] = { .type = NLA_STRING,
+ .len = THERMAL_NAME_LENGTH },
+ /* Cooling devices */
+ [THERMAL_GENL_ATTR_CDEV] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_CDEV_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CDEV_CUR_STATE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CDEV_MAX_STATE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CDEV_NAME] = { .type = NLA_STRING,
+ .len = THERMAL_NAME_LENGTH },
+ /* CPU capabilities */
+ [THERMAL_GENL_ATTR_CPU_CAPABILITY] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_CPU_CAPABILITY_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY] = { .type = NLA_U32 },
+
+ /* 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 {
+ struct nlattr **attrs;
+ struct sk_buff *msg;
+ const char *name;
+ int tz_id;
+ int cdev_id;
+ int trip_id;
+ int trip_temp;
+ int trip_type;
+ int trip_hyst;
+ int temp;
+ int prev_temp;
+ int direction;
+ int cdev_state;
+ int cdev_max_state;
+ struct thermal_genl_cpu_caps *cpu_capabilities;
+ int cpu_capabilities_count;
+};
+
+typedef int (*cb_t)(struct param *);
+
+static struct genl_family thermal_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 *******************************/
+
+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_genl_family, 0,
+ THERMAL_GENL_SAMPLING_TEMP);
+ if (!hdr)
+ goto out_free;
+
+ if (nla_put_u32(skb, THERMAL_GENL_ATTR_TZ_ID, id))
+ goto out_cancel;
+
+ if (nla_put_u32(skb, THERMAL_GENL_ATTR_TZ_TEMP, temp))
+ goto out_cancel;
+
+ genlmsg_end(skb, hdr);
+
+ genlmsg_multicast(&thermal_genl_family, skb, 0, THERMAL_GENL_SAMPLING_GROUP, GFP_KERNEL);
+
+ return 0;
+out_cancel:
+ genlmsg_cancel(skb, hdr);
+out_free:
+ nlmsg_free(skb);
+
+ return -EMSGSIZE;
+}
+
+/**************************** Event encoding *********************************/
+
+static int thermal_genl_event_tz_create(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
+ nla_put_string(p->msg, THERMAL_GENL_ATTR_TZ_NAME, p->name))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_tz(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_tz_trip_up(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_ID, p->trip_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TEMP, p->temp))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_tz_trip_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) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_TYPE, p->trip_type) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_TEMP, p->trip_temp) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_HYST, p->trip_hyst))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_cdev_add(struct param *p)
+{
+ if (nla_put_string(p->msg, THERMAL_GENL_ATTR_CDEV_NAME,
+ p->name) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID,
+ p->cdev_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_MAX_STATE,
+ p->cdev_max_state))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_cdev_delete(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID, p->cdev_id))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_cdev_state_update(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID,
+ p->cdev_id) ||
+ nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_CUR_STATE,
+ p->cdev_state))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_gov_change(struct param *p)
+{
+ if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
+ nla_put_string(p->msg, THERMAL_GENL_ATTR_GOV_NAME, p->name))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_event_cpu_capability_change(struct param *p)
+{
+ struct thermal_genl_cpu_caps *cpu_cap = p->cpu_capabilities;
+ struct sk_buff *msg = p->msg;
+ struct nlattr *start_cap;
+ int i;
+
+ start_cap = nla_nest_start(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY);
+ if (!start_cap)
+ return -EMSGSIZE;
+
+ for (i = 0; i < p->cpu_capabilities_count; ++i) {
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_ID,
+ cpu_cap->cpu))
+ goto out_cancel_nest;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE,
+ cpu_cap->performance))
+ goto out_cancel_nest;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY,
+ cpu_cap->efficiency))
+ goto out_cancel_nest;
+
+ ++cpu_cap;
+ }
+
+ nla_nest_end(msg, start_cap);
+
+ return 0;
+out_cancel_nest:
+ nla_nest_cancel(msg, start_cap);
+
+ return -EMSGSIZE;
+}
+
+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")));
+
+int thermal_genl_event_tz_enable(struct param *p)
+ __attribute__((alias("thermal_genl_event_tz")));
+
+int thermal_genl_event_tz_disable(struct param *p)
+ __attribute__((alias("thermal_genl_event_tz")));
+
+int thermal_genl_event_tz_trip_down(struct param *p)
+ __attribute__((alias("thermal_genl_event_tz_trip_up")));
+
+int thermal_genl_event_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,
+ [THERMAL_GENL_EVENT_TZ_DELETE] = thermal_genl_event_tz_delete,
+ [THERMAL_GENL_EVENT_TZ_ENABLE] = thermal_genl_event_tz_enable,
+ [THERMAL_GENL_EVENT_TZ_DISABLE] = thermal_genl_event_tz_disable,
+ [THERMAL_GENL_EVENT_TZ_TRIP_UP] = thermal_genl_event_tz_trip_up,
+ [THERMAL_GENL_EVENT_TZ_TRIP_DOWN] = thermal_genl_event_tz_trip_down,
+ [THERMAL_GENL_EVENT_TZ_TRIP_CHANGE] = thermal_genl_event_tz_trip_change,
+ [THERMAL_GENL_EVENT_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,
+};
+
+/*
+ * Generic netlink event encoding
+ */
+static int thermal_genl_send_event(enum thermal_genl_event event,
+ struct param *p)
+{
+ struct sk_buff *msg;
+ int ret = -EMSGSIZE;
+ void *hdr;
+
+ 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_genl_family, 0, event);
+ if (!hdr)
+ goto out_free_msg;
+
+ ret = event_cb[event](p);
+ if (ret)
+ goto out_cancel_msg;
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_multicast(&thermal_genl_family, msg, 0, THERMAL_GENL_EVENT_GROUP, GFP_KERNEL);
+
+ return 0;
+
+out_cancel_msg:
+ genlmsg_cancel(msg, hdr);
+out_free_msg:
+ nlmsg_free(msg);
+
+ return ret;
+}
+
+int thermal_notify_tz_create(const struct thermal_zone_device *tz)
+{
+ 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(const struct thermal_zone_device *tz)
+{
+ struct param p = { .tz_id = tz->id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DELETE, &p);
+}
+
+int thermal_notify_tz_enable(const struct thermal_zone_device *tz)
+{
+ struct param p = { .tz_id = tz->id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_ENABLE, &p);
+}
+
+int thermal_notify_tz_disable(const struct thermal_zone_device *tz)
+{
+ struct param p = { .tz_id = tz->id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DISABLE, &p);
+}
+
+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 = 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(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
+{
+ 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_change(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
+{
+ 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(const struct thermal_cooling_device *cdev,
+ int 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(const struct thermal_cooling_device *cdev)
+{
+ 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(const struct thermal_cooling_device *cdev)
+{
+ struct param p = { .cdev_id = cdev->id };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_DELETE, &p);
+}
+
+int thermal_notify_tz_gov_change(const struct thermal_zone_device *tz,
+ const char *name)
+{
+ struct param p = { .tz_id = tz->id, .name = name };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_GOV_CHANGE, &p);
+}
+
+int thermal_genl_cpu_capability_event(int count,
+ struct thermal_genl_cpu_caps *caps)
+{
+ struct param p = { .cpu_capabilities_count = count, .cpu_capabilities = caps };
+
+ return thermal_genl_send_event(THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE, &p);
+}
+EXPORT_SYMBOL_GPL(thermal_genl_cpu_capability_event);
+
+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,
+ void *data)
+{
+ struct sk_buff *msg = data;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, tz->id) ||
+ nla_put_string(msg, THERMAL_GENL_ATTR_TZ_NAME, tz->type))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_cmd_tz_get_id(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ struct nlattr *start_tz;
+ int ret;
+
+ start_tz = nla_nest_start(msg, THERMAL_GENL_ATTR_TZ);
+ if (!start_tz)
+ return -EMSGSIZE;
+
+ ret = for_each_thermal_zone(__thermal_genl_cmd_tz_get_id, msg);
+ if (ret)
+ goto out_cancel_nest;
+
+ nla_nest_end(msg, start_tz);
+
+ return 0;
+
+out_cancel_nest:
+ nla_nest_cancel(msg, start_tz);
+
+ return ret;
+}
+
+static int thermal_genl_cmd_tz_get_trip(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ const struct thermal_trip_desc *td;
+ struct nlattr *start_trip;
+ int id;
+
+ 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_TZ_TRIP);
+ if (!start_trip)
+ return -EMSGSIZE;
+
+ guard(thermal_zone)(tz);
+
+ for_each_trip_desc(tz, td) {
+ const struct thermal_trip *trip = &td->trip;
+
+ 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;
+ }
+
+ nla_nest_end(msg, start_trip);
+
+ return 0;
+}
+
+static int thermal_genl_cmd_tz_get_temp(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ int temp, ret, id;
+
+ if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])
+ return -EINVAL;
+
+ id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+
+ CLASS(thermal_zone_get_by_id, tz)(id);
+ if (!tz)
+ return -EINVAL;
+
+ ret = thermal_zone_get_temp(tz, &temp);
+ if (ret)
+ return ret;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id) ||
+ nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TEMP, temp))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_cmd_tz_get_gov(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ int id;
+
+ 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);
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id) ||
+ nla_put_string(msg, THERMAL_GENL_ATTR_TZ_GOV_NAME,
+ tz->governor->name))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int __thermal_genl_cmd_cdev_get(struct thermal_cooling_device *cdev,
+ void *data)
+{
+ struct sk_buff *msg = data;
+
+ if (nla_put_u32(msg, THERMAL_GENL_ATTR_CDEV_ID, cdev->id))
+ return -EMSGSIZE;
+
+ if (nla_put_string(msg, THERMAL_GENL_ATTR_CDEV_NAME, cdev->type))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int thermal_genl_cmd_cdev_get(struct param *p)
+{
+ struct sk_buff *msg = p->msg;
+ struct nlattr *start_cdev;
+ int ret;
+
+ start_cdev = nla_nest_start(msg, THERMAL_GENL_ATTR_CDEV);
+ if (!start_cdev)
+ return -EMSGSIZE;
+
+ ret = for_each_thermal_cooling_device(__thermal_genl_cmd_cdev_get, msg);
+ if (ret)
+ goto out_cancel_nest;
+
+ nla_nest_end(msg, start_cdev);
+
+ return 0;
+out_cancel_nest:
+ nla_nest_cancel(msg, start_cdev);
+
+ return ret;
+}
+
+static 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_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,
+ struct netlink_callback *cb)
+{
+ struct param p = { .msg = skb };
+ const struct genl_dumpit_info *info = genl_dumpit_info(cb);
+ int cmd = info->op.cmd;
+ int ret;
+ void *hdr;
+
+ hdr = genlmsg_put(skb, 0, 0, &thermal_genl_family, 0, cmd);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ ret = cmd_cb[cmd](&p);
+ if (ret)
+ goto out_cancel_msg;
+
+ genlmsg_end(skb, hdr);
+
+ return 0;
+
+out_cancel_msg:
+ genlmsg_cancel(skb, hdr);
+
+ return ret;
+}
+
+static int thermal_genl_cmd_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct param p = { .attrs = info->attrs };
+ struct sk_buff *msg;
+ void *hdr;
+ int cmd = info->genlhdr->cmd;
+ int ret = -EMSGSIZE;
+
+ msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+ p.msg = msg;
+
+ hdr = genlmsg_put_reply(msg, info, &thermal_genl_family, 0, cmd);
+ if (!hdr)
+ goto out_free_msg;
+
+ ret = cmd_cb[cmd](&p);
+ if (ret)
+ goto out_cancel_msg;
+
+ genlmsg_end(msg, hdr);
+
+ return genlmsg_reply(msg, info);
+
+out_cancel_msg:
+ genlmsg_cancel(msg, hdr);
+out_free_msg:
+ nlmsg_free(msg);
+
+ return ret;
+}
+
+static 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,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .dumpit = thermal_genl_cmd_dumpit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_TZ_GET_TRIP,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_TZ_GET_TEMP,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_TZ_GET_GOV,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = thermal_genl_cmd_doit,
+ },
+ {
+ .cmd = THERMAL_GENL_CMD_CDEV_GET,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .dumpit = thermal_genl_cmd_dumpit,
+ },
+ {
+ .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_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_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_genl_family);
+}
+
+void __init thermal_netlink_exit(void)
+{
+ genl_unregister_family(&thermal_genl_family);
+}
diff --git a/drivers/thermal/thermal_netlink.h b/drivers/thermal/thermal_netlink.h
new file mode 100644
index 000000000..075e9ae85
--- /dev/null
+++ b/drivers/thermal/thermal_netlink.h
@@ -0,0 +1,178 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Linaro Ltd 2020
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ */
+
+struct thermal_genl_cpu_caps {
+ int cpu;
+ int performance;
+ int efficiency;
+};
+
+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_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(const struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
+static inline int thermal_genl_register_notifier(struct notifier_block *nb)
+{
+ return 0;
+}
+
+static inline int thermal_genl_unregister_notifier(struct notifier_block *nb)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_delete(const struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_enable(const struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_disable(const struct thermal_zone_device *tz)
+{
+ return 0;
+}
+
+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_up(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
+{
+ return 0;
+}
+
+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(const struct thermal_cooling_device *cdev,
+ int state)
+{
+ return 0;
+}
+
+static inline int thermal_notify_cdev_add(const struct thermal_cooling_device *cdev)
+{
+ return 0;
+}
+
+static inline int thermal_notify_cdev_delete(const struct thermal_cooling_device *cdev)
+{
+ return 0;
+}
+
+static inline int thermal_notify_tz_gov_change(const struct thermal_zone_device *tz,
+ const char *name)
+{
+ return 0;
+}
+
+static inline int thermal_genl_sampling_temp(int id, int temp)
+{
+ return 0;
+}
+
+static inline int thermal_genl_cpu_capability_event(int count, struct thermal_genl_cpu_caps *caps)
+{
+ return 0;
+}
+
+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
new file mode 100644
index 000000000..0217a49b0
--- /dev/null
+++ b/drivers/thermal/thermal_of.c
@@ -0,0 +1,652 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * of-thermal.c - Generic Thermal Management device tree support.
+ *
+ * Copyright (C) 2013 Texas Instruments
+ * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/string.h>
+
+#include "thermal_core.h"
+
+/*** functions parsing device tree nodes ***/
+
+/*
+ * It maps 'enum thermal_trip_type' found in include/linux/thermal.h
+ * into the device tree binding of 'trip', property type.
+ */
+static const char * const trip_types[] = {
+ [THERMAL_TRIP_ACTIVE] = "active",
+ [THERMAL_TRIP_PASSIVE] = "passive",
+ [THERMAL_TRIP_HOT] = "hot",
+ [THERMAL_TRIP_CRITICAL] = "critical",
+};
+
+/**
+ * thermal_of_get_trip_type - Get phy mode for given device_node
+ * @np: Pointer to the given device_node
+ * @type: Pointer to resulting trip type
+ *
+ * The function gets trip type string from property 'type',
+ * and store its index in trip_types table in @type,
+ *
+ * Return: 0 on success, or errno in error case.
+ */
+static int thermal_of_get_trip_type(struct device_node *np,
+ enum thermal_trip_type *type)
+{
+ const char *t;
+ int err, i;
+
+ err = of_property_read_string(np, "type", &t);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(trip_types); i++)
+ if (!strcasecmp(t, trip_types[i])) {
+ *type = i;
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+static int thermal_of_populate_trip(struct device_node *np,
+ struct thermal_trip *trip)
+{
+ int prop;
+ int ret;
+
+ ret = of_property_read_u32(np, "temperature", &prop);
+ if (ret < 0) {
+ pr_err("missing temperature property\n");
+ return ret;
+ }
+ trip->temperature = prop;
+
+ ret = of_property_read_u32(np, "hysteresis", &prop);
+ if (ret < 0) {
+ pr_err("missing hysteresis property\n");
+ return ret;
+ }
+ trip->hysteresis = prop;
+
+ ret = thermal_of_get_trip_type(np, &trip->type);
+ if (ret < 0) {
+ pr_err("wrong trip type property\n");
+ return ret;
+ }
+
+ 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)
+{
+ int ret, count;
+
+ *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)
+ return NULL;
+
+ struct thermal_trip *tt __free(kfree) = kzalloc_objs(*tt, count);
+ if (!tt)
+ return ERR_PTR(-ENOMEM);
+
+ count = 0;
+ for_each_child_of_node_scoped(trips, trip) {
+ ret = thermal_of_populate_trip(trip, &tt[count++]);
+ if (ret)
+ return ERR_PTR(ret);
+ }
+
+ *ntrips = count;
+
+ return no_free_ptr(tt);
+}
+
+static struct device_node *of_thermal_zone_find(struct device_node *sensor, int id)
+{
+ struct of_phandle_args sensor_specs;
+
+ 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);
+ }
+
+ /*
+ * Search for each thermal zone, a defined sensor
+ * corresponding to the one passed as parameter
+ */
+ for_each_available_child_of_node_scoped(np, child) {
+
+ int count, i;
+
+ count = of_count_phandle_with_args(child, "thermal-sensors",
+ "#thermal-sensor-cells");
+ if (count <= 0) {
+ pr_err("%pOFP: missing thermal sensor\n", child);
+ return ERR_PTR(-EINVAL);
+ }
+
+ for (i = 0; i < count; i++) {
+
+ int ret;
+
+ ret = of_parse_phandle_with_args(child, "thermal-sensors",
+ "#thermal-sensor-cells",
+ i, &sensor_specs);
+ if (ret < 0) {
+ pr_err("%pOFP: 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 %pOFP id=%d belongs to %pOFP\n", sensor, id, child);
+ return no_free_ptr(child);
+ }
+ }
+ }
+
+ return ERR_PTR(-ENODEV);
+}
+
+static int thermal_of_monitor_init(struct device_node *np, int *delay, int *pdelay)
+{
+ int ret;
+
+ ret = of_property_read_u32(np, "polling-delay-passive", pdelay);
+ if (ret == -EINVAL) {
+ *pdelay = 0;
+ } else if (ret < 0) {
+ pr_err("%pOFP: Couldn't get polling-delay-passive: %d\n", np, ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "polling-delay", delay);
+ if (ret == -EINVAL) {
+ *delay = 0;
+ } else if (ret < 0) {
+ pr_err("%pOFP: Couldn't get polling-delay: %d\n", np, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void thermal_of_parameters_init(struct device_node *np,
+ struct thermal_zone_params *tzp)
+{
+ int coef[2];
+ int ncoef = ARRAY_SIZE(coef);
+ int prop, ret;
+
+ tzp->no_hwmon = true;
+
+ if (!of_property_read_u32(np, "sustainable-power", &prop))
+ tzp->sustainable_power = prop;
+
+ /*
+ * For now, the thermal framework supports only one sensor per
+ * thermal zone. Thus, we are considering only the first two
+ * values as slope and offset.
+ */
+ ret = of_property_read_u32_array(np, "coefficients", coef, ncoef);
+ if (ret) {
+ coef[0] = 1;
+ coef[1] = 0;
+ }
+
+ tzp->slope = coef[0];
+ tzp->offset = coef[1];
+}
+
+static struct device_node *thermal_of_zone_get_by_name(struct thermal_zone_device *tz)
+{
+ struct device_node *np, *tz_np;
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np)
+ return ERR_PTR(-ENODEV);
+
+ tz_np = of_get_child_by_name(np, tz->type);
+
+ of_node_put(np);
+
+ if (!tz_np)
+ return ERR_PTR(-ENODEV);
+
+ return tz_np;
+}
+
+static 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;
+
+ of_property_read_u32(map_np, "contribution", &weight);
+
+ ret = of_parse_phandle_with_args(map_np, "cooling-device", "#cooling-cells",
+ index, &cooling_spec);
+
+ if (ret < 0) {
+ pr_err("Invalid cooling-device entry\n");
+ return false;
+ }
+
+ of_node_put(cooling_spec.np);
+
+ /*
+ * There are two formats:
+ * - Legacy format : <&cdev lower upper>
+ * - New format : <&cdev cdev_id lower upper>
+ *
+ * With the new format, along with the device node pointer,
+ * the cdev_id must match with the cooling device cdev_id in
+ * order to bind
+ */
+ if (cooling_spec.args_count < 2 || cooling_spec.args_count > 3) {
+ pr_err("Invalid number of cooling device parameters\n");
+ return false;
+ }
+
+ if (cooling_spec.np != cdev->np)
+ return false;
+
+ if (cooling_spec.args_count == 3 &&
+ cooling_spec.args[0] != cdev->cdev_id)
+ return false;
+
+ if (cooling_spec.args_count != 3) {
+ c->lower = cooling_spec.args[0];
+ c->upper = cooling_spec.args[1];
+ } else {
+ c->lower = cooling_spec.args[1];
+ c->upper = cooling_spec.args[2];
+ }
+ c->weight = weight;
+
+ return true;
+}
+
+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)
+{
+ for_each_child_of_node_scoped(cm_np, child) {
+ int count, i;
+
+ struct device_node *tr_np __free(device_node) =
+ of_parse_phandle(child, "trip", 0);
+ if (tr_np != trip->priv)
+ continue;
+
+ /* 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");
+
+ for (i = 0; i < count; i++) {
+ if (thermal_of_get_cooling_spec(child, i, cdev, c))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+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;
+ 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 false;
+ }
+
+ cm_np = of_get_child_by_name(tz_np, "cooling-maps");
+ if (!cm_np)
+ goto out;
+
+ /* 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 result;
+}
+
+/**
+ * thermal_of_zone_unregister - Cleanup the specific allocated ressources
+ *
+ * This function disables the thermal zone and frees the different
+ * ressources allocated specific to the thermal OF.
+ *
+ * @tz: a pointer to the thermal zone structure
+ */
+static void thermal_of_zone_unregister(struct thermal_zone_device *tz)
+{
+ thermal_zone_device_disable(tz);
+ thermal_zone_device_unregister(tz);
+}
+
+/**
+ * thermal_of_zone_register - Register a thermal zone with device node
+ * sensor
+ *
+ * The thermal_of_zone_register() parses a device tree given a device
+ * node sensor and identifier. It searches for the thermal zone
+ * associated to the couple sensor/id and retrieves all the thermal
+ * zone properties and registers new thermal zone with those
+ * properties.
+ *
+ * @sensor: A device node pointer corresponding to the sensor in the device tree
+ * @id: An integer as sensor identifier
+ * @data: A private data to be stored in the thermal zone dedicated private area
+ * @ops: A set of thermal sensor ops
+ *
+ * Return: a valid thermal zone structure pointer on success.
+ * - EINVAL: if the device tree thermal description is malformed
+ * - ENOMEM: if one structure can not be allocated
+ * - Other negative errors are returned by the underlying called functions
+ */
+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 device_node *np;
+ const char *action;
+ int delay, pdelay;
+ int ntrips;
+ int ret;
+
+ np = of_thermal_zone_find(sensor, id);
+ if (IS_ERR(np)) {
+ if (PTR_ERR(np) != -ENODEV)
+ pr_err("Failed to find thermal zone for %pOFP id=%d\n", sensor, id);
+ return ERR_CAST(np);
+ }
+
+ trips = thermal_of_trips_init(np, &ntrips);
+ if (IS_ERR(trips)) {
+ pr_err("Failed to parse trip points for %pOFP id=%d\n", sensor, id);
+ ret = PTR_ERR(trips);
+ goto out_of_node_put;
+ }
+
+ if (!trips)
+ pr_info("No trip points found for %pOFP id=%d\n", sensor, id);
+
+ ret = thermal_of_monitor_init(np, &delay, &pdelay);
+ if (ret) {
+ pr_err("Failed to initialize monitoring delays from %pOFP\n", np);
+ goto out_kfree_trips;
+ }
+
+ thermal_of_parameters_init(np, &tzp);
+
+ of_ops.should_bind = thermal_of_should_bind;
+
+ 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,
+ data, &of_ops, &tzp,
+ pdelay, delay);
+ if (IS_ERR(tz)) {
+ ret = PTR_ERR(tz);
+ pr_err("Failed to register thermal zone %pOFP: %d\n", np, ret);
+ goto out_kfree_trips;
+ }
+
+ of_node_put(np);
+ kfree(trips);
+
+ ret = thermal_zone_device_enable(tz);
+ if (ret) {
+ pr_err("Failed to enable thermal zone '%s', id=%d: %d\n",
+ tz->type, tz->id, ret);
+ thermal_of_zone_unregister(tz);
+ return ERR_PTR(ret);
+ }
+
+ return tz;
+
+out_kfree_trips:
+ kfree(trips);
+out_of_node_put:
+ of_node_put(np);
+
+ return ERR_PTR(ret);
+}
+
+static void devm_thermal_of_zone_release(struct device *dev, void *res)
+{
+ thermal_of_zone_unregister(*(struct thermal_zone_device **)res);
+}
+
+static int devm_thermal_of_zone_match(struct device *dev, void *res,
+ void *data)
+{
+ struct thermal_zone_device **r = res;
+
+ if (WARN_ON(!r || !*r))
+ return 0;
+
+ return *r == data;
+}
+
+/**
+ * devm_thermal_of_zone_register - register a thermal tied with the sensor life cycle
+ *
+ * This function is the device version of the thermal_of_zone_register() function.
+ *
+ * @dev: a device structure pointer to sensor to be tied with the thermal zone OF life cycle
+ * @sensor_id: the sensor identifier
+ * @data: a pointer to a private data to be stored in the thermal zone 'devdata' field
+ * @ops: a pointer to the ops structure associated with the sensor
+ */
+struct thermal_zone_device *devm_thermal_of_zone_register(struct device *dev, int sensor_id, void *data,
+ const struct thermal_zone_device_ops *ops)
+{
+ struct thermal_zone_device **ptr, *tzd;
+
+ ptr = devres_alloc(devm_thermal_of_zone_release, sizeof(*ptr),
+ GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ tzd = thermal_of_zone_register(dev->of_node, sensor_id, data, ops);
+ if (IS_ERR(tzd)) {
+ devres_free(ptr);
+ return tzd;
+ }
+
+ *ptr = tzd;
+ devres_add(dev, ptr);
+
+ return tzd;
+}
+EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register);
+
+/**
+ * devm_thermal_of_zone_unregister - Resource managed version of
+ * thermal_of_zone_unregister().
+ * @dev: Device for which resource was allocated.
+ * @tz: a pointer to struct thermal_zone where the sensor is registered.
+ *
+ * This function removes the sensor callbacks and private data from the
+ * thermal zone device registered with devm_thermal_zone_of_sensor_register()
+ * API. It will also silent the zone by remove the .get_temp() and .get_trend()
+ * thermal zone device callbacks.
+ * Normally this function will not need to be called and the resource
+ * management code will ensure that the resource is freed.
+ */
+void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_device *tz)
+{
+ WARN_ON(devres_release(dev, devm_thermal_of_zone_release,
+ devm_thermal_of_zone_match, tz));
+}
+EXPORT_SYMBOL_GPL(devm_thermal_of_zone_unregister);
+
+/**
+ * thermal_of_cooling_device_register() - register an OF thermal cooling device
+ * @np: a pointer to a device tree node.
+ * @cdev_id: a cooling device id in the cooling controller
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ * It also gives the opportunity to link the cooling device to a device tree
+ * node, so that it can be bound to a thermal zone created out of device tree.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+ int ret;
+
+ cdev = thermal_cooling_device_alloc(type, ops);
+ if (IS_ERR(cdev))
+ return cdev;
+
+ cdev->np = np;
+ cdev->cdev_id = cdev_id;
+
+ ret = thermal_cooling_device_add(cdev, devdata);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
+
+static void thermal_of_cooling_device_release(void *data)
+{
+ struct thermal_cooling_device *cdev = data;
+
+ thermal_cooling_device_unregister(cdev);
+}
+
+static struct thermal_cooling_device *
+__devm_thermal_of_cooling_device_register(struct device *dev, struct device_node *np,
+ u32 cdev_id, const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+ int ret;
+
+ cdev = thermal_of_cooling_device_register(np, cdev_id, type, devdata, ops);
+ if (IS_ERR(cdev))
+ return cdev;
+
+ ret = devm_add_action_or_reset(dev, thermal_of_cooling_device_release, cdev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return cdev;
+}
+
+/**
+ * devm_thermal_of_cooling_device_register() - register an OF thermal cooling device
+ * @dev: a valid struct device pointer of a sensor device.
+ * @cdev_id: a cooling device index in the cooling controller
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This function will register a cooling device with device tree node reference.
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+devm_thermal_of_cooling_device_register(struct device *dev, u32 cdev_id,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return __devm_thermal_of_cooling_device_register(dev, dev->of_node, cdev_id,
+ type, devdata, ops);
+}
+EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register);
+
+/**
+ * devm_thermal_of_child_cooling_device_register() - register an OF thermal cooling
+ * device
+ * @dev: a valid struct device pointer of a sensor device.
+ * @np: a pointer to a device tree node.
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This function will register a cooling device with device tree node reference.
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * This function should be used when a cooling controller has child
+ * nodes which are referenced in the thermal zone cooling map.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+devm_thermal_of_child_cooling_device_register(struct device *dev,
+ struct device_node *np,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return __devm_thermal_of_cooling_device_register(dev, np, 0, type, devdata, ops);
+}
+EXPORT_SYMBOL_GPL(devm_thermal_of_child_cooling_device_register);
diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c
new file mode 100644
index 000000000..adbcb2c01
--- /dev/null
+++ b/drivers/thermal/thermal_sysfs.c
@@ -0,0 +1,879 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * thermal.c - sysfs interface of thermal devices
+ *
+ * Copyright (C) 2016 Eduardo Valentin <edubezval@gmail.com>
+ *
+ * Highly based on original thermal_core.c
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/container_of.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/string_choices.h>
+#include <linux/jiffies.h>
+
+#include "thermal_core.h"
+
+/* sys I/F for thermal zone */
+
+static ssize_t
+type_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ return sysfs_emit(buf, "%s\n", tz->type);
+}
+
+static ssize_t
+temp_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int temperature, ret;
+
+ ret = thermal_zone_get_temp(tz, &temperature);
+
+ if (!ret)
+ return sysfs_emit(buf, "%d\n", temperature);
+
+ if (ret == -EAGAIN)
+ return -ENODATA;
+
+ 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);
+
+ guard(thermal_zone)(tz);
+
+ return sysfs_emit(buf, "%s\n",
+ str_enabled_disabled(tz->mode == THERMAL_DEVICE_ENABLED));
+}
+
+static ssize_t
+mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int result;
+
+ if (!strncmp(buf, "enabled", sizeof("enabled") - 1))
+ result = thermal_zone_device_enable(tz);
+ else if (!strncmp(buf, "disabled", sizeof("disabled") - 1))
+ result = thermal_zone_device_disable(tz);
+ else
+ result = -EINVAL;
+
+ if (result)
+ return result;
+
+ 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_trip *trip = thermal_trip_of_attr(attr, type);
+
+ return sysfs_emit(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);
+ int temp;
+
+ if (kstrtoint(buf, 10, &temp))
+ return -EINVAL;
+
+ guard(thermal_zone)(tz);
+
+ 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;
+ }
+
+ thermal_zone_set_trip_temp(tz, trip, temp);
+
+ __thermal_zone_device_update(tz, THERMAL_TRIP_CHANGED);
+
+ return count;
+}
+
+static ssize_t
+trip_point_temp_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_trip *trip = thermal_trip_of_attr(attr, temp);
+
+ return sysfs_emit(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);
+ int hyst;
+
+ if (kstrtoint(buf, 10, &hyst) || hyst < 0)
+ return -EINVAL;
+
+ guard(thermal_zone)(tz);
+
+ if (hyst == trip->hysteresis)
+ return count;
+
+ /*
+ * 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;
+ }
+
+ 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 count;
+}
+
+static ssize_t
+trip_point_hyst_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_trip *trip = thermal_trip_of_attr(attr, hyst);
+
+ return sysfs_emit(buf, "%d\n", READ_ONCE(trip->hysteresis));
+}
+
+static ssize_t
+policy_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ char name[THERMAL_NAME_LENGTH];
+ int ret;
+
+ strscpy(name, buf);
+
+ ret = thermal_zone_device_set_policy(tz, name);
+ if (!ret)
+ ret = count;
+
+ return ret;
+}
+
+static ssize_t
+policy_show(struct device *dev, struct device_attribute *devattr, char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ return sysfs_emit(buf, "%s\n", tz->governor->name);
+}
+
+static ssize_t
+available_policies_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ return thermal_build_list_of_policies(buf);
+}
+
+#if (IS_ENABLED(CONFIG_THERMAL_EMULATION))
+static ssize_t
+emul_temp_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int temperature;
+
+ if (kstrtoint(buf, 10, &temperature))
+ return -EINVAL;
+
+ guard(thermal_zone)(tz);
+
+ if (tz->ops.set_emul_temp) {
+ int ret;
+
+ ret = tz->ops.set_emul_temp(tz, temperature);
+ if (ret)
+ return ret;
+ } else {
+ tz->emul_temperature = temperature;
+ }
+
+ __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return count;
+}
+static DEVICE_ATTR_WO(emul_temp);
+#endif
+
+static ssize_t
+sustainable_power_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ if (tz->tzp)
+ return sysfs_emit(buf, "%u\n", tz->tzp->sustainable_power);
+ else
+ return -EIO;
+}
+
+static ssize_t
+sustainable_power_store(struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ u32 sustainable_power;
+
+ if (!tz->tzp)
+ return -EIO;
+
+ if (kstrtou32(buf, 10, &sustainable_power))
+ return -EINVAL;
+
+ tz->tzp->sustainable_power = sustainable_power;
+
+ return count;
+}
+
+#define create_s32_tzp_attr(name) \
+ static ssize_t \
+ name##_show(struct device *dev, struct device_attribute *devattr, \
+ char *buf) \
+ { \
+ struct thermal_zone_device *tz = to_thermal_zone(dev); \
+ \
+ if (tz->tzp) \
+ return sysfs_emit(buf, "%d\n", tz->tzp->name); \
+ else \
+ return -EIO; \
+ } \
+ \
+ static ssize_t \
+ name##_store(struct device *dev, struct device_attribute *devattr, \
+ const char *buf, size_t count) \
+ { \
+ struct thermal_zone_device *tz = to_thermal_zone(dev); \
+ s32 value; \
+ \
+ if (!tz->tzp) \
+ return -EIO; \
+ \
+ if (kstrtos32(buf, 10, &value)) \
+ return -EINVAL; \
+ \
+ tz->tzp->name = value; \
+ \
+ return count; \
+ } \
+ static DEVICE_ATTR_RW(name)
+
+create_s32_tzp_attr(k_po);
+create_s32_tzp_attr(k_pu);
+create_s32_tzp_attr(k_i);
+create_s32_tzp_attr(k_d);
+create_s32_tzp_attr(integral_cutoff);
+create_s32_tzp_attr(slope);
+create_s32_tzp_attr(offset);
+#undef create_s32_tzp_attr
+
+/*
+ * These are thermal zone device attributes that will always be present.
+ * All the attributes created for tzp (create_s32_tzp_attr) also are always
+ * present on the sysfs interface.
+ */
+static DEVICE_ATTR_RO(type);
+static DEVICE_ATTR_RO(temp);
+static DEVICE_ATTR_RW(policy);
+static DEVICE_ATTR_RO(available_policies);
+static DEVICE_ATTR_RW(sustainable_power);
+
+/* These thermal zone device attributes are created based on conditions */
+static DEVICE_ATTR_RW(mode);
+
+/* These attributes are unconditionally added to a thermal zone */
+static struct attribute *thermal_zone_dev_attrs[] = {
+ &dev_attr_type.attr,
+ &dev_attr_temp.attr,
+#if (IS_ENABLED(CONFIG_THERMAL_EMULATION))
+ &dev_attr_emul_temp.attr,
+#endif
+ &dev_attr_policy.attr,
+ &dev_attr_available_policies.attr,
+ &dev_attr_sustainable_power.attr,
+ &dev_attr_k_po.attr,
+ &dev_attr_k_pu.attr,
+ &dev_attr_k_i.attr,
+ &dev_attr_k_d.attr,
+ &dev_attr_integral_cutoff.attr,
+ &dev_attr_slope.attr,
+ &dev_attr_offset.attr,
+ NULL,
+};
+
+static const struct attribute_group thermal_zone_attribute_group = {
+ .attrs = thermal_zone_dev_attrs,
+};
+
+static struct attribute *thermal_zone_mode_attrs[] = {
+ &dev_attr_mode.attr,
+ NULL,
+};
+
+static const struct attribute_group thermal_zone_mode_attribute_group = {
+ .attrs = thermal_zone_mode_attrs,
+};
+
+static const struct attribute_group *thermal_zone_attribute_groups[] = {
+ &thermal_zone_attribute_group,
+ &thermal_zone_mode_attribute_group,
+ /* This is not NULL terminated as we create the group dynamically */
+};
+
+/**
+ * create_trip_attrs() - create attributes for trip points
+ * @tz: the thermal zone device
+ *
+ * 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)
+{
+ struct thermal_trip_desc *td;
+ struct attribute **attrs;
+ int i;
+
+ attrs = kzalloc_objs(*attrs, tz->num_trips * 3 + 1);
+ if (!attrs)
+ return -ENOMEM;
+
+ i = 0;
+ for_each_trip_desc(tz, td) {
+ struct thermal_trip_attrs *trip_attrs = &td->trip_attrs;
+
+ /* create trip type attribute */
+ snprintf(trip_attrs->type.name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_type", i);
+
+ 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(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[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[i + 2 * tz->num_trips] = &trip_attrs->hyst.attr.attr;
+ i++;
+ }
+ attrs[tz->num_trips * 3] = NULL;
+
+ tz->trips_attribute_group.attrs = attrs;
+
+ return 0;
+}
+
+/**
+ * destroy_trip_attrs() - destroy attributes for trip points
+ * @tz: the thermal zone device
+ *
+ * helper function to free resources allocated by create_trip_attrs()
+ */
+static void destroy_trip_attrs(struct thermal_zone_device *tz)
+{
+ if (tz)
+ kfree(tz->trips_attribute_group.attrs);
+}
+
+int thermal_zone_create_device_groups(struct thermal_zone_device *tz)
+{
+ const struct attribute_group **groups;
+ int i, size, result;
+
+ /* we need one extra for trips and the NULL to terminate the array */
+ size = ARRAY_SIZE(thermal_zone_attribute_groups) + 2;
+ /* This also takes care of API requirement to be NULL terminated */
+ groups = kzalloc_objs(*groups, size);
+ if (!groups)
+ return -ENOMEM;
+
+ for (i = 0; i < size - 2; i++)
+ groups[i] = thermal_zone_attribute_groups[i];
+
+ if (tz->num_trips) {
+ result = create_trip_attrs(tz);
+ if (result) {
+ kfree(groups);
+
+ return result;
+ }
+
+ groups[size - 2] = &tz->trips_attribute_group;
+ }
+
+ tz->device.groups = groups;
+
+ return 0;
+}
+
+void thermal_zone_destroy_device_groups(struct thermal_zone_device *tz)
+{
+ if (!tz)
+ return;
+
+ if (tz->num_trips)
+ destroy_trip_attrs(tz);
+
+ kfree(tz->device.groups);
+}
+
+/* sys I/F for cooling device */
+static ssize_t
+cdev_type_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+
+ return sysfs_emit(buf, "%s\n", cdev->type);
+}
+
+static ssize_t max_state_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+
+ return sysfs_emit(buf, "%ld\n", cdev->max_state);
+}
+
+static ssize_t cur_state_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ unsigned long state;
+ int ret;
+
+ ret = cdev->ops->get_cur_state(cdev, &state);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%ld\n", state);
+}
+
+static ssize_t
+cur_state_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ unsigned long state;
+ int result;
+
+ result = kstrtoul(buf, 10, &state);
+ if (result < 0)
+ return result;
+
+ /* Requested state should be less than max_state + 1 */
+ if (state > cdev->max_state)
+ return -EINVAL;
+
+ guard(cooling_dev)(cdev);
+
+ result = cdev->ops->set_cur_state(cdev, state);
+ if (result)
+ return result;
+
+ thermal_cooling_device_stats_update(cdev, state);
+
+ return count;
+}
+
+static struct device_attribute
+dev_attr_cdev_type = __ATTR(type, 0444, cdev_type_show, NULL);
+static DEVICE_ATTR_RO(max_state);
+static DEVICE_ATTR_RW(cur_state);
+
+static struct attribute *cooling_device_attrs[] = {
+ &dev_attr_cdev_type.attr,
+ &dev_attr_max_state.attr,
+ &dev_attr_cur_state.attr,
+ NULL,
+};
+
+static const struct attribute_group cooling_device_attr_group = {
+ .attrs = cooling_device_attrs,
+};
+
+static const struct attribute_group *cooling_device_attr_groups[] = {
+ &cooling_device_attr_group,
+ NULL, /* Space allocated for cooling_device_stats_attr_group */
+ NULL,
+};
+
+#ifdef CONFIG_THERMAL_STATISTICS
+struct cooling_dev_stats {
+ spinlock_t lock;
+ unsigned int total_trans;
+ unsigned long state;
+ ktime_t last_time;
+ ktime_t *time_in_state;
+ unsigned int *trans_table;
+};
+
+static void update_time_in_state(struct cooling_dev_stats *stats)
+{
+ ktime_t now = ktime_get(), delta;
+
+ delta = ktime_sub(now, stats->last_time);
+ stats->time_in_state[stats->state] =
+ ktime_add(stats->time_in_state[stats->state], delta);
+ stats->last_time = now;
+}
+
+void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
+ unsigned long new_state)
+{
+ struct cooling_dev_stats *stats = cdev->stats;
+
+ lockdep_assert_held(&cdev->lock);
+
+ if (!stats)
+ return;
+
+ spin_lock(&stats->lock);
+
+ if (stats->state == new_state)
+ goto unlock;
+
+ update_time_in_state(stats);
+ stats->trans_table[stats->state * (cdev->max_state + 1) + new_state]++;
+ stats->state = new_state;
+ stats->total_trans++;
+
+unlock:
+ spin_unlock(&stats->lock);
+}
+
+static ssize_t total_trans_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ struct cooling_dev_stats *stats;
+ int ret;
+
+ guard(cooling_dev)(cdev);
+
+ stats = cdev->stats;
+ if (!stats)
+ return 0;
+
+ spin_lock(&stats->lock);
+ ret = sysfs_emit(buf, "%u\n", stats->total_trans);
+ spin_unlock(&stats->lock);
+
+ return ret;
+}
+
+static ssize_t
+time_in_state_ms_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ struct cooling_dev_stats *stats;
+ ssize_t len = 0;
+ int i;
+
+ guard(cooling_dev)(cdev);
+
+ stats = cdev->stats;
+ if (!stats)
+ return 0;
+
+ spin_lock(&stats->lock);
+
+ update_time_in_state(stats);
+
+ for (i = 0; i <= cdev->max_state; i++) {
+ len += sysfs_emit_at(buf, len, "state%u\t%llu\n", i,
+ ktime_to_ms(stats->time_in_state[i]));
+ }
+ spin_unlock(&stats->lock);
+
+ return len;
+}
+
+static ssize_t
+reset_store(struct device *dev, struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ struct cooling_dev_stats *stats;
+ int i, states;
+
+ guard(cooling_dev)(cdev);
+
+ stats = cdev->stats;
+ if (!stats)
+ return count;
+
+ states = cdev->max_state + 1;
+
+ spin_lock(&stats->lock);
+
+ stats->total_trans = 0;
+ stats->last_time = ktime_get();
+ memset(stats->trans_table, 0,
+ states * states * sizeof(*stats->trans_table));
+
+ for (i = 0; i < states; i++)
+ stats->time_in_state[i] = ktime_set(0, 0);
+
+ spin_unlock(&stats->lock);
+
+ return count;
+}
+
+static ssize_t trans_table_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+ struct cooling_dev_stats *stats;
+ ssize_t len = 0;
+ int i, j, copied;
+
+ guard(cooling_dev)(cdev);
+
+ stats = cdev->stats;
+ if (!stats)
+ return -ENODATA;
+
+ len += sysfs_emit_at(buf, len, " From : To\n");
+ len += sysfs_emit_at(buf, len, " : ");
+ for (i = 0; i <= cdev->max_state; i++) {
+ copied = sysfs_emit_at(buf, len, "state%2u ", i);
+ if (!copied)
+ goto buf_full;
+ len += copied;
+ }
+ len += sysfs_emit_at(buf, len, "\n");
+
+ for (i = 0; i <= cdev->max_state; i++) {
+ copied = sysfs_emit_at(buf, len, "state%2u:", i);
+ if (!copied)
+ goto buf_full;
+ len += copied;
+
+ for (j = 0; j <= cdev->max_state; j++) {
+ copied = sysfs_emit_at(buf, len, "%8u ",
+ stats->trans_table[i * (cdev->max_state + 1) + j]);
+ if (!copied)
+ goto buf_full;
+ len += copied;
+ }
+ copied = sysfs_emit_at(buf, len, "\n");
+ if (!copied)
+ goto buf_full;
+ len += copied;
+ }
+
+ return len;
+
+buf_full:
+ pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n");
+ return -EFBIG;
+}
+
+static DEVICE_ATTR_RO(total_trans);
+static DEVICE_ATTR_RO(time_in_state_ms);
+static DEVICE_ATTR_WO(reset);
+static DEVICE_ATTR_RO(trans_table);
+
+static struct attribute *cooling_device_stats_attrs[] = {
+ &dev_attr_total_trans.attr,
+ &dev_attr_time_in_state_ms.attr,
+ &dev_attr_reset.attr,
+ &dev_attr_trans_table.attr,
+ NULL
+};
+
+static const struct attribute_group cooling_device_stats_attr_group = {
+ .attrs = cooling_device_stats_attrs,
+ .name = "stats"
+};
+
+static void cooling_device_stats_setup(struct thermal_cooling_device *cdev)
+{
+ const struct attribute_group *stats_attr_group = NULL;
+ struct cooling_dev_stats *stats;
+ /* Total number of states is highest state + 1 */
+ unsigned long states = cdev->max_state + 1;
+ int var;
+
+ var = sizeof(*stats);
+ var += sizeof(*stats->time_in_state) * states;
+ var += sizeof(*stats->trans_table) * states * states;
+
+ stats = kzalloc(var, GFP_KERNEL);
+ if (!stats)
+ goto out;
+
+ stats->time_in_state = (ktime_t *)(stats + 1);
+ stats->trans_table = (unsigned int *)(stats->time_in_state + states);
+ cdev->stats = stats;
+ stats->last_time = ktime_get();
+
+ spin_lock_init(&stats->lock);
+
+ stats_attr_group = &cooling_device_stats_attr_group;
+
+out:
+ /* Fill the empty slot left in cooling_device_attr_groups */
+ var = ARRAY_SIZE(cooling_device_attr_groups) - 2;
+ cooling_device_attr_groups[var] = stats_attr_group;
+}
+
+static void cooling_device_stats_destroy(struct thermal_cooling_device *cdev)
+{
+ kfree(cdev->stats);
+ cdev->stats = NULL;
+}
+
+#else
+
+static inline void
+cooling_device_stats_setup(struct thermal_cooling_device *cdev) {}
+static inline void
+cooling_device_stats_destroy(struct thermal_cooling_device *cdev) {}
+
+#endif /* CONFIG_THERMAL_STATISTICS */
+
+void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *cdev)
+{
+ cooling_device_stats_setup(cdev);
+ cdev->device.groups = cooling_device_attr_groups;
+}
+
+void thermal_cooling_device_destroy_sysfs(struct thermal_cooling_device *cdev)
+{
+ cooling_device_stats_destroy(cdev);
+}
+
+void thermal_cooling_device_stats_reinit(struct thermal_cooling_device *cdev)
+{
+ lockdep_assert_held(&cdev->lock);
+
+ cooling_device_stats_destroy(cdev);
+ cooling_device_stats_setup(cdev);
+}
+
+/* these helper will be used only at the time of bindig */
+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);
+
+ return sysfs_emit(buf, "%d\n", thermal_zone_trip_id(tz, instance->trip));
+}
+
+ssize_t
+weight_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_instance *instance;
+
+ instance = container_of(attr, struct thermal_instance, weight_attr);
+
+ return sysfs_emit(buf, "%d\n", instance->weight);
+}
+
+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;
+
+ ret = kstrtoint(buf, 0, &weight);
+ if (ret)
+ 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 000000000..970b8fd33
--- /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_obj(*t);
+ 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 000000000..cb372659a
--- /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
new file mode 100644
index 000000000..df8f4edd6
--- /dev/null
+++ b/drivers/thermal/thermal_trace.h
@@ -0,0 +1,207 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM thermal
+
+#if !defined(_TRACE_THERMAL_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_THERMAL_H
+
+#include <linux/devfreq.h>
+#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);
+TRACE_DEFINE_ENUM(THERMAL_TRIP_ACTIVE);
+
+#define show_tzt_type(type) \
+ __print_symbolic(type, \
+ { THERMAL_TRIP_CRITICAL, "CRITICAL"}, \
+ { THERMAL_TRIP_HOT, "HOT"}, \
+ { THERMAL_TRIP_PASSIVE, "PASSIVE"}, \
+ { THERMAL_TRIP_ACTIVE, "ACTIVE"})
+
+TRACE_EVENT(thermal_temperature,
+
+ TP_PROTO(struct thermal_zone_device *tz),
+
+ TP_ARGS(tz),
+
+ TP_STRUCT__entry(
+ __string(thermal_zone, tz->type)
+ __field(int, id)
+ __field(int, temp_prev)
+ __field(int, temp)
+ ),
+
+ TP_fast_assign(
+ __assign_str(thermal_zone);
+ __entry->id = tz->id;
+ __entry->temp_prev = tz->last_temperature;
+ __entry->temp = tz->temperature;
+ ),
+
+ TP_printk("thermal_zone=%s id=%d temp_prev=%d temp=%d",
+ __get_str(thermal_zone), __entry->id, __entry->temp_prev,
+ __entry->temp)
+);
+
+TRACE_EVENT(cdev_update,
+
+ TP_PROTO(struct thermal_cooling_device *cdev, unsigned long target),
+
+ TP_ARGS(cdev, target),
+
+ TP_STRUCT__entry(
+ __string(type, cdev->type)
+ __field(unsigned long, target)
+ ),
+
+ TP_fast_assign(
+ __assign_str(type);
+ __entry->target = target;
+ ),
+
+ TP_printk("type=%s target=%lu", __get_str(type), __entry->target)
+);
+
+TRACE_EVENT(thermal_zone_trip,
+
+ TP_PROTO(struct thermal_zone_device *tz, int trip,
+ enum thermal_trip_type trip_type),
+
+ TP_ARGS(tz, trip, trip_type),
+
+ TP_STRUCT__entry(
+ __string(thermal_zone, tz->type)
+ __field(int, id)
+ __field(int, trip)
+ __field(enum thermal_trip_type, trip_type)
+ ),
+
+ TP_fast_assign(
+ __assign_str(thermal_zone);
+ __entry->id = tz->id;
+ __entry->trip = trip;
+ __entry->trip_type = trip_type;
+ ),
+
+ TP_printk("thermal_zone=%s id=%d trip=%d trip_type=%s",
+ __get_str(thermal_zone), __entry->id, __entry->trip,
+ show_tzt_type(__entry->trip_type))
+);
+
+#ifdef CONFIG_CPU_THERMAL
+TRACE_EVENT(thermal_power_cpu_get_power_simple,
+ TP_PROTO(int cpu, u32 power),
+
+ TP_ARGS(cpu, power),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ __field(u32, power)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ __entry->power = power;
+ ),
+
+ TP_printk("cpu=%d power=%u", __entry->cpu, __entry->power)
+);
+
+TRACE_EVENT(thermal_power_cpu_limit,
+ TP_PROTO(const struct cpumask *cpus, unsigned int freq,
+ unsigned long cdev_state, u32 power),
+
+ TP_ARGS(cpus, freq, cdev_state, power),
+
+ TP_STRUCT__entry(
+ __bitmask(cpumask, num_possible_cpus())
+ __field(unsigned int, freq )
+ __field(unsigned long, cdev_state)
+ __field(u32, power )
+ ),
+
+ TP_fast_assign(
+ __assign_bitmask(cpumask, cpumask_bits(cpus),
+ num_possible_cpus());
+ __entry->freq = freq;
+ __entry->cdev_state = cdev_state;
+ __entry->power = power;
+ ),
+
+ TP_printk("cpus=%s freq=%u cdev_state=%lu power=%u",
+ __get_bitmask(cpumask), __entry->freq, __entry->cdev_state,
+ __entry->power)
+);
+#endif /* CONFIG_CPU_THERMAL */
+
+#ifdef CONFIG_DEVFREQ_THERMAL
+TRACE_EVENT(thermal_power_devfreq_get_power,
+ TP_PROTO(struct thermal_cooling_device *cdev,
+ struct devfreq_dev_status *status, unsigned long freq,
+ u32 power),
+
+ TP_ARGS(cdev, status, freq, power),
+
+ TP_STRUCT__entry(
+ __string(type, cdev->type )
+ __field(unsigned long, freq )
+ __field(u32, busy_time)
+ __field(u32, total_time)
+ __field(u32, power)
+ ),
+
+ TP_fast_assign(
+ __assign_str(type);
+ __entry->freq = freq;
+ __entry->busy_time = status->busy_time;
+ __entry->total_time = status->total_time;
+ __entry->power = power;
+ ),
+
+ TP_printk("type=%s freq=%lu load=%u power=%u",
+ __get_str(type), __entry->freq,
+ __entry->total_time == 0 ? 0 :
+ (100 * __entry->busy_time) / __entry->total_time,
+ __entry->power)
+);
+
+TRACE_EVENT(thermal_power_devfreq_limit,
+ TP_PROTO(struct thermal_cooling_device *cdev, unsigned long freq,
+ unsigned long cdev_state, u32 power),
+
+ TP_ARGS(cdev, freq, cdev_state, power),
+
+ TP_STRUCT__entry(
+ __string(type, cdev->type)
+ __field(unsigned int, freq )
+ __field(unsigned long, cdev_state)
+ __field(u32, power )
+ ),
+
+ TP_fast_assign(
+ __assign_str(type);
+ __entry->freq = freq;
+ __entry->cdev_state = cdev_state;
+ __entry->power = power;
+ ),
+
+ TP_printk("type=%s freq=%u cdev_state=%lu power=%u",
+ __get_str(type), __entry->freq, __entry->cdev_state,
+ __entry->power)
+);
+#endif /* CONFIG_DEVFREQ_THERMAL */
+#endif /* _TRACE_THERMAL_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE thermal_trace
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/drivers/thermal/thermal_trace_ipa.h b/drivers/thermal/thermal_trace_ipa.h
new file mode 100644
index 000000000..a82821eeb
--- /dev/null
+++ b/drivers/thermal/thermal_trace_ipa.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM thermal_power_allocator
+
+#if !defined(_TRACE_THERMAL_POWER_ALLOCATOR_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_THERMAL_POWER_ALLOCATOR_H
+
+#include <linux/tracepoint.h>
+
+#include "thermal_core.h"
+
+TRACE_EVENT(thermal_power_allocator,
+ 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 )
+ __field(u32, total_req_power )
+ __field(u32, total_granted_power )
+ __field(size_t, num_actors )
+ __field(u32, power_range )
+ __field(u32, max_allocatable_power )
+ __field(int, current_temp )
+ __field(s32, delta_temp )
+ ),
+ TP_fast_assign(
+ __entry->tz_id = tz->id;
+ __entry->total_req_power = total_req_power;
+ __entry->total_granted_power = total_granted_power;
+ __entry->num_actors = num_actors;
+ __entry->power_range = power_range;
+ __entry->max_allocatable_power = max_allocatable_power;
+ __entry->current_temp = current_temp;
+ __entry->delta_temp = delta_temp;
+ ),
+
+ 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),
+ TP_ARGS(tz, err, err_integral, p, i, d, output),
+ TP_STRUCT__entry(
+ __field(int, tz_id )
+ __field(s32, err )
+ __field(s32, err_integral)
+ __field(s64, p )
+ __field(s64, i )
+ __field(s64, d )
+ __field(s32, output )
+ ),
+ TP_fast_assign(
+ __entry->tz_id = tz->id;
+ __entry->err = err;
+ __entry->err_integral = err_integral;
+ __entry->p = p;
+ __entry->i = i;
+ __entry->d = d;
+ __entry->output = output;
+ ),
+
+ TP_printk("thermal_zone_id=%d err=%d err_integral=%d p=%lld i=%lld d=%lld output=%d",
+ __entry->tz_id, __entry->err, __entry->err_integral,
+ __entry->p, __entry->i, __entry->d, __entry->output)
+);
+#endif /* _TRACE_THERMAL_POWER_ALLOCATOR_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE thermal_trace_ipa
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/drivers/thermal/thermal_trip.c b/drivers/thermal/thermal_trip.c
new file mode 100644
index 000000000..4b8238468
--- /dev/null
+++ b/drivers/thermal/thermal_trip.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2008 Intel Corp
+ * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
+ * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ * Copyright 2022 Linaro Limited
+ *
+ * Thermal trips handling
+ */
+#include "thermal_core.h"
+
+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)
+{
+ if (trip_type < THERMAL_TRIP_ACTIVE || trip_type > THERMAL_TRIP_CRITICAL)
+ return "unknown";
+
+ return trip_type_names[trip_type];
+}
+
+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;
+
+ 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_for_each_trip(struct thermal_zone_device *tz,
+ int (*cb)(struct thermal_trip *, void *),
+ void *data)
+{
+ guard(thermal_zone)(tz);
+
+ return for_each_thermal_trip(tz, cb, data);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_for_each_trip);
+
+void thermal_zone_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ int ret;
+
+ lockdep_assert_held(&tz->lock);
+
+ if (!tz->ops.set_trips)
+ return;
+
+ /* No need to change trip points */
+ if (tz->prev_low_trip == low && tz->prev_high_trip == high)
+ return;
+
+ tz->prev_low_trip = low;
+ tz->prev_high_trip = high;
+
+ dev_dbg(&tz->device,
+ "new temperature boundaries: %d < x < %d\n", low, high);
+
+ /*
+ * 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);
+ if (ret)
+ dev_err(&tz->device, "Failed to set trips: %d\n", ret);
+}
+
+int thermal_zone_trip_id(const struct thermal_zone_device *tz,
+ const struct thermal_trip *trip)
+{
+ /*
+ * 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/Kconfig b/drivers/thermal/ti-soc-thermal/Kconfig
new file mode 100644
index 000000000..683a7027a
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/Kconfig
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config TI_SOC_THERMAL
+ tristate "Texas Instruments SoCs temperature sensor driver"
+ help
+ If you say yes here you get support for the Texas Instruments
+ OMAP4460+ on die bandgap temperature sensor support. The register
+ set is part of system control module.
+
+ This includes alert interrupts generation and also the TSHUT
+ support.
+
+config TI_THERMAL
+ bool "Texas Instruments SoCs thermal framework support"
+ depends on TI_SOC_THERMAL
+ help
+ If you say yes here you want to get support for generic thermal
+ framework for the Texas Instruments on die bandgap temperature sensor.
+
+ This includes trip points definitions, extrapolation rules and
+ CPU cooling device bindings.
+
+config OMAP3_THERMAL
+ bool "Texas Instruments OMAP3 thermal support"
+ depends on TI_SOC_THERMAL
+ depends on ARCH_OMAP3 || COMPILE_TEST
+ help
+ If you say yes here you get thermal support for the Texas Instruments
+ OMAP3 SoC family. The current chips supported are:
+ - OMAP3430
+
+ OMAP3 chips normally don't need thermal management, and sensors in
+ this generation are not accurate, nor they are very close to
+ the important hotspots.
+
+ Say 'N' here.
+
+config OMAP4_THERMAL
+ bool "Texas Instruments OMAP4 thermal support"
+ depends on TI_SOC_THERMAL
+ depends on ARCH_OMAP4 || COMPILE_TEST
+ help
+ If you say yes here you get thermal support for the Texas Instruments
+ OMAP4 SoC family. The current chip supported are:
+ - OMAP4430
+ - OMAP4460
+ - OMAP4470
+
+ This includes alert interrupts generation and also the TSHUT
+ support.
+
+config OMAP5_THERMAL
+ bool "Texas Instruments OMAP5 thermal support"
+ depends on TI_SOC_THERMAL
+ depends on SOC_OMAP5 || COMPILE_TEST
+ help
+ If you say yes here you get thermal support for the Texas Instruments
+ OMAP5 SoC family. The current chip supported are:
+ - OMAP5430
+
+ This includes alert interrupts generation and also the TSHUT
+ support.
+
+config DRA752_THERMAL
+ bool "Texas Instruments DRA752 thermal support"
+ depends on TI_SOC_THERMAL
+ depends on SOC_DRA7XX || COMPILE_TEST
+ help
+ If you say yes here you get thermal support for the Texas Instruments
+ DRA752 SoC family. The current chip supported are:
+ - DRA752
+
+ This includes alert interrupts generation and also the TSHUT
+ support.
diff --git a/drivers/thermal/ti-soc-thermal/Makefile b/drivers/thermal/ti-soc-thermal/Makefile
new file mode 100644
index 000000000..f180ebead
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal.o
+ti-soc-thermal-y := ti-bandgap.o
+ti-soc-thermal-$(CONFIG_TI_THERMAL) += ti-thermal-common.o
+ti-soc-thermal-$(CONFIG_DRA752_THERMAL) += dra752-thermal-data.o
+ti-soc-thermal-$(CONFIG_OMAP3_THERMAL) += omap3-thermal-data.o
+ti-soc-thermal-$(CONFIG_OMAP4_THERMAL) += omap4-thermal-data.o
+ti-soc-thermal-$(CONFIG_OMAP5_THERMAL) += omap5-thermal-data.o
diff --git a/drivers/thermal/ti-soc-thermal/TODO b/drivers/thermal/ti-soc-thermal/TODO
new file mode 100644
index 000000000..7da787d19
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/TODO
@@ -0,0 +1,12 @@
+List of TODOs (by Eduardo Valentin)
+
+on ti-bandgap.c:
+- Revisit PM support
+
+on ti-thermal-common.c/ti-thermal.h:
+- Revisit need for locking
+
+generally:
+- make sure this code works on OMAP4430, OMAP4460 and OMAP5430
+
+Copy patches to Eduardo Valentin <eduardo.valentin@ti.com>
diff --git a/drivers/thermal/ti-soc-thermal/dra752-bandgap.h b/drivers/thermal/ti-soc-thermal/dra752-bandgap.h
new file mode 100644
index 000000000..1402b8c44
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/dra752-bandgap.h
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * DRA752 bandgap registers, bitfields and temperature definitions
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ * Tero Kristo <t-kristo@ti.com>
+ *
+ * This is an auto generated file.
+ */
+#ifndef __DRA752_BANDGAP_H
+#define __DRA752_BANDGAP_H
+
+/**
+ * *** DRA752 ***
+ *
+ * Below, in sequence, are the Register definitions,
+ * the bitfields and the temperature definitions for DRA752.
+ */
+
+/**
+ * DRA752 register definitions
+ *
+ * Registers are defined as offsets. The offsets are
+ * relative to FUSE_OPP_BGAP_GPU on DRA752.
+ * DRA752_BANDGAP_BASE 0x4a0021e0
+ *
+ * Register below are grouped by domain (not necessarily in offset order)
+ */
+
+
+/* DRA752.common register offsets */
+#define DRA752_BANDGAP_CTRL_1_OFFSET 0x1a0
+#define DRA752_BANDGAP_STATUS_1_OFFSET 0x1c8
+#define DRA752_BANDGAP_CTRL_2_OFFSET 0x39c
+#define DRA752_BANDGAP_STATUS_2_OFFSET 0x3b8
+
+/* DRA752.core register offsets */
+#define DRA752_STD_FUSE_OPP_BGAP_CORE_OFFSET 0x8
+#define DRA752_TEMP_SENSOR_CORE_OFFSET 0x154
+#define DRA752_BANDGAP_THRESHOLD_CORE_OFFSET 0x1ac
+#define DRA752_DTEMP_CORE_1_OFFSET 0x20c
+#define DRA752_DTEMP_CORE_2_OFFSET 0x210
+
+/* DRA752.iva register offsets */
+#define DRA752_STD_FUSE_OPP_BGAP_IVA_OFFSET 0x388
+#define DRA752_TEMP_SENSOR_IVA_OFFSET 0x398
+#define DRA752_BANDGAP_THRESHOLD_IVA_OFFSET 0x3a4
+#define DRA752_DTEMP_IVA_1_OFFSET 0x3d4
+#define DRA752_DTEMP_IVA_2_OFFSET 0x3d8
+
+/* DRA752.mpu register offsets */
+#define DRA752_STD_FUSE_OPP_BGAP_MPU_OFFSET 0x4
+#define DRA752_TEMP_SENSOR_MPU_OFFSET 0x14c
+#define DRA752_BANDGAP_THRESHOLD_MPU_OFFSET 0x1a4
+#define DRA752_DTEMP_MPU_1_OFFSET 0x1e4
+#define DRA752_DTEMP_MPU_2_OFFSET 0x1e8
+
+/* DRA752.dspeve register offsets */
+#define DRA752_STD_FUSE_OPP_BGAP_DSPEVE_OFFSET 0x384
+#define DRA752_TEMP_SENSOR_DSPEVE_OFFSET 0x394
+#define DRA752_BANDGAP_THRESHOLD_DSPEVE_OFFSET 0x3a0
+#define DRA752_DTEMP_DSPEVE_1_OFFSET 0x3c0
+#define DRA752_DTEMP_DSPEVE_2_OFFSET 0x3c4
+
+/* DRA752.gpu register offsets */
+#define DRA752_STD_FUSE_OPP_BGAP_GPU_OFFSET 0x0
+#define DRA752_TEMP_SENSOR_GPU_OFFSET 0x150
+#define DRA752_BANDGAP_THRESHOLD_GPU_OFFSET 0x1a8
+#define DRA752_DTEMP_GPU_1_OFFSET 0x1f8
+#define DRA752_DTEMP_GPU_2_OFFSET 0x1fc
+
+/**
+ * Register bitfields for DRA752
+ *
+ * All the macros below define the required bits for
+ * controlling temperature on DRA752. Bit defines are
+ * grouped by register.
+ */
+
+/* DRA752.BANDGAP_STATUS_1 */
+#define DRA752_BANDGAP_STATUS_1_HOT_CORE_MASK BIT(5)
+#define DRA752_BANDGAP_STATUS_1_COLD_CORE_MASK BIT(4)
+#define DRA752_BANDGAP_STATUS_1_HOT_GPU_MASK BIT(3)
+#define DRA752_BANDGAP_STATUS_1_COLD_GPU_MASK BIT(2)
+#define DRA752_BANDGAP_STATUS_1_HOT_MPU_MASK BIT(1)
+#define DRA752_BANDGAP_STATUS_1_COLD_MPU_MASK BIT(0)
+
+/* DRA752.BANDGAP_CTRL_2 */
+#define DRA752_BANDGAP_CTRL_2_FREEZE_IVA_MASK BIT(22)
+#define DRA752_BANDGAP_CTRL_2_FREEZE_DSPEVE_MASK BIT(21)
+#define DRA752_BANDGAP_CTRL_2_MASK_HOT_IVA_MASK BIT(3)
+#define DRA752_BANDGAP_CTRL_2_MASK_COLD_IVA_MASK BIT(2)
+#define DRA752_BANDGAP_CTRL_2_MASK_HOT_DSPEVE_MASK BIT(1)
+#define DRA752_BANDGAP_CTRL_2_MASK_COLD_DSPEVE_MASK BIT(0)
+
+/* DRA752.BANDGAP_STATUS_2 */
+#define DRA752_BANDGAP_STATUS_2_HOT_IVA_MASK BIT(3)
+#define DRA752_BANDGAP_STATUS_2_COLD_IVA_MASK BIT(2)
+#define DRA752_BANDGAP_STATUS_2_HOT_DSPEVE_MASK BIT(1)
+#define DRA752_BANDGAP_STATUS_2_COLD_DSPEVE_MASK BIT(0)
+
+/* DRA752.BANDGAP_CTRL_1 */
+#define DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK (0x7 << 27)
+#define DRA752_BANDGAP_CTRL_1_FREEZE_CORE_MASK BIT(23)
+#define DRA752_BANDGAP_CTRL_1_FREEZE_GPU_MASK BIT(22)
+#define DRA752_BANDGAP_CTRL_1_FREEZE_MPU_MASK BIT(21)
+#define DRA752_BANDGAP_CTRL_1_MASK_HOT_CORE_MASK BIT(5)
+#define DRA752_BANDGAP_CTRL_1_MASK_COLD_CORE_MASK BIT(4)
+#define DRA752_BANDGAP_CTRL_1_MASK_HOT_GPU_MASK BIT(3)
+#define DRA752_BANDGAP_CTRL_1_MASK_COLD_GPU_MASK BIT(2)
+#define DRA752_BANDGAP_CTRL_1_MASK_HOT_MPU_MASK BIT(1)
+#define DRA752_BANDGAP_CTRL_1_MASK_COLD_MPU_MASK BIT(0)
+
+/* DRA752.TEMP_SENSOR */
+#define DRA752_TEMP_SENSOR_TMPSOFF_MASK BIT(11)
+#define DRA752_TEMP_SENSOR_EOCZ_MASK BIT(10)
+#define DRA752_TEMP_SENSOR_DTEMP_MASK (0x3ff << 0)
+
+/* DRA752.BANDGAP_THRESHOLD */
+#define DRA752_BANDGAP_THRESHOLD_HOT_MASK (0x3ff << 16)
+#define DRA752_BANDGAP_THRESHOLD_COLD_MASK (0x3ff << 0)
+
+/**
+ * Temperature limits and thresholds for DRA752
+ *
+ * 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.
+ */
+
+/* DRA752.common temperature definitions */
+/* ADC conversion table limits */
+#define DRA752_ADC_START_VALUE 540
+#define DRA752_ADC_END_VALUE 945
+
+/* DRA752.GPU temperature definitions */
+/* bandgap clock limits */
+#define DRA752_GPU_MAX_FREQ 1500000
+#define DRA752_GPU_MIN_FREQ 1000000
+/* interrupts thresholds */
+#define DRA752_GPU_T_HOT 800
+#define DRA752_GPU_T_COLD 795
+
+/* DRA752.MPU temperature definitions */
+/* bandgap clock limits */
+#define DRA752_MPU_MAX_FREQ 1500000
+#define DRA752_MPU_MIN_FREQ 1000000
+/* interrupts thresholds */
+#define DRA752_MPU_T_HOT 800
+#define DRA752_MPU_T_COLD 795
+
+/* DRA752.CORE temperature definitions */
+/* bandgap clock limits */
+#define DRA752_CORE_MAX_FREQ 1500000
+#define DRA752_CORE_MIN_FREQ 1000000
+/* interrupts thresholds */
+#define DRA752_CORE_T_HOT 800
+#define DRA752_CORE_T_COLD 795
+
+/* DRA752.DSPEVE temperature definitions */
+/* bandgap clock limits */
+#define DRA752_DSPEVE_MAX_FREQ 1500000
+#define DRA752_DSPEVE_MIN_FREQ 1000000
+/* interrupts thresholds */
+#define DRA752_DSPEVE_T_HOT 800
+#define DRA752_DSPEVE_T_COLD 795
+
+/* DRA752.IVA temperature definitions */
+/* bandgap clock limits */
+#define DRA752_IVA_MAX_FREQ 1500000
+#define DRA752_IVA_MIN_FREQ 1000000
+/* interrupts thresholds */
+#define DRA752_IVA_T_HOT 800
+#define DRA752_IVA_T_COLD 795
+
+#endif /* __DRA752_BANDGAP_H */
diff --git a/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c
new file mode 100644
index 000000000..a33107350
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * DRA752 thermal data.
+ *
+ * Copyright (C) 2013 Texas Instruments Inc.
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ * Tero Kristo <t-kristo@ti.com>
+ *
+ * This file is partially autogenerated.
+ */
+
+#include "ti-thermal.h"
+#include "ti-bandgap.h"
+#include "dra752-bandgap.h"
+
+/*
+ * DRA752 has five instances of thermal sensor: MPU, GPU, CORE,
+ * IVA and DSPEVE need to describe the individual registers and
+ * bit fields.
+ */
+
+/*
+ * DRA752 CORE thermal sensor register offsets and bit-fields
+ */
+static struct temp_sensor_registers
+dra752_core_temp_sensor_registers = {
+ .temp_sensor_ctrl = DRA752_TEMP_SENSOR_CORE_OFFSET,
+ .bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK,
+ .bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK,
+ .bgap_mask_ctrl = DRA752_BANDGAP_CTRL_1_OFFSET,
+ .mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_CORE_MASK,
+ .mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_CORE_MASK,
+ .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
+ .mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_CORE_MASK,
+ .bgap_threshold = DRA752_BANDGAP_THRESHOLD_CORE_OFFSET,
+ .threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK,
+ .threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK,
+ .bgap_status = DRA752_BANDGAP_STATUS_1_OFFSET,
+ .status_hot_mask = DRA752_BANDGAP_STATUS_1_HOT_CORE_MASK,
+ .status_cold_mask = DRA752_BANDGAP_STATUS_1_COLD_CORE_MASK,
+ .ctrl_dtemp_1 = DRA752_DTEMP_CORE_1_OFFSET,
+ .ctrl_dtemp_2 = DRA752_DTEMP_CORE_2_OFFSET,
+ .bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_CORE_OFFSET,
+};
+
+/*
+ * DRA752 IVA thermal sensor register offsets and bit-fields
+ */
+static struct temp_sensor_registers
+dra752_iva_temp_sensor_registers = {
+ .temp_sensor_ctrl = DRA752_TEMP_SENSOR_IVA_OFFSET,
+ .bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK,
+ .bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK,
+ .bgap_mask_ctrl = DRA752_BANDGAP_CTRL_2_OFFSET,
+ .mask_hot_mask = DRA752_BANDGAP_CTRL_2_MASK_HOT_IVA_MASK,
+ .mask_cold_mask = DRA752_BANDGAP_CTRL_2_MASK_COLD_IVA_MASK,
+ .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
+ .mask_freeze_mask = DRA752_BANDGAP_CTRL_2_FREEZE_IVA_MASK,
+ .bgap_threshold = DRA752_BANDGAP_THRESHOLD_IVA_OFFSET,
+ .threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK,
+ .threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK,
+ .bgap_status = DRA752_BANDGAP_STATUS_2_OFFSET,
+ .status_hot_mask = DRA752_BANDGAP_STATUS_2_HOT_IVA_MASK,
+ .status_cold_mask = DRA752_BANDGAP_STATUS_2_COLD_IVA_MASK,
+ .ctrl_dtemp_1 = DRA752_DTEMP_IVA_1_OFFSET,
+ .ctrl_dtemp_2 = DRA752_DTEMP_IVA_2_OFFSET,
+ .bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_IVA_OFFSET,
+};
+
+/*
+ * DRA752 MPU thermal sensor register offsets and bit-fields
+ */
+static struct temp_sensor_registers
+dra752_mpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = DRA752_TEMP_SENSOR_MPU_OFFSET,
+ .bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK,
+ .bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK,
+ .bgap_mask_ctrl = DRA752_BANDGAP_CTRL_1_OFFSET,
+ .mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_MPU_MASK,
+ .mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_MPU_MASK,
+ .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
+ .mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_MPU_MASK,
+ .bgap_threshold = DRA752_BANDGAP_THRESHOLD_MPU_OFFSET,
+ .threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK,
+ .threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK,
+ .bgap_status = DRA752_BANDGAP_STATUS_1_OFFSET,
+ .status_hot_mask = DRA752_BANDGAP_STATUS_1_HOT_MPU_MASK,
+ .status_cold_mask = DRA752_BANDGAP_STATUS_1_COLD_MPU_MASK,
+ .ctrl_dtemp_1 = DRA752_DTEMP_MPU_1_OFFSET,
+ .ctrl_dtemp_2 = DRA752_DTEMP_MPU_2_OFFSET,
+ .bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_MPU_OFFSET,
+};
+
+/*
+ * DRA752 DSPEVE thermal sensor register offsets and bit-fields
+ */
+static struct temp_sensor_registers
+dra752_dspeve_temp_sensor_registers = {
+ .temp_sensor_ctrl = DRA752_TEMP_SENSOR_DSPEVE_OFFSET,
+ .bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK,
+ .bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK,
+ .bgap_mask_ctrl = DRA752_BANDGAP_CTRL_2_OFFSET,
+ .mask_hot_mask = DRA752_BANDGAP_CTRL_2_MASK_HOT_DSPEVE_MASK,
+ .mask_cold_mask = DRA752_BANDGAP_CTRL_2_MASK_COLD_DSPEVE_MASK,
+ .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
+ .mask_freeze_mask = DRA752_BANDGAP_CTRL_2_FREEZE_DSPEVE_MASK,
+ .bgap_threshold = DRA752_BANDGAP_THRESHOLD_DSPEVE_OFFSET,
+ .threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK,
+ .threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK,
+ .bgap_status = DRA752_BANDGAP_STATUS_2_OFFSET,
+ .status_hot_mask = DRA752_BANDGAP_STATUS_2_HOT_DSPEVE_MASK,
+ .status_cold_mask = DRA752_BANDGAP_STATUS_2_COLD_DSPEVE_MASK,
+ .ctrl_dtemp_1 = DRA752_DTEMP_DSPEVE_1_OFFSET,
+ .ctrl_dtemp_2 = DRA752_DTEMP_DSPEVE_2_OFFSET,
+ .bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_DSPEVE_OFFSET,
+};
+
+/*
+ * DRA752 GPU thermal sensor register offsets and bit-fields
+ */
+static struct temp_sensor_registers
+dra752_gpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = DRA752_TEMP_SENSOR_GPU_OFFSET,
+ .bgap_tempsoff_mask = DRA752_TEMP_SENSOR_TMPSOFF_MASK,
+ .bgap_eocz_mask = DRA752_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = DRA752_TEMP_SENSOR_DTEMP_MASK,
+ .bgap_mask_ctrl = DRA752_BANDGAP_CTRL_1_OFFSET,
+ .mask_hot_mask = DRA752_BANDGAP_CTRL_1_MASK_HOT_GPU_MASK,
+ .mask_cold_mask = DRA752_BANDGAP_CTRL_1_MASK_COLD_GPU_MASK,
+ .mask_counter_delay_mask = DRA752_BANDGAP_CTRL_1_COUNTER_DELAY_MASK,
+ .mask_freeze_mask = DRA752_BANDGAP_CTRL_1_FREEZE_GPU_MASK,
+ .bgap_threshold = DRA752_BANDGAP_THRESHOLD_GPU_OFFSET,
+ .threshold_thot_mask = DRA752_BANDGAP_THRESHOLD_HOT_MASK,
+ .threshold_tcold_mask = DRA752_BANDGAP_THRESHOLD_COLD_MASK,
+ .bgap_status = DRA752_BANDGAP_STATUS_1_OFFSET,
+ .status_hot_mask = DRA752_BANDGAP_STATUS_1_HOT_GPU_MASK,
+ .status_cold_mask = DRA752_BANDGAP_STATUS_1_COLD_GPU_MASK,
+ .ctrl_dtemp_1 = DRA752_DTEMP_GPU_1_OFFSET,
+ .ctrl_dtemp_2 = DRA752_DTEMP_GPU_2_OFFSET,
+ .bgap_efuse = DRA752_STD_FUSE_OPP_BGAP_GPU_OFFSET,
+};
+
+/* Thresholds and limits for DRA752 MPU temperature sensor */
+static struct temp_sensor_data dra752_mpu_temp_sensor_data = {
+ .t_hot = DRA752_MPU_T_HOT,
+ .t_cold = DRA752_MPU_T_COLD,
+ .min_freq = DRA752_MPU_MIN_FREQ,
+ .max_freq = DRA752_MPU_MAX_FREQ,
+};
+
+/* Thresholds and limits for DRA752 GPU temperature sensor */
+static struct temp_sensor_data dra752_gpu_temp_sensor_data = {
+ .t_hot = DRA752_GPU_T_HOT,
+ .t_cold = DRA752_GPU_T_COLD,
+ .min_freq = DRA752_GPU_MIN_FREQ,
+ .max_freq = DRA752_GPU_MAX_FREQ,
+};
+
+/* Thresholds and limits for DRA752 CORE temperature sensor */
+static struct temp_sensor_data dra752_core_temp_sensor_data = {
+ .t_hot = DRA752_CORE_T_HOT,
+ .t_cold = DRA752_CORE_T_COLD,
+ .min_freq = DRA752_CORE_MIN_FREQ,
+ .max_freq = DRA752_CORE_MAX_FREQ,
+};
+
+/* Thresholds and limits for DRA752 DSPEVE temperature sensor */
+static struct temp_sensor_data dra752_dspeve_temp_sensor_data = {
+ .t_hot = DRA752_DSPEVE_T_HOT,
+ .t_cold = DRA752_DSPEVE_T_COLD,
+ .min_freq = DRA752_DSPEVE_MIN_FREQ,
+ .max_freq = DRA752_DSPEVE_MAX_FREQ,
+};
+
+/* Thresholds and limits for DRA752 IVA temperature sensor */
+static struct temp_sensor_data dra752_iva_temp_sensor_data = {
+ .t_hot = DRA752_IVA_T_HOT,
+ .t_cold = DRA752_IVA_T_COLD,
+ .min_freq = DRA752_IVA_MIN_FREQ,
+ .max_freq = DRA752_IVA_MAX_FREQ,
+};
+
+/*
+ * DRA752 : Temperature values in milli degree celsius
+ * ADC code values from 540 to 945
+ */
+static
+int dra752_adc_to_temp[DRA752_ADC_END_VALUE - DRA752_ADC_START_VALUE + 1] = {
+ /* Index 540 - 549 */
+ -40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200,
+ -37800,
+ /* Index 550 - 559 */
+ -37400, -37000, -36600, -36200, -35800, -35300, -34700, -34200, -33800,
+ -33400,
+ /* Index 560 - 569 */
+ -33000, -32600, -32200, -31800, -31400, -31000, -30600, -30200, -29800,
+ -29400,
+ /* Index 570 - 579 */
+ -29000, -28600, -28200, -27700, -27100, -26600, -26200, -25800, -25400,
+ -25000,
+ /* Index 580 - 589 */
+ -24600, -24200, -23800, -23400, -23000, -22600, -22200, -21800, -21400,
+ -21000,
+ /* Index 590 - 599 */
+ -20500, -19900, -19400, -19000, -18600, -18200, -17800, -17400, -17000,
+ -16600,
+ /* Index 600 - 609 */
+ -16200, -15800, -15400, -15000, -14600, -14200, -13800, -13400, -13000,
+ -12500,
+ /* Index 610 - 619 */
+ -11900, -11400, -11000, -10600, -10200, -9800, -9400, -9000, -8600,
+ -8200,
+ /* Index 620 - 629 */
+ -7800, -7400, -7000, -6600, -6200, -5800, -5400, -5000, -4500,
+ -3900,
+ /* Index 630 - 639 */
+ -3400, -3000, -2600, -2200, -1800, -1400, -1000, -600, -200,
+ 200,
+ /* Index 640 - 649 */
+ 600, 1000, 1400, 1800, 2200, 2600, 3000, 3400, 3900,
+ 4500,
+ /* Index 650 - 659 */
+ 5000, 5400, 5800, 6200, 6600, 7000, 7400, 7800, 8200,
+ 8600,
+ /* Index 660 - 669 */
+ 9000, 9400, 9800, 10200, 10600, 11000, 11400, 11800, 12200,
+ 12700,
+ /* Index 670 - 679 */
+ 13300, 13800, 14200, 14600, 15000, 15400, 15800, 16200, 16600,
+ 17000,
+ /* Index 680 - 689 */
+ 17400, 17800, 18200, 18600, 19000, 19400, 19800, 20200, 20600,
+ 21000,
+ /* Index 690 - 699 */
+ 21400, 21900, 22500, 23000, 23400, 23800, 24200, 24600, 25000,
+ 25400,
+ /* Index 700 - 709 */
+ 25800, 26200, 26600, 27000, 27400, 27800, 28200, 28600, 29000,
+ 29400,
+ /* Index 710 - 719 */
+ 29800, 30200, 30600, 31000, 31400, 31900, 32500, 33000, 33400,
+ 33800,
+ /* Index 720 - 729 */
+ 34200, 34600, 35000, 35400, 35800, 36200, 36600, 37000, 37400,
+ 37800,
+ /* Index 730 - 739 */
+ 38200, 38600, 39000, 39400, 39800, 40200, 40600, 41000, 41400,
+ 41800,
+ /* Index 740 - 749 */
+ 42200, 42600, 43100, 43700, 44200, 44600, 45000, 45400, 45800,
+ 46200,
+ /* Index 750 - 759 */
+ 46600, 47000, 47400, 47800, 48200, 48600, 49000, 49400, 49800,
+ 50200,
+ /* Index 760 - 769 */
+ 50600, 51000, 51400, 51800, 52200, 52600, 53000, 53400, 53800,
+ 54200,
+ /* Index 770 - 779 */
+ 54600, 55000, 55400, 55900, 56500, 57000, 57400, 57800, 58200,
+ 58600,
+ /* Index 780 - 789 */
+ 59000, 59400, 59800, 60200, 60600, 61000, 61400, 61800, 62200,
+ 62600,
+ /* Index 790 - 799 */
+ 63000, 63400, 63800, 64200, 64600, 65000, 65400, 65800, 66200,
+ 66600,
+ /* Index 800 - 809 */
+ 67000, 67400, 67800, 68200, 68600, 69000, 69400, 69800, 70200,
+ 70600,
+ /* Index 810 - 819 */
+ 71000, 71500, 72100, 72600, 73000, 73400, 73800, 74200, 74600,
+ 75000,
+ /* Index 820 - 829 */
+ 75400, 75800, 76200, 76600, 77000, 77400, 77800, 78200, 78600,
+ 79000,
+ /* Index 830 - 839 */
+ 79400, 79800, 80200, 80600, 81000, 81400, 81800, 82200, 82600,
+ 83000,
+ /* Index 840 - 849 */
+ 83400, 83800, 84200, 84600, 85000, 85400, 85800, 86200, 86600,
+ 87000,
+ /* Index 850 - 859 */
+ 87400, 87800, 88200, 88600, 89000, 89400, 89800, 90200, 90600,
+ 91000,
+ /* Index 860 - 869 */
+ 91400, 91800, 92200, 92600, 93000, 93400, 93800, 94200, 94600,
+ 95000,
+ /* Index 870 - 879 */
+ 95400, 95800, 96200, 96600, 97000, 97500, 98100, 98600, 99000,
+ 99400,
+ /* Index 880 - 889 */
+ 99800, 100200, 100600, 101000, 101400, 101800, 102200, 102600, 103000,
+ 103400,
+ /* Index 890 - 899 */
+ 103800, 104200, 104600, 105000, 105400, 105800, 106200, 106600, 107000,
+ 107400,
+ /* Index 900 - 909 */
+ 107800, 108200, 108600, 109000, 109400, 109800, 110200, 110600, 111000,
+ 111400,
+ /* Index 910 - 919 */
+ 111800, 112200, 112600, 113000, 113400, 113800, 114200, 114600, 115000,
+ 115400,
+ /* Index 920 - 929 */
+ 115800, 116200, 116600, 117000, 117400, 117800, 118200, 118600, 119000,
+ 119400,
+ /* Index 930 - 939 */
+ 119800, 120200, 120600, 121000, 121400, 121800, 122200, 122600, 123000,
+ 123400,
+ /* Index 940 - 945 */
+ 123800, 124200, 124600, 124900, 125000, 125000,
+};
+
+/* DRA752 data */
+const struct ti_bandgap_data dra752_data = {
+ .features = TI_BANDGAP_FEATURE_FREEZE_BIT |
+ TI_BANDGAP_FEATURE_TALERT |
+ TI_BANDGAP_FEATURE_COUNTER_DELAY |
+ TI_BANDGAP_FEATURE_HISTORY_BUFFER |
+ TI_BANDGAP_FEATURE_ERRATA_814,
+ .fclock_name = "l3instr_ts_gclk_div",
+ .div_ck_name = "l3instr_ts_gclk_div",
+ .conv_table = dra752_adc_to_temp,
+ .adc_start_val = DRA752_ADC_START_VALUE,
+ .adc_end_val = DRA752_ADC_END_VALUE,
+ .expose_sensor = ti_thermal_expose_sensor,
+ .remove_sensor = ti_thermal_remove_sensor,
+ .sensors = {
+ {
+ .registers = &dra752_mpu_temp_sensor_registers,
+ .ts_data = &dra752_mpu_temp_sensor_data,
+ .domain = "cpu",
+ .register_cooling = ti_thermal_register_cpu_cooling,
+ .unregister_cooling = ti_thermal_unregister_cpu_cooling,
+ .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB,
+ .constant_pcb = DRA752_GRADIENT_CONST_W_PCB,
+ },
+ {
+ .registers = &dra752_gpu_temp_sensor_registers,
+ .ts_data = &dra752_gpu_temp_sensor_data,
+ .domain = "gpu",
+ .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB,
+ .constant_pcb = DRA752_GRADIENT_CONST_W_PCB,
+ },
+ {
+ .registers = &dra752_core_temp_sensor_registers,
+ .ts_data = &dra752_core_temp_sensor_data,
+ .domain = "core",
+ .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB,
+ .constant_pcb = DRA752_GRADIENT_CONST_W_PCB,
+ },
+ {
+ .registers = &dra752_dspeve_temp_sensor_registers,
+ .ts_data = &dra752_dspeve_temp_sensor_data,
+ .domain = "dspeve",
+ .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB,
+ .constant_pcb = DRA752_GRADIENT_CONST_W_PCB,
+ },
+ {
+ .registers = &dra752_iva_temp_sensor_registers,
+ .ts_data = &dra752_iva_temp_sensor_data,
+ .domain = "iva",
+ .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB,
+ .constant_pcb = DRA752_GRADIENT_CONST_W_PCB,
+ },
+ },
+ .sensor_count = 5,
+};
diff --git a/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c
new file mode 100644
index 000000000..72e1ff270
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * OMAP3 thermal driver.
+ *
+ * Copyright (C) 2011-2012 Texas Instruments Inc.
+ * Copyright (C) 2014 Pavel Machek <pavel@ucw.cz>
+ *
+ * Note
+ * http://www.ti.com/lit/er/sprz278f/sprz278f.pdf "Advisory
+ * 3.1.1.186 MMC OCP Clock Not Gated When Thermal Sensor Is Used"
+ *
+ * Also TI says:
+ * Just be careful when you try to make thermal policy like decisions
+ * based on this sensor. Placement of the sensor w.r.t the actual logic
+ * generating heat has to be a factor as well. If you are just looking
+ * for an approximation temperature (thermometerish kind), you might be
+ * ok with this. I am not sure we'd find any TI data around this.. just a
+ * heads up.
+ */
+
+#include "ti-thermal.h"
+#include "ti-bandgap.h"
+
+/*
+ * OMAP34XX has one instance of thermal sensor for MPU
+ * need to describe the individual bit fields
+ */
+static struct temp_sensor_registers
+omap34xx_mpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = 0,
+ .bgap_soc_mask = BIT(8),
+ .bgap_eocz_mask = BIT(7),
+ .bgap_dtemp_mask = 0x7f,
+
+ .bgap_mode_ctrl = 0,
+ .mode_ctrl_mask = BIT(9),
+};
+
+/* Thresholds and limits for OMAP34XX MPU temperature sensor */
+static struct temp_sensor_data omap34xx_mpu_temp_sensor_data = {
+ .min_freq = 32768,
+ .max_freq = 32768,
+};
+
+/*
+ * Temperature values in milli degree celsius
+ */
+static const int
+omap34xx_adc_to_temp[128] = {
+ -40000, -40000, -40000, -40000, -40000, -39000, -38000, -36000,
+ -34000, -32000, -31000, -29000, -28000, -26000, -25000, -24000,
+ -22000, -21000, -19000, -18000, -17000, -15000, -14000, -12000,
+ -11000, -9000, -8000, -7000, -5000, -4000, -2000, -1000, 0000,
+ 1000, 3000, 4000, 5000, 7000, 8000, 10000, 11000, 13000, 14000,
+ 15000, 17000, 18000, 20000, 21000, 22000, 24000, 25000, 27000,
+ 28000, 30000, 31000, 32000, 34000, 35000, 37000, 38000, 39000,
+ 41000, 42000, 44000, 45000, 47000, 48000, 49000, 51000, 52000,
+ 53000, 55000, 56000, 58000, 59000, 60000, 62000, 63000, 65000,
+ 66000, 67000, 69000, 70000, 72000, 73000, 74000, 76000, 77000,
+ 79000, 80000, 81000, 83000, 84000, 85000, 87000, 88000, 89000,
+ 91000, 92000, 94000, 95000, 96000, 98000, 99000, 100000,
+ 102000, 103000, 105000, 106000, 107000, 109000, 110000, 111000,
+ 113000, 114000, 116000, 117000, 118000, 120000, 121000, 122000,
+ 124000, 124000, 125000, 125000, 125000, 125000, 125000
+};
+
+/* OMAP34XX data */
+const struct ti_bandgap_data omap34xx_data = {
+ .features = TI_BANDGAP_FEATURE_CLK_CTRL | TI_BANDGAP_FEATURE_UNRELIABLE,
+ .fclock_name = "ts_fck",
+ .div_ck_name = "ts_fck",
+ .conv_table = omap34xx_adc_to_temp,
+ .adc_start_val = 0,
+ .adc_end_val = 127,
+ .expose_sensor = ti_thermal_expose_sensor,
+ .remove_sensor = ti_thermal_remove_sensor,
+
+ .sensors = {
+ {
+ .registers = &omap34xx_mpu_temp_sensor_registers,
+ .ts_data = &omap34xx_mpu_temp_sensor_data,
+ .domain = "cpu",
+ .slope_pcb = 0,
+ .constant_pcb = 20000,
+ .register_cooling = NULL,
+ .unregister_cooling = NULL,
+ },
+ },
+ .sensor_count = 1,
+};
+
+/*
+ * OMAP36XX has one instance of thermal sensor for MPU
+ * need to describe the individual bit fields
+ */
+static struct temp_sensor_registers
+omap36xx_mpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = 0,
+ .bgap_soc_mask = BIT(9),
+ .bgap_eocz_mask = BIT(8),
+ .bgap_dtemp_mask = 0xFF,
+
+ .bgap_mode_ctrl = 0,
+ .mode_ctrl_mask = BIT(10),
+};
+
+/* Thresholds and limits for OMAP36XX MPU temperature sensor */
+static struct temp_sensor_data omap36xx_mpu_temp_sensor_data = {
+ .min_freq = 32768,
+ .max_freq = 32768,
+};
+
+/*
+ * Temperature values in milli degree celsius
+ */
+static const int
+omap36xx_adc_to_temp[128] = {
+ -40000, -40000, -40000, -40000, -40000, -40000, -40000, -40000,
+ -40000, -40000, -40000, -40000, -40000, -38000, -35000, -34000,
+ -32000, -30000, -28000, -26000, -24000, -22000, -20000, -18500,
+ -17000, -15000, -13500, -12000, -10000, -8000, -6500, -5000, -3500,
+ -1500, 0, 2000, 3500, 5000, 6500, 8500, 10000, 12000, 13500,
+ 15000, 17000, 19000, 21000, 23000, 25000, 27000, 28500, 30000,
+ 32000, 33500, 35000, 37000, 38500, 40000, 42000, 43500, 45000,
+ 47000, 48500, 50000, 52000, 53500, 55000, 57000, 58500, 60000,
+ 62000, 64000, 66000, 68000, 70000, 71500, 73500, 75000, 77000,
+ 78500, 80000, 82000, 83500, 85000, 87000, 88500, 90000, 92000,
+ 93500, 95000, 97000, 98500, 100000, 102000, 103500, 105000, 107000,
+ 109000, 111000, 113000, 115000, 117000, 118500, 120000, 122000,
+ 123500, 125000, 125000, 125000, 125000, 125000, 125000, 125000,
+ 125000, 125000, 125000, 125000, 125000, 125000, 125000, 125000,
+ 125000, 125000, 125000, 125000, 125000, 125000, 125000
+};
+
+/* OMAP36XX data */
+const struct ti_bandgap_data omap36xx_data = {
+ .features = TI_BANDGAP_FEATURE_CLK_CTRL | TI_BANDGAP_FEATURE_UNRELIABLE,
+ .fclock_name = "ts_fck",
+ .div_ck_name = "ts_fck",
+ .conv_table = omap36xx_adc_to_temp,
+ .adc_start_val = 0,
+ .adc_end_val = 127,
+ .expose_sensor = ti_thermal_expose_sensor,
+ .remove_sensor = ti_thermal_remove_sensor,
+
+ .sensors = {
+ {
+ .registers = &omap36xx_mpu_temp_sensor_registers,
+ .ts_data = &omap36xx_mpu_temp_sensor_data,
+ .domain = "cpu",
+ .slope_pcb = 0,
+ .constant_pcb = 20000,
+ .register_cooling = NULL,
+ .unregister_cooling = NULL,
+ },
+ },
+ .sensor_count = 1,
+};
diff --git a/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c
new file mode 100644
index 000000000..b4ef7340a
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * OMAP4 thermal driver.
+ *
+ * Copyright (C) 2011-2012 Texas Instruments Inc.
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+
+#include "ti-thermal.h"
+#include "ti-bandgap.h"
+#include "omap4xxx-bandgap.h"
+
+/*
+ * OMAP4430 has one instance of thermal sensor for MPU
+ * need to describe the individual bit fields
+ */
+static struct temp_sensor_registers
+omap4430_mpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = OMAP4430_TEMP_SENSOR_CTRL_OFFSET,
+ .bgap_tempsoff_mask = OMAP4430_BGAP_TEMPSOFF_MASK,
+ .bgap_soc_mask = OMAP4430_BGAP_TEMP_SENSOR_SOC_MASK,
+ .bgap_eocz_mask = OMAP4430_BGAP_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = OMAP4430_BGAP_TEMP_SENSOR_DTEMP_MASK,
+
+ .bgap_mode_ctrl = OMAP4430_TEMP_SENSOR_CTRL_OFFSET,
+ .mode_ctrl_mask = OMAP4430_CONTINUOUS_MODE_MASK,
+
+ .bgap_efuse = OMAP4430_FUSE_OPP_BGAP,
+};
+
+/* Thresholds and limits for OMAP4430 MPU temperature sensor */
+static struct temp_sensor_data omap4430_mpu_temp_sensor_data = {
+ .min_freq = OMAP4430_MIN_FREQ,
+ .max_freq = OMAP4430_MAX_FREQ,
+};
+
+/*
+ * Temperature values in milli degree celsius
+ * ADC code values from 13 to 107, see TRM
+ * "18.4.10.2.3 ADC Codes Versus Temperature".
+ */
+static const int
+omap4430_adc_to_temp[OMAP4430_ADC_END_VALUE - OMAP4430_ADC_START_VALUE + 1] = {
+ -40000, -38000, -35000, -34000, -32000, -30000, -28000, -26000, -24000,
+ -22000, -20000, -18500, -17000, -15000, -13500, -12000, -10000, -8000,
+ -6500, -5000, -3500, -1500, 0, 2000, 3500, 5000, 6500, 8500, 10000,
+ 12000, 13500, 15000, 17000, 19000, 21000, 23000, 25000, 27000, 28500,
+ 30000, 32000, 33500, 35000, 37000, 38500, 40000, 42000, 43500, 45000,
+ 47000, 48500, 50000, 52000, 53500, 55000, 57000, 58500, 60000, 62000,
+ 64000, 66000, 68000, 70000, 71500, 73500, 75000, 77000, 78500, 80000,
+ 82000, 83500, 85000, 87000, 88500, 90000, 92000, 93500, 95000, 97000,
+ 98500, 100000, 102000, 103500, 105000, 107000, 109000, 111000, 113000,
+ 115000, 117000, 118500, 120000, 122000, 123500, 125000,
+};
+
+/* OMAP4430 data */
+const struct ti_bandgap_data omap4430_data = {
+ .features = TI_BANDGAP_FEATURE_MODE_CONFIG |
+ TI_BANDGAP_FEATURE_CLK_CTRL |
+ TI_BANDGAP_FEATURE_POWER_SWITCH |
+ TI_BANDGAP_FEATURE_CONT_MODE_ONLY,
+ .fclock_name = "bandgap_fclk",
+ .div_ck_name = "bandgap_fclk",
+ .conv_table = omap4430_adc_to_temp,
+ .adc_start_val = OMAP4430_ADC_START_VALUE,
+ .adc_end_val = OMAP4430_ADC_END_VALUE,
+ .expose_sensor = ti_thermal_expose_sensor,
+ .remove_sensor = ti_thermal_remove_sensor,
+ .sensors = {
+ {
+ .registers = &omap4430_mpu_temp_sensor_registers,
+ .ts_data = &omap4430_mpu_temp_sensor_data,
+ .domain = "cpu",
+ .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_4430,
+ .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_4430,
+ .register_cooling = ti_thermal_register_cpu_cooling,
+ .unregister_cooling = ti_thermal_unregister_cpu_cooling,
+ },
+ },
+ .sensor_count = 1,
+};
+/*
+ * OMAP4460 has one instance of thermal sensor for MPU
+ * need to describe the individual bit fields
+ */
+static struct temp_sensor_registers
+omap4460_mpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = OMAP4460_TEMP_SENSOR_CTRL_OFFSET,
+ .bgap_tempsoff_mask = OMAP4460_BGAP_TEMPSOFF_MASK,
+ .bgap_soc_mask = OMAP4460_BGAP_TEMP_SENSOR_SOC_MASK,
+ .bgap_eocz_mask = OMAP4460_BGAP_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = OMAP4460_BGAP_TEMP_SENSOR_DTEMP_MASK,
+
+ .bgap_mask_ctrl = OMAP4460_BGAP_CTRL_OFFSET,
+ .mask_hot_mask = OMAP4460_MASK_HOT_MASK,
+ .mask_cold_mask = OMAP4460_MASK_COLD_MASK,
+
+ .bgap_mode_ctrl = OMAP4460_BGAP_CTRL_OFFSET,
+ .mode_ctrl_mask = OMAP4460_CONTINUOUS_MODE_MASK,
+
+ .bgap_counter = OMAP4460_BGAP_COUNTER_OFFSET,
+ .counter_mask = OMAP4460_COUNTER_MASK,
+
+ .bgap_threshold = OMAP4460_BGAP_THRESHOLD_OFFSET,
+ .threshold_thot_mask = OMAP4460_T_HOT_MASK,
+ .threshold_tcold_mask = OMAP4460_T_COLD_MASK,
+
+ .tshut_threshold = OMAP4460_BGAP_TSHUT_OFFSET,
+ .tshut_hot_mask = OMAP4460_TSHUT_HOT_MASK,
+ .tshut_cold_mask = OMAP4460_TSHUT_COLD_MASK,
+
+ .bgap_status = OMAP4460_BGAP_STATUS_OFFSET,
+ .status_hot_mask = OMAP4460_HOT_FLAG_MASK,
+ .status_cold_mask = OMAP4460_COLD_FLAG_MASK,
+
+ .bgap_efuse = OMAP4460_FUSE_OPP_BGAP,
+};
+
+/* Thresholds and limits for OMAP4460 MPU temperature sensor */
+static struct temp_sensor_data omap4460_mpu_temp_sensor_data = {
+ .tshut_hot = OMAP4460_TSHUT_HOT,
+ .tshut_cold = OMAP4460_TSHUT_COLD,
+ .t_hot = OMAP4460_T_HOT,
+ .t_cold = OMAP4460_T_COLD,
+ .min_freq = OMAP4460_MIN_FREQ,
+ .max_freq = OMAP4460_MAX_FREQ,
+};
+
+/*
+ * Temperature values in milli degree celsius
+ * ADC code values from 530 to 923
+ */
+static const int
+omap4460_adc_to_temp[OMAP4460_ADC_END_VALUE - OMAP4460_ADC_START_VALUE + 1] = {
+ -40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200,
+ -37800, -37300, -36800, -36400, -36000, -35600, -35200, -34800,
+ -34300, -33800, -33400, -33000, -32600, -32200, -31800, -31300,
+ -30800, -30400, -30000, -29600, -29200, -28700, -28200, -27800,
+ -27400, -27000, -26600, -26200, -25700, -25200, -24800, -24400,
+ -24000, -23600, -23200, -22700, -22200, -21800, -21400, -21000,
+ -20600, -20200, -19700, -19200, -18800, -18400, -18000, -17600,
+ -17200, -16700, -16200, -15800, -15400, -15000, -14600, -14200,
+ -13700, -13200, -12800, -12400, -12000, -11600, -11200, -10700,
+ -10200, -9800, -9400, -9000, -8600, -8200, -7700, -7200, -6800,
+ -6400, -6000, -5600, -5200, -4800, -4300, -3800, -3400, -3000,
+ -2600, -2200, -1800, -1300, -800, -400, 0, 400, 800, 1200, 1600,
+ 2100, 2600, 3000, 3400, 3800, 4200, 4600, 5100, 5600, 6000, 6400,
+ 6800, 7200, 7600, 8000, 8500, 9000, 9400, 9800, 10200, 10600, 11000,
+ 11400, 11900, 12400, 12800, 13200, 13600, 14000, 14400, 14800,
+ 15300, 15800, 16200, 16600, 17000, 17400, 17800, 18200, 18700,
+ 19200, 19600, 20000, 20400, 20800, 21200, 21600, 22100, 22600,
+ 23000, 23400, 23800, 24200, 24600, 25000, 25400, 25900, 26400,
+ 26800, 27200, 27600, 28000, 28400, 28800, 29300, 29800, 30200,
+ 30600, 31000, 31400, 31800, 32200, 32600, 33100, 33600, 34000,
+ 34400, 34800, 35200, 35600, 36000, 36400, 36800, 37300, 37800,
+ 38200, 38600, 39000, 39400, 39800, 40200, 40600, 41100, 41600,
+ 42000, 42400, 42800, 43200, 43600, 44000, 44400, 44800, 45300,
+ 45800, 46200, 46600, 47000, 47400, 47800, 48200, 48600, 49000,
+ 49500, 50000, 50400, 50800, 51200, 51600, 52000, 52400, 52800,
+ 53200, 53700, 54200, 54600, 55000, 55400, 55800, 56200, 56600,
+ 57000, 57400, 57800, 58200, 58700, 59200, 59600, 60000, 60400,
+ 60800, 61200, 61600, 62000, 62400, 62800, 63300, 63800, 64200,
+ 64600, 65000, 65400, 65800, 66200, 66600, 67000, 67400, 67800,
+ 68200, 68700, 69200, 69600, 70000, 70400, 70800, 71200, 71600,
+ 72000, 72400, 72800, 73200, 73600, 74100, 74600, 75000, 75400,
+ 75800, 76200, 76600, 77000, 77400, 77800, 78200, 78600, 79000,
+ 79400, 79800, 80300, 80800, 81200, 81600, 82000, 82400, 82800,
+ 83200, 83600, 84000, 84400, 84800, 85200, 85600, 86000, 86400,
+ 86800, 87300, 87800, 88200, 88600, 89000, 89400, 89800, 90200,
+ 90600, 91000, 91400, 91800, 92200, 92600, 93000, 93400, 93800,
+ 94200, 94600, 95000, 95500, 96000, 96400, 96800, 97200, 97600,
+ 98000, 98400, 98800, 99200, 99600, 100000, 100400, 100800, 101200,
+ 101600, 102000, 102400, 102800, 103200, 103600, 104000, 104400,
+ 104800, 105200, 105600, 106100, 106600, 107000, 107400, 107800,
+ 108200, 108600, 109000, 109400, 109800, 110200, 110600, 111000,
+ 111400, 111800, 112200, 112600, 113000, 113400, 113800, 114200,
+ 114600, 115000, 115400, 115800, 116200, 116600, 117000, 117400,
+ 117800, 118200, 118600, 119000, 119400, 119800, 120200, 120600,
+ 121000, 121400, 121800, 122200, 122600, 123000, 123400, 123800, 124200,
+ 124600, 124900, 125000, 125000, 125000, 125000
+};
+
+/* OMAP4460 data */
+const struct ti_bandgap_data omap4460_data = {
+ .features = TI_BANDGAP_FEATURE_TSHUT |
+ TI_BANDGAP_FEATURE_TSHUT_CONFIG |
+ TI_BANDGAP_FEATURE_TALERT |
+ TI_BANDGAP_FEATURE_MODE_CONFIG |
+ TI_BANDGAP_FEATURE_POWER_SWITCH |
+ TI_BANDGAP_FEATURE_CLK_CTRL |
+ TI_BANDGAP_FEATURE_COUNTER,
+ .fclock_name = "bandgap_ts_fclk",
+ .div_ck_name = "div_ts_ck",
+ .conv_table = omap4460_adc_to_temp,
+ .adc_start_val = OMAP4460_ADC_START_VALUE,
+ .adc_end_val = OMAP4460_ADC_END_VALUE,
+ .expose_sensor = ti_thermal_expose_sensor,
+ .remove_sensor = ti_thermal_remove_sensor,
+ .report_temperature = ti_thermal_report_sensor_temperature,
+ .sensors = {
+ {
+ .registers = &omap4460_mpu_temp_sensor_registers,
+ .ts_data = &omap4460_mpu_temp_sensor_data,
+ .domain = "cpu",
+ .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_4460,
+ .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_4460,
+ .register_cooling = ti_thermal_register_cpu_cooling,
+ .unregister_cooling = ti_thermal_unregister_cpu_cooling,
+ },
+ },
+ .sensor_count = 1,
+};
+
+/* OMAP4470 data */
+const struct ti_bandgap_data omap4470_data = {
+ .features = TI_BANDGAP_FEATURE_TSHUT |
+ TI_BANDGAP_FEATURE_TSHUT_CONFIG |
+ TI_BANDGAP_FEATURE_TALERT |
+ TI_BANDGAP_FEATURE_MODE_CONFIG |
+ TI_BANDGAP_FEATURE_POWER_SWITCH |
+ TI_BANDGAP_FEATURE_CLK_CTRL |
+ TI_BANDGAP_FEATURE_COUNTER,
+ .fclock_name = "bandgap_ts_fclk",
+ .div_ck_name = "div_ts_ck",
+ .conv_table = omap4460_adc_to_temp,
+ .adc_start_val = OMAP4460_ADC_START_VALUE,
+ .adc_end_val = OMAP4460_ADC_END_VALUE,
+ .expose_sensor = ti_thermal_expose_sensor,
+ .remove_sensor = ti_thermal_remove_sensor,
+ .report_temperature = ti_thermal_report_sensor_temperature,
+ .sensors = {
+ {
+ .registers = &omap4460_mpu_temp_sensor_registers,
+ .ts_data = &omap4460_mpu_temp_sensor_data,
+ .domain = "cpu",
+ .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_4470,
+ .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_4470,
+ .register_cooling = ti_thermal_register_cpu_cooling,
+ .unregister_cooling = ti_thermal_unregister_cpu_cooling,
+ },
+ },
+ .sensor_count = 1,
+};
diff --git a/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h
new file mode 100644
index 000000000..3963f1bad
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * OMAP4xxx bandgap registers, bitfields and temperature definitions
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+#ifndef __OMAP4XXX_BANDGAP_H
+#define __OMAP4XXX_BANDGAP_H
+
+/**
+ * *** OMAP4430 ***
+ *
+ * Below, in sequence, are the Register definitions,
+ * the bitfields and the temperature definitions for OMAP4430.
+ */
+
+/**
+ * OMAP4430 register definitions
+ *
+ * Registers are defined as offsets. The offsets are
+ * relative to FUSE_OPP_BGAP on 4430.
+ */
+
+/* OMAP4430.FUSE_OPP_BGAP */
+#define OMAP4430_FUSE_OPP_BGAP 0x0
+
+/* OMAP4430.TEMP_SENSOR */
+#define OMAP4430_TEMP_SENSOR_CTRL_OFFSET 0xCC
+
+/**
+ * Register and bit definitions for OMAP4430
+ *
+ * All the macros below define the required bits for
+ * controlling temperature on OMAP4430. Bit defines are
+ * grouped by register.
+ */
+
+/* OMAP4430.TEMP_SENSOR bits */
+#define OMAP4430_BGAP_TEMPSOFF_MASK BIT(12)
+#define OMAP4430_BGAP_TSHUT_MASK BIT(11)
+#define OMAP4430_CONTINUOUS_MODE_MASK BIT(10)
+#define OMAP4430_BGAP_TEMP_SENSOR_SOC_MASK BIT(9)
+#define OMAP4430_BGAP_TEMP_SENSOR_EOCZ_MASK BIT(8)
+#define OMAP4430_BGAP_TEMP_SENSOR_DTEMP_MASK (0xff << 0)
+
+/**
+ * Temperature limits and thresholds for OMAP4430
+ *
+ * All the macros below are definitions for handling the
+ * ADC conversions and representation of temperature limits
+ * and thresholds for OMAP4430.
+ */
+
+/*
+ * ADC conversion table limits. Ignore values outside the TRM listed
+ * range to avoid bogus thermal shutdowns. See omap4430 TRM chapter
+ * "18.4.10.2.3 ADC Codes Versus Temperature".
+ */
+#define OMAP4430_ADC_START_VALUE 13
+#define OMAP4430_ADC_END_VALUE 107
+/* bandgap clock limits (no control on 4430) */
+#define OMAP4430_MAX_FREQ 32768
+#define OMAP4430_MIN_FREQ 32768
+
+/**
+ * *** OMAP4460 *** Applicable for OMAP4470
+ *
+ * Below, in sequence, are the Register definitions,
+ * the bitfields and the temperature definitions for OMAP4460.
+ */
+
+/**
+ * OMAP4460 register definitions
+ *
+ * Registers are defined as offsets. The offsets are
+ * relative to FUSE_OPP_BGAP on 4460.
+ */
+
+/* OMAP4460.FUSE_OPP_BGAP */
+#define OMAP4460_FUSE_OPP_BGAP 0x0
+
+/* OMAP4460.TEMP_SENSOR */
+#define OMAP4460_TEMP_SENSOR_CTRL_OFFSET 0xCC
+
+/* OMAP4460.BANDGAP_CTRL */
+#define OMAP4460_BGAP_CTRL_OFFSET 0x118
+
+/* OMAP4460.BANDGAP_COUNTER */
+#define OMAP4460_BGAP_COUNTER_OFFSET 0x11C
+
+/* OMAP4460.BANDGAP_THRESHOLD */
+#define OMAP4460_BGAP_THRESHOLD_OFFSET 0x120
+
+/* OMAP4460.TSHUT_THRESHOLD */
+#define OMAP4460_BGAP_TSHUT_OFFSET 0x124
+
+/* OMAP4460.BANDGAP_STATUS */
+#define OMAP4460_BGAP_STATUS_OFFSET 0x128
+
+/**
+ * Register bitfields for OMAP4460
+ *
+ * All the macros below define the required bits for
+ * controlling temperature on OMAP4460. Bit defines are
+ * grouped by register.
+ */
+/* OMAP4460.TEMP_SENSOR bits */
+#define OMAP4460_BGAP_TEMPSOFF_MASK BIT(13)
+#define OMAP4460_BGAP_TEMP_SENSOR_SOC_MASK BIT(11)
+#define OMAP4460_BGAP_TEMP_SENSOR_EOCZ_MASK BIT(10)
+#define OMAP4460_BGAP_TEMP_SENSOR_DTEMP_MASK (0x3ff << 0)
+
+/* OMAP4460.BANDGAP_CTRL bits */
+#define OMAP4460_CONTINUOUS_MODE_MASK BIT(31)
+#define OMAP4460_MASK_HOT_MASK BIT(1)
+#define OMAP4460_MASK_COLD_MASK BIT(0)
+
+/* OMAP4460.BANDGAP_COUNTER bits */
+#define OMAP4460_COUNTER_MASK (0xffffff << 0)
+
+/* OMAP4460.BANDGAP_THRESHOLD bits */
+#define OMAP4460_T_HOT_MASK (0x3ff << 16)
+#define OMAP4460_T_COLD_MASK (0x3ff << 0)
+
+/* OMAP4460.TSHUT_THRESHOLD bits */
+#define OMAP4460_TSHUT_HOT_MASK (0x3ff << 16)
+#define OMAP4460_TSHUT_COLD_MASK (0x3ff << 0)
+
+/* OMAP4460.BANDGAP_STATUS bits */
+#define OMAP4460_HOT_FLAG_MASK BIT(1)
+#define OMAP4460_COLD_FLAG_MASK BIT(0)
+
+/**
+ * Temperature limits and thresholds for OMAP4460
+ *
+ * All the macros below are definitions for handling the
+ * ADC conversions and representation of temperature limits
+ * and thresholds for OMAP4460.
+ */
+
+/* ADC conversion table limits */
+#define OMAP4460_ADC_START_VALUE 530
+#define OMAP4460_ADC_END_VALUE 932
+/* bandgap clock limits */
+#define OMAP4460_MAX_FREQ 1500000
+#define OMAP4460_MIN_FREQ 1000000
+/* interrupts thresholds */
+#define OMAP4460_TSHUT_HOT 900 /* 122 deg C */
+#define OMAP4460_TSHUT_COLD 895 /* 100 deg C */
+#define OMAP4460_T_HOT 800 /* 73 deg C */
+#define OMAP4460_T_COLD 795 /* 71 deg C */
+
+#endif /* __OMAP4XXX_BANDGAP_H */
diff --git a/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c
new file mode 100644
index 000000000..69bc89bcd
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * OMAP5 thermal driver.
+ *
+ * Copyright (C) 2011-2012 Texas Instruments Inc.
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+
+#include "ti-thermal.h"
+#include "ti-bandgap.h"
+#include "omap5xxx-bandgap.h"
+
+/*
+ * OMAP5430 has three instances of thermal sensor for MPU, GPU & CORE,
+ * need to describe the individual registers and bit fields.
+ */
+
+/*
+ * OMAP5430 MPU thermal sensor register offset and bit-fields
+ */
+static struct temp_sensor_registers
+omap5430_mpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = OMAP5430_TEMP_SENSOR_MPU_OFFSET,
+ .bgap_tempsoff_mask = OMAP5430_BGAP_TEMPSOFF_MASK,
+ .bgap_eocz_mask = OMAP5430_BGAP_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = OMAP5430_BGAP_TEMP_SENSOR_DTEMP_MASK,
+
+ .bgap_mask_ctrl = OMAP5430_BGAP_CTRL_OFFSET,
+ .mask_hot_mask = OMAP5430_MASK_HOT_MPU_MASK,
+ .mask_cold_mask = OMAP5430_MASK_COLD_MPU_MASK,
+ .mask_counter_delay_mask = OMAP5430_MASK_COUNTER_DELAY_MASK,
+ .mask_freeze_mask = OMAP5430_MASK_FREEZE_MPU_MASK,
+
+ .bgap_counter = OMAP5430_BGAP_CTRL_OFFSET,
+ .counter_mask = OMAP5430_COUNTER_MASK,
+
+ .bgap_threshold = OMAP5430_BGAP_THRESHOLD_MPU_OFFSET,
+ .threshold_thot_mask = OMAP5430_T_HOT_MASK,
+ .threshold_tcold_mask = OMAP5430_T_COLD_MASK,
+
+ .tshut_threshold = OMAP5430_BGAP_TSHUT_MPU_OFFSET,
+ .tshut_hot_mask = OMAP5430_TSHUT_HOT_MASK,
+ .tshut_cold_mask = OMAP5430_TSHUT_COLD_MASK,
+
+ .bgap_status = OMAP5430_BGAP_STATUS_OFFSET,
+ .status_hot_mask = OMAP5430_HOT_MPU_FLAG_MASK,
+ .status_cold_mask = OMAP5430_COLD_MPU_FLAG_MASK,
+
+ .ctrl_dtemp_1 = OMAP5430_BGAP_DTEMP_MPU_1_OFFSET,
+ .ctrl_dtemp_2 = OMAP5430_BGAP_DTEMP_MPU_2_OFFSET,
+ .bgap_efuse = OMAP5430_FUSE_OPP_BGAP_MPU,
+};
+
+/*
+ * OMAP5430 GPU thermal sensor register offset and bit-fields
+ */
+static struct temp_sensor_registers
+omap5430_gpu_temp_sensor_registers = {
+ .temp_sensor_ctrl = OMAP5430_TEMP_SENSOR_GPU_OFFSET,
+ .bgap_tempsoff_mask = OMAP5430_BGAP_TEMPSOFF_MASK,
+ .bgap_eocz_mask = OMAP5430_BGAP_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = OMAP5430_BGAP_TEMP_SENSOR_DTEMP_MASK,
+
+ .bgap_mask_ctrl = OMAP5430_BGAP_CTRL_OFFSET,
+ .mask_hot_mask = OMAP5430_MASK_HOT_GPU_MASK,
+ .mask_cold_mask = OMAP5430_MASK_COLD_GPU_MASK,
+ .mask_counter_delay_mask = OMAP5430_MASK_COUNTER_DELAY_MASK,
+ .mask_freeze_mask = OMAP5430_MASK_FREEZE_GPU_MASK,
+
+ .bgap_counter = OMAP5430_BGAP_CTRL_OFFSET,
+ .counter_mask = OMAP5430_COUNTER_MASK,
+
+ .bgap_threshold = OMAP5430_BGAP_THRESHOLD_GPU_OFFSET,
+ .threshold_thot_mask = OMAP5430_T_HOT_MASK,
+ .threshold_tcold_mask = OMAP5430_T_COLD_MASK,
+
+ .tshut_threshold = OMAP5430_BGAP_TSHUT_GPU_OFFSET,
+ .tshut_hot_mask = OMAP5430_TSHUT_HOT_MASK,
+ .tshut_cold_mask = OMAP5430_TSHUT_COLD_MASK,
+
+ .bgap_status = OMAP5430_BGAP_STATUS_OFFSET,
+ .status_hot_mask = OMAP5430_HOT_GPU_FLAG_MASK,
+ .status_cold_mask = OMAP5430_COLD_GPU_FLAG_MASK,
+
+ .ctrl_dtemp_1 = OMAP5430_BGAP_DTEMP_GPU_1_OFFSET,
+ .ctrl_dtemp_2 = OMAP5430_BGAP_DTEMP_GPU_2_OFFSET,
+
+ .bgap_efuse = OMAP5430_FUSE_OPP_BGAP_GPU,
+};
+
+/*
+ * OMAP5430 CORE thermal sensor register offset and bit-fields
+ */
+static struct temp_sensor_registers
+omap5430_core_temp_sensor_registers = {
+ .temp_sensor_ctrl = OMAP5430_TEMP_SENSOR_CORE_OFFSET,
+ .bgap_tempsoff_mask = OMAP5430_BGAP_TEMPSOFF_MASK,
+ .bgap_eocz_mask = OMAP5430_BGAP_TEMP_SENSOR_EOCZ_MASK,
+ .bgap_dtemp_mask = OMAP5430_BGAP_TEMP_SENSOR_DTEMP_MASK,
+
+ .bgap_mask_ctrl = OMAP5430_BGAP_CTRL_OFFSET,
+ .mask_hot_mask = OMAP5430_MASK_HOT_CORE_MASK,
+ .mask_cold_mask = OMAP5430_MASK_COLD_CORE_MASK,
+ .mask_counter_delay_mask = OMAP5430_MASK_COUNTER_DELAY_MASK,
+ .mask_freeze_mask = OMAP5430_MASK_FREEZE_CORE_MASK,
+
+ .bgap_counter = OMAP5430_BGAP_CTRL_OFFSET,
+ .counter_mask = OMAP5430_COUNTER_MASK,
+
+ .bgap_threshold = OMAP5430_BGAP_THRESHOLD_CORE_OFFSET,
+ .threshold_thot_mask = OMAP5430_T_HOT_MASK,
+ .threshold_tcold_mask = OMAP5430_T_COLD_MASK,
+
+ .tshut_threshold = OMAP5430_BGAP_TSHUT_CORE_OFFSET,
+ .tshut_hot_mask = OMAP5430_TSHUT_HOT_MASK,
+ .tshut_cold_mask = OMAP5430_TSHUT_COLD_MASK,
+
+ .bgap_status = OMAP5430_BGAP_STATUS_OFFSET,
+ .status_hot_mask = OMAP5430_HOT_CORE_FLAG_MASK,
+ .status_cold_mask = OMAP5430_COLD_CORE_FLAG_MASK,
+
+ .ctrl_dtemp_1 = OMAP5430_BGAP_DTEMP_CORE_1_OFFSET,
+ .ctrl_dtemp_2 = OMAP5430_BGAP_DTEMP_CORE_2_OFFSET,
+
+ .bgap_efuse = OMAP5430_FUSE_OPP_BGAP_CORE,
+};
+
+/* Thresholds and limits for OMAP5430 MPU temperature sensor */
+static struct temp_sensor_data omap5430_mpu_temp_sensor_data = {
+ .tshut_hot = OMAP5430_MPU_TSHUT_HOT,
+ .tshut_cold = OMAP5430_MPU_TSHUT_COLD,
+ .t_hot = OMAP5430_MPU_T_HOT,
+ .t_cold = OMAP5430_MPU_T_COLD,
+ .min_freq = OMAP5430_MPU_MIN_FREQ,
+ .max_freq = OMAP5430_MPU_MAX_FREQ,
+};
+
+/* Thresholds and limits for OMAP5430 GPU temperature sensor */
+static struct temp_sensor_data omap5430_gpu_temp_sensor_data = {
+ .tshut_hot = OMAP5430_GPU_TSHUT_HOT,
+ .tshut_cold = OMAP5430_GPU_TSHUT_COLD,
+ .t_hot = OMAP5430_GPU_T_HOT,
+ .t_cold = OMAP5430_GPU_T_COLD,
+ .min_freq = OMAP5430_GPU_MIN_FREQ,
+ .max_freq = OMAP5430_GPU_MAX_FREQ,
+};
+
+/* Thresholds and limits for OMAP5430 CORE temperature sensor */
+static struct temp_sensor_data omap5430_core_temp_sensor_data = {
+ .tshut_hot = OMAP5430_CORE_TSHUT_HOT,
+ .tshut_cold = OMAP5430_CORE_TSHUT_COLD,
+ .t_hot = OMAP5430_CORE_T_HOT,
+ .t_cold = OMAP5430_CORE_T_COLD,
+ .min_freq = OMAP5430_CORE_MIN_FREQ,
+ .max_freq = OMAP5430_CORE_MAX_FREQ,
+};
+
+/*
+ * OMAP54xx ES2.0 : Temperature values in milli degree celsius
+ * ADC code values from 540 to 945
+ */
+static int
+omap5430_adc_to_temp[
+ OMAP5430_ADC_END_VALUE - OMAP5430_ADC_START_VALUE + 1] = {
+ /* Index 540 - 549 */
+ -40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200,
+ -37800,
+ /* Index 550 - 559 */
+ -37400, -37000, -36600, -36200, -35800, -35300, -34700, -34200, -33800,
+ -33400,
+ /* Index 560 - 569 */
+ -33000, -32600, -32200, -31800, -31400, -31000, -30600, -30200, -29800,
+ -29400,
+ /* Index 570 - 579 */
+ -29000, -28600, -28200, -27700, -27100, -26600, -26200, -25800, -25400,
+ -25000,
+ /* Index 580 - 589 */
+ -24600, -24200, -23800, -23400, -23000, -22600, -22200, -21600, -21400,
+ -21000,
+ /* Index 590 - 599 */
+ -20500, -19900, -19400, -19000, -18600, -18200, -17800, -17400, -17000,
+ -16600,
+ /* Index 600 - 609 */
+ -16200, -15800, -15400, -15000, -14600, -14200, -13800, -13400, -13000,
+ -12500,
+ /* Index 610 - 619 */
+ -11900, -11400, -11000, -10600, -10200, -9800, -9400, -9000, -8600,
+ -8200,
+ /* Index 620 - 629 */
+ -7800, -7400, -7000, -6600, -6200, -5800, -5400, -5000, -4500, -3900,
+ /* Index 630 - 639 */
+ -3400, -3000, -2600, -2200, -1800, -1400, -1000, -600, -200, 200,
+ /* Index 640 - 649 */
+ 600, 1000, 1400, 1800, 2200, 2600, 3000, 3400, 3900, 4500,
+ /* Index 650 - 659 */
+ 5000, 5400, 5800, 6200, 6600, 7000, 7400, 7800, 8200, 8600,
+ /* Index 660 - 669 */
+ 9000, 9400, 9800, 10200, 10600, 11000, 11400, 11800, 12200, 12700,
+ /* Index 670 - 679 */
+ 13300, 13800, 14200, 14600, 15000, 15400, 15800, 16200, 16600, 17000,
+ /* Index 680 - 689 */
+ 17400, 17800, 18200, 18600, 19000, 19400, 19800, 20200, 20600, 21100,
+ /* Index 690 - 699 */
+ 21400, 21900, 22500, 23000, 23400, 23800, 24200, 24600, 25000, 25400,
+ /* Index 700 - 709 */
+ 25800, 26200, 26600, 27000, 27400, 27800, 28200, 28600, 29000, 29400,
+ /* Index 710 - 719 */
+ 29800, 30200, 30600, 31000, 31400, 31900, 32500, 33000, 33400, 33800,
+ /* Index 720 - 729 */
+ 34200, 34600, 35000, 35400, 35800, 36200, 36600, 37000, 37400, 37800,
+ /* Index 730 - 739 */
+ 38200, 38600, 39000, 39400, 39800, 40200, 40600, 41000, 41400, 41800,
+ /* Index 740 - 749 */
+ 42200, 42600, 43100, 43700, 44200, 44600, 45000, 45400, 45800, 46200,
+ /* Index 750 - 759 */
+ 46600, 47000, 47400, 47800, 48200, 48600, 49000, 49400, 49800, 50200,
+ /* Index 760 - 769 */
+ 50600, 51000, 51400, 51800, 52200, 52600, 53000, 53400, 53800, 54200,
+ /* Index 770 - 779 */
+ 54600, 55000, 55400, 55900, 56500, 57000, 57400, 57800, 58200, 58600,
+ /* Index 780 - 789 */
+ 59000, 59400, 59800, 60200, 60600, 61000, 61400, 61800, 62200, 62600,
+ /* Index 790 - 799 */
+ 63000, 63400, 63800, 64200, 64600, 65000, 65400, 65800, 66200, 66600,
+ /* Index 800 - 809 */
+ 67000, 67400, 67800, 68200, 68600, 69000, 69400, 69800, 70200, 70600,
+ /* Index 810 - 819 */
+ 71000, 71500, 72100, 72600, 73000, 73400, 73800, 74200, 74600, 75000,
+ /* Index 820 - 829 */
+ 75400, 75800, 76200, 76600, 77000, 77400, 77800, 78200, 78600, 79000,
+ /* Index 830 - 839 */
+ 79400, 79800, 80200, 80600, 81000, 81400, 81800, 82200, 82600, 83000,
+ /* Index 840 - 849 */
+ 83400, 83800, 84200, 84600, 85000, 85400, 85800, 86200, 86600, 87000,
+ /* Index 850 - 859 */
+ 87400, 87800, 88200, 88600, 89000, 89400, 89800, 90200, 90600, 91000,
+ /* Index 860 - 869 */
+ 91400, 91800, 92200, 92600, 93000, 93400, 93800, 94200, 94600, 95000,
+ /* Index 870 - 879 */
+ 95400, 95800, 96200, 96600, 97000, 97500, 98100, 98600, 99000, 99400,
+ /* Index 880 - 889 */
+ 99800, 100200, 100600, 101000, 101400, 101800, 102200, 102600, 103000,
+ 103400,
+ /* Index 890 - 899 */
+ 103800, 104200, 104600, 105000, 105400, 105800, 106200, 106600, 107000,
+ 107400,
+ /* Index 900 - 909 */
+ 107800, 108200, 108600, 109000, 109400, 109800, 110200, 110600, 111000,
+ 111400,
+ /* Index 910 - 919 */
+ 111800, 112200, 112600, 113000, 113400, 113800, 114200, 114600, 115000,
+ 115400,
+ /* Index 920 - 929 */
+ 115800, 116200, 116600, 117000, 117400, 117800, 118200, 118600, 119000,
+ 119400,
+ /* Index 930 - 939 */
+ 119800, 120200, 120600, 121000, 121400, 121800, 122400, 122600, 123000,
+ 123400,
+ /* Index 940 - 945 */
+ 123800, 124200, 124600, 124900, 125000, 125000,
+};
+
+/* OMAP54xx ES2.0 data */
+const struct ti_bandgap_data omap5430_data = {
+ .features = TI_BANDGAP_FEATURE_TSHUT_CONFIG |
+ TI_BANDGAP_FEATURE_FREEZE_BIT |
+ TI_BANDGAP_FEATURE_TALERT |
+ TI_BANDGAP_FEATURE_COUNTER_DELAY |
+ TI_BANDGAP_FEATURE_HISTORY_BUFFER,
+ .fclock_name = "l3instr_ts_gclk_div",
+ .div_ck_name = "l3instr_ts_gclk_div",
+ .conv_table = omap5430_adc_to_temp,
+ .adc_start_val = OMAP5430_ADC_START_VALUE,
+ .adc_end_val = OMAP5430_ADC_END_VALUE,
+ .expose_sensor = ti_thermal_expose_sensor,
+ .remove_sensor = ti_thermal_remove_sensor,
+ .report_temperature = ti_thermal_report_sensor_temperature,
+ .sensors = {
+ {
+ .registers = &omap5430_mpu_temp_sensor_registers,
+ .ts_data = &omap5430_mpu_temp_sensor_data,
+ .domain = "cpu",
+ .register_cooling = ti_thermal_register_cpu_cooling,
+ .unregister_cooling = ti_thermal_unregister_cpu_cooling,
+ .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_5430_CPU,
+ .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_5430_CPU,
+ },
+ {
+ .registers = &omap5430_gpu_temp_sensor_registers,
+ .ts_data = &omap5430_gpu_temp_sensor_data,
+ .domain = "gpu",
+ .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_5430_GPU,
+ .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_5430_GPU,
+ },
+ {
+ .registers = &omap5430_core_temp_sensor_registers,
+ .ts_data = &omap5430_core_temp_sensor_data,
+ .domain = "core",
+ },
+ },
+ .sensor_count = 3,
+};
diff --git a/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h b/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h
new file mode 100644
index 000000000..b70084b80
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * OMAP5xxx bandgap registers, bitfields and temperature definitions
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+#ifndef __OMAP5XXX_BANDGAP_H
+#define __OMAP5XXX_BANDGAP_H
+
+/**
+ * *** OMAP5430 ***
+ *
+ * Below, in sequence, are the Register definitions,
+ * the bitfields and the temperature definitions for OMAP5430.
+ */
+
+/**
+ * OMAP5430 register definitions
+ *
+ * Registers are defined as offsets. The offsets are
+ * relative to FUSE_OPP_BGAP_GPU on 5430.
+ *
+ * Register below are grouped by domain (not necessarily in offset order)
+ */
+
+/* OMAP5430.GPU register offsets */
+#define OMAP5430_FUSE_OPP_BGAP_GPU 0x0
+#define OMAP5430_TEMP_SENSOR_GPU_OFFSET 0x150
+#define OMAP5430_BGAP_THRESHOLD_GPU_OFFSET 0x1A8
+#define OMAP5430_BGAP_TSHUT_GPU_OFFSET 0x1B4
+#define OMAP5430_BGAP_DTEMP_GPU_1_OFFSET 0x1F8
+#define OMAP5430_BGAP_DTEMP_GPU_2_OFFSET 0x1FC
+
+/* OMAP5430.MPU register offsets */
+#define OMAP5430_FUSE_OPP_BGAP_MPU 0x4
+#define OMAP5430_TEMP_SENSOR_MPU_OFFSET 0x14C
+#define OMAP5430_BGAP_THRESHOLD_MPU_OFFSET 0x1A4
+#define OMAP5430_BGAP_TSHUT_MPU_OFFSET 0x1B0
+#define OMAP5430_BGAP_DTEMP_MPU_1_OFFSET 0x1E4
+#define OMAP5430_BGAP_DTEMP_MPU_2_OFFSET 0x1E8
+
+/* OMAP5430.MPU register offsets */
+#define OMAP5430_FUSE_OPP_BGAP_CORE 0x8
+#define OMAP5430_TEMP_SENSOR_CORE_OFFSET 0x154
+#define OMAP5430_BGAP_THRESHOLD_CORE_OFFSET 0x1AC
+#define OMAP5430_BGAP_TSHUT_CORE_OFFSET 0x1B8
+#define OMAP5430_BGAP_DTEMP_CORE_1_OFFSET 0x20C
+#define OMAP5430_BGAP_DTEMP_CORE_2_OFFSET 0x210
+
+/* OMAP5430.common register offsets */
+#define OMAP5430_BGAP_CTRL_OFFSET 0x1A0
+#define OMAP5430_BGAP_STATUS_OFFSET 0x1C8
+
+/**
+ * Register bitfields for OMAP5430
+ *
+ * All the macros below define the required bits for
+ * controlling temperature on OMAP5430. Bit defines are
+ * grouped by register.
+ */
+
+/* OMAP5430.TEMP_SENSOR */
+#define OMAP5430_BGAP_TEMP_SENSOR_SOC_MASK BIT(12)
+#define OMAP5430_BGAP_TEMPSOFF_MASK BIT(11)
+#define OMAP5430_BGAP_TEMP_SENSOR_EOCZ_MASK BIT(10)
+#define OMAP5430_BGAP_TEMP_SENSOR_DTEMP_MASK (0x3ff << 0)
+
+/* OMAP5430.BANDGAP_CTRL */
+#define OMAP5430_MASK_COUNTER_DELAY_MASK (0x7 << 27)
+#define OMAP5430_MASK_FREEZE_CORE_MASK BIT(23)
+#define OMAP5430_MASK_FREEZE_GPU_MASK BIT(22)
+#define OMAP5430_MASK_FREEZE_MPU_MASK BIT(21)
+#define OMAP5430_MASK_HOT_CORE_MASK BIT(5)
+#define OMAP5430_MASK_COLD_CORE_MASK BIT(4)
+#define OMAP5430_MASK_HOT_GPU_MASK BIT(3)
+#define OMAP5430_MASK_COLD_GPU_MASK BIT(2)
+#define OMAP5430_MASK_HOT_MPU_MASK BIT(1)
+#define OMAP5430_MASK_COLD_MPU_MASK BIT(0)
+
+/* OMAP5430.BANDGAP_COUNTER */
+#define OMAP5430_COUNTER_MASK (0xffffff << 0)
+
+/* OMAP5430.BANDGAP_THRESHOLD */
+#define OMAP5430_T_HOT_MASK (0x3ff << 16)
+#define OMAP5430_T_COLD_MASK (0x3ff << 0)
+
+/* OMAP5430.TSHUT_THRESHOLD */
+#define OMAP5430_TSHUT_HOT_MASK (0x3ff << 16)
+#define OMAP5430_TSHUT_COLD_MASK (0x3ff << 0)
+
+/* OMAP5430.BANDGAP_STATUS */
+#define OMAP5430_HOT_CORE_FLAG_MASK BIT(5)
+#define OMAP5430_COLD_CORE_FLAG_MASK BIT(4)
+#define OMAP5430_HOT_GPU_FLAG_MASK BIT(3)
+#define OMAP5430_COLD_GPU_FLAG_MASK BIT(2)
+#define OMAP5430_HOT_MPU_FLAG_MASK BIT(1)
+#define OMAP5430_COLD_MPU_FLAG_MASK BIT(0)
+
+/**
+ * Temperature limits and thresholds for OMAP5430
+ *
+ * All the macros below are definitions for handling the
+ * ADC conversions and representation of temperature limits
+ * and thresholds for OMAP5430. Definitions are grouped
+ * by temperature domain.
+ */
+
+/* OMAP5430.common temperature definitions */
+/* ADC conversion table limits */
+#define OMAP5430_ADC_START_VALUE 540
+#define OMAP5430_ADC_END_VALUE 945
+
+/* OMAP5430.GPU temperature definitions */
+/* bandgap clock limits */
+#define OMAP5430_GPU_MAX_FREQ 1500000
+#define OMAP5430_GPU_MIN_FREQ 1000000
+/* interrupts thresholds */
+#define OMAP5430_GPU_TSHUT_HOT 915
+#define OMAP5430_GPU_TSHUT_COLD 900
+#define OMAP5430_GPU_T_HOT 800
+#define OMAP5430_GPU_T_COLD 795
+
+/* OMAP5430.MPU temperature definitions */
+/* bandgap clock limits */
+#define OMAP5430_MPU_MAX_FREQ 1500000
+#define OMAP5430_MPU_MIN_FREQ 1000000
+/* interrupts thresholds */
+#define OMAP5430_MPU_TSHUT_HOT 915
+#define OMAP5430_MPU_TSHUT_COLD 900
+#define OMAP5430_MPU_T_HOT 800
+#define OMAP5430_MPU_T_COLD 795
+
+/* OMAP5430.CORE temperature definitions */
+/* bandgap clock limits */
+#define OMAP5430_CORE_MAX_FREQ 1500000
+#define OMAP5430_CORE_MIN_FREQ 1000000
+/* interrupts thresholds */
+#define OMAP5430_CORE_TSHUT_HOT 915
+#define OMAP5430_CORE_TSHUT_COLD 900
+#define OMAP5430_CORE_T_HOT 800
+#define OMAP5430_CORE_T_COLD 795
+
+#endif /* __OMAP5XXX_BANDGAP_H */
diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c
new file mode 100644
index 000000000..ba43399d0
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c
@@ -0,0 +1,1297 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TI Bandgap temperature sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: J Keerthy <j-keerthy@ti.com>
+ * Author: Moiz Sonasath <m-sonasath@ti.com>
+ * Couple of fixes, DT and MFD adaptation:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/cpu_pm.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/reboot.h>
+#include <linux/spinlock.h>
+#include <linux/sys_soc.h>
+#include <linux/types.h>
+
+#include "ti-bandgap.h"
+
+static int ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id);
+#ifdef CONFIG_PM_SLEEP
+static int bandgap_omap_cpu_notifier(struct notifier_block *nb,
+ unsigned long cmd, void *v);
+#endif
+
+/*** Helper functions to access registers and their bitfields ***/
+
+/**
+ * ti_bandgap_readl() - simple read helper function
+ * @bgp: pointer to ti_bandgap structure
+ * @reg: desired register (offset) to be read
+ *
+ * Helper function to read bandgap registers. It uses the io remapped area.
+ * Return: the register value.
+ */
+static u32 ti_bandgap_readl(struct ti_bandgap *bgp, u32 reg)
+{
+ return readl(bgp->base + reg);
+}
+
+/**
+ * ti_bandgap_writel() - simple write helper function
+ * @bgp: pointer to ti_bandgap structure
+ * @val: desired register value to be written
+ * @reg: desired register (offset) to be written
+ *
+ * Helper function to write bandgap registers. It uses the io remapped area.
+ */
+static void ti_bandgap_writel(struct ti_bandgap *bgp, u32 val, u32 reg)
+{
+ writel(val, bgp->base + reg);
+}
+
+/**
+ * DOC: macro to update bits.
+ *
+ * RMW_BITS() - used to read, modify and update bandgap bitfields.
+ * The value passed will be shifted.
+ */
+#define RMW_BITS(bgp, id, reg, mask, val) \
+do { \
+ struct temp_sensor_registers *t; \
+ u32 r; \
+ \
+ t = bgp->conf->sensors[(id)].registers; \
+ r = ti_bandgap_readl(bgp, t->reg); \
+ r &= ~t->mask; \
+ r |= (val) << __ffs(t->mask); \
+ ti_bandgap_writel(bgp, r, t->reg); \
+} while (0)
+
+/*** Basic helper functions ***/
+
+/**
+ * ti_bandgap_power() - controls the power state of a bandgap device
+ * @bgp: pointer to ti_bandgap structure
+ * @on: desired power state (1 - on, 0 - off)
+ *
+ * Used to power on/off a bandgap device instance. Only used on those
+ * that features tempsoff bit.
+ *
+ * Return: 0 on success, -ENOTSUPP if tempsoff is not supported.
+ */
+static int ti_bandgap_power(struct ti_bandgap *bgp, bool on)
+{
+ int i;
+
+ if (!TI_BANDGAP_HAS(bgp, POWER_SWITCH))
+ return -ENOTSUPP;
+
+ for (i = 0; i < bgp->conf->sensor_count; i++)
+ /* active on 0 */
+ RMW_BITS(bgp, i, temp_sensor_ctrl, bgap_tempsoff_mask, !on);
+ return 0;
+}
+
+/**
+ * ti_errata814_bandgap_read_temp() - helper function to read dra7 sensor temperature
+ * @bgp: pointer to ti_bandgap structure
+ * @reg: desired register (offset) to be read
+ *
+ * Function to read dra7 bandgap sensor temperature. This is done separately
+ * so as to workaround the errata "Bandgap Temperature read Dtemp can be
+ * corrupted" - Errata ID: i814".
+ * Read accesses to registers listed below can be corrupted due to incorrect
+ * resynchronization between clock domains.
+ * Read access to registers below can be corrupted :
+ * CTRL_CORE_DTEMP_MPU/GPU/CORE/DSPEVE/IVA_n (n = 0 to 4)
+ * CTRL_CORE_TEMP_SENSOR_MPU/GPU/CORE/DSPEVE/IVA_n
+ *
+ * Return: the register value.
+ */
+static u32 ti_errata814_bandgap_read_temp(struct ti_bandgap *bgp, u32 reg)
+{
+ u32 val1, val2;
+
+ val1 = ti_bandgap_readl(bgp, reg);
+ val2 = ti_bandgap_readl(bgp, reg);
+
+ /* If both times we read the same value then that is right */
+ if (val1 == val2)
+ return val1;
+
+ /* if val1 and val2 are different read it third time */
+ return ti_bandgap_readl(bgp, reg);
+}
+
+/**
+ * ti_bandgap_read_temp() - helper function to read sensor temperature
+ * @bgp: pointer to ti_bandgap structure
+ * @id: bandgap sensor id
+ *
+ * Function to concentrate the steps to read sensor temperature register.
+ * This function is desired because, depending on bandgap device version,
+ * it might be needed to freeze the bandgap state machine, before fetching
+ * the register value.
+ *
+ * Return: temperature in ADC values.
+ */
+static u32 ti_bandgap_read_temp(struct ti_bandgap *bgp, int id)
+{
+ struct temp_sensor_registers *tsr;
+ u32 temp, reg;
+
+ tsr = bgp->conf->sensors[id].registers;
+ reg = tsr->temp_sensor_ctrl;
+
+ if (TI_BANDGAP_HAS(bgp, FREEZE_BIT)) {
+ RMW_BITS(bgp, id, bgap_mask_ctrl, mask_freeze_mask, 1);
+ /*
+ * In case we cannot read from cur_dtemp / dtemp_0,
+ * then we read from the last valid temp read
+ */
+ reg = tsr->ctrl_dtemp_1;
+ }
+
+ /* read temperature */
+ if (TI_BANDGAP_HAS(bgp, ERRATA_814))
+ temp = ti_errata814_bandgap_read_temp(bgp, reg);
+ else
+ temp = ti_bandgap_readl(bgp, reg);
+
+ temp &= tsr->bgap_dtemp_mask;
+
+ if (TI_BANDGAP_HAS(bgp, FREEZE_BIT))
+ RMW_BITS(bgp, id, bgap_mask_ctrl, mask_freeze_mask, 0);
+
+ return temp;
+}
+
+/*** IRQ handlers ***/
+
+/**
+ * ti_bandgap_talert_irq_handler() - handles Temperature alert IRQs
+ * @irq: IRQ number
+ * @data: private data (struct ti_bandgap *)
+ *
+ * This is the Talert handler. Use it only if bandgap device features
+ * HAS(TALERT). This handler goes over all sensors and checks their
+ * conditions and acts accordingly. In case there are events pending,
+ * it will reset the event mask to wait for the opposite event (next event).
+ * Every time there is a new event, it will be reported to thermal layer.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t ti_bandgap_talert_irq_handler(int irq, void *data)
+{
+ struct ti_bandgap *bgp = data;
+ struct temp_sensor_registers *tsr;
+ u32 t_hot = 0, t_cold = 0, ctrl;
+ int i;
+
+ spin_lock(&bgp->lock);
+ for (i = 0; i < bgp->conf->sensor_count; i++) {
+ tsr = bgp->conf->sensors[i].registers;
+ ctrl = ti_bandgap_readl(bgp, tsr->bgap_status);
+
+ /* Read the status of t_hot */
+ t_hot = ctrl & tsr->status_hot_mask;
+
+ /* Read the status of t_cold */
+ t_cold = ctrl & tsr->status_cold_mask;
+
+ if (!t_cold && !t_hot)
+ continue;
+
+ ctrl = ti_bandgap_readl(bgp, tsr->bgap_mask_ctrl);
+ /*
+ * One TALERT interrupt: Two sources
+ * If the interrupt is due to t_hot then mask t_hot and
+ * unmask t_cold else mask t_cold and unmask t_hot
+ */
+ if (t_hot) {
+ ctrl &= ~tsr->mask_hot_mask;
+ ctrl |= tsr->mask_cold_mask;
+ } else if (t_cold) {
+ ctrl &= ~tsr->mask_cold_mask;
+ ctrl |= tsr->mask_hot_mask;
+ }
+
+ ti_bandgap_writel(bgp, ctrl, tsr->bgap_mask_ctrl);
+
+ dev_dbg(bgp->dev,
+ "%s: IRQ from %s sensor: hotevent %d coldevent %d\n",
+ __func__, bgp->conf->sensors[i].domain,
+ t_hot, t_cold);
+
+ /* report temperature to whom may concern */
+ if (bgp->conf->report_temperature)
+ bgp->conf->report_temperature(bgp, i);
+ }
+ spin_unlock(&bgp->lock);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ti_bandgap_tshut_irq_handler() - handles Temperature shutdown signal
+ * @irq: IRQ number
+ * @data: private data (unused)
+ *
+ * This is the Tshut handler. Use it only if bandgap device features
+ * HAS(TSHUT). If any sensor fires the Tshut signal, we simply shutdown
+ * the system.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t ti_bandgap_tshut_irq_handler(int irq, void *data)
+{
+ pr_emerg("%s: TSHUT temperature reached. Needs shut down...\n",
+ __func__);
+
+ orderly_poweroff(true);
+
+ return IRQ_HANDLED;
+}
+
+/*** Helper functions which manipulate conversion ADC <-> mi Celsius ***/
+
+/**
+ * ti_bandgap_adc_to_mcelsius() - converts an ADC value to mCelsius scale
+ * @bgp: struct ti_bandgap pointer
+ * @adc_val: value in ADC representation
+ * @t: address where to write the resulting temperature in mCelsius
+ *
+ * Simple conversion from ADC representation to mCelsius. In case the ADC value
+ * is out of the ADC conv table range, it returns -ERANGE, 0 on success.
+ * The conversion table is indexed by the ADC values.
+ *
+ * Return: 0 if conversion was successful, else -ERANGE in case the @adc_val
+ * argument is out of the ADC conv table range.
+ */
+static
+int ti_bandgap_adc_to_mcelsius(struct ti_bandgap *bgp, int adc_val, int *t)
+{
+ const struct ti_bandgap_data *conf = bgp->conf;
+
+ /* look up for temperature in the table and return the temperature */
+ if (adc_val < conf->adc_start_val || adc_val > conf->adc_end_val)
+ return -ERANGE;
+
+ *t = bgp->conf->conv_table[adc_val - conf->adc_start_val];
+ return 0;
+}
+
+/**
+ * ti_bandgap_validate() - helper to check the sanity of a struct ti_bandgap
+ * @bgp: struct ti_bandgap pointer
+ * @id: bandgap sensor id
+ *
+ * Checks if the bandgap pointer is valid and if the sensor id is also
+ * applicable.
+ *
+ * Return: 0 if no errors, -EINVAL for invalid @bgp pointer or -ERANGE if
+ * @id cannot index @bgp sensors.
+ */
+static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id)
+{
+ if (IS_ERR_OR_NULL(bgp)) {
+ pr_err("%s: invalid bandgap pointer\n", __func__);
+ return -EINVAL;
+ }
+
+ if ((id < 0) || (id >= bgp->conf->sensor_count)) {
+ dev_err(bgp->dev, "%s: sensor id out of range (%d)\n",
+ __func__, id);
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+/**
+ * ti_bandgap_read_counter() - read the sensor counter
+ * @bgp: pointer to bandgap instance
+ * @id: sensor id
+ * @interval: resulting update interval in miliseconds
+ */
+static void ti_bandgap_read_counter(struct ti_bandgap *bgp, int id,
+ int *interval)
+{
+ struct temp_sensor_registers *tsr;
+ int time;
+
+ tsr = bgp->conf->sensors[id].registers;
+ time = ti_bandgap_readl(bgp, tsr->bgap_counter);
+ time = (time & tsr->counter_mask) >>
+ __ffs(tsr->counter_mask);
+ time = time * 1000 / bgp->clk_rate;
+ *interval = time;
+}
+
+/**
+ * ti_bandgap_read_counter_delay() - read the sensor counter delay
+ * @bgp: pointer to bandgap instance
+ * @id: sensor id
+ * @interval: resulting update interval in miliseconds
+ */
+static void ti_bandgap_read_counter_delay(struct ti_bandgap *bgp, int id,
+ int *interval)
+{
+ struct temp_sensor_registers *tsr;
+ int reg_val;
+
+ tsr = bgp->conf->sensors[id].registers;
+
+ reg_val = ti_bandgap_readl(bgp, tsr->bgap_mask_ctrl);
+ reg_val = (reg_val & tsr->mask_counter_delay_mask) >>
+ __ffs(tsr->mask_counter_delay_mask);
+ switch (reg_val) {
+ case 0:
+ *interval = 0;
+ break;
+ case 1:
+ *interval = 1;
+ break;
+ case 2:
+ *interval = 10;
+ break;
+ case 3:
+ *interval = 100;
+ break;
+ case 4:
+ *interval = 250;
+ break;
+ case 5:
+ *interval = 500;
+ break;
+ default:
+ dev_warn(bgp->dev, "Wrong counter delay value read from register %X",
+ reg_val);
+ }
+}
+
+/**
+ * ti_bandgap_read_update_interval() - read the sensor update interval
+ * @bgp: pointer to bandgap instance
+ * @id: sensor id
+ * @interval: resulting update interval in miliseconds
+ *
+ * Return: 0 on success or the proper error code
+ */
+int ti_bandgap_read_update_interval(struct ti_bandgap *bgp, int id,
+ int *interval)
+{
+ int ret = 0;
+
+ ret = ti_bandgap_validate(bgp, id);
+ if (ret)
+ goto exit;
+
+ if (!TI_BANDGAP_HAS(bgp, COUNTER) &&
+ !TI_BANDGAP_HAS(bgp, COUNTER_DELAY)) {
+ ret = -ENOTSUPP;
+ goto exit;
+ }
+
+ if (TI_BANDGAP_HAS(bgp, COUNTER)) {
+ ti_bandgap_read_counter(bgp, id, interval);
+ goto exit;
+ }
+
+ ti_bandgap_read_counter_delay(bgp, id, interval);
+exit:
+ return ret;
+}
+
+/**
+ * ti_bandgap_write_counter_delay() - set the counter_delay
+ * @bgp: pointer to bandgap instance
+ * @id: sensor id
+ * @interval: desired update interval in miliseconds
+ *
+ * Return: 0 on success or the proper error code
+ */
+static int ti_bandgap_write_counter_delay(struct ti_bandgap *bgp, int id,
+ u32 interval)
+{
+ int rval;
+
+ switch (interval) {
+ case 0: /* Immediate conversion */
+ rval = 0x0;
+ break;
+ case 1: /* Conversion after ever 1ms */
+ rval = 0x1;
+ break;
+ case 10: /* Conversion after ever 10ms */
+ rval = 0x2;
+ break;
+ case 100: /* Conversion after ever 100ms */
+ rval = 0x3;
+ break;
+ case 250: /* Conversion after ever 250ms */
+ rval = 0x4;
+ break;
+ case 500: /* Conversion after ever 500ms */
+ rval = 0x5;
+ break;
+ default:
+ dev_warn(bgp->dev, "Delay %d ms is not supported\n", interval);
+ return -EINVAL;
+ }
+
+ spin_lock(&bgp->lock);
+ RMW_BITS(bgp, id, bgap_mask_ctrl, mask_counter_delay_mask, rval);
+ spin_unlock(&bgp->lock);
+
+ return 0;
+}
+
+/**
+ * ti_bandgap_write_counter() - set the bandgap sensor counter
+ * @bgp: pointer to bandgap instance
+ * @id: sensor id
+ * @interval: desired update interval in miliseconds
+ */
+static void ti_bandgap_write_counter(struct ti_bandgap *bgp, int id,
+ u32 interval)
+{
+ interval = interval * bgp->clk_rate / 1000;
+ spin_lock(&bgp->lock);
+ RMW_BITS(bgp, id, bgap_counter, counter_mask, interval);
+ spin_unlock(&bgp->lock);
+}
+
+/**
+ * ti_bandgap_write_update_interval() - set the update interval
+ * @bgp: pointer to bandgap instance
+ * @id: sensor id
+ * @interval: desired update interval in miliseconds
+ *
+ * Return: 0 on success or the proper error code
+ */
+int ti_bandgap_write_update_interval(struct ti_bandgap *bgp,
+ int id, u32 interval)
+{
+ int ret = ti_bandgap_validate(bgp, id);
+ if (ret)
+ goto exit;
+
+ if (!TI_BANDGAP_HAS(bgp, COUNTER) &&
+ !TI_BANDGAP_HAS(bgp, COUNTER_DELAY)) {
+ ret = -ENOTSUPP;
+ goto exit;
+ }
+
+ if (TI_BANDGAP_HAS(bgp, COUNTER)) {
+ ti_bandgap_write_counter(bgp, id, interval);
+ goto exit;
+ }
+
+ ret = ti_bandgap_write_counter_delay(bgp, id, interval);
+exit:
+ return ret;
+}
+
+/**
+ * ti_bandgap_read_temperature() - report current temperature
+ * @bgp: pointer to bandgap instance
+ * @id: sensor id
+ * @temperature: resulting temperature
+ *
+ * Return: 0 on success or the proper error code
+ */
+int ti_bandgap_read_temperature(struct ti_bandgap *bgp, int id,
+ int *temperature)
+{
+ u32 temp;
+ int ret;
+
+ ret = ti_bandgap_validate(bgp, id);
+ if (ret)
+ return ret;
+
+ if (!TI_BANDGAP_HAS(bgp, MODE_CONFIG)) {
+ ret = ti_bandgap_force_single_read(bgp, id);
+ if (ret)
+ return ret;
+ }
+
+ spin_lock(&bgp->lock);
+ temp = ti_bandgap_read_temp(bgp, id);
+ spin_unlock(&bgp->lock);
+
+ ret = ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
+ if (ret)
+ return -EIO;
+
+ *temperature = temp;
+
+ return 0;
+}
+
+/**
+ * ti_bandgap_set_sensor_data() - helper function to store thermal
+ * framework related data.
+ * @bgp: pointer to bandgap instance
+ * @id: sensor id
+ * @data: thermal framework related data to be stored
+ *
+ * Return: 0 on success or the proper error code
+ */
+int ti_bandgap_set_sensor_data(struct ti_bandgap *bgp, int id, void *data)
+{
+ int ret = ti_bandgap_validate(bgp, id);
+ if (ret)
+ return ret;
+
+ bgp->regval[id].data = data;
+
+ return 0;
+}
+
+/**
+ * ti_bandgap_get_sensor_data() - helper function to get thermal
+ * framework related data.
+ * @bgp: pointer to bandgap instance
+ * @id: sensor id
+ *
+ * Return: data stored by set function with sensor id on success or NULL
+ */
+void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id)
+{
+ int ret = ti_bandgap_validate(bgp, id);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return bgp->regval[id].data;
+}
+
+/*** Helper functions used during device initialization ***/
+
+/**
+ * ti_bandgap_force_single_read() - executes 1 single ADC conversion
+ * @bgp: pointer to struct ti_bandgap
+ * @id: sensor id which it is desired to read 1 temperature
+ *
+ * Used to initialize the conversion state machine and set it to a valid
+ * state. Called during device initialization and context restore events.
+ *
+ * Return: 0
+ */
+static int
+ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id)
+{
+ struct temp_sensor_registers *tsr = bgp->conf->sensors[id].registers;
+ void __iomem *temp_sensor_ctrl = bgp->base + tsr->temp_sensor_ctrl;
+ int error;
+ u32 val;
+
+ /* Select continuous or single conversion mode */
+ if (TI_BANDGAP_HAS(bgp, MODE_CONFIG)) {
+ if (TI_BANDGAP_HAS(bgp, CONT_MODE_ONLY))
+ RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 1);
+ else
+ RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 0);
+ }
+
+ /* Set Start of Conversion if available */
+ if (tsr->bgap_soc_mask) {
+ RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 1);
+
+ /* Wait for EOCZ going up */
+ error = readl_poll_timeout_atomic(temp_sensor_ctrl, val,
+ val & tsr->bgap_eocz_mask,
+ 1, 1000);
+ if (error)
+ dev_warn(bgp->dev, "eocz timed out waiting high\n");
+
+ /* Clear Start of Conversion if available */
+ RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 0);
+ }
+
+ /* Wait for EOCZ going down, always needed even if no bgap_soc_mask */
+ error = readl_poll_timeout_atomic(temp_sensor_ctrl, val,
+ !(val & tsr->bgap_eocz_mask),
+ 1, 1500);
+ if (error)
+ dev_warn(bgp->dev, "eocz timed out waiting low\n");
+
+ return 0;
+}
+
+/**
+ * ti_bandgap_set_continuous_mode() - One time enabling of continuous mode
+ * @bgp: pointer to struct ti_bandgap
+ *
+ * Call this function only if HAS(MODE_CONFIG) is set. As this driver may
+ * be used for junction temperature monitoring, it is desirable that the
+ * sensors are operational all the time, so that alerts are generated
+ * properly.
+ *
+ * Return: 0
+ */
+static int ti_bandgap_set_continuous_mode(struct ti_bandgap *bgp)
+{
+ int i;
+
+ for (i = 0; i < bgp->conf->sensor_count; i++) {
+ /* Perform a single read just before enabling continuous */
+ ti_bandgap_force_single_read(bgp, i);
+ RMW_BITS(bgp, i, bgap_mode_ctrl, mode_ctrl_mask, 1);
+ }
+
+ return 0;
+}
+
+/**
+ * ti_bandgap_get_trend() - To fetch the temperature trend of a sensor
+ * @bgp: pointer to struct ti_bandgap
+ * @id: id of the individual sensor
+ * @trend: Pointer to trend.
+ *
+ * This function needs to be called to fetch the temperature trend of a
+ * Particular sensor. The function computes the difference in temperature
+ * w.r.t time. For the bandgaps with built in history buffer the temperatures
+ * are read from the buffer and for those without the Buffer -ENOTSUPP is
+ * returned.
+ *
+ * Return: 0 if no error, else return corresponding error. If no
+ * error then the trend value is passed on to trend parameter
+ */
+int ti_bandgap_get_trend(struct ti_bandgap *bgp, int id, int *trend)
+{
+ struct temp_sensor_registers *tsr;
+ u32 temp1, temp2, reg1, reg2;
+ int t1, t2, interval, ret = 0;
+
+ ret = ti_bandgap_validate(bgp, id);
+ if (ret)
+ goto exit;
+
+ if (!TI_BANDGAP_HAS(bgp, HISTORY_BUFFER) ||
+ !TI_BANDGAP_HAS(bgp, FREEZE_BIT)) {
+ ret = -ENOTSUPP;
+ goto exit;
+ }
+
+ spin_lock(&bgp->lock);
+
+ tsr = bgp->conf->sensors[id].registers;
+
+ /* Freeze and read the last 2 valid readings */
+ RMW_BITS(bgp, id, bgap_mask_ctrl, mask_freeze_mask, 1);
+ reg1 = tsr->ctrl_dtemp_1;
+ reg2 = tsr->ctrl_dtemp_2;
+
+ /* read temperature from history buffer */
+ temp1 = ti_bandgap_readl(bgp, reg1);
+ temp1 &= tsr->bgap_dtemp_mask;
+
+ temp2 = ti_bandgap_readl(bgp, reg2);
+ temp2 &= tsr->bgap_dtemp_mask;
+
+ /* Convert from adc values to mCelsius temperature */
+ ret = ti_bandgap_adc_to_mcelsius(bgp, temp1, &t1);
+ if (ret)
+ goto unfreeze;
+
+ ret = ti_bandgap_adc_to_mcelsius(bgp, temp2, &t2);
+ if (ret)
+ goto unfreeze;
+
+ /* Fetch the update interval */
+ ret = ti_bandgap_read_update_interval(bgp, id, &interval);
+ if (ret)
+ goto unfreeze;
+
+ /* Set the interval to 1 ms if bandgap counter delay is not set */
+ if (interval == 0)
+ interval = 1;
+
+ *trend = (t1 - t2) / interval;
+
+ dev_dbg(bgp->dev, "The temperatures are t1 = %d and t2 = %d and trend =%d\n",
+ t1, t2, *trend);
+
+unfreeze:
+ RMW_BITS(bgp, id, bgap_mask_ctrl, mask_freeze_mask, 0);
+ spin_unlock(&bgp->lock);
+exit:
+ return ret;
+}
+
+/**
+ * ti_bandgap_tshut_init() - setup and initialize tshut handling
+ * @bgp: pointer to struct ti_bandgap
+ * @pdev: pointer to device struct platform_device
+ *
+ * Call this function only in case the bandgap features HAS(TSHUT).
+ * In this case, the driver needs to handle the TSHUT signal as an IRQ.
+ * The IRQ is wired as a GPIO, and for this purpose, it is required
+ * to specify which GPIO line is used. TSHUT IRQ is fired anytime
+ * one of the bandgap sensors violates the TSHUT high/hot threshold.
+ * And in that case, the system must go off.
+ *
+ * Return: 0 if no error, else error status
+ */
+static int ti_bandgap_tshut_init(struct ti_bandgap *bgp,
+ struct platform_device *pdev)
+{
+ int status;
+
+ status = request_irq(gpiod_to_irq(bgp->tshut_gpiod),
+ ti_bandgap_tshut_irq_handler,
+ IRQF_TRIGGER_RISING, "tshut", NULL);
+ if (status)
+ dev_err(bgp->dev, "request irq failed for TSHUT");
+
+ return 0;
+}
+
+/**
+ * ti_bandgap_talert_init() - setup and initialize talert handling
+ * @bgp: pointer to struct ti_bandgap
+ * @pdev: pointer to device struct platform_device
+ *
+ * Call this function only in case the bandgap features HAS(TALERT).
+ * In this case, the driver needs to handle the TALERT signals as an IRQs.
+ * TALERT is a normal IRQ and it is fired any time thresholds (hot or cold)
+ * are violated. In these situation, the driver must reprogram the thresholds,
+ * accordingly to specified policy.
+ *
+ * Return: 0 if no error, else return corresponding error.
+ */
+static int ti_bandgap_talert_init(struct ti_bandgap *bgp,
+ struct platform_device *pdev)
+{
+ int ret;
+
+ bgp->irq = platform_get_irq(pdev, 0);
+ if (bgp->irq < 0)
+ return bgp->irq;
+
+ ret = request_threaded_irq(bgp->irq, NULL,
+ ti_bandgap_talert_irq_handler,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+ "talert", bgp);
+ if (ret) {
+ dev_err(&pdev->dev, "Request threaded irq failed.\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_ti_bandgap_match[];
+/**
+ * ti_bandgap_build() - parse DT and setup a struct ti_bandgap
+ * @pdev: pointer to device struct platform_device
+ *
+ * Used to read the device tree properties accordingly to the bandgap
+ * matching version. Based on bandgap version and its capabilities it
+ * will build a struct ti_bandgap out of the required DT entries.
+ *
+ * Return: valid bandgap structure if successful, else returns ERR_PTR
+ * return value must be verified with IS_ERR.
+ */
+static struct ti_bandgap *ti_bandgap_build(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ const struct of_device_id *of_id;
+ struct ti_bandgap *bgp;
+ struct resource *res;
+ int i;
+
+ /* just for the sake */
+ if (!node) {
+ dev_err(&pdev->dev, "no platform information available\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL);
+ if (!bgp)
+ return ERR_PTR(-ENOMEM);
+
+ of_id = of_match_device(of_ti_bandgap_match, &pdev->dev);
+ if (of_id)
+ bgp->conf = of_id->data;
+
+ /* register shadow for context save and restore */
+ bgp->regval = devm_kcalloc(&pdev->dev, bgp->conf->sensor_count,
+ sizeof(*bgp->regval), GFP_KERNEL);
+ if (!bgp->regval)
+ return ERR_PTR(-ENOMEM);
+
+ i = 0;
+ do {
+ void __iomem *chunk;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ if (!res)
+ break;
+ chunk = devm_ioremap_resource(&pdev->dev, res);
+ if (i == 0)
+ bgp->base = chunk;
+ if (IS_ERR(chunk))
+ return ERR_CAST(chunk);
+
+ i++;
+ } while (res);
+
+ if (TI_BANDGAP_HAS(bgp, TSHUT)) {
+ bgp->tshut_gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_IN);
+ if (IS_ERR(bgp->tshut_gpiod)) {
+ dev_err(&pdev->dev, "invalid gpio for tshut\n");
+ return ERR_CAST(bgp->tshut_gpiod);
+ }
+ }
+
+ return bgp;
+}
+
+/*
+ * List of SoCs on which the CPU PM notifier can cause erros on the DTEMP
+ * readout.
+ * Enabled notifier on these machines results in erroneous, random values which
+ * could trigger unexpected thermal shutdown.
+ */
+static const struct soc_device_attribute soc_no_cpu_notifier[] = {
+ { .machine = "OMAP4430" },
+ { /* sentinel */ }
+};
+
+/*** Device driver call backs ***/
+
+static
+int ti_bandgap_probe(struct platform_device *pdev)
+{
+ struct ti_bandgap *bgp;
+ int clk_rate, ret, i;
+
+ bgp = ti_bandgap_build(pdev);
+ if (IS_ERR(bgp)) {
+ dev_err(&pdev->dev, "failed to fetch platform data\n");
+ return PTR_ERR(bgp);
+ }
+ bgp->dev = &pdev->dev;
+
+ if (TI_BANDGAP_HAS(bgp, UNRELIABLE))
+ dev_warn(&pdev->dev,
+ "This OMAP thermal sensor is unreliable. You've been warned\n");
+
+ if (TI_BANDGAP_HAS(bgp, TSHUT)) {
+ ret = ti_bandgap_tshut_init(bgp, pdev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to initialize system tshut IRQ\n");
+ return ret;
+ }
+ }
+
+ bgp->fclock = clk_get(NULL, bgp->conf->fclock_name);
+ if (IS_ERR(bgp->fclock)) {
+ dev_err(&pdev->dev, "failed to request fclock reference\n");
+ ret = PTR_ERR(bgp->fclock);
+ goto free_irqs;
+ }
+
+ bgp->div_clk = clk_get(NULL, bgp->conf->div_ck_name);
+ if (IS_ERR(bgp->div_clk)) {
+ dev_err(&pdev->dev, "failed to request div_ts_ck clock ref\n");
+ ret = PTR_ERR(bgp->div_clk);
+ goto put_fclock;
+ }
+
+ for (i = 0; i < bgp->conf->sensor_count; i++) {
+ struct temp_sensor_registers *tsr;
+ u32 val;
+
+ tsr = bgp->conf->sensors[i].registers;
+ /*
+ * check if the efuse has a non-zero value if not
+ * it is an untrimmed sample and the temperatures
+ * may not be accurate
+ */
+ val = ti_bandgap_readl(bgp, tsr->bgap_efuse);
+ if (!val)
+ dev_info(&pdev->dev,
+ "Non-trimmed BGAP, Temp not accurate\n");
+ }
+
+ clk_rate = clk_round_rate(bgp->div_clk,
+ bgp->conf->sensors[0].ts_data->max_freq);
+ if (clk_rate < bgp->conf->sensors[0].ts_data->min_freq ||
+ clk_rate <= 0) {
+ ret = -ENODEV;
+ dev_err(&pdev->dev, "wrong clock rate (%d)\n", clk_rate);
+ goto put_clks;
+ }
+
+ ret = clk_set_rate(bgp->div_clk, clk_rate);
+ if (ret)
+ dev_err(&pdev->dev, "Cannot re-set clock rate. Continuing\n");
+
+ bgp->clk_rate = clk_rate;
+ if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
+ clk_prepare_enable(bgp->fclock);
+
+
+ spin_lock_init(&bgp->lock);
+ bgp->dev = &pdev->dev;
+ platform_set_drvdata(pdev, bgp);
+
+ ti_bandgap_power(bgp, true);
+
+ /* Set default counter to 1 for now */
+ if (TI_BANDGAP_HAS(bgp, COUNTER))
+ for (i = 0; i < bgp->conf->sensor_count; i++)
+ RMW_BITS(bgp, i, bgap_counter, counter_mask, 1);
+
+ /* Set default thresholds for alert and shutdown */
+ for (i = 0; i < bgp->conf->sensor_count; i++) {
+ struct temp_sensor_data *ts_data;
+
+ ts_data = bgp->conf->sensors[i].ts_data;
+
+ if (TI_BANDGAP_HAS(bgp, TALERT)) {
+ /* Set initial Talert thresholds */
+ RMW_BITS(bgp, i, bgap_threshold,
+ threshold_tcold_mask, ts_data->t_cold);
+ RMW_BITS(bgp, i, bgap_threshold,
+ threshold_thot_mask, ts_data->t_hot);
+ /* Enable the alert events */
+ RMW_BITS(bgp, i, bgap_mask_ctrl, mask_hot_mask, 1);
+ RMW_BITS(bgp, i, bgap_mask_ctrl, mask_cold_mask, 1);
+ }
+
+ if (TI_BANDGAP_HAS(bgp, TSHUT_CONFIG)) {
+ /* Set initial Tshut thresholds */
+ RMW_BITS(bgp, i, tshut_threshold,
+ tshut_hot_mask, ts_data->tshut_hot);
+ RMW_BITS(bgp, i, tshut_threshold,
+ tshut_cold_mask, ts_data->tshut_cold);
+ }
+ }
+
+ if (TI_BANDGAP_HAS(bgp, MODE_CONFIG))
+ ti_bandgap_set_continuous_mode(bgp);
+
+ /* Set .250 seconds time as default counter */
+ if (TI_BANDGAP_HAS(bgp, COUNTER))
+ for (i = 0; i < bgp->conf->sensor_count; i++)
+ RMW_BITS(bgp, i, bgap_counter, counter_mask,
+ bgp->clk_rate / 4);
+
+ /* Every thing is good? Then expose the sensors */
+ for (i = 0; i < bgp->conf->sensor_count; i++) {
+ char *domain;
+
+ if (bgp->conf->sensors[i].register_cooling) {
+ ret = bgp->conf->sensors[i].register_cooling(bgp, i);
+ if (ret)
+ goto remove_sensors;
+ }
+
+ if (bgp->conf->expose_sensor) {
+ domain = bgp->conf->sensors[i].domain;
+ ret = bgp->conf->expose_sensor(bgp, i, domain);
+ if (ret)
+ goto remove_last_cooling;
+ }
+ }
+
+ /*
+ * Enable the Interrupts once everything is set. Otherwise irq handler
+ * might be called as soon as it is enabled where as rest of framework
+ * is still getting initialised.
+ */
+ if (TI_BANDGAP_HAS(bgp, TALERT)) {
+ ret = ti_bandgap_talert_init(bgp, pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to initialize Talert IRQ\n");
+ i = bgp->conf->sensor_count;
+ goto disable_clk;
+ }
+ }
+
+#ifdef CONFIG_PM_SLEEP
+ bgp->nb.notifier_call = bandgap_omap_cpu_notifier;
+ if (!soc_device_match(soc_no_cpu_notifier))
+ cpu_pm_register_notifier(&bgp->nb);
+#endif
+
+ return 0;
+
+remove_last_cooling:
+ if (bgp->conf->sensors[i].unregister_cooling)
+ bgp->conf->sensors[i].unregister_cooling(bgp, i);
+remove_sensors:
+ for (i--; i >= 0; i--) {
+ if (bgp->conf->sensors[i].unregister_cooling)
+ bgp->conf->sensors[i].unregister_cooling(bgp, i);
+ if (bgp->conf->remove_sensor)
+ bgp->conf->remove_sensor(bgp, i);
+ }
+ ti_bandgap_power(bgp, false);
+disable_clk:
+ if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
+ clk_disable_unprepare(bgp->fclock);
+put_clks:
+ clk_put(bgp->div_clk);
+put_fclock:
+ clk_put(bgp->fclock);
+free_irqs:
+ if (TI_BANDGAP_HAS(bgp, TSHUT))
+ free_irq(gpiod_to_irq(bgp->tshut_gpiod), NULL);
+
+ return ret;
+}
+
+static
+void ti_bandgap_remove(struct platform_device *pdev)
+{
+ struct ti_bandgap *bgp = platform_get_drvdata(pdev);
+ int i;
+
+ if (!soc_device_match(soc_no_cpu_notifier))
+ cpu_pm_unregister_notifier(&bgp->nb);
+
+ /* Remove sensor interfaces */
+ for (i = 0; i < bgp->conf->sensor_count; i++) {
+ if (bgp->conf->sensors[i].unregister_cooling)
+ bgp->conf->sensors[i].unregister_cooling(bgp, i);
+
+ if (bgp->conf->remove_sensor)
+ bgp->conf->remove_sensor(bgp, i);
+ }
+
+ ti_bandgap_power(bgp, false);
+
+ if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
+ clk_disable_unprepare(bgp->fclock);
+ clk_put(bgp->fclock);
+ clk_put(bgp->div_clk);
+
+ if (TI_BANDGAP_HAS(bgp, TALERT))
+ free_irq(bgp->irq, bgp);
+
+ if (TI_BANDGAP_HAS(bgp, TSHUT))
+ free_irq(gpiod_to_irq(bgp->tshut_gpiod), NULL);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int ti_bandgap_save_ctxt(struct ti_bandgap *bgp)
+{
+ int i;
+
+ for (i = 0; i < bgp->conf->sensor_count; i++) {
+ struct temp_sensor_registers *tsr;
+ struct temp_sensor_regval *rval;
+
+ rval = &bgp->regval[i];
+ tsr = bgp->conf->sensors[i].registers;
+
+ if (TI_BANDGAP_HAS(bgp, MODE_CONFIG))
+ rval->bg_mode_ctrl = ti_bandgap_readl(bgp,
+ tsr->bgap_mode_ctrl);
+ if (TI_BANDGAP_HAS(bgp, COUNTER))
+ rval->bg_counter = ti_bandgap_readl(bgp,
+ tsr->bgap_counter);
+ if (TI_BANDGAP_HAS(bgp, TALERT)) {
+ rval->bg_threshold = ti_bandgap_readl(bgp,
+ tsr->bgap_threshold);
+ rval->bg_ctrl = ti_bandgap_readl(bgp,
+ tsr->bgap_mask_ctrl);
+ }
+
+ if (TI_BANDGAP_HAS(bgp, TSHUT_CONFIG))
+ rval->tshut_threshold = ti_bandgap_readl(bgp,
+ tsr->tshut_threshold);
+ }
+
+ return 0;
+}
+
+static int ti_bandgap_restore_ctxt(struct ti_bandgap *bgp)
+{
+ int i;
+
+ for (i = 0; i < bgp->conf->sensor_count; i++) {
+ struct temp_sensor_registers *tsr;
+ struct temp_sensor_regval *rval;
+
+ rval = &bgp->regval[i];
+ tsr = bgp->conf->sensors[i].registers;
+
+ if (TI_BANDGAP_HAS(bgp, TSHUT_CONFIG))
+ ti_bandgap_writel(bgp, rval->tshut_threshold,
+ tsr->tshut_threshold);
+ /* Force immediate temperature measurement and update
+ * of the DTEMP field
+ */
+ ti_bandgap_force_single_read(bgp, i);
+
+ if (TI_BANDGAP_HAS(bgp, COUNTER))
+ ti_bandgap_writel(bgp, rval->bg_counter,
+ tsr->bgap_counter);
+ if (TI_BANDGAP_HAS(bgp, MODE_CONFIG))
+ ti_bandgap_writel(bgp, rval->bg_mode_ctrl,
+ tsr->bgap_mode_ctrl);
+ if (TI_BANDGAP_HAS(bgp, TALERT)) {
+ ti_bandgap_writel(bgp, rval->bg_threshold,
+ tsr->bgap_threshold);
+ ti_bandgap_writel(bgp, rval->bg_ctrl,
+ tsr->bgap_mask_ctrl);
+ }
+ }
+
+ return 0;
+}
+
+static int ti_bandgap_suspend(struct device *dev)
+{
+ struct ti_bandgap *bgp = dev_get_drvdata(dev);
+ int err;
+
+ err = ti_bandgap_save_ctxt(bgp);
+ ti_bandgap_power(bgp, false);
+
+ if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
+ clk_disable_unprepare(bgp->fclock);
+
+ bgp->is_suspended = true;
+
+ return err;
+}
+
+static int bandgap_omap_cpu_notifier(struct notifier_block *nb,
+ unsigned long cmd, void *v)
+{
+ struct ti_bandgap *bgp;
+
+ bgp = container_of(nb, struct ti_bandgap, nb);
+
+ spin_lock(&bgp->lock);
+ switch (cmd) {
+ case CPU_CLUSTER_PM_ENTER:
+ if (bgp->is_suspended)
+ break;
+ ti_bandgap_save_ctxt(bgp);
+ ti_bandgap_power(bgp, false);
+ if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
+ clk_disable(bgp->fclock);
+ break;
+ case CPU_CLUSTER_PM_ENTER_FAILED:
+ case CPU_CLUSTER_PM_EXIT:
+ if (bgp->is_suspended)
+ break;
+ if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
+ clk_enable(bgp->fclock);
+ ti_bandgap_power(bgp, true);
+ ti_bandgap_restore_ctxt(bgp);
+ break;
+ }
+ spin_unlock(&bgp->lock);
+
+ return NOTIFY_OK;
+}
+
+static int ti_bandgap_resume(struct device *dev)
+{
+ struct ti_bandgap *bgp = dev_get_drvdata(dev);
+
+ if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
+ clk_prepare_enable(bgp->fclock);
+
+ ti_bandgap_power(bgp, true);
+ bgp->is_suspended = false;
+
+ return ti_bandgap_restore_ctxt(bgp);
+}
+static SIMPLE_DEV_PM_OPS(ti_bandgap_dev_pm_ops, ti_bandgap_suspend,
+ ti_bandgap_resume);
+
+#define DEV_PM_OPS (&ti_bandgap_dev_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif
+
+static const struct of_device_id of_ti_bandgap_match[] = {
+#ifdef CONFIG_OMAP3_THERMAL
+ {
+ .compatible = "ti,omap34xx-bandgap",
+ .data = (void *)&omap34xx_data,
+ },
+ {
+ .compatible = "ti,omap36xx-bandgap",
+ .data = (void *)&omap36xx_data,
+ },
+#endif
+#ifdef CONFIG_OMAP4_THERMAL
+ {
+ .compatible = "ti,omap4430-bandgap",
+ .data = (void *)&omap4430_data,
+ },
+ {
+ .compatible = "ti,omap4460-bandgap",
+ .data = (void *)&omap4460_data,
+ },
+ {
+ .compatible = "ti,omap4470-bandgap",
+ .data = (void *)&omap4470_data,
+ },
+#endif
+#ifdef CONFIG_OMAP5_THERMAL
+ {
+ .compatible = "ti,omap5430-bandgap",
+ .data = (void *)&omap5430_data,
+ },
+#endif
+#ifdef CONFIG_DRA752_THERMAL
+ {
+ .compatible = "ti,dra752-bandgap",
+ .data = (void *)&dra752_data,
+ },
+#endif
+ /* Sentinel */
+ { },
+};
+MODULE_DEVICE_TABLE(of, of_ti_bandgap_match);
+
+static struct platform_driver ti_bandgap_sensor_driver = {
+ .probe = ti_bandgap_probe,
+ .remove = ti_bandgap_remove,
+ .driver = {
+ .name = "ti-soc-thermal",
+ .pm = DEV_PM_OPS,
+ .of_match_table = of_ti_bandgap_match,
+ },
+};
+
+module_platform_driver(ti_bandgap_sensor_driver);
+
+MODULE_DESCRIPTION("OMAP4+ bandgap temperature sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ti-soc-thermal");
+MODULE_AUTHOR("Texas Instrument Inc.");
diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.h b/drivers/thermal/ti-soc-thermal/ti-bandgap.h
new file mode 100644
index 000000000..46263c1da
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.h
@@ -0,0 +1,378 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * OMAP4 Bandgap temperature sensor driver
+ *
+ * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+#ifndef __TI_BANDGAP_H
+#define __TI_BANDGAP_H
+
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/cpu_pm.h>
+#include <linux/device.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm.h>
+
+struct gpio_desc;
+
+/**
+ * DOC: bandgap driver data structure
+ * ==================================
+ *
+ * +----------+----------------+
+ * | struct temp_sensor_regval |
+ * +---------------------------+
+ * * (Array of)
+ * |
+ * |
+ * +-------------------+ +-----------------+
+ * | struct ti_bandgap |-->| struct device * |
+ * +----------+--------+ +-----------------+
+ * |
+ * |
+ * V
+ * +------------------------+
+ * | struct ti_bandgap_data |
+ * +------------------------+
+ * |
+ * |
+ * * (Array of)
+ * +------------+------------------------------------------------------+
+ * | +----------+------------+ +-------------------------+ |
+ * | | struct ti_temp_sensor |-->| struct temp_sensor_data | |
+ * | +-----------------------+ +------------+------------+ |
+ * | | |
+ * | + |
+ * | V |
+ * | +----------+-------------------+ |
+ * | | struct temp_sensor_registers | |
+ * | +------------------------------+ |
+ * | |
+ * +-------------------------------------------------------------------+
+ *
+ * Above is a simple diagram describing how the data structure below
+ * are organized. For each bandgap device there should be a ti_bandgap_data
+ * containing the device instance configuration, as well as, an array of
+ * sensors, representing every sensor instance present in this bandgap.
+ */
+
+/**
+ * struct temp_sensor_registers - descriptor to access registers and bitfields
+ * @temp_sensor_ctrl: TEMP_SENSOR_CTRL register offset
+ * @bgap_tempsoff_mask: mask to temp_sensor_ctrl.tempsoff
+ * @bgap_soc_mask: mask to temp_sensor_ctrl.soc
+ * @bgap_eocz_mask: mask to temp_sensor_ctrl.eocz
+ * @bgap_dtemp_mask: mask to temp_sensor_ctrl.dtemp
+ * @bgap_mask_ctrl: BANDGAP_MASK_CTRL register offset
+ * @mask_hot_mask: mask to bandgap_mask_ctrl.mask_hot
+ * @mask_cold_mask: mask to bandgap_mask_ctrl.mask_cold
+ * @mask_counter_delay_mask: mask to bandgap_mask_ctrl.mask_counter_delay
+ * @mask_freeze_mask: mask to bandgap_mask_ctrl.mask_free
+ * @bgap_mode_ctrl: BANDGAP_MODE_CTRL register offset
+ * @mode_ctrl_mask: mask to bandgap_mode_ctrl.mode_ctrl
+ * @bgap_counter: BANDGAP_COUNTER register offset
+ * @counter_mask: mask to bandgap_counter.counter
+ * @bgap_threshold: BANDGAP_THRESHOLD register offset (TALERT thresholds)
+ * @threshold_thot_mask: mask to bandgap_threhold.thot
+ * @threshold_tcold_mask: mask to bandgap_threhold.tcold
+ * @tshut_threshold: TSHUT_THRESHOLD register offset (TSHUT thresholds)
+ * @tshut_hot_mask: mask to tshut_threhold.thot
+ * @tshut_cold_mask: mask to tshut_threhold.thot
+ * @bgap_status: BANDGAP_STATUS register offset
+ * @status_hot_mask: mask to bandgap_status.hot
+ * @status_cold_mask: mask to bandgap_status.cold
+ * @ctrl_dtemp_1: CTRL_DTEMP1 register offset
+ * @ctrl_dtemp_2: CTRL_DTEMP2 register offset
+ * @bgap_efuse: BANDGAP_EFUSE register offset
+ *
+ * The register offsets and bitfields might change across
+ * OMAP and variants versions. Hence this struct serves as a
+ * descriptor map on how to access the registers and the bitfields.
+ *
+ * This descriptor contains registers of all versions of bandgap chips.
+ * Not all versions will use all registers, depending on the available
+ * features. Please read TRMs for descriptive explanation on each bitfield.
+ */
+
+struct temp_sensor_registers {
+ u32 temp_sensor_ctrl;
+ u32 bgap_tempsoff_mask;
+ u32 bgap_soc_mask;
+ u32 bgap_eocz_mask;
+ u32 bgap_dtemp_mask;
+
+ u32 bgap_mask_ctrl;
+ u32 mask_hot_mask;
+ u32 mask_cold_mask;
+ u32 mask_counter_delay_mask;
+ u32 mask_freeze_mask;
+
+ u32 bgap_mode_ctrl;
+ u32 mode_ctrl_mask;
+
+ u32 bgap_counter;
+ u32 counter_mask;
+
+ u32 bgap_threshold;
+ u32 threshold_thot_mask;
+ u32 threshold_tcold_mask;
+
+ u32 tshut_threshold;
+ u32 tshut_hot_mask;
+ u32 tshut_cold_mask;
+
+ u32 bgap_status;
+ u32 status_hot_mask;
+ u32 status_cold_mask;
+
+ u32 ctrl_dtemp_1;
+ u32 ctrl_dtemp_2;
+ u32 bgap_efuse;
+};
+
+/**
+ * struct temp_sensor_data - The thresholds and limits for temperature sensors.
+ * @tshut_hot: temperature to trigger a thermal reset (initial value)
+ * @tshut_cold: temp to get the plat out of reset due to thermal (init val)
+ * @t_hot: temperature to trigger a thermal alert (high initial value)
+ * @t_cold: temperature to trigger a thermal alert (low initial value)
+ * @min_freq: sensor minimum clock rate
+ * @max_freq: sensor maximum clock rate
+ *
+ * This data structure will hold the required thresholds and temperature limits
+ * for a specific temperature sensor, like shutdown temperature, alert
+ * temperature, clock / rate used, ADC conversion limits and update intervals
+ */
+struct temp_sensor_data {
+ u32 tshut_hot;
+ u32 tshut_cold;
+ u32 t_hot;
+ u32 t_cold;
+ u32 min_freq;
+ u32 max_freq;
+};
+
+struct ti_bandgap_data;
+
+/**
+ * struct temp_sensor_regval - temperature sensor register values and priv data
+ * @bg_mode_ctrl: temp sensor control register value
+ * @bg_ctrl: bandgap ctrl register value
+ * @bg_counter: bandgap counter value
+ * @bg_threshold: bandgap threshold register value
+ * @tshut_threshold: bandgap tshut register value
+ * @data: private data
+ *
+ * Data structure to save and restore bandgap register set context. Only
+ * required registers are shadowed, when needed.
+ */
+struct temp_sensor_regval {
+ u32 bg_mode_ctrl;
+ u32 bg_ctrl;
+ u32 bg_counter;
+ u32 bg_threshold;
+ u32 tshut_threshold;
+ void *data;
+};
+
+/**
+ * struct ti_bandgap - bandgap device structure
+ * @dev: struct device pointer
+ * @base: io memory base address
+ * @conf: struct with bandgap configuration set (# sensors, conv_table, etc)
+ * @regval: temperature sensor register values
+ * @fclock: pointer to functional clock of temperature sensor
+ * @div_clk: pointer to divider clock of temperature sensor fclk
+ * @lock: spinlock for ti_bandgap structure
+ * @irq: MPU IRQ number for thermal alert
+ * @tshut_gpio: GPIO where Tshut signal is routed
+ * @clk_rate: Holds current clock rate
+ *
+ * The bandgap device structure representing the bandgap device instance.
+ * It holds most of the dynamic stuff. Configurations and sensor specific
+ * entries are inside the @conf structure.
+ */
+struct ti_bandgap {
+ struct device *dev;
+ void __iomem *base;
+ const struct ti_bandgap_data *conf;
+ struct temp_sensor_regval *regval;
+ struct clk *fclock;
+ struct clk *div_clk;
+ spinlock_t lock; /* shields this struct */
+ int irq;
+ struct gpio_desc *tshut_gpiod;
+ u32 clk_rate;
+ struct notifier_block nb;
+ unsigned int is_suspended:1;
+};
+
+/**
+ * struct ti_temp_sensor - bandgap temperature sensor configuration data
+ * @ts_data: pointer to struct with thresholds, limits of temperature sensor
+ * @registers: pointer to the list of register offsets and bitfields
+ * @domain: the name of the domain where the sensor is located
+ * @slope_pcb: sensor gradient slope info for hotspot extrapolation equation
+ * with no external influence
+ * @constant_pcb: sensor gradient const info for hotspot extrapolation equation
+ * with no external influence
+ * @register_cooling: function to describe how this sensor is going to be cooled
+ * @unregister_cooling: function to release cooling data
+ *
+ * Data structure to describe a temperature sensor handled by a bandgap device.
+ * It should provide configuration details on this sensor, such as how to
+ * access the registers affecting this sensor, shadow register buffer, how to
+ * assess the gradient from hotspot, how to cooldown the domain when sensor
+ * reports too hot temperature.
+ */
+struct ti_temp_sensor {
+ struct temp_sensor_data *ts_data;
+ struct temp_sensor_registers *registers;
+ char *domain;
+ /* for hotspot extrapolation */
+ const int slope_pcb;
+ const int constant_pcb;
+ int (*register_cooling)(struct ti_bandgap *bgp, int id);
+ int (*unregister_cooling)(struct ti_bandgap *bgp, int id);
+};
+
+/**
+ * DOC: ti bandgap feature types
+ *
+ * TI_BANDGAP_FEATURE_TSHUT - used when the thermal shutdown signal output
+ * of a bandgap device instance is routed to the processor. This means
+ * the system must react and perform the shutdown by itself (handle an
+ * IRQ, for instance).
+ *
+ * TI_BANDGAP_FEATURE_TSHUT_CONFIG - used when the bandgap device has control
+ * over the thermal shutdown configuration. This means that the thermal
+ * shutdown thresholds are programmable, for instance.
+ *
+ * TI_BANDGAP_FEATURE_TALERT - used when the bandgap device instance outputs
+ * a signal representing violation of programmable alert thresholds.
+ *
+ * TI_BANDGAP_FEATURE_MODE_CONFIG - used when it is possible to choose which
+ * mode, continuous or one shot, the bandgap device instance will operate.
+ *
+ * TI_BANDGAP_FEATURE_COUNTER - used when the bandgap device instance allows
+ * programming the update interval of its internal state machine.
+ *
+ * TI_BANDGAP_FEATURE_POWER_SWITCH - used when the bandgap device allows
+ * itself to be switched on/off.
+ *
+ * TI_BANDGAP_FEATURE_CLK_CTRL - used when the clocks feeding the bandgap
+ * device are gateable or not.
+ *
+ * TI_BANDGAP_FEATURE_FREEZE_BIT - used when the bandgap device features
+ * a history buffer that its update can be freezed/unfreezed.
+ *
+ * TI_BANDGAP_FEATURE_COUNTER_DELAY - used when the bandgap device features
+ * a delay programming based on distinct values.
+ *
+ * TI_BANDGAP_FEATURE_HISTORY_BUFFER - used when the bandgap device features
+ * a history buffer of temperatures.
+ *
+ * TI_BANDGAP_FEATURE_ERRATA_814 - used to workaorund when the bandgap device
+ * has Errata 814
+ * TI_BANDGAP_FEATURE_UNRELIABLE - used when the sensor readings are too
+ * inaccurate.
+ * TI_BANDGAP_FEATURE_CONT_MODE_ONLY - used when single mode hangs the sensor
+ * TI_BANDGAP_HAS(b, f) - macro to check if a bandgap device is capable of a
+ * specific feature (above) or not. Return non-zero, if yes.
+ */
+#define TI_BANDGAP_FEATURE_TSHUT BIT(0)
+#define TI_BANDGAP_FEATURE_TSHUT_CONFIG BIT(1)
+#define TI_BANDGAP_FEATURE_TALERT BIT(2)
+#define TI_BANDGAP_FEATURE_MODE_CONFIG BIT(3)
+#define TI_BANDGAP_FEATURE_COUNTER BIT(4)
+#define TI_BANDGAP_FEATURE_POWER_SWITCH BIT(5)
+#define TI_BANDGAP_FEATURE_CLK_CTRL BIT(6)
+#define TI_BANDGAP_FEATURE_FREEZE_BIT BIT(7)
+#define TI_BANDGAP_FEATURE_COUNTER_DELAY BIT(8)
+#define TI_BANDGAP_FEATURE_HISTORY_BUFFER BIT(9)
+#define TI_BANDGAP_FEATURE_ERRATA_814 BIT(10)
+#define TI_BANDGAP_FEATURE_UNRELIABLE BIT(11)
+#define TI_BANDGAP_FEATURE_CONT_MODE_ONLY BIT(12)
+#define TI_BANDGAP_HAS(b, f) \
+ ((b)->conf->features & TI_BANDGAP_FEATURE_ ## f)
+
+/**
+ * struct ti_bandgap_data - ti bandgap data configuration structure
+ * @features: a bitwise flag set to describe the device features
+ * @conv_table: Pointer to ADC to temperature conversion table
+ * @adc_start_val: ADC conversion table starting value
+ * @adc_end_val: ADC conversion table ending value
+ * @fclock_name: clock name of the functional clock
+ * @div_ck_name: clock name of the clock divisor
+ * @sensor_count: count of temperature sensor within this bandgap device
+ * @report_temperature: callback to report thermal alert to thermal API
+ * @expose_sensor: callback to export sensor to thermal API
+ * @remove_sensor: callback to destroy sensor from thermal API
+ * @sensors: array of sensors present in this bandgap instance
+ *
+ * This is a data structure which should hold most of the static configuration
+ * of a bandgap device instance. It should describe which features this instance
+ * is capable of, the clock names to feed this device, the amount of sensors and
+ * their configuration representation, and how to export and unexport them to
+ * a thermal API.
+ */
+struct ti_bandgap_data {
+ unsigned int features;
+ const int *conv_table;
+ u32 adc_start_val;
+ u32 adc_end_val;
+ char *fclock_name;
+ char *div_ck_name;
+ int sensor_count;
+ int (*report_temperature)(struct ti_bandgap *bgp, int id);
+ int (*expose_sensor)(struct ti_bandgap *bgp, int id, char *domain);
+ int (*remove_sensor)(struct ti_bandgap *bgp, int id);
+
+ /* this needs to be at the end */
+ struct ti_temp_sensor sensors[];
+};
+
+int ti_bandgap_read_update_interval(struct ti_bandgap *bgp, int id,
+ int *interval);
+int ti_bandgap_write_update_interval(struct ti_bandgap *bgp, int id,
+ u32 interval);
+int ti_bandgap_read_temperature(struct ti_bandgap *bgp, int id,
+ int *temperature);
+int ti_bandgap_set_sensor_data(struct ti_bandgap *bgp, int id, void *data);
+void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id);
+int ti_bandgap_get_trend(struct ti_bandgap *bgp, int id, int *trend);
+
+#ifdef CONFIG_OMAP3_THERMAL
+extern const struct ti_bandgap_data omap34xx_data;
+extern const struct ti_bandgap_data omap36xx_data;
+#else
+#define omap34xx_data NULL
+#define omap36xx_data NULL
+#endif
+
+#ifdef CONFIG_OMAP4_THERMAL
+extern const struct ti_bandgap_data omap4430_data;
+extern const struct ti_bandgap_data omap4460_data;
+extern const struct ti_bandgap_data omap4470_data;
+#else
+#define omap4430_data NULL
+#define omap4460_data NULL
+#define omap4470_data NULL
+#endif
+
+#ifdef CONFIG_OMAP5_THERMAL
+extern const struct ti_bandgap_data omap5430_data;
+#else
+#define omap5430_data NULL
+#endif
+
+#ifdef CONFIG_DRA752_THERMAL
+extern const struct ti_bandgap_data dra752_data;
+#else
+#define dra752_data NULL
+#endif
+#endif
diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
new file mode 100644
index 000000000..0cf0826b8
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * OMAP thermal driver interface
+ *
+ * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/gfp.h>
+#include <linux/kernel.h>
+#include <linux/workqueue.h>
+#include <linux/thermal.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/cpu_cooling.h>
+#include <linux/of.h>
+
+#include "ti-thermal.h"
+#include "ti-bandgap.h"
+#include "../thermal_hwmon.h"
+
+#define TI_BANDGAP_UPDATE_INTERVAL_MS 250
+
+/* common data structures */
+struct ti_thermal_data {
+ struct cpufreq_policy *policy;
+ struct thermal_zone_device *ti_thermal;
+ struct thermal_zone_device *pcb_tz;
+ struct thermal_cooling_device *cool_dev;
+ struct ti_bandgap *bgp;
+ enum thermal_device_mode mode;
+ struct work_struct thermal_wq;
+ int sensor_id;
+ bool our_zone;
+};
+
+static void ti_thermal_work(struct work_struct *work)
+{
+ struct ti_thermal_data *data = container_of(work,
+ struct ti_thermal_data, thermal_wq);
+
+ thermal_zone_device_update(data->ti_thermal, THERMAL_EVENT_UNSPECIFIED);
+
+ dev_dbg(data->bgp->dev, "updated thermal zone %s\n",
+ thermal_zone_device_type(data->ti_thermal));
+}
+
+/**
+ * ti_thermal_hotspot_temperature - returns sensor extrapolated temperature
+ * @t: omap sensor temperature
+ * @s: omap sensor slope value
+ * @c: omap sensor const value
+ */
+static inline int ti_thermal_hotspot_temperature(int t, int s, int c)
+{
+ int delta = t * s / 1000 + c;
+
+ if (delta < 0)
+ delta = 0;
+
+ return t + delta;
+}
+
+/* thermal zone ops */
+/* Get temperature callback function for thermal zone */
+static inline int __ti_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct thermal_zone_device *pcb_tz = NULL;
+ struct ti_thermal_data *data = thermal_zone_device_priv(tz);
+ struct ti_bandgap *bgp;
+ const struct ti_temp_sensor *s;
+ int ret, tmp, slope, constant;
+ int pcb_temp;
+
+ if (!data)
+ return 0;
+
+ bgp = data->bgp;
+ s = &bgp->conf->sensors[data->sensor_id];
+
+ ret = ti_bandgap_read_temperature(bgp, data->sensor_id, &tmp);
+ if (ret)
+ return ret;
+
+ /* Default constants */
+ slope = thermal_zone_get_slope(tz);
+ constant = thermal_zone_get_offset(tz);
+
+ pcb_tz = data->pcb_tz;
+ /* In case pcb zone is available, use the extrapolation rule with it */
+ if (!IS_ERR(pcb_tz)) {
+ ret = thermal_zone_get_temp(pcb_tz, &pcb_temp);
+ if (!ret) {
+ tmp -= pcb_temp; /* got a valid PCB temp */
+ slope = s->slope_pcb;
+ constant = s->constant_pcb;
+ } else {
+ dev_err(bgp->dev,
+ "Failed to read PCB state. Using defaults\n");
+ ret = 0;
+ }
+ }
+ *temp = ti_thermal_hotspot_temperature(tmp, slope, constant);
+
+ return ret;
+}
+
+static 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;
+ int id, tr, ret = 0;
+
+ bgp = data->bgp;
+ id = data->sensor_id;
+
+ ret = ti_bandgap_get_trend(bgp, id, &tr);
+ if (ret)
+ return ret;
+
+ if (tr > 0)
+ *trend = THERMAL_TREND_RAISING;
+ else if (tr < 0)
+ *trend = THERMAL_TREND_DROPPING;
+ else
+ *trend = THERMAL_TREND_STABLE;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops ti_of_thermal_ops = {
+ .get_temp = __ti_thermal_get_temp,
+ .get_trend = __ti_thermal_get_trend,
+};
+
+static struct ti_thermal_data
+*ti_thermal_build_data(struct ti_bandgap *bgp, int id)
+{
+ struct ti_thermal_data *data;
+
+ data = devm_kzalloc(bgp->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ dev_err(bgp->dev, "kzalloc fail\n");
+ return NULL;
+ }
+ data->sensor_id = id;
+ data->bgp = bgp;
+ data->mode = THERMAL_DEVICE_ENABLED;
+ /* pcb_tz will be either valid or PTR_ERR() */
+ data->pcb_tz = thermal_zone_get_zone_by_name("pcb");
+ INIT_WORK(&data->thermal_wq, ti_thermal_work);
+
+ return data;
+}
+
+int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id,
+ char *domain)
+{
+ struct ti_thermal_data *data;
+
+ data = ti_bandgap_get_sensor_data(bgp, id);
+
+ if (IS_ERR_OR_NULL(data))
+ data = ti_thermal_build_data(bgp, id);
+
+ if (!data)
+ return -EINVAL;
+
+ /* in case this is specified by DT */
+ data->ti_thermal = devm_thermal_of_zone_register(bgp->dev, id,
+ data, &ti_of_thermal_ops);
+ if (IS_ERR(data->ti_thermal)) {
+ dev_err(bgp->dev, "thermal zone device is NULL\n");
+ return PTR_ERR(data->ti_thermal);
+ }
+
+ ti_bandgap_set_sensor_data(bgp, id, data);
+ ti_bandgap_write_update_interval(bgp, data->sensor_id,
+ TI_BANDGAP_UPDATE_INTERVAL_MS);
+
+ devm_thermal_add_hwmon_sysfs(bgp->dev, data->ti_thermal);
+
+ return 0;
+}
+
+int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id)
+{
+ struct ti_thermal_data *data;
+
+ data = ti_bandgap_get_sensor_data(bgp, id);
+
+ if (!IS_ERR_OR_NULL(data) && data->ti_thermal) {
+ if (data->our_zone)
+ thermal_zone_device_unregister(data->ti_thermal);
+ }
+
+ return 0;
+}
+
+int ti_thermal_report_sensor_temperature(struct ti_bandgap *bgp, int id)
+{
+ struct ti_thermal_data *data;
+
+ data = ti_bandgap_get_sensor_data(bgp, id);
+
+ schedule_work(&data->thermal_wq);
+
+ return 0;
+}
+
+int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id)
+{
+ struct ti_thermal_data *data;
+ struct device_node *np = bgp->dev->of_node;
+
+ /*
+ * We are assuming here that if one deploys the zone
+ * using DT, then it must be aware that the cooling device
+ * loading has to happen via cpufreq driver.
+ */
+ if (of_property_present(np, "#thermal-sensor-cells"))
+ return 0;
+
+ data = ti_bandgap_get_sensor_data(bgp, id);
+ if (!data || IS_ERR(data))
+ data = ti_thermal_build_data(bgp, id);
+
+ if (!data)
+ return -EINVAL;
+
+ data->policy = cpufreq_cpu_get(0);
+ if (!data->policy) {
+ pr_debug("%s: CPUFreq policy not found\n", __func__);
+ return -EPROBE_DEFER;
+ }
+
+ /* Register cooling device */
+ data->cool_dev = cpufreq_cooling_register(data->policy);
+ if (IS_ERR(data->cool_dev)) {
+ int ret = PTR_ERR(data->cool_dev);
+ dev_err(bgp->dev, "Failed to register cpu cooling device %d\n",
+ ret);
+ cpufreq_cpu_put(data->policy);
+
+ return ret;
+ }
+ ti_bandgap_set_sensor_data(bgp, id, data);
+
+ return 0;
+}
+
+int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id)
+{
+ struct ti_thermal_data *data;
+
+ data = ti_bandgap_get_sensor_data(bgp, id);
+
+ if (!IS_ERR_OR_NULL(data)) {
+ cpufreq_cooling_unregister(data->cool_dev);
+ if (data->policy)
+ cpufreq_cpu_put(data->policy);
+ }
+
+ return 0;
+}
diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal.h b/drivers/thermal/ti-soc-thermal/ti-thermal.h
new file mode 100644
index 000000000..4fd2c2018
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/ti-thermal.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * OMAP thermal definitions
+ *
+ * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ */
+#ifndef __TI_THERMAL_H
+#define __TI_THERMAL_H
+
+#include "ti-bandgap.h"
+
+/* PCB sensor calculation constants */
+#define OMAP_GRADIENT_SLOPE_W_PCB_4430 0
+#define OMAP_GRADIENT_CONST_W_PCB_4430 20000
+#define OMAP_GRADIENT_SLOPE_W_PCB_4460 1142
+#define OMAP_GRADIENT_CONST_W_PCB_4460 -393
+#define OMAP_GRADIENT_SLOPE_W_PCB_4470 1063
+#define OMAP_GRADIENT_CONST_W_PCB_4470 -477
+
+#define OMAP_GRADIENT_SLOPE_W_PCB_5430_CPU 100
+#define OMAP_GRADIENT_CONST_W_PCB_5430_CPU 484
+#define OMAP_GRADIENT_SLOPE_W_PCB_5430_GPU 464
+#define OMAP_GRADIENT_CONST_W_PCB_5430_GPU -5102
+
+#define DRA752_GRADIENT_SLOPE_W_PCB 0
+#define DRA752_GRADIENT_CONST_W_PCB 2000
+
+/* trip points of interest in milicelsius (at hotspot level) */
+#define OMAP_TRIP_COLD 100000
+#define OMAP_TRIP_HOT 110000
+#define OMAP_TRIP_SHUTDOWN 125000
+#define OMAP_TRIP_NUMBER 2
+#define OMAP_TRIP_STEP \
+ ((OMAP_TRIP_SHUTDOWN - OMAP_TRIP_HOT) / (OMAP_TRIP_NUMBER - 1))
+
+/* Update rates */
+#define FAST_TEMP_MONITORING_RATE 250
+
+#ifdef CONFIG_TI_THERMAL
+int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, char *domain);
+int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id);
+int ti_thermal_report_sensor_temperature(struct ti_bandgap *bgp, int id);
+int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id);
+int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id);
+#else
+static inline
+int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, char *domain)
+{
+ return 0;
+}
+
+static inline
+int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id)
+{
+ return 0;
+}
+
+static inline
+int ti_thermal_report_sensor_temperature(struct ti_bandgap *bgp, int id)
+{
+ return 0;
+}
+
+static inline
+int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id)
+{
+ return 0;
+}
+
+static inline
+int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id)
+{
+ return 0;
+}
+#endif
+#endif
diff --git a/drivers/thermal/uniphier_thermal.c b/drivers/thermal/uniphier_thermal.c
new file mode 100644
index 000000000..1a04294ef
--- /dev/null
+++ b/drivers/thermal/uniphier_thermal.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * uniphier_thermal.c - Socionext UniPhier thermal driver
+ * Copyright 2014 Panasonic Corporation
+ * Copyright 2016-2017 Socionext Inc.
+ * Author:
+ * Kunihiko Hayashi <hayashi.kunihiko@socionext.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+/*
+ * block registers
+ * addresses are the offset from .block_base
+ */
+#define PVTCTLEN 0x0000
+#define PVTCTLEN_EN BIT(0)
+
+#define PVTCTLMODE 0x0004
+#define PVTCTLMODE_MASK 0xf
+#define PVTCTLMODE_TEMPMON 0x5
+
+#define EMONREPEAT 0x0040
+#define EMONREPEAT_ENDLESS BIT(24)
+#define EMONREPEAT_PERIOD GENMASK(3, 0)
+#define EMONREPEAT_PERIOD_1000000 0x9
+
+/*
+ * common registers
+ * addresses are the offset from .map_base
+ */
+#define PVTCTLSEL 0x0900
+#define PVTCTLSEL_MASK GENMASK(2, 0)
+#define PVTCTLSEL_MONITOR 0
+
+#define SETALERT0 0x0910
+#define SETALERT1 0x0914
+#define SETALERT2 0x0918
+#define SETALERT_TEMP_OVF (GENMASK(7, 0) << 16)
+#define SETALERT_TEMP_OVF_VALUE(val) (((val) & GENMASK(7, 0)) << 16)
+#define SETALERT_EN BIT(0)
+
+#define PMALERTINTCTL 0x0920
+#define PMALERTINTCTL_CLR(ch) BIT(4 * (ch) + 2)
+#define PMALERTINTCTL_SET(ch) BIT(4 * (ch) + 1)
+#define PMALERTINTCTL_EN(ch) BIT(4 * (ch) + 0)
+#define PMALERTINTCTL_MASK (GENMASK(10, 8) | GENMASK(6, 4) | \
+ GENMASK(2, 0))
+
+#define TMOD 0x0928
+#define TMOD_WIDTH 9
+
+#define TMODCOEF 0x0e5c
+
+#define TMODSETUP0_EN BIT(30)
+#define TMODSETUP0_VAL(val) (((val) & GENMASK(13, 0)) << 16)
+#define TMODSETUP1_EN BIT(15)
+#define TMODSETUP1_VAL(val) ((val) & GENMASK(14, 0))
+
+/* SoC critical temperature */
+#define CRITICAL_TEMP_LIMIT (120 * 1000)
+
+/* Max # of alert channels */
+#define ALERT_CH_NUM 3
+
+/* SoC specific thermal sensor data */
+struct uniphier_tm_soc_data {
+ u32 map_base;
+ u32 block_base;
+ u32 tmod_setup_addr;
+};
+
+struct uniphier_tm_dev {
+ struct regmap *regmap;
+ struct device *dev;
+ bool alert_en[ALERT_CH_NUM];
+ struct thermal_zone_device *tz_dev;
+ const struct uniphier_tm_soc_data *data;
+};
+
+static int uniphier_tm_initialize_sensor(struct uniphier_tm_dev *tdev)
+{
+ struct regmap *map = tdev->regmap;
+ u32 val;
+ u32 tmod_calib[2];
+ int ret;
+
+ /* stop PVT */
+ regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,
+ PVTCTLEN_EN, 0);
+
+ /*
+ * Since SoC has a calibrated value that was set in advance,
+ * TMODCOEF shows non-zero and PVT refers the value internally.
+ *
+ * If TMODCOEF shows zero, the boards don't have the calibrated
+ * value, and the driver has to set default value from DT.
+ */
+ ret = regmap_read(map, tdev->data->map_base + TMODCOEF, &val);
+ if (ret)
+ return ret;
+ if (!val) {
+ /* look for the default values in DT */
+ ret = of_property_read_u32_array(tdev->dev->of_node,
+ "socionext,tmod-calibration",
+ tmod_calib,
+ ARRAY_SIZE(tmod_calib));
+ if (ret)
+ return ret;
+
+ regmap_write(map, tdev->data->tmod_setup_addr,
+ TMODSETUP0_EN | TMODSETUP0_VAL(tmod_calib[0]) |
+ TMODSETUP1_EN | TMODSETUP1_VAL(tmod_calib[1]));
+ }
+
+ /* select temperature mode */
+ regmap_write_bits(map, tdev->data->block_base + PVTCTLMODE,
+ PVTCTLMODE_MASK, PVTCTLMODE_TEMPMON);
+
+ /* set monitoring period */
+ regmap_write_bits(map, tdev->data->block_base + EMONREPEAT,
+ EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD,
+ EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD_1000000);
+
+ /* set monitor mode */
+ regmap_write_bits(map, tdev->data->map_base + PVTCTLSEL,
+ PVTCTLSEL_MASK, PVTCTLSEL_MONITOR);
+
+ return 0;
+}
+
+static void uniphier_tm_set_alert(struct uniphier_tm_dev *tdev, u32 ch,
+ u32 temp)
+{
+ struct regmap *map = tdev->regmap;
+
+ /* set alert temperature */
+ regmap_write_bits(map, tdev->data->map_base + SETALERT0 + (ch << 2),
+ SETALERT_EN | SETALERT_TEMP_OVF,
+ SETALERT_EN |
+ SETALERT_TEMP_OVF_VALUE(temp / 1000));
+}
+
+static void uniphier_tm_enable_sensor(struct uniphier_tm_dev *tdev)
+{
+ struct regmap *map = tdev->regmap;
+ int i;
+ u32 bits = 0;
+
+ for (i = 0; i < ALERT_CH_NUM; i++)
+ if (tdev->alert_en[i])
+ bits |= PMALERTINTCTL_EN(i);
+
+ /* enable alert interrupt */
+ regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL,
+ PMALERTINTCTL_MASK, bits);
+
+ /* start PVT */
+ regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,
+ PVTCTLEN_EN, PVTCTLEN_EN);
+
+ usleep_range(700, 1500); /* The spec note says at least 700us */
+}
+
+static void uniphier_tm_disable_sensor(struct uniphier_tm_dev *tdev)
+{
+ struct regmap *map = tdev->regmap;
+
+ /* disable alert interrupt */
+ regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL,
+ PMALERTINTCTL_MASK, 0);
+
+ /* stop PVT */
+ regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,
+ PVTCTLEN_EN, 0);
+
+ usleep_range(1000, 2000); /* The spec note says at least 1ms */
+}
+
+static int uniphier_tm_get_temp(struct thermal_zone_device *tz, int *out_temp)
+{
+ struct uniphier_tm_dev *tdev = thermal_zone_device_priv(tz);
+ struct regmap *map = tdev->regmap;
+ int ret;
+ u32 temp;
+
+ ret = regmap_read(map, tdev->data->map_base + TMOD, &temp);
+ if (ret)
+ return ret;
+
+ /* MSB of the TMOD field is a sign bit */
+ *out_temp = sign_extend32(temp, TMOD_WIDTH - 1) * 1000;
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops uniphier_of_thermal_ops = {
+ .get_temp = uniphier_tm_get_temp,
+};
+
+static void uniphier_tm_irq_clear(struct uniphier_tm_dev *tdev)
+{
+ u32 mask = 0, bits = 0;
+ int i;
+
+ for (i = 0; i < ALERT_CH_NUM; i++) {
+ mask |= (PMALERTINTCTL_CLR(i) | PMALERTINTCTL_SET(i));
+ bits |= PMALERTINTCTL_CLR(i);
+ }
+
+ /* clear alert interrupt */
+ regmap_write_bits(tdev->regmap,
+ tdev->data->map_base + PMALERTINTCTL, mask, bits);
+}
+
+static irqreturn_t uniphier_tm_alarm_irq(int irq, void *_tdev)
+{
+ struct uniphier_tm_dev *tdev = _tdev;
+
+ disable_irq_nosync(irq);
+ uniphier_tm_irq_clear(tdev);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t uniphier_tm_alarm_irq_thread(int irq, void *_tdev)
+{
+ struct uniphier_tm_dev *tdev = _tdev;
+
+ thermal_zone_device_update(tdev->tz_dev, THERMAL_EVENT_UNSPECIFIED);
+
+ 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 ret, irq;
+
+ tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
+ if (!tdev)
+ return -ENOMEM;
+ tdev->dev = dev;
+
+ tdev->data = of_device_get_match_data(dev);
+ if (WARN_ON(!tdev->data))
+ return -EINVAL;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ /* get regmap from syscon node */
+ parent = of_get_parent(dev->of_node); /* parent should be syscon node */
+ regmap = syscon_node_to_regmap(parent);
+ of_node_put(parent);
+ if (IS_ERR(regmap)) {
+ dev_err(dev, "failed to get regmap (error %ld)\n",
+ PTR_ERR(regmap));
+ return PTR_ERR(regmap);
+ }
+ tdev->regmap = regmap;
+
+ ret = uniphier_tm_initialize_sensor(tdev);
+ if (ret) {
+ dev_err(dev, "failed to initialize sensor\n");
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(dev, irq, uniphier_tm_alarm_irq,
+ uniphier_tm_alarm_irq_thread,
+ 0, "thermal", tdev);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, tdev);
+
+ tdev->tz_dev = devm_thermal_of_zone_register(dev, 0, tdev,
+ &uniphier_of_thermal_ops);
+ if (IS_ERR(tdev->tz_dev)) {
+ dev_err(dev, "failed to register sensor device\n");
+ return PTR_ERR(tdev->tz_dev);
+ }
+
+ /* set alert temperatures */
+ twd.tdev = tdev;
+ thermal_zone_for_each_trip(tdev->tz_dev, uniphier_tm_trip_walk_cb, &twd);
+
+ 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;
+ }
+
+ uniphier_tm_enable_sensor(tdev);
+
+ return 0;
+}
+
+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);
+}
+
+static const struct uniphier_tm_soc_data uniphier_pxs2_tm_data = {
+ .map_base = 0xe000,
+ .block_base = 0xe000,
+ .tmod_setup_addr = 0xe904,
+};
+
+static const struct uniphier_tm_soc_data uniphier_ld20_tm_data = {
+ .map_base = 0xe000,
+ .block_base = 0xe800,
+ .tmod_setup_addr = 0xe938,
+};
+
+static const struct of_device_id uniphier_tm_dt_ids[] = {
+ {
+ .compatible = "socionext,uniphier-pxs2-thermal",
+ .data = &uniphier_pxs2_tm_data,
+ },
+ {
+ .compatible = "socionext,uniphier-ld20-thermal",
+ .data = &uniphier_ld20_tm_data,
+ },
+ {
+ .compatible = "socionext,uniphier-pxs3-thermal",
+ .data = &uniphier_ld20_tm_data,
+ },
+ {
+ .compatible = "socionext,uniphier-nx1-thermal",
+ .data = &uniphier_ld20_tm_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, uniphier_tm_dt_ids);
+
+static struct platform_driver uniphier_tm_driver = {
+ .probe = uniphier_tm_probe,
+ .remove = uniphier_tm_remove,
+ .driver = {
+ .name = "uniphier-thermal",
+ .of_match_table = uniphier_tm_dt_ids,
+ },
+};
+module_platform_driver(uniphier_tm_driver);
+
+MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>");
+MODULE_DESCRIPTION("UniPhier thermal driver");
+MODULE_LICENSE("GPL v2");