1e6bb42e3SRenaud CERRATO /* 2e6bb42e3SRenaud CERRATO * Watchdog driver for Atmel AT91SAM9x processors. 3e6bb42e3SRenaud CERRATO * 4e6bb42e3SRenaud CERRATO * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr 5e6bb42e3SRenaud CERRATO * 6e6bb42e3SRenaud CERRATO * This program is free software; you can redistribute it and/or 7e6bb42e3SRenaud CERRATO * modify it under the terms of the GNU General Public License 8e6bb42e3SRenaud CERRATO * as published by the Free Software Foundation; either version 9e6bb42e3SRenaud CERRATO * 2 of the License, or (at your option) any later version. 10e6bb42e3SRenaud CERRATO */ 11e6bb42e3SRenaud CERRATO 12e6bb42e3SRenaud CERRATO /* 13e6bb42e3SRenaud CERRATO * The Watchdog Timer Mode Register can be only written to once. If the 14e6bb42e3SRenaud CERRATO * timeout need to be set from Linux, be sure that the bootstrap or the 15e6bb42e3SRenaud CERRATO * bootloader doesn't write to this register. 16e6bb42e3SRenaud CERRATO */ 17e6bb42e3SRenaud CERRATO 1827c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1927c766aaSJoe Perches 20e6bb42e3SRenaud CERRATO #include <linux/errno.h> 21e6bb42e3SRenaud CERRATO #include <linux/init.h> 222af29b78SAndrew Victor #include <linux/io.h> 23e6bb42e3SRenaud CERRATO #include <linux/kernel.h> 24e6bb42e3SRenaud CERRATO #include <linux/module.h> 25e6bb42e3SRenaud CERRATO #include <linux/moduleparam.h> 26e6bb42e3SRenaud CERRATO #include <linux/platform_device.h> 27e6bb42e3SRenaud CERRATO #include <linux/types.h> 28e6bb42e3SRenaud CERRATO #include <linux/watchdog.h> 29e6bb42e3SRenaud CERRATO #include <linux/jiffies.h> 30e6bb42e3SRenaud CERRATO #include <linux/timer.h> 31e6bb42e3SRenaud CERRATO #include <linux/bitops.h> 32e6bb42e3SRenaud CERRATO #include <linux/uaccess.h> 33be49bbaeSFabio Porcedda #include <linux/of.h> 34e6bb42e3SRenaud CERRATO 35e7b39145SJean-Christophe Plagniol-Villard #include "at91sam9_wdt.h" 36e6bb42e3SRenaud CERRATO 37e6bb42e3SRenaud CERRATO #define DRV_NAME "AT91SAM9 Watchdog" 38e6bb42e3SRenaud CERRATO 39c1c30a29SJean-Christophe PLAGNIOL-VILLARD #define wdt_read(field) \ 40c1c30a29SJean-Christophe PLAGNIOL-VILLARD __raw_readl(at91wdt_private.base + field) 41c1c30a29SJean-Christophe PLAGNIOL-VILLARD #define wdt_write(field, val) \ 42c1c30a29SJean-Christophe PLAGNIOL-VILLARD __raw_writel((val), at91wdt_private.base + field) 43c1c30a29SJean-Christophe PLAGNIOL-VILLARD 44e6bb42e3SRenaud CERRATO /* AT91SAM9 watchdog runs a 12bit counter @ 256Hz, 45e6bb42e3SRenaud CERRATO * use this to convert a watchdog 46e6bb42e3SRenaud CERRATO * value from/to milliseconds. 47e6bb42e3SRenaud CERRATO */ 48e6bb42e3SRenaud CERRATO #define ms_to_ticks(t) (((t << 8) / 1000) - 1) 49e6bb42e3SRenaud CERRATO #define ticks_to_ms(t) (((t + 1) * 1000) >> 8) 50e6bb42e3SRenaud CERRATO 51e6bb42e3SRenaud CERRATO /* Hardware timeout in seconds */ 52e6bb42e3SRenaud CERRATO #define WDT_HW_TIMEOUT 2 53e6bb42e3SRenaud CERRATO 54e6bb42e3SRenaud CERRATO /* Timer heartbeat (500ms) */ 55e6bb42e3SRenaud CERRATO #define WDT_TIMEOUT (HZ/2) 56e6bb42e3SRenaud CERRATO 57e6bb42e3SRenaud CERRATO /* User land timeout */ 58e6bb42e3SRenaud CERRATO #define WDT_HEARTBEAT 15 59*c1fd5f64SFabio Porcedda static int heartbeat; 60e6bb42e3SRenaud CERRATO module_param(heartbeat, int, 0); 61e6bb42e3SRenaud CERRATO MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. " 62e6bb42e3SRenaud CERRATO "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")"); 63e6bb42e3SRenaud CERRATO 6486a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT; 6586a1e189SWim Van Sebroeck module_param(nowayout, bool, 0); 66e6bb42e3SRenaud CERRATO MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 67e6bb42e3SRenaud CERRATO "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 68e6bb42e3SRenaud CERRATO 69490ac7afSWenyou Yang static struct watchdog_device at91_wdt_dev; 70e6bb42e3SRenaud CERRATO static void at91_ping(unsigned long data); 71e6bb42e3SRenaud CERRATO 72e6bb42e3SRenaud CERRATO static struct { 73c1c30a29SJean-Christophe PLAGNIOL-VILLARD void __iomem *base; 74e6bb42e3SRenaud CERRATO unsigned long next_heartbeat; /* the next_heartbeat for the timer */ 75e6bb42e3SRenaud CERRATO struct timer_list timer; /* The timer that pings the watchdog */ 76e6bb42e3SRenaud CERRATO } at91wdt_private; 77e6bb42e3SRenaud CERRATO 78e6bb42e3SRenaud CERRATO /* ......................................................................... */ 79e6bb42e3SRenaud CERRATO 80e6bb42e3SRenaud CERRATO /* 81e6bb42e3SRenaud CERRATO * Reload the watchdog timer. (ie, pat the watchdog) 82e6bb42e3SRenaud CERRATO */ 83e6bb42e3SRenaud CERRATO static inline void at91_wdt_reset(void) 84e6bb42e3SRenaud CERRATO { 85c1c30a29SJean-Christophe PLAGNIOL-VILLARD wdt_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); 86e6bb42e3SRenaud CERRATO } 87e6bb42e3SRenaud CERRATO 88e6bb42e3SRenaud CERRATO /* 89e6bb42e3SRenaud CERRATO * Timer tick 90e6bb42e3SRenaud CERRATO */ 91e6bb42e3SRenaud CERRATO static void at91_ping(unsigned long data) 92e6bb42e3SRenaud CERRATO { 93e6bb42e3SRenaud CERRATO if (time_before(jiffies, at91wdt_private.next_heartbeat) || 94490ac7afSWenyou Yang (!watchdog_active(&at91_wdt_dev))) { 95e6bb42e3SRenaud CERRATO at91_wdt_reset(); 96e6bb42e3SRenaud CERRATO mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); 97e6bb42e3SRenaud CERRATO } else 9827c766aaSJoe Perches pr_crit("I will reset your machine !\n"); 99e6bb42e3SRenaud CERRATO } 100e6bb42e3SRenaud CERRATO 101490ac7afSWenyou Yang static int at91_wdt_ping(struct watchdog_device *wdd) 102e6bb42e3SRenaud CERRATO { 103490ac7afSWenyou Yang /* calculate when the next userspace timeout will be */ 104490ac7afSWenyou Yang at91wdt_private.next_heartbeat = jiffies + wdd->timeout * HZ; 105490ac7afSWenyou Yang return 0; 106e6bb42e3SRenaud CERRATO } 107e6bb42e3SRenaud CERRATO 108490ac7afSWenyou Yang static int at91_wdt_start(struct watchdog_device *wdd) 109e6bb42e3SRenaud CERRATO { 110490ac7afSWenyou Yang /* calculate the next userspace timeout and modify the timer */ 111490ac7afSWenyou Yang at91_wdt_ping(wdd); 112490ac7afSWenyou Yang mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); 113490ac7afSWenyou Yang return 0; 114490ac7afSWenyou Yang } 115e6bb42e3SRenaud CERRATO 116490ac7afSWenyou Yang static int at91_wdt_stop(struct watchdog_device *wdd) 117490ac7afSWenyou Yang { 118490ac7afSWenyou Yang /* The watchdog timer hardware can not be stopped... */ 119490ac7afSWenyou Yang return 0; 120490ac7afSWenyou Yang } 121e6bb42e3SRenaud CERRATO 122490ac7afSWenyou Yang static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout) 123490ac7afSWenyou Yang { 124490ac7afSWenyou Yang wdd->timeout = new_timeout; 125e6bb42e3SRenaud CERRATO return 0; 126e6bb42e3SRenaud CERRATO } 127e6bb42e3SRenaud CERRATO 128e6bb42e3SRenaud CERRATO /* 129e6bb42e3SRenaud CERRATO * Set the watchdog time interval in 1/256Hz (write-once) 130e6bb42e3SRenaud CERRATO * Counter is 12 bit. 131e6bb42e3SRenaud CERRATO */ 132e6bb42e3SRenaud CERRATO static int at91_wdt_settimeout(unsigned int timeout) 133e6bb42e3SRenaud CERRATO { 134e6bb42e3SRenaud CERRATO unsigned int reg; 135e6bb42e3SRenaud CERRATO unsigned int mr; 136e6bb42e3SRenaud CERRATO 137e6bb42e3SRenaud CERRATO /* Check if disabled */ 138c1c30a29SJean-Christophe PLAGNIOL-VILLARD mr = wdt_read(AT91_WDT_MR); 139e6bb42e3SRenaud CERRATO if (mr & AT91_WDT_WDDIS) { 14027c766aaSJoe Perches pr_err("sorry, watchdog is disabled\n"); 141e6bb42e3SRenaud CERRATO return -EIO; 142e6bb42e3SRenaud CERRATO } 143e6bb42e3SRenaud CERRATO 144e6bb42e3SRenaud CERRATO /* 145e6bb42e3SRenaud CERRATO * All counting occurs at SLOW_CLOCK / 128 = 256 Hz 146e6bb42e3SRenaud CERRATO * 147e6bb42e3SRenaud CERRATO * Since WDV is a 12-bit counter, the maximum period is 148e6bb42e3SRenaud CERRATO * 4096 / 256 = 16 seconds. 149e6bb42e3SRenaud CERRATO */ 150e6bb42e3SRenaud CERRATO reg = AT91_WDT_WDRSTEN /* causes watchdog reset */ 151e6bb42e3SRenaud CERRATO /* | AT91_WDT_WDRPROC causes processor reset only */ 152e6bb42e3SRenaud CERRATO | AT91_WDT_WDDBGHLT /* disabled in debug mode */ 153e6bb42e3SRenaud CERRATO | AT91_WDT_WDD /* restart at any time */ 154e6bb42e3SRenaud CERRATO | (timeout & AT91_WDT_WDV); /* timer value */ 155c1c30a29SJean-Christophe PLAGNIOL-VILLARD wdt_write(AT91_WDT_MR, reg); 156e6bb42e3SRenaud CERRATO 157e6bb42e3SRenaud CERRATO return 0; 158e6bb42e3SRenaud CERRATO } 159e6bb42e3SRenaud CERRATO 160490ac7afSWenyou Yang /* ......................................................................... */ 161490ac7afSWenyou Yang 162e6bb42e3SRenaud CERRATO static const struct watchdog_info at91_wdt_info = { 163e6bb42e3SRenaud CERRATO .identity = DRV_NAME, 164e73a7802SWim Van Sebroeck .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 165e73a7802SWim Van Sebroeck WDIOF_MAGICCLOSE, 166e6bb42e3SRenaud CERRATO }; 167e6bb42e3SRenaud CERRATO 168490ac7afSWenyou Yang static const struct watchdog_ops at91_wdt_ops = { 169e6bb42e3SRenaud CERRATO .owner = THIS_MODULE, 170490ac7afSWenyou Yang .start = at91_wdt_start, 171490ac7afSWenyou Yang .stop = at91_wdt_stop, 172490ac7afSWenyou Yang .ping = at91_wdt_ping, 173490ac7afSWenyou Yang .set_timeout = at91_wdt_set_timeout, 174e6bb42e3SRenaud CERRATO }; 175e6bb42e3SRenaud CERRATO 176490ac7afSWenyou Yang static struct watchdog_device at91_wdt_dev = { 177490ac7afSWenyou Yang .info = &at91_wdt_info, 178490ac7afSWenyou Yang .ops = &at91_wdt_ops, 179*c1fd5f64SFabio Porcedda .timeout = WDT_HEARTBEAT, 180490ac7afSWenyou Yang .min_timeout = 1, 181490ac7afSWenyou Yang .max_timeout = 0xFFFF, 182e6bb42e3SRenaud CERRATO }; 183e6bb42e3SRenaud CERRATO 184e6bb42e3SRenaud CERRATO static int __init at91wdt_probe(struct platform_device *pdev) 185e6bb42e3SRenaud CERRATO { 186c1c30a29SJean-Christophe PLAGNIOL-VILLARD struct resource *r; 187e6bb42e3SRenaud CERRATO int res; 188e6bb42e3SRenaud CERRATO 189c1c30a29SJean-Christophe PLAGNIOL-VILLARD r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 190c1c30a29SJean-Christophe PLAGNIOL-VILLARD if (!r) 191c1c30a29SJean-Christophe PLAGNIOL-VILLARD return -ENODEV; 192c1c30a29SJean-Christophe PLAGNIOL-VILLARD at91wdt_private.base = ioremap(r->start, resource_size(r)); 193c1c30a29SJean-Christophe PLAGNIOL-VILLARD if (!at91wdt_private.base) { 194c1c30a29SJean-Christophe PLAGNIOL-VILLARD dev_err(&pdev->dev, "failed to map registers, aborting.\n"); 195c1c30a29SJean-Christophe PLAGNIOL-VILLARD return -ENOMEM; 196c1c30a29SJean-Christophe PLAGNIOL-VILLARD } 197c1c30a29SJean-Christophe PLAGNIOL-VILLARD 198490ac7afSWenyou Yang at91_wdt_dev.parent = &pdev->dev; 199*c1fd5f64SFabio Porcedda watchdog_init_timeout(&at91_wdt_dev, heartbeat, &pdev->dev); 200490ac7afSWenyou Yang watchdog_set_nowayout(&at91_wdt_dev, nowayout); 201490ac7afSWenyou Yang 202e6bb42e3SRenaud CERRATO /* Set watchdog */ 203e6bb42e3SRenaud CERRATO res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000)); 204e6bb42e3SRenaud CERRATO if (res) 205e6bb42e3SRenaud CERRATO return res; 206e6bb42e3SRenaud CERRATO 207490ac7afSWenyou Yang res = watchdog_register_device(&at91_wdt_dev); 208e6bb42e3SRenaud CERRATO if (res) 209e6bb42e3SRenaud CERRATO return res; 210e6bb42e3SRenaud CERRATO 211490ac7afSWenyou Yang at91wdt_private.next_heartbeat = jiffies + at91_wdt_dev.timeout * HZ; 212e6bb42e3SRenaud CERRATO setup_timer(&at91wdt_private.timer, at91_ping, 0); 213e6bb42e3SRenaud CERRATO mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); 214e6bb42e3SRenaud CERRATO 21527c766aaSJoe Perches pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n", 216*c1fd5f64SFabio Porcedda at91_wdt_dev.timeout, nowayout); 217e6bb42e3SRenaud CERRATO 218e6bb42e3SRenaud CERRATO return 0; 219e6bb42e3SRenaud CERRATO } 220e6bb42e3SRenaud CERRATO 221e6bb42e3SRenaud CERRATO static int __exit at91wdt_remove(struct platform_device *pdev) 222e6bb42e3SRenaud CERRATO { 223490ac7afSWenyou Yang watchdog_unregister_device(&at91_wdt_dev); 224e6bb42e3SRenaud CERRATO 225490ac7afSWenyou Yang pr_warn("I quit now, hardware will probably reboot!\n"); 226490ac7afSWenyou Yang del_timer(&at91wdt_private.timer); 227e6bb42e3SRenaud CERRATO 228490ac7afSWenyou Yang return 0; 229e6bb42e3SRenaud CERRATO } 230e6bb42e3SRenaud CERRATO 231be49bbaeSFabio Porcedda #if defined(CONFIG_OF) 2326c41e474SArnd Bergmann static const struct of_device_id at91_wdt_dt_ids[] = { 233be49bbaeSFabio Porcedda { .compatible = "atmel,at91sam9260-wdt" }, 234be49bbaeSFabio Porcedda { /* sentinel */ } 235be49bbaeSFabio Porcedda }; 236be49bbaeSFabio Porcedda 237be49bbaeSFabio Porcedda MODULE_DEVICE_TABLE(of, at91_wdt_dt_ids); 238be49bbaeSFabio Porcedda #endif 239be49bbaeSFabio Porcedda 240e6bb42e3SRenaud CERRATO static struct platform_driver at91wdt_driver = { 241e6bb42e3SRenaud CERRATO .remove = __exit_p(at91wdt_remove), 242e6bb42e3SRenaud CERRATO .driver = { 243e6bb42e3SRenaud CERRATO .name = "at91_wdt", 244e6bb42e3SRenaud CERRATO .owner = THIS_MODULE, 245be49bbaeSFabio Porcedda .of_match_table = of_match_ptr(at91_wdt_dt_ids), 246e6bb42e3SRenaud CERRATO }, 247e6bb42e3SRenaud CERRATO }; 248e6bb42e3SRenaud CERRATO 2491cb9204cSFabio Porcedda module_platform_driver_probe(at91wdt_driver, at91wdt_probe); 250e6bb42e3SRenaud CERRATO 251e6bb42e3SRenaud CERRATO MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>"); 252e6bb42e3SRenaud CERRATO MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors"); 253e6bb42e3SRenaud CERRATO MODULE_LICENSE("GPL"); 254