diff options
Diffstat (limited to 'drivers/watchdog/s3c2410_wdt.c')
-rw-r--r-- | drivers/watchdog/s3c2410_wdt.c | 203 |
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 | |||
63 | static bool nowayout = WATCHDOG_NOWAYOUT; | 75 | static bool nowayout = WATCHDOG_NOWAYOUT; |
64 | static int tmr_margin; | 76 | static int tmr_margin; |
65 | static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; | 77 | static 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)"); |
84 | MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)"); | 96 | MODULE_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 | |||
113 | struct 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 | |||
86 | struct s3c2410_wdt { | 122 | struct 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 | ||
136 | static const struct s3c2410_wdt_variant drv_data_s3c2410 = { | ||
137 | .quirks = 0 | ||
138 | }; | ||
139 | |||
140 | #ifdef CONFIG_OF | ||
141 | static 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 | |||
150 | static 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 | |||
159 | static 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 | }; | ||
168 | MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); | ||
169 | #endif | ||
170 | |||
171 | static const struct platform_device_id s3c2410_wdt_ids[] = { | ||
172 | { | ||
173 | .name = "s3c2410-wdt", | ||
174 | .driver_data = (unsigned long)&drv_data_s3c2410, | ||
175 | }, | ||
176 | {} | ||
177 | }; | ||
178 | MODULE_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 | ||
195 | static 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 | |||
113 | static int s3c2410wdt_keepalive(struct watchdog_device *wdd) | 224 | static 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 | ||
269 | static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb, | 377 | static 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 | ||
442 | static 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 */ | ||
460 | static inline struct s3c2410_wdt_variant * | ||
461 | get_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 | |||
334 | static int s3c2410wdt_probe(struct platform_device *pdev) | 473 | static 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 | ||
459 | static int s3c2410wdt_remove(struct platform_device *dev) | 617 | static 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 | ||
482 | static int s3c2410wdt_suspend(struct device *dev) | 647 | static 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 | ||
496 | static int s3c2410wdt_resume(struct device *dev) | 666 | static 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) | |||
512 | static SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops, s3c2410wdt_suspend, | 687 | static SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops, s3c2410wdt_suspend, |
513 | s3c2410wdt_resume); | 688 | s3c2410wdt_resume); |
514 | 689 | ||
515 | #ifdef CONFIG_OF | ||
516 | static const struct of_device_id s3c2410_wdt_match[] = { | ||
517 | { .compatible = "samsung,s3c2410-wdt" }, | ||
518 | {}, | ||
519 | }; | ||
520 | MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); | ||
521 | #endif | ||
522 | |||
523 | static struct platform_driver s3c2410wdt_driver = { | 690 | static 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>"); |
539 | MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); | 707 | MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); |
540 | MODULE_LICENSE("GPL"); | 708 | MODULE_LICENSE("GPL"); |
541 | MODULE_ALIAS("platform:s3c2410-wdt"); | ||