From: David Hardeman I wrote earlier to the list[1] asking for a driver for the watchdog included in the 6300ESB chipset. I got a 2.4 driver via private email from Ross Biro which I've changed into what I hope resembles a 2.6 driver (which was done by looking a lot at the watchdog drivers already in the 2.6 tree). I've attached the result, and I'm hoping to get some feedback on the coding as a first step. I can't actually test it on the hardware right now as I won't have physical access until April. So my own tests have been limited to "compiles-without-warnings" and "can-be-insmodded-in-other-machine-without-oops". [1] http://marc.theaimsgroup.com/?l=linux-kernel&m=110711079825794&w=2 [2] http://marc.theaimsgroup.com/?l=linux-kernel&m=110711973917746&w=2 Signed-off-by: Andrew Morton --- 25-akpm/drivers/char/watchdog/Kconfig | 10 25-akpm/drivers/char/watchdog/Makefile | 1 25-akpm/drivers/char/watchdog/i6300esb.c | 508 +++++++++++++++++++++++++++++++ 25-akpm/drivers/char/watchdog/i6300esb.h | 62 +++ 4 files changed, 581 insertions(+) diff -puN /dev/null drivers/char/watchdog/i6300esb.c --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/drivers/char/watchdog/i6300esb.c 2005-02-21 15:51:59.000000000 -0800 @@ -0,0 +1,508 @@ +/* + * i6300esb 0.03: Watchdog timer driver for Intel 6300ESB chipset + * + * (c) Copyright 2004 Google 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. + * + * based on i810-tco.c which is + * + * (c) Copyright 2000 kernel concepts + * developed for + * Jentro AG, Haar/Munich (Germany) + * + * which is in turn based on softdog.c by Alan Cox + * + * The timer is implemented in the following I/O controller hubs: + * (See the intel documentation on http://developer.intel.com.) + * 6300ESB chip : document number 300641-003 + * + * 2004YYZZ Ross Biro + * Initial version 0.01 + * 2004YYZZ Ross Biro + * Version 0.02 + * 20050210 David Härdeman + * Ported driver to kernel 2.6 + */ + +/* + * Includes, defines, variables, module parameters, ... + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "i6300esb.h" + +/* Module and version information */ +#define ESB_VERSION "0.03" +#define ESB_MODULE_NAME "i6300ESB timer" +#define ESB_DRIVER_NAME ESB_MODULE_NAME ", v" ESB_VERSION +#define PFX ESB_MODULE_NAME ": " + +/* internal variables */ +static void __iomem *BASEADDR; +static spinlock_t esb_lock; /* Guards the hardware */ +static unsigned long timer_alive; +static struct pci_dev *esb_pci; +static unsigned short triggered; /* The status of the watchdog upon boot */ +static char esb_expect_close; + +/* module parameters */ +#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (1 (2 * 0x03ff)) + return -EINVAL; + + spin_lock(&esb_lock); + + /* 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 1KHz) + */ + val = time << 9; + + /* Write timer 1 */ + esb_unlock_registers(); + writel(val, ESB_TIMER1_REG); + + /* Write timer 2 */ + esb_unlock_registers(); + writel(val, ESB_TIMER2_REG); + + /* Reload */ + esb_unlock_registers(); + writew(0x10, ESB_RELOAD_REG); + + /* FIXME: Do we need to flush everything out? */ + + /* Done */ + heartbeat = time; + spin_unlock(&esb_lock); + return 0; +} + +static int esb_timer_read (void) +{ + u32 count; + + /* This isn't documented, and doesn't take into + * acount which stage is running, but it looks + * like a 20 bit count down, so we might as well report it. + */ + pci_read_config_dword(esb_pci, 0x64, &count); + return (int)count; +} + +/* + * /dev/watchdog handling + */ + +static int esb_open (struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &timer_alive)) + return -EBUSY; + + /* Reload and activate timer */ + esb_timer_keepalive (); + esb_timer_start (); + + return nonseekable_open(inode, file); +} + +static int esb_release (struct inode *inode, struct file *file) +{ + /* Shut off the timer. */ + if (esb_expect_close == 42) { + esb_timer_stop (); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + esb_timer_keepalive (); + } + clear_bit(0, &timer_alive); + esb_expect_close = 0; + return 0; +} + +static ssize_t esb_write (struct file *file, const char __user *data, + size_t len, loff_t * ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* note: just in case someone wrote the magic character + * five months ago... */ + esb_expect_close = 0; + + /* scan to see whether or not we got the magic character */ + for (i = 0; i != len; i++) { + char c; + if(get_user(c, data+i)) + return -EFAULT; + if (c == 'V') + esb_expect_close = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + esb_timer_keepalive (); + } + return len; +} + +static int esb_ioctl (struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + 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 = ESB_MODULE_NAME, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, + sizeof (ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + return put_user (esb_timer_read(), p); + + case WDIOC_GETBOOTSTATUS: + return put_user (triggered, p); + + case WDIOC_KEEPALIVE: + esb_timer_keepalive (); + return 0; + + case WDIOC_SETOPTIONS: + { + if (get_user (new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + esb_timer_stop (); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + esb_timer_keepalive (); + esb_timer_start (); + retval = 0; + } + + return retval; + } + + case WDIOC_SETTIMEOUT: + { + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (esb_timer_set_heartbeat(new_heartbeat)) + return -EINVAL; + + esb_timer_keepalive (); + /* Fall */ + } + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + + default: + return -ENOIOCTLCMD; + } +} + +/* + * Notify system + */ + +static int esb_notify_sys (struct notifier_block *this, unsigned long code, void *unused) +{ + if (code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + esb_timer_stop (); + } + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations esb_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = esb_write, + .ioctl = esb_ioctl, + .open = esb_open, + .release = esb_release, +}; + +static struct miscdevice esb_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &esb_fops, +}; + +static struct notifier_block esb_notifier = { + .notifier_call = esb_notify_sys, +}; + +/* + * Data for PCI driver interface + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * register a pci_driver, because someone else might one day + * want to register another driver on the same PCI id. + */ +static struct pci_device_id esb_pci_tbl[] = { + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_9, PCI_ANY_ID, PCI_ANY_ID, }, + { 0, }, /* End of list */ +}; +MODULE_DEVICE_TABLE (pci, esb_pci_tbl); + +/* + * Init & exit routines + */ + +static unsigned char __init esb_getdevice (void) +{ + u8 val1; + unsigned short val2; + + struct pci_dev *dev = NULL; + /* + * Find the PCI device + */ + + while ((dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + if (pci_match_device(esb_pci_tbl, dev)) { + esb_pci = dev; + break; + } + } + + if (esb_pci) { + if (pci_enable_device(esb_pci)) { + printk (KERN_ERR PFX "failed to enable device\n"); + goto out; + } + + if (pci_request_region(esb_pci, 0, ESB_MODULE_NAME)) { + printk (KERN_ERR PFX "failed to request region\n"); + goto err_disable; + } + + BASEADDR = ioremap(pci_resource_start(esb_pci, 0), + pci_resource_len(esb_pci, 0)); + if (BASEADDR == NULL) { + /* Something's wrong here, BASEADDR has to be set */ + printk (KERN_ERR PFX "failed to get BASEADDR\n"); + goto err_release; + } + + /* + * 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. + * + * We also enable reboots and set the timer frequency to + * the PCI clock divided by 2^15 (approx 1KHz). + */ + 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) + printk (KERN_WARNING PFX "nowayout already set\n"); + + /* 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); + triggered = (val2 & (0x01 << 9) >> 9); + + /* Reset trigger flag and timers */ + esb_unlock_registers(); + writew((0x11 << 8), ESB_RELOAD_REG); + + /* Done */ + return 1; + +err_release: + pci_release_region(esb_pci, 0); +err_disable: + pci_disable_device(esb_pci); + } +out: + return 0; +} + +static int __init watchdog_init (void) +{ + int ret; + + spin_lock_init(&esb_lock); + + /* Check whether or not the hardware watchdog is there */ + if (!esb_getdevice () || esb_pci == NULL) + return -ENODEV; + + /* Check that the heartbeat value is within it's range ; if not reset to the default */ + if (esb_timer_set_heartbeat (heartbeat)) { + esb_timer_set_heartbeat (WATCHDOG_HEARTBEAT); + printk(KERN_INFO PFX "heartbeat value must be 1, All Rights Reserved. + * http://www.kernelconcepts.de + * + * 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 kernel concepts nor Nils Faerber admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 2000 kernel concepts + * developed for + * Jentro AG, Haar/Munich (Germany) + * + * TCO timer driver for i8xx chipsets + * based on softdog.c by Alan Cox + * + * For history and the complete list of supported I/O Controller Hub's + * see i8xx_tco.c + */ + + +/* + * Some address definitions for the TCO + */ + +/* PCI configuration registers */ +#define ESB_CONFIG_REG 0x60 /* Config register */ +#define ESB_LOCK_REG 0x68 /* WDT lock register */ + +/* Memory mapped registers */ +#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 */ + + +/* + * Some register bits + */ + +/* Lock register bits */ +#define ESB_WDT_FUNC ( 0x01 << 2 ) /* Watchdog functionality */ +#define ESB_WDT_ENABLE ( 0x01 << 1 ) /* Enable WDT */ +#define ESB_WDT_LOCK ( 0x01 << 0 ) /* Lock (nowayout) */ + +/* Config register bits */ +#define ESB_WDT_REBOOT ( 0x01 << 5 ) /* Enable reboot on timeout */ +#define ESB_WDT_FREQ ( 0x01 << 2 ) /* Decrement frequency */ +#define ESB_WDT_INTTYPE ( 0x11 << 0 ) /* Interrupt type on timer1 timeout */ + + +/* + * Some magic constants + */ +#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */ +#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */ diff -puN drivers/char/watchdog/Kconfig~6300esb-watchdog-driver drivers/char/watchdog/Kconfig --- 25/drivers/char/watchdog/Kconfig~6300esb-watchdog-driver 2005-02-21 15:51:59.000000000 -0800 +++ 25-akpm/drivers/char/watchdog/Kconfig 2005-02-21 15:51:59.000000000 -0800 @@ -252,6 +252,16 @@ config I8XX_TCO To compile this driver as a module, choose M here: the module will be called i8xx_tco. +config I6300ESB_WDT + tristate "Intel 6300ESB Timer/Watchdog" + depends on WATCHDOG && X86 && PCI + ---help--- + Hardware driver for the watchdog timer built into the Intel + 6300ESB controller hub. + + To compile this driver as a module, choose M here: the + module will be called i6300esb. + config SC1200_WDT tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" depends on WATCHDOG && X86 diff -puN drivers/char/watchdog/Makefile~6300esb-watchdog-driver drivers/char/watchdog/Makefile --- 25/drivers/char/watchdog/Makefile~6300esb-watchdog-driver 2005-02-21 15:51:59.000000000 -0800 +++ 25-akpm/drivers/char/watchdog/Makefile 2005-02-21 15:51:59.000000000 -0800 @@ -19,6 +19,7 @@ obj-$(CONFIG_WDTPCI) += wdt_pci.o obj-$(CONFIG_21285_WATCHDOG) += wdt285.o obj-$(CONFIG_977_WATCHDOG) += wdt977.o obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o +obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o obj-$(CONFIG_MACHZ_WDT) += machzwd.o obj-$(CONFIG_SH_WDT) += shwdt.o obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o _