This commit adds preliminary support for ST's new STM32N6xx MCUs. Supported features of this MCU so far are: - basic clock tree initialisation, running at 800MHz - fully working USB - XSPI in memory-mapped mode - machine.Pin - machine.UART - RTC and deepsleep support - SD card - filesystem - ROMFS - WiFi and BLE via cyw43-driver (SDIO backend) Note that the N6 does not have internal flash, and has some tricky boot sequence, so using a custom bootloader (mboot) is almost a necessity. Signed-off-by: Damien George <damien@micropython.org>
599 lines
21 KiB
C
599 lines
21 KiB
C
/*
|
|
* 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)
|