[PATCH 4/5] lib: utils/cache: add Andes last level cache controller

Ben Zong-You Xie ben717 at andestech.com
Sun Dec 28 23:19:13 PST 2025


Introduce a FDT-based driver for the Andes Last Level Cache (LLC)
controller to support cache maintenance operations.

Signed-off-by: Ben Zong-You Xie <ben717 at andestech.com>
---
 include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h |  15 ++
 lib/utils/cache/Kconfig                      |   5 +
 lib/utils/cache/fdt_andes_llcache.c          | 166 +++++++++++++++++++
 lib/utils/cache/objects.mk                   |   3 +
 lib/utils/hsm/fdt_hsm_andes_atcsmu.c         |  10 ++
 platform/generic/configs/defconfig           |   1 +
 6 files changed, 200 insertions(+)
 create mode 100644 lib/utils/cache/fdt_andes_llcache.c

diff --git a/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h b/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
index 881d8b215060..ef851b7501b5 100644
--- a/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
+++ b/include/sbi_utils/hsm/fdt_hsm_andes_atcsmu.h
@@ -11,6 +11,8 @@
 
 /* clang-format off */
 
+#define SCRATCH_PAD_OFFSET		0x40
+
 #define RESET_VEC_LO_OFFSET		0x50
 #define RESET_VEC_HI_OFFSET		0x60
 #define RESET_VEC_8CORE_OFFSET		0x1a0
@@ -31,6 +33,9 @@
 
 #define PCS0_WE_OFFSET			0x90
 #define PCSm_WE_OFFSET(i)		((i + 3) * 0x20 + PCS0_WE_OFFSET)
+#define PCS_WAKEUP_RTC_ALARM_MASK	BIT(2)
+#define PCS_WAKEUP_UART2_MASK		BIT(9)
+#define PCS_WAKEUP_MSIP_MASK		BIT(29)
 
 #define PCS0_CTL_OFFSET			0x94
 #define PCSm_CTL_OFFSET(i)		((i + 3) * 0x20 + PCS0_CTL_OFFSET)
@@ -38,6 +43,14 @@
 #define WAKEUP_CMD			0x8
 #define DEEP_SLEEP_CMD			0xb
 
+#define PCS0_STATUS_OFFSET		0x98
+#define PCSm_STATUS_OFFSET(i)		((i + 3) * 0x20 + PCS0_STATUS_OFFSET)
+#define PD_TYPE_MASK			GENMASK(2, 0)
+#define PD_TYPE_SLEEP			2
+#define PD_STATUS_MASK			GENMASK(7, 3)
+#define PD_STATUS_LIGHT_SLEEP		0
+#define PD_STATUS_DEEP_SLEEP		0x10
+
 /* clang-format on */
 
 void atcsmu_set_wakeup_events(u32 events, u32 hartid);
@@ -45,5 +58,7 @@ bool atcsmu_support_sleep_mode(u32 sleep_type, u32 hartid);
 void atcsmu_set_command(u32 pcs_ctl, u32 hartid);
 int atcsmu_set_reset_vector(u64 wakeup_addr, u32 hartid);
 u32 atcsmu_get_sleep_type(u32 hartid);
+void atcsmu_write_scratch(u32 value);
+u32 atcsmu_read_scratch(void);
 
 #endif
diff --git a/lib/utils/cache/Kconfig b/lib/utils/cache/Kconfig
index be7d57c37d24..ea815dc4ef03 100644
--- a/lib/utils/cache/Kconfig
+++ b/lib/utils/cache/Kconfig
@@ -10,6 +10,11 @@ config FDT_CACHE
 
 if FDT_CACHE
 
+config FDT_CACHE_ANDES_LLCACHE
+	bool "Andes FDT last level cache driver"
+	depends on FDT_HSM_ANDES_ATCSMU
+	default n
+
 config FDT_CACHE_SIFIVE_CCACHE
 	bool "SiFive CCACHE FDT cache driver"
 	default n
