Hi there,
I am trying to interface at25xe321d with QSPI but it always end up returning zeros. Please find attached code and dts file for reference. Thanks Moh
/************************************************************************************************** * SYSTEM INCLUDE FILES **************************************************************************************************/ #include <hal/nrf_gpio.h> #include <hal/nrf_qspi.h> #include <nrfx_qspi.h> #include <zephyr/device.h> #include <zephyr/kernel.h> #include <zephyr/logging/log.h> /************************************************************************************************** * NON-SYSTEM INCLUDE FILES **************************************************************************************************/ #include "hal_gpio.h" #include "hal_qspi.h" /************************************************************************************************** * MACRO DEFINITIONS **************************************************************************************************/ /* No macro definitions in this file */ /************************************************************************************************** * LOG MODULE **************************************************************************************************/ LOG_MODULE_REGISTER(hal_qspi, LOG_LEVEL_INF); /************************************************************************************************** * PRIVATE VARIABLES **************************************************************************************************/ static bool qspi_initialized = false; /************************************************************************************************** * INTERRUPT/CALLBACK HANDLER FUNCTIONS **************************************************************************************************/ /** * @brief QSPI event handler. * * This function is called when a QSPI event occurs. * * @param event The QSPI event that occurred. * @param p_context The context data for the event. */ static void qspi_handler(nrfx_qspi_evt_t event, void* p_context) { ARG_UNUSED(event); ARG_UNUSED(p_context); } /************************************************************************************************** * PRIVATE FUNCTION DEFINITIONS **************************************************************************************************/ /* No private functions in this file */ /************************************************************************************************** * PUBLIC FUNCTION DEFINITIONS **************************************************************************************************/ /** * @brief Initialize the QSPI interface. * * This function initializes the QSPI interface and configures the QSPI peripheral for operation. * * @return 0 on success, or a negative error code on failure. */ int hal_qspi_init(void) { if (qspi_initialized) { return 0; } nrfx_qspi_config_t qspi_cfg = { .pins = { .sck_pin = NRF_GPIO_PIN_MAP((qspi_pins[4].port == NRF_P1 ? 1 : 0), qspi_pins[4].pin), // SCK .csn_pin = NRF_GPIO_PIN_MAP((qspi_pins[0].port == NRF_P1 ? 1 : 0), qspi_pins[0].pin), // CSN .io0_pin = NRF_GPIO_PIN_MAP((qspi_pins[3].port == NRF_P1 ? 1 : 0), qspi_pins[3].pin), // IO0 .io1_pin = NRF_GPIO_PIN_MAP((qspi_pins[1].port == NRF_P1 ? 1 : 0), qspi_pins[1].pin), // IO1 .io2_pin = NRF_GPIO_PIN_MAP((qspi_pins[2].port == NRF_P1 ? 1 : 0), qspi_pins[2].pin), // IO2 .io3_pin = NRF_GPIO_PIN_MAP((qspi_pins[5].port == NRF_P1 ? 1 : 0), qspi_pins[5].pin), // IO3 }, .prot_if = { .readoc = NRF_QSPI_READOC_READ4IO, // Quad I/O Read .writeoc = NRF_QSPI_WRITEOC_PP4O, // Quad I/O Page Program .addrmode = NRF_QSPI_ADDRMODE_24BIT, // 24-bit addressing .dpmconfig = true, // Deep Power-down supported }, .phy_if = { .sck_delay = 3, // Delay between edge of SCK and sampling .dpmen = false, .spi_mode = NRF_QSPI_MODE_0, // Mode 0 (CPOL=0, CPHA=0) .sck_freq = NRF_QSPI_FREQ_32MDIV4, // 16 MHz }, .irq_priority = NRFX_QSPI_DEFAULT_CONFIG_IRQ_PRIORITY, }; // Initialize QSPI with event handler nrfx_err_t ret = nrfx_qspi_init(&qspi_cfg, qspi_handler, NULL); if (ret != NRFX_SUCCESS) { LOG_ERR("QSPI initialization failed: %d", ret); return -EIO; } k_usleep(40); // Let Flash stabilize // Optional: Soft Reset AT25XE321D hal_qspi_cmd_write(0x66, NULL, 0); // Reset Enable hal_qspi_cmd_write(0x99, NULL, 0); // Reset Memory nrf_qspi_event_clear(NRF_QSPI, NRF_QSPI_EVENT_READY); nrf_qspi_task_trigger(NRF_QSPI, NRF_QSPI_TASK_ACTIVATE); while (!nrf_qspi_event_check(NRF_QSPI, NRF_QSPI_EVENT_READY)) { } qspi_initialized = true; LOG_INF("QSPI initialized successfully"); return 0; } /** * @brief Uninitialize the QSPI interface. * * This function uninitializes the QSPI interface and releases any resources used by the QSPI * peripheral. * * @return 0 on success, or a negative error code on failure. */ int hal_qspi_uninit(void) { if (!qspi_initialized) { return 0; } nrfx_qspi_uninit(); qspi_initialized = false; LOG_INF("QSPI deinitialized successfully"); return 0; } /** * @brief Read data from the QSPI flash memory. * * @param addr address in flash memory * @param buf pointer to buffer where data will be stored * @param len number of bytes to read * @return 0 on success, negative error code on failure */ int hal_qspi_read(uint32_t addr, void* buf, size_t len) { return (nrfx_qspi_read(buf, len, addr) == NRFX_SUCCESS) ? 0 : -EIO; } /** * @brief Write data to the QSPI flash memory. * * @param addr address in flash memory * @param data pointer to data to write * @param len number of bytes to write * @return 0 on success, negative error code on failure */ int hal_qspi_write(uint32_t addr, const void* data, size_t len) { return (nrfx_qspi_write(data, len, addr) == NRFX_SUCCESS) ? 0 : -EIO; } /** * @brief Wait for the QSPI flash memory to be ready for the next operation. * * @param timeout_us Timeout in microseconds for waiting. * @return 0 on success, or a negative error code on failure. */ int hal_qspi_wait_ready(uint32_t timeout_us) { while (timeout_us > 0) { if (!nrfx_qspi_mem_busy_check()) { return 0; } k_busy_wait(50); timeout_us -= 50; } return -ETIMEDOUT; } /** * @brief Send a single-byte command (no TX/RX data) * * @param opcode The QSPI command opcode to send. * @return 0 on success, negative error code on failure. */ int hal_qspi_send_cmd(uint8_t opcode) { nrf_qspi_cinstr_conf_t conf = { .opcode = opcode, .length = NRF_QSPI_CINSTR_LEN_1B, .io2_level = true, .io3_level = true, .wipwait = false, .wren = false, // No write enable needed }; return (nrfx_qspi_cinstr_xfer(&conf, NULL, NULL) == NRFX_SUCCESS) ? 0 : -EIO; } /** * @brief Send a command and receive data. * * @param opcode QSPI opcode to send * @param rx_buf Pointer to buffer where received data will be stored * @param rx_len Number of bytes to read * @return 0 on success, negative error code on failure */ int hal_qspi_cmd_read(uint8_t opcode, void* rx_buf, size_t rx_len) { if (!rx_buf || rx_len == 0 || rx_len > 8) return -EINVAL; nrf_qspi_cinstr_conf_t cinstr_cfg = { .opcode = opcode, .length = NRF_QSPI_CINSTR_LEN_1B + rx_len, .io2_level = true, .io3_level = true, .wipwait = true, .wren = false, // Not writing }; nrfx_err_t err_code = nrfx_qspi_cinstr_xfer(&cinstr_cfg, NULL, rx_buf); if (err_code != NRFX_SUCCESS) { return -EIO; } return 0; } /** * @brief Send a command with data. * * @param opcode QSPI opcode to send * @param tx_buf Pointer to buffer containing data to send * @param tx_len Number of bytes to send * @return 0 on success, negative error code on failure */ int hal_qspi_cmd_write(uint8_t opcode, const void* tx_buf, size_t tx_len) { if (!tx_buf || tx_len == 0 || tx_len > 8) return -EINVAL; nrf_qspi_cinstr_conf_t conf = { .opcode = opcode, .length = NRF_QSPI_CINSTR_LEN_1B + tx_len, .io2_level = true, .io3_level = true, .wipwait = true, .wren = true, // Writing to flash }; return (nrfx_qspi_cinstr_xfer(&conf, tx_buf, NULL) == NRFX_SUCCESS) ? 0 : -EIO; }
/************************************************************************************************** * SYSTEM INCLUDE FILES **************************************************************************************************/ #include <stdlib.h> #include <string.h> #include <zephyr/device.h> #include <zephyr/devicetree.h> #include <zephyr/kernel.h> #include <zephyr/logging/log.h> /************************************************************************************************** * NON-SYSTEM INCLUDE FILES **************************************************************************************************/ #include "at25xe321d.h" #include "hal_qspi.h" /************************************************************************************************** * MACRO DEFINITIONS **************************************************************************************************/ /** @brief AT25XE321D command set. */ #define AT25XE321D_CMD_READ_JEDEC_ID 0x94 // Read Manufacturer and Device ID. #define AT25XE321D_CMD_WRITE_ENABLE 0x06 // Enable write operations. #define AT25XE321D_CMD_WRITE_DISABLE 0x04 // Disable write operations. #define AT25XE321D_CMD_READ_STATUS 0x05 // Read the status register. #define AT25XE321D_CMD_WRITE_STATUS 0x01 // Write the status register. #define AT25XE321D_CMD_READ_DATA 0x03 // Read data from memory. #define AT25XE321D_CMD_FAST_READ_DATA 0x0B // Fast Read data from memory. #define AT25XE321D_CMD_PAGE_PROGRAM 0x02 // Write data to memory. #define AT25XE321D_CMD_PAGE_ERASE 0x81 // Erase a 256 bytes. #define AT25XE321D_CMD_SECTOR_ERASE 0x20 // Erase a 4KB sector. #define AT25XE321D_CMD_BLOCK_ERASE_32KB 0x52 // Erase a 32KB block. #define AT25XE321D_CMD_BLOCK_ERASE_64KB 0xD8 // Erase a 64KB block. #define AT25XE321D_CMD_CHIP_ERASE 0xC7 // Erase the entire chip. #define AT25XE321D_CMD_ENTER_POWER_DOWN 0xB9 // Enter deep power-down mode. #define AT25XE321D_CMD_RELEASE_POWER_DOWN 0xAB // Exit deep power-down mode. #define AT25XE321D_CMD_RESET_ENABLE 0x66 // Reset Enable command. #define AT25XE321D_CMD_RESET_MEMORY 0x99 // Reset Memory command. /** @brief Sizes for memory operations. */ #define AT25XE321D_FLASH_SIZE (4 * 1024 * 1024) // 4MB in bytes // AT25XE321D flash memory size #define AT25XE321D_PAGE_SIZE 256 // 256-Byte page #define AT25XE321D_SECTOR_SIZE 4096 // 4KB Block #define AT25XE321D_BLOCK_32KB_SIZE 32768 // 32KB Block #define AT25XE321D_BLOCK_64KB_SIZE 65536 // 64KB Block #define AT25XE321D_CHIP_SIZE 4194304 // Full Chip size (4MB) /** @brief AT25XE321D Max Address */ #define AT25XE321D_MAX_ADDRESS 0x3FFFFF // Maximum Flash address. /** @brief AT25XE321D Status Register bits. */ #define AT25XE321D_SR_WIP 0x01 // Write-In-Progress bit #define AT25XE321D_SR_WEL 0x02 // Write Enable Latch bit #define AT25XE321D_SR_BP0 0x04 // Block Protect bit 0 #define AT25XE321D_SR_BP1 0x08 // Block Protect bit 1 #define AT25XE321D_SR_BP2 0x10 // Block Protect bit 2 #define AT25XE321D_SR_SRWD 0x80 // Status Register Write Disable bit #define AT25XE321D_SR_BP_MASK 0x1C // Mask for Block Protect bits (BP0, BP1, BP2) /** @brief AT25XE321D Write Timeout */ #define AT25XE321D_WRITE_TIMEOUT_US 500000 /** @brief Expected Manufacturer and Device IDs for the AT25XE321D Flash. */ #define AT25XE321D_EXPECTED_MANUF_ID 0x1F #define AT25XE321D_EXPECTED_DEVICE_ID1 0x47 #define AT25XE321D_EXPECTED_DEVICE_ID2 0x0C #define AT25XE321D_EXPECTED_DEVICE_ID3 0x01 /************************************************************************************************** * LOG MODULE **************************************************************************************************/ LOG_MODULE_REGISTER(at25xe321d, LOG_LEVEL_INF); /************************************************************************************************** * PRIVATE VARIABLES **************************************************************************************************/ /** @brief Size of the AT25XE321D flash memory. */ static uint32_t at25xe321d_size = 0; /************************************************************************************************** * INTERRUPT/CALLBACK HANDLER FUNCTIONS **************************************************************************************************/ /* No interrupt/callback handlers in this file */ /************************************************************************************************** * PRIVATE FUNCTION DEFINITIONS **************************************************************************************************/ /* No private functions in this file */ /************************************************************************************************** * PUBLIC FUNCTION DEFINITIONS **************************************************************************************************/ /** * @brief Initializes the AT25XE321D Flash memory. * * Initializes the SPI driver and configures the flash memory for operation. * * @return 0 on success, -ENODEV if the initialization fails. */ int at25xe321d_init(void) { int ret; ret = hal_qspi_init(); if (ret < 0) { LOG_ERR("Failed to initialize QSPI: %d", ret); return ret; } uint8_t manuf_id, device_id1, device_id2, device_id3, ext_device_info; ret = at25xe321d_verify_id(&manuf_id, &device_id1, &device_id2, &device_id3, &ext_device_info); if (ret == 0) { // Successfully verified ID and extended information LOG_INF("ID verified: Manuf=0x%02X, Device1=0x%02X, Device2=0x%02X, Device3=0x%02X, " "Extended Info=0x%02X", manuf_id, device_id1, device_id2, device_id3, ext_device_info); } else { LOG_ERR("Failed to verify ID"); return ret; } LOG_INF("AT25XE321D Flash memory initialized successfully"); return 0; } /** * @brief Verifies the Manufacturer ID and Device ID of the AT25XE321D Flash. * * This function sends a command to the AT25XE321D Flash to read the Manufacturer ID, Device ID, * and any extended device information provided by the chip. It compares the returned values to * ensure the correct device is present and provides the extended device information if available. * * @param manuf_id Pointer to a variable where the Manufacturer ID will be stored. * @param device_id1 Pointer to a variable where the first byte of the Device ID will be stored. * @param device_id2 Pointer to a variable where the second byte of the Device ID will be stored. * @param device_id3 Pointer to a variable where the third byte of the Device ID will be stored. * @param ext_device_info Pointer to a buffer where the extended device information (up to 4 bytes) * will be stored. This data will be fetched starting from the 5th byte of the response. * * @return 0 on success, negative error code if the device ID does not match expected values or if * the read operation fails. * -EINVAL if the Manufacturer ID or Device ID is not as expected. */ int at25xe321d_verify_id(uint8_t* manuf_id, uint8_t* device_id1, uint8_t* device_id2, uint8_t* device_id3, uint8_t* ext_device_info) { int ret; uint8_t rx_data[6] = { 0 }; // Array to store manufacturer ID, device ID, and extended info ret = hal_qspi_cmd_read(AT25XE321D_CMD_READ_JEDEC_ID, rx_data, sizeof(rx_data)); if (ret < 0) return ret; // Parse the Manufacturer ID and Device ID *manuf_id = rx_data[1]; // Manufacturer ID is in the second byte *device_id1 = rx_data[2]; // Device ID byte 1 (third byte) *device_id2 = rx_data[3]; // Device ID byte 2 (fourth byte) *device_id3 = rx_data[4]; // Device ID byte 3 (fifth byte) *ext_device_info = rx_data[5]; // Device ID byte 4 extended information // Validate Manufacturer ID if (*manuf_id != AT25XE321D_EXPECTED_MANUF_ID) return -EINVAL; // Decode the capacity based on Device ID Part 2 if (*device_id1 == AT25XE321D_EXPECTED_DEVICE_ID1 && *device_id2 == AT25XE321D_EXPECTED_DEVICE_ID2 && *device_id3 == AT25XE321D_EXPECTED_DEVICE_ID3) { at25xe321d_size = 4 * 1024 * 1024; // 32 Mbit = 4 MB } else return -EINVAL; LOG_DBG("ID verified: Manuf=0x%02X, Device1=0x%02X, Device2=0x%02X, Device3=0x%02X, " "Extended Info=0x%02X", *manuf_id, *device_id1, *device_id2, *device_id3, *ext_device_info); return 0; } /** * @brief Get the memory size of the AT25XE321D flash device. * * This function returns the memory size of AT25XE321D device. * * @return Size of the memory in bytes. */ size_t at25xe321d_get_memory_size(void) { return at25xe321d_size; } /** * @brief Get the page size of the AT25XE321D flash device. * * This function returns the page size of AT25XE321D device. * * @return Size of the page in bytes. */ size_t at25xe321d_get_page_size(void) { return AT25XE321D_PAGE_SIZE; } /** * @brief Get the sector size of the AT25XE321D flash device. * * This function returns the sector size of AT25XE321D device. * * @return Size of the sector in bytes. */ size_t at25xe321d_get_sector_size(void) { return AT25XE321D_SECTOR_SIZE; } /** * @brief Writes data to the AT25XE321D flash memory. * * @param addr Address in flash memory to start writing to. * @param buf Pointer to the data buffer containing the data to write. * @param len Length of data to write in bytes (must not exceed the page size). * @return 0 on success, or an error code on failure. */ int at25xe321d_write(uint32_t addr, const uint8_t* buf, size_t len) { if (len == 0 || buf == NULL || len > AT25XE321D_PAGE_SIZE) { LOG_ERR("Invalid parameters for write operation"); return -EINVAL; } if ((addr % AT25XE321D_PAGE_SIZE) + len > AT25XE321D_PAGE_SIZE) { LOG_ERR("Write operation spans across page boundaries"); return -EINVAL; } if (addr >= AT25XE321D_FLASH_SIZE || (addr + len) > AT25XE321D_FLASH_SIZE) { LOG_ERR("Address out of range"); return -EINVAL; } int ret = at25xe321d_write_enable(); if (ret != 0) { LOG_ERR("Failed to enable write"); return ret; } ret = hal_qspi_write(addr, buf, len); if (ret < 0) return ret; // Timeout for page program const uint32_t page_program_timeout_us = 5000; // 5ms // Wait for the device to complete the programming operation ret = at25xe321d_wait_until_ready(page_program_timeout_us); if (ret != 0) { LOG_ERR("Device not ready after write at address 0x%06X", addr); return ret; } LOG_INF("Page programming successful: Address = 0x%06X, Size = %zu", addr, len); return 0; } /** * @brief Reads data from the AT25XE321D flash memory. * * @param addr Address in flash memory to start reading from. * @param buf Pointer to a buffer where the data will be stored. * @param len Length of data to read in bytes. * @return 0 on success, or an error code on failure. */ int at25xe321d_read(uint32_t addr, uint8_t* buf, size_t len) { if (len == 0 || buf == NULL) { LOG_ERR("Invalid parameters for read operation"); return -EINVAL; } int ret = at25xe321d_wait_until_ready(50000); if (ret < 0) return ret; return hal_qspi_read(addr, buf, len); } /** * @brief Program data to AT25XE321D flash memory, handling multiple page writes. * * @param address The starting address in flash memory where data should be written. * @param data Pointer to the data buffer to be written. * @param size The size of the data to write, which must not exceed a page size. * @return 0 on success, or a negative error code on failure. */ int at25xe321d_program(uint32_t address, const uint8_t* data, size_t size) { int ret = 0; uint32_t pageNumberStart = address / AT25XE321D_PAGE_SIZE; uint32_t pageNumberEnd = (address + size - 1) / AT25XE321D_PAGE_SIZE; // Check if the data fits in a single page if (pageNumberStart == pageNumberEnd) { // Write the data in a single page ret = at25xe321d_page_program(address, data, size); } else { // Write in two separate pages size_t len1 = (pageNumberEnd * AT25XE321D_PAGE_SIZE) - address; size_t len2 = size - len1; // Program the first part of the data in the first page ret = at25xe321d_page_program(address, data, len1); // Program the second part of the data in the next page ret = at25xe321d_page_program(address + len1, data + len1, len2); } return ret; } /** * @brief Program a single page in the AT25XE321D. * * @param address The starting address in the flash memory for the page programming. * @param data Pointer to the data buffer to be written to the page. * @param size The size of the data to write, should not exceed page size. * @return 0 on success, or a negative error code on failure. */ int at25xe321d_page_program(uint32_t address, const uint8_t* data, size_t size) { // Assumes size <= AT25XE321D_PAGE_SIZE, which is a prerequisite if (size > AT25XE321D_PAGE_SIZE) { LOG_ERR("Data size exceeds page size"); return -EINVAL; // Error: Invalid size } // Enable write operation int ret = at25xe321d_write_enable(); if (ret != 0) { LOG_ERR("Failed to enable write for page program"); return ret; } return at25xe321d_write(address, data, size); } /** * @brief Disables write operations on the AT25XE321D flash memory. * * @return 0 on success, or an error code on failure. */ int at25xe321d_write_disable(void) { return hal_qspi_send_cmd(AT25XE321D_CMD_WRITE_DISABLE); } /** * @brief Writes to the Status Register of the AT25XE321D flash memory. * * This function sends the Write Status Register (WRSR) command, followed by * the new value to the Status Register. This allows changes to the protection * bits (BP2, BP1, BP0) and other flags in the Status Register. * * @param status_value The new value for the Status Register. * @return 0 if successful, error code otherwise. */ int at25xe321d_write_status(uint8_t status_value) { int ret = at25xe321d_write_enable(); if (ret != 0) { LOG_ERR("Failed to enable write before writing status"); return ret; } ret = hal_qspi_cmd_write(AT25XE321D_CMD_WRITE_STATUS, &status_value, 1); if (ret != 0) { LOG_ERR("Failed to write status register"); return ret; } // Use a short timeout for writing status uint32_t status_write_timeout_us = 5000; // 5ms ret = at25xe321d_wait_until_ready(status_write_timeout_us); if (ret != 0) { LOG_ERR("Flash is not ready after writing to status register"); return ret; } LOG_INF("Status register written successfully with value 0x%02X", status_value); return 0; } /** * @brief Read Status Register * * Reads the status register of the flash memory, which provides information * on write enable status, busy status, and other flags. * * @param status Pointer to variable to store the status byte * @return 0 if successful, error code otherwise */ int at25xe321d_read_status(uint8_t* status) { if (status == NULL) { LOG_ERR("Invalid parameter: status is NULL"); return -EINVAL; } return hal_qspi_cmd_read(AT25XE321D_CMD_READ_STATUS, status, 1); } /** * @brief Enable write operations on the AT25XE321D flash memory. * * Sends the Write Enable command and verifies that the Write Enable Latch (WEL) is set. * Also checks if write protection is enabled and logs a warning if so. * * @return 0 on success, or a negative error code on failure. */ /** * @brief Enable write operations on the AT25XE321D flash memory. * * Sends the Write Enable command and verifies that the Write Enable Latch (WEL) is set. * Also checks if write protection is enabled and logs a warning if so. * * @return 0 on success, or a negative error code on failure. */ int at25xe321d_write_enable(void) { return hal_qspi_send_cmd(AT25XE321D_CMD_WRITE_ENABLE); } /** * @brief Waits until the AT25XE321D flash memory is ready for the next operation. * * This function continuously checks the Write-In-Progress (WIP) bit in the * status register until the flash memory indicates it is ready or the timeout expires. * * @param timeout_us Timeout in microseconds for waiting. * @return 0 on success, or a negative error code on failure: * - -EIO: Communication error occurred during SPI read or write. * - -ETIMEDOUT: Timeout expired while waiting for the device to be ready. */ int at25xe321d_wait_until_ready(uint32_t timeout_us) { return hal_qspi_wait_ready(timeout_us); } /** * @brief Erases the entire flash memory chip. * * @return 0 on success, or an error code on failure. */ int at25xe321d_erase_chip(void) { // Enable write operations int ret = at25xe321d_write_enable(); if (ret != 0) { LOG_ERR("Failed to enable write for chip erase"); return ret; } // Send chip erase command ret = hal_qspi_send_cmd(AT25XE321D_CMD_CHIP_ERASE); if (ret != 0) { LOG_ERR("Failed to send chip erase command"); return ret; } // Use an extended timeout for chip erase uint32_t chip_erase_timeout_us = 120 * 1000 * 1000; // 120 seconds ret = at25xe321d_wait_until_ready(chip_erase_timeout_us); if (ret != 0) { LOG_ERR("Chip erase operation timed out"); return ret; } // Disable write operations after erase ret = at25xe321d_write_disable(); if (ret != 0) { LOG_ERR("Failed to disable write after chip erase"); return ret; } LOG_INF("Chip erase completed successfully"); return 0; } /** * @brief Erases a 4KB sector of the flash memory. * * @param addr Address of the sector to erase (must be sector-aligned). * @return 0 on success, or an error code on failure. */ int at25xe321d_erase_sector(uint32_t addr) { // Ensure the address is sector-aligned if (addr % AT25XE321D_SECTOR_SIZE != 0) { LOG_ERR("Address 0x%06X is not sector-aligned", addr); return -EINVAL; } // Enable write operations int ret = at25xe321d_write_enable(); if (ret != 0) { LOG_ERR("Failed to enable write before erasing sector at address 0x%06X", addr); return ret; } // Prepare the erase sector command uint8_t cmd[4] = { AT25XE321D_CMD_SECTOR_ERASE, // Sector erase command (uint8_t) (addr >> 16), // Address MSB (uint8_t) (addr >> 8), // Address middle byte (uint8_t) (addr) // Address LSB }; // Send the erase command ret = hal_qspi_cmd_write(cmd[0], cmd + 1, sizeof(cmd) - 1); if (ret != 0) { LOG_ERR("Failed to send sector erase command to address 0x%06X", addr); return ret; } uint32_t sector_erase_timeout_us = 2000 * 1000; // 2 seconds // Wait until the operation is complete ret = at25xe321d_wait_until_ready(sector_erase_timeout_us); if (ret != 0) { LOG_ERR("Timeout or error waiting for erase completion at address 0x%06X", addr); return ret; } // Disable write operations as a safety measure ret = at25xe321d_write_disable(); if (ret != 0) { LOG_ERR("Failed to disable write after erasing sector at address 0x%06X", addr); return ret; } LOG_INF("Successfully erased 4KB sector at address 0x%06X", addr); return 0; } /** * @brief Erase a block. * * @param addr The starting address of the block to erase. * @param size The size of the block to erase (32KB or 64KB). * * @return 0 on success, or a negative error code on failure. */ int at25xe321d_erase_block(uint32_t addr, size_t size) { uint8_t cmd[4]; if (size == 32 * 1024) { cmd[0] = AT25XE321D_CMD_BLOCK_ERASE_32KB; } else if (size == 64 * 1024) { cmd[0] = AT25XE321D_CMD_BLOCK_ERASE_64KB; } else { return -EINVAL; } // Set the address bytes cmd[1] = (uint8_t) (addr >> 16); cmd[2] = (uint8_t) (addr >> 8); cmd[3] = (uint8_t) (addr); // Enable write operations int ret = at25xe321d_write_enable(); if (ret != 0) { LOG_ERR("Failed to enable write before block erase"); return ret; } // Send the block erase command ret = hal_qspi_cmd_write(cmd[0], cmd + 1, sizeof(cmd) - 1); if (ret != 0) { LOG_ERR("Failed to send block erase command"); return ret; } // Use an extended timeout for block erase uint32_t block_erase_timeout_us = (size == 32 * 1024) ? 30 * 1000 * 1000 : 60 * 1000 * 1000; ret = at25xe321d_wait_until_ready(block_erase_timeout_us); if (ret != 0) { LOG_ERR("Block erase operation timed out"); return ret; } return at25xe321d_write_disable(); } /** * @brief Enter deep power-down mode. * * @return 0 on success, or a negative error code on failure. */ int at25xe321d_enter_power_down(void) { return hal_qspi_send_cmd(AT25XE321D_CMD_ENTER_POWER_DOWN); } /** * @brief Release from deep power-down mode. * * Sends the Release Power Down command to the Flash, bringing it out of deep power-down mode. * * @return 0 on success, or a negative error code on failure. */ int at25xe321d_release_power_down(void) { return hal_qspi_send_cmd(AT25XE321D_CMD_RELEASE_POWER_DOWN); } /** * @brief Checks if write protection is enabled on the AT25XE321D flash memory. * * Reads the Status Register and determines if any of the Block Protect (BP) bits are set. * * @return true if write protection is enabled, false otherwise. */ bool at25xe321d_check_write_protection(void) { uint8_t status = 0; // Read the status register int ret = at25xe321d_read_status(&status); if (ret != 0) { LOG_ERR("Failed to read status register"); return true; // Default to write protection enabled on error } // Check if any BP bits are set (indicating write protection) if (status & AT25XE321D_SR_BP_MASK) { LOG_INF("Write protection is enabled"); return true; } else { LOG_INF("Write protection is disabled"); return false; } } /** * @brief Disables write protection on the AT25XE321D flash memory. * * This function clears the BP (Block Protect) bits in the Status Register, * disabling write protection for the entire chip. * * @return 0 on success, or a negative error code on failure. */ int at25xe321d_disable_write_protection(void) { uint8_t status = 0; // Read the current status register int ret = at25xe321d_read_status(&status); if (ret != 0) { LOG_ERR("Failed to read status register"); return ret; } // Modify the status register to clear BP bits status &= ~(AT25XE321D_SR_BP_MASK); // Clear the Block Protect bits // Write the modified status register back ret = at25xe321d_write_enable(); if (ret != 0) { LOG_ERR("Failed to enable write"); return ret; } ret = at25xe321d_write_status(status); if (ret != 0) { LOG_ERR("Failed to write status register"); return ret; } LOG_INF("Write protection disabled"); return 0; } /** * @brief Reset the AT25XE321D flash memory. * * Performs a software reset of the flash memory, bringing it back to its default state. * * @return 0 on success, or an error code on failure. */ int at25xe321d_reset(void) { int ret = hal_qspi_send_cmd(AT25XE321D_CMD_RESET_ENABLE); if (ret < 0) return ret; return hal_qspi_send_cmd(AT25XE321D_CMD_RESET_MEMORY); } /** * @brief Erase a specific region of the AT25XE321D chip based on size and address. * * @param address The starting address of the erase operation. * @param erase_size The size of the region to erase (256, 4KB, 32KB, 64KB, or full chip). * @return 0 on success, or a negative error code on failure. */ int at25xe321d_erase(uint32_t address, uint32_t erase_size) { // Ensure the address is within bounds for the selected erase size if (address >= AT25XE321D_CHIP_SIZE) { LOG_ERR("Invalid address: %08X, exceeds chip size", address); return -EINVAL; } uint8_t cmd[4]; uint32_t erase_timeout_us; // Choose the erase command based on the requested erase size switch (erase_size) { case AT25XE321D_PAGE_SIZE: // 256-Byte page erase cmd[0] = AT25XE321D_CMD_PAGE_ERASE; cmd[1] = (address >> 16) & 0xFF; cmd[2] = (address >> 8) & 0xFF; cmd[3] = address & 0xFF; erase_timeout_us = 5000; // 5ms for page erase break; case AT25XE321D_SECTOR_SIZE: // 4KB Block erase cmd[0] = AT25XE321D_CMD_SECTOR_ERASE; cmd[1] = (address >> 16) & 0xFF; cmd[2] = (address >> 8) & 0xFF; cmd[3] = address & 0xFF; erase_timeout_us = 2 * 1000 * 1000; // 2 seconds for sector erase break; case AT25XE321D_BLOCK_32KB_SIZE: // 32KB Block erase cmd[0] = AT25XE321D_CMD_BLOCK_ERASE_32KB; cmd[1] = (address >> 16) & 0xFF; cmd[2] = (address >> 8) & 0xFF; cmd[3] = address & 0xFF; erase_timeout_us = 30 * 1000 * 1000; // 30 seconds for 32KB block erase break; case AT25XE321D_BLOCK_64KB_SIZE: // 64KB Block erase cmd[0] = AT25XE321D_CMD_BLOCK_ERASE_64KB; cmd[1] = (address >> 16) & 0xFF; cmd[2] = (address >> 8) & 0xFF; cmd[3] = address & 0xFF; erase_timeout_us = 60 * 1000 * 1000; // 60 seconds for 64KB block erase break; case AT25XE321D_CHIP_SIZE: // Full chip erase cmd[0] = AT25XE321D_CMD_CHIP_ERASE; cmd[1] = 0x00; // Full chip erase does not need address cmd[2] = 0x00; cmd[3] = 0x00; erase_timeout_us = 120 * 1000 * 1000; // 120 seconds for chip erase break; default: LOG_ERR("Unsupported erase size: %d", erase_size); return -EINVAL; } // Write enable before performing the erase int ret = at25xe321d_write_enable(); if (ret != 0) { LOG_ERR("Failed to enable write for erase"); return ret; } // Send the erase command ret = hal_qspi_cmd_write(cmd[0], cmd + 1, sizeof(cmd) - 1); if (ret != 0) { LOG_ERR("SPI write failed for erase command"); return ret; } // Wait until the erase operation is complete ret = at25xe321d_wait_until_ready(erase_timeout_us); if (ret != 0) { LOG_ERR("Erase operation did not complete successfully"); return ret; } LOG_INF("Erase operation completed successfully at address 0x%08X", address); return 0; }
&qspi { status = "okay"; pinctrl-0 = <&qspi_default>; pinctrl-1 = <&qspi_sleep>; pinctrl-names = "default", "sleep"; at25xe32: at25xe321d@0 { reg = <0x0>; jedec-id = [1f 47 0c]; sck-frequency = <16000000>; compatible = "nordic,qspi-nor"; size = <33554432>; has-dpd; t-enter-dpd = <3000>; t-exit-dpd = <200000>; // Configure to actully use Quad SPI data. writeoc = "pp4o"; readoc = "read4io"; quad-enable-requirements = "S2B1v6"; /* Storage Partitions */ partitions { compatible = "fixed-partitions"; #address-cells = <1>; #size-cells = <1>; slot1_partition: partition@0 { label = "image-1"; reg = <0x00000000 0x006A000>; /* 424 KB */ }; store_partition: partition@100000 { label = "store"; reg = <0x00100000 0x00300000>; /* 3MB */ }; }; }; };