sh: APM/PM support.

This adds some simple PM stubs and the basic APM interfaces,
primarily for use by hp6xx, where the existing userland
expects it.

Signed-off-by: Andriy Skulysh <askulysh@gmail.com>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
This commit is contained in:
Andriy Skulysh 2006-09-27 16:20:22 +09:00 committed by Paul Mundt
parent ef48e8e349
commit 3aa770e797
21 changed files with 1124 additions and 22 deletions

View File

@ -638,6 +638,16 @@ source "fs/Kconfig.binfmt"
endmenu
menu "Power management options (EXPERIMENTAL)"
depends on EXPERIMENTAL
source kernel/power/Kconfig
config APM
bool "Advanced Power Management Emulation"
depends on PM
endmenu
source "net/Kconfig"
source "drivers/Kconfig"

View File

@ -2,5 +2,8 @@
# Makefile for the HP6xx specific parts of the kernel
#
obj-y := mach.o setup.o
obj-y := mach.o setup.o
obj-$(CONFIG_PM) += pm.o pm_wakeup.o
obj-$(CONFIG_APM) += hp6xx_apm.o

View File

@ -0,0 +1,123 @@
/*
* bios-less APM driver for hp680
*
* Copyright 2005 (c) Andriy Skulysh <askulysh@gmail.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/config.h>
#include <linux/module.h>
#include <linux/apm_bios.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <asm/apm.h>
#include <asm/adc.h>
#include <asm/hp6xx/hp6xx.h>
#define SH7709_PGDR 0xa400012c
#define APM_CRITICAL 10
#define APM_LOW 30
#define HP680_BATTERY_MAX 875
#define HP680_BATTERY_MIN 600
#define HP680_BATTERY_AC_ON 900
#define MODNAME "hp6x0_apm"
static int hp6x0_apm_get_info(char *buf, char **start, off_t fpos, int length)
{
u8 pgdr;
char *p;
int battery_status;
int battery_flag;
int ac_line_status;
int time_units = APM_BATTERY_LIFE_UNKNOWN;
int battery = adc_single(ADC_CHANNEL_BATTERY);
int backup = adc_single(ADC_CHANNEL_BACKUP);
int charging = adc_single(ADC_CHANNEL_CHARGE);
int percentage;
percentage = 100 * (battery - HP680_BATTERY_MIN) /
(HP680_BATTERY_MAX - HP680_BATTERY_MIN);
ac_line_status = (battery > HP680_BATTERY_AC_ON) ?
APM_AC_ONLINE : APM_AC_OFFLINE;
p = buf;
pgdr = ctrl_inb(SH7709_PGDR);
if (pgdr & PGDR_MAIN_BATTERY_OUT) {
battery_status = APM_BATTERY_STATUS_NOT_PRESENT;
battery_flag = 0x80;
percentage = -1;
} else if (charging < 8 ) {
battery_status = APM_BATTERY_STATUS_CHARGING;
battery_flag = 0x08;
ac_line_status = 0xff;
} else if (percentage <= APM_CRITICAL) {
battery_status = APM_BATTERY_STATUS_CRITICAL;
battery_flag = 0x04;
} else if (percentage <= APM_LOW) {
battery_status = APM_BATTERY_STATUS_LOW;
battery_flag = 0x02;
} else {
battery_status = APM_BATTERY_STATUS_HIGH;
battery_flag = 0x01;
}
p += sprintf(p, "1.0 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
APM_32_BIT_SUPPORT,
ac_line_status,
battery_status,
battery_flag,
percentage,
time_units,
"min");
p += sprintf(p, "bat=%d backup=%d charge=%d\n",
battery, backup, charging);
return p - buf;
}
static irqreturn_t hp6x0_apm_interrupt(int irq, void *dev, struct pt_regs *regs)
{
if (!apm_suspended)
apm_queue_event(APM_USER_SUSPEND);
return IRQ_HANDLED;
}
static int __init hp6x0_apm_init(void)
{
int ret;
ret = request_irq(HP680_BTN_IRQ, hp6x0_apm_interrupt,
SA_INTERRUPT, MODNAME, 0);
if (unlikely(ret < 0)) {
printk(KERN_ERR MODNAME ": IRQ %d request failed\n",
HP680_BTN_IRQ);
return ret;
}
apm_get_info = hp6x0_apm_get_info;
return ret;
}
static void __exit hp6x0_apm_exit(void)
{
free_irq(HP680_BTN_IRQ, 0);
apm_get_info = 0;
}
module_init(hp6x0_apm_init);
module_exit(hp6x0_apm_exit);
MODULE_AUTHOR("Adriy Skulysh");
MODULE_DESCRIPTION("hp6xx Advanced Power Management");
MODULE_LICENSE("GPL");

88
arch/sh/boards/hp6xx/pm.c Normal file
View File

@ -0,0 +1,88 @@
/*
* hp6x0 Power Management Routines
*
* Copyright (c) 2006 Andriy Skulysh <askulsyh@gmail.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/config.h>
#include <linux/init.h>
#include <linux/suspend.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <asm/io.h>
#include <asm/hd64461.h>
#include <asm/hp6xx/hp6xx.h>
#include <asm/cpu/dac.h>
#include <asm/pm.h>
#define STBCR 0xffffff82
#define STBCR2 0xffffff88
static int hp6x0_pm_enter(suspend_state_t state)
{
u8 stbcr, stbcr2;
#ifdef CONFIG_HD64461_ENABLER
u8 scr;
u16 hd64461_stbcr;
#endif
if (state != PM_SUSPEND_MEM)
return -EINVAL;
#ifdef CONFIG_HD64461_ENABLER
outb(0, HD64461_PCC1CSCIER);
scr = inb(HD64461_PCC1SCR);
scr |= HD64461_PCCSCR_VCC1;
outb(scr, HD64461_PCC1SCR);
hd64461_stbcr = inw(HD64461_STBCR);
hd64461_stbcr |= HD64461_STBCR_SPC1ST;
outw(hd64461_stbcr, HD64461_STBCR);
#endif
ctrl_outb(0x1f, DACR);
stbcr = ctrl_inb(STBCR);
ctrl_outb(0x01, STBCR);
stbcr2 = ctrl_inb(STBCR2);
ctrl_outb(0x7f , STBCR2);
outw(0xf07f, HD64461_SCPUCR);
pm_enter();
outw(0, HD64461_SCPUCR);
ctrl_outb(stbcr, STBCR);
ctrl_outb(stbcr2, STBCR2);
#ifdef CONFIG_HD64461_ENABLER
hd64461_stbcr = inw(HD64461_STBCR);
hd64461_stbcr &= ~HD64461_STBCR_SPC1ST;
outw(hd64461_stbcr, HD64461_STBCR);
outb(0x4c, HD64461_PCC1CSCIER);
outb(0x00, HD64461_PCC1CSCR);
#endif
return 0;
}
/*
* Set to PM_DISK_FIRMWARE so we can quickly veto suspend-to-disk.
*/
static struct pm_ops hp6x0_pm_ops = {
.pm_disk_mode = PM_DISK_FIRMWARE,
.enter = hp6x0_pm_enter,
};
static int __init hp6x0_pm_init(void)
{
pm_set_ops(&hp6x0_pm_ops);
return 0;
}
late_initcall(hp6x0_pm_init);

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2006 Andriy Skulysh <askulsyh@gmail.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
*/
#include <linux/linkage.h>
#include <asm/cpu/mmu_context.h>
#define k0 r0
#define k1 r1
#define k2 r2
#define k3 r3
#define k4 r4
/*
* Kernel mode register usage:
* k0 scratch
* k1 scratch
* k2 scratch (Exception code)
* k3 scratch (Return address)
* k4 scratch
* k5 reserved
* k6 Global Interrupt Mask (0--15 << 4)
* k7 CURRENT_THREAD_INFO (pointer to current thread info)
*/
ENTRY(wakeup_start)
! clear STBY bit
mov #-126, k2
and #127, k0
mov.b k0, @k2
! enable refresh
mov.l 5f, k1
mov.w 6f, k0
mov.w k0, @k1
! jump to handler
mov.l 2f, k2
mov.l 3f, k3
mov.l @k2, k2
mov.l 4f, k1
jmp @k1
nop
.align 2
1: .long EXPEVT
2: .long INTEVT
3: .long ret_from_irq
4: .long handle_exception
5: .long 0xffffff68
6: .word 0x0524
ENTRY(wakeup_end)
nop

