2013-06-24 08:00:06 +04:00
/*
* Kontron PLD watchdog driver
*
* Copyright ( c ) 2010 - 2013 Kontron Europe GmbH
* Author : Michael Brunner < michael . brunner @ kontron . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License 2 as published
* by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* Note : From the PLD watchdog point of view timeout and pretimeout are
* defined differently than in the kernel .
* First the pretimeout stage runs out before the timeout stage gets
* active .
*
* Kernel / API : P - - - - - | pretimeout
* | - - - - - - - - - - - - - - - - - - - - - - - T timeout
* Watchdog : | - - - - - - - - - - - - - - - - - P pretimeout_stage
* | - - - - - T timeout_stage
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/uaccess.h>
# include <linux/watchdog.h>
# include <linux/platform_device.h>
# include <linux/mfd/kempld.h>
# define KEMPLD_WDT_STAGE_TIMEOUT(x) (0x1b + (x) * 4)
# define KEMPLD_WDT_STAGE_CFG(x) (0x18 + (x))
# define STAGE_CFG_GET_PRESCALER(x) (((x) & 0x30) >> 4)
2013-09-23 14:16:57 +04:00
# define STAGE_CFG_SET_PRESCALER(x) (((x) & 0x3) << 4)
2013-06-24 08:00:06 +04:00
# define STAGE_CFG_PRESCALER_MASK 0x30
# define STAGE_CFG_ACTION_MASK 0x7
# define STAGE_CFG_ASSERT (1 << 3)
# define KEMPLD_WDT_MAX_STAGES 2
# define KEMPLD_WDT_KICK 0x16
# define KEMPLD_WDT_CFG 0x17
# define KEMPLD_WDT_CFG_ENABLE 0x10
# define KEMPLD_WDT_CFG_ENABLE_LOCK 0x8
# define KEMPLD_WDT_CFG_GLOBAL_LOCK 0x80
enum {
ACTION_NONE = 0 ,
ACTION_RESET ,
ACTION_NMI ,
ACTION_SMI ,
ACTION_SCI ,
ACTION_DELAY ,
} ;
enum {
STAGE_TIMEOUT = 0 ,
STAGE_PRETIMEOUT ,
} ;
enum {
PRESCALER_21 = 0 ,
PRESCALER_17 ,
PRESCALER_12 ,
} ;
2013-08-01 09:39:46 +04:00
static const u32 kempld_prescaler [ ] = {
2013-06-24 08:00:06 +04:00
[ PRESCALER_21 ] = ( 1 < < 21 ) - 1 ,
[ PRESCALER_17 ] = ( 1 < < 17 ) - 1 ,
[ PRESCALER_12 ] = ( 1 < < 12 ) - 1 ,
0 ,
} ;
struct kempld_wdt_stage {
unsigned int id ;
u32 mask ;
} ;
struct kempld_wdt_data {
struct kempld_device_data * pld ;
struct watchdog_device wdd ;
unsigned int pretimeout ;
struct kempld_wdt_stage stage [ KEMPLD_WDT_MAX_STAGES ] ;
# ifdef CONFIG_PM
u8 pm_status_store ;
# endif
} ;
# define DEFAULT_TIMEOUT 30 /* seconds */
# define DEFAULT_PRETIMEOUT 0
static unsigned int timeout = DEFAULT_TIMEOUT ;
module_param ( timeout , uint , 0 ) ;
MODULE_PARM_DESC ( timeout ,
" Watchdog timeout in seconds. (>=0, default= "
__MODULE_STRING ( DEFAULT_TIMEOUT ) " ) " ) ;
static unsigned int pretimeout = DEFAULT_PRETIMEOUT ;
module_param ( pretimeout , uint , 0 ) ;
MODULE_PARM_DESC ( pretimeout ,
" Watchdog pretimeout in seconds. (>=0, default= "
__MODULE_STRING ( DEFAULT_PRETIMEOUT ) " ) " ) ;
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static int kempld_wdt_set_stage_action ( struct kempld_wdt_data * wdt_data ,
struct kempld_wdt_stage * stage ,
u8 action )
{
struct kempld_device_data * pld = wdt_data - > pld ;
u8 stage_cfg ;
if ( ! stage | | ! stage - > mask )
return - EINVAL ;
kempld_get_mutex ( pld ) ;
stage_cfg = kempld_read8 ( pld , KEMPLD_WDT_STAGE_CFG ( stage - > id ) ) ;
stage_cfg & = ~ STAGE_CFG_ACTION_MASK ;
stage_cfg | = ( action & STAGE_CFG_ACTION_MASK ) ;
if ( action = = ACTION_RESET )
stage_cfg | = STAGE_CFG_ASSERT ;
else
stage_cfg & = ~ STAGE_CFG_ASSERT ;
kempld_write8 ( pld , KEMPLD_WDT_STAGE_CFG ( stage - > id ) , stage_cfg ) ;
kempld_release_mutex ( pld ) ;
return 0 ;
}
static int kempld_wdt_set_stage_timeout ( struct kempld_wdt_data * wdt_data ,
struct kempld_wdt_stage * stage ,
unsigned int timeout )
{
struct kempld_device_data * pld = wdt_data - > pld ;
2017-03-01 12:15:29 +03:00
u32 prescaler ;
2013-06-24 08:00:06 +04:00
u64 stage_timeout64 ;
u32 stage_timeout ;
u32 remainder ;
u8 stage_cfg ;
2017-03-01 12:15:29 +03:00
prescaler = kempld_prescaler [ PRESCALER_21 ] ;
2013-06-24 08:00:06 +04:00
if ( ! stage )
return - EINVAL ;
stage_timeout64 = ( u64 ) timeout * pld - > pld_clock ;
remainder = do_div ( stage_timeout64 , prescaler ) ;
if ( remainder )
stage_timeout64 + + ;
if ( stage_timeout64 > stage - > mask )
return - EINVAL ;
stage_timeout = stage_timeout64 & stage - > mask ;
kempld_get_mutex ( pld ) ;
stage_cfg = kempld_read8 ( pld , KEMPLD_WDT_STAGE_CFG ( stage - > id ) ) ;
stage_cfg & = ~ STAGE_CFG_PRESCALER_MASK ;
2014-04-24 17:49:19 +04:00
stage_cfg | = STAGE_CFG_SET_PRESCALER ( PRESCALER_21 ) ;
2013-06-24 08:00:06 +04:00
kempld_write8 ( pld , KEMPLD_WDT_STAGE_CFG ( stage - > id ) , stage_cfg ) ;
kempld_write32 ( pld , KEMPLD_WDT_STAGE_TIMEOUT ( stage - > id ) ,
stage_timeout ) ;
kempld_release_mutex ( pld ) ;
return 0 ;
}
/*
* kempld_get_mutex must be called prior to calling this function .
*/
static unsigned int kempld_wdt_get_timeout ( struct kempld_wdt_data * wdt_data ,
struct kempld_wdt_stage * stage )
{
struct kempld_device_data * pld = wdt_data - > pld ;
unsigned int timeout ;
u64 stage_timeout ;
u32 prescaler ;
u32 remainder ;
u8 stage_cfg ;
if ( ! stage - > mask )
return 0 ;
stage_cfg = kempld_read8 ( pld , KEMPLD_WDT_STAGE_CFG ( stage - > id ) ) ;
stage_timeout = kempld_read32 ( pld , KEMPLD_WDT_STAGE_TIMEOUT ( stage - > id ) ) ;
prescaler = kempld_prescaler [ STAGE_CFG_GET_PRESCALER ( stage_cfg ) ] ;
stage_timeout = ( stage_timeout & stage - > mask ) * prescaler ;
remainder = do_div ( stage_timeout , pld - > pld_clock ) ;
if ( remainder )
stage_timeout + + ;
timeout = stage_timeout ;
WARN_ON_ONCE ( timeout ! = stage_timeout ) ;
return timeout ;
}
static int kempld_wdt_set_timeout ( struct watchdog_device * wdd ,
unsigned int timeout )
{
struct kempld_wdt_data * wdt_data = watchdog_get_drvdata ( wdd ) ;
struct kempld_wdt_stage * pretimeout_stage ;
struct kempld_wdt_stage * timeout_stage ;
int ret ;
timeout_stage = & wdt_data - > stage [ STAGE_TIMEOUT ] ;
pretimeout_stage = & wdt_data - > stage [ STAGE_PRETIMEOUT ] ;
if ( pretimeout_stage - > mask & & wdt_data - > pretimeout > 0 )
timeout = wdt_data - > pretimeout ;
ret = kempld_wdt_set_stage_action ( wdt_data , timeout_stage ,
ACTION_RESET ) ;
if ( ret )
return ret ;
ret = kempld_wdt_set_stage_timeout ( wdt_data , timeout_stage ,
timeout ) ;
if ( ret )
return ret ;
wdd - > timeout = timeout ;
return 0 ;
}
static int kempld_wdt_set_pretimeout ( struct watchdog_device * wdd ,
unsigned int pretimeout )
{
struct kempld_wdt_data * wdt_data = watchdog_get_drvdata ( wdd ) ;
struct kempld_wdt_stage * pretimeout_stage ;
u8 action = ACTION_NONE ;
int ret ;
pretimeout_stage = & wdt_data - > stage [ STAGE_PRETIMEOUT ] ;
if ( ! pretimeout_stage - > mask )
return - ENXIO ;
if ( pretimeout > wdd - > timeout )
return - EINVAL ;
if ( pretimeout > 0 )
action = ACTION_NMI ;
ret = kempld_wdt_set_stage_action ( wdt_data , pretimeout_stage ,
action ) ;
if ( ret )
return ret ;
ret = kempld_wdt_set_stage_timeout ( wdt_data , pretimeout_stage ,
wdd - > timeout - pretimeout ) ;
if ( ret )
return ret ;
wdt_data - > pretimeout = pretimeout ;
return 0 ;
}
static void kempld_wdt_update_timeouts ( struct kempld_wdt_data * wdt_data )
{
struct kempld_device_data * pld = wdt_data - > pld ;
struct kempld_wdt_stage * pretimeout_stage ;
struct kempld_wdt_stage * timeout_stage ;
unsigned int pretimeout , timeout ;
pretimeout_stage = & wdt_data - > stage [ STAGE_PRETIMEOUT ] ;
timeout_stage = & wdt_data - > stage [ STAGE_TIMEOUT ] ;
kempld_get_mutex ( pld ) ;
pretimeout = kempld_wdt_get_timeout ( wdt_data , pretimeout_stage ) ;
timeout = kempld_wdt_get_timeout ( wdt_data , timeout_stage ) ;
kempld_release_mutex ( pld ) ;
if ( pretimeout )
wdt_data - > pretimeout = timeout ;
else
wdt_data - > pretimeout = 0 ;
wdt_data - > wdd . timeout = pretimeout + timeout ;
}
static int kempld_wdt_start ( struct watchdog_device * wdd )
{
struct kempld_wdt_data * wdt_data = watchdog_get_drvdata ( wdd ) ;
struct kempld_device_data * pld = wdt_data - > pld ;
u8 status ;
int ret ;
ret = kempld_wdt_set_timeout ( wdd , wdd - > timeout ) ;
if ( ret )
return ret ;
kempld_get_mutex ( pld ) ;
status = kempld_read8 ( pld , KEMPLD_WDT_CFG ) ;
status | = KEMPLD_WDT_CFG_ENABLE ;
kempld_write8 ( pld , KEMPLD_WDT_CFG , status ) ;
status = kempld_read8 ( pld , KEMPLD_WDT_CFG ) ;
kempld_release_mutex ( pld ) ;
/* Check if the watchdog was enabled */
if ( ! ( status & KEMPLD_WDT_CFG_ENABLE ) )
return - EACCES ;
return 0 ;
}
static int kempld_wdt_stop ( struct watchdog_device * wdd )
{
struct kempld_wdt_data * wdt_data = watchdog_get_drvdata ( wdd ) ;
struct kempld_device_data * pld = wdt_data - > pld ;
u8 status ;
kempld_get_mutex ( pld ) ;
status = kempld_read8 ( pld , KEMPLD_WDT_CFG ) ;
status & = ~ KEMPLD_WDT_CFG_ENABLE ;
kempld_write8 ( pld , KEMPLD_WDT_CFG , status ) ;
status = kempld_read8 ( pld , KEMPLD_WDT_CFG ) ;
kempld_release_mutex ( pld ) ;
/* Check if the watchdog was disabled */
if ( status & KEMPLD_WDT_CFG_ENABLE )
return - EACCES ;
return 0 ;
}
static int kempld_wdt_keepalive ( struct watchdog_device * wdd )
{
struct kempld_wdt_data * wdt_data = watchdog_get_drvdata ( wdd ) ;
struct kempld_device_data * pld = wdt_data - > pld ;
kempld_get_mutex ( pld ) ;
kempld_write8 ( pld , KEMPLD_WDT_KICK , ' K ' ) ;
kempld_release_mutex ( pld ) ;
return 0 ;
}
static long kempld_wdt_ioctl ( struct watchdog_device * wdd , unsigned int cmd ,
unsigned long arg )
{
struct kempld_wdt_data * wdt_data = watchdog_get_drvdata ( wdd ) ;
void __user * argp = ( void __user * ) arg ;
int ret = - ENOIOCTLCMD ;
int __user * p = argp ;
int new_value ;
switch ( cmd ) {
case WDIOC_SETPRETIMEOUT :
if ( get_user ( new_value , p ) )
return - EFAULT ;
ret = kempld_wdt_set_pretimeout ( wdd , new_value ) ;
if ( ret )
return ret ;
ret = kempld_wdt_keepalive ( wdd ) ;
break ;
case WDIOC_GETPRETIMEOUT :
2013-08-01 09:39:46 +04:00
ret = put_user ( wdt_data - > pretimeout , ( int __user * ) arg ) ;
2013-06-24 08:00:06 +04:00
break ;
}
return ret ;
}
static int kempld_wdt_probe_stages ( struct watchdog_device * wdd )
{
struct kempld_wdt_data * wdt_data = watchdog_get_drvdata ( wdd ) ;
struct kempld_device_data * pld = wdt_data - > pld ;
struct kempld_wdt_stage * pretimeout_stage ;
struct kempld_wdt_stage * timeout_stage ;
u8 index , data , data_orig ;
u32 mask ;
int i , j ;
pretimeout_stage = & wdt_data - > stage [ STAGE_PRETIMEOUT ] ;
timeout_stage = & wdt_data - > stage [ STAGE_TIMEOUT ] ;
pretimeout_stage - > mask = 0 ;
timeout_stage - > mask = 0 ;
for ( i = 0 ; i < 3 ; i + + ) {
index = KEMPLD_WDT_STAGE_TIMEOUT ( i ) ;
mask = 0 ;
kempld_get_mutex ( pld ) ;
/* Probe each byte individually. */
for ( j = 0 ; j < 4 ; j + + ) {
data_orig = kempld_read8 ( pld , index + j ) ;
kempld_write8 ( pld , index + j , 0x00 ) ;
data = kempld_read8 ( pld , index + j ) ;
/* A failed write means this byte is reserved */
if ( data ! = 0x00 )
break ;
kempld_write8 ( pld , index + j , data_orig ) ;
mask | = 0xff < < ( j * 8 ) ;
}
kempld_release_mutex ( pld ) ;
/* Assign available stages to timeout and pretimeout */
if ( ! timeout_stage - > mask ) {
timeout_stage - > mask = mask ;
timeout_stage - > id = i ;
} else {
if ( pld - > feature_mask & KEMPLD_FEATURE_BIT_NMI ) {
pretimeout_stage - > mask = timeout_stage - > mask ;
timeout_stage - > mask = mask ;
pretimeout_stage - > id = timeout_stage - > id ;
timeout_stage - > id = i ;
}
break ;
}
}
if ( ! timeout_stage - > mask )
return - ENODEV ;
return 0 ;
}
2016-12-26 20:05:11 +03:00
static const struct watchdog_info kempld_wdt_info = {
2013-06-24 08:00:06 +04:00
. identity = " KEMPLD Watchdog " ,
. options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE |
WDIOF_PRETIMEOUT
} ;
2016-09-01 20:35:26 +03:00
static const struct watchdog_ops kempld_wdt_ops = {
2013-06-24 08:00:06 +04:00
. owner = THIS_MODULE ,
. start = kempld_wdt_start ,
. stop = kempld_wdt_stop ,
. ping = kempld_wdt_keepalive ,
. set_timeout = kempld_wdt_set_timeout ,
. ioctl = kempld_wdt_ioctl ,
} ;
static int kempld_wdt_probe ( struct platform_device * pdev )
{
struct kempld_device_data * pld = dev_get_drvdata ( pdev - > dev . parent ) ;
struct kempld_wdt_data * wdt_data ;
struct device * dev = & pdev - > dev ;
struct watchdog_device * wdd ;
u8 status ;
int ret = 0 ;
wdt_data = devm_kzalloc ( dev , sizeof ( * wdt_data ) , GFP_KERNEL ) ;
if ( ! wdt_data )
return - ENOMEM ;
wdt_data - > pld = pld ;
wdd = & wdt_data - > wdd ;
wdd - > parent = dev ;
kempld_get_mutex ( pld ) ;
status = kempld_read8 ( pld , KEMPLD_WDT_CFG ) ;
kempld_release_mutex ( pld ) ;
/* Enable nowayout if watchdog is already locked */
if ( status & ( KEMPLD_WDT_CFG_ENABLE_LOCK |
KEMPLD_WDT_CFG_GLOBAL_LOCK ) ) {
if ( ! nowayout )
dev_warn ( dev ,
" Forcing nowayout - watchdog lock enabled! \n " ) ;
nowayout = true ;
}
wdd - > info = & kempld_wdt_info ;
wdd - > ops = & kempld_wdt_ops ;
watchdog_set_drvdata ( wdd , wdt_data ) ;
watchdog_set_nowayout ( wdd , nowayout ) ;
ret = kempld_wdt_probe_stages ( wdd ) ;
if ( ret )
return ret ;
kempld_wdt_set_timeout ( wdd , timeout ) ;
kempld_wdt_set_pretimeout ( wdd , pretimeout ) ;
/* Check if watchdog is already enabled */
if ( status & KEMPLD_WDT_CFG_ENABLE ) {
/* Get current watchdog settings */
kempld_wdt_update_timeouts ( wdt_data ) ;
dev_info ( dev , " Watchdog was already enabled \n " ) ;
}
platform_set_drvdata ( pdev , wdt_data ) ;
ret = watchdog_register_device ( wdd ) ;
if ( ret )
return ret ;
dev_info ( dev , " Watchdog registered with %ds timeout \n " , wdd - > timeout ) ;
return 0 ;
}
static void kempld_wdt_shutdown ( struct platform_device * pdev )
{
struct kempld_wdt_data * wdt_data = platform_get_drvdata ( pdev ) ;
kempld_wdt_stop ( & wdt_data - > wdd ) ;
}
static int kempld_wdt_remove ( struct platform_device * pdev )
{
struct kempld_wdt_data * wdt_data = platform_get_drvdata ( pdev ) ;
struct watchdog_device * wdd = & wdt_data - > wdd ;
int ret = 0 ;
if ( ! nowayout )
ret = kempld_wdt_stop ( wdd ) ;
watchdog_unregister_device ( wdd ) ;
return ret ;
}
# ifdef CONFIG_PM
/* Disable watchdog if it is active during suspend */
static int kempld_wdt_suspend ( struct platform_device * pdev ,
pm_message_t message )
{
struct kempld_wdt_data * wdt_data = platform_get_drvdata ( pdev ) ;
struct kempld_device_data * pld = wdt_data - > pld ;
struct watchdog_device * wdd = & wdt_data - > wdd ;
kempld_get_mutex ( pld ) ;
wdt_data - > pm_status_store = kempld_read8 ( pld , KEMPLD_WDT_CFG ) ;
kempld_release_mutex ( pld ) ;
kempld_wdt_update_timeouts ( wdt_data ) ;
if ( wdt_data - > pm_status_store & KEMPLD_WDT_CFG_ENABLE )
return kempld_wdt_stop ( wdd ) ;
return 0 ;
}
/* Enable watchdog and configure it if necessary */
static int kempld_wdt_resume ( struct platform_device * pdev )
{
struct kempld_wdt_data * wdt_data = platform_get_drvdata ( pdev ) ;
struct watchdog_device * wdd = & wdt_data - > wdd ;
/*
* If watchdog was stopped before suspend be sure it gets disabled
* again , for the case BIOS has enabled it during resume
*/
if ( wdt_data - > pm_status_store & KEMPLD_WDT_CFG_ENABLE )
return kempld_wdt_start ( wdd ) ;
else
return kempld_wdt_stop ( wdd ) ;
}
# else
# define kempld_wdt_suspend NULL
# define kempld_wdt_resume NULL
# endif
static struct platform_driver kempld_wdt_driver = {
. driver = {
. name = " kempld-wdt " ,
} ,
. probe = kempld_wdt_probe ,
. remove = kempld_wdt_remove ,
. shutdown = kempld_wdt_shutdown ,
. suspend = kempld_wdt_suspend ,
. resume = kempld_wdt_resume ,
} ;
module_platform_driver ( kempld_wdt_driver ) ;
MODULE_DESCRIPTION ( " KEM PLD Watchdog Driver " ) ;
MODULE_AUTHOR ( " Michael Brunner <michael.brunner@kontron.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;