diff options
Diffstat (limited to 'arch/arm/mach-at91/pm.c')
-rw-r--r-- | arch/arm/mach-at91/pm.c | 238 |
1 files changed, 164 insertions, 74 deletions
diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c index aa4116e9452f..5062699cbb12 100644 --- a/arch/arm/mach-at91/pm.c +++ b/arch/arm/mach-at91/pm.c @@ -29,19 +29,26 @@ #include <linux/atomic.h> #include <asm/mach/time.h> #include <asm/mach/irq.h> - -#include <mach/cpu.h> -#include <mach/hardware.h> +#include <asm/fncpy.h> +#include <asm/cacheflush.h> #include "generic.h" #include "pm.h" +/* + * FIXME: this is needed to communicate between the pinctrl driver and + * the PM implementation in the machine. Possibly part of the PM + * implementation should be moved down into the pinctrl driver and get + * called as part of the generic suspend/resume path. + */ +extern void at91_pinctrl_gpio_suspend(void); +extern void at91_pinctrl_gpio_resume(void); + static struct { unsigned long uhp_udp_mask; int memctrl; } at91_pm_data; -static void (*at91_pm_standby)(void); void __iomem *at91_ramc_base[2]; static int at91_pm_valid_state(suspend_state_t state) @@ -119,76 +126,67 @@ int at91_suspend_entering_slow_clock(void) } EXPORT_SYMBOL(at91_suspend_entering_slow_clock); - -static void (*slow_clock)(void __iomem *pmc, void __iomem *ramc0, +static void (*at91_suspend_sram_fn)(void __iomem *pmc, void __iomem *ramc0, void __iomem *ramc1, int memctrl); -#ifdef CONFIG_AT91_SLOW_CLOCK -extern void at91_slow_clock(void __iomem *pmc, void __iomem *ramc0, +extern void at91_pm_suspend_in_sram(void __iomem *pmc, void __iomem *ramc0, void __iomem *ramc1, int memctrl); -extern u32 at91_slow_clock_sz; -#endif +extern u32 at91_pm_suspend_in_sram_sz; + +static void at91_pm_suspend(suspend_state_t state) +{ + unsigned int pm_data = at91_pm_data.memctrl; + + pm_data |= (state == PM_SUSPEND_MEM) ? + AT91_PM_MODE(AT91_PM_SLOW_CLOCK) : 0; + + flush_cache_all(); + outer_disable(); + + at91_suspend_sram_fn(at91_pmc_base, at91_ramc_base[0], + at91_ramc_base[1], pm_data); + + outer_resume(); +} static int at91_pm_enter(suspend_state_t state) { at91_pinctrl_gpio_suspend(); switch (state) { + /* + * Suspend-to-RAM is like STANDBY plus slow clock mode, so + * drivers must suspend more deeply, the master clock switches + * to the clk32k and turns off the main oscillator + */ + case PM_SUSPEND_MEM: /* - * Suspend-to-RAM is like STANDBY plus slow clock mode, so - * drivers must suspend more deeply: only the master clock - * controller may be using the main oscillator. + * Ensure that clocks are in a valid state. */ - case PM_SUSPEND_MEM: - /* - * Ensure that clocks are in a valid state. - */ - if (!at91_pm_verify_clocks()) - goto error; - - /* - * Enter slow clock mode by switching over to clk32k and - * turning off the main oscillator; reverse on wakeup. - */ - if (slow_clock) { -#ifdef CONFIG_AT91_SLOW_CLOCK - /* copy slow_clock handler to SRAM, and call it */ - memcpy(slow_clock, at91_slow_clock, at91_slow_clock_sz); -#endif - slow_clock(at91_pmc_base, at91_ramc_base[0], - at91_ramc_base[1], - at91_pm_data.memctrl); - break; - } else { - pr_info("AT91: PM - no slow clock mode enabled ...\n"); - /* FALLTHROUGH leaving master clock alone */ - } + if (!at91_pm_verify_clocks()) + goto error; - /* - * STANDBY mode has *all* drivers suspended; ignores irqs not - * marked as 'wakeup' event sources; and reduces DRAM power. - * But otherwise it's identical to PM_SUSPEND_ON: cpu idle, and - * nothing fancy done with main or cpu clocks. - */ - case PM_SUSPEND_STANDBY: - /* - * NOTE: the Wait-for-Interrupt instruction needs to be - * in icache so no SDRAM accesses are needed until the - * wakeup IRQ occurs and self-refresh is terminated. - * For ARM 926 based chips, this requirement is weaker - * as at91sam9 can access a RAM in self-refresh mode. - */ - if (at91_pm_standby) - at91_pm_standby(); - break; + at91_pm_suspend(state); - case PM_SUSPEND_ON: - cpu_do_idle(); - break; + break; - default: - pr_debug("AT91: PM - bogus suspend state %d\n", state); - goto error; + /* + * STANDBY mode has *all* drivers suspended; ignores irqs not + * marked as 'wakeup' event sources; and reduces DRAM power. + * But otherwise it's identical to PM_SUSPEND_ON: cpu idle, and + * nothing fancy done with main or cpu clocks. + */ + case PM_SUSPEND_STANDBY: + at91_pm_suspend(state); + break; + + case PM_SUSPEND_ON: + cpu_do_idle(); + break; + + default: + pr_debug("AT91: PM - bogus suspend state %d\n", state); + goto error; } error: @@ -218,12 +216,99 @@ static struct platform_device at91_cpuidle_device = { .name = "cpuidle-at91", }; -void at91_pm_set_standby(void (*at91_standby)(void)) +static void at91_pm_set_standby(void (*at91_standby)(void)) { - if (at91_standby) { + if (at91_standby) at91_cpuidle_device.dev.platform_data = at91_standby; - at91_pm_standby = at91_standby; +} + +/* + * The AT91RM9200 goes into self-refresh mode with this command, and will + * terminate self-refresh automatically on the next SDRAM access. + * + * Self-refresh mode is exited as soon as a memory access is made, but we don't + * know for sure when that happens. However, we need to restore the low-power + * mode if it was enabled before going idle. Restoring low-power mode while + * still in self-refresh is "not recommended", but seems to work. + */ +static void at91rm9200_standby(void) +{ + u32 lpr = at91_ramc_read(0, AT91RM9200_SDRAMC_LPR); + + asm volatile( + "b 1f\n\t" + ".align 5\n\t" + "1: mcr p15, 0, %0, c7, c10, 4\n\t" + " str %0, [%1, %2]\n\t" + " str %3, [%1, %4]\n\t" + " mcr p15, 0, %0, c7, c0, 4\n\t" + " str %5, [%1, %2]" + : + : "r" (0), "r" (at91_ramc_base[0]), "r" (AT91RM9200_SDRAMC_LPR), + "r" (1), "r" (AT91RM9200_SDRAMC_SRR), + "r" (lpr)); +} + +/* We manage both DDRAM/SDRAM controllers, we need more than one value to + * remember. + */ +static void at91_ddr_standby(void) +{ + /* Those two values allow us to delay self-refresh activation + * to the maximum. */ + u32 lpr0, lpr1 = 0; + u32 saved_lpr0, saved_lpr1 = 0; + + if (at91_ramc_base[1]) { + saved_lpr1 = at91_ramc_read(1, AT91_DDRSDRC_LPR); + lpr1 = saved_lpr1 & ~AT91_DDRSDRC_LPCB; + lpr1 |= AT91_DDRSDRC_LPCB_SELF_REFRESH; + } + + saved_lpr0 = at91_ramc_read(0, AT91_DDRSDRC_LPR); + lpr0 = saved_lpr0 & ~AT91_DDRSDRC_LPCB; + lpr0 |= AT91_DDRSDRC_LPCB_SELF_REFRESH; + + /* self-refresh mode now */ + at91_ramc_write(0, AT91_DDRSDRC_LPR, lpr0); + if (at91_ramc_base[1]) + at91_ramc_write(1, AT91_DDRSDRC_LPR, lpr1); + + cpu_do_idle(); + + at91_ramc_write(0, AT91_DDRSDRC_LPR, saved_lpr0); + if (at91_ramc_base[1]) + at91_ramc_write(1, AT91_DDRSDRC_LPR, saved_lpr1); +} + +/* We manage both DDRAM/SDRAM controllers, we need more than one value to + * remember. + */ +static void at91sam9_sdram_standby(void) +{ + u32 lpr0, lpr1 = 0; + u32 saved_lpr0, saved_lpr1 = 0; + + if (at91_ramc_base[1]) { + saved_lpr1 = at91_ramc_read(1, AT91_SDRAMC_LPR); + lpr1 = saved_lpr1 & ~AT91_SDRAMC_LPCB; + lpr1 |= AT91_SDRAMC_LPCB_SELF_REFRESH; } + + saved_lpr0 = at91_ramc_read(0, AT91_SDRAMC_LPR); + lpr0 = saved_lpr0 & ~AT91_SDRAMC_LPCB; + lpr0 |= AT91_SDRAMC_LPCB_SELF_REFRESH; + + /* self-refresh mode now */ + at91_ramc_write(0, AT91_SDRAMC_LPR, lpr0); + if (at91_ramc_base[1]) + at91_ramc_write(1, AT91_SDRAMC_LPR, lpr1); + + cpu_do_idle(); + + at91_ramc_write(0, AT91_SDRAMC_LPR, saved_lpr0); + if (at91_ramc_base[1]) + at91_ramc_write(1, AT91_SDRAMC_LPR, saved_lpr1); } static const struct of_device_id ramc_ids[] __initconst = { @@ -263,7 +348,6 @@ static __init void at91_dt_ramc(void) at91_pm_set_standby(standby); } -#ifdef CONFIG_AT91_SLOW_CLOCK static void __init at91_pm_sram_init(void) { struct gen_pool *sram_pool; @@ -291,30 +375,36 @@ static void __init at91_pm_sram_init(void) return; } - sram_base = gen_pool_alloc(sram_pool, at91_slow_clock_sz); + sram_base = gen_pool_alloc(sram_pool, at91_pm_suspend_in_sram_sz); if (!sram_base) { - pr_warn("%s: unable to alloc ocram!\n", __func__); + pr_warn("%s: unable to alloc sram!\n", __func__); return; } sram_pbase = gen_pool_virt_to_phys(sram_pool, sram_base); - slow_clock = __arm_ioremap_exec(sram_pbase, at91_slow_clock_sz, false); -} -#endif + at91_suspend_sram_fn = __arm_ioremap_exec(sram_pbase, + at91_pm_suspend_in_sram_sz, false); + if (!at91_suspend_sram_fn) { + pr_warn("SRAM: Could not map\n"); + return; + } + /* Copy the pm suspend handler to SRAM */ + at91_suspend_sram_fn = fncpy(at91_suspend_sram_fn, + &at91_pm_suspend_in_sram, at91_pm_suspend_in_sram_sz); +} static void __init at91_pm_init(void) { -#ifdef CONFIG_AT91_SLOW_CLOCK at91_pm_sram_init(); -#endif - - pr_info("AT91: Power Management%s\n", (slow_clock ? " (with slow clock mode)" : "")); if (at91_cpuidle_device.dev.platform_data) platform_device_register(&at91_cpuidle_device); - suspend_set_ops(&at91_pm_ops); + if (at91_suspend_sram_fn) + suspend_set_ops(&at91_pm_ops); + else + pr_info("AT91: PM not supported, due to no SRAM allocated\n"); } void __init at91rm9200_pm_init(void) |