From 2fad06008a9be76f01fe78865ade03110968114e Mon Sep 17 00:00:00 2001 From: Anthony Merlino Date: Tue, 6 Apr 2021 13:41:26 -0400 Subject: [PATCH] stm32h7: Adds tickless support. --- arch/arm/Kconfig | 2 + arch/arm/src/stm32h7/Kconfig | 21 + arch/arm/src/stm32h7/Make.defs | 6 + .../src/stm32h7/hardware/stm32h7xxx_dbgmcu.h | 1 - arch/arm/src/stm32h7/stm32_tickless.c | 1059 +++++++++++++++++ arch/arm/src/stm32h7/stm32_tim.c | 113 +- arch/arm/src/stm32h7/stm32_tim.h | 8 + 7 files changed, 1190 insertions(+), 20 deletions(-) create mode 100644 arch/arm/src/stm32h7/stm32_tickless.c diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index f2b7b4c787..36f12f4995 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -377,6 +377,8 @@ config ARCH_CHIP_STM32H7 select ARCH_HAVE_SPI_BITORDER select ARM_HAVE_MPU_UNIFIED select ARMV7M_HAVE_STACKCHECK + select ARCH_HAVE_TICKLESS + select ARCH_HAVE_TIMEKEEPING ---help--- STMicro STM32H7 architectures (ARM Cortex-M7). diff --git a/arch/arm/src/stm32h7/Kconfig b/arch/arm/src/stm32h7/Kconfig index 4f60dd01a9..804586bfb2 100644 --- a/arch/arm/src/stm32h7/Kconfig +++ b/arch/arm/src/stm32h7/Kconfig @@ -1838,6 +1838,27 @@ config STM32H7_DMACAPABLE_ASSUME_CACHE_ALIGNED menu "Timer Configuration" +if SCHED_TICKLESS + +config STM32H7_TICKLESS_TIMER + int "Tickless hardware timer" + default 2 + range 1 17 + ---help--- + If the Tickless OS feature is enabled, then one clock must be + assigned to provided the timer needed by the OS. + +config STM32H7_TICKLESS_CHANNEL + int "Tickless timer channel" + default 1 + range 1 4 + ---help--- + If the Tickless OS feature is enabled, the one clock must be + assigned to provided the free-running timer needed by the OS + and one channel on that clock is needed to handle intervals. + +endif # SCHED_TICKLESS + config STM32H7_ONESHOT bool "TIM one-shot wrapper" default n diff --git a/arch/arm/src/stm32h7/Make.defs b/arch/arm/src/stm32h7/Make.defs index 38aa971638..c36ec04dc0 100644 --- a/arch/arm/src/stm32h7/Make.defs +++ b/arch/arm/src/stm32h7/Make.defs @@ -36,6 +36,10 @@ CMN_CSRCS += arm_systemreset.c arm_trigger_irq.c arm_udelay.c arm_unblocktask.c CMN_CSRCS += arm_usestack.c arm_vfork.c arm_switchcontext.c arm_puts.c CMN_CSRCS += arm_tcbinfo.c +ifeq ($(CONFIG_ARMV7M_SYSTICK),y) +CMN_CSRCS += arm_systick.c +endif + # Configuration-dependent common files ifeq ($(CONFIG_ARMV7M_STACKCHECK),y) @@ -88,6 +92,8 @@ CHIP_CSRCS += stm32_uid.c ifneq ($(CONFIG_SCHED_TICKLESS),y) CHIP_CSRCS += stm32_timerisr.c +else +CHIP_CSRCS += stm32_tickless.c endif ifeq ($(CONFIG_STM32H7_ONESHOT),y) diff --git a/arch/arm/src/stm32h7/hardware/stm32h7xxx_dbgmcu.h b/arch/arm/src/stm32h7/hardware/stm32h7xxx_dbgmcu.h index 177b4610a6..77515b3352 100644 --- a/arch/arm/src/stm32h7/hardware/stm32h7xxx_dbgmcu.h +++ b/arch/arm/src/stm32h7/hardware/stm32h7xxx_dbgmcu.h @@ -105,7 +105,6 @@ #define DBGMCU_APB2Z1_TIM15STOP (1 << 16) /* Bit 16: TIM15 stopped when halted */ #define DBGMCU_APB2Z1_TIM16STOP (1 << 17) /* Bit 17: TIM16 stopped when halted */ #define DBGMCU_APB2Z1_TIM17STOP (1 << 18) /* Bit 18: TIM17 stopped when halted */ -#define DBGMCU_APB2Z1_TIM17STOP (1 << 18) /* Bit 18: TIM17 stopped when halted */ #define DBGMCU_APB2Z1_HRTIMSTOP (1 << 29) /* Bit 29: HRTIM stopped when halted */ /* Debug MCU APB4 freeze register */ diff --git a/arch/arm/src/stm32h7/stm32_tickless.c b/arch/arm/src/stm32h7/stm32_tickless.c new file mode 100644 index 0000000000..f534630689 --- /dev/null +++ b/arch/arm/src/stm32h7/stm32_tickless.c @@ -0,0 +1,1059 @@ +/**************************************************************************** + * 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(FAR 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(FAR 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 + +#include +#include +#include +#include + +#include +#include +#include +#include + +#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_CHANNEL == 1 +#define DIER_CAPT_IE ATIM_DIER_CC1IE +#elif CONFIG_STM32H7_TICKLESS_CHANNEL == 2 +#define DIER_CAPT_IE ATIM_DIER_CC2IE +#elif CONFIG_STM32H7_TICKLESS_CHANNEL == 3 +#define DIER_CAPT_IE ATIM_DIER_CC3IE +#elif CONFIG_STM32H7_TICKLESS_CHANNEL == 4 +#define DIER_CAPT_IE ATIM_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 */ + FAR struct stm32_tim_dev_s *tch; /* Handle returned by stm32_tim_init() */ + uint32_t frequency; +#ifdef CONFIG_CLOCK_TIMEKEEPING + uint64_t counter_mask; +#else + uint32_t overflow; /* Timer counter overflow */ +#endif + 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 &= ~(3 << (channel << 2)); + + /* 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 = (ATIM_CCMR_MODE_FRZN << ATIM_CCMR1_OC1M_SHIFT); + + /* Set polarity */ + + ccer_val |= ATIM_CCER_CC1P << (channel << 2); + + /* 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 + * + ****************************************************************************/ + +#ifndef CONFIG_CLOCK_TIMEKEEPING +static void stm32_timing_handler(void) +{ + g_tickless.overflow++; + + STM32_TIM_ACKINT(g_tickless.tch, 0); +} +#endif /* CONFIG_CLOCK_TIMEKEEPING */ + +/**************************************************************************** + * 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(); + +#ifndef CONFIG_CLOCK_TIMEKEEPING + if (interrupt_flags & ATIM_SR_UIF) + { + stm32_timing_handler(); + } +#endif /* CONFIG_CLOCK_TIMEKEEPING */ + + if (interrupt_flags & (1 << g_tickless.channel)) + { + stm32_interval_handler(); + } + + return OK; +} + +/**************************************************************************** + * Name: stm32_get_counter + * + ****************************************************************************/ + +static uint64_t stm32_get_counter(void) +{ + return ((uint64_t)g_tickless.overflow << 32) | + STM32_TIM_GETCOUNTER(g_tickless.tch); +} + +/**************************************************************************** + * 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_TIM6 + case 6: + + /* Basic timers not supported by this implementation */ + + DEBUGASSERT(0); + break; +#endif + +#ifdef CONFIG_STM32H7_TIM7 + case 7: + + /* Basic timers not supported by this implementation */ + + DEBUGASSERT(0); + 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: + DEBUGASSERT(0); + } + + /* 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; + + tmrinfo("timer=%d channel=%d frequency=%d Hz\n", + g_tickless.timer, g_tickless.channel, g_tickless.frequency); + + g_tickless.tch = stm32_tim_init(g_tickless.timer); + if (!g_tickless.tch) + { + tmrerr("ERROR: Failed to allocate TIM%d\n", g_tickless.timer); + DEBUGASSERT(0); + } + + STM32_TIM_SETCLOCK(g_tickless.tch, g_tickless.frequency); + +#ifdef CONFIG_CLOCK_TIMEKEEPING + + /* Should this be changed to 0xffff because we use 16 bit timers? */ + + g_tickless.counter_mask = 0xffffffffull; +#else + g_tickless.overflow = 0; + + /* Set up to receive the callback when the counter overflow occurs */ + + STM32_TIM_SETISR(g_tickless.tch, stm32_tickless_handler, NULL, 0); +#endif + + /* 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, 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. + * + ****************************************************************************/ + +#ifndef CONFIG_CLOCK_TIMEKEEPING +int up_timer_gettime(FAR 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 == 0) + { + 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, 0); + 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, 0); + + /* 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=%u\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=(%u, %lu)\n", + usec, (unsigned long)ts->tv_sec, (unsigned long)ts->tv_nsec); + + return OK; +} + +#else + +int up_timer_getcounter(FAR uint64_t *cycles) +{ + *cycles = (uint64_t)STM32_TIM_GETCOUNTER(g_tickless.tch); + return OK; +} + +#endif /* CONFIG_CLOCK_TIMEKEEPING */ + +/**************************************************************************** + * Name: up_timer_getmask + * + * Description: + * To be provided + * + * Input Parameters: + * mask - Location to return the 64-bit mask + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_CLOCK_TIMEKEEPING +void up_timer_getmask(FAR uint64_t *mask) +{ + DEBUGASSERT(mask != NULL); + *mask = g_tickless.counter_mask; +} +#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(FAR 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) + { + 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(FAR 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(FAR 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(FAR struct timespec *ts) +{ + uint64_t nsecs = (((uint64_t)g_tickless.overflow << 32) | + STM32_TIM_GETCOUNTER(g_tickless.tch)) * NSEC_PER_TICK; + + 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 */ diff --git a/arch/arm/src/stm32h7/stm32_tim.c b/arch/arm/src/stm32h7/stm32_tim.c index 50765c25c7..03ffa3997f 100644 --- a/arch/arm/src/stm32h7/stm32_tim.c +++ b/arch/arm/src/stm32h7/stm32_tim.c @@ -251,24 +251,31 @@ struct stm32_tim_priv_s /* Timer methods */ -static int stm32_tim_setmode(FAR struct stm32_tim_dev_s *dev, - stm32_tim_mode_t mode); -static int stm32_tim_setclock(FAR struct stm32_tim_dev_s *dev, - uint32_t freq); -static void stm32_tim_setperiod(FAR struct stm32_tim_dev_s *dev, - uint32_t period); -static int stm32_tim_setchannel(FAR struct stm32_tim_dev_s *dev, - uint8_t channel, stm32_tim_channel_t mode); -static int stm32_tim_setcompare(FAR struct stm32_tim_dev_s *dev, - uint8_t channel, uint32_t compare); -static int stm32_tim_getcapture(FAR struct stm32_tim_dev_s *dev, - uint8_t channel); -static int stm32_tim_setisr(FAR struct stm32_tim_dev_s *dev, xcpt_t handler, - void *arg, int source); -static void stm32_tim_enableint(FAR struct stm32_tim_dev_s *dev, int source); -static void stm32_tim_disableint(FAR struct stm32_tim_dev_s *dev, - int source); -static void stm32_tim_ackint(FAR struct stm32_tim_dev_s *dev, int source); +static int stm32_tim_setmode(FAR struct stm32_tim_dev_s *dev, + stm32_tim_mode_t mode); +static int stm32_tim_setclock(FAR struct stm32_tim_dev_s *dev, + uint32_t freq); +static void stm32_tim_setperiod(FAR struct stm32_tim_dev_s *dev, + uint32_t period); +static uint32_t stm32_tim_getcounter(FAR struct stm32_tim_dev_s *dev); +static void stm32_tim_setcounter(FAR struct stm32_tim_dev_s *dev, + uint32_t count); +static int stm32_tim_getwidth(FAR struct stm32_tim_dev_s *dev); +static int stm32_tim_setchannel(FAR struct stm32_tim_dev_s *dev, + uint8_t channel, + stm32_tim_channel_t mode); +static int stm32_tim_setcompare(FAR struct stm32_tim_dev_s *dev, + uint8_t channel, uint32_t compare); +static int stm32_tim_getcapture(FAR struct stm32_tim_dev_s *dev, + uint8_t channel); +static int stm32_tim_setisr(FAR struct stm32_tim_dev_s *dev, + xcpt_t handler, void *arg, int source); +static void stm32_tim_enableint(FAR struct stm32_tim_dev_s *dev, + int source); +static void stm32_tim_disableint(FAR struct stm32_tim_dev_s *dev, + int source); +static void stm32_tim_ackint(FAR struct stm32_tim_dev_s *dev, int source); +static int stm32_tim_checkint(FAR struct stm32_tim_dev_s *dev, int source); /**************************************************************************** * Private Data @@ -279,13 +286,17 @@ static const struct stm32_tim_ops_s stm32_tim_ops = .setmode = &stm32_tim_setmode, .setclock = &stm32_tim_setclock, .setperiod = &stm32_tim_setperiod, + .getcounter = &stm32_tim_getcounter, + .setcounter = &stm32_tim_setcounter, + .getwidth = &stm32_tim_getwidth, .setchannel = &stm32_tim_setchannel, .setcompare = &stm32_tim_setcompare, .getcapture = &stm32_tim_getcapture, .setisr = &stm32_tim_setisr, .enableint = &stm32_tim_enableint, .disableint = &stm32_tim_disableint, - .ackint = &stm32_tim_ackint + .ackint = &stm32_tim_ackint, + .checkint = &stm32_tim_checkint, }; #ifdef CONFIG_STM32H7_TIM1 @@ -485,6 +496,64 @@ static void stm32_tim_disable(FAR struct stm32_tim_dev_s *dev) stm32_putreg16(dev, STM32_GTIM_CR1_OFFSET, val); } +/***************************************************************************** + * Name: stm32_tim_getwidth + *****************************************************************************/ + +static int stm32_tim_getwidth(FAR struct stm32_tim_dev_s *dev) +{ + /* Only TIM2 and TIM5 timers may be 32-bits in width */ + + switch (((struct stm32_tim_priv_s *)dev)->base) + { +#if defined(CONFIG_STM32H7_TIM2) + case STM32_TIM2_BASE: + return 32; +#endif + +#if defined(CONFIG_STM32H7_TIM5) + case STM32_TIM5_BASE: + return 32; +#endif + + /* All others are 16-bit times */ + + default: + return 16; + } +} + +/***************************************************************************** + * Name: stm32_tim_getcounter + *****************************************************************************/ + +static uint32_t stm32_tim_getcounter(FAR struct stm32_tim_dev_s *dev) +{ + DEBUGASSERT(dev != NULL); + return stm32_tim_getwidth(dev) > 16 ? + stm32_getreg32(dev, STM32_BTIM_CNT_OFFSET) : + (uint32_t)stm32_getreg16(dev, STM32_BTIM_CNT_OFFSET); +} + +/***************************************************************************** + * Name: stm32_tim_setcounter + *****************************************************************************/ + +static void stm32_tim_setcounter(FAR struct stm32_tim_dev_s *dev, + uint32_t count) +{ + DEBUGASSERT(dev != NULL); + + if (stm32_tim_getwidth(dev) > 16) + { + stm32_putreg32(dev, STM32_BTIM_CNT_OFFSET, count); + } + else + { + stm32_putreg16(dev, STM32_BTIM_CNT_OFFSET, (uint16_t)count); + } +} + /* Reset timer into system default state, but do not affect output/input * pins */ @@ -773,6 +842,12 @@ static void stm32_tim_disableint(FAR struct stm32_tim_dev_s *dev, int source) stm32_modifyreg16(dev, STM32_GTIM_DIER_OFFSET, source, 0); } +static int stm32_tim_checkint(FAR struct stm32_tim_dev_s *dev, int source) +{ + uint16_t regval = stm32_getreg16(dev, STM32_BTIM_SR_OFFSET); + return (regval & source) ? 1 : 0; +} + static void stm32_tim_ackint(FAR struct stm32_tim_dev_s *dev, int source) { stm32_putreg16(dev, STM32_GTIM_SR_OFFSET, ~source); diff --git a/arch/arm/src/stm32h7/stm32_tim.h b/arch/arm/src/stm32h7/stm32_tim.h index e22d98f719..c02456715f 100644 --- a/arch/arm/src/stm32h7/stm32_tim.h +++ b/arch/arm/src/stm32h7/stm32_tim.h @@ -39,6 +39,9 @@ #define STM32_TIM_SETMODE(d,mode) ((d)->ops->setmode(d,mode)) #define STM32_TIM_SETCLOCK(d,freq) ((d)->ops->setclock(d,freq)) #define STM32_TIM_SETPERIOD(d,period) ((d)->ops->setperiod(d,period)) +#define STM32_TIM_GETCOUNTER(d) ((d)->ops->getcounter(d)) +#define STM32_TIM_SETCOUNTER(d,c) ((d)->ops->setcounter(d,c)) +#define STM32_TIM_GETWIDTH(d) ((d)->ops->getwidth(d)) #define STM32_TIM_SETCHANNEL(d,ch,mode) ((d)->ops->setchannel(d,ch,mode)) #define STM32_TIM_SETCOMPARE(d,ch,comp) ((d)->ops->setcompare(d,ch,comp)) #define STM32_TIM_GETCAPTURE(d,ch) ((d)->ops->getcapture(d,ch)) @@ -46,6 +49,7 @@ #define STM32_TIM_ENABLEINT(d,s) ((d)->ops->enableint(d,s)) #define STM32_TIM_DISABLEINT(d,s) ((d)->ops->disableint(d,s)) #define STM32_TIM_ACKINT(d,s) ((d)->ops->ackint(d,s)) +#define STM32_TIM_CHECKINT(d,s) ((d)->ops->checkint(d,s)) /**************************************************************************** * Public Types @@ -143,9 +147,12 @@ struct stm32_tim_ops_s int (*setmode)(FAR struct stm32_tim_dev_s *dev, stm32_tim_mode_t mode); int (*setclock)(FAR struct stm32_tim_dev_s *dev, uint32_t freq); void (*setperiod)(FAR struct stm32_tim_dev_s *dev, uint32_t period); + uint32_t (*getcounter)(FAR struct stm32_tim_dev_s *dev); + void (*setcounter)(FAR struct stm32_tim_dev_s *dev, uint32_t count); /* General and Advanced Timers Adds */ + int (*getwidth)(FAR struct stm32_tim_dev_s *dev); int (*setchannel)(FAR struct stm32_tim_dev_s *dev, uint8_t channel, stm32_tim_channel_t mode); int (*setcompare)(FAR struct stm32_tim_dev_s *dev, uint8_t channel, @@ -159,6 +166,7 @@ struct stm32_tim_ops_s void (*enableint)(FAR struct stm32_tim_dev_s *dev, int source); void (*disableint)(FAR struct stm32_tim_dev_s *dev, int source); void (*ackint)(FAR struct stm32_tim_dev_s *dev, int source); + int (*checkint)(FAR struct stm32_tim_dev_s *dev, int source); }; /****************************************************************************