1/* 2 * AVR32 AP Power Management 3 * 4 * Copyright (C) 2008 Atmel Corporation 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * version 2 as published by the Free Software Foundation. 9 */ 10#include <linux/io.h> 11#include <linux/suspend.h> 12#include <linux/vmalloc.h> 13 14#include <asm/cacheflush.h> 15#include <asm/sysreg.h> 16 17#include <mach/chip.h> 18#include <mach/pm.h> 19#include <mach/sram.h> 20 21#include "sdramc.h" 22 23#define SRAM_PAGE_FLAGS (SYSREG_BIT(TLBELO_D) | SYSREG_BF(SZ, 1) \ 24 | SYSREG_BF(AP, 3) | SYSREG_BIT(G)) 25 26 27static unsigned long pm_sram_start; 28static size_t pm_sram_size; 29static struct vm_struct *pm_sram_area; 30 31static void (*avr32_pm_enter_standby)(unsigned long sdramc_base); 32static void (*avr32_pm_enter_str)(unsigned long sdramc_base); 33 34/* 35 * Must be called with interrupts disabled. Exceptions will be masked 36 * on return (i.e. all exceptions will be "unrecoverable".) 37 */ 38static void *avr32_pm_map_sram(void) 39{ 40 unsigned long vaddr; 41 unsigned long page_addr; 42 u32 tlbehi; 43 u32 mmucr; 44 45 vaddr = (unsigned long)pm_sram_area->addr; 46 page_addr = pm_sram_start & PAGE_MASK; 47 48 /* 49 * Mask exceptions and grab the first TLB entry. We won't be 50 * needing it while sleeping. 51 */ 52 asm volatile("ssrf %0" : : "i"(SYSREG_EM_OFFSET) : "memory"); 53 54 mmucr = sysreg_read(MMUCR); 55 tlbehi = sysreg_read(TLBEHI); 56 sysreg_write(MMUCR, SYSREG_BFINS(DRP, 0, mmucr)); 57 58 tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi)); 59 tlbehi |= vaddr & PAGE_MASK; 60 tlbehi |= SYSREG_BIT(TLBEHI_V); 61 62 sysreg_write(TLBELO, page_addr | SRAM_PAGE_FLAGS); 63 sysreg_write(TLBEHI, tlbehi); 64 __builtin_tlbw(); 65 66 return (void *)(vaddr + pm_sram_start - page_addr); 67} 68 69/* 70 * Must be called with interrupts disabled. Exceptions will be 71 * unmasked on return. 72 */ 73static void avr32_pm_unmap_sram(void) 74{ 75 u32 mmucr; 76 u32 tlbehi; 77 u32 tlbarlo; 78 79 /* Going to update TLB entry at index 0 */ 80 mmucr = sysreg_read(MMUCR); 81 tlbehi = sysreg_read(TLBEHI); 82 sysreg_write(MMUCR, SYSREG_BFINS(DRP, 0, mmucr)); 83 84 /* Clear the "valid" bit */ 85 tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi)); 86 sysreg_write(TLBEHI, tlbehi); 87 88 /* Mark it as "not accessed" */ 89 tlbarlo = sysreg_read(TLBARLO); 90 sysreg_write(TLBARLO, tlbarlo | 0x80000000U); 91 92 /* Update the TLB */ 93 __builtin_tlbw(); 94 95 /* Unmask exceptions */ 96 asm volatile("csrf %0" : : "i"(SYSREG_EM_OFFSET) : "memory"); 97} 98 99static int avr32_pm_valid_state(suspend_state_t state) 100{ 101 switch (state) { 102 case PM_SUSPEND_ON: 103 case PM_SUSPEND_STANDBY: 104 case PM_SUSPEND_MEM: 105 return 1; 106 107 default: 108 return 0; 109 } 110} 111 112static int avr32_pm_enter(suspend_state_t state) 113{ 114 u32 lpr_saved; 115 u32 evba_saved; 116 void *sram; 117 118 switch (state) { 119 case PM_SUSPEND_STANDBY: 120 sram = avr32_pm_map_sram(); 121 122 /* Switch to in-sram exception handlers */ 123 evba_saved = sysreg_read(EVBA); 124 sysreg_write(EVBA, (unsigned long)sram); 125 126 /* 127 * Save the LPR register so that we can re-enable 128 * SDRAM Low Power mode on resume. 129 */ 130 lpr_saved = sdramc_readl(LPR); 131 pr_debug("%s: Entering standby...\n", __func__); 132 avr32_pm_enter_standby(SDRAMC_BASE); 133 sdramc_writel(LPR, lpr_saved); 134 135 /* Switch back to regular exception handlers */ 136 sysreg_write(EVBA, evba_saved); 137 138 avr32_pm_unmap_sram(); 139 break; 140 141 case PM_SUSPEND_MEM: 142 sram = avr32_pm_map_sram(); 143 144 /* Switch to in-sram exception handlers */ 145 evba_saved = sysreg_read(EVBA); 146 sysreg_write(EVBA, (unsigned long)sram); 147 148 /* 149 * Save the LPR register so that we can re-enable 150 * SDRAM Low Power mode on resume. 151 */ 152 lpr_saved = sdramc_readl(LPR); 153 pr_debug("%s: Entering suspend-to-ram...\n", __func__); 154 avr32_pm_enter_str(SDRAMC_BASE); 155 sdramc_writel(LPR, lpr_saved); 156 157 /* Switch back to regular exception handlers */ 158 sysreg_write(EVBA, evba_saved); 159 160 avr32_pm_unmap_sram(); 161 break; 162 163 case PM_SUSPEND_ON: 164 pr_debug("%s: Entering idle...\n", __func__); 165 cpu_enter_idle(); 166 break; 167 168 default: 169 pr_debug("%s: Invalid suspend state %d\n", __func__, state); 170 goto out; 171 } 172 173 pr_debug("%s: wakeup\n", __func__); 174 175out: 176 return 0; 177} 178 179static const struct platform_suspend_ops avr32_pm_ops = { 180 .valid = avr32_pm_valid_state, 181 .enter = avr32_pm_enter, 182}; 183 184static unsigned long __init avr32_pm_offset(void *symbol) 185{ 186 extern u8 pm_exception[]; 187 188 return (unsigned long)symbol - (unsigned long)pm_exception; 189} 190 191static int __init avr32_pm_init(void) 192{ 193 extern u8 pm_exception[]; 194 extern u8 pm_irq0[]; 195 extern u8 pm_standby[]; 196 extern u8 pm_suspend_to_ram[]; 197 extern u8 pm_sram_end[]; 198 void *dst; 199 200 /* 201 * To keep things simple, we depend on not needing more than a 202 * single page. 203 */ 204 pm_sram_size = avr32_pm_offset(pm_sram_end); 205 if (pm_sram_size > PAGE_SIZE) 206 goto err; 207 208 pm_sram_start = sram_alloc(pm_sram_size); 209 if (!pm_sram_start) 210 goto err_alloc_sram; 211 212 /* Grab a virtual area we can use later on. */ 213 pm_sram_area = get_vm_area(pm_sram_size, VM_IOREMAP); 214 if (!pm_sram_area) 215 goto err_vm_area; 216 pm_sram_area->phys_addr = pm_sram_start; 217 218 local_irq_disable(); 219 dst = avr32_pm_map_sram(); 220 memcpy(dst, pm_exception, pm_sram_size); 221 flush_dcache_region(dst, pm_sram_size); 222 invalidate_icache_region(dst, pm_sram_size); 223 avr32_pm_unmap_sram(); 224 local_irq_enable(); 225 226 avr32_pm_enter_standby = dst + avr32_pm_offset(pm_standby); 227 avr32_pm_enter_str = dst + avr32_pm_offset(pm_suspend_to_ram); 228 intc_set_suspend_handler(avr32_pm_offset(pm_irq0)); 229 230 suspend_set_ops(&avr32_pm_ops); 231 232 printk("AVR32 AP Power Management enabled\n"); 233 234 return 0; 235 236err_vm_area: 237 sram_free(pm_sram_start, pm_sram_size); 238err_alloc_sram: 239err: 240 pr_err("AVR32 Power Management initialization failed\n"); 241 return -ENOMEM; 242} 243arch_initcall(avr32_pm_init); 244