All of lore.kernel.org
 help / color / mirror / Atom feed
* watchdog driver for W83627HF chip
@ 2002-12-19  2:54 zhz
  0 siblings, 0 replies; only message in thread
From: zhz @ 2002-12-19  2:54 UTC (permalink / raw)
  To: linux-kernel

[-- Attachment #1: Type: text/plain, Size: 223 bytes --]

hi, man,
 
    I have written the watchdog driver for the Winbond W83627HF 
chip.  The attachment is the driver source code. Any suggestion is
welcome.
    Thanks a lot.
                            
Regards, 
Zhou HongZhen

[-- Attachment #2: w83627hf_wdt.c --]
[-- Type: application/octet-stream, Size: 15443 bytes --]


/*
 * 	W83627HF watchdog driver for Linux 2.2.x
 *
 * 	(c) Copyright 2002	Zhou HongZhen.
 *
 *	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.
 *
 *	(c) Copyright 2002	Zhou HongZhen <inetbash@yahoo.com>
 *	                                      <netdiff@netease.com>
 *
 *	Release 1.0.
 *
 *	Fixes
 *
 * */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/miscdevice.h>
#include <linux/init.h>
#include <linux/smp_lock.h>
#include <asm/param.h>
#include <asm/io.h>
#include <linux/ioport.h>
#include <asm/system.h>
#include <asm/semaphore.h>
#include <linux/sysctl.h>
#include <asm/uaccess.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/watchdog.h>

#if 0
#define W83627HF_WDT_DEBUG
#endif

#if 0
#undef CONFIG_WATCHDOG_NOWAYOUT
#else
#define CONFIG_WATCHDOG_NOWAYOUT
#endif

#ifdef W83627HF_WDT_DEBUG
#define assert_w83627hf_wdt(p) if (1) {\
	if (!(p)) \
		panic("assert failed in %s line %d\n", __FILE__, __LINE__);\
} else

#else
#define assert_w83627hf_wdt(p)
#endif

#define W83627HF_LPCIP 0x295 /* LPC Index Port */
#define W83627HF_LPCDP 0x296 /* LPC Data Port */

#define W83627HF_EFER 0x2E /* Extended Function Enable Register */
#define W83627HF_EFIR 0x2E /* Extended Function Index Register*/
#define W83627HF_EFDR 0x2F /* Extended Function Data Register */

#define W83627HF_ENTER_EFM 0x87 /* enter externed function mode */
#define W83627HF_EXIT_EFM 0xAA /* exit externed function mode */

/* default watchdog timeout value -- 12s */
#define W83627HF_WDT_DEF_TIMO 0x0C 
#define W83627HF_WDT_MAX_TIMO 0xFF
#define W83627HF_WDT_MIN_TIMO 0x01
/* #define W83627HF_WDT_MIN_TIMO 0x05 */

/* default watchdog timeout unit -- second */
#define W83627HF_WDT_DEF_TIMU 0x00

/* only one can open the watchdog device */
static unsigned long wdt_is_open = 0;
#ifndef CONFIG_WATCHDOG_NOWAYOUT
static unsigned long wdt_expect_close = 0;
#endif

/* 0x01--0xFF seconds(minutes). 0x00 to disable watchdog. */
static unsigned int w83627hf_margin = W83627HF_WDT_DEF_TIMO;
static unsigned int w83627hf_timeunit = W83627HF_WDT_DEF_TIMU;

#define W83627HF_WDT_MAGIC_NUM 13
#define W83627HF_WDT_MAGIC_U 3
#define W83627HF_WDT_MAGIC_M 1
/* magic word which change margin and timeunit */
static char w83627hf_wdt_magic[W83627HF_WDT_MAGIC_NUM] = "?M?UFH72638W";

#define w83627hf_wdt_feed() if (1) {\
	w83627hf_timeunit = w83627hf_timeunit ? \
		0x08 : 0x00;\
\
	if (W83627HF_WDT_MAX_TIMO < w83627hf_margin) \
		w83627hf_margin = W83627HF_WDT_MAX_TIMO;\
\
	if (0!=w83627hf_margin &&\
		W83627HF_WDT_MIN_TIMO>w83627hf_margin) \
		w83627hf_margin = W83627HF_WDT_MIN_TIMO; \
\
	outb(W83627HF_ENTER_EFM, W83627HF_EFER); \
	outb(W83627HF_ENTER_EFM, W83627HF_EFER); \
\
	outb(0x07, W83627HF_EFIR);\
	outb(0x08, W83627HF_EFDR);\
\
	outb(0xF5, W83627HF_EFIR);\
	outb((unsigned char)w83627hf_timeunit, W83627HF_EFDR);\
\
	outb(0xF6, W83627HF_EFIR);\
	outb((unsigned char)w83627hf_margin, W83627HF_EFDR);\
\
	outb(W83627HF_EXIT_EFM, W83627HF_EFER); \
} else

