ARM: OMAP2+: WDTIMER integration: fix !PM boot crash, disarm timer after hwmod reset
authorKevin Hilman <khilman@ti.com>
Tue, 8 May 2012 17:34:30 +0000 (11:34 -0600)
committerPaul Walmsley <paul@pwsan.com>
Tue, 8 May 2012 23:25:37 +0000 (17:25 -0600)
Without runtime PM enabled, hwmod needs to leave all IP blocks in an
enabled state by default so any driver access to the HW will succeed.
This is accomplished by seting the postsetup_state to enabled for all
hwmods during init when runtime PM is disabled.

Currently, we have a special case for WDT in that its postsetup_state
is always set to disabled.  This is done so that the WDT is disabled
and the timer is disarmed at boot in case there is no WDT driver.
This also means that when runtime PM is disabled, if a WDT driver *is*
built in the kernel, the kernel will crash on the first access to the
WDT hardware.

We can't simply leave the WDT module enabled, because the timer is
armed by default after reset. That means that if there is no WDT
driver initialzed or loaded before the timer expires, the kernel will
reboot.

To fix this, a custom reset method is added to the watchdog class of
omap_hwmod.  This method will *always* disarm the timer after hwmod
reset.  The WDT timer then will only be rearmed when/if the driver is
loaded for the WDT.  With the timer disarmed by default, we no longer
need a special-case for the postsetup_state of WDT during init, so it
is removed.

Any platforms wishing to ensure the watchdog remains armed across the
entire boot boot can simply disable the reset-on-init feature of the
watchdog hwmod using omap_hwmod_no_setup_reset().

Tested on 3530/Overo, 4430/Panda.

NOTE: on 4430, the hwmod OCP reset does not seem to rearm the timer as
documented in the TRM (and what happens on OMAP3.)  I noticed this
because testing the HWMOD_INIT_NO_RESET feature with no driver loaded,
I expected a reboot part way through the boot, but did not see a
reboot.  Adding some debug to read the counter, I verified that right
after OCP softreset, the counter is not firing.  After writing the
magic start sequence, the timer starts counting.  This means that the
timer disarm sequence added here does not seem to be needed for 4430,
but is technically the correct way to ensure the timer is disarmed, so
it is left in for OMAP4.

Special thanks to Paul Walmsley for helping brainstorm ideas to fix
this problem.

Cc: Paul Walmsley <paul@pwsan.com>
Signed-off-by: Kevin Hilman <khilman@ti.com>
Cc: Santosh Shilimkar <santosh.shilimkar@ti.com>
Acked-by: Santosh Shilimkar <santosh.shilimkar@ti.com>
[paul@pwsan.com: updated the omap2_wd_timer_reset() function in the
 wake of commit 3c55c1baffa5f719eb2ae9729088bc867f972f53 ("ARM:
 OMAP2+: hwmod: Revert "ARM: OMAP2+: hwmod: Make omap_hwmod_softreset
 wait for reset status""); added kerneldoc; rolled in warning fix from Kevin]
Signed-off-by: Paul Walmsley <paul@pwsan.com>
arch/arm/mach-omap2/io.c
arch/arm/mach-omap2/omap_hwmod_2xxx_ipblock_data.c
arch/arm/mach-omap2/omap_hwmod_3xxx_data.c
arch/arm/mach-omap2/omap_hwmod_44xx_data.c
arch/arm/mach-omap2/wd_timer.c
arch/arm/mach-omap2/wd_timer.h

index 065bd768987cfbc9dabd513f4dc41cef54d7dc9d..fafcc35b970c4611d32ed7ff70b80e3958748d36 100644 (file)
@@ -363,24 +363,6 @@ static void __init omap_hwmod_init_postsetup(void)
 #endif
        omap_hwmod_for_each(_set_hwmod_postsetup_state, &postsetup_state);
 
-       /*
-        * Set the default postsetup state for unusual modules (like
-        * MPU WDT).
-        *
-        * The postsetup_state is not actually used until
-        * omap_hwmod_late_init(), so boards that desire full watchdog
-        * coverage of kernel initialization can reprogram the
-        * postsetup_state between the calls to
-        * omap2_init_common_infra() and omap_sdrc_init().
-        *
-        * XXX ideally we could detect whether the MPU WDT was currently
-        * enabled here and make this conditional
-        */
-       postsetup_state = _HWMOD_STATE_DISABLED;
-       omap_hwmod_for_each_by_class("wd_timer",
-                                    _set_hwmod_postsetup_state,
-                                    &postsetup_state);
-
        omap_pm_if_early_init();
 }
 