View File

@ -15,6 +15,9 @@
#include <asm/hp6xx/hp6xx.h>
#include <asm/cpu/dac.h>
#define SCPCR 0xa4000116
#define SCPDR 0xa4000136
const char *get_system_type(void)
{
return "HP6xx";
@ -24,6 +27,7 @@ int __init platform_setup(void)
{
u8 v8;
u16 v;
v = inw(HD64461_STBCR);
v |= HD64461_STBCR_SURTST | HD64461_STBCR_SIRST |
HD64461_STBCR_STM1ST | HD64461_STBCR_STM0ST |
@ -50,5 +54,15 @@ int __init platform_setup(void)
v8 &= ~DACR_DAE;
ctrl_outb(v8,DACR);
v8 = ctrl_inb(SCPDR);
v8 |= SCPDR_TS_SCAN_X | SCPDR_TS_SCAN_Y;
v8 &= ~SCPDR_TS_SCAN_ENABLE;
ctrl_outb(v8, SCPDR);
v = ctrl_inw(SCPCR);
v &= ~SCPCR_TS_MASK;
v |= SCPCR_TS_ENABLE;
ctrl_outw(v, SCPCR);
return 0;
}

View File

@ -18,3 +18,5 @@ obj-$(CONFIG_SH_CPU_FREQ) += cpufreq.o
obj-$(CONFIG_MODULES) += module.o
obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o
obj-$(CONFIG_APM) += apm.o
obj-$(CONFIG_PM) += pm.o

539
arch/sh/kernel/apm.c Normal file
View File

@ -0,0 +1,539 @@
/*
* bios-less APM driver for hp680
*
* Copyright 2005 (c) Andriy Skulysh <askulysh@gmail.com>
*
* based on ARM APM driver by
* Jamey Hicks <jamey@crl.dec.com>
*
* adapted from the APM BIOS driver for Linux by
* Stephen Rothwell (sfr@linuxcare.com)
*
* APM 1.2 Reference:
* Intel Corporation, Microsoft Corporation. Advanced Power Management
* (APM) BIOS Interface Specification, Revision 1.2, February 1996.
*
* [This document is available from Microsoft at:
* http://www.microsoft.com/hwdev/busbios/amp_12.htm]
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/timer.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/miscdevice.h>
#include <linux/apm_bios.h>
#include <linux/pm.h>
#include <linux/pm_legacy.h>
#include <asm/apm.h>
#define MODNAME "apm"
/*
* The apm_bios device is one of the misc char devices.
* This is its minor number.
*/
#define APM_MINOR_DEV 134
/*
* Maximum number of events stored
*/
#define APM_MAX_EVENTS 16
struct apm_queue {
unsigned int event_head;
unsigned int event_tail;
apm_event_t events[APM_MAX_EVENTS];
};
/*
* The per-file APM data
*/
struct apm_user {
struct list_head list;
unsigned int suser: 1;
unsigned int writer: 1;
unsigned int reader: 1;
int suspend_result;
unsigned int suspend_state;
#define SUSPEND_NONE 0 /* no suspend pending */
#define SUSPEND_PENDING 1 /* suspend pending read */
#define SUSPEND_READ 2 /* suspend read, pending ack */
#define SUSPEND_ACKED 3 /* suspend acked */
#define SUSPEND_DONE 4 /* suspend completed */
struct apm_queue queue;
};
/*
* Local variables
*/
static int suspends_pending;
static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
/*
* This is a list of everyone who has opened /dev/apm_bios
*/
static DECLARE_RWSEM(user_list_lock);
static LIST_HEAD(apm_user_list);
/*
* kapmd info. kapmd provides us a process context to handle
* "APM" events within - specifically necessary if we're going
* to be suspending the system.
*/
static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
static DECLARE_COMPLETION(kapmd_exit);
static DEFINE_SPINLOCK(kapmd_queue_lock);
static struct apm_queue kapmd_queue;
int apm_suspended;
EXPORT_SYMBOL(apm_suspended);
/* Platform-specific apm_read_proc(). */
int (*apm_get_info)(char *buf, char **start, off_t fpos, int length);
EXPORT_SYMBOL(apm_get_info);
/*
* APM event queue management.
*/
static inline int queue_empty(struct apm_queue *q)
{
return q->event_head == q->event_tail;
}
static inline apm_event_t queue_get_event(struct apm_queue *q)
{
q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
return q->events[q->event_tail];
}
static void queue_add_event(struct apm_queue *q, apm_event_t event)
{
q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
if (q->event_head == q->event_tail) {
static int notified;
if (notified++ == 0)
printk(KERN_ERR "apm: an event queue overflowed\n");
q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
}
q->events[q->event_head] = event;
}
static void queue_event_one_user(struct apm_user *as, apm_event_t event)
{
if (as->suser && as->writer) {
switch (event) {
case APM_SYS_SUSPEND:
case APM_USER_SUSPEND:
/*
* If this user already has a suspend pending,
* don't queue another one.
*/
if (as->suspend_state != SUSPEND_NONE)
return;
as->suspend_state = SUSPEND_PENDING;
suspends_pending++;
break;
}
}
queue_add_event(&as->queue, event);
}
static void queue_event(apm_event_t event, struct apm_user *sender)
{
struct apm_user *as;
down_read(&user_list_lock);
list_for_each_entry(as, &apm_user_list, list)
if (as != sender && as->reader)
queue_event_one_user(as, event);
up_read(&user_list_lock);
wake_up_interruptible(&apm_waitqueue);
}
/**
* apm_queue_event - queue an APM event for kapmd
* @event: APM event
*
* Queue an APM event for kapmd to process and ultimately take the
* appropriate action. Only a subset of events are handled:
* %APM_LOW_BATTERY
* %APM_POWER_STATUS_CHANGE
* %APM_USER_SUSPEND
* %APM_SYS_SUSPEND
* %APM_CRITICAL_SUSPEND
*/
void apm_queue_event(apm_event_t event)
{
spin_lock_irq(&kapmd_queue_lock);
queue_add_event(&kapmd_queue, event);
spin_unlock_irq(&kapmd_queue_lock);
wake_up_interruptible(&kapmd_wait);
}
EXPORT_SYMBOL(apm_queue_event);
static void apm_suspend(void)
{
struct apm_user *as;
int err;
apm_suspended = 1;
err = pm_suspend(PM_SUSPEND_MEM);
/*
* Anyone on the APM queues will think we're still suspended.
* Send a message so everyone knows we're now awake again.
*/
queue_event(APM_NORMAL_RESUME, NULL);
/*
* Finally, wake up anyone who is sleeping on the suspend.
*/
down_read(&user_list_lock);
list_for_each_entry(as, &apm_user_list, list) {
as->suspend_result = err;
as->suspend_state = SUSPEND_DONE;
}
up_read(&user_list_lock);
wake_up(&apm_suspend_waitqueue);
apm_suspended = 0;
}
static ssize_t apm_read(struct file *fp, char __user *buf,
size_t count, loff_t *ppos)
{
struct apm_user *as = fp->private_data;
apm_event_t event;
int i = count, ret = 0;
if (count < sizeof(apm_event_t))
return -EINVAL;
if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
return -EAGAIN;
wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
event = queue_get_event(&as->queue);
ret = -EFAULT;
if (copy_to_user(buf, &event, sizeof(event)))
break;
if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)
as->suspend_state = SUSPEND_READ;
buf += sizeof(event);
i -= sizeof(event);
}
if (i < count)
ret = count - i;
return ret;
}
static unsigned int apm_poll(struct file *fp, poll_table * wait)
{
struct apm_user *as = fp->private_data;
poll_wait(fp, &apm_waitqueue, wait);
return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
}
/*
* apm_ioctl - handle APM ioctl
*
* APM_IOC_SUSPEND
* This IOCTL is overloaded, and performs two functions. It is used to:
* - initiate a suspend
* - acknowledge a suspend read from /dev/apm_bios.
* Only when everyone who has opened /dev/apm_bios with write permission
* has acknowledge does the actual suspend happen.
*/
static int
apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
{
struct apm_user *as = filp->private_data;
unsigned long flags;
int err = -EINVAL;
if (!as->suser || !as->writer)
return -EPERM;
switch (cmd) {
case APM_IOC_SUSPEND:
as->suspend_result = -EINTR;
if (as->suspend_state == SUSPEND_READ) {
/*
* If we read a suspend command from /dev/apm_bios,
* then the corresponding APM_IOC_SUSPEND ioctl is
* interpreted as an acknowledge.
*/
as->suspend_state = SUSPEND_ACKED;
suspends_pending--;
} else {
/*
* Otherwise it is a request to suspend the system.
* Queue an event for all readers, and expect an
* acknowledge from all writers who haven't already
* acknowledged.
*/
queue_event(APM_USER_SUSPEND, as);
}
/*
* If there are no further acknowledges required, suspend
* the system.
*/
if (suspends_pending == 0)
apm_suspend();
/*
* Wait for the suspend/resume to complete. If there are
* pending acknowledges, we wait here for them.
*
* Note that we need to ensure that the PM subsystem does
* not kick us out of the wait when it suspends the threads.
*/
flags = current->flags;
current->flags |= PF_NOFREEZE;
/*
* Note: do not allow a thread which is acking the suspend
* to escape until the resume is complete.
*/
if (as->suspend_state == SUSPEND_ACKED)
wait_event(apm_suspend_waitqueue,
as->suspend_state == SUSPEND_DONE);
else
wait_event_interruptible(apm_suspend_waitqueue,
as->suspend_state == SUSPEND_DONE);
current->flags = flags;
err = as->suspend_result;
as->suspend_state = SUSPEND_NONE;
break;
}
return err;
}
static int apm_release(struct inode * inode, struct file * filp)
{
struct apm_user *as = filp->private_data;
filp->private_data = NULL;
down_write(&user_list_lock);
list_del(&as->list);
up_write(&user_list_lock);
/*
* We are now unhooked from the chain. As far as new
* events are concerned, we no longer exist. However, we
* need to balance suspends_pending, which means the
* possibility of sleeping.
*/
if (as->suspend_state != SUSPEND_NONE) {
suspends_pending -= 1;
if (suspends_pending == 0)
apm_suspend();
}
kfree(as);
return 0;
}
static int apm_open(struct inode * inode, struct file * filp)
{
struct apm_user *as;
as = kzalloc(sizeof(*as), GFP_KERNEL);
if (as) {
/*
* XXX - this is a tiny bit broken, when we consider BSD
* process accounting. If the device is opened by root, we
* instantly flag that we used superuser privs. Who knows,
* we might close the device immediately without doing a
* privileged operation -- cevans
*/
as->suser = capable(CAP_SYS_ADMIN);
as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
down_write(&user_list_lock);
list_add(&as->list, &apm_user_list);
up_write(&user_list_lock);
filp->private_data = as;
}
return as ? 0 : -ENOMEM;
}
static struct file_operations apm_bios_fops = {
.owner = THIS_MODULE,
.read = apm_read,
.poll = apm_poll,
.ioctl = apm_ioctl,
.open = apm_open,
.release = apm_release,
};
static struct miscdevice apm_device = {
.minor = APM_MINOR_DEV,
.name = "apm_bios",
.fops = &apm_bios_fops
};
#ifdef CONFIG_PROC_FS
/*
* Arguments, with symbols from linux/apm_bios.h.
*
* 0) Linux driver version (this will change if format changes)
* 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
* 2) APM flags from APM Installation Check (0x00):
* bit 0: APM_16_BIT_SUPPORT
* bit 1: APM_32_BIT_SUPPORT
* bit 2: APM_IDLE_SLOWS_CLOCK
* bit 3: APM_BIOS_DISABLED
* bit 4: APM_BIOS_DISENGAGED
* 3) AC line status
* 0x00: Off-line
* 0x01: On-line
* 0x02: On backup power (BIOS >= 1.1 only)
* 0xff: Unknown
* 4) Battery status
* 0x00: High
* 0x01: Low
* 0x02: Critical
* 0x03: Charging
* 0x04: Selected battery not present (BIOS >= 1.2 only)
* 0xff: Unknown
* 5) Battery flag
* bit 0: High
* bit 1: Low
* bit 2: Critical
* bit 3: Charging
* bit 7: No system battery
* 0xff: Unknown
* 6) Remaining battery life (percentage of charge):
* 0-100: valid
* -1: Unknown
* 7) Remaining battery life (time units):
* Number of remaining minutes or seconds
* -1: Unknown
* 8) min = minutes; sec = seconds
*/
static int apm_read_proc(char *buf, char **start, off_t fpos, int length)
{
if (likely(apm_get_info))
return apm_get_info(buf, start, fpos, length);
return -EINVAL;
}
#endif
static int kapmd(void *arg)
{
daemonize("kapmd");
current->flags |= PF_NOFREEZE;
do {
apm_event_t event;
wait_event_interruptible(kapmd_wait,
!queue_empty(&kapmd_queue) || !pm_active);
if (!pm_active)
break;
spin_lock_irq(&kapmd_queue_lock);
event = 0;
if (!queue_empty(&kapmd_queue))
event = queue_get_event(&kapmd_queue);
spin_unlock_irq(&kapmd_queue_lock);
switch (event) {
case 0:
break;
case APM_LOW_BATTERY:
case APM_POWER_STATUS_CHANGE:
queue_event(event, NULL);
break;
case APM_USER_SUSPEND:
case APM_SYS_SUSPEND:
queue_event(event, NULL);
if (suspends_pending == 0)
apm_suspend();
break;
case APM_CRITICAL_SUSPEND:
apm_suspend();
break;
}
} while (1);
complete_and_exit(&kapmd_exit, 0);
}
static int __init apm_init(void)
{
int ret;
pm_active = 1;
ret = kernel_thread(kapmd, NULL, CLONE_KERNEL);
if (unlikely(ret < 0)) {
pm_active = 0;
return ret;
}
create_proc_info_entry("apm", 0, NULL, apm_read_proc);
ret = misc_register(&apm_device);
if (unlikely(ret != 0)) {
remove_proc_entry("apm", NULL);
pm_active = 0;
wake_up(&kapmd_wait);
wait_for_completion(&kapmd_exit);
}
return ret;
}
static void __exit apm_exit(void)
{
misc_deregister(&apm_device);
remove_proc_entry("apm", NULL);
pm_active = 0;
wake_up(&kapmd_wait);
wait_for_completion(&kapmd_exit);
}
module_init(apm_init);
module_exit(apm_exit);
MODULE_AUTHOR("Stephen Rothwell, Andriy Skulysh");
MODULE_DESCRIPTION("Advanced Power Management");
MODULE_LICENSE("GPL");

