* 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/moduleparam.h>
-#include <linux/config.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
-#include <linux/notifier.h>
-#include <linux/reboot.h>
#include <linux/init.h>
-#include <linux/device.h>
+#include <linux/platform_device.h>
#include <linux/interrupt.h>
+#include <linux/clk.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/arch/map.h>
-#include <asm/hardware/clock.h>
-#undef S3C2410_VA_WATCHDOG
-#define S3C2410_VA_WATCHDOG (0)
+#undef S3C24XX_VA_WATCHDOG
+#define S3C24XX_VA_WATCHDOG (0)
#include <asm/arch/regs-watchdog.h>
#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0)
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15)
-#ifdef CONFIG_WATCHDOG_NOWAYOUT
-static int nowayout = 1;
-#else
-static int nowayout = 0;
-#endif
-
+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;
if (timeout < 1)
return -EINVAL;
- /* I think someone must have missed a divide-by-2 in the 2410,
- * as a divisor of 128 gives half the calculated delay...
- */
-
- freq /= 128/2;
+ freq /= 128;
count = timeout * freq;
DBG("%s: count=%d, timeout=%d, freq=%d\n",
if(down_trylock(&open_lock))
return -EBUSY;
- if (nowayout) {
+ if (nowayout)
__module_get(THIS_MODULE);
- } else {
- allow_close = CLOSE_STATE_ALLOW;
- }
+
+ allow_close = CLOSE_STATE_NOT;
/* start the timer */
s3c2410wdt_start();
* Shut off the timer.
* Lock it in if it's a module and we set nowayout
*/
+
if (allow_close == CLOSE_STATE_ALLOW) {
s3c2410wdt_stop();
} else {
switch (cmd) {
default:
- return -ENOIOCTLCMD;
+ return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &s3c2410_wdt_ident,
}
}
-/*
- * Notifier for system down
- */
-
-static int s3c2410wdt_notify_sys(struct notifier_block *this, unsigned long code,
- void *unused)
-{
- if(code==SYS_DOWN || code==SYS_HALT) {
- /* Turn the WDT off */
- s3c2410wdt_stop();
- }
- return NOTIFY_DONE;
-}
-
/* kernel interface */
-static struct file_operations s3c2410wdt_fops = {
+static const struct file_operations s3c2410wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = s3c2410wdt_write,
.fops = &s3c2410wdt_fops,
};
-static struct notifier_block s3c2410wdt_notifier = {
- .notifier_call = s3c2410wdt_notify_sys,
-};
-
/* interrupt handler code */
-static irqreturn_t s3c2410wdt_irq(int irqno, void *param,
- struct pt_regs *regs)
+static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
{
printk(KERN_INFO PFX "Watchdog timer expired!\n");
}
/* device interface */
-static int s3c2410wdt_probe(struct device *dev)
+static int s3c2410wdt_probe(struct platform_device *pdev)
{
- struct platform_device *pdev = to_platform_device(dev);
struct resource *res;
int started = 0;
int ret;
int size;
- DBG("%s: probe=%p, device=%p\n", __FUNCTION__, pdev, dev);
+ DBG("%s: probe=%p\n", __FUNCTION__, pdev);
/* get the memory region for the watchdog timer */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL) {
printk(KERN_INFO PFX "failed to get irq resource\n");
+ iounmap(wdt_base);
return -ENOENT;
}
- ret = request_irq(res->start, s3c2410wdt_irq, 0, pdev->name, dev);
+ ret = request_irq(res->start, s3c2410wdt_irq, 0, pdev->name, pdev);
if (ret != 0) {
printk(KERN_INFO PFX "failed to install irq (%d)\n", ret);
+ iounmap(wdt_base);
return ret;
}
- wdt_clock = clk_get(dev, "watchdog");
+ wdt_clock = clk_get(&pdev->dev, "watchdog");
if (wdt_clock == NULL) {
printk(KERN_INFO PFX "failed to find watchdog clock source\n");
+ iounmap(wdt_base);
return -ENOENT;
}
- clk_use(wdt_clock);
clk_enable(wdt_clock);
/* see if we can actually set the requested timer margin, and if
}
}
- ret = register_reboot_notifier(&s3c2410wdt_notifier);
- if (ret) {
- printk (KERN_ERR PFX "cannot register reboot notifier (%d)\n",
- ret);
- return ret;
- }
-
ret = misc_register(&s3c2410wdt_miscdev);
if (ret) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (%d)\n",
WATCHDOG_MINOR, ret);
- unregister_reboot_notifier(&s3c2410wdt_notifier);
+ iounmap(wdt_base);
return ret;
}
if (tmr_atboot && started == 0) {
printk(KERN_INFO PFX "Starting Watchdog Timer\n");
s3c2410wdt_start();
+ } else if (!tmr_atboot) {
+ /* if we're not enabling the watchdog, then ensure it is
+ * disabled if it has been left running from the bootloader
+ * or other source */
+
+ s3c2410wdt_stop();
}
return 0;
}
-static int s3c2410wdt_remove(struct device *dev)
+static int s3c2410wdt_remove(struct platform_device *dev)
{
if (wdt_mem != NULL) {
release_resource(wdt_mem);
if (wdt_clock != NULL) {
clk_disable(wdt_clock);
- clk_unuse(wdt_clock);
clk_put(wdt_clock);
wdt_clock = NULL;
}
+ iounmap(wdt_base);
misc_deregister(&s3c2410wdt_miscdev);
return 0;
}
-static struct device_driver s3c2410wdt_driver = {
- .name = "s3c2410-wdt",
- .bus = &platform_bus_type,
+static void s3c2410wdt_shutdown(struct platform_device *dev)
+{
+ s3c2410wdt_stop();
+}
+
+#ifdef CONFIG_PM
+
+static unsigned long wtcon_save;
+static unsigned long wtdat_save;
+
+static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state)
+{
+ /* Save watchdog state, and turn it off. */
+ wtcon_save = readl(wdt_base + S3C2410_WTCON);
+ wtdat_save = readl(wdt_base + S3C2410_WTDAT);
+
+ /* Note that WTCNT doesn't need to be saved. */
+ s3c2410wdt_stop();
+
+ return 0;
+}
+
+static int s3c2410wdt_resume(struct platform_device *dev)
+{
+ /* Restore watchdog state. */
+
+ writel(wtdat_save, wdt_base + S3C2410_WTDAT);
+ writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */
+ writel(wtcon_save, wdt_base + S3C2410_WTCON);
+
+ printk(KERN_INFO PFX "watchdog %sabled\n",
+ (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
+
+ return 0;
+}
+
+#else
+#define s3c2410wdt_suspend NULL
+#define s3c2410wdt_resume NULL
+#endif /* CONFIG_PM */
+
+
+static struct platform_driver s3c2410wdt_driver = {
.probe = s3c2410wdt_probe,
.remove = s3c2410wdt_remove,
+ .shutdown = s3c2410wdt_shutdown,
+ .suspend = s3c2410wdt_suspend,
+ .resume = s3c2410wdt_resume,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "s3c2410-wdt",
+ },
};
-
static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
static int __init watchdog_init(void)
{
printk(banner);
- return driver_register(&s3c2410wdt_driver);
+ return platform_driver_register(&s3c2410wdt_driver);
}
static void __exit watchdog_exit(void)
{
- driver_unregister(&s3c2410wdt_driver);
- unregister_reboot_notifier(&s3c2410wdt_notifier);
+ platform_driver_unregister(&s3c2410wdt_driver);
}
module_init(watchdog_init);
module_exit(watchdog_exit);
-MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, "
+ "Dimitry Andric <dimitry.andric@tomtom.com>");
MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);