[PATCH 3/3] platform: generic: spacemit: k3: add platform support
Troy Mitchell
troy.mitchell at linux.spacemit.com
Fri Jul 24 02:27:03 PDT 2026
From: Xianbin Zhu <xianbin.zhu at linux.spacemit.com>
Add initial OpenSBI platform support for the SpacemiT K3 SoC, which
has 16 harts (8 X100 harts in clusters C0/C1, 8 A100 harts in C2/C3).
During cold boot, the boot hart sets warm-boot entry vectors for all
four clusters, enables CCI snooping, clears PMU power-down vote bits
for all harts so WFI does not trigger power-down, and wakes all
non-boot harts (1-15) so they enter WFI waiting for the kernel.
Per-hart initialization programs PMA attributes, enables dcache/icache
via MSETUP, sets smpen[x] in ML2SETUP to enable L1 D-cache snooping
and prefetch for this core (hardware then maintains coherency between
this core's L1 D-cache and all other caches in the system), and enables
the H extension in MISA for X100 harts (MISA.H is writable on K3 X100
cores; M-mode must set it explicitly to activate hypervisor support).
k3_asm.S provides the warm-boot entry stub that enables caches and
sets smpen[x] to enable L1 D-cache snooping before jumping to C code.
This ensures that when the boot hart wakes the other cores, those cores
enter with their local caches active and L1 D-cache coherency enabled
so they can observe any shared-data writes made by the boot hart during
cold-boot initialization. The boot hart enables global CCI-550
snoop/DVM requests separately before waking the secondary harts.
New files:
- platform/generic/include/spacemit/k3.h: K3-specific register definitions
- platform/generic/include/spacemit/k3_asm.h:
warm-boot entry symbol declaration
- platform/generic/spacemit/k3.c: K3 platform driver
- platform/generic/spacemit/k3_asm.S: warm-boot assembly entry stub
Signed-off-by: Xianbin Zhu <xianbin.zhu at linux.spacemit.com>
Co-developed-by: Troy Mitchell <troy.mitchell at linux.spacemit.com>
Signed-off-by: Troy Mitchell <troy.mitchell at linux.spacemit.com>
---
platform/generic/Kconfig | 5 +
platform/generic/configs/defconfig | 1 +
platform/generic/include/spacemit/common.h | 4 +-
platform/generic/include/spacemit/k3.h | 144 ++++++++++++++++
platform/generic/include/spacemit/k3_asm.h | 16 ++
platform/generic/spacemit/k3.c | 264 +++++++++++++++++++++++++++++
platform/generic/spacemit/k3_asm.S | 33 ++++
platform/generic/spacemit/objects.mk | 2 +
8 files changed, 468 insertions(+), 1 deletion(-)
diff --git a/platform/generic/Kconfig b/platform/generic/Kconfig
index 958610d3..0302eda6 100644
--- a/platform/generic/Kconfig
+++ b/platform/generic/Kconfig
@@ -109,6 +109,11 @@ config PLATFORM_SPACEMIT_K1
select PLATFORM_SPACEMIT
default n
+config PLATFORM_SPACEMIT_K3
+ bool "Spacemit K3 support"
+ select PLATFORM_SPACEMIT
+ default n
+
config CPU_MIPS_P8700
bool
default n
diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig
index 341f6b25..58d40898 100644
--- a/platform/generic/configs/defconfig
+++ b/platform/generic/configs/defconfig
@@ -15,6 +15,7 @@ CONFIG_PLATFORM_THEAD=y
CONFIG_PLATFORM_MIPS_P8700_EYEQ7H=y
CONFIG_PLATFORM_MIPS_P8700_BOSTON=y
CONFIG_PLATFORM_SPACEMIT_K1=y
+CONFIG_PLATFORM_SPACEMIT_K3=y
CONFIG_FDT_CACHE=y
CONFIG_FDT_CACHE_ANDES_LLCACHE=y
CONFIG_FDT_CACHE_SIFIVE_CCACHE=y
diff --git a/platform/generic/include/spacemit/common.h b/platform/generic/include/spacemit/common.h
index 317bb3e9..7b50cc4f 100644
--- a/platform/generic/include/spacemit/common.h
+++ b/platform/generic/include/spacemit/common.h
@@ -9,7 +9,9 @@
#ifndef __RISCV_SPACEMIT_COMMON_H__
#define __RISCV_SPACEMIT_COMMON_H__
+#ifndef __ASSEMBLY__
#include <sbi/sbi_bitops.h>
+#endif
#define CSR_MSETUP 0x7c0
#define CSR_ML2SETUP 0x7f0
@@ -74,7 +76,7 @@
#define PMU_AP_IDLE_CORE_IDLE BIT(0)
/* bit 1: CORE_PWRDWN - power off core on WFI (requires CORE_IDLE set) */
#define PMU_AP_IDLE_CORE_PWRDWN BIT(1)
-/* bit 2: Core L1 SRAM Power Down - reserved/not used in K1 and K3 */
+/* bit 2: Core L1 SRAM Power Down - reserved in K1, used in K3 */
#define PMU_AP_IDLE_CORE_L1_SRAM_PWRDWN BIT(2)
/* bit 3: MASK_GIC_NIRQ_TO_CORE - mask nIRQ from GIC, auto-cleared by HW on power-down entry */
#define PMU_AP_IDLE_MASK_GIC_NIRQ BIT(3)
diff --git a/platform/generic/include/spacemit/k3.h b/platform/generic/include/spacemit/k3.h
new file mode 100644
index 00000000..a84d9ed8
--- /dev/null
+++ b/platform/generic/include/spacemit/k3.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (c) 2026 SpacemiT
+ * Authors:
+ * Xianbin Zhu <xianbin.zhu at linux.spacemit.com>
+ * Troy Mitchell <troy.mitchell at linux.spacemit.com>
+ */
+
+#ifndef __RISCV_SPACEMIT_K3_H__
+#define __RISCV_SPACEMIT_K3_H__
+
+#include <spacemit/common.h>
+
+/* PMA configuration CSR */
+#define CSR_PMACFG0 0xbc0
+
+/*
+ * PMACFG0 audio buffer region field: bits [55:48].
+ * Mask and attribute are applied after a 48-bit shift because
+ * csrc/csrs only accept a 12-bit immediate or a register.
+ */
+#define PMACFG0_AUDIO_BUF_FIELD_MASK 0xff
+#define PMACFG0_AUDIO_BUF_FIELD_SHIFT 48
+#define PMACFG0_AUDIO_BUF_ATTR_CACHEABLE 0x20
+
+/*
+ * PMACFG0 XIP region field: bits [23:16].
+ * Lower 12 bits are zero so GAS emits a single lui instruction.
+ */
+#define PMACFG0_XIP_FIELD_CLEAR 0x00ff0000UL
+#define PMACFG0_XIP_ATTR_IO 0x00220000UL
+
+#define CSR_ML2HINT 0x7f7
+
+/* Enable instruction prefetch in L2 cache on miss */
+#define ML2SETUP_IPRF BIT(16)
+/* Enable TLB prefetch in L2 cache */
+#define ML2SETUP_TPRF BIT(18)
+/* Enable top-of-clk of trace */
+#define ML2HINT_TRACE_TOP_ICGEN BIT(26)
+
+/*
+ * Combined MSETUP flags enabled at boot time: DE|IE|BPE|PFE|MME|ECCE.
+ * Expressed as a plain hex constant so it can be used in assembly (BIT()
+ * expands to 1UL<<n which GAS cannot evaluate).
+ */
+#define MSETUP_BOOT_FLAGS 0x10073
+
+/* A100 cores: hart 8-11 belong to C2 cluster, hart 12-15 to C3 cluster */
+#define PMU_CAP_CORE8_WAKEUP (PMU_AP_BASE + 0x360)
+#define PMU_CAP_CORE9_WAKEUP (PMU_AP_BASE + 0x364)
+#define PMU_CAP_CORE10_WAKEUP (PMU_AP_BASE + 0x368)
+#define PMU_CAP_CORE11_WAKEUP (PMU_AP_BASE + 0x36c)
+#define PMU_CAP_CORE12_WAKEUP (PMU_AP_BASE + 0x22c)
+#define PMU_CAP_CORE13_WAKEUP (PMU_AP_BASE + 0x230)
+#define PMU_CAP_CORE14_WAKEUP (PMU_AP_BASE + 0x234)
+#define PMU_CAP_CORE15_WAKEUP (PMU_AP_BASE + 0x238)
+
+/* A100 per-core idle config registers (harts 8-15) */
+#define PMU_CAP_CORE8_IDLE_CFG (PMU_AP_BASE + 0x340)
+#define PMU_CAP_CORE9_IDLE_CFG (PMU_AP_BASE + 0x344)
+#define PMU_CAP_CORE10_IDLE_CFG (PMU_AP_BASE + 0x348)
+#define PMU_CAP_CORE11_IDLE_CFG (PMU_AP_BASE + 0x34c)
+#define PMU_CAP_CORE12_IDLE_CFG (PMU_AP_BASE + 0x20c)
+#define PMU_CAP_CORE13_IDLE_CFG (PMU_AP_BASE + 0x210)
+#define PMU_CAP_CORE14_IDLE_CFG (PMU_AP_BASE + 0x214)
+#define PMU_CAP_CORE15_IDLE_CFG (PMU_AP_BASE + 0x218)
+
+/* CX CAPMP cluster idle config registers */
+#define PMU_CX_CAPMP_IDLE_CFG0 (PMU_AP_BASE + 0x120)
+#define PMU_CX_CAPMP_IDLE_CFG1 (PMU_AP_BASE + 0x0e4)
+#define PMU_CX_CAPMP_IDLE_CFG2 (PMU_AP_BASE + 0x150)
+#define PMU_CX_CAPMP_IDLE_CFG3 (PMU_AP_BASE + 0x154)
+#define PMU_CX_CAPMP_IDLE_CFG4 (PMU_AP_BASE + 0x314)
+#define PMU_CX_CAPMP_IDLE_CFG5 (PMU_AP_BASE + 0x318)
+#define PMU_CX_CAPMP_IDLE_CFG6 (PMU_AP_BASE + 0x31c)
+#define PMU_CX_CAPMP_IDLE_CFG7 (PMU_AP_BASE + 0x320)
+#define PMU_CX_CAPMP_IDLE_CFG8 (PMU_AP_BASE + 0x350)
+#define PMU_CX_CAPMP_IDLE_CFG9 (PMU_AP_BASE + 0x354)
+#define PMU_CX_CAPMP_IDLE_CFG10 (PMU_AP_BASE + 0x358)
+#define PMU_CX_CAPMP_IDLE_CFG11 (PMU_AP_BASE + 0x35c)
+#define PMU_CX_CAPMP_IDLE_CFG12 (PMU_AP_BASE + 0x21c)
+#define PMU_CX_CAPMP_IDLE_CFG13 (PMU_AP_BASE + 0x220)
+#define PMU_CX_CAPMP_IDLE_CFG14 (PMU_AP_BASE + 0x224)
+#define PMU_CX_CAPMP_IDLE_CFG15 (PMU_AP_BASE + 0x228)
+
+#define PMU_AP_IDLE_PWRDOWN_K3_MASK (PMU_AP_IDLE_CORE_IDLE | \
+ PMU_AP_IDLE_CORE_PWRDWN | \
+ PMU_AP_IDLE_CORE_L1_SRAM_PWRDWN | \
+ PMU_AP_IDLE_MASK_GIC_NIRQ | \
+ PMU_AP_IDLE_MASK_GIC_NFIQ)
+
+/*
+ * PMU CX per-cluster idle control bits (PMU_CX_CAPMPn_IDLE_CFG).
+ */
+/* bit 0: MP Idle - gate cluster clocks externally on WFI */
+#define PMU_CX_IDLE_MP_IDLE BIT(0)
+/* bit 1: MP Power Down - power off cluster logic when idle */
+#define PMU_CX_IDLE_MP_PWRDWN BIT(1)
+/* bit 2: L2 Cache SRAM Power Down */
+#define PMU_CX_IDLE_L2_SRAM_PWRDWN BIT(2)
+/* bit 3: SCU SRAM Power Down - not used, SCU SRAM does not support retention */
+#define PMU_CX_IDLE_MP_SCU_SRAM_PWRDWN BIT(3)
+/* bit 7: ACNACTM Hardware Control - HW controls ACNACTM port on MP low power entry */
+#define PMU_CX_IDLE_ACNACTM_HW_CTRL BIT(7)
+
+#define CLUSTER_PWR_DOWN_VALUE (PMU_CX_IDLE_MP_IDLE | \
+ PMU_CX_IDLE_MP_PWRDWN | \
+ PMU_CX_IDLE_L2_SRAM_PWRDWN | \
+ PMU_CX_IDLE_MP_SCU_SRAM_PWRDWN | \
+ PMU_CX_IDLE_ACNACTM_HW_CTRL)
+
+/* boot entry for A100 clusters */
+#define C2_RVBADDR_LO_ADDR (0xd4282c00 + 0x3e8)
+#define C2_RVBADDR_HI_ADDR (0xd4282c00 + 0x3ec)
+#define C3_RVBADDR_LO_ADDR (0xd4282c00 + 0x260)
+#define C3_RVBADDR_HI_ADDR (0xd4282c00 + 0x264)
+
+/*
+ * PMU AP core WFI status registers. Each bit indicates the corresponding
+ * core has entered WFI idle state.
+ *
+ * X100 harts (0-7): PMU_AP_X100_IDLE_STATUS
+ * cluster 0 (harts 0-3): bits 4, 7, 10, 13
+ * cluster 1 (harts 4-7): bits 20, 23, 26, 29
+ * A100 harts (8-15): PMU_AP_A100_IDLE_STATUS
+ * cluster 2 (harts 8-11): bits 4, 7, 10, 13
+ * cluster 3 (harts 12-15): bits 20, 23, 26, 29
+ */
+#define PMU_AP_X100_IDLE_STATUS 0xd4282890
+#define PMU_AP_A100_IDLE_STATUS 0xd4282880
+
+#define PLATFORM_A100_FIRST_ID 8
+#define PLATFORM_A100_LAST_ID 15
+
+#define PLAT_CCI_K3_AIDMA_IFACE_IX 0
+#define PLAT_CCI_K3_CLUSTER0_IFACE_IX 1
+#define PLAT_CCI_K3_CLUSTER1_IFACE_IX 2
+#define PLAT_CCI_K3_CLUSTER2_0_IFACE_IX 3
+#define PLAT_CCI_K3_CLUSTER2_1_IFACE_IX 4
+#define PLAT_CCI_K3_CLUSTER3_0_IFACE_IX 5
+#define PLAT_CCI_K3_CLUSTER3_1_IFACE_IX 6
+
+#endif /* __RISCV_SPACEMIT_K3_H__ */
diff --git a/platform/generic/include/spacemit/k3_asm.h b/platform/generic/include/spacemit/k3_asm.h
new file mode 100644
index 00000000..70c00478
--- /dev/null
+++ b/platform/generic/include/spacemit/k3_asm.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (c) 2026 SpacemiT
+ * Authors:
+ * Xianbin Zhu <xianbin.zhu at linux.spacemit.com>
+ * Troy Mitchell <troy.mitchell at linux.spacemit.com>
+ */
+
+#ifndef __RISCV_SPACEMIT_K3_ASM_H__
+#define __RISCV_SPACEMIT_K3_ASM_H__
+
+#ifndef __ASSEMBLY__
+void _spacemit_k3_warm_start(void);
+#endif
+
+#endif /* __RISCV_SPACEMIT_K3_ASM_H__ */
diff --git a/platform/generic/spacemit/k3.c b/platform/generic/spacemit/k3.c
new file mode 100644
index 00000000..318aa9e7
--- /dev/null
+++ b/platform/generic/spacemit/k3.c
@@ -0,0 +1,264 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 SpacemiT
+ * Authors:
+ * Xianbin Zhu <xianbin.zhu at linux.spacemit.com>
+ * Troy Mitchell <troy.mitchell at linux.spacemit.com>
+ */
+
+#include <platform_override.h>
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_timer.h>
+#include <spacemit/k3.h>
+#include <spacemit/k3_asm.h>
+#include <spacemit/spacemit.h>
+
+#define PLATFORM_MAX_CPUS 16
+
+/*
+ * Maximum time to wait for each secondary hart to enter WFI after it is
+ * woken during cold boot. Use the platform timer so the timeout does not
+ * depend on CPU frequency or MMIO latency.
+ */
+#define HART_WFI_TIMEOUT_MS 100
+
+static const int cci_map[] = {
+ PLAT_CCI_K3_AIDMA_IFACE_IX,
+ PLAT_CCI_K3_CLUSTER0_IFACE_IX,
+ PLAT_CCI_K3_CLUSTER1_IFACE_IX,
+ PLAT_CCI_K3_CLUSTER2_0_IFACE_IX,
+ PLAT_CCI_K3_CLUSTER2_1_IFACE_IX,
+ PLAT_CCI_K3_CLUSTER3_0_IFACE_IX,
+ PLAT_CCI_K3_CLUSTER3_1_IFACE_IX,
+};
+
+static const unsigned long cx_capmp_idle_cfg[] = {
+ PMU_CX_CAPMP_IDLE_CFG0, PMU_CX_CAPMP_IDLE_CFG1,
+ PMU_CX_CAPMP_IDLE_CFG2, PMU_CX_CAPMP_IDLE_CFG3,
+ PMU_CX_CAPMP_IDLE_CFG4, PMU_CX_CAPMP_IDLE_CFG5,
+ PMU_CX_CAPMP_IDLE_CFG6, PMU_CX_CAPMP_IDLE_CFG7,
+ PMU_CX_CAPMP_IDLE_CFG8, PMU_CX_CAPMP_IDLE_CFG9,
+ PMU_CX_CAPMP_IDLE_CFG10, PMU_CX_CAPMP_IDLE_CFG11,
+ PMU_CX_CAPMP_IDLE_CFG12, PMU_CX_CAPMP_IDLE_CFG13,
+ PMU_CX_CAPMP_IDLE_CFG14, PMU_CX_CAPMP_IDLE_CFG15,
+};
+
+static const unsigned long core_pwrdown_reg[] = {
+ PMU_AP_CORE0_IDLE_CFG, PMU_AP_CORE1_IDLE_CFG,
+ PMU_AP_CORE2_IDLE_CFG, PMU_AP_CORE3_IDLE_CFG,
+ PMU_AP_CORE4_IDLE_CFG, PMU_AP_CORE5_IDLE_CFG,
+ PMU_AP_CORE6_IDLE_CFG, PMU_AP_CORE7_IDLE_CFG,
+ PMU_CAP_CORE8_IDLE_CFG, PMU_CAP_CORE9_IDLE_CFG,
+ PMU_CAP_CORE10_IDLE_CFG, PMU_CAP_CORE11_IDLE_CFG,
+ PMU_CAP_CORE12_IDLE_CFG, PMU_CAP_CORE13_IDLE_CFG,
+ PMU_CAP_CORE14_IDLE_CFG, PMU_CAP_CORE15_IDLE_CFG,
+};
+
+/* Wakeup registers indexed by hart ID (0-15). */
+static const unsigned long core_wakeup_reg[] = {
+ PMU_AP_CORE0_WAKEUP, PMU_AP_CORE1_WAKEUP,
+ PMU_AP_CORE2_WAKEUP, PMU_AP_CORE3_WAKEUP,
+ PMU_AP_CORE4_WAKEUP, PMU_AP_CORE5_WAKEUP,
+ PMU_AP_CORE6_WAKEUP, PMU_AP_CORE7_WAKEUP,
+ PMU_CAP_CORE8_WAKEUP, PMU_CAP_CORE9_WAKEUP,
+ PMU_CAP_CORE10_WAKEUP, PMU_CAP_CORE11_WAKEUP,
+ PMU_CAP_CORE12_WAKEUP, PMU_CAP_CORE13_WAKEUP,
+ PMU_CAP_CORE14_WAKEUP, PMU_CAP_CORE15_WAKEUP,
+};
+
+/*
+ * WFI idle status registers and per-hart mask bits. Each hart has a
+ * dedicated bit in one of two status registers (X100 harts 0-7 in
+ * PMU_AP_X100_IDLE_STATUS, A100 harts 8-15 in PMU_AP_A100_IDLE_STATUS).
+ * Index 0 is unused — the boot hart never waits on itself.
+ */
+static const unsigned long wfi_status_reg[] = {
+ 0,
+ PMU_AP_X100_IDLE_STATUS, PMU_AP_X100_IDLE_STATUS,
+ PMU_AP_X100_IDLE_STATUS, PMU_AP_X100_IDLE_STATUS,
+ PMU_AP_X100_IDLE_STATUS, PMU_AP_X100_IDLE_STATUS,
+ PMU_AP_X100_IDLE_STATUS,
+ PMU_AP_A100_IDLE_STATUS, PMU_AP_A100_IDLE_STATUS,
+ PMU_AP_A100_IDLE_STATUS, PMU_AP_A100_IDLE_STATUS,
+ PMU_AP_A100_IDLE_STATUS, PMU_AP_A100_IDLE_STATUS,
+ PMU_AP_A100_IDLE_STATUS, PMU_AP_A100_IDLE_STATUS,
+};
+static const unsigned long wfi_status_mask[] = {
+ 0,
+ BIT(7), BIT(10), BIT(13), BIT(20),
+ BIT(23), BIT(26), BIT(29),
+ BIT(4), BIT(7), BIT(10), BIT(13),
+ BIT(20), BIT(23), BIT(26), BIT(29),
+};
+
+/*
+ * Clear the power-down bits for both the core and its cluster so that
+ * the hart does not trigger a power-down sequence on WFI. Called during
+ * cold boot to ensure cores and clusters stay powered while SBI initializes.
+ */
+static void spacemit_disable_pwrdown_core(u32 hartid)
+{
+ unsigned int value;
+
+ if (hartid >= array_size(cx_capmp_idle_cfg))
+ return;
+
+ value = readl((void *)(unsigned long)cx_capmp_idle_cfg[hartid]);
+ value &= ~CLUSTER_PWR_DOWN_VALUE;
+ writel(value, (void *)(unsigned long)cx_capmp_idle_cfg[hartid]);
+
+ value = readl((void *)(unsigned long)core_pwrdown_reg[hartid]);
+ value &= ~PMU_AP_IDLE_PWRDOWN_K3_MASK;
+ writel(value, (void *)(unsigned long)core_pwrdown_reg[hartid]);
+}
+
+/*
+ * Per-hart hardware initialization, mirroring what warm-boot harts previously
+ * did in assembly before entering C code. Called on every hart on both cold
+ * and warm boot paths.
+ *
+ * 1. PMA: set audio buffer (bits [55:48]) cacheable and XIP
+ * region (bits [23:16]) as IO before enabling dcache.
+ * 2. Enable dcache/icache and other CPU features (MSETUP).
+ * 3. Set smpen[x] in ML2SETUP to enable L1 D-cache snooping and prefetch.
+ * 4. Enable H extension in MISA (X100 only; not implemented on A100).
+ */
+static void spacemit_k3_hart_init(u32 hartid)
+{
+ csr_clear(CSR_PMACFG0,
+ (unsigned long)PMACFG0_AUDIO_BUF_FIELD_MASK
+ << PMACFG0_AUDIO_BUF_FIELD_SHIFT);
+ csr_set(CSR_PMACFG0,
+ (unsigned long)PMACFG0_AUDIO_BUF_ATTR_CACHEABLE
+ << PMACFG0_AUDIO_BUF_FIELD_SHIFT);
+ csr_clear(CSR_PMACFG0, PMACFG0_XIP_FIELD_CLEAR);
+ csr_set(CSR_PMACFG0, PMACFG0_XIP_ATTR_IO);
+ asm volatile("sfence.vma" : : : "memory");
+
+ csr_set(CSR_MSETUP, MSETUP_DE | MSETUP_IE | MSETUP_BPE |
+ MSETUP_PFE | MSETUP_MME | MSETUP_ECCE);
+ csr_set(CSR_ML2SETUP, 1 << (hartid % PLATFORM_MAX_CPUS_PER_CLUSTER));
+ csr_set(CSR_ML2SETUP, ML2SETUP_IPRF | ML2SETUP_TPRF);
+ csr_set(CSR_ML2HINT, ML2HINT_TRACE_TOP_ICGEN);
+
+ if (hartid < PLATFORM_A100_FIRST_ID) {
+ unsigned long misa = csr_read(CSR_MISA);
+
+ /*
+ * MISA.H is writable on K3 X100 cores: the H extension is
+ * implemented but disabled out of reset, and M-mode must
+ * explicitly set this bit to enable hypervisor support.
+ * A100 cores do not implement H, so this write is X100-only.
+ */
+ misa |= 1UL << ('H' - 'A');
+ csr_write(CSR_MISA, misa);
+ }
+}
+
+/*
+ * One-time platform setup run by the boot hart during cold boot:
+ * set warm-boot entry for all four clusters, enable CCI snooping,
+ * and wake all non-boot harts (1-15) so they enter WFI and wait
+ * for the kernel to bring them online.
+ */
+static void spacemit_k3_pre_init(void)
+{
+ unsigned long entry = (unsigned long)&_spacemit_k3_warm_start;
+ int i;
+
+ writel((u32)entry, (void *)(unsigned long)C0_RVBADDR_LO_ADDR);
+ writel((u32)(entry >> 32), (void *)(unsigned long)C0_RVBADDR_HI_ADDR);
+
+ writel((u32)entry, (void *)(unsigned long)C1_RVBADDR_LO_ADDR);
+ writel((u32)(entry >> 32), (void *)(unsigned long)C1_RVBADDR_HI_ADDR);
+
+ writel((u32)entry, (void *)(unsigned long)C2_RVBADDR_LO_ADDR);
+ writel((u32)(entry >> 32), (void *)(unsigned long)C2_RVBADDR_HI_ADDR);
+
+ writel((u32)entry, (void *)(unsigned long)C3_RVBADDR_LO_ADDR);
+ writel((u32)(entry >> 32), (void *)(unsigned long)C3_RVBADDR_HI_ADDR);
+
+ for (i = 0; i < array_size(cci_map); i++)
+ cci_enable_snoop_dvm_reqs(cci_map, i);
+
+ /* Clear core and cluster power-down votes so WFI does not trigger power-down. */
+ for (i = 0; i < PLATFORM_MAX_CPUS; i++)
+ spacemit_disable_pwrdown_core(i);
+
+ /*
+ * Wake up all non-boot harts (1-15). Each hart N has a dedicated PMU
+ * wakeup register (PMU_AP/CAP_COREn_WAKEUP). Writing BIT(N) to hart N's
+ * register triggers the wakeup pulse for that hart. This is confirmed by
+ * hardware: the register address selects the target hart, and the value
+ * written must have bit N set (not a fixed trigger bit).
+ * See K3 User Manual, "16 Peripherals / Power Management / Wakeup Core".
+ */
+ for (i = 1; i < array_size(core_wakeup_reg); i++)
+ writel(1 << i, (void *)(unsigned long)core_wakeup_reg[i]);
+
+ /*
+ * Wait for each non-boot hart to enter WFI. Status bit positions are
+ * non-uniform (see k3.h), so use the file-scope lookup tables.
+ */
+ for (i = 1; i < PLATFORM_MAX_CPUS; i++) {
+ u64 deadline = sbi_timer_value_after_msecs(HART_WFI_TIMEOUT_MS);
+
+ while (!(readl((void *)(unsigned long)wfi_status_reg[i]) & wfi_status_mask[i])) {
+ if (sbi_timer_value() >= deadline) {
+ sbi_printf("warn: hart %d did not enter WFI\n", i);
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Platform early initialization.
+ */
+static int spacemit_k3_early_init(bool cold_boot)
+{
+ int rc;
+
+ if (cold_boot)
+ spacemit_k3_pre_init();
+
+ rc = generic_early_init(cold_boot);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static bool spacemit_cold_boot_allowed(u32 hartid)
+{
+ /*
+ * Run per-hart hardware init (PMA, dcache/icache, and L1 D-cache
+ * snoop enable) here so it executes on every hart before any SBI
+ * code runs. cold_boot_allowed is the earliest per-hart hook
+ * available in the generic platform.
+ */
+ spacemit_k3_hart_init(hartid);
+
+ return !hartid;
+}
+
+static int spacemit_k3_platform_init(const void *fdt, int nodeoff,
+ const struct fdt_match *match)
+{
+ generic_platform_ops.early_init = spacemit_k3_early_init;
+ generic_platform_ops.cold_boot_allowed = spacemit_cold_boot_allowed;
+
+ return 0;
+}
+
+static const struct fdt_match spacemit_k3_match[] = {
+ { .compatible = "spacemit,k3" },
+ { /* sentinel */ }
+};
+
+const struct fdt_driver spacemit_k3 = {
+ .match_table = spacemit_k3_match,
+ .init = spacemit_k3_platform_init,
+};
diff --git a/platform/generic/spacemit/k3_asm.S b/platform/generic/spacemit/k3_asm.S
new file mode 100644
index 00000000..fe45cca6
--- /dev/null
+++ b/platform/generic/spacemit/k3_asm.S
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (c) 2026 SpacemiT
+ * Authors:
+ * Xianbin Zhu <xianbin.zhu at linux.spacemit.com>
+ * Troy Mitchell <troy.mitchell at linux.spacemit.com>
+ */
+
+#define __ASSEMBLY__
+#include <spacemit/common.h>
+#include <spacemit/k3.h>
+
+ .section .entry, "ax", %progbits
+ .align 3
+ .globl _spacemit_k3_warm_start
+_spacemit_k3_warm_start:
+ /* Enable dcache, icache, and CPU features */
+ li t0, MSETUP_BOOT_FLAGS
+ csrs CSR_MSETUP, t0
+
+ /*
+ * Set smpen[x] in ML2SETUP to enable L1 D-cache snooping for this
+ * core, so the hardware maintains coherency between this core's L1
+ * D-cache and all other caches in the system.
+ * The bit position is 1 << (hartid % PLATFORM_MAX_CPUS_PER_CLUSTER).
+ */
+ csrr t0, mhartid
+ andi t0, t0, (PLATFORM_MAX_CPUS_PER_CLUSTER - 1)
+ li t1, 1
+ sll t1, t1, t0
+ csrs CSR_ML2SETUP, t1
+
+ j _start_warm
diff --git a/platform/generic/spacemit/objects.mk b/platform/generic/spacemit/objects.mk
index 8309ad1f..282543b5 100644
--- a/platform/generic/spacemit/objects.mk
+++ b/platform/generic/spacemit/objects.mk
@@ -5,3 +5,5 @@
platform-objs-$(CONFIG_PLATFORM_SPACEMIT) += spacemit/spacemit.o
carray-platform_override_modules-$(CONFIG_PLATFORM_SPACEMIT_K1) += spacemit_k1
platform-objs-$(CONFIG_PLATFORM_SPACEMIT_K1) += spacemit/k1.o
+carray-platform_override_modules-$(CONFIG_PLATFORM_SPACEMIT_K3) += spacemit_k3
+platform-objs-$(CONFIG_PLATFORM_SPACEMIT_K3) += spacemit/k3.o spacemit/k3_asm.o
--
2.55.0
More information about the opensbi
mailing list