#define w83627hf_wdt_stop() if (1) {\
	outb(W83627HF_ENTER_EFM, W83627HF_EFER); \
	outb(W83627HF_ENTER_EFM, W83627HF_EFER); \
\
	outb(0x07, W83627HF_EFIR);\
	outb(0x08, W83627HF_EFDR);\
\
	/* 0x00 to disable watchdog */\
	outb(0xF6, W83627HF_EFIR);\
	outb(0x00, W83627HF_EFDR);\
\
	outb(W83627HF_EXIT_EFM, W83627HF_EFER); \
} else


#ifdef W83627HF_WDT_DEBUG
/* watchdog sysctl configuration(just for debug) */

enum {
	SYSCTL_W83627HF_WDT_MARGIN=1,
	SYSCTL_W83627HF_WDT_TIMEUNIT,
	SYSCTL_W83627HF_WDT_FEED
};

enum {
	SYSCTL_W83627HF_WDT=21
};

static int sysctl_w83627hf_wdt_noused = 0;

static int w83627hf_wdt_sysctl_feed(struct ctl_table *table, int write,
	struct file *filp, void *buffer, size_t *lenp);

static struct ctl_table sysctl_w83627hf_wdt_dir[] = {
	{SYSCTL_W83627HF_WDT_MARGIN, "margin",
		&w83627hf_margin, sizeof(int),
		0644, 0, &proc_dointvec},
	{SYSCTL_W83627HF_WDT_TIMEUNIT, "timeunit",
		&w83627hf_timeunit, sizeof(int),
		0644, 0, &proc_dointvec},
	{SYSCTL_W83627HF_WDT_FEED, "feed",
		&sysctl_w83627hf_wdt_noused, sizeof(int),
		0200, 0, &w83627hf_wdt_sysctl_feed},
	{0}
};

static struct ctl_table sysctl_w83627hf_wdt_root[] = {
	{SYSCTL_W83627HF_WDT, "w83627hf_wdt",
		0, 0, 0555, sysctl_w83627hf_wdt_dir},
	{0}
};

static struct ctl_table_header *sysctl_w83627hf_wdt_header = 0;

/*
 * When you write something into /proc/sys/w83627hf_wdt/feed, the dog begin
 * watch the system for you.
 * Remember to feed it again before the dog burks.
 * Set /proc/sys/w83627hf_wdt/margin to 0, and update 
 * /proc/sys/w83627hf_wdt/feed will stop the dog.
 * */
int w83627hf_wdt_sysctl_feed(struct ctl_table *table, int write,
	struct file *filp, void *buffer, size_t *lenp)
{
	if (!write)
		return -EPERM;

	w83627hf_wdt_feed();

	return 0;
}

int w83627hf_wdt_sysctl_create(void)
{
	sysctl_w83627hf_wdt_header =
		register_sysctl_table(sysctl_w83627hf_wdt_root, 0);
	if (0 == sysctl_w83627hf_wdt_header) {
		printk("W83627HF WDT: register_sysctl_table() failed\n");
		return -1;
	}

	return 0;
}

void w83627hf_wdt_sysctl_clean(void)
{
	unregister_sysctl_table(sysctl_w83627hf_wdt_header);
	sysctl_w83627hf_wdt_header = 0;
}
#endif

/*
 * Read the margin and timeunit from watchdog device, just for debug.
 * */
