[PATCH v3 12/12] platform: sifive: Add SiFive SMC0 driver
Anup Patel
anup at brainfault.org
Mon Aug 4 01:39:08 PDT 2025
On Tue, Jul 8, 2025 at 1:20 PM Nick Hu <nick.hu at sifive.com> wrote:
>
> The SiFive SMC0 controls the clock and power domain of the core complex
> on the SiFive platform. The core complex enters the low power state
> after the secondary cores enter the tile power gating and last core
> execute the `CEASE` instruction with the corresponding SMC0
> configurations. The devices that inside both tile power domain and core
> complex power domain will be off, including caches and timer. Therefore
> we need to flush the last level cache before entering the core complex
> power gating and update the timer after waking up.
>
> Signed-off-by: Nick Hu <nick.hu at sifive.com>
> Reviewed-by: Cyan Yang <cyan.yang at sifive.com>
> ---
> platform/generic/Kconfig | 1 +
> platform/generic/include/sifive/sifive_smc0.h | 13 +
> platform/generic/include/sifive/sifive_tmc0.h | 3 +
> platform/generic/sifive/Kconfig | 5 +
> platform/generic/sifive/objects.mk | 2 +
> .../pmdomain/fdt_pmdomain_sifive_smc0.c | 322 ++++++++++++++++++
> .../pmdomain/fdt_pmdomain_sifive_tmc0.c | 88 +++++
> 7 files changed, 434 insertions(+)
> create mode 100644 platform/generic/include/sifive/sifive_smc0.h
> create mode 100644 platform/generic/sifive/pmdomain/fdt_pmdomain_sifive_smc0.c
The SMC0 driver should be under the lib/utils/suspend/ directory.
Regards,
Anup
>
> diff --git a/platform/generic/Kconfig b/platform/generic/Kconfig
> index 1d6ea3f4..048822ef 100644
> --- a/platform/generic/Kconfig
> +++ b/platform/generic/Kconfig
> @@ -46,6 +46,7 @@ config PLATFORM_RENESAS_RZFIVE
> config PLATFORM_SIFIVE_DEV
> bool "SiFive development platform support"
> depends on FDT_CACHE
> + select SIFIVE_SMC0
> select SIFIVE_TMC0
> default n
>
> diff --git a/platform/generic/include/sifive/sifive_smc0.h b/platform/generic/include/sifive/sifive_smc0.h
> new file mode 100644
> index 00000000..d13bdf4d
> --- /dev/null
> +++ b/platform/generic/include/sifive/sifive_smc0.h
> @@ -0,0 +1,13 @@
> +/*
> + * SPDX-License-Identifier: BSD-2-Clause
> + *
> + * Copyright (c) 2025 SiFive Inc.
> + */
> +
> +#ifndef __SIFIVE_SMC0_H__
> +#define __SIFIVE_SMC0_H__
> +
> +bool sifive_smc0_check_warm_reset(void);
> +void sifive_smc0_mtime_update(void);
> +
> +#endif
> diff --git a/platform/generic/include/sifive/sifive_tmc0.h b/platform/generic/include/sifive/sifive_tmc0.h
> index 6100e0dc..ad0d2407 100644
> --- a/platform/generic/include/sifive/sifive_tmc0.h
> +++ b/platform/generic/include/sifive/sifive_tmc0.h
> @@ -7,6 +7,9 @@
> #ifndef __SIFIVE_TMC0_H__
> #define __SIFIVE_TMC0_H__
>
> +int sifive_tmc0_set_wakemask_enareq(u32 hartid);
> +void sifive_tmc0_set_wakemask_disreq(u32 hartid);
> +bool sifive_tmc0_is_pg(u32 hartid);
> int sifive_tmc0_cold_init(void);
>
> #endif
> diff --git a/platform/generic/sifive/Kconfig b/platform/generic/sifive/Kconfig
> index 2cbcea08..efb00c02 100644
> --- a/platform/generic/sifive/Kconfig
> +++ b/platform/generic/sifive/Kconfig
> @@ -1,5 +1,10 @@
> # SPDX-License-Identifier: BSD-2-Clause
>
> +
> +config SIFIVE_SMC0
> + bool "SiFive SMC v0 driver support"
> + default n
> +
> config SIFIVE_TMC0
> bool "SiFive TMC v0 driver support"
> default n
> diff --git a/platform/generic/sifive/objects.mk b/platform/generic/sifive/objects.mk
> index f8938713..28001453 100644
> --- a/platform/generic/sifive/objects.mk
> +++ b/platform/generic/sifive/objects.mk
> @@ -11,4 +11,6 @@ platform-objs-$(CONFIG_PLATFORM_SIFIVE_FU540) += sifive/fu540.o
> carray-platform_override_modules-$(CONFIG_PLATFORM_SIFIVE_FU740) += sifive_fu740
> platform-objs-$(CONFIG_PLATFORM_SIFIVE_FU740) += sifive/fu740.o
>
> +carray-fdt_early_drivers-$(CONFIG_SIFIVE_SMC0) += fdt_sifive_smc0
> +platform-objs-$(CONFIG_SIFIVE_SMC0) += sifive/pmdomain/fdt_pmdomain_sifive_smc0.o
> platform-objs-$(CONFIG_SIFIVE_TMC0) += sifive/pmdomain/fdt_pmdomain_sifive_tmc0.o
> diff --git a/platform/generic/sifive/pmdomain/fdt_pmdomain_sifive_smc0.c b/platform/generic/sifive/pmdomain/fdt_pmdomain_sifive_smc0.c
> new file mode 100644
> index 00000000..6c6d5d1f
> --- /dev/null
> +++ b/platform/generic/sifive/pmdomain/fdt_pmdomain_sifive_smc0.c
> @@ -0,0 +1,322 @@
> +/*
> + * SPDX-License-Identifier: BSD-2-Clause
> + *
> + * Copyright (c) 2025 SiFive
> + */
> +
> +#include <libfdt.h>
> +#include <sbi/riscv_asm.h>
> +#include <sbi/riscv_io.h>
> +#include <sbi/sbi_console.h>
> +#include <sbi/sbi_domain.h>
> +#include <sbi/sbi_error.h>
> +#include <sbi/sbi_hart.h>
> +#include <sbi/sbi_hsm.h>
> +#include <sbi/sbi_system.h>
> +#include <sbi/sbi_timer.h>
> +#include <sbi_utils/cache/fdt_cmo_helper.h>
> +#include <sbi_utils/fdt/fdt_driver.h>
> +#include <sbi_utils/fdt/fdt_helper.h>
> +#include <sbi_utils/timer/aclint_mtimer.h>
> +#include <sifive/sifive_inst.h>
> +#include <sifive/sifive_smc0.h>
> +#include <sifive/sifive_tmc0.h>
> +
> +#define SIFIVE_SMC_PGPREP_OFF 0x0
> +#define SIFIVE_SMC_PG_OFF 0x4
> +#define SIFIVE_SMC_CCTIMER_OFF 0xc
> +#define SIFIVE_SMC_RESUMEPC_LO_OFF 0x10
> +#define SIFIVE_SMC_RESUMEPC_HI_OFF 0x14
> +#define SIFIVE_SMC_SYNC_PMC_OFF 0x24
> +#define SIFIVE_SMC_CYCLECOUNT_LO_OFF 0x28
> +#define SIFIVE_SMC_CYCLECOUNT_HI_OFF 0x2c
> +#define SIFIVE_SMC_WFI_UNCORE_CG_OFF 0x50
> +
> +#define SIFIVE_SMC_PGPREP_ENA_REQ BIT(31)
> +#define SIFIVE_SMC_PGPREP_ENA_ACK BIT(30)
> +#define SIFIVE_SMC_PGPREP_DIS_REQ BIT(29)
> +#define SIFIVE_SMC_PGPREP_DIS_ACK BIT(29)
> +#define SIFIVE_SMC_PGPREP_FRONTNOTQ BIT(19)
> +#define SIFIVE_SMC_PGPREP_CLFPNOTQ BIT(18)
> +#define SIFIVE_SMC_PGPREP_PMCENAERR BIT(17)
> +#define SIFIVE_SMC_PGPREP_WAKE_DETECT BIT(16)
> +#define SIFIVE_SMC_PGPREP_BUSERR BIT(15)
> +#define SIFIVE_SMC_PGPREP_EARLY_ABORT BIT(3)
> +#define SIFIVE_SMC_PGPREP_INTERNAL_ABORT BIT(2)
> +#define SIFIVE_SMC_PGPREP_ENARSP (SIFIVE_SMC_PGPREP_FRONTNOTQ | \
> + SIFIVE_SMC_PGPREP_CLFPNOTQ | \
> + SIFIVE_SMC_PGPREP_PMCENAERR | \
> + SIFIVE_SMC_PGPREP_WAKE_DETECT | \
> + SIFIVE_SMC_PGPREP_BUSERR)
> +
> +#define SIFIVE_SMC_PGPREP_ABORT (SIFIVE_SMC_PGPREP_EARLY_ABORT | \
> + SIFIVE_SMC_PGPREP_INTERNAL_ABORT)
> +
> +#define SIFIVE_SMC_PG_ENA_REQ BIT(31)
> +#define SIFIVE_SMC_PG_WARM_RESET BIT(1)
> +
> +#define SIFIVE_SMC_SYNCPMC_SYNC_REQ BIT(31)
> +#define SIFIVE_SMC_SYNCPMC_SYNC_WREQ BIT(30)
> +#define SIFIVE_SMC_SYNCPMC_SYNC_ACK BIT(29)
> +
> +static struct aclint_mtimer_data smc_sync_timer;
> +static unsigned long smc0_base;
> +
> +static void sifive_smc0_set_pmcsync(char regid, bool write_mode)
> +{
> + unsigned long addr = smc0_base + SIFIVE_SMC_SYNC_PMC_OFF;
> + u32 v = regid | SIFIVE_SMC_SYNCPMC_SYNC_REQ;
> +
> + if (write_mode)
> + v |= SIFIVE_SMC_SYNCPMC_SYNC_WREQ;
> +
> + writel(v, (void *)addr);
> + while (!(readl((void *)addr) & SIFIVE_SMC_SYNCPMC_SYNC_ACK));
> +}
> +
> +static u64 sifive_smc0_time_read(volatile u64 *addr)
> +{
> + u32 lo, hi;
> +
> + do {
> + sifive_smc0_set_pmcsync(SIFIVE_SMC_CYCLECOUNT_LO_OFF, false);
> + sifive_smc0_set_pmcsync(SIFIVE_SMC_CYCLECOUNT_HI_OFF, false);
> + hi = readl_relaxed((u32 *)addr + 1);
> + lo = readl_relaxed((u32 *)addr);
> + } while (hi != readl_relaxed((u32 *)addr + 1));
> +
> + return ((u64)hi << 32) | (u64)lo;
> +}
> +
> +static void sifive_smc0_set_resumepc(physical_addr_t raddr)
> +{
> + /* Set resumepc_lo */
> + writel((u32)raddr, (void *)(smc0_base + SIFIVE_SMC_RESUMEPC_LO_OFF));
> + /* copy resumepc_lo from SMC to PMC */
> + sifive_smc0_set_pmcsync(SIFIVE_SMC_RESUMEPC_LO_OFF, true);
> +
> + /* Set resumepc_hi */
> + writel((u32)(raddr >> 32), (void *)(smc0_base + SIFIVE_SMC_RESUMEPC_HI_OFF));
> + /* copy resumepc_hi from SMC to PMC */
> + sifive_smc0_set_pmcsync(SIFIVE_SMC_RESUMEPC_HI_OFF, true);
> +}
> +
> +static u32 sifive_smc0_get_pgprep_enarsp(void)
> +{
> + u32 v = readl((void *)(smc0_base + SIFIVE_SMC_PGPREP_OFF));
> +
> + return v & SIFIVE_SMC_PGPREP_ENARSP;
> +}
> +
> +static void sifive_smc0_set_pgprep_disreq(void)
> +{
> + unsigned long addr = smc0_base + SIFIVE_SMC_PGPREP_OFF;
> + u32 v = readl((void *)addr);
> +
> + writel(v | SIFIVE_SMC_PGPREP_DIS_REQ, (void *)addr);
> + while (!(readl((void *)addr) & SIFIVE_SMC_PGPREP_DIS_ACK));
> +}
> +
> +static u32 sifive_smc0_set_pgprep_enareq(void)
> +{
> + unsigned long addr = smc0_base + SIFIVE_SMC_PGPREP_OFF;
> + u32 v = readl((void *)addr);
> +
> + writel(v | SIFIVE_SMC_PGPREP_ENA_REQ, (void *)addr);
> + while (!(readl((void *)addr) & SIFIVE_SMC_PGPREP_ENA_ACK));
> +
> + v = readl((void *)addr);
> +
> + return v & SIFIVE_SMC_PGPREP_ABORT;
> +}
> +
> +static void sifive_smc0_set_pg_enareq(void)
> +{
> + unsigned long addr = smc0_base + SIFIVE_SMC_PG_OFF;
> + u32 v = readl((void *)addr);
> +
> + writel(v | SIFIVE_SMC_PG_ENA_REQ, (void *)addr);
> +}
> +
> +bool sifive_smc0_check_warm_reset(void)
> +{
> + unsigned long addr = smc0_base + SIFIVE_SMC_PG_OFF;
> +
> + if (!smc0_base)
> + return false;
> +
> + sifive_smc0_set_pmcsync(SIFIVE_SMC_PG_OFF, false);
> +
> + return readl((void *)addr) & SIFIVE_SMC_PG_WARM_RESET ? true : false;
> +}
> +
> +static inline void sifive_smc0_set_cg(bool enable)
> +{
> + unsigned long addr = smc0_base + SIFIVE_SMC_WFI_UNCORE_CG_OFF;
> +
> + if (enable)
> + writel(0, (void *)addr);
> + else
> + writel(1, (void *)addr);
> +}
> +
> +static int sifive_smc0_prep(void)
> +{
> + const struct sbi_domain *dom = &root;
> + struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
> + unsigned long i;
> + int rc;
> + u32 target;
> +
> + if (!smc0_base)
> + return SBI_ENODEV;
> +
> + /* Prevent all secondary tiles from waking up from PG state */
> + sbi_hartmask_for_each_hartindex(i, dom->possible_harts) {
> + target = sbi_hartindex_to_hartid(i);
> + if (target != current_hartid()) {
> + rc = sifive_tmc0_set_wakemask_enareq(target);
> + if (rc) {
> + sbi_printf("Fail to enable wakemask for hart %d\n",
> + target);
> + goto fail;
> + }
> + }
> + }
> +
> + /* Check if all secondary tiles enter PG state */
> + sbi_hartmask_for_each_hartindex(i, dom->possible_harts) {
> + target = sbi_hartindex_to_hartid(i);
> + if (target != current_hartid() &&
> + !sifive_tmc0_is_pg(target)) {
> + sbi_printf("Hart %d not in the PG state\n", target);
> + goto fail;
> + }
> + }
> +
> + rc = sifive_smc0_set_pgprep_enareq();
> + if (rc) {
> + sbi_printf("SMC0 error: abort code: 0x%x\n", rc);
> + goto fail;
> + }
> +
> + rc = sifive_smc0_get_pgprep_enarsp();
> + if (rc) {
> + sifive_smc0_set_pgprep_disreq();
> + sbi_printf("SMC0 error: error response code: 0x%x\n", rc);
> + goto fail;
> + }
> +
> + sifive_smc0_set_resumepc(scratch->warmboot_addr);
> + return SBI_OK;
> +fail:
> + sbi_hartmask_for_each_hartindex(i, dom->possible_harts) {
> + target = sbi_hartindex_to_hartid(i);
> + if (target != current_hartid())
> + sifive_tmc0_set_wakemask_disreq(target);
> + }
> +
> + return SBI_EFAIL;
> +}
> +
> +static int sifive_smc0_enter(void)
> +{
> + const struct sbi_domain *dom = &root;
> + struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
> + unsigned long i;
> + u32 target, rc;
> +
> + /* Flush cache and check if there is wake detect or bus error */
> + if (fdt_cmo_llc_flush_all() &&
> + sbi_hart_has_extension(scratch, SBI_HART_EXT_XSF_CFLUSH_D_L1))
> + sifive_cflush();
> +
> + rc = sifive_smc0_get_pgprep_enarsp();
> + if (rc) {
> + sbi_printf("SMC0 error: error response code: 0x%x\n", rc);
> + rc = SBI_EFAIL;
> + goto fail;
> + }
> +
> + if (sbi_hart_has_extension(scratch, SBI_HART_EXT_XSF_CEASE)) {
> + sifive_smc0_set_pg_enareq();
> + while (1)
> + sifive_cease();
> + }
> +
> + rc = SBI_ENOTSUPP;
> +fail:
> + sifive_smc0_set_pgprep_disreq();
> + sbi_hartmask_for_each_hartindex(i, dom->possible_harts) {
> + target = sbi_hartindex_to_hartid(i);
> + if (target != current_hartid())
> + sifive_tmc0_set_wakemask_disreq(target);
> + }
> + return rc;
> +}
> +
> +static int sifive_smc0_pg(void)
> +{
> + int rc;
> +
> + rc = sifive_smc0_prep();
> + if (rc)
> + return rc;
> +
> + return sifive_smc0_enter();
> +}
> +
> +static int sifive_smc0_system_suspend(u32 sleep_type, unsigned long addr)
> +{
> + /* Disable the timer interrupt */
> + sbi_timer_exit(sbi_scratch_thishart_ptr());
> +
> + return sifive_smc0_pg();
> +}
> +
> +static int sifive_smc0_system_suspend_check(u32 sleep_type)
> +{
> + return sleep_type == SBI_SUSP_SLEEP_TYPE_SUSPEND ? SBI_OK : SBI_EINVAL;
> +}
> +
> +static struct sbi_system_suspend_device smc0_sys_susp = {
> + .name = "Sifive SMC0",
> + .system_suspend_check = sifive_smc0_system_suspend_check,
> + .system_suspend = sifive_smc0_system_suspend,
> +};
> +
> +void sifive_smc0_mtime_update(void)
> +{
> + struct aclint_mtimer_data *mt = aclint_get_mtimer_data();
> +
> + aclint_mtimer_update(mt, &smc_sync_timer);
> +}
> +
> +static int sifive_smc0_probe(const void *fdt, int nodeoff, const struct fdt_match *match)
> +{
> + int rc;
> + u64 addr;
> +
> + rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &addr, NULL);
> + if (rc)
> + return rc;
> +
> + smc0_base = (unsigned long)addr;
> + smc_sync_timer.time_rd = sifive_smc0_time_read;
> + smc_sync_timer.mtime_addr = smc0_base + SIFIVE_SMC_CYCLECOUNT_LO_OFF;
> +
> + sbi_system_suspend_set_device(&smc0_sys_susp);
> + sifive_smc0_set_cg(true);
> +
> + return SBI_OK;
> +}
> +
> +static const struct fdt_match sifive_smc0_match[] = {
> + { .compatible = "sifive,smc0" },
> + { },
> +};
> +
> +const struct fdt_driver fdt_sifive_smc0 = {
> + .match_table = sifive_smc0_match,
> + .init = sifive_smc0_probe,
> +};
> diff --git a/platform/generic/sifive/pmdomain/fdt_pmdomain_sifive_tmc0.c b/platform/generic/sifive/pmdomain/fdt_pmdomain_sifive_tmc0.c
> index 70d02b33..359c0be7 100644
> --- a/platform/generic/sifive/pmdomain/fdt_pmdomain_sifive_tmc0.c
> +++ b/platform/generic/sifive/pmdomain/fdt_pmdomain_sifive_tmc0.c
> @@ -18,7 +18,9 @@
> #include <sbi_utils/fdt/fdt_driver.h>
> #include <sbi_utils/fdt/fdt_helper.h>
> #include <sbi_utils/ipi/aclint_mswi.h>
> +#include <sbi_utils/irqchip/aplic.h>
> #include <sifive/sifive_inst.h>
> +#include <sifive/sifive_smc0.h>
> #include <sifive/sifive_tmc0.h>
>
> struct sifive_tmc0 {
> @@ -79,6 +81,73 @@ static unsigned long tmc0_offset;
> #define SIFIVE_TMC_WAKE_MASK_WREQ BIT(31)
> #define SIFIVE_TMC_WAKE_MASK_ACK BIT(30)
>
> +int sifive_tmc0_set_wakemask_enareq(u32 hartid)
> +{
> + struct sbi_scratch *scratch = sbi_hartid_to_scratch(hartid);
> + struct sifive_tmc0 *tmc0 = tmc0_ptr_get(scratch);
> + unsigned long addr;
> + u32 v;
> +
> + if (!tmc0)
> + return SBI_ENODEV;
> +
> + addr = tmc0->reg + SIFIVE_TMC_WAKE_MASK_OFF;
> + v = readl((void *)addr);
> + writel(v | SIFIVE_TMC_WAKE_MASK_WREQ, (void *)addr);
> +
> + while (!(readl((void *)addr) & SIFIVE_TMC_WAKE_MASK_ACK));
> +
> + return SBI_OK;
> +}
> +
> +void sifive_tmc0_set_wakemask_disreq(u32 hartid)
> +{
> + struct sbi_scratch *scratch = sbi_hartid_to_scratch(hartid);
> + struct sifive_tmc0 *tmc0 = tmc0_ptr_get(scratch);
> + unsigned long addr;
> + u32 v;
> +
> + if (!tmc0)
> + return;
> +
> + addr = tmc0->reg + SIFIVE_TMC_WAKE_MASK_OFF;
> + v = readl((void *)addr);
> + writel(v & ~SIFIVE_TMC_WAKE_MASK_WREQ, (void *)addr);
> +
> + while (readl((void *)addr) & SIFIVE_TMC_WAKE_MASK_ACK);
> +}
> +
> +bool sifive_tmc0_is_pg(u32 hartid)
> +{
> + struct sbi_scratch *scratch = sbi_hartid_to_scratch(hartid);
> + struct sifive_tmc0 *tmc0 = tmc0_ptr_get(scratch);
> + unsigned long addr;
> + u32 v;
> +
> + if (!tmc0)
> + return false;
> +
> + addr = tmc0->reg + SIFIVE_TMC_PG_OFF;
> + v = readl((void *)addr);
> + if (!(v & SIFIVE_TMC_PG_ENA_ACK) ||
> + (v & SIFIVE_TMC_PG_ENARSP) ||
> + (v & SIFIVE_TMC_PG_DIS_REQ))
> + return false;
> +
> + return true;
> +}
> +
> +static bool sifive_tmc0_check_warm_reset(void)
> +{
> + struct sifive_tmc0 *tmc0 = tmc0_ptr_get(sbi_scratch_thishart_ptr());
> + unsigned long addr = tmc0->reg + SIFIVE_TMC_PG_OFF;
> +
> + if (!tmc0)
> + return false;
> +
> + return readl((void *)addr) & SIFIVE_TMC_PG_WARM_RESET ? true : false;
> +}
> +
> static void sifive_tmc0_set_resumepc(physical_addr_t addr)
> {
> struct sifive_tmc0 *tmc0 = tmc0_ptr_get(sbi_scratch_thishart_ptr());
> @@ -354,10 +423,29 @@ static int sifive_tmc0_stop(void)
> return SBI_OK;
> }
>
> +static void sifive_pg_resume(void)
> +{
> + /*
> + * To simplify the implementation, the SW won't clear the warm
> + * reset bit. If the system woken up from the Core Complex power
> + * gating, the warm reset bit of the TMC0 will be clear. Therefore
> + * we need to check the warm reset bit of the TMC0 first as the
> + * Tile power gating won't clear the warm reset bit of the SMC0.
> + */
> + if (sifive_tmc0_check_warm_reset())
> + return;
> +
> + if (sifive_smc0_check_warm_reset()) {
> + aplic_restore();
> + sifive_smc0_mtime_update();
> + }
> +}
> +
> static struct sbi_hsm_device tmc0_hsm_dev = {
> .name = "SiFive TMC0",
> .hart_start = sifive_tmc0_start,
> .hart_stop = sifive_tmc0_stop,
> + .hart_resume = sifive_pg_resume,
> };
>
> int sifive_tmc0_cold_init(void)
> --
> 2.17.1
>
More information about the opensbi
mailing list