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#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  21
  22#include <linux/module.h>               /* For module specific items */
  23#include <linux/moduleparam.h>          /* For new moduleparam's */
  24#include <linux/types.h>                /* For standard types (like size_t) */
  25#include <linux/errno.h>                /* For the -ENODEV/... values */
  26#include <linux/kernel.h>               /* For printk/panic/... */
  27#include <linux/fs.h>                   /* For file operations */
  28#include <linux/miscdevice.h>           /* For MODULE_ALIAS_MISCDEV
  29                                                        (WATCHDOG_MINOR) */
  30#include <linux/watchdog.h>             /* For the watchdog specific items */
  31#include <linux/init.h>                 /* For __init/__exit/... */
  32#include <linux/platform_device.h>      /* For platform_driver framework */
  33#include <linux/spinlock.h>             /* For spin_lock/spin_unlock/... */
  34#include <linux/uaccess.h>              /* For copy_to_user/put_user/... */
  35#include <linux/io.h>                   /* For devm_ioremap_nocache */
  36
  37#include <asm/mach-rc32434/integ.h>     /* For the Watchdog registers */
  38
  39#define VERSION "1.0"
  40
  41static struct {
  42        unsigned long inuse;
  43        spinlock_t io_lock;
  44} rc32434_wdt_device;
  45
  46static struct integ __iomem *wdt_reg;
  47
  48static int expect_close;
  49
  50/* Board internal clock speed in Hz,
  51 * the watchdog timer ticks at. */
  52extern unsigned int idt_cpu_freq;
  53
  54/* translate wtcompare value to seconds and vice versa */
  55#define WTCOMP2SEC(x)   (x / idt_cpu_freq)
  56#define SEC2WTCOMP(x)   (x * idt_cpu_freq)
  57
  58/* Use a default timeout of 20s. This should be
  59 * safe for CPU clock speeds up to 400MHz, as
  60 * ((2 ^ 32) - 1) / (400MHz / 2) = 21s.  */
  61#define WATCHDOG_TIMEOUT 20
  62
  63static int timeout = WATCHDOG_TIMEOUT;
  64module_param(timeout, int, 0);
  65MODULE_PARM_DESC(timeout, "Watchdog timeout value, in seconds (default="
  66                __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
  67
  68static bool nowayout = WATCHDOG_NOWAYOUT;
  69module_param(nowayout, bool, 0);
  70MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  71        __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  72
  73/* apply or and nand masks to data read from addr and write back */
  74#define SET_BITS(addr, or, nand) \
  75        writel((readl(&addr) | or) & ~nand, &addr)
  76
  77static int rc32434_wdt_set(int new_timeout)
  78{
  79        int max_to = WTCOMP2SEC((u32)-1);
  80
  81        if (new_timeout < 0 || new_timeout > max_to) {
  82                pr_err("timeout value must be between 0 and %d\n", max_to);
  83                return -EINVAL;
  84        }
  85        timeout = new_timeout;
  86        spin_lock(&rc32434_wdt_device.io_lock);
  87        writel(SEC2WTCOMP(timeout), &wdt_reg->wtcompare);
  88        spin_unlock(&rc32434_wdt_device.io_lock);
  89
  90        return 0;
  91}
  92
  93static void rc32434_wdt_start(void)
  94{
  95        u32 or, nand;
  96
  97        spin_lock(&rc32434_wdt_device.io_lock);
  98
  99        /* zero the counter before enabling */
 100        writel(0, &wdt_reg->wtcount);
 101
 102        /* don't generate a non-maskable interrupt,
 103         * do a warm reset instead */
 104        nand = 1 << RC32434_ERR_WNE;
 105        or = 1 << RC32434_ERR_WRE;
 106
 107        /* reset the ERRCS timeout bit in case it's set */
 108        nand |= 1 << RC32434_ERR_WTO;
 109
 110        SET_BITS(wdt_reg->errcs, or, nand);
 111
 112        /* set the timeout (either default or based on module param) */
 113        rc32434_wdt_set(timeout);
 114
 115        /* reset WTC timeout bit and enable WDT */
 116        nand = 1 << RC32434_WTC_TO;
 117        or = 1 << RC32434_WTC_EN;
 118
 119        SET_BITS(wdt_reg->wtc, or, nand);
 120
 121        spin_unlock(&rc32434_wdt_device.io_lock);
 122        pr_info("Started watchdog timer\n");
 123}
 124
 125static void rc32434_wdt_stop(void)
 126{
 127        spin_lock(&rc32434_wdt_device.io_lock);
 128
 129        /* Disable WDT */
 130        SET_BITS(wdt_reg->wtc, 0, 1 << RC32434_WTC_EN);
 131
 132        spin_unlock(&rc32434_wdt_device.io_lock);
 133        pr_info("Stopped watchdog timer\n");
 134}
 135
 136static void rc32434_wdt_ping(void)
 137{
 138        spin_lock(&rc32434_wdt_device.io_lock);
 139        writel(0, &wdt_reg->wtcount);
 140        spin_unlock(&rc32434_wdt_device.io_lock);
 141}
 142
 143static int rc32434_wdt_open(struct inode *inode, struct file *file)
 144{
 145        if (test_and_set_bit(0, &rc32434_wdt_device.inuse))
 146                return -EBUSY;
 147
 148        if (nowayout)
 149                __module_get(THIS_MODULE);
 150
 151        rc32434_wdt_start();
 152        rc32434_wdt_ping();
 153
 154        return nonseekable_open(inode, file);
 155}
 156
 157static int rc32434_wdt_release(struct inode *inode, struct file *file)
 158{
 159        if (expect_close == 42) {
 160                rc32434_wdt_stop();
 161                module_put(THIS_MODULE);
 162        } else {
 163                pr_crit("device closed unexpectedly. WDT will not stop!\n");
 164                rc32434_wdt_ping();
 165        }
 166        clear_bit(0, &rc32434_wdt_device.inuse);
 167        return 0;
 168}
 169
 170static ssize_t rc32434_wdt_write(struct file *file, const char *data,
 171                                size_t len, loff_t *ppos)
 172{
 173        if (len) {
 174                if (!nowayout) {
 175                        size_t i;
 176
 177                        /* In case it was set long ago */
 178                        expect_close = 0;
 179
 180                        for (i = 0; i != len; i++) {
 181                                char c;
 182                                if (get_user(c, data + i))
 183                                        return -EFAULT;
 184                                if (c == 'V')
 185                                        expect_close = 42;
 186                        }
 187                }
 188                rc32434_wdt_ping();
 189                return len;
 190        }
 191        return 0;
 192}
 193
 194static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,
 195                                unsigned long arg)
 196{
 197        void __user *argp = (void __user *)arg;
 198        int new_timeout;
 199        unsigned int value;
 200        static const struct watchdog_info ident = {
 201                .options =              WDIOF_SETTIMEOUT |
 202                                        WDIOF_KEEPALIVEPING |
 203                                        WDIOF_MAGICCLOSE,
 204                .identity =             "RC32434_WDT Watchdog",
 205        };
 206        switch (cmd) {
 207        case WDIOC_GETSUPPORT:
 208                if (copy_to_user(argp, &ident, sizeof(ident)))
 209                        return -EFAULT;
 210                break;
 211        case WDIOC_GETSTATUS:
 212        case WDIOC_GETBOOTSTATUS:
 213                value = 0;
 214                if (copy_to_user(argp, &value, sizeof(int)))
 215                        return -EFAULT;
 216                break;
 217        case WDIOC_SETOPTIONS:
 218                if (copy_from_user(&value, argp, sizeof(int)))
 219                        return -EFAULT;
 220                switch (value) {
 221                case WDIOS_ENABLECARD:
 222                        rc32434_wdt_start();
 223                        break;
 224                case WDIOS_DISABLECARD:
 225                        rc32434_wdt_stop();
 226                        break;
 227                default:
 228                        return -EINVAL;
 229                }
 230                break;
 231        case WDIOC_KEEPALIVE:
 232                rc32434_wdt_ping();
 233                break;
 234        case WDIOC_SETTIMEOUT:
 235                if (copy_from_user(&new_timeout, argp, sizeof(int)))
 236                        return -EFAULT;
 237                if (rc32434_wdt_set(new_timeout))
 238                        return -EINVAL;
 239                /* Fall through */
 240        case WDIOC_GETTIMEOUT:
 241                return copy_to_user(argp, &timeout, sizeof(int));
 242        default:
 243                return -ENOTTY;
 244        }
 245
 246        return 0;
 247}
 248
 249static const struct file_operations rc32434_wdt_fops = {
 250        .owner          = THIS_MODULE,
 251        .llseek         = no_llseek,
 252        .write          = rc32434_wdt_write,
 253        .unlocked_ioctl = rc32434_wdt_ioctl,
 254        .open           = rc32434_wdt_open,
 255        .release        = rc32434_wdt_release,
 256};
 257
 258static struct miscdevice rc32434_wdt_miscdev = {
 259        .minor  = WATCHDOG_MINOR,
 260        .name   = "watchdog",
 261        .fops   = &rc32434_wdt_fops,
 262};
 263
 264static int rc32434_wdt_probe(struct platform_device *pdev)
 265{
 266        int ret;
 267        struct resource *r;
 268
 269        r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb532_wdt_res");
 270        if (!r) {
 271                pr_err("failed to retrieve resources\n");
 272                return -ENODEV;
 273        }
 274
 275        wdt_reg = devm_ioremap_nocache(&pdev->dev, r->start, resource_size(r));
 276        if (!wdt_reg) {
 277                pr_err("failed to remap I/O resources\n");
 278                return -ENXIO;
 279        }
 280
 281        spin_lock_init(&rc32434_wdt_device.io_lock);
 282
 283        /* Make sure the watchdog is not running */
 284        rc32434_wdt_stop();
 285
 286        /* Check that the heartbeat value is within it's range;
 287         * if not reset to the default */
 288        if (rc32434_wdt_set(timeout)) {
 289                rc32434_wdt_set(WATCHDOG_TIMEOUT);
 290                pr_info("timeout value must be between 0 and %d\n",
 291                        WTCOMP2SEC((u32)-1));
 292        }
 293
 294        ret = misc_register(&rc32434_wdt_miscdev);
 295        if (ret < 0) {
 296                pr_err("failed to register watchdog device\n");
 297                return ret;
 298        }
 299
 300        pr_info("Watchdog Timer version " VERSION ", timer margin: %d sec\n",
 301                timeout);
 302
 303        return 0;
 304}
 305
 306static int rc32434_wdt_remove(struct platform_device *pdev)
 307{
 308        misc_deregister(&rc32434_wdt_miscdev);
 309        return 0;
 310}
 311
 312static void rc32434_wdt_shutdown(struct platform_device *pdev)
 313{
 314        rc32434_wdt_stop();
 315}
 316
 317static struct platform_driver rc32434_wdt_driver = {
 318        .probe          = rc32434_wdt_probe,
 319        .remove         = rc32434_wdt_remove,
 320        .shutdown       = rc32434_wdt_shutdown,
 321        .driver         = {
 322                        .name = "rc32434_wdt",
 323        }
 324};
 325
 326module_platform_driver(rc32434_wdt_driver);
 327
 328MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>,"
 329                "Florian Fainelli <florian@openwrt.org>");
 330MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog");
 331MODULE_LICENSE("GPL");
 332MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 333