advantechwdt.c 7.9 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8
/*
 *	Advantech Single Board Computer WDT driver
 *
 *	(c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
 *
 *	Based on acquirewdt.c which is based on wdt.c.
 *	Original copyright messages:
 *
9 10
 *	(c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
 *						All Rights Reserved.
L
Linus Torvalds 已提交
11 12 13 14 15 16 17 18 19 20
 *
 *	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.
 *
 *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
 *	warranty for any of this software. This material is provided
 *	"AS-IS" and at no charge.
 *
21
 *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
L
Linus Torvalds 已提交
22 23 24 25 26 27 28 29 30
 *
 *	14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
 *	    Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
 *
 *	16-Oct-2002 Rob Radez <rob@osinvestor.com>
 *	    Clean up ioctls, clean up init + exit, add expect close support,
 *	    add wdt_start and wdt_stop as parameters.
 */

31 32
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

L
Linus Torvalds 已提交
33 34 35 36 37 38 39
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/ioport.h>
40
#include <linux/platform_device.h>
L
Linus Torvalds 已提交
41
#include <linux/init.h>
W
Wim Van Sebroeck 已提交
42 43
#include <linux/io.h>
#include <linux/uaccess.h>
L
Linus Torvalds 已提交
44 45


46
#define DRV_NAME "advantechwdt"
L
Linus Torvalds 已提交
47 48 49
#define WATCHDOG_NAME "Advantech WDT"
#define WATCHDOG_TIMEOUT 60		/* 60 sec default timeout */

50 51
/* the watchdog platform device */
static struct platform_device *advwdt_platform_device;
L
Linus Torvalds 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
static unsigned long advwdt_is_open;
static char adv_expect_close;

/*
 *	You must set these - there is no sane way to probe for this board.
 *
 *	To enable or restart, write the timeout value in seconds (1 to 63)
 *	to I/O port wdt_start.  To disable, read I/O port wdt_stop.
 *	Both are 0x443 for most boards (tested on a PCA-6276VE-00B1), but
 *	check your manual (at least the PCA-6159 seems to be different -
 *	the manual says wdt_stop is 0x43, not 0x443).
 *	(0x43 is also a write-only control register for the 8254 timer!)
 */

static int wdt_stop = 0x443;
module_param(wdt_stop, int, 0);
MODULE_PARM_DESC(wdt_stop, "Advantech WDT 'stop' io port (default 0x443)");

static int wdt_start = 0x443;
module_param(wdt_start, int, 0);
MODULE_PARM_DESC(wdt_start, "Advantech WDT 'start' io port (default 0x443)");

static int timeout = WATCHDOG_TIMEOUT;	/* in seconds */
module_param(timeout, int, 0);
76 77 78
MODULE_PARM_DESC(timeout,
	"Watchdog timeout in seconds. 1<= timeout <=63, default="
		__MODULE_STRING(WATCHDOG_TIMEOUT) ".");
L
Linus Torvalds 已提交
79

