2009-12-18 21:02:38 +03:00
/* Watchdog timer for machines with the CS5535/CS5536 companion chip
2008-01-21 20:07:00 +03:00
*
* Copyright ( C ) 2006 - 2007 , Advanced Micro Devices , Inc .
2009-12-18 21:02:38 +03:00
* Copyright ( C ) 2009 Andres Salomon < dilinger @ collabora . co . uk >
2008-01-21 20:07:00 +03:00
*
* 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 .
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2008-01-21 20:07:00 +03:00
# 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
2009-12-18 21:02:38 +03:00
# include <linux/cs5535.h>
2008-01-21 20:07:00 +03:00
# 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 ) ;
2009-03-18 11:35:09 +03:00
MODULE_PARM_DESC ( timeout ,
" Watchdog timeout in seconds. 1<= timeout <=131, default= "
__MODULE_STRING ( WATCHDOG_TIMEOUT ) " . " ) ;
2008-01-21 20:07:00 +03:00
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2009-03-18 11:35:09 +03:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2008-01-21 20:07:00 +03:00
static struct platform_device * geodewdt_platform_device ;
static unsigned long wdt_flags ;
2009-12-18 21:02:38 +03:00
static struct cs5535_mfgpt_timer * wdt_timer ;
2008-01-21 20:07:00 +03:00
static int safe_close ;
static void geodewdt_ping ( void )
{
/* Stop the counter */
2009-12-18 21:02:38 +03:00
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , 0 ) ;
2008-01-21 20:07:00 +03:00
/* Reset the counter */
2009-12-18 21:02:38 +03:00
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_COUNTER , 0 ) ;
2008-01-21 20:07:00 +03:00
/* Enable the counter */
2009-12-18 21:02:38 +03:00
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , MFGPT_SETUP_CNTEN ) ;
2008-01-21 20:07:00 +03:00
}
static void geodewdt_disable ( void )
{
2009-12-18 21:02:38 +03:00
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , 0 ) ;
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_COUNTER , 0 ) ;
2008-01-21 20:07:00 +03:00
}
static int geodewdt_set_heartbeat ( int val )
{
if ( val < 1 | | val > GEODEWDT_MAX_SECONDS )
return - EINVAL ;
2009-12-18 21:02:38 +03:00
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , 0 ) ;
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_CMP2 , val * GEODEWDT_HZ ) ;
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_COUNTER , 0 ) ;
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP , MFGPT_SETUP_CNTEN ) ;
2008-01-21 20:07:00 +03:00
timeout = val ;
return 0 ;
}
2008-08-07 00:19:41 +04:00
static int geodewdt_open ( struct inode * inode , struct file * file )
2008-01-21 20:07:00 +03:00
{
2008-08-07 00:19:41 +04:00
if ( test_and_set_bit ( WDT_FLAGS_OPEN , & wdt_flags ) )
return - EBUSY ;
2008-01-21 20:07:00 +03:00
2008-08-07 00:19:41 +04:00
if ( ! test_and_clear_bit ( WDT_FLAGS_ORPHAN , & wdt_flags ) )
__module_get ( THIS_MODULE ) ;
2008-01-21 20:07:00 +03:00
geodewdt_ping ( ) ;
2008-08-07 00:19:41 +04:00
return nonseekable_open ( inode , file ) ;
2008-01-21 20:07:00 +03:00
}
2008-08-07 00:19:41 +04:00
static int geodewdt_release ( struct inode * inode , struct file * file )
2008-01-21 20:07:00 +03:00
{
if ( safe_close ) {
geodewdt_disable ( ) ;
module_put ( THIS_MODULE ) ;
2008-08-07 00:19:41 +04:00
} else {
2012-02-16 03:06:19 +04:00
pr_crit ( " Unexpected close - watchdog is not stopping \n " ) ;
2008-01-21 20:07:00 +03:00
geodewdt_ping ( ) ;
set_bit ( WDT_FLAGS_ORPHAN , & wdt_flags ) ;
}
clear_bit ( WDT_FLAGS_OPEN , & wdt_flags ) ;
safe_close = 0 ;
return 0 ;
}
2008-08-07 00:19:41 +04:00
static ssize_t geodewdt_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
2008-01-21 20:07:00 +03:00
{
2008-08-07 00:19:41 +04:00
if ( len ) {
2008-01-21 20:07:00 +03:00
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 ;
}
2008-09-18 16:26:15 +04:00
static long geodewdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2008-01-21 20:07:00 +03:00
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int interval ;
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2008-01-21 20:07:00 +03:00
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE ,
. firmware_version = 1 ,
. identity = WATCHDOG_NAME ,
2008-08-07 00:19:41 +04:00
} ;
2008-01-21 20:07:00 +03:00
2008-07-17 22:08:47 +04:00
switch ( cmd ) {
2008-01-21 20:07:00 +03:00
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_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 ;
}
2008-07-18 15:41:17 +04:00
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 ) ;
2008-01-21 20:07:00 +03:00
default :
return - ENOTTY ;
}
return 0 ;
}
static const struct file_operations geodewdt_fops = {
2008-08-07 00:19:41 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = geodewdt_write ,
2008-09-18 16:26:15 +04:00
. unlocked_ioctl = geodewdt_ioctl ,
2008-08-07 00:19:41 +04:00
. open = geodewdt_open ,
. release = geodewdt_release ,
2008-01-21 20:07:00 +03:00
} ;
static struct miscdevice geodewdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
2008-08-07 00:19:41 +04:00
. fops = & geodewdt_fops ,
2008-01-21 20:07:00 +03:00
} ;
2014-03-14 16:16:47 +04:00
static int __init geodewdt_probe ( struct platform_device * dev )
2008-01-21 20:07:00 +03:00
{
2009-12-18 21:02:38 +03:00
int ret ;
2008-01-21 20:07:00 +03:00
2009-12-18 21:02:38 +03:00
wdt_timer = cs5535_mfgpt_alloc_timer ( MFGPT_TIMER_ANY , MFGPT_DOMAIN_WORKING ) ;
if ( ! wdt_timer ) {
2012-02-16 03:06:19 +04:00
pr_err ( " No timers were available \n " ) ;
2008-01-21 20:07:00 +03:00
return - ENODEV ;
}
/* Set up the timer */
2009-12-18 21:02:38 +03:00
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_SETUP ,
2008-01-21 20:07:00 +03:00
GEODEWDT_SCALE | ( 3 < < 8 ) ) ;
/* Set up comparator 2 to reset when the event fires */
2009-12-18 21:02:38 +03:00
cs5535_mfgpt_toggle_event ( wdt_timer , MFGPT_CMP2 , MFGPT_EVENT_RESET , 1 ) ;
2008-01-21 20:07:00 +03:00
/* Set up the initial timeout */
2009-12-18 21:02:38 +03:00
cs5535_mfgpt_write ( wdt_timer , MFGPT_REG_CMP2 ,
2008-01-21 20:07:00 +03:00
timeout * GEODEWDT_HZ ) ;
ret = misc_register ( & geodewdt_miscdev ) ;
return ret ;
}
2012-11-19 22:26:24 +04:00
static int geodewdt_remove ( struct platform_device * dev )
2008-01-21 20:07:00 +03:00
{
misc_deregister ( & geodewdt_miscdev ) ;
return 0 ;
}
2008-08-07 00:19:41 +04:00
static void geodewdt_shutdown ( struct platform_device * dev )
2008-01-21 20:07:00 +03:00
{
geodewdt_disable ( ) ;
}
static struct platform_driver geodewdt_driver = {
2012-11-19 22:21:12 +04:00
. remove = geodewdt_remove ,
2008-01-21 20:07:00 +03:00
. shutdown = geodewdt_shutdown ,
. driver = {
. name = DRV_NAME ,
} ,
} ;
2008-08-07 00:19:41 +04:00
static int __init geodewdt_init ( void )
2008-01-21 20:07:00 +03:00
{
int ret ;
2009-03-18 11:35:09 +03:00
geodewdt_platform_device = platform_device_register_simple ( DRV_NAME ,
- 1 , NULL , 0 ) ;
2014-03-14 16:16:47 +04:00
if ( IS_ERR ( geodewdt_platform_device ) )
return PTR_ERR ( geodewdt_platform_device ) ;
ret = platform_driver_probe ( & geodewdt_driver , geodewdt_probe ) ;
if ( ret )
2008-01-21 20:07:00 +03:00
goto err ;
return 0 ;
err :
2014-03-14 16:16:47 +04:00
platform_device_unregister ( geodewdt_platform_device ) ;
2008-01-21 20:07:00 +03:00
return ret ;
}
2008-08-07 00:19:41 +04:00
static void __exit geodewdt_exit ( void )
2008-01-21 20:07:00 +03:00
{
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 " ) ;