aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/omap_wdt.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog/omap_wdt.c')
-rw-r--r--drivers/watchdog/omap_wdt.c128
1 files changed, 114 insertions, 14 deletions
diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c
index 2b4acb8..574588b 100644
--- a/drivers/watchdog/omap_wdt.c
+++ b/drivers/watchdog/omap_wdt.c
@@ -43,6 +43,9 @@
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/nmi.h>
#include <mach/hardware.h>
#include <plat/prcm.h>
@@ -54,6 +57,10 @@ static unsigned timer_margin;
module_param(timer_margin, uint, 0);
MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)");
+static int kernelpet = 1;
+module_param(kernelpet, int, 0);
+MODULE_PARM_DESC(kernelpet, "pet watchdog in kernel via irq");
+
static unsigned int wdt_trgr_pattern = 0x1234;
static spinlock_t wdt_lock;
@@ -63,6 +70,8 @@ struct omap_wdt_dev {
int omap_wdt_users;
struct resource *mem;
struct miscdevice omap_wdt_miscdev;
+ int irq;
+ struct notifier_block nb;
};
static void omap_wdt_ping(struct omap_wdt_dev *wdev)
@@ -122,6 +131,7 @@ static void omap_wdt_adjust_timeout(unsigned new_timeout)
static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev)
{
u32 pre_margin = GET_WLDR_VAL(timer_margin);
+ u32 delay_period = GET_WLDR_VAL(timer_margin / 2);
void __iomem *base = wdev->base;
pm_runtime_get_sync(wdev->dev);
@@ -134,15 +144,31 @@ static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev)
while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04)
cpu_relax();
+ /* Set delay interrupt to half the watchdog interval. */
+ while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 1 << 5)
+ cpu_relax();
+ __raw_writel(delay_period, base + OMAP_WATCHDOG_WDLY);
+
pm_runtime_put_sync(wdev->dev);
}
-/*
- * Allow only one task to hold it open
- */
-static int omap_wdt_open(struct inode *inode, struct file *file)
+
+static irqreturn_t omap_wdt_interrupt(int irq, void *dev_id)
+{
+ struct omap_wdt_dev *wdev = dev_id;
+ void __iomem *base = wdev->base;
+ u32 i;
+
+ pm_runtime_get_sync(wdev->dev);
+ omap_wdt_ping(wdev);
+ i = __raw_readl(base + OMAP_WATCHDOG_WIRQSTAT);
+ __raw_writel(i, base + OMAP_WATCHDOG_WIRQSTAT);
+ pm_runtime_put_sync_suspend(wdev->dev);
+ return IRQ_HANDLED;
+}
+
+static int omap_wdt_setup(struct omap_wdt_dev *wdev)
{
- struct omap_wdt_dev *wdev = platform_get_drvdata(omap_wdt_dev);
void __iomem *base = wdev->base;
if (test_and_set_bit(1, (unsigned long *)&(wdev->omap_wdt_users)))
@@ -158,20 +184,41 @@ static int omap_wdt_open(struct inode *inode, struct file *file)
while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01)
cpu_relax();
- file->private_data = (void *) wdev;
-
omap_wdt_set_timeout(wdev);
omap_wdt_ping(wdev); /* trigger loading of new timeout value */
+
+ /* Enable delay interrupt */
+
+ if (kernelpet && wdev->irq)
+ __raw_writel(0x2, base + OMAP_WATCHDOG_WIRQENSET);
+
omap_wdt_enable(wdev);
pm_runtime_put_sync(wdev->dev);
+ return 0;
+}
+
+/*
+ * Allow only one task to hold it open
+ */
+static int omap_wdt_open(struct inode *inode, struct file *file)
+{
+ struct omap_wdt_dev *wdev = platform_get_drvdata(omap_wdt_dev);
+ int ret;
+ ret = omap_wdt_setup(wdev);
+
+ if (ret)
+ return ret;
+
+ file->private_data = (void *) wdev;
return nonseekable_open(inode, file);
}
static int omap_wdt_release(struct inode *inode, struct file *file)
{
struct omap_wdt_dev *wdev = file->private_data;
+ void __iomem *base = wdev->base;
/*
* Shut off the timer unless NOWAYOUT is defined.
@@ -181,6 +228,10 @@ static int omap_wdt_release(struct inode *inode, struct file *file)
omap_wdt_disable(wdev);
+ /* Disable delay interrupt */
+ if (kernelpet && wdev->irq)
+ __raw_writel(0x2, base + OMAP_WATCHDOG_WIRQENCLR);
+
pm_runtime_put_sync(wdev->dev);
#else
printk(KERN_CRIT "omap_wdt: Unexpected close, not stopping!\n");
@@ -270,9 +321,20 @@ static const struct file_operations omap_wdt_fops = {
.llseek = no_llseek,
};
+static int omap_wdt_nb_func(struct notifier_block *nb, unsigned long val,
+ void *v)
+{
+ struct omap_wdt_dev *wdev = container_of(nb, struct omap_wdt_dev, nb);
+ pm_runtime_get_sync(wdev->dev);
+ omap_wdt_ping(wdev);
+ pm_runtime_put_sync_suspend(wdev->dev);
+
+ return NOTIFY_OK;
+}
+
static int __devinit omap_wdt_probe(struct platform_device *pdev)
{
- struct resource *res, *mem;
+ struct resource *res, *mem, *res_irq;
struct omap_wdt_dev *wdev;
int ret;
@@ -294,6 +356,8 @@ static int __devinit omap_wdt_probe(struct platform_device *pdev)
goto err_busy;
}
+ res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+
wdev = kzalloc(sizeof(struct omap_wdt_dev), GFP_KERNEL);
if (!wdev) {
ret = -ENOMEM;
@@ -310,9 +374,20 @@ static int __devinit omap_wdt_probe(struct platform_device *pdev)
goto err_ioremap;
}
+ if (res_irq) {
+ ret = request_irq(res_irq->start, omap_wdt_interrupt, 0,
+ dev_name(&pdev->dev), wdev);
+
+ if (ret)
+ goto err_irq;
+
+ wdev->irq = res_irq->start;
+ }
+
platform_set_drvdata(pdev, wdev);
pm_runtime_enable(wdev->dev);
+ pm_runtime_irq_safe(wdev->dev);
pm_runtime_get_sync(wdev->dev);
omap_wdt_disable(wdev);
@@ -335,10 +410,22 @@ static int __devinit omap_wdt_probe(struct platform_device *pdev)
omap_wdt_dev = pdev;
+ if (kernelpet && wdev->irq) {
+ wdev->nb.notifier_call = omap_wdt_nb_func;
+ atomic_notifier_chain_register(&touch_watchdog_notifier_head,
+ &wdev->nb);
+ return omap_wdt_setup(wdev);
+ }
+
return 0;
err_misc:
platform_set_drvdata(pdev, NULL);
+
+ if (wdev->irq)
+ free_irq(wdev->irq, wdev);
+
+err_irq:
iounmap(wdev->base);
err_ioremap:
@@ -377,6 +464,13 @@ static int __devexit omap_wdt_remove(struct platform_device *pdev)
release_mem_region(res->start, resource_size(res));
platform_set_drvdata(pdev, NULL);
+ if (wdev->irq)
+ free_irq(wdev->irq, wdev);
+
+ if (kernelpet && wdev->irq)
+ atomic_notifier_chain_unregister(&touch_watchdog_notifier_head,
+ &wdev->nb);
+
iounmap(wdev->base);
kfree(wdev);
@@ -393,28 +487,30 @@ static int __devexit omap_wdt_remove(struct platform_device *pdev)
* may not play well enough with NOWAYOUT...
*/
-static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state)
+static int omap_wdt_suspend(struct device *dev)
{
+ struct platform_device *pdev = to_platform_device(dev);
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev);
if (wdev->omap_wdt_users) {
pm_runtime_get_sync(wdev->dev);
omap_wdt_disable(wdev);
- pm_runtime_put_sync(wdev->dev);
+ pm_runtime_put_sync_suspend(wdev->dev);
}
return 0;
}
-static int omap_wdt_resume(struct platform_device *pdev)
+static int omap_wdt_resume(struct device *dev)
{
+ struct platform_device *pdev = to_platform_device(dev);
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev);
if (wdev->omap_wdt_users) {
pm_runtime_get_sync(wdev->dev);
omap_wdt_enable(wdev);
omap_wdt_ping(wdev);
- pm_runtime_put_sync(wdev->dev);
+ pm_runtime_put_sync_suspend(wdev->dev);
}
return 0;
@@ -425,15 +521,19 @@ static int omap_wdt_resume(struct platform_device *pdev)
#define omap_wdt_resume NULL
#endif
+static const struct dev_pm_ops omap_wdt_pm_ops = {
+ .suspend_noirq = omap_wdt_suspend,
+ .resume_noirq = omap_wdt_resume,
+};
+
static struct platform_driver omap_wdt_driver = {
.probe = omap_wdt_probe,
.remove = __devexit_p(omap_wdt_remove),
.shutdown = omap_wdt_shutdown,
- .suspend = omap_wdt_suspend,
- .resume = omap_wdt_resume,
.driver = {
.owner = THIS_MODULE,
.name = "omap_wdt",
+ .pm = &omap_wdt_pm_ops,
},
};