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