| /* 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) */ |