/* Copyright (C) 2021 Westberry Technology (ChangZhou) Corp., Ltd This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "util.h" #include "wait.h" #include "debug.h" #include "timer.h" #include "flash_spi.h" #include "spi_master.h" /* The time-out time of spi flash transmission. */ #ifndef EXTERNAL_FLASH_SPI_TIMEOUT # define EXTERNAL_FLASH_SPI_TIMEOUT 1000 #endif /* ID comands */ #define FLASH_CMD_RDID 0x9F /* RDID (Read Identification) */ #define FLASH_CMD_RES 0xAB /* RES (Read Electronic ID) */ #define FLASH_CMD_REMS 0x90 /* REMS (Read Electronic & Device ID) */ /* register comands */ #define FLASH_CMD_WRSR 0x01 /* WRSR (Write Status register) */ #define FLASH_CMD_RDSR 0x05 /* RDSR (Read Status register) */ /* READ comands */ #define FLASH_CMD_READ 0x03 /* READ (1 x I/O) */ #define FLASH_CMD_FASTREAD 0x0B /* FAST READ (Fast read data) */ #define FLASH_CMD_DREAD 0x3B /* DREAD (1In/2 Out fast read) */ /* Program comands */ #define FLASH_CMD_WREN 0x06 /* WREN (Write Enable) */ #define FLASH_CMD_WRDI 0x04 /* WRDI (Write Disable) */ #define FLASH_CMD_PP 0x02 /* PP (page program) */ /* Erase comands */ #define FLASH_CMD_SE 0x20 /* SE (Sector Erase) */ #define FLASH_CMD_BE 0xD8 /* BE (Block Erase) */ #define FLASH_CMD_CE 0x60 /* CE (Chip Erase) hex code: 60 or C7 */ /* Mode setting comands */ #define FLASH_CMD_DP 0xB9 /* DP (Deep Power Down) */ #define FLASH_CMD_RDP 0xAB /* RDP (Release form Deep Power Down) */ /* Status register */ #define FLASH_FLAG_WIP 0x01 /* Write in progress bit */ #define FLASH_FLAG_WEL 0x02 /* Write enable latch bit */ // #define DEBUG_FLASH_SPI_OUTPUT static bool spi_flash_start(void) { return spi_start(EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN, EXTERNAL_FLASH_SPI_LSBFIRST, EXTERNAL_FLASH_SPI_MODE, EXTERNAL_FLASH_SPI_CLOCK_DIVISOR); } static flash_status_t spi_flash_wait_while_busy(void) { uint32_t deadline = timer_read32() + EXTERNAL_FLASH_SPI_TIMEOUT; flash_status_t response = FLASH_STATUS_SUCCESS; uint8_t retval; do { bool res = spi_flash_start(); if (!res) { dprint("Failed to start SPI! [spi flash wait while busy]\n"); return FLASH_STATUS_ERROR; } spi_write(FLASH_CMD_RDSR); retval = (uint8_t)spi_read(); spi_stop(); if (timer_read32() >= deadline) { response = FLASH_STATUS_TIMEOUT; break; } } while (retval & FLASH_FLAG_WIP); return response; } static flash_status_t spi_flash_write_enable(void) { bool res = spi_flash_start(); if (!res) { dprint("Failed to start SPI! [spi flash write enable]\n"); return FLASH_STATUS_ERROR; } spi_write(FLASH_CMD_WREN); spi_stop(); return FLASH_STATUS_SUCCESS; } static flash_status_t spi_flash_write_disable(void) { bool res = spi_flash_start(); if (!res) { dprint("Failed to start SPI! [spi flash write disable]\n"); return FLASH_STATUS_ERROR; } spi_write(FLASH_CMD_WRDI); spi_stop(); return FLASH_STATUS_SUCCESS; } /* This function is used for read transfer, write transfer and erase transfer. */ static flash_status_t spi_flash_transaction(uint8_t cmd, uint32_t addr, uint8_t *data, size_t len) { flash_status_t response = FLASH_STATUS_SUCCESS; uint8_t buffer[EXTERNAL_FLASH_ADDRESS_SIZE + 1]; buffer[0] = cmd; for (int i = 0; i < EXTERNAL_FLASH_ADDRESS_SIZE; ++i) { buffer[EXTERNAL_FLASH_ADDRESS_SIZE - i] = addr & 0xFF; addr >>= 8; } bool res = spi_flash_start(); if (!res) { dprint("Failed to start SPI! [spi flash transmit]\n"); return FLASH_STATUS_ERROR; } response = spi_transmit(buffer, sizeof(buffer)); if ((!response) && (data != NULL)) { switch (cmd) { case FLASH_CMD_READ: response = spi_receive(data, len); break; case FLASH_CMD_PP: response = spi_transmit(data, len); break; default: response = FLASH_STATUS_ERROR; break; } } spi_stop(); return response; } void flash_init(void) { spi_init(); } flash_status_t flash_erase_chip(void) { flash_status_t response = FLASH_STATUS_SUCCESS; /* Wait for the write-in-progress bit to be cleared. */ response = spi_flash_wait_while_busy(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to check WIP flag! [spi flash erase chip]\n"); return response; } /* Enable writes. */ response = spi_flash_write_enable(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to write-enable! [spi flash erase chip]\n"); return response; } /* Erase Chip. */ bool res = spi_flash_start(); if (!res) { dprint("Failed to start SPI! [spi flash erase chip]\n"); return FLASH_STATUS_ERROR; } spi_write(FLASH_CMD_CE); spi_stop(); /* Wait for the write-in-progress bit to be cleared.*/ response = spi_flash_wait_while_busy(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to check WIP flag! [spi flash erase chip]\n"); return response; } return response; } flash_status_t flash_erase_sector(uint32_t addr) { flash_status_t response = FLASH_STATUS_SUCCESS; /* Check that the address exceeds the limit. */ if ((addr + (EXTERNAL_FLASH_SECTOR_SIZE)) >= (EXTERNAL_FLASH_SIZE) || ((addr % (EXTERNAL_FLASH_SECTOR_SIZE)) != 0)) { dprintf("Flash erase sector address over limit! [addr:0x%x]\n", (uint32_t)addr); return FLASH_STATUS_ERROR; } /* Wait for the write-in-progress bit to be cleared. */ response = spi_flash_wait_while_busy(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to check WIP flag! [spi flash erase sector]\n"); return response; } /* Enable writes. */ response = spi_flash_write_enable(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to write-enable! [spi flash erase sector]\n"); return response; } /* Erase Sector. */ response = spi_flash_transaction(FLASH_CMD_SE, addr, NULL, 0); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to erase sector! [spi flash erase sector]\n"); return response; } /* Wait for the write-in-progress bit to be cleared.*/ response = spi_flash_wait_while_busy(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to check WIP flag! [spi flash erase sector]\n"); return response; } return response; } flash_status_t flash_erase_block(uint32_t addr) { flash_status_t response = FLASH_STATUS_SUCCESS; /* Check that the address exceeds the limit. */ if ((addr + (EXTERNAL_FLASH_BLOCK_SIZE)) >= (EXTERNAL_FLASH_SIZE) || ((addr % (EXTERNAL_FLASH_BLOCK_SIZE)) != 0)) { dprintf("Flash erase block address over limit! [addr:0x%x]\n", (uint32_t)addr); return FLASH_STATUS_ERROR; } /* Wait for the write-in-progress bit to be cleared. */ response = spi_flash_wait_while_busy(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to check WIP flag! [spi flash erase block]\n"); return response; } /* Enable writes. */ response = spi_flash_write_enable(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to write-enable! [spi flash erase block]\n"); return response; } /* Erase Block. */ response = spi_flash_transaction(FLASH_CMD_BE, addr, NULL, 0); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to erase block! [spi flash erase block]\n"); return response; } /* Wait for the write-in-progress bit to be cleared.*/ response = spi_flash_wait_while_busy(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to check WIP flag! [spi flash erase block]\n"); return response; } return response; } flash_status_t flash_read_block(uint32_t addr, void *buf, size_t len) { flash_status_t response = FLASH_STATUS_SUCCESS; uint8_t * read_buf = (uint8_t *)buf; /* Wait for the write-in-progress bit to be cleared. */ response = spi_flash_wait_while_busy(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to check WIP flag! [spi flash read block]\n"); memset(read_buf, 0, len); return response; } /* Perform read. */ response = spi_flash_transaction(FLASH_CMD_READ, addr, read_buf, len); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to read block! [spi flash read block]\n"); memset(read_buf, 0, len); return response; } #if defined(CONSOLE_ENABLE) && defined(DEBUG_FLASH_SPI_OUTPUT) dprintf("[SPI FLASH R] 0x%08lX: ", addr); for (size_t i = 0; i < len; ++i) { dprintf(" %02X", (int)(((uint8_t *)read_buf)[i])); } dprintf("\n"); #endif // DEBUG_FLASH_SPI_OUTPUT return response; } flash_status_t flash_write_block(uint32_t addr, const void *buf, size_t len) { flash_status_t response = FLASH_STATUS_SUCCESS; uint8_t * write_buf = (uint8_t *)buf; while (len > 0) { uint32_t page_offset = addr % EXTERNAL_FLASH_PAGE_SIZE; size_t write_length = EXTERNAL_FLASH_PAGE_SIZE - page_offset; if (write_length > len) { write_length = len; } /* Wait for the write-in-progress bit to be cleared. */ response = spi_flash_wait_while_busy(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to check WIP flag! [spi flash write block]\n"); return response; } /* Enable writes. */ response = spi_flash_write_enable(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to write-enable! [spi flash write block]\n"); return response; } #if defined(CONSOLE_ENABLE) && defined(DEBUG_FLASH_SPI_OUTPUT) dprintf("[SPI FLASH W] 0x%08lX: ", addr); for (size_t i = 0; i < write_length; i++) { dprintf(" %02X", (int)(uint8_t)(write_buf[i])); } dprintf("\n"); #endif // DEBUG_FLASH_SPI_OUTPUT /* Perform the write. */ response = spi_flash_transaction(FLASH_CMD_PP, addr, write_buf, write_length); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to write block! [spi flash write block]\n"); return response; } write_buf += write_length; addr += write_length; len -= write_length; } /* Wait for the write-in-progress bit to be cleared. */ response = spi_flash_wait_while_busy(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to check WIP flag! [spi flash write block]\n"); return response; } /* Disable writes. */ response = spi_flash_write_disable(); if (response != FLASH_STATUS_SUCCESS) { dprint("Failed to write-disable! [spi flash write block]\n"); return response; } return response; }