diff --git a/boards/arm/atsamd20_xpro/atsamd20_xpro.dts b/boards/arm/atsamd20_xpro/atsamd20_xpro.dts index a530e4c37c8..59fafedffa0 100644 --- a/boards/arm/atsamd20_xpro/atsamd20_xpro.dts +++ b/boards/arm/atsamd20_xpro/atsamd20_xpro.dts @@ -79,3 +79,7 @@ }; }; }; + +&rtc { + status = "ok"; +}; diff --git a/drivers/timer/CMakeLists.txt b/drivers/timer/CMakeLists.txt index f3dee5eb8bc..d758930e16b 100644 --- a/drivers/timer/CMakeLists.txt +++ b/drivers/timer/CMakeLists.txt @@ -10,3 +10,4 @@ zephyr_sources_if_kconfig( rv32m1_lptmr_timer.c) zephyr_sources_if_kconfig( cortex_m_systick.c) zephyr_sources_ifdef(CONFIG_XTENSA_TIMER xtensa_sys_timer.c) zephyr_sources_if_kconfig( native_posix_timer.c) +zephyr_sources_if_kconfig( sam0_rtc_timer.c) diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig index 2bc5d565a06..20ec434b8c7 100644 --- a/drivers/timer/Kconfig +++ b/drivers/timer/Kconfig @@ -175,6 +175,15 @@ config XTENSA_TIMER_ID carefully. Generally you want the timer with the highest priority maskable interrupt. +config SAM0_RTC_TIMER + bool "Atmel SAM0 series RTC timer" + depends on SOC_FAMILY_SAM0 + select TICKLESS_CAPABLE + help + This module implements a kernel device driver for the Atmel SAM0 + series Real Time Counter and provides the standard "system clock + driver" interfaces. + config SYSTEM_CLOCK_DISABLE bool "API to disable system clock" help diff --git a/drivers/timer/sam0_rtc_timer.c b/drivers/timer/sam0_rtc_timer.c new file mode 100644 index 00000000000..61d768b0409 --- /dev/null +++ b/drivers/timer/sam0_rtc_timer.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2018 omSquare s.r.o. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Atmel SAM0 series RTC-based system timer + * + * This system timer implementation supports both tickless and ticking modes. + * In tickless mode, RTC counts continually in 32-bit mode and timeouts are + * scheduled using the RTC comparator. In ticking mode, RTC is configured to + * generate an interrupt every tick. + */ + +#include +#include +#include +#include + +/* RTC registers. */ +#define RTC0 ((RtcMode0 *) DT_RTC_SAM0_BASE_ADDRESS) + +/* Number of sys timer cycles per on tick. */ +#define CYCLES_PER_TICK (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC \ + / CONFIG_SYS_CLOCK_TICKS_PER_SEC) + +/* Maximum number of ticks. */ +#define MAX_TICKS (UINT32_MAX / CYCLES_PER_TICK - 2) + +#ifdef CONFIG_TICKLESS_KERNEL + +/* + * Due to the nature of clock synchronization, reading from or writing to some + * RTC registers takes approximately six RTC_GCLK cycles. This constant defines + * a safe threshold for the comparator. + */ +#define TICK_THRESHOLD 7 + +BUILD_ASSERT_MSG(CYCLES_PER_TICK > TICK_THRESHOLD, + "CYCLES_PER_TICK must be greater than TICK_THRESHOLD for " + "tickless mode"); + +#else /* !CONFIG_TICKLESS_KERNEL */ + +/* + * For some reason, RTC does not generate interrupts when COMP == 0, + * MATCHCLR == 1 and PRESCALER == 0. So we need to check that CYCLES_PER_TICK + * is more than one. + */ +BUILD_ASSERT_MSG(CYCLES_PER_TICK > 1, + "CYCLES_PER_TICK must be greater than 1 for ticking mode"); + +#endif /* CONFIG_TICKLESS_KERNEL */ + +/* Helper macro to get the correct GCLK GEN based on configuration. */ +#define GCLK_GEN(n) GCLK_EVAL(n) +#define GCLK_EVAL(n) GCLK_CLKCTRL_GEN_GCLK##n + +/* Tick/cycle count of the last announce call. */ +static volatile u32_t rtc_last; + +#ifndef CONFIG_TICKLESS_KERNEL + +/* Current tick count. */ +static volatile u32_t rtc_counter; + +/* Tick value of the next timeout. */ +static volatile u32_t rtc_timeout; + +#endif /* CONFIG_TICKLESS_KERNEL */ + +/* + * Waits for RTC bus synchronization. + */ +static inline void rtc_sync(void) +{ + while (RTC0->STATUS.reg & RTC_STATUS_SYNCBUSY) { + /* Wait for bus synchronization... */ + } +} + +/* + * Reads RTC COUNT register. First a read request must be written to READREQ, + * then - when bus synchronization completes - the COUNT register is read and + * returned. + */ +static u32_t rtc_count(void) +{ + RTC0->READREQ.reg = RTC_READREQ_RREQ; + rtc_sync(); + return RTC0->COUNT.reg; +} + +static void rtc_reset(void) +{ + rtc_sync(); + + /* Disable interrupt. */ + RTC0->INTENCLR.reg = RTC_MODE0_INTENCLR_MASK; + /* Clear interrupt flag. */ + RTC0->INTFLAG.reg = RTC_MODE0_INTFLAG_MASK; + + /* Disable RTC module. */ + RTC0->CTRL.reg &= ~RTC_MODE0_CTRL_ENABLE; + + rtc_sync(); + + /* Initiate software reset. */ + RTC0->CTRL.reg |= RTC_MODE0_CTRL_SWRST; +} + +static void rtc_isr(void *arg) +{ + ARG_UNUSED(arg); + + /* Read and clear the interrupt flag register. */ + u16_t status = RTC0->INTFLAG.reg; + + RTC0->INTFLAG.reg = status; + +#ifdef CONFIG_TICKLESS_KERNEL + + /* Read the current counter and announce the elapsed time in ticks. */ + u32_t count = rtc_count(); + + if (count != rtc_last) { + u32_t ticks = (count - rtc_last) / CYCLES_PER_TICK; + + z_clock_announce(ticks); + rtc_last += ticks * CYCLES_PER_TICK; + } + +#else /* !CONFIG_TICKLESS_KERNEL */ + + if (status) { + /* RTC just ticked one more tick... */ + if (++rtc_counter == rtc_timeout) { + z_clock_announce(rtc_counter - rtc_last); + rtc_last = rtc_counter; + } + } else { + /* ISR was invoked directly from z_clock_set_timeout. */ + z_clock_announce(0); + } + +#endif /* CONFIG_TICKLESS_KERNEL */ +} + +int z_clock_driver_init(struct device *device) +{ + ARG_UNUSED(device); + + /* Set up bus clock and GCLK generator. */ + PM->APBAMASK.reg |= PM_APBAMASK_RTC; + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(RTC_GCLK_ID) | GCLK_CLKCTRL_CLKEN + | GCLK_GEN(DT_RTC_SAM0_CLOCK_GENERATOR); + + while (GCLK->STATUS.bit.SYNCBUSY) { + /* Synchronize GCLK. */ + } + + /* Reset module to hardware defaults. */ + rtc_reset(); + + rtc_last = 0; + + /* Configure RTC with 32-bit mode, configured prescaler and MATCHCLR. */ + u16_t ctrl = RTC_MODE0_CTRL_MODE(0) | RTC_MODE0_CTRL_PRESCALER(0); +#ifndef CONFIG_TICKLESS_KERNEL + ctrl |= RTC_MODE0_CTRL_MATCHCLR; +#endif + rtc_sync(); + RTC0->CTRL.reg = ctrl; + +#ifdef CONFIG_TICKLESS_KERNEL + /* Tickless kernel lets RTC count continually and ignores overflows. */ + RTC0->INTENSET.reg = RTC_MODE0_INTENSET_CMP0; +#else + /* Non-tickless mode uses comparator together with MATCHCLR. */ + rtc_sync(); + RTC0->COMP[0].reg = CYCLES_PER_TICK; + RTC0->INTENSET.reg = RTC_MODE0_INTENSET_OVF; + rtc_counter = 0; + rtc_timeout = 0; +#endif + + /* Enable RTC module. */ + rtc_sync(); + RTC0->CTRL.reg |= RTC_MODE0_CTRL_ENABLE; + + /* Enable RTC interrupt. */ + NVIC_ClearPendingIRQ(DT_RTC_SAM0_IRQ); + IRQ_CONNECT(DT_RTC_SAM0_IRQ, DT_RTC_SAM0_IRQ_PRIORITY, rtc_isr, 0, 0); + irq_enable(DT_RTC_SAM0_IRQ); + + return 0; +} + +void z_clock_set_timeout(s32_t ticks, bool idle) +{ + ARG_UNUSED(idle); + +#ifdef CONFIG_TICKLESS_KERNEL + + ticks = (ticks == K_FOREVER) ? MAX_TICKS : ticks; + ticks = max(min(ticks - 1, (s32_t) MAX_TICKS), 0); + + /* Compute number of RTC cycles until the next timeout. */ + u32_t count = rtc_count(); + u32_t timeout = ticks * CYCLES_PER_TICK + count % CYCLES_PER_TICK; + + /* Round to the nearest tick boundary. */ + timeout = (timeout + CYCLES_PER_TICK - 1) / CYCLES_PER_TICK + * CYCLES_PER_TICK; + + if (timeout < TICK_THRESHOLD) { + timeout += CYCLES_PER_TICK; + } + + rtc_sync(); + RTC0->COMP[0].reg = count + timeout; + +#else /* !CONFIG_TICKLESS_KERNEL */ + + if (ticks == K_FOREVER) { + /* Disable comparator for K_FOREVER and other negative + * values. + */ + rtc_timeout = rtc_counter; + return; + } + + if (ticks < 1) { + ticks = 1; + } + + /* Avoid race condition between reading counter and ISR incrementing + * it. + */ + int key = irq_lock(); + + rtc_timeout = rtc_counter + ticks; + irq_unlock(key); + +#endif /* CONFIG_TICKLESS_KERNEL */ +} + +u32_t z_clock_elapsed(void) +{ +#ifdef CONFIG_TICKLESS_KERNEL + return (rtc_count() - rtc_last) / CYCLES_PER_TICK; +#else + return rtc_counter - rtc_last; +#endif +} + +u32_t _timer_cycle_get_32(void) +{ + /* Just return the absolute value of RTC cycle counter. */ + return rtc_count(); +} diff --git a/dts/arm/atmel/samd.dtsi b/dts/arm/atmel/samd.dtsi index 1750e2bfd05..4d0d58af1a4 100644 --- a/dts/arm/atmel/samd.dtsi +++ b/dts/arm/atmel/samd.dtsi @@ -134,6 +134,15 @@ num-bidir-endpoints = <8>; label = "USB0"; }; + + rtc: rtc@40001400 { + compatible = "atmel,sam0-rtc"; + reg = <0x40001400 0x1C>; + interrupts = <3 0>; + clock-generator = <0>; + status = "disabled"; + label = "RTC"; + }; }; }; diff --git a/dts/bindings/rtc/atmel,sam0-rtc.yaml b/dts/bindings/rtc/atmel,sam0-rtc.yaml new file mode 100644 index 00000000000..70d73290934 --- /dev/null +++ b/dts/bindings/rtc/atmel,sam0-rtc.yaml @@ -0,0 +1,31 @@ +# +# Copyright (c) 2018 omSquare s.r.o. +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +title: Atmel SAM0 RTC +version: 0.1 + +description: > + This binding gives a base representation of the Atmel SAM0 RTC + +inherits: + !include rtc.yaml + +properties: + compatible: + constraint: "atmel,sam0-rtc" + + reg: + type: array + description: mmio register space + generation: define + category: required + + clock-generator: + type: int + description: clock generator index + generation: define + category: required +... diff --git a/soc/arm/atmel_sam0/samd20/dts_fixup.h b/soc/arm/atmel_sam0/samd20/dts_fixup.h index 1a352e26bc2..e760b6a8a65 100644 --- a/soc/arm/atmel_sam0/samd20/dts_fixup.h +++ b/soc/arm/atmel_sam0/samd20/dts_fixup.h @@ -99,6 +99,11 @@ #define DT_PINMUX_SAM0_B_BASE_ADDRESS DT_ATMEL_SAM0_PINMUX_41004480_BASE_ADDRESS #define DT_PINMUX_SAM0_B_LABEL DT_ATMEL_SAM0_PINMUX_41004480_LABEL +#define DT_RTC_SAM0_BASE_ADDRESS DT_ATMEL_SAM0_RTC_40001400_BASE_ADDRESS +#define DT_RTC_SAM0_IRQ DT_ATMEL_SAM0_RTC_40001400_IRQ_0 +#define DT_RTC_SAM0_IRQ_PRIORITY DT_ATMEL_SAM0_RTC_40001400_IRQ_0_PRIORITY +#define DT_RTC_SAM0_CLOCK_GENERATOR DT_ATMEL_SAM0_RTC_40001400_CLOCK_GENERATOR + #define DT_NUM_IRQ_PRIO_BITS DT_ARM_V6M_NVIC_E000E100_ARM_NUM_IRQ_PRIORITY_BITS /* End of SoC Level DTS fixup file */