1// SPDX-License-Identifier: GPL-2.0-or-later
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
12#include <linux/module.h>
13#include <linux/moduleparam.h>
14#include <linux/types.h>
15#include <linux/timer.h>
16#include <linux/watchdog.h>
17#include <linux/platform_device.h>
18#include <linux/interrupt.h>
19#include <linux/clk.h>
20#include <linux/uaccess.h>
21#include <linux/io.h>
22#include <linux/cpufreq.h>
23#include <linux/slab.h>
24#include <linux/err.h>
25#include <linux/of.h>
26#include <linux/mfd/syscon.h>
27#include <linux/regmap.h>
28#include <linux/delay.h>
29
30#define S3C2410_WTCON		0x00
31#define S3C2410_WTDAT		0x04
32#define S3C2410_WTCNT		0x08
33#define S3C2410_WTCLRINT	0x0c
34
35#define S3C2410_WTCNT_MAXCNT	0xffff
36
37#define S3C2410_WTCON_RSTEN	(1 << 0)
38#define S3C2410_WTCON_INTEN	(1 << 2)
39#define S3C2410_WTCON_ENABLE	(1 << 5)
40
41#define S3C2410_WTCON_DIV16	(0 << 3)
42#define S3C2410_WTCON_DIV32	(1 << 3)
43#define S3C2410_WTCON_DIV64	(2 << 3)
44#define S3C2410_WTCON_DIV128	(3 << 3)
45
46#define S3C2410_WTCON_MAXDIV	0x80
47
48#define S3C2410_WTCON_PRESCALE(x)	((x) << 8)
49#define S3C2410_WTCON_PRESCALE_MASK	(0xff << 8)
50#define S3C2410_WTCON_PRESCALE_MAX	0xff
51
52#define S3C2410_WATCHDOG_ATBOOT		(0)
53#define S3C2410_WATCHDOG_DEFAULT_TIME	(15)
54
55#define EXYNOS5_RST_STAT_REG_OFFSET		0x0404
56#define EXYNOS5_WDT_DISABLE_REG_OFFSET		0x0408
57#define EXYNOS5_WDT_MASK_RESET_REG_OFFSET	0x040c
58#define EXYNOS850_CLUSTER0_NONCPU_OUT		0x1220
59#define EXYNOS850_CLUSTER0_NONCPU_INT_EN	0x1244
60#define EXYNOS850_CLUSTER1_NONCPU_OUT		0x1620
61#define EXYNOS850_CLUSTER1_NONCPU_INT_EN	0x1644
62#define EXYNOSAUTOV9_CLUSTER1_NONCPU_OUT	0x1520
63#define EXYNOSAUTOV9_CLUSTER1_NONCPU_INT_EN	0x1544
64
65#define EXYNOS850_CLUSTER0_WDTRESET_BIT		24
66#define EXYNOS850_CLUSTER1_WDTRESET_BIT		23
67#define EXYNOSAUTOV9_CLUSTER0_WDTRESET_BIT	25
68#define EXYNOSAUTOV9_CLUSTER1_WDTRESET_BIT	24
69
70/**
71 * DOC: Quirk flags for different Samsung watchdog IP-cores
72 *
73 * This driver supports multiple Samsung SoCs, each of which might have
74 * different set of registers and features supported. As watchdog block
75 * sometimes requires modifying PMU registers for proper functioning, register
76 * differences in both watchdog and PMU IP-cores should be accounted for. Quirk
77 * flags described below serve the purpose of telling the driver about mentioned
78 * SoC traits, and can be specified in driver data for each particular supported
79 * device.
80 *
81 * %QUIRK_HAS_WTCLRINT_REG: Watchdog block has WTCLRINT register. It's used to
82 * clear the interrupt once the interrupt service routine is complete. It's
83 * write-only, writing any values to this register clears the interrupt, but
84 * reading is not permitted.
85 *
86 * %QUIRK_HAS_PMU_MASK_RESET: PMU block has the register for disabling/enabling
87 * WDT reset request. On old SoCs it's usually called MASK_WDT_RESET_REQUEST,
88 * new SoCs have CLUSTERx_NONCPU_INT_EN register, which 'mask_bit' value is
89 * inverted compared to the former one.
90 *
91 * %QUIRK_HAS_PMU_RST_STAT: PMU block has RST_STAT (reset status) register,
92 * which contains bits indicating the reason for most recent CPU reset. If
93 * present, driver will use this register to check if previous reboot was due to
94 * watchdog timer reset.
95 *
96 * %QUIRK_HAS_PMU_AUTO_DISABLE: PMU block has AUTOMATIC_WDT_RESET_DISABLE
97 * register. If 'mask_bit' bit is set, PMU will disable WDT reset when
98 * corresponding processor is in reset state.
99 *
100 * %QUIRK_HAS_PMU_CNT_EN: PMU block has some register (e.g. CLUSTERx_NONCPU_OUT)
101 * with "watchdog counter enable" bit. That bit should be set to make watchdog
102 * counter running.
103 */
104#define QUIRK_HAS_WTCLRINT_REG			(1 << 0)
105#define QUIRK_HAS_PMU_MASK_RESET		(1 << 1)
106#define QUIRK_HAS_PMU_RST_STAT			(1 << 2)
107#define QUIRK_HAS_PMU_AUTO_DISABLE		(1 << 3)
108#define QUIRK_HAS_PMU_CNT_EN			(1 << 4)
109
110/* These quirks require that we have a PMU register map */
111#define QUIRKS_HAVE_PMUREG \
112	(QUIRK_HAS_PMU_MASK_RESET | QUIRK_HAS_PMU_RST_STAT | \
113	 QUIRK_HAS_PMU_AUTO_DISABLE | QUIRK_HAS_PMU_CNT_EN)
114
115static bool nowayout	= WATCHDOG_NOWAYOUT;
116static int tmr_margin;
117static int tmr_atboot	= S3C2410_WATCHDOG_ATBOOT;
118static int soft_noboot;
119
120module_param(tmr_margin,  int, 0);
121module_param(tmr_atboot,  int, 0);
122module_param(nowayout,   bool, 0);
123module_param(soft_noboot, int, 0);
124
125MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default="
126		__MODULE_STRING(S3C2410_WATCHDOG_DEFAULT_TIME) ")");
127MODULE_PARM_DESC(tmr_atboot,
128		"Watchdog is started at boot time if set to 1, default="
129			__MODULE_STRING(S3C2410_WATCHDOG_ATBOOT));
130MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
131			__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
132MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default 0)");
133
134/**
135 * struct s3c2410_wdt_variant - Per-variant config data
136 *
137 * @disable_reg: Offset in pmureg for the register that disables the watchdog
138 * timer reset functionality.
139 * @mask_reset_reg: Offset in pmureg for the register that masks the watchdog
140 * timer reset functionality.
141 * @mask_reset_inv: If set, mask_reset_reg value will have inverted meaning.
142 * @mask_bit: Bit number for the watchdog timer in the disable register and the
143 * mask reset register.
144 * @rst_stat_reg: Offset in pmureg for the register that has the reset status.
145 * @rst_stat_bit: Bit number in the rst_stat register indicating a watchdog
146 * reset.
147 * @cnt_en_reg: Offset in pmureg for the register that enables WDT counter.
148 * @cnt_en_bit: Bit number for "watchdog counter enable" in cnt_en register.
149 * @quirks: A bitfield of quirks.
150 */
151
152struct s3c2410_wdt_variant {
153	int disable_reg;
154	int mask_reset_reg;
155	bool mask_reset_inv;
156	int mask_bit;
157	int rst_stat_reg;
158	int rst_stat_bit;
159	int cnt_en_reg;
160	int cnt_en_bit;
161	u32 quirks;
162};
163
164struct s3c2410_wdt {
165	struct device		*dev;
166	struct clk		*bus_clk; /* for register interface (PCLK) */
167	struct clk		*src_clk; /* for WDT counter */
168	void __iomem		*reg_base;
169	unsigned int		count;
170	spinlock_t		lock;
171	unsigned long		wtcon_save;
172	unsigned long		wtdat_save;
173	struct watchdog_device	wdt_device;
174	struct notifier_block	freq_transition;
175	const struct s3c2410_wdt_variant *drv_data;
176	struct regmap *pmureg;
177};
178
179static const struct s3c2410_wdt_variant drv_data_s3c2410 = {
180	.quirks = 0
181};
182
183#ifdef CONFIG_OF
184static const struct s3c2410_wdt_variant drv_data_s3c6410 = {
185	.quirks = QUIRK_HAS_WTCLRINT_REG,
186};
187
188static const struct s3c2410_wdt_variant drv_data_exynos5250  = {
189	.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
190	.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
191	.mask_bit = 20,
192	.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
193	.rst_stat_bit = 20,
194	.quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
195		  QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE,
196};
197
198static const struct s3c2410_wdt_variant drv_data_exynos5420 = {
199	.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
200	.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
201	.mask_bit = 0,
202	.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
203	.rst_stat_bit = 9,
204	.quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
205		  QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE,
206};
207
208static const struct s3c2410_wdt_variant drv_data_exynos7 = {
209	.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
210	.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
211	.mask_bit = 23,
212	.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
213	.rst_stat_bit = 23,	/* A57 WDTRESET */
214	.quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
215		  QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE,
216};
217
218static const struct s3c2410_wdt_variant drv_data_exynos850_cl0 = {
219	.mask_reset_reg = EXYNOS850_CLUSTER0_NONCPU_INT_EN,
220	.mask_bit = 2,
221	.mask_reset_inv = true,
222	.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
223	.rst_stat_bit = EXYNOS850_CLUSTER0_WDTRESET_BIT,
224	.cnt_en_reg = EXYNOS850_CLUSTER0_NONCPU_OUT,
225	.cnt_en_bit = 7,
226	.quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
227		  QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN,
228};
229
230static const struct s3c2410_wdt_variant drv_data_exynos850_cl1 = {
231	.mask_reset_reg = EXYNOS850_CLUSTER1_NONCPU_INT_EN,
232	.mask_bit = 2,
233	.mask_reset_inv = true,
234	.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
235	.rst_stat_bit = EXYNOS850_CLUSTER1_WDTRESET_BIT,
236	.cnt_en_reg = EXYNOS850_CLUSTER1_NONCPU_OUT,
237	.cnt_en_bit = 7,
238	.quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
239		  QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN,
240};
241
242static const struct s3c2410_wdt_variant drv_data_exynosautov9_cl0 = {
243	.mask_reset_reg = EXYNOS850_CLUSTER0_NONCPU_INT_EN,
244	.mask_bit = 2,
245	.mask_reset_inv = true,
246	.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
247	.rst_stat_bit = EXYNOSAUTOV9_CLUSTER0_WDTRESET_BIT,
248	.cnt_en_reg = EXYNOS850_CLUSTER0_NONCPU_OUT,
249	.cnt_en_bit = 7,
250	.quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET |
251		  QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN,
252};
253
254static const struct s3c2410_wdt_variant drv_data_exynosautov9_cl1 = {
255	.mask_reset_reg = EXYNOSAUTOV9_CLUSTER1_NONCPU_INT_EN,
256	.mask_bit = 2,
257	.mask_reset_inv = true,
258	.rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
259	.rst_stat_bit = EXYNOSAUTOV9_CLUSTER1_WDTRESET_BIT,
260	.cnt_en_reg = EXYNOSAUTOV9_CLUSTER1_NONCPU_OUT,
261	.cnt_en_bit = 7,
262	.quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET |
263		  QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN,
264};
265
266static const struct of_device_id s3c2410_wdt_match[] = {
267	{ .compatible = "samsung,s3c2410-wdt",
268	  .data = &drv_data_s3c2410 },
269	{ .compatible = "samsung,s3c6410-wdt",
270	  .data = &drv_data_s3c6410 },
271	{ .compatible = "samsung,exynos5250-wdt",
272	  .data = &drv_data_exynos5250 },
273	{ .compatible = "samsung,exynos5420-wdt",
274	  .data = &drv_data_exynos5420 },
275	{ .compatible = "samsung,exynos7-wdt",
276	  .data = &drv_data_exynos7 },
277	{ .compatible = "samsung,exynos850-wdt",
278	  .data = &drv_data_exynos850_cl0 },
279	{ .compatible = "samsung,exynosautov9-wdt",
280	  .data = &drv_data_exynosautov9_cl0 },
281	{},
282};
283MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
284#endif
285
286static const struct platform_device_id s3c2410_wdt_ids[] = {
287	{
288		.name = "s3c2410-wdt",
289		.driver_data = (unsigned long)&drv_data_s3c2410,
290	},
291	{}
292};
293MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids);
294
295/* functions */
296
297static inline unsigned long s3c2410wdt_get_freq(struct s3c2410_wdt *wdt)
298{
299	return clk_get_rate(wdt->src_clk ? wdt->src_clk : wdt->bus_clk);
300}
301
302static inline unsigned int s3c2410wdt_max_timeout(struct s3c2410_wdt *wdt)
303{
304	const unsigned long freq = s3c2410wdt_get_freq(wdt);
305
306	return S3C2410_WTCNT_MAXCNT / (freq / (S3C2410_WTCON_PRESCALE_MAX + 1)
307				       / S3C2410_WTCON_MAXDIV);
308}
309
310static int s3c2410wdt_disable_wdt_reset(struct s3c2410_wdt *wdt, bool mask)
311{
312	const u32 mask_val = BIT(wdt->drv_data->mask_bit);
313	const u32 val = mask ? mask_val : 0;
314	int ret;
315
316	ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->disable_reg,
317				 mask_val, val);
318	if (ret < 0)
319		dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
320
321	return ret;
322}
323
324static int s3c2410wdt_mask_wdt_reset(struct s3c2410_wdt *wdt, bool mask)
325{
326	const u32 mask_val = BIT(wdt->drv_data->mask_bit);
327	const bool val_inv = wdt->drv_data->mask_reset_inv;
328	const u32 val = (mask ^ val_inv) ? mask_val : 0;
329	int ret;
330
331	ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->mask_reset_reg,
332				 mask_val, val);
333	if (ret < 0)
334		dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
335
336	return ret;
337}
338
339static int s3c2410wdt_enable_counter(struct s3c2410_wdt *wdt, bool en)
340{
341	const u32 mask_val = BIT(wdt->drv_data->cnt_en_bit);
342	const u32 val = en ? mask_val : 0;
343	int ret;
344
345	ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->cnt_en_reg,
346				 mask_val, val);
347	if (ret < 0)
348		dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
349
350	return ret;
351}
352
353static int s3c2410wdt_enable(struct s3c2410_wdt *wdt, bool en)
354{
355	int ret;
356
357	if (wdt->drv_data->quirks & QUIRK_HAS_PMU_AUTO_DISABLE) {
358		ret = s3c2410wdt_disable_wdt_reset(wdt, !en);
359		if (ret < 0)
360			return ret;
361	}
362
363	if (wdt->drv_data->quirks & QUIRK_HAS_PMU_MASK_RESET) {
364		ret = s3c2410wdt_mask_wdt_reset(wdt, !en);
365		if (ret < 0)
366			return ret;
367	}
368
369	if (wdt->drv_data->quirks & QUIRK_HAS_PMU_CNT_EN) {
370		ret = s3c2410wdt_enable_counter(wdt, en);
371		if (ret < 0)
372			return ret;
373	}
374
375	return 0;
376}
377
378static int s3c2410wdt_keepalive(struct watchdog_device *wdd)
379{
380	struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
381	unsigned long flags;
382
383	spin_lock_irqsave(&wdt->lock, flags);
384	writel(wdt->count, wdt->reg_base + S3C2410_WTCNT);
385	spin_unlock_irqrestore(&wdt->lock, flags);
386
387	return 0;
388}
389
390static void __s3c2410wdt_stop(struct s3c2410_wdt *wdt)
391{
392	unsigned long wtcon;
393
394	wtcon = readl(wdt->reg_base + S3C2410_WTCON);
395	wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
396	writel(wtcon, wdt->reg_base + S3C2410_WTCON);
397}
398
399static int s3c2410wdt_stop(struct watchdog_device *wdd)
400{
401	struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
402	unsigned long flags;
403
404	spin_lock_irqsave(&wdt->lock, flags);
405	__s3c2410wdt_stop(wdt);
406	spin_unlock_irqrestore(&wdt->lock, flags);
407
408	return 0;
409}
410
411static int s3c2410wdt_start(struct watchdog_device *wdd)
412{
413	unsigned long wtcon;
414	struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
415	unsigned long flags;
416
417	spin_lock_irqsave(&wdt->lock, flags);
418
419	__s3c2410wdt_stop(wdt);
420
421	wtcon = readl(wdt->reg_base + S3C2410_WTCON);
422	wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
423
424	if (soft_noboot) {
425		wtcon |= S3C2410_WTCON_INTEN;
426		wtcon &= ~S3C2410_WTCON_RSTEN;
427	} else {
428		wtcon &= ~S3C2410_WTCON_INTEN;
429		wtcon |= S3C2410_WTCON_RSTEN;
430	}
431
432	dev_dbg(wdt->dev, "Starting watchdog: count=0x%08x, wtcon=%08lx\n",
433		wdt->count, wtcon);
434
435	writel(wdt->count, wdt->reg_base + S3C2410_WTDAT);
436	writel(wdt->count, wdt->reg_base + S3C2410_WTCNT);
437	writel(wtcon, wdt->reg_base + S3C2410_WTCON);
438	spin_unlock_irqrestore(&wdt->lock, flags);
439
440	return 0;
441}
442
443static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd,
444				    unsigned int timeout)
445{
446	struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
447	unsigned long freq = s3c2410wdt_get_freq(wdt);
448	unsigned int count;
449	unsigned int divisor = 1;
450	unsigned long wtcon;
451
452	if (timeout < 1)
453		return -EINVAL;
454
455	freq = DIV_ROUND_UP(freq, 128);
456	count = timeout * freq;
457
458	dev_dbg(wdt->dev, "Heartbeat: count=%d, timeout=%d, freq=%lu\n",
459		count, timeout, freq);
460
461	/* if the count is bigger than the watchdog register,
462	   then work out what we need to do (and if) we can
463	   actually make this value
464	*/
465
466	if (count >= 0x10000) {
467		divisor = DIV_ROUND_UP(count, 0xffff);
468
469		if (divisor > 0x100) {
470			dev_err(wdt->dev, "timeout %d too big\n", timeout);
471			return -EINVAL;
472		}
473	}
474
475	dev_dbg(wdt->dev, "Heartbeat: timeout=%d, divisor=%d, count=%d (%08x)\n",
476		timeout, divisor, count, DIV_ROUND_UP(count, divisor));
477
478	count = DIV_ROUND_UP(count, divisor);
479	wdt->count = count;
480
481	/* update the pre-scaler */
482	wtcon = readl(wdt->reg_base + S3C2410_WTCON);
483	wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
484	wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
485
486	writel(count, wdt->reg_base + S3C2410_WTDAT);
487	writel(wtcon, wdt->reg_base + S3C2410_WTCON);
488
489	wdd->timeout = (count * divisor) / freq;
490
491	return 0;
492}
493
494static int s3c2410wdt_restart(struct watchdog_device *wdd, unsigned long action,
495			      void *data)
496{
497	struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
498	void __iomem *wdt_base = wdt->reg_base;
499
500	/* disable watchdog, to be safe  */
501	writel(0, wdt_base + S3C2410_WTCON);
502
503	/* put initial values into count and data */
504	writel(0x80, wdt_base + S3C2410_WTCNT);
505	writel(0x80, wdt_base + S3C2410_WTDAT);
506
507	/* set the watchdog to go and reset... */
508	writel(S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV16 |
509		S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x20),
510		wdt_base + S3C2410_WTCON);
511
512	/* wait for reset to assert... */
513	mdelay(500);
514
515	return 0;
516}
517
518#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
519
520static const struct watchdog_info s3c2410_wdt_ident = {
521	.options          =     OPTIONS,
522	.firmware_version =	0,
523	.identity         =	"S3C2410 Watchdog",
524};
525
526static const struct watchdog_ops s3c2410wdt_ops = {
527	.owner = THIS_MODULE,
528	.start = s3c2410wdt_start,
529	.stop = s3c2410wdt_stop,
530	.ping = s3c2410wdt_keepalive,
531	.set_timeout = s3c2410wdt_set_heartbeat,
532	.restart = s3c2410wdt_restart,
533};
534
535static const struct watchdog_device s3c2410_wdd = {
536	.info = &s3c2410_wdt_ident,
537	.ops = &s3c2410wdt_ops,
538	.timeout = S3C2410_WATCHDOG_DEFAULT_TIME,
539};
540
541/* interrupt handler code */
542
543static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
544{
545	struct s3c2410_wdt *wdt = platform_get_drvdata(param);
546
547	dev_info(wdt->dev, "watchdog timer expired (irq)\n");
548
549	s3c2410wdt_keepalive(&wdt->wdt_device);
550
551	if (wdt->drv_data->quirks & QUIRK_HAS_WTCLRINT_REG)
552		writel(0x1, wdt->reg_base + S3C2410_WTCLRINT);
553
554	return IRQ_HANDLED;
555}
556
557static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt)
558{
559	unsigned int rst_stat;
560	int ret;
561
562	if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_RST_STAT))
563		return 0;
564
565	ret = regmap_read(wdt->pmureg, wdt->drv_data->rst_stat_reg, &rst_stat);
566	if (ret)
567		dev_warn(wdt->dev, "Couldn't get RST_STAT register\n");
568	else if (rst_stat & BIT(wdt->drv_data->rst_stat_bit))
569		return WDIOF_CARDRESET;
570
571	return 0;
572}
573
574static inline int
575s3c2410_get_wdt_drv_data(struct platform_device *pdev, struct s3c2410_wdt *wdt)
576{
577	const struct s3c2410_wdt_variant *variant;
578	struct device *dev = &pdev->dev;
579
580	variant = of_device_get_match_data(dev);
581	if (!variant) {
582		/* Device matched by platform_device_id */
583		variant = (struct s3c2410_wdt_variant *)
584			   platform_get_device_id(pdev)->driver_data;
585	}
586
587#ifdef CONFIG_OF
588	/* Choose Exynos850/ExynosAutov9 driver data w.r.t. cluster index */
589	if (variant == &drv_data_exynos850_cl0 ||
590	    variant == &drv_data_exynosautov9_cl0) {
591		u32 index;
592		int err;
593
594		err = of_property_read_u32(dev->of_node,
595					   "samsung,cluster-index", &index);
596		if (err)
597			return dev_err_probe(dev, -EINVAL, "failed to get cluster index\n");
598
599		switch (index) {
600		case 0:
601			break;
602		case 1:
603			variant = (variant == &drv_data_exynos850_cl0) ?
604				&drv_data_exynos850_cl1 :
605				&drv_data_exynosautov9_cl1;
606			break;
607		default:
608			return dev_err_probe(dev, -EINVAL, "wrong cluster index: %u\n", index);
609		}
610	}
611#endif
612
613	wdt->drv_data = variant;
614	return 0;
615}
616
617static void s3c2410wdt_wdt_disable_action(void *data)
618{
619	s3c2410wdt_enable(data, false);
620}
621
622static int s3c2410wdt_probe(struct platform_device *pdev)
623{
624	struct device *dev = &pdev->dev;
625	struct s3c2410_wdt *wdt;
626	unsigned int wtcon;
627	int wdt_irq;
628	int ret;
629
630	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
631	if (!wdt)
632		return -ENOMEM;
633
634	wdt->dev = dev;
635	spin_lock_init(&wdt->lock);
636	wdt->wdt_device = s3c2410_wdd;
637
638	ret = s3c2410_get_wdt_drv_data(pdev, wdt);
639	if (ret)
640		return ret;
641
642	if (wdt->drv_data->quirks & QUIRKS_HAVE_PMUREG) {
643		wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
644						"samsung,syscon-phandle");
645		if (IS_ERR(wdt->pmureg))
646			return dev_err_probe(dev, PTR_ERR(wdt->pmureg),
647					     "syscon regmap lookup failed.\n");
648	}
649
650	wdt_irq = platform_get_irq(pdev, 0);
651	if (wdt_irq < 0)
652		return wdt_irq;
653
654	/* get the memory region for the watchdog timer */
655	wdt->reg_base = devm_platform_ioremap_resource(pdev, 0);
656	if (IS_ERR(wdt->reg_base))
657		return PTR_ERR(wdt->reg_base);
658
659	wdt->bus_clk = devm_clk_get_enabled(dev, "watchdog");
660	if (IS_ERR(wdt->bus_clk))
661		return dev_err_probe(dev, PTR_ERR(wdt->bus_clk), "failed to get bus clock\n");
662
663	/*
664	 * "watchdog_src" clock is optional; if it's not present -- just skip it
665	 * and use "watchdog" clock as both bus and source clock.
666	 */
667	wdt->src_clk = devm_clk_get_optional_enabled(dev, "watchdog_src");
668	if (IS_ERR(wdt->src_clk))
669		return dev_err_probe(dev, PTR_ERR(wdt->src_clk), "failed to get source clock\n");
670
671	wdt->wdt_device.min_timeout = 1;
672	wdt->wdt_device.max_timeout = s3c2410wdt_max_timeout(wdt);
673
674	watchdog_set_drvdata(&wdt->wdt_device, wdt);
675
676	/* see if we can actually set the requested timer margin, and if
677	 * not, try the default value */
678
679	watchdog_init_timeout(&wdt->wdt_device, tmr_margin, dev);
680	ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
681					wdt->wdt_device.timeout);
682	if (ret) {
683		ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
684					       S3C2410_WATCHDOG_DEFAULT_TIME);
685		if (ret == 0)
686			dev_warn(dev, "tmr_margin value out of range, default %d used\n",
687				 S3C2410_WATCHDOG_DEFAULT_TIME);
688		else
689			return dev_err_probe(dev, ret, "failed to use default timeout\n");
690	}
691
692	ret = devm_request_irq(dev, wdt_irq, s3c2410wdt_irq, 0,
693			       pdev->name, pdev);
694	if (ret != 0)
695		return dev_err_probe(dev, ret, "failed to install irq (%d)\n", ret);
696
697	watchdog_set_nowayout(&wdt->wdt_device, nowayout);
698	watchdog_set_restart_priority(&wdt->wdt_device, 128);
699
700	wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt);
701	wdt->wdt_device.parent = dev;
702
703	/*
704	 * If "tmr_atboot" param is non-zero, start the watchdog right now. Also
705	 * set WDOG_HW_RUNNING bit, so that watchdog core can kick the watchdog.
706	 *
707	 * If we're not enabling the watchdog, then ensure it is disabled if it
708	 * has been left running from the bootloader or other source.
709	 */
710	if (tmr_atboot) {
711		dev_info(dev, "starting watchdog timer\n");
712		s3c2410wdt_start(&wdt->wdt_device);
713		set_bit(WDOG_HW_RUNNING, &wdt->wdt_device.status);
714	} else {
715		s3c2410wdt_stop(&wdt->wdt_device);
716	}
717
718	ret = devm_watchdog_register_device(dev, &wdt->wdt_device);
719	if (ret)
720		return ret;
721
722	ret = s3c2410wdt_enable(wdt, true);
723	if (ret < 0)
724		return ret;
725
726	ret = devm_add_action_or_reset(dev, s3c2410wdt_wdt_disable_action, wdt);
727	if (ret)
728		return ret;
729
730	platform_set_drvdata(pdev, wdt);
731
732	/* print out a statement of readiness */
733
734	wtcon = readl(wdt->reg_base + S3C2410_WTCON);
735
736	dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
737		 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",
738		 (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis",
739		 (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis");
740
741	return 0;
742}
743
744static void s3c2410wdt_shutdown(struct platform_device *dev)
745{
746	struct s3c2410_wdt *wdt = platform_get_drvdata(dev);
747
748	s3c2410wdt_enable(wdt, false);
749	s3c2410wdt_stop(&wdt->wdt_device);
750}
751
752static int s3c2410wdt_suspend(struct device *dev)
753{
754	int ret;
755	struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
756
757	/* Save watchdog state, and turn it off. */
758	wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON);
759	wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT);
760
761	ret = s3c2410wdt_enable(wdt, false);
762	if (ret < 0)
763		return ret;
764
765	/* Note that WTCNT doesn't need to be saved. */
766	s3c2410wdt_stop(&wdt->wdt_device);
767
768	return 0;
769}
770
771static int s3c2410wdt_resume(struct device *dev)
772{
773	int ret;
774	struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
775
776	/* Restore watchdog state. */
777	writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTDAT);
778	writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */
779	writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON);
780
781	ret = s3c2410wdt_enable(wdt, true);
782	if (ret < 0)
783		return ret;
784
785	dev_info(dev, "watchdog %sabled\n",
786		(wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
787
788	return 0;
789}
790
791static DEFINE_SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops,
792				s3c2410wdt_suspend, s3c2410wdt_resume);
793
794static struct platform_driver s3c2410wdt_driver = {
795	.probe		= s3c2410wdt_probe,
796	.shutdown	= s3c2410wdt_shutdown,
797	.id_table	= s3c2410_wdt_ids,
798	.driver		= {
799		.name	= "s3c2410-wdt",
800		.pm	= pm_sleep_ptr(&s3c2410wdt_pm_ops),
801		.of_match_table	= of_match_ptr(s3c2410_wdt_match),
802	},
803};
804
805module_platform_driver(s3c2410wdt_driver);
806
807MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, Dimitry Andric <dimitry.andric@tomtom.com>");
808MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
809MODULE_LICENSE("GPL");
810