Linux Audio

Check our new training course

Loading...
v3.1
  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 device *dev;
 
 
 
406	unsigned int wtcon;
407	int started = 0;
408	int ret;
409	int size;
410
411	DBG("%s: probe=%p\n", __func__, pdev);
412
413	dev = &pdev->dev;
414	wdt_dev = &pdev->dev;
415
416	/* get the memory region for the watchdog timer */
417
418	wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
419	if (wdt_mem == NULL) {
420		dev_err(dev, "no memory resource specified\n");
421		return -ENOENT;
422	}
423
424	size = resource_size(wdt_mem);
425	if (!request_mem_region(wdt_mem->start, size, pdev->name)) {
426		dev_err(dev, "failed to get memory region\n");
427		return -EBUSY;
428	}
429
430	wdt_base = ioremap(wdt_mem->start, size);
431	if (wdt_base == NULL) {
432		dev_err(dev, "failed to ioremap() region\n");
433		ret = -EINVAL;
434		goto err_req;
435	}
436
437	DBG("probe: mapped wdt_base=%p\n", wdt_base);
438
439	wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
440	if (wdt_irq == NULL) {
441		dev_err(dev, "no irq resource specified\n");
442		ret = -ENOENT;
443		goto err_map;
444	}
445
446	ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
447	if (ret != 0) {
448		dev_err(dev, "failed to install irq (%d)\n", ret);
449		goto err_map;
 
 
450	}
451
452	wdt_clock = clk_get(&pdev->dev, "watchdog");
453	if (IS_ERR(wdt_clock)) {
 
 
454		dev_err(dev, "failed to find watchdog clock source\n");
455		ret = PTR_ERR(wdt_clock);
456		goto err_irq;
457	}
458
459	clk_enable(wdt_clock);
 
 
 
 
460
461	if (s3c2410wdt_cpufreq_register() < 0) {
462		printk(KERN_ERR PFX "failed to register cpufreq\n");
 
463		goto err_clk;
464	}
465
 
 
466	/* see if we can actually set the requested timer margin, and if
467	 * not, try the default value */
468
469	if (s3c2410wdt_set_heartbeat(tmr_margin)) {
470		started = s3c2410wdt_set_heartbeat(
 
 
 
471					CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
472
473		if (started == 0)
474			dev_info(dev,
475			   "tmr_margin value out of range, default %d used\n",
476			       CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
477		else
478			dev_info(dev, "default timer value is out of range, "
479							"cannot start\n");
480	}
481
482	ret = misc_register(&s3c2410wdt_miscdev);
 
 
 
 
 
 
 
 
 
 
 
483	if (ret) {
484		dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
485			WATCHDOG_MINOR, ret);
486		goto err_cpufreq;
487	}
488
 
 
 
 
489	if (tmr_atboot && started == 0) {
490		dev_info(dev, "starting watchdog timer\n");
491		s3c2410wdt_start();
492	} else if (!tmr_atboot) {
493		/* if we're not enabling the watchdog, then ensure it is
494		 * disabled if it has been left running from the bootloader
495		 * or other source */
496
497		s3c2410wdt_stop();
498	}
499
 
 
500	/* print out a statement of readiness */
501
502	wtcon = readl(wdt_base + S3C2410_WTCON);
503
504	dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
505		 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",
506		 (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",
507		 (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
508
509	return 0;
510
 
 
 
511 err_cpufreq:
512	s3c2410wdt_cpufreq_deregister();
513
514 err_clk:
515	clk_disable(wdt_clock);
516	clk_put(wdt_clock);
517
518 err_irq:
519	free_irq(wdt_irq->start, pdev);
520
521 err_map:
522	iounmap(wdt_base);
523
524 err_req:
525	release_mem_region(wdt_mem->start, size);
526	wdt_mem = NULL;
527
 
528	return ret;
529}
530
531static int __devexit s3c2410wdt_remove(struct platform_device *dev)
532{
533	misc_deregister(&s3c2410wdt_miscdev);
 
534
535	s3c2410wdt_cpufreq_deregister();
 
 
536
537	clk_disable(wdt_clock);
538	clk_put(wdt_clock);
539	wdt_clock = NULL;
540
541	free_irq(wdt_irq->start, dev);
542	wdt_irq = NULL;
543
544	iounmap(wdt_base);
545
546	release_mem_region(wdt_mem->start, resource_size(wdt_mem));
547	wdt_mem = NULL;
548	return 0;
549}
550
551static void s3c2410wdt_shutdown(struct platform_device *dev)
552{
553	s3c2410wdt_stop();
554}
555
556#ifdef CONFIG_PM
557
558static unsigned long wtcon_save;
559static unsigned long wtdat_save;
560
561static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state)
 
 
562{
 
 
 
563	/* Save watchdog state, and turn it off. */
564	wtcon_save = readl(wdt_base + S3C2410_WTCON);
565	wtdat_save = readl(wdt_base + S3C2410_WTDAT);
 
 
 
 
566
567	/* Note that WTCNT doesn't need to be saved. */
568	s3c2410wdt_stop();
569
570	return 0;
571}
572
573static int s3c2410wdt_resume(struct platform_device *dev)
574{
575	/* Restore watchdog state. */
 
576
577	writel(wtdat_save, wdt_base + S3C2410_WTDAT);
578	writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */
579	writel(wtcon_save, wdt_base + S3C2410_WTCON);
 
 
 
 
 
580
581	printk(KERN_INFO PFX "watchdog %sabled\n",
582	       (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
583
584	return 0;
585}
586
587#else
588#define s3c2410wdt_suspend NULL
589#define s3c2410wdt_resume  NULL
590#endif /* CONFIG_PM */
591
592#ifdef CONFIG_OF
593static const struct of_device_id s3c2410_wdt_match[] = {
594	{ .compatible = "samsung,s3c2410-wdt" },
595	{},
596};
597MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
598#else
599#define s3c2410_wdt_match NULL
600#endif
601
 
 
 
602static struct platform_driver s3c2410wdt_driver = {
603	.probe		= s3c2410wdt_probe,
604	.remove		= __devexit_p(s3c2410wdt_remove),
605	.shutdown	= s3c2410wdt_shutdown,
606	.suspend	= s3c2410wdt_suspend,
607	.resume		= s3c2410wdt_resume,
608	.driver		= {
609		.owner	= THIS_MODULE,
610		.name	= "s3c2410-wdt",
611		.of_match_table	= s3c2410_wdt_match,
 
612	},
613};
614
615
616static char banner[] __initdata =
617	KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
618
619static int __init watchdog_init(void)
620{
621	printk(banner);
622	return platform_driver_register(&s3c2410wdt_driver);
623}
624
625static void __exit watchdog_exit(void)
626{
627	platform_driver_unregister(&s3c2410wdt_driver);
628}
629
630module_init(watchdog_init);
631module_exit(watchdog_exit);
632
633MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, "
634	      "Dimitry Andric <dimitry.andric@tomtom.com>");
635MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
636MODULE_LICENSE("GPL");
637MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
638MODULE_ALIAS("platform:s3c2410-wdt");
v3.15
  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#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 27
 28#include <linux/module.h>
 29#include <linux/moduleparam.h>
 30#include <linux/types.h>
 31#include <linux/timer.h>
 
 32#include <linux/watchdog.h>
 
 
 33#include <linux/platform_device.h>
 34#include <linux/interrupt.h>
 35#include <linux/clk.h>
 36#include <linux/uaccess.h>
 37#include <linux/io.h>
 38#include <linux/cpufreq.h>
 39#include <linux/slab.h>
 40#include <linux/err.h>
 41#include <linux/of.h>
 42#include <linux/mfd/syscon.h>
 43#include <linux/regmap.h>
 44
 45#define S3C2410_WTCON		0x00
 46#define S3C2410_WTDAT		0x04
 47#define S3C2410_WTCNT		0x08
 48
 49#define S3C2410_WTCON_RSTEN	(1 << 0)
 50#define S3C2410_WTCON_INTEN	(1 << 2)
 51#define S3C2410_WTCON_ENABLE	(1 << 5)
 52
 53#define S3C2410_WTCON_DIV16	(0 << 3)
 54#define S3C2410_WTCON_DIV32	(1 << 3)
 55#define S3C2410_WTCON_DIV64	(2 << 3)
 56#define S3C2410_WTCON_DIV128	(3 << 3)
 57
 58#define S3C2410_WTCON_PRESCALE(x)	((x) << 8)
 59#define S3C2410_WTCON_PRESCALE_MASK	(0xff << 8)
 
 
 
 
 
 
 60
 61#define CONFIG_S3C2410_WATCHDOG_ATBOOT		(0)
 62#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME	(15)
 63
 64#define EXYNOS5_RST_STAT_REG_OFFSET		0x0404
 65#define EXYNOS5_WDT_DISABLE_REG_OFFSET		0x0408
 66#define EXYNOS5_WDT_MASK_RESET_REG_OFFSET	0x040c
 67#define QUIRK_HAS_PMU_CONFIG			(1 << 0)
 68#define QUIRK_HAS_RST_STAT			(1 << 1)
 69
 70/* These quirks require that we have a PMU register map */
 71#define QUIRKS_HAVE_PMUREG			(QUIRK_HAS_PMU_CONFIG | \
 72						 QUIRK_HAS_RST_STAT)
 73
 74static bool nowayout	= WATCHDOG_NOWAYOUT;
 75static int tmr_margin;
 76static int tmr_atboot	= CONFIG_S3C2410_WATCHDOG_ATBOOT;
 77static int soft_noboot;
 78static int debug;
 79
 80module_param(tmr_margin,  int, 0);
 81module_param(tmr_atboot,  int, 0);
 82module_param(nowayout,   bool, 0);
 83module_param(soft_noboot, int, 0);
 84module_param(debug,	  int, 0);
 85
 86MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default="
 87		__MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")");
 88MODULE_PARM_DESC(tmr_atboot,
 89		"Watchdog is started at boot time if set to 1, default="
 90			__MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT));
 91MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
 92			__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 93MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, "
 94			"0 to reboot (default 0)");
 95MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)");
 96
 97/**
 98 * struct s3c2410_wdt_variant - Per-variant config data
 99 *
100 * @disable_reg: Offset in pmureg for the register that disables the watchdog
101 * timer reset functionality.
102 * @mask_reset_reg: Offset in pmureg for the register that masks the watchdog
103 * timer reset functionality.
104 * @mask_bit: Bit number for the watchdog timer in the disable register and the
105 * mask reset register.
106 * @rst_stat_reg: Offset in pmureg for the register that has the reset status.
107 * @rst_stat_bit: Bit number in the rst_stat register indicating a watchdog
108 * reset.
109 * @quirks: A bitfield of quirks.
110 */
111
112struct s3c2410_wdt_variant {
113	int disable_reg;
114	int mask_reset_reg;
115	int mask_bit;
116	int rst_stat_reg;
117	int rst_stat_bit;
118	u32 quirks;
119};
120
121struct s3c2410_wdt {
122	struct device		*dev;
123	struct clk		*clock;
124	void __iomem		*reg_base;
125	unsigned int		count;
126	spinlock_t		lock;
127	unsigned long		wtcon_save;
128	unsigned long		wtdat_save;
129	struct watchdog_device	wdt_device;
130	struct notifier_block	freq_transition;
131	struct s3c2410_wdt_variant *drv_data;
132	struct regmap *pmureg;
133};
134
135static const struct s3c2410_wdt_variant drv_data_s3c2410 = {
136	.quirks = 0
137};
138
139#ifdef CONFIG_OF
140static const struct s3c2410_wdt_variant drv_data_exynos5250  = {
141	.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
142	.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
143	.mask_bit = 20,
144	.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
145	.rst_stat_bit = 20,
146	.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT,
147};
148
149static const struct s3c2410_wdt_variant drv_data_exynos5420 = {
150	.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
151	.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
152	.mask_bit = 0,
153	.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
154	.rst_stat_bit = 9,
155	.quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT,
156};
157
158static const struct of_device_id s3c2410_wdt_match[] = {
159	{ .compatible = "samsung,s3c2410-wdt",
160	  .data = &drv_data_s3c2410 },
161	{ .compatible = "samsung,exynos5250-wdt",
162	  .data = &drv_data_exynos5250 },
163	{ .compatible = "samsung,exynos5420-wdt",
164	  .data = &drv_data_exynos5420 },
165	{},
166};
167MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
168#endif
169
170static const struct platform_device_id s3c2410_wdt_ids[] = {
171	{
172		.name = "s3c2410-wdt",
173		.driver_data = (unsigned long)&drv_data_s3c2410,
174	},
175	{}
176};
177MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids);
178
179/* watchdog control routines */
180
181#define DBG(fmt, ...)					\
182do {							\
183	if (debug)					\
184		pr_info(fmt, ##__VA_ARGS__);		\
185} while (0)
186
187/* functions */
188
189static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb)
190{
191	return container_of(nb, struct s3c2410_wdt, freq_transition);
 
 
192}
193
194static int s3c2410wdt_mask_and_disable_reset(struct s3c2410_wdt *wdt, bool mask)
195{
196	int ret;
197	u32 mask_val = 1 << wdt->drv_data->mask_bit;
198	u32 val = 0;
199
200	/* No need to do anything if no PMU CONFIG needed */
201	if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG))
202		return 0;
203
204	if (mask)
205		val = mask_val;
206
207	ret = regmap_update_bits(wdt->pmureg,
208			wdt->drv_data->disable_reg,
209			mask_val, val);
210	if (ret < 0)
211		goto error;
212
213	ret = regmap_update_bits(wdt->pmureg,
214			wdt->drv_data->mask_reset_reg,
215			mask_val, val);
216 error:
217	if (ret < 0)
218		dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
219
220	return ret;
221}
222
223static int s3c2410wdt_keepalive(struct watchdog_device *wdd)
224{
225	struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
226
227	spin_lock(&wdt->lock);
228	writel(wdt->count, wdt->reg_base + S3C2410_WTCNT);
229	spin_unlock(&wdt->lock);
230
231	return 0;
232}
233
234static void __s3c2410wdt_stop(struct s3c2410_wdt *wdt)
235{
236	unsigned long wtcon;
237
238	wtcon = readl(wdt->reg_base + S3C2410_WTCON);
239	wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
240	writel(wtcon, wdt->reg_base + S3C2410_WTCON);
241}
242
243static int s3c2410wdt_stop(struct watchdog_device *wdd)
244{
245	struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
246
247	spin_lock(&wdt->lock);
248	__s3c2410wdt_stop(wdt);
249	spin_unlock(&wdt->lock);
250
251	return 0;
252}
253
254static int s3c2410wdt_start(struct watchdog_device *wdd)
255{
256	unsigned long wtcon;
257	struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
258
259	spin_lock(&wdt->lock);
260
261	__s3c2410wdt_stop(wdt);
262
263	wtcon = readl(wdt->reg_base + S3C2410_WTCON);
264	wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
265
266	if (soft_noboot) {
267		wtcon |= S3C2410_WTCON_INTEN;
268		wtcon &= ~S3C2410_WTCON_RSTEN;
269	} else {
270		wtcon &= ~S3C2410_WTCON_INTEN;
271		wtcon |= S3C2410_WTCON_RSTEN;
272	}
273
274	DBG("%s: count=0x%08x, wtcon=%08lx\n",
275	    __func__, wdt->count, wtcon);
276
277	writel(wdt->count, wdt->reg_base + S3C2410_WTDAT);
278	writel(wdt->count, wdt->reg_base + S3C2410_WTCNT);
279	writel(wtcon, wdt->reg_base + S3C2410_WTCON);
280	spin_unlock(&wdt->lock);
281
282	return 0;
 
 
 
283}
284
285static inline int s3c2410wdt_is_running(struct s3c2410_wdt *wdt)
286{
287	return readl(wdt->reg_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE;
288}
289
290static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeout)
291{
292	struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
293	unsigned long freq = clk_get_rate(wdt->clock);
294	unsigned int count;
295	unsigned int divisor = 1;
296	unsigned long wtcon;
297
298	if (timeout < 1)
299		return -EINVAL;
300
301	freq = DIV_ROUND_UP(freq, 128);
302	count = timeout * freq;
303
304	DBG("%s: count=%d, timeout=%d, freq=%lu\n",
305	    __func__, count, timeout, freq);
306
307	/* if the count is bigger than the watchdog register,
308	   then work out what we need to do (and if) we can
309	   actually make this value
310	*/
311
312	if (count >= 0x10000) {
313		divisor = DIV_ROUND_UP(count, 0xffff);
 
 
 
314
315		if (divisor > 0x100) {
316			dev_err(wdt->dev, "timeout %d too big\n", timeout);
317			return -EINVAL;
318		}
319	}
320
 
 
321	DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
322	    __func__, timeout, divisor, count, DIV_ROUND_UP(count, divisor));
323
324	count = DIV_ROUND_UP(count, divisor);
325	wdt->count = count;
326
327	/* update the pre-scaler */
328	wtcon = readl(wdt->reg_base + S3C2410_WTCON);
329	wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
330	wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
331
332	writel(count, wdt->reg_base + S3C2410_WTDAT);
333	writel(wtcon, wdt->reg_base + S3C2410_WTCON);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
335	wdd->timeout = (count * divisor) / freq;
 
 
 
 
 
 
 
 
 
 
336
 
 
 
 
 
 
 
 
337	return 0;
338}
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
341
342static const struct watchdog_info s3c2410_wdt_ident = {
343	.options          =     OPTIONS,
344	.firmware_version =	0,
345	.identity         =	"S3C2410 Watchdog",
346};
347
348static struct watchdog_ops s3c2410wdt_ops = {
349	.owner = THIS_MODULE,
350	.start = s3c2410wdt_start,
351	.stop = s3c2410wdt_stop,
352	.ping = s3c2410wdt_keepalive,
353	.set_timeout = s3c2410wdt_set_heartbeat,
354};
355
356static struct watchdog_device s3c2410_wdd = {
357	.info = &s3c2410_wdt_ident,
358	.ops = &s3c2410wdt_ops,
359	.timeout = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360};
361
362/* interrupt handler code */
363
364static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
365{
366	struct s3c2410_wdt *wdt = platform_get_drvdata(param);
367
368	dev_info(wdt->dev, "watchdog timer expired (irq)\n");
369
370	s3c2410wdt_keepalive(&wdt->wdt_device);
371	return IRQ_HANDLED;
372}
373
374#ifdef CONFIG_ARM_S3C24XX_CPUFREQ
 