View File

@ -308,7 +308,7 @@ ENTRY(exception_error)
.align 2
ret_from_exception:
preempt_stop()
ret_from_irq:
ENTRY(ret_from_irq)
!
mov #OFF_SR, r0
mov.l @(r0,r15), r0 ! get status register
@ -704,7 +704,7 @@ interrupt:
!
!
.align 2
handle_exception:
ENTRY(handle_exception)
! Using k0, k1 for scratch registers (r0_bank1, r1_bank),
! save all registers onto stack.
!

88
arch/sh/kernel/pm.c Normal file
View File

@ -0,0 +1,88 @@
/*
* Generic Power Management Routine
*
* Copyright (c) 2006 Andriy Skulysh <askulsyh@gmail.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/suspend.h>
#include <linux/delay.h>
#include <linux/gfp.h>
#include <asm/freq.h>
#include <asm/io.h>
#include <asm/watchdog.h>
#include <asm/pm.h>
#define INTR_OFFSET 0x600
#define STBCR 0xffffff82
#define STBCR2 0xffffff88
#define STBCR_STBY 0x80
#define STBCR_MSTP2 0x04
#define MCR 0xffffff68
#define RTCNT 0xffffff70
#define MCR_RMODE 2
#define MCR_RFSH 4
void pm_enter(void)
{
u8 stbcr, csr;
u16 frqcr, mcr;
u32 vbr_new, vbr_old;
set_bl_bit();
/* set wdt */
csr = sh_wdt_read_csr();
csr &= ~WTCSR_TME;
csr |= WTCSR_CKS_4096;
sh_wdt_write_csr(csr);
csr = sh_wdt_read_csr();
sh_wdt_write_cnt(0);
/* disable PLL1 */
frqcr = ctrl_inw(FRQCR);
frqcr &= ~(FRQCR_PLLEN | FRQCR_PSTBY);
ctrl_outw(frqcr, FRQCR);
/* enable standby */
stbcr = ctrl_inb(STBCR);
ctrl_outb(stbcr | STBCR_STBY | STBCR_MSTP2, STBCR);
/* set self-refresh */
mcr = ctrl_inw(MCR);
ctrl_outw(mcr & ~MCR_RFSH, MCR);
/* set interrupt handler */
asm volatile("stc vbr, %0" : "=r" (vbr_old));
vbr_new = get_zeroed_page(GFP_ATOMIC);
udelay(50);
memcpy((void*)(vbr_new + INTR_OFFSET),
&wakeup_start, &wakeup_end - &wakeup_start);
asm volatile("ldc %0, vbr" : : "r" (vbr_new));
ctrl_outw(0, RTCNT);
ctrl_outw(mcr | MCR_RFSH | MCR_RMODE, MCR);
cpu_sleep();
asm volatile("ldc %0, vbr" : : "r" (vbr_old));
free_page(vbr_new);
/* enable PLL1 */
frqcr = ctrl_inw(FRQCR);
frqcr |= FRQCR_PSTBY;
ctrl_outw(frqcr, FRQCR);
udelay(50);
frqcr |= FRQCR_PLLEN;
ctrl_outw(frqcr, FRQCR);
ctrl_outb(stbcr, STBCR);
clear_bl_bit();
}

