diff --git a/drivers/bus/qspi.h b/drivers/bus/qspi.h index 009f55b159..160cc63d4a 100644 --- a/drivers/bus/qspi.h +++ b/drivers/bus/qspi.h @@ -37,14 +37,20 @@ enum { MP_QSPI_IOCTL_DEINIT, MP_QSPI_IOCTL_BUS_ACQUIRE, MP_QSPI_IOCTL_BUS_RELEASE, + MP_QSPI_IOCTL_FLASH_SIZE, +}; + +enum qspi_tranfer_mode { + MP_QSPI_TRANSFER_CMD_ADDR_DATA, + MP_QSPI_TRANSFER_CMD_QADDR_QDATA, }; typedef struct _mp_qspi_proto_t { - int (*ioctl)(void *self, uint32_t cmd); + int (*ioctl)(void *self, uint32_t cmd, uint32_t arg); int (*write_cmd_data)(void *self, uint8_t cmd, size_t len, uint32_t data); int (*write_cmd_addr_data)(void *self, uint8_t cmd, uint32_t addr, size_t len, const uint8_t *src); int (*read_cmd)(void *self, uint8_t cmd, size_t len, uint32_t *dest); - int (*read_cmd_qaddr_qdata)(void *self, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest); + int (*read_cmd_addr_data)(void *self, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest, uint8_t mode); } mp_qspi_proto_t; typedef struct _mp_soft_qspi_obj_t { diff --git a/drivers/bus/softqspi.c b/drivers/bus/softqspi.c index 65a504a739..d5eaf5e6a1 100644 --- a/drivers/bus/softqspi.c +++ b/drivers/bus/softqspi.c @@ -56,7 +56,7 @@ static void nibble_write(mp_soft_qspi_obj_t *self, uint8_t v) { mp_hal_pin_write(self->io3, (v >> 3) & 1); } -static int mp_soft_qspi_ioctl(void *self_in, uint32_t cmd) { +static int mp_soft_qspi_ioctl(void *self_in, uint32_t cmd, uint32_t arg) { mp_soft_qspi_obj_t *self = (mp_soft_qspi_obj_t*)self_in; switch (cmd) { @@ -188,16 +188,28 @@ static int mp_soft_qspi_read_cmd(void *self_in, uint8_t cmd, size_t len, uint32_ return 0; } -static int mp_soft_qspi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest) { +static int mp_soft_qspi_read_cmd_addr_data(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest, uint8_t mode) { + int ret = 0; mp_soft_qspi_obj_t *self = (mp_soft_qspi_obj_t*)self_in; uint8_t cmd_buf[7] = {cmd}; uint8_t addr_len = mp_spi_set_addr_buff(&cmd_buf[1], addr); CS_LOW(self); mp_soft_qspi_transfer(self, 1, cmd_buf, NULL); - mp_soft_qspi_qwrite(self, addr_len + 3, &cmd_buf[1]); // 3/4 addr bytes, 1 extra byte (0), 2 dummy bytes (4 dummy cycles) - mp_soft_qspi_qread(self, len, dest); + if (mode == MP_QSPI_TRANSFER_CMD_ADDR_DATA) { + // cmd, address and data on 1 line. + // 3 addr bytes, 1 dummy byte (8 dummy cycles x 1 line) + mp_soft_qspi_transfer(self, addr_len + 1, &cmd_buf[1], NULL); + mp_soft_qspi_transfer(self, len, NULL, dest); + } else if (mode == MP_QSPI_TRANSFER_CMD_QADDR_QDATA) { + // cmd 1 line, address and data on 4 lines. + // 3/4 addr bytes, 1 extra byte (mode: 0), 2 dummy bytes (4 dummy cycles x 4 lines) + mp_soft_qspi_qwrite(self, addr_len + 3, &cmd_buf[1]); + mp_soft_qspi_qread(self, len, dest); + } else { + ret = -1; + } CS_HIGH(self); - return 0; + return ret; } const mp_qspi_proto_t mp_soft_qspi_proto = { @@ -205,5 +217,5 @@ const mp_qspi_proto_t mp_soft_qspi_proto = { .write_cmd_data = mp_soft_qspi_write_cmd_data, .write_cmd_addr_data = mp_soft_qspi_write_cmd_addr_data, .read_cmd = mp_soft_qspi_read_cmd, - .read_cmd_qaddr_qdata = mp_soft_qspi_read_cmd_qaddr_qdata, + .read_cmd_addr_data = mp_soft_qspi_read_cmd_addr_data, }; diff --git a/drivers/memory/external_flash_device.h b/drivers/memory/external_flash_device.h index b5cf4a8877..37d914f2c7 100644 --- a/drivers/memory/external_flash_device.h +++ b/drivers/memory/external_flash_device.h @@ -29,11 +29,16 @@ #include #include -typedef struct { +typedef struct _external_flash_device { + // Flash size in bytes. uint32_t total_size; + uint16_t start_up_time_us; // Three response bytes to 0x9f JEDEC ID command. + // The first field is always a manufacturer_id, however the other two are used + // differently be each manufacturer. + // All three together will always uniquely identify a chip model. uint8_t manufacturer_id; uint8_t memory_type; uint8_t capacity; @@ -66,9 +71,12 @@ typedef struct { bool single_status_byte : 1; } external_flash_device; -// Settings for the Adesto Tech AT25DF081A 1MiB SPI flash. Its on the SAMD21 -// Xplained board. +// typedef struct _external_flash_device ; + +// Settings for the Adesto Tech / Renesas AT25DF081A 1MiB SPI flash. +// Its on the SAMD21 Xplained board. // Datasheet: https://www.adestotech.com/wp-content/uploads/doc8715.pdf +// https://www.renesas.com/eu/en/document/dst/at25df081a-datasheet #define AT25DF081A { \ .total_size = (1 << 20), /* 1 MiB */ \ .start_up_time_us = 10000, \ @@ -85,6 +93,42 @@ typedef struct { .single_status_byte = false, \ } +// Settings for the Renesas AT25SF161B 2MiB SPI flash. +// Datasheet: https://www.renesas.com/us/en/document/dst/at25sf161b-datasheet?r=1608796 +#define AT25SF161B { \ + .total_size = (1 << 21), /* 2 MiB */ \ + .start_up_time_us = 5000, \ + .manufacturer_id = 0x1f, \ + .memory_type = 0x86, \ + .capacity = 0x01, \ + .max_clock_speed_mhz = 133, \ + .quad_enable_bit_mask = 0x02, \ + .has_sector_protection = true, \ + .supports_fast_read = true, \ + .supports_qspi = true, \ + .supports_qspi_writes = true, \ + .write_status_register_split = true, \ + .single_status_byte = false, \ +} + +// Settings for the Renesas AT25SF641B 8MiB SPI flash. +// Datasheet: https://www.renesas.com/us/en/document/dst/at25sf641b-datasheet?r=1608816 +#define AT25SF641B { \ + .total_size = (1 << 23), /* 8 MiB */ \ + .start_up_time_us = 5000, \ + .manufacturer_id = 0x1f, \ + .memory_type = 0x88, \ + .capacity = 0x01, \ + .max_clock_speed_mhz = 133, \ + .quad_enable_bit_mask = 0x02, \ + .has_sector_protection = true, \ + .supports_fast_read = true, \ + .supports_qspi = true, \ + .supports_qspi_writes = true, \ + .write_status_register_split = true, \ + .single_status_byte = false, \ +} + // Settings for the Gigadevice GD25Q16C 2MiB SPI flash. // Datasheet: http://www.gigadevice.com/datasheet/gd25q16c/ #define GD25Q16C { \ @@ -388,6 +432,24 @@ typedef struct { .single_status_byte = true, \ } +// Settings for the Macronix MX25L25673G 32MiB SPI flash. +// Datasheet: https://www.macronix.com/Lists/Datasheet/Attachments/8761/MX25L25673G,%203V,%20256Mb,%20v1.7.pdf +#define MX25L25673G { \ + .total_size = (1 << 25), /* 32 MiB */ \ + .start_up_time_us = 5000, \ + .manufacturer_id = 0xc2, \ + .memory_type = 0x20, \ + .capacity = 0x19, \ + .max_clock_speed_mhz = 133, \ + .quad_enable_bit_mask = 0x40, \ + .has_sector_protection = false, \ + .supports_fast_read = true, \ + .supports_qspi = true, \ + .supports_qspi_writes = true, \ + .write_status_register_split = false, \ + .single_status_byte = true, \ +} + // Settings for the Macronix MX25R6435F 8MiB SPI flash. // Datasheet: http://www.macronix.com/Lists/Datasheet/Attachments/7428/MX25R6435F,%20Wide%20Range,%2064Mb,%20v1.4.pdf // By default its in lower power mode which can only do 8mhz. In high power mode it can do 80mhz. diff --git a/drivers/memory/spiflash.c b/drivers/memory/spiflash.c index e66153636b..49647e2dbb 100644 --- a/drivers/memory/spiflash.c +++ b/drivers/memory/spiflash.c @@ -30,17 +30,19 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "drivers/memory/spiflash.h" +#include "drivers/memory/external_flash_device.h" -#define QSPI_QE_MASK (0x02) #define USE_WR_DELAY (1) #define CMD_WRSR (0x01) +#define CMD_WRCR (0x31) #define CMD_WRITE (0x02) #define CMD_READ (0x03) #define CMD_RDSR (0x05) #define CMD_WREN (0x06) #define CMD_SEC_ERASE (0x20) #define CMD_RDCR (0x35) +#define CMD_RD_SFDP (0x5a) #define CMD_RD_DEVID (0x9f) #define CMD_CHIP_ERASE (0xc7) #define CMD_C4READ (0xeb) @@ -56,17 +58,36 @@ #define PAGE_SIZE (256) // maximum bytes we can write in one SPI transfer #define SECTOR_SIZE MP_SPIFLASH_ERASE_BLOCK_SIZE +#ifndef MICROPY_HW_SPIFLASH_DEVICES +#define MICROPY_HW_SPIFLASH_DEVICES +#endif + +#if !BUILDING_MBOOT +#define diag_printf(...) mp_printf(MICROPY_ERROR_PRINTER, __VA_ARGS__) +#else +#define diag_printf(...) +#endif + +// List of all possible flash devices used by device. +// MICROPY_HW_SPIFLASH_DEVICES can be set to a comma separated list in mpconfigboard.h +static const external_flash_device possible_devices[] = { + MICROPY_HW_SPIFLASH_DEVICES +}; +#define EXTERNAL_FLASH_DEVICE_COUNT MP_ARRAY_SIZE(possible_devices) +static external_flash_device generic_config = GENERIC; + + static void mp_spiflash_acquire_bus(mp_spiflash_t *self) { const mp_spiflash_config_t *c = self->config; if (c->bus_kind == MP_SPIFLASH_BUS_QSPI) { - c->bus.u_qspi.proto->ioctl(c->bus.u_qspi.data, MP_QSPI_IOCTL_BUS_ACQUIRE); + c->bus.u_qspi.proto->ioctl(c->bus.u_qspi.data, MP_QSPI_IOCTL_BUS_ACQUIRE, 0); } } static void mp_spiflash_release_bus(mp_spiflash_t *self) { const mp_spiflash_config_t *c = self->config; if (c->bus_kind == MP_SPIFLASH_BUS_QSPI) { - c->bus.u_qspi.proto->ioctl(c->bus.u_qspi.data, MP_QSPI_IOCTL_BUS_RELEASE); + c->bus.u_qspi.proto->ioctl(c->bus.u_qspi.data, MP_QSPI_IOCTL_BUS_RELEASE, 0); } } @@ -84,14 +105,15 @@ static int mp_spiflash_write_cmd_data(mp_spiflash_t *self, uint8_t cmd, size_t l return ret; } -static int mp_spiflash_transfer_cmd_addr_data(mp_spiflash_t *self, uint8_t cmd, uint32_t addr, size_t len, const uint8_t *src, uint8_t *dest) { +static int mp_spiflash_transfer_cmd_addr_data(mp_spiflash_t *self, uint8_t cmd, uint32_t addr, size_t len, const uint8_t *src, uint8_t *dest, uint8_t mode) { int ret = 0; const mp_spiflash_config_t *c = self->config; if (c->bus_kind == MP_SPIFLASH_BUS_SPI) { - uint8_t buf[5] = {cmd, 0}; + uint8_t buf[6] = {cmd, 0}; uint8_t buff_len = 1 + mp_spi_set_addr_buff(&buf[1], addr); + uint8_t dummy = (cmd == CMD_RD_SFDP)? 1 : 0; mp_hal_pin_write(c->bus.u_spi.cs, 0); - c->bus.u_spi.proto->transfer(c->bus.u_spi.data, buff_len, buf, NULL); + c->bus.u_spi.proto->transfer(c->bus.u_spi.data, buff_len + dummy, buf, NULL); if (len && (src != NULL)) { c->bus.u_spi.proto->transfer(c->bus.u_spi.data, len, src, NULL); } else if (len && (dest != NULL)) { @@ -101,7 +123,7 @@ static int mp_spiflash_transfer_cmd_addr_data(mp_spiflash_t *self, uint8_t cmd, mp_hal_pin_write(c->bus.u_spi.cs, 1); } else { if (dest != NULL) { - ret = c->bus.u_qspi.proto->read_cmd_qaddr_qdata(c->bus.u_qspi.data, cmd, addr, len, dest); + ret = c->bus.u_qspi.proto->read_cmd_addr_data(c->bus.u_qspi.data, cmd, addr, len, dest, mode); } else { ret = c->bus.u_qspi.proto->write_cmd_addr_data(c->bus.u_qspi.data, cmd, addr, len, src); } @@ -114,7 +136,7 @@ static int mp_spiflash_read_cmd(mp_spiflash_t *self, uint8_t cmd, size_t len, ui if (c->bus_kind == MP_SPIFLASH_BUS_SPI) { mp_hal_pin_write(c->bus.u_spi.cs, 0); c->bus.u_spi.proto->transfer(c->bus.u_spi.data, 1, &cmd, NULL); - c->bus.u_spi.proto->transfer(c->bus.u_spi.data, len, (void*)dest, (void*)dest); + c->bus.u_spi.proto->transfer(c->bus.u_spi.data, len, (void *)dest, (void *)dest); mp_hal_pin_write(c->bus.u_spi.cs, 1); return 0; } else { @@ -125,12 +147,15 @@ static int mp_spiflash_read_cmd(mp_spiflash_t *self, uint8_t cmd, size_t len, ui static int mp_spiflash_read_data(mp_spiflash_t *self, uint32_t addr, size_t len, uint8_t *dest) { const mp_spiflash_config_t *c = self->config; uint8_t cmd; + uint8_t mode; if (c->bus_kind == MP_SPIFLASH_BUS_SPI) { cmd = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? CMD_READ_32 : CMD_READ; + mode = MP_QSPI_TRANSFER_CMD_ADDR_DATA; } else { cmd = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? CMD_C4READ_32 : CMD_C4READ; + mode = MP_QSPI_TRANSFER_CMD_QADDR_QDATA; } - return mp_spiflash_transfer_cmd_addr_data(self, cmd, addr, len, NULL, dest); + return mp_spiflash_transfer_cmd_addr_data(self, cmd, addr, len, NULL, dest, mode); } static int mp_spiflash_write_cmd(mp_spiflash_t *self, uint8_t cmd) { @@ -164,7 +189,8 @@ static inline void mp_spiflash_deepsleep_internal(mp_spiflash_t *self, int value mp_spiflash_write_cmd(self, value ? 0xb9 : 0xab); // sleep/wake } -void mp_spiflash_init(mp_spiflash_t *self) { +int mp_spiflash_init(mp_spiflash_t *self) { + int ret = 0; self->flags = 0; if (self->config->bus_kind == MP_SPIFLASH_BUS_SPI) { @@ -172,7 +198,7 @@ void mp_spiflash_init(mp_spiflash_t *self) { mp_hal_pin_output(self->config->bus.u_spi.cs); self->config->bus.u_spi.proto->ioctl(self->config->bus.u_spi.data, MP_SPI_IOCTL_INIT); } else { - self->config->bus.u_qspi.proto->ioctl(self->config->bus.u_qspi.data, MP_QSPI_IOCTL_INIT); + self->config->bus.u_qspi.proto->ioctl(self->config->bus.u_qspi.data, MP_QSPI_IOCTL_INIT, 0); } mp_spiflash_acquire_bus(self); @@ -183,30 +209,130 @@ void mp_spiflash_init(mp_spiflash_t *self) { #if defined(CHECK_DEVID) // Validate device id uint32_t devid; - int ret = mp_spiflash_read_cmd(self, CMD_RD_DEVID, 3, &devid); + ret = mp_spiflash_read_cmd(self, CMD_RD_DEVID, 3, &devid); if (ret != 0 || devid != CHECK_DEVID) { mp_spiflash_release_bus(self); - return; + return -2; } #endif - if (self->config->bus_kind == MP_SPIFLASH_BUS_QSPI) { - // Set QE bit - uint32_t sr = 0, cr = 0; - int ret = mp_spiflash_read_cmd(self, CMD_RDSR, 1, &sr); - if (ret == 0) { - ret = mp_spiflash_read_cmd(self, CMD_RDCR, 1, &cr); + // Start with generic configuration, update with exact if found. + self->device = &generic_config; + + uint8_t jedec_ids[3]; + ret = mp_spiflash_read_cmd(self, CMD_RD_DEVID, 3, (uint32_t *)jedec_ids); + if (ret != 0) { + mp_spiflash_release_bus(self); + return -2; + } + for (uint8_t i = 0; i < EXTERNAL_FLASH_DEVICE_COUNT; i++) { + const external_flash_device *possible_device = &possible_devices[i]; + if (jedec_ids[0] == possible_device->manufacturer_id && + jedec_ids[1] == possible_device->memory_type && + jedec_ids[2] == possible_device->capacity) { + self->device = possible_device; + break; } - uint32_t data = (sr & 0xff) | (cr & 0xff) << 8; - if (ret == 0 && !(data & (QSPI_QE_MASK << 8))) { - data |= QSPI_QE_MASK << 8; - mp_spiflash_write_cmd(self, CMD_WREN); - mp_spiflash_write_cmd_data(self, CMD_WRSR, 2, data); - mp_spiflash_wait_wip0(self); + } + // If the flash device is not known, try to autodetect suitable settings. + if (self->device == &generic_config) { + if (jedec_ids[0] == 0xc2) { // Macronix devices + generic_config.quad_enable_bit_mask = 0x04; + generic_config.single_status_byte = true; + } + #if MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES + generic_config.total_size = MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES; + #else + // Try to read "Serial Flash Discoverable Parameters" + // JEDEC Standard No. 216, 9 x 32bit dwords of data. + // Start be reading the headers to confirm sfdp is supported and find the parameter table address. + uint32_t sfdp[4] = {0}; + ret = mp_spiflash_transfer_cmd_addr_data(self, CMD_RD_SFDP, 0, sizeof(sfdp), NULL, (uint8_t *)sfdp, MP_QSPI_TRANSFER_CMD_ADDR_DATA); + const char sfdp_header[] = {'S', 'F', 'D', 'P'}; + if (ret != 0 || sfdp[0] != *(uint32_t *)sfdp_header) { + diag_printf("mp_spiflash: sfdp not supported\n"); + diag_printf("Set MICROPY_HW_SPIFLASH_DEVICES or MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES"); + diag_printf("jedec ids: 0x%x 0x%x 0x%x\n", jedec_ids[0], jedec_ids[1], jedec_ids[2]); + mp_spiflash_release_bus(self); + return -2; + } else { + // Read the first few SFDP parameter tables. + uint32_t sfdp_param_table_addr = sfdp[3] & 0xFFFFFF; + ret = mp_spiflash_transfer_cmd_addr_data(self, CMD_RD_SFDP, sfdp_param_table_addr, sizeof(sfdp), NULL, (uint8_t *)sfdp, MP_QSPI_TRANSFER_CMD_ADDR_DATA); + // Flash Memory Density + uint32_t size = sfdp[1] & ~(1 << 31); + if (size != 0) { + if (sfdp[1] & (1 << 31)) { + // When bit-31 is set to 1, the total bits is 2^size. + generic_config.total_size = 1 << size; + } else { + // When bit-31 is set to 0, the total bits is size + 1. + generic_config.total_size = (size + 1) / 8; + } + } + uint8_t opcode_sec_erase = (sfdp[0] >> 8 & 0xFF); + if (opcode_sec_erase != 0x20) { + diag_printf("mp_spiflash_sec_erase: opcode not supported\n"); + } + if (sfdp[0] & (1 << 21)) { + // Supports (1-4-4) Fast Read: Device supports single line opcode, + // quad line address, and quad output Fast Read. + generic_config.supports_fast_read = true; + generic_config.supports_qspi = true; + } + } + #endif + } + + if (self->config->bus_kind == MP_SPIFLASH_BUS_QSPI) { + // Set quad enable (QE) bit. + uint32_t sr = 0, cr = 0; + ret = mp_spiflash_read_cmd(self, CMD_RDSR, 1, &sr); + + if (ret == 0 && self->device->single_status_byte) { + // QE bit is in status byte 1 + if ((sr & self->device->quad_enable_bit_mask) == 0) { + mp_spiflash_write_cmd(self, CMD_WREN); + sr |= self->device->quad_enable_bit_mask; + mp_spiflash_write_cmd_data(self, CMD_WRSR, 1, sr); + } + // Verify it's written correctly + ret = mp_spiflash_read_cmd(self, CMD_RDSR, 1, &sr); + if (ret == 0 && (sr & self->device->quad_enable_bit_mask) == 0) { + // QE bit could not be set + ret = -1; + } + } + if (ret == 0 && (!self->device->single_status_byte)) { + // QE bit is in command register / status byte 2 + ret = mp_spiflash_read_cmd(self, CMD_RDCR, 1, &cr); + if ((cr & self->device->quad_enable_bit_mask) == 0) { + mp_spiflash_write_cmd(self, CMD_WREN); + cr |= self->device->quad_enable_bit_mask; + if (self->device->write_status_register_split) { + // Some devices have a separate command to write CR + mp_spiflash_write_cmd_data(self, CMD_WRCR, 1, cr); + } else { + // Other devices expect both SR and CR to be written in one operation + uint32_t data = (sr & 0xff) | (cr & 0xff) << 8; + mp_spiflash_write_cmd_data(self, CMD_WRSR, 2, data); + } + } + // Verify it's written correctly + ret = mp_spiflash_read_cmd(self, CMD_RDCR, 1, &cr); + if (ret == 0 && (cr & self->device->quad_enable_bit_mask) == 0) { + // QE bit could not be set + ret = -1; + } } } + if (self->config->bus_kind == MP_SPIFLASH_BUS_QSPI) { + self->config->bus.u_qspi.proto->ioctl(self->config->bus.u_qspi.data, MP_QSPI_IOCTL_FLASH_SIZE, self->device->total_size); + } + mp_spiflash_release_bus(self); + return ret; } void mp_spiflash_deepsleep(mp_spiflash_t *self, int value) { @@ -235,7 +361,7 @@ static int mp_spiflash_erase_block_internal(mp_spiflash_t *self, uint32_t addr) // erase the sector uint8_t cmd = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? CMD_SEC_ERASE_32 : CMD_SEC_ERASE; - ret = mp_spiflash_transfer_cmd_addr_data(self, cmd, addr, 0, NULL, NULL); + ret = mp_spiflash_transfer_cmd_addr_data(self, cmd, addr, 0, NULL, NULL, 0); if (ret != 0) { return ret; } @@ -260,7 +386,7 @@ static int mp_spiflash_write_page(mp_spiflash_t *self, uint32_t addr, size_t len // write the page uint8_t cmd = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? CMD_WRITE_32 : CMD_WRITE; - ret = mp_spiflash_transfer_cmd_addr_data(self, cmd, addr, len, src, NULL); + ret = mp_spiflash_transfer_cmd_addr_data(self, cmd, addr, len, src, NULL, 0); if (ret != 0) { return ret; } @@ -404,7 +530,7 @@ static int mp_spiflash_cached_write_part(mp_spiflash_t *self, uint32_t addr, siz // Restriction for now, so we don't need to erase multiple pages if (offset + len > SECTOR_SIZE) { - printf("mp_spiflash_cached_write_part: len is too large\n"); + diag_printf("mp_spiflash_cached_write_part: len is too large\n"); return -MP_EIO; } diff --git a/drivers/memory/spiflash.h b/drivers/memory/spiflash.h index 5ccf7d44c9..a37540e4b5 100644 --- a/drivers/memory/spiflash.h +++ b/drivers/memory/spiflash.h @@ -37,6 +37,7 @@ enum { }; struct _mp_spiflash_t; +struct _external_flash_device; #if MICROPY_HW_SPIFLASH_ENABLE_CACHE // A cache must be provided by the user in the config struct. The same cache @@ -69,9 +70,10 @@ typedef struct _mp_spiflash_config_t { typedef struct _mp_spiflash_t { const mp_spiflash_config_t *config; volatile uint32_t flags; + const struct _external_flash_device *device; } mp_spiflash_t; -void mp_spiflash_init(mp_spiflash_t *self); +int mp_spiflash_init(mp_spiflash_t *self); void mp_spiflash_deepsleep(mp_spiflash_t *self, int value); // These functions go direct to the SPI flash device diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 50ac48e5a1..1c7720e1eb 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -278,6 +278,7 @@ SRC_C += \ adc.c \ sdio.c \ subghz.c \ + mpu.c \ $(wildcard $(BOARD_DIR)/*.c) SRC_O += \ diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h index f0ef67e77e..b059927b67 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h @@ -65,10 +65,10 @@ void board_sleep(int value); #define MICROPY_HW_RTC_USE_CALOUT (1) // SPI flash #1, for R/W storage +#define MICROPY_HW_SPIFLASH_DEVICES AT25SF161B #define MICROPY_HW_SOFTQSPI_SCK_LOW(self) (GPIOE->BSRR = (0x10000 << 11)) #define MICROPY_HW_SOFTQSPI_SCK_HIGH(self) (GPIOE->BSRR = (1 << 11)) #define MICROPY_HW_SOFTQSPI_NIBBLE_READ(self) ((GPIOE->IDR >> 7) & 0xf) -#define MICROPY_HW_SPIFLASH_SIZE_BITS (16 * 1024 * 1024) #define MICROPY_HW_SPIFLASH_CS (pyb_pin_QSPI1_CS) #define MICROPY_HW_SPIFLASH_SCK (pyb_pin_QSPI1_CLK) #define MICROPY_HW_SPIFLASH_IO0 (pyb_pin_QSPI1_D0) @@ -84,7 +84,6 @@ extern struct _spi_bdev_t spi_bdev; #endif #define MICROPY_HW_BDEV_SPIFLASH (&spi_bdev) #define MICROPY_HW_BDEV_SPIFLASH_CONFIG (&spiflash_config) -#define MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES (MICROPY_HW_SPIFLASH_SIZE_BITS / 8) #define MICROPY_HW_BDEV_SPIFLASH_EXTENDED (&spi_bdev) // for extended block protocol // SPI flash #2, to be memory mapped diff --git a/ports/stm32/boards/STM32F769DISC/board_init.c b/ports/stm32/boards/STM32F769DISC/board_init.c index 578dc9adb6..2b639f3123 100644 --- a/ports/stm32/boards/STM32F769DISC/board_init.c +++ b/ports/stm32/boards/STM32F769DISC/board_init.c @@ -1,5 +1,6 @@ #include "storage.h" #include "qspi.h" +#include "mpconfigboard.h" // This configuration is needed for mboot to be able to write to the external QSPI flash @@ -21,6 +22,6 @@ spi_bdev_t spi_bdev; // This init function is needed to memory map the QSPI flash early in the boot process void board_early_init(void) { - qspi_init(); + qspi_init(MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES); qspi_memory_map(); } diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 90a62c0244..d298d958b7 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -185,10 +185,14 @@ MP_NOINLINE static bool init_flash_fs(uint reset_mode) { if (len != -1) { // Detected a littlefs filesystem so create correct block device for it - mp_obj_t args[] = { MP_OBJ_NEW_QSTR(MP_QSTR_len), MP_OBJ_NEW_SMALL_INT(len) }; - bdev = MP_OBJ_TYPE_GET_SLOT(&pyb_flash_type, make_new)(&pyb_flash_type, 0, 1, args); + mp_obj_t lfs_bdev = pyb_flash_new_obj(0, len); + if (lfs_bdev == mp_const_none) { + // Uncaught exception; len must be an invalid length. + mp_printf(&mp_plat_print, "MPY: corrupted filesystem\n"); + } else { + bdev = lfs_bdev; + } } - #endif // Try to mount the flash on "/flash" and chdir to it for the boot-up directory. diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 67a6ee990e..15513fba9c 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -548,11 +548,9 @@ // - MICROPY_HW_BDEV_SPIFLASH - pointer to a spi_bdev_t // - MICROPY_HW_BDEV_SPIFLASH_CONFIG - pointer to an mp_spiflash_config_t // - MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES - size in bytes of the SPI flash -#define MICROPY_HW_BDEV_IOCTL(op, arg) ( \ - (op) == BDEV_IOCTL_NUM_BLOCKS ? (MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES / FLASH_BLOCK_SIZE) : \ - (op) == BDEV_IOCTL_INIT ? spi_bdev_ioctl(MICROPY_HW_BDEV_SPIFLASH, (op), (uint32_t)MICROPY_HW_BDEV_SPIFLASH_CONFIG) : \ - spi_bdev_ioctl(MICROPY_HW_BDEV_SPIFLASH, (op), (arg)) \ - ) +// The board can specify the SPI flash chip(s) being used as comma separated list in: +// - MICROPY_HW_SPIFLASH_DEVICES +#define MICROPY_HW_BDEV_IOCTL(op, arg) (spi_bdev_ioctl(MICROPY_HW_BDEV_SPIFLASH, (op), (arg))) #define MICROPY_HW_BDEV_READBLOCKS(dest, bl, n) spi_bdev_readblocks(MICROPY_HW_BDEV_SPIFLASH, (dest), (bl), (n)) #define MICROPY_HW_BDEV_WRITEBLOCKS(src, bl, n) spi_bdev_writeblocks(MICROPY_HW_BDEV_SPIFLASH, (src), (bl), (n)) #endif diff --git a/ports/stm32/mpu.c b/ports/stm32/mpu.c new file mode 100644 index 0000000000..088d75fea9 --- /dev/null +++ b/ports/stm32/mpu.c @@ -0,0 +1,73 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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. + */ + +#include +#include "py/mphal.h" +#include "mpu.h" + +#if (defined(STM32F4) && defined(MICROPY_HW_ETH_MDC)) || defined(STM32F7) || defined(STM32H7) || defined(STM32WB) + +void mpu_config_region(uint32_t region, uint32_t base_addr, uint32_t attr_size) { + MPU->RNR = region; + MPU->RBAR = base_addr; + MPU->RASR = attr_size; +} + +#elif defined(STM32H5) + +void mpu_config_region(uint32_t region, uint32_t base_addr, uint32_t size) { + if (region == MPU_REGION_ETH) { + // Configure region 1 to make DMA memory non-cacheable. + + __DMB(); + // Configure attribute 1, inner-outer non-cacheable (=0x44). + MPU->MAIR0 = (MPU->MAIR0 & ~MPU_MAIR0_Attr1_Msk) + | 0x44 << MPU_MAIR0_Attr1_Pos; + __DMB(); + + // RBAR + // BASE Bits [31:5] of base address + // SH[4:3] 00 = Non-shareable + // AP[2:1] 01 = Read/write by any privilege level + // XN[0]: 1 = No execution + + // RLAR + // LIMIT Limit address. Contains bits[31:5] of the upper inclusive limit of the selected MPU memory region + // AT[3:1] 001 = Attribute 1 + // EN[0] 1 = Enabled + MPU->RNR = region; + MPU->RBAR = (base_addr & MPU_RBAR_BASE_Msk) + | MPU_ACCESS_NOT_SHAREABLE << MPU_RBAR_SH_Pos + | MPU_REGION_ALL_RW << MPU_RBAR_AP_Pos + | MPU_INSTRUCTION_ACCESS_DISABLE << MPU_RBAR_XN_Pos; + MPU->RLAR = ((base_addr + size - 1) & MPU_RLAR_LIMIT_Msk) + | MPU_ATTRIBUTES_NUMBER1 << MPU_RLAR_AttrIndx_Pos + | MPU_REGION_ENABLE << MPU_RLAR_EN_Pos; + } + __DMB(); +} + +#endif diff --git a/ports/stm32/mpu.h b/ports/stm32/mpu.h index 5ef1466184..9153720a28 100644 --- a/ports/stm32/mpu.h +++ b/ports/stm32/mpu.h @@ -118,11 +118,7 @@ static inline uint32_t mpu_config_start(void) { return disable_irq(); } -static inline void mpu_config_region(uint32_t region, uint32_t base_addr, uint32_t attr_size) { - MPU->RNR = region; - MPU->RBAR = base_addr; - MPU->RASR = attr_size; -} +MP_NOINLINE void mpu_config_region(uint32_t region, uint32_t base_addr, uint32_t attr_size); static inline void mpu_config_end(uint32_t irq_state) { __ISB(); @@ -172,44 +168,15 @@ static inline uint32_t mpu_config_start(void) { return disable_irq(); } -static inline void mpu_config_region(uint32_t region, uint32_t base_addr, uint32_t size) { - if (region == MPU_REGION_ETH) { - // Configure region 1 to make DMA memory non-cacheable. +MP_NOINLINE void mpu_config_region(uint32_t region, uint32_t base_addr, uint32_t attr_size); +if (region == MPU_REGION_ETH) { + static inline void mpu_config_end(uint32_t irq_state) { + __ISB(); + __DSB(); __DMB(); - // Configure attribute 1, inner-outer non-cacheable (=0x44). - MPU->MAIR0 = (MPU->MAIR0 & ~MPU_MAIR0_Attr1_Msk) - | 0x44 << MPU_MAIR0_Attr1_Pos; - __DMB(); - - // RBAR - // BASE Bits [31:5] of base address - // SH[4:3] 00 = Non-shareable - // AP[2:1] 01 = Read/write by any privilege level - // XN[0]: 1 = No execution - - // RLAR - // LIMIT Limit address. Contains bits[31:5] of the upper inclusive limit of the selected MPU memory region - // AT[3:1] 001 = Attribute 1 - // EN[0] 1 = Enabled - MPU->RNR = region; - MPU->RBAR = (base_addr & MPU_RBAR_BASE_Msk) - | MPU_ACCESS_NOT_SHAREABLE << MPU_RBAR_SH_Pos - | MPU_REGION_ALL_RW << MPU_RBAR_AP_Pos - | MPU_INSTRUCTION_ACCESS_DISABLE << MPU_RBAR_XN_Pos; - MPU->RLAR = ((base_addr + size - 1) & MPU_RLAR_LIMIT_Msk) - | MPU_ATTRIBUTES_NUMBER1 << MPU_RLAR_AttrIndx_Pos - | MPU_REGION_ENABLE << MPU_RLAR_EN_Pos; + enable_irq(irq_state); } - __DMB(); -} - -static inline void mpu_config_end(uint32_t irq_state) { - __ISB(); - __DSB(); - __DMB(); - enable_irq(irq_state); -} #else diff --git a/ports/stm32/octospi.c b/ports/stm32/octospi.c index 1d59e2b560..feabe905d7 100644 --- a/ports/stm32/octospi.c +++ b/ports/stm32/octospi.c @@ -92,7 +92,7 @@ void octospi_init(void) { OCTOSPI1->CR |= OCTOSPI_CR_EN; } -static int octospi_ioctl(void *self_in, uint32_t cmd) { +static int octospi_ioctl(void *self_in, uint32_t cmd, uint32_t arg) { (void)self_in; switch (cmd) { case MP_QSPI_IOCTL_INIT: @@ -269,37 +269,59 @@ static int octospi_read_cmd(void *self_in, uint8_t cmd, size_t len, uint32_t *de return 0; } -static int octospi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest) { +static int octospi_read_cmd_addr_data(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest, uint8_t mode) { + // Note this only support use with 1 or 4 line commands. + // Full 8-line mode support is not included. + // Some commands will auto-downgrade to support 2-line mode if needed by hardware. (void)self_in; - #if defined(MICROPY_HW_OSPIFLASH_IO1) && !defined(MICROPY_HW_OSPIFLASH_IO2) && !defined(MICROPY_HW_OSPIFLASH_IO4) + uint32_t adsize = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? 3 : 2; + + uint32_t dmode = 0; + uint32_t admode = 0; + uint32_t dcyc = 0; + uint32_t abmode = 0; + + if (mode == MP_QSPI_TRANSFER_CMD_QADDR_QDATA) { + dmode = 3; // 4 data lines used + admode = 3; // 4 address lines used + dcyc = 4; // 4 dummy cycles (2 bytes) + abmode = 3; // alternate-byte bytes sent on 4 lines + } else if (mode == MP_QSPI_TRANSFER_CMD_ADDR_DATA) { + dmode = 1; // 1 data lines used + admode = 1; // 1 address lines used + dcyc = 8; // 8 dummy cycles (1 byte) + abmode = 0; // No alternate-byte bytes sent + } else { + return -1; + } + + #if !defined(MICROPY_HW_OSPIFLASH_IO2) && !defined(MICROPY_HW_OSPIFLASH_IO4) // Use 2-line address, 2-line data. - uint32_t adsize = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? 3 : 2; - uint32_t dmode = 2; // data on 2-lines - uint32_t admode = 2; // address on 2-lines - uint32_t dcyc = 4; // 4 dummy cycles + dmode = 2; // data on 2-lines + admode = 2; // address on 2-lines + dcyc = 4; // 4 dummy cycles if (cmd == 0xeb || cmd == 0xec) { // Convert to 2-line command. cmd = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? 0xbc : 0xbb; } + #endif - #else + #if !defined(MICROPY_HW_OSPIFLASH_IO1) // Fallback to use 1-line address, 1-line data. - uint32_t adsize = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? 3 : 2; - uint32_t dmode = 1; // data on 1-line - uint32_t admode = 1; // address on 1-line - uint32_t dcyc = 0; // 0 dummy cycles + dmode = 1; // data on 1-line + admode = 1; // address on 1-line + dcyc = 0; // 0 dummy cycles if (cmd == 0xeb || cmd == 0xec) { // Convert to 1-line command. cmd = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? 0x13 : 0x03; } - #endif OCTOSPI1->FCR = OCTOSPI_FCR_CTCF; // clear TC flag @@ -311,7 +333,7 @@ static int octospi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t add | 0 << OCTOSPI_CCR_SIOO_Pos // send instruction every transaction | dmode << OCTOSPI_CCR_DMODE_Pos // data on n lines | 0 << OCTOSPI_CCR_ABSIZE_Pos // 8-bit alternate byte - | 0 << OCTOSPI_CCR_ABMODE_Pos // no alternate byte + | abmode << OCTOSPI_CCR_ABMODE_Pos // alternate byte | adsize << OCTOSPI_CCR_ADSIZE_Pos // 32 or 24-bit address size | admode << OCTOSPI_CCR_ADMODE_Pos // address on n lines | 1 << OCTOSPI_CCR_IMODE_Pos // instruction on 1 line @@ -357,7 +379,7 @@ const mp_qspi_proto_t octospi_proto = { .write_cmd_data = octospi_write_cmd_data, .write_cmd_addr_data = octospi_write_cmd_addr_data, .read_cmd = octospi_read_cmd, - .read_cmd_qaddr_qdata = octospi_read_cmd_qaddr_qdata, + .read_cmd_addr_data = octospi_read_cmd_addr_data, }; #endif // defined(MICROPY_HW_OSPIFLASH_SIZE_BITS_LOG2) diff --git a/ports/stm32/qspi.c b/ports/stm32/qspi.c index 98364db41d..54342eab09 100644 --- a/ports/stm32/qspi.c +++ b/ports/stm32/qspi.c @@ -32,7 +32,7 @@ #include "qspi.h" #include "pin_static_af.h" -#if defined(MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2) +#if MICROPY_HW_ENABLE_QSPI || defined(MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2) #define QSPI_MAP_ADDR (0x90000000) @@ -52,17 +52,18 @@ #define MICROPY_HW_QSPI_CS_HIGH_CYCLES 2 // nCS stays high for 2 cycles #endif -#ifndef MICROPY_HW_QSPI_MPU_REGION_SIZE -#define MICROPY_HW_QSPI_MPU_REGION_SIZE ((1 << (MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 - 3)) >> 20) +#ifndef MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 #endif -#if (MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 - 3 - 1) >= 24 -#define QSPI_CMD 0xec -#define QSPI_ADSIZE 3 -#else -#define QSPI_CMD 0xeb -#define QSPI_ADSIZE 2 -#endif +// Fast Read command in 32bit and 24bit addressing. +#define QSPI_FAST_READ_A4_CMD 0xec +#define QSPI_FAST_READ_A3_CMD 0xeb + +// this formula computes the log2 of "m" +#define BITS_TO_LOG2(m) ((m) - 1) / (((m) - 1) % 255 + 1) / 255 % 255 * 8 + 7 - 86 / (((m) - 1) % 255 + 12) + +#define MBytes (1024 * 1024) +static size_t qspi_memory_size_bytes = 0; static inline void qspi_mpu_disable_all(void) { // Configure MPU to disable access to entire QSPI region, to prevent CPU @@ -72,6 +73,8 @@ static inline void qspi_mpu_disable_all(void) { mpu_config_end(irq_state); } +#if 1 + static inline void qspi_mpu_enable_mapped(void) { // Configure MPU to allow access to only the valid part of external SPI flash. // The memory accesses to the mapped QSPI are faster if the MPU is not used @@ -83,36 +86,106 @@ static inline void qspi_mpu_enable_mapped(void) { // other enabled region overlaps the disabled subregion, and the access is // unprivileged or the background region is disabled, the MPU issues a fault. uint32_t irq_state = mpu_config_start(); - #if MICROPY_HW_QSPI_MPU_REGION_SIZE > 128 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0xFF, MPU_REGION_SIZE_256MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 64 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_256MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 32 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_256MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 16 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 8 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_32MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 4 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_32MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 2 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_32MB)); - #elif MICROPY_HW_QSPI_MPU_REGION_SIZE > 1 - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_32MB)); - mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_16MB)); - #else - mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); - mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_32MB)); - mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_4MB)); - #endif + + if (qspi_memory_size_bytes > (128 * MBytes)) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0xFF, MPU_REGION_SIZE_256MB)); + } else if (qspi_memory_size_bytes > (64 * MBytes)) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_256MB)); + } else if (qspi_memory_size_bytes > (32 * MBytes)) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_256MB)); + } else if (qspi_memory_size_bytes > (16 * MBytes)) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + } else if (qspi_memory_size_bytes > (8 * MBytes)) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_32MB)); + } else if (qspi_memory_size_bytes > (4 * MBytes)) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_32MB)); + } else if (qspi_memory_size_bytes > (2 * MBytes)) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_32MB)); + } else if (qspi_memory_size_bytes > (1 * MBytes)) { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x0F, MPU_REGION_SIZE_32MB)); + mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_16MB)); + } else { + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_256MB)); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x01, MPU_REGION_SIZE_32MB)); + mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, MPU_CONFIG_NOACCESS(0x03, MPU_REGION_SIZE_4MB)); + } mpu_config_end(irq_state); } -void qspi_init(void) { +#else + +// This variant of the function is harder to read, but 76 bytes smaller. + +static inline void qspi_mpu_enable_mapped(void) { + // Configure MPU to allow access to only the valid part of external SPI flash. + // The memory accesses to the mapped QSPI are faster if the MPU is not used + // for the memory-mapped region, so 3 MPU regions are used to disable access + // to everything except the valid address space, using holes in the bottom + // of the regions and nesting them. + // Note: Disabling a subregion (by setting its corresponding SRD bit to 1) + // means another region overlapping the disabled range matches instead. If no + // other enabled region overlaps the disabled subregion, and the access is + // unprivileged or the background region is disabled, the MPU issues a fault. + uint32_t irq_state = mpu_config_start(); + + static const uint8_t region_definitions[][7] = { + // Each row per MB region total size, specifying region srd and size for MPU_REGION_QSPI1, 2 and 3. + {128, 0xFF, MPU_REGION_SIZE_256MB, 0, 0, 0, 0}, + { 64, 0x0F, MPU_REGION_SIZE_256MB, 0, 0, 0, 0}, + { 32, 0x03, MPU_REGION_SIZE_256MB, 0, 0, 0, 0}, + { 16, 0x01, MPU_REGION_SIZE_256MB, 0, 0, 0, 0}, + { 8, 0x01, MPU_REGION_SIZE_256MB, + 0x0F, MPU_REGION_SIZE_32MB, 0, 0}, + { 4, 0x01, MPU_REGION_SIZE_256MB, + 0x03, MPU_REGION_SIZE_32MB, 0, 0}, + { 2, 0x01, MPU_REGION_SIZE_256MB, + 0x01, MPU_REGION_SIZE_32MB, 0, 0}, + { 1, 0x01, MPU_REGION_SIZE_256MB, + 0x0F, MPU_REGION_SIZE_32MB, + 0x01, MPU_REGION_SIZE_16MB}, + { 0, 0x01, MPU_REGION_SIZE_256MB, + 0x01, MPU_REGION_SIZE_32MB, + 0x03, MPU_REGION_SIZE_4MB}, + }; + size_t qspi_memory_size_mbytes = qspi_memory_size_bytes / 1024 / 1024; + + for (uint8_t i = 0; i < 9; ++i) { + if (qspi_memory_size_mbytes > region_definitions[i][0]) { + uint32_t attr_size_1 = MPU_CONFIG_NOACCESS(region_definitions[i][1], region_definitions[i][2]); + mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, attr_size_1); + if (region_definitions[i][3] > 0) { + uint32_t attr_size_2 = MPU_CONFIG_NOACCESS(region_definitions[i][3], region_definitions[i][4]); + mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, attr_size_2); + } + if (region_definitions[i][5] > 0) { + uint32_t attr_size_3 = MPU_CONFIG_NOACCESS(region_definitions[i][5], region_definitions[i][6]); + mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, attr_size_3); + } + break; + } + } + mpu_config_end(irq_state); +} + +#endif + +void qspi_set_memory_size(size_t memory_size_bytes) { + qspi_memory_size_bytes = memory_size_bytes; + size_t QSPIFLASH_SIZE_BITS_LOG2 = BITS_TO_LOG2(qspi_memory_size_bytes * 8); + QUADSPI->DCR = + (QSPIFLASH_SIZE_BITS_LOG2 - 3 - 1) << QUADSPI_DCR_FSIZE_Pos + | (MICROPY_HW_QSPI_CS_HIGH_CYCLES - 1) << QUADSPI_DCR_CSHT_Pos + | 0 << QUADSPI_DCR_CKMODE_Pos // CLK idles at low state + ; +} + +void qspi_init(size_t memory_size_bytes) { + qspi_memory_size_bytes = memory_size_bytes; + qspi_mpu_disable_all(); // Configure pins @@ -143,15 +216,20 @@ void qspi_init(void) { | 1 << QUADSPI_CR_EN_Pos // enable the peripheral ; - QUADSPI->DCR = - (MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 - 3 - 1) << QUADSPI_DCR_FSIZE_Pos - | (MICROPY_HW_QSPI_CS_HIGH_CYCLES - 1) << QUADSPI_DCR_CSHT_Pos - | 0 << QUADSPI_DCR_CKMODE_Pos // CLK idles at low state - ; + if (qspi_memory_size_bytes) { + qspi_set_memory_size(qspi_memory_size_bytes); + } } -void qspi_memory_map(void) { +void qspi_memory_map() { // Enable memory-mapped mode + uint8_t cmd = QSPI_FAST_READ_A3_CMD; + uint8_t adsize = 2; + if (qspi_memory_size_bytes > (16 * MBytes)) { + // Flash chips over 16MB require 32bit addressing. + cmd = QSPI_FAST_READ_A4_CMD; + adsize = 3; + } QUADSPI->ABR = 0; // disable continuous read mode @@ -163,20 +241,20 @@ void qspi_memory_map(void) { | 4 << QUADSPI_CCR_DCYC_Pos // 4 dummy cycles | 0 << QUADSPI_CCR_ABSIZE_Pos // 8-bit alternate byte | 3 << QUADSPI_CCR_ABMODE_Pos // alternate byte on 4 lines - | QSPI_ADSIZE << QUADSPI_CCR_ADSIZE_Pos + | adsize << QUADSPI_CCR_ADSIZE_Pos | 3 << QUADSPI_CCR_ADMODE_Pos // address on 4 lines | 1 << QUADSPI_CCR_IMODE_Pos // instruction on 1 line - | QSPI_CMD << QUADSPI_CCR_INSTRUCTION_Pos + | cmd << QUADSPI_CCR_INSTRUCTION_Pos ; qspi_mpu_enable_mapped(); } -static int qspi_ioctl(void *self_in, uint32_t cmd) { +static int qspi_ioctl(void *self_in, uint32_t cmd, uint32_t arg) { (void)self_in; switch (cmd) { case MP_QSPI_IOCTL_INIT: - qspi_init(); + qspi_init(0); break; case MP_QSPI_IOCTL_BUS_ACQUIRE: // Disable memory-mapped region during bus access @@ -192,6 +270,11 @@ static int qspi_ioctl(void *self_in, uint32_t cmd) { // Switch to memory-map mode when bus is idle qspi_memory_map(); break; + case MP_QSPI_IOCTL_FLASH_SIZE: + if (arg > 0) { + qspi_set_memory_size(arg); + } + return qspi_memory_size_bytes; } return 0; // success } @@ -350,11 +433,29 @@ static int qspi_read_cmd(void *self_in, uint8_t cmd, size_t len, uint32_t *dest) return 0; } -static int qspi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest) { +static int qspi_read_cmd_addr_data(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest, uint8_t mode) { (void)self_in; - uint8_t adsize = MICROPY_HW_SPI_ADDR_IS_32BIT(addr) ? 3 : 2; + uint32_t dmode = 0; + uint32_t admode = 0; + uint32_t dcyc = 0; + uint32_t abmode = 0; + + if (mode == MP_QSPI_TRANSFER_CMD_QADDR_QDATA) { + dmode = 3; // 4 data lines used + admode = 3; // 4 address lines used + dcyc = 4; // 4 dummy cycles (2 bytes) + abmode = 3; // alternate-byte bytes sent on 4 lines + } else if (mode == MP_QSPI_TRANSFER_CMD_ADDR_DATA) { + dmode = 1; // 1 data lines used + admode = 1; // 1 address lines used + dcyc = 8; // 8 dummy cycles (1 byte) + abmode = 0; // No alternate-byte bytes sent + } else { + return -1; + } + QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag QUADSPI->DLR = len - 1; // number of bytes to read @@ -363,12 +464,12 @@ static int qspi_read_cmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, 0 << QUADSPI_CCR_DDRM_Pos // DDR mode disabled | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction | 1 << QUADSPI_CCR_FMODE_Pos // indirect read mode - | 3 << QUADSPI_CCR_DMODE_Pos // data on 4 lines - | 4 << QUADSPI_CCR_DCYC_Pos // 4 dummy cycles + | dmode << QUADSPI_CCR_DMODE_Pos // data lines + | dcyc << QUADSPI_CCR_DCYC_Pos // dummy cycles | 0 << QUADSPI_CCR_ABSIZE_Pos // 8-bit alternate byte - | 3 << QUADSPI_CCR_ABMODE_Pos // alternate byte on 4 lines + | abmode << QUADSPI_CCR_ABMODE_Pos // alternate byte count / lines | adsize << QUADSPI_CCR_ADSIZE_Pos // 32 or 24-bit address size - | 3 << QUADSPI_CCR_ADMODE_Pos // address on 4 lines + | admode << QUADSPI_CCR_ADMODE_Pos // address lines | 1 << QUADSPI_CCR_IMODE_Pos // instruction on 1 line | cmd << QUADSPI_CCR_INSTRUCTION_Pos // quad read opcode ; @@ -419,7 +520,7 @@ const mp_qspi_proto_t qspi_proto = { .write_cmd_data = qspi_write_cmd_data, .write_cmd_addr_data = qspi_write_cmd_addr_data, .read_cmd = qspi_read_cmd, - .read_cmd_qaddr_qdata = qspi_read_cmd_qaddr_qdata, + .read_cmd_addr_data = qspi_read_cmd_addr_data, }; #endif // defined(MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2) diff --git a/ports/stm32/qspi.h b/ports/stm32/qspi.h index c774b12582..e1b9c8402d 100644 --- a/ports/stm32/qspi.h +++ b/ports/stm32/qspi.h @@ -30,7 +30,7 @@ extern const mp_qspi_proto_t qspi_proto; -void qspi_init(void); +void qspi_init(size_t memory_size_bytes); void qspi_memory_map(void); #endif // MICROPY_INCLUDED_STM32_QSPI_H diff --git a/ports/stm32/spibdev.c b/ports/stm32/spibdev.c index fecd4a9915..5366b41cb7 100644 --- a/ports/stm32/spibdev.c +++ b/ports/stm32/spibdev.c @@ -29,16 +29,33 @@ #include "irq.h" #include "led.h" #include "storage.h" +#include "drivers/memory/external_flash_device.h" #if MICROPY_HW_ENABLE_STORAGE int32_t spi_bdev_ioctl(spi_bdev_t *bdev, uint32_t op, uint32_t arg) { switch (op) { case BDEV_IOCTL_INIT: + #ifdef MICROPY_HW_BDEV_SPIFLASH_CONFIG + if (!arg) { + arg = (uint32_t)(MICROPY_HW_BDEV_SPIFLASH_CONFIG); + } + #endif bdev->spiflash.config = (const mp_spiflash_config_t *)arg; - mp_spiflash_init(&bdev->spiflash); + int ret = mp_spiflash_init(&bdev->spiflash); bdev->flash_tick_counter_last_write = 0; - return 0; + return ret; + + case BDEV_IOCTL_NUM_BLOCKS: + #if MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES + return MICROPY_HW_BDEV_SPIFLASH_SIZE_BYTES / FLASH_BLOCK_SIZE; + #else + if (bdev->spiflash.device != NULL) { + external_flash_device *flash = (external_flash_device *)bdev->spiflash.device; + return flash->total_size / FLASH_BLOCK_SIZE; + } + return -1; + #endif case BDEV_IOCTL_IRQ_HANDLER: { int ret = 0; diff --git a/ports/stm32/storage.c b/ports/stm32/storage.c index a6594fd4d9..2e6d185b77 100644 --- a/ports/stm32/storage.c +++ b/ports/stm32/storage.c @@ -48,11 +48,11 @@ static void storage_systick_callback(uint32_t ticks_ms); void storage_init(void) { if (!storage_is_initialised) { - storage_is_initialised = true; - systick_enable_dispatch(SYSTICK_DISPATCH_STORAGE, storage_systick_callback); - MICROPY_HW_BDEV_IOCTL(BDEV_IOCTL_INIT, 0); + if (MICROPY_HW_BDEV_IOCTL(BDEV_IOCTL_INIT, 0) == 0) { + storage_is_initialised = true; + } #if defined(MICROPY_HW_BDEV2_IOCTL) MICROPY_HW_BDEV2_IOCTL(BDEV_IOCTL_INIT, 0); @@ -273,6 +273,30 @@ const pyb_flash_obj_t pyb_flash_obj = { 0, // actual size handled in ioctl, MP_BLOCKDEV_IOCTL_BLOCK_COUNT case }; +mp_obj_t pyb_flash_new_obj(mp_int_t start, mp_int_t len) { + + uint32_t bl_len = (storage_get_block_count() - FLASH_PART1_START_BLOCK) * FLASH_BLOCK_SIZE; + + if (start == -1) { + start = 0; + } else if (!(0 <= start && start < bl_len && start % MICROPY_HW_BDEV_BLOCKSIZE_EXT == 0)) { + return mp_const_none; + } + + if (len == -1) { + len = bl_len - start; + } else if (!(0 < len && start + len <= bl_len && len % MICROPY_HW_BDEV_BLOCKSIZE_EXT == 0)) { + return mp_const_none; + } + + pyb_flash_obj_t *self = mp_obj_malloc(pyb_flash_obj_t, &pyb_flash_type); + self->use_native_block_size = false; + self->start = start; + self->len = len; + + return MP_OBJ_FROM_PTR(self); +} + static void pyb_flash_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { pyb_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self == &pyb_flash_obj) { @@ -296,30 +320,15 @@ static mp_obj_t pyb_flash_make_new(const mp_obj_type_t *type, size_t n_args, siz // Default singleton object that accesses entire flash, including virtual partition table return MP_OBJ_FROM_PTR(&pyb_flash_obj); } - - pyb_flash_obj_t *self = mp_obj_malloc(pyb_flash_obj_t, &pyb_flash_type); - self->use_native_block_size = false; - - uint32_t bl_len = (storage_get_block_count() - FLASH_PART1_START_BLOCK) * FLASH_BLOCK_SIZE; - mp_int_t start = args[ARG_start].u_int; - if (start == -1) { - start = 0; - } else if (!(0 <= start && start < bl_len && start % MICROPY_HW_BDEV_BLOCKSIZE_EXT == 0)) { - mp_raise_ValueError(NULL); - } - mp_int_t len = args[ARG_len].u_int; - if (len == -1) { - len = bl_len - start; - } else if (!(0 < len && start + len <= bl_len && len % MICROPY_HW_BDEV_BLOCKSIZE_EXT == 0)) { + + mp_obj_t self = pyb_flash_new_obj(start, len); + if (self == mp_const_none) { + // Invalid start or end arg mp_raise_ValueError(NULL); } - - self->start = start; - self->len = len; - - return MP_OBJ_FROM_PTR(self); + return self; } static mp_obj_t pyb_flash_readblocks(size_t n_args, const mp_obj_t *args) { diff --git a/ports/stm32/storage.h b/ports/stm32/storage.h index 05654855aa..accf6c3904 100644 --- a/ports/stm32/storage.h +++ b/ports/stm32/storage.h @@ -77,4 +77,8 @@ extern const struct _pyb_flash_obj_t pyb_flash_obj; struct _fs_user_mount_t; void pyb_flash_init_vfs(struct _fs_user_mount_t *vfs); +#if !BUILDING_MBOOT +mp_obj_t pyb_flash_new_obj(mp_int_t start, mp_int_t len); +#endif + #endif // MICROPY_INCLUDED_STM32_STORAGE_H