/* 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" #include "util.h" /* 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) { /* Select which clock to use for this timer */ 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; } /* Wait for module disable to take effect before updating count */ while (IS_BIT_SET(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN)) ; NPCX_ITCNT16(ITIM_EVENT_NO) = MAX(evt_cnt, 1); /* Event module enable */ SET_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN); /* Wait for module enable */ while (!IS_BIT_SET(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, cnt2; cnt = NPCX_ITCNT16(ITIM_EVENT_NO); /* Wait for two consecutive equal values are read */ while ((cnt2 = NPCX_ITCNT16(ITIM_EVENT_NO)) != cnt) cnt = cnt2; 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 doesn'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, 3); /*****************************************************************************/ /* 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, cnt2; cnt = NPCX_ITCNT32; /* * Wait for two consecutive equal values are read no matter * ITIM's source clock is APB2 or 32K since mux's delay. */ while ((cnt2 = NPCX_ITCNT32) != cnt) cnt = cnt2; #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, 3); /* Handle ITIM32 overflow if interrupt is disabled */ void __hw_clock_handle_overflow(uint32_t clksrc_high) { timestamp_t newtime; /* Overflow occurred? */ if (!IS_BIT_SET(NPCX_ITCTS(ITIM32), NPCX_ITCTS_TO_STS)) return; /* Clear timeout status */ SET_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_TO_STS); /* * Restore ITIM32 preload counter value to maximum and execute * process_timers() later in ISR by trigger software interrupt in * force_time(). */ newtime.le.hi = clksrc_high + 1; newtime.le.lo = 0; force_time(newtime); } 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); void __hw_early_init_hwtimer(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(); hw_clock_source_set_preload(start_t, 1); } /* Note that early_init_hwtimer() has already executed by this point */ int __hw_clock_source_init(uint32_t start_t) { /* * Override the count with the start value now that counting has * started. Note that we may have already called this function from * gpio_pre_init(), but only in the case where we expected a reset, so * we should not get here in that case. */ __hw_early_init_hwtimer(start_t); /* Enable interrupt of ITIM */ task_enable_irq(NPCX_IRQ_ITIM32); return NPCX_IRQ_ITIM32; }