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 33int 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 49static unsigned long __irq_mask; 50 51/* 52 * Setup interrupt masks, etc to enable wakeup by power switch 53 */ 54static 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 */ 64static 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 */ 72static int __default_power_switch_check(void) 73{ 74 return 1; 75} 76 77void (*__power_switch_wake_setup)(void) = __default_power_switch_setup; 78int (*__power_switch_wake_check)(void) = __default_power_switch_check; 79void (*__power_switch_wake_cleanup)(void) = __default_power_switch_cleanup; 80 81int 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 117unsigned 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 132static 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 */ 153static 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 174static 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 200static 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 213static 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 243static 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 272static 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 285static 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 298static 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 331static 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 */ 344static 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