linux-old/drivers/char/scx200_wdt.c
<<
>>
Prefs
   1/* linux/drivers/char/scx200_wdt.c 
   2
   3   National Semiconductor SCx200 Watchdog support
   4
   5   Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
   6
   7   Som code taken from:
   8   National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
   9   (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
  10
  11   This program is free software; you can redistribute it and/or
  12   modify it under the terms of the GNU General Public License as
  13   published by the Free Software Foundation; either version 2 of the
  14   License, or (at your option) any later version.
  15
  16   The author(s) of this software shall not be held liable for damages
  17   of any nature resulting due to the use of this software. This
  18   software is provided AS-IS with no warranties. */
  19
  20#include <linux/config.h>
  21#include <linux/module.h>
  22#include <linux/errno.h>
  23#include <linux/kernel.h>
  24#include <linux/init.h>
  25#include <linux/miscdevice.h>
  26#include <linux/watchdog.h>
  27#include <linux/notifier.h>
  28#include <linux/reboot.h>
  29#include <linux/pci.h>
  30#include <asm/uaccess.h>
  31#include <asm/io.h>
  32
  33#include <linux/scx200.h>
  34
  35#define NAME "scx200_wdt"
  36
  37MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
  38MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
  39MODULE_LICENSE("GPL");
  40
  41#ifndef CONFIG_WATCHDOG_NOWAYOUT
  42#define CONFIG_WATCHDOG_NOWAYOUT 0
  43#endif
  44
  45static int margin = 60;         /* in seconds */
  46MODULE_PARM(margin, "i");
  47MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
  48
  49static int nowayout = CONFIG_WATCHDOG_NOWAYOUT;
  50MODULE_PARM(nowayout, "i");
  51MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
  52
  53static u16 wdto_restart;
  54static struct semaphore open_semaphore;
  55static unsigned expect_close;
  56
  57/* Bits of the WDCNFG register */
  58#define W_ENABLE 0x00fa         /* Enable watchdog */
  59#define W_DISABLE 0x0000        /* Disable watchdog */
  60
  61/* The scaling factor for the timer, this depends on the value of W_ENABLE */
  62#define W_SCALE (32768/1024)
  63
  64static void scx200_wdt_ping(void)
  65{
  66        outw(wdto_restart, SCx200_CB_BASE + SCx200_WDT_WDTO);
  67}
  68
  69static void scx200_wdt_update_margin(void)
  70{
  71        printk(KERN_INFO NAME ": timer margin %d seconds\n", margin);
  72        wdto_restart = margin * W_SCALE;
  73}
  74
  75static void scx200_wdt_enable(void)
  76{
  77        printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n", 
  78               wdto_restart);
  79
  80        outw(0, SCx200_CB_BASE + SCx200_WDT_WDTO);
  81        outb(SCx200_WDT_WDSTS_WDOVF, SCx200_CB_BASE + SCx200_WDT_WDSTS);
  82        outw(W_ENABLE, SCx200_CB_BASE + SCx200_WDT_WDCNFG);
  83
  84        scx200_wdt_ping();
  85}
  86
  87static void scx200_wdt_disable(void)
  88{
  89        printk(KERN_DEBUG NAME ": disabling watchdog timer\n");
  90                
  91        outw(0, SCx200_CB_BASE + SCx200_WDT_WDTO);
  92        outb(SCx200_WDT_WDSTS_WDOVF, SCx200_CB_BASE + SCx200_WDT_WDSTS);
  93        outw(W_DISABLE, SCx200_CB_BASE + SCx200_WDT_WDCNFG);
  94}
  95
  96static int scx200_wdt_open(struct inode *inode, struct file *file)
  97{
  98        /* only allow one at a time */
  99        if (down_trylock(&open_semaphore))
 100                return -EBUSY;
 101        scx200_wdt_enable();
 102        expect_close = 0;
 103
 104        return 0;
 105}
 106
 107static int scx200_wdt_release(struct inode *inode, struct file *file)
 108{
 109        if (!expect_close) {
 110                printk(KERN_WARNING NAME ": watchdog device closed unexpectedly, will not disable the watchdog timer\n");
 111        } else if (!nowayout) {
 112                scx200_wdt_disable();
 113        }
 114        up(&open_semaphore);
 115
 116        return 0;
 117}
 118
 119static int scx200_wdt_notify_sys(struct notifier_block *this, 
 120                                      unsigned long code, void *unused)
 121{
 122        if (code == SYS_HALT || code == SYS_POWER_OFF)
 123                if (!nowayout)
 124                        scx200_wdt_disable();
 125
 126        return NOTIFY_DONE;
 127}
 128
 129static struct notifier_block scx200_wdt_notifier =
 130{
 131        notifier_call: scx200_wdt_notify_sys
 132};
 133
 134static ssize_t scx200_wdt_write(struct file *file, const char *data, 
 135                                     size_t len, loff_t *ppos)
 136{
 137        if (ppos != &file->f_pos)
 138                return -ESPIPE;
 139
 140        /* check for a magic close character */
 141        if (len) 
 142        {
 143                size_t i;
 144
 145                scx200_wdt_ping();
 146
 147                expect_close = 0;
 148                for (i = 0; i < len; ++i) {
 149                        char c;
 150                        if (get_user(c, data+i))
 151                                return -EFAULT;
 152                        if (c == 'V')
 153                                expect_close = 1;
 154                }
 155
 156                return len;
 157        }
 158
 159        return 0;
 160}
 161
 162static int scx200_wdt_ioctl(struct inode *inode, struct file *file,
 163        unsigned int cmd, unsigned long arg)
 164{
 165        static struct watchdog_info ident = {
 166                .identity = "NatSemi SCx200 Watchdog",
 167                .firmware_version = 1, 
 168                .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING),
 169        };
 170        int new_margin;
 171        
 172        switch (cmd) {
 173        default:
 174                return -ENOTTY;
 175        case WDIOC_GETSUPPORT:
 176                if(copy_to_user((struct watchdog_info *)arg, &ident, 
 177                                sizeof(ident)))
 178                        return -EFAULT;
 179                return 0;
 180        case WDIOC_GETSTATUS:
 181        case WDIOC_GETBOOTSTATUS:
 182                if (put_user(0, (int *)arg))
 183                        return -EFAULT;
 184                return 0;
 185        case WDIOC_KEEPALIVE:
 186                scx200_wdt_ping();
 187                return 0;
 188        case WDIOC_SETTIMEOUT:
 189                if (get_user(new_margin, (int *)arg))
 190                        return -EFAULT;
 191                if (new_margin < 1)
 192                        return -EINVAL;
 193                margin = new_margin;
 194                scx200_wdt_update_margin();
 195                scx200_wdt_ping();
 196        case WDIOC_GETTIMEOUT:
 197                if (put_user(margin, (int *)arg))
 198                        return -EFAULT;
 199                return 0;
 200        }
 201}
 202
 203static struct file_operations scx200_wdt_fops = {
 204        .owner   = THIS_MODULE,
 205        .write   = scx200_wdt_write,
 206        .ioctl   = scx200_wdt_ioctl,
 207        .open    = scx200_wdt_open,
 208        .release = scx200_wdt_release,
 209};
 210
 211static struct miscdevice scx200_wdt_miscdev = {
 212        .minor = WATCHDOG_MINOR,
 213        .name  = NAME,
 214        .fops  = &scx200_wdt_fops,
 215};
 216
 217static int __init scx200_wdt_init(void)
 218{
 219        int r;
 220
 221        printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n");
 222
 223        /* First check that this really is a NatSemi SCx200 CPU */
 224        if ((pci_find_device(PCI_VENDOR_ID_NS, 
 225                             PCI_DEVICE_ID_NS_SCx200_BRIDGE,
 226                             NULL)) == NULL)
 227                return -ENODEV;
 228
 229        /* More sanity checks, verify that the configuration block is there */
 230        if (!scx200_cb_probe(SCx200_CB_BASE)) {
 231                printk(KERN_WARNING NAME ": no configuration block found\n");
 232                return -ENODEV;
 233        }
 234
 235        if (!request_region(SCx200_CB_BASE + SCx200_WDT_OFFSET, 
 236                            SCx200_WDT_SIZE, 
 237                            "NatSemi SCx200 Watchdog")) {
 238                printk(KERN_WARNING NAME ": watchdog I/O region busy\n");
 239                return -EBUSY;
 240        }
 241
 242        scx200_wdt_update_margin();
 243        scx200_wdt_disable();
 244
 245        sema_init(&open_semaphore, 1);
 246
 247        r = misc_register(&scx200_wdt_miscdev);
 248        if (r)
 249                return r;
 250
 251        r = register_reboot_notifier(&scx200_wdt_notifier);
 252        if (r) {
 253                printk(KERN_ERR NAME ": unable to register reboot notifier");
 254                misc_deregister(&scx200_wdt_miscdev);
 255                return r;
 256        }
 257
 258        return 0;
 259}
 260
 261static void __exit scx200_wdt_cleanup(void)
 262{
 263        unregister_reboot_notifier(&scx200_wdt_notifier);
 264        misc_deregister(&scx200_wdt_miscdev);
 265        release_region(SCx200_CB_BASE + SCx200_WDT_OFFSET,
 266                       SCx200_WDT_SIZE);
 267}
 268
 269module_init(scx200_wdt_init);
 270module_exit(scx200_wdt_cleanup);
 271
 272/*
 273    Local variables:
 274        compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules"
 275        c-basic-offset: 8
 276    End:
 277*/
 278
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.