diff --git a/lib/utils/cache/fdt_andes_llcache.c b/lib/utils/cache/fdt_andes_llcache.c
new file mode 100644
index 000000000000..b3619c8fd83d
--- /dev/null
+++ b/lib/utils/cache/fdt_andes_llcache.c
@@ -0,0 +1,166 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Andes Technology Corporation
+ */
+
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_heap.h>
+#include <sbi_utils/cache/fdt_cache.h>
+#include <sbi_utils/fdt/fdt_driver.h>
+#include <sbi_utils/hsm/fdt_hsm_andes_atcsmu.h>
+
+#define LLCACHE_REG_CFG_OFFSET		0x0
+#define LLCACHE_REG_CTRL_OFFSET		0x8
+#define LLCACHE_REG_CCTL_CMD_OFFSET	0x40
+#define LLCACHE_REG_CCTL_STATUS_OFFSET	0x80
+
+#define LLCACHE_REG_CFG_MAP_MASK	BIT(20)
+#define LLCACHE_REG_CTRL_EN_MASK	BIT(0)
+#define LLCACHE_REG_CTRL_INIT_MASK	BIT(14)
+#define LLCACHE_REG_CCTL_STATUS_MASK	GENMASK(3, 0)
+
+#define LLCACHE_WBINVAL_ALL		0x12
+
+struct andes_llcache {
+	struct cache_device dev;
+	void *base;
+	uint32_t cmd_stride;
+	uint32_t status_stride;
+	uint32_t status_core_stride;
+};
+
+#define to_llcache(_dev) container_of(_dev, struct andes_llcache, dev)
+
+static bool andes_llcache_init_done(struct andes_llcache *llcache)
+{
+	uint32_t llcache_ctrl;
+	void *ctrl_addr = (char *)llcache->base + LLCACHE_REG_CTRL_OFFSET;
+
+	llcache_ctrl = readl_relaxed(ctrl_addr);
+	return !EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_INIT_MASK);
+}
+
+static bool andes_llcache_cctl_done(struct andes_llcache *llcache, uint32_t hartid)
+{
+	uint32_t llcache_cctl_status;
+	void *cctl_status_addr = (char *)llcache->base + LLCACHE_REG_CCTL_STATUS_OFFSET +
+				 hartid * llcache->status_stride;
+
+	llcache_cctl_status = readl_relaxed(cctl_status_addr);
+	return !EXTRACT_FIELD(llcache_cctl_status,
+			      LLCACHE_REG_CCTL_STATUS_MASK <<
+			      hartid * llcache->status_core_stride);
+}
+
+static int andes_llcache_flush_all(struct cache_device *dev)
+{
+	uint32_t hartid = current_hartid();
+	struct andes_llcache *llcache = to_llcache(dev);
+	void *cctl_cmd_addr = (char *)llcache->base + LLCACHE_REG_CCTL_CMD_OFFSET +
+			      hartid * llcache->cmd_stride;
+
+	/*
+	 * Each command register corresponds to one CPU core, so each CPU core
+	 * should only use its command registers to do the cache operation.
+	 */
+	writel(LLCACHE_WBINVAL_ALL, cctl_cmd_addr);
+
+	/* Wait for the command completion */
+	while (!andes_llcache_cctl_done(llcache, hartid))
+		;
+
+	return 0;
+}
+
+static int andes_llcache_enable(struct cache_device *dev, bool enable)
+{
+	struct andes_llcache *llcache = to_llcache(dev);
+	u32 llcache_ctrl;
+	void *ctrl_addr = (char *)llcache->base + LLCACHE_REG_CTRL_OFFSET;
+
+	/*
+	 * To properly enable the last level cache to cache both instructions
+	 * and data, apply the following sequence:
+	 *
+	 * - Write the control register with the desired value, except the
+	 *   CEN field should be set to zero. Thus, store the control register
+	 *   value with the CEN field being 0 when disabling the last level
+	 *   cache.
+	 * - Write the control register again using the same value of step 1
+	 *   with the CEN field being 1.
+	 */
+	if (enable) {
+		llcache_ctrl = atcsmu_read_scratch();
+		writel(llcache_ctrl, ctrl_addr);
+		writel(llcache_ctrl | LLCACHE_REG_CTRL_EN_MASK, ctrl_addr);
+	} else {
+		llcache_ctrl = readl(ctrl_addr);
+		atcsmu_write_scratch(llcache_ctrl & ~LLCACHE_REG_CTRL_EN_MASK);
+		writel(llcache_ctrl & ~LLCACHE_REG_CTRL_EN_MASK, ctrl_addr);
+	}
+
+	llcache_ctrl = readl(ctrl_addr);
+	return enable == EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_EN_MASK);
+}
+
+static struct cache_ops andes_llcache_ops = {
+	.cache_flush_all = andes_llcache_flush_all,
+	.cache_enable = andes_llcache_enable,
+};
+
+static int andes_llcache_probe(const void *fdt, int nodeoff, const struct fdt_match *match)
+{
+	int rc;
+	u64 llcache_base = 0;
+	struct andes_llcache *llcache;
+	struct cache_device *dev;
+	uint32_t llcache_cfg;
+
+	rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &llcache_base, NULL);
+	if (rc < 0 || !llcache_base)
+		return SBI_ENODEV;
+
+	llcache = sbi_zalloc(sizeof(*llcache));
+	if (!llcache)
+		return SBI_ENOMEM;
+
+	dev = &llcache->dev;
+	dev->ops = &andes_llcache_ops;
+	rc = fdt_cache_add(fdt, nodeoff, dev);
+	if (rc) {
+		sbi_free(llcache);
+		return rc;
+	}
+
+	llcache->base = (void *)llcache_base;
+	llcache_cfg = readl_relaxed((char *)llcache->base + LLCACHE_REG_CFG_OFFSET);
+
+	/* Configurations for V1/V0 memory map */
+	if (EXTRACT_FIELD(llcache_cfg, LLCACHE_REG_CFG_MAP_MASK)) {
+		llcache->cmd_stride = 0x1000;
+		llcache->status_stride = 0x1000;
+		llcache->status_core_stride = 0;
+	} else {
+		llcache->cmd_stride = 0x10;
+		llcache->status_stride = 0x0;
+		llcache->status_core_stride = 4;
+	}
+
+	/* Wait for the hardware initialization done */
+	while (!andes_llcache_init_done(llcache))
+		;
+
+	return SBI_OK;
+}
+
+static const struct fdt_match andes_llcache_match[] = {
+	{ .compatible = "andestech,llcache" },
+	{},
+};
+
+const struct fdt_driver fdt_andes_llcache = {
+	.match_table = andes_llcache_match,
+	.init = andes_llcache_probe,
+};
diff --git a/lib/utils/cache/objects.mk b/lib/utils/cache/objects.mk
index aa76adc2e3bc..6c9bce84903e 100644
--- a/lib/utils/cache/objects.mk
+++ b/lib/utils/cache/objects.mk
@@ -8,6 +8,9 @@ libsbiutils-objs-$(CONFIG_FDT_CACHE) += cache/fdt_cache.o
 libsbiutils-objs-$(CONFIG_FDT_CACHE) += cache/fdt_cache_drivers.carray.o
 libsbiutils-objs-$(CONFIG_FDT_CACHE) += cache/fdt_cmo_helper.o
 
