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