RE: Fintek F81865 chip (Wdog support)

From: Bruno André Ferreira
Date: Fri Aug 02 2013 - 11:04:50 EST


Sorry.

Wrong driver...

Best regards,
Bruno Ferreira
________________________________________
From: BrunoFerreira [bruno.ferreira@xxxxxxxxxx]
Sent: Friday, August 02, 2013 3:17 PM
To: Wim Van Sebroeck
Cc: Giel van Schijndel; linux-watchdog@xxxxxxxxxxxxxxx
Subject: Re: Fintek F81865 chip (Wdog support)

Hi All.

Sorry about the delay.
Please find in attach the driver source code with support for this chip.

Kind regards,
Bruno Ferreira

On 05/26/2013 05:44 PM, Wim Van Sebroeck wrote:
> Hi All,
>
>> On Wed, 13 Mar 2013 15:04:38 +0000, BrunoFerreira wrote:
>>> I'm currently working with a new board iEi NOVA-PV-D5251 [1] that
>>> have the Fintek F81865 chip for Super I/O support and I will need to
>>> develop the watchdog driver and another to access to the IO that this
>>> board supports (gpio). I made a search and I see that already exists
>>> a
>>> driver for F71808E chip, I use this driver as an example an I made a
>>> new driver for F81865 chip. My driver is working pretty well, but
>>> I've
>>> a question that I can't find the answer on datasheet of this chip
>>> that
>>> may be Wim or Giel could know.
>>> On both chips, we need to configure a pin that can work as a normal
>>> GPIO (F81865: Set pin 70 the function of WDTRST#/GPIO15 is WDTRST# |
>>> F71808E: Set pin 21 to GPIO23/WDTRST#, then to WDTRST#), this is here
>>> where I get myself confused, this WDTRST will be mapped in any GPIO
>>> output on my board? I mean, if the watchdog is enable I will get any
>>> output pin set to 1 (i.e. the GPIO15, output 5?) and when the
>>> watchdog
>>> goes down this output goes to 0?
>> Sorry, I can't give you a definite answer on that. All Fintek
>> datasheets I've seen are very poorly written. At one point I actually
>> stopped trusting the datasheet enough to go through the hassle of
>> hooking up a scope to the pins of the chip.
>>
>> Hazarding a guess however, I'd say that when the watchdog is enabled
>> you cannot use the WDTRST pin as a GPIO pin.
>>
>>> The other question is to Wim, can you tell me if there is interest in
>>> add this chip support to kernel?
>> This would be a yes, unless there are good reasons not to include it in
>> mainline.
> The answer is indeed yes. Has a patch been created since this message?
>
> Kind regards,
> Wim.
>
>

/***************************************************************************
* Copyright (C) 2013 Bruno Ferreira <bruno.ferreira@xxxxxxxxxx> *
* *
* 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. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/

/**
* [x86] Add support for Fintek hardware watchdogs (Closes: #601187)
- resource: Add shared I/O region support
- hwmon: f71882fg: Use a muxed resource lock for the Super I/O port
- watchdog: Add f71808e_wdt driver

http://lxr.free-electrons.com/source/drivers/watchdog/f71808e_wdt.c?v=3.3
https://github.com/spotify/linux/blob/master/drivers/watchdog/f71808e_wdt.c
*/

#include <linux/err.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/uaccess.h>
#include <linux/watchdog.h>
#include <linux/ioport.h>

#define DRVNAME "f81865_wdt"

/* Global Control Registers */
#define SIO_F81865_LD_WDT 0x07 /* Watchdog Logic Number Register (LDN) */
#define SIO_F81865_LD_GPIO 0x06 /* GPIO Logic Number Register (LDN) */
#define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
#define SIO_LOCK_KEY 0xAA /* Key to diasble Super-I/O */

#define SIO_REG_LDSEL 0x07 /* Logical device select */
#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
#define SIO_REG_DEVREV 0x22 /* Device revision */
#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */

/* WDT Device Configuration Registers (LDN CR07) */
#define SIO_REG_ENABLE 0x30 /* WDT Device enable register */
#define SIO_REG_ADDR 0x60 /* Base device address (2 bytes) */
#define F81865_REG_WDT_CONF 0xf5 /* WDT Control Register */
#define F81865_REG_WD_TIME 0xf6 /* WDT Time Register */
#define F81865_REG_WD_PME 0xfa /* WDT PME Enable register */

/* Manufacture and Chip Information */
#define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */
#define SIO_F81865_ID 0x0704 /* Chipset ID*/

/* Watchdog Timer Function and WDT Control Register Flags */
#define F81865_FLAG_WDTMOUT_STS 6 /* WD timeout event */
#define F81865_FLAG_WD_EN 5 /* WD time counting */
#define F81865_FLAG_WD_PULSE 4 /* WD pulse mode */
#define F81865_FLAG_WD_UNIT 3 /* WD time unit */
#define F81865_FLAG_WD_UACTIVE 2 /* WD RSTOUT polarity */

/* Default values */
#define WATCHDOG_TIMEOUT 60 /* 1 minute default timeout */
#define WATCHDOG_MAX_TIMEOUT (60 * 255) /* WD_TIME is a byte long */
#define WATCHDOG_PULSE_WIDTH 5000 /* 125 ms, default pulse width for
watchdog signal */

/* Module parameters */
static unsigned short force_id;
module_param(force_id, ushort, 0);
MODULE_PARM_DESC(force_id, " Override the detected device ID");

static const int max_timeout = WATCHDOG_MAX_TIMEOUT;
static int timeout = 60; /* default timeout in seconds */
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout,
" Watchdog timeout in seconds. 1<= timeout <="
__MODULE_STRING(WATCHDOG_MAX_TIMEOUT) " (default="
__MODULE_STRING(WATCHDOG_TIMEOUT) ")");

