1/*
2 *	SBC EPX C3 0.1	A Hardware Watchdog Device for the Winsystems EPX-C3
3 *	single board computer
4 *
5 *	(c) Copyright 2006 Calin A. Culianu <calin@ajvar.org>, All Rights
6 *	Reserved.
7 *
8 *	This program is free software; you can redistribute it and/or
9 *	modify it under the terms of the GNU General Public License
10 *	as published by the Free Software Foundation; either version
11 *	2 of the License, or (at your option) any later version.
12 *
13 *	based on softdog.c by Alan Cox <alan@redhat.com>
14 */
15
16#include <linux/module.h>
17#include <linux/moduleparam.h>
18#include <linux/types.h>
19#include <linux/kernel.h>
20#include <linux/fs.h>
21#include <linux/mm.h>
22#include <linux/miscdevice.h>
23#include <linux/watchdog.h>
24#include <linux/notifier.h>
25#include <linux/reboot.h>
26#include <linux/init.h>
27#include <linux/ioport.h>
28#include <asm/uaccess.h>
29#include <asm/io.h>
30
31#define PFX "epx_c3: "
32static int epx_c3_alive;
33
34#define WATCHDOG_TIMEOUT 1		/* 1 sec default timeout */
35
36static int nowayout = WATCHDOG_NOWAYOUT;
37module_param(nowayout, int, 0);
38MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
39
40#define EPXC3_WATCHDOG_CTL_REG 0x1ee /* write 1 to enable, 0 to disable */
41#define EPXC3_WATCHDOG_PET_REG 0x1ef /* write anything to pet once enabled */
42
43static void epx_c3_start(void)
44{
45	outb(1, EPXC3_WATCHDOG_CTL_REG);
46}
47
48static void epx_c3_stop(void)
49{
50
51	outb(0, EPXC3_WATCHDOG_CTL_REG);
52
53	printk(KERN_INFO PFX "Stopped watchdog timer.\n");
54}
55
56static void epx_c3_pet(void)
57{
58	outb(1, EPXC3_WATCHDOG_PET_REG);
59}
60
61/*
62 *	Allow only one person to hold it open
63 */
64static int epx_c3_open(struct inode *inode, struct file *file)
65{
66	if (epx_c3_alive)
67		return -EBUSY;
68
69	if (nowayout)
70		__module_get(THIS_MODULE);
71
72	/* Activate timer */
73	epx_c3_start();
74	epx_c3_pet();
75
76	epx_c3_alive = 1;
77	printk(KERN_INFO "Started watchdog timer.\n");
78
79	return nonseekable_open(inode, file);
80}
81
82static int epx_c3_release(struct inode *inode, struct file *file)
83{
84	/* Shut off the timer.
85	 * Lock it in if it's a module and we defined ...NOWAYOUT */
86	if (!nowayout)
87		epx_c3_stop();		/* Turn the WDT off */
88
89	epx_c3_alive = 0;
90
91	return 0;
92}
93
94static ssize_t epx_c3_write(struct file *file, const char __user *data,
95			size_t len, loff_t *ppos)
96{
97	/* Refresh the timer. */
98	if (len)
99		epx_c3_pet();
100	return len;
101}
102
103static int epx_c3_ioctl(struct inode *inode, struct file *file,
104			unsigned int cmd, unsigned long arg)
105{
106	int options, retval = -EINVAL;
107	int __user *argp = (void __user *)arg;
108	static struct watchdog_info ident = {
109		.options		= WDIOF_KEEPALIVEPING |
110					  WDIOF_MAGICCLOSE,
111		.firmware_version	= 0,
112		.identity		= "Winsystems EPX-C3 H/W Watchdog",
113	};
114
115	switch (cmd) {
116	case WDIOC_GETSUPPORT:
117		if (copy_to_user(argp, &ident, sizeof(ident)))
118			return -EFAULT;
119		return 0;
120	case WDIOC_GETSTATUS:
121	case WDIOC_GETBOOTSTATUS:
122		return put_user(0, argp);
123	case WDIOC_KEEPALIVE:
124		epx_c3_pet();
125		return 0;
126	case WDIOC_GETTIMEOUT:
127		return put_user(WATCHDOG_TIMEOUT, argp);
128	case WDIOC_SETOPTIONS:
129		if (get_user(options, argp))
130			return -EFAULT;
131
132		if (options & WDIOS_DISABLECARD) {
133			epx_c3_stop();
134			retval = 0;
135		}
136
137		if (options & WDIOS_ENABLECARD) {
138			epx_c3_start();
139			retval = 0;
140		}
141
142		return retval;
143	default:
144		return -ENOTTY;
145	}
146}
147
148static int epx_c3_notify_sys(struct notifier_block *this, unsigned long code,
149				void *unused)
150{
151	if (code == SYS_DOWN || code == SYS_HALT)
152		epx_c3_stop();		/* Turn the WDT off */
153
154	return NOTIFY_DONE;
155}
156
157static const struct file_operations epx_c3_fops = {
158	.owner		= THIS_MODULE,
159	.llseek		= no_llseek,
160	.write		= epx_c3_write,
161	.ioctl		= epx_c3_ioctl,
162	.open		= epx_c3_open,
163	.release	= epx_c3_release,
164};
165
166static struct miscdevice epx_c3_miscdev = {
167	.minor		= WATCHDOG_MINOR,
168	.name		= "watchdog",
169	.fops		= &epx_c3_fops,
170};
171
172static struct notifier_block epx_c3_notifier = {
173	.notifier_call = epx_c3_notify_sys,
174};
175
176static const char banner[] __initdata =
177    KERN_INFO PFX "Hardware Watchdog Timer for Winsystems EPX-C3 SBC: 0.1\n";
178
179static int __init watchdog_init(void)
180{
181	int ret;
182
183	if (!request_region(EPXC3_WATCHDOG_CTL_REG, 2, "epxc3_watchdog"))
184		return -EBUSY;
185
186	ret = register_reboot_notifier(&epx_c3_notifier);
187	if (ret) {
188		printk(KERN_ERR PFX "cannot register reboot notifier "
189			"(err=%d)\n", ret);
190		goto out;
191	}
192
193	ret = misc_register(&epx_c3_miscdev);
194	if (ret) {
195		printk(KERN_ERR PFX "cannot register miscdev on minor=%d "
196			"(err=%d)\n", WATCHDOG_MINOR, ret);
197		unregister_reboot_notifier(&epx_c3_notifier);
198		goto out;
199	}
200
201	printk(banner);
202
203	return 0;
204
205out:
206	release_region(EPXC3_WATCHDOG_CTL_REG, 2);
207	return ret;
208}
209
210static void __exit watchdog_exit(void)
211{
212	misc_deregister(&epx_c3_miscdev);
213	unregister_reboot_notifier(&epx_c3_notifier);
214	release_region(EPXC3_WATCHDOG_CTL_REG, 2);
215}
216
217module_init(watchdog_init);
218module_exit(watchdog_exit);
219
220MODULE_AUTHOR("Calin A. Culianu <calin@ajvar.org>");
221MODULE_DESCRIPTION("Hardware Watchdog Device for Winsystems EPX-C3 SBC.  Note that there is no way to probe for this device -- so only use it if you are *sure* you are runnning on this specific SBC system from Winsystems!  It writes to IO ports 0x1ee and 0x1ef!");
222MODULE_LICENSE("GPL");
223MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
224