These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / watchdog / imgpdc_wdt.c
index 0deaa4f..15ab072 100644 (file)
@@ -9,6 +9,35 @@
  *
  * Based on drivers/watchdog/sunxi_wdt.c Copyright (c) 2013 Carlo Caione
  *                                                     2012 Henrik Nordstrom
+ *
+ * Notes
+ * -----
+ * The timeout value is rounded to the next power of two clock cycles.
+ * This is configured using the PDC_WDT_CONFIG register, according to this
+ * formula:
+ *
+ *     timeout = 2^(delay + 1) clock cycles
+ *
+ * Where 'delay' is the value written in PDC_WDT_CONFIG register.
+ *
+ * Therefore, the hardware only allows to program watchdog timeouts, expressed
+ * as a power of two number of watchdog clock cycles. The current implementation
+ * guarantees that the actual watchdog timeout will be _at least_ the value
+ * programmed in the imgpdg_wdt driver.
+ *
+ * The following table shows how the user-configured timeout relates
+ * to the actual hardware timeout (watchdog clock @ 40000 Hz):
+ *
+ * input timeout | WD_DELAY | actual timeout
+ * -----------------------------------
+ *      10       |   18     |  13 seconds
+ *      20       |   19     |  26 seconds
+ *      30       |   20     |  52 seconds
+ *      60       |   21     |  104 seconds
+ *
+ * Albeit coarse, this granularity would suffice most watchdog uses.
+ * If the platform allows it, the user should be able to change the watchdog
+ * clock rate and achieve a finer timeout granularity.
  */
 
 #include <linux/clk.h>
@@ -16,6 +45,7 @@
 #include <linux/log2.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/reboot.h>
 #include <linux/slab.h>
 #include <linux/watchdog.h>
 
@@ -42,7 +72,7 @@
 #define PDC_WDT_MIN_TIMEOUT            1
 #define PDC_WDT_DEF_TIMEOUT            64
 
-static int heartbeat = PDC_WDT_DEF_TIMEOUT;
+static int heartbeat;
 module_param(heartbeat, int, 0);
 MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds "
        "(default=" __MODULE_STRING(PDC_WDT_DEF_TIMEOUT) ")");
@@ -57,6 +87,7 @@ struct pdc_wdt_dev {
        struct clk *wdt_clk;
        struct clk *sys_clk;
        void __iomem *base;
+       struct notifier_block restart_handler;
 };
 
 static int pdc_wdt_keepalive(struct watchdog_device *wdt_dev)
@@ -84,18 +115,24 @@ static int pdc_wdt_stop(struct watchdog_device *wdt_dev)
        return 0;
 }
 
+static void __pdc_wdt_set_timeout(struct pdc_wdt_dev *wdt)
+{
+       unsigned long clk_rate = clk_get_rate(wdt->wdt_clk);
+       unsigned int val;
+
+       val = readl(wdt->base + PDC_WDT_CONFIG) & ~PDC_WDT_CONFIG_DELAY_MASK;
+       val |= order_base_2(wdt->wdt_dev.timeout * clk_rate) - 1;
+       writel(val, wdt->base + PDC_WDT_CONFIG);
+}
+
 static int pdc_wdt_set_timeout(struct watchdog_device *wdt_dev,
                               unsigned int new_timeout)
 {
-       unsigned int val;
        struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
-       unsigned long clk_rate = clk_get_rate(wdt->wdt_clk);
 
        wdt->wdt_dev.timeout = new_timeout;
 
-       val = readl(wdt->base + PDC_WDT_CONFIG) & ~PDC_WDT_CONFIG_DELAY_MASK;
-       val |= order_base_2(new_timeout * clk_rate) - 1;
-       writel(val, wdt->base + PDC_WDT_CONFIG);
+       __pdc_wdt_set_timeout(wdt);
 
        return 0;
 }
@@ -106,6 +143,8 @@ static int pdc_wdt_start(struct watchdog_device *wdt_dev)
        unsigned int val;
        struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
 
+       __pdc_wdt_set_timeout(wdt);
+
        val = readl(wdt->base + PDC_WDT_CONFIG);
        val |= PDC_WDT_CONFIG_ENABLE;
        writel(val, wdt->base + PDC_WDT_CONFIG);
@@ -128,8 +167,21 @@ static const struct watchdog_ops pdc_wdt_ops = {
        .set_timeout    = pdc_wdt_set_timeout,
 };
 
+static int pdc_wdt_restart(struct notifier_block *this, unsigned long mode,
+                          void *cmd)
+{
+       struct pdc_wdt_dev *wdt = container_of(this, struct pdc_wdt_dev,
+                                              restart_handler);
+
+       /* Assert SOFT_RESET */
+       writel(0x1, wdt->base + PDC_WDT_SOFT_RESET);
+
+       return NOTIFY_OK;
+}
+
 static int pdc_wdt_probe(struct platform_device *pdev)
 {
+       u64 div;
        int ret, val;
        unsigned long clk_rate;
        struct resource *res;
@@ -189,16 +241,15 @@ static int pdc_wdt_probe(struct platform_device *pdev)
 
        pdc_wdt->wdt_dev.info = &pdc_wdt_info;
        pdc_wdt->wdt_dev.ops = &pdc_wdt_ops;
-       pdc_wdt->wdt_dev.max_timeout = 1 << PDC_WDT_CONFIG_DELAY_MASK;
+
+       div = 1ULL << (PDC_WDT_CONFIG_DELAY_MASK + 1);
+       do_div(div, clk_rate);
+       pdc_wdt->wdt_dev.max_timeout = div;
+       pdc_wdt->wdt_dev.timeout = PDC_WDT_DEF_TIMEOUT;
        pdc_wdt->wdt_dev.parent = &pdev->dev;
        watchdog_set_drvdata(&pdc_wdt->wdt_dev, pdc_wdt);
 
-       ret = watchdog_init_timeout(&pdc_wdt->wdt_dev, heartbeat, &pdev->dev);
-       if (ret < 0) {
-               pdc_wdt->wdt_dev.timeout = pdc_wdt->wdt_dev.max_timeout;
-               dev_warn(&pdev->dev,
-                        "Initial timeout out of range! setting max timeout\n");
-       }
+       watchdog_init_timeout(&pdc_wdt->wdt_dev, heartbeat, &pdev->dev);
 
        pdc_wdt_stop(&pdc_wdt->wdt_dev);
 
@@ -238,6 +289,13 @@ static int pdc_wdt_probe(struct platform_device *pdev)
        if (ret)
                goto disable_wdt_clk;
 
+       pdc_wdt->restart_handler.notifier_call = pdc_wdt_restart;
+       pdc_wdt->restart_handler.priority = 128;
+       ret = register_restart_handler(&pdc_wdt->restart_handler);
+       if (ret)
+               dev_warn(&pdev->dev, "failed to register restart handler: %d\n",
+                        ret);
+
        return 0;
 
 disable_wdt_clk:
@@ -258,6 +316,7 @@ static int pdc_wdt_remove(struct platform_device *pdev)
 {
        struct pdc_wdt_dev *pdc_wdt = platform_get_drvdata(pdev);
 
+       unregister_restart_handler(&pdc_wdt->restart_handler);
        pdc_wdt_stop(&pdc_wdt->wdt_dev);
        watchdog_unregister_device(&pdc_wdt->wdt_dev);
        clk_disable_unprepare(pdc_wdt->wdt_clk);