From 22a44465e4867d863674917f85ccbb6dc599b8a1 Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Thu, 23 Aug 2018 10:08:11 -0600 Subject: [PATCH] arch/arm/src/armv7-m: Implement SYSTICK timer driver --- arch/arm/src/armv7-m/Kconfig | 6 + arch/arm/src/armv7-m/systick.h | 91 ++++++++ arch/arm/src/armv7-m/up_systick.c | 331 ++++++++++++++++++++++++++++++ 3 files changed, 428 insertions(+) create mode 100644 arch/arm/src/armv7-m/systick.h create mode 100644 arch/arm/src/armv7-m/up_systick.c diff --git a/arch/arm/src/armv7-m/Kconfig b/arch/arm/src/armv7-m/Kconfig index 79d709ee83..a533b8d232 100644 --- a/arch/arm/src/armv7-m/Kconfig +++ b/arch/arm/src/armv7-m/Kconfig @@ -255,3 +255,9 @@ config ARMV7M_ITMSYSLOG_SWODIV range 1 8192 endif # ARMV7M_ITMSYSLOG + +config ARMV7M_SYSTICK + bool "SysTick timer driver" + depends on TIMER + ---help--- + Enable SysTick timer driver. diff --git a/arch/arm/src/armv7-m/systick.h b/arch/arm/src/armv7-m/systick.h new file mode 100644 index 0000000000..ac11427b8a --- /dev/null +++ b/arch/arm/src/armv7-m/systick.h @@ -0,0 +1,91 @@ +/**************************************************************************** + * arch/arm/src/armv7-m/systick.h + * + * Copyright (C) 2018 Pinecone Inc. All rights reserved. + * Author: Xiang Xiao + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_ARMV7_M_SYSTICK_H +#define __ARCH_ARM_SRC_ARMV7_M_SYSTICK_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: systick_initialize + * + * Description: + * This function initialize SYSTICK hardware module which is part of NVIC + * and return an instance of a "lower half" timer interface. + * + * Input parameters: + * coreclk - false if SYSTICK working clock come from the external + * reference clock, otherwise true. + * freq - The clock frequency in Hz. If freq is zero, calculate the value + * from NVIC_SYSTICK_CALIB register. + * minor - The timer driver minor number, skip the registration if minor + * < 0. + * + * Returned Value: + * On success, a non-NULL timer_lowerhalf_s is returned to the caller. + * In the event of any failure, a NULL value is returned. + * + ****************************************************************************/ + +#ifdef CONFIG_ARMV7M_SYSTICK +struct timer_lowerhalf_s *systick_initialize(bool coreclk, unsigned int freq, + int minor); +#else +# define systick_initialize(coreclk, freq, minor) NULL +#endif /* CONFIG_ARMV7M_SYSTICK */ + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __ARCH_ARM_SRC_ARMV7_M_SYSTICK_H */ diff --git a/arch/arm/src/armv7-m/up_systick.c b/arch/arm/src/armv7-m/up_systick.c new file mode 100644 index 0000000000..4bffea6a00 --- /dev/null +++ b/arch/arm/src/armv7-m/up_systick.c @@ -0,0 +1,331 @@ +/**************************************************************************** + * arch/arm/src/armv7-m/up_systick.c + * + * Copyright (C) 2018 Pinecone Inc. All rights reserved. + * Author: Xiang Xiao + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include + +#include "nvic.h" +#include "systick.h" +#include "up_arch.h" + +#ifdef CONFIG_ARMV7M_SYSTICK + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define NVIC_IRQ_SYSTICK 15 /* Vector 15: System tick */ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure provides the private representation of the "lower-half" + * driver state structure. This structure must be cast-compatible with the + * timer_lowerhalf_s structure. + */ + +struct systick_lowerhalf_s +{ + const struct timer_ops_s *ops; /* Lower half operations */ + uint32_t freq; /* Timer working clock frequency(Hz) */ + tccb_t callback; /* Current user interrupt callback */ + void *arg; /* Argument passed to upper half callback */ + uint32_t *next_interval; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int systick_start(FAR struct timer_lowerhalf_s *lower_); +static int systick_stop(FAR struct timer_lowerhalf_s *lower_); +static int systick_getstatus(FAR struct timer_lowerhalf_s *lower_, + FAR struct timer_status_s *status); +static int systick_settimeout(FAR struct timer_lowerhalf_s *lower_, + uint32_t timeout); +static void systick_setcallback(FAR struct timer_lowerhalf_s *lower_, + tccb_t callback, FAR void *arg); +static int systick_maxtimeout(FAR struct timer_lowerhalf_s *lower_, + uint32_t *maxtimeout); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct timer_ops_s g_systick_ops = +{ + .start = systick_start, + .stop = systick_stop, + .getstatus = systick_getstatus, + .settimeout = systick_settimeout, + .setcallback = systick_setcallback, + .maxtimeout = systick_maxtimeout, +}; + +static struct systick_lowerhalf_s g_systick_lower = +{ + .ops = &g_systick_ops, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline uint64_t usec_from_count(uint32_t count, uint32_t freq) +{ + return (uint64_t)count * USEC_PER_SEC / freq; +} + +static inline uint64_t usec_to_count(uint32_t usec, uint32_t freq) +{ + return (uint64_t)usec * freq / USEC_PER_SEC; +} + +static bool systick_is_running(void) +{ + return !!(getreg32(NVIC_SYSTICK_CTRL) & NVIC_SYSTICK_CTRL_ENABLE); +} + +static bool systick_irq_pending(struct systick_lowerhalf_s *lower) +{ + if (lower->next_interval) + { + return false; /* Interrupt is in process */ + } + else + { + return !!(getreg32(NVIC_INTCTRL) & NVIC_INTCTRL_PENDSTSET); + } +} + +static int systick_start(FAR struct timer_lowerhalf_s *lower_) +{ + putreg32(0, NVIC_SYSTICK_CURRENT); + modifyreg32(NVIC_SYSTICK_CTRL, 0, NVIC_SYSTICK_CTRL_ENABLE); + return 0; +} + +static int systick_stop(FAR struct timer_lowerhalf_s *lower_) +{ + modifyreg32(NVIC_SYSTICK_CTRL, NVIC_SYSTICK_CTRL_ENABLE, 0); + return 0; +} + +static int systick_getstatus(FAR struct timer_lowerhalf_s *lower_, + FAR struct timer_status_s *status) +{ + struct systick_lowerhalf_s *lower = (struct systick_lowerhalf_s *)lower_; + irqstate_t flags = enter_critical_section(); + + status->flags = lower->callback ? TCFLAGS_HANDLER : 0; + status->flags |= systick_is_running() ? TCFLAGS_ACTIVE : 0; + status->timeout = usec_from_count(getreg32(NVIC_SYSTICK_RELOAD), + lower->freq); + status->timeleft = usec_from_count(getreg32(NVIC_SYSTICK_CURRENT), + lower->freq); + + if (systick_irq_pending(lower)) + { + /* Interrupt is pending and the timer wrap happen? */ + + if (status->timeleft) + { + /* Make timeout-timeleft equal the real elapsed time */ + + status->timeout += status->timeout - status->timeleft; + status->timeleft = 0; + } + } + else if (status->timeleft == 0) + { + status->timeleft = status->timeout; + } + + leave_critical_section(flags); + return 0; +} + +static int systick_settimeout(FAR struct timer_lowerhalf_s *lower_, + uint32_t timeout) +{ + struct systick_lowerhalf_s *lower = (struct systick_lowerhalf_s *)lower_; + + irqstate_t flags = enter_critical_section(); + if (lower->next_interval) + { + /* If the timer callback is in the process, + * delay the update to timer interrupt handler. + */ + + *lower->next_interval = timeout; + } + else + { + uint32_t reload; + + reload = usec_to_count(timeout, lower->freq); + putreg32(reload, NVIC_SYSTICK_RELOAD); + if (systick_is_running()) + { + if (reload != getreg32(NVIC_SYSTICK_CURRENT)) + { + putreg32(0, NVIC_SYSTICK_CURRENT); + } + } + } + + leave_critical_section(flags); + return 0; +} + +static void systick_setcallback(FAR struct timer_lowerhalf_s *lower_, + CODE tccb_t callback, FAR void *arg) +{ + struct systick_lowerhalf_s *lower = (struct systick_lowerhalf_s *)lower_; + + irqstate_t flags = enter_critical_section(); + lower->callback = callback; + lower->arg = arg; + leave_critical_section(flags); +} + +static int systick_maxtimeout(FAR struct timer_lowerhalf_s *lower_, + uint32_t *maxtimeout) +{ + uint64_t maxtimeout64 = usec_from_count( + NVIC_SYSTICK_RELOAD_MASK, g_systick_lower.freq); + + if (maxtimeout64 > UINT32_MAX) + { + *maxtimeout = UINT32_MAX; + } + else + { + *maxtimeout = maxtimeout64; + } + + return 0; +} + +static int systick_interrupt(int irq, FAR void *context, FAR void *arg) +{ + struct systick_lowerhalf_s *lower = arg; + + if (lower->callback && systick_is_running()) + { + uint32_t reload = getreg32(NVIC_SYSTICK_RELOAD); + uint32_t interval = usec_from_count(reload, lower->freq); + uint32_t next_interval = interval; + + lower->next_interval = &next_interval; + if (lower->callback(&next_interval, lower->arg)) + { + if (next_interval && next_interval != interval) + { + reload = usec_to_count(next_interval, lower->freq); + putreg32(reload, NVIC_SYSTICK_RELOAD); + putreg32(0, NVIC_SYSTICK_CURRENT); + } + } + else + { + modifyreg32(NVIC_SYSTICK_CTRL, NVIC_SYSTICK_CTRL_ENABLE, 0); + } + + lower->next_interval = NULL; + } + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct timer_lowerhalf_s *systick_initialize(bool coreclk, + unsigned int freq, int minor) +{ + struct systick_lowerhalf_s *lower = (struct systick_lowerhalf_s *)&g_systick_lower; + + /* Calculate the working clock frequency if need */ + + if (freq == 0) + { + uint32_t calib = getreg32(NVIC_SYSTICK_CALIB); + uint32_t tenms = calib & NVIC_SYSTICK_CALIB_TENMS_MASK; + lower->freq = 100 * (tenms + 1); + } + else + { + lower->freq = freq; + } + + /* Disable SYSTICK, but enable interrupt */ + + if (coreclk) + { + putreg32(NVIC_SYSTICK_CTRL_CLKSOURCE | NVIC_SYSTICK_CTRL_TICKINT, NVIC_SYSTICK_CTRL); + } + else + { + putreg32(NVIC_SYSTICK_CTRL_TICKINT, NVIC_SYSTICK_CTRL); + } + + irq_attach(NVIC_IRQ_SYSTICK, systick_interrupt, lower); + up_enable_irq(NVIC_IRQ_SYSTICK); + + /* Register the timer driver if need */ + + if (minor >= 0) + { + char devname[32]; + + sprintf(devname, "/dev/timer%d", minor); + timer_register(devname, (struct timer_lowerhalf_s *)lower); + } + + return (struct timer_lowerhalf_s *)lower; +} + +#endif /* CONFIG_ARMV7M_SYSTICK */