diff mbox series

[v3,12/12] platform: sifive: Add SiFive SMC0 driver

Message ID 20250708074940.10904-13-nick.hu@sifive.com
State Changes Requested
Headers show
Series Add SiFive TMC0 and SMC0 driver | expand

Commit Message

Nick Hu July 8, 2025, 7:49 a.m. UTC
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

Comments

Anup Patel Aug. 4, 2025, 8:39 a.m. UTC | #1
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 mbox series

Patch

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)