2007-05-14 16:45:25 +04:00
/*
* Watchdog driver for Kendin / Micrel KS8695 .
*
* ( C ) 2007 Andrew Victor
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2007-10-19 10:40:25 +04:00
# include <linux/bitops.h>
2007-05-14 16:45:25 +04:00
# include <linux/errno.h>
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/platform_device.h>
# include <linux/types.h>
# include <linux/watchdog.h>
2008-05-19 17:06:53 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2010-12-14 18:29:31 +03:00
# include <mach/hardware.h>
2012-09-11 09:48:15 +04:00
# define KS8695_TMR_OFFSET (0xF0000 + 0xE400)
# define KS8695_TMR_VA (KS8695_IO_VA + KS8695_TMR_OFFSET)
/*
* Timer registers
*/
# define KS8695_TMCON (0x00) /* Timer Control Register */
# define KS8695_T0TC (0x08) /* Timer 0 Timeout Count Register */
# define TMCON_T0EN (1 << 0) /* Timer 0 Enable */
/* Timer0 Timeout Counter Register */
# define T0TC_WATCHDOG (0xff) /* Enable watchdog mode */
2007-05-14 16:45:25 +04:00
# define WDT_DEFAULT_TIME 5 /* seconds */
# define WDT_MAX_TIME 171 /* seconds */
static int wdt_time = WDT_DEFAULT_TIME ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2007-05-14 16:45:25 +04:00
module_param ( wdt_time , int , 0 ) ;
2008-05-19 17:06:53 +04:00
MODULE_PARM_DESC ( wdt_time , " Watchdog time in seconds. (default= "
__MODULE_STRING ( WDT_DEFAULT_TIME ) " ) " ) ;
2007-05-14 16:45:25 +04:00
# ifdef CONFIG_WATCHDOG_NOWAYOUT
2012-03-05 19:51:11 +04:00
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:06:53 +04:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2007-05-14 16:45:25 +04:00
# endif
static unsigned long ks8695wdt_busy ;
2011-11-29 09:54:01 +04:00
static DEFINE_SPINLOCK ( ks8695_lock ) ;
2007-05-14 16:45:25 +04:00
/* ......................................................................... */
/*
* Disable the watchdog .
*/
2008-05-19 17:06:53 +04:00
static inline void ks8695_wdt_stop ( void )
2007-05-14 16:45:25 +04:00
{
unsigned long tmcon ;
2008-05-19 17:06:53 +04:00
spin_lock ( & ks8695_lock ) ;
2007-05-14 16:45:25 +04:00
/* disable timer0 */
tmcon = __raw_readl ( KS8695_TMR_VA + KS8695_TMCON ) ;
__raw_writel ( tmcon & ~ TMCON_T0EN , KS8695_TMR_VA + KS8695_TMCON ) ;
2008-05-19 17:06:53 +04:00
spin_unlock ( & ks8695_lock ) ;
2007-05-14 16:45:25 +04:00
}
/*
* Enable and reset the watchdog .
*/
2008-05-19 17:06:53 +04:00
static inline void ks8695_wdt_start ( void )
2007-05-14 16:45:25 +04:00
{
unsigned long tmcon ;
2009-08-04 22:55:56 +04:00
unsigned long tval = wdt_time * KS8695_CLOCK_RATE ;
2007-05-14 16:45:25 +04:00
2008-05-19 17:06:53 +04:00
spin_lock ( & ks8695_lock ) ;
2007-05-14 16:45:25 +04:00
/* disable timer0 */
tmcon = __raw_readl ( KS8695_TMR_VA + KS8695_TMCON ) ;
__raw_writel ( tmcon & ~ TMCON_T0EN , KS8695_TMR_VA + KS8695_TMCON ) ;
/* program timer0 */
__raw_writel ( tval | T0TC_WATCHDOG , KS8695_TMR_VA + KS8695_T0TC ) ;
/* re-enable timer0 */
tmcon = __raw_readl ( KS8695_TMR_VA + KS8695_TMCON ) ;
__raw_writel ( tmcon | TMCON_T0EN , KS8695_TMR_VA + KS8695_TMCON ) ;
2008-05-19 17:06:53 +04:00
spin_unlock ( & ks8695_lock ) ;
2007-05-14 16:45:25 +04:00
}
/*
* Reload the watchdog timer . ( ie , pat the watchdog )
*/
2008-05-19 17:06:53 +04:00
static inline void ks8695_wdt_reload ( void )
2007-05-14 16:45:25 +04:00
{
unsigned long tmcon ;
2008-05-19 17:06:53 +04:00
spin_lock ( & ks8695_lock ) ;
2007-05-14 16:45:25 +04:00
/* disable, then re-enable timer0 */
tmcon = __raw_readl ( KS8695_TMR_VA + KS8695_TMCON ) ;
__raw_writel ( tmcon & ~ TMCON_T0EN , KS8695_TMR_VA + KS8695_TMCON ) ;
__raw_writel ( tmcon | TMCON_T0EN , KS8695_TMR_VA + KS8695_TMCON ) ;
2008-05-19 17:06:53 +04:00
spin_unlock ( & ks8695_lock ) ;
2007-05-14 16:45:25 +04:00
}
/*
* Change the watchdog time interval .
*/
static int ks8695_wdt_settimeout ( int new_time )
{
/*
2009-08-04 22:55:56 +04:00
* All counting occurs at KS8695_CLOCK_RATE / 128 = 0.256 Hz
2007-05-14 16:45:25 +04:00
*
* Since WDV is a 16 - bit counter , the maximum period is
* 65536 / 0.256 = 256 seconds .
*/
if ( ( new_time < = 0 ) | | ( new_time > WDT_MAX_TIME ) )
return - EINVAL ;
2008-05-19 17:06:53 +04:00
/* Set new watchdog time. It will be used when
ks8695_wdt_start ( ) is called . */
2007-05-14 16:45:25 +04:00
wdt_time = new_time ;
return 0 ;
}
/* ......................................................................... */
/*
* Watchdog device is opened , and watchdog starts running .
*/
static int ks8695_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 0 , & ks8695wdt_busy ) )
return - EBUSY ;
ks8695_wdt_start ( ) ;
return nonseekable_open ( inode , file ) ;
}
/*
* Close the watchdog device .
* If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also
* disabled .
*/
static int ks8695_wdt_close ( struct inode * inode , struct file * file )
{
2008-05-19 17:06:53 +04:00
/* Disable the watchdog when file is closed */
2007-05-14 16:45:25 +04:00
if ( ! nowayout )
2008-05-19 17:06:53 +04:00
ks8695_wdt_stop ( ) ;
2007-05-14 16:45:25 +04:00
clear_bit ( 0 , & ks8695wdt_busy ) ;
return 0 ;
}
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ks8695_wdt_info = {
2007-05-14 16:45:25 +04:00
. identity = " ks8695 watchdog " ,
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
} ;
/*
* Handle commands from user - space .
*/
2008-05-19 17:06:53 +04:00
static long ks8695_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2007-05-14 16:45:25 +04:00
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int new_value ;
2008-05-19 17:06:53 +04:00
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ks8695_wdt_info ,
sizeof ( ks8695_wdt_info ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_SETOPTIONS :
if ( get_user ( new_value , p ) )
return - EFAULT ;
if ( new_value & WDIOS_DISABLECARD )
ks8695_wdt_stop ( ) ;
if ( new_value & WDIOS_ENABLECARD )
2007-05-14 16:45:25 +04:00
ks8695_wdt_start ( ) ;
2008-05-19 17:06:53 +04:00
return 0 ;
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
ks8695_wdt_reload ( ) ; /* pat the watchdog */
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_value , p ) )
return - EFAULT ;
if ( ks8695_wdt_settimeout ( new_value ) )
return - EINVAL ;
/* Enable new time value */
ks8695_wdt_start ( ) ;
/* Return current value */
return put_user ( wdt_time , p ) ;
case WDIOC_GETTIMEOUT :
return put_user ( wdt_time , p ) ;
2008-05-19 17:06:53 +04:00
default :
return - ENOTTY ;
2007-05-14 16:45:25 +04:00
}
}
/*
* Pat the watchdog whenever device is written to .
*/
2008-05-19 17:06:53 +04:00
static ssize_t ks8695_wdt_write ( struct file * file , const char * data ,
size_t len , loff_t * ppos )
2007-05-14 16:45:25 +04:00
{
ks8695_wdt_reload ( ) ; /* pat the watchdog */
return len ;
}
/* ......................................................................... */
static const struct file_operations ks8695wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
2008-05-19 17:06:53 +04:00
. unlocked_ioctl = ks8695_wdt_ioctl ,
2007-05-14 16:45:25 +04:00
. open = ks8695_wdt_open ,
. release = ks8695_wdt_close ,
. write = ks8695_wdt_write ,
} ;
static struct miscdevice ks8695wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & ks8695wdt_fops ,
} ;
2012-11-19 22:21:41 +04:00
static int ks8695wdt_probe ( struct platform_device * pdev )
2007-05-14 16:45:25 +04:00
{
int res ;
if ( ks8695wdt_miscdev . parent )
return - EBUSY ;
ks8695wdt_miscdev . parent = & pdev - > dev ;
res = misc_register ( & ks8695wdt_miscdev ) ;
if ( res )
return res ;
2012-02-16 03:06:19 +04:00
pr_info ( " KS8695 Watchdog Timer enabled (%d seconds%s) \n " ,
wdt_time , nowayout ? " , nowayout " : " " ) ;
2007-05-14 16:45:25 +04:00
return 0 ;
}
2012-11-19 22:26:24 +04:00
static int ks8695wdt_remove ( struct platform_device * pdev )
2007-05-14 16:45:25 +04:00
{
int res ;
res = misc_deregister ( & ks8695wdt_miscdev ) ;
if ( ! res )
ks8695wdt_miscdev . parent = NULL ;
return res ;
}
static void ks8695wdt_shutdown ( struct platform_device * pdev )
{
ks8695_wdt_stop ( ) ;
}
# ifdef CONFIG_PM
static int ks8695wdt_suspend ( struct platform_device * pdev , pm_message_t message )
{
ks8695_wdt_stop ( ) ;
return 0 ;
}
static int ks8695wdt_resume ( struct platform_device * pdev )
{
if ( ks8695wdt_busy )
ks8695_wdt_start ( ) ;
return 0 ;
}
# else
# define ks8695wdt_suspend NULL
# define ks8695wdt_resume NULL
# endif
static struct platform_driver ks8695wdt_driver = {
. probe = ks8695wdt_probe ,
2012-11-19 22:21:12 +04:00
. remove = ks8695wdt_remove ,
2007-05-14 16:45:25 +04:00
. shutdown = ks8695wdt_shutdown ,
. suspend = ks8695wdt_suspend ,
. resume = ks8695wdt_resume ,
. driver = {
. name = " ks8695_wdt " ,
} ,
} ;
static int __init ks8695_wdt_init ( void )
{
2008-05-19 17:06:53 +04:00
/* Check that the heartbeat value is within range;
if not reset to the default */
2007-05-14 16:45:25 +04:00
if ( ks8695_wdt_settimeout ( wdt_time ) ) {
ks8695_wdt_settimeout ( WDT_DEFAULT_TIME ) ;
2009-04-15 00:20:07 +04:00
pr_info ( " ks8695_wdt: wdt_time value must be 1 <= wdt_time <= %i "
" , using %d \n " , wdt_time , WDT_MAX_TIME ) ;
2007-05-14 16:45:25 +04:00
}
return platform_driver_register ( & ks8695wdt_driver ) ;
}
static void __exit ks8695_wdt_exit ( void )
{
platform_driver_unregister ( & ks8695wdt_driver ) ;
}
module_init ( ks8695_wdt_init ) ;
module_exit ( ks8695_wdt_exit ) ;
MODULE_AUTHOR ( " Andrew Victor " ) ;
MODULE_DESCRIPTION ( " Watchdog driver for KS8695 " ) ;
MODULE_LICENSE ( " GPL " ) ;
2008-04-11 08:29:23 +04:00
MODULE_ALIAS ( " platform:ks8695_wdt " ) ;