* [PATCH v4 00/11] thermal: samsung: Add support for Google GS101 TMU
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
Add support for the Thermal Management Unit (TMU) on the Google GS101
SoC.
The GS101 TMU implementation utilizes a hybrid architecture where
management is shared between the kernel and the Alive Clock and
Power Manager (ACPM) firmware. This hybrid ACPM TMU architecture is
also present on other Samsung Exynos SoCs (e.g., AutoV920, Exynos850).
Dependencies
============
- context dependency on the ACPM fixes sent at:
Link: https://lore.kernel.org/linux-samsung-soc/20260423-acpm-fixes-sashiko-reports-v1-0-2217b790925e@linaro.org/T/#t
- cleanup and prep firmware patches 2, 3, 4, 5, 6, 7: required by the
thermal driver (patch 8).
- bindings (patch 1): required for DTS validation.
- thermal driver patch 8: required by defconfig (patch 11) - logical
dependency.
Given the thermal driver is a new addition, I suggest everything to go
through the Samsung SoC tree, with ACKs from the Thermal maintainers.
The MFD and clk maintainers are included because of the cleanup patches
(4 and 5). ACPM updated some structures that the mfd and clk client
drivers are using, so these patches shall naturally go via the Samsung
SoC tree.
If the Thermal maintainers prefer to take the bindings and the thermal
driver patches via their tree we'll need:
- an immutable branch containing the firmware patches from the Samsung
SoC tree to serve as a base for the thermal driver.
- an immutable branch containing the bindings and the thermal driver
from the thermal tree to serve as a base for the dts and defconfig.
Architecture Overview
=====================
The hardware supports two parallel control paths. For this
implementation, responsibilities are split as follows:
1. Kernel Responsibility:
- maintain direct memory-mapped access to the interrupt pending
(INTPEND) registers to identify thermal events.
- map physical hardware interrupts to logical thermal zones.
- coordinate functional operations through the ACPM IPC protocol.
2. Firmware Responsibility (ACPM):
- handle sensor initialization.
- manage thermal thresholds configuration.
- perform temperature acquisition and expose data via IPC.
Sensor Mapping (One-to-Many)
============================
The SoC contains multiple physical temperature sensors, but the ACPM
firmware abstracts these into logical groups (Clusters) for reporting:
- ACPM Sensor 0 (Big Cluster): Aggregates physical sensors 0, 6, 7, 8, 9.
- ACPM Sensor 1 (Mid Cluster): Aggregates physical sensors 4, 5.
- ACPM Sensor 2 (Little Cluster): Aggregates physical sensors 1, 2.
The driver maps physical interrupt bits back to these logical parents.
When an interrupt fires, the driver checks the bitmask in the INTPEND
registers and updates the corresponding logical thermal zone.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
Changes in v4: address sashiko review:
- thermal driver: avoid mixing mutex cleanup helpers with goto statements
- firmware, tmu:
- remove __packed from union acpm_tmu_msg.
- return ERR_PTR(-ENODEV) for devm_acpm_get_by_phandle when
CONFIG_EXYNOS_ACPM_PROTOCOL is disabled.
- Link to v3: https://lore.kernel.org/r/20260420-acpm-tmu-v3-0-3dc8e93f0b26@linaro.org
Changes in v3:
- thermal driver: use .set_trips() instead of .set_trip_point()
- new cleaning/prerequisite patches for firmware/acpm:
- firmware: samsung: acpm: Make acpm_ops const and access via pointer
- firmware: samsung: acpm: Drop redundant _ops suffix in acpm_ops members
- firmware: samsung: acpm: Consolidate transfer initialization helper
- firmware: acpm: TMU helpers - check return value from the firmware
- overall change: emphasize that the ACPM TMU hibrid approach applies to
other Samsung SoCs as well (Exynos850, AutoV920).
- dts: drop active trip points, update trip point values
- collect R-b tags
- Link to v2: https://lore.kernel.org/r/20260119-acpm-tmu-v2-0-e02a834f04c6@linaro.org
Changes in v2:
- architecture: switch from a syscon/MFD approach to a thermal-sensor
node with a phandle to the ACPM interface
- bindings: address Krzysztof's feedback, drop redundencies,
interrupts description.
- firmware: introduce devm_acpm_get_by_phandle() to standardize IPC
handle acquisition.
- thermal driver: drop compatible's data and use the static data from
the driver directly.
- defconfig, make EXYNOS_ACPM_THERMAL a module
- Link to v1: https://lore.kernel.org/r/20260114-acpm-tmu-v1-0-cfe56d93e90f@linaro.org
---
Tudor Ambarus (11):
dt-bindings: thermal: Add Google GS101 TMU
firmware: samsung: acpm: Consolidate transfer initialization helper
firmware: samsung: acpm: Annotate rx_data->cmd with __counted_by_ptr
firmware: samsung: acpm: Drop redundant _ops suffix in acpm_ops members
firmware: samsung: acpm: Make acpm_ops const and access via pointer
firmware: samsung: acpm: Add TMU protocol support
firmware: samsung: acpm: Add devm_acpm_get_by_phandle helper
thermal: samsung: Add Exynos ACPM TMU driver GS101
MAINTAINERS: Add entry for Samsung Exynos ACPM thermal driver
arm64: dts: exynos: gs101: Add thermal management unit
arm64: defconfig: enable Exynos ACPM thermal support
.../bindings/thermal/google,gs101-tmu-top.yaml | 68 +++
MAINTAINERS | 8 +
arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi | 136 +++++
arch/arm64/boot/dts/exynos/google/gs101.dtsi | 18 +
arch/arm64/configs/defconfig | 1 +
drivers/clk/samsung/clk-acpm.c | 8 +-
drivers/firmware/samsung/Makefile | 1 +
drivers/firmware/samsung/exynos-acpm-dvfs.c | 20 +-
drivers/firmware/samsung/exynos-acpm-pmic.c | 20 +-
drivers/firmware/samsung/exynos-acpm-tmu.c | 240 +++++++++
drivers/firmware/samsung/exynos-acpm-tmu.h | 28 ++
drivers/firmware/samsung/exynos-acpm.c | 113 +++--
drivers/firmware/samsung/exynos-acpm.h | 2 +
drivers/mfd/sec-acpm.c | 6 +-
drivers/thermal/samsung/Kconfig | 17 +
drivers/thermal/samsung/Makefile | 2 +
drivers/thermal/samsung/acpm-tmu.c | 547 +++++++++++++++++++++
.../linux/firmware/samsung/exynos-acpm-protocol.h | 32 +-
18 files changed, 1195 insertions(+), 72 deletions(-)
---
base-commit: 2e68039281932e6dc37718a1ea7cbb8e2cda42e6
change-id: 20260113-acpm-tmu-27e21f0e2c3b
prerequisite-change-id: 20260423-acpm-fixes-sashiko-reports-ae28b6ed5581:v1
prerequisite-patch-id: 18d89d0e2bc0efe2cb366746ac4db36f4682f061
prerequisite-patch-id: eb4f90add371877a1930c442c5464c4da7242889
prerequisite-patch-id: 021cd1ee6d2b93f554dd5098cd1158977294dc41
prerequisite-patch-id: b5da16b5c6d6731ea519ed68302fd52ce57c7ffa
Best regards,
--
Tudor Ambarus <tudor.ambarus@linaro.org>
^ permalink raw reply
* [PATCH v4 04/11] firmware: samsung: acpm: Drop redundant _ops suffix in acpm_ops members
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Rename the `dvfs_ops` and `pmic_ops` members of `struct acpm_ops` to
`dvfs` and `pmic` respectively.
Since these members are housed within the `acpm_ops` structure and
utilize the `acpm_*_ops` types, the `_ops` suffix on the variable names
creates unnecessary redundancy (e.g., `handle.ops.dvfs_ops`).
This cleanup removes the stuttering, leading to cleaner consumer code.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/clk/samsung/clk-acpm.c | 8 ++++----
drivers/firmware/samsung/exynos-acpm.c | 4 ++--
drivers/mfd/sec-acpm.c | 6 +++---
include/linux/firmware/samsung/exynos-acpm-protocol.h | 4 ++--
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/drivers/clk/samsung/clk-acpm.c b/drivers/clk/samsung/clk-acpm.c
index d8944160793a..93667777094c 100644
--- a/drivers/clk/samsung/clk-acpm.c
+++ b/drivers/clk/samsung/clk-acpm.c
@@ -68,8 +68,8 @@ static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw,
{
struct acpm_clk *clk = to_acpm_clk(hw);
- return clk->handle->ops.dvfs_ops.get_rate(clk->handle,
- clk->mbox_chan_id, clk->id);
+ return clk->handle->ops.dvfs.get_rate(clk->handle, clk->mbox_chan_id,
+ clk->id);
}
static int acpm_clk_determine_rate(struct clk_hw *hw,
@@ -89,8 +89,8 @@ static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
{
struct acpm_clk *clk = to_acpm_clk(hw);
- return clk->handle->ops.dvfs_ops.set_rate(clk->handle,
- clk->mbox_chan_id, clk->id, rate);
+ return clk->handle->ops.dvfs.set_rate(clk->handle, clk->mbox_chan_id,
+ clk->id, rate);
}
static const struct clk_ops acpm_clk_ops = {
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index e5c85d769d0a..a2cd54ee4589 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -627,8 +627,8 @@ static int acpm_channels_init(struct acpm_info *acpm)
*/
static void acpm_setup_ops(struct acpm_info *acpm)
{
- struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs_ops;
- struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic_ops;
+ struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs;
+ struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic;
dvfs_ops->set_rate = acpm_dvfs_set_rate;
dvfs_ops->get_rate = acpm_dvfs_get_rate;
diff --git a/drivers/mfd/sec-acpm.c b/drivers/mfd/sec-acpm.c
index 0e23b9d9f7ee..9e15b260b8df 100644
--- a/drivers/mfd/sec-acpm.c
+++ b/drivers/mfd/sec-acpm.c
@@ -391,7 +391,7 @@ static int sec_pmic_acpm_bus_write(void *context, const void *data,
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
size_t val_count = count - BITS_TO_BYTES(ACPM_ADDR_BITS);
const u8 *d = data;
const u8 *vals = &d[BITS_TO_BYTES(ACPM_ADDR_BITS)];
@@ -411,7 +411,7 @@ static int sec_pmic_acpm_bus_read(void *context, const void *reg_buf, size_t reg
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
const u8 *r = reg_buf;
u8 reg;
@@ -430,7 +430,7 @@ static int sec_pmic_acpm_bus_reg_update_bits(void *context, unsigned int reg, un
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
return pmic_ops->update_reg(acpm, ctx->shared->acpm_chan_id, ctx->type, reg & 0xff,
ctx->shared->speedy_channel, val, mask);
diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
index d4db2796a6fb..b206efa62be6 100644
--- a/include/linux/firmware/samsung/exynos-acpm-protocol.h
+++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
@@ -36,8 +36,8 @@ struct acpm_pmic_ops {
};
struct acpm_ops {
- struct acpm_dvfs_ops dvfs_ops;
- struct acpm_pmic_ops pmic_ops;
+ struct acpm_dvfs_ops dvfs;
+ struct acpm_pmic_ops pmic;
};
/**
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 01/11] dt-bindings: thermal: Add Google GS101 TMU
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Document the Thermal Management Unit (TMU) found on the Google GS101 SoC.
The GS101 TMU utilizes a hybrid control model shared between the
Application Processor (AP) and the ACPM (Alive Clock and Power Manager)
firmware. This hybrid ACPM TMU architecture is also present on other
Samsung Exynos SoCs (e.g., AutoV920, Exynos850).
While the TMU is a standard memory-mapped IP block, on this platform
the AP's direct register access is restricted to the interrupt pending
(INTPEND) registers for event identification. High-level functional
tasks, such as sensor initialization, threshold programming, and
temperature reads, are delegated to the ACPM firmware.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
.../bindings/thermal/google,gs101-tmu-top.yaml | 68 ++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml b/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml
new file mode 100644
index 000000000000..d0eb2393d581
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/thermal/google,gs101-tmu-top.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Samsung Exynos ACPM Thermal Management Unit (TMU)
+
+maintainers:
+ - Tudor Ambarus <tudor.ambarus@linaro.org>
+
+description:
+ The Samsung Exynos ACPM TMU is a thermal sensor block found on Exynos
+ based platforms (such as Google GS101 and Exynos850). It supports
+ both direct register-level access and firmware-mediated management
+ via the ACPM (Alive Clock and Power Manager) firmware.
+
+ On these platforms, the hardware is managed in a hybrid fashion. The
+ Application Processor (AP) maintains direct memory-mapped access
+ exclusively to the interrupt pending registers to identify thermal
+ events. All other functional aspects - including sensor
+ initialization, threshold configuration, and temperature acquisition
+ - are handled by the ACPM firmware. The AP coordinates these
+ operations through the ACPM IPC protocol.
+
+properties:
+ compatible:
+ const: google,gs101-tmu-top
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: APB peripheral clock (PCLK) for TMU register access.
+
+ interrupts:
+ maxItems: 1
+
+ "#thermal-sensor-cells":
+ const: 1
+
+ samsung,acpm-ipc:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: Phandle to the ACPM IPC node.
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - interrupts
+ - "#thermal-sensor-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/google,gs101.h>
+
+ thermal-sensor@100a0000 {
+ compatible = "google,gs101-tmu-top";
+ reg = <0x100a0000 0x800>;
+ clocks = <&cmu_misc CLK_GOUT_MISC_TMU_TOP_PCLK>;
+ interrupts = <GIC_SPI 769 IRQ_TYPE_LEVEL_HIGH 0>;
+ #thermal-sensor-cells = <1>;
+ samsung,acpm-ipc = <&acpm_ipc>;
+ };
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 02/11] firmware: samsung: acpm: Consolidate transfer initialization helper
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Both the DVFS and PMIC ACPM sub-drivers implement their own identical
local helper functions (acpm_dvfs_set_xfer and acpm_pmic_set_xfer) to
initialize the acpm_xfer structure before sending an IPC message.
Move this logic into a single centralized helper, acpm_set_xfer(),
in the core ACPM driver to reduce boilerplate and code duplication.
In addition to cleaning up the DVFS and PMIC implementations, this
centralized method will also be utilized by the upcoming Exynos ACPM
Thermal Management Unit (TMU) driver.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm-dvfs.c | 20 ++------------------
drivers/firmware/samsung/exynos-acpm-pmic.c | 20 +++++---------------
drivers/firmware/samsung/exynos-acpm.c | 26 ++++++++++++++++++++++++++
drivers/firmware/samsung/exynos-acpm.h | 2 ++
4 files changed, 35 insertions(+), 33 deletions(-)
diff --git a/drivers/firmware/samsung/exynos-acpm-dvfs.c b/drivers/firmware/samsung/exynos-acpm-dvfs.c
index fdea7aa24ca0..7266312ef5a6 100644
--- a/drivers/firmware/samsung/exynos-acpm-dvfs.c
+++ b/drivers/firmware/samsung/exynos-acpm-dvfs.c
@@ -21,22 +21,6 @@
#define ACPM_DVFS_FREQ_REQ 0
#define ACPM_DVFS_FREQ_GET 1
-static void acpm_dvfs_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
- unsigned int acpm_chan_id, bool response)
-{
- xfer->acpm_chan_id = acpm_chan_id;
- xfer->txcnt = cmdlen;
- xfer->txd = cmd;
-
- if (response) {
- xfer->rxcnt = cmdlen;
- xfer->rxd = cmd;
- } else {
- xfer->rxcnt = 0;
- xfer->rxd = NULL;
- }
-}
-
static void acpm_dvfs_init_set_rate_cmd(u32 cmd[4], unsigned int clk_id,
unsigned long rate)
{
@@ -54,7 +38,7 @@ int acpm_dvfs_set_rate(struct acpm_handle *handle,
u32 cmd[4];
acpm_dvfs_init_set_rate_cmd(cmd, clk_id, rate);
- acpm_dvfs_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, false);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, false);
return acpm_do_xfer(handle, &xfer);
}
@@ -74,7 +58,7 @@ unsigned long acpm_dvfs_get_rate(struct acpm_handle *handle,
int ret;
acpm_dvfs_init_get_rate_cmd(cmd, clk_id);
- acpm_dvfs_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
diff --git a/drivers/firmware/samsung/exynos-acpm-pmic.c b/drivers/firmware/samsung/exynos-acpm-pmic.c
index 0c50993cc9a8..f032f2c69685 100644
--- a/drivers/firmware/samsung/exynos-acpm-pmic.c
+++ b/drivers/firmware/samsung/exynos-acpm-pmic.c
@@ -58,16 +58,6 @@ static inline u32 acpm_pmic_get_bulk(u32 data, unsigned int i)
return (data >> (ACPM_PMIC_BULK_SHIFT * i)) & ACPM_PMIC_BULK_MASK;
}
-static void acpm_pmic_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen,
- unsigned int acpm_chan_id)
-{
- xfer->txd = cmd;
- xfer->rxd = cmd;
- xfer->txcnt = cmdlen;
- xfer->rxcnt = cmdlen;
- xfer->acpm_chan_id = acpm_chan_id;
-}
-
static void acpm_pmic_init_read_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan)
{
cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) |
@@ -86,7 +76,7 @@ int acpm_pmic_read_reg(struct acpm_handle *handle,
int ret;
acpm_pmic_init_read_cmd(cmd, type, reg, chan);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
@@ -119,7 +109,7 @@ int acpm_pmic_bulk_read(struct acpm_handle *handle,
return -EINVAL;
acpm_pmic_init_bulk_read_cmd(cmd, type, reg, chan, count);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
@@ -159,7 +149,7 @@ int acpm_pmic_write_reg(struct acpm_handle *handle,
int ret;
acpm_pmic_init_write_cmd(cmd, type, reg, chan, value);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
@@ -199,7 +189,7 @@ int acpm_pmic_bulk_write(struct acpm_handle *handle,
return -EINVAL;
acpm_pmic_init_bulk_write_cmd(cmd, type, reg, chan, count, buf);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
@@ -229,7 +219,7 @@ int acpm_pmic_update_reg(struct acpm_handle *handle,
int ret;
acpm_pmic_init_update_cmd(cmd, type, reg, chan, value, mask);
- acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id);
+ acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true);
ret = acpm_do_xfer(handle, &xfer);
if (ret)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 896fbdc2700e..c4aca61a63e4 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -470,6 +470,32 @@ int acpm_do_xfer(struct acpm_handle *handle, const struct acpm_xfer *xfer)
return acpm_wait_for_message_response(achan, xfer);
}
+/**
+ * acpm_set_xfer() - initialize an ACPM IPC transfer structure.
+ * @xfer: pointer to the ACPM transfer structure that is being initialized.
+ * @cmd: pointer to the buffer containing the command to be transmitted
+ * to the ACPM firmware.
+ * @cmdcnt: length of the command in 32-bit words.
+ * @acpm_chan_id: mailbox channel identifier.
+ * @response: boolean flag indicating whether the kernel expects the ACPM
+ * firmware to send a reply to this specific command.
+ */
+static void acpm_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdcnt,
+ unsigned int acpm_chan_id, bool response)
+{
+ xfer->acpm_chan_id = acpm_chan_id;
+ xfer->txcnt = cmdcnt;
+ xfer->txd = cmd;
+
+ if (response) {
+ xfer->rxcnt = cmdcnt;
+ xfer->rxd = cmd;
+ } else {
+ xfer->rxcnt = 0;
+ xfer->rxd = NULL;
+ }
+}
+
/**
* acpm_chan_shmem_get_params() - get channel parameters and addresses of the
* TX/RX queues.
diff --git a/drivers/firmware/samsung/exynos-acpm.h b/drivers/firmware/samsung/exynos-acpm.h
index 5df8354dc96c..708f6b0102ac 100644
--- a/drivers/firmware/samsung/exynos-acpm.h
+++ b/drivers/firmware/samsung/exynos-acpm.h
@@ -17,6 +17,8 @@ struct acpm_xfer {
struct acpm_handle;
+void acpm_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdcnt,
+ unsigned int acpm_chan_id, bool response);
int acpm_do_xfer(struct acpm_handle *handle,
const struct acpm_xfer *xfer);
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 05/11] firmware: samsung: acpm: Make acpm_ops const and access via pointer
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Replace the embedded `struct acpm_ops` inside `struct acpm_handle` with
a pointer to a `const struct acpm_ops`.
Previously, the operations structure was embedded directly within the
handle and populated dynamically at runtime via `acpm_setup_ops()`.
This resulted in mutable function pointers and unnecessary per-instance
memory overhead.
By defining `exynos_acpm_driver_ops` statically as a `const` structure,
the function pointers are now safely housed in the read-only `.rodata`
section. This improves security by preventing function pointer
overwrites, saves memory, and slightly reduces initialization overhead
in `acpm_probe()`.
Consequently, update all consumer drivers (clk, mfd) to access the
operations via the new pointer indirection (`->ops->`). Finally, fix
the previously empty kernel-doc description for the ops member to
reflect its new pointer nature.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/clk/samsung/clk-acpm.c | 8 ++---
drivers/firmware/samsung/exynos-acpm.c | 36 ++++++++++------------
drivers/mfd/sec-acpm.c | 6 ++--
.../linux/firmware/samsung/exynos-acpm-protocol.h | 4 +--
4 files changed, 25 insertions(+), 29 deletions(-)
diff --git a/drivers/clk/samsung/clk-acpm.c b/drivers/clk/samsung/clk-acpm.c
index 93667777094c..953ca8d5720a 100644
--- a/drivers/clk/samsung/clk-acpm.c
+++ b/drivers/clk/samsung/clk-acpm.c
@@ -68,8 +68,8 @@ static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw,
{
struct acpm_clk *clk = to_acpm_clk(hw);
- return clk->handle->ops.dvfs.get_rate(clk->handle, clk->mbox_chan_id,
- clk->id);
+ return clk->handle->ops->dvfs.get_rate(clk->handle, clk->mbox_chan_id,
+ clk->id);
}
static int acpm_clk_determine_rate(struct clk_hw *hw,
@@ -89,8 +89,8 @@ static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
{
struct acpm_clk *clk = to_acpm_clk(hw);
- return clk->handle->ops.dvfs.set_rate(clk->handle, clk->mbox_chan_id,
- clk->id, rate);
+ return clk->handle->ops->dvfs.set_rate(clk->handle, clk->mbox_chan_id,
+ clk->id, rate);
}
static const struct clk_ops acpm_clk_ops = {
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index a2cd54ee4589..38f40fb67ea7 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -621,30 +621,26 @@ static int acpm_channels_init(struct acpm_info *acpm)
return 0;
}
-/**
- * acpm_setup_ops() - setup the operations structures.
- * @acpm: pointer to the driver data.
- */
-static void acpm_setup_ops(struct acpm_info *acpm)
-{
- struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs;
- struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic;
-
- dvfs_ops->set_rate = acpm_dvfs_set_rate;
- dvfs_ops->get_rate = acpm_dvfs_get_rate;
-
- pmic_ops->read_reg = acpm_pmic_read_reg;
- pmic_ops->bulk_read = acpm_pmic_bulk_read;
- pmic_ops->write_reg = acpm_pmic_write_reg;
- pmic_ops->bulk_write = acpm_pmic_bulk_write;
- pmic_ops->update_reg = acpm_pmic_update_reg;
-}
-
static void acpm_clk_pdev_unregister(void *data)
{
platform_device_unregister(data);
}
+static const struct acpm_ops exynos_acpm_driver_ops = {
+ .dvfs = {
+ .set_rate = acpm_dvfs_set_rate,
+ .get_rate = acpm_dvfs_get_rate,
+ },
+
+ .pmic = {
+ .read_reg = acpm_pmic_read_reg,
+ .bulk_read = acpm_pmic_bulk_read,
+ .write_reg = acpm_pmic_write_reg,
+ .bulk_write = acpm_pmic_bulk_write,
+ .update_reg = acpm_pmic_update_reg,
+ },
+};
+
static int acpm_probe(struct platform_device *pdev)
{
const struct acpm_match_data *match_data;
@@ -689,7 +685,7 @@ static int acpm_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "Failed to add mbox free action.\n");
- acpm_setup_ops(acpm);
+ acpm->handle.ops = &exynos_acpm_driver_ops;
platform_set_drvdata(pdev, acpm);
diff --git a/drivers/mfd/sec-acpm.c b/drivers/mfd/sec-acpm.c
index 9e15b260b8df..3397d13d3b7f 100644
--- a/drivers/mfd/sec-acpm.c
+++ b/drivers/mfd/sec-acpm.c
@@ -391,7 +391,7 @@ static int sec_pmic_acpm_bus_write(void *context, const void *data,
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic;
size_t val_count = count - BITS_TO_BYTES(ACPM_ADDR_BITS);
const u8 *d = data;
const u8 *vals = &d[BITS_TO_BYTES(ACPM_ADDR_BITS)];
@@ -411,7 +411,7 @@ static int sec_pmic_acpm_bus_read(void *context, const void *reg_buf, size_t reg
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic;
const u8 *r = reg_buf;
u8 reg;
@@ -430,7 +430,7 @@ static int sec_pmic_acpm_bus_reg_update_bits(void *context, unsigned int reg, un
{
struct sec_pmic_acpm_bus_context *ctx = context;
struct acpm_handle *acpm = ctx->shared->acpm;
- const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic;
+ const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic;
return pmic_ops->update_reg(acpm, ctx->shared->acpm_chan_id, ctx->type, reg & 0xff,
ctx->shared->speedy_channel, val, mask);
diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
index b206efa62be6..fbf1829b33db 100644
--- a/include/linux/firmware/samsung/exynos-acpm-protocol.h
+++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
@@ -42,10 +42,10 @@ struct acpm_ops {
/**
* struct acpm_handle - Reference to an initialized protocol instance
- * @ops:
+ * @ops: pointer to the constant ACPM protocol operations.
*/
struct acpm_handle {
- struct acpm_ops ops;
+ const struct acpm_ops *ops;
};
struct device;
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 03/11] firmware: samsung: acpm: Annotate rx_data->cmd with __counted_by_ptr
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Rename the `n_cmd` member of `struct acpm_rx_data` to `cmdcnt` to
maintain consistent nomenclature across the driver (aligning with
`txcnt`, `rxcnt`, and transfer helpers).
With the member renamed, annotate the dynamically allocated `cmd`
pointer with the `__counted_by_ptr(cmdcnt)` macro to improve runtime
bounds checking.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index c4aca61a63e4..e5c85d769d0a 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -103,12 +103,12 @@ struct acpm_queue {
* struct acpm_rx_data - RX queue data.
*
* @cmd: pointer to where the data shall be saved.
- * @n_cmd: number of 32-bit commands.
+ * @cmdcnt: allocated capacity of the @cmd buffer in 32-bit words.
* @rxcnt: expected length of the response in 32-bit words.
*/
struct acpm_rx_data {
- u32 *cmd;
- size_t n_cmd;
+ u32 *cmd __counted_by_ptr(cmdcnt);
+ size_t cmdcnt;
size_t rxcnt;
};
@@ -384,7 +384,7 @@ static void acpm_prepare_xfer(struct acpm_chan *achan,
/* Clear data for upcoming responses */
rx_data = &achan->rx_data[achan->seqnum - 1];
- memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd);
+ memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->cmdcnt);
/* zero means no response expected */
rx_data->rxcnt = xfer->rxcnt;
@@ -537,19 +537,19 @@ static int acpm_achan_alloc_cmds(struct acpm_chan *achan)
{
struct device *dev = achan->acpm->dev;
struct acpm_rx_data *rx_data;
- size_t cmd_size, n_cmd;
+ size_t cmd_size, cmdcnt;
int i;
if (achan->mlen == 0)
return 0;
cmd_size = sizeof(*(achan->rx_data[0].cmd));
- n_cmd = DIV_ROUND_UP_ULL(achan->mlen, cmd_size);
+ cmdcnt = DIV_ROUND_UP_ULL(achan->mlen, cmd_size);
for (i = 0; i < ACPM_SEQNUM_MAX; i++) {
rx_data = &achan->rx_data[i];
- rx_data->n_cmd = n_cmd;
- rx_data->cmd = devm_kcalloc(dev, n_cmd, cmd_size, GFP_KERNEL);
+ rx_data->cmdcnt = cmdcnt;
+ rx_data->cmd = devm_kcalloc(dev, cmdcnt, cmd_size, GFP_KERNEL);
if (!rx_data->cmd)
return -ENOMEM;
}
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 06/11] firmware: samsung: acpm: Add TMU protocol support
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
The Thermal Management Unit (TMU) on the Google GS101 SoC is managed
through a hybrid model shared between the kernel and the Alive Clock
and Power Manager (ACPM) firmware.
Add the protocol helpers required to communicate with the ACPM for
thermal operations, including initialization, threshold configuration,
temperature reading, and system suspend/resume handshakes.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
drivers/firmware/samsung/Makefile | 1 +
drivers/firmware/samsung/exynos-acpm-tmu.c | 240 +++++++++++++++++++++
drivers/firmware/samsung/exynos-acpm-tmu.h | 28 +++
drivers/firmware/samsung/exynos-acpm.c | 12 ++
.../linux/firmware/samsung/exynos-acpm-protocol.h | 18 ++
5 files changed, 299 insertions(+)
diff --git a/drivers/firmware/samsung/Makefile b/drivers/firmware/samsung/Makefile
index 80d4f89b33a9..5a6f72bececf 100644
--- a/drivers/firmware/samsung/Makefile
+++ b/drivers/firmware/samsung/Makefile
@@ -3,4 +3,5 @@
acpm-protocol-objs := exynos-acpm.o
acpm-protocol-objs += exynos-acpm-pmic.o
acpm-protocol-objs += exynos-acpm-dvfs.o
+acpm-protocol-objs += exynos-acpm-tmu.o
obj-$(CONFIG_EXYNOS_ACPM_PROTOCOL) += acpm-protocol.o
diff --git a/drivers/firmware/samsung/exynos-acpm-tmu.c b/drivers/firmware/samsung/exynos-acpm-tmu.c
new file mode 100644
index 000000000000..7d9b75be032c
--- /dev/null
+++ b/drivers/firmware/samsung/exynos-acpm-tmu.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2020 Samsung Electronics Co., Ltd.
+ * Copyright 2020 Google LLC.
+ * Copyright 2026 Linaro Ltd.
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/firmware/samsung/exynos-acpm-protocol.h>
+#include <linux/ktime.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include "exynos-acpm.h"
+#include "exynos-acpm-tmu.h"
+
+/* IPC Request Types */
+#define ACPM_TMU_INIT 0x01
+#define ACPM_TMU_READ_TEMP 0x02
+#define ACPM_TMU_SUSPEND 0x04
+#define ACPM_TMU_RESUME 0x10
+#define ACPM_TMU_THRESHOLD 0x11
+#define ACPM_TMU_INTEN 0x12
+#define ACPM_TMU_CONTROL 0x13
+#define ACPM_TMU_IRQ_CLEAR 0x14
+
+#define ACPM_TMU_TX_DATA_LEN 8
+#define ACPM_TMU_RX_DATA_LEN 7
+
+struct acpm_tmu_tx {
+ u16 ctx;
+ u16 fw_use;
+ u8 type;
+ u8 rsvd0;
+ u8 tzid;
+ u8 rsvd1;
+ u8 data[ACPM_TMU_TX_DATA_LEN];
+} __packed;
+
+struct acpm_tmu_rx {
+ u16 ctx;
+ u16 fw_use;
+ u8 type;
+ s8 ret;
+ u8 tzid;
+ s8 temp;
+ u8 rsvd;
+ u8 data[ACPM_TMU_RX_DATA_LEN];
+} __packed;
+
+union acpm_tmu_msg {
+ u32 data[4];
+ struct acpm_tmu_tx tx;
+ struct acpm_tmu_rx rx;
+};
+
+static int acpm_tmu_to_linux_err(s8 fw_err)
+{
+ /*
+ * ACPM_TMU_INIT uses BIT(0) and BIT(1) of msg.rx.ret to flag APM
+ * capabilities. Treat zero and all positive values as success.
+ */
+ if (fw_err >= 0)
+ return 0;
+
+ if (fw_err == -1)
+ return -EACCES;
+
+ return -EIO;
+}
+
+int acpm_tmu_init(struct acpm_handle *handle, unsigned int acpm_chan_id)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_INIT;
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_read_temp(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, int *temp)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_READ_TEMP;
+ msg.tx.tzid = tz;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ ret = acpm_tmu_to_linux_err(msg.rx.ret);
+ if (ret)
+ return ret;
+
+ *temp = msg.rx.temp;
+
+ return 0;
+}
+
+int acpm_tmu_set_threshold(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz,
+ const u8 temperature[8], size_t tlen)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int i, ret;
+
+ if (tlen > ACPM_TMU_TX_DATA_LEN)
+ return -EINVAL;
+
+ msg.tx.type = ACPM_TMU_THRESHOLD;
+ msg.tx.tzid = tz;
+
+ for (i = 0; i < tlen; i++)
+ msg.tx.data[i] = temperature[i];
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_set_interrupt_enable(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz, u8 inten)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_INTEN;
+ msg.tx.tzid = tz;
+ msg.tx.data[0] = inten;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_tz_control(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, bool enable)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_CONTROL;
+ msg.tx.tzid = tz;
+ msg.tx.data[0] = enable ? 1 : 0;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_clear_tz_irq(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_IRQ_CLEAR;
+ msg.tx.tzid = tz;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_suspend(struct acpm_handle *handle, unsigned int acpm_chan_id)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_SUSPEND;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
+
+int acpm_tmu_resume(struct acpm_handle *handle, unsigned int acpm_chan_id)
+{
+ union acpm_tmu_msg msg = {0};
+ struct acpm_xfer xfer;
+ int ret;
+
+ msg.tx.type = ACPM_TMU_RESUME;
+
+ acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id,
+ true);
+
+ ret = acpm_do_xfer(handle, &xfer);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_to_linux_err(msg.rx.ret);
+}
diff --git a/drivers/firmware/samsung/exynos-acpm-tmu.h b/drivers/firmware/samsung/exynos-acpm-tmu.h
new file mode 100644
index 000000000000..8b89f29fda67
--- /dev/null
+++ b/drivers/firmware/samsung/exynos-acpm-tmu.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2020 Samsung Electronics Co., Ltd.
+ * Copyright 2020 Google LLC.
+ * Copyright 2026 Linaro Ltd.
+ */
+#ifndef __EXYNOS_ACPM_TMU_H__
+#define __EXYNOS_ACPM_TMU_H__
+
+#include <linux/types.h>
+
+struct acpm_handle;
+
+int acpm_tmu_init(struct acpm_handle *handle, unsigned int acpm_chan_id);
+int acpm_tmu_read_temp(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, int *temp);
+int acpm_tmu_set_threshold(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz,
+ const u8 temperature[8], size_t tlen);
+int acpm_tmu_set_interrupt_enable(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz, u8 inten);
+int acpm_tmu_tz_control(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, bool enable);
+int acpm_tmu_clear_tz_irq(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz);
+int acpm_tmu_suspend(struct acpm_handle *handle, unsigned int acpm_chan_id);
+int acpm_tmu_resume(struct acpm_handle *handle, unsigned int acpm_chan_id);
+#endif /* __EXYNOS_ACPM_TMU_H__ */
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 38f40fb67ea7..655b80fc635f 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -31,6 +31,7 @@
#include "exynos-acpm.h"
#include "exynos-acpm-dvfs.h"
#include "exynos-acpm-pmic.h"
+#include "exynos-acpm-tmu.h"
#define ACPM_PROTOCOL_SEQNUM GENMASK(21, 16)
@@ -639,6 +640,17 @@ static const struct acpm_ops exynos_acpm_driver_ops = {
.bulk_write = acpm_pmic_bulk_write,
.update_reg = acpm_pmic_update_reg,
},
+
+ .tmu = {
+ .init = acpm_tmu_init,
+ .read_temp = acpm_tmu_read_temp,
+ .set_threshold = acpm_tmu_set_threshold,
+ .set_interrupt_enable = acpm_tmu_set_interrupt_enable,
+ .tz_control = acpm_tmu_tz_control,
+ .clear_tz_irq = acpm_tmu_clear_tz_irq,
+ .suspend = acpm_tmu_suspend,
+ .resume = acpm_tmu_resume,
+ },
};
static int acpm_probe(struct platform_device *pdev)
diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
index fbf1829b33db..08d9f5c95701 100644
--- a/include/linux/firmware/samsung/exynos-acpm-protocol.h
+++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
@@ -35,9 +35,27 @@ struct acpm_pmic_ops {
u8 type, u8 reg, u8 chan, u8 value, u8 mask);
};
+struct acpm_tmu_ops {
+ int (*init)(struct acpm_handle *handle, unsigned int acpm_chan_id);
+ int (*read_temp)(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, int *temp);
+ int (*set_threshold)(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz,
+ const u8 temperature[8], size_t tlen);
+ int (*set_interrupt_enable)(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz, u8 inten);
+ int (*tz_control)(struct acpm_handle *handle, unsigned int acpm_chan_id,
+ u8 tz, bool enable);
+ int (*clear_tz_irq)(struct acpm_handle *handle,
+ unsigned int acpm_chan_id, u8 tz);
+ int (*suspend)(struct acpm_handle *handle, unsigned int acpm_chan_id);
+ int (*resume)(struct acpm_handle *handle, unsigned int acpm_chan_id);
+};
+
struct acpm_ops {
struct acpm_dvfs_ops dvfs;
struct acpm_pmic_ops pmic;
+ struct acpm_tmu_ops tmu;
};
/**
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 09/11] MAINTAINERS: Add entry for Samsung Exynos ACPM thermal driver
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Add a MAINTAINERS entry for the Samsung Exynos ACPM thermal driver.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c9b7b6f9828e..759c05bd0004 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23693,6 +23693,14 @@ F: drivers/clk/samsung/clk-acpm.c
F: drivers/firmware/samsung/exynos-acpm*
F: include/linux/firmware/samsung/exynos-acpm-protocol.h
+SAMSUNG EXYNOS ACPM THERMAL DRIVER
+M: Tudor Ambarus <tudor.ambarus@linaro.org>
+L: linux-kernel@vger.kernel.org
+L: linux-samsung-soc@vger.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml
+F: drivers/thermal/samsung/acpm-tmu.c
+
SAMSUNG EXYNOS MAILBOX DRIVER
M: Tudor Ambarus <tudor.ambarus@linaro.org>
L: linux-kernel@vger.kernel.org
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 10/11] arm64: dts: exynos: gs101: Add thermal management unit
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Add the Thermal Management Unit (TMU) support for the Google GS101 SoC.
Describe the TMU using a consolidated SoC node that includes memory
resources for interrupt identification and a phandle to the ACPM IPC
interface for functional control.
Define thermal zones for the little, mid, and big CPU clusters, including
associated trip points and cooling-device maps to enable thermal
mitigation.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi | 136 +++++++++++++++++++++++
arch/arm64/boot/dts/exynos/google/gs101.dtsi | 18 +++
2 files changed, 154 insertions(+)
diff --git a/arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi b/arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi
new file mode 100644
index 000000000000..b27d1a539ec2
--- /dev/null
+++ b/arch/arm64/boot/dts/exynos/google/gs101-tmu.dtsi
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Google GS101 TMU configurations device tree source
+ *
+ * Copyright 2020 Samsung Electronics Co., Ltd.
+ * Copyright 2020 Google LLC.
+ * Copyright 2026 Linaro Ltd.
+ */
+
+#include <dt-bindings/thermal/thermal.h>
+
+/ {
+ thermal-zones {
+ cpucl2-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tmu_top 0>;
+
+ trips {
+ big_switch_on: big-switch-on {
+ temperature = <80000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+
+ big_mitigate: big-mitigate {
+ temperature = <90000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ big_hot: big-hot {
+ temperature = <100000>;
+ hysteresis = <5000>;
+ type = "hot";
+ };
+
+ big_critical: big-critical {
+ temperature = <105000>;
+ hysteresis = <2000>;
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&big_mitigate>;
+ cooling-device = <&cpu6 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu7 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ cpucl1-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tmu_top 1>;
+
+ trips {
+ mid_switch_on: mid-switch-on {
+ temperature = <80000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+
+ mid_mitigate: mid-mitigate {
+ temperature = <90000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ mid_hot: mid-hot {
+ temperature = <100000>;
+ hysteresis = <5000>;
+ type = "hot";
+ };
+
+ mid_critical: mid-critical {
+ temperature = <105000>;
+ hysteresis = <2000>;
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&mid_mitigate>;
+ cooling-device = <&cpu4 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu5 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ cpucl0-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tmu_top 2>;
+
+ trips {
+ little_switch_on: little-switch-on {
+ temperature = <80000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+
+ little_mitigate: little-mitigate {
+ temperature = <90000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ little_hot: little-hot {
+ temperature = <100000>;
+ hysteresis = <5000>;
+ type = "hot";
+ };
+
+ little_critical: little-critical {
+ temperature = <105000>;
+ hysteresis = <2000>;
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map0 {
+ trip = <&little_mitigate>;
+ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+ };
+};
diff --git a/arch/arm64/boot/dts/exynos/google/gs101.dtsi b/arch/arm64/boot/dts/exynos/google/gs101.dtsi
index d085f9fb0f62..4b8c7edaddb6 100644
--- a/arch/arm64/boot/dts/exynos/google/gs101.dtsi
+++ b/arch/arm64/boot/dts/exynos/google/gs101.dtsi
@@ -74,6 +74,7 @@ cpu0: cpu@0 {
compatible = "arm,cortex-a55";
reg = <0x0000>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL0>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&ananke_cpu_sleep>;
capacity-dmips-mhz = <250>;
@@ -86,6 +87,7 @@ cpu1: cpu@100 {
compatible = "arm,cortex-a55";
reg = <0x0100>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL0>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&ananke_cpu_sleep>;
capacity-dmips-mhz = <250>;
@@ -98,6 +100,7 @@ cpu2: cpu@200 {
compatible = "arm,cortex-a55";
reg = <0x0200>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL0>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&ananke_cpu_sleep>;
capacity-dmips-mhz = <250>;
@@ -110,6 +113,7 @@ cpu3: cpu@300 {
compatible = "arm,cortex-a55";
reg = <0x0300>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL0>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&ananke_cpu_sleep>;
capacity-dmips-mhz = <250>;
@@ -122,6 +126,7 @@ cpu4: cpu@400 {
compatible = "arm,cortex-a76";
reg = <0x0400>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL1>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&enyo_cpu_sleep>;
capacity-dmips-mhz = <620>;
@@ -134,6 +139,7 @@ cpu5: cpu@500 {
compatible = "arm,cortex-a76";
reg = <0x0500>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL1>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&enyo_cpu_sleep>;
capacity-dmips-mhz = <620>;
@@ -146,6 +152,7 @@ cpu6: cpu@600 {
compatible = "arm,cortex-x1";
reg = <0x0600>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL2>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&hera_cpu_sleep>;
capacity-dmips-mhz = <1024>;
@@ -158,6 +165,7 @@ cpu7: cpu@700 {
compatible = "arm,cortex-x1";
reg = <0x0700>;
clocks = <&acpm_ipc GS101_CLK_ACPM_DVFS_CPUCL2>;
+ #cooling-cells = <2>;
enable-method = "psci";
cpu-idle-states = <&hera_cpu_sleep>;
capacity-dmips-mhz = <1024>;
@@ -639,6 +647,15 @@ watchdog_cl1: watchdog@10070000 {
status = "disabled";
};
+ tmu_top: thermal-sensor@100a0000 {
+ compatible = "google,gs101-tmu-top";
+ reg = <0x100a0000 0x800>;
+ clocks = <&cmu_misc CLK_GOUT_MISC_TMU_TOP_PCLK>;
+ interrupts = <GIC_SPI 769 IRQ_TYPE_LEVEL_HIGH 0>;
+ samsung,acpm-ipc = <&acpm_ipc>;
+ #thermal-sensor-cells = <1>;
+ };
+
trng: rng@10141400 {
compatible = "google,gs101-trng",
"samsung,exynos850-trng";
@@ -1861,3 +1878,4 @@ timer {
};
#include "gs101-pinctrl.dtsi"
+#include "gs101-tmu.dtsi"
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 11/11] arm64: defconfig: enable Exynos ACPM thermal support
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Enable the Exynos ACPM thermal driver (CONFIG_EXYNOS_ACPM_THERMAL)
to allow temperature monitoring and thermal management on Samsung
Exynos SoCs that use the Alive Clock and Power Manager (ACPM)
protocol.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index d905a0777f93..3fe76a4c2633 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -793,6 +793,7 @@ CONFIG_BCM2711_THERMAL=m
CONFIG_BCM2835_THERMAL=m
CONFIG_BRCMSTB_THERMAL=m
CONFIG_EXYNOS_THERMAL=y
+CONFIG_EXYNOS_ACPM_THERMAL=m
CONFIG_TEGRA_SOCTHERM=m
CONFIG_TEGRA_BPMP_THERMAL=m
CONFIG_GENERIC_ADC_THERMAL=m
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 07/11] firmware: samsung: acpm: Add devm_acpm_get_by_phandle helper
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Introduce devm_acpm_get_by_phandle() to standardize how consumer
drivers acquire a handle to the ACPM IPC interface. Enforce the
use of the "samsung,acpm-ipc" property name across the SoC and
simplify the boilerplate code in client drivers.
The first consumer of this helper is the Exynos ACPM Thermal Management
Unit (TMU) driver. The TMU utilizes a hybrid management approach: direct
register access from the Application Processor (AP) is restricted to the
interrupt pending (INTPEND) registers for event identification.
High-level functional tasks, such as sensor initialization, threshold
programming, and temperature reads, are delegated to the ACPM firmware
via this IPC interface.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
drivers/firmware/samsung/exynos-acpm.c | 23 ++++++++++++++++++++++
.../linux/firmware/samsung/exynos-acpm-protocol.h | 6 ++++++
2 files changed, 29 insertions(+)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 655b80fc635f..6dee461f5827 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -812,6 +812,29 @@ struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_acpm_get_by_node);
+/**
+ * devm_acpm_get_by_phandle - Resource managed lookup of the standardized
+ * "samsung,acpm-ipc" handle.
+ * @dev: consumer device
+ *
+ * Returns a pointer to the acpm_handle on success, or an ERR_PTR on failure.
+ */
+struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev)
+{
+ struct acpm_handle *handle;
+ struct device_node *np;
+
+ np = of_parse_phandle(dev->of_node, "samsung,acpm-ipc", 0);
+ if (!np)
+ return ERR_PTR(-ENODEV);
+
+ handle = devm_acpm_get_by_node(dev, np);
+ of_node_put(np);
+
+ return handle;
+}
+EXPORT_SYMBOL_GPL(devm_acpm_get_by_phandle);
+
static const struct acpm_match_data acpm_gs101 = {
.initdata_base = ACPM_GS101_INITDATA_BASE,
.acpm_clk_dev_name = "gs101-acpm-clk",
diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h
index 08d9f5c95701..83cbd425b652 100644
--- a/include/linux/firmware/samsung/exynos-acpm-protocol.h
+++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h
@@ -71,6 +71,7 @@ struct device;
#if IS_ENABLED(CONFIG_EXYNOS_ACPM_PROTOCOL)
struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
struct device_node *np);
+struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev);
#else
static inline struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
@@ -78,6 +79,11 @@ static inline struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
{
return ERR_PTR(-ENODEV);
}
+
+static inline struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev)
+{
+ return ERR_PTR(-ENODEV);
+}
#endif
#endif /* __EXYNOS_ACPM_PROTOCOL_H */
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v4 08/11] thermal: samsung: Add Exynos ACPM TMU driver GS101
From: Tudor Ambarus @ 2026-04-23 15:22 UTC (permalink / raw)
To: Rafael J. Wysocki, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Krzysztof Kozlowski,
Alim Akhtar, Bartlomiej Zolnierkiewicz, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Daniel Lezcano, Sylwester Nawrocki, Chanwoo Choi,
Michael Turquette, Stephen Boyd, Lee Jones
Cc: willmcvicker, jyescas, shin.son, linux-samsung-soc, linux-kernel,
linux-pm, devicetree, linux-arm-kernel, linux-hardening,
linux-clk, Tudor Ambarus, Krzysztof Kozlowski
In-Reply-To: <20260423-acpm-tmu-v4-0-8b59f8548634@linaro.org>
Add driver for the Thermal Management Unit (TMU) managed via the Alive
Clock and Power Manager (ACPM), found on Samsung Exynos SoCs such as
Google GS101 (and Exynos850, autov920, etc.).
The TMU on utilizes a hybrid management model shared between the
Application Processor (AP) and the ACPM firmware. The driver maintains
direct memory-mapped access to the TMU interrupt pending registers to
identify thermal events, while delegating functional tasks - such as
sensor initialization, threshold configuration, and temperature
acquisition - to the ACPM firmware via the ACPM IPC protocol.
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
drivers/thermal/samsung/Kconfig | 17 ++
drivers/thermal/samsung/Makefile | 2 +
drivers/thermal/samsung/acpm-tmu.c | 547 +++++++++++++++++++++++++++++++++++++
3 files changed, 566 insertions(+)
diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig
index f4eff5a41a84..0d3ffbdc66f0 100644
--- a/drivers/thermal/samsung/Kconfig
+++ b/drivers/thermal/samsung/Kconfig
@@ -9,3 +9,20 @@ 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 EXYNOS_ACPM_PROTOCOL || (COMPILE_TEST && !EXYNOS_ACPM_PROTOCOL)
+ help
+ Support for the Thermal Management Unit (TMU) on Samsung Exynos SoCs
+ (such as Google GS101 and Exynos850).
+
+ 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..d4e42b23c0c1
--- /dev/null
+++ b/drivers/thermal/samsung/acpm-tmu.c
@@ -0,0 +1,547 @@
+// 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 int acpm_tmu_control(struct acpm_tmu_priv *priv, bool on)
+{
+ struct device *dev = priv->dev;
+ int i, ret;
+
+ 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)
+ break;
+ }
+
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+}
+
+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, ret;
+
+ if (!sensor->enabled)
+ return -EAGAIN;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
+ scoped_guard(mutex, &sensor->lock) {
+ 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 = sensor->enabled;
+ int ret;
+
+ guard(mutex)(&sensor->lock);
+
+ 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)
+ return ret;
+
+ ret = ops->set_interrupt_enable(handle, mbox_chan_id, acpm_sensor_id,
+ inten);
+ if (ret)
+ return ret;
+
+ /* Restore based on cached state. */
+ if (was_enabled)
+ ret = acpm_tmu_op_tz_control(sensor, true);
+
+ 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;
+ 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;
+
+ thermal_zone_device_update(sensor->tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ 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);
+ }
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ return IRQ_HANDLED;
+}
+
+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");
+
+ ret = devm_request_threaded_irq(dev, priv->irq, NULL,
+ acpm_tmu_thread_fn, IRQF_ONESHOT,
+ dev_name(dev), priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request 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_pm_put;
+ }
+
+ ret = devm_thermal_add_hwmon_sysfs(dev, sensor->tzd);
+ if (ret)
+ dev_warn(dev, "Failed to add hwmon sysfs!\n");
+ }
+
+ ret = acpm_tmu_control(priv, true);
+ if (ret) {
+ ret = dev_err_probe(dev, ret, "Failed to enable TMU\n");
+ goto err_pm_put;
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+
+err_pm_put:
+ 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);
+
+ acpm_tmu_control(priv, 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;
+
+ ret = acpm_tmu_control(priv, false);
+ if (ret)
+ return ret;
+
+ /* APB clock not required for this specific msg */
+ return ops->suspend(handle, priv->mbox_chan_id);
+}
+
+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;
+
+ /* APB clock not required for this specific msg */
+ ret = ops->resume(handle, priv->mbox_chan_id);
+ if (ret)
+ return ret;
+
+ return acpm_tmu_control(priv, true);
+}
+
+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");
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* Re: [PATCH v3 4/8] thermal: amlogic: Add support for secure monitor calibration readout
From: Ronald Claveau @ 2026-04-23 15:29 UTC (permalink / raw)
To: Daniel Lezcano
Cc: linux-pm, linux-amlogic, devicetree, linux-kernel,
linux-arm-kernel, Guillaume La Roque, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl
In-Reply-To: <8a4dcb94-ab88-4559-9027-49308ad02b73@oss.qualcomm.com>
On 4/23/26 5:17 PM, Daniel Lezcano wrote:
> On 4/23/26 17:09, Ronald Claveau wrote:
>> Hi Daniel,
>>
>> Thanks for your feedback.
>>
>> On 4/23/26 12:25 PM, Daniel Lezcano wrote:
>>>
>>> Hi Ronald,
>>>
>
> function1() {
>
>
>>>> + if (pdata->data->use_sm) {
>>>> + struct device_node *sm_np;
>>>> + struct of_phandle_args ph_args;
>>>> +
>>>> + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
>>>> + "amlogic,secure-monitor",
>>>> + 1, 0, &ph_args);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + sm_np = ph_args.np;
>>>> + if (!sm_np) {
>>>> + dev_err(dev,
>>>> + "Failed to parse secure monitor phandle\n");
>>>> + return -ENODEV;
>>>> + }
>>>> +
>>>> + pdata->sm_fw = meson_sm_get(sm_np);
>>>> + of_node_put(sm_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];
>>>> + }
>
> }
>
> function2() {
>
> else {
>>>> + 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);
>>>> + }
>>>> }
>
> }
>
>> Sure, I will do that.
>>
>>>> pdata->tzd = devm_thermal_of_zone_register(&pdev->dev
>>>
>>> The thermal zone is registered before calling
>>> amlogic_thermal_initialize(), thus pdata->trim_info is not initialized.
>>> When a thermal zone is registered the thermal framework reads the
>>> temperature, so it reads an invalid value because:
>>>
>>> devm_thermal_of_zone_register()
>>> -> thermal_of_zone_register()
>>> -> thermal_zone_device_register_with_trips()
>>> -> thermal_zone_device_enable()
>>> -> __thermal_zone_device_update()
>>> -> __thermal_zone_get_temp()
>>> -> amlogic_thermal_get_temp()
>>> -> amlogic_thermal_code_to_millicelsius()
>>> [ Use of uninitialized pdata->trim_info ]
>>>
>>> Right ?
>>>
>>
>> Yes, I will move the initialize before the register.
>>
>>> IIUC, amlogic_thermal_initialize() can be also split and moved the
>>> corresponding blocks to the functions to be created in the comment
>>> above.
>>>
>>
>> The SM and syscon setup will be extracted into two functions.
>> amlogic_thermal_initialize() itself is kept as-is but moved before
>> devm_thermal_of_zone_register().
>> Let me know if you'd prefer a different approach.
>
> In the initialize function the following chunk is added:
>
> + if (pdata->data->use_sm) {
> + return meson_sm_get_thermal_calib(pdata->sm_fw,
> + &pdata->trim_info,
> + pdata->tsensor_id);
> + }
>
> I was suggesting to move it to function1() and the rest of code from
> initialize in function2()
>
> That results in amlogic_thermal_initialize() to be dissolved in
> function1() and function2()
>
Thanks for your answer, that was my second option, I will do that.
--
Best regards,
Ronald
^ permalink raw reply
* [PATCH 0/2] drivers/perf: hisi: Updates for HiSilicon uncore PMUs
From: Yushan Wang @ 2026-04-23 15:29 UTC (permalink / raw)
To: will, mark.rutland, robin.murphy, linux-arm-kernel, linux-kernel
Cc: fanghao11, linuxarm, liuyonglong, prime.zeng, wangzhou1,
wangyushan12
This patchset added support of ITS PMU, and new version of MN PMU.
ITS PMU supports counting number and latency of interrupts by catagory,
and statistics of micro-ops of ITS.
The new version of MN PMU added cycles event, to be used for MN metric
computing.
Yifan Wu (1):
drivers/perf: hisi: Add new function for HiSilicon MN PMU driver
Yushan Wang (1):
drivers/perf: hisi: Support uncore ITS PMU
Documentation/admin-guide/perf/hisi-pmu.rst | 6 +
drivers/perf/hisilicon/Makefile | 2 +-
drivers/perf/hisilicon/hisi_uncore_its_pmu.c | 365 +++++++++++++++++++
drivers/perf/hisilicon/hisi_uncore_mn_pmu.c | 61 +++-
4 files changed, 427 insertions(+), 7 deletions(-)
create mode 100644 drivers/perf/hisilicon/hisi_uncore_its_pmu.c
--
2.33.0
^ permalink raw reply
* [PATCH 2/2] drivers/perf: hisi: Add new function for HiSilicon MN PMU driver
From: Yushan Wang @ 2026-04-23 15:29 UTC (permalink / raw)
To: will, mark.rutland, robin.murphy, linux-arm-kernel, linux-kernel
Cc: fanghao11, linuxarm, liuyonglong, prime.zeng, wangzhou1,
wangyushan12
In-Reply-To: <20260423152959.1458563-1-wangyushan12@huawei.com>
From: Yifan Wu <wuyifan50@huawei.com>
MN (Miscellaneous Node) is a hybrid node in ARM CHI. The MN PMU driver
using the HiSilicon uncore PMU framework.
On HiSilicon HIP13 platform, cycle event is supported on MN PMU. The
cycle event is exposed directly in driver and some variables shall be
added suffix to distinguish the version.
Signed-off-by: Yifan Wu <wuyifan50@huawei.com>
Signed-off-by: Yushan Wang <wangyushan12@huawei.com>
---
drivers/perf/hisilicon/hisi_uncore_mn_pmu.c | 61 +++++++++++++++++++--
1 file changed, 55 insertions(+), 6 deletions(-)
diff --git a/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c b/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c
index 4df4eebe243e..cdd5a1591408 100644
--- a/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c
+++ b/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c
@@ -192,7 +192,7 @@ static const struct attribute_group hisi_mn_pmu_format_group = {
.attrs = hisi_mn_pmu_format_attr,
};
-static struct attribute *hisi_mn_pmu_events_attr[] = {
+static struct attribute *hisi_mn_pmu_events_attr_v1[] = {
HISI_PMU_EVENT_ATTR(req_eobarrier_num, 0x00),
HISI_PMU_EVENT_ATTR(req_ecbarrier_num, 0x01),
HISI_PMU_EVENT_ATTR(req_dvmop_num, 0x02),
@@ -219,14 +219,55 @@ static struct attribute *hisi_mn_pmu_events_attr[] = {
NULL
};
-static const struct attribute_group hisi_mn_pmu_events_group = {
+static const struct attribute_group hisi_mn_pmu_events_group_v1 = {
.name = "events",
- .attrs = hisi_mn_pmu_events_attr,
+ .attrs = hisi_mn_pmu_events_attr_v1,
};
-static const struct attribute_group *hisi_mn_pmu_attr_groups[] = {
+static const struct attribute_group *hisi_mn_pmu_attr_groups_v1[] = {
&hisi_mn_pmu_format_group,
- &hisi_mn_pmu_events_group,
+ &hisi_mn_pmu_events_group_v1,
+ &hisi_pmu_cpumask_attr_group,
+ &hisi_pmu_identifier_group,
+ NULL
+};
+
+static struct attribute *hisi_mn_pmu_events_attr_v2[] = {
+ HISI_PMU_EVENT_ATTR(req_eobarrier_num, 0x00),
+ HISI_PMU_EVENT_ATTR(req_ecbarrier_num, 0x01),
+ HISI_PMU_EVENT_ATTR(req_dvmop_num, 0x02),
+ HISI_PMU_EVENT_ATTR(req_dvmsync_num, 0x03),
+ HISI_PMU_EVENT_ATTR(req_retry_num, 0x04),
+ HISI_PMU_EVENT_ATTR(req_writenosnp_num, 0x05),
+ HISI_PMU_EVENT_ATTR(req_readnosnp_num, 0x06),
+ HISI_PMU_EVENT_ATTR(snp_dvm_num, 0x07),
+ HISI_PMU_EVENT_ATTR(snp_dvmsync_num, 0x08),
+ HISI_PMU_EVENT_ATTR(l3t_req_dvm_num, 0x09),
+ HISI_PMU_EVENT_ATTR(l3t_req_dvmsync_num, 0x0A),
+ HISI_PMU_EVENT_ATTR(mn_req_dvm_num, 0x0B),
+ HISI_PMU_EVENT_ATTR(mn_req_dvmsync_num, 0x0C),
+ HISI_PMU_EVENT_ATTR(pa_req_dvm_num, 0x0D),
+ HISI_PMU_EVENT_ATTR(pa_req_dvmsync_num, 0x0E),
+ HISI_PMU_EVENT_ATTR(cycles, 0x0F),
+ HISI_PMU_EVENT_ATTR(snp_dvm_latency, 0x80),
+ HISI_PMU_EVENT_ATTR(snp_dvmsync_latency, 0x81),
+ HISI_PMU_EVENT_ATTR(l3t_req_dvm_latency, 0x82),
+ HISI_PMU_EVENT_ATTR(l3t_req_dvmsync_latency, 0x83),
+ HISI_PMU_EVENT_ATTR(mn_req_dvm_latency, 0x84),
+ HISI_PMU_EVENT_ATTR(mn_req_dvmsync_latency, 0x85),
+ HISI_PMU_EVENT_ATTR(pa_req_dvm_latency, 0x86),
+ HISI_PMU_EVENT_ATTR(pa_req_dvmsync_latency, 0x87),
+ NULL
+};
+
+static const struct attribute_group hisi_mn_pmu_events_group_v2 = {
+ .name = "events",
+ .attrs = hisi_mn_pmu_events_attr_v2,
+};
+
+static const struct attribute_group *hisi_mn_pmu_attr_groups_v2[] = {
+ &hisi_mn_pmu_format_group,
+ &hisi_mn_pmu_events_group_v2,
&hisi_pmu_cpumask_attr_group,
&hisi_pmu_identifier_group,
NULL
@@ -351,7 +392,14 @@ static struct hisi_mn_pmu_regs hisi_mn_v1_pmu_regs = {
};
static const struct hisi_pmu_dev_info hisi_mn_v1 = {
- .attr_groups = hisi_mn_pmu_attr_groups,
+ .attr_groups = hisi_mn_pmu_attr_groups_v1,
+ .counter_bits = 48,
+ .check_event = HISI_MN_EVTYPE_MASK,
+ .private = &hisi_mn_v1_pmu_regs,
+};
+
+static const struct hisi_pmu_dev_info hisi_mn_v2 = {
+ .attr_groups = hisi_mn_pmu_attr_groups_v2,
.counter_bits = 48,
.check_event = HISI_MN_EVTYPE_MASK,
.private = &hisi_mn_v1_pmu_regs,
@@ -359,6 +407,7 @@ static const struct hisi_pmu_dev_info hisi_mn_v1 = {
static const struct acpi_device_id hisi_mn_pmu_acpi_match[] = {
{ "HISI0222", (kernel_ulong_t) &hisi_mn_v1 },
+ { "HISI0224", (kernel_ulong_t) &hisi_mn_v2 },
{ }
};
MODULE_DEVICE_TABLE(acpi, hisi_mn_pmu_acpi_match);
--
2.33.0
^ permalink raw reply related
* [PATCH 1/2] drivers/perf: hisi: Support uncore ITS PMU
From: Yushan Wang @ 2026-04-23 15:29 UTC (permalink / raw)
To: will, mark.rutland, robin.murphy, linux-arm-kernel, linux-kernel
Cc: fanghao11, linuxarm, liuyonglong, prime.zeng, wangzhou1,
wangyushan12
In-Reply-To: <20260423152959.1458563-1-wangyushan12@huawei.com>
Support uncore ITS PMU, which provides the capability of counting
the number of interrupts routed to ITS by interrupt catagories, and the
latency. It also supports collecting statistics of micro-ops of ITS.
The driver adapts to HiSilicon uncore PMU framework. It does not support
overflow interruption, which is the same as NoC PMU, so a few dummy
functions or handling interrupts are left empty.
Signed-off-by: Yushan Wang <wangyushan12@huawei.com>
---
Documentation/admin-guide/perf/hisi-pmu.rst | 6 +
drivers/perf/hisilicon/Makefile | 2 +-
drivers/perf/hisilicon/hisi_uncore_its_pmu.c | 365 +++++++++++++++++++
3 files changed, 372 insertions(+), 1 deletion(-)
create mode 100644 drivers/perf/hisilicon/hisi_uncore_its_pmu.c
diff --git a/Documentation/admin-guide/perf/hisi-pmu.rst b/Documentation/admin-guide/perf/hisi-pmu.rst
index d56b2d690709..66ce9f2e6fe3 100644
--- a/Documentation/admin-guide/perf/hisi-pmu.rst
+++ b/Documentation/admin-guide/perf/hisi-pmu.rst
@@ -128,6 +128,12 @@ channel with this option. The current supported channels are as follows:
7. tt_en: NoC PMU supports counting only transactions that have tracetag set
if this option is set. See the 2nd list for more information about tracetag.
+8. int_id: ITS PMU supports filtering by interrupt id, which is defined by
+ hardware. Interrupt id takes up to 32 bits, and can be devided into 2 parts:
+
+- Upper 16 bits: DeviceID if counting LPI, PEID if counting SGI/PPI.
+- Lower 16 bits: EventID if counting LPI, IntID if counting SGI/PPI.
+
For HiSilicon uncore PMU v3 whose identifier is 0x40, some uncore PMUs are
further divided into parts for finer granularity of tracing, each part has its
own dedicated PMU, and all such PMUs together cover the monitoring job of events
diff --git a/drivers/perf/hisilicon/Makefile b/drivers/perf/hisilicon/Makefile
index 186be3d02238..5f28cfdb8a72 100644
--- a/drivers/perf/hisilicon/Makefile
+++ b/drivers/perf/hisilicon/Makefile
@@ -2,7 +2,7 @@
obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o hisi_uncore_l3c_pmu.o \
hisi_uncore_hha_pmu.o hisi_uncore_ddrc_pmu.o hisi_uncore_sllc_pmu.o \
hisi_uncore_pa_pmu.o hisi_uncore_cpa_pmu.o hisi_uncore_uc_pmu.o \
- hisi_uncore_noc_pmu.o hisi_uncore_mn_pmu.o
+ hisi_uncore_noc_pmu.o hisi_uncore_mn_pmu.o hisi_uncore_its_pmu.o
obj-$(CONFIG_HISI_PCIE_PMU) += hisi_pcie_pmu.o
obj-$(CONFIG_HNS3_PMU) += hns3_pmu.o
diff --git a/drivers/perf/hisilicon/hisi_uncore_its_pmu.c b/drivers/perf/hisilicon/hisi_uncore_its_pmu.c
new file mode 100644
index 000000000000..608c298b8944
--- /dev/null
+++ b/drivers/perf/hisilicon/hisi_uncore_its_pmu.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for HiSilicon Uncore ITS PMU device
+ *
+ * Copyright (c) 2026 HiSilicon Technologies Co., Ltd.
+ * Author: Yushan Wang <wangyushan12@huawei.com>
+ */
+#include <linux/bitops.h>
+#include <linux/cpuhotplug.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/sysfs.h>
+
+#include "hisi_uncore_pmu.h"
+
+#define ITS_PMU_VERSION 0x21000
+#define ITS_PMU_GLOBAL_CTRL 0x21004
+#define ITS_PMU_GLOBAL_CTRL_PMU_EN BIT(0)
+#define ITS_PMU_COUNTER_CTRL 0x21008
+#define ITS_PMU_EVENT_CTRL 0x2100c
+#define ITS_PMU_COUNTER0 0x21010
+
+#define ITS_PMU_INT_ID_MASK 0x20008
+#define ITS_PMU_INT_ID_CTRL 0x20084
+
+#define ITS_PMU_NR_COUNTERS 4
+
+#define ITS_PMU_EVENT_CNTRn(cntr0, n) ((cntr0) + 8 * (n))
+#define ITS_PMU_CNTR_CTRL_MASK(n) GENMASK(8 * ((n) + 1) - 1, 8 * (n))
+#define ITS_PMU_CNTR_EVENT_CFG(n, e) ((e) << ((n) * 8))
+#define ITS_PMU_EVENT_CTRL_TYPE GENMASK(12, 0)
+
+HISI_PMU_EVENT_ATTR_EXTRACTOR(int_id, config1, 31, 0);
+
+/* Dynamic CPU hotplug state used by this PMU driver */
+static enum cpuhp_state hisi_its_pmu_cpuhp_state;
+
+struct hisi_its_pmu_regs {
+ u32 version;
+ u32 pmu_ctrl;
+ u32 event_ctrl0;
+ u32 event_cntr0;
+ u32 cntr_ctrl;
+};
+
+static void hisi_its_pmu_write_evtype(struct hisi_pmu *its_pmu, int idx, u32 type)
+{
+ struct hisi_its_pmu_regs *reg_info = its_pmu->dev_info->private;
+ u32 reg;
+
+ reg = readl(its_pmu->base + reg_info->event_ctrl0);
+ reg &= ~ITS_PMU_CNTR_CTRL_MASK(idx);
+ reg |= ITS_PMU_CNTR_EVENT_CFG(idx, type);
+ writel(reg, its_pmu->base + reg_info->event_ctrl0);
+}
+
+static u64 hisi_its_pmu_read_counter(struct hisi_pmu *its_pmu,
+ struct hw_perf_event *hwc)
+{
+ struct hisi_its_pmu_regs *reg_info = its_pmu->dev_info->private;
+
+ return readq(its_pmu->base + ITS_PMU_EVENT_CNTRn(reg_info->event_cntr0, hwc->idx));
+}
+
+static void hisi_its_pmu_write_counter(struct hisi_pmu *its_pmu,
+ struct hw_perf_event *hwc, u64 val)
+{
+ struct hisi_its_pmu_regs *reg_info = its_pmu->dev_info->private;
+
+ writeq(val, its_pmu->base + ITS_PMU_EVENT_CNTRn(reg_info->event_cntr0, hwc->idx));
+}
+
+static void hisi_its_pmu_enable_counter(struct hisi_pmu *its_pmu,
+ struct hw_perf_event *hwc)
+{
+ struct hisi_its_pmu_regs *reg_info = its_pmu->dev_info->private;
+ u32 reg;
+
+ reg = readl(its_pmu->base + reg_info->cntr_ctrl);
+ reg |= BIT(hwc->idx);
+ writel(reg, its_pmu->base + reg_info->cntr_ctrl);
+}
+
+static void hisi_its_pmu_disable_counter(struct hisi_pmu *its_pmu,
+ struct hw_perf_event *hwc)
+{
+ struct hisi_its_pmu_regs *reg_info = its_pmu->dev_info->private;
+ u32 reg;
+
+ reg = readl(its_pmu->base + reg_info->cntr_ctrl);
+ reg &= ~BIT(hwc->idx);
+ writel(reg, its_pmu->base + reg_info->cntr_ctrl);
+}
+
+static void hisi_its_pmu_enable_counter_int(struct hisi_pmu *its_pmu,
+ struct hw_perf_event *hwc)
+{
+ /* We don't support interrupt, so a stub here. */
+}
+
+static void hisi_its_pmu_disable_counter_int(struct hisi_pmu *its_pmu,
+ struct hw_perf_event *hwc)
+{
+}
+
+static void hisi_its_pmu_start_counters(struct hisi_pmu *its_pmu)
+{
+ struct hisi_its_pmu_regs *reg_info = its_pmu->dev_info->private;
+ u32 reg;
+
+ reg = readl(its_pmu->base + reg_info->pmu_ctrl);
+ reg |= ITS_PMU_GLOBAL_CTRL_PMU_EN;
+ writel(reg, its_pmu->base + reg_info->pmu_ctrl);
+}
+
+static void hisi_its_pmu_stop_counters(struct hisi_pmu *its_pmu)
+{
+ struct hisi_its_pmu_regs *reg_info = its_pmu->dev_info->private;
+ u32 reg;
+
+ reg = readl(its_pmu->base + reg_info->pmu_ctrl);
+ reg &= ~ITS_PMU_GLOBAL_CTRL_PMU_EN;
+ writel(reg, its_pmu->base + reg_info->pmu_ctrl);
+}
+
+static void hisi_its_pmu_enable_filter(struct perf_event *event)
+{
+ struct hisi_pmu *its_pmu = to_hisi_pmu(event->pmu);
+ u32 int_id = hisi_get_int_id(event);
+ u32 reg = int_id ? 0 : -1U;
+
+ if (int_id)
+ writel(int_id, its_pmu->base + ITS_PMU_INT_ID_CTRL);
+
+ writel(reg, its_pmu->base + ITS_PMU_INT_ID_MASK);
+}
+
+static void hisi_its_pmu_disable_filter(struct perf_event *event)
+{
+ struct hisi_pmu *its_pmu = to_hisi_pmu(event->pmu);
+ u32 int_id = hisi_get_int_id(event);
+
+ if (bitmap_weight(its_pmu->pmu_events.used_mask, its_pmu->num_counters) > 1)
+ return;
+
+ if (int_id) {
+ writel(0, its_pmu->base + ITS_PMU_INT_ID_CTRL);
+ writel(-1U, its_pmu->base + ITS_PMU_INT_ID_MASK);
+ }
+}
+
+static const struct hisi_uncore_ops hisi_uncore_its_ops = {
+ .write_evtype = hisi_its_pmu_write_evtype,
+ .get_event_idx = hisi_uncore_pmu_get_event_idx,
+ .read_counter = hisi_its_pmu_read_counter,
+ .write_counter = hisi_its_pmu_write_counter,
+ .enable_counter = hisi_its_pmu_enable_counter,
+ .disable_counter = hisi_its_pmu_disable_counter,
+ .enable_counter_int = hisi_its_pmu_enable_counter_int,
+ .disable_counter_int = hisi_its_pmu_disable_counter_int,
+ .start_counters = hisi_its_pmu_start_counters,
+ .stop_counters = hisi_its_pmu_stop_counters,
+ .enable_filter = hisi_its_pmu_enable_filter,
+ .disable_filter = hisi_its_pmu_disable_filter,
+};
+
+static struct attribute *hisi_its_pmu_format_attrs[] = {
+ HISI_PMU_FORMAT_ATTR(event, "config:0-16"),
+ HISI_PMU_FORMAT_ATTR(int_id, "config1:0-31"),
+ NULL
+};
+
+static const struct attribute_group hisi_its_pmu_format_group = {
+ .name = "format",
+ .attrs = hisi_its_pmu_format_attrs,
+};
+
+static struct attribute *hisi_its_pmu_events_attrs[] = {
+ HISI_PMU_EVENT_ATTR(lpi_num, 0xc0),
+ HISI_PMU_EVENT_ATTR(lpi_time, 0x80),
+ HISI_PMU_EVENT_ATTR(sgi_num, 0xc1),
+ HISI_PMU_EVENT_ATTR(sgi_time, 0x81),
+ HISI_PMU_EVENT_ATTR(ppi_num, 0xc2),
+ HISI_PMU_EVENT_ATTR(ppi_time, 0x82),
+ HISI_PMU_EVENT_ATTR(sl3_lpi_num, 0xc3),
+ HISI_PMU_EVENT_ATTR(sl3_sgi_num, 0xc4),
+ HISI_PMU_EVENT_ATTR(sl3_ppi_num, 0xc5),
+ HISI_PMU_EVENT_ATTR(sl0_ddr_read, 0xc9),
+ HISI_PMU_EVENT_ATTR(sl0_ddr_time, 0x89),
+ HISI_PMU_EVENT_ATTR(sl1_ddr_read, 0xca),
+ HISI_PMU_EVENT_ATTR(sl1_ddr_time, 0x8a),
+ HISI_PMU_EVENT_ATTR(sl2_ddr_read, 0xcb),
+ HISI_PMU_EVENT_ATTR(sl2_ddr_time, 0x8b),
+ HISI_PMU_EVENT_ATTR(cycles, 0xcc),
+ NULL
+};
+
+static const struct attribute_group hisi_its_pmu_events_group = {
+ .name = "events",
+ .attrs = hisi_its_pmu_events_attrs,
+};
+
+static const struct attribute_group *hisi_its_pmu_attr_groups[] = {
+ &hisi_its_pmu_format_group,
+ &hisi_its_pmu_events_group,
+ &hisi_pmu_cpumask_attr_group,
+ &hisi_pmu_identifier_group,
+ NULL
+};
+
+static int hisi_its_pmu_dev_init(struct platform_device *pdev, struct hisi_pmu *its_pmu)
+{
+ struct hisi_its_pmu_regs *reg_info;
+
+ hisi_uncore_pmu_init_topology(its_pmu, &pdev->dev);
+
+ if (its_pmu->topo.scl_id < 0)
+ return dev_err_probe(&pdev->dev, -EINVAL, "failed to get scl-id\n");
+
+ if (its_pmu->topo.index_id < 0)
+ return dev_err_probe(&pdev->dev, -EINVAL, "failed to get idx-id\n");
+
+ its_pmu->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(its_pmu->base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(its_pmu->base),
+ "fail to remap io memory\n");
+
+ its_pmu->dev_info = device_get_match_data(&pdev->dev);
+ if (!its_pmu->dev_info)
+ return -ENODEV;
+
+ its_pmu->pmu_events.attr_groups = its_pmu->dev_info->attr_groups;
+ its_pmu->counter_bits = its_pmu->dev_info->counter_bits;
+ its_pmu->check_event = its_pmu->dev_info->check_event;
+ its_pmu->num_counters = ITS_PMU_NR_COUNTERS;
+ its_pmu->ops = &hisi_uncore_its_ops;
+ its_pmu->dev = &pdev->dev;
+ its_pmu->on_cpu = -1;
+
+ reg_info = its_pmu->dev_info->private;
+ its_pmu->identifier = readl(its_pmu->base + reg_info->version);
+
+ return 0;
+}
+
+static void hisi_its_pmu_remove_cpuhp_instance(void *hotplug_node)
+{
+ cpuhp_state_remove_instance_nocalls(hisi_its_pmu_cpuhp_state, hotplug_node);
+}
+
+static void hisi_its_pmu_unregister_pmu(void *pmu)
+{
+ perf_pmu_unregister(pmu);
+}
+
+static int hisi_its_pmu_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct hisi_pmu *its_pmu;
+ char *name;
+ int ret;
+
+ its_pmu = devm_kzalloc(dev, sizeof(*its_pmu), GFP_KERNEL);
+ if (!its_pmu)
+ return -ENOMEM;
+
+ /*
+ * HiSilicon Uncore PMU framework needs to get common hisi_pmu device
+ * from device's drvdata.
+ */
+ platform_set_drvdata(pdev, its_pmu);
+
+ ret = hisi_its_pmu_dev_init(pdev, its_pmu);
+ if (ret)
+ return ret;
+
+ ret = cpuhp_state_add_instance(hisi_its_pmu_cpuhp_state, &its_pmu->node);
+ if (ret)
+ return dev_err_probe(dev, ret, "Fail to register cpuhp instance\n");
+
+ ret = devm_add_action_or_reset(dev, hisi_its_pmu_remove_cpuhp_instance,
+ &its_pmu->node);
+ if (ret)
+ return ret;
+
+ hisi_pmu_init(its_pmu, THIS_MODULE);
+
+ name = devm_kasprintf(dev, GFP_KERNEL, "hisi_scl%d_its%d",
+ its_pmu->topo.scl_id, its_pmu->topo.index_id);
+ if (!name)
+ return -ENOMEM;
+
+ ret = perf_pmu_register(&its_pmu->pmu, name, -1);
+ if (ret)
+ return dev_err_probe(dev, ret, "Fail to register PMU\n");
+
+ return devm_add_action_or_reset(dev, hisi_its_pmu_unregister_pmu,
+ &its_pmu->pmu);
+}
+
+static struct hisi_its_pmu_regs hisi_its_v1_pmu_regs = {
+ .version = ITS_PMU_VERSION,
+ .pmu_ctrl = ITS_PMU_GLOBAL_CTRL,
+ .event_ctrl0 = ITS_PMU_EVENT_CTRL,
+ .event_cntr0 = ITS_PMU_COUNTER0,
+ .cntr_ctrl = ITS_PMU_COUNTER_CTRL,
+};
+
+static const struct hisi_pmu_dev_info hisi_its_v1 = {
+ .attr_groups = hisi_its_pmu_attr_groups,
+ .counter_bits = 48,
+ .check_event = ITS_PMU_EVENT_CTRL_TYPE,
+ .private = &hisi_its_v1_pmu_regs,
+};
+
+static const struct acpi_device_id hisi_its_pmu_ids[] = {
+ { "HISI0591", (kernel_ulong_t) &hisi_its_v1 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, hisi_its_pmu_ids);
+
+static struct platform_driver hisi_its_pmu_driver = {
+ .driver = {
+ .name = "hisi_its_pmu",
+ .acpi_match_table = hisi_its_pmu_ids,
+ .suppress_bind_attrs = true,
+ },
+ .probe = hisi_its_pmu_probe,
+};
+
+static int __init hisi_its_pmu_module_init(void)
+{
+ int ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
+ "perf/hisi/its:online",
+ hisi_uncore_pmu_online_cpu,
+ hisi_uncore_pmu_offline_cpu);
+ if (ret < 0) {
+ pr_err("hisi_its_pmu: Fail to setup cpuhp callbacks, ret = %d\n", ret);
+ return ret;
+ }
+ hisi_its_pmu_cpuhp_state = ret;
+
+ ret = platform_driver_register(&hisi_its_pmu_driver);
+ if (ret)
+ cpuhp_remove_multi_state(hisi_its_pmu_cpuhp_state);
+
+ return ret;
+}
+module_init(hisi_its_pmu_module_init);
+
+static void __exit hisi_its_pmu_module_exit(void)
+{
+ platform_driver_unregister(&hisi_its_pmu_driver);
+ cpuhp_remove_multi_state(hisi_its_pmu_cpuhp_state);
+}
+module_exit(hisi_its_pmu_module_exit);
+
+MODULE_IMPORT_NS("HISI_PMU");
+MODULE_DESCRIPTION("HiSilicon SoC Uncore ITS PMU driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Yushan Wang <wangyushan12@huawei.com>");
--
2.33.0
^ permalink raw reply related
* Re: [PATCH v3 06/11] iommu: Defer __iommu_group_free_device() to be outside group->mutex
From: Nicolin Chen @ 2026-04-23 15:47 UTC (permalink / raw)
To: Baolu Lu
Cc: Will Deacon, Robin Murphy, Joerg Roedel, Bjorn Helgaas,
Jason Gunthorpe, Rafael J . Wysocki, Len Brown,
Pranjal Shrivastava, Mostafa Saleh, Kevin Tian, linux-arm-kernel,
iommu, linux-kernel, linux-acpi, linux-pci, vsethi, Shuai Xue
In-Reply-To: <3570e178-f887-45c9-a251-e089915cfbd9@linux.intel.com>
On Thu, Apr 23, 2026 at 03:55:02PM +0800, Baolu Lu wrote:
> On 4/17/26 07:28, Nicolin Chen wrote:
> > +static void __iommu_group_empty_assert_owner_cnt(struct iommu_group *group)
> > +{
> > + lockdep_assert_held(&group->mutex);
> > + /*
> > + * If the group has become empty then ownership must have been
> > + * released, and the current domain must be set back to NULL or
> > + * the default domain.
> > + */
>
> Nit: this comment doesn't quite match the following code. The code
> doesn't check "group->domain != NULL". Or perhaps in that case,
> group->default_domain must be NULL?
This is the original patch from Jason:
https://lore.kernel.org/r/4-v3-328044aa278c+45e49-iommu_probe_jgg@nvidia.com
I kept the comments as-is, though It might be slightly confusing?
I think it means:
If group->default_domain == NULL, it does check "set back to NULL".
If group->default_domain != NULL, it then checks "default domain".
Maybe it could be "must be set back to the default domain (which
itself can be NULL"?
> Furthermore, if a device is currently quarantined, group->domain will be
> the blocking_domain. If that quarantined device is then hot-removed and
> happens to be the last device in the group, will this WARN_ON trigger
> unnecessarily?
If a device is quarantined, its group->domain is retained to the
previously attached domain. Its blocking state is logged in the
gdev->blocked flag. So, I think it can pass the test.
Thanks
Nicolin
^ permalink raw reply
* [PATCH v4 1/8] dt-bindings: thermal: amlogic: Add support for T7
From: Ronald Claveau via B4 Relay @ 2026-04-23 16:07 UTC (permalink / raw)
To: Guillaume La Roque, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl
Cc: linux-pm, linux-amlogic, devicetree, linux-kernel,
linux-arm-kernel, Ronald Claveau, Conor Dooley
In-Reply-To: <20260423-add-thermal-t7-vim4-v4-0-d4c1528d5044@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Add the amlogic,t7-thermal compatible for the Amlogic T7 thermal sensor.
Unlike existing variants which use a phandle to the ao-secure syscon,
the T7 relies on a secure monitor interface described by a phandle and
a sensor index argument.
The T7 integrates multiple thermal sensors, all accessed through the
same SMC call. The sensor index argument is required to identify which
sensor's calibration data the secure monitor should return, as a single
SM_THERMAL_CALIB_READ command serves all of them.
Introduce the amlogic,secure-monitor property as a phandle-array and
make amlogic,ao-secure or amlogic,secure-monitor conditionally required
depending on the compatible.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
.../bindings/thermal/amlogic,thermal.yaml | 37 ++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml b/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml
index 70b273271754b..e28612510d679 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>;
+ };
...
--
2.49.0
^ permalink raw reply related
* [PATCH v4 2/8] firmware: meson: sm: Thermal calibration read via secure monitor
From: Ronald Claveau via B4 Relay @ 2026-04-23 16:07 UTC (permalink / raw)
To: Guillaume La Roque, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl
Cc: linux-pm, linux-amlogic, devicetree, linux-kernel,
linux-arm-kernel, Ronald Claveau
In-Reply-To: <20260423-add-thermal-t7-vim4-v4-0-d4c1528d5044@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Add SM_THERMAL_CALIB_READ to the secure monitor command enum and
introduce meson_sm_get_thermal_calib() to allow drivers to retrieve
thermal sensor calibration data through the firmware interface.
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
include/linux/firmware/meson/meson_sm.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h
index 8eaf8922ab020..3ebc2bd9a9760 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_ */
--
2.49.0
^ permalink raw reply related
* [PATCH v4 6/8] arm64: dts: amlogic: t7: Add thermal sensor nodes
From: Ronald Claveau via B4 Relay @ 2026-04-23 16:07 UTC (permalink / raw)
To: Guillaume La Roque, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl
Cc: linux-pm, linux-amlogic, devicetree, linux-kernel,
linux-arm-kernel, Ronald Claveau
In-Reply-To: <20260423-add-thermal-t7-vim4-v4-0-d4c1528d5044@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Add six temperature sensor nodes using the amlogic,t7-thermal compatible:
a73, a53, gpu, nna, vpu, and hevc. Each sensor retrieves its calibration
data from the secure monitor via the amlogic,secure-monitor phandle with
the corresponding tsensor_id argument.
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 58 +++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
index 7aec65f036a9c..62f259b2b17d2 100644
--- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
+++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
@@ -656,6 +656,24 @@ sec_ao: ao-secure@10220 {
amlogic,has-chip-id;
};
+ a73_tsensor: 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>;
+ };
+
+ a53_tsensor: temperature-sensor@22000 {
+ compatible = "amlogic,t7-thermal";
+ reg = <0x0 0x22000 0x0 0x50>;
+ interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clkc_periphs CLKID_TS>;
+ #thermal-sensor-cells = <0>;
+ amlogic,secure-monitor = <&sm 2>;
+ };
+
pwm_ao_ef: pwm@30000 {
compatible = "amlogic,t7-pwm", "amlogic,meson-s4-pwm";
reg = <0x0 0x30000 0x0 0x24>;
@@ -770,6 +788,46 @@ sd_emmc_c: mmc@8c000 {
assigned-clock-parents = <&xtal>;
status = "disabled";
};
+
+ gpu_tsensor: temperature-sensor@94000 {
+ compatible = "amlogic,t7-thermal";
+ reg = <0x0 0x94000 0x0 0x50>;
+ interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clkc_periphs CLKID_TS>;
+ power-domains = <&pwrc PWRC_T7_MALI_TOP_ID>;
+ #thermal-sensor-cells = <0>;
+ amlogic,secure-monitor = <&sm 3>;
+ };
+
+ nna_tsensor: temperature-sensor@96000 {
+ compatible = "amlogic,t7-thermal";
+ reg = <0x0 0x96000 0x0 0x50>;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clkc_periphs CLKID_TS>;
+ power-domains = <&pwrc PWRC_T7_NNA_TOP_ID>;
+ #thermal-sensor-cells = <0>;
+ amlogic,secure-monitor = <&sm 4>;
+ };
+
+ vpu_tsensor: temperature-sensor@98000 {
+ compatible = "amlogic,t7-thermal";
+ reg = <0x0 0x98000 0x0 0x50>;
+ interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clkc_periphs CLKID_TS>;
+ power-domains = <&pwrc PWRC_T7_VPU_HDMI_ID>;
+ #thermal-sensor-cells = <0>;
+ amlogic,secure-monitor = <&sm 6>;
+ };
+
+ hevc_tsensor: temperature-sensor@9a000 {
+ compatible = "amlogic,t7-thermal";
+ reg = <0x0 0x9a000 0x0 0x50>;
+ interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clkc_periphs CLKID_TS>;
+ power-domains = <&pwrc PWRC_T7_DOS_HEVC_ID>;
+ #thermal-sensor-cells = <0>;
+ amlogic,secure-monitor = <&sm 5>;
+ };
};
};
--
2.49.0
^ permalink raw reply related
* [PATCH v4 0/8] arm64: amlogic: T7 thermal support
From: Ronald Claveau via B4 Relay @ 2026-04-23 16:07 UTC (permalink / raw)
To: Guillaume La Roque, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl
Cc: linux-pm, linux-amlogic, devicetree, linux-kernel,
linux-arm-kernel, Ronald Claveau, Conor Dooley
This series adds thermal monitoring support for the Amlogic T7 SoC,
used on the Khadas VIM4 board.
The T7 exposes six thermal sensors (a53, a73, gpu, nna, vpu, hevc),
each accessible through the secure monitor firmware interface rather
than a directly mapped eFuse register as on older SoCs.
The series is organized as follows:
- Patch 1 extends the amlogic,t7-thermal DT binding to describe the
new amlogic,secure-monitor property.
- Patches 2-3 extend the Meson secure monitor driver to expose a
thermal calibration read command (SMC ID 0x82000047).
- Patch 4 adds the secure monitor readout path to the amlogic thermal
driver and introduces the amlogic,t7-thermal compatible.
- Patches 5-7 wire up the T7 DTSI with CPU cooling cells, sensor
nodes, and thermal zones.
- Patch 8 extends the Khadas VIM4 DTS to map all thermal zones to the
on-board MCU fan controller (states 30–100, corresponding to the
FAN_CTRL register range 0x1E–0x64).
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
Changes in v4:
- PATCH 4: Extract SM and syscon setup into amlogic_thermal_probe_sm() and
amlogic_thermal_probe_syscon() removing amlogic_thermal_initialize()
from Daniel's feedback.
Also fix pre-existing bug: move calibration readout before
devm_thermal_of_zone_register() to avoid reading uninitialized
trim_info, according to Daniel's feedback.
- Link to v3: https://lore.kernel.org/r/20260421-add-thermal-t7-vim4-v3-0-a2e7215ed003@aliel.fr
Changes in v3:
- PATCH 1: Replace second if check by an else statement.
Remove unnecessary label in example according to Conor's feedback
- Link to v2: https://lore.kernel.org/r/20260413-add-thermal-t7-vim4-v2-0-1002d90a0602@aliel.fr
Changes in v2:
- PATCH 1: change two const entries to enum, explain why sensor index is needed
reorder conditional compatible for required, and fallback only according to
Krzysztof's feedback.
- PATCH 3: Add kerneldoc for meson_sm_get_thermal_calib exported function
according to Krzysztof's feedback.
- Link to v1: https://lore.kernel.org/r/20260410-add-thermal-t7-vim4-v1-0-19f2b8da74d7@aliel.fr
---
Ronald Claveau (8):
dt-bindings: thermal: amlogic: Add support for T7
firmware: meson: sm: Thermal calibration read via secure monitor
firmware: meson: sm: Add thermal calibration SMC call
thermal: amlogic: Add support for secure monitor calibration readout
arm64: dts: amlogic: t7: Add cooling cells to all CPUs
arm64: dts: amlogic: t7: Add thermal sensor nodes
arm64: dts: amlogic: t7: Add thermal zones
arm64: dts: amlogic: t7: khadas-vim4: Add fan cooling to thermal zones
.../bindings/thermal/amlogic,thermal.yaml | 37 +++-
.../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 102 +++++++++
arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 245 +++++++++++++++++++++
drivers/firmware/meson/meson_sm.c | 29 ++-
drivers/thermal/amlogic_thermal.c | 115 +++++++---
include/linux/firmware/meson/meson_sm.h | 3 +
6 files changed, 494 insertions(+), 37 deletions(-)
---
base-commit: f7b64ed948718290209074a50bb0df17e5944873
change-id: 20260410-add-thermal-t7-vim4-00e571badcc1
prerequisite-message-id: <20260326092645.1053261-1-jian.hu@amlogic.com>
prerequisite-patch-id: f03a086b4137158412b2d47b3de793b858de8dde
prerequisite-patch-id: 123970c9b29c2090440f2fd71c85d3c6fd8e36de
prerequisite-patch-id: 3e2e56b0926ba327b520f935df4ced5089bbe503
prerequisite-patch-id: 65a5d76ffdbc9b3aab3385bb65cb027004c30e7e
prerequisite-patch-id: 237269801826dd3ad7fb16eb4d7d6d4eab504278
prerequisite-patch-id: 57e9b08a968aedf543d3d0d56cf1ca4db20b2a16
prerequisite-change-id: 20260326-add-bcm43752-compatible-e264a4f7973a:v2
prerequisite-patch-id: cd98b74fa56af72af2553f391c400981d83cd4f4
prerequisite-patch-id: b730f5e42be1d89d193e63a0265495cdbf2c7d7b
prerequisite-change-id: 20260330-fix-invalid-property-bbe54d933f71:v2
prerequisite-patch-id: 8d675e7a239985c762843515b241f0a2f45f9c92
prerequisite-change-id: 20260331-fix-aml-t7-null-reset-2b608ebf9da4:v1
prerequisite-patch-id: 5b5de77af11747ce964404fb827d2ee2bff47ea5
prerequisite-patch-id: 1e37fc75fed1e533adee0f3e7e6ead1f8ff3c55c
prerequisite-patch-id: 65a5d76ffdbc9b3aab3385bb65cb027004c30e7e
prerequisite-patch-id: 2daf583fb5e7449a02bd217d8aca330171b598aa
prerequisite-patch-id: 237269801826dd3ad7fb16eb4d7d6d4eab504278
prerequisite-patch-id: d1ddf9b7710e91f8062de83bd7ba55afb2c4c112
prerequisite-patch-id: 57e9b08a968aedf543d3d0d56cf1ca4db20b2a16
prerequisite-patch-id: cd98b74fa56af72af2553f391c400981d83cd4f4
prerequisite-patch-id: b730f5e42be1d89d193e63a0265495cdbf2c7d7b
prerequisite-patch-id: 9debd88fa60febed9cd7208f86603b4c2d270520
prerequisite-patch-id: 314ef9ff0c4d1d15dab1dea9d92aa065f1eac3e9
prerequisite-change-id: 20260402-add-mcu-fan-khadas-vim4-ac1cbe553c9b:v4
prerequisite-patch-id: f03a086b4137158412b2d47b3de793b858de8dde
prerequisite-patch-id: 123970c9b29c2090440f2fd71c85d3c6fd8e36de
prerequisite-patch-id: 3e2e56b0926ba327b520f935df4ced5089bbe503
prerequisite-patch-id: 65a5d76ffdbc9b3aab3385bb65cb027004c30e7e
prerequisite-patch-id: 237269801826dd3ad7fb16eb4d7d6d4eab504278
prerequisite-patch-id: 57e9b08a968aedf543d3d0d56cf1ca4db20b2a16
prerequisite-patch-id: cd98b74fa56af72af2553f391c400981d83cd4f4
prerequisite-patch-id: b730f5e42be1d89d193e63a0265495cdbf2c7d7b
prerequisite-patch-id: 8d675e7a239985c762843515b241f0a2f45f9c92
prerequisite-patch-id: 9debd88fa60febed9cd7208f86603b4c2d270520
prerequisite-patch-id: 314ef9ff0c4d1d15dab1dea9d92aa065f1eac3e9
prerequisite-patch-id: e6a1b58b91a877504a12ae68bee71eb4e496c33b
prerequisite-patch-id: 406f88d7dabd3a870b358fb53c21686f29eb32b7
prerequisite-patch-id: d7a75ae3be0f54e0a7e81ccb0043a2f05423c9d0
prerequisite-patch-id: 5e19dc5ace12b532284246f5c2ff3f214d8a9c4f
prerequisite-patch-id: d6a87ebcf5246eb67b94ca0908afa3df9f9383fe
prerequisite-patch-id: 4809bbedf79f59e1abc52c17cffc0b1bbb43d365
prerequisite-patch-id: c050e8bac4b5491f6c7008a5ccb26f20fad38b46
prerequisite-patch-id: 30677db8fc57270787245103c0d5acf8791307b0
Best regards,
--
Ronald Claveau <linux-kernel-dev@aliel.fr>
^ permalink raw reply
* [PATCH v4 7/8] arm64: dts: amlogic: t7: Add thermal zones
From: Ronald Claveau via B4 Relay @ 2026-04-23 16:07 UTC (permalink / raw)
To: Guillaume La Roque, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl
Cc: linux-pm, linux-amlogic, devicetree, linux-kernel,
linux-arm-kernel, Ronald Claveau
In-Reply-To: <20260423-add-thermal-t7-vim4-v4-0-d4c1528d5044@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Add thermal zones for all six sensors: a53, a73, gpu, nna, vpu, and hevc.
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 179 ++++++++++++++++++++++++++++
1 file changed, 179 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
index 62f259b2b17d2..c6ea0f20a879f 100644
--- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
+++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
@@ -9,6 +9,7 @@
#include <dt-bindings/clock/amlogic,t7-scmi.h>
#include <dt-bindings/clock/amlogic,t7-pll-clkc.h>
#include <dt-bindings/clock/amlogic,t7-peripherals-clkc.h>
+#include <dt-bindings/thermal/thermal.h>
/ {
interrupt-parent = <&gic>;
@@ -829,6 +830,184 @@ hevc_tsensor: temperature-sensor@9a000 {
amlogic,secure-monitor = <&sm 5>;
};
};
+ };
+
+ thermal-zones {
+ a53_thermal: a53-thermal {
+ polling-delay = <1000>;
+ polling-delay-passive = <100>;
+ thermal-sensors = <&a53_tsensor>;
+
+ trips {
+ a53_passive: a53-passive {
+ temperature = <85000>; /* millicelsius */
+ hysteresis = <2000>; /* millicelsius */
+ type = "passive";
+ };
+
+ a53_hot: a53-hot {
+ temperature = <95000>; /* millicelsius */
+ hysteresis = <2000>; /* millicelsius */
+ type = "hot";
+ };
+
+ a53_critical: a53-critical {
+ temperature = <110000>; /* millicelsius */
+ hysteresis = <2000>; /* millicelsius */
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map-a53 {
+ trip = <&a53_passive>;
+ cooling-device =
+ <&cpu100 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu101 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu102 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu103 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ a73_thermal: a73-thermal {
+ polling-delay = <1000>;
+ polling-delay-passive = <100>;
+ thermal-sensors = <&a73_tsensor>;
+
+ trips {
+ a73_passive: a73-passive {
+ temperature = <85000>; /* millicelsius */
+ hysteresis = <2000>; /* millicelsius */
+ type = "passive";
+ };
+
+ a73_hot: a73-hot {
+ temperature = <95000>; /* millicelsius */
+ hysteresis = <2000>; /* millicelsius */
+ type = "hot";
+ };
+
+ a73_critical: a73-critical {
+ temperature = <110000>; /* millicelsius */
+ hysteresis = <2000>; /* millicelsius */
+ type = "critical";
+ };
+ };
+
+ cooling-maps {
+ map-a73 {
+ trip = <&a73_passive>;
+ cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ gpu_thermal: gpu-thermal {
+ polling-delay = <1000>;
+ polling-delay-passive = <100>;
+ thermal-sensors = <&gpu_tsensor>;
+
+ trips {
+ gpu_passive: gpu-passive {
+ temperature = <95000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ gpu_hot: gpu-hot {
+ temperature = <105000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+ gpu_critical: gpu-critical {
+ temperature = <115000>;
+ hysteresis = <1000>;
+ type = "critical";
+ };
+ };
+ };
+
+ hevc_thermal: hevc-thermal {
+ polling-delay = <1000>;
+ polling-delay-passive = <100>;
+ thermal-sensors = <&hevc_tsensor>;
+
+ trips {
+ hevc_passive: hevc-passive {
+ temperature = <95000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ hevc_hot: hevc-hot {
+ temperature = <105000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ hevc_critical: hevc-critical {
+ temperature = <115000>;
+ hysteresis = <1000>;
+ type = "critical";
+ };
+ };
+ };
+
+ nna_thermal: nna-thermal {
+ polling-delay = <1000>;
+ polling-delay-passive = <100>;
+ thermal-sensors = <&nna_tsensor>;
+
+ trips {
+ nna_passive: nna-passive {
+ temperature = <95000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ nna_hot: nna-hot {
+ temperature = <105000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ nna_critical: nna-critical {
+ temperature = <115000>;
+ hysteresis = <1000>;
+ type = "critical";
+ };
+ };
+ };
+
+ vpu_thermal: vpu-thermal {
+ polling-delay = <1000>;
+ polling-delay-passive = <100>;
+ thermal-sensors = <&vpu_tsensor>;
+
+ trips {
+ vpu_passive: vpu-passive {
+ temperature = <95000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ vpu_hot: vpu-hot {
+ temperature = <105000>;
+ hysteresis = <5000>;
+ type = "passive";
+ };
+
+ vpu_critical: vpu-critical {
+ temperature = <115000>;
+ hysteresis = <1000>;
+ type = "critical";
+ };
+ };
+ };
};
};
--
2.49.0
^ permalink raw reply related
* [PATCH v4 3/8] firmware: meson: sm: Add thermal calibration SMC call
From: Ronald Claveau via B4 Relay @ 2026-04-23 16:07 UTC (permalink / raw)
To: Guillaume La Roque, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl
Cc: linux-pm, linux-amlogic, devicetree, linux-kernel,
linux-arm-kernel, Ronald Claveau
In-Reply-To: <20260423-add-thermal-t7-vim4-v4-0-d4c1528d5044@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Add SM_THERMAL_CALIB_READ at SMC ID 0x82000047 in the command
table and implement meson_sm_get_thermal_calib(), which forwards the
tsensor_id argument to the secure monitor and returns the calibration data.
Also realign the CMD() column to improve readability.
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
drivers/firmware/meson/meson_sm.c | 29 ++++++++++++++++++++++++-----
1 file changed, 24 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c
index 3ab67aaa9e5da..4e57986724212 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,24 @@ 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
--
2.49.0
^ permalink raw reply related
* [PATCH v4 5/8] arm64: dts: amlogic: t7: Add cooling cells to all CPUs
From: Ronald Claveau via B4 Relay @ 2026-04-23 16:07 UTC (permalink / raw)
To: Guillaume La Roque, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl
Cc: linux-pm, linux-amlogic, devicetree, linux-kernel,
linux-arm-kernel, Ronald Claveau
In-Reply-To: <20260423-add-thermal-t7-vim4-v4-0-d4c1528d5044@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Add #cooling-cells = <2> to all CPU nodes (both little and big cluster)
to allow them to be used as cooling devices in thermal zone mappings.
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
index 560c9dce35266..7aec65f036a9c 100644
--- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
+++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
@@ -63,6 +63,7 @@ cpu100: cpu@100 {
i-cache-size = <0x8000>;
i-cache-sets = <32>;
next-level-cache = <&l2_cache_l>;
+ #cooling-cells = <2>;
};
cpu101: cpu@101 {
@@ -77,6 +78,7 @@ cpu101: cpu@101 {
i-cache-size = <0x8000>;
i-cache-sets = <32>;
next-level-cache = <&l2_cache_l>;
+ #cooling-cells = <2>;
};
cpu102: cpu@102 {
@@ -91,6 +93,7 @@ cpu102: cpu@102 {
i-cache-size = <0x8000>;
i-cache-sets = <32>;
next-level-cache = <&l2_cache_l>;
+ #cooling-cells = <2>;
};
cpu103: cpu@103 {
@@ -105,6 +108,7 @@ cpu103: cpu@103 {
i-cache-size = <0x8000>;
i-cache-sets = <32>;
next-level-cache = <&l2_cache_l>;
+ #cooling-cells = <2>;
};
cpu0: cpu@0 {
@@ -119,6 +123,7 @@ cpu0: cpu@0 {
i-cache-size = <0x10000>;
i-cache-sets = <64>;
next-level-cache = <&l2_cache_b>;
+ #cooling-cells = <2>;
};
cpu1: cpu@1 {
@@ -133,6 +138,7 @@ cpu1: cpu@1 {
i-cache-size = <0x10000>;
i-cache-sets = <64>;
next-level-cache = <&l2_cache_b>;
+ #cooling-cells = <2>;
};
cpu2: cpu@2 {
@@ -147,6 +153,7 @@ cpu2: cpu@2 {
i-cache-size = <0x10000>;
i-cache-sets = <64>;
next-level-cache = <&l2_cache_b>;
+ #cooling-cells = <2>;
};
cpu3: cpu@3 {
@@ -161,6 +168,7 @@ cpu3: cpu@3 {
i-cache-size = <0x10000>;
i-cache-sets = <64>;
next-level-cache = <&l2_cache_b>;
+ #cooling-cells = <2>;
};
l2_cache_l: l2-cache-cluster0 {
--
2.49.0
^ permalink raw reply related
* [PATCH v4 4/8] thermal: amlogic: Add support for secure monitor calibration readout
From: Ronald Claveau via B4 Relay @ 2026-04-23 16:07 UTC (permalink / raw)
To: Guillaume La Roque, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl
Cc: linux-pm, linux-amlogic, devicetree, linux-kernel,
linux-arm-kernel, Ronald Claveau
In-Reply-To: <20260423-add-thermal-t7-vim4-v4-0-d4c1528d5044@aliel.fr>
From: Ronald Claveau <linux-kernel-dev@aliel.fr>
Some SoCs (e.g. T7) expose thermal calibration data through the secure
monitor rather than a directly accessible eFuse register. Add a use_sm
flag to amlogic_thermal_data to select this path, and retrieve the
firmware handle and tsensor_id from the "amlogic,secure-monitor" DT
phandle with one fixed argument.
Also introduce the amlogic,t7-thermal compatible using this new path.
While refactoring, fix a pre-existing bug where
amlogic_thermal_initialize() was called after
devm_thermal_of_zone_register(), causing the thermal framework to
read an uninitialized trim_info on zone registration.
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
drivers/thermal/amlogic_thermal.c | 115 ++++++++++++++++++++++++++++----------
1 file changed, 85 insertions(+), 30 deletions(-)
diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c
index 5448d772db12a..0d0c01e57b85e 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,70 @@ 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 device_node *sm_np;
+ 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;
+
+ sm_np = ph_args.np;
+ if (!sm_np) {
+ dev_err(dev,
+ "Failed to parse secure monitor phandle\n");
+ return -ENODEV;
+ }
+
+ pdata->sm_fw = meson_sm_get(sm_np);
+ of_node_put(sm_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 ret = 0;
+ 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) {
+ ret = -EINVAL;
+ dev_err(&pdata->pdev->dev,
+ "tsensor thermal calibration not supported: 0x%x!\n",
+ ver);
+ }
+
+ return ret;
+}
+
static const struct thermal_zone_device_ops amlogic_thermal_ops = {
.get_temp = amlogic_thermal_get_temp,
};
@@ -226,6 +275,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 +294,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 +330,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 +349,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;
--
2.49.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox