diff options
author | Andrew Victor <andrew@sanpeople.com> | 2006-06-20 19:30:19 +0100 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2006-06-20 19:30:19 +0100 |
commit | 907d6deb625cd7ff5fea3ef4b20dfb6c1c19c3ee (patch) | |
tree | e89da97418b5b4fa51212bc14a558bcf0c3f900b | |
parent | 6902f523a328bba5af036699b1352389c0746526 (diff) | |
download | blackbird-op-linux-907d6deb625cd7ff5fea3ef4b20dfb6c1c19c3ee.tar.gz blackbird-op-linux-907d6deb625cd7ff5fea3ef4b20dfb6c1c19c3ee.zip |
[ARM] 3605/1: AT91RM9200 Power Management
Patch from Andrew Victor
This patch adds the core Power Management support for the AT91RM9200
processor. It will support suspend-to-RAM and standby modes.
The suspend-to-RAM functionality is not 100% complete. The code that
needs to be execute from the internal SRAM to restore the system is
outstanding. For now we just fall through to Standby mode.
The AT91-specific at91_suspend_entering_slow_clock() function will
eventually be replaced by clk_must_disable() once that functionality is
added to mainline clock API.
Patch from David Brownell.
Signed-off-by: Andrew Victor <andrew@sanpeople.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r-- | arch/arm/mach-at91rm9200/generic.h | 7 | ||||
-rw-r--r-- | arch/arm/mach-at91rm9200/pm.c | 225 | ||||
-rw-r--r-- | include/asm-arm/arch-at91rm9200/gpio.h | 5 |
3 files changed, 237 insertions, 0 deletions
diff --git a/arch/arm/mach-at91rm9200/generic.h b/arch/arm/mach-at91rm9200/generic.h index 9bd541eba0a0..f0d969d7d874 100644 --- a/arch/arm/mach-at91rm9200/generic.h +++ b/arch/arm/mach-at91rm9200/generic.h @@ -16,3 +16,10 @@ extern struct sys_timer at91rm9200_timer; extern void __init at91rm9200_map_io(void); extern int __init at91_clock_init(unsigned long main_clock); +struct device; +extern void __init at91_clock_associate(const char *id, struct device *dev, const char *func); + + /* Power Management */ +extern void at91_irq_suspend(void); +extern void at91_irq_resume(void); + diff --git a/arch/arm/mach-at91rm9200/pm.c b/arch/arm/mach-at91rm9200/pm.c new file mode 100644 index 000000000000..47e5480feb7e --- /dev/null +++ b/arch/arm/mach-at91rm9200/pm.c @@ -0,0 +1,225 @@ +/* + * arch/arm/mach-at91rm9200/pm.c + * AT91 Power Management + * + * Copyright (C) 2005 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/pm.h> +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <linux/pm.h> +#include <linux/interrupt.h> +#include <linux/sysfs.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/atomic.h> +#include <asm/mach/time.h> +#include <asm/mach/irq.h> +#include <asm/mach-types.h> + +#include <asm/arch/gpio.h> + +#include "generic.h" + + +static int at91_pm_valid_state(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_ON: + case PM_SUSPEND_STANDBY: + case PM_SUSPEND_MEM: + return 1; + + default: + return 0; + } +} + + +static suspend_state_t target_state; + +/* + * Called after processes are frozen, but before we shutdown devices. + */ +static int at91_pm_prepare(suspend_state_t state) +{ + target_state = state; + return 0; +} + +/* + * Verify that all the clocks are correct before entering + * slow-clock mode. + */ +static int at91_pm_verify_clocks(void) +{ + unsigned long scsr; + int i; + + scsr = at91_sys_read(AT91_PMC_SCSR); + + /* USB must not be using PLLB */ + if ((scsr & (AT91_PMC_UHP | AT91_PMC_UDP)) != 0) { + pr_debug("AT91: PM - Suspend-to-RAM with USB still active\n"); + return 0; + } + +#ifdef CONFIG_AT91_PROGRAMMABLE_CLOCKS + /* PCK0..PCK3 must be disabled, or configured to use clk32k */ + for (i = 0; i < 4; i++) { + u32 css; + + if ((scsr & (AT91_PMC_PCK0 << i)) == 0) + continue; + + css = at91_sys_read(AT91_PMC_PCKR(i)) & AT91_PMC_CSS; + if (css != AT91_PMC_CSS_SLOW) { + pr_debug("AT91: PM - Suspend-to-RAM with PCK%d src %d\n", i, css); + return 0; + } + } +#endif + + return 1; +} + +/* + * Call this from platform driver suspend() to see how deeply to suspend. + * For example, some controllers (like OHCI) need one of the PLL clocks + * in order to act as a wakeup source, and those are not available when + * going into slow clock mode. + * + * REVISIT: generalize as clk_will_be_available(clk)? Other platforms have + * the very same problem (but not using at91 main_clk), and it'd be better + * to add one generic API rather than lots of platform-specific ones. + */ +int at91_suspend_entering_slow_clock(void) +{ + return (target_state == PM_SUSPEND_MEM); +} +EXPORT_SYMBOL(at91_suspend_entering_slow_clock); + + +static void (*slow_clock)(void); + + + +static int at91_pm_enter(suspend_state_t state) +{ + at91_gpio_suspend(); + at91_irq_suspend(); + + pr_debug("AT91: PM - wake mask %08x, pm state %d\n", + /* remember all the always-wake irqs */ + (at91_sys_read(AT91_PMC_PCSR) + | (1 << AT91_ID_FIQ) + | (1 << AT91_ID_SYS) + | (1 << AT91_ID_IRQ0) + | (1 << AT91_ID_IRQ1) + | (1 << AT91_ID_IRQ2) + | (1 << AT91_ID_IRQ3) + | (1 << AT91_ID_IRQ4) + | (1 << AT91_ID_IRQ5) + | (1 << AT91_ID_IRQ6)) + & at91_sys_read(AT91_AIC_IMR), + state); + + switch (state) { + /* + * 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. + */ + 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) { + slow_clock(); + break; + } else { + /* DEVELOPMENT ONLY */ + pr_info("AT91: PM - no slow clock mode yet ...\n"); + /* FALLTHROUGH leaving master clock alone */ + } + + /* + * 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 the SDRAM stays in self-refresh mode until + * the wakeup IRQ occurs. + */ + asm("b 1f; .align 5; 1:"); + asm("mcr p15, 0, r0, c7, c10, 4"); /* drain write buffer */ + at91_sys_write(AT91_SDRAMC_SRR, 1); /* self-refresh mode */ + /* fall though to next state */ + + case PM_SUSPEND_ON: + asm("mcr p15, 0, r0, c7, c0, 4"); /* wait for interrupt */ + break; + + default: + pr_debug("AT91: PM - bogus suspend state %d\n", state); + goto error; + } + + pr_debug("AT91: PM - wakeup %08x\n", + at91_sys_read(AT91_AIC_IPR) & at91_sys_read(AT91_AIC_IMR)); + +error: + target_state = PM_SUSPEND_ON; + at91_irq_resume(); + at91_gpio_resume(); + return 0; +} + + +static struct pm_ops at91_pm_ops ={ + .pm_disk_mode = 0, + .valid = at91_pm_valid_state, + .prepare = at91_pm_prepare, + .enter = at91_pm_enter, +}; + +static int __init at91_pm_init(void) +{ + printk("AT91: Power Management\n"); + +#ifdef CONFIG_AT91_PM_SLOW_CLOCK + /* REVISIT allocations of SRAM should be dynamically managed. + * FIQ handlers and other components will want SRAM/TCM too... + */ + slow_clock = (void *) (AT91_VA_BASE_SRAM + (3 * SZ_4K)); + memcpy(slow_clock, at91rm9200_slow_clock, at91rm9200_slow_clock_sz); +#endif + + /* Disable SDRAM low-power mode. Cannot be used with self-refresh. */ + at91_sys_write(AT91_SDRAMC_LPR, 0); + + pm_set_ops(&at91_pm_ops); + + return 0; +} +arch_initcall(at91_pm_init); diff --git a/include/asm-arm/arch-at91rm9200/gpio.h b/include/asm-arm/arch-at91rm9200/gpio.h index 6176ab2dc417..dbde1baaf251 100644 --- a/include/asm-arm/arch-at91rm9200/gpio.h +++ b/include/asm-arm/arch-at91rm9200/gpio.h @@ -13,6 +13,8 @@ #ifndef __ASM_ARCH_AT91RM9200_GPIO_H #define __ASM_ARCH_AT91RM9200_GPIO_H +#include <asm/irq.h> + #define PIN_BASE NR_AIC_IRQS #define PQFP_GPIO_BANKS 3 /* PQFP package has 3 banks */ @@ -188,6 +190,9 @@ extern int at91_set_multi_drive(unsigned pin, int is_on); /* callable at any time */ extern int at91_set_gpio_value(unsigned pin, int value); extern int at91_get_gpio_value(unsigned pin); + +extern void at91_gpio_suspend(void); +extern void at91_gpio_resume(void); #endif #endif |