diff --git a/include/sbi_utils/hsm/fdt_hsm_sifive_tmc0.h b/include/sbi_utils/hsm/fdt_hsm_sifive_tmc0.h new file mode 100644 index 00000000..06cfb390 --- /dev/null +++ b/include/sbi_utils/hsm/fdt_hsm_sifive_tmc0.h @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 SiFive Inc. + */ + +#ifndef __FDT_HSM_SIFIVE_TMC0_H__ +#define __FDT_HSM_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); + +#endif diff --git a/lib/utils/hsm/fdt_hsm_sifive_tmc0.c b/lib/utils/hsm/fdt_hsm_sifive_tmc0.c index 8b08a7d1..2690a638 100644 --- a/lib/utils/hsm/fdt_hsm_sifive_tmc0.c +++ b/lib/utils/hsm/fdt_hsm_sifive_tmc0.c @@ -18,6 +18,7 @@ #include #include #include +#include struct sifive_tmc0 { unsigned long reg; @@ -77,6 +78,62 @@ 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 void sifive_tmc0_set_resumepc(physical_addr_t addr) { struct sifive_tmc0 *tmc0 = tmc0_ptr_get(sbi_scratch_thishart_ptr()); diff --git a/lib/utils/suspend/Kconfig b/lib/utils/suspend/Kconfig index 2cbea75c..6c09d952 100644 --- a/lib/utils/suspend/Kconfig +++ b/lib/utils/suspend/Kconfig @@ -14,6 +14,10 @@ config FDT_SUSPEND_RPMI depends on FDT_MAILBOX && RPMI_MAILBOX default n +config FDT_SUSPEND_SIFIVE_SMC0 + bool "FDT SIFIVE SMC0 suspend driver" + depends on FDT_HSM_SIFIVE_TMC0 && IRQCHIP_APLIC + default n endif endmenu diff --git a/lib/utils/suspend/fdt_suspend_sifive_smc0.c b/lib/utils/suspend/fdt_suspend_sifive_smc0.c new file mode 100644 index 00000000..7a23e68b --- /dev/null +++ b/lib/utils/suspend/fdt_suspend_sifive_smc0.c @@ -0,0 +1,318 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 SiFive + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +#if __riscv_xlen > 32 + /* 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); +#endif +} + +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); +} + +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_XSIFIVE_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_XSIFIVE_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 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_system_suspend_check(u32 sleep_type) +{ + return sleep_type == SBI_SUSP_SLEEP_TYPE_SUSPEND ? SBI_OK : SBI_EINVAL; +} + +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 void sifive_smc0_system_resume(void) +{ + aplic_reinit_all(); + sifive_smc0_mtime_update(); +} + +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, + .system_resume = sifive_smc0_system_resume, +}; + +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_suspend_sifive_smc0 = { + .match_table = sifive_smc0_match, + .init = sifive_smc0_probe, +}; diff --git a/lib/utils/suspend/objects.mk b/lib/utils/suspend/objects.mk index 9c386248..1fb29b5e 100644 --- a/lib/utils/suspend/objects.mk +++ b/lib/utils/suspend/objects.mk @@ -9,3 +9,6 @@ carray-fdt_early_drivers-$(CONFIG_FDT_SUSPEND_RPMI) += fdt_suspend_rpmi libsbiutils-objs-$(CONFIG_FDT_SUSPEND_RPMI) += suspend/fdt_suspend_rpmi.o + +carray-fdt_early_drivers-$(CONFIG_FDT_SUSPEND_SIFIVE_SMC0) += fdt_suspend_sifive_smc0 +libsbiutils-objs-$(CONFIG_FDT_SUSPEND_SIFIVE_SMC0) += suspend/fdt_suspend_sifive_smc0.o diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig index e88332ef..569f9697 100644 --- a/platform/generic/configs/defconfig +++ b/platform/generic/configs/defconfig @@ -57,6 +57,7 @@ CONFIG_FDT_SERIAL_XILINX_UARTLITE=y CONFIG_SERIAL_SEMIHOSTING=y CONFIG_FDT_SUSPEND=y CONFIG_FDT_SUSPEND_RPMI=y +CONFIG_FDT_SUSPEND_SIFIVE_SMC0=y CONFIG_FDT_TIMER=y CONFIG_FDT_TIMER_MTIMER=y CONFIG_FDT_TIMER_PLMT=y