[PATCH 1/7] cache: ax45mp_cache: refactor cache driver for generic Andes platform support

Hui Min Mina Chou minachou at andestech.com
Mon Mar 30 03:27:18 PDT 2026


Andes cache driver is not only usable with the AX45MP CPU but can also be
applied to other CPU within Andes platform (such as A27L2).
To improve maintainability and support future SoCs, this patch performs a
comprehensive refactoring to move away from model-specific naming.

key changes include:
 - replaced AX45MP-specific Kconfig and function names with generic "ANDES"
   prefixes to support multiple CPU types
 - updated all L2-related identifiers, structs, and prefixes to "LLC"
   to accurately reflect its role as the system's last-level cache
 - moved UCCTL* CSR definitions to <linux/soc/andes/csr.h>
 - standardized L1D and LLC macro prefixes (ANDES_L1D_* and ANDES_LLC_*)
   for better clarity
 - renamed compatible strings from ax45mp-cache to generic llcache
 - rename ax45mp_cache.c to andes_llcache.c

This is a structural refactoring; no functional behavior is changed.

Signed-off-by: charles <dminus at andestech.com>
Signed-off-by: Hui Min Mina Chou <minachou at andestech.com>
---
 arch/riscv/Kconfig.errata     |   2 +-
 drivers/cache/Kconfig         |   6 +-
 drivers/cache/Makefile        |   2 +-
 drivers/cache/andes_llcache.c | 224 ++++++++++++++++++++++++++++++++++
 drivers/cache/ax45mp_cache.c  | 217 --------------------------------
 drivers/soc/renesas/Kconfig   |   2 +-
 include/linux/soc/andes/csr.h |  12 ++
 7 files changed, 242 insertions(+), 223 deletions(-)
 create mode 100644 drivers/cache/andes_llcache.c
 delete mode 100644 drivers/cache/ax45mp_cache.c
 create mode 100644 include/linux/soc/andes/csr.h

diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata
index 3c945d086c7d..e32f1563ce3a 100644
--- a/arch/riscv/Kconfig.errata
+++ b/arch/riscv/Kconfig.errata
@@ -1,7 +1,7 @@
 menu "CPU errata selection"
 
 config ERRATA_ANDES
-	bool "Andes AX45MP errata"
+	bool "Andes errata"
 	depends on RISCV_ALTERNATIVE && RISCV_SBI
 	help
 	  All Andes errata Kconfig depend on this Kconfig. Disabling
diff --git a/drivers/cache/Kconfig b/drivers/cache/Kconfig
index 1518449d47b5..78142189f45c 100644
--- a/drivers/cache/Kconfig
+++ b/drivers/cache/Kconfig
@@ -10,11 +10,11 @@ menuconfig CACHEMAINT_FOR_DMA
 
 if CACHEMAINT_FOR_DMA
 
-config AX45MP_L2_CACHE
-	bool "Andes Technology AX45MP L2 Cache controller"
+config ANDES_CACHE
+	bool "Andes platform CPUs Cache controller"
 	select RISCV_NONSTANDARD_CACHE_OPS
 	help
-	  Support for the L2 cache controller on Andes Technology AX45MP platforms.
+	  Support for the L1 and LLC (last level cache) controller on Andes platform CPUs.
 
 config SIFIVE_CCACHE
 	bool "Sifive Composable Cache controller"
diff --git a/drivers/cache/Makefile b/drivers/cache/Makefile
index b3362b15d6c1..4a218ad6cec0 100644
--- a/drivers/cache/Makefile
+++ b/drivers/cache/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 
-obj-$(CONFIG_AX45MP_L2_CACHE)		+= ax45mp_cache.o
+obj-$(CONFIG_ANDES_CACHE)		+= andes_llcache.o
 obj-$(CONFIG_SIFIVE_CCACHE)		+= sifive_ccache.o
 obj-$(CONFIG_STARFIVE_STARLINK_CACHE)	+= starfive_starlink_cache.o
 
