libata-sff: port_task is SFF specific
[safe/jmp/linux-2.6] / drivers / watchdog / s3c2410_wdt.c
index 98532c0..e4cebef 100644 (file)
@@ -6,7 +6,7 @@
  * S3C2410 Watchdog Timer Support
  *
  * Based on, softdog.c by Alan Cox,
- *     (c) Copyright 1996 Alan Cox <alan@redhat.com>
+ *     (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>
  *
  * 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
  * 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
- *
- * Changelog:
- *     05-Oct-2004     BJD     Added semaphore init to stop crashes on open
- *                             Fixed tmr_count / wdt_count confusion
- *                             Added configurable debug
- *
- *     11-Jan-2005     BJD     Fixed divide-by-2 in timeout code
- *
- *     25-Jan-2005     DA      Added suspend/resume support
- *                             Replaced reboot notifier with .shutdown method
- *
- *     10-Mar-2005     LCVR    Changed S3C2410_VA to S3C24XX_VA
 */
 
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/interrupt.h>
 #include <linux/clk.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/cpufreq.h>
+#include <linux/slab.h>
 
-#include <asm/uaccess.h>
-#include <asm/io.h>
-
-#include <asm/arch/map.h>
+#include <mach/map.h>
 
 #undef S3C_VA_WATCHDOG
 #define S3C_VA_WATCHDOG (0)
 
-#include <asm/plat-s3c/regs-watchdog.h>
+#include <plat/regs-watchdog.h>
 
 #define PFX "s3c2410-wdt: "
 
@@ -65,8 +54,8 @@
 static int nowayout    = WATCHDOG_NOWAYOUT;
 static int tmr_margin  = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;
 static int tmr_atboot  = CONFIG_S3C2410_WATCHDOG_ATBOOT;
-static int soft_noboot = 0;
-static int debug       = 0;
+static int soft_noboot;
+static int debug;
 
 module_param(tmr_margin,  int, 0);
 module_param(tmr_atboot,  int, 0);
@@ -74,63 +63,66 @@ module_param(nowayout,    int, 0);
 module_param(soft_noboot, int, 0);
 module_param(debug,      int, 0);
 
-MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")");
-
-MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT));
-
-MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
-
-MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)");
-
+MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default="
+               __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")");
+MODULE_PARM_DESC(tmr_atboot,
+               "Watchdog is started at boot time if set to 1, default="
+                       __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT));
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+                       __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, "
+                       "0 to reboot (default depends on ONLY_TESTING)");
 MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)");
 
-
-typedef enum close_state {
-       CLOSE_STATE_NOT,
-       CLOSE_STATE_ALLOW=0x4021
-} close_state_t;
-
-static DECLARE_MUTEX(open_lock);
-
+static unsigned long open_lock;
 static struct device    *wdt_dev;      /* platform device attached to */
 static struct resource *wdt_mem;
 static struct resource *wdt_irq;
 static struct clk      *wdt_clock;
 static void __iomem    *wdt_base;
 static unsigned int     wdt_count;
-static close_state_t    allow_close;
+static char             expect_close;
+static DEFINE_SPINLOCK(wdt_lock);
 
 /* watchdog control routines */
 
 #define DBG(msg...) do { \
        if (debug) \
                printk(KERN_INFO msg); \
-       } while(0)
+       } while (0)
 
 /* functions */
 
-static int s3c2410wdt_keepalive(void)
+static void s3c2410wdt_keepalive(void)
 {
+       spin_lock(&wdt_lock);
        writel(wdt_count, wdt_base + S3C2410_WTCNT);
-       return 0;
+       spin_unlock(&wdt_lock);
 }
 
-static int s3c2410wdt_stop(void)
+static void __s3c2410wdt_stop(void)
 {
        unsigned long wtcon;
 
        wtcon = readl(wdt_base + S3C2410_WTCON);
        wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
        writel(wtcon, wdt_base + S3C2410_WTCON);
+}
 
