2005-04-17 02:20:36 +04:00
/*
* National Semiconductor PC87307 / PC97307 ( ala SC1200 ) WDT driver
* ( c ) Copyright 2002 Zwane Mwaikambo < zwane @ commfireservices . com > ,
* All Rights Reserved .
* Based on wdt . c and wdt977 . c by Alan Cox and Woody Suwalski respectively .
*
* 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 .
*
* The author ( s ) of this software shall not be held liable for damages
* of any nature resulting due to the use of this software . This
* software is provided AS - IS with no warranties .
*
* Changelog :
* 20020220 Zwane Mwaikambo Code based on datasheet , no hardware .
2008-08-04 20:56:28 +04:00
* 20020221 Zwane Mwaikambo Cleanups as suggested by Jeff Garzik
* and Alan Cox .
2005-04-17 02:20:36 +04:00
* 20020222 Zwane Mwaikambo Added probing .
* 20020225 Zwane Mwaikambo Added ISAPNP support .
* 20020412 Rob Radez Broke out start / stop functions
2008-08-04 20:56:28 +04:00
* < rob @ osinvestor . com > Return proper status instead of
* temperature warning
* Add WDIOC_GETBOOTSTATUS and
* WDIOC_SETOPTIONS ioctls
2005-04-17 02:20:36 +04:00
* Fix CONFIG_WATCHDOG_NOWAYOUT
2008-08-04 20:56:28 +04:00
* 20020530 Joel Becker Add Matt Domsch ' s nowayout module
* option
2005-04-17 02:20:36 +04:00
* 20030116 Adam Belay Updated to the latest pnp code
*
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/ioport.h>
# include <linux/spinlock.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/init.h>
# include <linux/pnp.h>
# include <linux/fs.h>
2008-04-19 06:21:05 +04:00
# include <linux/semaphore.h>
2008-08-04 20:56:28 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2005-04-17 02:20:36 +04:00
# define SC1200_MODULE_VER "build 20020303"
# define SC1200_MODULE_NAME "sc1200wdt"
# define MAX_TIMEOUT 255 /* 255 minutes */
# define PMIR (io) /* Power Management Index Register */
# define PMDR (io+1) /* Power Management Data Register */
/* Data Register indexes */
# define FER1 0x00 /* Function enable register 1 */
# define FER2 0x01 /* Function enable register 2 */
# define PMC1 0x02 /* Power Management Ctrl 1 */
# define PMC2 0x03 /* Power Management Ctrl 2 */
# define PMC3 0x04 /* Power Management Ctrl 3 */
# define WDTO 0x05 /* Watchdog timeout register */
# define WDCF 0x06 /* Watchdog config register */
# define WDST 0x07 /* Watchdog status register */
/* WDCF bitfields - which devices assert WDO */
# define KBC_IRQ 0x01 /* Keyboard Controller */
# define MSE_IRQ 0x02 /* Mouse */
# define UART1_IRQ 0x03 /* Serial0 */
# define UART2_IRQ 0x04 /* Serial1 */
/* 5 -7 are reserved */
static int timeout = 1 ;
static int io = - 1 ;
static int io_len = 2 ; /* for non plug and play */
2008-08-04 20:56:28 +04:00
static unsigned long open_flag ;
2005-04-17 02:20:36 +04:00
static char expect_close ;
2007-11-02 02:27:08 +03:00
static DEFINE_SPINLOCK ( sc1200wdt_lock ) ; /* io port access serialisation */
2005-04-17 02:20:36 +04:00
# if defined CONFIG_PNP
static int isapnp = 1 ;
static struct pnp_dev * wdt_dev ;
module_param ( isapnp , int , 0 ) ;
2008-08-04 20:56:28 +04:00
MODULE_PARM_DESC ( isapnp ,
" When set to 0 driver ISA PnP support will be disabled " ) ;
2005-04-17 02:20:36 +04:00
# endif
module_param ( io , int , 0 ) ;
MODULE_PARM_DESC ( io , " io port " ) ;
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout , " range is 0-255 minutes, default is 1 " ) ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-08-04 20:56:28 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2005-04-17 02:20:36 +04:00
/* Read from Data Register */
2008-08-04 20:56:28 +04:00
static inline void __sc1200wdt_read_data ( unsigned char index ,
unsigned char * data )
2005-04-17 02:20:36 +04:00
{
outb_p ( index , PMIR ) ;
* data = inb ( PMDR ) ;
}
2008-08-04 20:56:28 +04:00
static void sc1200wdt_read_data ( unsigned char index , unsigned char * data )
{
spin_lock ( & sc1200wdt_lock ) ;
__sc1200wdt_read_data ( index , data ) ;
spin_unlock ( & sc1200wdt_lock ) ;
}
2005-04-17 02:20:36 +04:00
/* Write to Data Register */
2008-08-04 20:56:28 +04:00
static inline void __sc1200wdt_write_data ( unsigned char index ,
unsigned char data )
2005-04-17 02:20:36 +04:00
{
outb_p ( index , PMIR ) ;
outb ( data , PMDR ) ;
2008-08-04 20:56:28 +04:00
}
static inline void sc1200wdt_write_data ( unsigned char index ,
unsigned char data )
{
spin_lock ( & sc1200wdt_lock ) ;
__sc1200wdt_write_data ( index , data ) ;
2005-04-17 02:20:36 +04:00
spin_unlock ( & sc1200wdt_lock ) ;
}
static void sc1200wdt_start ( void )
{
unsigned char reg ;
2008-08-04 20:56:28 +04:00
spin_lock ( & sc1200wdt_lock ) ;
2005-04-17 02:20:36 +04:00
2008-08-04 20:56:28 +04:00
__sc1200wdt_read_data ( WDCF , & reg ) ;
2005-04-17 02:20:36 +04:00
/* assert WDO when any of the following interrupts are triggered too */
reg | = ( KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ ) ;
2008-08-04 20:56:28 +04:00
__sc1200wdt_write_data ( WDCF , reg ) ;
2005-04-17 02:20:36 +04:00
/* set the timeout and get the ball rolling */
2008-08-04 20:56:28 +04:00
__sc1200wdt_write_data ( WDTO , timeout ) ;
2005-04-17 02:20:36 +04:00
2008-08-04 20:56:28 +04:00
spin_unlock ( & sc1200wdt_lock ) ;
}
2005-04-17 02:20:36 +04:00
static void sc1200wdt_stop ( void )
{
sc1200wdt_write_data ( WDTO , 0 ) ;
}
/* This returns the status of the WDO signal, inactive high. */
static inline int sc1200wdt_status ( void )
{
unsigned char ret ;
sc1200wdt_read_data ( WDST , & ret ) ;
/* If the bit is inactive, the watchdog is enabled, so return
* KEEPALIVEPING which is a bit of a kludge because there ' s nothing
* else for enabled / disabled status
*/
2008-08-04 20:56:28 +04:00
return ( ret & 0x01 ) ? 0 : WDIOF_KEEPALIVEPING ;
2005-04-17 02:20:36 +04:00
}
static int sc1200wdt_open ( struct inode * inode , struct file * file )
{
/* allow one at a time */
2008-08-04 20:56:28 +04:00
if ( test_and_set_bit ( 0 , & open_flag ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
if ( timeout > MAX_TIMEOUT )
timeout = MAX_TIMEOUT ;
sc1200wdt_start ( ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " Watchdog enabled, timeout = %d min(s) " , timeout ) ;
2005-04-17 02:20:36 +04:00
2007-07-25 01:38:37 +04:00
return nonseekable_open ( inode , file ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-04 20:56:28 +04:00
static long sc1200wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2005-04-17 02:20:36 +04:00
{
int new_timeout ;
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
2008-08-04 20:56:28 +04:00
static const struct watchdog_info ident = {
. options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE ,
2005-04-17 02:20:36 +04:00
. firmware_version = 0 ,
. identity = " PC87307/PC97307 " ,
} ;
switch ( cmd ) {
2008-08-04 20:56:28 +04:00
case WDIOC_GETSUPPORT :
2009-09-02 13:10:07 +04:00
if ( copy_to_user ( argp , & ident , sizeof ( ident ) ) )
2008-08-04 20:56:28 +04:00
return - EFAULT ;
return 0 ;
2005-04-17 02:20:36 +04:00
2008-08-04 20:56:28 +04:00
case WDIOC_GETSTATUS :
return put_user ( sc1200wdt_status ( ) , p ) ;
2005-04-17 02:20:36 +04:00
2008-08-04 20:56:28 +04:00
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
2005-04-17 02:20:36 +04:00
2008-08-04 20:56:28 +04:00
case WDIOC_SETOPTIONS :
{
int options , retval = - EINVAL ;
2005-04-17 02:20:36 +04:00
2008-08-04 20:56:28 +04:00
if ( get_user ( options , p ) )
return - EFAULT ;
2005-04-17 02:20:36 +04:00
2008-08-04 20:56:28 +04:00
if ( options & WDIOS_DISABLECARD ) {
sc1200wdt_stop ( ) ;
retval = 0 ;
}
2005-04-17 02:20:36 +04:00
2008-08-04 20:56:28 +04:00
if ( options & WDIOS_ENABLECARD ) {
sc1200wdt_start ( ) ;
retval = 0 ;
2005-04-17 02:20:36 +04:00
}
2008-08-04 20:56:28 +04:00
return retval ;
}
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
sc1200wdt_write_data ( WDTO , timeout ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_timeout , p ) )
return - EFAULT ;
/* the API states this is given in secs */
new_timeout / = 60 ;
if ( new_timeout < 0 | | new_timeout > MAX_TIMEOUT )
return - EINVAL ;
timeout = new_timeout ;
sc1200wdt_write_data ( WDTO , timeout ) ;
/* fall through and return the new timeout */
case WDIOC_GETTIMEOUT :
return put_user ( timeout * 60 , p ) ;
2008-08-04 20:56:28 +04:00
default :
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
}
}
static int sc1200wdt_release ( struct inode * inode , struct file * file )
{
if ( expect_close = = 42 ) {
sc1200wdt_stop ( ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " Watchdog disabled \n " ) ;
2005-04-17 02:20:36 +04:00
} else {
sc1200wdt_write_data ( WDTO , timeout ) ;
2012-02-16 03:06:19 +04:00
pr_crit ( " Unexpected close!, timeout = %d min(s) \n " , timeout ) ;
2005-04-17 02:20:36 +04:00
}
2008-08-04 20:56:28 +04:00
clear_bit ( 0 , & open_flag ) ;
2005-04-17 02:20:36 +04:00
expect_close = 0 ;
return 0 ;
}
2008-08-04 20:56:28 +04:00
static ssize_t sc1200wdt_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
if ( len ) {
if ( ! nowayout ) {
size_t i ;
expect_close = 0 ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
2008-08-07 00:19:41 +04:00
if ( get_user ( c , data + i ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 42 ;
}
}
sc1200wdt_write_data ( WDTO , timeout ) ;
return len ;
}
return 0 ;
}
2008-08-04 20:56:28 +04:00
static int sc1200wdt_notify_sys ( struct notifier_block * this ,
unsigned long code , void * unused )
2005-04-17 02:20:36 +04:00
{
if ( code = = SYS_DOWN | | code = = SYS_HALT )
sc1200wdt_stop ( ) ;
return NOTIFY_DONE ;
}
2008-08-04 20:56:28 +04:00
static struct notifier_block sc1200wdt_notifier = {
2005-04-17 02:20:36 +04:00
. notifier_call = sc1200wdt_notify_sys ,
} ;
2008-08-04 20:56:28 +04:00
static const struct file_operations sc1200wdt_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = sc1200wdt_write ,
2008-08-04 20:56:28 +04:00
. unlocked_ioctl = sc1200wdt_ioctl ,
2005-04-17 02:20:36 +04:00
. open = sc1200wdt_open ,
. release = sc1200wdt_release ,
} ;
2008-08-04 20:56:28 +04:00
static struct miscdevice sc1200wdt_miscdev = {
2005-04-17 02:20:36 +04:00
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & sc1200wdt_fops ,
} ;
static int __init sc1200wdt_probe ( void )
{
/* The probe works by reading the PMC3 register's default value of 0x0e
* there is one caveat , if the device disables the parallel port or any
* of the UARTs we won ' t be able to detect it .
2008-08-04 20:56:28 +04:00
* NB . This could be done with accuracy by reading the SID registers ,
* but we don ' t have access to those io regions .
2005-04-17 02:20:36 +04:00
*/
unsigned char reg ;
sc1200wdt_read_data ( PMC3 , & reg ) ;
2008-08-04 20:56:28 +04:00
reg & = 0x0f ; /* we don't want the UART busy bits */
2005-04-17 02:20:36 +04:00
return ( reg = = 0x0e ) ? 0 : - ENODEV ;
}
# if defined CONFIG_PNP
static struct pnp_device_id scl200wdt_pnp_devices [ ] = {
/* National Semiconductor PC87307/PC97307 watchdog component */
{ . id = " NSC0800 " , . driver_data = 0 } ,
{ . id = " " } ,
} ;
2008-08-04 20:56:28 +04:00
static int scl200wdt_pnp_probe ( struct pnp_dev * dev ,
const struct pnp_device_id * dev_id )
2005-04-17 02:20:36 +04:00
{
/* this driver only supports one card at a time */
if ( wdt_dev | | ! isapnp )
return - EBUSY ;
wdt_dev = dev ;
io = pnp_port_start ( wdt_dev , 0 ) ;
io_len = pnp_port_len ( wdt_dev , 0 ) ;
if ( ! request_region ( io , io_len , SC1200_MODULE_NAME ) ) {
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to register IO port %#x \n " , io ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
2012-02-16 03:06:19 +04:00
pr_info ( " PnP device found at io port %#x/%d \n " , io , io_len ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2008-08-04 20:56:28 +04:00
static void scl200wdt_pnp_remove ( struct pnp_dev * dev )
2005-04-17 02:20:36 +04:00
{
2008-08-04 20:56:28 +04:00
if ( wdt_dev ) {
2005-04-17 02:20:36 +04:00
release_region ( io , io_len ) ;
wdt_dev = NULL ;
}
}
static struct pnp_driver scl200wdt_pnp_driver = {
. name = " scl200wdt " ,
. id_table = scl200wdt_pnp_devices ,
. probe = scl200wdt_pnp_probe ,
. remove = scl200wdt_pnp_remove ,
} ;
# endif /* CONFIG_PNP */
static int __init sc1200wdt_init ( void )
{
int ret ;
2012-02-16 03:06:19 +04:00
pr_info ( " %s \n " , SC1200_MODULE_VER ) ;
2005-04-17 02:20:36 +04:00
# if defined CONFIG_PNP
if ( isapnp ) {
ret = pnp_register_driver ( & scl200wdt_pnp_driver ) ;
if ( ret )
goto out_clean ;
}
# endif
if ( io = = - 1 ) {
2012-02-16 03:06:19 +04:00
pr_err ( " io parameter must be specified \n " ) ;
2005-04-17 02:20:36 +04:00
ret = - EINVAL ;
2006-10-28 22:43:19 +04:00
goto out_pnp ;
2005-04-17 02:20:36 +04:00
}
# if defined CONFIG_PNP
/* now that the user has specified an IO port and we haven't detected
* any devices , disable pnp support */
2013-12-04 19:31:52 +04:00
if ( isapnp )
pnp_unregister_driver ( & scl200wdt_pnp_driver ) ;
2005-04-17 02:20:36 +04:00
isapnp = 0 ;
# endif
if ( ! request_region ( io , io_len , SC1200_MODULE_NAME ) ) {
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to register IO port %#x \n " , io ) ;
2005-04-17 02:20:36 +04:00
ret = - EBUSY ;
2006-10-28 22:43:19 +04:00
goto out_pnp ;
2005-04-17 02:20:36 +04:00
}
ret = sc1200wdt_probe ( ) ;
if ( ret )
goto out_io ;
ret = register_reboot_notifier ( & sc1200wdt_notifier ) ;
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to register reboot notifier err = %d \n " , ret ) ;
2005-04-17 02:20:36 +04:00
goto out_io ;
}
ret = misc_register ( & sc1200wdt_miscdev ) ;
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to register miscdev on minor %d \n " ,
WATCHDOG_MINOR ) ;
2005-04-17 02:20:36 +04:00
goto out_rbt ;
}
/* ret = 0 */
out_clean :
return ret ;
out_rbt :
unregister_reboot_notifier ( & sc1200wdt_notifier ) ;
out_io :
release_region ( io , io_len ) ;
2006-10-28 22:43:19 +04:00
out_pnp :
# if defined CONFIG_PNP
if ( isapnp )
pnp_unregister_driver ( & scl200wdt_pnp_driver ) ;
# endif
2005-04-17 02:20:36 +04:00
goto out_clean ;
}
static void __exit sc1200wdt_exit ( void )
{
misc_deregister ( & sc1200wdt_miscdev ) ;
unregister_reboot_notifier ( & sc1200wdt_notifier ) ;
# if defined CONFIG_PNP
2008-08-04 20:56:28 +04:00
if ( isapnp )
2005-04-17 02:20:36 +04:00
pnp_unregister_driver ( & scl200wdt_pnp_driver ) ;
else
# endif
release_region ( io , io_len ) ;
}
module_init ( sc1200wdt_init ) ;
module_exit ( sc1200wdt_exit ) ;
MODULE_AUTHOR ( " Zwane Mwaikambo <zwane@commfireservices.com> " ) ;
2009-03-18 11:35:09 +03:00
MODULE_DESCRIPTION (
" Driver for National Semiconductor PC87307/PC97307 watchdog component " ) ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;