Blame view

drivers/watchdog/scx200_wdt.c 6.58 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  /* drivers/char/watchdog/scx200_wdt.c
  
     National Semiconductor SCx200 Watchdog support
  
     Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
  
     Some code taken from:
     National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
     (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
  
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License as
     published by the Free Software Foundation; either version 2 of the
     License, or (at your option) any later version.
  
     The author(s) of this software shall not be held liable for damages
     of any nature resulting due to the use of this software. This
     software is provided AS-IS with no warranties. */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
19
20
21
22
23
24
25
26
  #include <linux/module.h>
  #include <linux/moduleparam.h>
  #include <linux/init.h>
  #include <linux/miscdevice.h>
  #include <linux/watchdog.h>
  #include <linux/notifier.h>
  #include <linux/reboot.h>
  #include <linux/fs.h>
6473d160b   Jean Delvare   PCI: Cleanup the ...
27
  #include <linux/ioport.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
28
  #include <linux/scx200.h>
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
29
30
  #include <linux/uaccess.h>
  #include <linux/io.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
31
32
33
34
35
36
37
  
  #define NAME "scx200_wdt"
  
  MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
  MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
  MODULE_LICENSE("GPL");
  MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
38
39
40
  static int margin = 60;		/* in seconds */
  module_param(margin, int, 0);
  MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
4bfdf3783   Andrey Panin   [PATCH] consolida...
41
  static int nowayout = WATCHDOG_NOWAYOUT;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
42
43
44
45
  module_param(nowayout, int, 0);
  MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
  
  static u16 wdto_restart;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
