Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 02a00cf6 authored by Haavard Skinnemoen's avatar Haavard Skinnemoen Committed by Haavard Skinnemoen
Browse files

avr32: Power Management support ("standby" and "mem" modes)



Implement Standby support. In this mode, we'll suspend all drivers,
put the SDRAM in self-refresh mode and switch off the HSB bus
("frozen" mode.)

Implement Suspend-to-mem support. In this mode, we suspend all
drivers, put the SDRAM into self-refresh mode and switch off all
internal clocks except the 32 kHz oscillator ("stop" mode.)

The lowest-level suspend code runs from a small portion of SRAM
allocated at startup time. This gets rid of a small potential race
with the SDRAM where we might try to enter self-refresh mode in the
middle of an icache burst. We also relocate all interrupt and
exception handlers to SRAM during the small window when we enter and
exit the low-power modes.

We don't need to do any special tricks to start and stop the PLL. The
main clock is automatically gated by hardware until the PLL is stable.

Signed-off-by: default avatarHaavard Skinnemoen <hskinnemoen@atmel.com>
parent aa8e87ca
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -205,6 +205,11 @@ endmenu

menu "Power management options"

source "kernel/power/Kconfig"

config ARCH_SUSPEND_POSSIBLE
	def_bool y

menu "CPU Frequency scaling"

source "drivers/cpufreq/Kconfig"
+5 −0
Original line number Diff line number Diff line
obj-y				+= pdc.o clock.o intc.o extint.o pio.o hsmc.o
obj-$(CONFIG_CPU_AT32AP700X)	+= at32ap700x.o pm-at32ap700x.o
obj-$(CONFIG_CPU_FREQ_AT32AP)	+= cpufreq.o
obj-$(CONFIG_PM)		+= pm.o

ifeq ($(CONFIG_PM_DEBUG),y)
CFLAGS_pm.o	+= -DDEBUG
endif
+53 −1
Original line number Diff line number Diff line
@@ -22,6 +22,10 @@ struct intc {
	void __iomem		*regs;
	struct irq_chip		chip;
	struct sys_device	sysdev;
#ifdef CONFIG_PM
	unsigned long		suspend_ipr;
	unsigned long		saved_ipr[64];
#endif
};

extern struct platform_device at32_intc0_device;
@@ -138,8 +142,56 @@ fail:
	panic("Interrupt controller initialization failed!\n");
}

#ifdef CONFIG_PM
void intc_set_suspend_handler(unsigned long offset)
{
	intc0.suspend_ipr = offset;
}

static int intc_suspend(struct sys_device *sdev, pm_message_t state)
{
	struct intc *intc = container_of(sdev, struct intc, sysdev);
	int i;

	if (unlikely(!irqs_disabled())) {
		pr_err("intc_suspend: called with interrupts enabled\n");
		return -EINVAL;
	}

	if (unlikely(!intc->suspend_ipr)) {
		pr_err("intc_suspend: suspend_ipr not initialized\n");
		return -EINVAL;
	}

	for (i = 0; i < 64; i++) {
		intc->saved_ipr[i] = intc_readl(intc, INTPR0 + 4 * i);
		intc_writel(intc, INTPR0 + 4 * i, intc->suspend_ipr);
	}

	return 0;
}

static int intc_resume(struct sys_device *sdev)
{
	struct intc *intc = container_of(sdev, struct intc, sysdev);
	int i;

	WARN_ON(!irqs_disabled());

	for (i = 0; i < 64; i++)
		intc_writel(intc, INTPR0 + 4 * i, intc->saved_ipr[i]);

	return 0;
}
#else
#define intc_suspend	NULL
#define intc_resume	NULL
#endif

static struct sysdev_class intc_class = {
	.name		= "intc",
	.suspend	= intc_suspend,
	.resume		= intc_resume,
};

static int __init intc_init_sysdev(void)
+108 −0
Original line number Diff line number Diff line
@@ -12,6 +12,12 @@
#include <asm/thread_info.h>
#include <asm/arch/pm.h>

