| /* Copyright 2014 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 "clock.h" |
| #include "common.h" |
| #include "console.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "hwtimer_chip.h" |
| #include "registers.h" |
| #include "system_chip.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| #include "watchdog.h" |
| |
| /* WDCNT value for watchdog period */ |
| #define WDCNT_VALUE \ |
| ((CONFIG_WATCHDOG_PERIOD_MS * INT_32K_CLOCK) / (1024 * 1000)) |
| |
| void watchdog_init_warning_timer(void) |
| { |
| /* init watchdog timer first */ |
| init_hw_timer(ITIM_WDG_NO, ITIM_SOURCE_CLOCK_32K); |
| |
| /* |
| * prescaler to TIMER_TICK |
| * Ttick_unit = (PRE_8+1) * T32k |
| * PRE_8 = (Ttick_unit/T32K) - 1 |
| * Unit: 1 msec |
| */ |
| NPCX_ITPRE(ITIM_WDG_NO) = |
| DIV_ROUND_NEAREST(1000 * INT_32K_CLOCK, SECOND) - 1; |
| |
| /* Event module disable */ |
| CLEAR_BIT(NPCX_ITCTS(ITIM_WDG_NO), NPCX_ITCTS_ITEN); |
| /* ITIM count down : event expired */ |
| NPCX_ITCNT(ITIM_WDG_NO) = CONFIG_AUX_TIMER_PERIOD_MS; |
| /* Event module enable */ |
| SET_BIT(NPCX_ITCTS(ITIM_WDG_NO), NPCX_ITCTS_ITEN); |
| /* Clear pending interrupt before enabling */ |
| task_clear_pending_irq(ITIM_INT(ITIM_WDG_NO)); |
| /* Enable interrupt of ITIM */ |
| task_enable_irq(ITIM_INT(ITIM_WDG_NO)); |
| } |
| |
| static timestamp_t last_watchdog_touch; |
| void watchdog_stop_and_unlock(void) |
| { |
| /* |
| * Ensure we have waited at least 3 watchdog ticks since touching WD |
| * timer. 3 / (32768 / 1024) HZ = 93.75ms |
| */ |
| while (time_since32(last_watchdog_touch) < (100 * MSEC)) |
| continue; |
| |
| NPCX_WDSDM = 0x87; |
| NPCX_WDSDM = 0x61; |
| NPCX_WDSDM = 0x63; |
| |
| /* Wait for WD to stop running */ |
| while (IS_BIT_SET(NPCX_T0CSR, NPCX_T0CSR_WD_RUN)) |
| ; |
| } |
| |
| static void touch_watchdog_count(void) |
| { |
| NPCX_WDSDM = 0x5C; |
| last_watchdog_touch = get_time(); |
| } |
| |
| static void watchdog_reload_warning_timer(void) |
| { |
| /* Disable warning timer module */ |
| CLEAR_BIT(NPCX_ITCTS(ITIM_WDG_NO), NPCX_ITCTS_ITEN); |
| /* Wait for module disable to take effect before updating count */ |
| while (IS_BIT_SET(NPCX_ITCTS(ITIM_WDG_NO), NPCX_ITCTS_ITEN)) |
| ; |
| |
| /* Reload the warning timer count */ |
| NPCX_ITCNT(ITIM_WDG_NO) = CONFIG_AUX_TIMER_PERIOD_MS; |
| |
| /* enable warning timer module */ |
| SET_BIT(NPCX_ITCTS(ITIM_WDG_NO), NPCX_ITCTS_ITEN); |
| /* Wait for module enable */ |
| while (!IS_BIT_SET(NPCX_ITCTS(ITIM_WDG_NO), NPCX_ITCTS_ITEN)) |
| ; |
| } |
| |
| void __keep watchdog_check(uint32_t excep_lr, uint32_t excep_sp) |
| { |
| #ifdef CONFIG_TASK_PROFILING |
| /* |
| * Perform IRQ profiling accounting. This is normally done by |
| * DECLARE_IRQ(), but we are not using that for ITIM_WDG_NO. |
| */ |
| task_start_irq_handler((void *)excep_lr); |
| #endif |
| |
| /* Clear timeout status for event */ |
| SET_BIT(NPCX_ITCTS(ITIM_WDG_NO), NPCX_ITCTS_TO_STS); |
| |
| /* Print panic info */ |
| watchdog_trace(excep_lr, excep_sp); |
| } |
| |
| /* ISR for watchdog warning naked will keep SP & LR */ |
| void IRQ_HANDLER(ITIM_INT(ITIM_WDG_NO))(void) __attribute__((naked)); |
| void IRQ_HANDLER(ITIM_INT(ITIM_WDG_NO))(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 conveninently 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"); |
| } |
| const struct irq_priority __keep IRQ_PRIORITY(ITIM_INT(ITIM_WDG_NO)) |
| __attribute__((section(".rodata.irqprio"))) = { ITIM_INT(ITIM_WDG_NO), |
| 0 }; |
| /* put the watchdog at the highest priority */ |
| |
| void watchdog_reload(void) |
| { |
| /* Disable watchdog interrupt */ |
| task_disable_irq(ITIM_INT(ITIM_WDG_NO)); |
| |
| watchdog_reload_warning_timer(); |
| |
| #if 1 /* mark this for testing watchdog */ |
| /* Touch watchdog & reset software counter */ |
| touch_watchdog_count(); |
| #endif |
| |
| /* Enable watchdog interrupt */ |
| task_enable_irq(ITIM_INT(ITIM_WDG_NO)); |
| } |
| DECLARE_HOOK(HOOK_TICK, watchdog_reload, HOOK_PRIO_DEFAULT); |
| |
| int watchdog_init(void) |
| { |
| #if SUPPORT_WDG |
| const uint32_t pre_wdcnt = WDCNT_VALUE; |
| |
| /* Touch watchdog before init if it is already running */ |
| if (IS_BIT_SET(NPCX_T0CSR, NPCX_T0CSR_WD_RUN)) |
| touch_watchdog_count(); |
| |
| /* Keep prescaler ratio timer0 clock to 1:1024 */ |
| NPCX_TWCP = 0x0A; |
| |
| /* Clear watchdog reset status initially*/ |
| SET_BIT(NPCX_T0CSR, NPCX_T0CSR_WDRST_STS); |
| |
| /* Reset TWCFG */ |
| NPCX_TWCFG = 0; |
| /* Watchdog touch by writing 5Ch to WDSDM */ |
| SET_BIT(NPCX_TWCFG, NPCX_TWCFG_WDSDME); |
| /* Select T0IN clock as watchdog prescaler clock */ |
| SET_BIT(NPCX_TWCFG, NPCX_TWCFG_WDCT0I); |
| /* Disable early touch functionality */ |
| SET_BIT(NPCX_T0CSR, NPCX_T0CSR_TESDIS); |
| |
| /* |
| * Calculate and set WDCNT initial reload value and T0OUT timeout |
| * period |
| * When WDCNT counts down to 0: generate watchdog reset |
| */ |
| if (pre_wdcnt <= 255) { |
| /* Keep prescaler ratio watchdog clock to 1:1 */ |
| NPCX_WDCP = 0; |
| NPCX_WDCNT = pre_wdcnt; |
| } else { |
| uint8_t wdcp; |
| uint8_t pre_scal; |
| |
| pre_scal = DIV_ROUND_UP(pre_wdcnt, 255); |
| |
| /* |
| * Find the smallest power of 2 greater than or equal to the |
| * prescaler |
| */ |
| wdcp = __fls(pre_scal - 1) + 1; |
| pre_scal = 1 << wdcp; |
| |
| NPCX_WDCP = wdcp; |
| NPCX_WDCNT = pre_wdcnt / pre_scal; |
| } |
| |
| /* Disable interrupt */ |
| interrupt_disable(); |
| /* Reload TWDT0/WDCNT */ |
| SET_BIT(NPCX_T0CSR, NPCX_T0CSR_RST); |
| /* Wait for timer is loaded and restart */ |
| while (IS_BIT_SET(NPCX_T0CSR, NPCX_T0CSR_RST)) |
| ; |
| /* Enable interrupt */ |
| interrupt_enable(); |
| |
| /* Init watchdog warning timer */ |
| watchdog_init_warning_timer(); |
| #endif |
| return EC_SUCCESS; |
| } |