W
Wim Van Sebroeck 已提交
80 81
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
82 83 84
MODULE_PARM_DESC(nowayout,
	"Watchdog cannot be stopped once started (default="
		__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
L
Linus Torvalds 已提交
85 86

/*
87
 *	Watchdog Operations
L
Linus Torvalds 已提交
88 89
 */

90
static void advwdt_ping(void)
L
Linus Torvalds 已提交
91 92 93 94 95
{
	/* Write a watchdog value */
	outb_p(timeout, wdt_start);
}

96
static void advwdt_disable(void)
L
Linus Torvalds 已提交
97 98 99 100
{
	inb_p(wdt_stop);
}

101
static int advwdt_set_heartbeat(int t)
102
{
103
	if (t < 1 || t > 63)
104 105 106 107 108
		return -EINVAL;
	timeout = t;
	return 0;
}

109 110 111 112
/*
 *	/dev/watchdog handling
 */

113 114
static ssize_t advwdt_write(struct file *file, const char __user *buf,
						size_t count, loff_t *ppos)
L
Linus Torvalds 已提交
115 116 117 118 119 120 121 122 123
{
	if (count) {
		if (!nowayout) {
			size_t i;

			adv_expect_close = 0;

			for (i = 0; i != count; i++) {
				char c;
124
				if (get_user(c, buf + i))
L
Linus Torvalds 已提交
125 126 127 128 129 130 131 132 133 134
					return -EFAULT;
				if (c == 'V')
					adv_expect_close = 42;
			}
		}
		advwdt_ping();
	}
	return count;
}

135
static long advwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
L
Linus Torvalds 已提交
136 137 138 139
{
	int new_timeout;
	void __user *argp = (void __user *)arg;
	int __user *p = argp;
140
	static const struct watchdog_info ident = {
141 142 143
		.options = WDIOF_KEEPALIVEPING |
			   WDIOF_SETTIMEOUT |
			   WDIOF_MAGICCLOSE,
L
Linus Torvalds 已提交
144
		.firmware_version = 1,
145
		.identity = WATCHDOG_NAME,
L
Linus Torvalds 已提交
146 147 148 149
	};

	switch (cmd) {
	case WDIOC_GETSUPPORT:
150 151 152
		if (copy_to_user(argp, &ident, sizeof(ident)))
			return -EFAULT;
		break;
L
Linus Torvalds 已提交
153 154 155

	case WDIOC_GETSTATUS:
	case WDIOC_GETBOOTSTATUS:
156
		return put_user(0, p);
L
Linus Torvalds 已提交
157 158 159

	case WDIOC_SETOPTIONS:
	{
160
		int options, retval = -EINVAL;
L
Linus Torvalds 已提交
161

162 163 164 165 166 167 168 169 170 171 172
		if (get_user(options, p))
			return -EFAULT;
		if (options & WDIOS_DISABLECARD) {
			advwdt_disable();
			retval = 0;
		}
		if (options & WDIOS_ENABLECARD) {
			advwdt_ping();
			retval = 0;
		}
		return retval;
L
Linus Torvalds 已提交
173
	}
174 175 176 177 178 179 180 181 182 183 184 185 186
	case WDIOC_KEEPALIVE:
		advwdt_ping();
		break;

	case WDIOC_SETTIMEOUT:
		if (get_user(new_timeout, p))
			return -EFAULT;
		if (advwdt_set_heartbeat(new_timeout))
			return -EINVAL;
		advwdt_ping();
		/* Fall */
	case WDIOC_GETTIMEOUT:
		return put_user(timeout, p);
L
Linus Torvalds 已提交
187
	default:
188
		return -ENOTTY;
L
Linus Torvalds 已提交
189 190 191 192
	}
	return 0;
}

193
static int advwdt_open(struct inode *inode, struct file *file)
L
Linus Torvalds 已提交
194 195 196 197 198 199 200 201 202 203 204
{
	if (test_and_set_bit(0, &advwdt_is_open))
		return -EBUSY;
	/*
	 *	Activate
	 */

	advwdt_ping();
	return nonseekable_open(inode, file);
}

205
static int advwdt_close(struct inode *inode, struct file *file)
L
Linus Torvalds 已提交
206 207 208 209
{
	if (adv_expect_close == 42) {
		advwdt_disable();
	} else {
210
		pr_crit("Unexpected close, not stopping watchdog!\n");
L
Linus Torvalds 已提交
211 212 213 214 215 216 217 218 219 220 221
		advwdt_ping();
	}
	clear_bit(0, &advwdt_is_open);
	adv_expect_close = 0;
	return 0;
}

/*
 *	Kernel Interfaces
 */

222
static const struct file_operations advwdt_fops = {
L
Linus Torvalds 已提交
223 224 225
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.write		= advwdt_write,
226
	.unlocked_ioctl	= advwdt_ioctl,
L
Linus Torvalds 已提交
227 228 229 230 231
	.open		= advwdt_open,
	.release	= advwdt_close,
};

static struct miscdevice advwdt_miscdev = {
232 233 234
	.minor	= WATCHDOG_MINOR,
	.name	= "watchdog",
	.fops	= &advwdt_fops,
L
Linus Torvalds 已提交
235 236
};

237 238 239 240
/*
 *	Init & exit routines
 */

241
static int __devinit advwdt_probe(struct platform_device *dev)
L
Linus Torvalds 已提交
242 243 244 245 246
{
	int ret;

	if (wdt_stop != wdt_start) {
		if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
247 248
			pr_err("I/O address 0x%04x already in use\n",
			       wdt_stop);
L
Linus Torvalds 已提交
249 250 251 252 253 254
			ret = -EIO;
			goto out;
		}
	}

	if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
255
		pr_err("I/O address 0x%04x already in use\n", wdt_start);
L
Linus Torvalds 已提交
256 257 258 259
		ret = -EIO;
		goto unreg_stop;
	}

