aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/s3c2410_wdt.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog/s3c2410_wdt.c')
-rw-r--r--drivers/watchdog/s3c2410_wdt.c203
1 files changed, 185 insertions, 18 deletions
diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c
index 7d8fd041ee25..aec946df6ed9 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,16 @@
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_RST_STAT_REG_OFFSET 0x0404
66#define EXYNOS5_WDT_DISABLE_REG_OFFSET 0x0408
67#define EXYNOS5_WDT_MASK_RESET_REG_OFFSET 0x040c
68#define QUIRK_HAS_PMU_CONFIG (1 << 0)
69#define QUIRK_HAS_RST_STAT (1 << 1)
70
71/* These quirks require that we have a PMU register map */
72#define QUIRKS_HAVE_PMUREG (QUIRK_HAS_PMU_CONFIG | \
73 QUIRK_HAS_RST_STAT)
74
63static bool nowayout = WATCHDOG_NOWAYOUT; 75static bool nowayout = WATCHDOG_NOWAYOUT;
64static int tmr_margin; 76static int tmr_margin;
65static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; 77static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT;
@@ -83,6 +95,30 @@ MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, "
83 "0 to reboot (default 0)"); 95 "0 to reboot (default 0)");
84MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)"); 96MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)");
85 97
98/**
99 * struct s3c2410_wdt_variant - Per-variant config data
100 *
101 * @disable_reg: Offset in pmureg for the register that disables the watchdog
102 * timer reset functionality.
103 * @mask_reset_reg: Offset in pmureg for the register that masks the watchdog
104 * timer reset functionality.
105 * @mask_bit: Bit number for the watchdog timer in the disable register and the
106 * mask reset register.
107 * @rst_stat_reg: Offset in pmureg for the register that has the reset status.
108 * @rst_stat_bit: Bit number in the rst_stat register indicating a watchdog
109 * reset.
110 * @quirks: A bitfield of quirks.
111 */
112
113struct s3c2410_wdt_variant {
114 int disable_reg;
115 int mask_reset_reg;
116 int mask_bit;
117 int rst_stat_reg;
118 int rst_stat_bit;
119 u32 quirks;
120};
121
86struct s3c2410_wdt { 122struct s3c2410_wdt {
87 struct device *dev; 123 struct device *dev;
88 struct clk *clock; 124 struct clk *clock;
@@ -93,8 +129,54 @@ struct s3c2410_wdt {
93 unsigned long wtdat_save; 129 unsigned long wtdat_save;
94 struct watchdog_device wdt_device; 130 struct watchdog_device wdt_device;
95 struct notifier_block freq_transition; 131 struct notifier_block freq_transition;
132 struct s3c2410_wdt_variant *drv_data;
133 struct regmap *pmureg;
96}; 134};
97 135
136static const struct s3c2410_wdt_variant drv_data_s3c2410 = {
137 .quirks = 0
138};
139
140#ifdef CONFIG_OF
141static const struct s3c2410_wdt_variant drv_data_exynos5250 = {
142 .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
143 .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
144 .mask_bit = 20,
145 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
146 .rst_stat_bit = 20,
147 .quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT,
148};
149
150static const struct s3c2410_wdt_variant drv_data_exynos5420 = {
151 .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
152 .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
153 .mask_bit = 0,
154 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
155 .rst_stat_bit = 9,
156 .quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT,
157};
158
159static const struct of_device_id s3c2410_wdt_match[] = {
160 { .compatible = "samsung,s3c2410-wdt",
161 .data = &drv_data_s3c2410 },
162 { .compatible = "samsung,exynos5250-wdt",
163 .data = &drv_data_exynos5250 },
164 { .compatible = "samsung,exynos5420-wdt",
165 .data = &drv_data_exynos5420 },
166 {},
167};
168MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
169#endif
170
171static const struct platform_device_id s3c2410_wdt_ids[] = {
172 {
173 .name = "s3c2410-wdt",
174 .driver_data = (unsigned long)&drv_data_s3c2410,
175 },
176 {}
177};
178MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids);
179
98/* watchdog control routines */ 180/* watchdog control routines */
99 181
100#define DBG(fmt, ...) \ 182#define DBG(fmt, ...) \
@@ -110,6 +192,35 @@ static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb)
110 return container_of(nb, struct s3c2410_wdt, freq_transition); 192 return container_of(nb, struct s3c2410_wdt, freq_transition);
111} 193}
112 194
195static int s3c2410wdt_mask_and_disable_reset(struct s3c2410_wdt *wdt, bool mask)
196{
197 int ret;
198 u32 mask_val = 1 << wdt->drv_data->mask_bit;
199 u32 val = 0;
200
201 /* No need to do anything if no PMU CONFIG needed */
202 if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG))
203 return 0;
204
205 if (mask)
206 val = mask_val;
207
208 ret = regmap_update_bits(wdt->pmureg,
209 wdt->drv_data->disable_reg,
210 mask_val, val);
211 if (ret < 0)
212 goto error;
213
214 ret = regmap_update_bits(wdt->pmureg,
215 wdt->drv_data->mask_reset_reg,
216 mask_val, val);
217 error:
218 if (ret < 0)
219 dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
220
221 return ret;
222}
223
113static int s3c2410wdt_keepalive(struct watchdog_device *wdd) 224static int s3c2410wdt_keepalive(struct watchdog_device *wdd)
114{ 225{
115 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 226 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
@@ -188,7 +299,7 @@ static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeou
188 if (timeout < 1) 299 if (timeout < 1)
189 return -EINVAL; 300 return -EINVAL;
190 301
191 freq /= 128; 302 freq = DIV_ROUND_UP(freq, 128);
192 count = timeout * freq; 303 count = timeout * freq;
193 304
194 DBG("%s: count=%d, timeout=%d, freq=%lu\n", 305 DBG("%s: count=%d, timeout=%d, freq=%lu\n",
@@ -200,21 +311,18 @@ static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeou
200 */ 311 */
201 312
202 if (count >= 0x10000) { 313 if (count >= 0x10000) {
203 for (divisor = 1; divisor <= 0x100; divisor++) { 314 divisor = DIV_ROUND_UP(count, 0xffff);
204 if ((count / divisor) < 0x10000)
205 break;
206 }
207 315
208 if ((count / divisor) >= 0x10000) { 316 if (divisor > 0x100) {
209 dev_err(wdt->dev, "timeout %d too big\n", timeout); 317 dev_err(wdt->dev, "timeout %d too big\n", timeout);
210 return -EINVAL; 318 return -EINVAL;
211 } 319 }
212 } 320 }
213 321
214 DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", 322 DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
215 __func__, timeout, divisor, count, count/divisor); 323 __func__, timeout, divisor, count, DIV_ROUND_UP(count, divisor));
216 324
217 count /= divisor; 325 count = DIV_ROUND_UP(count, divisor);
218 wdt->count = count; 326 wdt->count = count;
219 327
220 /* update the pre-scaler */ 328 /* update the pre-scaler */
@@ -264,7 +372,7 @@ static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
264 return IRQ_HANDLED; 372 return IRQ_HANDLED;
265} 373}
266 374
267#ifdef CONFIG_CPU_FREQ 375#ifdef CONFIG_ARM_S3C24XX_CPUFREQ
268 376
269static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb, 377static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb,
270 unsigned long val, void *data) 378 unsigned long val, void *data)
@@ -331,6 +439,37 @@ static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt)
331} 439}
332#endif 440#endif
333 441
442static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt)
443{
444 unsigned int rst_stat;
445 int ret;
446
447 if (!(wdt->drv_data->quirks & QUIRK_HAS_RST_STAT))
448 return 0;
449
450 ret = regmap_read(wdt->pmureg, wdt->drv_data->rst_stat_reg, &rst_stat);
451 if (ret)
452 dev_warn(wdt->dev, "Couldn't get RST_STAT register\n");
453 else if (rst_stat & BIT(wdt->drv_data->rst_stat_bit))
454 return WDIOF_CARDRESET;
455
456 return 0;
457}
458
459/* s3c2410_get_wdt_driver_data */
460static inline struct s3c2410_wdt_variant *
461get_wdt_drv_data(struct platform_device *pdev)
462{
463 if (pdev->dev.of_node) {
464 const struct of_device_id *match;
465 match = of_match_node(s3c2410_wdt_match, pdev->dev.of_node);
466 return (struct s3c2410_wdt_variant *)match->data;
467 } else {
468 return (struct s3c2410_wdt_variant *)
469 platform_get_device_id(pdev)->driver_data;
470 }
471}
472
334static int s3c2410wdt_probe(struct platform_device *pdev) 473static int s3c2410wdt_probe(struct platform_device *pdev)
335{ 474{
336 struct device *dev; 475 struct device *dev;
@@ -353,6 +492,16 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
353 spin_lock_init(&wdt->lock); 492 spin_lock_init(&wdt->lock);
354 wdt->wdt_device = s3c2410_wdd; 493 wdt->wdt_device = s3c2410_wdd;
355 494
495 wdt->drv_data = get_wdt_drv_data(pdev);
496 if (wdt->drv_data->quirks & QUIRKS_HAVE_PMUREG) {
497 wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
498 "samsung,syscon-phandle");
499 if (IS_ERR(wdt->pmureg)) {
500 dev_err(dev, "syscon regmap lookup failed.\n");
501 return PTR_ERR(wdt->pmureg);
502 }
503 }
504
356 wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 505 wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
357 if (wdt_irq == NULL) { 506 if (wdt_irq == NULL) {
358 dev_err(dev, "no irq resource specified\n"); 507 dev_err(dev, "no irq resource specified\n");
@@ -415,12 +564,18 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
415 564
416 watchdog_set_nowayout(&wdt->wdt_device, nowayout); 565 watchdog_set_nowayout(&wdt->wdt_device, nowayout);
417 566
567 wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt);
568
418 ret = watchdog_register_device(&wdt->wdt_device); 569 ret = watchdog_register_device(&wdt->wdt_device);
419 if (ret) { 570 if (ret) {
420 dev_err(dev, "cannot register watchdog (%d)\n", ret); 571 dev_err(dev, "cannot register watchdog (%d)\n", ret);
421 goto err_cpufreq; 572 goto err_cpufreq;
422 } 573 }
423 574
575 ret = s3c2410wdt_mask_and_disable_reset(wdt, false);
576 if (ret < 0)
577 goto err_unregister;
578
424 if (tmr_atboot && started == 0) { 579 if (tmr_atboot && started == 0) {
425 dev_info(dev, "starting watchdog timer\n"); 580 dev_info(dev, "starting watchdog timer\n");
426 s3c2410wdt_start(&wdt->wdt_device); 581 s3c2410wdt_start(&wdt->wdt_device);
@@ -445,6 +600,9 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
445 600
446 return 0; 601 return 0;
447 602
603 err_unregister:
604 watchdog_unregister_device(&wdt->wdt_device);
605
448 err_cpufreq: 606 err_cpufreq:
449 s3c2410wdt_cpufreq_deregister(wdt); 607 s3c2410wdt_cpufreq_deregister(wdt);
450 608
@@ -458,8 +616,13 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
458 616
459static int s3c2410wdt_remove(struct platform_device *dev) 617static int s3c2410wdt_remove(struct platform_device *dev)
460{ 618{
619 int ret;
461 struct s3c2410_wdt *wdt = platform_get_drvdata(dev); 620 struct s3c2410_wdt *wdt = platform_get_drvdata(dev);
462 621
622 ret = s3c2410wdt_mask_and_disable_reset(wdt, true);
623 if (ret < 0)
624 return ret;
625
463 watchdog_unregister_device(&wdt->wdt_device); 626 watchdog_unregister_device(&wdt->wdt_device);
464 627
465 s3c2410wdt_cpufreq_deregister(wdt); 628 s3c2410wdt_cpufreq_deregister(wdt);
@@ -474,6 +637,8 @@ static void s3c2410wdt_shutdown(struct platform_device *dev)
474{ 637{
475 struct s3c2410_wdt *wdt = platform_get_drvdata(dev); 638 struct s3c2410_wdt *wdt = platform_get_drvdata(dev);
476 639
640 s3c2410wdt_mask_and_disable_reset(wdt, true);
641
477 s3c2410wdt_stop(&wdt->wdt_device); 642 s3c2410wdt_stop(&wdt->wdt_device);
478} 643}
479 644
@@ -481,12 +646,17 @@ static void s3c2410wdt_shutdown(struct platform_device *dev)
481 646
482static int s3c2410wdt_suspend(struct device *dev) 647static int s3c2410wdt_suspend(struct device *dev)
483{ 648{
649 int ret;
484 struct s3c2410_wdt *wdt = dev_get_drvdata(dev); 650 struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
485 651
486 /* Save watchdog state, and turn it off. */ 652 /* Save watchdog state, and turn it off. */
487 wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON); 653 wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON);
488 wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT); 654 wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT);
489 655
656 ret = s3c2410wdt_mask_and_disable_reset(wdt, true);
657 if (ret < 0)
658 return ret;
659
490 /* Note that WTCNT doesn't need to be saved. */ 660 /* Note that WTCNT doesn't need to be saved. */
491 s3c2410wdt_stop(&wdt->wdt_device); 661 s3c2410wdt_stop(&wdt->wdt_device);
492 662
@@ -495,6 +665,7 @@ static int s3c2410wdt_suspend(struct device *dev)
495 665
496static int s3c2410wdt_resume(struct device *dev) 666static int s3c2410wdt_resume(struct device *dev)
497{ 667{
668 int ret;
498 struct s3c2410_wdt *wdt = dev_get_drvdata(dev); 669 struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
499 670
500 /* Restore watchdog state. */ 671 /* Restore watchdog state. */
@@ -502,6 +673,10 @@ static int s3c2410wdt_resume(struct device *dev)
502 writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */ 673 writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */
503 writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON); 674 writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON);
504 675
676 ret = s3c2410wdt_mask_and_disable_reset(wdt, false);
677 if (ret < 0)
678 return ret;
679
505 dev_info(dev, "watchdog %sabled\n", 680 dev_info(dev, "watchdog %sabled\n",
506 (wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); 681 (wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
507 682
@@ -512,18 +687,11 @@ static int s3c2410wdt_resume(struct device *dev)
512static SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops, s3c2410wdt_suspend, 687static SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops, s3c2410wdt_suspend,
513 s3c2410wdt_resume); 688 s3c2410wdt_resume);
514 689
515#ifdef CONFIG_OF
516static const struct of_device_id s3c2410_wdt_match[] = {
517 { .compatible = "samsung,s3c2410-wdt" },
518 {},
519};
520MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
521#endif
522
523static struct platform_driver s3c2410wdt_driver = { 690static struct platform_driver s3c2410wdt_driver = {
524 .probe = s3c2410wdt_probe, 691 .probe = s3c2410wdt_probe,
525 .remove = s3c2410wdt_remove, 692 .remove = s3c2410wdt_remove,
526 .shutdown = s3c2410wdt_shutdown, 693 .shutdown = s3c2410wdt_shutdown,
694 .id_table = s3c2410_wdt_ids,
527 .driver = { 695 .driver = {
528 .owner = THIS_MODULE, 696 .owner = THIS_MODULE,
529 .name = "s3c2410-wdt", 697 .name = "s3c2410-wdt",
@@ -538,4 +706,3 @@ MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, "
538 "Dimitry Andric <dimitry.andric@tomtom.com>"); 706 "Dimitry Andric <dimitry.andric@tomtom.com>");
539MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); 707MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
540MODULE_LICENSE("GPL"); 708MODULE_LICENSE("GPL");
541MODULE_ALIAS("platform:s3c2410-wdt");