#include "pm.h"
#include "sdramc.h"

/* Same as 0xfff00000 but fits in a 21 bit signed immediate */
#define PM_BASE	-0x100000

	.section .bss, "wa", @nobits
	.global	disable_idle_sleep
	.type	disable_idle_sleep, @object
@@ -64,3 +70,105 @@ cpu_idle_skip_sleep:
	unmask_interrupts
	retal	r12
	.size	cpu_idle_skip_sleep, . - cpu_idle_skip_sleep

#ifdef CONFIG_PM
	.section .init.text, "ax", @progbits

	.global	pm_exception
	.type	pm_exception, @function
pm_exception:
	/*
	 * Exceptions are masked when we switch to this handler, so
	 * we'll only get "unrecoverable" exceptions (offset 0.)
	 */
	sub	r12, pc, . - .Lpanic_msg
	lddpc	pc, .Lpanic_addr

	.align	2
.Lpanic_addr:
	.long	panic
.Lpanic_msg:
	.asciz	"Unrecoverable exception during suspend\n"
	.size	pm_exception, . - pm_exception

	.global	pm_irq0
	.type	pm_irq0, @function
pm_irq0:
	/* Disable interrupts and return after the sleep instruction */
	mfsr	r9, SYSREG_RSR_INT0
	mtsr	SYSREG_RAR_INT0, r8
	sbr	r9, SYSREG_GM_OFFSET
	mtsr	SYSREG_RSR_INT0, r9
	rete

	/*
	 * void cpu_enter_standby(unsigned long sdramc_base)
	 *
	 * Enter PM_SUSPEND_STANDBY mode. At this point, all drivers
	 * are suspended and interrupts are disabled. Interrupts
	 * marked as 'wakeup' event sources may still come along and
	 * get us out of here.
	 *
	 * The SDRAM will be put into self-refresh mode (which does
	 * not require a clock from the CPU), and the CPU will be put
	 * into "frozen" mode (HSB bus stopped). The SDRAM controller
	 * will automatically bring the SDRAM into normal mode on the
	 * first access, and the power manager will automatically
	 * start the HSB and CPU clocks upon a wakeup event.
	 *
	 * This code uses the same "skip sleep" technique as above.
	 * It is very important that we jump directly to
	 * cpu_after_sleep after the sleep instruction since that's
	 * where we'll end up if the interrupt handler decides that we
	 * need to skip the sleep instruction.
	 */
	.global	pm_standby
	.type	pm_standby, @function
pm_standby:
	/*
	 * interrupts are already masked at this point, and EVBA
	 * points to pm_exception above.
	 */
	ld.w	r10, r12[SDRAMC_LPR]
	sub	r8, pc, . - 1f		/* return address for irq handler */
	mov	r11, SDRAMC_LPR_LPCB_SELF_RFR
	bfins	r10, r11, 0, 2		/* LPCB <- self Refresh */
	sync	0			/* flush write buffer */
	st.w	r12[SDRAMC_LPR], r11	/* put SDRAM in self-refresh mode */
	ld.w	r11, r12[SDRAMC_LPR]
	unmask_interrupts
	sleep	CPU_SLEEP_FROZEN
1:	mask_interrupts
	retal	r12
	.size	pm_standby, . - pm_standby

	.global	pm_suspend_to_ram
	.type	pm_suspend_to_ram, @function
pm_suspend_to_ram:
	/*
	 * interrupts are already masked at this point, and EVBA
	 * points to pm_exception above.
	 */
	mov	r11, 0
	cache	r11[2], 8		/* clean all dcache lines */
	sync	0			/* flush write buffer */
	ld.w	r10, r12[SDRAMC_LPR]
	sub	r8, pc, . - 1f		/* return address for irq handler */
	mov	r11, SDRAMC_LPR_LPCB_SELF_RFR
	bfins	r10, r11, 0, 2		/* LPCB <- self refresh */
	st.w	r12[SDRAMC_LPR], r10	/* put SDRAM in self-refresh mode */
	ld.w	r11, r12[SDRAMC_LPR]

	unmask_interrupts
	sleep	CPU_SLEEP_STOP
