| Message ID | 20250708074940.10904-13-nick.hu@sifive.com |
|---|---|
| State | Changes Requested |
| Headers | show |
| Series | Add SiFive TMC0 and SMC0 driver | expand |
On Tue, Jul 8, 2025 at 1:20 PM Nick Hu <nick.hu@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@sifive.com> > Reviewed-by: Cyan Yang <cyan.yang@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 >
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)