2007-09-20 20:06:41 +04:00
/*
* drivers / watchdog / ar7_wdt . c
*
* Copyright ( C ) 2007 Nicolas Thill < nico @ openwrt . org >
* Copyright ( c ) 2005 Enrik Berkhan < Enrik . Berkhan @ akk . org >
*
* Some code taken from :
* National Semiconductor SCx200 Watchdog support
* Copyright ( c ) 2001 , 2002 Christer Weinigel < wingel @ nano - system . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/miscdevice.h>
2009-07-21 14:11:32 +04:00
# include <linux/platform_device.h>
2007-09-20 20:06:41 +04:00
# include <linux/watchdog.h>
# include <linux/fs.h>
# include <linux/ioport.h>
# include <linux/io.h>
# include <linux/uaccess.h>
# include <asm/addrspace.h>
2009-06-03 15:05:48 +04:00
# include <asm/mach-ar7/ar7.h>
2007-09-20 20:06:41 +04:00
# define DRVNAME "ar7_wdt"
# define LONGNAME "TI AR7 Watchdog Timer"
MODULE_AUTHOR ( " Nicolas Thill <nico@openwrt.org> " ) ;
MODULE_DESCRIPTION ( LONGNAME ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
static int margin = 60 ;
module_param ( margin , int , 0 ) ;
MODULE_PARM_DESC ( margin , " Watchdog margin in seconds " ) ;
static int nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , int , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Disable watchdog shutdown on close " ) ;
# define READ_REG(x) readl((void __iomem *)&(x))
# define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
struct ar7_wdt {
u32 kick_lock ;
u32 kick ;
u32 change_lock ;
u32 change ;
u32 disable_lock ;
u32 disable ;
u32 prescale_lock ;
u32 prescale ;
} ;
2008-08-04 20:53:22 +04:00
static unsigned long wdt_is_open ;
static spinlock_t wdt_lock ;
2007-09-20 20:06:41 +04:00
static unsigned expect_close ;
/* XXX currently fixed, allows max margin ~68.72 secs */
# define prescale_value 0xffff
2009-07-21 14:11:32 +04:00
/* Resource of the WDT registers */
static struct resource * ar7_regs_wdt ;
2007-09-20 20:06:41 +04:00
/* Pointer to the remapped WDT IO space */
static struct ar7_wdt * ar7_wdt ;
static void ar7_wdt_kick ( u32 value )
{
WRITE_REG ( ar7_wdt - > kick_lock , 0x5555 ) ;
if ( ( READ_REG ( ar7_wdt - > kick_lock ) & 3 ) = = 1 ) {
WRITE_REG ( ar7_wdt - > kick_lock , 0xaaaa ) ;
if ( ( READ_REG ( ar7_wdt - > kick_lock ) & 3 ) = = 3 ) {
WRITE_REG ( ar7_wdt - > kick , value ) ;
return ;
}
}
printk ( KERN_ERR DRVNAME " : failed to unlock WDT kick reg \n " ) ;
}
static void ar7_wdt_prescale ( u32 value )
{
WRITE_REG ( ar7_wdt - > prescale_lock , 0x5a5a ) ;
if ( ( READ_REG ( ar7_wdt - > prescale_lock ) & 3 ) = = 1 ) {
WRITE_REG ( ar7_wdt - > prescale_lock , 0xa5a5 ) ;
if ( ( READ_REG ( ar7_wdt - > prescale_lock ) & 3 ) = = 3 ) {
WRITE_REG ( ar7_wdt - > prescale , value ) ;
return ;
}
}
printk ( KERN_ERR DRVNAME " : failed to unlock WDT prescale reg \n " ) ;
}
static void ar7_wdt_change ( u32 value )
{
WRITE_REG ( ar7_wdt - > change_lock , 0x6666 ) ;
if ( ( READ_REG ( ar7_wdt - > change_lock ) & 3 ) = = 1 ) {
WRITE_REG ( ar7_wdt - > change_lock , 0xbbbb ) ;
if ( ( READ_REG ( ar7_wdt - > change_lock ) & 3 ) = = 3 ) {
WRITE_REG ( ar7_wdt - > change , value ) ;
return ;
}
}
printk ( KERN_ERR DRVNAME " : failed to unlock WDT change reg \n " ) ;
}
static void ar7_wdt_disable ( u32 value )
{
WRITE_REG ( ar7_wdt - > disable_lock , 0x7777 ) ;
if ( ( READ_REG ( ar7_wdt - > disable_lock ) & 3 ) = = 1 ) {
WRITE_REG ( ar7_wdt - > disable_lock , 0xcccc ) ;
if ( ( READ_REG ( ar7_wdt - > disable_lock ) & 3 ) = = 2 ) {
WRITE_REG ( ar7_wdt - > disable_lock , 0xdddd ) ;
if ( ( READ_REG ( ar7_wdt - > disable_lock ) & 3 ) = = 3 ) {
WRITE_REG ( ar7_wdt - > disable , value ) ;
return ;
}
}
}
printk ( KERN_ERR DRVNAME " : failed to unlock WDT disable reg \n " ) ;
}
static void ar7_wdt_update_margin ( int new_margin )
{
u32 change ;
change = new_margin * ( ar7_vbus_freq ( ) / prescale_value ) ;
2008-08-04 20:53:22 +04:00
if ( change < 1 )
change = 1 ;
if ( change > 0xffff )
change = 0xffff ;
2007-09-20 20:06:41 +04:00
ar7_wdt_change ( change ) ;
margin = change * prescale_value / ar7_vbus_freq ( ) ;
printk ( KERN_INFO DRVNAME
" : timer margin %d seconds (prescale %d, change %d, freq %d) \n " ,
margin , prescale_value , change , ar7_vbus_freq ( ) ) ;
}
static void ar7_wdt_enable_wdt ( void )
{
printk ( KERN_DEBUG DRVNAME " : enabling watchdog timer \n " ) ;
ar7_wdt_disable ( 1 ) ;
ar7_wdt_kick ( 1 ) ;
}
static void ar7_wdt_disable_wdt ( void )
{
printk ( KERN_DEBUG DRVNAME " : disabling watchdog timer \n " ) ;
ar7_wdt_disable ( 0 ) ;
}
static int ar7_wdt_open ( struct inode * inode , struct file * file )
{
/* only allow one at a time */
2008-08-04 20:53:22 +04:00
if ( test_and_set_bit ( 0 , & wdt_is_open ) )
2007-09-20 20:06:41 +04:00
return - EBUSY ;
ar7_wdt_enable_wdt ( ) ;
expect_close = 0 ;
return nonseekable_open ( inode , file ) ;
}
static int ar7_wdt_release ( struct inode * inode , struct file * file )
{
if ( ! expect_close )
printk ( KERN_WARNING DRVNAME
" : watchdog device closed unexpectedly, "
" will not disable the watchdog timer \n " ) ;
else if ( ! nowayout )
ar7_wdt_disable_wdt ( ) ;
2008-08-04 20:53:22 +04:00
clear_bit ( 0 , & wdt_is_open ) ;
2007-09-20 20:06:41 +04:00
return 0 ;
}
static ssize_t ar7_wdt_write ( struct file * file , const char * data ,
size_t len , loff_t * ppos )
{
/* check for a magic close character */
if ( len ) {
size_t i ;
2008-08-04 20:53:22 +04:00
spin_lock ( & wdt_lock ) ;
2007-09-20 20:06:41 +04:00
ar7_wdt_kick ( 1 ) ;
2008-08-04 20:53:22 +04:00
spin_unlock ( & wdt_lock ) ;
2007-09-20 20:06:41 +04:00
expect_close = 0 ;
for ( i = 0 ; i < len ; + + i ) {
char c ;
2008-08-07 00:19:41 +04:00
if ( get_user ( c , data + i ) )
2007-09-20 20:06:41 +04:00
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 1 ;
}
}
return len ;
}
2008-08-04 20:53:22 +04:00
static long ar7_wdt_ioctl ( struct file * file ,
unsigned int cmd , unsigned long arg )
2007-09-20 20:06:41 +04:00
{
static struct watchdog_info ident = {
. identity = LONGNAME ,
. firmware_version = 1 ,
2009-05-11 22:33:00 +04:00
. options = ( WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ) ,
2007-09-20 20:06:41 +04:00
} ;
int new_margin ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
if ( copy_to_user ( ( struct watchdog_info * ) arg , & ident ,
sizeof ( ident ) ) )
return - EFAULT ;
return 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
if ( put_user ( 0 , ( int * ) arg ) )
return - EFAULT ;
return 0 ;
case WDIOC_KEEPALIVE :
ar7_wdt_kick ( 1 ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_margin , ( int * ) arg ) )
return - EFAULT ;
if ( new_margin < 1 )
return - EINVAL ;
2008-08-04 20:53:22 +04:00
spin_lock ( & wdt_lock ) ;
2007-09-20 20:06:41 +04:00
ar7_wdt_update_margin ( new_margin ) ;
ar7_wdt_kick ( 1 ) ;
2008-08-04 20:53:22 +04:00
spin_unlock ( & wdt_lock ) ;
2007-09-20 20:06:41 +04:00
case WDIOC_GETTIMEOUT :
if ( put_user ( margin , ( int * ) arg ) )
return - EFAULT ;
return 0 ;
2008-07-18 15:41:17 +04:00
default :
return - ENOTTY ;
2007-09-20 20:06:41 +04:00
}
}
2008-01-22 22:48:10 +03:00
static const struct file_operations ar7_wdt_fops = {
2007-09-20 20:06:41 +04:00
. owner = THIS_MODULE ,
. write = ar7_wdt_write ,
2008-08-04 20:53:22 +04:00
. unlocked_ioctl = ar7_wdt_ioctl ,
2007-09-20 20:06:41 +04:00
. open = ar7_wdt_open ,
. release = ar7_wdt_release ,
} ;
static struct miscdevice ar7_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & ar7_wdt_fops ,
} ;
2009-07-21 14:11:32 +04:00
static int __devinit ar7_wdt_probe ( struct platform_device * pdev )
2007-09-20 20:06:41 +04:00
{
int rc ;
2008-08-04 20:53:22 +04:00
spin_lock_init ( & wdt_lock ) ;
2009-07-21 14:11:32 +04:00
ar7_regs_wdt =
platform_get_resource_byname ( pdev , IORESOURCE_MEM , " regs " ) ;
if ( ! ar7_regs_wdt ) {
printk ( KERN_ERR DRVNAME " : could not get registers resource \n " ) ;
rc = - ENODEV ;
goto out ;
}
2007-09-20 20:06:41 +04:00
2009-07-21 14:11:32 +04:00
if ( ! request_mem_region ( ar7_regs_wdt - > start ,
resource_size ( ar7_regs_wdt ) , LONGNAME ) ) {
2007-09-20 20:06:41 +04:00
printk ( KERN_WARNING DRVNAME " : watchdog I/O region busy \n " ) ;
2009-07-21 14:11:32 +04:00
rc = - EBUSY ;
goto out ;
2007-09-20 20:06:41 +04:00
}
2009-07-21 14:11:32 +04:00
ar7_wdt = ioremap ( ar7_regs_wdt - > start , resource_size ( ar7_regs_wdt ) ) ;
if ( ! ar7_wdt ) {
printk ( KERN_ERR DRVNAME " : could not ioremap registers \n " ) ;
rc = - ENXIO ;
2009-08-31 17:49:14 +04:00
goto out_mem_region ;
2009-07-21 14:11:32 +04:00
}
2007-09-20 20:06:41 +04:00
ar7_wdt_disable_wdt ( ) ;
ar7_wdt_prescale ( prescale_value ) ;
ar7_wdt_update_margin ( margin ) ;
rc = misc_register ( & ar7_wdt_miscdev ) ;
if ( rc ) {
printk ( KERN_ERR DRVNAME " : unable to register misc device \n " ) ;
2009-07-21 14:11:32 +04:00
goto out_alloc ;
2007-09-20 20:06:41 +04:00
}
goto out ;
out_alloc :
iounmap ( ar7_wdt ) ;
2009-08-31 17:49:14 +04:00
out_mem_region :
2009-07-21 14:11:32 +04:00
release_mem_region ( ar7_regs_wdt - > start , resource_size ( ar7_regs_wdt ) ) ;
2007-09-20 20:06:41 +04:00
out :
return rc ;
}
2009-07-21 14:11:32 +04:00
static int __devexit ar7_wdt_remove ( struct platform_device * pdev )
2007-09-20 20:06:41 +04:00
{
misc_deregister ( & ar7_wdt_miscdev ) ;
iounmap ( ar7_wdt ) ;
2009-07-21 14:11:32 +04:00
release_mem_region ( ar7_regs_wdt - > start , resource_size ( ar7_regs_wdt ) ) ;
return 0 ;
}
static void ar7_wdt_shutdown ( struct platform_device * pdev )
{
if ( ! nowayout )
ar7_wdt_disable_wdt ( ) ;
}
static struct platform_driver ar7_wdt_driver = {
. probe = ar7_wdt_probe ,
. remove = __devexit_p ( ar7_wdt_remove ) ,
. shutdown = ar7_wdt_shutdown ,
. driver = {
. owner = THIS_MODULE ,
. name = " ar7_wdt " ,
} ,
} ;
static int __init ar7_wdt_init ( void )
{
return platform_driver_register ( & ar7_wdt_driver ) ;
}
static void __exit ar7_wdt_cleanup ( void )
{
platform_driver_unregister ( & ar7_wdt_driver ) ;
2007-09-20 20:06:41 +04:00
}
module_init ( ar7_wdt_init ) ;
module_exit ( ar7_wdt_cleanup ) ;