2008-09-24 04:26:26 +04:00
/*
* PIKA FPGA based Watchdog Timer
*
* Copyright ( c ) 2008 PIKA Technologies
* Sean MacLennan < smaclennan @ pikatech . com >
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2008-09-24 04:26:26 +04:00
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/fs.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/reboot.h>
# include <linux/jiffies.h>
# include <linux/timer.h>
# include <linux/bitops.h>
# include <linux/uaccess.h>
# include <linux/io.h>
# include <linux/of_platform.h>
# define DRV_NAME "PIKA-WDT"
/* Hardware timeout in seconds */
# define WDT_HW_TIMEOUT 2
/* Timer heartbeat (500ms) */
# define WDT_TIMEOUT (HZ / 2)
/* User land timeout */
# define WDT_HEARTBEAT 15
static int heartbeat = WDT_HEARTBEAT ;
module_param ( heartbeat , int , 0 ) ;
MODULE_PARM_DESC ( heartbeat , " Watchdog heartbeats in seconds. "
" (default = " __MODULE_STRING ( WDT_HEARTBEAT ) " ) " ) ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-09-24 04:26:26 +04:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started "
" (default= " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static struct {
void __iomem * fpga ;
unsigned long next_heartbeat ; /* the next_heartbeat for the timer */
unsigned long open ;
char expect_close ;
int bootstatus ;
struct timer_list timer ; /* The timer that pings the watchdog */
} pikawdt_private ;
2010-03-09 03:46:41 +03:00
static struct watchdog_info ident = {
2008-09-24 04:26:26 +04:00
. identity = DRV_NAME ,
. options = WDIOF_CARDRESET |
WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
} ;
/*
* Reload the watchdog timer . ( ie , pat the watchdog )
*/
static inline void pikawdt_reset ( void )
{
/* -- FPGA: Reset Control Register (32bit R/W) (Offset: 0x14) --
* Bit 7 , WTCHDG_EN : When set to 1 , the watchdog timer is enabled .
* Once enabled , it cannot be disabled . The watchdog can be
* kicked by performing any write access to the reset
* control register ( this register ) .
* Bit 8 - 11 , WTCHDG_TIMEOUT_SEC : Sets the watchdog timeout value in
* seconds . Valid ranges are 1 to 15 seconds . The value can
* be modified dynamically .
*/
unsigned reset = in_be32 ( pikawdt_private . fpga + 0x14 ) ;
/* enable with max timeout - 15 seconds */
reset | = ( 1 < < 7 ) + ( WDT_HW_TIMEOUT < < 8 ) ;
out_be32 ( pikawdt_private . fpga + 0x14 , reset ) ;
}
/*
* Timer tick
*/
static void pikawdt_ping ( unsigned long data )
{
if ( time_before ( jiffies , pikawdt_private . next_heartbeat ) | |
( ! nowayout & & ! pikawdt_private . open ) ) {
pikawdt_reset ( ) ;
mod_timer ( & pikawdt_private . timer , jiffies + WDT_TIMEOUT ) ;
} else
2012-02-16 03:06:19 +04:00
pr_crit ( " I will reset your machine ! \n " ) ;
2008-09-24 04:26:26 +04:00
}
static void pikawdt_keepalive ( void )
{
pikawdt_private . next_heartbeat = jiffies + heartbeat * HZ ;
}
static void pikawdt_start ( void )
{
pikawdt_keepalive ( ) ;
mod_timer ( & pikawdt_private . timer , jiffies + WDT_TIMEOUT ) ;
}
/*
* Watchdog device is opened , and watchdog starts running .
*/
static int pikawdt_open ( struct inode * inode , struct file * file )
{
/* /dev/watchdog can only be opened once */
if ( test_and_set_bit ( 0 , & pikawdt_private . open ) )
return - EBUSY ;
pikawdt_start ( ) ;
return nonseekable_open ( inode , file ) ;
}
/*
* Close the watchdog device .
*/
static int pikawdt_release ( struct inode * inode , struct file * file )
{
/* stop internal ping */
if ( ! pikawdt_private . expect_close )
del_timer ( & pikawdt_private . timer ) ;
clear_bit ( 0 , & pikawdt_private . open ) ;
pikawdt_private . expect_close = 0 ;
return 0 ;
}
/*
* Pat the watchdog whenever device is written to .
*/
static ssize_t pikawdt_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
{
if ( ! len )
return 0 ;
/* Scan for magic character */
if ( ! nowayout ) {
size_t i ;
pikawdt_private . expect_close = 0 ;
for ( i = 0 ; i < len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' ) {
pikawdt_private . expect_close = 42 ;
break ;
}
}
}
pikawdt_keepalive ( ) ;
return len ;
}
/*
* Handle commands from user - space .
*/
static long pikawdt_ioctl ( struct file * file ,
unsigned int cmd , unsigned long arg )
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int new_value ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_GETBOOTSTATUS :
return put_user ( pikawdt_private . bootstatus , p ) ;
case WDIOC_KEEPALIVE :
pikawdt_keepalive ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_value , p ) )
return - EFAULT ;
heartbeat = new_value ;
pikawdt_keepalive ( ) ;
return put_user ( new_value , p ) ; /* return current value */
case WDIOC_GETTIMEOUT :
return put_user ( heartbeat , p ) ;
}
return - ENOTTY ;
}
static const struct file_operations pikawdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. open = pikawdt_open ,
. release = pikawdt_release ,
. write = pikawdt_write ,
. unlocked_ioctl = pikawdt_ioctl ,
} ;
static struct miscdevice pikawdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & pikawdt_fops ,
} ;
static int __init pikawdt_init ( void )
{
struct device_node * np ;
void __iomem * fpga ;
static u32 post1 ;
int ret ;
np = of_find_compatible_node ( NULL , NULL , " pika,fpga " ) ;
if ( np = = NULL ) {
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to find fpga \n " ) ;
2008-09-24 04:26:26 +04:00
return - ENOENT ;
}
pikawdt_private . fpga = of_iomap ( np , 0 ) ;
of_node_put ( np ) ;
if ( pikawdt_private . fpga = = NULL ) {
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to map fpga \n " ) ;
2008-09-24 04:26:26 +04:00
return - ENOMEM ;
}
ident . firmware_version = in_be32 ( pikawdt_private . fpga + 0x1c ) & 0xffff ;
/* POST information is in the sd area. */
np = of_find_compatible_node ( NULL , NULL , " pika,fpga-sd " ) ;
if ( np = = NULL ) {
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to find fpga-sd \n " ) ;
2008-09-24 04:26:26 +04:00
ret = - ENOENT ;
goto out ;
}
fpga = of_iomap ( np , 0 ) ;
of_node_put ( np ) ;
if ( fpga = = NULL ) {
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to map fpga-sd \n " ) ;
2008-09-24 04:26:26 +04:00
ret = - ENOMEM ;
goto out ;
}
/* -- FPGA: POST Test Results Register 1 (32bit R/W) (Offset: 0x4040) --
* Bit 31 , WDOG : Set to 1 when the last reset was caused by a watchdog
* timeout .
*/
post1 = in_be32 ( fpga + 0x40 ) ;
if ( post1 & 0x80000000 )
pikawdt_private . bootstatus = WDIOF_CARDRESET ;
iounmap ( fpga ) ;
setup_timer ( & pikawdt_private . timer , pikawdt_ping , 0 ) ;
ret = misc_register ( & pikawdt_miscdev ) ;
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " Unable to register miscdev \n " ) ;
2008-09-24 04:26:26 +04:00
goto out ;
}
2012-02-16 03:06:19 +04:00
pr_info ( " initialized. heartbeat=%d sec (nowayout=%d) \n " ,
heartbeat , nowayout ) ;
2008-09-24 04:26:26 +04:00
return 0 ;
out :
iounmap ( pikawdt_private . fpga ) ;
return ret ;
}
static void __exit pikawdt_exit ( void )
{
misc_deregister ( & pikawdt_miscdev ) ;
iounmap ( pikawdt_private . fpga ) ;
}
module_init ( pikawdt_init ) ;
module_exit ( pikawdt_exit ) ;
MODULE_AUTHOR ( " Sean MacLennan <smaclennan@pikatech.com> " ) ;
MODULE_DESCRIPTION ( " PIKA FPGA based Watchdog Timer " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;