|
@@ -0,0 +1,245 @@
|
|
|
|
+/*
|
|
|
|
+ * 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);
|