summaryrefslogtreecommitdiffstatshomepage
path: root/ports/stm32/xspi.c
diff options
context:
space:
mode:
Diffstat (limited to 'ports/stm32/xspi.c')
-rw-r--r--ports/stm32/xspi.c599
1 files changed, 599 insertions, 0 deletions
diff --git a/ports/stm32/xspi.c b/ports/stm32/xspi.c
new file mode 100644
index 0000000000..b113110c05
--- /dev/null
+++ b/ports/stm32/xspi.c
@@ -0,0 +1,599 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024-2025 Damien P. George
+ *
+ * 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.
+ */
+
+// This XSPI driver is currently configured to run in 1-line (SPI) mode.
+// It uses the mp_qspi_proto_t QSPI protocol and translates quad-commands
+// into 1-line commands.
+
+#include <string.h>
+#include "py/mperrno.h"
+#include "py/mphal.h"
+#include "xspi.h"
+
+#if defined(MICROPY_HW_XSPIFLASH_SIZE_BITS_LOG2)
+
+#ifndef MICROPY_HW_XSPI_PRESCALER
+#define MICROPY_HW_XSPI_PRESCALER (4) // F_CLK = F_AHB/4
+#endif
+
+#ifndef MICROPY_HW_XSPI_CS_HIGH_CYCLES
+#define MICROPY_HW_XSPI_CS_HIGH_CYCLES (2) // nCS stays high for 4 cycles
+#endif
+
+// Currently hard-coded to use XSPI2 instance.
+#define XSPIx (XSPI2)
+
+// For XSPI2, PN0 through PN12.
+#define XSPI2_AF (9)
+
+typedef struct _xspi_flash_t {
+ XSPI_TypeDef *xspi;
+ uintptr_t xip_base;
+} xspi_flash_t;
+
+const xspi_flash_t xspi_flash1 = {
+ .xspi = XSPI1,
+ .xip_base = 0x90000000,
+};
+
+const xspi_flash_t xspi_flash2 = {
+ .xspi = XSPI2,
+ .xip_base = 0x70000000,
+};
+
+static bool xspi_dtr_enabled = false;
+
+#ifdef pyb_pin_FLASH_RESET
+// Can't rely on SysTick being available, so use a busy loop for delays.
+// The timing here is approximate and assumes a CPU frequency of 800MHz.
+static void xspi_delay_us(unsigned int us) {
+ while (us--) {
+ for (unsigned int i = 0; i < 800; ++i) {
+ __NOP();
+ }
+ }
+}
+#endif
+
+static inline void mp_hal_pin_config_alt_speed(mp_hal_pin_obj_t pin, uint32_t pull, uint32_t alt, uint32_t speed) {
+ mp_hal_pin_config(pin, MP_HAL_PIN_MODE_ALT, pull, alt);
+ mp_hal_pin_config_speed(pin, speed);
+}
+
+static int xspi_read_111_ext(uint8_t cmd, bool addr_enabled, uint32_t addr, size_t len, uint8_t *dest);
+static int xspi_write_111_ext(uint8_t cmd, bool addr_enabled, uint32_t addr, size_t len, const uint8_t *src);
+static int xspi_read_888_dtr_ext(uint16_t cmd, bool addr_enabled, uint32_t addr, uint32_t num_dummy, size_t len, uint8_t *dest);
+static int xspi_write_888_dtr_ext(uint16_t cmd, bool addr_enabled, uint32_t addr, size_t len, const uint8_t *src);
+static void xspi_memory_map_111(void);
+static void xspi_memory_map_888(void);
+static void xspi_memory_map_exit(void);
+
+void xspi_init(void) {
+ // Configure XSPI pins.
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_CS, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_SCK, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_DQS, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO0, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO1, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO2, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO3, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO4, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO5, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO6, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+ mp_hal_pin_config_alt_speed(pyb_pin_XSPIM_P2_IO7, MP_HAL_PIN_PULL_NONE, XSPI2_AF, MP_HAL_PIN_SPEED_VERY_HIGH);
+
+ LL_AHB5_GRP1_ForceReset(LL_AHB5_GRP1_PERIPH_XSPIM);
+ LL_AHB5_GRP1_ForceReset(LL_AHB5_GRP1_PERIPH_XSPI1);
+ LL_AHB5_GRP1_ForceReset(LL_AHB5_GRP1_PERIPH_XSPI2);
+ LL_AHB5_GRP1_ForceReset(LL_AHB5_GRP1_PERIPH_XSPI3);
+
+ LL_AHB5_GRP1_ReleaseReset(LL_AHB5_GRP1_PERIPH_XSPIM);
+ LL_AHB5_GRP1_ReleaseReset(LL_AHB5_GRP1_PERIPH_XSPI1);
+ LL_AHB5_GRP1_ReleaseReset(LL_AHB5_GRP1_PERIPH_XSPI2);
+ LL_AHB5_GRP1_ReleaseReset(LL_AHB5_GRP1_PERIPH_XSPI3);
+
+ LL_RCC_SetXSPIClockSource(LL_RCC_XSPI1_CLKSOURCE_HCLK);
+ LL_RCC_SetXSPIClockSource(LL_RCC_XSPI2_CLKSOURCE_HCLK);
+
+ LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_XSPIM);
+ LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_XSPI1);
+ LL_AHB5_GRP1_EnableClock(LL_AHB5_GRP1_PERIPH_XSPI2);
+
+ // Configure XSPIM in direct mode.
+ XSPI1->CR &= ~XSPI_CR_EN;
+ XSPI2->CR &= ~XSPI_CR_EN;
+ XSPIM->CR = 0;
+
+ // Configure the XSPIx peripheral.
+
+ XSPIx->CR =
+ 3 << XSPI_CR_FTHRES_Pos // 4 byte must be available to read/write
+ | 0 << XSPI_CR_MSEL_Pos // FLASH 0 selected
+ | 0 << XSPI_CR_CSSEL_Pos // use NCS1 as chip select
+ | 0 << XSPI_CR_DMM_Pos // dual-memory mode disabled
+ | 1 << XSPI_CR_TCEN_Pos // time-out counter enabled
+ | 0 << XSPI_CR_DMAEN_Pos // DMA disabled
+ | 0 << XSPI_CR_ABORT_Pos // no abort request
+ | 0 << XSPI_CR_EN_Pos // disabled
+ ;
+
+ XSPIx->DCR1 =
+ 1 << XSPI_DCR1_MTYP_Pos // Macronix mode
+ | (MICROPY_HW_XSPIFLASH_SIZE_BITS_LOG2 - 3 - 1) << XSPI_DCR1_DEVSIZE_Pos
+ | (MICROPY_HW_XSPI_CS_HIGH_CYCLES - 1) << XSPI_DCR1_CSHT_Pos
+ | 0 << XSPI_DCR1_FRCK_Pos // CLK is not free running
+ | 0 << XSPI_DCR1_CKMODE_Pos // CLK idles at low state
+ ;
+
+ XSPIx->DCR2 =
+ 0 << XSPI_DCR2_WRAPSIZE_Pos // separate wrap reads are not supported by the memory
+ | (MICROPY_HW_XSPI_PRESCALER - 1) << XSPI_DCR2_PRESCALER_Pos
+ ;
+
+ XSPIx->DCR3 =
+ 0
+ // 10 << XSPI_DCR3_CSBOUND_Pos // transaction boundary at 1024
+ ;
+
+ XSPIx->DCR4 =
+ 0 << XSPI_DCR4_REFRESH_Pos // refresh disabled (it's non-volatile memory)
+ ;
+
+ XSPIx->TCR = 0;
+
+ // Enable the XSPI peripheral.
+ XSPIx->CR |= XSPI_CR_EN;
+
+ // XSPIM init
+ XSPI1->CR &= ~(1 << XSPI_CR_EN_Pos);
+ XSPI2->CR &= ~(1 << XSPI_CR_EN_Pos);
+ XSPIM->CR = 0; // can also be (1 << 4) to pass through CS signal
+ XSPIx->CR |= 1 << XSPI_CR_EN_Pos;
+
+ #ifdef pyb_pin_FLASH_RESET
+ // Reset SPI flash to make sure it's in a known state (SPI mode).
+ mp_hal_pin_output(pyb_pin_FLASH_RESET);
+ mp_hal_pin_low(pyb_pin_FLASH_RESET);
+ xspi_delay_us(1000);
+ mp_hal_pin_high(pyb_pin_FLASH_RESET);
+ xspi_delay_us(10000);
+ #endif
+
+ // Enable memory-mapped mode.
+ // Can select either SPI or DTR mode.
+ if (1) {
+ xspi_switch_to_dtr();
+ xspi_memory_map_888();
+ } else {
+ xspi_memory_map_111();
+ }
+}
+
+uint32_t xspi_get_xip_base(const xspi_flash_t *self) {
+ return self->xip_base;
+}
+
+bool xspi_is_valid_addr(const xspi_flash_t *self, uint32_t addr) {
+ return self->xip_base <= addr && addr < self->xip_base + 256 * 1024 * 1024;
+}
+
+static int xspi_read_111_ext(uint8_t cmd, bool addr_enabled, uint32_t addr, size_t len, uint8_t *dest) {
+ uint32_t admode = addr_enabled ? 1 : 0;
+ XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 1 << XSPI_CR_FMODE_Pos; // indirect read mode
+ XSPIx->CCR =
+ 1 << XSPI_CCR_DMODE_Pos // data on 1 line
+ | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address size
+ | admode << XSPI_CCR_ADMODE_Pos // address on 1 line, or disabled
+ | 1 << XSPI_CCR_IMODE_Pos // instruction on 1 line
+ ;
+ XSPIx->TCR = 0 << XSPI_TCR_DCYC_Pos; // 0 dummy cycles
+ XSPIx->DLR = len - 1; // number of bytes to read
+ XSPIx->IR = cmd; // read opcode (triggers the start of the transaction if address disabled)
+ if (addr_enabled) {
+ XSPIx->AR = addr; // triggers the start of the transaction
+ }
+
+ #if 0 // untested code
+ // Read in the data 4 bytes at a time if dest is aligned.
+ if (((uintptr_t)dest & 3) == 0) {
+ while (len >= 4) {
+ while (!(XSPIx->SR & XSPI_SR_FTF)) {
+ if (XSPIx->SR & XSPI_SR_TEF) {
+ return -MP_EIO;
+ }
+ }
+ *(uint32_t *)dest = XSPIx->DR;
+ dest += 4;
+ len -= 4;
+ }
+ }
+ #endif
+
+ // Read in data 1 byte at a time.
+ while (len--) {
+ while (!((XSPIx->SR >> XSPI_SR_FLEVEL_Pos) & 0x3f)) {
+ if (XSPIx->SR & XSPI_SR_TEF) {
+ return -MP_EIO;
+ }
+ }
+ *dest++ = *(volatile uint8_t *)&XSPIx->DR;
+ }
+
+ XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag
+
+ return 0;
+}
+
+static int xspi_write_111_ext(uint8_t cmd, bool addr_enabled, uint32_t addr, size_t len, const uint8_t *src) {
+ uint32_t dmode = len == 0 ? 0 : 1;
+ uint32_t admode = addr_enabled ? 1 : 0;
+
+ // Configure and start the transfer.
+ // Transfer starts with IR write if no address or data, with AR write if no data,
+ // otherwise with DR write.
+
+ XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 0 << XSPI_CR_FMODE_Pos; // indirect write mode
+ XSPIx->CCR =
+ dmode << XSPI_CCR_DMODE_Pos // data on 1 line, or disabled
+ | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address size
+ | admode << XSPI_CCR_ADMODE_Pos // address on 1 line, or disabled
+ | 1 << XSPI_CCR_IMODE_Pos // instruction on 1 line
+ ;
+ XSPIx->TCR = 0 << XSPI_TCR_DCYC_Pos; // 0 dummy cycles
+ if (len != 0) {
+ XSPIx->DLR = len - 1;
+ }
+ XSPIx->IR = cmd; // write opcode
+ if (addr_enabled) {
+ XSPIx->AR = addr; // address
+ }
+
+ // Write out the data one byte at a time
+ while (len--) {
+ while (!(XSPIx->SR & XSPI_SR_FTF)) {
+ if (XSPIx->SR & XSPI_SR_TEF) {
+ return -MP_EIO;
+ }
+ }
+ *(volatile uint8_t *)&XSPIx->DR = *src++;
+ }
+
+ // Wait for write to finish
+ while (!(XSPIx->SR & XSPI_SR_TCF)) {
+ if (XSPIx->SR & XSPI_SR_TEF) {
+ return -MP_EIO;
+ }
+ }
+
+ XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag
+
+ // Wait for peripheral to return to idle.
+ while (XSPIx->SR & XSPI_SR_BUSY) {
+ }
+
+ return 0;
+}
+
+static int xspi_read_888_dtr_ext(uint16_t cmd, bool addr_enabled, uint32_t addr, uint32_t num_dummy, size_t len, uint8_t *dest) {
+ uint32_t admode = addr_enabled ? 4 : 0;
+
+ // Configure and start the transfer.
+ // Transfer starts with IR write if no address, otherwise with AR write.
+
+ XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 1 << XSPI_CR_FMODE_Pos; // indirect read mode
+ XSPIx->CCR =
+ 1 << XSPI_CCR_DQSE_Pos // DQS enabled
+ | 1 << XSPI_CCR_DDTR_Pos // data DTR enabled
+ | 4 << XSPI_CCR_DMODE_Pos // data on 8 lines
+ | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address size
+ | 1 << XSPI_CCR_ADDTR_Pos // address DTR enabled
+ | admode << XSPI_CCR_ADMODE_Pos // address on 8 lines, or disabled
+ | 1 << XSPI_CCR_ISIZE_Pos // 16-bit instruction
+ | 1 << XSPI_CCR_IDTR_Pos // instruction DTR enabled
+ | 4 << XSPI_CCR_IMODE_Pos // instruction on 8 lines
+ ;
+ XSPIx->TCR = num_dummy << XSPI_TCR_DCYC_Pos; // N dummy cycles
+ XSPIx->DLR = len - 1;
+ XSPIx->IR = cmd; // read opcode
+ if (addr_enabled) {
+ XSPIx->AR = addr; // address
+ }
+
+ // Read in data 1 byte at a time.
+ while (len--) {
+ while (!((XSPIx->SR >> XSPI_SR_FLEVEL_Pos) & 0x3f)) {
+ if (XSPIx->SR & XSPI_SR_TEF) {
+ return -MP_EIO;
+ }
+ }
+ *dest++ = *(volatile uint8_t *)&XSPIx->DR;
+ }
+
+ XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag
+
+ return 0;
+}
+
+static int xspi_write_888_dtr_ext(uint16_t cmd, bool addr_enabled, uint32_t addr, size_t len, const uint8_t *src) {
+ uint32_t dmode = len == 0 ? 0 : 4;
+ uint32_t admode = addr_enabled ? 4 : 0;
+
+ // Configure and start the transfer.
+ // Transfer starts with IR write if no address or data, with AR write if no data,
+ // otherwise with DR write.
+
+ XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 0 << XSPI_CR_FMODE_Pos; // indirect write mode
+ XSPIx->CCR =
+ 1 << XSPI_CCR_DDTR_Pos // data DTR enabled
+ | dmode << XSPI_CCR_DMODE_Pos // data on 8 lines, or disabled
+ | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address size
+ | 1 << XSPI_CCR_ADDTR_Pos // address DTR enabled
+ | admode << XSPI_CCR_ADMODE_Pos // address on 8 lines, or disabled
+ | 1 << XSPI_CCR_ISIZE_Pos // 16-bit instruction
+ | 1 << XSPI_CCR_IDTR_Pos // instruction DTR enabled
+ | 4 << XSPI_CCR_IMODE_Pos // instruction on 8 lines
+ ;
+ XSPIx->TCR = 0 << XSPI_TCR_DCYC_Pos; // 0 dummy cycles
+ if (len != 0) {
+ XSPIx->DLR = len - 1;
+ }
+ XSPIx->IR = cmd; // write opcode
+ if (addr_enabled) {
+ XSPIx->AR = addr; // address
+ }
+
+ // Write out the data one byte at a time
+ while (len--) {
+ while (!(XSPIx->SR & XSPI_SR_FTF)) {
+ if (XSPIx->SR & XSPI_SR_TEF) {
+ return -MP_EIO;
+ }
+ }
+ *(volatile uint8_t *)&XSPIx->DR = *src++;
+ }
+
+ // Wait for write to finish
+ while (!(XSPIx->SR & XSPI_SR_TCF)) {
+ if (XSPIx->SR & XSPI_SR_TEF) {
+ return -MP_EIO;
+ }
+ }
+
+ XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag
+
+ // Wait for peripheral to return to idle.
+ while (XSPIx->SR & XSPI_SR_BUSY) {
+ }
+
+ return 0;
+}
+
+static void xspi_memory_map_111(void) {
+ XSPIx->CCR =
+ 1 << XSPI_CCR_DMODE_Pos // data on 1 line
+ | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address
+ | 1 << XSPI_CCR_ADMODE_Pos // address on 1 line
+ | 1 << XSPI_CCR_IMODE_Pos // instruction on 1 line
+ ;
+
+ XSPIx->TCR = 0 << XSPI_TCR_DCYC_Pos; // no dummy cycles
+ XSPIx->IR = 0x13; // READ4B
+ XSPIx->LPTR = 1024; // timeout period in number of CLK cycles
+
+ // Enable the XSPI peripheral in memory-mapped mode.
+ XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 3 << XSPI_CR_FMODE_Pos;
+}
+
+static void xspi_memory_map_888(void) {
+ XSPIx->CCR =
+ 1 << XSPI_CCR_DQSE_Pos // DQS enabled
+ | 1 << XSPI_CCR_DDTR_Pos // data DTR enabled
+ | 4 << XSPI_CCR_DMODE_Pos // data on 8 lines
+ | 3 << XSPI_CCR_ADSIZE_Pos // 32-bit address
+ | 1 << XSPI_CCR_ADDTR_Pos // address DTR enabled
+ | 4 << XSPI_CCR_ADMODE_Pos // address on 8 lines
+ | 1 << XSPI_CCR_ISIZE_Pos // 16-bit instruction
+ | 1 << XSPI_CCR_IDTR_Pos // instruction DTR enabled
+ | 4 << XSPI_CCR_IMODE_Pos // instruction on 8 lines
+ ;
+
+ XSPIx->TCR = 20 << XSPI_TCR_DCYC_Pos; // 20 dummy cycles for reading (minimum, flash may insert more by holding DQS low)
+ XSPIx->IR = 0xee11; // octal DTR read mode (8DTRD)
+ XSPIx->LPTR = 1024; // timeout period in number of CLK cycles
+
+ // Enable the XSPI peripheral in memory-mapped mode.
+ XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 3 << XSPI_CR_FMODE_Pos;
+}
+
+static void xspi_memory_map_exit(void) {
+ // Abort any ongoing transfer if peripheral is busy.
+ if (XSPIx->SR & XSPI_SR_BUSY) {
+ XSPIx->CR |= XSPI_CR_ABORT;
+ while (!(XSPIx->SR & XSPI_SR_TCF)) {
+ }
+ XSPIx->FCR = XSPI_FCR_CTCF; // clear TC flag
+ while (XSPIx->SR & XSPI_SR_BUSY) {
+ }
+ }
+ XSPIx->CR = (XSPIx->CR & ~XSPI_CR_FMODE_Msk) | 0 << XSPI_CR_FMODE_Pos; // indirect write mode
+}
+
+void xspi_switch_to_dtr(void) {
+ uint8_t buf[4];
+
+ // WREN.
+ xspi_write_111_ext(0x06, false, 0, 0, NULL);
+
+ // Wait WEL=1, with small timeout.
+ for (unsigned int i = 0; i < 100; ++i) {
+ xspi_read_111_ext(0x05, false, 0, 1, buf);
+ if (buf[0] & 2) {
+ break;
+ }
+ }
+
+ // Switch to DOPI DTR mode.
+ buf[0] = 2;
+ xspi_write_111_ext(0x72, true, 0x00000000, 1, buf);
+
+ xspi_dtr_enabled = true;
+}
+
+void xspi_switch_to_spi(void) {
+ uint8_t buf[4];
+
+ // WREN.
+ xspi_write_888_dtr_ext(0x06f9, false, 0, 0, NULL);
+
+ // Wait WEL=1, with small timeout.
+ for (unsigned int i = 0; i < 100; ++i) {
+ xspi_read_111_ext(0x05, false, 0, 1, buf);
+ if (buf[0] & 2) {
+ break;
+ }
+ }
+
+ // Switch to SPI mode.
+ buf[0] = 0;
+ buf[1] = 0;
+ xspi_write_888_dtr_ext(0x728d, true, 0x00000000, 2, buf);
+
+ xspi_dtr_enabled = false;
+}
+
+static int xspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) {
+ xspi_flash_t *self = self_in;
+ switch (cmd) {
+ case MP_QSPI_IOCTL_INIT:
+ // XSPI must be manually initialise by calling `xspi_init()` at boot.
+ // Here, just determine if it's in SPI or DTR mode.
+ xspi_dtr_enabled = XSPIx->IR == 0xee11;
+ break;
+ case MP_QSPI_IOCTL_BUS_ACQUIRE:
+ xspi_memory_map_exit();
+ break;
+ case MP_QSPI_IOCTL_BUS_RELEASE:
+ if (xspi_dtr_enabled) {
+ xspi_memory_map_888();
+ } else {
+ xspi_memory_map_111();
+ }
+ break;
+ case MP_QSPI_IOCTL_MEMORY_MODIFIED: {
+ uintptr_t *addr_len = (uintptr_t *)arg;
+ volatile void *addr = (volatile void *)(self->xip_base + addr_len[0]);
+ size_t len = addr_len[1];
+ SCB_InvalidateICache_by_Addr(addr, len);
+ SCB_InvalidateDCache_by_Addr(addr, len);
+ break;
+ }
+ }
+ return 0; // success
+}
+
+// These commands may be passed to this function.
+#define CMD_WREN (0x06)
+#define CMD_RSTEN (0x66)
+#define CMD_RESET (0x99)
+#define CMD_SLEEP (0xb9)
+#define CMD_AWAKE (0xab)
+static int xspi_write_cmd_data(void *self_in, uint8_t cmd, size_t len, uint32_t data) {
+ if (xspi_dtr_enabled) {
+ uint16_t cmd16 = 0;
+ if (cmd == CMD_WREN) {
+ cmd16 = 0x06f9;
+ } else if (cmd == CMD_SLEEP) {
+ cmd16 = 0xb946;
+ } else if (cmd == CMD_AWAKE) {
+ cmd16 = 0xab54;
+ }
+ return xspi_write_888_dtr_ext(cmd16, false, 0, len, (const uint8_t *)&data);
+ }
+ return xspi_write_111_ext(cmd, false, 0, len, (const uint8_t *)&data);
+}
+
+// These commands may be passed to this function.
+#define CMD_WRITE (0x02)
+#define CMD_WRITE_32 (0x12)
+#define CMD_SEC_ERASE (0x20)
+#define CMD_SEC_ERASE_32 (0x21)
+static int xspi_write_cmd_addr_data(void *self_in, uint8_t cmd, uint32_t addr, size_t len, const uint8_t *src) {
+ // Convert 24-bit address commands to 32-bit address commands.
+ if (cmd == CMD_WRITE) {
+ cmd = CMD_WRITE_32;
+ } else if (cmd == CMD_SEC_ERASE) {
+ cmd = CMD_SEC_ERASE_32;
+ }
+ if (xspi_dtr_enabled) {
+ uint16_t cmd16 = 0;
+ if (cmd == CMD_WRITE_32) {
+ cmd16 = 0x12ed;
+ } else if (cmd == CMD_SEC_ERASE_32) {
+ cmd16 = 0x21de;
+ }
+ return xspi_write_888_dtr_ext(cmd16, true, addr, len, src);
+ }
+ return xspi_write_111_ext(cmd, true, addr, len, src);
+}
+
+// These commands may be passed to this function.
+#define CMD_RDSR (0x05)
+#define CMD_RD_DEVID (0x9f)
+static int xspi_read_cmd(void *self_in, uint8_t cmd, size_t len, uint32_t *dest) {
+ (void)self_in;
+ if (xspi_dtr_enabled) {
+ uint16_t cmd16 = 0;
+ uint32_t num_dummy = 0;
+ if (cmd == CMD_RDSR) {
+ cmd16 = 0x05fa;
+ num_dummy = 4;
+ len = 2;
+ } else if (cmd == CMD_RD_DEVID) {
+ // TODO this doesn't really work, because result is in STR format.
+ cmd16 = 0x9f60;
+ num_dummy = 4;
+ }
+ return xspi_read_888_dtr_ext(cmd16, true, 0, num_dummy, len, (uint8_t *)dest);
+ }
+ return xspi_read_111_ext(cmd, false, 0, len, (uint8_t *)dest);
+}
+
+static int xspi_direct_read(void *self_in, uint32_t addr, size_t len, uint8_t *dest) {
+ xspi_flash_t *self = self_in;
+ memcpy(dest, (const void *)(self->xip_base + addr), len);
+ return 0;
+}
+
+const mp_qspi_proto_t xspi_proto = {
+ .ioctl = xspi_ioctl,
+ .write_cmd_data = xspi_write_cmd_data,
+ .write_cmd_addr_data = xspi_write_cmd_addr_data,
+ .read_cmd = xspi_read_cmd,
+ .read_cmd_qaddr_qdata = NULL, // unused because .direct_read is set below, and caching is disabled
+ .direct_read = xspi_direct_read,
+};
+
+#endif // defined(MICROPY_HW_XSPIFLASH_SIZE_BITS_LOG2)