@@ -10,6 +10,8 @@
#include <linux/platform_device.h>
#include <linux/watchdog.h>
#include <linux/of_device.h>
+#include <linux/interrupt.h>
+#include <linux/watchdog.h>
enum wdt_reg {
WDT_RST,
@@ -54,15 +56,37 @@ struct qcom_wdt *to_qcom_wdt(struct watchdog_device *wdd)
return container_of(wdd, struct qcom_wdt, wdd);
}
+static inline int qcom_wdt_enable(struct watchdog_device *wdd)
+{
+ /* enable the bark interrupt */
+ if (wdd->info->options & WDIOF_PRETIMEOUT)
+ return 3;
+
+ return 1;
+}
+
+static irqreturn_t qcom_wdt_irq(int irq, void *cookie)
+{
+ struct watchdog_device *wdd = (struct watchdog_device *) cookie;
+
+ watchdog_notify_pretimeout(wdd);
+
+ return IRQ_HANDLED;
+}
+
static int qcom_wdt_start(struct watchdog_device *wdd)
{
struct qcom_wdt *wdt = to_qcom_wdt(wdd);
+ unsigned int bark = wdd->pretimeout;
+
+ if (!(wdd->info->options & WDIOF_PRETIMEOUT))
+ bark = wdd->timeout;
writel(0, wdt_addr(wdt, WDT_EN));
writel(1, wdt_addr(wdt, WDT_RST));
- writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME));
+ writel(bark * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME));
writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME));
- writel(1, wdt_addr(wdt, WDT_EN));
+ writel(qcom_wdt_enable(wdd), wdt_addr(wdt, WDT_EN));
return 0;
}
@@ -89,6 +113,14 @@ static int qcom_wdt_set_timeout(struct watchdog_device *wdd,
return qcom_wdt_start(wdd);
}
+static int qcom_wdt_set_pretimeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->pretimeout = timeout;
+
+ return 0;
+}
+
static int qcom_wdt_restart(struct watchdog_device *wdd, unsigned long action,
void *data)
{
@@ -105,7 +137,7 @@ static int qcom_wdt_restart(struct watchdog_device *wdd, unsigned long action,
writel(1, wdt_addr(wdt, WDT_RST));
writel(timeout, wdt_addr(wdt, WDT_BARK_TIME));
writel(timeout, wdt_addr(wdt, WDT_BITE_TIME));
- writel(1, wdt_addr(wdt, WDT_EN));
+ writel(qcom_wdt_enable(wdd), wdt_addr(wdt, WDT_EN));
/*
* Actually make sure the above sequence hits hardware before sleeping.
@@ -121,11 +153,12 @@ static const struct watchdog_ops qcom_wdt_ops = {
.stop = qcom_wdt_stop,
.ping = qcom_wdt_ping,
.set_timeout = qcom_wdt_set_timeout,
+ .set_pretimeout = qcom_wdt_set_pretimeout,
.restart = qcom_wdt_restart,
.owner = THIS_MODULE,
};
-static const struct watchdog_info qcom_wdt_info = {
+static struct watchdog_info qcom_wdt_info = {
.options = WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE
| WDIOF_SETTIMEOUT
@@ -146,7 +179,7 @@ static int qcom_wdt_probe(struct platform_device *pdev)
struct device_node *np = dev->of_node;
const u32 *regs;
u32 percpu_offset;
- int ret;
+ int irq, ret;
regs = of_device_get_match_data(dev);
if (!regs) {
@@ -222,6 +255,23 @@ static int qcom_wdt_probe(struct platform_device *pdev)
wdt->wdd.timeout = min(wdt->wdd.max_timeout, 30U);
watchdog_init_timeout(&wdt->wdd, 0, dev);
+ irq = platform_get_irq(pdev, 0);
+ if (irq >= 0) {
+ /* enable the pre-timeout notification */
+ qcom_wdt_info.options |= WDIOF_PRETIMEOUT;
+
+ ret = devm_request_irq(&pdev->dev, irq, qcom_wdt_irq,
+ IRQF_TRIGGER_RISING, "wdog_bark",
+ &wdt->wdd);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request irq\n");
+ return ret;
+ }
+ }
+
+ if (qcom_wdt_info.options & WDIOF_PRETIMEOUT)
+ wdt->wdd.pretimeout = wdt->wdd.timeout - 1;
+
ret = devm_watchdog_register_device(dev, &wdt->wdd);
if (ret)
return ret;
Use the bark interrupt as the pre-timeout notifier whenever this interrupt is available. By default, the pretimeout notification shall occur one second earlier than the timeout. Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez-ortiz@linaro.org> --- v3: remove unnecesary variable added to private. v2: register the pre-timeout notifier instead. v1: use the bark irq to notify the user. drivers/watchdog/qcom-wdt.c | 60 +++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-)