[PATCH v2 5/5] platform: generic: spacemit: k3: add platform support

Bo Gan ganboing at gmail.com
Thu Aug 20 02:18:22 PDT 2026


Hi Troy, Xianbin,

See my comments below:

On 8/17/26 18:14, Troy Mitchell wrote:
> From: Xianbin Zhu <xianbin.zhu at linux.spacemit.com>
> 
> Add initial OpenSBI platform support for the SpacemiT K3 SoC, which has
> 16 harts: eight X100 harts in clusters C0/C1 and eight 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). Wait up to 100 ms for each secondary hart to enter WFI,
> then warn and continue so one failed hart cannot stall boot indefinitely.
> 
> Run per-hart initialization from the nascent hook on both cold- and
> warm-boot paths. Program PMA attributes, enable the D-cache and I-cache
> through MSETUP, set smpen[x] in ML2SETUP to enable L1 D-cache snooping
> and prefetch, and enable the H extension in MISA for X100 harts. MISA.H
> is writable on K3 X100 cores, and M-mode must set it explicitly to
> activate hypervisor support.
> 
> Start secondary harts through an assembly stub that enables caches and
> sets smpen[x] before entering common warm-boot code. This lets them
> observe shared-data writes made by the boot hart during cold-boot
> initialization. Enable global CCI-550 snoop and DVM requests on the boot
> hart before waking the secondary harts.
> 
> 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             | 266 +++++++++++++++++++++++++++++
>   platform/generic/spacemit/k3_asm.S         |  33 ++++
>   platform/generic/spacemit/objects.mk       |   2 +
>   8 files changed, 470 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..e484d67b
> --- /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				0x7de
> +
> +/*
> + * 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).
> + */

Use _BITUL/ULL? They should take care of omitting the "UL" if __ASSEMBLER__

> +#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..1e8d8c97
> --- /dev/null
> +++ b/platform/generic/spacemit/k3.c
> @@ -0,0 +1,266 @@
> +/*
> + * 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_asm.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,

Why not just PMU_AP_X100_IDLE_STATUS? You don't need to use it, but for
the sake of faithfully describing the HW?

> +	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,

Same issue as above. Just do BIT(4)

> +	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;

This condition will never be true.

> +
> +	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.

I'm confused by this. Are we doing some duplicate work? Hasn't
_spacemit_k3_warm_start already done those? What's the audio buffer used
for? It says set audio buffer attributes before enabling dcache, but
isn't dcache already enabled by the asm code?


> + * 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");

Better use __sbi_sfence_vma_all()? Also is __sbi_hfence_gvma_all() needed
for X100?

> +
> +	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);
> +	}
> +}
> +
> +static int spacemit_k3_nascent_init(void)
> +{
> +	spacemit_k3_hart_init(current_hartid());
> +
> +	return generic_nascent_init();
> +}
> +
> +/*
> + * 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)
> +{
> +	return !hartid;
> +}
> +
> +static int spacemit_k3_platform_init(const void *fdt, int nodeoff,
> +				     const struct fdt_match *match)
> +{
> +	generic_platform_ops.nascent_init = spacemit_k3_nascent_init;
> +	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
> 

Perhaps in objects.mk, you should skip the build for rv32. I bet you'll get
warnings or even errors targeting rv32. No point of doing that anyway.

Bo




More information about the opensbi mailing list