[RFC PATCH 2/2] platform: Add HSM implementation for Allwinner D1
Xiang W
wxjstz at 126.com
Fri May 6 04:29:38 PDT 2022
在 2022-05-06星期五的 01:57 -0500,Samuel Holland写道:
> Allwinner D1 contains a "PPU" power domain controller which can
> automatically power down/up the CPU power domain. This power domain
> includes the C906 core along with its bundled PLIC and CLINT.
>
> While this is a fully functioning HSM implementation, there are some
> open design concerns:
> 0) Is OpenSBI even the right place to save/restore the PLIC registers?
> 1) How much of the PLIC register save/restore code can be shared with
> the PLIC driver?
> 2) Should the HSM resume path call into irqchip, timer, etc. drivers?
> 3) If we make this code more general, how do we allocate space for the
> saved registers?
> 4) How much of this code can be shared with the C910 platform?
> 5) Linux/the OS should be setting the wakeup mask, not OpenSBI, because
> the mask is also used (with different contents) for system suspend.
>
> I have added some additonal commentary and explanation of how the
> hardware works as C++ style comments.
>
> Signed-off-by: Samuel Holland <samuel at sholland.org>
> ---
>
> platform/generic/objects.mk | 1 +
> platform/generic/platform.c | 2 +
> platform/generic/sun20i_d1.c | 225 +++++++++++++++++++++++++++++++++++
> 3 files changed, 228 insertions(+)
> create mode 100644 platform/generic/sun20i_d1.c
>
> diff --git a/platform/generic/objects.mk b/platform/generic/objects.mk
> index cb15a18..d8c8980 100644
> --- a/platform/generic/objects.mk
> +++ b/platform/generic/objects.mk
> @@ -10,3 +10,4 @@
> platform-objs-y += platform.o
> platform-objs-y += sifive_fu540.o
> platform-objs-y += sifive_fu740.o
> +platform-objs-y += sun20i_d1.o
> diff --git a/platform/generic/platform.c b/platform/generic/platform.c
> index 8a4fb70..e719535 100644
> --- a/platform/generic/platform.c
> +++ b/platform/generic/platform.c
> @@ -26,10 +26,12 @@
>
> extern const struct platform_override sifive_fu540;
> extern const struct platform_override sifive_fu740;
> +extern const struct platform_override sun20i_d1;
>
> static const struct platform_override *special_platforms[] = {
> &sifive_fu540,
> &sifive_fu740,
> + &sun20i_d1,
> };
>
> static const struct platform_override *generic_plat = NULL;
> diff --git a/platform/generic/sun20i_d1.c b/platform/generic/sun20i_d1.c
> new file mode 100644
> index 0000000..4375bb9
> --- /dev/null
> +++ b/platform/generic/sun20i_d1.c
> @@ -0,0 +1,225 @@
> +/*
> + * SPDX-License-Identifier: BSD-2-Clause
> + *
> + * Copyright (c) 2022 Samuel Holland <samuel at sholland.org>
> + */
> +
> +#include <platform_override.h>
> +#include <sbi/riscv_io.h>
> +#include <sbi/sbi_bitops.h>
> +#include <sbi/sbi_hsm.h>
> +#include <sbi_utils/fdt/fdt_helper.h>
> +
> +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
ARRAY_SIZE has be defined in include/sbi/sbi_types.h
Regards,
Xiang W
> +
> +#define CSR_MXSTATUS 0x7c0
> +#define CSR_MHCR 0x7c1
> +#define CSR_MCOR 0x7c2
> +#define CSR_MHINT 0x7c5
> +
> +static unsigned long csrs[3];
> +
> +static void sun20i_d1_csr_save(void)
> +{
> + /* Save custom CSRs. */
> + csrs[0] = csr_read(CSR_MXSTATUS);
> + csrs[1] = csr_read(CSR_MHCR);
> + csrs[2] = csr_read(CSR_MHINT);
> +
> + /* Flush and disable caches. */
> + csr_write(CSR_MCOR, 0x22);
> + csr_write(CSR_MHCR, 0x0);
> + mb();
> + RISCV_FENCE_I;
> +}
> +
> +static void sun20i_d1_csr_restore(void)
> +{
> + /* Invalidate caches and branch predictor. */
> + csr_write(CSR_MCOR, 0x70013);
> + mb();
> + RISCV_FENCE_I;
> +
> + /* Restore custom CSRs. */
> + // ...which re-enables the caches.
> + csr_write(CSR_MXSTATUS, csrs[0]);
> + csr_write(CSR_MHCR, csrs[1]);
> + csr_write(CSR_MHINT, csrs[2]);
> +}
> +
> +// D1 loses all PLIC registers when the CPU is powered down.
> +
> +#define SUN20I_D1_PLIC_BASE ((void *)0x10000000)
> +
> +#define PLIC_PRIORITY_BASE 0x0
> +#define PLIC_ENABLE_BASE 0x2000
> +#define PLIC_ENABLE_STRIDE 0x80
> +#define PLIC_CONTEXT_BASE 0x200000
> +#define PLIC_CONTEXT_STRIDE 0x1000
> +
> +#define THEAD_PLIC_CTRL_REG 0x001ffffc
> +
> +// There are 160 "real" IRQs, numbered 16-175. These are the ones that map to
> +// the wakeup mask registers. The 16 IRQ offset is for GIC compatibility. So
> +// copying SIE to the wakeup mask should work, but needs some bit manipulation.
> +static u8 plic_prio[32 * 6];
> +static u32 plic_sie[6];
> +static u32 plic_th[2];
> +
> +static void sun20i_d1_plic_save(void)
> +{
> + for (int i = 0; i < ARRAY_SIZE(plic_prio); ++i)
> + plic_prio[i] = readl_relaxed(SUN20I_D1_PLIC_BASE + PLIC_PRIORITY_BASE + 4 * i);
> + for (int i = 0; i < ARRAY_SIZE(plic_sie); ++i)
> + plic_sie[i] = readl_relaxed(SUN20I_D1_PLIC_BASE + PLIC_ENABLE_BASE + PLIC_ENABLE_STRIDE * 1 + 4 * i);
> + for (int i = 0; i < ARRAY_SIZE(plic_th); ++i)
> + plic_th[i] = readl_relaxed(SUN20I_D1_PLIC_BASE + PLIC_CONTEXT_BASE + PLIC_CONTEXT_STRIDE * i);
> +}
> +
> +static void sun20i_d1_plic_restore(void)
> +{
> + for (int i = 0; i < ARRAY_SIZE(plic_prio); ++i)
> + writel_relaxed(plic_prio[i], SUN20I_D1_PLIC_BASE + PLIC_PRIORITY_BASE + 4 * i);
> + for (int i = 0; i < ARRAY_SIZE(plic_sie); ++i)
> + writel_relaxed(plic_sie[i], SUN20I_D1_PLIC_BASE + PLIC_ENABLE_BASE + PLIC_ENABLE_STRIDE * 1 + 4 * i);
> + for (int i = 0; i < ARRAY_SIZE(plic_th); ++i)
> + writel_relaxed(plic_th[i], SUN20I_D1_PLIC_BASE + PLIC_CONTEXT_BASE + PLIC_CONTEXT_STRIDE * i);
> +
> + // This is also set by the PLIC driver during cold boot.
> + writel_relaxed(BIT(0), SUN20I_D1_PLIC_BASE + THEAD_PLIC_CTRL_REG);
> +}
> +
> +#define SUN20I_D1_PPU_BASE ((void *)0x07001000)
> +
> +#define PPU_PD_REQUEST(i) (0x20 + 0x80 * (i))
> +#define PPU_PD_REQUEST_ON 1
> +#define PPU_PD_REQUEST_OFF 2
> +
> +#define PPU_PD_STATUS(i) (0x24 + 0x80 * (i))
> +#define PPU_PD_STATUS_ON 0x10000
> +#define PPU_PD_STATUS_OFF 0x20000
> +#define PPU_PD_STATUS_TRANS_COMPLETE BIT(1)
> +
> +#define PPU_PD_ACTIVE_CTRL(i) (0x2c + 0x80 * (i))
> +
> +#define SUN20I_D1_PRCM_BASE ((void *)0x07010000)
> +
> +#define PPU_BGR_REG 0x1ac
> +#define PPU_BGR_VAL (BIT(16) | BIT(0))
> +
> +static void sun20i_d1_ppu_save(void)
> +{
> + /* Enable MMIO access (clock gate/reset). */
> + // Do not trust Linux to leave this clock enabled.
> + writel_relaxed(PPU_BGR_VAL, SUN20I_D1_PRCM_BASE + PPU_BGR_REG);
> +
> + /* Activate automatic power-down at the next WFI. */
> + writel_relaxed(1, SUN20I_D1_PPU_BASE + PPU_PD_ACTIVE_CTRL(0));
> +}
> +
> +static void sun20i_d1_ppu_restore(void)
> +{
> + /* Disable automatic power-down. */
> + writel_relaxed(0, SUN20I_D1_PPU_BASE + PPU_PD_ACTIVE_CTRL(0));
> +}
> +
> +#define SUN20I_D1_CCU_BASE ((void *)0x02001000)
> +
> +#define RISCV_CFG_BGR_REG 0xd0c
> +#define RISCV_CFG_BGR_VAL (BIT(16) | BIT(0))
> +
> +#define SUN20I_D1_RISCV_CFG_BASE ((void *)0x06010000)
> +
> +#define RESET_ENTRY_LO_REG 0x0004
> +#define RESET_ENTRY_HI_REG 0x0008
> +#define WAKEUP_EN_REG 0x0020
> +#define WAKEUP_MASK_REG(i) (0x0024 + 4 * (i))
> +
> +static void sun20i_d1_riscv_cfg_save(ulong raddr)
> +{
> + /* Enable MMIO access (clock gate/reset). */
> + // Do not trust Linux to leave this clock enabled.
> + writel_relaxed(RISCV_CFG_BGR_VAL, SUN20I_D1_CCU_BASE + RISCV_CFG_BGR_REG);
> +
> + /* Program reset entry address. */
> + // Does this really need to be done for every suspend? The warmboot
> + // entry point does not move. Can we add a "set entry address" HSM
> + // driver hook that gets called only once?
> + writel_relaxed((u32)raddr, SUN20I_D1_RISCV_CFG_BASE + RESET_ENTRY_LO_REG);
> + writel_relaxed(raddr >> 32, SUN20I_D1_RISCV_CFG_BASE + RESET_ENTRY_HI_REG);
> +
> + /* Enable wakeup for all IRQs. */
> + // This should be left to Linux to set, or copied from the PLIC SIE.
> + for (int i = 0; i < 5; ++i)
> + writel_relaxed(0xffffffff, SUN20I_D1_RISCV_CFG_BASE + WAKEUP_MASK_REG(i));
> +
> + /* Enable PPU wakeup for IRQs. */
> + writel_relaxed(1, SUN20I_D1_RISCV_CFG_BASE + WAKEUP_EN_REG);
> +}
> +
> +static void sun20i_d1_riscv_cfg_restore(void)
> +{
> + /* Disable PPU wakeup for IRQs. */
> + writel_relaxed(0, SUN20I_D1_RISCV_CFG_BASE + WAKEUP_EN_REG);
> +}
> +
> +static int sun20i_d1_hart_suspend(u32 suspend_type, ulong raddr)
> +{
> + void (*warmboot)(void) = (void (*)(void))raddr;
> +
> + sun20i_d1_plic_save();
> + sun20i_d1_ppu_save();
> + sun20i_d1_riscv_cfg_save(raddr);
> + sun20i_d1_csr_save();
> +
> + /* If no IRQ is pending, this will power down the CPU domain. */
> + // Power-down will only if both of the PPU and RISCV_CFG bits are set.
> + //
> + // PPU_PD_STATUS_TRANS_COMPLETE will be set if the domain was actually
> + // powered down and back up (PPU_PD_STATUS(0) will be 0x10007).
> + //
> + // Manual power-up or down can be requested with PPU_PD_REQUEST(0), but
> + // this is not too useful for a uniprocessor SoC.
> + wfi();
> +
> + /*
> + * An IRQ was already pending, so we did not power down.
> + * Jump directly to the warmboot entry point.
> + */
> + // Would be nice to mark this `noreturn`.
> + warmboot();
> +
> + return 0;
> +}
> +
> +static void sun20i_d1_hart_resume(void)
> +{
> + sun20i_d1_csr_restore();
> + sun20i_d1_riscv_cfg_restore();
> + sun20i_d1_ppu_restore();
> + sun20i_d1_plic_restore();
> +}
> +
> +static const struct sbi_hsm_device sun20i_d1_ppu = {
> + .name = "sun20i-d1-ppu",
> + .hart_suspend = sun20i_d1_hart_suspend,
> + .hart_resume = sun20i_d1_hart_resume,
> +};
> +
> +static int sun20i_d1_final_init(bool cold_boot, const struct fdt_match *match)
> +{
> + if (cold_boot)
> + sbi_hsm_set_device(&sun20i_d1_ppu);
> +
> + return 0;
> +}
> +
> +static const struct fdt_match sun20i_d1_match[] = {
> + { .compatible = "allwinner,sun20i-d1" },
> + { },
> +};
> +
> +const struct platform_override sun20i_d1 = {
> + .match_table = sun20i_d1_match,
> + .final_init = sun20i_d1_final_init,
> +};
> --
> 2.35.1
>
>
More information about the opensbi
mailing list