diff options
-rw-r--r-- | Documentation/devicetree/bindings/watchdog/samsung-wdt.txt | 21 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 1 | ||||
-rw-r--r-- | drivers/watchdog/s3c2410_wdt.c | 154 |
3 files changed, 166 insertions, 10 deletions
diff --git a/Documentation/devicetree/bindings/watchdog/samsung-wdt.txt b/Documentation/devicetree/bindings/watchdog/samsung-wdt.txt index 2aa486cc1ff6..cfff37511aac 100644 --- a/Documentation/devicetree/bindings/watchdog/samsung-wdt.txt +++ b/Documentation/devicetree/bindings/watchdog/samsung-wdt.txt | |||
@@ -5,10 +5,29 @@ after a preset amount of time during which the WDT reset event has not | |||
5 | occurred. | 5 | occurred. |
6 | 6 | ||
7 | Required properties: | 7 | Required properties: |
8 | - compatible : should be "samsung,s3c2410-wdt" | 8 | - compatible : should be one among the following |
9 | (a) "samsung,s3c2410-wdt" for Exynos4 and previous SoCs | ||
10 | (b) "samsung,exynos5250-wdt" for Exynos5250 | ||
11 | (c) "samsung,exynos5420-wdt" for Exynos5420 | ||
12 | |||
9 | - reg : base physical address of the controller and length of memory mapped | 13 | - reg : base physical address of the controller and length of memory mapped |
10 | region. | 14 | region. |
11 | - interrupts : interrupt number to the cpu. | 15 | - interrupts : interrupt number to the cpu. |
16 | - samsung,syscon-phandle : reference to syscon node (This property required only | ||
17 | in case of compatible being "samsung,exynos5250-wdt" or "samsung,exynos5420-wdt". | ||
18 | In case of Exynos5250 and 5420 this property points to syscon node holding the PMU | ||
19 | base address) | ||
12 | 20 | ||
13 | Optional properties: | 21 | Optional properties: |
14 | - timeout-sec : contains the watchdog timeout in seconds. | 22 | - timeout-sec : contains the watchdog timeout in seconds. |
23 | |||
24 | Example: | ||
25 | |||
26 | watchdog@101D0000 { | ||
27 | compatible = "samsung,exynos5250-wdt"; | ||
28 | reg = <0x101D0000 0x100>; | ||
29 | interrupts = <0 42 0>; | ||
30 | clocks = <&clock 336>; | ||
31 | clock-names = "watchdog"; | ||
32 | samsung,syscon-phandle = <&pmu_syscon>; | ||
33 | }; | ||
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 7a2fedab0fb5..c11a3387335d 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -196,6 +196,7 @@ config S3C2410_WATCHDOG | |||
196 | tristate "S3C2410 Watchdog" | 196 | tristate "S3C2410 Watchdog" |
197 | depends on HAVE_S3C2410_WATCHDOG | 197 | depends on HAVE_S3C2410_WATCHDOG |
198 | select WATCHDOG_CORE | 198 | select WATCHDOG_CORE |
199 | select MFD_SYSCON if ARCH_EXYNOS5 | ||
199 | help | 200 | help |
200 | Watchdog timer block in the Samsung SoCs. This will reboot | 201 | Watchdog timer block in the Samsung SoCs. This will reboot |
201 | the system when the timer expires with the watchdog enabled. | 202 | the system when the timer expires with the watchdog enabled. |
diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index 8beaa1799df6..64f5470a6a0e 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c | |||
@@ -40,6 +40,8 @@ | |||
40 | #include <linux/slab.h> | 40 | #include <linux/slab.h> |
41 | #include <linux/err.h> | 41 | #include <linux/err.h> |
42 | #include <linux/of.h> | 42 | #include <linux/of.h> |
43 | #include <linux/mfd/syscon.h> | ||
44 | #include <linux/regmap.h> | ||
43 | 45 | ||
44 | #define S3C2410_WTCON 0x00 | 46 | #define S3C2410_WTCON 0x00 |
45 | #define S3C2410_WTDAT 0x04 | 47 | #define S3C2410_WTDAT 0x04 |
@@ -60,6 +62,10 @@ | |||
60 | #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) | 62 | #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) |
61 | #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) | 63 | #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) |
62 | 64 | ||
65 | #define EXYNOS5_WDT_DISABLE_REG_OFFSET 0x0408 | ||
66 | #define EXYNOS5_WDT_MASK_RESET_REG_OFFSET 0x040c | ||
67 | #define QUIRK_HAS_PMU_CONFIG (1 << 0) | ||
68 | |||
63 | static bool nowayout = WATCHDOG_NOWAYOUT; | 69 | static bool nowayout = WATCHDOG_NOWAYOUT; |
64 | static int tmr_margin; | 70 | static int tmr_margin; |
65 | static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; | 71 | static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; |
@@ -83,6 +89,25 @@ MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, " | |||
83 | "0 to reboot (default 0)"); | 89 | "0 to reboot (default 0)"); |
84 | MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)"); | 90 | MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)"); |
85 | 91 | ||
92 | /** | ||
93 | * struct s3c2410_wdt_variant - Per-variant config data | ||
94 | * | ||
95 | * @disable_reg: Offset in pmureg for the register that disables the watchdog | ||
96 | * timer reset functionality. | ||
97 | * @mask_reset_reg: Offset in pmureg for the register that masks the watchdog | ||
98 | * timer reset functionality. | ||
99 | * @mask_bit: Bit number for the watchdog timer in the disable register and the | ||
100 | * mask reset register. | ||
101 | * @quirks: A bitfield of quirks. | ||
102 | */ | ||
103 | |||
104 | struct s3c2410_wdt_variant { | ||
105 | int disable_reg; | ||
106 | int mask_reset_reg; | ||
107 | int mask_bit; | ||
108 | u32 quirks; | ||
109 | }; | ||
110 | |||
86 | struct s3c2410_wdt { | 111 | struct s3c2410_wdt { |
87 | struct device *dev; | 112 | struct device *dev; |
88 | struct clk *clock; | 113 | struct clk *clock; |
@@ -93,7 +118,49 @@ struct s3c2410_wdt { | |||
93 | unsigned long wtdat_save; | 118 | unsigned long wtdat_save; |
94 | struct watchdog_device wdt_device; | 119 | struct watchdog_device wdt_device; |
95 | struct notifier_block freq_transition; | 120 | struct notifier_block freq_transition; |
121 | struct s3c2410_wdt_variant *drv_data; | ||
122 | struct regmap *pmureg; | ||
123 | }; | ||
124 | |||
125 | static const struct s3c2410_wdt_variant drv_data_s3c2410 = { | ||
126 | .quirks = 0 | ||
127 | }; | ||
128 | |||
129 | #ifdef CONFIG_OF | ||
130 | static const struct s3c2410_wdt_variant drv_data_exynos5250 = { | ||
131 | .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET, | ||
132 | .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET, | ||
133 | .mask_bit = 20, | ||
134 | .quirks = QUIRK_HAS_PMU_CONFIG | ||
135 | }; | ||
136 | |||
137 | static const struct s3c2410_wdt_variant drv_data_exynos5420 = { | ||
138 | .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET, | ||
139 | .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET, | ||
140 | .mask_bit = 0, | ||
141 | .quirks = QUIRK_HAS_PMU_CONFIG | ||
142 | }; | ||
143 | |||
144 | static const struct of_device_id s3c2410_wdt_match[] = { | ||
145 | { .compatible = "samsung,s3c2410-wdt", | ||
146 | .data = &drv_data_s3c2410 }, | ||
147 | { .compatible = "samsung,exynos5250-wdt", | ||
148 | .data = &drv_data_exynos5250 }, | ||
149 | { .compatible = "samsung,exynos5420-wdt", | ||
150 | .data = &drv_data_exynos5420 }, | ||
151 | {}, | ||
96 | }; | 152 | }; |
153 | MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); | ||
154 | #endif | ||
155 | |||
156 | static const struct platform_device_id s3c2410_wdt_ids[] = { | ||
157 | { | ||
158 | .name = "s3c2410-wdt", | ||
159 | .driver_data = (unsigned long)&drv_data_s3c2410, | ||
160 | }, | ||
161 | {} | ||
162 | }; | ||
163 | MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids); | ||
97 | 164 | ||
98 | /* watchdog control routines */ | 165 | /* watchdog control routines */ |
99 | 166 | ||
@@ -110,6 +177,35 @@ static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb) | |||
110 | return container_of(nb, struct s3c2410_wdt, freq_transition); | 177 | return container_of(nb, struct s3c2410_wdt, freq_transition); |
111 | } | 178 | } |
112 | 179 | ||
180 | static int s3c2410wdt_mask_and_disable_reset(struct s3c2410_wdt *wdt, bool mask) | ||
181 | { | ||
182 | int ret; | ||
183 | u32 mask_val = 1 << wdt->drv_data->mask_bit; | ||
184 | u32 val = 0; | ||
185 | |||
186 | /* No need to do anything if no PMU CONFIG needed */ | ||
187 | if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG)) | ||
188 | return 0; | ||
189 | |||
190 | if (mask) | ||
191 | val = mask_val; | ||
192 | |||
193 | ret = regmap_update_bits(wdt->pmureg, | ||
194 | wdt->drv_data->disable_reg, | ||
195 | mask_val, val); | ||
196 | if (ret < 0) | ||
197 | goto error; | ||
198 | |||
199 | ret = regmap_update_bits(wdt->pmureg, | ||
200 | wdt->drv_data->mask_reset_reg, | ||
201 | mask_val, val); | ||
202 | error: | ||
203 | if (ret < 0) | ||
204 | dev_err(wdt->dev, "failed to update reg(%d)\n", ret); | ||
205 | |||
206 | return ret; | ||
207 | } | ||
208 | |||
113 | static int s3c2410wdt_keepalive(struct watchdog_device *wdd) | 209 | static int s3c2410wdt_keepalive(struct watchdog_device *wdd) |
114 | { | 210 | { |
115 | struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); | 211 | struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); |
@@ -328,6 +424,20 @@ static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt) | |||
328 | } | 424 | } |
329 | #endif | 425 | #endif |
330 | 426 | ||
427 | /* s3c2410_get_wdt_driver_data */ | ||
428 | static inline struct s3c2410_wdt_variant * | ||
429 | get_wdt_drv_data(struct platform_device *pdev) | ||
430 | { | ||
431 | if (pdev->dev.of_node) { | ||
432 | const struct of_device_id *match; | ||
433 | match = of_match_node(s3c2410_wdt_match, pdev->dev.of_node); | ||
434 | return (struct s3c2410_wdt_variant *)match->data; | ||
435 | } else { | ||
436 | return (struct s3c2410_wdt_variant *) | ||
437 | platform_get_device_id(pdev)->driver_data; | ||
438 | } | ||
439 | } | ||
440 | |||
331 | static int s3c2410wdt_probe(struct platform_device *pdev) | 441 | static int s3c2410wdt_probe(struct platform_device *pdev) |
332 | { | 442 | { |
333 | struct device *dev; | 443 | struct device *dev; |
@@ -350,6 +460,16 @@ static int s3c2410wdt_probe(struct platform_device *pdev) | |||
350 | spin_lock_init(&wdt->lock); | 460 | spin_lock_init(&wdt->lock); |
351 | wdt->wdt_device = s3c2410_wdd; | 461 | wdt->wdt_device = s3c2410_wdd; |
352 | 462 | ||
463 | wdt->drv_data = get_wdt_drv_data(pdev); | ||
464 | if (wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG) { | ||
465 | wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, | ||
466 | "samsung,syscon-phandle"); | ||
467 | if (IS_ERR(wdt->pmureg)) { | ||
468 | dev_err(dev, "syscon regmap lookup failed.\n"); | ||
469 | return PTR_ERR(wdt->pmureg); | ||
470 | } | ||
471 | } | ||
472 | |||
353 | wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | 473 | wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); |
354 | if (wdt_irq == NULL) { | 474 | if (wdt_irq == NULL) { |
355 | dev_err(dev, "no irq resource specified\n"); | 475 | dev_err(dev, "no irq resource specified\n"); |
@@ -418,6 +538,10 @@ static int s3c2410wdt_probe(struct platform_device *pdev) | |||
418 | goto err_cpufreq; | 538 | goto err_cpufreq; |
419 | } | 539 | } |
420 | 540 | ||
541 | ret = s3c2410wdt_mask_and_disable_reset(wdt, false); | ||
542 | if (ret < 0) | ||
543 | goto err_unregister; | ||
544 | |||
421 | if (tmr_atboot && started == 0) { | 545 | if (tmr_atboot && started == 0) { |
422 | dev_info(dev, "starting watchdog timer\n"); | 546 | dev_info(dev, "starting watchdog timer\n"); |
423 | s3c2410wdt_start(&wdt->wdt_device); | 547 | s3c2410wdt_start(&wdt->wdt_device); |
@@ -442,6 +566,9 @@ static int s3c2410wdt_probe(struct platform_device *pdev) | |||
442 | 566 | ||
443 | return 0; | 567 | return 0; |
444 | 568 | ||
569 | err_unregister: | ||
570 | watchdog_unregister_device(&wdt->wdt_device); | ||
571 | |||
445 | err_cpufreq: | 572 | err_cpufreq: |
446 | s3c2410wdt_cpufreq_deregister(wdt); | 573 | s3c2410wdt_cpufreq_deregister(wdt); |
447 | 574 | ||
@@ -455,8 +582,13 @@ static int s3c2410wdt_probe(struct platform_device *pdev) | |||
455 | 582 | ||
456 | static int s3c2410wdt_remove(struct platform_device *dev) | 583 | static int s3c2410wdt_remove(struct platform_device *dev) |
457 | { | 584 | { |
585 | int ret; | ||
458 | struct s3c2410_wdt *wdt = platform_get_drvdata(dev); | 586 | struct s3c2410_wdt *wdt = platform_get_drvdata(dev); |
459 | 587 | ||
588 | ret = s3c2410wdt_mask_and_disable_reset(wdt, true); | ||
589 | if (ret < 0) | ||
590 | return ret; | ||
591 | |||
460 | watchdog_unregister_device(&wdt->wdt_device); | 592 | watchdog_unregister_device(&wdt->wdt_device); |
461 | 593 | ||
462 | s3c2410wdt_cpufreq_deregister(wdt); | 594 | s3c2410wdt_cpufreq_deregister(wdt); |
@@ -471,6 +603,8 @@ static void s3c2410wdt_shutdown(struct platform_device *dev) | |||
471 | { | 603 | { |
472 | struct s3c2410_wdt *wdt = platform_get_drvdata(dev); | 604 | struct s3c2410_wdt *wdt = platform_get_drvdata(dev); |
473 | 605 | ||
606 | s3c2410wdt_mask_and_disable_reset(wdt, true); | ||
607 | |||
474 | s3c2410wdt_stop(&wdt->wdt_device); | 608 | s3c2410wdt_stop(&wdt->wdt_device); |
475 | } | 609 | } |
476 | 610 | ||
@@ -478,12 +612,17 @@ static void s3c2410wdt_shutdown(struct platform_device *dev) | |||
478 | 612 | ||
479 | static int s3c2410wdt_suspend(struct device *dev) | 613 | static int s3c2410wdt_suspend(struct device *dev) |
480 | { | 614 | { |
615 | int ret; | ||
481 | struct s3c2410_wdt *wdt = dev_get_drvdata(dev); | 616 | struct s3c2410_wdt *wdt = dev_get_drvdata(dev); |
482 | 617 | ||
483 | /* Save watchdog state, and turn it off. */ | 618 | /* Save watchdog state, and turn it off. */ |
484 | wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON); | 619 | wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON); |
485 | wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT); | 620 | wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT); |
486 | 621 | ||
622 | ret = s3c2410wdt_mask_and_disable_reset(wdt, true); | ||
623 | if (ret < 0) | ||
624 | return ret; | ||
625 | |||
487 | /* Note that WTCNT doesn't need to be saved. */ | 626 | /* Note that WTCNT doesn't need to be saved. */ |
488 | s3c2410wdt_stop(&wdt->wdt_device); | 627 | s3c2410wdt_stop(&wdt->wdt_device); |
489 | 628 | ||
@@ -492,6 +631,7 @@ static int s3c2410wdt_suspend(struct device *dev) | |||
492 | 631 | ||
493 | static int s3c2410wdt_resume(struct device *dev) | 632 | static int s3c2410wdt_resume(struct device *dev) |
494 | { | 633 | { |
634 | int ret; | ||
495 | struct s3c2410_wdt *wdt = dev_get_drvdata(dev); | 635 | struct s3c2410_wdt *wdt = dev_get_drvdata(dev); |
496 | 636 | ||
497 | /* Restore watchdog state. */ | 637 | /* Restore watchdog state. */ |
@@ -499,6 +639,10 @@ static int s3c2410wdt_resume(struct device *dev) | |||
499 | writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */ | 639 | writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */ |
500 | writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON); | 640 | writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON); |
501 | 641 | ||
642 | ret = s3c2410wdt_mask_and_disable_reset(wdt, false); | ||
643 | if (ret < 0) | ||
644 | return ret; | ||
645 | |||
502 | dev_info(dev, "watchdog %sabled\n", | 646 | dev_info(dev, "watchdog %sabled\n", |
503 | (wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); | 647 | (wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); |
504 | 648 | ||
@@ -509,18 +653,11 @@ static int s3c2410wdt_resume(struct device *dev) | |||
509 | static SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops, s3c2410wdt_suspend, | 653 | static SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops, s3c2410wdt_suspend, |
510 | s3c2410wdt_resume); | 654 | s3c2410wdt_resume); |
511 | 655 | ||
512 | #ifdef CONFIG_OF | ||
513 | static const struct of_device_id s3c2410_wdt_match[] = { | ||
514 | { .compatible = "samsung,s3c2410-wdt" }, | ||
515 | {}, | ||
516 | }; | ||
517 | MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); | ||
518 | #endif | ||
519 | |||
520 | static struct platform_driver s3c2410wdt_driver = { | 656 | static struct platform_driver s3c2410wdt_driver = { |
521 | .probe = s3c2410wdt_probe, | 657 | .probe = s3c2410wdt_probe, |
522 | .remove = s3c2410wdt_remove, | 658 | .remove = s3c2410wdt_remove, |
523 | .shutdown = s3c2410wdt_shutdown, | 659 | .shutdown = s3c2410wdt_shutdown, |
660 | .id_table = s3c2410_wdt_ids, | ||
524 | .driver = { | 661 | .driver = { |
525 | .owner = THIS_MODULE, | 662 | .owner = THIS_MODULE, |
526 | .name = "s3c2410-wdt", | 663 | .name = "s3c2410-wdt", |
@@ -535,4 +672,3 @@ MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, " | |||
535 | "Dimitry Andric <dimitry.andric@tomtom.com>"); | 672 | "Dimitry Andric <dimitry.andric@tomtom.com>"); |
536 | MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); | 673 | MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); |
537 | MODULE_LICENSE("GPL"); | 674 | MODULE_LICENSE("GPL"); |
538 | MODULE_ALIAS("platform:s3c2410-wdt"); | ||