/*****************************************************************************/ /* Copyright (C) 2024-25 Sathish Kumar P - All Rights Reserved */ /* You may use, distribute and modify this code under the */ /* terms of the XYZ license, which unfortunately wont be */ /* written for another century. */ /* */ /* You should have received a copy of the XYZ license with */ /* this file. If not, please write to: sathishembeddedgeek@gmail.com, */ /* or visit : */ /*****************************************************************************/ /*****************************************************************************/ /* FILENAME : App_Flash.c */ /* */ /* DESCRIPTION : Application flash interface */ /* */ /* NOTES : Copyright Sathish Kumar P. All rights reserved. */ /* */ /* AUTHOR : Sathish Kumar */ /* sathishembeddedgeek@gmail.com */ /* */ /* START DATE : 20th December 2024 */ /* */ /* VERSION DATE WHO DETAIL */ /* 00.00.01 20DEC24 Sathish Kumar initial version */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* I N C L U D E S */ /* */ /*****************************************************************************/ #include "App_Flash.h" /* FES Common Lib Headers.*/ #include #if (HW_TYPE == HW_DEV_BRD) #include "n25q128a.h" #elif (HW_TYPE == HW_OWN_BRD) #warning "Invalid HW Selected!!!" #else #warning "Invalid HW Selected!!!" #endif /*****************************************************************************/ /* */ /* D E F I N I T I O N S */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* C O N S T A N T S & V A R I A B L E S */ /* */ /*****************************************************************************/ static volatile uint8_t cmd_it_cb_flag; static volatile uint8_t rx_it_cb_flag; static volatile uint8_t tx_it_cb_flag; static volatile uint8_t status_it_cb_flag; QSPI_CommandTypeDef s_command; /*****************************************************************************/ /* */ /* F U N C T I O N P R O T O T Y P E S */ /* */ /*****************************************************************************/ static uint8_t QSPI_ResetMemory(QSPI_HandleTypeDef *hqspi); static uint8_t QSPI_DummyCyclesCfg(QSPI_HandleTypeDef *hqspi); static uint8_t QSPI_WriteEnable(QSPI_HandleTypeDef *hqspi); static uint8_t QSPI_AutoPollingMemReady(QSPI_HandleTypeDef *hqspi, uint32_t Timeout); static inline uint8_t QSPI_SendCmd(QSPI_HandleTypeDef *hqspi,QSPI_CommandTypeDef *s_cmd); static inline uint8_t QSPI_TxData(QSPI_HandleTypeDef *hqspi,uint8_t *tx_data); static inline uint8_t QSPI_RxData(QSPI_HandleTypeDef *hqspi,uint8_t *rx_data); static inline uint8_t QSPI_AutoPolling(QSPI_HandleTypeDef *hqspi,QSPI_CommandTypeDef *s_cmd,QSPI_AutoPollingTypeDef *s_cfg,uint32_t timeout); static uint8_t QSPI_EnableQSPIMode(QSPI_HandleTypeDef *hqspi); /*****************************************************************************/ /* */ /* F U N C T I O N S */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ uint8_t App_Flash_Init() { /* QSPI memory reset */ if (QSPI_ResetMemory(FLASH_QSPI_HDL) != 0) { return HAL_ERROR; } if (QSPI_EnableQSPIMode(FLASH_QSPI_HDL) != 0) { return HAL_ERROR; } /* Configuration of the dummy cycles on QSPI memory side */ if (QSPI_DummyCyclesCfg(FLASH_QSPI_HDL) != 0) { return HAL_ERROR; } return HAL_OK; } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ uint8_t App_Flash_HW_Test() { uint8_t writebuf[] = "Hello world from QSPI"; uint8_t readbuf[50]; uint16_t wrlen = strlen((char*)writebuf); if(App_Flash_Erase_Block(0)!=0) { LOG(LOG_ERROR, "App_Flash_Erase_Block Failed !!!"); return 1; } if(App_Flash_Write(writebuf, 0, wrlen)!=0) { LOG(LOG_ERROR, "App_Flash_Write Failed !!!"); return 2; } if(App_Flash_Read(readbuf, 0,wrlen)!=0) { LOG(LOG_ERROR, "App_Flash_Read Failed !!!"); return 3; } if(strncmp((char*)readbuf,(char*)writebuf,wrlen)!=0) { LOG(LOG_ERROR, "content mismatch Failed !!!"); return 3; } return 0; } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ uint8_t App_Flash_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size) { #if (HW_TYPE == HW_DEV_BRD) /* Initialize the read command */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = QUAD_INOUT_FAST_READ_CMD; s_command.AddressMode = QSPI_ADDRESS_4_LINES; s_command.AddressSize = QSPI_ADDRESS_24_BITS; s_command.Address = ReadAddr; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_4_LINES; s_command.DummyCycles = N25Q128A_DUMMY_CYCLES_READ_QUAD; s_command.NbData = Size; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* Configure the command */ if (QSPI_SendCmd(FLASH_QSPI_HDL, &s_command) != HAL_OK) { return HAL_ERROR; } /* Set S# timing for Read command: Min 20ns for N25Q128A memory */ MODIFY_REG(hqspi.Instance->DCR, QUADSPI_DCR_CSHT, QSPI_CS_HIGH_TIME_2_CYCLE); /* Reception of the data */ if (QSPI_RxData(FLASH_QSPI_HDL, pData) != HAL_OK) { return HAL_ERROR; } /* Restore S# timing for nonRead commands */ MODIFY_REG(hqspi.Instance->DCR, QUADSPI_DCR_CSHT, QSPI_CS_HIGH_TIME_5_CYCLE); return HAL_OK; #else return HAL_ERROR; #endif } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ uint8_t App_Flash_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size) { uint32_t end_addr, current_size, current_addr; /* Calculation of the size between the write address and the end of the page */ current_size = APP_FLASH_PAGE_SIZE - (WriteAddr % APP_FLASH_PAGE_SIZE); /* Check if the size of the data is less than the remaining place in the page */ if (current_size > Size) { current_size = Size; } /* Initialize the address variables */ current_addr = WriteAddr; end_addr = WriteAddr + Size; #if (HW_TYPE == HW_DEV_BRD) /* Initialize the program command */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = EXT_QUAD_IN_FAST_PROG_CMD; s_command.AddressMode = QSPI_ADDRESS_4_LINES; s_command.AddressSize = QSPI_ADDRESS_24_BITS; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_4_LINES; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; #endif /* Perform the write page by page */ do { #if (HW_TYPE == HW_OWN_BRD) #else s_command.Address = current_addr; #endif s_command.NbData = current_size; /* Enable write operations */ if (QSPI_WriteEnable(FLASH_QSPI_HDL) != HAL_OK) { return HAL_ERROR; } /* Configure the command */ if (QSPI_SendCmd(FLASH_QSPI_HDL, &s_command) != HAL_OK) { return HAL_ERROR; } /* Transmission of the data */ if (QSPI_TxData(FLASH_QSPI_HDL, pData) != HAL_OK) { return HAL_ERROR; } /* Configure automatic polling mode to wait for end of program */ if (QSPI_AutoPollingMemReady(FLASH_QSPI_HDL, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != 0) { return HAL_ERROR; } /* Update the address and size variables for next page programming */ current_addr += current_size; pData += current_size; current_size = ((current_addr + APP_FLASH_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : APP_FLASH_PAGE_SIZE; } while (current_addr < end_addr); return HAL_OK; } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ uint8_t App_Flash_Erase_Block(uint32_t BlockAddress) { #if (HW_TYPE == HW_DEV_BRD) /* Initialize the erase command */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = SUBSECTOR_ERASE_CMD; s_command.AddressMode = QSPI_ADDRESS_1_LINE; s_command.AddressSize = QSPI_ADDRESS_24_BITS; s_command.Address = BlockAddress; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_NONE; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* Enable write operations */ if (QSPI_WriteEnable(FLASH_QSPI_HDL) != HAL_OK) { return HAL_ERROR; } /* Send the command */ if (QSPI_SendCmd(FLASH_QSPI_HDL, &s_command) != HAL_OK) { return HAL_ERROR; } /* Configure automatic polling mode to wait for end of erase */ if (QSPI_AutoPollingMemReady(FLASH_QSPI_HDL, N25Q128A_SUBSECTOR_ERASE_MAX_TIME) != 0) { return HAL_ERROR; } return HAL_OK; #elif (HW_TYPE == HW_OWN_BRD) return HAL_ERROR; #else return HAL_ERROR; #endif } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ uint8_t App_Flash_Erase_Chip(void) { #if (HW_TYPE == HW_DEV_BRD) /* Initialize the erase command */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = BULK_ERASE_CMD; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_NONE; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* Enable write operations */ if (QSPI_WriteEnable(FLASH_QSPI_HDL) != 0) { return HAL_ERROR; } /* Send the command */ if (QSPI_SendCmd(FLASH_QSPI_HDL, &s_command) != HAL_OK) { return HAL_ERROR; } /* Configure automatic polling mode to wait for end of erase */ if (QSPI_AutoPollingMemReady(FLASH_QSPI_HDL, N25Q128A_BULK_ERASE_MAX_TIME) != 0) { return HAL_ERROR; } return HAL_OK; #else return HAL_ERROR; #endif } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ uint8_t App_Flash_GetStatus(void) { #if (HW_TYPE == HW_DEV_BRD) uint8_t reg; /* Initialize the read flag status register command */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = READ_FLAG_STATUS_REG_CMD; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_1_LINE; s_command.DummyCycles = 0; s_command.NbData = 1; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* Configure the command */ if (QSPI_SendCmd(FLASH_QSPI_HDL, &s_command) != HAL_OK) { return HAL_ERROR; } /* Reception of the data */ if (QSPI_RxData(FLASH_QSPI_HDL, ®) != HAL_OK) { return HAL_ERROR; } /* Check the value of the register */ if ((reg & (N25Q128A_FSR_PRERR | N25Q128A_FSR_VPPERR | N25Q128A_FSR_PGERR | N25Q128A_FSR_ERERR)) != 0) { return HAL_ERROR; } else if ((reg & (N25Q128A_FSR_PGSUS | N25Q128A_FSR_ERSUS)) != 0) { return HAL_TIMEOUT; } else if ((reg & N25Q128A_FSR_READY) != 0) { return HAL_OK; } else { return HAL_BUSY; } #elif (HW_TYPE == HW_OWN_BRD) return HAL_ERROR; #else return HAL_ERROR; #endif } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ uint8_t App_Flash_GetInfo(Flash_Info* pInfo) { /* Configure the structure with the memory configuration */ pInfo->FlashSize = APP_FLASH_SIZE; pInfo->EraseSectorSize = APP_FLASH_SUBSECTOR_SIZE; pInfo->EraseSectorsNumber = (APP_FLASH_SIZE/APP_FLASH_SUBSECTOR_SIZE); pInfo->ProgPageSize = APP_FLASH_PAGE_SIZE; pInfo->ProgPagesNumber = (APP_FLASH_SIZE/APP_FLASH_PAGE_SIZE); return HAL_OK; } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ static inline uint8_t QSPI_SendCmd(QSPI_HandleTypeDef *hqspi,QSPI_CommandTypeDef *s_cmd) { return HAL_QSPI_Command(hqspi, s_cmd,HAL_QPSI_TIMEOUT_DEFAULT_VALUE); } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ static inline uint8_t QSPI_TxData(QSPI_HandleTypeDef *hqspi,uint8_t *tx_data) { return HAL_QSPI_Transmit(hqspi, tx_data,HAL_QPSI_TIMEOUT_DEFAULT_VALUE); } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ static inline uint8_t QSPI_RxData(QSPI_HandleTypeDef *hqspi,uint8_t *rx_data) { return HAL_QSPI_Receive(hqspi, rx_data,HAL_QPSI_TIMEOUT_DEFAULT_VALUE); } /*****************************************************************************/ /* See header file of description */ /*****************************************************************************/ static inline uint8_t QSPI_AutoPolling(QSPI_HandleTypeDef *hqspi,QSPI_CommandTypeDef *s_cmd,QSPI_AutoPollingTypeDef *s_cfg,uint32_t timeout) { return HAL_QSPI_AutoPolling(hqspi, s_cmd, s_cfg,timeout); } /** * @brief This function reset the QSPI memory. * @param hqspi: QSPI handle */ static uint8_t QSPI_EnableQSPIMode(QSPI_HandleTypeDef *hqspi) { #if (HW_TYPE == HW_OWN_BRD) #else return HAL_OK; #endif } /** * @brief This function reset the QSPI memory. * @param hqspi: QSPI handle */ static uint8_t QSPI_ResetMemory(QSPI_HandleTypeDef *hqspi) { #if (HW_TYPE == HW_DEV_BRD) /* Initialize the reset enable command */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = RESET_ENABLE_CMD; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_NONE; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* Send the command */ if (QSPI_SendCmd(hqspi, &s_command) != HAL_OK) { return HAL_ERROR; } /* Send the reset memory command */ s_command.Instruction = RESET_MEMORY_CMD; if (QSPI_SendCmd(hqspi, &s_command) != HAL_OK) { return HAL_ERROR; } /* Configure automatic polling mode to wait the memory is ready */ if (QSPI_AutoPollingMemReady(hqspi, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != 0) { return HAL_ERROR; } return HAL_OK; #elif (HW_TYPE == HW_OWN_BRD) return HAL_ERROR; #else #endif } /** * @brief This function configure the dummy cycles on memory side. * @param hqspi: QSPI handle */ static uint8_t QSPI_DummyCyclesCfg(QSPI_HandleTypeDef *hqspi) { #if (HW_TYPE == HW_DEV_BRD) uint8_t reg; /* Initialize the read volatile configuration register command */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = READ_VOL_CFG_REG_CMD; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_1_LINE; s_command.DummyCycles = 0; s_command.NbData = 1; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* Configure the command */ if (QSPI_SendCmd(hqspi, &s_command) != HAL_OK) { return HAL_ERROR; } /* Reception of the data */ if (HAL_QSPI_Receive(hqspi, ®,HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return HAL_ERROR; } /* Enable write operations */ if (QSPI_WriteEnable(hqspi) != 0) { return HAL_ERROR; } /* Update volatile configuration register (with new dummy cycles) */ s_command.Instruction = WRITE_VOL_CFG_REG_CMD; MODIFY_REG(reg, N25Q128A_VCR_NB_DUMMY, (N25Q128A_DUMMY_CYCLES_READ_QUAD << POSITION_VAL(N25Q128A_VCR_NB_DUMMY))); /* Configure the write volatile configuration register command */ if (QSPI_SendCmd(hqspi, &s_command) != HAL_OK) { return HAL_ERROR; } /* Transmission of the data */ if (QSPI_TxData(hqspi, ®) != HAL_OK) { return HAL_ERROR; } #endif return HAL_OK; } /** * @brief This function send a Write Enable and wait it is effective. * @param hqspi: QSPI handle */ static uint8_t QSPI_WriteEnable(QSPI_HandleTypeDef *hqspi) { QSPI_CommandTypeDef s_command; QSPI_AutoPollingTypeDef s_config; /* Enable write operations */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = WRITE_ENABLE_CMD; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_NONE; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; if (QSPI_SendCmd(hqspi, &s_command) != HAL_OK) { return HAL_ERROR; } #if (HW_TYPE == HW_DEV_BRD) /* Configure automatic polling mode to wait for write enabling */ s_config.Match = N25Q128A_SR_WREN; s_config.Mask = N25Q128A_SR_WREN; s_config.MatchMode = QSPI_MATCH_MODE_AND; s_config.StatusBytesSize = 1; s_config.Interval = 0x10; s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE; s_command.Instruction = READ_STATUS_REG_CMD; s_command.DataMode = QSPI_DATA_1_LINE; if (QSPI_AutoPolling(hqspi, &s_command, &s_config,HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return HAL_ERROR; } return HAL_OK; #elif (HW_TYPE == HW_OWN_BRD) return HAL_ERROR; #endif } /** * @brief This function read the SR of the memory and wait the EOP. * @param hqspi: QSPI handle * @param Timeout */ static uint8_t QSPI_AutoPollingMemReady(QSPI_HandleTypeDef *hqspi, uint32_t Timeout) { QSPI_CommandTypeDef s_command; QSPI_AutoPollingTypeDef s_config; #if (HW_TYPE == HW_DEV_BRD) /* Configure automatic polling mode to wait for memory ready */ s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; s_command.Instruction = READ_STATUS_REG_CMD; s_command.AddressMode = QSPI_ADDRESS_NONE; s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; s_command.DataMode = QSPI_DATA_1_LINE; s_command.DummyCycles = 0; s_command.DdrMode = QSPI_DDR_MODE_DISABLE; s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; s_config.Match = 0; s_config.Mask = N25Q128A_SR_WIP; s_config.MatchMode = QSPI_MATCH_MODE_AND; s_config.StatusBytesSize = 1; s_config.Interval = 0x10; s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE; #elif (HW_TYPE == HW_OWN_BRD) #endif if (QSPI_AutoPolling(hqspi, &s_command, &s_config,Timeout) != HAL_OK) { return HAL_ERROR; } return HAL_OK; } /*****************************************************************************/ /* */ /* E N D O F F I L E */ /* */ /*****************************************************************************/