diff options
Diffstat (limited to 'arch/arm/mach-at91/pm.c')
| -rw-r--r-- | arch/arm/mach-at91/pm.c | 258 | 
1 files changed, 173 insertions, 85 deletions
diff --git a/arch/arm/mach-at91/pm.c b/arch/arm/mach-at91/pm.c index 5e34fb143309..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,60 +348,63 @@ 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;  	phys_addr_t sram_pbase;  	unsigned long sram_base;  	struct device_node *node; -	struct platform_device *pdev; +	struct platform_device *pdev = NULL; -	node = of_find_compatible_node(NULL, NULL, "mmio-sram"); -	if (!node) { -		pr_warn("%s: failed to find sram node!\n", __func__); -		return; +	for_each_compatible_node(node, NULL, "mmio-sram") { +		pdev = of_find_device_by_node(node); +		if (pdev) { +			of_node_put(node); +			break; +		}  	} -	pdev = of_find_device_by_node(node);  	if (!pdev) {  		pr_warn("%s: failed to find sram device!\n", __func__); -		goto put_node; +		return;  	}  	sram_pool = dev_get_gen_pool(&pdev->dev);  	if (!sram_pool) {  		pr_warn("%s: sram pool unavailable!\n", __func__); -		goto put_node; +		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__); -		goto put_node; +		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); +	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; +	} -put_node: -	of_node_put(node); +	/* 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);  } -#endif -  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)  |