Files
OpenCellular/chip/npcx/hwtimer.c
Mulin Chao 3ccb91fe10 npcx: Fixed bug that unexpected value of timer which source clock is 32K
In rare case, FW read the unexpected counter value of timer which source
clock is 32K (Watchdog timer and ITIM16/32 which use 32K source clock).
The root cause is the clocks between reading registers and timer's are
asynchronous. It has a chance to get invalid counter value when timer is
under transaction edge. The solution is using two consecutive equal
readings to make sure the counter value is valid.

Beside different source clocks of timer, we also found there's chip's bug
which causes unexpected value of timer. If an interrupt that occurs very
shortly before entering deep idle with instant wakeup, it might result in
disruptive execution (such as skipping some instructions or hard fault)
after "wfi". The workaround is adding the same bypass for idle in deep
idle section.

Modified sources:
1. clock.c: Add bypass for instant wakeup from deep sleep.
2. hwtimer.c: Add consecutive reading function for event timer.
3. watchdog.c: Add consecutive reading function for watchdog timer.

BUG=chrome-os-partner:34346
TEST=make buildall -j; test nuvoton IC specific drivers
BRANCH=none

Change-Id: I7c9f1fb9618a3c29826d8f4599864a8dac4203bf
Signed-off-by: Mulin Chao <mlchao@nuvoton.com>
Reviewed-on: https://chromium-review.googlesource.com/327356
Reviewed-by: Shawn N <shawnn@chromium.org>
2016-05-11 21:24:32 -07:00

298 lines
7.9 KiB
C

