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 ;
} ;
2015-08-12 11:15:56 +03:00
struct mpc8xxx_wdt_ddata {
struct mpc8xxx_wdt __iomem * base ;
struct watchdog_device wdd ;
struct timer_list timer ;
spinlock_t lock ;
} ;
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 ) " ) " ) ;
2015-08-12 11:15:56 +03:00
static void mpc8xxx_wdt_keepalive ( struct mpc8xxx_wdt_ddata * ddata )
2006-01-15 00:20:50 +03:00
{
/* Ping the WDT */
2015-08-12 11:15:56 +03:00
spin_lock ( & ddata - > lock ) ;
out_be16 ( & ddata - > base - > swsrr , 0x556c ) ;
out_be16 ( & ddata - > base - > swsrr , 0xaa39 ) ;
spin_unlock ( & ddata - > lock ) ;
2006-01-15 00:20:50 +03: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
{
2015-08-12 11:15:56 +03:00
struct mpc8xxx_wdt_ddata * ddata = ( void * ) arg ;
2013-12-04 10:32:14 +04:00
2015-08-12 11:15:56 +03:00
mpc8xxx_wdt_keepalive ( ddata ) ;
2008-07-04 10:51:34 +04:00
/* We're pinging it twice faster than needed, just to be sure. */
2015-08-12 11:15:56 +03:00
mod_timer ( & ddata - > timer , jiffies + HZ * ddata - > wdd . 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
{
2015-08-12 11:15:56 +03:00
struct mpc8xxx_wdt_ddata * ddata =
container_of ( w , struct mpc8xxx_wdt_ddata , wdd ) ;
2015-08-12 11:15:52 +03:00
u32 tmp = SWCRR_SWEN | SWCRR_SWPR ;
2006-01-15 00:20:50 +03:00
/* Good, fire up the show */
if ( reset )
tmp | = SWCRR_SWRI ;
tmp | = timeout < < 16 ;
2015-08-12 11:15:56 +03:00
out_be32 ( & ddata - > base - > swcrr , tmp ) ;
2006-01-15 00:20:50 +03:00
2015-08-12 11:15:56 +03:00
del_timer_sync ( & ddata - > timer ) ;
2008-07-04 10:51:34 +04:00
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
{
2015-08-12 11:15:56 +03:00
struct mpc8xxx_wdt_ddata * ddata =
container_of ( w , struct mpc8xxx_wdt_ddata , wdd ) ;
mpc8xxx_wdt_keepalive ( ddata ) ;
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
{
2015-08-12 11:15:56 +03:00
struct mpc8xxx_wdt_ddata * ddata =
container_of ( w , struct mpc8xxx_wdt_ddata , wdd ) ;
mod_timer ( & ddata - > timer , jiffies ) ;
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 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 ,
} ;
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 ;
2015-08-12 11:15:55 +03:00
struct resource * res ;
2012-05-21 23:57:39 +04:00
const struct mpc8xxx_wdt_type * wdt_type ;
2015-08-12 11:15:56 +03:00
struct mpc8xxx_wdt_ddata * ddata ;
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
2015-08-12 11:15:54 +03:00
wdt_type = of_device_get_match_data ( & ofdev - > dev ) ;
if ( ! wdt_type )
2011-02-17 12:43:24 +03:00
return - EINVAL ;
2008-07-04 10:51:32 +04:00
if ( ! freq | | freq = = - 1 )
return - EINVAL ;
2006-01-15 00:20:50 +03:00
2015-08-12 11:15:56 +03:00
ddata = devm_kzalloc ( & ofdev - > dev , sizeof ( * ddata ) , GFP_KERNEL ) ;
if ( ! ddata )
return - ENOMEM ;
2015-08-12 11:15:55 +03:00
res = platform_get_resource ( ofdev , IORESOURCE_MEM , 0 ) ;
2015-08-12 11:15:56 +03:00
ddata - > base = devm_ioremap_resource ( & ofdev - > dev , res ) ;
if ( IS_ERR ( ddata - > base ) )
return PTR_ERR ( ddata - > base ) ;
2006-01-15 00:20:50 +03:00
2015-08-12 11:15:56 +03:00
enabled = in_be32 ( & ddata - > base - > swcrr ) & SWCRR_SWEN ;
2008-07-04 10:51:34 +04:00
if ( ! enabled & & wdt_type - > hw_enabled ) {
2012-02-16 03:06:19 +04:00
pr_info ( " could not be enabled in software \n " ) ;
2015-08-12 11:15:57 +03:00
return - ENODEV ;
2008-07-04 10:51:34 +04:00
}
2015-08-12 11:15:56 +03:00
spin_lock_init ( & ddata - > lock ) ;
setup_timer ( & ddata - > timer , mpc8xxx_wdt_timer_ping ,
( unsigned long ) ddata ) ;
ddata - > wdd . info = & mpc8xxx_wdt_info ,
ddata - > wdd . ops = & mpc8xxx_wdt_ops ,
2006-01-15 00:20:50 +03:00
/* Calculate the timeout in seconds */
2015-08-12 11:15:52 +03:00
timeout_sec = ( timeout * wdt_type - > prescaler ) / freq ;
2006-01-15 00:20:50 +03:00
2015-08-12 11:15:56 +03:00
ddata - > wdd . timeout = timeout_sec ;
2015-08-12 11:15:53 +03:00
2015-08-12 11:15:56 +03:00
watchdog_set_nowayout ( & ddata - > wdd , nowayout ) ;
2015-08-12 11:15:53 +03:00
2015-08-12 11:15:56 +03:00
ret = watchdog_register_device ( & ddata - > wdd ) ;
2015-08-12 11:15:53 +03:00
if ( ret ) {
pr_err ( " cannot register watchdog device (err=%d) \n " , ret ) ;
2015-08-12 11:15:55 +03:00
return ret ;
2015-08-12 11:15:53 +03:00
}
2008-08-21 03:38:36 +04:00
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 )
2015-08-12 11:15:56 +03:00
mod_timer ( & ddata - > timer , jiffies ) ;
platform_set_drvdata ( ofdev , ddata ) ;
2006-01-15 00:20:50 +03:00
return 0 ;
}
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
{
2015-08-12 11:15:56 +03:00
struct mpc8xxx_wdt_ddata * ddata = platform_get_drvdata ( ofdev ) ;
2013-12-04 10:32:14 +04:00
pr_crit ( " Watchdog removed, expect the %s soon! \n " ,
reset ? " reset " : " machine check exception " ) ;
2015-08-12 11:15:56 +03:00
del_timer_sync ( & ddata - > timer ) ;
watchdog_unregister_device ( & ddata - > wdd ) ;
2006-01-15 00:20:50 +03:00
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: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 " ) ;