/* * Copyright (c) 2014-2015 Wind River Systems, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file * @brief ARC Timer0 device driver * * This module implements a kernel device driver for the ARCv2 processor Timer0 * and provides the standard "system clock driver" interfaces. * * If the TICKLESS_IDLE kernel configuration option is enabled, the timer may * be programmed to wake the system in N >= TICKLESS_IDLE_THRESH ticks. The * kernel invokes _timer_idle_enter() to program the up counter to trigger an * interrupt in N ticks. When the timer expires (or when another interrupt is * detected), the kernel's interrupt stub invokes _timer_idle_exit() to leave * the tickless idle state. * * @internal * The ARCv2 processor timer provides a 32-bit incrementing, wrap-to-zero * counter. * * Factors that increase the driver's tickless idle complexity: * 1. As the Timer0 up-counter is a 32-bit value, the number of ticks for which * the system can be in tickless idle is limited to 'max_system_ticks'. * * 2. The act of entering tickless idle may potentially straddle a tick * boundary. This can be detected in _timer_idle_enter() after Timer0 is * programmed with the new limit and acted upon in _timer_idle_exit(). * * 3. Tickless idle may be prematurely aborted due to a straddled tick. See * previous factor. * * 4. Tickless idle may end naturally. This is detected and handled in * _timer_idle_exit(). * * 5. Tickless idle may be prematurely aborted due to a non-timer interrupt. * If this occurs, Timer0 is reprogrammed to trigger at the next tick. * @endinternal */ #include #include #include #include #include #include #include #include #include #include /* * note: This implementation assumes Timer0 is present. Be sure * to build the ARC CPU with Timer0. */ #include #define _ARC_V2_TMR_CTRL_IE 0x1 /* interrupt enable */ #define _ARC_V2_TMR_CTRL_NH 0x2 /* count only while not halted */ #define _ARC_V2_TMR_CTRL_W 0x4 /* watchdog mode enable */ #define _ARC_V2_TMR_CTRL_IP 0x8 /* interrupt pending flag */ /* running total of timer count */ static uint32_t __noinit cycles_per_tick; static uint32_t accumulated_cycle_count; #ifdef CONFIG_TICKLESS_IDLE static uint32_t __noinit max_system_ticks; static uint32_t __noinit programmed_limit; static uint32_t __noinit programmed_ticks; static bool straddled_tick_on_idle_enter = false; extern int32_t _sys_idle_elapsed_ticks; #endif /** * * @brief Get contents of Timer0 count register * * @return Current Timer0 count */ static ALWAYS_INLINE uint32_t timer0_count_register_get(void) { return _arc_v2_aux_reg_read(_ARC_V2_TMR0_COUNT); } /** * * @brief Set Timer0 count register to the specified value * * @return N/A */ static ALWAYS_INLINE void timer0_count_register_set(uint32_t value) { _arc_v2_aux_reg_write(_ARC_V2_TMR0_COUNT, value); } /** * * @brief Get contents of Timer0 control register * * @return N/A */ static ALWAYS_INLINE uint32_t timer0_control_register_get(void) { return _arc_v2_aux_reg_read(_ARC_V2_TMR0_CONTROL); } /** * * @brief Set Timer0 control register to the specified value * * @return N/A */ static ALWAYS_INLINE void timer0_control_register_set(uint32_t value) { _arc_v2_aux_reg_write(_ARC_V2_TMR0_CONTROL, value); } /** * * @brief Set Timer0 limit register to the specified value * * @return N/A */ static ALWAYS_INLINE void timer0_limit_register_set(uint32_t count) { _arc_v2_aux_reg_write(_ARC_V2_TMR0_LIMIT, count); } #ifdef CONFIG_TICKLESS_IDLE static ALWAYS_INLINE void update_accumulated_count(void) { accumulated_cycle_count += (_sys_idle_elapsed_ticks * cycles_per_tick); } #else /* CONFIG_TICKLESS_IDLE */ static ALWAYS_INLINE void update_accumulated_count(void) { accumulated_cycle_count += cycles_per_tick; } #endif /* CONFIG_TICKLESS_IDLE */ /** * * @brief System clock periodic tick handler * * This routine handles the system clock periodic tick interrupt. It always * announces one tick. * * @return N/A */ void _timer_int_handler(void *unused) { ARG_UNUSED(unused); /* clear the interrupt by writing 0 to IP bit of the control register */ timer0_control_register_set(_ARC_V2_TMR_CTRL_NH | _ARC_V2_TMR_CTRL_IE); #if defined(CONFIG_TICKLESS_IDLE) timer0_limit_register_set(cycles_per_tick - 1); __ASSERT_EVAL({}, uint32_t timer_count = timer0_count_register_get(), timer_count <= (cycles_per_tick - 1), "timer_count: %d, limit %d\n", timer_count, cycles_per_tick - 1); _sys_clock_final_tick_announce(); #else _sys_clock_tick_announce(); #endif update_accumulated_count(); } #if defined(CONFIG_TICKLESS_IDLE) /* * @brief initialize the tickless idle feature * * This routine initializes the tickless idle feature. * * @return N/A */ static void tickless_idle_init(void) { /* calculate the max number of ticks with this 32-bit H/W counter */ max_system_ticks = 0xffffffff / cycles_per_tick; } /* * @brief Place the system timer into idle state * * Re-program the timer to enter into the idle state for either the given * number of ticks or the maximum number of ticks that can be programmed * into hardware. * * @return N/A */ void _timer_idle_enter(int32_t ticks) { uint32_t status; if ((ticks == TICKS_UNLIMITED) || (ticks > max_system_ticks)) { /* * The number of cycles until the timer must fire next might not fit * in the 32-bit counter register. To work around this, program * the counter to fire in the maximum number of ticks. */ ticks = max_system_ticks; } programmed_ticks = ticks; programmed_limit = (programmed_ticks * cycles_per_tick) - 1; timer0_limit_register_set(programmed_limit); /* * If Timer0's IP bit is set, then it is known that we have straddled * a tick boundary while entering tickless idle. */ status = timer0_control_register_get(); if (status & _ARC_V2_TMR_CTRL_IP) { straddled_tick_on_idle_enter = true; } __ASSERT_EVAL({}, uint32_t timer_count = timer0_count_register_get(), timer_count <= programmed_limit, "timer_count: %d, limit %d\n", timer_count, programmed_limit); } /* * @brief handling of tickless idle when interrupted * * The routine, called by _SysPowerSaveIdleExit, is responsible for taking the * timer out of idle mode and generating an interrupt at the next tick * interval. It is expected that interrupts have been disabled. * * RETURNS: N/A */ void _timer_idle_exit(void) { if (straddled_tick_on_idle_enter) { /* Aborting the tickless idle due to a straddled tick. */ straddled_tick_on_idle_enter = false; __ASSERT_EVAL({}, uint32_t timer_count = timer0_count_register_get(), timer_count <= programmed_limit, "timer_count: %d, limit %d\n", timer_count, programmed_limit); return; } uint32_t control; uint32_t current_count; current_count = timer0_count_register_get(); control = timer0_control_register_get(); if (control & _ARC_V2_TMR_CTRL_IP) { /* * The timer has expired. The handler _timer_int_handler() is * guaranteed to execute. Track the number of elapsed ticks. The * handler _timer_int_handler() will account for the final tick. */ _sys_idle_elapsed_ticks = programmed_ticks - 1; update_accumulated_count(); _sys_clock_tick_announce(); __ASSERT_EVAL({}, uint32_t timer_count = timer0_count_register_get(), timer_count <= programmed_limit, "timer_count: %d, limit %d\n", timer_count, programmed_limit); return; } /* * A non-timer interrupt occurred. Announce any * ticks that have elapsed during the tickless idle. */ _sys_idle_elapsed_ticks = current_count / cycles_per_tick; if (_sys_idle_elapsed_ticks > 0) { update_accumulated_count(); _sys_clock_tick_announce(); } /* * Ensure the timer will expire at the end of the next tick in case * the ISR makes any tasks and/or fibers ready to run. */ timer0_limit_register_set(cycles_per_tick - 1); timer0_count_register_set(current_count % cycles_per_tick); __ASSERT_EVAL({}, uint32_t timer_count = timer0_count_register_get(), timer_count <= (cycles_per_tick - 1), "timer_count: %d, limit %d\n", timer_count, cycles_per_tick-1); } #else static void tickless_idle_init(void) {} #endif /* CONFIG_TICKLESS_IDLE */ /** * * @brief Initialize and enable the system clock * * This routine is used to program the ARCv2 timer to deliver interrupts at the * rate specified via the 'sys_clock_us_per_tick' global variable. * * @return 0 */ int _sys_clock_driver_init(struct device *device) { ARG_UNUSED(device); /* ensure that the timer will not generate interrupts */ timer0_control_register_set(0); timer0_count_register_set(0); cycles_per_tick = sys_clock_hw_cycles_per_tick; IRQ_CONNECT(IRQ_TIMER0, CONFIG_ARCV2_TIMER_IRQ_PRIORITY, _timer_int_handler, NULL, 0); /* * Set the reload value to achieve the configured tick rate, enable the * counter and interrupt generation. */ tickless_idle_init(); timer0_limit_register_set(cycles_per_tick - 1); timer0_control_register_set(_ARC_V2_TMR_CTRL_NH | _ARC_V2_TMR_CTRL_IE); /* everything has been configured: safe to enable the interrupt */ irq_enable(IRQ_TIMER0); return 0; } /** * * @brief Read the platform's timer hardware * * This routine returns the current time in terms of timer hardware clock * cycles. * * @return up counter of elapsed clock cycles */ uint32_t sys_cycle_get_32(void) { return (accumulated_cycle_count + timer0_count_register_get()); } #if defined(CONFIG_SYSTEM_CLOCK_DISABLE) /** * * @brief Stop announcing ticks into the kernel * * This routine disables timer interrupt generation and delivery. * Note that the timer's counting cannot be stopped by software. * * @return N/A */ void sys_clock_disable(void) { unsigned int key; /* interrupt lock level */ uint32_t control; /* timer control register value */ key = irq_lock(); /* disable interrupt generation */ control = timer0_control_register_get(); timer0_control_register_set(control & ~_ARC_V2_TMR_CTRL_IE); irq_unlock(key); /* disable interrupt in the interrupt controller */ irq_disable(CONFIG_ARCV2_TIMER0_INT_LVL); } #endif /* CONFIG_SYSTEM_CLOCK_DISABLE */