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