diff options
Diffstat (limited to 'drivers/watchdog/bcm47xx_wdt.c')
-rw-r--r-- | drivers/watchdog/bcm47xx_wdt.c | 339 |
1 files changed, 144 insertions, 195 deletions
diff --git a/drivers/watchdog/bcm47xx_wdt.c b/drivers/watchdog/bcm47xx_wdt.c index bc0e91e78e86..b4021a2b459b 100644 --- a/drivers/watchdog/bcm47xx_wdt.c +++ b/drivers/watchdog/bcm47xx_wdt.c | |||
@@ -3,6 +3,7 @@ | |||
3 | * | 3 | * |
4 | * Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs> | 4 | * Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs> |
5 | * Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.fr> | 5 | * Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.fr> |
6 | * Copyright (C) 2012-2013 Hauke Mehrtens <hauke@hauke-m.de> | ||
6 | * | 7 | * |
7 | * This program is free software; you can redistribute it and/or | 8 | * This program is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU General Public License | 9 | * modify it under the terms of the GNU General Public License |
@@ -12,165 +13,143 @@ | |||
12 | 13 | ||
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
14 | 15 | ||
16 | #include <linux/bcm47xx_wdt.h> | ||
15 | #include <linux/bitops.h> | 17 | #include <linux/bitops.h> |
16 | #include <linux/errno.h> | 18 | #include <linux/errno.h> |
17 | #include <linux/fs.h> | ||
18 | #include <linux/init.h> | 19 | #include <linux/init.h> |
19 | #include <linux/kernel.h> | 20 | #include <linux/kernel.h> |
20 | #include <linux/miscdevice.h> | ||
21 | #include <linux/module.h> | 21 | #include <linux/module.h> |
22 | #include <linux/moduleparam.h> | 22 | #include <linux/moduleparam.h> |
23 | #include <linux/platform_device.h> | ||
23 | #include <linux/reboot.h> | 24 | #include <linux/reboot.h> |
24 | #include <linux/types.h> | 25 | #include <linux/types.h> |
25 | #include <linux/uaccess.h> | ||
26 | #include <linux/watchdog.h> | 26 | #include <linux/watchdog.h> |
27 | #include <linux/timer.h> | 27 | #include <linux/timer.h> |
28 | #include <linux/jiffies.h> | 28 | #include <linux/jiffies.h> |
29 | #include <linux/ssb/ssb_embedded.h> | ||
30 | #include <asm/mach-bcm47xx/bcm47xx.h> | ||
31 | 29 | ||
32 | #define DRV_NAME "bcm47xx_wdt" | 30 | #define DRV_NAME "bcm47xx_wdt" |
33 | 31 | ||
34 | #define WDT_DEFAULT_TIME 30 /* seconds */ | 32 | #define WDT_DEFAULT_TIME 30 /* seconds */ |
35 | #define WDT_MAX_TIME 255 /* seconds */ | 33 | #define WDT_SOFTTIMER_MAX 255 /* seconds */ |
34 | #define WDT_SOFTTIMER_THRESHOLD 60 /* seconds */ | ||
36 | 35 | ||
37 | static int wdt_time = WDT_DEFAULT_TIME; | 36 | static int timeout = WDT_DEFAULT_TIME; |
38 | static bool nowayout = WATCHDOG_NOWAYOUT; | 37 | static bool nowayout = WATCHDOG_NOWAYOUT; |
39 | 38 | ||
40 | module_param(wdt_time, int, 0); | 39 | module_param(timeout, int, 0); |
41 | MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default=" | 40 | MODULE_PARM_DESC(timeout, "Watchdog time in seconds. (default=" |
42 | __MODULE_STRING(WDT_DEFAULT_TIME) ")"); | 41 | __MODULE_STRING(WDT_DEFAULT_TIME) ")"); |
43 | 42 | ||
44 | #ifdef CONFIG_WATCHDOG_NOWAYOUT | ||
45 | module_param(nowayout, bool, 0); | 43 | module_param(nowayout, bool, 0); |
46 | MODULE_PARM_DESC(nowayout, | 44 | MODULE_PARM_DESC(nowayout, |
47 | "Watchdog cannot be stopped once started (default=" | 45 | "Watchdog cannot be stopped once started (default=" |
48 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | 46 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
49 | #endif | ||
50 | 47 | ||
51 | static unsigned long bcm47xx_wdt_busy; | 48 | static inline struct bcm47xx_wdt *bcm47xx_wdt_get(struct watchdog_device *wdd) |
52 | static char expect_release; | ||
53 | static struct timer_list wdt_timer; | ||
54 | static atomic_t ticks; | ||
55 | |||
56 | static inline void bcm47xx_wdt_hw_start(void) | ||
57 | { | 49 | { |
58 | /* this is 2,5s on 100Mhz clock and 2s on 133 Mhz */ | 50 | return container_of(wdd, struct bcm47xx_wdt, wdd); |
59 | switch (bcm47xx_bus_type) { | ||
60 | #ifdef CONFIG_BCM47XX_SSB | ||
61 | case BCM47XX_BUS_TYPE_SSB: | ||
62 | ssb_watchdog_timer_set(&bcm47xx_bus.ssb, 0xfffffff); | ||
63 | break; | ||
64 | #endif | ||
65 | #ifdef CONFIG_BCM47XX_BCMA | ||
66 | case BCM47XX_BUS_TYPE_BCMA: | ||
67 | bcma_chipco_watchdog_timer_set(&bcm47xx_bus.bcma.bus.drv_cc, | ||
68 | 0xfffffff); | ||
69 | break; | ||
70 | #endif | ||
71 | } | ||
72 | } | 51 | } |
73 | 52 | ||
74 | static inline int bcm47xx_wdt_hw_stop(void) | 53 | static int bcm47xx_wdt_hard_keepalive(struct watchdog_device *wdd) |
75 | { | 54 | { |
76 | switch (bcm47xx_bus_type) { | 55 | struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd); |
77 | #ifdef CONFIG_BCM47XX_SSB | ||
78 | case BCM47XX_BUS_TYPE_SSB: | ||
79 | return ssb_watchdog_timer_set(&bcm47xx_bus.ssb, 0); | ||
80 | #endif | ||
81 | #ifdef CONFIG_BCM47XX_BCMA | ||
82 | case BCM47XX_BUS_TYPE_BCMA: | ||
83 | bcma_chipco_watchdog_timer_set(&bcm47xx_bus.bcma.bus.drv_cc, 0); | ||
84 | return 0; | ||
85 | #endif | ||
86 | } | ||
87 | return -EINVAL; | ||
88 | } | ||
89 | 56 | ||
90 | static void bcm47xx_timer_tick(unsigned long unused) | 57 | wdt->timer_set_ms(wdt, wdd->timeout * 1000); |
91 | { | 58 | |
92 | if (!atomic_dec_and_test(&ticks)) { | 59 | return 0; |
93 | bcm47xx_wdt_hw_start(); | ||
94 | mod_timer(&wdt_timer, jiffies + HZ); | ||
95 | } else { | ||
96 | pr_crit("Watchdog will fire soon!!!\n"); | ||
97 | } | ||
98 | } | 60 | } |
99 | 61 | ||
100 | static inline void bcm47xx_wdt_pet(void) | 62 | static int bcm47xx_wdt_hard_start(struct watchdog_device *wdd) |
101 | { | 63 | { |
102 | atomic_set(&ticks, wdt_time); | 64 | return 0; |
103 | } | 65 | } |
104 | 66 | ||
105 | static void bcm47xx_wdt_start(void) | 67 | static int bcm47xx_wdt_hard_stop(struct watchdog_device *wdd) |
106 | { | 68 | { |
107 | bcm47xx_wdt_pet(); | 69 | struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd); |
108 | bcm47xx_timer_tick(0); | 70 | |
71 | wdt->timer_set(wdt, 0); | ||
72 | |||
73 | return 0; | ||
109 | } | 74 | } |
110 | 75 | ||
111 | static void bcm47xx_wdt_pause(void) | 76 | static int bcm47xx_wdt_hard_set_timeout(struct watchdog_device *wdd, |
77 | unsigned int new_time) | ||
112 | { | 78 | { |
113 | del_timer_sync(&wdt_timer); | 79 | struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd); |
114 | bcm47xx_wdt_hw_stop(); | 80 | u32 max_timer = wdt->max_timer_ms; |
81 | |||
82 | if (new_time < 1 || new_time > max_timer / 1000) { | ||
83 | pr_warn("timeout value must be 1<=x<=%d, using %d\n", | ||
84 | max_timer / 1000, new_time); | ||
85 | return -EINVAL; | ||
86 | } | ||
87 | |||
88 | wdd->timeout = new_time; | ||
89 | return 0; | ||
115 | } | 90 | } |
116 | 91 | ||
117 | static void bcm47xx_wdt_stop(void) | 92 | static struct watchdog_ops bcm47xx_wdt_hard_ops = { |
93 | .owner = THIS_MODULE, | ||
94 | .start = bcm47xx_wdt_hard_start, | ||
95 | .stop = bcm47xx_wdt_hard_stop, | ||
96 | .ping = bcm47xx_wdt_hard_keepalive, | ||
97 | .set_timeout = bcm47xx_wdt_hard_set_timeout, | ||
98 | }; | ||
99 | |||
100 | static void bcm47xx_wdt_soft_timer_tick(unsigned long data) | ||
118 | { | 101 | { |
119 | bcm47xx_wdt_pause(); | 102 | struct bcm47xx_wdt *wdt = (struct bcm47xx_wdt *)data; |
103 | u32 next_tick = min(wdt->wdd.timeout * 1000, wdt->max_timer_ms); | ||
104 | |||
105 | if (!atomic_dec_and_test(&wdt->soft_ticks)) { | ||
106 | wdt->timer_set_ms(wdt, next_tick); | ||
107 | mod_timer(&wdt->soft_timer, jiffies + HZ); | ||
108 | } else { | ||
109 | pr_crit("Watchdog will fire soon!!!\n"); | ||
110 | } | ||
120 | } | 111 | } |
121 | 112 | ||
122 | static int bcm47xx_wdt_settimeout(int new_time) | 113 | static int bcm47xx_wdt_soft_keepalive(struct watchdog_device *wdd) |
123 | { | 114 | { |
124 | if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) | 115 | struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd); |
125 | return -EINVAL; | 116 | |
117 | atomic_set(&wdt->soft_ticks, wdd->timeout); | ||
126 | 118 | ||
127 | wdt_time = new_time; | ||
128 | return 0; | 119 | return 0; |
129 | } | 120 | } |
130 | 121 | ||
131 | static int bcm47xx_wdt_open(struct inode *inode, struct file *file) | 122 | static int bcm47xx_wdt_soft_start(struct watchdog_device *wdd) |
132 | { | 123 | { |
133 | if (test_and_set_bit(0, &bcm47xx_wdt_busy)) | 124 | struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd); |
134 | return -EBUSY; | 125 | |
126 | bcm47xx_wdt_soft_keepalive(wdd); | ||
127 | bcm47xx_wdt_soft_timer_tick((unsigned long)wdt); | ||
135 | 128 | ||
136 | bcm47xx_wdt_start(); | 129 | return 0; |
137 | return nonseekable_open(inode, file); | ||
138 | } | 130 | } |
139 | 131 | ||
140 | static int bcm47xx_wdt_release(struct inode *inode, struct file *file) | 132 | static int bcm47xx_wdt_soft_stop(struct watchdog_device *wdd) |
141 | { | 133 | { |
142 | if (expect_release == 42) { | 134 | struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd); |
143 | bcm47xx_wdt_stop(); | 135 | |
144 | } else { | 136 | del_timer_sync(&wdt->soft_timer); |
145 | pr_crit("Unexpected close, not stopping watchdog!\n"); | 137 | wdt->timer_set(wdt, 0); |
146 | bcm47xx_wdt_start(); | ||
147 | } | ||
148 | 138 | ||
149 | clear_bit(0, &bcm47xx_wdt_busy); | ||
150 | expect_release = 0; | ||
151 | return 0; | 139 | return 0; |
152 | } | 140 | } |
153 | 141 | ||
154 | static ssize_t bcm47xx_wdt_write(struct file *file, const char __user *data, | 142 | static int bcm47xx_wdt_soft_set_timeout(struct watchdog_device *wdd, |
155 | size_t len, loff_t *ppos) | 143 | unsigned int new_time) |
156 | { | 144 | { |
157 | if (len) { | 145 | if (new_time < 1 || new_time > WDT_SOFTTIMER_MAX) { |
158 | if (!nowayout) { | 146 | pr_warn("timeout value must be 1<=x<=%d, using %d\n", |
159 | size_t i; | 147 | WDT_SOFTTIMER_MAX, new_time); |
160 | 148 | return -EINVAL; | |
161 | expect_release = 0; | ||
162 | |||
163 | for (i = 0; i != len; i++) { | ||
164 | char c; | ||
165 | if (get_user(c, data + i)) | ||
166 | return -EFAULT; | ||
167 | if (c == 'V') | ||
168 | expect_release = 42; | ||
169 | } | ||
170 | } | ||
171 | bcm47xx_wdt_pet(); | ||
172 | } | 149 | } |
173 | return len; | 150 | |
151 | wdd->timeout = new_time; | ||
152 | return 0; | ||
174 | } | 153 | } |
175 | 154 | ||
176 | static const struct watchdog_info bcm47xx_wdt_info = { | 155 | static const struct watchdog_info bcm47xx_wdt_info = { |
@@ -180,130 +159,100 @@ static const struct watchdog_info bcm47xx_wdt_info = { | |||
180 | WDIOF_MAGICCLOSE, | 159 | WDIOF_MAGICCLOSE, |
181 | }; | 160 | }; |
182 | 161 | ||
183 | static long bcm47xx_wdt_ioctl(struct file *file, | ||
184 | unsigned int cmd, unsigned long arg) | ||
185 | { | ||
186 | void __user *argp = (void __user *)arg; | ||
187 | int __user *p = argp; | ||
188 | int new_value, retval = -EINVAL; | ||
189 | |||
190 | switch (cmd) { | ||
191 | case WDIOC_GETSUPPORT: | ||
192 | return copy_to_user(argp, &bcm47xx_wdt_info, | ||
193 | sizeof(bcm47xx_wdt_info)) ? -EFAULT : 0; | ||
194 | |||
195 | case WDIOC_GETSTATUS: | ||
196 | case WDIOC_GETBOOTSTATUS: | ||
197 | return put_user(0, p); | ||
198 | |||
199 | case WDIOC_SETOPTIONS: | ||
200 | if (get_user(new_value, p)) | ||
201 | return -EFAULT; | ||
202 | |||
203 | if (new_value & WDIOS_DISABLECARD) { | ||
204 | bcm47xx_wdt_stop(); | ||
205 | retval = 0; | ||
206 | } | ||
207 | |||
208 | if (new_value & WDIOS_ENABLECARD) { | ||
209 | bcm47xx_wdt_start(); | ||
210 | retval = 0; | ||
211 | } | ||
212 | |||
213 | return retval; | ||
214 | |||
215 | case WDIOC_KEEPALIVE: | ||
216 | bcm47xx_wdt_pet(); | ||
217 | return 0; | ||
218 | |||
219 | case WDIOC_SETTIMEOUT: | ||
220 | if (get_user(new_value, p)) | ||
221 | return -EFAULT; | ||
222 | |||
223 | if (bcm47xx_wdt_settimeout(new_value)) | ||
224 | return -EINVAL; | ||
225 | |||
226 | bcm47xx_wdt_pet(); | ||
227 | |||
228 | case WDIOC_GETTIMEOUT: | ||
229 | return put_user(wdt_time, p); | ||
230 | |||
231 | default: | ||
232 | return -ENOTTY; | ||
233 | } | ||
234 | } | ||
235 | |||
236 | static int bcm47xx_wdt_notify_sys(struct notifier_block *this, | 162 | static int bcm47xx_wdt_notify_sys(struct notifier_block *this, |
237 | unsigned long code, void *unused) | 163 | unsigned long code, void *unused) |
238 | { | 164 | { |
165 | struct bcm47xx_wdt *wdt; | ||
166 | |||
167 | wdt = container_of(this, struct bcm47xx_wdt, notifier); | ||
239 | if (code == SYS_DOWN || code == SYS_HALT) | 168 | if (code == SYS_DOWN || code == SYS_HALT) |
240 | bcm47xx_wdt_stop(); | 169 | wdt->wdd.ops->stop(&wdt->wdd); |
241 | return NOTIFY_DONE; | 170 | return NOTIFY_DONE; |
242 | } | 171 | } |
243 | 172 | ||
244 | static const struct file_operations bcm47xx_wdt_fops = { | 173 | static struct watchdog_ops bcm47xx_wdt_soft_ops = { |
245 | .owner = THIS_MODULE, | 174 | .owner = THIS_MODULE, |
246 | .llseek = no_llseek, | 175 | .start = bcm47xx_wdt_soft_start, |
247 | .unlocked_ioctl = bcm47xx_wdt_ioctl, | 176 | .stop = bcm47xx_wdt_soft_stop, |
248 | .open = bcm47xx_wdt_open, | 177 | .ping = bcm47xx_wdt_soft_keepalive, |
249 | .release = bcm47xx_wdt_release, | 178 | .set_timeout = bcm47xx_wdt_soft_set_timeout, |
250 | .write = bcm47xx_wdt_write, | ||
251 | }; | ||
252 | |||
253 | static struct miscdevice bcm47xx_wdt_miscdev = { | ||
254 | .minor = WATCHDOG_MINOR, | ||
255 | .name = "watchdog", | ||
256 | .fops = &bcm47xx_wdt_fops, | ||
257 | }; | ||
258 | |||
259 | static struct notifier_block bcm47xx_wdt_notifier = { | ||
260 | .notifier_call = bcm47xx_wdt_notify_sys, | ||
261 | }; | 179 | }; |
262 | 180 | ||
263 | static int __init bcm47xx_wdt_init(void) | 181 | static int bcm47xx_wdt_probe(struct platform_device *pdev) |
264 | { | 182 | { |
265 | int ret; | 183 | int ret; |
184 | bool soft; | ||
185 | struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev); | ||
266 | 186 | ||
267 | if (bcm47xx_wdt_hw_stop() < 0) | 187 | if (!wdt) |
268 | return -ENODEV; | 188 | return -ENXIO; |
269 | 189 | ||
270 | setup_timer(&wdt_timer, bcm47xx_timer_tick, 0L); | 190 | soft = wdt->max_timer_ms < WDT_SOFTTIMER_THRESHOLD * 1000; |
271 | 191 | ||
272 | if (bcm47xx_wdt_settimeout(wdt_time)) { | 192 | if (soft) { |
273 | bcm47xx_wdt_settimeout(WDT_DEFAULT_TIME); | 193 | wdt->wdd.ops = &bcm47xx_wdt_soft_ops; |
274 | pr_info("wdt_time value must be 0 < wdt_time < %d, using %d\n", | 194 | setup_timer(&wdt->soft_timer, bcm47xx_wdt_soft_timer_tick, |
275 | (WDT_MAX_TIME + 1), wdt_time); | 195 | (long unsigned int)wdt); |
196 | } else { | ||
197 | wdt->wdd.ops = &bcm47xx_wdt_hard_ops; | ||
276 | } | 198 | } |
277 | 199 | ||
278 | ret = register_reboot_notifier(&bcm47xx_wdt_notifier); | 200 | wdt->wdd.info = &bcm47xx_wdt_info; |
201 | wdt->wdd.timeout = WDT_DEFAULT_TIME; | ||
202 | ret = wdt->wdd.ops->set_timeout(&wdt->wdd, timeout); | ||
279 | if (ret) | 203 | if (ret) |
280 | return ret; | 204 | goto err_timer; |
205 | watchdog_set_nowayout(&wdt->wdd, nowayout); | ||
281 | 206 | ||
282 | ret = misc_register(&bcm47xx_wdt_miscdev); | 207 | wdt->notifier.notifier_call = &bcm47xx_wdt_notify_sys; |
283 | if (ret) { | 208 | |
284 | unregister_reboot_notifier(&bcm47xx_wdt_notifier); | 209 | ret = register_reboot_notifier(&wdt->notifier); |
285 | return ret; | 210 | if (ret) |
286 | } | 211 | goto err_timer; |
287 | 212 | ||
288 | pr_info("BCM47xx Watchdog Timer enabled (%d seconds%s)\n", | 213 | ret = watchdog_register_device(&wdt->wdd); |
289 | wdt_time, nowayout ? ", nowayout" : ""); | 214 | if (ret) |
215 | goto err_notifier; | ||
216 | |||
217 | dev_info(&pdev->dev, "BCM47xx Watchdog Timer enabled (%d seconds%s%s)\n", | ||
218 | timeout, nowayout ? ", nowayout" : "", | ||
219 | soft ? ", Software Timer" : ""); | ||
290 | return 0; | 220 | return 0; |
221 | |||
222 | err_notifier: | ||
223 | unregister_reboot_notifier(&wdt->notifier); | ||
224 | err_timer: | ||
225 | if (soft) | ||
226 | del_timer_sync(&wdt->soft_timer); | ||
227 | |||
228 | return ret; | ||
291 | } | 229 | } |
292 | 230 | ||
293 | static void __exit bcm47xx_wdt_exit(void) | 231 | static int bcm47xx_wdt_remove(struct platform_device *pdev) |
294 | { | 232 | { |
295 | if (!nowayout) | 233 | struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev); |
296 | bcm47xx_wdt_stop(); | ||
297 | 234 | ||
298 | misc_deregister(&bcm47xx_wdt_miscdev); | 235 | if (!wdt) |
236 | return -ENXIO; | ||
299 | 237 | ||
300 | unregister_reboot_notifier(&bcm47xx_wdt_notifier); | 238 | watchdog_unregister_device(&wdt->wdd); |
239 | unregister_reboot_notifier(&wdt->notifier); | ||
240 | |||
241 | return 0; | ||
301 | } | 242 | } |
302 | 243 | ||
303 | module_init(bcm47xx_wdt_init); | 244 | static struct platform_driver bcm47xx_wdt_driver = { |
304 | module_exit(bcm47xx_wdt_exit); | 245 | .driver = { |
246 | .owner = THIS_MODULE, | ||
247 | .name = "bcm47xx-wdt", | ||
248 | }, | ||
249 | .probe = bcm47xx_wdt_probe, | ||
250 | .remove = bcm47xx_wdt_remove, | ||
251 | }; | ||
252 | |||
253 | module_platform_driver(bcm47xx_wdt_driver); | ||
305 | 254 | ||
306 | MODULE_AUTHOR("Aleksandar Radovanovic"); | 255 | MODULE_AUTHOR("Aleksandar Radovanovic"); |
256 | MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>"); | ||
307 | MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx"); | 257 | MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx"); |
308 | MODULE_LICENSE("GPL"); | 258 | MODULE_LICENSE("GPL"); |
309 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||