2006-08-14 23:55:29 +04:00
/*
* Watchdog implementation for GPI h / w found on PMC - Sierra RM9xxx
* chips .
*
* Copyright ( C ) 2004 by Basler Vision Technologies AG
* Author : Thomas Koeller < thomas . koeller @ baslerweb . 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/platform_device.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/interrupt.h>
# include <linux/fs.h>
# include <linux/reboot.h>
2006-11-18 01:50:06 +03:00
# include <linux/notifier.h>
2006-08-14 23:55:29 +04:00
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
2008-05-19 17:07:54 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2006-08-14 23:55:29 +04:00
# include <asm/atomic.h>
# include <asm/processor.h>
# include <asm/system.h>
# include <asm/rm9k-ocd.h>
# include <rm9k_wdt.h>
# define CLOCK 125000000
# define MAX_TIMEOUT_SECONDS 32
# define CPCCR 0x0080
# define CPGIG1SR 0x0044
# define CPGIG1ER 0x0054
/* Function prototypes */
2006-12-06 03:45:39 +03:00
static irqreturn_t wdt_gpi_irqhdl ( int , void * ) ;
2006-11-18 01:50:06 +03:00
static void wdt_gpi_start ( void ) ;
static void wdt_gpi_stop ( void ) ;
2006-08-14 23:55:29 +04:00
static void wdt_gpi_set_timeout ( unsigned int ) ;
static int wdt_gpi_open ( struct inode * , struct file * ) ;
static int wdt_gpi_release ( struct inode * , struct file * ) ;
2008-05-19 17:07:54 +04:00
static ssize_t wdt_gpi_write ( struct file * , const char __user * , size_t ,
loff_t * ) ;
2006-08-14 23:55:29 +04:00
static long wdt_gpi_ioctl ( struct file * , unsigned int , unsigned long ) ;
static int wdt_gpi_notify ( struct notifier_block * , unsigned long , void * ) ;
2008-05-19 17:07:54 +04:00
static const struct resource * wdt_gpi_get_resource ( struct platform_device * ,
const char * , unsigned int ) ;
2009-02-06 18:40:12 +03:00
static int __init wdt_gpi_probe ( struct platform_device * ) ;
static int __exit wdt_gpi_remove ( struct platform_device * ) ;
2006-08-14 23:55:29 +04:00
static const char wdt_gpi_name [ ] = " wdt_gpi " ;
static atomic_t opencnt ;
static int expect_close ;
2006-11-18 01:50:06 +03:00
static int locked ;
2006-08-14 23:55:29 +04:00
/* These are set from device resources */
2008-05-19 17:07:54 +04:00
static void __iomem * wd_regs ;
2006-08-14 23:55:29 +04:00
static unsigned int wd_irq , wd_ctr ;
/* Module arguments */
static int timeout = MAX_TIMEOUT_SECONDS ;
module_param ( timeout , int , 0444 ) ;
2006-11-17 23:51:35 +03:00
MODULE_PARM_DESC ( timeout , " Watchdog timeout in seconds " ) ;
2006-08-14 23:55:29 +04:00
static unsigned long resetaddr = 0xbffdc200 ;
module_param ( resetaddr , ulong , 0444 ) ;
2006-11-17 23:51:35 +03:00
MODULE_PARM_DESC ( resetaddr , " Address to write to to force a reset " ) ;
2006-08-14 23:55:29 +04:00
static unsigned long flagaddr = 0xbffdc104 ;
module_param ( flagaddr , ulong , 0444 ) ;
2006-11-17 23:51:35 +03:00
MODULE_PARM_DESC ( flagaddr , " Address to write to boot flags to " ) ;
2006-11-18 01:50:06 +03:00
static int powercycle ;
2006-08-14 23:55:29 +04:00
module_param ( powercycle , bool , 0444 ) ;
2006-11-17 23:51:35 +03:00
MODULE_PARM_DESC ( powercycle , " Cycle power if watchdog expires " ) ;
2006-08-14 23:55:29 +04:00
static int nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0444 ) ;
2006-11-17 23:51:35 +03:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be disabled once started " ) ;
2006-08-14 23:55:29 +04:00
2006-12-06 03:45:26 +03:00
/* Kernel interfaces */
2007-02-12 11:55:32 +03:00
static const struct file_operations fops = {
2006-12-06 03:45:26 +03:00
. owner = THIS_MODULE ,
. open = wdt_gpi_open ,
. release = wdt_gpi_release ,
. write = wdt_gpi_write ,
. unlocked_ioctl = wdt_gpi_ioctl ,
} ;
static struct miscdevice miscdev = {
. minor = WATCHDOG_MINOR ,
. name = wdt_gpi_name ,
. fops = & fops ,
} ;
static struct notifier_block wdt_gpi_shutdown = {
. notifier_call = wdt_gpi_notify ,
} ;
2006-11-18 01:15:48 +03:00
/* Interrupt handler */
2006-12-06 03:45:39 +03:00
static irqreturn_t wdt_gpi_irqhdl ( int irq , void * ctxt )
2006-08-14 23:55:29 +04:00
{
2006-11-18 01:15:48 +03:00
if ( ! unlikely ( __raw_readl ( wd_regs + 0x0008 ) & 0x1 ) )
return IRQ_NONE ;
__raw_writel ( 0x1 , wd_regs + 0x0008 ) ;
2006-08-14 23:55:29 +04:00
2006-11-18 01:50:06 +03:00
printk ( KERN_CRIT " %s: watchdog expired - resetting system \n " ,
2006-11-18 01:15:48 +03:00
wdt_gpi_name ) ;
2006-08-14 23:55:29 +04:00
2006-11-18 01:15:48 +03:00
* ( volatile char * ) flagaddr | = 0x01 ;
* ( volatile char * ) resetaddr = powercycle ? 0x01 : 0x2 ;
iob ( ) ;
while ( 1 )
cpu_relax ( ) ;
2006-08-14 23:55:29 +04:00
}
2006-11-18 01:15:48 +03:00
/* Watchdog functions */
2006-11-18 01:36:08 +03:00
static void wdt_gpi_start ( void )
{
u32 reg ;
lock_titan_regs ( ) ;
reg = titan_readl ( CPGIG1ER ) ;
titan_writel ( reg | ( 0x100 < < wd_ctr ) , CPGIG1ER ) ;
iob ( ) ;
unlock_titan_regs ( ) ;
}
static void wdt_gpi_stop ( void )
{
u32 reg ;
lock_titan_regs ( ) ;
reg = titan_readl ( CPCCR ) & ~ ( 0xf < < ( wd_ctr * 4 ) ) ;
titan_writel ( reg , CPCCR ) ;
reg = titan_readl ( CPGIG1ER ) ;
titan_writel ( reg & ~ ( 0x100 < < wd_ctr ) , CPGIG1ER ) ;
iob ( ) ;
unlock_titan_regs ( ) ;
}
2006-08-14 23:55:29 +04:00
static void wdt_gpi_set_timeout ( unsigned int to )
{
u32 reg ;
const u32 wdval = ( to * CLOCK ) & ~ 0x0000000f ;
lock_titan_regs ( ) ;
reg = titan_readl ( CPCCR ) & ~ ( 0xf < < ( wd_ctr * 4 ) ) ;
titan_writel ( reg , CPCCR ) ;
wmb ( ) ;
__raw_writel ( wdval , wd_regs + 0x0000 ) ;
wmb ( ) ;
titan_writel ( reg | ( 0x2 < < ( wd_ctr * 4 ) ) , CPCCR ) ;
wmb ( ) ;
titan_writel ( reg | ( 0x5 < < ( wd_ctr * 4 ) ) , CPCCR ) ;
iob ( ) ;
unlock_titan_regs ( ) ;
}
2006-11-18 01:15:48 +03:00
/* /dev/watchdog operations */
2006-11-18 01:50:06 +03:00
static int wdt_gpi_open ( struct inode * inode , struct file * file )
2006-08-14 23:55:29 +04:00
{
int res ;
2006-11-18 01:50:06 +03:00
if ( unlikely ( atomic_dec_if_positive ( & opencnt ) < 0 ) )
2006-08-14 23:55:29 +04:00
return - EBUSY ;
expect_close = 0 ;
if ( locked ) {
module_put ( THIS_MODULE ) ;
free_irq ( wd_irq , & miscdev ) ;
locked = 0 ;
}
2007-02-14 11:33:16 +03:00
res = request_irq ( wd_irq , wdt_gpi_irqhdl , IRQF_SHARED | IRQF_DISABLED ,
2006-08-14 23:55:29 +04:00
wdt_gpi_name , & miscdev ) ;
if ( unlikely ( res ) )
return res ;
wdt_gpi_set_timeout ( timeout ) ;
2006-11-18 01:36:08 +03:00
wdt_gpi_start ( ) ;
2006-08-14 23:55:29 +04:00
printk ( KERN_INFO " %s: watchdog started, timeout = %u seconds \n " ,
wdt_gpi_name , timeout ) ;
2006-11-18 01:50:06 +03:00
return nonseekable_open ( inode , file ) ;
2006-08-14 23:55:29 +04:00
}
2006-11-18 01:50:06 +03:00
static int wdt_gpi_release ( struct inode * inode , struct file * file )
2006-08-14 23:55:29 +04:00
{
if ( nowayout ) {
2006-11-18 01:50:06 +03:00
printk ( KERN_INFO " %s: no way out - watchdog left running \n " ,
2006-08-14 23:55:29 +04:00
wdt_gpi_name ) ;
__module_get ( THIS_MODULE ) ;
locked = 1 ;
} else {
if ( expect_close ) {
2006-11-18 01:36:08 +03:00
wdt_gpi_stop ( ) ;
2006-08-14 23:55:29 +04:00
free_irq ( wd_irq , & miscdev ) ;
2008-05-19 17:07:54 +04:00
printk ( KERN_INFO " %s: watchdog stopped \n " ,
wdt_gpi_name ) ;
2006-08-14 23:55:29 +04:00
} else {
2006-11-18 01:50:06 +03:00
printk ( KERN_CRIT " %s: unexpected close() - "
2006-08-14 23:55:29 +04:00
" watchdog left running \n " ,
wdt_gpi_name ) ;
wdt_gpi_set_timeout ( timeout ) ;
__module_get ( THIS_MODULE ) ;
locked = 1 ;
}
}
atomic_inc ( & opencnt ) ;
return 0 ;
}
2008-08-07 00:19:41 +04:00
static ssize_t wdt_gpi_write ( struct file * f , const char __user * d , size_t s ,
loff_t * o )
2006-08-14 23:55:29 +04:00
{
char val ;
wdt_gpi_set_timeout ( timeout ) ;
expect_close = ( s > 0 ) & & ! get_user ( val , d ) & & ( val = = ' V ' ) ;
return s ? 1 : 0 ;
}
2008-05-19 17:07:54 +04:00
static long wdt_gpi_ioctl ( struct file * f , unsigned int cmd , unsigned long arg )
2006-08-14 23:55:29 +04:00
{
long res = - ENOTTY ;
const long size = _IOC_SIZE ( cmd ) ;
int stat ;
2006-11-18 01:50:06 +03:00
void __user * argp = ( void __user * ) arg ;
2006-08-14 23:55:29 +04:00
static struct watchdog_info wdinfo = {
. identity = " RM9xxx/GPI watchdog " ,
. firmware_version = 0 ,
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
} ;
if ( unlikely ( _IOC_TYPE ( cmd ) ! = WATCHDOG_IOCTL_BASE ) )
return - ENOTTY ;
if ( ( _IOC_DIR ( cmd ) & _IOC_READ )
& & ! access_ok ( VERIFY_WRITE , arg , size ) )
return - EFAULT ;
if ( ( _IOC_DIR ( cmd ) & _IOC_WRITE )
& & ! access_ok ( VERIFY_READ , arg , size ) )
return - EFAULT ;
expect_close = 0 ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
wdinfo . options = nowayout ?
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING :
2008-05-19 17:07:54 +04:00
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ;
2006-11-18 01:50:06 +03:00
res = __copy_to_user ( argp , & wdinfo , size ) ? - EFAULT : size ;
2006-08-14 23:55:29 +04:00
break ;
case WDIOC_GETSTATUS :
break ;
case WDIOC_GETBOOTSTATUS :
stat = ( * ( volatile char * ) flagaddr & 0x01 )
? WDIOF_CARDRESET : 0 ;
2006-11-18 01:50:06 +03:00
res = __copy_to_user ( argp , & stat , size ) ?
2006-08-14 23:55:29 +04:00
- EFAULT : size ;
break ;
case WDIOC_SETOPTIONS :
break ;
case WDIOC_KEEPALIVE :
wdt_gpi_set_timeout ( timeout ) ;
res = size ;
break ;
case WDIOC_SETTIMEOUT :
{
int val ;
2006-11-18 01:50:06 +03:00
if ( unlikely ( __copy_from_user ( & val , argp , size ) ) ) {
2006-08-14 23:55:29 +04:00
res = - EFAULT ;
break ;
}
2006-11-18 01:50:06 +03:00
if ( val > MAX_TIMEOUT_SECONDS )
val = MAX_TIMEOUT_SECONDS ;
2006-08-14 23:55:29 +04:00
timeout = val ;
wdt_gpi_set_timeout ( val ) ;
res = size ;
2006-11-18 01:50:06 +03:00
printk ( KERN_INFO " %s: timeout set to %u seconds \n " ,
2006-08-14 23:55:29 +04:00
wdt_gpi_name , timeout ) ;
}
break ;
case WDIOC_GETTIMEOUT :
2006-11-18 01:50:06 +03:00
res = __copy_to_user ( argp , & timeout , size ) ?
2006-08-14 23:55:29 +04:00
- EFAULT : size ;
break ;
}
return res ;
}
2006-11-18 01:50:06 +03:00
/* Shutdown notifier */
2008-08-07 00:19:41 +04:00
static int wdt_gpi_notify ( struct notifier_block * this , unsigned long code ,
void * unused )
2006-08-14 23:55:29 +04:00
{
2006-11-18 01:36:08 +03:00
if ( code = = SYS_DOWN | | code = = SYS_HALT )
wdt_gpi_stop ( ) ;
2006-08-14 23:55:29 +04:00
return NOTIFY_DONE ;
}
2006-11-18 01:15:48 +03:00
/* Init & exit procedures */
2008-08-07 00:19:41 +04:00
static const struct resource * wdt_gpi_get_resource ( struct platform_device * pdv ,
const char * name , unsigned int type )
2006-11-18 01:15:48 +03:00
{
char buf [ 80 ] ;
2009-09-02 13:10:07 +04:00
if ( snprintf ( buf , sizeof ( buf ) , " %s_0 " , name ) > = sizeof ( buf ) )
2006-11-18 01:15:48 +03:00
return NULL ;
return platform_get_resource_byname ( pdv , type , buf ) ;
}
2009-04-15 00:30:55 +04:00
/* No hotplugging on the platform bus - use __devinit */
static int __devinit wdt_gpi_probe ( struct platform_device * pdv )
2006-11-18 01:15:48 +03:00
{
int res ;
const struct resource
* const rr = wdt_gpi_get_resource ( pdv , WDT_RESOURCE_REGS ,
IORESOURCE_MEM ) ,
* const ri = wdt_gpi_get_resource ( pdv , WDT_RESOURCE_IRQ ,
IORESOURCE_IRQ ) ,
* const rc = wdt_gpi_get_resource ( pdv , WDT_RESOURCE_COUNTER ,
0 ) ;
if ( unlikely ( ! rr | | ! ri | | ! rc ) )
return - ENXIO ;
wd_regs = ioremap_nocache ( rr - > start , rr - > end + 1 - rr - > start ) ;
if ( unlikely ( ! wd_regs ) )
return - ENOMEM ;
wd_irq = ri - > start ;
wd_ctr = rc - > start ;
res = misc_register ( & miscdev ) ;
if ( res )
iounmap ( wd_regs ) ;
else
register_reboot_notifier ( & wdt_gpi_shutdown ) ;
return res ;
}
2009-04-15 00:30:55 +04:00
static int __devexit wdt_gpi_remove ( struct platform_device * dev )
2006-11-18 01:15:48 +03:00
{
int res ;
unregister_reboot_notifier ( & wdt_gpi_shutdown ) ;
res = misc_deregister ( & miscdev ) ;
iounmap ( wd_regs ) ;
wd_regs = NULL ;
return res ;
}
/* Device driver init & exit */
2009-02-06 18:40:12 +03:00
static struct platform_driver wgt_gpi_driver = {
. driver = {
. name = wdt_gpi_name ,
. owner = THIS_MODULE ,
} ,
2006-11-18 01:15:48 +03:00
. probe = wdt_gpi_probe ,
2009-02-06 18:40:12 +03:00
. remove = __devexit_p ( wdt_gpi_remove ) ,
2006-11-18 01:15:48 +03:00
} ;
2006-08-14 23:55:29 +04:00
static int __init wdt_gpi_init_module ( void )
{
atomic_set ( & opencnt , 1 ) ;
if ( timeout > MAX_TIMEOUT_SECONDS )
timeout = MAX_TIMEOUT_SECONDS ;
2009-02-06 18:40:12 +03:00
return platform_driver_register ( & wdt_gpi_driver ) ;
2006-08-14 23:55:29 +04:00
}
static void __exit wdt_gpi_cleanup_module ( void )
{
2009-02-06 18:40:12 +03:00
platform_driver_unregister ( & wdt_gpi_driver ) ;
2006-08-14 23:55:29 +04:00
}
module_init ( wdt_gpi_init_module ) ;
module_exit ( wdt_gpi_cleanup_module ) ;
MODULE_AUTHOR ( " Thomas Koeller <thomas.koeller@baslerweb.com> " ) ;
MODULE_DESCRIPTION ( " Basler eXcite watchdog driver for gpi devices " ) ;
MODULE_VERSION ( " 0.1 " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
2006-11-17 23:51:35 +03:00