260 261
	/* Check that the heartbeat value is within it's range ;
	 * if not reset to the default */
262 263
	if (advwdt_set_heartbeat(timeout)) {
		advwdt_set_heartbeat(WATCHDOG_TIMEOUT);
264
		pr_info("timeout value must be 1<=x<=63, using %d\n", timeout);
265 266
	}

L
Linus Torvalds 已提交
267 268
	ret = misc_register(&advwdt_miscdev);
	if (ret != 0) {
269 270
		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
		       WATCHDOG_MINOR, ret);
271
		goto unreg_regions;
L
Linus Torvalds 已提交
272
	}
273
	pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
L
Linus Torvalds 已提交
274 275 276 277 278 279 280 281 282 283 284
		timeout, nowayout);
out:
	return ret;
unreg_regions:
	release_region(wdt_start, 1);
unreg_stop:
	if (wdt_stop != wdt_start)
		release_region(wdt_stop, 1);
	goto out;
}

285
static int __devexit advwdt_remove(struct platform_device *dev)
L
Linus Torvalds 已提交
286 287
{
	misc_deregister(&advwdt_miscdev);
288 289 290
	release_region(wdt_start, 1);
	if (wdt_stop != wdt_start)
		release_region(wdt_stop, 1);
291 292 293 294

	return 0;
}

295
static void advwdt_shutdown(struct platform_device *dev)
296 297 298 299 300
{
	/* Turn the WDT off if we have a soft shutdown */
	advwdt_disable();
}

301 302
static struct platform_driver advwdt_driver = {
	.probe		= advwdt_probe,
303
	.remove		= advwdt_remove,
304
	.shutdown	= advwdt_shutdown,
305 306 307 308 309 310
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= DRV_NAME,
	},
};

311
static int __init advwdt_init(void)
312 313 314
{
	int err;

315
	pr_info("WDT driver for Advantech single board computer initialising\n");
316 317 318 319 320

	err = platform_driver_register(&advwdt_driver);
	if (err)
		return err;

321 322
	advwdt_platform_device = platform_device_register_simple(DRV_NAME,
								-1, NULL, 0);
323 324 325 326 327 328 329 330 331 332 333 334
	if (IS_ERR(advwdt_platform_device)) {
		err = PTR_ERR(advwdt_platform_device);
		goto unreg_platform_driver;
	}

	return 0;

unreg_platform_driver:
	platform_driver_unregister(&advwdt_driver);
	return err;
}

335
static void __exit advwdt_exit(void)
336 337 338
{
	platform_device_unregister(advwdt_platform_device);
	platform_driver_unregister(&advwdt_driver);
339
	pr_info("Watchdog Module Unloaded\n");
L
Linus Torvalds 已提交
340 341 342 343 344 345 346 347 348
}

module_init(advwdt_init);
module_exit(advwdt_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>");
MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);