2008-02-25 14:59:26 +03:00
/*
* RDC321x watchdog driver
*
* Copyright ( C ) 2007 Florian Fainelli < florian @ openwrt . org >
*
* This driver is highly inspired from the cpu5_wdt driver
*
* 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 .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/miscdevice.h>
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/ioport.h>
# include <linux/timer.h>
# include <linux/completion.h>
# include <linux/jiffies.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
# include <linux/io.h>
# include <linux/uaccess.h>
2009-01-18 21:37:21 +03:00
# include <asm/rdc321x_defs.h>
2008-02-25 14:59:26 +03:00
# define RDC_WDT_MASK 0x80000000 /* Mask */
# define RDC_WDT_EN 0x00800000 /* Enable bit */
# define RDC_WDT_WTI 0x00200000 /* Generate CPU reset/NMI/WDT on timeout */
# define RDC_WDT_RST 0x00100000 /* Reset bit */
# define RDC_WDT_WIF 0x00040000 /* WDT IRQ Flag */
# define RDC_WDT_IRT 0x00000100 /* IRQ Routing table */
# define RDC_WDT_CNT 0x00000001 /* WDT count */
# define RDC_CLS_TMR 0x80003844 /* Clear timer */
# define RDC_WDT_INTERVAL (HZ / 10+1)
static int ticks = 1000 ;
/* some device data */
static struct {
struct completion stop ;
int running ;
struct timer_list timer ;
int queue ;
int default_ticks ;
unsigned long inuse ;
spinlock_t lock ;
} rdc321x_wdt_device ;
/* generic helper functions */
static void rdc321x_wdt_trigger ( unsigned long unused )
{
unsigned long flags ;
if ( rdc321x_wdt_device . running )
ticks - - ;
/* keep watchdog alive */
spin_lock_irqsave ( & rdc321x_wdt_device . lock , flags ) ;
outl ( RDC_WDT_EN | inl ( RDC3210_CFGREG_DATA ) ,
RDC3210_CFGREG_DATA ) ;
spin_unlock_irqrestore ( & rdc321x_wdt_device . lock , flags ) ;
/* requeue?? */
if ( rdc321x_wdt_device . queue & & ticks )
mod_timer ( & rdc321x_wdt_device . timer ,
jiffies + RDC_WDT_INTERVAL ) ;
else {
/* ticks doesn't matter anyway */
complete ( & rdc321x_wdt_device . stop ) ;
}
}
static void rdc321x_wdt_reset ( void )
{
ticks = rdc321x_wdt_device . default_ticks ;
}
static void rdc321x_wdt_start ( void )
{
unsigned long flags ;
if ( ! rdc321x_wdt_device . queue ) {
rdc321x_wdt_device . queue = 1 ;
/* Clear the timer */
spin_lock_irqsave ( & rdc321x_wdt_device . lock , flags ) ;
outl ( RDC_CLS_TMR , RDC3210_CFGREG_ADDR ) ;
/* Enable watchdog and set the timeout to 81.92 us */
outl ( RDC_WDT_EN | RDC_WDT_CNT , RDC3210_CFGREG_DATA ) ;
spin_unlock_irqrestore ( & rdc321x_wdt_device . lock , flags ) ;
mod_timer ( & rdc321x_wdt_device . timer ,
jiffies + RDC_WDT_INTERVAL ) ;
}
/* if process dies, counter is not decremented */
rdc321x_wdt_device . running + + ;
}
static int rdc321x_wdt_stop ( void )
{
if ( rdc321x_wdt_device . running )
rdc321x_wdt_device . running = 0 ;
ticks = rdc321x_wdt_device . default_ticks ;
return - EIO ;
}
/* filesystem operations */
static int rdc321x_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 0 , & rdc321x_wdt_device . inuse ) )
return - EBUSY ;
return nonseekable_open ( inode , file ) ;
}
static int rdc321x_wdt_release ( struct inode * inode , struct file * file )
{
clear_bit ( 0 , & rdc321x_wdt_device . inuse ) ;
return 0 ;
}
2008-09-18 16:26:15 +04:00
static long rdc321x_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2008-02-25 14:59:26 +03:00
{
void __user * argp = ( void __user * ) arg ;
unsigned int value ;
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2008-02-25 14:59:26 +03:00
. options = WDIOF_CARDRESET ,
. identity = " RDC321x WDT " ,
} ;
unsigned long flags ;
switch ( cmd ) {
case WDIOC_KEEPALIVE :
rdc321x_wdt_reset ( ) ;
break ;
case WDIOC_GETSTATUS :
/* Read the value from the DATA register */
spin_lock_irqsave ( & rdc321x_wdt_device . lock , flags ) ;
value = inl ( RDC3210_CFGREG_DATA ) ;
spin_unlock_irqrestore ( & rdc321x_wdt_device . lock , flags ) ;
if ( copy_to_user ( argp , & value , sizeof ( int ) ) )
return - EFAULT ;
break ;
case WDIOC_GETSUPPORT :
if ( copy_to_user ( argp , & ident , sizeof ( ident ) ) )
return - EFAULT ;
break ;
case WDIOC_SETOPTIONS :
if ( copy_from_user ( & value , argp , sizeof ( int ) ) )
return - EFAULT ;
switch ( value ) {
case WDIOS_ENABLECARD :
rdc321x_wdt_start ( ) ;
break ;
case WDIOS_DISABLECARD :
return rdc321x_wdt_stop ( ) ;
default :
return - EINVAL ;
}
break ;
default :
return - ENOTTY ;
}
return 0 ;
}
static ssize_t rdc321x_wdt_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
{
if ( ! count )
return - EIO ;
rdc321x_wdt_reset ( ) ;
return count ;
}
static const struct file_operations rdc321x_wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
2008-09-18 16:26:15 +04:00
. unlocked_ioctl = rdc321x_wdt_ioctl ,
2008-02-25 14:59:26 +03:00
. open = rdc321x_wdt_open ,
. write = rdc321x_wdt_write ,
. release = rdc321x_wdt_release ,
} ;
static struct miscdevice rdc321x_wdt_misc = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & rdc321x_wdt_fops ,
} ;
static int __devinit rdc321x_wdt_probe ( struct platform_device * pdev )
{
int err ;
err = misc_register ( & rdc321x_wdt_misc ) ;
if ( err < 0 ) {
printk ( KERN_ERR PFX " watchdog misc_register failed \n " ) ;
return err ;
}
spin_lock_init ( & rdc321x_wdt_device . lock ) ;
/* Reset the watchdog */
outl ( RDC_WDT_RST , RDC3210_CFGREG_DATA ) ;
init_completion ( & rdc321x_wdt_device . stop ) ;
rdc321x_wdt_device . queue = 0 ;
clear_bit ( 0 , & rdc321x_wdt_device . inuse ) ;
setup_timer ( & rdc321x_wdt_device . timer , rdc321x_wdt_trigger , 0 ) ;
rdc321x_wdt_device . default_ticks = ticks ;
printk ( KERN_INFO PFX " watchdog init success \n " ) ;
return 0 ;
}
2009-04-15 00:30:55 +04:00
static int __devexit rdc321x_wdt_remove ( struct platform_device * pdev )
2008-02-25 14:59:26 +03:00
{
if ( rdc321x_wdt_device . queue ) {
rdc321x_wdt_device . queue = 0 ;
wait_for_completion ( & rdc321x_wdt_device . stop ) ;
}
misc_deregister ( & rdc321x_wdt_misc ) ;
return 0 ;
}
static struct platform_driver rdc321x_wdt_driver = {
. probe = rdc321x_wdt_probe ,
2009-04-15 00:30:55 +04:00
. remove = __devexit_p ( rdc321x_wdt_remove ) ,
2008-02-25 14:59:26 +03:00
. driver = {
. owner = THIS_MODULE ,
. name = " rdc321x-wdt " ,
} ,
} ;
static int __init rdc321x_wdt_init ( void )
{
return platform_driver_register ( & rdc321x_wdt_driver ) ;
}
static void __exit rdc321x_wdt_exit ( void )
{
platform_driver_unregister ( & rdc321x_wdt_driver ) ;
}
module_init ( rdc321x_wdt_init ) ;
module_exit ( rdc321x_wdt_exit ) ;
MODULE_AUTHOR ( " Florian Fainelli <florian@openwrt.org> " ) ;
MODULE_DESCRIPTION ( " RDC321x watchdog driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;