1
linux/arch/frv/kernel/pm.c
David Howells 9dec17eb57 [PATCH] FRV: Fix FRV arch compile errors
Fix some FRV arch compile errors, including:

 (*) Marking nr_kernel_pages as __meminitdata so that references to it end up
     being properly calculated rather than being assumed to be in the small
     data section (and thus calculated wrt the GP register).  Not doing this
     causes the linker to emit errors as the offset is too big to fit into the
     load instruction.

 (*) Move pm_power_off into an unconditionally compiled .c file as it's now
     unconditionally accessed.

 (*) Declare frv_change_cmode() in a header file rather than in a .c file, and
     declare it asmlinkage.

Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-07-10 13:24:21 -07:00

430 lines
8.3 KiB
C

/*
* FR-V Power Management Routines
*
* Copyright (c) 2004 Red Hat, Inc.
*
* Based on SA1100 version:
* Copyright (c) 2001 Cliff Brake <cbrake@accelent.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/pm_legacy.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/sysctl.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/mb86943a.h>
#include "local.h"
/*
* Debug macros
*/
#define DEBUG
int pm_do_suspend(void)
{
local_irq_disable();
__set_LEDS(0xb1);
/* go zzz */
frv_cpu_suspend(pdm_suspend_mode);
__set_LEDS(0xb2);
local_irq_enable();
return 0;
}
static unsigned long __irq_mask;
/*
* Setup interrupt masks, etc to enable wakeup by power switch
*/
static void __default_power_switch_setup(void)
{
/* default is to mask all interrupt sources. */
__irq_mask = *(unsigned long *)0xfeff9820;
*(unsigned long *)0xfeff9820 = 0xfffe0000;
}
/*
* Cleanup interrupt masks, etc after wakeup by power switch
*/
static void __default_power_switch_cleanup(void)
{
*(unsigned long *)0xfeff9820 = __irq_mask;
}
/*
* Return non-zero if wakeup irq was caused by power switch
*/
static int __default_power_switch_check(void)
{
return 1;
}
void (*__power_switch_wake_setup)(void) = __default_power_switch_setup;
int (*__power_switch_wake_check)(void) = __default_power_switch_check;
void (*__power_switch_wake_cleanup)(void) = __default_power_switch_cleanup;
int pm_do_bus_sleep(void)
{
local_irq_disable();
/*
* Here is where we need some platform-dependent setup
* of the interrupt state so that appropriate wakeup
* sources are allowed and all others are masked.
*/
__power_switch_wake_setup();
__set_LEDS(0xa1);
/* go zzz
*
* This is in a loop in case power switch shares an irq with other
* devices. The wake_check() tells us if we need to finish waking
* or go back to sleep.
*/
do {
frv_cpu_suspend(HSR0_PDM_BUS_SLEEP);
} while (__power_switch_wake_check && !__power_switch_wake_check());
__set_LEDS(0xa2);
/*
* Here is where we need some platform-dependent restore
* of the interrupt state prior to being called.
*/
__power_switch_wake_cleanup();
local_irq_enable();
return 0;
}
unsigned long sleep_phys_sp(void *sp)
{
return virt_to_phys(sp);
}
#ifdef CONFIG_SYSCTL
/*
* Use a temporary sysctl number. Horrid, but will be cleaned up in 2.6
* when all the PM interfaces exist nicely.
*/
#define CTL_PM 9899
#define CTL_PM_SUSPEND 1
#define CTL_PM_CMODE 2
#define CTL_PM_P0 4
#define CTL_PM_CM 5
static int user_atoi(char __user *ubuf, size_t len)
{
char buf[16];
unsigned long ret;
if (len > 15)
return -EINVAL;
if (copy_from_user(buf, ubuf, len))
return -EFAULT;
buf[len] = 0;
ret = simple_strtoul(buf, NULL, 0);
if (ret > INT_MAX)
return -ERANGE;
return ret;
}
/*
* Send us to sleep.
*/
static int sysctl_pm_do_suspend(ctl_table *ctl, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *fpos)
{
int retval, mode;
if (*lenp <= 0)
return -EIO;
mode = user_atoi(buffer, *lenp);
if ((mode != 1) && (mode != 5))
return -EINVAL;
retval = pm_send_all(PM_SUSPEND, (void *)3);
if (retval == 0) {
if (mode == 5)
retval = pm_do_bus_sleep();
else
retval = pm_do_suspend();
pm_send_all(PM_RESUME, (void *)0);
}
return retval;
}
static int try_set_cmode(int new_cmode)
{
if (new_cmode > 15)
return -EINVAL;
if (!(clock_cmodes_permitted & (1<<new_cmode)))
return -EINVAL;
/* tell all the drivers we're suspending */
pm_send_all(PM_SUSPEND, (void *)3);
/* now change cmode */
local_irq_disable();
frv_dma_pause_all();
frv_change_cmode(new_cmode);
determine_clocks(0);
time_divisor_init();
#ifdef DEBUG
determine_clocks(1);
#endif
frv_dma_resume_all();
local_irq_enable();
/* tell all the drivers we're resuming */
pm_send_all(PM_RESUME, (void *)0);
return 0;
}
static int cmode_procctl(ctl_table *ctl, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *fpos)
{
int new_cmode;
if (!write)
return proc_dointvec(ctl, write, filp, buffer, lenp, fpos);
new_cmode = user_atoi(buffer, *lenp);
return try_set_cmode(new_cmode)?:*lenp;
}
static int cmode_sysctl(ctl_table *table, int __user *name, int nlen,
void __user *oldval, size_t __user *oldlenp,
void __user *newval, size_t newlen, void **context)
{
if (oldval && oldlenp) {
size_t oldlen;
if (get_user(oldlen, oldlenp))
return -EFAULT;
if (oldlen != sizeof(int))
return -EINVAL;
if (put_user(clock_cmode_current, (unsigned __user *)oldval) ||
put_user(sizeof(int), oldlenp))
return -EFAULT;
}
if (newval && newlen) {
int new_cmode;
if (newlen != sizeof(int))
return -EINVAL;
if (get_user(new_cmode, (int __user *)newval))
return -EFAULT;
return try_set_cmode(new_cmode)?:1;
}
return 1;
}
static int try_set_p0(int new_p0)
{
unsigned long flags, clkc;
if (new_p0 < 0 || new_p0 > 1)
return -EINVAL;
local_irq_save(flags);
__set_PSR(flags & ~PSR_ET);
frv_dma_pause_all();
clkc = __get_CLKC();
if (new_p0)
clkc |= CLKC_P0;
else
clkc &= ~CLKC_P0;
__set_CLKC(clkc);
determine_clocks(0);
time_divisor_init();
#ifdef DEBUG
determine_clocks(1);
#endif
frv_dma_resume_all();
local_irq_restore(flags);
return 0;
}
static int try_set_cm(int new_cm)
{
unsigned long flags, clkc;
if (new_cm < 0 || new_cm > 1)
return -EINVAL;
local_irq_save(flags);
__set_PSR(flags & ~PSR_ET);
frv_dma_pause_all();
clkc = __get_CLKC();
clkc &= ~CLKC_CM;
clkc |= new_cm;
__set_CLKC(clkc);
determine_clocks(0);
time_divisor_init();
#if 1 //def DEBUG
determine_clocks(1);
#endif
frv_dma_resume_all();
local_irq_restore(flags);
return 0;
}
static int p0_procctl(ctl_table *ctl, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *fpos)
{
int new_p0;
if (!write)
return proc_dointvec(ctl, write, filp, buffer, lenp, fpos);
new_p0 = user_atoi(buffer, *lenp);
return try_set_p0(new_p0)?:*lenp;
}
static int p0_sysctl(ctl_table *table, int __user *name, int nlen,
void __user *oldval, size_t __user *oldlenp,
void __user *newval, size_t newlen, void **context)
{
if (oldval && oldlenp) {
size_t oldlen;
if (get_user(oldlen, oldlenp))
return -EFAULT;
if (oldlen != sizeof(int))
return -EINVAL;
if (put_user(clock_p0_current, (unsigned __user *)oldval) ||
put_user(sizeof(int), oldlenp))
return -EFAULT;
}
if (newval && newlen) {
int new_p0;
if (newlen != sizeof(int))
return -EINVAL;
if (get_user(new_p0, (int __user *)newval))
return -EFAULT;
return try_set_p0(new_p0)?:1;
}
return 1;
}
static int cm_procctl(ctl_table *ctl, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *fpos)
{
int new_cm;
if (!write)
return proc_dointvec(ctl, write, filp, buffer, lenp, fpos);
new_cm = user_atoi(buffer, *lenp);
return try_set_cm(new_cm)?:*lenp;
}
static int cm_sysctl(ctl_table *table, int __user *name, int nlen,
void __user *oldval, size_t __user *oldlenp,
void __user *newval, size_t newlen, void **context)
{
if (oldval && oldlenp) {
size_t oldlen;
if (get_user(oldlen, oldlenp))
return -EFAULT;
if (oldlen != sizeof(int))
return -EINVAL;
if (put_user(clock_cm_current, (unsigned __user *)oldval) ||
put_user(sizeof(int), oldlenp))
return -EFAULT;
}
if (newval && newlen) {
int new_cm;
if (newlen != sizeof(int))
return -EINVAL;
if (get_user(new_cm, (int __user *)newval))
return -EFAULT;
return try_set_cm(new_cm)?:1;
}
return 1;
}
static struct ctl_table pm_table[] =
{
{CTL_PM_SUSPEND, "suspend", NULL, 0, 0200, NULL, &sysctl_pm_do_suspend},
{CTL_PM_CMODE, "cmode", &clock_cmode_current, sizeof(int), 0644, NULL, &cmode_procctl, &cmode_sysctl, NULL},
{CTL_PM_P0, "p0", &clock_p0_current, sizeof(int), 0644, NULL, &p0_procctl, &p0_sysctl, NULL},
{CTL_PM_CM, "cm", &clock_cm_current, sizeof(int), 0644, NULL, &cm_procctl, &cm_sysctl, NULL},
{0}
};
static struct ctl_table pm_dir_table[] =
{
{CTL_PM, "pm", NULL, 0, 0555, pm_table},
{0}
};
/*
* Initialize power interface
*/
static int __init pm_init(void)
{
register_sysctl_table(pm_dir_table, 1);
return 0;
}
__initcall(pm_init);
#endif