source: src/linux/universal/linux-4.9/drivers/watchdog/s3c2410_wdt.c @ 31859

Last change on this file since 31859 was 31859, checked in by brainslayer, 6 weeks ago

kernel update

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