core/flash/spi_flash.c (1,482 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "spi_flash.h"
#include "common/unused.h"
#include "flash/flash_common.h"
#include "flash/flash_logging.h"
/* Status bits indicating when flash is operating in 4-byte address mode. */
#define MACRONIX_4BYTE_STATUS (1U << 5)
#define WINBOND_4BYTE_STATUS (1U << 0)
#define MICRON_4BYTE_STATES (1U << 0)
/* Config bits indicating address mode on reset. */
#define WINBOND_4BYTE_DEFAULT (1U << 1)
#define MICRON_4BYTE_DEFAULT (1U << 0)
/* Status bits indicating when flash has QSPI enabled. */
#define RESET_HOLD_ENABLE (1U << 4)
#define QSPI_ENABLE_BIT1 (1U << 1)
#define QSPI_ENABLE_BIT6 (1U << 6)
#define QSPI_ENABLE_BIT7 (1U << 7)
/* Flag bits for controlling flash reset during initialization. */
#define SPI_FLASH_DO_RESET (1U << 0)
#define SPI_FLASH_RESET_IS_REQUIRED (1U << 1)
/* Infineon Flash status and configuration register address space.
STR stands for status register , CFR stands for configuration register,
V stands for Volatile, N stands for Nonvolatile. */
#define INFINEON_STR1V 0x00800000
#define INFINEON_STR2V 0x00800001
#define INFINEON_CFR1V 0x00800002
#define INFINEON_CFR2V 0x00800003
#define INFINEON_CFR3V 0x00800004
#define INFINEON_CFR4V 0x00800005
#define INFINEON_CFR5V 0x00800006
#define INFINEON_STR1N 0x00000000
#define INFINEON_CFR1N 0x00000002
#define INFINEON_CFR2N 0x00000003
#define INFINEON_CFR3N 0x00000004
#define INFINEON_CFR4N 0x00000005
#define INFINEON_CFR5N 0x00000006
/* Macronix MX66UW flash volatile configuration register 2 address
* that controls the error correction signal. */
#define MX66UW_CFR2V_ECS 0x00000400
/**
* Check the requested operation to ensure it is valid for the device.
*/
#define SPI_FLASH_BOUNDS_CHECK(bytes, addr, len) \
do { \
if (addr >= bytes) { \
return SPI_FLASH_ADDRESS_OUT_OF_RANGE; \
} \
\
if ((addr + len) > bytes) { \
return SPI_FLASH_OPERATION_OUT_OF_RANGE; \
} \
} while (0)
/**
* Configure the read command for the flash device.
*
* @param flash The flash interface to configure.
* @param command Read command information to use for configuration.
* @param opcode_4byte The read command to use in 4-byte mode.
* @param use_4byte Flag indicating if 4-byte mode is enabled.
* @param flags Transaction flags for the read.
*/
static void spi_flash_configure_read_command (const struct spi_flash *flash,
const struct spi_flash_sfdp_read_cmd *command, uint8_t opcode_4byte, bool use_4byte,
uint16_t flags)
{
flash->state->command.read_dummy = command->dummy_bytes;
flash->state->command.read_mode = command->mode_bytes;
flash->state->command.read_flags = flags;
if (use_4byte) {
flash->state->command.read = opcode_4byte;
flash->state->command.read_flags |= FLASH_FLAG_4BYTE_ADDRESS;
}
else {
flash->state->command.read = command->opcode;
}
}
/**
* Configure the program and erase commands for the flash device.
*
* @param flash The flash interface to configure.
*/
static void spi_flash_set_write_erase_commands (const struct spi_flash *flash)
{
if ((flash->state->capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) ==
(FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) {
flash->state->command.write = FLASH_CMD_4BYTE_PP;
flash->state->command.write_flags = FLASH_FLAG_4BYTE_ADDRESS;
flash->state->command.erase_sector = FLASH_CMD_4BYTE_4K_ERASE;
flash->state->command.sector_flags = FLASH_FLAG_4BYTE_ADDRESS;
flash->state->command.erase_block = FLASH_CMD_4BYTE_64K_ERASE;
flash->state->command.block_flags = FLASH_FLAG_4BYTE_ADDRESS;
}
}
/**
* Configure the command set for the device based on its capabilities.
*
* @param flash The flash interface to configure.
* @param read Information from SFDP for read commands.
* @param sfdp SFDP tables for additional command information.
*/
static void spi_flash_set_device_commands (const struct spi_flash *flash,
const struct spi_flash_sfdp_read_commands *read, const struct spi_flash_sfdp_basic_table *sfdp)
{
bool use_4byte;
use_4byte = ((flash->state->capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) ==
(FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR));
if (read && (flash->state->capabilities & FLASH_CAP_QUAD_1_4_4)) {
spi_flash_configure_read_command (flash, &read->quad_1_4_4, FLASH_CMD_4BYTE_QIO_READ,
use_4byte, FLASH_FLAG_QUAD_ADDR | FLASH_FLAG_QUAD_DATA);
}
else if (read && (flash->state->capabilities & FLASH_CAP_QUAD_1_1_4)) {
spi_flash_configure_read_command (flash, &read->quad_1_1_4, FLASH_CMD_4BYTE_QUAD_READ,
use_4byte, FLASH_FLAG_QUAD_DATA);
}
else if (read && (flash->state->capabilities & FLASH_CAP_DUAL_1_2_2)) {
spi_flash_configure_read_command (flash, &read->dual_1_2_2, FLASH_CMD_4BYTE_DIO_READ,
use_4byte, FLASH_FLAG_DUAL_ADDR | FLASH_FLAG_DUAL_DATA);
}
else if (read && (flash->state->capabilities & FLASH_CAP_DUAL_1_1_2)) {
spi_flash_configure_read_command (flash, &read->dual_1_1_2, FLASH_CMD_4BYTE_DUAL_READ,
use_4byte, FLASH_FLAG_DUAL_DATA);
}
else if (use_4byte) {
if (flash->state->use_fast_read) {
flash->state->command.read = FLASH_CMD_4BYTE_FAST_READ;
flash->state->command.read_dummy = 1;
}
else {
flash->state->command.read = FLASH_CMD_4BYTE_READ;
}
flash->state->command.read_flags = FLASH_FLAG_4BYTE_ADDRESS;
}
spi_flash_set_write_erase_commands (flash);
if (sfdp) {
spi_flash_sfdp_get_reset_command (sfdp, &flash->state->command.reset);
spi_flash_sfdp_get_deep_powerdown_commands (sfdp, &flash->state->command.enter_pwrdown,
&flash->state->command.release_pwrdown);
}
}
/**
* Configure a device for use and detect device properties. The device interface must be fully
* initialized prior finishing device and interface configuration.
*
* This will complete the steps outlined for spi_flash_initialize_device and
* spi_flash_initialize_device_state.
*
* @param flash The flash interface to configure.
* @param wake_device Flag indicating if the device should be removed from deep power down.
* @param reset_device Flags indicating if the device should be reset prior to initialization.
* @param drive_strength Flag indicating if the device output drive strength should be configured.
*
* @return 0 if the device and interface were successfully configured or an error code.
*/
static int spi_flash_configure_device (const struct spi_flash *flash, bool wake_device,
enum spi_flash_reset reset_device, bool drive_strength)
{
struct spi_flash_sfdp sfdp;
int status;
if (wake_device) {
status = spi_flash_deep_power_down (flash, 0);
if (status != 0) {
return status;
}
}
status = spi_flash_get_device_id (flash, NULL, NULL);
if (status != 0) {
return status;
}
if ((flash->state->device_id[0] == 0xff) || (flash->state->device_id[0] == 0x00)) {
status = SPI_FLASH_NO_DEVICE;
return status;
}
status = spi_flash_sfdp_init (&sfdp, flash->spi);
if (status != 0) {
return status;
}
status = spi_flash_discover_device_properties (flash, &sfdp);
if (status != 0) {
goto exit;
}
/* Make sure the device is not writing any data before we proceed. Resets will corrupt the
* flash and register writes will fail if a write is currently in progress. */
status = spi_flash_wait_for_write (flash, 30000);
if (status != 0) {
goto exit;
}
if (reset_device & SPI_FLASH_DO_RESET) {
status = spi_flash_reset_device (flash);
if ((status != 0) &&
((status != SPI_FLASH_RESET_NOT_SUPPORTED) ||
(reset_device & SPI_FLASH_RESET_IS_REQUIRED))) {
/* Only fail initialization if reset is supported and fails or if the device does not
* support reset and the reset was marked as being a required step. */
goto exit;
}
}
if (drive_strength) {
status = spi_flash_configure_drive_strength (flash);
if (status != 0) {
goto exit;
}
}
if ((flash->state->capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) ==
(FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) {
status = spi_flash_detect_4byte_address_mode (flash);
if (status != 0) {
goto exit;
}
}
if (flash->state->command.read_flags & FLASH_FLAG_QUAD_DATA) {
status = spi_flash_enable_quad_spi (flash, 1);
if (status != 0) {
goto exit;
}
}
status = spi_flash_clear_block_protect (flash);
if (status != 0) {
goto exit;
}
exit:
spi_flash_sfdp_release (&sfdp);
return status;
}
/**
* Completely initialize a SPI flash interface and device so it is ready for use. This includes:
* - Initializing the SPI flash interface.
* - Configuring the interface and device based on discovered properties.
* - Detecting the address mode of the device.
*
* @param flash The flash interface to initialize.
* @param state Variable context for the flash interface. This must be uninitialized.
* @param spi The SPI master connected to the flash.
* @param fast_read Flag indicating if the FAST_READ command should be used for SPI reads.
* @param wake_device Flag indicating if the device should be removed from deep power down.
* @param reset_device Option indicating if the device should be reset prior to initialization.
* @param drive_strength Flag indicating if the device output drive strength should be configured.
*
* @return 0 if the SPI flash was successfully initialized or an error code.
*/
int spi_flash_initialize_device (struct spi_flash *flash, struct spi_flash_state *state,
const struct flash_master *spi, bool fast_read, bool wake_device,
enum spi_flash_reset reset_device, bool drive_strength)
{
int status;
if (fast_read) {
status = spi_flash_init_fast_read (flash, state, spi);
}
else {
status = spi_flash_init (flash, state, spi);
}
if (status != 0) {
return status;
}
status = spi_flash_configure_device (flash, wake_device, reset_device, drive_strength);
if (status != 0) {
spi_flash_release (flash);
}
return status;
}
/**
* Completely initialize a SPI flash interface and device so it is ready for use. This includes:
* - Initializing the SPI flash interface state. The rest of the base interface is assumed to
* already be initialized, likely through static initialization.
* - Configuring the interface and device based on discovered properties.
* - Detecting the address mode of the device.
*
* @param flash The flash interface that contains the state to initialize.
* @param fast_read Flag indicating if the FAST_READ command should be used for SPI reads.
* @param wake_device Flag indicating if the device should be removed from deep power down.
* @param reset_device Option indicating if the device should be reset prior to initialization.
* @param drive_strength Flag indicating if the device output drive strength should be configured.
*
* @return 0 if the SPI flash was successfully initialized or an error code.
*/
int spi_flash_initialize_device_state (const struct spi_flash *flash, bool fast_read,
bool wake_device, enum spi_flash_reset reset_device, bool drive_strength)
{
int status;
if (fast_read) {
status = spi_flash_init_state_fast_read (flash);
}
else {
status = spi_flash_init_state (flash);
}
if (status != 0) {
return status;
}
status = spi_flash_configure_device (flash, wake_device, reset_device, drive_strength);
if (status != 0) {
spi_flash_release (flash);
}
return status;
}
/**
* Complete the restore process for a the state of a flash interface that has already been
* initialized.
*
* @param flash The flash interface to restore.
* @param info The saved device information to restore interface state from.
*/
static void spi_flash_finish_device_restore (const struct spi_flash *flash,
const struct spi_flash_device_info *info)
{
memcpy (flash->state->device_id, info->device_id, sizeof (flash->state->device_id));
flash->state->device_size = info->device_size;
flash->state->capabilities = info->capabilities;
flash->state->use_busy_flag = !!(info->flags & SPI_FLASH_DEVICE_INFO_BUSY_FLAG);
flash->state->switch_4byte = (enum spi_flash_sfdp_4byte_addressing) info->switch_4byte;
flash->state->reset_3byte = !!(info->flags & SPI_FLASH_DEVICE_INFO_RESET_3BYTE);
flash->state->quad_enable = (enum spi_flash_sfdp_quad_enable) info->quad_enable;
flash->state->sr1_volatile = !!(info->flags & SPI_FLASH_DEVICE_INFO_SR1_VOLATILE);
flash->state->command.read = info->read_opcode;
flash->state->command.read_dummy = info->read_dummy;
flash->state->command.read_mode = info->read_mode;
flash->state->command.read_flags = info->read_flags;
spi_flash_set_write_erase_commands (flash);
flash->state->command.reset = info->reset_opcode;
flash->state->command.enter_pwrdown = info->enter_pwrdown;
flash->state->command.release_pwrdown = info->release_pwrdown;
}
/**
* Initialize a SPI flash device from a saved context. Upon completion, the interface will be ready
* to use, but no transaction with the flash device will be performed. This could leave the
* interface and device in an inconsistent state (e.g. the current address mode). It is recommended
* that the interface be synchronized with the flash when SPI accesses are possible.
*
* @param flash The flash interface to initialize.
* @param state Variable context for the flash interface. This must be uninitialized.
* @param spi The SPI master connected to the flash.
* @param info The saved device information to use for interface initialization.
*
* @return 0 if the flash interface was successfully initialized or an error code.
*/
int spi_flash_restore_device (struct spi_flash *flash, struct spi_flash_state *state,
const struct flash_master *spi, const struct spi_flash_device_info *info)
{
int status;
if (info == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
if (info->use_fast_read) {
status = spi_flash_init_fast_read (flash, state, spi);
}
else {
status = spi_flash_init (flash, state, spi);
}
if (status != 0) {
return status;
}
spi_flash_finish_device_restore (flash, info);
return 0;
}
/**
* Initialize a SPI flash device state from a saved context. Upon completion, the interface will be
* ready to use, but no transaction with the flash device will be performed. This could leave the
* interface and device in an inconsistent state (e.g. the current address mode). It is recommended
* that the interface be synchronized with the flash when SPI accesses are possible.
*
* Only the state will be initialized. The rest of the interface is assumed to already have been
* initialized, likely through static initialization.
*
* @param flash The flash interface that contains the state to initialize.
* @param info The saved device information to use for interface initialization.
*
* @return 0 if the flash interface was successfully initialized or an error code.
*/
int spi_flash_restore_device_state (const struct spi_flash *flash,
const struct spi_flash_device_info *info)
{
int status;
if (info == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
if (info->use_fast_read) {
status = spi_flash_init_state_fast_read (flash);
}
else {
status = spi_flash_init_state (flash);
}
if (status != 0) {
return status;
}
spi_flash_finish_device_restore (flash, info);
return 0;
}
/**
* Initialize the SPI flash interface API.
*
* @param flash The flash interface to initialize.
* @param state Variable context for the flash interface. This must be uninitialized.
* @param spi The SPI master connected to the flash.
*
* @return 0 if the flash API was initialized or an error code.
*/
static int spi_flash_init_api (struct spi_flash *flash, struct spi_flash_state *state,
const struct flash_master *spi)
{
if ((flash == NULL) || (state == NULL) || (spi == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
memset (flash, 0, sizeof (struct spi_flash));
flash->base.get_device_size =
(int (*) (const struct flash*, uint32_t*)) spi_flash_get_device_size;
flash->base.read = (int (*) (const struct flash*, uint32_t, uint8_t*, size_t)) spi_flash_read;
flash->base.get_page_size = (int (*) (const struct flash*, uint32_t*)) spi_flash_get_page_size;
flash->base.minimum_write_per_page =
(int (*) (const struct flash*, uint32_t*)) spi_flash_minimum_write_per_page;
flash->base.write =
(int (*) (const struct flash*, uint32_t, const uint8_t*, size_t)) spi_flash_write;
flash->base.get_sector_size =
(int (*) (const struct flash*, uint32_t*)) spi_flash_get_sector_size;
flash->base.sector_erase = (int (*) (const struct flash*, uint32_t)) spi_flash_sector_erase;
flash->base.get_block_size =
(int (*) (const struct flash*, uint32_t*)) spi_flash_get_block_size;
flash->base.block_erase = (int (*) (const struct flash*, uint32_t)) spi_flash_block_erase;
flash->base.chip_erase = (int (*) (const struct flash*)) spi_flash_chip_erase;
flash->state = state;
flash->spi = spi;
return 0;
}
/**
* Initialize the SPI flash interface.
*
* This is not sufficient to be able to fully access the SPI flash device. Use
* {@link spi_flash_initialize_device} for complete device initialization.
*
* @param flash The flash interface to initialize.
* @param state Variable context for the flash interface. This must be uninitialized.
* @param spi The SPI master connected to the flash.
*
* @return 0 if the flash interface was initialized or an error code.
*/
int spi_flash_init (struct spi_flash *flash, struct spi_flash_state *state,
const struct flash_master *spi)
{
int status;
status = spi_flash_init_api (flash, state, spi);
if (status == 0) {
status = spi_flash_init_state (flash);
}
return status;
}
/**
* Initialize the SPI flash interface. The FAST_READ command will be used for SPI reads.
*
* This is not sufficient to be able to fully access the SPI flash device. Use
* {@link spi_flash_initialize_device} for complete device initialization.
*
* @param flash The flash interface to initialize.
* @param state Variable context for the flash interface. This must be uninitialized.
* @param spi The SPI master connected to the flash.
*
* @return 0 if the flash interface was initialized or an error code.
*/
int spi_flash_init_fast_read (struct spi_flash *flash, struct spi_flash_state *state,
const struct flash_master *spi)
{
int status;
status = spi_flash_init_api (flash, state, spi);
if (status == 0) {
status = spi_flash_init_state_fast_read (flash);
}
return status;
}
/**
* Initialize only the variable state for an SPI flash interface. The rest of the interface is
* assumed to have already been initialized.
*
* This would generally be used with a statically initialized instance.
*
* This is not sufficient to be able to fully access the SPI flash device. Use
* {@link spi_flash_initialize_device_state} for complete device initialization.
*
* @param flash The flash interface that contains the state to initialize.
*
* @return 0 if the state was successfully initialized or an error code.
*/
int spi_flash_init_state (const struct spi_flash *flash)
{
int status;
if ((flash == NULL) || (flash->state == NULL) || (flash->spi == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
memset (flash->state, 0, sizeof (struct spi_flash_state));
status = platform_mutex_init (&flash->state->lock);
if (status != 0) {
return status;
}
flash->state->device_id[0] = 0xff;
/* Populate common command codes for basic flash operations. */
flash->state->command.read = FLASH_CMD_READ;
flash->state->command.write = FLASH_CMD_PP;
flash->state->command.erase_sector = FLASH_CMD_4K_ERASE;
flash->state->command.erase_block = FLASH_CMD_64K_ERASE;
/* Make assumptions about the power down command support to allow the overall device
* initialization sequence to wake devices up. If the device is powered down, it will not
* respond to any commands, so there is no way to query the device to determine command
* support. If scenarios arise where different commands are needed, the interface will need
* some additional information from the caller. */
flash->state->command.enter_pwrdown = FLASH_CMD_DP;
flash->state->command.release_pwrdown = FLASH_CMD_RDP;
/* Make an assumption in the default case that the flash device supports the common 66/99
* sequence for triggering a soft reset, and that the reset reverts the address mode to the
* default state. This allows some minimal scenarios where additional device discovery is not
* necessary to still reset the device, but most scenarios will never see these default
* assumptions. */
flash->state->command.reset = FLASH_CMD_RST;
flash->state->reset_3byte = true;
/* Continuing with default assumptions, assume a device that supports both 3 and 4 byte address
* modes. */
flash->state->capabilities = (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR);
return 0;
}
/**
* Initialize only the variable state for an SPI flash interface. The rest of the interface is
* assumed to have already been initialized. The FAST_READ command will be used for SPI reads.
*
* This would generally be used with a statically initialized instance.
*
* This is not sufficient to be able to fully access the SPI flash device. Use
* {@link spi_flash_initialize_device_state} for complete device initialization.
*
* @param flash The flash interface that contains the state to initialize.
*
* @return 0 if the state was successfully initialized or an error code.
*/
int spi_flash_init_state_fast_read (const struct spi_flash *flash)
{
int status;
status = spi_flash_init_state (flash);
if (status == 0) {
flash->state->use_fast_read = true;
flash->state->command.read = FLASH_CMD_FAST_READ;
flash->state->command.read_dummy = 1;
}
return status;
}
/**
* Release the SPI flash interface.
*
* @param flash The flash interface to release.
*/
void spi_flash_release (const struct spi_flash *flash)
{
if (flash) {
platform_mutex_free (&flash->state->lock);
}
}
/**
* Save the SPI device context. This will allow a new SPI flash interface to be created to
* communicate with the flash device without needing to query the device.
*
* @param flash The flash interface to save.
* @param info The context that will be updated for the flash device.
*
* @return 0 if the flash device context was successfully saved or an error code.
*/
int spi_flash_save_device_info (const struct spi_flash *flash, struct spi_flash_device_info *info)
{
if ((flash == NULL) || (info == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
info->version = SPI_FLASH_DEVICE_INFO_VERSION;
memcpy (info->device_id, flash->state->device_id, sizeof (info->device_id));
info->device_size = flash->state->device_size;
info->capabilities = flash->state->capabilities;
info->use_fast_read = flash->state->use_fast_read;
info->read_opcode = flash->state->command.read;
info->read_dummy = flash->state->command.read_dummy;
info->read_mode = flash->state->command.read_mode;
info->read_flags = flash->state->command.read_flags;
info->reset_opcode = flash->state->command.reset;
info->enter_pwrdown = flash->state->command.enter_pwrdown;
info->release_pwrdown = flash->state->command.release_pwrdown;
info->switch_4byte = flash->state->switch_4byte;
info->quad_enable = flash->state->quad_enable;
info->flags = 0;
if (flash->state->use_busy_flag) {
info->flags |= SPI_FLASH_DEVICE_INFO_BUSY_FLAG;
}
if (flash->state->reset_3byte) {
info->flags |= SPI_FLASH_DEVICE_INFO_RESET_3BYTE;
}
if (flash->state->sr1_volatile) {
info->flags |= SPI_FLASH_DEVICE_INFO_SR1_VOLATILE;
}
return 0;
}
/**
* Send a write command to the flash that only sends the command code.
*
* @param flash The flash instance to use to send the command.
* @param cmd The command code to send to the device.
*
* @return 0 if the command was successfully sent or an error code.
*/
static int spi_flash_simple_command (const struct spi_flash *flash, uint8_t cmd)
{
struct flash_xfer xfer;
FLASH_XFER_INIT_CMD_ONLY (xfer, cmd, 0);
return flash->spi->xfer (flash->spi, &xfer);
}
/**
* Send the write enable command to the flash device.
*
* @param flash The flash instance to use to send the command.
*
* @return 0 if the command was successfully sent or an error code.
*/
static int spi_flash_write_enable (const struct spi_flash *flash)
{
return spi_flash_simple_command (flash, FLASH_CMD_WREN);
}
/**
* Send the volatile write enable command to the flash device.
*
* @param flash The flash instance to use to send the command.
*
* @return 0 if the command was successfully sent or an error code.
*/
static int spi_flash_volatile_write_enable (const struct spi_flash *flash)
{
return spi_flash_simple_command (flash, FLASH_CMD_VOLATILE_WREN);
}
/**
* Determine if the flash is currently executing a write command.
*
* @param flash The flash instance to check.
*
* @return 0 if no write is in progress, 1 if there is, or an error code.
*/
static int spi_flash_is_wip_set (const struct spi_flash *flash)
{
struct flash_xfer xfer;
uint8_t reg;
int status;
if (!flash->state->use_busy_flag) {
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDSR, ®, 1, 0);
}
else {
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDSR_FLAG, ®, 1, 0);
}
status = flash->spi->xfer (flash->spi, &xfer);
if (status == 0) {
if (!flash->state->use_busy_flag) {
return ((reg & FLASH_STATUS_WIP) != 0);
}
else {
return ((reg & FLASH_FLAG_STATUS_READY) == 0);
}
}
else {
return status;
}
}
/**
* Wait for a write operation to complete.
*
* @param flash The flash instance that is executing a write operation.
* @param timeout The maximum number of milliseconds to wait for completion. A negative number will
* wait forever. 0 will return immediately.
* @param no_sleep Flag indicating no sleep should be inserted between status reads.
*
* @return 0 if the write was completed or an error code.
*/
static int spi_flash_wait_for_write_completion (const struct spi_flash *flash, int32_t timeout,
uint8_t no_sleep)
{
platform_clock timeout_val;
int done = 0;
int status;
if (timeout > 0) {
status = platform_init_timeout (timeout, &timeout_val);
if (status) {
return status;
}
}
do {
status = spi_flash_is_wip_set (flash);
if (status == 0) {
done = 1;
}
else if (status == 1) {
status = 0;
if ((timeout > 0) && (platform_has_timeout_expired (&timeout_val) == 1)) {
status = SPI_FLASH_WIP_TIMEOUT;
}
else if (timeout == 0) {
status = SPI_FLASH_WIP_TIMEOUT;
}
if (status == 0) {
if (!no_sleep) {
platform_msleep (10);
}
}
}
} while ((status == 0) && !done);
return status;
}
/**
* Send a write command that writes to a register that requires a 4byte address.
* This will block until the register write has completed.
*
* @param flash The flash instance to use to send the command.
* @param cmd The command code that writes the register.
* @param addr The address of the register to write.
* @param data The data to write to the register.
* @param length The length of the data to write.
*
* @return 0 if the command was successfully completed or an error code.
*/
static int spi_flash_write_register_with_4byte_address (const struct spi_flash *flash, uint8_t cmd,
uint32_t addr, uint8_t *data, size_t length)
{
struct flash_xfer xfer;
int status;
status = spi_flash_is_wip_set (flash);
if (status != 0) {
return (status == 1) ? SPI_FLASH_WRITE_IN_PROGRESS : status;
}
status = spi_flash_write_enable (flash);
if (status != 0) {
return status;
}
FLASH_XFER_INIT_WRITE (xfer, cmd, addr, 0, data, length, FLASH_FLAG_4BYTE_ADDRESS);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
return status;
}
return spi_flash_wait_for_write_completion (flash, -1, 1);
}
/**
* Send a write command that writes to register that requires no addressing. This will block until
* the register write has completed.
*
* @param flash The flash instance to use to send the command.
* @param cmd The command code that writes the register.
* @param data The data to write to the register.
* @param length The length of the data to write.
* @param volatile_wren Flag indicating the volatile write enable command should be used.
*
* @return 0 if the command was successfully completed or an error code.
*/
static int spi_flash_write_register (const struct spi_flash *flash, uint8_t cmd, uint8_t *data,
size_t length, bool volatile_wren)
{
struct flash_xfer xfer;
int status;
status = spi_flash_is_wip_set (flash);
if (status != 0) {
return (status == 1) ? SPI_FLASH_WRITE_IN_PROGRESS : status;
}
if (volatile_wren) {
status = spi_flash_volatile_write_enable (flash);
}
else {
status = spi_flash_write_enable (flash);
}
if (status != 0) {
return status;
}
FLASH_XFER_INIT_WRITE_REG (xfer, cmd, data, length, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
return status;
}
return spi_flash_wait_for_write_completion (flash, -1, 1);
}
/**
* Discover device properties necessary for operation through SFDP. This must be done prior to
* using the interface to the device.
*
* This call supersedes {@link spi_flash_set_device_size}, which should no longer be used outside
* of unit testing. This call sets the device size and will also detect and configure many other
* relevant parameters.
*
* @param flash The flash interface to configure.
* @param sfdp The SFDP interface to use for property detection. The SFDP instance is not
* maintained internally and it can be managed independently of the SPI flash interface.
*
* @return 0 if the SPI flash properties were successfully detected or an error code.
*/
int spi_flash_discover_device_properties (const struct spi_flash *flash,
const struct spi_flash_sfdp *sfdp)
{
struct spi_flash_sfdp_basic_table parameters;
uint32_t spi_capabilities;
struct spi_flash_sfdp_read_commands read;
int status;
if ((flash == NULL) || (sfdp == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
status = spi_flash_sfdp_basic_table_init (¶meters, sfdp);
if (status != 0) {
return status;
}
platform_mutex_lock (&flash->state->lock);
spi_flash_sfdp_get_device_capabilities (¶meters, &flash->state->capabilities);
spi_flash_sfdp_get_read_commands (¶meters, &read);
spi_capabilities = flash->spi->capabilities (flash->spi) & flash->state->capabilities;
if ((spi_capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) !=
(flash->state->capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR))) {
status = SPI_FLASH_INCOMPATIBLE_SPI_MASTER;
goto exit;
}
flash->state->capabilities = spi_capabilities;
flash->state->reset_3byte = false;
status = spi_flash_sfdp_get_4byte_mode_switch (¶meters, &flash->state->switch_4byte);
if (status != 0) {
goto exit;
}
/* Based on special cases for 4 byte mode switch, fix 3 byte or 4 byte address mode. */
switch (flash->state->switch_4byte) {
case SPI_FLASH_SFDP_4BYTE_MODE_FIXED:
flash->state->capabilities &= ~FLASH_CAP_3BYTE_ADDR;
break;
case SPI_FLASH_SFDP_4BYTE_MODE_UNSUPPORTED:
flash->state->capabilities &= ~FLASH_CAP_4BYTE_ADDR;
break;
default:
break;
}
switch (flash->state->capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) {
case (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR):
if (!spi_flash_sfdp_supports_4byte_commands (¶meters)) {
/* We expect the flash device to support explicit 4-byte address commands. If it
* does not, we can't support communicating with that flash. It is possible to add
* support for these devices using enter/exit 4-byte commands, EAR, etc., but that
* is a lot more complicated. Since devices that don't support these commands are
* a minority of devices or older, there is not much benefit to adding this now. */
status = SPI_FLASH_NO_4BYTE_CMDS;
goto exit;
}
flash->state->reset_3byte = spi_flash_sfdp_exit_4byte_mode_on_reset (¶meters);
break;
case FLASH_CAP_4BYTE_ADDR:
flash->state->addr_mode = FLASH_FLAG_4BYTE_ADDRESS;
break;
}
spi_flash_set_device_commands (flash, &read, ¶meters);
status = spi_flash_sfdp_get_quad_enable (¶meters, &flash->state->quad_enable);
if (status != 0) {
goto exit;
}
status = spi_flash_sfdp_get_device_size (¶meters);
if (ROT_IS_ERROR (status)) {
goto exit;
}
flash->state->device_size = status;
flash->state->use_busy_flag = spi_flash_sfdp_use_busy_flag_status (¶meters);
flash->state->sr1_volatile = spi_flash_sfdp_use_volatile_write_enable (¶meters);
status = 0;
exit:
platform_mutex_unlock (&flash->state->lock);
spi_flash_sfdp_basic_table_release (¶meters);
return status;
}
/**
* Set the capacity of the flash device. This must be set before using the interface to the
* device.
*
* While this should not be used in most application code, there are scenarios where setting the
* flash device size in this way can be useful. Specifically, this works when the flash device is
* definitively known and the overhead of SFDP is not desirable. It is also useful for test code.
* If this function is used in any other scenario, it is possible to configure the driver in a way
* that is not compatible with the flash device.
*
* In general, {@link spi_flash_discover_device_properties} should be used to set the device size.
*
* @param flash The flash instance to configure.
* @param bytes The capacity of the physical flash device, in bytes.
*
* @return 0 if the interface was configured successfully or an error code.
*/
int spi_flash_set_device_size (const struct spi_flash *flash, uint32_t bytes)
{
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
flash->state->device_size = bytes;
if (bytes > 0x1000000) {
/* Assume a device that can switch between address modes. */
flash->state->capabilities = (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR);
spi_flash_set_device_commands (flash, NULL, NULL);
}
else {
/* Devices with 16MB or less of storage typically don't support 4-byte address mode, nor do
* they need it. */
flash->state->capabilities = FLASH_CAP_3BYTE_ADDR;
}
platform_mutex_unlock (&flash->state->lock);
return 0;
}
/**
* Set the capacity of the flash device for a flash that is no more than 16MB.
* This must be set before using the interface to the device.
*
* While this should not be used in most application code, there are scenarios where setting the
* flash device size in this way can be useful. Specifically, this works when the flash device is
* definitively known and the overhead of SFDP is not desirable. In case of ROM, setting device size
* does not have dependency on SFDP. So when size is less than 16MB it is useful to call this API.
* It is also useful for test code. If this function is used in any other scenario,
* it is possible to configure the driver in a way that is not compatible with the flash device.
*
* In general, {@link spi_flash_discover_device_properties} should be used to set the device size.
*
* @param flash The flash instance to configure.
* @param bytes The capacity of the physical flash device, in bytes.
*
* @return 0 if the interface was configured successfully or an error code.
*/
int spi_flash_set_device_size_small (const struct spi_flash *flash, uint32_t bytes)
{
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
if (bytes > 0x1000000) {
return SPI_FLASH_DEVICE_SIZE_OVER_16MB;
}
platform_mutex_lock (&flash->state->lock);
flash->state->device_size = bytes;
/* Devices with 16MB or less of storage typically don't support 4-byte address mode, nor do
* they need it. */
flash->state->capabilities = FLASH_CAP_3BYTE_ADDR;
platform_mutex_unlock (&flash->state->lock);
return 0;
}
/**
* Set the opcode and parameters that should be used when reading data from flash.
*
* This will ignore any other properties of the device and/or SPI master and use exactly what is
* provided to this function. Given that, it is possible to configure the driver in a way that is
* not compatible with the flash device, SPI master, or both. Therefore, it should only be used in
* scenarios where the system configuration and state are definitively known.
*
* In general, {@link spi_flash_discover_device_properties} should be used to properly configure the
* driver state.
*
* @param flash Tha flash instance to configure.
* @param command Read command information that should be used by the driver.
* @param flags Transaction flags for read operations.
*
* @return 0 if the interface was configured successfully or an error code.
*/
int spi_flash_set_read_command (const struct spi_flash *flash,
const struct spi_flash_sfdp_read_cmd *command, uint16_t flags)
{
if ((flash == NULL) || (command == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
spi_flash_configure_read_command (flash, command, 0, false, flags);
platform_mutex_unlock (&flash->state->lock);
return 0;
}
/**
* Set the opcode and parameters that should be used when writing data to flash. This does not
* affect erase commands.
*
* This will ignore any other properties of the device and/or SPI master and use exactly what is
* provided to this function. Given that, it is possible to configure the driver in a way that is
* not compatible with the flash device, SPI master, or both. Therefore, it should only be used in
* scenarios where the system configuration and state are definitively known.
*
* In general, {@link spi_flash_discover_device_properties} should be used to properly configure the
* driver state.
*
* @param flash Tha flash instance to configure.
* @param opcode Write command code that should be used by the driver.
* @param flags Transaction flags for write operations.
*
* @return 0 if the interface was configured successfully or an error code.
*/
int spi_flash_set_write_command (const struct spi_flash *flash, uint8_t opcode, uint16_t flags)
{
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
flash->state->command.write = opcode;
flash->state->command.write_flags = flags;
platform_mutex_unlock (&flash->state->lock);
return 0;
}
/**
* Read the device ID from the SPI flash.
*
* @param flash The flash to identify.
* @param vendor The buffer that will hold the vender ID. Null to ignore vendor ID.
* @param device The buffer that will hold the device ID. Null to ignore device ID.
*
* @return 0 if the identifier was successfully read or an error code.
*/
int spi_flash_get_device_id (const struct spi_flash *flash, uint8_t *vendor, uint16_t *device)
{
struct flash_xfer xfer;
int status = 0;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
if ((flash->state->device_id[0] == 0xff) || (flash->state->device_id[0] == 0)) {
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDID, flash->state->device_id,
sizeof (flash->state->device_id), 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
flash->state->device_id[0] = 0xff;
goto exit;
}
}
if (vendor != NULL) {
*vendor = flash->state->device_id[0];
}
if (device != NULL) {
*device = (flash->state->device_id[1] << 8) | flash->state->device_id[2];
}
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Get the size of the flash device.
*
* @param flash The flash to query.
* @param bytes The buffer that will hold the number of bytes in the device.
*
* @return 0 if the device size was successfully read or an error code.
*/
int spi_flash_get_device_size (const struct spi_flash *flash, uint32_t *bytes)
{
if ((flash == NULL) || (bytes == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
*bytes = flash->state->device_size;
return 0;
}
/**
* Issue commands to trigger a soft reset of the SPI flash device.
*
* @param flash The flash to reset.
* @param wait_ms The amount of time to wait after issuing the reset commands.
*
* @return 0 if the device was successfully reset or an error code.
*/
static int spi_flash_execute_reset_device (const struct spi_flash *flash, uint32_t wait_ms)
{
int status;
if (flash->state->command.reset == FLASH_CMD_RST) {
status = spi_flash_simple_command (flash, FLASH_CMD_RSTEN);
if (status != 0) {
return status;
}
}
status = spi_flash_simple_command (flash, flash->state->command.reset);
if (status == 0) {
platform_msleep (wait_ms);
}
return status;
}
/**
* Soft reset the SPI flash device.
*
* @param flash The flash to reset.
*
* @return 0 if the device was successfully reset or an error code.
*/
int spi_flash_reset_device (const struct spi_flash *flash)
{
int status;
uint16_t rst_addr_mode;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
if (!flash->state->command.reset) {
return SPI_FLASH_RESET_NOT_SUPPORTED;
}
if (flash->state->reset_3byte) {
/* If 4-byte address mode is cleared on reset, check device settings to see if this
* property has been overriden. */
status = spi_flash_is_4byte_address_mode_on_reset (flash);
if ((status == 0) || (status == SPI_FLASH_UNSUPPORTED_DEVICE)) {
rst_addr_mode = 0;
}
else if (status == 1) {
rst_addr_mode = FLASH_FLAG_4BYTE_ADDRESS;
}
else {
return status;
}
}
else {
rst_addr_mode = flash->state->addr_mode;
}
platform_mutex_lock (&flash->state->lock);
/* Block the reset if there is a write in progress. Issuing a reset in this case can cause data
* corruption and cause an indeterminate delay after the reset. In some cases, the reset will
* not get executed by the device. */
status = spi_flash_is_wip_set (flash);
if (status != 0) {
status = (status == 1) ? SPI_FLASH_WRITE_IN_PROGRESS : status;
goto exit;
}
/* We don't need to wait a long time, since we know the reset is not interrupting a write
* operation. */
status = spi_flash_execute_reset_device (flash, 1);
if (status == 0) {
flash->state->addr_mode = rst_addr_mode;
}
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Force a software reset of a SPI flash device. This is a simplified workflow that doesn't have
* any of the pre-checks and other flash state tracking present in the more advanced handling.
*
* Specifically this call will not:
* - Update the address mode of the device after executing the reset command. It is up to the
* caller to ensure the address mode is known after the reset.
* spi_flash_detect_4byte_address_mode can be used for this purpose.
* - Check to see if there is any write in progress before issuing the reset command. Issuing a
* reset during a program or erase operation can corrupt data on flash. Depending on the
* device, it can also have an effect on how much time it takes the device to come out of
* reset.
*
* @param flash The flash to reset.
* @param wait_ms The amount of time to wait after issuing the reset commands before returning. If
* this is 0, it will return immediately after issuing the commands.
*
* @return 0 if the reset commands were issued successfully or an error code.
*/
int spi_flash_force_reset_device (const struct spi_flash *flash, uint32_t wait_ms)
{
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
if (!flash->state->command.reset) {
return SPI_FLASH_RESET_NOT_SUPPORTED;
}
platform_mutex_lock (&flash->state->lock);
/* No effort is being made to determine a reasonable wait time after issuing the device reset.
* It will vary based on device and current state. Leave it to the caller to decide. */
status = spi_flash_execute_reset_device (flash, wait_ms);
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Clear the block protect bits in the main status register.
*
* @param flash The flash device to configure.
*
* @return 0 if the command was successful or an error code.
*/
int spi_flash_clear_block_protect (const struct spi_flash *flash)
{
struct flash_xfer xfer;
uint8_t reg[2] = {0};
uint8_t cmd_len = 1;
uint8_t mask = 0x83;
uint8_t cmpl_bp = 0;
uint8_t cmpl_mask = 0xff;
uint8_t vendor;
uint16_t device_id;
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
status = spi_flash_get_device_id (flash, &vendor, &device_id);
if (status != 0) {
return status;
}
platform_mutex_lock (&flash->state->lock);
if ((vendor != FLASH_ID_MICROCHIP) && (vendor != FLASH_ID_INFINEON) &&
(!((vendor == FLASH_ID_MACRONIX) &&
(FLASH_ID_DEVICE_SERIES (device_id) == FLASH_ID_MX66UW)))) {
/* Depending on the quad enable bit, the block clear needs to be handled differently:
* - If the quad bit is in SR1, then we need to be sure not to clear it.
* - On some devices, writing only 1 byte to SR1 will automatically clear SR2. On these
* devices we need to write both SR1 and SR2 to ensure the quad bit doesn't get
* cleared. */
switch (flash->state->quad_enable) {
case SPI_FLASH_SFDP_QUAD_QE_BIT6_SR1:
mask = 0xc3;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2:
cmd_len = 2;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2_35:
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDSR2, ®[1], 1, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
goto exit;
}
break;
default:
break;
}
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDSR, reg, cmd_len, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
goto exit;
}
/* On Winbond devices with QE bit in SR2, ensure bit 6 in SR2
* (complement block protect in SR1) is cleared to ensure block protection is cleared. */
if (vendor == FLASH_ID_WINBOND) {
cmpl_bp = 0x40;
cmpl_mask = 0x03;
}
if ((reg[0] & ~mask) || (reg[1] & cmpl_bp)) {
if (flash->state->quad_enable == SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2_35) {
cmd_len = 2;
}
reg[0] &= mask;
reg[1] &= cmpl_mask;
status = spi_flash_write_register (flash, FLASH_CMD_WRSR, reg, cmd_len,
flash->state->sr1_volatile);
}
}
else if (vendor == FLASH_ID_INFINEON) {
/* Infineon devices have a different block protect mechanism. The block protect bits are
* in the status register 1, but the content of STR1 is different from legacy devices.
* The block protect bits are cleared by writing 0 to the block protect bits. Along with that,
* single byte write has to be enabled by writing 0 to volatile configuration register 4. */
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDSR, reg, 1, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
goto exit;
}
if (reg[0] & 0x1c) {
reg[0] &= ~0x1c;
status = spi_flash_write_register_with_4byte_address (flash, FLASH_CMD_INFINEON_WR_ARG,
INFINEON_STR1V, reg, 1);
if (status != 0) {
goto exit;
}
}
/* Enable single byte write */
reg[0] = 0xC0;
status = spi_flash_write_register_with_4byte_address (flash, FLASH_CMD_INFINEON_WR_ARG,
INFINEON_CFR4V, reg, 1);
}
else if ((vendor == FLASH_ID_MACRONIX) &&
(FLASH_ID_DEVICE_SERIES (device_id) == FLASH_ID_MX66UW)) {
/* Macronix MX66U OSPI flash device has different block protect mechanism from Macronix
* SPI flash devices. The block protect bits in status register are different. Double page
* programming has to be enabled by writing to volatile configuration register 2. */
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDSR, reg, 1, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
goto exit;
}
if (reg[0] & 0x3c) {
reg[0] &= ~0x3c;
status = spi_flash_write_register (flash, FLASH_CMD_WRSR, reg, cmd_len,
flash->state->sr1_volatile);
if (status != 0) {
goto exit;
}
}
/* Modify ECC configuration to enable double page programming. */
reg[0] = 0x02;
status = spi_flash_write_register_with_4byte_address (flash, FLASH_CMD_MACRONIX_WRCR2,
MX66UW_CFR2V_ECS, reg, 1);
if (status != 0) {
goto exit;
}
}
else {
/* Microchip flash does not have block protect bits in the status register. Instead, there
* is a single command that unlocks all protection. This needs to be sent after every power
* cycle before any erase or program operations can be performed. */
FLASH_XFER_INIT_CMD_ONLY (xfer, FLASH_CMD_GBULK, 0);
status = flash->spi->xfer (flash->spi, &xfer);
}
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Transition the flash device to/from deep power down mode. While in deep power down mode, no
* commands will be executed by the flash device, except the command to release it from deep power
* down.
*
* @param flash The flash device to configure.
* @param enable 1 to enter deep power down mode or 0 to release the device from this mode.
*
* @return 0 if the command was successfully sent to the device or an error code.
*/
int spi_flash_deep_power_down (const struct spi_flash *flash, uint8_t enable)
{
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
if (!flash->state->command.enter_pwrdown) {
return (enable) ? SPI_FLASH_PWRDOWN_NOT_SUPPORTED : 0;
}
platform_mutex_lock (&flash->state->lock);
if (enable) {
status = spi_flash_simple_command (flash, flash->state->command.enter_pwrdown);
}
else {
status = spi_flash_simple_command (flash, flash->state->command.release_pwrdown);
}
if (status == 0) {
platform_msleep (100);
}
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Determine if the address mode of the flash device can be changed.
*
* @param flash The flash to query.
*
* @return 1 if the address mode of the device is fixed, 0 if it can be changed, or an error code.
*/
int spi_flash_is_address_mode_fixed (const struct spi_flash *flash)
{
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
if (flash->state->switch_4byte == SPI_FLASH_SFDP_4BYTE_MODE_INSTRUCTION_SET) {
return 1;
}
return ((flash->state->capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) !=
(FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR));
}
/**
* Determine if the flash device requires Write Enable to be set in order to switch address modes.
*
* @param flash The flash to query.
*
* @return 1 if write enable is required, 0 if it is not, or an error code. If the address mode
* cannot be switched, SPI_FLASH_ADDR_MODE_FIXED will be returned.
*/
int spi_flash_address_mode_requires_write_enable (const struct spi_flash *flash)
{
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
if (spi_flash_is_address_mode_fixed (flash)) {
return SPI_FLASH_ADDR_MODE_FIXED;
}
return (flash->state->switch_4byte == SPI_FLASH_SFDP_4BYTE_MODE_COMMAND_WRITE_ENABLE);
}
/**
* Determine if the flash device defaults to 4-byte address mode on device resets.
*
* @param flash The flash to query.
*
* @return 1 if the device defaults to 4-byte mode, 0 if 3-byte mode is the default, or an error
* code.
*/
int spi_flash_is_4byte_address_mode_on_reset (const struct spi_flash *flash)
{
struct flash_xfer xfer;
uint8_t vendor;
uint8_t cmd;
uint8_t mask;
uint8_t reg;
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
/* Handle fixed address mode. */
switch (flash->state->capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) {
case FLASH_CAP_3BYTE_ADDR:
return 0;
case FLASH_CAP_4BYTE_ADDR:
return 1;
}
/* Handle devices which don't support address mode switching. */
if (flash->state->switch_4byte == SPI_FLASH_SFDP_4BYTE_MODE_INSTRUCTION_SET) {
return 0;
}
/* Detecting address state on reset is vendor dependent. */
status = spi_flash_get_device_id (flash, &vendor, NULL);
if (status != 0) {
return status;
}
switch (vendor) {
case FLASH_ID_MACRONIX:
cmd = 0;
mask = 0;
break;
case FLASH_ID_WINBOND:
cmd = FLASH_CMD_RDSR3;
mask = WINBOND_4BYTE_DEFAULT;
break;
case FLASH_ID_MICRON:
case FLASH_ID_MICRON_X:
cmd = FLASH_CMD_RD_NV_CFG;
mask = MICRON_4BYTE_DEFAULT;
break;
default:
return SPI_FLASH_UNSUPPORTED_DEVICE;
}
if (cmd) {
platform_mutex_lock (&flash->state->lock);
FLASH_XFER_INIT_READ_REG (xfer, cmd, ®, 1, 0);
status = flash->spi->xfer (flash->spi, &xfer);
platform_mutex_unlock (&flash->state->lock);
if (status != 0) {
return status;
}
}
return ((vendor == FLASH_ID_MICRON) || (vendor == FLASH_ID_MICRON_X)) ?
!(reg & mask) : !!(reg & mask);
}
/**
* Determine if the requested address mode is supported by the flash device.
*
* @param flash The flash to query.
* @param mode 1 for 4-byte mode or 0 3-byte mode.
*
* @return 0 if the address mode is supported or an error code.
*/
static int spi_flash_supports_address_mode (const struct spi_flash *flash, uint8_t mode)
{
if (flash->state->switch_4byte == SPI_FLASH_SFDP_4BYTE_MODE_INSTRUCTION_SET) {
/* These devices don't support address mode switching. It is not recommended
* to send commands to flash that are not in the command list. Returning 3byte mode
* here ensures that upper layers like spi_flash_enable_4byte_address_mode don't send
* unsupported commands to flash. */
return (mode) ? SPI_FLASH_UNSUPPORTED_ADDR_MODE : SPI_FLASH_ADDR_MODE_FIXED;
}
switch (flash->state->capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) {
case FLASH_CAP_3BYTE_ADDR:
return (mode) ? SPI_FLASH_UNSUPPORTED_ADDR_MODE : SPI_FLASH_ADDR_MODE_FIXED;
case FLASH_CAP_4BYTE_ADDR:
return (mode) ? SPI_FLASH_ADDR_MODE_FIXED : SPI_FLASH_UNSUPPORTED_ADDR_MODE;
}
return 0;
}
/**
* Enable or disable 4-byte address mode for commands sent to the flash device.
*
* @param flash The flash to configure the address mode for.
* @param enable 1 to enable 4-byte mode or 0 to disable it (and go to 3-byte mode).
*
* @return 0 if the address mode was successfully configured or an error code.
*/
int spi_flash_enable_4byte_address_mode (const struct spi_flash *flash, uint8_t enable)
{
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
status = spi_flash_supports_address_mode (flash, enable);
if (status != 0) {
if (status == SPI_FLASH_ADDR_MODE_FIXED) {
status = 0;
}
goto exit;
}
if (flash->state->switch_4byte == SPI_FLASH_SFDP_4BYTE_MODE_COMMAND_WRITE_ENABLE) {
status = spi_flash_write_enable (flash);
if (status != 0) {
goto exit;
}
}
if (enable) {
status = spi_flash_simple_command (flash, FLASH_CMD_EN4B);
if (status == 0) {
flash->state->addr_mode = FLASH_FLAG_4BYTE_ADDRESS;
}
}
else {
status = spi_flash_simple_command (flash, FLASH_CMD_EX4B);
if (status == 0) {
flash->state->addr_mode = 0;
}
}
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Indicate if the SPI flash is operating in 4-byte address mode.
*
* This is just the state of the interface communicating with the SPI flash, so no commands are sent
* to the device. The interface and device must always be in sync regarding this setting. Use
* {@link spi_flash_detect_4byte_address_mode} or {@link spi_flash_enable_4byte_address_mode} to
* detect or configure the device state.
*
* @param flash The flash device to query.
*
* @return 1 if 4-byte address mode is enabled, 0 if it is not, or an error code.
*/
int spi_flash_is_4byte_address_mode (const struct spi_flash *flash)
{
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
return !!(flash->state->addr_mode);
}
/**
* Read the SPI flash state to determine what address mode the device is operating in.
*
* @param flash The flash to device query.
*
* @return 0 if the address mode was successfully determined or an error code.
*/
int spi_flash_detect_4byte_address_mode (const struct spi_flash *flash)
{
struct flash_xfer xfer;
uint8_t vendor;
uint8_t cmd = FLASH_CMD_RDSR3;
uint8_t reg;
int status;
int mask;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
if ((flash->state->capabilities & (FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) !=
(FLASH_CAP_3BYTE_ADDR | FLASH_CAP_4BYTE_ADDR)) {
return 0;
}
if (flash->state->switch_4byte == SPI_FLASH_SFDP_4BYTE_MODE_INSTRUCTION_SET) {
/* These devices support 3 byte commands to be sent with 3 byte address. */
return 0;
}
status = spi_flash_get_device_id (flash, &vendor, NULL);
if (status != 0) {
return status;
}
switch (vendor) {
case FLASH_ID_MACRONIX:
mask = MACRONIX_4BYTE_STATUS;
break;
case FLASH_ID_WINBOND:
mask = WINBOND_4BYTE_STATUS;
break;
case FLASH_ID_MICRON:
case FLASH_ID_MICRON_X:
mask = MICRON_4BYTE_STATES;
cmd = FLASH_CMD_RDSR_FLAG;
break;
default:
return SPI_FLASH_UNSUPPORTED_DEVICE;
}
platform_mutex_lock (&flash->state->lock);
FLASH_XFER_INIT_READ_REG (xfer, cmd, ®, 1, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
goto exit;
}
flash->state->addr_mode = (reg & mask) ? FLASH_FLAG_4BYTE_ADDRESS : 0;
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Specify the addressing mode that should be used to communicate with the SPI flash without
* sending any SPI commands.
*
* NOTE: Since no commands are sent to flash, it is possible to configure the interface incorrectly
* and cause flash access errors and/or system hangs. Be sure this is ONLY used in situations where
* the state of the flash device is 100% known. Prefer {@link spi_flash_detect_4byte_address_mode}
* in situations where it is not desired to change the current state of the flash device.
*
* @param flash The flash instance to update.
* @param enable 1 to use 4-byte more or 0 for 3-byte mode.
*
* @return 0 if the addressing mode was successfully set or an error code.
*/
int spi_flash_force_4byte_address_mode (const struct spi_flash *flash, uint8_t enable)
{
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
status = spi_flash_supports_address_mode (flash, enable);
if (status != 0) {
if (status == SPI_FLASH_ADDR_MODE_FIXED) {
status = 0;
}
goto exit;
}
if (enable) {
flash->state->addr_mode = FLASH_FLAG_4BYTE_ADDRESS;
}
else {
flash->state->addr_mode = 0;
}
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Enable and disable support for Quad SPI commands to the flash device.
*
* @param flash The flash to configure.
* @param enable 1 to enable Quad commands or 0 to disable them.
*
* @return 0 if the command was successful or an error code.
*/
int spi_flash_enable_quad_spi (const struct spi_flash *flash, uint8_t enable)
{
struct flash_xfer xfer;
uint8_t reg[2];
uint8_t cmd_len = 2;
uint8_t cmd = FLASH_CMD_RDSR;
bool volatile_wren;
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
switch (flash->state->quad_enable) {
case SPI_FLASH_SFDP_QUAD_NO_QE_BIT:
status = 0;
goto exit;
case SPI_FLASH_SFDP_QUAD_NO_QE_HOLD_DISABLE:
cmd = FLASH_CMD_RD_NV_CFG;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2_35:
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDSR2, ®[1], 1, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
goto exit;
}
cmd_len = 1;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT6_SR1:
cmd_len = 1;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT7_SR2:
cmd_len = 1;
cmd = FLASH_CMD_ALT_RDSR2;
break;
default:
break;
}
FLASH_XFER_INIT_READ_REG (xfer, cmd, reg, cmd_len, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
goto exit;
}
cmd = FLASH_CMD_WRSR;
volatile_wren = flash->state->sr1_volatile;
switch (flash->state->quad_enable) {
case SPI_FLASH_SFDP_QUAD_NO_QE_HOLD_DISABLE:
if (enable) {
reg[0] &= ~RESET_HOLD_ENABLE;
}
else {
reg[0] |= RESET_HOLD_ENABLE;
}
cmd = FLASH_CMD_WR_NV_CFG;
volatile_wren = false;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2:
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2_NO_CLR:
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2_35:
if (enable) {
reg[1] |= QSPI_ENABLE_BIT1;
}
else {
reg[1] &= ~QSPI_ENABLE_BIT1;
}
cmd_len = 2;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT6_SR1:
if (enable) {
reg[0] |= QSPI_ENABLE_BIT6;
}
else {
reg[0] &= ~QSPI_ENABLE_BIT6;
}
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT7_SR2:
if (enable) {
reg[0] |= QSPI_ENABLE_BIT7;
}
else {
reg[0] &= ~QSPI_ENABLE_BIT7;
}
cmd = FLASH_CMD_ALT_WRSR2;
volatile_wren = false;
break;
default:
break;
}
status = spi_flash_write_register (flash, cmd, reg, cmd_len, volatile_wren);
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Determine if the SPI flash has Quad SPI enabled or disabled.
*
* @param flash The flash device to query.
*
* @return 0 if Quad SPI is disabled, 1 if Quad SPI is enabled, or an error code.
*/
int spi_flash_is_quad_spi_enabled (const struct spi_flash *flash)
{
struct flash_xfer xfer;
uint8_t reg[2];
uint8_t cmd_len = 2;
uint8_t cmd = FLASH_CMD_RDSR;
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
switch (flash->state->quad_enable) {
case SPI_FLASH_SFDP_QUAD_NO_QE_BIT:
status = 1;
goto exit;
case SPI_FLASH_SFDP_QUAD_NO_QE_HOLD_DISABLE:
cmd = FLASH_CMD_RD_NV_CFG;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2_35:
cmd = FLASH_CMD_RDSR2;
cmd_len = 1;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT6_SR1:
cmd_len = 1;
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT7_SR2:
cmd = FLASH_CMD_ALT_RDSR2;
cmd_len = 1;
break;
default:
break;
}
FLASH_XFER_INIT_READ_REG (xfer, cmd, reg, cmd_len, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
goto exit;
}
switch (flash->state->quad_enable) {
case SPI_FLASH_SFDP_QUAD_NO_QE_HOLD_DISABLE:
status = !(reg[0] & RESET_HOLD_ENABLE);
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2_35:
reg[1] = reg[0];
/* fall through */ /* no break */
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2:
case SPI_FLASH_SFDP_QUAD_QE_BIT1_SR2_NO_CLR:
status = !!(reg[1] & QSPI_ENABLE_BIT1);
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT6_SR1:
status = !!(reg[0] & QSPI_ENABLE_BIT6);
break;
case SPI_FLASH_SFDP_QUAD_QE_BIT7_SR2:
status = !!(reg[0] & QSPI_ENABLE_BIT7);
break;
default:
break;
}
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Configure the output drive strength of the flash device, if necessary. If no drive strength
* configuration is needed for the device, nothing is done.
*
* @param flash The flash device to configure.
*
* @return 0 if the drive strength was successfully configured or an error code.
*/
int spi_flash_configure_drive_strength (const struct spi_flash *flash)
{
struct flash_xfer xfer;
uint8_t vendor;
uint8_t reg;
uint8_t data = 0x20; // 75% drive strength
int status = 0;
status = spi_flash_get_device_id (flash, &vendor, NULL);
if (status != 0) {
return status;
}
platform_mutex_lock (&flash->state->lock);
switch (vendor) {
case FLASH_ID_WINBOND:
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDSR3, ®, 1, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
break;
}
if (data != (reg & 0x60)) {
data |= (reg & ~0x60);
status = spi_flash_write_register (flash, 0x11, &data, 1, false);
if (status != 0) {
break;
}
FLASH_XFER_INIT_READ_REG (xfer, FLASH_CMD_RDSR3, ®, 1, 0);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
break;
}
if (reg != data) {
status = SPI_FLASH_CONFIG_FAILURE;
}
}
break;
}
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Read data from the SPI flash.
*
* @param flash The flash to read from.
* @param address The address to start reading from.
* @param data The buffer to hold the data that has been read.
* @param length The number of bytes to read.
*
* @return 0 if the bytes were read from flash or an error code.
*/
int spi_flash_read (const struct spi_flash *flash, uint32_t address, uint8_t *data, size_t length)
{
struct flash_xfer xfer;
int status;
if ((flash == NULL) || (data == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
SPI_FLASH_BOUNDS_CHECK (flash->state->device_size, address, length);
platform_mutex_lock (&flash->state->lock);
status = spi_flash_is_wip_set (flash);
if (status != 0) {
status = (status == 1) ? SPI_FLASH_WRITE_IN_PROGRESS : status;
goto exit;
}
FLASH_XFER_INIT_READ (xfer, flash->state->command.read, address,
flash->state->command.read_dummy, flash->state->command.read_mode, data, length,
flash->state->command.read_flags | flash->state->addr_mode);
status = flash->spi->xfer (flash->spi, &xfer);
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Get the size of a flash page for write operations.
*
* @param flash The flash to query.
* @param bytes Output for the number of bytes in a flash page.
*
* @return 0 if the page size was successfully read or an error code.
*/
int spi_flash_get_page_size (const struct spi_flash *flash, uint32_t *bytes)
{
if ((flash == NULL) || (bytes == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
/* All supported devices use a 256 byte page size. If necessary, this value can be read from
* the SFDP tables. */
*bytes = FLASH_PAGE_SIZE;
return 0;
}
/* API handler for get_page_size, minimum_write_per_page, get_sector_size, and get_block_size when
* statically initialized for read only access. */
int spi_flash_get_size_read_only (const struct flash *flash, uint32_t *bytes)
{
UNUSED (flash);
UNUSED (bytes);
return SPI_FLASH_READ_ONLY_INTERFACE;
}
/**
* Get the minimum number of bytes that must be written to a single flash page. Writing fewer bytes
* than the minimum to any page will still result in a minimum sized write to flash. The extra bytes
* that were written must be erased before they can be written again.
*
* @param flash The flash to query.
* @param bytes Output for the minimum number of bytes for a page write.
*
* @return 0 if the minimum write size was successfully read or an error code.
*/
int spi_flash_minimum_write_per_page (const struct spi_flash *flash, uint32_t *bytes)
{
if ((flash == NULL) || (bytes == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
*bytes = 1;
return 0;
}
/**
* Write data to the SPI flash. The flash needs to be erased prior to writing.
*
* @param flash The flash to write to.
* @param address The address to start writing to.
* @param data The data to write.
* @param length The number of bytes to write.
*
* @return The number of bytes written to the flash or an error code. Use ROT_IS_ERROR to check the
* return value.
*/
int spi_flash_write (const struct spi_flash *flash, uint32_t address, const uint8_t *data,
size_t length)
{
struct flash_xfer xfer;
uint32_t page = FLASH_PAGE_BASE (address);
uint32_t next = page + FLASH_PAGE_SIZE;
size_t remaining = length;
int status = 0;
if ((flash == NULL) || (data == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
SPI_FLASH_BOUNDS_CHECK (flash->state->device_size, address, length);
platform_mutex_lock (&flash->state->lock);
status = spi_flash_is_wip_set (flash);
if (status != 0) {
status = (status == 1) ? SPI_FLASH_WRITE_IN_PROGRESS : status;
goto exit;
}
while ((status == 0) && remaining) {
uint32_t end = address + remaining;
size_t write_len;
if (page != FLASH_PAGE_BASE (end)) {
write_len = next - address;
}
else {
write_len = remaining;
}
status = spi_flash_write_enable (flash);
if (status != 0) {
continue;
}
FLASH_XFER_INIT_WRITE (xfer, flash->state->command.write, address, 0, (uint8_t*) data,
write_len, flash->state->command.write_flags | flash->state->addr_mode);
status = flash->spi->xfer (flash->spi, &xfer);
if (status == 0) {
status = spi_flash_wait_for_write_completion (flash, -1, 1);
if (status == 0) {
remaining -= write_len;
data += write_len;
page = next;
address = next;
next += FLASH_PAGE_SIZE;
}
}
}
exit:
platform_mutex_unlock (&flash->state->lock);
length = length - remaining;
if (length) {
if (status != 0) {
debug_log_create_entry (DEBUG_LOG_SEVERITY_ERROR, DEBUG_LOG_COMPONENT_FLASH,
FLASH_LOGGING_INCOMPLETE_WRITE, address, status);
}
return length;
}
else {
return status;
}
}
/* API handler for write when statically initialized for read only access. */
int spi_flash_write_read_only (const struct flash *flash, uint32_t address, const uint8_t *data,
size_t length)
{
UNUSED (flash);
UNUSED (address);
UNUSED (data);
UNUSED (length);
return SPI_FLASH_READ_ONLY_INTERFACE;
}
/**
* Erase a region of flash.
*
* @param flash The flash to erase.
* @param address An address within the region to erase.
* @param erase_cmd The erase command to use.
* @param erase_flags Transfer flags for the command.
*
* @return 0 if the region was erased or an error code.
*/
static int spi_flash_erase_region (const struct spi_flash *flash, uint32_t address,
uint8_t erase_cmd, uint16_t erase_flags)
{
struct flash_xfer xfer;
int status;
if (address >= flash->state->device_size) {
return SPI_FLASH_ADDRESS_OUT_OF_RANGE;
}
platform_mutex_lock (&flash->state->lock);
status = spi_flash_is_wip_set (flash);
if (status != 0) {
status = (status == 1) ? SPI_FLASH_WRITE_IN_PROGRESS : status;
goto exit;
}
status = spi_flash_write_enable (flash);
if (status != 0) {
goto exit;
}
FLASH_XFER_INIT_NO_DATA (xfer, erase_cmd, address, erase_flags | flash->state->addr_mode);
status = flash->spi->xfer (flash->spi, &xfer);
if (status != 0) {
goto exit;
}
status = spi_flash_wait_for_write_completion (flash, -1, 0);
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Get the size of a flash sector for erase operations.
*
* @param flash The flash to query.
* @param bytes Output for the number of bytes in a flash sector.
*
* @return 0 if the sector size was successfully read or an error code.
*/
int spi_flash_get_sector_size (const struct spi_flash *flash, uint32_t *bytes)
{
if ((flash == NULL) || (bytes == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
/* It is possible to detect this value through SFDP. As more flash devices are supported, it
* may be necessary to parse this value from the SFDP tables. */
*bytes = FLASH_SECTOR_SIZE;
return 0;
}
/**
* Erase a 4kB sector of flash.
*
* @param flash The flash to erase.
* @param sector_addr An address within the sector to erase.
*
* @return 0 if the sector was erased or an error code.
*/
int spi_flash_sector_erase (const struct spi_flash *flash, uint32_t sector_addr)
{
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
return spi_flash_erase_region (flash, FLASH_SECTOR_BASE (sector_addr),
flash->state->command.erase_sector, flash->state->command.sector_flags);
}
/* API handler for sector_erase and block_erase when statically initialized for read only access. */
int spi_flash_erase_read_only (const struct flash *flash, uint32_t addr)
{
UNUSED (flash);
UNUSED (addr);
return SPI_FLASH_READ_ONLY_INTERFACE;
}
/**
* Get the size of a flash block for erase operations.
*
* @param flash The flash to query.
* @param bytes Output for the number of bytes in a flash block.
*
* @return 0 if the block size was successfully read or an error code.
*/
int spi_flash_get_block_size (const struct spi_flash *flash, uint32_t *bytes)
{
if ((flash == NULL) || (bytes == NULL)) {
return SPI_FLASH_INVALID_ARGUMENT;
}
/* It is possible to detect this value through SFDP. As more flash devices are supported, it
* may be necessary to parse this value from the SFDP tables. */
*bytes = FLASH_BLOCK_SIZE;
return 0;
}
/**
* Erase a 64kB block of flash.
*
* @param flash The flash to erase.
* @param block_addr An address within the block to erase.
*
* @return 0 if the block was erased or an error code.
*/
int spi_flash_block_erase (const struct spi_flash *flash, uint32_t block_addr)
{
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
return spi_flash_erase_region (flash, FLASH_BLOCK_BASE (block_addr),
flash->state->command.erase_block, flash->state->command.block_flags);
}
/**
* Erase the entire flash chip.
*
* @param flash The flash to erase.
*
* @return 0 if the flash chip was erased or an error code.
*/
int spi_flash_chip_erase (const struct spi_flash *flash)
{
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
status = spi_flash_is_wip_set (flash);
if (status != 0) {
status = (status == 1) ? SPI_FLASH_WRITE_IN_PROGRESS : status;
goto exit;
}
status = spi_flash_write_enable (flash);
if (status != 0) {
goto exit;
}
status = spi_flash_simple_command (flash, FLASH_CMD_CE);
if (status != 0) {
goto exit;
}
status = spi_flash_wait_for_write_completion (flash, -1, 0);
exit:
platform_mutex_unlock (&flash->state->lock);
return status;
}
/* API handler for chip_erase when statically initialized for read only access. */
int spi_flash_chip_erase_read_only (const struct flash *flash)
{
UNUSED (flash);
return SPI_FLASH_READ_ONLY_INTERFACE;
}
/**
* Determine if the flash is currently executing a write command.
*
* @param flash The flash instance to check.
*
* @return 0 if no write is in progress, 1 if there is, or an error code.
*/
int spi_flash_is_write_in_progress (const struct spi_flash *flash)
{
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
status = spi_flash_is_wip_set (flash);
platform_mutex_unlock (&flash->state->lock);
return status;
}
/**
* Wait for a write operation to complete.
*
* @param flash The flash instance that is executing a write operation.
* @param timeout The maximum number of milliseconds to wait for completion. A negative number will
* wait forever. 0 will return immediately.
*
* @return 0 if the write was completed or an error code.
*/
int spi_flash_wait_for_write (const struct spi_flash *flash, int32_t timeout)
{
int status;
if (flash == NULL) {
return SPI_FLASH_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash->state->lock);
status = spi_flash_wait_for_write_completion (flash, timeout, 0);
platform_mutex_unlock (&flash->state->lock);
return status;
}