Linux 6.7-rc7
[linux-modified.git] / drivers / watchdog / s3c2410_wdt.c
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
115 static bool nowayout    = WATCHDOG_NOWAYOUT;
116 static int tmr_margin;
117 static int tmr_atboot   = S3C2410_WATCHDOG_ATBOOT;
118 static int soft_noboot;
119
120 module_param(tmr_margin,  int, 0);
121 module_param(tmr_atboot,  int, 0);
122 module_param(nowayout,   bool, 0);
123 module_param(soft_noboot, int, 0);
124
125 MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default="
126                 __MODULE_STRING(S3C2410_WATCHDOG_DEFAULT_TIME) ")");
127 MODULE_PARM_DESC(tmr_atboot,
128                 "Watchdog is started at boot time if set to 1, default="
129                         __MODULE_STRING(S3C2410_WATCHDOG_ATBOOT));
130 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
131                         __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
132 MODULE_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
152 struct 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
164 struct 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
179 static const struct s3c2410_wdt_variant drv_data_s3c2410 = {
180         .quirks = 0
181 };
182
183 #ifdef CONFIG_OF
184 static const struct s3c2410_wdt_variant drv_data_s3c6410 = {
185         .quirks = QUIRK_HAS_WTCLRINT_REG,
186 };
187
188 static 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
198 static 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
208 static 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
218 static 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
230 static 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
242 static 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
254 static 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
266 static 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 };
283 MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
284 #endif
285
286 static const struct platform_device_id s3c2410_wdt_ids[] = {
287         {
288                 .name = "s3c2410-wdt",
289                 .driver_data = (unsigned long)&drv_data_s3c2410,
290         },
291         {}
292 };
293 MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids);
294
295 /* functions */
296
297 static 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
302 static 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
310 static 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
324 static 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
339 static 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
353 static 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
378 static 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
390 static 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
399 static 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
411 static 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
443 static 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
494 static 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
520 static const struct watchdog_info s3c2410_wdt_ident = {
521         .options          =     OPTIONS,
522         .firmware_version =     0,
523         .identity         =     "S3C2410 Watchdog",
524 };
525
526 static 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
535 static 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
543 static 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
557 static 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
574 static inline int
575 s3c2410_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
617 static void s3c2410wdt_wdt_disable_action(void *data)
618 {
619         s3c2410wdt_enable(data, false);
620 }
621
622 static 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
744 static 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
752 static 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
771 static 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
791 static DEFINE_SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops,
792                                 s3c2410wdt_suspend, s3c2410wdt_resume);
793
794 static 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
805 module_platform_driver(s3c2410wdt_driver);
806
807 MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, Dimitry Andric <dimitry.andric@tomtom.com>");
808 MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
809 MODULE_LICENSE("GPL");