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