2010-12-22 10:56:17 +08:00
|
|
|
/*
|
|
|
|
* OMAP2+ MPU WD_TIMER-specific code
|
|
|
|
*
|
2012-10-30 10:57:44 +08:00
|
|
|
* Copyright (C) 2012 Texas Instruments, Inc.
|
|
|
|
*
|
2010-12-22 10:56:17 +08:00
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
|
2012-10-30 10:49:44 +08:00
|
|
|
#include <linux/platform_data/omap-wd-timer.h>
|
2010-12-22 10:56:17 +08:00
|
|
|
|
2012-10-30 10:49:44 +08:00
|
|
|
#include "omap_hwmod.h"
|
|
|
|
#include "omap_device.h"
|
2011-01-07 11:49:29 +08:00
|
|
|
#include "wd_timer.h"
|
ARM: OMAP2+: WDTIMER integration: fix !PM boot crash, disarm timer after hwmod reset
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>
2012-05-09 01:34:30 +08:00
|
|
|
#include "common.h"
|
2012-10-30 10:49:44 +08:00
|
|
|
#include "prm.h"
|
|
|
|
#include "soc.h"
|
2011-01-07 11:49:29 +08:00
|
|
|
|
2010-12-22 10:56:17 +08:00
|
|
|
/*
|
|
|
|
* In order to avoid any assumptions from bootloader regarding WDT
|
|
|
|
* settings, WDT module is reset during init. This enables the watchdog
|
|
|
|
* timer. Hence it is required to disable the watchdog after the WDT reset
|
|
|
|
* during init. Otherwise the system would reboot as per the default
|
|
|
|
* watchdog timer registers settings.
|
|
|
|
*/
|
|
|
|
#define OMAP_WDT_WPS 0x34
|
|
|
|
#define OMAP_WDT_SPR 0x48
|
|
|
|
|
|
|
|
int omap2_wd_timer_disable(struct omap_hwmod *oh)
|
|
|
|
{
|
|
|
|
void __iomem *base;
|
|
|
|
|
|
|
|
if (!oh) {
|
|
|
|
pr_err("%s: Could not look up wdtimer_hwmod\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
base = omap_hwmod_get_mpu_rt_va(oh);
|
|
|
|
if (!base) {
|
|
|
|
pr_err("%s: Could not get the base address for %s\n",
|
|
|
|
oh->name, __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sequence required to disable watchdog */
|
|
|
|
__raw_writel(0xAAAA, base + OMAP_WDT_SPR);
|
|
|
|
while (__raw_readl(base + OMAP_WDT_WPS) & 0x10)
|
|
|
|
cpu_relax();
|
|
|
|
|
|
|
|
__raw_writel(0x5555, base + OMAP_WDT_SPR);
|
|
|
|
while (__raw_readl(base + OMAP_WDT_WPS) & 0x10)
|
|
|
|
cpu_relax();
|
|
|
|
|
OMAP2+: wd_timer: disable on boot via hwmod postsetup mechanism
The OMAP watchdog timer IP blocks require a specific set of register
writes to occur before they will be disabled[1], even if the device
clocks appear to be disabled in the CM_*CLKEN registers. In the MPU
watchdog case, failure to execute this reset sequence will eventually
cause the watchdog to reset the OMAP unexpectedly.
Previously, the code to disable this watchdog was manually called from
mach-omap2/devices.c during device initialization. This causes the
watchdog to be unconditionally disabled for a portion of kernel
initialization. This should be controllable by the board-*.c files,
since some system integrators will want full watchdog coverage of
kernel initialization. Also, the watchdog disable code was not
connected to the hwmod shutdown code. This means that calling
omap_hwmod_shutdown() will not, in fact, disable the watchdog, and the
goal of omap_hwmod_shutdown() is to be able to shutdown any on-chip
OMAP device.
To resolve the latter problem, populate the pre_shutdown pointer in
the watchdog timer hwmod classes with a function that executes the
watchdog shutdown sequence. This allows the hwmod code to fully
disable the watchdog.
Then, to allow some board files to support watchdog coverage
throughout kernel initialization, add common code to mach-omap2/io.c
to cause the MPU watchdog to be disabled on boot unless a board file
specifically requests it to remain enabled. Board files can do this
by changing the watchdog timer hwmod's postsetup state between the
omap2_init_common_infrastructure() and omap2_init_common_devices()
function calls.
1. OMAP34xx Multimedia Device Silicon Revision 3.1.x Rev. ZH
[SWPU222H], Section 16.4.3.6, "Start/Stop Sequence for WDTs (Using
WDTi.WSPR Register)"
Signed-off-by: Paul Walmsley <paul@pwsan.com>
Cc: Benoît Cousson <b-cousson@ti.com>
Cc: Kevin Hilman <khilman@deeprootsystems.com>
Cc: Charulatha Varadarajan <charu@ti.com>
2010-12-22 06:39:15 +08:00
|
|
|
return 0;
|
2010-12-22 10:56:17 +08:00
|
|
|
}
|
|
|
|
|
ARM: OMAP2+: WDTIMER integration: fix !PM boot crash, disarm timer after hwmod reset
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>
2012-05-09 01:34:30 +08:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
2012-10-30 10:49:44 +08:00
|
|
|
|
|
|
|
static int __init omap_init_wdt(void)
|
|
|
|
{
|
|
|
|
int id = -1;
|
|
|
|
struct platform_device *pdev;
|
|
|
|
struct omap_hwmod *oh;
|
|
|
|
char *oh_name = "wd_timer2";
|
|
|
|
char *dev_name = "omap_wdt";
|
|
|
|
struct omap_wd_timer_platform_data pdata;
|
|
|
|
|
|
|
|
if (!cpu_class_is_omap2() || of_have_populated_dt())
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
oh = omap_hwmod_lookup(oh_name);
|
|
|
|
if (!oh) {
|
|
|
|
pr_err("Could not look up wd_timer%d hwmod\n", id);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pdata.read_reset_sources = prm_read_reset_sources;
|
|
|
|
|
|
|
|
pdev = omap_device_build(dev_name, id, oh, &pdata,
|
2013-01-26 15:48:53 +08:00
|
|
|
sizeof(struct omap_wd_timer_platform_data));
|
2012-10-30 10:49:44 +08:00
|
|
|
WARN(IS_ERR(pdev), "Can't build omap_device for %s:%s.\n",
|
|
|
|
dev_name, oh->name);
|
|
|
|
return 0;
|
|
|
|
}
|
2013-01-12 03:24:18 +08:00
|
|
|
omap_subsys_initcall(omap_init_wdt);
|