2005-08-17 11:01:33 +04:00
/*
* mv64x60_wdt . c - MV64X60 ( Marvell Discovery ) watchdog userspace interface
*
* Author : James Chapman < jchapman @ katalix . com >
*
* Platform - specific setup code should configure the dog to generate
* interrupt or reset as required . This code only enables / disables
* and services the watchdog .
*
* Derived from mpc8xx_wdt . c , with the following copyright .
2008-05-19 17:07:26 +04:00
*
2005-08-17 11:01:33 +04:00
* 2002 ( c ) Florian Schirmer < jolt @ tuxbox . org > 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-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-08-17 11:01:33 +04:00
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/watchdog.h>
2005-10-29 22:07:23 +04:00
# include <linux/platform_device.h>
2007-07-24 22:12:24 +04:00
# include <linux/mv643xx.h>
2008-05-19 17:07:26 +04:00
# include <linux/uaccess.h>
# include <linux/io.h>
2005-08-17 11:01:33 +04:00
2007-07-24 22:09:18 +04:00
# define MV64x60_WDT_WDC_OFFSET 0
2007-07-24 22:31:25 +04:00
/*
* The watchdog configuration register contains a pair of 2 - bit fields ,
* 1. a reload field , bits 27 - 26 , which triggers a reload of
* the countdown register , and
* 2. an enable field , bits 25 - 24 , which toggles between
* enabling and disabling the watchdog timer .
* Bit 31 is a read - only field which indicates whether the
* watchdog timer is currently enabled .
*
* The low 24 bits contain the timer reload value .
*/
# define MV64x60_WDC_ENABLE_SHIFT 24
# define MV64x60_WDC_SERVICE_SHIFT 26
# define MV64x60_WDC_ENABLED_SHIFT 31
# define MV64x60_WDC_ENABLED_TRUE 1
# define MV64x60_WDC_ENABLED_FALSE 0
2005-08-17 11:01:33 +04:00
/* Flags bits */
# define MV64x60_WDOG_FLAG_OPENED 0
static unsigned long wdt_flags ;
static int wdt_status ;
2007-07-24 22:09:18 +04:00
static void __iomem * mv64x60_wdt_regs ;
2005-08-17 11:01:33 +04:00
static int mv64x60_wdt_timeout ;
2007-07-24 22:31:25 +04:00
static int mv64x60_wdt_count ;
2007-07-24 22:15:26 +04:00
static unsigned int bus_clk ;
2007-07-24 22:18:14 +04:00
static char expect_close ;
2007-07-24 22:31:25 +04:00
static DEFINE_SPINLOCK ( mv64x60_wdt_spinlock ) ;
2005-08-17 11:01:33 +04:00
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:07:26 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2007-07-24 22:17:23 +04:00
2007-07-24 22:31:25 +04:00
static int mv64x60_wdt_toggle_wdc ( int enabled_predicate , int field_shift )
2005-08-17 11:01:33 +04:00
{
2007-07-24 22:31:25 +04:00
u32 data ;
u32 enabled ;
int ret = 0 ;
spin_lock ( & mv64x60_wdt_spinlock ) ;
data = readl ( mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET ) ;
enabled = ( data > > MV64x60_WDC_ENABLED_SHIFT ) & 1 ;
/* only toggle the requested field if enabled state matches predicate */
if ( ( enabled ^ enabled_predicate ) = = 0 ) {
/* We write a 1, then a 2 -- to the appropriate field */
data = ( 1 < < field_shift ) | mv64x60_wdt_count ;
writel ( data , mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET ) ;
data = ( 2 < < field_shift ) | mv64x60_wdt_count ;
writel ( data , mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET ) ;
ret = 1 ;
}
spin_unlock ( & mv64x60_wdt_spinlock ) ;
return ret ;
2005-08-17 11:01:33 +04:00
}
static void mv64x60_wdt_service ( void )
{
2007-07-24 22:31:25 +04:00
mv64x60_wdt_toggle_wdc ( MV64x60_WDC_ENABLED_TRUE ,
MV64x60_WDC_SERVICE_SHIFT ) ;
2005-08-17 11:01:33 +04:00
}
2007-07-24 22:31:25 +04:00
static void mv64x60_wdt_handler_enable ( void )
2005-08-17 11:01:33 +04:00
{
2007-07-24 22:31:25 +04:00
if ( mv64x60_wdt_toggle_wdc ( MV64x60_WDC_ENABLED_FALSE ,
MV64x60_WDC_ENABLE_SHIFT ) ) {
mv64x60_wdt_service ( ) ;
2012-02-16 03:06:19 +04:00
pr_notice ( " watchdog activated \n " ) ;
2005-08-17 11:01:33 +04:00
}
}
2007-07-24 22:31:25 +04:00
static void mv64x60_wdt_handler_disable ( void )
2005-08-17 11:01:33 +04:00
{
2007-07-24 22:31:25 +04:00
if ( mv64x60_wdt_toggle_wdc ( MV64x60_WDC_ENABLED_TRUE ,
MV64x60_WDC_ENABLE_SHIFT ) )
2012-02-16 03:06:19 +04:00
pr_notice ( " watchdog deactivated \n " ) ;
2005-08-17 11:01:33 +04:00
}
2007-07-24 22:31:25 +04:00
static void mv64x60_wdt_set_timeout ( unsigned int timeout )
2007-07-24 22:15:26 +04:00
{
/* maximum bus cycle count is 0xFFFFFFFF */
if ( timeout > 0xFFFFFFFF / bus_clk )
timeout = 0xFFFFFFFF / bus_clk ;
2007-07-24 22:31:25 +04:00
mv64x60_wdt_count = timeout * bus_clk > > 8 ;
2007-07-24 22:15:26 +04:00
mv64x60_wdt_timeout = timeout ;
}
2005-08-17 11:01:33 +04:00
static int mv64x60_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( MV64x60_WDOG_FLAG_OPENED , & wdt_flags ) )
return - EBUSY ;
2007-07-24 22:17:23 +04:00
if ( nowayout )
__module_get ( THIS_MODULE ) ;
2005-08-17 11:01:33 +04:00
mv64x60_wdt_handler_enable ( ) ;
2007-07-24 22:13:26 +04:00
return nonseekable_open ( inode , file ) ;
2005-08-17 11:01:33 +04:00
}
static int mv64x60_wdt_release ( struct inode * inode , struct file * file )
{
2007-07-24 22:18:14 +04:00
if ( expect_close = = 42 )
2007-07-24 22:17:23 +04:00
mv64x60_wdt_handler_disable ( ) ;
2007-07-24 22:18:14 +04:00
else {
2012-02-16 03:06:19 +04:00
pr_crit ( " unexpected close, not stopping timer! \n " ) ;
2007-07-24 22:18:14 +04:00
mv64x60_wdt_service ( ) ;
}
expect_close = 0 ;
2005-08-17 11:01:33 +04:00
clear_bit ( MV64x60_WDOG_FLAG_OPENED , & wdt_flags ) ;
return 0 ;
}
2005-09-29 03:42:27 +04:00
static ssize_t mv64x60_wdt_write ( struct file * file , const char __user * data ,
2008-05-19 17:07:26 +04:00
size_t len , loff_t * ppos )
2005-08-17 11:01:33 +04:00
{
2007-07-24 22:18:14 +04:00
if ( len ) {
if ( ! nowayout ) {
size_t i ;
expect_close = 0 ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
2008-05-19 17:07:26 +04:00
if ( get_user ( c , data + i ) )
2007-07-24 22:18:14 +04:00
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 42 ;
}
}
2005-08-17 11:01:33 +04:00
mv64x60_wdt_service ( ) ;
2007-07-24 22:18:14 +04:00
}
2005-08-17 11:01:33 +04:00
return len ;
}
2008-05-19 17:07:26 +04:00
static long mv64x60_wdt_ioctl ( struct file * file ,
unsigned int cmd , unsigned long arg )
2005-08-17 11:01:33 +04:00
{
2007-07-24 22:15:26 +04:00
int timeout ;
2007-07-24 22:16:29 +04:00
int options ;
2005-09-29 03:42:27 +04:00
void __user * argp = ( void __user * ) arg ;
2009-12-26 21:55:22 +03:00
static const struct watchdog_info info = {
2007-07-24 22:15:26 +04:00
. options = WDIOF_SETTIMEOUT |
2007-07-24 22:18:14 +04:00
WDIOF_MAGICCLOSE |
2007-07-24 22:15:26 +04:00
WDIOF_KEEPALIVEPING ,
2005-08-17 11:01:33 +04:00
. firmware_version = 0 ,
. identity = " MV64x60 watchdog " ,
} ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
2005-09-29 03:42:27 +04:00
if ( copy_to_user ( argp , & info , sizeof ( info ) ) )
2005-08-17 11:01:33 +04:00
return - EFAULT ;
break ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
2005-09-29 03:42:27 +04:00
if ( put_user ( wdt_status , ( int __user * ) argp ) )
2005-08-17 11:01:33 +04:00
return - EFAULT ;
wdt_status & = ~ WDIOF_KEEPALIVEPING ;
break ;
case WDIOC_GETTEMP :
return - EOPNOTSUPP ;
case WDIOC_SETOPTIONS :
2007-07-24 22:16:29 +04:00
if ( get_user ( options , ( int __user * ) argp ) )
return - EFAULT ;
if ( options & WDIOS_DISABLECARD )
mv64x60_wdt_handler_disable ( ) ;
if ( options & WDIOS_ENABLECARD )
mv64x60_wdt_handler_enable ( ) ;
break ;
2005-08-17 11:01:33 +04:00
case WDIOC_KEEPALIVE :
mv64x60_wdt_service ( ) ;
wdt_status | = WDIOF_KEEPALIVEPING ;
break ;
case WDIOC_SETTIMEOUT :
2007-07-24 22:15:26 +04:00
if ( get_user ( timeout , ( int __user * ) argp ) )
return - EFAULT ;
mv64x60_wdt_set_timeout ( timeout ) ;
/* Fall through */
2005-08-17 11:01:33 +04:00
case WDIOC_GETTIMEOUT :
2007-07-24 22:14:21 +04:00
if ( put_user ( mv64x60_wdt_timeout , ( int __user * ) argp ) )
2005-08-17 11:01:33 +04:00
return - EFAULT ;
break ;
default :
2006-09-09 19:34:31 +04:00
return - ENOTTY ;
2005-08-17 11:01:33 +04:00
}
return 0 ;
}
2006-07-03 11:24:21 +04:00
static const struct file_operations mv64x60_wdt_fops = {
2005-08-17 11:01:33 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = mv64x60_wdt_write ,
2008-05-19 17:07:26 +04:00
. unlocked_ioctl = mv64x60_wdt_ioctl ,
2005-08-17 11:01:33 +04:00
. open = mv64x60_wdt_open ,
. release = mv64x60_wdt_release ,
} ;
static struct miscdevice mv64x60_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & mv64x60_wdt_fops ,
} ;
2012-11-19 22:21:41 +04:00
static int mv64x60_wdt_probe ( struct platform_device * dev )
2005-08-17 11:01:33 +04:00
{
2005-11-10 01:32:44 +03:00
struct mv64x60_wdt_pdata * pdata = dev - > dev . platform_data ;
2007-07-24 22:09:18 +04:00
struct resource * r ;
2007-07-24 22:15:26 +04:00
int timeout = 10 ;
2005-08-17 11:01:33 +04:00
2007-07-24 22:15:26 +04:00
bus_clk = 133 ; /* in MHz */
2005-08-17 11:01:33 +04:00
if ( pdata ) {
2007-07-24 22:15:26 +04:00
timeout = pdata - > timeout ;
2005-08-17 11:01:33 +04:00
bus_clk = pdata - > bus_clk ;
}
2007-07-24 22:15:26 +04:00
/* Since bus_clk is truncated MHz, actual frequency could be
* up to 1 MHz higher . Round up , since it ' s better to time out
* too late than too soon .
*/
bus_clk + + ;
bus_clk * = 1000000 ; /* convert to Hz */
2007-07-24 22:09:18 +04:00
r = platform_get_resource ( dev , IORESOURCE_MEM , 0 ) ;
if ( ! r )
return - ENODEV ;
2009-12-04 20:24:04 +03:00
mv64x60_wdt_regs = ioremap ( r - > start , resource_size ( r ) ) ;
2007-07-24 22:09:18 +04:00
if ( mv64x60_wdt_regs = = NULL )
return - ENOMEM ;
2005-08-17 11:01:33 +04:00
2007-07-24 22:15:26 +04:00
mv64x60_wdt_set_timeout ( timeout ) ;
2005-08-17 11:01:33 +04:00
2007-07-24 22:19:47 +04:00
mv64x60_wdt_handler_disable ( ) ; /* in case timer was already running */
2005-08-17 11:01:33 +04:00
return misc_register ( & mv64x60_wdt_miscdev ) ;
}
2005-11-10 01:32:44 +03:00
static int __devexit mv64x60_wdt_remove ( struct platform_device * dev )
2005-08-17 11:01:33 +04:00
{
misc_deregister ( & mv64x60_wdt_miscdev ) ;
mv64x60_wdt_handler_disable ( ) ;
2007-07-24 22:09:18 +04:00
iounmap ( mv64x60_wdt_regs ) ;
2005-08-17 11:01:33 +04:00
return 0 ;
}
2005-11-10 01:32:44 +03:00
static struct platform_driver mv64x60_wdt_driver = {
2005-08-17 11:01:33 +04:00
. probe = mv64x60_wdt_probe ,
2012-11-19 22:21:12 +04:00
. remove = mv64x60_wdt_remove ,
2005-11-10 01:32:44 +03:00
. driver = {
. owner = THIS_MODULE ,
. name = MV64x60_WDT_NAME ,
} ,
2005-08-17 11:01:33 +04:00
} ;
static int __init mv64x60_wdt_init ( void )
{
2012-02-16 03:06:19 +04:00
pr_info ( " MV64x60 watchdog driver \n " ) ;
2005-08-17 11:01:33 +04:00
2007-07-24 22:07:38 +04:00
return platform_driver_register ( & mv64x60_wdt_driver ) ;
2005-08-17 11:01:33 +04:00
}
static void __exit mv64x60_wdt_exit ( void )
{
2005-11-10 01:32:44 +03:00
platform_driver_unregister ( & mv64x60_wdt_driver ) ;
2005-08-17 11:01:33 +04:00
}
module_init ( mv64x60_wdt_init ) ;
module_exit ( mv64x60_wdt_exit ) ;
MODULE_AUTHOR ( " James Chapman <jchapman@katalix.com> " ) ;
MODULE_DESCRIPTION ( " MV64x60 watchdog driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
2008-04-11 08:29:23 +04:00
MODULE_ALIAS ( " platform: " MV64x60_WDT_NAME ) ;