* 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/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: "
__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;
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 */
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;
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,
if (nowayout)
__module_get(THIS_MODULE);
- allow_close = CLOSE_STATE_NOT;
+ expect_close = 0;
/* start the timer */
s3c2410wdt_start();
* 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;
}
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;
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 const struct watchdog_info s3c2410_wdt_ident = {
.options = OPTIONS,
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;
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");
}
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;
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 */
"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) {
return 0;
+ err_cpufreq:
+ s3c2410wdt_cpufreq_deregister();
+
err_clk:
clk_disable(wdt_clock);
clk_put(wdt_clock);
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;
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,