mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: zhz@wlm.com.cn
To: linux-kernel@vger.kernel.org
Subject: watchdog driver for W83627HF chip
Date: Thu, 19 Dec 2002 02:54:21 GMT	[thread overview]
Message-ID: <20021219025421.C0B968C8DD@263commail.com> (raw)

[-- 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;


                 reply	other threads:[~2002-12-19  2:53 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20021219025421.C0B968C8DD@263commail.com \
    --to=zhz@wlm.com.cn \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome