blob: 50edda70e094cb0dad95db22f152e817ce876f26 [file] [log] [blame]
/* Copyright 2017 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* Watchdog driver */
#include "hooks.h"
#include "registers.h"
#include "task.h"
#include "tfdp_chip.h"
#include "watchdog.h"
void watchdog_reload(void)
{
MCHP_WDG_KICK = 1;
if (IS_ENABLED(CONFIG_WATCHDOG_HELP)) {
/* Reload the auxiliary timer */
MCHP_TMR16_CTL(0) &= ~BIT(5);
MCHP_TMR16_CNT(0) = CONFIG_AUX_TIMER_PERIOD_MS;
MCHP_TMR16_CTL(0) |= BIT(5);
}
}
DECLARE_HOOK(HOOK_TICK, watchdog_reload, HOOK_PRIO_DEFAULT);
#if defined(CHIP_FAMILY_MEC152X) || defined(CHIP_FAMILY_MEC172X)
static void wdg_intr_enable(int enable)
{
if (enable) {
MCHP_WDG_STATUS = MCHP_WDG_STS_IRQ;
MCHP_WDG_IEN = MCHP_WDG_IEN_IRQ_EN;
MCHP_WDG_CTL |= MCHP_WDG_RESET_IRQ_EN;
MCHP_INT_ENABLE(MCHP_WDG_GIRQ) = MCHP_WDG_GIRQ_BIT;
task_enable_irq(MCHP_IRQ_WDG);
} else {
MCHP_WDG_IEN = 0U;
MCHP_WDG_CTL &= ~(MCHP_WDG_RESET_IRQ_EN);
MCHP_INT_DISABLE(MCHP_WDG_GIRQ) = MCHP_WDG_GIRQ_BIT;
task_disable_irq(MCHP_IRQ_WDG);
}
}
#else
static void wdg_intr_enable(int enable)
{
(void)enable;
}
#endif
/*
* MEC1701 WDG asserts chip reset on LOAD count expiration.
* WDG interrupt is simulated using a 16-bit general purpose
* timer whose period is sufficiently less that the WDG timeout
* period allowing watchdog trace data to be saved.
*
* MEC152x adds interrupt capability to the WDT.
* Enable MEC152x WDG interrupt. WDG event will assert
* IRQ and kick itself starting another LOAD timeout.
* After the new LOAD expires WDG will assert chip reset.
* The WDG ISR calls watchdog trace save API, upon return we
* enter a spin loop waiting for the LOAD period to expire.
* WDG does not have a way to trigger an immediate reset except
* by re-programming it.
*/
int watchdog_init(void)
{
if (IS_ENABLED(CONFIG_WATCHDOG_HELP)) {
/*
* MEC170x Watchdog does not warn us before expiring.
* Let's use a 16-bit timer as an auxiliary timer.
*/
/* Clear 16-bit basic timer 0 PCR sleep enable */
MCHP_PCR_SLP_DIS_DEV(MCHP_PCR_BTMR16_0);
/* Stop the auxiliary timer if it's running */
MCHP_TMR16_CTL(0) &= ~BIT(5);
/* Enable auxiliary timer */
MCHP_TMR16_CTL(0) |= BIT(0);
/* Prescaler = 48000 -> 1kHz -> Period = 1 ms */
MCHP_TMR16_CTL(0) = (MCHP_TMR16_CTL(0) & 0xffffU) |
(47999 << 16);
/* No auto restart */
MCHP_TMR16_CTL(0) &= ~BIT(3);
/* Count down */
MCHP_TMR16_CTL(0) &= ~BIT(2);
/* Enable interrupt from auxiliary timer */
MCHP_TMR16_IEN(0) |= BIT(0);
task_enable_irq(MCHP_IRQ_TIMER16_0);
MCHP_INT_ENABLE(MCHP_TMR16_GIRQ) = MCHP_TMR16_GIRQ_BIT(0);
/* Load and start the auxiliary timer */
MCHP_TMR16_CNT(0) = CONFIG_AUX_TIMER_PERIOD_MS;
MCHP_TMR16_CNT(0) |= BIT(5);
}
MCHP_WDG_CTL = 0;
/* Clear WDT PCR sleep enable */
MCHP_PCR_SLP_DIS_DEV(MCHP_PCR_WDT);
/* Set timeout. It takes 1007 us to decrement WDG_CNT by 1. */
MCHP_WDG_LOAD = CONFIG_WATCHDOG_PERIOD_MS * 1000 / 1007;
wdg_intr_enable(1);
/*
* Start watchdog
* If chipset debug build enable feature to prevent watchdog from
* counting if a debug cable is attached to JTAG_RST#.
*/
if (IS_ENABLED(CONFIG_CHIPSET_DEBUG))
MCHP_WDG_CTL |=
(MCHP_WDT_CTL_ENABLE | MCHP_WDT_CTL_JTAG_STALL_EN);
else
MCHP_WDG_CTL |= MCHP_WDT_CTL_ENABLE;
return EC_SUCCESS;
}
/* MEC152x Watchdog can fire an interrupt to CPU before system reset */
#if defined(CHIP_FAMILY_MEC152X) || defined(CHIP_FAMILY_MEC172X)
void __keep watchdog_check(uint32_t excep_lr, uint32_t excep_sp)
{
/* Clear WDG first then aggregator */
MCHP_WDG_STATUS = MCHP_WDG_STS_IRQ;
MCHP_INT_SOURCE(MCHP_WDG_GIRQ) = MCHP_WDG_GIRQ_BIT;
/* Cause WDG to reload again. */
MCHP_WDG_KICK = 1;
watchdog_trace(excep_lr, excep_sp);
/* Reset system by re-programing WDT to trigger after 2 32KHz clocks */
MCHP_WDG_CTL = 0; /* clear enable to allow write to load register */
MCHP_WDG_LOAD = 2;
MCHP_WDG_CTL |= MCHP_WDT_CTL_ENABLE;
}
/* ISR for watchdog warning naked will keep SP & LR */
void IRQ_HANDLER(MCHP_IRQ_WDG)(void) __keep __attribute__((naked));
void IRQ_HANDLER(MCHP_IRQ_WDG)(void)
{
/* Naked call so we can extract raw LR and SP */
asm volatile("mov r0, lr\n"
"mov r1, sp\n"
/*
* Must push registers in pairs to keep 64-bit aligned
* stack for ARM EABI. This also conveniently saves
* R0=LR so we can pass it to task_resched_if_needed.
*/
"push {r0, lr}\n"
"bl watchdog_check\n"
"pop {r0, lr}\n"
"b task_resched_if_needed\n");
}
/* put the watchdog at the highest priority */
const struct irq_priority __keep IRQ_PRIORITY(MCHP_IRQ_WDG)
__attribute__((section(".rodata.irqprio"))) = { MCHP_IRQ_WDG, 0 };
#else
/*
* MEC1701 watchdog only resets. Use a 16-bit timer to fire in interrupt
* for saving watchdog trace.
*/
#ifdef CONFIG_WATCHDOG_HELP
void __keep watchdog_check(uint32_t excep_lr, uint32_t excep_sp)
{
/* Clear status */
MCHP_TMR16_STS(0) |= 1;
/* clear aggregator status */
MCHP_INT_SOURCE(MCHP_TMR16_GIRQ) = MCHP_TMR16_GIRQ_BIT(0);
watchdog_trace(excep_lr, excep_sp);
}
void IRQ_HANDLER(MCHP_IRQ_TIMER16_0)(void) __keep __attribute__((naked));
void IRQ_HANDLER(MCHP_IRQ_TIMER16_0)(void)
{
/* Naked call so we can extract raw LR and SP */
asm volatile("mov r0, lr\n"
"mov r1, sp\n"
/*
* Must push registers in pairs to keep 64-bit aligned
* stack for ARM EABI. This also conveniently saves
* R0=LR so we can pass it to task_resched_if_needed.
*/
"push {r0, lr}\n"
"bl watchdog_check\n"
"pop {r0, lr}\n"
"b task_resched_if_needed\n");
}
/* Put the watchdog at the highest interrupt priority. */
const struct irq_priority __keep IRQ_PRIORITY(MCHP_IRQ_TIMER16_0)
__attribute__((section(".rodata.irqprio"))) = { MCHP_IRQ_TIMER16_0, 0 };
#endif /* #ifdef CONFIG_WATCHDOG_HELP */
#endif /* #if defined(CHIP_FAMILY_MEC152X) || defined(CHIP_FAMILY_MEC172X) */