static unsigned int pulse_width = WATCHDOG_PULSE_WIDTH;
module_param(pulse_width, uint, 0);
MODULE_PARM_DESC(pulse_width,
" Watchdog signal pulse width. 0(=level), 1 ms, 25 ms, 125 ms or 5000 ms"
" (default=" __MODULE_STRING(WATCHDOG_PULSE_WIDTH) ")");

static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0444);
MODULE_PARM_DESC(nowayout, " Disable watchdog shutdown on close");

static unsigned int start_withtimeout;
module_param(start_withtimeout, uint, 0);
MODULE_PARM_DESC(start_withtimeout, " Start watchdog timer on module load with"
" given initial timeout. Zero (default) disables this feature.");

/* Chips variants */
enum chips { f81865 };

/* Chips Names */
static const char *f81865_names[] = { "F81865" };

/* Super-I/O Function prototypes */
static inline int superio_enter(int base);
static inline void superio_exit(int base);
static inline void superio_select(int base, int ldn);
static inline int superio_inb(int base, int reg);
static inline int superio_inw(int base, int reg);
static inline void superio_outb(int base, int reg, u8 val);
static inline void superio_set_bit(int base, int reg, int bit);
static inline void superio_clear_bit(int base, int reg, int bit);

/* Wdog internal data information */
struct watchdog_data {
unsigned short sioaddr; /* default index port */
enum chips type; /* chip type */
unsigned long opened; /* driver open state */
struct mutex lock; /* concurrency control */
char expect_close; /* controlled close */
struct watchdog_info ident; /* wdog information*/

unsigned short timeout; /* current wdog timeout */
u8 timer_val; /* content for the WD_TIME register */
u8 pulse_val; /* pulse width flag */
char pulse_mode; /* enable pulse output mode? */
char caused_reboot; /* last reboot was by the watchdog */
};

static struct watchdog_data watchdog = {
.lock = __MUTEX_INITIALIZER(watchdog.lock),
};

/* Super I/O functions */
static inline int superio_enter(int base)
{
/* don't step on other drivers' I/O space by accident */
if (!request_muxed_region(base, 2, DRVNAME)) {
printk(KERN_ERR DRVNAME ": I/O address 0x%04x already in use\n",
(int)base);
return -EBUSY;
}

/* according to the datasheet the key must be send twice! */
outb(SIO_UNLOCK_KEY, base);
outb(SIO_UNLOCK_KEY, base);

return 0;
}

