micropython/ports/samd/samd_qspiflash.c

492 lines
17 KiB
C

/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Adafruit Industries
* Copyright (c) 2023 Robert Hammelrath
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Port of the Adafruit QSPIflash driver for SAMD devices
*
*/
#include <stdint.h>
#include <string.h>
#include "py/obj.h"
#include "py/runtime.h"
#include "py/mphal.h"
#include "py/mperrno.h"
#include "modmachine.h"
#include "extmod/machine_spi.h"
#include "extmod/vfs.h"
#include "pin_af.h"
#include "clock_config.h"
#include "sam.h"
#ifdef MICROPY_HW_QSPIFLASH
#include "drivers/memory/external_flash_device.h"
// QSPI command codes
enum
{
QSPI_CMD_READ = 0x03,
QSPI_CMD_READ_4B = 0x13,
QSPI_CMD_QUAD_READ = 0x6B,// 1 line address, 4 line data
QSPI_CMD_READ_JEDEC_ID = 0x9f,
QSPI_CMD_PAGE_PROGRAM = 0x02,
QSPI_CMD_PAGE_PROGRAM_4B = 0x12,
QSPI_CMD_QUAD_PAGE_PROGRAM = 0x32, // 1 line address, 4 line data
QSPI_CMD_READ_STATUS = 0x05,
QSPI_CMD_READ_STATUS2 = 0x35,
QSPI_CMD_WRITE_STATUS = 0x01,
QSPI_CMD_WRITE_STATUS2 = 0x31,
QSPI_CMD_ENABLE_RESET = 0x66,
QSPI_CMD_RESET = 0x99,
QSPI_CMD_WRITE_ENABLE = 0x06,
QSPI_CMD_WRITE_DISABLE = 0x04,
QSPI_CMD_ERASE_SECTOR = 0x20,
QSPI_CMD_ERASE_SECTOR_4B = 0x21,
QSPI_CMD_ERASE_BLOCK = 0xD8,
QSPI_CMD_ERASE_CHIP = 0xC7,
QSPI_CMD_READ_SFDP_PARAMETER = 0x5A,
};
// QSPI flash pins are: CS=PB11, SCK=PB10, IO0-IO3=PA08, PA09, PA10 and PA11.
#define PIN_CS (43)
#define PIN_SCK (42)
#define PIN_IO0 (8)
#define PIN_IO1 (9)
#define PIN_IO2 (10)
#define PIN_IO3 (11)
#define PAGE_SIZE (256)
#define SECTOR_SIZE (4096)
typedef struct _samd_qspiflash_obj_t {
mp_obj_base_t base;
uint16_t pagesize;
uint16_t sectorsize;
uint32_t size;
uint8_t phase;
uint8_t polarity;
} samd_qspiflash_obj_t;
/// List of all possible flash devices used by Adafruit boards
static const external_flash_device possible_devices[] = {
MICROPY_HW_QSPIFLASH
};
#define EXTERNAL_FLASH_DEVICE_COUNT MP_ARRAY_SIZE(possible_devices)
static external_flash_device const *flash_device;
static external_flash_device generic_config = GENERIC;
extern const mp_obj_type_t samd_qspiflash_type;
// The QSPIflash object is a singleton
static samd_qspiflash_obj_t qspiflash_obj = { { &samd_qspiflash_type } };
// Turn off cache and invalidate all data in it.
static void samd_peripherals_disable_and_clear_cache(void) {
CMCC->CTRL.bit.CEN = 0;
while (CMCC->SR.bit.CSTS) {
}
CMCC->MAINT0.bit.INVALL = 1;
}
// Enable cache
static void samd_peripherals_enable_cache(void) {
CMCC->CTRL.bit.CEN = 1;
}
// Run a single QSPI instruction.
// Parameters are:
// - command instruction code
// - iframe iframe register value (configured by caller according to command code)
// - addr the address to read or write from. If the instruction doesn't require an address, this parameter is meaningless.
// - buffer pointer to the data to be written or stored depending on the type is Read or Write
// - size the number of bytes to read or write.
bool run_instruction(uint8_t command, uint32_t iframe, uint32_t addr, uint8_t *buffer, uint32_t size) {
samd_peripherals_disable_and_clear_cache();
uint8_t *qspi_mem = (uint8_t *)QSPI_AHB;
if (addr) {
qspi_mem += addr;
}
QSPI->INSTRCTRL.bit.INSTR = command;
QSPI->INSTRADDR.reg = addr;
QSPI->INSTRFRAME.reg = iframe;
// Dummy read of INSTRFRAME needed to synchronize.
// See Instruction Transmission Flow Diagram, figure 37.9, page 995
// and Example 4, page 998, section 37.6.8.5.
(volatile uint32_t)QSPI->INSTRFRAME.reg;
if (buffer && size) {
uint32_t const tfr_type = iframe & QSPI_INSTRFRAME_TFRTYPE_Msk;
if ((tfr_type == QSPI_INSTRFRAME_TFRTYPE_READ) || (tfr_type == QSPI_INSTRFRAME_TFRTYPE_READMEMORY)) {
memcpy(buffer, qspi_mem, size);
} else {
memcpy(qspi_mem, buffer, size);
}
}
__asm volatile ("dsb");
__asm volatile ("isb");
QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE | QSPI_CTRLA_LASTXFER;
while (!QSPI->INTFLAG.bit.INSTREND) {
}
QSPI->INTFLAG.reg = QSPI_INTFLAG_INSTREND;
samd_peripherals_enable_cache();
return true;
}
bool run_command(uint8_t command) {
uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
QSPI_INSTRFRAME_TFRTYPE_READ | QSPI_INSTRFRAME_INSTREN;
return run_instruction(command, iframe, 0, NULL, 0);
}
bool read_command(uint8_t command, uint8_t *response, uint32_t len) {
uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
QSPI_INSTRFRAME_TFRTYPE_READ | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_DATAEN;
return run_instruction(command, iframe, 0, response, len);
}
bool read_memory_single(uint8_t command, uint32_t addr, uint8_t *response, uint32_t len) {
uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
QSPI_INSTRFRAME_TFRTYPE_READ | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN |
QSPI_INSTRFRAME_DATAEN | QSPI_INSTRFRAME_DUMMYLEN(8);
return run_instruction(command, iframe, addr, response, len);
}
bool write_command(uint8_t command, uint8_t const *data, uint32_t len) {
uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
QSPI_INSTRFRAME_TFRTYPE_WRITE | QSPI_INSTRFRAME_INSTREN | (data != NULL ? QSPI_INSTRFRAME_DATAEN : 0);
return run_instruction(command, iframe, 0, (uint8_t *)data, len);
}
bool erase_command(uint8_t command, uint32_t address) {
// Sector Erase
uint32_t iframe = QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | QSPI_INSTRFRAME_ADDRLEN_24BITS |
QSPI_INSTRFRAME_TFRTYPE_WRITE | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN;
return run_instruction(command, iframe, address, NULL, 0);
}
bool read_memory_quad(uint8_t command, uint32_t addr, uint8_t *data, uint32_t len) {
uint32_t iframe = QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT | QSPI_INSTRFRAME_ADDRLEN_24BITS |
QSPI_INSTRFRAME_TFRTYPE_READMEMORY | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN | QSPI_INSTRFRAME_DATAEN |
/*QSPI_INSTRFRAME_CRMODE |*/ QSPI_INSTRFRAME_DUMMYLEN(8);
return run_instruction(command, iframe, addr, data, len);
}
bool write_memory_quad(uint8_t command, uint32_t addr, uint8_t *data, uint32_t len) {
uint32_t iframe = QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT | QSPI_INSTRFRAME_ADDRLEN_24BITS |
QSPI_INSTRFRAME_TFRTYPE_WRITEMEMORY | QSPI_INSTRFRAME_INSTREN | QSPI_INSTRFRAME_ADDREN | QSPI_INSTRFRAME_DATAEN;
return run_instruction(command, iframe, addr, data, len);
}
static uint8_t read_status(void) {
uint8_t r;
read_command(QSPI_CMD_READ_STATUS, &r, 1);
return r;
}
static uint8_t read_status2(void) {
uint8_t r;
read_command(QSPI_CMD_READ_STATUS2, &r, 1);
return r;
}
static bool write_enable(void) {
return run_command(QSPI_CMD_WRITE_ENABLE);
}
static void wait_for_flash_ready(void) {
// both WIP and WREN bit should be clear
while (read_status() & 0x03) {
}
}
static uint8_t get_baud(int32_t freq_mhz) {
int baud = get_peripheral_freq() / (freq_mhz * 1000000) - 1;
if (baud < 1) {
baud = 1;
}
if (baud > 255) {
baud = 255;
}
return baud;
}
int get_sfdp_table(uint8_t *table, int maxlen) {
uint8_t header[16];
read_memory_single(QSPI_CMD_READ_SFDP_PARAMETER, 0, header, sizeof(header));
int len = MIN(header[11] * 4, maxlen);
int addr = header[12] + (header[13] << 8) + (header[14] << 16);
read_memory_single(QSPI_CMD_READ_SFDP_PARAMETER, addr, table, len);
return len;
}
STATIC mp_obj_t samd_qspiflash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
mp_arg_check_num(n_args, n_kw, 0, 0, false);
// The QSPI is a singleton
samd_qspiflash_obj_t *self = &qspiflash_obj;
self->phase = 0;
self->polarity = 0;
self->pagesize = PAGE_SIZE;
self->sectorsize = SECTOR_SIZE;
// Enable the device clock
MCLK->AHBMASK.reg |= MCLK_AHBMASK_QSPI;
MCLK->AHBMASK.reg |= MCLK_AHBMASK_QSPI_2X;
MCLK->APBCMASK.reg |= MCLK_APBCMASK_QSPI;
// Configure the pins.
mp_hal_set_pin_mux(PIN_CS, ALT_FCT_QSPI);
mp_hal_set_pin_mux(PIN_SCK, ALT_FCT_QSPI);
mp_hal_set_pin_mux(PIN_IO0, ALT_FCT_QSPI);
mp_hal_set_pin_mux(PIN_IO1, ALT_FCT_QSPI);
mp_hal_set_pin_mux(PIN_IO2, ALT_FCT_QSPI);
mp_hal_set_pin_mux(PIN_IO3, ALT_FCT_QSPI);
// Configure the QSPI interface
QSPI->CTRLA.bit.SWRST = 1;
mp_hal_delay_us(1000); // Maybe not required.
QSPI->CTRLB.reg = QSPI_CTRLB_MODE_MEMORY |
QSPI_CTRLB_CSMODE_NORELOAD |
QSPI_CTRLB_DATALEN_8BITS |
QSPI_CTRLB_CSMODE_LASTXFER;
// start with low 4Mhz, Mode 0
QSPI->BAUD.reg = QSPI_BAUD_BAUD(get_baud(4)) |
(self->phase << QSPI_BAUD_CPHA_Pos) |
(self->polarity << QSPI_BAUD_CPOL_Pos);
QSPI->CTRLA.bit.ENABLE = 1;
uint8_t jedec_ids[3];
read_command(QSPI_CMD_READ_JEDEC_ID, jedec_ids, sizeof(jedec_ids));
// Read the common sfdp table
// Check the device addr length, support of 1-1-4 mode and get the sector size
uint8_t sfdp_table[128];
int len = get_sfdp_table(sfdp_table, sizeof(sfdp_table));
if (len >= 29) {
self->sectorsize = 1 << sfdp_table[28];
bool addr4b = ((sfdp_table[2] >> 1) & 0x03) == 0x02;
bool supports_qspi_114 = (sfdp_table[2] & 0x40) != 0;
if (addr4b || !supports_qspi_114) {
mp_raise_ValueError(MP_ERROR_TEXT("QSPI mode not supported"));
}
}
// Check, if the flash device is known and get it's properties.
flash_device = NULL;
for (uint8_t i = 0; i < EXTERNAL_FLASH_DEVICE_COUNT; i++) {
const external_flash_device *possible_device = &possible_devices[i];
if (jedec_ids[0] == possible_device->manufacturer_id &&
jedec_ids[1] == possible_device->memory_type &&
jedec_ids[2] == possible_device->capacity) {
flash_device = possible_device;
break;
}
}
// If the flash device is not known, try generic config options
if (flash_device == NULL) {
if (jedec_ids[0] == 0xc2) { // Macronix devices
generic_config.quad_enable_bit_mask = 0x04;
generic_config.single_status_byte = true;
}
generic_config.total_size = 1 << jedec_ids[2];
flash_device = &generic_config;
}
self->size = flash_device->total_size;
// The write in progress bit should be low.
while (read_status() & 0x01) {
}
// The suspended write/erase bit should be low.
while (read_status2() & 0x80) {
}
run_command(QSPI_CMD_ENABLE_RESET);
run_command(QSPI_CMD_RESET);
// Wait 30us for the reset
mp_hal_delay_us(30);
// Speed up the frequency
QSPI->BAUD.bit.BAUD = get_baud(flash_device->max_clock_speed_mhz);
// Enable Quad Mode if available
uint8_t status = 0;
if (flash_device->quad_enable_bit_mask) {
// Verify that QSPI mode is enabled.
status = flash_device->single_status_byte ? read_status() : read_status2();
}
// Check the quad enable bit.
if ((status & flash_device->quad_enable_bit_mask) == 0) {
write_enable();
uint8_t full_status[2] = {0x00, flash_device->quad_enable_bit_mask};
if (flash_device->write_status_register_split) {
write_command(QSPI_CMD_WRITE_STATUS2, full_status + 1, 1);
} else if (flash_device->single_status_byte) {
write_command(QSPI_CMD_WRITE_STATUS, full_status + 1, 1);
} else {
write_command(QSPI_CMD_WRITE_STATUS, full_status, 2);
}
}
// Turn off writes in case this is a microcontroller only reset.
run_command(QSPI_CMD_WRITE_DISABLE);
wait_for_flash_ready();
return self;
}
STATIC mp_obj_t samd_qspiflash_read(samd_qspiflash_obj_t *self, uint32_t addr, uint8_t *dest, uint32_t len) {
if (len > 0) {
wait_for_flash_ready();
// Command 0x6B 1 line address, 4 line Data
// with Continuous Read Mode and Quad output mode, read memory type
read_memory_quad(QSPI_CMD_QUAD_READ, addr, dest, len);
}
return mp_const_none;
}
STATIC mp_obj_t samd_qspiflash_write(samd_qspiflash_obj_t *self, uint32_t addr, uint8_t *src, uint32_t len) {
uint32_t length = len;
uint32_t pos = 0;
uint8_t *buf = src;
while (pos < length) {
uint16_t maxsize = self->pagesize - pos % self->pagesize;
uint16_t size = (length - pos) > maxsize ? maxsize : length - pos;
wait_for_flash_ready();
write_enable();
write_memory_quad(QSPI_CMD_QUAD_PAGE_PROGRAM, addr, buf + pos, size);
addr += size;
pos += size;
}
return mp_const_none;
}
STATIC mp_obj_t samd_qspiflash_erase(uint32_t addr) {
wait_for_flash_ready();
write_enable();
erase_command(QSPI_CMD_ERASE_SECTOR, addr);
return mp_const_none;
}
STATIC mp_obj_t samd_qspiflash_readblocks(size_t n_args, const mp_obj_t *args) {
samd_qspiflash_obj_t *self = MP_OBJ_TO_PTR(args[0]);
uint32_t offset = (mp_obj_get_int(args[1]) * self->sectorsize);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE);
if (n_args == 4) {
offset += mp_obj_get_int(args[3]);
}
// Read data to flash (adf4 API)
samd_qspiflash_read(self, offset, bufinfo.buf, bufinfo.len);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(samd_qspiflash_readblocks_obj, 3, 4, samd_qspiflash_readblocks);
STATIC mp_obj_t samd_qspiflash_writeblocks(size_t n_args, const mp_obj_t *args) {
samd_qspiflash_obj_t *self = MP_OBJ_TO_PTR(args[0]);
uint32_t offset = (mp_obj_get_int(args[1]) * self->sectorsize);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
if (n_args == 3) {
samd_qspiflash_erase(offset);
// TODO check return value
} else {
offset += mp_obj_get_int(args[3]);
}
// Write data to flash (adf4 API)
samd_qspiflash_write(self, offset, bufinfo.buf, bufinfo.len);
// TODO check return value
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(samd_qspiflash_writeblocks_obj, 3, 4, samd_qspiflash_writeblocks);
STATIC mp_obj_t samd_qspiflash_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) {
samd_qspiflash_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_int_t cmd = mp_obj_get_int(cmd_in);
switch (cmd) {
case MP_BLOCKDEV_IOCTL_INIT:
return MP_OBJ_NEW_SMALL_INT(0);
case MP_BLOCKDEV_IOCTL_DEINIT:
return MP_OBJ_NEW_SMALL_INT(0);
case MP_BLOCKDEV_IOCTL_SYNC:
return MP_OBJ_NEW_SMALL_INT(0);
case MP_BLOCKDEV_IOCTL_BLOCK_COUNT:
return MP_OBJ_NEW_SMALL_INT(self->size / self->sectorsize);
case MP_BLOCKDEV_IOCTL_BLOCK_SIZE:
return MP_OBJ_NEW_SMALL_INT(self->sectorsize);
case MP_BLOCKDEV_IOCTL_BLOCK_ERASE: {
samd_qspiflash_erase(mp_obj_get_int(arg_in) * self->sectorsize);
// TODO check return value
return MP_OBJ_NEW_SMALL_INT(0);
}
default:
return mp_const_none;
}
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(samd_qspiflash_ioctl_obj, samd_qspiflash_ioctl);
STATIC const mp_rom_map_elem_t samd_qspiflash_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&samd_qspiflash_readblocks_obj) },
{ MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&samd_qspiflash_writeblocks_obj) },
{ MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&samd_qspiflash_ioctl_obj) },
};
STATIC MP_DEFINE_CONST_DICT(samd_qspiflash_locals_dict, samd_qspiflash_locals_dict_table);
MP_DEFINE_CONST_OBJ_TYPE(
samd_qspiflash_type,
MP_QSTR_Flash,
MP_TYPE_FLAG_NONE,
make_new, samd_qspiflash_make_new,
locals_dict, &samd_qspiflash_locals_dict
);
#endif // MICROPY_HW_QSPI_FLASH