Commit e6bb42e3d669afbeb4c971994614bcf241687666

Authored by Renaud CERRATO
Committed by Wim Van Sebroeck
1 parent 7d8b090661

[WATCHDOG] Add AT91SAM9X watchdog

Add a driver for the watchdog timer embedded into AT91SAM9X chips.

Signed-off-by: Renaud Cerrato <r.cerrato@til-technologies.fr>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>

Showing 3 changed files with 336 additions and 0 deletions Side-by-side Diff

drivers/watchdog/Kconfig
... ... @@ -66,6 +66,13 @@
66 66 Watchdog timer embedded into AT91RM9200 chips. This will reboot your
67 67 system when the timeout is reached.
68 68  
  69 +config AT91SAM9X_WATCHDOG
  70 + tristate "AT91SAM9X watchdog"
  71 + depends on WATCHDOG && (ARCH_AT91SAM9260 || ARCH_AT91SAM9261)
  72 + help
  73 + Watchdog timer embedded into AT91SAM9X chips. This will reboot your
  74 + system when the timeout is reached.
  75 +
69 76 config 21285_WATCHDOG
70 77 tristate "DC21285 watchdog"
71 78 depends on FOOTBRIDGE
drivers/watchdog/Makefile
... ... @@ -26,6 +26,7 @@
26 26  
27 27 # ARM Architecture
28 28 obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o
  29 +obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o
29 30 obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o
30 31 obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
31 32 obj-$(CONFIG_977_WATCHDOG) += wdt977.o
drivers/watchdog/at91sam9_wdt.c
  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 <asm/arch/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
  52 +static int heartbeat = WDT_HEARTBEAT;
  53 +module_param(heartbeat, int, 0);
  54 +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
  55 + "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
  56 +
  57 +static int nowayout = WATCHDOG_NOWAYOUT;
  58 +module_param(nowayout, int, 0);
  59 +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  60 + "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  61 +
  62 +static void at91_ping(unsigned long data);
  63 +
  64 +static 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 + */
  77 +static 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 + */
  85 +static 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 + */
  98 +static 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 + */
  112 +static 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 + */
  128 +static 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 +
  156 +static 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 + */
  164 +static 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 + */
  202 +static 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 +
  232 +static 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 +
  241 +static struct miscdevice at91wdt_miscdev = {
  242 + .minor = WATCHDOG_MINOR,
  243 + .name = "watchdog",
  244 + .fops = &at91wdt_fops,
  245 +};
  246 +
  247 +static 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 +
  274 +static 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 +
  287 +static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message)
  288 +{
  289 + return 0;
  290 +}
  291 +
  292 +static 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 +
  302 +static 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 +
  312 +static int __init at91sam_wdt_init(void)
  313 +{
  314 + return platform_driver_probe(&at91wdt_driver, at91wdt_probe);
  315 +}
  316 +
  317 +static void __exit at91sam_wdt_exit(void)
  318 +{
  319 + platform_driver_unregister(&at91wdt_driver);
  320 +}
  321 +
  322 +module_init(at91sam_wdt_init);
  323 +module_exit(at91sam_wdt_exit);
  324 +
  325 +MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>");
  326 +MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors");
  327 +MODULE_LICENSE("GPL");
  328 +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);