From 3aa770e7972723f479122cf66b529017d2175289 Mon Sep 17 00:00:00 2001 From: Andriy Skulysh Date: Wed, 27 Sep 2006 16:20:22 +0900 Subject: 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 Signed-off-by: Paul Mundt --- arch/sh/Kconfig | 10 + arch/sh/boards/hp6xx/Makefile | 5 +- arch/sh/boards/hp6xx/hp6xx_apm.c | 123 +++++++ arch/sh/boards/hp6xx/pm.c | 88 +++++ arch/sh/boards/hp6xx/pm_wakeup.S | 58 ++++ arch/sh/boards/hp6xx/setup.c | 14 + arch/sh/kernel/Makefile | 2 + arch/sh/kernel/apm.c | 539 +++++++++++++++++++++++++++++ arch/sh/kernel/entry.S | 4 +- arch/sh/kernel/pm.c | 88 +++++ arch/sh/kernel/sh_ksyms.c | 4 + arch/sh/kernel/time.c | 25 ++ arch/sh/kernel/timers/timer-tmu.c | 18 +- drivers/input/touchscreen/hp680_ts_input.c | 14 - include/asm-sh/apm.h | 46 +++ include/asm-sh/cpu-sh3/freq.h | 4 + include/asm-sh/hd64461.h | 7 +- include/asm-sh/hp6xx/hp6xx.h | 53 ++- include/asm-sh/pm.h | 17 + include/asm-sh/system.h | 25 ++ include/asm-sh/timer.h | 2 + 21 files changed, 1124 insertions(+), 22 deletions(-) create mode 100644 arch/sh/boards/hp6xx/hp6xx_apm.c create mode 100644 arch/sh/boards/hp6xx/pm.c create mode 100644 arch/sh/boards/hp6xx/pm_wakeup.S create mode 100644 arch/sh/kernel/apm.c create mode 100644 arch/sh/kernel/pm.c create mode 100644 include/asm-sh/apm.h create mode 100644 include/asm-sh/pm.h diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig index fe982f0eba2306..d1d724447826bd 100644 --- a/arch/sh/Kconfig +++ b/arch/sh/Kconfig @@ -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" diff --git a/arch/sh/boards/hp6xx/Makefile b/arch/sh/boards/hp6xx/Makefile index 927fe0aa5dfa7b..fdf98ffcf43773 100644 --- a/arch/sh/boards/hp6xx/Makefile +++ b/arch/sh/boards/hp6xx/Makefile @@ -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 + diff --git a/arch/sh/boards/hp6xx/hp6xx_apm.c b/arch/sh/boards/hp6xx/hp6xx_apm.c new file mode 100644 index 00000000000000..ad0e712c29f695 --- /dev/null +++ b/arch/sh/boards/hp6xx/hp6xx_apm.c @@ -0,0 +1,123 @@ +/* + * bios-less APM driver for hp680 + * + * Copyright 2005 (c) Andriy Skulysh + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/arch/sh/boards/hp6xx/pm.c b/arch/sh/boards/hp6xx/pm.c new file mode 100644 index 00000000000000..0e501bcbd7a964 --- /dev/null +++ b/arch/sh/boards/hp6xx/pm.c @@ -0,0 +1,88 @@ +/* + * hp6x0 Power Management Routines + * + * Copyright (c) 2006 Andriy Skulysh + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); diff --git a/arch/sh/boards/hp6xx/pm_wakeup.S b/arch/sh/boards/hp6xx/pm_wakeup.S new file mode 100644 index 00000000000000..45e9bf0b91155d --- /dev/null +++ b/arch/sh/boards/hp6xx/pm_wakeup.S @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2006 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. + * + */ + +#include +#include + +#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 diff --git a/arch/sh/boards/hp6xx/setup.c b/arch/sh/boards/hp6xx/setup.c index 71f315663cc964..5fc00f0727c4f6 100644 --- a/arch/sh/boards/hp6xx/setup.c +++ b/arch/sh/boards/hp6xx/setup.c @@ -15,6 +15,9 @@ #include #include +#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; } diff --git a/arch/sh/kernel/Makefile b/arch/sh/kernel/Makefile index 2de82b66af4a24..0e2148f63e7e45 100644 --- a/arch/sh/kernel/Makefile +++ b/arch/sh/kernel/Makefile @@ -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 diff --git a/arch/sh/kernel/apm.c b/arch/sh/kernel/apm.c new file mode 100644 index 00000000000000..871e7d640002fa --- /dev/null +++ b/arch/sh/kernel/apm.c @@ -0,0 +1,539 @@ +/* + * bios-less APM driver for hp680 + * + * Copyright 2005 (c) Andriy Skulysh + * + * based on ARM APM driver by + * Jamey Hicks + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/arch/sh/kernel/entry.S b/arch/sh/kernel/entry.S index 306eb8d83de5e5..b087d34dba35ce 100644 --- a/arch/sh/kernel/entry.S +++ b/arch/sh/kernel/entry.S @@ -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. ! diff --git a/arch/sh/kernel/pm.c b/arch/sh/kernel/pm.c new file mode 100644 index 00000000000000..10ab62c9aedece --- /dev/null +++ b/arch/sh/kernel/pm.c @@ -0,0 +1,88 @@ +/* + * Generic Power Management Routine + * + * Copyright (c) 2006 Andriy Skulysh + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License. + */ +#include +#include +#include +#include +#include +#include +#include + +#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(); +} diff --git a/arch/sh/kernel/sh_ksyms.c b/arch/sh/kernel/sh_ksyms.c index b73feafcd06e67..fd73ab0326e992 100644 --- a/arch/sh/kernel/sh_ksyms.c +++ b/arch/sh/kernel/sh_ksyms.c @@ -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); diff --git a/arch/sh/kernel/time.c b/arch/sh/kernel/time.c index a1589f85499dc5..c8db6ca4f9d146 100644 --- a/arch/sh/kernel/time.c +++ b/arch/sh/kernel/time.c @@ -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) diff --git a/arch/sh/kernel/timers/timer-tmu.c b/arch/sh/kernel/timers/timer-tmu.c index d4212add53b264..ea4bdf8cdf0d6b 100644 --- a/arch/sh/kernel/timers/timer-tmu.c +++ b/arch/sh/kernel/timers/timer-tmu.c @@ -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, }; diff --git a/drivers/input/touchscreen/hp680_ts_input.c b/drivers/input/touchscreen/hp680_ts_input.c index fa97e0f79e7eaa..ee6c2f40cdf6d6 100644 --- a/drivers/input/touchscreen/hp680_ts_input.c +++ b/drivers/input/touchscreen/hp680_ts_input.c @@ -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; diff --git a/include/asm-sh/apm.h b/include/asm-sh/apm.h new file mode 100644 index 00000000000000..8b091e93651fbd --- /dev/null +++ b/include/asm-sh/apm.h @@ -0,0 +1,46 @@ +/* + * Copyright 2006 (c) 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. + * + */ + +#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 diff --git a/include/asm-sh/cpu-sh3/freq.h b/include/asm-sh/cpu-sh3/freq.h index b61b6e331df0ef..273f3229785cfb 100644 --- a/include/asm-sh/cpu-sh3/freq.h +++ b/include/asm-sh/cpu-sh3/freq.h @@ -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 */ diff --git a/include/asm-sh/hd64461.h b/include/asm-sh/hd64461.h index 0f2e2132cc35cf..27e5c34e265991 100644 --- a/include/asm-sh/hd64461.h +++ b/include/asm-sh/hd64461.h @@ -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 diff --git a/include/asm-sh/hp6xx/hp6xx.h b/include/asm-sh/hp6xx/hp6xx.h index a26247fd3d87ca..f35134c159dd10 100644 --- a/include/asm-sh/hp6xx/hp6xx.h +++ b/include/asm-sh/hp6xx/hp6xx.h @@ -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 +#include + +#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 */ diff --git a/include/asm-sh/pm.h b/include/asm-sh/pm.h new file mode 100644 index 00000000000000..56fdbd6b1c94fc --- /dev/null +++ b/include/asm-sh/pm.h @@ -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 + * + */ +#ifndef __ASM_SH_PM_H +#define __ASM_SH_PM_H + +extern u8 wakeup_start; +extern u8 wakeup_end; + +void pm_enter(void); + +#endif diff --git a/include/asm-sh/system.h b/include/asm-sh/system.h index bd7dc0554b11dc..477422afeb0d55 100644 --- a/include/asm-sh/system.h +++ b/include/asm-sh/system.h @@ -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" ) diff --git a/include/asm-sh/timer.h b/include/asm-sh/timer.h index dd6579c0b04cf0..c7ab28095ba0ea 100644 --- a/include/asm-sh/timer.h +++ b/include/asm-sh/timer.h @@ -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); }; -- cgit 1.2.3-korg