watchdog: f71808e_wdt: migrate to new kernel watchdog API
[linux-2.6-microblaze.git] / drivers / watchdog / f71808e_wdt.c
index 82da9ba..67e3446 100644 (file)
@@ -9,16 +9,10 @@
 #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/watchdog.h>
 
 #define DRVNAME "f71808e_wdt"
@@ -136,24 +130,18 @@ static inline void superio_select(int base, int ld);
 static inline void superio_exit(int base);
 
 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 fintek_wdt watchdog = {
-       .lock = __MUTEX_INITIALIZER(watchdog.lock),
-};
+static struct fintek_wdt watchdog;
 
 /* Super I/O functions */
 static inline int superio_inb(int base, int reg)
@@ -217,16 +205,8 @@ static inline void superio_exit(int base)
        release_region(base, 2);
 }
 
-static int fintek_wdt_set_timeout(int timeout)
+static int fintek_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
 {
-       if (timeout <= 0
-        || timeout >  WATCHDOG_MAX_TIMEOUT) {
-               pr_err("watchdog timeout out of range\n");
-               return -EINVAL;
-       }
-
-       mutex_lock(&watchdog.lock);
-
        if (timeout > 0xff) {
                watchdog.timer_val = DIV_ROUND_UP(timeout, 60);
                watchdog.minutes_mode = true;
@@ -236,16 +216,13 @@ static int fintek_wdt_set_timeout(int timeout)
                watchdog.minutes_mode = false;
        }
 
-       watchdog.timeout = timeout;
-
-       mutex_unlock(&watchdog.lock);
+       wdd->timeout = timeout;
 
        return 0;
 }
 
 static int fintek_wdt_set_pulse_width(unsigned int pw)
 {
-       int err = 0;
        unsigned int t1 = 25, t2 = 125, t3 = 5000;
 
        if (watchdog.type == f71868) {
@@ -254,8 +231,6 @@ static int fintek_wdt_set_pulse_width(unsigned int pw)
                t3 = 6000;
        }
 
-       mutex_lock(&watchdog.lock);
-
        if        (pw <=  1) {
                watchdog.pulse_val = 0;
        } else if (pw <= t1) {
@@ -266,25 +241,21 @@ static int fintek_wdt_set_pulse_width(unsigned int pw)
                watchdog.pulse_val = 3;
        } else {
                pr_err("pulse width out of range\n");
-               err = -EINVAL;
-               goto exit_unlock;
+               return -EINVAL;
        }
 
        watchdog.pulse_mode = pw;
 
-exit_unlock:
-       mutex_unlock(&watchdog.lock);
-       return err;
+       return 0;
 }
 
-static int fintek_wdt_keepalive(void)
+static int fintek_wdt_keepalive(struct watchdog_device *wdd)
 {
-       int err = 0;
+       int err;
 
-       mutex_lock(&watchdog.lock);
        err = superio_enter(watchdog.sioaddr);
        if (err)
-               goto exit_unlock;
+               return err;
        superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
 
        if (watchdog.minutes_mode)
@@ -302,25 +273,22 @@ static int fintek_wdt_keepalive(void)
 
        superio_exit(watchdog.sioaddr);
 
-exit_unlock:
-       mutex_unlock(&watchdog.lock);
-       return err;
+       return 0;
 }
 
-static int fintek_wdt_start(void)
+static int fintek_wdt_start(struct watchdog_device *wdd)
 {
        int err;
        u8 tmp;
 
        /* Make sure we don't die as soon as the watchdog is enabled below */
-       err = fintek_wdt_keepalive();
+       err = fintek_wdt_keepalive(wdd);
        if (err)
                return err;
 
-       mutex_lock(&watchdog.lock);
        err = superio_enter(watchdog.sioaddr);
        if (err)
-               goto exit_unlock;
+               return err;
        superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
 
        /* Watchdog pin configuration */
@@ -428,20 +396,17 @@ static int fintek_wdt_start(void)
 
 exit_superio:
        superio_exit(watchdog.sioaddr);
-exit_unlock:
-       mutex_unlock(&watchdog.lock);
 
        return err;
 }
 
-static int fintek_wdt_stop(void)
+static int fintek_wdt_stop(struct watchdog_device *wdd)
 {
-       int err = 0;
+       int err;
 
-       mutex_lock(&watchdog.lock);
        err = superio_enter(watchdog.sioaddr);
        if (err)
-               goto exit_unlock;
+               return err;
        superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
 
        superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF,
@@ -449,232 +414,31 @@ static int fintek_wdt_stop(void)
 
        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);
