2006-01-15 00:20:50 +03:00
/*
2008-07-04 10:51:36 +04:00
* mpc8xxx_wdt . c - MPC8xx / MPC83xx / MPC86xx watchdog userspace interface
2006-01-15 00:20:50 +03:00
*
* Authors : Dave Updegraff < dave @ cray . org >
* Kumar Gala < galak @ kernel . crashing . org >
* Attribution : from 83 xx_wst : Florian Schirmer < jolt @ tuxbox . org >
* . . and from sc520_wdt
2008-07-04 10:51:34 +04:00
* Copyright ( c ) 2008 MontaVista Software , Inc .
* Anton Vorontsov < avorontsov @ ru . mvista . com >
2006-01-15 00:20:50 +03:00
*
* Note : it appears that you can only actually ENABLE or DISABLE the thing
* once after POR . Once enabled , you cannot disable , and vice versa .
*
* 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 .
*/
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/kernel.h>
2008-07-04 10:51:34 +04:00
# include <linux/timer.h>
2006-01-15 00:20:50 +03:00
# include <linux/miscdevice.h>
2008-07-04 10:51:32 +04:00
# include <linux/of_platform.h>
2006-01-15 00:20:50 +03:00
# include <linux/module.h>
# include <linux/watchdog.h>
2008-05-19 17:07:09 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2008-07-04 10:51:32 +04:00
# include <sysdev/fsl_soc.h>
2006-01-15 00:20:50 +03:00
2008-07-04 10:51:35 +04:00
struct mpc8xxx_wdt {
2006-01-15 00:20:50 +03:00
__be32 res0 ;
__be32 swcrr ; /* System watchdog control register */
# define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */
# define SWCRR_SWEN 0x00000004 /* Watchdog Enable bit. */
# define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/
# define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */
__be32 swcnr ; /* System watchdog count register */
u8 res1 [ 2 ] ;
__be16 swsrr ; /* System watchdog service register */
u8 res2 [ 0xF0 ] ;
} ;
2008-07-04 10:51:35 +04:00
struct mpc8xxx_wdt_type {
2008-07-04 10:51:34 +04:00
int prescaler ;
bool hw_enabled ;
} ;
2008-07-04 10:51:35 +04:00
static struct mpc8xxx_wdt __iomem * wd_base ;
2008-08-21 03:38:36 +04:00
static int mpc8xxx_wdt_init_late ( void ) ;
2006-01-15 00:20:50 +03:00
static u16 timeout = 0xffff ;
module_param ( timeout , ushort , 0 ) ;
2008-05-19 17:07:09 +04:00
MODULE_PARM_DESC ( timeout ,
" Watchdog timeout in ticks. (0<timeout<65536, default=65535 " ) ;
2006-01-15 00:20:50 +03:00
static int reset = 1 ;
module_param ( reset , bool , 0 ) ;
2008-05-19 17:07:09 +04:00
MODULE_PARM_DESC ( reset ,
" Watchdog Interrupt/Reset Mode. 0 = interrupt, 1 = reset " ) ;
2006-01-15 00:20:50 +03:00
2008-07-04 10:51:34 +04:00
static int nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , int , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started "
" (default= " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2006-01-15 00:20:50 +03:00
/*
* We always prescale , but if someone really doesn ' t want to they can set this
* to 0
*/
static int prescale = 1 ;
static unsigned int timeout_sec ;
static unsigned long wdt_is_open ;
2007-11-02 02:27:08 +03:00
static DEFINE_SPINLOCK ( wdt_spinlock ) ;
2006-01-15 00:20:50 +03:00
2008-07-04 10:51:35 +04:00
static void mpc8xxx_wdt_keepalive ( void )
2006-01-15 00:20:50 +03:00
{
/* Ping the WDT */
spin_lock ( & wdt_spinlock ) ;
out_be16 ( & wd_base - > swsrr , 0x556c ) ;
out_be16 ( & wd_base - > swsrr , 0xaa39 ) ;
spin_unlock ( & wdt_spinlock ) ;
}
2008-07-04 10:51:35 +04:00
static void mpc8xxx_wdt_timer_ping ( unsigned long arg ) ;
static DEFINE_TIMER ( wdt_timer , mpc8xxx_wdt_timer_ping , 0 , 0 ) ;
2008-07-04 10:51:34 +04:00
2008-07-04 10:51:35 +04:00
static void mpc8xxx_wdt_timer_ping ( unsigned long arg )
2008-07-04 10:51:34 +04:00
{
2008-07-04 10:51:35 +04:00
mpc8xxx_wdt_keepalive ( ) ;
2008-07-04 10:51:34 +04:00
/* We're pinging it twice faster than needed, just to be sure. */
mod_timer ( & wdt_timer , jiffies + HZ * timeout_sec / 2 ) ;
}
2008-07-04 10:51:35 +04:00
static void mpc8xxx_wdt_pr_warn ( const char * msg )
2008-07-04 10:51:34 +04:00
{
2008-07-04 10:51:35 +04:00
pr_crit ( " mpc8xxx_wdt: %s, expect the %s soon! \n " , msg ,
2008-07-04 10:51:34 +04:00
reset ? " reset " : " machine check exception " ) ;
}
2008-07-04 10:51:35 +04:00
static ssize_t mpc8xxx_wdt_write ( struct file * file , const char __user * buf ,
2006-01-15 00:20:50 +03:00
size_t count , loff_t * ppos )
{
if ( count )
2008-07-04 10:51:35 +04:00
mpc8xxx_wdt_keepalive ( ) ;
2006-01-15 00:20:50 +03:00
return count ;
}
2008-07-04 10:51:35 +04:00
static int mpc8xxx_wdt_open ( struct inode * inode , struct file * file )
2006-01-15 00:20:50 +03:00
{
u32 tmp = SWCRR_SWEN ;
if ( test_and_set_bit ( 0 , & wdt_is_open ) )
return - EBUSY ;
/* Once we start the watchdog we can't stop it */
2008-07-04 10:51:34 +04:00
if ( nowayout )
__module_get ( THIS_MODULE ) ;
2006-01-15 00:20:50 +03:00
/* Good, fire up the show */
if ( prescale )
tmp | = SWCRR_SWPR ;
if ( reset )
tmp | = SWCRR_SWRI ;
tmp | = timeout < < 16 ;
out_be32 ( & wd_base - > swcrr , tmp ) ;
2008-07-04 10:51:34 +04:00
del_timer_sync ( & wdt_timer ) ;
2006-01-15 00:20:50 +03:00
return nonseekable_open ( inode , file ) ;
}
2008-07-04 10:51:35 +04:00
static int mpc8xxx_wdt_release ( struct inode * inode , struct file * file )
2006-01-15 00:20:50 +03:00
{
2008-07-04 10:51:34 +04:00
if ( ! nowayout )
2008-07-04 10:51:35 +04:00
mpc8xxx_wdt_timer_ping ( 0 ) ;
2008-07-04 10:51:34 +04:00
else
2008-07-04 10:51:35 +04:00
mpc8xxx_wdt_pr_warn ( " watchdog closed " ) ;
2006-01-15 00:20:50 +03:00
clear_bit ( 0 , & wdt_is_open ) ;
return 0 ;
}
2008-07-04 10:51:36 +04:00
static long mpc8xxx_wdt_ioctl ( struct file * file , unsigned int cmd ,
2008-05-19 17:07:09 +04:00
unsigned long arg )
2006-01-15 00:20:50 +03:00
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
static struct watchdog_info ident = {
. options = WDIOF_KEEPALIVEPING ,
. firmware_version = 1 ,
2008-07-04 10:51:35 +04:00
. identity = " MPC8xxx " ,
2006-01-15 00:20:50 +03:00
} ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
2007-07-21 17:42:18 +04:00
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
2006-01-15 00:20:50 +03:00
case WDIOC_KEEPALIVE :
2008-07-04 10:51:35 +04:00
mpc8xxx_wdt_keepalive ( ) ;
2006-01-15 00:20:50 +03:00
return 0 ;
case WDIOC_GETTIMEOUT :
return put_user ( timeout_sec , p ) ;
default :
2006-09-09 19:34:31 +04:00
return - ENOTTY ;
2006-01-15 00:20:50 +03:00
}
}
2008-07-04 10:51:35 +04:00
static const struct file_operations mpc8xxx_wdt_fops = {
2006-01-15 00:20:50 +03:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
2008-07-04 10:51:35 +04:00
. write = mpc8xxx_wdt_write ,
. unlocked_ioctl = mpc8xxx_wdt_ioctl ,
. open = mpc8xxx_wdt_open ,
. release = mpc8xxx_wdt_release ,
2006-01-15 00:20:50 +03:00
} ;
2008-07-04 10:51:35 +04:00
static struct miscdevice mpc8xxx_wdt_miscdev = {
2006-01-15 00:20:50 +03:00
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
2008-07-04 10:51:35 +04:00
. fops = & mpc8xxx_wdt_fops ,
2006-01-15 00:20:50 +03:00
} ;
2008-07-04 10:51:35 +04:00
static int __devinit mpc8xxx_wdt_probe ( struct of_device * ofdev ,
2008-07-04 10:51:32 +04:00
const struct of_device_id * match )
2006-01-15 00:20:50 +03:00
{
int ret ;
2008-07-04 10:51:34 +04:00
struct device_node * np = ofdev - > node ;
2008-07-04 10:51:35 +04:00
struct mpc8xxx_wdt_type * wdt_type = match - > data ;
2008-07-04 10:51:32 +04:00
u32 freq = fsl_get_sys_freq ( ) ;
2008-07-04 10:51:34 +04:00
bool enabled ;
2006-01-15 00:20:50 +03:00
2008-07-04 10:51:32 +04:00
if ( ! freq | | freq = = - 1 )
return - EINVAL ;
2006-01-15 00:20:50 +03:00
2008-07-04 10:51:34 +04:00
wd_base = of_iomap ( np , 0 ) ;
2008-07-04 10:51:32 +04:00
if ( ! wd_base )
return - ENOMEM ;
2006-01-15 00:20:50 +03:00
2008-07-04 10:51:34 +04:00
enabled = in_be32 ( & wd_base - > swcrr ) & SWCRR_SWEN ;
if ( ! enabled & & wdt_type - > hw_enabled ) {
2008-07-04 10:51:35 +04:00
pr_info ( " mpc8xxx_wdt: could not be enabled in software \n " ) ;
2008-07-04 10:51:34 +04:00
ret = - ENOSYS ;
goto err_unmap ;
}
2006-01-15 00:20:50 +03:00
/* Calculate the timeout in seconds */
if ( prescale )
2008-07-04 10:51:34 +04:00
timeout_sec = ( timeout * wdt_type - > prescaler ) / freq ;
2006-01-15 00:20:50 +03:00
else
2008-07-04 10:51:32 +04:00
timeout_sec = timeout / freq ;
2006-01-15 00:20:50 +03:00
2008-08-21 03:38:36 +04:00
# ifdef MODULE
ret = mpc8xxx_wdt_init_late ( ) ;
if ( ret )
goto err_unmap ;
# endif
2008-07-04 10:51:35 +04:00
pr_info ( " WDT driver for MPC8xxx initialized. mode:%s timeout=%d "
2008-07-04 10:51:32 +04:00
" (%d seconds) \n " , reset ? " reset " : " interrupt " , timeout ,
timeout_sec ) ;
2008-07-04 10:51:34 +04:00
/*
* If the watchdog was previously enabled or we ' re running on
2008-07-04 10:51:35 +04:00
* MPC8xxx , we should ping the wdt from the kernel until the
2008-07-04 10:51:34 +04:00
* userspace handles it .
*/
if ( enabled )
2008-07-04 10:51:35 +04:00
mpc8xxx_wdt_timer_ping ( 0 ) ;
2006-01-15 00:20:50 +03:00
return 0 ;
err_unmap :
iounmap ( wd_base ) ;
2008-07-04 10:51:36 +04:00
wd_base = NULL ;
2006-01-15 00:20:50 +03:00
return ret ;
}
2008-07-04 10:51:35 +04:00
static int __devexit mpc8xxx_wdt_remove ( struct of_device * ofdev )
2006-01-15 00:20:50 +03:00
{
2008-07-04 10:51:35 +04:00
mpc8xxx_wdt_pr_warn ( " watchdog removed " ) ;
2008-07-04 10:51:34 +04:00
del_timer_sync ( & wdt_timer ) ;
2008-07-04 10:51:35 +04:00
misc_deregister ( & mpc8xxx_wdt_miscdev ) ;
2006-01-15 00:20:50 +03:00
iounmap ( wd_base ) ;
return 0 ;
}
2008-07-04 10:51:35 +04:00
static const struct of_device_id mpc8xxx_wdt_match [ ] = {
2008-07-04 10:51:32 +04:00
{
. compatible = " mpc83xx_wdt " ,
2008-07-04 10:51:35 +04:00
. data = & ( struct mpc8xxx_wdt_type ) {
2008-07-04 10:51:34 +04:00
. prescaler = 0x10000 ,
} ,
} ,
{
. compatible = " fsl,mpc8610-wdt " ,
2008-07-04 10:51:35 +04:00
. data = & ( struct mpc8xxx_wdt_type ) {
2008-07-04 10:51:34 +04:00
. prescaler = 0x10000 ,
. hw_enabled = true ,
} ,
2008-07-04 10:51:32 +04:00
} ,
2008-07-04 10:51:36 +04:00
{
. compatible = " fsl,mpc823-wdt " ,
. data = & ( struct mpc8xxx_wdt_type ) {
. prescaler = 0x800 ,
} ,
} ,
2008-07-04 10:51:32 +04:00
{ } ,
} ;
2008-07-04 10:51:35 +04:00
MODULE_DEVICE_TABLE ( of , mpc8xxx_wdt_match ) ;
2008-07-04 10:51:32 +04:00
2008-07-04 10:51:35 +04:00
static struct of_platform_driver mpc8xxx_wdt_driver = {
. match_table = mpc8xxx_wdt_match ,
. probe = mpc8xxx_wdt_probe ,
. remove = __devexit_p ( mpc8xxx_wdt_remove ) ,
2006-01-15 00:20:50 +03:00
. driver = {
2008-07-04 10:51:35 +04:00
. name = " mpc8xxx_wdt " ,
2008-04-11 08:29:23 +04:00
. owner = THIS_MODULE ,
2006-01-15 00:20:50 +03:00
} ,
} ;
2008-07-04 10:51:36 +04:00
/*
* We do wdt initialization in two steps : arch_initcall probes the wdt
* very early to start pinging the watchdog ( misc devices are not yet
* available ) , and later module_init ( ) just registers the misc device .
*/
2008-08-21 03:38:36 +04:00
static int mpc8xxx_wdt_init_late ( void )
2008-07-04 10:51:36 +04:00
{
int ret ;
if ( ! wd_base )
return - ENODEV ;
ret = misc_register ( & mpc8xxx_wdt_miscdev ) ;
if ( ret ) {
pr_err ( " cannot register miscdev on minor=%d (err=%d) \n " ,
WATCHDOG_MINOR , ret ) ;
return ret ;
}
return 0 ;
}
2008-08-21 03:38:36 +04:00
# ifndef MODULE
2008-07-04 10:51:36 +04:00
module_init ( mpc8xxx_wdt_init_late ) ;
2008-08-21 03:38:36 +04:00
# endif
2008-07-04 10:51:36 +04:00
2008-07-04 10:51:35 +04:00
static int __init mpc8xxx_wdt_init ( void )
2006-01-15 00:20:50 +03:00
{
2008-07-04 10:51:35 +04:00
return of_register_platform_driver ( & mpc8xxx_wdt_driver ) ;
2006-01-15 00:20:50 +03:00
}
2008-07-04 10:51:36 +04:00
arch_initcall ( mpc8xxx_wdt_init ) ;
2006-01-15 00:20:50 +03:00
2008-07-04 10:51:35 +04:00
static void __exit mpc8xxx_wdt_exit ( void )
2006-01-15 00:20:50 +03:00
{
2008-07-04 10:51:35 +04:00
of_unregister_platform_driver ( & mpc8xxx_wdt_driver ) ;
2006-01-15 00:20:50 +03:00
}
2008-07-04 10:51:35 +04:00
module_exit ( mpc8xxx_wdt_exit ) ;
2006-01-15 00:20:50 +03:00
MODULE_AUTHOR ( " Dave Updegraff, Kumar Gala " ) ;
2008-07-04 10:51:36 +04:00
MODULE_DESCRIPTION ( " Driver for watchdog timer in MPC8xx/MPC83xx/MPC86xx "
" uProcessors " ) ;
2006-01-15 00:20:50 +03:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;