diff --git a/docs/samd/pinout.rst b/docs/samd/pinout.rst index 212275a014338..5fe1e14ecb374 100644 --- a/docs/samd/pinout.rst +++ b/docs/samd/pinout.rst @@ -160,14 +160,14 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 22 PA22 D13 6 - - 3/0 5/1 4/0 1/6 0/2 34 PB02 DOTSTAR_CLK 2 14 - - 5/0 6/0 2/2 - 35 PB03 DOTSTAR_DATA 9 15 - - 5/1 6/1 - - - 43 PB11 FLASH_CS 12 - - - 4/3 5/1 0/5 1/1 - 11 PA11 FLASH_HOLD 11 11 - 0/3 2/3 1/1 0/3 1/7 - 9 PA09 FLASH_MISO 9 9 3 0/1 2/0 0/1 0/1 1/5 - 8 PA08 FLASH_MOSI - 8 2 0/0 2/1 0/0 0/0 1/4 - 42 PB10 FLASH_SCK 10 - - - 4/2 5/0 0/4 1/0 - 10 PA10 FLASH_WP 10 10 - 0/2 2/2 1/0 0/2 1/6 55 PB23 MISO 7 - - 1/3 5/3 7/1 - - 0 PA00 MOSI 0 - - - 1/0 2/0 - - + 43 PB11 QSPI_CS 12 - - - 4/3 5/1 0/5 1/1 + 8 PA08 QSPI_D0 - 8 2 0/0 2/1 0/0 0/0 1/4 + 9 PA09 QSPI_D1 9 9 3 0/1 2/0 0/1 0/1 1/5 + 10 PA10 QSPI_D2 10 10 - 0/2 2/2 1/0 0/2 1/6 + 11 PA11 QSPI_D3 11 11 - 0/3 2/3 1/1 0/3 1/7 + 42 PB10 QSPI_SCK 10 - - - 4/2 5/0 0/4 1/0 1 PA01 SCK 1 - - - 1/1 2/1 - - 13 PA13 SCL 13 - - 2/1 4/0 2/1 0/7 1/3 12 PA12 SDA 12 - - 2/0 4/1 2/0 0/6 1/2 @@ -288,15 +288,15 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 21 PA21 D11 5 - - 5/3 3/3 7/1 1/5 0/1 22 PA22 D12 6 - - 3/0 5/1 4/0 1/6 0/2 23 PA23 D13 7 - - 3/1 5/0 4/1 1/7 0/3 - 43 PB11 FLASH_CS 12 - - - 4/3 5/1 0/5 1/1 - 11 PA11 FLASH_HOLD 11 11 - 0/3 2/3 1/1 0/3 1/7 - 9 PA09 FLASH_MISO 9 9 3 0/1 2/0 0/1 0/1 1/5 - 8 PA08 FLASH_MOSI - 8 2 0/0 2/1 0/0 0/0 1/4 - 42 PB10 FLASH_SCK 10 - - - 4/2 5/0 0/4 1/0 - 10 PA10 FLASH_WP 10 10 - 0/2 2/2 1/0 0/2 1/6 54 PB22 MISO 22 - - 1/2 5/2 7/0 - - 55 PB23 MOSI 7 - - 1/3 5/3 7/1 - - 35 PB03 NEOPIXEL 9 15 - - 5/1 6/1 - - + 43 PB11 QSPI_CS 12 - - - 4/3 5/1 0/5 1/1 + 8 PA08 QSPI_D0 - 8 2 0/0 2/1 0/0 0/0 1/4 + 9 PA09 QSPI_D1 9 9 3 0/1 2/0 0/1 0/1 1/5 + 10 PA10 QSPI_D2 10 10 - 0/2 2/2 1/0 0/2 1/6 + 11 PA11 QSPI_D3 11 11 - 0/3 2/3 1/1 0/3 1/7 + 42 PB10 QSPI_SCK 10 - - - 4/2 5/0 0/4 1/0 17 PA17 SCK 1 - - 1/1 3/0 2/1 1/1 0/5 13 PA13 SCL 13 - - 2/1 4/0 2/1 0/7 1/3 12 PA12 SDA 12 - - 2/0 4/1 2/0 0/6 1/2 @@ -650,6 +650,12 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 15 PA15 LED 15 - - 2/3 4/3 3/1 2/1 1/3 55 PB23 MISO 7 - - 1/3 5/3 7/1 - - 54 PB22 MOSI 22 - - 1/2 5/2 7/0 - - + 43 PB11 QSPI_CS 12 - - - 4/3 5/1 0/5 1/1 + 8 PA08 QSPI_D0 - 8 2 0/0 2/1 0/0 0/0 1/4 + 9 PA09 QSPI_D1 9 9 3 0/1 2/0 0/1 0/1 1/5 + 10 PA10 QSPI_D2 10 10 - 0/2 2/2 1/0 0/2 1/6 + 11 PA11 QSPI_D3 11 11 - 0/3 2/3 1/1 0/3 1/7 + 42 PB10 QSPI_SCK 10 - - - 4/2 5/0 0/4 1/0 1 PA01 SCK 1 - - - 1/1 2/1 - - 13 PA13 SCL 13 - - 2/1 4/0 2/1 0/7 1/3 12 PA12 SDA 12 - - 2/0 4/1 2/0 0/6 1/2 @@ -657,17 +663,11 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 31 PA31 SWDIO 15 - - 7/3 1/3 6/1 2/1 - 24 PA24 USB_DM 8 - - 3/2 5/2 5/0 2/2 - 25 PA25 USB_DP 9 - - 3/3 5/3 5/1 - - - 8 PA08 - - 8 2 0/0 2/1 0/0 0/0 1/4 - 9 PA09 - 9 9 3 0/1 2/0 0/1 0/1 1/5 - 10 PA10 - 10 10 - 0/2 2/2 1/0 0/2 1/6 - 11 PA11 - 11 11 - 0/3 2/3 1/1 0/3 1/7 14 PA14 - 14 - - 2/2 4/2 3/0 2/0 1/2 18 PA18 - 2 - - 1/2 3/2 3/0 1/2 0/6 22 PA22 - 6 - - 3/0 5/1 4/0 1/6 0/2 23 PA23 - 7 - - 3/1 5/0 4/1 1/7 0/3 27 PA27 - 11 - - - - - - - - 42 PB10 - 10 - - - 4/2 5/0 0/4 1/0 - 43 PB11 - 12 - - - 4/3 5/1 0/5 1/1 === ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== For the definition of the table columns see the explanation at the table for @@ -734,6 +734,12 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 117 PD21 SD_DET 11 - - 1/3 3/3 - 1/1 - 83 PC19 SD_CS 3 - - 6/3 0/3 - 0/3 - 82 PC18 SD_MISO 2 - - 6/2 0/2 - 0/2 - + 43 PB11 QSPI_CS 12 - - - 4/3 5/1 0/5 1/1 + 8 PA08 QSPI_D0 - 8 2 0/0 2/1 0/0 0/0 1/4 + 9 PA09 QSPI_D1 9 9 3 0/1 2/0 0/1 0/1 1/5 + 10 PA10 QSPI_D2 10 10 - 0/2 2/2 1/0 0/2 1/6 + 11 PA11 QSPI_D3 11 11 - 0/3 2/3 1/1 0/3 1/7 + 42 PB10 QSPI_SCK 10 - - - 4/2 5/0 0/4 1/0 80 PC16 SD_MOSI 0 - - 6/0 0/1 - 0/0 - 81 PC17 SD_SCK 1 - - 6/1 0/0 - 0/1 - 30 PA30 SWCLK 14 - - 7/2 1/2 6/0 2/0 - @@ -750,17 +756,11 @@ Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM 2 PA02 - 2 0 - - - - - - 3 PA03 - 3 10 - - - - - - 5 PA05 - 5 5 - - 0/1 0/1 - - - 8 PA08 - - 8 2 0/0 2/1 0/0 0/0 1/4 - 9 PA09 - 9 9 3 0/1 2/0 0/1 0/1 1/5 - 10 PA10 - 10 10 - 0/2 2/2 1/0 0/2 1/6 - 11 PA11 - 11 11 - 0/3 2/3 1/1 0/3 1/7 14 PA14 - 14 - - 2/2 4/2 3/0 2/0 1/2 18 PA18 - 2 - - 1/2 3/2 3/0 1/2 0/6 19 PA19 - 3 - - 1/3 3/3 3/1 1/3 0/7 23 PA23 - 7 - - 3/1 5/0 4/1 1/7 0/3 27 PA27 - 11 - - - - - - - - 42 PB10 - 10 - - - 4/2 5/0 0/4 1/0 - 43 PB11 - 12 - - - 4/3 5/1 0/5 1/1 46 PB14 - 14 - - 4/2 - 5/0 4/0 0/2 49 PB17 - 1 - - 5/1 - 6/1 3/1 0/5 54 PB22 - 22 - - 1/2 5/2 7/0 - - diff --git a/drivers/memory/external_flash_device.h b/drivers/memory/external_flash_device.h new file mode 100644 index 0000000000000..03798446036aa --- /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/Makefile b/ports/samd/Makefile index 6f7c8fbd85ad3..211607650ba4c 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -113,7 +113,9 @@ SRC_C += \ pin_af.c \ samd_flash.c \ samd_isr.c \ + samd_qspiflash.c \ samd_soc.c \ + samd_spiflash.c \ tusb_port.c \ SHARED_SRC_C += \ diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.h index 815597899c4ae..880df8d200390 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.h @@ -2,3 +2,6 @@ #define MICROPY_HW_MCU_NAME "SAMD21G18A" #define MICROPY_HW_XOSC32K (1) + +#define MICROPY_HW_SPIFLASH (1) +#define MICROPY_HW_SPIFLASH_ID (2) diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.mk index a760cf047e629..aa3fbd35dacef 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/mpconfigboard.mk @@ -6,3 +6,4 @@ TEXT0 = 0x2000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings MICROPY_VFS_LFS1 ?= 1 +MICROPY_HW_CODESIZE ?= 248K diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv index 35b6d1fd4e076..65362770624de 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv @@ -8,9 +8,9 @@ PIN_PB03,LED_RX PIN_PA11,D0 PIN_PA10,D1 -PIN_PA14,D2 -PIN_PA09,D3 -PIN_PA08,D4 +PIN_PA08,FLASH_MOSI +PIN_PA14,FLASH_MISO +PIN_PA09,FLASH_SCK PIN_PA15,D5 PIN_PA20,D6 PIN_PA21,D7 diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.h index b78c003b19a5d..a9f7d518e2364 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_FEATHER_M4_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.mk index 781faa24106dd..70d73ee6fa1b5 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/mpconfigboard.mk @@ -6,3 +6,4 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings MICROPY_VFS_LFS1 ?= 1 +MICROPY_HW_CODESIZE ?= 496K diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv index ad8449ac9ec78..df3373f246a84 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv @@ -28,12 +28,13 @@ PIN_PA17,SCK PIN_PB01,VDIV PIN_PA03,AREF PIN_PB03,NEOPIXEL -PIN_PB11,FLASH_CS -PIN_PB10,FLASH_SCK -PIN_PA08,FLASH_MOSI -PIN_PA09,FLASH_MISO -PIN_PA10,FLASH_WP -PIN_PA11,FLASH_HOLD + +PIN_PB11,QSPI_CS +PIN_PB10,QSPI_SCK +PIN_PA08,QSPI_D0 +PIN_PA09,QSPI_D1 +PIN_PA10,QSPI_D2 +PIN_PA11,QSPI_D3 PIN_PA24,USB_DM PIN_PA25,USB_DP diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.h index 160c61ea2aaae..16018fdc56356 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.h +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.h @@ -2,3 +2,6 @@ #define MICROPY_HW_MCU_NAME "SAMD21G18A" #define MICROPY_HW_DFLL_USB_SYNC (1) + +#define MICROPY_HW_SPIFLASH (1) +#define MICROPY_HW_SPIFLASH_ID (5) diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.mk index a760cf047e629..aa3fbd35dacef 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/mpconfigboard.mk @@ -6,3 +6,4 @@ TEXT0 = 0x2000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings MICROPY_VFS_LFS1 ?= 1 +MICROPY_HW_CODESIZE ?= 248K diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.h index f53481d631917..2f246c60b188c 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/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.mk index da3e47589ad05..7ef411431d7fb 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/mpconfigboard.mk @@ -6,3 +6,4 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings MICROPY_VFS_LFS1 ?= 1 +MICROPY_HW_CODESIZE ?= 496K diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv index 43c8e9a64a971..b63a1f4b13554 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv @@ -28,12 +28,13 @@ PIN_PB23,MISO PIN_PA01,SCK PIN_PB02,DOTSTAR_CLK PIN_PB03,DOTSTAR_DATA -PIN_PB11,FLASH_CS -PIN_PB10,FLASH_SCK -PIN_PA08,FLASH_MOSI -PIN_PA09,FLASH_MISO -PIN_PA10,FLASH_WP -PIN_PA11,FLASH_HOLD + +PIN_PB11,QSPI_CS +PIN_PB10,QSPI_SCK +PIN_PA08,QSPI_D0 +PIN_PA09,QSPI_D1 +PIN_PA10,QSPI_D2 +PIN_PA11,QSPI_D3 PIN_PA24,USB_DM PIN_PA25,USB_DP diff --git a/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.mk b/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.mk index 238ca0055d6a4..a196612539c21 100644 --- a/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.mk +++ b/ports/samd/boards/ADAFRUIT_METRO_M4_EXPRESS/mpconfigboard.mk @@ -10,3 +10,4 @@ MICROPY_PY_NETWORK ?= 1 MICROPY_PY_NETWORK_NINAW10 ?= 1 BOARD_VARIANTS += "wlan" +MICROPY_HW_CODESIZE ?= 496K diff --git a/ports/samd/boards/MINISAM_M4/mpconfigboard.h b/ports/samd/boards/MINISAM_M4/mpconfigboard.h index 6715a16f01450..2dc403bad60fe 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/MINISAM_M4/mpconfigboard.mk b/ports/samd/boards/MINISAM_M4/mpconfigboard.mk index 73afb0210446f..4ccbf92c7e7a9 100644 --- a/ports/samd/boards/MINISAM_M4/mpconfigboard.mk +++ b/ports/samd/boards/MINISAM_M4/mpconfigboard.mk @@ -7,3 +7,4 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings MICROPY_VFS_LFS1 ?= 1 +MICROPY_HW_CODESIZE ?= 496K diff --git a/ports/samd/boards/MINISAM_M4/pins.csv b/ports/samd/boards/MINISAM_M4/pins.csv index 793e523fcd96c..3ea5f85cbacb9 100644 --- a/ports/samd/boards/MINISAM_M4/pins.csv +++ b/ports/samd/boards/MINISAM_M4/pins.csv @@ -26,6 +26,15 @@ PIN_PA01,SCK PIN_PB03,DOTSTAR_DATA PIN_PB02,DOTSTAR_CLK +PIN_PB11,QSPI_CS +PIN_PB10,QSPI_SCK +PIN_PA08,QSPI_D0 +PIN_PA09,QSPI_D1 +PIN_PA10,QSPI_D2 +PIN_PA11,QSPI_D3 + +PIN_PA15,LED + PIN_PA24,USB_DM PIN_PA25,USB_DP PIN_PA26,USB_SOF diff --git a/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h b/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.h index 8260992101c5d..062f69ae4e773 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/SEEED_WIO_TERMINAL/mpconfigboard.mk b/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.mk index c2c4da6152738..03f97e138c53c 100644 --- a/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.mk +++ b/ports/samd/boards/SEEED_WIO_TERMINAL/mpconfigboard.mk @@ -6,3 +6,4 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings MICROPY_VFS_LFS1 ?= 1 +MICROPY_HW_CODESIZE ?= 496K diff --git a/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv b/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv index 55cbb5bf0492d..c32108b9f72a0 100644 --- a/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv +++ b/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv @@ -58,6 +58,13 @@ PIN_PC13,LCD_YD PIN_PC30,MIC PIN_PD11,BUZZER +PIN_PB11,QSPI_CS +PIN_PB10,QSPI_SCK +PIN_PA08,QSPI_D0 +PIN_PA09,QSPI_D1 +PIN_PA10,QSPI_D2 +PIN_PA11,QSPI_D3 + PIN_PA15,LED_BLUE PIN_PC05,LED_LCD diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h index a51b71c363e18..706fc3c64c37b 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/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk index 9e5cf887dfce3..65b1ba7a33df4 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk @@ -6,3 +6,4 @@ TEXT0 = 0x4000 # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings MICROPY_VFS_LFS1 ?= 1 +MICROPY_HW_CODESIZE ?= 1008K diff --git a/ports/samd/modsamd.c b/ports/samd/modsamd.c index 7593a7bb0da38..307de62af56e8 100644 --- a/ports/samd/modsamd.c +++ b/ports/samd/modsamd.c @@ -32,7 +32,18 @@ #include "pin_af.h" #include "samd_soc.h" +#if MICROPY_HW_MCUFLASH extern const mp_obj_type_t samd_flash_type; +#define SPIFLASH_TYPE samd_flash_type +#endif +#ifdef MICROPY_HW_QSPIFLASH +extern const mp_obj_type_t samd_qspiflash_type; +#define SPIFLASH_TYPE samd_qspiflash_type +#endif +#if MICROPY_HW_SPIFLASH +extern const mp_obj_type_t samd_spiflash_type; +#define SPIFLASH_TYPE samd_spiflash_type +#endif STATIC mp_obj_t samd_pininfo(mp_obj_t pin_obj) { const machine_pin_obj_t *pin_af = pin_find(pin_obj); @@ -67,7 +78,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(samd_pininfo_obj, samd_pininfo); STATIC const mp_rom_map_elem_t samd_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_samd) }, - { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&samd_flash_type) }, + { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&SPIFLASH_TYPE) }, { MP_ROM_QSTR(MP_QSTR_pininfo), MP_ROM_PTR(&samd_pininfo_obj) }, }; STATIC MP_DEFINE_CONST_DICT(samd_module_globals, samd_module_globals_table); diff --git a/ports/samd/modules/_boot.py b/ports/samd/modules/_boot.py index e183125f2e4bf..5fe2bc640c584 100644 --- a/ports/samd/modules/_boot.py +++ b/ports/samd/modules/_boot.py @@ -2,18 +2,18 @@ import uos import samd -samd.Flash.flash_init() bdev = samd.Flash() # Try to mount the filesystem, and format the flash if it doesn't exist. fs_type = uos.VfsLfs2 if hasattr(uos, "VfsLfs2") else uos.VfsLfs1 try: - vfs = fs_type(bdev) + vfs = fs_type(bdev, progsize=256) except: - fs_type.mkfs(bdev) - vfs = fs_type(bdev) + fs_type.mkfs(bdev, progsize=256) + vfs = fs_type(bdev, progsize=256) uos.mount(vfs, "/") +del vfs, fs_type, bdev, uos, samd gc.collect() -del uos, vfs, gc +del gc diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index bf03b5f7bd681..7392b03059079 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -136,6 +136,11 @@ #define MICROPY_BOARD_PENDSV_ENTRIES #endif +// Use internal flash for the file system if no flash file system is selected. +#if !defined(MICROPY_HW_MCUFLASH) && !defined(MICROPY_HW_QSPIFLASH) && !(defined(MICROPY_HW_SPIFLASH) && defined(MICROPY_HW_SPIFLASH_ID)) +#define MICROPY_HW_MCUFLASH (1) +#endif // !defined(MICROPY_HW_MCUFLASH) .... + // Miscellaneous settings __attribute__((always_inline)) static inline void enable_irq(uint32_t state) { __set_PRIMASK(state); diff --git a/ports/samd/pin_af.h b/ports/samd/pin_af.h index edab72aaed6a4..3a15ae7f356a9 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_flash.c b/ports/samd/samd_flash.c index 6d9ee96895b01..b00b4af6032bf 100644 --- a/ports/samd/samd_flash.c +++ b/ports/samd/samd_flash.c @@ -29,7 +29,8 @@ #include "py/runtime.h" #include "extmod/vfs.h" #include "samd_soc.h" -#include "hal_flash.h" + +#if MICROPY_HW_MCUFLASH // ASF 4 #include "hal_flash.h" @@ -62,18 +63,9 @@ STATIC samd_flash_obj_t samd_flash_obj = { .flash_size = (uint32_t)&_sflash_fs, // Get from MCU-Specific loader script. }; -// FLASH stuff -STATIC mp_obj_t samd_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - // No args required. bdev=Flash(). Start Addr & Size defined in samd_flash_obj. - mp_arg_check_num(n_args, n_kw, 0, 0, false); - - // Return singleton object. - return MP_OBJ_FROM_PTR(&samd_flash_obj); -} - // Flash init (from cctpy) // Method is needed for when MP starts up in _boot.py -STATIC mp_obj_t samd_flash_init(void) { +STATIC void samd_flash_init(void) { #ifdef SAMD51 hri_mclk_set_AHBMASK_NVMCTRL_bit(MCLK); #endif @@ -82,9 +74,17 @@ STATIC mp_obj_t samd_flash_init(void) { #endif flash_init(&flash_desc, NVMCTRL); - return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(samd_flash_init_obj, samd_flash_init); + +STATIC mp_obj_t samd_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // No args required. bdev=Flash(). Start Addr & Size defined in samd_flash_obj. + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + samd_flash_init(); + + // Return singleton object. + return MP_OBJ_FROM_PTR(&samd_flash_obj); +} // Function for ioctl. STATIC mp_obj_t eraseblock(uint32_t sector_in) { @@ -102,14 +102,6 @@ STATIC mp_obj_t samd_flash_version(void) { } STATIC MP_DEFINE_CONST_FUN_OBJ_0(samd_flash_version_obj, samd_flash_version); -STATIC mp_obj_t samd_flash_size(void) { - // ASF4 API calls - mp_int_t PAGES = flash_get_total_pages(&flash_desc); - mp_int_t PAGE_SIZE = flash_get_page_size(&flash_desc); - return MP_OBJ_NEW_SMALL_INT(PAGES * PAGE_SIZE); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(samd_flash_size_obj, samd_flash_size); - STATIC mp_obj_t samd_flash_readblocks(size_t n_args, const mp_obj_t *args) { uint32_t offset = (mp_obj_get_int(args[1]) * BLOCK_SIZE) + samd_flash_obj.flash_base; mp_buffer_info_t bufinfo; @@ -171,8 +163,6 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(samd_flash_ioctl_obj, samd_flash_ioctl); STATIC const mp_rom_map_elem_t samd_flash_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_flash_version), MP_ROM_PTR(&samd_flash_version_obj) }, - { MP_ROM_QSTR(MP_QSTR_flash_size), MP_ROM_PTR(&samd_flash_size_obj) }, - { MP_ROM_QSTR(MP_QSTR_flash_init), MP_ROM_PTR(&samd_flash_init_obj) }, { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&samd_flash_readblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&samd_flash_writeblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&samd_flash_ioctl_obj) }, @@ -186,3 +176,5 @@ MP_DEFINE_CONST_OBJ_TYPE( make_new, samd_flash_make_new, locals_dict, &samd_flash_locals_dict ); + +#endif // MICROPY_HW_MCUFLASH diff --git a/ports/samd/samd_qspiflash.c b/ports/samd/samd_qspiflash.c new file mode 100644 index 0000000000000..9bb79de5c5a28 --- /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 diff --git a/ports/samd/samd_spiflash.c b/ports/samd/samd_spiflash.c new file mode 100644 index 0000000000000..eaa0ec14348a8 --- /dev/null +++ b/ports/samd/samd_spiflash.c @@ -0,0 +1,299 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2020 Peter Hinch + * 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. + * + */ + +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "extmod/machine_spi.h" +#include "extmod/vfs.h" +#include "modmachine.h" +#include "pin_af.h" + +#if MICROPY_HW_SPIFLASH + +#define _READ_INDEX (0) +#define _PROGRAM_PAGE_INDEX (1) +#define _SECTOR_ERASE_INDEX (2) + +const uint8_t _COMMANDS_24BIT[] = {0x03, 0x02, 0x20}; // READ, PROGRAM_PAGE, ERASE_4K +const uint8_t _COMMANDS_32BIT[] = {0x13, 0x12, 0x21}; // READ, PROGRAM_PAGE, ERASE_4K + +#define COMMAND_JEDEC_ID (0x9F) +#define COMMAND_READ_STATUS (0x05) +#define COMMAND_WRITE_ENABLE (0x06) +#define COMMAND_READ_SFDP (0x5A) +#define PAGE_SIZE (256) +#define SECTOR_SIZE (4096) + +typedef struct _spiflash_obj_t { + mp_obj_base_t base; + mp_obj_base_t *spi; + mp_hal_pin_obj_t cs; + bool addr_is_32bit; + uint16_t pagesize; + uint16_t sectorsize; + const uint8_t *commands; + uint32_t size; +} spiflash_obj_t; + +extern const mp_obj_type_t samd_spiflash_type; + +// The SPIflash object is a singleton +static spiflash_obj_t spiflash_obj = { + { &samd_spiflash_type }, NULL, 0, false, PAGE_SIZE, SECTOR_SIZE, NULL, 0 +}; + +static void spi_transfer(mp_obj_base_t *spi, size_t len, const uint8_t *src, uint8_t *dest) { + mp_machine_spi_p_t *spi_p = (mp_machine_spi_p_t *)MP_OBJ_TYPE_GET_SLOT(spi->type, protocol); + spi_p->transfer(spi, len, src, dest); +} + +static void wait(spiflash_obj_t *self) { + uint8_t msg[2]; + uint32_t timeout = 100000; + + // each loop takes at least about 5us @ 120Mhz. So a timeout of + // 100000 wait 500ms max. at 120Mhz. Sector erase lasts about + // 100ms worst case, page write is < 1ms. + do { + msg[0] = COMMAND_READ_STATUS; + mp_hal_pin_write(self->cs, 0); + spi_transfer((mp_obj_base_t *)self->spi, 2, msg, msg); + mp_hal_pin_write(self->cs, 1); + } while (msg[1] != 0 && timeout-- > 0); +} + +static void get_id(spiflash_obj_t *self, uint8_t id[3]) { + uint8_t msg[1]; + + msg[0] = COMMAND_JEDEC_ID; + mp_hal_pin_write(self->cs, 0); + spi_transfer(self->spi, 1, msg, NULL); + spi_transfer(self->spi, 3, id, id); + mp_hal_pin_write(self->cs, 1); +} +static void write_addr(spiflash_obj_t *self, uint8_t cmd, uint32_t addr) { + uint8_t msg[5]; + uint8_t index = 1; + msg[0] = cmd; + if (self->addr_is_32bit) { + msg[index++] = addr >> 24; + } + msg[index++] = (addr >> 16) & 0xff; + msg[index++] = (addr >> 8) & 0xff; + msg[index++] = addr & 0xff; + mp_hal_pin_write(self->cs, 0); + spi_transfer(self->spi, self->addr_is_32bit ? 5 : 4, msg, msg); +} + +static void write_enable(spiflash_obj_t *self) { + uint8_t msg[1]; + + msg[0] = COMMAND_WRITE_ENABLE; + mp_hal_pin_write(self->cs, 0); + spi_transfer(self->spi, 1, msg, NULL); + mp_hal_pin_write(self->cs, 1); +} + +static void get_sfdp(spiflash_obj_t *self, uint32_t addr, uint8_t *buffer, int size) { + uint8_t dummy[1]; + write_addr(self, COMMAND_READ_SFDP, addr); + spi_transfer(self->spi, 1, dummy, NULL); + spi_transfer(self->spi, size, buffer, buffer); + mp_hal_pin_write(self->cs, 1); +} + +STATIC mp_obj_t spiflash_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); + + // Set up the object + spiflash_obj_t *self = &spiflash_obj; + + mp_obj_t spi_args[] = { + MP_OBJ_NEW_SMALL_INT(MICROPY_HW_SPIFLASH_ID), + MP_OBJ_NEW_SMALL_INT(24000000), // baudrate + MP_OBJ_NEW_QSTR(MP_QSTR_mosi), MP_OBJ_NEW_QSTR(MP_QSTR_FLASH_MOSI), + MP_OBJ_NEW_QSTR(MP_QSTR_miso), MP_OBJ_NEW_QSTR(MP_QSTR_FLASH_MISO), + MP_OBJ_NEW_QSTR(MP_QSTR_sck), MP_OBJ_NEW_QSTR(MP_QSTR_FLASH_SCK), + }; + self->spi = MP_OBJ_TYPE_GET_SLOT(&machine_spi_type, make_new)((mp_obj_t)&machine_spi_type, 2, 3, spi_args); + + mp_obj_t pin_args[] = { + MP_OBJ_NEW_QSTR(MP_QSTR_FLASH_CS), + MP_ROM_INT(1), + }; + machine_pin_obj_t *cs = MP_OBJ_TYPE_GET_SLOT(&machine_pin_type, make_new)((mp_obj_t)&machine_pin_type, 2, 0, pin_args); + self->cs = cs->pin_id; + mp_hal_pin_write(self->cs, 1); + + wait(self); + + // Get the flash size from the device ID (default) + uint8_t id[3]; + get_id(self, id); + if (id[1] == 0x84 && id[2] == 1) { // Adesto + self->size = 512 * 1024; + } else if (id[1] == 0x1f && id[2] == 1) { // Atmel / Renesas + self->size = 1024 * 1024; + } else { + self->size = 1 << id[2]; + } + + // Get the addr_is_32bit flag and the sector size + uint8_t buffer[128]; + get_sfdp(self, 0, buffer, 16); // get the header + int len = MIN(buffer[11] * 4, sizeof(buffer)); + if (len >= 29) { + int addr = buffer[12] + (buffer[13] << 8) + (buffer[14] << 16); + get_sfdp(self, addr, buffer, len); // Get the JEDEC mandatory table + self->sectorsize = 1 << buffer[28]; + self->addr_is_32bit = ((buffer[2] >> 1) & 0x03) != 0; + } + self->commands = self->addr_is_32bit ? _COMMANDS_32BIT : _COMMANDS_24BIT; + + return self; +} + +STATIC mp_obj_t spiflash_read(spiflash_obj_t *self, uint32_t addr, uint8_t *dest, uint32_t len) { + if (len > 0) { + write_addr(self, self->commands[_READ_INDEX], addr); + spi_transfer(self->spi, len, dest, dest); + mp_hal_pin_write(self->cs, 1); + } + + return mp_const_none; +} + +STATIC mp_obj_t spiflash_write(spiflash_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; + + write_enable(self); + write_addr(self, self->commands[_PROGRAM_PAGE_INDEX], addr); + spi_transfer(self->spi, size, buf + pos, NULL); + mp_hal_pin_write(self->cs, 1); + wait(self); + + addr += size; + pos += size; + } + + return mp_const_none; +} + +STATIC mp_obj_t spiflash_erase(spiflash_obj_t *self, uint32_t addr) { + write_enable(self); + write_addr(self, self->commands[_SECTOR_ERASE_INDEX], addr); + mp_hal_pin_write(self->cs, 1); + wait(self); + + return mp_const_none; +} + +STATIC mp_obj_t spiflash_readblocks(size_t n_args, const mp_obj_t *args) { + spiflash_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) + spiflash_read(self, offset, bufinfo.buf, bufinfo.len); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(spiflash_readblocks_obj, 3, 4, spiflash_readblocks); + +STATIC mp_obj_t spiflash_writeblocks(size_t n_args, const mp_obj_t *args) { + spiflash_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) { + spiflash_erase(self, offset); + // TODO check return value + } else { + offset += mp_obj_get_int(args[3]); + } + // Write data to flash (adf4 API) + spiflash_write(self, offset, bufinfo.buf, bufinfo.len); + // TODO check return value + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(spiflash_writeblocks_obj, 3, 4, spiflash_writeblocks); + +STATIC mp_obj_t spiflash_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { + spiflash_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: { + spiflash_erase(self, 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(spiflash_ioctl_obj, spiflash_ioctl); + +STATIC const mp_rom_map_elem_t spiflash_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&spiflash_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&spiflash_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&spiflash_ioctl_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(spiflash_locals_dict, spiflash_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + samd_spiflash_type, + MP_QSTR_Flash, + MP_TYPE_FLAG_NONE, + make_new, spiflash_make_new, + locals_dict, &spiflash_locals_dict + ); + +#endif // #if MICROPY_HW_SPIFLASH