// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2016 * Author: Chen-Yu Tsai * * Based on assembly code by Marc Zyngier , * which was based on code by Carl van Schaik . */ #include #include #include #include #include #include #include #include #include #include #define __irq __attribute__ ((interrupt ("IRQ"))) #define GICD_BASE (SUNXI_GIC400_BASE + GIC_DIST_OFFSET) #define GICC_BASE (SUNXI_GIC400_BASE + GIC_CPU_OFFSET_A15) /* * Offsets into the CPUCFG block applicable to most SUNXIs. */ #define SUNXI_CPU_RST(cpu) (0x40 + (cpu) * 0x40 + 0x0) #define SUNXI_CPU_STATUS(cpu) (0x40 + (cpu) * 0x40 + 0x8) #define SUNXI_GEN_CTRL (0x184) #define SUNXI_PRIV0 (0x1a4) #define SUN7I_CPU1_PWR_CLAMP (0x1b0) #define SUN7I_CPU1_PWROFF (0x1b4) #define SUNXI_DBG_CTRL1 (0x1e4) /* * R40 is different from other single cluster SoCs. * * The power clamps are located in the unused space after the per-core * reset controls for core 3. The secondary core entry address register * is in the SRAM controller address range. */ #define SUN8I_R40_PWROFF (0x110) #define SUN8I_R40_PWR_CLAMP(cpu) (0x120 + (cpu) * 0x4) #define SUN8I_R40_SRAMC_SOFT_ENTRY_REG0 (0xbc) /* * R528 is also different, as it has both cores powered up (but held in reset * state) after the SoC is reset. Like the R40, it uses a "soft" entry point * address register, but unlike the R40, it uses a newer "CPUX" block to manage * CPU state, rather than the older CPUCFG system. */ #define SUN8I_R528_SOFT_ENTRY (0x1c8) #define SUN8I_R528_C0_RST_CTRL (0x0000) #define SUN8I_R528_C0_CTRL_REG0 (0x0010) #define SUN8I_R528_C0_CPU_STATUS (0x0080) #define SUN8I_R528_C0_STATUS_STANDBYWFI (16) /* Only newer cores have this additional IP block. */ #ifndef SUNXI_R_CPUCFG_BASE #define SUNXI_R_CPUCFG_BASE 0 #endif static void __secure cp15_write_cntp_tval(u32 tval) { asm volatile ("mcr p15, 0, %0, c14, c2, 0" : : "r" (tval)); } static void __secure cp15_write_cntp_ctl(u32 val) { asm volatile ("mcr p15, 0, %0, c14, c2, 1" : : "r" (val)); } static u32 __secure cp15_read_cntp_ctl(void) { u32 val; asm volatile ("mrc p15, 0, %0, c14, c2, 1" : "=r" (val)); return val; } #define ONE_MS (CONFIG_COUNTER_FREQUENCY / 1000) static void __secure __mdelay(u32 ms) { u32 reg = ONE_MS * ms; cp15_write_cntp_tval(reg); isb(); cp15_write_cntp_ctl(3); do { isb(); reg = cp15_read_cntp_ctl(); } while (!(reg & BIT(2))); cp15_write_cntp_ctl(0); isb(); } static void __secure clamp_release(u32 *clamp) { u32 tmp = 0x1ff; do { tmp >>= 1; writel(tmp, clamp); } while (tmp); __mdelay(10); } static void __secure clamp_set(u32 *clamp) { writel(0xff, clamp); } static void __secure sunxi_cpu_set_entry(int __always_unused cpu, void *entry) { if (IS_ENABLED(CONFIG_MACH_SUN8I_R40)) { writel((u32)entry, SUNXI_SRAMC_BASE + SUN8I_R40_SRAMC_SOFT_ENTRY_REG0); } else if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) { writel((u32)entry, SUNXI_R_CPUCFG_BASE + SUN8I_R528_SOFT_ENTRY); } else { writel((u32)entry, SUNXI_CPUCFG_BASE + SUNXI_PRIV0); } } static void __secure sunxi_cpu_set_power(int cpu, bool on) { u32 *clamp = NULL; u32 *pwroff; /* sun7i (A20) is different from other single cluster SoCs */ if (IS_ENABLED(CONFIG_MACH_SUN7I)) { clamp = (void *)SUNXI_CPUCFG_BASE + SUN7I_CPU1_PWR_CLAMP; pwroff = (void *)SUNXI_CPUCFG_BASE + SUN7I_CPU1_PWROFF; cpu = 0; } else if (IS_ENABLED(CONFIG_MACH_SUN8I_R40)) { clamp = (void *)SUNXI_CPUCFG_BASE + SUN8I_R40_PWR_CLAMP(cpu); pwroff = (void *)SUNXI_CPUCFG_BASE + SUN8I_R40_PWROFF; } else if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) { /* R528 leaves both cores powered up, manages them via reset */ return; } else { if (IS_ENABLED(CONFIG_MACH_SUN6I) || IS_ENABLED(CONFIG_MACH_SUN8I_H3)) clamp = (void *)SUNXI_PRCM_BASE + 0x140 + cpu * 0x4; pwroff = (void *)SUNXI_PRCM_BASE + 0x100; } if (on) { /* Release power clamp */ if (clamp) clamp_release(clamp); /* Clear power gating */ clrbits_le32(pwroff, BIT(cpu)); } else { /* Set power gating */ setbits_le32(pwroff, BIT(cpu)); /* Activate power clamp */ if (clamp) clamp_set(clamp); } } static void __secure sunxi_cpu_set_reset(int cpu, bool reset) { if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) { if (reset) clrbits_le32(SUNXI_CPUCFG_BASE + SUN8I_R528_C0_RST_CTRL, BIT(cpu)); else setbits_le32(SUNXI_CPUCFG_BASE + SUN8I_R528_C0_RST_CTRL, BIT(cpu)); return; } writel(reset ? 0b00 : 0b11, SUNXI_CPUCFG_BASE + SUNXI_CPU_RST(cpu)); } static void __secure sunxi_cpu_set_locking(int cpu, bool lock) { if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) { /* Not required on R528 */ return; } if (lock) clrbits_le32(SUNXI_CPUCFG_BASE + SUNXI_DBG_CTRL1, BIT(cpu)); else setbits_le32(SUNXI_CPUCFG_BASE + SUNXI_DBG_CTRL1, BIT(cpu)); } static bool __secure sunxi_cpu_poll_wfi(int cpu) { if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) { return !!(readl(SUNXI_CPUCFG_BASE + SUN8I_R528_C0_CPU_STATUS) & BIT(SUN8I_R528_C0_STATUS_STANDBYWFI + cpu)); } return !!(readl(SUNXI_CPUCFG_BASE + SUNXI_CPU_STATUS(cpu)) & BIT(2)); } static void __secure sunxi_cpu_invalidate_cache(int cpu) { if (IS_ENABLED(CONFIG_MACH_SUN8I_R528)) { clrbits_le32(SUNXI_CPUCFG_BASE + SUN8I_R528_C0_CTRL_REG0, BIT(cpu)); return; } clrbits_le32(SUNXI_CPUCFG_BASE + SUNXI_GEN_CTRL, BIT(cpu)); } static void __secure sunxi_cpu_power_off(u32 cpuid) { u32 cpu = cpuid & 0x3; /* Wait for the core to enter WFI */ while (!sunxi_cpu_poll_wfi(cpu)) __mdelay(1); /* Assert reset on target CPU */ sunxi_cpu_set_reset(cpu, true); /* Lock CPU (Disable external debug access) */ sunxi_cpu_set_locking(cpu, true); /* Power down CPU */ sunxi_cpu_set_power(cpuid, false); /* Unlock CPU (Reenable external debug access) */ sunxi_cpu_set_locking(cpu, false); } static u32 __secure cp15_read_scr(void) { u32 scr; asm volatile ("mrc p15, 0, %0, c1, c1, 0" : "=r" (scr)); return scr; } static void __secure cp15_write_scr(u32 scr) { asm volatile ("mcr p15, 0, %0, c1, c1, 0" : : "r" (scr)); isb(); } /* * Although this is an FIQ handler, the FIQ is processed in monitor mode, * which means there's no FIQ banked registers. This is the same as IRQ * mode, so use the IRQ attribute to ask the compiler to handler entry * and return. */ void __secure __irq psci_fiq_enter(void) { u32 scr, reg, cpu; /* Switch to secure mode */ scr = cp15_read_scr(); cp15_write_scr(scr & ~BIT(0)); /* Validate reason based on IAR and acknowledge */ reg = readl(GICC_BASE + GICC_IAR); /* Skip spurious interrupts 1022 and 1023 */ if (reg == 1023 || reg == 1022) goto out; /* End of interrupt */ writel(reg, GICC_BASE + GICC_EOIR); dsb(); /* Get CPU number */ cpu = (reg >> 10) & 0x7; /* Power off the CPU */ sunxi_cpu_power_off(cpu); out: /* Restore security level */ cp15_write_scr(scr); } int __secure psci_cpu_on(u32 __always_unused unused, u32 mpidr, u32 pc, u32 context_id) { u32 cpu = (mpidr & 0x3); /* store target PC and context id */ psci_save(cpu, pc, context_id); /* Set secondary core power on PC */ sunxi_cpu_set_entry(cpu, &psci_cpu_entry); /* Assert reset on target CPU */ sunxi_cpu_set_reset(cpu, true); /* Invalidate L1 cache */ sunxi_cpu_invalidate_cache(cpu); /* Lock CPU (Disable external debug access) */ sunxi_cpu_set_locking(cpu, true); /* Power up target CPU */ sunxi_cpu_set_power(cpu, true); /* De-assert reset on target CPU */ sunxi_cpu_set_reset(cpu, false); /* Unlock CPU (Reenable external debug access) */ sunxi_cpu_set_locking(cpu, false); return ARM_PSCI_RET_SUCCESS; } s32 __secure psci_cpu_off(void) { psci_cpu_off_common(); /* Ask CPU0 via SGI15 to pull the rug... */ writel(BIT(16) | 15, GICD_BASE + GICD_SGIR); dsb(); /* Wait to be turned off */ while (1) wfi(); } void __secure psci_arch_init(void) { u32 reg; /* SGI15 as Group-0 */ clrbits_le32(GICD_BASE + GICD_IGROUPRn, BIT(15)); /* Set SGI15 priority to 0 */ writeb(0, GICD_BASE + GICD_IPRIORITYRn + 15); /* Be cool with non-secure */ writel(0xff, GICC_BASE + GICC_PMR); /* Switch FIQEn on */ setbits_le32(GICC_BASE + GICC_CTLR, BIT(3)); reg = cp15_read_scr(); reg |= BIT(2); /* Enable FIQ in monitor mode */ reg &= ~BIT(0); /* Secure mode */ cp15_write_scr(reg); }