2009-06-05 20:57:18 +04:00
/*
* Watchdog driver for Broadcom BCM47XX
*
* Copyright ( C ) 2008 Aleksandar Radovanovic < biblbroks @ sezampro . rs >
* Copyright ( C ) 2009 Matthieu CASTET < castet . matthieu @ free . fr >
2013-01-24 21:13:34 +04:00
* Copyright ( C ) 2012 - 2013 Hauke Mehrtens < hauke @ hauke - m . de >
2009-06-05 20:57:18 +04:00
*
* 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
2013-01-24 21:13:34 +04:00
# include <linux/bcm47xx_wdt.h>
2009-06-05 20:57:18 +04:00
# include <linux/bitops.h>
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
2013-01-24 21:13:34 +04:00
# include <linux/platform_device.h>
2009-06-05 20:57:18 +04:00
# include <linux/reboot.h>
# include <linux/types.h>
# include <linux/watchdog.h>
# include <linux/timer.h>
# include <linux/jiffies.h>
# define DRV_NAME "bcm47xx_wdt"
# define WDT_DEFAULT_TIME 30 /* seconds */
2013-01-12 21:14:09 +04:00
# define WDT_SOFTTIMER_MAX 255 /* seconds */
2013-01-12 21:14:11 +04:00
# define WDT_SOFTTIMER_THRESHOLD 60 /* seconds */
2009-06-05 20:57:18 +04:00
2013-01-12 21:14:10 +04:00
static int timeout = WDT_DEFAULT_TIME ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2009-06-05 20:57:18 +04:00
2013-01-12 21:14:10 +04:00
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout , " Watchdog time in seconds. (default= "
2009-06-05 20:57:18 +04:00
__MODULE_STRING ( WDT_DEFAULT_TIME ) " ) " ) ;
2012-03-05 19:51:11 +04:00
module_param ( nowayout , bool , 0 ) ;
2009-06-05 20:57:18 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2013-01-24 21:13:34 +04:00
static inline struct bcm47xx_wdt * bcm47xx_wdt_get ( struct watchdog_device * wdd )
2009-06-05 20:57:18 +04:00
{
2013-01-24 21:13:34 +04:00
return container_of ( wdd , struct bcm47xx_wdt , wdd ) ;
2009-06-05 20:57:18 +04:00
}
2013-01-12 21:14:11 +04:00
static int bcm47xx_wdt_hard_keepalive ( struct watchdog_device * wdd )
{
struct bcm47xx_wdt * wdt = bcm47xx_wdt_get ( wdd ) ;
wdt - > timer_set_ms ( wdt , wdd - > timeout * 1000 ) ;
return 0 ;
}
static int bcm47xx_wdt_hard_start ( struct watchdog_device * wdd )
{
return 0 ;
}
static int bcm47xx_wdt_hard_stop ( struct watchdog_device * wdd )
{
struct bcm47xx_wdt * wdt = bcm47xx_wdt_get ( wdd ) ;
wdt - > timer_set ( wdt , 0 ) ;
return 0 ;
}
static int bcm47xx_wdt_hard_set_timeout ( struct watchdog_device * wdd ,
unsigned int new_time )
{
struct bcm47xx_wdt * wdt = bcm47xx_wdt_get ( wdd ) ;
u32 max_timer = wdt - > max_timer_ms ;
if ( new_time < 1 | | new_time > max_timer / 1000 ) {
pr_warn ( " timeout value must be 1<=x<=%d, using %d \n " ,
max_timer / 1000 , new_time ) ;
return - EINVAL ;
}
wdd - > timeout = new_time ;
return 0 ;
}
static struct watchdog_ops bcm47xx_wdt_hard_ops = {
. owner = THIS_MODULE ,
. start = bcm47xx_wdt_hard_start ,
. stop = bcm47xx_wdt_hard_stop ,
. ping = bcm47xx_wdt_hard_keepalive ,
. set_timeout = bcm47xx_wdt_hard_set_timeout ,
} ;
2013-01-12 21:14:09 +04:00
static void bcm47xx_wdt_soft_timer_tick ( unsigned long data )
2009-06-05 20:57:18 +04:00
{
2013-01-24 21:13:34 +04:00
struct bcm47xx_wdt * wdt = ( struct bcm47xx_wdt * ) data ;
u32 next_tick = min ( wdt - > wdd . timeout * 1000 , wdt - > max_timer_ms ) ;
2009-06-05 20:57:18 +04:00
2013-01-24 21:13:34 +04:00
if ( ! atomic_dec_and_test ( & wdt - > soft_ticks ) ) {
wdt - > timer_set_ms ( wdt , next_tick ) ;
mod_timer ( & wdt - > soft_timer , jiffies + HZ ) ;
2009-06-05 20:57:18 +04:00
} else {
2012-02-16 03:06:19 +04:00
pr_crit ( " Watchdog will fire soon!!! \n " ) ;
2009-06-05 20:57:18 +04:00
}
}
2013-01-12 21:14:09 +04:00
static int bcm47xx_wdt_soft_keepalive ( struct watchdog_device * wdd )
2009-06-05 20:57:18 +04:00
{
2013-01-24 21:13:34 +04:00
struct bcm47xx_wdt * wdt = bcm47xx_wdt_get ( wdd ) ;
atomic_set ( & wdt - > soft_ticks , wdd - > timeout ) ;
2013-01-12 21:14:07 +04:00
return 0 ;
2009-06-05 20:57:18 +04:00
}
2013-01-12 21:14:09 +04:00
static int bcm47xx_wdt_soft_start ( struct watchdog_device * wdd )
2009-06-05 20:57:18 +04:00
{
2013-01-24 21:13:34 +04:00
struct bcm47xx_wdt * wdt = bcm47xx_wdt_get ( wdd ) ;
2013-01-12 21:14:09 +04:00
bcm47xx_wdt_soft_keepalive ( wdd ) ;
bcm47xx_wdt_soft_timer_tick ( ( unsigned long ) wdt ) ;
2013-01-12 21:14:07 +04:00
return 0 ;
2009-06-05 20:57:18 +04:00
}
2013-01-12 21:14:09 +04:00
static int bcm47xx_wdt_soft_stop ( struct watchdog_device * wdd )
2009-06-05 20:57:18 +04:00
{
2013-01-24 21:13:34 +04:00
struct bcm47xx_wdt * wdt = bcm47xx_wdt_get ( wdd ) ;
del_timer_sync ( & wdt - > soft_timer ) ;
wdt - > timer_set ( wdt , 0 ) ;
2009-06-05 20:57:18 +04:00
2013-01-12 21:14:07 +04:00
return 0 ;
2009-06-05 20:57:18 +04:00
}
2013-01-12 21:14:09 +04:00
static int bcm47xx_wdt_soft_set_timeout ( struct watchdog_device * wdd ,
unsigned int new_time )
2009-06-05 20:57:18 +04:00
{
2013-01-12 21:14:09 +04:00
if ( new_time < 1 | | new_time > WDT_SOFTTIMER_MAX ) {
2013-01-24 21:13:34 +04:00
pr_warn ( " timeout value must be 1<=x<=%d, using %d \n " ,
2013-01-12 21:14:09 +04:00
WDT_SOFTTIMER_MAX , new_time ) ;
2009-06-05 20:57:18 +04:00
return - EINVAL ;
2013-01-24 21:13:34 +04:00
}
2009-06-05 20:57:18 +04:00
2013-01-24 21:13:34 +04:00
wdd - > timeout = new_time ;
2009-06-05 20:57:18 +04:00
return 0 ;
}
2009-12-26 21:55:22 +03:00
static const struct watchdog_info bcm47xx_wdt_info = {
2011-02-23 23:04:38 +03:00
. identity = DRV_NAME ,
. options = WDIOF_SETTIMEOUT |
2009-06-05 20:57:18 +04:00
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
} ;
static int bcm47xx_wdt_notify_sys ( struct notifier_block * this ,
2013-01-12 21:14:07 +04:00
unsigned long code , void * unused )
2009-06-05 20:57:18 +04:00
{
2013-01-24 21:13:34 +04:00
struct bcm47xx_wdt * wdt ;
wdt = container_of ( this , struct bcm47xx_wdt , notifier ) ;
2009-06-05 20:57:18 +04:00
if ( code = = SYS_DOWN | | code = = SYS_HALT )
2013-01-24 21:13:34 +04:00
wdt - > wdd . ops - > stop ( & wdt - > wdd ) ;
2009-06-05 20:57:18 +04:00
return NOTIFY_DONE ;
}
2015-01-25 13:40:57 +03:00
static int bcm47xx_wdt_restart ( struct notifier_block * this , unsigned long mode ,
void * cmd )
{
struct bcm47xx_wdt * wdt ;
wdt = container_of ( this , struct bcm47xx_wdt , restart_handler ) ;
wdt - > timer_set ( wdt , 1 ) ;
return NOTIFY_DONE ;
}
2013-01-12 21:14:09 +04:00
static struct watchdog_ops bcm47xx_wdt_soft_ops = {
2009-06-05 20:57:18 +04:00
. owner = THIS_MODULE ,
2013-01-12 21:14:09 +04:00
. start = bcm47xx_wdt_soft_start ,
. stop = bcm47xx_wdt_soft_stop ,
. ping = bcm47xx_wdt_soft_keepalive ,
. set_timeout = bcm47xx_wdt_soft_set_timeout ,
2009-06-05 20:57:18 +04:00
} ;
2013-01-24 21:13:34 +04:00
static int bcm47xx_wdt_probe ( struct platform_device * pdev )
2009-06-05 20:57:18 +04:00
{
int ret ;
2013-01-12 21:14:11 +04:00
bool soft ;
2013-01-24 21:13:34 +04:00
struct bcm47xx_wdt * wdt = dev_get_platdata ( & pdev - > dev ) ;
2009-06-05 20:57:18 +04:00
2013-01-24 21:13:34 +04:00
if ( ! wdt )
return - ENXIO ;
2009-06-05 20:57:18 +04:00
2013-01-12 21:14:11 +04:00
soft = wdt - > max_timer_ms < WDT_SOFTTIMER_THRESHOLD * 1000 ;
if ( soft ) {
wdt - > wdd . ops = & bcm47xx_wdt_soft_ops ;
setup_timer ( & wdt - > soft_timer , bcm47xx_wdt_soft_timer_tick ,
( long unsigned int ) wdt ) ;
} else {
wdt - > wdd . ops = & bcm47xx_wdt_hard_ops ;
}
2009-06-05 20:57:18 +04:00
2013-01-24 21:13:34 +04:00
wdt - > wdd . info = & bcm47xx_wdt_info ;
wdt - > wdd . timeout = WDT_DEFAULT_TIME ;
ret = wdt - > wdd . ops - > set_timeout ( & wdt - > wdd , timeout ) ;
if ( ret )
goto err_timer ;
watchdog_set_nowayout ( & wdt - > wdd , nowayout ) ;
wdt - > notifier . notifier_call = & bcm47xx_wdt_notify_sys ;
2009-06-05 20:57:18 +04:00
2013-01-24 21:13:34 +04:00
ret = register_reboot_notifier ( & wdt - > notifier ) ;
2009-06-05 20:57:18 +04:00
if ( ret )
2013-01-24 21:13:34 +04:00
goto err_timer ;
2009-06-05 20:57:18 +04:00
2015-01-25 13:40:57 +03:00
wdt - > restart_handler . notifier_call = & bcm47xx_wdt_restart ;
wdt - > restart_handler . priority = 64 ;
ret = register_restart_handler ( & wdt - > restart_handler ) ;
2013-01-24 21:13:34 +04:00
if ( ret )
goto err_notifier ;
2009-06-05 20:57:18 +04:00
2015-01-25 13:40:57 +03:00
ret = watchdog_register_device ( & wdt - > wdd ) ;
if ( ret )
goto err_handler ;
2013-01-12 21:14:11 +04:00
dev_info ( & pdev - > dev , " BCM47xx Watchdog Timer enabled (%d seconds%s%s) \n " ,
timeout , nowayout ? " , nowayout " : " " ,
soft ? " , Software Timer " : " " ) ;
2009-06-05 20:57:18 +04:00
return 0 ;
2013-01-24 21:13:34 +04:00
2015-01-25 13:40:57 +03:00
err_handler :
unregister_restart_handler ( & wdt - > restart_handler ) ;
2013-01-24 21:13:34 +04:00
err_notifier :
unregister_reboot_notifier ( & wdt - > notifier ) ;
err_timer :
2013-01-12 21:14:11 +04:00
if ( soft )
del_timer_sync ( & wdt - > soft_timer ) ;
2013-01-24 21:13:34 +04:00
return ret ;
2009-06-05 20:57:18 +04:00
}
2013-01-24 21:13:34 +04:00
static int bcm47xx_wdt_remove ( struct platform_device * pdev )
2009-06-05 20:57:18 +04:00
{
2013-01-24 21:13:34 +04:00
struct bcm47xx_wdt * wdt = dev_get_platdata ( & pdev - > dev ) ;
if ( ! wdt )
return - ENXIO ;
watchdog_unregister_device ( & wdt - > wdd ) ;
unregister_reboot_notifier ( & wdt - > notifier ) ;
2009-06-05 20:57:18 +04:00
2013-01-24 21:13:34 +04:00
return 0 ;
2009-06-05 20:57:18 +04:00
}
2013-01-24 21:13:34 +04:00
static struct platform_driver bcm47xx_wdt_driver = {
. driver = {
. name = " bcm47xx-wdt " ,
} ,
. probe = bcm47xx_wdt_probe ,
. remove = bcm47xx_wdt_remove ,
} ;
module_platform_driver ( bcm47xx_wdt_driver ) ;
2009-06-05 20:57:18 +04:00
MODULE_AUTHOR ( " Aleksandar Radovanovic " ) ;
2013-01-24 21:13:34 +04:00
MODULE_AUTHOR ( " Hauke Mehrtens <hauke@hauke-m.de> " ) ;
2009-06-05 20:57:18 +04:00
MODULE_DESCRIPTION ( " Watchdog driver for Broadcom BCM47xx " ) ;
MODULE_LICENSE ( " GPL " ) ;