From mboxrd@z Thu Jan 1 00:00:00 1970 From: daniel.lezcano@linaro.org (Daniel Lezcano) Date: Thu, 11 Apr 2013 00:35:21 +0200 Subject: [PATCH] ARM: shmobile: r8a7740: Add Suspend-To-RAM modes and CPUIdle In-Reply-To: <1365609107-14290-1-git-send-email-hechtb+renesas@gmail.com> References: <1365609107-14290-1-git-send-email-hechtb+renesas@gmail.com> Message-ID: <5165E929.9030309@linaro.org> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org On 04/10/2013 05:51 PM, Bastian Hecht wrote: > We add 2 sleep modes and register a CPUIdle driver that uses them. > > - A3SM PLL0 on/off: Power domain A3SM that contains the ARM core > and the 2nd level cache with either PLL0 on > or off > > As the suspend to memory mechanism we use A3SM PLL off. > > The setup of the SYSC regarding the external IRQs is taken from > pm-sh7372.c from Magnus Damm. > > Signed-off-by: Bastian Hecht > --- > Ok I need to add some remarks here: > > First this patch relies on a fix I've sent out today: > "ARM: hw_breakpoint: Do not use __cpuinitdata for dbg_cpu_pm_nb" > Without it we can't use suspend at all without a kernel panic. > > Next you can test this in two ways: > Either add IRQF_NO_SUSPEND to the touchscreen driver st1232 at IRQ registration > (hack) or extend the irqpin driver to support wakeup devices properly, meaning > adding suspend and resume callbacks that handle it (proper way). Or you can add > no_console_suspend to the boot options in r8a7740-armadillo800eva.dts. This way > you can use the serial console as wakeup device. > > > arch/arm/mach-shmobile/Makefile | 2 +- > arch/arm/mach-shmobile/include/mach/r8a7740.h | 2 + > arch/arm/mach-shmobile/pm-r8a7740.c | 220 ++++++++++++++++++++++++- > arch/arm/mach-shmobile/sleep-r8a7740.S | 68 ++++++++ > 4 files changed, 289 insertions(+), 3 deletions(-) > create mode 100644 arch/arm/mach-shmobile/sleep-r8a7740.S > > diff --git a/arch/arm/mach-shmobile/Makefile b/arch/arm/mach-shmobile/Makefile > index 068f1da..0568894 100644 > --- a/arch/arm/mach-shmobile/Makefile > +++ b/arch/arm/mach-shmobile/Makefile > @@ -30,7 +30,7 @@ obj-$(CONFIG_SUSPEND) += suspend.o > obj-$(CONFIG_CPU_IDLE) += cpuidle.o > obj-$(CONFIG_ARCH_SHMOBILE) += pm-rmobile.o > obj-$(CONFIG_ARCH_SH7372) += pm-sh7372.o sleep-sh7372.o > -obj-$(CONFIG_ARCH_R8A7740) += pm-r8a7740.o > +obj-$(CONFIG_ARCH_R8A7740) += pm-r8a7740.o sleep-r8a7740.o > obj-$(CONFIG_ARCH_R8A7779) += pm-r8a7779.o > obj-$(CONFIG_ARCH_SH73A0) += pm-sh73a0.o > > diff --git a/arch/arm/mach-shmobile/include/mach/r8a7740.h b/arch/arm/mach-shmobile/include/mach/r8a7740.h > index abdc4d4..ba4707b 100644 > --- a/arch/arm/mach-shmobile/include/mach/r8a7740.h > +++ b/arch/arm/mach-shmobile/include/mach/r8a7740.h > @@ -540,6 +540,8 @@ extern void r8a7740_add_standard_devices(void); > extern void r8a7740_clock_init(u8 md_ck); > extern void r8a7740_pinmux_init(void); > extern void r8a7740_pm_init(void); > +extern void r8a7740_resume(void); > +extern void r8a7740_shutdown(void); > > #ifdef CONFIG_PM > extern void __init r8a7740_init_pm_domains(void); > diff --git a/arch/arm/mach-shmobile/pm-r8a7740.c b/arch/arm/mach-shmobile/pm-r8a7740.c > index 40b87aa..d4613f0 100644 > --- a/arch/arm/mach-shmobile/pm-r8a7740.c > +++ b/arch/arm/mach-shmobile/pm-r8a7740.c > @@ -8,10 +8,54 @@ > * License. See the file "COPYING" in the main directory of this archive > * for more details. > */ > +#include > #include > +#include > +#include > #include > +#include > +#include > +#include > +#include > +#include > #include > #include > +#include > + > +/* CPGA */ > +#define PLLC01STPCR IOMEM(0xe61500c8) > +#define SYSTBCR IOMEM(0xe6150024) > + > +/* SYSC */ > +#define STBCHR IOMEM(0xe6180000) > +#define STBCHRB IOMEM(0xe6180040) > +#define SPDCR IOMEM(0xe6180008) > +#define SBAR IOMEM(0xe6180020) > +#define SRSTFR IOMEM(0xe61800B4) > +#define WUPSMSK IOMEM(0xe618002c) > +#define WUPSMSK2 IOMEM(0xe6180048) > +#define WUPSFAC IOMEM(0xe6180098) > +#define IRQCR IOMEM(0xe618022c) > +#define IRQCR2 IOMEM(0xe6180238) > +#define IRQCR3 IOMEM(0xe6180244) > +#define IRQCR4 IOMEM(0xe6180248) > + > +/* SRSTFR flags */ > +#define RAMST (1 << 19) > +#define RCLNKA (1 << 7) > +#define RCPRES (1 << 5) > +#define RCWD1 (1 << 4) > +#define RPF (1 << 0) > + > +/* INTC */ > +#define ICR1A IOMEM(0xe6900000) > +#define ICR2A IOMEM(0xe6900004) > +#define ICR3A IOMEM(0xe6900008) > +#define ICR4A IOMEM(0xe690000c) > +#define INTMSK00A IOMEM(0xe6900040) > +#define INTMSK10A IOMEM(0xe6900044) > +#define INTMSK20A IOMEM(0xe6900048) > +#define INTMSK30A IOMEM(0xe690004c) > > #ifdef CONFIG_PM > static int r8a7740_pd_a4s_suspend(void) > @@ -58,12 +102,183 @@ void __init r8a7740_init_pm_domains(void) > rmobile_init_domains(r8a7740_pm_domains, ARRAY_SIZE(r8a7740_pm_domains)); > pm_genpd_add_subdomain_names("A4S", "A3SP"); > } > - > #endif /* CONFIG_PM */ > > +#if defined(CONFIG_SUSPEND) || defined(CONFIG_CPU_IDLE) > +static void r8a7740_set_reset_vector(unsigned long address) > +{ > + __raw_writel(address, SBAR); > +} > + > +static void r8a7740_icr_to_irqcr(unsigned long icr, u16 *irqcr1p, u16 *irqcr2p) > +{ > + u16 tmp, irqcr1, irqcr2; > + int k; > + > + irqcr1 = 0; > + irqcr2 = 0; > + > + /* convert INTCA ICR register layout to SYSC IRQCR+IRQCR2 */ > + for (k = 0; k <= 7; k++) { > + tmp = (icr >> ((7 - k) * 4)) & 0xf; > + irqcr1 |= (tmp & 0x03) << (k * 2); > + irqcr2 |= (tmp >> 2) << (k * 2); > + } > + > + *irqcr1p = irqcr1; > + *irqcr2p = irqcr2; > +} > + > +static void r8a7740_setup_sysc(unsigned long msk, unsigned long msk2) > +{ > + u16 irqcrx_low, irqcrx_high, irqcry_low, irqcry_high; > + unsigned long tmp; > + > + /* read IRQ0A -> IRQ15A mask */ > + tmp = bitrev8(__raw_readb(INTMSK00A)); > + tmp |= bitrev8(__raw_readb(INTMSK10A)) << 8; > + > + /* setup WUPSMSK from clocks and external IRQ mask */ > + msk = (~msk & 0xc030000f) | (tmp << 4); > + __raw_writel(msk, WUPSMSK); > + > + /* propage level/edge trigger for external IRQ 0->15 */ > + r8a7740_icr_to_irqcr(__raw_readl(ICR1A), &irqcrx_low, &irqcry_low); > + r8a7740_icr_to_irqcr(__raw_readl(ICR2A), &irqcrx_high, &irqcry_high); > + __raw_writel((irqcrx_high << 16) | irqcrx_low, IRQCR); > + __raw_writel((irqcry_high << 16) | irqcry_low, IRQCR2); > + > + /* read IRQ16A -> IRQ31A mask */ > + tmp = bitrev8(__raw_readb(INTMSK20A)); > + tmp |= bitrev8(__raw_readb(INTMSK30A)) << 8; > + > + /* setup WUPSMSK2 from clocks and external IRQ mask */ > + msk2 = (~msk2 & 0x00030000) | tmp; > + __raw_writel(msk2, WUPSMSK2); > + > + /* propage level/edge trigger for external IRQ 16->31 */ > + r8a7740_icr_to_irqcr(__raw_readl(ICR3A), &irqcrx_low, &irqcry_low); > + r8a7740_icr_to_irqcr(__raw_readl(ICR4A), &irqcrx_high, &irqcry_high); > + __raw_writel((irqcrx_high << 16) | irqcrx_low, IRQCR3); > + __raw_writel((irqcry_high << 16) | irqcry_low, IRQCR4); > +} > + > +static void r8a7740_prepare_wakeup(void) > +{ > + /* clear all flags that lead to a cold boot */ > + __raw_writel(~(RAMST | RCLNKA | RCPRES | RCWD1 | RPF), SRSTFR); > + /* indicate warm boot */ > + __raw_writel(0x80000000, STBCHRB); > + /* clear other flags checked by internal ROM boot loader */ > + __raw_writel(0x00000000, STBCHR); > +} > + > +static int r8a7740_do_suspend(unsigned long unused) > +{ > + /* > + * cpu_suspend() guarantees that all data made it to the L2. > + * Flush it out now and disable the cache controller. > + */ > + outer_flush_all(); > + outer_disable(); > + > + r8a7740_shutdown(); > + > + /* in case WFI fails to enter the low power state, restore things */ > + outer_resume(); > + > + return 0; > +} > + > +static void r8a7740_enter_a3sm_common(int pllc0_on) > +{ > + u32 reg32; > + > + if (pllc0_on) > + __raw_writel(0, PLLC01STPCR); > + else > + __raw_writel(1 << 28, PLLC01STPCR); > + > + r8a7740_set_reset_vector(__pa(r8a7740_resume)); > + r8a7740_prepare_wakeup(); > + r8a7740_setup_sysc(1 << 0, 0); > + > + /* Activate delayed shutdown of A3SM */ > + reg32 = __raw_readl(SPDCR); > + reg32 |= (1 << 31) | (1 << 12); > + __raw_writel(reg32, SPDCR); > + > + /* We activate CPU Core Standby as well here */ > + reg32 = __raw_readl(SYSTBCR); > + reg32 |= (1 << 4); > + __raw_writel(reg32, SYSTBCR); > + > + /* Clear Wakeup Factors and do suspend */ > + reg32 = __raw_readl(WUPSFAC); > + cpu_suspend(0, r8a7740_do_suspend); > + outer_resume(); > + reg32 = __raw_readl(WUPSFAC); > + > + /* Clear CPU Core Standby flag for other WFI instructions */ > + reg32 &= ~(1 << 4); > + __raw_writel(reg32, SYSTBCR); > + > +} > +#endif /* CONFIG_SUSPEND || CONFIG_CPU_IDLE */ > + > +#ifdef CONFIG_CPU_IDLE > +static int r8a7740_enter_a3sm_pll_on(struct cpuidle_device *dev, > + struct cpuidle_driver *drv, int index) > +{ > + r8a7740_enter_a3sm_common(1); > + return 1; > +} > + > +static int r8a7740_enter_a3sm_pll_off(struct cpuidle_device *dev, > + struct cpuidle_driver *drv, int index) > +{ > + r8a7740_enter_a3sm_common(0); > + return 2; > +} > + > +static struct cpuidle_driver r8a7740_cpuidle_driver = { > + .name = "r8a7740_cpuidle", > + .owner = THIS_MODULE, > + .en_core_tk_irqen = 1, > + .state_count = 3, > + .safe_state_index = 0, /* C1 */ > + .states[0] = ARM_CPUIDLE_WFI_STATE, > + .states[0].enter = shmobile_enter_wfi, The WFI macro already set a simple enter function doing cpu_do_idle. We are trying to consolidate the cpuidle drivers across the different platforms [1]. One of the objective is to move the cpuidle drivers to drivers/cpuidle, a place where they belong to. Do you think it is possible you split your code to have one part with the cpuidle driver and another part with the PM code ? [1] http://www.spinics.net/lists/arm-kernel/msg237282.html [2] https://patchwork.kernel.org/patch/2365041/ > + .states[1] = { > + .name = "C2", > + .desc = "A3SM PLL ON", > + .exit_latency = 40, > + .target_residency = 30 + 40, > + .flags = CPUIDLE_FLAG_TIME_VALID, > + .enter = r8a7740_enter_a3sm_pll_on, > + }, > + .states[2] = { > + .name = "C3", > + .desc = "A3SM PLL OFF", > + .exit_latency = 120, > + .target_residency = 30 + 120, > + .flags = CPUIDLE_FLAG_TIME_VALID, > + .enter = r8a7740_enter_a3sm_pll_off, > + }, > +}; > + > +static void r8a7740_cpuidle_init(void) > +{ > + shmobile_cpuidle_set_driver(&r8a7740_cpuidle_driver); > +} > +#else > +static void r8a7740_cpuidle_init(void) {} > +#endif /* CONFIG_CPU_IDLE */ > + > #ifdef CONFIG_SUSPEND > static int r8a7740_enter_suspend(suspend_state_t suspend_state) > { > + r8a7740_enter_a3sm_common(0); > cpu_do_idle(); > return 0; > } > @@ -74,9 +289,10 @@ static void r8a7740_suspend_init(void) > } > #else > static void r8a7740_suspend_init(void) {} > -#endif > +#endif /* CONFIG_SUSPEND */ > > void __init r8a7740_pm_init(void) > { > r8a7740_suspend_init(); > + r8a7740_cpuidle_init(); > } > diff --git a/arch/arm/mach-shmobile/sleep-r8a7740.S b/arch/arm/mach-shmobile/sleep-r8a7740.S > new file mode 100644 > index 0000000..0ae310d > --- /dev/null > +++ b/arch/arm/mach-shmobile/sleep-r8a7740.S > @@ -0,0 +1,68 @@ > +/* > + * Low level sleep code for the SoC r8a7740 > + * > + * Copyright (C) 2013 Bastian Hecht > + * > + * This file is subject to the terms and conditions of the GNU General Public > + * License. See the file "COPYING" in the main directory of this archive > + * for more details. > + */ > + > +#include > +#include > +#include > + > +#if defined(CONFIG_SUSPEND) || defined(CONFIG_CPU_IDLE) > + > +/* r8a7740_shutdown expects L1 and L2 to be flushed */ > + .text > +ENTRY(r8a7740_shutdown) > + stmfd sp!, {r4-r12, lr} > + > + /* make sure the stack stays intact */ > + bl v7_flush_dcache_all > + > + /* > + * Clear the SCTLR.C bit to prevent further data cache > + * allocation. Clearing SCTLR.C would make all the data accesses > + * strongly ordered and would not hit the cache. > + */ > + mrc p15, 0, r0, c1, c0, 0 > + bic r0, r0, #(1 << 2) @ Disable the C bit > + mcr p15, 0, r0, c1, c0, 0 > + isb > + > + bl v7_flush_dcache_all > + bl cpu_v7_do_idle > + > + /* in rare cases when WFI fails we end up here and restore things */ > + mrc p15, 0, r0, c1, c0, 0 > + orr r0, r0, #(1 << 2) @ Enable the C bit > + mcr p15, 0, r0, c1, c0, 0 > + isb > + > + ldmfd sp!, {r4-r12, pc} > +ENDPROC(r8a7740) > + > + .text > +ENTRY(v7_cpu_resume) > + bl v7_invalidate_l1 > + b cpu_resume > +ENDPROC(v7_cpu_resume) > + > +/* > + * The entry point of a warm reboot, used by wakeup scenarios > + * > + * The CPU jumps in this case to (0xfffff000 & SBAR), so we need > + * to align this function properly. > + * We use a long jump into the text segment and use the physical > + * address as the MMU is still turned off. > + */ > + .align 12 > + .text > +ENTRY(r8a7740_resume) > + ldr pc, 1f > +1: .long v7_cpu_resume - PAGE_OFFSET + PLAT_PHYS_OFFSET > +ENDPROC(r8a7740_resume_core_standby) > +#endif > -- Linaro.org ? Open source software for ARM SoCs Follow Linaro: Facebook | Twitter | Blog