linux/drivers/watchdog/rc32434_wdt.c
<<
>>
Prefs
   1/*
   2 *  IDT Interprise 79RC32434 watchdog driver
   3 *
   4 *  Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org>
   5 *  Copyright (C) 2008, Florian Fainelli <florian@openwrt.org>
   6 *
   7 *  based on
   8 *  SoftDog 0.05:       A Software Watchdog Device
   9 *
  10 *  (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
  11 *                                      All Rights Reserved.
  12 *
  13 *  This program is free software; you can redistribute it and/or
  14 *  modify it under the terms of the GNU General Public License
  15 *  as published by the Free Software Foundation; either version
  16 *  2 of the License, or (at your option) any later version.
  17 *
  18 */
  19
  20#include <linux/module.h>
  21#include <linux/types.h>
  22#include <linux/kernel.h>
  23#include <linux/fs.h>
  24#include <linux/mm.h>
  25#include <linux/miscdevice.h>
  26#include <linux/watchdog.h>
  27#include <linux/reboot.h>
  28#include <linux/smp_lock.h>
  29#include <linux/init.h>
  30#include <linux/platform_device.h>
  31#include <linux/uaccess.h>
  32
  33#include <asm/bootinfo.h>
  34#include <asm/time.h>
  35#include <asm/mach-rc32434/integ.h>
  36
  37#define MAX_TIMEOUT                     20
  38#define RC32434_WDT_INTERVAL            (15 * HZ)
  39
  40#define VERSION "0.2"
  41
  42static struct {
  43        struct completion stop;
  44        int running;
  45        struct timer_list timer;
  46        int queue;
  47        int default_ticks;
  48        unsigned long inuse;
  49} rc32434_wdt_device;
  50
  51static struct integ __iomem *wdt_reg;
  52static int ticks = 100 * HZ;
  53
  54static int expect_close;
  55static int timeout;
  56
  57static int nowayout = WATCHDOG_NOWAYOUT;
  58module_param(nowayout, int, 0);
  59MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  60        __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  61
  62
  63static void rc32434_wdt_start(void)
  64{
  65        u32 val;
  66
  67        if (!rc32434_wdt_device.inuse) {
  68                writel(0, &wdt_reg->wtcount);
  69
  70                val = RC32434_ERR_WRE;
  71                writel(readl(&wdt_reg->errcs) | val, &wdt_reg->errcs);
  72
  73                val = RC32434_WTC_EN;
  74                writel(readl(&wdt_reg->wtc) | val, &wdt_reg->wtc);
  75        }
  76        rc32434_wdt_device.running++;
  77}
  78
  79static void rc32434_wdt_stop(void)
  80{
  81        u32 val;
  82
  83        if (rc32434_wdt_device.running) {
  84
  85                val = ~RC32434_WTC_EN;
  86                writel(readl(&wdt_reg->wtc) & val, &wdt_reg->wtc);
  87
  88                val = ~RC32434_ERR_WRE;
  89                writel(readl(&wdt_reg->errcs) & val, &wdt_reg->errcs);
  90
  91                rc32434_wdt_device.running = 0;
  92        }
  93}
  94
  95static void rc32434_wdt_set(int new_timeout)
  96{
  97        u32 cmp = new_timeout * HZ;
  98        u32 state, val;
  99
 100        timeout = new_timeout;
 101        /*
 102         * store and disable WTC
 103         */
 104        state = (u32)(readl(&wdt_reg->wtc) & RC32434_WTC_EN);
 105        val = ~RC32434_WTC_EN;
 106        writel(readl(&wdt_reg->wtc) & val, &wdt_reg->wtc);
 107
 108        writel(0, &wdt_reg->wtcount);
 109        writel(cmp, &wdt_reg->wtcompare);
 110
 111        /*
 112         * restore WTC
 113         */
 114
 115        writel(readl(&wdt_reg->wtc) | state, &wdt_reg);
 116}
 117
 118static void rc32434_wdt_reset(void)
 119{
 120        ticks = rc32434_wdt_device.default_ticks;
 121}
 122
 123static void rc32434_wdt_update(unsigned long unused)
 124{
 125        if (rc32434_wdt_device.running)
 126                ticks--;
 127
 128        writel(0, &wdt_reg->wtcount);
 129
 130        if (rc32434_wdt_device.queue && ticks)
 131                mod_timer(&rc32434_wdt_device.timer,
 132                        jiffies + RC32434_WDT_INTERVAL);
 133        else
 134                complete(&rc32434_wdt_device.stop);
 135}
 136
 137static int rc32434_wdt_open(struct inode *inode, struct file *file)
 138{
 139        if (test_and_set_bit(0, &rc32434_wdt_device.inuse))
 140                return -EBUSY;
 141
 142        if (nowayout)
 143                __module_get(THIS_MODULE);
 144
 145        return nonseekable_open(inode, file);
 146}
 147
 148static int rc32434_wdt_release(struct inode *inode, struct file *file)
 149{
 150        if (expect_close && nowayout == 0) {
 151                rc32434_wdt_stop();
 152                printk(KERN_INFO KBUILD_MODNAME ": disabling watchdog timer\n");
 153                module_put(THIS_MODULE);
 154        } else
 155                printk(KERN_CRIT KBUILD_MODNAME
 156                        ": device closed unexpectedly. WDT will not stop !\n");
 157
 158        clear_bit(0, &rc32434_wdt_device.inuse);
 159        return 0;
 160}
 161
 162static ssize_t rc32434_wdt_write(struct file *file, const char *data,
 163                                size_t len, loff_t *ppos)
 164{
 165        if (len) {
 166                if (!nowayout) {
 167                        size_t i;
 168
 169                        /* In case it was set long ago */
 170                        expect_close = 0;
 171
 172                        for (i = 0; i != len; i++) {
 173                                char c;
 174                                if (get_user(c, data + i))
 175                                        return -EFAULT;
 176                                if (c == 'V')
 177                                        expect_close = 1;
 178                        }
 179                }
 180                rc32434_wdt_update(0);
 181                return len;
 182        }
 183        return 0;
 184}
 185
 186static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,
 187                                unsigned long arg)
 188{
 189        void __user *argp = (void __user *)arg;
 190        int new_timeout;
 191        unsigned int value;
 192        static struct watchdog_info ident = {
 193                .options =              WDIOF_SETTIMEOUT |
 194                                        WDIOF_KEEPALIVEPING |
 195                                        WDIOF_MAGICCLOSE,
 196                .identity =             "RC32434_WDT Watchdog",
 197        };
 198        switch (cmd) {
 199        case WDIOC_KEEPALIVE:
 200                rc32434_wdt_reset();
 201                break;
 202        case WDIOC_GETSTATUS:
 203        case WDIOC_GETBOOTSTATUS:
 204                value = readl(&wdt_reg->wtcount);
 205                if (copy_to_user(argp, &value, sizeof(int)))
 206                        return -EFAULT;
 207                break;
 208        case WDIOC_GETSUPPORT:
 209                if (copy_to_user(argp, &ident, sizeof(ident)))
 210                        return -EFAULT;
 211                break;
 212        case WDIOC_SETOPTIONS:
 213                if (copy_from_user(&value, argp, sizeof(int)))
 214                        return -EFAULT;
 215                switch (value) {
 216                case WDIOS_ENABLECARD:
 217                        rc32434_wdt_start();
 218                        break;
 219                case WDIOS_DISABLECARD:
 220                        rc32434_wdt_stop();
 221                default:
 222                        return -EINVAL;
 223                }
 224                break;
 225        case WDIOC_SETTIMEOUT:
 226                if (copy_from_user(&new_timeout, argp, sizeof(int)))
 227                        return -EFAULT;
 228                if (new_timeout < 1)
 229                        return -EINVAL;
 230                if (new_timeout > MAX_TIMEOUT)
 231                        return -EINVAL;
 232                rc32434_wdt_set(new_timeout);
 233        case WDIOC_GETTIMEOUT:
 234                return copy_to_user(argp, &timeout, sizeof(int));
 235        default:
 236                return -ENOTTY;
 237        }
 238
 239        return 0;
 240}
 241
 242static struct file_operations rc32434_wdt_fops = {
 243        .owner          = THIS_MODULE,
 244        .llseek         = no_llseek,
 245        .write          = rc32434_wdt_write,
 246        .unlocked_ioctl = rc32434_wdt_ioctl,
 247        .open           = rc32434_wdt_open,
 248        .release        = rc32434_wdt_release,
 249};
 250
 251static struct miscdevice rc32434_wdt_miscdev = {
 252        .minor  = WATCHDOG_MINOR,
 253        .name   = "watchdog",
 254        .fops   = &rc32434_wdt_fops,
 255};
 256
 257static char banner[] = KERN_INFO KBUILD_MODNAME
 258                ": Watchdog Timer version " VERSION ", timer margin: %d sec\n";
 259
 260static int rc32434_wdt_probe(struct platform_device *pdev)
 261{
 262        int ret;
 263        struct resource *r;
 264
 265        r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb500_wdt_res");
 266        if (!r) {
 267                printk(KERN_ERR KBUILD_MODNAME
 268                        "failed to retrieve resources\n");
 269                return -ENODEV;
 270        }
 271
 272        wdt_reg = ioremap_nocache(r->start, r->end - r->start);
 273        if (!wdt_reg) {
 274                printk(KERN_ERR KBUILD_MODNAME
 275                        "failed to remap I/O resources\n");
 276                return -ENXIO;
 277        }
 278
 279        ret = misc_register(&rc32434_wdt_miscdev);
 280
 281        if (ret < 0) {
 282                printk(KERN_ERR KBUILD_MODNAME
 283                        "failed to register watchdog device\n");
 284                goto unmap;
 285        }
 286
 287        init_completion(&rc32434_wdt_device.stop);
 288        rc32434_wdt_device.queue = 0;
 289
 290        clear_bit(0, &rc32434_wdt_device.inuse);
 291
 292        setup_timer(&rc32434_wdt_device.timer, rc32434_wdt_update, 0L);
 293
 294        rc32434_wdt_device.default_ticks = ticks;
 295
 296        rc32434_wdt_start();
 297
 298        printk(banner, timeout);
 299
 300        return 0;
 301
 302unmap:
 303        iounmap(wdt_reg);
 304        return ret;
 305}
 306
 307static int rc32434_wdt_remove(struct platform_device *pdev)
 308{
 309        if (rc32434_wdt_device.queue) {
 310                rc32434_wdt_device.queue = 0;
 311                wait_for_completion(&rc32434_wdt_device.stop);
 312        }
 313        misc_deregister(&rc32434_wdt_miscdev);
 314
 315        iounmap(wdt_reg);
 316
 317        return 0;
 318}
 319
 320static struct platform_driver rc32434_wdt = {
 321        .probe  = rc32434_wdt_probe,
 322        .remove = rc32434_wdt_remove,
 323        .driver = {
 324                .name = "rc32434_wdt",
 325        }
 326};
 327
 328static int __init rc32434_wdt_init(void)
 329{
 330        return platform_driver_register(&rc32434_wdt);
 331}
 332
 333static void __exit rc32434_wdt_exit(void)
 334{
 335        platform_driver_unregister(&rc32434_wdt);
 336}
 337
 338module_init(rc32434_wdt_init);
 339module_exit(rc32434_wdt_exit);
 340
 341MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>,"
 342                "Florian Fainelli <florian@openwrt.org>");
 343MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog");
 344MODULE_LICENSE("GPL");
 345MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 346