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