1:	mask_interrupts

	retal	r12
	.size	pm_suspend_to_ram, . - pm_suspend_to_ram

	.global	pm_sram_end
	.type	pm_sram_end, @function
pm_sram_end:
	.size	pm_sram_end, 0

#endif /* CONFIG_PM */
+245 −0
Original line number Diff line number Diff line
/*
 * AVR32 AP Power Management
 *
 * Copyright (C) 2008 Atmel Corporation
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 */
#include <linux/io.h>
#include <linux/suspend.h>
#include <linux/vmalloc.h>

#include <asm/cacheflush.h>
#include <asm/sysreg.h>

#include <asm/arch/pm.h>
#include <asm/arch/sram.h>

/* FIXME: This is only valid for AP7000 */
#define SDRAMC_BASE	0xfff03800

#include "sdramc.h"

#define SRAM_PAGE_FLAGS	(SYSREG_BIT(TLBELO_D) | SYSREG_BF(SZ, 1)	\
				| SYSREG_BF(AP, 3) | SYSREG_BIT(G))


static unsigned long	pm_sram_start;
static size_t		pm_sram_size;
static struct vm_struct	*pm_sram_area;

static void (*avr32_pm_enter_standby)(unsigned long sdramc_base);
static void (*avr32_pm_enter_str)(unsigned long sdramc_base);

/*
 * Must be called with interrupts disabled. Exceptions will be masked
 * on return (i.e. all exceptions will be "unrecoverable".)
 */
static void *avr32_pm_map_sram(void)
{
	unsigned long	vaddr;
	unsigned long	page_addr;
	u32		tlbehi;
	u32		mmucr;

	vaddr = (unsigned long)pm_sram_area->addr;
	page_addr = pm_sram_start & PAGE_MASK;

	/*
	 * Mask exceptions and grab the first TLB entry. We won't be
	 * needing it while sleeping.
	 */
	asm volatile("ssrf	%0" : : "i"(SYSREG_EM_OFFSET) : "memory");

	mmucr = sysreg_read(MMUCR);
	tlbehi = sysreg_read(TLBEHI);
	sysreg_write(MMUCR, SYSREG_BFINS(DRP, 0, mmucr));

	tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi));
	tlbehi |= vaddr & PAGE_MASK;
	tlbehi |= SYSREG_BIT(TLBEHI_V);

	sysreg_write(TLBELO, page_addr | SRAM_PAGE_FLAGS);
	sysreg_write(TLBEHI, tlbehi);
	__builtin_tlbw();

	return (void *)(vaddr + pm_sram_start - page_addr);
}

/*
 * Must be called with interrupts disabled. Exceptions will be
 * unmasked on return.
 */
static void avr32_pm_unmap_sram(void)
{
	u32	mmucr;
	u32	tlbehi;
	u32	tlbarlo;

	/* Going to update TLB entry at index 0 */
	mmucr = sysreg_read(MMUCR);
	tlbehi = sysreg_read(TLBEHI);
	sysreg_write(MMUCR, SYSREG_BFINS(DRP, 0, mmucr));

	/* Clear the "valid" bit */
	tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi));
	sysreg_write(TLBEHI, tlbehi);

	/* Mark it as "not accessed" */
	tlbarlo = sysreg_read(TLBARLO);
	sysreg_write(TLBARLO, tlbarlo | 0x80000000U);

	/* Update the TLB */
	__builtin_tlbw();

	/* Unmask exceptions */
	asm volatile("csrf	%0" : : "i"(SYSREG_EM_OFFSET) : "memory");
}

