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