1/* 2 * Watchdog driver for Atmel AT32AP700X devices 3 * 4 * Copyright (C) 2005-2006 Atmel Corporation 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 * 10 * 11 * Errata: WDT Clear is blocked after WDT Reset 12 * 13 * A watchdog timer event will, after reset, block writes to the WDT_CLEAR 14 * register, preventing the program to clear the next Watchdog Timer Reset. 15 * 16 * If you still want to use the WDT after a WDT reset a small code can be 17 * insterted at the startup checking the AVR32_PM.rcause register for WDT reset 18 * and use a GPIO pin to reset the system. This method requires that one of the 19 * GPIO pins are available and connected externally to the RESET_N pin. After 20 * the GPIO pin has pulled down the reset line the GPIO will be reset and leave 21 * the pin tristated with pullup. 22 */ 23 24#include <linux/init.h> 25#include <linux/kernel.h> 26#include <linux/module.h> 27#include <linux/moduleparam.h> 28#include <linux/miscdevice.h> 29#include <linux/fs.h> 30#include <linux/platform_device.h> 31#include <linux/watchdog.h> 32#include <linux/uaccess.h> 33#include <linux/io.h> 34#include <linux/spinlock.h> 35#include <linux/slab.h> 36 37#define TIMEOUT_MIN 1 38#define TIMEOUT_MAX 2 39#define TIMEOUT_DEFAULT TIMEOUT_MAX 40 41/* module parameters */ 42static int timeout = TIMEOUT_DEFAULT; 43module_param(timeout, int, 0); 44MODULE_PARM_DESC(timeout, 45 "Timeout value. Limited to be 1 or 2 seconds. (default=" 46 __MODULE_STRING(TIMEOUT_DEFAULT) ")"); 47 48static bool nowayout = WATCHDOG_NOWAYOUT; 49module_param(nowayout, bool, 0); 50MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 51 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 52 53/* Watchdog registers and write/read macro */ 54#define WDT_CTRL 0x00 55#define WDT_CTRL_EN 0 56#define WDT_CTRL_PSEL 8 57#define WDT_CTRL_KEY 24 58 59#define WDT_CLR 0x04 60 61#define WDT_RCAUSE 0x10 62#define WDT_RCAUSE_POR 0 63#define WDT_RCAUSE_EXT 2 64#define WDT_RCAUSE_WDT 3 65#define WDT_RCAUSE_JTAG 4 66#define WDT_RCAUSE_SERP 5 67 68#define WDT_BIT(name) (1 << WDT_##name) 69#define WDT_BF(name, value) ((value) << WDT_##name) 70 71#define wdt_readl(dev, reg) \ 72 __raw_readl((dev)->regs + WDT_##reg) 73#define wdt_writel(dev, reg, value) \ 74 __raw_writel((value), (dev)->regs + WDT_##reg) 75 76struct wdt_at32ap700x { 77 void __iomem *regs; 78 spinlock_t io_lock; 79 int timeout; 80 int boot_status; 81 unsigned long users; 82 struct miscdevice miscdev; 83}; 84 85static struct wdt_at32ap700x *wdt; 86static char expect_release; 87 88/* 89 * Disable the watchdog. 90 */ 91static inline void at32_wdt_stop(void) 92{ 93 unsigned long psel; 94 95 spin_lock(&wdt->io_lock); 96 psel = wdt_readl(wdt, CTRL) & WDT_BF(CTRL_PSEL, 0x0f); 97 wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0x55)); 98 wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0xaa)); 99 spin_unlock(&wdt->io_lock); 100} 101 102/* 103 * Enable and reset the watchdog. 104 */ 105static inline void at32_wdt_start(void) 106{ 107 /* 0xf is 2^16 divider = 2 sec, 0xe is 2^15 divider = 1 sec */ 108 unsigned long psel = (wdt->timeout > 1) ? 0xf : 0xe; 109 110 spin_lock(&wdt->io_lock); 111 wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN) 112 | WDT_BF(CTRL_PSEL, psel) 113 | WDT_BF(CTRL_KEY, 0x55)); 114 wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN) 115 | WDT_BF(CTRL_PSEL, psel) 116 | WDT_BF(CTRL_KEY, 0xaa)); 117 spin_unlock(&wdt->io_lock); 118} 119 120/* 121 * Pat the watchdog timer. 122 */ 123static inline void at32_wdt_pat(void) 124{ 125 spin_lock(&wdt->io_lock); 126 wdt_writel(wdt, CLR, 0x42); 127 spin_unlock(&wdt->io_lock); 128} 129 130/* 131 * Watchdog device is opened, and watchdog starts running. 132 */ 133static int at32_wdt_open(struct inode *inode, struct file *file) 134{ 135 if (test_and_set_bit(1, &wdt->users)) 136 return -EBUSY; 137 138 at32_wdt_start(); 139 return nonseekable_open(inode, file); 140} 141 142/* 143 * Close the watchdog device. 144 */ 145static int at32_wdt_close(struct inode *inode, struct file *file) 146{ 147 if (expect_release == 42) { 148 at32_wdt_stop(); 149 } else { 150 dev_dbg(wdt->miscdev.parent, 151 "unexpected close, not stopping watchdog!\n"); 152 at32_wdt_pat(); 153 } 154 clear_bit(1, &wdt->users); 155 expect_release = 0; 156 return 0; 157} 158 159/* 160 * Change the watchdog time interval. 161 */ 162static int at32_wdt_settimeout(int time) 163{ 164 /* 165 * All counting occurs at 1 / SLOW_CLOCK (32 kHz) and max prescaler is 166 * 2 ^ 16 allowing up to 2 seconds timeout. 167 */ 168 if ((time < TIMEOUT_MIN) || (time > TIMEOUT_MAX)) 169 return -EINVAL; 170 171 /* 172 * Set new watchdog time. It will be used when at32_wdt_start() is 173 * called. 174 */ 175 wdt->timeout = time; 176 return 0; 177} 178 179/* 180 * Get the watchdog status. 181 */ 182static int at32_wdt_get_status(void) 183{ 184 int rcause; 185 int status = 0; 186 187 rcause = wdt_readl(wdt, RCAUSE); 188 189 switch (rcause) { 190 case WDT_BIT(RCAUSE_EXT): 191 status = WDIOF_EXTERN1; 192 break; 193 case WDT_BIT(RCAUSE_WDT): 194 status = WDIOF_CARDRESET; 195 break; 196 case WDT_BIT(RCAUSE_POR): /* fall through */ 197 case WDT_BIT(RCAUSE_JTAG): /* fall through */ 198 case WDT_BIT(RCAUSE_SERP): /* fall through */ 199 default: 200 break; 201 } 202 203 return status; 204} 205 206static const struct watchdog_info at32_wdt_info = { 207 .identity = "at32ap700x watchdog", 208 .options = WDIOF_SETTIMEOUT | 209 WDIOF_KEEPALIVEPING | 210 WDIOF_MAGICCLOSE, 211}; 212 213/* 214 * Handle commands from user-space. 215 */ 216static long at32_wdt_ioctl(struct file *file, 217 unsigned int cmd, unsigned long arg) 218{ 219 int ret = -ENOTTY; 220 int time; 221 void __user *argp = (void __user *)arg; 222 int __user *p = argp; 223 224 switch (cmd) { 225 case WDIOC_GETSUPPORT: 226 ret = copy_to_user(argp, &at32_wdt_info, 227 sizeof(at32_wdt_info)) ? -EFAULT : 0; 228 break; 229 case WDIOC_GETSTATUS: 230 ret = put_user(0, p); 231 break; 232 case WDIOC_GETBOOTSTATUS: 233 ret = put_user(wdt->boot_status, p); 234 break; 235 case WDIOC_SETOPTIONS: 236 ret = get_user(time, p); 237 if (ret) 238 break; 239 if (time & WDIOS_DISABLECARD) 240 at32_wdt_stop(); 241 if (time & WDIOS_ENABLECARD) 242 at32_wdt_start(); 243 ret = 0; 244 break; 245 case WDIOC_KEEPALIVE: 246 at32_wdt_pat(); 247 ret = 0; 248 break; 249 case WDIOC_SETTIMEOUT: 250 ret = get_user(time, p); 251 if (ret) 252 break; 253 ret = at32_wdt_settimeout(time); 254 if (ret) 255 break; 256 /* Enable new time value */ 257 at32_wdt_start(); 258 /* fall through */ 259 case WDIOC_GETTIMEOUT: 260 ret = put_user(wdt->timeout, p); 261 break; 262 } 263 264 return ret; 265} 266 267static ssize_t at32_wdt_write(struct file *file, const char __user *data, 268 size_t len, loff_t *ppos) 269{ 270 /* See if we got the magic character 'V' and reload the timer */ 271 if (len) { 272 if (!nowayout) { 273 size_t i; 274 275 /* 276 * note: just in case someone wrote the magic 277 * character five months ago... 278 */ 279 expect_release = 0; 280 281 /* 282 * scan to see whether or not we got the magic 283 * character 284 */ 285 for (i = 0; i != len; i++) { 286 char c; 287 if (get_user(c, data + i)) 288 return -EFAULT; 289 if (c == 'V') 290 expect_release = 42; 291 } 292 } 293 /* someone wrote to us, we should pat the watchdog */ 294 at32_wdt_pat(); 295 } 296 return len; 297} 298 299static const struct file_operations at32_wdt_fops = { 300 .owner = THIS_MODULE, 301 .llseek = no_llseek, 302 .unlocked_ioctl = at32_wdt_ioctl, 303 .open = at32_wdt_open, 304 .release = at32_wdt_close, 305 .write = at32_wdt_write, 306}; 307 308static int __init at32_wdt_probe(struct platform_device *pdev) 309{ 310 struct resource *regs; 311 int ret; 312 313 if (wdt) { 314 dev_dbg(&pdev->dev, "only 1 wdt instance supported.\n"); 315 return -EBUSY; 316 } 317 318 regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); 319 if (!regs) { 320 dev_dbg(&pdev->dev, "missing mmio resource\n"); 321 return -ENXIO; 322 } 323 324 wdt = devm_kzalloc(&pdev->dev, sizeof(struct wdt_at32ap700x), 325 GFP_KERNEL); 326 if (!wdt) 327 return -ENOMEM; 328 329 wdt->regs = devm_ioremap(&pdev->dev, regs->start, resource_size(regs)); 330 if (!wdt->regs) { 331 ret = -ENOMEM; 332 dev_dbg(&pdev->dev, "could not map I/O memory\n"); 333 goto err_free; 334 } 335 336 spin_lock_init(&wdt->io_lock); 337 wdt->boot_status = at32_wdt_get_status(); 338 339 /* Work-around for watchdog silicon errata. */ 340 if (wdt->boot_status & WDIOF_CARDRESET) { 341 dev_info(&pdev->dev, "CPU must be reset with external " 342 "reset or POR due to silicon errata.\n"); 343 ret = -EIO; 344 goto err_free; 345 } else { 346 wdt->users = 0; 347 } 348 349 wdt->miscdev.minor = WATCHDOG_MINOR; 350 wdt->miscdev.name = "watchdog"; 351 wdt->miscdev.fops = &at32_wdt_fops; 352 wdt->miscdev.parent = &pdev->dev; 353 354 platform_set_drvdata(pdev, wdt); 355 356 if (at32_wdt_settimeout(timeout)) { 357 at32_wdt_settimeout(TIMEOUT_DEFAULT); 358 dev_dbg(&pdev->dev, 359 "default timeout invalid, set to %d sec.\n", 360 TIMEOUT_DEFAULT); 361 } 362 363 ret = misc_register(&wdt->miscdev); 364 if (ret) { 365 dev_dbg(&pdev->dev, "failed to register wdt miscdev\n"); 366 goto err_free; 367 } 368 369 dev_info(&pdev->dev, 370 "AT32AP700X WDT at 0x%p, timeout %d sec (nowayout=%d)\n", 371 wdt->regs, wdt->timeout, nowayout); 372 373 return 0; 374 375err_free: 376 wdt = NULL; 377 return ret; 378} 379 380static int __exit at32_wdt_remove(struct platform_device *pdev) 381{ 382 if (wdt && platform_get_drvdata(pdev) == wdt) { 383 /* Stop the timer before we leave */ 384 if (!nowayout) 385 at32_wdt_stop(); 386 387 misc_deregister(&wdt->miscdev); 388 wdt = NULL; 389 } 390 return 0; 391} 392 393static void at32_wdt_shutdown(struct platform_device *pdev) 394{ 395 at32_wdt_stop(); 396} 397 398#ifdef CONFIG_PM 399static int at32_wdt_suspend(struct platform_device *pdev, pm_message_t message) 400{ 401 at32_wdt_stop(); 402 return 0; 403} 404 405static int at32_wdt_resume(struct platform_device *pdev) 406{ 407 if (wdt->users) 408 at32_wdt_start(); 409 return 0; 410} 411#else 412#define at32_wdt_suspend NULL 413#define at32_wdt_resume NULL 414#endif 415 416/* work with hotplug and coldplug */ 417MODULE_ALIAS("platform:at32_wdt"); 418 419static struct platform_driver at32_wdt_driver = { 420 .remove = __exit_p(at32_wdt_remove), 421 .suspend = at32_wdt_suspend, 422 .resume = at32_wdt_resume, 423 .driver = { 424 .name = "at32_wdt", 425 }, 426 .shutdown = at32_wdt_shutdown, 427}; 428 429module_platform_driver_probe(at32_wdt_driver, at32_wdt_probe); 430 431MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>"); 432MODULE_DESCRIPTION("Watchdog driver for Atmel AT32AP700X"); 433MODULE_LICENSE("GPL"); 434