2006-05-21 16:37:44 +04:00
/*
2009-11-15 16:44:54 +03:00
* intel TCO Watchdog Driver
2006-05-21 16:37:44 +04:00
*
2009-01-28 23:51:04 +03:00
* ( c ) Copyright 2006 - 2009 Wim Van Sebroeck < wim @ iguana . be > .
2006-05-21 16:37: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 .
*
* Neither Wim Van Sebroeck nor Iguana vzw . admit liability nor
* provide warranty for any of this software . This material is
* provided " AS-IS " and at no charge .
*
* The TCO watchdog is implemented in the following I / O controller hubs :
* ( See the intel documentation on http : //developer.intel.com.)
2009-11-15 16:44:54 +03:00
* document number 290655 - 003 , 290677 - 014 : 82801 AA ( ICH ) , 82801 AB ( ICHO )
* document number 290687 - 002 , 298242 - 027 : 82801 BA ( ICH2 )
* document number 290733 - 003 , 290739 - 013 : 82801 CA ( ICH3 - S )
* document number 290716 - 001 , 290718 - 007 : 82801 CAM ( ICH3 - M )
* document number 290744 - 001 , 290745 - 025 : 82801 DB ( ICH4 )
* document number 252337 - 001 , 252663 - 00 8 : 82801 DBM ( ICH4 - M )
* document number 273599 - 001 , 273645 - 002 : 82801 E ( C - ICH )
* document number 252516 - 001 , 252517 - 02 8 : 82801 EB ( ICH5 ) , 82801 ER ( ICH5R )
* document number 300641 - 004 , 300884 - 013 : 6300 ESB
* document number 301473 - 002 , 301474 - 026 : 82801F ( ICH6 )
* document number 313082 - 001 , 313075 - 006 : 631 xESB , 632 xESB
* document number 307013 - 003 , 307014 - 024 : 82801 G ( ICH7 )
* document number 313056 - 003 , 313057 - 017 : 82801 H ( ICH8 )
* document number 316972 - 004 , 316973 - 012 : 82801 I ( ICH9 )
* document number 319973 - 002 , 319974 - 002 : 82801 J ( ICH10 )
2010-01-14 23:58:05 +03:00
* document number 322169 - 001 , 322170 - 003 : 5 Series , 3400 Series ( PCH )
2009-12-07 22:42:26 +03:00
* document number 320066 - 003 , 320257 - 00 8 : EP80597 ( IICH )
2010-01-14 23:58:05 +03:00
* document number TBD : Cougar Point ( CPT )
2006-05-21 16:37:44 +04:00
*/
/*
* Includes , defines , variables , module parameters , . . .
*/
/* Module and version information */
2008-08-07 00:19:41 +04:00
# define DRV_NAME "iTCO_wdt"
2009-01-28 23:51:04 +03:00
# define DRV_VERSION "1.05"
2006-05-21 16:37:44 +04:00
# define PFX DRV_NAME ": "
/* Includes */
2006-06-30 10:44:53 +04:00
# include <linux/module.h> /* For module specific items */
# include <linux/moduleparam.h> /* For new moduleparam's */
# include <linux/types.h> /* For standard types (like size_t) */
# include <linux/errno.h> /* For the -ENODEV/... values */
# include <linux/kernel.h> /* For printk/panic/... */
2008-05-19 17:06:25 +04:00
# include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV
( WATCHDOG_MINOR ) */
2006-06-30 10:44:53 +04:00
# include <linux/watchdog.h> /* For the watchdog specific items */
# include <linux/init.h> /* For __init/__exit/... */
# include <linux/fs.h> /* For file operations */
# include <linux/platform_device.h> /* For platform_driver framework */
# include <linux/pci.h> /* For pci functions */
# include <linux/ioport.h> /* For io-port access */
# include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
2008-05-19 17:06:25 +04:00
# include <linux/uaccess.h> /* For copy_to_user/put_user/... */
# include <linux/io.h> /* For inb/outb/... */
2006-06-30 10:44:53 +04:00
2008-05-19 17:06:25 +04:00
# include "iTCO_vendor.h"
2006-05-21 16:37:44 +04:00
/* TCO related info */
enum iTCO_chipsets {
TCO_ICH = 0 , /* ICH */
TCO_ICH0 , /* ICH0 */
TCO_ICH2 , /* ICH2 */
TCO_ICH2M , /* ICH2-M */
TCO_ICH3 , /* ICH3-S */
TCO_ICH3M , /* ICH3-M */
TCO_ICH4 , /* ICH4 */
TCO_ICH4M , /* ICH4-M */
TCO_CICH , /* C-ICH */
TCO_ICH5 , /* ICH5 & ICH5R */
TCO_6300ESB , /* 6300ESB */
TCO_ICH6 , /* ICH6 & ICH6R */
TCO_ICH6M , /* ICH6-M */
TCO_ICH6W , /* ICH6W & ICH6RW */
2008-11-20 01:25:53 +03:00
TCO_631XESB , /* 631xESB/632xESB */
2006-05-21 16:37:44 +04:00
TCO_ICH7 , /* ICH7 & ICH7R */
2008-11-20 01:25:53 +03:00
TCO_ICH7DH , /* ICH7DH */
TCO_ICH7M , /* ICH7-M & ICH7-U */
2006-05-21 16:37:44 +04:00
TCO_ICH7MDH , /* ICH7-M DH */
2006-10-08 23:05:21 +04:00
TCO_ICH8 , /* ICH8 & ICH8R */
TCO_ICH8DH , /* ICH8DH */
TCO_ICH8DO , /* ICH8DO */
2007-08-31 12:23:10 +04:00
TCO_ICH8M , /* ICH8M */
2008-11-20 01:25:53 +03:00
TCO_ICH8ME , /* ICH8M-E */
2007-07-27 01:11:28 +04:00
TCO_ICH9 , /* ICH9 */
TCO_ICH9R , /* ICH9R */
TCO_ICH9DH , /* ICH9DH */
2008-08-07 00:19:41 +04:00
TCO_ICH9DO , /* ICH9DO */
2008-11-20 01:25:53 +03:00
TCO_ICH9M , /* ICH9M */
TCO_ICH9ME , /* ICH9M-E */
TCO_ICH10 , /* ICH10 */
TCO_ICH10R , /* ICH10R */
TCO_ICH10D , /* ICH10D */
TCO_ICH10DO , /* ICH10DO */
2009-11-11 04:24:01 +03:00
TCO_PCH , /* PCH Desktop Full Featured */
TCO_PCHM , /* PCH Mobile Full Featured */
2010-01-14 23:58:05 +03:00
TCO_P55 , /* P55 */
TCO_PM55 , /* PM55 */
TCO_H55 , /* H55 */
TCO_QM57 , /* QM57 */
TCO_H57 , /* H57 */
TCO_HM55 , /* HM55 */
TCO_Q57 , /* Q57 */
TCO_HM57 , /* HM57 */
2009-11-11 04:24:01 +03:00
TCO_PCHMSFF , /* PCH Mobile SFF Full Featured */
2010-01-14 23:58:05 +03:00
TCO_QS57 , /* QS57 */
TCO_3400 , /* 3400 */
TCO_3420 , /* 3420 */
TCO_3450 , /* 3450 */
2009-12-07 22:42:26 +03:00
TCO_EP80579 , /* EP80579 */
2010-01-14 23:58:05 +03:00
TCO_CPTD , /* CPT Desktop */
TCO_CPTM , /* CPT Mobile */
2006-05-21 16:37:44 +04:00
} ;
static struct {
char * name ;
unsigned int iTCO_version ;
} iTCO_chipset_info [ ] __devinitdata = {
{ " ICH " , 1 } ,
{ " ICH0 " , 1 } ,
{ " ICH2 " , 1 } ,
{ " ICH2-M " , 1 } ,
{ " ICH3-S " , 1 } ,
{ " ICH3-M " , 1 } ,
{ " ICH4 " , 1 } ,
{ " ICH4-M " , 1 } ,
{ " C-ICH " , 1 } ,
{ " ICH5 or ICH5R " , 1 } ,
{ " 6300ESB " , 1 } ,
{ " ICH6 or ICH6R " , 2 } ,
{ " ICH6-M " , 2 } ,
{ " ICH6W or ICH6RW " , 2 } ,
2008-11-20 01:25:53 +03:00
{ " 631xESB/632xESB " , 2 } ,
2006-05-21 16:37:44 +04:00
{ " ICH7 or ICH7R " , 2 } ,
2008-11-20 01:25:53 +03:00
{ " ICH7DH " , 2 } ,
{ " ICH7-M or ICH7-U " , 2 } ,
2006-05-21 16:37:44 +04:00
{ " ICH7-M DH " , 2 } ,
2006-10-04 16:18:29 +04:00
{ " ICH8 or ICH8R " , 2 } ,
2006-10-08 23:05:21 +04:00
{ " ICH8DH " , 2 } ,
{ " ICH8DO " , 2 } ,
2007-08-31 12:23:10 +04:00
{ " ICH8M " , 2 } ,
2008-11-20 01:25:53 +03:00
{ " ICH8M-E " , 2 } ,
2007-07-27 01:11:28 +04:00
{ " ICH9 " , 2 } ,
{ " ICH9R " , 2 } ,
{ " ICH9DH " , 2 } ,
2008-04-30 18:51:10 +04:00
{ " ICH9DO " , 2 } ,
2008-11-20 01:25:53 +03:00
{ " ICH9M " , 2 } ,
{ " ICH9M-E " , 2 } ,
{ " ICH10 " , 2 } ,
{ " ICH10R " , 2 } ,
{ " ICH10D " , 2 } ,
{ " ICH10DO " , 2 } ,
2009-11-11 04:24:01 +03:00
{ " PCH Desktop Full Featured " , 2 } ,
{ " PCH Mobile Full Featured " , 2 } ,
2010-01-14 23:58:05 +03:00
{ " P55 " , 2 } ,
{ " PM55 " , 2 } ,
{ " H55 " , 2 } ,
{ " QM57 " , 2 } ,
{ " H57 " , 2 } ,
{ " HM55 " , 2 } ,
{ " Q57 " , 2 } ,
{ " HM57 " , 2 } ,
2009-11-11 04:24:01 +03:00
{ " PCH Mobile SFF Full Featured " , 2 } ,
2010-01-14 23:58:05 +03:00
{ " QS57 " , 2 } ,
{ " 3400 " , 2 } ,
{ " 3420 " , 2 } ,
{ " 3450 " , 2 } ,
2009-12-07 22:42:26 +03:00
{ " EP80579 " , 2 } ,
2010-01-14 23:58:05 +03:00
{ " CPT Desktop " , 2 } ,
{ " CPT Mobile " , 2 } ,
2008-05-19 17:06:25 +04:00
{ NULL , 0 }
2006-05-21 16:37:44 +04:00
} ;
2007-08-20 00:17:58 +04:00
# define ITCO_PCI_DEVICE(dev, data) \
. vendor = PCI_VENDOR_ID_INTEL , \
. device = dev , \
. subvendor = PCI_ANY_ID , \
. subdevice = PCI_ANY_ID , \
. class = 0 , \
. class_mask = 0 , \
. driver_data = data
2006-05-21 16:37:44 +04:00
/*
* This data only exists for exporting the supported PCI ids
* via MODULE_DEVICE_TABLE . We do not actually register a
* pci_driver , because the I / O Controller Hub has also other
* functions that probably will be registered by other drivers .
*/
static struct pci_device_id iTCO_wdt_pci_tbl [ ] = {
2008-05-19 17:06:25 +04:00
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801AA_0 , TCO_ICH ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801AB_0 , TCO_ICH0 ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801BA_0 , TCO_ICH2 ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801BA_10 , TCO_ICH2M ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801CA_0 , TCO_ICH3 ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801CA_12 , TCO_ICH3M ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801DB_0 , TCO_ICH4 ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801DB_12 , TCO_ICH4M ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801E_0 , TCO_CICH ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_82801EB_0 , TCO_ICH5 ) } ,
2007-08-20 00:17:58 +04:00
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ESB_1 , TCO_6300ESB ) } ,
2008-05-19 17:06:25 +04:00
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH6_0 , TCO_ICH6 ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH6_1 , TCO_ICH6M ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH6_2 , TCO_ICH6W ) } ,
2007-08-20 00:17:58 +04:00
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ESB2_0 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x2671 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x2672 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x2673 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x2674 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x2675 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x2676 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x2677 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x2678 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x2679 , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x267a , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x267b , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x267c , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x267d , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x267e , TCO_631XESB ) } ,
{ ITCO_PCI_DEVICE ( 0x267f , TCO_631XESB ) } ,
2008-11-20 01:25:53 +03:00
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH7_0 , TCO_ICH7 ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH7_30 , TCO_ICH7DH ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH7_1 , TCO_ICH7M ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH7_31 , TCO_ICH7MDH ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH8_0 , TCO_ICH8 ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH8_2 , TCO_ICH8DH ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH8_3 , TCO_ICH8DO ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH8_4 , TCO_ICH8M ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH8_1 , TCO_ICH8ME ) } ,
{ ITCO_PCI_DEVICE ( 0x2918 , TCO_ICH9 ) } ,
{ ITCO_PCI_DEVICE ( 0x2916 , TCO_ICH9R ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH9_2 , TCO_ICH9DH ) } ,
{ ITCO_PCI_DEVICE ( PCI_DEVICE_ID_INTEL_ICH9_4 , TCO_ICH9DO ) } ,
{ ITCO_PCI_DEVICE ( 0x2919 , TCO_ICH9M ) } ,
{ ITCO_PCI_DEVICE ( 0x2917 , TCO_ICH9ME ) } ,
{ ITCO_PCI_DEVICE ( 0x3a18 , TCO_ICH10 ) } ,
{ ITCO_PCI_DEVICE ( 0x3a16 , TCO_ICH10R ) } ,
{ ITCO_PCI_DEVICE ( 0x3a1a , TCO_ICH10D ) } ,
{ ITCO_PCI_DEVICE ( 0x3a14 , TCO_ICH10DO ) } ,
2009-11-11 04:24:01 +03:00
{ ITCO_PCI_DEVICE ( 0x3b00 , TCO_PCH ) } ,
{ ITCO_PCI_DEVICE ( 0x3b01 , TCO_PCHM ) } ,
2010-01-14 23:58:05 +03:00
{ ITCO_PCI_DEVICE ( 0x3b02 , TCO_P55 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b03 , TCO_PM55 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b06 , TCO_H55 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b07 , TCO_QM57 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b08 , TCO_H57 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b09 , TCO_HM55 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b0a , TCO_Q57 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b0b , TCO_HM57 ) } ,
2009-11-11 04:24:01 +03:00
{ ITCO_PCI_DEVICE ( 0x3b0d , TCO_PCHMSFF ) } ,
2010-01-14 23:58:05 +03:00
{ ITCO_PCI_DEVICE ( 0x3b0f , TCO_QS57 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b12 , TCO_3400 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b14 , TCO_3420 ) } ,
{ ITCO_PCI_DEVICE ( 0x3b16 , TCO_3450 ) } ,
2009-12-07 22:42:26 +03:00
{ ITCO_PCI_DEVICE ( 0x5031 , TCO_EP80579 ) } ,
2010-01-14 23:58:05 +03:00
{ ITCO_PCI_DEVICE ( 0x1c42 , TCO_CPTD ) } ,
{ ITCO_PCI_DEVICE ( 0x1c43 , TCO_CPTM ) } ,
2006-05-21 16:37:44 +04:00
{ 0 , } , /* End of list */
} ;
2008-05-19 17:06:25 +04:00
MODULE_DEVICE_TABLE ( pci , iTCO_wdt_pci_tbl ) ;
2006-05-21 16:37:44 +04:00
/* Address definitions for the TCO */
2008-05-19 17:06:25 +04:00
/* TCO base address */
2009-04-15 00:20:07 +04:00
# define TCOBASE (iTCO_wdt_private.ACPIBASE + 0x60)
2008-05-19 17:06:25 +04:00
/* SMI Control and Enable Register */
2009-04-15 00:20:07 +04:00
# define SMI_EN (iTCO_wdt_private.ACPIBASE + 0x30)
2006-05-21 16:37:44 +04:00
2009-04-15 00:20:07 +04:00
# define TCO_RLD (TCOBASE + 0x00) /* TCO Timer Reload and Curr. Value */
# define TCOv1_TMR (TCOBASE + 0x01) /* TCOv1 Timer Initial Value */
# define TCO_DAT_IN (TCOBASE + 0x02) /* TCO Data In Register */
# define TCO_DAT_OUT (TCOBASE + 0x03) /* TCO Data Out Register */
# define TCO1_STS (TCOBASE + 0x04) /* TCO1 Status Register */
# define TCO2_STS (TCOBASE + 0x06) /* TCO2 Status Register */
# define TCO1_CNT (TCOBASE + 0x08) /* TCO1 Control Register */
# define TCO2_CNT (TCOBASE + 0x0a) /* TCO2 Control Register */
# define TCOv2_TMR (TCOBASE + 0x12) /* TCOv2 Timer Initial Value */
2006-05-21 16:37:44 +04:00
/* internal variables */
static unsigned long is_active ;
static char expect_release ;
2008-05-19 17:06:25 +04:00
static struct { /* this is private data for the iTCO_wdt device */
/* TCO version/generation */
unsigned int iTCO_version ;
/* The cards ACPIBASE address (TCOBASE = ACPIBASE+0x60) */
unsigned long ACPIBASE ;
/* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2)*/
unsigned long __iomem * gcs ;
/* the lock for io operations */
spinlock_t io_lock ;
/* the PCI-device */
struct pci_dev * pdev ;
2006-05-21 16:37:44 +04:00
} iTCO_wdt_private ;
2008-05-19 17:06:25 +04:00
/* the watchdog platform device */
static struct platform_device * iTCO_wdt_platform_device ;
2006-06-30 10:44:53 +04:00
2006-05-21 16:37:44 +04:00
/* module parameters */
# define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
static int heartbeat = WATCHDOG_HEARTBEAT ; /* in seconds */
module_param ( heartbeat , int , 0 ) ;
2009-03-18 11:35:09 +03:00
MODULE_PARM_DESC ( heartbeat , " Watchdog heartbeat in seconds. "
" (2<heartbeat<39 (TCO v1) or 613 (TCO v2), default= "
__MODULE_STRING ( WATCHDOG_HEARTBEAT ) " ) " ) ;
2006-05-21 16:37:44 +04:00
static int nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , int , 0 ) ;
2008-05-19 17:06:25 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2006-11-12 20:05:09 +03:00
2006-05-21 16:37:44 +04:00
/*
* Some TCO specific functions
*/
static inline unsigned int seconds_to_ticks ( int seconds )
{
/* the internal timer is stored as ticks which decrement
* every 0.6 seconds */
return ( seconds * 10 ) / 6 ;
}
static void iTCO_wdt_set_NO_REBOOT_bit ( void )
{
u32 val32 ;
/* Set the NO_REBOOT bit: this disables reboots */
if ( iTCO_wdt_private . iTCO_version = = 2 ) {
val32 = readl ( iTCO_wdt_private . gcs ) ;
val32 | = 0x00000020 ;
writel ( val32 , iTCO_wdt_private . gcs ) ;
} else if ( iTCO_wdt_private . iTCO_version = = 1 ) {
pci_read_config_dword ( iTCO_wdt_private . pdev , 0xd4 , & val32 ) ;
val32 | = 0x00000002 ;
pci_write_config_dword ( iTCO_wdt_private . pdev , 0xd4 , val32 ) ;
}
}
static int iTCO_wdt_unset_NO_REBOOT_bit ( void )
{
int ret = 0 ;
u32 val32 ;
/* Unset the NO_REBOOT bit: this enables reboots */
if ( iTCO_wdt_private . iTCO_version = = 2 ) {
val32 = readl ( iTCO_wdt_private . gcs ) ;
val32 & = 0xffffffdf ;
writel ( val32 , iTCO_wdt_private . gcs ) ;
val32 = readl ( iTCO_wdt_private . gcs ) ;
if ( val32 & 0x00000020 )
ret = - EIO ;
} else if ( iTCO_wdt_private . iTCO_version = = 1 ) {
pci_read_config_dword ( iTCO_wdt_private . pdev , 0xd4 , & val32 ) ;
val32 & = 0xfffffffd ;
pci_write_config_dword ( iTCO_wdt_private . pdev , 0xd4 , val32 ) ;
pci_read_config_dword ( iTCO_wdt_private . pdev , 0xd4 , & val32 ) ;
if ( val32 & 0x00000002 )
ret = - EIO ;
}
return ret ; /* returns: 0 = OK, -EIO = Error */
}
static int iTCO_wdt_start ( void )
{
unsigned int val ;
spin_lock ( & iTCO_wdt_private . io_lock ) ;
2006-11-12 20:05:09 +03:00
iTCO_vendor_pre_start ( iTCO_wdt_private . ACPIBASE , heartbeat ) ;
2006-05-21 16:37:44 +04:00
/* disable chipset's NO_REBOOT bit */
if ( iTCO_wdt_unset_NO_REBOOT_bit ( ) ) {
2007-10-23 05:08:27 +04:00
spin_unlock ( & iTCO_wdt_private . io_lock ) ;
2009-03-18 11:35:09 +03:00
printk ( KERN_ERR PFX " failed to reset NO_REBOOT flag, "
" reboot disabled by hardware \n " ) ;
2006-05-21 16:37:44 +04:00
return - EIO ;
}
2008-11-19 22:39:58 +03:00
/* Force the timer to its reload value by writing to the TCO_RLD
register */
if ( iTCO_wdt_private . iTCO_version = = 2 )
outw ( 0x01 , TCO_RLD ) ;
else if ( iTCO_wdt_private . iTCO_version = = 1 )
outb ( 0x01 , TCO_RLD ) ;
2006-05-21 16:37:44 +04:00
/* Bit 11: TCO Timer Halt -> 0 = The TCO timer is enabled to count */
val = inw ( TCO1_CNT ) ;
val & = 0xf7ff ;
outw ( val , TCO1_CNT ) ;
val = inw ( TCO1_CNT ) ;
spin_unlock ( & iTCO_wdt_private . io_lock ) ;
if ( val & 0x0800 )
return - 1 ;
return 0 ;
}
static int iTCO_wdt_stop ( void )
{
unsigned int val ;
spin_lock ( & iTCO_wdt_private . io_lock ) ;
2006-11-12 20:05:09 +03:00
iTCO_vendor_pre_stop ( iTCO_wdt_private . ACPIBASE ) ;
2006-05-21 16:37:44 +04:00
/* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */
val = inw ( TCO1_CNT ) ;
val | = 0x0800 ;
outw ( val , TCO1_CNT ) ;
val = inw ( TCO1_CNT ) ;
/* Set the NO_REBOOT bit to prevent later reboots, just for sure */
iTCO_wdt_set_NO_REBOOT_bit ( ) ;
spin_unlock ( & iTCO_wdt_private . io_lock ) ;
if ( ( val & 0x0800 ) = = 0 )
return - 1 ;
return 0 ;
}
static int iTCO_wdt_keepalive ( void )
{
spin_lock ( & iTCO_wdt_private . io_lock ) ;
2006-11-12 20:05:09 +03:00
iTCO_vendor_pre_keepalive ( iTCO_wdt_private . ACPIBASE , heartbeat ) ;
2006-05-21 16:37:44 +04:00
/* Reload the timer by writing to the TCO Timer Counter register */
2008-05-19 17:06:25 +04:00
if ( iTCO_wdt_private . iTCO_version = = 2 )
2006-05-21 16:37:44 +04:00
outw ( 0x01 , TCO_RLD ) ;
2008-05-19 17:06:25 +04:00
else if ( iTCO_wdt_private . iTCO_version = = 1 )
2006-05-21 16:37:44 +04:00
outb ( 0x01 , TCO_RLD ) ;
spin_unlock ( & iTCO_wdt_private . io_lock ) ;
return 0 ;
}
static int iTCO_wdt_set_heartbeat ( int t )
{
unsigned int val16 ;
unsigned char val8 ;
unsigned int tmrval ;
tmrval = seconds_to_ticks ( t ) ;
/* from the specs: */
/* "Values of 0h-3h are ignored and should not be attempted" */
if ( tmrval < 0x04 )
return - EINVAL ;
if ( ( ( iTCO_wdt_private . iTCO_version = = 2 ) & & ( tmrval > 0x3ff ) ) | |
( ( iTCO_wdt_private . iTCO_version = = 1 ) & & ( tmrval > 0x03f ) ) )
return - EINVAL ;
2006-11-12 20:05:09 +03:00
iTCO_vendor_pre_set_heartbeat ( tmrval ) ;
2006-05-21 16:37:44 +04:00
/* Write new heartbeat to watchdog */
if ( iTCO_wdt_private . iTCO_version = = 2 ) {
spin_lock ( & iTCO_wdt_private . io_lock ) ;
val16 = inw ( TCOv2_TMR ) ;
val16 & = 0xfc00 ;
val16 | = tmrval ;
outw ( val16 , TCOv2_TMR ) ;
val16 = inw ( TCOv2_TMR ) ;
spin_unlock ( & iTCO_wdt_private . io_lock ) ;
if ( ( val16 & 0x3ff ) ! = tmrval )
return - EINVAL ;
} else if ( iTCO_wdt_private . iTCO_version = = 1 ) {
spin_lock ( & iTCO_wdt_private . io_lock ) ;
val8 = inb ( TCOv1_TMR ) ;
val8 & = 0xc0 ;
val8 | = ( tmrval & 0xff ) ;
outb ( val8 , TCOv1_TMR ) ;
val8 = inb ( TCOv1_TMR ) ;
spin_unlock ( & iTCO_wdt_private . io_lock ) ;
if ( ( val8 & 0x3f ) ! = tmrval )
return - EINVAL ;
}
heartbeat = t ;
return 0 ;
}
2008-05-19 17:06:25 +04:00
static int iTCO_wdt_get_timeleft ( int * time_left )
2006-05-21 16:37:44 +04:00
{
unsigned int val16 ;
unsigned char val8 ;
/* read the TCO Timer */
if ( iTCO_wdt_private . iTCO_version = = 2 ) {
spin_lock ( & iTCO_wdt_private . io_lock ) ;
val16 = inw ( TCO_RLD ) ;
val16 & = 0x3ff ;
spin_unlock ( & iTCO_wdt_private . io_lock ) ;
* time_left = ( val16 * 6 ) / 10 ;
} else if ( iTCO_wdt_private . iTCO_version = = 1 ) {
spin_lock ( & iTCO_wdt_private . io_lock ) ;
val8 = inb ( TCO_RLD ) ;
val8 & = 0x3f ;
spin_unlock ( & iTCO_wdt_private . io_lock ) ;
* time_left = ( val8 * 6 ) / 10 ;
2006-10-10 11:40:44 +04:00
} else
return - EINVAL ;
2006-05-21 16:37:44 +04:00
return 0 ;
}
/*
* / dev / watchdog handling
*/
2008-05-19 17:06:25 +04:00
static int iTCO_wdt_open ( struct inode * inode , struct file * file )
2006-05-21 16:37:44 +04:00
{
/* /dev/watchdog can only be opened once */
if ( test_and_set_bit ( 0 , & is_active ) )
return - EBUSY ;
/*
* Reload and activate timer
*/
iTCO_wdt_start ( ) ;
return nonseekable_open ( inode , file ) ;
}
2008-05-19 17:06:25 +04:00
static int iTCO_wdt_release ( struct inode * inode , struct file * file )
2006-05-21 16:37:44 +04:00
{
/*
* Shut off the timer .
*/
if ( expect_release = = 42 ) {
iTCO_wdt_stop ( ) ;
} else {
2008-05-19 17:06:25 +04:00
printk ( KERN_CRIT PFX
" Unexpected close, not stopping watchdog! \n " ) ;
2006-05-21 16:37:44 +04:00
iTCO_wdt_keepalive ( ) ;
}
clear_bit ( 0 , & is_active ) ;
expect_release = 0 ;
return 0 ;
}
2008-05-19 17:06:25 +04:00
static ssize_t iTCO_wdt_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
2006-05-21 16:37:44 +04:00
{
/* See if we got the magic character 'V' and reload the timer */
if ( len ) {
if ( ! nowayout ) {
size_t i ;
2008-05-19 17:06:25 +04:00
/* note: just in case someone wrote the magic
character five months ago . . . */
2006-05-21 16:37:44 +04:00
expect_release = 0 ;
2008-05-19 17:06:25 +04:00
/* scan to see whether or not we got the
magic character */
2006-05-21 16:37:44 +04:00
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
2008-08-07 00:19:41 +04:00
if ( get_user ( c , data + i ) )
2006-05-21 16:37:44 +04:00
return - EFAULT ;
if ( c = = ' V ' )
expect_release = 42 ;
}
}
/* someone wrote to us, we should reload the timer */
iTCO_wdt_keepalive ( ) ;
}
return len ;
}
2008-05-19 17:06:25 +04:00
static long iTCO_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2006-05-21 16:37:44 +04:00
{
int new_options , retval = - EINVAL ;
int new_heartbeat ;
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
static struct watchdog_info ident = {
. options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
. firmware_version = 0 ,
. identity = DRV_NAME ,
} ;
switch ( cmd ) {
2008-05-19 17:06:25 +04:00
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
2006-05-21 16:37:44 +04:00
2008-05-19 17:06:25 +04:00
case WDIOC_SETOPTIONS :
{
if ( get_user ( new_options , p ) )
return - EFAULT ;
2006-05-21 16:37:44 +04:00
2008-05-19 17:06:25 +04:00
if ( new_options & WDIOS_DISABLECARD ) {
iTCO_wdt_stop ( ) ;
retval = 0 ;
2006-05-21 16:37:44 +04:00
}
2008-05-19 17:06:25 +04:00
if ( new_options & WDIOS_ENABLECARD ) {
2006-05-21 16:37:44 +04:00
iTCO_wdt_keepalive ( ) ;
2008-05-19 17:06:25 +04:00
iTCO_wdt_start ( ) ;
retval = 0 ;
2006-05-21 16:37:44 +04:00
}
2008-05-19 17:06:25 +04:00
return retval ;
}
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
iTCO_wdt_keepalive ( ) ;
return 0 ;
2008-05-19 17:06:25 +04:00
case WDIOC_SETTIMEOUT :
{
if ( get_user ( new_heartbeat , p ) )
return - EFAULT ;
if ( iTCO_wdt_set_heartbeat ( new_heartbeat ) )
return - EINVAL ;
iTCO_wdt_keepalive ( ) ;
/* Fall */
}
case WDIOC_GETTIMEOUT :
return put_user ( heartbeat , p ) ;
case WDIOC_GETTIMELEFT :
{
int time_left ;
if ( iTCO_wdt_get_timeleft ( & time_left ) )
return - EINVAL ;
return put_user ( time_left , p ) ;
}
default :
return - ENOTTY ;
2006-05-21 16:37:44 +04:00
}
}
/*
* Kernel Interfaces
*/
2007-02-12 11:55:32 +03:00
static const struct file_operations iTCO_wdt_fops = {
2008-05-19 17:06:25 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = iTCO_wdt_write ,
. unlocked_ioctl = iTCO_wdt_ioctl ,
. open = iTCO_wdt_open ,
. release = iTCO_wdt_release ,
2006-05-21 16:37:44 +04:00
} ;
static struct miscdevice iTCO_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & iTCO_wdt_fops ,
} ;
/*
* Init & exit routines
*/
2008-05-19 17:06:25 +04:00
static int __devinit iTCO_wdt_init ( struct pci_dev * pdev ,
const struct pci_device_id * ent , struct platform_device * dev )
2006-05-21 16:37:44 +04:00
{
int ret ;
u32 base_address ;
unsigned long RCBA ;
2009-01-28 23:51:04 +03:00
unsigned long val32 ;
2006-05-21 16:37:44 +04:00
/*
* Find the ACPI / PM base I / O address which is the base
* for the TCO registers ( TCOBASE = ACPIBASE + 0x60 )
* ACPIBASE is bits [ 15 : 7 ] from 0x40 - 0x43
*/
pci_read_config_dword ( pdev , 0x40 , & base_address ) ;
2007-05-11 22:59:24 +04:00
base_address & = 0x0000ff80 ;
2006-05-21 16:37:44 +04:00
if ( base_address = = 0x00000000 ) {
/* Something's wrong here, ACPIBASE has to be set */
printk ( KERN_ERR PFX " failed to get TCOBASE address \n " ) ;
2006-07-20 00:39:13 +04:00
pci_dev_put ( pdev ) ;
2006-05-21 16:37:44 +04:00
return - ENODEV ;
}
2008-05-19 17:06:25 +04:00
iTCO_wdt_private . iTCO_version =
iTCO_chipset_info [ ent - > driver_data ] . iTCO_version ;
2006-05-21 16:37:44 +04:00
iTCO_wdt_private . ACPIBASE = base_address ;
iTCO_wdt_private . pdev = pdev ;
2008-05-19 17:06:25 +04:00
/* Get the Memory-Mapped GCS register, we need it for the
NO_REBOOT flag ( TCO v2 ) . To get access to it you have to
read RCBA from PCI Config space 0xf0 and use it as base .
GCS = RCBA + ICH6_GCS ( 0x3410 ) . */
2006-05-21 16:37:44 +04:00
if ( iTCO_wdt_private . iTCO_version = = 2 ) {
pci_read_config_dword ( pdev , 0xf0 , & base_address ) ;
2009-06-05 15:13:08 +04:00
if ( ( base_address & 1 ) = = 0 ) {
printk ( KERN_ERR PFX " RCBA is disabled by harddware \n " ) ;
ret = - ENODEV ;
goto out ;
}
2006-05-21 16:37:44 +04:00
RCBA = base_address & 0xffffc000 ;
2008-05-19 17:06:25 +04:00
iTCO_wdt_private . gcs = ioremap ( ( RCBA + 0x3410 ) , 4 ) ;
2006-05-21 16:37:44 +04:00
}
/* Check chipset's NO_REBOOT bit */
2006-11-12 20:05:09 +03:00
if ( iTCO_wdt_unset_NO_REBOOT_bit ( ) & & iTCO_vendor_check_noreboot_on ( ) ) {
2009-03-18 11:35:09 +03:00
printk ( KERN_ERR PFX " failed to reset NO_REBOOT flag, "
" reboot disabled by hardware \n " ) ;
2006-05-21 16:37:44 +04:00
ret = - ENODEV ; /* Cannot reset NO_REBOOT bit */
2009-06-05 15:13:08 +04:00
goto out_unmap ;
2006-05-21 16:37:44 +04:00
}
/* Set the NO_REBOOT bit to prevent later reboots, just for sure */
iTCO_wdt_set_NO_REBOOT_bit ( ) ;
2008-11-19 22:39:58 +03:00
/* The TCO logic uses the TCO_EN bit in the SMI_EN register */
2006-05-21 16:37:44 +04:00
if ( ! request_region ( SMI_EN , 4 , " iTCO_wdt " ) ) {
2008-05-19 17:06:25 +04:00
printk ( KERN_ERR PFX
" I/O address 0x%04lx already in use \n " , SMI_EN ) ;
2006-05-21 16:37:44 +04:00
ret = - EIO ;
2009-06-05 15:13:08 +04:00
goto out_unmap ;
2006-05-21 16:37:44 +04:00
}
2009-01-28 23:51:04 +03:00
/* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# */
val32 = inl ( SMI_EN ) ;
val32 & = 0xffffdfff ; /* Turn off SMI clearing watchdog */
outl ( val32 , SMI_EN ) ;
2006-05-21 16:37:44 +04:00
2008-05-19 17:06:25 +04:00
/* The TCO I/O registers reside in a 32-byte range pointed to
by the TCOBASE value */
if ( ! request_region ( TCOBASE , 0x20 , " iTCO_wdt " ) ) {
printk ( KERN_ERR PFX " I/O address 0x%04lx already in use \n " ,
2006-05-21 16:37:44 +04:00
TCOBASE ) ;
ret = - EIO ;
2008-11-19 22:39:58 +03:00
goto unreg_smi_en ;
2006-05-21 16:37:44 +04:00
}
2008-05-19 17:06:25 +04:00
printk ( KERN_INFO PFX
" Found a %s TCO device (Version=%d, TCOBASE=0x%04lx) \n " ,
iTCO_chipset_info [ ent - > driver_data ] . name ,
iTCO_chipset_info [ ent - > driver_data ] . iTCO_version ,
TCOBASE ) ;
2006-05-21 16:37:44 +04:00
/* Clear out the (probably old) status */
2008-11-19 23:02:02 +03:00
outb ( 8 , TCO1_STS ) ; /* Clear the Time Out Status bit */
outb ( 2 , TCO2_STS ) ; /* Clear SECOND_TO_STS bit */
outb ( 4 , TCO2_STS ) ; /* Clear BOOT_STS bit */
2006-05-21 16:37:44 +04:00
/* Make sure the watchdog is not running */
iTCO_wdt_stop ( ) ;
2008-05-19 17:06:25 +04:00
/* Check that the heartbeat value is within it's range;
if not reset to the default */
2006-05-21 16:37:44 +04:00
if ( iTCO_wdt_set_heartbeat ( heartbeat ) ) {
iTCO_wdt_set_heartbeat ( WATCHDOG_HEARTBEAT ) ;
2009-03-18 11:35:09 +03:00
printk ( KERN_INFO PFX
" heartbeat value must be 2 < heartbeat < 39 (TCO v1) "
" or 613 (TCO v2), using %d \n " , heartbeat ) ;
2006-05-21 16:37:44 +04:00
}
ret = misc_register ( & iTCO_wdt_miscdev ) ;
if ( ret ! = 0 ) {
2008-05-19 17:06:25 +04:00
printk ( KERN_ERR PFX
" cannot register miscdev on minor=%d (err=%d) \n " ,
WATCHDOG_MINOR , ret ) ;
2006-08-05 22:59:01 +04:00
goto unreg_region ;
2006-05-21 16:37:44 +04:00
}
2008-05-19 17:06:25 +04:00
printk ( KERN_INFO PFX " initialized. heartbeat=%d sec (nowayout=%d) \n " ,
heartbeat , nowayout ) ;
2006-05-21 16:37:44 +04:00
return 0 ;
unreg_region :
2008-05-19 17:06:25 +04:00
release_region ( TCOBASE , 0x20 ) ;
2008-11-19 22:39:58 +03:00
unreg_smi_en :
release_region ( SMI_EN , 4 ) ;
2009-06-05 15:13:08 +04:00
out_unmap :
2006-05-21 16:37:44 +04:00
if ( iTCO_wdt_private . iTCO_version = = 2 )
iounmap ( iTCO_wdt_private . gcs ) ;
2009-06-05 15:13:08 +04:00
out :
2006-07-20 00:39:13 +04:00
pci_dev_put ( iTCO_wdt_private . pdev ) ;
2006-08-05 22:59:01 +04:00
iTCO_wdt_private . ACPIBASE = 0 ;
2006-05-21 16:37:44 +04:00
return ret ;
}
2007-08-31 12:15:34 +04:00
static void __devexit iTCO_wdt_cleanup ( void )
2006-05-21 16:37:44 +04:00
{
/* Stop the timer before we leave */
if ( ! nowayout )
iTCO_wdt_stop ( ) ;
/* Deregister */
misc_deregister ( & iTCO_wdt_miscdev ) ;
release_region ( TCOBASE , 0x20 ) ;
2008-11-19 22:39:58 +03:00
release_region ( SMI_EN , 4 ) ;
2006-05-21 16:37:44 +04:00
if ( iTCO_wdt_private . iTCO_version = = 2 )
iounmap ( iTCO_wdt_private . gcs ) ;
2006-07-20 00:39:13 +04:00
pci_dev_put ( iTCO_wdt_private . pdev ) ;
2006-08-05 22:59:01 +04:00
iTCO_wdt_private . ACPIBASE = 0 ;
2006-05-21 16:37:44 +04:00
}
2007-08-31 12:15:34 +04:00
static int __devinit iTCO_wdt_probe ( struct platform_device * dev )
2006-05-21 16:37:44 +04:00
{
int found = 0 ;
struct pci_dev * pdev = NULL ;
const struct pci_device_id * ent ;
spin_lock_init ( & iTCO_wdt_private . io_lock ) ;
for_each_pci_dev ( pdev ) {
ent = pci_match_id ( iTCO_wdt_pci_tbl , pdev ) ;
if ( ent ) {
2006-06-30 10:44:53 +04:00
if ( ! ( iTCO_wdt_init ( pdev , ent , dev ) ) ) {
2006-05-21 16:37:44 +04:00
found + + ;
break ;
}
}
}
if ( ! found ) {
printk ( KERN_INFO PFX " No card detected \n " ) ;
return - ENODEV ;
}
return 0 ;
}
2007-08-31 12:15:34 +04:00
static int __devexit iTCO_wdt_remove ( struct platform_device * dev )
2006-05-21 16:37:44 +04:00
{
if ( iTCO_wdt_private . ACPIBASE )
iTCO_wdt_cleanup ( ) ;
2006-06-30 10:44:53 +04:00
return 0 ;
}
static void iTCO_wdt_shutdown ( struct platform_device * dev )
{
iTCO_wdt_stop ( ) ;
}
# define iTCO_wdt_suspend NULL
# define iTCO_wdt_resume NULL
static struct platform_driver iTCO_wdt_driver = {
. probe = iTCO_wdt_probe ,
2007-08-31 12:15:34 +04:00
. remove = __devexit_p ( iTCO_wdt_remove ) ,
2006-06-30 10:44:53 +04:00
. shutdown = iTCO_wdt_shutdown ,
. suspend = iTCO_wdt_suspend ,
. resume = iTCO_wdt_resume ,
. driver = {
. owner = THIS_MODULE ,
. name = DRV_NAME ,
} ,
} ;
static int __init iTCO_wdt_init_module ( void )
{
int err ;
2008-11-19 22:39:58 +03:00
printk ( KERN_INFO PFX " Intel TCO WatchDog Timer Driver v%s \n " ,
DRV_VERSION ) ;
2006-06-30 10:44:53 +04:00
err = platform_driver_register ( & iTCO_wdt_driver ) ;
if ( err )
return err ;
2008-05-19 17:06:25 +04:00
iTCO_wdt_platform_device = platform_device_register_simple ( DRV_NAME ,
- 1 , NULL , 0 ) ;
2006-06-30 10:44:53 +04:00
if ( IS_ERR ( iTCO_wdt_platform_device ) ) {
err = PTR_ERR ( iTCO_wdt_platform_device ) ;
goto unreg_platform_driver ;
}
return 0 ;
unreg_platform_driver :
platform_driver_unregister ( & iTCO_wdt_driver ) ;
return err ;
}
static void __exit iTCO_wdt_cleanup_module ( void )
{
platform_device_unregister ( iTCO_wdt_platform_device ) ;
platform_driver_unregister ( & iTCO_wdt_driver ) ;
2006-05-21 16:37:44 +04:00
printk ( KERN_INFO PFX " Watchdog Module Unloaded. \n " ) ;
}
module_init ( iTCO_wdt_init_module ) ;
module_exit ( iTCO_wdt_cleanup_module ) ;
MODULE_AUTHOR ( " Wim Van Sebroeck <wim@iguana.be> " ) ;
MODULE_DESCRIPTION ( " Intel TCO WatchDog Timer Driver " ) ;
2006-06-30 10:44:53 +04:00
MODULE_VERSION ( DRV_VERSION ) ;
2006-05-21 16:37:44 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;