| /* |
| * 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 <mach/pm.h> |
| #include <mach/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); |