static inline void superio_exit(int base)
{
outb(SIO_LOCK_KEY, base);
release_region(base, 2);
}

static inline void superio_select(int base, int ldn)
{
outb(SIO_REG_LDSEL, base);
outb(ldn, base + 1);
}

static inline int superio_inb(int base, int reg)
{
outb(reg, base);
return inb(base + 1);
}

static int superio_inw(int base, int reg)
{
int val;
val = superio_inb(base, reg) << 8;
val |= superio_inb(base, reg + 1);
return val;
}

static inline void superio_outb(int base, int reg, u8 val)
{
outb(reg, base);
outb(val, base + 1);
}

static inline void superio_set_bit(int base, int reg, int bit)
{
unsigned long val = superio_inb(base, reg);
__set_bit(bit, &val);
superio_outb(base, reg, val);
}

static inline void superio_clear_bit(int base, int reg, int bit)
{
unsigned long val = superio_inb(base, reg);
__clear_bit(bit, &val);
superio_outb(base, reg, val);
}

/* Internal Configuration functions */
static int watchdog_set_timeout(int timeout)
{
if (timeout <= 0 || timeout > max_timeout) {
printk(KERN_ERR DRVNAME ": watchdog timeout out of range\n");
return -EINVAL;
}

mutex_lock(&watchdog.lock);

watchdog.timeout = timeout;
if (timeout > 0xff) {
watchdog.timer_val = DIV_ROUND_UP(timeout, 60);
} else {
watchdog.timer_val = timeout;
}

mutex_unlock(&watchdog.lock);
printk(KERN_INFO DRVNAME ": watchdog_set_timeout(%d)...\n", timeout);
return 0;
}

static int watchdog_set_pulse_width(unsigned int pw)
{
int err = 0;
mutex_lock(&watchdog.lock);

if (pw <= 1) {
watchdog.pulse_val = 0;
} else if (pw <= 25) {
watchdog.pulse_val = 1;
} else if (pw <= 125) {
watchdog.pulse_val = 2;
} else if (pw <= 5000) {
watchdog.pulse_val = 3;
} else {
printk(KERN_ERR DRVNAME ": pulse width out of range\n");
err = -EINVAL;
goto exit_unlock;
}
watchdog.pulse_mode = pw;

exit_unlock:
mutex_unlock(&watchdog.lock);

printk(KERN_INFO DRVNAME ": watchdog_set_pulse_width:'%d' - (%d)...\n", pw,
err);
return err;
}

/* Driver useful functions */
static int watchdog_keepalive(void)
{
int err = 0;
mutex_lock(&watchdog.lock);

err = superio_enter(watchdog.sioaddr);
if (err) goto exit_unlock;

superio_select(watchdog.sioaddr, SIO_F81865_LD_WDT);

/* Set timer value */
superio_outb(watchdog.sioaddr, F81865_REG_WD_TIME, watchdog.timer_val);

superio_exit(watchdog.sioaddr);

exit_unlock:
mutex_unlock(&watchdog.lock);

return err;
}

