2008-01-21 20:07:00 +03:00
/* Watchdog timer for the Geode GX/LX with the CS5535/CS5536 companion chip
*
* Copyright ( C ) 2006 - 2007 , Advanced Micro Devices , Inc .
*
* 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 .
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/fs.h>
# include <linux/platform_device.h>
# include <linux/reboot.h>
2008-07-15 15:46:11 +04:00
# include <linux/uaccess.h>
2008-01-21 20:07:00 +03:00
# include <asm/geode.h>
# define GEODEWDT_HZ 500
# define GEODEWDT_SCALE 6
# define GEODEWDT_MAX_SECONDS 131
# define WDT_FLAGS_OPEN 1
# define WDT_FLAGS_ORPHAN 2
# define DRV_NAME "geodewdt"
# define WATCHDOG_NAME "Geode GX / LX WDT"
# define WATCHDOG_TIMEOUT 60
static int timeout = WATCHDOG_TIMEOUT ;
module_param ( timeout , int , 0 ) ;
MODULE_PARM_DESC ( timeout , " Watchdog timeout in seconds. 1<= timeout <=131, default= " __MODULE_STRING ( WATCHDOG_TIMEOUT ) " . " ) ;
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 ) " ) " ) ;
static struct platform_device * geodewdt_platform_device ;
static unsigned long wdt_flags ;
static int wdt_timer ;
static int safe_close ;
static void geodewdt_ping ( void )
{
/* Stop the counter */
geode_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , 0 ) ;
/* Reset the counter */
geode_mfgpt_write ( wdt_timer , MFGPT_REG_COUNTER , 0 ) ;
/* Enable the counter */
geode_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , MFGPT_SETUP_CNTEN ) ;
}
static void geodewdt_disable ( void )
{
geode_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , 0 ) ;
geode_mfgpt_write ( wdt_timer , MFGPT_REG_COUNTER , 0 ) ;
}
static int geodewdt_set_heartbeat ( int val )
{
if ( val < 1 | | val > GEODEWDT_MAX_SECONDS )
return - EINVAL ;
geode_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , 0 ) ;
geode_mfgpt_write ( wdt_timer , MFGPT_REG_CMP2 , val * GEODEWDT_HZ ) ;
geode_mfgpt_write ( wdt_timer , MFGPT_REG_COUNTER , 0 ) ;
geode_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , MFGPT_SETUP_CNTEN ) ;
timeout = val ;
return 0 ;
}
static int
geodewdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( WDT_FLAGS_OPEN , & wdt_flags ) )
return - EBUSY ;
if ( ! test_and_clear_bit ( WDT_FLAGS_ORPHAN , & wdt_flags ) )
__module_get ( THIS_MODULE ) ;
geodewdt_ping ( ) ;
return nonseekable_open ( inode , file ) ;
}
static int
geodewdt_release ( struct inode * inode , struct file * file )
{
if ( safe_close ) {
geodewdt_disable ( ) ;
module_put ( THIS_MODULE ) ;
}
else {
printk ( KERN_CRIT " Unexpected close - watchdog is not stopping. \n " ) ;
geodewdt_ping ( ) ;
set_bit ( WDT_FLAGS_ORPHAN , & wdt_flags ) ;
}
clear_bit ( WDT_FLAGS_OPEN , & wdt_flags ) ;
safe_close = 0 ;
return 0 ;
}
static ssize_t
geodewdt_write ( struct file * file , const char __user * data , size_t len ,
loff_t * ppos )
{
if ( len ) {
if ( ! nowayout ) {
size_t i ;
safe_close = 0 ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
safe_close = 1 ;
}
}
geodewdt_ping ( ) ;
}
return len ;
}
static int
geodewdt_ioctl ( struct inode * inode , struct file * file , unsigned int cmd ,
unsigned long arg )
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int interval ;
static struct watchdog_info ident = {
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE ,
. firmware_version = 1 ,
. identity = WATCHDOG_NAME ,
} ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ident ,
sizeof ( ident ) ) ? - EFAULT : 0 ;
break ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_KEEPALIVE :
geodewdt_ping ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( interval , p ) )
return - EFAULT ;
if ( geodewdt_set_heartbeat ( interval ) )
return - EINVAL ;
/* Fall through */
case WDIOC_GETTIMEOUT :
return put_user ( timeout , p ) ;
case WDIOC_SETOPTIONS :
{
int options , ret = - EINVAL ;
if ( get_user ( options , p ) )
return - EFAULT ;
if ( options & WDIOS_DISABLECARD ) {
geodewdt_disable ( ) ;
ret = 0 ;
}
if ( options & WDIOS_ENABLECARD ) {
geodewdt_ping ( ) ;
ret = 0 ;
}
return ret ;
}
default :
return - ENOTTY ;
}
return 0 ;
}
static const struct file_operations geodewdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = geodewdt_write ,
. ioctl = geodewdt_ioctl ,
. open = geodewdt_open ,
. release = geodewdt_release ,
} ;
static struct miscdevice geodewdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & geodewdt_fops
} ;
static int __devinit
geodewdt_probe ( struct platform_device * dev )
{
int ret , timer ;
2008-05-30 19:02:50 +04:00
timer = geode_mfgpt_alloc_timer ( MFGPT_TIMER_ANY , MFGPT_DOMAIN_WORKING ) ;
2008-01-21 20:07:00 +03:00
if ( timer = = - 1 ) {
printk ( KERN_ERR " geodewdt: No timers were available \n " ) ;
return - ENODEV ;
}
wdt_timer = timer ;
/* Set up the timer */
geode_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP ,
GEODEWDT_SCALE | ( 3 < < 8 ) ) ;
/* Set up comparator 2 to reset when the event fires */
geode_mfgpt_toggle_event ( wdt_timer , MFGPT_CMP2 , MFGPT_EVENT_RESET , 1 ) ;
/* Set up the initial timeout */
geode_mfgpt_write ( wdt_timer , MFGPT_REG_CMP2 ,
timeout * GEODEWDT_HZ ) ;
ret = misc_register ( & geodewdt_miscdev ) ;
return ret ;
}
static int __devexit
geodewdt_remove ( struct platform_device * dev )
{
misc_deregister ( & geodewdt_miscdev ) ;
return 0 ;
}
static void
geodewdt_shutdown ( struct platform_device * dev )
{
geodewdt_disable ( ) ;
}
static struct platform_driver geodewdt_driver = {
. probe = geodewdt_probe ,
. remove = __devexit_p ( geodewdt_remove ) ,
. shutdown = geodewdt_shutdown ,
. driver = {
. owner = THIS_MODULE ,
. name = DRV_NAME ,
} ,
} ;
static int __init
geodewdt_init ( void )
{
int ret ;
ret = platform_driver_register ( & geodewdt_driver ) ;
if ( ret )
return ret ;
geodewdt_platform_device = platform_device_register_simple ( DRV_NAME , - 1 , NULL , 0 ) ;
if ( IS_ERR ( geodewdt_platform_device ) ) {
ret = PTR_ERR ( geodewdt_platform_device ) ;
goto err ;
}
return 0 ;
err :
platform_driver_unregister ( & geodewdt_driver ) ;
return ret ;
}
static void __exit
geodewdt_exit ( void )
{
platform_device_unregister ( geodewdt_platform_device ) ;
platform_driver_unregister ( & geodewdt_driver ) ;
}
module_init ( geodewdt_init ) ;
module_exit ( geodewdt_exit ) ;
MODULE_AUTHOR ( " Advanced Micro Devices, Inc " ) ;
MODULE_DESCRIPTION ( " Geode GX/LX Watchdog Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;