static ssize_t w83627hf_wdt_read(struct file *file, char *buf,
	size_t len, loff_t *ppos)
{
	unsigned char t[2];

	assert_w83627hf_wdt(wdt_is_open);

	if (ppos != &file->f_pos) {
#ifdef W83627HF_WDT_DEBUG
		printk("W83627HF WDT: device can NOT be seek\n");
#endif
		return -ESPIPE;
	}

	if (2 > len) {
#ifdef W83627HF_WDT_DEBUG
		printk("W83627HF WDT: read invalid data length %d\n", len);
#endif
		return -EINVAL;
	}

	outb(W83627HF_ENTER_EFM, W83627HF_EFER);
	outb(W83627HF_ENTER_EFM, W83627HF_EFER);

	outb(0x07, W83627HF_EFIR);
	outb(0x08, W83627HF_EFDR);

	outb(0xF5, W83627HF_EFIR);
	t[0] = (0x08 & inb(W83627HF_EFDR)) ? 0x01 : 0x00;

	outb(0xF6, W83627HF_EFIR);
	t[1] = inb(W83627HF_EFDR);

	outb(W83627HF_EXIT_EFM, W83627HF_EFER);

	if (copy_to_user(buf, t, 2)) {
#ifdef W83627HF_WDT_DEBUG
		printk("W83627HF WDT: copy_to_user failed\n");
#endif
		return -EFAULT;
	}

	return 2;
}

static ssize_t w83627hf_wdt_write(struct file *file, const char *data,
	size_t len, loff_t *ppos)
{
	size_t i;
	int j = 0, seek = 0;
	unsigned int timeunit = 0;

	assert_w83627hf_wdt(wdt_is_open);

	if (ppos != &file->f_pos) {
#ifdef W83627HF_WDT_DEBUG
		printk("W83627HF WDT: device can NOT be seek\n");
#endif
		return -ESPIPE;
	}

	if (0 == len)
		goto ping;

	for (i=0; i<len; i++){
		switch (data[i]) {
		case 'W':
			j = W83627HF_WDT_MAGIC_NUM - 2;
			seek = 1;
			break;
#ifndef CONFIG_WATCHDOG_NOWAYOUT
		case 'V':
			wdt_expect_close = 1;
			printk(KERN_DEBUG "expect_close changed\n");
			j = 0;
			seek = 0;
			break;
#endif
		default:
			if (0 == seek) {
				assert_w83627hf_wdt(0 == j);
				break;
			}
			assert_w83627hf_wdt(1 == seek);
			assert_w83627hf_wdt(0 <= j - 1);

			if (W83627HF_WDT_MAGIC_U == j) {
				assert_w83627hf_wdt('?' ==
					w83627hf_wdt_magic[j - 1]);
				if (1 == data[i]) 
					timeunit = 1;
				else
					timeunit = 0;
				j--;
			} else if (W83627HF_WDT_MAGIC_M == j) {
				assert_w83627hf_wdt('?' ==
					w83627hf_wdt_magic[j - 1]);
				if (0<=data[i] &&
					W83627HF_WDT_DEF_TIMO>=data[i]) {
					w83627hf_margin = data[i];
					w83627hf_timeunit = timeunit;
					printk(KERN_DEBUG "margin chanded! "
						"margin == %d, unit == %d\n", 
						w83627hf_margin, 
						w83627hf_timeunit);
				}
				j = 0;
				seek = 0;
			} else if (data[i] == w83627hf_wdt_magic[j - 1]) {
				j--;
			} else {
				j = 0;
				seek = 0;
			}

			break;
		}
	}

ping:
	if (wdt_is_open) {
		w83627hf_wdt_feed();
	}

	return len;
}

static int w83627hf_wdt_ioctl(struct inode *inode, struct file *file,
	unsigned int cmd, unsigned long arg)
{
	int ret = 0;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0)
	int time;
#endif
	struct watchdog_info ident = {
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0)
		WDIOF_SETTIMEOUT |
#endif
		0,
		1,
		"W83627HF"
	};

	assert_w83627hf_wdt(wdt_is_open);

	if (WATCHDOG_MINOR != MINOR(inode->i_rdev))
		return -ENODEV;

	switch (cmd) {
	case WDIOC_GETSUPPORT:
		if (copy_to_user((struct watchdog_info *)arg, &ident,
			sizeof(ident)))
			ret = -EFAULT;
		break;
	case WDIOC_KEEPALIVE:
		if (wdt_is_open) {
			w83627hf_wdt_feed();
		}
		break;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0)
	case WDIOC_SETTIMEOUT:
		if (copy_from_user(&time, (int *)arg, sizeof(int))) {
			ret = -EFAULT;
			break;
		}

		printk(KERN_DEBUG "W83627HF WDT: w83627hf_margin %d.\n",
			w83627hf_margin);

		if (W83627HF_WDT_MAX_TIMO < time) {
			printk("W83627HF WDT: w83637hf_margin > 0xFF\n");
			time = W83627HF_WDT_MAX_TIMO;
		}

		if (0!=time && W83627HF_WDT_MIN_TIMO>time) {
			printk("W83627HF WDT: w83627hf_margin < 0x01\n");
			time = W83627HF_WDT_MIN_TIMO;
		}

		w83627hf_margin = time;
		/* Fall */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,18)
	case WDIOC_GETTIMEOUT:
		if (copy_to_user((int *)arg, &w83627hf_margin,
			sizeof(int)))
			ret = -EFAULT;
