[PATCH 03/10] board: samsung: e850-96: Add ACPM code
Sam Protsenko
semen.protsenko at linaro.org
Thu Aug 7 00:27:03 CEST 2025
Add functions to access I3C bus via APM (Active Power Management) core
by using ACPM IPC protocol. It will be further used for configuring PMIC
chip voltage regulators.
Signed-off-by: Sam Protsenko <semen.protsenko at linaro.org>
---
board/samsung/e850-96/Makefile | 2 +-
board/samsung/e850-96/acpm.c | 169 +++++++++++++++++++++++++++++++++
board/samsung/e850-96/acpm.h | 27 ++++++
3 files changed, 197 insertions(+), 1 deletion(-)
create mode 100644 board/samsung/e850-96/acpm.c
create mode 100644 board/samsung/e850-96/acpm.h
diff --git a/board/samsung/e850-96/Makefile b/board/samsung/e850-96/Makefile
index 71d46ea3d2b4..08eb8dca5bf7 100644
--- a/board/samsung/e850-96/Makefile
+++ b/board/samsung/e850-96/Makefile
@@ -3,4 +3,4 @@
# Copyright (C) 2024, Linaro Limited
# Sam Protsenko <semen.protsenko at linaro.org>
-obj-y := e850-96.o fw.o
+obj-y := e850-96.o fw.o acpm.o
diff --git a/board/samsung/e850-96/acpm.c b/board/samsung/e850-96/acpm.c
new file mode 100644
index 000000000000..1cc5c6d0e4a3
--- /dev/null
+++ b/board/samsung/e850-96/acpm.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2025 Linaro Ltd.
+ * Author: Sam Protsenko <semen.protsenko at linaro.org>
+ *
+ * ACPM (Active Clock and Power Management) is an IPC protocol for communicating
+ * with APM (Active Power Management) core. The message exchange between AP
+ * (Application Processor) and APM is happening by using shared memory in SRAM
+ * (iRAM) and generating interrupts using Mailbox block. By using this IPC
+ * interface it's possible to offload power management tasks to APM core, which
+ * acts as a supervisor for CPU. One of the main tasks of APM is controlling
+ * PMIC chip over I3C bus. So in order to access PMIC chip registers it's
+ * recommended to do so by sending corresponding commands to APM via ACPM IPC
+ * protocol. The IPC interaction sequence looks like this:
+ *
+ * AP (CPU) <-> ACPM IPC (Mailbox + SRAM) <-> APM <-> I3C <-> PMIC
+ *
+ * This file contains functions for accessing I3C bus via APM block using
+ * ACPM IPC.
+ */
+
+#include <linux/iopoll.h>
+#include <linux/time.h>
+#include <asm/io.h>
+#include "acpm.h"
+
+/* Mailbox registers */
+#define MBOX_INTGR0 0x8 /* Interrupt Generation */
+#define MBOX_INTCR1 0x20 /* Interrupt Clear */
+#define MBOX_INTSR1 0x28 /* Interrupt Status */
+#define MBOX_INTGR_OFFSET 16
+#define MBOX_TIMEOUT (1 * USEC_PER_SEC)
+
+/* APM shared memory registers */
+#define SHMEM_SR0 0x0
+#define SHMEM_SR1 0x4
+#define SHMEM_SR2 0x8
+#define SHMEM_SR3 0xc
+
+/* IPC functions */
+#define IPC_FUNC_READ 0x0
+#define IPC_FUNC_WRITE 0x1
+/* Command 0 shifts and masks */
+#define IPC_REG_SHIFT 0
+#define IPC_REG_MASK 0xff
+#define IPC_TYPE_SHIFT 8
+#define IPC_TYPE_MASK 0xf
+#define IPC_CHANNEL_SHIFT 12
+#define IPC_CHANNEL_MASK 0xf
+/* Command 1 shifts and masks */
+#define IPC_FUNC_SHIFT 0
+#define IPC_FUNC_MASK 0xff
+#define IPC_WRITE_VAL_SHIFT 8
+#define IPC_WRITE_VAL_MASK 0xff
+/* Command 3 shifts and masks */
+#define IPC_DEST_SHIFT 8
+#define IPC_DEST_MASK 0xff
+#define IPC_RETURN_SHIFT 24
+#define IPC_RETURN_MASK 0xff
+
+/**
+ * acpm_ipc_send_data_async() - Send data to I3C block over ACPM IPC
+ * @acpm: ACPM data
+ * @cmd0: Command 0 value to send
+ * @cmd1: Command 1 value to send
+ */
+static void acpm_ipc_send_data_async(struct acpm *acpm, u32 cmd0, u32 cmd1)
+{
+ u32 irq_bit = 1 << acpm->ipc_ch;
+ u32 intgr = irq_bit << MBOX_INTGR_OFFSET;
+
+ /* Write data to the shared memory */
+ writel(cmd0, acpm->sram_base + SHMEM_SR0);
+ writel(cmd1, acpm->sram_base + SHMEM_SR1);
+ dsb();
+
+ /* Generate interrupt for I3C block */
+ writel(intgr, acpm->mbox_base + MBOX_INTGR0);
+}
+
+/**
+ * acpm_ipc_wait_resp() - Read response data from I3C block over ACPM IPC
+ * @acpm: ACPM data
+ * @cmd2: Will contain read value for command 2
+ * @cmd3: Will contain read value for command 3
+ *
+ * Return: 0 on success or negative value on error.
+ */
+static int acpm_ipc_wait_resp(struct acpm *acpm, u32 *cmd2, u32 *cmd3)
+{
+ u32 irq_bit = 1 << acpm->ipc_ch;
+ u32 reg;
+ int ret;
+
+ /* Wait for the interrupt from I3C block */
+ ret = readl_poll_timeout(acpm->mbox_base + MBOX_INTSR1, reg,
+ reg & irq_bit, MBOX_TIMEOUT);
+ if (ret < 0)
+ return ret;
+
+ /* Clear the interrupt */
+ writel(irq_bit, acpm->mbox_base + MBOX_INTCR1);
+
+ /* Read data from the shared memory */
+ *cmd2 = readl(acpm->sram_base + SHMEM_SR2);
+ *cmd3 = readl(acpm->sram_base + SHMEM_SR3);
+
+ return 0;
+}
+
+/**
+ * acpm_i3c_read() - Read an I3C register of some I3C slave device
+ * @acpm: ACPM data
+ * @ch: I3C channel (bus) number (0-15)
+ * @addr: I3C address of slave device (0-15)
+ * @reg: Address of I3C register in the slave device to read from
+ * @val: Will contain the read value
+ *
+ * Return: 0 on success or non-zero code on error (may be positive).
+ */
+int acpm_i3c_read(struct acpm *acpm, u8 ch, u8 addr, u8 reg, u8 *val)
+{
+ u32 cmd[4] = { 0 };
+ u8 ret;
+
+ cmd[0] = (ch & IPC_CHANNEL_MASK) << IPC_CHANNEL_SHIFT |
+ (addr & IPC_TYPE_MASK) << IPC_TYPE_SHIFT |
+ (reg & IPC_REG_MASK) << IPC_REG_SHIFT;
+ cmd[1] = IPC_FUNC_READ << IPC_FUNC_SHIFT;
+
+ acpm_ipc_send_data_async(acpm, cmd[0], cmd[1]);
+ ret = acpm_ipc_wait_resp(acpm, &cmd[2], &cmd[3]);
+ if (ret)
+ return ret;
+
+ *val = (cmd[3] >> IPC_DEST_SHIFT) & IPC_DEST_MASK;
+ ret = (cmd[3] >> IPC_RETURN_SHIFT) & IPC_RETURN_MASK;
+ return ret;
+}
+
+/**
+ * acpm_i3c_write() - Write an I3C register of some I3C slave device
+ * @acpm: ACPM data
+ * @ch: I3C channel (bus) number (0-15)
+ * @addr: I3C address of slave device (0-15)
+ * @reg: Address of I3C register in the slave device to write into
+ * @val: Value to write
+ *
+ * Return: 0 on success or non-zero code on error (may be positive).
+ */
+int acpm_i3c_write(struct acpm *acpm, u8 ch, u8 addr, u8 reg, u8 val)
+{
+ u32 cmd[4] = { 0 };
+ u8 ret;
+
+ cmd[0] = (ch & IPC_CHANNEL_MASK) << IPC_CHANNEL_SHIFT |
+ (addr & IPC_TYPE_MASK) << IPC_TYPE_SHIFT |
+ (reg & IPC_REG_MASK) << IPC_REG_SHIFT;
+ cmd[1] = IPC_FUNC_WRITE << IPC_FUNC_SHIFT |
+ (val & IPC_WRITE_VAL_MASK) << IPC_WRITE_VAL_SHIFT;
+
+ acpm_ipc_send_data_async(acpm, cmd[0], cmd[1]);
+ ret = acpm_ipc_wait_resp(acpm, &cmd[2], &cmd[3]);
+ if (ret)
+ return ret;
+
+ ret = (cmd[3] >> IPC_RETURN_SHIFT) & IPC_RETURN_MASK;
+ return ret;
+}
diff --git a/board/samsung/e850-96/acpm.h b/board/samsung/e850-96/acpm.h
new file mode 100644
index 000000000000..9373969209fa
--- /dev/null
+++ b/board/samsung/e850-96/acpm.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2025 Linaro Ltd.
+ * Sam Protsenko <semen.protsenko at linaro.org>
+ */
+
+#ifndef __E850_96_ACPM_H
+#define __E850_96_ACPM_H
+
+#include <linux/types.h>
+
+/**
+ * struct acpm - Data for I3C communication over ACPM IPC protocol
+ * @mbox_base: Base address of APM mailbox block
+ * @sram_base: Base address of shared memory used for APM messages
+ * @ipc_ch: Mailbox channel number used for communication with I3C block (0-15)
+ */
+struct acpm {
+ void __iomem *mbox_base;
+ void __iomem *sram_base;
+ u8 ipc_ch;
+};
+
+int acpm_i3c_read(struct acpm *acpm, u8 ch, u8 addr, u8 reg, u8 *val);
+int acpm_i3c_write(struct acpm *acpm, u8 ch, u8 addr, u8 reg, u8 val);
+
+#endif /* __E850_96_ACPM_H */
--
2.39.5
More information about the U-Boot
mailing list