diff --git a/drivers/memory/external_flash_device.h b/drivers/memory/external_flash_device.h new file mode 100644 index 0000000000..0379844603 --- /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 +#include + +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 b78c003b19..a9f7d518e2 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 f53481d631..2f246c60b1 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 6715a16f01..2dc403bad6 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 8260992101..062f69ae4e 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 a51b71c363..706fc3c64c 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 edab72aaed..3a15ae7f35 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 0000000000..9bb79de5c5 --- /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 +#include +#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