blob: a7c9fa7cc79cfa633eb8e8c53cf0e8c050cbf579 [file] [log] [blame]
/* 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;
}