Switch the SMP support for the Exynos5260 to use the MCPM infrastructure, including support for suspend/resume. As far as possible this replicates the operations performed by the last 3.4 kernel released by Samsung. Signed-off-by: Stuart Menefy <stuart.menefy@xxxxxxxxxxxxxxxx> --- arch/arm/boot/dts/exynos5260.dtsi | 21 +++++++ arch/arm/mach-exynos/common.h | 1 + arch/arm/mach-exynos/exynos.c | 1 - arch/arm/mach-exynos/mcpm-exynos.c | 118 +++++++++++++++++++++++++++++-------- arch/arm/mach-exynos/platsmp.c | 37 ++++++++++-- arch/arm/mach-exynos/suspend.c | 86 ++++++++++++++++++++++++--- 6 files changed, 227 insertions(+), 37 deletions(-) diff --git a/arch/arm/boot/dts/exynos5260.dtsi b/arch/arm/boot/dts/exynos5260.dtsi index 84c9b4443dc6..e8a5c8715b94 100644 --- a/arch/arm/boot/dts/exynos5260.dtsi +++ b/arch/arm/boot/dts/exynos5260.dtsi @@ -233,6 +233,24 @@ status = "disabled"; }; + sysram@2020000 { + compatible = "mmio-sram"; + reg = <0x02020000 0x54000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x02020000 0x54000>; + + smp-sysram@0 { + compatible = "samsung,exynos4210-sysram"; + reg = <0x0 0x1000>; + }; + + smp-sysram@53000 { + compatible = "samsung,exynos4210-sysram-ns"; + reg = <0x53000 0x1000>; + }; + }; + mct: mct@100b0000 { compatible = "samsung,exynos4210-mct"; reg = <0x100B0000 0x1000>; @@ -299,6 +317,9 @@ pmu_system_controller: system-controller@10d50000 { compatible = "samsung,exynos5260-pmu", "syscon"; reg = <0x10D50000 0x10000>; + interrupt-controller; + #interrupt-cells = <3>; + interrupt-parent = <&gic>; }; uart0: serial@12c00000 { diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h index 58e6014e3578..ede449bc73f1 100644 --- a/arch/arm/mach-exynos/common.h +++ b/arch/arm/mach-exynos/common.h @@ -110,6 +110,7 @@ enum { void exynos_firmware_init(void); /* CPU BOOT mode flag for Exynos3250 SoC bootloader */ +#define HOTPLUG (1 << 2) /* 5260 */ #define C2_STATE (1 << 3) /* * Magic values for bootloader indicating chosen low power mode. diff --git a/arch/arm/mach-exynos/exynos.c b/arch/arm/mach-exynos/exynos.c index 865dcc4c3181..2866cb0ff51a 100644 --- a/arch/arm/mach-exynos/exynos.c +++ b/arch/arm/mach-exynos/exynos.c @@ -119,7 +119,6 @@ void exynos_set_delayed_reset_assertion(bool enable) * feat, the matches below should be moved to suspend.c. */ static const struct of_device_id exynos_dt_pmu_match[] = { - { .compatible = "samsung,exynos5260-pmu" }, { .compatible = "samsung,exynos5410-pmu" }, { /*sentinel*/ }, }; diff --git a/arch/arm/mach-exynos/mcpm-exynos.c b/arch/arm/mach-exynos/mcpm-exynos.c index 571349e1e02e..e5e8d1f27995 100644 --- a/arch/arm/mach-exynos/mcpm-exynos.c +++ b/arch/arm/mach-exynos/mcpm-exynos.c @@ -17,10 +17,15 @@ #include <asm/smp_plat.h> #include "common.h" +#include "smc.h" #define EXYNOS5420_CPUS_PER_CLUSTER 4 #define EXYNOS5420_NR_CLUSTERS 2 +#define EXYNOS5260_CPUS_PER_BIG_CLUSTER 2 +#define EXYNOS5260_CPUS_PER_LITTLE_CLUSTER 4 +#define EXYNOS5260_NR_CLUSTERS 2 + #define EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN BIT(9) #define EXYNOS5420_USE_ARM_CORE_DOWN_STATE BIT(29) #define EXYNOS5420_USE_L2_COMMON_UP_STATE BIT(30) @@ -57,6 +62,17 @@ static void __iomem *ns_sram_base_addr __ro_after_init; static int exynos_cpunr(unsigned int cpu, unsigned int cluster) { + if (soc_is_exynos5260()) { + static const int cpus_per_cluster[EXYNOS5260_NR_CLUSTERS] = { + EXYNOS5260_CPUS_PER_BIG_CLUSTER, + EXYNOS5260_CPUS_PER_LITTLE_CLUSTER + }; + if (cluster >= EXYNOS5260_NR_CLUSTERS || + cpu >= cpus_per_cluster[cluster]) + return -EINVAL; + return cpu + (cluster * 4); + } + if (cpu >= EXYNOS5420_CPUS_PER_CLUSTER || cluster >= EXYNOS5420_NR_CLUSTERS) return -EINVAL; @@ -92,8 +108,20 @@ static int exynos_cpu_powerup(unsigned int cpu, unsigned int cluster) while (!pmu_raw_readl(S5P_PMU_SPARE2)) udelay(10); - pmu_raw_writel(EXYNOS5420_KFC_CORE_RESET(cpu), - EXYNOS_SWRESET); + if (soc_is_exynos5260()) { + u32 val, offset; + + offset = EXYNOS_ARM_CORE_STATUS(cpunr); + val = pmu_raw_readl(offset); + val |= EXYNOS5260_STATUS_WAKEUP_FROM_LOCAL_CFG; + pmu_raw_writel(val, offset); + + pmu_raw_writel(EXYNOS5260_SWRESET_PORESET, + EXYNOS_ARM_CORE_RESET(cpunr)); + } else { + pmu_raw_writel(EXYNOS5420_KFC_CORE_RESET(cpu), + EXYNOS_SWRESET); + } } } @@ -106,6 +134,9 @@ static int exynos_cluster_powerup(unsigned int cluster) if (cluster >= EXYNOS5420_NR_CLUSTERS) return -EINVAL; + if (soc_is_exynos5260()) + return 0; + exynos_cluster_power_up(cluster); return 0; } @@ -119,21 +150,44 @@ static void exynos_cpu_powerdown_prepare(unsigned int cpu, unsigned int cluster) exynos_cpu_power_down(cpunr); } +static void exynos_wfi_alternative(bool last_man) +{ + if (soc_is_exynos5260()) { + exynos_smc(SMC_CMD_SHUTDOWN, + last_man ? OP_TYPE_CLUSTER : OP_TYPE_CORE, + SMC_POWERSTATE_IDLE, + 0); + return; + } + + wfi(); +} + static void exynos_cluster_powerdown_prepare(unsigned int cluster) { pr_debug("%s: cluster %u\n", __func__, cluster); BUG_ON(cluster >= EXYNOS5420_NR_CLUSTERS); + + if (soc_is_exynos5260()) + return; + exynos_cluster_power_down(cluster); } static void exynos_cpu_cache_disable(void) { + if (soc_is_exynos5260()) + return; + /* Disable and flush the local CPU cache. */ exynos_v7_exit_coherency_flush(louis); } static void exynos_cluster_cache_disable(void) { + if (soc_is_exynos5260()) + return; + if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A15) { /* * On the Cortex-A15 we need to disable @@ -180,12 +234,20 @@ static void exynos_cpu_is_up(unsigned int cpu, unsigned int cluster) { /* especially when resuming: make sure power control is set */ exynos_cpu_powerup(cpu, cluster); + + if (soc_is_exynos5260()) { + /* Note this is different to exynos_cpunr() */ + int cpunr = cpu + (cluster ? 0 : 4); + + exynos_clear_boot_flag(cpunr, HOTPLUG); + } } static const struct mcpm_platform_ops exynos_power_ops = { .cpu_powerup = exynos_cpu_powerup, .cluster_powerup = exynos_cluster_powerup, .cpu_powerdown_prepare = exynos_cpu_powerdown_prepare, + .wfi_alternative = exynos_wfi_alternative, .cluster_powerdown_prepare = exynos_cluster_powerdown_prepare, .cpu_cache_disable = exynos_cpu_cache_disable, .cluster_cache_disable = exynos_cluster_cache_disable, @@ -205,6 +267,7 @@ static void __naked exynos_pm_power_up_setup(unsigned int affinity_level) } static const struct of_device_id exynos_dt_mcpm_match[] = { + { .compatible = "samsung,exynos5260" }, { .compatible = "samsung,exynos5420" }, { .compatible = "samsung,exynos5800" }, {}, @@ -253,11 +316,13 @@ static int __init exynos_mcpm_init(void) return -ENOMEM; } - /* - * To increase the stability of KFC reset we need to program - * the PMU SPARE3 register - */ - pmu_raw_writel(EXYNOS5420_SWRESET_KFC_SEL, S5P_PMU_SPARE3); + if (soc_is_exynos5420() || soc_is_exynos5800()) { + /* + * To increase the stability of KFC reset we need to program + * the PMU SPARE3 register. + */ + pmu_raw_writel(EXYNOS5420_SWRESET_KFC_SEL, S5P_PMU_SPARE3); + } ret = mcpm_platform_register(&exynos_power_ops); if (!ret) @@ -273,24 +338,27 @@ static int __init exynos_mcpm_init(void) pr_info("Exynos MCPM support installed\n"); - /* - * On Exynos5420/5800 for the A15 and A7 clusters: - * - * EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN ensures that all the cores - * in a cluster are turned off before turning off the cluster L2. - * - * EXYNOS5420_USE_ARM_CORE_DOWN_STATE ensures that a cores is powered - * off before waking it up. - * - * EXYNOS5420_USE_L2_COMMON_UP_STATE ensures that cluster L2 will be - * turned on before the first man is powered up. - */ - for (i = 0; i < EXYNOS5420_NR_CLUSTERS; i++) { - value = pmu_raw_readl(EXYNOS_COMMON_OPTION(i)); - value |= EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN | - EXYNOS5420_USE_ARM_CORE_DOWN_STATE | - EXYNOS5420_USE_L2_COMMON_UP_STATE; - pmu_raw_writel(value, EXYNOS_COMMON_OPTION(i)); + if (soc_is_exynos5420() || soc_is_exynos5800()) { + /* + * On Exynos5420/5800 for the A15 and A7 clusters: + * + * EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN ensures that all the + * cores in a cluster are turned off before turning off the + * cluster L2. + * + * EXYNOS5420_USE_ARM_CORE_DOWN_STATE ensures that a cores is + * powered off before waking it up. + * + * EXYNOS5420_USE_L2_COMMON_UP_STATE ensures that cluster L2 + * will be turned on before the first man is powered up. + */ + for (i = 0; i < EXYNOS5420_NR_CLUSTERS; i++) { + value = pmu_raw_readl(EXYNOS_COMMON_OPTION(i)); + value |= EXYNOS5420_ENABLE_AUTOMATIC_CORE_DOWN | + EXYNOS5420_USE_ARM_CORE_DOWN_STATE | + EXYNOS5420_USE_L2_COMMON_UP_STATE; + pmu_raw_writel(value, EXYNOS_COMMON_OPTION(i)); + } } exynos_mcpm_setup_entry_point(); diff --git a/arch/arm/mach-exynos/platsmp.c b/arch/arm/mach-exynos/platsmp.c index b6da7edbbd2f..07c21b2afc11 100644 --- a/arch/arm/mach-exynos/platsmp.c +++ b/arch/arm/mach-exynos/platsmp.c @@ -100,6 +100,23 @@ void exynos_cpu_power_down(int cpu) return; } + if (cpu == 4 && (soc_is_exynos5260())) { + /* + * Bypass power down for CPU4 (KFC0) during suspend. Check for + * the SYS_PWR_REG value to decide if we are suspending + * the system. + */ + int val = pmu_raw_readl(EXYNOS5260_KFC_CORE0_SYS_PWR_REG); + + if ((val & 0xf) == 8) + return; + } + + if (soc_is_exynos5260()) { + pmu_raw_writel(0, EXYNOS_ARM_CORE_CONFIGURATION(cpu)); + return; + } + core_conf = pmu_raw_readl(EXYNOS_ARM_CORE_CONFIGURATION(cpu)); core_conf &= ~S5P_CORE_LOCAL_PWR_EN; pmu_raw_writel(core_conf, EXYNOS_ARM_CORE_CONFIGURATION(cpu)); @@ -117,6 +134,9 @@ void exynos_cpu_power_up(int cpu) if (soc_is_exynos3250()) core_conf |= S5P_CORE_AUTOWAKEUP_EN; + else if (soc_is_exynos5260()) + core_conf = EXYNOS5260_CONFIGURATION_LOCAL_POWER_CFG | + EXYNOS5260_CONFIGURATION_INITIATE_WAKEUP_FROM_LOWPOWER; pmu_raw_writel(core_conf, EXYNOS_ARM_CORE_CONFIGURATION(cpu)); @@ -129,8 +149,17 @@ void exynos_cpu_power_up(int cpu) */ int exynos_cpu_power_state(int cpu) { - return (pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(cpu)) & - S5P_CORE_LOCAL_PWR_EN); + u32 status = pmu_raw_readl(EXYNOS_ARM_CORE_STATUS(cpu)); + + if (soc_is_exynos5260()) { + if ((status & EXYNOS5260_STATUS_STATES) == + EXYNOS5260_STATUS_STATES_NORMAL) + return S5P_CORE_LOCAL_PWR_EN; + else + return 0; + } + + return status & S5P_CORE_LOCAL_PWR_EN; } /** @@ -197,7 +226,7 @@ static inline void __iomem *cpu_boot_reg(int cpu) boot_reg = cpu_boot_reg_base(); if (!boot_reg) return IOMEM_ERR_PTR(-ENODEV); - if (soc_is_exynos4412()) + if (soc_is_exynos4412() || soc_is_exynos5260()) boot_reg += 4*cpu; else if (soc_is_exynos5420() || soc_is_exynos5800()) boot_reg += 4; @@ -371,7 +400,7 @@ static int exynos_boot_secondary(unsigned int cpu, struct task_struct *idle) call_firmware_op(cpu_boot, core_id); - if (soc_is_exynos3250()) + if (soc_is_exynos3250() /* || soc_is_exynos5260() */) dsb_sev(); else arch_send_wakeup_ipi_mask(cpumask_of(cpu)); diff --git a/arch/arm/mach-exynos/suspend.c b/arch/arm/mach-exynos/suspend.c index 0850505ac78b..174b8064a655 100644 --- a/arch/arm/mach-exynos/suspend.c +++ b/arch/arm/mach-exynos/suspend.c @@ -230,6 +230,7 @@ EXYNOS_PMU_IRQ(exynos3250_pmu_irq, "samsung,exynos3250-pmu"); EXYNOS_PMU_IRQ(exynos4210_pmu_irq, "samsung,exynos4210-pmu"); EXYNOS_PMU_IRQ(exynos4412_pmu_irq, "samsung,exynos4412-pmu"); EXYNOS_PMU_IRQ(exynos5250_pmu_irq, "samsung,exynos5250-pmu"); +EXYNOS_PMU_IRQ(exynos5260_pmu_irq, "samsung,exynos5260-pmu"); EXYNOS_PMU_IRQ(exynos5420_pmu_irq, "samsung,exynos5420-pmu"); static int exynos_cpu_do_idle(void) @@ -258,6 +259,24 @@ static int exynos3250_cpu_suspend(unsigned long arg) return exynos_cpu_do_idle(); } +static int exynos5260_cpu_suspend(unsigned long arg) +{ + /* MCPM works with HW CPU identifiers */ + unsigned int mpidr = read_cpuid_mpidr(); + unsigned int cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1); + unsigned int cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + + if (IS_ENABLED(CONFIG_EXYNOS5420_MCPM)) { + mcpm_set_entry_vector(cpu, cluster, exynos_cpu_resume); + mcpm_cpu_suspend(); + } + + pr_info("Failed to suspend the system\n"); + + /* return value != 0 means failure */ + return 1; +} + static int exynos5420_cpu_suspend(unsigned long arg) { /* MCPM works with HW CPU identifiers */ @@ -324,6 +343,17 @@ static void exynos3250_pm_prepare(void) pmu_raw_writel(__pa_symbol(exynos_cpu_resume), S5P_INFORM0); } +static void exynos5260_pm_prepare(void) +{ + /* Set wake-up mask registers */ + pmu_raw_writel(exynos_irqwake_intmask & ~(1 << 31), + EXYNOS5260_WAKEUP_MASK1); + /* EXYNOS5260_EINT_WAKEUP_MASK is set by pinctrl */ + + /* Set value of power down register for sleep mode */ + exynos_sys_powerdown_conf(SYS_SLEEP); +} + static void exynos5420_pm_prepare(void) { unsigned int tmp; @@ -369,14 +399,18 @@ static void exynos5420_pm_prepare(void) pmu_raw_writel(tmp, EXYNOS5420_PSGEN_OPTION); } - static int exynos_pm_suspend(void) { + int val; exynos_pm_central_suspend(); /* Setting SEQ_OPTION register */ - pmu_raw_writel(S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0, - S5P_CENTRAL_SEQ_OPTION); + if (soc_is_exynos5260()) + val = EXYNOS5260_USE_STANDBYWFI_KFC_CORE0 | + EXYNOS5260_USE_PROLOGNED_LOGIC_RESET; + else + val = S5P_USE_STANDBY_WFI0 | S5P_USE_STANDBY_WFE0; + pmu_raw_writel(val, S5P_CENTRAL_SEQ_OPTION); if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9) exynos_cpu_save_register(); @@ -384,6 +418,17 @@ static int exynos_pm_suspend(void) return 0; } +static int exynos5260_pm_suspend(void) +{ + pmu_raw_writel(EXYNOS5260_USE_STANDBYWFI_KFC_CORE0 | + EXYNOS5260_USE_PROLOGNED_LOGIC_RESET, + S5P_CENTRAL_SEQ_OPTION); + + exynos_pm_central_suspend(); + + return 0; +} + static int exynos5420_pm_suspend(void) { u32 this_cluster; @@ -442,6 +487,19 @@ static void exynos3250_pm_resume(void) pmu_raw_writel(0x0, S5P_INFORM1); } +static void exynos5260_pm_resume(void) +{ + pmu_raw_writel(EXYNOS5260_USE_STANDBY_WFI_ALL | + EXYNOS5260_USE_PROLOGNED_LOGIC_RESET, + S5P_CENTRAL_SEQ_OPTION); + + if (exynos_pm_central_resume()) + goto early_wakeup; + +early_wakeup: + return; +} + static void exynos5420_prepare_pm_resume(void) { if (IS_ENABLED(CONFIG_EXYNOS5420_MCPM)) @@ -593,6 +651,15 @@ static const struct exynos_pm_data exynos5250_pm_data = { .cpu_suspend = exynos_cpu_suspend, }; +static const struct exynos_pm_data exynos5260_pm_data = { + .wkup_irq = exynos3250_wkup_irq, + .wake_disable_mask = ((0xFF << 8) | (0x1F << 1)), + .pm_suspend = exynos5260_pm_suspend, + .pm_resume = exynos5260_pm_resume, + .pm_prepare = exynos5260_pm_prepare, + .cpu_suspend = exynos5260_cpu_suspend, +}; + static const struct exynos_pm_data exynos5420_pm_data = { .wkup_irq = exynos5250_wkup_irq, .wake_disable_mask = (0x7F << 7) | (0x1F << 1), @@ -617,6 +684,9 @@ static const struct of_device_id exynos_pmu_of_device_ids[] __initconst = { .compatible = "samsung,exynos5250-pmu", .data = &exynos5250_pm_data, }, { + .compatible = "samsung,exynos5260-pmu", + .data = &exynos5260_pm_data, + }, { .compatible = "samsung,exynos5420-pmu", .data = &exynos5420_pm_data, }, @@ -644,10 +714,12 @@ void __init exynos_pm_init(void) pm_data = (const struct exynos_pm_data *) match->data; - /* All wakeup disable */ - tmp = pmu_raw_readl(S5P_WAKEUP_MASK); - tmp |= pm_data->wake_disable_mask; - pmu_raw_writel(tmp, S5P_WAKEUP_MASK); + if (!soc_is_exynos5260()) { + /* All wakeup disable */ + tmp = pmu_raw_readl(S5P_WAKEUP_MASK); + tmp |= pm_data->wake_disable_mask; + pmu_raw_writel(tmp, S5P_WAKEUP_MASK); + } exynos_pm_syscore_ops.suspend = pm_data->pm_suspend; exynos_pm_syscore_ops.resume = pm_data->pm_resume; -- 2.13.6