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