diff options
Diffstat (limited to 'drivers/watchdog/f71808e_wdt.c')
| -rw-r--r-- | drivers/watchdog/f71808e_wdt.c | 736 |
1 files changed, 292 insertions, 444 deletions
diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c index 016bd9355190..6a16d3d0bb1e 100644 --- a/drivers/watchdog/f71808e_wdt.c +++ b/drivers/watchdog/f71808e_wdt.c @@ -1,53 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /*************************************************************************** * Copyright (C) 2006 by Hans Edgington <hans@edgington.nl> * * Copyright (C) 2007-2009 Hans de Goede <hdegoede@redhat.com> * * Copyright (C) 2010 Giel van Schijndel <me@mortis.eu> * * * - * 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. * ***************************************************************************/ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #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/platform_device.h> #include <linux/watchdog.h> #define DRVNAME "f71808e_wdt" #define SIO_F71808FG_LD_WDT 0x07 /* Watchdog timer logical device */ #define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */ -#define SIO_LOCK_KEY 0xAA /* Key to diasble Super-I/O */ +#define SIO_LOCK_KEY 0xAA /* Key to disable 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) */ +#define SIO_REG_CLOCK_SEL 0x26 /* Clock select */ #define SIO_REG_ROM_ADDR_SEL 0x27 /* ROM address select */ +#define SIO_F81866_REG_PORT_SEL 0x27 /* F81866 Multi-Function Register */ +#define SIO_REG_TSI_LEVEL_SEL 0x28 /* TSI Level select */ #define SIO_REG_MFUNCT1 0x29 /* Multi function select 1 */ #define SIO_REG_MFUNCT2 0x2a /* Multi function select 2 */ #define SIO_REG_MFUNCT3 0x2b /* Multi function select 3 */ +#define SIO_F81866_REG_GPIO1 0x2c /* F81866 GPIO1 Enable Register */ #define SIO_REG_ENABLE 0x30 /* Logical device enable */ #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ @@ -55,10 +41,15 @@ #define SIO_F71808_ID 0x0901 /* Chipset ID */ #define SIO_F71858_ID 0x0507 /* Chipset ID */ #define SIO_F71862_ID 0x0601 /* Chipset ID */ +#define SIO_F71868_ID 0x1106 /* Chipset ID */ #define SIO_F71869_ID 0x0814 /* Chipset ID */ #define SIO_F71869A_ID 0x1007 /* Chipset ID */ #define SIO_F71882_ID 0x0541 /* Chipset ID */ #define SIO_F71889_ID 0x0723 /* Chipset ID */ +#define SIO_F81803_ID 0x1210 /* Chipset ID */ +#define SIO_F81865_ID 0x0704 /* Chipset ID */ +#define SIO_F81866_ID 0x1010 /* Chipset ID */ +#define SIO_F81966_ID 0x1502 /* F81804 chipset ID, same for f81966 */ #define F71808FG_REG_WDO_CONF 0xf0 #define F71808FG_REG_WDT_CONF 0xf5 @@ -66,11 +57,14 @@ #define F71808FG_FLAG_WDOUT_EN 7 -#define F71808FG_FLAG_WDTMOUT_STS 5 +#define F71808FG_FLAG_WDTMOUT_STS 6 #define F71808FG_FLAG_WD_EN 5 #define F71808FG_FLAG_WD_PULSE 4 #define F71808FG_FLAG_WD_UNIT 3 +#define F81865_REG_WDO_CONF 0xfa +#define F81865_FLAG_WDOUT_EN 0 + /* Default values */ #define WATCHDOG_TIMEOUT 60 /* 1 minute default timeout */ #define WATCHDOG_MAX_TIMEOUT (60 * 255) @@ -83,7 +77,6 @@ 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 = WATCHDOG_TIMEOUT; /* default timeout in seconds */ module_param(timeout, int, 0); MODULE_PARM_DESC(timeout, @@ -94,7 +87,7 @@ MODULE_PARM_DESC(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" + "Watchdog signal pulse width. 0(=level), 1, 25, 30, 125, 150, 5000 or 6000 ms" " (default=" __MODULE_STRING(WATCHDOG_PULSE_WIDTH) ")"); static unsigned int f71862fg_pin = WATCHDOG_F71862FG_PIN; @@ -112,15 +105,21 @@ 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."); -enum chips { f71808fg, f71858fg, f71862fg, f71869, f71882fg, f71889fg }; +enum chips { f71808fg, f71858fg, f71862fg, f71868, f71869, f71882fg, f71889fg, + f81803, f81865, f81866, f81966}; -static const char *f71808e_names[] = { +static const char * const fintek_wdt_names[] = { "f71808fg", "f71858fg", "f71862fg", + "f71868", "f71869", "f71882fg", "f71889fg", + "f81803", + "f81865", + "f81866", + "f81966" }; /* Super-I/O Function prototypes */ @@ -133,24 +132,20 @@ static inline int superio_enter(int base); static inline void superio_select(int base, int ld); static inline void superio_exit(int base); -struct watchdog_data { +struct fintek_wdt { + struct watchdog_device wdd; unsigned short sioaddr; enum chips type; - unsigned long opened; - struct mutex lock; - char expect_close; struct watchdog_info ident; - unsigned short timeout; u8 timer_val; /* content for the wd_time register */ char minutes_mode; 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), +struct fintek_wdt_pdata { + enum chips type; }; /* Super I/O functions */ @@ -215,149 +210,158 @@ static inline void superio_exit(int base) release_region(base, 2); } -static int watchdog_set_timeout(int timeout) +static int fintek_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { - if (timeout <= 0 - || timeout > max_timeout) { - pr_err("watchdog timeout out of range\n"); - return -EINVAL; - } - - mutex_lock(&watchdog.lock); + struct fintek_wdt *wd = watchdog_get_drvdata(wdd); - watchdog.timeout = timeout; if (timeout > 0xff) { - watchdog.timer_val = DIV_ROUND_UP(timeout, 60); - watchdog.minutes_mode = true; + wd->timer_val = DIV_ROUND_UP(timeout, 60); + wd->minutes_mode = true; + timeout = wd->timer_val * 60; } else { - watchdog.timer_val = timeout; - watchdog.minutes_mode = false; + wd->timer_val = timeout; + wd->minutes_mode = false; } - mutex_unlock(&watchdog.lock); + wdd->timeout = timeout; return 0; } -static int watchdog_set_pulse_width(unsigned int pw) +static int fintek_wdt_set_pulse_width(struct fintek_wdt *wd, 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; + unsigned int t1 = 25, t2 = 125, t3 = 5000; + + if (wd->type == f71868) { + t1 = 30; + t2 = 150; + t3 = 6000; + } + + if (pw <= 1) { + wd->pulse_val = 0; + } else if (pw <= t1) { + wd->pulse_val = 1; + } else if (pw <= t2) { + wd->pulse_val = 2; + } else if (pw <= t3) { + wd->pulse_val = 3; } else { pr_err("pulse width out of range\n"); - err = -EINVAL; - goto exit_unlock; + return -EINVAL; } - watchdog.pulse_mode = pw; + wd->pulse_mode = pw; -exit_unlock: - mutex_unlock(&watchdog.lock); - return err; + return 0; } -static int watchdog_keepalive(void) +static int fintek_wdt_keepalive(struct watchdog_device *wdd) { - int err = 0; + struct fintek_wdt *wd = watchdog_get_drvdata(wdd); + int err; - mutex_lock(&watchdog.lock); - err = superio_enter(watchdog.sioaddr); + err = superio_enter(wd->sioaddr); if (err) - goto exit_unlock; - superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + return err; + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); - if (watchdog.minutes_mode) + if (wd->minutes_mode) /* select minutes for timer units */ - superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + superio_set_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, F71808FG_FLAG_WD_UNIT); else /* select seconds for timer units */ - superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, F71808FG_FLAG_WD_UNIT); /* Set timer value */ - superio_outb(watchdog.sioaddr, F71808FG_REG_WD_TIME, - watchdog.timer_val); + superio_outb(wd->sioaddr, F71808FG_REG_WD_TIME, + wd->timer_val); - superio_exit(watchdog.sioaddr); - -exit_unlock: - mutex_unlock(&watchdog.lock); - return err; -} - -static int f71862fg_pin_configure(unsigned short ioaddr) -{ - /* When ioaddr is non-zero the calling function has to take care of - mutex handling and superio preparation! */ + superio_exit(wd->sioaddr); - if (f71862fg_pin == 63) { - if (ioaddr) { - /* SPI must be disabled first to use this pin! */ - superio_clear_bit(ioaddr, SIO_REG_ROM_ADDR_SEL, 6); - superio_set_bit(ioaddr, SIO_REG_MFUNCT3, 4); - } - } else if (f71862fg_pin == 56) { - if (ioaddr) - superio_set_bit(ioaddr, SIO_REG_MFUNCT1, 1); - } else { - pr_err("Invalid argument f71862fg_pin=%d\n", f71862fg_pin); - return -EINVAL; - } return 0; } -static int watchdog_start(void) +static int fintek_wdt_start(struct watchdog_device *wdd) { + struct fintek_wdt *wd = watchdog_get_drvdata(wdd); + int err; + u8 tmp; + /* Make sure we don't die as soon as the watchdog is enabled below */ - int err = watchdog_keepalive(); + err = fintek_wdt_keepalive(wdd); if (err) return err; - mutex_lock(&watchdog.lock); - err = superio_enter(watchdog.sioaddr); + err = superio_enter(wd->sioaddr); if (err) - goto exit_unlock; - superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + return err; + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); /* Watchdog pin configuration */ - switch (watchdog.type) { + switch (wd->type) { case f71808fg: /* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */ - superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT2, 3); - superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT3, 3); + superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT2, 3); + superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT3, 3); break; case f71862fg: - err = f71862fg_pin_configure(watchdog.sioaddr); - if (err) - goto exit_superio; + if (f71862fg_pin == 63) { + /* SPI must be disabled first to use this pin! */ + superio_clear_bit(wd->sioaddr, SIO_REG_ROM_ADDR_SEL, 6); + superio_set_bit(wd->sioaddr, SIO_REG_MFUNCT3, 4); + } else if (f71862fg_pin == 56) { + superio_set_bit(wd->sioaddr, SIO_REG_MFUNCT1, 1); + } break; + case f71868: case f71869: /* GPIO14 --> WDTRST# */ - superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 4); + superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT1, 4); break; case f71882fg: /* Set pin 56 to WDTRST# */ - superio_set_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 1); + superio_set_bit(wd->sioaddr, SIO_REG_MFUNCT1, 1); break; case f71889fg: /* set pin 40 to WDTRST# */ - superio_outb(watchdog.sioaddr, SIO_REG_MFUNCT3, - superio_inb(watchdog.sioaddr, SIO_REG_MFUNCT3) & 0xcf); + superio_outb(wd->sioaddr, SIO_REG_MFUNCT3, + superio_inb(wd->sioaddr, SIO_REG_MFUNCT3) & 0xcf); + break; + + case f81803: + /* Enable TSI Level register bank */ + superio_clear_bit(wd->sioaddr, SIO_REG_CLOCK_SEL, 3); + /* Set pin 27 to WDTRST# */ + superio_outb(wd->sioaddr, SIO_REG_TSI_LEVEL_SEL, 0x5f & + superio_inb(wd->sioaddr, SIO_REG_TSI_LEVEL_SEL)); + break; + + case f81865: + /* Set pin 70 to WDTRST# */ + superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT3, 5); + break; + + case f81866: + case f81966: + /* + * GPIO1 Control Register when 27h BIT3:2 = 01 & BIT0 = 0. + * The PIN 70(GPIO15/WDTRST) is controlled by 2Ch: + * BIT5: 0 -> WDTRST# + * 1 -> GPIO15 + */ + tmp = superio_inb(wd->sioaddr, SIO_F81866_REG_PORT_SEL); + tmp &= ~(BIT(3) | BIT(0)); + tmp |= BIT(2); + superio_outb(wd->sioaddr, SIO_F81866_REG_PORT_SEL, tmp); + + superio_clear_bit(wd->sioaddr, SIO_F81866_REG_GPIO1, 5); break; default: @@ -369,372 +373,170 @@ static int watchdog_start(void) goto exit_superio; } - superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); - superio_set_bit(watchdog.sioaddr, SIO_REG_ENABLE, 0); - superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDO_CONF, - F71808FG_FLAG_WDOUT_EN); + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); + superio_set_bit(wd->sioaddr, SIO_REG_ENABLE, 0); - superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + if (wd->type == f81865 || wd->type == f81866 || wd->type == f81966) + superio_set_bit(wd->sioaddr, F81865_REG_WDO_CONF, + F81865_FLAG_WDOUT_EN); + else + superio_set_bit(wd->sioaddr, F71808FG_REG_WDO_CONF, + F71808FG_FLAG_WDOUT_EN); + + superio_set_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, F71808FG_FLAG_WD_EN); - if (watchdog.pulse_mode) { + if (wd->pulse_mode) { /* Select "pulse" output mode with given duration */ - u8 wdt_conf = superio_inb(watchdog.sioaddr, + u8 wdt_conf = superio_inb(wd->sioaddr, F71808FG_REG_WDT_CONF); /* Set WD_PSWIDTH bits (1:0) */ - wdt_conf = (wdt_conf & 0xfc) | (watchdog.pulse_val & 0x03); + wdt_conf = (wdt_conf & 0xfc) | (wd->pulse_val & 0x03); /* Set WD_PULSE to "pulse" mode */ wdt_conf |= BIT(F71808FG_FLAG_WD_PULSE); - superio_outb(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + superio_outb(wd->sioaddr, F71808FG_REG_WDT_CONF, wdt_conf); } else { /* Select "level" output mode */ - superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, F71808FG_FLAG_WD_PULSE); } exit_superio: - superio_exit(watchdog.sioaddr); -exit_unlock: - mutex_unlock(&watchdog.lock); - - 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_F71808FG_LD_WDT); - - superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, - F71808FG_FLAG_WD_EN); - - superio_exit(watchdog.sioaddr); - -exit_unlock: - mutex_unlock(&watchdog.lock); + superio_exit(wd->sioaddr); 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); - - return status; -} - -static bool watchdog_is_running(void) -{ - /* - * if we fail to determine the watchdog's status assume it to be - * running to be on the safe side - */ - bool is_running = true; - - mutex_lock(&watchdog.lock); - if (superio_enter(watchdog.sioaddr)) - goto exit_unlock; - superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); - - is_running = (superio_inb(watchdog.sioaddr, SIO_REG_ENABLE) & BIT(0)) - && (superio_inb(watchdog.sioaddr, F71808FG_REG_WDT_CONF) - & F71808FG_FLAG_WD_EN); - - superio_exit(watchdog.sioaddr); - -exit_unlock: - mutex_unlock(&watchdog.lock); - return is_running; -} - -/* /dev/watchdog api */ - -static int watchdog_open(struct inode *inode, struct file *file) +static int fintek_wdt_stop(struct watchdog_device *wdd) { + struct fintek_wdt *wd = watchdog_get_drvdata(wdd); 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); + err = superio_enter(wd->sioaddr); + if (err) return err; - } + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); - if (nowayout) - __module_get(THIS_MODULE); + superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_EN); - watchdog.expect_close = 0; - return nonseekable_open(inode, file); -} + superio_exit(wd->sioaddr); -static int watchdog_release(struct inode *inode, struct file *file) -{ - clear_bit(0, &watchdog.opened); - - if (!watchdog.expect_close) { - watchdog_keepalive(); - pr_crit("Unexpected close, not stopping watchdog!\n"); - } else if (!nowayout) { - watchdog_stop(); - } return 0; } -/* - * watchdog_write: - * @file: file handle to the watchdog - * @buf: buffer to write - * @count: count of bytes - * @ppos: pointer to the position to write. No seeks allowed - * - * A write to a watchdog device is defined as a keepalive signal. Any - * write of data will do, as we we don't define content meaning. - */ - -static ssize_t watchdog_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) +static bool fintek_wdt_is_running(struct fintek_wdt *wd, u8 wdt_conf) { - 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; + return (superio_inb(wd->sioaddr, SIO_REG_ENABLE) & BIT(0)) + && (wdt_conf & BIT(F71808FG_FLAG_WD_EN)); } -/* - * watchdog_ioctl: - * @inode: inode of the device - * @file: file handle to the device - * @cmd: watchdog command - * @arg: argument pointer - * - * The watchdog API defines a common set of functions for all watchdogs - * according to their available features. - */ -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_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(); - /* Fall */ - - case WDIOC_GETTIMEOUT: - return put_user(watchdog.timeout, uarg.i); - - default: - return -ENOTTY; - - } -} +static const struct watchdog_ops fintek_wdt_ops = { + .owner = THIS_MODULE, + .start = fintek_wdt_start, + .stop = fintek_wdt_stop, + .ping = fintek_wdt_keepalive, + .set_timeout = fintek_wdt_set_timeout, +}; -static int watchdog_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) +static int fintek_wdt_probe(struct platform_device *pdev) { - if (code == SYS_DOWN || code == SYS_HALT) - watchdog_stop(); - return NOTIFY_DONE; -} + struct device *dev = &pdev->dev; + struct fintek_wdt_pdata *pdata; + struct watchdog_device *wdd; + struct fintek_wdt *wd; + int wdt_conf, err = 0; + struct resource *res; + int sioaddr; -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, -}; + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) + return -ENXIO; -static struct miscdevice watchdog_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &watchdog_fops, -}; + sioaddr = res->start; -static struct notifier_block watchdog_notifier = { - .notifier_call = watchdog_notify_sys, -}; + wd = devm_kzalloc(dev, sizeof(*wd), GFP_KERNEL); + if (!wd) + return -ENOMEM; -static int __init watchdog_init(int sioaddr) -{ - int wdt_conf, err = 0; + pdata = dev->platform_data; - /* No need to lock watchdog.lock here because no entry points - * into the module have been registered yet. - */ - watchdog.sioaddr = sioaddr; - watchdog.ident.options = WDIOC_SETTIMEOUT - | WDIOF_MAGICCLOSE - | WDIOF_KEEPALIVEPING; + wd->type = pdata->type; + wd->sioaddr = sioaddr; + wd->ident.options = WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE + | WDIOF_KEEPALIVEPING + | WDIOF_CARDRESET; - snprintf(watchdog.ident.identity, - sizeof(watchdog.ident.identity), "%s watchdog", - f71808e_names[watchdog.type]); + snprintf(wd->ident.identity, + sizeof(wd->ident.identity), "%s watchdog", + fintek_wdt_names[wd->type]); err = superio_enter(sioaddr); if (err) return err; - superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); wdt_conf = superio_inb(sioaddr, F71808FG_REG_WDT_CONF); - watchdog.caused_reboot = wdt_conf & F71808FG_FLAG_WDTMOUT_STS; - - superio_exit(sioaddr); - err = watchdog_set_timeout(timeout); - if (err) - return err; - err = watchdog_set_pulse_width(pulse_width); - if (err) - return err; + /* + * We don't want WDTMOUT_STS to stick around till regular reboot. + * Write 1 to the bit to clear it to zero. + */ + superio_outb(sioaddr, F71808FG_REG_WDT_CONF, + wdt_conf | BIT(F71808FG_FLAG_WDTMOUT_STS)); - err = register_reboot_notifier(&watchdog_notifier); - if (err) - return err; + wdd = &wd->wdd; - err = misc_register(&watchdog_miscdev); - if (err) { - pr_err("cannot register miscdev on minor=%d\n", - watchdog_miscdev.minor); - goto exit_reboot; - } + if (fintek_wdt_is_running(wd, wdt_conf)) + set_bit(WDOG_HW_RUNNING, &wdd->status); - if (start_withtimeout) { - if (start_withtimeout <= 0 - || start_withtimeout > max_timeout) { - pr_err("starting timeout out of range\n"); - err = -EINVAL; - goto exit_miscdev; - } + superio_exit(sioaddr); - err = watchdog_start(); - if (err) { - pr_err("cannot start watchdog timer\n"); - goto exit_miscdev; - } + wdd->parent = dev; + wdd->info = &wd->ident; + wdd->ops = &fintek_wdt_ops; + wdd->min_timeout = 1; + wdd->max_timeout = WATCHDOG_MAX_TIMEOUT; - mutex_lock(&watchdog.lock); - err = superio_enter(sioaddr); - if (err) - goto exit_unlock; - superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + watchdog_set_drvdata(wdd, wd); + watchdog_set_nowayout(wdd, nowayout); + watchdog_stop_on_unregister(wdd); + watchdog_stop_on_reboot(wdd); + watchdog_init_timeout(wdd, start_withtimeout ?: timeout, NULL); - if (start_withtimeout > 0xff) { - /* select minutes for timer units */ - superio_set_bit(sioaddr, F71808FG_REG_WDT_CONF, - F71808FG_FLAG_WD_UNIT); - superio_outb(sioaddr, F71808FG_REG_WD_TIME, - DIV_ROUND_UP(start_withtimeout, 60)); - } else { - /* select seconds for timer units */ - superio_clear_bit(sioaddr, F71808FG_REG_WDT_CONF, - F71808FG_FLAG_WD_UNIT); - superio_outb(sioaddr, F71808FG_REG_WD_TIME, - start_withtimeout); - } + if (wdt_conf & BIT(F71808FG_FLAG_WDTMOUT_STS)) + wdd->bootstatus = WDIOF_CARDRESET; - superio_exit(sioaddr); - mutex_unlock(&watchdog.lock); + /* + * WATCHDOG_HANDLE_BOOT_ENABLED can result in keepalive being directly + * called without a set_timeout before, so it needs to be done here + * unconditionally. + */ + fintek_wdt_set_timeout(wdd, wdd->timeout); + fintek_wdt_set_pulse_width(wd, pulse_width); - if (nowayout) - __module_get(THIS_MODULE); + if (start_withtimeout) { + err = fintek_wdt_start(wdd); + if (err) { + dev_err(dev, "cannot start watchdog timer\n"); + return err; + } - pr_info("watchdog started with initial timeout of %u sec\n", - start_withtimeout); + set_bit(WDOG_HW_RUNNING, &wdd->status); + dev_info(dev, "watchdog started with initial timeout of %u sec\n", + start_withtimeout); } - return 0; - -exit_unlock: - mutex_unlock(&watchdog.lock); -exit_miscdev: - misc_deregister(&watchdog_miscdev); -exit_reboot: - unregister_reboot_notifier(&watchdog_notifier); - - return err; + return devm_watchdog_register_device(dev, wdd); } -static int __init f71808e_find(int sioaddr) +static int __init fintek_wdt_find(int sioaddr) { + enum chips type; u16 devid; int err = superio_enter(sioaddr); if (err) @@ -750,26 +552,40 @@ static int __init f71808e_find(int sioaddr) devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID); switch (devid) { case SIO_F71808_ID: - watchdog.type = f71808fg; + type = f71808fg; break; case SIO_F71862_ID: - watchdog.type = f71862fg; - err = f71862fg_pin_configure(0); /* validate module parameter */ + type = f71862fg; + break; + case SIO_F71868_ID: + type = f71868; break; case SIO_F71869_ID: case SIO_F71869A_ID: - watchdog.type = f71869; + type = f71869; break; case SIO_F71882_ID: - watchdog.type = f71882fg; + type = f71882fg; break; case SIO_F71889_ID: - watchdog.type = f71889fg; + type = f71889fg; break; case SIO_F71858_ID: /* Confirmed (by datasheet) not to have a watchdog. */ err = -ENODEV; goto exit; + case SIO_F81803_ID: + type = f81803; + break; + case SIO_F81865_ID: + type = f81865; + break; + case SIO_F81866_ID: + type = f81866; + break; + case SIO_F81966_ID: + type = f81966; + break; default: pr_info("Unrecognized Fintek device: %04x\n", (unsigned int)devid); @@ -778,43 +594,75 @@ static int __init f71808e_find(int sioaddr) } pr_info("Found %s watchdog chip, revision %d\n", - f71808e_names[watchdog.type], + fintek_wdt_names[type], (int)superio_inb(sioaddr, SIO_REG_DEVREV)); + exit: superio_exit(sioaddr); - return err; + return err ? err : type; } -static int __init f71808e_init(void) +static struct platform_driver fintek_wdt_driver = { + .probe = fintek_wdt_probe, + .driver = { + .name = DRVNAME, + }, +}; + +static struct platform_device *fintek_wdt_pdev; + +static int __init fintek_wdt_init(void) { static const unsigned short addrs[] = { 0x2e, 0x4e }; - int err = -ENODEV; + struct fintek_wdt_pdata pdata; + struct resource wdt_res = {}; + int ret; int i; + if (f71862fg_pin != 63 && f71862fg_pin != 56) { + pr_err("Invalid argument f71862fg_pin=%d\n", f71862fg_pin); + return -EINVAL; + } + for (i = 0; i < ARRAY_SIZE(addrs); i++) { - err = f71808e_find(addrs[i]); - if (err == 0) + ret = fintek_wdt_find(addrs[i]); + if (ret >= 0) break; } if (i == ARRAY_SIZE(addrs)) - return err; + return ret; + + pdata.type = ret; + + ret = platform_driver_register(&fintek_wdt_driver); + if (ret) + return ret; + + wdt_res.name = "superio port"; + wdt_res.flags = IORESOURCE_IO; + wdt_res.start = addrs[i]; + wdt_res.end = addrs[i] + 1; - return watchdog_init(addrs[i]); + fintek_wdt_pdev = platform_device_register_resndata(NULL, DRVNAME, -1, + &wdt_res, 1, + &pdata, sizeof(pdata)); + if (IS_ERR(fintek_wdt_pdev)) { + platform_driver_unregister(&fintek_wdt_driver); + return PTR_ERR(fintek_wdt_pdev); + } + + return 0; } -static void __exit f71808e_exit(void) +static void __exit fintek_wdt_exit(void) { - if (watchdog_is_running()) { - pr_warn("Watchdog timer still running, stopping it\n"); - watchdog_stop(); - } - misc_deregister(&watchdog_miscdev); - unregister_reboot_notifier(&watchdog_notifier); + platform_device_unregister(fintek_wdt_pdev); + platform_driver_unregister(&fintek_wdt_driver); } MODULE_DESCRIPTION("F71808E Watchdog Driver"); MODULE_AUTHOR("Giel van Schijndel <me@mortis.eu>"); MODULE_LICENSE("GPL"); -module_init(f71808e_init); -module_exit(f71808e_exit); +module_init(fintek_wdt_init); +module_exit(fintek_wdt_exit); |
