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