blob: eddba3de5cfbcfff71810d7fe7b09624b766a2bd [file] [log] [blame]
/*
* Copyright (c) 2019-2021, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nordic_qspi_nor
#include <errno.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/init.h>
#include <zephyr/pm/device.h>
#include <zephyr/drivers/pinctrl.h>
#include <soc.h>
#include <string.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(qspi_nor, CONFIG_FLASH_LOG_LEVEL);
#include "spi_nor.h"
#include "jesd216.h"
#include "flash_priv.h"
#include <nrfx_qspi.h>
#include <hal/nrf_clock.h>
#include <hal/nrf_gpio.h>
struct qspi_nor_data {
#ifdef CONFIG_MULTITHREADING
/* The semaphore to control exclusive access on write/erase. */
struct k_sem trans;
/* The semaphore to control exclusive access to the device. */
struct k_sem sem;
/* The semaphore to indicate that transfer has completed. */
struct k_sem sync;
/* The semaphore to control driver init/uninit. */
struct k_sem count;
#else /* CONFIG_MULTITHREADING */
/* A flag that signals completed transfer when threads are
* not enabled.
*/
volatile bool ready;
#endif /* CONFIG_MULTITHREADING */
#if defined(CONFIG_SOC_SERIES_NRF53X)
bool keep_base_clock_div_set;
#endif
};
struct qspi_nor_config {
nrfx_qspi_config_t nrfx_cfg;
/* Size from devicetree, in bytes */
uint32_t size;
/* JEDEC id from devicetree */
uint8_t id[SPI_NOR_MAX_ID_LEN];
#ifdef CONFIG_PINCTRL
const struct pinctrl_dev_config *pcfg;
#endif
};
/* Status register bits */
#define QSPI_SECTOR_SIZE SPI_NOR_SECTOR_SIZE
#define QSPI_BLOCK_SIZE SPI_NOR_BLOCK_SIZE
/* instance 0 flash size in bytes */
#if DT_INST_NODE_HAS_PROP(0, size_in_bytes)
#define INST_0_BYTES (DT_INST_PROP(0, size_in_bytes))
#elif DT_INST_NODE_HAS_PROP(0, size)
#define INST_0_BYTES (DT_INST_PROP(0, size) / 8)
#else
#error "No size specified. 'size' or 'size-in-bytes' must be set"
#endif
BUILD_ASSERT(!(DT_INST_NODE_HAS_PROP(0, size_in_bytes) && DT_INST_NODE_HAS_PROP(0, size)),
"Node " DT_NODE_PATH(DT_DRV_INST(0)) " has both size and size-in-bytes "
"properties; use exactly one");
/*
* Determine a configuration value (INST_0_SCK_CFG) to be used to achieve the
* SCK frequency specified in DT and, if needed, a divider (BASE_CLOCK_DIV) for
* the clock from which the SCK frequency is derived.
*/
#define INST_0_SCK_FREQUENCY DT_INST_PROP(0, sck_frequency)
BUILD_ASSERT(INST_0_SCK_FREQUENCY >= (NRF_QSPI_BASE_CLOCK_FREQ / 16),
"Unsupported SCK frequency.");
#if defined(CONFIG_SOC_SERIES_NRF53X)
/*
* On nRF53 Series SoCs, the highest SCK frequencies can only be achieved
* when the HFCLK192M clock divider is changed from the default /4 setting.
* Such change results in increased power consumption, so the divider needs
* to be changed only for periods when it is actually needed.
*/
#if (INST_0_SCK_FREQUENCY >= NRF_QSPI_BASE_CLOCK_FREQ)
/* Use HFCLK192M / 1 / (2*1) = 96 MHz */
#define BASE_CLOCK_DIV NRF_CLOCK_HFCLK_DIV_1
#define INST_0_SCK_CFG NRF_QSPI_FREQ_DIV1
#elif (INST_0_SCK_FREQUENCY >= (NRF_QSPI_BASE_CLOCK_FREQ / 2))
/* Use HFCLK192M / 2 / (2*1) = 48 MHz */
#define BASE_CLOCK_DIV NRF_CLOCK_HFCLK_DIV_2
#define INST_0_SCK_CFG NRF_QSPI_FREQ_DIV1
#elif (INST_0_SCK_FREQUENCY >= (NRF_QSPI_BASE_CLOCK_FREQ / 3))
/* Use HFCLK192M / 1 / (2*3) = 32 MHz */
#define BASE_CLOCK_DIV NRF_CLOCK_HFCLK_DIV_1
#define INST_0_SCK_CFG NRF_QSPI_FREQ_DIV3
#elif (INST_0_SCK_FREQUENCY >= (NRF_QSPI_BASE_CLOCK_FREQ / 4))
/* Use HFCLK192M / 4 / (2*1) = 24 MHz */
/* BASE_CLOCK_DIV not defined => the default NRF_CLOCK_HFCLK_DIV_4 is used. */
#define INST_0_SCK_CFG NRF_QSPI_FREQ_DIV1
#elif (INST_0_SCK_FREQUENCY >= (NRF_QSPI_BASE_CLOCK_FREQ / 6))
/* Use HFCLK192M / 2 / (2*3) = 16 MHz */
#define BASE_CLOCK_DIV NRF_CLOCK_HFCLK_DIV_2
#define INST_0_SCK_CFG NRF_QSPI_FREQ_DIV3
#else
/* BASE_CLOCK_DIV not defined => the default NRF_CLOCK_HFCLK_DIV_4 is used. */
#define INST_0_SCK_CFG (ceiling_fraction(NRF_QSPI_BASE_CLOCK_FREQ / 4, \
INST_0_SCK_FREQUENCY) - 1)
#endif
#else
/*
* On nRF52 Series SoCs, the base clock divider is not configurable,
* so BASE_CLOCK_DIV is not defined.
*/
#if (INST_0_SCK_FREQUENCY >= NRF_QSPI_BASE_CLOCK_FREQ)
#define INST_0_SCK_CFG NRF_QSPI_FREQ_DIV1
#else
#define INST_0_SCK_CFG (ceiling_fraction(NRF_QSPI_BASE_CLOCK_FREQ, \
INST_0_SCK_FREQUENCY) - 1)
#endif
#endif /* defined(CONFIG_SOC_SERIES_NRF53X) */
/* 0 for MODE0 (CPOL=0, CPHA=0), 1 for MODE3 (CPOL=1, CPHA=1). */
#define INST_0_SPI_MODE DT_INST_PROP(0, cpol)
BUILD_ASSERT(DT_INST_PROP(0, cpol) == DT_INST_PROP(0, cpha),
"Invalid combination of \"cpol\" and \"cpha\" properties.");
/* for accessing devicetree properties of the bus node */
#define QSPI_NODE DT_INST_BUS(0)
#define QSPI_PROP_AT(prop, idx) DT_PROP_BY_IDX(QSPI_NODE, prop, idx)
#define QSPI_PROP_LEN(prop) DT_PROP_LEN(QSPI_NODE, prop)
#define INST_0_QER _CONCAT(JESD216_DW15_QER_VAL_, \
DT_STRING_TOKEN(DT_DRV_INST(0), \
quad_enable_requirements))
#define IS_EQUAL(x, y) ((x) == (y))
#define SR1_WRITE_CLEARS_SR2 IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v1)
#define SR2_WRITE_NEEDS_SR1 (IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v1) || \
IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v4) || \
IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v5))
#define QER_IS_S2B1 (IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v1) || \
IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v4) || \
IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v5) || \
IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v6))
BUILD_ASSERT((IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_NONE)
|| IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S1B6)
|| IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v1)
|| IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v4)
|| IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v5)
|| IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v6)),
"Driver only supports NONE, S1B6, S2B1v1, S2B1v4, S2B1v5 or S2B1v6 for quad-enable-requirements");
#define INST_0_4BA DT_INST_PROP_OR(0, enter_4byte_addr, 0)
#if (INST_0_4BA != 0)
BUILD_ASSERT(((INST_0_4BA & 0x03) != 0),
"Driver only supports command (0xB7) for entering 4 byte addressing mode");
BUILD_ASSERT(DT_INST_PROP(0, address_size_32),
"After entering 4 byte addressing mode, 4 byte addressing is expected");
#endif
static bool qspi_initialized;
static int qspi_device_init(const struct device *dev);
static void qspi_device_uninit(const struct device *dev);
#define WORD_SIZE 4
/**
* @brief QSPI buffer structure
* Structure used both for TX and RX purposes.
*
* @param buf is a valid pointer to a data buffer.
* Can not be NULL.
* @param len is the length of the data to be handled.
* If no data to transmit/receive - pass 0.
*/
struct qspi_buf {
uint8_t *buf;
size_t len;
};
/**
* @brief QSPI command structure
* Structure used for custom command usage.
*
* @param op_code is a command value (i.e 0x9F - get Jedec ID)
* @param tx_buf structure used for TX purposes. Can be NULL if not used.
* @param rx_buf structure used for RX purposes. Can be NULL if not used.
*/
struct qspi_cmd {
uint8_t op_code;
const struct qspi_buf *tx_buf;
const struct qspi_buf *rx_buf;
};
static int qspi_nor_write_protection_set(const struct device *dev,
bool write_protect);
#ifdef CONFIG_PM_DEVICE
static int exit_dpd(const struct device *const dev);
#endif
/**
* @brief Test whether offset is aligned.
*/
#define QSPI_IS_SECTOR_ALIGNED(_ofs) (((_ofs) & (QSPI_SECTOR_SIZE - 1U)) == 0)
#define QSPI_IS_BLOCK_ALIGNED(_ofs) (((_ofs) & (QSPI_BLOCK_SIZE - 1U)) == 0)
/**
* @brief Converts NRFX return codes to the zephyr ones
*/
static inline int qspi_get_zephyr_ret_code(nrfx_err_t res)
{
switch (res) {
case NRFX_SUCCESS:
return 0;
case NRFX_ERROR_INVALID_PARAM:
case NRFX_ERROR_INVALID_ADDR:
return -EINVAL;
case NRFX_ERROR_INVALID_STATE:
return -ECANCELED;
case NRFX_ERROR_BUSY:
case NRFX_ERROR_TIMEOUT:
default:
return -EBUSY;
}
}
static inline void qspi_lock(const struct device *dev)
{
struct qspi_nor_data *dev_data = dev->data;
#ifdef CONFIG_MULTITHREADING
k_sem_take(&dev_data->sem, K_FOREVER);
#else /* CONFIG_MULTITHREADING */
ARG_UNUSED(dev_data);
#endif /* CONFIG_MULTITHREADING */
/*
* If the base clock divider needs to be changed, change it only
* for the time the driver is locked to perform a QSPI operation,
* unless the divider is forced to be kept set permanently.
*/
#if defined(BASE_CLOCK_DIV)
if (!dev_data->keep_base_clock_div_set) {
nrf_clock_hfclk192m_div_set(NRF_CLOCK, BASE_CLOCK_DIV);
}
#endif
}
static inline void qspi_unlock(const struct device *dev)
{
struct qspi_nor_data *dev_data = dev->data;
#if defined(BASE_CLOCK_DIV)
/* Restore the default base clock divider, unless instructed not to. */
if (!dev_data->keep_base_clock_div_set) {
nrf_clock_hfclk192m_div_set(NRF_CLOCK, NRF_CLOCK_HFCLK_DIV_4);
}
#endif
#ifdef CONFIG_MULTITHREADING
k_sem_give(&dev_data->sem);
#else
ARG_UNUSED(dev_data);
#endif
}
static inline void qspi_trans_lock(const struct device *dev)
{
#ifdef CONFIG_MULTITHREADING
struct qspi_nor_data *dev_data = dev->data;
k_sem_take(&dev_data->trans, K_FOREVER);
#else /* CONFIG_MULTITHREADING */
ARG_UNUSED(dev);
#endif /* CONFIG_MULTITHREADING */
}
static inline void qspi_trans_unlock(const struct device *dev)
{
#ifdef CONFIG_MULTITHREADING
struct qspi_nor_data *dev_data = dev->data;
k_sem_give(&dev_data->trans);
#else /* CONFIG_MULTITHREADING */
ARG_UNUSED(dev);
#endif /* CONFIG_MULTITHREADING */
}
static inline void qspi_wait_for_completion(const struct device *dev,
nrfx_err_t res)
{
struct qspi_nor_data *dev_data = dev->data;
if (res == NRFX_SUCCESS) {
#ifdef CONFIG_MULTITHREADING
k_sem_take(&dev_data->sync, K_FOREVER);
#else /* CONFIG_MULTITHREADING */
unsigned int key = irq_lock();
while (!dev_data->ready) {
k_cpu_atomic_idle(key);
key = irq_lock();
}
dev_data->ready = false;
irq_unlock(key);
#endif /* CONFIG_MULTITHREADING */
}
}
static inline void qspi_complete(struct qspi_nor_data *dev_data)
{
#ifdef CONFIG_MULTITHREADING
k_sem_give(&dev_data->sync);
#else /* CONFIG_MULTITHREADING */
dev_data->ready = true;
#endif /* CONFIG_MULTITHREADING */
}
/**
* @brief QSPI handler
*
* @param event Driver event type
* @param p_context Pointer to context. Use in interrupt handler.
* @retval None
*/
static void qspi_handler(nrfx_qspi_evt_t event, void *p_context)
{
struct qspi_nor_data *dev_data = p_context;
if (event == NRFX_QSPI_EVENT_DONE) {
qspi_complete(dev_data);
}
}
static int qspi_device_init(const struct device *dev)
{
struct qspi_nor_data *dev_data = dev->data;
nrfx_err_t res;
int ret = 0;
qspi_lock(dev);
/* In multithreading, driver can call qspi_device_init more than once
* before calling qspi_device_uninit. Keepping count, so QSPI is
* uninitialized only at the last call (count == 0).
*/
#ifdef CONFIG_MULTITHREADING
k_sem_give(&dev_data->count);
#endif
if (!qspi_initialized) {
const struct qspi_nor_config *dev_config = dev->config;
res = nrfx_qspi_init(&dev_config->nrfx_cfg,
qspi_handler,
dev_data);
ret = qspi_get_zephyr_ret_code(res);
qspi_initialized = (ret == 0);
}
qspi_unlock(dev);
return ret;
}
static void qspi_device_uninit(const struct device *dev)
{
bool last = true;
qspi_lock(dev);
#ifdef CONFIG_MULTITHREADING
struct qspi_nor_data *dev_data = dev->data;
/* The last thread to finish using the driver uninit the QSPI */
(void) k_sem_take(&dev_data->count, K_NO_WAIT);
last = (k_sem_count_get(&dev_data->count) == 0);
#endif
if (last) {
while (nrfx_qspi_mem_busy_check() != NRFX_SUCCESS) {
if (IS_ENABLED(CONFIG_MULTITHREADING)) {
k_msleep(50);
} else {
k_busy_wait(50000);
}
}
nrfx_qspi_uninit();
#ifndef CONFIG_PINCTRL
nrf_gpio_cfg_output(QSPI_PROP_AT(csn_pins, 0));
nrf_gpio_pin_set(QSPI_PROP_AT(csn_pins, 0));
#endif
qspi_initialized = false;
}
qspi_unlock(dev);
}
/* QSPI send custom command.
*
* If this is used for both send and receive the buffer sizes must be
* equal and cover the whole transaction.
*/
static int qspi_send_cmd(const struct device *dev, const struct qspi_cmd *cmd,
bool wren)
{
/* Check input parameters */
if (!cmd) {
return -EINVAL;
}
const void *tx_buf = NULL;
size_t tx_len = 0;
void *rx_buf = NULL;
size_t rx_len = 0;
size_t xfer_len = sizeof(cmd->op_code);
if (cmd->tx_buf) {
tx_buf = cmd->tx_buf->buf;
tx_len = cmd->tx_buf->len;
}
if (cmd->rx_buf) {
rx_buf = cmd->rx_buf->buf;
rx_len = cmd->rx_buf->len;
}
if ((rx_len != 0) && (tx_len != 0)) {
if (rx_len != tx_len) {
return -EINVAL;
}
xfer_len += tx_len;
} else {
/* At least one of these is zero. */
xfer_len += tx_len + rx_len;
}
if (xfer_len > NRF_QSPI_CINSTR_LEN_9B) {
LOG_WRN("cinstr %02x transfer too long: %zu",
cmd->op_code, xfer_len);
return -EINVAL;
}
nrf_qspi_cinstr_conf_t cinstr_cfg = {
.opcode = cmd->op_code,
.length = xfer_len,
.io2_level = true,
.io3_level = true,
.wipwait = false,
.wren = wren,
};
qspi_lock(dev);
int res = nrfx_qspi_cinstr_xfer(&cinstr_cfg, tx_buf, rx_buf);
qspi_unlock(dev);
return qspi_get_zephyr_ret_code(res);
}
#if !IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_NONE)
/* RDSR. Negative value is error. */
static int qspi_rdsr(const struct device *dev, uint8_t sr_num)
{
uint8_t opcode = SPI_NOR_CMD_RDSR;
if (sr_num > 2 || sr_num == 0) {
return -EINVAL;
}
if (sr_num == 2) {
opcode = SPI_NOR_CMD_RDSR2;
}
uint8_t sr = 0xFF;
const struct qspi_buf sr_buf = {
.buf = &sr,
.len = sizeof(sr),
};
struct qspi_cmd cmd = {
.op_code = opcode,
.rx_buf = &sr_buf,
};
int ret = qspi_send_cmd(dev, &cmd, false);
return (ret < 0) ? ret : sr;
}
/* Wait until RDSR confirms write is not in progress. */
static int qspi_wait_while_writing(const struct device *dev)
{
int ret;
do {
ret = qspi_rdsr(dev, 1);
} while ((ret >= 0)
&& ((ret & SPI_NOR_WIP_BIT) != 0U));
return (ret < 0) ? ret : 0;
}
static int qspi_wrsr(const struct device *dev, uint8_t sr_val, uint8_t sr_num)
{
int ret = 0;
uint8_t opcode = SPI_NOR_CMD_WRSR;
uint8_t length = 1;
uint8_t sr_array[2] = {0};
if (sr_num > 2 || sr_num == 0) {
return -EINVAL;
}
if (sr_num == 1) {
sr_array[0] = sr_val;
#if SR1_WRITE_CLEARS_SR2
/* Writing sr1 clears sr2. need to read/modify/write both. */
ret = qspi_rdsr(dev, 2);
if (ret < 0) {
LOG_ERR("RDSR for WRSR failed: %d", ret);
return ret;
}
sr_array[1] = ret;
length = 2;
#endif
} else { /* sr_num == 2 */
#if SR2_WRITE_NEEDS_SR1
/* Writing sr2 requires writing sr1 as well.
* Uses standard WRSR opcode
*/
sr_array[1] = sr_val;
ret = qspi_rdsr(dev, 1);
if (ret < 0) {
LOG_ERR("RDSR for WRSR failed: %d", ret);
return ret;
}
sr_array[0] = ret;
length = 2;
#elif IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S2B1v6)
/* Writing sr2 uses a dedicated WRSR2 command */
sr_array[0] = sr_val;
opcode = SPI_NOR_CMD_WRSR2;
#else
LOG_ERR("Attempted to write status register 2, but no known method to write sr2");
return -EINVAL;
#endif
}
const struct qspi_buf sr_buf = {
.buf = sr_array,
.len = length,
};
struct qspi_cmd cmd = {
.op_code = opcode,
.tx_buf = &sr_buf,
};
ret = qspi_send_cmd(dev, &cmd, true);
/* Writing SR can take some time, and further
* commands sent while it's happening can be
* corrupted. Wait.
*/
if (ret == 0) {
ret = qspi_wait_while_writing(dev);
}
return ret;
}
#endif /* !IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_NONE) */
/* QSPI erase */
static int qspi_erase(const struct device *dev, uint32_t addr, uint32_t size)
{
/* address must be sector-aligned */
if ((addr % QSPI_SECTOR_SIZE) != 0) {
return -EINVAL;
}
/* size must be a non-zero multiple of sectors */
if ((size == 0) || (size % QSPI_SECTOR_SIZE) != 0) {
return -EINVAL;
}
int rv = 0;
const struct qspi_nor_config *params = dev->config;
rv = qspi_device_init(dev);
if (rv != 0) {
goto out;
}
qspi_trans_lock(dev);
rv = qspi_nor_write_protection_set(dev, false);
if (rv != 0) {
goto out_trans_unlock;
}
qspi_lock(dev);
while (size > 0) {
nrfx_err_t res = !NRFX_SUCCESS;
uint32_t adj = 0;
if (size == params->size) {
/* chip erase */
res = nrfx_qspi_chip_erase();
adj = size;
} else if ((size >= QSPI_BLOCK_SIZE) &&
QSPI_IS_BLOCK_ALIGNED(addr)) {
/* 64 kB block erase */
res = nrfx_qspi_erase(NRF_QSPI_ERASE_LEN_64KB, addr);
adj = QSPI_BLOCK_SIZE;
} else if ((size >= QSPI_SECTOR_SIZE) &&
QSPI_IS_SECTOR_ALIGNED(addr)) {
/* 4kB sector erase */
res = nrfx_qspi_erase(NRF_QSPI_ERASE_LEN_4KB, addr);
adj = QSPI_SECTOR_SIZE;
} else {
/* minimal erase size is at least a sector size */
LOG_ERR("unsupported at 0x%lx size %zu", (long)addr, size);
res = NRFX_ERROR_INVALID_PARAM;
}
qspi_wait_for_completion(dev, res);
if (res == NRFX_SUCCESS) {
addr += adj;
size -= adj;
} else {
LOG_ERR("erase error at 0x%lx size %zu", (long)addr, size);
rv = qspi_get_zephyr_ret_code(res);
break;
}
}
qspi_unlock(dev);
int rv2 = qspi_nor_write_protection_set(dev, true);
if (!rv) {
rv = rv2;
}
out_trans_unlock:
qspi_trans_unlock(dev);
out:
qspi_device_uninit(dev);
return rv;
}
/* Configures QSPI memory for the transfer */
static int qspi_nrfx_configure(const struct device *dev)
{
struct qspi_nor_data *dev_data = dev->data;
const struct qspi_nor_config *dev_config = dev->config;
nrfx_err_t res = nrfx_qspi_init(&dev_config->nrfx_cfg,
qspi_handler,
dev_data);
int ret = qspi_get_zephyr_ret_code(res);
if (ret < 0) {
return ret;
}
#if DT_INST_NODE_HAS_PROP(0, rx_delay)
if (!nrf53_errata_121()) {
nrf_qspi_iftiming_set(NRF_QSPI, DT_INST_PROP(0, rx_delay));
}
#endif
#ifdef CONFIG_PM_DEVICE
/* It may happen that after the flash chip was previously put into
* the DPD mode, the system was reset but the flash chip was not.
* Consequently, the flash chip can be in the DPD mode at this point.
* Some flash chips will just exit the DPD mode on the first CS pulse,
* but some need to receive the dedicated command to do it, so send it.
*/
ret = exit_dpd(dev);
if (ret < 0) {
return ret;
}
#endif
/* Set QE to match transfer mode. If not using quad
* it's OK to leave QE set, but doing so prevents use
* of WP#/RESET#/HOLD# which might be useful.
*
* Note build assert above ensures QER is S1B6 or
* S2B1v1/4/5/6. Other options require more logic.
*/
#if !IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_NONE)
nrf_qspi_prot_conf_t const *prot_if =
&dev_config->nrfx_cfg.prot_if;
bool qe_value = (prot_if->writeoc == NRF_QSPI_WRITEOC_PP4IO) ||
(prot_if->writeoc == NRF_QSPI_WRITEOC_PP4O) ||
(prot_if->readoc == NRF_QSPI_READOC_READ4IO) ||
(prot_if->readoc == NRF_QSPI_READOC_READ4O);
uint8_t sr_num = 0;
uint8_t qe_mask = 0;
#if IS_EQUAL(INST_0_QER, JESD216_DW15_QER_VAL_S1B6)
sr_num = 1;
qe_mask = BIT(6);
#elif QER_IS_S2B1
sr_num = 2;
qe_mask = BIT(1);
#else
LOG_ERR("Unsupported QER type");
return -EINVAL;
#endif
ret = qspi_rdsr(dev, sr_num);
if (ret < 0) {
LOG_ERR("RDSR failed: %d", ret);
return ret;
}
uint8_t sr = (uint8_t)ret;
bool qe_state = ((sr & qe_mask) != 0U);
LOG_DBG("RDSR %02x QE %d need %d: %s", sr, qe_state, qe_value,
(qe_state != qe_value) ? "updating" : "no-change");
ret = 0;
if (qe_state != qe_value) {
sr ^= qe_mask;
ret = qspi_wrsr(dev, sr, sr_num);
}
if (ret < 0) {
LOG_ERR("QE %s failed: %d", qe_value ? "set" : "clear",
ret);
return ret;
}
#endif
if (INST_0_4BA != 0) {
struct qspi_cmd cmd = {
.op_code = SPI_NOR_CMD_4BA,
};
/* Call will send write enable before instruction if that
* requirement is encoded in INST_0_4BA.
*/
ret = qspi_send_cmd(dev, &cmd, (INST_0_4BA & 0x02));
if (ret < 0) {
LOG_ERR("E4BA cmd issue failed: %d.", ret);
} else {
LOG_DBG("E4BA cmd issued.");
}
}
return ret;
}
static int qspi_read_jedec_id(const struct device *dev,
uint8_t *id)
{
const struct qspi_buf rx_buf = {
.buf = id,
.len = 3
};
const struct qspi_cmd cmd = {
.op_code = SPI_NOR_CMD_RDID,
.rx_buf = &rx_buf,
};
int ret = qspi_device_init(dev);
if (ret == 0) {
ret = qspi_send_cmd(dev, &cmd, false);
}
qspi_device_uninit(dev);
return ret;
}
#if defined(CONFIG_FLASH_JESD216_API)
static int qspi_sfdp_read(const struct device *dev, off_t offset,
void *data, size_t len)
{
__ASSERT(data != NULL, "null destination");
uint8_t addr_buf[] = {
offset >> 16,
offset >> 8,
offset,
0, /* wait state */
};
nrf_qspi_cinstr_conf_t cinstr_cfg = {
.opcode = JESD216_CMD_READ_SFDP,
.length = NRF_QSPI_CINSTR_LEN_1B,
.io2_level = true,
.io3_level = true,
};
int ret = qspi_device_init(dev);
nrfx_err_t res = NRFX_SUCCESS;
if (ret != 0) {
LOG_DBG("qspi_device_init: %d", ret);
qspi_device_uninit(dev);
return ret;
}
qspi_lock(dev);
res = nrfx_qspi_lfm_start(&cinstr_cfg);
if (res != NRFX_SUCCESS) {
LOG_DBG("lfm_start: %x", res);
goto out;
}
res = nrfx_qspi_lfm_xfer(addr_buf, NULL, sizeof(addr_buf), false);
if (res != NRFX_SUCCESS) {
LOG_DBG("lfm_xfer addr: %x", res);
goto out;
}
res = nrfx_qspi_lfm_xfer(NULL, data, len, true);
if (res != NRFX_SUCCESS) {
LOG_DBG("lfm_xfer read: %x", res);
goto out;
}
out:
qspi_unlock(dev);
qspi_device_uninit(dev);
return qspi_get_zephyr_ret_code(res);
}
#endif /* CONFIG_FLASH_JESD216_API */
/**
* @brief Retrieve the Flash JEDEC ID and compare it with the one expected
*
* @param dev The device structure
* @return 0 on success, negative errno code otherwise
*/
static inline int qspi_nor_read_id(const struct device *dev)
{
uint8_t id[SPI_NOR_MAX_ID_LEN];
int ret = qspi_read_jedec_id(dev, id);
if (ret != 0) {
return -EIO;
}
const struct qspi_nor_config *qnc = dev->config;
if (memcmp(qnc->id, id, SPI_NOR_MAX_ID_LEN) != 0) {
LOG_ERR("JEDEC id [%02x %02x %02x] expect [%02x %02x %02x]",
id[0], id[1], id[2],
qnc->id[0], qnc->id[1], qnc->id[2]);
return -ENODEV;
}
return 0;
}
static inline nrfx_err_t read_non_aligned(const struct device *dev,
off_t addr,
void *dest, size_t size)
{
uint8_t __aligned(WORD_SIZE) buf[WORD_SIZE * 2];
uint8_t *dptr = dest;
off_t flash_prefix = (WORD_SIZE - (addr % WORD_SIZE)) % WORD_SIZE;
if (flash_prefix > size) {
flash_prefix = size;
}
off_t dest_prefix = (WORD_SIZE - (off_t)dptr % WORD_SIZE) % WORD_SIZE;
if (dest_prefix > size) {
dest_prefix = size;
}
off_t flash_suffix = (size - flash_prefix) % WORD_SIZE;
off_t flash_middle = size - flash_prefix - flash_suffix;
off_t dest_middle = size - dest_prefix -
(size - dest_prefix) % WORD_SIZE;
if (flash_middle > dest_middle) {
flash_middle = dest_middle;
flash_suffix = size - flash_prefix - flash_middle;
}
nrfx_err_t res = NRFX_SUCCESS;
/* read from aligned flash to aligned memory */
if (flash_middle != 0) {
res = nrfx_qspi_read(dptr + dest_prefix, flash_middle,
addr + flash_prefix);
qspi_wait_for_completion(dev, res);
if (res != NRFX_SUCCESS) {
return res;
}
/* perform shift in RAM */
if (flash_prefix != dest_prefix) {
memmove(dptr + flash_prefix, dptr + dest_prefix, flash_middle);
}
}
/* read prefix */
if (flash_prefix != 0) {
res = nrfx_qspi_read(buf, WORD_SIZE, addr -
(WORD_SIZE - flash_prefix));
qspi_wait_for_completion(dev, res);
if (res != NRFX_SUCCESS) {
return res;
}
memcpy(dptr, buf + WORD_SIZE - flash_prefix, flash_prefix);
}
/* read suffix */
if (flash_suffix != 0) {
res = nrfx_qspi_read(buf, WORD_SIZE * 2,
addr + flash_prefix + flash_middle);
qspi_wait_for_completion(dev, res);
if (res != NRFX_SUCCESS) {
return res;
}
memcpy(dptr + flash_prefix + flash_middle, buf, flash_suffix);
}
return res;
}
static int qspi_nor_read(const struct device *dev, off_t addr, void *dest,
size_t size)
{
if (!dest) {
return -EINVAL;
}
/* read size must be non-zero */
if (!size) {
return 0;
}
const struct qspi_nor_config *params = dev->config;
/* affected region should be within device */
if (addr < 0 ||
(addr + size) > params->size) {
LOG_ERR("read error: address or size "
"exceeds expected values."
"Addr: 0x%lx size %zu", (long)addr, size);
return -EINVAL;
}
int rc = qspi_device_init(dev);
if (rc != 0) {
goto out;
}
qspi_lock(dev);
nrfx_err_t res = read_non_aligned(dev, addr, dest, size);
qspi_unlock(dev);
rc = qspi_get_zephyr_ret_code(res);
out:
qspi_device_uninit(dev);
return rc;
}
/* addr aligned, sptr not null, slen less than 4 */
static inline nrfx_err_t write_sub_word(const struct device *dev, off_t addr,
const void *sptr, size_t slen)
{
uint8_t __aligned(4) buf[4];
nrfx_err_t res;
/* read out the whole word so that unchanged data can be
* written back
*/
res = nrfx_qspi_read(buf, sizeof(buf), addr);
qspi_wait_for_completion(dev, res);
if (res == NRFX_SUCCESS) {
memcpy(buf, sptr, slen);
res = nrfx_qspi_write(buf, sizeof(buf), addr);
qspi_wait_for_completion(dev, res);
}
return res;
}
BUILD_ASSERT((CONFIG_NORDIC_QSPI_NOR_STACK_WRITE_BUFFER_SIZE % 4) == 0,
"NOR stack buffer must be multiple of 4 bytes");
/* If enabled write using a stack-allocated aligned SRAM buffer as
* required for DMA transfers by QSPI peripheral.
*
* If not enabled return the error the peripheral would have produced.
*/
static inline nrfx_err_t write_from_nvmc(const struct device *dev, off_t addr,
const void *sptr, size_t slen)
{
nrfx_err_t res = NRFX_SUCCESS;
if (CONFIG_NORDIC_QSPI_NOR_STACK_WRITE_BUFFER_SIZE > 0) {
uint8_t __aligned(4) buf[CONFIG_NORDIC_QSPI_NOR_STACK_WRITE_BUFFER_SIZE];
const uint8_t *sp = sptr;
while ((slen > 0) && (res == NRFX_SUCCESS)) {
size_t len = MIN(slen, sizeof(buf));
memcpy(buf, sp, len);
res = nrfx_qspi_write(buf, sizeof(buf), addr);
qspi_wait_for_completion(dev, res);
if (res == NRFX_SUCCESS) {
slen -= len;
sp += len;
addr += len;
}
}
} else {
res = NRFX_ERROR_INVALID_ADDR;
}
return res;
}
static int qspi_nor_write(const struct device *dev, off_t addr,
const void *src,
size_t size)
{
if (!src) {
return -EINVAL;
}
/* write size must be non-zero, less than 4, or a multiple of 4 */
if ((size == 0)
|| ((size > 4) && ((size % 4U) != 0))) {
return -EINVAL;
}
/* address must be 4-byte aligned */
if ((addr % 4U) != 0) {
return -EINVAL;
}
const struct qspi_nor_config *params = dev->config;
/* affected region should be within device */
if (addr < 0 ||
(addr + size) > params->size) {
LOG_ERR("write error: address or size "
"exceeds expected values."
"Addr: 0x%lx size %zu", (long)addr, size);
return -EINVAL;
}
nrfx_err_t res = NRFX_SUCCESS;
int rc = qspi_device_init(dev);
if (rc != 0) {
goto out;
}
qspi_trans_lock(dev);
res = qspi_nor_write_protection_set(dev, false);
qspi_lock(dev);
if (!res) {
if (size < 4U) {
res = write_sub_word(dev, addr, src, size);
} else if (!nrfx_is_in_ram(src)) {
res = write_from_nvmc(dev, addr, src, size);
} else {
res = nrfx_qspi_write(src, size, addr);
qspi_wait_for_completion(dev, res);
}
}
qspi_unlock(dev);
int res2 = qspi_nor_write_protection_set(dev, true);
qspi_trans_unlock(dev);
if (!res) {
res = res2;
}
rc = qspi_get_zephyr_ret_code(res);
out:
qspi_device_uninit(dev);
return rc;
}
static int qspi_nor_erase(const struct device *dev, off_t addr, size_t size)
{
const struct qspi_nor_config *params = dev->config;
/* affected region should be within device */
if (addr < 0 ||
(addr + size) > params->size) {
LOG_ERR("erase error: address or size "
"exceeds expected values."
"Addr: 0x%lx size %zu", (long)addr, size);
return -EINVAL;
}
int ret = qspi_erase(dev, addr, size);
return ret;
}
static int qspi_nor_write_protection_set(const struct device *dev,
bool write_protect)
{
int ret = 0;
struct qspi_cmd cmd = {
.op_code = ((write_protect) ? SPI_NOR_CMD_WRDI : SPI_NOR_CMD_WREN),
};
if (qspi_send_cmd(dev, &cmd, false) != 0) {
ret = -EIO;
}
return ret;
}
/**
* @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 qspi_nor_configure(const struct device *dev)
{
int ret = qspi_nrfx_configure(dev);
if (ret != 0) {
return ret;
}
qspi_device_uninit(dev);
/* now the spi bus is configured, we can verify the flash id */
if (qspi_nor_read_id(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 qspi_nor_init(const struct device *dev)
{
#if defined(CONFIG_SOC_SERIES_NRF53X)
/* Make sure the default /4 divider is set initially. */
nrf_clock_hfclk192m_div_set(NRF_CLOCK, NRF_CLOCK_HFCLK_DIV_4);
#endif
#ifdef CONFIG_PINCTRL
const struct qspi_nor_config *dev_config = dev->config;
int ret = pinctrl_apply_state(dev_config->pcfg, PINCTRL_STATE_DEFAULT);
if (ret < 0) {
return ret;
}
#endif
IRQ_CONNECT(DT_IRQN(QSPI_NODE), DT_IRQ(QSPI_NODE, priority),
nrfx_isr, nrfx_qspi_irq_handler, 0);
return qspi_nor_configure(dev);
}
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
/* instance 0 page count */
#define LAYOUT_PAGES_COUNT (INST_0_BYTES / \
CONFIG_NORDIC_QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE)
BUILD_ASSERT((CONFIG_NORDIC_QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE *
LAYOUT_PAGES_COUNT)
== INST_0_BYTES,
"QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE incompatible with flash size");
static const struct flash_pages_layout dev_layout = {
.pages_count = LAYOUT_PAGES_COUNT,
.pages_size = CONFIG_NORDIC_QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE,
};
#undef LAYOUT_PAGES_COUNT
static void qspi_nor_pages_layout(const struct device *dev,
const struct flash_pages_layout **layout,
size_t *layout_size)
{
*layout = &dev_layout;
*layout_size = 1;
}
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
static const struct flash_parameters *
qspi_flash_get_parameters(const struct device *dev)
{
ARG_UNUSED(dev);
static const struct flash_parameters qspi_flash_parameters = {
.write_block_size = 4,
.erase_value = 0xff,
};
return &qspi_flash_parameters;
}
static const struct flash_driver_api qspi_nor_api = {
.read = qspi_nor_read,
.write = qspi_nor_write,
.erase = qspi_nor_erase,
.get_parameters = qspi_flash_get_parameters,
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
.page_layout = qspi_nor_pages_layout,
#endif
#if defined(CONFIG_FLASH_JESD216_API)
.sfdp_read = qspi_sfdp_read,
.read_jedec_id = qspi_read_jedec_id,
#endif /* CONFIG_FLASH_JESD216_API */
};
#ifdef CONFIG_PM_DEVICE
static int enter_dpd(const struct device *const dev)
{
if (IS_ENABLED(DT_INST_PROP(0, has_dpd))) {
struct qspi_cmd cmd = {
.op_code = SPI_NOR_CMD_DPD,
};
uint32_t t_enter_dpd = DT_INST_PROP_OR(0, t_enter_dpd, 0);
int ret;
ret = qspi_send_cmd(dev, &cmd, false);
if (ret < 0) {
return ret;
}
if (t_enter_dpd) {
uint32_t t_enter_dpd_us =
ceiling_fraction(t_enter_dpd, NSEC_PER_USEC);
k_busy_wait(t_enter_dpd_us);
}
}
return 0;
}
static int exit_dpd(const struct device *const dev)
{
if (IS_ENABLED(DT_INST_PROP(0, has_dpd))) {
struct qspi_cmd cmd = {
.op_code = SPI_NOR_CMD_RDPD,
};
uint32_t t_exit_dpd = DT_INST_PROP_OR(0, t_exit_dpd, 0);
int ret;
ret = qspi_send_cmd(dev, &cmd, false);
if (ret < 0) {
return ret;
}
if (t_exit_dpd) {
uint32_t t_exit_dpd_us =
ceiling_fraction(t_exit_dpd, NSEC_PER_USEC);
k_busy_wait(t_exit_dpd_us);
}
}
return 0;
}
static int qspi_nor_pm_action(const struct device *dev,
enum pm_device_action action)
{
struct qspi_nor_data *dev_data = dev->data;
const struct qspi_nor_config *dev_config = dev->config;
int ret;
nrfx_err_t err;
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
ret = qspi_device_init(dev);
if (ret < 0) {
return ret;
}
if (nrfx_qspi_mem_busy_check() != NRFX_SUCCESS) {
return -EBUSY;
}
ret = enter_dpd(dev);
if (ret < 0) {
return ret;
}
nrfx_qspi_uninit();
#ifdef CONFIG_PINCTRL
ret = pinctrl_apply_state(dev_config->pcfg,
PINCTRL_STATE_SLEEP);
if (ret < 0) {
return ret;
}
#endif
break;
case PM_DEVICE_ACTION_RESUME:
#ifdef CONFIG_PINCTRL
ret = pinctrl_apply_state(dev_config->pcfg,
PINCTRL_STATE_DEFAULT);
if (ret < 0) {
return ret;
}
#endif
err = nrfx_qspi_init(&dev_config->nrfx_cfg,
qspi_handler,
dev_data);
if (err != NRFX_SUCCESS) {
return -EIO;
}
ret = exit_dpd(dev);
if (ret < 0) {
return ret;
}
qspi_device_uninit(dev);
break;
default:
return -ENOTSUP;
}
return 0;
}
#endif /* CONFIG_PM_DEVICE */
void z_impl_nrf_qspi_nor_base_clock_div_force(const struct device *dev,
bool force)
{
#if defined(BASE_CLOCK_DIV)
struct qspi_nor_data *dev_data = dev->data;
/*
* The divider is normally changed, unless the flag is set, only for
* periods when the driver is locked, so the flag itself also can only
* be modified while the driver is locked.
*/
qspi_lock(dev);
dev_data->keep_base_clock_div_set = force;
qspi_unlock(dev);
#else
ARG_UNUSED(dev);
ARG_UNUSED(force);
#endif
}
#ifdef CONFIG_USERSPACE
#include <zephyr/syscall_handler.h>
void z_vrfy_nrf_qspi_nor_base_clock_div_force(const struct device *dev,
bool force)
{
Z_OOPS(Z_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_FLASH,
&qspi_nor_api));
z_impl_nrf_qspi_nor_base_clock_div_force(dev, force);
}
#include <syscalls/nrf_qspi_nor_base_clock_div_force_mrsh.c>
#endif /* CONFIG_USERSPACE */
static struct qspi_nor_data qspi_nor_dev_data = {
#ifdef CONFIG_MULTITHREADING
.trans = Z_SEM_INITIALIZER(qspi_nor_dev_data.trans, 1, 1),
.sem = Z_SEM_INITIALIZER(qspi_nor_dev_data.sem, 1, 1),
.sync = Z_SEM_INITIALIZER(qspi_nor_dev_data.sync, 0, 1),
.count = Z_SEM_INITIALIZER(qspi_nor_dev_data.count, 0, K_SEM_MAX_LIMIT),
#endif /* CONFIG_MULTITHREADING */
};
NRF_DT_CHECK_PIN_ASSIGNMENTS(QSPI_NODE, 1, sck_pin, csn_pins, io_pins);
IF_ENABLED(CONFIG_PINCTRL, (PINCTRL_DT_DEFINE(QSPI_NODE)));
static const struct qspi_nor_config qspi_nor_dev_config = {
#ifdef CONFIG_PINCTRL
.nrfx_cfg.skip_gpio_cfg = true,
.nrfx_cfg.skip_psel_cfg = true,
.pcfg = PINCTRL_DT_DEV_CONFIG_GET(QSPI_NODE),
#else
.nrfx_cfg.pins = {
.sck_pin = DT_PROP(QSPI_NODE, sck_pin),
.csn_pin = QSPI_PROP_AT(csn_pins, 0),
.io0_pin = QSPI_PROP_AT(io_pins, 0),
.io1_pin = QSPI_PROP_AT(io_pins, 1),
#if QSPI_PROP_LEN(io_pins) > 2
.io2_pin = QSPI_PROP_AT(io_pins, 2),
.io3_pin = QSPI_PROP_AT(io_pins, 3),
#else
.io2_pin = NRF_QSPI_PIN_NOT_CONNECTED,
.io3_pin = NRF_QSPI_PIN_NOT_CONNECTED,
#endif
},
#endif /* CONFIG_PINCTRL */
.nrfx_cfg.prot_if = {
.readoc = COND_CODE_1(DT_INST_NODE_HAS_PROP(0, readoc),
(_CONCAT(NRF_QSPI_READOC_,
DT_STRING_UPPER_TOKEN(DT_DRV_INST(0),
readoc))),
(NRF_QSPI_READOC_FASTREAD)),
.writeoc = COND_CODE_1(DT_INST_NODE_HAS_PROP(0, writeoc),
(_CONCAT(NRF_QSPI_WRITEOC_,
DT_STRING_UPPER_TOKEN(DT_DRV_INST(0),
writeoc))),
(NRF_QSPI_WRITEOC_PP)),
.addrmode = DT_INST_PROP(0, address_size_32)
? NRF_QSPI_ADDRMODE_32BIT
: NRF_QSPI_ADDRMODE_24BIT,
},
.nrfx_cfg.phy_if = {
.sck_freq = INST_0_SCK_CFG,
.sck_delay = DT_INST_PROP(0, sck_delay),
.spi_mode = INST_0_SPI_MODE,
},
.size = INST_0_BYTES,
.id = DT_INST_PROP(0, jedec_id),
};
PM_DEVICE_DT_INST_DEFINE(0, qspi_nor_pm_action);
DEVICE_DT_INST_DEFINE(0, qspi_nor_init, PM_DEVICE_DT_INST_GET(0),
&qspi_nor_dev_data, &qspi_nor_dev_config,
POST_KERNEL, CONFIG_NORDIC_QSPI_NOR_INIT_PRIORITY,
&qspi_nor_api);