/* * Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF * * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation, * incorporation in a commercial product or incorporation into a product or project which allows commercial use. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "hardware/SPI_Flash.h" #include "interfaces/gpio.h" #include #include "main.h" // private functions static bool spi_flash_busy(void); static void spi_flash_setWriteEnable(bool cmd); static inline void spi_flash_enable(void); static inline void spi_flash_disable(void); #if defined(PLATFORM_MD9600) #define HANDLE_SPI hspi2 __attribute__((section(".data.$RAM2"))) uint8_t SPI_Flash_sectorbuffer[4096]; #elif defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) #define HANDLE_SPI hspi1 __attribute__((section(".ccmram"))) uint8_t SPI_Flash_sectorbuffer[4096]; #else #error SPI FLASH: unsupported platform #endif // COMMANDS. Not all implemented or used #define W_EN 0x06 // write enable #define W_DE 0x04 // write disable #define R_SR1 0x05 // read status register 1 #define W_SR1 0x01 // write status register 1 #define R_SR2 0x35 // read status register 2 #define W_SR2 0x31 // write status register 2 #define R_SR3 0x15 // read status register 3 #define W_SR3 0x11 // write status register 3 #define PAGE_PGM 0x02 // page program #define QPAGE_PGM 0x32 // quad input page program #define BLK_E_64K 0xD8 // block erase 64KB #define BLK_E_32K 0x52 // block erase 32KB #define SECTOR_E 0x20 // sector erase 4KB #define CHIP_ERASE 0xc7 // chip erase #define CHIP_ERASE2 0x60 // same as CHIP_ERASE #define E_SUSPEND 0x75 // erase suspend #define E_RESUME 0x7a // erase resume #define PWR_DWN 0xb9 // power down #define HIGH_PERF_M 0xa3 // high performance mode #define CONT_R_RST 0xff // continuous read mode reset #define RELEASE 0xab // release power down or HPM/Dev ID (deprecated) #define R_MANUF_ID 0x90 // read Manufacturer and Dev ID (deprecated) #define R_UNIQUE_ID 0x4b // read unique ID (suggested) #define R_JEDEC_ID 0x9f // read JEDEC ID = Manuf+ID (suggested) #define READ_DATA 0x03 // read one or more bytes #define FAST_READ 0x0b // read one or more bytes at highest possible frequency #define R_SEC_REGS 0x48 //read security registers #define WINBOND_MANUF 0xef uint32_t flashChipPartNumber; bool SPI_Flash_init(void) { HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port, SPI_Flash_CS_Pin, GPIO_PIN_SET); // Disable flashChipPartNumber = SPI_Flash_readPartID(); // 4014 25Q80 8M bits 1M bytes, used in the GD-77 // 4015 25Q16 16M bits 2M bytes, used in the Baofeng DM-1801 ? // 4017 25Q64 64M bits. Used in Roger's special GD-77 radios modified on the TYT production line // 4018 25Q128 128M bits. MD9600 / MDUV380 / MD380 etc return (flashChipPartNumber == 0x4018); } // Returns false for failed // Note. There is no error checking that the device is not initially busy. bool SPI_Flash_read(uint32_t addr, uint8_t *dataBuf, int size) { uint8_t commandBuf[4]= { READ_DATA, addr >> 16, addr >> 8, addr };// command spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, commandBuf, 4, HAL_MAX_DELAY); HAL_SPI_Receive(&HANDLE_SPI, dataBuf, size, HAL_MAX_DELAY); spi_flash_disable(); return true; } bool SPI_Flash_write(uint32_t addr, uint8_t *dataBuf, int size) { bool retVal = true; int flashWritePos = addr; int flashSector; int flashEndSector; int bytesToWriteInCurrentSector = size; flashSector = flashWritePos / 4096; flashEndSector = (flashWritePos + size) / 4096; if (flashSector != flashEndSector) { bytesToWriteInCurrentSector = (flashEndSector * 4096) - flashWritePos; } if (bytesToWriteInCurrentSector != 4096) { SPI_Flash_read(flashSector * 4096, SPI_Flash_sectorbuffer, 4096); } uint8_t *writePos = SPI_Flash_sectorbuffer + flashWritePos - (flashSector * 4096); memcpy(writePos, dataBuf, bytesToWriteInCurrentSector); retVal = SPI_Flash_eraseSector(flashSector * 4096); if (!retVal) { return false; } for (int i = 0; i < 16; i++) { retVal = SPI_Flash_writePage(flashSector * 4096 + i * 256, SPI_Flash_sectorbuffer + i * 256); if (!retVal) { return false; } } if (flashSector != flashEndSector) { uint8_t *bufPusOffset = (uint8_t *) dataBuf + bytesToWriteInCurrentSector; bytesToWriteInCurrentSector = size - bytesToWriteInCurrentSector; SPI_Flash_read(flashEndSector * 4096, SPI_Flash_sectorbuffer, 4096); memcpy(SPI_Flash_sectorbuffer, (uint8_t *) bufPusOffset, bytesToWriteInCurrentSector); retVal = SPI_Flash_eraseSector(flashEndSector * 4096); if (!retVal) { return false; } for (int i = 0; i < 16; i++) { retVal = SPI_Flash_writePage(flashEndSector * 4096 + i * 256, SPI_Flash_sectorbuffer + i * 256); if (!retVal) { return false; } } } return true; } uint32_t SPI_Flash_readStatusRegisters(void) { uint8_t cmdVal = R_SR1; uint8_t r1 = 0x0, r2 = 0x0, r3 = 0x0; spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, &cmdVal, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&HANDLE_SPI, &r1, 1, HAL_MAX_DELAY); spi_flash_disable(); cmdVal = R_SR2; spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, &cmdVal, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&HANDLE_SPI, &r2, 1, HAL_MAX_DELAY); spi_flash_disable(); cmdVal = R_SR3; spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, &cmdVal, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&HANDLE_SPI, &r3, 1, HAL_MAX_DELAY); spi_flash_disable(); return (r3 << 16) | (r2 << 8) | r1; } uint8_t SPI_Flash_readManufacturer(void) { uint8_t commandBuf[4] = { R_JEDEC_ID, 0x00, 0x00, 0x00 }; uint8_t recBuf[4]; spi_flash_enable(); HAL_SPI_TransmitReceive(&HANDLE_SPI, commandBuf, recBuf, 4, HAL_MAX_DELAY); spi_flash_disable(); return recBuf[1]; } uint32_t SPI_Flash_readPartID(void) { uint8_t commandBuf[4] = { R_JEDEC_ID, 0x00, 0x00, 0x00 }; uint8_t recBuf[4]; spi_flash_enable(); HAL_SPI_TransmitReceive(&HANDLE_SPI, commandBuf, recBuf, 4, HAL_MAX_DELAY); spi_flash_disable(); return (recBuf[2] << 8) | recBuf[3]; } bool SPI_Flash_writePage(uint32_t addr_start,uint8_t *dataBuf) { bool isBusy; int waitCounter = 5;// Worst case is something like 3mS uint8_t commandBuf[4]= { PAGE_PGM, addr_start >> 16, addr_start >> 8, 0x00 } ; spi_flash_setWriteEnable(true); spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, commandBuf, 4, HAL_MAX_DELAY); HAL_SPI_Transmit(&HANDLE_SPI, dataBuf, 0x100, HAL_MAX_DELAY); spi_flash_disable(); do { osDelay(1); isBusy = spi_flash_busy(); } while ((waitCounter-- > 0) && isBusy); return !isBusy; } // Returns true if erased and false if failed. bool SPI_Flash_eraseSector(uint32_t addr_start) { int waitCounter = 500;// erase can take up to 500 mS bool isBusy; uint8_t commandBuf[4] = { SECTOR_E, addr_start >> 16, addr_start >> 8, 0x00 }; spi_flash_setWriteEnable(true); // it calls spi_flash_{enable/disable}() by itself spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, commandBuf, 4, HAL_MAX_DELAY); spi_flash_disable(); do { osDelay(1); isBusy = spi_flash_busy(); } while ((waitCounter-- > 0) && isBusy); return !isBusy;// If still busy after } static inline void spi_flash_enable(void) { HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port, SPI_Flash_CS_Pin, GPIO_PIN_RESET); } static void spi_flash_disable(void) { HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port, SPI_Flash_CS_Pin, GPIO_PIN_SET); } static bool spi_flash_busy(void) { uint8_t r1; uint8_t cmdVal = R_SR1; spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, &cmdVal, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&HANDLE_SPI, &r1, 1, HAL_MAX_DELAY); spi_flash_disable(); return (r1 & SR_BUSY); } static void spi_flash_setWriteEnable(bool cmd) { uint8_t cmdValue = (cmd ? W_EN : W_DE); spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, &cmdValue, 1, HAL_MAX_DELAY); spi_flash_disable(); } bool SPI_Flash_readSecurityRegisters(int startBlock, uint8_t *dataBuf, int size) { const uint32_t addrs[] = { 0x1000, 0x2000, 0x3000 }; const int securityBlockSize = 256; int numberofblocks = size / securityBlockSize; for (uint8_t i = startBlock; i < numberofblocks + 1; i++) { uint32_t addr = addrs[i]; uint8_t commandBuf[5] = { R_SEC_REGS, ((addr >> 16) & 0xFF), ((addr >> 8) & 0xFF), (addr & 0xFF), 0x00 }; spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, commandBuf, 5, HAL_MAX_DELAY); HAL_SPI_Receive(&HANDLE_SPI, dataBuf + ((i - startBlock) * securityBlockSize), securityBlockSize, HAL_MAX_DELAY); spi_flash_disable(); } return true; } uint8_t SPI_Flash_readSingleSecurityRegister(int addr) { uint8_t value; uint8_t commandBuf[5] = { R_SEC_REGS, ((addr >> 16) & 0xFF), ((addr >> 8) & 0xFF), (addr & 0xFF), 0x00 }; spi_flash_enable(); HAL_SPI_Transmit(&HANDLE_SPI, commandBuf, 5, HAL_MAX_DELAY); HAL_SPI_Receive(&HANDLE_SPI, &value, 1, HAL_MAX_DELAY); spi_flash_disable(); return value; } #if 0 bool SPI_Flash_StateIsBusy(void) { HAL_SPI_StateTypeDef state = HAL_SPI_GetState(&HANDLE_SPI); return ((state == HAL_SPI_STATE_BUSY) || (state == HAL_SPI_STATE_BUSY_TX) || (state == HAL_SPI_STATE_BUSY_RX) || (state == HAL_SPI_STATE_BUSY_TX_RX)); } #endif