Commit b63aa731cd9e3fed7694a99f9c233f5f4b244f03
Committed by
Wim Van Sebroeck
1 parent
dee00abbbc
Exists in
master
and in
7 other branches
watchdog: add support for Broadcom BCM63xx built-in watchdog
This patch adds support for the Broadcom BCM63xx SoC built-in watchdog, it uses one of the BCM63xx hardware timer id. Signed-off-by: Miguel Gaio <miguel.gaio@efixo.com> Signed-off-by: Florian Fainelli <florian@openwrt.org> Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Showing 3 changed files with 361 additions and 0 deletions Side-by-side Diff
drivers/watchdog/Kconfig
... | ... | @@ -917,6 +917,16 @@ |
917 | 917 | from the first interrupt, it is then only poked when the |
918 | 918 | device is written. |
919 | 919 | |
920 | +config BCM63XX_WDT | |
921 | + tristate "Broadcom BCM63xx hardware watchdog" | |
922 | + depends on BCM63XX | |
923 | + help | |
924 | + Watchdog driver for the built in watchdog hardware in Broadcom | |
925 | + BCM63xx SoC. | |
926 | + | |
927 | + To compile this driver as a loadable module, choose M here. | |
928 | + The module will be called bcm63xx_wdt. | |
929 | + | |
920 | 930 | # PARISC Architecture |
921 | 931 | |
922 | 932 | # POWERPC Architecture |
drivers/watchdog/Makefile
... | ... | @@ -109,6 +109,7 @@ |
109 | 109 | |
110 | 110 | # MIPS Architecture |
111 | 111 | obj-$(CONFIG_BCM47XX_WDT) += bcm47xx_wdt.o |
112 | +obj-$(CONFIG_BCM63XX_WDT) += bcm63xx_wdt.o | |
112 | 113 | obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o |
113 | 114 | obj-$(CONFIG_INDYDOG) += indydog.o |
114 | 115 | obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o |
drivers/watchdog/bcm63xx_wdt.c
1 | +/* | |
2 | + * Broadcom BCM63xx SoC watchdog driver | |
3 | + * | |
4 | + * Copyright (C) 2007, Miguel Gaio <miguel.gaio@efixo.com> | |
5 | + * Copyright (C) 2008, Florian Fainelli <florian@openwrt.org> | |
6 | + * | |
7 | + * This program is free software; you can redistribute it and/or | |
8 | + * modify it under the terms of the GNU General Public License | |
9 | + * as published by the Free Software Foundation; either version | |
10 | + * 2 of the License, or (at your option) any later version. | |
11 | + */ | |
12 | + | |
13 | +#include <linux/bitops.h> | |
14 | +#include <linux/errno.h> | |
15 | +#include <linux/fs.h> | |
16 | +#include <linux/init.h> | |
17 | +#include <linux/kernel.h> | |
18 | +#include <linux/miscdevice.h> | |
19 | +#include <linux/module.h> | |
20 | +#include <linux/moduleparam.h> | |
21 | +#include <linux/reboot.h> | |
22 | +#include <linux/types.h> | |
23 | +#include <linux/uaccess.h> | |
24 | +#include <linux/watchdog.h> | |
25 | +#include <linux/timer.h> | |
26 | +#include <linux/jiffies.h> | |
27 | +#include <linux/interrupt.h> | |
28 | +#include <linux/ptrace.h> | |
29 | +#include <linux/resource.h> | |
30 | +#include <linux/platform_device.h> | |
31 | + | |
32 | +#include <bcm63xx_cpu.h> | |
33 | +#include <bcm63xx_io.h> | |
34 | +#include <bcm63xx_regs.h> | |
35 | +#include <bcm63xx_timer.h> | |
36 | + | |
37 | +#define PFX KBUILD_MODNAME | |
38 | + | |
39 | +#define WDT_HZ 50000000 /* Fclk */ | |
40 | +#define WDT_DEFAULT_TIME 30 /* seconds */ | |
41 | +#define WDT_MAX_TIME 256 /* seconds */ | |
42 | + | |
43 | +static struct { | |
44 | + void __iomem *regs; | |
45 | + struct timer_list timer; | |
46 | + int default_ticks; | |
47 | + unsigned long inuse; | |
48 | + atomic_t ticks; | |
49 | +} bcm63xx_wdt_device; | |
50 | + | |
51 | +static int expect_close; | |
52 | + | |
53 | +static int wdt_time = WDT_DEFAULT_TIME; | |
54 | +static int nowayout = WATCHDOG_NOWAYOUT; | |
55 | +module_param(nowayout, int, 0); | |
56 | +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
57 | + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
58 | + | |
59 | +/* HW functions */ | |
60 | +static void bcm63xx_wdt_hw_start(void) | |
61 | +{ | |
62 | + bcm_writel(0xfffffffe, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG); | |
63 | + bcm_writel(WDT_START_1, bcm63xx_wdt_device.regs + WDT_CTL_REG); | |
64 | + bcm_writel(WDT_START_2, bcm63xx_wdt_device.regs + WDT_CTL_REG); | |
65 | +} | |
66 | + | |
67 | +static void bcm63xx_wdt_hw_stop(void) | |
68 | +{ | |
69 | + bcm_writel(WDT_STOP_1, bcm63xx_wdt_device.regs + WDT_CTL_REG); | |
70 | + bcm_writel(WDT_STOP_2, bcm63xx_wdt_device.regs + WDT_CTL_REG); | |
71 | +} | |
72 | + | |
73 | +static void bcm63xx_wdt_isr(void *data) | |
74 | +{ | |
75 | + struct pt_regs *regs = get_irq_regs(); | |
76 | + | |
77 | + die(PFX " fire", regs); | |
78 | +} | |
79 | + | |
80 | +static void bcm63xx_timer_tick(unsigned long unused) | |
81 | +{ | |
82 | + if (!atomic_dec_and_test(&bcm63xx_wdt_device.ticks)) { | |
83 | + bcm63xx_wdt_hw_start(); | |
84 | + mod_timer(&bcm63xx_wdt_device.timer, jiffies + HZ); | |
85 | + } else | |
86 | + printk(KERN_CRIT PFX ": watchdog will restart system\n"); | |
87 | +} | |
88 | + | |
89 | +static void bcm63xx_wdt_pet(void) | |
90 | +{ | |
91 | + atomic_set(&bcm63xx_wdt_device.ticks, wdt_time); | |
92 | +} | |
93 | + | |
94 | +static void bcm63xx_wdt_start(void) | |
95 | +{ | |
96 | + bcm63xx_wdt_pet(); | |
97 | + bcm63xx_timer_tick(0); | |
98 | +} | |
99 | + | |
100 | +static void bcm63xx_wdt_pause(void) | |
101 | +{ | |
102 | + del_timer_sync(&bcm63xx_wdt_device.timer); | |
103 | + bcm63xx_wdt_hw_stop(); | |
104 | +} | |
105 | + | |
106 | +static int bcm63xx_wdt_settimeout(int new_time) | |
107 | +{ | |
108 | + if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) | |
109 | + return -EINVAL; | |
110 | + | |
111 | + wdt_time = new_time; | |
112 | + | |
113 | + return 0; | |
114 | +} | |
115 | + | |
116 | +static int bcm63xx_wdt_open(struct inode *inode, struct file *file) | |
117 | +{ | |
118 | + if (test_and_set_bit(0, &bcm63xx_wdt_device.inuse)) | |
119 | + return -EBUSY; | |
120 | + | |
121 | + bcm63xx_wdt_start(); | |
122 | + return nonseekable_open(inode, file); | |
123 | +} | |
124 | + | |
125 | +static int bcm63xx_wdt_release(struct inode *inode, struct file *file) | |
126 | +{ | |
127 | + if (expect_close == 42) | |
128 | + bcm63xx_wdt_pause(); | |
129 | + else { | |
130 | + printk(KERN_CRIT PFX | |
131 | + ": Unexpected close, not stopping watchdog!\n"); | |
132 | + bcm63xx_wdt_start(); | |
133 | + } | |
134 | + clear_bit(0, &bcm63xx_wdt_device.inuse); | |
135 | + expect_close = 0; | |
136 | + return 0; | |
137 | +} | |
138 | + | |
139 | +static ssize_t bcm63xx_wdt_write(struct file *file, const char *data, | |
140 | + size_t len, loff_t *ppos) | |
141 | +{ | |
142 | + if (len) { | |
143 | + if (!nowayout) { | |
144 | + size_t i; | |
145 | + | |
146 | + /* In case it was set long ago */ | |
147 | + expect_close = 0; | |
148 | + | |
149 | + for (i = 0; i != len; i++) { | |
150 | + char c; | |
151 | + if (get_user(c, data + i)) | |
152 | + return -EFAULT; | |
153 | + if (c == 'V') | |
154 | + expect_close = 42; | |
155 | + } | |
156 | + } | |
157 | + bcm63xx_wdt_pet(); | |
158 | + } | |
159 | + return len; | |
160 | +} | |
161 | + | |
162 | +static struct watchdog_info bcm63xx_wdt_info = { | |
163 | + .identity = PFX, | |
164 | + .options = WDIOF_SETTIMEOUT | | |
165 | + WDIOF_KEEPALIVEPING | | |
166 | + WDIOF_MAGICCLOSE, | |
167 | +}; | |
168 | + | |
169 | + | |
170 | +static long bcm63xx_wdt_ioctl(struct file *file, unsigned int cmd, | |
171 | + unsigned long arg) | |
172 | +{ | |
173 | + void __user *argp = (void __user *)arg; | |
174 | + int __user *p = argp; | |
175 | + int new_value, retval = -EINVAL; | |
176 | + | |
177 | + switch (cmd) { | |
178 | + case WDIOC_GETSUPPORT: | |
179 | + return copy_to_user(argp, &bcm63xx_wdt_info, | |
180 | + sizeof(bcm63xx_wdt_info)) ? -EFAULT : 0; | |
181 | + | |
182 | + case WDIOC_GETSTATUS: | |
183 | + case WDIOC_GETBOOTSTATUS: | |
184 | + return put_user(0, p); | |
185 | + | |
186 | + case WDIOC_SETOPTIONS: | |
187 | + if (get_user(new_value, p)) | |
188 | + return -EFAULT; | |
189 | + | |
190 | + if (new_value & WDIOS_DISABLECARD) { | |
191 | + bcm63xx_wdt_pause(); | |
192 | + retval = 0; | |
193 | + } | |
194 | + if (new_value & WDIOS_ENABLECARD) { | |
195 | + bcm63xx_wdt_start(); | |
196 | + retval = 0; | |
197 | + } | |
198 | + | |
199 | + return retval; | |
200 | + | |
201 | + case WDIOC_KEEPALIVE: | |
202 | + bcm63xx_wdt_pet(); | |
203 | + return 0; | |
204 | + | |
205 | + case WDIOC_SETTIMEOUT: | |
206 | + if (get_user(new_value, p)) | |
207 | + return -EFAULT; | |
208 | + | |
209 | + if (bcm63xx_wdt_settimeout(new_value)) | |
210 | + return -EINVAL; | |
211 | + | |
212 | + bcm63xx_wdt_pet(); | |
213 | + | |
214 | + case WDIOC_GETTIMEOUT: | |
215 | + return put_user(wdt_time, p); | |
216 | + | |
217 | + default: | |
218 | + return -ENOTTY; | |
219 | + | |
220 | + } | |
221 | +} | |
222 | + | |
223 | +static int bcm63xx_wdt_notify_sys(struct notifier_block *this, | |
224 | + unsigned long code, void *unused) | |
225 | +{ | |
226 | + if (code == SYS_DOWN || code == SYS_HALT) | |
227 | + bcm63xx_wdt_pause(); | |
228 | + return NOTIFY_DONE; | |
229 | +} | |
230 | + | |
231 | +static const struct file_operations bcm63xx_wdt_fops = { | |
232 | + .owner = THIS_MODULE, | |
233 | + .llseek = no_llseek, | |
234 | + .write = bcm63xx_wdt_write, | |
235 | + .unlocked_ioctl = bcm63xx_wdt_ioctl, | |
236 | + .open = bcm63xx_wdt_open, | |
237 | + .release = bcm63xx_wdt_release, | |
238 | +}; | |
239 | + | |
240 | +static struct miscdevice bcm63xx_wdt_miscdev = { | |
241 | + .minor = WATCHDOG_MINOR, | |
242 | + .name = "watchdog", | |
243 | + .fops = &bcm63xx_wdt_fops, | |
244 | +}; | |
245 | + | |
246 | +static struct notifier_block bcm63xx_wdt_notifier = { | |
247 | + .notifier_call = bcm63xx_wdt_notify_sys, | |
248 | +}; | |
249 | + | |
250 | + | |
251 | +static int bcm63xx_wdt_probe(struct platform_device *pdev) | |
252 | +{ | |
253 | + int ret; | |
254 | + struct resource *r; | |
255 | + | |
256 | + setup_timer(&bcm63xx_wdt_device.timer, bcm63xx_timer_tick, 0L); | |
257 | + | |
258 | + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
259 | + if (!r) { | |
260 | + dev_err(&pdev->dev, "failed to get resources\n"); | |
261 | + return -ENODEV; | |
262 | + } | |
263 | + | |
264 | + bcm63xx_wdt_device.regs = ioremap_nocache(r->start, r->end - r->start); | |
265 | + if (!bcm63xx_wdt_device.regs) { | |
266 | + dev_err(&pdev->dev, "failed to remap I/O resources\n"); | |
267 | + return -ENXIO; | |
268 | + } | |
269 | + | |
270 | + ret = bcm63xx_timer_register(TIMER_WDT_ID, bcm63xx_wdt_isr, NULL); | |
271 | + if (ret < 0) { | |
272 | + dev_err(&pdev->dev, "failed to register wdt timer isr\n"); | |
273 | + goto unmap; | |
274 | + } | |
275 | + | |
276 | + if (bcm63xx_wdt_settimeout(wdt_time)) { | |
277 | + bcm63xx_wdt_settimeout(WDT_DEFAULT_TIME); | |
278 | + dev_info(&pdev->dev, | |
279 | + ": wdt_time value must be 1 <= wdt_time <= 256, using %d\n", | |
280 | + wdt_time); | |
281 | + } | |
282 | + | |
283 | + ret = register_reboot_notifier(&bcm63xx_wdt_notifier); | |
284 | + if (ret) { | |
285 | + dev_err(&pdev->dev, "failed to register reboot_notifier\n"); | |
286 | + goto unregister_timer; | |
287 | + } | |
288 | + | |
289 | + ret = misc_register(&bcm63xx_wdt_miscdev); | |
290 | + if (ret < 0) { | |
291 | + dev_err(&pdev->dev, "failed to register watchdog device\n"); | |
292 | + goto unregister_reboot_notifier; | |
293 | + } | |
294 | + | |
295 | + dev_info(&pdev->dev, " started, timer margin: %d sec\n", | |
296 | + WDT_DEFAULT_TIME); | |
297 | + | |
298 | + return 0; | |
299 | + | |
300 | +unregister_reboot_notifier: | |
301 | + unregister_reboot_notifier(&bcm63xx_wdt_notifier); | |
302 | +unregister_timer: | |
303 | + bcm63xx_timer_unregister(TIMER_WDT_ID); | |
304 | +unmap: | |
305 | + iounmap(bcm63xx_wdt_device.regs); | |
306 | + return ret; | |
307 | +} | |
308 | + | |
309 | +static int bcm63xx_wdt_remove(struct platform_device *pdev) | |
310 | +{ | |
311 | + if (!nowayout) | |
312 | + bcm63xx_wdt_pause(); | |
313 | + | |
314 | + misc_deregister(&bcm63xx_wdt_miscdev); | |
315 | + | |
316 | + iounmap(bcm63xx_wdt_device.regs); | |
317 | + | |
318 | + unregister_reboot_notifier(&bcm63xx_wdt_notifier); | |
319 | + bcm63xx_timer_unregister(TIMER_WDT_ID); | |
320 | + | |
321 | + return 0; | |
322 | +} | |
323 | + | |
324 | +static struct platform_driver bcm63xx_wdt = { | |
325 | + .probe = bcm63xx_wdt_probe, | |
326 | + .remove = bcm63xx_wdt_remove, | |
327 | + .driver = { | |
328 | + .name = "bcm63xx-wdt", | |
329 | + } | |
330 | +}; | |
331 | + | |
332 | +static int __init bcm63xx_wdt_init(void) | |
333 | +{ | |
334 | + return platform_driver_register(&bcm63xx_wdt); | |
335 | +} | |
336 | + | |
337 | +static void __exit bcm63xx_wdt_exit(void) | |
338 | +{ | |
339 | + platform_driver_unregister(&bcm63xx_wdt); | |
340 | +} | |
341 | + | |
342 | +module_init(bcm63xx_wdt_init); | |
343 | +module_exit(bcm63xx_wdt_exit); | |
344 | + | |
345 | +MODULE_AUTHOR("Miguel Gaio <miguel.gaio@efixo.com>"); | |
346 | +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); | |
347 | +MODULE_DESCRIPTION("Driver for the Broadcom BCM63xx SoC watchdog"); | |
348 | +MODULE_LICENSE("GPL"); | |
349 | +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | |
350 | +MODULE_ALIAS("platform:bcm63xx-wdt"); |