diff options
Diffstat (limited to 'arch/arm/mach-rockchip')
-rw-r--r-- | arch/arm/mach-rockchip/Kconfig | 24 | ||||
-rw-r--r-- | arch/arm/mach-rockchip/Makefile | 6 | ||||
-rw-r--r-- | arch/arm/mach-rockchip/core.h | 10 | ||||
-rw-r--r-- | arch/arm/mach-rockchip/headsmp.S | 16 | ||||
-rw-r--r-- | arch/arm/mach-rockchip/platsmp.c | 374 | ||||
-rw-r--r-- | arch/arm/mach-rockchip/pm.c | 331 | ||||
-rw-r--r-- | arch/arm/mach-rockchip/pm.h | 97 | ||||
-rw-r--r-- | arch/arm/mach-rockchip/rockchip.c | 75 | ||||
-rw-r--r-- | arch/arm/mach-rockchip/sleep.S | 64 |
9 files changed, 997 insertions, 0 deletions
diff --git a/arch/arm/mach-rockchip/Kconfig b/arch/arm/mach-rockchip/Kconfig new file mode 100644 index 000000000..b7855cc66 --- /dev/null +++ b/arch/arm/mach-rockchip/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-only +config ARCH_ROCKCHIP + bool "Rockchip RK2928 and RK3xxx SOCs" + depends on ARCH_MULTI_V7 + select PINCTRL + select PINCTRL_ROCKCHIP + select ARCH_HAS_RESET_CONTROLLER + select ARM_AMBA + select ARM_GIC + select CACHE_L2X0 + select GPIOLIB + select HAVE_ARM_ARCH_TIMER + select HAVE_ARM_SCU if SMP + select HAVE_ARM_TWD if SMP + select DW_APB_TIMER_OF + select REGULATOR if PM + select ROCKCHIP_TIMER + select ARM_GLOBAL_TIMER + select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK + select ZONE_DMA if ARM_LPAE + select PM + help + Support for Rockchip's Cortex-A9 Single-to-Quad-Core-SoCs + containing the RK2928, RK30xx and RK31xx series. diff --git a/arch/arm/mach-rockchip/Makefile b/arch/arm/mach-rockchip/Makefile new file mode 100644 index 000000000..62596d5de --- /dev/null +++ b/arch/arm/mach-rockchip/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +CFLAGS_platsmp.o := -march=armv7-a + +obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip.o +obj-$(CONFIG_PM_SLEEP) += pm.o sleep.o +obj-$(CONFIG_SMP) += headsmp.o platsmp.o diff --git a/arch/arm/mach-rockchip/core.h b/arch/arm/mach-rockchip/core.h new file mode 100644 index 000000000..fabcaa666 --- /dev/null +++ b/arch/arm/mach-rockchip/core.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2013 MundoReader S.L. + * Author: Heiko Stuebner <heiko@sntech.de> + */ + +extern char rockchip_secondary_trampoline; +extern char rockchip_secondary_trampoline_end; + +extern unsigned long rockchip_boot_fn; diff --git a/arch/arm/mach-rockchip/headsmp.S b/arch/arm/mach-rockchip/headsmp.S new file mode 100644 index 000000000..37a7ea524 --- /dev/null +++ b/arch/arm/mach-rockchip/headsmp.S @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2013 MundoReader S.L. + * Author: Heiko Stuebner <heiko@sntech.de> + */ +#include <linux/linkage.h> +#include <linux/init.h> + +ENTRY(rockchip_secondary_trampoline) + ldr pc, 1f +ENDPROC(rockchip_secondary_trampoline) + .globl rockchip_boot_fn +rockchip_boot_fn: +1: .space 4 + +ENTRY(rockchip_secondary_trampoline_end) diff --git a/arch/arm/mach-rockchip/platsmp.c b/arch/arm/mach-rockchip/platsmp.c new file mode 100644 index 000000000..36915a073 --- /dev/null +++ b/arch/arm/mach-rockchip/platsmp.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2013 MundoReader S.L. + * Author: Heiko Stuebner <heiko@sntech.de> + */ + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#include <linux/reset.h> +#include <linux/cpu.h> +#include <asm/cacheflush.h> +#include <asm/cp15.h> +#include <asm/smp_scu.h> +#include <asm/smp_plat.h> +#include <asm/mach/map.h> + +#include "core.h" + +static void __iomem *scu_base_addr; +static void __iomem *sram_base_addr; +static int ncores; + +#define PMU_PWRDN_CON 0x08 +#define PMU_PWRDN_ST 0x0c + +#define PMU_PWRDN_SCU 4 + +static struct regmap *pmu; +static int has_pmu = true; + +static int pmu_power_domain_is_on(int pd) +{ + u32 val; + int ret; + + ret = regmap_read(pmu, PMU_PWRDN_ST, &val); + if (ret < 0) + return ret; + + return !(val & BIT(pd)); +} + +static struct reset_control *rockchip_get_core_reset(int cpu) +{ + struct device *dev = get_cpu_device(cpu); + struct device_node *np; + + /* The cpu device is only available after the initial core bringup */ + if (dev) + np = dev->of_node; + else + np = of_get_cpu_node(cpu, NULL); + + return of_reset_control_get_exclusive(np, NULL); +} + +static int pmu_set_power_domain(int pd, bool on) +{ + u32 val = (on) ? 0 : BIT(pd); + struct reset_control *rstc = rockchip_get_core_reset(pd); + int ret; + + if (IS_ERR(rstc) && read_cpuid_part() != ARM_CPU_PART_CORTEX_A9) { + pr_err("%s: could not get reset control for core %d\n", + __func__, pd); + return PTR_ERR(rstc); + } + + /* + * We need to soft reset the cpu when we turn off the cpu power domain, + * or else the active processors might be stalled when the individual + * processor is powered down. + */ + if (!IS_ERR(rstc) && !on) + reset_control_assert(rstc); + + if (has_pmu) { + ret = regmap_update_bits(pmu, PMU_PWRDN_CON, BIT(pd), val); + if (ret < 0) { + pr_err("%s: could not update power domain\n", + __func__); + return ret; + } + + ret = -1; + while (ret != on) { + ret = pmu_power_domain_is_on(pd); + if (ret < 0) { + pr_err("%s: could not read power domain state\n", + __func__); + return ret; + } + } + } + + if (!IS_ERR(rstc)) { + if (on) + reset_control_deassert(rstc); + reset_control_put(rstc); + } + + return 0; +} + +/* + * Handling of CPU cores + */ + +static int rockchip_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + int ret; + + if (!sram_base_addr || (has_pmu && !pmu)) { + pr_err("%s: sram or pmu missing for cpu boot\n", __func__); + return -ENXIO; + } + + if (cpu >= ncores) { + pr_err("%s: cpu %d outside maximum number of cpus %d\n", + __func__, cpu, ncores); + return -ENXIO; + } + + /* start the core */ + ret = pmu_set_power_domain(0 + cpu, true); + if (ret < 0) + return ret; + + if (read_cpuid_part() != ARM_CPU_PART_CORTEX_A9) { + /* + * We communicate with the bootrom to active the cpus other + * than cpu0, after a blob of initialize code, they will + * stay at wfe state, once they are activated, they will check + * the mailbox: + * sram_base_addr + 4: 0xdeadbeaf + * sram_base_addr + 8: start address for pc + * The cpu0 need to wait the other cpus other than cpu0 entering + * the wfe state.The wait time is affected by many aspects. + * (e.g: cpu frequency, bootrom frequency, sram frequency, ...) + */ + mdelay(1); /* ensure the cpus other than cpu0 to startup */ + + writel(__pa_symbol(secondary_startup), sram_base_addr + 8); + writel(0xDEADBEAF, sram_base_addr + 4); + dsb_sev(); + } + + return 0; +} + +/** + * rockchip_smp_prepare_sram - populate necessary sram block + * Starting cores execute the code residing at the start of the on-chip sram + * after power-on. Therefore make sure, this sram region is reserved and + * big enough. After this check, copy the trampoline code that directs the + * core to the real startup code in ram into the sram-region. + * @node: mmio-sram device node + */ +static int __init rockchip_smp_prepare_sram(struct device_node *node) +{ + unsigned int trampoline_sz = &rockchip_secondary_trampoline_end - + &rockchip_secondary_trampoline; + struct resource res; + unsigned int rsize; + int ret; + + ret = of_address_to_resource(node, 0, &res); + if (ret < 0) { + pr_err("%s: could not get address for node %pOF\n", + __func__, node); + return ret; + } + + rsize = resource_size(&res); + if (rsize < trampoline_sz) { + pr_err("%s: reserved block with size 0x%x is too small for trampoline size 0x%x\n", + __func__, rsize, trampoline_sz); + return -EINVAL; + } + + /* set the boot function for the sram code */ + rockchip_boot_fn = __pa_symbol(secondary_startup); + + /* copy the trampoline to sram, that runs during startup of the core */ + memcpy_toio(sram_base_addr, &rockchip_secondary_trampoline, trampoline_sz); + flush_cache_all(); + outer_clean_range(0, trampoline_sz); + + dsb_sev(); + + return 0; +} + +static const struct regmap_config rockchip_pmu_regmap_config = { + .name = "rockchip-pmu", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int __init rockchip_smp_prepare_pmu(void) +{ + struct device_node *node; + void __iomem *pmu_base; + + /* + * This function is only called via smp_ops->smp_prepare_cpu(). + * That only happens if a "/cpus" device tree node exists + * and has an "enable-method" property that selects the SMP + * operations defined herein. + */ + node = of_find_node_by_path("/cpus"); + + pmu = syscon_regmap_lookup_by_phandle(node, "rockchip,pmu"); + of_node_put(node); + if (!IS_ERR(pmu)) + return 0; + + pmu = syscon_regmap_lookup_by_compatible("rockchip,rk3066-pmu"); + if (!IS_ERR(pmu)) + return 0; + + /* fallback, create our own regmap for the pmu area */ + pmu = NULL; + node = of_find_compatible_node(NULL, NULL, "rockchip,rk3066-pmu"); + if (!node) { + pr_err("%s: could not find pmu dt node\n", __func__); + return -ENODEV; + } + + pmu_base = of_iomap(node, 0); + of_node_put(node); + if (!pmu_base) { + pr_err("%s: could not map pmu registers\n", __func__); + return -ENOMEM; + } + + pmu = regmap_init_mmio(NULL, pmu_base, &rockchip_pmu_regmap_config); + if (IS_ERR(pmu)) { + int ret = PTR_ERR(pmu); + + iounmap(pmu_base); + pmu = NULL; + pr_err("%s: regmap init failed\n", __func__); + return ret; + } + + return 0; +} + +static void __init rockchip_smp_prepare_cpus(unsigned int max_cpus) +{ + struct device_node *node; + unsigned int i; + + node = of_find_compatible_node(NULL, NULL, "rockchip,rk3066-smp-sram"); + if (!node) { + pr_err("%s: could not find sram dt node\n", __func__); + return; + } + + sram_base_addr = of_iomap(node, 0); + if (!sram_base_addr) { + pr_err("%s: could not map sram registers\n", __func__); + of_node_put(node); + return; + } + + if (has_pmu && rockchip_smp_prepare_pmu()) { + of_node_put(node); + return; + } + + if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) { + if (rockchip_smp_prepare_sram(node)) { + of_node_put(node); + return; + } + + /* enable the SCU power domain */ + pmu_set_power_domain(PMU_PWRDN_SCU, true); + + of_node_put(node); + node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-scu"); + if (!node) { + pr_err("%s: missing scu\n", __func__); + return; + } + + scu_base_addr = of_iomap(node, 0); + if (!scu_base_addr) { + pr_err("%s: could not map scu registers\n", __func__); + of_node_put(node); + return; + } + + /* + * While the number of cpus is gathered from dt, also get the + * number of cores from the scu to verify this value when + * booting the cores. + */ + ncores = scu_get_core_count(scu_base_addr); + pr_err("%s: ncores %d\n", __func__, ncores); + + scu_enable(scu_base_addr); + } else { + unsigned int l2ctlr; + + asm ("mrc p15, 1, %0, c9, c0, 2\n" : "=r" (l2ctlr)); + ncores = ((l2ctlr >> 24) & 0x3) + 1; + } + of_node_put(node); + + /* Make sure that all cores except the first are really off */ + for (i = 1; i < ncores; i++) + pmu_set_power_domain(0 + i, false); +} + +static void __init rk3036_smp_prepare_cpus(unsigned int max_cpus) +{ + has_pmu = false; + + rockchip_smp_prepare_cpus(max_cpus); +} + +#ifdef CONFIG_HOTPLUG_CPU +static int rockchip_cpu_kill(unsigned int cpu) +{ + /* + * We need a delay here to ensure that the dying CPU can finish + * executing v7_coherency_exit() and reach the WFI/WFE state + * prior to having the power domain disabled. + */ + mdelay(1); + + pmu_set_power_domain(0 + cpu, false); + return 1; +} + +static void rockchip_cpu_die(unsigned int cpu) +{ + v7_exit_coherency_flush(louis); + while (1) + cpu_do_idle(); +} +#endif + +static const struct smp_operations rk3036_smp_ops __initconst = { + .smp_prepare_cpus = rk3036_smp_prepare_cpus, + .smp_boot_secondary = rockchip_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_kill = rockchip_cpu_kill, + .cpu_die = rockchip_cpu_die, +#endif +}; + +static const struct smp_operations rockchip_smp_ops __initconst = { + .smp_prepare_cpus = rockchip_smp_prepare_cpus, + .smp_boot_secondary = rockchip_boot_secondary, +#ifdef CONFIG_HOTPLUG_CPU + .cpu_kill = rockchip_cpu_kill, + .cpu_die = rockchip_cpu_die, +#endif +}; + +CPU_METHOD_OF_DECLARE(rk3036_smp, "rockchip,rk3036-smp", &rk3036_smp_ops); +CPU_METHOD_OF_DECLARE(rk3066_smp, "rockchip,rk3066-smp", &rockchip_smp_ops); diff --git a/arch/arm/mach-rockchip/pm.c b/arch/arm/mach-rockchip/pm.c new file mode 100644 index 000000000..30d781d80 --- /dev/null +++ b/arch/arm/mach-rockchip/pm.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd + * Author: Tony Xie <tony.xie@rock-chips.com> + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include <linux/suspend.h> +#include <linux/mfd/syscon.h> +#include <linux/regulator/machine.h> + +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <asm/suspend.h> + +#include "pm.h" + +/* These enum are option of low power mode */ +enum { + ROCKCHIP_ARM_OFF_LOGIC_NORMAL = 0, + ROCKCHIP_ARM_OFF_LOGIC_DEEP = 1, +}; + +struct rockchip_pm_data { + const struct platform_suspend_ops *ops; + int (*init)(struct device_node *np); +}; + +static void __iomem *rk3288_bootram_base; +static phys_addr_t rk3288_bootram_phy; + +static struct regmap *pmu_regmap; +static struct regmap *sgrf_regmap; +static struct regmap *grf_regmap; + +static u32 rk3288_pmu_pwr_mode_con; +static u32 rk3288_sgrf_soc_con0; +static u32 rk3288_sgrf_cpu_con0; + +static inline u32 rk3288_l2_config(void) +{ + u32 l2ctlr; + + asm("mrc p15, 1, %0, c9, c0, 2" : "=r" (l2ctlr)); + return l2ctlr; +} + +static void __init rk3288_config_bootdata(void) +{ + rkpm_bootdata_cpusp = rk3288_bootram_phy + (SZ_4K - 8); + rkpm_bootdata_cpu_code = __pa_symbol(cpu_resume); + + rkpm_bootdata_l2ctlr_f = 1; + rkpm_bootdata_l2ctlr = rk3288_l2_config(); +} + +#define GRF_UOC0_CON0 0x320 +#define GRF_UOC1_CON0 0x334 +#define GRF_UOC2_CON0 0x348 +#define GRF_SIDDQ BIT(13) + +static bool rk3288_slp_disable_osc(void) +{ + static const u32 reg_offset[] = { GRF_UOC0_CON0, GRF_UOC1_CON0, + GRF_UOC2_CON0 }; + u32 reg, i; + + /* + * if any usb phy is still on(GRF_SIDDQ==0), that means we need the + * function of usb wakeup, so do not switch to 32khz, since the usb phy + * clk does not connect to 32khz osc + */ + for (i = 0; i < ARRAY_SIZE(reg_offset); i++) { + regmap_read(grf_regmap, reg_offset[i], ®); + if (!(reg & GRF_SIDDQ)) + return false; + } + + return true; +} + +static void rk3288_slp_mode_set(int level) +{ + u32 mode_set, mode_set1; + bool osc_disable = rk3288_slp_disable_osc(); + + regmap_read(sgrf_regmap, RK3288_SGRF_CPU_CON0, &rk3288_sgrf_cpu_con0); + regmap_read(sgrf_regmap, RK3288_SGRF_SOC_CON0, &rk3288_sgrf_soc_con0); + + regmap_read(pmu_regmap, RK3288_PMU_PWRMODE_CON, + &rk3288_pmu_pwr_mode_con); + + /* + * SGRF_FAST_BOOT_EN - system to boot from FAST_BOOT_ADDR + * PCLK_WDT_GATE - disable WDT during suspend. + */ + regmap_write(sgrf_regmap, RK3288_SGRF_SOC_CON0, + SGRF_PCLK_WDT_GATE | SGRF_FAST_BOOT_EN + | SGRF_PCLK_WDT_GATE_WRITE | SGRF_FAST_BOOT_EN_WRITE); + + /* + * The dapswjdp can not auto reset before resume, that cause it may + * access some illegal address during resume. Let's disable it before + * suspend, and the MASKROM will enable it back. + */ + regmap_write(sgrf_regmap, RK3288_SGRF_CPU_CON0, SGRF_DAPDEVICEEN_WRITE); + + /* booting address of resuming system is from this register value */ + regmap_write(sgrf_regmap, RK3288_SGRF_FAST_BOOT_ADDR, + rk3288_bootram_phy); + + mode_set = BIT(PMU_GLOBAL_INT_DISABLE) | BIT(PMU_L2FLUSH_EN) | + BIT(PMU_SREF0_ENTER_EN) | BIT(PMU_SREF1_ENTER_EN) | + BIT(PMU_DDR0_GATING_EN) | BIT(PMU_DDR1_GATING_EN) | + BIT(PMU_PWR_MODE_EN) | BIT(PMU_CHIP_PD_EN) | + BIT(PMU_SCU_EN); + + mode_set1 = BIT(PMU_CLR_CORE) | BIT(PMU_CLR_CPUP); + + if (level == ROCKCHIP_ARM_OFF_LOGIC_DEEP) { + /* arm off, logic deep sleep */ + mode_set |= BIT(PMU_BUS_PD_EN) | BIT(PMU_PMU_USE_LF) | + BIT(PMU_DDR1IO_RET_EN) | BIT(PMU_DDR0IO_RET_EN) | + BIT(PMU_ALIVE_USE_LF) | BIT(PMU_PLL_PD_EN); + + if (osc_disable) + mode_set |= BIT(PMU_OSC_24M_DIS); + + mode_set1 |= BIT(PMU_CLR_ALIVE) | BIT(PMU_CLR_BUS) | + BIT(PMU_CLR_PERI) | BIT(PMU_CLR_DMA); + + regmap_write(pmu_regmap, RK3288_PMU_WAKEUP_CFG1, + PMU_ARMINT_WAKEUP_EN); + + /* + * In deep suspend we use PMU_PMU_USE_LF to let the rk3288 + * switch its main clock supply to the alternative 32kHz + * source. Therefore set 30ms on a 32kHz clock for pmic + * stabilization. Similar 30ms on 24MHz for the other + * mode below. + */ + regmap_write(pmu_regmap, RK3288_PMU_STABL_CNT, 32 * 30); + + /* only wait for stabilization, if we turned the osc off */ + regmap_write(pmu_regmap, RK3288_PMU_OSC_CNT, + osc_disable ? 32 * 30 : 0); + } else { + /* + * arm off, logic normal + * if pmu_clk_core_src_gate_en is not set, + * wakeup will be error + */ + mode_set |= BIT(PMU_CLK_CORE_SRC_GATE_EN); + + regmap_write(pmu_regmap, RK3288_PMU_WAKEUP_CFG1, + PMU_ARMINT_WAKEUP_EN | PMU_GPIOINT_WAKEUP_EN); + + /* 30ms on a 24MHz clock for pmic stabilization */ + regmap_write(pmu_regmap, RK3288_PMU_STABL_CNT, 24000 * 30); + + /* oscillator is still running, so no need to wait */ + regmap_write(pmu_regmap, RK3288_PMU_OSC_CNT, 0); + } + + regmap_write(pmu_regmap, RK3288_PMU_PWRMODE_CON, mode_set); + regmap_write(pmu_regmap, RK3288_PMU_PWRMODE_CON1, mode_set1); +} + +static void rk3288_slp_mode_set_resume(void) +{ + regmap_write(sgrf_regmap, RK3288_SGRF_CPU_CON0, + rk3288_sgrf_cpu_con0 | SGRF_DAPDEVICEEN_WRITE); + + regmap_write(pmu_regmap, RK3288_PMU_PWRMODE_CON, + rk3288_pmu_pwr_mode_con); + + regmap_write(sgrf_regmap, RK3288_SGRF_SOC_CON0, + rk3288_sgrf_soc_con0 | SGRF_PCLK_WDT_GATE_WRITE + | SGRF_FAST_BOOT_EN_WRITE); +} + +static int rockchip_lpmode_enter(unsigned long arg) +{ + flush_cache_all(); + + cpu_do_idle(); + + pr_err("%s: Failed to suspend\n", __func__); + + return 1; +} + +static int rk3288_suspend_enter(suspend_state_t state) +{ + local_fiq_disable(); + + rk3288_slp_mode_set(ROCKCHIP_ARM_OFF_LOGIC_NORMAL); + + cpu_suspend(0, rockchip_lpmode_enter); + + rk3288_slp_mode_set_resume(); + + local_fiq_enable(); + + return 0; +} + +static int rk3288_suspend_prepare(void) +{ + return regulator_suspend_prepare(PM_SUSPEND_MEM); +} + +static void rk3288_suspend_finish(void) +{ + if (regulator_suspend_finish()) + pr_err("%s: Suspend finish failed\n", __func__); +} + +static int __init rk3288_suspend_init(struct device_node *np) +{ + struct device_node *sram_np; + struct resource res; + int ret; + + pmu_regmap = syscon_node_to_regmap(np); + if (IS_ERR(pmu_regmap)) { + pr_err("%s: could not find pmu regmap\n", __func__); + return PTR_ERR(pmu_regmap); + } + + sgrf_regmap = syscon_regmap_lookup_by_compatible( + "rockchip,rk3288-sgrf"); + if (IS_ERR(sgrf_regmap)) { + pr_err("%s: could not find sgrf regmap\n", __func__); + return PTR_ERR(sgrf_regmap); + } + + grf_regmap = syscon_regmap_lookup_by_compatible( + "rockchip,rk3288-grf"); + if (IS_ERR(grf_regmap)) { + pr_err("%s: could not find grf regmap\n", __func__); + return PTR_ERR(grf_regmap); + } + + sram_np = of_find_compatible_node(NULL, NULL, + "rockchip,rk3288-pmu-sram"); + if (!sram_np) { + pr_err("%s: could not find bootram dt node\n", __func__); + return -ENODEV; + } + + rk3288_bootram_base = of_iomap(sram_np, 0); + if (!rk3288_bootram_base) { + pr_err("%s: could not map bootram base\n", __func__); + of_node_put(sram_np); + return -ENOMEM; + } + + ret = of_address_to_resource(sram_np, 0, &res); + if (ret) { + pr_err("%s: could not get bootram phy addr\n", __func__); + of_node_put(sram_np); + return ret; + } + rk3288_bootram_phy = res.start; + + of_node_put(sram_np); + + rk3288_config_bootdata(); + + /* copy resume code and data to bootsram */ + memcpy(rk3288_bootram_base, rockchip_slp_cpu_resume, + rk3288_bootram_sz); + + return 0; +} + +static const struct platform_suspend_ops rk3288_suspend_ops = { + .enter = rk3288_suspend_enter, + .valid = suspend_valid_only_mem, + .prepare = rk3288_suspend_prepare, + .finish = rk3288_suspend_finish, +}; + +static const struct rockchip_pm_data rk3288_pm_data __initconst = { + .ops = &rk3288_suspend_ops, + .init = rk3288_suspend_init, +}; + +static const struct of_device_id rockchip_pmu_of_device_ids[] __initconst = { + { + .compatible = "rockchip,rk3288-pmu", + .data = &rk3288_pm_data, + }, + { /* sentinel */ }, +}; + +void __init rockchip_suspend_init(void) +{ + const struct rockchip_pm_data *pm_data; + const struct of_device_id *match; + struct device_node *np; + int ret; + + np = of_find_matching_node_and_match(NULL, rockchip_pmu_of_device_ids, + &match); + if (!match) { + pr_err("Failed to find PMU node\n"); + goto out_put; + } + pm_data = (struct rockchip_pm_data *) match->data; + + if (pm_data->init) { + ret = pm_data->init(np); + + if (ret) { + pr_err("%s: matches init error %d\n", __func__, ret); + goto out_put; + } + } + + suspend_set_ops(pm_data->ops); + +out_put: + of_node_put(np); +} diff --git a/arch/arm/mach-rockchip/pm.h b/arch/arm/mach-rockchip/pm.h new file mode 100644 index 000000000..479500072 --- /dev/null +++ b/arch/arm/mach-rockchip/pm.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd + * Author: Tony Xie <tony.xie@rock-chips.com> + */ + +#ifndef __MACH_ROCKCHIP_PM_H +#define __MACH_ROCKCHIP_PM_H + +extern unsigned long rkpm_bootdata_cpusp; +extern unsigned long rkpm_bootdata_cpu_code; +extern unsigned long rkpm_bootdata_l2ctlr_f; +extern unsigned long rkpm_bootdata_l2ctlr; +extern unsigned long rkpm_bootdata_ddr_code; +extern unsigned long rkpm_bootdata_ddr_data; +extern unsigned long rk3288_bootram_sz; + +void rockchip_slp_cpu_resume(void); +#ifdef CONFIG_PM_SLEEP +void __init rockchip_suspend_init(void); +#else +static inline void rockchip_suspend_init(void) +{ +} +#endif + +/****** following is rk3288 defined **********/ +#define RK3288_PMU_WAKEUP_CFG0 0x00 +#define RK3288_PMU_WAKEUP_CFG1 0x04 +#define RK3288_PMU_PWRMODE_CON 0x18 +#define RK3288_PMU_OSC_CNT 0x20 +#define RK3288_PMU_PLL_CNT 0x24 +#define RK3288_PMU_STABL_CNT 0x28 +#define RK3288_PMU_DDR0IO_PWRON_CNT 0x2c +#define RK3288_PMU_DDR1IO_PWRON_CNT 0x30 +#define RK3288_PMU_CORE_PWRDWN_CNT 0x34 +#define RK3288_PMU_CORE_PWRUP_CNT 0x38 +#define RK3288_PMU_GPU_PWRDWN_CNT 0x3c +#define RK3288_PMU_GPU_PWRUP_CNT 0x40 +#define RK3288_PMU_WAKEUP_RST_CLR_CNT 0x44 +#define RK3288_PMU_PWRMODE_CON1 0x90 + +#define RK3288_SGRF_SOC_CON0 (0x0000) +#define RK3288_SGRF_FAST_BOOT_ADDR (0x0120) +#define SGRF_PCLK_WDT_GATE BIT(6) +#define SGRF_PCLK_WDT_GATE_WRITE BIT(22) +#define SGRF_FAST_BOOT_EN BIT(8) +#define SGRF_FAST_BOOT_EN_WRITE BIT(24) + +#define RK3288_SGRF_CPU_CON0 (0x40) +#define SGRF_DAPDEVICEEN BIT(0) +#define SGRF_DAPDEVICEEN_WRITE BIT(16) + +/* PMU_WAKEUP_CFG1 bits */ +#define PMU_ARMINT_WAKEUP_EN BIT(0) +#define PMU_GPIOINT_WAKEUP_EN BIT(3) + +enum rk3288_pwr_mode_con { + PMU_PWR_MODE_EN = 0, + PMU_CLK_CORE_SRC_GATE_EN, + PMU_GLOBAL_INT_DISABLE, + PMU_L2FLUSH_EN, + PMU_BUS_PD_EN, + PMU_A12_0_PD_EN, + PMU_SCU_EN, + PMU_PLL_PD_EN, + PMU_CHIP_PD_EN, /* POWER OFF PIN ENABLE */ + PMU_PWROFF_COMB, + PMU_ALIVE_USE_LF, + PMU_PMU_USE_LF, + PMU_OSC_24M_DIS, + PMU_INPUT_CLAMP_EN, + PMU_WAKEUP_RESET_EN, + PMU_SREF0_ENTER_EN, + PMU_SREF1_ENTER_EN, + PMU_DDR0IO_RET_EN, + PMU_DDR1IO_RET_EN, + PMU_DDR0_GATING_EN, + PMU_DDR1_GATING_EN, + PMU_DDR0IO_RET_DE_REQ, + PMU_DDR1IO_RET_DE_REQ +}; + +enum rk3288_pwr_mode_con1 { + PMU_CLR_BUS = 0, + PMU_CLR_CORE, + PMU_CLR_CPUP, + PMU_CLR_ALIVE, + PMU_CLR_DMA, + PMU_CLR_PERI, + PMU_CLR_GPU, + PMU_CLR_VIDEO, + PMU_CLR_HEVC, + PMU_CLR_VIO, +}; + +#endif /* __MACH_ROCKCHIP_PM_H */ diff --git a/arch/arm/mach-rockchip/rockchip.c b/arch/arm/mach-rockchip/rockchip.c new file mode 100644 index 000000000..beea4564e --- /dev/null +++ b/arch/arm/mach-rockchip/rockchip.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Device Tree support for Rockchip SoCs + * + * Copyright (c) 2013 MundoReader S.L. + * Author: Heiko Stuebner <heiko@sntech.de> + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/of_clk.h> +#include <linux/of_platform.h> +#include <linux/irqchip.h> +#include <linux/clocksource.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> +#include <asm/hardware/cache-l2x0.h> +#include "core.h" +#include "pm.h" + +#define RK3288_TIMER6_7_PHYS 0xff810000 + +static void __init rockchip_timer_init(void) +{ + if (of_machine_is_compatible("rockchip,rk3288")) { + void __iomem *reg_base; + + /* + * Most/all uboot versions for rk3288 don't enable timer7 + * which is needed for the architected timer to work. + * So make sure it is running during early boot. + */ + reg_base = ioremap(RK3288_TIMER6_7_PHYS, SZ_16K); + if (reg_base) { + writel(0, reg_base + 0x30); + writel(0xffffffff, reg_base + 0x20); + writel(0xffffffff, reg_base + 0x24); + writel(1, reg_base + 0x30); + dsb(); + iounmap(reg_base); + } else { + pr_err("rockchip: could not map timer7 registers\n"); + } + } + + of_clk_init(NULL); + timer_probe(); +} + +static void __init rockchip_dt_init(void) +{ + rockchip_suspend_init(); +} + +static const char * const rockchip_board_dt_compat[] = { + "rockchip,rk2928", + "rockchip,rk3066a", + "rockchip,rk3066b", + "rockchip,rk3188", + "rockchip,rk3228", + "rockchip,rk3288", + "rockchip,rv1108", + NULL, +}; + +DT_MACHINE_START(ROCKCHIP_DT, "Rockchip (Device Tree)") + .l2c_aux_val = 0, + .l2c_aux_mask = ~0, + .init_time = rockchip_timer_init, + .dt_compat = rockchip_board_dt_compat, + .init_machine = rockchip_dt_init, +MACHINE_END diff --git a/arch/arm/mach-rockchip/sleep.S b/arch/arm/mach-rockchip/sleep.S new file mode 100644 index 000000000..3eca3922c --- /dev/null +++ b/arch/arm/mach-rockchip/sleep.S @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd + * Author: Tony Xie <tony.xie@rock-chips.com> + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/memory.h> + +.data +/* + * this code will be copied from + * ddr to sram for system resumeing. + * so it is ".data section". + */ + .align 2 + +ENTRY(rockchip_slp_cpu_resume) + setmode PSR_I_BIT | PSR_F_BIT | SVC_MODE, r1 @ set svc, irqs off + mrc p15, 0, r1, c0, c0, 5 + and r1, r1, #0xf + cmp r1, #0 + /* olny cpu0 can continue to run, the others is halt here */ + beq cpu0run +secondary_loop: + wfe + b secondary_loop +cpu0run: + ldr r3, rkpm_bootdata_l2ctlr_f + cmp r3, #0 + beq sp_set + ldr r3, rkpm_bootdata_l2ctlr + mcr p15, 1, r3, c9, c0, 2 +sp_set: + ldr sp, rkpm_bootdata_cpusp + ldr r1, rkpm_bootdata_cpu_code + bx r1 +ENDPROC(rockchip_slp_cpu_resume) + +/* Parameters filled in by the kernel */ + +/* Flag for whether to restore L2CTLR on resume */ + .global rkpm_bootdata_l2ctlr_f +rkpm_bootdata_l2ctlr_f: + .long 0 + +/* Saved L2CTLR to restore on resume */ + .global rkpm_bootdata_l2ctlr +rkpm_bootdata_l2ctlr: + .long 0 + +/* CPU resume SP addr */ + .globl rkpm_bootdata_cpusp +rkpm_bootdata_cpusp: + .long 0 + +/* CPU resume function (physical address) */ + .globl rkpm_bootdata_cpu_code +rkpm_bootdata_cpu_code: + .long 0 + +ENTRY(rk3288_bootram_sz) + .word . - rockchip_slp_cpu_resume |