aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/bcm47xx_wdt.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog/bcm47xx_wdt.c')
-rw-r--r--drivers/watchdog/bcm47xx_wdt.c339
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
37static int wdt_time = WDT_DEFAULT_TIME; 36static int timeout = WDT_DEFAULT_TIME;
38static bool nowayout = WATCHDOG_NOWAYOUT; 37static bool nowayout = WATCHDOG_NOWAYOUT;
39 38
40module_param(wdt_time, int, 0); 39module_param(timeout, int, 0);
41MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default=" 40MODULE_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
45module_param(nowayout, bool, 0); 43module_param(nowayout, bool, 0);
46MODULE_PARM_DESC(nowayout, 44MODULE_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
51static unsigned long bcm47xx_wdt_busy; 48static inline struct bcm47xx_wdt *bcm47xx_wdt_get(struct watchdog_device *wdd)
52static char expect_release;
53static struct timer_list wdt_timer;
54static atomic_t ticks;
55
56static 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
74static inline int bcm47xx_wdt_hw_stop(void) 53static 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
90static 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
100static inline void bcm47xx_wdt_pet(void) 62static int bcm47xx_wdt_hard_start(struct watchdog_device *wdd)
101{ 63{
102 atomic_set(&ticks, wdt_time); 64 return 0;
103} 65}
104 66
105static void bcm47xx_wdt_start(void) 67static 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
111static void bcm47xx_wdt_pause(void) 76static 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
117static void bcm47xx_wdt_stop(void) 92static 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
100static 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
122static int bcm47xx_wdt_settimeout(int new_time) 113static 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
131static int bcm47xx_wdt_open(struct inode *inode, struct file *file) 122static 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
140static int bcm47xx_wdt_release(struct inode *inode, struct file *file) 132static 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
154static ssize_t bcm47xx_wdt_write(struct file *file, const char __user *data, 142static 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
176static const struct watchdog_info bcm47xx_wdt_info = { 155static 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
183static 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
236static int bcm47xx_wdt_notify_sys(struct notifier_block *this, 162static 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
244static const struct file_operations bcm47xx_wdt_fops = { 173static 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
253static struct miscdevice bcm47xx_wdt_miscdev = {
254 .minor = WATCHDOG_MINOR,
255 .name = "watchdog",
256 .fops = &bcm47xx_wdt_fops,
257};
258
259static struct notifier_block bcm47xx_wdt_notifier = {
260 .notifier_call = bcm47xx_wdt_notify_sys,
261}; 179};
262 180
263static int __init bcm47xx_wdt_init(void) 181static 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
222err_notifier:
223 unregister_reboot_notifier(&wdt->notifier);
224err_timer:
225 if (soft)
226 del_timer_sync(&wdt->soft_timer);
227
228 return ret;
291} 229}
292 230
293static void __exit bcm47xx_wdt_exit(void) 231static 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
303module_init(bcm47xx_wdt_init); 244static struct platform_driver bcm47xx_wdt_driver = {
304module_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
253module_platform_driver(bcm47xx_wdt_driver);
305 254
306MODULE_AUTHOR("Aleksandar Radovanovic"); 255MODULE_AUTHOR("Aleksandar Radovanovic");
256MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
307MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx"); 257MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx");
308MODULE_LICENSE("GPL"); 258MODULE_LICENSE("GPL");
309MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);