static int avr32_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 int avr32_pm_enter(suspend_state_t state)
{
	u32		lpr_saved;
	u32		evba_saved;
	void		*sram;

	switch (state) {
	case PM_SUSPEND_STANDBY:
		sram = avr32_pm_map_sram();

		/* Switch to in-sram exception handlers */
		evba_saved = sysreg_read(EVBA);
		sysreg_write(EVBA, (unsigned long)sram);

		/*
		 * Save the LPR register so that we can re-enable
		 * SDRAM Low Power mode on resume.
		 */
		lpr_saved = sdramc_readl(LPR);
		pr_debug("%s: Entering standby...\n", __func__);
		avr32_pm_enter_standby(SDRAMC_BASE);
		sdramc_writel(LPR, lpr_saved);

		/* Switch back to regular exception handlers */
		sysreg_write(EVBA, evba_saved);

		avr32_pm_unmap_sram();
		break;

	case PM_SUSPEND_MEM:
		sram = avr32_pm_map_sram();

		/* Switch to in-sram exception handlers */
		evba_saved = sysreg_read(EVBA);
		sysreg_write(EVBA, (unsigned long)sram);

		/*
		 * Save the LPR register so that we can re-enable
		 * SDRAM Low Power mode on resume.
		 */
		lpr_saved = sdramc_readl(LPR);
		pr_debug("%s: Entering suspend-to-ram...\n", __func__);
		avr32_pm_enter_str(SDRAMC_BASE);
		sdramc_writel(LPR, lpr_saved);

		/* Switch back to regular exception handlers */
		sysreg_write(EVBA, evba_saved);

		avr32_pm_unmap_sram();
		break;

	case PM_SUSPEND_ON:
		pr_debug("%s: Entering idle...\n", __func__);
		cpu_enter_idle();
		break;

	default:
		pr_debug("%s: Invalid suspend state %d\n", __func__, state);
		goto out;
	}

	pr_debug("%s: wakeup\n", __func__);

out:
	return 0;
}

static struct platform_suspend_ops avr32_pm_ops = {
	.valid	= avr32_pm_valid_state,
	.enter	= avr32_pm_enter,
};

static unsigned long avr32_pm_offset(void *symbol)
{
	extern u8 pm_exception[];

	return (unsigned long)symbol - (unsigned long)pm_exception;
}

static int __init avr32_pm_init(void)
{
	extern u8 pm_exception[];
	extern u8 pm_irq0[];
	extern u8 pm_standby[];
	extern u8 pm_suspend_to_ram[];
	extern u8 pm_sram_end[];
	void *dst;

	/*
	 * To keep things simple, we depend on not needing more than a
	 * single page.
	 */
	pm_sram_size = avr32_pm_offset(pm_sram_end);
	if (pm_sram_size > PAGE_SIZE)
		goto err;

	pm_sram_start = sram_alloc(pm_sram_size);
	if (!pm_sram_start)
		goto err_alloc_sram;

	/* Grab a virtual area we can use later on. */
	pm_sram_area = get_vm_area(pm_sram_size, VM_IOREMAP);
	if (!pm_sram_area)
		goto err_vm_area;
	pm_sram_area->phys_addr = pm_sram_start;

	local_irq_disable();
	dst = avr32_pm_map_sram();
	memcpy(dst, pm_exception, pm_sram_size);
	flush_dcache_region(dst, pm_sram_size);
	invalidate_icache_region(dst, pm_sram_size);
	avr32_pm_unmap_sram();
	local_irq_enable();

	avr32_pm_enter_standby = dst + avr32_pm_offset(pm_standby);
	avr32_pm_enter_str = dst + avr32_pm_offset(pm_suspend_to_ram);
	intc_set_suspend_handler(avr32_pm_offset(pm_irq0));

	suspend_set_ops(&avr32_pm_ops);

	printk("AVR32 AP Power Management enabled\n");

	return 0;

err_vm_area:
	sram_free(pm_sram_start, pm_sram_size);
err_alloc_sram:
err:
	pr_err("AVR32 Power Management initialization failed\n");
	return -ENOMEM;
}
arch_initcall(avr32_pm_init);
Loading