diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-03-24 10:45:59 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-03-24 10:45:59 -0700 |
commit | faea72dd0f155a9d349e191d74830716c702651d (patch) | |
tree | cb08f5ebfe30e167af10b730ab6468ea701cf0c5 | |
parent | 5b1e167d8de86d698114a0a8de61e9d1365d3e8a (diff) | |
parent | 81ad4276b505e987dd8ebbdf63605f92cd172b52 (diff) | |
download | lwn-faea72dd0f155a9d349e191d74830716c702651d.tar.gz lwn-faea72dd0f155a9d349e191d74830716c702651d.zip |
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux
Pull thermal updates from Zhang Rui:
- Fix a regression where bogus trip points on some Lenovo laptops start
to screw up thermal control after commit 81ad4276b505 ("Thermal:
initialize thermal zone device correctly").
On these Lenovo laptops, a bogus passive trip point is reported,
which is 0 degree Celsius. Without commit 81ad4276b505, thermal zone
fails to set cooling devices to proper cooling state, which is a bug.
But with commit 81ad4276b505 applied, the processors are always
throttled on these Lenovo laptops because the current temperature is
always higher than the passive trip point.
Fix things to ignore such bogus trip points. (Zhang Rui)
- Introduce Mediatek thermal driver. (Sascha Hauer)
- Introduce devm_ versions of OF thermal sensor register API. (Laxman
Dewangan)
- Changes in Kconfigs to allow compile test on UM arch. (Krzysztof
Kozlowski)
- Introduce Skylake support in intel_pch_thermal driver. (Srinivas
Pandruvada)
- Several small fixes on Rockchip, TI-SoC, Tegra, RCar, and Exynos
thermal drivers.
* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: (26 commits)
Thermal: Ignore invalid trip points
thermal: trace: migrating thermal traces to use TRACE_DEFINE_ENUM() macros
thermal: intel_pch_thermal: Enable Skylake PCH thermal
thermal: doc: Add details of devm_thermal_zone_of_sensor_{register,unregister}
thermal: of-thermal: Add devm version of thermal_zone_of_sensor_register
thermal: doc: Add details of thermal_zone_of_sensor_{register,unregister}
thermal: exynos: Defer probe if vtmu is present but not registered
thermal: exynos: Use devm_regulator_get_optional() for vtmu
thermal: exynos: List vtmu-supply as optional property in DT binding
thermal: exynos: Print a message about exceeded number of supported trip-points
thermal: exynos: Document number of supported trip-points
thermal: exynos: Document compatible for Exynos5433 TMU
thermal: mtk: allow compile testing on UM
thermal: tegra_soctherm: fix sign bit of temperature
thermal: Fix build error of missing devm_ioremap_resource on UM
thermal: ti-soc-thermal: clean up the error handling a bit
thermal: rcar: Use ARCH_RENESAS
thermal: rcar_thermal: don't open code of_device_get_match_data()
thermal: db8500_cpufreq_cooling: Compile with COMPILE_TEST
thermal: rockchip: fix the tsadc sequence output on rk3228/rk3399
...
-rw-r--r-- | Documentation/devicetree/bindings/thermal/exynos-thermal.txt | 18 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/thermal/mediatek-thermal.txt | 43 | ||||
-rw-r--r-- | Documentation/thermal/sysfs-api.txt | 68 | ||||
-rw-r--r-- | drivers/thermal/Kconfig | 21 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/intel_pch_thermal.c | 6 | ||||
-rw-r--r-- | drivers/thermal/mtk_thermal.c | 625 | ||||
-rw-r--r-- | drivers/thermal/of-thermal.c | 81 | ||||
-rw-r--r-- | drivers/thermal/rcar_thermal.c | 3 | ||||
-rw-r--r-- | drivers/thermal/rockchip_thermal.c | 239 | ||||
-rw-r--r-- | drivers/thermal/samsung/Kconfig | 1 | ||||
-rw-r--r-- | drivers/thermal/samsung/exynos_tmu.c | 19 | ||||
-rw-r--r-- | drivers/thermal/tegra_soctherm.c | 2 | ||||
-rw-r--r-- | drivers/thermal/thermal_core.c | 13 | ||||
-rw-r--r-- | drivers/thermal/ti-soc-thermal/ti-bandgap.c | 10 | ||||
-rw-r--r-- | include/linux/thermal.h | 20 | ||||
-rw-r--r-- | include/trace/events/thermal.h | 16 |
17 files changed, 1065 insertions, 121 deletions
diff --git a/Documentation/devicetree/bindings/thermal/exynos-thermal.txt b/Documentation/devicetree/bindings/thermal/exynos-thermal.txt index 695150a4136b..70b4c16c7ed8 100644 --- a/Documentation/devicetree/bindings/thermal/exynos-thermal.txt +++ b/Documentation/devicetree/bindings/thermal/exynos-thermal.txt @@ -11,6 +11,7 @@ "samsung,exynos5420-tmu" for TMU channel 0, 1 on Exynos5420 "samsung,exynos5420-tmu-ext-triminfo" for TMU channels 2, 3 and 4 Exynos5420 (Must pass triminfo base and triminfo clock) + "samsung,exynos5433-tmu" "samsung,exynos5440-tmu" "samsung,exynos7-tmu" - interrupt-parent : The phandle for the interrupt controller @@ -40,9 +41,14 @@ for current TMU channel -- "tmu_sclk" clock for functional operation of the current TMU channel -- vtmu-supply: This entry is optional and provides the regulator node supplying - voltage to TMU. If needed this entry can be placed inside - board/platform specific dts file. + +The Exynos TMU supports generating interrupts when reaching given +temperature thresholds. Number of supported thermal trip points depends +on the SoC (only first trip points defined in DT will be configured): + - most of SoC: 4 + - samsung,exynos5433-tmu: 8 + - samsung,exynos7-tmu: 8 + Following properties are mandatory (depending on SoC): - samsung,tmu_gain: Gain value for internal TMU operation. - samsung,tmu_reference_voltage: Value of TMU IP block's reference voltage @@ -56,6 +62,12 @@ Following properties are mandatory (depending on SoC): - samsung,tmu_default_temp_offset: Default temperature offset - samsung,tmu_cal_type: Callibration type +** Optional properties: + +- vtmu-supply: This entry is optional and provides the regulator node supplying + voltage to TMU. If needed this entry can be placed inside + board/platform specific dts file. + Example 1): tmu@100C0000 { diff --git a/Documentation/devicetree/bindings/thermal/mediatek-thermal.txt b/Documentation/devicetree/bindings/thermal/mediatek-thermal.txt new file mode 100644 index 000000000000..81f9a512bc2a --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/mediatek-thermal.txt @@ -0,0 +1,43 @@ +* Mediatek Thermal + +This describes the device tree binding for the Mediatek thermal controller +which measures the on-SoC temperatures. This device does not have its own ADC, +instead it directly controls the AUXADC via AHB bus accesses. For this reason +this device needs phandles to the AUXADC. Also it controls a mux in the +apmixedsys register space via AHB bus accesses, so a phandle to the APMIXEDSYS +is also needed. + +Required properties: +- compatible: "mediatek,mt8173-thermal" +- reg: Address range of the thermal controller +- interrupts: IRQ for the thermal controller +- clocks, clock-names: Clocks needed for the thermal controller. required + clocks are: + "therm": Main clock needed for register access + "auxadc": The AUXADC clock +- resets: Reference to the reset controller controlling the thermal controller. +- mediatek,auxadc: A phandle to the AUXADC which the thermal controller uses +- mediatek,apmixedsys: A phandle to the APMIXEDSYS controller. +- #thermal-sensor-cells : Should be 0. See ./thermal.txt for a description. + +Optional properties: +- nvmem-cells: A phandle to the calibration data provided by a nvmem device. If + unspecified default values shall be used. +- nvmem-cell-names: Should be "calibration-data" + +Example: + + thermal: thermal@1100b000 { + #thermal-sensor-cells = <1>; + compatible = "mediatek,mt8173-thermal"; + reg = <0 0x1100b000 0 0x1000>; + interrupts = <0 70 IRQ_TYPE_LEVEL_LOW>; + clocks = <&pericfg CLK_PERI_THERM>, <&pericfg CLK_PERI_AUXADC>; + clock-names = "therm", "auxadc"; + resets = <&pericfg MT8173_PERI_THERM_SW_RST>; + reset-names = "therm"; + mediatek,auxadc = <&auxadc>; + mediatek,apmixedsys = <&apmixedsys>; + nvmem-cells = <&thermal_calibration_data>; + nvmem-cell-names = "calibration-data"; + }; diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt index 8c745c8931da..ed419d6c8dec 100644 --- a/Documentation/thermal/sysfs-api.txt +++ b/Documentation/thermal/sysfs-api.txt @@ -72,6 +72,74 @@ temperature) and throttle appropriate devices. It deletes the corresponding entry form /sys/class/thermal folder and unbind all the thermal cooling devices it uses. +1.1.3 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) + + This interface adds a new sensor to a DT thermal zone. + 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 parameters for this interface are: + dev: Device node of sensor containing valid node pointer in + dev->of_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 *. + + get_temp: a pointer to a function that reads the + sensor temperature. This is mandatory + callback provided by sensor driver. + get_trend: a pointer to a function that reads the + sensor temperature trend. + set_emul_temp: a pointer to a function that sets + sensor emulated temperature. + The thermal zone temperature is provided by the get_temp() function + pointer of thermal_zone_of_device_ops. When called, it will + have the private pointer @data back. + + It returns error pointer if fails otherwise valid thermal zone device + handle. Caller should check the return handle with IS_ERR() for finding + whether success or not. + +1.1.4 void thermal_zone_of_sensor_unregister(struct device *dev, + struct thermal_zone_device *tzd) + + This interface unregisters a sensor from a DT thermal zone which was + successfully added by interface thermal_zone_of_sensor_register(). + This function removes the sensor callbacks and private data from the + thermal zone device registered with thermal_zone_of_sensor_register() + interface. It will also silent the zone by remove the .get_temp() and + get_trend() thermal zone device callbacks. + +1.1.5 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) + + This interface is resource managed version of + thermal_zone_of_sensor_register(). + All details of thermal_zone_of_sensor_register() described in + section 1.1.3 is applicable here. + The benefit of using this interface to register sensor is that it + is not require to explicitly call thermal_zone_of_sensor_unregister() + in error path or during driver unbinding as this is done by driver + resource manager. + +1.1.6 void devm_thermal_zone_of_sensor_unregister(struct device *dev, + struct thermal_zone_device *tzd) + + This interface is resource managed version of + thermal_zone_of_sensor_unregister(). + All details of thermal_zone_of_sensor_unregister() described in + section 1.1.4 is applicable here. + Normally this function will not need to be called and the resource + management code will ensure that the resource is freed. + 1.2 thermal cooling device interface 1.2.1 struct thermal_cooling_device *thermal_cooling_device_register(char *name, void *devdata, struct thermal_cooling_device_ops *) diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 7c92c09be213..c37eedc35a24 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -178,6 +178,7 @@ config THERMAL_EMULATION config HISI_THERMAL tristate "Hisilicon thermal driver" depends on (ARCH_HISI && CPU_THERMAL && OF) || COMPILE_TEST + depends on HAS_IOMEM help Enable this to plug hisilicon's thermal sensor driver into the Linux thermal framework. cpufreq is used as the cooling device to throttle @@ -197,6 +198,7 @@ config IMX_THERMAL 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 @@ -206,6 +208,7 @@ 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 @@ -214,7 +217,7 @@ config ROCKCHIP_THERMAL config RCAR_THERMAL tristate "Renesas R-Car thermal driver" - depends on ARCH_SHMOBILE || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST depends on HAS_IOMEM help Enable this to plug the R-Car thermal sensor driver into the Linux @@ -223,6 +226,7 @@ config RCAR_THERMAL 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 @@ -231,6 +235,7 @@ config KIRKWOOD_THERMAL 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 @@ -249,6 +254,7 @@ config DB8500_THERMAL config ARMADA_THERMAL tristate "Armada 370/XP 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 @@ -266,7 +272,8 @@ config TEGRA_SOCTHERM config DB8500_CPUFREQ_COOLING tristate "DB8500 cpufreq cooling" - depends on ARCH_U8500 + depends on ARCH_U8500 || COMPILE_TEST + depends on HAS_IOMEM depends on CPU_THERMAL default y help @@ -365,8 +372,18 @@ config INTEL_PCH_THERMAL 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 + default y + help + Enable this option if you want to have support for thermal management + controller present in Mediatek SoCs + menu "Texas Instruments thermal drivers" depends on ARCH_HAS_BANDGAP || COMPILE_TEST +depends on HAS_IOMEM source "drivers/thermal/ti-soc-thermal/Kconfig" endmenu diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index cfae6a654793..8e9cbc3b5679 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -48,3 +48,4 @@ obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o obj-$(CONFIG_ST_THERMAL) += st/ obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o +obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o diff --git a/drivers/thermal/intel_pch_thermal.c b/drivers/thermal/intel_pch_thermal.c index 00d81af648b8..6a6ec1c95a7a 100644 --- a/drivers/thermal/intel_pch_thermal.c +++ b/drivers/thermal/intel_pch_thermal.c @@ -24,6 +24,7 @@ /* Intel PCH thermal Device IDs */ #define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */ +#define PCH_THERMAL_DID_SKL 0x9D31 /* Skylake PCH */ /* Wildcat Point-LP PCH Thermal registers */ #define WPT_TEMP 0x0000 /* Temperature */ @@ -201,6 +202,10 @@ static int intel_pch_thermal_probe(struct pci_dev *pdev, ptd->ops = &pch_dev_ops_wpt; dev_name = "pch_wildcat_point"; break; + case PCH_THERMAL_DID_SKL: + ptd->ops = &pch_dev_ops_wpt; + dev_name = "pch_skylake"; + break; default: dev_err(&pdev->dev, "unknown pch thermal device\n"); return -ENODEV; @@ -266,6 +271,7 @@ static void intel_pch_thermal_remove(struct pci_dev *pdev) static struct pci_device_id intel_pch_thermal_id[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL) }, { 0, }, }; MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id); diff --git a/drivers/thermal/mtk_thermal.c b/drivers/thermal/mtk_thermal.c new file mode 100644 index 000000000000..3d93b1c07cee --- /dev/null +++ b/drivers/thermal/mtk_thermal.c @@ -0,0 +1,625 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: Hanyi Wu <hanyi.wu@mediatek.com> + * Sascha Hauer <s.hauer@pengutronix.de> + * + * 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/platform_device.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/thermal.h> +#include <linux/reset.h> +#include <linux/types.h> +#include <linux/nvmem-consumer.h> + +/* AUXADC Registers */ +#define AUXADC_CON0_V 0x000 +#define AUXADC_CON1_V 0x004 +#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 AUXADC_MISC_V 0x094 + +#define AUXADC_CON1_CHANNEL(x) BIT(x) + +#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) + +#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 */ +#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 THERMAL_NAME "mtk-thermal" + +struct mtk_thermal; + +struct mtk_thermal_bank { + struct mtk_thermal *mt; + int id; +}; + +struct mtk_thermal { + struct device *dev; + void __iomem *thermal_base; + + struct clk *clk_peri_therm; + struct clk *clk_auxadc; + + struct mtk_thermal_bank banks[MT8173_NUM_ZONES]; + + /* 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]; + + struct thermal_zone_device *tzd; +}; + +struct mtk_thermal_bank_cfg { + unsigned int num_sensors; + unsigned int sensors[MT8173_NUM_SENSORS_PER_ZONE]; +}; + +static const int sensor_mux_values[MT8173_NUM_SENSORS] = { 0, 1, 2, 3, 16 }; + +/* + * 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_bank_cfg bank_data[] = { + { + .num_sensors = 2, + .sensors = { MT8173_TS2, MT8173_TS3 }, + }, { + .num_sensors = 2, + .sensors = { MT8173_TS2, MT8173_TS4 }, + }, { + .num_sensors = 3, + .sensors = { MT8173_TS1, MT8173_TS2, MT8173_TSABB }, + }, { + .num_sensors = 1, + .sensors = { MT8173_TS2 }, + }, +}; + +struct mtk_thermal_sense_point { + int msr; + int adcpnp; +}; + +static const struct mtk_thermal_sense_point + sensing_points[MT8173_NUM_SENSORS_PER_ZONE] = { + { + .msr = TEMP_MSR0, + .adcpnp = TEMP_ADCPNP0, + }, { + .msr = TEMP_MSR1, + .adcpnp = TEMP_ADCPNP1, + }, { + .msr = TEMP_MSR2, + .adcpnp = TEMP_ADCPNP2, + }, { + .msr = TEMP_MSR3, + .adcpnp = TEMP_ADCPNP3, + }, +}; + +/** + * 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; + int i, temp = INT_MIN, max = INT_MIN; + u32 raw; + + for (i = 0; i < bank_data[bank->id].num_sensors; i++) { + raw = readl(mt->thermal_base + sensing_points[i].msr); + + temp = raw_to_mcelsius(mt, 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 < MT8173_NUM_ZONES; 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_bank_cfg *cfg = &bank_data[num]; + 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(MT8173_TEMP_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(MT8173_TEMP_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(MT8173_TEMP_AUXADC_CHANNEL), + mt->thermal_base + TEMP_ADCVALIDADDR); + + /* AHB address for auxadc voltage output */ + writel(auxadc_phys_base + AUXADC_DATA(MT8173_TEMP_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 < cfg->num_sensors; i++) + writel(sensor_mux_values[cfg->sensors[i]], + mt->thermal_base + sensing_points[i].adcpnp); + + writel((1 << cfg->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 < MT8173_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]); + 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 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; + + mt = devm_kzalloc(&pdev->dev, sizeof(*mt), GFP_KERNEL); + if (!mt) + return -ENOMEM; + + 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 = clk_prepare_enable(mt->clk_auxadc); + if (ret) { + dev_err(&pdev->dev, "Can't enable auxadc clk: %d\n", ret); + return ret; + } + + ret = device_reset(&pdev->dev); + if (ret) + goto err_disable_clk_auxadc; + + 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 < MT8173_NUM_ZONES; i++) + mtk_thermal_init_bank(mt, i, apmixed_phys_base, + auxadc_phys_base); + + platform_set_drvdata(pdev, mt); + + mt->tzd = thermal_zone_of_sensor_register(&pdev->dev, 0, mt, + &mtk_thermal_ops); + if (IS_ERR(mt->tzd)) + goto err_register; + + return 0; + +err_register: + 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); + + thermal_zone_of_sensor_unregister(&pdev->dev, mt->tzd); + + clk_disable_unprepare(mt->clk_peri_therm); + clk_disable_unprepare(mt->clk_auxadc); + + return 0; +} + +static const struct of_device_id mtk_thermal_of_match[] = { + { + .compatible = "mediatek,mt8173-thermal", + }, { + }, +}; + +static struct platform_driver mtk_thermal_driver = { + .probe = mtk_thermal_probe, + .remove = mtk_thermal_remove, + .driver = { + .name = THERMAL_NAME, + .of_match_table = mtk_thermal_of_match, + }, +}; + +module_platform_driver(mtk_thermal_driver); + +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 index 9043f8f91852..49ac23d3e776 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -555,6 +555,87 @@ void thermal_zone_of_sensor_unregister(struct device *dev, } 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 hermal_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 ***/ /** diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c index 0e735acea33a..82daba09e150 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -430,8 +430,7 @@ static int rcar_thermal_probe(struct platform_device *pdev) struct rcar_thermal_priv *priv; struct device *dev = &pdev->dev; struct resource *res, *irq; - const struct of_device_id *of_id = of_match_device(rcar_thermal_dt_ids, dev); - unsigned long of_data = (unsigned long)of_id->data; + unsigned long of_data = (unsigned long)of_device_get_match_data(dev); int mres = 0; int i; int ret = -ENODEV; diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index b58e3fb9b311..233a564442a0 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -58,8 +58,8 @@ enum sensor_id { /** * The conversion table has the adc value and temperature. - * ADC_DECREMENT: the adc value is of diminishing.(e.g. v2_code_table) - * ADC_INCREMENT: the adc value is incremental.(e.g. v3_code_table) + * 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, @@ -135,7 +135,13 @@ struct rockchip_thermal_data { enum tshut_polarity tshut_polarity; }; -/* TSADC Sensor info define: */ +/** + * 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_AUTO_CON 0x04 #define TSADCV2_INT_EN 0x08 #define TSADCV2_INT_PD 0x0c @@ -149,13 +155,20 @@ struct rockchip_thermal_data { #define TSADCV2_AUTO_EN BIT(0) #define TSADCV2_AUTO_SRC_EN(chn) BIT(4 + (chn)) #define TSADCV2_AUTO_TSHUT_POLARITY_HIGH BIT(8) +/** + * TSADCV1_AUTO_Q_SEL_EN: + * whether select (1024 - tsadc_q) as output + * 1'b0:use tsadc_q as output(temperature-code is rising sequence) + * 1'b1:use(1024 - tsadc_q) as output (temperature-code is falling sequence) + */ +#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 TSADCV1_INT_PD_CLEAR_MASK ~BIT(16) #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 @@ -177,45 +190,46 @@ struct tsadc_table { * linearly interpolated. * Code to Temperature mapping should be updated based on sillcon results. */ -static const struct tsadc_table v1_code_table[] = { - {TSADCV3_DATA_MASK, -40000}, - {436, -40000}, - {431, -35000}, - {426, -30000}, - {421, -25000}, - {416, -20000}, - {411, -15000}, - {406, -10000}, - {401, -5000}, - {395, 0}, - {390, 5000}, - {385, 10000}, - {380, 15000}, - {375, 20000}, - {370, 25000}, - {364, 30000}, - {359, 35000}, - {354, 40000}, - {349, 45000}, - {343, 50000}, - {338, 55000}, - {333, 60000}, - {328, 65000}, - {322, 70000}, - {317, 75000}, - {312, 80000}, - {307, 85000}, - {301, 90000}, - {296, 95000}, - {291, 100000}, - {286, 105000}, - {280, 110000}, - {275, 115000}, - {270, 120000}, - {264, 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 v2_code_table[] = { +static const struct tsadc_table rk3288_code_table[] = { {TSADCV2_DATA_MASK, -40000}, {3800, -40000}, {3792, -35000}, @@ -253,7 +267,7 @@ static const struct tsadc_table v2_code_table[] = { {3421, 125000}, }; -static const struct tsadc_table v3_code_table[] = { +static const struct tsadc_table rk3368_code_table[] = { {0, -40000}, {106, -40000}, {108, -35000}, @@ -292,42 +306,43 @@ static const struct tsadc_table v3_code_table[] = { {TSADCV3_DATA_MASK, 125000}, }; -static const struct tsadc_table v4_code_table[] = { - {TSADCV3_DATA_MASK, -40000}, - {431, -40000}, - {426, -35000}, - {421, -30000}, - {415, -25000}, - {410, -20000}, - {405, -15000}, - {399, -10000}, - {394, -5000}, - {389, 0}, - {383, 5000}, - {378, 10000}, - {373, 15000}, - {367, 20000}, - {362, 25000}, - {357, 30000}, - {351, 35000}, - {346, 40000}, - {340, 45000}, - {335, 50000}, - {330, 55000}, - {324, 60000}, - {319, 65000}, - {313, 70000}, - {308, 75000}, - {302, 80000}, - {297, 85000}, - {291, 90000}, - {286, 95000}, - {281, 100000}, - {275, 105000}, - {270, 110000}, - {264, 115000}, - {259, 120000}, - {253, 125000}, +static const struct tsadc_table rk3399_code_table[] = { + {0, -40000}, + {593, -40000}, + {598, -35000}, + {603, -30000}, + {609, -25000}, + {614, -20000}, + {619, -15000}, + {625, -10000}, + {630, -5000}, + {635, 0}, + {641, 5000}, + {646, 10000}, + {651, 15000}, + {657, 20000}, + {662, 25000}, + {667, 30000}, + {673, 35000}, + {678, 40000}, + {684, 45000}, + {689, 50000}, + {694, 55000}, + {700, 60000}, + {705, 65000}, + {711, 70000}, + {716, 75000}, + {722, 80000}, + {727, 85000}, + {733, 90000}, + {738, 95000}, + {743, 100000}, + {749, 105000}, + {754, 110000}, + {760, 115000}, + {765, 120000}, + {771, 125000}, + {TSADCV3_DATA_MASK, 125000}, }; static u32 rk_tsadcv2_temp_to_code(struct chip_tsadc_table table, @@ -411,7 +426,7 @@ static int rk_tsadcv2_code_to_temp(struct chip_tsadc_table table, u32 code, * temperature between 2 table entries is linear and interpolate * to produce less granular result. */ - num = table.id[mid].temp - v2_code_table[mid - 1].temp; + 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); @@ -453,20 +468,20 @@ static void rk_tsadcv2_initialize(void __iomem *regs, regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); } -static void rk_tsadcv1_irq_ack(void __iomem *regs) +static void rk_tsadcv2_irq_ack(void __iomem *regs) { u32 val; val = readl_relaxed(regs + TSADCV2_INT_PD); - writel_relaxed(val & TSADCV1_INT_PD_CLEAR_MASK, regs + TSADCV2_INT_PD); + writel_relaxed(val & TSADCV2_INT_PD_CLEAR_MASK, regs + TSADCV2_INT_PD); } -static void rk_tsadcv2_irq_ack(void __iomem *regs) +static void rk_tsadcv3_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); + writel_relaxed(val & TSADCV3_INT_PD_CLEAR_MASK, regs + TSADCV2_INT_PD); } static void rk_tsadcv2_control(void __iomem *regs, bool enable) @@ -482,6 +497,25 @@ static void rk_tsadcv2_control(void __iomem *regs, bool enable) writel_relaxed(val, regs + TSADCV2_AUTO_CON); } +/** + * @rk_tsadcv3_control: + * 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(struct chip_tsadc_table table, int chn, void __iomem *regs, int *temp) { @@ -531,17 +565,17 @@ static const struct rockchip_tsadc_chip rk3228_tsadc_data = { .tshut_temp = 95000, .initialize = rk_tsadcv2_initialize, - .irq_ack = rk_tsadcv1_irq_ack, - .control = rk_tsadcv2_control, + .irq_ack = rk_tsadcv3_irq_ack, + .control = rk_tsadcv3_control, .get_temp = rk_tsadcv2_get_temp, .set_tshut_temp = rk_tsadcv2_tshut_temp, .set_tshut_mode = rk_tsadcv2_tshut_mode, .table = { - .id = v1_code_table, - .length = ARRAY_SIZE(v1_code_table), + .id = rk3228_code_table, + .length = ARRAY_SIZE(rk3228_code_table), .data_mask = TSADCV3_DATA_MASK, - .mode = ADC_DECREMENT, + .mode = ADC_INCREMENT, }, }; @@ -562,8 +596,8 @@ static const struct rockchip_tsadc_chip rk3288_tsadc_data = { .set_tshut_mode = rk_tsadcv2_tshut_mode, .table = { - .id = v2_code_table, - .length = ARRAY_SIZE(v2_code_table), + .id = rk3288_code_table, + .length = ARRAY_SIZE(rk3288_code_table), .data_mask = TSADCV2_DATA_MASK, .mode = ADC_DECREMENT, }, @@ -586,8 +620,8 @@ static const struct rockchip_tsadc_chip rk3368_tsadc_data = { .set_tshut_mode = rk_tsadcv2_tshut_mode, .table = { - .id = v3_code_table, - .length = ARRAY_SIZE(v3_code_table), + .id = rk3368_code_table, + .length = ARRAY_SIZE(rk3368_code_table), .data_mask = TSADCV3_DATA_MASK, .mode = ADC_INCREMENT, }, @@ -603,17 +637,17 @@ static const struct rockchip_tsadc_chip rk3399_tsadc_data = { .tshut_temp = 95000, .initialize = rk_tsadcv2_initialize, - .irq_ack = rk_tsadcv1_irq_ack, - .control = rk_tsadcv2_control, + .irq_ack = rk_tsadcv3_irq_ack, + .control = rk_tsadcv3_control, .get_temp = rk_tsadcv2_get_temp, .set_tshut_temp = rk_tsadcv2_tshut_temp, .set_tshut_mode = rk_tsadcv2_tshut_mode, .table = { - .id = v4_code_table, - .length = ARRAY_SIZE(v4_code_table), + .id = rk3399_code_table, + .length = ARRAY_SIZE(rk3399_code_table), .data_mask = TSADCV3_DATA_MASK, - .mode = ADC_DECREMENT, + .mode = ADC_INCREMENT, }, }; @@ -693,15 +727,14 @@ static int rockchip_configure_from_dt(struct device *dev, 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 (thermal->tshut_temp > INT_MAX) { - dev_err(dev, "Invalid tshut temperature specified: %d\n", - thermal->tshut_temp); - return -ERANGE; - } - if (of_property_read_u32(np, "rockchip,hw-tshut-mode", &tshut_mode)) { dev_warn(dev, "Missing tshut mode property, using default (%s)\n", diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index e0da3865e060..222e644169f0 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -1,6 +1,7 @@ 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 diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index fa61eff88496..f3ce94ec73b5 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -184,6 +184,7 @@ * @temp_error2: fused value of the second point trim. * @regulator: pointer to the TMU regulator structure. * @reg_conf: pointer to structure to register with core thermal. + * @ntrip: number of supported trip points. * @tmu_initialize: SoC specific TMU initialization method * @tmu_control: SoC specific TMU control method * @tmu_read: SoC specific TMU temperature read method @@ -203,6 +204,7 @@ struct exynos_tmu_data { u16 temp_error1, temp_error2; struct regulator *regulator; struct thermal_zone_device *tzd; + unsigned int ntrip; int (*tmu_initialize)(struct platform_device *pdev); void (*tmu_control)(struct platform_device *pdev, bool on); @@ -346,6 +348,14 @@ static int exynos_tmu_initialize(struct platform_device *pdev) struct exynos_tmu_data *data = platform_get_drvdata(pdev); int ret; + if (of_thermal_get_ntrips(data->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(data->tzd) - data->ntrip)); + } + mutex_lock(&data->lock); clk_enable(data->clk); if (!IS_ERR(data->clk_sec)) @@ -1210,6 +1220,7 @@ static int exynos_map_dt_data(struct platform_device *pdev) data->tmu_control = exynos4210_tmu_control; data->tmu_read = exynos4210_tmu_read; data->tmu_clear_irqs = exynos4210_tmu_clear_irqs; + data->ntrip = 4; break; case SOC_ARCH_EXYNOS3250: case SOC_ARCH_EXYNOS4412: @@ -1222,6 +1233,7 @@ static int exynos_map_dt_data(struct platform_device *pdev) 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; break; case SOC_ARCH_EXYNOS5433: data->tmu_initialize = exynos5433_tmu_initialize; @@ -1229,6 +1241,7 @@ static int exynos_map_dt_data(struct platform_device *pdev) 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; break; case SOC_ARCH_EXYNOS5440: data->tmu_initialize = exynos5440_tmu_initialize; @@ -1236,6 +1249,7 @@ static int exynos_map_dt_data(struct platform_device *pdev) data->tmu_read = exynos5440_tmu_read; data->tmu_set_emulation = exynos5440_tmu_set_emulation; data->tmu_clear_irqs = exynos5440_tmu_clear_irqs; + data->ntrip = 4; break; case SOC_ARCH_EXYNOS7: data->tmu_initialize = exynos7_tmu_initialize; @@ -1243,6 +1257,7 @@ static int exynos_map_dt_data(struct platform_device *pdev) 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; break; default: dev_err(&pdev->dev, "Platform not supported\n"); @@ -1295,7 +1310,7 @@ static int exynos_tmu_probe(struct platform_device *pdev) * TODO: Add regulator as an SOC feature, so that regulator enable * is a compulsory call. */ - data->regulator = devm_regulator_get(&pdev->dev, "vtmu"); + data->regulator = devm_regulator_get_optional(&pdev->dev, "vtmu"); if (!IS_ERR(data->regulator)) { ret = regulator_enable(data->regulator); if (ret) { @@ -1303,6 +1318,8 @@ static int exynos_tmu_probe(struct platform_device *pdev) return ret; } } else { + if (PTR_ERR(data->regulator) == -EPROBE_DEFER) + return -EPROBE_DEFER; dev_info(&pdev->dev, "Regulator node (vtmu) not found\n"); } diff --git a/drivers/thermal/tegra_soctherm.c b/drivers/thermal/tegra_soctherm.c index 74ea5765938b..136975220c92 100644 --- a/drivers/thermal/tegra_soctherm.c +++ b/drivers/thermal/tegra_soctherm.c @@ -57,7 +57,7 @@ #define READBACK_VALUE_MASK 0xff00 #define READBACK_VALUE_SHIFT 8 #define READBACK_ADD_HALF BIT(7) -#define READBACK_NEGATE BIT(1) +#define READBACK_NEGATE BIT(0) #define FUSE_TSENSOR8_CALIB 0x180 #define FUSE_SPARE_REALIGNMENT_REG_0 0x1fc diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index a0a8fd1235e2..d4b54653ecf8 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -454,6 +454,10 @@ 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) @@ -1800,6 +1804,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, { struct thermal_zone_device *tz; enum thermal_trip_type trip_type; + int trip_temp; int result; int count; int passive = 0; @@ -1871,9 +1876,15 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, goto unregister; for (count = 0; count < trips; count++) { - tz->ops->get_trip_type(tz, count, &trip_type); + if (tz->ops->get_trip_type(tz, count, &trip_type)) + set_bit(count, &tz->trips_disabled); if (trip_type == THERMAL_TRIP_PASSIVE) passive = 1; + 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); } if (!passive) { diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index 1e34a1efc554..06ea9766a70a 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -1265,7 +1265,7 @@ static int ti_bandgap_probe(struct platform_device *pdev) { struct ti_bandgap *bgp; - int clk_rate, ret = 0, i; + int clk_rate, ret, i; bgp = ti_bandgap_build(pdev); if (IS_ERR(bgp)) { @@ -1288,16 +1288,14 @@ int ti_bandgap_probe(struct platform_device *pdev) } bgp->fclock = clk_get(NULL, bgp->conf->fclock_name); - ret = IS_ERR(bgp->fclock); - if (ret) { + 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); - ret = IS_ERR(bgp->div_clk); - if (ret) { + 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 free_irqs; @@ -1314,7 +1312,7 @@ int ti_bandgap_probe(struct platform_device *pdev) * may not be accurate */ val = ti_bandgap_readl(bgp, tsr->bgap_efuse); - if (ret || !val) + if (!val) dev_info(&pdev->dev, "Non-trimmed BGAP, Temp not accurate\n"); } diff --git a/include/linux/thermal.h b/include/linux/thermal.h index e13a1ace50e9..a55d0523f75d 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -156,6 +156,7 @@ struct thermal_attr { * @trip_hyst_attrs: attributes for trip points for sysfs: trip hysteresis * @devdata: private pointer for device private data * @trips: number of trip points the thermal zone supports + * @trips_disabled; bitmap for disabled trips * @passive_delay: number of milliseconds to wait between polls when * performing passive cooling. * @polling_delay: number of milliseconds to wait between polls when @@ -191,6 +192,7 @@ struct thermal_zone_device { struct thermal_attr *trip_hyst_attrs; void *devdata; int trips; + unsigned long trips_disabled; /* bitmap for disabled trips */ int passive_delay; int polling_delay; int temperature; @@ -362,6 +364,11 @@ thermal_zone_of_sensor_register(struct device *dev, int id, void *data, const struct thermal_zone_of_device_ops *ops); void thermal_zone_of_sensor_unregister(struct device *dev, struct thermal_zone_device *tz); +struct thermal_zone_device *devm_thermal_zone_of_sensor_register( + struct device *dev, int id, void *data, + const struct thermal_zone_of_device_ops *ops); +void devm_thermal_zone_of_sensor_unregister(struct device *dev, + struct thermal_zone_device *tz); #else static inline struct thermal_zone_device * thermal_zone_of_sensor_register(struct device *dev, int id, void *data, @@ -376,6 +383,19 @@ void thermal_zone_of_sensor_unregister(struct device *dev, { } +static inline struct thermal_zone_device *devm_thermal_zone_of_sensor_register( + struct device *dev, int id, void *data, + const struct thermal_zone_of_device_ops *ops) +{ + return ERR_PTR(-ENODEV); +} + +static inline +void devm_thermal_zone_of_sensor_unregister(struct device *dev, + struct thermal_zone_device *tz) +{ +} + #endif #if IS_ENABLED(CONFIG_THERMAL) diff --git a/include/trace/events/thermal.h b/include/trace/events/thermal.h index 5738bb3e2343..2b4a8ff72d0d 100644 --- a/include/trace/events/thermal.h +++ b/include/trace/events/thermal.h @@ -8,6 +8,18 @@ #include <linux/thermal.h> #include <linux/tracepoint.h> +TRACE_DEFINE_ENUM(THERMAL_TRIP_CRITICAL); +TRACE_DEFINE_ENUM(THERMAL_TRIP_HOT); +TRACE_DEFINE_ENUM(THERMAL_TRIP_PASSIVE); +TRACE_DEFINE_ENUM(THERMAL_TRIP_ACTIVE); + +#define show_tzt_type(type) \ + __print_symbolic(type, \ + { THERMAL_TRIP_CRITICAL, "CRITICAL"}, \ + { THERMAL_TRIP_HOT, "HOT"}, \ + { THERMAL_TRIP_PASSIVE, "PASSIVE"}, \ + { THERMAL_TRIP_ACTIVE, "ACTIVE"}) + TRACE_EVENT(thermal_temperature, TP_PROTO(struct thermal_zone_device *tz), @@ -73,9 +85,9 @@ TRACE_EVENT(thermal_zone_trip, __entry->trip_type = trip_type; ), - TP_printk("thermal_zone=%s id=%d trip=%d trip_type=%d", + TP_printk("thermal_zone=%s id=%d trip=%d trip_type=%s", __get_str(thermal_zone), __entry->id, __entry->trip, - __entry->trip_type) + show_tzt_type(__entry->trip_type)) ); TRACE_EVENT(thermal_power_cpu_get_power, |