blob: c57661cf6fa8f9fd9758c6d2a92569f9b2ee8fcf [file] [log] [blame]
/****************************************************************************
* arch/xtensa/src/esp32/esp32_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(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(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 sched_timer_expiration(void): Called by the platform-specific
* logic when the interval timer expires.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <assert.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/clock.h>
#include <arch/xtensa/xtensa_specregs.h>
#include <arch/xtensa/core_macros.h>
#include <arch/board/board.h>
#include "xtensa.h"
#include "xtensa_attr.h"
#include "xtensa_counter.h"
#ifdef CONFIG_SCHED_TICKLESS
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define CTICK_PER_SEC (BOARD_CLOCK_FREQUENCY)
#define CTICK_PER_USEC (CTICK_PER_SEC / USEC_PER_SEC)
#define SEC_2_CTICK(s) ((s) * CTICK_PER_SEC)
#define USEC_2_CTICK(us) ((us) * CTICK_PER_USEC)
#define NSEC_2_CTICK(nsec) (((nsec) * CTICK_PER_USEC) / NSEC_PER_USEC)
#define CTICK_2_SEC(tick) ((tick) / CTICK_PER_SEC)
#define CTICK_2_USEC(tick) ((tick) / CTICK_PER_USEC)
#define CTICK_2_NSEC(tick) ((tick) * 1000 / CTICK_PER_USEC)
#define CPU_TICKS_MAX (UINT32_MAX / 4 * 3)
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static inline uint64_t up_tmr_total_count(void);
static inline uint64_t up_tmr_getcount(void);
static void IRAM_ATTR up_tmr_setcompare(uint32_t ticks);
static void IRAM_ATTR up_tmr_setcount(uint64_t ticks);
static int up_timer_expire(int irq, void *regs, void *arg);
/****************************************************************************
* Private Data
****************************************************************************/
static bool g_timer_started; /* Whether an interval timer is being started */
static uint64_t g_cticks; /* Total ticks of system since power-on */
static uint32_t g_loop_cnt; /* System Cycle counter cycle times */
/* Redundant ticks of an interval timer on the cycle counter */
static uint32_t g_last_cticks;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: up_tmr_total_count
*
* Description:
* Return the total ticks of system since power-on.
*
* Input Parameters:
* None
*
* Returned Value:
* Total system ticks
*
****************************************************************************/
static inline uint64_t up_tmr_total_count(void)
{
return g_cticks + xtensa_getcount();
}
/****************************************************************************
* Name: up_tmr_getcount
*
* Description:
* Return the remaining ticks in the currently running timer.
*
* Input Parameters:
* None
*
* Returned Value:
* Remaining ticks
*
****************************************************************************/
static inline uint64_t up_tmr_getcount(void)
{
return (uint64_t)g_loop_cnt * CPU_TICKS_MAX +
g_last_cticks;
}
/****************************************************************************
* Name: up_tmr_setcompare
*
* Description:
* Set the value of the compare register, save the currently running
* system tick and clear cycle count register.
*
* Input Parameters:
* ticks - Set the new value of the compare register
*
* Returned Value:
* None
*
****************************************************************************/
static void IRAM_ATTR up_tmr_setcompare(uint32_t ticks)
{
xtensa_setcompare(ticks);
g_cticks += xtensa_getcount();
xtensa_setcount(0);
}
/****************************************************************************
* Name: up_tmr_setcount
*
* Description:
* Set the value of the compare register
*
* Input Parameters:
* ticks - ticks for a timer operation
*
* Returned Value:
* None
*
****************************************************************************/
static void IRAM_ATTR up_tmr_setcount(uint64_t ticks)
{
irqstate_t flags;
uint32_t loop_cnt;
uint32_t last_ticks;
if (ticks == 0)
{
ticks = 1;
}
loop_cnt = ticks / CPU_TICKS_MAX;
last_ticks = ticks % CPU_TICKS_MAX;
if (loop_cnt != 0)
{
xtensa_setcompare(CPU_TICKS_MAX);
}
else
{
xtensa_setcompare(last_ticks);
}
flags = enter_critical_section();
g_loop_cnt = loop_cnt;
g_last_cticks = last_ticks;
g_timer_started = true;
leave_critical_section(flags);
g_cticks += xtensa_getcount();
xtensa_setcount(0);
}
/****************************************************************************
* Name: up_timer_expire
*
* Description:
* Called as the IRQ handler for timer expiration.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static int up_timer_expire(int irq, void *regs, void *arg)
{
irqstate_t flags;
bool do_sched = false;
flags = enter_critical_section();
if (g_timer_started)
{
if (g_loop_cnt != 0)
{
--g_loop_cnt;
if (g_loop_cnt == 0)
{
if (g_last_cticks != 0)
{
up_tmr_setcompare(g_last_cticks);
}
else
{
do_sched = true;
up_tmr_setcompare(CPU_TICKS_MAX);
}
}
else
{
up_tmr_setcompare(CPU_TICKS_MAX);
}
}
else
{
do_sched = true;
}
if (do_sched)
{
up_timer_cancel(NULL);
nxsched_timer_expiration();
}
}
else
{
up_tmr_setcompare(CPU_TICKS_MAX);
}
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* 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, 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.
*
****************************************************************************/
int IRAM_ATTR up_timer_gettime(struct timespec *ts)
{
uint64_t ticks;
irqstate_t flags;
flags = enter_critical_section();
ticks = up_tmr_total_count();
ts->tv_sec = CTICK_2_SEC(ticks);
ts->tv_nsec = CTICK_2_NSEC(ticks % CTICK_PER_SEC);
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* 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.
*
****************************************************************************/
int IRAM_ATTR up_timer_cancel(struct timespec *ts)
{
uint64_t rst_ticks;
uint64_t cur_ticks;
uint64_t ticks;
irqstate_t flags;
flags = enter_critical_section();
if (ts != NULL)
{
if (!g_timer_started)
{
ts->tv_sec = 0;
ts->tv_nsec = 0;
}
else
{
rst_ticks = up_tmr_getcount();
cur_ticks = xtensa_getcount();
if (rst_ticks <= cur_ticks)
{
ticks = 0;
}
else
{
ticks = rst_ticks - cur_ticks;
}
ts->tv_sec = CTICK_2_SEC(ticks);
ts->tv_nsec = CTICK_2_NSEC(ticks % CTICK_PER_SEC);
}
}
g_timer_started = false;
up_tmr_setcompare(CPU_TICKS_MAX);
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* 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.
*
****************************************************************************/
int IRAM_ATTR up_timer_start(const struct timespec *ts)
{
uint64_t cpu_ticks;
irqstate_t flags;
flags = enter_critical_section();
if (g_timer_started)
{
up_timer_cancel(NULL);
}
cpu_ticks = SEC_2_CTICK((uint64_t)ts->tv_sec) +
NSEC_2_CTICK((uint64_t)ts->tv_nsec);
up_tmr_setcount(cpu_ticks);
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* 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)
{
/* Set up periodic timer */
up_tmr_setcompare(CPU_TICKS_MAX);
/* NOTE: Timer 0 is an internal interrupt source so we do not need to
* attach any peripheral ID to the dedicated CPU interrupt.
*/
/* Attach the timer interrupt */
irq_attach(XTENSA_IRQ_TIMER0, (xcpt_t)up_timer_expire, NULL);
/* Enable the timer 0 CPU interrupt. */
up_enable_irq(XTENSA_IRQ_TIMER0);
}
/****************************************************************************
* Name: up_get_idletime
*
* Description:
* This function returns the idle time.
*
* Input Parameters:
* None
*
* Returned Value:
* The time in system ticks remaining for idle.
* Zero means system is busy.
*
****************************************************************************/
uint32_t IRAM_ATTR up_get_idletime(void)
{
uint32_t us;
uint64_t ticks;
uint64_t rst_ticks;
irqstate_t flags;
flags = enter_critical_section();
if (!g_timer_started)
{
us = 0;
}
else
{
ticks = xtensa_getcount();
rst_ticks = up_tmr_getcount();
if (rst_ticks > ticks)
{
us = CTICK_2_USEC(rst_ticks - ticks);
}
else
{
us = 0;
}
}
leave_critical_section(flags);
return us;
}
/****************************************************************************
* Name: up_step_idletime
*
* Description:
* Add system time by idletime_us.
*
* Input Parameters:
* us - Idle time(us)
*
* Returned Value:
* None
*
****************************************************************************/
void IRAM_ATTR up_step_idletime(uint32_t us)
{
uint64_t ticks;
uint64_t timer_ticks;
irqstate_t flags;
DEBUGASSERT(g_timer_started);
flags = enter_critical_section();
ticks = USEC_2_CTICK((uint64_t)us);
timer_ticks = up_tmr_getcount();
DEBUGASSERT(ticks < timer_ticks);
g_cticks += ticks;
up_tmr_setcount(timer_ticks - ticks);
leave_critical_section(flags);
}
#endif /* CONFIG_SCHED_TICKLESS */