46
  static char expect_close;
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
47
48
  static unsigned long open_lock;
  static DEFINE_SPINLOCK(scx_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
49
50
51
52
53
54
55
56
57
58
  
  /* Bits of the WDCNFG register */
  #define W_ENABLE 0x00fa		/* Enable watchdog */
  #define W_DISABLE 0x0000	/* Disable watchdog */
  
  /* The scaling factor for the timer, this depends on the value of W_ENABLE */
  #define W_SCALE (32768/1024)
  
  static void scx200_wdt_ping(void)
  {
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
59
  	spin_lock(&scx_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
60
  	outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
61
  	spin_unlock(&scx_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
62
63
64
65
66
67
68
69
70
71
72
73
74
75
  }
  
  static void scx200_wdt_update_margin(void)
  {
  	printk(KERN_INFO NAME ": timer margin %d seconds
  ", margin);
  	wdto_restart = margin * W_SCALE;
  }
  
  static void scx200_wdt_enable(void)
  {
  	printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d
  ",
  	       wdto_restart);
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
76
  	spin_lock(&scx_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
77
78
79
  	outw(0, scx200_cb_base + SCx200_WDT_WDTO);
  	outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
  	outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
80
  	spin_unlock(&scx_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
81
82
83
84
85
86
87
88
  
  	scx200_wdt_ping();
  }
  
  static void scx200_wdt_disable(void)
  {
  	printk(KERN_DEBUG NAME ": disabling watchdog timer
  ");
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
89
  	spin_lock(&scx_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
90
91
92
  	outw(0, scx200_cb_base + SCx200_WDT_WDTO);
  	outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
  	outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
93
  	spin_unlock(&scx_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
94
95
96
97
98
  }
  
  static int scx200_wdt_open(struct inode *inode, struct file *file)
  {
  	/* only allow one at a time */
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
99
  	if (test_and_set_bit(0, &open_lock))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
100
101
102
103
104
105
106
107
  		return -EBUSY;
  	scx200_wdt_enable();
  
  	return nonseekable_open(inode, file);
  }
  
  static int scx200_wdt_release(struct inode *inode, struct file *file)
  {
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
108
  	if (expect_close != 42)
a77dba7e4   Wim Van Sebroeck   [WATCHDOG] Some m...
109
110
111
112
  		printk(KERN_WARNING NAME
  			": watchdog device closed unexpectedly, "
  			"will not disable the watchdog timer
  ");
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
113
  	else if (!nowayout)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
114
  		scx200_wdt_disable();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
115
  	expect_close = 0;
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
116
  	clear_bit(0, &open_lock);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
117
118
119
120
121
122
123
124
125
126
127
128
129
  
  	return 0;
  }
  
  static int scx200_wdt_notify_sys(struct notifier_block *this,
  				      unsigned long code, void *unused)
  {
  	if (code == SYS_HALT || code == SYS_POWER_OFF)
  		if (!nowayout)
  			scx200_wdt_disable();
  
  	return NOTIFY_DONE;
  }
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
130
  static struct notifier_block scx200_wdt_notifier = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
131
132
133
134
135
136
137
  	.notifier_call = scx200_wdt_notify_sys,
  };
  
  static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
  				     size_t len, loff_t *ppos)
  {
  	/* check for a magic close character */
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
138
  	if (len) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
139
140
141
142
143
144
145
  		size_t i;
  
  		scx200_wdt_ping();
  
  		expect_close = 0;
  		for (i = 0; i < len; ++i) {
  			char c;
7944d3a5a   Wim Van Sebroeck   [WATCHDOG] more c...
146
  			if (get_user(c, data + i))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
147
148
149
150
151
152
153
154
155
156
  				return -EFAULT;
  			if (c == 'V')
  				expect_close = 42;
  		}
  
  		return len;
  	}
  
  	return 0;
  }
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
157
158
  static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
  							unsigned long arg)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
159
160
161
  {
  	void __user *argp = (void __user *)arg;
  	int __user *p = argp;
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
162
  	static const struct watchdog_info ident = {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
163
164
  		.identity = "NatSemi SCx200 Watchdog",
  		.firmware_version = 1,
e73a78027   Wim Van Sebroeck   [WATCHDOG] Correc...
165
166
  		.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
  						WDIOF_MAGICCLOSE,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
167
168
169
170
  	};
  	int new_margin;
  
  	switch (cmd) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
171
  	case WDIOC_GETSUPPORT:
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
172
  		if (copy_to_user(argp, &ident, sizeof(ident)))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
  			return -EFAULT;
  		return 0;
  	case WDIOC_GETSTATUS:
  	case WDIOC_GETBOOTSTATUS:
  		if (put_user(0, p))
  			return -EFAULT;
  		return 0;
  	case WDIOC_KEEPALIVE:
  		scx200_wdt_ping();
  		return 0;
  	case WDIOC_SETTIMEOUT:
  		if (get_user(new_margin, p))
  			return -EFAULT;
  		if (new_margin < 1)
  			return -EINVAL;
  		margin = new_margin;
  		scx200_wdt_update_margin();
  		scx200_wdt_ping();
  	case WDIOC_GETTIMEOUT:
  		if (put_user(margin, p))
  			return -EFAULT;
  		return 0;
0c06090c9   Wim Van Sebroeck   [WATCHDOG] Coding...
195
196
  	default:
  		return -ENOTTY;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
197
198
  	}
  }
62322d255   Arjan van de Ven   [PATCH] make more...
199
  static const struct file_operations scx200_wdt_fops = {
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
200
201
202
203
204
  	.owner = THIS_MODULE,
  	.llseek = no_llseek,
  	.write = scx200_wdt_write,
  	.unlocked_ioctl = scx200_wdt_ioctl,
  	.open = scx200_wdt_open,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
205
206
207
208
209
  	.release = scx200_wdt_release,
  };
  
  static struct miscdevice scx200_wdt_miscdev = {
  	.minor = WATCHDOG_MINOR,
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
210
211
  	.name = "watchdog",
  	.fops = &scx200_wdt_fops,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
  };
  
  static int __init scx200_wdt_init(void)
  {
  	int r;
  
  	printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver
  ");
  
  	/* check that we have found the configuration block */
  	if (!scx200_cb_present())
  		return -ENODEV;
  
  	if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
  			    SCx200_WDT_SIZE,
  			    "NatSemi SCx200 Watchdog")) {
  		printk(KERN_WARNING NAME ": watchdog I/O region busy
  ");
  		return -EBUSY;
  	}
  
  	scx200_wdt_update_margin();
  	scx200_wdt_disable();
c6cb13aea   Wim Van Sebroeck   [WATCHDOG] misc_r...
235
  	r = register_reboot_notifier(&scx200_wdt_notifier);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
236
  	if (r) {
c6cb13aea   Wim Van Sebroeck   [WATCHDOG] misc_r...
237
  		printk(KERN_ERR NAME ": unable to register reboot notifier");
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
238
239
240
241
  		release_region(scx200_cb_base + SCx200_WDT_OFFSET,
  				SCx200_WDT_SIZE);
  		return r;
  	}
c6cb13aea   Wim Van Sebroeck   [WATCHDOG] misc_r...
242
  	r = misc_register(&scx200_wdt_miscdev);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
243
  	if (r) {
c6cb13aea   Wim Van Sebroeck   [WATCHDOG] misc_r...
244
  		unregister_reboot_notifier(&scx200_wdt_notifier);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
245
246
247
248
249
250
251
252
253
254
  		release_region(scx200_cb_base + SCx200_WDT_OFFSET,
  				SCx200_WDT_SIZE);
  		return r;
  	}
  
  	return 0;
  }
  
  static void __exit scx200_wdt_cleanup(void)
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
255
  	misc_deregister(&scx200_wdt_miscdev);
c6cb13aea   Wim Van Sebroeck   [WATCHDOG] misc_r...
256
  	unregister_reboot_notifier(&scx200_wdt_notifier);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
257
258
259
260
261
262
263
264
265
  	release_region(scx200_cb_base + SCx200_WDT_OFFSET,
  		       SCx200_WDT_SIZE);
  }
  
  module_init(scx200_wdt_init);
  module_exit(scx200_wdt_cleanup);
  
  /*
      Local variables:
9b748ed03   Alan Cox   [WATCHDOG 44/57] ...
266
267
  	compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules"
  	c-basic-offset: 8
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
268
269
      End:
  */