zephyr/drivers/timer/cc13xx_cc26xx_rtc_timer.c
Florian Grandel 75c83edc48 dts: ti: cc13xx_cc26xx: devicetree sysclk alignment
This change introduces the "_rtc_timer" suffix for the system tick timer
driver "compatible" property and aligns naming conventions with the
actual CC13/26xx SoC series product policy.

This frees up the "_rtc" namespace to introduce additional APIs based on
the same peripheral in the future (not part of this PR):

rtc: rtc@... {
  compatible = "ti,cc13xx-cc26xx-rtc";
  ...

  timer {
    compatible = "ti,cc13xx-cc26xx-rtc-timer";
    ...
  };

  counter {
    compatible = "ti,cc13xx-cc26xx-rtc-counter";
    ...
  };

  pps {
    compatible = "ti,cc13xx-cc26xx-rtc-pps";
    ...
  };
};

Or alternatively an MFD pattern with similar requirements.

Fixing the namespacing now makes sense standalone as it reduces the
chance of custom drivers being broken in the future.

Redundant extension of the mandatory system clock devicetree node is
replaced with a single `status = "okay"` which seems to be the more
sensible default to avoid user error when defining custom boards.
Knowledgeable users can still override this if really needed.

Signed-off-by: Florian Grandel <fgrandel@code-for-humans.de>
2023-07-07 18:46:24 -04:00

256 lines
5.8 KiB
C

