1*e6bb42e3SRenaud CERRATO /* 2*e6bb42e3SRenaud CERRATO * Watchdog driver for Atmel AT91SAM9x processors. 3*e6bb42e3SRenaud CERRATO * 4*e6bb42e3SRenaud CERRATO * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr 5*e6bb42e3SRenaud CERRATO * 6*e6bb42e3SRenaud CERRATO * This program is free software; you can redistribute it and/or 7*e6bb42e3SRenaud CERRATO * modify it under the terms of the GNU General Public License 8*e6bb42e3SRenaud CERRATO * as published by the Free Software Foundation; either version 9*e6bb42e3SRenaud CERRATO * 2 of the License, or (at your option) any later version. 10*e6bb42e3SRenaud CERRATO */ 11*e6bb42e3SRenaud CERRATO 12*e6bb42e3SRenaud CERRATO /* 13*e6bb42e3SRenaud CERRATO * The Watchdog Timer Mode Register can be only written to once. If the 14*e6bb42e3SRenaud CERRATO * timeout need to be set from Linux, be sure that the bootstrap or the 15*e6bb42e3SRenaud CERRATO * bootloader doesn't write to this register. 16*e6bb42e3SRenaud CERRATO */ 17*e6bb42e3SRenaud CERRATO 18*e6bb42e3SRenaud CERRATO #include <linux/errno.h> 19*e6bb42e3SRenaud CERRATO #include <linux/fs.h> 20*e6bb42e3SRenaud CERRATO #include <linux/init.h> 21*e6bb42e3SRenaud CERRATO #include <linux/kernel.h> 22*e6bb42e3SRenaud CERRATO #include <linux/miscdevice.h> 23*e6bb42e3SRenaud CERRATO #include <linux/module.h> 24*e6bb42e3SRenaud CERRATO #include <linux/moduleparam.h> 25*e6bb42e3SRenaud CERRATO #include <linux/platform_device.h> 26*e6bb42e3SRenaud CERRATO #include <linux/types.h> 27*e6bb42e3SRenaud CERRATO #include <linux/watchdog.h> 28*e6bb42e3SRenaud CERRATO #include <linux/jiffies.h> 29*e6bb42e3SRenaud CERRATO #include <linux/timer.h> 30*e6bb42e3SRenaud CERRATO #include <linux/bitops.h> 31*e6bb42e3SRenaud CERRATO #include <linux/uaccess.h> 32*e6bb42e3SRenaud CERRATO 33*e6bb42e3SRenaud CERRATO #include <asm/arch/at91_wdt.h> 34*e6bb42e3SRenaud CERRATO 35*e6bb42e3SRenaud CERRATO #define DRV_NAME "AT91SAM9 Watchdog" 36*e6bb42e3SRenaud CERRATO 37*e6bb42e3SRenaud CERRATO /* AT91SAM9 watchdog runs a 12bit counter @ 256Hz, 38*e6bb42e3SRenaud CERRATO * use this to convert a watchdog 39*e6bb42e3SRenaud CERRATO * value from/to milliseconds. 40*e6bb42e3SRenaud CERRATO */ 41*e6bb42e3SRenaud CERRATO #define ms_to_ticks(t) (((t << 8) / 1000) - 1) 42*e6bb42e3SRenaud CERRATO #define ticks_to_ms(t) (((t + 1) * 1000) >> 8) 43*e6bb42e3SRenaud CERRATO 44*e6bb42e3SRenaud CERRATO /* Hardware timeout in seconds */ 45*e6bb42e3SRenaud CERRATO #define WDT_HW_TIMEOUT 2 46*e6bb42e3SRenaud CERRATO 47*e6bb42e3SRenaud CERRATO /* Timer heartbeat (500ms) */ 48*e6bb42e3SRenaud CERRATO #define WDT_TIMEOUT (HZ/2) 49*e6bb42e3SRenaud CERRATO 50*e6bb42e3SRenaud CERRATO /* User land timeout */ 51*e6bb42e3SRenaud CERRATO #define WDT_HEARTBEAT 15 52*e6bb42e3SRenaud CERRATO static int heartbeat = WDT_HEARTBEAT; 53*e6bb42e3SRenaud CERRATO module_param(heartbeat, int, 0); 54*e6bb42e3SRenaud CERRATO MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. " 55*e6bb42e3SRenaud CERRATO "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")"); 56*e6bb42e3SRenaud CERRATO 57*e6bb42e3SRenaud CERRATO static int nowayout = WATCHDOG_NOWAYOUT; 58*e6bb42e3SRenaud CERRATO module_param(nowayout, int, 0); 59*e6bb42e3SRenaud CERRATO MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 60*e6bb42e3SRenaud CERRATO "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 61*e6bb42e3SRenaud CERRATO 62*e6bb42e3SRenaud CERRATO static void at91_ping(unsigned long data); 63*e6bb42e3SRenaud CERRATO 64*e6bb42e3SRenaud CERRATO static struct { 65*e6bb42e3SRenaud CERRATO unsigned long next_heartbeat; /* the next_heartbeat for the timer */ 66*e6bb42e3SRenaud CERRATO unsigned long open; 67*e6bb42e3SRenaud CERRATO char expect_close; 68*e6bb42e3SRenaud CERRATO struct timer_list timer; /* The timer that pings the watchdog */ 69*e6bb42e3SRenaud CERRATO } at91wdt_private; 70*e6bb42e3SRenaud CERRATO 71*e6bb42e3SRenaud CERRATO /* ......................................................................... */ 72*e6bb42e3SRenaud CERRATO 73*e6bb42e3SRenaud CERRATO 74*e6bb42e3SRenaud CERRATO /* 75*e6bb42e3SRenaud CERRATO * Reload the watchdog timer. (ie, pat the watchdog) 76*e6bb42e3SRenaud CERRATO */ 77*e6bb42e3SRenaud CERRATO static inline void at91_wdt_reset(void) 78*e6bb42e3SRenaud CERRATO { 79*e6bb42e3SRenaud CERRATO at91_sys_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); 80*e6bb42e3SRenaud CERRATO } 81*e6bb42e3SRenaud CERRATO 82*e6bb42e3SRenaud CERRATO /* 83*e6bb42e3SRenaud CERRATO * Timer tick 84*e6bb42e3SRenaud CERRATO */ 85*e6bb42e3SRenaud CERRATO static void at91_ping(unsigned long data) 86*e6bb42e3SRenaud CERRATO { 87*e6bb42e3SRenaud CERRATO if (time_before(jiffies, at91wdt_private.next_heartbeat) || 88*e6bb42e3SRenaud CERRATO (!nowayout && !at91wdt_private.open)) { 89*e6bb42e3SRenaud CERRATO at91_wdt_reset(); 90*e6bb42e3SRenaud CERRATO mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); 91*e6bb42e3SRenaud CERRATO } else 92*e6bb42e3SRenaud CERRATO printk(KERN_CRIT DRV_NAME": I will reset your machine !\n"); 93*e6bb42e3SRenaud CERRATO } 94*e6bb42e3SRenaud CERRATO 95*e6bb42e3SRenaud CERRATO /* 96*e6bb42e3SRenaud CERRATO * Watchdog device is opened, and watchdog starts running. 97*e6bb42e3SRenaud CERRATO */ 98*e6bb42e3SRenaud CERRATO static int at91_wdt_open(struct inode *inode, struct file *file) 99*e6bb42e3SRenaud CERRATO { 100*e6bb42e3SRenaud CERRATO if (test_and_set_bit(0, &at91wdt_private.open)) 101*e6bb42e3SRenaud CERRATO return -EBUSY; 102*e6bb42e3SRenaud CERRATO 103*e6bb42e3SRenaud CERRATO at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; 104*e6bb42e3SRenaud CERRATO mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); 105*e6bb42e3SRenaud CERRATO 106*e6bb42e3SRenaud CERRATO return nonseekable_open(inode, file); 107*e6bb42e3SRenaud CERRATO } 108*e6bb42e3SRenaud CERRATO 109*e6bb42e3SRenaud CERRATO /* 110*e6bb42e3SRenaud CERRATO * Close the watchdog device. 111*e6bb42e3SRenaud CERRATO */ 112*e6bb42e3SRenaud CERRATO static int at91_wdt_close(struct inode *inode, struct file *file) 113*e6bb42e3SRenaud CERRATO { 114*e6bb42e3SRenaud CERRATO clear_bit(0, &at91wdt_private.open); 115*e6bb42e3SRenaud CERRATO 116*e6bb42e3SRenaud CERRATO /* stop internal ping */ 117*e6bb42e3SRenaud CERRATO if (!at91wdt_private.expect_close) 118*e6bb42e3SRenaud CERRATO del_timer(&at91wdt_private.timer); 119*e6bb42e3SRenaud CERRATO 120*e6bb42e3SRenaud CERRATO at91wdt_private.expect_close = 0; 121*e6bb42e3SRenaud CERRATO return 0; 122*e6bb42e3SRenaud CERRATO } 123*e6bb42e3SRenaud CERRATO 124*e6bb42e3SRenaud CERRATO /* 125*e6bb42e3SRenaud CERRATO * Set the watchdog time interval in 1/256Hz (write-once) 126*e6bb42e3SRenaud CERRATO * Counter is 12 bit. 127*e6bb42e3SRenaud CERRATO */ 128*e6bb42e3SRenaud CERRATO static int at91_wdt_settimeout(unsigned int timeout) 129*e6bb42e3SRenaud CERRATO { 130*e6bb42e3SRenaud CERRATO unsigned int reg; 131*e6bb42e3SRenaud CERRATO unsigned int mr; 132*e6bb42e3SRenaud CERRATO 133*e6bb42e3SRenaud CERRATO /* Check if disabled */ 134*e6bb42e3SRenaud CERRATO mr = at91_sys_read(AT91_WDT_MR); 135*e6bb42e3SRenaud CERRATO if (mr & AT91_WDT_WDDIS) { 136*e6bb42e3SRenaud CERRATO printk(KERN_ERR DRV_NAME": sorry, watchdog is disabled\n"); 137*e6bb42e3SRenaud CERRATO return -EIO; 138*e6bb42e3SRenaud CERRATO } 139*e6bb42e3SRenaud CERRATO 140*e6bb42e3SRenaud CERRATO /* 141*e6bb42e3SRenaud CERRATO * All counting occurs at SLOW_CLOCK / 128 = 256 Hz 142*e6bb42e3SRenaud CERRATO * 143*e6bb42e3SRenaud CERRATO * Since WDV is a 12-bit counter, the maximum period is 144*e6bb42e3SRenaud CERRATO * 4096 / 256 = 16 seconds. 145*e6bb42e3SRenaud CERRATO */ 146*e6bb42e3SRenaud CERRATO reg = AT91_WDT_WDRSTEN /* causes watchdog reset */ 147*e6bb42e3SRenaud CERRATO /* | AT91_WDT_WDRPROC causes processor reset only */ 148*e6bb42e3SRenaud CERRATO | AT91_WDT_WDDBGHLT /* disabled in debug mode */ 149*e6bb42e3SRenaud CERRATO | AT91_WDT_WDD /* restart at any time */ 150*e6bb42e3SRenaud CERRATO | (timeout & AT91_WDT_WDV); /* timer value */ 151*e6bb42e3SRenaud CERRATO at91_sys_write(AT91_WDT_MR, reg); 152*e6bb42e3SRenaud CERRATO 153*e6bb42e3SRenaud CERRATO return 0; 154*e6bb42e3SRenaud CERRATO } 155*e6bb42e3SRenaud CERRATO 156*e6bb42e3SRenaud CERRATO static const struct watchdog_info at91_wdt_info = { 157*e6bb42e3SRenaud CERRATO .identity = DRV_NAME, 158*e6bb42e3SRenaud CERRATO .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 159*e6bb42e3SRenaud CERRATO }; 160*e6bb42e3SRenaud CERRATO 161*e6bb42e3SRenaud CERRATO /* 162*e6bb42e3SRenaud CERRATO * Handle commands from user-space. 163*e6bb42e3SRenaud CERRATO */ 164*e6bb42e3SRenaud CERRATO static long at91_wdt_ioctl(struct file *file, 165*e6bb42e3SRenaud CERRATO unsigned int cmd, unsigned long arg) 166*e6bb42e3SRenaud CERRATO { 167*e6bb42e3SRenaud CERRATO void __user *argp = (void __user *)arg; 168*e6bb42e3SRenaud CERRATO int __user *p = argp; 169*e6bb42e3SRenaud CERRATO int new_value; 170*e6bb42e3SRenaud CERRATO 171*e6bb42e3SRenaud CERRATO switch (cmd) { 172*e6bb42e3SRenaud CERRATO case WDIOC_GETSUPPORT: 173*e6bb42e3SRenaud CERRATO return copy_to_user(argp, &at91_wdt_info, 174*e6bb42e3SRenaud CERRATO sizeof(at91_wdt_info)) ? -EFAULT : 0; 175*e6bb42e3SRenaud CERRATO 176*e6bb42e3SRenaud CERRATO case WDIOC_GETSTATUS: 177*e6bb42e3SRenaud CERRATO case WDIOC_GETBOOTSTATUS: 178*e6bb42e3SRenaud CERRATO return put_user(0, p); 179*e6bb42e3SRenaud CERRATO 180*e6bb42e3SRenaud CERRATO case WDIOC_KEEPALIVE: 181*e6bb42e3SRenaud CERRATO at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; 182*e6bb42e3SRenaud CERRATO return 0; 183*e6bb42e3SRenaud CERRATO 184*e6bb42e3SRenaud CERRATO case WDIOC_SETTIMEOUT: 185*e6bb42e3SRenaud CERRATO if (get_user(new_value, p)) 186*e6bb42e3SRenaud CERRATO return -EFAULT; 187*e6bb42e3SRenaud CERRATO 188*e6bb42e3SRenaud CERRATO heartbeat = new_value; 189*e6bb42e3SRenaud CERRATO at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; 190*e6bb42e3SRenaud CERRATO 191*e6bb42e3SRenaud CERRATO return put_user(new_value, p); /* return current value */ 192*e6bb42e3SRenaud CERRATO 193*e6bb42e3SRenaud CERRATO case WDIOC_GETTIMEOUT: 194*e6bb42e3SRenaud CERRATO return put_user(heartbeat, p); 195*e6bb42e3SRenaud CERRATO } 196*e6bb42e3SRenaud CERRATO return -ENOTTY; 197*e6bb42e3SRenaud CERRATO } 198*e6bb42e3SRenaud CERRATO 199*e6bb42e3SRenaud CERRATO /* 200*e6bb42e3SRenaud CERRATO * Pat the watchdog whenever device is written to. 201*e6bb42e3SRenaud CERRATO */ 202*e6bb42e3SRenaud CERRATO static ssize_t at91_wdt_write(struct file *file, const char *data, size_t len, 203*e6bb42e3SRenaud CERRATO loff_t *ppos) 204*e6bb42e3SRenaud CERRATO { 205*e6bb42e3SRenaud CERRATO if (!len) 206*e6bb42e3SRenaud CERRATO return 0; 207*e6bb42e3SRenaud CERRATO 208*e6bb42e3SRenaud CERRATO /* Scan for magic character */ 209*e6bb42e3SRenaud CERRATO if (!nowayout) { 210*e6bb42e3SRenaud CERRATO size_t i; 211*e6bb42e3SRenaud CERRATO 212*e6bb42e3SRenaud CERRATO at91wdt_private.expect_close = 0; 213*e6bb42e3SRenaud CERRATO 214*e6bb42e3SRenaud CERRATO for (i = 0; i < len; i++) { 215*e6bb42e3SRenaud CERRATO char c; 216*e6bb42e3SRenaud CERRATO if (get_user(c, data + i)) 217*e6bb42e3SRenaud CERRATO return -EFAULT; 218*e6bb42e3SRenaud CERRATO if (c == 'V') { 219*e6bb42e3SRenaud CERRATO at91wdt_private.expect_close = 42; 220*e6bb42e3SRenaud CERRATO break; 221*e6bb42e3SRenaud CERRATO } 222*e6bb42e3SRenaud CERRATO } 223*e6bb42e3SRenaud CERRATO } 224*e6bb42e3SRenaud CERRATO 225*e6bb42e3SRenaud CERRATO at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; 226*e6bb42e3SRenaud CERRATO 227*e6bb42e3SRenaud CERRATO return len; 228*e6bb42e3SRenaud CERRATO } 229*e6bb42e3SRenaud CERRATO 230*e6bb42e3SRenaud CERRATO /* ......................................................................... */ 231*e6bb42e3SRenaud CERRATO 232*e6bb42e3SRenaud CERRATO static const struct file_operations at91wdt_fops = { 233*e6bb42e3SRenaud CERRATO .owner = THIS_MODULE, 234*e6bb42e3SRenaud CERRATO .llseek = no_llseek, 235*e6bb42e3SRenaud CERRATO .unlocked_ioctl = at91_wdt_ioctl, 236*e6bb42e3SRenaud CERRATO .open = at91_wdt_open, 237*e6bb42e3SRenaud CERRATO .release = at91_wdt_close, 238*e6bb42e3SRenaud CERRATO .write = at91_wdt_write, 239*e6bb42e3SRenaud CERRATO }; 240*e6bb42e3SRenaud CERRATO 241*e6bb42e3SRenaud CERRATO static struct miscdevice at91wdt_miscdev = { 242*e6bb42e3SRenaud CERRATO .minor = WATCHDOG_MINOR, 243*e6bb42e3SRenaud CERRATO .name = "watchdog", 244*e6bb42e3SRenaud CERRATO .fops = &at91wdt_fops, 245*e6bb42e3SRenaud CERRATO }; 246*e6bb42e3SRenaud CERRATO 247*e6bb42e3SRenaud CERRATO static int __init at91wdt_probe(struct platform_device *pdev) 248*e6bb42e3SRenaud CERRATO { 249*e6bb42e3SRenaud CERRATO int res; 250*e6bb42e3SRenaud CERRATO 251*e6bb42e3SRenaud CERRATO if (at91wdt_miscdev.parent) 252*e6bb42e3SRenaud CERRATO return -EBUSY; 253*e6bb42e3SRenaud CERRATO at91wdt_miscdev.parent = &pdev->dev; 254*e6bb42e3SRenaud CERRATO 255*e6bb42e3SRenaud CERRATO /* Set watchdog */ 256*e6bb42e3SRenaud CERRATO res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000)); 257*e6bb42e3SRenaud CERRATO if (res) 258*e6bb42e3SRenaud CERRATO return res; 259*e6bb42e3SRenaud CERRATO 260*e6bb42e3SRenaud CERRATO res = misc_register(&at91wdt_miscdev); 261*e6bb42e3SRenaud CERRATO if (res) 262*e6bb42e3SRenaud CERRATO return res; 263*e6bb42e3SRenaud CERRATO 264*e6bb42e3SRenaud CERRATO at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; 265*e6bb42e3SRenaud CERRATO setup_timer(&at91wdt_private.timer, at91_ping, 0); 266*e6bb42e3SRenaud CERRATO mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); 267*e6bb42e3SRenaud CERRATO 268*e6bb42e3SRenaud CERRATO printk(KERN_INFO DRV_NAME " enabled (heartbeat=%d sec, nowayout=%d)\n", 269*e6bb42e3SRenaud CERRATO heartbeat, nowayout); 270*e6bb42e3SRenaud CERRATO 271*e6bb42e3SRenaud CERRATO return 0; 272*e6bb42e3SRenaud CERRATO } 273*e6bb42e3SRenaud CERRATO 274*e6bb42e3SRenaud CERRATO static int __exit at91wdt_remove(struct platform_device *pdev) 275*e6bb42e3SRenaud CERRATO { 276*e6bb42e3SRenaud CERRATO int res; 277*e6bb42e3SRenaud CERRATO 278*e6bb42e3SRenaud CERRATO res = misc_deregister(&at91wdt_miscdev); 279*e6bb42e3SRenaud CERRATO if (!res) 280*e6bb42e3SRenaud CERRATO at91wdt_miscdev.parent = NULL; 281*e6bb42e3SRenaud CERRATO 282*e6bb42e3SRenaud CERRATO return res; 283*e6bb42e3SRenaud CERRATO } 284*e6bb42e3SRenaud CERRATO 285*e6bb42e3SRenaud CERRATO #ifdef CONFIG_PM 286*e6bb42e3SRenaud CERRATO 287*e6bb42e3SRenaud CERRATO static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message) 288*e6bb42e3SRenaud CERRATO { 289*e6bb42e3SRenaud CERRATO return 0; 290*e6bb42e3SRenaud CERRATO } 291*e6bb42e3SRenaud CERRATO 292*e6bb42e3SRenaud CERRATO static int at91wdt_resume(struct platform_device *pdev) 293*e6bb42e3SRenaud CERRATO { 294*e6bb42e3SRenaud CERRATO return 0; 295*e6bb42e3SRenaud CERRATO } 296*e6bb42e3SRenaud CERRATO 297*e6bb42e3SRenaud CERRATO #else 298*e6bb42e3SRenaud CERRATO #define at91wdt_suspend NULL 299*e6bb42e3SRenaud CERRATO #define at91wdt_resume NULL 300*e6bb42e3SRenaud CERRATO #endif 301*e6bb42e3SRenaud CERRATO 302*e6bb42e3SRenaud CERRATO static struct platform_driver at91wdt_driver = { 303*e6bb42e3SRenaud CERRATO .remove = __exit_p(at91wdt_remove), 304*e6bb42e3SRenaud CERRATO .suspend = at91wdt_suspend, 305*e6bb42e3SRenaud CERRATO .resume = at91wdt_resume, 306*e6bb42e3SRenaud CERRATO .driver = { 307*e6bb42e3SRenaud CERRATO .name = "at91_wdt", 308*e6bb42e3SRenaud CERRATO .owner = THIS_MODULE, 309*e6bb42e3SRenaud CERRATO }, 310*e6bb42e3SRenaud CERRATO }; 311*e6bb42e3SRenaud CERRATO 312*e6bb42e3SRenaud CERRATO static int __init at91sam_wdt_init(void) 313*e6bb42e3SRenaud CERRATO { 314*e6bb42e3SRenaud CERRATO return platform_driver_probe(&at91wdt_driver, at91wdt_probe); 315*e6bb42e3SRenaud CERRATO } 316*e6bb42e3SRenaud CERRATO 317*e6bb42e3SRenaud CERRATO static void __exit at91sam_wdt_exit(void) 318*e6bb42e3SRenaud CERRATO { 319*e6bb42e3SRenaud CERRATO platform_driver_unregister(&at91wdt_driver); 320*e6bb42e3SRenaud CERRATO } 321*e6bb42e3SRenaud CERRATO 322*e6bb42e3SRenaud CERRATO module_init(at91sam_wdt_init); 323*e6bb42e3SRenaud CERRATO module_exit(at91sam_wdt_exit); 324*e6bb42e3SRenaud CERRATO 325*e6bb42e3SRenaud CERRATO MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>"); 326*e6bb42e3SRenaud CERRATO MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors"); 327*e6bb42e3SRenaud CERRATO MODULE_LICENSE("GPL"); 328*e6bb42e3SRenaud CERRATO MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 329