Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/holtmann/bluet...
[safe/jmp/linux-2.6] / drivers / watchdog / s3c2410_wdt.c
index 825dbd5..8760a26 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
 #include <linux/clk.h>
 #include <linux/uaccess.h>
 #include <linux/io.h>
+#include <linux/cpufreq.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: "
 
@@ -68,15 +69,10 @@ MODULE_PARM_DESC(tmr_atboot,
                        __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(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 unsigned long open_lock;
 static struct device    *wdt_dev;      /* platform device attached to */
 static struct resource *wdt_mem;
@@ -84,7 +80,7 @@ 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 */
@@ -147,9 +143,14 @@ static void s3c2410wdt_start(void)
        spin_unlock(&wdt_lock);
 }
 
+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;
@@ -160,7 +161,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,7 +212,7 @@ static int s3c2410wdt_open(struct inode *inode, struct file *file)
        if (nowayout)
                __module_get(THIS_MODULE);
 
-       allow_close = CLOSE_STATE_NOT;
+       expect_close = 0;
 
        /* start the timer */
        s3c2410wdt_start();
@@ -225,13 +226,13 @@ 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 {
                dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
                s3c2410wdt_keepalive();
        }
-       allow_close = CLOSE_STATE_NOT;
+       expect_close = 0;
        clear_bit(0, &open_lock);
        return 0;
 }
@@ -247,7 +248,7 @@ static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
                        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;
@@ -255,7 +256,7 @@ 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();
@@ -263,7 +264,7 @@ static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
        return len;
 }
 
-#define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE
+#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
 
 static const struct watchdog_info s3c2410_wdt_ident = {
        .options          =     OPTIONS,
@@ -329,9 +330,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;
@@ -353,7 +421,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");
@@ -392,6 +460,11 @@ 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 */
 
@@ -404,14 +477,15 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
                           "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");
+                       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) {
@@ -436,6 +510,9 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
 
        return 0;
 
+ err_cpufreq:
+       s3c2410wdt_cpufreq_deregister();
+
  err_clk:
        clk_disable(wdt_clock);
        clk_put(wdt_clock);
@@ -453,8 +530,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;
@@ -515,7 +594,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,