2007-06-08 03:06:41 +04:00
/*
* Watchdog driver for Atmel AT32AP700X devices
*
* Copyright ( C ) 2005 - 2006 Atmel Corporation
*
* 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 .
2007-10-30 16:56:20 +03:00
*
*
* Errata : WDT Clear is blocked after WDT Reset
*
* A watchdog timer event will , after reset , block writes to the WDT_CLEAR
* register , preventing the program to clear the next Watchdog Timer Reset .
*
* If you still want to use the WDT after a WDT reset a small code can be
* insterted at the startup checking the AVR32_PM . rcause register for WDT reset
* and use a GPIO pin to reset the system . This method requires that one of the
* GPIO pins are available and connected externally to the RESET_N pin . After
* the GPIO pin has pulled down the reset line the GPIO will be reset and leave
* the pin tristated with pullup .
2007-06-08 03:06:41 +04:00
*/
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/miscdevice.h>
# include <linux/fs.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
2007-06-08 03:06:43 +04:00
# include <linux/uaccess.h>
# include <linux/io.h>
2007-07-03 21:59:04 +04:00
# include <linux/spinlock.h>
2007-06-08 03:06:41 +04:00
# define TIMEOUT_MIN 1
# define TIMEOUT_MAX 2
2007-06-19 00:49:35 +04:00
# define TIMEOUT_DEFAULT TIMEOUT_MAX
/* module parameters */
static int timeout = TIMEOUT_DEFAULT ;
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout ,
" Timeout value. Limited to be 1 or 2 seconds. (default= "
__MODULE_STRING ( TIMEOUT_DEFAULT ) " ) " ) ;
2007-06-08 03:06:41 +04:00
2007-06-21 01:36:43 +04:00
static int nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , int , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2007-06-08 03:06:41 +04:00
/* Watchdog registers and write/read macro */
# define WDT_CTRL 0x00
# define WDT_CTRL_EN 0
# define WDT_CTRL_PSEL 8
# define WDT_CTRL_KEY 24
# define WDT_CLR 0x04
2007-10-30 16:56:20 +03:00
# define WDT_RCAUSE 0x10
# define WDT_RCAUSE_POR 0
# define WDT_RCAUSE_EXT 2
# define WDT_RCAUSE_WDT 3
# define WDT_RCAUSE_JTAG 4
# define WDT_RCAUSE_SERP 5
2007-06-08 03:06:41 +04:00
# define WDT_BIT(name) (1 << WDT_##name)
2007-06-17 23:34:23 +04:00
# define WDT_BF(name, value) ((value) << WDT_##name)
2007-06-08 03:06:41 +04:00
2007-06-17 23:34:23 +04:00
# define wdt_readl(dev, reg) \
2007-06-08 03:06:41 +04:00
__raw_readl ( ( dev ) - > regs + WDT_ # # reg )
2007-06-17 23:34:23 +04:00
# define wdt_writel(dev, reg, value) \
2007-06-08 03:06:41 +04:00
__raw_writel ( ( value ) , ( dev ) - > regs + WDT_ # # reg )
struct wdt_at32ap700x {
void __iomem * regs ;
2007-07-03 21:59:04 +04:00
spinlock_t io_lock ;
2007-06-08 03:06:41 +04:00
int timeout ;
2007-10-30 16:56:20 +03:00
int boot_status ;
2007-07-03 21:59:29 +04:00
unsigned long users ;
2007-06-08 03:06:41 +04:00
struct miscdevice miscdev ;
} ;
static struct wdt_at32ap700x * wdt ;
2007-06-21 01:36:43 +04:00
static char expect_release ;
2007-06-08 03:06:41 +04:00
/*
* Disable the watchdog .
*/
2007-06-17 23:34:23 +04:00
static inline void at32_wdt_stop ( void )
2007-06-08 03:06:41 +04:00
{
2007-07-03 21:59:29 +04:00
unsigned long psel ;
2007-07-03 21:59:04 +04:00
spin_lock ( & wdt - > io_lock ) ;
2007-07-03 21:59:29 +04:00
psel = wdt_readl ( wdt , CTRL ) & WDT_BF ( CTRL_PSEL , 0x0f ) ;
2007-06-08 03:06:41 +04:00
wdt_writel ( wdt , CTRL , psel | WDT_BF ( CTRL_KEY , 0x55 ) ) ;
wdt_writel ( wdt , CTRL , psel | WDT_BF ( CTRL_KEY , 0xaa ) ) ;
2007-07-03 21:59:04 +04:00
spin_unlock ( & wdt - > io_lock ) ;
2007-06-08 03:06:41 +04:00
}
/*
* Enable and reset the watchdog .
*/
2007-06-17 23:34:23 +04:00
static inline void at32_wdt_start ( void )
2007-06-08 03:06:41 +04:00
{
/* 0xf is 2^16 divider = 2 sec, 0xe is 2^15 divider = 1 sec */
unsigned long psel = ( wdt - > timeout > 1 ) ? 0xf : 0xe ;
2007-07-03 21:59:04 +04:00
spin_lock ( & wdt - > io_lock ) ;
2007-06-08 03:06:41 +04:00
wdt_writel ( wdt , CTRL , WDT_BIT ( CTRL_EN )
| WDT_BF ( CTRL_PSEL , psel )
| WDT_BF ( CTRL_KEY , 0x55 ) ) ;
wdt_writel ( wdt , CTRL , WDT_BIT ( CTRL_EN )
| WDT_BF ( CTRL_PSEL , psel )
| WDT_BF ( CTRL_KEY , 0xaa ) ) ;
2007-07-03 21:59:04 +04:00
spin_unlock ( & wdt - > io_lock ) ;
2007-06-08 03:06:41 +04:00
}
/*
* Pat the watchdog timer .
*/
2007-06-17 23:34:23 +04:00
static inline void at32_wdt_pat ( void )
2007-06-08 03:06:41 +04:00
{
2007-07-03 21:59:04 +04:00
spin_lock ( & wdt - > io_lock ) ;
2007-06-08 03:06:41 +04:00
wdt_writel ( wdt , CLR , 0x42 ) ;
2007-07-03 21:59:04 +04:00
spin_unlock ( & wdt - > io_lock ) ;
2007-06-08 03:06:41 +04:00
}
/*
* Watchdog device is opened , and watchdog starts running .
*/
static int at32_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 1 , & wdt - > users ) )
return - EBUSY ;
at32_wdt_start ( ) ;
return nonseekable_open ( inode , file ) ;
}
/*
2007-06-21 01:36:43 +04:00
* Close the watchdog device .
2007-06-08 03:06:41 +04:00
*/
static int at32_wdt_close ( struct inode * inode , struct file * file )
{
2007-06-21 01:36:43 +04:00
if ( expect_release = = 42 ) {
at32_wdt_stop ( ) ;
} else {
dev_dbg ( wdt - > miscdev . parent ,
2007-10-30 16:56:20 +03:00
" unexpected close, not stopping watchdog! \n " ) ;
2007-06-21 01:36:43 +04:00
at32_wdt_pat ( ) ;
}
2007-06-08 03:06:41 +04:00
clear_bit ( 1 , & wdt - > users ) ;
2007-06-21 01:36:43 +04:00
expect_release = 0 ;
2007-06-08 03:06:41 +04:00
return 0 ;
}
/*
* Change the watchdog time interval .
*/
static int at32_wdt_settimeout ( int time )
{
/*
* All counting occurs at 1 / SLOW_CLOCK ( 32 kHz ) and max prescaler is
* 2 ^ 16 allowing up to 2 seconds timeout .
*/
if ( ( time < TIMEOUT_MIN ) | | ( time > TIMEOUT_MAX ) )
return - EINVAL ;
2007-06-08 03:06:43 +04:00
/*
* Set new watchdog time . It will be used when at32_wdt_start ( ) is
* called .
*/
2007-06-08 03:06:41 +04:00
wdt - > timeout = time ;
return 0 ;
}
2007-10-30 16:56:20 +03:00
/*
* Get the watchdog status .
*/
static int at32_wdt_get_status ( void )
{
int rcause ;
int status = 0 ;
rcause = wdt_readl ( wdt , RCAUSE ) ;
switch ( rcause ) {
case WDT_BIT ( RCAUSE_EXT ) :
status = WDIOF_EXTERN1 ;
break ;
case WDT_BIT ( RCAUSE_WDT ) :
status = WDIOF_CARDRESET ;
break ;
case WDT_BIT ( RCAUSE_POR ) : /* fall through */
case WDT_BIT ( RCAUSE_JTAG ) : /* fall through */
case WDT_BIT ( RCAUSE_SERP ) : /* fall through */
default :
break ;
}
return status ;
}
2007-06-08 03:06:41 +04:00
static struct watchdog_info at32_wdt_info = {
. identity = " at32ap700x watchdog " ,
2007-06-21 01:36:43 +04:00
. options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
2007-06-08 03:06:41 +04:00
} ;
/*
* Handle commands from user - space .
*/
static int at32_wdt_ioctl ( struct inode * inode , struct file * file ,
unsigned int cmd , unsigned long arg )
{
int ret = - ENOTTY ;
int time ;
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
2007-06-08 03:06:43 +04:00
switch ( cmd ) {
2007-06-08 03:06:41 +04:00
case WDIOC_KEEPALIVE :
at32_wdt_pat ( ) ;
ret = 0 ;
break ;
case WDIOC_GETSUPPORT :
ret = copy_to_user ( argp , & at32_wdt_info ,
sizeof ( at32_wdt_info ) ) ? - EFAULT : 0 ;
break ;
case WDIOC_SETTIMEOUT :
ret = get_user ( time , p ) ;
if ( ret )
break ;
ret = at32_wdt_settimeout ( time ) ;
if ( ret )
break ;
/* Enable new time value */
at32_wdt_start ( ) ;
/* fall through */
case WDIOC_GETTIMEOUT :
ret = put_user ( wdt - > timeout , p ) ;
break ;
2007-10-30 16:56:20 +03:00
case WDIOC_GETSTATUS :
2007-06-08 03:06:41 +04:00
ret = put_user ( 0 , p ) ;
break ;
2007-10-30 16:56:20 +03:00
case WDIOC_GETBOOTSTATUS :
ret = put_user ( wdt - > boot_status , p ) ;
break ;
2007-06-08 03:06:41 +04:00
case WDIOC_SETOPTIONS :
ret = get_user ( time , p ) ;
if ( ret )
break ;
if ( time & WDIOS_DISABLECARD )
at32_wdt_stop ( ) ;
if ( time & WDIOS_ENABLECARD )
at32_wdt_start ( ) ;
ret = 0 ;
break ;
}
return ret ;
}
2007-06-21 01:36:43 +04:00
static ssize_t at32_wdt_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
2007-06-08 03:06:41 +04:00
{
2007-06-21 01:36:43 +04:00
/* See if we got the magic character 'V' and reload the timer */
if ( len ) {
if ( ! nowayout ) {
size_t i ;
/*
* note : just in case someone wrote the magic
* character five months ago . . .
*/
expect_release = 0 ;
/*
* scan to see whether or not we got the magic
* character
*/
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
expect_release = 42 ;
}
}
/* someone wrote to us, we should pat the watchdog */
at32_wdt_pat ( ) ;
}
2007-06-08 03:06:41 +04:00
return len ;
}
static const struct file_operations at32_wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. ioctl = at32_wdt_ioctl ,
. open = at32_wdt_open ,
. release = at32_wdt_close ,
. write = at32_wdt_write ,
} ;
static int __init at32_wdt_probe ( struct platform_device * pdev )
{
struct resource * regs ;
int ret ;
if ( wdt ) {
dev_dbg ( & pdev - > dev , " only 1 wdt instance supported. \n " ) ;
return - EBUSY ;
}
regs = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! regs ) {
dev_dbg ( & pdev - > dev , " missing mmio resource \n " ) ;
return - ENXIO ;
}
wdt = kzalloc ( sizeof ( struct wdt_at32ap700x ) , GFP_KERNEL ) ;
if ( ! wdt ) {
dev_dbg ( & pdev - > dev , " no memory for wdt structure \n " ) ;
return - ENOMEM ;
}
wdt - > regs = ioremap ( regs - > start , regs - > end - regs - > start + 1 ) ;
2007-06-08 22:03:01 +04:00
if ( ! wdt - > regs ) {
ret = - ENOMEM ;
dev_dbg ( & pdev - > dev , " could not map I/O memory \n " ) ;
goto err_free ;
}
2007-10-30 16:56:20 +03:00
2007-07-03 21:59:04 +04:00
spin_lock_init ( & wdt - > io_lock ) ;
2007-10-30 16:56:20 +03:00
wdt - > boot_status = at32_wdt_get_status ( ) ;
/* Work-around for watchdog silicon errata. */
if ( wdt - > boot_status & WDIOF_CARDRESET ) {
dev_info ( & pdev - > dev , " CPU must be reset with external "
" reset or POR due to silicon errata. \n " ) ;
ret = - EIO ;
goto err_iounmap ;
} else {
wdt - > users = 0 ;
}
2007-06-08 03:06:41 +04:00
wdt - > miscdev . minor = WATCHDOG_MINOR ;
wdt - > miscdev . name = " watchdog " ;
wdt - > miscdev . fops = & at32_wdt_fops ;
2007-06-19 00:49:35 +04:00
if ( at32_wdt_settimeout ( timeout ) ) {
at32_wdt_settimeout ( TIMEOUT_DEFAULT ) ;
2007-06-08 03:06:41 +04:00
dev_dbg ( & pdev - > dev ,
" default timeout invalid, set to %d sec. \n " ,
2007-06-19 00:49:35 +04:00
TIMEOUT_DEFAULT ) ;
2007-06-08 03:06:41 +04:00
}
ret = misc_register ( & wdt - > miscdev ) ;
if ( ret ) {
dev_dbg ( & pdev - > dev , " failed to register wdt miscdev \n " ) ;
2007-06-08 22:03:01 +04:00
goto err_iounmap ;
2007-06-08 03:06:41 +04:00
}
platform_set_drvdata ( pdev , wdt ) ;
wdt - > miscdev . parent = & pdev - > dev ;
2007-06-21 01:36:43 +04:00
dev_info ( & pdev - > dev ,
" AT32AP700X WDT at 0x%p, timeout %d sec (nowayout=%d) \n " ,
wdt - > regs , wdt - > timeout , nowayout ) ;
2007-06-08 03:06:41 +04:00
return 0 ;
2007-06-08 22:03:01 +04:00
err_iounmap :
iounmap ( wdt - > regs ) ;
err_free :
2007-06-08 03:06:41 +04:00
kfree ( wdt ) ;
wdt = NULL ;
return ret ;
}
static int __exit at32_wdt_remove ( struct platform_device * pdev )
{
if ( wdt & & platform_get_drvdata ( pdev ) = = wdt ) {
2007-06-21 01:36:43 +04:00
/* Stop the timer before we leave */
if ( ! nowayout )
at32_wdt_stop ( ) ;
2007-06-08 03:06:41 +04:00
misc_deregister ( & wdt - > miscdev ) ;
2007-06-08 22:01:47 +04:00
iounmap ( wdt - > regs ) ;
2007-06-08 03:06:41 +04:00
kfree ( wdt ) ;
wdt = NULL ;
platform_set_drvdata ( pdev , NULL ) ;
}
return 0 ;
}
static void at32_wdt_shutdown ( struct platform_device * pdev )
{
at32_wdt_stop ( ) ;
}
# ifdef CONFIG_PM
static int at32_wdt_suspend ( struct platform_device * pdev , pm_message_t message )
{
at32_wdt_stop ( ) ;
return 0 ;
}
static int at32_wdt_resume ( struct platform_device * pdev )
{
if ( wdt - > users )
at32_wdt_start ( ) ;
return 0 ;
}
2007-06-08 03:08:53 +04:00
# else
# define at32_wdt_suspend NULL
# define at32_wdt_resume NULL
2007-06-08 03:06:41 +04:00
# endif
2008-04-11 08:29:23 +04:00
/* work with hotplug and coldplug */
MODULE_ALIAS ( " platform:at32_wdt " ) ;
2007-06-08 03:06:41 +04:00
static struct platform_driver at32_wdt_driver = {
. remove = __exit_p ( at32_wdt_remove ) ,
. suspend = at32_wdt_suspend ,
. resume = at32_wdt_resume ,
. driver = {
. name = " at32_wdt " ,
. owner = THIS_MODULE ,
} ,
. shutdown = at32_wdt_shutdown ,
} ;
static int __init at32_wdt_init ( void )
{
return platform_driver_probe ( & at32_wdt_driver , at32_wdt_probe ) ;
}
module_init ( at32_wdt_init ) ;
static void __exit at32_wdt_exit ( void )
{
platform_driver_unregister ( & at32_wdt_driver ) ;
}
module_exit ( at32_wdt_exit ) ;
MODULE_AUTHOR ( " Hans-Christian Egtvedt <hcegtvedt@atmel.com> " ) ;
MODULE_DESCRIPTION ( " Watchdog driver for Atmel AT32AP700X " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;