View File

@ -116,6 +116,10 @@ EXPORT_SYMBOL(__down_trylock);
EXPORT_SYMBOL(synchronize_irq);
#endif
#ifdef CONFIG_PM
EXPORT_SYMBOL(pm_suspend);
#endif
EXPORT_SYMBOL(csum_partial);
#ifdef CONFIG_IPV6
EXPORT_SYMBOL(csum_ipv6_magic);

View File

@ -143,8 +143,33 @@ void handle_timer_tick(struct pt_regs *regs)
}
}
#ifdef CONFIG_PM
int timer_suspend(struct sys_device *dev, pm_message_t state)
{
struct sys_timer *sys_timer = container_of(dev, struct sys_timer, dev);
sys_timer->ops->stop();
return 0;
}
int timer_resume(struct sys_device *dev)
{
struct sys_timer *sys_timer = container_of(dev, struct sys_timer, dev);
sys_timer->ops->start();
return 0;
}
#else
#define timer_suspend NULL
#define timer_resume NULL
#endif
static struct sysdev_class timer_sysclass = {
set_kset_name("timer"),
.suspend = timer_suspend,
.resume = timer_resume,
};
static int __init timer_init_sysfs(void)

View File

@ -188,6 +188,18 @@ static struct clk tmu0_clk = {
.ops = &tmu_clk_ops,
};
static int tmu_timer_start(void)
{
ctrl_outb(TMU_TSTR_INIT, TMU_TSTR);
return 0;
}
static int tmu_timer_stop(void)
{
ctrl_outb(0, TMU_TSTR);
return 0;
}
static int tmu_timer_init(void)
{
unsigned long interval;
@ -197,7 +209,7 @@ static int tmu_timer_init(void)
tmu0_clk.parent = clk_get("module_clk");
/* Start TMU0 */
ctrl_outb(0, TMU_TSTR);
tmu_timer_stop();
#if !defined(CONFIG_CPU_SUBTYPE_SH7300) && !defined(CONFIG_CPU_SUBTYPE_SH7760)
ctrl_outb(TMU_TOCR_INIT, TMU_TOCR);
#endif
@ -211,13 +223,15 @@ static int tmu_timer_init(void)
ctrl_outl(interval, TMU0_TCOR);
ctrl_outl(interval, TMU0_TCNT);
ctrl_outb(TMU_TSTR_INIT, TMU_TSTR);
tmu_timer_start();
return 0;
}
struct sys_timer_ops tmu_timer_ops = {
.init = tmu_timer_init,
.start = tmu_timer_start,
.stop = tmu_timer_stop,
.get_frequency = tmu_timer_get_frequency,
.get_offset = tmu_timer_get_offset,
};

