2005-04-17 02:20:36 +04:00
/*
* Watchdog for the 7101 PMU version found in the ALi M1535 chipsets
*
* 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 .
*/
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/types.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/ioport.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/init.h>
# include <linux/fs.h>
# include <linux/pci.h>
2008-05-19 17:04:57 +04:00
# include <linux/uaccess.h>
# include <linux/io.h>
2005-04-17 02:20:36 +04:00
# define WATCHDOG_NAME "ALi_M1535"
# define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
/* internal variables */
static unsigned long ali_is_open ;
static char ali_expect_release ;
static struct pci_dev * ali_pci ;
2008-05-19 17:04:57 +04:00
static u32 ali_timeout_bits ; /* stores the computed timeout */
2007-11-02 02:27:08 +03:00
static DEFINE_SPINLOCK ( ali_lock ) ; /* Guards the hardware */
2005-04-17 02:20:36 +04:00
/* module parameters */
static int timeout = WATCHDOG_TIMEOUT ;
module_param ( timeout , int , 0 ) ;
2008-05-19 17:04:57 +04:00
MODULE_PARM_DESC ( timeout ,
" Watchdog timeout in seconds. (0 < timeout < 18000, default= "
__MODULE_STRING ( WATCHDOG_TIMEOUT ) " ) " ) ;
2005-04-17 02:20:36 +04:00
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:04:57 +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
/*
* ali_start - start watchdog countdown
*
* Starts the timer running providing the timer has a counter
* configuration set .
*/
static void ali_start ( void )
{
u32 val ;
spin_lock ( & ali_lock ) ;
pci_read_config_dword ( ali_pci , 0xCC , & val ) ;
val & = ~ 0x3F ; /* Mask count */
2009-03-18 11:35:09 +03:00
val | = ( 1 < < 25 ) | ali_timeout_bits ;
2005-04-17 02:20:36 +04:00
pci_write_config_dword ( ali_pci , 0xCC , val ) ;
spin_unlock ( & ali_lock ) ;
}
/*
* ali_stop - stop the timer countdown
*
* Stop the ALi watchdog countdown
*/
static void ali_stop ( void )
{
u32 val ;
spin_lock ( & ali_lock ) ;
pci_read_config_dword ( ali_pci , 0xCC , & val ) ;
2009-03-18 11:35:09 +03:00
val & = ~ 0x3F ; /* Mask count to zero (disabled) */
val & = ~ ( 1 < < 25 ) ; /* and for safety mask the reset enable */
2005-04-17 02:20:36 +04:00
pci_write_config_dword ( ali_pci , 0xCC , val ) ;
spin_unlock ( & ali_lock ) ;
}
/*
* ali_keepalive - send a keepalive to the watchdog
*
2009-03-18 11:35:09 +03:00
* Send a keepalive to the timer ( actually we restart the timer ) .
2005-04-17 02:20:36 +04:00
*/
static void ali_keepalive ( void )
{
ali_start ( ) ;
}
/*
* ali_settimer - compute the timer reload value
* @ t : time in seconds
*
* Computes the timeout values needed
*/
static int ali_settimer ( int t )
{
2008-05-19 17:04:57 +04:00
if ( t < 0 )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2008-05-19 17:04:57 +04:00
else if ( t < 60 )
2009-03-18 11:35:09 +03:00
ali_timeout_bits = t | ( 1 < < 6 ) ;
2008-05-19 17:04:57 +04:00
else if ( t < 3600 )
2009-03-18 11:35:09 +03:00
ali_timeout_bits = ( t / 60 ) | ( 1 < < 7 ) ;
2008-05-19 17:04:57 +04:00
else if ( t < 18000 )
2009-03-18 11:35:09 +03:00
ali_timeout_bits = ( t / 300 ) | ( 1 < < 6 ) | ( 1 < < 7 ) ;
2008-05-19 17:04:57 +04:00
else
return - EINVAL ;
2005-04-17 02:20:36 +04:00
timeout = t ;
return 0 ;
}
/*
* / dev / watchdog handling
*/
/*
* ali_write - writes to ALi watchdog
* @ file : file from VFS
* @ data : user address of data
* @ len : length of data
* @ ppos : pointer to the file offset
*
* Handle a write to the ALi watchdog . Writing to the file pings
* the watchdog and resets it . Writing the magic ' V ' sequence allows
* the next close to turn off the watchdog .
*/
static ssize_t ali_write ( struct file * file , const char __user * data ,
2009-03-18 11:35:09 +03:00
size_t len , loff_t * ppos )
2005-04-17 02:20:36 +04:00
{
/* See if we got the magic character 'V' and reload the timer */
if ( len ) {
if ( ! nowayout ) {
size_t i ;
2008-05-19 17:04:57 +04:00
/* note: just in case someone wrote the
magic character five months ago . . . */
2005-04-17 02:20:36 +04:00
ali_expect_release = 0 ;
2008-05-19 17:04:57 +04:00
/* scan to see whether or not we got
the magic character */
2005-04-17 02:20:36 +04:00
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 ' )
ali_expect_release = 42 ;
}
}
/* someone wrote to us, we should reload the timer */
ali_start ( ) ;
}
return len ;
}
/*
* ali_ioctl - handle watchdog ioctls
* @ file : VFS file pointer
* @ cmd : ioctl number
* @ arg : arguments to the ioctl
*
* Handle the watchdog ioctls supported by the ALi driver . Really
* we want an extension to enable irq ack monitoring and the like
*/
2008-05-19 17:04:57 +04:00
static long ali_ioctl ( struct file * file , unsigned int cmd , unsigned long arg )
2005-04-17 02:20:36 +04:00
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2005-04-17 02:20:36 +04:00
. options = WDIOF_KEEPALIVEPING |
WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE ,
. firmware_version = 0 ,
. identity = " ALi M1535 WatchDog Timer " ,
} ;
switch ( cmd ) {
2008-05-19 17:04:57 +04:00
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
2005-04-17 02:20:36 +04:00
2008-05-19 17:04:57 +04:00
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_SETOPTIONS :
{
int new_options , retval = - EINVAL ;
if ( get_user ( new_options , p ) )
return - EFAULT ;
if ( new_options & WDIOS_DISABLECARD ) {
ali_stop ( ) ;
retval = 0 ;
2005-04-17 02:20:36 +04:00
}
2008-05-19 17:04:57 +04:00
if ( new_options & WDIOS_ENABLECARD ) {
ali_start ( ) ;
retval = 0 ;
2005-04-17 02:20:36 +04:00
}
2008-05-19 17:04:57 +04:00
return retval ;
}
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
ali_keepalive ( ) ;
return 0 ;
2008-05-19 17:04:57 +04:00
case WDIOC_SETTIMEOUT :
{
int new_timeout ;
if ( get_user ( new_timeout , p ) )
return - EFAULT ;
if ( ali_settimer ( new_timeout ) )
return - EINVAL ;
ali_keepalive ( ) ;
/* Fall */
}
case WDIOC_GETTIMEOUT :
return put_user ( timeout , p ) ;
default :
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
}
}
/*
* ali_open - handle open of ali watchdog
* @ inode : inode from VFS
* @ file : file from VFS
*
* Open the ALi watchdog device . Ensure only one person opens it
* at a time . Also start the watchdog running .
*/
static int ali_open ( struct inode * inode , struct file * file )
{
/* /dev/watchdog can only be opened once */
if ( test_and_set_bit ( 0 , & ali_is_open ) )
return - EBUSY ;
/* Activate */
ali_start ( ) ;
return nonseekable_open ( inode , file ) ;
}
/*
* ali_release - close an ALi watchdog
* @ inode : inode from VFS
* @ file : file from VFS
*
* Close the ALi watchdog device . Actual shutdown of the timer
* only occurs if the magic sequence has been set .
*/
static int ali_release ( struct inode * inode , struct file * file )
{
/*
* Shut off the timer .
*/
2008-05-19 17:04:57 +04:00
if ( ali_expect_release = = 42 )
2005-04-17 02:20:36 +04:00
ali_stop ( ) ;
2008-05-19 17:04:57 +04:00
else {
2012-02-16 03:06:19 +04:00
pr_crit ( " Unexpected close, not stopping watchdog! \n " ) ;
2005-04-17 02:20:36 +04:00
ali_keepalive ( ) ;
}
clear_bit ( 0 , & ali_is_open ) ;
ali_expect_release = 0 ;
return 0 ;
}
/*
* ali_notify_sys - System down notifier
*
* Notifier for system down
*/
2008-05-19 17:04:57 +04:00
static int ali_notify_sys ( struct notifier_block * this ,
unsigned long code , void * unused )
2005-04-17 02:20:36 +04:00
{
2008-05-19 17:04:57 +04:00
if ( code = = SYS_DOWN | | code = = SYS_HALT )
ali_stop ( ) ; /* Turn the WDT off */
2005-04-17 02:20:36 +04:00
return NOTIFY_DONE ;
}
/*
* Data for PCI driver interface
*
* This data only exists for exporting the supported
* PCI ids via MODULE_DEVICE_TABLE . We do not actually
* register a pci_driver , because someone else might one day
* want to register another driver on the same PCI id .
*/
2013-12-03 03:30:22 +04:00
static const struct pci_device_id ali_pci_tbl [ ] __used = {
2007-08-16 23:32:19 +04:00
{ PCI_VENDOR_ID_AL , 0x1533 , PCI_ANY_ID , PCI_ANY_ID , } ,
2005-07-27 22:43:42 +04:00
{ PCI_VENDOR_ID_AL , 0x1535 , PCI_ANY_ID , PCI_ANY_ID , } ,
2005-04-17 02:20:36 +04:00
{ 0 , } ,
} ;
MODULE_DEVICE_TABLE ( pci , ali_pci_tbl ) ;
/*
* ali_find_watchdog - find a 1535 and 7101
*
* Scans the PCI hardware for a 1535 series bridge and matching 7101
* watchdog device . This may be overtight but it is better to be safe
*/
static int __init ali_find_watchdog ( void )
{
struct pci_dev * pdev ;
u32 wdog ;
2007-08-16 23:32:19 +04:00
/* Check for a 1533/1535 series bridge */
2006-07-18 20:30:00 +04:00
pdev = pci_get_device ( PCI_VENDOR_ID_AL , 0x1535 , NULL ) ;
2007-08-16 23:32:19 +04:00
if ( pdev = = NULL )
pdev = pci_get_device ( PCI_VENDOR_ID_AL , 0x1533 , NULL ) ;
if ( pdev = = NULL )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
2006-07-18 20:30:00 +04:00
pci_dev_put ( pdev ) ;
2005-04-17 02:20:36 +04:00
/* Check for the a 7101 PMU */
2006-07-18 20:30:00 +04:00
pdev = pci_get_device ( PCI_VENDOR_ID_AL , 0x7101 , NULL ) ;
2008-05-19 17:04:57 +04:00
if ( pdev = = NULL )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
2008-05-19 17:04:57 +04:00
if ( pci_enable_device ( pdev ) ) {
2006-07-18 20:30:00 +04:00
pci_dev_put ( pdev ) ;
2005-04-17 02:20:36 +04:00
return - EIO ;
2006-07-18 20:30:00 +04:00
}
2005-04-17 02:20:36 +04:00
ali_pci = pdev ;
/*
* Initialize the timer bits
*/
pci_read_config_dword ( pdev , 0xCC , & wdog ) ;
2008-05-19 17:04:57 +04:00
/* Timer bits */
wdog & = ~ 0x3F ;
/* Issued events */
2009-03-18 11:35:09 +03:00
wdog & = ~ ( ( 1 < < 27 ) | ( 1 < < 26 ) | ( 1 < < 25 ) | ( 1 < < 24 ) ) ;
2008-05-19 17:04:57 +04:00
/* No monitor bits */
2009-03-18 11:35:09 +03:00
wdog & = ~ ( ( 1 < < 16 ) | ( 1 < < 13 ) | ( 1 < < 12 ) | ( 1 < < 11 ) | ( 1 < < 10 ) | ( 1 < < 9 ) ) ;
2005-04-17 02:20:36 +04:00
pci_write_config_dword ( pdev , 0xCC , wdog ) ;
return 0 ;
}
/*
* Kernel Interfaces
*/
2006-07-03 11:24:21 +04:00
static const struct file_operations ali_fops = {
2011-02-23 23:04:38 +03:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
2008-05-19 17:04:57 +04:00
. write = ali_write ,
. unlocked_ioctl = ali_ioctl ,
2011-02-23 23:04:38 +03:00
. open = ali_open ,
. release = ali_release ,
2005-04-17 02:20:36 +04:00
} ;
static struct miscdevice ali_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & ali_fops ,
} ;
static struct notifier_block ali_notifier = {
. notifier_call = ali_notify_sys ,
} ;
/*
* watchdog_init - module initialiser
*
* Scan for a suitable watchdog and if so initialize it . Return an error
* if we cannot , the error causes the module to unload
*/
static int __init watchdog_init ( void )
{
int ret ;
/* Check whether or not the hardware watchdog is there */
2008-05-19 17:04:57 +04:00
if ( ali_find_watchdog ( ) ! = 0 )
2005-04-17 02:20:36 +04:00
return - ENODEV ;
2008-05-19 17:04:57 +04:00
/* Check that the timeout value is within it's range;
if not reset to the default */
2005-04-17 02:20:36 +04:00
if ( timeout < 1 | | timeout > = 18000 ) {
timeout = WATCHDOG_TIMEOUT ;
2012-02-16 03:06:19 +04:00
pr_info ( " timeout value must be 0 < timeout < 18000, using %d \n " ,
timeout ) ;
2005-04-17 02:20:36 +04:00
}
/* Calculate the watchdog's timeout */
ali_settimer ( timeout ) ;
2007-12-26 23:32:51 +03:00
ret = register_reboot_notifier ( & ali_notifier ) ;
2005-04-17 02:20:36 +04:00
if ( ret ! = 0 ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot register reboot notifier (err=%d) \n " , ret ) ;
2005-04-17 02:20:36 +04:00
goto out ;
}
2007-12-26 23:32:51 +03:00
ret = misc_register ( & ali_miscdev ) ;
2005-04-17 02:20:36 +04:00
if ( ret ! = 0 ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot register miscdev on minor=%d (err=%d) \n " ,
WATCHDOG_MINOR , ret ) ;
2007-12-26 23:32:51 +03:00
goto unreg_reboot ;
2005-04-17 02:20:36 +04:00
}
2012-02-16 03:06:19 +04:00
pr_info ( " initialized. timeout=%d sec (nowayout=%d) \n " ,
2005-04-17 02:20:36 +04:00
timeout , nowayout ) ;
out :
return ret ;
2007-12-26 23:32:51 +03:00
unreg_reboot :
unregister_reboot_notifier ( & ali_notifier ) ;
2005-04-17 02:20:36 +04:00
goto out ;
}
/*
* watchdog_exit - module de - initialiser
*
* Called while unloading a successfully installed watchdog module .
*/
static void __exit watchdog_exit ( void )
{
/* Stop the timer before we leave */
ali_stop ( ) ;
/* Deregister */
misc_deregister ( & ali_miscdev ) ;
2007-12-26 23:32:51 +03:00
unregister_reboot_notifier ( & ali_notifier ) ;
2006-07-18 20:30:00 +04:00
pci_dev_put ( ali_pci ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( watchdog_init ) ;
module_exit ( watchdog_exit ) ;
MODULE_AUTHOR ( " Alan Cox " ) ;
MODULE_DESCRIPTION ( " ALi M1535 PMU Watchdog Timer driver " ) ;
MODULE_LICENSE ( " GPL " ) ;