diff --git a/include/sbi_utils/hsm/fdt_hsm_sifive_inst.h b/include/sbi_utils/hsm/fdt_hsm_sifive_inst.h new file mode 100644 index 00000000..de8aba19 --- /dev/null +++ b/include/sbi_utils/hsm/fdt_hsm_sifive_inst.h @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 SiFive Inc. + */ + +#ifndef __FDT_HSM_SIFIVE_INST_H__ +#define __FDT_HSM_SIFIVE_INST_H__ + +static inline void sifive_cease(void) +{ + __asm__ __volatile__(".insn 0x30500073" ::: "memory"); +} + +static inline void sifive_cflush(void) +{ + __asm__ __volatile__(".insn 0xfc000073" ::: "memory"); +} + +#endif diff --git a/lib/utils/hsm/Kconfig b/lib/utils/hsm/Kconfig index 1384a5fa..94973c8f 100644 --- a/lib/utils/hsm/Kconfig +++ b/lib/utils/hsm/Kconfig @@ -14,6 +14,11 @@ config FDT_HSM_RPMI depends on FDT_MAILBOX && RPMI_MAILBOX default n +config FDT_HSM_SIFIVE_TMC0 + bool "FDT SiFive TMC v0 driver" + depends on FDT_CACHE + default n + config FDT_HSM_SPACEMIT bool "FDT SPACEMIT HSM driver" default n diff --git a/lib/utils/hsm/fdt_hsm_sifive_tmc0.c b/lib/utils/hsm/fdt_hsm_sifive_tmc0.c new file mode 100644 index 00000000..8b08a7d1 --- /dev/null +++ b/lib/utils/hsm/fdt_hsm_sifive_tmc0.c @@ -0,0 +1,310 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 SiFive + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct sifive_tmc0 { + unsigned long reg; + struct sbi_dlist node; + u32 id; +}; + +static SBI_LIST_HEAD(tmc0_list); +static unsigned long tmc0_offset; + +#define tmc0_ptr_get(__scratch) \ + sbi_scratch_read_type((__scratch), struct sifive_tmc0 *, tmc0_offset) + +#define tmc0_ptr_set(__scratch, __tmc0) \ + sbi_scratch_write_type((__scratch), struct sifive_tmc0 *, tmc0_offset, (__tmc0)) + +/* TMC.PGPREP */ +#define SIFIVE_TMC_PGPREP_OFF 0x0 +#define SIFIVE_TMC_PGPREP_ENA_REQ BIT(31) +#define SIFIVE_TMC_PGPREP_ENA_ACK BIT(30) +#define SIFIVE_TMC_PGPREP_DIS_REQ BIT(29) +#define SIFIVE_TMC_PGPREP_DIS_ACK BIT(28) +#define SIFIVE_TMC_PGPREP_CLFPNOTQ BIT(18) +#define SIFIVE_TMC_PGPREP_PMCENAERR BIT(17) +#define SIFIVE_TMC_PGPREP_PMCDENY BIT(16) +#define SIFIVE_TMC_PGPREP_BUSERR BIT(15) +#define SIFIVE_TMC_PGPREP_WAKE_DETECT BIT(12) +#define SIFIVE_TMC_PGPREP_INTERNAL_ABORT BIT(2) +#define SIFIVE_TMC_PGPREP_ENARSP (SIFIVE_TMC_PGPREP_CLFPNOTQ | \ + SIFIVE_TMC_PGPREP_PMCENAERR | \ + SIFIVE_TMC_PGPREP_PMCDENY | \ + SIFIVE_TMC_PGPREP_BUSERR | \ + SIFIVE_TMC_PGPREP_WAKE_DETECT) + +/* TMC.PG */ +#define SIFIVE_TMC_PG_OFF 0x4 +#define SIFIVE_TMC_PG_ENA_REQ BIT(31) +#define SIFIVE_TMC_PG_ENA_ACK BIT(30) +#define SIFIVE_TMC_PG_DIS_REQ BIT(29) +#define SIFIVE_TMC_PG_DIS_ACK BIT(28) +#define SIFIVE_TMC_PG_PMC_ENA_ERR BIT(17) +#define SIFIVE_TMC_PG_PMC_DENY BIT(16) +#define SIFIVE_TMC_PG_BUS_ERR BIT(15) +#define SIFIVE_TMC_PG_MASTNOTQ BIT(14) +#define SIFIVE_TMC_PG_WARM_RESET BIT(1) +#define SIFIVE_TMC_PG_ENARSP (SIFIVE_TMC_PG_PMC_ENA_ERR | \ + SIFIVE_TMC_PG_PMC_DENY | \ + SIFIVE_TMC_PG_BUS_ERR | \ + SIFIVE_TMC_PG_MASTNOTQ) + +/* TMC.RESUMEPC */ +#define SIFIVE_TMC_RESUMEPC_LO 0x10 +#define SIFIVE_TMC_RESUMEPC_HI 0x14 + +/* TMC.WAKEMASK */ +#define SIFIVE_TMC_WAKE_MASK_OFF 0x20 +#define SIFIVE_TMC_WAKE_MASK_WREQ BIT(31) +#define SIFIVE_TMC_WAKE_MASK_ACK BIT(30) + +static void sifive_tmc0_set_resumepc(physical_addr_t addr) +{ + struct sifive_tmc0 *tmc0 = tmc0_ptr_get(sbi_scratch_thishart_ptr()); + + writel((u32)addr, (void *)(tmc0->reg + SIFIVE_TMC_RESUMEPC_LO)); +#if __riscv_xlen > 32 + writel((u32)(addr >> 32), (void *)(tmc0->reg + SIFIVE_TMC_RESUMEPC_HI)); +#endif +} + +static u32 sifive_tmc0_set_pgprep_enareq(void) +{ + struct sifive_tmc0 *tmc0 = tmc0_ptr_get(sbi_scratch_thishart_ptr()); + unsigned long reg = tmc0->reg + SIFIVE_TMC_PGPREP_OFF; + u32 v = readl((void *)reg); + + writel(v | SIFIVE_TMC_PGPREP_ENA_REQ, (void *)reg); + while (!(readl((void *)reg) & SIFIVE_TMC_PGPREP_ENA_ACK)); + + v = readl((void *)reg); + return v & SIFIVE_TMC_PGPREP_INTERNAL_ABORT; +} + +static void sifive_tmc0_set_pgprep_disreq(void) +{ + struct sifive_tmc0 *tmc0 = tmc0_ptr_get(sbi_scratch_thishart_ptr()); + unsigned long reg = tmc0->reg + SIFIVE_TMC_PGPREP_OFF; + u32 v = readl((void *)reg); + + writel(v | SIFIVE_TMC_PGPREP_DIS_REQ, (void *)reg); + while (!(readl((void *)reg) & SIFIVE_TMC_PGPREP_DIS_ACK)); +} + +static u32 sifive_tmc0_get_pgprep_enarsp(void) +{ + struct sifive_tmc0 *tmc0 = tmc0_ptr_get(sbi_scratch_thishart_ptr()); + unsigned long reg = tmc0->reg + SIFIVE_TMC_PGPREP_OFF; + u32 v = readl((void *)reg); + + return v & SIFIVE_TMC_PGPREP_ENARSP; +} + +static void sifive_tmc0_set_pg_enareq(void) +{ + struct sifive_tmc0 *tmc0 = tmc0_ptr_get(sbi_scratch_thishart_ptr()); + unsigned long reg = tmc0->reg + SIFIVE_TMC_PG_OFF; + u32 v = readl((void *)reg); + + writel(v | SIFIVE_TMC_PG_ENA_REQ, (void *)reg); +} + +static int sifive_tmc0_prep(void) +{ + struct sbi_scratch *scratch = sbi_scratch_thishart_ptr(); + u32 rc; + + if (!tmc0_ptr_get(scratch)) + return SBI_ENODEV; + + rc = sifive_tmc0_set_pgprep_enareq(); + if (rc) { + sbi_printf("TMC0 error: Internal Abort (Wake detect)\n"); + goto fail; + } + + rc = sifive_tmc0_get_pgprep_enarsp(); + if (rc) { + sifive_tmc0_set_pgprep_disreq(); + sbi_printf("TMC0 error: error response code: 0x%x\n", rc); + goto fail; + } + + sifive_tmc0_set_resumepc(scratch->warmboot_addr); + + return SBI_OK; + +fail: + return SBI_EFAIL; +} + +static int sifive_tmc0_enter(void) +{ + struct sbi_scratch *scratch = sbi_scratch_thishart_ptr(); + u32 rc; + + /* Flush cache and check if there is wake detect or bus error */ + if (fdt_cmo_private_flc_flush_all() && + sbi_hart_has_extension(scratch, SBI_HART_EXT_XSIFIVE_CFLUSH_D_L1)) + sifive_cflush(); + + rc = sifive_tmc0_get_pgprep_enarsp(); + if (rc) { + sbi_printf("TMC0 error: error response code: 0x%x\n", rc); + goto fail; + } + + if (sbi_hart_has_extension(scratch, SBI_HART_EXT_XSIFIVE_CEASE)) { + sifive_tmc0_set_pg_enareq(); + while (1) + sifive_cease(); + } + + rc = SBI_ENOTSUPP; +fail: + sifive_tmc0_set_pgprep_disreq(); + return rc; +} + +static int sifive_tmc0_tile_pg(void) +{ + int rc; + + rc = sifive_tmc0_prep(); + if (rc) + return rc; + + return sifive_tmc0_enter(); +} + +static int sifive_tmc0_start(u32 hartid, ulong saddr) +{ + /* + * In system suspend, the IMSIC will be reset in SiFive platform so + * we use the CLINT IPI as the wake event. + */ + sbi_ipi_raw_send(sbi_hartid_to_hartindex(hartid), true); + + return SBI_OK; +} + +static int sifive_tmc0_stop(void) +{ + unsigned long mie = csr_read(CSR_MIE); + int rc; + /* Set IPI as wake up source */ + csr_set(CSR_MIE, MIP_MEIP | MIP_MSIP); + + rc = sifive_tmc0_tile_pg(); + if (rc) { + csr_write(CSR_MIE, mie); + return rc; + } + + return SBI_OK; +} + +static struct sbi_hsm_device tmc0_hsm_dev = { + .name = "SiFive TMC0", + .hart_start = sifive_tmc0_start, + .hart_stop = sifive_tmc0_stop, +}; + +static int sifive_tmc0_bind_cpu(struct sifive_tmc0 *tmc0) +{ + const void *fdt = fdt_get_address(); + struct sbi_scratch *scratch; + int cpus_off, cpu_off, rc; + const fdt32_t *val; + u32 hartid; + + cpus_off = fdt_path_offset(fdt, "/cpus"); + if (cpus_off < 0) + return SBI_ENOENT; + + fdt_for_each_subnode(cpu_off, fdt, cpus_off) { + rc = fdt_parse_hart_id(fdt, cpu_off, &hartid); + if (rc) + continue; + + scratch = sbi_hartid_to_scratch(hartid); + if (!scratch) + continue; + + val = fdt_getprop(fdt, cpu_off, "power-domains", NULL); + if (!val) + return SBI_ENOENT; + + if (tmc0->id == fdt32_to_cpu(val[0])) { + tmc0_ptr_set(scratch, tmc0); + return SBI_OK; + } + } + + return SBI_ENODEV; +} + +static int sifive_tmc0_probe(const void *fdt, int nodeoff, const struct fdt_match *match) +{ + struct sifive_tmc0 *tmc0; + u64 addr; + int rc; + + if (!tmc0_offset) { + tmc0_offset = sbi_scratch_alloc_type_offset(struct sifive_tmc0 *); + if (!tmc0_offset) + return SBI_ENOMEM; + + sbi_hsm_set_device(&tmc0_hsm_dev); + } + + tmc0 = sbi_zalloc(sizeof(*tmc0)); + if (!tmc0) + return SBI_ENOMEM; + + rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &addr, NULL); + if (rc) + goto free_tmc0; + + tmc0->reg = (unsigned long)addr; + tmc0->id = fdt_get_phandle(fdt_get_address(), nodeoff); + + rc = sifive_tmc0_bind_cpu(tmc0); + if (rc) + goto free_tmc0; + + return SBI_OK; + +free_tmc0: + sbi_free(tmc0); + return rc; +} + +static const struct fdt_match sifive_tmc0_match[] = { + { .compatible = "sifive,tmc0" }, + { }, +}; + +const struct fdt_driver fdt_hsm_sifive_tmc0 = { + .match_table = sifive_tmc0_match, + .init = sifive_tmc0_probe, +}; diff --git a/lib/utils/hsm/objects.mk b/lib/utils/hsm/objects.mk index 6c15741b..0d005449 100644 --- a/lib/utils/hsm/objects.mk +++ b/lib/utils/hsm/objects.mk @@ -12,3 +12,6 @@ libsbiutils-objs-$(CONFIG_FDT_HSM_RPMI) += hsm/fdt_hsm_rpmi.o carray-fdt_early_drivers-$(CONFIG_FDT_HSM_SPACEMIT) += fdt_hsm_spacemit libsbiutils-objs-$(CONFIG_FDT_HSM_SPACEMIT) += hsm/fdt_hsm_spacemit.o + +carray-fdt_early_drivers-$(CONFIG_FDT_HSM_SIFIVE_TMC0) += fdt_hsm_sifive_tmc0 +libsbiutils-objs-$(CONFIG_FDT_HSM_SIFIVE_TMC0) += hsm/fdt_hsm_sifive_tmc0.o \ No newline at end of file diff --git a/platform/generic/configs/defconfig b/platform/generic/configs/defconfig index c074518a..e88332ef 100644 --- a/platform/generic/configs/defconfig +++ b/platform/generic/configs/defconfig @@ -20,6 +20,7 @@ CONFIG_FDT_GPIO_SIFIVE=y CONFIG_FDT_GPIO_STARFIVE=y CONFIG_FDT_HSM=y CONFIG_FDT_HSM_RPMI=y +CONFIG_FDT_HSM_SIFIVE_TMC0=y CONFIG_FDT_I2C=y CONFIG_FDT_I2C_SIFIVE=y CONFIG_FDT_I2C_DW=y