2008-06-26 10:47:45 +02:00
/*
2009-06-01 13:56:02 -04:00
* drivers / watchdog / orion_wdt . c
2008-06-26 10:47:45 +02:00
*
2009-06-01 13:56:02 -04:00
* Watchdog driver for Orion / Kirkwood processors
2008-06-26 10:47:45 +02:00
*
* Author : Sylver Bruneau < sylver . bruneau @ googlemail . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
2012-02-15 15:06:19 -08:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2008-06-26 10:47:45 +02:00
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/fs.h>
# include <linux/miscdevice.h>
2009-02-24 14:59:22 -08:00
# include <linux/platform_device.h>
2008-06-26 10:47:45 +02:00
# include <linux/watchdog.h>
# include <linux/init.h>
# include <linux/uaccess.h>
# include <linux/io.h>
2008-10-02 09:32:45 +00:00
# include <linux/spinlock.h>
2009-04-22 20:08:17 +01:00
# include <mach/bridge-regs.h>
2009-06-01 13:56:02 -04:00
# include <plat/orion_wdt.h>
2008-06-26 10:47:45 +02:00
/*
* Watchdog timer block registers .
*/
2012-03-15 00:33:26 +00:00
# define TIMER_CTRL 0x0000
2008-06-26 10:47:45 +02:00
# define WDT_EN 0x0010
2012-03-15 00:33:26 +00:00
# define WDT_VAL 0x0024
2008-06-26 10:47:45 +02:00
2009-02-24 14:59:22 -08:00
# define WDT_MAX_CYCLE_COUNT 0xffffffff
2008-06-26 10:47:45 +02:00
# define WDT_IN_USE 0
# define WDT_OK_TO_CLOSE 1
2012-03-05 16:51:11 +01:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2009-02-24 14:59:22 -08:00
static int heartbeat = - 1 ; /* module parameter (seconds) */
static unsigned int wdt_max_duration ; /* (seconds) */
static unsigned int wdt_tclk ;
2012-03-15 00:33:26 +00:00
static void __iomem * wdt_reg ;
2008-06-26 10:47:45 +02:00
static unsigned long wdt_status ;
2011-11-29 13:54:01 +08:00
static DEFINE_SPINLOCK ( wdt_lock ) ;
2008-06-26 10:47:45 +02:00
2009-06-01 13:56:02 -04:00
static void orion_wdt_ping ( void )
2009-02-20 19:44:59 +01:00
{
spin_lock ( & wdt_lock ) ;
/* Reload watchdog duration */
2012-03-15 00:33:26 +00:00
writel ( wdt_tclk * heartbeat , wdt_reg + WDT_VAL ) ;
2009-02-20 19:44:59 +01:00
spin_unlock ( & wdt_lock ) ;
}
2009-06-01 13:56:02 -04:00
static void orion_wdt_enable ( void )
2008-06-26 10:47:45 +02:00
{
u32 reg ;
2008-10-02 09:32:45 +00:00
spin_lock ( & wdt_lock ) ;
2008-06-26 10:47:45 +02:00
/* Set watchdog duration */
2012-03-15 00:33:26 +00:00
writel ( wdt_tclk * heartbeat , wdt_reg + WDT_VAL ) ;
2008-06-26 10:47:45 +02:00
/* Clear watchdog timer interrupt */
reg = readl ( BRIDGE_CAUSE ) ;
reg & = ~ WDT_INT_REQ ;
writel ( reg , BRIDGE_CAUSE ) ;
/* Enable watchdog timer */
2012-03-15 00:33:26 +00:00
reg = readl ( wdt_reg + TIMER_CTRL ) ;
2008-06-26 10:47:45 +02:00
reg | = WDT_EN ;
2012-03-15 00:33:26 +00:00
writel ( reg , wdt_reg + TIMER_CTRL ) ;
2008-06-26 10:47:45 +02:00
/* Enable reset on watchdog */
2009-06-01 13:38:33 +02:00
reg = readl ( RSTOUTn_MASK ) ;
reg | = WDT_RESET_OUT_EN ;
writel ( reg , RSTOUTn_MASK ) ;
2008-10-02 09:32:45 +00:00
spin_unlock ( & wdt_lock ) ;
2008-06-26 10:47:45 +02:00
}
2009-06-01 13:56:02 -04:00
static void orion_wdt_disable ( void )
2008-06-26 10:47:45 +02:00
{
u32 reg ;
2008-10-02 09:32:45 +00:00
spin_lock ( & wdt_lock ) ;
2008-06-26 10:47:45 +02:00
/* Disable reset on watchdog */
2009-06-01 13:38:33 +02:00
reg = readl ( RSTOUTn_MASK ) ;
reg & = ~ WDT_RESET_OUT_EN ;
writel ( reg , RSTOUTn_MASK ) ;
2008-06-26 10:47:45 +02:00
/* Disable watchdog timer */
2012-03-15 00:33:26 +00:00
reg = readl ( wdt_reg + TIMER_CTRL ) ;
2008-06-26 10:47:45 +02:00
reg & = ~ WDT_EN ;
2012-03-15 00:33:26 +00:00
writel ( reg , wdt_reg + TIMER_CTRL ) ;
2008-10-02 09:32:45 +00:00
spin_unlock ( & wdt_lock ) ;
}
2009-06-01 13:56:02 -04:00
static int orion_wdt_get_timeleft ( int * time_left )
2008-10-02 09:32:45 +00:00
{
spin_lock ( & wdt_lock ) ;
2012-03-15 00:33:26 +00:00
* time_left = readl ( wdt_reg + WDT_VAL ) / wdt_tclk ;
2008-10-02 09:32:45 +00:00
spin_unlock ( & wdt_lock ) ;
return 0 ;
2008-06-26 10:47:45 +02:00
}
2009-06-01 13:56:02 -04:00
static int orion_wdt_open ( struct inode * inode , struct file * file )
2008-06-26 10:47:45 +02:00
{
if ( test_and_set_bit ( WDT_IN_USE , & wdt_status ) )
return - EBUSY ;
clear_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
2009-06-01 13:56:02 -04:00
orion_wdt_enable ( ) ;
2008-06-26 10:47:45 +02:00
return nonseekable_open ( inode , file ) ;
}
2009-06-01 13:56:02 -04:00
static ssize_t orion_wdt_write ( struct file * file , const char * data ,
2008-06-26 10:47:45 +02:00
size_t len , loff_t * ppos )
{
if ( len ) {
if ( ! nowayout ) {
size_t i ;
clear_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
set_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
}
}
2009-06-01 13:56:02 -04:00
orion_wdt_ping ( ) ;
2008-06-26 10:47:45 +02:00
}
return len ;
}
2009-06-01 13:56:02 -04:00
static int orion_wdt_settimeout ( int new_time )
2009-02-20 19:44:59 +01:00
{
if ( ( new_time < = 0 ) | | ( new_time > wdt_max_duration ) )
return - EINVAL ;
/* Set new watchdog time to be used when
2009-06-01 13:56:02 -04:00
* orion_wdt_enable ( ) or orion_wdt_ping ( ) is called . */
2009-02-20 19:44:59 +01:00
heartbeat = new_time ;
return 0 ;
}
static const struct watchdog_info ident = {
2008-06-26 10:47:45 +02:00
. options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING ,
2009-06-01 13:56:02 -04:00
. identity = " Orion Watchdog " ,
2008-06-26 10:47:45 +02:00
} ;
2009-06-01 13:56:02 -04:00
static long orion_wdt_ioctl ( struct file * file , unsigned int cmd ,
2008-06-26 10:47:45 +02:00
unsigned long arg )
{
int ret = - ENOTTY ;
int time ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
ret = copy_to_user ( ( struct watchdog_info * ) arg , & ident ,
sizeof ( ident ) ) ? - EFAULT : 0 ;
break ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
ret = put_user ( 0 , ( int * ) arg ) ;
break ;
case WDIOC_KEEPALIVE :
2009-06-01 13:56:02 -04:00
orion_wdt_ping ( ) ;
2008-06-26 10:47:45 +02:00
ret = 0 ;
break ;
case WDIOC_SETTIMEOUT :
ret = get_user ( time , ( int * ) arg ) ;
if ( ret )
break ;
2009-06-01 13:56:02 -04:00
if ( orion_wdt_settimeout ( time ) ) {
2008-06-26 10:47:45 +02:00
ret = - EINVAL ;
break ;
}
2009-06-01 13:56:02 -04:00
orion_wdt_ping ( ) ;
2008-06-26 10:47:45 +02:00
/* Fall through */
case WDIOC_GETTIMEOUT :
ret = put_user ( heartbeat , ( int * ) arg ) ;
break ;
case WDIOC_GETTIMELEFT :
2009-06-01 13:56:02 -04:00
if ( orion_wdt_get_timeleft ( & time ) ) {
2008-06-26 10:47:45 +02:00
ret = - EINVAL ;
break ;
}
ret = put_user ( time , ( int * ) arg ) ;
break ;
}
return ret ;
}
2009-06-01 13:56:02 -04:00
static int orion_wdt_release ( struct inode * inode , struct file * file )
2008-06-26 10:47:45 +02:00
{
if ( test_bit ( WDT_OK_TO_CLOSE , & wdt_status ) )
2009-06-01 13:56:02 -04:00
orion_wdt_disable ( ) ;
2008-06-26 10:47:45 +02:00
else
2012-02-15 15:06:19 -08:00
pr_crit ( " Device closed unexpectedly - timer will not stop \n " ) ;
2008-06-26 10:47:45 +02:00
clear_bit ( WDT_IN_USE , & wdt_status ) ;
clear_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
return 0 ;
}
2009-06-01 13:56:02 -04:00
static const struct file_operations orion_wdt_fops = {
2008-06-26 10:47:45 +02:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
2009-06-01 13:56:02 -04:00
. write = orion_wdt_write ,
. unlocked_ioctl = orion_wdt_ioctl ,
. open = orion_wdt_open ,
. release = orion_wdt_release ,
2008-06-26 10:47:45 +02:00
} ;
2009-06-01 13:56:02 -04:00
static struct miscdevice orion_wdt_miscdev = {
2008-06-26 10:47:45 +02:00
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
2009-06-01 13:56:02 -04:00
. fops = & orion_wdt_fops ,
2008-06-26 10:47:45 +02:00
} ;
2009-06-01 13:56:02 -04:00
static int __devinit orion_wdt_probe ( struct platform_device * pdev )
2008-06-26 10:47:45 +02:00
{
2009-06-01 13:56:02 -04:00
struct orion_wdt_platform_data * pdata = pdev - > dev . platform_data ;
2012-03-15 00:33:26 +00:00
struct resource * res ;
2008-06-26 10:47:45 +02:00
int ret ;
2009-02-24 14:59:22 -08:00
if ( pdata ) {
wdt_tclk = pdata - > tclk ;
} else {
2012-02-15 15:06:19 -08:00
pr_err ( " misses platform data \n " ) ;
2009-02-24 14:59:22 -08:00
return - ENODEV ;
}
2012-03-15 00:33:26 +00:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
wdt_reg = ioremap ( res - > start , resource_size ( res ) ) ;
2009-06-01 13:56:02 -04:00
if ( orion_wdt_miscdev . parent )
2009-02-24 14:59:22 -08:00
return - EBUSY ;
2009-06-01 13:56:02 -04:00
orion_wdt_miscdev . parent = & pdev - > dev ;
2009-02-24 14:59:22 -08:00
wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk ;
2009-06-01 13:56:02 -04:00
if ( orion_wdt_settimeout ( heartbeat ) )
2009-02-24 14:59:22 -08:00
heartbeat = wdt_max_duration ;
2008-10-02 09:32:45 +00:00
2009-06-01 13:56:02 -04:00
ret = misc_register ( & orion_wdt_miscdev ) ;
2009-02-24 14:59:22 -08:00
if ( ret )
return ret ;
2012-02-15 15:06:19 -08:00
pr_info ( " Initial timeout %d sec%s \n " ,
heartbeat , nowayout ? " , nowayout " : " " ) ;
2009-02-24 14:59:22 -08:00
return 0 ;
}
2009-06-01 13:56:02 -04:00
static int __devexit orion_wdt_remove ( struct platform_device * pdev )
2009-02-24 14:59:22 -08:00
{
int ret ;
if ( test_bit ( WDT_IN_USE , & wdt_status ) ) {
2009-06-01 13:56:02 -04:00
orion_wdt_disable ( ) ;
2009-02-24 14:59:22 -08:00
clear_bit ( WDT_IN_USE , & wdt_status ) ;
}
2009-06-01 13:56:02 -04:00
ret = misc_deregister ( & orion_wdt_miscdev ) ;
2009-02-24 14:59:22 -08:00
if ( ! ret )
2009-06-01 13:56:02 -04:00
orion_wdt_miscdev . parent = NULL ;
2008-06-26 10:47:45 +02:00
return ret ;
}
2009-06-01 13:56:02 -04:00
static void orion_wdt_shutdown ( struct platform_device * pdev )
2009-02-20 19:44:59 +01:00
{
if ( test_bit ( WDT_IN_USE , & wdt_status ) )
2009-06-01 13:56:02 -04:00
orion_wdt_disable ( ) ;
2009-02-20 19:44:59 +01:00
}
2009-06-01 13:56:02 -04:00
static struct platform_driver orion_wdt_driver = {
. probe = orion_wdt_probe ,
. remove = __devexit_p ( orion_wdt_remove ) ,
. shutdown = orion_wdt_shutdown ,
2009-02-24 14:59:22 -08:00
. driver = {
. owner = THIS_MODULE ,
2009-06-01 13:56:02 -04:00
. name = " orion_wdt " ,
2009-02-24 14:59:22 -08:00
} ,
} ;
2011-11-29 13:56:27 +08:00
module_platform_driver ( orion_wdt_driver ) ;
2008-06-26 10:47:45 +02:00
MODULE_AUTHOR ( " Sylver Bruneau <sylver.bruneau@googlemail.com> " ) ;
2009-06-01 13:56:02 -04:00
MODULE_DESCRIPTION ( " Orion Processor Watchdog " ) ;
2008-06-26 10:47:45 +02:00
module_param ( heartbeat , int , 0 ) ;
2009-02-20 19:44:59 +01:00
MODULE_PARM_DESC ( heartbeat , " Initial watchdog heartbeat in seconds " ) ;
2008-06-26 10:47:45 +02:00
2012-03-05 16:51:11 +01:00
module_param ( nowayout , bool , 0 ) ;
2009-02-20 19:44:59 +01:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2008-06-26 10:47:45 +02:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;