#endif
		break;
#endif
	default:
		ret = -ENOTTY;
		break;
	}

	return ret;
}

/*
 * Once the user application open the device file, watchdog start work.
 * You MUST feed the dog before it burks.
 * */
static int w83627hf_wdt_open(struct inode *inode, struct file *file)
{
	if (WATCHDOG_MINOR != MINOR(inode->i_rdev))
		return -ENODEV;

	/* only one can open this device */
	if (wdt_is_open)
		return -EBUSY;
	wdt_is_open = 1;

	MOD_INC_USE_COUNT;

#ifndef CONFIG_WATCHDOG_NOWAYOUT
	wdt_expect_close = 0;
#endif

	if (W83627HF_WDT_MAX_TIMO < w83627hf_margin) {
		printk("W83627HF WDT: w83627hf_margin > 0xFF\n");
		w83627hf_margin = W83627HF_WDT_MAX_TIMO;
	}

	if (0!=w83627hf_margin &&
		W83627HF_WDT_MIN_TIMO>w83627hf_margin) {
		printk("W83627HF WDT: w83627hf_margin < 0x01\n");
		w83627hf_margin = W83627HF_WDT_MIN_TIMO;
	}

	/* enter the extended function mode */
	outb(W83627HF_ENTER_EFM, W83627HF_EFER);
	outb(W83627HF_ENTER_EFM, W83627HF_EFER);

	/*
	 * watchdog timer is controlled by CRF5, CRF6, CRF7 of logical
	 * device 8.
	 * */
	outb(0x2B, W83627HF_EFIR);
	outb(0xC0, W83627HF_EFDR);

	/* write CR07 to select logical device A */
	outb(0x07, W83627HF_EFIR);
	outb(0x0A, W83627HF_EFDR);

	/* disable watchdog IRQ */
	outb(0xF7, W83627HF_EFIR);
	outb(0x00, W83627HF_EFDR);

	/* write CR07 to select logical device 8 */
	outb(0x07, W83627HF_EFIR);
	outb(0x08, W83627HF_EFDR);

	/* count time base second(NOT minute). */
	outb(0xF5, W83627HF_EFIR);
	outb((unsigned char)W83627HF_WDT_DEF_TIMU, W83627HF_EFDR);

	/* write the timeout vlaue into CRF6 */
	outb(0xF6, W83627HF_EFIR);
	outb((unsigned char)w83627hf_margin, W83627HF_EFDR);

	/* watchdog will NOT be reset upon a mouse/keyboard interrupt. */
	outb(0xF7, W83627HF_EFIR);
	outb(0x00, W83627HF_EFDR);

	/* exit extended function mode */
	outb(W83627HF_EXIT_EFM, W83627HF_EFER);

	printk(KERN_INFO "W83627HF WDT: wake up, current timeout %d.\n",
		w83627hf_margin);

	return 0;
}

static int w83627hf_wdt_release(struct inode *inode, struct file *file)
{
	assert_w83627hf_wdt(wdt_is_open);

	if (WATCHDOG_MINOR != MINOR(inode->i_rdev))
		return -ENODEV;

#ifndef CONFIG_WATCHDOG_NOWAYOUT
	if (wdt_expect_close) {
		if(wdt_is_open) {
			w83627hf_wdt_stop();
		}
	}

	wdt_expect_close = 0;
#endif

	wdt_is_open = 0;

	MOD_DEC_USE_COUNT;

	printk(KERN_INFO "W83627HF WDT: sleeping again.\n");

	return 0;
}

/*
 * probe the W83627HF chip.
 * */