-       return 0;
+static void s3c2410wdt_stop(void)
+{
+       spin_lock(&wdt_lock);
+       __s3c2410wdt_stop();
+       spin_unlock(&wdt_lock);
 }
 
-static int s3c2410wdt_start(void)
+static void s3c2410wdt_start(void)
 {
        unsigned long wtcon;
 
-       s3c2410wdt_stop();
+       spin_lock(&wdt_lock);
+
+       __s3c2410wdt_stop();
 
        wtcon = readl(wdt_base + S3C2410_WTCON);
        wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
@@ -149,13 +141,17 @@ static int s3c2410wdt_start(void)
        writel(wdt_count, wdt_base + S3C2410_WTDAT);
        writel(wdt_count, wdt_base + S3C2410_WTCNT);
        writel(wtcon, wdt_base + S3C2410_WTCON);
+       spin_unlock(&wdt_lock);
+}
 
-       return 0;
+static inline int s3c2410wdt_is_running(void)
+{
+       return readl(wdt_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE;
 }
 
 static int s3c2410wdt_set_heartbeat(int timeout)
 {
-       unsigned int freq = clk_get_rate(wdt_clock);
+       unsigned long freq = clk_get_rate(wdt_clock);
        unsigned int count;
        unsigned int divisor = 1;
        unsigned long wtcon;
@@ -166,7 +162,7 @@ static int s3c2410wdt_set_heartbeat(int timeout)
        freq /= 128;
        count = timeout * freq;
 
-       DBG("%s: count=%d, timeout=%d, freq=%d\n",
+       DBG("%s: count=%d, timeout=%d, freq=%lu\n",
            __func__, count, timeout, freq);
 
        /* if the count is bigger than the watchdog register,
@@ -211,13 +207,13 @@ static int s3c2410wdt_set_heartbeat(int timeout)
 
 static int s3c2410wdt_open(struct inode *inode, struct file *file)
 {
-       if(down_trylock(&open_lock))
+       if (test_and_set_bit(0, &open_lock))
                return -EBUSY;
 
        if (nowayout)
                __module_get(THIS_MODULE);
 
-       allow_close = CLOSE_STATE_NOT;
+       expect_close = 0;
 
        /* start the timer */
        s3c2410wdt_start();
@@ -231,15 +227,14 @@ static int s3c2410wdt_release(struct inode *inode, struct file *file)
         *      Lock it in if it's a module and we set nowayout
         */
 
-       if (allow_close == CLOSE_STATE_ALLOW) {
+       if (expect_close == 42)
                s3c2410wdt_stop();
-       else {
+       else {
                dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
                s3c2410wdt_keepalive();
        }
-
-       allow_close = CLOSE_STATE_NOT;
-       up(&open_lock);
+       expect_close = 0;
+       clear_bit(0, &open_lock);
        return 0;
 }
 
@@ -249,12 +244,12 @@ static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
        /*
         *      Refresh the timer.
         */
-       if(len) {
+       if (len) {
                if (!nowayout) {
                        size_t i;
 
                        /* In case it was set long ago */
-                       allow_close = CLOSE_STATE_NOT;
+                       expect_close = 0;
 
                        for (i = 0; i != len; i++) {
                                char c;
@@ -262,59 +257,51 @@ static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
                                if (get_user(c, data + i))
                                        return -EFAULT;
                                if (c == 'V')
-                                       allow_close = CLOSE_STATE_ALLOW;
+                                       expect_close = 42;
                        }
                }
-
                s3c2410wdt_keepalive();
        }
        return len;
 }
 
-#define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE
+#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
 
-static struct watchdog_info s3c2410_wdt_ident = {
+static const struct watchdog_info s3c2410_wdt_ident = {
        .options          =     OPTIONS,
        .firmware_version =     0,
        .identity         =     "S3C2410 Watchdog",
 };
 
 
-static int s3c2410wdt_ioctl(struct inode *inode, struct file *file,
-       unsigned int cmd, unsigned long arg)
+static long s3c2410wdt_ioctl(struct file *file,        unsigned int cmd,
+                                                       unsigned long arg)
 {
        void __user *argp = (void __user *)arg;
        int __user *p = argp;
        int new_margin;
 
        switch (cmd) {
-               default:
-                       return -ENOTTY;
-
-               case WDIOC_GETSUPPORT:
-                       return copy_to_user(argp, &s3c2410_wdt_ident,
-                               sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
-
-               case WDIOC_GETSTATUS:
-               case WDIOC_GETBOOTSTATUS:
-                       return put_user(0, p);
-
-               case WDIOC_KEEPALIVE:
-                       s3c2410wdt_keepalive();
-                       return 0;
-
-               case WDIOC_SETTIMEOUT:
-                       if (get_user(new_margin, p))
-                               return -EFAULT;
-
-                       if (s3c2410wdt_set_heartbeat(new_margin))
-                               return -EINVAL;
-
-                       s3c2410wdt_keepalive();
-                       return put_user(tmr_margin, p);
-
-               case WDIOC_GETTIMEOUT:
-                       return put_user(tmr_margin, p);
+       case WDIOC_GETSUPPORT:
+               return copy_to_user(argp, &s3c2410_wdt_ident,
+                       sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
+       case WDIOC_GETSTATUS:
+       case WDIOC_GETBOOTSTATUS:
+               return put_user(0, p);
+       case WDIOC_KEEPALIVE:
+               s3c2410wdt_keepalive();
+               return 0;
+       case WDIOC_SETTIMEOUT:
+               if (get_user(new_margin, p))
+                       return -EFAULT;
+               if (s3c2410wdt_set_heartbeat(new_margin))
+                       return -EINVAL;
+               s3c2410wdt_keepalive();
+               return put_user(tmr_margin, p);
+       case WDIOC_GETTIMEOUT:
+               return put_user(tmr_margin, p);
+       default:
+               return -ENOTTY;
        }
 }
 
@@ -324,7 +311,7 @@ static const struct file_operations s3c2410wdt_fops = {
        .owner          = THIS_MODULE,
        .llseek         = no_llseek,
        .write          = s3c2410wdt_write,
-       .ioctl          = s3c2410wdt_ioctl,
+       .unlocked_ioctl = s3c2410wdt_ioctl,
        .open           = s3c2410wdt_open,
        .release        = s3c2410wdt_release,
 };
@@ -344,9 +331,76 @@ static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
        s3c2410wdt_keepalive();
        return IRQ_HANDLED;
 }
+
+
+#ifdef CONFIG_CPU_FREQ
+
+static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb,
+                                         unsigned long val, void *data)
+{
+       int ret;
+
+       if (!s3c2410wdt_is_running())
+               goto done;
+
+       if (val == CPUFREQ_PRECHANGE) {
+               /* To ensure that over the change we don't cause the
+                * watchdog to trigger, we perform an keep-alive if
+                * the watchdog is running.
+                */
+
+               s3c2410wdt_keepalive();
+       } else if (val == CPUFREQ_POSTCHANGE) {
+               s3c2410wdt_stop();
+
+               ret = s3c2410wdt_set_heartbeat(tmr_margin);
+
+               if (ret >= 0)
+                       s3c2410wdt_start();
+               else
+                       goto err;
+       }
+
+done:
+       return 0;
+
+ err:
+       dev_err(wdt_dev, "cannot set new value for timeout %d\n", tmr_margin);
+       return ret;
+}
+
+static struct notifier_block s3c2410wdt_cpufreq_transition_nb = {
+       .notifier_call  = s3c2410wdt_cpufreq_transition,
+};
+
+static inline int s3c2410wdt_cpufreq_register(void)
+{
+       return cpufreq_register_notifier(&s3c2410wdt_cpufreq_transition_nb,
+                                        CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+static inline void s3c2410wdt_cpufreq_deregister(void)
+{
+       cpufreq_unregister_notifier(&s3c2410wdt_cpufreq_transition_nb,
+                                   CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+#else
+static inline int s3c2410wdt_cpufreq_register(void)
+{
+       return 0;
+}
+
+static inline void s3c2410wdt_cpufreq_deregister(void)
+{
+}
+#endif
+
+
+
 /* device interface */
 
-static int s3c2410wdt_probe(struct platform_device *pdev)
+static int __devinit s3c2410wdt_probe(struct platform_device *pdev)
 {
        struct resource *res;
        struct device *dev;
@@ -368,7 +422,7 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
                return -ENOENT;
        }
 
-       size = (res->end-res->start)+1;
+       size = resource_size(res);
        wdt_mem = request_mem_region(res->start, size, pdev->name);
        if (wdt_mem == NULL) {
                dev_err(dev, "failed to get memory region\n");
@@ -377,7 +431,7 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
        }
 
        wdt_base = ioremap(res->start, size);
-       if (wdt_base == 0) {
+       if (wdt_base == NULL) {
                dev_err(dev, "failed to ioremap() region\n");
                ret = -EINVAL;
                goto err_req;
@@ -407,25 +461,32 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
 
        clk_enable(wdt_clock);
 
+       if (s3c2410wdt_cpufreq_register() < 0) {
+               printk(KERN_ERR PFX "failed to register cpufreq\n");
+               goto err_clk;
+       }
+
        /* see if we can actually set the requested timer margin, and if
         * not, try the default value */
 
        if (s3c2410wdt_set_heartbeat(tmr_margin)) {
-               started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
+               started = s3c2410wdt_set_heartbeat(
+                                       CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
 
-               if (started == 0) {
-                       dev_info(dev,"tmr_margin value out of range, default %d used\n",
+               if (started == 0)
+                       dev_info(dev,
+                          "tmr_margin value out of range, default %d used\n",
                               CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
-               } else {
-                       dev_info(dev, "default timer value is out of range, cannot start\n");
-               }
+               else
+                       dev_info(dev, "default timer value is out of range, "
+                                                       "cannot start\n");
        }
 
        ret = misc_register(&s3c2410wdt_miscdev);
        if (ret) {
                dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
                        WATCHDOG_MINOR, ret);
-               goto err_clk;
+               goto err_cpufreq;
        }
 
        if (tmr_atboot && started == 0) {
@@ -447,9 +508,12 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
                 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",
                 (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",
                 (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
-       
+
        return 0;
 
+ err_cpufreq:
+       s3c2410wdt_cpufreq_deregister();
+
  err_clk:
        clk_disable(wdt_clock);
        clk_put(wdt_clock);
@@ -467,8 +531,10 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
        return ret;
 }
 
-static int s3c2410wdt_remove(struct platform_device *dev)
+static int __devexit s3c2410wdt_remove(struct platform_device *dev)
 {
+       s3c2410wdt_cpufreq_deregister();
+
        release_resource(wdt_mem);
        kfree(wdt_mem);
        wdt_mem = NULL;
@@ -487,7 +553,7 @@ static int s3c2410wdt_remove(struct platform_device *dev)
 
 static void s3c2410wdt_shutdown(struct platform_device *dev)
 {
-       s3c2410wdt_stop();      
+       s3c2410wdt_stop();
 }
 
 #ifdef CONFIG_PM
@@ -529,7 +595,7 @@ static int s3c2410wdt_resume(struct platform_device *dev)
 
 static struct platform_driver s3c2410wdt_driver = {
        .probe          = s3c2410wdt_probe,
-       .remove         = s3c2410wdt_remove,
+       .remove         = __devexit_p(s3c2410wdt_remove),
        .shutdown       = s3c2410wdt_shutdown,
        .suspend        = s3c2410wdt_suspend,
        .resume         = s3c2410wdt_resume,
@@ -540,7 +606,8 @@ static struct platform_driver s3c2410wdt_driver = {
 };
 
 
-static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
+static char banner[] __initdata =
+       KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
 
 static int __init watchdog_init(void)
 {