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