/*
* Copyright (c) 2019, Texas Instruments Incorporated
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ti_cc13xx_cc26xx_rtc_timer
/*
* TI SimpleLink CC13X2/CC26X2 RTC-based system timer
*
* This system timer implementation supports both tickless and ticking modes.
* RTC counts continually in 64-bit mode and timeouts are
* scheduled using the RTC comparator. An interrupt is triggered whenever
* the comparator value set is reached.
*/
#include <zephyr/device.h>
#include <soc.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/irq.h>
#include <zephyr/spinlock.h>
#include <zephyr/sys_clock.h>
#include <zephyr/sys/util.h>
#include <driverlib/interrupt.h>
#include <driverlib/aon_rtc.h>
#include <driverlib/aon_event.h>
#define RTC_COUNTS_PER_SEC 0x100000000ULL
/* Number of counts per rtc timer cycle */
#define RTC_COUNTS_PER_CYCLE (RTC_COUNTS_PER_SEC / \
sys_clock_hw_cycles_per_sec())
/* Number of counts per system clock tick */
#define RTC_COUNTS_PER_TICK (RTC_COUNTS_PER_SEC / \
CONFIG_SYS_CLOCK_TICKS_PER_SEC)
/* Number of RTC cycles per system clock tick */
#define CYCLES_PER_TICK (sys_clock_hw_cycles_per_sec() / \
CONFIG_SYS_CLOCK_TICKS_PER_SEC)
/*
* Maximum number of ticks.
*/
#define MAX_CYC 0x7FFFFFFFFFFFULL
#define MAX_TICKS (MAX_CYC / RTC_COUNTS_PER_TICK)
/*
* Due to the nature of clock synchronization, the comparator cannot be set
* to a value that is too close to the current time. This constant defines
* a safe threshold for the comparator.
*/
#define COMPARE_MARGIN 6
/* RTC count of the last announce call, rounded down to tick boundary. */
static volatile uint64_t rtc_last;
#ifdef CONFIG_TICKLESS_KERNEL
static struct k_spinlock lock;
#else
static uint64_t nextThreshold = RTC_COUNTS_PER_TICK;
#endif /* CONFIG_TICKLESS_KERNEL */
static void setThreshold(uint32_t next)
{
uint32_t now;
unsigned int key;
key = irq_lock();
/* get the current RTC count corresponding to compare window */
now = AONRTCCurrentCompareValueGet();
/* if next is too soon, set at least one RTC tick in future */
/* assume next never be more than half the maximum 32 bit count value */
if ((next - now) > (uint32_t)0x80000000) {
/* now is past next */
next = now + COMPARE_MARGIN;
} else if ((now + COMPARE_MARGIN - next) < (uint32_t)0x80000000) {
if (next < now + COMPARE_MARGIN) {
next = now + COMPARE_MARGIN;
}
}
/* set next compare threshold in RTC */
AONRTCCompareValueSet(AON_RTC_CH0, next);
irq_unlock(key);
}
void rtc_isr(const void *arg)
{
#ifndef CONFIG_TICKLESS_KERNEL
uint64_t newThreshold;
uint32_t next;
#else
uint64_t ticks, currCount;
#endif
ARG_UNUSED(arg);
AONRTCEventClear(AON_RTC_CH0);
#ifdef CONFIG_TICKLESS_KERNEL
k_spinlock_key_t key = k_spin_lock(&lock);
currCount = (uint64_t)AONRTCCurrent64BitValueGet();
ticks = (currCount - rtc_last) / RTC_COUNTS_PER_TICK;
rtc_last += ticks * RTC_COUNTS_PER_TICK;
k_spin_unlock(&lock, key);
sys_clock_announce(ticks);
#else /* !CONFIG_TICKLESS_KERNEL */
/* calculate new 64-bit RTC count for next interrupt */
newThreshold = nextThreshold + RTC_COUNTS_PER_TICK;
next = (uint32_t)((uint64_t)newThreshold >> 16);
setThreshold(next);
nextThreshold = newThreshold;
rtc_last += RTC_COUNTS_PER_TICK;
sys_clock_announce(1);
#endif /* CONFIG_TICKLESS_KERNEL */
}
static void initDevice(void)
{
AONRTCDisable();
AONRTCReset();
HWREG(AON_RTC_BASE + AON_RTC_O_SYNC) = 1;
/* read sync register to complete reset */
HWREG(AON_RTC_BASE + AON_RTC_O_SYNC);
AONRTCEventClear(AON_RTC_CH0);
IntPendClear(INT_AON_RTC_COMB);
HWREG(AON_RTC_BASE + AON_RTC_O_SYNC);
}
static void startDevice(void)
{
uint32_t compare;
uint64_t period;
unsigned int key;
key = irq_lock();
/* reset timer */
AONRTCReset();
AONRTCEventClear(AON_RTC_CH0);
IntPendClear(INT_AON_RTC_COMB);
/*
* set the compare register to one period.
* For a very small period round up to interrupt upon 4th tick in
* compare register
*/
period = RTC_COUNTS_PER_TICK;
if (period < 0x40000) {
compare = 0x4; /* 4 * 15.5us ~= 62us */
} else {
/* else, interrupt on first period expiration */
compare = period >> 16;
}
/* set the compare value at the RTC */
AONRTCCompareValueSet(AON_RTC_CH0, compare);
/* enable compare channel 0 */
AONEventMcuWakeUpSet(AON_EVENT_MCU_WU0, AON_EVENT_RTC0);
AONRTCChannelEnable(AON_RTC_CH0);
AONRTCCombinedEventConfig(AON_RTC_CH0);
/* start timer */
AONRTCEnable();
irq_unlock(key);
}
void sys_clock_set_timeout(int32_t ticks, bool idle)
{
ARG_UNUSED(idle);
#ifdef CONFIG_TICKLESS_KERNEL
ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : ticks;
ticks = CLAMP(ticks - 1, 0, (int32_t) MAX_TICKS);
k_spinlock_key_t key = k_spin_lock(&lock);
/* Compute number of RTC cycles until the next timeout. */
uint64_t count = AONRTCCurrent64BitValueGet();
uint64_t timeout = ticks * RTC_COUNTS_PER_TICK +
(count - rtc_last);
/* Round to the nearest tick boundary. */
timeout = DIV_ROUND_UP(timeout, RTC_COUNTS_PER_TICK) *
RTC_COUNTS_PER_TICK;
timeout = MIN(timeout, MAX_CYC);
timeout += rtc_last;
/* Set the comparator */
setThreshold(timeout >> 16);
k_spin_unlock(&lock, key);
#endif /* CONFIG_TICKLESS_KERNEL */
}
uint32_t sys_clock_elapsed(void)
{
uint32_t ret = (AONRTCCurrent64BitValueGet() - rtc_last) /
RTC_COUNTS_PER_TICK;
return ret;
}
uint32_t sys_clock_cycle_get_32(void)
{
return (uint32_t)(AONRTCCurrent64BitValueGet() / RTC_COUNTS_PER_CYCLE);
}
uint64_t sys_clock_cycle_get_64(void)
{
return AONRTCCurrent64BitValueGet() / RTC_COUNTS_PER_CYCLE;
}
static int sys_clock_driver_init(void)
{
rtc_last = 0U;
initDevice();
startDevice();
/* Enable RTC interrupt. */
IRQ_CONNECT(DT_INST_IRQN(0),
DT_INST_IRQ(0, priority),
rtc_isr, 0, 0);
irq_enable(DT_INST_IRQN(0));
return 0;
}
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);