[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