2005-08-17 11:07:44 +04:00
/*
2005-09-02 00:34:53 +04:00
* i6300esb : Watchdog timer driver for Intel 6300 ESB chipset
2005-08-17 11:07:44 +04:00
*
* ( c ) Copyright 2004 Google Inc .
2007-10-20 01:21:04 +04:00
* ( c ) Copyright 2005 David Härdeman < david @ 2 gen . com >
2005-08-17 11:07:44 +04: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 .
*
2008-08-07 00:19:41 +04:00
* based on i810 - tco . c which is in turn based on softdog . c
2005-08-17 11:07:44 +04:00
*
2008-08-07 00:19:41 +04:00
* The timer is implemented in the following I / O controller hubs :
* ( See the intel documentation on http : //developer.intel.com.)
2009-03-19 22:02:44 +03:00
* 6300 ESB chip : document number 300641 - 004
2005-08-17 11:07:44 +04:00
*
* 2004 YYZZ Ross Biro
* Initial version 0.01
* 2004 YYZZ Ross Biro
2008-08-07 00:19:41 +04:00
* Version 0.02
2007-10-20 01:21:04 +04:00
* 20050210 David Härdeman < david @ 2 gen . com >
2008-08-07 00:19:41 +04:00
* Ported driver to kernel 2.6
2017-10-26 19:10:13 +03:00
* 20171016 Radu Rendec < rrendec @ arista . com >
* Change driver to use the watchdog subsystem
2005-08-17 11:07:44 +04:00
*/
/*
* Includes , defines , variables , module parameters , . . .
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-08-17 11:07:44 +04:00
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/fs.h>
# include <linux/mm.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/pci.h>
# include <linux/ioport.h>
2008-05-19 17:05:57 +04:00
# include <linux/uaccess.h>
# include <linux/io.h>
2005-08-17 11:07:44 +04:00
/* Module and version information */
2010-03-08 16:48:01 +03:00
# define ESB_VERSION "0.05"
2005-08-17 11:07:44 +04:00
# define ESB_MODULE_NAME "i6300ESB timer"
2005-09-02 00:34:53 +04:00
/* PCI configuration registers */
# define ESB_CONFIG_REG 0x60 /* Config register */
# define ESB_LOCK_REG 0x68 /* WDT lock register */
/* Memory mapped registers */
2009-03-25 22:20:10 +03:00
# define ESB_TIMER1_REG (BASEADDR + 0x00) /* Timer1 value after each reset */
# define ESB_TIMER2_REG (BASEADDR + 0x04) /* Timer2 value after each reset */
# define ESB_GINTSR_REG (BASEADDR + 0x08) /* General Interrupt Status Register */
# define ESB_RELOAD_REG (BASEADDR + 0x0c) /* Reload register */
2005-09-02 00:34:53 +04:00
/* Lock register bits */
2008-05-19 17:05:57 +04:00
# define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */
# define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */
# define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */
2005-09-02 00:34:53 +04:00
/* Config register bits */
2008-05-19 17:05:57 +04:00
# define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */
# define ESB_WDT_FREQ (0x01 << 2) /* Decrement frequency */
2010-03-08 14:02:38 +03:00
# define ESB_WDT_INTTYPE (0x03 << 0) /* Interrupt type on timer1 timeout */
2005-09-02 00:34:53 +04:00
/* Reload register bits */
2009-03-25 22:14:45 +03:00
# define ESB_WDT_TIMEOUT (0x01 << 9) /* Watchdog timed out */
2008-05-19 17:05:57 +04:00
# define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */
2005-09-02 00:34:53 +04:00
/* Magic constants */
# define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
# define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */
2005-08-17 11:07:44 +04:00
/* internal variables */
static void __iomem * BASEADDR ;
static struct pci_dev * esb_pci ;
2010-03-08 16:48:01 +03:00
/* We can only use 1 card due to the /dev/watchdog restriction */
static int cards_found ;
2009-03-19 22:02:44 +03:00
2005-08-17 11:07:44 +04:00
/* module parameters */
2008-05-19 17:05:57 +04:00
/* 30 sec default heartbeat (1 < heartbeat < 2*1023) */
# define WATCHDOG_HEARTBEAT 30
2017-10-26 19:10:13 +03:00
static int heartbeat ; /* in seconds */
2005-08-17 11:07:44 +04:00
module_param ( heartbeat , int , 0 ) ;
2008-05-19 17:05:57 +04:00
MODULE_PARM_DESC ( heartbeat ,
" Watchdog heartbeat in seconds. (1<heartbeat<2046, default= "
__MODULE_STRING ( WATCHDOG_HEARTBEAT ) " ) " ) ;
2005-08-17 11:07:44 +04:00
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:05:57 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2005-08-17 11:07:44 +04:00
/*
* Some i6300ESB specific functions
*/
/*
* Prepare for reloading the timer by unlocking the proper registers .
* This is performed by first writing 0x80 followed by 0x86 to the
* reload register . After this the appropriate registers can be written
* to once before they need to be unlocked again .
*/
2008-08-07 00:19:41 +04:00
static inline void esb_unlock_registers ( void )
{
2010-03-08 14:02:38 +03:00
writew ( ESB_UNLOCK1 , ESB_RELOAD_REG ) ;
writew ( ESB_UNLOCK2 , ESB_RELOAD_REG ) ;
2005-08-17 11:07:44 +04:00
}
2017-10-26 19:10:13 +03:00
static int esb_timer_start ( struct watchdog_device * wdd )
2005-08-17 11:07:44 +04:00
{
2017-10-26 19:10:13 +03:00
int _wdd_nowayout = test_bit ( WDOG_NO_WAY_OUT , & wdd - > status ) ;
2005-08-17 11:07:44 +04:00
u8 val ;
2009-03-23 16:50:38 +03:00
esb_unlock_registers ( ) ;
writew ( ESB_WDT_RELOAD , ESB_RELOAD_REG ) ;
2005-08-17 11:07:44 +04:00
/* Enable or Enable + Lock? */
2017-10-26 19:10:13 +03:00
val = ESB_WDT_ENABLE | ( _wdd_nowayout ? ESB_WDT_LOCK : 0x00 ) ;
2008-05-19 17:05:57 +04:00
pci_write_config_byte ( esb_pci , ESB_LOCK_REG , val ) ;
2009-03-23 16:50:38 +03:00
return 0 ;
2005-08-17 11:07:44 +04:00
}
2017-10-26 19:10:13 +03:00
static int esb_timer_stop ( struct watchdog_device * wdd )
2005-08-17 11:07:44 +04:00
{
u8 val ;
/* First, reset timers as suggested by the docs */
esb_unlock_registers ( ) ;
2005-08-17 11:11:46 +04:00
writew ( ESB_WDT_RELOAD , ESB_RELOAD_REG ) ;
2005-08-17 11:07:44 +04:00
/* Then disable the WDT */
pci_write_config_byte ( esb_pci , ESB_LOCK_REG , 0x0 ) ;
pci_read_config_byte ( esb_pci , ESB_LOCK_REG , & val ) ;
/* Returns 0 if the timer was disabled, non-zero otherwise */
2009-03-25 22:16:28 +03:00
return val & ESB_WDT_ENABLE ;
2005-08-17 11:07:44 +04:00
}
2017-10-26 19:10:13 +03:00
static int esb_timer_keepalive ( struct watchdog_device * wdd )
2005-08-17 11:07:44 +04:00
{
esb_unlock_registers ( ) ;
2005-08-17 11:11:46 +04:00
writew ( ESB_WDT_RELOAD , ESB_RELOAD_REG ) ;
2008-05-19 17:05:57 +04:00
/* FIXME: Do we need to flush anything here? */
2017-10-26 19:10:13 +03:00
return 0 ;
2005-08-17 11:07:44 +04:00
}
2017-10-26 19:10:13 +03:00
static int esb_timer_set_heartbeat ( struct watchdog_device * wdd ,
unsigned int time )
2005-08-17 11:07:44 +04:00
{
u32 val ;
/* We shift by 9, so if we are passed a value of 1 sec,
* val will be 1 < < 9 = 512 , then write that to two
* timers = > 2 * 512 = 1024 ( which is decremented at 1 KHz )
*/
val = time < < 9 ;
/* Write timer 1 */
esb_unlock_registers ( ) ;
writel ( val , ESB_TIMER1_REG ) ;
/* Write timer 2 */
esb_unlock_registers ( ) ;
2008-08-07 00:19:41 +04:00
writel ( val , ESB_TIMER2_REG ) ;
2005-08-17 11:07:44 +04:00
2008-05-19 17:05:57 +04:00
/* Reload */
2005-08-17 11:07:44 +04:00
esb_unlock_registers ( ) ;
2005-08-17 11:11:46 +04:00
writew ( ESB_WDT_RELOAD , ESB_RELOAD_REG ) ;
2005-08-17 11:07:44 +04:00
/* FIXME: Do we need to flush everything out? */
/* Done */
2017-10-26 19:10:13 +03:00
wdd - > timeout = time ;
2005-08-17 11:07:44 +04:00
return 0 ;
}
/*
2017-10-26 19:10:13 +03:00
* Watchdog Subsystem Interfaces
2005-08-17 11:07:44 +04:00
*/
2017-10-26 19:10:13 +03:00
static struct watchdog_info esb_info = {
. identity = ESB_MODULE_NAME ,
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE ,
} ;
2005-08-17 11:07:44 +04:00
2017-10-26 19:10:13 +03:00
static const struct watchdog_ops esb_ops = {
2008-05-19 17:05:57 +04:00
. owner = THIS_MODULE ,
2017-10-26 19:10:13 +03:00
. start = esb_timer_start ,
. stop = esb_timer_stop ,
. set_timeout = esb_timer_set_heartbeat ,
. ping = esb_timer_keepalive ,
2005-08-17 11:07:44 +04:00
} ;
2017-10-26 19:10:13 +03:00
static struct watchdog_device esb_dev = {
. info = & esb_info ,
. ops = & esb_ops ,
. min_timeout = 1 ,
. max_timeout = 2 * 0x03ff ,
. timeout = WATCHDOG_HEARTBEAT ,
2005-08-17 11:07:44 +04:00
} ;
/*
* Data for PCI driver interface
*/
2013-12-03 03:30:22 +04:00
static const struct pci_device_id esb_pci_tbl [ ] = {
2008-05-19 17:05:57 +04:00
{ PCI_DEVICE ( PCI_VENDOR_ID_INTEL , PCI_DEVICE_ID_INTEL_ESB_9 ) , } ,
{ 0 , } , /* End of list */
2005-08-17 11:07:44 +04:00
} ;
2008-05-19 17:05:57 +04:00
MODULE_DEVICE_TABLE ( pci , esb_pci_tbl ) ;
2005-08-17 11:07:44 +04:00
/*
* Init & exit routines
*/
2012-11-19 22:21:41 +04:00
static unsigned char esb_getdevice ( struct pci_dev * pdev )
2005-08-17 11:07:44 +04:00
{
2010-03-08 16:48:01 +03:00
if ( pci_enable_device ( pdev ) ) {
2017-10-26 19:10:13 +03:00
dev_err ( & pdev - > dev , " failed to enable device \n " ) ;
2009-03-25 22:16:28 +03:00
goto err_devput ;
}
2005-08-17 11:07:44 +04:00
2010-03-08 16:48:01 +03:00
if ( pci_request_region ( pdev , 0 , ESB_MODULE_NAME ) ) {
2017-10-26 19:10:13 +03:00
dev_err ( & pdev - > dev , " failed to request region \n " ) ;
2009-03-25 22:16:28 +03:00
goto err_disable ;
}
2005-08-17 11:07:44 +04:00
2010-03-08 16:48:01 +03:00
BASEADDR = pci_ioremap_bar ( pdev , 0 ) ;
2009-03-25 22:16:28 +03:00
if ( BASEADDR = = NULL ) {
/* Something's wrong here, BASEADDR has to be set */
2017-10-26 19:10:13 +03:00
dev_err ( & pdev - > dev , " failed to get BASEADDR \n " ) ;
2009-03-25 22:16:28 +03:00
goto err_release ;
}
/* Done */
2010-03-08 16:48:01 +03:00
esb_pci = pdev ;
2009-03-25 22:16:28 +03:00
return 1 ;
2005-08-17 11:07:44 +04:00
err_release :
2010-03-08 16:48:01 +03:00
pci_release_region ( pdev , 0 ) ;
2005-08-17 11:07:44 +04:00
err_disable :
2010-03-08 16:48:01 +03:00
pci_disable_device ( pdev ) ;
2005-08-21 15:02:41 +04:00
err_devput :
2005-08-17 11:07:44 +04:00
return 0 ;
}
2012-11-19 22:21:41 +04:00
static void esb_initdevice ( void )
2009-03-25 22:16:28 +03:00
{
u8 val1 ;
u16 val2 ;
/*
* Config register :
* Bit 5 : 0 = Enable WDT_OUTPUT
* Bit 2 : 0 = set the timer frequency to the PCI clock
* divided by 2 ^ 15 ( approx 1 KHz ) .
* Bits 1 : 0 : 11 = WDT_INT_TYPE Disabled .
* The watchdog has two timers , it can be setup so that the
* expiry of timer1 results in an interrupt and the expiry of
* timer2 results in a reboot . We set it to not generate
* any interrupts as there is not much we can do with it
* right now .
*/
pci_write_config_word ( esb_pci , ESB_CONFIG_REG , 0x0003 ) ;
/* Check that the WDT isn't already locked */
pci_read_config_byte ( esb_pci , ESB_LOCK_REG , & val1 ) ;
if ( val1 & ESB_WDT_LOCK )
2017-10-26 19:10:13 +03:00
dev_warn ( & esb_pci - > dev , " nowayout already set \n " ) ;
2009-03-25 22:16:28 +03:00
/* Set the timer to watchdog mode and disable it for now */
pci_write_config_byte ( esb_pci , ESB_LOCK_REG , 0x00 ) ;
/* Check if the watchdog was previously triggered */
esb_unlock_registers ( ) ;
val2 = readw ( ESB_RELOAD_REG ) ;
if ( val2 & ESB_WDT_TIMEOUT )
2017-10-26 19:10:13 +03:00
esb_dev . bootstatus = WDIOF_CARDRESET ;
2009-03-25 22:16:28 +03:00
/* Reset WDT_TIMEOUT flag and timers */
esb_unlock_registers ( ) ;
writew ( ( ESB_WDT_TIMEOUT | ESB_WDT_RELOAD ) , ESB_RELOAD_REG ) ;
/* And set the correct timeout value */
2017-10-26 19:10:13 +03:00
esb_timer_set_heartbeat ( & esb_dev , esb_dev . timeout ) ;
2009-03-25 22:16:28 +03:00
}
2012-11-19 22:21:41 +04:00
static int esb_probe ( struct pci_dev * pdev ,
2010-03-08 16:48:01 +03:00
const struct pci_device_id * ent )
2005-08-17 11:07:44 +04:00
{
2008-05-19 17:05:57 +04:00
int ret ;
2010-03-08 16:48:01 +03:00
cards_found + + ;
if ( cards_found = = 1 )
2012-02-16 03:06:19 +04:00
pr_info ( " Intel 6300ESB WatchDog Timer Driver v%s \n " ,
2010-03-08 16:48:01 +03:00
ESB_VERSION ) ;
if ( cards_found > 1 ) {
2012-02-16 03:06:19 +04:00
pr_err ( " This driver only supports 1 device \n " ) ;
2010-03-08 16:48:01 +03:00
return - ENODEV ;
}
2008-05-19 17:05:57 +04:00
/* Check whether or not the hardware watchdog is there */
2010-03-08 16:48:01 +03:00
if ( ! esb_getdevice ( pdev ) | | esb_pci = = NULL )
2008-05-19 17:05:57 +04:00
return - ENODEV ;
2009-03-25 22:16:28 +03:00
/* Initialize the watchdog and make sure it does not run */
2017-10-26 19:10:13 +03:00
if ( watchdog_init_timeout ( & esb_dev , heartbeat , NULL ) )
pr_info ( " heartbeat value must be 1<heartbeat<2046, using %u \n " ,
esb_dev . timeout ) ;
watchdog_set_nowayout ( & esb_dev , nowayout ) ;
watchdog_stop_on_reboot ( & esb_dev ) ;
watchdog_stop_on_unregister ( & esb_dev ) ;
2009-03-25 22:16:28 +03:00
esb_initdevice ( ) ;
/* Register the watchdog so that userspace has access to it */
2017-10-26 19:10:13 +03:00
ret = watchdog_register_device ( & esb_dev ) ;
2008-05-19 17:05:57 +04:00
if ( ret ! = 0 ) {
2017-10-26 19:10:13 +03:00
dev_err ( & pdev - > dev ,
" cannot register watchdog device (err=%d) \n " , ret ) ;
2009-03-19 22:02:44 +03:00
goto err_unmap ;
2008-05-19 17:05:57 +04:00
}
2017-10-26 19:10:13 +03:00
dev_info ( & pdev - > dev ,
" initialized (0x%p). heartbeat=%d sec (nowayout=%d) \n " ,
BASEADDR , esb_dev . timeout , nowayout ) ;
2008-05-19 17:05:57 +04:00
return 0 ;
2005-08-17 11:07:44 +04:00
err_unmap :
iounmap ( BASEADDR ) ;
pci_release_region ( esb_pci , 0 ) ;
pci_disable_device ( esb_pci ) ;
2010-03-08 16:48:01 +03:00
esb_pci = NULL ;
2008-05-19 17:05:57 +04:00
return ret ;
2005-08-17 11:07:44 +04:00
}
2012-11-19 22:26:24 +04:00
static void esb_remove ( struct pci_dev * pdev )
2005-08-17 11:07:44 +04:00
{
2017-10-26 19:10:13 +03:00
watchdog_unregister_device ( & esb_dev ) ;
2005-08-17 11:07:44 +04:00
iounmap ( BASEADDR ) ;
pci_release_region ( esb_pci , 0 ) ;
pci_disable_device ( esb_pci ) ;
2010-03-08 16:48:01 +03:00
esb_pci = NULL ;
2009-03-19 22:02:44 +03:00
}
2010-03-08 16:48:01 +03:00
static struct pci_driver esb_driver = {
. name = ESB_MODULE_NAME ,
. id_table = esb_pci_tbl ,
2009-03-19 22:02:44 +03:00
. probe = esb_probe ,
2012-11-19 22:21:12 +04:00
. remove = esb_remove ,
2009-03-19 22:02:44 +03:00
} ;
2012-05-04 16:43:25 +04:00
module_pci_driver ( esb_driver ) ;
2005-08-17 11:07:44 +04:00
2007-10-20 01:21:04 +04:00
MODULE_AUTHOR ( " Ross Biro and David Härdeman " ) ;
2005-08-17 11:07:44 +04:00
MODULE_DESCRIPTION ( " Watchdog driver for Intel 6300ESB chipsets " ) ;
MODULE_LICENSE ( " GPL " ) ;