[PATCH 5/6] firmware: samsung: acpm: Add TMU protocol support
Peter Griffin
peter.griffin at linaro.org
Fri May 8 14:47:40 PDT 2026
On Wed, 6 May 2026 at 12:39, Tudor Ambarus <tudor.ambarus at linaro.org> wrote:
>
> 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 at linaro.org>
> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski at oss.qualcomm.com>
> ---
Reviewed-by: Peter Griffin <peter.griffin at linaro.org>
> drivers/firmware/samsung/Makefile | 1 +
> drivers/firmware/samsung/exynos-acpm-tmu.c | 239 +++++++++++++++++++++
> 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, 298 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..c68d60b4c0b3
> --- /dev/null
> +++ b/drivers/firmware/samsung/exynos-acpm-tmu.c
> @@ -0,0 +1,239 @@
> +// 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/string.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 ret;
> +
> + if (tlen > ACPM_TMU_TX_DATA_LEN)
> + return -EINVAL;
> +
> + msg.tx.type = ACPM_TMU_THRESHOLD;
> + msg.tx.tzid = tz;
> + memcpy(msg.tx.data, temperature, tlen);
> +
> + 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 1a0d98b55439..2cebc5456968 100644
> --- a/drivers/firmware/samsung/exynos-acpm.c
> +++ b/drivers/firmware/samsung/exynos-acpm.c
> @@ -33,6 +33,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)
>
> @@ -688,6 +689,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
>
More information about the linux-arm-kernel
mailing list