diff --git a/drivers/cache/andes_llcache.c b/drivers/cache/andes_llcache.c
new file mode 100644
index 000000000000..d5e382f3c801
--- /dev/null
+++ b/drivers/cache/andes_llcache.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * non-coherent cache operations for Andes Platform CPUs.
+ *
+ * Copyright (C) 2023 Renesas Electronics Corp.
+ */
+
+#include <linux/cacheflush.h>
+#include <linux/cacheinfo.h>
+#include <linux/dma-direction.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/soc/andes/csr.h>
+
+#include <asm/dma-noncoherent.h>
+
+/* L1 D-cache operation encoding */
+#define ANDES_L1D_CCTL_VA_INVAL			0x0	/* Invalidate an L1D cacheline */
+#define ANDES_L1D_CCTL_VA_WB			0x1	/* Write-back an L1D cacheline */
+#define ANDES_L1D_CCTL_VA_WBINVAL		0x2	/* Flush an L1D cacheline */
+#define ANDES_L1D_CCTL_WBINVAL_ALL		0x6	/* Flush the entire L1D cache */
+
+/* LLC registers */
+#define ANDES_LLC_REG_CFG_OFFSET		0x0
+#define ANDES_LLC_REG_CTRL_OFFSET		0x8
+#define ANDES_LLC_REG_ASYNC_ERR_OFFSET		0x30
+#define ANDES_LLC_REG_ERR_OFFSET		0x38
+#define ANDES_LLC_REG_CCTL_CMD_OFFSET_C0	0x40
+#define ANDES_LLC_REG_CCTL_ACC_OFFSET_C0	0x48
+#define ANDES_LLC_REG_CCTL_STATUS_OFFSET_C0	0x80
+
+/* LLC CCTL status encoding */
+#define ANDES_LLC_CCTL_STATUS_IDLE		0x0
+#define ANDES_LLC_CCTL_STATUS_RUNNING		0x1
+#define ANDES_LLC_CCTL_STATUS_ILLEGAL		0x2
+
+/* LLC CCTL status core 0 mask */
+#define ANDES_LLC_CCTL_STATUS_MASK_C0		GENMASK(3, 0)
+
+/* LLC operation encoding */
+#define ANDES_LLC_CCTL_PA_INVAL			0x8	/* Invalidate an LLC cacheline */
+#define ANDES_LLC_CCTL_PA_WB			0x9	/* Write-back an LLC cacheline */
+#define ANDES_LLC_CCTL_PA_WBINVAL		0xa	/* Flush an LLC cacheline */
+#define ANDES_LLC_CCTL_WBINVAL_ALL		0x12	/* Flush the entire LLC cache */
+
+/* LLC CCTL registers and fields by core */
+#define ANDES_LLC_REG_PER_CORE_OFFSET		0x10
+#define ANDES_CCTL_LLC_STATUS_PER_CORE_OFFSET	0x4
+
+#define ANDES_LLC_REG_CCTL_CMD_OFFSET_BY_CORE(n)	\
+	(ANDES_LLC_REG_CCTL_CMD_OFFSET_C0 + ((n) * ANDES_LLC_REG_PER_CORE_OFFSET))
+#define ANDES_LLC_REG_CCTL_ACC_OFFSET_BY_CORE(n)	\
+	(ANDES_LLC_REG_CCTL_ACC_OFFSET_C0 + ((n) * ANDES_LLC_REG_PER_CORE_OFFSET))
+#define ANDES_LLC_CCTL_STATUS_MASK_BY_CORE(n)	\
+	(ANDES_LLC_CCTL_STATUS_MASK_C0 << ((n) * ANDES_CCTL_LLC_STATUS_PER_CORE_OFFSET))
+
+#define ANDES_CACHE_LINE_SIZE			64
+
+struct andes_priv {
+	void __iomem *llc_base;
+	u32 andes_cache_line_size;
+};
+
+static struct andes_priv andes_priv;
+
+/* LLC operations */
+static inline uint32_t andes_cpu_llc_get_cctl_status(void)
+{
+	return readl(andes_priv.llc_base + ANDES_LLC_REG_CCTL_STATUS_OFFSET_C0);
+}
+
+static void andes_cpu_cache_operation(unsigned long start, unsigned long end,
+				       unsigned int l1_op, unsigned int llc_op)
+{
+	unsigned long line_size = andes_priv.andes_cache_line_size;
+	void __iomem *base = andes_priv.llc_base;
+	int mhartid = smp_processor_id();
+	unsigned long pa;
+
+	while (end > start) {
+		csr_write(CSR_UCCTLBEGINADDR, start);
+		csr_write(CSR_UCCTLCOMMAND, l1_op);
+
+		pa = virt_to_phys((void *)start);
+		writel(pa, base + ANDES_LLC_REG_CCTL_ACC_OFFSET_BY_CORE(mhartid));
+		writel(llc_op, base + ANDES_LLC_REG_CCTL_CMD_OFFSET_BY_CORE(mhartid));
+		while ((andes_cpu_llc_get_cctl_status() &
+			ANDES_LLC_CCTL_STATUS_MASK_BY_CORE(mhartid)) !=
+			ANDES_LLC_CCTL_STATUS_IDLE)
+			;
+
+		start += line_size;
+	}
+}
+
+/* Write-back L1 and LLC entry */
+static inline void andes_cpu_dcache_wb_range(unsigned long start, unsigned long end)
+{
+	andes_cpu_cache_operation(start, end, ANDES_L1D_CCTL_VA_WB,
+				   ANDES_LLC_CCTL_PA_WB);
+}
+
+/* Invalidate the L1 and LLC entry */
+static inline void andes_cpu_dcache_inval_range(unsigned long start, unsigned long end)
+{
+	andes_cpu_cache_operation(start, end, ANDES_L1D_CCTL_VA_INVAL,
+				   ANDES_LLC_CCTL_PA_INVAL);
+}
+
+static void andes_dma_cache_inv(phys_addr_t paddr, size_t size)
+{
+	unsigned long start = (unsigned long)phys_to_virt(paddr);
+	unsigned long end = start + size;
+	unsigned long line_size;
+	unsigned long flags;
+
+	if (unlikely(start == end))
+		return;
+
+	line_size = andes_priv.andes_cache_line_size;
+
+	start = start & (~(line_size - 1));
+	end = ((end + line_size - 1) & (~(line_size - 1)));
+
+	local_irq_save(flags);
+
+	andes_cpu_dcache_inval_range(start, end);
+
+	local_irq_restore(flags);
+}
+
+static void andes_dma_cache_wback(phys_addr_t paddr, size_t size)
+{
+	unsigned long start = (unsigned long)phys_to_virt(paddr);
+	unsigned long end = start + size;
+	unsigned long line_size;
+	unsigned long flags;
+
+	if (unlikely(start == end))
+		return;
+
+	line_size = andes_priv.andes_cache_line_size;
+	start = start & (~(line_size - 1));
+	end = ((end + line_size - 1) & (~(line_size - 1)));
+	local_irq_save(flags);
+	andes_cpu_dcache_wb_range(start, end);
+	local_irq_restore(flags);
+}
+
+static void andes_dma_cache_wback_inv(phys_addr_t paddr, size_t size)
+{
+	andes_dma_cache_wback(paddr, size);
+	andes_dma_cache_inv(paddr, size);
+}
+
+static int andes_get_llc_line_size(struct device_node *np)
+{
+	int ret;
+
+	ret = of_property_read_u32(np, "cache-line-size", &andes_priv.andes_cache_line_size);
+	if (ret) {
+		pr_err("Failed to get cache-line-size, defaulting to 64 bytes\n");
+		return ret;
+	}
+
+	if (andes_priv.andes_cache_line_size != ANDES_CACHE_LINE_SIZE) {
+		pr_err("Expected cache-line-size to be 64 bytes (found:%u)\n",
+		       andes_priv.andes_cache_line_size);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct riscv_nonstd_cache_ops andes_cmo_ops __initconst = {
+	.wback = &andes_dma_cache_wback,
+	.inv = &andes_dma_cache_inv,
+	.wback_inv = &andes_dma_cache_wback_inv,
+};
+
+static const struct of_device_id andes_cache_ids[] = {
+	{ .compatible = "andestech,llcache" },
+	{ /* sentinel */ }
+};
+
+static int __init andes_cache_init(void)
+{
+	struct resource res;
+	int ret;
+
+	struct device_node *np __free(device_node) =
+		of_find_matching_node(NULL, andes_cache_ids);
+	if (!of_device_is_available(np))
+		return -ENODEV;
+
+	ret = of_address_to_resource(np, 0, &res);
+	if (ret)
+		return ret;
+
+	/*
+	 * If IOCP is present on the Andes AX45MP core riscv_cbom_block_size
+	 * will be 0 for sure, so we can definitely rely on it. If
+	 * riscv_cbom_block_size = 0 we don't need to handle CMO using SW any
+	 * more so we just return success here and only if its being set we
+	 * continue further in the probe path.
+	 */
+	if (!riscv_cbom_block_size)
+		return 0;
+
+	andes_priv.llc_base = ioremap(res.start, resource_size(&res));
+	if (!andes_priv.llc_base)
+		return -ENOMEM;
+
+	ret = andes_get_llc_line_size(np);
+	if (ret) {
+		iounmap(andes_priv.llc_base);
+		return ret;
+	}
+
+	riscv_noncoherent_register_cache_ops(&andes_cmo_ops);
+
+	return 0;
+}
+early_initcall(andes_cache_init);
diff --git a/drivers/cache/ax45mp_cache.c b/drivers/cache/ax45mp_cache.c
deleted file mode 100644
index 934c5087ec2b..000000000000
--- a/drivers/cache/ax45mp_cache.c
+++ /dev/null
@@ -1,217 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * non-coherent cache functions for Andes AX45MP
- *
- * Copyright (C) 2023 Renesas Electronics Corp.
- */
-
-#include <linux/cacheflush.h>
-#include <linux/cacheinfo.h>
-#include <linux/dma-direction.h>
-#include <linux/of_address.h>
-#include <linux/of_platform.h>
-
-#include <asm/dma-noncoherent.h>
-
-/* L2 cache registers */
-#define AX45MP_L2C_REG_CTL_OFFSET		0x8
-
-#define AX45MP_L2C_REG_C0_CMD_OFFSET		0x40
-#define AX45MP_L2C_REG_C0_ACC_OFFSET		0x48
-#define AX45MP_L2C_REG_STATUS_OFFSET		0x80
-
-/* D-cache operation */
-#define AX45MP_CCTL_L1D_VA_INVAL		0 /* Invalidate an L1 cache entry */
-#define AX45MP_CCTL_L1D_VA_WB			1 /* Write-back an L1 cache entry */
-
-/* L2 CCTL status */
-#define AX45MP_CCTL_L2_STATUS_IDLE		0
-
-/* L2 CCTL status cores mask */
-#define AX45MP_CCTL_L2_STATUS_C0_MASK		0xf
-
-/* L2 cache operation */
-#define AX45MP_CCTL_L2_PA_INVAL			0x8 /* Invalidate an L2 cache entry */
-#define AX45MP_CCTL_L2_PA_WB			0x9 /* Write-back an L2 cache entry */
-
-#define AX45MP_L2C_REG_PER_CORE_OFFSET		0x10
-#define AX45MP_CCTL_L2_STATUS_PER_CORE_OFFSET	4
-
-#define AX45MP_L2C_REG_CN_CMD_OFFSET(n)	\
-	(AX45MP_L2C_REG_C0_CMD_OFFSET + ((n) * AX45MP_L2C_REG_PER_CORE_OFFSET))
-#define AX45MP_L2C_REG_CN_ACC_OFFSET(n)	\
-	(AX45MP_L2C_REG_C0_ACC_OFFSET + ((n) * AX45MP_L2C_REG_PER_CORE_OFFSET))
-#define AX45MP_CCTL_L2_STATUS_CN_MASK(n)	\
-	(AX45MP_CCTL_L2_STATUS_C0_MASK << ((n) * AX45MP_CCTL_L2_STATUS_PER_CORE_OFFSET))
-
-#define AX45MP_CCTL_REG_UCCTLBEGINADDR_NUM	0x80b
-#define AX45MP_CCTL_REG_UCCTLCOMMAND_NUM	0x80c
-
-#define AX45MP_CACHE_LINE_SIZE			64
-
-struct ax45mp_priv {
-	void __iomem *l2c_base;
-	u32 ax45mp_cache_line_size;
-};
-
-static struct ax45mp_priv ax45mp_priv;
-
-/* L2 Cache operations */
-static inline uint32_t ax45mp_cpu_l2c_get_cctl_status(void)
-{
-	return readl(ax45mp_priv.l2c_base + AX45MP_L2C_REG_STATUS_OFFSET);
-}
-
-static void ax45mp_cpu_cache_operation(unsigned long start, unsigned long end,
-				       unsigned int l1_op, unsigned int l2_op)
-{
-	unsigned long line_size = ax45mp_priv.ax45mp_cache_line_size;
-	void __iomem *base = ax45mp_priv.l2c_base;
-	int mhartid = smp_processor_id();
-	unsigned long pa;
-
-	while (end > start) {
-		csr_write(AX45MP_CCTL_REG_UCCTLBEGINADDR_NUM, start);
-		csr_write(AX45MP_CCTL_REG_UCCTLCOMMAND_NUM, l1_op);
-
-		pa = virt_to_phys((void *)start);
-		writel(pa, base + AX45MP_L2C_REG_CN_ACC_OFFSET(mhartid));
-		writel(l2_op, base + AX45MP_L2C_REG_CN_CMD_OFFSET(mhartid));
-		while ((ax45mp_cpu_l2c_get_cctl_status() &
-			AX45MP_CCTL_L2_STATUS_CN_MASK(mhartid)) !=
-			AX45MP_CCTL_L2_STATUS_IDLE)
-			;
-
-		start += line_size;
-	}
-}
-
-/* Write-back L1 and L2 cache entry */
-static inline void ax45mp_cpu_dcache_wb_range(unsigned long start, unsigned long end)
-{
-	ax45mp_cpu_cache_operation(start, end, AX45MP_CCTL_L1D_VA_WB,
-				   AX45MP_CCTL_L2_PA_WB);
-}
-
-/* Invalidate the L1 and L2 cache entry */
-static inline void ax45mp_cpu_dcache_inval_range(unsigned long start, unsigned long end)
-{
-	ax45mp_cpu_cache_operation(start, end, AX45MP_CCTL_L1D_VA_INVAL,
-				   AX45MP_CCTL_L2_PA_INVAL);
-}
-
-static void ax45mp_dma_cache_inv(phys_addr_t paddr, size_t size)
-{
-	unsigned long start = (unsigned long)phys_to_virt(paddr);
-	unsigned long end = start + size;
-	unsigned long line_size;
-	unsigned long flags;
-
-	if (unlikely(start == end))
-		return;
-
-	line_size = ax45mp_priv.ax45mp_cache_line_size;
-
-	start = start & (~(line_size - 1));
-	end = ((end + line_size - 1) & (~(line_size - 1)));
-
-	local_irq_save(flags);
-
-	ax45mp_cpu_dcache_inval_range(start, end);
-
-	local_irq_restore(flags);
-}
-
-static void ax45mp_dma_cache_wback(phys_addr_t paddr, size_t size)
-{
-	unsigned long start = (unsigned long)phys_to_virt(paddr);
-	unsigned long end = start + size;
-	unsigned long line_size;
-	unsigned long flags;
-
-	if (unlikely(start == end))
-		return;
-
-	line_size = ax45mp_priv.ax45mp_cache_line_size;
-	start = start & (~(line_size - 1));
-	end = ((end + line_size - 1) & (~(line_size - 1)));
-	local_irq_save(flags);
-	ax45mp_cpu_dcache_wb_range(start, end);
-	local_irq_restore(flags);
-}
-
-static void ax45mp_dma_cache_wback_inv(phys_addr_t paddr, size_t size)
-{
-	ax45mp_dma_cache_wback(paddr, size);
-	ax45mp_dma_cache_inv(paddr, size);
-}
-
-static int ax45mp_get_l2_line_size(struct device_node *np)
-{
-	int ret;
-
-	ret = of_property_read_u32(np, "cache-line-size", &ax45mp_priv.ax45mp_cache_line_size);
-	if (ret) {
-		pr_err("Failed to get cache-line-size, defaulting to 64 bytes\n");
-		return ret;
-	}
-
-	if (ax45mp_priv.ax45mp_cache_line_size != AX45MP_CACHE_LINE_SIZE) {
-		pr_err("Expected cache-line-size to be 64 bytes (found:%u)\n",
-		       ax45mp_priv.ax45mp_cache_line_size);
-		return -EINVAL;
-	}
-
-	return 0;
-}
-
-static const struct riscv_nonstd_cache_ops ax45mp_cmo_ops __initdata = {
-	.wback = &ax45mp_dma_cache_wback,
-	.inv = &ax45mp_dma_cache_inv,
-	.wback_inv = &ax45mp_dma_cache_wback_inv,
-};
-
-static const struct of_device_id ax45mp_cache_ids[] = {
-	{ .compatible = "andestech,ax45mp-cache" },
-	{ /* sentinel */ }
-};
-
-static int __init ax45mp_cache_init(void)
-{
-	struct resource res;
-	int ret;
-
-	struct device_node *np __free(device_node) =
-		of_find_matching_node(NULL, ax45mp_cache_ids);
-	if (!of_device_is_available(np))
-		return -ENODEV;
-
-	ret = of_address_to_resource(np, 0, &res);
-	if (ret)
-		return ret;
-
-	/*
-	 * If IOCP is present on the Andes AX45MP core riscv_cbom_block_size
-	 * will be 0 for sure, so we can definitely rely on it. If
-	 * riscv_cbom_block_size = 0 we don't need to handle CMO using SW any
-	 * more so we just return success here and only if its being set we
-	 * continue further in the probe path.
-	 */
-	if (!riscv_cbom_block_size)
-		return 0;
-
-	ax45mp_priv.l2c_base = ioremap(res.start, resource_size(&res));
-	if (!ax45mp_priv.l2c_base)
-		return -ENOMEM;
-
-	ret = ax45mp_get_l2_line_size(np);
-	if (ret) {
-		iounmap(ax45mp_priv.l2c_base);
-		return ret;
-	}
-
-	riscv_noncoherent_register_cache_ops(&ax45mp_cmo_ops);
-
-	return 0;
-}
-early_initcall(ax45mp_cache_init);
diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig
index 1e50dc7c31cd..e0319c8236ee 100644
--- a/drivers/soc/renesas/Kconfig
+++ b/drivers/soc/renesas/Kconfig
@@ -447,7 +447,7 @@ config ARCH_R9A07G043
 	depends on !RISCV_ISA_ZICBOM
 	depends on RISCV_SBI
 	select ARCH_RZG2L
-	select AX45MP_L2_CACHE
+	select ANDES_CACHE
 	select CACHEMAINT_FOR_DMA
 	select DMA_GLOBAL_POOL
 	select ERRATA_ANDES
diff --git a/include/linux/soc/andes/csr.h b/include/linux/soc/andes/csr.h
new file mode 100644
index 000000000000..3214b4b08a46
--- /dev/null
+++ b/include/linux/soc/andes/csr.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 Andes Technology Corporation.
+ */
+#ifndef __LINUX_SOC_ANDES_CSR_H
+#define __LINUX_SOC_ANDES_CSR_H
+
+/* User mode control registers */
+#define CSR_UCCTLBEGINADDR		0x80b
+#define CSR_UCCTLCOMMAND		0x80c
+
+#endif /* !__LINUX_SOC_ANDES_CSR_H */
-- 
2.34.1




More information about the linux-riscv mailing list