2012-05-10 18:37:43 +04:00
/*
* Intel Atom E6xx Watchdog driver
*
* Copyright ( C ) 2011 Alexander Stein
* < alexander . stein @ systec - electronic . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of version 2 of the GNU General
* Public License as published by the Free Software Foundation .
*
* 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 .
* The full GNU General Public License is included in this
* distribution in the file called COPYING .
*
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/platform_device.h>
2012-05-15 00:14:29 +04:00
# include <linux/io.h>
2012-05-10 18:37:43 +04:00
# include <linux/kernel.h>
# include <linux/types.h>
# include <linux/watchdog.h>
# include <linux/miscdevice.h>
# include <linux/seq_file.h>
# include <linux/debugfs.h>
# include <linux/uaccess.h>
# include <linux/spinlock.h>
# define DRIVER_NAME "ie6xx_wdt"
# define PV1 0x00
# define PV2 0x04
# define RR0 0x0c
# define RR1 0x0d
# define WDT_RELOAD 0x01
# define WDT_TOUT 0x02
# define WDTCR 0x10
# define WDT_PRE_SEL 0x04
# define WDT_RESET_SEL 0x08
# define WDT_RESET_EN 0x10
# define WDT_TOUT_EN 0x20
# define DCR 0x14
# define WDTLR 0x18
# define WDT_LOCK 0x01
# define WDT_ENABLE 0x02
# define WDT_TOUT_CNF 0x03
# define MIN_TIME 1
# define MAX_TIME (10 * 60) /* 10 minutes */
# define DEFAULT_TIME 60
static unsigned int timeout = DEFAULT_TIME ;
module_param ( timeout , uint , 0 ) ;
MODULE_PARM_DESC ( timeout ,
" Default Watchdog timer setting ( "
__MODULE_STRING ( DEFAULT_TIME ) " s). "
" The range is from 1 to 600 " ) ;
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static u8 resetmode = 0x10 ;
module_param ( resetmode , byte , 0 ) ;
MODULE_PARM_DESC ( resetmode ,
" Resetmode bits: 0x08 warm reset (cold reset otherwise), "
" 0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10) " ) ;
static struct {
unsigned short sch_wdtba ;
struct spinlock unlock_sequence ;
# ifdef CONFIG_DEBUG_FS
struct dentry * debugfs ;
# endif
} ie6xx_wdt_data ;
/*
* This is needed to write to preload and reload registers
* struct ie6xx_wdt_data . unlock_sequence must be used
* to prevent sequence interrupts
*/
static void ie6xx_wdt_unlock_registers ( void )
{
outb ( 0x80 , ie6xx_wdt_data . sch_wdtba + RR0 ) ;
outb ( 0x86 , ie6xx_wdt_data . sch_wdtba + RR0 ) ;
}
static int ie6xx_wdt_ping ( struct watchdog_device * wdd )
{
spin_lock ( & ie6xx_wdt_data . unlock_sequence ) ;
ie6xx_wdt_unlock_registers ( ) ;
outb ( WDT_RELOAD , ie6xx_wdt_data . sch_wdtba + RR1 ) ;
spin_unlock ( & ie6xx_wdt_data . unlock_sequence ) ;
return 0 ;
}
static int ie6xx_wdt_set_timeout ( struct watchdog_device * wdd , unsigned int t )
{
u32 preload ;
u64 clock ;
u8 wdtcr ;
/* Watchdog clock is PCI Clock (33MHz) */
clock = 33000000 ;
/* and the preload value is loaded into [34:15] of the down counter */
preload = ( t * clock ) > > 15 ;
/*
* Manual states preload must be one less .
* Does not wrap as t is at least 1
*/
preload - = 1 ;
spin_lock ( & ie6xx_wdt_data . unlock_sequence ) ;
/* Set ResetMode & Enable prescaler for range 10ms to 10 min */
wdtcr = resetmode & 0x38 ;
outb ( wdtcr , ie6xx_wdt_data . sch_wdtba + WDTCR ) ;
ie6xx_wdt_unlock_registers ( ) ;
outl ( 0 , ie6xx_wdt_data . sch_wdtba + PV1 ) ;
ie6xx_wdt_unlock_registers ( ) ;
outl ( preload , ie6xx_wdt_data . sch_wdtba + PV2 ) ;
ie6xx_wdt_unlock_registers ( ) ;
outb ( WDT_RELOAD | WDT_TOUT , ie6xx_wdt_data . sch_wdtba + RR1 ) ;
spin_unlock ( & ie6xx_wdt_data . unlock_sequence ) ;
wdd - > timeout = t ;
return 0 ;
}
static int ie6xx_wdt_start ( struct watchdog_device * wdd )
{
ie6xx_wdt_set_timeout ( wdd , wdd - > timeout ) ;
/* Enable the watchdog timer */
spin_lock ( & ie6xx_wdt_data . unlock_sequence ) ;
outb ( WDT_ENABLE , ie6xx_wdt_data . sch_wdtba + WDTLR ) ;
spin_unlock ( & ie6xx_wdt_data . unlock_sequence ) ;
return 0 ;
}
static int ie6xx_wdt_stop ( struct watchdog_device * wdd )
{
if ( inb ( ie6xx_wdt_data . sch_wdtba + WDTLR ) & WDT_LOCK )
return - 1 ;
/* Disable the watchdog timer */
spin_lock ( & ie6xx_wdt_data . unlock_sequence ) ;
outb ( 0 , ie6xx_wdt_data . sch_wdtba + WDTLR ) ;
spin_unlock ( & ie6xx_wdt_data . unlock_sequence ) ;
return 0 ;
}
static const struct watchdog_info ie6xx_wdt_info = {
. identity = " Intel Atom E6xx Watchdog " ,
. options = WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING ,
} ;
static const struct watchdog_ops ie6xx_wdt_ops = {
. owner = THIS_MODULE ,
. start = ie6xx_wdt_start ,
. stop = ie6xx_wdt_stop ,
. ping = ie6xx_wdt_ping ,
. set_timeout = ie6xx_wdt_set_timeout ,
} ;
static struct watchdog_device ie6xx_wdt_dev = {
. info = & ie6xx_wdt_info ,
. ops = & ie6xx_wdt_ops ,
. min_timeout = MIN_TIME ,
. max_timeout = MAX_TIME ,
} ;
# ifdef CONFIG_DEBUG_FS
static int ie6xx_wdt_dbg_show ( struct seq_file * s , void * unused )
{
seq_printf ( s , " PV1 = 0x%08x \n " ,
inl ( ie6xx_wdt_data . sch_wdtba + PV1 ) ) ;
seq_printf ( s , " PV2 = 0x%08x \n " ,
inl ( ie6xx_wdt_data . sch_wdtba + PV2 ) ) ;
seq_printf ( s , " RR = 0x%08x \n " ,
inw ( ie6xx_wdt_data . sch_wdtba + RR0 ) ) ;
seq_printf ( s , " WDTCR = 0x%08x \n " ,
inw ( ie6xx_wdt_data . sch_wdtba + WDTCR ) ) ;
seq_printf ( s , " DCR = 0x%08x \n " ,
inl ( ie6xx_wdt_data . sch_wdtba + DCR ) ) ;
seq_printf ( s , " WDTLR = 0x%08x \n " ,
inw ( ie6xx_wdt_data . sch_wdtba + WDTLR ) ) ;
seq_printf ( s , " \n " ) ;
return 0 ;
}
static int ie6xx_wdt_dbg_open ( struct inode * inode , struct file * file )
{
return single_open ( file , ie6xx_wdt_dbg_show , NULL ) ;
}
static const struct file_operations ie6xx_wdt_dbg_operations = {
. open = ie6xx_wdt_dbg_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
} ;
static void __devinit ie6xx_wdt_debugfs_init ( void )
{
/* /sys/kernel/debug/ie6xx_wdt */
ie6xx_wdt_data . debugfs = debugfs_create_file ( " ie6xx_wdt " ,
S_IFREG | S_IRUGO , NULL , NULL , & ie6xx_wdt_dbg_operations ) ;
}
2012-07-04 20:32:11 +04:00
static void ie6xx_wdt_debugfs_exit ( void )
2012-05-10 18:37:43 +04:00
{
debugfs_remove ( ie6xx_wdt_data . debugfs ) ;
}
# else
static void __devinit ie6xx_wdt_debugfs_init ( void )
{
}
2012-07-04 20:32:11 +04:00
static void ie6xx_wdt_debugfs_exit ( void )
2012-05-10 18:37:43 +04:00
{
}
# endif
static int __devinit ie6xx_wdt_probe ( struct platform_device * pdev )
{
struct resource * res ;
u8 wdtlr ;
int ret ;
res = platform_get_resource ( pdev , IORESOURCE_IO , 0 ) ;
if ( ! res )
return - ENODEV ;
if ( ! request_region ( res - > start , resource_size ( res ) , pdev - > name ) ) {
2012-05-12 05:33:31 +04:00
dev_err ( & pdev - > dev , " Watchdog region 0x%llx already in use! \n " ,
( u64 ) res - > start ) ;
2012-05-10 18:37:43 +04:00
return - EBUSY ;
}
ie6xx_wdt_data . sch_wdtba = res - > start ;
dev_dbg ( & pdev - > dev , " WDT = 0x%X \n " , ie6xx_wdt_data . sch_wdtba ) ;
ie6xx_wdt_dev . timeout = timeout ;
watchdog_set_nowayout ( & ie6xx_wdt_dev , nowayout ) ;
spin_lock_init ( & ie6xx_wdt_data . unlock_sequence ) ;
wdtlr = inb ( ie6xx_wdt_data . sch_wdtba + WDTLR ) ;
if ( wdtlr & WDT_LOCK )
dev_warn ( & pdev - > dev ,
" Watchdog Timer is Locked (Reg=0x%x) \n " , wdtlr ) ;
ie6xx_wdt_debugfs_init ( ) ;
ret = watchdog_register_device ( & ie6xx_wdt_dev ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" Watchdog timer: cannot register device (err =%d) \n " ,
ret ) ;
goto misc_register_error ;
}
return 0 ;
misc_register_error :
ie6xx_wdt_debugfs_exit ( ) ;
release_region ( res - > start , resource_size ( res ) ) ;
ie6xx_wdt_data . sch_wdtba = 0 ;
return ret ;
}
static int __devexit ie6xx_wdt_remove ( struct platform_device * pdev )
{
struct resource * res ;
res = platform_get_resource ( pdev , IORESOURCE_IO , 0 ) ;
ie6xx_wdt_stop ( NULL ) ;
watchdog_unregister_device ( & ie6xx_wdt_dev ) ;
ie6xx_wdt_debugfs_exit ( ) ;
release_region ( res - > start , resource_size ( res ) ) ;
ie6xx_wdt_data . sch_wdtba = 0 ;
return 0 ;
}
static struct platform_driver ie6xx_wdt_driver = {
. probe = ie6xx_wdt_probe ,
. remove = __devexit_p ( ie6xx_wdt_remove ) ,
. driver = {
. name = DRIVER_NAME ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init ie6xx_wdt_init ( void )
{
/* Check boot parameters to verify that their initial values */
/* are in range. */
if ( ( timeout < MIN_TIME ) | |
( timeout > MAX_TIME ) ) {
pr_err ( " Watchdog timer: value of timeout %d (dec) "
" is out of range from %d to %d (dec) \n " ,
timeout , MIN_TIME , MAX_TIME ) ;
return - EINVAL ;
}
return platform_driver_register ( & ie6xx_wdt_driver ) ;
}
static void __exit ie6xx_wdt_exit ( void )
{
platform_driver_unregister ( & ie6xx_wdt_driver ) ;
}
late_initcall ( ie6xx_wdt_init ) ;
module_exit ( ie6xx_wdt_exit ) ;
MODULE_AUTHOR ( " Alexander Stein <alexander.stein@systec-electronic.com> " ) ;
MODULE_DESCRIPTION ( " Intel Atom E6xx Watchdog Device Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
MODULE_ALIAS ( " platform: " DRIVER_NAME ) ;