[PATCH v5 8/8] riscv: p8700: Add Coherence Manager (CM) and IOCU support

Uros Stajic
2025-12-24T16:42:29Z

From: Chao-ying Fu <cfu@mips.com>

Add support for Coherence Manager (CM) and IOCU discovery and
configuration on the P8700 platform.

Signed-off-by: Chao-ying Fu <cfu@mips.com>
Signed-off-by: Uros Stajic <uros.stajic@htecgroup.com>
---
arch/riscv/cpu/p8700/Makefile | 3 +
arch/riscv/cpu/p8700/cache.c | 6 ++
arch/riscv/cpu/p8700/cm-iocu.c | 75 ++++++++++++++++
arch/riscv/cpu/p8700/cm.c | 92 +++++++++++++++++++
arch/riscv/cpu/p8700/p8700.c | 21 +++++
arch/riscv/include/asm/arch-p8700/cm.h | 61 +++++++++++++
arch/riscv/include/asm/arch-p8700/p8700.h | 35 ++++++++
arch/riscv/include/asm/global_data.h | 4 +
board/mips/boston-riscv/Makefile | 1 +
board/mips/boston-riscv/iocu.c | 102 ++++++++++++++++++++++
10 files changed, 400 insertions(+)
create mode 100644 arch/riscv/cpu/p8700/cm-iocu.c
create mode 100644 arch/riscv/cpu/p8700/cm.c
create mode 100644 arch/riscv/cpu/p8700/p8700.c
create mode 100644 arch/riscv/include/asm/arch-p8700/cm.h
create mode 100644 board/mips/boston-riscv/iocu.c

diff --git a/arch/riscv/cpu/p8700/Makefile b/arch/riscv/cpu/p8700/Makefile
index 32c5e2dce21..45f68bbda3d 100644
--- a/arch/riscv/cpu/p8700/Makefile
+++ b/arch/riscv/cpu/p8700/Makefile
@@ -3,5 +3,8 @@
# Copyright (C) 2021, Chao-ying Fu <cfu@mips.com>

obj-y += cache.o
+obj-y += cm.o
+obj-y += cm-iocu.o
obj-y += cpu.o
obj-y += dram.o
+obj-y += p8700.o
diff --git a/arch/riscv/cpu/p8700/cache.c b/arch/riscv/cpu/p8700/cache.c
index 7559c688321..df6e6c9663e 100644
--- a/arch/riscv/cpu/p8700/cache.c
+++ b/arch/riscv/cpu/p8700/cache.c
@@ -48,6 +48,9 @@ static void probe_cache_config(void)

