linux/arch/frv/kernel/pm.c

353 lines
6.1 KiB
C
Raw Normal View History

/*
* 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/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_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(struct ctl_table *ctl, int write,
void __user *buffer, size_t *lenp, loff_t *fpos)
{
int mode;
if (*lenp <= 0)
return -EIO;
mode = user_atoi(buffer, *lenp);
switch (mode) {
case 1:
return pm_do_suspend();
case 5:
return pm_do_bus_sleep();
default:
return -EINVAL;
}
}
static int try_set_cmode(int new_cmode)
{
if (new_cmode > 15)
return -EINVAL;
if (!(clock_cmodes_permitted & (1<<new_cmode)))
return -EINVAL;
/* 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();
return 0;
}
static int cmode_procctl(struct ctl_table *ctl, int write,
void __user *buffer, size_t *lenp, loff_t *fpos)
{
int new_cmode;
if (!write)
return proc_dointvec(ctl, write, buffer, lenp, fpos);
new_cmode = user_atoi(buffer, *lenp);
return try_set_cmode(new_cmode)?:*lenp;
}
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(struct ctl_table *ctl, int write,
void __user *buffer, size_t *lenp, loff_t *fpos)
{
int new_p0;
if (!write)
return proc_dointvec(ctl, write, buffer, lenp, fpos);
new_p0 = user_atoi(buffer, *lenp);
return try_set_p0(new_p0)?:*lenp;
}
static int cm_procctl(struct ctl_table *ctl, int write,
void __user *buffer, size_t *lenp, loff_t *fpos)
{
int new_cm;
if (!write)
return proc_dointvec(ctl, write, buffer, lenp, fpos);
new_cm = user_atoi(buffer, *lenp);
return try_set_cm(new_cm)?:*lenp;
}
static struct ctl_table pm_table[] =
{
{
.procname = "suspend",
.data = NULL,
.maxlen = 0,
.mode = 0200,
.proc_handler = sysctl_pm_do_suspend,
},
{
.procname = "cmode",
.data = &clock_cmode_current,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = cmode_procctl,
},
{
.procname = "p0",
.data = &clock_p0_current,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = p0_procctl,
},
{
.procname = "cm",
.data = &clock_cm_current,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = cm_procctl,
},
{ }
};
static struct ctl_table pm_dir_table[] =
{
{
.procname = "pm",
.mode = 0555,
.child = pm_table,
},
{ }
};
/*
* Initialize power interface
*/
static int __init pm_init(void)
{
[PATCH] sysctl: remove insert_at_head from register_sysctl The semantic effect of insert_at_head is that it would allow new registered sysctl entries to override existing sysctl entries of the same name. Which is pain for caching and the proc interface never implemented. I have done an audit and discovered that none of the current users of register_sysctl care as (excpet for directories) they do not register duplicate sysctl entries. So this patch simply removes the support for overriding existing entries in the sys_sysctl interface since no one uses it or cares and it makes future enhancments harder. Signed-off-by: Eric W. Biederman <ebiederm@xmission.com> Acked-by: Ralf Baechle <ralf@linux-mips.org> Acked-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Cc: Russell King <rmk@arm.linux.org.uk> Cc: David Howells <dhowells@redhat.com> Cc: "Luck, Tony" <tony.luck@intel.com> Cc: Ralf Baechle <ralf@linux-mips.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Martin Schwidefsky <schwidefsky@de.ibm.com> Cc: Andi Kleen <ak@muc.de> Cc: Jens Axboe <axboe@kernel.dk> Cc: Corey Minyard <minyard@acm.org> Cc: Neil Brown <neilb@suse.de> Cc: "John W. Linville" <linville@tuxdriver.com> Cc: James Bottomley <James.Bottomley@steeleye.com> Cc: Jan Kara <jack@ucw.cz> Cc: Trond Myklebust <trond.myklebust@fys.uio.no> Cc: Mark Fasheh <mark.fasheh@oracle.com> Cc: David Chinner <dgc@sgi.com> Cc: "David S. Miller" <davem@davemloft.net> Cc: Patrick McHardy <kaber@trash.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-02-14 16:34:09 +08:00
register_sysctl_table(pm_dir_table);
return 0;
}
__initcall(pm_init);
#endif