View File

@ -15,7 +15,6 @@
#define HP680_TS_ABS_Y_MIN 80
#define HP680_TS_ABS_Y_MAX 910
#define SCPCR 0xa4000116
#define PHDR 0xa400012e
#define SCPDR 0xa4000136
@ -77,19 +76,6 @@ static irqreturn_t hp680_ts_interrupt(int irq, void *dev, struct pt_regs *regs)
static int __init hp680_ts_init(void)
{
u8 scpdr;
u16 scpcr;
scpdr = ctrl_inb(SCPDR);
scpdr |= SCPDR_TS_SCAN_X | SCPDR_TS_SCAN_Y;
scpdr &= ~SCPDR_TS_SCAN_ENABLE;
ctrl_outb(scpdr, SCPDR);
scpcr = ctrl_inw(SCPCR);
scpcr &= ~SCPCR_TS_MASK;
scpcr |= SCPCR_TS_ENABLE;
ctrl_outw(scpcr, SCPCR);
hp680_ts_dev = input_allocate_device();
if (!hp680_ts_dev)
return -ENOMEM;

46
include/asm-sh/apm.h Normal file
View File

@ -0,0 +1,46 @@
/*
* Copyright 2006 (c) Andriy Skulysh <askulysh@gmail.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
*/
#ifndef __ASM_SH_APM_H
#define __ASM_SH_APM_H
#define APM_AC_OFFLINE 0
#define APM_AC_ONLINE 1
#define APM_AC_BACKUP 2
#define APM_AC_UNKNOWN 0xff
#define APM_BATTERY_STATUS_HIGH 0
#define APM_BATTERY_STATUS_LOW 1
#define APM_BATTERY_STATUS_CRITICAL 2
#define APM_BATTERY_STATUS_CHARGING 3
#define APM_BATTERY_STATUS_NOT_PRESENT 4
#define APM_BATTERY_STATUS_UNKNOWN 0xff
#define APM_BATTERY_LIFE_UNKNOWN 0xFFFF
#define APM_BATTERY_LIFE_MINUTES 0x8000
#define APM_BATTERY_LIFE_VALUE_MASK 0x7FFF
#define APM_BATTERY_FLAG_HIGH (1 << 0)
#define APM_BATTERY_FLAG_LOW (1 << 1)
#define APM_BATTERY_FLAG_CRITICAL (1 << 2)
#define APM_BATTERY_FLAG_CHARGING (1 << 3)
#define APM_BATTERY_FLAG_NOT_PRESENT (1 << 7)
#define APM_BATTERY_FLAG_UNKNOWN 0xff
#define APM_UNITS_MINS 0
#define APM_UNITS_SECS 1
#define APM_UNITS_UNKNOWN -1
extern int (*apm_get_info)(char *buf, char **start, off_t fpos, int length);
extern int apm_suspended;
void apm_queue_event(apm_event_t event);
#endif

View File

@ -18,5 +18,9 @@
#define MIN_DIVISOR_NR 0
#define MAX_DIVISOR_NR 4
#define FRQCR_CKOEN 0x0100
#define FRQCR_PLLEN 0x0080
#define FRQCR_PSTBY 0x0040
#endif /* __ASM_CPU_SH3_FREQ_H */

View File

@ -40,7 +40,12 @@
#define HD64461_LCDCBAR 0x11000
#define HD64461_LCDCLOR 0x11002
#define HD64461_LCDCCR 0x11004
#define HD64461_LCDCCR_MOFF 0x80
#define HD64461_LCDCCR_STBACK 0x0400
#define HD64461_LCDCCR_STREQ 0x0100
#define HD64461_LCDCCR_MOFF 0x0080
#define HD64461_LCDCCR_REFSEL 0x0040
#define HD64461_LCDCCR_EPON 0x0020
#define HD64461_LCDCCR_SPON 0x0010
#define HD64461_LDR1 0x11010
#define HD64461_LDR1_DON 0x01

View File

@ -2,16 +2,33 @@
#define __ASM_SH_HP6XX_H
/*
* Copyright (C) 2003 Andriy Skulysh
* Copyright (C) 2003, 2004, 2005 Andriy Skulysh
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
*/
#define HP680_TS_IRQ IRQ3_IRQ
#define HP680_BTN_IRQ IRQ0_IRQ
#define HP680_TS_IRQ IRQ3_IRQ
#define HP680_HD64461_IRQ IRQ4_IRQ
#define DAC_LCD_BRIGHTNESS 0
#define DAC_SPEAKER_VOLUME 1
#define PGDR_OPENED 0x01
#define PGDR_MAIN_BATTERY_OUT 0x04
#define PGDR_PLAY_BUTTON 0x08
#define PGDR_REWIND_BUTTON 0x10
#define PGDR_RECORD_BUTTON 0x20
#define PHDR_TS_PEN_DOWN 0x08
#define PJDR_LED_BLINK 0x02
#define PKDR_LED_GREEN 0x10
#define SCPDR_TS_SCAN_ENABLE 0x20
#define SCPDR_TS_SCAN_Y 0x02
#define SCPDR_TS_SCAN_X 0x01
@ -21,11 +38,43 @@
#define ADC_CHANNEL_TS_Y 1
#define ADC_CHANNEL_TS_X 2
#define ADC_CHANNEL_BATTERY 3
#define ADC_CHANNEL_BACKUP 4
#define ADC_CHANNEL_CHARGE 5
#define HD64461_GPADR_SPEAKER 0x01
#define HD64461_GPADR_PCMCIA0 (0x02|0x08)
#define HD64461_GPBDR_LCDOFF 0x01
#define HD64461_GPBDR_LCD_CONTRAST_MASK 0x78
#define HD64461_GPBDR_LED_RED 0x80
#include <asm/hd64461.h>
#include <asm/io.h>
#define PJDR 0xa4000130
#define PKDR 0xa4000132
static inline void hp6xx_led_red(int on)
{
u16 v16;
v16 = ctrl_inw(CONFIG_HD64461_IOBASE + HD64461_GPBDR - 0x10000);
if (on)
ctrl_outw(v16 & (~HD64461_GPBDR_LED_RED), CONFIG_HD64461_IOBASE + HD64461_GPBDR - 0x10000);
else
ctrl_outw(v16 | HD64461_GPBDR_LED_RED, CONFIG_HD64461_IOBASE + HD64461_GPBDR - 0x10000);
}
static inline void hp6xx_led_green(int on)
{
u8 v8;
v8 = ctrl_inb(PKDR);
if (on)
ctrl_outb(v8 & (~PKDR_LED_GREEN), PKDR);
else
ctrl_outb(v8 | PKDR_LED_GREEN, PKDR);
}
#endif /* __ASM_SH_HP6XX_H */

17
include/asm-sh/pm.h Normal file
View File

@ -0,0 +1,17 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright 2006 (c) Andriy Skulysh <askulysh@gmail.com>
*
*/
#ifndef __ASM_SH_PM_H
#define __ASM_SH_PM_H
extern u8 wakeup_start;
extern u8 wakeup_end;
void pm_enter(void);
#endif

View File

@ -172,6 +172,31 @@ static __inline__ void local_irq_disable(void)
: "memory");
}
static __inline__ void set_bl_bit(void)
{
unsigned long __dummy0, __dummy1;
__asm__ __volatile__ ("stc sr, %0\n\t"
"or %2, %0\n\t"
"and %3, %0\n\t"
"ldc %0, sr"
: "=&r" (__dummy0), "=r" (__dummy1)
: "r" (0x10000000), "r" (0xffffff0f)
: "memory");
}
static __inline__ void clear_bl_bit(void)
{
unsigned long __dummy0, __dummy1;
__asm__ __volatile__ ("stc sr, %0\n\t"
"and %2, %0\n\t"
"ldc %0, sr"
: "=&r" (__dummy0), "=r" (__dummy1)
: "1" (~0x10000000)
: "memory");
}
#define local_save_flags(x) \
__asm__("stc sr, %0; and #0xf0, %0" : "=&z" (x) :/**/: "memory" )

View File

@ -6,6 +6,8 @@
struct sys_timer_ops {
int (*init)(void);
int (*start)(void);
int (*stop)(void);
unsigned long (*get_offset)(void);
unsigned long (*get_frequency)(void);
};