This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 232d25427579e4c1d1a1ea0f943503603802a3a5 Author: Matteo Golin <[email protected]> AuthorDate: Sat Jul 11 14:39:18 2026 -0400 bcm2711/oneshot: Implement oneshot timer driver for BCM2711 This commit implements a count-based oneshot timer driver for the BCM2711 system timers. Signed-off-by: Matteo Golin <[email protected]> --- arch/arm64/include/bcm2711/irq.h | 2 + arch/arm64/src/bcm2711/CMakeLists.txt | 3 +- arch/arm64/src/bcm2711/Make.defs | 1 + arch/arm64/src/bcm2711/bcm2711_oneshot.c | 529 +++++++++++++++++++++ arch/arm64/src/bcm2711/bcm2711_oneshot.h | 58 +++ arch/arm64/src/bcm2711/hardware/bcm2711_systimer.h | 5 + 6 files changed, 597 insertions(+), 1 deletion(-) diff --git a/arch/arm64/include/bcm2711/irq.h b/arch/arm64/include/bcm2711/irq.h index 287022d4a6d..0f70f78b7bb 100644 --- a/arch/arm64/include/bcm2711/irq.h +++ b/arch/arm64/include/bcm2711/irq.h @@ -57,6 +57,8 @@ #define BCM_IRQ_VC_BASE 96 #define BCM_IRQ_VC(n) (BCM_IRQ_VC_BASE + n) +#define BCM_IRQ_VC_TIMER(n) BCM_IRQ_VC((n)) + #define BCM_IRQ_VC_TIMER0 BCM_IRQ_VC(0) #define BCM_IRQ_VC_TIMER1 BCM_IRQ_VC(1) #define BCM_IRQ_VC_TIMER2 BCM_IRQ_VC(2) diff --git a/arch/arm64/src/bcm2711/CMakeLists.txt b/arch/arm64/src/bcm2711/CMakeLists.txt index 36235fff2a5..b52f3c3032d 100644 --- a/arch/arm64/src/bcm2711/CMakeLists.txt +++ b/arch/arm64/src/bcm2711/CMakeLists.txt @@ -20,7 +20,8 @@ # BCM2711 specific C source files -set(SRCS bcm2711_boot.c bcm2711_mailbox.c bcm2711_serial.c bcm2711_gpio.c) +set(SRCS bcm2711_boot.c bcm2711_mailbox.c bcm2711_serial.c bcm2711_gpio.c + bcm2711_oneshot.c) if(CONFIG_SMP) list(APPEND SRCS bcm2711_cpustart.c) diff --git a/arch/arm64/src/bcm2711/Make.defs b/arch/arm64/src/bcm2711/Make.defs index 061a2a75a0a..ec0ab3ebbdf 100644 --- a/arch/arm64/src/bcm2711/Make.defs +++ b/arch/arm64/src/bcm2711/Make.defs @@ -26,6 +26,7 @@ CHIP_CSRCS += bcm2711_boot.c CHIP_CSRCS += bcm2711_mailbox.c CHIP_CSRCS += bcm2711_serial.c CHIP_CSRCS += bcm2711_gpio.c +CHIP_CSRCS += bcm2711_oneshot.c ifeq ($(CONFIG_SMP),y) CHIP_CSRCS += bcm2711_cpustart.c diff --git a/arch/arm64/src/bcm2711/bcm2711_oneshot.c b/arch/arm64/src/bcm2711/bcm2711_oneshot.c new file mode 100644 index 00000000000..c348109263d --- /dev/null +++ b/arch/arm64/src/bcm2711/bcm2711_oneshot.c @@ -0,0 +1,529 @@ +/**************************************************************************** + * arch/arm64/src/bcm2711/bcm2711_oneshot.c + * + * Contributed by: Matteo Golin <[email protected]> + * + * SPDX-License-Identifer: Apache-2.0 + * + * 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. + * + ****************************************************************************/ + +/* This one-shot timer driver is based off of the BCM2711's system timer + * interface, which provides four channels. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/arch.h> +#include <nuttx/config.h> + +#include <nuttx/debug.h> + +#include <nuttx/irq.h> + +#include "chip.h" +#include "arm64_arch.h" +#include "arm64_gic.h" + +#include "bcm2711_oneshot.h" + +#include "hardware/bcm2711_systimer.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct bcm2711_chan_s +{ + struct oneshot_lowerhalf_s dev; + mutex_t lock; /* Exclusive access */ + clkcnt_t cmp; /* Full-size compare value */ + uint8_t chan; /* Which timer is being used 0-3 */ + bool init; /* Initialized or not */ + bool running; /* Is the timer actively running or not */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static clkcnt_t bcm2711_current(FAR struct oneshot_lowerhalf_s *lower); +static void bcm2711_start(FAR struct oneshot_lowerhalf_s *lower, + clkcnt_t delay); +static void bcm2711_start_absolute(FAR struct oneshot_lowerhalf_s *lower, + clkcnt_t cnt); +static void bcm2711_cancel(FAR struct oneshot_lowerhalf_s *lower); +static clkcnt_t bcm2711_max_delay(FAR struct oneshot_lowerhalf_s *lower); + +static void bcm2711_tim_irq_en(struct bcm2711_chan_s *priv, bool en); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct oneshot_operations_s g_ops = +{ + .current = bcm2711_current, + .start = bcm2711_start, + .start_absolute = bcm2711_start_absolute, + .cancel = bcm2711_cancel, + .max_delay = bcm2711_max_delay, +}; + +static struct bcm2711_chan_s g_chans[BCM_SYST_NUMCHANS] = +{ + { + .chan = 0, + .dev = + { + .ops = &g_ops, + }, + .init = false, + }, + { + .chan = 1, + .dev = + { + .ops = &g_ops, + }, + .init = false, + }, + { + .chan = 2, + .dev = + { + .ops = &g_ops, + }, + .init = false, + }, + { + .chan = 3, + .dev = + { + .ops = &g_ops, + }, + .init = false, + }, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: getcurrent + * + * Description: + * Get the current timer count value. + * + * Returned Value: + * The timer counter value. + * + ****************************************************************************/ + +static clkcnt_t get_current_count(void) +{ + clkcnt_t count; + count = (clkcnt_t)getreg32(BCM_SYST_CHI) << 32; + count |= getreg32(BCM_SYST_CLO); + return count; +} + +/**************************************************************************** + * Name: compare_exceeded + * + * Description: + * Check if the timer compare has been exceeded by the comparison that + * generated the last interrupt + * + * Input Parameters: + * cnt - Current timer count + * cmp - Full resolution comparison count + * + * Returned Value: + * True if the comparison has been exceeded (timer expired), false if not. + * + ****************************************************************************/ + +static bool compare_exceeded(clkcnt_t cnt, clkcnt_t cmp) +{ + /* We've passed the compare value, and we're under 32 bits different (i.e. + * this is the first possible compare trigger) + */ + + if (cnt >= cmp && cnt - cmp < UINT32_MAX) + { + return true; + } + else + { + /* Our compare is larger than the count, which is possible when there + * is a roll-over. Let's check if we're within the 32-bit compare + * window. + */ + + return (cmp - cnt) < UINT32_MAX; + } +} + +/**************************************************************************** + * Name: bcm2711_tim_handler + * + * Description: + * Timer interrupt handler + * + * Input Parameters: + * irq - The IRQ number + * ctx - The interrupt context + * arg - The handler argument (channel lower-half casted to void) + * + * Returned Value: + * 0 on success, negated errno on error. + * + ****************************************************************************/ + +static int bcm2711_tim_handler(int irq, void *ctx, void *arg) +{ + struct bcm2711_chan_s *priv = (struct bcm2711_chan_s *)arg; + clkcnt_t cur; + irqstate_t flags; + + /* Check that we actually got a compare interrupt using the status bits. */ + + if (!(getreg32(BCM_SYST_CS) & BCM_SYST_CS_M(priv->chan))) + { + return -EIO; /* Shouldn't have gotten an interrupt */ + } + + /* We have a match, which means the lower 32-bits of the counter match our + * compare register. We compare the full 64 bit current count against our + * full resolution compare value to see if we've exceed the point where the + * timer should expire. + */ + + flags = enter_critical_section(); + cur = bcm2711_current(&priv->dev); + leave_critical_section(flags); + + /* Trigger the callback, and disable our interrupt if we should fire. + * Otherwise, keep ticking until it's time. + */ + + if (compare_exceeded(cur, priv->cmp)) + { + priv->running = false; + bcm2711_tim_irq_en(priv, false); + tmrinfo("Callback..."); + priv->dev.callback(&priv->dev, priv->dev.arg); + tmrinfo("Callback called for oneshot %u.", priv->chan); + } + + /* Clear the interrupt status before leaving */ + + putreg32(BCM_SYST_CS_M(priv->chan), BCM_SYST_CS); + return 0; +} + +/**************************************************************************** + * Name: bcm2711_tim_irq_en + * + * Description: + * Enable the timer's interrupt. + * + * Input Parameters: + * priv - The timer channel lower-half + * en - True to enable, false to disable + * + ****************************************************************************/ + +static void bcm2711_tim_irq_en(struct bcm2711_chan_s *priv, bool en) +{ + if (en) + { + /* For safety, clear any match status in advance. We don't want a + * spurious interrupt from something leftover. + */ + + putreg32(BCM_SYST_CS_M(priv->chan), BCM_SYST_CS); + up_enable_irq(BCM_IRQ_VC_TIMER(priv->chan)); + tmrinfo("Interrupt for timer %u enabled.", priv->chan); + return; + } + + up_disable_irq(BCM_IRQ_VC_TIMER(priv->chan)); + tmrinfo("Interrupt for timer %u disabled.", priv->chan); +} + +/**************************************************************************** + * Name: bcm2711_tim_irqinit + * + * Description: + * The interrupt handler used for the timer interrupt. + * + * Input Parameters: + * priv - The lower-half timer driver + * + * Returned Value: + * 0 on success, negated errno on failure. + * + ****************************************************************************/ + +static int bcm2711_tim_irqinit(struct bcm2711_chan_s *priv) +{ + int err; + + err = irq_attach(BCM_IRQ_VC_TIMER(priv->chan), bcm2711_tim_handler, priv); + if (err < 0) + { + tmrerr("Couldn't attach interrupt for timer %u: %d", priv->chan, err); + return err; + } + + tmrinfo("Interrupt handler attached for timer %u", priv->chan); + +#ifdef CONFIG_ARCH_IRQPRIO + err = up_prioritize_irq(BCM_IRQ_VC_TIMER(priv->chan), 0); + if (err < 0) + { + tmrerr("Failed to prioritize timer %u interrupt", priv->chan); + } +#endif + + err = up_set_irq_type(BCM_IRQ_VC_TIMER(priv->chan), IRQ_RISING_EDGE); + if (err < 0) + { + tmrerr("Failed to timer %u IRQ type", priv->chan); + } + + return err; +} + +/**************************************************************************** + * Name: bcm2711_current + * + * Description: + * Get the current timer count. + * + * Input Parameters: + * lower - The lower-half driver instance + * + * Returned Value: + * The current timer count. + * + ****************************************************************************/ + +static clkcnt_t bcm2711_current(FAR struct oneshot_lowerhalf_s *lower) +{ + UNUSED(lower); + clkcnt_t count; + irqstate_t flags; + + /* We have to perform two register reads; one for high bits, one for low + * bits. We want to make sure we're not interrupted here and lose sync. + */ + + flags = enter_critical_section(); + count = get_current_count(); + leave_critical_section(flags); + + return count; +} + +/**************************************************************************** + * Name: bcm2711_start + * + * Description: + * Start a relative timer. + * + * Input Parameters: + * lower - The lower-half driver instance + * delay - The relative delay from the current + * + ****************************************************************************/ + +static void bcm2711_start(FAR struct oneshot_lowerhalf_s *lower, + clkcnt_t delay) +{ + struct bcm2711_chan_s *priv = (struct bcm2711_chan_s *)lower; + irqstate_t flags; + clkcnt_t cnt; + + /* Compute the absolute time with the offset. Set the resulting absolute + * timer. + */ + + nxmutex_lock(&priv->lock); + if (priv->running) + { + nxmutex_unlock(&priv->lock); + return; + } + + flags = enter_critical_section(); + cnt = get_current_count() + delay; + priv->cmp = cnt; + leave_critical_section(flags); + + priv->running = true; + putreg32((uint32_t)(cnt & UINT32_MAX), BCM_SYST_C(priv->chan)); + bcm2711_tim_irq_en(priv, true); /* Enable interrupts */ + nxmutex_unlock(&priv->lock); +} + +/**************************************************************************** + * Name: bcm2711_start_absolute + * + * Description: + * Start an absolute timer. + * + * Input Parameters: + * lower - The lower-half driver instance + * cnt - The absolute count for the timer to go off + * + ****************************************************************************/ + +static void bcm2711_start_absolute(FAR struct oneshot_lowerhalf_s *lower, + clkcnt_t cnt) +{ + struct bcm2711_chan_s *priv = (struct bcm2711_chan_s *)lower; + + nxmutex_lock(&priv->lock); + if (priv->running) + { + nxmutex_unlock(&priv->lock); + return; + } + + priv->running = true; + priv->cmp = cnt; + putreg32((uint32_t)(cnt & UINT32_MAX), BCM_SYST_C(priv->chan)); + bcm2711_tim_irq_en(priv, true); /* Enable interrupts */ + nxmutex_unlock(&priv->lock); +} + +/**************************************************************************** + * Name: bcm2711_cancel + * + * Description: + * Cancel the timer event + * + * Input Parameters: + * lower - The lower-half driver instance + * + ****************************************************************************/ + +static void bcm2711_cancel(FAR struct oneshot_lowerhalf_s *lower) +{ + struct bcm2711_chan_s *priv = (struct bcm2711_chan_s *)lower; + + /* Disable the timer's interrupt handler, which will stop events from + * happening for it. + */ + + nxmutex_lock(&priv->lock); + priv->running = false; + bcm2711_tim_irq_en((struct bcm2711_chan_s *)lower, false); + nxmutex_unlock(&priv->lock); +} + +/**************************************************************************** + * Name: bcm2711_max_delay + * + * Description: + * Get the timer's maximum delay. + * + * Input Parameters: + * lower - The lower-half driver instance + * + * Returned Value: + * The timer's maximum delay. + * + ****************************************************************************/ + +static clkcnt_t bcm2711_max_delay(FAR struct oneshot_lowerhalf_s *lower) +{ + UNUSED(lower); + return UINT64_MAX; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: oneshot_initialize + * + * Description: + * Initialize the oneshot timer and return a oneshot lower half driver + * instance. + * + * Returned Value: + * On success, a non-NULL instance of the oneshot lower-half driver is + * returned. NULL is return on any failure. + * + ****************************************************************************/ + +struct oneshot_lowerhalf_s *oneshot_initialize(int chan, uint16_t resolution) +{ + int err; + + /* Channels in the range 0-3 */ + + if (chan < 0 || chan > BCM_SYST_NUMCHANS - 1) + { + tmrerr("Timer %d does not exist.", chan); + return NULL; + } + + /* The system has a minimum resolution of 1us. We otherwise ignore the + * argument, because we will operate in a resolution of 1us always. + */ + + if (resolution < 1) + { + tmrerr("Resolution of %u uS is too small.", resolution); + return NULL; + } + + /* Channel initialized already */ + + if (g_chans[chan].init) + { + return &g_chans[chan].dev; + } + + g_chans[chan].chan = chan; + g_chans[chan].running = false; + nxmutex_init(&g_chans[chan].lock); + + err = bcm2711_tim_irqinit(&g_chans[chan]); + if (err < 0) + { + return NULL; + } + + /* Tell the upper-half how to perform the timing conversion using the clock + * frequency. The system timer runs at 1MHz. + */ + + oneshot_count_init(&g_chans[chan].dev, BCM_SYST_FREQ); + + g_chans[chan].init = true; + return &g_chans[chan].dev; +} diff --git a/arch/arm64/src/bcm2711/bcm2711_oneshot.h b/arch/arm64/src/bcm2711/bcm2711_oneshot.h new file mode 100644 index 00000000000..1a8f6aeb027 --- /dev/null +++ b/arch/arm64/src/bcm2711/bcm2711_oneshot.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * arch/arm64/src/bcm2711/bcm2711_oneshot.h + * + * Contributed by: Matteo Golin <[email protected]> + * + * SPDX-License-Identifer: Apache-2.0 + * + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM64_SRC_BCM2711_BCM2711_ONESHOT_H +#define __ARCH_ARM64_SRC_BCM2711_BCM2711_ONESHOT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <nuttx/timers/oneshot.h> + +#ifndef __ASSEMBLY__ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ + +#endif /* __ARCH_ARM64_SRC_BCM2711_BCM2711_ONESHOT_H */ diff --git a/arch/arm64/src/bcm2711/hardware/bcm2711_systimer.h b/arch/arm64/src/bcm2711/hardware/bcm2711_systimer.h index 818985c8ff6..bfdb3386147 100644 --- a/arch/arm64/src/bcm2711/hardware/bcm2711_systimer.h +++ b/arch/arm64/src/bcm2711/hardware/bcm2711_systimer.h @@ -33,6 +33,9 @@ * Pre-processor Definitions ****************************************************************************/ +#define BCM_SYST_NUMCHANS (4) +#define BCM_SYST_FREQ (1000000) /* 1MHz */ + /* System timer register offsets */ #define BCM_SYST_CS_OFFSET 0x00 @@ -50,6 +53,7 @@ #define BCM_SYST_CS _BCM_SYST(BCM_SYST_CS_OFFSET) #define BCM_SYST_CLO _BCM_SYST(BCM_SYST_CLO_OFFSET) #define BCM_SYST_CHI _BCM_SYST(BCM_SYST_CHI_OFFSET) +#define BCM_SYST_C(n) _BCM_SYST(BCM_SYST_C0_OFFSET + 0x4 * (n)) #define BCM_SYST_C0 _BCM_SYST(BCM_SYST_C0_OFFSET) #define BCM_SYST_C1 _BCM_SYST(BCM_SYST_C1_OFFSET) #define BCM_SYST_C2 _BCM_SYST(BCM_SYST_C2_OFFSET) @@ -57,6 +61,7 @@ /* System timer register bit definitions */ +#define BCM_SYST_CS_M(n) (1 << (n)) #define BCM_SYST_CS_M3 (1 << 3) #define BCM_SYST_CS_M2 (1 << 2) #define BCM_SYST_CS_M1 (1 << 1)
