patch-2.4.20 linux-2.4.20/drivers/char/amd7xx_tco.c
Next file: linux-2.4.20/drivers/char/amiserial.c
Previous file: linux-2.4.20/drivers/char/amd76x_pm.h
Back to the patch index
Back to the overall index
- Lines: 378
- Date:
Thu Nov 28 15:53:12 2002
- Orig file:
linux-2.4.19/drivers/char/amd7xx_tco.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -urN linux-2.4.19/drivers/char/amd7xx_tco.c linux-2.4.20/drivers/char/amd7xx_tco.c
@@ -0,0 +1,377 @@
+/*
+ * AMD 766/768 TCO Timer Driver
+ * (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
+ * All Rights Reserved.
+ *
+ * Parts from;
+ * Hardware driver for the AMD 768 Random Number Generator (RNG)
+ * (c) Copyright 2001 Red Hat Inc <alan@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * The author(s) of this software shall not be held liable for damages
+ * of any nature resulting due to the use of this software. This
+ * software is provided AS-IS with no warranties.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/spinlock.h>
+#include <linux/ioport.h>
+#include <asm/semaphore.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+
+#define AMDTCO_MODULE_VER "build 20020601"
+#define AMDTCO_MODULE_NAME "amd7xx_tco"
+#define PFX AMDTCO_MODULE_NAME ": "
+
+#define MAX_TIMEOUT 38 /* max of 38 seconds */
+
+/* pmbase registers */
+#define GLOBAL_SMI_REG 0x2a
+#define TCO_EN (1 << 1) /* bit 1 in global SMI register */
+#define TCO_RELOAD_REG 0x40 /* bits 0-5 are current count, 6-7 are reserved */
+#define TCO_INITVAL_REG 0x41 /* bits 0-5 are value to load, 6-7 are reserved */
+#define TCO_TIMEOUT_MASK 0x3f
+#define TCO_STATUS2_REG 0x46
+#define NDTO_STS2 (1 << 1) /* we're interested in the second timeout */
+#define BOOT_STS (1 << 2) /* will be set if NDTO_STS2 was set before reboot */
+#define TCO_CTRL1_REG 0x48
+#define TCO_HALT (1 << 11)
+
+static char banner[] __initdata = KERN_INFO PFX AMDTCO_MODULE_VER;
+static int timeout = 38;
+static u32 pmbase; /* PMxx I/O base */
+static struct pci_dev *dev;
+static struct semaphore open_sem;
+spinlock_t amdtco_lock; /* only for device access */
+static int expect_close = 0;
+
+MODULE_PARM(timeout, "i");
+MODULE_PARM_DESC(timeout, "range is 0-38 seconds, default is 38");
+
+static inline int amdtco_status(void)
+{
+ u16 reg;
+ int status = 0;
+
+ reg = inb(pmbase+TCO_CTRL1_REG);
+ if ((reg & TCO_HALT) == 0)
+ status |= WDIOF_KEEPALIVEPING;
+
+ reg = inb(pmbase+TCO_STATUS2_REG);
+ if (reg & BOOT_STS)
+ status |= WDIOF_CARDRESET;
+
+ return status;
+}
+
+static inline void amdtco_ping(void)
+{
+ u8 reg;
+
+ spin_lock(&amdtco_lock);
+ reg = inb(pmbase+TCO_RELOAD_REG);
+ outb(1 | reg, pmbase+TCO_RELOAD_REG);
+ spin_unlock(&amdtco_lock);
+}
+
+static inline int amdtco_gettimeout(void)
+{
+ return inb(TCO_RELOAD_REG) & TCO_TIMEOUT_MASK;
+}
+
+static inline void amdtco_settimeout(unsigned int timeout)
+{
+ u8 reg;
+
+ spin_lock(&amdtco_lock);
+ reg = inb(pmbase+TCO_INITVAL_REG);
+ reg |= timeout & TCO_TIMEOUT_MASK;
+ outb(reg, pmbase+TCO_INITVAL_REG);
+ spin_unlock(&amdtco_lock);
+}
+
+static inline void amdtco_global_enable(void)
+{
+ u16 reg;
+
+ spin_lock(&amdtco_lock);
+ reg = inw(pmbase+GLOBAL_SMI_REG);
+ reg |= TCO_EN;
+ outw(reg, pmbase+GLOBAL_SMI_REG);
+ spin_unlock(&amdtco_lock);
+}
+
+static inline void amdtco_enable(void)
+{
+ u16 reg;
+
+ spin_lock(&amdtco_lock);
+ reg = inw(pmbase+TCO_CTRL1_REG);
+ reg &= ~TCO_HALT;
+ outw(reg, pmbase+TCO_CTRL1_REG);
+ spin_unlock(&amdtco_lock);
+}
+
+static inline void amdtco_disable(void)
+{
+ u16 reg;
+
+ spin_lock(&amdtco_lock);
+ reg = inw(pmbase+TCO_CTRL1_REG);
+ reg |= TCO_HALT;
+ outw(reg, pmbase+TCO_CTRL1_REG);
+ spin_unlock(&amdtco_lock);
+}
+
+static int amdtco_fop_open(struct inode *inode, struct file *file)
+{
+ if (down_trylock(&open_sem))
+ return -EBUSY;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+ MOD_INC_USE_COUNT;
+#endif
+
+ if (timeout > MAX_TIMEOUT)
+ timeout = MAX_TIMEOUT;
+
+ amdtco_settimeout(timeout);
+ amdtco_global_enable();
+ amdtco_ping();
+ printk(KERN_INFO PFX "Watchdog enabled, timeout = %d/%d seconds",
+ amdtco_gettimeout(), timeout);
+
+ return 0;
+}
+
+
+static int amdtco_fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int new_timeout;
+ int tmp;
+
+ static struct watchdog_info ident = {
+ options: WDIOF_SETTIMEOUT | WDIOF_CARDRESET,
+ identity: "AMD 766/768"
+ };
+
+ switch (cmd) {
+ default:
+ return -ENOTTY;
+
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user((struct watchdog_info *)arg, &ident, sizeof ident))
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_GETSTATUS:
+ return put_user(amdtco_status(), (int *)arg);
+
+ case WDIOC_KEEPALIVE:
+ amdtco_ping();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, (int *)arg))
+ return -EFAULT;
+
+ if (new_timeout < 0)
+ return -EINVAL;
+
+ if (new_timeout > MAX_TIMEOUT)
+ new_timeout = MAX_TIMEOUT;
+
+ timeout = new_timeout;
+ amdtco_settimeout(timeout);
+ /* fall through and return the new timeout */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(amdtco_gettimeout(), (int *)arg);
+
+ case WDIOC_SETOPTIONS:
+ if (copy_from_user(&tmp, (int *)arg, sizeof tmp))
+ return -EFAULT;
+
+ if (tmp & WDIOS_DISABLECARD)
+ amdtco_disable();
+
+ if (tmp & WDIOS_ENABLECARD)
+ amdtco_enable();
+
+ return 0;
+ }
+}
+
+
+static int amdtco_fop_release(struct inode *inode, struct file *file)
+{
+ if (expect_close) {
+ amdtco_disable();
+ printk(KERN_INFO PFX "Watchdog disabled\n");
+ } else {
+ amdtco_ping();
+ printk(KERN_CRIT PFX "Unexpected close!, timeout in %d seconds)\n", timeout);
+ }
+
+ up(&open_sem);
+ return 0;
+}
+
+
+static ssize_t amdtco_fop_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+ if (ppos != &file->f_pos)
+ return -ESPIPE;
+
+ if (len) {
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+ size_t i;
+ char c;
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ if (get_user(c, data + i))
+ return -EFAULT;
+
+ if (c == 'V')
+ expect_close = 1;
+ }
+#endif
+ amdtco_ping();
+ return len;
+ }
+
+ return 0;
+}
+
+
+static int amdtco_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ amdtco_disable();
+
+ return NOTIFY_DONE;
+}
+
+
+static struct notifier_block amdtco_notifier =
+{
+ notifier_call: amdtco_notify_sys
+};
+
+static struct file_operations amdtco_fops =
+{
+ owner: THIS_MODULE,
+ write: amdtco_fop_write,
+ ioctl: amdtco_fop_ioctl,
+ open: amdtco_fop_open,
+ release: amdtco_fop_release
+};
+
+static struct miscdevice amdtco_miscdev =
+{
+ minor: WATCHDOG_MINOR,
+ name: "watchdog",
+ fops: &amdtco_fops
+};
+
+static struct pci_device_id amdtco_pci_tbl[] __initdata = {
+ /* AMD 766 PCI_IDs here */
+ { 0x1022, 0x7443, PCI_ANY_ID, PCI_ANY_ID, },
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE (pci, amdtco_pci_tbl);
+
+static int __init amdtco_init(void)
+{
+ int ret;
+
+ sema_init(&open_sem, 1);
+ spin_lock_init(&amdtco_lock);
+
+ pci_for_each_dev(dev) {
+ if (pci_match_device (amdtco_pci_tbl, dev) != NULL)
+ goto found_one;
+ }
+
+ return -ENODEV;
+
+found_one:
+
+ if ((ret = register_reboot_notifier(&amdtco_notifier))) {
+ printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret);
+ goto out_clean;
+ }
+
+ if ((ret = misc_register(&amdtco_miscdev))) {
+ printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR);
+ goto out_unreg_reboot;
+ }
+
+ pci_read_config_dword(dev, 0x58, &pmbase);
+ pmbase &= 0x0000FF00;
+
+ if (pmbase == 0) {
+ printk (KERN_ERR PFX "power management base not set\n");
+ ret = -EIO;
+ goto out_unreg_misc;
+ }
+
+ /* ret = 0; */
+ printk(banner);
+ goto out_clean;
+
+out_unreg_misc:
+ misc_deregister(&amdtco_miscdev);
+out_unreg_reboot:
+ unregister_reboot_notifier(&amdtco_notifier);
+out_clean:
+ return ret;
+}
+
+static void __exit amdtco_exit(void)
+{
+ misc_deregister(&amdtco_miscdev);
+ unregister_reboot_notifier(&amdtco_notifier);
+}
+
+
+#ifndef MODULE
+static int __init amdtco_setup(char *str)
+{
+ int ints[4];
+
+ str = get_options (str, ARRAY_SIZE(ints), ints);
+ if (ints[0] > 0)
+ timeout = ints[1];
+
+ return 1;
+}
+
+__setup("amd7xx_tco=", amdtco_setup);
+#endif
+
+module_init(amdtco_init);
+module_exit(amdtco_exit);
+
+MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>");
+MODULE_DESCRIPTION("AMD 766/768 TCO Timer Driver");
+MODULE_LICENSE("GPL");
+EXPORT_NO_SYMBOLS;
+
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)