375
376static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb,
377					  unsigned long val, void *data)
378{
379	int ret;
380	struct s3c2410_wdt *wdt = freq_to_wdt(nb);
381
382	if (!s3c2410wdt_is_running(wdt))
383		goto done;
384
385	if (val == CPUFREQ_PRECHANGE) {
386		/* To ensure that over the change we don't cause the
387		 * watchdog to trigger, we perform an keep-alive if
388		 * the watchdog is running.
389		 */
390
391		s3c2410wdt_keepalive(&wdt->wdt_device);
392	} else if (val == CPUFREQ_POSTCHANGE) {
393		s3c2410wdt_stop(&wdt->wdt_device);
394
395		ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
396						wdt->wdt_device.timeout);
397
398		if (ret >= 0)
399			s3c2410wdt_start(&wdt->wdt_device);
400		else
401			goto err;
402	}
403
404done:
405	return 0;
406
407 err:
408	dev_err(wdt->dev, "cannot set new value for timeout %d\n",
409				wdt->wdt_device.timeout);
410	return ret;
411}
412
413static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt)
 
 
 
 
414{
415	wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition;
416
417	return cpufreq_register_notifier(&wdt->freq_transition,
418					 CPUFREQ_TRANSITION_NOTIFIER);
419}
420
421static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt)
422{
423	wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition;
424
425	cpufreq_unregister_notifier(&wdt->freq_transition,
426				    CPUFREQ_TRANSITION_NOTIFIER);
427}
428
429#else
430
431static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt)
432{
433	return 0;
434}
435
436static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt)
437{
438}
439#endif
440
441static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt)
442{
443	unsigned int rst_stat;
444	int ret;
445
446	if (!(wdt->drv_data->quirks & QUIRK_HAS_RST_STAT))
447		return 0;
448
449	ret = regmap_read(wdt->pmureg, wdt->drv_data->rst_stat_reg, &rst_stat);
450	if (ret)
451		dev_warn(wdt->dev, "Couldn't get RST_STAT register\n");
452	else if (rst_stat & BIT(wdt->drv_data->rst_stat_bit))
453		return WDIOF_CARDRESET;
454
455	return 0;
456}
457
458/* s3c2410_get_wdt_driver_data */
459static inline struct s3c2410_wdt_variant *
460get_wdt_drv_data(struct platform_device *pdev)
461{
462	if (pdev->dev.of_node) {
463		const struct of_device_id *match;
464		match = of_match_node(s3c2410_wdt_match, pdev->dev.of_node);
465		return (struct s3c2410_wdt_variant *)match->data;
466	} else {
467		return (struct s3c2410_wdt_variant *)
468			platform_get_device_id(pdev)->driver_data;
469	}
470}
471
472static int s3c2410wdt_probe(struct platform_device *pdev)
473{
474	struct device *dev;
475	struct s3c2410_wdt *wdt;
476	struct resource *wdt_mem;
477	struct resource *wdt_irq;
478	unsigned int wtcon;
479	int started = 0;
480	int ret;
 
481
482	DBG("%s: probe=%p\n", __func__, pdev);
483
484	dev = &pdev->dev;
 
 
 
485
486	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
487	if (!wdt)
488		return -ENOMEM;
489
490	wdt->dev = &pdev->dev;
491	spin_lock_init(&wdt->lock);
492	wdt->wdt_device = s3c2410_wdd;
493
494	wdt->drv_data = get_wdt_drv_data(pdev);
495	if (wdt->drv_data->quirks & QUIRKS_HAVE_PMUREG) {
496		wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
497						"samsung,syscon-phandle");
498		if (IS_ERR(wdt->pmureg)) {
499			dev_err(dev, "syscon regmap lookup failed.\n");
500			return PTR_ERR(wdt->pmureg);
501		}
 
502	}
503
 
 
504	wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
505	if (wdt_irq == NULL) {
506		dev_err(dev, "no irq resource specified\n");
507		ret = -ENOENT;
508		goto err;
509	}
510
511	/* get the memory region for the watchdog timer */
512	wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
513	wdt->reg_base = devm_ioremap_resource(dev, wdt_mem);
514	if (IS_ERR(wdt->reg_base)) {
515		ret = PTR_ERR(wdt->reg_base);
516		goto err;
517	}
518
519	DBG("probe: mapped reg_base=%p\n", wdt->reg_base);
520
521	wdt->clock = devm_clk_get(dev, "watchdog");
522	if (IS_ERR(wdt->clock)) {
523		dev_err(dev, "failed to find watchdog clock source\n");
524		ret = PTR_ERR(wdt->clock);
525		goto err;
526	}
527
528	ret = clk_prepare_enable(wdt->clock);
529	if (ret < 0) {
530		dev_err(dev, "failed to enable clock\n");
531		return ret;
532	}
533
534	ret = s3c2410wdt_cpufreq_register(wdt);
535	if (ret < 0) {
536		dev_err(dev, "failed to register cpufreq\n");
537		goto err_clk;
538	}
539
540	watchdog_set_drvdata(&wdt->wdt_device, wdt);
541
542	/* see if we can actually set the requested timer margin, and if
543	 * not, try the default value */
544
545	watchdog_init_timeout(&wdt->wdt_device, tmr_margin, &pdev->dev);
546	ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
547					wdt->wdt_device.timeout);
548	if (ret) {
549		started = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
550					CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
551
552		if (started == 0)
553			dev_info(dev,
554			   "tmr_margin value out of range, default %d used\n",
555			       CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
556		else
557			dev_info(dev, "default timer value is out of range, "
558							"cannot start\n");
559	}
560
561	ret = devm_request_irq(dev, wdt_irq->start, s3c2410wdt_irq, 0,
562				pdev->name, pdev);
563	if (ret != 0) {
564		dev_err(dev, "failed to install irq (%d)\n", ret);
565		goto err_cpufreq;
566	}
567
568	watchdog_set_nowayout(&wdt->wdt_device, nowayout);
569
570	wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt);
571
572	ret = watchdog_register_device(&wdt->wdt_device);
573	if (ret) {
574		dev_err(dev, "cannot register watchdog (%d)\n", ret);
 
575		goto err_cpufreq;
576	}
577
578	ret = s3c2410wdt_mask_and_disable_reset(wdt, false);
579	if (ret < 0)
580		goto err_unregister;
581
582	if (tmr_atboot && started == 0) {
583		dev_info(dev, "starting watchdog timer\n");
584		s3c2410wdt_start(&wdt->wdt_device);
585	} else if (!tmr_atboot) {
586		/* if we're not enabling the watchdog, then ensure it is
587		 * disabled if it has been left running from the bootloader
588		 * or other source */
589
590		s3c2410wdt_stop(&wdt->wdt_device);
591	}
592
593	platform_set_drvdata(pdev, wdt);
594
595	/* print out a statement of readiness */
596
597	wtcon = readl(wdt->reg_base + S3C2410_WTCON);
598
599	dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
600		 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",
601		 (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis",
602		 (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis");
603
604	return 0;
605
606 err_unregister:
607	watchdog_unregister_device(&wdt->wdt_device);
608
609 err_cpufreq:
610	s3c2410wdt_cpufreq_deregister(wdt);
611
612 err_clk:
613	clk_disable_unprepare(wdt->clock);
 
 
 
 
 
 
 
 
 
 
 
614
615 err:
616	return ret;
617}
618
619static int s3c2410wdt_remove(struct platform_device *dev)
620{
621	int ret;
622	struct s3c2410_wdt *wdt = platform_get_drvdata(dev);
623
624	ret = s3c2410wdt_mask_and_disable_reset(wdt, true);
625	if (ret < 0)
626		return ret;
627
628	watchdog_unregister_device(&wdt->wdt_device);
 
 
629
630	s3c2410wdt_cpufreq_deregister(wdt);
 
631
632	clk_disable_unprepare(wdt->clock);
633
 
 
634	return 0;
635}
636
637static void s3c2410wdt_shutdown(struct platform_device *dev)
638{
639	struct s3c2410_wdt *wdt = platform_get_drvdata(dev);
 
640
641	s3c2410wdt_mask_and_disable_reset(wdt, true);
642
643	s3c2410wdt_stop(&wdt->wdt_device);
644}
645
646#ifdef CONFIG_PM_SLEEP
647
648static int s3c2410wdt_suspend(struct device *dev)
649{
650	int ret;
651	struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
652
653	/* Save watchdog state, and turn it off. */
654	wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON);
655	wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT);
656
657	ret = s3c2410wdt_mask_and_disable_reset(wdt, true);
658	if (ret < 0)
659		return ret;
660
661	/* Note that WTCNT doesn't need to be saved. */
662	s3c2410wdt_stop(&wdt->wdt_device);
663
664	return 0;
665}
666
667static int s3c2410wdt_resume(struct device *dev)
668{
669	int ret;
670	struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
671
672	/* Restore watchdog state. */
673	writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTDAT);
674	writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */
675	writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON);
676
677	ret = s3c2410wdt_mask_and_disable_reset(wdt, false);
678	if (ret < 0)
679		return ret;
680
681	dev_info(dev, "watchdog %sabled\n",
682		(wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
683
684	return 0;
685}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686#endif
687
688static SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops, s3c2410wdt_suspend,
689			s3c2410wdt_resume);
690
691static struct platform_driver s3c2410wdt_driver = {
692	.probe		= s3c2410wdt_probe,
693	.remove		= s3c2410wdt_remove,
694	.shutdown	= s3c2410wdt_shutdown,
695	.id_table	= s3c2410_wdt_ids,
 
696	.driver		= {
697		.owner	= THIS_MODULE,
698		.name	= "s3c2410-wdt",
699		.pm	= &s3c2410wdt_pm_ops,
700		.of_match_table	= of_match_ptr(s3c2410_wdt_match),
701	},
702};
703
704module_platform_driver(s3c2410wdt_driver);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
706MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, "
707	      "Dimitry Andric <dimitry.andric@tomtom.com>");
708MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
709MODULE_LICENSE("GPL");