]>
Commit | Line | Data |
---|---|---|
22ac9232 | 1 | /* |
3b937a7d | 2 | * drivers/watchdog/orion_wdt.c |
22ac9232 | 3 | * |
3b937a7d | 4 | * Watchdog driver for Orion/Kirkwood processors |
22ac9232 SB |
5 | * |
6 | * Author: Sylver Bruneau <sylver.bruneau@googlemail.com> | |
7 | * | |
8 | * This file is licensed under the terms of the GNU General Public | |
9 | * License version 2. This program is licensed "as is" without any | |
10 | * warranty of any kind, whether express or implied. | |
11 | */ | |
12 | ||
27c766aa JP |
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
14 | ||
22ac9232 SB |
15 | #include <linux/module.h> |
16 | #include <linux/moduleparam.h> | |
17 | #include <linux/types.h> | |
18 | #include <linux/kernel.h> | |
9e058d4f | 19 | #include <linux/platform_device.h> |
22ac9232 SB |
20 | #include <linux/watchdog.h> |
21 | #include <linux/init.h> | |
e97662e1 | 22 | #include <linux/interrupt.h> |
22ac9232 | 23 | #include <linux/io.h> |
4f04be62 | 24 | #include <linux/clk.h> |
0dd6e484 | 25 | #include <linux/err.h> |
1e7bad0f | 26 | #include <linux/of.h> |
22ac9232 | 27 | |
868eb616 EG |
28 | /* RSTOUT mask register physical address for Orion5x, Kirkwood and Dove */ |
29 | #define ORION_RSTOUT_MASK_OFFSET 0x20108 | |
30 | ||
31 | /* Internal registers can be configured at any 1 MiB aligned address */ | |
32 | #define INTERNAL_REGS_MASK ~(SZ_1M - 1) | |
33 | ||
22ac9232 SB |
34 | /* |
35 | * Watchdog timer block registers. | |
36 | */ | |
a855a7ce | 37 | #define TIMER_CTRL 0x0000 |
0dd6e484 | 38 | #define WDT_EN 0x0010 |
a855a7ce | 39 | #define WDT_VAL 0x0024 |
22ac9232 | 40 | |
9e058d4f | 41 | #define WDT_MAX_CYCLE_COUNT 0xffffffff |
22ac9232 | 42 | |
fa142ff5 | 43 | #define WDT_RESET_OUT_EN BIT(1) |
fa142ff5 | 44 | |
86a1e189 | 45 | static bool nowayout = WATCHDOG_NOWAYOUT; |
9e058d4f TR |
46 | static int heartbeat = -1; /* module parameter (seconds) */ |
47 | static unsigned int wdt_max_duration; /* (seconds) */ | |
4f04be62 | 48 | static struct clk *clk; |
9e058d4f | 49 | static unsigned int wdt_tclk; |
a855a7ce | 50 | static void __iomem *wdt_reg; |
868eb616 | 51 | static void __iomem *wdt_rstout; |
22ac9232 | 52 | |
0dd6e484 | 53 | static int orion_wdt_ping(struct watchdog_device *wdt_dev) |
df6707b2 | 54 | { |
df6707b2 | 55 | /* Reload watchdog duration */ |
0dd6e484 | 56 | writel(wdt_tclk * wdt_dev->timeout, wdt_reg + WDT_VAL); |
0dd6e484 | 57 | return 0; |
df6707b2 TR |
58 | } |
59 | ||
0dd6e484 | 60 | static int orion_wdt_start(struct watchdog_device *wdt_dev) |
22ac9232 | 61 | { |
22ac9232 | 62 | /* Set watchdog duration */ |
0dd6e484 | 63 | writel(wdt_tclk * wdt_dev->timeout, wdt_reg + WDT_VAL); |
22ac9232 | 64 | |
22ac9232 | 65 | /* Enable watchdog timer */ |
fc8cd2ac | 66 | atomic_io_modify(wdt_reg + TIMER_CTRL, WDT_EN, WDT_EN); |
22ac9232 SB |
67 | |
68 | /* Enable reset on watchdog */ | |
868eb616 | 69 | atomic_io_modify(wdt_rstout, WDT_RESET_OUT_EN, WDT_RESET_OUT_EN); |
0dd6e484 | 70 | return 0; |
22ac9232 SB |
71 | } |
72 | ||
0dd6e484 | 73 | static int orion_wdt_stop(struct watchdog_device *wdt_dev) |
22ac9232 | 74 | { |
22ac9232 | 75 | /* Disable reset on watchdog */ |
868eb616 | 76 | atomic_io_modify(wdt_rstout, WDT_RESET_OUT_EN, 0); |
22ac9232 SB |
77 | |
78 | /* Disable watchdog timer */ | |
fc8cd2ac | 79 | atomic_io_modify(wdt_reg + TIMER_CTRL, WDT_EN, 0); |
0dd6e484 | 80 | return 0; |
6d0f0dfd WVS |
81 | } |
82 | ||
d9d0c53d EG |
83 | static int orion_wdt_enabled(void) |
84 | { | |
85 | bool enabled, running; | |
86 | ||
868eb616 | 87 | enabled = readl(wdt_rstout) & WDT_RESET_OUT_EN; |
d9d0c53d EG |
88 | running = readl(wdt_reg + TIMER_CTRL) & WDT_EN; |
89 | ||
90 | return enabled && running; | |
91 | } | |
92 | ||
0dd6e484 | 93 | static unsigned int orion_wdt_get_timeleft(struct watchdog_device *wdt_dev) |
6d0f0dfd | 94 | { |
fc8cd2ac | 95 | return readl(wdt_reg + WDT_VAL) / wdt_tclk; |
22ac9232 SB |
96 | } |
97 | ||
0dd6e484 AL |
98 | static int orion_wdt_set_timeout(struct watchdog_device *wdt_dev, |
99 | unsigned int timeout) | |
22ac9232 | 100 | { |
0dd6e484 | 101 | wdt_dev->timeout = timeout; |
df6707b2 TR |
102 | return 0; |
103 | } | |
104 | ||
0dd6e484 AL |
105 | static const struct watchdog_info orion_wdt_info = { |
106 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | |
107 | .identity = "Orion Watchdog", | |
22ac9232 SB |
108 | }; |
109 | ||
0dd6e484 AL |
110 | static const struct watchdog_ops orion_wdt_ops = { |
111 | .owner = THIS_MODULE, | |
112 | .start = orion_wdt_start, | |
113 | .stop = orion_wdt_stop, | |
114 | .ping = orion_wdt_ping, | |
115 | .set_timeout = orion_wdt_set_timeout, | |
116 | .get_timeleft = orion_wdt_get_timeleft, | |
22ac9232 SB |
117 | }; |
118 | ||
0dd6e484 AL |
119 | static struct watchdog_device orion_wdt = { |
120 | .info = &orion_wdt_info, | |
121 | .ops = &orion_wdt_ops, | |
c1fd5f64 | 122 | .min_timeout = 1, |
22ac9232 SB |
123 | }; |
124 | ||
e97662e1 EG |
125 | static irqreturn_t orion_wdt_irq(int irq, void *devid) |
126 | { | |
127 | panic("Watchdog Timeout"); | |
128 | return IRQ_HANDLED; | |
129 | } | |
130 | ||
868eb616 EG |
131 | /* |
132 | * The original devicetree binding for this driver specified only | |
133 | * one memory resource, so in order to keep DT backwards compatibility | |
134 | * we try to fallback to a hardcoded register address, if the resource | |
135 | * is missing from the devicetree. | |
136 | */ | |
137 | static void __iomem *orion_wdt_ioremap_rstout(struct platform_device *pdev, | |
138 | phys_addr_t internal_regs) | |
139 | { | |
140 | struct resource *res; | |
141 | phys_addr_t rstout; | |
142 | ||
143 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | |
144 | if (res) | |
145 | return devm_ioremap(&pdev->dev, res->start, | |
146 | resource_size(res)); | |
147 | ||
148 | /* This workaround works only for "orion-wdt", DT-enabled */ | |
149 | if (!of_device_is_compatible(pdev->dev.of_node, "marvell,orion-wdt")) | |
150 | return NULL; | |
151 | ||
152 | rstout = internal_regs + ORION_RSTOUT_MASK_OFFSET; | |
153 | ||
154 | WARN(1, FW_BUG "falling back to harcoded RSTOUT reg 0x%x\n", rstout); | |
155 | return devm_ioremap(&pdev->dev, rstout, 0x4); | |
156 | } | |
157 | ||
2d991a16 | 158 | static int orion_wdt_probe(struct platform_device *pdev) |
22ac9232 | 159 | { |
a855a7ce | 160 | struct resource *res; |
e97662e1 | 161 | int ret, irq; |
22ac9232 | 162 | |
0dd6e484 | 163 | clk = devm_clk_get(&pdev->dev, NULL); |
4f04be62 | 164 | if (IS_ERR(clk)) { |
0dd6e484 | 165 | dev_err(&pdev->dev, "Orion Watchdog missing clock\n"); |
bb02c662 | 166 | return PTR_ERR(clk); |
9e058d4f | 167 | } |
bb02c662 EG |
168 | ret = clk_prepare_enable(clk); |
169 | if (ret) | |
170 | return ret; | |
4f04be62 | 171 | wdt_tclk = clk_get_rate(clk); |
9e058d4f | 172 | |
a855a7ce | 173 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
bb02c662 EG |
174 | if (!res) { |
175 | ret = -ENODEV; | |
176 | goto disable_clk; | |
177 | } | |
178 | ||
0dd6e484 | 179 | wdt_reg = devm_ioremap(&pdev->dev, res->start, resource_size(res)); |
bb02c662 EG |
180 | if (!wdt_reg) { |
181 | ret = -ENOMEM; | |
182 | goto disable_clk; | |
183 | } | |
9e058d4f | 184 | |
868eb616 EG |
185 | wdt_rstout = orion_wdt_ioremap_rstout(pdev, res->start & |
186 | INTERNAL_REGS_MASK); | |
187 | if (!wdt_rstout) { | |
188 | ret = -ENODEV; | |
189 | goto disable_clk; | |
190 | } | |
191 | ||
9e058d4f | 192 | wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk; |
0dd6e484 | 193 | |
c1fd5f64 | 194 | orion_wdt.timeout = wdt_max_duration; |
0dd6e484 | 195 | orion_wdt.max_timeout = wdt_max_duration; |
c1fd5f64 | 196 | watchdog_init_timeout(&orion_wdt, heartbeat, &pdev->dev); |
0dd6e484 | 197 | |
d9d0c53d EG |
198 | /* |
199 | * Let's make sure the watchdog is fully stopped, unless it's | |
200 | * explicitly enabled. This may be the case if the module was | |
201 | * removed and re-insterted, or if the bootloader explicitly | |
202 | * set a running watchdog before booting the kernel. | |
203 | */ | |
204 | if (!orion_wdt_enabled()) | |
205 | orion_wdt_stop(&orion_wdt); | |
206 | ||
e97662e1 EG |
207 | /* Request the IRQ only after the watchdog is disabled */ |
208 | irq = platform_get_irq(pdev, 0); | |
209 | if (irq > 0) { | |
210 | /* | |
211 | * Not all supported platforms specify an interrupt for the | |
212 | * watchdog, so let's make it optional. | |
213 | */ | |
214 | ret = devm_request_irq(&pdev->dev, irq, orion_wdt_irq, 0, | |
215 | pdev->name, &orion_wdt); | |
216 | if (ret < 0) { | |
217 | dev_err(&pdev->dev, "failed to request IRQ\n"); | |
218 | goto disable_clk; | |
219 | } | |
220 | } | |
221 | ||
0dd6e484 AL |
222 | watchdog_set_nowayout(&orion_wdt, nowayout); |
223 | ret = watchdog_register_device(&orion_wdt); | |
bb02c662 EG |
224 | if (ret) |
225 | goto disable_clk; | |
9e058d4f | 226 | |
27c766aa | 227 | pr_info("Initial timeout %d sec%s\n", |
c1fd5f64 | 228 | orion_wdt.timeout, nowayout ? ", nowayout" : ""); |
9e058d4f | 229 | return 0; |
bb02c662 EG |
230 | |
231 | disable_clk: | |
232 | clk_disable_unprepare(clk); | |
233 | return ret; | |
9e058d4f TR |
234 | } |
235 | ||
4b12b896 | 236 | static int orion_wdt_remove(struct platform_device *pdev) |
9e058d4f | 237 | { |
0dd6e484 | 238 | watchdog_unregister_device(&orion_wdt); |
4f04be62 | 239 | clk_disable_unprepare(clk); |
0dd6e484 | 240 | return 0; |
22ac9232 SB |
241 | } |
242 | ||
3b937a7d | 243 | static void orion_wdt_shutdown(struct platform_device *pdev) |
df6707b2 | 244 | { |
0dd6e484 | 245 | orion_wdt_stop(&orion_wdt); |
df6707b2 TR |
246 | } |
247 | ||
1d131368 | 248 | static const struct of_device_id orion_wdt_of_match_table[] = { |
1e7bad0f AL |
249 | { .compatible = "marvell,orion-wdt", }, |
250 | {}, | |
251 | }; | |
252 | MODULE_DEVICE_TABLE(of, orion_wdt_of_match_table); | |
253 | ||
3b937a7d NP |
254 | static struct platform_driver orion_wdt_driver = { |
255 | .probe = orion_wdt_probe, | |
82268714 | 256 | .remove = orion_wdt_remove, |
3b937a7d | 257 | .shutdown = orion_wdt_shutdown, |
9e058d4f TR |
258 | .driver = { |
259 | .owner = THIS_MODULE, | |
3b937a7d | 260 | .name = "orion_wdt", |
85eee819 | 261 | .of_match_table = orion_wdt_of_match_table, |
9e058d4f TR |
262 | }, |
263 | }; | |
264 | ||
b8ec6118 | 265 | module_platform_driver(orion_wdt_driver); |
22ac9232 SB |
266 | |
267 | MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>"); | |
3b937a7d | 268 | MODULE_DESCRIPTION("Orion Processor Watchdog"); |
22ac9232 SB |
269 | |
270 | module_param(heartbeat, int, 0); | |
df6707b2 | 271 | MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds"); |
22ac9232 | 272 | |
86a1e189 | 273 | module_param(nowayout, bool, 0); |
df6707b2 TR |
274 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
275 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
22ac9232 SB |
276 | |
277 | MODULE_LICENSE("GPL"); | |
f3ea733e | 278 | MODULE_ALIAS("platform:orion_wdt"); |