Switch SAMD21 ticks to PER event

The EVSYS is used to generate an interrupt from the event. This
simplifies timing used in pulseio that conflicted with the
auto-reload countdown.

Fixes #3890
This commit is contained in:
Scott Shawcroft 2021-08-10 15:23:45 -07:00
parent f805e63297
commit f0859ac954
No known key found for this signature in database
GPG Key ID: 0DFD512649C052DA
4 changed files with 126 additions and 95 deletions

View File

@ -149,7 +149,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
mp_hal_stdout_tx_strn("\x04", 1);
}
// check for SystemExit
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) {
if (mp_obj_is_subclass_fast(mp_obj_get_type((mp_obj_t)nlr.ret_val), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) {
// at the moment, the value of SystemExit is unused
ret = pyexec_system_exit;
#if CIRCUITPY_ALARM

View File

@ -265,13 +265,13 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma,
#ifdef SAM_D5X_E5X
int irq = dma->event_channel < 4 ? EVSYS_0_IRQn + dma->event_channel : EVSYS_4_IRQn;
// Only disable and clear on SAMD51 because the SAMD21 shares EVSYS with ticks.
NVIC_DisableIRQ(irq);
NVIC_ClearPendingIRQ(irq);
#else
int irq = EVSYS_IRQn;
#endif
NVIC_DisableIRQ(irq);
NVIC_ClearPendingIRQ(irq);
DmacDescriptor *first_descriptor = dma_descriptor(dma_channel);
setup_audio_descriptor(first_descriptor, dma->beat_size, output_spacing, output_register_address);
if (single_buffer) {
@ -366,7 +366,7 @@ STATIC void dma_callback_fun(void *arg) {
audio_dma_load_next_block(dma);
}
void evsyshandler_common(void) {
void audio_evsys_handler(void) {
for (uint8_t i = 0; i < AUDIO_DMA_CHANNEL_COUNT; i++) {
audio_dma_t *dma = audio_dma_state[i];
if (dma == NULL) {
@ -380,26 +380,4 @@ void evsyshandler_common(void) {
}
}
#ifdef SAM_D5X_E5X
void EVSYS_0_Handler(void) {
evsyshandler_common();
}
void EVSYS_1_Handler(void) {
evsyshandler_common();
}
void EVSYS_2_Handler(void) {
evsyshandler_common();
}
void EVSYS_3_Handler(void) {
evsyshandler_common();
}
void EVSYS_4_Handler(void) {
evsyshandler_common();
}
#else
void EVSYS_Handler(void) {
evsyshandler_common();
}
#endif
#endif

View File

@ -99,4 +99,6 @@ void audio_dma_background(void);
uint8_t find_sync_event_channel_raise(void);
void audio_evsys_handler(void);
#endif // MICROPY_INCLUDED_ATMEL_SAMD_AUDIO_DMA_H

View File

@ -91,26 +91,19 @@
#if CIRCUITPY_PEW
#include "common-hal/_pew/PewPew.h"
#endif
volatile bool hold_interrupt = false;
static volatile bool sleep_ok = true;
#ifdef SAMD21
static void rtc_set_continuous(bool continuous) {
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
;
}
RTC->MODE0.READREQ.reg = (continuous ? RTC_READREQ_RCONT : 0) | 0x0010;
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
;
}
}
static uint8_t _tick_event_channel = 0;
// Sleeping requires a register write that can stall interrupt handling. Turning
// off sleeps allows for more accurate interrupt timing. (Python still thinks
// it is sleeping though.)
void rtc_start_pulse(void) {
rtc_set_continuous(true);
hold_interrupt = true;
sleep_ok = false;
}
void rtc_end_pulse(void) {
hold_interrupt = false;
rtc_set_continuous(false);
sleep_ok = true;
}
#endif
@ -161,6 +154,20 @@ static void save_usb_clock_calibration(void) {
}
#endif
static void rtc_continuous_mode(void) {
#ifdef SAMD21
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
}
RTC->MODE0.READREQ.reg = RTC_READREQ_RCONT | 0x0010;
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
}
// Do the first request and wait for it.
RTC->MODE0.READREQ.reg = RTC_READREQ_RREQ | RTC_READREQ_RCONT | 0x0010;
while (RTC->MODE0.STATUS.bit.SYNCBUSY) {
}
#endif
}
static void rtc_init(void) {
#ifdef SAMD21
_gclk_enable_channel(RTC_GCLK_ID, GCLK_CLKCTRL_GEN_GCLK2_Val);
@ -168,9 +175,17 @@ static void rtc_init(void) {
while (RTC->MODE0.CTRL.bit.SWRST != 0) {
}
// Turn on periodic events to use as tick. We control whether it interrupts
// us with the EVSYS INTEN register.
RTC->MODE0.EVCTRL.reg = RTC_MODE0_EVCTRL_PEREO2;
RTC->MODE0.CTRL.reg = RTC_MODE0_CTRL_ENABLE |
RTC_MODE0_CTRL_MODE_COUNT32 |
RTC_MODE0_CTRL_PRESCALER_DIV2;
// Turn on continuous sync of the count register. This will speed up all
// tick reads.
rtc_continuous_mode();
#endif
#ifdef SAM_D5X_E5X
hri_mclk_set_APBAMASK_RTC_bit(MCLK);
@ -363,6 +378,9 @@ void reset_port(void) {
#endif
reset_event_system();
#ifdef SAMD21
_tick_event_channel = EVSYS_SYNCH_NUM;
#endif
reset_all_pins();
@ -430,21 +448,14 @@ uint32_t port_get_saved_word(void) {
// TODO: Move this to an RTC backup register so we can preserve it when only the BACKUP power domain
// is enabled.
static volatile uint64_t overflowed_ticks = 0;
#ifdef SAMD21
static volatile bool _ticks_enabled = false;
#endif
static uint32_t _get_count(uint64_t *overflow_count) {
#ifdef SAM_D5X_E5X
while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COUNTSYNC | RTC_MODE0_SYNCBUSY_COUNT)) != 0) {
}
#endif
#ifdef SAMD21
// Request a read so we don't stall the bus later. See section 14.3.1.5 Read Request
RTC->MODE0.READREQ.reg = RTC_READREQ_RREQ | 0x0010;
while (RTC->MODE0.STATUS.bit.SYNCBUSY != 0) {
}
#endif
// SAMD21 does continuous sync so we don't need to wait here.
// Disable interrupts so we can grab the count and the overflow.
common_hal_mcu_disable_interrupts();
uint32_t count = RTC->MODE0.COUNT.reg;
@ -458,29 +469,6 @@ static uint32_t _get_count(uint64_t *overflow_count) {
volatile bool _woken_up;
static void _port_interrupt_after_ticks(uint32_t ticks) {
uint32_t current_ticks = _get_count(NULL);
if (ticks > 1 << 28) {
// We'll interrupt sooner with an overflow.
return;
}
#ifdef SAMD21
if (hold_interrupt) {
return;
}
#endif
uint32_t target = current_ticks + (ticks << 4);
RTC->MODE0.COMP[0].reg = target;
#ifdef SAM_D5X_E5X
while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COMP0)) != 0) {
}
#endif
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0;
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_CMP0;
current_ticks = _get_count(NULL);
_woken_up = current_ticks >= target;
}
void RTC_Handler(void) {
uint32_t intflag = RTC->MODE0.INTFLAG.reg;
if (intflag & RTC_MODE0_INTFLAG_OVF) {
@ -497,19 +485,10 @@ void RTC_Handler(void) {
}
#endif
if (intflag & RTC_MODE0_INTFLAG_CMP0) {
// Clear the interrupt because we may have hit a sleep and _ticks_enabled
// Clear the interrupt because we may have hit a sleep
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0;
_woken_up = true;
#ifdef SAMD21
if (_ticks_enabled) {
// Do things common to all ports when the tick occurs.
supervisor_tick();
// Check _ticks_enabled again because a tick handler may have turned it off.
if (_ticks_enabled) {
_port_interrupt_after_ticks(1);
}
}
#endif
// SAMD21 ticks are handled by EVSYS
#ifdef SAM_D5X_E5X
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_CMP0;
#endif
@ -526,6 +505,39 @@ uint64_t port_get_raw_ticks(uint8_t *subticks) {
return overflow_count + current_ticks / 16;
}
void evsyshandler_common(void) {
#ifdef SAMD21
if (_tick_event_channel < EVSYS_SYNCH_NUM && event_interrupt_active(_tick_event_channel)) {
supervisor_tick();
}
#endif
#if CIRCUITPY_AUDIOIO || CIRCUITPY_AUDIOBUSIO
audio_evsys_handler();
#endif
}
#ifdef SAM_D5X_E5X
void EVSYS_0_Handler(void) {
evsyshandler_common();
}
void EVSYS_1_Handler(void) {
evsyshandler_common();
}
void EVSYS_2_Handler(void) {
evsyshandler_common();
}
void EVSYS_3_Handler(void) {
evsyshandler_common();
}
void EVSYS_4_Handler(void) {
evsyshandler_common();
}
#else
void EVSYS_Handler(void) {
evsyshandler_common();
}
#endif
// Enable 1/1024 second tick.
void port_enable_tick(void) {
#ifdef SAM_D5X_E5X
@ -533,9 +545,23 @@ void port_enable_tick(void) {
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_PER2;
#endif
#ifdef SAMD21
// TODO: Switch to using the PER *event* from the RTC to generate an interrupt via EVSYS.
_ticks_enabled = true;
_port_interrupt_after_ticks(1);
// SAMD21 ticks won't survive port_reset(). This *should* be ok since it'll
// be triggered by ticks and no Python will be running.
if (_tick_event_channel >= EVSYS_SYNCH_NUM) {
turn_on_event_system();
_tick_event_channel = find_sync_event_channel();
}
// This turns on both the event detected interrupt (EVD) and overflow (OVR).
init_event_channel_interrupt(_tick_event_channel, CORE_GCLK, EVSYS_ID_GEN_RTC_PER_2);
// Disable overflow interrupt because we ignore it.
if (_tick_event_channel >= 8) {
uint8_t value = 1 << (_tick_event_channel - 8);
EVSYS->INTENCLR.reg = EVSYS_INTENSET_OVRp8(value);
} else {
uint8_t value = 1 << _tick_event_channel;
EVSYS->INTENCLR.reg = EVSYS_INTENSET_OVR(value);
}
NVIC_EnableIRQ(EVSYS_IRQn);
#endif
}
@ -545,21 +571,46 @@ void port_disable_tick(void) {
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_PER2;
#endif
#ifdef SAMD21
_ticks_enabled = false;
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_CMP0;
if (_tick_event_channel >= 8) {
uint8_t value = 1 << (_tick_event_channel - 8);
EVSYS->INTENCLR.reg = EVSYS_INTENSET_EVDp8(value);
} else {
uint8_t value = 1 << _tick_event_channel;
EVSYS->INTENCLR.reg = EVSYS_INTENSET_EVD(value);
}
#endif
}
// This is called by sleep, we ignore it when our ticks are enabled because
// they'll wake us up earlier. If we don't, we'll mess up ticks by overwriting
// the next RTC wake up time.
void port_interrupt_after_ticks(uint32_t ticks) {
uint32_t current_ticks = _get_count(NULL);
if (ticks > 1 << 28) {
// We'll interrupt sooner with an overflow.
return;
}
#ifdef SAMD21
if (_ticks_enabled) {
if (!sleep_ok) {
return;
}
#endif
_port_interrupt_after_ticks(ticks);
uint32_t target = current_ticks + (ticks << 4);
// Try and avoid a bus stall when writing COMP by checking for an obvious
// existing sync.
while (RTC->MODE0.STATUS.bit.SYNCBUSY == 1) {
}
// Writing the COMP register can take up to 180us to synchronize. During
// this time, the bus will stall and no interrupts will be serviced.
RTC->MODE0.COMP[0].reg = target;
#ifdef SAM_D5X_E5X
while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COMP0)) != 0) {
}
#endif
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0;
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_CMP0;
// Set continuous mode again because setting COMP may disable it.
rtc_continuous_mode();
current_ticks = _get_count(NULL);
_woken_up = current_ticks >= target;
}
void port_idle_until_interrupt(void) {
@ -571,7 +622,7 @@ void port_idle_until_interrupt(void) {
}
#endif
common_hal_mcu_disable_interrupts();
if (!tud_task_event_ready() && !hold_interrupt && !_woken_up) {
if (!tud_task_event_ready() && sleep_ok && !_woken_up) {
__DSB();
__WFI();
}