blob: acde8d59992b9dd4e3697d3113dd97db837b685b [file] [log] [blame]
/*
* Copyright (c) 2018 Savoir-Faire Linux.
* Copyright (c) 2020 Peter Bigot Consulting, LLC
*
* This driver is heavily inspired from the spi_flash_w25qxxdv.c SPI NOR driver.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT jedec_spi_nor
#include <errno.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/init.h>
#include <string.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys_clock.h>
#include "spi_nor.h"
#include "jesd216.h"
#include "flash_priv.h"
LOG_MODULE_REGISTER(spi_nor, CONFIG_FLASH_LOG_LEVEL);
/* Device Power Management Notes
*
* These flash devices have several modes during operation:
* * When CSn is asserted (during a SPI operation) the device is
* active.
* * When CSn is deasserted the device enters a standby mode.
* * Some devices support a Deep Power-Down mode which reduces current
* to as little as 0.1% of standby.
*
* The power reduction from DPD is sufficient to warrant allowing its
* use even in cases where Zephyr's device power management is not
* available. This is selected through the SPI_NOR_IDLE_IN_DPD
* Kconfig option.
*
* When mapped to the Zephyr Device Power Management states:
* * PM_DEVICE_STATE_ACTIVE covers both active and standby modes;
* * PM_DEVICE_STATE_SUSPENDED, and PM_DEVICE_STATE_OFF all correspond to
* deep-power-down mode.
*/
#define SPI_NOR_MAX_ADDR_WIDTH 4
#if DT_INST_NODE_HAS_PROP(0, t_enter_dpd)
#define T_DP_MS DIV_ROUND_UP(DT_INST_PROP(0, t_enter_dpd), NSEC_PER_MSEC)
#else /* T_ENTER_DPD */
#define T_DP_MS 0
#endif /* T_ENTER_DPD */
#if DT_INST_NODE_HAS_PROP(0, t_exit_dpd)
#define T_RES1_MS DIV_ROUND_UP(DT_INST_PROP(0, t_exit_dpd), NSEC_PER_MSEC)
#endif /* T_EXIT_DPD */
#if DT_INST_NODE_HAS_PROP(0, dpd_wakeup_sequence)
#define T_DPDD_MS DIV_ROUND_UP(DT_INST_PROP_BY_IDX(0, dpd_wakeup_sequence, 0), NSEC_PER_MSEC)
#define T_CRDP_MS DIV_ROUND_UP(DT_INST_PROP_BY_IDX(0, dpd_wakeup_sequence, 1), NSEC_PER_MSEC)
#define T_RDP_MS DIV_ROUND_UP(DT_INST_PROP_BY_IDX(0, dpd_wakeup_sequence, 2), NSEC_PER_MSEC)
#else /* DPD_WAKEUP_SEQUENCE */
#define T_DPDD_MS 0
#endif /* DPD_WAKEUP_SEQUENCE */
/* Build-time data associated with the device. */
struct spi_nor_config {
/* Devicetree SPI configuration */
struct spi_dt_spec spi;
/* Runtime SFDP stores no static configuration. */
#ifndef CONFIG_SPI_NOR_SFDP_RUNTIME
/* Size of device in bytes, from size property */
uint32_t flash_size;
#ifdef CONFIG_FLASH_PAGE_LAYOUT
/* Flash page layout can be determined from devicetree. */
struct flash_pages_layout layout;
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
/* Expected JEDEC ID, from jedec-id property */
uint8_t jedec_id[SPI_NOR_MAX_ID_LEN];
#if defined(CONFIG_SPI_NOR_SFDP_MINIMAL)
/* Optional support for entering 32-bit address mode. */
uint8_t enter_4byte_addr;
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
#if defined(CONFIG_SPI_NOR_SFDP_DEVICETREE)
/* Length of BFP structure, in 32-bit words. */
uint8_t bfp_len;
/* Pointer to the BFP table as read from the device
* (little-endian stored words), from sfdp-bfp property
*/
const struct jesd216_bfp *bfp;
#endif /* CONFIG_SPI_NOR_SFDP_DEVICETREE */
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
/* Optional bits in SR to be cleared on startup.
*
* This information cannot be derived from SFDP.
*/
uint8_t has_lock;
};
/**
* struct spi_nor_data - Structure for defining the SPI NOR access
* @sem: The semaphore to access to the flash
*/
struct spi_nor_data {
struct k_sem sem;
#if DT_INST_NODE_HAS_PROP(0, has_dpd)
/* Low 32-bits of uptime counter at which device last entered
* deep power-down.
*/
uint32_t ts_enter_dpd;
#endif
/* Miscellaneous flags */
/* If set addressed operations should use 32-bit rather than
* 24-bit addresses.
*
* This is ignored if the access parameter to a command
* explicitly specifies 24-bit or 32-bit addressing.
*/
bool flag_access_32bit: 1;
/* Minimal SFDP stores no dynamic configuration. Runtime and
* devicetree store page size and erase_types; runtime also
* stores flash size and layout.
*/
#ifndef CONFIG_SPI_NOR_SFDP_MINIMAL
struct jesd216_erase_type erase_types[JESD216_NUM_ERASE_TYPES];
/* Number of bytes per page */
uint16_t page_size;
#ifdef CONFIG_SPI_NOR_SFDP_RUNTIME
/* Size of flash, in bytes */
uint32_t flash_size;
#ifdef CONFIG_FLASH_PAGE_LAYOUT
struct flash_pages_layout layout;
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
};
#ifdef CONFIG_SPI_NOR_SFDP_MINIMAL
/* The historically supported erase sizes. */
static const struct jesd216_erase_type minimal_erase_types[JESD216_NUM_ERASE_TYPES] = {
{
.cmd = SPI_NOR_CMD_BE,
.exp = 16,
},
{
.cmd = SPI_NOR_CMD_SE,
.exp = 12,
},
};
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
static int spi_nor_write_protection_set(const struct device *dev,
bool write_protect);
/* Get pointer to array of supported erase types. Static const for
* minimal, data for runtime and devicetree.
*/
static inline const struct jesd216_erase_type *
dev_erase_types(const struct device *dev)
{
#ifdef CONFIG_SPI_NOR_SFDP_MINIMAL
return minimal_erase_types;
#else /* CONFIG_SPI_NOR_SFDP_MINIMAL */
const struct spi_nor_data *data = dev->data;
return data->erase_types;
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
}
/* Get the size of the flash device. Data for runtime, constant for
* minimal and devicetree.
*/
static inline uint32_t dev_flash_size(const struct device *dev)
{
#ifdef CONFIG_SPI_NOR_SFDP_RUNTIME
const struct spi_nor_data *data = dev->data;
return data->flash_size;
#else /* CONFIG_SPI_NOR_SFDP_RUNTIME */
const struct spi_nor_config *cfg = dev->config;
return cfg->flash_size;
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
}
/* Get the flash device page size. Constant for minimal, data for
* runtime and devicetree.
*/
static inline uint16_t dev_page_size(const struct device *dev)
{
#ifdef CONFIG_SPI_NOR_SFDP_MINIMAL
return 256;
#else /* CONFIG_SPI_NOR_SFDP_MINIMAL */
const struct spi_nor_data *data = dev->data;
return data->page_size;
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
}
static const struct flash_parameters flash_nor_parameters = {
.write_block_size = 1,
.erase_value = 0xff,
};
/* Capture the time at which the device entered deep power-down. */
static inline void record_entered_dpd(const struct device *const dev)
{
#if DT_INST_NODE_HAS_PROP(0, has_dpd)
struct spi_nor_data *const driver_data = dev->data;
driver_data->ts_enter_dpd = k_uptime_get_32();
#endif
}
/* Check the current time against the time DPD was entered and delay
* until it's ok to initiate the DPD exit process.
*/
static inline void delay_until_exit_dpd_ok(const struct device *const dev)
{
#if DT_INST_NODE_HAS_PROP(0, has_dpd)
struct spi_nor_data *const driver_data = dev->data;
int32_t since = (int32_t)(k_uptime_get_32() - driver_data->ts_enter_dpd);
/* If the time is negative the 32-bit counter has wrapped,
* which is certainly long enough no further delay is
* required. Otherwise we have to check whether it's been
* long enough taking into account necessary delays for
* entering and exiting DPD.
*/
if (since >= 0) {
/* Subtract time required for DPD to be reached */
since -= T_DP_MS;
/* Subtract time required in DPD before exit */
since -= T_DPDD_MS;
/* If the adjusted time is negative we have to wait
* until it reaches zero before we can proceed.
*/
if (since < 0) {
k_sleep(K_MSEC((uint32_t)-since));
}
}
#endif /* DT_INST_NODE_HAS_PROP(0, has_dpd) */
}
/* Indicates that an access command includes bytes for the address.
* If not provided the opcode is not followed by address bytes.
*/
#define NOR_ACCESS_ADDRESSED BIT(0)
/* Indicates that addressed access uses a 24-bit address regardless of
* spi_nor_data::flag_32bit_addr.
*/
#define NOR_ACCESS_24BIT_ADDR BIT(1)
/* Indicates that addressed access uses a 32-bit address regardless of
* spi_nor_data::flag_32bit_addr.
*/
#define NOR_ACCESS_32BIT_ADDR BIT(2)
/* Indicates that an access command is performing a write. If not
* provided access is a read.
*/
#define NOR_ACCESS_WRITE BIT(7)
/*
* @brief Send an SPI command
*
* @param dev Device struct
* @param opcode The command to send
* @param access flags that determine how the command is constructed.
* See NOR_ACCESS_*.
* @param addr The address to send
* @param data The buffer to store or read the value
* @param length The size of the buffer
* @return 0 on success, negative errno code otherwise
*/
static int spi_nor_access(const struct device *const dev,
uint8_t opcode, unsigned int access,
off_t addr, void *data, size_t length)
{
const struct spi_nor_config *const driver_cfg = dev->config;
struct spi_nor_data *const driver_data = dev->data;
bool is_addressed = (access & NOR_ACCESS_ADDRESSED) != 0U;
bool is_write = (access & NOR_ACCESS_WRITE) != 0U;
uint8_t buf[5] = { 0 };
struct spi_buf spi_buf[2] = {
{
.buf = buf,
.len = 1,
},
{
.buf = data,
.len = length
}
};
buf[0] = opcode;
if (is_addressed) {
bool access_24bit = (access & NOR_ACCESS_24BIT_ADDR) != 0;
bool access_32bit = (access & NOR_ACCESS_32BIT_ADDR) != 0;
bool use_32bit = (access_32bit
|| (!access_24bit
&& driver_data->flag_access_32bit));
union {
uint32_t u32;
uint8_t u8[4];
} addr32 = {
.u32 = sys_cpu_to_be32(addr),
};
if (use_32bit) {
memcpy(&buf[1], &addr32.u8[0], 4);
spi_buf[0].len += 4;
} else {
memcpy(&buf[1], &addr32.u8[1], 3);
spi_buf[0].len += 3;
}
};
const struct spi_buf_set tx_set = {
.buffers = spi_buf,
.count = (length != 0) ? 2 : 1,
};
const struct spi_buf_set rx_set = {
.buffers = spi_buf,
.count = 2,
};
if (is_write) {
return spi_write_dt(&driver_cfg->spi, &tx_set);
}
return spi_transceive_dt(&driver_cfg->spi, &tx_set, &rx_set);
}
#define spi_nor_cmd_read(dev, opcode, dest, length) \
spi_nor_access(dev, opcode, 0, 0, dest, length)
#define spi_nor_cmd_addr_read(dev, opcode, addr, dest, length) \
spi_nor_access(dev, opcode, NOR_ACCESS_ADDRESSED, addr, dest, length)
#define spi_nor_cmd_write(dev, opcode) \
spi_nor_access(dev, opcode, NOR_ACCESS_WRITE, 0, NULL, 0)
#define spi_nor_cmd_addr_write(dev, opcode, addr, src, length) \
spi_nor_access(dev, opcode, NOR_ACCESS_WRITE | NOR_ACCESS_ADDRESSED, \
addr, (void *)src, length)
/**
* @brief Wait until the flash is ready
*
* @note The device must be externally acquired before invoking this
* function.
*
* This function should be invoked after every ERASE, PROGRAM, or
* WRITE_STATUS operation before continuing. This allows us to assume
* that the device is ready to accept new commands at any other point
* in the code.
*
* @param dev The device structure
* @return 0 on success, negative errno code otherwise
*/
static int spi_nor_wait_until_ready(const struct device *dev)
{
int ret;
uint8_t reg;
do {
ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDSR, &reg, sizeof(reg));
} while (!ret && (reg & SPI_NOR_WIP_BIT));
return ret;
}
#if defined(CONFIG_SPI_NOR_SFDP_RUNTIME) || defined(CONFIG_FLASH_JESD216_API)
/*
* @brief Read content from the SFDP hierarchy
*
* @note The device must be externally acquired before invoking this
* function.
*
* @param dev Device struct
* @param addr The address to send
* @param data The buffer to store or read the value
* @param length The size of the buffer
* @return 0 on success, negative errno code otherwise
*/
static int read_sfdp(const struct device *const dev,
off_t addr, void *data, size_t length)
{
/* READ_SFDP requires a 24-bit address followed by a single
* byte for a wait state. This is effected by using 32-bit
* address by shifting the 24-bit address up 8 bits.
*/
return spi_nor_access(dev, JESD216_CMD_READ_SFDP,
NOR_ACCESS_32BIT_ADDR | NOR_ACCESS_ADDRESSED,
addr << 8, data, length);
}
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
static int enter_dpd(const struct device *const dev)
{
int ret = 0;
if (IS_ENABLED(DT_INST_PROP(0, has_dpd))) {
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_DPD);
if (ret == 0) {
record_entered_dpd(dev);
}
}
return ret;
}
static int exit_dpd(const struct device *const dev)
{
int ret = 0;
if (IS_ENABLED(DT_INST_PROP(0, has_dpd))) {
delay_until_exit_dpd_ok(dev);
#if DT_INST_NODE_HAS_PROP(0, dpd_wakeup_sequence)
/* Assert CSn and wait for tCRDP.
*
* Unfortunately the SPI API doesn't allow us to
* control CSn so fake it by writing a known-supported
* single-byte command, hoping that'll hold the assert
* long enough. This is highly likely, since the
* duration is usually less than two SPI clock cycles.
*/
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDID);
/* Deassert CSn and wait for tRDP */
k_sleep(K_MSEC(T_RDP_MS));
#else /* DPD_WAKEUP_SEQUENCE */
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_RDPD);
if (ret == 0) {
#if DT_INST_NODE_HAS_PROP(0, t_exit_dpd)
k_sleep(K_MSEC(T_RES1_MS));
#endif /* T_EXIT_DPD */
}
#endif /* DPD_WAKEUP_SEQUENCE */
}
return ret;
}
/* Everything necessary to acquire owning access to the device.
*
* This means taking the lock and, if necessary, waking the device
* from deep power-down mode.
*/
static void acquire_device(const struct device *dev)
{
if (IS_ENABLED(CONFIG_MULTITHREADING)) {
struct spi_nor_data *const driver_data = dev->data;
k_sem_take(&driver_data->sem, K_FOREVER);
}
if (IS_ENABLED(CONFIG_SPI_NOR_IDLE_IN_DPD)) {
exit_dpd(dev);
}
}
/* Everything necessary to release access to the device.
*
* This means (optionally) putting the device into deep power-down
* mode, and releasing the lock.
*/
static void release_device(const struct device *dev)
{
if (IS_ENABLED(CONFIG_SPI_NOR_IDLE_IN_DPD)) {
enter_dpd(dev);
}
if (IS_ENABLED(CONFIG_MULTITHREADING)) {
struct spi_nor_data *const driver_data = dev->data;
k_sem_give(&driver_data->sem);
}
}
/**
* @brief Read the status register.
*
* @note The device must be externally acquired before invoking this
* function.
*
* @param dev Device struct
*
* @return the non-negative value of the status register, or an error code.
*/
static int spi_nor_rdsr(const struct device *dev)
{
uint8_t reg;
int ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDSR, &reg, sizeof(reg));
if (ret == 0) {
ret = reg;
}
return ret;
}
/**
* @brief Write the status register.
*
* @note The device must be externally acquired before invoking this
* function.
*
* @param dev Device struct
* @param sr The new value of the status register
*
* @return 0 on success or a negative error code.
*/
static int spi_nor_wrsr(const struct device *dev,
uint8_t sr)
{
int ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
if (ret == 0) {
ret = spi_nor_access(dev, SPI_NOR_CMD_WRSR, NOR_ACCESS_WRITE, 0, &sr,
sizeof(sr));
spi_nor_wait_until_ready(dev);
}
return ret;
}
#if DT_INST_NODE_HAS_PROP(0, mxicy_mx25r_power_mode)
/**
* @brief Read the configuration register.
*
* @note The device must be externally acquired before invoking this
* function.
*
* @param dev Device struct
*
* @return the non-negative value of the configuration register, or an error code.
*/
static int mxicy_rdcr(const struct device *dev)
{
uint16_t cr;
enum { CMD_RDCR = 0x15 };
int ret = spi_nor_cmd_read(dev, CMD_RDCR, &cr, sizeof(cr));
if (ret < 0) {
return ret;
}
return cr;
}
/**
* @brief Write the configuration register.
*
* @note The device must be externally acquired before invoking this
* function.
*
* @param dev Device struct
* @param cr The new value of the configuration register
*
* @return 0 on success or a negative error code.
*/
static int mxicy_wrcr(const struct device *dev,
uint16_t cr)
{
/* The configuration register bytes on the Macronix MX25R devices are
* written using the Write Status Register command where the configuration
* register bytes are written as two extra bytes after the status register.
* First read out the current status register to preserve the value.
*/
int sr = spi_nor_rdsr(dev);
if (sr < 0) {
LOG_ERR("Read status register failed: %d", sr);
return sr;
}
int ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
if (ret == 0) {
uint8_t data[] = {
sr,
cr & 0xFF, /* Configuration register 1 */
cr >> 8 /* Configuration register 2 */
};
ret = spi_nor_access(dev, SPI_NOR_CMD_WRSR, NOR_ACCESS_WRITE, 0, data,
sizeof(data));
spi_nor_wait_until_ready(dev);
}
return ret;
}
static int mxicy_configure(const struct device *dev, const uint8_t *jedec_id)
{
/* Low-power/high perf mode is second bit in configuration register 2 */
enum { LH_SWITCH_BIT = 9 };
const uint8_t JEDEC_MACRONIX_ID = 0xc2;
const uint8_t JEDEC_MX25R_TYPE_ID = 0x28;
int current_cr, new_cr, ret;
/* lh_switch enum index:
* 0: Ultra low power
* 1: High performance mode
*/
const bool use_high_perf = DT_INST_ENUM_IDX(0, mxicy_mx25r_power_mode);
/* Only supported on Macronix MX25R Ultra Low Power series. */
if (jedec_id[0] != JEDEC_MACRONIX_ID || jedec_id[1] != JEDEC_MX25R_TYPE_ID) {
LOG_WRN("L/H switch not supported for device id: %02x %02x %02x", jedec_id[0],
jedec_id[1], jedec_id[2]);
/* Do not return an error here because the flash still functions */
return 0;
}
acquire_device(dev);
/* Read current configuration register */
ret = mxicy_rdcr(dev);
if (ret < 0) {
return ret;
}
current_cr = ret;
LOG_DBG("Use high performance mode? %d", use_high_perf);
new_cr = current_cr;
WRITE_BIT(new_cr, LH_SWITCH_BIT, use_high_perf);
if (new_cr != current_cr) {
ret = mxicy_wrcr(dev, new_cr);
} else {
ret = 0;
}
if (ret < 0) {
LOG_ERR("Enable high performace mode failed: %d", ret);
}
release_device(dev);
return ret;
}
#endif /* DT_INST_NODE_HAS_PROP(0, mxicy_mx25r_power_mode) */
static int spi_nor_read(const struct device *dev, off_t addr, void *dest,
size_t size)
{
const size_t flash_size = dev_flash_size(dev);
int ret;
/* should be between 0 and flash size */
if ((addr < 0) || ((addr + size) > flash_size)) {
return -EINVAL;
}
acquire_device(dev);
ret = spi_nor_cmd_addr_read(dev, SPI_NOR_CMD_READ, addr, dest, size);
release_device(dev);
return ret;
}
static int spi_nor_write(const struct device *dev, off_t addr,
const void *src,
size_t size)
{
const size_t flash_size = dev_flash_size(dev);
const uint16_t page_size = dev_page_size(dev);
int ret = 0;
/* should be between 0 and flash size */
if ((addr < 0) || ((size + addr) > flash_size)) {
return -EINVAL;
}
acquire_device(dev);
ret = spi_nor_write_protection_set(dev, false);
if (ret == 0) {
while (size > 0) {
size_t to_write = size;
/* Don't write more than a page. */
if (to_write >= page_size) {
to_write = page_size;
}
/* Don't write across a page boundary */
if (((addr + to_write - 1U) / page_size)
!= (addr / page_size)) {
to_write = page_size - (addr % page_size);
}
spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
ret = spi_nor_cmd_addr_write(dev, SPI_NOR_CMD_PP, addr,
src, to_write);
if (ret != 0) {
break;
}
size -= to_write;
src = (const uint8_t *)src + to_write;
addr += to_write;
spi_nor_wait_until_ready(dev);
}
}
int ret2 = spi_nor_write_protection_set(dev, true);
if (!ret) {
ret = ret2;
}
release_device(dev);
return ret;
}
static int spi_nor_erase(const struct device *dev, off_t addr, size_t size)
{
const size_t flash_size = dev_flash_size(dev);
int ret = 0;
/* erase area must be subregion of device */
if ((addr < 0) || ((size + addr) > flash_size)) {
return -EINVAL;
}
/* address must be sector-aligned */
if (!SPI_NOR_IS_SECTOR_ALIGNED(addr)) {
return -EINVAL;
}
/* size must be a multiple of sectors */
if ((size % SPI_NOR_SECTOR_SIZE) != 0) {
return -EINVAL;
}
acquire_device(dev);
ret = spi_nor_write_protection_set(dev, false);
while ((size > 0) && (ret == 0)) {
spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
if (size == flash_size) {
/* chip erase */
spi_nor_cmd_write(dev, SPI_NOR_CMD_CE);
size -= flash_size;
} else {
const struct jesd216_erase_type *erase_types =
dev_erase_types(dev);
const struct jesd216_erase_type *bet = NULL;
for (uint8_t ei = 0; ei < JESD216_NUM_ERASE_TYPES; ++ei) {
const struct jesd216_erase_type *etp =
&erase_types[ei];
if ((etp->exp != 0)
&& SPI_NOR_IS_ALIGNED(addr, etp->exp)
&& SPI_NOR_IS_ALIGNED(size, etp->exp)
&& ((bet == NULL)
|| (etp->exp > bet->exp))) {
bet = etp;
}
}
if (bet != NULL) {
spi_nor_cmd_addr_write(dev, bet->cmd, addr, NULL, 0);
addr += BIT(bet->exp);
size -= BIT(bet->exp);
} else {
LOG_DBG("Can't erase %zu at 0x%lx",
size, (long)addr);
ret = -EINVAL;
}
}
#ifdef __XCC__
/*
* FIXME: remove this hack once XCC is fixed.
*
* Without this volatile return value, XCC would segfault
* compiling this file complaining about failure in CGPREP
* phase.
*/
volatile int xcc_ret =
#endif
spi_nor_wait_until_ready(dev);
}
int ret2 = spi_nor_write_protection_set(dev, true);
if (!ret) {
ret = ret2;
}
release_device(dev);
return ret;
}
/* @note The device must be externally acquired before invoking this
* function.
*/
static int spi_nor_write_protection_set(const struct device *dev,
bool write_protect)
{
int ret;
ret = spi_nor_cmd_write(dev, (write_protect) ?
SPI_NOR_CMD_WRDI : SPI_NOR_CMD_WREN);
if (IS_ENABLED(DT_INST_PROP(0, requires_ulbpr))
&& (ret == 0)
&& !write_protect) {
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_ULBPR);
}
return ret;
}
#if defined(CONFIG_FLASH_JESD216_API) || defined(CONFIG_SPI_NOR_SFDP_RUNTIME)
static int spi_nor_sfdp_read(const struct device *dev, off_t addr,
void *dest, size_t size)
{
acquire_device(dev);
int ret = read_sfdp(dev, addr, dest, size);
release_device(dev);
return ret;
}
#endif /* CONFIG_FLASH_JESD216_API || CONFIG_SPI_NOR_SFDP_RUNTIME */
static int spi_nor_read_jedec_id(const struct device *dev,
uint8_t *id)
{
if (id == NULL) {
return -EINVAL;
}
acquire_device(dev);
int ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDID, id, SPI_NOR_MAX_ID_LEN);
release_device(dev);
return ret;
}
/* Put the device into the appropriate address mode, if supported.
*
* On successful return spi_nor_data::flag_access_32bit has been set
* (cleared) if the device is configured for 4-byte (3-byte) addresses
* for read, write, and erase commands.
*
* @param dev the device
*
* @param enter_4byte_addr the Enter 4-Byte Addressing bit set from
* DW16 of SFDP BFP. A value of all zeros or all ones is interpreted
* as "not supported".
*
* @retval -ENOTSUP if 4-byte addressing is supported but not in a way
* that the driver can handle.
* @retval negative codes if the attempt was made and failed
* @retval 0 if the device is successfully left in 24-bit mode or
* reconfigured to 32-bit mode.
*/
static int spi_nor_set_address_mode(const struct device *dev,
uint8_t enter_4byte_addr)
{
int ret = 0;
/* Do nothing if not provided (either no bits or all bits
* set).
*/
if ((enter_4byte_addr == 0)
|| (enter_4byte_addr == 0xff)) {
return 0;
}
LOG_DBG("Checking enter-4byte-addr %02x", enter_4byte_addr);
/* This currently only supports command 0xB7 (Enter 4-Byte
* Address Mode), with or without preceding WREN.
*/
if ((enter_4byte_addr & 0x03) == 0) {
return -ENOTSUP;
}
acquire_device(dev);
if ((enter_4byte_addr & 0x02) != 0) {
/* Enter after WREN. */
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_WREN);
}
if (ret == 0) {
ret = spi_nor_cmd_write(dev, SPI_NOR_CMD_4BA);
}
if (ret == 0) {
struct spi_nor_data *data = dev->data;
data->flag_access_32bit = true;
}
release_device(dev);
return ret;
}
#ifndef CONFIG_SPI_NOR_SFDP_MINIMAL
static int spi_nor_process_bfp(const struct device *dev,
const struct jesd216_param_header *php,
const struct jesd216_bfp *bfp)
{
struct spi_nor_data *data = dev->data;
struct jesd216_erase_type *etp = data->erase_types;
const size_t flash_size = jesd216_bfp_density(bfp) / 8U;
LOG_INF("%s: %u MiBy flash", dev->name, (uint32_t)(flash_size >> 20));
/* Copy over the erase types, preserving their order. (The
* Sector Map Parameter table references them by index.)
*/
memset(data->erase_types, 0, sizeof(data->erase_types));
for (uint8_t ti = 1; ti <= ARRAY_SIZE(data->erase_types); ++ti) {
if (jesd216_bfp_erase(bfp, ti, etp) == 0) {
LOG_DBG("Erase %u with %02x", (uint32_t)BIT(etp->exp), etp->cmd);
}
++etp;
}
data->page_size = jesd216_bfp_page_size(php, bfp);
#ifdef CONFIG_SPI_NOR_SFDP_RUNTIME
data->flash_size = flash_size;
#else /* CONFIG_SPI_NOR_SFDP_RUNTIME */
if (flash_size != dev_flash_size(dev)) {
LOG_ERR("BFP flash size mismatch with devicetree");
return -EINVAL;
}
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
LOG_DBG("Page size %u bytes", data->page_size);
/* If 4-byte addressing is supported, switch to it. */
if (jesd216_bfp_addrbytes(bfp) != JESD216_SFDP_BFP_DW1_ADDRBYTES_VAL_3B) {
struct jesd216_bfp_dw16 dw16;
int rc = 0;
if (jesd216_bfp_decode_dw16(php, bfp, &dw16) == 0) {
rc = spi_nor_set_address_mode(dev, dw16.enter_4ba);
}
if (rc != 0) {
LOG_ERR("Unable to enter 4-byte mode: %d\n", rc);
return rc;
}
}
return 0;
}
static int spi_nor_process_sfdp(const struct device *dev)
{
int rc;
#if defined(CONFIG_SPI_NOR_SFDP_RUNTIME)
/* For runtime we need to read the SFDP table, identify the
* BFP block, and process it.
*/
const uint8_t decl_nph = 2;
union {
/* We only process BFP so use one parameter block */
uint8_t raw[JESD216_SFDP_SIZE(decl_nph)];
struct jesd216_sfdp_header sfdp;
} u;
const struct jesd216_sfdp_header *hp = &u.sfdp;
rc = spi_nor_sfdp_read(dev, 0, u.raw, sizeof(u.raw));
if (rc != 0) {
LOG_ERR("SFDP read failed: %d", rc);
return rc;
}
uint32_t magic = jesd216_sfdp_magic(hp);
if (magic != JESD216_SFDP_MAGIC) {
LOG_ERR("SFDP magic %08x invalid", magic);
return -EINVAL;
}
LOG_INF("%s: SFDP v %u.%u AP %x with %u PH", dev->name,
hp->rev_major, hp->rev_minor, hp->access, 1 + hp->nph);
const struct jesd216_param_header *php = hp->phdr;
const struct jesd216_param_header *phpe = php + MIN(decl_nph, 1 + hp->nph);
while (php != phpe) {
uint16_t id = jesd216_param_id(php);
LOG_INF("PH%u: %04x rev %u.%u: %u DW @ %x",
(php - hp->phdr), id, php->rev_major, php->rev_minor,
php->len_dw, jesd216_param_addr(php));
if (id == JESD216_SFDP_PARAM_ID_BFP) {
union {
uint32_t dw[MIN(php->len_dw, 20)];
struct jesd216_bfp bfp;
} u;
const struct jesd216_bfp *bfp = &u.bfp;
rc = spi_nor_sfdp_read(dev, jesd216_param_addr(php), u.dw, sizeof(u.dw));
if (rc == 0) {
rc = spi_nor_process_bfp(dev, php, bfp);
}
if (rc != 0) {
LOG_INF("SFDP BFP failed: %d", rc);
break;
}
}
++php;
}
#elif defined(CONFIG_SPI_NOR_SFDP_DEVICETREE)
/* For devicetree we need to synthesize a parameter header and
* process the stored BFP data as if we had read it.
*/
const struct spi_nor_config *cfg = dev->config;
struct jesd216_param_header bfp_hdr = {
.len_dw = cfg->bfp_len,
};
rc = spi_nor_process_bfp(dev, &bfp_hdr, cfg->bfp);
#else
#error Unhandled SFDP choice
#endif
return rc;
}
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static int setup_pages_layout(const struct device *dev)
{
int rv = 0;
#if defined(CONFIG_SPI_NOR_SFDP_RUNTIME)
struct spi_nor_data *data = dev->data;
const size_t flash_size = dev_flash_size(dev);
const uint32_t layout_page_size = CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE;
uint8_t exp = 0;
/* Find the smallest erase size. */
for (size_t i = 0; i < ARRAY_SIZE(data->erase_types); ++i) {
const struct jesd216_erase_type *etp = &data->erase_types[i];
if ((etp->cmd != 0)
&& ((exp == 0) || (etp->exp < exp))) {
exp = etp->exp;
}
}
if (exp == 0) {
return -ENOTSUP;
}
uint32_t erase_size = BIT(exp);
/* Error if layout page size is not a multiple of smallest
* erase size.
*/
if ((layout_page_size % erase_size) != 0) {
LOG_ERR("layout page %u not compatible with erase size %u",
layout_page_size, erase_size);
return -EINVAL;
}
/* Warn but accept layout page sizes that leave inaccessible
* space.
*/
if ((flash_size % layout_page_size) != 0) {
LOG_INF("layout page %u wastes space with device size %zu",
layout_page_size, flash_size);
}
data->layout.pages_size = layout_page_size;
data->layout.pages_count = flash_size / layout_page_size;
LOG_DBG("layout %u x %u By pages", data->layout.pages_count, data->layout.pages_size);
#elif defined(CONFIG_SPI_NOR_SFDP_DEVICETREE)
const struct spi_nor_config *cfg = dev->config;
const struct flash_pages_layout *layout = &cfg->layout;
const size_t flash_size = dev_flash_size(dev);
size_t layout_size = layout->pages_size * layout->pages_count;
if (flash_size != layout_size) {
LOG_ERR("device size %u mismatch %zu * %zu By pages",
flash_size, layout->pages_count, layout->pages_size);
return -EINVAL;
}
#else /* CONFIG_SPI_NOR_SFDP_RUNTIME */
#error Unhandled SFDP choice
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
return rv;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
/**
* @brief Configure the flash
*
* @param dev The flash device structure
* @param info The flash info structure
* @return 0 on success, negative errno code otherwise
*/
static int spi_nor_configure(const struct device *dev)
{
const struct spi_nor_config *cfg = dev->config;
uint8_t jedec_id[SPI_NOR_MAX_ID_LEN];
int rc;
/* Validate bus and CS is ready */
if (!spi_is_ready_dt(&cfg->spi)) {
return -ENODEV;
}
/* After a soft-reset the flash might be in DPD or busy writing/erasing.
* Exit DPD and wait until flash is ready.
*/
acquire_device(dev);
rc = spi_nor_rdsr(dev);
if (rc > 0 && (rc & SPI_NOR_WIP_BIT)) {
LOG_WRN("Waiting until flash is ready");
spi_nor_wait_until_ready(dev);
}
release_device(dev);
/* now the spi bus is configured, we can verify SPI
* connectivity by reading the JEDEC ID.
*/
rc = spi_nor_read_jedec_id(dev, jedec_id);
if (rc != 0) {
LOG_ERR("JEDEC ID read failed: %d", rc);
return -ENODEV;
}
#ifndef CONFIG_SPI_NOR_SFDP_RUNTIME
/* For minimal and devicetree we need to check the JEDEC ID
* against the one from devicetree, to ensure we didn't find a
* device that has different parameters.
*/
if (memcmp(jedec_id, cfg->jedec_id, sizeof(jedec_id)) != 0) {
LOG_ERR("Device id %02x %02x %02x does not match config %02x %02x %02x",
jedec_id[0], jedec_id[1], jedec_id[2],
cfg->jedec_id[0], cfg->jedec_id[1], cfg->jedec_id[2]);
return -EINVAL;
}
#endif
/* Check for block protect bits that need to be cleared. This
* information cannot be determined from SFDP content, so the
* devicetree node property must be set correctly for any device
* that powers up with block protect enabled.
*/
if (cfg->has_lock != 0) {
acquire_device(dev);
rc = spi_nor_rdsr(dev);
/* Only clear if RDSR worked and something's set. */
if (rc > 0) {
rc = spi_nor_wrsr(dev, rc & ~cfg->has_lock);
}
if (rc != 0) {
LOG_ERR("BP clear failed: %d\n", rc);
return -ENODEV;
}
release_device(dev);
}
#ifdef CONFIG_SPI_NOR_SFDP_MINIMAL
/* For minimal we support some overrides from specific
* devicertee properties.
*/
if (cfg->enter_4byte_addr != 0) {
rc = spi_nor_set_address_mode(dev, cfg->enter_4byte_addr);
if (rc != 0) {
LOG_ERR("Unable to enter 4-byte mode: %d\n", rc);
return -ENODEV;
}
}
#else /* CONFIG_SPI_NOR_SFDP_MINIMAL */
/* For devicetree and runtime we need to process BFP data and
* set up or validate page layout.
*/
rc = spi_nor_process_sfdp(dev);
if (rc != 0) {
LOG_ERR("SFDP read failed: %d", rc);
return -ENODEV;
}
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
rc = setup_pages_layout(dev);
if (rc != 0) {
LOG_ERR("layout setup failed: %d", rc);
return -ENODEV;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#endif /* CONFIG_SPI_NOR_SFDP_MINIMAL */
#if DT_INST_NODE_HAS_PROP(0, mxicy_mx25r_power_mode)
/* Do not fail init if setting configuration register fails */
(void) mxicy_configure(dev, jedec_id);
#endif /* DT_INST_NODE_HAS_PROP(0, mxicy_mx25r_power_mode) */
if (IS_ENABLED(CONFIG_SPI_NOR_IDLE_IN_DPD)
&& (enter_dpd(dev) != 0)) {
return -ENODEV;
}
return 0;
}
/**
* @brief Initialize and configure the flash
*
* @param name The flash name
* @return 0 on success, negative errno code otherwise
*/
static int spi_nor_init(const struct device *dev)
{
if (IS_ENABLED(CONFIG_MULTITHREADING)) {
struct spi_nor_data *const driver_data = dev->data;
k_sem_init(&driver_data->sem, 1, K_SEM_MAX_LIMIT);
}
return spi_nor_configure(dev);
}
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
static void spi_nor_pages_layout(const struct device *dev,
const struct flash_pages_layout **layout,
size_t *layout_size)
{
/* Data for runtime, const for devicetree and minimal. */
#ifdef CONFIG_SPI_NOR_SFDP_RUNTIME
const struct spi_nor_data *data = dev->data;
*layout = &data->layout;
#else /* CONFIG_SPI_NOR_SFDP_RUNTIME */
const struct spi_nor_config *cfg = dev->config;
*layout = &cfg->layout;
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
*layout_size = 1;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
static const struct flash_parameters *
flash_nor_get_parameters(const struct device *dev)
{
ARG_UNUSED(dev);
return &flash_nor_parameters;
}
static const struct flash_driver_api spi_nor_api = {
.read = spi_nor_read,
.write = spi_nor_write,
.erase = spi_nor_erase,
.get_parameters = flash_nor_get_parameters,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
.page_layout = spi_nor_pages_layout,
#endif
#if defined(CONFIG_FLASH_JESD216_API)
.sfdp_read = spi_nor_sfdp_read,
.read_jedec_id = spi_nor_read_jedec_id,
#endif
};
#ifndef CONFIG_SPI_NOR_SFDP_RUNTIME
/* We need to know the size and ID of the configuration data we're
* using so we can disable the device we see at runtime if it isn't
* compatible with what we're taking from devicetree or minimal.
*/
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, jedec_id),
"jedec,spi-nor jedec-id required for non-runtime SFDP");
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
/* For devicetree or minimal page layout we need to know the size of
* the device. We can't extract it from the raw BFP data, so require
* it to be present in devicetree.
*/
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, size),
"jedec,spi-nor size required for non-runtime SFDP page layout");
/* instance 0 size in bytes */
#define INST_0_BYTES (DT_INST_PROP(0, size) / 8)
BUILD_ASSERT(SPI_NOR_IS_SECTOR_ALIGNED(CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE),
"SPI_NOR_FLASH_LAYOUT_PAGE_SIZE must be multiple of 4096");
/* instance 0 page count */
#define LAYOUT_PAGES_COUNT (INST_0_BYTES / CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE)
BUILD_ASSERT((CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE * LAYOUT_PAGES_COUNT)
== INST_0_BYTES,
"SPI_NOR_FLASH_LAYOUT_PAGE_SIZE incompatible with flash size");
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
#ifdef CONFIG_SPI_NOR_SFDP_DEVICETREE
BUILD_ASSERT(DT_INST_NODE_HAS_PROP(0, sfdp_bfp),
"jedec,spi-nor sfdp-bfp required for devicetree SFDP");
static const __aligned(4) uint8_t bfp_data_0[] = DT_INST_PROP(0, sfdp_bfp);
#endif /* CONFIG_SPI_NOR_SFDP_DEVICETREE */
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
#if DT_INST_NODE_HAS_PROP(0, has_lock)
/* Currently we only know of devices where the BP bits are present in
* the first byte of the status register. Complain if that changes.
*/
BUILD_ASSERT(DT_INST_PROP(0, has_lock) == (DT_INST_PROP(0, has_lock) & 0xFF),
"Need support for lock clear beyond SR1");
#endif
static const struct spi_nor_config spi_nor_config_0 = {
.spi = SPI_DT_SPEC_INST_GET(0, SPI_WORD_SET(8),
CONFIG_SPI_NOR_CS_WAIT_DELAY),
#if !defined(CONFIG_SPI_NOR_SFDP_RUNTIME)
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
.layout = {
.pages_count = LAYOUT_PAGES_COUNT,
.pages_size = CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE,
},
#undef LAYOUT_PAGES_COUNT
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
.flash_size = DT_INST_PROP(0, size) / 8,
.jedec_id = DT_INST_PROP(0, jedec_id),
#if DT_INST_NODE_HAS_PROP(0, has_lock)
.has_lock = DT_INST_PROP(0, has_lock),
#endif
#if defined(CONFIG_SPI_NOR_SFDP_MINIMAL) \
&& DT_INST_NODE_HAS_PROP(0, enter_4byte_addr)
.enter_4byte_addr = DT_INST_PROP(0, enter_4byte_addr),
#endif
#ifdef CONFIG_SPI_NOR_SFDP_DEVICETREE
.bfp_len = sizeof(bfp_data_0) / 4,
.bfp = (const struct jesd216_bfp *)bfp_data_0,
#endif /* CONFIG_SPI_NOR_SFDP_DEVICETREE */
#endif /* CONFIG_SPI_NOR_SFDP_RUNTIME */
};
static struct spi_nor_data spi_nor_data_0;
DEVICE_DT_INST_DEFINE(0, &spi_nor_init, NULL,
&spi_nor_data_0, &spi_nor_config_0,
POST_KERNEL, CONFIG_SPI_NOR_INIT_PRIORITY,
&spi_nor_api);