static int watchdog_start(void)
{
int err = 0;

mutex_lock(&watchdog.lock);
err = superio_enter(watchdog.sioaddr);
if (err) goto exit_unlock;

/* @WARNING@ I'm sure that is the right LND... NEED TO CONFIRM...*/
// SIO_F81865_LD_GPIO -> 0x06
//superio_select(watchdog.sioaddr, SIO_F81865_LD_WDT);

/* Watchdog output pin configuration */
switch (watchdog.type) {
case f81865:
/* Set pin 70 the function of WDTRST#/GPIO15 is WDTRST# */
//superio_clear_bit(watchdog.sioaddr, 0x2b, 5);
printk(KERN_ERR DRVNAME ": Set pin 70 the function of WDTRST#/GPIO15 "
"is WDTRST#\n");
break;
default:
printk(KERN_ERR DRVNAME ": Unable to configure WDTRST pin...\n");
err = -ENODEV;
goto exit_superio;
}

superio_select(watchdog.sioaddr, SIO_F81865_LD_WDT);
superio_set_bit(watchdog.sioaddr, SIO_REG_ENABLE, 0);

/* Enable WD time out output via WDTRST# */
superio_set_bit(watchdog.sioaddr, F81865_REG_WD_PME, 0);

/* Set Pulse mode ...*/
if (watchdog.pulse_mode) {
/* Select "pulse" output mode with given duration */
u8 wdt_conf = superio_inb(watchdog.sioaddr, F81865_REG_WDT_CONF);

/* Set WD_PSWIDTH bits (1:0) */
wdt_conf = (wdt_conf & 0xfc) | (watchdog.pulse_val & 0x03);
/* Set WD_PULSE to "pulse" mode */
wdt_conf |= BIT(F81865_FLAG_WD_PULSE);

superio_outb(watchdog.sioaddr, F81865_REG_WDT_CONF, wdt_conf);
} else {
/* Select "level" output mode */
superio_clear_bit(watchdog.sioaddr, F81865_REG_WDT_CONF,
F81865_FLAG_WD_PULSE);
}

/* Set timer value */
superio_outb(watchdog.sioaddr, F81865_REG_WD_TIME, watchdog.timer_val);

/* Enable WD */
superio_set_bit(watchdog.sioaddr, F81865_REG_WDT_CONF, F81865_FLAG_WD_EN);

exit_superio:
superio_exit(watchdog.sioaddr);
exit_unlock:
mutex_unlock(&watchdog.lock);

printk(KERN_INFO DRVNAME ": watchdog_start(%d)...\n", err);
return err;
}

static int watchdog_stop(void)
{
int err = 0;
mutex_lock(&watchdog.lock);

err = superio_enter(watchdog.sioaddr);
if (err) goto exit_unlock;

superio_select(watchdog.sioaddr, SIO_F81865_LD_WDT);
superio_clear_bit(watchdog.sioaddr, F81865_REG_WDT_CONF, F81865_FLAG_WD_EN);
superio_exit(watchdog.sioaddr);

exit_unlock:
mutex_unlock(&watchdog.lock);

return err;
}

static int watchdog_get_status(void)
{
int status = 0;

mutex_lock(&watchdog.lock);
status = (watchdog.caused_reboot) ? WDIOF_CARDRESET : 0;
mutex_unlock(&watchdog.lock);

printk(KERN_INFO DRVNAME ": watchdog_get_status(%d)...\n", status);
return status;
}

static bool watchdog_is_running(void)
{
bool is_running = true;
mutex_lock(&watchdog.lock);

if (superio_enter(watchdog.sioaddr)) goto exit_unlock;
superio_select(watchdog.sioaddr, SIO_F81865_LD_WDT);

is_running = (superio_inb(watchdog.sioaddr, SIO_REG_ENABLE) & BIT(0)) &
((superio_inb(watchdog.sioaddr, F81865_REG_WDT_CONF) &
BIT(F81865_FLAG_WD_EN)) != 0) ? 1 : 0;

superio_exit(watchdog.sioaddr);

exit_unlock:
mutex_unlock(&watchdog.lock);

printk(KERN_INFO DRVNAME ": watchdog_is_running(%s)...\n",
(is_running ? "Yes" : "No"));
return is_running;
}

/* /dev/watchdog api */
static int watchdog_open(struct inode *inode, struct file *file)
{
int err;

/* If the watchdog is alive we don't need to start it again */
if (test_and_set_bit(0, &watchdog.opened)) return -EBUSY;

err = watchdog_start();
if (err) {
clear_bit(0, &watchdog.opened);
printk(KERN_ERR DRVNAME ": fail to open watchdog...\n");
return err;
}

if (nowayout) __module_get(THIS_MODULE);

watchdog.expect_close = 0;
printk(KERN_INFO DRVNAME ": watchdog open...\n");
return nonseekable_open(inode, file);
}