+carray-fdt_cache_drivers-$(CONFIG_FDT_CACHE_ANDES_LLCACHE) += fdt_andes_llcache
+libsbiutils-objs-$(CONFIG_FDT_CACHE_ANDES_LLCACHE) += cache/fdt_andes_llcache.o
+
 carray-fdt_cache_drivers-$(CONFIG_FDT_CACHE_SIFIVE_CCACHE) += fdt_sifive_ccache
 libsbiutils-objs-$(CONFIG_FDT_CACHE_SIFIVE_CCACHE) += cache/fdt_sifive_ccache.o
 
diff --git a/lib/utils/hsm/fdt_hsm_andes_atcsmu.c b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
index 3fe931aa1816..32f6fef808c6 100644
--- a/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
+++ b/lib/utils/hsm/fdt_hsm_andes_atcsmu.c
@@ -84,6 +84,16 @@ u32 atcsmu_get_sleep_type(u32 hartid)
 	return readl_relaxed((char *)atcsmu_base + PCSm_SCRATCH_OFFSET(hartid));
 }
 
+void atcsmu_write_scratch(u32 value)
+{
+	writel_relaxed(value, (char *)atcsmu_base + SCRATCH_PAD_OFFSET);
+}
+
+u32 atcsmu_read_scratch(void)
+{
+	return readl_relaxed((char *)atcsmu_base + SCRATCH_PAD_OFFSET);
+}
+
 static int ae350_hart_start(u32 hartid, ulong saddr)
 {
 	u32 hartindex = sbi_hartid_to_hartindex(hartid);
diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig
index d85da930edbc..4b7615d6c51b 100644
--- a/platform/generic/configs/defconfig
+++ b/platform/generic/configs/defconfig
@@ -13,6 +13,7 @@ CONFIG_PLATFORM_THEAD=y
 CONFIG_PLATFORM_MIPS_P8700=y
 CONFIG_PLATFORM_SPACEMIT_K1=y
 CONFIG_FDT_CACHE=y
+CONFIG_FDT_CACHE_ANDES_LLCACHE=y
 CONFIG_FDT_CACHE_SIFIVE_CCACHE=y
 CONFIG_FDT_CACHE_SIFIVE_EC=y
 CONFIG_FDT_CACHE_SIFIVE_PL2=y
-- 
2.34.1




More information about the opensbi mailing list