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