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