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/init.h>
  22#include <linux/io.h>
  23#include <linux/kernel.h>
  24#include <linux/module.h>
  25#include <linux/moduleparam.h>
  26#include <linux/platform_device.h>
  27#include <linux/types.h>
  28#include <linux/watchdog.h>
  29#include <linux/jiffies.h>
  30#include <linux/timer.h>
  31#include <linux/bitops.h>
  32#include <linux/uaccess.h>
  33#include <linux/of.h>
  34
  35#include "at91sam9_wdt.h"
  36
  37#define DRV_NAME "AT91SAM9 Watchdog"
  38
  39#define wdt_read(field) \
  40        __raw_readl(at91wdt_private.base + field)
  41#define wdt_write(field, val) \
  42        __raw_writel((val), at91wdt_private.base + field)
  43
  44/* AT91SAM9 watchdog runs a 12bit counter @ 256Hz,
  45 * use this to convert a watchdog
  46 * value from/to milliseconds.
  47 */
  48#define ms_to_ticks(t)  (((t << 8) / 1000) - 1)
  49#define ticks_to_ms(t)  (((t + 1) * 1000) >> 8)
  50
  51/* Hardware timeout in seconds */
  52#define WDT_HW_TIMEOUT 2
  53
  54/* Timer heartbeat (500ms) */
  55#define WDT_TIMEOUT     (HZ/2)
  56
  57/* User land timeout */
  58#define WDT_HEARTBEAT 15
  59static int heartbeat;
  60module_param(heartbeat, int, 0);
  61MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
  62        "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
  63
  64static bool nowayout = WATCHDOG_NOWAYOUT;
  65module_param(nowayout, bool, 0);
  66MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  67        "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  68
  69static struct watchdog_device at91_wdt_dev;
  70static void at91_ping(unsigned long data);
  71
  72static struct {
  73        void __iomem *base;
  74        unsigned long next_heartbeat;   /* the next_heartbeat for the timer */
  75        struct timer_list timer;        /* The timer that pings the watchdog */
  76} at91wdt_private;
  77
  78/* ......................................................................... */
  79
  80/*
  81 * Reload the watchdog timer.  (ie, pat the watchdog)
  82 */
  83static inline void at91_wdt_reset(void)
  84{
  85        wdt_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
  86}
  87
  88/*
  89 * Timer tick
  90 */
  91static void at91_ping(unsigned long data)
  92{
  93        if (time_before(jiffies, at91wdt_private.next_heartbeat) ||
  94            (!watchdog_active(&at91_wdt_dev))) {
  95                at91_wdt_reset();
  96                mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
  97        } else
  98                pr_crit("I will reset your machine !\n");
  99}
 100
 101static int at91_wdt_ping(struct watchdog_device *wdd)
 102{
 103        /* calculate when the next userspace timeout will be */
 104        at91wdt_private.next_heartbeat = jiffies + wdd->timeout * HZ;
 105        return 0;
 106}
 107
 108static int at91_wdt_start(struct watchdog_device *wdd)
 109{
 110        /* calculate the next userspace timeout and modify the timer */
 111        at91_wdt_ping(wdd);
 112        mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
 113        return 0;
 114}
 115
 116static int at91_wdt_stop(struct watchdog_device *wdd)
 117{
 118        /* The watchdog timer hardware can not be stopped... */
 119        return 0;
 120}
 121
 122static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout)
 123{
 124        wdd->timeout = new_timeout;
 125        return 0;
 126}
 127
 128/*
 129 * Set the watchdog time interval in 1/256Hz (write-once)
 130 * Counter is 12 bit.
 131 */
 132static int at91_wdt_settimeout(unsigned int timeout)
 133{
 134        unsigned int reg;
 135        unsigned int mr;
 136
 137        /* Check if disabled */
 138        mr = wdt_read(AT91_WDT_MR);
 139        if (mr & AT91_WDT_WDDIS) {
 140                pr_err("sorry, watchdog is disabled\n");
 141                return -EIO;
 142        }
 143
 144        /*
 145         * All counting occurs at SLOW_CLOCK / 128 = 256 Hz
 146         *
 147         * Since WDV is a 12-bit counter, the maximum period is
 148         * 4096 / 256 = 16 seconds.
 149         */
 150        reg = AT91_WDT_WDRSTEN  /* causes watchdog reset */
 151                /* | AT91_WDT_WDRPROC   causes processor reset only */
 152                | AT91_WDT_WDDBGHLT     /* disabled in debug mode */
 153                | AT91_WDT_WDD          /* restart at any time */
 154                | (timeout & AT91_WDT_WDV);  /* timer value */
 155        wdt_write(AT91_WDT_MR, reg);
 156
 157        return 0;
 158}
 159
 160/* ......................................................................... */
 161
 162static const struct watchdog_info at91_wdt_info = {
 163        .identity       = DRV_NAME,
 164        .options        = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
 165                                                WDIOF_MAGICCLOSE,
 166};
 167
 168static const struct watchdog_ops at91_wdt_ops = {
 169        .owner =        THIS_MODULE,
 170        .start =        at91_wdt_start,
 171        .stop =         at91_wdt_stop,
 172        .ping =         at91_wdt_ping,
 173        .set_timeout =  at91_wdt_set_timeout,
 174};
 175
 176static struct watchdog_device at91_wdt_dev = {
 177        .info =         &at91_wdt_info,
 178        .ops =          &at91_wdt_ops,
 179        .timeout =      WDT_HEARTBEAT,
 180        .min_timeout =  1,
 181        .max_timeout =  0xFFFF,
 182};
 183
 184static int __init at91wdt_probe(struct platform_device *pdev)
 185{
 186        struct resource *r;
 187        int res;
 188
 189        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 190        if (!r)
 191                return -ENODEV;
 192        at91wdt_private.base = ioremap(r->start, resource_size(r));
 193        if (!at91wdt_private.base) {
 194                dev_err(&pdev->dev, "failed to map registers, aborting.\n");
 195                return -ENOMEM;
 196        }
 197
 198        at91_wdt_dev.parent = &pdev->dev;
 199        watchdog_init_timeout(&at91_wdt_dev, heartbeat, &pdev->dev);
 200        watchdog_set_nowayout(&at91_wdt_dev, nowayout);
 201
 202        /* Set watchdog */
 203        res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000));
 204        if (res)
 205                return res;
 206
 207        res = watchdog_register_device(&at91_wdt_dev);
 208        if (res)
 209                return res;
 210
 211        at91wdt_private.next_heartbeat = jiffies + at91_wdt_dev.timeout * HZ;
 212        setup_timer(&at91wdt_private.timer, at91_ping, 0);
 213        mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
 214
 215        pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n",
 216                at91_wdt_dev.timeout, nowayout);
 217
 218        return 0;
 219}
 220
 221static int __exit at91wdt_remove(struct platform_device *pdev)
 222{
 223        watchdog_unregister_device(&at91_wdt_dev);
 224
 225        pr_warn("I quit now, hardware will probably reboot!\n");
 226        del_timer(&at91wdt_private.timer);
 227
 228        return 0;
 229}
 230
 231#if defined(CONFIG_OF)
 232static const struct of_device_id at91_wdt_dt_ids[] = {
 233        { .compatible = "atmel,at91sam9260-wdt" },
 234        { /* sentinel */ }
 235};
 236
 237MODULE_DEVICE_TABLE(of, at91_wdt_dt_ids);
 238#endif
 239
 240static struct platform_driver at91wdt_driver = {
 241        .remove         = __exit_p(at91wdt_remove),
 242        .driver         = {
 243                .name   = "at91_wdt",
 244                .owner  = THIS_MODULE,
 245                .of_match_table = of_match_ptr(at91_wdt_dt_ids),
 246        },
 247};
 248
 249module_platform_driver_probe(at91wdt_driver, at91wdt_probe);
 250
 251MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>");
 252MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors");
 253MODULE_LICENSE("GPL");
 254