static int watchdog_release(struct inode *inode, struct file *file)
{
clear_bit(0, &watchdog.opened);

if (!watchdog.expect_close) {
watchdog_keepalive();
printk(KERN_CRIT DRVNAME
": Unexpected close, not stopping watchdog!\n");
} else if (!nowayout) {
watchdog_stop();
}

printk(KERN_INFO DRVNAME ": watchdog release...\n");
return 0;
}

static ssize_t watchdog_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
if (count) {
if(!nowayout) {
size_t i;
/* In case it was set long ago */
bool expect_close = false;
for (i = 0; i != count; i++) {
char c;
if (get_user(c, buf + i)) return -EFAULT;
expect_close = (c == 'V');
}

/* Properly order writes across fork()ed processes */
mutex_lock(&watchdog.lock);
watchdog.expect_close = expect_close;
mutex_unlock(&watchdog.lock);
}

/* someone wrote to us, we should restart timer */
watchdog_keepalive();
}
return count;
}

static long watchdog_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
int status;
int new_options;
int new_timeout;

union {
struct watchdog_info __user *ident;
int __user *i;
} uarg;

uarg.i = (int __user *) arg;
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(uarg.ident, &watchdog.ident,
sizeof(watchdog.ident)) ? -EFAULT : 0;

case WDIOC_GETSTATUS:
status = watchdog_get_status();
if (status < 0) return status;
return put_user(status, uarg.i);

case WDIOC_GETBOOTSTATUS:
return put_user(0, uarg.i);

case WDIOC_SETOPTIONS:
if (get_user(new_options, uarg.i)) return -EFAULT;
if (new_options & WDIOS_DISABLECARD) watchdog_stop();
if (new_options & WDIOS_ENABLECARD) return watchdog_start();

case WDIOC_KEEPALIVE:
watchdog_is_running();
watchdog_keepalive(); return 0;

case WDIOC_SETTIMEOUT:
if (get_user(new_timeout, uarg.i)) return -EFAULT;
if (watchdog_set_timeout(new_timeout)) return -EINVAL;
watchdog_keepalive();
/* Return the WDIOC_GETTIMEOUT */

case WDIOC_GETTIMEOUT:
return put_user(watchdog.timeout, uarg.i);
default:
return -ENOTTY;
}
}

static int watchdog_notify_sys(struct notifier_block *this, unsigned long code,
void *unused)
{
if (code == SYS_DOWN || code == SYS_HALT) watchdog_stop();
return NOTIFY_DONE;
}

/* /dev/watchdog api available options */
static const struct file_operations watchdog_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.open = watchdog_open,
.release = watchdog_release,
.write = watchdog_write,
.unlocked_ioctl = watchdog_ioctl,
};

static struct miscdevice watchdog_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &watchdog_fops,
};

static struct notifier_block watchdog_notifier = {
.notifier_call = watchdog_notify_sys,
};

