91
91
#if CIRCUITPY_PEW
92
92
#include "common-hal/_pew/PewPew.h"
93
93
#endif
94
- volatile bool hold_interrupt = false ;
94
+ static volatile bool sleep_ok = true ;
95
95
#ifdef SAMD21
96
- static void rtc_set_continuous (bool continuous ) {
97
- while (RTC -> MODE0 .STATUS .bit .SYNCBUSY ) {
98
- ;
99
- }
100
- RTC -> MODE0 .READREQ .reg = (continuous ? RTC_READREQ_RCONT : 0 ) | 0x0010 ;
101
- while (RTC -> MODE0 .STATUS .bit .SYNCBUSY ) {
102
- ;
103
- }
104
- }
96
+ static uint8_t _tick_event_channel = 0 ;
105
97
98
+ // Sleeping requires a register write that can stall interrupt handling. Turning
99
+ // off sleeps allows for more accurate interrupt timing. (Python still thinks
100
+ // it is sleeping though.)
106
101
void rtc_start_pulse (void ) {
107
- rtc_set_continuous (true);
108
- hold_interrupt = true;
102
+ sleep_ok = false;
109
103
}
110
104
111
105
void rtc_end_pulse (void ) {
112
- hold_interrupt = false;
113
- rtc_set_continuous (false);
106
+ sleep_ok = true;
114
107
}
115
108
#endif
116
109
@@ -161,16 +154,38 @@ static void save_usb_clock_calibration(void) {
161
154
}
162
155
#endif
163
156
157
+ static void rtc_continuous_mode (void ) {
158
+ #ifdef SAMD21
159
+ while (RTC -> MODE0 .STATUS .bit .SYNCBUSY ) {
160
+ }
161
+ RTC -> MODE0 .READREQ .reg = RTC_READREQ_RCONT | 0x0010 ;
162
+ while (RTC -> MODE0 .STATUS .bit .SYNCBUSY ) {
163
+ }
164
+ // Do the first request and wait for it.
165
+ RTC -> MODE0 .READREQ .reg = RTC_READREQ_RREQ | RTC_READREQ_RCONT | 0x0010 ;
166
+ while (RTC -> MODE0 .STATUS .bit .SYNCBUSY ) {
167
+ }
168
+ #endif
169
+ }
170
+
164
171
static void rtc_init (void ) {
165
172
#ifdef SAMD21
166
173
_gclk_enable_channel (RTC_GCLK_ID , GCLK_CLKCTRL_GEN_GCLK2_Val );
167
174
RTC -> MODE0 .CTRL .bit .SWRST = true;
168
175
while (RTC -> MODE0 .CTRL .bit .SWRST != 0 ) {
169
176
}
170
177
178
+ // Turn on periodic events to use as tick. We control whether it interrupts
179
+ // us with the EVSYS INTEN register.
180
+ RTC -> MODE0 .EVCTRL .reg = RTC_MODE0_EVCTRL_PEREO2 ;
181
+
171
182
RTC -> MODE0 .CTRL .reg = RTC_MODE0_CTRL_ENABLE |
172
183
RTC_MODE0_CTRL_MODE_COUNT32 |
173
184
RTC_MODE0_CTRL_PRESCALER_DIV2 ;
185
+
186
+ // Turn on continuous sync of the count register. This will speed up all
187
+ // tick reads.
188
+ rtc_continuous_mode ();
174
189
#endif
175
190
#ifdef SAM_D5X_E5X
176
191
hri_mclk_set_APBAMASK_RTC_bit (MCLK );
@@ -363,6 +378,9 @@ void reset_port(void) {
363
378
#endif
364
379
365
380
reset_event_system ();
381
+ #ifdef SAMD21
382
+ _tick_event_channel = EVSYS_SYNCH_NUM ;
383
+ #endif
366
384
367
385
reset_all_pins ();
368
386
@@ -430,21 +448,14 @@ uint32_t port_get_saved_word(void) {
430
448
// TODO: Move this to an RTC backup register so we can preserve it when only the BACKUP power domain
431
449
// is enabled.
432
450
static volatile uint64_t overflowed_ticks = 0 ;
433
- #ifdef SAMD21
434
- static volatile bool _ticks_enabled = false;
435
- #endif
436
451
437
452
static uint32_t _get_count (uint64_t * overflow_count ) {
438
453
#ifdef SAM_D5X_E5X
439
454
while ((RTC -> MODE0 .SYNCBUSY .reg & (RTC_MODE0_SYNCBUSY_COUNTSYNC | RTC_MODE0_SYNCBUSY_COUNT )) != 0 ) {
440
455
}
441
456
#endif
442
- #ifdef SAMD21
443
- // Request a read so we don't stall the bus later. See section 14.3.1.5 Read Request
444
- RTC -> MODE0 .READREQ .reg = RTC_READREQ_RREQ | 0x0010 ;
445
- while (RTC -> MODE0 .STATUS .bit .SYNCBUSY != 0 ) {
446
- }
447
- #endif
457
+ // SAMD21 does continuous sync so we don't need to wait here.
458
+
448
459
// Disable interrupts so we can grab the count and the overflow.
449
460
common_hal_mcu_disable_interrupts ();
450
461
uint32_t count = RTC -> MODE0 .COUNT .reg ;
@@ -458,29 +469,6 @@ static uint32_t _get_count(uint64_t *overflow_count) {
458
469
459
470
volatile bool _woken_up ;
460
471
461
- static void _port_interrupt_after_ticks (uint32_t ticks ) {
462
- uint32_t current_ticks = _get_count (NULL );
463
- if (ticks > 1 << 28 ) {
464
- // We'll interrupt sooner with an overflow.
465
- return ;
466
- }
467
- #ifdef SAMD21
468
- if (hold_interrupt ) {
469
- return ;
470
- }
471
- #endif
472
- uint32_t target = current_ticks + (ticks << 4 );
473
- RTC -> MODE0 .COMP [0 ].reg = target ;
474
- #ifdef SAM_D5X_E5X
475
- while ((RTC -> MODE0 .SYNCBUSY .reg & (RTC_MODE0_SYNCBUSY_COMP0 )) != 0 ) {
476
- }
477
- #endif
478
- RTC -> MODE0 .INTFLAG .reg = RTC_MODE0_INTFLAG_CMP0 ;
479
- RTC -> MODE0 .INTENSET .reg = RTC_MODE0_INTENSET_CMP0 ;
480
- current_ticks = _get_count (NULL );
481
- _woken_up = current_ticks >= target ;
482
- }
483
-
484
472
void RTC_Handler (void ) {
485
473
uint32_t intflag = RTC -> MODE0 .INTFLAG .reg ;
486
474
if (intflag & RTC_MODE0_INTFLAG_OVF ) {
@@ -497,19 +485,10 @@ void RTC_Handler(void) {
497
485
}
498
486
#endif
499
487
if (intflag & RTC_MODE0_INTFLAG_CMP0 ) {
500
- // Clear the interrupt because we may have hit a sleep and _ticks_enabled
488
+ // Clear the interrupt because we may have hit a sleep
501
489
RTC -> MODE0 .INTFLAG .reg = RTC_MODE0_INTFLAG_CMP0 ;
502
490
_woken_up = true;
503
- #ifdef SAMD21
504
- if (_ticks_enabled ) {
505
- // Do things common to all ports when the tick occurs.
506
- supervisor_tick ();
507
- // Check _ticks_enabled again because a tick handler may have turned it off.
508
- if (_ticks_enabled ) {
509
- _port_interrupt_after_ticks (1 );
510
- }
511
- }
512
- #endif
491
+ // SAMD21 ticks are handled by EVSYS
513
492
#ifdef SAM_D5X_E5X
514
493
RTC -> MODE0 .INTENCLR .reg = RTC_MODE0_INTENCLR_CMP0 ;
515
494
#endif
@@ -526,16 +505,63 @@ uint64_t port_get_raw_ticks(uint8_t *subticks) {
526
505
return overflow_count + current_ticks / 16 ;
527
506
}
528
507
508
+ void evsyshandler_common (void ) {
509
+ #ifdef SAMD21
510
+ if (_tick_event_channel < EVSYS_SYNCH_NUM && event_interrupt_active (_tick_event_channel )) {
511
+ supervisor_tick ();
512
+ }
513
+ #endif
514
+ #if CIRCUITPY_AUDIOIO || CIRCUITPY_AUDIOBUSIO
515
+ audio_evsys_handler ();
516
+ #endif
517
+ }
518
+
519
+ #ifdef SAM_D5X_E5X
520
+ void EVSYS_0_Handler (void ) {
521
+ evsyshandler_common ();
522
+ }
523
+ void EVSYS_1_Handler (void ) {
524
+ evsyshandler_common ();
525
+ }
526
+ void EVSYS_2_Handler (void ) {
527
+ evsyshandler_common ();
528
+ }
529
+ void EVSYS_3_Handler (void ) {
530
+ evsyshandler_common ();
531
+ }
532
+ void EVSYS_4_Handler (void ) {
533
+ evsyshandler_common ();
534
+ }
535
+ #else
536
+ void EVSYS_Handler (void ) {
537
+ evsyshandler_common ();
538
+ }
539
+ #endif
540
+
529
541
// Enable 1/1024 second tick.
530
542
void port_enable_tick (void ) {
531
543
#ifdef SAM_D5X_E5X
532
544
// PER2 will generate an interrupt every 32 ticks of the source 32.768 clock.
533
545
RTC -> MODE0 .INTENSET .reg = RTC_MODE0_INTENSET_PER2 ;
534
546
#endif
535
547
#ifdef SAMD21
536
- // TODO: Switch to using the PER *event* from the RTC to generate an interrupt via EVSYS.
537
- _ticks_enabled = true;
538
- _port_interrupt_after_ticks (1 );
548
+ // SAMD21 ticks won't survive port_reset(). This *should* be ok since it'll
549
+ // be triggered by ticks and no Python will be running.
550
+ if (_tick_event_channel >= EVSYS_SYNCH_NUM ) {
551
+ turn_on_event_system ();
552
+ _tick_event_channel = find_sync_event_channel ();
553
+ }
554
+ // This turns on both the event detected interrupt (EVD) and overflow (OVR).
555
+ init_event_channel_interrupt (_tick_event_channel , CORE_GCLK , EVSYS_ID_GEN_RTC_PER_2 );
556
+ // Disable overflow interrupt because we ignore it.
557
+ if (_tick_event_channel >= 8 ) {
558
+ uint8_t value = 1 << (_tick_event_channel - 8 );
559
+ EVSYS -> INTENCLR .reg = EVSYS_INTENSET_OVRp8 (value );
560
+ } else {
561
+ uint8_t value = 1 << _tick_event_channel ;
562
+ EVSYS -> INTENCLR .reg = EVSYS_INTENSET_OVR (value );
563
+ }
564
+ NVIC_EnableIRQ (EVSYS_IRQn );
539
565
#endif
540
566
}
541
567
@@ -545,21 +571,46 @@ void port_disable_tick(void) {
545
571
RTC -> MODE0 .INTENCLR .reg = RTC_MODE0_INTENCLR_PER2 ;
546
572
#endif
547
573
#ifdef SAMD21
548
- _ticks_enabled = false;
549
- RTC -> MODE0 .INTENCLR .reg = RTC_MODE0_INTENCLR_CMP0 ;
574
+ if (_tick_event_channel >= 8 ) {
575
+ uint8_t value = 1 << (_tick_event_channel - 8 );
576
+ EVSYS -> INTENCLR .reg = EVSYS_INTENSET_EVDp8 (value );
577
+ } else {
578
+ uint8_t value = 1 << _tick_event_channel ;
579
+ EVSYS -> INTENCLR .reg = EVSYS_INTENSET_EVD (value );
580
+ }
550
581
#endif
551
582
}
552
583
553
- // This is called by sleep, we ignore it when our ticks are enabled because
554
- // they'll wake us up earlier. If we don't, we'll mess up ticks by overwriting
555
- // the next RTC wake up time.
556
584
void port_interrupt_after_ticks (uint32_t ticks ) {
585
+ uint32_t current_ticks = _get_count (NULL );
586
+ if (ticks > 1 << 28 ) {
587
+ // We'll interrupt sooner with an overflow.
588
+ return ;
589
+ }
557
590
#ifdef SAMD21
558
- if (_ticks_enabled ) {
591
+ if (! sleep_ok ) {
559
592
return ;
560
593
}
561
594
#endif
562
- _port_interrupt_after_ticks (ticks );
595
+
596
+ uint32_t target = current_ticks + (ticks << 4 );
597
+ // Try and avoid a bus stall when writing COMP by checking for an obvious
598
+ // existing sync.
599
+ while (RTC -> MODE0 .STATUS .bit .SYNCBUSY == 1 ) {
600
+ }
601
+ // Writing the COMP register can take up to 180us to synchronize. During
602
+ // this time, the bus will stall and no interrupts will be serviced.
603
+ RTC -> MODE0 .COMP [0 ].reg = target ;
604
+ #ifdef SAM_D5X_E5X
605
+ while ((RTC -> MODE0 .SYNCBUSY .reg & (RTC_MODE0_SYNCBUSY_COMP0 )) != 0 ) {
606
+ }
607
+ #endif
608
+ RTC -> MODE0 .INTFLAG .reg = RTC_MODE0_INTFLAG_CMP0 ;
609
+ RTC -> MODE0 .INTENSET .reg = RTC_MODE0_INTENSET_CMP0 ;
610
+ // Set continuous mode again because setting COMP may disable it.
611
+ rtc_continuous_mode ();
612
+ current_ticks = _get_count (NULL );
613
+ _woken_up = current_ticks >= target ;
563
614
}
564
615
565
616
void port_idle_until_interrupt (void ) {
@@ -571,7 +622,7 @@ void port_idle_until_interrupt(void) {
571
622
}
572
623
#endif
573
624
common_hal_mcu_disable_interrupts ();
574
- if (!tud_task_event_ready () && ! hold_interrupt && !_woken_up ) {
625
+ if (!tud_task_event_ready () && sleep_ok && !_woken_up ) {
575
626
__DSB ();
576
627
__WFI ();
577
628
}
0 commit comments