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(ctl_table * ctl,int write,void __user * buffer,size_t * lenp,loff_t * fpos)153 static int sysctl_pm_do_suspend(ctl_table *ctl, int write,
154 				void __user *buffer, size_t *lenp, loff_t *fpos)
155 {
156 	int retval, mode;
157 
158 	if (*lenp <= 0)
159 		return -EIO;
160 
161 	mode = user_atoi(buffer, *lenp);
162 	if ((mode != 1) && (mode != 5))
163 		return -EINVAL;
164 
165 	if (retval == 0) {
166 		if (mode == 5)
167 		    retval = pm_do_bus_sleep();
168 		else
169 		    retval = pm_do_suspend();
170 	}
171 
172 	return retval;
173 }
174 
try_set_cmode(int new_cmode)175 static int try_set_cmode(int new_cmode)
176 {
177 	if (new_cmode > 15)
178 		return -EINVAL;
179 	if (!(clock_cmodes_permitted & (1<<new_cmode)))
180 		return -EINVAL;
181 
182 	/* now change cmode */
183 	local_irq_disable();
184 	frv_dma_pause_all();
185 
186 	frv_change_cmode(new_cmode);
187 
188 	determine_clocks(0);
189 	time_divisor_init();
190 
191 #ifdef DEBUG
192 	determine_clocks(1);
193 #endif
194 	frv_dma_resume_all();
195 	local_irq_enable();
196 
197 	return 0;
198 }
199 
200 
cmode_procctl(ctl_table * ctl,int write,void __user * buffer,size_t * lenp,loff_t * fpos)201 static int cmode_procctl(ctl_table *ctl, int write,
202 			 void __user *buffer, size_t *lenp, loff_t *fpos)
203 {
204 	int new_cmode;
205 
206 	if (!write)
207 		return proc_dointvec(ctl, write, buffer, lenp, fpos);
208 
209 	new_cmode = user_atoi(buffer, *lenp);
210 
211 	return try_set_cmode(new_cmode)?:*lenp;
212 }
213 
try_set_p0(int new_p0)214 static int try_set_p0(int new_p0)
215 {
216 	unsigned long flags, clkc;
217 
218 	if (new_p0 < 0 || new_p0 > 1)
219 		return -EINVAL;
220 
221 	local_irq_save(flags);
222 	__set_PSR(flags & ~PSR_ET);
223 
224 	frv_dma_pause_all();
225 
226 	clkc = __get_CLKC();
227 	if (new_p0)
228 		clkc |= CLKC_P0;
229 	else
230 		clkc &= ~CLKC_P0;
231 	__set_CLKC(clkc);
232 
233 	determine_clocks(0);
234 	time_divisor_init();
235 
236 #ifdef DEBUG
237 	determine_clocks(1);
238 #endif
239 	frv_dma_resume_all();
240 	local_irq_restore(flags);
241 	return 0;
242 }
243 
try_set_cm(int new_cm)244 static int try_set_cm(int new_cm)
245 {
246 	unsigned long flags, clkc;
247 
248 	if (new_cm < 0 || new_cm > 1)
249 		return -EINVAL;
250 
251 	local_irq_save(flags);
252 	__set_PSR(flags & ~PSR_ET);
253 
254 	frv_dma_pause_all();
255 
256 	clkc = __get_CLKC();
257 	clkc &= ~CLKC_CM;
258 	clkc |= new_cm;
259 	__set_CLKC(clkc);
260 
261 	determine_clocks(0);
262 	time_divisor_init();
263 
264 #if 1 //def DEBUG
265 	determine_clocks(1);
266 #endif
267 
268 	frv_dma_resume_all();
269 	local_irq_restore(flags);
270 	return 0;
271 }
272 
p0_procctl(ctl_table * ctl,int write,void __user * buffer,size_t * lenp,loff_t * fpos)273 static int p0_procctl(ctl_table *ctl, int write,
274 		      void __user *buffer, size_t *lenp, loff_t *fpos)
275 {
276 	int new_p0;
277 
278 	if (!write)
279 		return proc_dointvec(ctl, write, buffer, lenp, fpos);
280 
281 	new_p0 = user_atoi(buffer, *lenp);
282 
283 	return try_set_p0(new_p0)?:*lenp;
284 }
285 
cm_procctl(ctl_table * ctl,int write,void __user * buffer,size_t * lenp,loff_t * fpos)286 static int cm_procctl(ctl_table *ctl, int write,
287 		      void __user *buffer, size_t *lenp, loff_t *fpos)
288 {
289 	int new_cm;
290 
291 	if (!write)
292 		return proc_dointvec(ctl, write, buffer, lenp, fpos);
293 
294 	new_cm = user_atoi(buffer, *lenp);
295 
296 	return try_set_cm(new_cm)?:*lenp;
297 }
298 
299 static struct ctl_table pm_table[] =
300 {
301 	{
302 		.procname	= "suspend",
303 		.data		= NULL,
304 		.maxlen		= 0,
305 		.mode		= 0200,
306 		.proc_handler	= sysctl_pm_do_suspend,
307 	},
308 	{
309 		.procname	= "cmode",
310 		.data		= &clock_cmode_current,
311 		.maxlen		= sizeof(int),
312 		.mode		= 0644,
313 		.proc_handler	= cmode_procctl,
314 	},
315 	{
316 		.procname	= "p0",
317 		.data		= &clock_p0_current,
318 		.maxlen		= sizeof(int),
319 		.mode		= 0644,
320 		.proc_handler	= p0_procctl,
321 	},
322 	{
323 		.procname	= "cm",
324 		.data		= &clock_cm_current,
325 		.maxlen		= sizeof(int),
326 		.mode		= 0644,
327 		.proc_handler	= cm_procctl,
328 	},
329 	{ }
330 };
331 
332 static struct ctl_table pm_dir_table[] =
333 {
334 	{
335 		.procname	= "pm",
336 		.mode		= 0555,
337 		.child		= pm_table,
338 	},
339 	{ }
340 };
341 
342 /*
343  * Initialize power interface
344  */
pm_init(void)345 static int __init pm_init(void)
346 {
347 	register_sysctl_table(pm_dir_table);
348 	return 0;
349 }
350 
351 __initcall(pm_init);
352 
353 #endif
354