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 >
2011-02-23 23:04:38 +03:00
* 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 .
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2006-01-15 00:20:50 +03:00
# 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>
2013-09-17 23:28:33 +04:00
# include <linux/of_address.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 ,
2010-05-01 20:46:15 +04:00
" Watchdog timeout in ticks. (0<timeout<65536, default=65535) " ) ;
2006-01-15 00:20:50 +03:00
2012-01-13 03:02:20 +04:00
static bool reset = 1 ;
2006-01-15 00:20:50 +03:00
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
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-07-04 10:51:34 +04:00
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 ;
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 ) ;
}
2013-12-04 10:32:14 +04:00
static struct watchdog_device mpc8xxx_wdt_dev ;
2008-07-04 10:51:35 +04:00
static void mpc8xxx_wdt_timer_ping ( unsigned long arg ) ;
2013-12-04 10:32:14 +04:00
static DEFINE_TIMER ( wdt_timer , mpc8xxx_wdt_timer_ping , 0 ,
( unsigned long ) & mpc8xxx_wdt_dev ) ;
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
{
2013-12-04 10:32:14 +04:00
struct watchdog_device * w = ( struct watchdog_device * ) arg ;
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. */
2013-12-04 10:32:14 +04:00
mod_timer ( & wdt_timer , jiffies + HZ * w - > timeout / 2 ) ;
2008-07-04 10:51:34 +04:00
}
2013-12-04 10:32:14 +04:00
static int mpc8xxx_wdt_start ( struct watchdog_device * w )
2006-01-15 00:20:50 +03:00
{
u32 tmp = SWCRR_SWEN ;
/* 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 ) ;
2013-12-04 10:32:14 +04:00
return 0 ;
2006-01-15 00:20:50 +03:00
}
2013-12-04 10:32:14 +04:00
static int mpc8xxx_wdt_ping ( struct watchdog_device * w )
2006-01-15 00:20:50 +03:00
{
2013-12-04 10:32:14 +04:00
mpc8xxx_wdt_keepalive ( ) ;
2006-01-15 00:20:50 +03:00
return 0 ;
}
2013-12-04 10:32:14 +04:00
static int mpc8xxx_wdt_stop ( struct watchdog_device * w )
2006-01-15 00:20:50 +03:00
{
2013-12-04 10:32:14 +04:00
mod_timer ( & wdt_timer , jiffies ) ;
return 0 ;
2006-01-15 00:20:50 +03:00
}
2013-12-04 10:32:14 +04:00
static struct watchdog_info mpc8xxx_wdt_info = {
. options = WDIOF_KEEPALIVEPING ,
. firmware_version = 1 ,
. identity = " MPC8xxx " ,
2006-01-15 00:20:50 +03:00
} ;
2013-12-04 10:32:14 +04:00
static struct watchdog_ops mpc8xxx_wdt_ops = {
. owner = THIS_MODULE ,
. start = mpc8xxx_wdt_start ,
. ping = mpc8xxx_wdt_ping ,
. stop = mpc8xxx_wdt_stop ,
} ;
static struct watchdog_device mpc8xxx_wdt_dev = {
. info = & mpc8xxx_wdt_info ,
. ops = & mpc8xxx_wdt_ops ,
2006-01-15 00:20:50 +03:00
} ;
2011-05-18 21:19:24 +04:00
static const struct of_device_id mpc8xxx_wdt_match [ ] ;
2012-11-19 22:21:41 +04:00
static int mpc8xxx_wdt_probe ( struct platform_device * ofdev )
2006-01-15 00:20:50 +03:00
{
int ret ;
2011-05-18 21:19:24 +04:00
const struct of_device_id * match ;
2010-06-02 12:25:52 +04:00
struct device_node * np = ofdev - > dev . of_node ;
2012-05-21 23:57:39 +04:00
const struct mpc8xxx_wdt_type * wdt_type ;
2008-07-04 10:51:32 +04:00
u32 freq = fsl_get_sys_freq ( ) ;
2008-07-04 10:51:34 +04:00
bool enabled ;
2013-12-04 10:32:14 +04:00
unsigned int timeout_sec ;
2006-01-15 00:20:50 +03:00
2011-05-18 21:19:24 +04:00
match = of_match_device ( mpc8xxx_wdt_match , & ofdev - > dev ) ;
if ( ! match )
2011-02-17 12:43:24 +03:00
return - EINVAL ;
2011-05-18 21:19:24 +04:00
wdt_type = match - > data ;
2011-02-17 12:43:24 +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 ) {
2012-02-16 03:06:19 +04:00
pr_info ( " 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
2013-12-04 10:32:14 +04:00
mpc8xxx_wdt_dev . timeout = timeout_sec ;
2008-08-21 03:38:36 +04:00
# ifdef MODULE
ret = mpc8xxx_wdt_init_late ( ) ;
if ( ret )
goto err_unmap ;
# endif
2012-02-16 03:06:19 +04:00
pr_info ( " WDT driver for MPC8xxx initialized. mode:%s timeout=%d (%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 )
2013-12-04 10:32:14 +04:00
mod_timer ( & wdt_timer , jiffies ) ;
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 ;
}
2012-11-19 22:26:24 +04:00
static int mpc8xxx_wdt_remove ( struct platform_device * ofdev )
2006-01-15 00:20:50 +03:00
{
2013-12-04 10:32:14 +04:00
pr_crit ( " Watchdog removed, expect the %s soon! \n " ,
reset ? " reset " : " machine check exception " ) ;
2008-07-04 10:51:34 +04:00
del_timer_sync ( & wdt_timer ) ;
2013-12-04 10:32:14 +04:00
watchdog_unregister_device ( & mpc8xxx_wdt_dev ) ;
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 ,
2013-11-30 19:45:40 +04:00
. hw_enabled = true ,
2008-07-04 10:51:36 +04:00
} ,
} ,
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
2011-02-17 12:43:24 +03:00
static struct platform_driver mpc8xxx_wdt_driver = {
2008-07-04 10:51:35 +04:00
. probe = mpc8xxx_wdt_probe ,
2012-11-19 22:21:12 +04:00
. remove = mpc8xxx_wdt_remove ,
2010-04-14 03:13:02 +04:00
. driver = {
. name = " mpc8xxx_wdt " ,
. of_match_table = mpc8xxx_wdt_match ,
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 ;
2013-12-04 10:32:14 +04:00
watchdog_set_nowayout ( & mpc8xxx_wdt_dev , nowayout ) ;
ret = watchdog_register_device ( & mpc8xxx_wdt_dev ) ;
2008-07-04 10:51:36 +04:00
if ( ret ) {
2013-12-04 10:32:14 +04:00
pr_err ( " cannot register watchdog device (err=%d) \n " , ret ) ;
2008-07-04 10:51:36 +04:00
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
{
2011-02-17 12:43:24 +03:00
return platform_driver_register ( & 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
{
2011-02-17 12:43:24 +03:00
platform_driver_unregister ( & 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 " ) ;