static int w83627hf_probe(void)
{
	unsigned char device;
	unsigned char chip;
	int ret = -1;

	if (0 > check_region(W83627HF_LPCIP, 2)) {
		ret = -EBUSY;
		goto out;
	}

	outb(0x49, W83627HF_LPCIP);
	device = inb(W83627HF_LPCDP);
	if (0x01 != (device >> 1)) {
		/*printk("W83627HF WDT: Device ID %x != 0x01\n", device >> 1);*/
		goto out;
	}

	outb(0x58, W83627HF_LPCIP);
	chip = inb(W83627HF_LPCDP);
	if (0x21 != chip) {
		/*printk("W83627HF WDT: Chip ID %x != 0x21\n", chip);*/
		goto out;
	}

	/*printk(KERN_INFO "W83627HF WDT: W83627HF chip found\n");*/
	ret = 0;

out:
	return ret;
}

static int w83627hf_wdt_notify(struct notifier_block *this, unsigned long code,
	void *unused)
{
	if (SYS_DOWN==code || SYS_HALT==code) {
		if (wdt_is_open) {
			w83627hf_wdt_stop();
#ifndef CONFIG_WATCHDOG_NOWAYOUT
			wdt_expect_close = 0;
#endif
			wdt_is_open = 0;
		}
	}

	return NOTIFY_DONE;
}

static struct notifier_block w83627hf_wdt_notifier=
{
	w83627hf_wdt_notify,
	0,
	0
};

static struct file_operations w83627hf_wdt_fops = {
	llseek:		0,
	read:		w83627hf_wdt_read,
	write:		w83627hf_wdt_write,
	readdir:	0,
	poll:		0,
	ioctl:		w83627hf_wdt_ioctl,
	mmap:		0,
	open:		w83627hf_wdt_open,
	flush:		0,
	release:	w83627hf_wdt_release,
};

static struct miscdevice w83627hf_wdt_miscdev = {
	WATCHDOG_MINOR,
	"watchdog",
	&w83627hf_wdt_fops
};

int init_module(void)
{
	int ret;

	if ((ret = w83627hf_probe()))
		goto out;

	ret = -1;
	request_region(W83627HF_LPCIP, 2, "W83627HF_WDT");

	if (misc_register(&w83627hf_wdt_miscdev)) {
		printk("W83627HF WDT: misc_register() failed\n");
		goto clean_region;
	}

	if (register_reboot_notifier(&w83627hf_wdt_notifier)) {
		printk("W83627HF WDT: register_reboot_notifier() failed\n");
		goto clean_miscdev;
	}

#ifdef W83627HF_WDT_DEBUG
	if (w83627hf_wdt_sysctl_create())
		goto clean_notifier;
#endif

	if (W83627HF_WDT_MAX_TIMO < w83627hf_margin) {
		printk("W83627HF WDT: w83627hf_margin > 0xFF\n");
		w83627hf_margin = W83627HF_WDT_MAX_TIMO;
	}

	if (W83627HF_WDT_MIN_TIMO > w83627hf_margin) {
		printk("W83627HF WDT: w83627hf_margin < 0x01\n");
		w83627hf_margin = W83627HF_WDT_MIN_TIMO;
	}

	printk(KERN_INFO "W83627HF WDT: sleeping.\n");

	ret = 0;
	goto out;

#ifdef W83627HF_WDT_DEBUG
clean_notifier:
	unregister_reboot_notifier(&w83627hf_wdt_notifier);
#endif
clean_miscdev:
	misc_deregister(&w83627hf_wdt_miscdev);
clean_region:
	release_region(W83627HF_LPCIP, 2);
out:
	return ret;
}

void cleanup_module(void)
{
#ifdef W83627HF_WDT_DEBUG
	w83627hf_wdt_sysctl_clean();
#endif
	unregister_reboot_notifier(&w83627hf_wdt_notifier);
	misc_deregister(&w83627hf_wdt_miscdev);
	release_region(W83627HF_LPCIP, 2);
}

MODULE_PARM(w83627hf_margin, "i");
MODULE_PARM_DESC(w83627hf_magin, "WDT timeout (default 12 <0x01-0xFF>)");
MODULE_AUTHOR("Zhou HongZhen -- <inetbash@yahoo.com>|<netdiff@netease.com>");
MODULE_DESCRIPTION("Watchdog driver for Winbond W83627HF chip");
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0)
MODULE_LICENSE("GPL");
#endif

EXPORT_NO_SYMBOLS;


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2002-12-19  2:53 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2002-12-19  2:54 watchdog driver for W83627HF chip zhz

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.