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

Related