| /**************************************************************************** |
| * arch/arm/src/stm32h7/stm32_tickless.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. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Tickless OS Support. |
| * |
| * When CONFIG_SCHED_TICKLESS is enabled, all support for timer interrupts |
| * is suppressed and the platform specific code is expected to provide the |
| * following custom functions. |
| * |
| * void up_timer_initialize(void): Initializes the timer facilities. |
| * Called early in the initialization sequence (by up_initialize()). |
| * int up_timer_gettime(struct timespec *ts): Returns the current |
| * time from the platform specific time source. |
| * int up_timer_cancel(void): Cancels the interval timer. |
| * int up_timer_start(const struct timespec *ts): Start (or re-starts) |
| * the interval timer. |
| * |
| * The RTOS will provide the following interfaces for use by the platform- |
| * specific interval timer implementation: |
| * |
| * void nxsched_timer_expiration(void): Called by the platform-specific |
| * logic when the interval timer expires. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * STM32 Timer Usage |
| * |
| * This implementation uses one timer: A free running timer to provide |
| * the current time and a capture/compare channel for timed-events. |
| * |
| * BASIC timers that are found on some STM32 chips (timers 6 and 7) are |
| * incompatible with this implementation because they don't have capture/ |
| * compare channels. There are two interrupts generated from our timer, |
| * the overflow interrupt which drives the timing handler and the capture/ |
| * compare interrupt which drives the interval handler. There are some low |
| * level timer control functions implemented here because the API of |
| * stm32_tim.c does not provide adequate control over capture/compare |
| * interrupts. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <errno.h> |
| #include <assert.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/timers/arch_timer.h> |
| #include <debug.h> |
| #include <arch/board/board.h> |
| |
| #include "arm_internal.h" |
| |
| #include "stm32_tim.h" |
| #include "stm32_dbgmcu.h" |
| |
| #include "systick.h" |
| |
| #ifdef CONFIG_SCHED_TICKLESS |
| |
| /* Only TIM2 and TIM5 timers may be 32-bits in width */ |
| |
| #undef HAVE_32BIT_TICKLESS |
| |
| #if (CONFIG_STM32H7_TICKLESS_TIMER == 2) || \ |
| (CONFIG_STM32H7_TICKLESS_TIMER == 5) |
| #define HAVE_32BIT_TICKLESS 1 |
| #endif |
| |
| #if (CONFIG_STM32H7_TICKLESS_TIMER == 6) || \ |
| (CONFIG_STM32H7_TICKLESS_TIMER == 7) |
| # error Basic timers not supported by the tickless driver |
| #endif |
| |
| #if CONFIG_STM32H7_TICKLESS_CHANNEL == 1 |
| #define DIER_CAPT_IE GTIM_DIER_CC1IE |
| #elif CONFIG_STM32H7_TICKLESS_CHANNEL == 2 |
| #define DIER_CAPT_IE GTIM_DIER_CC2IE |
| #elif CONFIG_STM32H7_TICKLESS_CHANNEL == 3 |
| #define DIER_CAPT_IE GTIM_DIER_CC3IE |
| #elif CONFIG_STM32H7_TICKLESS_CHANNEL == 4 |
| #define DIER_CAPT_IE GTIM_DIER_CC4IE |
| #endif |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct stm32_tickless_s |
| { |
| uint8_t timer; /* The timer/counter in use */ |
| uint8_t channel; /* The timer channel to use for intervals */ |
| struct stm32_tim_dev_s *tch; /* Handle returned by stm32_tim_init() */ |
| uint32_t frequency; |
| uint32_t overflow; /* Timer counter overflow */ |
| volatile bool pending; /* True: pending task */ |
| uint32_t period; /* Interval period */ |
| uint32_t base; |
| #ifdef CONFIG_SCHED_TICKLESS_ALARM |
| uint64_t last_alrm; |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static struct stm32_tickless_s g_tickless; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: stm32_getreg16 |
| * |
| * Description: |
| * Get a 16-bit register value by offset |
| * |
| ****************************************************************************/ |
| |
| static inline uint16_t stm32_getreg16(uint8_t offset) |
| { |
| return getreg16(g_tickless.base + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_putreg16 |
| * |
| * Description: |
| * Put a 16-bit register value by offset |
| * |
| ****************************************************************************/ |
| |
| static inline void stm32_putreg16(uint8_t offset, uint16_t value) |
| { |
| putreg16(value, g_tickless.base + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_modifyreg16 |
| * |
| * Description: |
| * Modify a 16-bit register value by offset |
| * |
| ****************************************************************************/ |
| |
| static inline void stm32_modifyreg16(uint8_t offset, uint16_t clearbits, |
| uint16_t setbits) |
| { |
| modifyreg16(g_tickless.base + offset, clearbits, setbits); |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_tickless_enableint |
| ****************************************************************************/ |
| |
| static inline void stm32_tickless_enableint(int channel) |
| { |
| stm32_modifyreg16(STM32_BTIM_DIER_OFFSET, 0, 1 << channel); |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_tickless_disableint |
| ****************************************************************************/ |
| |
| static inline void stm32_tickless_disableint(int channel) |
| { |
| stm32_modifyreg16(STM32_BTIM_DIER_OFFSET, 1 << channel, 0); |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_tickless_ackint |
| ****************************************************************************/ |
| |
| static inline void stm32_tickless_ackint(int channel) |
| { |
| stm32_putreg16(STM32_BTIM_SR_OFFSET, ~(1 << channel)); |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_tickless_getint |
| ****************************************************************************/ |
| |
| static inline uint16_t stm32_tickless_getint(void) |
| { |
| return stm32_getreg16(STM32_BTIM_SR_OFFSET); |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_tickless_setchannel |
| ****************************************************************************/ |
| |
| static int stm32_tickless_setchannel(uint8_t channel) |
| { |
| uint16_t ccmr_orig = 0; |
| uint16_t ccmr_val = 0; |
| uint16_t ccmr_mask = 0xff; |
| uint16_t ccer_val = stm32_getreg16(STM32_GTIM_CCER_OFFSET); |
| uint8_t ccmr_offset = STM32_GTIM_CCMR1_OFFSET; |
| |
| /* Further we use range as 0..3; if channel=0 it will also overflow here */ |
| |
| if (--channel > 4) |
| { |
| return -EINVAL; |
| } |
| |
| /* Assume that channel is disabled and polarity is active high */ |
| |
| ccer_val &= ~((GTIM_CCER_CC1P | GTIM_CCER_CC1E) << |
| GTIM_CCER_CCXBASE(channel)); |
| |
| /* This function is not supported on basic timers. To enable or |
| * disable it, simply set its clock to valid frequency or zero. |
| */ |
| |
| if (g_tickless.base == STM32_TIM6_BASE || |
| g_tickless.base == STM32_TIM7_BASE) |
| { |
| return -EINVAL; |
| } |
| |
| /* Frozen mode because we don't want to change the GPIO, preload register |
| * disabled. |
| */ |
| |
| ccmr_val = (GTIM_CCMR_MODE_FRZN << GTIM_CCMR1_OC1M_SHIFT); |
| |
| /* Set polarity */ |
| |
| ccer_val |= GTIM_CCER_CC1P << GTIM_CCER_CCXBASE(channel); |
| |
| /* Define its position (shift) and get register offset */ |
| |
| if ((channel & 1) != 0) |
| { |
| ccmr_val <<= 8; |
| ccmr_mask <<= 8; |
| } |
| |
| if (channel > 1) |
| { |
| ccmr_offset = STM32_GTIM_CCMR2_OFFSET; |
| } |
| |
| ccmr_orig = stm32_getreg16(ccmr_offset); |
| ccmr_orig &= ~ccmr_mask; |
| ccmr_orig |= ccmr_val; |
| stm32_putreg16(ccmr_offset, ccmr_orig); |
| stm32_putreg16(STM32_GTIM_CCER_OFFSET, ccer_val); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_interval_handler |
| * |
| * Description: |
| * Called when the timer counter matches the compare register |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Called early in the initialization sequence before any special |
| * concurrency protections are required. |
| * |
| ****************************************************************************/ |
| |
| static void stm32_interval_handler(void) |
| { |
| #ifdef CONFIG_SCHED_TICKLESS_ALARM |
| struct timespec tv; |
| #endif |
| tmrinfo("Expired...\n"); |
| |
| /* Disable the compare interrupt now. */ |
| |
| stm32_tickless_disableint(g_tickless.channel); |
| stm32_tickless_ackint(g_tickless.channel); |
| |
| g_tickless.pending = false; |
| |
| #ifndef CONFIG_SCHED_TICKLESS_ALARM |
| nxsched_timer_expiration(); |
| #else |
| up_timer_gettime(&tv); |
| nxsched_alarm_expiration(&tv); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_timing_handler |
| * |
| * Description: |
| * Timer interrupt callback. When the freerun timer counter overflows, |
| * this interrupt will occur. We will just increment an overflow count. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void stm32_timing_handler(void) |
| { |
| g_tickless.overflow++; |
| |
| STM32_TIM_ACKINT(g_tickless.tch, GTIM_SR_UIF); |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_tickless_handler |
| * |
| * Description: |
| * Generic interrupt handler for this timer. It checks the source of the |
| * interrupt and fires the appropriate handler. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static int stm32_tickless_handler(int irq, void *context, void *arg) |
| { |
| int interrupt_flags = stm32_tickless_getint(); |
| |
| if (interrupt_flags & GTIM_SR_UIF) |
| { |
| stm32_timing_handler(); |
| } |
| |
| if (interrupt_flags & (1 << g_tickless.channel)) |
| { |
| stm32_interval_handler(); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: stm32_get_counter |
| * |
| ****************************************************************************/ |
| |
| static uint64_t stm32_get_counter(void) |
| { |
| #ifdef HAVE_32BIT_TICKLESS |
| return ((uint64_t)g_tickless.overflow << 32) | |
| STM32_TIM_GETCOUNTER(g_tickless.tch); |
| #else |
| return ((uint64_t)g_tickless.overflow << 16) | |
| STM32_TIM_GETCOUNTER(g_tickless.tch); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: up_timer_initialize |
| * |
| * Description: |
| * Initializes all platform-specific timer facilities. This function is |
| * called early in the initialization sequence by up_initialize(). |
| * On return, the current up-time should be available from |
| * up_timer_gettime() and the interval timer is ready for use (but not |
| * actively timing. |
| * |
| * Provided by platform-specific code and called from the architecture- |
| * specific logic. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Called early in the initialization sequence before any special |
| * concurrency protections are required. |
| * |
| ****************************************************************************/ |
| |
| void up_timer_initialize(void) |
| { |
| switch (CONFIG_STM32H7_TICKLESS_TIMER) |
| { |
| #ifdef CONFIG_STM32H7_TIM1 |
| case 1: |
| g_tickless.base = STM32_TIM1_BASE; |
| modifyreg32(STM32_DBGMCU_APB2FZ1, 0, DBGMCU_APB2Z1_TIM1STOP); |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_TIM2 |
| case 2: |
| g_tickless.base = STM32_TIM2_BASE; |
| modifyreg32(STM32_DBGMCU_APB1LFZ1, 0, DBGMCU_APB1L_TIM2STOP); |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_TIM3 |
| case 3: |
| g_tickless.base = STM32_TIM3_BASE; |
| modifyreg32(STM32_DBGMCU_APB1LFZ1, 0, DBGMCU_APB1L_TIM3STOP); |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_TIM4 |
| case 4: |
| g_tickless.base = STM32_TIM4_BASE; |
| modifyreg32(STM32_DBGMCU_APB1LFZ1, 0, DBGMCU_APB1L_TIM4STOP); |
| break; |
| #endif |
| #ifdef CONFIG_STM32H7_TIM5 |
| case 5: |
| g_tickless.base = STM32_TIM5_BASE; |
| modifyreg32(STM32_DBGMCU_APB1LFZ1, 0, DBGMCU_APB1L_TIM5STOP); |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_TIM8 |
| case 8: |
| g_tickless.base = STM32_TIM8_BASE; |
| modifyreg32(STM32_DBGMCU_APB2FZ1, 0, DBGMCU_APB2Z1_TIM8STOP); |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_TIM9 |
| case 9: |
| g_tickless.base = STM32_TIM9_BASE; |
| |
| /* A freeze bit for TIM9 doesn't seem to exist */ |
| |
| break; |
| #endif |
| #ifdef CONFIG_STM32H7_TIM10 |
| case 10: |
| g_tickless.base = STM32_TIM10_BASE; |
| |
| /* A freeze bit for TIM10 doesn't seem to exist */ |
| |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_TIM11 |
| case 11: |
| g_tickless.base = STM32_TIM11_BASE; |
| |
| /* A freeze bit for TIM11 doesn't seem to exist */ |
| |
| break; |
| #endif |
| #ifdef CONFIG_STM32H7_TIM12 |
| case 12: |
| g_tickless.base = STM32_TIM12_BASE; |
| modifyreg32(STM32_DBGMCU_APB1LFZ1, 0, DBGMCU_APB1L_TIM12STOP); |
| break; |
| #endif |
| #ifdef CONFIG_STM32H7_TIM13 |
| case 13: |
| g_tickless.base = STM32_TIM13_BASE; |
| modifyreg32(STM32_DBGMCU_APB1LFZ1, 0, DBGMCU_APB1L_TIM13STOP); |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_TIM14 |
| case 14: |
| g_tickless.base = STM32_TIM14_BASE; |
| modifyreg32(STM32_DBGMCU_APB1LFZ1, 0, DBGMCU_APB1L_TIM14STOP); |
| break; |
| #endif |
| #ifdef CONFIG_STM32H7_TIM15 |
| case 15: |
| g_tickless.base = STM32_TIM15_BASE; |
| modifyreg32(STM32_DBGMCU_APB2FZ1, 0, DBGMCU_APB2Z1_TIM15STOP); |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_TIM16 |
| case 16: |
| g_tickless.base = STM32_TIM16_BASE; |
| modifyreg32(STM32_DBGMCU_APB2FZ1, 0, DBGMCU_APB2Z1_TIM16STOP); |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_TIM17 |
| case 17: |
| g_tickless.base = STM32_TIM17_BASE; |
| modifyreg32(STM32_DBGMCU_APB2FZ1, 0, DBGMCU_APB2Z1_TIM17STOP); |
| break; |
| #endif |
| |
| default: |
| DEBUGPANIC(); |
| } |
| |
| /* Get the TC frequency that corresponds to the requested resolution */ |
| |
| g_tickless.frequency = USEC_PER_SEC / (uint32_t)CONFIG_USEC_PER_TICK; |
| g_tickless.timer = CONFIG_STM32H7_TICKLESS_TIMER; |
| g_tickless.channel = CONFIG_STM32H7_TICKLESS_CHANNEL; |
| g_tickless.pending = false; |
| g_tickless.period = 0; |
| g_tickless.overflow = 0; |
| |
| tmrinfo("timer=%d channel=%d frequency=%lu Hz\n", |
| g_tickless.timer, g_tickless.channel, g_tickless.frequency); |
| |
| g_tickless.tch = stm32_tim_init(g_tickless.timer); |
| if (g_tickless.tch == NULL) |
| { |
| tmrerr("ERROR: Failed to allocate TIM%d\n", g_tickless.timer); |
| DEBUGPANIC(); |
| } |
| |
| STM32_TIM_SETCLOCK(g_tickless.tch, g_tickless.frequency); |
| |
| /* Set up to receive the callback when the counter overflow occurs */ |
| |
| STM32_TIM_SETISR(g_tickless.tch, stm32_tickless_handler, NULL, 0); |
| |
| /* Initialize interval to zero */ |
| |
| STM32_TIM_SETCOMPARE(g_tickless.tch, g_tickless.channel, 0); |
| |
| /* Setup compare channel for the interval timing */ |
| |
| stm32_tickless_setchannel(g_tickless.channel); |
| |
| /* Set timer period */ |
| |
| #ifdef HAVE_32BIT_TICKLESS |
| STM32_TIM_SETPERIOD(g_tickless.tch, UINT32_MAX); |
| #ifdef CONFIG_SCHED_TICKLESS_LIMIT_MAX_SLEEP |
| g_oneshot_maxticks = UINT32_MAX; |
| #endif |
| #else |
| STM32_TIM_SETPERIOD(g_tickless.tch, UINT16_MAX); |
| #ifdef CONFIG_SCHED_TICKLESS_LIMIT_MAX_SLEEP |
| g_oneshot_maxticks = UINT16_MAX; |
| #endif |
| #endif |
| |
| /* Initialize the counter */ |
| |
| STM32_TIM_SETMODE(g_tickless.tch, STM32_TIM_MODE_UP); |
| |
| /* Start the timer */ |
| |
| STM32_TIM_ACKINT(g_tickless.tch, ~0); |
| STM32_TIM_ENABLEINT(g_tickless.tch, 0); |
| |
| #if defined(CONFIG_ARMV7M_SYSTICK) && defined(CONFIG_CPULOAD_PERIOD) |
| nxsched_period_extclk(systick_initialize(true, |
| STM32_CPUCLK_FREQUENCY, -1)); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: up_timer_gettime |
| * |
| * Description: |
| * Return the elapsed time since power-up (or, more correctly, since |
| * up_timer_initialize() was called). This function is functionally |
| * equivalent to: |
| * |
| * int clock_gettime(clockid_t clockid, 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. |
| * |
| ****************************************************************************/ |
| |
| int up_timer_gettime(struct timespec *ts) |
| { |
| uint64_t usec; |
| uint32_t counter; |
| uint32_t verify; |
| uint32_t overflow; |
| uint32_t sec; |
| int pending; |
| irqstate_t flags; |
| |
| DEBUGASSERT(ts); |
| |
| /* Timer not initialized yet, return zero */ |
| |
| if (g_tickless.tch == NULL) |
| { |
| ts->tv_nsec = 0; |
| ts->tv_sec = 0; |
| return OK; |
| } |
| |
| /* Temporarily disable the overflow counter. NOTE that we have to be |
| * careful here because stm32_tc_getpending() will reset the pending |
| * interrupt status. If we do not handle the overflow here then, it will |
| * be lost. |
| */ |
| |
| flags = enter_critical_section(); |
| |
| overflow = g_tickless.overflow; |
| counter = STM32_TIM_GETCOUNTER(g_tickless.tch); |
| pending = STM32_TIM_CHECKINT(g_tickless.tch, GTIM_SR_UIF); |
| verify = STM32_TIM_GETCOUNTER(g_tickless.tch); |
| |
| /* If an interrupt was pending before we re-enabled interrupts, |
| * then the overflow needs to be incremented. |
| */ |
| |
| if (pending) |
| { |
| STM32_TIM_ACKINT(g_tickless.tch, GTIM_SR_UIF); |
| |
| /* Increment the overflow count and use the value of the |
| * guaranteed to be AFTER the overflow occurred. |
| */ |
| |
| overflow++; |
| counter = verify; |
| |
| /* Update tickless overflow counter. */ |
| |
| g_tickless.overflow = overflow; |
| } |
| |
| leave_critical_section(flags); |
| |
| tmrinfo("counter=%lu (%lu) overflow=%lu, pending=%i\n", |
| (unsigned long)counter, (unsigned long)verify, |
| (unsigned long)overflow, pending); |
| tmrinfo("frequency=%lu\n", g_tickless.frequency); |
| |
| /* Convert the whole thing to units of microseconds. |
| * |
| * frequency = ticks / second |
| * seconds = ticks * frequency |
| * usecs = (ticks * USEC_PER_SEC) / frequency; |
| */ |
| #ifdef HAVE_32BIT_TICKLESS |
| usec = ((((uint64_t)overflow << 32) + (uint64_t)counter) * USEC_PER_SEC) / |
| g_tickless.frequency; |
| #else |
| usec = ((((uint64_t)overflow << 16) + (uint64_t)counter) * USEC_PER_SEC) / |
| g_tickless.frequency; |
| #endif |
| |
| /* And return the value of the timer */ |
| |
| sec = (uint32_t)(usec / USEC_PER_SEC); |
| ts->tv_sec = sec; |
| ts->tv_nsec = (usec - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; |
| |
| tmrinfo("usec=%llu ts=(%lu, %lu)\n", |
| usec, (unsigned long)ts->tv_sec, (unsigned long)ts->tv_nsec); |
| |
| return OK; |
| } |
| |
| #ifdef CONFIG_CLOCK_TIMEKEEPING |
| |
| /**************************************************************************** |
| * Name: up_timer_gettick |
| * |
| * Description: |
| * To be provided |
| * |
| * Input Parameters: |
| * cycles - 64-bit return value |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| int up_timer_gettick(clock_t *ticks) |
| { |
| *ticks = (clock_t)STM32_TIM_GETCOUNTER(g_tickless.tch); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: up_timer_getmask |
| * |
| * Description: |
| * To be provided |
| * |
| * Input Parameters: |
| * mask - Location to return the 64-bit mask |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void up_timer_getmask(clock_t *mask) |
| { |
| DEBUGASSERT(mask != NULL); |
| #ifdef HAVE_32BIT_TICKLESS |
| *mask = UINT32_MAX; |
| #else |
| *mask = UINT16_MAX; |
| #endif |
| } |
| |
| #endif /* CONFIG_CLOCK_TIMEKEEPING */ |
| |
| /**************************************************************************** |
| * 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. |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_SCHED_TICKLESS_ALARM |
| int up_timer_cancel(struct timespec *ts) |
| { |
| irqstate_t flags; |
| uint64_t usec; |
| uint64_t sec; |
| uint64_t nsec; |
| uint32_t count; |
| uint32_t period; |
| |
| /* Was the timer running? */ |
| |
| flags = enter_critical_section(); |
| if (!g_tickless.pending) |
| { |
| /* No.. Just return zero timer remaining and successful cancellation. |
| * This function may execute at a high rate with no timer running |
| * (as when pre-emption is enabled and disabled). |
| */ |
| |
| if (ts != NULL) |
| { |
| ts->tv_sec = 0; |
| ts->tv_nsec = 0; |
| } |
| |
| leave_critical_section(flags); |
| return OK; |
| } |
| |
| /* Yes.. Get the timer counter and period registers and disable the compare |
| * interrupt. |
| */ |
| |
| tmrinfo("Cancelling...\n"); |
| |
| /* Disable the interrupt. */ |
| |
| stm32_tickless_disableint(g_tickless.channel); |
| |
| count = STM32_TIM_GETCOUNTER(g_tickless.tch); |
| period = g_tickless.period; |
| |
| g_tickless.pending = false; |
| leave_critical_section(flags); |
| |
| /* Did the caller provide us with a location to return the time |
| * remaining? |
| */ |
| |
| if (ts != NULL) |
| { |
| /* Yes.. then calculate and return the time remaining on the |
| * oneshot timer. |
| */ |
| |
| tmrinfo("period=%lu count=%lu\n", |
| (unsigned long)period, (unsigned long)count); |
| |
| #ifndef HAVE_32BIT_TICKLESS |
| if (count > period) |
| { |
| /* Handle rollover */ |
| |
| period += UINT16_MAX; |
| } |
| else if (count == period) |
| #else |
| if (count >= period) |
| #endif |
| { |
| /* No time remaining */ |
| |
| ts->tv_sec = 0; |
| ts->tv_nsec = 0; |
| return OK; |
| } |
| |
| /* The total time remaining is the difference. Convert that |
| * to units of microseconds. |
| * |
| * frequency = ticks / second |
| * seconds = ticks * frequency |
| * usecs = (ticks * USEC_PER_SEC) / frequency; |
| */ |
| |
| usec = (((uint64_t)(period - count)) * USEC_PER_SEC) / |
| g_tickless.frequency; |
| |
| /* Return the time remaining in the correct form */ |
| |
| sec = usec / USEC_PER_SEC; |
| nsec = ((usec) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; |
| |
| ts->tv_sec = (time_t)sec; |
| ts->tv_nsec = (unsigned long)nsec; |
| |
| tmrinfo("remaining (%lu, %lu)\n", |
| (unsigned long)ts->tv_sec, (unsigned long)ts->tv_nsec); |
| } |
| |
| return OK; |
| } |
| #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. |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_SCHED_TICKLESS_ALARM |
| int up_timer_start(const struct timespec *ts) |
| { |
| uint64_t usec; |
| uint64_t period; |
| uint32_t count; |
| irqstate_t flags; |
| |
| tmrinfo("ts=(%lu, %lu)\n", |
| (unsigned long)ts->tv_sec, (unsigned long)ts->tv_nsec); |
| DEBUGASSERT(ts); |
| DEBUGASSERT(g_tickless.tch); |
| |
| /* Was an interval already running? */ |
| |
| flags = enter_critical_section(); |
| if (g_tickless.pending) |
| { |
| /* Yes.. then cancel it */ |
| |
| tmrinfo("Already running... cancelling\n"); |
| up_timer_cancel(NULL); |
| } |
| |
| /* Express the delay in microseconds */ |
| |
| usec = (uint64_t)ts->tv_sec * USEC_PER_SEC + |
| (uint64_t)(ts->tv_nsec / NSEC_PER_USEC); |
| |
| /* Get the timer counter frequency and determine the number of counts need |
| * to achieve the requested delay. |
| * |
| * frequency = ticks / second |
| * ticks = seconds * frequency |
| * = (usecs * frequency) / USEC_PER_SEC; |
| */ |
| |
| period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC; |
| count = STM32_TIM_GETCOUNTER(g_tickless.tch); |
| |
| tmrinfo("usec=%llu period=%08llx\n", usec, period); |
| |
| /* Set interval compare value. Rollover is fine, |
| * channel will trigger on the next period. |
| */ |
| |
| #ifdef HAVE_32BIT_TICKLESS |
| DEBUGASSERT(period <= UINT32_MAX); |
| g_tickless.period = (uint32_t)(period + count); |
| #else |
| DEBUGASSERT(period <= UINT16_MAX); |
| g_tickless.period = (uint16_t)(period + count); |
| #endif |
| |
| STM32_TIM_SETCOMPARE(g_tickless.tch, g_tickless.channel, |
| g_tickless.period); |
| |
| /* Enable interrupts. We should get the callback when the interrupt |
| * occurs. |
| */ |
| |
| stm32_tickless_ackint(g_tickless.channel); |
| stm32_tickless_enableint(g_tickless.channel); |
| |
| g_tickless.pending = true; |
| leave_critical_section(flags); |
| return OK; |
| } |
| #endif |
| |
| #ifdef CONFIG_SCHED_TICKLESS_ALARM |
| int up_alarm_start(const struct timespec *ts) |
| { |
| size_t offset = 1; |
| uint64_t tm = ((uint64_t)ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec) / |
| NSEC_PER_TICK; |
| irqstate_t flags; |
| |
| flags = enter_critical_section(); |
| |
| STM32_TIM_SETCOMPARE(g_tickless.tch, CONFIG_STM32H7_TICKLESS_CHANNEL, tm); |
| |
| stm32_tickless_ackint(g_tickless.channel); |
| stm32_tickless_enableint(CONFIG_STM32H7_TICKLESS_CHANNEL); |
| |
| g_tickless.pending = true; |
| |
| /* If we have already passed this time, there is a chance we didn't set the |
| * compare register in time and we've missed the interrupt. If we don't |
| * catch this case, we won't interrupt until a full loop of the clock. |
| * |
| * Since we can't make assumptions about the clock speed and tick rate, |
| * we simply keep adding an offset to the current time, until we can leave |
| * certain that the interrupt is going to fire as soon as we leave the |
| * critical section. |
| */ |
| |
| while (tm <= stm32_get_counter()) |
| { |
| tm = stm32_get_counter() + offset++; |
| STM32_TIM_SETCOMPARE(g_tickless.tch, CONFIG_STM32H7_TICKLESS_CHANNEL, |
| tm); |
| } |
| |
| leave_critical_section(flags); |
| return OK; |
| } |
| |
| int up_alarm_cancel(struct timespec *ts) |
| { |
| #ifdef HAVE_32BIT_TICKLESS |
| uint64_t nsecs = (((uint64_t)g_tickless.overflow << 32) | |
| STM32_TIM_GETCOUNTER(g_tickless.tch)) * NSEC_PER_TICK; |
| #else |
| uint64_t nsecs = (((uint64_t)g_tickless.overflow << 16) | |
| STM32_TIM_GETCOUNTER(g_tickless.tch)) * NSEC_PER_TICK; |
| #endif |
| |
| ts->tv_sec = nsecs / NSEC_PER_SEC; |
| ts->tv_nsec = nsecs - ts->tv_sec * NSEC_PER_SEC; |
| |
| stm32_tickless_disableint(CONFIG_STM32H7_TICKLESS_CHANNEL); |
| |
| return 0; |
| } |
| #endif |
| |
| #endif /* CONFIG_SCHED_TICKLESS */ |