2017-09-22 21:05:51 -04:00
|
|
|
/*
|
|
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
|
|
*
|
|
|
|
* The MIT License (MIT)
|
|
|
|
*
|
|
|
|
* Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
* THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
2019-12-06 15:18:20 -05:00
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
2020-12-02 21:05:29 -05:00
|
|
|
#include "supervisor/board.h"
|
2017-09-22 21:05:51 -04:00
|
|
|
#include "supervisor/port.h"
|
|
|
|
|
|
|
|
// ASF 4
|
|
|
|
#include "atmel_start_pins.h"
|
2020-03-17 15:10:35 -04:00
|
|
|
#include "peripheral_clk_config.h"
|
2017-09-22 21:05:51 -04:00
|
|
|
#include "hal/include/hal_delay.h"
|
2019-12-06 15:18:20 -05:00
|
|
|
#include "hal/include/hal_flash.h"
|
2017-09-22 21:05:51 -04:00
|
|
|
#include "hal/include/hal_gpio.h"
|
|
|
|
#include "hal/include/hal_init.h"
|
|
|
|
#include "hpl/gclk/hpl_gclk_base.h"
|
|
|
|
#include "hpl/pm/hpl_pm_base.h"
|
|
|
|
|
2020-06-18 15:13:59 -04:00
|
|
|
#if defined(SAMD21)
|
2018-01-08 12:44:06 -05:00
|
|
|
#include "hri/hri_pm_d21.h"
|
2020-06-18 15:13:59 -04:00
|
|
|
#elif defined(SAME54)
|
|
|
|
#include "hri/hri_rstc_e54.h"
|
2020-09-08 11:43:24 -04:00
|
|
|
#elif defined(SAME51)
|
|
|
|
#include "sam.h"
|
|
|
|
#include "hri/hri_rstc_e51.h"
|
2020-06-18 15:13:59 -04:00
|
|
|
#elif defined(SAMD51)
|
2018-01-08 12:44:06 -05:00
|
|
|
#include "hri/hri_rstc_d51.h"
|
2020-06-18 15:13:59 -04:00
|
|
|
#else
|
|
|
|
#error Unknown chip family
|
2018-01-08 12:44:06 -05:00
|
|
|
#endif
|
|
|
|
|
2021-10-01 19:36:04 -04:00
|
|
|
#include "common-hal/alarm/__init__.h"
|
2017-11-14 21:22:16 -05:00
|
|
|
#include "common-hal/analogio/AnalogIn.h"
|
|
|
|
#include "common-hal/analogio/AnalogOut.h"
|
2018-03-12 19:09:13 -04:00
|
|
|
#include "common-hal/audiobusio/PDMIn.h"
|
|
|
|
#include "common-hal/audiobusio/I2SOut.h"
|
|
|
|
#include "common-hal/audioio/AudioOut.h"
|
2018-10-19 21:46:22 -04:00
|
|
|
#include "common-hal/busio/SPI.h"
|
2017-10-20 07:49:33 -04:00
|
|
|
#include "common-hal/microcontroller/Pin.h"
|
2018-02-14 19:59:04 -05:00
|
|
|
#include "common-hal/pulseio/PulseIn.h"
|
2018-02-13 21:17:20 -05:00
|
|
|
#include "common-hal/pulseio/PulseOut.h"
|
2020-08-18 16:08:33 -04:00
|
|
|
#include "common-hal/pwmio/PWMOut.h"
|
2019-05-15 22:29:34 -04:00
|
|
|
#include "common-hal/ps2io/Ps2.h"
|
2018-04-07 09:10:39 -04:00
|
|
|
#include "common-hal/rtc/RTC.h"
|
2019-08-18 08:44:10 -04:00
|
|
|
|
|
|
|
#if CIRCUITPY_TOUCHIO_USE_NATIVE
|
2018-05-22 17:20:35 -04:00
|
|
|
#include "common-hal/touchio/TouchIn.h"
|
2019-08-18 08:44:10 -04:00
|
|
|
#endif
|
|
|
|
|
2018-06-15 19:16:21 -04:00
|
|
|
#include "samd/cache.h"
|
|
|
|
#include "samd/clocks.h"
|
|
|
|
#include "samd/events.h"
|
|
|
|
#include "samd/external_interrupts.h"
|
|
|
|
#include "samd/dma.h"
|
2020-05-25 09:51:02 -04:00
|
|
|
#include "shared-bindings/microcontroller/__init__.h"
|
2018-04-07 09:10:39 -04:00
|
|
|
#include "shared-bindings/rtc/__init__.h"
|
2021-10-01 19:36:04 -04:00
|
|
|
#include "shared-bindings/alarm/time/TimeAlarm.h"
|
2021-08-11 12:58:31 -04:00
|
|
|
#include "shared_timers.h"
|
2018-10-19 21:46:22 -04:00
|
|
|
#include "reset.h"
|
|
|
|
|
2021-08-19 15:18:13 -04:00
|
|
|
#include "supervisor/background_callback.h"
|
2018-12-06 17:24:20 -05:00
|
|
|
#include "supervisor/shared/safe_mode.h"
|
|
|
|
#include "supervisor/shared/stack.h"
|
2020-03-13 02:05:12 -04:00
|
|
|
#include "supervisor/shared/tick.h"
|
2018-12-06 17:24:20 -05:00
|
|
|
|
2021-08-20 15:45:59 -04:00
|
|
|
#include "tusb.h"
|
|
|
|
|
2019-04-16 13:11:54 -04:00
|
|
|
#if CIRCUITPY_GAMEPADSHIFT
|
|
|
|
#include "shared-module/gamepadshift/__init__.h"
|
|
|
|
#endif
|
2020-06-21 09:31:26 -04:00
|
|
|
#if CIRCUITPY_PEW
|
|
|
|
#include "common-hal/_pew/PewPew.h"
|
|
|
|
#endif
|
2021-08-10 18:23:45 -04:00
|
|
|
static volatile bool sleep_ok = true;
|
2020-07-06 22:47:11 -04:00
|
|
|
#ifdef SAMD21
|
2021-08-10 18:23:45 -04:00
|
|
|
static uint8_t _tick_event_channel = 0;
|
2020-10-12 21:36:18 -04:00
|
|
|
|
2021-08-10 18:23:45 -04:00
|
|
|
// 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.)
|
2021-04-20 16:21:05 -04:00
|
|
|
void rtc_start_pulse(void) {
|
2021-08-10 18:23:45 -04:00
|
|
|
sleep_ok = false;
|
2020-07-04 22:17:19 -04:00
|
|
|
}
|
2020-07-07 00:25:33 -04:00
|
|
|
|
2021-04-20 16:21:05 -04:00
|
|
|
void rtc_end_pulse(void) {
|
2021-08-10 18:23:45 -04:00
|
|
|
sleep_ok = true;
|
2020-07-04 22:17:19 -04:00
|
|
|
}
|
|
|
|
#endif
|
2018-03-11 07:52:31 -04:00
|
|
|
|
2017-09-22 21:05:51 -04:00
|
|
|
extern volatile bool mp_msc_enabled;
|
|
|
|
|
|
|
|
#if defined(SAMD21) && defined(ENABLE_MICRO_TRACE_BUFFER)
|
|
|
|
// Stores 2 ^ TRACE_BUFFER_MAGNITUDE_PACKETS packets.
|
|
|
|
// 7 -> 128 packets
|
|
|
|
#define TRACE_BUFFER_MAGNITUDE_PACKETS 7
|
|
|
|
// Size in uint32_t. Two per packet.
|
|
|
|
#define TRACE_BUFFER_SIZE (1 << (TRACE_BUFFER_MAGNITUDE_PACKETS + 1))
|
|
|
|
// Size in bytes. 4 bytes per uint32_t.
|
|
|
|
#define TRACE_BUFFER_SIZE_BYTES (TRACE_BUFFER_SIZE << 2)
|
2018-10-22 20:57:28 -04:00
|
|
|
__attribute__((__aligned__(TRACE_BUFFER_SIZE_BYTES))) uint32_t mtb[TRACE_BUFFER_SIZE] = {0};
|
2017-09-22 21:05:51 -04:00
|
|
|
#endif
|
|
|
|
|
2019-12-06 15:18:20 -05:00
|
|
|
#if CALIBRATE_CRYSTALLESS
|
|
|
|
static void save_usb_clock_calibration(void) {
|
|
|
|
// If we are on USB lets double check our fine calibration for the clock and
|
|
|
|
// save the new value if its different enough.
|
|
|
|
SYSCTRL->DFLLSYNC.bit.READREQ = 1;
|
|
|
|
uint16_t saved_calibration = 0x1ff;
|
2021-04-30 11:47:37 -04:00
|
|
|
if (strcmp((char *)CIRCUITPY_INTERNAL_CONFIG_START_ADDR, "CIRCUITPYTHON1") == 0) {
|
|
|
|
saved_calibration = ((uint16_t *)CIRCUITPY_INTERNAL_CONFIG_START_ADDR)[8];
|
2019-12-06 15:18:20 -05:00
|
|
|
}
|
|
|
|
while (SYSCTRL->PCLKSR.bit.DFLLRDY == 0) {
|
|
|
|
// TODO(tannewt): Run the mass storage stuff if this takes a while.
|
|
|
|
}
|
|
|
|
int16_t current_calibration = SYSCTRL->DFLLVAL.bit.FINE;
|
|
|
|
if (abs(current_calibration - saved_calibration) > 10) {
|
|
|
|
// Copy the full internal config page to memory.
|
|
|
|
uint8_t page_buffer[NVMCTRL_ROW_SIZE];
|
2021-04-30 11:47:37 -04:00
|
|
|
memcpy(page_buffer, (uint8_t *)CIRCUITPY_INTERNAL_CONFIG_START_ADDR, NVMCTRL_ROW_SIZE);
|
2019-12-06 15:18:20 -05:00
|
|
|
|
|
|
|
// Modify it.
|
|
|
|
memcpy(page_buffer, "CIRCUITPYTHON1", 15);
|
|
|
|
// First 16 bytes (0-15) are ID. Little endian!
|
|
|
|
page_buffer[16] = current_calibration & 0xff;
|
|
|
|
page_buffer[17] = current_calibration >> 8;
|
|
|
|
|
|
|
|
// Write it back.
|
|
|
|
// We don't use features that use any advanced NVMCTRL features so we can fake the descriptor
|
|
|
|
// whenever we need it instead of storing it long term.
|
|
|
|
struct flash_descriptor desc;
|
|
|
|
desc.dev.hw = NVMCTRL;
|
2021-04-30 11:47:37 -04:00
|
|
|
flash_write(&desc, (uint32_t)CIRCUITPY_INTERNAL_CONFIG_START_ADDR, page_buffer, NVMCTRL_ROW_SIZE);
|
2019-12-06 15:18:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-08-10 18:23:45 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-03-13 02:05:12 -04:00
|
|
|
static void rtc_init(void) {
|
2021-04-30 11:47:37 -04:00
|
|
|
#ifdef SAMD21
|
2020-03-17 15:10:35 -04:00
|
|
|
_gclk_enable_channel(RTC_GCLK_ID, GCLK_CLKCTRL_GEN_GCLK2_Val);
|
|
|
|
RTC->MODE0.CTRL.bit.SWRST = true;
|
2021-04-30 11:47:37 -04:00
|
|
|
while (RTC->MODE0.CTRL.bit.SWRST != 0) {
|
|
|
|
}
|
2020-03-17 15:10:35 -04:00
|
|
|
|
2021-08-10 18:23:45 -04:00
|
|
|
// 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;
|
|
|
|
|
2020-03-17 15:10:35 -04:00
|
|
|
RTC->MODE0.CTRL.reg = RTC_MODE0_CTRL_ENABLE |
|
2021-04-30 11:47:37 -04:00
|
|
|
RTC_MODE0_CTRL_MODE_COUNT32 |
|
|
|
|
RTC_MODE0_CTRL_PRESCALER_DIV2;
|
2021-08-10 18:23:45 -04:00
|
|
|
|
|
|
|
// Turn on continuous sync of the count register. This will speed up all
|
|
|
|
// tick reads.
|
|
|
|
rtc_continuous_mode();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
|
|
|
#ifdef SAM_D5X_E5X
|
2020-03-13 02:05:12 -04:00
|
|
|
hri_mclk_set_APBAMASK_RTC_bit(MCLK);
|
2020-03-13 19:13:24 -04:00
|
|
|
RTC->MODE0.CTRLA.bit.SWRST = true;
|
2021-04-30 11:47:37 -04:00
|
|
|
while (RTC->MODE0.SYNCBUSY.bit.SWRST != 0) {
|
|
|
|
}
|
2020-03-13 19:13:24 -04:00
|
|
|
|
2020-03-13 02:05:12 -04:00
|
|
|
RTC->MODE0.CTRLA.reg = RTC_MODE0_CTRLA_ENABLE |
|
2021-04-30 11:47:37 -04:00
|
|
|
RTC_MODE0_CTRLA_MODE_COUNT32 |
|
|
|
|
RTC_MODE0_CTRLA_PRESCALER_DIV2 |
|
|
|
|
RTC_MODE0_CTRLA_COUNTSYNC;
|
|
|
|
#endif
|
2020-03-17 15:10:35 -04:00
|
|
|
|
2020-03-13 02:05:12 -04:00
|
|
|
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_OVF;
|
|
|
|
|
|
|
|
// Set all peripheral interrupt priorities to the lowest priority by default.
|
|
|
|
for (uint16_t i = 0; i < PERIPH_COUNT_IRQn; i++) {
|
|
|
|
NVIC_SetPriority(i, (1UL << __NVIC_PRIO_BITS) - 1UL);
|
|
|
|
}
|
|
|
|
// Bump up the rtc interrupt so nothing else interferes with timekeeping.
|
|
|
|
NVIC_SetPriority(RTC_IRQn, 0);
|
|
|
|
#ifdef SAMD21
|
|
|
|
NVIC_SetPriority(USB_IRQn, 1);
|
|
|
|
#endif
|
|
|
|
|
2020-06-18 15:13:59 -04:00
|
|
|
#ifdef SAM_D5X_E5X
|
2020-03-13 02:05:12 -04:00
|
|
|
NVIC_SetPriority(USB_0_IRQn, 1);
|
|
|
|
NVIC_SetPriority(USB_1_IRQn, 1);
|
|
|
|
NVIC_SetPriority(USB_2_IRQn, 1);
|
|
|
|
NVIC_SetPriority(USB_3_IRQn, 1);
|
|
|
|
#endif
|
2020-03-13 19:13:24 -04:00
|
|
|
NVIC_ClearPendingIRQ(RTC_IRQn);
|
|
|
|
NVIC_EnableIRQ(RTC_IRQn);
|
2021-04-30 11:47:37 -04:00
|
|
|
#if CIRCUITPY_RTC
|
2020-05-11 12:31:18 -04:00
|
|
|
rtc_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2020-05-11 13:03:44 -04:00
|
|
|
|
2020-03-13 02:05:12 -04:00
|
|
|
}
|
|
|
|
|
2017-09-22 21:05:51 -04:00
|
|
|
safe_mode_t port_init(void) {
|
2021-04-30 11:47:37 -04:00
|
|
|
#if defined(SAMD21)
|
2018-05-03 23:43:02 -04:00
|
|
|
|
2020-07-08 14:59:00 -04:00
|
|
|
// Set brownout detection.
|
2018-05-03 23:43:02 -04:00
|
|
|
// Disable while changing level.
|
|
|
|
SYSCTRL->BOD33.bit.ENABLE = 0;
|
2020-07-08 14:59:00 -04:00
|
|
|
SYSCTRL->BOD33.bit.LEVEL = SAMD21_BOD33_LEVEL;
|
2018-05-03 23:43:02 -04:00
|
|
|
SYSCTRL->BOD33.bit.ENABLE = 1;
|
|
|
|
|
2017-09-22 21:05:51 -04:00
|
|
|
#ifdef ENABLE_MICRO_TRACE_BUFFER
|
2021-04-30 11:47:37 -04:00
|
|
|
REG_MTB_POSITION = ((uint32_t)(mtb - REG_MTB_BASE)) & 0xFFFFFFF8;
|
|
|
|
REG_MTB_FLOW = (((uint32_t)mtb - REG_MTB_BASE) + TRACE_BUFFER_SIZE_BYTES) & 0xFFFFFFF8;
|
|
|
|
REG_MTB_MASTER = 0x80000000 + (TRACE_BUFFER_MAGNITUDE_PACKETS - 1);
|
2017-09-22 21:05:51 -04:00
|
|
|
#else
|
2021-04-30 11:47:37 -04:00
|
|
|
// Triple check that the MTB is off. Switching between debug and non-debug
|
|
|
|
// builds can leave it set over reset and wreak havok as a result.
|
|
|
|
REG_MTB_MASTER = 0x00000000 + 6;
|
|
|
|
#endif
|
2017-09-22 21:05:51 -04:00
|
|
|
#endif
|
|
|
|
|
2021-04-30 11:47:37 -04:00
|
|
|
#if defined(SAM_D5X_E5X)
|
2020-07-08 14:59:00 -04:00
|
|
|
// Set brownout detection.
|
2018-05-03 23:43:02 -04:00
|
|
|
// Disable while changing level.
|
|
|
|
SUPC->BOD33.bit.ENABLE = 0;
|
2020-07-08 14:59:00 -04:00
|
|
|
SUPC->BOD33.bit.LEVEL = SAMD5x_E5x_BOD33_LEVEL;
|
2018-05-03 23:43:02 -04:00
|
|
|
SUPC->BOD33.bit.ENABLE = 1;
|
|
|
|
|
2018-05-07 21:55:37 -04:00
|
|
|
// MPU (Memory Protection Unit) setup.
|
|
|
|
// We hoped we could make the QSPI region be non-cachable with the MPU,
|
|
|
|
// but the CMCC doesn't seem to pay attention to the MPU settings.
|
|
|
|
// Leaving this code here disabled,
|
|
|
|
// because it was hard enough to figure out, and maybe there's
|
|
|
|
// a mistake that could make it work in the future.
|
2021-04-30 11:47:37 -04:00
|
|
|
#if 0
|
2018-05-07 21:55:37 -04:00
|
|
|
// Designate QSPI memory mapped region as not cachable.
|
|
|
|
|
|
|
|
// Turn off MPU in case it is on.
|
|
|
|
MPU->CTRL = 0;
|
|
|
|
// Configure region 0.
|
|
|
|
MPU->RNR = 0;
|
|
|
|
// Region base: start of QSPI mapping area.
|
|
|
|
// QSPI region runs from 0x04000000 up to and not including 0x05000000: 16 megabytes
|
|
|
|
MPU->RBAR = QSPI_AHB;
|
|
|
|
MPU->RASR =
|
|
|
|
0b011 << MPU_RASR_AP_Pos | // full read/write access for privileged and user mode
|
2021-04-30 11:47:37 -04:00
|
|
|
0b000 << MPU_RASR_TEX_Pos | // caching not allowed, strongly ordered
|
|
|
|
1 << MPU_RASR_S_Pos | // sharable
|
|
|
|
0 << MPU_RASR_C_Pos | // not cachable
|
|
|
|
0 << MPU_RASR_B_Pos | // not bufferable
|
|
|
|
0b10111 << MPU_RASR_SIZE_Pos | // 16MB region size
|
|
|
|
1 << MPU_RASR_ENABLE_Pos // enable this region
|
|
|
|
;
|
2018-05-07 21:55:37 -04:00
|
|
|
// Turn off regions 1-7.
|
2021-04-30 11:47:37 -04:00
|
|
|
for (uint32_t i = 1; i < 8; i++) {
|
2018-05-07 21:55:37 -04:00
|
|
|
MPU->RNR = i;
|
|
|
|
MPU->RBAR = 0;
|
|
|
|
MPU->RASR = 0;
|
|
|
|
}
|
2018-05-03 23:43:02 -04:00
|
|
|
|
2018-05-07 21:55:37 -04:00
|
|
|
// Turn on MPU. Turn on PRIVDEFENA, which defines a default memory
|
|
|
|
// map for all privileged access, so we don't have to set up other regions
|
|
|
|
// besides QSPI.
|
|
|
|
MPU->CTRL = MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2018-05-07 21:55:37 -04:00
|
|
|
|
|
|
|
samd_peripherals_enable_cache();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2018-01-08 12:44:06 -05:00
|
|
|
|
2021-04-30 11:47:37 -04:00
|
|
|
#ifdef SAMD21
|
2018-05-03 12:10:33 -04:00
|
|
|
hri_nvmctrl_set_CTRLB_RWS_bf(NVMCTRL, 2);
|
|
|
|
_pm_init();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2019-12-06 15:18:20 -05:00
|
|
|
|
2021-04-30 11:47:37 -04:00
|
|
|
#if CALIBRATE_CRYSTALLESS
|
2019-12-06 15:18:20 -05:00
|
|
|
uint32_t fine = DEFAULT_DFLL48M_FINE_CALIBRATION;
|
|
|
|
// The fine calibration data is stored in an NVM page after the text and data storage but before
|
|
|
|
// the optional file system. The first 16 bytes are the identifier for the section.
|
2021-04-30 11:47:37 -04:00
|
|
|
if (strcmp((char *)CIRCUITPY_INTERNAL_CONFIG_START_ADDR, "CIRCUITPYTHON1") == 0) {
|
|
|
|
fine = ((uint16_t *)CIRCUITPY_INTERNAL_CONFIG_START_ADDR)[8];
|
2019-12-06 15:18:20 -05:00
|
|
|
}
|
|
|
|
clock_init(BOARD_HAS_CRYSTAL, fine);
|
2021-04-30 11:47:37 -04:00
|
|
|
#else
|
2019-12-06 15:18:20 -05:00
|
|
|
// Use a default fine value
|
|
|
|
clock_init(BOARD_HAS_CRYSTAL, DEFAULT_DFLL48M_FINE_CALIBRATION);
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2018-06-01 09:33:25 -04:00
|
|
|
|
2018-04-07 09:10:39 -04:00
|
|
|
rtc_init();
|
2017-09-22 21:05:51 -04:00
|
|
|
|
2018-03-09 15:05:12 -05:00
|
|
|
init_shared_dma();
|
2018-08-15 14:01:01 -04:00
|
|
|
|
2019-03-08 18:30:05 -05:00
|
|
|
// Reset everything into a known state before board_init.
|
|
|
|
reset_port();
|
|
|
|
|
2018-06-01 16:45:28 -04:00
|
|
|
#ifdef SAMD21
|
|
|
|
if (PM->RCAUSE.bit.BOD33 == 1 || PM->RCAUSE.bit.BOD12 == 1) {
|
|
|
|
return BROWNOUT;
|
|
|
|
}
|
|
|
|
#endif
|
2020-06-18 15:13:59 -04:00
|
|
|
#ifdef SAM_D5X_E5X
|
2018-06-01 16:45:28 -04:00
|
|
|
if (RSTC->RCAUSE.bit.BODVDD == 1 || RSTC->RCAUSE.bit.BODCORE == 1) {
|
|
|
|
return BROWNOUT;
|
|
|
|
}
|
|
|
|
#endif
|
2017-09-22 21:05:51 -04:00
|
|
|
|
|
|
|
if (board_requests_safe_mode()) {
|
|
|
|
return USER_SAFE_MODE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NO_SAFE_MODE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void reset_port(void) {
|
2021-04-30 11:47:37 -04:00
|
|
|
#if CIRCUITPY_BUSIO
|
2018-10-19 21:46:22 -04:00
|
|
|
reset_sercoms();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
|
|
|
#if CIRCUITPY_AUDIOIO
|
2018-10-09 16:56:02 -04:00
|
|
|
audio_dma_reset();
|
2018-03-12 19:09:13 -04:00
|
|
|
audioout_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
|
|
|
#if CIRCUITPY_AUDIOBUSIO
|
|
|
|
// pdmin_reset();
|
|
|
|
#endif
|
|
|
|
#if CIRCUITPY_AUDIOBUSIO_I2SOUT
|
2020-07-26 21:27:32 -04:00
|
|
|
i2sout_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2019-02-18 22:44:31 -05:00
|
|
|
|
2021-04-30 11:47:37 -04:00
|
|
|
#if CIRCUITPY_TOUCHIO && CIRCUITPY_TOUCHIO_USE_NATIVE
|
2018-05-22 17:20:35 -04:00
|
|
|
touchin_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2018-05-29 21:21:19 -04:00
|
|
|
eic_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#if CIRCUITPY_PULSEIO
|
2020-04-03 21:07:56 -04:00
|
|
|
pulsein_reset();
|
2018-02-13 21:17:20 -05:00
|
|
|
pulseout_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
|
|
|
#if CIRCUITPY_PWMIO
|
2018-02-13 02:41:26 -05:00
|
|
|
pwmout_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2021-08-11 12:58:31 -04:00
|
|
|
#if CIRCUITPY_PWMIO || CIRCUITPY_AUDIOIO
|
|
|
|
reset_timers();
|
|
|
|
#endif
|
2018-05-24 20:20:18 -04:00
|
|
|
|
2021-04-30 11:47:37 -04:00
|
|
|
#if CIRCUITPY_ANALOGIO
|
2018-05-24 20:20:18 -04:00
|
|
|
analogin_reset();
|
|
|
|
analogout_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2017-11-14 21:22:16 -05:00
|
|
|
|
2018-03-12 19:09:13 -04:00
|
|
|
reset_gclks();
|
|
|
|
|
2021-04-30 11:47:37 -04:00
|
|
|
#if CIRCUITPY_GAMEPADSHIFT
|
2019-04-16 13:11:54 -04:00
|
|
|
gamepadshift_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
|
|
|
#if CIRCUITPY_PEW
|
2018-08-01 09:29:26 -04:00
|
|
|
pew_reset();
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2018-03-11 07:52:31 -04:00
|
|
|
|
2018-03-12 19:09:13 -04:00
|
|
|
reset_event_system();
|
2021-08-10 18:23:45 -04:00
|
|
|
#ifdef SAMD21
|
|
|
|
_tick_event_channel = EVSYS_SYNCH_NUM;
|
|
|
|
#endif
|
2018-03-12 19:09:13 -04:00
|
|
|
|
2017-10-20 07:49:33 -04:00
|
|
|
reset_all_pins();
|
2018-02-21 16:30:26 -05:00
|
|
|
|
|
|
|
// Output clocks for debugging.
|
|
|
|
// not supported by SAMD51G; uncomment for SAMD51J or update for 51G
|
2020-06-18 15:13:59 -04:00
|
|
|
// #ifdef SAM_D5X_E5X
|
2018-02-21 16:30:26 -05:00
|
|
|
// gpio_set_pin_function(PIN_PA10, GPIO_PIN_FUNCTION_M); // GCLK4, D3
|
|
|
|
// gpio_set_pin_function(PIN_PA11, GPIO_PIN_FUNCTION_M); // GCLK5, A4
|
|
|
|
// gpio_set_pin_function(PIN_PB14, GPIO_PIN_FUNCTION_M); // GCLK0, D5
|
|
|
|
// gpio_set_pin_function(PIN_PB15, GPIO_PIN_FUNCTION_M); // GCLK1, D6
|
|
|
|
// #endif
|
|
|
|
|
2021-04-30 11:47:37 -04:00
|
|
|
#if CALIBRATE_CRYSTALLESS
|
2018-10-19 21:46:22 -04:00
|
|
|
if (tud_cdc_connected()) {
|
2018-06-01 21:01:42 -04:00
|
|
|
save_usb_clock_calibration();
|
|
|
|
}
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2017-09-22 21:05:51 -04:00
|
|
|
}
|
|
|
|
|
2018-10-19 21:46:22 -04:00
|
|
|
void reset_to_bootloader(void) {
|
|
|
|
_bootloader_dbl_tap = DBL_TAP_MAGIC;
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
2018-12-06 17:24:20 -05:00
|
|
|
void reset_cpu(void) {
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
2020-10-11 08:59:33 -04:00
|
|
|
bool port_has_fixed_stack(void) {
|
|
|
|
return false;
|
2020-04-17 19:23:28 -04:00
|
|
|
}
|
|
|
|
|
2019-10-18 05:00:09 -04:00
|
|
|
uint32_t *port_stack_get_limit(void) {
|
|
|
|
return &_ebss;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t *port_stack_get_top(void) {
|
|
|
|
return &_estack;
|
|
|
|
}
|
|
|
|
|
2020-01-18 21:06:56 -05:00
|
|
|
uint32_t *port_heap_get_bottom(void) {
|
|
|
|
return port_stack_get_limit();
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t *port_heap_get_top(void) {
|
|
|
|
return port_stack_get_top();
|
|
|
|
}
|
|
|
|
|
2019-02-13 03:35:14 -05:00
|
|
|
// Place the word to save 8k from the end of RAM so we and the bootloader don't clobber it.
|
|
|
|
#ifdef SAMD21
|
2021-04-30 11:47:37 -04:00
|
|
|
uint32_t *safe_word = (uint32_t *)(HMCRAMC0_ADDR + HMCRAMC0_SIZE - 0x2000);
|
2019-02-13 03:35:14 -05:00
|
|
|
#endif
|
2020-06-18 15:13:59 -04:00
|
|
|
#ifdef SAM_D5X_E5X
|
2021-04-30 11:47:37 -04:00
|
|
|
uint32_t *safe_word = (uint32_t *)(HSRAM_ADDR + HSRAM_SIZE - 0x2000);
|
2019-02-13 03:35:14 -05:00
|
|
|
#endif
|
|
|
|
|
2018-12-06 17:24:20 -05:00
|
|
|
void port_set_saved_word(uint32_t value) {
|
2019-02-13 03:35:14 -05:00
|
|
|
*safe_word = value;
|
2018-12-06 17:24:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t port_get_saved_word(void) {
|
2019-02-13 03:35:14 -05:00
|
|
|
return *safe_word;
|
2018-12-06 17:24:20 -05:00
|
|
|
}
|
|
|
|
|
2020-03-13 02:05:12 -04:00
|
|
|
// 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;
|
|
|
|
|
2021-04-30 11:47:37 -04:00
|
|
|
static uint32_t _get_count(uint64_t *overflow_count) {
|
2020-10-09 15:53:00 -04:00
|
|
|
#ifdef SAM_D5X_E5X
|
2021-04-30 11:47:37 -04:00
|
|
|
while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COUNTSYNC | RTC_MODE0_SYNCBUSY_COUNT)) != 0) {
|
|
|
|
}
|
2020-10-09 15:53:00 -04:00
|
|
|
#endif
|
2021-08-10 18:23:45 -04:00
|
|
|
// SAMD21 does continuous sync so we don't need to wait here.
|
|
|
|
|
2020-10-12 21:36:18 -04:00
|
|
|
// Disable interrupts so we can grab the count and the overflow.
|
|
|
|
common_hal_mcu_disable_interrupts();
|
|
|
|
uint32_t count = RTC->MODE0.COUNT.reg;
|
|
|
|
if (overflow_count != NULL) {
|
|
|
|
*overflow_count = overflowed_ticks;
|
|
|
|
}
|
|
|
|
common_hal_mcu_enable_interrupts();
|
2020-10-09 15:53:00 -04:00
|
|
|
|
2020-10-12 21:36:18 -04:00
|
|
|
return count;
|
2020-10-09 15:53:00 -04:00
|
|
|
}
|
|
|
|
|
2021-05-13 14:04:07 -04:00
|
|
|
volatile bool _woken_up;
|
|
|
|
|
2020-03-13 02:05:12 -04:00
|
|
|
void RTC_Handler(void) {
|
|
|
|
uint32_t intflag = RTC->MODE0.INTFLAG.reg;
|
|
|
|
if (intflag & RTC_MODE0_INTFLAG_OVF) {
|
|
|
|
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_OVF;
|
|
|
|
// Our RTC is 32 bits and we're clocking it at 16.384khz which is 16 (2 ** 4) subticks per
|
|
|
|
// tick.
|
2021-04-30 11:47:37 -04:00
|
|
|
overflowed_ticks += (1L << (32 - 4));
|
2021-05-13 14:04:07 -04:00
|
|
|
}
|
2020-06-18 15:13:59 -04:00
|
|
|
#ifdef SAM_D5X_E5X
|
2021-05-13 14:04:07 -04:00
|
|
|
if (intflag & RTC_MODE0_INTFLAG_PER2) {
|
2020-03-13 02:05:12 -04:00
|
|
|
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_PER2;
|
|
|
|
// Do things common to all ports when the tick occurs
|
|
|
|
supervisor_tick();
|
2021-05-13 14:04:07 -04:00
|
|
|
}
|
2020-03-17 15:10:35 -04:00
|
|
|
#endif
|
2021-05-13 14:04:07 -04:00
|
|
|
if (intflag & RTC_MODE0_INTFLAG_CMP0) {
|
2021-08-10 18:23:45 -04:00
|
|
|
// Clear the interrupt because we may have hit a sleep
|
2020-03-13 02:05:12 -04:00
|
|
|
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP0;
|
2021-05-13 14:04:07 -04:00
|
|
|
_woken_up = true;
|
2021-08-10 18:23:45 -04:00
|
|
|
// SAMD21 ticks are handled by EVSYS
|
2020-06-18 15:13:59 -04:00
|
|
|
#ifdef SAM_D5X_E5X
|
2020-03-17 15:10:35 -04:00
|
|
|
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_CMP0;
|
2021-10-01 19:36:04 -04:00
|
|
|
// Check if we're sleeping
|
2021-10-04 00:42:04 -04:00
|
|
|
if (SAMD_ALARM_FLAG) {
|
2021-10-01 19:36:04 -04:00
|
|
|
timer_callback();
|
|
|
|
SAMD_ALARM_FLAG = 0;
|
|
|
|
}
|
2020-03-17 15:10:35 -04:00
|
|
|
#endif
|
2020-03-13 02:05:12 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-30 11:47:37 -04:00
|
|
|
uint64_t port_get_raw_ticks(uint8_t *subticks) {
|
2021-01-06 11:09:06 -05:00
|
|
|
uint64_t overflow_count;
|
2020-10-12 21:36:18 -04:00
|
|
|
uint32_t current_ticks = _get_count(&overflow_count);
|
2020-03-13 02:05:12 -04:00
|
|
|
if (subticks != NULL) {
|
|
|
|
*subticks = (current_ticks % 16) * 2;
|
|
|
|
}
|
|
|
|
|
2020-10-12 21:36:18 -04:00
|
|
|
return overflow_count + current_ticks / 16;
|
2020-03-13 02:05:12 -04:00
|
|
|
}
|
|
|
|
|
2021-08-10 18:23:45 -04:00
|
|
|
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
|
|
|
|
|
2020-03-13 02:05:12 -04:00
|
|
|
// Enable 1/1024 second tick.
|
|
|
|
void port_enable_tick(void) {
|
2020-06-18 15:13:59 -04:00
|
|
|
#ifdef SAM_D5X_E5X
|
2020-03-13 02:05:12 -04:00
|
|
|
// PER2 will generate an interrupt every 32 ticks of the source 32.768 clock.
|
2020-03-13 19:13:24 -04:00
|
|
|
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_PER2;
|
2020-03-17 15:10:35 -04:00
|
|
|
#endif
|
|
|
|
#ifdef SAMD21
|
2021-08-10 18:23:45 -04:00
|
|
|
// 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);
|
2020-03-17 15:10:35 -04:00
|
|
|
#endif
|
2020-03-13 02:05:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Disable 1/1024 second tick.
|
|
|
|
void port_disable_tick(void) {
|
2020-06-18 15:13:59 -04:00
|
|
|
#ifdef SAM_D5X_E5X
|
2020-03-13 19:13:24 -04:00
|
|
|
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_PER2;
|
2020-03-17 15:10:35 -04:00
|
|
|
#endif
|
|
|
|
#ifdef SAMD21
|
2021-08-10 18:23:45 -04:00
|
|
|
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);
|
|
|
|
}
|
2020-03-17 15:10:35 -04:00
|
|
|
#endif
|
2020-03-13 02:05:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void port_interrupt_after_ticks(uint32_t ticks) {
|
2021-08-10 18:23:45 -04:00
|
|
|
uint32_t current_ticks = _get_count(NULL);
|
|
|
|
if (ticks > 1 << 28) {
|
|
|
|
// We'll interrupt sooner with an overflow.
|
|
|
|
return;
|
|
|
|
}
|
2021-01-08 13:30:11 -05:00
|
|
|
#ifdef SAMD21
|
2021-08-10 18:23:45 -04:00
|
|
|
if (!sleep_ok) {
|
2020-07-04 22:17:19 -04:00
|
|
|
return;
|
|
|
|
}
|
2021-01-08 13:30:11 -05:00
|
|
|
#endif
|
2021-08-10 18:23:45 -04:00
|
|
|
|
|
|
|
uint32_t target = current_ticks + (ticks << 4);
|
2021-08-11 14:53:26 -04:00
|
|
|
#ifdef SAMD21
|
2021-08-10 18:23:45 -04:00
|
|
|
// Try and avoid a bus stall when writing COMP by checking for an obvious
|
|
|
|
// existing sync.
|
|
|
|
while (RTC->MODE0.STATUS.bit.SYNCBUSY == 1) {
|
|
|
|
}
|
2021-08-11 14:53:26 -04:00
|
|
|
#endif
|
2021-08-10 18:23:45 -04:00
|
|
|
// 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;
|
2020-03-13 02:05:12 -04:00
|
|
|
}
|
|
|
|
|
2020-12-02 21:05:29 -05:00
|
|
|
void port_idle_until_interrupt(void) {
|
2020-06-18 15:13:59 -04:00
|
|
|
#ifdef SAM_D5X_E5X
|
2020-03-13 02:05:12 -04:00
|
|
|
// Clear the FPU interrupt because it can prevent us from sleeping.
|
2021-04-30 11:47:37 -04:00
|
|
|
if (__get_FPSCR() & ~(0x9f)) {
|
|
|
|
__set_FPSCR(__get_FPSCR() & ~(0x9f));
|
|
|
|
(void)__get_FPSCR();
|
2020-03-13 02:05:12 -04:00
|
|
|
}
|
2020-03-17 15:10:35 -04:00
|
|
|
#endif
|
2020-05-25 09:51:02 -04:00
|
|
|
common_hal_mcu_disable_interrupts();
|
2021-08-19 15:18:13 -04:00
|
|
|
if (!background_callback_pending() && sleep_ok && !_woken_up) {
|
2020-05-25 09:51:02 -04:00
|
|
|
__DSB();
|
|
|
|
__WFI();
|
|
|
|
}
|
|
|
|
common_hal_mcu_enable_interrupts();
|
2020-03-13 02:05:12 -04:00
|
|
|
}
|
|
|
|
|
2017-09-22 21:05:51 -04:00
|
|
|
/**
|
|
|
|
* \brief Default interrupt handler for unused IRQs.
|
|
|
|
*/
|
2021-04-30 11:47:37 -04:00
|
|
|
__attribute__((used)) void HardFault_Handler(void) {
|
|
|
|
#ifdef ENABLE_MICRO_TRACE_BUFFER
|
2018-10-22 20:57:28 -04:00
|
|
|
// Turn off the micro trace buffer so we don't fill it up in the infinite
|
|
|
|
// loop below.
|
|
|
|
REG_MTB_MASTER = 0x00000000 + 6;
|
2021-04-30 11:47:37 -04:00
|
|
|
#endif
|
2018-12-06 17:24:20 -05:00
|
|
|
|
|
|
|
reset_into_safe_mode(HARD_CRASH);
|
2017-11-14 21:22:16 -05:00
|
|
|
while (true) {
|
2021-04-30 11:47:37 -04:00
|
|
|
asm ("nop;");
|
2017-09-22 21:05:51 -04:00
|
|
|
}
|
|
|
|
}
|