/**************************************************************************** * drivers/timers/arch_timer.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you 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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #if defined(CONFIG_SCHED_TICKLESS) && defined(CONFIG_SCHED_TICKLESS_ALARM) # error CONFIG_SCHED_TICKLESS_ALARM must be unset to use the arch timer #endif #define CONFIG_BOARD_LOOPSPER100USEC ((CONFIG_BOARD_LOOPSPERMSEC+5)/10) #define CONFIG_BOARD_LOOPSPER10USEC ((CONFIG_BOARD_LOOPSPERMSEC+50)/100) #define CONFIG_BOARD_LOOPSPERUSEC ((CONFIG_BOARD_LOOPSPERMSEC+500)/1000) #define TIMER_START(l) ((l)->ops->start(l)) #define TIMER_GETSTATUS(l,s) ((l)->ops->getstatus(l,s)) #define TIMER_SETTIMEOUT(l,t) ((l)->ops->settimeout(l,t)) #define TIMER_SETCALLBACK(l,c,a) ((l)->ops->setcallback(l,c,a)) #define TIMER_MAXTIMEOUT(l,t) ((l)->ops->maxtimeout(l,t)) /**************************************************************************** * Private Types ****************************************************************************/ struct arch_timer_s { FAR struct timer_lowerhalf_s *lower; uint32_t *next_interval; uint32_t maxtimeout; uint64_t timebase; }; /**************************************************************************** * Private Data ****************************************************************************/ static struct arch_timer_s g_timer; /**************************************************************************** * Private Functions ****************************************************************************/ #if defined(CONFIG_SCHED_TICKLESS) || defined(CONFIG_SCHED_CRITMONITOR) \ || defined(CONFIG_SCHED_IRQMONITOR_GETTIME) static inline void timespec_from_usec(FAR struct timespec *ts, uint64_t microseconds) { ts->tv_sec = microseconds / USEC_PER_SEC; microseconds -= (uint64_t)ts->tv_sec * USEC_PER_SEC; ts->tv_nsec = microseconds * NSEC_PER_USEC; } #endif #ifdef CONFIG_SCHED_TICKLESS static inline uint64_t timespec_to_usec(const FAR struct timespec *ts) { return (uint64_t)ts->tv_sec * USEC_PER_SEC + ts->tv_nsec / NSEC_PER_USEC; } static inline bool timeout_diff(uint32_t new, uint32_t old) { return new < old ? old - new >= USEC_PER_TICK : new - old >= USEC_PER_TICK; } static uint32_t update_timeout(uint32_t timeout) { struct timer_status_s status; /* Don't need critical section here * since caller already do it for us */ TIMER_GETSTATUS(g_timer.lower, &status); if (g_timer.next_interval) { /* If the timer interrupt is in the process, * let the callback return the right interval. */ *g_timer.next_interval = timeout; } else if (timeout_diff(timeout, status.timeleft)) { /* Otherwise, update the timeout directly. */ TIMER_SETTIMEOUT(g_timer.lower, timeout); g_timer.timebase += status.timeout - status.timeleft; } return status.timeleft; } #endif static uint64_t current_usec(void) { struct timer_status_s status; uint64_t timebase; do { timebase = g_timer.timebase; TIMER_GETSTATUS(g_timer.lower, &status); } while (timebase != g_timer.timebase); return timebase + (status.timeout - status.timeleft); } static void udelay_accurate(useconds_t microseconds) { uint64_t start = current_usec(); while (current_usec() - start < microseconds) { ; /* Wait until the timeout reach */ } } static void udelay_coarse(useconds_t microseconds) { volatile int i; /* We'll do this a little at a time because we expect that the * CONFIG_BOARD_LOOPSPERUSEC is very inaccurate during to truncation in * the divisions of its calculation. We'll use the largest values that * we can in order to prevent significant error buildup in the loops. */ while (microseconds > 1000) { for (i = 0; i < CONFIG_BOARD_LOOPSPERMSEC; i++) { } microseconds -= 1000; } while (microseconds > 100) { for (i = 0; i < CONFIG_BOARD_LOOPSPER100USEC; i++) { } microseconds -= 100; } while (microseconds > 10) { for (i = 0; i < CONFIG_BOARD_LOOPSPER10USEC; i++) { } microseconds -= 10; } while (microseconds > 0) { for (i = 0; i < CONFIG_BOARD_LOOPSPERUSEC; i++) { } microseconds--; } } static bool timer_callback(FAR uint32_t *next_interval_us, FAR void *arg) { #ifdef CONFIG_SCHED_TICKLESS struct timer_status_s status; uint32_t next_interval; g_timer.timebase += *next_interval_us; next_interval = g_timer.maxtimeout; g_timer.next_interval = &next_interval; nxsched_timer_expiration(); g_timer.next_interval = NULL; TIMER_GETSTATUS(g_timer.lower, &status); if (timeout_diff(next_interval, status.timeleft)) { g_timer.timebase += status.timeout - status.timeleft; *next_interval_us = next_interval; } #else g_timer.timebase += USEC_PER_TICK; nxsched_process_timer(); #endif return true; } /**************************************************************************** * Public Functions ****************************************************************************/ void up_timer_set_lowerhalf(FAR struct timer_lowerhalf_s *lower) { g_timer.lower = lower; TIMER_MAXTIMEOUT(g_timer.lower, &g_timer.maxtimeout); #ifdef CONFIG_SCHED_TICKLESS g_oneshot_maxticks = g_timer.maxtimeout / USEC_PER_TICK; TIMER_SETTIMEOUT(g_timer.lower, g_timer.maxtimeout); #else TIMER_SETTIMEOUT(g_timer.lower, USEC_PER_TICK); #endif TIMER_SETCALLBACK(g_timer.lower, timer_callback, NULL); TIMER_START(g_timer.lower); } /**************************************************************************** * Name: up_timer_gettime * * Description: * Return the elapsed time since power-up (or, more correctly, since * the architecture-specific timer was initialized). This function is * functionally equivalent to: * * int clock_gettime(clockid_t clockid, FAR struct timespec *ts); * * when clockid is CLOCK_MONOTONIC. * * This function provides the basis for reporting the current time and * also is used to eliminate error build-up from small errors in interval * time calculations. * * Provided by platform-specific code and called from the RTOS base code. * * Input Parameters: * ts - Provides the location in which to return the up-time. * * Returned Value: * Zero (OK) is returned on success; a negated errno value is returned on * any failure. * * Assumptions: * Called from the normal tasking context. The implementation must * provide whatever mutual exclusion is necessary for correct operation. * This can include disabling interrupts in order to assure atomic register * operations. * ****************************************************************************/ #ifdef CONFIG_CLOCK_TIMEKEEPING int up_timer_getcounter(FAR uint64_t *cycles) { int ret = -EAGAIN; if (g_timer.lower != NULL) { *cycles = current_usec() / USEC_PER_TICK; ret = 0; } return ret; } void up_timer_getmask(FAR uint64_t *mask) { uint32_t maxticks = g_timer.maxtimeout / USEC_PER_TICK; *mask = 0; while (1) { uint64_t next = (*mask << 1) | 1; if (next > maxticks) { break; } *mask = next; } } #endif #if defined(CONFIG_SCHED_TICKLESS) int up_timer_gettime(FAR struct timespec *ts) { int ret = -EAGAIN; if (g_timer.lower != NULL) { timespec_from_usec(ts, current_usec()); ret = 0; } return ret; } #endif /**************************************************************************** * Name: up_timer_cancel * * Description: * Cancel the interval timer and return the time remaining on the timer. * These two steps need to be as nearly atomic as possible. * nxsched_timer_expiration() will not be called unless the timer is * restarted with up_timer_start(). * * If, as a race condition, the timer has already expired when this * function is called, then that pending interrupt must be cleared so * that up_timer_start() and the remaining time of zero should be * returned. * * NOTE: This function may execute at a high rate with no timer running (as * when pre-emption is enabled and disabled). * * Provided by platform-specific code and called from the RTOS base code. * * Input Parameters: * ts - Location to return the remaining time. Zero should be returned * if the timer is not active. ts may be zero in which case the * time remaining is not returned. * * Returned Value: * Zero (OK) is returned on success. A call to up_timer_cancel() when * the timer is not active should also return success; a negated errno * value is returned on any failure. * * Assumptions: * May be called from interrupt level handling or from the normal tasking * level. Interrupts may need to be disabled internally to assure * non-reentrancy. * ****************************************************************************/ #ifdef CONFIG_SCHED_TICKLESS int up_timer_cancel(FAR struct timespec *ts) { int ret = -EAGAIN; if (g_timer.lower != NULL) { timespec_from_usec(ts, update_timeout(g_timer.maxtimeout)); ret = 0; } return ret; } #endif /**************************************************************************** * Name: up_timer_start * * Description: * Start the interval timer. nxsched_timer_expiration() will be called at * the completion of the timeout (unless up_timer_cancel is called to stop * the timing. * * Provided by platform-specific code and called from the RTOS base code. * * Input Parameters: * ts - Provides the time interval until nxsched_timer_expiration() is * called. * * Returned Value: * Zero (OK) is returned on success; a negated errno value is returned on * any failure. * * Assumptions: * May be called from interrupt level handling or from the normal tasking * level. Interrupts may need to be disabled internally to assure * non-reentrancy. * ****************************************************************************/ #ifdef CONFIG_SCHED_TICKLESS int up_timer_start(FAR const struct timespec *ts) { int ret = -EAGAIN; if (g_timer.lower != NULL) { update_timeout(timespec_to_usec(ts)); ret = 0; } return ret; } #endif /**************************************************************************** * Name: up_critmon_* * * Description: * The first interface simply provides the current time value in unknown * units. NOTE: This function may be called early before the timer has * been initialized. In that event, the function should just return a * start time of zero. * * Nothing is assumed about the units of this time value. The following * are assumed, however: (1) The time is an unsigned integer value, (2) * the time is monotonically increasing, and (3) the elapsed time (also * in unknown units) can be obtained by subtracting a start time from * the current time. * * The second interface simple converts an elapsed time into well known * units. ****************************************************************************/ #ifdef CONFIG_SCHED_CRITMONITOR uint32_t up_critmon_gettime(void) { uint32_t ret = 0; if (g_timer.lower != NULL) { ret = current_usec(); } return ret; } void up_critmon_convert(uint32_t elapsed, FAR struct timespec *ts) { timespec_from_usec(ts, elapsed); } #endif /**************************************************************************** * Name: up_mdelay * * Description: * Delay inline for the requested number of milliseconds. * *** NOT multi-tasking friendly *** * ****************************************************************************/ void up_mdelay(unsigned int milliseconds) { up_udelay(USEC_PER_MSEC * milliseconds); } /**************************************************************************** * Name: up_udelay * * Description: * Delay inline for the requested number of microseconds. * * *** NOT multi-tasking friendly *** * ****************************************************************************/ void up_udelay(useconds_t microseconds) { if (g_timer.lower != NULL) { udelay_accurate(microseconds); } else /* Period timer hasn't been initialized yet */ { udelay_coarse(microseconds); } }