2006-08-24 19:11:50 +04:00
/*
2006-09-07 13:57:00 +04:00
* w83697hf / hg WDT driver
2006-08-24 19:11:50 +04:00
*
2006-09-07 13:57:00 +04:00
* ( c ) Copyright 2006 Samuel Tardieu < sam @ rfc1149 . net >
2006-08-24 19:11:50 +04:00
* ( c ) Copyright 2006 Marcus Junker < junker @ anduras . de >
*
2006-09-07 13:57:00 +04:00
* Based on w83627hf_wdt . c which is based on advantechwdt . c
* which is based on wdt . c .
2006-08-24 19:11:50 +04:00
* Original copyright messages :
*
2007-10-20 01:21:04 +04:00
* ( c ) Copyright 2003 Pádraig Brady < P @ draigBrady . com >
2006-08-24 19:11:50 +04:00
*
* ( c ) Copyright 2000 - 2001 Marek Michalkiewicz < marekm @ linux . org . pl >
*
2008-10-27 18:17:56 +03:00
* ( c ) Copyright 1996 Alan Cox < alan @ lxorguk . ukuu . org . uk > ,
* All Rights Reserved .
2006-08-24 19:11:50 +04:00
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*
* Neither Marcus Junker nor ANDURAS AG admit liability nor provide
* warranty for any of this software . This material is provided
* " AS-IS " and at no charge .
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/fs.h>
# include <linux/ioport.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/init.h>
2006-09-02 20:50:20 +04:00
# include <linux/spinlock.h>
2008-05-19 17:09:23 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2006-08-24 19:11:50 +04:00
# include <asm/system.h>
2006-09-07 13:57:00 +04:00
# define WATCHDOG_NAME "w83697hf / hg WDT"
2006-08-24 19:11:50 +04:00
# define PFX WATCHDOG_NAME ": "
# define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
2008-03-12 16:28:03 +03:00
# define WATCHDOG_EARLY_DISABLE 1 /* Disable until userland kicks in */
2006-08-24 19:11:50 +04:00
static unsigned long wdt_is_open ;
static char expect_close ;
2007-11-02 02:27:08 +03:00
static DEFINE_SPINLOCK ( io_lock ) ;
2006-08-24 19:11:50 +04:00
/* You must set this - there is no sane way to probe for this board. */
2006-09-07 13:57:00 +04:00
static int wdt_io = 0x2e ;
2006-08-24 19:11:50 +04:00
module_param ( wdt_io , int , 0 ) ;
2008-05-19 17:09:23 +04:00
MODULE_PARM_DESC ( wdt_io ,
" w83697hf/hg WDT io port (default 0x2e, 0 = autodetect) " ) ;
2006-08-24 19:11:50 +04:00
static int timeout = WATCHDOG_TIMEOUT ; /* in seconds */
module_param ( timeout , int , 0 ) ;
2008-05-19 17:09:23 +04:00
MODULE_PARM_DESC ( timeout ,
" Watchdog timeout in seconds. 1<= timeout <=255 (default= "
__MODULE_STRING ( WATCHDOG_TIMEOUT ) " ) " ) ;
2006-08-24 19:11:50 +04:00
static int nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , int , 0 ) ;
2008-05-19 17:09:23 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2006-08-24 19:11:50 +04:00
2008-03-12 16:28:03 +03:00
static int early_disable = WATCHDOG_EARLY_DISABLE ;
module_param ( early_disable , int , 0 ) ;
2008-05-19 17:09:23 +04:00
MODULE_PARM_DESC ( early_disable ,
" Watchdog gets disabled at boot time (default= "
__MODULE_STRING ( WATCHDOG_EARLY_DISABLE ) " ) " ) ;
2008-03-12 16:28:03 +03:00
2006-08-24 19:11:50 +04:00
/*
* Kernel methods .
*/
2008-05-19 17:09:23 +04:00
# define W83697HF_EFER (wdt_io + 0) /* Extended Function Enable Register */
# define W83697HF_EFIR (wdt_io + 0) / * Extended Function Index Register
( same as EFER ) */
# define W83697HF_EFDR (wdt_io + 1) /* Extended Function Data Register */
2006-08-24 19:11:50 +04:00
2008-05-19 17:09:23 +04:00
static inline void w83697hf_unlock ( void )
2006-08-24 19:11:50 +04:00
{
2006-09-07 13:57:00 +04:00
outb_p ( 0x87 , W83697HF_EFER ) ; /* Enter extended function mode */
outb_p ( 0x87 , W83697HF_EFER ) ; /* Again according to manual */
2006-09-07 13:57:00 +04:00
}
2008-05-19 17:09:23 +04:00
static inline void w83697hf_lock ( void )
2006-09-07 13:57:00 +04:00
{
outb_p ( 0xAA , W83697HF_EFER ) ; /* Leave extended function mode */
}
2006-09-07 13:57:00 +04:00
/*
2006-09-07 13:57:00 +04:00
* The three functions w83697hf_get_reg ( ) , w83697hf_set_reg ( ) and
* w83697hf_write_timeout ( ) must be called with the device unlocked .
2006-09-07 13:57:00 +04:00
*/
2008-05-19 17:09:23 +04:00
static unsigned char w83697hf_get_reg ( unsigned char reg )
2006-09-07 13:57:00 +04:00
{
outb_p ( reg , W83697HF_EFIR ) ;
return inb_p ( W83697HF_EFDR ) ;
}
2008-05-19 17:09:23 +04:00
static void w83697hf_set_reg ( unsigned char reg , unsigned char data )
2006-09-07 13:57:00 +04:00
{
outb_p ( reg , W83697HF_EFIR ) ;
outb_p ( data , W83697HF_EFDR ) ;
}
2008-05-19 17:09:23 +04:00
static void w83697hf_write_timeout ( int timeout )
2006-09-07 13:57:00 +04:00
{
2008-05-19 17:09:23 +04:00
/* Write Timeout counter to CRF4 */
w83697hf_set_reg ( 0xF4 , timeout ) ;
2006-09-07 13:57:00 +04:00
}
2008-05-19 17:09:23 +04:00
static void w83697hf_select_wdt ( void )
2006-09-07 13:57:00 +04:00
{
w83697hf_unlock ( ) ;
w83697hf_set_reg ( 0x07 , 0x08 ) ; /* Switch to logic device 8 (GPIO2) */
}
2008-05-19 17:09:23 +04:00
static inline void w83697hf_deselect_wdt ( void )
2006-09-07 13:57:00 +04:00
{
w83697hf_lock ( ) ;
}
2008-05-19 17:09:23 +04:00
static void w83697hf_init ( void )
2006-08-24 19:11:50 +04:00
{
2006-09-07 13:57:00 +04:00
unsigned char bbuf ;
2006-08-24 19:11:50 +04:00
2006-09-07 13:57:00 +04:00
w83697hf_select_wdt ( ) ;
2006-09-07 13:57:00 +04:00
bbuf = w83697hf_get_reg ( 0x29 ) ;
bbuf & = ~ 0x60 ;
bbuf | = 0x20 ;
2008-05-19 17:09:23 +04:00
/* Set pin 119 to WDTO# mode (= CR29, WDT0) */
w83697hf_set_reg ( 0x29 , bbuf ) ;
2006-08-24 19:11:50 +04:00
2006-09-07 13:57:00 +04:00
bbuf = w83697hf_get_reg ( 0xF3 ) ;
bbuf & = ~ 0x04 ;
w83697hf_set_reg ( 0xF3 , bbuf ) ; /* Count mode is seconds */
2006-08-24 19:11:50 +04:00
2006-09-07 13:57:00 +04:00
w83697hf_deselect_wdt ( ) ;
2006-08-24 19:11:50 +04:00
}
2008-05-19 17:09:23 +04:00
static void wdt_ping ( void )
2006-08-24 19:11:50 +04:00
{
2006-09-02 20:50:20 +04:00
spin_lock ( & io_lock ) ;
2006-09-07 13:57:00 +04:00
w83697hf_select_wdt ( ) ;
2006-08-24 19:11:50 +04:00
2006-09-07 13:57:00 +04:00
w83697hf_write_timeout ( timeout ) ;
2006-08-24 19:11:50 +04:00
2006-09-07 13:57:00 +04:00
w83697hf_deselect_wdt ( ) ;
2006-09-02 20:50:20 +04:00
spin_unlock ( & io_lock ) ;
2006-08-24 19:11:50 +04:00
}
2008-05-19 17:09:23 +04:00
static void wdt_enable ( void )
2006-08-24 19:11:50 +04:00
{
2006-09-07 13:57:00 +04:00
spin_lock ( & io_lock ) ;
w83697hf_select_wdt ( ) ;
w83697hf_write_timeout ( timeout ) ;
w83697hf_set_reg ( 0x30 , 1 ) ; /* Enable timer */
w83697hf_deselect_wdt ( ) ;
spin_unlock ( & io_lock ) ;
2006-08-24 19:11:50 +04:00
}
2008-05-19 17:09:23 +04:00
static void wdt_disable ( void )
2006-08-24 19:11:50 +04:00
{
2006-09-07 13:57:00 +04:00
spin_lock ( & io_lock ) ;
w83697hf_select_wdt ( ) ;
w83697hf_set_reg ( 0x30 , 0 ) ; /* Disable timer */
w83697hf_write_timeout ( 0 ) ;
w83697hf_deselect_wdt ( ) ;
spin_unlock ( & io_lock ) ;
2006-08-24 19:11:50 +04:00
}
2008-05-19 17:09:23 +04:00
static unsigned char wdt_running ( void )
2008-03-12 16:28:03 +03:00
{
unsigned char t ;
spin_lock ( & io_lock ) ;
w83697hf_select_wdt ( ) ;
t = w83697hf_get_reg ( 0xF4 ) ; /* Read timer */
w83697hf_deselect_wdt ( ) ;
spin_unlock ( & io_lock ) ;
return t ;
}
2008-05-19 17:09:23 +04:00
static int wdt_set_heartbeat ( int t )
2006-08-24 19:11:50 +04:00
{
2008-05-19 17:09:23 +04:00
if ( t < 1 | | t > 255 )
2006-08-24 19:11:50 +04:00
return - EINVAL ;
timeout = t ;
return 0 ;
}
2008-05-19 17:09:23 +04:00
static ssize_t wdt_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
2006-08-24 19:11:50 +04:00
{
if ( count ) {
if ( ! nowayout ) {
size_t i ;
expect_close = 0 ;
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
2008-08-07 00:19:41 +04:00
if ( get_user ( c , buf + i ) )
2006-08-24 19:11:50 +04:00
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 42 ;
}
}
wdt_ping ( ) ;
}
return count ;
}
2008-05-19 17:09:23 +04:00
static long wdt_ioctl ( struct file * file , unsigned int cmd , unsigned long arg )
2006-08-24 19:11:50 +04:00
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int new_timeout ;
2008-05-19 17:09:23 +04:00
static const struct watchdog_info ident = {
. options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
| WDIOF_MAGICCLOSE ,
2006-08-24 19:11:50 +04:00
. firmware_version = 1 ,
. identity = " W83697HF WDT " ,
} ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
2006-09-07 13:57:00 +04:00
if ( copy_to_user ( argp , & ident , sizeof ( ident ) ) )
return - EFAULT ;
break ;
2006-08-24 19:11:50 +04:00
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
2006-09-07 13:57:00 +04:00
return put_user ( 0 , p ) ;
2006-08-24 19:11:50 +04:00
case WDIOC_SETOPTIONS :
{
2006-09-07 13:57:00 +04:00
int options , retval = - EINVAL ;
2006-08-24 19:11:50 +04:00
2006-09-07 13:57:00 +04:00
if ( get_user ( options , p ) )
return - EFAULT ;
2006-08-24 19:11:50 +04:00
2006-09-07 13:57:00 +04:00
if ( options & WDIOS_DISABLECARD ) {
wdt_disable ( ) ;
retval = 0 ;
}
2006-08-24 19:11:50 +04:00
2006-09-07 13:57:00 +04:00
if ( options & WDIOS_ENABLECARD ) {
2006-09-07 13:57:00 +04:00
wdt_enable ( ) ;
2006-09-07 13:57:00 +04:00
retval = 0 ;
}
2006-08-24 19:11:50 +04:00
2006-09-07 13:57:00 +04:00
return retval ;
2006-08-24 19:11:50 +04:00
}
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
wdt_ping ( ) ;
break ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_timeout , p ) )
return - EFAULT ;
if ( wdt_set_heartbeat ( new_timeout ) )
return - EINVAL ;
wdt_ping ( ) ;
/* Fall */
case WDIOC_GETTIMEOUT :
return put_user ( timeout , p ) ;
2006-08-24 19:11:50 +04:00
default :
2006-09-07 13:57:00 +04:00
return - ENOTTY ;
2006-08-24 19:11:50 +04:00
}
return 0 ;
}
2008-05-19 17:09:23 +04:00
static int wdt_open ( struct inode * inode , struct file * file )
2006-08-24 19:11:50 +04:00
{
if ( test_and_set_bit ( 0 , & wdt_is_open ) )
return - EBUSY ;
/*
* Activate
*/
2006-09-07 13:57:00 +04:00
wdt_enable ( ) ;
2006-08-24 19:11:50 +04:00
return nonseekable_open ( inode , file ) ;
}
2008-05-19 17:09:23 +04:00
static int wdt_close ( struct inode * inode , struct file * file )
2006-08-24 19:11:50 +04:00
{
2008-05-19 17:09:23 +04:00
if ( expect_close = = 42 )
2006-08-24 19:11:50 +04:00
wdt_disable ( ) ;
2008-05-19 17:09:23 +04:00
else {
printk ( KERN_CRIT PFX
" Unexpected close, not stopping watchdog! \n " ) ;
2006-08-24 19:11:50 +04:00
wdt_ping ( ) ;
}
expect_close = 0 ;
clear_bit ( 0 , & wdt_is_open ) ;
return 0 ;
}
/*
* Notifier for system down
*/
2008-05-19 17:09:23 +04:00
static int wdt_notify_sys ( struct notifier_block * this , unsigned long code ,
2006-08-24 19:11:50 +04:00
void * unused )
{
2008-08-07 00:19:41 +04:00
if ( code = = SYS_DOWN | | code = = SYS_HALT )
wdt_disable ( ) ; /* Turn the WDT off */
2006-08-24 19:11:50 +04:00
return NOTIFY_DONE ;
}
/*
* Kernel Interfaces
*/
2007-02-12 11:55:32 +03:00
static const struct file_operations wdt_fops = {
2006-08-24 19:11:50 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = wdt_write ,
2008-05-19 17:09:23 +04:00
. unlocked_ioctl = wdt_ioctl ,
2006-08-24 19:11:50 +04:00
. open = wdt_open ,
. release = wdt_close ,
} ;
static struct miscdevice wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & wdt_fops ,
} ;
/*
* The WDT needs to learn about soft shutdowns in order to
* turn the timebomb registers off .
*/
static struct notifier_block wdt_notifier = {
. notifier_call = wdt_notify_sys ,
} ;
2008-05-19 17:09:23 +04:00
static int w83697hf_check_wdt ( void )
2006-09-07 13:57:00 +04:00
{
if ( ! request_region ( wdt_io , 2 , WATCHDOG_NAME ) ) {
2008-05-19 17:09:23 +04:00
printk ( KERN_ERR PFX
" I/O address 0x%x already in use \n " , wdt_io ) ;
2006-09-07 13:57:00 +04:00
return - EIO ;
}
2008-05-19 17:09:23 +04:00
printk ( KERN_DEBUG PFX
" Looking for watchdog at address 0x%x \n " , wdt_io ) ;
2006-09-07 13:57:00 +04:00
w83697hf_unlock ( ) ;
if ( w83697hf_get_reg ( 0x20 ) = = 0x60 ) {
2008-05-19 17:09:23 +04:00
printk ( KERN_INFO PFX
" watchdog found at address 0x%x \n " , wdt_io ) ;
2006-09-07 13:57:00 +04:00
w83697hf_lock ( ) ;
return 0 ;
}
2008-05-19 17:09:23 +04:00
/* Reprotect in case it was a compatible device */
w83697hf_lock ( ) ;
2006-09-07 13:57:00 +04:00
2008-05-19 17:09:23 +04:00
printk ( KERN_INFO PFX " watchdog not found at address 0x%x \n " , wdt_io ) ;
2006-09-07 13:57:00 +04:00
release_region ( wdt_io , 2 ) ;
return - EIO ;
}
2006-09-15 19:59:07 +04:00
static int w83697hf_ioports [ ] = { 0x2e , 0x4e , 0x00 } ;
2008-05-19 17:09:23 +04:00
static int __init wdt_init ( void )
2006-08-24 19:11:50 +04:00
{
2006-09-15 19:59:07 +04:00
int ret , i , found = 0 ;
2006-08-24 19:11:50 +04:00
2008-05-19 17:09:23 +04:00
printk ( KERN_INFO PFX " WDT driver for W83697HF/HG initializing \n " ) ;
2006-08-24 19:11:50 +04:00
2006-09-15 19:59:07 +04:00
if ( wdt_io = = 0 ) {
/* we will autodetect the W83697HF/HG watchdog */
for ( i = 0 ; ( ( ! found ) & & ( w83697hf_ioports [ i ] ! = 0 ) ) ; i + + ) {
wdt_io = w83697hf_ioports [ i ] ;
2008-01-19 00:01:34 +03:00
if ( ! w83697hf_check_wdt ( ) )
2006-09-15 19:59:07 +04:00
found + + ;
}
} else {
2006-09-07 13:57:00 +04:00
if ( ! w83697hf_check_wdt ( ) )
2006-09-15 19:59:07 +04:00
found + + ;
2006-09-07 13:57:00 +04:00
}
2006-09-15 19:59:07 +04:00
if ( ! found ) {
2008-05-19 17:09:23 +04:00
printk ( KERN_ERR PFX " No W83697HF/HG could be found \n " ) ;
2006-09-15 19:59:07 +04:00
ret = - EIO ;
goto out ;
}
2006-09-07 13:57:00 +04:00
2006-09-07 13:57:00 +04:00
w83697hf_init ( ) ;
2008-03-12 16:28:03 +03:00
if ( early_disable ) {
if ( wdt_running ( ) )
2008-08-07 00:19:41 +04:00
printk ( KERN_WARNING PFX " Stopping previously enabled watchdog until userland kicks in \n " ) ;
2008-03-12 16:28:03 +03:00
wdt_disable ( ) ;
}
2006-09-07 13:57:00 +04:00
2006-08-24 19:11:50 +04:00
if ( wdt_set_heartbeat ( timeout ) ) {
wdt_set_heartbeat ( WATCHDOG_TIMEOUT ) ;
2008-05-19 17:09:23 +04:00
printk ( KERN_INFO PFX
" timeout value must be 1 <= timeout <= 255, using %d \n " ,
WATCHDOG_TIMEOUT ) ;
2006-08-24 19:11:50 +04:00
}
ret = register_reboot_notifier ( & wdt_notifier ) ;
if ( ret ! = 0 ) {
2008-05-19 17:09:23 +04:00
printk ( KERN_ERR PFX
" cannot register reboot notifier (err=%d) \n " , ret ) ;
2006-08-24 19:11:50 +04:00
goto unreg_regions ;
}
ret = misc_register ( & wdt_miscdev ) ;
if ( ret ! = 0 ) {
2008-05-19 17:09:23 +04:00
printk ( KERN_ERR PFX
" cannot register miscdev on minor=%d (err=%d) \n " ,
WATCHDOG_MINOR , ret ) ;
2006-08-24 19:11:50 +04:00
goto unreg_reboot ;
}
2008-05-19 17:09:23 +04:00
printk ( KERN_INFO PFX " initialized. timeout=%d sec (nowayout=%d) \n " ,
2006-08-24 19:11:50 +04:00
timeout , nowayout ) ;
out :
return ret ;
unreg_reboot :
unregister_reboot_notifier ( & wdt_notifier ) ;
unreg_regions :
2006-09-07 13:57:00 +04:00
release_region ( wdt_io , 2 ) ;
2006-08-24 19:11:50 +04:00
goto out ;
}
2008-05-19 17:09:23 +04:00
static void __exit wdt_exit ( void )
2006-08-24 19:11:50 +04:00
{
misc_deregister ( & wdt_miscdev ) ;
unregister_reboot_notifier ( & wdt_notifier ) ;
2006-09-07 13:57:00 +04:00
release_region ( wdt_io , 2 ) ;
2006-08-24 19:11:50 +04:00
}
module_init ( wdt_init ) ;
module_exit ( wdt_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
2006-09-07 13:57:00 +04:00
MODULE_AUTHOR ( " Marcus Junker <junker@anduras.de>, Samuel Tardieu <sam@rfc1149.net> " ) ;
2006-09-07 13:57:00 +04:00
MODULE_DESCRIPTION ( " w83697hf/hg WDT driver " ) ;
2006-08-24 19:11:50 +04:00
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;