summaryrefslogtreecommitdiffstats
path: root/drivers/thermal
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
commit76cb841cb886eef6b3bee341a2266c76578724ad (patch)
treef5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/thermal
parentInitial commit. (diff)
downloadlinux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz
linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/thermal')
-rw-r--r--drivers/thermal/Kconfig492
-rw-r--r--drivers/thermal/Makefile63
-rw-r--r--drivers/thermal/armada_thermal.c723
-rw-r--r--drivers/thermal/broadcom/Kconfig24
-rw-r--r--drivers/thermal/broadcom/Makefile3
-rw-r--r--drivers/thermal/broadcom/bcm2835_thermal.c323
-rw-r--r--drivers/thermal/broadcom/brcmstb_thermal.c374
-rw-r--r--drivers/thermal/broadcom/ns-thermal.c106
-rw-r--r--drivers/thermal/clock_cooling.c452
-rw-r--r--drivers/thermal/cpu_cooling.c820
-rw-r--r--drivers/thermal/da9062-thermal.c315
-rw-r--r--drivers/thermal/db8500_thermal.c533
-rw-r--r--drivers/thermal/devfreq_cooling.c608
-rw-r--r--drivers/thermal/dove_thermal.c195
-rw-r--r--drivers/thermal/fair_share.c146
-rw-r--r--drivers/thermal/gov_bang_bang.c138
-rw-r--r--drivers/thermal/hisi_thermal.c627
-rw-r--r--drivers/thermal/imx_thermal.c917
-rw-r--r--drivers/thermal/int340x_thermal/Kconfig42
-rw-r--r--drivers/thermal/int340x_thermal/Makefile8
-rw-r--r--drivers/thermal/int340x_thermal/acpi_thermal_rel.c394
-rw-r--r--drivers/thermal/int340x_thermal/acpi_thermal_rel.h85
-rw-r--r--drivers/thermal/int340x_thermal/int3400_thermal.c402
-rw-r--r--drivers/thermal/int340x_thermal/int3402_thermal.c108
-rw-r--r--drivers/thermal/int340x_thermal/int3403_thermal.c311
-rw-r--r--drivers/thermal/int340x_thermal/int3406_thermal.c213
-rw-r--r--drivers/thermal/int340x_thermal/int340x_thermal_zone.c295
-rw-r--r--drivers/thermal/int340x_thermal/int340x_thermal_zone.h70
-rw-r--r--drivers/thermal/int340x_thermal/processor_thermal_device.c527
-rw-r--r--drivers/thermal/intel_bxt_pmic_thermal.c299
-rw-r--r--drivers/thermal/intel_pch_thermal.c432
-rw-r--r--drivers/thermal/intel_powerclamp.c815
-rw-r--r--drivers/thermal/intel_quark_dts_thermal.c471
-rw-r--r--drivers/thermal/intel_soc_dts_iosf.c478
-rw-r--r--drivers/thermal/intel_soc_dts_iosf.h62
-rw-r--r--drivers/thermal/intel_soc_dts_thermal.c132
-rw-r--r--drivers/thermal/kirkwood_thermal.c125
-rw-r--r--drivers/thermal/max77620_thermal.c171
-rw-r--r--drivers/thermal/mtk_thermal.c805
-rw-r--r--drivers/thermal/of-thermal.c1054
-rw-r--r--drivers/thermal/power_allocator.c663
-rw-r--r--drivers/thermal/qcom-spmi-temp-alarm.c343
-rw-r--r--drivers/thermal/qcom/Kconfig11
-rw-r--r--drivers/thermal/qcom/Makefile2
-rw-r--r--drivers/thermal/qcom/tsens-8916.c113
-rw-r--r--drivers/thermal/qcom/tsens-8960.c292
-rw-r--r--drivers/thermal/qcom/tsens-8974.c244
-rw-r--r--drivers/thermal/qcom/tsens-common.c154
-rw-r--r--drivers/thermal/qcom/tsens-v2.c77
-rw-r--r--drivers/thermal/qcom/tsens.c210
-rw-r--r--drivers/thermal/qcom/tsens.h96
-rw-r--r--drivers/thermal/qoriq_thermal.c314
-rw-r--r--drivers/thermal/rcar_gen3_thermal.c469
-rw-r--r--drivers/thermal/rcar_thermal.c656
-rw-r--r--drivers/thermal/rockchip_thermal.c1411
-rw-r--r--drivers/thermal/samsung/Kconfig10
-rw-r--r--drivers/thermal/samsung/Makefile5
-rw-r--r--drivers/thermal/samsung/exynos_tmu.c1204
-rw-r--r--drivers/thermal/spear_thermal.c196
-rw-r--r--drivers/thermal/st/Kconfig12
-rw-r--r--drivers/thermal/st/Makefile3
-rw-r--r--drivers/thermal/st/st_thermal.c315
-rw-r--r--drivers/thermal/st/st_thermal.h104
-rw-r--r--drivers/thermal/st/st_thermal_memmap.c209
-rw-r--r--drivers/thermal/st/st_thermal_syscfg.c178
-rw-r--r--drivers/thermal/step_wise.c230
-rw-r--r--drivers/thermal/tango_thermal.c125
-rw-r--r--drivers/thermal/tegra/Kconfig20
-rw-r--r--drivers/thermal/tegra/Makefile8
-rw-r--r--drivers/thermal/tegra/soctherm-fuse.c169
-rw-r--r--drivers/thermal/tegra/soctherm.c1463
-rw-r--r--drivers/thermal/tegra/soctherm.h137
-rw-r--r--drivers/thermal/tegra/tegra-bpmp-thermal.c263
-rw-r--r--drivers/thermal/tegra/tegra124-soctherm.c214
-rw-r--r--drivers/thermal/tegra/tegra132-soctherm.c214
-rw-r--r--drivers/thermal/tegra/tegra210-soctherm.c215
-rw-r--r--drivers/thermal/thermal-generic-adc.c171
-rw-r--r--drivers/thermal/thermal_core.c1582
-rw-r--r--drivers/thermal/thermal_core.h144
-rw-r--r--drivers/thermal/thermal_helpers.c226
-rw-r--r--drivers/thermal/thermal_hwmon.c250
-rw-r--r--drivers/thermal/thermal_hwmon.h34
-rw-r--r--drivers/thermal/thermal_sysfs.c1007
-rw-r--r--drivers/thermal/ti-soc-thermal/Kconfig72
-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.h193
-rw-r--r--drivers/thermal/ti-soc-thermal/dra752-thermal-data.c381
-rw-r--r--drivers/thermal/ti-soc-thermal/omap3-thermal-data.c166
-rw-r--r--drivers/thermal/ti-soc-thermal/omap4-thermal-data.c252
-rw-r--r--drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h169
-rw-r--r--drivers/thermal/ti-soc-thermal/omap5-thermal-data.c312
-rw-r--r--drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h159
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-bandgap.c1267
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-bandgap.h386
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-thermal-common.c286
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-thermal.h107
-rw-r--r--drivers/thermal/uniphier_thermal.c388
-rw-r--r--drivers/thermal/user_space.c69
-rw-r--r--drivers/thermal/x86_pkg_temp_thermal.c558
-rw-r--r--drivers/thermal/zx2967_thermal.c258
101 files changed, 32964 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
new file mode 100644
index 000000000..0e69edc77
--- /dev/null
+++ b/drivers/thermal/Kconfig
@@ -0,0 +1,492 @@
+#
+# Generic thermal sysfs drivers configuration
+#
+
+menuconfig THERMAL
+ tristate "Generic Thermal sysfs driver"
+ help
+ Generic Thermal Sysfs driver offers a generic mechanism for
+ thermal management. Usually it's made up of one or more thermal
+ zone and cooling device.
+ Each thermal zone contains its own temperature, trip points,
+ cooling devices.
+ All platforms with ACPI thermal support can use this driver.
+ If you want this support, you should say Y or M here.
+
+if THERMAL
+
+config THERMAL_STATISTICS
+ bool "Thermal state transition statistics"
+ help
+ Export thermal state transition statistics information through sysfs.
+
+ If in doubt, say N.
+
+config THERMAL_EMERGENCY_POWEROFF_DELAY_MS
+ int "Emergency poweroff delay in milli-seconds"
+ depends on THERMAL
+ 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.
+
+config THERMAL_WRITABLE_TRIPS
+ bool "Enable writable trip points"
+ help
+ This option allows the system integrator to choose whether
+ trip temperatures can be changed from userspace. The
+ writable trips need to be specified when setting up the
+ thermal zone but the choice here takes precedence.
+
+ Say 'Y' here if you would like to allow userspace tools to
+ change trip temperatures.
+
+choice
+ prompt "Default Thermal governor"
+ default THERMAL_DEFAULT_GOV_STEP_WISE
+ 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
+ Select this if you want to let the user space manage the
+ platform thermals.
+
+config THERMAL_DEFAULT_GOV_POWER_ALLOCATOR
+ bool "power_allocator"
+ select 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.
+
+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"
+ 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 CPU_FREQ
+ depends on THERMAL_OF
+ 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.
+
+ If you want this support, you should say Y here.
+
+config CLOCK_THERMAL
+ bool "Generic clock cooling support"
+ depends on COMMON_CLK
+ depends on PM_OPP
+ help
+ This entry implements the generic clock cooling mechanism through
+ frequency clipping. Typically used to cool off co-processors. The
+ device that is configured to use this cooling mechanism will be
+ controlled to reduce clock frequency whenever temperature is high.
+
+config DEVFREQ_THERMAL
+ bool "Generic device cooling support"
+ depends on PM_DEVFREQ
+ 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 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 HISI_THERMAL
+ tristate "Hisilicon thermal driver"
+ depends on ARCH_HISI || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ default y
+ 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 && CPU_THERMAL) || 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 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
+ depends on HAS_IOMEM
+ 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 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 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 RCAR_THERMAL
+ tristate "Renesas R-Car thermal driver"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Enable this to plug the R-Car thermal sensor driver into the Linux
+ thermal framework.
+
+config RCAR_GEN3_THERMAL
+ tristate "Renesas R-Car Gen3 thermal driver"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ Enable this to plug the R-Car Gen3 thermal sensor driver into the Linux
+ thermal framework.
+
+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
+ 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.
+
+config INTEL_POWERCLAMP
+ tristate "Intel PowerClamp idle injection driver"
+ depends on THERMAL
+ depends on X86
+ depends on CPU_SUP_INTEL
+ 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_PKG_TEMP_THERMAL
+ tristate "X86 package temperature thermal driver"
+ depends on X86_THERMAL_VECTOR
+ select THERMAL_GOV_USER_SPACE
+ select THERMAL_WRITABLE_TRIPS
+ 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
+ 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 && PCI && ACPI
+ select INTEL_SOC_DTS_IOSF_CORE
+ select THERMAL_WRITABLE_TRIPS
+ help
+ Enable this to register Intel SoCs (e.g. Bay Trail) platform digital
+ temperature sensor (DTS). These SoCs have two additional DTSs in
+ 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/int340x_thermal/Kconfig
+endmenu
+
+config INTEL_BXT_PMIC_THERMAL
+ tristate "Intel Broxton PMIC thermal driver"
+ depends on X86 && INTEL_SOC_PMIC_BXTWC && 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
+ 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 MTK_THERMAL
+ tristate "Temperature sensor driver for mediatek SoCs"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on NVMEM || NVMEM=n
+ depends on RESET_CONTROLLER
+ default y
+ help
+ Enable this option if you want to have support for thermal management
+ controller present in Mediatek SoCs
+
+menu "Broadcom thermal drivers"
+depends on ARCH_BCM || ARCH_BRCMSTB || ARCH_BCM2835 || 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 && OF
+source "drivers/thermal/st/Kconfig"
+endmenu
+
+config TANGO_THERMAL
+ tristate "Tango thermal management"
+ depends on ARCH_TANGO || COMPILE_TEST
+ help
+ Enable the Tango thermal driver, which supports the primitive
+ temperature sensor embedded in Tango chips since the SMP8758.
+ This sensor only generates a 1-bit signal to indicate whether
+ the die temperature exceeds a programmable threshold.
+
+source "drivers/thermal/tegra/Kconfig"
+
+config 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 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 ZX2967_THERMAL
+ tristate "Thermal sensors on zx2967 SoC"
+ depends on ARCH_ZX || COMPILE_TEST
+ help
+ Enable the zx2967 thermal sensors driver, which supports
+ the primitive temperature sensor embedded in zx2967 SoCs.
+ This sensor generates the real time die temperature.
+
+config UNIPHIER_THERMAL
+ tristate "Socionext UniPhier thermal driver"
+ depends on ARCH_UNIPHIER || COMPILE_TEST
+ 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.
+endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
new file mode 100644
index 000000000..610344eb3
--- /dev/null
+++ b/drivers/thermal/Makefile
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for sensor chip drivers.
+#
+
+obj-$(CONFIG_THERMAL) += thermal_sys.o
+thermal_sys-y += thermal_core.o thermal_sysfs.o \
+ thermal_helpers.o
+
+# interface to/from other layers providing sensors
+thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o
+thermal_sys-$(CONFIG_THERMAL_OF) += of-thermal.o
+
+# governors
+thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o
+thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG) += gov_bang_bang.o
+thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o
+thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o
+thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += power_allocator.o
+
+# cpufreq cooling
+thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
+
+# clock cooling
+thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o
+
+# devfreq cooling
+thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
+
+# platform thermal drivers
+obj-y += broadcom/
+obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
+obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
+obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o
+obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
+obj-$(CONFIG_RCAR_GEN3_THERMAL) += rcar_gen3_thermal.o
+obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o
+obj-y += samsung/
+obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o
+obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o
+obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o
+obj-$(CONFIG_TANGO_THERMAL) += tango_thermal.o
+obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o
+obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o
+obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o
+obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.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_TI_SOC_THERMAL) += ti-soc-thermal/
+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_ST_THERMAL) += st/
+obj-$(CONFIG_QCOM_TSENS) += qcom/
+obj-y += tegra/
+obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
+obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o
+obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
+obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o
+obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o
diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c
new file mode 100644
index 000000000..1c9830b2c
--- /dev/null
+++ b/drivers/thermal/armada_thermal.c
@@ -0,0 +1,723 @@
+/*
+ * Marvell EBU Armada SoCs thermal sensor driver
+ *
+ * Copyright (C) 2013 Marvell
+ *
+ * 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/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>
+
+/* Thermal Manager Control and Status Register */
+#define PMU_TDC0_SW_RST_MASK (0x1 << 1)
+#define PMU_TM_DISABLE_OFFS 0
+#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_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_OTF_CAL_MASK (0x1 << 30)
+#define PMU_TDC0_START_CAL_MASK (0x1 << 25)
+
+#define A375_UNIT_CONTROL_SHIFT 27
+#define A375_UNIT_CONTROL_MASK 0x7
+#define A375_READOUT_INVERT BIT(15)
+#define A375_HW_RESETn BIT(8)
+
+/* Errata fields */
+#define CONTROL0_TSEN_TC_TRIM_MASK 0x7
+#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_SHIFT 13
+#define CONTROL0_TSEN_CHAN_MASK 0xF
+#define CONTROL0_TSEN_OSR_SHIFT 24
+#define CONTROL0_TSEN_OSR_MAX 0x3
+#define CONTROL0_TSEN_MODE_SHIFT 30
+#define CONTROL0_TSEN_MODE_EXTERNAL 0x2
+#define CONTROL0_TSEN_MODE_MASK 0x3
+
+#define CONTROL1_TSEN_AVG_SHIFT 0
+#define CONTROL1_TSEN_AVG_MASK 0x7
+#define CONTROL1_EXT_TSEN_SW_RESET BIT(7)
+#define CONTROL1_EXT_TSEN_HW_RESETn BIT(8)
+
+#define STATUS_POLL_PERIOD_US 1000
+#define STATUS_POLL_TIMEOUT_US 100000
+
+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;
+ int current_channel;
+};
+
+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;
+ u32 is_valid_bit;
+
+ /* Syscon access */
+ unsigned int syscon_control0_off;
+ unsigned int syscon_control1_off;
+ unsigned int syscon_status_off;
+
+ /* 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);
+ reg |= PMU_TDC0_OTF_CAL_MASK;
+
+ /* Reference calibration value */
+ reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
+ reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
+
+ /* Reset the sensor */
+ reg |= PMU_TDC0_SW_RST_MASK;
+
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+
+ /* Enable the sensor */
+ regmap_read(priv->syscon, data->syscon_status_off, &reg);
+ reg &= ~PMU_TM_DISABLE_MASK;
+ 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);
+ reg |= PMU_TDC0_OTF_CAL_MASK;
+
+ /* Reference calibration value */
+ reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
+ reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
+
+ /* Reset the sensor */
+ reg &= ~PMU_TDC0_START_CAL_MASK;
+
+ 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);
+ reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT);
+ reg &= ~A375_READOUT_INVERT;
+ reg &= ~A375_HW_RESETn;
+ regmap_write(priv->syscon, data->syscon_control1_off, reg);
+
+ msleep(20);
+
+ reg |= A375_HW_RESETn;
+ 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);
+ reg |= CONTROL1_EXT_TSEN_HW_RESETn;
+ reg &= ~CONTROL1_EXT_TSEN_SW_RESET;
+ 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);
+ reg &= ~CONTROL0_TSEN_TC_TRIM_MASK;
+ reg |= CONTROL0_TSEN_TC_TRIM_VAL;
+ regmap_write(priv->syscon, data->syscon_control0_off, reg);
+}
+
+static void armada_ap806_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);
+ reg &= ~CONTROL0_TSEN_RESET;
+ reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_ENABLE;
+
+ /* Sample every ~2ms */
+ reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT;
+
+ /* Enable average (2 samples by default) */
+ reg &= ~CONTROL0_TSEN_AVG_BYPASS;
+
+ 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);
+ reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT;
+ 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);
+ reg &= ~CONTROL1_TSEN_AVG_MASK << CONTROL1_TSEN_AVG_SHIFT;
+ reg |= 1 << CONTROL1_TSEN_AVG_SHIFT;
+ 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;
+}
+
+/* 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);
+ ctrl0 &= ~CONTROL0_TSEN_START;
+ regmap_write(priv->syscon, data->syscon_control0_off, ctrl0);
+
+ /* Reset the mode, internal sensor will be automatically selected */
+ ctrl0 &= ~(CONTROL0_TSEN_MODE_MASK << CONTROL0_TSEN_MODE_SHIFT);
+
+ /* Other channels are external and should be selected accordingly */
+ if (channel) {
+ /* Change the mode to external */
+ ctrl0 |= CONTROL0_TSEN_MODE_EXTERNAL <<
+ CONTROL0_TSEN_MODE_SHIFT;
+ /* Select the sensor */
+ ctrl0 &= ~(CONTROL0_TSEN_CHAN_MASK << CONTROL0_TSEN_CHAN_SHIFT);
+ ctrl0 |= (channel - 1) << CONTROL0_TSEN_CHAN_SHIFT;
+ }
+
+ /* Actually set the mode/channel */
+ regmap_write(priv->syscon, data->syscon_control0_off, ctrl0);
+ priv->current_channel = channel;
+
+ /* Re-start the measurements */
+ ctrl0 |= CONTROL0_TSEN_START;
+ 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)) {
+ dev_err(priv->dev,
+ "Temperature sensor reading not valid\n");
+ 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->devdata;
+ int ret;
+
+ /* Valid check */
+ if (!armada_is_valid(priv)) {
+ dev_err(priv->dev,
+ "Temperature sensor reading not valid\n");
+ return -EIO;
+ }
+
+ /* Do the actual reading */
+ ret = armada_read_sensor(priv, temp);
+
+ return ret;
+}
+
+static struct thermal_zone_device_ops legacy_ops = {
+ .get_temp = armada_get_temp_legacy,
+};
+
+static int armada_get_temp(void *_sensor, int *temp)
+{
+ struct armada_thermal_sensor *sensor = _sensor;
+ 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);
+
+unlock_mutex:
+ mutex_unlock(&priv->update_lock);
+
+ return ret;
+}
+
+static struct thermal_zone_of_device_ops of_ops = {
+ .get_temp = armada_get_temp,
+};
+
+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 = 0xd0,
+};
+
+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_ap806_init,
+ .is_valid_bit = BIT(16),
+ .temp_shift = 0,
+ .temp_mask = 0x3ff,
+ .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,
+ .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,
+ .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 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-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,
+ .fast_io = true,
+};
+
+static int armada_thermal_probe_legacy(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ struct armada_thermal_data *data = priv->data;
+ struct resource *res;
+ void __iomem *base;
+
+ /* First memory region points towards the status register */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EIO;
+
+ /*
+ * Edit the resource start address and length to map over all the
+ * registers, instead of pointing at them one by one.
+ */
+ res->start -= data->syscon_status_off;
+ res->end = res->start + max(data->syscon_status_off,
+ max(data->syscon_control0_off,
+ data->syscon_control1_off)) +
+ sizeof(unsigned int) - 1;
+
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->syscon = devm_regmap_init_mmio(&pdev->dev, base,
+ &armada_thermal_regmap_config);
+ if (IS_ERR(priv->syscon))
+ return PTR_ERR(priv->syscon);
+
+ return 0;
+}
+
+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);
+ if (IS_ERR(priv->syscon))
+ return PTR_ERR(priv->syscon);
+
+ return 0;
+}
+
+static void armada_set_sane_name(struct platform_device *pdev,
+ struct armada_thermal_priv *priv)
+{
+ const char *name = dev_name(&pdev->dev);
+ char *insane_char;
+
+ if (strlen(name) > THERMAL_NAME_LENGTH) {
+ /*
+ * 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 */
+ strncpy(priv->zone_name, name, THERMAL_NAME_LENGTH - 1);
+ priv->zone_name[THERMAL_NAME_LENGTH - 1] = '\0';
+
+ /* Then check there are no '-' or hwmon core will complain */
+ do {
+ insane_char = strpbrk(priv->zone_name, "-");
+ if (insane_char)
+ *insane_char = '_';
+ } while (insane_char);
+}
+
+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;
+ 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_zone_device_register(priv->zone_name, 0, 0, priv,
+ &legacy_ops, NULL, 0, 0);
+ if (IS_ERR(tz)) {
+ dev_err(&pdev->dev,
+ "Failed to register thermal zone device\n");
+ return PTR_ERR(tz);
+ }
+
+ 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);
+
+ /*
+ * 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_zone_of_sensor_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;
+ }
+ }
+
+ return 0;
+}
+
+static int armada_thermal_exit(struct platform_device *pdev)
+{
+ struct armada_drvdata *drvdata = platform_get_drvdata(pdev);
+
+ if (drvdata->type == LEGACY)
+ thermal_zone_device_unregister(drvdata->data.tz);
+
+ return 0;
+}
+
+static struct platform_driver armada_thermal_driver = {
+ .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..c106a15bf
--- /dev/null
+++ b/drivers/thermal/broadcom/Kconfig
@@ -0,0 +1,24 @@
+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.
diff --git a/drivers/thermal/broadcom/Makefile b/drivers/thermal/broadcom/Makefile
new file mode 100644
index 000000000..fae10ecaf
--- /dev/null
+++ b/drivers/thermal/broadcom/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o
+obj-$(CONFIG_BRCMSTB_THERMAL) += brcmstb_thermal.o
+obj-$(CONFIG_BCM_NS_THERMAL) += ns-thermal.o
diff --git a/drivers/thermal/broadcom/bcm2835_thermal.c b/drivers/thermal/broadcom/bcm2835_thermal.c
new file mode 100644
index 000000000..8646fb742
--- /dev/null
+++ b/drivers/thermal/broadcom/bcm2835_thermal.c
@@ -0,0 +1,323 @@
+/*
+ * Driver for Broadcom BCM2835 SoC temperature sensor
+ *
+ * Copyright (C) 2016 Martin Sperl
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/clk.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.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;
+
+ if (temp < 0)
+ temp = 0;
+ if (temp >= BIT(BCM2835_TS_TSENSSTAT_DATA_BITS))
+ temp = BIT(BCM2835_TS_TSENSSTAT_DATA_BITS) - 1;
+
+ return temp;
+}
+
+static int bcm2835_thermal_get_temp(void *d, int *temp)
+{
+ struct bcm2835_thermal_data *data = d;
+ 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);
+ if (!data->debugfsdir)
+ return;
+
+ 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_of_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)
+{
+ const struct of_device_id *match;
+ struct thermal_zone_device *tz;
+ struct bcm2835_thermal_data *data;
+ struct resource *res;
+ int err = 0;
+ u32 val;
+ unsigned long rate;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ match = of_match_device(bcm2835_thermal_of_match_table,
+ &pdev->dev);
+ if (!match)
+ return -EINVAL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ data->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(data->regs)) {
+ err = PTR_ERR(data->regs);
+ dev_err(&pdev->dev, "Could not get registers: %d\n", err);
+ return err;
+ }
+
+ data->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(data->clk)) {
+ err = PTR_ERR(data->clk);
+ if (err != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Could not get clk: %d\n", err);
+ return err;
+ }
+
+ err = clk_prepare_enable(data->clk);
+ if (err)
+ return err;
+
+ rate = clk_get_rate(data->clk);
+ if ((rate < 1920000) || (rate > 5000000))
+ dev_warn(&pdev->dev,
+ "Clock %pCn running at %lu Hz is outside of the recommended range: 1.92 to 5MHz\n",
+ data->clk, rate);
+
+ /* register of thermal sensor and get info from DT */
+ tz = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
+ &bcm2835_thermal_ops);
+ if (IS_ERR(tz)) {
+ err = PTR_ERR(tz);
+ dev_err(&pdev->dev,
+ "Failed to register the thermal device: %d\n",
+ err);
+ goto err_clk;
+ }
+
+ /*
+ * 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 trip_temp, offset, slope;
+
+ 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 = tz->ops->get_trip_temp(tz, 0, &trip_temp);
+ if (err < 0) {
+ dev_err(&pdev->dev,
+ "Not able to read trip_temp: %d\n",
+ err);
+ goto err_tz;
+ }
+
+ /* 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(trip_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
+ */
+ tz->tzp->no_hwmon = false;
+ err = thermal_add_hwmon_sysfs(tz);
+ if (err)
+ goto err_tz;
+
+ bcm2835_thermal_debugfs(pdev);
+
+ return 0;
+err_tz:
+ thermal_zone_of_sensor_unregister(&pdev->dev, tz);
+err_clk:
+ clk_disable_unprepare(data->clk);
+
+ return err;
+}
+
+static int bcm2835_thermal_remove(struct platform_device *pdev)
+{
+ struct bcm2835_thermal_data *data = platform_get_drvdata(pdev);
+ struct thermal_zone_device *tz = data->tz;
+
+ debugfs_remove_recursive(data->debugfsdir);
+ thermal_zone_of_sensor_unregister(&pdev->dev, tz);
+ clk_disable_unprepare(data->clk);
+
+ return 0;
+}
+
+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..8d16a41ea
--- /dev/null
+++ b/drivers/thermal/broadcom/brcmstb_thermal.c
@@ -0,0 +1,374 @@
+/*
+ * Broadcom STB AVS TMON thermal sensor driver
+ *
+ * Copyright (c) 2015-2017 Broadcom
+ *
+ * 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.
+ *
+ */
+
+#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/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_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_priv {
+ void __iomem *tmon_base;
+ struct device *dev;
+ struct thermal_zone_device *thermal;
+};
+
+/* Convert a HW code to a temperature reading (millidegree celsius) */
+static inline int avs_tmon_code_to_temp(struct thermal_zone_device *tz,
+ u32 code)
+{
+ return (AVS_TMON_TEMP_OFFSET -
+ (int)((code & AVS_TMON_TEMP_MAX) * AVS_TMON_TEMP_SLOPE));
+}
+
+/*
+ * 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 thermal_zone_device *tz,
+ int temp, bool low)
+{
+ if (temp < AVS_TMON_TEMP_MIN)
+ return AVS_TMON_TEMP_MAX; /* Maximum code value */
+
+ if (temp >= AVS_TMON_TEMP_OFFSET)
+ return 0; /* Minimum code value */
+
+ if (low)
+ return (u32)(DIV_ROUND_UP(AVS_TMON_TEMP_OFFSET - temp,
+ AVS_TMON_TEMP_SLOPE));
+ else
+ return (u32)((AVS_TMON_TEMP_OFFSET - temp) /
+ AVS_TMON_TEMP_SLOPE);
+}
+
+static int brcmstb_get_temp(void *data, int *temp)
+{
+ struct brcmstb_thermal_priv *priv = data;
+ u32 val;
+ long t;
+
+ val = __raw_readl(priv->tmon_base + AVS_TMON_STATUS);
+
+ if (!(val & AVS_TMON_STATUS_valid_msk)) {
+ dev_err(priv->dev, "reading not valid\n");
+ return -EIO;
+ }
+
+ val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift;
+
+ t = avs_tmon_code_to_temp(priv->thermal, val);
+ if (t < 0)
+ *temp = 0;
+ else
+ *temp = 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->thermal, 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->thermal, 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->thermal, 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(void *data, int low, int high)
+{
+ struct brcmstb_thermal_priv *priv = data;
+
+ 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 struct thermal_zone_of_device_ops of_ops = {
+ .get_temp = brcmstb_get_temp,
+ .set_trips = brcmstb_set_trips,
+};
+
+static const struct of_device_id brcmstb_thermal_id_table[] = {
+ { .compatible = "brcm,avs-tmon" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table);
+
+static int brcmstb_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_zone_device *thermal;
+ struct brcmstb_thermal_priv *priv;
+ struct resource *res;
+ int irq, ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->tmon_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->tmon_base))
+ return PTR_ERR(priv->tmon_base);
+
+ priv->dev = &pdev->dev;
+ platform_set_drvdata(pdev, priv);
+
+ thermal = thermal_zone_of_sensor_register(&pdev->dev, 0, priv, &of_ops);
+ if (IS_ERR(thermal)) {
+ ret = PTR_ERR(thermal);
+ dev_err(&pdev->dev, "could not register sensor: %d\n", ret);
+ return ret;
+ }
+
+ priv->thermal = thermal;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "could not get IRQ\n");
+ ret = irq;
+ goto err;
+ }
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ brcmstb_tmon_irq_thread, IRQF_ONESHOT,
+ DRV_NAME, priv);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not request IRQ: %d\n", ret);
+ goto err;
+ }
+
+ dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n");
+
+ return 0;
+
+err:
+ thermal_zone_of_sensor_unregister(&pdev->dev, thermal);
+ return ret;
+}
+
+static int brcmstb_thermal_exit(struct platform_device *pdev)
+{
+ struct brcmstb_thermal_priv *priv = platform_get_drvdata(pdev);
+ struct thermal_zone_device *thermal = priv->thermal;
+
+ if (thermal)
+ thermal_zone_of_sensor_unregister(&pdev->dev, priv->thermal);
+
+ return 0;
+}
+
+static struct platform_driver brcmstb_thermal_driver = {
+ .probe = brcmstb_thermal_probe,
+ .remove = brcmstb_thermal_exit,
+ .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..322e741a2
--- /dev/null
+++ b/drivers/thermal/broadcom/ns-thermal.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 Rafał Miłecki <rafal@milecki.pl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#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
+
+struct ns_thermal {
+ struct thermal_zone_device *tz;
+ void __iomem *pvtmon;
+};
+
+static int ns_thermal_get_temp(void *data, int *temp)
+{
+ struct ns_thermal *ns_thermal = data;
+ int offset = thermal_zone_get_offset(ns_thermal->tz);
+ int slope = thermal_zone_get_slope(ns_thermal->tz);
+ u32 val;
+
+ val = readl(ns_thermal->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, ns_thermal->pvtmon + PVTMON_CONTROL0);
+ }
+
+ val = readl(ns_thermal->pvtmon + PVTMON_STATUS);
+ *temp = slope * val + offset;
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops ns_thermal_ops = {
+ .get_temp = ns_thermal_get_temp,
+};
+
+static int ns_thermal_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ns_thermal *ns_thermal;
+
+ ns_thermal = devm_kzalloc(dev, sizeof(*ns_thermal), GFP_KERNEL);
+ if (!ns_thermal)
+ return -ENOMEM;
+
+ ns_thermal->pvtmon = of_iomap(dev_of_node(dev), 0);
+ if (WARN_ON(!ns_thermal->pvtmon))
+ return -ENOENT;
+
+ ns_thermal->tz = devm_thermal_zone_of_sensor_register(dev, 0,
+ ns_thermal,
+ &ns_thermal_ops);
+ if (IS_ERR(ns_thermal->tz)) {
+ iounmap(ns_thermal->pvtmon);
+ return PTR_ERR(ns_thermal->tz);
+ }
+
+ platform_set_drvdata(pdev, ns_thermal);
+
+ return 0;
+}
+
+static int ns_thermal_remove(struct platform_device *pdev)
+{
+ struct ns_thermal *ns_thermal = platform_get_drvdata(pdev);
+
+ iounmap(ns_thermal->pvtmon);
+
+ return 0;
+}
+
+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/clock_cooling.c b/drivers/thermal/clock_cooling.c
new file mode 100644
index 000000000..56711c255
--- /dev/null
+++ b/drivers/thermal/clock_cooling.c
@@ -0,0 +1,452 @@
+/*
+ * drivers/thermal/clock_cooling.c
+ *
+ * Copyright (C) 2014 Eduardo Valentin <edubezval@gmail.com>
+ *
+ * Copyright (C) 2013 Texas Instruments Inc.
+ * Contact: Eduardo Valentin <eduardo.valentin@ti.com>
+ *
+ * Highly based on cpu_cooling.c.
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+ * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#include <linux/clk.h>
+#include <linux/cpufreq.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/mutex.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/clock_cooling.h>
+
+/**
+ * struct clock_cooling_device - data for cooling device with clock
+ * @id: unique integer value corresponding to each clock_cooling_device
+ * registered.
+ * @dev: struct device pointer to the device being used to cool off using
+ * clock frequencies.
+ * @cdev: thermal_cooling_device pointer to keep track of the
+ * registered cooling device.
+ * @clk_rate_change_nb: reference to notifier block used to receive clock
+ * rate changes.
+ * @freq_table: frequency table used to keep track of available frequencies.
+ * @clock_state: integer value representing the current state of clock
+ * cooling devices.
+ * @clock_val: integer value representing the absolute value of the clipped
+ * frequency.
+ * @clk: struct clk reference used to enforce clock limits.
+ * @lock: mutex lock to protect this struct.
+ *
+ * This structure is required for keeping information of each
+ * clock_cooling_device registered. In order to prevent corruption of this a
+ * mutex @lock is used.
+ */
+struct clock_cooling_device {
+ int id;
+ struct device *dev;
+ struct thermal_cooling_device *cdev;
+ struct notifier_block clk_rate_change_nb;
+ struct cpufreq_frequency_table *freq_table;
+ unsigned long clock_state;
+ unsigned long clock_val;
+ struct clk *clk;
+ struct mutex lock; /* lock to protect the content of this struct */
+};
+#define to_clock_cooling_device(x) \
+ container_of(x, struct clock_cooling_device, clk_rate_change_nb)
+static DEFINE_IDA(clock_ida);
+
+/* Below code defines functions to be used for clock as cooling device */
+
+enum clock_cooling_property {
+ GET_LEVEL,
+ GET_FREQ,
+ GET_MAXL,
+};
+
+/**
+ * clock_cooling_get_property - fetch a property of interest for a give cpu.
+ * @ccdev: clock cooling device reference
+ * @input: query parameter
+ * @output: query return
+ * @property: type of query (frequency, level, max level)
+ *
+ * This is the common function to
+ * 1. get maximum clock cooling states
+ * 2. translate frequency to cooling state
+ * 3. translate cooling state to frequency
+ * Note that the code may be not in good shape
+ * but it is written in this way in order to:
+ * a) reduce duplicate code as most of the code can be shared.
+ * b) make sure the logic is consistent when translating between
+ * cooling states and frequencies.
+ *
+ * Return: 0 on success, -EINVAL when invalid parameters are passed.
+ */
+static int clock_cooling_get_property(struct clock_cooling_device *ccdev,
+ unsigned long input,
+ unsigned long *output,
+ enum clock_cooling_property property)
+{
+ int i;
+ unsigned long max_level = 0, level = 0;
+ unsigned int freq = CPUFREQ_ENTRY_INVALID;
+ int descend = -1;
+ struct cpufreq_frequency_table *pos, *table = ccdev->freq_table;
+
+ if (!output)
+ return -EINVAL;
+
+ if (!table)
+ return -EINVAL;
+
+ cpufreq_for_each_valid_entry(pos, table) {
+ /* ignore duplicate entry */
+ if (freq == pos->frequency)
+ continue;
+
+ /* get the frequency order */
+ if (freq != CPUFREQ_ENTRY_INVALID && descend == -1)
+ descend = freq > pos->frequency;
+
+ freq = pos->frequency;
+ max_level++;
+ }
+
+ /* No valid cpu frequency entry */
+ if (max_level == 0)
+ return -EINVAL;
+
+ /* max_level is an index, not a counter */
+ max_level--;
+
+ /* get max level */
+ if (property == GET_MAXL) {
+ *output = max_level;
+ return 0;
+ }
+
+ if (property == GET_FREQ)
+ level = descend ? input : (max_level - input);
+
+ i = 0;
+ cpufreq_for_each_valid_entry(pos, table) {
+ /* ignore duplicate entry */
+ if (freq == pos->frequency)
+ continue;
+
+ /* now we have a valid frequency entry */
+ freq = pos->frequency;
+
+ if (property == GET_LEVEL && (unsigned int)input == freq) {
+ /* get level by frequency */
+ *output = descend ? i : (max_level - i);
+ return 0;
+ }
+ if (property == GET_FREQ && level == i) {
+ /* get frequency by level */
+ *output = freq;
+ return 0;
+ }
+ i++;
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * clock_cooling_get_level - return the cooling level of given clock cooling.
+ * @cdev: reference of a thermal cooling device of used as clock cooling device
+ * @freq: the frequency of interest
+ *
+ * This function will match the cooling level corresponding to the
+ * requested @freq and return it.
+ *
+ * Return: The matched cooling level on success or THERMAL_CSTATE_INVALID
+ * otherwise.
+ */
+unsigned long clock_cooling_get_level(struct thermal_cooling_device *cdev,
+ unsigned long freq)
+{
+ struct clock_cooling_device *ccdev = cdev->devdata;
+ unsigned long val;
+
+ if (clock_cooling_get_property(ccdev, (unsigned long)freq, &val,
+ GET_LEVEL))
+ return THERMAL_CSTATE_INVALID;
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(clock_cooling_get_level);
+
+/**
+ * clock_cooling_get_frequency - get the absolute value of frequency from level.
+ * @ccdev: clock cooling device reference
+ * @level: cooling level
+ *
+ * This function matches cooling level with frequency. Based on a cooling level
+ * of frequency, equals cooling state of cpu cooling device, it will return
+ * the corresponding frequency.
+ * e.g level=0 --> 1st MAX FREQ, level=1 ---> 2nd MAX FREQ, .... etc
+ *
+ * Return: 0 on error, the corresponding frequency otherwise.
+ */
+static unsigned long
+clock_cooling_get_frequency(struct clock_cooling_device *ccdev,
+ unsigned long level)
+{
+ int ret = 0;
+ unsigned long freq;
+
+ ret = clock_cooling_get_property(ccdev, level, &freq, GET_FREQ);
+ if (ret)
+ return 0;
+
+ return freq;
+}
+
+/**
+ * clock_cooling_apply - function to apply frequency clipping.
+ * @ccdev: clock_cooling_device pointer containing frequency clipping data.
+ * @cooling_state: value of the cooling state.
+ *
+ * Function used to make sure the clock layer is aware of current thermal
+ * limits. The limits are applied by updating the clock rate in case it is
+ * higher than the corresponding frequency based on the requested cooling_state.
+ *
+ * Return: 0 on success, an error code otherwise (-EINVAL in case wrong
+ * cooling state).
+ */
+static int clock_cooling_apply(struct clock_cooling_device *ccdev,
+ unsigned long cooling_state)
+{
+ unsigned long clip_freq, cur_freq;
+ int ret = 0;
+
+ /* Here we write the clipping */
+ /* Check if the old cooling action is same as new cooling action */
+ if (ccdev->clock_state == cooling_state)
+ return 0;
+
+ clip_freq = clock_cooling_get_frequency(ccdev, cooling_state);
+ if (!clip_freq)
+ return -EINVAL;
+
+ cur_freq = clk_get_rate(ccdev->clk);
+
+ mutex_lock(&ccdev->lock);
+ ccdev->clock_state = cooling_state;
+ ccdev->clock_val = clip_freq;
+ /* enforce clock level */
+ if (cur_freq > clip_freq)
+ ret = clk_set_rate(ccdev->clk, clip_freq);
+ mutex_unlock(&ccdev->lock);
+
+ return ret;
+}
+
+/**
+ * clock_cooling_clock_notifier - notifier callback on clock rate changes.
+ * @nb: struct notifier_block * with callback info.
+ * @event: value showing clock event for which this function invoked.
+ * @data: callback-specific data
+ *
+ * Callback to hijack the notification on clock transition.
+ * Every time there is a clock change, we intercept all pre change events
+ * and block the transition in case the new rate infringes thermal limits.
+ *
+ * Return: NOTIFY_DONE (success) or NOTIFY_BAD (new_rate > thermal limit).
+ */
+static int clock_cooling_clock_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct clk_notifier_data *ndata = data;
+ struct clock_cooling_device *ccdev = to_clock_cooling_device(nb);
+
+ switch (event) {
+ case PRE_RATE_CHANGE:
+ /*
+ * checks on current state
+ * TODO: current method is not best we can find as it
+ * allows possibly voltage transitions, in case DVFS
+ * layer is also hijacking clock pre notifications.
+ */
+ if (ndata->new_rate > ccdev->clock_val)
+ return NOTIFY_BAD;
+ /* fall through */
+ case POST_RATE_CHANGE:
+ case ABORT_RATE_CHANGE:
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+/* clock cooling device thermal callback functions are defined below */
+
+/**
+ * clock_cooling_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ *
+ * Callback for the thermal cooling device to return the clock
+ * max cooling state.
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+static int clock_cooling_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct clock_cooling_device *ccdev = cdev->devdata;
+ unsigned long count = 0;
+ int ret;
+
+ ret = clock_cooling_get_property(ccdev, 0, &count, GET_MAXL);
+ if (!ret)
+ *state = count;
+
+ return ret;
+}
+
+/**
+ * clock_cooling_get_cur_state - function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling state.
+ *
+ * Callback for the thermal cooling device to return the clock
+ * current cooling state.
+ *
+ * Return: 0 (success)
+ */
+static int clock_cooling_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct clock_cooling_device *ccdev = cdev->devdata;
+
+ *state = ccdev->clock_state;
+
+ return 0;
+}
+
+/**
+ * clock_cooling_set_cur_state - function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ *
+ * Callback for the thermal cooling device to change the clock cooling
+ * current cooling state.
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+static int clock_cooling_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct clock_cooling_device *clock_device = cdev->devdata;
+
+ return clock_cooling_apply(clock_device, state);
+}
+
+/* Bind clock callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const clock_cooling_ops = {
+ .get_max_state = clock_cooling_get_max_state,
+ .get_cur_state = clock_cooling_get_cur_state,
+ .set_cur_state = clock_cooling_set_cur_state,
+};
+
+/**
+ * clock_cooling_register - function to create clock cooling device.
+ * @dev: struct device pointer to the device used as clock cooling device.
+ * @clock_name: string containing the clock used as cooling mechanism.
+ *
+ * This interface function registers the clock cooling device with the name
+ * "thermal-clock-%x". The cooling device is based on clock frequencies.
+ * The struct device is assumed to be capable of DVFS transitions.
+ * The OPP layer is used to fetch and fill the available frequencies for
+ * the referred device. The ordered frequency table is used to control
+ * the clock cooling device cooling states and to limit clock transitions
+ * based on the cooling state requested by the thermal framework.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+clock_cooling_register(struct device *dev, const char *clock_name)
+{
+ struct thermal_cooling_device *cdev;
+ struct clock_cooling_device *ccdev = NULL;
+ char dev_name[THERMAL_NAME_LENGTH];
+ int ret = 0;
+
+ ccdev = devm_kzalloc(dev, sizeof(*ccdev), GFP_KERNEL);
+ if (!ccdev)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&ccdev->lock);
+ ccdev->dev = dev;
+ ccdev->clk = devm_clk_get(dev, clock_name);
+ if (IS_ERR(ccdev->clk))
+ return ERR_CAST(ccdev->clk);
+
+ ret = ida_simple_get(&clock_ida, 0, 0, GFP_KERNEL);
+ if (ret < 0)
+ return ERR_PTR(ret);
+ ccdev->id = ret;
+
+ snprintf(dev_name, sizeof(dev_name), "thermal-clock-%d", ccdev->id);
+
+ cdev = thermal_cooling_device_register(dev_name, ccdev,
+ &clock_cooling_ops);
+ if (IS_ERR(cdev)) {
+ ida_simple_remove(&clock_ida, ccdev->id);
+ return ERR_PTR(-EINVAL);
+ }
+ ccdev->cdev = cdev;
+ ccdev->clk_rate_change_nb.notifier_call = clock_cooling_clock_notifier;
+
+ /* Assuming someone has already filled the opp table for this device */
+ ret = dev_pm_opp_init_cpufreq_table(dev, &ccdev->freq_table);
+ if (ret) {
+ ida_simple_remove(&clock_ida, ccdev->id);
+ return ERR_PTR(ret);
+ }
+ ccdev->clock_state = 0;
+ ccdev->clock_val = clock_cooling_get_frequency(ccdev, 0);
+
+ clk_notifier_register(ccdev->clk, &ccdev->clk_rate_change_nb);
+
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(clock_cooling_register);
+
+/**
+ * clock_cooling_unregister - function to remove clock cooling device.
+ * @cdev: thermal cooling device pointer.
+ *
+ * This interface function unregisters the "thermal-clock-%x" cooling device.
+ */
+void clock_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct clock_cooling_device *ccdev;
+
+ if (!cdev)
+ return;
+
+ ccdev = cdev->devdata;
+
+ clk_notifier_unregister(ccdev->clk, &ccdev->clk_rate_change_nb);
+ dev_pm_opp_free_cpufreq_table(ccdev->dev, &ccdev->freq_table);
+
+ thermal_cooling_device_unregister(ccdev->cdev);
+ ida_simple_remove(&clock_ida, ccdev->id);
+}
+EXPORT_SYMBOL_GPL(clock_cooling_unregister);
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 000000000..8592ef694
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,820 @@
+/*
+ * linux/drivers/thermal/cpu_cooling.c
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+ * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * Copyright (C) 2014 Viresh Kumar <viresh.kumar@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+#include <trace/events/thermal.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 freq_table - frequency table along with power entries
+ * @frequency: frequency in KHz
+ * @power: power in mW
+ *
+ * This structure is built when the cooling device registers and helps
+ * in translating frequency to power and vice versa.
+ */
+struct freq_table {
+ u32 frequency;
+ u32 power;
+};
+
+/**
+ * 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
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ * registered.
+ * @last_load: load measured by the latest call to cpufreq_get_requested_power()
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ * cooling devices.
+ * @clipped_freq: integer value representing the absolute value of the clipped
+ * frequency.
+ * @max_level: maximum cooling level. One less than total number of valid
+ * cpufreq frequencies.
+ * @freq_table: Freq table in descending order of frequencies
+ * @cdev: thermal_cooling_device pointer to keep track of the
+ * registered cooling device.
+ * @policy: cpufreq policy.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ * @idle_time: idle time stats
+ *
+ * This structure is required for keeping information of each registered
+ * cpufreq_cooling_device.
+ */
+struct cpufreq_cooling_device {
+ int id;
+ u32 last_load;
+ unsigned int cpufreq_state;
+ unsigned int clipped_freq;
+ unsigned int max_level;
+ struct freq_table *freq_table; /* In descending order */
+ struct thermal_cooling_device *cdev;
+ struct cpufreq_policy *policy;
+ struct list_head node;
+ struct time_in_idle *idle_time;
+};
+
+static DEFINE_IDA(cpufreq_ida);
+static DEFINE_MUTEX(cooling_list_lock);
+static LIST_HEAD(cpufreq_cdev_list);
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * 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 freq_table *freq_table = cpufreq_cdev->freq_table;
+ unsigned long level;
+
+ for (level = 1; level <= cpufreq_cdev->max_level; level++)
+ if (freq > freq_table[level].frequency)
+ break;
+
+ return level - 1;
+}
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb: struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ *
+ * Callback to hijack the notification on cpufreq policy transition.
+ * Every time there is a change in policy, we will intercept and
+ * update the cpufreq policy with thermal constraints.
+ *
+ * Return: 0 (success)
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct cpufreq_policy *policy = data;
+ unsigned long clipped_freq;
+ struct cpufreq_cooling_device *cpufreq_cdev;
+
+ if (event != CPUFREQ_ADJUST)
+ return NOTIFY_DONE;
+
+ mutex_lock(&cooling_list_lock);
+ list_for_each_entry(cpufreq_cdev, &cpufreq_cdev_list, node) {
+ /*
+ * A new copy of the policy is sent to the notifier and can't
+ * compare that directly.
+ */
+ if (policy->cpu != cpufreq_cdev->policy->cpu)
+ continue;
+
+ /*
+ * policy->max is the maximum allowed frequency defined by user
+ * and clipped_freq is the maximum that thermal constraints
+ * allow.
+ *
+ * If clipped_freq is lower than policy->max, then we need to
+ * readjust policy->max.
+ *
+ * But, if clipped_freq is greater than policy->max, we don't
+ * need to do anything.
+ */
+ clipped_freq = cpufreq_cdev->clipped_freq;
+
+ if (policy->max > clipped_freq)
+ cpufreq_verify_within_limits(policy, 0, clipped_freq);
+ break;
+ }
+ mutex_unlock(&cooling_list_lock);
+
+ return NOTIFY_OK;
+}
+
+/**
+ * update_freq_table() - Update the freq table with power numbers
+ * @cpufreq_cdev: the cpufreq cooling device in which to update the table
+ * @capacitance: dynamic power coefficient for these cpus
+ *
+ * Update the freq table with power numbers. This table will be used in
+ * cpu_power_to_freq() and cpu_freq_to_power() to convert between power and
+ * frequency efficiently. Power is stored in mW, frequency in KHz. The
+ * resulting table is in descending order.
+ *
+ * Return: 0 on success, -EINVAL if there are no OPPs for any CPUs,
+ * or -ENOMEM if we run out of memory.
+ */
+static int update_freq_table(struct cpufreq_cooling_device *cpufreq_cdev,
+ u32 capacitance)
+{
+ struct freq_table *freq_table = cpufreq_cdev->freq_table;
+ struct dev_pm_opp *opp;
+ struct device *dev = NULL;
+ int num_opps = 0, cpu = cpufreq_cdev->policy->cpu, i;
+
+ dev = get_cpu_device(cpu);
+ if (unlikely(!dev)) {
+ dev_warn(&cpufreq_cdev->cdev->device,
+ "No cpu device for cpu %d\n", cpu);
+ return -ENODEV;
+ }
+
+ num_opps = dev_pm_opp_get_opp_count(dev);
+ if (num_opps < 0)
+ return num_opps;
+
+ /*
+ * The cpufreq table is also built from the OPP table and so the count
+ * should match.
+ */
+ if (num_opps != cpufreq_cdev->max_level + 1) {
+ dev_warn(dev, "Number of OPPs not matching with max_levels\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i <= cpufreq_cdev->max_level; i++) {
+ unsigned long freq = freq_table[i].frequency * 1000;
+ u32 freq_mhz = freq_table[i].frequency / 1000;
+ u64 power;
+ u32 voltage_mv;
+
+ /*
+ * Find ceil frequency as 'freq' may be slightly lower than OPP
+ * freq due to truncation while converting to kHz.
+ */
+ opp = dev_pm_opp_find_freq_ceil(dev, &freq);
+ if (IS_ERR(opp)) {
+ dev_err(dev, "failed to get opp for %lu frequency\n",
+ freq);
+ return -EINVAL;
+ }
+
+ voltage_mv = dev_pm_opp_get_voltage(opp) / 1000;
+ dev_pm_opp_put(opp);
+
+ /*
+ * Do the multiplication with MHz and millivolt so as
+ * to not overflow.
+ */
+ power = (u64)capacitance * freq_mhz * voltage_mv * voltage_mv;
+ do_div(power, 1000000000);
+
+ /* power is stored in mW */
+ freq_table[i].power = power;
+ }
+
+ return 0;
+}
+
+static u32 cpu_freq_to_power(struct cpufreq_cooling_device *cpufreq_cdev,
+ u32 freq)
+{
+ int i;
+ struct freq_table *freq_table = cpufreq_cdev->freq_table;
+
+ for (i = 1; i <= cpufreq_cdev->max_level; i++)
+ if (freq > freq_table[i].frequency)
+ break;
+
+ return freq_table[i - 1].power;
+}
+
+static u32 cpu_power_to_freq(struct cpufreq_cooling_device *cpufreq_cdev,
+ u32 power)
+{
+ int i;
+ struct freq_table *freq_table = cpufreq_cdev->freq_table;
+
+ for (i = 0; i < cpufreq_cdev->max_level; i++)
+ if (power >= freq_table[i].power)
+ break;
+
+ return freq_table[i].frequency;
+}
+
+/**
+ * get_load() - get load for a cpu since last updated
+ * @cpufreq_cdev: &struct cpufreq_cooling_device for this cpu
+ * @cpu: cpu number
+ * @cpu_idx: index of the cpu in time_in_idle*
+ *
+ * Return: The average load of cpu @cpu in percentage since this
+ * function was last called.
+ */
+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;
+}
+
+/**
+ * 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 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, an error code otherwise.
+ */
+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, an error code otherwise.
+ */
+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 clip_freq;
+
+ /* Request state should be less than max_level */
+ if (WARN_ON(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;
+
+ clip_freq = cpufreq_cdev->freq_table[state].frequency;
+ cpufreq_cdev->cpufreq_state = state;
+ cpufreq_cdev->clipped_freq = clip_freq;
+
+ cpufreq_update_policy(cpufreq_cdev->policy->cpu);
+
+ return 0;
+}
+
+/**
+ * cpufreq_get_requested_power() - get the current power
+ * @cdev: &thermal_cooling_device pointer
+ * @tz: a valid thermal zone device pointer
+ * @power: pointer in which to store the resulting power
+ *
+ * Calculate the current power consumption of the cpus in milliwatts
+ * 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, -E* if getting the static power failed.
+ */
+static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz,
+ 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;
+ u32 *load_cpu = NULL;
+
+ freq = cpufreq_quick_get(policy->cpu);
+
+ if (trace_thermal_power_cpu_get_power_enabled()) {
+ u32 ncpus = cpumask_weight(policy->related_cpus);
+
+ load_cpu = kcalloc(ncpus, sizeof(*load_cpu), GFP_KERNEL);
+ }
+
+ for_each_cpu(cpu, policy->related_cpus) {
+ u32 load;
+
+ if (cpu_online(cpu))
+ load = get_load(cpufreq_cdev, cpu, i);
+ else
+ load = 0;
+
+ total_load += load;
+ if (load_cpu)
+ load_cpu[i] = load;
+
+ i++;
+ }
+
+ cpufreq_cdev->last_load = total_load;
+
+ *power = get_dynamic_power(cpufreq_cdev, freq);
+
+ if (load_cpu) {
+ trace_thermal_power_cpu_get_power(policy->related_cpus, freq,
+ load_cpu, i, *power);
+
+ kfree(load_cpu);
+ }
+
+ return 0;
+}
+
+/**
+ * cpufreq_state2power() - convert a cpu cdev state to power consumed
+ * @cdev: &thermal_cooling_device pointer
+ * @tz: a valid thermal zone device pointer
+ * @state: cooling device state to be converted
+ * @power: pointer in which to store the resulting power
+ *
+ * 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 could not
+ * be converted into a frequency or other -E* if there was an error
+ * when calculating the static power.
+ */
+static int cpufreq_state2power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz,
+ unsigned long state, u32 *power)
+{
+ unsigned int freq, num_cpus;
+ struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
+
+ /* Request state should be less than max_level */
+ if (WARN_ON(state > cpufreq_cdev->max_level))
+ return -EINVAL;
+
+ num_cpus = cpumask_weight(cpufreq_cdev->policy->cpus);
+
+ freq = cpufreq_cdev->freq_table[state].frequency;
+ *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
+ * @tz: a valid thermal zone device pointer
+ * @power: power in milliwatts to be converted
+ * @state: pointer in which to store the resulting state
+ *
+ * Calculate a cooling device state for the cpus described by @cdev
+ * that would allow them to consume at most @power mW and store it in
+ * @state. Note that this calculation depends on external factors
+ * such as the cpu load or the current static power. Calling this
+ * function with the same power as input can yield different cooling
+ * device states depending on those external factors.
+ *
+ * Return: 0 on success, -ENODEV if no cpus are online or -EINVAL if
+ * the calculated frequency could not be converted to a valid state.
+ * The latter should not happen unless the frequencies available to
+ * cpufreq have changed since the initialization of the cpu cooling
+ * device.
+ */
+static int cpufreq_power2state(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz, u32 power,
+ unsigned long *state)
+{
+ unsigned int cur_freq, target_freq;
+ u32 last_load, normalised_power;
+ struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
+ struct cpufreq_policy *policy = cpufreq_cdev->policy;
+
+ cur_freq = cpufreq_quick_get(policy->cpu);
+ power = power > 0 ? power : 0;
+ 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;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+
+static struct thermal_cooling_device_ops cpufreq_cooling_ops = {
+ .get_max_state = cpufreq_get_max_state,
+ .get_cur_state = cpufreq_get_cur_state,
+ .set_cur_state = cpufreq_set_cur_state,
+};
+
+static struct thermal_cooling_device_ops cpufreq_power_cooling_ops = {
+ .get_max_state = cpufreq_get_max_state,
+ .get_cur_state = cpufreq_get_cur_state,
+ .set_cur_state = cpufreq_set_cur_state,
+ .get_requested_power = cpufreq_get_requested_power,
+ .state2power = cpufreq_state2power,
+ .power2state = cpufreq_power2state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+ .notifier_call = cpufreq_thermal_notifier,
+};
+
+static unsigned int find_next_max(struct cpufreq_frequency_table *table,
+ unsigned int prev_max)
+{
+ struct cpufreq_frequency_table *pos;
+ unsigned int max = 0;
+
+ cpufreq_for_each_valid_entry(pos, table) {
+ if (pos->frequency > max && pos->frequency < prev_max)
+ max = pos->frequency;
+ }
+
+ return max;
+}
+
+/**
+ * __cpufreq_cooling_register - helper function to create cpufreq cooling device
+ * @np: a valid struct device_node to the cooling device device tree node
+ * @policy: cpufreq policy
+ * Normally this should be same as cpufreq policy->related_cpus.
+ * @capacitance: dynamic power coefficient for these cpus
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "thermal-cpufreq-%x". 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, u32 capacitance)
+{
+ struct thermal_cooling_device *cdev;
+ struct cpufreq_cooling_device *cpufreq_cdev;
+ char dev_name[THERMAL_NAME_LENGTH];
+ unsigned int freq, i, num_cpus;
+ int ret;
+ struct thermal_cooling_device_ops *cooling_ops;
+ bool first;
+
+ if (IS_ERR_OR_NULL(policy)) {
+ pr_err("%s: cpufreq policy isn't valid: %p\n", __func__, policy);
+ return ERR_PTR(-EINVAL);
+ }
+
+ i = cpufreq_table_count_valid_entries(policy);
+ if (!i) {
+ pr_debug("%s: CPUFreq table not found or has no valid entries\n",
+ __func__);
+ return ERR_PTR(-ENODEV);
+ }
+
+ cpufreq_cdev = kzalloc(sizeof(*cpufreq_cdev), GFP_KERNEL);
+ if (!cpufreq_cdev)
+ return ERR_PTR(-ENOMEM);
+
+ cpufreq_cdev->policy = policy;
+ num_cpus = cpumask_weight(policy->related_cpus);
+ cpufreq_cdev->idle_time = kcalloc(num_cpus,
+ sizeof(*cpufreq_cdev->idle_time),
+ GFP_KERNEL);
+ if (!cpufreq_cdev->idle_time) {
+ cdev = ERR_PTR(-ENOMEM);
+ goto free_cdev;
+ }
+
+ /* max_level is an index, not a counter */
+ cpufreq_cdev->max_level = i - 1;
+
+ cpufreq_cdev->freq_table = kmalloc_array(i,
+ sizeof(*cpufreq_cdev->freq_table),
+ GFP_KERNEL);
+ if (!cpufreq_cdev->freq_table) {
+ cdev = ERR_PTR(-ENOMEM);
+ goto free_idle_time;
+ }
+
+ ret = ida_simple_get(&cpufreq_ida, 0, 0, GFP_KERNEL);
+ if (ret < 0) {
+ cdev = ERR_PTR(ret);
+ goto free_table;
+ }
+ cpufreq_cdev->id = ret;
+
+ snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d",
+ cpufreq_cdev->id);
+
+ /* Fill freq-table in descending order of frequencies */
+ for (i = 0, freq = -1; i <= cpufreq_cdev->max_level; i++) {
+ freq = find_next_max(policy->freq_table, freq);
+ cpufreq_cdev->freq_table[i].frequency = freq;
+
+ /* Warn for duplicate entries */
+ if (!freq)
+ pr_warn("%s: table has duplicate entries\n", __func__);
+ else
+ pr_debug("%s: freq:%u KHz\n", __func__, freq);
+ }
+
+ if (capacitance) {
+ ret = update_freq_table(cpufreq_cdev, capacitance);
+ if (ret) {
+ cdev = ERR_PTR(ret);
+ goto remove_ida;
+ }
+
+ cooling_ops = &cpufreq_power_cooling_ops;
+ } else {
+ cooling_ops = &cpufreq_cooling_ops;
+ }
+
+ cdev = thermal_of_cooling_device_register(np, dev_name, cpufreq_cdev,
+ cooling_ops);
+ if (IS_ERR(cdev))
+ goto remove_ida;
+
+ cpufreq_cdev->clipped_freq = cpufreq_cdev->freq_table[0].frequency;
+ cpufreq_cdev->cdev = cdev;
+
+ mutex_lock(&cooling_list_lock);
+ /* Register the notifier for first cpufreq cooling device */
+ first = list_empty(&cpufreq_cdev_list);
+ list_add(&cpufreq_cdev->node, &cpufreq_cdev_list);
+ mutex_unlock(&cooling_list_lock);
+
+ if (first)
+ cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+
+ return cdev;
+
+remove_ida:
+ ida_simple_remove(&cpufreq_ida, cpufreq_cdev->id);
+free_table:
+ kfree(cpufreq_cdev->freq_table);
+free_idle_time:
+ kfree(cpufreq_cdev->idle_time);
+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
+ * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ * cooling devices.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+cpufreq_cooling_register(struct cpufreq_policy *policy)
+{
+ return __cpufreq_cooling_register(NULL, policy, 0);
+}
+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
+ * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ * cooling devices. Using this API, the cpufreq cooling device will be
+ * linked to the device tree node provided.
+ *
+ * Using this function, the cooling device will implement the power
+ * extensions by using a simple cpu power model. The cpus must have
+ * registered their OPPs using the OPP library.
+ *
+ * It also takes into account, if property present in policy CPU node, the
+ * static power consumed by the cpu.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * and NULL on failure.
+ */
+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;
+ u32 capacitance = 0;
+
+ if (!np) {
+ pr_err("cpu_cooling: OF node not available for cpu%d\n",
+ policy->cpu);
+ return NULL;
+ }
+
+ if (of_find_property(np, "#cooling-cells", NULL)) {
+ of_property_read_u32(np, "dynamic-power-coefficient",
+ &capacitance);
+
+ cdev = __cpufreq_cooling_register(np, policy, capacitance);
+ if (IS_ERR(cdev)) {
+ pr_err("cpu_cooling: cpu%d is not running 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 "thermal-cpufreq-%x" cooling device.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct cpufreq_cooling_device *cpufreq_cdev;
+ bool last;
+
+ if (!cdev)
+ return;
+
+ cpufreq_cdev = cdev->devdata;
+
+ mutex_lock(&cooling_list_lock);
+ list_del(&cpufreq_cdev->node);
+ /* Unregister the notifier for the last cpufreq cooling device */
+ last = list_empty(&cpufreq_cdev_list);
+ mutex_unlock(&cooling_list_lock);
+
+ if (last)
+ cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+
+ thermal_cooling_device_unregister(cpufreq_cdev->cdev);
+ ida_simple_remove(&cpufreq_ida, cpufreq_cdev->id);
+ kfree(cpufreq_cdev->idle_time);
+ kfree(cpufreq_cdev->freq_table);
+ kfree(cpufreq_cdev);
+}
+EXPORT_SYMBOL_GPL(cpufreq_cooling_unregister);
diff --git a/drivers/thermal/da9062-thermal.c b/drivers/thermal/da9062-thermal.c
new file mode 100644
index 000000000..01b0cb994
--- /dev/null
+++ b/drivers/thermal/da9062-thermal.c
@@ -0,0 +1,315 @@
+/*
+ * Thermal device driver for DA9062 and DA9061
+ * Copyright (C) 2017 Dialog Semiconductor
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ */
+
+/* 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)
+
+struct da9062_thermal_config {
+ const char *name;
+};
+
+struct da9062_thermal {
+ struct da9062 *hw;
+ struct delayed_work work;
+ struct thermal_zone_device *zone;
+ enum thermal_device_mode mode;
+ struct mutex lock; /* protection for da9062_thermal temperature */
+ int temperature;
+ int irq;
+ 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);
+
+ delay = msecs_to_jiffies(thermal->zone->passive_delay);
+ 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_mode(struct thermal_zone_device *z,
+ enum thermal_device_mode *mode)
+{
+ struct da9062_thermal *thermal = z->devdata;
+ *mode = thermal->mode;
+ return 0;
+}
+
+static int da9062_thermal_get_trip_type(struct thermal_zone_device *z,
+ int trip,
+ enum thermal_trip_type *type)
+{
+ struct da9062_thermal *thermal = z->devdata;
+
+ switch (trip) {
+ case 0:
+ *type = THERMAL_TRIP_HOT;
+ break;
+ default:
+ dev_err(thermal->dev,
+ "Driver does not support more than 1 trip-wire\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int da9062_thermal_get_trip_temp(struct thermal_zone_device *z,
+ int trip,
+ int *temp)
+{
+ struct da9062_thermal *thermal = z->devdata;
+
+ switch (trip) {
+ case 0:
+ *temp = DA9062_MILLI_CELSIUS(125);
+ break;
+ default:
+ dev_err(thermal->dev,
+ "Driver does not support more than 1 trip-wire\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int da9062_thermal_get_temp(struct thermal_zone_device *z,
+ int *temp)
+{
+ struct da9062_thermal *thermal = z->devdata;
+
+ mutex_lock(&thermal->lock);
+ *temp = thermal->temperature;
+ mutex_unlock(&thermal->lock);
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops da9062_thermal_ops = {
+ .get_temp = da9062_thermal_get_temp,
+ .get_mode = da9062_thermal_get_mode,
+ .get_trip_type = da9062_thermal_get_trip_type,
+ .get_trip_temp = da9062_thermal_get_trip_temp,
+};
+
+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;
+ unsigned int pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD;
+ 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->mode = THERMAL_DEVICE_ENABLED;
+ thermal->dev = &pdev->dev;
+
+ INIT_DELAYED_WORK(&thermal->work, da9062_thermal_poll_on);
+ mutex_init(&thermal->lock);
+
+ thermal->zone = thermal_zone_device_register(thermal->config->name,
+ 1, 0, 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;
+ }
+
+ dev_dbg(&pdev->dev,
+ "TJUNC temperature polling period set at %d ms\n",
+ thermal->zone->passive_delay);
+
+ ret = platform_get_irq_byname(pdev, "THERMAL");
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to get platform IRQ.\n");
+ 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 int da9062_thermal_remove(struct platform_device *pdev)
+{
+ struct da9062_thermal *thermal = platform_get_drvdata(pdev);
+
+ free_irq(thermal->irq, thermal);
+ cancel_delayed_work_sync(&thermal->work);
+ thermal_zone_device_unregister(thermal->zone);
+ return 0;
+}
+
+static struct platform_driver da9062_thermal_driver = {
+ .probe = da9062_thermal_probe,
+ .remove = da9062_thermal_remove,
+ .driver = {
+ .name = "da9062-thermal",
+ .of_match_table = da9062_compatible_reg_id_table,
+ },
+};
+
+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..f491faf16
--- /dev/null
+++ b/drivers/thermal/db8500_thermal.c
@@ -0,0 +1,533 @@
+/*
+ * db8500_thermal.c - DB8500 Thermal Management Implementation
+ *
+ * Copyright (C) 2012 ST-Ericsson
+ * Copyright (C) 2012 Linaro Ltd.
+ *
+ * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/cpu_cooling.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/dbx500-prcmu.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_data/db8500_thermal.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
+
+struct db8500_thermal_zone {
+ struct thermal_zone_device *therm_dev;
+ struct mutex th_lock;
+ struct work_struct therm_work;
+ struct db8500_thsens_platform_data *trip_tab;
+ enum thermal_device_mode mode;
+ enum thermal_trend trend;
+ unsigned long cur_temp_pseudo;
+ unsigned int cur_index;
+};
+
+/* Local function to check if thermal zone matches cooling devices */
+static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
+ struct db8500_trip_point *trip_point)
+{
+ int i;
+
+ if (!strlen(cdev->type))
+ return -EINVAL;
+
+ for (i = 0; i < COOLING_DEV_MAX; i++) {
+ if (!strcmp(trip_point->cdev_name[i], cdev->type))
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+/* Callback to bind cooling device to thermal zone */
+static int db8500_cdev_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned long max_state, upper, lower;
+ int i, ret = -EINVAL;
+
+ cdev->ops->get_max_state(cdev, &max_state);
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
+ continue;
+
+ upper = lower = i > max_state ? max_state : i;
+
+ ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
+ upper, lower, THERMAL_WEIGHT_DEFAULT);
+
+ dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
+ i, ret, ret ? "fail" : "succeed");
+ }
+
+ return ret;
+}
+
+/* Callback to unbind cooling device from thermal zone */
+static int db8500_cdev_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ int i, ret = -EINVAL;
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
+ continue;
+
+ ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
+
+ dev_info(&cdev->device, "%s unbind from %d: %s\n", cdev->type,
+ i, ret ? "fail" : "succeed");
+ }
+
+ return ret;
+}
+
+/* Callback to get current temperature */
+static int db8500_sys_get_temp(struct thermal_zone_device *thermal, int *temp)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ /*
+ * 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 = pzone->cur_temp_pseudo;
+
+ return 0;
+}
+
+/* Callback to get temperature changing trend */
+static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trend *trend)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ *trend = pzone->trend;
+
+ return 0;
+}
+
+/* Callback to get thermal zone mode */
+static int db8500_sys_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ mutex_lock(&pzone->th_lock);
+ *mode = pzone->mode;
+ mutex_unlock(&pzone->th_lock);
+
+ return 0;
+}
+
+/* Callback to set thermal zone mode */
+static int db8500_sys_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ mutex_lock(&pzone->th_lock);
+
+ pzone->mode = mode;
+ if (mode == THERMAL_DEVICE_ENABLED)
+ schedule_work(&pzone->therm_work);
+
+ mutex_unlock(&pzone->th_lock);
+
+ return 0;
+}
+
+/* Callback to get trip point type */
+static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trip_type *type)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+
+ if (trip >= ptrips->num_trips)
+ return -EINVAL;
+
+ *type = ptrips->trip_points[trip].type;
+
+ return 0;
+}
+
+/* Callback to get trip point temperature */
+static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
+ int trip, int *temp)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+
+ if (trip >= ptrips->num_trips)
+ return -EINVAL;
+
+ *temp = ptrips->trip_points[trip].temp;
+
+ return 0;
+}
+
+/* Callback to get critical trip point temperature */
+static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
+ int *temp)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ int i;
+
+ for (i = ptrips->num_trips - 1; i > 0; i--) {
+ if (ptrips->trip_points[i].type == THERMAL_TRIP_CRITICAL) {
+ *temp = ptrips->trip_points[i].temp;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static struct thermal_zone_device_ops thdev_ops = {
+ .bind = db8500_cdev_bind,
+ .unbind = db8500_cdev_unbind,
+ .get_temp = db8500_sys_get_temp,
+ .get_trend = db8500_sys_get_trend,
+ .get_mode = db8500_sys_get_mode,
+ .set_mode = db8500_sys_set_mode,
+ .get_trip_type = db8500_sys_get_trip_type,
+ .get_trip_temp = db8500_sys_get_trip_temp,
+ .get_crit_temp = db8500_sys_get_crit_temp,
+};
+
+static void db8500_thermal_update_config(struct db8500_thermal_zone *pzone,
+ unsigned int idx, enum thermal_trend trend,
+ unsigned long next_low, unsigned long next_high)
+{
+ prcmu_stop_temp_sense();
+
+ pzone->cur_index = idx;
+ pzone->cur_temp_pseudo = (next_low + next_high)/2;
+ pzone->trend = trend;
+
+ prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
+ prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
+}
+
+static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
+{
+ struct db8500_thermal_zone *pzone = irq_data;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned int idx = pzone->cur_index;
+ unsigned long next_low, next_high;
+
+ if (unlikely(idx == 0))
+ /* Meaningless for thermal management, ignoring it */
+ return IRQ_HANDLED;
+
+ if (idx == 1) {
+ next_high = ptrips->trip_points[0].temp;
+ next_low = PRCMU_DEFAULT_LOW_TEMP;
+ } else {
+ next_high = ptrips->trip_points[idx-1].temp;
+ next_low = ptrips->trip_points[idx-2].temp;
+ }
+ idx -= 1;
+
+ db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
+ next_low, next_high);
+
+ dev_dbg(&pzone->therm_dev->device,
+ "PRCMU set max %ld, min %ld\n", next_high, next_low);
+
+ schedule_work(&pzone->therm_work);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
+{
+ struct db8500_thermal_zone *pzone = irq_data;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned int idx = pzone->cur_index;
+ unsigned long next_low, next_high;
+
+ if (idx < ptrips->num_trips - 1) {
+ next_high = ptrips->trip_points[idx+1].temp;
+ next_low = ptrips->trip_points[idx].temp;
+ idx += 1;
+
+ db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING,
+ next_low, next_high);
+
+ dev_dbg(&pzone->therm_dev->device,
+ "PRCMU set max %ld, min %ld\n", next_high, next_low);
+ } else if (idx == ptrips->num_trips - 1)
+ pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
+
+ schedule_work(&pzone->therm_work);
+
+ return IRQ_HANDLED;
+}
+
+static void db8500_thermal_work(struct work_struct *work)
+{
+ enum thermal_device_mode cur_mode;
+ struct db8500_thermal_zone *pzone;
+
+ pzone = container_of(work, struct db8500_thermal_zone, therm_work);
+
+ mutex_lock(&pzone->th_lock);
+ cur_mode = pzone->mode;
+ mutex_unlock(&pzone->th_lock);
+
+ if (cur_mode == THERMAL_DEVICE_DISABLED)
+ return;
+
+ thermal_zone_device_update(pzone->therm_dev, THERMAL_EVENT_UNSPECIFIED);
+ dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n");
+}
+
+#ifdef CONFIG_OF
+static struct db8500_thsens_platform_data*
+ db8500_thermal_parse_dt(struct platform_device *pdev)
+{
+ struct db8500_thsens_platform_data *ptrips;
+ struct device_node *np = pdev->dev.of_node;
+ char prop_name[32];
+ const char *tmp_str;
+ u32 tmp_data;
+ int i, j;
+
+ ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
+ if (!ptrips)
+ return NULL;
+
+ if (of_property_read_u32(np, "num-trips", &tmp_data))
+ goto err_parse_dt;
+
+ if (tmp_data > THERMAL_MAX_TRIPS)
+ goto err_parse_dt;
+
+ ptrips->num_trips = tmp_data;
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ sprintf(prop_name, "trip%d-temp", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data))
+ goto err_parse_dt;
+
+ ptrips->trip_points[i].temp = tmp_data;
+
+ sprintf(prop_name, "trip%d-type", i);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto err_parse_dt;
+
+ if (!strcmp(tmp_str, "active"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
+ else if (!strcmp(tmp_str, "passive"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
+ else if (!strcmp(tmp_str, "hot"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
+ else if (!strcmp(tmp_str, "critical"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ else
+ goto err_parse_dt;
+
+ sprintf(prop_name, "trip%d-cdev-num", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data))
+ goto err_parse_dt;
+
+ if (tmp_data > COOLING_DEV_MAX)
+ goto err_parse_dt;
+
+ for (j = 0; j < tmp_data; j++) {
+ sprintf(prop_name, "trip%d-cdev-name%d", i, j);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto err_parse_dt;
+
+ if (strlen(tmp_str) >= THERMAL_NAME_LENGTH)
+ goto err_parse_dt;
+
+ strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
+ }
+ }
+ return ptrips;
+
+err_parse_dt:
+ dev_err(&pdev->dev, "Parsing device tree data error.\n");
+ return NULL;
+}
+#else
+static inline struct db8500_thsens_platform_data*
+ db8500_thermal_parse_dt(struct platform_device *pdev)
+{
+ return NULL;
+}
+#endif
+
+static int db8500_thermal_probe(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *pzone = NULL;
+ struct db8500_thsens_platform_data *ptrips = NULL;
+ struct device_node *np = pdev->dev.of_node;
+ int low_irq, high_irq, ret = 0;
+ unsigned long dft_low, dft_high;
+
+ if (np)
+ ptrips = db8500_thermal_parse_dt(pdev);
+ else
+ ptrips = dev_get_platdata(&pdev->dev);
+
+ if (!ptrips)
+ return -EINVAL;
+
+ pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
+ if (!pzone)
+ return -ENOMEM;
+
+ mutex_init(&pzone->th_lock);
+ mutex_lock(&pzone->th_lock);
+
+ pzone->mode = THERMAL_DEVICE_DISABLED;
+ pzone->trip_tab = ptrips;
+
+ INIT_WORK(&pzone->therm_work, db8500_thermal_work);
+
+ low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
+ if (low_irq < 0) {
+ dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n");
+ ret = low_irq;
+ goto out_unlock;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
+ prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ "dbx500_temp_low", pzone);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
+ goto out_unlock;
+ }
+
+ high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
+ if (high_irq < 0) {
+ dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
+ ret = high_irq;
+ goto out_unlock;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
+ prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ "dbx500_temp_high", pzone);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
+ goto out_unlock;
+ }
+
+ pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
+ ptrips->num_trips, 0, pzone, &thdev_ops, NULL, 0, 0);
+
+ if (IS_ERR(pzone->therm_dev)) {
+ dev_err(&pdev->dev, "Register thermal zone device failed.\n");
+ ret = PTR_ERR(pzone->therm_dev);
+ goto out_unlock;
+ }
+ dev_info(&pdev->dev, "Thermal zone device registered.\n");
+
+ dft_low = PRCMU_DEFAULT_LOW_TEMP;
+ dft_high = ptrips->trip_points[0].temp;
+
+ db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
+ dft_low, dft_high);
+
+ platform_set_drvdata(pdev, pzone);
+ pzone->mode = THERMAL_DEVICE_ENABLED;
+
+out_unlock:
+ mutex_unlock(&pzone->th_lock);
+
+ return ret;
+}
+
+static int db8500_thermal_remove(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(pzone->therm_dev);
+ cancel_work_sync(&pzone->therm_work);
+ mutex_destroy(&pzone->th_lock);
+
+ return 0;
+}
+
+static int db8500_thermal_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
+
+ flush_work(&pzone->therm_work);
+ prcmu_stop_temp_sense();
+
+ return 0;
+}
+
+static int db8500_thermal_resume(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned long dft_low, dft_high;
+
+ dft_low = PRCMU_DEFAULT_LOW_TEMP;
+ dft_high = ptrips->trip_points[0].temp;
+
+ db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
+ dft_low, dft_high);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id db8500_thermal_match[] = {
+ { .compatible = "stericsson,db8500-thermal" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, db8500_thermal_match);
+#endif
+
+static struct platform_driver db8500_thermal_driver = {
+ .driver = {
+ .name = "db8500-thermal",
+ .of_match_table = of_match_ptr(db8500_thermal_match),
+ },
+ .probe = db8500_thermal_probe,
+ .suspend = db8500_thermal_suspend,
+ .resume = db8500_thermal_resume,
+ .remove = db8500_thermal_remove,
+};
+
+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..ef5925688
--- /dev/null
+++ b/drivers/thermal/devfreq_cooling.c
@@ -0,0 +1,608 @@
+/*
+ * devfreq_cooling: Thermal cooling device implementation for devices using
+ * devfreq
+ *
+ * Copyright (C) 2014-2015 ARM Limited
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * TODO:
+ * - If OPPs are added or removed after devfreq cooling has
+ * registered, the devfreq cooling won't react to it.
+ */
+
+#include <linux/devfreq.h>
+#include <linux/devfreq_cooling.h>
+#include <linux/export.h>
+#include <linux/idr.h>
+#include <linux/slab.h>
+#include <linux/pm_opp.h>
+#include <linux/thermal.h>
+
+#include <trace/events/thermal.h>
+
+#define SCALE_ERROR_MITIGATION 100
+
+static DEFINE_IDA(devfreq_ida);
+
+/**
+ * struct devfreq_cooling_device - Devfreq cooling device
+ * @id: unique integer value corresponding to each
+ * devfreq_cooling_device registered.
+ * @cdev: Pointer to associated thermal cooling device.
+ * @devfreq: Pointer to associated devfreq device.
+ * @cooling_state: Current cooling state.
+ * @power_table: Pointer to table with maximum power draw for each
+ * cooling state. State is the index into the table, and
+ * the power is in mW.
+ * @freq_table: Pointer to a table with the frequencies sorted in descending
+ * order. You can index the table by cooling device state
+ * @freq_table_size: Size of the @freq_table and @power_table
+ * @power_ops: Pointer to devfreq_cooling_power, used to generate the
+ * @power_table.
+ * @res_util: Resource utilization scaling factor for the power.
+ * It is multiplied by 100 to minimize the error. It is used
+ * for estimation of the power budget instead of using
+ * 'utilization' (which is 'busy_time / 'total_time').
+ * The 'res_util' range is from 100 to (power_table[state] * 100)
+ * for the corresponding 'state'.
+ */
+struct devfreq_cooling_device {
+ int id;
+ struct thermal_cooling_device *cdev;
+ struct devfreq *devfreq;
+ unsigned long cooling_state;
+ u32 *power_table;
+ u32 *freq_table;
+ size_t freq_table_size;
+ struct devfreq_cooling_power *power_ops;
+ u32 res_util;
+ int capped_state;
+};
+
+/**
+ * partition_enable_opps() - disable all opps above a given state
+ * @dfc: Pointer to devfreq we are operating on
+ * @cdev_state: cooling device state we're setting
+ *
+ * Go through the OPPs of the device, enabling all OPPs until
+ * @cdev_state and disabling those frequencies above it.
+ */
+static int partition_enable_opps(struct devfreq_cooling_device *dfc,
+ unsigned long cdev_state)
+{
+ int i;
+ struct device *dev = dfc->devfreq->dev.parent;
+
+ for (i = 0; i < dfc->freq_table_size; i++) {
+ struct dev_pm_opp *opp;
+ int ret = 0;
+ unsigned int freq = dfc->freq_table[i];
+ bool want_enable = i >= cdev_state ? true : false;
+
+ opp = dev_pm_opp_find_freq_exact(dev, freq, !want_enable);
+
+ if (PTR_ERR(opp) == -ERANGE)
+ continue;
+ else if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ dev_pm_opp_put(opp);
+
+ if (want_enable)
+ ret = dev_pm_opp_enable(dev, freq);
+ else
+ ret = dev_pm_opp_disable(dev, freq);
+
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int devfreq_cooling_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+
+ *state = dfc->freq_table_size - 1;
+
+ 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;
+ int ret;
+
+ if (state == dfc->cooling_state)
+ return 0;
+
+ dev_dbg(dev, "Setting cooling state %lu\n", state);
+
+ if (state >= dfc->freq_table_size)
+ return -EINVAL;
+
+ ret = partition_enable_opps(dfc, state);
+ if (ret)
+ return ret;
+
+ dfc->cooling_state = state;
+
+ return 0;
+}
+
+/**
+ * freq_get_state() - get the cooling state corresponding to a frequency
+ * @dfc: Pointer to devfreq cooling device
+ * @freq: frequency in Hz
+ *
+ * Return: the cooling state associated with the @freq, or
+ * THERMAL_CSTATE_INVALID if it wasn't found.
+ */
+static unsigned long
+freq_get_state(struct devfreq_cooling_device *dfc, unsigned long freq)
+{
+ int i;
+
+ for (i = 0; i < dfc->freq_table_size; i++) {
+ if (dfc->freq_table[i] == freq)
+ return i;
+ }
+
+ return THERMAL_CSTATE_INVALID;
+}
+
+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;
+}
+
+/**
+ * get_static_power() - calculate the static power
+ * @dfc: Pointer to devfreq cooling device
+ * @freq: Frequency in Hz
+ *
+ * Calculate the static power in milliwatts using the supplied
+ * get_static_power(). The current voltage is calculated using the
+ * OPP library. If no get_static_power() was supplied, assume the
+ * static power is negligible.
+ */
+static unsigned long
+get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq)
+{
+ struct devfreq *df = dfc->devfreq;
+ unsigned long voltage;
+
+ if (!dfc->power_ops->get_static_power)
+ return 0;
+
+ voltage = get_voltage(df, freq);
+
+ if (voltage == 0)
+ return 0;
+
+ return dfc->power_ops->get_static_power(df, voltage);
+}
+
+/**
+ * get_dynamic_power - calculate the dynamic power
+ * @dfc: Pointer to devfreq cooling device
+ * @freq: Frequency in Hz
+ * @voltage: Voltage in millivolts
+ *
+ * Calculate the dynamic power in milliwatts consumed by the device at
+ * frequency @freq and voltage @voltage. If the get_dynamic_power()
+ * was supplied as part of the devfreq_cooling_power struct, then that
+ * function is used. Otherwise, a simple power model (Pdyn = Coeff *
+ * Voltage^2 * Frequency) is used.
+ */
+static unsigned long
+get_dynamic_power(struct devfreq_cooling_device *dfc, unsigned long freq,
+ unsigned long voltage)
+{
+ u64 power;
+ u32 freq_mhz;
+ struct devfreq_cooling_power *dfc_power = dfc->power_ops;
+
+ if (dfc_power->get_dynamic_power)
+ return dfc_power->get_dynamic_power(dfc->devfreq, freq,
+ voltage);
+
+ freq_mhz = freq / 1000000;
+ power = (u64)dfc_power->dyn_power_coeff * freq_mhz * voltage * voltage;
+ do_div(power, 1000000000);
+
+ return power;
+}
+
+
+static inline unsigned long get_total_power(struct devfreq_cooling_device *dfc,
+ unsigned long freq,
+ unsigned long voltage)
+{
+ return get_static_power(dfc, freq) + get_dynamic_power(dfc, freq,
+ voltage);
+}
+
+
+static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz,
+ u32 *power)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+ struct devfreq *df = dfc->devfreq;
+ struct devfreq_dev_status *status = &df->last_status;
+ unsigned long state;
+ unsigned long freq = status->current_frequency;
+ unsigned long voltage;
+ u32 dyn_power = 0;
+ u32 static_power = 0;
+ int res;
+
+ state = freq_get_state(dfc, freq);
+ if (state == THERMAL_CSTATE_INVALID) {
+ res = -EAGAIN;
+ goto fail;
+ }
+
+ if (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->capped_state;
+ dfc->res_util = dfc->power_table[state];
+ dfc->res_util *= SCALE_ERROR_MITIGATION;
+
+ if (*power > 1)
+ dfc->res_util /= *power;
+ } else {
+ goto fail;
+ }
+ } else {
+ dyn_power = dfc->power_table[state];
+
+ /* Scale dynamic power for utilization */
+ dyn_power *= status->busy_time;
+ dyn_power /= status->total_time;
+ /* Get static power */
+ static_power = get_static_power(dfc, freq);
+
+ *power = dyn_power + static_power;
+ }
+
+ trace_thermal_power_devfreq_get_power(cdev, status, freq, dyn_power,
+ static_power, *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,
+ struct thermal_zone_device *tz,
+ unsigned long state,
+ u32 *power)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+ unsigned long freq;
+ u32 static_power;
+
+ if (state >= dfc->freq_table_size)
+ return -EINVAL;
+
+ freq = dfc->freq_table[state];
+ static_power = get_static_power(dfc, freq);
+
+ *power = dfc->power_table[state] + static_power;
+ return 0;
+}
+
+static int devfreq_cooling_power2state(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz,
+ u32 power, unsigned long *state)
+{
+ struct devfreq_cooling_device *dfc = cdev->devdata;
+ struct devfreq *df = dfc->devfreq;
+ struct devfreq_dev_status *status = &df->last_status;
+ unsigned long freq = status->current_frequency;
+ unsigned long busy_time;
+ s32 dyn_power;
+ u32 static_power;
+ s32 est_power;
+ int i;
+
+ if (dfc->power_ops->get_real_power) {
+ /* Scale for resource utilization */
+ est_power = power * dfc->res_util;
+ est_power /= SCALE_ERROR_MITIGATION;
+ } else {
+ static_power = get_static_power(dfc, freq);
+
+ dyn_power = power - static_power;
+ dyn_power = dyn_power > 0 ? dyn_power : 0;
+
+ /* Scale dynamic power for utilization */
+ busy_time = status->busy_time ?: 1;
+ est_power = (dyn_power * status->total_time) / busy_time;
+ }
+
+ /*
+ * Find the first cooling state that is within the power
+ * budget for dynamic power.
+ */
+ for (i = 0; i < dfc->freq_table_size - 1; i++)
+ if (est_power >= dfc->power_table[i])
+ break;
+
+ *state = i;
+ dfc->capped_state = i;
+ trace_thermal_power_devfreq_limit(cdev, freq, *state, power);
+ return 0;
+}
+
+static struct thermal_cooling_device_ops devfreq_cooling_ops = {
+ .get_max_state = devfreq_cooling_get_max_state,
+ .get_cur_state = devfreq_cooling_get_cur_state,
+ .set_cur_state = devfreq_cooling_set_cur_state,
+};
+
+/**
+ * devfreq_cooling_gen_tables() - Generate power and freq tables.
+ * @dfc: Pointer to devfreq cooling device.
+ *
+ * Generate power and frequency tables: the power table hold the
+ * device's maximum power usage at each cooling state (OPP). The
+ * static and dynamic power using the appropriate voltage and
+ * frequency for the state, is acquired from the struct
+ * devfreq_cooling_power, and summed to make the maximum power draw.
+ *
+ * The frequency table holds the frequencies in descending order.
+ * That way its indexed by cooling device state.
+ *
+ * The tables are malloced, and pointers put in dfc. They must be
+ * freed when unregistering the devfreq cooling device.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc)
+{
+ struct devfreq *df = dfc->devfreq;
+ struct device *dev = df->dev.parent;
+ int ret, num_opps;
+ unsigned long freq;
+ u32 *power_table = NULL;
+ u32 *freq_table;
+ int i;
+
+ num_opps = dev_pm_opp_get_opp_count(dev);
+
+ if (dfc->power_ops) {
+ power_table = kcalloc(num_opps, sizeof(*power_table),
+ GFP_KERNEL);
+ if (!power_table)
+ return -ENOMEM;
+ }
+
+ freq_table = kcalloc(num_opps, sizeof(*freq_table),
+ GFP_KERNEL);
+ if (!freq_table) {
+ ret = -ENOMEM;
+ goto free_power_table;
+ }
+
+ for (i = 0, freq = ULONG_MAX; i < num_opps; i++, freq--) {
+ unsigned long power, voltage;
+ struct dev_pm_opp *opp;
+
+ opp = dev_pm_opp_find_freq_floor(dev, &freq);
+ if (IS_ERR(opp)) {
+ ret = PTR_ERR(opp);
+ goto free_tables;
+ }
+
+ voltage = dev_pm_opp_get_voltage(opp) / 1000; /* mV */
+ dev_pm_opp_put(opp);
+
+ if (dfc->power_ops) {
+ if (dfc->power_ops->get_real_power)
+ power = get_total_power(dfc, freq, voltage);
+ else
+ power = get_dynamic_power(dfc, freq, voltage);
+
+ dev_dbg(dev, "Power table: %lu MHz @ %lu mV: %lu = %lu mW\n",
+ freq / 1000000, voltage, power, power);
+
+ power_table[i] = power;
+ }
+
+ freq_table[i] = freq;
+ }
+
+ if (dfc->power_ops)
+ dfc->power_table = power_table;
+
+ dfc->freq_table = freq_table;
+ dfc->freq_table_size = num_opps;
+
+ return 0;
+
+free_tables:
+ kfree(freq_table);
+free_power_table:
+ kfree(power_table);
+
+ return ret;
+}
+
+/**
+ * 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 devfreq_cooling_device *dfc;
+ char dev_name[THERMAL_NAME_LENGTH];
+ int err;
+
+ dfc = kzalloc(sizeof(*dfc), GFP_KERNEL);
+ if (!dfc)
+ return ERR_PTR(-ENOMEM);
+
+ dfc->devfreq = df;
+
+ if (dfc_power) {
+ dfc->power_ops = dfc_power;
+
+ devfreq_cooling_ops.get_requested_power =
+ devfreq_cooling_get_requested_power;
+ devfreq_cooling_ops.state2power = devfreq_cooling_state2power;
+ devfreq_cooling_ops.power2state = devfreq_cooling_power2state;
+ }
+
+ err = devfreq_cooling_gen_tables(dfc);
+ if (err)
+ goto free_dfc;
+
+ err = ida_simple_get(&devfreq_ida, 0, 0, GFP_KERNEL);
+ if (err < 0)
+ goto free_tables;
+ dfc->id = err;
+
+ snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d", dfc->id);
+
+ cdev = thermal_of_cooling_device_register(np, dev_name, dfc,
+ &devfreq_cooling_ops);
+ if (IS_ERR(cdev)) {
+ err = PTR_ERR(cdev);
+ dev_err(df->dev.parent,
+ "Failed to register devfreq cooling device (%d)\n",
+ err);
+ goto release_ida;
+ }
+
+ dfc->cdev = cdev;
+
+ return cdev;
+
+release_ida:
+ ida_simple_remove(&devfreq_ida, dfc->id);
+free_tables:
+ kfree(dfc->power_table);
+ 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_unregister() - Unregister devfreq cooling device.
+ * @dfc: Pointer to devfreq cooling device to unregister.
+ */
+void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct devfreq_cooling_device *dfc;
+
+ if (!cdev)
+ return;
+
+ dfc = cdev->devdata;
+
+ thermal_cooling_device_unregister(dfc->cdev);
+ ida_simple_remove(&devfreq_ida, dfc->id);
+ kfree(dfc->power_table);
+ 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..a0bc9de42
--- /dev/null
+++ b/drivers/thermal/dove_thermal.c
@@ -0,0 +1,195 @@
+/*
+ * Dove thermal sensor driver
+ *
+ * Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch>
+ *
+ * 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/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->devdata;
+
+ /* Valid check */
+ reg = readl_relaxed(priv->control + PMU_TEMP_DIOD_CTRL1_REG);
+ if ((reg & PMU_TDC1_TEMP_VALID_MASK) == 0x0) {
+ dev_err(&thermal->device,
+ "Temperature sensor reading not valid\n");
+ 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 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;
+ struct resource *res;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->sensor = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->sensor))
+ return PTR_ERR(priv->sensor);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ priv->control = devm_ioremap_resource(&pdev->dev, res);
+ 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_zone_device_register("dove_thermal", 0, 0,
+ priv, &ops, NULL, 0, 0);
+ if (IS_ERR(thermal)) {
+ dev_err(&pdev->dev,
+ "Failed to register thermal zone device\n");
+ return PTR_ERR(thermal);
+ }
+
+ platform_set_drvdata(pdev, thermal);
+
+ return 0;
+}
+
+static int dove_thermal_exit(struct platform_device *pdev)
+{
+ struct thermal_zone_device *dove_thermal =
+ platform_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(dove_thermal);
+
+ return 0;
+}
+
+MODULE_DEVICE_TABLE(of, dove_thermal_id_table);
+
+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/fair_share.c b/drivers/thermal/fair_share.c
new file mode 100644
index 000000000..26d7387f5
--- /dev/null
+++ b/drivers/thermal/fair_share.c
@@ -0,0 +1,146 @@
+/*
+ * fair_share.c - A simple weight based Thermal governor
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/thermal.h>
+#include <trace/events/thermal.h>
+
+#include "thermal_core.h"
+
+/**
+ * get_trip_level: - obtains the current trip level for a zone
+ * @tz: thermal zone device
+ */
+static int get_trip_level(struct thermal_zone_device *tz)
+{
+ int count = 0;
+ int trip_temp;
+ enum thermal_trip_type trip_type;
+
+ if (tz->trips == 0 || !tz->ops->get_trip_temp)
+ return 0;
+
+ for (count = 0; count < tz->trips; count++) {
+ tz->ops->get_trip_temp(tz, count, &trip_temp);
+ if (tz->temperature < trip_temp)
+ break;
+ }
+
+ /*
+ * count > 0 only if temperature is greater than first trip
+ * point, in which case, trip_point = count - 1
+ */
+ if (count > 0) {
+ tz->ops->get_trip_type(tz, count - 1, &trip_type);
+ trace_thermal_zone_trip(tz, count - 1, trip_type);
+ }
+
+ return count;
+}
+
+static long get_target_state(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev, int percentage, int level)
+{
+ unsigned long max_state;
+
+ cdev->ops->get_max_state(cdev, &max_state);
+
+ return (long)(percentage * level * max_state) / (100 * tz->trips);
+}
+
+/**
+ * fair_share_throttle - throttles devices associated with the given zone
+ * @tz - thermal_zone_device
+ * @trip - trip point index
+ *
+ * Throttling Logic: This uses three parameters to calculate the new
+ * throttle state of the cooling devices associated with the given zone.
+ *
+ * Parameters used for Throttling:
+ * P1. max_state: Maximum throttle state exposed by the cooling device.
+ * P2. percentage[i]/100:
+ * How 'effective' the 'i'th device is, in cooling the given zone.
+ * P3. cur_trip_level/max_no_of_trips:
+ * This describes the extent to which the devices should be throttled.
+ * We do not want to throttle too much when we trip a lower temperature,
+ * whereas the throttling is at full swing if we trip critical levels.
+ * (Heavily assumes the trip points are in ascending order)
+ * new_state of cooling device = P3 * P2 * P1
+ */
+static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
+{
+ struct thermal_instance *instance;
+ int total_weight = 0;
+ int total_instance = 0;
+ int cur_trip_level = get_trip_level(tz);
+
+ mutex_lock(&tz->lock);
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (instance->trip != trip)
+ continue;
+
+ total_weight += instance->weight;
+ total_instance++;
+ }
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ int percentage;
+ struct thermal_cooling_device *cdev = instance->cdev;
+
+ if (instance->trip != trip)
+ continue;
+
+ if (!total_weight)
+ percentage = 100 / total_instance;
+ else
+ percentage = (instance->weight * 100) / total_weight;
+
+ instance->target = get_target_state(tz, cdev, percentage,
+ cur_trip_level);
+
+ mutex_lock(&instance->cdev->lock);
+ instance->cdev->updated = false;
+ mutex_unlock(&instance->cdev->lock);
+ thermal_cdev_update(cdev);
+ }
+
+ mutex_unlock(&tz->lock);
+ return 0;
+}
+
+static struct thermal_governor thermal_gov_fair_share = {
+ .name = "fair_share",
+ .throttle = fair_share_throttle,
+};
+
+int thermal_gov_fair_share_register(void)
+{
+ return thermal_register_governor(&thermal_gov_fair_share);
+}
+
+void thermal_gov_fair_share_unregister(void)
+{
+ thermal_unregister_governor(&thermal_gov_fair_share);
+}
+
diff --git a/drivers/thermal/gov_bang_bang.c b/drivers/thermal/gov_bang_bang.c
new file mode 100644
index 000000000..fc5e5057f
--- /dev/null
+++ b/drivers/thermal/gov_bang_bang.c
@@ -0,0 +1,138 @@
+/*
+ * gov_bang_bang.c - A simple thermal throttling governor using hysteresis
+ *
+ * Copyright (C) 2014 Peter Feuerer <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>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2.
+ *
+ * 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/thermal.h>
+
+#include "thermal_core.h"
+
+static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
+{
+ int trip_temp, trip_hyst;
+ struct thermal_instance *instance;
+
+ tz->ops->get_trip_temp(tz, trip, &trip_temp);
+
+ if (!tz->ops->get_trip_hyst) {
+ pr_warn_once("Undefined get_trip_hyst for thermal zone %s - "
+ "running with default hysteresis zero\n", tz->type);
+ trip_hyst = 0;
+ } else
+ tz->ops->get_trip_hyst(tz, trip, &trip_hyst);
+
+ dev_dbg(&tz->device, "Trip%d[temp=%d]:temp=%d:hyst=%d\n",
+ trip, trip_temp, tz->temperature,
+ trip_hyst);
+
+ mutex_lock(&tz->lock);
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (instance->trip != trip)
+ continue;
+
+ /* in case fan is in initial state, switch the fan off */
+ if (instance->target == THERMAL_NO_TARGET)
+ instance->target = 0;
+
+ /* in case fan is neither on nor off set the fan to active */
+ if (instance->target != 0 && instance->target != 1) {
+ pr_warn("Thermal instance %s controlled by bang-bang has unexpected state: %ld\n",
+ instance->name, instance->target);
+ instance->target = 1;
+ }
+
+ /*
+ * enable fan when temperature exceeds trip_temp and disable
+ * the fan in case it falls below trip_temp minus hysteresis
+ */
+ if (instance->target == 0 && tz->temperature >= trip_temp)
+ instance->target = 1;
+ else if (instance->target == 1 &&
+ tz->temperature <= trip_temp - trip_hyst)
+ instance->target = 0;
+
+ dev_dbg(&instance->cdev->device, "target=%d\n",
+ (int)instance->target);
+
+ mutex_lock(&instance->cdev->lock);
+ instance->cdev->updated = false; /* cdev needs update */
+ mutex_unlock(&instance->cdev->lock);
+ }
+
+ mutex_unlock(&tz->lock);
+}
+
+/**
+ * bang_bang_control - controls devices associated with the given zone
+ * @tz - thermal_zone_device
+ * @trip - the trip point
+ *
+ * Regulation Logic: a two point regulation, deliver cooling state depending
+ * on the previous state shown in this diagram:
+ *
+ * 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.
+ *
+ */
+static int bang_bang_control(struct thermal_zone_device *tz, int trip)
+{
+ struct thermal_instance *instance;
+
+ thermal_zone_trip_update(tz, trip);
+
+ mutex_lock(&tz->lock);
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node)
+ thermal_cdev_update(instance->cdev);
+
+ mutex_unlock(&tz->lock);
+
+ return 0;
+}
+
+static struct thermal_governor thermal_gov_bang_bang = {
+ .name = "bang_bang",
+ .throttle = bang_bang_control,
+};
+
+int thermal_gov_bang_bang_register(void)
+{
+ return thermal_register_governor(&thermal_gov_bang_bang);
+}
+
+void thermal_gov_bang_bang_unregister(void)
+{
+ thermal_unregister_governor(&thermal_gov_bang_bang);
+}
diff --git a/drivers/thermal/hisi_thermal.c b/drivers/thermal/hisi_thermal.c
new file mode 100644
index 000000000..761d0559c
--- /dev/null
+++ b/drivers/thermal/hisi_thermal.c
@@ -0,0 +1,627 @@
+/*
+ * 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>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of_device.h>
+
+#include "thermal_core.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_DEFAULT_SENSOR 2
+#define HI3660_DEFAULT_SENSOR 1
+
+struct hisi_thermal_sensor {
+ struct thermal_zone_device *tzd;
+ uint32_t id;
+ uint32_t thres_temp;
+};
+
+struct hisi_thermal_data {
+ int (*get_temp)(struct hisi_thermal_data *data);
+ int (*enable_sensor)(struct hisi_thermal_data *data);
+ int (*disable_sensor)(struct hisi_thermal_data *data);
+ int (*irq_handler)(struct hisi_thermal_data *data);
+ struct platform_device *pdev;
+ struct clk *clk;
+ struct hisi_thermal_sensor sensor;
+ void __iomem *regs;
+ int irq;
+};
+
+/*
+ * 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_data *data)
+{
+ hi6220_thermal_alarm_clear(data->regs, 1);
+ return 0;
+}
+
+static int hi3660_thermal_irq_handler(struct hisi_thermal_data *data)
+{
+ hi3660_thermal_alarm_clear(data->regs, data->sensor.id, 1);
+ return 0;
+}
+
+static int hi6220_thermal_get_temp(struct hisi_thermal_data *data)
+{
+ return hi6220_thermal_get_temperature(data->regs);
+}
+
+static int hi3660_thermal_get_temp(struct hisi_thermal_data *data)
+{
+ return hi3660_thermal_get_temperature(data->regs, data->sensor.id);
+}
+
+static int hi6220_thermal_disable_sensor(struct hisi_thermal_data *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_data *data)
+{
+ /* disable sensor module */
+ hi3660_thermal_alarm_enable(data->regs, data->sensor.id, 0);
+ return 0;
+}
+
+static int hi6220_thermal_enable_sensor(struct hisi_thermal_data *data)
+{
+ struct hisi_thermal_sensor *sensor = &data->sensor;
+ 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_data *data)
+{
+ unsigned int value;
+ struct hisi_thermal_sensor *sensor = &data->sensor;
+
+ /* 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;
+ struct resource *res;
+ int ret;
+
+ data->get_temp = hi6220_thermal_get_temp;
+ data->enable_sensor = hi6220_thermal_enable_sensor;
+ data->disable_sensor = hi6220_thermal_disable_sensor;
+ data->irq_handler = hi6220_thermal_irq_handler;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ data->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(data->regs)) {
+ dev_err(dev, "failed to get io address\n");
+ return PTR_ERR(data->regs);
+ }
+
+ data->clk = devm_clk_get(dev, "thermal_clk");
+ if (IS_ERR(data->clk)) {
+ ret = PTR_ERR(data->clk);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get thermal clk: %d\n", ret);
+ return ret;
+ }
+
+ data->irq = platform_get_irq(pdev, 0);
+ if (data->irq < 0)
+ return data->irq;
+
+ data->sensor.id = HI6220_DEFAULT_SENSOR;
+
+ return 0;
+}
+
+static int hi3660_thermal_probe(struct hisi_thermal_data *data)
+{
+ struct platform_device *pdev = data->pdev;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+
+ data->get_temp = hi3660_thermal_get_temp;
+ data->enable_sensor = hi3660_thermal_enable_sensor;
+ data->disable_sensor = hi3660_thermal_disable_sensor;
+ data->irq_handler = hi3660_thermal_irq_handler;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ data->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(data->regs)) {
+ dev_err(dev, "failed to get io address\n");
+ return PTR_ERR(data->regs);
+ }
+
+ data->irq = platform_get_irq(pdev, 0);
+ if (data->irq < 0)
+ return data->irq;
+
+ data->sensor.id = HI3660_DEFAULT_SENSOR;
+
+ return 0;
+}
+
+static int hisi_thermal_get_temp(void *__data, int *temp)
+{
+ struct hisi_thermal_data *data = __data;
+ struct hisi_thermal_sensor *sensor = &data->sensor;
+
+ *temp = data->get_temp(data);
+
+ dev_dbg(&data->pdev->dev, "id=%d, temp=%d, thres=%d\n",
+ sensor->id, *temp, sensor->thres_temp);
+
+ return 0;
+}
+
+static const struct thermal_zone_of_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_data *data = dev;
+ struct hisi_thermal_sensor *sensor = &data->sensor;
+ int temp = 0;
+
+ data->irq_handler(data);
+
+ hisi_thermal_get_temp(data, &temp);
+
+ if (temp >= sensor->thres_temp) {
+ dev_crit(&data->pdev->dev, "THERMAL ALARM: %d > %d\n",
+ temp, sensor->thres_temp);
+
+ thermal_zone_device_update(data->sensor.tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ } else {
+ dev_crit(&data->pdev->dev, "THERMAL ALARM stopped: %d < %d\n",
+ temp, sensor->thres_temp);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int hisi_thermal_register_sensor(struct platform_device *pdev,
+ struct hisi_thermal_data *data,
+ struct hisi_thermal_sensor *sensor)
+{
+ int ret, i;
+ const struct thermal_trip *trip;
+
+ sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
+ sensor->id, data,
+ &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;
+ }
+
+ trip = of_thermal_get_trip_points(sensor->tzd);
+
+ for (i = 0; i < of_thermal_get_ntrips(sensor->tzd); i++) {
+ if (trip[i].type == THERMAL_TRIP_PASSIVE) {
+ sensor->thres_temp = trip[i].temperature;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_hisi_thermal_match[] = {
+ {
+ .compatible = "hisilicon,tsensor",
+ .data = hi6220_thermal_probe
+ },
+ {
+ .compatible = "hisilicon,hi3660-tsensor",
+ .data = hi3660_thermal_probe
+ },
+ { /* 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;
+
+ tzd->ops->set_mode(tzd,
+ on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED);
+}
+
+static int hisi_thermal_probe(struct platform_device *pdev)
+{
+ struct hisi_thermal_data *data;
+ int (*platform_probe)(struct hisi_thermal_data *);
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->pdev = pdev;
+ platform_set_drvdata(pdev, data);
+
+ platform_probe = of_device_get_match_data(dev);
+ if (!platform_probe) {
+ dev_err(dev, "failed to get probe func\n");
+ return -EINVAL;
+ }
+
+ ret = platform_probe(data);
+ if (ret)
+ return ret;
+
+ ret = hisi_thermal_register_sensor(pdev, data,
+ &data->sensor);
+ if (ret) {
+ dev_err(dev, "failed to register thermal sensor: %d\n", ret);
+ return ret;
+ }
+
+ ret = data->enable_sensor(data);
+ if (ret) {
+ dev_err(dev, "Failed to setup the sensor: %d\n", ret);
+ return ret;
+ }
+
+ if (data->irq) {
+ ret = devm_request_threaded_irq(dev, data->irq, NULL,
+ hisi_thermal_alarm_irq_thread,
+ IRQF_ONESHOT, "hisi_thermal", data);
+ if (ret < 0) {
+ dev_err(dev, "failed to request alarm irq: %d\n", ret);
+ return ret;
+ }
+ }
+
+ hisi_thermal_toggle_sensor(&data->sensor, true);
+
+ return 0;
+}
+
+static int hisi_thermal_remove(struct platform_device *pdev)
+{
+ struct hisi_thermal_data *data = platform_get_drvdata(pdev);
+ struct hisi_thermal_sensor *sensor = &data->sensor;
+
+ hisi_thermal_toggle_sensor(sensor, false);
+
+ data->disable_sensor(data);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int hisi_thermal_suspend(struct device *dev)
+{
+ struct hisi_thermal_data *data = dev_get_drvdata(dev);
+
+ data->disable_sensor(data);
+
+ return 0;
+}
+
+static int hisi_thermal_resume(struct device *dev)
+{
+ struct hisi_thermal_data *data = dev_get_drvdata(dev);
+
+ return data->enable_sensor(data);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
+ hisi_thermal_suspend, hisi_thermal_resume);
+
+static struct platform_driver hisi_thermal_driver = {
+ .driver = {
+ .name = "hisi_thermal",
+ .pm = &hisi_thermal_pm_ops,
+ .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/imx_thermal.c b/drivers/thermal/imx_thermal.c
new file mode 100644
index 000000000..aa452acb6
--- /dev/null
+++ b/drivers/thermal/imx_thermal.c
@@ -0,0 +1,917 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2013 Freescale Semiconductor, Inc.
+
+#include <linux/clk.h>
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpu_cooling.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/nvmem-consumer.h>
+
+#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,
+ IMX_TRIP_NUM,
+};
+
+#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_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 cpufreq_policy *policy;
+ struct thermal_zone_device *tz;
+ struct thermal_cooling_device *cdev;
+ enum thermal_device_mode mode;
+ struct regmap *tempmon;
+ u32 c1, c2; /* See formula in imx_init_calib() */
+ int temp_passive;
+ int temp_critical;
+ 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 = tz->devdata;
+ const struct thermal_soc_data *soc_data = data->socdata;
+ struct regmap *map = data->tempmon;
+ unsigned int n_meas;
+ bool wait;
+ u32 val;
+
+ if (data->mode == THERMAL_DEVICE_ENABLED) {
+ /* Check if a measurement is currently in progress */
+ regmap_read(map, soc_data->temp_data, &val);
+ wait = !(val & soc_data->temp_valid_mask);
+ } else {
+ /*
+ * Every time we measure the temperature, we will power on the
+ * temperature sensor, enable measurements, take a reading,
+ * disable measurements, power off the temperature sensor.
+ */
+ regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
+ soc_data->power_down_mask);
+ regmap_write(map, soc_data->sensor_ctrl + REG_SET,
+ soc_data->measure_temp_mask);
+
+ wait = true;
+ }
+
+ /*
+ * According to the temp sensor designers, it may require up to ~17us
+ * to complete a measurement.
+ */
+ if (wait)
+ usleep_range(20, 50);
+
+ regmap_read(map, soc_data->temp_data, &val);
+
+ if (data->mode != THERMAL_DEVICE_ENABLED) {
+ regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
+ soc_data->measure_temp_mask);
+ regmap_write(map, soc_data->sensor_ctrl + REG_SET,
+ soc_data->power_down_mask);
+ }
+
+ if ((val & soc_data->temp_valid_mask) == 0) {
+ dev_dbg(&tz->device, "temp measurement never finished\n");
+ return -EAGAIN;
+ }
+
+ 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 == data->temp_passive &&
+ *temp >= data->temp_passive)
+ imx_set_alarm_temp(data, data->temp_critical);
+ if (data->alarm_temp == data->temp_critical &&
+ *temp < data->temp_passive) {
+ imx_set_alarm_temp(data, data->temp_passive);
+ dev_dbg(&tz->device, "thermal alarm off: T < %d\n",
+ data->alarm_temp / 1000);
+ }
+ }
+
+ if (*temp != data->last_temp) {
+ dev_dbg(&tz->device, "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);
+ }
+
+ return 0;
+}
+
+static int imx_get_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode *mode)
+{
+ struct imx_thermal_data *data = tz->devdata;
+
+ *mode = data->mode;
+
+ return 0;
+}
+
+static int imx_set_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode mode)
+{
+ struct imx_thermal_data *data = tz->devdata;
+ struct regmap *map = data->tempmon;
+ const struct thermal_soc_data *soc_data = data->socdata;
+
+ if (mode == THERMAL_DEVICE_ENABLED) {
+ tz->polling_delay = IMX_POLLING_DELAY;
+ tz->passive_delay = IMX_PASSIVE_DELAY;
+
+ regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
+ soc_data->power_down_mask);
+ regmap_write(map, soc_data->sensor_ctrl + REG_SET,
+ soc_data->measure_temp_mask);
+
+ if (!data->irq_enabled) {
+ data->irq_enabled = true;
+ enable_irq(data->irq);
+ }
+ } else {
+ regmap_write(map, soc_data->sensor_ctrl + REG_CLR,
+ soc_data->measure_temp_mask);
+ regmap_write(map, soc_data->sensor_ctrl + REG_SET,
+ soc_data->power_down_mask);
+
+ tz->polling_delay = 0;
+ tz->passive_delay = 0;
+
+ if (data->irq_enabled) {
+ disable_irq(data->irq);
+ data->irq_enabled = false;
+ }
+ }
+
+ data->mode = mode;
+ thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return 0;
+}
+
+static int imx_get_trip_type(struct thermal_zone_device *tz, int trip,
+ enum thermal_trip_type *type)
+{
+ *type = (trip == IMX_TRIP_PASSIVE) ? THERMAL_TRIP_PASSIVE :
+ THERMAL_TRIP_CRITICAL;
+ return 0;
+}
+
+static int imx_get_crit_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct imx_thermal_data *data = tz->devdata;
+
+ *temp = data->temp_critical;
+ return 0;
+}
+
+static int imx_get_trip_temp(struct thermal_zone_device *tz, int trip,
+ int *temp)
+{
+ struct imx_thermal_data *data = tz->devdata;
+
+ *temp = (trip == IMX_TRIP_PASSIVE) ? data->temp_passive :
+ data->temp_critical;
+ return 0;
+}
+
+static int imx_set_trip_temp(struct thermal_zone_device *tz, int trip,
+ int temp)
+{
+ struct imx_thermal_data *data = tz->devdata;
+
+ /* do not allow changing critical threshold */
+ if (trip == IMX_TRIP_CRITICAL)
+ return -EPERM;
+
+ /* do not allow passive to be set higher than critical */
+ if (temp < 0 || temp > data->temp_critical)
+ return -EINVAL;
+
+ data->temp_passive = temp;
+
+ imx_set_alarm_temp(data, temp);
+
+ return 0;
+}
+
+static int imx_bind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
+{
+ int ret;
+
+ ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
+ THERMAL_NO_LIMIT,
+ THERMAL_NO_LIMIT,
+ THERMAL_WEIGHT_DEFAULT);
+ if (ret) {
+ dev_err(&tz->device,
+ "binding zone %s with cdev %s failed:%d\n",
+ tz->type, cdev->type, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_unbind(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev)
+{
+ int ret;
+
+ ret = thermal_zone_unbind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev);
+ if (ret) {
+ dev_err(&tz->device,
+ "unbinding zone %s with cdev %s failed:%d\n",
+ tz->type, cdev->type, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops imx_tz_ops = {
+ .bind = imx_bind,
+ .unbind = imx_unbind,
+ .get_temp = imx_get_temp,
+ .get_mode = imx_get_mode,
+ .set_mode = imx_set_mode,
+ .get_trip_type = imx_get_trip_type,
+ .get_trip_temp = imx_get_trip_temp,
+ .get_crit_temp = imx_get_crit_temp,
+ .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)
+ */
+ data->temp_critical = data->temp_max - (1000 * 5);
+ data->temp_passive = data->temp_max - (1000 * 10);
+}
+
+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->tz->device, "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);
+
+/*
+ * 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 = of_get_cpu_node(data->policy->cpu, NULL);
+ int ret;
+
+ if (!np || !of_find_property(np, "#cooling-cells", NULL)) {
+ data->cdev = cpufreq_cooling_register(data->policy);
+ if (IS_ERR(data->cdev)) {
+ ret = PTR_ERR(data->cdev);
+ cpufreq_cpu_put(data->policy);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int imx_thermal_probe(struct platform_device *pdev)
+{
+ struct imx_thermal_data *data;
+ struct regmap *map;
+ int measure_freq;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "fsl,tempmon");
+ if (IS_ERR(map)) {
+ ret = PTR_ERR(map);
+ dev_err(&pdev->dev, "failed to get tempmon regmap: %d\n", ret);
+ return ret;
+ }
+ data->tempmon = map;
+
+ data->socdata = of_device_get_match_data(&pdev->dev);
+ if (!data->socdata) {
+ dev_err(&pdev->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_find_property(pdev->dev.of_node, "nvmem-cells", NULL)) {
+ ret = imx_init_from_nvmem_cells(pdev);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init from nvmem: %d\n",
+ ret);
+ return ret;
+ }
+ } else {
+ ret = imx_init_from_tempmon_data(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init from 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);
+
+ data->policy = cpufreq_cpu_get(0);
+ if (!data->policy) {
+ pr_debug("%s: CPUFreq policy not found\n", __func__);
+ return -EPROBE_DEFER;
+ }
+
+ ret = imx_thermal_register_legacy_cooling(data);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to register cpufreq cooling device: %d\n", ret);
+ return ret;
+ }
+
+ data->thermal_clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(data->thermal_clk)) {
+ ret = PTR_ERR(data->thermal_clk);
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev,
+ "failed to get thermal clk: %d\n", ret);
+ cpufreq_cooling_unregister(data->cdev);
+ cpufreq_cpu_put(data->policy);
+ return ret;
+ }
+
+ /*
+ * 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(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
+ cpufreq_cooling_unregister(data->cdev);
+ cpufreq_cpu_put(data->policy);
+ return ret;
+ }
+
+ data->tz = thermal_zone_device_register("imx_thermal_zone",
+ IMX_TRIP_NUM,
+ BIT(IMX_TRIP_PASSIVE), data,
+ &imx_tz_ops, NULL,
+ IMX_PASSIVE_DELAY,
+ IMX_POLLING_DELAY);
+ if (IS_ERR(data->tz)) {
+ ret = PTR_ERR(data->tz);
+ dev_err(&pdev->dev,
+ "failed to register thermal zone device %d\n", ret);
+ clk_disable_unprepare(data->thermal_clk);
+ cpufreq_cooling_unregister(data->cdev);
+ cpufreq_cpu_put(data->policy);
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "%s CPU temperature grade - max:%dC"
+ " critical:%dC passive:%dC\n", data->temp_grade,
+ data->temp_max / 1000, data->temp_critical / 1000,
+ data->temp_passive / 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, data->temp_passive);
+
+ if (data->socdata->version == TEMPMON_IMX6SX)
+ imx_set_panic_temp(data, data->temp_critical);
+
+ regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
+ data->socdata->power_down_mask);
+ regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
+ data->socdata->measure_temp_mask);
+
+ data->irq_enabled = true;
+ data->mode = THERMAL_DEVICE_ENABLED;
+
+ ret = devm_request_threaded_irq(&pdev->dev, data->irq,
+ imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
+ 0, "imx_thermal", data);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
+ clk_disable_unprepare(data->thermal_clk);
+ thermal_zone_device_unregister(data->tz);
+ cpufreq_cooling_unregister(data->cdev);
+ cpufreq_cpu_put(data->policy);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_thermal_remove(struct platform_device *pdev)
+{
+ struct imx_thermal_data *data = platform_get_drvdata(pdev);
+ struct regmap *map = data->tempmon;
+
+ /* Disable measurements */
+ regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
+ data->socdata->power_down_mask);
+ if (!IS_ERR(data->thermal_clk))
+ clk_disable_unprepare(data->thermal_clk);
+
+ thermal_zone_device_unregister(data->tz);
+ cpufreq_cooling_unregister(data->cdev);
+ cpufreq_cpu_put(data->policy);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx_thermal_suspend(struct device *dev)
+{
+ struct imx_thermal_data *data = dev_get_drvdata(dev);
+ struct regmap *map = data->tempmon;
+
+ /*
+ * Need to disable thermal sensor, otherwise, when thermal core
+ * try to get temperature before thermal sensor resume, a wrong
+ * temperature will be read as the thermal sensor is powered
+ * down.
+ */
+ regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
+ data->socdata->measure_temp_mask);
+ regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
+ data->socdata->power_down_mask);
+ data->mode = THERMAL_DEVICE_DISABLED;
+ clk_disable_unprepare(data->thermal_clk);
+
+ return 0;
+}
+
+static int imx_thermal_resume(struct device *dev)
+{
+ struct imx_thermal_data *data = dev_get_drvdata(dev);
+ struct regmap *map = data->tempmon;
+ int ret;
+
+ ret = clk_prepare_enable(data->thermal_clk);
+ if (ret)
+ return ret;
+ /* Enabled thermal sensor after resume */
+ regmap_write(map, data->socdata->sensor_ctrl + REG_CLR,
+ data->socdata->power_down_mask);
+ regmap_write(map, data->socdata->sensor_ctrl + REG_SET,
+ data->socdata->measure_temp_mask);
+ data->mode = THERMAL_DEVICE_ENABLED;
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops,
+ imx_thermal_suspend, imx_thermal_resume);
+
+static struct platform_driver imx_thermal = {
+ .driver = {
+ .name = "imx_thermal",
+ .pm = &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/int340x_thermal/Kconfig b/drivers/thermal/int340x_thermal/Kconfig
new file mode 100644
index 000000000..0582bd12a
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/Kconfig
@@ -0,0 +1,42 @@
+#
+# ACPI INT340x thermal drivers configuration
+#
+
+config INT340X_THERMAL
+ tristate "ACPI INT340X thermal drivers"
+ depends on X86 && ACPI
+ select THERMAL_GOV_USER_SPACE
+ select ACPI_THERMAL_REL
+ select ACPI_FAN
+ select INTEL_SOC_DTS_IOSF_CORE
+ 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.
+
+endif
diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile
new file mode 100644
index 000000000..287eb0a14
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/Makefile
@@ -0,0 +1,8 @@
+# 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_INT3406_THERMAL) += int3406_thermal.o
+obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o
diff --git a/drivers/thermal/int340x_thermal/acpi_thermal_rel.c b/drivers/thermal/int340x_thermal/acpi_thermal_rel.c
new file mode 100644
index 000000000..45e7e5cbd
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/acpi_thermal_rel.c
@@ -0,0 +1,394 @@
+/* acpi_thermal_rel.c driver for exporting ACPI thermal relationship
+ *
+ * Copyright (c) 2014 Intel Corp
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ */
+
+/*
+ * 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 "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;
+ struct acpi_device *adev;
+ 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" };
+
+ if (!acpi_has_method(handle, "_TRT"))
+ return -ENODEV;
+
+ 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 = kcalloc(*trt_count, sizeof(struct trt), GFP_KERNEL);
+ 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;
+
+ result = acpi_bus_get_device(trt->source, &adev);
+ if (result)
+ pr_warn("Failed to get source ACPI device\n");
+
+ result = acpi_bus_get_device(trt->target, &adev);
+ if (result)
+ 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;
+ struct acpi_device *adev;
+ 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" };
+
+ if (!acpi_has_method(handle, "_ART"))
+ return -ENODEV;
+
+ 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 = kcalloc(*art_count, sizeof(struct art), GFP_KERNEL);
+ 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 (art->source) {
+ result = acpi_bus_get_device(art->source, &adev);
+ if (result)
+ pr_warn("Failed to get source ACPI device\n");
+ }
+ if (art->target) {
+ result = acpi_bus_get_device(art->target, &adev);
+ if (result)
+ pr_warn("Failed to get target ACPI device\n");
+ }
+ }
+
+ *artp = arts;
+ /* don't count bad entries */
+ *art_count -= nr_bad_entries;
+end:
+ kfree(buffer.pointer);
+ return result;
+}
+EXPORT_SYMBOL(acpi_parse_art);
+
+
+/* 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_NAME_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 */
+ memcpy(&art_user[i].weight, &arts[i].weight,
+ sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2));
+ }
+
+ 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 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;
+
+ 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);
+
+ 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,
+ .llseek = no_llseek,
+};
+
+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/int340x_thermal/acpi_thermal_rel.h b/drivers/thermal/int340x_thermal/acpi_thermal_rel.h
new file mode 100644
index 000000000..58822575f
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/acpi_thermal_rel.h
@@ -0,0 +1,85 @@
+/* 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)
+
+struct art {
+ acpi_handle source;
+ acpi_handle target;
+ u64 weight;
+ u64 ac0_max;
+ u64 ac1_max;
+ u64 ac2_max;
+ u64 ac3_max;
+ u64 ac4_max;
+ u64 ac5_max;
+ u64 ac6_max;
+ u64 ac7_max;
+ u64 ac8_max;
+ u64 ac9_max;
+} __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_ART_ELEMENTS 13
+/* for usrspace */
+union art_object {
+ struct {
+ char source_device[8]; /* ACPI single name */
+ char target_device[8]; /* ACPI single name */
+ u64 weight;
+ u64 ac0_max_level;
+ u64 ac1_max_level;
+ u64 ac2_max_level;
+ u64 ac3_max_level;
+ u64 ac4_max_level;
+ u64 ac5_max_level;
+ u64 ac6_max_level;
+ u64 ac7_max_level;
+ u64 ac8_max_level;
+ u64 ac9_max_level;
+ };
+ 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];
+};
+
+#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/int340x_thermal/int3400_thermal.c b/drivers/thermal/int340x_thermal/int3400_thermal.c
new file mode 100644
index 000000000..68393c3c1
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/int3400_thermal.c
@@ -0,0 +1,402 @@
+/*
+ * INT3400 thermal driver
+ *
+ * Copyright (C) 2014, Intel Corporation
+ * Authors: Zhang Rui <rui.zhang@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#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
+
+enum int3400_thermal_uuid {
+ INT3400_THERMAL_PASSIVE_1,
+ INT3400_THERMAL_ACTIVE,
+ 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] = {
+ "42A441D6-AE6A-462b-A84B-4A8CE79027D3",
+ "3A95C389-E4B8-4629-A526-C52C88626BAE",
+ "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 int3400_thermal_priv {
+ struct acpi_device *adev;
+ struct thermal_zone_device *thermal;
+ int mode;
+ int art_count;
+ struct art *arts;
+ int trt_count;
+ struct trt *trts;
+ u32 uuid_bitmap;
+ int rel_misc_dev_res;
+ int current_uuid_index;
+};
+
+static ssize_t available_uuids_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
+ int i;
+ int length = 0;
+
+ for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
+ if (priv->uuid_bitmap & (1 << i))
+ if (PAGE_SIZE - length > 0)
+ length += snprintf(&buf[length],
+ PAGE_SIZE - length,
+ "%s\n",
+ int3400_thermal_uuids[i]);
+ }
+
+ return length;
+}
+
+static ssize_t current_uuid_show(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
+
+ if (priv->uuid_bitmap & (1 << priv->current_uuid_index))
+ return sprintf(buf, "%s\n",
+ int3400_thermal_uuids[priv->current_uuid_index]);
+ else
+ return sprintf(buf, "INVALID\n");
+}
+
+static ssize_t current_uuid_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
+ if ((priv->uuid_bitmap & (1 << i)) &&
+ !(strncmp(buf, int3400_thermal_uuids[i],
+ sizeof(int3400_thermal_uuids[i]) - 1))) {
+ priv->current_uuid_index = i;
+ return count;
+ }
+ }
+
+ return -EINVAL;
+}
+
+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 int int3400_thermal_run_osc(acpi_handle handle,
+ enum int3400_thermal_uuid uuid, bool enable)
+{
+ u32 ret, buf[2];
+ acpi_status status;
+ int result = 0;
+ struct acpi_osc_context context = {
+ .uuid_str = int3400_thermal_uuids[uuid],
+ .rev = 1,
+ .cap.length = 8,
+ };
+
+ buf[OSC_QUERY_DWORD] = 0;
+ buf[OSC_SUPPORT_DWORD] = enable;
+
+ context.cap.pointer = buf;
+
+ status = acpi_run_osc(handle, &context);
+ if (ACPI_SUCCESS(status)) {
+ ret = *((u32 *)(context.ret.pointer + 4));
+ if (ret != enable)
+ result = -EPERM;
+ } else
+ result = -EPERM;
+
+ kfree(context.ret.pointer);
+ return result;
+}
+
+static void int3400_notify(acpi_handle handle,
+ u32 event,
+ void *data)
+{
+ struct int3400_thermal_priv *priv = data;
+ char *thermal_prop[5];
+
+ if (!priv)
+ return;
+
+ switch (event) {
+ case INT3400_THERMAL_TABLE_CHANGED:
+ thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s",
+ priv->thermal->type);
+ thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d",
+ priv->thermal->temperature);
+ thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=");
+ thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d",
+ THERMAL_TABLE_CHANGED);
+ thermal_prop[4] = NULL;
+ kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE,
+ thermal_prop);
+ kfree(thermal_prop[0]);
+ kfree(thermal_prop[1]);
+ kfree(thermal_prop[2]);
+ kfree(thermal_prop[3]);
+ break;
+ default:
+ /* Ignore unknown notification codes sent to INT3400 device */
+ break;
+ }
+}
+
+static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
+ int *temp)
+{
+ *temp = 20 * 1000; /* faked temp sensor with 20C */
+ return 0;
+}
+
+static int int3400_thermal_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ struct int3400_thermal_priv *priv = thermal->devdata;
+
+ if (!priv)
+ return -EINVAL;
+
+ *mode = priv->mode;
+
+ return 0;
+}
+
+static int int3400_thermal_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ struct int3400_thermal_priv *priv = thermal->devdata;
+ bool enable;
+ int result = 0;
+
+ if (!priv)
+ return -EINVAL;
+
+ if (mode == THERMAL_DEVICE_ENABLED)
+ enable = true;
+ else if (mode == THERMAL_DEVICE_DISABLED)
+ enable = false;
+ else
+ return -EINVAL;
+
+ if (enable != priv->mode) {
+ priv->mode = enable;
+ result = int3400_thermal_run_osc(priv->adev->handle,
+ priv->current_uuid_index,
+ enable);
+ }
+ return result;
+}
+
+static struct thermal_zone_device_ops int3400_thermal_ops = {
+ .get_temp = int3400_thermal_get_temp,
+};
+
+static struct thermal_zone_params int3400_thermal_params = {
+ .governor_name = "user_space",
+ .no_hwmon = true,
+};
+
+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(sizeof(struct int3400_thermal_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->adev = adev;
+
+ result = int3400_thermal_get_uuids(priv);
+ if (result)
+ goto free_priv;
+
+ 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_thermal_ops.get_mode = int3400_thermal_get_mode;
+ int3400_thermal_ops.set_mode = int3400_thermal_set_mode;
+
+ priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
+ priv, &int3400_thermal_ops,
+ &int3400_thermal_params, 0, 0);
+ 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;
+
+ result = acpi_install_notify_handler(
+ priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
+ (void *)priv);
+ if (result)
+ goto free_sysfs;
+
+ return 0;
+
+free_sysfs:
+ sysfs_remove_group(&pdev->dev.kobj, &uuid_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:
+ kfree(priv->trts);
+ kfree(priv->arts);
+free_priv:
+ kfree(priv);
+ return result;
+}
+
+static int int3400_thermal_remove(struct platform_device *pdev)
+{
+ struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
+
+ acpi_remove_notify_handler(
+ priv->adev->handle, ACPI_DEVICE_NOTIFY,
+ int3400_notify);
+
+ if (!priv->rel_misc_dev_res)
+ acpi_thermal_rel_misc_device_remove(priv->adev->handle);
+
+ sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
+ thermal_zone_device_unregister(priv->thermal);
+ kfree(priv->trts);
+ kfree(priv->arts);
+ kfree(priv);
+ return 0;
+}
+
+static const struct acpi_device_id int3400_thermal_match[] = {
+ {"INT3400", 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/int340x_thermal/int3402_thermal.c b/drivers/thermal/int340x_thermal/int3402_thermal.c
new file mode 100644
index 000000000..8e90b3151
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/int3402_thermal.c
@@ -0,0 +1,108 @@
+/*
+ * INT3402 thermal driver for memory temperature reporting
+ *
+ * Copyright (C) 2014, Intel Corporation
+ * Authors: Aaron Lu <aaron.lu@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#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 (!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 int int3402_thermal_remove(struct platform_device *pdev)
+{
+ struct int3402_thermal_data *d = platform_get_drvdata(pdev);
+
+ acpi_remove_notify_handler(d->handle,
+ ACPI_DEVICE_NOTIFY, int3402_notify);
+ int340x_thermal_zone_remove(d->int340x_zone);
+
+ return 0;
+}
+
+static const struct acpi_device_id int3402_thermal_match[] = {
+ {"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/int340x_thermal/int3403_thermal.c b/drivers/thermal/int340x_thermal/int3403_thermal.c
new file mode 100644
index 000000000..0c19fcd56
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/int3403_thermal.c
@@ -0,0 +1,311 @@
+/*
+ * ACPI INT3403 thermal driver
+ * Copyright (c) 2013, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#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_performance_state {
+ u64 performance;
+ u64 power;
+ u64 latency;
+ u64 linear;
+ u64 control;
+ u64 raw_performace;
+ char *raw_unit;
+ int reserved;
+};
+
+struct int3403_cdev {
+ struct thermal_cooling_device *cdev;
+ unsigned long max_state;
+};
+
+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_read_trips(obj->int340x_zone);
+ int340x_thermal_zone_device_update(obj->int340x_zone,
+ THERMAL_TRIP_CHANGED);
+ break;
+ default:
+ dev_err(&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)) {
+ printk(KERN_WARNING "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;
+ 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, "PTYP",
+ NULL, &priv->type);
+ if (ACPI_FAILURE(status)) {
+ unsigned long long tmp;
+
+ status = acpi_evaluate_integer(priv->adev->handle, "_TMP",
+ NULL, &tmp);
+ 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 int 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;
+ }
+
+ return 0;
+}
+
+static const struct acpi_device_id int3403_device_ids[] = {
+ {"INT3403", 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/int340x_thermal/int3406_thermal.c b/drivers/thermal/int340x_thermal/int3406_thermal.c
new file mode 100644
index 000000000..f69ab026b
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/int3406_thermal.c
@@ -0,0 +1,213 @@
+/*
+ * INT3406 thermal driver for display participant device
+ *
+ * Copyright (C) 2016, Intel Corporation
+ * Authors: Aaron Lu <aaron.lu@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#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 int int3406_thermal_remove(struct platform_device *pdev)
+{
+ struct int3406_thermal_data *d = platform_get_drvdata(pdev);
+
+ thermal_cooling_device_unregister(d->cooling_dev);
+ kfree(d->br);
+ return 0;
+}
+
+static const struct acpi_device_id int3406_thermal_match[] = {
+ {"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/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/int340x_thermal/int340x_thermal_zone.c
new file mode 100644
index 000000000..9ec27ac18
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/int340x_thermal_zone.c
@@ -0,0 +1,295 @@
+/*
+ * int340x_thermal_zone.c
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/acpi.h>
+#include <linux/thermal.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 = zone->devdata;
+ unsigned long long tmp;
+ acpi_status status;
+
+ if (d->override_ops && d->override_ops->get_temp)
+ return d->override_ops->get_temp(zone, temp);
+
+ 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 = (unsigned long)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_get_trip_temp(struct thermal_zone_device *zone,
+ int trip, int *temp)
+{
+ struct int34x_thermal_zone *d = zone->devdata;
+ int i;
+
+ if (d->override_ops && d->override_ops->get_trip_temp)
+ return d->override_ops->get_trip_temp(zone, trip, temp);
+
+ if (trip < d->aux_trip_nr)
+ *temp = d->aux_trips[trip];
+ else if (trip == d->crt_trip_id)
+ *temp = d->crt_temp;
+ else if (trip == d->psv_trip_id)
+ *temp = d->psv_temp;
+ else if (trip == d->hot_trip_id)
+ *temp = d->hot_temp;
+ else {
+ for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
+ if (d->act_trips[i].valid &&
+ d->act_trips[i].id == trip) {
+ *temp = d->act_trips[i].temp;
+ break;
+ }
+ }
+ if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int int340x_thermal_get_trip_type(struct thermal_zone_device *zone,
+ int trip,
+ enum thermal_trip_type *type)
+{
+ struct int34x_thermal_zone *d = zone->devdata;
+ int i;
+
+ if (d->override_ops && d->override_ops->get_trip_type)
+ return d->override_ops->get_trip_type(zone, trip, type);
+
+ if (trip < d->aux_trip_nr)
+ *type = THERMAL_TRIP_PASSIVE;
+ else if (trip == d->crt_trip_id)
+ *type = THERMAL_TRIP_CRITICAL;
+ else if (trip == d->hot_trip_id)
+ *type = THERMAL_TRIP_HOT;
+ else if (trip == d->psv_trip_id)
+ *type = THERMAL_TRIP_PASSIVE;
+ else {
+ for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
+ if (d->act_trips[i].valid &&
+ d->act_trips[i].id == trip) {
+ *type = THERMAL_TRIP_ACTIVE;
+ break;
+ }
+ }
+ if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
+ int trip, int temp)
+{
+ struct int34x_thermal_zone *d = zone->devdata;
+ acpi_status status;
+ char name[10];
+
+ if (d->override_ops && d->override_ops->set_trip_temp)
+ return d->override_ops->set_trip_temp(zone, trip, temp);
+
+ snprintf(name, sizeof(name), "PAT%d", trip);
+ status = acpi_execute_simple_method(d->adev->handle, name,
+ MILLICELSIUS_TO_DECI_KELVIN(temp));
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ d->aux_trips[trip] = temp;
+
+ return 0;
+}
+
+
+static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone,
+ int trip, int *temp)
+{
+ struct int34x_thermal_zone *d = zone->devdata;
+ acpi_status status;
+ unsigned long long hyst;
+
+ if (d->override_ops && d->override_ops->get_trip_hyst)
+ return d->override_ops->get_trip_hyst(zone, trip, temp);
+
+ status = acpi_evaluate_integer(d->adev->handle, "GTSH", NULL, &hyst);
+ if (ACPI_FAILURE(status))
+ *temp = 0;
+ else
+ *temp = hyst * 100;
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops int340x_thermal_zone_ops = {
+ .get_temp = int340x_thermal_get_zone_temp,
+ .get_trip_temp = int340x_thermal_get_trip_temp,
+ .get_trip_type = int340x_thermal_get_trip_type,
+ .set_trip_temp = int340x_thermal_set_trip_temp,
+ .get_trip_hyst = int340x_thermal_get_trip_hyst,
+};
+
+static int int340x_thermal_get_trip_config(acpi_handle handle, char *name,
+ int *temp)
+{
+ unsigned long long r;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(handle, name, NULL, &r);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ *temp = DECI_KELVIN_TO_MILLICELSIUS(r);
+
+ return 0;
+}
+
+int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone)
+{
+ int trip_cnt = int34x_zone->aux_trip_nr;
+ int i;
+
+ int34x_zone->crt_trip_id = -1;
+ if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_CRT",
+ &int34x_zone->crt_temp))
+ int34x_zone->crt_trip_id = trip_cnt++;
+
+ int34x_zone->hot_trip_id = -1;
+ if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_HOT",
+ &int34x_zone->hot_temp))
+ int34x_zone->hot_trip_id = trip_cnt++;
+
+ int34x_zone->psv_trip_id = -1;
+ if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_PSV",
+ &int34x_zone->psv_temp))
+ int34x_zone->psv_trip_id = trip_cnt++;
+
+ for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
+ char name[5] = { '_', 'A', 'C', '0' + i, '\0' };
+
+ if (int340x_thermal_get_trip_config(int34x_zone->adev->handle,
+ name,
+ &int34x_zone->act_trips[i].temp))
+ break;
+
+ int34x_zone->act_trips[i].id = trip_cnt++;
+ int34x_zone->act_trips[i].valid = true;
+ }
+
+ return trip_cnt;
+}
+EXPORT_SYMBOL_GPL(int340x_thermal_read_trips);
+
+static struct thermal_zone_params int340x_thermal_params = {
+ .governor_name = "user_space",
+ .no_hwmon = true,
+};
+
+struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
+ struct thermal_zone_device_ops *override_ops)
+{
+ struct int34x_thermal_zone *int34x_thermal_zone;
+ acpi_status status;
+ unsigned long long trip_cnt;
+ int trip_mask = 0;
+ int ret;
+
+ int34x_thermal_zone = kzalloc(sizeof(*int34x_thermal_zone),
+ GFP_KERNEL);
+ if (!int34x_thermal_zone)
+ return ERR_PTR(-ENOMEM);
+
+ int34x_thermal_zone->adev = adev;
+ int34x_thermal_zone->override_ops = override_ops;
+
+ status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt);
+ if (ACPI_FAILURE(status))
+ trip_cnt = 0;
+ else {
+ int34x_thermal_zone->aux_trips =
+ kcalloc(trip_cnt,
+ sizeof(*int34x_thermal_zone->aux_trips),
+ GFP_KERNEL);
+ if (!int34x_thermal_zone->aux_trips) {
+ ret = -ENOMEM;
+ goto err_trip_alloc;
+ }
+ trip_mask = BIT(trip_cnt) - 1;
+ int34x_thermal_zone->aux_trip_nr = trip_cnt;
+ }
+
+ trip_cnt = int340x_thermal_read_trips(int34x_thermal_zone);
+
+ int34x_thermal_zone->lpat_table = acpi_lpat_get_conversion_table(
+ adev->handle);
+
+ int34x_thermal_zone->zone = thermal_zone_device_register(
+ acpi_device_bid(adev),
+ trip_cnt,
+ trip_mask, int34x_thermal_zone,
+ &int340x_thermal_zone_ops,
+ &int340x_thermal_params,
+ 0, 0);
+ if (IS_ERR(int34x_thermal_zone->zone)) {
+ ret = PTR_ERR(int34x_thermal_zone->zone);
+ goto err_thermal_zone;
+ }
+
+ return int34x_thermal_zone;
+
+err_thermal_zone:
+ acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table);
+ kfree(int34x_thermal_zone->aux_trips);
+err_trip_alloc:
+ kfree(int34x_thermal_zone);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(int340x_thermal_zone_add);
+
+void int340x_thermal_zone_remove(struct int34x_thermal_zone
+ *int34x_thermal_zone)
+{
+ thermal_zone_device_unregister(int34x_thermal_zone->zone);
+ acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table);
+ kfree(int34x_thermal_zone->aux_trips);
+ kfree(int34x_thermal_zone);
+}
+EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove);
+
+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/int340x_thermal/int340x_thermal_zone.h b/drivers/thermal/int340x_thermal/int340x_thermal_zone.h
new file mode 100644
index 000000000..5f3ba4775
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/int340x_thermal_zone.h
@@ -0,0 +1,70 @@
+/*
+ * int340x_thermal_zone.h
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#ifndef __INT340X_THERMAL_ZONE_H__
+#define __INT340X_THERMAL_ZONE_H__
+
+#include <acpi/acpi_lpat.h>
+
+#define INT340X_THERMAL_MAX_ACT_TRIP_COUNT 10
+
+struct active_trip {
+ int temp;
+ int id;
+ bool valid;
+};
+
+struct int34x_thermal_zone {
+ struct acpi_device *adev;
+ struct active_trip act_trips[INT340X_THERMAL_MAX_ACT_TRIP_COUNT];
+ unsigned long *aux_trips;
+ int aux_trip_nr;
+ int psv_temp;
+ int psv_trip_id;
+ int crt_temp;
+ int crt_trip_id;
+ int hot_temp;
+ int hot_trip_id;
+ struct thermal_zone_device *zone;
+ struct thermal_zone_device_ops *override_ops;
+ void *priv_data;
+ struct acpi_lpat_conversion_table *lpat_table;
+};
+
+struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *,
+ struct thermal_zone_device_ops *override_ops);
+void int340x_thermal_zone_remove(struct int34x_thermal_zone *);
+int int340x_thermal_read_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/int340x_thermal/processor_thermal_device.c b/drivers/thermal/int340x_thermal/processor_thermal_device.c
new file mode 100644
index 000000000..8e1cf4d78
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/processor_thermal_device.c
@@ -0,0 +1,527 @@
+/*
+ * processor_thermal_device.c
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#include "int340x_thermal_zone.h"
+#include "../intel_soc_dts_iosf.h"
+
+/* Broadwell-U/HSB thermal reporting device */
+#define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603
+#define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03
+
+/* Skylake thermal reporting device */
+#define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903
+
+/* CannonLake thermal reporting device */
+#define PCI_DEVICE_ID_PROC_CNL_THERMAL 0x5a03
+#define PCI_DEVICE_ID_PROC_CFL_THERMAL 0x3E83
+
+/* Braswell thermal reporting device */
+#define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC
+
+/* Broxton thermal reporting device */
+#define PCI_DEVICE_ID_PROC_BXT0_THERMAL 0x0A8C
+#define PCI_DEVICE_ID_PROC_BXT1_THERMAL 0x1A8C
+#define PCI_DEVICE_ID_PROC_BXTX_THERMAL 0x4A8C
+#define PCI_DEVICE_ID_PROC_BXTP_THERMAL 0x5A8C
+
+/* GeminiLake thermal reporting device */
+#define PCI_DEVICE_ID_PROC_GLK_THERMAL 0x318C
+
+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;
+};
+
+enum proc_thermal_emum_mode_type {
+ PROC_THERMAL_NONE,
+ PROC_THERMAL_PCI,
+ PROC_THERMAL_PLATFORM_DEV
+};
+
+/*
+ * We can have only one type of enumeration, PCI or Platform,
+ * not both. So we don't need instance specific data.
+ */
+static enum proc_thermal_emum_mode_type proc_thermal_emum_mode =
+ PROC_THERMAL_NONE;
+
+#define POWER_LIMIT_SHOW(index, suffix) \
+static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct pci_dev *pci_dev; \
+ struct platform_device *pdev; \
+ struct proc_thermal_device *proc_dev; \
+ \
+ if (proc_thermal_emum_mode == PROC_THERMAL_NONE) { \
+ dev_warn(dev, "Attempted to get power limit before device was initialized!\n"); \
+ return 0; \
+ } \
+ \
+ if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) { \
+ pdev = to_platform_device(dev); \
+ proc_dev = platform_get_drvdata(pdev); \
+ } else { \
+ pci_dev = to_pci_dev(dev); \
+ proc_dev = pci_get_drvdata(pci_dev); \
+ } \
+ return sprintf(buf, "%lu\n",\
+ (unsigned long)proc_dev->power_limits[index].suffix * 1000); \
+}
+
+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 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,
+ NULL
+};
+
+static const struct attribute_group power_limit_attribute_group = {
+ .attrs = power_limit_attrs,
+ .name = "power_limits"
+};
+
+static int stored_tjmax; /* since it is fixed, we can have local storage */
+
+static int get_tjmax(void)
+{
+ u32 eax, edx;
+ u32 val;
+ int err;
+
+ err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
+ if (err)
+ return err;
+
+ val = (eax >> 16) & 0xff;
+ if (val)
+ return val;
+
+ return -EINVAL;
+}
+
+static int read_temp_msr(int *temp)
+{
+ int cpu;
+ u32 eax, edx;
+ int err;
+ unsigned long curr_temp_off = 0;
+
+ *temp = 0;
+
+ for_each_online_cpu(cpu) {
+ err = rdmsr_safe_on_cpu(cpu, MSR_IA32_THERM_STATUS, &eax,
+ &edx);
+ if (err)
+ goto err_ret;
+ else {
+ if (eax & 0x80000000) {
+ curr_temp_off = (eax >> 16) & 0x7f;
+ if (!*temp || curr_temp_off < *temp)
+ *temp = curr_temp_off;
+ } else {
+ err = -EINVAL;
+ goto err_ret;
+ }
+ }
+ }
+
+ return 0;
+err_ret:
+ return err;
+}
+
+static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone,
+ int *temp)
+{
+ int ret;
+
+ ret = read_temp_msr(temp);
+ if (!ret)
+ *temp = (stored_tjmax - *temp) * 1000;
+
+ return ret;
+}
+
+static struct thermal_zone_device_ops proc_thermal_local_ops = {
+ .get_temp = proc_thermal_get_zone_temp,
+};
+
+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_err(proc_priv->dev, "Unsupported event [0x%x]\n", event);
+ break;
+ }
+}
+
+
+static int proc_thermal_add(struct device *dev,
+ struct proc_thermal_device **priv)
+{
+ struct proc_thermal_device *proc_priv;
+ struct acpi_device *adev;
+ acpi_status status;
+ unsigned long long tmp;
+ struct thermal_zone_device_ops *ops = NULL;
+ int ret;
+
+ adev = ACPI_COMPANION(dev);
+ if (!adev)
+ return -ENODEV;
+
+ proc_priv = devm_kzalloc(dev, sizeof(*proc_priv), GFP_KERNEL);
+ if (!proc_priv)
+ return -ENOMEM;
+
+ proc_priv->dev = dev;
+ proc_priv->adev = adev;
+ *priv = proc_priv;
+
+ ret = proc_thermal_read_ppcc(proc_priv);
+ if (ret)
+ return ret;
+
+ status = acpi_evaluate_integer(adev->handle, "_TMP", NULL, &tmp);
+ if (ACPI_FAILURE(status)) {
+ /* there is no _TMP method, add local method */
+ stored_tjmax = get_tjmax();
+ if (stored_tjmax > 0)
+ ops = &proc_thermal_local_ops;
+ }
+
+ proc_priv->int340x_zone = int340x_thermal_zone_add(adev, ops);
+ 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;
+
+ return 0;
+
+remove_zone:
+ int340x_thermal_zone_remove(proc_priv->int340x_zone);
+
+ return ret;
+}
+
+static 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_group(&proc_priv->dev->kobj,
+ &power_limit_attribute_group);
+}
+
+static int int3401_add(struct platform_device *pdev)
+{
+ struct proc_thermal_device *proc_priv;
+ int ret;
+
+ if (proc_thermal_emum_mode == PROC_THERMAL_PCI) {
+ dev_err(&pdev->dev, "error: enumerated as PCI dev\n");
+ return -ENODEV;
+ }
+
+ ret = proc_thermal_add(&pdev->dev, &proc_priv);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, proc_priv);
+ proc_thermal_emum_mode = PROC_THERMAL_PLATFORM_DEV;
+
+ dev_info(&pdev->dev, "Creating sysfs group for PROC_THERMAL_PLATFORM_DEV\n");
+
+ return sysfs_create_group(&pdev->dev.kobj,
+ &power_limit_attribute_group);
+}
+
+static int int3401_remove(struct platform_device *pdev)
+{
+ proc_thermal_remove(platform_get_drvdata(pdev));
+
+ return 0;
+}
+
+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 *unused)
+{
+ struct proc_thermal_device *proc_priv;
+ int ret;
+
+ if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) {
+ dev_err(&pdev->dev, "error: enumerated as platform dev\n");
+ return -ENODEV;
+ }
+
+ ret = pci_enable_device(pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "error: could not enable device\n");
+ return ret;
+ }
+
+ ret = proc_thermal_add(&pdev->dev, &proc_priv);
+ if (ret) {
+ pci_disable_device(pdev);
+ return ret;
+ }
+
+ pci_set_drvdata(pdev, proc_priv);
+ proc_thermal_emum_mode = PROC_THERMAL_PCI;
+
+ if (pdev->device == PCI_DEVICE_ID_PROC_BSW_THERMAL) {
+ /*
+ * Enumerate additional DTS sensors available via IOSF.
+ * But we are not treating as a failure condition, if
+ * there are no aux DTSs enabled or fails. This driver
+ * already exposes sensors, which can be accessed via
+ * ACPI/MSR. So we don't want to fail for auxiliary DTSs.
+ */
+ proc_priv->soc_dts = intel_soc_dts_iosf_init(
+ INTEL_SOC_DTS_INTERRUPT_MSI, 2, 0);
+
+ if (!IS_ERR(proc_priv->soc_dts) && pdev->irq) {
+ ret = pci_enable_msi(pdev);
+ if (!ret) {
+ ret = request_threaded_irq(pdev->irq, NULL,
+ proc_thermal_pci_msi_irq,
+ IRQF_ONESHOT, "proc_thermal",
+ pdev);
+ if (ret) {
+ intel_soc_dts_iosf_exit(
+ proc_priv->soc_dts);
+ pci_disable_msi(pdev);
+ proc_priv->soc_dts = NULL;
+ }
+ }
+ } else
+ dev_err(&pdev->dev, "No auxiliary DTSs enabled\n");
+ }
+
+ dev_info(&pdev->dev, "Creating sysfs group for PROC_THERMAL_PCI\n");
+
+ return sysfs_create_group(&pdev->dev.kobj,
+ &power_limit_attribute_group);
+}
+
+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_remove(proc_priv);
+ pci_disable_device(pdev);
+}
+
+static const struct pci_device_id proc_thermal_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_SKL_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT0_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT1_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTX_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTP_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CNL_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CFL_THERMAL)},
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_GLK_THERMAL)},
+ { 0, },
+};
+
+MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids);
+
+static struct pci_driver proc_thermal_pci_driver = {
+ .name = "proc_thermal",
+ .probe = proc_thermal_pci_probe,
+ .remove = proc_thermal_pci_remove,
+ .id_table = proc_thermal_pci_ids,
+};
+
+static const struct acpi_device_id int3401_device_ids[] = {
+ {"INT3401", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, int3401_device_ids);
+
+static struct platform_driver int3401_driver = {
+ .probe = int3401_add,
+ .remove = int3401_remove,
+ .driver = {
+ .name = "int3401 thermal",
+ .acpi_match_table = int3401_device_ids,
+ },
+};
+
+static int __init proc_thermal_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&int3401_driver);
+ if (ret)
+ return ret;
+
+ ret = pci_register_driver(&proc_thermal_pci_driver);
+
+ return ret;
+}
+
+static void __exit proc_thermal_exit(void)
+{
+ platform_driver_unregister(&int3401_driver);
+ pci_unregister_driver(&proc_thermal_pci_driver);
+}
+
+module_init(proc_thermal_init);
+module_exit(proc_thermal_exit);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel_bxt_pmic_thermal.c b/drivers/thermal/intel_bxt_pmic_thermal.c
new file mode 100644
index 000000000..94cfd0064
--- /dev/null
+++ b/drivers/thermal/intel_bxt_pmic_thermal.c
@@ -0,0 +1,299 @@
+/*
+ * Intel Broxton PMIC thermal driver
+ *
+ * Copyright (C) 2016 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed 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/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) {
+ dev_err(dev, "request irq(%d) failed: %d\n", virq, 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_pch_thermal.c b/drivers/thermal/intel_pch_thermal.c
new file mode 100644
index 000000000..8a7f69b4b
--- /dev/null
+++ b/drivers/thermal/intel_pch_thermal.c
@@ -0,0 +1,432 @@
+/* intel_pch_thermal.c - Intel PCH Thermal driver
+ *
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * Authors:
+ * Tushar Dave <tushar.n.dave@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#include <linux/pm.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 */
+
+/* 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_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 */
+
+static char driver_name[] = "Intel PCH thermal driver";
+
+struct pch_thermal_device {
+ void __iomem *hw_base;
+ const struct pch_dev_ops *ops;
+ struct pci_dev *pdev;
+ struct thermal_zone_device *tzd;
+ int crt_trip_id;
+ unsigned long crt_temp;
+ int hot_trip_id;
+ unsigned long hot_temp;
+ int psv_trip_id;
+ unsigned long psv_temp;
+ 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 void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd,
+ int *nr_trips)
+{
+ struct acpi_device *adev;
+
+ ptd->psv_trip_id = -1;
+
+ adev = ACPI_COMPANION(&ptd->pdev->dev);
+ if (adev) {
+ unsigned long long r;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(adev->handle, "_PSV", NULL,
+ &r);
+ if (ACPI_SUCCESS(status)) {
+ unsigned long trip_temp;
+
+ trip_temp = DECI_KELVIN_TO_MILLICELSIUS(r);
+ if (trip_temp) {
+ ptd->psv_temp = trip_temp;
+ ptd->psv_trip_id = *nr_trips;
+ ++(*nr_trips);
+ }
+ }
+ }
+}
+#else
+static void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd,
+ int *nr_trips)
+{
+ ptd->psv_trip_id = -1;
+
+}
+#endif
+
+static int pch_wpt_init(struct pch_thermal_device *ptd, int *nr_trips)
+{
+ u8 tsel;
+ u16 trip_temp;
+
+ *nr_trips = 0;
+
+ /* 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");
+ return -ENODEV;
+ }
+
+ 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");
+ return -ENODEV;
+ }
+
+read_trips:
+ ptd->crt_trip_id = -1;
+ trip_temp = readw(ptd->hw_base + WPT_CTT);
+ trip_temp &= 0x1FF;
+ if (trip_temp) {
+ /* Resolution of 1/2 degree C and an offset of -50C */
+ ptd->crt_temp = trip_temp * 1000 / 2 - 50000;
+ ptd->crt_trip_id = 0;
+ ++(*nr_trips);
+ }
+
+ ptd->hot_trip_id = -1;
+ trip_temp = readw(ptd->hw_base + WPT_PHL);
+ trip_temp &= 0x1FF;
+ if (trip_temp) {
+ /* Resolution of 1/2 degree C and an offset of -50C */
+ ptd->hot_temp = trip_temp * 1000 / 2 - 50000;
+ ptd->hot_trip_id = *nr_trips;
+ ++(*nr_trips);
+ }
+
+ pch_wpt_add_acpi_psv_trip(ptd, nr_trips);
+
+ return 0;
+}
+
+static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp)
+{
+ u16 wpt_temp;
+
+ wpt_temp = WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP);
+
+ /* Resolution of 1/2 degree C and an offset of -50C */
+ *temp = (wpt_temp * 1000 / 2 - 50000);
+
+ return 0;
+}
+
+static int pch_wpt_suspend(struct pch_thermal_device *ptd)
+{
+ u8 tsel;
+
+ if (ptd->bios_enabled)
+ return 0;
+
+ tsel = readb(ptd->hw_base + WPT_TSEL);
+
+ writeb(tsel & 0xFE, ptd->hw_base + WPT_TSEL);
+
+ return 0;
+}
+
+static int pch_wpt_resume(struct pch_thermal_device *ptd)
+{
+ 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;
+}
+
+struct pch_dev_ops {
+ int (*hw_init)(struct pch_thermal_device *ptd, int *nr_trips);
+ int (*get_temp)(struct pch_thermal_device *ptd, int *temp);
+ int (*suspend)(struct pch_thermal_device *ptd);
+ int (*resume)(struct pch_thermal_device *ptd);
+};
+
+
+/* dev ops for Wildcat Point */
+static const struct pch_dev_ops pch_dev_ops_wpt = {
+ .hw_init = pch_wpt_init,
+ .get_temp = pch_wpt_get_temp,
+ .suspend = pch_wpt_suspend,
+ .resume = pch_wpt_resume,
+};
+
+static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ struct pch_thermal_device *ptd = tzd->devdata;
+
+ return ptd->ops->get_temp(ptd, temp);
+}
+
+static int pch_get_trip_type(struct thermal_zone_device *tzd, int trip,
+ enum thermal_trip_type *type)
+{
+ struct pch_thermal_device *ptd = tzd->devdata;
+
+ if (ptd->crt_trip_id == trip)
+ *type = THERMAL_TRIP_CRITICAL;
+ else if (ptd->hot_trip_id == trip)
+ *type = THERMAL_TRIP_HOT;
+ else if (ptd->psv_trip_id == trip)
+ *type = THERMAL_TRIP_PASSIVE;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int pch_get_trip_temp(struct thermal_zone_device *tzd, int trip, int *temp)
+{
+ struct pch_thermal_device *ptd = tzd->devdata;
+
+ if (ptd->crt_trip_id == trip)
+ *temp = ptd->crt_temp;
+ else if (ptd->hot_trip_id == trip)
+ *temp = ptd->hot_temp;
+ else if (ptd->psv_trip_id == trip)
+ *temp = ptd->psv_temp;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops tzd_ops = {
+ .get_temp = pch_thermal_get_temp,
+ .get_trip_type = pch_get_trip_type,
+ .get_trip_temp = pch_get_trip_temp,
+};
+
+enum board_ids {
+ board_hsw,
+ board_wpt,
+ board_skl,
+ board_cnl,
+};
+
+static const struct board_info {
+ const char *name;
+ const struct pch_dev_ops *ops;
+} board_info[] = {
+ [board_hsw] = {
+ .name = "pch_haswell",
+ .ops = &pch_dev_ops_wpt,
+ },
+ [board_wpt] = {
+ .name = "pch_wildcat_point",
+ .ops = &pch_dev_ops_wpt,
+ },
+ [board_skl] = {
+ .name = "pch_skylake",
+ .ops = &pch_dev_ops_wpt,
+ },
+ [board_cnl] = {
+ .name = "pch_cannonlake",
+ .ops = &pch_dev_ops_wpt,
+ },
+};
+
+static int intel_pch_thermal_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ enum board_ids board_id = id->driver_data;
+ const struct board_info *bi = &board_info[board_id];
+ struct pch_thermal_device *ptd;
+ int err;
+ int nr_trips;
+
+ ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL);
+ if (!ptd)
+ return -ENOMEM;
+
+ ptd->ops = bi->ops;
+
+ 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;
+ }
+
+ err = ptd->ops->hw_init(ptd, &nr_trips);
+ if (err)
+ goto error_cleanup;
+
+ ptd->tzd = thermal_zone_device_register(bi->name, nr_trips, 0, ptd,
+ &tzd_ops, NULL, 0, 0);
+ if (IS_ERR(ptd->tzd)) {
+ dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
+ bi->name);
+ err = PTR_ERR(ptd->tzd);
+ goto error_cleanup;
+ }
+
+ return 0;
+
+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_set_drvdata(pdev, NULL);
+ pci_release_region(pdev, 0);
+ pci_disable_device(pdev);
+}
+
+static int intel_pch_thermal_suspend(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
+
+ return ptd->ops->suspend(ptd);
+}
+
+static int intel_pch_thermal_resume(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
+
+ return ptd->ops->resume(ptd);
+}
+
+static const struct pci_device_id intel_pch_thermal_id[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1),
+ .driver_data = board_hsw, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2),
+ .driver_data = board_hsw, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT),
+ .driver_data = board_wpt, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL),
+ .driver_data = board_skl, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL_H),
+ .driver_data = board_skl, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL),
+ .driver_data = board_cnl, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H),
+ .driver_data = board_cnl, },
+ { 0, },
+};
+MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
+
+static const struct dev_pm_ops intel_pch_pm_ops = {
+ .suspend = intel_pch_thermal_suspend,
+ .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_powerclamp.c b/drivers/thermal/intel_powerclamp.c
new file mode 100644
index 000000000..8e8328347
--- /dev/null
+++ b/drivers/thermal/intel_powerclamp.c
@@ -0,0 +1,815 @@
+/*
+ * intel_powerclamp.c - package c-state idle injection
+ *
+ * Copyright (c) 2012, Intel Corporation.
+ *
+ * Authors:
+ * Arjan van de Ven <arjan@linux.intel.com>
+ * Jacob Pan <jacob.jun.pan@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *
+ * 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/kthread.h>
+#include <linux/cpu.h>
+#include <linux/thermal.h>
+#include <linux/slab.h>
+#include <linux/tick.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/sched/rt.h>
+#include <uapi/linux/sched/types.h>
+
+#include <asm/nmi.h>
+#include <asm/msr.h>
+#include <asm/mwait.h>
+#include <asm/cpu_device_id.h>
+#include <asm/hardirq.h>
+
+#define MAX_TARGET_RATIO (50U)
+/* 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 unsigned int target_mwait;
+static struct dentry *debug_dir;
+
+/* user selected target */
+static unsigned int set_target_ratio;
+static unsigned int current_ratio;
+static bool should_skip;
+static bool reduce_irq;
+static atomic_t idle_wakeup_counter;
+static unsigned int control_cpu; /* The cpu assigned to collect stat and update
+ * control parameters. default to BSP but BSP
+ * can be offlined.
+ */
+static bool clamping;
+
+static const struct sched_param sparam = {
+ .sched_priority = MAX_USER_RT_PRIO / 2,
+};
+struct powerclamp_worker_data {
+ struct kthread_worker *worker;
+ struct kthread_work balancing_work;
+ struct kthread_delayed_work idle_injection_work;
+ unsigned int cpu;
+ unsigned int count;
+ unsigned int guard;
+ unsigned int window_size_now;
+ unsigned int target_ratio;
+ unsigned int duration_jiffies;
+ bool clamping;
+};
+
+static struct powerclamp_worker_data __percpu *worker_data;
+static struct thermal_cooling_device *cooling_dev;
+static unsigned long *cpu_clamping_mask; /* bit map for tracking per cpu
+ * clamping kthread worker
+ */
+
+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;
+ }
+
+ duration = clamp(new_duration, 6ul, 25ul);
+ smp_mb();
+
+exit:
+
+ return ret;
+}
+
+static const struct kernel_param_ops duration_ops = {
+ .set = duration_set,
+ .get = param_get_int,
+};
+
+
+module_param_cb(duration, &duration_ops, &duration, 0644);
+MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec.");
+
+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.");
+
+static void find_target_mwait(void)
+{
+ unsigned int eax, ebx, ecx, edx;
+ unsigned int highest_cstate = 0;
+ unsigned int highest_subcstate = 0;
+ int i;
+
+ if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF)
+ return;
+
+ cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &edx);
+
+ if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) ||
+ !(ecx & CPUID5_ECX_INTERRUPT_BREAK))
+ return;
+
+ edx >>= MWAIT_SUBSTATE_SIZE;
+ for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) {
+ if (edx & MWAIT_SUBSTATE_MASK) {
+ highest_cstate = i;
+ highest_subcstate = edx & MWAIT_SUBSTATE_MASK;
+ }
+ }
+ target_mwait = (highest_cstate << MWAIT_SUBSTATE_SIZE) |
+ (highest_subcstate - 1);
+
+}
+
+struct pkg_cstate_info {
+ bool skip;
+ int msr_index;
+ 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 (!rdmsrl_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 (!rdmsrl_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;
+
+ /* 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;
+ }
+
+ /* REVISIT: simple penalty of double idle injection */
+ if (reduce_irq)
+ comp = ratio;
+ /* do not exceed limit */
+ if (comp + ratio >= MAX_TARGET_RATIO)
+ comp = MAX_TARGET_RATIO - ratio - 1;
+
+ 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 or
+ * there are too many wakeups during the last idle injection period, we
+ * cannot trust the data for compensation.
+ */
+ if (d->confidence >= CONFIDENCE_OK ||
+ atomic_read(&idle_wakeup_counter) >
+ win * num_online_cpus())
+ return;
+
+ delta = set_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);
+ /*
+ * too many external interrupts, set flag such
+ * that we can take measure later.
+ */
+ reduce_irq = atomic_read(&idle_wakeup_counter) >=
+ 2 * win * num_online_cpus();
+
+ atomic_set(&idle_wakeup_counter, 0);
+ /* if we are above target+guard, skip */
+ return set_target_ratio + guard <= current_ratio;
+}
+
+static void clamp_balancing_func(struct kthread_work *work)
+{
+ struct powerclamp_worker_data *w_data;
+ int sleeptime;
+ unsigned long target_jiffies;
+ unsigned int compensated_ratio;
+ int interval; /* jiffies to sleep for each attempt */
+
+ w_data = container_of(work, struct powerclamp_worker_data,
+ balancing_work);
+
+ /*
+ * 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.
+ */
+ w_data->target_ratio = READ_ONCE(set_target_ratio);
+ w_data->guard = 1 + w_data->target_ratio / 20;
+ w_data->window_size_now = window_size;
+ w_data->duration_jiffies = msecs_to_jiffies(duration);
+ w_data->count++;
+
+ /*
+ * 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 = w_data->target_ratio +
+ get_compensation(w_data->target_ratio);
+ if (compensated_ratio <= 0)
+ compensated_ratio = 1;
+ interval = w_data->duration_jiffies * 100 / compensated_ratio;
+
+ /* align idle time */
+ target_jiffies = roundup(jiffies, interval);
+ sleeptime = target_jiffies - jiffies;
+ if (sleeptime <= 0)
+ sleeptime = 1;
+
+ if (clamping && w_data->clamping && cpu_online(w_data->cpu))
+ kthread_queue_delayed_work(w_data->worker,
+ &w_data->idle_injection_work,
+ sleeptime);
+}
+
+static void clamp_idle_injection_func(struct kthread_work *work)
+{
+ struct powerclamp_worker_data *w_data;
+
+ w_data = container_of(work, struct powerclamp_worker_data,
+ idle_injection_work.work);
+
+ /*
+ * only elected controlling cpu can collect stats and update
+ * control parameters.
+ */
+ if (w_data->cpu == control_cpu &&
+ !(w_data->count % w_data->window_size_now)) {
+ should_skip =
+ powerclamp_adjust_controls(w_data->target_ratio,
+ w_data->guard,
+ w_data->window_size_now);
+ smp_mb();
+ }
+
+ if (should_skip)
+ goto balance;
+
+ play_idle(jiffies_to_msecs(w_data->duration_jiffies));
+
+balance:
+ if (clamping && w_data->clamping && cpu_online(w_data->cpu))
+ kthread_queue_work(w_data->worker, &w_data->balancing_work);
+}
+
+/*
+ * 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;
+
+ if (true == clamping)
+ schedule_delayed_work(&poll_pkg_cstate_work, HZ);
+}
+
+static void start_power_clamp_worker(unsigned long cpu)
+{
+ struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu);
+ struct kthread_worker *worker;
+
+ worker = kthread_create_worker_on_cpu(cpu, 0, "kidle_inj/%ld", cpu);
+ if (IS_ERR(worker))
+ return;
+
+ w_data->worker = worker;
+ w_data->count = 0;
+ w_data->cpu = cpu;
+ w_data->clamping = true;
+ set_bit(cpu, cpu_clamping_mask);
+ sched_setscheduler(worker->task, SCHED_FIFO, &sparam);
+ kthread_init_work(&w_data->balancing_work, clamp_balancing_func);
+ kthread_init_delayed_work(&w_data->idle_injection_work,
+ clamp_idle_injection_func);
+ kthread_queue_work(w_data->worker, &w_data->balancing_work);
+}
+
+static void stop_power_clamp_worker(unsigned long cpu)
+{
+ struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu);
+
+ if (!w_data->worker)
+ return;
+
+ w_data->clamping = false;
+ /*
+ * Make sure that all works that get queued after this point see
+ * the clamping disabled. The counter part is not needed because
+ * there is an implicit memory barrier when the queued work
+ * is proceed.
+ */
+ smp_wmb();
+ kthread_cancel_work_sync(&w_data->balancing_work);
+ kthread_cancel_delayed_work_sync(&w_data->idle_injection_work);
+ /*
+ * The balancing work still might be queued here because
+ * the handling of the "clapming" variable, cancel, and queue
+ * operations are not synchronized via a lock. But it is not
+ * a big deal. The balancing work is fast and destroy kthread
+ * will wait for it.
+ */
+ clear_bit(w_data->cpu, cpu_clamping_mask);
+ kthread_destroy_worker(w_data->worker);
+
+ w_data->worker = NULL;
+}
+
+static int start_power_clamp(void)
+{
+ unsigned long cpu;
+
+ set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1);
+ /* prevent cpu hotplug */
+ get_online_cpus();
+
+ /* prefer BSP */
+ control_cpu = 0;
+ if (!cpu_online(control_cpu))
+ control_cpu = smp_processor_id();
+
+ clamping = true;
+ schedule_delayed_work(&poll_pkg_cstate_work, 0);
+
+ /* start one kthread worker per online cpu */
+ for_each_online_cpu(cpu) {
+ start_power_clamp_worker(cpu);
+ }
+ put_online_cpus();
+
+ return 0;
+}
+
+static void end_power_clamp(void)
+{
+ int i;
+
+ /*
+ * Block requeuing in all the kthread workers. They will flush and
+ * stop faster.
+ */
+ clamping = false;
+ if (bitmap_weight(cpu_clamping_mask, num_possible_cpus())) {
+ for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) {
+ pr_debug("clamping worker for cpu %d alive, destroy\n",
+ i);
+ stop_power_clamp_worker(i);
+ }
+ }
+}
+
+static int powerclamp_cpu_online(unsigned int cpu)
+{
+ if (clamping == false)
+ return 0;
+ start_power_clamp_worker(cpu);
+ /* prefer BSP as controlling CPU */
+ if (cpu == 0) {
+ control_cpu = 0;
+ smp_mb();
+ }
+ return 0;
+}
+
+static int powerclamp_cpu_predown(unsigned int cpu)
+{
+ if (clamping == false)
+ return 0;
+
+ stop_power_clamp_worker(cpu);
+ if (cpu != control_cpu)
+ return 0;
+
+ control_cpu = cpumask_first(cpu_online_mask);
+ if (control_cpu == cpu)
+ control_cpu = cpumask_next(cpu, cpu_online_mask);
+ smp_mb();
+ return 0;
+}
+
+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)
+{
+ if (true == clamping)
+ *state = pkg_cstate_ratio_cur;
+ else
+ /* to save power, do not poll idle ratio while not clamping */
+ *state = -1; /* indicates invalid state */
+
+ return 0;
+}
+
+static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long new_target_ratio)
+{
+ int ret = 0;
+
+ new_target_ratio = clamp(new_target_ratio, 0UL,
+ (unsigned long) (MAX_TARGET_RATIO-1));
+ if (set_target_ratio == 0 && new_target_ratio > 0) {
+ pr_info("Start idle injection to reduce power\n");
+ set_target_ratio = new_target_ratio;
+ ret = start_power_clamp();
+ goto exit_set;
+ } else if (set_target_ratio > 0 && new_target_ratio == 0) {
+ pr_info("Stop forced idle injection\n");
+ end_power_clamp();
+ set_target_ratio = 0;
+ } else /* adjust currently running */ {
+ set_target_ratio = new_target_ratio;
+ /* make new set_target_ratio visible to other cpus */
+ smp_mb();
+ }
+
+exit_set:
+ return ret;
+}
+
+/* bind to generic thermal layer as cooling device*/
+static struct thermal_cooling_device_ops powerclamp_cooling_ops = {
+ .get_max_state = powerclamp_get_max_state,
+ .get_cur_state = powerclamp_get_cur_state,
+ .set_cur_state = powerclamp_set_cur_state,
+};
+
+static const struct x86_cpu_id __initconst intel_powerclamp_ids[] = {
+ { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_MWAIT },
+ {}
+};
+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;
+ }
+
+ /* find the deepest mwait value */
+ find_target_mwait();
+
+ return 0;
+}
+
+static int powerclamp_debug_show(struct seq_file *m, void *unused)
+{
+ int i = 0;
+
+ seq_printf(m, "controlling cpu: %d\n", control_cpu);
+ 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;
+}
+
+static int powerclamp_debug_open(struct inode *inode,
+ struct file *file)
+{
+ return single_open(file, powerclamp_debug_show, inode->i_private);
+}
+
+static const struct file_operations powerclamp_debug_fops = {
+ .open = powerclamp_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .owner = THIS_MODULE,
+};
+
+static inline void powerclamp_create_debug_files(void)
+{
+ debug_dir = debugfs_create_dir("intel_powerclamp", NULL);
+ if (!debug_dir)
+ return;
+
+ if (!debugfs_create_file("powerclamp_calib", S_IRUGO, debug_dir,
+ cal_data, &powerclamp_debug_fops))
+ goto file_error;
+
+ return;
+
+file_error:
+ debugfs_remove_recursive(debug_dir);
+}
+
+static enum cpuhp_state hp_state;
+
+static int __init powerclamp_init(void)
+{
+ int retval;
+ int bitmap_size;
+
+ bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long);
+ cpu_clamping_mask = kzalloc(bitmap_size, GFP_KERNEL);
+ if (!cpu_clamping_mask)
+ return -ENOMEM;
+
+ /* probe cpu features and ids here */
+ retval = powerclamp_probe();
+ if (retval)
+ goto exit_free;
+
+ /* set default limit, maybe adjusted during runtime based on feedback */
+ window_size = 2;
+ retval = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
+ "thermal/intel_powerclamp:online",
+ powerclamp_cpu_online,
+ powerclamp_cpu_predown);
+ if (retval < 0)
+ goto exit_free;
+
+ hp_state = retval;
+
+ worker_data = alloc_percpu(struct powerclamp_worker_data);
+ if (!worker_data) {
+ retval = -ENOMEM;
+ goto exit_unregister;
+ }
+
+ cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL,
+ &powerclamp_cooling_ops);
+ if (IS_ERR(cooling_dev)) {
+ retval = -ENODEV;
+ goto exit_free_thread;
+ }
+
+ if (!duration)
+ duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES);
+
+ powerclamp_create_debug_files();
+
+ return 0;
+
+exit_free_thread:
+ free_percpu(worker_data);
+exit_unregister:
+ cpuhp_remove_state_nocalls(hp_state);
+exit_free:
+ kfree(cpu_clamping_mask);
+ return retval;
+}
+module_init(powerclamp_init);
+
+static void __exit powerclamp_exit(void)
+{
+ end_power_clamp();
+ cpuhp_remove_state_nocalls(hp_state);
+ free_percpu(worker_data);
+ thermal_cooling_device_unregister(cooling_dev);
+ kfree(cpu_clamping_mask);
+
+ cancel_delayed_work_sync(&poll_pkg_cstate_work);
+ debugfs_remove_recursive(debug_dir);
+}
+module_exit(powerclamp_exit);
+
+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_quark_dts_thermal.c b/drivers/thermal/intel_quark_dts_thermal.c
new file mode 100644
index 000000000..5d33b350d
--- /dev/null
+++ b/drivers/thermal/intel_quark_dts_thermal.c
@@ -0,0 +1,471 @@
+/*
+ * 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>
+
+#define X86_FAMILY_QUARK 0x5
+#define X86_MODEL_QUARK_X1000 0x9
+
+/* DTS reset is programmed via QRK_MBI_UNIT_SOC */
+#define QRK_DTS_REG_OFFSET_RESET 0x34
+#define QRK_DTS_RESET_BIT BIT(0)
+
+/* 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_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
+/* If DTS not locked, all trip points are configurable */
+#define QRK_DTS_WR_MASK_SET 0x3
+/* If DTS locked, all trip points are not configurable */
+#define QRK_DTS_WR_MASK_CLR 0
+
+#define DEFAULT_POLL_DELAY 2000
+
+struct soc_sensor_entry {
+ bool locked;
+ u32 store_ptps;
+ u32 store_dts_enable;
+ enum thermal_device_mode mode;
+ 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 = tzd->devdata;
+ 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) {
+ aux_entry->mode = THERMAL_DEVICE_ENABLED;
+ 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;
+
+ aux_entry->mode = THERMAL_DEVICE_ENABLED;
+ } else {
+ aux_entry->mode = THERMAL_DEVICE_DISABLED;
+ 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 = tzd->devdata;
+ 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)) {
+ aux_entry->mode = THERMAL_DEVICE_DISABLED;
+ 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;
+
+ aux_entry->mode = THERMAL_DEVICE_DISABLED;
+ } else {
+ aux_entry->mode = THERMAL_DEVICE_ENABLED;
+ pr_info("DTS is locked. Cannot disable DTS\n");
+ ret = -EPERM;
+ }
+
+ return ret;
+}
+
+static int _get_trip_temp(int trip, int *temp)
+{
+ int status;
+ 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 status;
+
+ /*
+ * 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 0;
+}
+
+static inline int sys_get_trip_temp(struct thermal_zone_device *tzd,
+ int trip, int *temp)
+{
+ return _get_trip_temp(trip, temp);
+}
+
+static inline int sys_get_crit_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ return _get_trip_temp(QRK_DTS_ID_TP_CRITICAL, temp);
+}
+
+static int update_trip_temp(struct soc_sensor_entry *aux_entry,
+ int trip, 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 * QRK_DTS_SHIFT_TP)));
+ out |= (temp_out & QRK_DTS_MASK_TP_THRES) <<
+ (trip * 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, int trip,
+ int temp)
+{
+ return update_trip_temp(tzd->devdata, trip, temp);
+}
+
+static int sys_get_trip_type(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trip_type *type)
+{
+ if (trip)
+ *type = THERMAL_TRIP_HOT;
+ else
+ *type = THERMAL_TRIP_CRITICAL;
+
+ return 0;
+}
+
+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_get_mode(struct thermal_zone_device *tzd,
+ enum thermal_device_mode *mode)
+{
+ struct soc_sensor_entry *aux_entry = tzd->devdata;
+ *mode = aux_entry->mode;
+ return 0;
+}
+
+static int sys_set_mode(struct thermal_zone_device *tzd,
+ enum thermal_device_mode mode)
+{
+ 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 struct thermal_zone_device_ops tzone_ops = {
+ .get_temp = sys_get_curr_temp,
+ .get_trip_temp = sys_get_trip_temp,
+ .get_trip_type = sys_get_trip_type,
+ .set_trip_temp = sys_set_trip_temp,
+ .get_crit_temp = sys_get_crit_temp,
+ .get_mode = sys_get_mode,
+ .set_mode = sys_set_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 soc_sensor_entry *aux_entry;
+ int err;
+ u32 out;
+ int wr_mask;
+
+ aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL);
+ 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;
+
+ if (out & QRK_DTS_LOCK_BIT) {
+ aux_entry->locked = true;
+ wr_mask = QRK_DTS_WR_MASK_CLR;
+ } else {
+ aux_entry->locked = false;
+ wr_mask = QRK_DTS_WR_MASK_SET;
+ }
+
+ /* 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;
+ }
+
+ aux_entry->tzone = thermal_zone_device_register("quark_dts",
+ QRK_MAX_DTS_TRIPS,
+ wr_mask,
+ aux_entry, &tzone_ops, NULL, 0, polling_delay);
+ if (IS_ERR(aux_entry->tzone)) {
+ err = PTR_ERR(aux_entry->tzone);
+ goto err_ret;
+ }
+
+ mutex_lock(&dts_update_mutex);
+ err = soc_dts_enable(aux_entry->tzone);
+ mutex_unlock(&dts_update_mutex);
+ 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_VENDOR_INTEL, X86_FAMILY_QUARK, X86_MODEL_QUARK_X1000 },
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, qrk_thermal_ids);
+
+static int __init intel_quark_thermal_init(void)
+{
+ int err = 0;
+
+ if (!x86_match_cpu(qrk_thermal_ids) || !iosf_mbi_available())
+ return -ENODEV;
+
+ soc_dts = alloc_soc_dts();
+ if (IS_ERR(soc_dts)) {
+ err = PTR_ERR(soc_dts);
+ goto err_free;
+ }
+
+ return 0;
+
+err_free:
+ free_soc_dts(soc_dts);
+ return err;
+}
+
+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_soc_dts_iosf.c b/drivers/thermal/intel_soc_dts_iosf.c
new file mode 100644
index 000000000..e0813dfaa
--- /dev/null
+++ b/drivers/thermal/intel_soc_dts_iosf.c
@@ -0,0 +1,478 @@
+/*
+ * intel_soc_dts_iosf.c
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <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
+
+/* Only 2 out of 4 is allowed for OSPM */
+#define SOC_MAX_DTS_TRIPS 2
+
+/* Mask for two trips in status bits */
+#define SOC_DTS_TRIP_MASK 0x03
+
+/* DTS0 and DTS 1 */
+#define SOC_MAX_DTS_SENSORS 2
+
+static int get_tj_max(u32 *tj_max)
+{
+ u32 eax, edx;
+ u32 val;
+ int err;
+
+ err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
+ if (err)
+ goto err_ret;
+ else {
+ val = (eax >> 16) & 0xff;
+ if (val)
+ *tj_max = val * 1000;
+ else {
+ err = -EINVAL;
+ goto err_ret;
+ }
+ }
+
+ return 0;
+err_ret:
+ *tj_max = 0;
+
+ return err;
+}
+
+static int sys_get_trip_temp(struct thermal_zone_device *tzd, int trip,
+ int *temp)
+{
+ int status;
+ u32 out;
+ struct intel_soc_dts_sensor_entry *dts;
+ struct intel_soc_dts_sensors *sensors;
+
+ dts = tzd->devdata;
+ sensors = dts->sensors;
+ mutex_lock(&sensors->dts_update_lock);
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_PTPS, &out);
+ mutex_unlock(&sensors->dts_update_lock);
+ if (status)
+ return status;
+
+ out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING;
+ if (!out)
+ *temp = 0;
+ else
+ *temp = sensors->tj_max - out * 1000;
+
+ return 0;
+}
+
+static int update_trip_temp(struct intel_soc_dts_sensor_entry *dts,
+ int thres_index, int temp,
+ enum thermal_trip_type trip_type)
+{
+ int status;
+ u32 temp_out;
+ u32 out;
+ u32 store_ptps;
+ u32 store_ptmc;
+ u32 store_te_out;
+ u32 te_out;
+ u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE;
+ struct intel_soc_dts_sensors *sensors = dts->sensors;
+
+ if (sensors->intr_type == INTEL_SOC_DTS_INTERRUPT_MSI)
+ int_enable_bit |= SOC_DTS_TE_MSI_ENABLE;
+
+ 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;
+
+ out = (store_ptps & ~(0xFF << (thres_index * 8)));
+ out |= (temp_out & 0xFF) << (thres_index * 8);
+ 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;
+
+ dts->trip_types[thres_index] = trip_type;
+
+ 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, int trip,
+ int temp)
+{
+ struct intel_soc_dts_sensor_entry *dts = tzd->devdata;
+ struct intel_soc_dts_sensors *sensors = dts->sensors;
+ int status;
+
+ if (temp > sensors->tj_max)
+ return -EINVAL;
+
+ mutex_lock(&sensors->dts_update_lock);
+ status = update_trip_temp(tzd->devdata, trip, temp,
+ dts->trip_types[trip]);
+ mutex_unlock(&sensors->dts_update_lock);
+
+ return status;
+}
+
+static int sys_get_trip_type(struct thermal_zone_device *tzd,
+ int trip, enum thermal_trip_type *type)
+{
+ struct intel_soc_dts_sensor_entry *dts;
+
+ dts = tzd->devdata;
+
+ *type = dts->trip_types[trip];
+
+ return 0;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd,
+ int *temp)
+{
+ int status;
+ u32 out;
+ struct intel_soc_dts_sensor_entry *dts;
+ struct intel_soc_dts_sensors *sensors;
+
+ dts = tzd->devdata;
+ sensors = dts->sensors;
+ status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_TEMP, &out);
+ if (status)
+ return status;
+
+ out = (out & dts->temp_mask) >> dts->temp_shift;
+ out -= SOC_DTS_TJMAX_ENCODING;
+ *temp = sensors->tj_max - out * 1000;
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops tzone_ops = {
+ .get_temp = sys_get_curr_temp,
+ .get_trip_temp = sys_get_trip_temp,
+ .get_trip_type = sys_get_trip_type,
+ .set_trip_temp = sys_set_trip_temp,
+};
+
+static 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)
+{
+ if (dts) {
+ iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
+ SOC_DTS_OFFSET_ENABLE, dts->store_status);
+ thermal_zone_device_unregister(dts->tzone);
+ }
+}
+
+static int add_dts_thermal_zone(int id, struct intel_soc_dts_sensor_entry *dts,
+ bool notification_support, int trip_cnt,
+ int read_only_trip_cnt)
+{
+ char name[10];
+ int trip_count = 0;
+ int trip_mask = 0;
+ u32 store_ptps;
+ int ret;
+ int i;
+
+ /* 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;
+ dts->temp_mask = 0x00FF << (id * 8);
+ dts->temp_shift = id * 8;
+ if (notification_support) {
+ trip_count = min(SOC_MAX_DTS_TRIPS, trip_cnt);
+ trip_mask = BIT(trip_count - read_only_trip_cnt) - 1;
+ }
+
+ /* Check if the writable trip we provide is not used by BIOS */
+ ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
+ SOC_DTS_OFFSET_PTPS, &store_ptps);
+ if (ret)
+ trip_mask = 0;
+ else {
+ for (i = 0; i < trip_count; ++i) {
+ if (trip_mask & BIT(i))
+ if (store_ptps & (0xff << (i * 8)))
+ trip_mask &= ~BIT(i);
+ }
+ }
+ dts->trip_mask = trip_mask;
+ dts->trip_count = trip_count;
+ snprintf(name, sizeof(name), "soc_dts%d", id);
+ dts->tzone = thermal_zone_device_register(name,
+ trip_count,
+ trip_mask,
+ dts, &tzone_ops,
+ NULL, 0, 0);
+ if (IS_ERR(dts->tzone)) {
+ ret = PTR_ERR(dts->tzone);
+ goto err_ret;
+ }
+
+ ret = soc_dts_enable(id);
+ if (ret)
+ goto err_enable;
+
+ return 0;
+err_enable:
+ thermal_zone_device_unregister(dts->tzone);
+err_ret:
+ return ret;
+}
+
+int intel_soc_dts_iosf_add_read_only_critical_trip(
+ struct intel_soc_dts_sensors *sensors, int critical_offset)
+{
+ int i, j;
+
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ for (j = 0; j < sensors->soc_dts[i].trip_count; ++j) {
+ if (!(sensors->soc_dts[i].trip_mask & BIT(j))) {
+ return update_trip_temp(&sensors->soc_dts[i], j,
+ sensors->tj_max - critical_offset,
+ THERMAL_TRIP_CRITICAL);
+ }
+ }
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_add_read_only_critical_trip);
+
+void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors)
+{
+ u32 sticky_out;
+ 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);
+
+struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
+ enum intel_soc_dts_interrupt_type intr_type, int trip_count,
+ int read_only_trip_count)
+{
+ struct intel_soc_dts_sensors *sensors;
+ bool notification;
+ u32 tj_max;
+ int ret;
+ int i;
+
+ if (!iosf_mbi_available())
+ return ERR_PTR(-ENODEV);
+
+ if (!trip_count || read_only_trip_count > trip_count)
+ return ERR_PTR(-EINVAL);
+
+ if (get_tj_max(&tj_max))
+ return ERR_PTR(-EINVAL);
+
+ sensors = kzalloc(sizeof(*sensors), GFP_KERNEL);
+ 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;
+ if (intr_type == INTEL_SOC_DTS_INTERRUPT_NONE)
+ notification = false;
+ else
+ notification = true;
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ sensors->soc_dts[i].sensors = sensors;
+ ret = add_dts_thermal_zone(i, &sensors->soc_dts[i],
+ notification, trip_count,
+ read_only_trip_count);
+ if (ret)
+ goto err_free;
+ }
+
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+ ret = update_trip_temp(&sensors->soc_dts[i], 0, 0,
+ THERMAL_TRIP_PASSIVE);
+ if (ret)
+ goto err_remove_zone;
+
+ ret = update_trip_temp(&sensors->soc_dts[i], 1, 0,
+ THERMAL_TRIP_PASSIVE);
+ if (ret)
+ goto err_remove_zone;
+ }
+
+ return sensors;
+err_remove_zone:
+ for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i)
+ remove_dts_thermal_zone(&sensors->soc_dts[i]);
+
+err_free:
+ 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) {
+ update_trip_temp(&sensors->soc_dts[i], 0, 0, 0);
+ update_trip_temp(&sensors->soc_dts[i], 1, 0, 0);
+ remove_dts_thermal_zone(&sensors->soc_dts[i]);
+ }
+ kfree(sensors);
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel_soc_dts_iosf.h b/drivers/thermal/intel_soc_dts_iosf.h
new file mode 100644
index 000000000..625e37bf9
--- /dev/null
+++ b/drivers/thermal/intel_soc_dts_iosf.h
@@ -0,0 +1,62 @@
+/*
+ * intel_soc_dts_iosf.h
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#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
+
+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 temp_mask;
+ u32 temp_shift;
+ u32 store_status;
+ u32 trip_mask;
+ u32 trip_count;
+ enum thermal_trip_type trip_types[2];
+ struct thermal_zone_device *tzone;
+ struct intel_soc_dts_sensors *sensors;
+};
+
+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, int trip_count,
+ int read_only_trip_count);
+void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors);
+void intel_soc_dts_iosf_interrupt_handler(
+ struct intel_soc_dts_sensors *sensors);
+int intel_soc_dts_iosf_add_read_only_critical_trip(
+ struct intel_soc_dts_sensors *sensors, int critical_offset);
+#endif
diff --git a/drivers/thermal/intel_soc_dts_thermal.c b/drivers/thermal/intel_soc_dts_thermal.c
new file mode 100644
index 000000000..d748527d7
--- /dev/null
+++ b/drivers/thermal/intel_soc_dts_thermal.c
@@ -0,0 +1,132 @@
+/*
+ * intel_soc_dts_thermal.c
+ * Copyright (c) 2014, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/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_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT, 0,
+ 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, 2, 1);
+ 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);
+ }
+ }
+
+ err = intel_soc_dts_iosf_add_read_only_critical_trip(soc_dts,
+ crit_offset);
+ if (err)
+ goto error_trips;
+
+ return 0;
+
+error_trips:
+ if (soc_dts_thres_irq) {
+ free_irq(soc_dts_thres_irq, soc_dts);
+ acpi_unregister_gsi(soc_dts_thres_gsi);
+ }
+ intel_soc_dts_iosf_exit(soc_dts);
+
+ return err;
+}
+
+static void __exit intel_soc_thermal_exit(void)
+{
+ 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/kirkwood_thermal.c b/drivers/thermal/kirkwood_thermal.c
new file mode 100644
index 000000000..892236621
--- /dev/null
+++ b/drivers/thermal/kirkwood_thermal.c
@@ -0,0 +1,125 @@
+/*
+ * Kirkwood thermal sensor driver
+ *
+ * Copyright (C) 2012 Nobuhiro Iwamatsu <iwamatsu@nigauri.org>
+ *
+ * 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/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->devdata;
+
+ reg = readl_relaxed(priv->sensor);
+
+ /* Valid check */
+ if (!((reg >> KIRKWOOD_THERMAL_VALID_OFFSET) &
+ KIRKWOOD_THERMAL_VALID_MASK)) {
+ dev_err(&thermal->device,
+ "Temperature sensor reading not valid\n");
+ 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 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;
+ struct resource *res;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->sensor = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->sensor))
+ return PTR_ERR(priv->sensor);
+
+ thermal = thermal_zone_device_register("kirkwood_thermal", 0, 0,
+ priv, &ops, NULL, 0, 0);
+ if (IS_ERR(thermal)) {
+ dev_err(&pdev->dev,
+ "Failed to register thermal zone device\n");
+ return PTR_ERR(thermal);
+ }
+
+ platform_set_drvdata(pdev, thermal);
+
+ return 0;
+}
+
+static int kirkwood_thermal_exit(struct platform_device *pdev)
+{
+ struct thermal_zone_device *kirkwood_thermal =
+ platform_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(kirkwood_thermal);
+
+ return 0;
+}
+
+MODULE_DEVICE_TABLE(of, kirkwood_thermal_id_table);
+
+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/max77620_thermal.c b/drivers/thermal/max77620_thermal.c
new file mode 100644
index 000000000..159bbcee8
--- /dev/null
+++ b/drivers/thermal/max77620_thermal.c
@@ -0,0 +1,171 @@
+/*
+ * 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>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#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(void *data, int *temp)
+{
+ struct max77620_therm_info *mtherm = data;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(mtherm->rmap, MAX77620_REG_STATLBT, &val);
+ if (ret < 0) {
+ dev_err(mtherm->dev, "Failed to read STATLBT: %d\n", ret);
+ 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_of_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_zone_of_sensor_register(&pdev->dev, 0,
+ mtherm, &max77620_thermal_ops);
+ if (IS_ERR(mtherm->tz_device)) {
+ ret = PTR_ERR(mtherm->tz_device);
+ dev_err(&pdev->dev, "Failed to register thermal zone: %d\n",
+ ret);
+ return ret;
+ }
+
+ 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) {
+ dev_err(&pdev->dev, "Failed to request irq1: %d\n", ret);
+ 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) {
+ dev_err(&pdev->dev, "Failed to request irq2: %d\n", ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, mtherm);
+
+ 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/mtk_thermal.c b/drivers/thermal/mtk_thermal.c
new file mode 100644
index 000000000..0691f260f
--- /dev/null
+++ b/drivers/thermal/mtk_thermal.c
@@ -0,0 +1,805 @@
+/*
+ * 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>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed 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/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/of_device.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>
+
+/* 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_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_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 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
+
+/*
+ * Layout of the fuses providing the calibration data
+ * These macros could be used for MT8173, MT2701, and MT2712.
+ * 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 MT8173_CALIB_BUF0_VALID BIT(0)
+#define MT8173_CALIB_BUF1_ADC_GE(x) (((x) >> 22) & 0x3ff)
+#define MT8173_CALIB_BUF0_VTS_TS1(x) (((x) >> 17) & 0x1ff)
+#define MT8173_CALIB_BUF0_VTS_TS2(x) (((x) >> 8) & 0x1ff)
+#define MT8173_CALIB_BUF1_VTS_TS3(x) (((x) >> 0) & 0x1ff)
+#define MT8173_CALIB_BUF2_VTS_TS4(x) (((x) >> 23) & 0x1ff)
+#define MT8173_CALIB_BUF2_VTS_TSABB(x) (((x) >> 14) & 0x1ff)
+#define MT8173_CALIB_BUF0_DEGC_CALI(x) (((x) >> 1) & 0x3f)
+#define MT8173_CALIB_BUF0_O_SLOPE(x) (((x) >> 26) & 0x3f)
+#define MT8173_CALIB_BUF0_O_SLOPE_SIGN(x) (((x) >> 7) & 0x1)
+#define MT8173_CALIB_BUF1_ID(x) (((x) >> 9) & 0x1)
+
+/* 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
+
+/* 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
+
+#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
+
+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 *sensor_mux_values;
+ const int *msr;
+ const int *adcpnp;
+ struct thermal_bank_cfg bank_data[];
+};
+
+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 degc_cali;
+ s32 o_slope;
+ s32 vts[MT8173_NUM_SENSORS];
+
+ const struct mtk_thermal_data *conf;
+ struct mtk_thermal_bank banks[];
+};
+
+/* 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 };
+
+/* 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 };
+
+/* 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 };
+
+/* 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, };
+
+/**
+ * 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,
+ .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,
+};
+
+/**
+ * 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,
+ .bank_data = {
+ {
+ .num_sensors = 3,
+ .sensors = mt2701_bank_data,
+ },
+ },
+ .msr = mt2701_msr,
+ .adcpnp = mt2701_adcpnp,
+ .sensor_mux_values = mt2701_mux_values,
+};
+
+/**
+ * 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,
+ .bank_data = {
+ {
+ .num_sensors = 4,
+ .sensors = mt2712_bank_data,
+ },
+ },
+ .msr = mt2712_msr,
+ .adcpnp = mt2712_adcpnp,
+ .sensor_mux_values = mt2712_mux_values,
+};
+
+/*
+ * 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,
+ .bank_data = {
+ {
+ .num_sensors = 1,
+ .sensors = mt7622_bank_data,
+ },
+ },
+ .msr = mt7622_msr,
+ .adcpnp = mt7622_adcpnp,
+ .sensor_mux_values = mt7622_mux_values,
+};
+
+/**
+ * raw_to_mcelsius - convert a raw ADC value to mcelsius
+ * @mt: The thermal controller
+ * @raw: raw ADC value
+ *
+ * This converts the raw ADC value to mcelsius using the SoC specific
+ * calibration constants
+ */
+static int raw_to_mcelsius(struct mtk_thermal *mt, int sensno, s32 raw)
+{
+ s32 tmp;
+
+ raw &= 0xfff;
+
+ tmp = 203450520 << 3;
+ tmp /= 165 + mt->o_slope;
+ tmp /= 10000 + mt->adc_ge;
+ tmp *= raw - mt->vts[sensno] - 3350;
+ tmp >>= 3;
+
+ 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;
+
+ 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;
+
+ 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 = raw_to_mcelsius(mt,
+ conf->bank_data[bank->id].sensors[i],
+ raw);
+
+ /*
+ * The first read of a sensor often contains very high bogus
+ * temperature value. Filter these out so that the system does
+ * not immediately shut down.
+ */
+ if (temp > 200000)
+ temp = 0;
+
+ if (temp > max)
+ max = temp;
+ }
+
+ return max;
+}
+
+static int mtk_read_temp(void *data, int *temperature)
+{
+ struct mtk_thermal *mt = data;
+ 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_of_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)
+{
+ struct mtk_thermal_bank *bank = &mt->banks[num];
+ const struct mtk_thermal_data *conf = mt->conf;
+ int i;
+
+ 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), mt->thermal_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),
+ mt->thermal_base + TEMP_MONCTL2);
+
+ /* poll is set to 10u */
+ writel(TEMP_AHBPOLL_ADC_POLL_INTERVAL(768),
+ mt->thermal_base + TEMP_AHBPOLL);
+
+ /* temperature sampling control, 1 sample */
+ writel(0x0, mt->thermal_base + TEMP_MSRCTL0);
+
+ /* exceed this polling time, IRQ would be inserted */
+ writel(0xffffffff, mt->thermal_base + TEMP_AHBTO);
+
+ /* number of interrupts per event, 1 is enough */
+ writel(0x0, mt->thermal_base + TEMP_MONIDET0);
+ writel(0x0, mt->thermal_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), mt->thermal_base + TEMP_ADCMUX);
+
+ /* AHB address for auxadc mux selection */
+ writel(auxadc_phys_base + AUXADC_CON1_CLR_V,
+ mt->thermal_base + TEMP_ADCMUXADDR);
+
+ /* AHB address for pnp sensor mux selection */
+ writel(apmixed_phys_base + APMIXED_SYS_TS_CON1,
+ mt->thermal_base + TEMP_PNPMUXADDR);
+
+ /* AHB value for auxadc enable */
+ writel(BIT(conf->auxadc_channel), mt->thermal_base + TEMP_ADCEN);
+
+ /* AHB address for auxadc enable (channel 0 immediate mode selected) */
+ writel(auxadc_phys_base + AUXADC_CON1_SET_V,
+ mt->thermal_base + TEMP_ADCENADDR);
+
+ /* AHB address for auxadc valid bit */
+ writel(auxadc_phys_base + AUXADC_DATA(conf->auxadc_channel),
+ mt->thermal_base + TEMP_ADCVALIDADDR);
+
+ /* AHB address for auxadc voltage output */
+ writel(auxadc_phys_base + AUXADC_DATA(conf->auxadc_channel),
+ mt->thermal_base + TEMP_ADCVOLTADDR);
+
+ /* read valid & voltage are at the same register */
+ writel(0x0, mt->thermal_base + TEMP_RDCTRL);
+
+ /* indicate where the valid bit is */
+ writel(TEMP_ADCVALIDMASK_VALID_HIGH | TEMP_ADCVALIDMASK_VALID_POS(12),
+ mt->thermal_base + TEMP_ADCVALIDMASK);
+
+ /* no shift */
+ writel(0x0, mt->thermal_base + TEMP_ADCVOLTAGESHIFT);
+
+ /* enable auxadc mux write transaction */
+ writel(TEMP_ADCWRITECTRL_ADC_MUX_WRITE,
+ mt->thermal_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,
+ mt->thermal_base + TEMP_MONCTL0);
+
+ writel(TEMP_ADCWRITECTRL_ADC_PNP_WRITE |
+ TEMP_ADCWRITECTRL_ADC_MUX_WRITE,
+ mt->thermal_base + TEMP_ADCWRITECTRL);
+
+ mtk_thermal_put_bank(bank);
+}
+
+static u64 of_get_phys_base(struct device_node *np)
+{
+ u64 size64;
+ const __be32 *regaddr_p;
+
+ regaddr_p = of_get_address(np, 0, &size64, NULL);
+ if (!regaddr_p)
+ return OF_BAD_ADDR;
+
+ return of_translate_address(np, regaddr_p);
+}
+
+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;
+ 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;
+ }
+
+ if (buf[0] & MT8173_CALIB_BUF0_VALID) {
+ mt->adc_ge = MT8173_CALIB_BUF1_ADC_GE(buf[1]);
+ mt->vts[MT8173_TS1] = MT8173_CALIB_BUF0_VTS_TS1(buf[0]);
+ mt->vts[MT8173_TS2] = MT8173_CALIB_BUF0_VTS_TS2(buf[0]);
+ mt->vts[MT8173_TS3] = MT8173_CALIB_BUF1_VTS_TS3(buf[1]);
+ mt->vts[MT8173_TS4] = MT8173_CALIB_BUF2_VTS_TS4(buf[2]);
+ mt->vts[MT8173_TSABB] = MT8173_CALIB_BUF2_VTS_TSABB(buf[2]);
+ mt->degc_cali = MT8173_CALIB_BUF0_DEGC_CALI(buf[0]);
+ if (MT8173_CALIB_BUF1_ID(buf[1]) &
+ MT8173_CALIB_BUF0_O_SLOPE_SIGN(buf[0]))
+ mt->o_slope = -MT8173_CALIB_BUF0_O_SLOPE(buf[0]);
+ else
+ mt->o_slope = MT8173_CALIB_BUF0_O_SLOPE(buf[0]);
+ } else {
+ dev_info(dev, "Device not calibrated, using default calibration values\n");
+ }
+
+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,
+ }, {
+ },
+};
+MODULE_DEVICE_TABLE(of, mtk_thermal_of_match);
+
+static int mtk_thermal_probe(struct platform_device *pdev)
+{
+ int ret, i;
+ struct device_node *auxadc, *apmixedsys, *np = pdev->dev.of_node;
+ struct mtk_thermal *mt;
+ struct resource *res;
+ u64 auxadc_phys_base, apmixed_phys_base;
+ struct thermal_zone_device *tzdev;
+
+ mt = devm_kzalloc(&pdev->dev, sizeof(*mt), GFP_KERNEL);
+ if (!mt)
+ return -ENOMEM;
+
+ mt->conf = of_device_get_match_data(&pdev->dev);
+
+ mt->clk_peri_therm = devm_clk_get(&pdev->dev, "therm");
+ if (IS_ERR(mt->clk_peri_therm))
+ return PTR_ERR(mt->clk_peri_therm);
+
+ mt->clk_auxadc = devm_clk_get(&pdev->dev, "auxadc");
+ if (IS_ERR(mt->clk_auxadc))
+ return PTR_ERR(mt->clk_auxadc);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mt->thermal_base = devm_ioremap_resource(&pdev->dev, res);
+ 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_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_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(&pdev->dev);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(mt->clk_auxadc);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't enable auxadc clk: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mt->clk_peri_therm);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't enable peri clk: %d\n", ret);
+ goto err_disable_clk_auxadc;
+ }
+
+ for (i = 0; i < mt->conf->num_banks; i++)
+ mtk_thermal_init_bank(mt, i, apmixed_phys_base,
+ auxadc_phys_base);
+
+ platform_set_drvdata(pdev, mt);
+
+ tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, mt,
+ &mtk_thermal_ops);
+ if (IS_ERR(tzdev)) {
+ ret = PTR_ERR(tzdev);
+ goto err_disable_clk_peri_therm;
+ }
+
+ return 0;
+
+err_disable_clk_peri_therm:
+ clk_disable_unprepare(mt->clk_peri_therm);
+err_disable_clk_auxadc:
+ clk_disable_unprepare(mt->clk_auxadc);
+
+ return ret;
+}
+
+static int mtk_thermal_remove(struct platform_device *pdev)
+{
+ struct mtk_thermal *mt = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(mt->clk_peri_therm);
+ clk_disable_unprepare(mt->clk_auxadc);
+
+ return 0;
+}
+
+static struct platform_driver mtk_thermal_driver = {
+ .probe = mtk_thermal_probe,
+ .remove = mtk_thermal_remove,
+ .driver = {
+ .name = "mtk-thermal",
+ .of_match_table = mtk_thermal_of_match,
+ },
+};
+
+module_platform_driver(mtk_thermal_driver);
+
+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/of-thermal.c b/drivers/thermal/of-thermal.c
new file mode 100644
index 000000000..4f2816559
--- /dev/null
+++ b/drivers/thermal/of-thermal.c
@@ -0,0 +1,1054 @@
+// 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>
+ */
+#include <linux/thermal.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/string.h>
+
+#include "thermal_core.h"
+
+/*** Private data structures to represent thermal device tree data ***/
+
+/**
+ * struct __thermal_bind_param - a match between trip and cooling device
+ * @cooling_device: a pointer to identify the referred cooling device
+ * @trip_id: the trip point index
+ * @usage: the percentage (from 0 to 100) of cooling contribution
+ * @min: minimum cooling state used at this trip point
+ * @max: maximum cooling state used at this trip point
+ */
+
+struct __thermal_bind_params {
+ struct device_node *cooling_device;
+ unsigned int trip_id;
+ unsigned int usage;
+ unsigned long min;
+ unsigned long max;
+};
+
+/**
+ * struct __thermal_zone - internal representation of a thermal zone
+ * @mode: current thermal zone device mode (enabled/disabled)
+ * @passive_delay: polling interval while passive cooling is activated
+ * @polling_delay: zone polling interval
+ * @slope: slope of the temperature adjustment curve
+ * @offset: offset of the temperature adjustment curve
+ * @ntrips: number of trip points
+ * @trips: an array of trip points (0..ntrips - 1)
+ * @num_tbps: number of thermal bind params
+ * @tbps: an array of thermal bind params (0..num_tbps - 1)
+ * @sensor_data: sensor private data used while reading temperature and trend
+ * @ops: set of callbacks to handle the thermal zone based on DT
+ */
+
+struct __thermal_zone {
+ enum thermal_device_mode mode;
+ int passive_delay;
+ int polling_delay;
+ int slope;
+ int offset;
+
+ /* trip data */
+ int ntrips;
+ struct thermal_trip *trips;
+
+ /* cooling binding data */
+ int num_tbps;
+ struct __thermal_bind_params *tbps;
+
+ /* sensor interface */
+ void *sensor_data;
+ const struct thermal_zone_of_device_ops *ops;
+};
+
+/*** DT thermal zone device callbacks ***/
+
+static int of_thermal_get_temp(struct thermal_zone_device *tz,
+ int *temp)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (!data->ops->get_temp)
+ return -EINVAL;
+
+ return data->ops->get_temp(data->sensor_data, temp);
+}
+
+static int of_thermal_set_trips(struct thermal_zone_device *tz,
+ int low, int high)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (!data->ops || !data->ops->set_trips)
+ return -EINVAL;
+
+ return data->ops->set_trips(data->sensor_data, low, high);
+}
+
+/**
+ * of_thermal_get_ntrips - function to export number of available trip
+ * points.
+ * @tz: pointer to a thermal zone
+ *
+ * This function is a globally visible wrapper to get number of trip points
+ * stored in the local struct __thermal_zone
+ *
+ * Return: number of available trip points, -ENODEV when data not available
+ */
+int of_thermal_get_ntrips(struct thermal_zone_device *tz)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (!data || IS_ERR(data))
+ return -ENODEV;
+
+ return data->ntrips;
+}
+EXPORT_SYMBOL_GPL(of_thermal_get_ntrips);
+
+/**
+ * of_thermal_is_trip_valid - function to check if trip point is valid
+ *
+ * @tz: pointer to a thermal zone
+ * @trip: trip point to evaluate
+ *
+ * This function is responsible for checking if passed trip point is valid
+ *
+ * Return: true if trip point is valid, false otherwise
+ */
+bool of_thermal_is_trip_valid(struct thermal_zone_device *tz, int trip)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (!data || trip >= data->ntrips || trip < 0)
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(of_thermal_is_trip_valid);
+
+/**
+ * of_thermal_get_trip_points - function to get access to a globally exported
+ * trip points
+ *
+ * @tz: pointer to a thermal zone
+ *
+ * This function provides a pointer to trip points table
+ *
+ * Return: pointer to trip points table, NULL otherwise
+ */
+const struct thermal_trip *
+of_thermal_get_trip_points(struct thermal_zone_device *tz)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (!data)
+ return NULL;
+
+ return data->trips;
+}
+EXPORT_SYMBOL_GPL(of_thermal_get_trip_points);
+
+/**
+ * of_thermal_set_emul_temp - function to set emulated temperature
+ *
+ * @tz: pointer to a thermal zone
+ * @temp: temperature to set
+ *
+ * This function gives the ability to set emulated value of temperature,
+ * which is handy for debugging
+ *
+ * Return: zero on success, error code otherwise
+ */
+static int of_thermal_set_emul_temp(struct thermal_zone_device *tz,
+ int temp)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ return data->ops->set_emul_temp(data->sensor_data, temp);
+}
+
+static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip,
+ enum thermal_trend *trend)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (!data->ops->get_trend)
+ return -EINVAL;
+
+ return data->ops->get_trend(data->sensor_data, trip, trend);
+}
+
+static int of_thermal_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct __thermal_zone *data = thermal->devdata;
+ int i;
+
+ if (!data || IS_ERR(data))
+ return -ENODEV;
+
+ /* find where to bind */
+ for (i = 0; i < data->num_tbps; i++) {
+ struct __thermal_bind_params *tbp = data->tbps + i;
+
+ if (tbp->cooling_device == cdev->np) {
+ int ret;
+
+ ret = thermal_zone_bind_cooling_device(thermal,
+ tbp->trip_id, cdev,
+ tbp->max,
+ tbp->min,
+ tbp->usage);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int of_thermal_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct __thermal_zone *data = thermal->devdata;
+ int i;
+
+ if (!data || IS_ERR(data))
+ return -ENODEV;
+
+ /* find where to unbind */
+ for (i = 0; i < data->num_tbps; i++) {
+ struct __thermal_bind_params *tbp = data->tbps + i;
+
+ if (tbp->cooling_device == cdev->np) {
+ int ret;
+
+ ret = thermal_zone_unbind_cooling_device(thermal,
+ tbp->trip_id, cdev);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int of_thermal_get_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode *mode)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ *mode = data->mode;
+
+ return 0;
+}
+
+static int of_thermal_set_mode(struct thermal_zone_device *tz,
+ enum thermal_device_mode mode)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ mutex_lock(&tz->lock);
+
+ if (mode == THERMAL_DEVICE_ENABLED) {
+ tz->polling_delay = data->polling_delay;
+ tz->passive_delay = data->passive_delay;
+ } else {
+ tz->polling_delay = 0;
+ tz->passive_delay = 0;
+ }
+
+ mutex_unlock(&tz->lock);
+
+ data->mode = mode;
+ thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return 0;
+}
+
+static int of_thermal_get_trip_type(struct thermal_zone_device *tz, int trip,
+ enum thermal_trip_type *type)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ *type = data->trips[trip].type;
+
+ return 0;
+}
+
+static int of_thermal_get_trip_temp(struct thermal_zone_device *tz, int trip,
+ int *temp)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ *temp = data->trips[trip].temperature;
+
+ return 0;
+}
+
+static int of_thermal_set_trip_temp(struct thermal_zone_device *tz, int trip,
+ int temp)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ if (data->ops->set_trip_temp) {
+ int ret;
+
+ ret = data->ops->set_trip_temp(data->sensor_data, trip, temp);
+ if (ret)
+ return ret;
+ }
+
+ /* thermal framework should take care of data->mask & (1 << trip) */
+ data->trips[trip].temperature = temp;
+
+ return 0;
+}
+
+static int of_thermal_get_trip_hyst(struct thermal_zone_device *tz, int trip,
+ int *hyst)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ *hyst = data->trips[trip].hysteresis;
+
+ return 0;
+}
+
+static int of_thermal_set_trip_hyst(struct thermal_zone_device *tz, int trip,
+ int hyst)
+{
+ struct __thermal_zone *data = tz->devdata;
+
+ if (trip >= data->ntrips || trip < 0)
+ return -EDOM;
+
+ /* thermal framework should take care of data->mask & (1 << trip) */
+ data->trips[trip].hysteresis = hyst;
+
+ return 0;
+}
+
+static int of_thermal_get_crit_temp(struct thermal_zone_device *tz,
+ int *temp)
+{
+ struct __thermal_zone *data = tz->devdata;
+ int i;
+
+ for (i = 0; i < data->ntrips; i++)
+ if (data->trips[i].type == THERMAL_TRIP_CRITICAL) {
+ *temp = data->trips[i].temperature;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static struct thermal_zone_device_ops of_thermal_ops = {
+ .get_mode = of_thermal_get_mode,
+ .set_mode = of_thermal_set_mode,
+
+ .get_trip_type = of_thermal_get_trip_type,
+ .get_trip_temp = of_thermal_get_trip_temp,
+ .set_trip_temp = of_thermal_set_trip_temp,
+ .get_trip_hyst = of_thermal_get_trip_hyst,
+ .set_trip_hyst = of_thermal_set_trip_hyst,
+ .get_crit_temp = of_thermal_get_crit_temp,
+
+ .bind = of_thermal_bind,
+ .unbind = of_thermal_unbind,
+};
+
+/*** sensor API ***/
+
+static struct thermal_zone_device *
+thermal_zone_of_add_sensor(struct device_node *zone,
+ struct device_node *sensor, void *data,
+ const struct thermal_zone_of_device_ops *ops)
+{
+ struct thermal_zone_device *tzd;
+ struct __thermal_zone *tz;
+
+ tzd = thermal_zone_get_zone_by_name(zone->name);
+ if (IS_ERR(tzd))
+ return ERR_PTR(-EPROBE_DEFER);
+
+ tz = tzd->devdata;
+
+ if (!ops)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&tzd->lock);
+ tz->ops = ops;
+ tz->sensor_data = data;
+
+ tzd->ops->get_temp = of_thermal_get_temp;
+ tzd->ops->get_trend = of_thermal_get_trend;
+
+ /*
+ * The thermal zone core will calculate the window if they have set the
+ * optional set_trips pointer.
+ */
+ if (ops->set_trips)
+ tzd->ops->set_trips = of_thermal_set_trips;
+
+ if (ops->set_emul_temp)
+ tzd->ops->set_emul_temp = of_thermal_set_emul_temp;
+
+ mutex_unlock(&tzd->lock);
+
+ return tzd;
+}
+
+/**
+ * thermal_zone_of_sensor_register - registers a sensor to a DT thermal zone
+ * @dev: a valid struct device pointer of a sensor device. Must contain
+ * a valid .of_node, for the sensor node.
+ * @sensor_id: a sensor identifier, in case the sensor IP has more
+ * than one sensors
+ * @data: a private pointer (owned by the caller) that will be passed
+ * back, when a temperature reading is needed.
+ * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp.
+ *
+ * This function will search the list of thermal zones described in device
+ * tree and look for the zone that refer to the sensor device pointed by
+ * @dev->of_node as temperature providers. For the zone pointing to the
+ * sensor node, the sensor will be added to the DT thermal zone device.
+ *
+ * The thermal zone temperature is provided by the @get_temp function
+ * pointer. When called, it will have the private pointer @data back.
+ *
+ * The thermal zone temperature trend is provided by the @get_trend function
+ * pointer. When called, it will have the private pointer @data back.
+ *
+ * TODO:
+ * 01 - This function must enqueue the new sensor instead of using
+ * it as the only source of temperature values.
+ *
+ * 02 - There must be a way to match the sensor with all thermal zones
+ * that refer to it.
+ *
+ * Return: On success returns a valid struct thermal_zone_device,
+ * otherwise, it returns a corresponding ERR_PTR(). Caller must
+ * check the return value with help of IS_ERR() helper.
+ */
+struct thermal_zone_device *
+thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void *data,
+ const struct thermal_zone_of_device_ops *ops)
+{
+ struct device_node *np, *child, *sensor_np;
+ struct thermal_zone_device *tzd = ERR_PTR(-ENODEV);
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np)
+ return ERR_PTR(-ENODEV);
+
+ if (!dev || !dev->of_node) {
+ of_node_put(np);
+ return ERR_PTR(-EINVAL);
+ }
+
+ sensor_np = of_node_get(dev->of_node);
+
+ for_each_available_child_of_node(np, child) {
+ struct of_phandle_args sensor_specs;
+ int ret, id;
+
+ /* For now, thermal framework supports only 1 sensor per zone */
+ ret = of_parse_phandle_with_args(child, "thermal-sensors",
+ "#thermal-sensor-cells",
+ 0, &sensor_specs);
+ if (ret)
+ continue;
+
+ if (sensor_specs.args_count >= 1) {
+ id = sensor_specs.args[0];
+ WARN(sensor_specs.args_count > 1,
+ "%s: too many cells in sensor specifier %d\n",
+ sensor_specs.np->name, sensor_specs.args_count);
+ } else {
+ id = 0;
+ }
+
+ if (sensor_specs.np == sensor_np && id == sensor_id) {
+ tzd = thermal_zone_of_add_sensor(child, sensor_np,
+ data, ops);
+ if (!IS_ERR(tzd))
+ tzd->ops->set_mode(tzd, THERMAL_DEVICE_ENABLED);
+
+ of_node_put(sensor_specs.np);
+ of_node_put(child);
+ goto exit;
+ }
+ of_node_put(sensor_specs.np);
+ }
+exit:
+ of_node_put(sensor_np);
+ of_node_put(np);
+
+ return tzd;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_register);
+
+/**
+ * thermal_zone_of_sensor_unregister - unregisters a sensor from a DT thermal zone
+ * @dev: a valid struct device pointer of a sensor device. Must contain
+ * a valid .of_node, for the sensor node.
+ * @tzd: a pointer to struct thermal_zone_device where the sensor is registered.
+ *
+ * This function removes the sensor callbacks and private data from the
+ * thermal zone device registered with thermal_zone_of_sensor_register()
+ * API. It will also silent the zone by remove the .get_temp() and .get_trend()
+ * thermal zone device callbacks.
+ *
+ * TODO: When the support to several sensors per zone is added, this
+ * function must search the sensor list based on @dev parameter.
+ *
+ */
+void thermal_zone_of_sensor_unregister(struct device *dev,
+ struct thermal_zone_device *tzd)
+{
+ struct __thermal_zone *tz;
+
+ if (!dev || !tzd || !tzd->devdata)
+ return;
+
+ tz = tzd->devdata;
+
+ /* no __thermal_zone, nothing to be done */
+ if (!tz)
+ return;
+
+ mutex_lock(&tzd->lock);
+ tzd->ops->get_temp = NULL;
+ tzd->ops->get_trend = NULL;
+ tzd->ops->set_emul_temp = NULL;
+
+ tz->ops = NULL;
+ tz->sensor_data = NULL;
+ mutex_unlock(&tzd->lock);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_unregister);
+
+static void devm_thermal_zone_of_sensor_release(struct device *dev, void *res)
+{
+ thermal_zone_of_sensor_unregister(dev,
+ *(struct thermal_zone_device **)res);
+}
+
+static int devm_thermal_zone_of_sensor_match(struct device *dev, void *res,
+ void *data)
+{
+ struct thermal_zone_device **r = res;
+
+ if (WARN_ON(!r || !*r))
+ return 0;
+
+ return *r == data;
+}
+
+/**
+ * devm_thermal_zone_of_sensor_register - Resource managed version of
+ * thermal_zone_of_sensor_register()
+ * @dev: a valid struct device pointer of a sensor device. Must contain
+ * a valid .of_node, for the sensor node.
+ * @sensor_id: a sensor identifier, in case the sensor IP has more
+ * than one sensors
+ * @data: a private pointer (owned by the caller) that will be passed
+ * back, when a temperature reading is needed.
+ * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp.
+ *
+ * Refer thermal_zone_of_sensor_register() for more details.
+ *
+ * Return: On success returns a valid struct thermal_zone_device,
+ * otherwise, it returns a corresponding ERR_PTR(). Caller must
+ * check the return value with help of IS_ERR() helper.
+ * Registered thermal_zone_device device will automatically be
+ * released when device is unbounded.
+ */
+struct thermal_zone_device *devm_thermal_zone_of_sensor_register(
+ struct device *dev, int sensor_id,
+ void *data, const struct thermal_zone_of_device_ops *ops)
+{
+ struct thermal_zone_device **ptr, *tzd;
+
+ ptr = devres_alloc(devm_thermal_zone_of_sensor_release, sizeof(*ptr),
+ GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ tzd = thermal_zone_of_sensor_register(dev, sensor_id, data, ops);
+ if (IS_ERR(tzd)) {
+ devres_free(ptr);
+ return tzd;
+ }
+
+ *ptr = tzd;
+ devres_add(dev, ptr);
+
+ return tzd;
+}
+EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_register);
+
+/**
+ * devm_thermal_zone_of_sensor_unregister - Resource managed version of
+ * thermal_zone_of_sensor_unregister().
+ * @dev: Device for which which resource was allocated.
+ * @tzd: a pointer to struct thermal_zone_device where the sensor is registered.
+ *
+ * This function removes the sensor callbacks and private data from the
+ * thermal zone device registered with devm_thermal_zone_of_sensor_register()
+ * API. It will also silent the zone by remove the .get_temp() and .get_trend()
+ * thermal zone device callbacks.
+ * Normally this function will not need to be called and the resource
+ * management code will ensure that the resource is freed.
+ */
+void devm_thermal_zone_of_sensor_unregister(struct device *dev,
+ struct thermal_zone_device *tzd)
+{
+ WARN_ON(devres_release(dev, devm_thermal_zone_of_sensor_release,
+ devm_thermal_zone_of_sensor_match, tzd));
+}
+EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_unregister);
+
+/*** functions parsing device tree nodes ***/
+
+/**
+ * thermal_of_populate_bind_params - parse and fill cooling map data
+ * @np: DT node containing a cooling-map node
+ * @__tbp: data structure to be filled with cooling map info
+ * @trips: array of thermal zone trip points
+ * @ntrips: number of trip points inside trips.
+ *
+ * This function parses a cooling-map type of node represented by
+ * @np parameter and fills the read data into @__tbp data structure.
+ * It needs the already parsed array of trip points of the thermal zone
+ * in consideration.
+ *
+ * Return: 0 on success, proper error code otherwise
+ */
+static int thermal_of_populate_bind_params(struct device_node *np,
+ struct __thermal_bind_params *__tbp,
+ struct thermal_trip *trips,
+ int ntrips)
+{
+ struct of_phandle_args cooling_spec;
+ struct device_node *trip;
+ int ret, i;
+ u32 prop;
+
+ /* Default weight. Usage is optional */
+ __tbp->usage = THERMAL_WEIGHT_DEFAULT;
+ ret = of_property_read_u32(np, "contribution", &prop);
+ if (ret == 0)
+ __tbp->usage = prop;
+
+ trip = of_parse_phandle(np, "trip", 0);
+ if (!trip) {
+ pr_err("missing trip property\n");
+ return -ENODEV;
+ }
+
+ /* match using device_node */
+ for (i = 0; i < ntrips; i++)
+ if (trip == trips[i].np) {
+ __tbp->trip_id = i;
+ break;
+ }
+
+ if (i == ntrips) {
+ ret = -ENODEV;
+ goto end;
+ }
+
+ ret = of_parse_phandle_with_args(np, "cooling-device", "#cooling-cells",
+ 0, &cooling_spec);
+ if (ret < 0) {
+ pr_err("missing cooling_device property\n");
+ goto end;
+ }
+ __tbp->cooling_device = cooling_spec.np;
+ if (cooling_spec.args_count >= 2) { /* at least min and max */
+ __tbp->min = cooling_spec.args[0];
+ __tbp->max = cooling_spec.args[1];
+ } else {
+ pr_err("wrong reference to cooling device, missing limits\n");
+ }
+
+end:
+ of_node_put(trip);
+
+ return ret;
+}
+
+/**
+ * It maps 'enum thermal_trip_type' found in include/linux/thermal.h
+ * into the device tree binding of 'trip', property type.
+ */
+static const char * const trip_types[] = {
+ [THERMAL_TRIP_ACTIVE] = "active",
+ [THERMAL_TRIP_PASSIVE] = "passive",
+ [THERMAL_TRIP_HOT] = "hot",
+ [THERMAL_TRIP_CRITICAL] = "critical",
+};
+
+/**
+ * thermal_of_get_trip_type - Get phy mode for given device_node
+ * @np: Pointer to the given device_node
+ * @type: Pointer to resulting trip type
+ *
+ * The function gets trip type string from property 'type',
+ * and store its index in trip_types table in @type,
+ *
+ * Return: 0 on success, or errno in error case.
+ */
+static int thermal_of_get_trip_type(struct device_node *np,
+ enum thermal_trip_type *type)
+{
+ const char *t;
+ int err, i;
+
+ err = of_property_read_string(np, "type", &t);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(trip_types); i++)
+ if (!strcasecmp(t, trip_types[i])) {
+ *type = i;
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+/**
+ * thermal_of_populate_trip - parse and fill one trip point data
+ * @np: DT node containing a trip point node
+ * @trip: trip point data structure to be filled up
+ *
+ * This function parses a trip point type of node represented by
+ * @np parameter and fills the read data into @trip data structure.
+ *
+ * Return: 0 on success, proper error code otherwise
+ */
+static int thermal_of_populate_trip(struct device_node *np,
+ struct thermal_trip *trip)
+{
+ int prop;
+ int ret;
+
+ ret = of_property_read_u32(np, "temperature", &prop);
+ if (ret < 0) {
+ pr_err("missing temperature property\n");
+ return ret;
+ }
+ trip->temperature = prop;
+
+ ret = of_property_read_u32(np, "hysteresis", &prop);
+ if (ret < 0) {
+ pr_err("missing hysteresis property\n");
+ return ret;
+ }
+ trip->hysteresis = prop;
+
+ ret = thermal_of_get_trip_type(np, &trip->type);
+ if (ret < 0) {
+ pr_err("wrong trip type property\n");
+ return ret;
+ }
+
+ /* Required for cooling map matching */
+ trip->np = np;
+ of_node_get(np);
+
+ return 0;
+}
+
+/**
+ * thermal_of_build_thermal_zone - parse and fill one thermal zone data
+ * @np: DT node containing a thermal zone node
+ *
+ * This function parses a thermal zone type of node represented by
+ * @np parameter and fills the read data into a __thermal_zone data structure
+ * and return this pointer.
+ *
+ * TODO: Missing properties to parse: thermal-sensor-names
+ *
+ * Return: On success returns a valid struct __thermal_zone,
+ * otherwise, it returns a corresponding ERR_PTR(). Caller must
+ * check the return value with help of IS_ERR() helper.
+ */
+static struct __thermal_zone
+__init *thermal_of_build_thermal_zone(struct device_node *np)
+{
+ struct device_node *child = NULL, *gchild;
+ struct __thermal_zone *tz;
+ int ret, i;
+ u32 prop, coef[2];
+
+ if (!np) {
+ pr_err("no thermal zone np\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ tz = kzalloc(sizeof(*tz), GFP_KERNEL);
+ if (!tz)
+ return ERR_PTR(-ENOMEM);
+
+ ret = of_property_read_u32(np, "polling-delay-passive", &prop);
+ if (ret < 0) {
+ pr_err("missing polling-delay-passive property\n");
+ goto free_tz;
+ }
+ tz->passive_delay = prop;
+
+ ret = of_property_read_u32(np, "polling-delay", &prop);
+ if (ret < 0) {
+ pr_err("missing polling-delay property\n");
+ goto free_tz;
+ }
+ tz->polling_delay = prop;
+
+ /*
+ * REVIST: for now, the thermal framework supports only
+ * one sensor per thermal zone. Thus, we are considering
+ * only the first two values as slope and offset.
+ */
+ ret = of_property_read_u32_array(np, "coefficients", coef, 2);
+ if (ret == 0) {
+ tz->slope = coef[0];
+ tz->offset = coef[1];
+ } else {
+ tz->slope = 1;
+ tz->offset = 0;
+ }
+
+ /* trips */
+ child = of_get_child_by_name(np, "trips");
+
+ /* No trips provided */
+ if (!child)
+ goto finish;
+
+ tz->ntrips = of_get_child_count(child);
+ if (tz->ntrips == 0) /* must have at least one child */
+ goto finish;
+
+ tz->trips = kcalloc(tz->ntrips, sizeof(*tz->trips), GFP_KERNEL);
+ if (!tz->trips) {
+ ret = -ENOMEM;
+ goto free_tz;
+ }
+
+ i = 0;
+ for_each_child_of_node(child, gchild) {
+ ret = thermal_of_populate_trip(gchild, &tz->trips[i++]);
+ if (ret)
+ goto free_trips;
+ }
+
+ of_node_put(child);
+
+ /* cooling-maps */
+ child = of_get_child_by_name(np, "cooling-maps");
+
+ /* cooling-maps not provided */
+ if (!child)
+ goto finish;
+
+ tz->num_tbps = of_get_child_count(child);
+ if (tz->num_tbps == 0)
+ goto finish;
+
+ tz->tbps = kcalloc(tz->num_tbps, sizeof(*tz->tbps), GFP_KERNEL);
+ if (!tz->tbps) {
+ ret = -ENOMEM;
+ goto free_trips;
+ }
+
+ i = 0;
+ for_each_child_of_node(child, gchild) {
+ ret = thermal_of_populate_bind_params(gchild, &tz->tbps[i++],
+ tz->trips, tz->ntrips);
+ if (ret)
+ goto free_tbps;
+ }
+
+finish:
+ of_node_put(child);
+ tz->mode = THERMAL_DEVICE_DISABLED;
+
+ return tz;
+
+free_tbps:
+ for (i = i - 1; i >= 0; i--)
+ of_node_put(tz->tbps[i].cooling_device);
+ kfree(tz->tbps);
+free_trips:
+ for (i = 0; i < tz->ntrips; i++)
+ of_node_put(tz->trips[i].np);
+ kfree(tz->trips);
+ of_node_put(gchild);
+free_tz:
+ kfree(tz);
+ of_node_put(child);
+
+ return ERR_PTR(ret);
+}
+
+static inline void of_thermal_free_zone(struct __thermal_zone *tz)
+{
+ int i;
+
+ for (i = 0; i < tz->num_tbps; i++)
+ of_node_put(tz->tbps[i].cooling_device);
+ kfree(tz->tbps);
+ for (i = 0; i < tz->ntrips; i++)
+ of_node_put(tz->trips[i].np);
+ kfree(tz->trips);
+ kfree(tz);
+}
+
+/**
+ * of_parse_thermal_zones - parse device tree thermal data
+ *
+ * Initialization function that can be called by machine initialization
+ * code to parse thermal data and populate the thermal framework
+ * with hardware thermal zones info. This function only parses thermal zones.
+ * Cooling devices and sensor devices nodes are supposed to be parsed
+ * by their respective drivers.
+ *
+ * Return: 0 on success, proper error code otherwise
+ *
+ */
+int __init of_parse_thermal_zones(void)
+{
+ struct device_node *np, *child;
+ struct __thermal_zone *tz;
+ struct thermal_zone_device_ops *ops;
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np) {
+ pr_debug("unable to find thermal zones\n");
+ return 0; /* Run successfully on systems without thermal DT */
+ }
+
+ for_each_available_child_of_node(np, child) {
+ struct thermal_zone_device *zone;
+ struct thermal_zone_params *tzp;
+ int i, mask = 0;
+ u32 prop;
+
+ tz = thermal_of_build_thermal_zone(child);
+ if (IS_ERR(tz)) {
+ pr_err("failed to build thermal zone %s: %ld\n",
+ child->name,
+ PTR_ERR(tz));
+ continue;
+ }
+
+ ops = kmemdup(&of_thermal_ops, sizeof(*ops), GFP_KERNEL);
+ if (!ops)
+ goto exit_free;
+
+ tzp = kzalloc(sizeof(*tzp), GFP_KERNEL);
+ if (!tzp) {
+ kfree(ops);
+ goto exit_free;
+ }
+
+ /* No hwmon because there might be hwmon drivers registering */
+ tzp->no_hwmon = true;
+
+ if (!of_property_read_u32(child, "sustainable-power", &prop))
+ tzp->sustainable_power = prop;
+
+ for (i = 0; i < tz->ntrips; i++)
+ mask |= 1 << i;
+
+ /* these two are left for temperature drivers to use */
+ tzp->slope = tz->slope;
+ tzp->offset = tz->offset;
+
+ zone = thermal_zone_device_register(child->name, tz->ntrips,
+ mask, tz,
+ ops, tzp,
+ tz->passive_delay,
+ tz->polling_delay);
+ if (IS_ERR(zone)) {
+ pr_err("Failed to build %s zone %ld\n", child->name,
+ PTR_ERR(zone));
+ kfree(tzp);
+ kfree(ops);
+ of_thermal_free_zone(tz);
+ /* attempting to build remaining zones still */
+ }
+ }
+ of_node_put(np);
+
+ return 0;
+
+exit_free:
+ of_node_put(child);
+ of_node_put(np);
+ of_thermal_free_zone(tz);
+
+ /* no memory available, so free what we have built */
+ of_thermal_destroy_zones();
+
+ return -ENOMEM;
+}
+
+/**
+ * of_thermal_destroy_zones - remove all zones parsed and allocated resources
+ *
+ * Finds all zones parsed and added to the thermal framework and remove them
+ * from the system, together with their resources.
+ *
+ */
+void of_thermal_destroy_zones(void)
+{
+ struct device_node *np, *child;
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np) {
+ pr_debug("unable to find thermal zones\n");
+ return;
+ }
+
+ for_each_available_child_of_node(np, child) {
+ struct thermal_zone_device *zone;
+
+ zone = thermal_zone_get_zone_by_name(child->name);
+ if (IS_ERR(zone))
+ continue;
+
+ thermal_zone_device_unregister(zone);
+ kfree(zone->tzp);
+ kfree(zone->ops);
+ of_thermal_free_zone(zone->devdata);
+ }
+ of_node_put(np);
+}
diff --git a/drivers/thermal/power_allocator.c b/drivers/thermal/power_allocator.c
new file mode 100644
index 000000000..3055f9a12
--- /dev/null
+++ b/drivers/thermal/power_allocator.c
@@ -0,0 +1,663 @@
+/*
+ * A power allocator to manage temperature
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "Power allocator: " fmt
+
+#include <linux/rculist.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/thermal_power_allocator.h>
+
+#include "thermal_core.h"
+
+#define INVALID_TRIP -1
+
+#define FRAC_BITS 10
+#define int_to_frac(x) ((x) << FRAC_BITS)
+#define frac_to_int(x) ((x) >> FRAC_BITS)
+
+/**
+ * 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_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
+ * @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.
+ * @trip_switch_on: first passive trip point of the thermal zone. The
+ * governor switches on when this trip point is crossed.
+ * If the thermal zone only has one passive trip point,
+ * @trip_switch_on should be INVALID_TRIP.
+ * @trip_max_desired_temperature: last passive trip point of the thermal
+ * zone. The temperature we are
+ * controlling for.
+ */
+struct power_allocator_params {
+ bool allocated_tzp;
+ s64 err_integral;
+ s32 prev_err;
+ int trip_switch_on;
+ int trip_max_desired_temperature;
+};
+
+/**
+ * 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)
+{
+ u32 sustainable_power = 0;
+ struct thermal_instance *instance;
+ struct power_allocator_params *params = tz->governor_data;
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ struct thermal_cooling_device *cdev = instance->cdev;
+ u32 min_power;
+
+ if (instance->trip != params->trip_max_desired_temperature)
+ continue;
+
+ if (power_actor_get_min_power(cdev, tz, &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 number for the switch on temperature
+ * @control_temp: target temperature for the power allocator governor
+ * @force: whether to force the update of the constants
+ *
+ * This function is used to update the estimation of the PID
+ * controller constants in struct thermal_zone_parameters.
+ * Sustainable power is provided in case it was estimated. The
+ * estimated sustainable_power should not be stored in the
+ * thermal_zone_parameters so it has to be passed explicitly to this
+ * function.
+ *
+ * If @force is not set, the values in the thermal zone's parameters
+ * are preserved if they are not zero. If @force is set, the values
+ * in thermal zone's parameters are overwritten.
+ */
+static void estimate_pid_constants(struct thermal_zone_device *tz,
+ u32 sustainable_power, int trip_switch_on,
+ int control_temp, bool force)
+{
+ int ret;
+ int switch_on_temp;
+ u32 temperature_threshold;
+
+ ret = tz->ops->get_trip_temp(tz, trip_switch_on, &switch_on_temp);
+ if (ret)
+ switch_on_temp = 0;
+
+ temperature_threshold = control_temp - switch_on_temp;
+ /*
+ * 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;
+
+ if (!tz->tzp->k_po || force)
+ tz->tzp->k_po = int_to_frac(sustainable_power) /
+ temperature_threshold;
+
+ if (!tz->tzp->k_pu || force)
+ tz->tzp->k_pu = int_to_frac(2 * sustainable_power) /
+ temperature_threshold;
+
+ if (!tz->tzp->k_i || force)
+ tz->tzp->k_i = int_to_frac(10) / 1000;
+ /*
+ * The default for k_d and integral_cutoff is 0, so we can
+ * leave them as they are.
+ */
+}
+
+/**
+ * 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)
+{
+ s64 p, i, d, power_range;
+ s32 err, max_power_frac;
+ u32 sustainable_power;
+ struct power_allocator_params *params = tz->governor_data;
+
+ max_power_frac = int_to_frac(max_allocatable_power);
+
+ if (tz->tzp->sustainable_power) {
+ sustainable_power = tz->tzp->sustainable_power;
+ } else {
+ sustainable_power = estimate_sustainable_power(tz);
+ estimate_pid_constants(tz, sustainable_power,
+ params->trip_switch_on, control_temp,
+ true);
+ }
+
+ 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, tz->passive_delay);
+ 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;
+}
+
+/**
+ * divvy_up_power() - divvy the allocated power between the actors
+ * @req_power: each actor's requested power
+ * @max_power: each actor's maximum available power
+ * @num_actors: size of the @req_power, @max_power and @granted_power's array
+ * @total_req_power: sum of @req_power
+ * @power_range: total allocated power
+ * @granted_power: output array: each actor's granted power
+ * @extra_actor_power: an appropriately sized array to be used in the
+ * function as temporary storage of the extra power given
+ * to the actors
+ *
+ * This function divides the total allocated power (@power_range)
+ * fairly between the actors. It first tries to give each actor a
+ * 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.
+ *
+ * Granted power for each actor is written to @granted_power, which
+ * should've been allocated by the calling function.
+ */
+static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors,
+ u32 total_req_power, u32 power_range,
+ u32 *granted_power, u32 *extra_actor_power)
+{
+ u32 extra_power, capped_extra_power;
+ int i;
+
+ /*
+ * Prevent division by 0 if none of the actors request power.
+ */
+ if (!total_req_power)
+ total_req_power = 1;
+
+ capped_extra_power = 0;
+ extra_power = 0;
+ for (i = 0; i < num_actors; i++) {
+ u64 req_range = (u64)req_power[i] * power_range;
+
+ granted_power[i] = DIV_ROUND_CLOSEST_ULL(req_range,
+ total_req_power);
+
+ if (granted_power[i] > max_power[i]) {
+ extra_power += granted_power[i] - max_power[i];
+ granted_power[i] = max_power[i];
+ }
+
+ extra_actor_power[i] = max_power[i] - granted_power[i];
+ capped_extra_power += extra_actor_power[i];
+ }
+
+ if (!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);
+ if (capped_extra_power > 0)
+ for (i = 0; i < num_actors; i++)
+ granted_power[i] += (extra_actor_power[i] *
+ extra_power) / capped_extra_power;
+}
+
+static int allocate_power(struct thermal_zone_device *tz,
+ int control_temp)
+{
+ struct thermal_instance *instance;
+ struct power_allocator_params *params = tz->governor_data;
+ u32 *req_power, *max_power, *granted_power, *extra_actor_power;
+ u32 *weighted_req_power;
+ u32 total_req_power, max_allocatable_power, total_weighted_req_power;
+ u32 total_granted_power, power_range;
+ int i, num_actors, total_weight, ret = 0;
+ int trip_max_desired_temperature = params->trip_max_desired_temperature;
+
+ mutex_lock(&tz->lock);
+
+ num_actors = 0;
+ total_weight = 0;
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if ((instance->trip == trip_max_desired_temperature) &&
+ cdev_is_power_actor(instance->cdev)) {
+ num_actors++;
+ total_weight += instance->weight;
+ }
+ }
+
+ if (!num_actors) {
+ ret = -ENODEV;
+ goto unlock;
+ }
+
+ /*
+ * We need to allocate five arrays of the same size:
+ * req_power, max_power, granted_power, extra_actor_power and
+ * weighted_req_power. They are going to be needed until this
+ * function returns. Allocate them all in one go to simplify
+ * the allocation and deallocation logic.
+ */
+ BUILD_BUG_ON(sizeof(*req_power) != sizeof(*max_power));
+ BUILD_BUG_ON(sizeof(*req_power) != sizeof(*granted_power));
+ BUILD_BUG_ON(sizeof(*req_power) != sizeof(*extra_actor_power));
+ BUILD_BUG_ON(sizeof(*req_power) != sizeof(*weighted_req_power));
+ req_power = kcalloc(num_actors * 5, sizeof(*req_power), GFP_KERNEL);
+ if (!req_power) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ max_power = &req_power[num_actors];
+ granted_power = &req_power[2 * num_actors];
+ extra_actor_power = &req_power[3 * num_actors];
+ weighted_req_power = &req_power[4 * num_actors];
+
+ i = 0;
+ total_weighted_req_power = 0;
+ total_req_power = 0;
+ max_allocatable_power = 0;
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ int weight;
+ struct thermal_cooling_device *cdev = instance->cdev;
+
+ if (instance->trip != trip_max_desired_temperature)
+ continue;
+
+ if (!cdev_is_power_actor(cdev))
+ continue;
+
+ if (cdev->ops->get_requested_power(cdev, tz, &req_power[i]))
+ continue;
+
+ if (!total_weight)
+ weight = 1 << FRAC_BITS;
+ else
+ weight = instance->weight;
+
+ weighted_req_power[i] = frac_to_int(weight * req_power[i]);
+
+ if (power_actor_get_max_power(cdev, tz, &max_power[i]))
+ continue;
+
+ total_req_power += req_power[i];
+ max_allocatable_power += max_power[i];
+ total_weighted_req_power += weighted_req_power[i];
+
+ i++;
+ }
+
+ power_range = pid_controller(tz, control_temp, max_allocatable_power);
+
+ divvy_up_power(weighted_req_power, max_power, num_actors,
+ total_weighted_req_power, power_range, granted_power,
+ extra_actor_power);
+
+ total_granted_power = 0;
+ i = 0;
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (instance->trip != trip_max_desired_temperature)
+ continue;
+
+ if (!cdev_is_power_actor(instance->cdev))
+ continue;
+
+ power_actor_set_power(instance->cdev, instance,
+ granted_power[i]);
+ total_granted_power += granted_power[i];
+
+ i++;
+ }
+
+ trace_thermal_power_allocator(tz, req_power, total_req_power,
+ granted_power, total_granted_power,
+ num_actors, power_range,
+ max_allocatable_power, tz->temperature,
+ control_temp - tz->temperature);
+
+ kfree(req_power);
+unlock:
+ mutex_unlock(&tz->lock);
+
+ return ret;
+}
+
+/**
+ * get_governor_trips() - get the number of 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)
+{
+ int i, last_active, last_passive;
+ bool found_first_passive;
+
+ found_first_passive = false;
+ last_active = INVALID_TRIP;
+ last_passive = INVALID_TRIP;
+
+ for (i = 0; i < tz->trips; i++) {
+ enum thermal_trip_type type;
+ int ret;
+
+ ret = tz->ops->get_trip_type(tz, i, &type);
+ if (ret) {
+ dev_warn(&tz->device,
+ "Failed to get trip point %d type: %d\n", i,
+ ret);
+ continue;
+ }
+
+ if (type == THERMAL_TRIP_PASSIVE) {
+ if (!found_first_passive) {
+ params->trip_switch_on = i;
+ found_first_passive = true;
+ } else {
+ last_passive = i;
+ }
+ } else if (type == THERMAL_TRIP_ACTIVE) {
+ last_active = i;
+ } else {
+ break;
+ }
+ }
+
+ if (last_passive != INVALID_TRIP) {
+ params->trip_max_desired_temperature = last_passive;
+ } else if (found_first_passive) {
+ params->trip_max_desired_temperature = params->trip_switch_on;
+ params->trip_switch_on = INVALID_TRIP;
+ } else {
+ params->trip_switch_on = INVALID_TRIP;
+ params->trip_max_desired_temperature = 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 thermal_instance *instance;
+ struct power_allocator_params *params = tz->governor_data;
+
+ mutex_lock(&tz->lock);
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if ((instance->trip != params->trip_max_desired_temperature) ||
+ (!cdev_is_power_actor(instance->cdev)))
+ continue;
+
+ instance->target = 0;
+ mutex_lock(&instance->cdev->lock);
+ instance->cdev->updated = false;
+ mutex_unlock(&instance->cdev->lock);
+ thermal_cdev_update(instance->cdev);
+ }
+ mutex_unlock(&tz->lock);
+}
+
+/**
+ * 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.
+ */
+static int power_allocator_bind(struct thermal_zone_device *tz)
+{
+ int ret;
+ struct power_allocator_params *params;
+ int control_temp;
+
+ params = kzalloc(sizeof(*params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ if (!tz->tzp) {
+ tz->tzp = kzalloc(sizeof(*tz->tzp), GFP_KERNEL);
+ 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");
+
+ get_governor_trips(tz, params);
+
+ if (tz->trips > 0) {
+ ret = tz->ops->get_trip_temp(tz,
+ params->trip_max_desired_temperature,
+ &control_temp);
+ if (!ret)
+ estimate_pid_constants(tz, tz->tzp->sustainable_power,
+ params->trip_switch_on,
+ control_temp, false);
+ }
+
+ reset_pid_controller(params);
+
+ tz->governor_data = params;
+
+ return 0;
+
+free_params:
+ 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(tz->governor_data);
+ tz->governor_data = NULL;
+}
+
+static int power_allocator_throttle(struct thermal_zone_device *tz, int trip)
+{
+ int ret;
+ int switch_on_temp, control_temp;
+ struct power_allocator_params *params = tz->governor_data;
+
+ /*
+ * We get called for every trip point but we only need to do
+ * our calculations once
+ */
+ if (trip != params->trip_max_desired_temperature)
+ return 0;
+
+ ret = tz->ops->get_trip_temp(tz, params->trip_switch_on,
+ &switch_on_temp);
+ if (!ret && (tz->temperature < switch_on_temp)) {
+ tz->passive = 0;
+ reset_pid_controller(params);
+ allow_maximum_power(tz);
+ return 0;
+ }
+
+ tz->passive = 1;
+
+ ret = tz->ops->get_trip_temp(tz, params->trip_max_desired_temperature,
+ &control_temp);
+ if (ret) {
+ dev_warn(&tz->device,
+ "Failed to get the maximum desired temperature: %d\n",
+ ret);
+ return ret;
+ }
+
+ return allocate_power(tz, control_temp);
+}
+
+static struct thermal_governor thermal_gov_power_allocator = {
+ .name = "power_allocator",
+ .bind_to_tz = power_allocator_bind,
+ .unbind_from_tz = power_allocator_unbind,
+ .throttle = power_allocator_throttle,
+};
+
+int thermal_gov_power_allocator_register(void)
+{
+ return thermal_register_governor(&thermal_gov_power_allocator);
+}
+
+void thermal_gov_power_allocator_unregister(void)
+{
+ thermal_unregister_governor(&thermal_gov_power_allocator);
+}
diff --git a/drivers/thermal/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom-spmi-temp-alarm.c
new file mode 100644
index 000000000..ad4f3a8d6
--- /dev/null
+++ b/drivers/thermal/qcom-spmi-temp-alarm.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2011-2015, 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 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.
+ */
+
+#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/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+#define QPNP_TM_REG_TYPE 0x04
+#define QPNP_TM_REG_SUBTYPE 0x05
+#define QPNP_TM_REG_STATUS 0x08
+#define QPNP_TM_REG_SHUTDOWN_CTRL1 0x40
+#define QPNP_TM_REG_ALARM_CTRL 0x46
+
+#define QPNP_TM_TYPE 0x09
+#define QPNP_TM_SUBTYPE_GEN1 0x08
+#define QPNP_TM_SUBTYPE_GEN2 0x09
+
+#define STATUS_GEN1_STAGE_MASK GENMASK(1, 0)
+#define STATUS_GEN2_STATE_MASK GENMASK(6, 4)
+#define STATUS_GEN2_STATE_SHIFT 4
+
+#define SHUTDOWN_CTRL1_OVERRIDE_MASK GENMASK(7, 6)
+#define SHUTDOWN_CTRL1_THRESHOLD_MASK GENMASK(1, 0)
+
+#define ALARM_CTRL_FORCE_ENABLE BIT(7)
+
+/*
+ * Trip point values based on threshold control
+ * 0 = {105 C, 125 C, 145 C}
+ * 1 = {110 C, 130 C, 150 C}
+ * 2 = {115 C, 135 C, 155 C}
+ * 3 = {120 C, 140 C, 160 C}
+*/
+#define TEMP_STAGE_STEP 20000 /* Stage step: 20.000 C */
+#define TEMP_STAGE_HYSTERESIS 2000
+
+#define TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */
+#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */
+
+#define THRESH_MIN 0
+
+/* Temperature in Milli Celsius reported during stage 0 if no ADC is present */
+#define DEFAULT_TEMP 37000
+
+struct qpnp_tm_chip {
+ struct regmap *map;
+ struct thermal_zone_device *tz_dev;
+ unsigned int subtype;
+ long temp;
+ unsigned int thresh;
+ unsigned int stage;
+ unsigned int prev_stage;
+ unsigned int base;
+ 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_get_temp_stage() - return over-temperature stage
+ * @chip: Pointer to the qpnp_tm chip
+ *
+ * Return: stage (GEN1) or state (GEN2) on success, or errno on failure.
+ */
+static int qpnp_tm_get_temp_stage(struct qpnp_tm_chip *chip)
+{
+ int ret;
+ u8 reg = 0;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, &reg);
+ if (ret < 0)
+ return ret;
+
+ if (chip->subtype == QPNP_TM_SUBTYPE_GEN1)
+ ret = reg & STATUS_GEN1_STAGE_MASK;
+ else
+ ret = (reg & STATUS_GEN2_STATE_MASK) >> STATUS_GEN2_STATE_SHIFT;
+
+ return ret;
+}
+
+/*
+ * 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, stage_new, stage_old;
+ int ret;
+
+ ret = qpnp_tm_get_temp_stage(chip);
+ if (ret < 0)
+ return ret;
+ stage = ret;
+
+ if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) {
+ stage_new = stage;
+ stage_old = chip->stage;
+ } else {
+ stage_new = alarm_state_map[stage];
+ stage_old = alarm_state_map[chip->stage];
+ }
+
+ if (stage_new > stage_old) {
+ /* increasing stage, use lower bound */
+ chip->temp = (stage_new - 1) * TEMP_STAGE_STEP +
+ chip->thresh * TEMP_THRESH_STEP +
+ TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+ } else if (stage_new < stage_old) {
+ /* decreasing stage, use upper bound */
+ chip->temp = stage_new * TEMP_STAGE_STEP +
+ chip->thresh * TEMP_THRESH_STEP -
+ TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+ }
+
+ chip->stage = stage;
+
+ return 0;
+}
+
+static int qpnp_tm_get_temp(void *data, int *temp)
+{
+ struct qpnp_tm_chip *chip = data;
+ int ret, mili_celsius;
+
+ if (!temp)
+ return -EINVAL;
+
+ if (!chip->adc) {
+ ret = qpnp_tm_update_temp_no_adc(chip);
+ 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 < 0 ? 0 : chip->temp;
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops qpnp_tm_sensor_ops = {
+ .get_temp = qpnp_tm_get_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;
+}
+
+/*
+ * This function initializes the internal temp value based on only the
+ * current thermal stage and threshold. Setup threshold control and
+ * disable shutdown override.
+ */
+static int qpnp_tm_init(struct qpnp_tm_chip *chip)
+{
+ unsigned int stage;
+ int ret;
+ u8 reg = 0;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, &reg);
+ if (ret < 0)
+ return ret;
+
+ chip->thresh = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+ chip->temp = DEFAULT_TEMP;
+
+ ret = qpnp_tm_get_temp_stage(chip);
+ if (ret < 0)
+ return ret;
+ chip->stage = ret;
+
+ stage = chip->subtype == QPNP_TM_SUBTYPE_GEN1
+ ? chip->stage : alarm_state_map[chip->stage];
+
+ if (stage)
+ chip->temp = chip->thresh * TEMP_THRESH_STEP +
+ (stage - 1) * TEMP_STAGE_STEP +
+ TEMP_THRESH_MIN;
+
+ /*
+ * Set threshold and disable software override of stage 2 and 3
+ * shutdowns.
+ */
+ chip->thresh = THRESH_MIN;
+ reg &= ~(SHUTDOWN_CTRL1_OVERRIDE_MASK | SHUTDOWN_CTRL1_THRESHOLD_MASK);
+ reg |= chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+ ret = qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg);
+ 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);
+
+ return ret;
+}
+
+static int qpnp_tm_probe(struct platform_device *pdev)
+{
+ struct qpnp_tm_chip *chip;
+ struct device_node *node;
+ u8 type, subtype;
+ u32 res;
+ int ret, irq;
+
+ node = pdev->dev.of_node;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, chip);
+
+ 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) {
+ dev_err(&pdev->dev, "could not read type\n");
+ return ret;
+ }
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_SUBTYPE, &subtype);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not read subtype\n");
+ return ret;
+ }
+
+ if (type != QPNP_TM_TYPE || (subtype != QPNP_TM_SUBTYPE_GEN1
+ && subtype != QPNP_TM_SUBTYPE_GEN2)) {
+ dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n",
+ type, subtype);
+ return -ENODEV;
+ }
+
+ chip->subtype = subtype;
+
+ ret = qpnp_tm_init(chip);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "init failed\n");
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qpnp_tm_isr,
+ IRQF_ONESHOT, node->name, chip);
+ if (ret < 0)
+ return ret;
+
+ chip->tz_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, chip,
+ &qpnp_tm_sensor_ops);
+ if (IS_ERR(chip->tz_dev)) {
+ dev_err(&pdev->dev, "failed to register sensor\n");
+ return PTR_ERR(chip->tz_dev);
+ }
+
+ 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");
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
new file mode 100644
index 000000000..be32e5abc
--- /dev/null
+++ b/drivers/thermal/qcom/Kconfig
@@ -0,0 +1,11 @@
+config QCOM_TSENS
+ tristate "Qualcomm TSENS Temperature Alarm"
+ depends on THERMAL
+ depends on 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.
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
new file mode 100644
index 000000000..a821929ed
--- /dev/null
+++ b/drivers/thermal/qcom/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
+qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-v2.o
diff --git a/drivers/thermal/qcom/tsens-8916.c b/drivers/thermal/qcom/tsens-8916.c
new file mode 100644
index 000000000..fdf561b8b
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-8916.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 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.
+ *
+ */
+
+#include <linux/platform_device.h>
+#include "tsens.h"
+
+/* eeprom layout data for 8916 */
+#define BASE0_MASK 0x0000007f
+#define BASE1_MASK 0xfe000000
+#define BASE0_SHIFT 0
+#define BASE1_SHIFT 25
+
+#define S0_P1_MASK 0x00000f80
+#define S1_P1_MASK 0x003e0000
+#define S2_P1_MASK 0xf8000000
+#define S3_P1_MASK 0x000003e0
+#define S4_P1_MASK 0x000f8000
+
+#define S0_P2_MASK 0x0001f000
+#define S1_P2_MASK 0x07c00000
+#define S2_P2_MASK 0x0000001f
+#define S3_P2_MASK 0x00007c00
+#define S4_P2_MASK 0x01f00000
+
+#define S0_P1_SHIFT 7
+#define S1_P1_SHIFT 17
+#define S2_P1_SHIFT 27
+#define S3_P1_SHIFT 5
+#define S4_P1_SHIFT 15
+
+#define S0_P2_SHIFT 12
+#define S1_P2_SHIFT 22
+#define S2_P2_SHIFT 0
+#define S3_P2_SHIFT 10
+#define S4_P2_SHIFT 20
+
+#define CAL_SEL_MASK 0xe0000000
+#define CAL_SEL_SHIFT 29
+
+static int calibrate_8916(struct tsens_device *tmdev)
+{
+ int base0 = 0, base1 = 0, i;
+ u32 p1[5], p2[5];
+ int mode = 0;
+ u32 *qfprom_cdata, *qfprom_csel;
+
+ qfprom_cdata = (u32 *)qfprom_read(tmdev->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
+
+ qfprom_csel = (u32 *)qfprom_read(tmdev->dev, "calib_sel");
+ if (IS_ERR(qfprom_csel))
+ return PTR_ERR(qfprom_csel);
+
+ mode = (qfprom_csel[0] & CAL_SEL_MASK) >> CAL_SEL_SHIFT;
+ dev_dbg(tmdev->dev, "calibration mode is %d\n", mode);
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base1 = (qfprom_cdata[1] & BASE1_MASK) >> BASE1_SHIFT;
+ p2[0] = (qfprom_cdata[0] & S0_P2_MASK) >> S0_P2_SHIFT;
+ p2[1] = (qfprom_cdata[0] & S1_P2_MASK) >> S1_P2_SHIFT;
+ p2[2] = (qfprom_cdata[1] & S2_P2_MASK) >> S2_P2_SHIFT;
+ p2[3] = (qfprom_cdata[1] & S3_P2_MASK) >> S3_P2_SHIFT;
+ p2[4] = (qfprom_cdata[1] & S4_P2_MASK) >> S4_P2_SHIFT;
+ for (i = 0; i < tmdev->num_sensors; i++)
+ p2[i] = ((base1 + p2[i]) << 3);
+ /* Fall through */
+ case ONE_PT_CALIB2:
+ base0 = (qfprom_cdata[0] & BASE0_MASK);
+ p1[0] = (qfprom_cdata[0] & S0_P1_MASK) >> S0_P1_SHIFT;
+ p1[1] = (qfprom_cdata[0] & S1_P1_MASK) >> S1_P1_SHIFT;
+ p1[2] = (qfprom_cdata[0] & S2_P1_MASK) >> S2_P1_SHIFT;
+ p1[3] = (qfprom_cdata[1] & S3_P1_MASK) >> S3_P1_SHIFT;
+ p1[4] = (qfprom_cdata[1] & S4_P1_MASK) >> S4_P1_SHIFT;
+ for (i = 0; i < tmdev->num_sensors; i++)
+ p1[i] = (((base0) + p1[i]) << 3);
+ break;
+ default:
+ for (i = 0; i < tmdev->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ break;
+ }
+
+ compute_intercept_slope(tmdev, p1, p2, mode);
+
+ return 0;
+}
+
+static const struct tsens_ops ops_8916 = {
+ .init = init_common,
+ .calibrate = calibrate_8916,
+ .get_temp = get_temp_common,
+};
+
+const struct tsens_data data_8916 = {
+ .num_sensors = 5,
+ .ops = &ops_8916,
+ .hw_ids = (unsigned int []){0, 1, 2, 4, 5 },
+};
diff --git a/drivers/thermal/qcom/tsens-8960.c b/drivers/thermal/qcom/tsens-8960.c
new file mode 100644
index 000000000..0451277d3
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-8960.c
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 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.
+ *
+ */
+
+#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 CAL_MDEGC 30000
+
+#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 STATUS_CNTL_ADDR_8064 0x3660
+#define CNTL_ADDR 0x3620
+/* CNTL_ADDR bitmasks */
+#define EN BIT(0)
+#define SW_RST BIT(1)
+#define SENSOR0_EN BIT(3)
+#define SLP_CLK_ENA BIT(26)
+#define SLP_CLK_ENA_8660 BIT(24)
+#define MEASURE_PERIOD 1
+#define SENSOR0_SHIFT 3
+
+/* INT_STATUS_ADDR bitmasks */
+#define MIN_STATUS_MASK BIT(0)
+#define LOWER_STATUS_CLR BIT(1)
+#define UPPER_STATUS_CLR BIT(2)
+#define MAX_STATUS_MASK BIT(3)
+
+#define THRESHOLD_ADDR 0x3624
+/* THRESHOLD_ADDR bitmasks */
+#define THRESHOLD_MAX_LIMIT_SHIFT 24
+#define THRESHOLD_MIN_LIMIT_SHIFT 16
+#define THRESHOLD_UPPER_LIMIT_SHIFT 8
+#define THRESHOLD_LOWER_LIMIT_SHIFT 0
+
+/* Initial temperature threshold values */
+#define LOWER_LIMIT_TH 0x50
+#define UPPER_LIMIT_TH 0xdf
+#define MIN_LIMIT_TH 0x0
+#define MAX_LIMIT_TH 0xff
+
+#define S0_STATUS_ADDR 0x3628
+#define INT_STATUS_ADDR 0x363c
+#define TRDY_MASK BIT(7)
+#define TIMEOUT_US 100
+
+static int suspend_8960(struct tsens_device *tmdev)
+{
+ int ret;
+ unsigned int mask;
+ struct regmap *map = tmdev->map;
+
+ ret = regmap_read(map, THRESHOLD_ADDR, &tmdev->ctx.threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, CNTL_ADDR, &tmdev->ctx.control);
+ if (ret)
+ return ret;
+
+ if (tmdev->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_device *tmdev)
+{
+ int ret;
+ struct regmap *map = tmdev->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 (tmdev->num_sensors > 1) {
+ ret = regmap_update_bits(map, CONFIG_ADDR, CONFIG_MASK, CONFIG);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(map, THRESHOLD_ADDR, tmdev->ctx.threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(map, CNTL_ADDR, tmdev->ctx.control);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int enable_8960(struct tsens_device *tmdev, int id)
+{
+ int ret;
+ u32 reg, mask;
+
+ ret = regmap_read(tmdev->map, CNTL_ADDR, &reg);
+ if (ret)
+ return ret;
+
+ mask = BIT(id + SENSOR0_SHIFT);
+ ret = regmap_write(tmdev->map, CNTL_ADDR, reg | SW_RST);
+ if (ret)
+ return ret;
+
+ if (tmdev->num_sensors > 1)
+ reg |= mask | SLP_CLK_ENA | EN;
+ else
+ reg |= mask | SLP_CLK_ENA_8660 | EN;
+
+ ret = regmap_write(tmdev->map, CNTL_ADDR, reg);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void disable_8960(struct tsens_device *tmdev)
+{
+ int ret;
+ u32 reg_cntl;
+ u32 mask;
+
+ mask = GENMASK(tmdev->num_sensors - 1, 0);
+ mask <<= SENSOR0_SHIFT;
+ mask |= EN;
+
+ ret = regmap_read(tmdev->map, CNTL_ADDR, &reg_cntl);
+ if (ret)
+ return;
+
+ reg_cntl &= ~mask;
+
+ if (tmdev->num_sensors > 1)
+ reg_cntl &= ~SLP_CLK_ENA;
+ else
+ reg_cntl &= ~SLP_CLK_ENA_8660;
+
+ regmap_write(tmdev->map, CNTL_ADDR, reg_cntl);
+}
+
+static int init_8960(struct tsens_device *tmdev)
+{
+ int ret, i;
+ u32 reg_cntl;
+
+ tmdev->map = dev_get_regmap(tmdev->dev, NULL);
+ if (!tmdev->map)
+ return -ENODEV;
+
+ /*
+ * The status registers for each sensor are discontiguous
+ * because some SoCs have 5 sensors while others have more
+ * but the control registers stay in the same place, i.e
+ * directly after the first 5 status registers.
+ */
+ for (i = 0; i < tmdev->num_sensors; i++) {
+ if (i >= 5)
+ tmdev->sensor[i].status = S0_STATUS_ADDR + 40;
+ tmdev->sensor[i].status += i * 4;
+ }
+
+ reg_cntl = SW_RST;
+ ret = regmap_update_bits(tmdev->map, CNTL_ADDR, SW_RST, reg_cntl);
+ if (ret)
+ return ret;
+
+ if (tmdev->num_sensors > 1) {
+ reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18);
+ reg_cntl &= ~SW_RST;
+ ret = regmap_update_bits(tmdev->map, CONFIG_ADDR,
+ CONFIG_MASK, CONFIG);
+ } else {
+ reg_cntl |= SLP_CLK_ENA_8660 | (MEASURE_PERIOD << 16);
+ reg_cntl &= ~CONFIG_MASK_8660;
+ reg_cntl |= CONFIG_8660 << CONFIG_SHIFT_8660;
+ }
+
+ reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << SENSOR0_SHIFT;
+ ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl);
+ if (ret)
+ return ret;
+
+ reg_cntl |= EN;
+ ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int calibrate_8960(struct tsens_device *tmdev)
+{
+ int i;
+ char *data;
+
+ ssize_t num_read = tmdev->num_sensors;
+ struct tsens_sensor *s = tmdev->sensor;
+
+ data = qfprom_read(tmdev->dev, "calib");
+ if (IS_ERR(data))
+ data = qfprom_read(tmdev->dev, "calib_backup");
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ for (i = 0; i < num_read; i++, s++)
+ s->offset = data[i];
+
+ return 0;
+}
+
+/* Temperature on y axis and ADC-code on x-axis */
+static inline int code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
+{
+ int slope, offset;
+
+ slope = thermal_zone_get_slope(s->tzd);
+ offset = CAL_MDEGC - slope * s->offset;
+
+ return adc_code * slope + offset;
+}
+
+static int get_temp_8960(struct tsens_device *tmdev, int id, int *temp)
+{
+ int ret;
+ u32 code, trdy;
+ const struct tsens_sensor *s = &tmdev->sensor[id];
+ unsigned long timeout;
+
+ timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
+ do {
+ ret = regmap_read(tmdev->map, INT_STATUS_ADDR, &trdy);
+ if (ret)
+ return ret;
+ if (!(trdy & TRDY_MASK))
+ continue;
+ ret = regmap_read(tmdev->map, s->status, &code);
+ if (ret)
+ return ret;
+ *temp = code_to_mdegC(code, s);
+ return 0;
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+static const struct tsens_ops ops_8960 = {
+ .init = init_8960,
+ .calibrate = calibrate_8960,
+ .get_temp = get_temp_8960,
+ .enable = enable_8960,
+ .disable = disable_8960,
+ .suspend = suspend_8960,
+ .resume = resume_8960,
+};
+
+const struct tsens_data data_8960 = {
+ .num_sensors = 11,
+ .ops = &ops_8960,
+};
diff --git a/drivers/thermal/qcom/tsens-8974.c b/drivers/thermal/qcom/tsens-8974.c
new file mode 100644
index 000000000..9baf77e8c
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-8974.c
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 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.
+ *
+ */
+
+#include <linux/platform_device.h>
+#include "tsens.h"
+
+/* eeprom layout data for 8974 */
+#define BASE1_MASK 0xff
+#define S0_P1_MASK 0x3f00
+#define S1_P1_MASK 0xfc000
+#define S2_P1_MASK 0x3f00000
+#define S3_P1_MASK 0xfc000000
+#define S4_P1_MASK 0x3f
+#define S5_P1_MASK 0xfc0
+#define S6_P1_MASK 0x3f000
+#define S7_P1_MASK 0xfc0000
+#define S8_P1_MASK 0x3f000000
+#define S8_P1_MASK_BKP 0x3f
+#define S9_P1_MASK 0x3f
+#define S9_P1_MASK_BKP 0xfc0
+#define S10_P1_MASK 0xfc0
+#define S10_P1_MASK_BKP 0x3f000
+#define CAL_SEL_0_1 0xc0000000
+#define CAL_SEL_2 0x40000000
+#define CAL_SEL_SHIFT 30
+#define CAL_SEL_SHIFT_2 28
+
+#define S0_P1_SHIFT 8
+#define S1_P1_SHIFT 14
+#define S2_P1_SHIFT 20
+#define S3_P1_SHIFT 26
+#define S5_P1_SHIFT 6
+#define S6_P1_SHIFT 12
+#define S7_P1_SHIFT 18
+#define S8_P1_SHIFT 24
+#define S9_P1_BKP_SHIFT 6
+#define S10_P1_SHIFT 6
+#define S10_P1_BKP_SHIFT 12
+
+#define BASE2_SHIFT 12
+#define BASE2_BKP_SHIFT 18
+#define S0_P2_SHIFT 20
+#define S0_P2_BKP_SHIFT 26
+#define S1_P2_SHIFT 26
+#define S2_P2_BKP_SHIFT 6
+#define S3_P2_SHIFT 6
+#define S3_P2_BKP_SHIFT 12
+#define S4_P2_SHIFT 12
+#define S4_P2_BKP_SHIFT 18
+#define S5_P2_SHIFT 18
+#define S5_P2_BKP_SHIFT 24
+#define S6_P2_SHIFT 24
+#define S7_P2_BKP_SHIFT 6
+#define S8_P2_SHIFT 6
+#define S8_P2_BKP_SHIFT 12
+#define S9_P2_SHIFT 12
+#define S9_P2_BKP_SHIFT 18
+#define S10_P2_SHIFT 18
+#define S10_P2_BKP_SHIFT 24
+
+#define BASE2_MASK 0xff000
+#define BASE2_BKP_MASK 0xfc0000
+#define S0_P2_MASK 0x3f00000
+#define S0_P2_BKP_MASK 0xfc000000
+#define S1_P2_MASK 0xfc000000
+#define S1_P2_BKP_MASK 0x3f
+#define S2_P2_MASK 0x3f
+#define S2_P2_BKP_MASK 0xfc0
+#define S3_P2_MASK 0xfc0
+#define S3_P2_BKP_MASK 0x3f000
+#define S4_P2_MASK 0x3f000
+#define S4_P2_BKP_MASK 0xfc0000
+#define S5_P2_MASK 0xfc0000
+#define S5_P2_BKP_MASK 0x3f000000
+#define S6_P2_MASK 0x3f000000
+#define S6_P2_BKP_MASK 0x3f
+#define S7_P2_MASK 0x3f
+#define S7_P2_BKP_MASK 0xfc0
+#define S8_P2_MASK 0xfc0
+#define S8_P2_BKP_MASK 0x3f000
+#define S9_P2_MASK 0x3f000
+#define S9_P2_BKP_MASK 0xfc0000
+#define S10_P2_MASK 0xfc0000
+#define S10_P2_BKP_MASK 0x3f000000
+
+#define BKP_SEL 0x3
+#define BKP_REDUN_SEL 0xe0000000
+#define BKP_REDUN_SHIFT 29
+
+#define BIT_APPEND 0x3
+
+static int calibrate_8974(struct tsens_device *tmdev)
+{
+ int base1 = 0, base2 = 0, i;
+ u32 p1[11], p2[11];
+ int mode = 0;
+ u32 *calib, *bkp;
+ u32 calib_redun_sel;
+
+ calib = (u32 *)qfprom_read(tmdev->dev, "calib");
+ if (IS_ERR(calib))
+ return PTR_ERR(calib);
+
+ bkp = (u32 *)qfprom_read(tmdev->dev, "calib_backup");
+ if (IS_ERR(bkp))
+ return PTR_ERR(bkp);
+
+ calib_redun_sel = bkp[1] & BKP_REDUN_SEL;
+ calib_redun_sel >>= BKP_REDUN_SHIFT;
+
+ if (calib_redun_sel == BKP_SEL) {
+ mode = (calib[4] & CAL_SEL_0_1) >> CAL_SEL_SHIFT;
+ mode |= (calib[5] & CAL_SEL_2) >> CAL_SEL_SHIFT_2;
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base2 = (bkp[2] & BASE2_BKP_MASK) >> BASE2_BKP_SHIFT;
+ p2[0] = (bkp[2] & S0_P2_BKP_MASK) >> S0_P2_BKP_SHIFT;
+ p2[1] = (bkp[3] & S1_P2_BKP_MASK);
+ p2[2] = (bkp[3] & S2_P2_BKP_MASK) >> S2_P2_BKP_SHIFT;
+ p2[3] = (bkp[3] & S3_P2_BKP_MASK) >> S3_P2_BKP_SHIFT;
+ p2[4] = (bkp[3] & S4_P2_BKP_MASK) >> S4_P2_BKP_SHIFT;
+ p2[5] = (calib[4] & S5_P2_BKP_MASK) >> S5_P2_BKP_SHIFT;
+ p2[6] = (calib[5] & S6_P2_BKP_MASK);
+ p2[7] = (calib[5] & S7_P2_BKP_MASK) >> S7_P2_BKP_SHIFT;
+ p2[8] = (calib[5] & S8_P2_BKP_MASK) >> S8_P2_BKP_SHIFT;
+ p2[9] = (calib[5] & S9_P2_BKP_MASK) >> S9_P2_BKP_SHIFT;
+ p2[10] = (calib[5] & S10_P2_BKP_MASK) >> S10_P2_BKP_SHIFT;
+ /* Fall through */
+ case ONE_PT_CALIB:
+ case ONE_PT_CALIB2:
+ base1 = bkp[0] & BASE1_MASK;
+ p1[0] = (bkp[0] & S0_P1_MASK) >> S0_P1_SHIFT;
+ p1[1] = (bkp[0] & S1_P1_MASK) >> S1_P1_SHIFT;
+ p1[2] = (bkp[0] & S2_P1_MASK) >> S2_P1_SHIFT;
+ p1[3] = (bkp[0] & S3_P1_MASK) >> S3_P1_SHIFT;
+ p1[4] = (bkp[1] & S4_P1_MASK);
+ p1[5] = (bkp[1] & S5_P1_MASK) >> S5_P1_SHIFT;
+ p1[6] = (bkp[1] & S6_P1_MASK) >> S6_P1_SHIFT;
+ p1[7] = (bkp[1] & S7_P1_MASK) >> S7_P1_SHIFT;
+ p1[8] = (bkp[2] & S8_P1_MASK_BKP) >> S8_P1_SHIFT;
+ p1[9] = (bkp[2] & S9_P1_MASK_BKP) >> S9_P1_BKP_SHIFT;
+ p1[10] = (bkp[2] & S10_P1_MASK_BKP) >> S10_P1_BKP_SHIFT;
+ break;
+ }
+ } else {
+ mode = (calib[1] & CAL_SEL_0_1) >> CAL_SEL_SHIFT;
+ mode |= (calib[3] & CAL_SEL_2) >> CAL_SEL_SHIFT_2;
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base2 = (calib[2] & BASE2_MASK) >> BASE2_SHIFT;
+ p2[0] = (calib[2] & S0_P2_MASK) >> S0_P2_SHIFT;
+ p2[1] = (calib[2] & S1_P2_MASK) >> S1_P2_SHIFT;
+ p2[2] = (calib[3] & S2_P2_MASK);
+ p2[3] = (calib[3] & S3_P2_MASK) >> S3_P2_SHIFT;
+ p2[4] = (calib[3] & S4_P2_MASK) >> S4_P2_SHIFT;
+ p2[5] = (calib[3] & S5_P2_MASK) >> S5_P2_SHIFT;
+ p2[6] = (calib[3] & S6_P2_MASK) >> S6_P2_SHIFT;
+ p2[7] = (calib[4] & S7_P2_MASK);
+ p2[8] = (calib[4] & S8_P2_MASK) >> S8_P2_SHIFT;
+ p2[9] = (calib[4] & S9_P2_MASK) >> S9_P2_SHIFT;
+ p2[10] = (calib[4] & S10_P2_MASK) >> S10_P2_SHIFT;
+ /* Fall through */
+ case ONE_PT_CALIB:
+ case ONE_PT_CALIB2:
+ base1 = calib[0] & BASE1_MASK;
+ p1[0] = (calib[0] & S0_P1_MASK) >> S0_P1_SHIFT;
+ p1[1] = (calib[0] & S1_P1_MASK) >> S1_P1_SHIFT;
+ p1[2] = (calib[0] & S2_P1_MASK) >> S2_P1_SHIFT;
+ p1[3] = (calib[0] & S3_P1_MASK) >> S3_P1_SHIFT;
+ p1[4] = (calib[1] & S4_P1_MASK);
+ p1[5] = (calib[1] & S5_P1_MASK) >> S5_P1_SHIFT;
+ p1[6] = (calib[1] & S6_P1_MASK) >> S6_P1_SHIFT;
+ p1[7] = (calib[1] & S7_P1_MASK) >> S7_P1_SHIFT;
+ p1[8] = (calib[1] & S8_P1_MASK) >> S8_P1_SHIFT;
+ p1[9] = (calib[2] & S9_P1_MASK);
+ p1[10] = (calib[2] & S10_P1_MASK) >> S10_P1_SHIFT;
+ break;
+ }
+ }
+
+ switch (mode) {
+ case ONE_PT_CALIB:
+ for (i = 0; i < tmdev->num_sensors; i++)
+ p1[i] += (base1 << 2) | BIT_APPEND;
+ break;
+ case TWO_PT_CALIB:
+ for (i = 0; i < tmdev->num_sensors; i++) {
+ p2[i] += base2;
+ p2[i] <<= 2;
+ p2[i] |= BIT_APPEND;
+ }
+ /* Fall through */
+ case ONE_PT_CALIB2:
+ for (i = 0; i < tmdev->num_sensors; i++) {
+ p1[i] += base1;
+ p1[i] <<= 2;
+ p1[i] |= BIT_APPEND;
+ }
+ break;
+ default:
+ for (i = 0; i < tmdev->num_sensors; i++)
+ p2[i] = 780;
+ p1[0] = 502;
+ p1[1] = 509;
+ p1[2] = 503;
+ p1[3] = 509;
+ p1[4] = 505;
+ p1[5] = 509;
+ p1[6] = 507;
+ p1[7] = 510;
+ p1[8] = 508;
+ p1[9] = 509;
+ p1[10] = 508;
+ break;
+ }
+
+ compute_intercept_slope(tmdev, p1, p2, mode);
+
+ return 0;
+}
+
+static const struct tsens_ops ops_8974 = {
+ .init = init_common,
+ .calibrate = calibrate_8974,
+ .get_temp = get_temp_common,
+};
+
+const struct tsens_data data_8974 = {
+ .num_sensors = 11,
+ .ops = &ops_8974,
+};
diff --git a/drivers/thermal/qcom/tsens-common.c b/drivers/thermal/qcom/tsens-common.c
new file mode 100644
index 000000000..6207d8d92
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-common.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 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.
+ *
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include "tsens.h"
+
+#define S0_ST_ADDR 0x1030
+#define SN_ADDR_OFFSET 0x4
+#define SN_ST_TEMP_MASK 0x3ff
+#define CAL_DEGC_PT1 30
+#define CAL_DEGC_PT2 120
+#define SLOPE_FACTOR 1000
+#define SLOPE_DEFAULT 3200
+
+char *qfprom_read(struct device *dev, const char *cname)
+{
+ struct nvmem_cell *cell;
+ ssize_t data;
+ char *ret;
+
+ cell = nvmem_cell_get(dev, cname);
+ if (IS_ERR(cell))
+ return ERR_CAST(cell);
+
+ ret = nvmem_cell_read(cell, &data);
+ nvmem_cell_put(cell);
+
+ return ret;
+}
+
+/*
+ * Use this function on devices where slope and offset calculations
+ * depend on calibration data read from qfprom. On others the slope
+ * and offset values are derived from tz->tzp->slope and tz->tzp->offset
+ * resp.
+ */
+void compute_intercept_slope(struct tsens_device *tmdev, u32 *p1,
+ u32 *p2, u32 mode)
+{
+ int i;
+ int num, den;
+
+ for (i = 0; i < tmdev->num_sensors; i++) {
+ dev_dbg(tmdev->dev,
+ "sensor%d - data_point1:%#x data_point2:%#x\n",
+ i, p1[i], p2[i]);
+
+ tmdev->sensor[i].slope = SLOPE_DEFAULT;
+ if (mode == TWO_PT_CALIB) {
+ /*
+ * slope (m) = adc_code2 - adc_code1 (y2 - y1)/
+ * temp_120_degc - temp_30_degc (x2 - x1)
+ */
+ num = p2[i] - p1[i];
+ num *= SLOPE_FACTOR;
+ den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
+ tmdev->sensor[i].slope = num / den;
+ }
+
+ tmdev->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
+ (CAL_DEGC_PT1 *
+ tmdev->sensor[i].slope);
+ dev_dbg(tmdev->dev, "offset:%d\n", tmdev->sensor[i].offset);
+ }
+}
+
+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;
+}
+
+int get_temp_common(struct tsens_device *tmdev, int id, int *temp)
+{
+ struct tsens_sensor *s = &tmdev->sensor[id];
+ u32 code;
+ unsigned int status_reg;
+ int last_temp = 0, ret;
+
+ status_reg = S0_ST_ADDR + s->hw_id * SN_ADDR_OFFSET;
+ ret = regmap_read(tmdev->map, status_reg, &code);
+ if (ret)
+ return ret;
+ last_temp = code & SN_ST_TEMP_MASK;
+
+ *temp = code_to_degc(last_temp, s) * 1000;
+
+ return 0;
+}
+
+static const struct regmap_config tsens_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+int __init init_common(struct tsens_device *tmdev)
+{
+ void __iomem *base;
+ struct resource *res;
+ struct platform_device *op = of_find_device_by_node(tmdev->dev->of_node);
+
+ if (!op)
+ return -EINVAL;
+
+ /* The driver only uses the TM register address space for now */
+ if (op->num_resources > 1) {
+ tmdev->tm_offset = 0;
+ } else {
+ /* old DTs where SROT and TM were in a contiguous 2K block */
+ tmdev->tm_offset = 0x1000;
+ }
+
+ res = platform_get_resource(op, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(&op->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ tmdev->map = devm_regmap_init_mmio(tmdev->dev, base, &tsens_config);
+ if (IS_ERR(tmdev->map))
+ return PTR_ERR(tmdev->map);
+
+ return 0;
+}
diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
new file mode 100644
index 000000000..44da02f59
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2018, Linaro Limited
+ */
+
+#include <linux/regmap.h>
+#include <linux/bitops.h>
+#include "tsens.h"
+
+#define STATUS_OFFSET 0xa0
+#define LAST_TEMP_MASK 0xfff
+#define STATUS_VALID_BIT BIT(21)
+
+static int get_temp_tsens_v2(struct tsens_device *tmdev, int id, int *temp)
+{
+ struct tsens_sensor *s = &tmdev->sensor[id];
+ u32 code;
+ unsigned int status_reg;
+ u32 last_temp = 0, last_temp2 = 0, last_temp3 = 0;
+ int ret;
+
+ status_reg = tmdev->tm_offset + STATUS_OFFSET + s->hw_id * 4;
+ ret = regmap_read(tmdev->map, status_reg, &code);
+ if (ret)
+ return ret;
+ last_temp = code & LAST_TEMP_MASK;
+ if (code & STATUS_VALID_BIT)
+ goto done;
+
+ /* Try a second time */
+ ret = regmap_read(tmdev->map, status_reg, &code);
+ if (ret)
+ return ret;
+ if (code & STATUS_VALID_BIT) {
+ last_temp = code & LAST_TEMP_MASK;
+ goto done;
+ } else {
+ last_temp2 = code & LAST_TEMP_MASK;
+ }
+
+ /* Try a third/last time */
+ ret = regmap_read(tmdev->map, status_reg, &code);
+ if (ret)
+ return ret;
+ if (code & STATUS_VALID_BIT) {
+ last_temp = code & LAST_TEMP_MASK;
+ goto done;
+ } else {
+ last_temp3 = code & LAST_TEMP_MASK;
+ }
+
+ if (last_temp == last_temp2)
+ last_temp = last_temp2;
+ else if (last_temp2 == last_temp3)
+ last_temp = last_temp3;
+done:
+ /* Convert temperature from deciCelsius to milliCelsius */
+ *temp = sign_extend32(last_temp, fls(LAST_TEMP_MASK) - 1) * 100;
+
+ return 0;
+}
+
+static const struct tsens_ops ops_generic_v2 = {
+ .init = init_common,
+ .get_temp = get_temp_tsens_v2,
+};
+
+const struct tsens_data data_tsens_v2 = {
+ .ops = &ops_generic_v2,
+};
+
+/* Kept around for backward compatibility with old msm8996.dtsi */
+const struct tsens_data data_8996 = {
+ .num_sensors = 13,
+ .ops = &ops_generic_v2,
+};
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
new file mode 100644
index 000000000..b13971328
--- /dev/null
+++ b/drivers/thermal/qcom/tsens.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 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.
+ *
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include "tsens.h"
+
+static int tsens_get_temp(void *data, int *temp)
+{
+ const struct tsens_sensor *s = data;
+ struct tsens_device *tmdev = s->tmdev;
+
+ return tmdev->ops->get_temp(tmdev, s->id, temp);
+}
+
+static int tsens_get_trend(void *p, int trip, enum thermal_trend *trend)
+{
+ const struct tsens_sensor *s = p;
+ struct tsens_device *tmdev = s->tmdev;
+
+ if (tmdev->ops->get_trend)
+ return tmdev->ops->get_trend(tmdev, s->id, trend);
+
+ return -ENOTSUPP;
+}
+
+static int __maybe_unused tsens_suspend(struct device *dev)
+{
+ struct tsens_device *tmdev = dev_get_drvdata(dev);
+
+ if (tmdev->ops && tmdev->ops->suspend)
+ return tmdev->ops->suspend(tmdev);
+
+ return 0;
+}
+
+static int __maybe_unused tsens_resume(struct device *dev)
+{
+ struct tsens_device *tmdev = dev_get_drvdata(dev);
+
+ if (tmdev->ops && tmdev->ops->resume)
+ return tmdev->ops->resume(tmdev);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
+
+static const struct of_device_id tsens_table[] = {
+ {
+ .compatible = "qcom,msm8916-tsens",
+ .data = &data_8916,
+ }, {
+ .compatible = "qcom,msm8974-tsens",
+ .data = &data_8974,
+ }, {
+ .compatible = "qcom,msm8996-tsens",
+ .data = &data_8996,
+ }, {
+ .compatible = "qcom,tsens-v2",
+ .data = &data_tsens_v2,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, tsens_table);
+
+static const struct thermal_zone_of_device_ops tsens_of_ops = {
+ .get_temp = tsens_get_temp,
+ .get_trend = tsens_get_trend,
+};
+
+static int tsens_register(struct tsens_device *tmdev)
+{
+ int i;
+ struct thermal_zone_device *tzd;
+ u32 *hw_id, n = tmdev->num_sensors;
+
+ hw_id = devm_kcalloc(tmdev->dev, n, sizeof(u32), GFP_KERNEL);
+ if (!hw_id)
+ return -ENOMEM;
+
+ for (i = 0; i < tmdev->num_sensors; i++) {
+ tmdev->sensor[i].tmdev = tmdev;
+ tmdev->sensor[i].id = i;
+ tzd = devm_thermal_zone_of_sensor_register(tmdev->dev, i,
+ &tmdev->sensor[i],
+ &tsens_of_ops);
+ if (IS_ERR(tzd))
+ continue;
+ tmdev->sensor[i].tzd = tzd;
+ if (tmdev->ops->enable)
+ tmdev->ops->enable(tmdev, i);
+ }
+ return 0;
+}
+
+static int tsens_probe(struct platform_device *pdev)
+{
+ int ret, i;
+ struct device *dev;
+ struct device_node *np;
+ struct tsens_device *tmdev;
+ const struct tsens_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, "invalid number of sensors\n");
+ return -EINVAL;
+ }
+
+ tmdev = devm_kzalloc(dev,
+ struct_size(tmdev, sensor, num_sensors),
+ GFP_KERNEL);
+ if (!tmdev)
+ return -ENOMEM;
+
+ tmdev->dev = dev;
+ tmdev->num_sensors = num_sensors;
+ tmdev->ops = data->ops;
+ for (i = 0; i < tmdev->num_sensors; i++) {
+ if (data->hw_ids)
+ tmdev->sensor[i].hw_id = data->hw_ids[i];
+ else
+ tmdev->sensor[i].hw_id = i;
+ }
+
+ if (!tmdev->ops || !tmdev->ops->init || !tmdev->ops->get_temp)
+ return -EINVAL;
+
+ ret = tmdev->ops->init(tmdev);
+ if (ret < 0) {
+ dev_err(dev, "tsens init failed\n");
+ return ret;
+ }
+
+ if (tmdev->ops->calibrate) {
+ ret = tmdev->ops->calibrate(tmdev);
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "tsens calibration failed\n");
+ return ret;
+ }
+ }
+
+ ret = tsens_register(tmdev);
+
+ platform_set_drvdata(pdev, tmdev);
+
+ return ret;
+}
+
+static int tsens_remove(struct platform_device *pdev)
+{
+ struct tsens_device *tmdev = platform_get_drvdata(pdev);
+
+ if (tmdev->ops->disable)
+ tmdev->ops->disable(tmdev);
+
+ return 0;
+}
+
+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..14331eb45
--- /dev/null
+++ b/drivers/thermal/qcom/tsens.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. 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 __QCOM_TSENS_H__
+#define __QCOM_TSENS_H__
+
+#define ONE_PT_CALIB 0x1
+#define ONE_PT_CALIB2 0x2
+#define TWO_PT_CALIB 0x3
+
+#include <linux/thermal.h>
+
+struct tsens_device;
+
+struct tsens_sensor {
+ struct tsens_device *tmdev;
+ struct thermal_zone_device *tzd;
+ int offset;
+ int id;
+ int hw_id;
+ int slope;
+ u32 status;
+};
+
+/**
+ * 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
+ * @get_trend: Function to get the thermal/temp trend
+ */
+struct tsens_ops {
+ /* mandatory callbacks */
+ int (*init)(struct tsens_device *);
+ int (*calibrate)(struct tsens_device *);
+ int (*get_temp)(struct tsens_device *, int, int *);
+ /* optional callbacks */
+ int (*enable)(struct tsens_device *, int);
+ void (*disable)(struct tsens_device *);
+ int (*suspend)(struct tsens_device *);
+ int (*resume)(struct tsens_device *);
+ int (*get_trend)(struct tsens_device *, int, enum thermal_trend *);
+};
+
+/**
+ * struct tsens_data - tsens instance specific data
+ * @num_sensors: Max 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
+ */
+struct tsens_data {
+ const u32 num_sensors;
+ const struct tsens_ops *ops;
+ unsigned int *hw_ids;
+};
+
+/* Registers to be saved/restored across a context loss */
+struct tsens_context {
+ int threshold;
+ int control;
+};
+
+struct tsens_device {
+ struct device *dev;
+ u32 num_sensors;
+ struct regmap *map;
+ u32 tm_offset;
+ struct tsens_context ctx;
+ const struct tsens_ops *ops;
+ struct tsens_sensor sensor[0];
+};
+
+char *qfprom_read(struct device *, const char *);
+void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32);
+int init_common(struct tsens_device *);
+int get_temp_common(struct tsens_device *, int, int *);
+
+/* TSENS v1 targets */
+extern const struct tsens_data data_8916, data_8974, data_8960;
+/* TSENS v2 targets */
+extern const struct tsens_data data_8996, data_tsens_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..450ed66ed
--- /dev/null
+++ b/drivers/thermal/qoriq_thermal.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2016 Freescale Semiconductor, Inc.
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/thermal.h>
+
+#include "thermal_core.h"
+
+#define SITES_MAX 16
+
+/*
+ * QorIQ TMU Registers
+ */
+struct qoriq_tmu_site_regs {
+ u32 tritsr; /* Immediate Temperature Site Register */
+ u32 tratsr; /* Average Temperature Site Register */
+ u8 res0[0x8];
+};
+
+struct qoriq_tmu_regs {
+ u32 tmr; /* Mode Register */
+#define TMR_DISABLE 0x0
+#define TMR_ME 0x80000000
+#define TMR_ALPF 0x0c000000
+ u32 tsr; /* Status Register */
+ u32 tmtmir; /* Temperature measurement interval Register */
+#define TMTMIR_DEFAULT 0x0000000f
+ u8 res0[0x14];
+ u32 tier; /* Interrupt Enable Register */
+#define TIER_DISABLE 0x0
+ u32 tidr; /* Interrupt Detect Register */
+ u32 tiscr; /* Interrupt Site Capture Register */
+ u32 ticscr; /* Interrupt Critical Site Capture Register */
+ u8 res1[0x10];
+ u32 tmhtcrh; /* High Temperature Capture Register */
+ u32 tmhtcrl; /* Low Temperature Capture Register */
+ u8 res2[0x8];
+ u32 tmhtitr; /* High Temperature Immediate Threshold */
+ u32 tmhtatr; /* High Temperature Average Threshold */
+ u32 tmhtactr; /* High Temperature Average Crit Threshold */
+ u8 res3[0x24];
+ u32 ttcfgr; /* Temperature Configuration Register */
+ u32 tscfgr; /* Sensor Configuration Register */
+ u8 res4[0x78];
+ struct qoriq_tmu_site_regs site[SITES_MAX];
+ u8 res5[0x9f8];
+ u32 ipbrr0; /* IP Block Revision Register 0 */
+ u32 ipbrr1; /* IP Block Revision Register 1 */
+ u8 res6[0x310];
+ u32 ttr0cr; /* Temperature Range 0 Control Register */
+ u32 ttr1cr; /* Temperature Range 1 Control Register */
+ u32 ttr2cr; /* Temperature Range 2 Control Register */
+ u32 ttr3cr; /* Temperature Range 3 Control Register */
+};
+
+/*
+ * Thermal zone data
+ */
+struct qoriq_tmu_data {
+ struct thermal_zone_device *tz;
+ struct qoriq_tmu_regs __iomem *regs;
+ int sensor_id;
+ bool little_endian;
+};
+
+static void tmu_write(struct qoriq_tmu_data *p, u32 val, void __iomem *addr)
+{
+ if (p->little_endian)
+ iowrite32(val, addr);
+ else
+ iowrite32be(val, addr);
+}
+
+static u32 tmu_read(struct qoriq_tmu_data *p, void __iomem *addr)
+{
+ if (p->little_endian)
+ return ioread32(addr);
+ else
+ return ioread32be(addr);
+}
+
+static int tmu_get_temp(void *p, int *temp)
+{
+ u32 val;
+ struct qoriq_tmu_data *data = p;
+
+ val = tmu_read(data, &data->regs->site[data->sensor_id].tritsr);
+ *temp = (val & 0xff) * 1000;
+
+ return 0;
+}
+
+static int qoriq_tmu_get_sensor_id(void)
+{
+ int ret, id;
+ struct of_phandle_args sensor_specs;
+ struct device_node *np, *sensor_np;
+
+ np = of_find_node_by_name(NULL, "thermal-zones");
+ if (!np)
+ return -ENODEV;
+
+ sensor_np = of_get_next_child(np, NULL);
+ ret = of_parse_phandle_with_args(sensor_np, "thermal-sensors",
+ "#thermal-sensor-cells",
+ 0, &sensor_specs);
+ if (ret) {
+ of_node_put(np);
+ of_node_put(sensor_np);
+ return ret;
+ }
+
+ if (sensor_specs.args_count >= 1) {
+ id = sensor_specs.args[0];
+ WARN(sensor_specs.args_count > 1,
+ "%s: too many cells in sensor specifier %d\n",
+ sensor_specs.np->name, sensor_specs.args_count);
+ } else {
+ id = 0;
+ }
+
+ of_node_put(np);
+ of_node_put(sensor_np);
+
+ return id;
+}
+
+static int qoriq_tmu_calibration(struct platform_device *pdev)
+{
+ int i, val, len;
+ u32 range[4];
+ const u32 *calibration;
+ struct device_node *np = pdev->dev.of_node;
+ struct qoriq_tmu_data *data = platform_get_drvdata(pdev);
+
+ if (of_property_read_u32_array(np, "fsl,tmu-range", range, 4)) {
+ dev_err(&pdev->dev, "missing calibration range.\n");
+ return -ENODEV;
+ }
+
+ /* Init temperature range registers */
+ tmu_write(data, range[0], &data->regs->ttr0cr);
+ tmu_write(data, range[1], &data->regs->ttr1cr);
+ tmu_write(data, range[2], &data->regs->ttr2cr);
+ tmu_write(data, range[3], &data->regs->ttr3cr);
+
+ calibration = of_get_property(np, "fsl,tmu-calibration", &len);
+ if (calibration == NULL || len % 8) {
+ dev_err(&pdev->dev, "invalid calibration data.\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < len; i += 8, calibration += 2) {
+ val = of_read_number(calibration, 1);
+ tmu_write(data, val, &data->regs->ttcfgr);
+ val = of_read_number(calibration + 1, 1);
+ tmu_write(data, val, &data->regs->tscfgr);
+ }
+
+ return 0;
+}
+
+static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
+{
+ /* Disable interrupt, using polling instead */
+ tmu_write(data, TIER_DISABLE, &data->regs->tier);
+
+ /* Set update_interval */
+ tmu_write(data, TMTMIR_DEFAULT, &data->regs->tmtmir);
+
+ /* Disable monitoring */
+ tmu_write(data, TMR_DISABLE, &data->regs->tmr);
+}
+
+static const struct thermal_zone_of_device_ops tmu_tz_ops = {
+ .get_temp = tmu_get_temp,
+};
+
+static int qoriq_tmu_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct qoriq_tmu_data *data;
+ struct device_node *np = pdev->dev.of_node;
+ u32 site;
+
+ if (!np) {
+ dev_err(&pdev->dev, "Device OF-Node is NULL");
+ return -ENODEV;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(struct qoriq_tmu_data),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, data);
+
+ data->little_endian = of_property_read_bool(np, "little-endian");
+
+ data->sensor_id = qoriq_tmu_get_sensor_id();
+ if (data->sensor_id < 0) {
+ dev_err(&pdev->dev, "Failed to get sensor id\n");
+ ret = -ENODEV;
+ goto err_iomap;
+ }
+
+ data->regs = of_iomap(np, 0);
+ if (!data->regs) {
+ dev_err(&pdev->dev, "Failed to get memory region\n");
+ ret = -ENODEV;
+ goto err_iomap;
+ }
+
+ qoriq_tmu_init_device(data); /* TMU initialization */
+
+ ret = qoriq_tmu_calibration(pdev); /* TMU calibration */
+ if (ret < 0)
+ goto err_tmu;
+
+ data->tz = devm_thermal_zone_of_sensor_register(&pdev->dev,
+ data->sensor_id,
+ data, &tmu_tz_ops);
+ if (IS_ERR(data->tz)) {
+ ret = PTR_ERR(data->tz);
+ dev_err(&pdev->dev,
+ "Failed to register thermal zone device %d\n", ret);
+ goto err_tmu;
+ }
+
+ /* Enable monitoring */
+ site = 0x1 << (15 - data->sensor_id);
+ tmu_write(data, site | TMR_ME | TMR_ALPF, &data->regs->tmr);
+
+ return 0;
+
+err_tmu:
+ iounmap(data->regs);
+
+err_iomap:
+ platform_set_drvdata(pdev, NULL);
+
+ return ret;
+}
+
+static int qoriq_tmu_remove(struct platform_device *pdev)
+{
+ struct qoriq_tmu_data *data = platform_get_drvdata(pdev);
+
+ /* Disable monitoring */
+ tmu_write(data, TMR_DISABLE, &data->regs->tmr);
+
+ iounmap(data->regs);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int qoriq_tmu_suspend(struct device *dev)
+{
+ u32 tmr;
+ struct qoriq_tmu_data *data = dev_get_drvdata(dev);
+
+ /* Disable monitoring */
+ tmr = tmu_read(data, &data->regs->tmr);
+ tmr &= ~TMR_ME;
+ tmu_write(data, tmr, &data->regs->tmr);
+
+ return 0;
+}
+
+static int qoriq_tmu_resume(struct device *dev)
+{
+ u32 tmr;
+ struct qoriq_tmu_data *data = dev_get_drvdata(dev);
+
+ /* Enable monitoring */
+ tmr = tmu_read(data, &data->regs->tmr);
+ tmr |= TMR_ME;
+ tmu_write(data, tmr, &data->regs->tmr);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops,
+ qoriq_tmu_suspend, qoriq_tmu_resume);
+
+static const struct of_device_id qoriq_tmu_match[] = {
+ { .compatible = "fsl,qoriq-tmu", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, qoriq_tmu_match);
+
+static struct platform_driver qoriq_tmu = {
+ .driver = {
+ .name = "qoriq_thermal",
+ .pm = &qoriq_tmu_pm_ops,
+ .of_match_table = qoriq_tmu_match,
+ },
+ .probe = qoriq_tmu_probe,
+ .remove = qoriq_tmu_remove,
+};
+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/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c
new file mode 100644
index 000000000..8f553453d
--- /dev/null
+++ b/drivers/thermal/rcar_gen3_thermal.c
@@ -0,0 +1,469 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * R-Car Gen3 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_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/sys_soc.h>
+#include <linux/thermal.h>
+
+#include "thermal_core.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_CTSR 0x20
+#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
+
+/* 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)
+
+/* CTSR bits */
+#define CTSR_PONM BIT(8)
+#define CTSR_AOUT BIT(7)
+#define CTSR_THBGR BIT(5)
+#define CTSR_VMEN BIT(4)
+#define CTSR_VMST BIT(1)
+#define CTSR_THSST BIT(0)
+
+/* THCTR bits */
+#define THCTR_PONM BIT(6)
+#define THCTR_THSST BIT(0)
+
+#define CTEMP_MASK 0xFFF
+
+#define MCELSIUS(temp) ((temp) * 1000)
+#define GEN3_FUSE_MASK 0xFFF
+
+#define TSC_MAX_NUM 3
+
+/* Structure for thermal temperature calculation */
+struct equation_coefs {
+ int a1;
+ int b1;
+ int a2;
+ int b2;
+};
+
+struct rcar_gen3_thermal_tsc {
+ void __iomem *base;
+ struct thermal_zone_device *zone;
+ struct equation_coefs coef;
+ int low;
+ int high;
+};
+
+struct rcar_gen3_thermal_priv {
+ struct rcar_gen3_thermal_tsc *tscs[TSC_MAX_NUM];
+ unsigned int num_tscs;
+ void (*thermal_init)(struct rcar_gen3_thermal_tsc *tsc);
+};
+
+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
+ *
+ * [reg] = [temp] * a + b => [temp] = ([reg] - b) / a
+ *
+ * The constants a and b are calculated using two triplets of int values PTAT
+ * and THCODE. PTAT and THCODE can either be read from hardware or use hard
+ * coded values from driver. The formula to calculate a and b are taken from
+ * BSP and sparsely documented and understood.
+ *
+ * Examining the linear formula and the formula used to calculate constants a
+ * and b while knowing that the span for PTAT and THCODE values are between
+ * 0x000 and 0xfff the largest integer possible is 0xfff * 0xfff == 0xffe001.
+ * Integer also needs to be signed so that leaves 7 bits for binary
+ * fixed point scaling.
+ */
+
+#define FIXPT_SHIFT 7
+#define FIXPT_INT(_x) ((_x) << FIXPT_SHIFT)
+#define INT_FIXPT(_x) ((_x) >> FIXPT_SHIFT)
+#define FIXPT_DIV(_a, _b) DIV_ROUND_CLOSEST(((_a) << FIXPT_SHIFT), (_b))
+#define FIXPT_TO_MCELSIUS(_x) ((_x) * 1000 >> FIXPT_SHIFT)
+
+#define RCAR3_THERMAL_GRAN 500 /* mili Celsius */
+
+/* no idea where these constants come from */
+#define TJ_1 116
+#define TJ_3 -41
+
+static void rcar_gen3_thermal_calc_coefs(struct equation_coefs *coef,
+ int *ptat, int *thcode)
+{
+ int tj_2;
+
+ /* TODO: Find documentation and document constant calculation formula */
+
+ /*
+ * Division is not scaled in BSP and if scaled it might overflow
+ * the dividend (4095 * 4095 << 14 > INT_MAX) so keep it unscaled
+ */
+ tj_2 = (FIXPT_INT((ptat[1] - ptat[2]) * 157)
+ / (ptat[0] - ptat[2])) - FIXPT_INT(41);
+
+ coef->a1 = FIXPT_DIV(FIXPT_INT(thcode[1] - thcode[2]),
+ tj_2 - FIXPT_INT(TJ_3));
+ coef->b1 = FIXPT_INT(thcode[2]) - coef->a1 * TJ_3;
+
+ coef->a2 = FIXPT_DIV(FIXPT_INT(thcode[1] - thcode[0]),
+ tj_2 - FIXPT_INT(TJ_1));
+ coef->b2 = FIXPT_INT(thcode[0]) - coef->a2 * TJ_1;
+}
+
+static int rcar_gen3_thermal_round(int temp)
+{
+ int result, round_offs;
+
+ round_offs = temp >= 0 ? RCAR3_THERMAL_GRAN / 2 :
+ -RCAR3_THERMAL_GRAN / 2;
+ result = (temp + round_offs) / RCAR3_THERMAL_GRAN;
+ return result * RCAR3_THERMAL_GRAN;
+}
+
+static int rcar_gen3_thermal_get_temp(void *devdata, int *temp)
+{
+ struct rcar_gen3_thermal_tsc *tsc = devdata;
+ int mcelsius, val1, val2;
+ u32 reg;
+
+ /* Read register and convert to mili Celsius */
+ reg = rcar_gen3_thermal_read(tsc, REG_GEN3_TEMP) & CTEMP_MASK;
+
+ val1 = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b1, tsc->coef.a1);
+ val2 = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b2, tsc->coef.a2);
+ mcelsius = FIXPT_TO_MCELSIUS((val1 + val2) / 2);
+
+ /* Make sure we are inside specifications */
+ if ((mcelsius < MCELSIUS(-40)) || (mcelsius > MCELSIUS(125)))
+ return -EIO;
+
+ /* Round value to device granularity setting */
+ *temp = rcar_gen3_thermal_round(mcelsius);
+
+ return 0;
+}
+
+static int rcar_gen3_thermal_mcelsius_to_temp(struct rcar_gen3_thermal_tsc *tsc,
+ int mcelsius)
+{
+ int celsius, val1, val2;
+
+ celsius = DIV_ROUND_CLOSEST(mcelsius, 1000);
+ val1 = celsius * tsc->coef.a1 + tsc->coef.b1;
+ val2 = celsius * tsc->coef.a2 + tsc->coef.b2;
+
+ return INT_FIXPT((val1 + val2) / 2);
+}
+
+static int rcar_gen3_thermal_set_trips(void *devdata, int low, int high)
+{
+ struct rcar_gen3_thermal_tsc *tsc = devdata;
+
+ low = clamp_val(low, -40000, 120000);
+ high = clamp_val(high, -40000, 120000);
+
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP1,
+ rcar_gen3_thermal_mcelsius_to_temp(tsc, low));
+
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP2,
+ rcar_gen3_thermal_mcelsius_to_temp(tsc, high));
+
+ tsc->low = low;
+ tsc->high = high;
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = {
+ .get_temp = rcar_gen3_thermal_get_temp,
+ .set_trips = rcar_gen3_thermal_set_trips,
+};
+
+static void rcar_thermal_irq_set(struct rcar_gen3_thermal_priv *priv, bool on)
+{
+ unsigned int i;
+ u32 val = on ? IRQ_TEMPD1 | IRQ_TEMP2 : 0;
+
+ for (i = 0; i < priv->num_tscs; i++)
+ rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQMSK, val);
+}
+
+static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data)
+{
+ struct rcar_gen3_thermal_priv *priv = data;
+ u32 status;
+ int i;
+
+ 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)
+ thermal_zone_device_update(priv->tscs[i]->zone,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static const struct soc_device_attribute r8a7795es1[] = {
+ { .soc_id = "r8a7795", .revision = "ES1.*" },
+ { /* sentinel */ }
+};
+
+static void rcar_gen3_thermal_init_r8a7795es1(struct rcar_gen3_thermal_tsc *tsc)
+{
+ rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, CTSR_THBGR);
+ rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, 0x0);
+
+ usleep_range(1000, 2000);
+
+ rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, CTSR_PONM);
+
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F);
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN, IRQ_TEMPD1 | IRQ_TEMP2);
+
+ rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR,
+ CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN);
+
+ usleep_range(100, 200);
+
+ rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR,
+ CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN |
+ CTSR_VMST | CTSR_THSST);
+
+ usleep_range(1000, 2000);
+}
+
+static void rcar_gen3_thermal_init(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, 0x3F);
+ rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0);
+ 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 of_device_id rcar_gen3_thermal_dt_ids[] = {
+ { .compatible = "renesas,r8a7795-thermal", },
+ { .compatible = "renesas,r8a7796-thermal", },
+ { .compatible = "renesas,r8a77965-thermal", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rcar_gen3_thermal_dt_ids);
+
+static int rcar_gen3_thermal_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
+
+ rcar_thermal_irq_set(priv, false);
+
+ pm_runtime_put(dev);
+ pm_runtime_disable(dev);
+
+ 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;
+ int ret, irq, i;
+ char *irqname;
+
+ /* default values if FUSEs are missing */
+ /* TODO: Read values from hardware on supported platforms */
+ int ptat[3] = { 2631, 1509, 435 };
+ int thcode[TSC_MAX_NUM][3] = {
+ { 3397, 2800, 2221 },
+ { 3393, 2795, 2216 },
+ { 3389, 2805, 2237 },
+ };
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->thermal_init = rcar_gen3_thermal_init;
+ if (soc_device_match(r8a7795es1))
+ priv->thermal_init = rcar_gen3_thermal_init_r8a7795es1;
+
+ platform_set_drvdata(pdev, priv);
+
+ /*
+ * Request 2 (of the 3 possible) IRQs, the driver only needs to
+ * to trigger on the low and high trip points of the current
+ * temp window at this point.
+ */
+ for (i = 0; i < 2; i++) {
+ irq = platform_get_irq(pdev, i);
+ 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;
+ }
+
+ 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->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(tsc->base)) {
+ ret = PTR_ERR(tsc->base);
+ goto error_unregister;
+ }
+
+ priv->tscs[i] = tsc;
+
+ priv->thermal_init(tsc);
+ rcar_gen3_thermal_calc_coefs(&tsc->coef, ptat, thcode[i]);
+
+ zone = devm_thermal_zone_of_sensor_register(dev, i, tsc,
+ &rcar_gen3_tz_of_ops);
+ if (IS_ERR(zone)) {
+ dev_err(dev, "Can't register thermal zone\n");
+ ret = PTR_ERR(zone);
+ goto error_unregister;
+ }
+ tsc->zone = zone;
+
+ ret = of_thermal_get_ntrips(tsc->zone);
+ if (ret < 0)
+ goto error_unregister;
+
+ dev_info(dev, "TSC%d: Loaded %d trip points\n", i, ret);
+ }
+
+ priv->num_tscs = i;
+
+ if (!priv->num_tscs) {
+ ret = -ENODEV;
+ goto error_unregister;
+ }
+
+ rcar_thermal_irq_set(priv, true);
+
+ return 0;
+
+error_unregister:
+ rcar_gen3_thermal_remove(pdev);
+
+ return ret;
+}
+
+static int __maybe_unused rcar_gen3_thermal_suspend(struct device *dev)
+{
+ struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
+
+ rcar_thermal_irq_set(priv, false);
+
+ return 0;
+}
+
+static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev)
+{
+ struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev);
+ unsigned int i;
+
+ for (i = 0; i < priv->num_tscs; i++) {
+ struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i];
+
+ priv->thermal_init(tsc);
+ rcar_gen3_thermal_set_trips(tsc, tsc->low, tsc->high);
+ }
+
+ rcar_thermal_irq_set(priv, true);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops, rcar_gen3_thermal_suspend,
+ rcar_gen3_thermal_resume);
+
+static struct platform_driver rcar_gen3_thermal_driver = {
+ .driver = {
+ .name = "rcar_gen3_thermal",
+ .pm = &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/rcar_thermal.c b/drivers/thermal/rcar_thermal.c
new file mode 100644
index 000000000..140386d7c
--- /dev/null
+++ b/drivers/thermal/rcar_thermal.c
@@ -0,0 +1,656 @@
+// 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_device.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;
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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;
+ u32 ctemp;
+};
+
+#define rcar_thermal_for_each_priv(pos, common) \
+ list_for_each_entry(pos, &common->head, list)
+
+#define MCELSIUS(temp) ((temp) * 1000)
+#define rcar_zone_to_priv(zone) ((zone)->devdata)
+#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-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 i;
+ u32 ctemp, old, new;
+ int ret = -EINVAL;
+
+ 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);
+
+ ctemp = 0;
+ 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
+ */
+ udelay(300);
+
+ new = rcar_thermal_read(priv, THSSR) & CTEMP;
+ if (new == old) {
+ ctemp = new;
+ break;
+ }
+ old = new;
+ }
+
+ if (!ctemp) {
+ 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)));
+ }
+
+ dev_dbg(dev, "thermal%d %d -> %d\n", priv->id, priv->ctemp, ctemp);
+
+ priv->ctemp = ctemp;
+ ret = 0;
+err_out_unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int rcar_thermal_get_current_temp(struct rcar_thermal_priv *priv,
+ int *temp)
+{
+ int tmp;
+ int ret;
+
+ ret = rcar_thermal_update_temp(priv);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&priv->lock);
+ tmp = MCELSIUS((priv->ctemp * 5) - 65);
+ mutex_unlock(&priv->lock);
+
+ if ((tmp < MCELSIUS(-45)) || (tmp > MCELSIUS(125))) {
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ dev_err(dev, "it couldn't measure temperature correctly\n");
+ return -EIO;
+ }
+
+ *temp = tmp;
+
+ return 0;
+}
+
+static int rcar_thermal_of_get_temp(void *data, int *temp)
+{
+ struct rcar_thermal_priv *priv = data;
+
+ return rcar_thermal_get_current_temp(priv, temp);
+}
+
+static int rcar_thermal_get_temp(struct thermal_zone_device *zone, int *temp)
+{
+ struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
+
+ return rcar_thermal_get_current_temp(priv, temp);
+}
+
+static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone,
+ int trip, enum thermal_trip_type *type)
+{
+ struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ /* see rcar_thermal_get_temp() */
+ switch (trip) {
+ case 0: /* +90 <= temp */
+ *type = THERMAL_TRIP_CRITICAL;
+ break;
+ default:
+ dev_err(dev, "rcar driver trip error\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone,
+ int trip, int *temp)
+{
+ struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ /* see rcar_thermal_get_temp() */
+ switch (trip) {
+ case 0: /* +90 <= temp */
+ *temp = MCELSIUS(90);
+ break;
+ default:
+ dev_err(dev, "rcar driver trip error\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rcar_thermal_notify(struct thermal_zone_device *zone,
+ int trip, enum thermal_trip_type type)
+{
+ struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
+ struct device *dev = rcar_priv_to_dev(priv);
+
+ switch (type) {
+ case THERMAL_TRIP_CRITICAL:
+ /* FIXME */
+ dev_warn(dev, "Thermal reached to critical temperature\n");
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops rcar_thermal_zone_of_ops = {
+ .get_temp = rcar_thermal_of_get_temp,
+};
+
+static struct thermal_zone_device_ops rcar_thermal_zone_ops = {
+ .get_temp = rcar_thermal_get_temp,
+ .get_trip_type = rcar_thermal_get_trip_type,
+ .get_trip_temp = rcar_thermal_get_trip_temp,
+ .notify = rcar_thermal_notify,
+};
+
+/*
+ * 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 cctemp, nctemp;
+ int ret;
+
+ priv = container_of(work, struct rcar_thermal_priv, work.work);
+
+ ret = rcar_thermal_get_current_temp(priv, &cctemp);
+ if (ret < 0)
+ return;
+
+ ret = rcar_thermal_update_temp(priv);
+ if (ret < 0)
+ return;
+
+ rcar_thermal_irq_enable(priv);
+
+ ret = rcar_thermal_get_current_temp(priv, &nctemp);
+ if (ret < 0)
+ return;
+
+ if (nctemp != cctemp)
+ thermal_zone_device_update(priv->zone,
+ THERMAL_EVENT_UNSPECIFIED);
+}
+
+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;
+ unsigned long flags;
+ u32 status, mask;
+
+ spin_lock_irqsave(&common->lock, flags);
+
+ mask = rcar_thermal_common_read(common, INTMSK);
+ status = rcar_thermal_common_read(common, STR);
+ rcar_thermal_common_write(common, STR, 0x000F0F0F & mask);
+
+ spin_unlock_irqrestore(&common->lock, flags);
+
+ 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 int 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);
+
+ return 0;
+}
+
+static int rcar_thermal_probe(struct platform_device *pdev)
+{
+ struct rcar_thermal_common *common;
+ struct rcar_thermal_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct resource *res, *irq;
+ 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++) {
+ irq = platform_get_resource(pdev, IORESOURCE_IRQ, i);
+ if (!irq)
+ continue;
+ 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->start, rcar_thermal_irq,
+ IRQF_SHARED, dev_name(dev), common);
+ if (ret) {
+ dev_err(dev, "irq request failed\n ");
+ 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_zone_of_sensor_register(
+ dev, i, priv,
+ &rcar_thermal_zone_of_ops);
+ else
+ priv->zone = thermal_zone_device_register(
+ "rcar_thermal",
+ 1, 0, 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) {
+ /*
+ * thermal_zone doesn't enable hwmon as default,
+ * but, enable it here to keep compatible
+ */
+ priv->zone->tzp->no_hwmon = false;
+ ret = thermal_add_hwmon_sysfs(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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+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;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(rcar_thermal_pm_ops, rcar_thermal_suspend,
+ rcar_thermal_resume);
+
+static struct platform_driver rcar_thermal_driver = {
+ .driver = {
+ .name = "rcar_thermal",
+ .pm = &rcar_thermal_pm_ops,
+ .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/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c
new file mode 100644
index 000000000..f36375d5a
--- /dev/null
+++ b/drivers/thermal/rockchip_thermal.c
@@ -0,0 +1,1411 @@
+/*
+ * Copyright (c) 2014-2016, Fuzhou Rockchip Electronics Co., Ltd
+ * Caesar Wang <wxt@rock-chips.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.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/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 system has two Temperature Sensors.
+ * sensor0 is for CPU, and sensor1 is for GPU.
+ */
+enum sensor_id {
+ SENSOR_CPU = 0,
+ SENSOR_GPU,
+};
+
+/**
+ * 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,
+};
+
+/**
+ * The max sensors is two in rockchip SoCs.
+ * Two sensors: CPU and GPU sensor.
+ */
+#define SOC_MAX_SENSORS 2
+
+/**
+ * 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_id[SOC_MAX_SENSORS]: the sensor id of chip correspond to the channel
+ * @chn_num: the channel number of tsadc chip
+ * @tshut_temp: the hardware-controlled shutdown temperature value
+ * @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO)
+ * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
+ * @initialize: SoC special initialize tsadc controller method
+ * @irq_ack: clear the interrupt
+ * @get_temp: get the temperature
+ * @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
+ * @table: the chip-specific conversion table
+ */
+struct rockchip_tsadc_chip {
+ /* The sensor id of chip correspond to the ADC channel */
+ int chn_id[SOC_MAX_SENSORS];
+ int chn_num;
+
+ /* The hardware-controlled tshut property */
+ int tshut_temp;
+ enum tshut_mode tshut_mode;
+ enum tshut_polarity tshut_polarity;
+
+ /* 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);
+
+ /* 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
+ * @id: identifier of the thermal sensor
+ */
+struct rockchip_thermal_sensor {
+ struct rockchip_thermal_data *thermal;
+ struct thermal_zone_device *tzd;
+ int id;
+};
+
+/**
+ * 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[SOC_MAX_SENSORS]: the thermal sensor
+ * @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
+ * @tshut_temp: the hardware-controlled shutdown temperature value
+ * @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[SOC_MAX_SENSORS];
+
+ struct clk *clk;
+ struct clk *pclk;
+
+ struct regmap *grf;
+ void __iomem *regs;
+
+ int tshut_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 TSADCV2_DATA(chn) (0x20 + (chn) * 0x04)
+#define TSADCV2_COMP_INT(chn) (0x30 + (chn) * 0x04)
+#define TSADCV2_COMP_SHUT(chn) (0x40 + (chn) * 0x04)
+#define TSADCV2_HIGHT_INT_DEBOUNCE 0x60
+#define TSADCV2_HIGHT_TSHUT_DEBOUNCE 0x64
+#define TSADCV2_AUTO_PERIOD 0x68
+#define TSADCV2_AUTO_PERIOD_HT 0x6c
+
+#define TSADCV2_AUTO_EN BIT(0)
+#define TSADCV2_AUTO_SRC_EN(chn) BIT(4 + (chn))
+#define TSADCV2_AUTO_TSHUT_POLARITY_HIGH BIT(8)
+
+#define TSADCV3_AUTO_Q_SEL_EN BIT(1)
+
+#define TSADCV2_INT_SRC_EN(chn) BIT(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 TSADCV2_DATA_MASK 0xfff
+#define TSADCV3_DATA_MASK 0x3ff
+
+#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 TSADCV2_USER_INTER_PD_SOC 0x340 /* 13 clocks */
+
+#define GRF_SARADC_TESTBIT 0x0e644
+#define GRF_TSADC_TESTBIT_L 0x0e648
+#define GRF_TSADC_TESTBIT_H 0x0e64c
+
+#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)
+
+/**
+ * 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},
+ {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 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.
+ *
+ * (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.
+ *
+ * (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_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_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.
+ *
+ * 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 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_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_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 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 const struct rockchip_tsadc_chip rv1108_tsadc_data = {
+ .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */
+ .chn_num = 1, /* one channel for tsadc */
+
+ .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
+ .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
+ .tshut_temp = 95000,
+
+ .initialize = rk_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 = {
+ .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */
+ .chn_num = 1, /* one channel for tsadc */
+
+ .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
+ .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
+ .tshut_temp = 95000,
+
+ .initialize = rk_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 = {
+ .chn_id[SENSOR_CPU] = 1, /* cpu sensor is channel 1 */
+ .chn_id[SENSOR_GPU] = 2, /* gpu sensor is channel 2 */
+ .chn_num = 2, /* two channels for tsadc */
+
+ .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
+ .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
+ .tshut_temp = 95000,
+
+ .initialize = rk_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 = {
+ .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */
+ .chn_num = 1, /* one channels for tsadc */
+
+ .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 = {
+ .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */
+ .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */
+ .chn_num = 2, /* two channels for tsadc */
+
+ .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
+ .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
+ .tshut_temp = 95000,
+
+ .initialize = rk_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 = {
+ .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */
+ .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */
+ .chn_num = 2, /* two channels for tsadc */
+
+ .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
+ .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
+ .tshut_temp = 95000,
+
+ .initialize = rk_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 = {
+ .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */
+ .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */
+ .chn_num = 2, /* two channels for tsadc */
+
+ .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */
+ .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */
+ .tshut_temp = 95000,
+
+ .initialize = rk_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 of_device_id of_rockchip_thermal_match[] = {
+ {
+ .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,
+ },
+ { /* 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;
+
+ tzd->ops->set_mode(tzd,
+ on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED);
+}
+
+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(void *_sensor, int low, int high)
+{
+ struct rockchip_thermal_sensor *sensor = _sensor;
+ 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);
+}
+
+static int rockchip_thermal_get_temp(void *_sensor, int *out_temp)
+{
+ struct rockchip_thermal_sensor *sensor = _sensor;
+ 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);
+ dev_dbg(&thermal->pdev->dev, "sensor %d - temp: %d, retval: %d\n",
+ sensor->id, *out_temp, retval);
+
+ return retval;
+}
+
+static const struct thermal_zone_of_device_ops rockchip_of_thermal_ops = {
+ .get_temp = rockchip_thermal_get_temp,
+ .set_trips = rockchip_thermal_set_trips,
+};
+
+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;
+ }
+
+ /* The tsadc wont to handle the error in here since some SoCs didn't
+ * need this property.
+ */
+ thermal->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ if (IS_ERR(thermal->grf))
+ dev_warn(dev, "Missing rockchip,grf property\n");
+
+ 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;
+ int error;
+
+ tsadc->set_tshut_mode(id, thermal->regs, thermal->tshut_mode);
+
+ error = tsadc->set_tshut_temp(&tsadc->table, id, thermal->regs,
+ thermal->tshut_temp);
+ if (error)
+ dev_err(&pdev->dev, "%s: invalid tshut=%d, error=%d\n",
+ __func__, thermal->tshut_temp, error);
+
+ sensor->thermal = thermal;
+ sensor->id = id;
+ sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, id,
+ sensor, &rockchip_of_thermal_ops);
+ if (IS_ERR(sensor->tzd)) {
+ error = PTR_ERR(sensor->tzd);
+ dev_err(&pdev->dev, "failed to register sensor %d: %d\n",
+ id, error);
+ return error;
+ }
+
+ return 0;
+}
+
+/**
+ * Reset TSADC Controller, reset all tsadc registers.
+ */
+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;
+ const struct of_device_id *match;
+ struct resource *res;
+ int irq;
+ int i;
+ int error;
+
+ match = of_match_node(of_rockchip_thermal_match, np);
+ if (!match)
+ return -ENXIO;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq resource?\n");
+ return -EINVAL;
+ }
+
+ thermal = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_thermal_data),
+ GFP_KERNEL);
+ if (!thermal)
+ return -ENOMEM;
+
+ thermal->pdev = pdev;
+
+ thermal->chip = (const struct rockchip_tsadc_chip *)match->data;
+ if (!thermal->chip)
+ return -EINVAL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ thermal->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(thermal->regs))
+ return PTR_ERR(thermal->regs);
+
+ thermal->reset = devm_reset_control_get(&pdev->dev, "tsadc-apb");
+ if (IS_ERR(thermal->reset)) {
+ error = PTR_ERR(thermal->reset);
+ dev_err(&pdev->dev, "failed to get tsadc reset: %d\n", error);
+ return error;
+ }
+
+ thermal->clk = devm_clk_get(&pdev->dev, "tsadc");
+ if (IS_ERR(thermal->clk)) {
+ error = PTR_ERR(thermal->clk);
+ dev_err(&pdev->dev, "failed to get tsadc clock: %d\n", error);
+ return error;
+ }
+
+ thermal->pclk = devm_clk_get(&pdev->dev, "apb_pclk");
+ if (IS_ERR(thermal->pclk)) {
+ error = PTR_ERR(thermal->pclk);
+ dev_err(&pdev->dev, "failed to get apb_pclk clock: %d\n",
+ error);
+ return error;
+ }
+
+ error = clk_prepare_enable(thermal->clk);
+ if (error) {
+ dev_err(&pdev->dev, "failed to enable converter clock: %d\n",
+ error);
+ return error;
+ }
+
+ error = clk_prepare_enable(thermal->pclk);
+ if (error) {
+ dev_err(&pdev->dev, "failed to enable pclk: %d\n", error);
+ goto err_disable_clk;
+ }
+
+ rockchip_thermal_reset_controller(thermal->reset);
+
+ error = rockchip_configure_from_dt(&pdev->dev, np, thermal);
+ if (error) {
+ dev_err(&pdev->dev, "failed to parse device tree data: %d\n",
+ error);
+ goto err_disable_pclk;
+ }
+
+ thermal->chip->initialize(thermal->grf, thermal->regs,
+ thermal->tshut_polarity);
+
+ for (i = 0; i < thermal->chip->chn_num; i++) {
+ error = rockchip_thermal_register_sensor(pdev, thermal,
+ &thermal->sensors[i],
+ thermal->chip->chn_id[i]);
+ if (error) {
+ dev_err(&pdev->dev,
+ "failed to register sensor[%d] : error = %d\n",
+ i, error);
+ goto err_disable_pclk;
+ }
+ }
+
+ error = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ &rockchip_thermal_alarm_irq_thread,
+ IRQF_ONESHOT,
+ "rockchip_thermal", thermal);
+ if (error) {
+ dev_err(&pdev->dev,
+ "failed to request tsadc irq: %d\n", error);
+ goto err_disable_pclk;
+ }
+
+ thermal->chip->control(thermal->regs, true);
+
+ for (i = 0; i < thermal->chip->chn_num; i++)
+ rockchip_thermal_toggle_sensor(&thermal->sensors[i], true);
+
+ platform_set_drvdata(pdev, thermal);
+
+ return 0;
+
+err_disable_pclk:
+ clk_disable_unprepare(thermal->pclk);
+err_disable_clk:
+ clk_disable_unprepare(thermal->clk);
+
+ return error;
+}
+
+static int 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];
+
+ rockchip_thermal_toggle_sensor(sensor, false);
+ }
+
+ thermal->chip->control(thermal->regs, false);
+
+ clk_disable_unprepare(thermal->pclk);
+ clk_disable_unprepare(thermal->clk);
+
+ return 0;
+}
+
+static int __maybe_unused rockchip_thermal_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev);
+ 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 platform_device *pdev = to_platform_device(dev);
+ struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev);
+ int i;
+ int error;
+
+ 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);
+
+ thermal->chip->initialize(thermal->grf, thermal->regs,
+ thermal->tshut_polarity);
+
+ for (i = 0; i < thermal->chip->chn_num; i++) {
+ int id = thermal->sensors[i].id;
+
+ thermal->chip->set_tshut_mode(id, thermal->regs,
+ thermal->tshut_mode);
+
+ error = thermal->chip->set_tshut_temp(&thermal->chip->table,
+ id, thermal->regs,
+ thermal->tshut_temp);
+ if (error)
+ dev_err(&pdev->dev, "%s: invalid tshut=%d, error=%d\n",
+ __func__, thermal->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..222e64416
--- /dev/null
+++ b/drivers/thermal/samsung/Kconfig
@@ -0,0 +1,10 @@
+config EXYNOS_THERMAL
+ tristate "Exynos thermal management unit driver"
+ depends on THERMAL_OF
+ depends on HAS_IOMEM
+ 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..1e47d0d89
--- /dev/null
+++ b/drivers/thermal/samsung/Makefile
@@ -0,0 +1,5 @@
+#
+# 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..3376be686
--- /dev/null
+++ b/drivers/thermal/samsung/exynos_tmu.c
@@ -0,0 +1,1204 @@
+/*
+ * 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>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <dt-bindings/thermal/thermal_exynos.h>
+
+#include "../thermal_core.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
+ * @id: identifier of the one instance of the TMU controller.
+ * @base: base address of the single instance of the TMU controller.
+ * @base_second: base address of the common registers of the TMU controller.
+ * @irq: irq number of the TMU controller.
+ * @soc: id of the SOC type.
+ * @irq_work: pointer to the irq work structure.
+ * @lock: lock to implement synchronization.
+ * @clk: pointer to the clock structure.
+ * @clk_sec: pointer to the clock structure for accessing the base_second.
+ * @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
+ * @regulator: pointer to the TMU regulator structure.
+ * @reg_conf: pointer to structure to register with core thermal.
+ * @ntrip: number of supported trip points.
+ * @enabled: current status of TMU device
+ * @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 {
+ int id;
+ void __iomem *base;
+ void __iomem *base_second;
+ int irq;
+ enum soc_type soc;
+ struct work_struct irq_work;
+ struct mutex lock;
+ struct clk *clk, *clk_sec, *sclk;
+ u32 cal_type;
+ u32 efuse_value;
+ u32 min_efuse_value;
+ u32 max_efuse_value;
+ u16 temp_error1, temp_error2;
+ u8 gain;
+ u8 reference_voltage;
+ struct regulator *regulator;
+ struct thermal_zone_device *tzd;
+ unsigned int ntrip;
+ bool enabled;
+
+ void (*tmu_set_trip_temp)(struct exynos_tmu_data *data, int trip,
+ u8 temp);
+ void (*tmu_set_trip_hyst)(struct exynos_tmu_data *data, int trip,
+ u8 temp, u8 hyst);
+ void (*tmu_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);
+ struct thermal_zone_device *tzd = data->tzd;
+ const struct thermal_trip * const trips =
+ of_thermal_get_trip_points(tzd);
+ unsigned int status;
+ int ret = 0, temp, hyst;
+
+ if (!trips) {
+ dev_err(&pdev->dev,
+ "Cannot get trip points from device tree!\n");
+ return -ENODEV;
+ }
+
+ if (data->soc != SOC_ARCH_EXYNOS5433) /* FIXME */
+ ret = tzd->ops->get_crit_temp(tzd, &temp);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "No CRITICAL trip point defined in device tree!\n");
+ goto out;
+ }
+
+ if (of_thermal_get_ntrips(tzd) > data->ntrip) {
+ dev_info(&pdev->dev,
+ "More trip points than supported by this TMU.\n");
+ dev_info(&pdev->dev,
+ "%d trip points should be configured in polling mode.\n",
+ (of_thermal_get_ntrips(tzd) - data->ntrip));
+ }
+
+ 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 {
+ int i, ntrips =
+ min_t(int, of_thermal_get_ntrips(tzd), data->ntrip);
+
+ data->tmu_initialize(pdev);
+
+ /* Write temperature code for rising and falling threshold */
+ for (i = 0; i < ntrips; i++) {
+ /* Write temperature code for rising threshold */
+ ret = tzd->ops->get_trip_temp(tzd, i, &temp);
+ if (ret)
+ goto err;
+ temp /= MCELSIUS;
+ data->tmu_set_trip_temp(data, i, temp);
+
+ /* Write temperature code for falling threshold */
+ ret = tzd->ops->get_trip_hyst(tzd, i, &hyst);
+ if (ret)
+ goto err;
+ hyst /= MCELSIUS;
+ data->tmu_set_trip_hyst(data, i, temp, hyst);
+ }
+
+ data->tmu_clear_irqs(data);
+ }
+err:
+ clk_disable(data->clk);
+ mutex_unlock(&data->lock);
+ if (!IS_ERR(data->clk_sec))
+ clk_disable(data->clk_sec);
+out:
+ return ret;
+}
+
+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 exynos4210_tmu_set_trip_temp(struct exynos_tmu_data *data,
+ int trip, u8 temp)
+{
+ const struct thermal_trip * const trips =
+ of_thermal_get_trip_points(data->tzd);
+ u8 ref, th_code;
+
+ ref = trips[0].temperature / MCELSIUS;
+
+ if (trip == 0) {
+ th_code = temp_to_code(data, ref);
+ writeb(th_code, data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
+ }
+
+ temp -= ref;
+ writeb(temp, data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + trip * 4);
+}
+
+/* failing thresholds are not supported on Exynos4210 */
+static void exynos4210_tmu_set_trip_hyst(struct exynos_tmu_data *data,
+ int trip, u8 temp, u8 hyst)
+{
+}
+
+static void exynos4210_tmu_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));
+}
+
+static void exynos4412_tmu_set_trip_temp(struct exynos_tmu_data *data,
+ int trip, u8 temp)
+{
+ u32 th, con;
+
+ th = readl(data->base + EXYNOS_THD_TEMP_RISE);
+ th &= ~(0xff << 8 * trip);
+ th |= temp_to_code(data, temp) << 8 * trip;
+ writel(th, data->base + EXYNOS_THD_TEMP_RISE);
+
+ if (trip == 3) {
+ con = readl(data->base + EXYNOS_TMU_REG_CONTROL);
+ con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT);
+ writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
+ }
+}
+
+static void exynos4412_tmu_set_trip_hyst(struct exynos_tmu_data *data,
+ int trip, u8 temp, u8 hyst)
+{
+ u32 th;
+
+ th = readl(data->base + EXYNOS_THD_TEMP_FALL);
+ th &= ~(0xff << 8 * trip);
+ if (hyst)
+ th |= temp_to_code(data, temp - hyst) << 8 * trip;
+ writel(th, data->base + EXYNOS_THD_TEMP_FALL);
+}
+
+static void exynos4412_tmu_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_trip_temp(struct exynos_tmu_data *data,
+ int trip, u8 temp)
+{
+ unsigned int reg_off, j;
+ u32 th;
+
+ if (trip > 3) {
+ reg_off = EXYNOS5433_THD_TEMP_RISE7_4;
+ j = trip - 4;
+ } else {
+ reg_off = EXYNOS5433_THD_TEMP_RISE3_0;
+ j = trip;
+ }
+
+ th = readl(data->base + reg_off);
+ th &= ~(0xff << j * 8);
+ th |= (temp_to_code(data, temp) << j * 8);
+ writel(th, data->base + reg_off);
+}
+
+static void exynos5433_tmu_set_trip_hyst(struct exynos_tmu_data *data,
+ int trip, u8 temp, u8 hyst)
+{
+ unsigned int reg_off, j;
+ u32 th;
+
+ if (trip > 3) {
+ reg_off = EXYNOS5433_THD_TEMP_FALL7_4;
+ j = trip - 4;
+ } else {
+ reg_off = EXYNOS5433_THD_TEMP_FALL3_0;
+ j = trip;
+ }
+
+ th = readl(data->base + reg_off);
+ th &= ~(0xff << j * 8);
+ th |= (temp_to_code(data, temp - hyst) << j * 8);
+ writel(th, data->base + reg_off);
+}
+
+static void exynos5433_tmu_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_trip_temp(struct exynos_tmu_data *data,
+ int trip, u8 temp)
+{
+ unsigned int reg_off, bit_off;
+ u32 th;
+
+ reg_off = ((7 - trip) / 2) * 4;
+ bit_off = ((8 - trip) % 2);
+
+ th = readl(data->base + EXYNOS7_THD_TEMP_RISE7_6 + reg_off);
+ th &= ~(EXYNOS7_TMU_TEMP_MASK << (16 * bit_off));
+ th |= temp_to_code(data, temp) << (16 * bit_off);
+ writel(th, data->base + EXYNOS7_THD_TEMP_RISE7_6 + reg_off);
+}
+
+static void exynos7_tmu_set_trip_hyst(struct exynos_tmu_data *data,
+ int trip, u8 temp, u8 hyst)
+{
+ unsigned int reg_off, bit_off;
+ u32 th;
+
+ reg_off = ((7 - trip) / 2) * 4;
+ bit_off = ((8 - trip) % 2);
+
+ th = readl(data->base + EXYNOS7_THD_TEMP_FALL7_6 + reg_off);
+ th &= ~(EXYNOS7_TMU_TEMP_MASK << (16 * bit_off));
+ th |= temp_to_code(data, temp - hyst) << (16 * bit_off);
+ writel(th, data->base + EXYNOS7_THD_TEMP_FALL7_6 + reg_off);
+}
+
+static void exynos7_tmu_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);
+ struct thermal_zone_device *tz = data->tzd;
+ unsigned int con, interrupt_en = 0, i;
+
+ con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
+
+ if (on) {
+ for (i = 0; i < data->ntrip; i++) {
+ if (!of_thermal_is_trip_valid(tz, i))
+ continue;
+
+ interrupt_en |=
+ (1 << (EXYNOS_TMU_INTEN_RISE0_SHIFT + i * 4));
+ }
+
+ if (data->soc != SOC_ARCH_EXYNOS4210)
+ interrupt_en |=
+ interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
+
+ con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
+ } else {
+ con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
+ }
+
+ writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN);
+ writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
+}
+
+static void exynos5433_tmu_control(struct platform_device *pdev, bool on)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ struct thermal_zone_device *tz = data->tzd;
+ unsigned int con, interrupt_en = 0, pd_det_en, i;
+
+ con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
+
+ if (on) {
+ for (i = 0; i < data->ntrip; i++) {
+ if (!of_thermal_is_trip_valid(tz, i))
+ continue;
+
+ interrupt_en |=
+ (1 << (EXYNOS7_TMU_INTEN_RISE0_SHIFT + i));
+ }
+
+ interrupt_en |=
+ interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
+
+ con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
+ } else
+ con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
+
+ pd_det_en = on ? EXYNOS5433_PD_DET_EN : 0;
+
+ writel(pd_det_en, data->base + EXYNOS5433_TMU_PD_DET_EN);
+ writel(interrupt_en, data->base + EXYNOS5433_TMU_REG_INTEN);
+ writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
+}
+
+static void exynos7_tmu_control(struct platform_device *pdev, bool on)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ struct thermal_zone_device *tz = data->tzd;
+ unsigned int con, interrupt_en = 0, i;
+
+ con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
+
+ if (on) {
+ for (i = 0; i < data->ntrip; i++) {
+ if (!of_thermal_is_trip_valid(tz, i))
+ continue;
+
+ interrupt_en |=
+ (1 << (EXYNOS7_TMU_INTEN_RISE0_SHIFT + i));
+ }
+
+ interrupt_en |=
+ interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
+
+ con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
+ con |= (1 << EXYNOS7_PD_DET_EN_SHIFT);
+ } else {
+ con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
+ con &= ~(1 << EXYNOS7_PD_DET_EN_SHIFT);
+ }
+
+ writel(interrupt_en, data->base + EXYNOS7_TMU_REG_INTEN);
+ writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
+}
+
+static int exynos_get_temp(void *p, int *temp)
+{
+ struct exynos_tmu_data *data = p;
+ 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(void *drv_data, int temp)
+{
+ struct exynos_tmu_data *data = drv_data;
+ 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(void *drv_data, 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 void exynos_tmu_work(struct work_struct *work)
+{
+ struct exynos_tmu_data *data = container_of(work,
+ struct exynos_tmu_data, irq_work);
+
+ 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);
+ enable_irq(data->irq);
+}
+
+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 irqreturn_t exynos_tmu_irq(int irq, void *id)
+{
+ struct exynos_tmu_data *data = id;
+
+ disable_irq_nosync(irq);
+ schedule_work(&data->irq_work);
+
+ return IRQ_HANDLED;
+}
+
+static const struct of_device_id exynos_tmu_match[] = {
+ {
+ .compatible = "samsung,exynos3250-tmu",
+ .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->id = of_alias_get_id(pdev->dev.of_node, "tmuctrl");
+ if (data->id < 0)
+ data->id = 0;
+
+ data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
+ if (data->irq <= 0) {
+ dev_err(&pdev->dev, "failed to get IRQ\n");
+ 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 = (enum soc_type)of_device_get_match_data(&pdev->dev);
+
+ switch (data->soc) {
+ case SOC_ARCH_EXYNOS4210:
+ data->tmu_set_trip_temp = exynos4210_tmu_set_trip_temp;
+ data->tmu_set_trip_hyst = exynos4210_tmu_set_trip_hyst;
+ data->tmu_initialize = exynos4210_tmu_initialize;
+ data->tmu_control = exynos4210_tmu_control;
+ data->tmu_read = exynos4210_tmu_read;
+ data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
+ data->ntrip = 4;
+ data->gain = 15;
+ data->reference_voltage = 7;
+ data->efuse_value = 55;
+ 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_trip_temp = exynos4412_tmu_set_trip_temp;
+ data->tmu_set_trip_hyst = exynos4412_tmu_set_trip_hyst;
+ data->tmu_initialize = exynos4412_tmu_initialize;
+ data->tmu_control = exynos4210_tmu_control;
+ data->tmu_read = exynos4412_tmu_read;
+ data->tmu_set_emulation = exynos4412_tmu_set_emulation;
+ data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
+ data->ntrip = 4;
+ data->gain = 8;
+ data->reference_voltage = 16;
+ data->efuse_value = 55;
+ 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_trip_temp = exynos5433_tmu_set_trip_temp;
+ data->tmu_set_trip_hyst = exynos5433_tmu_set_trip_hyst;
+ data->tmu_initialize = exynos5433_tmu_initialize;
+ data->tmu_control = exynos5433_tmu_control;
+ data->tmu_read = exynos4412_tmu_read;
+ data->tmu_set_emulation = exynos4412_tmu_set_emulation;
+ data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
+ data->ntrip = 8;
+ data->gain = 8;
+ if (res.start == EXYNOS5433_G3D_BASE)
+ data->reference_voltage = 23;
+ 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_trip_temp = exynos7_tmu_set_trip_temp;
+ data->tmu_set_trip_hyst = exynos7_tmu_set_trip_hyst;
+ data->tmu_initialize = exynos7_tmu_initialize;
+ data->tmu_control = exynos7_tmu_control;
+ data->tmu_read = exynos7_tmu_read;
+ data->tmu_set_emulation = exynos4412_tmu_set_emulation;
+ data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
+ data->ntrip = 8;
+ data->gain = 9;
+ data->reference_voltage = 17;
+ data->efuse_value = 75;
+ 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 const struct thermal_zone_of_device_ops exynos_sensor_ops = {
+ .get_temp = exynos_get_temp,
+ .set_emul_temp = exynos_tmu_set_emulation,
+};
+
+static int exynos_tmu_probe(struct platform_device *pdev)
+{
+ struct exynos_tmu_data *data;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_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.
+ */
+ data->regulator = devm_regulator_get_optional(&pdev->dev, "vtmu");
+ if (!IS_ERR(data->regulator)) {
+ ret = regulator_enable(data->regulator);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable vtmu\n");
+ return ret;
+ }
+ } else {
+ if (PTR_ERR(data->regulator) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_info(&pdev->dev, "Regulator node (vtmu) not found\n");
+ }
+
+ ret = exynos_map_dt_data(pdev);
+ if (ret)
+ goto err_sensor;
+
+ INIT_WORK(&data->irq_work, exynos_tmu_work);
+
+ data->clk = devm_clk_get(&pdev->dev, "tmu_apbif");
+ if (IS_ERR(data->clk)) {
+ dev_err(&pdev->dev, "Failed to get clock\n");
+ ret = PTR_ERR(data->clk);
+ goto err_sensor;
+ }
+
+ data->clk_sec = devm_clk_get(&pdev->dev, "tmu_triminfo_apbif");
+ if (IS_ERR(data->clk_sec)) {
+ if (data->soc == SOC_ARCH_EXYNOS5420_TRIMINFO) {
+ dev_err(&pdev->dev, "Failed to get triminfo clock\n");
+ ret = PTR_ERR(data->clk_sec);
+ goto err_sensor;
+ }
+ } else {
+ ret = clk_prepare(data->clk_sec);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get clock\n");
+ goto err_sensor;
+ }
+ }
+
+ ret = clk_prepare(data->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get clock\n");
+ goto err_clk_sec;
+ }
+
+ switch (data->soc) {
+ case SOC_ARCH_EXYNOS5433:
+ case SOC_ARCH_EXYNOS7:
+ data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk");
+ if (IS_ERR(data->sclk)) {
+ dev_err(&pdev->dev, "Failed to get sclk\n");
+ ret = PTR_ERR(data->sclk);
+ goto err_clk;
+ } else {
+ ret = clk_prepare_enable(data->sclk);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to enable sclk\n");
+ goto err_clk;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * data->tzd must be registered before calling exynos_tmu_initialize(),
+ * requesting irq and calling exynos_tmu_control().
+ */
+ data->tzd = thermal_zone_of_sensor_register(&pdev->dev, 0, data,
+ &exynos_sensor_ops);
+ if (IS_ERR(data->tzd)) {
+ ret = PTR_ERR(data->tzd);
+ dev_err(&pdev->dev, "Failed to register sensor: %d\n", ret);
+ goto err_sclk;
+ }
+
+ ret = exynos_tmu_initialize(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to initialize TMU\n");
+ goto err_thermal;
+ }
+
+ ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
+ IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
+ goto err_thermal;
+ }
+
+ exynos_tmu_control(pdev, true);
+ return 0;
+
+err_thermal:
+ thermal_zone_of_sensor_unregister(&pdev->dev, data->tzd);
+err_sclk:
+ clk_disable_unprepare(data->sclk);
+err_clk:
+ clk_unprepare(data->clk);
+err_clk_sec:
+ if (!IS_ERR(data->clk_sec))
+ clk_unprepare(data->clk_sec);
+err_sensor:
+ if (!IS_ERR(data->regulator))
+ regulator_disable(data->regulator);
+
+ return ret;
+}
+
+static int exynos_tmu_remove(struct platform_device *pdev)
+{
+ struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+ struct thermal_zone_device *tzd = data->tzd;
+
+ thermal_zone_of_sensor_unregister(&pdev->dev, tzd);
+ exynos_tmu_control(pdev, false);
+
+ clk_disable_unprepare(data->sclk);
+ clk_unprepare(data->clk);
+ if (!IS_ERR(data->clk_sec))
+ clk_unprepare(data->clk_sec);
+
+ if (!IS_ERR(data->regulator))
+ regulator_disable(data->regulator);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+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/spear_thermal.c b/drivers/thermal/spear_thermal.c
new file mode 100644
index 000000000..81b35aace
--- /dev/null
+++ b/drivers/thermal/spear_thermal.c
@@ -0,0 +1,196 @@
+/*
+ * SPEAr thermal driver.
+ *
+ * Copyright (C) 2011-2012 ST Microelectronics
+ * Author: Vincenzo Frascino <vincenzo.frascino@st.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/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->devdata;
+
+ /*
+ * 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 struct thermal_zone_device_ops ops = {
+ .get_temp = thermal_get_temp,
+};
+
+static int __maybe_unused spear_thermal_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev);
+ struct spear_thermal_dev *stdev = spear_thermal->devdata;
+ 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 platform_device *pdev = to_platform_device(dev);
+ struct thermal_zone_device *spear_thermal = platform_get_drvdata(pdev);
+ struct spear_thermal_dev *stdev = spear_thermal->devdata;
+ unsigned int actual_mask = 0;
+ int ret = 0;
+
+ ret = clk_enable(stdev->clk);
+ if (ret) {
+ dev_err(&pdev->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;
+ struct resource *res;
+ 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 */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ stdev->thermal_base = devm_ioremap_resource(&pdev->dev, res);
+ 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_zone_device_register("spear_thermal", 0, 0,
+ stdev, &ops, NULL, 0, 0);
+ if (IS_ERR(spear_thermal)) {
+ dev_err(&pdev->dev, "thermal zone device is NULL\n");
+ ret = PTR_ERR(spear_thermal);
+ goto disable_clk;
+ }
+
+ platform_set_drvdata(pdev, spear_thermal);
+
+ dev_info(&spear_thermal->device, "Thermal Sensor Loaded at: 0x%p.\n",
+ stdev->thermal_base);
+
+ return 0;
+
+disable_clk:
+ clk_disable(stdev->clk);
+
+ return ret;
+}
+
+static int 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 = spear_thermal->devdata;
+
+ 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);
+
+ return 0;
+}
+
+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/st/Kconfig b/drivers/thermal/st/Kconfig
new file mode 100644
index 000000000..490fdbe22
--- /dev/null
+++ b/drivers/thermal/st/Kconfig
@@ -0,0 +1,12 @@
+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_SYSCFG
+ select ST_THERMAL
+ tristate "STi series syscfg register access based thermal sensors"
+
+config ST_THERMAL_MEMMAP
+ select ST_THERMAL
+ tristate "STi series memory mapped access based thermal sensors"
diff --git a/drivers/thermal/st/Makefile b/drivers/thermal/st/Makefile
new file mode 100644
index 000000000..b38878977
--- /dev/null
+++ b/drivers/thermal/st/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_ST_THERMAL) := st_thermal.o
+obj-$(CONFIG_ST_THERMAL_SYSCFG) += st_thermal_syscfg.o
+obj-$(CONFIG_ST_THERMAL_MEMMAP) += st_thermal_memmap.o
diff --git a/drivers/thermal/st/st_thermal.c b/drivers/thermal/st/st_thermal.c
new file mode 100644
index 000000000..be637e6b0
--- /dev/null
+++ b/drivers/thermal/st/st_thermal.c
@@ -0,0 +1,315 @@
+/*
+ * ST Thermal Sensor Driver core routines
+ * Author: Ajit Pal Singh <ajitpal.singh@st.com>
+ *
+ * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include "st_thermal.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 = th->devdata;
+ struct device *dev = sensor->dev;
+ 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);
+
+ dev_dbg(dev, "temperature: %d\n", temp);
+
+ *temperature = temp;
+
+ return 0;
+}
+
+static int st_thermal_get_trip_type(struct thermal_zone_device *th,
+ int trip, enum thermal_trip_type *type)
+{
+ struct st_thermal_sensor *sensor = th->devdata;
+ struct device *dev = sensor->dev;
+
+ switch (trip) {
+ case 0:
+ *type = THERMAL_TRIP_CRITICAL;
+ break;
+ default:
+ dev_err(dev, "invalid trip point\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int st_thermal_get_trip_temp(struct thermal_zone_device *th,
+ int trip, int *temp)
+{
+ struct st_thermal_sensor *sensor = th->devdata;
+ struct device *dev = sensor->dev;
+
+ switch (trip) {
+ case 0:
+ *temp = mcelsius(sensor->cdata->crit_temp);
+ break;
+ default:
+ dev_err(dev, "Invalid trip point\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops st_tz_ops = {
+ .get_temp = st_thermal_get_temp,
+ .get_trip_type = st_thermal_get_trip_type,
+ .get_trip_temp = st_thermal_get_trip_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 polling_delay;
+ 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;
+
+ polling_delay = sensor->ops->register_enable_irq ? 0 : 1000;
+
+ sensor->thermal_dev =
+ thermal_zone_device_register(dev_name(dev), 1, 0, sensor,
+ &st_tz_ops, NULL, 0, polling_delay);
+ if (IS_ERR(sensor->thermal_dev)) {
+ dev_err(dev, "failed to register thermal zone device\n");
+ ret = PTR_ERR(sensor->thermal_dev);
+ goto sensor_off;
+ }
+
+ platform_set_drvdata(pdev, sensor);
+
+ return 0;
+
+sensor_off:
+ st_thermal_sensor_off(sensor);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(st_thermal_register);
+
+int st_thermal_unregister(struct platform_device *pdev)
+{
+ struct st_thermal_sensor *sensor = platform_get_drvdata(pdev);
+
+ st_thermal_sensor_off(sensor);
+ thermal_zone_device_unregister(sensor->thermal_dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(st_thermal_unregister);
+
+#ifdef CONFIG_PM_SLEEP
+static int st_thermal_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct st_thermal_sensor *sensor = platform_get_drvdata(pdev);
+
+ return st_thermal_sensor_off(sensor);
+}
+
+static int st_thermal_resume(struct device *dev)
+{
+ int ret;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct st_thermal_sensor *sensor = platform_get_drvdata(pdev);
+
+ 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;
+}
+#endif
+
+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..fecafbe10
--- /dev/null
+++ b/drivers/thermal/st/st_thermal.h
@@ -0,0 +1,104 @@
+/*
+ * 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
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#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 int 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..91d42319d
--- /dev/null
+++ b/drivers/thermal/st/st_thermal_memmap.c
@@ -0,0 +1,209 @@
+/*
+ * ST Thermal Sensor Driver for memory mapped sensors.
+ * Author: Ajit Pal Singh <ajitpal.singh@st.com>
+ *
+ * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#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) {
+ dev_err(dev, "failed to register IRQ\n");
+ 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) {
+ dev_err(dev, "failed to register IRQ %d\n", sensor->irq);
+ 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);
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "no memory resources defined\n");
+ return -ENODEV;
+ }
+
+ sensor->mmio_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(sensor->mmio_base)) {
+ dev_err(dev, "failed to remap IO\n");
+ 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 stih416 mpe thermal sensor */
+static const struct st_thermal_compat_data st_416mpe_cdata = {
+ .reg_fields = st_mmap_thermal_regfields,
+ .ops = &st_mmap_sensor_ops,
+ .calibration_val = 14,
+ .temp_adjust_val = -95,
+ .crit_temp = 120,
+};
+
+/* Compatible device data stih407 thermal sensor */
+static const struct st_thermal_compat_data st_407_cdata = {
+ .reg_fields = st_mmap_thermal_regfields,
+ .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,stih416-mpe-thermal", .data = &st_416mpe_cdata },
+ { .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 int st_mmap_remove(struct platform_device *pdev)
+{
+ return st_thermal_unregister(pdev);
+}
+
+static struct platform_driver st_mmap_thermal_driver = {
+ .driver = {
+ .name = "st_thermal_mmap",
+ .pm = &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/st_thermal_syscfg.c b/drivers/thermal/st/st_thermal_syscfg.c
new file mode 100644
index 000000000..3df5b7890
--- /dev/null
+++ b/drivers/thermal/st/st_thermal_syscfg.c
@@ -0,0 +1,178 @@
+/*
+ * ST Thermal Sensor Driver for syscfg based sensors.
+ * Author: Ajit Pal Singh <ajitpal.singh@st.com>
+ *
+ * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+
+#include "st_thermal.h"
+
+/* STiH415 */
+#define STIH415_SYSCFG_FRONT(num) ((num - 100) * 4)
+#define STIH415_SAS_THSENS_CONF STIH415_SYSCFG_FRONT(178)
+#define STIH415_SAS_THSENS_STATUS STIH415_SYSCFG_FRONT(198)
+#define STIH415_SYSCFG_MPE(num) ((num - 600) * 4)
+#define STIH415_MPE_THSENS_CONF STIH415_SYSCFG_MPE(607)
+#define STIH415_MPE_THSENS_STATUS STIH415_SYSCFG_MPE(667)
+
+/* STiH416 */
+#define STIH416_SYSCFG_FRONT(num) ((num - 1000) * 4)
+#define STIH416_SAS_THSENS_CONF STIH416_SYSCFG_FRONT(1552)
+#define STIH416_SAS_THSENS_STATUS1 STIH416_SYSCFG_FRONT(1554)
+#define STIH416_SAS_THSENS_STATUS2 STIH416_SYSCFG_FRONT(1594)
+
+/* STiD127 */
+#define STID127_SYSCFG_CPU(num) ((num - 700) * 4)
+#define STID127_THSENS_CONF STID127_SYSCFG_CPU(743)
+#define STID127_THSENS_STATUS STID127_SYSCFG_CPU(767)
+
+static const struct reg_field st_415sas_regfields[MAX_REGFIELDS] = {
+ [TEMP_PWR] = REG_FIELD(STIH415_SAS_THSENS_CONF, 9, 9),
+ [DCORRECT] = REG_FIELD(STIH415_SAS_THSENS_CONF, 4, 8),
+ [OVERFLOW] = REG_FIELD(STIH415_SAS_THSENS_STATUS, 8, 8),
+ [DATA] = REG_FIELD(STIH415_SAS_THSENS_STATUS, 10, 16),
+};
+
+static const struct reg_field st_415mpe_regfields[MAX_REGFIELDS] = {
+ [TEMP_PWR] = REG_FIELD(STIH415_MPE_THSENS_CONF, 8, 8),
+ [DCORRECT] = REG_FIELD(STIH415_MPE_THSENS_CONF, 3, 7),
+ [OVERFLOW] = REG_FIELD(STIH415_MPE_THSENS_STATUS, 9, 9),
+ [DATA] = REG_FIELD(STIH415_MPE_THSENS_STATUS, 11, 18),
+};
+
+static const struct reg_field st_416sas_regfields[MAX_REGFIELDS] = {
+ [TEMP_PWR] = REG_FIELD(STIH416_SAS_THSENS_CONF, 9, 9),
+ [DCORRECT] = REG_FIELD(STIH416_SAS_THSENS_CONF, 4, 8),
+ [OVERFLOW] = REG_FIELD(STIH416_SAS_THSENS_STATUS1, 8, 8),
+ [DATA] = REG_FIELD(STIH416_SAS_THSENS_STATUS2, 10, 16),
+};
+
+static const struct reg_field st_127_regfields[MAX_REGFIELDS] = {
+ [TEMP_PWR] = REG_FIELD(STID127_THSENS_CONF, 7, 7),
+ [DCORRECT] = REG_FIELD(STID127_THSENS_CONF, 2, 6),
+ [OVERFLOW] = REG_FIELD(STID127_THSENS_STATUS, 9, 9),
+ [DATA] = REG_FIELD(STID127_THSENS_STATUS, 11, 18),
+};
+
+/* Private OPs for System Configuration Register based thermal sensors */
+static int st_syscfg_power_ctrl(struct st_thermal_sensor *sensor,
+ enum st_thermal_power_state power_state)
+{
+ return regmap_field_write(sensor->pwr, power_state);
+}
+
+static int st_syscfg_alloc_regfields(struct st_thermal_sensor *sensor)
+{
+ struct device *dev = sensor->dev;
+
+ sensor->pwr = devm_regmap_field_alloc(dev, sensor->regmap,
+ sensor->cdata->reg_fields[TEMP_PWR]);
+
+ if (IS_ERR(sensor->pwr)) {
+ dev_err(dev, "failed to alloc syscfg regfields\n");
+ return PTR_ERR(sensor->pwr);
+ }
+
+ return 0;
+}
+
+static int st_syscfg_regmap_init(struct st_thermal_sensor *sensor)
+{
+ sensor->regmap =
+ syscon_regmap_lookup_by_compatible(sensor->cdata->sys_compat);
+ if (IS_ERR(sensor->regmap)) {
+ dev_err(sensor->dev, "failed to find syscfg regmap\n");
+ return PTR_ERR(sensor->regmap);
+ }
+
+ return 0;
+}
+
+static const struct st_thermal_sensor_ops st_syscfg_sensor_ops = {
+ .power_ctrl = st_syscfg_power_ctrl,
+ .alloc_regfields = st_syscfg_alloc_regfields,
+ .regmap_init = st_syscfg_regmap_init,
+};
+
+/* Compatible device data for stih415 sas thermal sensor */
+static const struct st_thermal_compat_data st_415sas_cdata = {
+ .sys_compat = "st,stih415-front-syscfg",
+ .reg_fields = st_415sas_regfields,
+ .ops = &st_syscfg_sensor_ops,
+ .calibration_val = 16,
+ .temp_adjust_val = 20,
+ .crit_temp = 120,
+};
+
+/* Compatible device data for stih415 mpe thermal sensor */
+static const struct st_thermal_compat_data st_415mpe_cdata = {
+ .sys_compat = "st,stih415-system-syscfg",
+ .reg_fields = st_415mpe_regfields,
+ .ops = &st_syscfg_sensor_ops,
+ .calibration_val = 16,
+ .temp_adjust_val = -103,
+ .crit_temp = 120,
+};
+
+/* Compatible device data for stih416 sas thermal sensor */
+static const struct st_thermal_compat_data st_416sas_cdata = {
+ .sys_compat = "st,stih416-front-syscfg",
+ .reg_fields = st_416sas_regfields,
+ .ops = &st_syscfg_sensor_ops,
+ .calibration_val = 16,
+ .temp_adjust_val = 20,
+ .crit_temp = 120,
+};
+
+/* Compatible device data for stid127 thermal sensor */
+static const struct st_thermal_compat_data st_127_cdata = {
+ .sys_compat = "st,stid127-cpu-syscfg",
+ .reg_fields = st_127_regfields,
+ .ops = &st_syscfg_sensor_ops,
+ .calibration_val = 8,
+ .temp_adjust_val = -103,
+ .crit_temp = 120,
+};
+
+static const struct of_device_id st_syscfg_thermal_of_match[] = {
+ { .compatible = "st,stih415-sas-thermal", .data = &st_415sas_cdata },
+ { .compatible = "st,stih415-mpe-thermal", .data = &st_415mpe_cdata },
+ { .compatible = "st,stih416-sas-thermal", .data = &st_416sas_cdata },
+ { .compatible = "st,stid127-thermal", .data = &st_127_cdata },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, st_syscfg_thermal_of_match);
+
+static int st_syscfg_probe(struct platform_device *pdev)
+{
+ return st_thermal_register(pdev, st_syscfg_thermal_of_match);
+}
+
+static int st_syscfg_remove(struct platform_device *pdev)
+{
+ return st_thermal_unregister(pdev);
+}
+
+static struct platform_driver st_syscfg_thermal_driver = {
+ .driver = {
+ .name = "st_syscfg_thermal",
+ .pm = &st_thermal_pm_ops,
+ .of_match_table = st_syscfg_thermal_of_match,
+ },
+ .probe = st_syscfg_probe,
+ .remove = st_syscfg_remove,
+};
+module_platform_driver(st_syscfg_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/step_wise.c b/drivers/thermal/step_wise.c
new file mode 100644
index 000000000..ee047ca43
--- /dev/null
+++ b/drivers/thermal/step_wise.c
@@ -0,0 +1,230 @@
+/*
+ * step_wise.c - A step-by-step Thermal throttling governor
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/thermal.h>
+#include <trace/events/thermal.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, do nothing
+ * c. if the trend is THERMAL_TREND_RAISE_FULL, use upper limit
+ * for this trip point
+ * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit
+ * for this trip point
+ * If the temperature is lower than a trip point,
+ * a. if the trend is THERMAL_TREND_RAISING, do nothing
+ * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
+ * state for this trip point, if the cooling state already
+ * equals lower limit, deactivate the thermal instance
+ * c. if the trend is THERMAL_TREND_RAISE_FULL, do nothing
+ * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit,
+ * 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;
+ unsigned long next_target;
+
+ /*
+ * 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);
+ next_target = instance->target;
+ dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state);
+
+ if (!instance->initialized) {
+ if (throttle) {
+ next_target = (cur_state + 1) >= instance->upper ?
+ instance->upper :
+ ((cur_state + 1) < instance->lower ?
+ instance->lower : (cur_state + 1));
+ } else {
+ next_target = THERMAL_NO_TARGET;
+ }
+
+ return next_target;
+ }
+
+ switch (trend) {
+ case THERMAL_TREND_RAISING:
+ if (throttle) {
+ next_target = cur_state < instance->upper ?
+ (cur_state + 1) : instance->upper;
+ if (next_target < instance->lower)
+ next_target = instance->lower;
+ }
+ break;
+ case THERMAL_TREND_RAISE_FULL:
+ if (throttle)
+ next_target = instance->upper;
+ break;
+ case THERMAL_TREND_DROPPING:
+ if (cur_state <= instance->lower) {
+ if (!throttle)
+ next_target = THERMAL_NO_TARGET;
+ } else {
+ if (!throttle) {
+ next_target = cur_state - 1;
+ if (next_target > instance->upper)
+ next_target = instance->upper;
+ }
+ }
+ break;
+ case THERMAL_TREND_DROP_FULL:
+ if (cur_state == instance->lower) {
+ if (!throttle)
+ next_target = THERMAL_NO_TARGET;
+ } else
+ next_target = instance->lower;
+ break;
+ default:
+ break;
+ }
+
+ return next_target;
+}
+
+static void update_passive_instance(struct thermal_zone_device *tz,
+ enum thermal_trip_type type, int value)
+{
+ /*
+ * If value is +1, activate a passive instance.
+ * If value is -1, deactivate a passive instance.
+ */
+ if (type == THERMAL_TRIP_PASSIVE || type == THERMAL_TRIPS_NONE)
+ tz->passive += value;
+}
+
+static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
+{
+ int trip_temp;
+ enum thermal_trip_type trip_type;
+ enum thermal_trend trend;
+ struct thermal_instance *instance;
+ bool throttle = false;
+ int old_target;
+
+ if (trip == THERMAL_TRIPS_NONE) {
+ trip_temp = tz->forced_passive;
+ trip_type = THERMAL_TRIPS_NONE;
+ } else {
+ tz->ops->get_trip_temp(tz, trip, &trip_temp);
+ tz->ops->get_trip_type(tz, trip, &trip_type);
+ }
+
+ trend = get_tz_trend(tz, trip);
+
+ if (tz->temperature >= trip_temp) {
+ throttle = true;
+ trace_thermal_zone_trip(tz, trip, trip_type);
+ }
+
+ dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n",
+ trip, trip_type, trip_temp, trend, throttle);
+
+ mutex_lock(&tz->lock);
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+ if (instance->trip != trip)
+ continue;
+
+ old_target = instance->target;
+ instance->target = get_target_state(instance, trend, throttle);
+ dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n",
+ old_target, (int)instance->target);
+
+ if (instance->initialized && old_target == instance->target)
+ continue;
+
+ /* Activate a passive thermal instance */
+ if (old_target == THERMAL_NO_TARGET &&
+ instance->target != THERMAL_NO_TARGET)
+ update_passive_instance(tz, trip_type, 1);
+ /* Deactivate a passive thermal instance */
+ else if (old_target != THERMAL_NO_TARGET &&
+ instance->target == THERMAL_NO_TARGET)
+ update_passive_instance(tz, trip_type, -1);
+
+ instance->initialized = true;
+ mutex_lock(&instance->cdev->lock);
+ instance->cdev->updated = false; /* cdev needs update */
+ mutex_unlock(&instance->cdev->lock);
+ }
+
+ mutex_unlock(&tz->lock);
+}
+
+/**
+ * step_wise_throttle - throttles devices associated with the given zone
+ * @tz - thermal_zone_device
+ * @trip - trip point index
+ *
+ * Throttling Logic: This uses the trend of the thermal zone to throttle.
+ * If the thermal zone is 'heating up' this throttles all the cooling
+ * devices associated with the zone and its particular trip point, by one
+ * step. If the zone is 'cooling down' it brings back the performance of
+ * the devices by one step.
+ */
+static int step_wise_throttle(struct thermal_zone_device *tz, int trip)
+{
+ struct thermal_instance *instance;
+
+ thermal_zone_trip_update(tz, trip);
+
+ if (tz->forced_passive)
+ thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE);
+
+ mutex_lock(&tz->lock);
+
+ list_for_each_entry(instance, &tz->thermal_instances, tz_node)
+ thermal_cdev_update(instance->cdev);
+
+ mutex_unlock(&tz->lock);
+
+ return 0;
+}
+
+static struct thermal_governor thermal_gov_step_wise = {
+ .name = "step_wise",
+ .throttle = step_wise_throttle,
+};
+
+int thermal_gov_step_wise_register(void)
+{
+ return thermal_register_governor(&thermal_gov_step_wise);
+}
+
+void thermal_gov_step_wise_unregister(void)
+{
+ thermal_unregister_governor(&thermal_gov_step_wise);
+}
diff --git a/drivers/thermal/tango_thermal.c b/drivers/thermal/tango_thermal.c
new file mode 100644
index 000000000..4e67795cb
--- /dev/null
+++ b/drivers/thermal/tango_thermal.c
@@ -0,0 +1,125 @@
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+
+/*
+ * According to a data sheet draft, "this temperature sensor uses a bandgap
+ * type of circuit to compare a voltage which has a negative temperature
+ * coefficient with a voltage that is proportional to absolute temperature.
+ * A resistor bank allows 41 different temperature thresholds to be selected
+ * and the logic output will then indicate whether the actual die temperature
+ * lies above or below the selected threshold."
+ */
+
+#define TEMPSI_CMD 0
+#define TEMPSI_RES 4
+#define TEMPSI_CFG 8
+
+#define CMD_OFF 0
+#define CMD_ON 1
+#define CMD_READ 2
+
+#define IDX_MIN 15
+#define IDX_MAX 40
+
+struct tango_thermal_priv {
+ void __iomem *base;
+ int thresh_idx;
+};
+
+static bool temp_above_thresh(void __iomem *base, int thresh_idx)
+{
+ writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD);
+ usleep_range(10, 20);
+ writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD);
+
+ return readl(base + TEMPSI_RES);
+}
+
+static int tango_get_temp(void *arg, int *res)
+{
+ struct tango_thermal_priv *priv = arg;
+ int idx = priv->thresh_idx;
+
+ if (temp_above_thresh(priv->base, idx)) {
+ /* Search upward by incrementing thresh_idx */
+ while (idx < IDX_MAX && temp_above_thresh(priv->base, ++idx))
+ cpu_relax();
+ idx = idx - 1; /* always return lower bound */
+ } else {
+ /* Search downward by decrementing thresh_idx */
+ while (idx > IDX_MIN && !temp_above_thresh(priv->base, --idx))
+ cpu_relax();
+ }
+
+ *res = (idx * 9 / 2 - 38) * 1000; /* millidegrees Celsius */
+ priv->thresh_idx = idx;
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops ops = {
+ .get_temp = tango_get_temp,
+};
+
+static void tango_thermal_init(struct tango_thermal_priv *priv)
+{
+ writel(0, priv->base + TEMPSI_CFG);
+ writel(CMD_ON, priv->base + TEMPSI_CMD);
+}
+
+static int tango_thermal_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct tango_thermal_priv *priv;
+ struct thermal_zone_device *tzdev;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ platform_set_drvdata(pdev, priv);
+ priv->thresh_idx = IDX_MIN;
+ tango_thermal_init(priv);
+
+ tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv, &ops);
+ return PTR_ERR_OR_ZERO(tzdev);
+}
+
+static int __maybe_unused tango_thermal_resume(struct device *dev)
+{
+ tango_thermal_init(dev_get_drvdata(dev));
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(tango_thermal_pm, NULL, tango_thermal_resume);
+
+static const struct of_device_id tango_sensor_ids[] = {
+ {
+ .compatible = "sigma,smp8758-thermal",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, tango_sensor_ids);
+
+static struct platform_driver tango_thermal_driver = {
+ .probe = tango_thermal_probe,
+ .driver = {
+ .name = "tango-thermal",
+ .of_match_table = tango_sensor_ids,
+ .pm = &tango_thermal_pm,
+ },
+};
+
+module_platform_driver(tango_thermal_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sigma Designs");
+MODULE_DESCRIPTION("Tango temperature sensor");
diff --git a/drivers/thermal/tegra/Kconfig b/drivers/thermal/tegra/Kconfig
new file mode 100644
index 000000000..f8740f785
--- /dev/null
+++ b/drivers/thermal/tegra/Kconfig
@@ -0,0 +1,20 @@
+menu "NVIDIA Tegra thermal drivers"
+depends on ARCH_TEGRA
+
+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).
+
+endmenu
diff --git a/drivers/thermal/tegra/Makefile b/drivers/thermal/tegra/Makefile
new file mode 100644
index 000000000..0f2b66edf
--- /dev/null
+++ b/drivers/thermal/tegra/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_TEGRA_SOCTHERM) += tegra-soctherm.o
+obj-$(CONFIG_TEGRA_BPMP_THERMAL) += tegra-bpmp-thermal.o
+
+tegra-soctherm-y := soctherm.o soctherm-fuse.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..29963180c
--- /dev/null
+++ b/drivers/thermal/tegra/soctherm-fuse.c
@@ -0,0 +1,169 @@
+/*
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <soc/tegra/fuse.h>
+
+#include "soctherm.h"
+
+#define NOMINAL_CALIB_FT 105
+#define NOMINAL_CALIB_CP 25
+
+#define FUSE_TSENSOR_CALIB_CP_TS_BASE_MASK 0x1fff
+#define FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK (0x1fff << 13)
+#define FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT 13
+
+#define FUSE_TSENSOR_COMMON 0x180
+
+/*
+ * Tegra210: Layout of bits in FUSE_TSENSOR_COMMON:
+ * 3 2 1 0
+ * 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 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Tegra12x, etc:
+ * 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 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+#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(FUSE_TSENSOR_COMMON, &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 = sign_extend32(val, 5);
+
+ shared->actual_temp_cp = 2 * NOMINAL_CALIB_CP + shifted_cp;
+ shared->actual_temp_ft = 2 * 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..ed28110a3
--- /dev/null
+++ b/drivers/thermal/tegra/soctherm.c
@@ -0,0 +1,1463 @@
+/*
+ * Copyright (c) 2014, 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/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/thermal.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_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 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)
+
+/* 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))
+
+/* 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))
+
+static const int min_low_temp = -127000;
+static const int max_high_temp = 127000;
+
+enum soctherm_throttle_id {
+ THROTTLE_LIGHT = 0,
+ THROTTLE_HEAVY,
+ THROTTLE_SIZE,
+};
+
+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",
+};
+
+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_throt_cfg {
+ const char *name;
+ unsigned int id;
+ u8 priority;
+ u8 cpu_throt_level;
+ u32 cpu_throt_depth;
+ 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;
+
+ 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;
+};
+
+/**
+ * ccroc_writel() - writes a value to a CCROC register
+ * @ts: pointer to a struct tegra_soctherm
+ * @v: 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(void *data, int *out_temp)
+{
+ struct tegra_thermctl_zone *zone = data;
+ 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]
+ * @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_info(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 tegra_thermctl_set_trip_temp(void *data, int trip, int temp)
+{
+ struct tegra_thermctl_zone *zone = data;
+ struct thermal_zone_device *tz = zone->tz;
+ struct tegra_soctherm *ts = zone->ts;
+ const struct tegra_tsensor_group *sg = zone->sg;
+ struct device *dev = zone->dev;
+ enum thermal_trip_type type;
+ int ret;
+
+ if (!tz)
+ return -EINVAL;
+
+ ret = tz->ops->get_trip_type(tz, trip, &type);
+ if (ret)
+ return ret;
+
+ if (type == THERMAL_TRIP_CRITICAL) {
+ return thermtrip_program(dev, sg, temp);
+ } else if (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 (get_thermal_instance(tz, cdev, trip))
+ stc = find_throttle_cfg_by_name(ts, cdev->type);
+ else
+ continue;
+
+ return throttrip_program(dev, sg, stc, temp);
+ }
+ }
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops tegra_of_thermal_ops = {
+ .get_temp = tegra_thermctl_get_temp,
+ .set_trip_temp = tegra_thermctl_set_trip_temp,
+};
+
+static int get_hot_temp(struct thermal_zone_device *tz, int *trip, int *temp)
+{
+ int ntrips, i, ret;
+ enum thermal_trip_type type;
+
+ ntrips = of_thermal_get_ntrips(tz);
+ if (ntrips <= 0)
+ return -EINVAL;
+
+ for (i = 0; i < ntrips; i++) {
+ ret = tz->ops->get_trip_type(tz, i, &type);
+ if (ret)
+ return -EINVAL;
+ if (type == THERMAL_TRIP_HOT) {
+ ret = tz->ops->get_trip_temp(tz, i, temp);
+ if (!ret)
+ *trip = i;
+
+ return ret;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * tegra_soctherm_set_hwtrips() - set HW trip point from DT data
+ * @dev: struct device * of the SOC_THERM instance
+ *
+ * Configure the SOC_THERM HW trip points, setting "THERMTRIP"
+ * "THROTTLE" trip points , using "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);
+ struct soctherm_throt_cfg *stc;
+ int i, trip, temperature;
+ int ret;
+
+ ret = tz->ops->get_crit_temp(tz, &temperature);
+ if (ret) {
+ dev_warn(dev, "thermtrip: %s: missing critical temperature\n",
+ sg->name);
+ goto set_throttle;
+ }
+
+ 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);
+
+set_throttle:
+ ret = get_hot_temp(tz, &trip, &temperature);
+ if (ret) {
+ dev_warn(dev, "throttrip: %s: missing hot temperature\n",
+ sg->name);
+ return 0;
+ }
+
+ for (i = 0; i < THROTTLE_SIZE; i++) {
+ struct thermal_cooling_device *cdev;
+
+ if (!ts->throt_cfgs[i].init)
+ continue;
+
+ cdev = ts->throt_cfgs[i].cdev;
+ if (get_thermal_instance(tz, cdev, trip))
+ 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_warn(dev, "throttrip: %s: missing throttle cdev\n",
+ sg->name);
+
+ 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;
+}
+
+static int regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, regs_show, inode->i_private);
+}
+
+static const struct file_operations regs_fops = {
+ .open = regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void soctherm_debug_init(struct platform_device *pdev)
+{
+ struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
+ struct dentry *root, *file;
+
+ root = debugfs_create_dir("soctherm", NULL);
+ if (!root) {
+ dev_err(&pdev->dev, "failed to create debugfs directory\n");
+ return;
+ }
+
+ tegra->debugfs_dir = root;
+
+ file = debugfs_create_file("reg_contents", 0644, root,
+ pdev, &regs_fops);
+ if (!file) {
+ dev_err(&pdev->dev, "failed to create debugfs file\n");
+ debugfs_remove_recursive(tegra->debugfs_dir);
+ tegra->debugfs_dir = NULL;
+ }
+}
+#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 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,
+};
+
+/**
+ * soctherm_init_hw_throt_cdev() - Parse the HW throttle configurations
+ * and register them as cooling devices.
+ */
+static void soctherm_init_hw_throt_cdev(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra_soctherm *ts = dev_get_drvdata(dev);
+ struct device_node *np_stc, *np_stcc;
+ const char *name;
+ u32 val;
+ int i, r;
+
+ 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(np_stc, np_stcc) {
+ struct soctherm_throt_cfg *stc;
+ struct thermal_cooling_device *tcd;
+
+ 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;
+ }
+
+ r = of_property_read_u32(np_stcc, "nvidia,priority", &val);
+ if (r) {
+ dev_info(dev,
+ "throttle-cfg: %s: missing priority\n", name);
+ continue;
+ }
+ stc->priority = val;
+
+ if (ts->soc->use_ccroc) {
+ r = of_property_read_u32(np_stcc,
+ "nvidia,cpu-throt-level",
+ &val);
+ if (r) {
+ dev_info(dev,
+ "throttle-cfg: %s: missing cpu-throt-level\n",
+ name);
+ continue;
+ }
+ stc->cpu_throt_level = val;
+ } else {
+ r = of_property_read_u32(np_stcc,
+ "nvidia,cpu-throt-percent",
+ &val);
+ if (r) {
+ dev_info(dev,
+ "throttle-cfg: %s: missing cpu-throt-percent\n",
+ name);
+ continue;
+ }
+ stc->cpu_throt_depth = val;
+ }
+
+ tcd = thermal_of_cooling_device_register(np_stcc,
+ (char *)name, ts,
+ &throt_cooling_ops);
+ of_node_put(np_stcc);
+ 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
+ * @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
+ * @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
+ * @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));
+}
+
+/**
+ * soctherm_throttle_program() - programs pulse skippers' configuration
+ * @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;
+
+ /* Setup PSKIP parameters */
+ if (ts->soc->use_ccroc)
+ throttlectl_cpu_level_select(ts, throt);
+ else
+ throttlectl_cpu_mn(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 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_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 resource *res;
+ 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;
+
+ dev_set_drvdata(&pdev->dev, tegra);
+
+ tegra->soc = soc;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "soctherm-reg");
+ tegra->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(tegra->regs)) {
+ dev_err(&pdev->dev, "can't get soctherm registers");
+ return PTR_ERR(tegra->regs);
+ }
+
+ if (!tegra->soc->use_ccroc) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "car-reg");
+ tegra->clk_regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(tegra->clk_regs)) {
+ dev_err(&pdev->dev, "can't get car clk registers");
+ return PTR_ERR(tegra->clk_regs);
+ }
+ } else {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "ccroc-reg");
+ tegra->ccroc_regs = devm_ioremap_resource(&pdev->dev, res);
+ 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 calibaration 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;
+
+ 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) {
+ err = -ENOMEM;
+ goto disable_clocks;
+ }
+
+ 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_zone_of_sensor_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);
+ goto disable_clocks;
+ }
+
+ 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)
+ goto disable_clocks;
+ }
+
+ soctherm_debug_init(pdev);
+
+ return 0;
+
+disable_clocks:
+ soctherm_clk_enable(pdev, false);
+
+ return err;
+}
+
+static int tegra_soctherm_remove(struct platform_device *pdev)
+{
+ struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
+
+ debugfs_remove_recursive(tegra->debugfs_dir);
+
+ soctherm_clk_enable(pdev, false);
+
+ return 0;
+}
+
+static int __maybe_unused soctherm_suspend(struct device *dev)
+{
+ 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..e96ca73fd
--- /dev/null
+++ b/drivers/thermal/tegra/soctherm.h
@@ -0,0 +1,137 @@
+/*
+ * 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 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
+
+/**
+ * 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;
+ 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 tegra_soctherm_fuse {
+ u32 fuse_base_cp_mask, fuse_base_cp_shift;
+ u32 fuse_base_ft_mask, fuse_base_ft_shift;
+ u32 fuse_shift_ft_mask, fuse_shift_ft_shift;
+ u32 fuse_spare_realignment;
+};
+
+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;
+};
+
+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_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..b0980dbca
--- /dev/null
+++ b/drivers/thermal/tegra/tegra-bpmp-thermal.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2015-2017, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author:
+ * Mikko Perttunen <mperttunen@nvidia.com>
+ * Aapo Vienamo <avienamo@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/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(void *data, int *out_temp)
+{
+ struct tegra_bpmp_thermal_zone *zone = data;
+ struct mrq_thermal_host_to_bpmp_request req;
+ union mrq_thermal_bpmp_to_host_response reply;
+ struct tegra_bpmp_message msg;
+ 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;
+
+ *out_temp = reply.get_temp.temp;
+
+ return 0;
+}
+
+static int tegra_bpmp_thermal_set_trips(void *data, int low, int high)
+{
+ struct tegra_bpmp_thermal_zone *zone = data;
+ struct mrq_thermal_host_to_bpmp_request req;
+ struct tegra_bpmp_message msg;
+
+ 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);
+
+ return tegra_bpmp_transfer(zone->tegra->bpmp, &msg);
+}
+
+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;
+ int i;
+
+ req = (struct mrq_thermal_bpmp_to_host_request *)ch->ib->data;
+
+ 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;
+
+ *num_zones = reply.get_num_zones.num;
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops tegra_bpmp_of_thermal_ops = {
+ .get_temp = tegra_bpmp_thermal_get_temp,
+ .set_trips = tegra_bpmp_thermal_set_trips,
+};
+
+static int tegra_bpmp_thermal_probe(struct platform_device *pdev)
+{
+ struct tegra_bpmp *bpmp = dev_get_drvdata(pdev->dev.parent);
+ struct tegra_bpmp_thermal *tegra;
+ struct thermal_zone_device *tzd;
+ unsigned int i, max_num_zones;
+ int err;
+
+ 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);
+ if (err < 0) {
+ devm_kfree(&pdev->dev, zone);
+ continue;
+ }
+
+ tzd = devm_thermal_zone_of_sensor_register(
+ &pdev->dev, i, zone, &tegra_bpmp_of_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 int tegra_bpmp_thermal_remove(struct platform_device *pdev)
+{
+ struct tegra_bpmp_thermal *tegra = platform_get_drvdata(pdev);
+
+ tegra_bpmp_free_mrq(tegra->bpmp, MRQ_THERMAL, tegra);
+
+ return 0;
+}
+
+static const struct of_device_id tegra_bpmp_thermal_of_match[] = {
+ { .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/tegra124-soctherm.c b/drivers/thermal/tegra/tegra124-soctherm.c
new file mode 100644
index 000000000..36768630f
--- /dev/null
+++ b/drivers/thermal/tegra/tegra124-soctherm.c
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ *
+ */
+
+#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_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_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_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_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_base_ft_mask = 0x7ff << 10,
+ .fuse_base_ft_shift = 10,
+ .fuse_shift_ft_mask = 0x1f << 21,
+ .fuse_shift_ft_shift = 21,
+ .fuse_spare_realignment = 0x1fc,
+};
+
+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..97fa30501
--- /dev/null
+++ b/drivers/thermal/tegra/tegra132-soctherm.c
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ *
+ */
+
+#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_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_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_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_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_base_ft_mask = 0x7ff << 10,
+ .fuse_base_ft_shift = 10,
+ .fuse_shift_ft_mask = 0x1f << 21,
+ .fuse_shift_ft_shift = 21,
+ .fuse_spare_realignment = 0x1fc,
+};
+
+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..ad53169a8
--- /dev/null
+++ b/drivers/thermal/tegra/tegra210-soctherm.c
@@ -0,0 +1,215 @@
+/*
+ * 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.
+ *
+ */
+
+#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_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_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_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_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_base_ft_mask = 0x7ff << 21,
+ .fuse_base_ft_shift = 21,
+ .fuse_shift_ft_mask = 0x1f << 6,
+ .fuse_shift_ft_shift = 6,
+ .fuse_spare_realignment = 0,
+};
+
+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,
+};
diff --git a/drivers/thermal/thermal-generic-adc.c b/drivers/thermal/thermal-generic-adc.c
new file mode 100644
index 000000000..e22fc60ad
--- /dev/null
+++ b/drivers/thermal/thermal-generic-adc.c
@@ -0,0 +1,171 @@
+/*
+ * Generic ADC thermal driver
+ *
+ * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/iio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.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;
+
+ 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(void *data, int *temp)
+{
+ struct gadc_thermal_info *gti = data;
+ int val;
+ int ret;
+
+ ret = iio_read_channel_processed(gti->channel, &val);
+ if (ret < 0) {
+ dev_err(gti->dev, "IIO channel read failed %d\n", ret);
+ return ret;
+ }
+ *temp = gadc_thermal_adc_to_temp(gti, val);
+
+ return 0;
+}
+
+static const struct thermal_zone_of_device_ops gadc_thermal_ops = {
+ .get_temp = gadc_thermal_get_temp,
+};
+
+static int gadc_thermal_read_linear_lookup_table(struct device *dev,
+ struct gadc_thermal_info *gti)
+{
+ struct device_node *np = dev->of_node;
+ int ntable;
+ int ret;
+
+ ntable = of_property_count_elems_of_size(np, "temperature-lookup-table",
+ sizeof(u32));
+ if (ntable < 0) {
+ dev_err(dev, "Lookup table is not provided\n");
+ return ntable;
+ }
+
+ 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 gadc_thermal_info *gti;
+ int ret;
+
+ if (!pdev->dev.of_node) {
+ dev_err(&pdev->dev, "Only DT based supported\n");
+ return -ENODEV;
+ }
+
+ gti = devm_kzalloc(&pdev->dev, sizeof(*gti), GFP_KERNEL);
+ if (!gti)
+ return -ENOMEM;
+
+ ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti);
+ if (ret < 0)
+ return ret;
+
+ gti->dev = &pdev->dev;
+ platform_set_drvdata(pdev, gti);
+
+ gti->channel = devm_iio_channel_get(&pdev->dev, "sensor-channel");
+ if (IS_ERR(gti->channel)) {
+ ret = PTR_ERR(gti->channel);
+ dev_err(&pdev->dev, "IIO channel not found: %d\n", ret);
+ return ret;
+ }
+
+ gti->tz_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, gti,
+ &gadc_thermal_ops);
+ if (IS_ERR(gti->tz_dev)) {
+ ret = PTR_ERR(gti->tz_dev);
+ dev_err(&pdev->dev, "Thermal zone sensor register failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+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");
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
new file mode 100644
index 000000000..6c7825c58
--- /dev/null
+++ b/drivers/thermal/thermal_core.c
@@ -0,0 +1,1582 @@
+// 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/module.h>
+#include <linux/device.h>
+#include <linux/err.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 <net/netlink.h>
+#include <net/genetlink.h>
+#include <linux/suspend.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/thermal.h>
+
+#include "thermal_core.h"
+#include "thermal_hwmon.h"
+
+MODULE_AUTHOR("Zhang Rui");
+MODULE_DESCRIPTION("Generic thermal management sysfs support");
+MODULE_LICENSE("GPL v2");
+
+static DEFINE_IDA(thermal_tz_ida);
+static DEFINE_IDA(thermal_cdev_ida);
+
+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 DEFINE_MUTEX(poweroff_lock);
+
+static atomic_t in_suspend;
+static bool power_off_triggered;
+
+static struct thermal_governor *def_governor;
+
+/*
+ * 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;
+}
+
+int thermal_register_governor(struct thermal_governor *governor)
+{
+ int err;
+ const char *name;
+ struct thermal_zone_device *pos;
+
+ if (!governor)
+ return -EINVAL;
+
+ mutex_lock(&thermal_governor_lock);
+
+ err = -EBUSY;
+ if (!__find_governor(governor->name)) {
+ bool match_default;
+
+ err = 0;
+ list_add(&governor->governor_list, &thermal_governor_list);
+ match_default = !strncmp(governor->name,
+ DEFAULT_THERMAL_GOVERNOR,
+ THERMAL_NAME_LENGTH);
+
+ if (!def_governor && match_default)
+ def_governor = governor;
+ }
+
+ mutex_lock(&thermal_list_lock);
+
+ list_for_each_entry(pos, &thermal_tz_list, node) {
+ /*
+ * only thermal zones with specified tz->tzp->governor_name
+ * may run with tz->govenor unset
+ */
+ if (pos->governor)
+ continue;
+
+ name = pos->tzp->governor_name;
+
+ if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) {
+ int ret;
+
+ ret = thermal_set_governor(pos, governor);
+ if (ret)
+ dev_err(&pos->device,
+ "Failed to set governor %s for thermal zone %s: %d\n",
+ governor->name, pos->type, ret);
+ }
+ }
+
+ mutex_unlock(&thermal_list_lock);
+ mutex_unlock(&thermal_governor_lock);
+
+ return err;
+}
+
+void thermal_unregister_governor(struct thermal_governor *governor)
+{
+ struct thermal_zone_device *pos;
+
+ if (!governor)
+ return;
+
+ mutex_lock(&thermal_governor_lock);
+
+ if (!__find_governor(governor->name))
+ goto exit;
+
+ mutex_lock(&thermal_list_lock);
+
+ list_for_each_entry(pos, &thermal_tz_list, node) {
+ if (!strncasecmp(pos->governor->name, governor->name,
+ THERMAL_NAME_LENGTH))
+ thermal_set_governor(pos, NULL);
+ }
+
+ mutex_unlock(&thermal_list_lock);
+ list_del(&governor->governor_list);
+exit:
+ mutex_unlock(&thermal_governor_lock);
+}
+
+int thermal_zone_device_set_policy(struct thermal_zone_device *tz,
+ char *policy)
+{
+ struct thermal_governor *gov;
+ int ret = -EINVAL;
+
+ mutex_lock(&thermal_governor_lock);
+ mutex_lock(&tz->lock);
+
+ gov = __find_governor(strim(policy));
+ if (!gov)
+ goto exit;
+
+ ret = thermal_set_governor(tz, gov);
+
+exit:
+ mutex_unlock(&tz->lock);
+ mutex_unlock(&thermal_governor_lock);
+
+ return ret;
+}
+
+int thermal_build_list_of_policies(char *buf)
+{
+ struct thermal_governor *pos;
+ ssize_t count = 0;
+
+ mutex_lock(&thermal_governor_lock);
+
+ list_for_each_entry(pos, &thermal_governor_list, governor_list) {
+ count += scnprintf(buf + count, PAGE_SIZE - count, "%s ",
+ pos->name);
+ }
+ count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+ mutex_unlock(&thermal_governor_lock);
+
+ return count;
+}
+
+static int __init thermal_register_governors(void)
+{
+ int result;
+
+ result = thermal_gov_step_wise_register();
+ if (result)
+ return result;
+
+ result = thermal_gov_fair_share_register();
+ if (result)
+ return result;
+
+ result = thermal_gov_bang_bang_register();
+ if (result)
+ return result;
+
+ result = thermal_gov_user_space_register();
+ if (result)
+ return result;
+
+ return thermal_gov_power_allocator_register();
+}
+
+static void thermal_unregister_governors(void)
+{
+ thermal_gov_step_wise_unregister();
+ thermal_gov_fair_share_unregister();
+ thermal_gov_bang_bang_unregister();
+ thermal_gov_user_space_unregister();
+ thermal_gov_power_allocator_unregister();
+}
+
+/*
+ * 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,
+ int delay)
+{
+ if (delay > 1000)
+ mod_delayed_work(system_freezable_wq, &tz->poll_queue,
+ round_jiffies(msecs_to_jiffies(delay)));
+ else if (delay)
+ mod_delayed_work(system_freezable_wq, &tz->poll_queue,
+ msecs_to_jiffies(delay));
+ else
+ cancel_delayed_work(&tz->poll_queue);
+}
+
+static void monitor_thermal_zone(struct thermal_zone_device *tz)
+{
+ mutex_lock(&tz->lock);
+
+ if (tz->passive)
+ thermal_zone_device_set_polling(tz, tz->passive_delay);
+ else if (tz->polling_delay)
+ thermal_zone_device_set_polling(tz, tz->polling_delay);
+ else
+ thermal_zone_device_set_polling(tz, 0);
+
+ mutex_unlock(&tz->lock);
+}
+
+static void handle_non_critical_trips(struct thermal_zone_device *tz,
+ int trip,
+ enum thermal_trip_type trip_type)
+{
+ tz->governor ? tz->governor->throttle(tz, trip) :
+ def_governor->throttle(tz, trip);
+}
+
+/**
+ * thermal_emergency_poweroff_func - emergency poweroff work after a known delay
+ * @work: work_struct associated with the emergency poweroff function
+ *
+ * This function is called in very critical situations to force
+ * a kernel poweroff after a configurable timeout value.
+ */
+static void thermal_emergency_poweroff_func(struct work_struct *work)
+{
+ /*
+ * We have reached here after the emergency thermal shutdown
+ * Waiting period has expired. This means orderly_poweroff has
+ * not been able to shut off the system for some reason.
+ * Try to shut down the system immediately using kernel_power_off
+ * if populated
+ */
+ WARN(1, "Attempting kernel_power_off: Temperature too high\n");
+ kernel_power_off();
+
+ /*
+ * Worst of the worst case trigger emergency restart
+ */
+ WARN(1, "Attempting emergency_restart: Temperature too high\n");
+ emergency_restart();
+}
+
+static DECLARE_DELAYED_WORK(thermal_emergency_poweroff_work,
+ thermal_emergency_poweroff_func);
+
+/**
+ * thermal_emergency_poweroff - Trigger an emergency system poweroff
+ *
+ * This may be called from any critical situation to trigger a system shutdown
+ * after a known period of time. By default this is not scheduled.
+ */
+static void thermal_emergency_poweroff(void)
+{
+ int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS;
+ /*
+ * poweroff_delay_ms must be a carefully profiled positive value.
+ * Its a must for thermal_emergency_poweroff_work to be scheduled
+ */
+ if (poweroff_delay_ms <= 0)
+ return;
+ schedule_delayed_work(&thermal_emergency_poweroff_work,
+ msecs_to_jiffies(poweroff_delay_ms));
+}
+
+static void handle_critical_trips(struct thermal_zone_device *tz,
+ int trip, enum thermal_trip_type trip_type)
+{
+ int trip_temp;
+
+ tz->ops->get_trip_temp(tz, trip, &trip_temp);
+
+ /* If we have not crossed the trip_temp, we do not care. */
+ if (trip_temp <= 0 || tz->temperature < trip_temp)
+ return;
+
+ trace_thermal_zone_trip(tz, trip, trip_type);
+
+ if (tz->ops->notify)
+ tz->ops->notify(tz, trip, trip_type);
+
+ if (trip_type == THERMAL_TRIP_CRITICAL) {
+ dev_emerg(&tz->device,
+ "critical temperature reached (%d C), shutting down\n",
+ tz->temperature / 1000);
+ mutex_lock(&poweroff_lock);
+ if (!power_off_triggered) {
+ /*
+ * Queue a backup emergency shutdown in the event of
+ * orderly_poweroff failure
+ */
+ thermal_emergency_poweroff();
+ orderly_poweroff(true);
+ power_off_triggered = true;
+ }
+ mutex_unlock(&poweroff_lock);
+ }
+}
+
+static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
+{
+ enum thermal_trip_type type;
+
+ /* Ignore disabled trip points */
+ if (test_bit(trip, &tz->trips_disabled))
+ return;
+
+ tz->ops->get_trip_type(tz, trip, &type);
+
+ if (type == THERMAL_TRIP_CRITICAL || type == THERMAL_TRIP_HOT)
+ handle_critical_trips(tz, trip, type);
+ else
+ handle_non_critical_trips(tz, trip, type);
+ /*
+ * Alright, we handled this trip successfully.
+ * So, start monitoring again.
+ */
+ monitor_thermal_zone(tz);
+}
+
+static void update_temperature(struct thermal_zone_device *tz)
+{
+ int temp, ret;
+
+ ret = thermal_zone_get_temp(tz, &temp);
+ if (ret) {
+ if (ret != -EAGAIN)
+ dev_warn(&tz->device,
+ "failed to read out thermal zone (%d)\n",
+ ret);
+ return;
+ }
+
+ mutex_lock(&tz->lock);
+ tz->last_temperature = tz->temperature;
+ tz->temperature = temp;
+ mutex_unlock(&tz->lock);
+
+ trace_thermal_temperature(tz);
+ if (tz->last_temperature == THERMAL_TEMP_INVALID)
+ dev_dbg(&tz->device, "last_temperature N/A, current_temperature=%d\n",
+ tz->temperature);
+ else
+ dev_dbg(&tz->device, "last_temperature=%d, current_temperature=%d\n",
+ tz->last_temperature, tz->temperature);
+}
+
+static void thermal_zone_device_init(struct thermal_zone_device *tz)
+{
+ struct thermal_instance *pos;
+ tz->temperature = THERMAL_TEMP_INVALID;
+ tz->prev_low_trip = -INT_MAX;
+ tz->prev_high_trip = INT_MAX;
+ list_for_each_entry(pos, &tz->thermal_instances, tz_node)
+ pos->initialized = false;
+}
+
+static void thermal_zone_device_reset(struct thermal_zone_device *tz)
+{
+ tz->passive = 0;
+ thermal_zone_device_init(tz);
+}
+
+void thermal_zone_device_update(struct thermal_zone_device *tz,
+ enum thermal_notify_event event)
+{
+ int count;
+
+ if (atomic_read(&in_suspend))
+ return;
+
+ if (!tz->ops->get_temp)
+ return;
+
+ update_temperature(tz);
+
+ thermal_zone_set_trips(tz);
+
+ tz->notify_event = event;
+
+ for (count = 0; count < tz->trips; count++)
+ handle_thermal_trip(tz, count);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_update);
+
+/**
+ * thermal_notify_framework - Sensor drivers use this API to notify framework
+ * @tz: thermal zone device
+ * @trip: indicates which trip point has been crossed
+ *
+ * This function handles the trip events from sensor drivers. It starts
+ * throttling the cooling devices according to the policy configured.
+ * For CRITICAL and HOT trip points, this notifies the respective drivers,
+ * and does actual throttling for other trip points i.e ACTIVE and PASSIVE.
+ * The throttling policy is based on the configured platform data; if no
+ * platform data is provided, this uses the step_wise throttling policy.
+ */
+void thermal_notify_framework(struct thermal_zone_device *tz, int trip)
+{
+ handle_thermal_trip(tz, trip);
+}
+EXPORT_SYMBOL_GPL(thermal_notify_framework);
+
+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);
+}
+
+/*
+ * Power actor section: interface to power actors to estimate power
+ *
+ * Set of functions used to interact to cooling devices that know
+ * how to estimate their devices power consumption.
+ */
+
+/**
+ * power_actor_get_max_power() - get the maximum power that a cdev can consume
+ * @cdev: pointer to &thermal_cooling_device
+ * @tz: a valid thermal zone device pointer
+ * @max_power: pointer in which to store the maximum power
+ *
+ * Calculate the maximum power consumption in milliwats that the
+ * cooling device can currently consume and store it in @max_power.
+ *
+ * Return: 0 on success, -EINVAL if @cdev doesn't support the
+ * power_actor API or -E* on other error.
+ */
+int power_actor_get_max_power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz, u32 *max_power)
+{
+ if (!cdev_is_power_actor(cdev))
+ return -EINVAL;
+
+ return cdev->ops->state2power(cdev, tz, 0, max_power);
+}
+
+/**
+ * power_actor_get_min_power() - get the mainimum power that a cdev can consume
+ * @cdev: pointer to &thermal_cooling_device
+ * @tz: a valid thermal zone device pointer
+ * @min_power: pointer in which to store the minimum power
+ *
+ * Calculate the minimum power consumption in milliwatts that the
+ * cooling device can currently consume and store it in @min_power.
+ *
+ * Return: 0 on success, -EINVAL if @cdev doesn't support the
+ * power_actor API or -E* on other error.
+ */
+int power_actor_get_min_power(struct thermal_cooling_device *cdev,
+ struct thermal_zone_device *tz, u32 *min_power)
+{
+ unsigned long max_state;
+ int ret;
+
+ if (!cdev_is_power_actor(cdev))
+ return -EINVAL;
+
+ ret = cdev->ops->get_max_state(cdev, &max_state);
+ if (ret)
+ return ret;
+
+ return cdev->ops->state2power(cdev, tz, max_state, min_power);
+}
+
+/**
+ * power_actor_set_power() - limit the maximum power a cooling device consumes
+ * @cdev: pointer to &thermal_cooling_device
+ * @instance: thermal instance to update
+ * @power: the power in milliwatts
+ *
+ * Set the cooling device to consume at most @power milliwatts. The limit is
+ * expected to be a cap at the maximum power consumption.
+ *
+ * Return: 0 on success, -EINVAL if the cooling device does not
+ * implement the power actor API or -E* for other failures.
+ */
+int power_actor_set_power(struct thermal_cooling_device *cdev,
+ struct thermal_instance *instance, u32 power)
+{
+ unsigned long state;
+ int ret;
+
+ if (!cdev_is_power_actor(cdev))
+ return -EINVAL;
+
+ ret = cdev->ops->power2state(cdev, instance->tz, power, &state);
+ if (ret)
+ return ret;
+
+ instance->target = state;
+ mutex_lock(&cdev->lock);
+ cdev->updated = false;
+ mutex_unlock(&cdev->lock);
+ thermal_cdev_update(cdev);
+
+ return 0;
+}
+
+void thermal_zone_device_rebind_exception(struct thermal_zone_device *tz,
+ const char *cdev_type, size_t size)
+{
+ struct thermal_cooling_device *cdev = NULL;
+
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(cdev, &thermal_cdev_list, node) {
+ /* skip non matching cdevs */
+ if (strncmp(cdev_type, cdev->type, size))
+ continue;
+
+ /* re binding the exception matching the type pattern */
+ thermal_zone_bind_cooling_device(tz, THERMAL_TRIPS_NONE, cdev,
+ THERMAL_NO_LIMIT,
+ THERMAL_NO_LIMIT,
+ THERMAL_WEIGHT_DEFAULT);
+ }
+ mutex_unlock(&thermal_list_lock);
+}
+
+void thermal_zone_device_unbind_exception(struct thermal_zone_device *tz,
+ const char *cdev_type, size_t size)
+{
+ struct thermal_cooling_device *cdev = NULL;
+
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(cdev, &thermal_cdev_list, node) {
+ /* skip non matching cdevs */
+ if (strncmp(cdev_type, cdev->type, size))
+ continue;
+ /* unbinding the exception matching the type pattern */
+ thermal_zone_unbind_cooling_device(tz, THERMAL_TRIPS_NONE,
+ cdev);
+ }
+ mutex_unlock(&thermal_list_lock);
+}
+
+/*
+ * 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.
+ */
+
+/**
+ * thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone
+ * @tz: pointer to struct thermal_zone_device
+ * @trip: indicates which trip point the cooling devices is
+ * associated with in this thermal zone.
+ * @cdev: pointer to struct thermal_cooling_device
+ * @upper: the Maximum cooling state for this trip point.
+ * THERMAL_NO_LIMIT means no upper limit,
+ * and the cooling device can be in max_state.
+ * @lower: the Minimum cooling state can be used for this trip point.
+ * THERMAL_NO_LIMIT means no lower limit,
+ * and the cooling device can be in cooling state 0.
+ * @weight: The weight of the cooling device to be bound to the
+ * thermal zone. Use THERMAL_WEIGHT_DEFAULT for the
+ * default value
+ *
+ * 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.
+ */
+int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
+ int trip,
+ struct thermal_cooling_device *cdev,
+ unsigned long upper, unsigned long lower,
+ unsigned int weight)
+{
+ struct thermal_instance *dev;
+ struct thermal_instance *pos;
+ struct thermal_zone_device *pos1;
+ struct thermal_cooling_device *pos2;
+ unsigned long max_state;
+ int result, ret;
+
+ if (trip >= tz->trips || (trip < 0 && trip != THERMAL_TRIPS_NONE))
+ return -EINVAL;
+
+ list_for_each_entry(pos1, &thermal_tz_list, node) {
+ if (pos1 == tz)
+ break;
+ }
+ list_for_each_entry(pos2, &thermal_cdev_list, node) {
+ if (pos2 == cdev)
+ break;
+ }
+
+ if (tz != pos1 || cdev != pos2)
+ return -EINVAL;
+
+ ret = cdev->ops->get_max_state(cdev, &max_state);
+ if (ret)
+ return ret;
+
+ /* lower default 0, upper default max_state */
+ lower = lower == THERMAL_NO_LIMIT ? 0 : lower;
+ upper = upper == THERMAL_NO_LIMIT ? max_state : upper;
+
+ if (lower > upper || upper > max_state)
+ return -EINVAL;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+ dev->tz = tz;
+ dev->cdev = cdev;
+ dev->trip = trip;
+ dev->upper = upper;
+ dev->lower = lower;
+ dev->target = THERMAL_NO_TARGET;
+ dev->weight = weight;
+
+ result = ida_simple_get(&tz->ida, 0, 0, GFP_KERNEL);
+ if (result < 0)
+ goto free_mem;
+
+ dev->id = result;
+ sprintf(dev->name, "cdev%d", dev->id);
+ result =
+ sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name);
+ if (result)
+ goto release_ida;
+
+ sprintf(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;
+
+ sprintf(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;
+
+ mutex_lock(&tz->lock);
+ mutex_lock(&cdev->lock);
+ list_for_each_entry(pos, &tz->thermal_instances, tz_node)
+ if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
+ result = -EEXIST;
+ break;
+ }
+ if (!result) {
+ list_add_tail(&dev->tz_node, &tz->thermal_instances);
+ list_add_tail(&dev->cdev_node, &cdev->thermal_instances);
+ atomic_set(&tz->need_update, 1);
+ }
+ mutex_unlock(&cdev->lock);
+ mutex_unlock(&tz->lock);
+
+ if (!result)
+ return 0;
+
+ 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_simple_remove(&tz->ida, dev->id);
+free_mem:
+ kfree(dev);
+ return result;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_bind_cooling_device);
+
+/**
+ * thermal_zone_unbind_cooling_device() - unbind a cooling device from a
+ * thermal zone.
+ * @tz: pointer to a struct thermal_zone_device.
+ * @trip: indicates which trip point the cooling devices is
+ * associated with in this thermal zone.
+ * @cdev: pointer to a struct thermal_cooling_device.
+ *
+ * This interface function unbind a thermal cooling device from the certain
+ * trip point of a thermal zone device.
+ * This function is usually called in the thermal zone device .unbind callback.
+ *
+ * Return: 0 on success, the proper error value otherwise.
+ */
+int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
+ int trip,
+ struct thermal_cooling_device *cdev)
+{
+ struct thermal_instance *pos, *next;
+
+ mutex_lock(&tz->lock);
+ mutex_lock(&cdev->lock);
+ list_for_each_entry_safe(pos, next, &tz->thermal_instances, tz_node) {
+ if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
+ list_del(&pos->tz_node);
+ list_del(&pos->cdev_node);
+ mutex_unlock(&cdev->lock);
+ mutex_unlock(&tz->lock);
+ goto unbind;
+ }
+ }
+ mutex_unlock(&cdev->lock);
+ mutex_unlock(&tz->lock);
+
+ return -ENODEV;
+
+unbind:
+ device_remove_file(&tz->device, &pos->weight_attr);
+ device_remove_file(&tz->device, &pos->attr);
+ sysfs_remove_link(&tz->device.kobj, pos->name);
+ ida_simple_remove(&tz->ida, pos->id);
+ kfree(pos);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_unbind_cooling_device);
+
+static void thermal_release(struct device *dev)
+{
+ struct thermal_zone_device *tz;
+ struct thermal_cooling_device *cdev;
+
+ if (!strncmp(dev_name(dev), "thermal_zone",
+ sizeof("thermal_zone") - 1)) {
+ tz = to_thermal_zone(dev);
+ thermal_zone_destroy_device_groups(tz);
+ kfree(tz);
+ } else if (!strncmp(dev_name(dev), "cooling_device",
+ sizeof("cooling_device") - 1)) {
+ cdev = to_cooling_device(dev);
+ kfree(cdev);
+ }
+}
+
+static struct class thermal_class = {
+ .name = "thermal",
+ .dev_release = thermal_release,
+};
+
+static inline
+void print_bind_err_msg(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev, int ret)
+{
+ dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
+ tz->type, cdev->type, ret);
+}
+
+static void __bind(struct thermal_zone_device *tz, int mask,
+ struct thermal_cooling_device *cdev,
+ unsigned long *limits,
+ unsigned int weight)
+{
+ int i, ret;
+
+ for (i = 0; i < tz->trips; i++) {
+ if (mask & (1 << i)) {
+ unsigned long upper, lower;
+
+ upper = THERMAL_NO_LIMIT;
+ lower = THERMAL_NO_LIMIT;
+ if (limits) {
+ lower = limits[i * 2];
+ upper = limits[i * 2 + 1];
+ }
+ ret = thermal_zone_bind_cooling_device(tz, i, cdev,
+ upper, lower,
+ weight);
+ if (ret)
+ print_bind_err_msg(tz, cdev, ret);
+ }
+ }
+}
+
+static void bind_cdev(struct thermal_cooling_device *cdev)
+{
+ int i, ret;
+ const struct thermal_zone_params *tzp;
+ struct thermal_zone_device *pos = NULL;
+
+ mutex_lock(&thermal_list_lock);
+
+ list_for_each_entry(pos, &thermal_tz_list, node) {
+ if (!pos->tzp && !pos->ops->bind)
+ continue;
+
+ if (pos->ops->bind) {
+ ret = pos->ops->bind(pos, cdev);
+ if (ret)
+ print_bind_err_msg(pos, cdev, ret);
+ continue;
+ }
+
+ tzp = pos->tzp;
+ if (!tzp || !tzp->tbp)
+ continue;
+
+ for (i = 0; i < tzp->num_tbps; i++) {
+ if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
+ continue;
+ if (tzp->tbp[i].match(pos, cdev))
+ continue;
+ tzp->tbp[i].cdev = cdev;
+ __bind(pos, tzp->tbp[i].trip_mask, cdev,
+ tzp->tbp[i].binding_limits,
+ tzp->tbp[i].weight);
+ }
+ }
+
+ mutex_unlock(&thermal_list_lock);
+}
+
+/**
+ * __thermal_cooling_device_register() - register a new thermal cooling device
+ * @np: a pointer to a device tree node.
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * 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.
+ */
+static struct thermal_cooling_device *
+__thermal_cooling_device_register(struct device_node *np,
+ char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+ struct thermal_zone_device *pos = NULL;
+ int result;
+
+ if (type && strlen(type) >= THERMAL_NAME_LENGTH)
+ return ERR_PTR(-EINVAL);
+
+ if (!ops || !ops->get_max_state || !ops->get_cur_state ||
+ !ops->set_cur_state)
+ return ERR_PTR(-EINVAL);
+
+ cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
+ if (!cdev)
+ return ERR_PTR(-ENOMEM);
+
+ result = ida_simple_get(&thermal_cdev_ida, 0, 0, GFP_KERNEL);
+ if (result < 0) {
+ kfree(cdev);
+ return ERR_PTR(result);
+ }
+
+ cdev->id = result;
+ strlcpy(cdev->type, type ? : "", sizeof(cdev->type));
+ mutex_init(&cdev->lock);
+ INIT_LIST_HEAD(&cdev->thermal_instances);
+ cdev->np = np;
+ cdev->ops = ops;
+ cdev->updated = false;
+ cdev->device.class = &thermal_class;
+ cdev->devdata = devdata;
+ thermal_cooling_device_setup_sysfs(cdev);
+ dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
+ result = device_register(&cdev->device);
+ if (result) {
+ ida_simple_remove(&thermal_cdev_ida, cdev->id);
+ kfree(cdev);
+ return ERR_PTR(result);
+ }
+
+ /* Add 'this' new cdev to the global cdev list */
+ mutex_lock(&thermal_list_lock);
+ list_add(&cdev->node, &thermal_cdev_list);
+ mutex_unlock(&thermal_list_lock);
+
+ /* Update binding information for 'this' new cdev */
+ bind_cdev(cdev);
+
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(pos, &thermal_tz_list, node)
+ if (atomic_cmpxchg(&pos->need_update, 1, 0))
+ thermal_zone_device_update(pos,
+ THERMAL_EVENT_UNSPECIFIED);
+ mutex_unlock(&thermal_list_lock);
+
+ return cdev;
+}
+
+/**
+ * thermal_cooling_device_register() - register a new thermal cooling device
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+thermal_cooling_device_register(char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return __thermal_cooling_device_register(NULL, type, devdata, ops);
+}
+EXPORT_SYMBOL_GPL(thermal_cooling_device_register);
+
+/**
+ * thermal_of_cooling_device_register() - register an OF thermal cooling device
+ * @np: a pointer to a device tree node.
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This function will register a cooling device with device tree node reference.
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+thermal_of_cooling_device_register(struct device_node *np,
+ char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return __thermal_cooling_device_register(np, type, devdata, ops);
+}
+EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
+
+static void __unbind(struct thermal_zone_device *tz, int mask,
+ struct thermal_cooling_device *cdev)
+{
+ int i;
+
+ for (i = 0; i < tz->trips; i++)
+ if (mask & (1 << i))
+ thermal_zone_unbind_cooling_device(tz, i, cdev);
+}
+
+/**
+ * thermal_cooling_device_unregister - removes a thermal cooling device
+ * @cdev: the thermal cooling device to remove.
+ *
+ * thermal_cooling_device_unregister() must be called when a registered
+ * thermal cooling device is no longer needed.
+ */
+void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
+{
+ int i;
+ const struct thermal_zone_params *tzp;
+ struct thermal_zone_device *tz;
+ struct thermal_cooling_device *pos = NULL;
+
+ if (!cdev)
+ return;
+
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(pos, &thermal_cdev_list, node)
+ if (pos == cdev)
+ break;
+ if (pos != cdev) {
+ /* thermal cooling device not found */
+ mutex_unlock(&thermal_list_lock);
+ return;
+ }
+ list_del(&cdev->node);
+
+ /* Unbind all thermal zones associated with 'this' cdev */
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ if (tz->ops->unbind) {
+ tz->ops->unbind(tz, cdev);
+ continue;
+ }
+
+ if (!tz->tzp || !tz->tzp->tbp)
+ continue;
+
+ tzp = tz->tzp;
+ for (i = 0; i < tzp->num_tbps; i++) {
+ if (tzp->tbp[i].cdev == cdev) {
+ __unbind(tz, tzp->tbp[i].trip_mask, cdev);
+ tzp->tbp[i].cdev = NULL;
+ }
+ }
+ }
+
+ mutex_unlock(&thermal_list_lock);
+
+ ida_simple_remove(&thermal_cdev_ida, cdev->id);
+ device_del(&cdev->device);
+ thermal_cooling_device_destroy_sysfs(cdev);
+ put_device(&cdev->device);
+}
+EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
+
+static void bind_tz(struct thermal_zone_device *tz)
+{
+ int i, ret;
+ struct thermal_cooling_device *pos = NULL;
+ const struct thermal_zone_params *tzp = tz->tzp;
+
+ if (!tzp && !tz->ops->bind)
+ return;
+
+ mutex_lock(&thermal_list_lock);
+
+ /* If there is ops->bind, try to use ops->bind */
+ if (tz->ops->bind) {
+ list_for_each_entry(pos, &thermal_cdev_list, node) {
+ ret = tz->ops->bind(tz, pos);
+ if (ret)
+ print_bind_err_msg(tz, pos, ret);
+ }
+ goto exit;
+ }
+
+ if (!tzp || !tzp->tbp)
+ goto exit;
+
+ list_for_each_entry(pos, &thermal_cdev_list, node) {
+ for (i = 0; i < tzp->num_tbps; i++) {
+ if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
+ continue;
+ if (tzp->tbp[i].match(tz, pos))
+ continue;
+ tzp->tbp[i].cdev = pos;
+ __bind(tz, tzp->tbp[i].trip_mask, pos,
+ tzp->tbp[i].binding_limits,
+ tzp->tbp[i].weight);
+ }
+ }
+exit:
+ mutex_unlock(&thermal_list_lock);
+}
+
+/**
+ * thermal_zone_device_register() - register a new thermal zone device
+ * @type: the thermal zone device type
+ * @trips: the number of trip points the thermal zone support
+ * @mask: a bit string indicating the writeablility of trip points
+ * @devdata: private device data
+ * @ops: standard thermal zone device callbacks
+ * @tzp: thermal zone platform parameters
+ * @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(const char *type, int trips, int mask,
+ void *devdata, struct thermal_zone_device_ops *ops,
+ struct thermal_zone_params *tzp, int passive_delay,
+ int polling_delay)
+{
+ struct thermal_zone_device *tz;
+ enum thermal_trip_type trip_type;
+ int trip_temp;
+ int result;
+ int count;
+ struct thermal_governor *governor;
+
+ if (!type || strlen(type) == 0)
+ return ERR_PTR(-EINVAL);
+
+ if (type && strlen(type) >= THERMAL_NAME_LENGTH)
+ return ERR_PTR(-EINVAL);
+
+ if (trips > THERMAL_MAX_TRIPS || trips < 0 || mask >> trips)
+ return ERR_PTR(-EINVAL);
+
+ if (!ops)
+ return ERR_PTR(-EINVAL);
+
+ if (trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp))
+ return ERR_PTR(-EINVAL);
+
+ tz = kzalloc(sizeof(*tz), GFP_KERNEL);
+ if (!tz)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&tz->thermal_instances);
+ ida_init(&tz->ida);
+ mutex_init(&tz->lock);
+ result = ida_simple_get(&thermal_tz_ida, 0, 0, GFP_KERNEL);
+ if (result < 0)
+ goto free_tz;
+
+ tz->id = result;
+ strlcpy(tz->type, type, sizeof(tz->type));
+ tz->ops = ops;
+ tz->tzp = tzp;
+ tz->device.class = &thermal_class;
+ tz->devdata = devdata;
+ tz->trips = trips;
+ tz->passive_delay = passive_delay;
+ tz->polling_delay = polling_delay;
+
+ /* sys I/F */
+ /* Add nodes that are always present via .groups */
+ result = thermal_zone_create_device_groups(tz, mask);
+ if (result)
+ goto remove_id;
+
+ /* A new thermal zone needs to be updated anyway. */
+ atomic_set(&tz->need_update, 1);
+
+ dev_set_name(&tz->device, "thermal_zone%d", tz->id);
+ result = device_register(&tz->device);
+ if (result)
+ goto remove_device_groups;
+
+ for (count = 0; count < trips; count++) {
+ if (tz->ops->get_trip_type(tz, count, &trip_type))
+ set_bit(count, &tz->trips_disabled);
+ if (tz->ops->get_trip_temp(tz, count, &trip_temp))
+ set_bit(count, &tz->trips_disabled);
+ /* Check for bogus trip points */
+ if (trip_temp == 0)
+ set_bit(count, &tz->trips_disabled);
+ }
+
+ /* Update 'this' zone's governor information */
+ mutex_lock(&thermal_governor_lock);
+
+ if (tz->tzp)
+ governor = __find_governor(tz->tzp->governor_name);
+ else
+ governor = def_governor;
+
+ result = thermal_set_governor(tz, governor);
+ if (result) {
+ mutex_unlock(&thermal_governor_lock);
+ goto unregister;
+ }
+
+ mutex_unlock(&thermal_governor_lock);
+
+ if (!tz->tzp || !tz->tzp->no_hwmon) {
+ result = thermal_add_hwmon_sysfs(tz);
+ if (result)
+ goto unregister;
+ }
+
+ mutex_lock(&thermal_list_lock);
+ list_add_tail(&tz->node, &thermal_tz_list);
+ mutex_unlock(&thermal_list_lock);
+
+ /* Bind cooling devices for this zone */
+ bind_tz(tz);
+
+ INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
+
+ thermal_zone_device_reset(tz);
+ /* Update the new thermal zone and mark it as already updated. */
+ if (atomic_cmpxchg(&tz->need_update, 1, 0))
+ thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return tz;
+
+unregister:
+ ida_simple_remove(&thermal_tz_ida, tz->id);
+ device_unregister(&tz->device);
+ return ERR_PTR(result);
+
+remove_device_groups:
+ thermal_zone_destroy_device_groups(tz);
+remove_id:
+ ida_simple_remove(&thermal_tz_ida, tz->id);
+free_tz:
+ kfree(tz);
+ return ERR_PTR(result);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_device_register);
+
+/**
+ * thermal_zone_device_unregister - removes the registered thermal zone device
+ * @tz: the thermal zone device to remove
+ */
+void thermal_zone_device_unregister(struct thermal_zone_device *tz)
+{
+ int i;
+ const struct thermal_zone_params *tzp;
+ struct thermal_cooling_device *cdev;
+ struct thermal_zone_device *pos = NULL;
+
+ if (!tz)
+ return;
+
+ tzp = tz->tzp;
+
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(pos, &thermal_tz_list, node)
+ if (pos == tz)
+ break;
+ if (pos != tz) {
+ /* thermal zone device not found */
+ mutex_unlock(&thermal_list_lock);
+ return;
+ }
+ list_del(&tz->node);
+
+ /* Unbind all cdevs associated with 'this' thermal zone */
+ list_for_each_entry(cdev, &thermal_cdev_list, node) {
+ if (tz->ops->unbind) {
+ tz->ops->unbind(tz, cdev);
+ continue;
+ }
+
+ if (!tzp || !tzp->tbp)
+ break;
+
+ for (i = 0; i < tzp->num_tbps; i++) {
+ if (tzp->tbp[i].cdev == cdev) {
+ __unbind(tz, tzp->tbp[i].trip_mask, cdev);
+ tzp->tbp[i].cdev = NULL;
+ }
+ }
+ }
+
+ mutex_unlock(&thermal_list_lock);
+
+ cancel_delayed_work_sync(&tz->poll_queue);
+
+ thermal_set_governor(tz, NULL);
+
+ thermal_remove_hwmon_sysfs(tz);
+ ida_simple_remove(&thermal_tz_ida, tz->id);
+ ida_destroy(&tz->ida);
+ mutex_destroy(&tz->lock);
+ device_unregister(&tz->device);
+}
+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)
+ goto exit;
+
+ mutex_lock(&thermal_list_lock);
+ list_for_each_entry(pos, &thermal_tz_list, node)
+ if (!strncasecmp(name, pos->type, THERMAL_NAME_LENGTH)) {
+ found++;
+ ref = pos;
+ }
+ mutex_unlock(&thermal_list_lock);
+
+ /* nothing has been found, thus an error code for it */
+ if (found == 0)
+ ref = ERR_PTR(-ENODEV);
+ else if (found > 1)
+ /* Success only when an unique zone is found */
+ ref = ERR_PTR(-EEXIST);
+
+exit:
+ return ref;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
+
+#ifdef CONFIG_NET
+static const struct genl_multicast_group thermal_event_mcgrps[] = {
+ { .name = THERMAL_GENL_MCAST_GROUP_NAME, },
+};
+
+static struct genl_family thermal_event_genl_family __ro_after_init = {
+ .module = THIS_MODULE,
+ .name = THERMAL_GENL_FAMILY_NAME,
+ .version = THERMAL_GENL_VERSION,
+ .maxattr = THERMAL_GENL_ATTR_MAX,
+ .mcgrps = thermal_event_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(thermal_event_mcgrps),
+};
+
+int thermal_generate_netlink_event(struct thermal_zone_device *tz,
+ enum events event)
+{
+ struct sk_buff *skb;
+ struct nlattr *attr;
+ struct thermal_genl_event *thermal_event;
+ void *msg_header;
+ int size;
+ int result;
+ static unsigned int thermal_event_seqnum;
+
+ if (!tz)
+ return -EINVAL;
+
+ /* allocate memory */
+ size = nla_total_size(sizeof(struct thermal_genl_event)) +
+ nla_total_size(0);
+
+ skb = genlmsg_new(size, GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ /* add the genetlink message header */
+ msg_header = genlmsg_put(skb, 0, thermal_event_seqnum++,
+ &thermal_event_genl_family, 0,
+ THERMAL_GENL_CMD_EVENT);
+ if (!msg_header) {
+ nlmsg_free(skb);
+ return -ENOMEM;
+ }
+
+ /* fill the data */
+ attr = nla_reserve(skb, THERMAL_GENL_ATTR_EVENT,
+ sizeof(struct thermal_genl_event));
+
+ if (!attr) {
+ nlmsg_free(skb);
+ return -EINVAL;
+ }
+
+ thermal_event = nla_data(attr);
+ if (!thermal_event) {
+ nlmsg_free(skb);
+ return -EINVAL;
+ }
+
+ memset(thermal_event, 0, sizeof(struct thermal_genl_event));
+
+ thermal_event->orig = tz->id;
+ thermal_event->event = event;
+
+ /* send multicast genetlink message */
+ genlmsg_end(skb, msg_header);
+
+ result = genlmsg_multicast(&thermal_event_genl_family, skb, 0,
+ 0, GFP_ATOMIC);
+ if (result)
+ dev_err(&tz->device, "Failed to send netlink event:%d", result);
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(thermal_generate_netlink_event);
+
+static int __init genetlink_init(void)
+{
+ return genl_register_family(&thermal_event_genl_family);
+}
+
+static void genetlink_exit(void)
+{
+ genl_unregister_family(&thermal_event_genl_family);
+}
+#else /* !CONFIG_NET */
+static inline int genetlink_init(void) { return 0; }
+static inline void genetlink_exit(void) {}
+#endif /* !CONFIG_NET */
+
+static int thermal_pm_notify(struct notifier_block *nb,
+ unsigned long mode, void *_unused)
+{
+ struct thermal_zone_device *tz;
+
+ switch (mode) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_RESTORE_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ atomic_set(&in_suspend, 1);
+ break;
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ atomic_set(&in_suspend, 0);
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ thermal_zone_device_init(tz);
+ thermal_zone_device_update(tz,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static struct notifier_block thermal_pm_nb = {
+ .notifier_call = thermal_pm_notify,
+};
+
+static int __init thermal_init(void)
+{
+ int result;
+
+ mutex_init(&poweroff_lock);
+ result = thermal_register_governors();
+ if (result)
+ goto error;
+
+ result = class_register(&thermal_class);
+ if (result)
+ goto unregister_governors;
+
+ result = genetlink_init();
+ if (result)
+ goto unregister_class;
+
+ result = of_parse_thermal_zones();
+ if (result)
+ goto exit_netlink;
+
+ result = register_pm_notifier(&thermal_pm_nb);
+ if (result)
+ pr_warn("Thermal: Can not register suspend notifier, return %d\n",
+ result);
+
+ return 0;
+
+exit_netlink:
+ genetlink_exit();
+unregister_class:
+ class_unregister(&thermal_class);
+unregister_governors:
+ thermal_unregister_governors();
+error:
+ ida_destroy(&thermal_tz_ida);
+ ida_destroy(&thermal_cdev_ida);
+ mutex_destroy(&thermal_list_lock);
+ mutex_destroy(&thermal_governor_lock);
+ mutex_destroy(&poweroff_lock);
+ return result;
+}
+
+static void __exit thermal_exit(void)
+{
+ unregister_pm_notifier(&thermal_pm_nb);
+ of_thermal_destroy_zones();
+ genetlink_exit();
+ class_unregister(&thermal_class);
+ thermal_unregister_governors();
+ ida_destroy(&thermal_tz_ida);
+ ida_destroy(&thermal_cdev_ida);
+ mutex_destroy(&thermal_list_lock);
+ mutex_destroy(&thermal_governor_lock);
+}
+
+fs_initcall(thermal_init);
+module_exit(thermal_exit);
diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h
new file mode 100644
index 000000000..0df190ed8
--- /dev/null
+++ b/drivers/thermal/thermal_core.h
@@ -0,0 +1,144 @@
+/* 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/device.h>
+#include <linux/thermal.h>
+
+/* Initial state of a cooling device during binding */
+#define THERMAL_NO_TARGET -1UL
+
+/*
+ * 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_zone_device *tz;
+ struct thermal_cooling_device *cdev;
+ int 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 tz_node; /* node in tz->thermal_instances */
+ struct list_head cdev_node; /* node in cdev->thermal_instances */
+ unsigned int weight; /* The weight of the cooling device */
+};
+
+#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_register_governor(struct thermal_governor *);
+void thermal_unregister_governor(struct thermal_governor *);
+void thermal_zone_device_rebind_exception(struct thermal_zone_device *,
+ const char *, size_t);
+void thermal_zone_device_unbind_exception(struct thermal_zone_device *,
+ const char *, size_t);
+int thermal_zone_device_set_policy(struct thermal_zone_device *, char *);
+int thermal_build_list_of_policies(char *buf);
+
+/* sysfs I/F */
+int thermal_zone_create_device_groups(struct thermal_zone_device *, int);
+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);
+/* 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 */
+
+#ifdef CONFIG_THERMAL_GOV_STEP_WISE
+int thermal_gov_step_wise_register(void);
+void thermal_gov_step_wise_unregister(void);
+#else
+static inline int thermal_gov_step_wise_register(void) { return 0; }
+static inline void thermal_gov_step_wise_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_STEP_WISE */
+
+#ifdef CONFIG_THERMAL_GOV_FAIR_SHARE
+int thermal_gov_fair_share_register(void);
+void thermal_gov_fair_share_unregister(void);
+#else
+static inline int thermal_gov_fair_share_register(void) { return 0; }
+static inline void thermal_gov_fair_share_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_FAIR_SHARE */
+
+#ifdef CONFIG_THERMAL_GOV_BANG_BANG
+int thermal_gov_bang_bang_register(void);
+void thermal_gov_bang_bang_unregister(void);
+#else
+static inline int thermal_gov_bang_bang_register(void) { return 0; }
+static inline void thermal_gov_bang_bang_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_BANG_BANG */
+
+#ifdef CONFIG_THERMAL_GOV_USER_SPACE
+int thermal_gov_user_space_register(void);
+void thermal_gov_user_space_unregister(void);
+#else
+static inline int thermal_gov_user_space_register(void) { return 0; }
+static inline void thermal_gov_user_space_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_USER_SPACE */
+
+#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
+int thermal_gov_power_allocator_register(void);
+void thermal_gov_power_allocator_unregister(void);
+#else
+static inline int thermal_gov_power_allocator_register(void) { return 0; }
+static inline void thermal_gov_power_allocator_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_POWER_ALLOCATOR */
+
+/* device tree support */
+#ifdef CONFIG_THERMAL_OF
+int of_parse_thermal_zones(void);
+void of_thermal_destroy_zones(void);
+int of_thermal_get_ntrips(struct thermal_zone_device *);
+bool of_thermal_is_trip_valid(struct thermal_zone_device *, int);
+const struct thermal_trip *
+of_thermal_get_trip_points(struct thermal_zone_device *);
+#else
+static inline int of_parse_thermal_zones(void) { return 0; }
+static inline void of_thermal_destroy_zones(void) { }
+static inline int of_thermal_get_ntrips(struct thermal_zone_device *tz)
+{
+ return 0;
+}
+static inline bool of_thermal_is_trip_valid(struct thermal_zone_device *tz,
+ int trip)
+{
+ return false;
+}
+static inline const struct thermal_trip *
+of_thermal_get_trip_points(struct thermal_zone_device *tz)
+{
+ return NULL;
+}
+#endif
+
+#endif /* __THERMAL_CORE_H__ */
diff --git a/drivers/thermal/thermal_helpers.c b/drivers/thermal/thermal_helpers.c
new file mode 100644
index 000000000..2ba756af7
--- /dev/null
+++ b/drivers/thermal/thermal_helpers.c
@@ -0,0 +1,226 @@
+// 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/sysfs.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <trace/events/thermal.h>
+
+#include "thermal_core.h"
+
+int get_tz_trend(struct thermal_zone_device *tz, int 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;
+}
+EXPORT_SYMBOL(get_tz_trend);
+
+struct thermal_instance *
+get_thermal_instance(struct thermal_zone_device *tz,
+ struct thermal_cooling_device *cdev, int trip)
+{
+ struct thermal_instance *pos = NULL;
+ struct thermal_instance *target_instance = NULL;
+
+ mutex_lock(&tz->lock);
+ mutex_lock(&cdev->lock);
+
+ list_for_each_entry(pos, &tz->thermal_instances, tz_node) {
+ if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
+ target_instance = pos;
+ break;
+ }
+ }
+
+ mutex_unlock(&cdev->lock);
+ mutex_unlock(&tz->lock);
+
+ return target_instance;
+}
+EXPORT_SYMBOL(get_thermal_instance);
+
+/**
+ * thermal_zone_get_temp() - returns the temperature of a thermal zone
+ * @tz: a valid pointer to a struct thermal_zone_device
+ * @temp: a valid pointer to where to store the resulting temperature.
+ *
+ * When a valid thermal zone reference is passed, it will fetch its
+ * temperature and fill @temp.
+ *
+ * Return: On success returns 0, an error code otherwise
+ */
+int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ int ret = -EINVAL;
+ int count;
+ int crit_temp = INT_MAX;
+ enum thermal_trip_type type;
+
+ if (!tz || IS_ERR(tz) || !tz->ops->get_temp)
+ goto exit;
+
+ mutex_lock(&tz->lock);
+
+ ret = tz->ops->get_temp(tz, temp);
+
+ if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
+ for (count = 0; count < tz->trips; count++) {
+ ret = tz->ops->get_trip_type(tz, count, &type);
+ if (!ret && type == THERMAL_TRIP_CRITICAL) {
+ ret = tz->ops->get_trip_temp(tz, count,
+ &crit_temp);
+ 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;
+ }
+
+ mutex_unlock(&tz->lock);
+exit:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
+
+void thermal_zone_set_trips(struct thermal_zone_device *tz)
+{
+ int low = -INT_MAX;
+ int high = INT_MAX;
+ int trip_temp, hysteresis;
+ int i, ret;
+
+ mutex_lock(&tz->lock);
+
+ if (!tz->ops->set_trips || !tz->ops->get_trip_hyst)
+ goto exit;
+
+ for (i = 0; i < tz->trips; i++) {
+ int trip_low;
+
+ tz->ops->get_trip_temp(tz, i, &trip_temp);
+ tz->ops->get_trip_hyst(tz, i, &hysteresis);
+
+ trip_low = trip_temp - hysteresis;
+
+ if (trip_low < tz->temperature && trip_low > low)
+ low = trip_low;
+
+ if (trip_temp > tz->temperature && trip_temp < high)
+ high = trip_temp;
+ }
+
+ /* No need to change trip points */
+ if (tz->prev_low_trip == low && tz->prev_high_trip == high)
+ goto exit;
+
+ 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);
+
+exit:
+ mutex_unlock(&tz->lock);
+}
+EXPORT_SYMBOL_GPL(thermal_zone_set_trips);
+
+void thermal_cdev_update(struct thermal_cooling_device *cdev)
+{
+ struct thermal_instance *instance;
+ unsigned long target = 0;
+
+ mutex_lock(&cdev->lock);
+ /* cooling device is updated*/
+ if (cdev->updated) {
+ mutex_unlock(&cdev->lock);
+ return;
+ }
+
+ /* Make sure cdev enters the deepest cooling state */
+ list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
+ dev_dbg(&cdev->device, "zone%d->target=%lu\n",
+ instance->tz->id, instance->target);
+ if (instance->target == THERMAL_NO_TARGET)
+ continue;
+ if (instance->target > target)
+ target = instance->target;
+ }
+
+ if (!cdev->ops->set_cur_state(cdev, target))
+ thermal_cooling_device_stats_update(cdev, target);
+
+ cdev->updated = true;
+ mutex_unlock(&cdev->lock);
+ trace_cdev_update(cdev, target);
+ dev_dbg(&cdev->device, "set to state %lu\n", target);
+}
+EXPORT_SYMBOL(thermal_cdev_update);
+
+/**
+ * thermal_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..dd5d8ee37
--- /dev/null
+++ b/drivers/thermal/thermal_hwmon.c
@@ -0,0 +1,250 @@
+// 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/hwmon.h>
+#include <linux/thermal.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include "thermal_hwmon.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 */
+};
+
+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 sprintf(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;
+
+ ret = tz->ops->get_crit_temp(tz, &temperature);
+ if (ret)
+ return ret;
+
+ return sprintf(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];
+
+ mutex_lock(&thermal_hwmon_list_lock);
+ list_for_each_entry(hwmon, &thermal_hwmon_list, node) {
+ strcpy(type, tz->type);
+ strreplace(type, '-', '_');
+ if (!strcmp(hwmon->type, type)) {
+ mutex_unlock(&thermal_hwmon_list_lock);
+ return hwmon;
+ }
+ }
+ mutex_unlock(&thermal_hwmon_list_lock);
+
+ return NULL;
+}
+
+/* Find the temperature input matching a given thermal zone */
+static struct thermal_hwmon_temp *
+thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
+ const struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_temp *temp;
+
+ mutex_lock(&thermal_hwmon_list_lock);
+ list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
+ if (temp->tz == tz) {
+ mutex_unlock(&thermal_hwmon_list_lock);
+ return temp;
+ }
+ mutex_unlock(&thermal_hwmon_list_lock);
+
+ 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;
+
+ hwmon = thermal_hwmon_lookup_by_type(tz);
+ if (hwmon) {
+ new_hwmon_device = 0;
+ goto register_sys_interface;
+ }
+
+ hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
+ if (!hwmon)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&hwmon->tz_list);
+ strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
+ strreplace(hwmon->type, '-', '_');
+ hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type,
+ hwmon, NULL, NULL);
+ if (IS_ERR(hwmon->device)) {
+ result = PTR_ERR(hwmon->device);
+ goto free_mem;
+ }
+
+ register_sys_interface:
+ temp = kzalloc(sizeof(*temp), GFP_KERNEL);
+ 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;
+ }
+
+ mutex_lock(&thermal_hwmon_list_lock);
+ if (new_hwmon_device)
+ list_add_tail(&hwmon->node, &thermal_hwmon_list);
+ list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
+ mutex_unlock(&thermal_hwmon_list_lock);
+
+ return 0;
+
+ 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);
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs);
+
+void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_device *hwmon;
+ struct thermal_hwmon_temp *temp;
+
+ hwmon = thermal_hwmon_lookup_by_type(tz);
+ if (unlikely(!hwmon)) {
+ /* Should never happen... */
+ dev_dbg(&tz->device, "hwmon device lookup failed!\n");
+ return;
+ }
+
+ temp = thermal_hwmon_lookup_temp(hwmon, tz);
+ if (unlikely(!temp)) {
+ /* Should never happen... */
+ dev_dbg(&tz->device, "temperature input lookup failed!\n");
+ return;
+ }
+
+ device_remove_file(hwmon->device, &temp->temp_input.attr);
+ if (thermal_zone_crit_temp_valid(tz))
+ device_remove_file(hwmon->device, &temp->temp_crit.attr);
+
+ mutex_lock(&thermal_hwmon_list_lock);
+ list_del(&temp->hwmon_node);
+ kfree(temp);
+ if (!list_empty(&hwmon->tz_list)) {
+ mutex_unlock(&thermal_hwmon_list_lock);
+ return;
+ }
+ list_del(&hwmon->node);
+ mutex_unlock(&thermal_hwmon_list_lock);
+
+ hwmon_device_unregister(hwmon->device);
+ kfree(hwmon);
+}
+EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);
diff --git a/drivers/thermal/thermal_hwmon.h b/drivers/thermal/thermal_hwmon.h
new file mode 100644
index 000000000..a160b9d62
--- /dev/null
+++ b/drivers/thermal/thermal_hwmon.h
@@ -0,0 +1,34 @@
+/* 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);
+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 void
+thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+}
+#endif
+
+#endif /* __THERMAL_HWMON_H__ */
diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c
new file mode 100644
index 000000000..4dce4a8f7
--- /dev/null
+++ b/drivers/thermal/thermal_sysfs.c
@@ -0,0 +1,1007 @@
+// 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/sysfs.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/string.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 sprintf(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 ret;
+
+ return sprintf(buf, "%d\n", temperature);
+}
+
+static ssize_t
+mode_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ enum thermal_device_mode mode;
+ int result;
+
+ if (!tz->ops->get_mode)
+ return -EPERM;
+
+ result = tz->ops->get_mode(tz, &mode);
+ if (result)
+ return result;
+
+ return sprintf(buf, "%s\n", mode == THERMAL_DEVICE_ENABLED ? "enabled"
+ : "disabled");
+}
+
+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 (!tz->ops->set_mode)
+ return -EPERM;
+
+ if (!strncmp(buf, "enabled", sizeof("enabled") - 1))
+ result = tz->ops->set_mode(tz, THERMAL_DEVICE_ENABLED);
+ else if (!strncmp(buf, "disabled", sizeof("disabled") - 1))
+ result = tz->ops->set_mode(tz, THERMAL_DEVICE_DISABLED);
+ else
+ result = -EINVAL;
+
+ if (result)
+ return result;
+
+ return count;
+}
+
+static ssize_t
+trip_point_type_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ enum thermal_trip_type type;
+ int trip, result;
+
+ if (!tz->ops->get_trip_type)
+ return -EPERM;
+
+ if (sscanf(attr->attr.name, "trip_point_%d_type", &trip) != 1)
+ return -EINVAL;
+
+ result = tz->ops->get_trip_type(tz, trip, &type);
+ if (result)
+ return result;
+
+ switch (type) {
+ case THERMAL_TRIP_CRITICAL:
+ return sprintf(buf, "critical\n");
+ case THERMAL_TRIP_HOT:
+ return sprintf(buf, "hot\n");
+ case THERMAL_TRIP_PASSIVE:
+ return sprintf(buf, "passive\n");
+ case THERMAL_TRIP_ACTIVE:
+ return sprintf(buf, "active\n");
+ default:
+ return sprintf(buf, "unknown\n");
+ }
+}
+
+static ssize_t
+trip_point_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 trip, ret;
+ int temperature;
+
+ if (!tz->ops->set_trip_temp)
+ return -EPERM;
+
+ if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip) != 1)
+ return -EINVAL;
+
+ if (kstrtoint(buf, 10, &temperature))
+ return -EINVAL;
+
+ ret = tz->ops->set_trip_temp(tz, trip, temperature);
+ if (ret)
+ return ret;
+
+ thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return count;
+}
+
+static ssize_t
+trip_point_temp_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int trip, ret;
+ int temperature;
+
+ if (!tz->ops->get_trip_temp)
+ return -EPERM;
+
+ if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip) != 1)
+ return -EINVAL;
+
+ ret = tz->ops->get_trip_temp(tz, trip, &temperature);
+
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", temperature);
+}
+
+static ssize_t
+trip_point_hyst_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int trip, ret;
+ int temperature;
+
+ if (!tz->ops->set_trip_hyst)
+ return -EPERM;
+
+ if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip) != 1)
+ return -EINVAL;
+
+ if (kstrtoint(buf, 10, &temperature))
+ return -EINVAL;
+
+ /*
+ * We are not doing any check on the 'temperature' value
+ * here. The driver implementing 'set_trip_hyst' has to
+ * take care of this.
+ */
+ ret = tz->ops->set_trip_hyst(tz, trip, temperature);
+
+ if (!ret)
+ thermal_zone_set_trips(tz);
+
+ return ret ? ret : count;
+}
+
+static ssize_t
+trip_point_hyst_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int trip, ret;
+ int temperature;
+
+ if (!tz->ops->get_trip_hyst)
+ return -EPERM;
+
+ if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip) != 1)
+ return -EINVAL;
+
+ ret = tz->ops->get_trip_hyst(tz, trip, &temperature);
+
+ return ret ? ret : sprintf(buf, "%d\n", temperature);
+}
+
+static ssize_t
+passive_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ int state;
+
+ if (sscanf(buf, "%d\n", &state) != 1)
+ return -EINVAL;
+
+ /* sanity check: values below 1000 millicelcius don't make sense
+ * and can cause the system to go into a thermal heart attack
+ */
+ if (state && state < 1000)
+ return -EINVAL;
+
+ if (state && !tz->forced_passive) {
+ if (!tz->passive_delay)
+ tz->passive_delay = 1000;
+ thermal_zone_device_rebind_exception(tz, "Processor",
+ sizeof("Processor"));
+ } else if (!state && tz->forced_passive) {
+ tz->passive_delay = 0;
+ thermal_zone_device_unbind_exception(tz, "Processor",
+ sizeof("Processor"));
+ }
+
+ tz->forced_passive = state;
+
+ thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return count;
+}
+
+static ssize_t
+passive_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ return sprintf(buf, "%d\n", tz->forced_passive);
+}
+
+static ssize_t
+policy_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+ char name[THERMAL_NAME_LENGTH];
+ int ret;
+
+ snprintf(name, sizeof(name), "%s", 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 sprintf(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 ret = 0;
+ int temperature;
+
+ if (kstrtoint(buf, 10, &temperature))
+ return -EINVAL;
+
+ if (!tz->ops->set_emul_temp) {
+ mutex_lock(&tz->lock);
+ tz->emul_temperature = temperature;
+ mutex_unlock(&tz->lock);
+ } else {
+ ret = tz->ops->set_emul_temp(tz, temperature);
+ }
+
+ if (!ret)
+ thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+
+ return ret ? ret : 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 sprintf(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 sprintf(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);
+static DEVICE_ATTR_RW(passive);
+
+/* 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 struct attribute_group thermal_zone_attribute_group = {
+ .attrs = thermal_zone_dev_attrs,
+};
+
+/* We expose mode only if .get_mode is present */
+static struct attribute *thermal_zone_mode_attrs[] = {
+ &dev_attr_mode.attr,
+ NULL,
+};
+
+static umode_t thermal_zone_mode_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int attrno)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct thermal_zone_device *tz;
+
+ tz = container_of(dev, struct thermal_zone_device, device);
+
+ if (tz->ops->get_mode)
+ return attr->mode;
+
+ return 0;
+}
+
+static struct attribute_group thermal_zone_mode_attribute_group = {
+ .attrs = thermal_zone_mode_attrs,
+ .is_visible = thermal_zone_mode_is_visible,
+};
+
+/* We expose passive only if passive trips are present */
+static struct attribute *thermal_zone_passive_attrs[] = {
+ &dev_attr_passive.attr,
+ NULL,
+};
+
+static umode_t thermal_zone_passive_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int attrno)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct thermal_zone_device *tz;
+ enum thermal_trip_type trip_type;
+ int count, passive = 0;
+
+ tz = container_of(dev, struct thermal_zone_device, device);
+
+ for (count = 0; count < tz->trips && !passive; count++) {
+ tz->ops->get_trip_type(tz, count, &trip_type);
+
+ if (trip_type == THERMAL_TRIP_PASSIVE)
+ passive = 1;
+ }
+
+ if (!passive)
+ return attr->mode;
+
+ return 0;
+}
+
+static struct attribute_group thermal_zone_passive_attribute_group = {
+ .attrs = thermal_zone_passive_attrs,
+ .is_visible = thermal_zone_passive_is_visible,
+};
+
+static const struct attribute_group *thermal_zone_attribute_groups[] = {
+ &thermal_zone_attribute_group,
+ &thermal_zone_mode_attribute_group,
+ &thermal_zone_passive_attribute_group,
+ /* This is not NULL terminated as we create the group dynamically */
+};
+
+/**
+ * create_trip_attrs() - create attributes for trip points
+ * @tz: the thermal zone device
+ * @mask: Writeable trip point bitmap.
+ *
+ * helper function to instantiate sysfs entries for every trip
+ * point and its properties of a struct thermal_zone_device.
+ *
+ * Return: 0 on success, the proper error value otherwise.
+ */
+static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
+{
+ struct attribute **attrs;
+ int indx;
+
+ /* This function works only for zones with at least one trip */
+ if (tz->trips <= 0)
+ return -EINVAL;
+
+ tz->trip_type_attrs = kcalloc(tz->trips, sizeof(*tz->trip_type_attrs),
+ GFP_KERNEL);
+ if (!tz->trip_type_attrs)
+ return -ENOMEM;
+
+ tz->trip_temp_attrs = kcalloc(tz->trips, sizeof(*tz->trip_temp_attrs),
+ GFP_KERNEL);
+ if (!tz->trip_temp_attrs) {
+ kfree(tz->trip_type_attrs);
+ return -ENOMEM;
+ }
+
+ if (tz->ops->get_trip_hyst) {
+ tz->trip_hyst_attrs = kcalloc(tz->trips,
+ sizeof(*tz->trip_hyst_attrs),
+ GFP_KERNEL);
+ if (!tz->trip_hyst_attrs) {
+ kfree(tz->trip_type_attrs);
+ kfree(tz->trip_temp_attrs);
+ return -ENOMEM;
+ }
+ }
+
+ attrs = kcalloc(tz->trips * 3 + 1, sizeof(*attrs), GFP_KERNEL);
+ if (!attrs) {
+ kfree(tz->trip_type_attrs);
+ kfree(tz->trip_temp_attrs);
+ if (tz->ops->get_trip_hyst)
+ kfree(tz->trip_hyst_attrs);
+ return -ENOMEM;
+ }
+
+ for (indx = 0; indx < tz->trips; indx++) {
+ /* create trip type attribute */
+ snprintf(tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_type", indx);
+
+ sysfs_attr_init(&tz->trip_type_attrs[indx].attr.attr);
+ tz->trip_type_attrs[indx].attr.attr.name =
+ tz->trip_type_attrs[indx].name;
+ tz->trip_type_attrs[indx].attr.attr.mode = S_IRUGO;
+ tz->trip_type_attrs[indx].attr.show = trip_point_type_show;
+ attrs[indx] = &tz->trip_type_attrs[indx].attr.attr;
+
+ /* create trip temp attribute */
+ snprintf(tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_temp", indx);
+
+ sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr);
+ tz->trip_temp_attrs[indx].attr.attr.name =
+ tz->trip_temp_attrs[indx].name;
+ tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO;
+ tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show;
+ if (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) &&
+ mask & (1 << indx)) {
+ tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;
+ tz->trip_temp_attrs[indx].attr.store =
+ trip_point_temp_store;
+ }
+ attrs[indx + tz->trips] = &tz->trip_temp_attrs[indx].attr.attr;
+
+ /* create Optional trip hyst attribute */
+ if (!tz->ops->get_trip_hyst)
+ continue;
+ snprintf(tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH,
+ "trip_point_%d_hyst", indx);
+
+ sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr);
+ tz->trip_hyst_attrs[indx].attr.attr.name =
+ tz->trip_hyst_attrs[indx].name;
+ tz->trip_hyst_attrs[indx].attr.attr.mode = S_IRUGO;
+ tz->trip_hyst_attrs[indx].attr.show = trip_point_hyst_show;
+ if (tz->ops->set_trip_hyst) {
+ tz->trip_hyst_attrs[indx].attr.attr.mode |= S_IWUSR;
+ tz->trip_hyst_attrs[indx].attr.store =
+ trip_point_hyst_store;
+ }
+ attrs[indx + tz->trips * 2] =
+ &tz->trip_hyst_attrs[indx].attr.attr;
+ }
+ attrs[tz->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)
+ return;
+
+ kfree(tz->trip_type_attrs);
+ kfree(tz->trip_temp_attrs);
+ if (tz->ops->get_trip_hyst)
+ kfree(tz->trip_hyst_attrs);
+ kfree(tz->trips_attribute_group.attrs);
+}
+
+int thermal_zone_create_device_groups(struct thermal_zone_device *tz,
+ int mask)
+{
+ 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 = kcalloc(size, sizeof(*groups), GFP_KERNEL);
+ if (!groups)
+ return -ENOMEM;
+
+ for (i = 0; i < size - 2; i++)
+ groups[i] = thermal_zone_attribute_groups[i];
+
+ if (tz->trips) {
+ result = create_trip_attrs(tz, mask);
+ 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->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 sprintf(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);
+ unsigned long state;
+ int ret;
+
+ ret = cdev->ops->get_max_state(cdev, &state);
+ if (ret)
+ return ret;
+ return sprintf(buf, "%ld\n", 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 sprintf(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;
+
+ if (sscanf(buf, "%ld\n", &state) != 1)
+ return -EINVAL;
+
+ if ((long)state < 0)
+ return -EINVAL;
+
+ mutex_lock(&cdev->lock);
+
+ result = cdev->ops->set_cur_state(cdev, state);
+ if (!result)
+ thermal_cooling_device_stats_update(cdev, state);
+
+ mutex_unlock(&cdev->lock);
+ return result ? result : 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;
+ unsigned long max_states;
+ 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;
+
+ if (!stats)
+ return;
+
+ spin_lock(&stats->lock);
+
+ if (stats->state == new_state)
+ goto unlock;
+
+ update_time_in_state(stats);
+ stats->trans_table[stats->state * stats->max_states + 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 = cdev->stats;
+ int ret;
+
+ spin_lock(&stats->lock);
+ ret = sprintf(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 = cdev->stats;
+ ssize_t len = 0;
+ int i;
+
+ spin_lock(&stats->lock);
+ update_time_in_state(stats);
+
+ for (i = 0; i < stats->max_states; i++) {
+ len += sprintf(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 = cdev->stats;
+ int i, states = stats->max_states;
+
+ 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 < stats->max_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 = cdev->stats;
+ ssize_t len = 0;
+ int i, j;
+
+ len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n");
+ len += snprintf(buf + len, PAGE_SIZE - len, " : ");
+ for (i = 0; i < stats->max_states; i++) {
+ if (len >= PAGE_SIZE)
+ break;
+ len += snprintf(buf + len, PAGE_SIZE - len, "state%2u ", i);
+ }
+ if (len >= PAGE_SIZE)
+ return PAGE_SIZE;
+
+ len += snprintf(buf + len, PAGE_SIZE - len, "\n");
+
+ for (i = 0; i < stats->max_states; i++) {
+ if (len >= PAGE_SIZE)
+ break;
+
+ len += snprintf(buf + len, PAGE_SIZE - len, "state%2u:", i);
+
+ for (j = 0; j < stats->max_states; j++) {
+ if (len >= PAGE_SIZE)
+ break;
+ len += snprintf(buf + len, PAGE_SIZE - len, "%8u ",
+ stats->trans_table[i * stats->max_states + j]);
+ }
+ if (len >= PAGE_SIZE)
+ break;
+ len += snprintf(buf + len, PAGE_SIZE - len, "\n");
+ }
+
+ if (len >= PAGE_SIZE) {
+ pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n");
+ return -EFBIG;
+ }
+ return len;
+}
+
+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)
+{
+ struct cooling_dev_stats *stats;
+ unsigned long states;
+ int var;
+
+ if (cdev->ops->get_max_state(cdev, &states))
+ return;
+
+ states++; /* Total number of states is highest state + 1 */
+
+ var = sizeof(*stats);
+ var += sizeof(*stats->time_in_state) * states;
+ var += sizeof(*stats->trans_table) * states * states;
+
+ stats = kzalloc(var, GFP_KERNEL);
+ if (!stats)
+ return;
+
+ 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();
+ stats->max_states = states;
+
+ spin_lock_init(&stats->lock);
+
+ /* Fill the empty slot left in cooling_device_attr_groups */
+ var = ARRAY_SIZE(cooling_device_attr_groups) - 2;
+ cooling_device_attr_groups[var] = &cooling_device_stats_attr_group;
+}
+
+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);
+}
+
+/* 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_instance *instance;
+
+ instance =
+ container_of(attr, struct thermal_instance, attr);
+
+ if (instance->trip == THERMAL_TRIPS_NONE)
+ return sprintf(buf, "-1\n");
+ else
+ return sprintf(buf, "%d\n", instance->trip);
+}
+
+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 sprintf(buf, "%d\n", instance->weight);
+}
+
+ssize_t weight_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ 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);
+ instance->weight = weight;
+
+ return count;
+}
diff --git a/drivers/thermal/ti-soc-thermal/Kconfig b/drivers/thermal/ti-soc-thermal/Kconfig
new file mode 100644
index 000000000..fe0e877f8
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/Kconfig
@@ -0,0 +1,72 @@
+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..9490cd63f
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/dra752-bandgap.h
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#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 bellow 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 bellow 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..33a3030aa
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c
@@ -0,0 +1,381 @@
+/*
+ * 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.
+ *
+ * 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 "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..f5366807d
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c
@@ -0,0 +1,166 @@
+/*
+ * OMAP3 thermal driver.
+ *
+ * Copyright (C) 2011-2012 Texas Instruments Inc.
+ * Copyright (C) 2014 Pavel Machek <pavel@ucw.cz>
+ *
+ * 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.
+ *
+ * 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..0b9f835d9
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c
@@ -0,0 +1,252 @@
+/*
+ * OMAP4 thermal driver.
+ *
+ * Copyright (C) 2011-2012 Texas Instruments Inc.
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.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 "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_SINGLE_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,
+ .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_SINGLE_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..8a081abce
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap4xxx-bandgap.h
@@ -0,0 +1,169 @@
+/*
+ * OMAP4xxx bandgap registers, bitfields and temperature definitions
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#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 bellow 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_SINGLE_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 bellow 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 bellow 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_SINGLE_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 bellow 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..8191bae83
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c
@@ -0,0 +1,312 @@
+/*
+ * OMAP5 thermal driver.
+ *
+ * Copyright (C) 2011-2012 Texas Instruments Inc.
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.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 "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..9096403b7
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/omap5xxx-bandgap.h
@@ -0,0 +1,159 @@
+/*
+ * OMAP5xxx bandgap registers, bitfields and temperature definitions
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#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 bellow 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 bellow 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..097328d8e
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c
@@ -0,0 +1,1267 @@
+/*
+ * 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>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/reboot.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+
+#include "ti-bandgap.h"
+
+static int ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id);
+
+/*** 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
+ * 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 (!bgp || IS_ERR(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)
+{
+ u32 counter = 1000;
+ struct temp_sensor_registers *tsr;
+
+ /* Select single conversion mode */
+ if (TI_BANDGAP_HAS(bgp, MODE_CONFIG))
+ RMW_BITS(bgp, id, bgap_mode_ctrl, mode_ctrl_mask, 0);
+
+ /* Start of Conversion = 1 */
+ RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 1);
+
+ /* Wait for EOCZ going up */
+ tsr = bgp->conf->sensors[id].registers;
+
+ while (--counter) {
+ if (ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) &
+ tsr->bgap_eocz_mask)
+ break;
+ }
+
+ /* Start of Conversion = 0 */
+ RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 0);
+
+ /* Wait for EOCZ going down */
+ counter = 1000;
+ while (--counter) {
+ if (!(ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) &
+ tsr->bgap_eocz_mask))
+ break;
+ }
+
+ 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 gpio_nr = bgp->tshut_gpio;
+ int status;
+
+ /* Request for gpio_86 line */
+ status = gpio_request(gpio_nr, "tshut");
+ if (status < 0) {
+ dev_err(bgp->dev, "Could not request for TSHUT GPIO:%i\n", 86);
+ return status;
+ }
+ status = gpio_direction_input(gpio_nr);
+ if (status) {
+ dev_err(bgp->dev, "Cannot set input TSHUT GPIO %d\n", gpio_nr);
+ return status;
+ }
+
+ status = request_irq(gpio_to_irq(gpio_nr), ti_bandgap_tshut_irq_handler,
+ IRQF_TRIGGER_RISING, "tshut", NULL);
+ if (status) {
+ gpio_free(gpio_nr);
+ dev_err(bgp->dev, "request irq failed for TSHUT");
+ }
+
+ return 0;
+}
+
+/**
+ * ti_bandgap_alert_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) {
+ dev_err(&pdev->dev, "get_irq failed\n");
+ 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_gpio = of_get_gpio(node, 0);
+ if (!gpio_is_valid(bgp->tshut_gpio)) {
+ dev_err(&pdev->dev, "invalid gpio for tshut (%d)\n",
+ bgp->tshut_gpio);
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
+ return bgp;
+}
+
+/*** 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;
+ }
+ }
+
+ 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(gpio_to_irq(bgp->tshut_gpio), NULL);
+ gpio_free(bgp->tshut_gpio);
+ }
+
+ return ret;
+}
+
+static
+int ti_bandgap_remove(struct platform_device *pdev)
+{
+ struct ti_bandgap *bgp = platform_get_drvdata(pdev);
+ int i;
+
+ /* First thing is to remove sensor interfaces */
+ 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(gpio_to_irq(bgp->tshut_gpio), NULL);
+ gpio_free(bgp->tshut_gpio);
+ }
+
+ return 0;
+}
+
+#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;
+ u32 val = 0;
+
+ rval = &bgp->regval[i];
+ tsr = bgp->conf->sensors[i].registers;
+
+ if (TI_BANDGAP_HAS(bgp, COUNTER))
+ val = ti_bandgap_readl(bgp, tsr->bgap_counter);
+
+ if (TI_BANDGAP_HAS(bgp, TSHUT_CONFIG))
+ ti_bandgap_writel(bgp, rval->tshut_threshold,
+ tsr->tshut_threshold);
+ /* 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);
+
+ return err;
+}
+
+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);
+
+ 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..68d39ad43
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.h
@@ -0,0 +1,386 @@
+/*
+ * OMAP4 Bandgap temperature sensor driver
+ *
+ * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#ifndef __TI_BANDGAP_H
+#define __TI_BANDGAP_H
+
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/err.h>
+
+/**
+ * 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;
+ int tshut_gpio;
+ u32 clk_rate;
+};
+
+/**
+ * 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_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_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_thot(struct ti_bandgap *bgp, int id, int *thot);
+int ti_bandgap_write_thot(struct ti_bandgap *bgp, int id, int val);
+int ti_bandgap_read_tcold(struct ti_bandgap *bgp, int id, int *tcold);
+int ti_bandgap_write_tcold(struct ti_bandgap *bgp, int id, int val);
+int ti_bandgap_read_update_interval(struct ti_bandgap *bgp, int id,
+ int *interval);
+int ti_bandgap_write_update_interval(struct ti_bandgap *bgp, int id,
+ 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..343da0031
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
@@ -0,0 +1,286 @@
+/*
+ * OMAP thermal driver interface
+ *
+ * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#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"
+
+/* 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->ti_thermal->device, "updated thermal zone %s\n",
+ data->ti_thermal->type);
+}
+
+/**
+ * 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(void *devdata, int *temp)
+{
+ struct thermal_zone_device *pcb_tz = NULL;
+ struct ti_thermal_data *data = devdata;
+ 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(data->ti_thermal);
+ constant = thermal_zone_get_offset(data->ti_thermal);
+
+ 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 inline int ti_thermal_get_temp(struct thermal_zone_device *thermal,
+ int *temp)
+{
+ struct ti_thermal_data *data = thermal->devdata;
+
+ return __ti_thermal_get_temp(data, temp);
+}
+
+static int __ti_thermal_get_trend(void *p, int trip, enum thermal_trend *trend)
+{
+ struct ti_thermal_data *data = p;
+ struct ti_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_of_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_zone_of_sensor_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,
+ data->ti_thermal->polling_delay);
+
+ 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_find_property(np, "#thermal-sensor-cells", NULL))
+ 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..8e85ca973
--- /dev/null
+++ b/drivers/thermal/ti-soc-thermal/ti-thermal.h
@@ -0,0 +1,107 @@
+/*
+ * OMAP thermal definitions
+ *
+ * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
+ * Contact:
+ * Eduardo Valentin <eduardo.valentin@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#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
+
+/* helper macros */
+/**
+ * ti_thermal_get_trip_value - returns trip temperature based on index
+ * @i: trip index
+ */
+#define ti_thermal_get_trip_value(i) \
+ (OMAP_TRIP_HOT + ((i) * OMAP_TRIP_STEP))
+
+/**
+ * ti_thermal_is_valid_trip - check for trip index
+ * @i: trip index
+ */
+#define ti_thermal_is_valid_trip(trip) \
+ ((trip) >= 0 && (trip) < OMAP_TRIP_NUMBER)
+
+#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..55477d74d
--- /dev/null
+++ b/drivers/thermal/uniphier_thermal.c
@@ -0,0 +1,388 @@
+/**
+ * uniphier_thermal.c - Socionext UniPhier thermal driver
+ *
+ * Copyright 2014 Panasonic Corporation
+ * Copyright 2016-2017 Socionext Inc.
+ * All rights reserved.
+ *
+ * Author:
+ * Kunihiko Hayashi <hayashi.kunihiko@socionext.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the 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.
+ */
+
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+#include "thermal_core.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(void *data, int *out_temp)
+{
+ struct uniphier_tm_dev *tdev = data;
+ 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_of_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;
+}
+
+static int uniphier_tm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+ struct device_node *parent;
+ struct uniphier_tm_dev *tdev;
+ const struct thermal_trip *trips;
+ int i, ret, irq, ntrips, crit_temp = INT_MAX;
+
+ 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_zone_of_sensor_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);
+ }
+
+ /* get trip points */
+ trips = of_thermal_get_trip_points(tdev->tz_dev);
+ ntrips = of_thermal_get_ntrips(tdev->tz_dev);
+ if (ntrips > ALERT_CH_NUM) {
+ dev_err(dev, "thermal zone has too many trips\n");
+ return -E2BIG;
+ }
+
+ /* set alert temperatures */
+ for (i = 0; i < ntrips; i++) {
+ if (trips[i].type == THERMAL_TRIP_CRITICAL &&
+ trips[i].temperature < crit_temp)
+ crit_temp = trips[i].temperature;
+ uniphier_tm_set_alert(tdev, i, trips[i].temperature);
+ tdev->alert_en[i] = true;
+ }
+ if (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 int uniphier_tm_remove(struct platform_device *pdev)
+{
+ struct uniphier_tm_dev *tdev = platform_get_drvdata(pdev);
+
+ /* disable sensor */
+ uniphier_tm_disable_sensor(tdev);
+
+ return 0;
+}
+
+static const struct uniphier_tm_soc_data uniphier_pxs2_tm_data = {
+ .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,
+ },
+ { /* 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");
diff --git a/drivers/thermal/user_space.c b/drivers/thermal/user_space.c
new file mode 100644
index 000000000..8e92a06ef
--- /dev/null
+++ b/drivers/thermal/user_space.c
@@ -0,0 +1,69 @@
+/*
+ * user_space.c - A simple user space Thermal events notifier
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/thermal.h>
+#include <linux/slab.h>
+
+#include "thermal_core.h"
+
+/**
+ * notify_user_space - Notifies user space about thermal events
+ * @tz - thermal_zone_device
+ * @trip - trip point index
+ *
+ * This function notifies the user space through UEvents.
+ */
+static int notify_user_space(struct thermal_zone_device *tz, int trip)
+{
+ char *thermal_prop[5];
+ int i;
+
+ mutex_lock(&tz->lock);
+ thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", tz->type);
+ thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", tz->temperature);
+ thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=%d", trip);
+ 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]);
+ mutex_unlock(&tz->lock);
+ return 0;
+}
+
+static struct thermal_governor thermal_gov_user_space = {
+ .name = "user_space",
+ .throttle = notify_user_space,
+};
+
+int thermal_gov_user_space_register(void)
+{
+ return thermal_register_governor(&thermal_gov_user_space);
+}
+
+void thermal_gov_user_space_unregister(void)
+{
+ thermal_unregister_governor(&thermal_gov_user_space);
+}
+
diff --git a/drivers/thermal/x86_pkg_temp_thermal.c b/drivers/thermal/x86_pkg_temp_thermal.c
new file mode 100644
index 000000000..1ef937d79
--- /dev/null
+++ b/drivers/thermal/x86_pkg_temp_thermal.c
@@ -0,0 +1,558 @@
+/*
+ * x86_pkg_temp_thermal driver
+ * Copyright (c) 2013, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.
+ *
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.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/mce.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 pkg_device {
+ int cpu;
+ bool work_scheduled;
+ u32 tj_max;
+ u32 msr_pkg_therm_low;
+ u32 msr_pkg_therm_high;
+ 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 package pointers we allocated in init() */
+static int max_packages __read_mostly;
+/* Array of package pointers */
+static struct pkg_device **packages;
+/* Serializes interrupt notification, work and hotplug */
+static DEFINE_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 int pkg_temp_debugfs_init(void)
+{
+ struct dentry *d;
+
+ debugfs = debugfs_create_dir("pkg_temp_thermal", NULL);
+ if (!debugfs)
+ return -ENOENT;
+
+ d = debugfs_create_u32("pkg_thres_interrupt", S_IRUGO, debugfs,
+ &pkg_interrupt_cnt);
+ if (!d)
+ goto err_out;
+
+ d = debugfs_create_u32("pkg_thres_work", S_IRUGO, debugfs,
+ &pkg_work_cnt);
+ if (!d)
+ goto err_out;
+
+ return 0;
+
+err_out:
+ debugfs_remove_recursive(debugfs);
+ return -ENOENT;
+}
+
+/*
+ * Protection:
+ *
+ * - cpu hotplug: Read serialized by cpu hotplug lock
+ * Write must hold pkg_temp_lock
+ *
+ * - Other callsites: Must hold pkg_temp_lock
+ */
+static struct pkg_device *pkg_temp_thermal_get_dev(unsigned int cpu)
+{
+ int pkgid = topology_logical_package_id(cpu);
+
+ if (pkgid >= 0 && pkgid < max_packages)
+ return packages[pkgid];
+ return NULL;
+}
+
+/*
+* tj-max is is interesting because threshold is set relative to this
+* temperature.
+*/
+static int get_tj_max(int cpu, u32 *tj_max)
+{
+ u32 eax, edx, val;
+ int err;
+
+ err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
+ if (err)
+ return err;
+
+ val = (eax >> 16) & 0xff;
+ *tj_max = val * 1000;
+
+ return val ? 0 : -EINVAL;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ struct pkg_device *pkgdev = tzd->devdata;
+ u32 eax, edx;
+
+ rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_STATUS, &eax, &edx);
+ if (eax & 0x80000000) {
+ *temp = pkgdev->tj_max - ((eax >> 16) & 0x7f) * 1000;
+ pr_debug("sys_get_curr_temp %d\n", *temp);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int sys_get_trip_temp(struct thermal_zone_device *tzd,
+ int trip, int *temp)
+{
+ struct pkg_device *pkgdev = tzd->devdata;
+ unsigned long thres_reg_value;
+ u32 mask, shift, eax, edx;
+ int ret;
+
+ if (trip >= MAX_NUMBER_OF_TRIPS)
+ return -EINVAL;
+
+ if (trip) {
+ mask = THERM_MASK_THRESHOLD1;
+ shift = THERM_SHIFT_THRESHOLD1;
+ } else {
+ mask = THERM_MASK_THRESHOLD0;
+ shift = THERM_SHIFT_THRESHOLD0;
+ }
+
+ ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
+ &eax, &edx);
+ if (ret < 0)
+ return ret;
+
+ thres_reg_value = (eax & mask) >> shift;
+ if (thres_reg_value)
+ *temp = pkgdev->tj_max - thres_reg_value * 1000;
+ else
+ *temp = 0;
+ pr_debug("sys_get_trip_temp %d\n", *temp);
+
+ return 0;
+}
+
+static int
+sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
+{
+ struct pkg_device *pkgdev = tzd->devdata;
+ u32 l, h, mask, shift, intr;
+ int ret;
+
+ if (trip >= MAX_NUMBER_OF_TRIPS || temp >= pkgdev->tj_max)
+ return -EINVAL;
+
+ ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
+ &l, &h);
+ if (ret < 0)
+ return ret;
+
+ if (trip) {
+ 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;
+ }
+ l &= ~mask;
+ /*
+ * When users space sets a trip temperature == 0, which is indication
+ * that, it is no longer interested in receiving notifications.
+ */
+ if (!temp) {
+ l &= ~intr;
+ } else {
+ l |= (pkgdev->tj_max - temp)/1000 << shift;
+ l |= intr;
+ }
+
+ return wrmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
+}
+
+static int sys_get_trip_type(struct thermal_zone_device *thermal, int trip,
+ enum thermal_trip_type *type)
+{
+ *type = THERMAL_TRIP_PASSIVE;
+ return 0;
+}
+
+/* Thermal zone callback registry */
+static struct thermal_zone_device_ops tzone_ops = {
+ .get_temp = sys_get_curr_temp,
+ .get_trip_temp = sys_get_trip_temp,
+ .get_trip_type = sys_get_trip_type,
+ .set_trip_temp = sys_set_trip_temp,
+};
+
+static 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;
+ u32 l, h;
+
+ rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
+ /* only enable/disable if it had valid threshold value */
+ thres_0 = (l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0;
+ thres_1 = (l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1;
+ if (thres_0)
+ l |= THERM_INT_THRESHOLD0_ENABLE;
+ if (thres_1)
+ l |= THERM_INT_THRESHOLD1_ENABLE;
+ wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
+}
+
+/* Disable threshold interrupt on local package/cpu */
+static inline void disable_pkg_thres_interrupt(void)
+{
+ u32 l, h;
+
+ rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
+
+ l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE);
+ wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
+}
+
+static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work)
+{
+ struct thermal_zone_device *tzone = NULL;
+ int cpu = smp_processor_id();
+ struct pkg_device *pkgdev;
+ u64 msr_val, wr_val;
+
+ mutex_lock(&thermal_zone_mutex);
+ spin_lock_irq(&pkg_temp_lock);
+ ++pkg_work_cnt;
+
+ pkgdev = pkg_temp_thermal_get_dev(cpu);
+ if (!pkgdev) {
+ spin_unlock_irq(&pkg_temp_lock);
+ mutex_unlock(&thermal_zone_mutex);
+ return;
+ }
+ pkgdev->work_scheduled = false;
+
+ rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
+ wr_val = msr_val & ~(THERM_LOG_THRESHOLD0 | THERM_LOG_THRESHOLD1);
+ if (wr_val != msr_val) {
+ wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, wr_val);
+ tzone = pkgdev->tzone;
+ }
+
+ enable_pkg_thres_interrupt();
+ 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 pkg_device *pkgdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pkg_temp_lock, flags);
+ ++pkg_interrupt_cnt;
+
+ disable_pkg_thres_interrupt();
+
+ /* Work is per package, so scheduling it once is enough. */
+ pkgdev = pkg_temp_thermal_get_dev(cpu);
+ if (pkgdev && !pkgdev->work_scheduled) {
+ pkgdev->work_scheduled = true;
+ pkg_thermal_schedule_work(pkgdev->cpu, &pkgdev->work);
+ }
+
+ spin_unlock_irqrestore(&pkg_temp_lock, flags);
+ return 0;
+}
+
+static int pkg_temp_thermal_device_add(unsigned int cpu)
+{
+ int pkgid = topology_logical_package_id(cpu);
+ u32 tj_max, eax, ebx, ecx, edx;
+ struct pkg_device *pkgdev;
+ int thres_count, err;
+
+ if (pkgid >= max_packages)
+ 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);
+
+ err = get_tj_max(cpu, &tj_max);
+ if (err)
+ return err;
+
+ pkgdev = kzalloc(sizeof(*pkgdev), GFP_KERNEL);
+ if (!pkgdev)
+ return -ENOMEM;
+
+ INIT_DELAYED_WORK(&pkgdev->work, pkg_temp_thermal_threshold_work_fn);
+ pkgdev->cpu = cpu;
+ pkgdev->tj_max = tj_max;
+ pkgdev->tzone = thermal_zone_device_register("x86_pkg_temp",
+ thres_count,
+ (thres_count == MAX_NUMBER_OF_TRIPS) ? 0x03 : 0x01,
+ pkgdev, &tzone_ops, &pkg_temp_tz_params, 0, 0);
+ if (IS_ERR(pkgdev->tzone)) {
+ err = PTR_ERR(pkgdev->tzone);
+ kfree(pkgdev);
+ return err;
+ }
+ /* Store MSR value for package thermal interrupt, to restore at exit */
+ rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, pkgdev->msr_pkg_therm_low,
+ pkgdev->msr_pkg_therm_high);
+
+ cpumask_set_cpu(cpu, &pkgdev->cpumask);
+ spin_lock_irq(&pkg_temp_lock);
+ packages[pkgid] = pkgdev;
+ spin_unlock_irq(&pkg_temp_lock);
+ return 0;
+}
+
+static int pkg_thermal_cpu_offline(unsigned int cpu)
+{
+ struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu);
+ bool lastcpu, was_target;
+ int target;
+
+ if (!pkgdev)
+ return 0;
+
+ target = cpumask_any_but(&pkgdev->cpumask, cpu);
+ cpumask_clear_cpu(cpu, &pkgdev->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 = pkgdev->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);
+ pkgdev->tzone = NULL;
+ mutex_unlock(&thermal_zone_mutex);
+
+ thermal_zone_device_unregister(tzone);
+ }
+
+ /* Protect against work and interrupts */
+ 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 = pkgdev->cpu == cpu;
+ pkgdev->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) {
+ packages[topology_logical_package_id(cpu)] = NULL;
+ /* After this point nothing touches the MSR anymore. */
+ wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
+ pkgdev->msr_pkg_therm_low, pkgdev->msr_pkg_therm_high);
+ }
+
+ /*
+ * Check whether there is work scheduled and whether the work is
+ * targeted at the outgoing CPU.
+ */
+ if (pkgdev->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.
+ */
+ spin_unlock_irq(&pkg_temp_lock);
+ cancel_delayed_work_sync(&pkgdev->work);
+ 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 && pkgdev->work_scheduled)
+ pkg_thermal_schedule_work(target, &pkgdev->work);
+ }
+
+ spin_unlock_irq(&pkg_temp_lock);
+
+ /* Final cleanup if this is the last cpu */
+ if (lastcpu)
+ kfree(pkgdev);
+ return 0;
+}
+
+static int pkg_thermal_cpu_online(unsigned int cpu)
+{
+ struct pkg_device *pkgdev = 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 (pkgdev) {
+ cpumask_set_cpu(cpu, &pkgdev->cpumask);
+ return 0;
+ }
+ return pkg_temp_thermal_device_add(cpu);
+}
+
+static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = {
+ { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_PTS },
+ {}
+};
+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_packages = topology_max_packages();
+ packages = kcalloc(max_packages, sizeof(struct pkg_device *),
+ GFP_KERNEL);
+ if (!packages)
+ 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(packages);
+ 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(packages);
+}
+module_exit(pkg_temp_thermal_exit)
+
+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/zx2967_thermal.c b/drivers/thermal/zx2967_thermal.c
new file mode 100644
index 000000000..6acce0bce
--- /dev/null
+++ b/drivers/thermal/zx2967_thermal.c
@@ -0,0 +1,258 @@
+/*
+ * ZTE's zx2967 family thermal sensor driver
+ *
+ * Copyright (C) 2017 ZTE Ltd.
+ *
+ * Author: Baoyou Xie <baoyou.xie@linaro.org>
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+
+/* Power Mode: 0->low 1->high */
+#define ZX2967_THERMAL_POWER_MODE 0
+#define ZX2967_POWER_MODE_LOW 0
+#define ZX2967_POWER_MODE_HIGH 1
+
+/* DCF Control Register */
+#define ZX2967_THERMAL_DCF 0x4
+#define ZX2967_DCF_EN BIT(1)
+#define ZX2967_DCF_FREEZE BIT(0)
+
+/* Selection Register */
+#define ZX2967_THERMAL_SEL 0x8
+
+/* Control Register */
+#define ZX2967_THERMAL_CTRL 0x10
+
+#define ZX2967_THERMAL_READY BIT(12)
+#define ZX2967_THERMAL_TEMP_MASK GENMASK(11, 0)
+#define ZX2967_THERMAL_ID_MASK 0x18
+#define ZX2967_THERMAL_ID 0x10
+
+#define ZX2967_GET_TEMP_TIMEOUT_US (100 * 1024)
+
+/**
+ * struct zx2967_thermal_priv - zx2967 thermal sensor private structure
+ * @tzd: struct thermal_zone_device where the sensor is registered
+ * @lock: prevents read sensor in parallel
+ * @clk_topcrm: topcrm clk structure
+ * @clk_apb: apb clk structure
+ * @regs: pointer to base address of the thermal sensor
+ */
+
+struct zx2967_thermal_priv {
+ struct thermal_zone_device *tzd;
+ struct mutex lock;
+ struct clk *clk_topcrm;
+ struct clk *clk_apb;
+ void __iomem *regs;
+ struct device *dev;
+};
+
+static int zx2967_thermal_get_temp(void *data, int *temp)
+{
+ void __iomem *regs;
+ struct zx2967_thermal_priv *priv = data;
+ u32 val;
+ int ret;
+
+ if (!priv->tzd)
+ return -EAGAIN;
+
+ regs = priv->regs;
+ mutex_lock(&priv->lock);
+ writel_relaxed(ZX2967_POWER_MODE_LOW,
+ regs + ZX2967_THERMAL_POWER_MODE);
+ writel_relaxed(ZX2967_DCF_EN, regs + ZX2967_THERMAL_DCF);
+
+ val = readl_relaxed(regs + ZX2967_THERMAL_SEL);
+ val &= ~ZX2967_THERMAL_ID_MASK;
+ val |= ZX2967_THERMAL_ID;
+ writel_relaxed(val, regs + ZX2967_THERMAL_SEL);
+
+ /*
+ * Must wait for a while, surely it's a bit odd.
+ * otherwise temperature value we got has a few deviation, even if
+ * the THERMAL_READY bit is set.
+ */
+ usleep_range(100, 300);
+ ret = readx_poll_timeout(readl, regs + ZX2967_THERMAL_CTRL,
+ val, val & ZX2967_THERMAL_READY, 300,
+ ZX2967_GET_TEMP_TIMEOUT_US);
+ if (ret) {
+ dev_err(priv->dev, "Thermal sensor data timeout\n");
+ goto unlock;
+ }
+
+ writel_relaxed(ZX2967_DCF_FREEZE | ZX2967_DCF_EN,
+ regs + ZX2967_THERMAL_DCF);
+ val = readl_relaxed(regs + ZX2967_THERMAL_CTRL)
+ & ZX2967_THERMAL_TEMP_MASK;
+ writel_relaxed(ZX2967_POWER_MODE_HIGH,
+ regs + ZX2967_THERMAL_POWER_MODE);
+
+ /*
+ * Calculate temperature
+ * In dts, slope is multiplied by 1000.
+ */
+ *temp = DIV_ROUND_CLOSEST(((s32)val + priv->tzd->tzp->offset) * 1000,
+ priv->tzd->tzp->slope);
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static const struct thermal_zone_of_device_ops zx2967_of_thermal_ops = {
+ .get_temp = zx2967_thermal_get_temp,
+};
+
+static int zx2967_thermal_probe(struct platform_device *pdev)
+{
+ struct zx2967_thermal_priv *priv;
+ struct resource *res;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+
+ priv->clk_topcrm = devm_clk_get(&pdev->dev, "topcrm");
+ if (IS_ERR(priv->clk_topcrm)) {
+ ret = PTR_ERR(priv->clk_topcrm);
+ dev_err(&pdev->dev, "failed to get topcrm clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->clk_topcrm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable topcrm clock: %d\n",
+ ret);
+ return ret;
+ }
+
+ priv->clk_apb = devm_clk_get(&pdev->dev, "apb");
+ if (IS_ERR(priv->clk_apb)) {
+ ret = PTR_ERR(priv->clk_apb);
+ dev_err(&pdev->dev, "failed to get apb clock: %d\n", ret);
+ goto disable_clk_topcrm;
+ }
+
+ ret = clk_prepare_enable(priv->clk_apb);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable apb clock: %d\n",
+ ret);
+ goto disable_clk_topcrm;
+ }
+
+ mutex_init(&priv->lock);
+ priv->tzd = thermal_zone_of_sensor_register(&pdev->dev,
+ 0, priv, &zx2967_of_thermal_ops);
+
+ if (IS_ERR(priv->tzd)) {
+ ret = PTR_ERR(priv->tzd);
+ dev_err(&pdev->dev, "failed to register sensor: %d\n", ret);
+ goto disable_clk_all;
+ }
+
+ if (priv->tzd->tzp->slope == 0) {
+ thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd);
+ dev_err(&pdev->dev, "coefficients of sensor is invalid\n");
+ ret = -EINVAL;
+ goto disable_clk_all;
+ }
+
+ priv->dev = &pdev->dev;
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+
+disable_clk_all:
+ clk_disable_unprepare(priv->clk_apb);
+disable_clk_topcrm:
+ clk_disable_unprepare(priv->clk_topcrm);
+ return ret;
+}
+
+static int zx2967_thermal_exit(struct platform_device *pdev)
+{
+ struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev);
+
+ thermal_zone_of_sensor_unregister(&pdev->dev, priv->tzd);
+ clk_disable_unprepare(priv->clk_topcrm);
+ clk_disable_unprepare(priv->clk_apb);
+
+ return 0;
+}
+
+static const struct of_device_id zx2967_thermal_id_table[] = {
+ { .compatible = "zte,zx296718-thermal" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, zx2967_thermal_id_table);
+
+#ifdef CONFIG_PM_SLEEP
+static int zx2967_thermal_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev);
+
+ if (priv && priv->clk_topcrm)
+ clk_disable_unprepare(priv->clk_topcrm);
+
+ if (priv && priv->clk_apb)
+ clk_disable_unprepare(priv->clk_apb);
+
+ return 0;
+}
+
+static int zx2967_thermal_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct zx2967_thermal_priv *priv = platform_get_drvdata(pdev);
+ int error;
+
+ error = clk_prepare_enable(priv->clk_topcrm);
+ if (error)
+ return error;
+
+ error = clk_prepare_enable(priv->clk_apb);
+ if (error) {
+ clk_disable_unprepare(priv->clk_topcrm);
+ return error;
+ }
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(zx2967_thermal_pm_ops,
+ zx2967_thermal_suspend, zx2967_thermal_resume);
+
+static struct platform_driver zx2967_thermal_driver = {
+ .probe = zx2967_thermal_probe,
+ .remove = zx2967_thermal_exit,
+ .driver = {
+ .name = "zx2967_thermal",
+ .of_match_table = zx2967_thermal_id_table,
+ .pm = &zx2967_thermal_pm_ops,
+ },
+};
+module_platform_driver(zx2967_thermal_driver);
+
+MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>");
+MODULE_DESCRIPTION("ZTE zx2967 thermal driver");
+MODULE_LICENSE("GPL v2");