linux/drivers/watchdog/at91sam9_wdt.c
<<
>>
Prefs
   1/*
   2 * Watchdog driver for Atmel AT91SAM9x processors.
   3 *
   4 * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr
   5 *
   6 * This program is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU General Public License
   8 * as published by the Free Software Foundation; either version
   9 * 2 of the License, or (at your option) any later version.
  10 */
  11
  12/*
  13 * The Watchdog Timer Mode Register can be only written to once. If the
  14 * timeout need to be set from Linux, be sure that the bootstrap or the
  15 * bootloader doesn't write to this register.
  16 */
  17
  18#include <linux/errno.h>
  19#include <linux/fs.h>
  20#include <linux/init.h>
  21#include <linux/kernel.h>
  22#include <linux/miscdevice.h>
  23#include <linux/module.h>
  24#include <linux/moduleparam.h>
  25#include <linux/platform_device.h>
  26#include <linux/types.h>
  27#include <linux/watchdog.h>
  28#include <linux/jiffies.h>
  29#include <linux/timer.h>
  30#include <linux/bitops.h>
  31#include <linux/uaccess.h>
  32
  33#include <mach/at91_wdt.h>
  34
  35#define DRV_NAME "AT91SAM9 Watchdog"
  36
  37/* AT91SAM9 watchdog runs a 12bit counter @ 256Hz,
  38 * use this to convert a watchdog
  39 * value from/to milliseconds.
  40 */
  41#define ms_to_ticks(t)  (((t << 8) / 1000) - 1)
  42#define ticks_to_ms(t)  (((t + 1) * 1000) >> 8)
  43
  44/* Hardware timeout in seconds */
  45#define WDT_HW_TIMEOUT 2
  46
  47/* Timer heartbeat (500ms) */
  48#define WDT_TIMEOUT     (HZ/2)
  49
  50/* User land timeout */
  51#define WDT_HEARTBEAT 15
  52static int heartbeat = WDT_HEARTBEAT;
  53module_param(heartbeat, int, 0);
  54MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
  55        "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
  56
  57static int nowayout = WATCHDOG_NOWAYOUT;
  58module_param(nowayout, int, 0);
  59MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  60        "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  61
  62static void at91_ping(unsigned long data);
  63
  64static struct {
  65        unsigned long next_heartbeat;   /* the next_heartbeat for the timer */
  66        unsigned long open;
  67        char expect_close;
  68        struct timer_list timer;        /* The timer that pings the watchdog */
  69} at91wdt_private;
  70
  71/* ......................................................................... */
  72
  73
  74/*
  75 * Reload the watchdog timer.  (ie, pat the watchdog)
  76 */
  77static inline void at91_wdt_reset(void)
  78{
  79        at91_sys_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
  80}
  81
  82/*
  83 * Timer tick
  84 */
  85static void at91_ping(unsigned long data)
  86{
  87        if (time_before(jiffies, at91wdt_private.next_heartbeat) ||
  88                        (!nowayout && !at91wdt_private.open)) {
  89                at91_wdt_reset();
  90                mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
  91        } else
  92                printk(KERN_CRIT DRV_NAME": I will reset your machine !\n");
  93}
  94
  95/*
  96 * Watchdog device is opened, and watchdog starts running.
  97 */
  98static int at91_wdt_open(struct inode *inode, struct file *file)
  99{
 100        if (test_and_set_bit(0, &at91wdt_private.open))
 101                return -EBUSY;
 102
 103        at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
 104        mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
 105
 106        return nonseekable_open(inode, file);
 107}
 108
 109/*
 110 * Close the watchdog device.
 111 */
 112static int at91_wdt_close(struct inode *inode, struct file *file)
 113{
 114        clear_bit(0, &at91wdt_private.open);
 115
 116        /* stop internal ping */
 117        if (!at91wdt_private.expect_close)
 118                del_timer(&at91wdt_private.timer);
 119
 120        at91wdt_private.expect_close = 0;
 121        return 0;
 122}
 123
 124/*
 125 * Set the watchdog time interval in 1/256Hz (write-once)
 126 * Counter is 12 bit.
 127 */
 128static int at91_wdt_settimeout(unsigned int timeout)
 129{
 130        unsigned int reg;
 131        unsigned int mr;
 132
 133        /* Check if disabled */
 134        mr = at91_sys_read(AT91_WDT_MR);
 135        if (mr & AT91_WDT_WDDIS) {
 136                printk(KERN_ERR DRV_NAME": sorry, watchdog is disabled\n");
 137                return -EIO;
 138        }
 139
 140        /*
 141         * All counting occurs at SLOW_CLOCK / 128 = 256 Hz
 142         *
 143         * Since WDV is a 12-bit counter, the maximum period is
 144         * 4096 / 256 = 16 seconds.
 145         */
 146        reg = AT91_WDT_WDRSTEN  /* causes watchdog reset */
 147                /* | AT91_WDT_WDRPROC   causes processor reset only */
 148                | AT91_WDT_WDDBGHLT     /* disabled in debug mode */
 149                | AT91_WDT_WDD          /* restart at any time */
 150                | (timeout & AT91_WDT_WDV);  /* timer value */
 151        at91_sys_write(AT91_WDT_MR, reg);
 152
 153        return 0;
 154}
 155
 156static const struct watchdog_info at91_wdt_info = {
 157        .identity       = DRV_NAME,
 158        .options        = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
 159};
 160
 161/*
 162 * Handle commands from user-space.
 163 */
 164static long at91_wdt_ioctl(struct file *file,
 165                unsigned int cmd, unsigned long arg)
 166{
 167        void __user *argp = (void __user *)arg;
 168        int __user *p = argp;
 169        int new_value;
 170
 171        switch (cmd) {
 172        case WDIOC_GETSUPPORT:
 173                return copy_to_user(argp, &at91_wdt_info,
 174                                    sizeof(at91_wdt_info)) ? -EFAULT : 0;
 175
 176        case WDIOC_GETSTATUS:
 177        case WDIOC_GETBOOTSTATUS:
 178                return put_user(0, p);
 179
 180        case WDIOC_KEEPALIVE:
 181                at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
 182                return 0;
 183
 184        case WDIOC_SETTIMEOUT:
 185                if (get_user(new_value, p))
 186                        return -EFAULT;
 187
 188                heartbeat = new_value;
 189                at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
 190
 191                return put_user(new_value, p);  /* return current value */
 192
 193        case WDIOC_GETTIMEOUT:
 194                return put_user(heartbeat, p);
 195        }
 196        return -ENOTTY;
 197}
 198
 199/*
 200 * Pat the watchdog whenever device is written to.
 201 */
 202static ssize_t at91_wdt_write(struct file *file, const char *data, size_t len,
 203      loff_t *ppos)
 204{
 205        if (!len)
 206                return 0;
 207
 208        /* Scan for magic character */
 209        if (!nowayout) {
 210                size_t i;
 211
 212                at91wdt_private.expect_close = 0;
 213
 214                for (i = 0; i < len; i++) {
 215                        char c;
 216                        if (get_user(c, data + i))
 217                                return -EFAULT;
 218                        if (c == 'V') {
 219                                at91wdt_private.expect_close = 42;
 220                                break;
 221                        }
 222                }
 223        }
 224
 225        at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
 226
 227        return len;
 228}
 229
 230/* ......................................................................... */
 231
 232static const struct file_operations at91wdt_fops = {
 233        .owner                  = THIS_MODULE,
 234        .llseek                 = no_llseek,
 235        .unlocked_ioctl = at91_wdt_ioctl,
 236        .open                   = at91_wdt_open,
 237        .release                = at91_wdt_close,
 238        .write                  = at91_wdt_write,
 239};
 240
 241static struct miscdevice at91wdt_miscdev = {
 242        .minor          = WATCHDOG_MINOR,
 243        .name           = "watchdog",
 244        .fops           = &at91wdt_fops,
 245};
 246
 247static int __init at91wdt_probe(struct platform_device *pdev)
 248{
 249        int res;
 250
 251        if (at91wdt_miscdev.parent)
 252                return -EBUSY;
 253        at91wdt_miscdev.parent = &pdev->dev;
 254
 255        /* Set watchdog */
 256        res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000));
 257        if (res)
 258                return res;
 259
 260        res = misc_register(&at91wdt_miscdev);
 261        if (res)
 262                return res;
 263
 264        at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
 265        setup_timer(&at91wdt_private.timer, at91_ping, 0);
 266        mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
 267
 268        printk(KERN_INFO DRV_NAME " enabled (heartbeat=%d sec, nowayout=%d)\n",
 269                heartbeat, nowayout);
 270
 271        return 0;
 272}
 273
 274static int __exit at91wdt_remove(struct platform_device *pdev)
 275{
 276        int res;
 277
 278        res = misc_deregister(&at91wdt_miscdev);
 279        if (!res)
 280                at91wdt_miscdev.parent = NULL;
 281
 282        return res;
 283}
 284
 285#ifdef CONFIG_PM
 286
 287static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message)
 288{
 289        return 0;
 290}
 291
 292static int at91wdt_resume(struct platform_device *pdev)
 293{
 294        return 0;
 295}
 296
 297#else
 298#define at91wdt_suspend NULL
 299#define at91wdt_resume  NULL
 300#endif
 301
 302static struct platform_driver at91wdt_driver = {
 303        .remove         = __exit_p(at91wdt_remove),
 304        .suspend        = at91wdt_suspend,
 305        .resume         = at91wdt_resume,
 306        .driver         = {
 307                .name   = "at91_wdt",
 308                .owner  = THIS_MODULE,
 309        },
 310};
 311
 312static int __init at91sam_wdt_init(void)
 313{
 314        return platform_driver_probe(&at91wdt_driver, at91wdt_probe);
 315}
 316
 317static void __exit at91sam_wdt_exit(void)
 318{
 319        platform_driver_unregister(&at91wdt_driver);
 320}
 321
 322module_init(at91sam_wdt_init);
 323module_exit(at91sam_wdt_exit);
 324
 325MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>");
 326MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors");
 327MODULE_LICENSE("GPL");
 328MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 329