• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * FR-V Power Management Routines
3  *
4  * Copyright (c) 2004 Red Hat, Inc.
5  *
6  * Based on SA1100 version:
7  * Copyright (c) 2001 Cliff Brake <cbrake@accelent.com>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License.
11  *
12  */
13 
14 #include <linux/init.h>
15 #include <linux/module.h>
16 #include <linux/pm.h>
17 #include <linux/sched.h>
18 #include <linux/interrupt.h>
19 #include <linux/sysctl.h>
20 #include <linux/errno.h>
21 #include <linux/delay.h>
22 #include <asm/uaccess.h>
23 
24 #include <asm/mb86943a.h>
25 
26 #include "local.h"
27 
28 /*
29  * Debug macros
30  */
31 #define DEBUG
32 
pm_do_suspend(void)33 int pm_do_suspend(void)
34 {
35 	local_irq_disable();
36 
37 	__set_LEDS(0xb1);
38 
39 	/* go zzz */
40 	frv_cpu_suspend(pdm_suspend_mode);
41 
42 	__set_LEDS(0xb2);
43 
44 	local_irq_enable();
45 
46 	return 0;
47 }
48 
49 static unsigned long __irq_mask;
50 
51 /*
52  * Setup interrupt masks, etc to enable wakeup by power switch
53  */
__default_power_switch_setup(void)54 static void __default_power_switch_setup(void)
55 {
56 	/* default is to mask all interrupt sources. */
57 	__irq_mask = *(unsigned long *)0xfeff9820;
58 	*(unsigned long *)0xfeff9820 = 0xfffe0000;
59 }
60 
61 /*
62  * Cleanup interrupt masks, etc after wakeup by power switch
63  */
__default_power_switch_cleanup(void)64 static void __default_power_switch_cleanup(void)
65 {
66 	*(unsigned long *)0xfeff9820 = __irq_mask;
67 }
68 
69 /*
70  * Return non-zero if wakeup irq was caused by power switch
71  */
__default_power_switch_check(void)72 static int __default_power_switch_check(void)
73 {
74 	return 1;
75 }
76 
77 void (*__power_switch_wake_setup)(void) = __default_power_switch_setup;
78 int  (*__power_switch_wake_check)(void) = __default_power_switch_check;
79 void (*__power_switch_wake_cleanup)(void) = __default_power_switch_cleanup;
80 
pm_do_bus_sleep(void)81 int pm_do_bus_sleep(void)
82 {
83 	local_irq_disable();
84 
85 	/*
86          * Here is where we need some platform-dependent setup
87 	 * of the interrupt state so that appropriate wakeup
88 	 * sources are allowed and all others are masked.
89 	 */
90 	__power_switch_wake_setup();
91 
92 	__set_LEDS(0xa1);
93 
94 	/* go zzz
95 	 *
96 	 * This is in a loop in case power switch shares an irq with other
97 	 * devices. The wake_check() tells us if we need to finish waking
98 	 * or go back to sleep.
99 	 */
100 	do {
101 		frv_cpu_suspend(HSR0_PDM_BUS_SLEEP);
102 	} while (__power_switch_wake_check && !__power_switch_wake_check());
103 
104 	__set_LEDS(0xa2);
105 
106 	/*
107          * Here is where we need some platform-dependent restore
108 	 * of the interrupt state prior to being called.
109 	 */
110 	__power_switch_wake_cleanup();
111 
112 	local_irq_enable();
113 
114 	return 0;
115 }
116 
sleep_phys_sp(void * sp)117 unsigned long sleep_phys_sp(void *sp)
118 {
119 	return virt_to_phys(sp);
120 }
121 
122 #ifdef CONFIG_SYSCTL
123 /*
124  * Use a temporary sysctl number. Horrid, but will be cleaned up in 2.6
125  * when all the PM interfaces exist nicely.
126  */
127 #define CTL_PM_SUSPEND 1
128 #define CTL_PM_CMODE 2
129 #define CTL_PM_P0 4
130 #define CTL_PM_CM 5
131 
user_atoi(char __user * ubuf,size_t len)132 static int user_atoi(char __user *ubuf, size_t len)
133 {
134 	char buf[16];
135 	unsigned long ret;
136 
137 	if (len > 15)
138 		return -EINVAL;
139 
140 	if (copy_from_user(buf, ubuf, len))
141 		return -EFAULT;
142 
143 	buf[len] = 0;
144 	ret = simple_strtoul(buf, NULL, 0);
145 	if (ret > INT_MAX)
146 		return -ERANGE;
147 	return ret;
148 }
149 
150 /*
151  * Send us to sleep.
152  */
sysctl_pm_do_suspend(struct ctl_table * ctl,int write,void __user * buffer,size_t * lenp,loff_t * fpos)153 static int sysctl_pm_do_suspend(struct ctl_table *ctl, int write,
154 				void __user *buffer, size_t *lenp, loff_t *fpos)
155 {
156 	int mode;
157 
158 	if (*lenp <= 0)
159 		return -EIO;
160 
161 	mode = user_atoi(buffer, *lenp);
162 	switch (mode) {
163 	case 1:
164 	    return pm_do_suspend();
165 
166 	case 5:
167 	    return pm_do_bus_sleep();
168 
169 	default:
170 	    return -EINVAL;
171 	}
172 }
173 
try_set_cmode(int new_cmode)174 static int try_set_cmode(int new_cmode)
175 {
176 	if (new_cmode > 15)
177 		return -EINVAL;
178 	if (!(clock_cmodes_permitted & (1<<new_cmode)))
179 		return -EINVAL;
180 
181 	/* now change cmode */
182 	local_irq_disable();
183 	frv_dma_pause_all();
184 
185 	frv_change_cmode(new_cmode);
186 
187 	determine_clocks(0);
188 	time_divisor_init();
189 
190 #ifdef DEBUG
191 	determine_clocks(1);
192 #endif
193 	frv_dma_resume_all();
194 	local_irq_enable();
195 
196 	return 0;
197 }
198 
199 
cmode_procctl(struct ctl_table * ctl,int write,void __user * buffer,size_t * lenp,loff_t * fpos)200 static int cmode_procctl(struct ctl_table *ctl, int write,
201 			 void __user *buffer, size_t *lenp, loff_t *fpos)
202 {
203 	int new_cmode;
204 
205 	if (!write)
206 		return proc_dointvec(ctl, write, buffer, lenp, fpos);
207 
208 	new_cmode = user_atoi(buffer, *lenp);
209 
210 	return try_set_cmode(new_cmode)?:*lenp;
211 }
212 
try_set_p0(int new_p0)213 static int try_set_p0(int new_p0)
214 {
215 	unsigned long flags, clkc;
216 
217 	if (new_p0 < 0 || new_p0 > 1)
218 		return -EINVAL;
219 
220 	local_irq_save(flags);
221 	__set_PSR(flags & ~PSR_ET);
222 
223 	frv_dma_pause_all();
224 
225 	clkc = __get_CLKC();
226 	if (new_p0)
227 		clkc |= CLKC_P0;
228 	else
229 		clkc &= ~CLKC_P0;
230 	__set_CLKC(clkc);
231 
232 	determine_clocks(0);
233 	time_divisor_init();
234 
235 #ifdef DEBUG
236 	determine_clocks(1);
237 #endif
238 	frv_dma_resume_all();
239 	local_irq_restore(flags);
240 	return 0;
241 }
242 
try_set_cm(int new_cm)243 static int try_set_cm(int new_cm)
244 {
245 	unsigned long flags, clkc;
246 
247 	if (new_cm < 0 || new_cm > 1)
248 		return -EINVAL;
249 
250 	local_irq_save(flags);
251 	__set_PSR(flags & ~PSR_ET);
252 
253 	frv_dma_pause_all();
254 
255 	clkc = __get_CLKC();
256 	clkc &= ~CLKC_CM;
257 	clkc |= new_cm;
258 	__set_CLKC(clkc);
259 
260 	determine_clocks(0);
261 	time_divisor_init();
262 
263 #if 1 //def DEBUG
264 	determine_clocks(1);
265 #endif
266 
267 	frv_dma_resume_all();
268 	local_irq_restore(flags);
269 	return 0;
270 }
271 
p0_procctl(struct ctl_table * ctl,int write,void __user * buffer,size_t * lenp,loff_t * fpos)272 static int p0_procctl(struct ctl_table *ctl, int write,
273 		      void __user *buffer, size_t *lenp, loff_t *fpos)
274 {
275 	int new_p0;
276 
277 	if (!write)
278 		return proc_dointvec(ctl, write, buffer, lenp, fpos);
279 
280 	new_p0 = user_atoi(buffer, *lenp);
281 
282 	return try_set_p0(new_p0)?:*lenp;
283 }
284 
cm_procctl(struct ctl_table * ctl,int write,void __user * buffer,size_t * lenp,loff_t * fpos)285 static int cm_procctl(struct ctl_table *ctl, int write,
286 		      void __user *buffer, size_t *lenp, loff_t *fpos)
287 {
288 	int new_cm;
289 
290 	if (!write)
291 		return proc_dointvec(ctl, write, buffer, lenp, fpos);
292 
293 	new_cm = user_atoi(buffer, *lenp);
294 
295 	return try_set_cm(new_cm)?:*lenp;
296 }
297 
298 static struct ctl_table pm_table[] =
299 {
300 	{
301 		.procname	= "suspend",
302 		.data		= NULL,
303 		.maxlen		= 0,
304 		.mode		= 0200,
305 		.proc_handler	= sysctl_pm_do_suspend,
306 	},
307 	{
308 		.procname	= "cmode",
309 		.data		= &clock_cmode_current,
310 		.maxlen		= sizeof(int),
311 		.mode		= 0644,
312 		.proc_handler	= cmode_procctl,
313 	},
314 	{
315 		.procname	= "p0",
316 		.data		= &clock_p0_current,
317 		.maxlen		= sizeof(int),
318 		.mode		= 0644,
319 		.proc_handler	= p0_procctl,
320 	},
321 	{
322 		.procname	= "cm",
323 		.data		= &clock_cm_current,
324 		.maxlen		= sizeof(int),
325 		.mode		= 0644,
326 		.proc_handler	= cm_procctl,
327 	},
328 	{ }
329 };
330 
331 static struct ctl_table pm_dir_table[] =
332 {
333 	{
334 		.procname	= "pm",
335 		.mode		= 0555,
336 		.child		= pm_table,
337 	},
338 	{ }
339 };
340 
341 /*
342  * Initialize power interface
343  */
pm_init(void)344 static int __init pm_init(void)
345 {
346 	register_sysctl_table(pm_dir_table);
347 	return 0;
348 }
349 
350 __initcall(pm_init);
351 
352 #endif
353