summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/memory/external_flash_device.h461
-rw-r--r--ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h2
-rw-r--r--ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h2
-rw-r--r--ports/samd/boards/MINISAM_M4/mpconfigboard.h2
-rw-r--r--ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h2
-rw-r--r--ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h5
-rw-r--r--ports/samd/pin_af.h3
-rw-r--r--ports/samd/samd_qspiflash.c491
8 files changed, 968 insertions, 0 deletions
diff --git a/drivers/memory/external_flash_device.h b/drivers/memory/external_flash_device.h
new file mode 100644
index 000000000..037984460
--- /dev/null
+++ b/drivers/memory/external_flash_device.h
@@ -0,0 +1,461 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries LLC
+ *
+ * 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.
+ */
+#ifndef MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_DEVICES_H
+#define MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_DEVICES_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+typedef struct {
+ uint32_t total_size;
+ uint16_t start_up_time_us;
+
+ // Three response bytes to 0x9f JEDEC ID command.
+ uint8_t manufacturer_id;
+ uint8_t memory_type;
+ uint8_t capacity;
+
+ // Max clock speed for all operations and the fastest read mode.
+ uint8_t max_clock_speed_mhz;
+
+ // Bitmask for Quad Enable bit if present. 0x00 otherwise. This is for the highest byte in the
+ // status register.
+ uint8_t quad_enable_bit_mask;
+
+ bool has_sector_protection : 1;
+
+ // Supports the 0x0b fast read command with 8 dummy cycles.
+ bool supports_fast_read : 1;
+
+ // Supports the fast read, quad output command 0x6b with 8 dummy cycles.
+ bool supports_qspi : 1;
+
+ // Supports the quad input page program command 0x32. This is known as 1-1-4 because it only
+ // uses all four lines for data.
+ bool supports_qspi_writes : 1;
+
+ // Requires a separate command 0x31 to write to the second byte of the status register.
+ // Otherwise two byte are written via 0x01.
+ bool write_status_register_split : 1;
+
+ // True when the status register is a single byte. This implies the Quad Enable bit is in the
+ // first byte and the Read Status Register 2 command (0x35) is unsupported.
+ bool single_status_byte : 1;
+} external_flash_device;
+
+// Settings for the Adesto Tech AT25DF081A 1MiB SPI flash. Its on the SAMD21
+// Xplained board.
+// Datasheet: https://www.adestotech.com/wp-content/uploads/doc8715.pdf
+#define AT25DF081A { \
+ .total_size = (1 << 20), /* 1 MiB */ \
+ .start_up_time_us = 10000, \
+ .manufacturer_id = 0x1f, \
+ .memory_type = 0x45, \
+ .capacity = 0x01, \
+ .max_clock_speed_mhz = 85, \
+ .quad_enable_bit_mask = 0x00, \
+ .has_sector_protection = true, \
+ .supports_fast_read = true, \
+ .supports_qspi = false, \
+ .supports_qspi_writes = false, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Gigadevice GD25Q16C 2MiB SPI flash.
+// Datasheet: http://www.gigadevice.com/datasheet/gd25q16c/
+#define GD25Q16C { \
+ .total_size = (1 << 21), /* 2 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xc8, \
+ .memory_type = 0x40, \
+ .capacity = 0x15, \
+ .max_clock_speed_mhz = 104, /* if we need 120 then we can turn on high performance mode */ \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Gigadevice GD25Q64C 8MiB SPI flash.
+// Datasheet: http://www.elm-tech.com/en/products/spi-flash-memory/gd25q64/gd25q64.pdf
+#define GD25Q64C { \
+ .total_size = (1 << 23), /* 8 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xc8, \
+ .memory_type = 0x40, \
+ .capacity = 0x17, \
+ .max_clock_speed_mhz = 104, /* if we need 120 then we can turn on high performance mode */ \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = true, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Cypress (was Spansion) S25FL064L 8MiB SPI flash.
+// Datasheet: http://www.cypress.com/file/316661/download
+#define S25FL064L { \
+ .total_size = (1 << 23), /* 8 MiB */ \
+ .start_up_time_us = 300, \
+ .manufacturer_id = 0x01, \
+ .memory_type = 0x60, \
+ .capacity = 0x17, \
+ .max_clock_speed_mhz = 108, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Cypress (was Spansion) S25FL116K 2MiB SPI flash.
+// Datasheet: http://www.cypress.com/file/196886/download
+#define S25FL116K { \
+ .total_size = (1 << 21), /* 2 MiB */ \
+ .start_up_time_us = 10000, \
+ .manufacturer_id = 0x01, \
+ .memory_type = 0x40, \
+ .capacity = 0x15, \
+ .max_clock_speed_mhz = 108, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = false, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Cypress (was Spansion) S25FL216K 2MiB SPI flash.
+// Datasheet: http://www.cypress.com/file/197346/download
+#define S25FL216K { \
+ .total_size = (1 << 21), /* 2 MiB */ \
+ .start_up_time_us = 10000, \
+ .manufacturer_id = 0x01, \
+ .memory_type = 0x40, \
+ .capacity = 0x15, \
+ .max_clock_speed_mhz = 65, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = false, \
+ .supports_qspi_writes = false, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Winbond W25Q16FW 2MiB SPI flash.
+// Datasheet: https://www.winbond.com/resource-files/w25q16fw%20revj%2005182017%20sfdp.pdf
+#define W25Q16FW { \
+ .total_size = (1 << 21), /* 2 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x60, \
+ .capacity = 0x15, \
+ .max_clock_speed_mhz = 133, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Winbond W25Q16JV-IQ 2MiB SPI flash. Note that JV-IM has a different .memory_type (0x70)
+// Datasheet: https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf
+#define W25Q16JV_IQ { \
+ .total_size = (1 << 21), /* 2 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x40, \
+ .capacity = 0x15, \
+ .max_clock_speed_mhz = 133, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Winbond W25Q16JV-IM 2MiB SPI flash. Note that JV-IQ has a different .memory_type (0x40)
+// Datasheet: https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf
+#define W25Q16JV_IM { \
+ .total_size = (1 << 21), /* 2 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x70, \
+ .capacity = 0x15, \
+ .max_clock_speed_mhz = 133, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+}
+
+// Settings for the Winbond W25Q32BV 4MiB SPI flash.
+// Datasheet: https://www.winbond.com/resource-files/w25q32bv_revi_100413_wo_automotive.pdf
+#define W25Q32BV { \
+ .total_size = (1 << 22), /* 4 MiB */ \
+ .start_up_time_us = 10000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x60, \
+ .capacity = 0x16, \
+ .max_clock_speed_mhz = 104, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = false, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+// Settings for the Winbond W25Q32JV-IM 4MiB SPI flash.
+// Datasheet: https://www.winbond.com/resource-files/w25q32jv%20revg%2003272018%20plus.pdf
+#define W25Q32JV_IM { \
+ .total_size = (1 << 22), /* 4 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x70, \
+ .capacity = 0x16, \
+ .max_clock_speed_mhz = 133, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+}
+
+// Settings for the Winbond W25Q32JV-IM 4MiB SPI flash.
+// Datasheet: https://www.winbond.com/resource-files/w25q32jv%20revg%2003272018%20plus.pdf
+#define W25Q32JV_IQ { \
+ .total_size = (1 << 22), /* 4 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x40, \
+ .capacity = 0x16, \
+ .max_clock_speed_mhz = 133, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+}
+
+// Settings for the Winbond W25Q64JV-IM 8MiB SPI flash. Note that JV-IQ has a different .memory_type (0x40)
+// Datasheet: http://www.winbond.com/resource-files/w25q64jv%20revj%2003272018%20plus.pdf
+#define W25Q64JV_IM { \
+ .total_size = (1 << 23), /* 8 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x70, \
+ .capacity = 0x17, \
+ .max_clock_speed_mhz = 133, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Winbond W25Q64JV-IQ 8MiB SPI flash. Note that JV-IM has a different .memory_type (0x70)
+// Datasheet: http://www.winbond.com/resource-files/w25q64jv%20revj%2003272018%20plus.pdf
+#define W25Q64JV_IQ { \
+ .total_size = (1 << 23), /* 8 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x40, \
+ .capacity = 0x17, \
+ .max_clock_speed_mhz = 133, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Winbond W25Q80DL 1MiB SPI flash.
+// Datasheet: https://www.winbond.com/resource-files/w25q80dv%20dl_revh_10022015.pdf
+#define W25Q80DL { \
+ .total_size = (1 << 20), /* 1 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x60, \
+ .capacity = 0x14, \
+ .max_clock_speed_mhz = 104, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = false, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+
+// Settings for the Winbond W25Q128JV-SQ 16MiB SPI flash. Note that JV-IM has a different .memory_type (0x70)
+// Datasheet: https://www.winbond.com/resource-files/w25q128jv%20revf%2003272018%20plus.pdf
+#define W25Q128JV_SQ { \
+ .total_size = (1 << 24), /* 16 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x40, \
+ .capacity = 0x18, \
+ .max_clock_speed_mhz = 133, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for the Macronix MX25L1606 2MiB SPI flash.
+// Datasheet:
+#define MX25L1606 { \
+ .total_size = (1 << 21), /* 2 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xc2, \
+ .memory_type = 0x20, \
+ .capacity = 0x15, \
+ .max_clock_speed_mhz = 8, \
+ .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 MX25L3233F 4MiB SPI flash.
+// Datasheet: http://www.macronix.com/Lists/Datasheet/Attachments/7426/MX25L3233F,%203V,%2032Mb,%20v1.6.pdf
+#define MX25L3233F { \
+ .total_size = (1 << 22), /* 4 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xc2, \
+ .memory_type = 0x20, \
+ .capacity = 0x16, \
+ .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.
+#define MX25R6435F { \
+ .total_size = (1 << 23), /* 8 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xc2, \
+ .memory_type = 0x28, \
+ .capacity = 0x17, \
+ .max_clock_speed_mhz = 8, \
+ .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 Winbond W25Q128JV-PM 16MiB SPI flash. Note that JV-IM has a different .memory_type (0x70)
+// Datasheet: https://www.winbond.com/resource-files/w25q128jv%20revf%2003272018%20plus.pdf
+#define W25Q128JV_PM { \
+ .total_size = (1 << 24), /* 16 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x70, \
+ .capacity = 0x18, \
+ .max_clock_speed_mhz = 133, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+}
+
+// Settings for the Winbond W25Q32FV 4MiB SPI flash.
+// Datasheet:http://www.winbond.com/resource-files/w25q32fv%20revj%2006032016.pdf?__locale=en
+#define W25Q32FV { \
+ .total_size = (1 << 22), /* 4 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0xef, \
+ .memory_type = 0x40, \
+ .capacity = 0x16, \
+ .max_clock_speed_mhz = 104, \
+ .quad_enable_bit_mask = 0x00, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = false, \
+ .supports_qspi_writes = false, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+
+// Settings for a GENERIC device with the most common setting
+#define GENERIC { \
+ .total_size = (1 << 21), /* 2 MiB */ \
+ .start_up_time_us = 5000, \
+ .manufacturer_id = 0x00, \
+ .memory_type = 0x40, \
+ .capacity = 0x15, \
+ .max_clock_speed_mhz = 48, \
+ .quad_enable_bit_mask = 0x02, \
+ .has_sector_protection = false, \
+ .supports_fast_read = true, \
+ .supports_qspi = true, \
+ .supports_qspi_writes = true, \
+ .write_status_register_split = false, \
+ .single_status_byte = false, \
+}
+#endif // MICROPY_INCLUDED_ATMEL_SAMD_EXTERNAL_FLASH_DEVICES_H
diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h
index b78c003b1..a9f7d518e 100644
--- a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h
+++ b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h
@@ -3,3 +3,5 @@
#define MICROPY_HW_XOSC32K (1)
#define MICROPY_HW_MCU_OSC32KULP (1)
+
+#define MICROPY_HW_QSPIFLASH GD25Q16C
diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h
index f53481d63..2f246c60b 100644
--- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h
+++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h
@@ -2,3 +2,5 @@
#define MICROPY_HW_MCU_NAME "SAMD51G19A"
#define MICROPY_HW_DFLL_USB_SYNC (1)
+
+#define MICROPY_HW_QSPIFLASH GD25Q16C
diff --git a/ports/samd/boards/MINISAM_M4/mpconfigboard.h b/ports/samd/boards/MINISAM_M4/mpconfigboard.h
index 6715a16f0..2dc403bad 100644
--- a/ports/samd/boards/MINISAM_M4/mpconfigboard.h
+++ b/ports/samd/boards/MINISAM_M4/mpconfigboard.h
@@ -1,2 +1,4 @@
#define MICROPY_HW_BOARD_NAME "Mini SAM M4"
#define MICROPY_HW_MCU_NAME "SAMD51G19A"
+
+#define MICROPY_HW_QSPIFLASH GD25Q16C
diff --git a/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h b/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h
index 826099210..062f69ae4 100644
--- a/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h
+++ b/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h
@@ -2,3 +2,5 @@
#define MICROPY_HW_MCU_NAME "SAMD51P19A"
#define MICROPY_HW_XOSC32K (1)
+
+#define MICROPY_HW_QSPIFLASH W25Q32JV_IQ
diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h
index a51b71c36..706fc3c64 100644
--- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h
+++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h
@@ -8,3 +8,8 @@
// 256k. Since the SAMD51x20A has 256k RAM, the loader symbol is at that address
// and so there is a fix here using the previous definition.
#define DBL_TAP_ADDR_ALT ((volatile uint32_t *)(HSRAM_ADDR + HSRAM_SIZE - 0x10000 - 4))
+
+// Enabling both two lines below will set the boot file system to
+// the board's external flash.
+#define MICROPY_HW_SPIFLASH (1)
+#define MICROPY_HW_SPIFLASH_ID (0)
diff --git a/ports/samd/pin_af.h b/ports/samd/pin_af.h
index edab72aae..3a15ae7f3 100644
--- a/ports/samd/pin_af.h
+++ b/ports/samd/pin_af.h
@@ -65,6 +65,9 @@ typedef struct _machine_pin_obj_t {
#define ALT_FCT_TC 4
#define ALT_FCT_TCC1 5
#define ALT_FCT_TCC2 6
+#define ALT_FCT_QSPI 7
+#define ALT_FCT_CAN1 7
+#define ALT_FCT_USB 7
#endif
diff --git a/ports/samd/samd_qspiflash.c b/ports/samd/samd_qspiflash.c
new file mode 100644
index 000000000..9bb79de5c
--- /dev/null
+++ b/ports/samd/samd_qspiflash.c
@@ -0,0 +1,491 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Adafruit Industries
+ * Copyright (c) 2023 Robert Hammelrath
+ *
+ * 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.
+ *
+ * Port of the Adafruit QSPIflash driver for SAMD devices
+ *
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "py/obj.h"
+#include "py/runtime.h"
+#include "py/mphal.h"
+#include "py/mperrno.h"
+#include "modmachine.h"
+#include "extmod/machine_spi.h"
+#include "extmod/vfs.h"
+#include "pin_af.h"
+#include "clock_config.h"
+#include "sam.h"
+
+#ifdef MICROPY_HW_QSPIFLASH
+
+#include "drivers/memory/external_flash_device.h"
+
+// QSPI command codes
+enum
+{
+ QSPI_CMD_READ = 0x03,
+ QSPI_CMD_READ_4B = 0x13,
+ QSPI_CMD_QUAD_READ = 0x6B,// 1 line address, 4 line data
+
+ QSPI_CMD_READ_JEDEC_ID = 0x9f,
+
+ QSPI_CMD_PAGE_PROGRAM = 0x02,
+ QSPI_CMD_PAGE_PROGRAM_4B = 0x12,
+ QSPI_CMD_QUAD_PAGE_PROGRAM = 0x32, // 1 line address, 4 line data
+
+ QSPI_CMD_READ_STATUS = 0x05,
+ QSPI_CMD_READ_STATUS2 = 0x35,
+
+ QSPI_CMD_WRITE_STATUS = 0x01,
+ QSPI_CMD_WRITE_STATUS2 = 0x31,
+
+ QSPI_CMD_ENABLE_RESET = 0x66,
+ QSPI_CMD_RESET = 0x99,
+
+ QSPI_CMD_WRITE_ENABLE = 0x06,
+ QSPI_CMD_WRITE_DISABLE = 0x04,
+
+ QSPI_CMD_ERASE_SECTOR = 0x20,
+ QSPI_CMD_ERASE_SECTOR_4B = 0x21,
+ QSPI_CMD_ERASE_BLOCK = 0xD8,
+ QSPI_CMD_ERASE_CHIP = 0xC7,
+
+ QSPI_CMD_READ_SFDP_PARAMETER = 0x5A,
+};
+
+// QSPI flash pins are: CS=PB11, SCK=PB10, IO0-IO3=PA08, PA09, PA10 and PA11.
+#define PIN_CS (43)
+#define PIN_SCK (42)
+#define PIN_IO0 (8)
+#define PIN_IO1 (9)
+#define PIN_IO2 (10)
+#define PIN_IO3 (11)
+
+#define PAGE_SIZE (256)
+#define SECTOR_SIZE (4096)
+
+typedef struct _samd_qspiflash_obj_t {
+ mp_obj_base_t base;
+ uint16_t pagesize;
+ uint16_t sectorsize;
+ uint32_t size;
+ uint8_t phase;
+ uint8_t polarity;
+} samd_qspiflash_obj_t;
+
+/// List of all possible flash devices used by Adafruit boards
+static const external_flash_device possible_devices[] = {
+ MICROPY_HW_QSPIFLASH
+};
+
+#define EXTERNAL_FLASH_DEVICE_COUNT MP_ARRAY_SIZE(possible_devices)
+static external_flash_device const *flash_device;
+static external_flash_device generic_config = GENERIC;
+extern const mp_obj_type_t samd_qspiflash_type;
+
+// The QSPIflash object is a singleton
+static samd_qspiflash_obj_t qspiflash_obj = { { &samd_qspiflash_type } };
+
+// Turn off cache and invalidate all data in it.
+static void samd_peripherals_disable_and_clear_cache(void) {
+ CMCC->CTRL.bit.CEN = 0;
+ while (CMCC->SR.bit.CSTS) {
+ }
+ CMCC->MAINT0.bit.INVALL = 1;
+}
+
+// Enable cache
+static void samd_peripherals_enable_cache(void) {
+ CMCC->CTRL.bit.CEN = 1;
+}
+
+// Run a single QSPI instruction.
+// Parameters are:
+// - command instruction code
+// - iframe iframe register value (configured by caller according to command code)
+// - addr the address to read or write from. If the instruction doesn't require an address, this parameter is meaningless.
+// - buffer pointer to the data to be written or stored depending on the type is Read or Write
+// - size the number of bytes to read or write.
+bool run_instruction(uint8_t command, uint32_t iframe, uint32_t addr, uint8_t *buffer, uint32_t size) {
+
+ samd_peripherals_disable_and_clear_cache();
+
+ uint8_t *qspi_mem = (uint8_t *)QSPI_AHB;
+ if (addr) {
+ qspi_mem += addr;
+ }
+
+ QSPI->INSTRCTRL.bit.INSTR = command;
+ QSPI->INSTRADDR.reg = addr;
+ QSPI->INSTRFRAME.reg = iframe;
+
+ // Dummy read of INSTRFRAME needed to synchronize.
+ // See Instruction Transmission Flow Diagram, figure 37.9, page 995
+ // and Example 4, page 998, section 37.6.8.5.
+ (volatile uint32_t)QSPI->INSTRFRAME.reg;
+
+ if (buffer && size) {
+ uint32_t const tfr_type = iframe & QSPI_INSTRFRAME_TFRTYPE_Msk;
+ if ((tfr_type == QSPI_INSTRFRAME_TFRTYPE_READ) || (tfr_type == QSPI_INSTRFRAME_TFRTYPE_READMEMORY)) {
+ memcpy(buffer, qspi_mem, size);
+ } else {
+ memcpy(qspi_mem, buffer, size);
+ }
+ }
+
+ __asm volatile ("dsb");
+ __asm volatile ("isb");
+
+ QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER;
+ while (!QSPI->INTFLAG.bit.INSTREND) {
+ }
+ QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND;
+
+ samd_peripherals_enable_cache();
+ return true;
+}
+
+bool run_command(uint8_t command) {
+ uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
+ QSPI_INSTRFRAME_TFRTYPE_READ | QSPI_INSTRFRAME_INSTREN;
+ return run_instruction(command, iframe, 0, NULL, 0);
+}
+
+bool read_command(uint8_t command, uint8_t *response, uint32_t len) {
+ uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
+ QSPI_INSTRFRAME_TFRTYPE_READ | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_DATAEN;
+ return run_instruction(command, iframe, 0, response, len);
+}
+
+bool read_memory_single(uint8_t command, uint32_t addr, uint8_t *response, uint32_t len) {
+ uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
+ QSPI_INSTRFRAME_TFRTYPE_READ | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN |
+ QSPI_INSTRFRAME_DATAEN | QSPI_INSTRFRAME_DUMMYLEN(8);
+ return run_instruction(command, iframe, addr, response, len);
+}
+
+bool write_command(uint8_t command, uint8_t const *data, uint32_t len) {
+ uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
+ QSPI_INSTRFRAME_TFRTYPE_WRITE | QSPI_INSTRFRAME_INSTREN | (data != NULL ? QSPI_INSTRFRAME_DATAEN : 0);
+ return run_instruction(command, iframe, 0, (uint8_t *)data, len);
+}
+
+bool erase_command(uint8_t command, uint32_t address) {
+ // Sector Erase
+ uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
+ QSPI_INSTRFRAME_TFRTYPE_WRITE | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN;
+ return run_instruction(command, iframe, address, NULL, 0);
+}
+
+bool read_memory_quad(uint8_t command, uint32_t addr, uint8_t *data, uint32_t len) {
+ uint32_t iframe = QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT | QSPI_INSTRFRAME_ADDRLEN_24BITS |
+ QSPI_INSTRFRAME_TFRTYPE_READMEMORY | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN | QSPI_INSTRFRAME_DATAEN |
+ /*QSPI_INSTRFRAME_CRMODE |*/ QSPI_INSTRFRAME_DUMMYLEN(8);
+ return run_instruction(command, iframe, addr, data, len);
+}
+
+bool write_memory_quad(uint8_t command, uint32_t addr, uint8_t *data, uint32_t len) {
+ uint32_t iframe = QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT | QSPI_INSTRFRAME_ADDRLEN_24BITS |
+ QSPI_INSTRFRAME_TFRTYPE_WRITEMEMORY | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN | QSPI_INSTRFRAME_DATAEN;
+ return run_instruction(command, iframe, addr, data, len);
+}
+
+static uint8_t read_status(void) {
+ uint8_t r;
+ read_command(QSPI_CMD_READ_STATUS, &r, 1);
+ return r;
+}
+
+static uint8_t read_status2(void) {
+ uint8_t r;
+ read_command(QSPI_CMD_READ_STATUS2, &r, 1);
+ return r;
+}
+
+static bool write_enable(void) {
+ return run_command(QSPI_CMD_WRITE_ENABLE);
+}
+
+static void wait_for_flash_ready(void) {
+ // both WIP and WREN bit should be clear
+ while (read_status() & 0x03) {
+ }
+}
+
+static uint8_t get_baud(int32_t freq_mhz) {
+ int baud = get_peripheral_freq() / (freq_mhz * 1000000) - 1;
+ if (baud < 1) {
+ baud = 1;
+ }
+ if (baud > 255) {
+ baud = 255;
+ }
+ return baud;
+}
+
+int get_sfdp_table(uint8_t *table, int maxlen) {
+ uint8_t header[16];
+ read_memory_single(QSPI_CMD_READ_SFDP_PARAMETER, 0, header, sizeof(header));
+ int len = MIN(header[11] * 4, maxlen);
+ int addr = header[12] + (header[13] << 8) + (header[14] << 16);
+ read_memory_single(QSPI_CMD_READ_SFDP_PARAMETER, addr, table, len);
+ return len;
+}
+
+STATIC mp_obj_t samd_qspiflash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
+ mp_arg_check_num(n_args, n_kw, 0, 0, false);
+
+ // The QSPI is a singleton
+ samd_qspiflash_obj_t *self = &qspiflash_obj;
+ self->phase = 0;
+ self->polarity = 0;
+ self->pagesize = PAGE_SIZE;
+ self->sectorsize = SECTOR_SIZE;
+
+ // Enable the device clock
+ MCLK->AHBMASK.reg |= MCLK_AHBMASK_QSPI;
+ MCLK->AHBMASK.reg |= MCLK_AHBMASK_QSPI_2X;
+ MCLK->APBCMASK.reg |= MCLK_APBCMASK_QSPI;
+
+ // Configure the pins.
+ mp_hal_set_pin_mux(PIN_CS, ALT_FCT_QSPI);
+ mp_hal_set_pin_mux(PIN_SCK, ALT_FCT_QSPI);
+ mp_hal_set_pin_mux(PIN_IO0, ALT_FCT_QSPI);
+ mp_hal_set_pin_mux(PIN_IO1, ALT_FCT_QSPI);
+ mp_hal_set_pin_mux(PIN_IO2, ALT_FCT_QSPI);
+ mp_hal_set_pin_mux(PIN_IO3, ALT_FCT_QSPI);
+
+ // Configure the QSPI interface
+ QSPI->CTRLA.bit.SWRST = 1;
+ mp_hal_delay_us(1000); // Maybe not required.
+
+ QSPI->CTRLB.reg = QSPI_CTRLB_MODE_MEMORY |
+ QSPI_CTRLB_CSMODE_NORELOAD |
+ QSPI_CTRLB_DATALEN_8BITS |
+ QSPI_CTRLB_CSMODE_LASTXFER;
+ // start with low 4Mhz, Mode 0
+ QSPI->BAUD.reg = QSPI_BAUD_BAUD(get_baud(4)) |
+ (self->phase << QSPI_BAUD_CPHA_Pos) |
+ (self->polarity << QSPI_BAUD_CPOL_Pos);
+ QSPI->CTRLA.bit.ENABLE = 1;
+
+ uint8_t jedec_ids[3];
+ read_command(QSPI_CMD_READ_JEDEC_ID, jedec_ids, sizeof(jedec_ids));
+
+ // Read the common sfdp table
+ // Check the device addr length, support of 1-1-4 mode and get the sector size
+ uint8_t sfdp_table[128];
+ int len = get_sfdp_table(sfdp_table, sizeof(sfdp_table));
+ if (len >= 29) {
+ self->sectorsize = 1 << sfdp_table[28];
+ bool addr4b = ((sfdp_table[2] >> 1) & 0x03) == 0x02;
+ bool supports_qspi_114 = (sfdp_table[2] & 0x40) != 0;
+ if (addr4b || !supports_qspi_114) {
+ mp_raise_ValueError(MP_ERROR_TEXT("QSPI mode not supported"));
+ }
+ }
+
+ // Check, if the flash device is known and get it's properties.
+ flash_device = NULL;
+ 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) {
+ flash_device = possible_device;
+ break;
+ }
+ }
+
+ // If the flash device is not known, try generic config options
+ if (flash_device == NULL) {
+ if (jedec_ids[0] == 0xc2) { // Macronix devices
+ generic_config.quad_enable_bit_mask = 0x04;
+ generic_config.single_status_byte = true;
+ }
+ generic_config.total_size = 1 << jedec_ids[2];
+ flash_device = &generic_config;
+ }
+
+ self->size = flash_device->total_size;
+
+ // The write in progress bit should be low.
+ while (read_status() & 0x01) {
+ }
+ // The suspended write/erase bit should be low.
+ while (read_status2() & 0x80) {
+ }
+ run_command(QSPI_CMD_ENABLE_RESET);
+ run_command(QSPI_CMD_RESET);
+ // Wait 30us for the reset
+ mp_hal_delay_us(30);
+ // Speed up the frequency
+ QSPI->BAUD.bit.BAUD = get_baud(flash_device->max_clock_speed_mhz);
+
+ // Enable Quad Mode if available
+ uint8_t status = 0;
+ if (flash_device->quad_enable_bit_mask) {
+ // Verify that QSPI mode is enabled.
+ status = flash_device->single_status_byte ? read_status() : read_status2();
+ }
+
+ // Check the quad enable bit.
+ if ((status & flash_device->quad_enable_bit_mask) == 0) {
+ write_enable();
+ uint8_t full_status[2] = {0x00, flash_device->quad_enable_bit_mask};
+
+ if (flash_device->write_status_register_split) {
+ write_command(QSPI_CMD_WRITE_STATUS2, full_status + 1, 1);
+ } else if (flash_device->single_status_byte) {
+ write_command(QSPI_CMD_WRITE_STATUS, full_status + 1, 1);
+ } else {
+ write_command(QSPI_CMD_WRITE_STATUS, full_status, 2);
+ }
+ }
+ // Turn off writes in case this is a microcontroller only reset.
+ run_command(QSPI_CMD_WRITE_DISABLE);
+ wait_for_flash_ready();
+
+ return self;
+}
+
+STATIC mp_obj_t samd_qspiflash_read(samd_qspiflash_obj_t *self, uint32_t addr, uint8_t *dest, uint32_t len) {
+ if (len > 0) {
+ wait_for_flash_ready();
+ // Command 0x6B 1 line address, 4 line Data
+ // with Continuous Read Mode and Quad output mode, read memory type
+ read_memory_quad(QSPI_CMD_QUAD_READ, addr, dest, len);
+ }
+
+ return mp_const_none;
+}
+
+STATIC mp_obj_t samd_qspiflash_write(samd_qspiflash_obj_t *self, uint32_t addr, uint8_t *src, uint32_t len) {
+ uint32_t length = len;
+ uint32_t pos = 0;
+ uint8_t *buf = src;
+
+ while (pos < length) {
+ uint16_t maxsize = self->pagesize - pos % self->pagesize;
+ uint16_t size = (length - pos) > maxsize ? maxsize : length - pos;
+
+ wait_for_flash_ready();
+ write_enable();
+ write_memory_quad(QSPI_CMD_QUAD_PAGE_PROGRAM, addr, buf + pos, size);
+
+ addr += size;
+ pos += size;
+ }
+
+ return mp_const_none;
+}
+
+STATIC mp_obj_t samd_qspiflash_erase(uint32_t addr) {
+ wait_for_flash_ready();
+ write_enable();
+ erase_command(QSPI_CMD_ERASE_SECTOR, addr);
+
+ return mp_const_none;
+}
+
+STATIC mp_obj_t samd_qspiflash_readblocks(size_t n_args, const mp_obj_t *args) {
+ samd_qspiflash_obj_t *self = MP_OBJ_TO_PTR(args[0]);
+ uint32_t offset = (mp_obj_get_int(args[1]) * self->sectorsize);
+ mp_buffer_info_t bufinfo;
+ mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE);
+ if (n_args == 4) {
+ offset += mp_obj_get_int(args[3]);
+ }
+
+ // Read data to flash (adf4 API)
+ samd_qspiflash_read(self, offset, bufinfo.buf, bufinfo.len);
+
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(samd_qspiflash_readblocks_obj, 3, 4, samd_qspiflash_readblocks);
+
+STATIC mp_obj_t samd_qspiflash_writeblocks(size_t n_args, const mp_obj_t *args) {
+ samd_qspiflash_obj_t *self = MP_OBJ_TO_PTR(args[0]);
+ uint32_t offset = (mp_obj_get_int(args[1]) * self->sectorsize);
+ mp_buffer_info_t bufinfo;
+ mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
+ if (n_args == 3) {
+ samd_qspiflash_erase(offset);
+ // TODO check return value
+ } else {
+ offset += mp_obj_get_int(args[3]);
+ }
+ // Write data to flash (adf4 API)
+ samd_qspiflash_write(self, offset, bufinfo.buf, bufinfo.len);
+ // TODO check return value
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(samd_qspiflash_writeblocks_obj, 3, 4, samd_qspiflash_writeblocks);
+
+STATIC mp_obj_t samd_qspiflash_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) {
+ samd_qspiflash_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_int_t cmd = mp_obj_get_int(cmd_in);
+
+ switch (cmd) {
+ case MP_BLOCKDEV_IOCTL_INIT:
+ return MP_OBJ_NEW_SMALL_INT(0);
+ case MP_BLOCKDEV_IOCTL_DEINIT:
+ return MP_OBJ_NEW_SMALL_INT(0);
+ case MP_BLOCKDEV_IOCTL_SYNC:
+ return MP_OBJ_NEW_SMALL_INT(0);
+ case MP_BLOCKDEV_IOCTL_BLOCK_COUNT:
+ return MP_OBJ_NEW_SMALL_INT(self->size / self->sectorsize);
+ case MP_BLOCKDEV_IOCTL_BLOCK_SIZE:
+ return MP_OBJ_NEW_SMALL_INT(self->sectorsize);
+ case MP_BLOCKDEV_IOCTL_BLOCK_ERASE: {
+ samd_qspiflash_erase(mp_obj_get_int(arg_in) * self->sectorsize);
+ // TODO check return value
+ return MP_OBJ_NEW_SMALL_INT(0);
+ }
+ default:
+ return mp_const_none;
+ }
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_3(samd_qspiflash_ioctl_obj, samd_qspiflash_ioctl);
+
+STATIC const mp_rom_map_elem_t samd_qspiflash_locals_dict_table[] = {
+ { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&samd_qspiflash_readblocks_obj) },
+ { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&samd_qspiflash_writeblocks_obj) },
+ { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&samd_qspiflash_ioctl_obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(samd_qspiflash_locals_dict, samd_qspiflash_locals_dict_table);
+
+MP_DEFINE_CONST_OBJ_TYPE(
+ samd_qspiflash_type,
+ MP_QSTR_Flash,
+ MP_TYPE_FLAG_NONE,
+ make_new, samd_qspiflash_make_new,
+ locals_dict, &samd_qspiflash_locals_dict
+ );
+
+#endif // MICROPY_HW_QSPI_FLASH