void flush_dcache_range(unsigned long start, unsigned long end)
{
+ if (p8700_dma_is_coherent())
+ return;
+
if (lsize == 0)
probe_cache_config();

@@ -71,6 +74,9 @@ void flush_dcache_range(unsigned long start, unsigned long end)

void invalidate_dcache_range(unsigned long start, unsigned long end)
{
+ if (p8700_dma_is_coherent())
+ return;
+
if (lsize == 0)
probe_cache_config();

diff --git a/arch/riscv/cpu/p8700/cm-iocu.c b/arch/riscv/cpu/p8700/cm-iocu.c
new file mode 100644
index 00000000000..5850647d20f
--- /dev/null
+++ b/arch/riscv/cpu/p8700/cm-iocu.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MIPS Coherence Manager (CM) Support
+ *
+ * Copyright (c) 2016 Imagination Technologies Ltd.
+ */
+#include <asm/io.h>
+#include <asm/global_data.h>
+#include <asm/arch-p8700/cm.h>
+
+__weak bool plat_iocu_usable(unsigned int cluster, unsigned int iocu)
+{
+ return true;
+}
+
+static int init_cluster_iocus(unsigned int cluster)
+{
+ DECLARE_GLOBAL_DATA_PTR;
+ void __iomem *global_gcrs;
+ u32 cfg, num_iocus, num_iocus_usable, local_cluster;
+ int i;
+
+ local_cluster = mips_cluster_id();
+
+ global_gcrs = mips_cm_base();
+ if (cluster != local_cluster) {
+ // FIXME
+ return 1;
+ }
+
+ cfg = __raw_readl(global_gcrs + GCR_CONFIG);
+ num_iocus = cfg >> GCR_CONFIG_NUMIOCU_SHIFT;
+ num_iocus &= GCR_CONFIG_NUMIOCU_MASK;
+ gd->arch.num_iocus += num_iocus;
+
+ /* Discover how many IOCUs are usable */
+ num_iocus_usable = num_iocus;
+ for (i = num_iocus - 1; i >= 0; i--) {
+ if (!plat_iocu_usable(cluster, i))
+ num_iocus_usable--;
+ }
+ gd->arch.num_iocus_usable += num_iocus_usable;
+
+ /* If the cluster has no usable IOCUs there's nothing to do */
+ if (num_iocus_usable == 0) {
+ if (cluster != local_cluster)
+ power_down_cluster(cluster);
+
+ return 0;
+ }
+
+ /* If the IOCUs are in the local cluster we're good to go already */
+ if (cluster == local_cluster)
+ return 0;
+
+ /* Ensure that the cluster's L2 cache is initialised */
+ return init_cluster_l2(cluster);
+}
+
+int mips_cm_init_iocus(void)
+{
+ DECLARE_GLOBAL_DATA_PTR;
+ unsigned int cluster;
+ int err;
+
+ for (cluster = 0; cluster < mips_cm_num_clusters(); cluster++) {
+ err = init_cluster_iocus(cluster);
+ if (err) {
+ gd->arch.num_iocus_usable = err;
+ return 0;
+ }
+ }
+
+ return 0;
+}
diff --git a/arch/riscv/cpu/p8700/cm.c b/arch/riscv/cpu/p8700/cm.c
new file mode 100644
index 00000000000..ce7485c564d
--- /dev/null
+++ b/arch/riscv/cpu/p8700/cm.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MIPS Coherence Manager (CM) Support
+ *
+ * Copyright (c) 2016 Imagination Technologies Ltd.
+ */
+
+#include <asm/io.h>
+#include <asm/arch-p8700/cm.h>
+#include <asm/arch-p8700/p8700.h>
+
+static void mips_cpc_init(void)
+{
+}
+
+__weak const struct mmio_region *get_mmio_regions(void)
+{
+ return NULL;
+}
+
+static void p8700_setup_mmio_limits(void)
+{
+ void __iomem *gcrs = mips_cm_base();
+ const struct mmio_region *rgn = get_mmio_regions();
+ unsigned int num_clusters = mips_cm_num_clusters();
+ unsigned int limit = MIPS_CM_MMIO_LIMIT / num_clusters;
+ unsigned int i, reg_off;
+
+ if (!rgn)
+ return;
+
+ if (num_clusters != 1) {
+ // FIXME! Need to support multiple clusters.
+ return;
+ }
+
+ reg_off = GCR_MMIO0_BOTTOM;
+
+ for (i = 0; rgn[i].addr_high; i++) {
+ __raw_writeq(rgn[i].addr_high & GCR_MMIO0_TOP_ADDR,
+ gcrs + reg_off + (GCR_MMIO0_TOP - GCR_MMIO0_BOTTOM));
+
+ __raw_writeq((rgn[i].addr_low & GCR_MMIO0_BOTTOM_ADDR) |
+ (rgn[i].port << GCR_MMIO0_BOTTOM_PORT_SHIFT) |
+ (rgn[i].enable ? GCR_MMIO0_BOTTOM_ENABLE : 0),
+ gcrs + reg_off);
+ reg_off += GCR_MMIO1_BOTTOM - GCR_MMIO0_BOTTOM;
+ }
+
+ __raw_writel(limit, gcrs + GCR_MMIO_REQ_LIMIT);
+}
+
+int power_up_cluster(unsigned int cluster)
+{
+ return 0;
+}
+
+int power_down_cluster(unsigned int cluster)
+{
+ return 0;
+}
+
+int init_cluster_l2(unsigned int cluster)
+{
+ return 0;
+}
+
+int mips_cm_init(void)
+{
+ int err;
+
+ mips_cpc_init();
+
+ err = mips_cm_init_iocus();
+ if (err)
+ return err;
+
+ p8700_setup_mmio_limits();
+
+ return 0;
+}
+
+int arch_cpu_init(void)
+{
+ int err;
+
+ err = mips_cm_init();
+ if (err)
+ return err;
+
+ return 0;
+}
diff --git a/arch/riscv/cpu/p8700/p8700.c b/arch/riscv/cpu/p8700/p8700.c
new file mode 100644
index 00000000000..18f30d10c19
--- /dev/null
+++ b/arch/riscv/cpu/p8700/p8700.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021, Chao-ying Fu <cfu@mips.com>
+ */
+
+#include <asm/io.h>
+#include <linux/types.h>
+#include <asm/arch-p8700/p8700.h>
+
+#define BOSTON_IOCU_NOC_OFFSET 0x10
+
+bool p8700_dma_is_coherent(void)
+{
+ u32 pcie0_off = readl((void __iomem *)BOSTON_PLAT_NOCPCIE0ADDR);
+ u32 pcie1_off = readl((void __iomem *)BOSTON_PLAT_NOCPCIE1ADDR);
+ u32 pcie2_off = readl((void __iomem *)BOSTON_PLAT_NOCPCIE2ADDR);
+
+ return pcie0_off == BOSTON_IOCU_NOC_OFFSET &&
+ pcie1_off == BOSTON_IOCU_NOC_OFFSET &&
+ pcie2_off == BOSTON_IOCU_NOC_OFFSET;
+}
diff --git a/arch/riscv/include/asm/arch-p8700/cm.h b/arch/riscv/include/asm/arch-p8700/cm.h
new file mode 100644
index 00000000000..ab5ea7385b2
--- /dev/null
+++ b/arch/riscv/include/asm/arch-p8700/cm.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021, Chao-ying Fu <cfu@mips.com>
+ */
+
+#ifndef __P8700_CM_H__
+#define __P8700_CM_H__
+
+#include <asm/arch-p8700/p8700.h>
+
+struct mmio_region {
+ phys_addr_t addr_low;
+ phys_addr_t addr_high;
+ unsigned int port : 4;
+ unsigned int enable : 1;
+};
+
+const struct mmio_region *get_mmio_regions(void);
+
+void setup_redirect(unsigned int cluster, unsigned int core,
+ unsigned int vp, unsigned int block);
+
+int mips_cm_init_iocus(void);
+
+int power_up_cluster(unsigned int cluster);
+int power_down_cluster(unsigned int cluster);
+int init_cluster_l2(unsigned int cluster);
+
+static inline void *mips_cm_base(void)
+{
+ return (void *)CM_BASE;
+}
+
+static inline void *mips_cpc_base(void)
+{
+ return (void *)CPC_BASE;
+}
+
+static inline unsigned int mips_cm_num_clusters(void)
+{
+ u32 cfg;
+
+ cfg = __raw_readl(mips_cm_base() + GCR_CONFIG);
+ cfg >>= GCR_CONFIG_NUMCLUSTERS_SHIFT;
+ cfg &= GCR_CONFIG_NUMCLUSTERS_MASK;
+
+ return cfg;
+}
+
+static inline unsigned int mips_cluster_id(void)
+{
+ u32 temp;
+
+ asm volatile("csrr %0, mhartid" : "=r"(temp));
+ temp >>= MHARTID_CLUSTER_SHIFT;
+ temp &= MHARTID_CLUSTER_MASK;
+
+ return temp;
+}
+
+#endif /* __P8700_CM_H__ */
diff --git a/arch/riscv/include/asm/arch-p8700/p8700.h b/arch/riscv/include/asm/arch-p8700/p8700.h
index 5ca9b4b9497..0b864afb635 100644
--- a/arch/riscv/include/asm/arch-p8700/p8700.h
+++ b/arch/riscv/include/asm/arch-p8700/p8700.h
@@ -65,6 +65,10 @@
#define CM_BASE 0x16100000
#define CPC_BASE (CM_BASE + 0x8000)

+/* Block offsets */
+#define GCR_OFF_GLOBAL 0x0000
+#define GCR_OFF_LOCAL 0x2000
+
/* CPC Block offsets */
#define CPC_OFF_LOCAL 0x2000

@@ -75,6 +79,33 @@
#define CPC_Cx_CMD 0x0000
#define CPC_Cx_CMD_RESET 0x4

+/* GCR_CONFIG */
+#define GCR_CONFIG 0x0000
+#define GCR_REV 0x0030
+#define GCR_CONFIG_NUMCLUSTERS_SHIFT 23
+#define GCR_CONFIG_NUMCLUSTERS_MASK 0x7f
+#define GCR_CONFIG_NUMIOCU_SHIFT 8
+#define GCR_CONFIG_NUMIOCU_MASK 0xf
+#define GCR_CONFIG_NUMCORES_SHIFT 0
+#define GCR_CONFIG_NUMCORES_MASK 0xff
+
+/* GCR_REV CM versions */
+#define GCR_REV_CM3 0x0800
+#define GCR_REV_CM3_5 0x0900
+
+#define GCR_MMIO_REQ_LIMIT 0x06f8
+#define GCR_MMIO0_BOTTOM 0x0700
+#define GCR_MMIO0_BOTTOM_ADDR (0xffffffffull << 16)
+#define GCR_MMIO0_BOTTOM_PORT_SHIFT 2
+#define GCR_MMIO0_BOTTOM_PORT (0xf << 2)
+#define GCR_MMIO0_BOTTOM_DISABLE_LIMIT (0x1 << 1)
+#define GCR_MMIO0_BOTTOM_ENABLE (0x1 << 0)
+#define GCR_MMIO0_TOP 0x0708
+#define GCR_MMIO0_TOP_ADDR (0xffffffffull << 16)
+#define GCR_MMIO1_BOTTOM 0x0710
+
+#define MIPS_CM_MMIO_LIMIT 4
+
#define P8700_GCR_C0_COH_EN 0x20f8
#define P8700_GCR_C1_COH_EN 0x21f8
#define P8700_GCR_C2_COH_EN 0x22f8
@@ -107,4 +138,8 @@
#define BOSTON_PLAT_NOCPCIE1ADDR (BOSTON_PLAT_BASE + 0x40)
#define BOSTON_PLAT_NOCPCIE2ADDR (BOSTON_PLAT_BASE + 0x44)

+#ifndef __ASSEMBLY__
+bool p8700_dma_is_coherent(void);
+#endif /* __ASSEMBLY__ */
+
#endif /* __P8700_H__ */
diff --git a/arch/riscv/include/asm/global_data.h b/arch/riscv/include/asm/global_data.h
index 33f2b5ec5c8..198eacd1e07 100644
--- a/arch/riscv/include/asm/global_data.h
+++ b/arch/riscv/include/asm/global_data.h
@@ -44,6 +44,10 @@ struct arch_global_data {
ulong smbios_start; /* Start address of SMBIOS table */
#endif
struct resume_data *resume;
+#if CONFIG_IS_ENABLED(P8700_RISCV)
+ int num_iocus;
+ int num_iocus_usable;
+#endif
};

#include <asm-generic/global_data.h>
diff --git a/board/mips/boston-riscv/Makefile b/board/mips/boston-riscv/Makefile
index 0615c677d23..c67c001d4e6 100644
--- a/board/mips/boston-riscv/Makefile
+++ b/board/mips/boston-riscv/Makefile
@@ -4,5 +4,6 @@

obj-y += boston-riscv.o
obj-y += checkboard.o
+obj-y += iocu.o
obj-y += lowlevel_init.o
obj-y += reset.o
diff --git a/board/mips/boston-riscv/iocu.c b/board/mips/boston-riscv/iocu.c
new file mode 100644
index 00000000000..807f09804a3
--- /dev/null
+++ b/board/mips/boston-riscv/iocu.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2016 Imagination Technologies Ltd.
+ */
+
+#include "boston-regs.h"
+#include <dm.h>
+#include <env_callback.h>
+#include <event.h>
+#include <asm/io.h>
+#include <asm/arch-p8700/cm.h>
+
+static const struct mmio_region mmio_regions[] = {
+ { 0x10000000, 0x160f0000, .enable = 1 },
+ { 0x17ff0000, 0x17ff0000, .enable = 1 },
+ { 0 },
+};
+
+const struct mmio_region *get_mmio_regions(void)
+{
+ return mmio_regions;
+}
+
+static int set_io_coherent(bool coherent)
+{
+ DECLARE_GLOBAL_DATA_PTR;
+
+ if (!coherent) {
+ printf("I/O: Non-Coherent (Forced by environment)\n");
+ goto noncoherent;
+ }
+
+ if (gd->arch.num_iocus_usable < 0) {
+ printf("I/O: Non-Coherent (IOCU init error %d)\n",
+ gd->arch.num_iocus_usable);
+ goto noncoherent;
+ }
+
+ if (gd->arch.num_iocus == 0) {
+ printf("I/O: Non-Coherent (No IOCU)\n");
+ goto noncoherent;
+ }
+
+ if (gd->arch.num_iocus_usable == 0) {
+ printf("I/O: Non-Coherent (IOCU not connected)\n");
+ goto noncoherent;
+ }
+
+ /*
+ * We have some number of connected IOCUs. Map all PCIe DMA access to
+ * hit the IOCU by offsetting the addresses as they pass from the PCIe
+ * controller to the NoC.
+ */
+ writel(0x10, (u32 *)BOSTON_PLAT_NOCPCIE0ADDR);
+ writel(0x10, (u32 *)BOSTON_PLAT_NOCPCIE1ADDR);
+ writel(0x10, (u32 *)BOSTON_PLAT_NOCPCIE2ADDR);
+
+ printf("I/O: Coherent\n");
+ return 0;
+
+noncoherent:
+ /* Map all PCIe DMA access to its default, non-IOCU, target */
+ writel(0x00, (u32 *)BOSTON_PLAT_NOCPCIE0ADDR);
+ writel(0x00, (u32 *)BOSTON_PLAT_NOCPCIE1ADDR);
+ writel(0x00, (u32 *)BOSTON_PLAT_NOCPCIE2ADDR);
+
+ return 0;
+}
+
+static int on_io_coherent(const char *name, const char *value,
+ enum env_op op, int flags)
+{
+ switch (op) {
+ case env_op_create:
+ case env_op_overwrite:
+ if (!strcmp(value, "0")) {
+ set_io_coherent(false);
+ } else if (!strcmp(value, "1")) {
+ set_io_coherent(true);
+ } else {
+ printf("### io.coherent must equal 0 or 1\n");
+ return -EINVAL;
+ }
+ return 0;
+
+ case env_op_delete:
+ set_io_coherent(true);
+ return 0;
+
+ default:
+ return 0;
+ }
+}
+
+U_BOOT_ENV_CALLBACK(io_coherent, on_io_coherent);
+
+static int p8700_misc_init_f(void)
+{
+ return set_io_coherent(env_get_yesno("io.coherent") != 0);
+}
+
+EVENT_SPY_SIMPLE(EVT_MISC_INIT_F, p8700_misc_init_f);
--
2.34.1