/* /dev/watchdog Main functions */
static int __init watchdog_init(int sio_addr)
{
int wdt_conf, err = 0;

watchdog.sioaddr = sio_addr;
watchdog.ident.options = WDIOC_SETTIMEOUT | WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING;

snprintf(watchdog.ident.identity, sizeof(watchdog.ident.identity),
"%s watchdog", f81865_names[watchdog.type]);

/* start wdog configuration */
err = superio_enter(watchdog.sioaddr);
if (err) return err;

superio_select(watchdog.sioaddr, SIO_F81865_LD_WDT);
wdt_conf = superio_inb(watchdog.sioaddr, F81865_REG_WDT_CONF);
watchdog.caused_reboot = wdt_conf & F81865_FLAG_WDTMOUT_STS;
superio_exit(watchdog.sioaddr);

err = watchdog_set_timeout(timeout);
if (err) return err;

err = watchdog_set_pulse_width(pulse_width);
if (err) return err;

err = register_reboot_notifier(&watchdog_notifier);
if (err) return err;

err = misc_register(&watchdog_miscdev);
if (err) {
printk(KERN_ERR DRVNAME ": cannot register miscdev on minor=%d\n",
watchdog_miscdev.minor);
goto exit_reboot;
}

if (start_withtimeout) {
if (start_withtimeout <= 0 || start_withtimeout > max_timeout) {
printk(KERN_ERR DRVNAME ": starting timeout out of range\n");
err = -EINVAL;
goto exit_miscdev;
}

err = watchdog_start();
if (err) {
printk(KERN_ERR DRVNAME ": cannot start watchdog timer\n");
goto exit_miscdev;
}

mutex_lock(&watchdog.lock);
err = superio_enter(watchdog.sioaddr);
if (err) goto exit_unlock;

superio_select(watchdog.sioaddr, SIO_F81865_LD_WDT);

if (start_withtimeout > 0xff) {
/* select minutes for timer units */
superio_set_bit(watchdog.sioaddr, F81865_REG_WDT_CONF,
F81865_FLAG_WD_UNIT);
superio_outb(watchdog.sioaddr, F81865_REG_WD_TIME,
DIV_ROUND_UP(start_withtimeout, 60));
} else {
/* select seconds for timer units */
superio_clear_bit(watchdog.sioaddr, F81865_REG_WDT_CONF,
F81865_FLAG_WD_UNIT);
superio_outb(watchdog.sioaddr, F81865_REG_WD_TIME,
start_withtimeout);
}

/* set RSOUTPUT polarity to active low */
superio_clear_bit(watchdog.sioaddr, F81865_REG_WDT_CONF,
F81865_FLAG_WD_UACTIVE);

superio_exit(watchdog.sioaddr);
mutex_unlock(&watchdog.lock);

if (nowayout) __module_get(THIS_MODULE);

printk(KERN_INFO DRVNAME
": watchdog started with initial timeout of %u sec\n",
start_withtimeout);
}

printk(KERN_INFO DRVNAME
": watchdog started with timeout of %u sec\n",
watchdog.timeout);
return 0;

exit_unlock:
mutex_unlock(&watchdog.lock);
exit_miscdev:
misc_deregister(&watchdog_miscdev);
exit_reboot:
unregister_reboot_notifier(&watchdog_notifier);

return err;
}

static int __init f81865_find(int sio_addr)
{
u16 devid;
int err = superio_enter(sio_addr);
if (err) return err;

devid = superio_inw(sio_addr, SIO_REG_MANID);
if (devid != SIO_FINTEK_ID) {
pr_debug(DRVNAME ": Not a Fintek device\n");
err = -ENODEV;
goto exit;
}

devid = force_id ? force_id : superio_inw(sio_addr, SIO_REG_DEVID);
switch (devid) {
case SIO_F81865_ID:
watchdog.type = f81865;
break;
default:
printk(KERN_INFO DRVNAME ": Unrecognized Fintek device: %04x\n",
(unsigned int) devid);
err = -ENODEV;
goto exit;
}

printk(KERN_INFO DRVNAME ": Found %s watchdog chip, revision %d\n",
f81865_names[watchdog.type], (int) superio_inb(sio_addr, SIO_REG_DEVREV));

exit:
superio_exit(sio_addr);

return err;
}

static int __init f81865_init(void)
{
static const unsigned short addrs[] = { 0x2e, 0x4e };
int err = -ENODEV, i;

for (i=0; i < ARRAY_SIZE(addrs); i++) {
err = f81865_find(addrs[i]);
if (err == 0) break;
}
if (i == ARRAY_SIZE(addrs)) return err;

return watchdog_init(addrs[i]);
}

static void __exit f81865_exit(void)
{
if (watchdog_is_running()) {
printk(KERN_WARNING DRVNAME
": Watchdog timer still running, stopping it\n");
watchdog_stop();
}

misc_deregister(&watchdog_miscdev);
unregister_reboot_notifier(&watchdog_notifier);
}

MODULE_AUTHOR("Bruno Ferreira");
MODULE_DESCRIPTION("Hardware Watchdog Device Driver for F81865 chip I/O");
MODULE_LICENSE("GPL");

module_init(f81865_init);
module_exit(f81865_exit);