1/* linux/drivers/char/watchdog/s3c2410_wdt.c 2 * 3 * Copyright (c) 2004 Simtec Electronics 4 * Ben Dooks <ben@simtec.co.uk> 5 * 6 * S3C2410 Watchdog Timer Support 7 * 8 * Based on, softdog.c by Alan Cox, 9 * (c) Copyright 1996 Alan Cox <alan@redhat.com> 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; if not, write to the Free Software 23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24 * 25 * Changelog: 26 * 05-Oct-2004 BJD Added semaphore init to stop crashes on open 27 * Fixed tmr_count / wdt_count confusion 28 * Added configurable debug 29 * 30 * 11-Jan-2005 BJD Fixed divide-by-2 in timeout code 31 * 32 * 25-Jan-2005 DA Added suspend/resume support 33 * Replaced reboot notifier with .shutdown method 34 * 35 * 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA 36*/ 37 38#include <linux/module.h> 39#include <linux/moduleparam.h> 40#include <linux/types.h> 41#include <linux/timer.h> 42#include <linux/miscdevice.h> 43#include <linux/watchdog.h> 44#include <linux/fs.h> 45#include <linux/init.h> 46#include <linux/platform_device.h> 47#include <linux/interrupt.h> 48#include <linux/clk.h> 49 50#include <asm/uaccess.h> 51#include <asm/io.h> 52 53#include <asm/arch/map.h> 54 55#undef S3C24XX_VA_WATCHDOG 56#define S3C24XX_VA_WATCHDOG (0) 57 58#include <asm/arch/regs-watchdog.h> 59 60#define PFX "s3c2410-wdt: " 61 62#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) 63#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) 64 65static int nowayout = WATCHDOG_NOWAYOUT; 66static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; 67static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; 68static int soft_noboot = 0; 69static int debug = 0; 70 71module_param(tmr_margin, int, 0); 72module_param(tmr_atboot, int, 0); 73module_param(nowayout, int, 0); 74module_param(soft_noboot, int, 0); 75module_param(debug, int, 0); 76 77MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); 78 79MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); 80 81MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 82 83MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); 84 85MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)"); 86 87 88typedef enum close_state { 89 CLOSE_STATE_NOT, 90 CLOSE_STATE_ALLOW=0x4021 91} close_state_t; 92 93static DECLARE_MUTEX(open_lock); 94 95static struct resource *wdt_mem; 96static struct resource *wdt_irq; 97static struct clk *wdt_clock; 98static void __iomem *wdt_base; 99static unsigned int wdt_count; 100static close_state_t allow_close; 101 102/* watchdog control routines */ 103 104#define DBG(msg...) do { \ 105 if (debug) \ 106 printk(KERN_INFO msg); \ 107 } while(0) 108 109/* functions */ 110 111static int s3c2410wdt_keepalive(void) 112{ 113 writel(wdt_count, wdt_base + S3C2410_WTCNT); 114 return 0; 115} 116 117static int s3c2410wdt_stop(void) 118{ 119 unsigned long wtcon; 120 121 wtcon = readl(wdt_base + S3C2410_WTCON); 122 wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); 123 writel(wtcon, wdt_base + S3C2410_WTCON); 124 125 return 0; 126} 127 128static int s3c2410wdt_start(void) 129{ 130 unsigned long wtcon; 131 132 s3c2410wdt_stop(); 133 134 wtcon = readl(wdt_base + S3C2410_WTCON); 135 wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; 136 137 if (soft_noboot) { 138 wtcon |= S3C2410_WTCON_INTEN; 139 wtcon &= ~S3C2410_WTCON_RSTEN; 140 } else { 141 wtcon &= ~S3C2410_WTCON_INTEN; 142 wtcon |= S3C2410_WTCON_RSTEN; 143 } 144 145 DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n", 146 __FUNCTION__, wdt_count, wtcon); 147 148 writel(wdt_count, wdt_base + S3C2410_WTDAT); 149 writel(wdt_count, wdt_base + S3C2410_WTCNT); 150 writel(wtcon, wdt_base + S3C2410_WTCON); 151 152 return 0; 153} 154 155static int s3c2410wdt_set_heartbeat(int timeout) 156{ 157 unsigned int freq = clk_get_rate(wdt_clock); 158 unsigned int count; 159 unsigned int divisor = 1; 160 unsigned long wtcon; 161 162 if (timeout < 1) 163 return -EINVAL; 164 165 freq /= 128; 166 count = timeout * freq; 167 168 DBG("%s: count=%d, timeout=%d, freq=%d\n", 169 __FUNCTION__, count, timeout, freq); 170 171 /* if the count is bigger than the watchdog register, 172 then work out what we need to do (and if) we can 173 actually make this value 174 */ 175 176 if (count >= 0x10000) { 177 for (divisor = 1; divisor <= 0x100; divisor++) { 178 if ((count / divisor) < 0x10000) 179 break; 180 } 181 182 if ((count / divisor) >= 0x10000) { 183 printk(KERN_ERR PFX "timeout %d too big\n", timeout); 184 return -EINVAL; 185 } 186 } 187 188 tmr_margin = timeout; 189 190 DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", 191 __FUNCTION__, timeout, divisor, count, count/divisor); 192 193 count /= divisor; 194 wdt_count = count; 195 196 /* update the pre-scaler */ 197 wtcon = readl(wdt_base + S3C2410_WTCON); 198 wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; 199 wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); 200 201 writel(count, wdt_base + S3C2410_WTDAT); 202 writel(wtcon, wdt_base + S3C2410_WTCON); 203 204 return 0; 205} 206 207/* 208 * /dev/watchdog handling 209 */ 210 211static int s3c2410wdt_open(struct inode *inode, struct file *file) 212{ 213 if(down_trylock(&open_lock)) 214 return -EBUSY; 215 216 if (nowayout) 217 __module_get(THIS_MODULE); 218 219 allow_close = CLOSE_STATE_NOT; 220 221 /* start the timer */ 222 s3c2410wdt_start(); 223 return nonseekable_open(inode, file); 224} 225 226static int s3c2410wdt_release(struct inode *inode, struct file *file) 227{ 228 /* 229 * Shut off the timer. 230 * Lock it in if it's a module and we set nowayout 231 */ 232 233 if (allow_close == CLOSE_STATE_ALLOW) { 234 s3c2410wdt_stop(); 235 } else { 236 printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); 237 s3c2410wdt_keepalive(); 238 } 239 240 allow_close = CLOSE_STATE_NOT; 241 up(&open_lock); 242 return 0; 243} 244 245static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, 246 size_t len, loff_t *ppos) 247{ 248 /* 249 * Refresh the timer. 250 */ 251 if(len) { 252 if (!nowayout) { 253 size_t i; 254 255 /* In case it was set long ago */ 256 allow_close = CLOSE_STATE_NOT; 257 258 for (i = 0; i != len; i++) { 259 char c; 260 261 if (get_user(c, data + i)) 262 return -EFAULT; 263 if (c == 'V') 264 allow_close = CLOSE_STATE_ALLOW; 265 } 266 } 267 268 s3c2410wdt_keepalive(); 269 } 270 return len; 271} 272 273#define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE 274 275static struct watchdog_info s3c2410_wdt_ident = { 276 .options = OPTIONS, 277 .firmware_version = 0, 278 .identity = "S3C2410 Watchdog", 279}; 280 281 282static int s3c2410wdt_ioctl(struct inode *inode, struct file *file, 283 unsigned int cmd, unsigned long arg) 284{ 285 void __user *argp = (void __user *)arg; 286 int __user *p = argp; 287 int new_margin; 288 289 switch (cmd) { 290 default: 291 return -ENOTTY; 292 293 case WDIOC_GETSUPPORT: 294 return copy_to_user(argp, &s3c2410_wdt_ident, 295 sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0; 296 297 case WDIOC_GETSTATUS: 298 case WDIOC_GETBOOTSTATUS: 299 return put_user(0, p); 300 301 case WDIOC_KEEPALIVE: 302 s3c2410wdt_keepalive(); 303 return 0; 304 305 case WDIOC_SETTIMEOUT: 306 if (get_user(new_margin, p)) 307 return -EFAULT; 308 309 if (s3c2410wdt_set_heartbeat(new_margin)) 310 return -EINVAL; 311 312 s3c2410wdt_keepalive(); 313 return put_user(tmr_margin, p); 314 315 case WDIOC_GETTIMEOUT: 316 return put_user(tmr_margin, p); 317 } 318} 319 320/* kernel interface */ 321 322static const struct file_operations s3c2410wdt_fops = { 323 .owner = THIS_MODULE, 324 .llseek = no_llseek, 325 .write = s3c2410wdt_write, 326 .ioctl = s3c2410wdt_ioctl, 327 .open = s3c2410wdt_open, 328 .release = s3c2410wdt_release, 329}; 330 331static struct miscdevice s3c2410wdt_miscdev = { 332 .minor = WATCHDOG_MINOR, 333 .name = "watchdog", 334 .fops = &s3c2410wdt_fops, 335}; 336 337/* interrupt handler code */ 338 339static irqreturn_t s3c2410wdt_irq(int irqno, void *param) 340{ 341 printk(KERN_INFO PFX "Watchdog timer expired!\n"); 342 343 s3c2410wdt_keepalive(); 344 return IRQ_HANDLED; 345} 346/* device interface */ 347 348static int s3c2410wdt_probe(struct platform_device *pdev) 349{ 350 struct resource *res; 351 int started = 0; 352 int ret; 353 int size; 354 355 DBG("%s: probe=%p\n", __FUNCTION__, pdev); 356 357 /* get the memory region for the watchdog timer */ 358 359 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 360 if (res == NULL) { 361 printk(KERN_INFO PFX "failed to get memory region resouce\n"); 362 return -ENOENT; 363 } 364 365 size = (res->end-res->start)+1; 366 wdt_mem = request_mem_region(res->start, size, pdev->name); 367 if (wdt_mem == NULL) { 368 printk(KERN_INFO PFX "failed to get memory region\n"); 369 ret = -ENOENT; 370 goto err_req; 371 } 372 373 wdt_base = ioremap(res->start, size); 374 if (wdt_base == 0) { 375 printk(KERN_INFO PFX "failed to ioremap() region\n"); 376 ret = -EINVAL; 377 goto err_req; 378 } 379 380 DBG("probe: mapped wdt_base=%p\n", wdt_base); 381 382 wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 383 if (wdt_irq == NULL) { 384 printk(KERN_INFO PFX "failed to get irq resource\n"); 385 ret = -ENOENT; 386 goto err_map; 387 } 388 389 ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev); 390 if (ret != 0) { 391 printk(KERN_INFO PFX "failed to install irq (%d)\n", ret); 392 goto err_map; 393 } 394 395 wdt_clock = clk_get(&pdev->dev, "watchdog"); 396 if (IS_ERR(wdt_clock)) { 397 printk(KERN_INFO PFX "failed to find watchdog clock source\n"); 398 ret = PTR_ERR(wdt_clock); 399 goto err_irq; 400 } 401 402 clk_enable(wdt_clock); 403 404 /* see if we can actually set the requested timer margin, and if 405 * not, try the default value */ 406 407 if (s3c2410wdt_set_heartbeat(tmr_margin)) { 408 started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); 409 410 if (started == 0) { 411 printk(KERN_INFO PFX "tmr_margin value out of range, default %d used\n", 412 CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); 413 } else { 414 printk(KERN_INFO PFX "default timer value is out of range, cannot start\n"); 415 } 416 } 417 418 ret = misc_register(&s3c2410wdt_miscdev); 419 if (ret) { 420 printk (KERN_ERR PFX "cannot register miscdev on minor=%d (%d)\n", 421 WATCHDOG_MINOR, ret); 422 goto err_clk; 423 } 424 425 if (tmr_atboot && started == 0) { 426 printk(KERN_INFO PFX "Starting Watchdog Timer\n"); 427 s3c2410wdt_start(); 428 } else if (!tmr_atboot) { 429 /* if we're not enabling the watchdog, then ensure it is 430 * disabled if it has been left running from the bootloader 431 * or other source */ 432 433 s3c2410wdt_stop(); 434 } 435 436 return 0; 437 438 err_clk: 439 clk_disable(wdt_clock); 440 clk_put(wdt_clock); 441 442 err_irq: 443 free_irq(wdt_irq->start, pdev); 444 445 err_map: 446 iounmap(wdt_base); 447 448 err_req: 449 release_resource(wdt_mem); 450 kfree(wdt_mem); 451 452 return ret; 453} 454 455static int s3c2410wdt_remove(struct platform_device *dev) 456{ 457 release_resource(wdt_mem); 458 kfree(wdt_mem); 459 wdt_mem = NULL; 460 461 free_irq(wdt_irq->start, dev); 462 wdt_irq = NULL; 463 464 clk_disable(wdt_clock); 465 clk_put(wdt_clock); 466 wdt_clock = NULL; 467 468 iounmap(wdt_base); 469 misc_deregister(&s3c2410wdt_miscdev); 470 return 0; 471} 472 473static void s3c2410wdt_shutdown(struct platform_device *dev) 474{ 475 s3c2410wdt_stop(); 476} 477 478#ifdef CONFIG_PM 479 480static unsigned long wtcon_save; 481static unsigned long wtdat_save; 482 483static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state) 484{ 485 /* Save watchdog state, and turn it off. */ 486 wtcon_save = readl(wdt_base + S3C2410_WTCON); 487 wtdat_save = readl(wdt_base + S3C2410_WTDAT); 488 489 /* Note that WTCNT doesn't need to be saved. */ 490 s3c2410wdt_stop(); 491 492 return 0; 493} 494 495static int s3c2410wdt_resume(struct platform_device *dev) 496{ 497 /* Restore watchdog state. */ 498 499 writel(wtdat_save, wdt_base + S3C2410_WTDAT); 500 writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */ 501 writel(wtcon_save, wdt_base + S3C2410_WTCON); 502 503 printk(KERN_INFO PFX "watchdog %sabled\n", 504 (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); 505 506 return 0; 507} 508 509#else 510#define s3c2410wdt_suspend NULL 511#define s3c2410wdt_resume NULL 512#endif /* CONFIG_PM */ 513 514 515static struct platform_driver s3c2410wdt_driver = { 516 .probe = s3c2410wdt_probe, 517 .remove = s3c2410wdt_remove, 518 .shutdown = s3c2410wdt_shutdown, 519 .suspend = s3c2410wdt_suspend, 520 .resume = s3c2410wdt_resume, 521 .driver = { 522 .owner = THIS_MODULE, 523 .name = "s3c2410-wdt", 524 }, 525}; 526 527 528static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; 529 530static int __init watchdog_init(void) 531{ 532 printk(banner); 533 return platform_driver_register(&s3c2410wdt_driver); 534} 535 536static void __exit watchdog_exit(void) 537{ 538 platform_driver_unregister(&s3c2410wdt_driver); 539} 540 541module_init(watchdog_init); 542module_exit(watchdog_exit); 543 544MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, " 545 "Dimitry Andric <dimitry.andric@tomtom.com>"); 546MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); 547MODULE_LICENSE("GPL"); 548MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 549