]> rtime.felk.cvut.cz Git - hercules2020/nv-tegra/linux-4.4.git/commitdiff
watchdog: tegra: add support for enable-on-init
authorAllen Yu <alleny@nvidia.com>
Wed, 21 Jun 2017 13:36:52 +0000 (19:06 +0530)
committerWinnie Hsu <whsu@nvidia.com>
Fri, 7 Jul 2017 23:30:03 +0000 (16:30 -0700)
This change is highly based on below work by Allen Yu

    watchdog: tegra: revmap support for enable-on-init

    To recover device from early hang before userspace watchdog daemon starts,
    WDT needs to be enabled on init and a kernel WDT petting routine is preferable
    since the time to userspace boot is non-deterministic and it is possible that
    userspace may not provide a watchdog daemon. This change does following:

    - Request a threaded irq (WDT_IRQ) if enable-on-init is provided in DT
    - Reload WDT in the threaded interrupt context to probe CPU lockup
    - Remove the IRQ handler once userspace watchdog daemon takes over WDT

    For legacy chips like t124 and t132, WDT_IRQ is not available for WDT petting
    since it's used for FIQ debugger. So they still expect userspace to boot and
    start petting the watchdog.

    Bug 200100035

Bug 200314562

Change-Id: Ie33cd9256aeaa47b6662264f562cc9a5115cf6b8
Signed-off-by: Shreshtha SAHU <ssahu@nvidia.com>
Reviewed-on: https://git-master/r/1515197
GVS: Gerrit_Virtual_Submit
Reviewed-by: Bibek Basu <bbasu@nvidia.com>
Tested-by: Bibek Basu <bbasu@nvidia.com>
drivers/watchdog/tegra_wdt.c

index 283064fcd0773dff92e0502e708dcddf27a356fd..ce9d2e2546c716e34021df532238e66d53219a4a 100644 (file)
@@ -50,9 +50,14 @@ struct tegra_wdt {
        struct watchdog_device  wdd;
        void __iomem            *wdt_regs;
        void __iomem            *tmr_regs;
+       int                     irq;
        u8                      timer_id;
+       bool                    enable_on_init;
 };
 
+static int tegra_wdt_ping(struct watchdog_device *wdd);
+static int tegra_wdt_start(struct watchdog_device *wdd);
+
 /*
  * The total expiry count of Tegra WDTs is limited to HW design and depends
  * on skip configuration if supported. To be safe, we set the default expiry
@@ -124,6 +129,62 @@ static int tegra_tmr_index(struct resource *tmr_res)
        return timer_id;
 }
 
+static irqreturn_t tegra_wdt_irq(int irq, void *data)
+{
+       struct tegra_wdt *wdt = data;
+
+       tegra_wdt_ping(&wdt->wdd);
+
+       return IRQ_HANDLED;
+}
+
+static void tegra_wdt_ref(struct watchdog_device *wdd)
+{
+       struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+
+       if (wdt->irq <= 0)
+               return;
+
+       /*
+        * Remove the interrupt handler if userspace is taking over WDT.
+        */
+       if (wdt->enable_on_init)
+               disable_irq(wdt->irq);
+}
+
+/* Init, enable watchdog on WDT0 and create a thread to ping */
+static int tegra_wdt_daemon(struct watchdog_device *wdd)
+{
+       struct platform_device *pdev = to_platform_device(wdd->parent);
+       struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+       int ret;
+
+       if (!wdt->enable_on_init)
+               return 0;
+
+       wdt->irq = platform_get_irq(pdev, 0);
+       if (wdt->irq <= 0) {
+               dev_err(&pdev->dev, "failed to get WDT IRQ\n");
+               return -ENXIO;
+       }
+
+       ret = devm_request_threaded_irq(&pdev->dev, wdt->irq, NULL,
+                       tegra_wdt_irq, IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+                       dev_name(&pdev->dev), wdt);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to register irq %d err %d\n",
+                               wdt->irq, ret);
+               return ret;
+       }
+
+       tegra_wdt_start(wdd);
+       set_bit(WDOG_ACTIVE, &wdd->status);
+       dev_info(&pdev->dev, "Tegra WDT enabled on probe."
+                       " Timeout = %u seconds.\n", wdd->timeout);
+
+       return 0;
+}
+
 static int tegra_wdt_start(struct watchdog_device *wdd)
 {
        struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
@@ -148,7 +209,7 @@ static int tegra_wdt_start(struct watchdog_device *wdd)
         */
        val = (wdt->timer_id) |
              (trigger_period << WDT_CFG_PERIOD_SHIFT) |
-             WDT_CFG_PMC2CAR_RST_EN;
+               WDT_CFG_INT_EN | WDT_CFG_PMC2CAR_RST_EN;
        writel(val, wdt->wdt_regs + WDT_CFG);
 
        writel(WDT_CMD_START_COUNTER, wdt->wdt_regs + WDT_CMD);
@@ -236,6 +297,7 @@ static struct watchdog_ops tegra_wdt_ops = {
        .ping = tegra_wdt_ping,
        .set_timeout = tegra_wdt_set_timeout,
        .get_timeleft = tegra_wdt_get_timeleft,
+       .ref = tegra_wdt_ref,
 };
 
 static int tegra_wdt_probe(struct platform_device *pdev)
@@ -271,6 +333,9 @@ static int tegra_wdt_probe(struct platform_device *pdev)
        if (!wdt)
                return -ENOMEM;
 
+       wdt->enable_on_init = of_property_read_bool(
+                                       np, "nvidia,enable-on-init");
+
        /*
         * Get Timer index from (in decending priority):
         *      from "nvidia,timer-index" DT property
@@ -330,6 +395,11 @@ static int tegra_wdt_probe(struct platform_device *pdev)
        watchdog_init_timeout(wdd, heartbeat, &pdev->dev);
        watchdog_set_nowayout(wdd, nowayout);
 
+       /* Enable watchdog on WDT0 and create daemon to ping  */
+       ret = tegra_wdt_daemon(wdd);
+       if (ret)
+               return ret;
+
        ret = watchdog_register_device(wdd);
        if (ret) {
                dev_err(&pdev->dev,