linux/drivers/watchdog/rdc321x_wdt.c
<<
>>
Prefs
   1/*
   2 * RDC321x watchdog driver
   3 *
   4 * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
   5 *
   6 * This driver is highly inspired from the cpu5_wdt driver
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License as published by
  10 * the Free Software Foundation; either version 2 of the License, or
  11 * (at your option) any later version.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21 *
  22 */
  23
  24#include <linux/module.h>
  25#include <linux/moduleparam.h>
  26#include <linux/types.h>
  27#include <linux/errno.h>
  28#include <linux/miscdevice.h>
  29#include <linux/fs.h>
  30#include <linux/init.h>
  31#include <linux/ioport.h>
  32#include <linux/timer.h>
  33#include <linux/completion.h>
  34#include <linux/jiffies.h>
  35#include <linux/platform_device.h>
  36#include <linux/watchdog.h>
  37#include <linux/io.h>
  38#include <linux/uaccess.h>
  39#include <linux/mfd/rdc321x.h>
  40
  41#define RDC_WDT_MASK    0x80000000 /* Mask */
  42#define RDC_WDT_EN      0x00800000 /* Enable bit */
  43#define RDC_WDT_WTI     0x00200000 /* Generate CPU reset/NMI/WDT on timeout */
  44#define RDC_WDT_RST     0x00100000 /* Reset bit */
  45#define RDC_WDT_WIF     0x00040000 /* WDT IRQ Flag */
  46#define RDC_WDT_IRT     0x00000100 /* IRQ Routing table */
  47#define RDC_WDT_CNT     0x00000001 /* WDT count */
  48
  49#define RDC_CLS_TMR     0x80003844 /* Clear timer */
  50
  51#define RDC_WDT_INTERVAL        (HZ/10+1)
  52
  53static int ticks = 1000;
  54
  55/* some device data */
  56
  57static struct {
  58        struct completion stop;
  59        int running;
  60        struct timer_list timer;
  61        int queue;
  62        int default_ticks;
  63        unsigned long inuse;
  64        spinlock_t lock;
  65        struct pci_dev *sb_pdev;
  66        int base_reg;
  67} rdc321x_wdt_device;
  68
  69/* generic helper functions */
  70
  71static void rdc321x_wdt_trigger(unsigned long unused)
  72{
  73        unsigned long flags;
  74        u32 val;
  75
  76        if (rdc321x_wdt_device.running)
  77                ticks--;
  78
  79        /* keep watchdog alive */
  80        spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
  81        pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
  82                                        rdc321x_wdt_device.base_reg, &val);
  83        val |= RDC_WDT_EN;
  84        pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
  85                                        rdc321x_wdt_device.base_reg, val);
  86        spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
  87
  88        /* requeue?? */
  89        if (rdc321x_wdt_device.queue && ticks)
  90                mod_timer(&rdc321x_wdt_device.timer,
  91                                jiffies + RDC_WDT_INTERVAL);
  92        else {
  93                /* ticks doesn't matter anyway */
  94                complete(&rdc321x_wdt_device.stop);
  95        }
  96
  97}
  98
  99static void rdc321x_wdt_reset(void)
 100{
 101        ticks = rdc321x_wdt_device.default_ticks;
 102}
 103
 104static void rdc321x_wdt_start(void)
 105{
 106        unsigned long flags;
 107
 108        if (!rdc321x_wdt_device.queue) {
 109                rdc321x_wdt_device.queue = 1;
 110
 111                /* Clear the timer */
 112                spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
 113                pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
 114                                rdc321x_wdt_device.base_reg, RDC_CLS_TMR);
 115
 116                /* Enable watchdog and set the timeout to 81.92 us */
 117                pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
 118                                        rdc321x_wdt_device.base_reg,
 119                                        RDC_WDT_EN | RDC_WDT_CNT);
 120                spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
 121
 122                mod_timer(&rdc321x_wdt_device.timer,
 123                                jiffies + RDC_WDT_INTERVAL);
 124        }
 125
 126        /* if process dies, counter is not decremented */
 127        rdc321x_wdt_device.running++;
 128}
 129
 130static int rdc321x_wdt_stop(void)
 131{
 132        if (rdc321x_wdt_device.running)
 133                rdc321x_wdt_device.running = 0;
 134
 135        ticks = rdc321x_wdt_device.default_ticks;
 136
 137        return -EIO;
 138}
 139
 140/* filesystem operations */
 141static int rdc321x_wdt_open(struct inode *inode, struct file *file)
 142{
 143        if (test_and_set_bit(0, &rdc321x_wdt_device.inuse))
 144                return -EBUSY;
 145
 146        return nonseekable_open(inode, file);
 147}
 148
 149static int rdc321x_wdt_release(struct inode *inode, struct file *file)
 150{
 151        clear_bit(0, &rdc321x_wdt_device.inuse);
 152        return 0;
 153}
 154
 155static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd,
 156                                unsigned long arg)
 157{
 158        void __user *argp = (void __user *)arg;
 159        u32 value;
 160        static const struct watchdog_info ident = {
 161                .options = WDIOF_CARDRESET,
 162                .identity = "RDC321x WDT",
 163        };
 164        unsigned long flags;
 165
 166        switch (cmd) {
 167        case WDIOC_KEEPALIVE:
 168                rdc321x_wdt_reset();
 169                break;
 170        case WDIOC_GETSTATUS:
 171                /* Read the value from the DATA register */
 172                spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
 173                pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
 174                                        rdc321x_wdt_device.base_reg, &value);
 175                spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
 176                if (copy_to_user(argp, &value, sizeof(u32)))
 177                        return -EFAULT;
 178                break;
 179        case WDIOC_GETSUPPORT:
 180                if (copy_to_user(argp, &ident, sizeof(ident)))
 181                        return -EFAULT;
 182                break;
 183        case WDIOC_SETOPTIONS:
 184                if (copy_from_user(&value, argp, sizeof(int)))
 185                        return -EFAULT;
 186                switch (value) {
 187                case WDIOS_ENABLECARD:
 188                        rdc321x_wdt_start();
 189                        break;
 190                case WDIOS_DISABLECARD:
 191                        return rdc321x_wdt_stop();
 192                default:
 193                        return -EINVAL;
 194                }
 195                break;
 196        default:
 197                return -ENOTTY;
 198        }
 199        return 0;
 200}
 201
 202static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,
 203                                size_t count, loff_t *ppos)
 204{
 205        if (!count)
 206                return -EIO;
 207
 208        rdc321x_wdt_reset();
 209
 210        return count;
 211}
 212
 213static const struct file_operations rdc321x_wdt_fops = {
 214        .owner          = THIS_MODULE,
 215        .llseek         = no_llseek,
 216        .unlocked_ioctl = rdc321x_wdt_ioctl,
 217        .open           = rdc321x_wdt_open,
 218        .write          = rdc321x_wdt_write,
 219        .release        = rdc321x_wdt_release,
 220};
 221
 222static struct miscdevice rdc321x_wdt_misc = {
 223        .minor  = WATCHDOG_MINOR,
 224        .name   = "watchdog",
 225        .fops   = &rdc321x_wdt_fops,
 226};
 227
 228static int __devinit rdc321x_wdt_probe(struct platform_device *pdev)
 229{
 230        int err;
 231        struct resource *r;
 232        struct rdc321x_wdt_pdata *pdata;
 233
 234        pdata = platform_get_drvdata(pdev);
 235        if (!pdata) {
 236                dev_err(&pdev->dev, "no platform data supplied\n");
 237                return -ENODEV;
 238        }
 239
 240        r = platform_get_resource_byname(pdev, IORESOURCE_IO, "wdt-reg");
 241        if (!r) {
 242                dev_err(&pdev->dev, "failed to get wdt-reg resource\n");
 243                return -ENODEV;
 244        }
 245
 246        rdc321x_wdt_device.sb_pdev = pdata->sb_pdev;
 247        rdc321x_wdt_device.base_reg = r->start;
 248
 249        err = misc_register(&rdc321x_wdt_misc);
 250        if (err < 0) {
 251                dev_err(&pdev->dev, "misc_register failed\n");
 252                return err;
 253        }
 254
 255        spin_lock_init(&rdc321x_wdt_device.lock);
 256
 257        /* Reset the watchdog */
 258        pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
 259                                rdc321x_wdt_device.base_reg, RDC_WDT_RST);
 260
 261        init_completion(&rdc321x_wdt_device.stop);
 262        rdc321x_wdt_device.queue = 0;
 263
 264        clear_bit(0, &rdc321x_wdt_device.inuse);
 265
 266        setup_timer(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0);
 267
 268        rdc321x_wdt_device.default_ticks = ticks;
 269
 270        dev_info(&pdev->dev, "watchdog init success\n");
 271
 272        return 0;
 273}
 274
 275static int __devexit rdc321x_wdt_remove(struct platform_device *pdev)
 276{
 277        if (rdc321x_wdt_device.queue) {
 278                rdc321x_wdt_device.queue = 0;
 279                wait_for_completion(&rdc321x_wdt_device.stop);
 280        }
 281
 282        misc_deregister(&rdc321x_wdt_misc);
 283
 284        return 0;
 285}
 286
 287static struct platform_driver rdc321x_wdt_driver = {
 288        .probe = rdc321x_wdt_probe,
 289        .remove = __devexit_p(rdc321x_wdt_remove),
 290        .driver = {
 291                .owner = THIS_MODULE,
 292                .name = "rdc321x-wdt",
 293        },
 294};
 295
 296static int __init rdc321x_wdt_init(void)
 297{
 298        return platform_driver_register(&rdc321x_wdt_driver);
 299}
 300
 301static void __exit rdc321x_wdt_exit(void)
 302{
 303        platform_driver_unregister(&rdc321x_wdt_driver);
 304}
 305
 306module_init(rdc321x_wdt_init);
 307module_exit(rdc321x_wdt_exit);
 308
 309MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
 310MODULE_DESCRIPTION("RDC321x watchdog driver");
 311MODULE_LICENSE("GPL");
 312MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 313