/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* Hardware timers driver */
#include "clock.h"
#include "clock_chip.h"
#include "common.h"
#include "hooks.h"
#include "hwtimer.h"
#include "hwtimer_chip.h"
#include "math_util.h"
#include "registers.h"
#include "console.h"
#include "task.h"
#include "timer.h"
/* Use ITIM32 as main hardware timer */
#define TICK_ITIM32_MAX_CNT 0xFFFFFFFF
/* Maximum deadline of event */
#define EVT_MAX_EXPIRED_US 0xFFFFFFFF
/* Depth of event timer */
#define TICK_EVT_DEPTH 16 /* Depth of event timer Unit: bits */
#define TICK_EVT_INTERVAL (1 << TICK_EVT_DEPTH) /* Unit: us */
#define TICK_EVT_INTERVAL_MASK (TICK_EVT_INTERVAL - 1) /* Mask of interval */
#define TICK_EVT_MAX_CNT (TICK_EVT_INTERVAL - 1) /* Maximum event counter */
/* Time when event will be expired unit:us */
static volatile uint32_t evt_expired_us;
/* 32-bits event counter */
static volatile uint32_t evt_cnt;
/* Debugger information */
#if DEBUG_TMR
static volatile uint32_t evt_cnt_us_dbg;
static volatile uint32_t cur_cnt_us_dbg;
#endif
#if !(DEBUG_TMR)
#define CPUTS(...)
#define CPRINTS(...)
#else
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)
#endif
/*****************************************************************************/
/* Internal functions */
void init_hw_timer(int itim_no, enum ITIM_SOURCE_CLOCK_T source)
{
/* Use internal 32K clock/APB2 for ITIM16 */
UPDATE_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_CKSEL,
source != ITIM_SOURCE_CLOCK_APB2);
/* Clear timeout status */
SET_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_TO_STS);
/* ITIM timeout interrupt enable */
SET_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_TO_IE);
/* ITIM timeout wake-up enable */
SET_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_TO_WUE);
}
/*****************************************************************************/
/* HWTimer event handlers */
void __hw_clock_event_set(uint32_t deadline)
{
fp_t inv_evt_tick = FLOAT_TO_FP(INT_32K_CLOCK/(float)SECOND);
int32_t evt_cnt_us;
/* Is deadline min value? */
if (evt_expired_us != 0 && evt_expired_us < deadline)
return;
/* mark min event value */
evt_expired_us = deadline;
evt_cnt_us = deadline - __hw_clock_source_read();
#if DEBUG_TMR
evt_cnt_us_dbg = deadline - __hw_clock_source_read();
#endif
/* Deadline is behind current timer */
if (evt_cnt_us < 0)
evt_cnt_us = 1;
/* Event module disable */
CLEAR_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);
/*
* ITIM count down : event expired : Unit: 1/32768 sec
* It must exceed evt_expired_us for process_timers function
*/
evt_cnt = FP_TO_INT((fp_inter_t)(evt_cnt_us) * inv_evt_tick);
if (evt_cnt > TICK_EVT_MAX_CNT) {
CPRINTS("Event overflow! 0x%08x, us is %d\r\n",
evt_cnt, evt_cnt_us);
evt_cnt = TICK_EVT_MAX_CNT;
}
NPCX_ITCNT16(ITIM_EVENT_NO) = evt_cnt;
/* Event module enable */
SET_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);
/* Enable interrupt of ITIM */
task_enable_irq(ITIM16_INT(ITIM_EVENT_NO));
}
/* Returns the time-stamp of the next programmed event */
uint32_t __hw_clock_event_get(void)
{
if (evt_expired_us)
return evt_expired_us;
else /* No events. Give maximum deadline */
return EVT_MAX_EXPIRED_US;
}
/* Get current counter value of event timer */
uint16_t __hw_clock_event_count(void)
{
uint16_t cnt;
/* Wait for two consecutive equal values are read */
do {
cnt = NPCX_ITCNT16(ITIM_EVENT_NO);
} while (cnt != NPCX_ITCNT16(ITIM_EVENT_NO));
return cnt;
}
/* Returns time delay cause of deep idle */
uint32_t __hw_clock_get_sleep_time(uint16_t pre_evt_cnt)
{
fp_t evt_tick = FLOAT_TO_FP(SECOND/(float)INT_32K_CLOCK);
uint32_t sleep_time;
uint16_t cnt = __hw_clock_event_count();
/* Event has been triggered but timer ISR dosen't handle it */
if (IS_BIT_SET(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_TO_STS))
sleep_time = FP_TO_INT((fp_inter_t)(pre_evt_cnt+1) * evt_tick);
/* Event hasn't been triggered */
else
sleep_time = FP_TO_INT((fp_inter_t)(pre_evt_cnt+1 - cnt) *
evt_tick);
return sleep_time;
}
/* Cancel the next event programmed by __hw_clock_event_set */
void __hw_clock_event_clear(void)
{
/* ITIM event module disable */
CLEAR_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);
/* Disable interrupt of Event */
task_disable_irq(ITIM16_INT(ITIM_EVENT_NO));
/* Clear event parameters */
evt_expired_us = 0;
evt_cnt = 0;
}
/* Irq for hwtimer event */
void __hw_clock_event_irq(void)
{
/* ITIM event module disable */
CLEAR_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);
/* Disable interrupt of event */
task_disable_irq(ITIM16_INT(ITIM_EVENT_NO));
/* Clear timeout status for event */
SET_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_TO_STS);
/* Clear event parameters */
evt_expired_us = 0;
evt_cnt = 0;
/* handle upper driver */
process_timers(0);
#ifdef CONFIG_LOW_POWER_IDLE
/*
* Set event for ITIM32 after process_timers() since no events set if
* event's deadline is over 32 bits but current source clock isn't.
* ITIM32 is based on apb2 and ec won't wake-up in deep-idle even if it
* expires.
*/
if (evt_expired_us == 0)
__hw_clock_event_set(EVT_MAX_EXPIRED_US);
#endif
}
DECLARE_IRQ(ITIM16_INT(ITIM_EVENT_NO) , __hw_clock_event_irq, 1);
/*****************************************************************************/
/* HWTimer tick handlers */
/* Modify preload counter of source clock. */
void hw_clock_source_set_preload(uint32_t ts, uint8_t clear)
{
/* ITIM32 module disable */
CLEAR_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_ITEN);
CLEAR_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_CKSEL);
/* Set preload counter to current time */
NPCX_ITCNT32 = TICK_ITIM32_MAX_CNT - ts;
/* Clear timeout status or not */
if (clear)
SET_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_TO_STS);
/* ITIM32 module enable */
SET_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_ITEN);
}
/* Returns the value of the free-running counter used as clock. */
uint32_t __hw_clock_source_read(void)
{
uint32_t cnt = NPCX_ITCNT32;
#if DEBUG_TMR
cur_cnt_us_dbg = TICK_ITIM32_MAX_CNT - cnt;
#endif
return TICK_ITIM32_MAX_CNT - cnt;
}
/* Override the current value of the hardware counter */
void __hw_clock_source_set(uint32_t ts)
{
#if DEBUG_TMR
cur_cnt_us_dbg = TICK_ITIM32_MAX_CNT - ts;
#endif
hw_clock_source_set_preload(ts, 0);
}
/* Irq for hwtimer tick */
void __hw_clock_source_irq(void)
{
/* Is timeout trigger trigger? */
if (IS_BIT_SET(NPCX_ITCTS(ITIM32), NPCX_ITCTS_TO_STS)) {
/* Restore ITIM32 preload counter value to maximum value */
hw_clock_source_set_preload(0, 1);
/* 32-bits timer count overflow */
process_timers(1);
} else { /* Handle soft trigger */
process_timers(0);
#ifdef CONFIG_LOW_POWER_IDLE
/* Set event for ITIM32. Please see above for detail */
if (evt_expired_us == 0)
__hw_clock_event_set(EVT_MAX_EXPIRED_US);
#endif
}
}
DECLARE_IRQ(NPCX_IRQ_ITIM32, __hw_clock_source_irq, 0);
static void update_prescaler(void)
{
/*
* prescaler to time tick
* Ttick_unit = (PRE_8+1) * Tapb2_clk
* PRE_8 = (Ttick_unit/Tapb2_clk) -1
*/
NPCX_ITPRE(ITIM32) = (clock_get_apb2_freq() / SECOND) - 1;
/* Set event tick unit = 1/32768 sec */
NPCX_ITPRE(ITIM_EVENT_NO) = 0;
}
DECLARE_HOOK(HOOK_FREQ_CHANGE, update_prescaler, HOOK_PRIO_DEFAULT);
int __hw_clock_source_init(uint32_t start_t)
{
/*
* 1. Use ITIM16-1 as internal time reading
* 2. Use ITIM16-2 for event handling
*/
/* Enable clock for ITIM peripheral */
clock_enable_peripheral(CGC_OFFSET_TIMER, CGC_TIMER_MASK,
CGC_MODE_RUN | CGC_MODE_SLEEP);
/* init tick & event timer first */
init_hw_timer(ITIM32, ITIM_SOURCE_CLOCK_APB2);
init_hw_timer(ITIM_EVENT_NO, ITIM_SOURCE_CLOCK_32K);
/* Set initial prescaler */
update_prescaler();
/*
* Override the count with the start value now that counting has
* started.
*/
hw_clock_source_set_preload(start_t, 1);
/* Enable interrupt of ITIM */
task_enable_irq(NPCX_IRQ_ITIM32);
return NPCX_IRQ_ITIM32;
}