/* * Watchdog driver for Atmel AT91SAM9x processors. * * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ /* * The Watchdog Timer Mode Register can be only written to once. If the * timeout need to be set from Linux, be sure that the bootstrap or the * bootloader doesn't write to this register. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "at91sam9_wdt.h" #define DRV_NAME "AT91SAM9 Watchdog" /* AT91SAM9 watchdog runs a 12bit counter @ 256Hz, * use this to convert a watchdog * value from/to milliseconds. */ #define ms_to_ticks(t) (((t << 8) / 1000) - 1) #define ticks_to_ms(t) (((t + 1) * 1000) >> 8) /* Hardware timeout in seconds */ #define WDT_HW_TIMEOUT 2 /* Timer heartbeat (500ms) */ #define WDT_TIMEOUT (HZ/2) /* User land timeout */ #define WDT_HEARTBEAT 15 static int heartbeat = WDT_HEARTBEAT; module_param(heartbeat, int, 0); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. " "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")"); static int nowayout = WATCHDOG_NOWAYOUT; module_param(nowayout, int, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); static void at91_ping(unsigned long data); static struct { unsigned long next_heartbeat; /* the next_heartbeat for the timer */ unsigned long open; char expect_close; struct timer_list timer; /* The timer that pings the watchdog */ } at91wdt_private; /* ......................................................................... */ /* * Reload the watchdog timer. (ie, pat the watchdog) */ static inline void at91_wdt_reset(void) { at91_sys_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); } /* * Timer tick */ static void at91_ping(unsigned long data) { if (time_before(jiffies, at91wdt_private.next_heartbeat) || (!nowayout && !at91wdt_private.open)) { at91_wdt_reset(); mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); } else printk(KERN_CRIT DRV_NAME": I will reset your machine !\n"); } /* * Watchdog device is opened, and watchdog starts running. */ static int at91_wdt_open(struct inode *inode, struct file *file) { if (test_and_set_bit(0, &at91wdt_private.open)) return -EBUSY; at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); return nonseekable_open(inode, file); } /* * Close the watchdog device. */ static int at91_wdt_close(struct inode *inode, struct file *file) { clear_bit(0, &at91wdt_private.open); /* stop internal ping */ if (!at91wdt_private.expect_close) del_timer(&at91wdt_private.timer); at91wdt_private.expect_close = 0; return 0; } /* * Set the watchdog time interval in 1/256Hz (write-once) * Counter is 12 bit. */ static int at91_wdt_settimeout(unsigned int timeout) { unsigned int reg; unsigned int mr; /* Check if disabled */ mr = at91_sys_read(AT91_WDT_MR); if (mr & AT91_WDT_WDDIS) { printk(KERN_ERR DRV_NAME": sorry, watchdog is disabled\n"); return -EIO; } /* * All counting occurs at SLOW_CLOCK / 128 = 256 Hz * * Since WDV is a 12-bit counter, the maximum period is * 4096 / 256 = 16 seconds. */ reg = AT91_WDT_WDRSTEN /* causes watchdog reset */ /* | AT91_WDT_WDRPROC causes processor reset only */ | AT91_WDT_WDDBGHLT /* disabled in debug mode */ | AT91_WDT_WDD /* restart at any time */ | (timeout & AT91_WDT_WDV); /* timer value */ at91_sys_write(AT91_WDT_MR, reg); return 0; } static const struct watchdog_info at91_wdt_info = { .identity = DRV_NAME, .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, }; /* * Handle commands from user-space. */ static long at91_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; int new_value; switch (cmd) { case WDIOC_GETSUPPORT: return copy_to_user(argp, &at91_wdt_info, sizeof(at91_wdt_info)) ? -EFAULT : 0; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: return put_user(0, p); case WDIOC_KEEPALIVE: at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; return 0; case WDIOC_SETTIMEOUT: if (get_user(new_value, p)) return -EFAULT; heartbeat = new_value; at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; return put_user(new_value, p); /* return current value */ case WDIOC_GETTIMEOUT: return put_user(heartbeat, p); } return -ENOTTY; } /* * Pat the watchdog whenever device is written to. */ static ssize_t at91_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) { if (!len) return 0; /* Scan for magic character */ if (!nowayout) { size_t i; at91wdt_private.expect_close = 0; for (i = 0; i < len; i++) { char c; if (get_user(c, data + i)) return -EFAULT; if (c == 'V') { at91wdt_private.expect_close = 42; break; } } } at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; return len; } /* ......................................................................... */ static const struct file_operations at91wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .unlocked_ioctl = at91_wdt_ioctl, .open = at91_wdt_open, .release = at91_wdt_close, .write = at91_wdt_write, }; static struct miscdevice at91wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &at91wdt_fops, }; static int __init at91wdt_probe(struct platform_device *pdev) { int res; if (at91wdt_miscdev.parent) return -EBUSY; at91wdt_miscdev.parent = &pdev->dev; /* Set watchdog */ res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000)); if (res) return res; res = misc_register(&at91wdt_miscdev); if (res) return res; at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; setup_timer(&at91wdt_private.timer, at91_ping, 0); mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); printk(KERN_INFO DRV_NAME " enabled (heartbeat=%d sec, nowayout=%d)\n", heartbeat, nowayout); return 0; } static int __exit at91wdt_remove(struct platform_device *pdev) { int res; res = misc_deregister(&at91wdt_miscdev); if (!res) at91wdt_miscdev.parent = NULL; return res; } static struct platform_driver at91wdt_driver = { .remove = __exit_p(at91wdt_remove), .driver = { .name = "at91_wdt", .owner = THIS_MODULE, }, }; static int __init at91sam_wdt_init(void) { return platform_driver_probe(&at91wdt_driver, at91wdt_probe); } static void __exit at91sam_wdt_exit(void) { platform_driver_unregister(&at91wdt_driver); } module_init(at91sam_wdt_init); module_exit(at91sam_wdt_exit); MODULE_AUTHOR("Renaud CERRATO "); MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors"); MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);