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