diff options
Diffstat (limited to 'drivers/watchdog/at91sam9_wdt.c')
| -rw-r--r-- | drivers/watchdog/at91sam9_wdt.c | 457 |
1 files changed, 266 insertions, 191 deletions
diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c index eac26021e8d..489729b2629 100644 --- a/drivers/watchdog/at91sam9_wdt.c +++ b/drivers/watchdog/at91sam9_wdt.c @@ -15,32 +15,51 @@ * bootloader doesn't write to this register. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/errno.h> -#include <linux/fs.h> #include <linux/init.h> +#include <linux/interrupt.h> #include <linux/io.h> #include <linux/kernel.h> -#include <linux/miscdevice.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/platform_device.h> +#include <linux/reboot.h> #include <linux/types.h> #include <linux/watchdog.h> #include <linux/jiffies.h> #include <linux/timer.h> #include <linux/bitops.h> #include <linux/uaccess.h> +#include <linux/of.h> +#include <linux/of_irq.h> -#include <mach/at91_wdt.h> +#include "at91sam9_wdt.h" #define DRV_NAME "AT91SAM9 Watchdog" +#define wdt_read(wdt, field) \ + __raw_readl((wdt)->base + (field)) +#define wdt_write(wtd, field, val) \ + __raw_writel((val), (wdt)->base + (field)) + /* 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) +#define ticks_to_hz_rounddown(t) ((((t) + 1) * HZ) >> 8) +#define ticks_to_hz_roundup(t) (((((t) + 1) * HZ) + 255) >> 8) +#define ticks_to_secs(t) (((t) + 1) >> 8) +#define secs_to_ticks(s) ((s) ? (((s) << 8) - 1) : 0) + +#define WDT_MR_RESET 0x3FFF2FFF + +/* Watchdog max counter value in ticks */ +#define WDT_COUNTER_MAX_TICKS 0xFFF + +/* Watchdog max delta/value in secs */ +#define WDT_COUNTER_MAX_SECS ticks_to_secs(WDT_COUNTER_MAX_TICKS) /* Hardware timeout in seconds */ #define WDT_HW_TIMEOUT 2 @@ -50,34 +69,50 @@ /* User land timeout */ #define WDT_HEARTBEAT 15 -static int heartbeat = WDT_HEARTBEAT; +static int 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); +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 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 { +#define to_wdt(wdd) container_of(wdd, struct at91wdt, wdd) +struct at91wdt { + struct watchdog_device wdd; + void __iomem *base; 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; + u32 mr; + u32 mr_mask; + unsigned long heartbeat; /* WDT heartbeat in jiffies */ + bool nowayout; + unsigned int irq; +}; /* ......................................................................... */ +static irqreturn_t wdt_interrupt(int irq, void *dev_id) +{ + struct at91wdt *wdt = (struct at91wdt *)dev_id; + + if (wdt_read(wdt, AT91_WDT_SR)) { + pr_crit("at91sam9 WDT software reset\n"); + emergency_restart(); + pr_crit("Reboot didn't ?????\n"); + } + + return IRQ_HANDLED; +} /* * Reload the watchdog timer. (ie, pat the watchdog) */ -static inline void at91_wdt_reset(void) +static inline void at91_wdt_reset(struct at91wdt *wdt) { - at91_sys_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); + wdt_write(wdt, AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); } /* @@ -85,246 +120,286 @@ static inline void at91_wdt_reset(void) */ 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"); + struct at91wdt *wdt = (struct at91wdt *)data; + if (time_before(jiffies, wdt->next_heartbeat) || + !watchdog_active(&wdt->wdd)) { + at91_wdt_reset(wdt); + mod_timer(&wdt->timer, jiffies + wdt->heartbeat); + } else { + pr_crit("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) +static int at91_wdt_start(struct watchdog_device *wdd) { - if (test_and_set_bit(0, &at91wdt_private.open)) - return -EBUSY; + struct at91wdt *wdt = to_wdt(wdd); + /* calculate when the next userspace timeout will be */ + wdt->next_heartbeat = jiffies + wdd->timeout * HZ; + return 0; +} - at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; - mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); +static int at91_wdt_stop(struct watchdog_device *wdd) +{ + /* The watchdog timer hardware can not be stopped... */ + return 0; +} - return nonseekable_open(inode, file); +static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout) +{ + wdd->timeout = new_timeout; + return at91_wdt_start(wdd); } -/* - * Close the watchdog device. - */ -static int at91_wdt_close(struct inode *inode, struct file *file) +static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt) { - clear_bit(0, &at91wdt_private.open); + u32 tmp; + u32 delta; + u32 value; + int err; + u32 mask = wdt->mr_mask; + unsigned long min_heartbeat = 1; + unsigned long max_heartbeat; + struct device *dev = &pdev->dev; + + tmp = wdt_read(wdt, AT91_WDT_MR); + if ((tmp & mask) != (wdt->mr & mask)) { + if (tmp == WDT_MR_RESET) { + wdt_write(wdt, AT91_WDT_MR, wdt->mr); + tmp = wdt_read(wdt, AT91_WDT_MR); + } + } - /* stop internal ping */ - if (!at91wdt_private.expect_close) - del_timer(&at91wdt_private.timer); + if (tmp & AT91_WDT_WDDIS) { + if (wdt->mr & AT91_WDT_WDDIS) + return 0; + dev_err(dev, "watchdog is disabled\n"); + return -EINVAL; + } - at91wdt_private.expect_close = 0; - return 0; -} + value = tmp & AT91_WDT_WDV; + delta = (tmp & AT91_WDT_WDD) >> 16; -/* - * 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; + if (delta < value) + min_heartbeat = ticks_to_hz_roundup(value - delta); + + max_heartbeat = ticks_to_hz_rounddown(value); + if (!max_heartbeat) { + dev_err(dev, + "heartbeat is too small for the system to handle it correctly\n"); + return -EINVAL; + } + + /* + * Try to reset the watchdog counter 4 or 2 times more often than + * actually requested, to avoid spurious watchdog reset. + * If this is not possible because of the min_heartbeat value, reset + * it at the min_heartbeat period. + */ + if ((max_heartbeat / 4) >= min_heartbeat) + wdt->heartbeat = max_heartbeat / 4; + else if ((max_heartbeat / 2) >= min_heartbeat) + wdt->heartbeat = max_heartbeat / 2; + else + wdt->heartbeat = min_heartbeat; + + if (max_heartbeat < min_heartbeat + 4) + dev_warn(dev, + "min heartbeat and max heartbeat might be too close for the system to handle it correctly\n"); + + if ((tmp & AT91_WDT_WDFIEN) && wdt->irq) { + err = request_irq(wdt->irq, wdt_interrupt, + IRQF_SHARED | IRQF_IRQPOLL, + pdev->name, wdt); + if (err) + return err; } + if ((tmp & wdt->mr_mask) != (wdt->mr & wdt->mr_mask)) + dev_warn(dev, + "watchdog already configured differently (mr = %x expecting %x)\n", + tmp & wdt->mr_mask, wdt->mr & wdt->mr_mask); + + setup_timer(&wdt->timer, at91_ping, (unsigned long)wdt); + /* - * All counting occurs at SLOW_CLOCK / 128 = 256 Hz - * - * Since WDV is a 12-bit counter, the maximum period is - * 4096 / 256 = 16 seconds. + * Use min_heartbeat the first time to avoid spurious watchdog reset: + * we don't know for how long the watchdog counter is running, and + * - resetting it right now might trigger a watchdog fault reset + * - waiting for heartbeat time might lead to a watchdog timeout + * reset */ - 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); + mod_timer(&wdt->timer, jiffies + min_heartbeat); + + /* Try to set timeout from device tree first */ + if (watchdog_init_timeout(&wdt->wdd, 0, dev)) + watchdog_init_timeout(&wdt->wdd, heartbeat, dev); + watchdog_set_nowayout(&wdt->wdd, wdt->nowayout); + err = watchdog_register_device(&wdt->wdd); + if (err) + goto out_stop_timer; + + wdt->next_heartbeat = jiffies + wdt->wdd.timeout * HZ; return 0; + +out_stop_timer: + del_timer(&wdt->timer); + return err; } +/* ......................................................................... */ + 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) +static const struct watchdog_ops at91_wdt_ops = { + .owner = THIS_MODULE, + .start = at91_wdt_start, + .stop = at91_wdt_stop, + .set_timeout = at91_wdt_set_timeout, +}; + +#if defined(CONFIG_OF) +static int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt) { - void __user *argp = (void __user *)arg; - int __user *p = argp; - int new_value; + u32 min = 0; + u32 max = WDT_COUNTER_MAX_SECS; + const char *tmp; + + /* Get the interrupts property */ + wdt->irq = irq_of_parse_and_map(np, 0); + if (!wdt->irq) + dev_warn(wdt->wdd.parent, "failed to get IRQ from DT\n"); + + if (!of_property_read_u32_index(np, "atmel,max-heartbeat-sec", 0, + &max)) { + if (!max || max > WDT_COUNTER_MAX_SECS) + max = WDT_COUNTER_MAX_SECS; + + if (!of_property_read_u32_index(np, "atmel,min-heartbeat-sec", + 0, &min)) { + if (min >= max) + min = max - 1; + } + } - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &at91_wdt_info, - sizeof(at91_wdt_info)) ? -EFAULT : 0; + min = secs_to_ticks(min); + max = secs_to_ticks(max); + + wdt->mr_mask = 0x3FFFFFFF; + wdt->mr = 0; + if (!of_property_read_string(np, "atmel,watchdog-type", &tmp) && + !strcmp(tmp, "software")) { + wdt->mr |= AT91_WDT_WDFIEN; + wdt->mr_mask &= ~AT91_WDT_WDRPROC; + } else { + wdt->mr |= AT91_WDT_WDRSTEN; + } - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); + if (!of_property_read_string(np, "atmel,reset-type", &tmp) && + !strcmp(tmp, "proc")) + wdt->mr |= AT91_WDT_WDRPROC; - case WDIOC_KEEPALIVE: - at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; - return 0; + if (of_property_read_bool(np, "atmel,disable")) { + wdt->mr |= AT91_WDT_WDDIS; + wdt->mr_mask &= AT91_WDT_WDDIS; + } - case WDIOC_SETTIMEOUT: - if (get_user(new_value, p)) - return -EFAULT; + if (of_property_read_bool(np, "atmel,idle-halt")) + wdt->mr |= AT91_WDT_WDIDLEHLT; - heartbeat = new_value; - at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + if (of_property_read_bool(np, "atmel,dbg-halt")) + wdt->mr |= AT91_WDT_WDDBGHLT; - return put_user(new_value, p); /* return current value */ + wdt->mr |= max | ((max - min) << 16); - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); - } - return -ENOTTY; + return 0; } - -/* - * 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) +#else +static inline int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt) { - 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; + return 0; } - -/* ......................................................................... */ - -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, -}; +#endif 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; + struct resource *r; + int err; + struct at91wdt *wdt; + + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + wdt->mr = (WDT_HW_TIMEOUT * 256) | AT91_WDT_WDRSTEN | AT91_WDT_WDD | + AT91_WDT_WDDBGHLT | AT91_WDT_WDIDLEHLT; + wdt->mr_mask = 0x3FFFFFFF; + wdt->nowayout = nowayout; + wdt->wdd.parent = &pdev->dev; + wdt->wdd.info = &at91_wdt_info; + wdt->wdd.ops = &at91_wdt_ops; + wdt->wdd.timeout = WDT_HEARTBEAT; + wdt->wdd.min_timeout = 1; + wdt->wdd.max_timeout = 0xFFFF; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); + + if (pdev->dev.of_node) { + err = of_at91wdt_init(pdev->dev.of_node, wdt); + if (err) + return err; + } - res = misc_register(&at91wdt_miscdev); - if (res) - return res; + err = at91_wdt_init(pdev, wdt); + if (err) + return err; - at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; - setup_timer(&at91wdt_private.timer, at91_ping, 0); - mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); + platform_set_drvdata(pdev, wdt); - printk(KERN_INFO DRV_NAME " enabled (heartbeat=%d sec, nowayout=%d)\n", - heartbeat, nowayout); + pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n", + wdt->wdd.timeout, wdt->nowayout); return 0; } static int __exit at91wdt_remove(struct platform_device *pdev) { - int res; + struct at91wdt *wdt = platform_get_drvdata(pdev); + watchdog_unregister_device(&wdt->wdd); - res = misc_deregister(&at91wdt_miscdev); - if (!res) - at91wdt_miscdev.parent = NULL; + pr_warn("I quit now, hardware will probably reboot!\n"); + del_timer(&wdt->timer); - return res; -} - -#ifdef CONFIG_PM - -static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message) -{ return 0; } -static int at91wdt_resume(struct platform_device *pdev) -{ - return 0; -} +#if defined(CONFIG_OF) +static const struct of_device_id at91_wdt_dt_ids[] = { + { .compatible = "atmel,at91sam9260-wdt" }, + { /* sentinel */ } +}; -#else -#define at91wdt_suspend NULL -#define at91wdt_resume NULL +MODULE_DEVICE_TABLE(of, at91_wdt_dt_ids); #endif static struct platform_driver at91wdt_driver = { .remove = __exit_p(at91wdt_remove), - .suspend = at91wdt_suspend, - .resume = at91wdt_resume, .driver = { .name = "at91_wdt", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(at91_wdt_dt_ids), }, }; -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_platform_driver_probe(at91wdt_driver, at91wdt_probe); MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>"); MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
