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