summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-21 14:38:01 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-21 14:38:01 -1000
commit4403278e4ea2f654f8130a5c03eb8b4516a0f201 (patch)
tree921a7aaf5b1143c9cb2f9b9a4828280208c03616 /drivers
parentdf37e3924292fb41d325721008520ba93deced42 (diff)
parent503bb6e70bbf6304b8113373d09dfffcc4aa9183 (diff)
downloadlwn-master.tar.gz
lwn-master.zip
Merge tag 'thermal-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pmHEADmaster
Pull thermal control updates from Rafael Wysocki: "These are mostly updates of thermal drivers including a new Exynos ACPM thermal driver for Google GS101, new Renesas RZ/G3L thermal sensor support, new Qualcomm MSM8952 TSENS support, new Qualcomm PMAU0102 Master BandGap temperature monitor support in DT bindings, and new rugged Panther Lake support in intel_tcc_cooling. Moreover, the AN7583 driver is extended to register separate thermal zones for bandgap, GbE and CPU (its DT bindings are updated accordingly). There are also multiple fixes and assorted cleanups, as usual. Specifics: - Make the Intel powerclamp thermal driver reject invalid window_size values, drop redundant clamp() from duration_set() in it, and simplify 4 functions in it (Thorsten Blum) - Add rugged Panther Lake support to the intel_tcc_cooling thermal driver (Srinivas Pandruvada) - Fix temperature selection around 0 C in the int340x thermal driver, drop redundant else from proc_thermal_add() in it, and simplify slider_def_*_set() in it (Thorsten Blum) - Drop unbalanced paren from cpu-idle-cooling.rst (Manuel Ebner) - Use %pe to print error pointers symbolically in the thermal control code and fix assorted typos in comments in it (Sumeet Pawnikar and Hemanth Selam) - Add the Exynos ACPM thermal driver for Google GS101, along with DT bindings and a MAINTAINERS entry (Tudor Ambarus) - Add Renesas RZ/G3L thermal sensor support and DT bindings, and move default calibration values to per-SoC match data (Biju Das) - Add Qualcomm MSM8952 TSENS support and the corresponding DT binding compatible (Muzaffer Kadir) - Document the Qualcomm PMAU0102 Master BandGap temperature monitor compatible in the DT bindings (Dhruvin Rajpura) - Extend the QCom TSENS v2 trip temperature limits to the hardware range of -204°C to +204°C to prevent interrupt storms when operating beyond the previous software limits (Priyansh Jain) - Fix temperature threshold map indexing in the QCom SPMI temperature alarm driver to prevent out-of-bounds reads (Georgi Djakov) - Describe the additional bus clock required by the Amlogic T7 thermal sensor and switch the driver to the clk_bulk API to manage multiple clocks (Ronald Claveau) - Request the Airoha EN7581 IRQ after thermal zone registration to prevent the interrupt handler from accessing an unregistered or already freed thermal zone (Vitaliy Sochnev) - Fix the Airoha AN7583 ADC mux field mapping, serialize access to the shared ADC, and verify mux selection to avoid invalid temperature readings and spurious critical shutdowns (Vitaliy Sochnev) - Register separate thermal zones for the AN7583 bandgap, GbE and CPU sensors, and update the DT bindings to use one thermal sensor cell to select the sensor (Vitaliy Sochnev) - Fix signed integer overflow in the Renesas RZ/G3E temperature-to-code conversion, preserve compare interrupt state across runtime PM, and prevent overlapping temperature conversions (Ovidiu Panait) - Remove redundant trip window validation and runtime PM pm_runtime_mark_last_busy() calls from the Renesas RZ/G3E driver (Ovidiu Panait) - Propagate reset assertion failures from Renesas RZ/G3E suspend and attempt to restore power if the sensor was active (Pengpeng Hou) - Stop the TI bandgap alert IRQ before removing sensor interfaces and cancel pending alert work to prevent use-after-free on driver removal (Weigang He) - Validate Spreadtrum sensor IDs read from the device tree before using them for register offsets and array indexing (Weigang He) - Convert the TI OMAP bandgap DT bindings to schema and document the DRA752 compatible and thermal sensor cells (Bhargav Joshi) - Use DT property helpers matching the signed temperature and unsigned hysteresis types defined by the thermal zone bindings (Rob Herring) - Reuse common error handling code in the power allocator governor bind callback (Markus Elfring) - Constify the thermal zone device operations in the i.MX9 driver (Christophe JAILLET) - Fix kernel-doc parameter names and firmware node references in the cooling device registration helpers (Aryan Sinha) - Correct the documented return value of ti_bandgap_get_sensor_data() to describe validation errors and potentially NULL stored data (Karl Mehltretter) - Ensure NUL-termination when copying the cooling device command-line argument in tmon and fix the associated string truncation warning (Florian Fainelli) - Fix undefined behavior caused by a signed shift in the tmon thermal zone binding display (Liu Jing) - Fix wording in tmon comments (Hemanth Selam) - Simplify cpufreq_set_cur_state() in the cpufreq_cooling thermal control driver (Thorsten Blum)" * tag 'thermal-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (51 commits) thermal/drivers/rzg3e: Propagate reset assertion failure from suspend thermal: ti-soc-thermal: Fix the return value in ti_bandgap_get_sensor_data() kernel-doc thermal/fw: Fix kernel-doc parameter names thermal/qcom/tsens: Add support for MSM8952 tsens dt-bindings: thermal: tsens: Add MSM8952 thermal/drivers/amlogic: Switch to clk_bulk API to support multiple clocks dt-bindings: thermal: amlogic: describe bus clock for t7-thermal tools/thermal/tmon: Fix string truncation warning in optarg copy thermal/drivers/airoha: Register a thermal zone per AN7583 sensor dt-bindings: arm: airoha: Use one thermal sensor cell for AN7583 thermal/drivers/airoha: Serialise access to the shared ADC thermal/drivers/airoha: Fix AN7583 ADC mux field mapping thermal/drivers/sprd: validate the sensor id from the device tree thermal/drivers/ti-soc-thermal: Cancel pending alert work on remove thermal/drivers/qcom-spmi-temp-alarm: Fix temp_map indexing thermal: gov_power_allocator: Use common error handling code in power_allocator_bind() tools/thermal/tmon: Fix signed shift UB in binding display MAINTAINERS: Add entry for Samsung Exynos ACPM thermal driver thermal/drivers/samsung: Add Exynos ACPM TMU driver GS101 dt-bindings: thermal: Add Google GS101 TMU ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/thermal/airoha_thermal.c179
-rw-r--r--drivers/thermal/amlogic_thermal.c14
-rw-r--r--drivers/thermal/cpufreq_cooling.c14
-rw-r--r--drivers/thermal/devfreq_cooling.c4
-rw-r--r--drivers/thermal/gov_power_allocator.c18
-rw-r--r--drivers/thermal/imx91_thermal.c2
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device.c14
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c28
-rw-r--r--drivers/thermal/intel/intel_powerclamp.c64
-rw-r--r--drivers/thermal/intel/intel_tcc_cooling.c1
-rw-r--r--drivers/thermal/qcom/qcom-spmi-adc-tm5.c4
-rw-r--r--drivers/thermal/qcom/qcom-spmi-temp-alarm.c8
-rw-r--r--drivers/thermal/qcom/tsens-v1.c41
-rw-r--r--drivers/thermal/qcom/tsens-v2.c4
-rw-r--r--drivers/thermal/qcom/tsens.c3
-rw-r--r--drivers/thermal/qcom/tsens.h2
-rw-r--r--drivers/thermal/renesas/rzg3e_thermal.c45
-rw-r--r--drivers/thermal/renesas/rzg3s_thermal.c38
-rw-r--r--drivers/thermal/samsung/Kconfig19
-rw-r--r--drivers/thermal/samsung/Makefile2
-rw-r--r--drivers/thermal/samsung/acpm-tmu.c651
-rw-r--r--drivers/thermal/sprd_thermal.c6
-rw-r--r--drivers/thermal/thermal_core.c2
-rw-r--r--drivers/thermal/thermal_fw.c8
-rw-r--r--drivers/thermal/thermal_of.c15
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-bandgap.c22
-rw-r--r--drivers/thermal/ti-soc-thermal/ti-thermal-common.c4
27 files changed, 1028 insertions, 184 deletions
diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c
index 77b3a1af271f..7c63a1b44a6b 100644
--- a/drivers/thermal/airoha_thermal.c
+++ b/drivers/thermal/airoha_thermal.c
@@ -5,6 +5,7 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/mfd/syscon.h>
+#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
@@ -18,10 +19,10 @@
#define EN7581_DOUT_TADC 0x2f8
#define EN7581_DOUT_TADC_MASK GENMASK(15, 0)
-#define AN7583_MUX_SENSOR 0x2a0
+#define AN7583_MUX_TADC 0x2a0
#define AN7583_LOAD_ADJ GENMASK(3, 2)
-#define AN7583_MUX_TADC 0x2e4
-#define AN7583_MUX_TADC_MASK GENMASK(3, 1)
+#define AN7583_MUX_SENSOR 0x2e4
+#define AN7583_MUX_SENSOR_MASK GENMASK(3, 1)
#define AN7583_DOUT_TADC 0x2f0
/* PTP_THERMAL regs */
@@ -191,6 +192,7 @@
#define AN7583_NUM_SENSOR 3
#define AIROHA_THERMAL_NO_MUX_SENSOR -1
+#define AIROHA_THERMAL_MUX_TRIES 3
/* Convert temp to raw value as read from ADC ((((temp / 100) - init) * slope) / 1000) + offset */
#define TEMP_TO_RAW(priv, temp) ((((((temp) / 100) - (priv)->init_temp) * \
@@ -243,6 +245,11 @@ enum airoha_thermal_chip_scu_field {
AIROHA_THERMAL_FIELD_MAX,
};
+struct airoha_thermal_zone {
+ struct airoha_thermal_priv *priv;
+ int sensor;
+};
+
struct airoha_thermal_priv {
struct regmap *map;
struct regmap *chip_scu;
@@ -250,7 +257,8 @@ struct airoha_thermal_priv {
struct resource scu_adc_res;
u32 pllrg_protect;
- int current_adc;
+ /* Serialises mux selection and ADC sampling */
+ struct mutex lock;
struct thermal_zone_device *tz;
int init_temp;
@@ -260,6 +268,9 @@ struct airoha_thermal_priv {
struct airoha_thermal_soc_data {
u32 pllrg_protect;
+ /* Sensor mux value per zone, NULL without a sensor mux */
+ const int *sensors;
+ int num_sensors;
const struct thermal_zone_device_ops *thdev_ops;
int (*probe)(struct platform_device *pdev,
@@ -294,9 +305,26 @@ static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv)
return val;
}
+static bool airoha_thermal_mux_holds(struct airoha_thermal_priv *priv,
+ int tdac_idx, int sensor_idx)
+{
+ unsigned int val;
+
+ if (regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], &val) ||
+ val != tdac_idx)
+ return false;
+
+ if (sensor_idx == AIROHA_THERMAL_NO_MUX_SENSOR)
+ return true;
+
+ return !regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR], &val) &&
+ val == sensor_idx;
+}
+
static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv,
int tdac_idx, int sensor_idx)
{
+ int tries = AIROHA_THERMAL_MUX_TRIES;
u32 pllrg;
/* Save PLLRG current value */
@@ -307,19 +335,17 @@ static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv,
priv->pllrg_protect);
/*
- * Configure Thermal Sensor mux to sensor_idx.
+ * Configure Thermal Sensor mux to sensor_idx and Thermal ADC mux to
+ * tdac_idx. Writes can be dropped silently, so read them back.
* (if not supported, sensor_idx is AIROHA_THERMAL_NO_MUX_SENSOR)
*/
- if (sensor_idx != AIROHA_THERMAL_NO_MUX_SENSOR)
- regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR],
- sensor_idx);
-
- /* Configure Thermal ADC mux to tdac_idx */
- if (priv->current_adc != tdac_idx) {
+ do {
+ if (sensor_idx != AIROHA_THERMAL_NO_MUX_SENSOR)
+ regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR],
+ sensor_idx);
regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC],
tdac_idx);
- priv->current_adc = tdac_idx;
- }
+ } while (!airoha_thermal_mux_holds(priv, tdac_idx, sensor_idx) && --tries);
/* Restore PLLRG value on exit */
regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg);
@@ -330,7 +356,8 @@ static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv,
static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+ struct airoha_thermal_zone *zone = thermal_zone_device_priv(tz);
+ struct airoha_thermal_priv *priv = zone->priv;
int min_value, max_value, avg_value, value;
int i;
@@ -356,7 +383,8 @@ static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
static int en7581_thermal_set_trips(struct thermal_zone_device *tz, int low,
int high)
{
- struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+ struct airoha_thermal_zone *zone = thermal_zone_device_priv(tz);
+ struct airoha_thermal_priv *priv = zone->priv;
bool enable_monitor = false;
if (high != INT_MAX) {
@@ -528,7 +556,7 @@ static int en7581_thermal_probe(struct platform_device *pdev,
struct device_node *chip_scu_np;
struct device *dev = &pdev->dev;
void __iomem *base;
- int i, irq, ret;
+ int i;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
@@ -567,6 +595,18 @@ static int en7581_thermal_probe(struct platform_device *pdev,
of_address_to_resource(chip_scu_np, 0, &priv->scu_adc_res);
of_node_put(chip_scu_np);
+ en7581_thermal_setup_monitor(priv);
+ en7581_thermal_setup_adc_val(dev, priv);
+
+ return 0;
+}
+
+static int en7581_thermal_post_probe(struct platform_device *pdev)
+{
+ struct airoha_thermal_priv *priv = platform_get_drvdata(pdev);
+ int irq, ret;
+
+ /* After zone registration: the handler uses priv->tz */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
@@ -577,45 +617,63 @@ static int en7581_thermal_probe(struct platform_device *pdev,
if (ret)
return ret;
- en7581_thermal_setup_monitor(priv);
- en7581_thermal_setup_adc_val(dev, priv);
+ /* Enable LOW and HIGH interrupt (if supported) */
+ regmap_write(priv->map, EN7581_TEMPMONINT,
+ EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0);
return 0;
}
-static int en7581_thermal_post_probe(struct platform_device *pdev)
+static int an7583_thermal_read_diode(struct airoha_thermal_priv *priv,
+ int diode, int sensor_idx, int *val)
{
- struct airoha_thermal_priv *priv = platform_get_drvdata(pdev);
+ airoha_set_thermal_mux(priv, diode, sensor_idx);
+ *val = airoha_get_thermal_ADC(priv);
- /* Enable LOW and HIGH interrupt (if supported) */
- regmap_write(priv->map, EN7581_TEMPMONINT,
- EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0);
+ return airoha_thermal_mux_holds(priv, diode, sensor_idx) ? 0 : -EAGAIN;
+}
- return 0;
+static int an7583_thermal_read_diodes(struct airoha_thermal_priv *priv,
+ int sensor_idx, int *zero, int *d0,
+ int *d1)
+{
+ int ret;
+
+ ret = an7583_thermal_read_diode(priv, AN7583_ZERO_TADC, sensor_idx, zero);
+ if (ret)
+ return ret;
+
+ ret = an7583_thermal_read_diode(priv, AN7583_D0_TADC, sensor_idx, d0);
+ if (ret)
+ return ret;
+
+ return an7583_thermal_read_diode(priv, AN7583_D1_TADC, sensor_idx, d1);
}
static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
- struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
- int sensor_idx;
+ struct airoha_thermal_zone *zone = thermal_zone_device_priv(tz);
+ struct airoha_thermal_priv *priv = zone->priv;
+ int sensor_idx = zone->sensor;
int delta_diode, delta_gain;
int coeff, slope, offset;
+ int tries = AIROHA_THERMAL_MUX_TRIES;
+ int ret;
int diode_zero, diode_d0, diode_d1;
- /* Always read sensor AN7583_BGP_TEMP_SENSOR */
- sensor_idx = AN7583_BGP_TEMP_SENSOR;
-
coeff = an7583_thermal_coeff[sensor_idx];
slope = an7583_thermal_slope[sensor_idx];
offset = an7583_thermal_offset[sensor_idx];
- airoha_set_thermal_mux(priv, AN7583_ZERO_TADC, sensor_idx);
- diode_zero = airoha_get_thermal_ADC(priv);
- airoha_set_thermal_mux(priv, AN7583_D0_TADC, sensor_idx);
- diode_d0 = airoha_get_thermal_ADC(priv);
- airoha_set_thermal_mux(priv, AN7583_D1_TADC, sensor_idx);
- diode_d1 = airoha_get_thermal_ADC(priv);
+ mutex_lock(&priv->lock);
+ do {
+ ret = an7583_thermal_read_diodes(priv, sensor_idx, &diode_zero,
+ &diode_d0, &diode_d1);
+ } while (ret == -EAGAIN && --tries);
+ mutex_unlock(&priv->lock);
+ if (ret)
+ return ret;
delta_diode = diode_d1 - diode_d0;
delta_gain = (delta_diode * coeff) / 100 + (diode_zero - diode_d1);
@@ -634,8 +692,8 @@ static const struct thermal_zone_device_ops an7583_tz_ops = {
static const struct reg_field an7583_chip_scu_fields[AIROHA_THERMAL_FIELD_MAX] = {
[AIROHA_THERMAL_DOUT_TADC] = REG_FIELD(AN7583_DOUT_TADC, 0, 31),
- [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(AN7583_MUX_TADC, 1, 3),
- [AIROHA_THERMAL_MUX_SENSOR] = REG_FIELD(AN7583_MUX_SENSOR, 2, 3),
+ [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(AN7583_MUX_TADC, 2, 3),
+ [AIROHA_THERMAL_MUX_SENSOR] = REG_FIELD(AN7583_MUX_SENSOR, 1, 3),
};
static int an7583_thermal_probe(struct platform_device *pdev,
@@ -667,7 +725,7 @@ static int airoha_thermal_probe(struct platform_device *pdev)
const struct airoha_thermal_soc_data *soc_data;
struct airoha_thermal_priv *priv;
struct device *dev = &pdev->dev;
- int ret;
+ int ret, i;
soc_data = device_get_match_data(dev);
@@ -676,7 +734,10 @@ static int airoha_thermal_probe(struct platform_device *pdev)
return -ENOMEM;
priv->pllrg_protect = soc_data->pllrg_protect;
- priv->current_adc = -1;
+
+ ret = devm_mutex_init(dev, &priv->lock);
+ if (ret)
+ return ret;
if (!soc_data->probe)
return -EINVAL;
@@ -686,11 +747,31 @@ static int airoha_thermal_probe(struct platform_device *pdev)
return ret;
/* register of thermal sensor and get info from DT */
- priv->tz = devm_thermal_of_zone_register(dev, 0, priv,
- soc_data->thdev_ops);
- if (IS_ERR(priv->tz)) {
- dev_err(dev, "register thermal zone sensor failed\n");
- return PTR_ERR(priv->tz);
+ for (i = 0; i < soc_data->num_sensors; i++) {
+ struct airoha_thermal_zone *zone;
+ struct thermal_zone_device *tz;
+
+ zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL);
+ if (!zone)
+ return -ENOMEM;
+
+ zone->priv = priv;
+ zone->sensor = soc_data->sensors ? soc_data->sensors[i] :
+ AIROHA_THERMAL_NO_MUX_SENSOR;
+
+ tz = devm_thermal_of_zone_register(dev, i, zone,
+ soc_data->thdev_ops);
+ if (IS_ERR(tz)) {
+ /* A DT may describe fewer zones than sensors */
+ if (i && PTR_ERR(tz) == -ENODEV)
+ continue;
+
+ dev_err(dev, "register thermal zone %d failed\n", i);
+ return PTR_ERR(tz);
+ }
+
+ if (!i)
+ priv->tz = tz;
}
platform_set_drvdata(pdev, priv);
@@ -698,7 +779,15 @@ static int airoha_thermal_probe(struct platform_device *pdev)
return soc_data->post_probe ? soc_data->post_probe(pdev) : 0;
}
+/* The bandgap sensor stays zone 0 */
+static const int an7583_zone_sensors[AN7583_NUM_SENSOR] = {
+ AN7583_BGP_TEMP_SENSOR,
+ AN7583_GBE_TEMP_SENSOR,
+ AN7583_CPU_TEMP_SENSOR,
+};
+
static const struct airoha_thermal_soc_data en7581_data = {
+ .num_sensors = 1,
.pllrg_protect = EN7581_SCU_THERMAL_PROTECT_KEY,
.thdev_ops = &en7581_thdev_ops,
.probe = &en7581_thermal_probe,
@@ -706,6 +795,8 @@ static const struct airoha_thermal_soc_data en7581_data = {
};
static const struct airoha_thermal_soc_data an7583_data = {
+ .sensors = an7583_zone_sensors,
+ .num_sensors = AN7583_NUM_SENSOR,
.pllrg_protect = AN7583_SCU_THERMAL_PROTECT_KEY,
.thdev_ops = &an7583_tz_ops,
.probe = &an7583_thermal_probe,
diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c
index a0b530624b60..60728de6968a 100644
--- a/drivers/thermal/amlogic_thermal.c
+++ b/drivers/thermal/amlogic_thermal.c
@@ -100,7 +100,8 @@ struct amlogic_thermal {
const struct amlogic_thermal_data *data;
struct regmap *regmap;
struct regmap *sec_ao_map;
- struct clk *clk;
+ struct clk_bulk_data *clks;
+ int num_clks;
struct thermal_zone_device *tzd;
u32 trim_info;
struct meson_sm_firmware *sm_fw;
@@ -142,7 +143,7 @@ static int amlogic_thermal_enable(struct amlogic_thermal *data)
{
int ret;
- ret = clk_prepare_enable(data->clk);
+ ret = clk_bulk_prepare_enable(data->num_clks, data->clks);
if (ret)
return ret;
@@ -156,7 +157,7 @@ static void amlogic_thermal_disable(struct amlogic_thermal *data)
{
regmap_update_bits(data->regmap, TSENSOR_CFG_REG1,
TSENSOR_CFG_REG1_ENABLE, 0);
- clk_disable_unprepare(data->clk);
+ clk_bulk_disable_unprepare(data->num_clks, data->clks);
}
static int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
@@ -323,9 +324,10 @@ static int amlogic_thermal_probe(struct platform_device *pdev)
if (IS_ERR(pdata->regmap))
return PTR_ERR(pdata->regmap);
- pdata->clk = devm_clk_get(dev, NULL);
- if (IS_ERR(pdata->clk))
- return dev_err_probe(dev, PTR_ERR(pdata->clk), "failed to get clock\n");
+ ret = devm_clk_bulk_get_all(dev, &pdata->clks);
+ if (ret <= 0)
+ return dev_err_probe(dev, ret ?: -ENOENT, "failed to get clocks\n");
+ pdata->num_clks = ret;
if (pdata->data->use_sm)
ret = amlogic_thermal_probe_sm(pdev, pdata);
diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c
index 768859a7aed0..d75b42fb955e 100644
--- a/drivers/thermal/cpufreq_cooling.c
+++ b/drivers/thermal/cpufreq_cooling.c
@@ -488,12 +488,12 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
frequency = get_state_freq(cpufreq_cdev, state);
ret = freq_qos_update_request(&cpufreq_cdev->qos_req, frequency);
- if (ret >= 0) {
- cpufreq_cdev->cpufreq_state = state;
- ret = 0;
- }
+ if (ret < 0)
+ return ret;
+
+ cpufreq_cdev->cpufreq_state = state;
- return ret;
+ return 0;
}
/**
@@ -661,8 +661,8 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy)
cdev = __cpufreq_cooling_register(np, policy, em);
if (IS_ERR(cdev)) {
- pr_err("cpufreq_cooling: cpu%d failed to register as cooling device: %ld\n",
- policy->cpu, PTR_ERR(cdev));
+ pr_err("cpufreq_cooling: cpu%d failed to register as cooling device: %pe\n",
+ policy->cpu, cdev);
cdev = NULL;
}
}
diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c
index 0330a8112832..9ada52cbf7dd 100644
--- a/drivers/thermal/devfreq_cooling.c
+++ b/drivers/thermal/devfreq_cooling.c
@@ -156,8 +156,8 @@ static unsigned long get_voltage(struct devfreq *df, unsigned long freq)
opp = dev_pm_opp_find_freq_exact(dev, freq, false);
if (IS_ERR(opp)) {
- dev_err_ratelimited(dev, "Failed to find OPP for frequency %lu: %ld\n",
- freq, PTR_ERR(opp));
+ dev_err_ratelimited(dev, "Failed to find OPP for frequency %lu: %pe\n",
+ freq, opp);
return 0;
}
diff --git a/drivers/thermal/gov_power_allocator.c b/drivers/thermal/gov_power_allocator.c
index 37f2e22a999e..1cc9605a738f 100644
--- a/drivers/thermal/gov_power_allocator.c
+++ b/drivers/thermal/gov_power_allocator.c
@@ -660,10 +660,15 @@ static void power_allocator_update_tz(struct thermal_zone_device *tz,
enum thermal_notify_event reason)
{
struct power_allocator_params *params = tz->governor_data;
- const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
+ const struct thermal_trip_desc *td;
struct thermal_instance *instance;
int num_actors = 0;
+ if (!params->trip_max)
+ return;
+
+ td = trip_to_trip_desc(params->trip_max);
+
switch (reason) {
case THERMAL_TZ_BIND_CDEV:
case THERMAL_TZ_UNBIND_CDEV:
@@ -707,22 +712,20 @@ static int power_allocator_bind(struct thermal_zone_device *tz)
ret = check_power_actors(tz, params);
if (ret < 0) {
dev_warn(&tz->device, "power_allocator: binding failed\n");
- kfree(params);
- return ret;
+ goto free_params;
}
ret = allocate_actors_buffer(params, ret);
if (ret) {
dev_warn(&tz->device, "power_allocator: allocation failed\n");
- kfree(params);
- return ret;
+ goto free_params;
}
if (!tz->tzp) {
tz->tzp = kzalloc_obj(*tz->tzp);
if (!tz->tzp) {
ret = -ENOMEM;
- goto free_params;
+ goto free_power;
}
params->allocated_tzp = true;
@@ -746,8 +749,9 @@ static int power_allocator_bind(struct thermal_zone_device *tz)
return 0;
-free_params:
+free_power:
kfree(params->power);
+free_params:
kfree(params);
return ret;
diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c
index 274eee303142..196ee20fce1e 100644
--- a/drivers/thermal/imx91_thermal.c
+++ b/drivers/thermal/imx91_thermal.c
@@ -225,7 +225,7 @@ static int imx91_tmu_change_mode(struct thermal_zone_device *tz, enum thermal_de
return 0;
}
-static struct thermal_zone_device_ops tmu_tz_ops = {
+static const struct thermal_zone_device_ops tmu_tz_ops = {
.get_temp = imx91_tmu_get_temp,
.change_mode = imx91_tmu_change_mode,
.set_trips = imx91_tmu_set_trips,
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
index f80dbe2ca7e4..850bfe7c1ede 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
@@ -179,17 +179,21 @@ static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone,
{
int cpu;
int curr_temp, ret;
-
- *temp = 0;
+ bool temp_valid = false;
for_each_online_cpu(cpu) {
ret = intel_tcc_get_temp(cpu, &curr_temp, false);
if (ret < 0)
return ret;
- if (!*temp || curr_temp > *temp)
+ if (!temp_valid || curr_temp > *temp) {
*temp = curr_temp;
+ temp_valid = true;
+ }
}
+ if (!temp_valid)
+ return -ENODATA;
+
*temp *= 1000;
return 0;
@@ -291,10 +295,8 @@ int proc_thermal_add(struct device *dev, struct proc_thermal_device *proc_priv)
}
proc_priv->int340x_zone = int340x_thermal_zone_add(adev, get_temp);
- if (IS_ERR(proc_priv->int340x_zone)) {
+ if (IS_ERR(proc_priv->int340x_zone))
return PTR_ERR(proc_priv->int340x_zone);
- } else
- ret = 0;
ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
proc_thermal_notify,
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
index 91f291627132..e161b95828e8 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c
@@ -66,15 +66,16 @@ static int slider_def_balance_set(const char *arg, const struct kernel_param *kp
guard(mutex)(&slider_param_lock);
ret = kstrtou8(arg, 16, &slider_val);
- if (!ret) {
- if (slider_val <= slider_values[SOC_POWER_SLIDER_PERFORMANCE] ||
- slider_val >= slider_values[SOC_POWER_SLIDER_POWERSAVE])
- return -EINVAL;
+ if (ret)
+ return ret;
- slider_balanced_param = slider_val;
- }
+ if (slider_val <= slider_values[SOC_POWER_SLIDER_PERFORMANCE] ||
+ slider_val >= slider_values[SOC_POWER_SLIDER_POWERSAVE])
+ return -EINVAL;
- return ret;
+ slider_balanced_param = slider_val;
+
+ return 0;
}
static int slider_def_balance_get(char *buf, const struct kernel_param *kp)
@@ -101,14 +102,15 @@ static int slider_def_offset_set(const char *arg, const struct kernel_param *kp)
guard(mutex)(&slider_param_lock);
ret = kstrtou8(arg, 16, &offset);
- if (!ret) {
- if (offset > SOC_SLIDER_VALUE_MAXIMUM)
- return -EINVAL;
+ if (ret)
+ return ret;
- slider_offset = offset;
- }
+ if (offset > SOC_SLIDER_VALUE_MAXIMUM)
+ return -EINVAL;
- return ret;
+ slider_offset = offset;
+
+ return 0;
}
static int slider_def_offset_get(char *buf, const struct kernel_param *kp)
diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c
index bd7fd98dc310..1ffcb0a699e5 100644
--- a/drivers/thermal/intel/intel_powerclamp.c
+++ b/drivers/thermal/intel/intel_powerclamp.c
@@ -94,7 +94,7 @@ static int duration_set(const char *arg, const struct kernel_param *kp)
}
mutex_lock(&powerclamp_lock);
- duration = clamp(new_duration, 6ul, 25ul) * 1000;
+ duration = new_duration * 1000;
mutex_unlock(&powerclamp_lock);
exit:
@@ -103,13 +103,9 @@ exit:
static int duration_get(char *buf, const struct kernel_param *kp)
{
- int ret;
-
- mutex_lock(&powerclamp_lock);
- ret = sysfs_emit(buf, "%d\n", duration / 1000);
- mutex_unlock(&powerclamp_lock);
+ guard(mutex)(&powerclamp_lock);
- return ret;
+ return sysfs_emit(buf, "%u\n", duration / 1000);
}
static const struct kernel_param_ops duration_ops = {
@@ -143,12 +139,9 @@ copy_mask:
}
/* Return true if the cpumask and idle percent combination is invalid */
-static bool check_invalid(cpumask_var_t mask, u8 idle)
+static bool check_invalid(const struct cpumask *mask, u8 idle)
{
- if (cpumask_equal(cpu_present_mask, mask) && idle > MAX_ALL_CPU_IDLE)
- return true;
-
- return false;
+ return cpumask_equal(cpu_present_mask, mask) && idle > MAX_ALL_CPU_IDLE;
}
static int cpumask_set(const char *arg, const struct kernel_param *kp)
@@ -214,41 +207,32 @@ MODULE_PARM_DESC(cpumask, "Mask of CPUs to use for idle injection.");
static int max_idle_set(const char *arg, const struct kernel_param *kp)
{
u8 new_max_idle;
- int ret = 0;
+ int ret;
- mutex_lock(&powerclamp_lock);
+ guard(mutex)(&powerclamp_lock);
/* Can't set mask when cooling device is in use */
- if (powerclamp_data.clamping) {
- ret = -EAGAIN;
- goto skip_limit_set;
- }
+ if (powerclamp_data.clamping)
+ return -EAGAIN;
ret = kstrtou8(arg, 10, &new_max_idle);
if (ret)
- goto skip_limit_set;
+ return ret;
- if (new_max_idle > MAX_TARGET_RATIO) {
- ret = -EINVAL;
- goto skip_limit_set;
- }
+ if (new_max_idle > MAX_TARGET_RATIO)
+ return -EINVAL;
if (!cpumask_available(idle_injection_cpu_mask)) {
ret = allocate_copy_idle_injection_mask(cpu_present_mask);
if (ret)
- goto skip_limit_set;
+ return ret;
}
- if (check_invalid(idle_injection_cpu_mask, new_max_idle)) {
- ret = -EINVAL;
- goto skip_limit_set;
- }
+ if (check_invalid(idle_injection_cpu_mask, new_max_idle))
+ return -EINVAL;
max_idle = new_max_idle;
-skip_limit_set:
- mutex_unlock(&powerclamp_lock);
-
return ret;
}
@@ -289,9 +273,10 @@ static int window_size_set(const char *arg, const struct kernel_param *kp)
pr_err("Out of recommended window size %lu, between 2-10\n",
new_window_size);
ret = -EINVAL;
+ goto exit_win;
}
- window_size = clamp(new_window_size, 2ul, 10ul);
+ window_size = new_window_size;
smp_mb();
exit_win:
@@ -536,23 +521,17 @@ static struct idle_inject_device *ii_dev;
*/
static bool idle_inject_update(void)
{
- bool update = false;
-
/* We can't sleep in this callback */
if (!mutex_trylock(&powerclamp_lock))
return true;
if (!(powerclamp_data.count % powerclamp_data.window_size_now)) {
+ unsigned int runtime;
should_skip = powerclamp_adjust_controls(powerclamp_data.target_ratio,
powerclamp_data.guard,
powerclamp_data.window_size_now);
- update = true;
- }
-
- if (update) {
- unsigned int runtime = get_run_time();
-
+ runtime = get_run_time();
idle_inject_set_duration(ii_dev, runtime, duration);
}
@@ -560,10 +539,7 @@ static bool idle_inject_update(void)
mutex_unlock(&powerclamp_lock);
- if (should_skip)
- return false;
-
- return true;
+ return !should_skip;
}
/* This function starts idle injection by calling idle_inject_start() */
diff --git a/drivers/thermal/intel/intel_tcc_cooling.c b/drivers/thermal/intel/intel_tcc_cooling.c
index 52ea4f7fc9f9..75f6fbec2530 100644
--- a/drivers/thermal/intel/intel_tcc_cooling.c
+++ b/drivers/thermal/intel/intel_tcc_cooling.c
@@ -69,6 +69,7 @@ static const struct x86_cpu_id tcc_ids[] __initconst = {
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_PANTHERLAKE_R, NULL),
X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL),
X86_MATCH_VFM(INTEL_NOVALAKE, NULL),
X86_MATCH_VFM(INTEL_NOVALAKE_L, NULL),
diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
index af72db6299cd..4554a6fa55d1 100644
--- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
+++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
@@ -680,8 +680,8 @@ static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm)
continue;
}
- dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n",
- adc_tm->channels[i].channel, PTR_ERR(tzd));
+ dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %pe\n",
+ adc_tm->channels[i].channel, tzd);
return PTR_ERR(tzd);
}
adc_tm->channels[i].tzd = tzd;
diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
index fb003ca96454..911ebfd0cdeb 100644
--- a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
+++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
@@ -345,8 +345,8 @@ static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip,
}
skip:
- memcpy(chip->temp_thresh_map, chip->data->temp_map[threshold],
- sizeof(chip->temp_thresh_map));
+ memcpy(chip->temp_thresh_map, (*chip->data->temp_map)[threshold],
+ sizeof(chip->temp_thresh_map));
reg |= threshold;
if (disable_stage2_shutdown && !chip->require_stage2_shutdown)
reg |= SHUTDOWN_CTRL1_OVERRIDE_STAGE2;
@@ -535,8 +535,8 @@ static int qpnp_tm_sync_thresholds(struct qpnp_tm_chip *chip)
return ret;
threshold = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK;
- memcpy(chip->temp_thresh_map, chip->data->temp_map[threshold],
- sizeof(chip->temp_thresh_map));
+ memcpy(chip->temp_thresh_map, (*chip->data->temp_map)[threshold],
+ sizeof(chip->temp_thresh_map));
return ret;
}
diff --git a/drivers/thermal/qcom/tsens-v1.c b/drivers/thermal/qcom/tsens-v1.c
index faa5d00788ca..7bda8e6087c4 100644
--- a/drivers/thermal/qcom/tsens-v1.c
+++ b/drivers/thermal/qcom/tsens-v1.c
@@ -145,6 +145,34 @@ static const struct reg_field tsens_v1_regfields[MAX_REGFIELDS] = {
[TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
};
+static int __init init_8952(struct tsens_priv *priv)
+{
+ priv->sensor[0].p1_calib_offset = 2;
+ priv->sensor[0].p2_calib_offset = -1;
+ priv->sensor[1].p1_calib_offset = -4;
+ priv->sensor[1].p2_calib_offset = -4;
+ priv->sensor[2].p1_calib_offset = 4;
+ priv->sensor[2].p2_calib_offset = 5;
+ priv->sensor[3].p1_calib_offset = 1;
+ priv->sensor[3].p2_calib_offset = 1;
+ priv->sensor[4].p1_calib_offset = 2;
+ priv->sensor[4].p2_calib_offset = 3;
+ priv->sensor[5].p1_calib_offset = -1;
+ priv->sensor[5].p2_calib_offset = -1;
+ priv->sensor[6].p1_calib_offset = 0;
+ priv->sensor[6].p2_calib_offset = -1;
+ priv->sensor[7].p1_calib_offset = 3;
+ priv->sensor[7].p2_calib_offset = 4;
+ priv->sensor[8].p1_calib_offset = 2;
+ priv->sensor[8].p2_calib_offset = 4;
+ priv->sensor[9].p1_calib_offset = 0;
+ priv->sensor[9].p2_calib_offset = 0;
+ priv->sensor[10].p1_calib_offset = -3;
+ priv->sensor[10].p2_calib_offset = -2;
+
+ return init_common(priv);
+}
+
static int __init init_8956(struct tsens_priv *priv) {
priv->sensor[0].slope = 3313;
priv->sensor[1].slope = 3275;
@@ -223,6 +251,19 @@ struct tsens_plat_data data_8937 = {
.fields = tsens_v1_regfields,
};
+static const struct tsens_ops ops_8952 = {
+ .init = init_8952,
+ .calibrate = tsens_calibrate_common,
+ .get_temp = get_temp_tsens_valid,
+};
+
+struct tsens_plat_data data_8952 = {
+ .num_sensors = 11,
+ .ops = &ops_8952,
+ .feat = &tsens_v1_feat,
+ .fields = tsens_v1_regfields,
+};
+
static const struct tsens_ops ops_8956 = {
.init = init_8956,
.calibrate = tsens_calibrate_common,
diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
index 2ee117aa91ba..843dd29fad2e 100644
--- a/drivers/thermal/qcom/tsens-v2.c
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -54,8 +54,8 @@ static struct tsens_features tsens_v2_feat = {
.adc = 0,
.srot_split = 1,
.max_sensors = 16,
- .trip_min_temp = -40000,
- .trip_max_temp = 120000,
+ .trip_min_temp = -204000,
+ .trip_max_temp = 204000,
};
static struct tsens_features ipq8074_feat = {
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
index b5ec70201e2f..4d7013fc79d6 100644
--- a/drivers/thermal/qcom/tsens.c
+++ b/drivers/thermal/qcom/tsens.c
@@ -1192,6 +1192,9 @@ static const struct of_device_id tsens_table[] = {
.compatible = "qcom,msm8939-tsens",
.data = &data_8939,
}, {
+ .compatible = "qcom,msm8952-tsens",
+ .data = &data_8952,
+ }, {
.compatible = "qcom,msm8956-tsens",
.data = &data_8956,
}, {
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
index e8376accdff3..a35b15312961 100644
--- a/drivers/thermal/qcom/tsens.h
+++ b/drivers/thermal/qcom/tsens.h
@@ -669,7 +669,7 @@ extern struct tsens_plat_data data_8960;
extern struct tsens_plat_data data_8226, data_8909, data_8916, data_8939, data_8974, data_9607;
/* TSENS v1 targets */
-extern struct tsens_plat_data data_tsens_v1, data_8937, data_8976, data_8956;
+extern struct tsens_plat_data data_tsens_v1, data_8937, data_8952, data_8976, data_8956;
/* TSENS v1 with no RPM targets */
extern const struct tsens_plat_data data_ipq5018;
diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c
index c44f5b8858d0..e806d130dc53 100644
--- a/drivers/thermal/renesas/rzg3e_thermal.c
+++ b/drivers/thermal/renesas/rzg3e_thermal.c
@@ -93,6 +93,7 @@ struct rzg3e_thermal_info {
* @info: chip type specific information
* @trmval0: calibration value 0 (b)
* @trmval1: calibration value 1 (c)
+ * @sier: cached interrupt enable register
* @lock: protects hardware access during conversions
*/
struct rzg3e_thermal_priv {
@@ -103,6 +104,7 @@ struct rzg3e_thermal_priv {
const struct rzg3e_thermal_info *info;
u16 trmval0;
u16 trmval1;
+ u32 sier;
struct mutex lock;
};
@@ -148,12 +150,16 @@ static int rzg3e_thermal_power_on(struct rzg3e_thermal_priv *priv)
return ret;
}
+ /* Restore interrupt enable state */
+ writel(priv->sier, priv->base + TSU_SIER);
+
return 0;
}
static void rzg3e_thermal_power_off(struct rzg3e_thermal_priv *priv)
{
- /* Disable all interrupts */
+ /* Save and disable all interrupts */
+ priv->sier = readl(priv->base + TSU_SIER);
writel(0, priv->base + TSU_SIER);
/* Clear pending interrupts */
@@ -194,7 +200,12 @@ static u16 rzg3e_thermal_temp_to_code(struct rzg3e_thermal_priv *priv, int temp_
s64 numerator, denominator;
s64 code;
- numerator = (temp_mc - info->temp_d_mc) * (priv->trmval1 - priv->trmval0);
+ /*
+ * Perform the arithmetic in 64 bits so that it cannot overflow for
+ * -INT_MAX/INT_MAX values passed from the thermal core or when
+ * userspace writes arbitrary trip point temperatures.
+ */
+ numerator = ((s64)temp_mc - info->temp_d_mc) * (priv->trmval1 - priv->trmval0);
denominator = info->temp_e_mc - info->temp_d_mc;
code = div64_s64(numerator, denominator) + priv->trmval0;
@@ -214,6 +225,16 @@ static int rzg3e_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
guard(mutex)(&priv->lock);
+ /* Make sure a previous conversion is not in progress */
+ ret = readl_poll_timeout(priv->base + TSU_SSR, status,
+ !(status & TSU_SSR_CONV),
+ TSU_POLL_DELAY_US,
+ USEC_PER_MSEC);
+ if (ret) {
+ dev_err(priv->dev, "Timeout waiting for conversion\n");
+ goto out;
+ }
+
/* Clear any previous conversion status */
writel(TSU_SICR_ADCLR, priv->base + TSU_SICR);
@@ -241,7 +262,6 @@ static int rzg3e_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
*temp, *temp / 1000, abs(*temp) % 1000, code);
out:
- pm_runtime_mark_last_busy(priv->dev);
pm_runtime_put_autosuspend(priv->dev);
return ret;
}
@@ -254,10 +274,6 @@ static int rzg3e_thermal_set_trips(struct thermal_zone_device *tz,
u32 val;
int ret;
- /* Hardware requires low < high */
- if (low >= high)
- return -EINVAL;
-
ret = pm_runtime_resume_and_get(priv->dev);
if (ret < 0)
return ret;
@@ -293,11 +309,9 @@ static int rzg3e_thermal_set_trips(struct thermal_zone_device *tz,
/* Enable comparison with "out of range" mode (CMPCOND=0) */
writel(TSU_CMSR_CMPEN, priv->base + TSU_CMSR);
- /* Unmask compare IRQ and start a conversion to evaluate window */
+ /* Unmask compare IRQ */
writel(TSU_SIER_CMPIE, priv->base + TSU_SIER);
- writel(TSU_STRGR_ADST, priv->base + TSU_STRGR);
- pm_runtime_mark_last_busy(priv->dev);
pm_runtime_put_autosuspend(priv->dev);
return 0;
@@ -469,7 +483,6 @@ static int rzg3e_thermal_probe(struct platform_device *pdev)
if (ret)
dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
- pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
dev_info(dev, "RZ/G3E thermal sensor registered\n");
@@ -499,15 +512,19 @@ static int rzg3e_thermal_runtime_resume(struct device *dev)
static int rzg3e_thermal_suspend(struct device *dev)
{
struct rzg3e_thermal_priv *priv = dev_get_drvdata(dev);
+ bool active = pm_runtime_active(dev);
+ int ret;
/* If device is active, power it off */
- if (pm_runtime_active(dev))
+ if (active)
rzg3e_thermal_power_off(priv);
/* Assert reset to ensure clean state after resume */
- reset_control_assert(priv->rstc);
+ ret = reset_control_assert(priv->rstc);
+ if (ret && active)
+ rzg3e_thermal_power_on(priv);
- return 0;
+ return ret;
}
static int rzg3e_thermal_resume(struct device *dev)
diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c
index 7ced8f76a0ec..a8716da7fcd2 100644
--- a/drivers/thermal/renesas/rzg3s_thermal.c
+++ b/drivers/thermal/renesas/rzg3s_thermal.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/property.h>
#include <linux/reset.h>
#include <linux/thermal.h>
#include <linux/units.h>
@@ -27,10 +28,6 @@
#define TSU_READ_STEPS 8
-/* Default calibration values, if FUSE values are missing. */
-#define SW_CALIB0_VAL 1297
-#define SW_CALIB1_VAL 751
-
#define MCELSIUS(temp) ((temp) * MILLIDEGREE_PER_DEGREE)
/**
@@ -55,6 +52,16 @@ struct rzg3s_thermal_priv {
u16 calib1;
};
+/**
+ * struct rzg3s_thermal_info - RZ/G3S thermal information
+ * @calib0: calibration value if FUSE value is missing
+ * @calib1: calibration value if FUSE value is missing
+ */
+struct rzg3s_thermal_info {
+ u16 calib0;
+ u16 calib1;
+};
+
static int rzg3s_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct rzg3s_thermal_priv *priv = thermal_zone_device_priv(tz);
@@ -149,7 +156,8 @@ static const struct thermal_zone_device_ops rzg3s_tz_of_ops = {
.change_mode = rzg3s_thermal_change_mode,
};
-static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv)
+static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv,
+ const struct rzg3s_thermal_info *info)
{
struct device *dev = priv->dev;
u32 val;
@@ -163,13 +171,13 @@ static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv)
if (val & OTPTSUTRIM_EN_MASK)
priv->calib0 = FIELD_GET(OTPTSUTRIM_MASK, val);
else
- priv->calib0 = SW_CALIB0_VAL;
+ priv->calib0 = info->calib0;
val = readl(priv->base + OTPTSUTRIM_REG(1));
if (val & OTPTSUTRIM_EN_MASK)
priv->calib1 = FIELD_GET(OTPTSUTRIM_MASK, val);
else
- priv->calib1 = SW_CALIB1_VAL;
+ priv->calib1 = info->calib1;
pm_runtime_put_autosuspend(dev);
@@ -178,6 +186,7 @@ static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv)
static int rzg3s_thermal_probe(struct platform_device *pdev)
{
+ const struct rzg3s_thermal_info *info = device_get_match_data(&pdev->dev);
struct rzg3s_thermal_priv *priv;
struct device *dev = &pdev->dev;
int ret;
@@ -208,7 +217,7 @@ static int rzg3s_thermal_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "Failed to enable runtime PM!\n");
- ret = rzg3s_thermal_read_calib(priv);
+ ret = rzg3s_thermal_read_calib(priv, info);
if (ret)
return dev_err_probe(dev, ret, "Failed to read calibration data!\n");
@@ -251,8 +260,19 @@ static const struct dev_pm_ops rzg3s_thermal_pm_ops = {
SYSTEM_SLEEP_PM_OPS(rzg3s_thermal_suspend, rzg3s_thermal_resume)
};
+static const struct rzg3s_thermal_info rzg3s_info = {
+ .calib0 = 1297,
+ .calib1 = 751,
+};
+
+static const struct rzg3s_thermal_info rzg3l_info = {
+ .calib0 = 1322,
+ .calib1 = 772,
+};
+
static const struct of_device_id rzg3s_thermal_dt_ids[] = {
- { .compatible = "renesas,r9a08g045-tsu" },
+ { .compatible = "renesas,r9a08g045-tsu", .data = &rzg3s_info },
+ { .compatible = "renesas,r9a08g046-tsu", .data = &rzg3l_info },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rzg3s_thermal_dt_ids);
diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig
index e1e8035d24fb..f63eeae304b7 100644
--- a/drivers/thermal/samsung/Kconfig
+++ b/drivers/thermal/samsung/Kconfig
@@ -10,3 +10,22 @@ config EXYNOS_THERMAL
the TMU, reports temperature and handles cooling action if defined.
This driver uses the Exynos core thermal APIs and TMU configuration
data from the supported SoCs.
+
+config EXYNOS_ACPM_THERMAL
+ tristate "Exynos ACPM thermal management unit driver"
+ depends on THERMAL_OF
+ depends on HAS_IOMEM
+ depends on EXYNOS_ACPM_PROTOCOL
+ default ARCH_EXYNOS
+ help
+ Support for the Thermal Management Unit (TMU) on Samsung Exynos SoCs
+ utilizing the ACPM IPC protocol, such as the Google GS101.
+
+ The TMU on these platforms is managed through a hybrid architecture.
+ This driver handles direct register access for thermal interrupt status
+ monitoring and communicates with the Alive Clock and Power Manager
+ (ACPM) firmware via the ACPM IPC protocol for functional sensor control
+ and configuration.
+
+ Select this if you want to monitor device temperature and enable
+ thermal mitigation on Samsung Exynos ACPM based devices.
diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile
index f139407150d2..daed80647c34 100644
--- a/drivers/thermal/samsung/Makefile
+++ b/drivers/thermal/samsung/Makefile
@@ -4,3 +4,5 @@
#
obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
exynos_thermal-y := exynos_tmu.o
+obj-$(CONFIG_EXYNOS_ACPM_THERMAL) += exynos_acpm_thermal.o
+exynos_acpm_thermal-y := acpm-tmu.o
diff --git a/drivers/thermal/samsung/acpm-tmu.c b/drivers/thermal/samsung/acpm-tmu.c
new file mode 100644
index 000000000000..f9802080acd7
--- /dev/null
+++ b/drivers/thermal/samsung/acpm-tmu.c
@@ -0,0 +1,651 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2019 Samsung Electronics Co., Ltd.
+ * Copyright 2025 Google LLC.
+ * Copyright 2026 Linaro Ltd.
+ */
+
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/firmware/samsung/exynos-acpm-protocol.h>
+#include <linux/interrupt.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#include "../thermal_hwmon.h"
+
+#define EXYNOS_TMU_SENSOR(i) BIT(i)
+#define EXYNOS_TMU_SENSORS_MAX_COUNT 16
+
+#define GS101_CPUCL2_SENSOR_MASK (EXYNOS_TMU_SENSOR(0) | \
+ EXYNOS_TMU_SENSOR(6) | \
+ EXYNOS_TMU_SENSOR(7) | \
+ EXYNOS_TMU_SENSOR(8) | \
+ EXYNOS_TMU_SENSOR(9))
+#define GS101_CPUCL1_SENSOR_MASK (EXYNOS_TMU_SENSOR(4) | \
+ EXYNOS_TMU_SENSOR(5))
+#define GS101_CPUCL0_SENSOR_MASK (EXYNOS_TMU_SENSOR(1) | \
+ EXYNOS_TMU_SENSOR(2))
+
+#define GS101_REG_INTPEND(i) ((i) * 0x50 + 0xf8)
+
+enum {
+ P0_INTPEND,
+ P1_INTPEND,
+ P2_INTPEND,
+ P3_INTPEND,
+ P4_INTPEND,
+ P5_INTPEND,
+ P6_INTPEND,
+ P7_INTPEND,
+ P8_INTPEND,
+ P9_INTPEND,
+ P10_INTPEND,
+ P11_INTPEND,
+ P12_INTPEND,
+ P13_INTPEND,
+ P14_INTPEND,
+ P15_INTPEND,
+ REG_INTPEND_COUNT,
+};
+
+struct acpm_tmu_sensor_group {
+ u16 mask;
+ u8 id;
+};
+
+struct acpm_tmu_sensor {
+ const struct acpm_tmu_sensor_group *group;
+ struct thermal_zone_device *tzd;
+ struct acpm_tmu_priv *priv;
+ struct mutex lock; /* protects sensor state */
+ bool enabled;
+};
+
+struct acpm_tmu_priv {
+ struct regmap_field *regmap_fields[REG_INTPEND_COUNT];
+ struct acpm_handle *handle;
+ struct device *dev;
+ struct clk *clk;
+ unsigned int mbox_chan_id;
+ unsigned int num_sensors;
+ int irq;
+ struct acpm_tmu_sensor sensors[] __counted_by(num_sensors);
+};
+
+struct acpm_tmu_driver_data {
+ const struct reg_field *reg_fields;
+ const struct acpm_tmu_sensor_group *sensor_groups;
+ unsigned int num_sensor_groups;
+ unsigned int mbox_chan_id;
+};
+
+#define ACPM_TMU_SENSOR_GROUP(_mask, _id) \
+ { \
+ .mask = _mask, \
+ .id = _id, \
+ }
+
+static const struct acpm_tmu_sensor_group gs101_sensor_groups[] = {
+ ACPM_TMU_SENSOR_GROUP(GS101_CPUCL2_SENSOR_MASK, 0),
+ ACPM_TMU_SENSOR_GROUP(GS101_CPUCL1_SENSOR_MASK, 1),
+ ACPM_TMU_SENSOR_GROUP(GS101_CPUCL0_SENSOR_MASK, 2),
+};
+
+static const struct reg_field gs101_reg_fields[REG_INTPEND_COUNT] = {
+ [P0_INTPEND] = REG_FIELD(GS101_REG_INTPEND(0), 0, 31),
+ [P1_INTPEND] = REG_FIELD(GS101_REG_INTPEND(1), 0, 31),
+ [P2_INTPEND] = REG_FIELD(GS101_REG_INTPEND(2), 0, 31),
+ [P3_INTPEND] = REG_FIELD(GS101_REG_INTPEND(3), 0, 31),
+ [P4_INTPEND] = REG_FIELD(GS101_REG_INTPEND(4), 0, 31),
+ [P5_INTPEND] = REG_FIELD(GS101_REG_INTPEND(5), 0, 31),
+ [P6_INTPEND] = REG_FIELD(GS101_REG_INTPEND(6), 0, 31),
+ [P7_INTPEND] = REG_FIELD(GS101_REG_INTPEND(7), 0, 31),
+ [P8_INTPEND] = REG_FIELD(GS101_REG_INTPEND(8), 0, 31),
+ [P9_INTPEND] = REG_FIELD(GS101_REG_INTPEND(9), 0, 31),
+ [P10_INTPEND] = REG_FIELD(GS101_REG_INTPEND(10), 0, 31),
+ [P11_INTPEND] = REG_FIELD(GS101_REG_INTPEND(11), 0, 31),
+ [P12_INTPEND] = REG_FIELD(GS101_REG_INTPEND(12), 0, 31),
+ [P13_INTPEND] = REG_FIELD(GS101_REG_INTPEND(13), 0, 31),
+ [P14_INTPEND] = REG_FIELD(GS101_REG_INTPEND(14), 0, 31),
+ [P15_INTPEND] = REG_FIELD(GS101_REG_INTPEND(15), 0, 31),
+};
+
+static const struct regmap_config gs101_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .use_relaxed_mmio = true,
+ .max_register = GS101_REG_INTPEND(15),
+};
+
+static const struct acpm_tmu_driver_data acpm_tmu_gs101 = {
+ .reg_fields = gs101_reg_fields,
+ .sensor_groups = gs101_sensor_groups,
+ .num_sensor_groups = ARRAY_SIZE(gs101_sensor_groups),
+ .mbox_chan_id = 9,
+};
+
+static int acpm_tmu_op_tz_control(struct acpm_tmu_sensor *sensor, bool on)
+{
+ struct acpm_tmu_priv *priv = sensor->priv;
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ int ret;
+
+ ret = ops->tz_control(handle, priv->mbox_chan_id, sensor->group->id,
+ on);
+ if (ret)
+ return ret;
+
+ sensor->enabled = on;
+
+ return 0;
+}
+
+static void acpm_tmu_control_rollback(struct acpm_tmu_priv *priv, int start_idx)
+{
+ int i;
+
+ for (i = start_idx; i >= 0; i--) {
+ struct acpm_tmu_sensor *sensor = &priv->sensors[i];
+ int ret;
+
+ if (!sensor->tzd)
+ continue;
+
+ mutex_lock(&sensor->lock);
+ ret = acpm_tmu_op_tz_control(sensor, false);
+ mutex_unlock(&sensor->lock);
+ if (ret)
+ dev_err(priv->dev, "Rollback: Failed to disable sensor %d: %d\n",
+ i, ret);
+ }
+}
+
+static int acpm_tmu_control(struct acpm_tmu_priv *priv, bool on, bool rollback)
+{
+ struct device *dev = priv->dev;
+ int i, ret, err = 0;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ struct acpm_tmu_sensor *sensor = &priv->sensors[i];
+
+ /* Skip sensors that weren't found in DT */
+ if (!sensor->tzd)
+ continue;
+
+ mutex_lock(&sensor->lock);
+ ret = acpm_tmu_op_tz_control(sensor, on);
+ mutex_unlock(&sensor->lock);
+ if (ret) {
+ if (!err)
+ err = ret;
+
+ /* On enable, stop on first error if rollback is requested */
+ if (on && rollback) {
+ acpm_tmu_control_rollback(priv, i - 1);
+ break;
+ }
+ }
+ }
+
+ pm_runtime_put_sync(dev);
+
+ return err;
+}
+
+static int acpm_tmu_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz);
+ struct acpm_tmu_priv *priv = sensor->priv;
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ struct device *dev = priv->dev;
+ int acpm_temp = 0, ret;
+
+ /*
+ * Fast path: check locklessly if the sensor is enabled to avoid
+ * expensive runtime PM operations when it is disabled. Any race with
+ * concurrent disabling is caught by the second check under the lock
+ * after PM resume.
+ */
+ if (!sensor->enabled)
+ return -EAGAIN;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
+ scoped_guard(mutex, &sensor->lock) {
+ if (!sensor->enabled) {
+ ret = -EAGAIN;
+ } else {
+ ret = ops->read_temp(handle, priv->mbox_chan_id,
+ sensor->group->id, &acpm_temp);
+ }
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ if (ret)
+ return ret;
+
+ *temp = acpm_temp * MILLIDEGREE_PER_DEGREE;
+
+ return 0;
+}
+
+static int acpm_tmu_update_thresholds(struct acpm_tmu_sensor *sensor,
+ u8 thresholds[2], u8 inten)
+{
+ struct acpm_tmu_priv *priv = sensor->priv;
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ unsigned int mbox_chan_id = priv->mbox_chan_id;
+ u8 acpm_sensor_id = sensor->group->id;
+ bool was_enabled;
+ int ret, restore_ret;
+
+ guard(mutex)(&sensor->lock);
+
+ was_enabled = sensor->enabled;
+
+ if (was_enabled) {
+ ret = acpm_tmu_op_tz_control(sensor, false);
+ if (ret)
+ return ret;
+ }
+
+ ret = ops->set_threshold(handle, mbox_chan_id, acpm_sensor_id,
+ thresholds, 2);
+ if (!ret) {
+ ret = ops->set_interrupt_enable(handle, mbox_chan_id,
+ acpm_sensor_id, inten);
+ }
+
+ if (was_enabled) {
+ restore_ret = acpm_tmu_op_tz_control(sensor, true);
+ if (restore_ret)
+ dev_err(priv->dev, "Failed to restore sensor state: %d\n",
+ restore_ret);
+ if (!ret)
+ ret = restore_ret;
+ }
+
+ return ret;
+}
+
+static int acpm_tmu_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz);
+ struct acpm_tmu_priv *priv = sensor->priv;
+ struct device *dev = priv->dev;
+ u8 thresholds[2] = {};
+ u8 inten = 0;
+ int ret;
+
+ /* If a valid lower bound exists, set the threshold and enable its interrupt */
+ if (low > -INT_MAX) {
+ thresholds[0] = clamp_val(low / MILLIDEGREE_PER_DEGREE, 0, 255);
+ inten |= BIT(0);
+ }
+
+ /* If a valid upper bound exists, set the threshold and enable its interrupt */
+ if (high < INT_MAX) {
+ thresholds[1] = clamp_val(high / MILLIDEGREE_PER_DEGREE, 0, 255);
+ inten |= BIT(1);
+ }
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = acpm_tmu_update_thresholds(sensor, thresholds, inten);
+
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops acpm_tmu_sensor_ops = {
+ .get_temp = acpm_tmu_get_temp,
+ .set_trips = acpm_tmu_set_trips,
+};
+
+static int acpm_tmu_has_pending_irq(struct acpm_tmu_sensor *sensor,
+ bool *pending_irq)
+{
+ struct acpm_tmu_priv *priv = sensor->priv;
+ unsigned long mask = sensor->group->mask;
+ int i, ret;
+ u32 val;
+
+ guard(mutex)(&sensor->lock);
+
+ for_each_set_bit(i, &mask, EXYNOS_TMU_SENSORS_MAX_COUNT) {
+ ret = regmap_field_read(priv->regmap_fields[i], &val);
+ if (ret)
+ return ret;
+
+ if (val) {
+ *pending_irq = true;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static irqreturn_t acpm_tmu_thread_fn(int irq, void *id)
+{
+ struct acpm_tmu_priv *priv = id;
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ struct device *dev = priv->dev;
+ bool handled = false;
+ int i, ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret) {
+ dev_err(dev, "Failed to resume: %d\n", ret);
+ return IRQ_NONE;
+ }
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ struct acpm_tmu_sensor *sensor = &priv->sensors[i];
+ bool pending_irq = false;
+
+ if (!sensor->tzd)
+ continue;
+
+ ret = acpm_tmu_has_pending_irq(sensor, &pending_irq);
+ if (ret || !pending_irq)
+ continue;
+
+ handled = true;
+
+ scoped_guard(mutex, &sensor->lock) {
+ ret = ops->clear_tz_irq(handle, priv->mbox_chan_id,
+ sensor->group->id);
+ if (ret)
+ dev_err(priv->dev, "Sensor %d: failed to clear IRQ (%d)\n",
+ i, ret);
+ }
+
+ thermal_zone_device_update(sensor->tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static const struct of_device_id acpm_tmu_match[] = {
+ { .compatible = "google,gs101-tmu-top" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, acpm_tmu_match);
+
+static int acpm_tmu_probe(struct platform_device *pdev)
+{
+ const struct acpm_tmu_driver_data *data = &acpm_tmu_gs101;
+ struct acpm_handle *acpm_handle;
+ struct device *dev = &pdev->dev;
+ struct acpm_tmu_priv *priv;
+ struct regmap *regmap;
+ void __iomem *base;
+ int i, ret;
+
+ acpm_handle = devm_acpm_get_by_phandle(dev);
+ if (IS_ERR(acpm_handle))
+ return dev_err_probe(dev, PTR_ERR(acpm_handle),
+ "Failed to get ACPM handle\n");
+
+ priv = devm_kzalloc(dev,
+ struct_size(priv, sensors, data->num_sensor_groups),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->handle = acpm_handle;
+ priv->mbox_chan_id = data->mbox_chan_id;
+ priv->num_sensors = data->num_sensor_groups;
+
+ platform_set_drvdata(pdev, priv);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return dev_err_probe(dev, PTR_ERR(base), "Failed to ioremap resource\n");
+
+ regmap = devm_regmap_init_mmio(dev, base, &gs101_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n");
+
+ ret = devm_regmap_field_bulk_alloc(dev, regmap, priv->regmap_fields,
+ data->reg_fields, REG_INTPEND_COUNT);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Unable to map syscon registers\n");
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk),
+ "Failed to get the clock\n");
+
+ priv->irq = platform_get_irq(pdev, 0);
+ if (priv->irq < 0)
+ return dev_err_probe(dev, priv->irq, "Failed to get irq\n");
+
+ pm_runtime_set_autosuspend_delay(dev, 100);
+ pm_runtime_use_autosuspend(dev);
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to resume device\n");
+
+ ret = acpm_handle->ops->tmu.init(acpm_handle, priv->mbox_chan_id);
+ if (ret) {
+ ret = dev_err_probe(dev, ret, "Failed to init TMU\n");
+ goto err_pm_put;
+ }
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ struct acpm_tmu_sensor *sensor = &priv->sensors[i];
+
+ mutex_init(&sensor->lock);
+ sensor->group = &data->sensor_groups[i];
+ sensor->priv = priv;
+
+ sensor->tzd = devm_thermal_of_zone_register(dev, i, sensor,
+ &acpm_tmu_sensor_ops);
+ if (IS_ERR(sensor->tzd)) {
+ ret = PTR_ERR(sensor->tzd);
+ if (ret == -ENODEV) {
+ sensor->tzd = NULL;
+ dev_dbg(dev, "Sensor %d not used in DT, skipping\n", i);
+ continue;
+ }
+
+ ret = dev_err_probe(dev, ret, "Failed to register sensor %d\n", i);
+ goto err_rollback;
+ }
+
+ mutex_lock(&sensor->lock);
+ ret = acpm_tmu_op_tz_control(sensor, true);
+ mutex_unlock(&sensor->lock);
+ if (ret) {
+ ret = dev_err_probe(dev, ret, "Failed to enable sensor %d\n", i);
+ goto err_rollback;
+ }
+
+ thermal_zone_device_update(sensor->tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ ret = devm_thermal_add_hwmon_sysfs(dev, sensor->tzd);
+ if (ret)
+ dev_warn(dev, "Failed to add hwmon sysfs!\n");
+ }
+
+ ret = devm_request_threaded_irq(dev, priv->irq, NULL,
+ acpm_tmu_thread_fn, IRQF_ONESHOT,
+ dev_name(dev), priv);
+ if (ret) {
+ ret = dev_err_probe(dev, ret, "Failed to request irq\n");
+ goto err_rollback;
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+
+err_rollback:
+ acpm_tmu_control_rollback(priv, i - 1);
+err_pm_put:
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_put_sync(dev);
+ return ret;
+}
+
+static void acpm_tmu_remove(struct platform_device *pdev)
+{
+ struct acpm_tmu_priv *priv = platform_get_drvdata(pdev);
+
+ /* Stop IRQ first to prevent race with thread_fn */
+ disable_irq(priv->irq);
+
+ /*
+ * Disable autosuspend to force the subsequent pm_runtime_put_sync()
+ * inside acpm_tmu_control() to synchronously suspend the device
+ * immediately, preventing clock leaks when the driver is removed.
+ */
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ acpm_tmu_control(priv, false, false);
+}
+
+static int acpm_tmu_suspend(struct device *dev)
+{
+ struct acpm_tmu_priv *priv = dev_get_drvdata(dev);
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ int ret, restore_ret;
+
+ ret = acpm_tmu_control(priv, false, false);
+ if (ret)
+ goto err_restore_sensors;
+
+ /* APB clock not required for this specific msg */
+ ret = ops->suspend(handle, priv->mbox_chan_id);
+ if (ret)
+ goto err_restore_sensors;
+
+ ret = pm_runtime_force_suspend(dev);
+ if (ret)
+ goto err_resume_acpm;
+
+ return 0;
+
+err_resume_acpm:
+ restore_ret = ops->resume(handle, priv->mbox_chan_id);
+ if (restore_ret)
+ dev_err(dev, "Failed to resume ACPM after force suspend failure: %d\n",
+ restore_ret);
+
+err_restore_sensors:
+ restore_ret = acpm_tmu_control(priv, true, false);
+ if (restore_ret)
+ dev_err(dev, "Failed to re-enable TMU after suspend failure: %d\n",
+ restore_ret);
+
+ return ret;
+}
+
+static int acpm_tmu_resume(struct device *dev)
+{
+ struct acpm_tmu_priv *priv = dev_get_drvdata(dev);
+ struct acpm_handle *handle = priv->handle;
+ const struct acpm_tmu_ops *ops = &handle->ops->tmu;
+ int ret, restore_ret;
+
+ /* APB clock not required for this specific msg */
+ ret = ops->resume(handle, priv->mbox_chan_id);
+ if (ret)
+ return ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ goto err_suspend_acpm;
+
+ ret = acpm_tmu_control(priv, true, true);
+ if (ret)
+ goto err_suspend_pm;
+
+ return 0;
+
+err_suspend_pm:
+ restore_ret = pm_runtime_force_suspend(dev);
+ if (restore_ret)
+ dev_err(dev, "Failed to force suspend during resume rollback: %d\n",
+ restore_ret);
+err_suspend_acpm:
+ restore_ret = ops->suspend(handle, priv->mbox_chan_id);
+ if (restore_ret)
+ dev_err(dev, "Failed to suspend ACPM during resume rollback: %d\n",
+ restore_ret);
+ return ret;
+}
+
+static int acpm_tmu_runtime_suspend(struct device *dev)
+{
+ struct acpm_tmu_priv *priv = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int acpm_tmu_runtime_resume(struct device *dev)
+{
+ struct acpm_tmu_priv *priv = dev_get_drvdata(dev);
+
+ return clk_prepare_enable(priv->clk);
+}
+
+static const struct dev_pm_ops acpm_tmu_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(acpm_tmu_suspend, acpm_tmu_resume)
+ RUNTIME_PM_OPS(acpm_tmu_runtime_suspend, acpm_tmu_runtime_resume, NULL)
+};
+
+static struct platform_driver acpm_tmu_driver = {
+ .driver = {
+ .name = "gs-tmu",
+ .pm = pm_ptr(&acpm_tmu_pm_ops),
+ .of_match_table = acpm_tmu_match,
+ },
+ .probe = acpm_tmu_probe,
+ .remove = acpm_tmu_remove,
+};
+module_platform_driver(acpm_tmu_driver);
+
+MODULE_AUTHOR("Tudor Ambarus <tudor.ambarus@linaro.org>");
+MODULE_DESCRIPTION("Samsung Exynos ACPM TMU Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/sprd_thermal.c b/drivers/thermal/sprd_thermal.c
index d683fcb0f8ab..b57277e2eac0 100644
--- a/drivers/thermal/sprd_thermal.c
+++ b/drivers/thermal/sprd_thermal.c
@@ -396,6 +396,12 @@ static int sprd_thm_probe(struct platform_device *pdev)
goto of_put;
}
+ if (sen->id < 0 || sen->id >= SPRD_THM_MAX_SENSOR) {
+ dev_err(&pdev->dev, "invalid sensor id %d\n", sen->id);
+ ret = -EINVAL;
+ goto of_put;
+ }
+
ret = sprd_thm_sensor_calibration(sen_child, thm, sen);
if (ret) {
dev_err(&pdev->dev, "efuse cal analysis failed");
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
index ac928c199829..e8d4da0aeac3 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -1666,7 +1666,7 @@ EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
*
* Return: On success returns a reference to an unique thermal zone with
* matching name equals to @name, an ERR_PTR otherwise (-EINVAL for invalid
- * paramenters, -ENODEV for not found and -EEXIST for multiple matches).
+ * parameters, -ENODEV for not found and -EEXIST for multiple matches).
*/
struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name)
{
diff --git a/drivers/thermal/thermal_fw.c b/drivers/thermal/thermal_fw.c
index e8bd2049cf74..7c28bfada386 100644
--- a/drivers/thermal/thermal_fw.c
+++ b/drivers/thermal/thermal_fw.c
@@ -11,13 +11,13 @@
/**
* thermal_fwnode_cooling_device_register() - register an thermal cooling device
- * @np: a pointer to a device tree node.
- * @of_index: a cooling device index in the cooling controller
+ * @fwnode: a pointer to a firmware node.
+ * @fwn_index: a cooling device index in the cooling controller
* @type: the thermal cooling device type.
* @devdata: device private data.
* @ops: standard thermal cooling devices callbacks.
*
- * This function will register a cooling device with device tree node reference.
+ * This function will register a cooling device with a firmware node reference.
* This interface function adds a new thermal cooling device (fan/processor/...)
* to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself
* to all the thermal zone devices registered at the same time.
@@ -71,7 +71,7 @@ __devm_thermal_fwnode_cooling_device_register(struct device *dev, struct fwnode_
/**
* devm_thermal_fwnode_cooling_device_register() - register a thermal cooling device
* @dev: a valid struct device pointer of a sensor device.
- * @fw_index: a cooling device index in the cooling controller
+ * @fwn_index: a cooling device index in the cooling controller
* @type: the thermal cooling device type.
* @devdata: device private data.
* @ops: standard thermal cooling devices callbacks.
diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c
index 14dfac9359b8..9e96fb1b033e 100644
--- a/drivers/thermal/thermal_of.c
+++ b/drivers/thermal/thermal_of.c
@@ -63,22 +63,23 @@ static int thermal_of_get_trip_type(struct device_node *np,
static int thermal_of_populate_trip(struct device_node *np,
struct thermal_trip *trip)
{
- int prop;
+ u32 hysteresis;
+ s32 temperature;
int ret;
- ret = of_property_read_u32(np, "temperature", &prop);
+ ret = of_property_read_s32(np, "temperature", &temperature);
if (ret < 0) {
pr_err("missing temperature property\n");
return ret;
}
- trip->temperature = prop;
+ trip->temperature = temperature;
- ret = of_property_read_u32(np, "hysteresis", &prop);
+ ret = of_property_read_u32(np, "hysteresis", &hysteresis);
if (ret < 0) {
pr_err("missing hysteresis property\n");
return ret;
}
- trip->hysteresis = prop;
+ trip->hysteresis = hysteresis;
ret = thermal_of_get_trip_type(np, &trip->type);
if (ret < 0) {
@@ -349,10 +350,10 @@ out:
}
/**
- * thermal_of_zone_unregister - Cleanup the specific allocated ressources
+ * thermal_of_zone_unregister - Cleanup the specific allocated resources
*
* This function disables the thermal zone and frees the different
- * ressources allocated specific to the thermal OF.
+ * resources allocated specific to the thermal OF.
*
* @tz: a pointer to the thermal zone structure
*/
diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c
index ba43399d0b38..457639be2b32 100644
--- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c
+++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c
@@ -332,7 +332,7 @@ static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id)
* ti_bandgap_read_counter() - read the sensor counter
* @bgp: pointer to bandgap instance
* @id: sensor id
- * @interval: resulting update interval in miliseconds
+ * @interval: resulting update interval in milliseconds
*/
static void ti_bandgap_read_counter(struct ti_bandgap *bgp, int id,
int *interval)
@@ -352,7 +352,7 @@ static void ti_bandgap_read_counter(struct ti_bandgap *bgp, int id,
* ti_bandgap_read_counter_delay() - read the sensor counter delay
* @bgp: pointer to bandgap instance
* @id: sensor id
- * @interval: resulting update interval in miliseconds
+ * @interval: resulting update interval in milliseconds
*/
static void ti_bandgap_read_counter_delay(struct ti_bandgap *bgp, int id,
int *interval)
@@ -394,7 +394,7 @@ static void ti_bandgap_read_counter_delay(struct ti_bandgap *bgp, int id,
* ti_bandgap_read_update_interval() - read the sensor update interval
* @bgp: pointer to bandgap instance
* @id: sensor id
- * @interval: resulting update interval in miliseconds
+ * @interval: resulting update interval in milliseconds
*
* Return: 0 on success or the proper error code
*/
@@ -427,7 +427,7 @@ exit:
* ti_bandgap_write_counter_delay() - set the counter_delay
* @bgp: pointer to bandgap instance
* @id: sensor id
- * @interval: desired update interval in miliseconds
+ * @interval: desired update interval in milliseconds
*
* Return: 0 on success or the proper error code
*/
@@ -471,7 +471,7 @@ static int ti_bandgap_write_counter_delay(struct ti_bandgap *bgp, int id,
* ti_bandgap_write_counter() - set the bandgap sensor counter
* @bgp: pointer to bandgap instance
* @id: sensor id
- * @interval: desired update interval in miliseconds
+ * @interval: desired update interval in milliseconds
*/
static void ti_bandgap_write_counter(struct ti_bandgap *bgp, int id,
u32 interval)
@@ -486,7 +486,7 @@ static void ti_bandgap_write_counter(struct ti_bandgap *bgp, int id,
* ti_bandgap_write_update_interval() - set the update interval
* @bgp: pointer to bandgap instance
* @id: sensor id
- * @interval: desired update interval in miliseconds
+ * @interval: desired update interval in milliseconds
*
* Return: 0 on success or the proper error code
*/
@@ -576,7 +576,8 @@ int ti_bandgap_set_sensor_data(struct ti_bandgap *bgp, int id, void *data)
* @bgp: pointer to bandgap instance
* @id: sensor id
*
- * Return: data stored by set function with sensor id on success or NULL
+ * Return: the stored sensor data, which may be %NULL, or an ERR_PTR()
+ * if @bgp or @id is invalid.
*/
void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id)
{
@@ -1077,6 +1078,10 @@ void ti_bandgap_remove(struct platform_device *pdev)
if (!soc_device_match(soc_no_cpu_notifier))
cpu_pm_unregister_notifier(&bgp->nb);
+ /* Stop the alerts first: they queue work on the sensors' data */
+ if (TI_BANDGAP_HAS(bgp, TALERT))
+ free_irq(bgp->irq, bgp);
+
/* Remove sensor interfaces */
for (i = 0; i < bgp->conf->sensor_count; i++) {
if (bgp->conf->sensors[i].unregister_cooling)
@@ -1093,9 +1098,6 @@ void ti_bandgap_remove(struct platform_device *pdev)
clk_put(bgp->fclock);
clk_put(bgp->div_clk);
- if (TI_BANDGAP_HAS(bgp, TALERT))
- free_irq(bgp->irq, bgp);
-
if (TI_BANDGAP_HAS(bgp, TSHUT))
free_irq(gpiod_to_irq(bgp->tshut_gpiod), NULL);
}
diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
index 0cf0826b805a..4bf3b2e0fc68 100644
--- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
+++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
@@ -195,6 +195,10 @@ int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id)
data = ti_bandgap_get_sensor_data(bgp, id);
+ /* Work queued by the talert IRQ must not outlive the data */
+ if (!IS_ERR_OR_NULL(data))
+ cancel_work_sync(&data->thermal_wq);
+
if (!IS_ERR_OR_NULL(data) && data->ti_thermal) {
if (data->our_zone)
thermal_zone_device_unregister(data->ti_thermal);