forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
7.2 KiB
254 lines
7.2 KiB
/* |
|
* linux/drivers/parisc/power.c |
|
* HP PARISC soft power switch support driver |
|
* |
|
* Copyright (c) 2001-2007 Helge Deller <[email protected]> |
|
* All rights reserved. |
|
* |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions, and the following disclaimer, |
|
* without modification. |
|
* 2. The name of the author may not be used to endorse or promote products |
|
* derived from this software without specific prior written permission. |
|
* |
|
* Alternatively, this software may be distributed under the terms of the |
|
* GNU General Public License ("GPL"). |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR |
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
* |
|
* |
|
* HINT: |
|
* Support of the soft power switch button may be enabled or disabled at |
|
* runtime through the "/proc/sys/kernel/power" procfs entry. |
|
*/ |
|
|
|
#include <linux/module.h> |
|
#include <linux/init.h> |
|
#include <linux/kernel.h> |
|
#include <linux/notifier.h> |
|
#include <linux/reboot.h> |
|
#include <linux/sched/signal.h> |
|
#include <linux/kthread.h> |
|
#include <linux/pm.h> |
|
|
|
#include <asm/pdc.h> |
|
#include <asm/io.h> |
|
#include <asm/led.h> |
|
|
|
#define DRIVER_NAME "powersw" |
|
#define KTHREAD_NAME "kpowerswd" |
|
|
|
/* how often should the power button be polled ? */ |
|
#define POWERSWITCH_POLL_PER_SEC 2 |
|
|
|
/* how long does the power button needs to be down until we react ? */ |
|
#define POWERSWITCH_DOWN_SEC 2 |
|
|
|
/* assembly code to access special registers */ |
|
/* taken from PCXL ERS page 82 */ |
|
#define DIAG_CODE(code) (0x14000000 + ((code)<<5)) |
|
|
|
#define MFCPU_X(rDiagReg, t_ch, t_th, code) \ |
|
(DIAG_CODE(code) + ((rDiagReg)<<21) + ((t_ch)<<16) + ((t_th)<<0) ) |
|
|
|
#define MTCPU(dr, gr) MFCPU_X(dr, gr, 0, 0x12) /* move value of gr to dr[dr] */ |
|
#define MFCPU_C(dr, gr) MFCPU_X(dr, gr, 0, 0x30) /* for dr0 and dr8 only ! */ |
|
#define MFCPU_T(dr, gr) MFCPU_X(dr, 0, gr, 0xa0) /* all dr except dr0 and dr8 */ |
|
|
|
#define __getDIAG(dr) ( { \ |
|
register unsigned long __res asm("r28");\ |
|
__asm__ __volatile__ ( \ |
|
".word %1" : "=&r" (__res) : "i" (MFCPU_T(dr,28) ) \ |
|
); \ |
|
__res; \ |
|
} ) |
|
|
|
/* local shutdown counter */ |
|
static int shutdown_timer __read_mostly; |
|
|
|
/* check, give feedback and start shutdown after one second */ |
|
static void process_shutdown(void) |
|
{ |
|
if (shutdown_timer == 0) |
|
printk(KERN_ALERT KTHREAD_NAME ": Shutdown requested...\n"); |
|
|
|
shutdown_timer++; |
|
|
|
/* wait until the button was pressed for 1 second */ |
|
if (shutdown_timer == (POWERSWITCH_DOWN_SEC*POWERSWITCH_POLL_PER_SEC)) { |
|
static const char msg[] = "Shutting down..."; |
|
printk(KERN_INFO KTHREAD_NAME ": %s\n", msg); |
|
lcd_print(msg); |
|
|
|
/* send kill signal */ |
|
if (kill_cad_pid(SIGINT, 1)) { |
|
/* just in case killing init process failed */ |
|
machine_power_off(); |
|
} |
|
} |
|
} |
|
|
|
|
|
/* main power switch task struct */ |
|
static struct task_struct *power_task; |
|
|
|
/* filename in /proc which can be used to enable/disable the power switch */ |
|
#define SYSCTL_FILENAME "sys/kernel/power" |
|
|
|
/* soft power switch enabled/disabled */ |
|
int pwrsw_enabled __read_mostly = 1; |
|
|
|
/* main kernel thread worker. It polls the button state */ |
|
static int kpowerswd(void *param) |
|
{ |
|
__set_current_state(TASK_RUNNING); |
|
|
|
do { |
|
int button_not_pressed; |
|
unsigned long soft_power_reg = (unsigned long) param; |
|
|
|
schedule_timeout_interruptible(pwrsw_enabled ? HZ : HZ/POWERSWITCH_POLL_PER_SEC); |
|
|
|
if (unlikely(!pwrsw_enabled)) |
|
continue; |
|
|
|
if (soft_power_reg) { |
|
/* |
|
* Non-Gecko-style machines: |
|
* Check the power switch status which is read from the |
|
* real I/O location at soft_power_reg. |
|
* Bit 31 ("the lowest bit) is the status of the power switch. |
|
* This bit is "1" if the button is NOT pressed. |
|
*/ |
|
button_not_pressed = (gsc_readl(soft_power_reg) & 0x1); |
|
} else { |
|
/* |
|
* On gecko style machines (e.g. 712/xx and 715/xx) |
|
* the power switch status is stored in Bit 0 ("the highest bit") |
|
* of CPU diagnose register 25. |
|
* Warning: Some machines never reset the DIAG flag, even if |
|
* the button has been released again. |
|
*/ |
|
button_not_pressed = (__getDIAG(25) & 0x80000000); |
|
} |
|
|
|
if (likely(button_not_pressed)) { |
|
if (unlikely(shutdown_timer && /* avoid writing if not necessary */ |
|
shutdown_timer < (POWERSWITCH_DOWN_SEC*POWERSWITCH_POLL_PER_SEC))) { |
|
shutdown_timer = 0; |
|
printk(KERN_INFO KTHREAD_NAME ": Shutdown request aborted.\n"); |
|
} |
|
} else |
|
process_shutdown(); |
|
|
|
|
|
} while (!kthread_should_stop()); |
|
|
|
return 0; |
|
} |
|
|
|
|
|
/* |
|
* powerfail interruption handler (irq IRQ_FROM_REGION(CPU_IRQ_REGION)+2) |
|
*/ |
|
#if 0 |
|
static void powerfail_interrupt(int code, void *x) |
|
{ |
|
printk(KERN_CRIT "POWERFAIL INTERRUPTION !\n"); |
|
poweroff(); |
|
} |
|
#endif |
|
|
|
|
|
|
|
|
|
/* parisc_panic_event() is called by the panic handler. |
|
* As soon as a panic occurs, our tasklets above will not be |
|
* executed any longer. This function then re-enables the |
|
* soft-power switch and allows the user to switch off the system |
|
*/ |
|
static int parisc_panic_event(struct notifier_block *this, |
|
unsigned long event, void *ptr) |
|
{ |
|
/* re-enable the soft-power switch */ |
|
pdc_soft_power_button(0); |
|
return NOTIFY_DONE; |
|
} |
|
|
|
static struct notifier_block parisc_panic_block = { |
|
.notifier_call = parisc_panic_event, |
|
.priority = INT_MAX, |
|
}; |
|
|
|
|
|
static int __init power_init(void) |
|
{ |
|
unsigned long ret; |
|
unsigned long soft_power_reg; |
|
|
|
#if 0 |
|
request_irq( IRQ_FROM_REGION(CPU_IRQ_REGION)+2, &powerfail_interrupt, |
|
0, "powerfail", NULL); |
|
#endif |
|
|
|
/* enable the soft power switch if possible */ |
|
ret = pdc_soft_power_info(&soft_power_reg); |
|
if (ret == PDC_OK) |
|
ret = pdc_soft_power_button(1); |
|
if (ret != PDC_OK) |
|
soft_power_reg = -1UL; |
|
|
|
switch (soft_power_reg) { |
|
case 0: printk(KERN_INFO DRIVER_NAME ": Gecko-style soft power switch enabled.\n"); |
|
break; |
|
|
|
case -1UL: printk(KERN_INFO DRIVER_NAME ": Soft power switch support not available.\n"); |
|
return -ENODEV; |
|
|
|
default: printk(KERN_INFO DRIVER_NAME ": Soft power switch at 0x%08lx enabled.\n", |
|
soft_power_reg); |
|
} |
|
|
|
power_task = kthread_run(kpowerswd, (void*)soft_power_reg, KTHREAD_NAME); |
|
if (IS_ERR(power_task)) { |
|
printk(KERN_ERR DRIVER_NAME ": thread creation failed. Driver not loaded.\n"); |
|
pdc_soft_power_button(0); |
|
return -EIO; |
|
} |
|
|
|
/* Register a call for panic conditions. */ |
|
atomic_notifier_chain_register(&panic_notifier_list, |
|
&parisc_panic_block); |
|
|
|
return 0; |
|
} |
|
|
|
static void __exit power_exit(void) |
|
{ |
|
kthread_stop(power_task); |
|
|
|
atomic_notifier_chain_unregister(&panic_notifier_list, |
|
&parisc_panic_block); |
|
|
|
pdc_soft_power_button(0); |
|
} |
|
|
|
arch_initcall(power_init); |
|
module_exit(power_exit); |
|
|
|
|
|
MODULE_AUTHOR("Helge Deller <[email protected]>"); |
|
MODULE_DESCRIPTION("Soft power switch driver"); |
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
|