-
-       return status;
-}
-
-static bool fintek_wdt_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)
-                       & BIT(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)
-{
-       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 = fintek_wdt_start();
-       if (err) {
-               clear_bit(0, &watchdog.opened);
-               return err;
-       }
-
-       if (nowayout)
-               __module_get(THIS_MODULE);
-
-       watchdog.expect_close = 0;
-       return stream_open(inode, file);
-}
-
-static int watchdog_release(struct inode *inode, struct file *file)
-{
-       clear_bit(0, &watchdog.opened);
-
-       if (!watchdog.expect_close) {
-               fintek_wdt_keepalive();
-               pr_crit("Unexpected close, not stopping watchdog!\n");
-       } else if (!nowayout) {
-               fintek_wdt_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)
-{
-       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;
-                               if (c == 'V')
-                                       expect_close = true;
-                       }
-
-                       /* 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 */
-               fintek_wdt_keepalive();
-       }
-       return count;
-}
-
-/*
- *      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)
+static bool fintek_wdt_is_running(u8 wdt_conf)
 {
-       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)
-                       fintek_wdt_stop();
-
-               if (new_options & WDIOS_ENABLECARD)
-                       return fintek_wdt_start();
-               fallthrough;
-
-       case WDIOC_KEEPALIVE:
-               fintek_wdt_keepalive();
-               return 0;
-
-       case WDIOC_SETTIMEOUT:
-               if (get_user(new_timeout, uarg.i))
-                       return -EFAULT;
-
-               if (fintek_wdt_set_timeout(new_timeout))
-                       return -EINVAL;
-
-               fintek_wdt_keepalive();
-               fallthrough;
-
-       case WDIOC_GETTIMEOUT:
-               return put_user(watchdog.timeout, uarg.i);
-
-       default:
-               return -ENOTTY;
-
-       }
+       return (superio_inb(watchdog.sioaddr, SIO_REG_ENABLE) & BIT(0))
+               && (wdt_conf & BIT(F71808FG_FLAG_WD_EN));
 }
 
-static int watchdog_notify_sys(struct notifier_block *this, unsigned long code,
-       void *unused)
-{
-       if (code == SYS_DOWN || code == SYS_HALT)
-               fintek_wdt_stop();
-       return NOTIFY_DONE;
-}
-
-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,
-       .compat_ioctl   = compat_ptr_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,
+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 __init watchdog_init(int sioaddr)
 {
+       struct watchdog_device *wdd;
        int wdt_conf, err = 0;
 
-       /* No need to lock watchdog.lock here because no entry points
-        * into the module have been registered yet.
-        */
        watchdog.sioaddr = sioaddr;
-       watchdog.ident.options = WDIOF_MAGICCLOSE
+       watchdog.ident.options = WDIOF_SETTIMEOUT
+                               | WDIOF_MAGICCLOSE
                                | WDIOF_KEEPALIVEPING
                                | WDIOF_CARDRESET;
 
@@ -688,7 +452,6 @@ static int __init watchdog_init(int sioaddr)
        superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
 
        wdt_conf = superio_inb(sioaddr, F71808FG_REG_WDT_CONF);
-       watchdog.caused_reboot = wdt_conf & BIT(F71808FG_FLAG_WDTMOUT_STS);
 
        /*
         * We don't want WDTMOUT_STS to stick around till regular reboot.
@@ -697,80 +460,47 @@ static int __init watchdog_init(int sioaddr)
        superio_outb(sioaddr, F71808FG_REG_WDT_CONF,
                     wdt_conf | BIT(F71808FG_FLAG_WDTMOUT_STS));
 
+       wdd = &watchdog.wdd;
+
+       if (fintek_wdt_is_running(wdt_conf))
+               set_bit(WDOG_HW_RUNNING, &wdd->status);
+
        superio_exit(sioaddr);
 
-       err = fintek_wdt_set_timeout(timeout);
-       if (err)
-               return err;
-       err = fintek_wdt_set_pulse_width(pulse_width);
-       if (err)
-               return err;
+       wdd->info               = &watchdog.ident;
+       wdd->ops                = &fintek_wdt_ops;
+       wdd->min_timeout        = 1;
+       wdd->max_timeout        = WATCHDOG_MAX_TIMEOUT;
 
-       err = register_reboot_notifier(&watchdog_notifier);
-       if (err)
-               return err;
+       watchdog_set_nowayout(wdd, nowayout);
+       watchdog_stop_on_unregister(wdd);
+       watchdog_stop_on_reboot(wdd);
+       watchdog_init_timeout(wdd, start_withtimeout ?: timeout, NULL);
 
-       err = misc_register(&watchdog_miscdev);
-       if (err) {
-               pr_err("cannot register miscdev on minor=%d\n",
-                      watchdog_miscdev.minor);
-               goto exit_reboot;
-       }
+       if (wdt_conf & BIT(F71808FG_FLAG_WDTMOUT_STS))
+               wdd->bootstatus = WDIOF_CARDRESET;
 
-       if (start_withtimeout) {
-               if (start_withtimeout <= 0
-                || start_withtimeout >  WATCHDOG_MAX_TIMEOUT) {
-                       pr_err("starting timeout out of range\n");
-                       err = -EINVAL;
-                       goto exit_miscdev;
-               }
+       /*
+        * 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(pulse_width);
 
-               err = fintek_wdt_start();
+       if (start_withtimeout) {
+               err = fintek_wdt_start(wdd);
                if (err) {
                        pr_err("cannot start watchdog timer\n");
-                       goto exit_miscdev;
+                       return err;
                }
 
-               mutex_lock(&watchdog.lock);
-               err = superio_enter(sioaddr);
-               if (err)
-                       goto exit_unlock;
-               superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT);
-
-               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);
-               }
-
-               superio_exit(sioaddr);
-               mutex_unlock(&watchdog.lock);
-
-               if (nowayout)
-                       __module_get(THIS_MODULE);
-
+               set_bit(WDOG_HW_RUNNING, &wdd->status);
                pr_info("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 watchdog_register_device(wdd);
 }
 
 static int __init fintek_wdt_find(int sioaddr)
@@ -860,12 +590,7 @@ static int __init fintek_wdt_init(void)
 
 static void __exit fintek_wdt_exit(void)
 {
-       if (fintek_wdt_is_running()) {
-               pr_warn("Watchdog timer still running, stopping it\n");
-               fintek_wdt_stop();
-       }
-       misc_deregister(&watchdog_miscdev);
-       unregister_reboot_notifier(&watchdog_notifier);
+       watchdog_unregister_device(&watchdog.wdd);
 }
 
 MODULE_DESCRIPTION("F71808E Watchdog Driver");