diff options
author | Carlo Caione <carlo@caione.org> | 2014-09-20 13:06:50 -0400 |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2014-10-20 14:57:16 -0400 |
commit | 22e1b8f60f913cf71e688af9b64317b515303f4c (patch) | |
tree | 81f07aff045fed803296396e5f31cc1b3bf85106 | |
parent | 0c5691f00879cacf98a31b873c02d71c66d72855 (diff) |
ARM: meson: add watchdog driver
This patch adds the watchdog driver for the Amlogic Meson SoCs used also
to reboot the device.
Signed-off-by: Carlo Caione <carlo@caione.org>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
-rw-r--r-- | drivers/watchdog/Kconfig | 10 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/meson_wdt.c | 236 |
3 files changed, 247 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index a51ccf3461fb..9ff8588c0df6 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -494,6 +494,16 @@ config QCOM_WDT | |||
494 | To compile this driver as a module, choose M here: the | 494 | To compile this driver as a module, choose M here: the |
495 | module will be called qcom_wdt. | 495 | module will be called qcom_wdt. |
496 | 496 | ||
497 | config MESON_WATCHDOG | ||
498 | tristate "Amlogic Meson SoCs watchdog support" | ||
499 | depends on ARCH_MESON | ||
500 | select WATCHDOG_CORE | ||
501 | help | ||
502 | Say Y here to include support for the watchdog timer | ||
503 | in Amlogic Meson SoCs. | ||
504 | To compile this driver as a module, choose M here: the | ||
505 | module will be called meson_wdt. | ||
506 | |||
497 | # AVR32 Architecture | 507 | # AVR32 Architecture |
498 | 508 | ||
499 | config AT32AP700X_WDT | 509 | config AT32AP700X_WDT |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index f8eaaf45e923..c569ec8f8a76 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -62,6 +62,7 @@ obj-$(CONFIG_SIRFSOC_WATCHDOG) += sirfsoc_wdt.o | |||
62 | obj-$(CONFIG_QCOM_WDT) += qcom-wdt.o | 62 | obj-$(CONFIG_QCOM_WDT) += qcom-wdt.o |
63 | obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o | 63 | obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o |
64 | obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o | 64 | obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o |
65 | obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o | ||
65 | 66 | ||
66 | # AVR32 Architecture | 67 | # AVR32 Architecture |
67 | obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o | 68 | obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o |
diff --git a/drivers/watchdog/meson_wdt.c b/drivers/watchdog/meson_wdt.c new file mode 100644 index 000000000000..37f9f5ec6cb0 --- /dev/null +++ b/drivers/watchdog/meson_wdt.c | |||
@@ -0,0 +1,236 @@ | |||
1 | /* | ||
2 | * Meson Watchdog Driver | ||
3 | * | ||
4 | * Copyright (c) 2014 Carlo Caione | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License | ||
8 | * as published by the Free Software Foundation; either version | ||
9 | * 2 of the License, or (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/clk.h> | ||
13 | #include <linux/delay.h> | ||
14 | #include <linux/err.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/io.h> | ||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/moduleparam.h> | ||
20 | #include <linux/notifier.h> | ||
21 | #include <linux/of.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/reboot.h> | ||
24 | #include <linux/types.h> | ||
25 | #include <linux/watchdog.h> | ||
26 | |||
27 | #define DRV_NAME "meson_wdt" | ||
28 | |||
29 | #define MESON_WDT_TC 0x00 | ||
30 | #define MESON_WDT_TC_EN BIT(22) | ||
31 | #define MESON_WDT_TC_TM_MASK 0x3fffff | ||
32 | #define MESON_WDT_DC_RESET (3 << 24) | ||
33 | |||
34 | #define MESON_WDT_RESET 0x04 | ||
35 | |||
36 | #define MESON_WDT_TIMEOUT 30 | ||
37 | #define MESON_WDT_MIN_TIMEOUT 1 | ||
38 | #define MESON_WDT_MAX_TIMEOUT (MESON_WDT_TC_TM_MASK / 100000) | ||
39 | |||
40 | #define MESON_SEC_TO_TC(s) ((s) * 100000) | ||
41 | |||
42 | static bool nowayout = WATCHDOG_NOWAYOUT; | ||
43 | static unsigned int timeout = MESON_WDT_TIMEOUT; | ||
44 | |||
45 | struct meson_wdt_dev { | ||
46 | struct watchdog_device wdt_dev; | ||
47 | void __iomem *wdt_base; | ||
48 | struct notifier_block restart_handler; | ||
49 | }; | ||
50 | |||
51 | static int meson_restart_handle(struct notifier_block *this, unsigned long mode, | ||
52 | void *cmd) | ||
53 | { | ||
54 | u32 tc_reboot = MESON_WDT_DC_RESET | MESON_WDT_TC_EN | 100; | ||
55 | struct meson_wdt_dev *meson_wdt = container_of(this, | ||
56 | struct meson_wdt_dev, | ||
57 | restart_handler); | ||
58 | |||
59 | while (1) { | ||
60 | writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC); | ||
61 | mdelay(5); | ||
62 | } | ||
63 | |||
64 | return NOTIFY_DONE; | ||
65 | } | ||
66 | |||
67 | static int meson_wdt_ping(struct watchdog_device *wdt_dev) | ||
68 | { | ||
69 | struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); | ||
70 | |||
71 | writel(0, meson_wdt->wdt_base + MESON_WDT_RESET); | ||
72 | |||
73 | return 0; | ||
74 | } | ||
75 | |||
76 | static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev, | ||
77 | unsigned int timeout) | ||
78 | { | ||
79 | struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); | ||
80 | u32 reg; | ||
81 | |||
82 | reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); | ||
83 | reg &= ~MESON_WDT_TC_TM_MASK; | ||
84 | reg |= MESON_SEC_TO_TC(timeout); | ||
85 | writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); | ||
86 | } | ||
87 | |||
88 | static int meson_wdt_set_timeout(struct watchdog_device *wdt_dev, | ||
89 | unsigned int timeout) | ||
90 | { | ||
91 | wdt_dev->timeout = timeout; | ||
92 | |||
93 | meson_wdt_change_timeout(wdt_dev, timeout); | ||
94 | meson_wdt_ping(wdt_dev); | ||
95 | |||
96 | return 0; | ||
97 | } | ||
98 | |||
99 | static int meson_wdt_stop(struct watchdog_device *wdt_dev) | ||
100 | { | ||
101 | struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); | ||
102 | u32 reg; | ||
103 | |||
104 | reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); | ||
105 | reg &= ~MESON_WDT_TC_EN; | ||
106 | writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); | ||
107 | |||
108 | return 0; | ||
109 | } | ||
110 | |||
111 | static int meson_wdt_start(struct watchdog_device *wdt_dev) | ||
112 | { | ||
113 | struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); | ||
114 | u32 reg; | ||
115 | |||
116 | meson_wdt_change_timeout(wdt_dev, meson_wdt->wdt_dev.timeout); | ||
117 | meson_wdt_ping(wdt_dev); | ||
118 | |||
119 | reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); | ||
120 | reg |= MESON_WDT_TC_EN; | ||
121 | writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); | ||
122 | |||
123 | return 0; | ||
124 | } | ||
125 | |||
126 | static const struct watchdog_info meson_wdt_info = { | ||
127 | .identity = DRV_NAME, | ||
128 | .options = WDIOF_SETTIMEOUT | | ||
129 | WDIOF_KEEPALIVEPING | | ||
130 | WDIOF_MAGICCLOSE, | ||
131 | }; | ||
132 | |||
133 | static const struct watchdog_ops meson_wdt_ops = { | ||
134 | .owner = THIS_MODULE, | ||
135 | .start = meson_wdt_start, | ||
136 | .stop = meson_wdt_stop, | ||
137 | .ping = meson_wdt_ping, | ||
138 | .set_timeout = meson_wdt_set_timeout, | ||
139 | }; | ||
140 | |||
141 | static int meson_wdt_probe(struct platform_device *pdev) | ||
142 | { | ||
143 | struct resource *res; | ||
144 | struct meson_wdt_dev *meson_wdt; | ||
145 | int err; | ||
146 | |||
147 | meson_wdt = devm_kzalloc(&pdev->dev, sizeof(*meson_wdt), GFP_KERNEL); | ||
148 | if (!meson_wdt) | ||
149 | return -ENOMEM; | ||
150 | |||
151 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
152 | meson_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); | ||
153 | if (IS_ERR(meson_wdt->wdt_base)) | ||
154 | return PTR_ERR(meson_wdt->wdt_base); | ||
155 | |||
156 | meson_wdt->wdt_dev.parent = &pdev->dev; | ||
157 | meson_wdt->wdt_dev.info = &meson_wdt_info; | ||
158 | meson_wdt->wdt_dev.ops = &meson_wdt_ops; | ||
159 | meson_wdt->wdt_dev.timeout = MESON_WDT_TIMEOUT; | ||
160 | meson_wdt->wdt_dev.max_timeout = MESON_WDT_MAX_TIMEOUT; | ||
161 | meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT; | ||
162 | |||
163 | watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt); | ||
164 | |||
165 | watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, &pdev->dev); | ||
166 | watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout); | ||
167 | |||
168 | meson_wdt_stop(&meson_wdt->wdt_dev); | ||
169 | |||
170 | err = watchdog_register_device(&meson_wdt->wdt_dev); | ||
171 | if (err) | ||
172 | return err; | ||
173 | |||
174 | platform_set_drvdata(pdev, meson_wdt); | ||
175 | |||
176 | meson_wdt->restart_handler.notifier_call = meson_restart_handle; | ||
177 | meson_wdt->restart_handler.priority = 128; | ||
178 | err = register_restart_handler(&meson_wdt->restart_handler); | ||
179 | if (err) | ||
180 | dev_err(&pdev->dev, | ||
181 | "cannot register restart handler (err=%d)\n", err); | ||
182 | |||
183 | dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", | ||
184 | meson_wdt->wdt_dev.timeout, nowayout); | ||
185 | |||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | static int meson_wdt_remove(struct platform_device *pdev) | ||
190 | { | ||
191 | struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev); | ||
192 | |||
193 | unregister_restart_handler(&meson_wdt->restart_handler); | ||
194 | |||
195 | watchdog_unregister_device(&meson_wdt->wdt_dev); | ||
196 | |||
197 | return 0; | ||
198 | } | ||
199 | |||
200 | static void meson_wdt_shutdown(struct platform_device *pdev) | ||
201 | { | ||
202 | struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev); | ||
203 | |||
204 | meson_wdt_stop(&meson_wdt->wdt_dev); | ||
205 | } | ||
206 | |||
207 | static const struct of_device_id meson_wdt_dt_ids[] = { | ||
208 | { .compatible = "amlogic,meson6-wdt" }, | ||
209 | { /* sentinel */ } | ||
210 | }; | ||
211 | MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids); | ||
212 | |||
213 | static struct platform_driver meson_wdt_driver = { | ||
214 | .probe = meson_wdt_probe, | ||
215 | .remove = meson_wdt_remove, | ||
216 | .shutdown = meson_wdt_shutdown, | ||
217 | .driver = { | ||
218 | .owner = THIS_MODULE, | ||
219 | .name = DRV_NAME, | ||
220 | .of_match_table = meson_wdt_dt_ids, | ||
221 | }, | ||
222 | }; | ||
223 | |||
224 | module_platform_driver(meson_wdt_driver); | ||
225 | |||
226 | module_param(timeout, uint, 0); | ||
227 | MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); | ||
228 | |||
229 | module_param(nowayout, bool, 0); | ||
230 | MODULE_PARM_DESC(nowayout, | ||
231 | "Watchdog cannot be stopped once started (default=" | ||
232 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
233 | |||
234 | MODULE_LICENSE("GPL"); | ||
235 | MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); | ||
236 | MODULE_DESCRIPTION("Meson Watchdog Timer Driver"); | ||