index 5941f14130a1717625e16f5197e7a267355309c5..83eafd96ecaa23a2b8b210e56092734d302d9649 100644 (file)
@@ -89,7 +89,8 @@ static struct omap_hwmod_class_sysconfig omap2xxx_wd_timer_sysc = {
 struct omap_hwmod_class omap2xxx_wd_timer_hwmod_class = {
        .name           = "wd_timer",
        .sysc           = &omap2xxx_wd_timer_sysc,
-       .pre_shutdown   = &omap2_wd_timer_disable
+       .pre_shutdown   = &omap2_wd_timer_disable,
+       .reset          = &omap2_wd_timer_reset,
 };
 
 /*
index 2432574123c69622a10e94990e3eef110d1478e9..fd48797fa95ae2778f7e495868966f73bd4ee3e1 100644 (file)
@@ -418,7 +418,8 @@ static struct omap_hwmod_class_sysconfig i2c_sysc = {
 static struct omap_hwmod_class omap3xxx_wd_timer_hwmod_class = {
        .name           = "wd_timer",
        .sysc           = &omap3xxx_wd_timer_sysc,
-       .pre_shutdown   = &omap2_wd_timer_disable
+       .pre_shutdown   = &omap2_wd_timer_disable,
+       .reset          = &omap2_wd_timer_reset,
 };
 
 static struct omap_hwmod omap3xxx_wd_timer2_hwmod = {
index c997c97c08b726a8af33e99b9ea4108aac646019..950454a3fa314da4448c99eeaaf50f81201b382b 100644 (file)
@@ -3535,6 +3535,7 @@ static struct omap_hwmod_class omap44xx_wd_timer_hwmod_class = {
        .name           = "wd_timer",
        .sysc           = &omap44xx_wd_timer_sysc,
        .pre_shutdown   = &omap2_wd_timer_disable,
+       .reset          = &omap2_wd_timer_reset,
 };
 
 /* wd_timer2 */
index 4067669d96c47433889da4e92124426bee89bcaa..b2f1c67043a2b61b3b391ddedf6b6b6da75e8177 100644 (file)
@@ -14,6 +14,7 @@
 #include <plat/omap_hwmod.h>
 
 #include "wd_timer.h"
+#include "common.h"
 
 /*
  * In order to avoid any assumptions from bootloader regarding WDT
@@ -25,6 +26,8 @@
 #define OMAP_WDT_WPS           0x34
 #define OMAP_WDT_SPR           0x48
 
+/* Maximum microseconds to wait for OMAP module to softreset */
+#define MAX_MODULE_SOFTRESET_WAIT      10000
 
 int omap2_wd_timer_disable(struct omap_hwmod *oh)
 {
@@ -54,3 +57,45 @@ int omap2_wd_timer_disable(struct omap_hwmod *oh)
        return 0;
 }
 
+/**
+ * omap2_wdtimer_reset - reset and disable the WDTIMER IP block
+ * @oh: struct omap_hwmod *
+ *
+ * After the WDTIMER IP blocks are reset on OMAP2/3, we must also take
+ * care to execute the special watchdog disable sequence.  This is
+ * because the watchdog is re-armed upon OCP softreset.  (On OMAP4,
+ * this behavior was apparently changed and the watchdog is no longer
+ * re-armed after an OCP soft-reset.)  Returns -ETIMEDOUT if the reset
+ * did not complete, or 0 upon success.
+ *
+ * XXX Most of this code should be moved to the omap_hwmod.c layer
+ * during a normal merge window.  omap_hwmod_softreset() should be
+ * renamed to omap_hwmod_set_ocp_softreset(), and omap_hwmod_softreset()
+ * should call the hwmod _ocp_softreset() code.
+ */
+int omap2_wd_timer_reset(struct omap_hwmod *oh)
+{
+       int c = 0;
+
+       /* Write to the SOFTRESET bit */
+       omap_hwmod_softreset(oh);
+
+       /* Poll on RESETDONE bit */
+       omap_test_timeout((omap_hwmod_read(oh,
+                                          oh->class->sysc->syss_offs)
+                          & SYSS_RESETDONE_MASK),
+                         MAX_MODULE_SOFTRESET_WAIT, c);
+
+       if (oh->class->sysc->srst_udelay)
+               udelay(oh->class->sysc->srst_udelay);
+
+       if (c == MAX_MODULE_SOFTRESET_WAIT)
+               pr_warning("%s: %s: softreset failed (waited %d usec)\n",
+                          __func__, oh->name, MAX_MODULE_SOFTRESET_WAIT);
+       else
+               pr_debug("%s: %s: softreset in %d usec\n", __func__,
+                        oh->name, c);
+
+       return (c == MAX_MODULE_SOFTRESET_WAIT) ? -ETIMEDOUT :
+               omap2_wd_timer_disable(oh);
+}
index e0054a2d55052a0cd681648ec3c2e4461412dcd5..f6bbba73b535873aafd10168a7d6ebaf725cacfa 100644 (file)
@@ -13,5 +13,6 @@
 #include <plat/omap_hwmod.h>
 
 extern int omap2_wd_timer_disable(struct omap_hwmod *oh);
+extern int omap2_wd_timer_reset(struct omap_hwmod *oh);
 
 #endif