summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-06-15 09:05:11 +0300
committerLinus Torvalds <torvalds@linux-foundation.org>2026-06-15 09:05:11 +0300
commitf3c4a338b5064effd9ce6d2cfd26c73daa56f1f4 (patch)
tree98aefc57a16f09d6329d646a991dd6e8847632a4
parenteed451031f6ac098ca04ebdd63d34e083ba2d6f6 (diff)
parentbc1c872ce0ab3984f5fcefeb952541a802b891e7 (diff)
downloadlinux-f3c4a338b5064effd9ce6d2cfd26c73daa56f1f4.tar.xz
Merge tag 'thermal-7.2-rc1' of gitolite.kernel.org:pub/scm/linux/kernel/git/rafael/linux-pm
Pull thermal control updates from Rafael Wysocki: "These add new hardware support (i.MX93 TMU, Amlogic T7, Intel Arrow Lake, QCom Nord, Shikra and Hawi), fix issues in a number of places in the thermal control core and drivers, clean up code and refactor it in preparation for future changes: - Rework the initialization and cleanup of thermal class cooling devices to separate DT-based cooling device registration and cooling device registration without DT (Daniel Lezcano, Ovidiu Panait) - Update the cooling device DT bindings to support 3-cell cooling device representation, where the additional cell holds an ID to select a cooling mechanism for devices that offer multiple cooling mechanisms, and adjust the cooling device registration code accordingly (Gaurav Kohli, Daniel Lezcano) - Remove dead code from two functions in the thermal core and simplify the unregistration of thermal governors (Rafael Wysocki) - Fix critical temperature attribute removal handling in the generic thermal zone hwmon support code and rework that code to register a separate hwmon class device for each thermal zone (instead of using one hwmon class device for all thermal zones of the same type) to address thermal zone removal deadlocks (Rafael Wysocki) - Use attribute groups for adding temperature attributes to hwmon class devices associated with thermal zones (Rafael Wysocki) - Pass WQ_UNBOUND when allocating the thermal workqueue (Marco Crivellari) - Fix potential shift overflow in ptc_mmio_write() and improve error handling in proc_thermal_ptc_add() in the int340x thermal control driver (Aravind Anilraj) - Use sysfs_emit() for cpumask printing in the Intel powerclamp thermal driver (Yury Norov) - Add Arrow Lake CPU models to the intel_tcc_cooling driver (Srinivas Pandruvada) - Add QCom Nord, Shikra and Hawi temperature sensor DT bindings (Deepti Jaggi, Gaurav Kohli, Dipa Ramesh Mantre) - Use devm_add_action_or_reset() for clock disable on the NVidia soctherm and switch it to devm cooling device registration version (Daniel Lezcano) - Add the Amlogic T7 thermal sensor along with thermal calibration data read from SMC calls (Ronald Claveau) - Fix atomic temperature read in the QCom tsens driver to comply with hardware documentation (Priyansh Jain) - Add SpacemiT K1 thermal sensor support (Shuwei Wu) - Add i.MX93 temperature sensor support and filter out the invalid temperature (Jacky Bai) - Enable by default the TMU (Thermal Monitoring Unit) on Exynos platform (Krzysztof Kozlowski) - Rework interrupt initialization in the Tsens driver and add the optional wakeup source (Priyansh Jain) - Fix typo in a comment in the TSens QCom driver (Jinseok Kim) - Fix trailing whitespace and repeated word in the OF code, remove quoted string splitting across lines from the iMX7 driver, and remove a stray space from the thermal_trip_of_attr() macro definition (Mayur Kumar) - Update the thermal testing facility code to avoid NULL pointer dereferences by rejecting missing command arguments and replace sscanf() with kstrtoint() or kstrtoul() in that code (Ovidiu Panait, Samuel Moelius)" * tag 'thermal-7.2-rc1' of gitolite.kernel.org:pub/scm/linux/kernel/git/rafael/linux-pm: (54 commits) thermal: sysfs: Replace sscanf() with kstrtoul() thermal: testing: Replace sscanf() with kstrtoint() thermal: testing: reject missing command arguments thermal: intel: intel_tcc_cooling: Add Arrow Lake CPU models thermal/drivers/qcom/tsens: Disable wakeup interrupt setup on automotive targets thermal/drivers/qcom/tsens: Switch wake IRQ handling to PM callbacks thermal/core: Fix missing stub for devm_thermal_cooling_device_register dt-bindings: thermal: cooling-devices: Update support for 3 cells cooling device thermal/of: Support cooling device ID in cooling-spec thermal/of: Pass cdev_id and introduce devm registration helper thermal/of: Add cooling device ID support thermal/of: Rename the devm_thermal_of_cooling_device_register() function thermal/core: Make cooling device OF node conditional on CONFIG_THERMAL_OF thermal/of: Move cooling device OF helpers out of thermal core hwmon: Use non-OF thermal cooling device registration API thermal/core: Add devm_thermal_cooling_device_register() thermal/core: Introduce non-OF thermal_cooling_device_register() thermal/drivers/samsung: Enable TMU by default thermal/driver/qoriq: Workaround unexpected temperature readings from tmu thermal/drivers/qoriq: Add i.MX93 tmu support ...
-rw-r--r--Documentation/devicetree/bindings/hwmon/pwm-fan.yaml3
-rw-r--r--Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml37
-rw-r--r--Documentation/devicetree/bindings/thermal/qcom-tsens.yaml3
-rw-r--r--Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml1
-rw-r--r--Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml76
-rw-r--r--Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml8
-rw-r--r--Documentation/devicetree/bindings/thermal/thermal-zones.yaml3
-rw-r--r--drivers/firmware/meson/meson_sm.c28
-rw-r--r--drivers/gpu/drm/etnaviv/etnaviv_gpu.c5
-rw-r--r--drivers/hwmon/amc6821.c2
-rw-r--r--drivers/hwmon/aspeed-pwm-tacho.c5
-rw-r--r--drivers/hwmon/cros_ec_hwmon.c4
-rw-r--r--drivers/hwmon/dell-smm-hwmon.c4
-rw-r--r--drivers/hwmon/emc2305.c6
-rw-r--r--drivers/hwmon/gpio-fan.c6
-rw-r--r--drivers/hwmon/hwmon.c6
-rw-r--r--drivers/hwmon/max6650.c6
-rw-r--r--drivers/hwmon/mlxreg-fan.c4
-rw-r--r--drivers/hwmon/npcm750-pwm-fan.c6
-rw-r--r--drivers/hwmon/pwm-fan.c5
-rw-r--r--drivers/hwmon/qnap-mcu-hwmon.c6
-rw-r--r--drivers/hwmon/tc654.c5
-rw-r--r--drivers/memory/tegra/tegra210-emc-core.c4
-rw-r--r--drivers/soc/qcom/qcom_aoss.c2
-rw-r--r--drivers/thermal/Kconfig3
-rw-r--r--drivers/thermal/Makefile1
-rw-r--r--drivers/thermal/amlogic_thermal.c112
-rw-r--r--drivers/thermal/cpufreq_cooling.c2
-rw-r--r--drivers/thermal/cpuidle_cooling.c2
-rw-r--r--drivers/thermal/devfreq_cooling.c2
-rw-r--r--drivers/thermal/imx_thermal.c4
-rw-r--r--drivers/thermal/intel/int340x_thermal/platform_temperature_control.c12
-rw-r--r--drivers/thermal/intel/intel_powerclamp.c2
-rw-r--r--drivers/thermal/intel/intel_tcc_cooling.c3
-rw-r--r--drivers/thermal/khadas_mcu_fan.c7
-rw-r--r--drivers/thermal/qcom/tsens-v2.c9
-rw-r--r--drivers/thermal/qcom/tsens.c183
-rw-r--r--drivers/thermal/qcom/tsens.h24
-rw-r--r--drivers/thermal/qoriq_thermal.c71
-rw-r--r--drivers/thermal/samsung/Kconfig1
-rw-r--r--drivers/thermal/spacemit/Kconfig19
-rw-r--r--drivers/thermal/spacemit/Makefile3
-rw-r--r--drivers/thermal/spacemit/k1_tsensor.c280
-rw-r--r--drivers/thermal/tegra/soctherm.c34
-rw-r--r--drivers/thermal/testing/command.c12
-rw-r--r--drivers/thermal/testing/zone.c12
-rw-r--r--drivers/thermal/thermal_core.c347
-rw-r--r--drivers/thermal/thermal_core.h7
-rw-r--r--drivers/thermal/thermal_hwmon.c220
-rw-r--r--drivers/thermal/thermal_of.c152
-rw-r--r--drivers/thermal/thermal_sysfs.c10
-rw-r--r--include/linux/firmware/meson/meson_sm.h3
-rw-r--r--include/linux/hwmon.h3
-rw-r--r--include/linux/thermal.h71
54 files changed, 1279 insertions, 567 deletions
diff --git a/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml b/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml
index a84cc3a4cfdc..6a24851fd80d 100644
--- a/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml
+++ b/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml
@@ -63,7 +63,8 @@ properties:
description: The PWM that is used to control the fan.
maxItems: 1
- "#cooling-cells": true
+ "#cooling-cells":
+ const: 2
required:
- compatible
diff --git a/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml b/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml
index 70b273271754..e28612510d67 100644
--- a/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml
+++ b/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml
@@ -21,7 +21,9 @@ properties:
- amlogic,g12a-cpu-thermal
- amlogic,g12a-ddr-thermal
- const: amlogic,g12a-thermal
- - const: amlogic,a1-cpu-thermal
+ - enum:
+ - amlogic,a1-cpu-thermal
+ - amlogic,t7-thermal
reg:
maxItems: 1
@@ -42,12 +44,34 @@ properties:
'#thermal-sensor-cells':
const: 0
+ amlogic,secure-monitor:
+ description: phandle to the secure monitor
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ items:
+ - items:
+ - description: phandle to the secure monitor
+ - description: sensor index to get specific calibration data
+
required:
- compatible
- reg
- interrupts
- clocks
- - amlogic,ao-secure
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - amlogic,a1-cpu-thermal
+ - amlogic,g12a-thermal
+ then:
+ required:
+ - amlogic,ao-secure
+ else:
+ required:
+ - amlogic,secure-monitor
unevaluatedProperties: false
@@ -62,4 +86,13 @@ examples:
#thermal-sensor-cells = <0>;
amlogic,ao-secure = <&sec_AO>;
};
+ - |
+ temperature-sensor@20000 {
+ compatible = "amlogic,t7-thermal";
+ reg = <0x0 0x20000 0x0 0x50>;
+ interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clkc_periphs CLKID_TS>;
+ #thermal-sensor-cells = <0>;
+ amlogic,secure-monitor = <&sm 1>;
+ };
...
diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
index 7d34ba00e684..f0efaa8349ee 100644
--- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
+++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
@@ -56,8 +56,10 @@ properties:
- enum:
- qcom,eliza-tsens
- qcom,glymur-tsens
+ - qcom,hawi-tsens
- qcom,kaanapali-tsens
- qcom,milos-tsens
+ - qcom,nord-tsens
- qcom,msm8953-tsens
- qcom,msm8996-tsens
- qcom,msm8998-tsens
@@ -74,6 +76,7 @@ properties:
- qcom,sdm630-tsens
- qcom,sdm670-tsens
- qcom,sdm845-tsens
+ - qcom,shikra-tsens
- qcom,sm6115-tsens
- qcom,sm6350-tsens
- qcom,sm6375-tsens
diff --git a/Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml b/Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml
index aa756dae512a..f3b136f5e1cb 100644
--- a/Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml
+++ b/Documentation/devicetree/bindings/thermal/qoriq-thermal.yaml
@@ -25,6 +25,7 @@ properties:
enum:
- fsl,qoriq-tmu
- fsl,imx8mq-tmu
+ - fsl,imx93-tmu
reg:
maxItems: 1
diff --git a/Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml b/Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml
new file mode 100644
index 000000000000..6dad76a7dd36
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/spacemit,k1-tsensor.yaml
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/thermal/spacemit,k1-tsensor.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SpacemiT K1 Thermal Sensor
+
+description:
+ The SpacemiT K1 Thermal Sensor monitors the temperature of the SoC
+ using multiple internal sensors (e.g., soc, package, gpu, clusters).
+
+maintainers:
+ - Shuwei Wu <shuwei.wu@mailbox.org>
+
+$ref: thermal-sensor.yaml#
+
+properties:
+ compatible:
+ const: spacemit,k1-tsensor
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: Core clock for thermal sensor
+ - description: Bus clock for thermal sensor
+
+ clock-names:
+ items:
+ - const: core
+ - const: bus
+
+ interrupts:
+ maxItems: 1
+
+ resets:
+ items:
+ - description: Reset for the thermal sensor
+
+ "#thermal-sensor-cells":
+ const: 1
+ description:
+ The first cell indicates the sensor ID.
+ 0 = soc
+ 1 = package
+ 2 = gpu
+ 3 = cluster0
+ 4 = cluster1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - interrupts
+ - resets
+ - "#thermal-sensor-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/spacemit,k1-syscon.h>
+
+ thermal@d4018000 {
+ compatible = "spacemit,k1-tsensor";
+ reg = <0xd4018000 0x100>;
+ clocks = <&syscon_apbc CLK_TSEN>,
+ <&syscon_apbc CLK_TSEN_BUS>;
+ clock-names = "core", "bus";
+ interrupts = <61>;
+ resets = <&syscon_apbc RESET_TSEN>;
+ #thermal-sensor-cells = <1>;
+ };
diff --git a/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml b/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml
index b9022f1613d8..28f5818f1e60 100644
--- a/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml
+++ b/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml
@@ -44,10 +44,14 @@ select: true
properties:
"#cooling-cells":
description:
- Must be 2, in order to specify minimum and maximum cooling state used in
+ Must be 2 or 3. If 2, specifies minimum and maximum cooling state used in
the cooling-maps reference. The first cell is the minimum cooling state
and the second cell is the maximum cooling state requested.
- const: 2
+ If 3, the first cell specifies the thermal mitigation device specifier
+ index for devices that support multiple thermal mitigation mechanisms.
+ The two other cells are respectively the minimum cooling state and the
+ maximum cooling state.
+ enum: [2, 3]
additionalProperties: true
diff --git a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
index 07d9f576ffe7..999ad40a20d5 100644
--- a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
+++ b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
@@ -211,7 +211,8 @@ patternProperties:
device. Using the THERMAL_NO_LIMIT (-1UL) constant in the
cooling-device phandle limit specifier lets the framework
use the minimum and maximum cooling state for that cooling
- device automatically.
+ device automatically. If three arguments are specified,
+ the first argument is the cooling device specifier.
contribution:
$ref: /schemas/types.yaml#/definitions/uint32
diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c
index 3ab67aaa9e5d..ab9751a59b55 100644
--- a/drivers/firmware/meson/meson_sm.c
+++ b/drivers/firmware/meson/meson_sm.c
@@ -41,12 +41,13 @@ static const struct meson_sm_chip gxbb_chip = {
.cmd_shmem_in_base = 0x82000020,
.cmd_shmem_out_base = 0x82000021,
.cmd = {
- CMD(SM_EFUSE_READ, 0x82000030),
- CMD(SM_EFUSE_WRITE, 0x82000031),
+ CMD(SM_EFUSE_READ, 0x82000030),
+ CMD(SM_EFUSE_WRITE, 0x82000031),
CMD(SM_EFUSE_USER_MAX, 0x82000033),
- CMD(SM_GET_CHIP_ID, 0x82000044),
- CMD(SM_A1_PWRC_SET, 0x82000093),
- CMD(SM_A1_PWRC_GET, 0x82000095),
+ CMD(SM_GET_CHIP_ID, 0x82000044),
+ CMD(SM_THERMAL_CALIB_READ, 0x82000047),
+ CMD(SM_A1_PWRC_SET, 0x82000093),
+ CMD(SM_A1_PWRC_GET, 0x82000095),
{ /* sentinel */ },
},
};
@@ -245,6 +246,23 @@ struct meson_sm_firmware *meson_sm_get(struct device_node *sm_node)
}
EXPORT_SYMBOL_GPL(meson_sm_get);
+/**
+ * meson_sm_get_thermal_calib - Read thermal sensor calibration data.
+ * @fw: Pointer to secure-monitor firmware.
+ * @trim_info: Pointer to store the returned calibration data.
+ * @tsensor_id: Sensor index to identify which sensor's calibration data
+ * to retrieve
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info,
+ u32 tsensor_id)
+{
+ return meson_sm_call(fw, SM_THERMAL_CALIB_READ, trim_info, tsensor_id,
+ 0, 0, 0, 0);
+}
+EXPORT_SYMBOL_GPL(meson_sm_get_thermal_calib);
+
#define SM_CHIP_ID_LENGTH 119
#define SM_CHIP_ID_OFFSET 4
#define SM_CHIP_ID_SIZE 12
diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
index a891d4f1f843..552631c3554a 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c
@@ -1791,8 +1791,9 @@ static int etnaviv_gpu_bind(struct device *dev, struct device *master,
int ret;
if (IS_ENABLED(CONFIG_DRM_ETNAVIV_THERMAL)) {
- gpu->cooling = thermal_of_cooling_device_register(dev->of_node,
- (char *)dev_name(dev), gpu, &cooling_ops);
+ gpu->cooling = thermal_of_cooling_device_register(dev->of_node, 0,
+ dev_name(dev),
+ gpu, &cooling_ops);
if (IS_ERR(gpu->cooling))
return PTR_ERR(gpu->cooling);
}
diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c
index d5f864b360b0..8e5926b06070 100644
--- a/drivers/hwmon/amc6821.c
+++ b/drivers/hwmon/amc6821.c
@@ -1076,7 +1076,7 @@ static int amc6821_probe(struct i2c_client *client)
"Failed to initialize hwmon\n");
if (IS_ENABLED(CONFIG_THERMAL) && fan_np && data->fan_cooling_levels)
- return PTR_ERR_OR_ZERO(devm_thermal_of_cooling_device_register(dev,
+ return PTR_ERR_OR_ZERO(devm_thermal_of_child_cooling_device_register(dev,
fan_np, client->name, data, &amc6821_cooling_ops));
return 0;
diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c
index aa159bf158a3..1c5945d4ba37 100644
--- a/drivers/hwmon/aspeed-pwm-tacho.c
+++ b/drivers/hwmon/aspeed-pwm-tacho.c
@@ -841,8 +841,9 @@ static int aspeed_create_pwm_cooling(struct device *dev,
}
snprintf(cdev->name, MAX_CDEV_NAME_LEN, "%pOFn%d", child, pwm_port);
- cdev->tcdev = devm_thermal_of_cooling_device_register(dev, child,
- cdev->name, cdev, &aspeed_pwm_cool_ops);
+ cdev->tcdev = devm_thermal_of_child_cooling_device_register(dev, child,
+ cdev->name, cdev,
+ &aspeed_pwm_cool_ops);
if (IS_ERR(cdev->tcdev))
return PTR_ERR(cdev->tcdev);
diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c
index 6cf5ab0f4b73..77dd9f28962d 100644
--- a/drivers/hwmon/cros_ec_hwmon.c
+++ b/drivers/hwmon/cros_ec_hwmon.c
@@ -532,8 +532,8 @@ static void cros_ec_hwmon_register_fan_cooling_devices(struct device *dev,
cpriv->hwmon_priv = priv;
cpriv->index = i;
- cdev = devm_thermal_of_cooling_device_register(dev, NULL, type, cpriv,
- &cros_ec_thermal_cooling_ops);
+ cdev = devm_thermal_cooling_device_register(dev, type, cpriv,
+ &cros_ec_thermal_cooling_ops);
if (IS_ERR(cdev)) {
dev_warn(dev, "failed to register fan %zu as a cooling device: %pe\n", i,
cdev);
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 038edffc1ac7..47b373ea6db4 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -1161,8 +1161,8 @@ static int dell_smm_init_cdev(struct device *dev, u8 fan_num)
if (cdata) {
cdata->fan_num = fan_num;
cdata->data = data;
- cdev = devm_thermal_of_cooling_device_register(dev, NULL, name, cdata,
- &dell_smm_cooling_ops);
+ cdev = devm_thermal_cooling_device_register(dev, name, cdata,
+ &dell_smm_cooling_ops);
if (IS_ERR(cdev)) {
devm_kfree(dev, cdata);
ret = PTR_ERR(cdev);
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index 64b213e1451e..2505e9fac499 100644
--- a/drivers/hwmon/emc2305.c
+++ b/drivers/hwmon/emc2305.c
@@ -309,9 +309,9 @@ static int emc2305_set_single_tz(struct device *dev, struct device_node *fan_nod
pwm = data->pwm_min[cdev_idx];
data->cdev_data[cdev_idx].cdev =
- devm_thermal_of_cooling_device_register(dev, fan_node,
- emc2305_fan_name[idx], data,
- &emc2305_cooling_ops);
+ devm_thermal_of_child_cooling_device_register(dev, fan_node,
+ emc2305_fan_name[idx], data,
+ &emc2305_cooling_ops);
if (IS_ERR(data->cdev_data[cdev_idx].cdev)) {
dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]);
diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c
index a8892ced1e54..084828e1e281 100644
--- a/drivers/hwmon/gpio-fan.c
+++ b/drivers/hwmon/gpio-fan.c
@@ -592,8 +592,10 @@ static int gpio_fan_probe(struct platform_device *pdev)
}
/* Optional cooling device register for Device tree platforms */
- fan_data->cdev = devm_thermal_of_cooling_device_register(dev, np,
- "gpio-fan", fan_data, &gpio_fan_cool_ops);
+ fan_data->cdev = devm_thermal_of_child_cooling_device_register(dev, np,
+ "gpio-fan",
+ fan_data,
+ &gpio_fan_cool_ops);
dev_info(dev, "GPIO fan initialized\n");
diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c
index 6812d1fd7c28..c16e6da0690c 100644
--- a/drivers/hwmon/hwmon.c
+++ b/drivers/hwmon/hwmon.c
@@ -1082,6 +1082,7 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info);
* @dev: the parent device
* @name: hwmon name attribute
* @drvdata: driver data to attach to created device
+ * @extra_groups: pointer to list of additional non-standard attribute groups
*
* The use of this function is restricted. It is provided for legacy reasons
* and must only be called from the thermal subsystem.
@@ -1093,12 +1094,13 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info);
*/
struct device *
hwmon_device_register_for_thermal(struct device *dev, const char *name,
- void *drvdata)
+ void *drvdata,
+ const struct attribute_group **extra_groups)
{
if (!name || !dev)
return ERR_PTR(-EINVAL);
- return __hwmon_device_register(dev, name, drvdata, NULL, NULL);
+ return __hwmon_device_register(dev, name, drvdata, NULL, extra_groups);
}
EXPORT_SYMBOL_NS_GPL(hwmon_device_register_for_thermal, "HWMON_THERMAL");
diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c
index 56b8157885bb..3466edd7d501 100644
--- a/drivers/hwmon/max6650.c
+++ b/drivers/hwmon/max6650.c
@@ -794,9 +794,9 @@ static int max6650_probe(struct i2c_client *client)
return err;
if (IS_ENABLED(CONFIG_THERMAL)) {
- cooling_dev = devm_thermal_of_cooling_device_register(dev,
- dev->of_node, client->name,
- data, &max6650_cooling_ops);
+ cooling_dev = devm_thermal_of_child_cooling_device_register(dev, dev->of_node,
+ client->name, data,
+ &max6650_cooling_ops);
if (IS_ERR(cooling_dev)) {
dev_warn(dev, "thermal cooling device register failed: %ld\n",
PTR_ERR(cooling_dev));
diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c
index 137a90dd2075..860de6cfd8a4 100644
--- a/drivers/hwmon/mlxreg-fan.c
+++ b/drivers/hwmon/mlxreg-fan.c
@@ -583,8 +583,8 @@ static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan)
pwm->fan = fan;
/* Set minimal PWM speed. */
pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(MLXREG_FAN_MIN_DUTY);
- pwm->cdev = devm_thermal_of_cooling_device_register(dev, NULL, mlxreg_fan_name[i],
- pwm, &mlxreg_fan_cooling_ops);
+ pwm->cdev = devm_thermal_cooling_device_register(dev, mlxreg_fan_name[i],
+ pwm, &mlxreg_fan_cooling_ops);
if (IS_ERR(pwm->cdev)) {
dev_err(dev, "Failed to register cooling device\n");
return PTR_ERR(pwm->cdev);
diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c
index c8f5e695fb6d..aea0b8659f5f 100644
--- a/drivers/hwmon/npcm750-pwm-fan.c
+++ b/drivers/hwmon/npcm750-pwm-fan.c
@@ -857,8 +857,10 @@ static int npcm7xx_create_pwm_cooling(struct device *dev,
snprintf(cdev->name, THERMAL_NAME_LENGTH, "%pOFn%d", child,
pwm_port);
- cdev->tcdev = devm_thermal_of_cooling_device_register(dev, child,
- cdev->name, cdev, &npcm7xx_pwm_cool_ops);
+ cdev->tcdev = devm_thermal_of_child_cooling_device_register(dev, child,
+ cdev->name,
+ cdev,
+ &npcm7xx_pwm_cool_ops);
if (IS_ERR(cdev->tcdev))
return PTR_ERR(cdev->tcdev);
diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c
index 37269db2de84..e6a567d58579 100644
--- a/drivers/hwmon/pwm-fan.c
+++ b/drivers/hwmon/pwm-fan.c
@@ -685,8 +685,9 @@ static int pwm_fan_probe(struct platform_device *pdev)
ctx->pwm_fan_state = ctx->pwm_fan_max_state;
if (IS_ENABLED(CONFIG_THERMAL)) {
- cdev = devm_thermal_of_cooling_device_register(dev,
- dev->of_node, "pwm-fan", ctx, &pwm_fan_cooling_ops);
+ cdev = devm_thermal_of_child_cooling_device_register(dev, dev->of_node,
+ "pwm-fan", ctx,
+ &pwm_fan_cooling_ops);
if (IS_ERR(cdev)) {
ret = PTR_ERR(cdev);
dev_err(dev,
diff --git a/drivers/hwmon/qnap-mcu-hwmon.c b/drivers/hwmon/qnap-mcu-hwmon.c
index e86e64c4d391..c1c1e9d6f340 100644
--- a/drivers/hwmon/qnap-mcu-hwmon.c
+++ b/drivers/hwmon/qnap-mcu-hwmon.c
@@ -337,9 +337,9 @@ static int qnap_mcu_hwmon_probe(struct platform_device *pdev)
* levels and only succeed with either no or correct cooling levels.
*/
if (IS_ENABLED(CONFIG_THERMAL) && hwm->fan_cooling_levels) {
- cdev = devm_thermal_of_cooling_device_register(dev,
- to_of_node(hwm->fan_node), "qnap-mcu-hwmon",
- hwm, &qnap_mcu_hwmon_cooling_ops);
+ cdev = devm_thermal_of_child_cooling_device_register(dev, to_of_node(hwm->fan_node),
+ "qnap-mcu-hwmon", hwm,
+ &qnap_mcu_hwmon_cooling_ops);
if (IS_ERR(cdev))
return dev_err_probe(dev, PTR_ERR(cdev),
"Failed to register qnap-mcu-hwmon as cooling device\n");
diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c
index 39fe5836f237..ba18b442b81e 100644
--- a/drivers/hwmon/tc654.c
+++ b/drivers/hwmon/tc654.c
@@ -541,8 +541,9 @@ static int tc654_probe(struct i2c_client *client)
if (IS_ENABLED(CONFIG_THERMAL)) {
struct thermal_cooling_device *cdev;
- cdev = devm_thermal_of_cooling_device_register(dev, dev->of_node, client->name,
- hwmon_dev, &tc654_fan_cool_ops);
+ cdev = devm_thermal_of_child_cooling_device_register(dev, dev->of_node,
+ client->name, hwmon_dev,
+ &tc654_fan_cool_ops);
return PTR_ERR_OR_ZERO(cdev);
}
diff --git a/drivers/memory/tegra/tegra210-emc-core.c b/drivers/memory/tegra/tegra210-emc-core.c
index e96ca4157d48..065ae8bc2830 100644
--- a/drivers/memory/tegra/tegra210-emc-core.c
+++ b/drivers/memory/tegra/tegra210-emc-core.c
@@ -1966,8 +1966,8 @@ static int tegra210_emc_probe(struct platform_device *pdev)
tegra210_emc_debugfs_init(emc);
- cd = devm_thermal_of_cooling_device_register(emc->dev, np, "emc", emc,
- &tegra210_emc_cd_ops);
+ cd = devm_thermal_of_child_cooling_device_register(emc->dev, np, "emc", emc,
+ &tegra210_emc_cd_ops);
if (IS_ERR(cd)) {
err = PTR_ERR(cd);
dev_err(emc->dev, "failed to register cooling device: %d\n",
diff --git a/drivers/soc/qcom/qcom_aoss.c b/drivers/soc/qcom/qcom_aoss.c
index c255662b8fc3..259c41f0c34e 100644
--- a/drivers/soc/qcom/qcom_aoss.c
+++ b/drivers/soc/qcom/qcom_aoss.c
@@ -381,7 +381,7 @@ static int qmp_cooling_device_add(struct qmp *qmp,
qmp_cdev->qmp = qmp;
qmp_cdev->state = !qmp_cdev_max_state;
qmp_cdev->name = cdev_name;
- qmp_cdev->cdev = devm_thermal_of_cooling_device_register
+ qmp_cdev->cdev = devm_thermal_of_child_cooling_device_register
(qmp->dev, node,
cdev_name,
qmp_cdev, &qmp_cooling_device_ops);
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index b10080d61860..810eeccedfba 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -436,6 +436,7 @@ config AMLOGIC_THERMAL
tristate "Amlogic Thermal Support"
default ARCH_MESON
depends on OF && ARCH_MESON
+ depends on MESON_SM
help
If you say yes here you get support for Amlogic Thermal
for G12 SoC Family.
@@ -472,6 +473,8 @@ endmenu
source "drivers/thermal/renesas/Kconfig"
+source "drivers/thermal/spacemit/Kconfig"
+
source "drivers/thermal/tegra/Kconfig"
config GENERIC_ADC_THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index bb21e7ea7fc6..3b249195c088 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -65,6 +65,7 @@ obj-y += mediatek/
obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o
obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o
obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o
+obj-y += spacemit/
obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o
obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL) += khadas_mcu_fan.o
obj-$(CONFIG_LOONGSON2_THERMAL) += loongson2_thermal.o
diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c
index 5448d772db12..a0b530624b60 100644
--- a/drivers/thermal/amlogic_thermal.c
+++ b/drivers/thermal/amlogic_thermal.c
@@ -25,6 +25,7 @@
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/thermal.h>
+#include <linux/firmware/meson/meson_sm.h>
#include "thermal_hwmon.h"
@@ -84,12 +85,14 @@ struct amlogic_thermal_soc_calib_data {
* @u_efuse_off: register offset to read fused calibration value
* @calibration_parameters: calibration parameters structure pointer
* @regmap_config: regmap config for the device
+ * @use_sm: read data from secure monitor instead of efuse
* This structure is required for configuration of amlogic thermal driver.
*/
struct amlogic_thermal_data {
int u_efuse_off;
const struct amlogic_thermal_soc_calib_data *calibration_parameters;
const struct regmap_config *regmap_config;
+ bool use_sm;
};
struct amlogic_thermal {
@@ -100,6 +103,8 @@ struct amlogic_thermal {
struct clk *clk;
struct thermal_zone_device *tzd;
u32 trim_info;
+ struct meson_sm_firmware *sm_fw;
+ u32 tsensor_id;
};
/*
@@ -133,26 +138,6 @@ static int amlogic_thermal_code_to_millicelsius(struct amlogic_thermal *pdata,
return temp;
}
-static int amlogic_thermal_initialize(struct amlogic_thermal *pdata)
-{
- int ret = 0;
- int ver;
-
- regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off,
- &pdata->trim_info);
-
- ver = TSENSOR_TRIM_VERSION(pdata->trim_info);
-
- if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) {
- ret = -EINVAL;
- dev_err(&pdata->pdev->dev,
- "tsensor thermal calibration not supported: 0x%x!\n",
- ver);
- }
-
- return ret;
-}
-
static int amlogic_thermal_enable(struct amlogic_thermal *data)
{
int ret;
@@ -190,6 +175,67 @@ static int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
return 0;
}
+static int amlogic_thermal_probe_sm(struct platform_device *pdev,
+ struct amlogic_thermal *pdata)
+{
+ struct device *dev = &pdev->dev;
+ struct of_phandle_args ph_args;
+ int ret;
+
+ ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+ "amlogic,secure-monitor",
+ 1, 0, &ph_args);
+ if (ret)
+ return ret;
+
+ if (!ph_args.np) {
+ dev_err(dev, "Failed to parse secure monitor phandle\n");
+ return -ENODEV;
+ }
+
+ pdata->sm_fw = meson_sm_get(ph_args.np);
+ of_node_put(ph_args.np);
+ if (!pdata->sm_fw) {
+ dev_err(dev, "Failed to get secure monitor firmware\n");
+ return -EPROBE_DEFER;
+ }
+
+ pdata->tsensor_id = ph_args.args[0];
+
+ return meson_sm_get_thermal_calib(pdata->sm_fw,
+ &pdata->trim_info,
+ pdata->tsensor_id);
+}
+
+static int amlogic_thermal_probe_syscon(struct platform_device *pdev,
+ struct amlogic_thermal *pdata)
+{
+ struct device *dev = &pdev->dev;
+ int ver;
+
+ pdata->sec_ao_map =
+ syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "amlogic,ao-secure");
+ if (IS_ERR(pdata->sec_ao_map)) {
+ dev_err(dev, "syscon regmap lookup failed.\n");
+ return PTR_ERR(pdata->sec_ao_map);
+ }
+
+ regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off,
+ &pdata->trim_info);
+
+ ver = TSENSOR_TRIM_VERSION(pdata->trim_info);
+
+ if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) {
+ dev_err(&pdata->pdev->dev,
+ "tsensor thermal calibration not supported: 0x%x!\n",
+ ver);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static const struct thermal_zone_device_ops amlogic_thermal_ops = {
.get_temp = amlogic_thermal_get_temp,
};
@@ -226,6 +272,12 @@ static const struct amlogic_thermal_data amlogic_thermal_a1_cpu_param = {
.regmap_config = &amlogic_thermal_regmap_config_g12a,
};
+static const struct amlogic_thermal_data amlogic_thermal_t7_param = {
+ .use_sm = true,
+ .calibration_parameters = &amlogic_thermal_g12a,
+ .regmap_config = &amlogic_thermal_regmap_config_g12a,
+};
+
static const struct of_device_id of_amlogic_thermal_match[] = {
{
.compatible = "amlogic,g12a-ddr-thermal",
@@ -239,6 +291,10 @@ static const struct of_device_id of_amlogic_thermal_match[] = {
.compatible = "amlogic,a1-cpu-thermal",
.data = &amlogic_thermal_a1_cpu_param,
},
+ {
+ .compatible = "amlogic,t7-thermal",
+ .data = &amlogic_thermal_t7_param,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_amlogic_thermal_match);
@@ -271,12 +327,12 @@ static int amlogic_thermal_probe(struct platform_device *pdev)
if (IS_ERR(pdata->clk))
return dev_err_probe(dev, PTR_ERR(pdata->clk), "failed to get clock\n");
- pdata->sec_ao_map = syscon_regmap_lookup_by_phandle
- (pdev->dev.of_node, "amlogic,ao-secure");
- if (IS_ERR(pdata->sec_ao_map)) {
- dev_err(dev, "syscon regmap lookup failed.\n");
- return PTR_ERR(pdata->sec_ao_map);
- }
+ if (pdata->data->use_sm)
+ ret = amlogic_thermal_probe_sm(pdev, pdata);
+ else
+ ret = amlogic_thermal_probe_syscon(pdev, pdata);
+ if (ret)
+ return ret;
pdata->tzd = devm_thermal_of_zone_register(&pdev->dev,
0,
@@ -290,10 +346,6 @@ static int amlogic_thermal_probe(struct platform_device *pdev)
devm_thermal_add_hwmon_sysfs(&pdev->dev, pdata->tzd);
- ret = amlogic_thermal_initialize(pdata);
- if (ret)
- return ret;
-
ret = amlogic_thermal_enable(pdata);
return ret;
diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c
index 32bf5ab44f4a..768859a7aed0 100644
--- a/drivers/thermal/cpufreq_cooling.c
+++ b/drivers/thermal/cpufreq_cooling.c
@@ -592,7 +592,7 @@ __cpufreq_cooling_register(struct device_node *np,
if (!name)
goto remove_qos_req;
- cdev = thermal_of_cooling_device_register(np, name, cpufreq_cdev,
+ cdev = thermal_of_cooling_device_register(np, 0, name, cpufreq_cdev,
cooling_ops);
kfree(name);
diff --git a/drivers/thermal/cpuidle_cooling.c b/drivers/thermal/cpuidle_cooling.c
index 425f596614e8..bbd2e91cf5ab 100644
--- a/drivers/thermal/cpuidle_cooling.c
+++ b/drivers/thermal/cpuidle_cooling.c
@@ -207,7 +207,7 @@ static int __cpuidle_cooling_register(struct device_node *np,
goto out_unregister;
}
- cdev = thermal_of_cooling_device_register(np, name, idle_cdev,
+ cdev = thermal_of_cooling_device_register(np, 0, name, idle_cdev,
&cpuidle_cooling_ops);
if (IS_ERR(cdev)) {
ret = PTR_ERR(cdev);
diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c
index 1c7dffc8d45f..0330a8112832 100644
--- a/drivers/thermal/devfreq_cooling.c
+++ b/drivers/thermal/devfreq_cooling.c
@@ -454,7 +454,7 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
if (!name)
goto remove_qos_req;
- cdev = thermal_of_cooling_device_register(np, name, dfc, ops);
+ cdev = thermal_of_cooling_device_register(np, 0, name, dfc, ops);
kfree(name);
if (IS_ERR(cdev)) {
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
index 38c993d1bcb3..5aaacbc53478 100644
--- a/drivers/thermal/imx_thermal.c
+++ b/drivers/thermal/imx_thermal.c
@@ -693,8 +693,8 @@ static int imx_thermal_probe(struct platform_device *pdev)
goto clk_disable;
}
- dev_info(dev, "%s CPU temperature grade - max:%dC"
- " critical:%dC passive:%dC\n", data->temp_grade,
+ dev_info(dev, "%s CPU temperature grade - max:%dC critical:%dC passive:%dC\n",
+ data->temp_grade,
data->temp_max / 1000, trips[IMX_TRIP_CRITICAL].temperature / 1000,
trips[IMX_TRIP_PASSIVE].temperature / 1000);
diff --git a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c
index 0ccc72c93499..d92a6f84a778 100644
--- a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c
+++ b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c
@@ -138,7 +138,7 @@ static void ptc_mmio_write(struct pci_dev *pdev, u32 offset, int index, u32 valu
reg_val = readq((void __iomem *) (proc_priv->mmio_base + offset));
reg_val &= ~mask;
- reg_val |= (value << ptc_mmio_regs[index].shift);
+ reg_val |= ((u64)value << ptc_mmio_regs[index].shift);
writeq(reg_val, (void __iomem *) (proc_priv->mmio_base + offset));
}
@@ -278,12 +278,18 @@ static void ptc_delete_debugfs(void)
int proc_thermal_ptc_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
{
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_PTC) {
- int i;
+ int i, ret;
for (i = 0; i < PTC_MAX_INSTANCES; i++) {
ptc_instance[i].offset = ptc_offsets[i];
ptc_instance[i].pdev = pdev;
- ptc_create_groups(pdev, i, &ptc_instance[i]);
+ ret = ptc_create_groups(pdev, i, &ptc_instance[i]);
+ if (ret) {
+ while (i--)
+ sysfs_remove_group(&pdev->dev.kobj,
+ &ptc_instance[i].ptc_attr_group);
+ return ret;
+ }
}
ptc_create_debugfs();
diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c
index ccf380da12f2..bd7fd98dc310 100644
--- a/drivers/thermal/intel/intel_powerclamp.c
+++ b/drivers/thermal/intel/intel_powerclamp.c
@@ -200,7 +200,7 @@ static int cpumask_get(char *buf, const struct kernel_param *kp)
if (!cpumask_available(idle_injection_cpu_mask))
return -ENODEV;
- return cpumap_print_to_pagebuf(false, buf, idle_injection_cpu_mask);
+ return sysfs_emit(buf, "%*pb\n", cpumask_pr_args(idle_injection_cpu_mask));
}
static const struct kernel_param_ops cpumask_ops = {
diff --git a/drivers/thermal/intel/intel_tcc_cooling.c b/drivers/thermal/intel/intel_tcc_cooling.c
index 6c2ba0ba3178..52ea4f7fc9f9 100644
--- a/drivers/thermal/intel/intel_tcc_cooling.c
+++ b/drivers/thermal/intel/intel_tcc_cooling.c
@@ -65,6 +65,9 @@ static const struct x86_cpu_id tcc_ids[] __initconst = {
X86_MATCH_VFM(INTEL_RAPTORLAKE, NULL),
X86_MATCH_VFM(INTEL_RAPTORLAKE_P, NULL),
X86_MATCH_VFM(INTEL_RAPTORLAKE_S, NULL),
+ X86_MATCH_VFM(INTEL_ARROWLAKE, NULL),
+ X86_MATCH_VFM(INTEL_ARROWLAKE_U, NULL),
+ X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL),
X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL),
X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL),
X86_MATCH_VFM(INTEL_NOVALAKE, NULL),
diff --git a/drivers/thermal/khadas_mcu_fan.c b/drivers/thermal/khadas_mcu_fan.c
index d35e5313bea4..21b3d0a71bd0 100644
--- a/drivers/thermal/khadas_mcu_fan.c
+++ b/drivers/thermal/khadas_mcu_fan.c
@@ -90,9 +90,10 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev)
ctx->mcu = mcu;
platform_set_drvdata(pdev, ctx);
- cdev = devm_thermal_of_cooling_device_register(dev->parent,
- dev->parent->of_node, "khadas-mcu-fan", ctx,
- &khadas_mcu_fan_cooling_ops);
+ cdev = devm_thermal_of_child_cooling_device_register(dev->parent,
+ dev->parent->of_node,
+ "khadas-mcu-fan", ctx,
+ &khadas_mcu_fan_cooling_ops);
if (IS_ERR(cdev)) {
ret = PTR_ERR(cdev);
dev_err(dev, "Failed to register khadas-mcu-fan as cooling device: %d\n",
diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
index 8d9698ea3ec4..2ee117aa91ba 100644
--- a/drivers/thermal/qcom/tsens-v2.c
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -263,7 +263,6 @@ static int __init init_tsens_v2_no_rpm(struct tsens_priv *priv)
static const struct tsens_ops ops_generic_v2 = {
.init = init_common,
.get_temp = get_temp_tsens_valid,
- .resume = tsens_resume_common,
};
struct tsens_plat_data data_tsens_v2 = {
@@ -307,3 +306,11 @@ struct tsens_plat_data data_8996 = {
.feat = &tsens_v2_feat,
.fields = tsens_v2_regfields,
};
+
+/* Do not enable wakeup capable interrupts for automotive platforms */
+struct tsens_plat_data data_automotive_v2 = {
+ .ops = &ops_generic_v2,
+ .feat = &tsens_v2_feat,
+ .fields = tsens_v2_regfields,
+ .no_irq_wake = true,
+};
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
index a2422ebee816..6e3714ecab1d 100644
--- a/drivers/thermal/qcom/tsens.c
+++ b/drivers/thermal/qcom/tsens.c
@@ -27,7 +27,7 @@
* @up_viol: upper threshold violated
* @up_thresh: upper threshold temperature value
* @up_irq_mask: mask register for upper threshold irqs
- * @up_irq_clear: clear register for uppper threshold irqs
+ * @up_irq_clear: clear register for upper threshold irqs
* @low_viol: lower threshold violated
* @low_thresh: lower threshold temperature value
* @low_irq_mask: mask register for lower threshold irqs
@@ -316,9 +316,66 @@ static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
}
/**
- * tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
+ * tsens_read_temp - Retrieve temperature readings from the hardware.
* @s: Pointer to sensor struct
* @field: Index into regmap_field array pointing to temperature data
+ * @temp: temperature in deciCelsius to be read from hardware
+ *
+ * This function handles temperature returned in ADC code or deciCelsius
+ * depending on IP version.
+ *
+ * Return: 0 on success, a negative errno will be returned in error cases
+ */
+static int tsens_read_temp(const struct tsens_sensor *s, int field, int *temp)
+{
+ struct tsens_priv *priv = s->priv;
+ int temp_val[MAX_READ_RETRY] = {0};
+ u32 status;
+ int ret;
+ u32 last_temp_mask = GENMASK(priv->fields[LAST_TEMP_0].msb,
+ priv->fields[LAST_TEMP_0].lsb);
+ u32 valid_bit = priv->rf[VALID_0] ? BIT(priv->fields[VALID_0].lsb) : 0;
+
+ for (int i = 0; i < MAX_READ_RETRY; i++) {
+ ret = regmap_read(priv->tm_map, priv->fields[field].reg, &status);
+ if (ret)
+ return ret;
+
+ /* VER_0 doesn't have a VALID bit */
+ if (!valid_bit) {
+ *temp = status & last_temp_mask;
+ return 0;
+ }
+
+ temp_val[i] = status & last_temp_mask;
+
+ if (status & valid_bit) {
+ *temp = temp_val[i];
+ return 0;
+ }
+ }
+
+ /*
+ * As per the HW guidelines, if none of the attempts observe a
+ * valid sample, a stable fallback value must be returned. If the
+ * first and second samples match, the second value is returned;
+ * otherwise, if the second and third samples match, the third
+ * value is returned.
+ */
+ if (temp_val[0] == temp_val[1])
+ *temp = temp_val[1];
+ else if (temp_val[1] == temp_val[2])
+ *temp = temp_val[2];
+ else
+ return -EAGAIN;
+
+ return 0;
+}
+
+/**
+ * tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
+ * @s: Pointer to sensor struct
+ * @temp: temperature in milliCelsius to be read from hardware
*
* This function handles temperature returned in ADC code or deciCelsius
* depending on IP version.
@@ -326,20 +383,14 @@ static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
* Return: Temperature in milliCelsius on success, a negative errno will
* be returned in error cases
*/
-static int tsens_hw_to_mC(const struct tsens_sensor *s, int field)
+static int tsens_hw_to_mC(const struct tsens_sensor *s, int temp)
{
struct tsens_priv *priv = s->priv;
u32 resolution;
- u32 temp = 0;
- int ret;
resolution = priv->fields[LAST_TEMP_0].msb -
priv->fields[LAST_TEMP_0].lsb;
- ret = regmap_field_read(priv->rf[field], &temp);
- if (ret)
- return ret;
-
/* Convert temperature from ADC code to milliCelsius */
if (priv->feat->adc)
return code_to_degc(temp, s) * 1000;
@@ -514,8 +565,10 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
&d->crit_irq_mask);
if (ret)
return ret;
-
- d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id);
+ ret = regmap_field_read(priv->rf[CRIT_THRESH_0 + hw_id], &d->crit_thresh);
+ if (ret)
+ return ret;
+ d->crit_thresh = tsens_hw_to_mC(s, d->crit_thresh);
} else {
/* No mask register on older TSENS */
d->up_irq_mask = 0;
@@ -525,8 +578,16 @@ static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
d->crit_thresh = 0;
}
- d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
- d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
+ ret = regmap_field_read(priv->rf[UP_THRESH_0 + hw_id], &d->up_thresh);
+ if (ret)
+ return ret;
+
+ d->up_thresh = tsens_hw_to_mC(s, d->up_thresh);
+ ret = regmap_field_read(priv->rf[LOW_THRESH_0 + hw_id], &d->low_thresh);
+ if (ret)
+ return ret;
+
+ d->low_thresh = tsens_hw_to_mC(s, d->low_thresh);
dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n",
hw_id, __func__,
@@ -750,33 +811,15 @@ static void tsens_disable_irq(struct tsens_priv *priv)
int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
{
- struct tsens_priv *priv = s->priv;
int hw_id = s->hw_id;
u32 temp_idx = LAST_TEMP_0 + hw_id;
- u32 valid_idx = VALID_0 + hw_id;
- u32 valid;
int ret;
- /* VER_0 doesn't have VALID bit */
- if (tsens_version(priv) == VER_0)
- goto get_temp;
-
- /* Valid bit is 0 for 6 AHB clock cycles.
- * At 19.2MHz, 1 AHB clock is ~60ns.
- * We should enter this loop very, very rarely.
- * Wait 1 us since it's the min of poll_timeout macro.
- * Old value was 400 ns.
- */
- ret = regmap_field_read_poll_timeout(priv->rf[valid_idx], valid,
- valid, 1, 20 * USEC_PER_MSEC);
- if (ret)
- return ret;
-
-get_temp:
- /* Valid bit is set, OK to read the temperature */
- *temp = tsens_hw_to_mC(s, temp_idx);
+ ret = tsens_read_temp(s, temp_idx, temp);
+ if (!ret)
+ *temp = tsens_hw_to_mC(s, *temp);
- return 0;
+ return ret;
}
int get_temp_common(const struct tsens_sensor *s, int *temp)
@@ -1086,22 +1129,30 @@ static int tsens_get_temp(struct thermal_zone_device *tz, int *temp)
static int __maybe_unused tsens_suspend(struct device *dev)
{
+ int ret = 0;
struct tsens_priv *priv = dev_get_drvdata(dev);
- if (priv->ops && priv->ops->suspend)
- return priv->ops->suspend(priv);
+ if (priv->ops && priv->ops->suspend) {
+ ret = priv->ops->suspend(priv);
+ if (ret)
+ return ret;
+ }
- return 0;
+ return tsens_suspend_common(priv);
}
static int __maybe_unused tsens_resume(struct device *dev)
{
+ int ret = 0;
struct tsens_priv *priv = dev_get_drvdata(dev);
- if (priv->ops && priv->ops->resume)
- return priv->ops->resume(priv);
+ if (priv->ops && priv->ops->resume) {
+ ret = priv->ops->resume(priv);
+ if (ret)
+ return ret;
+ }
- return 0;
+ return tsens_resume_common(priv);
}
static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
@@ -1161,6 +1212,12 @@ static const struct of_device_id tsens_table[] = {
}, {
.compatible = "qcom,tsens-v2",
.data = &data_tsens_v2,
+ }, {
+ .compatible = "qcom,sa8775p-tsens",
+ .data = &data_automotive_v2,
+ }, {
+ .compatible = "qcom,sa8255p-tsens",
+ .data = &data_automotive_v2,
},
{}
};
@@ -1172,7 +1229,7 @@ static const struct thermal_zone_device_ops tsens_of_ops = {
};
static int tsens_register_irq(struct tsens_priv *priv, char *irqname,
- irq_handler_t thread_fn)
+ irq_handler_t thread_fn, int *irq_num)
{
struct platform_device *pdev;
int ret, irq;
@@ -1205,7 +1262,7 @@ static int tsens_register_irq(struct tsens_priv *priv, char *irqname,
dev_err(&pdev->dev, "%s: failed to get irq\n",
__func__);
else
- enable_irq_wake(irq);
+ *irq_num = irq;
}
put_device(&pdev->dev);
@@ -1232,11 +1289,38 @@ static int tsens_reinit(struct tsens_priv *priv)
return 0;
}
+int tsens_suspend_common(struct tsens_priv *priv)
+{
+ if (!device_may_wakeup(priv->dev))
+ return 0;
+
+ if (priv->feat->combo_int)
+ enable_irq_wake(priv->combined_irq);
+ else {
+ enable_irq_wake(priv->uplow_irq);
+ if (priv->feat->crit_int)
+ enable_irq_wake(priv->crit_irq);
+ }
+
+ return 0;
+}
+
int tsens_resume_common(struct tsens_priv *priv)
{
if (pm_suspend_target_state == PM_SUSPEND_MEM)
tsens_reinit(priv);
+ if (!device_may_wakeup(priv->dev))
+ return 0;
+
+ if (priv->feat->combo_int)
+ disable_irq_wake(priv->combined_irq);
+ else {
+ disable_irq_wake(priv->uplow_irq);
+ if (priv->feat->crit_int)
+ disable_irq_wake(priv->crit_irq);
+ }
+
return 0;
}
@@ -1276,15 +1360,18 @@ static int tsens_register(struct tsens_priv *priv)
if (priv->feat->combo_int) {
ret = tsens_register_irq(priv, "combined",
- tsens_combined_irq_thread);
+ tsens_combined_irq_thread, &priv->combined_irq);
} else {
- ret = tsens_register_irq(priv, "uplow", tsens_irq_thread);
+ ret = tsens_register_irq(priv, "uplow", tsens_irq_thread,
+ &priv->uplow_irq);
if (ret < 0)
return ret;
- if (priv->feat->crit_int)
+ if (priv->feat->crit_int) {
ret = tsens_register_irq(priv, "critical",
- tsens_critical_irq_thread);
+ tsens_critical_irq_thread,
+ &priv->crit_irq);
+ }
}
return ret;
@@ -1343,6 +1430,8 @@ static int tsens_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, priv);
+ device_init_wakeup(dev, !data->no_irq_wake);
+
if (!priv->ops || !priv->ops->init || !priv->ops->get_temp)
return -EINVAL;
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
index 2a7afa4c899b..e8376accdff3 100644
--- a/drivers/thermal/qcom/tsens.h
+++ b/drivers/thermal/qcom/tsens.h
@@ -21,6 +21,7 @@
#define THRESHOLD_MIN_ADC_CODE 0x0
#define MAX_SENSORS 16
+#define MAX_READ_RETRY 3
#include <linux/interrupt.h>
#include <linux/thermal.h>
@@ -531,6 +532,7 @@ struct tsens_features {
* @hw_ids: Subset of sensors ids supported by platform, if not the first n
* @feat: features of the IP
* @fields: bitfield locations
+ * @no_irq_wake: if set, TSENS interrupts will not be configured as wakeup sources
*/
struct tsens_plat_data {
const u32 num_sensors;
@@ -538,6 +540,7 @@ struct tsens_plat_data {
unsigned int *hw_ids;
struct tsens_features *feat;
const struct reg_field *fields;
+ bool no_irq_wake;
};
/**
@@ -567,6 +570,9 @@ struct tsens_context {
* @ops: pointer to list of callbacks supported by this device
* @debug_root: pointer to debugfs dentry for all tsens
* @debug: pointer to debugfs dentry for tsens controller
+ * @uplow_irq: IRQ number for uplow (upper/lower) threshold interrupts
+ * @crit_irq: IRQ number for critical threshold interrupts
+ * @combined_irq: IRQ number for combined threshold interrupts
* @sensor: list of sensors attached to this device
*/
struct tsens_priv {
@@ -588,6 +594,10 @@ struct tsens_priv {
struct dentry *debug_root;
struct dentry *debug;
+ int uplow_irq;
+ int crit_irq;
+ int combined_irq;
+
struct tsens_sensor sensor[] __counted_by(num_sensors);
};
@@ -639,8 +649,17 @@ int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp);
int get_temp_common(const struct tsens_sensor *s, int *temp);
#ifdef CONFIG_SUSPEND
int tsens_resume_common(struct tsens_priv *priv);
+int tsens_suspend_common(struct tsens_priv *priv);
#else
-#define tsens_resume_common NULL
+static inline int tsens_resume_common(struct tsens_priv *priv)
+{
+ return 0;
+}
+
+static inline int tsens_suspend_common(struct tsens_priv *priv)
+{
+ return 0;
+}
#endif
/* TSENS target */
@@ -659,4 +678,7 @@ extern const struct tsens_plat_data data_ipq5018;
extern struct tsens_plat_data data_8996, data_ipq8074, data_tsens_v2;
extern const struct tsens_plat_data data_ipq5332, data_ipq5424;
+/* TSENS automotive targets */
+extern struct tsens_plat_data data_automotive_v2;
+
#endif /* __QCOM_TSENS_H__ */
diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c
index 01b58be0dcc6..35439ec5f8bc 100644
--- a/drivers/thermal/qoriq_thermal.c
+++ b/drivers/thermal/qoriq_thermal.c
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright 2016 Freescale Semiconductor, Inc.
+// Copyright 2025 NXP
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
@@ -24,10 +26,14 @@
#define TMTMIR_DEFAULT 0x0000000f
#define TIER_DISABLE 0x0
#define TEUMR0_V2 0x51009c00
+#define TEUMR0_V21 0x55000c00
#define TMSARA_V2 0xe
#define TMU_VER1 0x1
#define TMU_VER2 0x2
+/* errata ID info define */
+#define TMU_ERR052243 BIT(0)
+
#define REGS_TMR 0x000 /* Mode Register */
#define TMR_DISABLE 0x0
#define TMR_ME 0x80000000
@@ -43,6 +49,15 @@
#define REGS_TIER 0x020 /* Interrupt Enable Register */
#define TIER_DISABLE 0x0
+#define REGS_TIDR 0x24
+#define TEMP_RATE_IRQ_MASK GENMASK(25, 24)
+#define TMRTRCTR 0x70
+#define TMRTRCTR_EN BIT(31)
+#define TMRTRCTR_TEMP_MASK GENMASK(7, 0)
+#define TMFTRCTR 0x74
+#define TMFTRCTR_EN BIT(31)
+#define TMFTRCTR_TEMP_MASK GENMASK(7, 0)
+#define TEMP_RATE_THR_LVL 0x7
#define REGS_TTCFGR 0x080 /* Temperature Configuration Register */
#define REGS_TSCFGR 0x084 /* Sensor Configuration Register */
@@ -73,14 +88,26 @@ struct qoriq_sensor {
int id;
};
+struct tmu_drvdata {
+ u32 teumr0;
+ u32 tmu_errata;
+};
+
struct qoriq_tmu_data {
int ver;
u32 ttrcr[NUM_TTRCR_MAX];
struct regmap *regmap;
struct clk *clk;
struct qoriq_sensor sensor[SITES_MAX];
+ const struct tmu_drvdata *drvdata;
};
+static inline bool qoriq_tmu_has_errata(const struct tmu_drvdata *drvdata,
+ u32 flag)
+{
+ return drvdata->tmu_errata & flag;
+}
+
static struct qoriq_tmu_data *qoriq_sensor_to_data(struct qoriq_sensor *s)
{
return container_of(s, struct qoriq_tmu_data, sensor[s->id]);
@@ -90,7 +117,7 @@ static int tmu_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct qoriq_sensor *qsensor = thermal_zone_device_priv(tz);
struct qoriq_tmu_data *qdata = qoriq_sensor_to_data(qsensor);
- u32 val;
+ u32 val, tidr;
/*
* REGS_TRITSR(id) has the following layout:
*
@@ -123,6 +150,15 @@ static int tmu_get_temp(struct thermal_zone_device *tz, int *temp)
10 * USEC_PER_MSEC))
return -ENODATA;
+ /*ERR052243: If a raising or falling edge happens, try later */
+ if (qoriq_tmu_has_errata(qdata->drvdata, TMU_ERR052243)) {
+ regmap_read(qdata->regmap, REGS_TIDR, &tidr);
+ if (tidr & TEMP_RATE_IRQ_MASK) {
+ regmap_write(qdata->regmap, REGS_TIDR, TEMP_RATE_IRQ_MASK);
+ return -EAGAIN;
+ }
+ }
+
if (qdata->ver == TMU_VER1) {
*temp = (val & GENMASK(7, 0)) * MILLIDEGREE_PER_DEGREE;
} else {
@@ -234,9 +270,18 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
regmap_write(data->regmap, REGS_TMTMIR, TMTMIR_DEFAULT);
} else {
regmap_write(data->regmap, REGS_V2_TMTMIR, TMTMIR_DEFAULT);
- regmap_write(data->regmap, REGS_V2_TEUMR(0), TEUMR0_V2);
+ regmap_write(data->regmap, REGS_V2_TEUMR(0),
+ data->drvdata->teumr0);
}
+ /* ERR052243: Set the raising & falling edge monitor */
+ if (qoriq_tmu_has_errata(data->drvdata, TMU_ERR052243)) {
+ regmap_write(data->regmap, TMRTRCTR, TMRTRCTR_EN |
+ FIELD_PREP(TMRTRCTR_TEMP_MASK, TEMP_RATE_THR_LVL));
+ regmap_write(data->regmap, TMFTRCTR, TMFTRCTR_EN |
+ FIELD_PREP(TMFTRCTR_TEMP_MASK, TEMP_RATE_THR_LVL));
+
+ }
/* Disable monitoring */
regmap_write(data->regmap, REGS_TMR, TMR_DISABLE);
}
@@ -319,6 +364,10 @@ static int qoriq_tmu_probe(struct platform_device *pdev)
data->ver = (ver >> 8) & 0xff;
+ data->drvdata = of_device_get_match_data(&pdev->dev);
+ if (!data->drvdata)
+ return dev_err_probe(dev, -EINVAL, "Failed to get match data\n");
+
qoriq_tmu_init_device(data); /* TMU initialization */
ret = qoriq_tmu_calibration(dev, data); /* TMU calibration */
@@ -376,9 +425,23 @@ static int qoriq_tmu_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops,
qoriq_tmu_suspend, qoriq_tmu_resume);
+static const struct tmu_drvdata qoriq_tmu_data = {
+ .teumr0 = TEUMR0_V2,
+};
+
+static const struct tmu_drvdata imx8mq_tmu_data = {
+ .teumr0 = TEUMR0_V2,
+};
+
+static const struct tmu_drvdata imx93_data = {
+ .teumr0 = TEUMR0_V21,
+ .tmu_errata = TMU_ERR052243,
+};
+
static const struct of_device_id qoriq_tmu_match[] = {
- { .compatible = "fsl,qoriq-tmu", },
- { .compatible = "fsl,imx8mq-tmu", },
+ { .compatible = "fsl,qoriq-tmu", .data = &qoriq_tmu_data },
+ { .compatible = "fsl,imx8mq-tmu", .data = &imx8mq_tmu_data },
+ { .compatible = "fsl,imx93-tmu", .data = &imx93_data },
{},
};
MODULE_DEVICE_TABLE(of, qoriq_tmu_match);
diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig
index f4eff5a41a84..e1e8035d24fb 100644
--- a/drivers/thermal/samsung/Kconfig
+++ b/drivers/thermal/samsung/Kconfig
@@ -3,6 +3,7 @@ config EXYNOS_THERMAL
tristate "Exynos thermal management unit driver"
depends on THERMAL_OF
depends on HAS_IOMEM
+ default ARCH_EXYNOS
help
If you say yes here you get support for the TMU (Thermal Management
Unit) driver for Samsung Exynos series of SoCs. This driver initialises
diff --git a/drivers/thermal/spacemit/Kconfig b/drivers/thermal/spacemit/Kconfig
new file mode 100644
index 000000000000..de7b5ece5af2
--- /dev/null
+++ b/drivers/thermal/spacemit/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "SpacemiT thermal drivers"
+depends on ARCH_SPACEMIT || COMPILE_TEST
+
+config SPACEMIT_K1_TSENSOR
+ tristate "SpacemiT K1 thermal sensor driver"
+ depends on THERMAL_OF
+ help
+ This driver provides support for the thermal sensor
+ integrated in the SpacemiT K1 SoC.
+
+ The thermal sensor monitors temperatures for five thermal zones:
+ soc, package, gpu, cluster0, and cluster1. It supports reporting
+ temperature values and handling high/low threshold interrupts.
+
+ Say Y here if you want to enable thermal monitoring on SpacemiT K1.
+ If compiled as a module, it will be called k1_tsensor.
+
+endmenu
diff --git a/drivers/thermal/spacemit/Makefile b/drivers/thermal/spacemit/Makefile
new file mode 100644
index 000000000000..82b30741e4ec
--- /dev/null
+++ b/drivers/thermal/spacemit/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_SPACEMIT_K1_TSENSOR) += k1_tsensor.o
diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c
new file mode 100644
index 000000000000..79222d233129
--- /dev/null
+++ b/drivers/thermal/spacemit/k1_tsensor.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Thermal sensor driver for SpacemiT K1 SoC
+ *
+ * Copyright (C) 2026 Shuwei Wu <shuwei.wu@mailbox.org>
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "../thermal_hwmon.h"
+
+#define K1_TSENSOR_PCTRL_REG 0x00
+#define K1_TSENSOR_PCTRL_ENABLE BIT(0)
+#define K1_TSENSOR_PCTRL_TEMP_MODE BIT(3)
+#define K1_TSENSOR_PCTRL_RAW_SEL BIT(7)
+
+#define K1_TSENSOR_PCTRL_CTUNE GENMASK(11, 8)
+#define K1_TSENSOR_PCTRL_SW_CTRL GENMASK(21, 18)
+#define K1_TSENSOR_PCTRL_HW_AUTO_MODE BIT(23)
+
+#define K1_TSENSOR_EN_REG 0x08
+#define K1_TSENSOR_EN_ALL GENMASK(MAX_SENSOR_NUMBER - 1, 0)
+
+#define K1_TSENSOR_TIME_REG 0x0C
+#define K1_TSENSOR_TIME_WAIT_REF_CNT GENMASK(3, 0)
+#define K1_TSENSOR_TIME_ADC_CNT_RST GENMASK(7, 4)
+#define K1_TSENSOR_TIME_FILTER_PERIOD GENMASK(21, 20)
+#define K1_TSENSOR_TIME_MASK GENMASK(23, 0)
+
+#define K1_TSENSOR_INT_CLR_REG 0x10
+#define K1_TSENSOR_INT_EN_REG 0x14
+#define K1_TSENSOR_INT_STA_REG 0x18
+
+#define K1_TSENSOR_INT_EN_MASK BIT(0)
+#define K1_TSENSOR_INT_MASK(x) (GENMASK(2, 1) << ((x) * 2))
+
+#define K1_TSENSOR_DATA_BASE_REG 0x20
+#define K1_TSENSOR_DATA_REG(x) (K1_TSENSOR_DATA_BASE_REG + ((x) / 2) * 4)
+#define K1_TSENSOR_DATA_LOW_MASK GENMASK(15, 0)
+#define K1_TSENSOR_DATA_HIGH_MASK GENMASK(31, 16)
+
+#define K1_TSENSOR_THRSH_BASE_REG 0x40
+#define K1_TSENSOR_THRSH_REG(x) (K1_TSENSOR_THRSH_BASE_REG + ((x) * 4))
+#define K1_TSENSOR_THRSH_LOW_MASK GENMASK(15, 0)
+#define K1_TSENSOR_THRSH_HIGH_MASK GENMASK(31, 16)
+
+#define MAX_SENSOR_NUMBER 5
+
+/* Hardware offset value required for temperature calculation */
+#define TEMPERATURE_OFFSET 278
+
+struct k1_tsensor_channel {
+ struct k1_tsensor *ts;
+ struct thermal_zone_device *tzd;
+ int id;
+};
+
+struct k1_tsensor {
+ void __iomem *base;
+ struct k1_tsensor_channel ch[MAX_SENSOR_NUMBER];
+};
+
+static void k1_tsensor_init(struct k1_tsensor *ts)
+{
+ u32 val;
+
+ /* Disable all the interrupts */
+ writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG);
+
+ /* Configure ADC sampling time and filter period */
+ val = readl(ts->base + K1_TSENSOR_TIME_REG);
+ val &= ~K1_TSENSOR_TIME_MASK;
+ val |= K1_TSENSOR_TIME_FILTER_PERIOD |
+ K1_TSENSOR_TIME_ADC_CNT_RST |
+ K1_TSENSOR_TIME_WAIT_REF_CNT;
+ writel(val, ts->base + K1_TSENSOR_TIME_REG);
+
+ /*
+ * Enable all sensors' auto mode, enable dither control,
+ * consecutive mode, and power up sensor.
+ */
+ val = readl(ts->base + K1_TSENSOR_PCTRL_REG);
+ val &= ~K1_TSENSOR_PCTRL_SW_CTRL;
+ val &= ~K1_TSENSOR_PCTRL_CTUNE;
+ val |= K1_TSENSOR_PCTRL_RAW_SEL |
+ K1_TSENSOR_PCTRL_TEMP_MODE |
+ K1_TSENSOR_PCTRL_HW_AUTO_MODE |
+ K1_TSENSOR_PCTRL_ENABLE;
+ writel(val, ts->base + K1_TSENSOR_PCTRL_REG);
+
+ /* Enable each sensor */
+ val = readl(ts->base + K1_TSENSOR_EN_REG);
+ val |= K1_TSENSOR_EN_ALL;
+ writel(val, ts->base + K1_TSENSOR_EN_REG);
+}
+
+static void k1_tsensor_enable_irq(struct k1_tsensor_channel *ch)
+{
+ struct k1_tsensor *ts = ch->ts;
+ u32 val;
+
+ val = readl(ts->base + K1_TSENSOR_INT_CLR_REG);
+ val |= K1_TSENSOR_INT_MASK(ch->id);
+ writel(val, ts->base + K1_TSENSOR_INT_CLR_REG);
+
+ val = readl(ts->base + K1_TSENSOR_INT_EN_REG);
+ val &= ~K1_TSENSOR_INT_MASK(ch->id);
+ writel(val, ts->base + K1_TSENSOR_INT_EN_REG);
+
+ /* Enable thermal interrupt */
+ val = readl(ts->base + K1_TSENSOR_INT_EN_REG);
+ val |= K1_TSENSOR_INT_EN_MASK;
+ writel(val, ts->base + K1_TSENSOR_INT_EN_REG);
+}
+
+/*
+ * The conversion formula used is:
+ * T(m°C) = (((raw_value & mask) >> shift) - TEMPERATURE_OFFSET) * 1000
+ */
+static int k1_tsensor_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz);
+ struct k1_tsensor *ts = ch->ts;
+ u32 val;
+
+ val = readl(ts->base + K1_TSENSOR_DATA_REG(ch->id));
+ if (ch->id % 2)
+ *temp = FIELD_GET(K1_TSENSOR_DATA_HIGH_MASK, val);
+ else
+ *temp = FIELD_GET(K1_TSENSOR_DATA_LOW_MASK, val);
+
+ *temp -= TEMPERATURE_OFFSET;
+ *temp *= 1000;
+
+ return 0;
+}
+
+/*
+ * For each sensor, the hardware threshold register is 32 bits:
+ * - Lower 16 bits [15:0] configure the low threshold temperature.
+ * - Upper 16 bits [31:16] configure the high threshold temperature.
+ */
+static int k1_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz);
+ struct k1_tsensor *ts = ch->ts;
+ u32 val;
+
+ if (low >= high)
+ return -EINVAL;
+
+ low = clamp_val(low / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET,
+ FIELD_MAX(K1_TSENSOR_THRSH_LOW_MASK));
+ high = clamp_val(high / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET,
+ FIELD_MAX(K1_TSENSOR_THRSH_HIGH_MASK));
+
+ val = readl(ts->base + K1_TSENSOR_THRSH_REG(ch->id));
+
+ val &= ~(K1_TSENSOR_THRSH_LOW_MASK | K1_TSENSOR_THRSH_HIGH_MASK);
+ val |= FIELD_PREP(K1_TSENSOR_THRSH_LOW_MASK, low);
+ val |= FIELD_PREP(K1_TSENSOR_THRSH_HIGH_MASK, high);
+
+ writel(val, ts->base + K1_TSENSOR_THRSH_REG(ch->id));
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops k1_tsensor_ops = {
+ .get_temp = k1_tsensor_get_temp,
+ .set_trips = k1_tsensor_set_trips,
+};
+
+static irqreturn_t k1_tsensor_irq_thread(int irq, void *data)
+{
+ struct k1_tsensor *ts = (struct k1_tsensor *)data;
+ int mask, status, i;
+
+ status = readl(ts->base + K1_TSENSOR_INT_STA_REG);
+
+ for (i = 0; i < MAX_SENSOR_NUMBER; i++) {
+ if (status & K1_TSENSOR_INT_MASK(i)) {
+ mask = readl(ts->base + K1_TSENSOR_INT_CLR_REG);
+ mask |= K1_TSENSOR_INT_MASK(i);
+ writel(mask, ts->base + K1_TSENSOR_INT_CLR_REG);
+ thermal_zone_device_update(ts->ch[i].tzd, THERMAL_EVENT_UNSPECIFIED);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int k1_tsensor_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct k1_tsensor *ts;
+ struct reset_control *reset;
+ struct clk *clk;
+ int i, irq, ret;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ts->base))
+ return dev_err_probe(dev, PTR_ERR(ts->base), "Failed to get reg\n");
+
+ reset = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(reset))
+ return dev_err_probe(dev, PTR_ERR(reset), "Failed to get/deassert reset control\n");
+
+ clk = devm_clk_get_enabled(dev, "core");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get core clock\n");
+
+ clk = devm_clk_get_enabled(dev, "bus");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get bus clock\n");
+
+ k1_tsensor_init(ts);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ k1_tsensor_irq_thread,
+ IRQF_ONESHOT, "k1_tsensor", ts);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < MAX_SENSOR_NUMBER; ++i) {
+ ts->ch[i].id = i;
+ ts->ch[i].ts = ts;
+ ts->ch[i].tzd = devm_thermal_of_zone_register(dev, i, ts->ch + i, &k1_tsensor_ops);
+ if (IS_ERR(ts->ch[i].tzd))
+ return PTR_ERR(ts->ch[i].tzd);
+
+ /* Attach sysfs hwmon attributes for userspace monitoring */
+ ret = devm_thermal_add_hwmon_sysfs(dev, ts->ch[i].tzd);
+ if (ret)
+ dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
+
+ k1_tsensor_enable_irq(ts->ch + i);
+ }
+
+ platform_set_drvdata(pdev, ts);
+
+ return 0;
+}
+
+static const struct of_device_id k1_tsensor_dt_ids[] = {
+ { .compatible = "spacemit,k1-tsensor" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, k1_tsensor_dt_ids);
+
+static struct platform_driver k1_tsensor_driver = {
+ .driver = {
+ .name = "k1_tsensor",
+ .of_match_table = k1_tsensor_dt_ids,
+ },
+ .probe = k1_tsensor_probe,
+};
+module_platform_driver(k1_tsensor_driver);
+
+MODULE_DESCRIPTION("SpacemiT K1 Thermal Sensor Driver");
+MODULE_AUTHOR("Shuwei Wu <shuwei.wu@mailbox.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c
index 5d26b52beaba..d8e988a0d43e 100644
--- a/drivers/thermal/tegra/soctherm.c
+++ b/drivers/thermal/tegra/soctherm.c
@@ -1499,6 +1499,13 @@ static int soctherm_clk_enable(struct platform_device *pdev, bool enable)
return 0;
}
+static void soctherm_clk_disable(void *data)
+{
+ struct platform_device *pdev = data;
+
+ soctherm_clk_enable(pdev, false);
+}
+
static int throt_get_cdev_max_state(struct thermal_cooling_device *cdev,
unsigned long *max_state)
{
@@ -1700,9 +1707,9 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev)
stc->init = true;
} else {
- tcd = thermal_of_cooling_device_register(np_stcc,
- (char *)name, ts,
- &throt_cooling_ops);
+ tcd = devm_thermal_of_child_cooling_device_register(dev, np_stcc,
+ (char *)name, ts,
+ &throt_cooling_ops);
if (IS_ERR_OR_NULL(tcd)) {
dev_err(dev,
"throttle-cfg: %s: failed to register cooling device\n",
@@ -2175,6 +2182,10 @@ static int tegra_soctherm_probe(struct platform_device *pdev)
if (err)
return err;
+ err = devm_add_action_or_reset(&pdev->dev, soctherm_clk_disable, pdev);
+ if (err)
+ return err;
+
soctherm_thermtrips_parse(pdev);
soctherm_init_hw_throt_cdev(pdev);
@@ -2184,10 +2195,8 @@ static int tegra_soctherm_probe(struct platform_device *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;
- }
+ if (!zone)
+ return -ENOMEM;
zone->reg = tegra->regs + soc->ttgs[i]->sensor_temp_offset;
zone->dev = &pdev->dev;
@@ -2201,7 +2210,7 @@ static int tegra_soctherm_probe(struct platform_device *pdev)
err = PTR_ERR(z);
dev_err(&pdev->dev, "failed to register sensor: %d\n",
err);
- goto disable_clocks;
+ return err;
}
zone->tz = z;
@@ -2210,7 +2219,7 @@ static int tegra_soctherm_probe(struct platform_device *pdev)
/* Configure hw trip points */
err = tegra_soctherm_set_hwtrips(&pdev->dev, soc->ttgs[i], z);
if (err)
- goto disable_clocks;
+ return err;
}
err = soctherm_interrupts_init(pdev, tegra);
@@ -2218,11 +2227,6 @@ static int tegra_soctherm_probe(struct platform_device *pdev)
soctherm_debug_init(pdev);
return 0;
-
-disable_clocks:
- soctherm_clk_enable(pdev, false);
-
- return err;
}
static void tegra_soctherm_remove(struct platform_device *pdev)
@@ -2230,8 +2234,6 @@ static void tegra_soctherm_remove(struct platform_device *pdev)
struct tegra_soctherm *tegra = platform_get_drvdata(pdev);
debugfs_remove_recursive(tegra->debugfs_dir);
-
- soctherm_clk_enable(pdev, false);
}
static int __maybe_unused soctherm_suspend(struct device *dev)
diff --git a/drivers/thermal/testing/command.c b/drivers/thermal/testing/command.c
index 1159ecea57e7..fbf7ab9729b5 100644
--- a/drivers/thermal/testing/command.c
+++ b/drivers/thermal/testing/command.c
@@ -116,18 +116,30 @@ static int tt_command_exec(int index, const char *arg)
break;
case TT_CMD_DELTZ:
+ if (!arg || !*arg)
+ return -EINVAL;
+
ret = tt_del_tz(arg);
break;
case TT_CMD_TZADDTRIP:
+ if (!arg || !*arg)
+ return -EINVAL;
+
ret = tt_zone_add_trip(arg);
break;
case TT_CMD_TZREG:
+ if (!arg || !*arg)
+ return -EINVAL;
+
ret = tt_zone_reg(arg);
break;
case TT_CMD_TZUNREG:
+ if (!arg || !*arg)
+ return -EINVAL;
+
ret = tt_zone_unreg(arg);
break;
diff --git a/drivers/thermal/testing/zone.c b/drivers/thermal/testing/zone.c
index 3c339242f52d..f7f9ca2f1f2c 100644
--- a/drivers/thermal/testing/zone.c
+++ b/drivers/thermal/testing/zone.c
@@ -239,9 +239,9 @@ int tt_del_tz(const char *arg)
int ret;
int id;
- ret = sscanf(arg, "%d", &id);
- if (ret != 1)
- return -EINVAL;
+ ret = kstrtoint(arg, 10, &id);
+ if (ret < 0)
+ return ret;
struct tt_work *tt_work __free(kfree) = kzalloc_obj(*tt_work);
if (!tt_work)
@@ -279,9 +279,9 @@ static struct tt_thermal_zone *tt_get_tt_zone(const char *arg)
struct tt_thermal_zone *tt_zone;
int ret, id;
- ret = sscanf(arg, "%d", &id);
- if (ret != 1)
- return ERR_PTR(-EINVAL);
+ ret = kstrtoint(arg, 10, &id);
+ if (ret < 0)
+ return ERR_PTR(ret);
guard(mutex)(&tt_thermal_zones_lock);
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
index 2f4e2dc46b8f..28a20d4b475c 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -116,78 +116,24 @@ static int thermal_set_governor(struct thermal_zone_device *tz,
return ret;
}
-int thermal_register_governor(struct thermal_governor *governor)
+static int __init thermal_register_governor(struct thermal_governor *governor)
{
- int err;
- const char *name;
- struct thermal_zone_device *pos;
if (!governor)
return -EINVAL;
- guard(mutex)(&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;
- }
-
- guard(mutex)(&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);
- }
- }
-
- return err;
-}
-
-void thermal_unregister_governor(struct thermal_governor *governor)
-{
- struct thermal_zone_device *pos;
+ if (__find_governor(governor->name))
+ return -EBUSY;
- if (!governor)
- return;
-
- guard(mutex)(&thermal_governor_lock);
-
- if (!__find_governor(governor->name))
- return;
+ list_add(&governor->governor_list, &thermal_governor_list);
- list_del(&governor->governor_list);
+ if (strncmp(governor->name, DEFAULT_THERMAL_GOVERNOR, THERMAL_NAME_LENGTH))
+ return 0;
- guard(mutex)(&thermal_list_lock);
+ if (!def_governor)
+ def_governor = governor;
- list_for_each_entry(pos, &thermal_tz_list, node) {
- if (!strncasecmp(pos->governor->name, governor->name,
- THERMAL_NAME_LENGTH))
- thermal_set_governor(pos, NULL);
- }
+ return 0;
}
int thermal_zone_device_set_policy(struct thermal_zone_device *tz,
@@ -225,40 +171,34 @@ int thermal_build_list_of_policies(char *buf)
static void __init thermal_unregister_governors(void)
{
- struct thermal_governor **governor;
+ struct thermal_governor *gov, *pos;
+
+ guard(mutex)(&thermal_governor_lock);
- for_each_governor_table(governor)
- thermal_unregister_governor(*governor);
+ list_for_each_entry_safe(gov, pos, &thermal_governor_list, governor_list)
+ list_del(&gov->governor_list);
}
static int __init thermal_register_governors(void)
{
- int ret = 0;
struct thermal_governor **governor;
+ guard(mutex)(&thermal_governor_lock);
+
for_each_governor_table(governor) {
+ int ret;
+
ret = thermal_register_governor(*governor);
if (ret) {
pr_err("Failed to register governor: '%s'",
(*governor)->name);
- break;
+ return ret;
}
- pr_info("Registered thermal governor '%s'",
- (*governor)->name);
+ pr_info("Registered thermal governor '%s'", (*governor)->name);
}
- if (ret) {
- struct thermal_governor **gov;
-
- for_each_governor_table(gov) {
- if (gov == governor)
- break;
- thermal_unregister_governor(*gov);
- }
- }
-
- return ret;
+ return 0;
}
static int __thermal_zone_device_set_mode(struct thermal_zone_device *tz,
@@ -949,34 +889,7 @@ unbind:
kfree(pos);
}
-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);
- thermal_set_governor(tz, NULL);
- ida_destroy(&tz->ida);
- mutex_destroy(&tz->lock);
- complete(&tz->removal);
- } else if (!strncmp(dev_name(dev), "cooling_device",
- sizeof("cooling_device") - 1)) {
- cdev = to_cooling_device(dev);
- thermal_cooling_device_destroy_sysfs(cdev);
- kfree_const(cdev->type);
- ida_free(&thermal_cdev_ida, cdev->id);
- kfree(cdev);
- }
-}
-
-static const struct class thermal_class = {
- .name = "thermal",
- .dev_release = thermal_release,
-};
-static bool thermal_class_unavailable __ro_after_init = true;
+static struct class *thermal_class __ro_after_init;
static inline
void print_bind_err_msg(struct thermal_zone_device *tz,
@@ -1040,42 +953,35 @@ static void thermal_cooling_device_init_complete(struct thermal_cooling_device *
thermal_zone_cdev_bind(tz, cdev);
}
-/**
- * __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,
- const char *type, void *devdata,
- const struct thermal_cooling_device_ops *ops)
+static void thermal_cdev_release(struct device *dev)
+{
+ struct thermal_cooling_device *cdev = to_cooling_device(dev);
+
+ thermal_cooling_device_destroy_sysfs(cdev);
+ kfree_const(cdev->type);
+ ida_free(&thermal_cdev_ida, cdev->id);
+ kfree(cdev);
+}
+
+struct thermal_cooling_device *
+thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops)
{
struct thermal_cooling_device *cdev;
- unsigned long current_state;
int ret;
if (!ops || !ops->get_max_state || !ops->get_cur_state ||
!ops->set_cur_state)
return ERR_PTR(-EINVAL);
- if (thermal_class_unavailable)
+ if (!thermal_class)
return ERR_PTR(-ENODEV);
cdev = kzalloc_obj(*cdev);
if (!cdev)
return ERR_PTR(-ENOMEM);
+ cdev->ops = ops;
+
ret = ida_alloc(&thermal_cdev_ida, GFP_KERNEL);
if (ret < 0)
goto out_kfree_cdev;
@@ -1087,17 +993,35 @@ __thermal_cooling_device_register(struct device_node *np,
goto out_ida_remove;
}
+ return cdev;
+
+out_ida_remove:
+ ida_free(&thermal_cdev_ida, cdev->id);
+out_kfree_cdev:
+ kfree(cdev);
+ return ERR_PTR(ret);
+}
+
+int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata)
+{
+ unsigned long current_state;
+ int ret;
+
mutex_init(&cdev->lock);
INIT_LIST_HEAD(&cdev->thermal_instances);
- cdev->np = np;
- cdev->ops = ops;
cdev->updated = false;
- cdev->device.class = &thermal_class;
+ cdev->device.class = thermal_class;
+ cdev->device.release = thermal_cdev_release;
+ device_initialize(&cdev->device);
cdev->devdata = devdata;
+ ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
+ if (ret)
+ goto out_put_device;
+
ret = cdev->ops->get_max_state(cdev, &cdev->max_state);
if (ret)
- goto out_cdev_type;
+ goto out_put_device;
/*
* The cooling device's current state is only needed for debug
@@ -1113,40 +1037,32 @@ __thermal_cooling_device_register(struct device_node *np,
thermal_cooling_device_setup_sysfs(cdev);
- ret = dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
+ ret = device_add(&cdev->device);
if (ret)
- goto out_cooling_dev;
-
- ret = device_register(&cdev->device);
- if (ret) {
- /* thermal_release() handles rest of the cleanup */
- put_device(&cdev->device);
- return ERR_PTR(ret);
- }
+ goto out_put_device;
if (current_state <= cdev->max_state)
thermal_debug_cdev_add(cdev, current_state);
thermal_cooling_device_init_complete(cdev);
- return cdev;
+ return 0;
-out_cooling_dev:
- thermal_cooling_device_destroy_sysfs(cdev);
-out_cdev_type:
- kfree_const(cdev->type);
-out_ida_remove:
- ida_free(&thermal_cdev_ida, cdev->id);
-out_kfree_cdev:
- kfree(cdev);
- return ERR_PTR(ret);
+out_put_device:
+ /*
+ * The device core will release the memory via
+ * thermal_release() after put_device() is called in the error
+ * path
+ */
+ put_device(&cdev->device);
+ return ret;
}
/**
* thermal_cooling_device_register() - register a new thermal cooling device
* @type: the thermal cooling device type.
* @devdata: device private data.
- * @ops: standard thermal cooling devices callbacks.
+ * @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
@@ -1159,82 +1075,62 @@ struct thermal_cooling_device *
thermal_cooling_device_register(const char *type, void *devdata,
const struct thermal_cooling_device_ops *ops)
{
- return __thermal_cooling_device_register(NULL, type, devdata, ops);
+ struct thermal_cooling_device *cdev;
+ int ret;
+
+ cdev = thermal_cooling_device_alloc(type, ops);
+ if (IS_ERR(cdev))
+ return cdev;
+
+ ret = thermal_cooling_device_add(cdev, devdata);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return cdev;
}
EXPORT_SYMBOL_GPL(thermal_cooling_device_register);
-/**
- * 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,
- const char *type, void *devdata,
- const struct thermal_cooling_device_ops *ops)
+static void thermal_cooling_device_release(void *data)
{
- return __thermal_cooling_device_register(np, type, devdata, ops);
-}
-EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
+ struct thermal_cooling_device *cdev = data;
-static void thermal_cooling_device_release(struct device *dev, void *res)
-{
- thermal_cooling_device_unregister(
- *(struct thermal_cooling_device **)res);
+ thermal_cooling_device_unregister(cdev);
}
/**
- * devm_thermal_of_cooling_device_register() - register an OF thermal cooling
- * device
+ * devm_thermal_cooling_device_register() - register a thermal cooling device
+ *
* @dev: a valid struct device pointer of a sensor device.
- * @np: a pointer to a device tree node.
* @type: the thermal cooling device type.
* @devdata: device private data.
* @ops: standard thermal cooling devices callbacks.
*
- * This function will register a cooling device with device tree node reference.
- * This interface function adds a new thermal cooling device (fan/processor/...)
- * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
- * to all the thermal zone devices registered at the same time.
+ * This function will register a cooling device. This interface
+ * function adds a new thermal cooling device (fan/processor/...) to
+ * /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind
+ * itself to all the thermal zone devices registered at the same time.
*
* Return: a pointer to the created struct thermal_cooling_device or an
* ERR_PTR. Caller must check return value with IS_ERR*() helpers.
*/
struct thermal_cooling_device *
-devm_thermal_of_cooling_device_register(struct device *dev,
- struct device_node *np,
- const char *type, void *devdata,
- const struct thermal_cooling_device_ops *ops)
+devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
{
- struct thermal_cooling_device **ptr, *tcd;
-
- ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr),
- GFP_KERNEL);
- if (!ptr)
- return ERR_PTR(-ENOMEM);
+ struct thermal_cooling_device *cdev;
+ int ret;
- tcd = __thermal_cooling_device_register(np, type, devdata, ops);
- if (IS_ERR(tcd)) {
- devres_free(ptr);
- return tcd;
- }
+ cdev = thermal_cooling_device_register(type, devdata, ops);
+ if (IS_ERR(cdev))
+ return cdev;
- *ptr = tcd;
- devres_add(dev, ptr);
+ ret = devm_add_action_or_reset(dev, thermal_cooling_device_release, cdev);
+ if (ret)
+ return ERR_PTR(ret);
- return tcd;
+ return cdev;
}
-EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register);
+EXPORT_SYMBOL_GPL(devm_thermal_cooling_device_register);
static bool thermal_cooling_device_present(struct thermal_cooling_device *cdev)
{
@@ -1470,6 +1366,17 @@ static void thermal_zone_init_complete(struct thermal_zone_device *tz)
__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
}
+static void thermal_zone_device_release(struct device *dev)
+{
+ struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+ thermal_zone_destroy_device_groups(tz);
+ thermal_set_governor(tz, NULL);
+ ida_destroy(&tz->ida);
+ mutex_destroy(&tz->lock);
+ complete(&tz->removal);
+}
+
/**
* thermal_zone_device_register_with_trips() - register a new thermal zone device
* @type: the thermal zone device type
@@ -1540,7 +1447,7 @@ thermal_zone_device_register_with_trips(const char *type,
if (polling_delay && passive_delay > polling_delay)
return ERR_PTR(-EINVAL);
- if (thermal_class_unavailable)
+ if (!thermal_class)
return ERR_PTR(-ENODEV);
tz = kzalloc_flex(*tz, trips, num_trips);
@@ -1576,7 +1483,8 @@ thermal_zone_device_register_with_trips(const char *type,
if (!tz->ops.critical)
tz->ops.critical = thermal_zone_device_critical;
- tz->device.class = &thermal_class;
+ tz->device.class = thermal_class;
+ tz->device.release = thermal_zone_device_release;
tz->devdata = devdata;
tz->num_trips = num_trips;
for_each_trip_desc(tz, td) {
@@ -1837,7 +1745,7 @@ static void __thermal_pm_prepare(void)
void thermal_pm_prepare(void)
{
- if (thermal_class_unavailable)
+ if (!thermal_class)
return;
__thermal_pm_prepare();
@@ -1868,7 +1776,7 @@ void thermal_pm_complete(void)
{
struct thermal_zone_device *tz;
- if (thermal_class_unavailable)
+ if (!thermal_class)
return;
guard(mutex)(&thermal_list_lock);
@@ -1881,6 +1789,7 @@ void thermal_pm_complete(void)
static int __init thermal_init(void)
{
+ struct class *tc;
int result;
thermal_debug_init();
@@ -1889,7 +1798,7 @@ static int __init thermal_init(void)
if (result)
goto error;
- thermal_wq = alloc_workqueue("thermal_events", WQ_POWER_EFFICIENT, 0);
+ thermal_wq = alloc_workqueue("thermal_events", WQ_UNBOUND, 0);
if (!thermal_wq) {
result = -ENOMEM;
goto unregister_netlink;
@@ -1897,19 +1806,19 @@ static int __init thermal_init(void)
result = thermal_register_governors();
if (result)
- goto destroy_workqueue;
-
- result = class_register(&thermal_class);
- if (result)
goto unregister_governors;
- thermal_class_unavailable = false;
+ tc = class_create("thermal");
+ if (IS_ERR(tc)) {
+ result = PTR_ERR(tc);
+ goto unregister_governors;
+ }
+ thermal_class = tc;
return 0;
unregister_governors:
thermal_unregister_governors();
-destroy_workqueue:
destroy_workqueue(thermal_wq);
unregister_netlink:
thermal_netlink_exit();
diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h
index d3acff602f9c..e98b0aa5aacc 100644
--- a/drivers/thermal/thermal_core.h
+++ b/drivers/thermal/thermal_core.h
@@ -258,8 +258,6 @@ struct thermal_instance {
#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 *);
int thermal_zone_device_set_policy(struct thermal_zone_device *, char *);
int thermal_build_list_of_policies(char *buf);
void __thermal_zone_device_update(struct thermal_zone_device *tz,
@@ -269,6 +267,11 @@ void thermal_zone_device_critical_shutdown(struct thermal_zone_device *tz);
void thermal_governor_update_tz(struct thermal_zone_device *tz,
enum thermal_notify_event reason);
+struct thermal_cooling_device *
+thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops);
+
+int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata);
+
/* Helpers */
#define for_each_trip_desc(__tz, __td) \
for (__td = __tz->trips; __td - __tz->trips < __tz->num_trips; __td++)
diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c
index b624892bc6d6..386dfb9f559e 100644
--- a/drivers/thermal/thermal_hwmon.c
+++ b/drivers/thermal/thermal_hwmon.c
@@ -19,27 +19,19 @@
#include "thermal_hwmon.h"
#include "thermal_core.h"
+/*
+ * Needs to be large enough to hold a thermal zone type string followed by an
+ * underline character and a 32-bit integer in decimal representation.
+ */
+#define THERMAL_HWMON_NAME_LENGTH (THERMAL_NAME_LENGTH + 11)
+
/* hwmon sys I/F */
/* thermal zone devices with the same type share one hwmon device */
struct thermal_hwmon_device {
- char type[THERMAL_NAME_LENGTH];
+ char name[THERMAL_HWMON_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);
@@ -47,19 +39,14 @@ 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)
+temp1_input_show(struct device *dev, struct device_attribute *attr, char *buf)
{
+ struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev);
+ struct thermal_zone_device *tz = hwmon->tz;
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;
@@ -67,14 +54,10 @@ temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
}
static ssize_t
-temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
+temp1_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;
+ struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev);
+ struct thermal_zone_device *tz = hwmon->tz;
int temperature;
int ret;
@@ -87,166 +70,97 @@ temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
return sysfs_emit(buf, "%d\n", temperature);
}
+static DEVICE_ATTR_RO(temp1_input);
+static DEVICE_ATTR_RO(temp1_crit);
-static struct thermal_hwmon_device *
-thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
+static struct attribute *thermal_hwmon_attrs[] = {
+ &dev_attr_temp1_input.attr,
+ &dev_attr_temp1_crit.attr,
+ NULL,
+};
+
+static umode_t thermal_hwmon_attr_is_visible(struct kobject *kobj,
+ struct attribute *a, int n)
{
- struct thermal_hwmon_device *hwmon;
- char type[THERMAL_NAME_LENGTH];
+ if (a == &dev_attr_temp1_input.attr)
+ return a->mode;
- mutex_lock(&thermal_hwmon_list_lock);
- list_for_each_entry(hwmon, &thermal_hwmon_list, node) {
- strscpy(type, tz->type);
- strreplace(type, '-', '_');
- if (!strcmp(hwmon->type, type)) {
- mutex_unlock(&thermal_hwmon_list_lock);
- return hwmon;
- }
+ if (a == &dev_attr_temp1_crit.attr) {
+ struct thermal_hwmon_device *hwmon = dev_get_drvdata(kobj_to_dev(kobj));
+ struct thermal_zone_device *tz = hwmon->tz;
+ int dummy;
+
+ if (tz->ops.get_crit_temp && !tz->ops.get_crit_temp(tz, &dummy))
+ return a->mode;
}
- mutex_unlock(&thermal_hwmon_list_lock);
- return NULL;
+ return 0;
}
-/* 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 const struct attribute_group thermal_hwmon_group = {
+ .attrs = thermal_hwmon_attrs,
+ .is_visible = thermal_hwmon_attr_is_visible,
+};
-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);
-}
+__ATTRIBUTE_GROUPS(thermal_hwmon);
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_obj(*hwmon);
if (!hwmon)
return -ENOMEM;
- INIT_LIST_HEAD(&hwmon->tz_list);
- strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
- strreplace(hwmon->type, '-', '_');
+ hwmon->tz = tz;
+ /*
+ * Append the thermal zone ID preceded by an underline character to the
+ * type to disambiguate the sensors command output.
+ */
+ scnprintf(hwmon->name, THERMAL_HWMON_NAME_LENGTH, "%s_%d", tz->type, tz->id);
+ strreplace(hwmon->name, '-', '_');
hwmon->device = hwmon_device_register_for_thermal(&tz->device,
- hwmon->type, hwmon);
+ hwmon->name, hwmon,
+ thermal_hwmon_groups);
if (IS_ERR(hwmon->device)) {
- result = PTR_ERR(hwmon->device);
- goto free_mem;
- }
-
- register_sys_interface:
- temp = kzalloc_obj(*temp);
- if (!temp) {
- result = -ENOMEM;
- goto unregister_name;
- }
+ int result = PTR_ERR(hwmon->device);
- 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;
+ kfree(hwmon);
+ return result;
}
+ /* The list is needed for hwmon lookup during removal. */
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);
+ list_add_tail(&hwmon->node, &thermal_hwmon_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:
- kfree(hwmon);
-
- return result;
}
EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs);
-void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
+static struct thermal_hwmon_device *
+thermal_hwmon_lookup(const 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;
+ list_for_each_entry(hwmon, &thermal_hwmon_list, node) {
+ if (hwmon->tz == tz)
+ return hwmon;
}
+ return NULL;
+}
- temp = thermal_hwmon_lookup_temp(hwmon, tz);
- if (unlikely(!temp)) {
- /* Should never happen... */
- dev_dbg(&tz->device, "temperature input lookup failed!\n");
- return;
- }
+void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
+{
+ struct thermal_hwmon_device *hwmon;
- 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);
+ scoped_guard(mutex, &thermal_hwmon_list_lock) {
+ hwmon = thermal_hwmon_lookup(tz);
+ if (!hwmon)
+ return;
- 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);
}
- list_del(&hwmon->node);
- mutex_unlock(&thermal_hwmon_list_lock);
hwmon_device_unregister(hwmon->device);
kfree(hwmon);
diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c
index 99085c806a1f..100fd8a0c8ce 100644
--- a/drivers/thermal/thermal_of.c
+++ b/drivers/thermal/thermal_of.c
@@ -98,7 +98,7 @@ static struct thermal_trip *thermal_of_trips_init(struct device_node *np, int *n
int ret, count;
*ntrips = 0;
-
+
struct device_node *trips __free(device_node) = of_get_child_by_name(np, "trips");
if (!trips)
return NULL;
@@ -259,16 +259,34 @@ static bool thermal_of_get_cooling_spec(struct device_node *map_np, int index,
of_node_put(cooling_spec.np);
- if (cooling_spec.args_count < 2) {
- pr_err("wrong reference to cooling device, missing limits\n");
+ /*
+ * There are two formats:
+ * - Legacy format : <&cdev lower upper>
+ * - New format : <&cdev cdev_id lower upper>
+ *
+ * With the new format, along with the device node pointer,
+ * the cdev_id must match with the cooling device cdev_id in
+ * order to bind
+ */
+ if (cooling_spec.args_count < 2 || cooling_spec.args_count > 3) {
+ pr_err("Invalid number of cooling device parameters\n");
return false;
}
if (cooling_spec.np != cdev->np)
return false;
- c->lower = cooling_spec.args[0];
- c->upper = cooling_spec.args[1];
+ if (cooling_spec.args_count == 3 &&
+ cooling_spec.args[0] != cdev->cdev_id)
+ return false;
+
+ if (cooling_spec.args_count != 3) {
+ c->lower = cooling_spec.args[0];
+ c->upper = cooling_spec.args[1];
+ } else {
+ c->lower = cooling_spec.args[1];
+ c->upper = cooling_spec.args[2];
+ }
c->weight = weight;
return true;
@@ -494,7 +512,7 @@ EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register);
/**
* devm_thermal_of_zone_unregister - Resource managed version of
* thermal_of_zone_unregister().
- * @dev: Device for which which resource was allocated.
+ * @dev: Device for which resource was allocated.
* @tz: a pointer to struct thermal_zone where the sensor is registered.
*
* This function removes the sensor callbacks and private data from the
@@ -510,3 +528,125 @@ void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_dev
devm_thermal_of_zone_match, tz));
}
EXPORT_SYMBOL_GPL(devm_thermal_of_zone_unregister);
+
+/**
+ * thermal_of_cooling_device_register() - register an OF thermal cooling device
+ * @np: a pointer to a device tree node.
+ * @cdev_id: a cooling device id in the cooling controller
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ * It also gives the opportunity to link the cooling device to a device tree
+ * node, so that it can be bound to a thermal zone created out of device tree.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+ int ret;
+
+ cdev = thermal_cooling_device_alloc(type, ops);
+ if (IS_ERR(cdev))
+ return cdev;
+
+ cdev->np = np;
+ cdev->cdev_id = cdev_id;
+
+ ret = thermal_cooling_device_add(cdev, devdata);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return cdev;
+}
+EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
+
+static void thermal_of_cooling_device_release(void *data)
+{
+ struct thermal_cooling_device *cdev = data;
+
+ thermal_cooling_device_unregister(cdev);
+}
+
+static struct thermal_cooling_device *
+__devm_thermal_of_cooling_device_register(struct device *dev, struct device_node *np,
+ u32 cdev_id, const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ struct thermal_cooling_device *cdev;
+ int ret;
+
+ cdev = thermal_of_cooling_device_register(np, cdev_id, type, devdata, ops);
+ if (IS_ERR(cdev))
+ return cdev;
+
+ ret = devm_add_action_or_reset(dev, thermal_of_cooling_device_release, cdev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return cdev;
+}
+
+/**
+ * devm_thermal_of_cooling_device_register() - register an OF thermal cooling device
+ * @dev: a valid struct device pointer of a sensor device.
+ * @cdev_id: a cooling device index in the cooling controller
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This function will register a cooling device with device tree node reference.
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+devm_thermal_of_cooling_device_register(struct device *dev, u32 cdev_id,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return __devm_thermal_of_cooling_device_register(dev, dev->of_node, cdev_id,
+ type, devdata, ops);
+}
+EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register);
+
+/**
+ * devm_thermal_of_child_cooling_device_register() - register an OF thermal cooling
+ * device
+ * @dev: a valid struct device pointer of a sensor device.
+ * @np: a pointer to a device tree node.
+ * @type: the thermal cooling device type.
+ * @devdata: device private data.
+ * @ops: standard thermal cooling devices callbacks.
+ *
+ * This function will register a cooling device with device tree node reference.
+ * This interface function adds a new thermal cooling device (fan/processor/...)
+ * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
+ * to all the thermal zone devices registered at the same time.
+ *
+ * This function should be used when a cooling controller has child
+ * nodes which are referenced in the thermal zone cooling map.
+ *
+ * Return: a pointer to the created struct thermal_cooling_device or an
+ * ERR_PTR. Caller must check return value with IS_ERR*() helpers.
+ */
+struct thermal_cooling_device *
+devm_thermal_of_child_cooling_device_register(struct device *dev,
+ struct device_node *np,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return __devm_thermal_of_cooling_device_register(dev, np, 0, type, devdata, ops);
+}
+EXPORT_SYMBOL_GPL(devm_thermal_of_child_cooling_device_register);
diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c
index 5eecae13f07d..b44abfc997ed 100644
--- a/drivers/thermal/thermal_sysfs.c
+++ b/drivers/thermal/thermal_sysfs.c
@@ -82,7 +82,7 @@ mode_store(struct device *dev, struct device_attribute *attr,
}
#define thermal_trip_of_attr(_ptr_, _attr_) \
- ({ \
+ ({ \
struct thermal_trip_desc *td; \
\
td = container_of(_ptr_, struct thermal_trip_desc, \
@@ -536,11 +536,9 @@ cur_state_store(struct device *dev, struct device_attribute *attr,
unsigned long state;
int result;
- if (sscanf(buf, "%ld\n", &state) != 1)
- return -EINVAL;
-
- if ((long)state < 0)
- return -EINVAL;
+ result = kstrtoul(buf, 10, &state);
+ if (result < 0)
+ return result;
/* Requested state should be less than max_state + 1 */
if (state > cdev->max_state)
diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h
index 8eaf8922ab02..3ebc2bd9a976 100644
--- a/include/linux/firmware/meson/meson_sm.h
+++ b/include/linux/firmware/meson/meson_sm.h
@@ -12,6 +12,7 @@ enum {
SM_EFUSE_WRITE,
SM_EFUSE_USER_MAX,
SM_GET_CHIP_ID,
+ SM_THERMAL_CALIB_READ,
SM_A1_PWRC_SET,
SM_A1_PWRC_GET,
};
@@ -27,5 +28,7 @@ int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer,
unsigned int bsize, unsigned int cmd_index, u32 arg0,
u32 arg1, u32 arg2, u32 arg3, u32 arg4);
struct meson_sm_firmware *meson_sm_get(struct device_node *firmware_node);
+int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info,
+ u32 tsensor_id);
#endif /* _MESON_SM_FW_H_ */
diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h
index 301a83afbd66..a578a10baff2 100644
--- a/include/linux/hwmon.h
+++ b/include/linux/hwmon.h
@@ -477,7 +477,8 @@ hwmon_device_register_with_info(struct device *dev,
const struct attribute_group **extra_groups);
struct device *
hwmon_device_register_for_thermal(struct device *dev, const char *name,
- void *drvdata);
+ void *drvdata,
+ const struct attribute_group **extra_groups);
struct device *
devm_hwmon_device_register_with_info(struct device *dev,
const char *name, void *drvdata,
diff --git a/include/linux/thermal.h b/include/linux/thermal.h
index 0ddc77aeeca2..083b4f533933 100644
--- a/include/linux/thermal.h
+++ b/include/linux/thermal.h
@@ -125,7 +125,6 @@ struct thermal_cooling_device {
const char *type;
unsigned long max_state;
struct device device;
- struct device_node *np;
void *devdata;
void *stats;
const struct thermal_cooling_device_ops *ops;
@@ -133,6 +132,10 @@ struct thermal_cooling_device {
struct mutex lock; /* protect thermal_instances list */
struct list_head thermal_instances;
struct list_head node;
+#ifdef CONFIG_THERMAL_OF
+ struct device_node *np;
+ u32 cdev_id;
+#endif
#ifdef CONFIG_THERMAL_DEBUGFS
struct thermal_debugfs *debugfs;
#endif
@@ -198,6 +201,21 @@ struct thermal_zone_device *devm_thermal_of_zone_register(struct device *dev, in
void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_device *tz);
+struct thermal_cooling_device *
+thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id,
+ const char *type, void *data,
+ const struct thermal_cooling_device_ops *ops);
+
+struct thermal_cooling_device *
+devm_thermal_of_cooling_device_register(struct device *dev, u32 cdev_id,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops);
+
+struct thermal_cooling_device *
+devm_thermal_of_child_cooling_device_register(struct device *dev,
+ struct device_node *np,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops);
#else
static inline
@@ -211,6 +229,31 @@ static inline void devm_thermal_of_zone_unregister(struct device *dev,
struct thermal_zone_device *tz)
{
}
+
+static inline struct thermal_cooling_device *
+thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline struct thermal_cooling_device *
+devm_thermal_of_cooling_device_register(struct device *dev, u32 cdev_id,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline struct thermal_cooling_device *
+devm_thermal_of_child_cooling_device_register(struct device *dev,
+ struct device_node *np,
+ const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
+{
+ return ERR_PTR(-ENODEV);
+}
#endif
int for_each_thermal_trip(struct thermal_zone_device *tz,
@@ -252,14 +295,11 @@ void thermal_zone_device_update(struct thermal_zone_device *,
struct thermal_cooling_device *thermal_cooling_device_register(const char *,
void *, const struct thermal_cooling_device_ops *);
+
struct thermal_cooling_device *
-thermal_of_cooling_device_register(struct device_node *np, const char *, void *,
- const struct thermal_cooling_device_ops *);
-struct thermal_cooling_device *
-devm_thermal_of_cooling_device_register(struct device *dev,
- struct device_node *np,
- const char *type, void *devdata,
- const struct thermal_cooling_device_ops *ops);
+devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops);
+
void thermal_cooling_device_update(struct thermal_cooling_device *);
void thermal_cooling_device_unregister(struct thermal_cooling_device *);
struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name);
@@ -304,19 +344,12 @@ static inline struct thermal_cooling_device *
thermal_cooling_device_register(const char *type, void *devdata,
const struct thermal_cooling_device_ops *ops)
{ return ERR_PTR(-ENODEV); }
+
static inline struct thermal_cooling_device *
-thermal_of_cooling_device_register(struct device_node *np,
- const char *type, void *devdata,
- const struct thermal_cooling_device_ops *ops)
+devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata,
+ const struct thermal_cooling_device_ops *ops)
{ return ERR_PTR(-ENODEV); }
-static inline struct thermal_cooling_device *
-devm_thermal_of_cooling_device_register(struct device *dev,
- struct device_node *np,
- const char *type, void *devdata,
- const struct thermal_cooling_device_ops *ops)
-{
- return ERR_PTR(-ENODEV);
-}
+
static inline void thermal_cooling_device_unregister(
struct thermal_cooling_device *cdev)
{ }