2008-02-25 14:59:26 +03:00
/*
* RDC321x watchdog driver
*
2010-03-23 05:56:24 +03:00
* Copyright ( C ) 2007 - 2010 Florian Fainelli < florian @ openwrt . org >
2008-02-25 14:59:26 +03:00
*
* 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/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>
2010-03-23 05:56:24 +03:00
# include <linux/mfd/rdc321x.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 ;
2010-03-23 05:56:24 +03:00
struct pci_dev * sb_pdev ;
int base_reg ;
2008-02-25 14:59:26 +03:00
} rdc321x_wdt_device ;
/* generic helper functions */
static void rdc321x_wdt_trigger ( unsigned long unused )
{
unsigned long flags ;
2010-03-23 05:56:24 +03:00
u32 val ;
2008-02-25 14:59:26 +03:00
if ( rdc321x_wdt_device . running )
ticks - - ;
/* keep watchdog alive */
spin_lock_irqsave ( & rdc321x_wdt_device . lock , flags ) ;
2010-03-23 05:56:24 +03:00
pci_read_config_dword ( rdc321x_wdt_device . sb_pdev ,
rdc321x_wdt_device . base_reg , & val ) ;
val | = RDC_WDT_EN ;
pci_write_config_dword ( rdc321x_wdt_device . sb_pdev ,
rdc321x_wdt_device . base_reg , val ) ;
2008-02-25 14:59:26 +03:00
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 ) ;
2010-03-23 05:56:24 +03:00
pci_write_config_dword ( rdc321x_wdt_device . sb_pdev ,
rdc321x_wdt_device . base_reg , RDC_CLS_TMR ) ;
2008-02-25 14:59:26 +03:00
/* Enable watchdog and set the timeout to 81.92 us */
2010-03-23 05:56:24 +03:00
pci_write_config_dword ( rdc321x_wdt_device . sb_pdev ,
rdc321x_wdt_device . base_reg ,
RDC_WDT_EN | RDC_WDT_CNT ) ;
2008-02-25 14:59:26 +03:00
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 ;
2010-03-23 05:56:24 +03:00
u32 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 ) ;
2010-03-23 05:56:24 +03:00
pci_read_config_dword ( rdc321x_wdt_device . sb_pdev ,
rdc321x_wdt_device . base_reg , & value ) ;
2008-02-25 14:59:26 +03:00
spin_unlock_irqrestore ( & rdc321x_wdt_device . lock , flags ) ;
2010-03-23 05:56:24 +03:00
if ( copy_to_user ( argp , & value , sizeof ( u32 ) ) )
2008-02-25 14:59:26 +03:00
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 ,
} ;
2012-11-19 22:21:41 +04:00
static int rdc321x_wdt_probe ( struct platform_device * pdev )
2008-02-25 14:59:26 +03:00
{
int err ;
2010-03-23 05:56:24 +03:00
struct resource * r ;
struct rdc321x_wdt_pdata * pdata ;
2013-07-30 14:58:51 +04:00
pdata = dev_get_platdata ( & pdev - > dev ) ;
2010-03-23 05:56:24 +03:00
if ( ! pdata ) {
dev_err ( & pdev - > dev , " no platform data supplied \n " ) ;
return - ENODEV ;
}
2010-05-16 00:58:27 +04:00
r = platform_get_resource_byname ( pdev , IORESOURCE_IO , " wdt-reg " ) ;
2010-03-23 05:56:24 +03:00
if ( ! r ) {
dev_err ( & pdev - > dev , " failed to get wdt-reg resource \n " ) ;
return - ENODEV ;
}
rdc321x_wdt_device . sb_pdev = pdata - > sb_pdev ;
rdc321x_wdt_device . base_reg = r - > start ;
2008-02-25 14:59:26 +03:00
err = misc_register ( & rdc321x_wdt_misc ) ;
if ( err < 0 ) {
2010-03-23 05:56:24 +03:00
dev_err ( & pdev - > dev , " misc_register failed \n " ) ;
2008-02-25 14:59:26 +03:00
return err ;
}
spin_lock_init ( & rdc321x_wdt_device . lock ) ;
/* Reset the watchdog */
2010-03-23 05:56:24 +03:00
pci_write_config_dword ( rdc321x_wdt_device . sb_pdev ,
rdc321x_wdt_device . base_reg , RDC_WDT_RST ) ;
2008-02-25 14:59:26 +03:00
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 ;
2010-03-23 05:56:24 +03:00
dev_info ( & pdev - > dev , " watchdog init success \n " ) ;
2008-02-25 14:59:26 +03:00
return 0 ;
}
2012-11-19 22:26:24 +04:00
static int 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 ,
2012-11-19 22:21:12 +04:00
. remove = rdc321x_wdt_remove ,
2008-02-25 14:59:26 +03:00
. driver = {
. name = " rdc321x-wdt " ,
} ,
} ;
2011-11-29 09:56:27 +04:00
module_platform_driver ( rdc321x_wdt_driver ) ;
2008-02-25 14:59:26 +03:00
MODULE_AUTHOR ( " Florian Fainelli <florian@openwrt.org> " ) ;
MODULE_DESCRIPTION ( " RDC321x watchdog driver " ) ;
MODULE_LICENSE ( " GPL " ) ;