Problem interfacing at25xe321d with QPSI

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 */
            };
        };
    };
};

Parents Reply
  • Hi, 

    Sorry for the delayed response. From the code snippets you posted it appears you have implemented your own driver using the nrfx HAL. Please try the spi_flash (project path: /zephyr/samples/drivers/spi_flash/) and see if you get the same result with this sample which uses the QSPI NOR driver from Zephyr. If this also fails, please try lowering the clock frequency to 1M and change from quad to single line read/write (fastread/pp) to help rule out other issues.

    Thanks,

    Vidar

Children
No Data
Related