blob: 8d3dc81e347659555972fe1bc0db8f42ab94cce5 [file] [log] [blame]
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief File containing QSPI device interface specific definitions for the
* Zephyr OS layer of the Wi-Fi driver.
*/
#include <errno.h>
#include <string.h>
#include <zephyr/init.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/wifi/nrf_wifi/bus/qspi_if.h>
#include <soc.h>
#include <nrf_erratas.h>
#include <nrfx_qspi.h>
#include <hal/nrf_clock.h>
#include <hal/nrf_gpio.h>
#include <zephyr/drivers/wifi/nrf_wifi/bus/rpu_hw_if.h>
#include "spi_nor.h"
/* The QSPI bus node which the NRF70 is on */
#define QSPI_IF_BUS_NODE DT_NODELABEL(qspi)
/* QSPI bus properties from the devicetree */
#define QSPI_IF_BUS_IRQN DT_IRQN(QSPI_IF_BUS_NODE)
#define QSPI_IF_BUS_IRQ_PRIO DT_IRQ(QSPI_IF_BUS_NODE, priority)
#define QSPI_IF_BUS_SCK_PIN DT_PROP(QSPI_IF_BUS_NODE, sck_pin)
#define QSPI_IF_BUS_CSN_PIN DT_PROP(QSPI_IF_BUS_NODE, csn_pins)
#define QSPI_IF_BUS_IO0_PIN DT_PROP_BY_IDX(QSPI_IF_BUS_NODE, io_pins, 0)
#define QSPI_IF_BUS_IO1_PIN DT_PROP_BY_IDX(QSPI_IF_BUS_NODE, io_pins, 1)
#define QSPI_IF_BUS_IO2_PIN DT_PROP_BY_IDX(QSPI_IF_BUS_NODE, io_pins, 2)
#define QSPI_IF_BUS_IO3_PIN DT_PROP_BY_IDX(QSPI_IF_BUS_NODE, io_pins, 3)
#define QSPI_IF_BUS_HAS_4_IO_PINS \
(DT_PROP_LEN(QSPI_IF_BUS_NODE, io_pins) == 4)
#define QSPI_IF_BUS_PINCTRL_DT_DEV_CONFIG_GET \
PINCTRL_DT_DEV_CONFIG_GET(QSPI_IF_BUS_NODE)
/* The NRF70 device node which is on the QSPI bus */
#define QSPI_IF_DEVICE_NODE DT_NODELABEL(nrf70)
/* NRF70 device QSPI properties */
#define QSPI_IF_DEVICE_FREQUENCY DT_PROP(QSPI_IF_DEVICE_NODE, qspi_frequency)
#define QSPI_IF_DEVICE_CPHA DT_PROP(QSPI_IF_DEVICE_NODE, qspi_cpha)
#define QSPI_IF_DEVICE_CPOL DT_PROP(QSPI_IF_DEVICE_NODE, qspi_cpol)
#define QSPI_IF_DEVICE_QUAD_MODE DT_PROP(QSPI_IF_DEVICE_NODE, qspi_quad_mode)
#define QSPI_IF_DEVICE_RX_DELAY DT_PROP(QSPI_IF_DEVICE_NODE, qspi_rx_delay)
static struct qspi_config *qspi_cfg;
#if NRF_QSPI_HAS_XIP_ENC || NRF_QSPI_HAS_DMA_ENC
static unsigned int nonce_last_addr;
static unsigned int nonce_cnt;
#endif /*NRF_QSPI_HAS_XIP_ENC || NRF_QSPI_HAS_DMA_ENC*/
/* Main config structure */
static nrfx_qspi_config_t QSPIconfig;
/*
* According to the respective specifications, the nRF52 QSPI supports clock
* frequencies 2 - 32 MHz and the nRF53 one supports 6 - 96 MHz.
*/
BUILD_ASSERT(QSPI_IF_DEVICE_FREQUENCY >= (NRF_QSPI_BASE_CLOCK_FREQ / 16),
"Unsupported SCK frequency.");
/*
* Determine a configuration value (INST_0_SCK_CFG) and, if needed, a divider
* (BASE_CLOCK_DIV) for the clock from which the SCK frequency is derived that
* need to be used to achieve the SCK frequency as close as possible (but not
* higher) to the one specified in DT.
*/
#if defined(CONFIG_SOC_SERIES_NRF53X)
/*
* On nRF53 Series SoCs, the default /4 divider for the HFCLK192M clock can
* only be used when the QSPI peripheral is idle. When a QSPI operation is
* performed, the divider needs to be changed to /1 or /2 (particularly,
* the specification says that the peripheral "supports 192 MHz and 96 MHz
* PCLK192M frequency"), but after that operation is complete, the default
* divider needs to be restored to avoid increased current consumption.
*/
#if (QSPI_IF_DEVICE_FREQUENCY >= NRF_QSPI_BASE_CLOCK_FREQ)
/* For requested SCK >= 96 MHz, 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
/* If anomaly 159 is to be prevented, only /1 divider can be used. */
#elif NRF53_ERRATA_159_ENABLE_WORKAROUND
#define BASE_CLOCK_DIV NRF_CLOCK_HFCLK_DIV_1
#define INST_0_SCK_CFG (DIV_ROUND_UP(NRF_QSPI_BASE_CLOCK_FREQ, \
QSPI_IF_DEVICE_FREQUENCY) - 1)
#elif (QSPI_IF_DEVICE_FREQUENCY >= (NRF_QSPI_BASE_CLOCK_FREQ / 2))
/* For 96 MHz > SCK >= 48 MHz, 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 (QSPI_IF_DEVICE_FREQUENCY >= (NRF_QSPI_BASE_CLOCK_FREQ / 3))
/* For 48 MHz > SCK >= 32 MHz, 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
#else
/* For requested SCK < 32 MHz, use divider /2 for HFCLK192M. */
#define BASE_CLOCK_DIV NRF_CLOCK_HFCLK_DIV_2
#define INST_0_SCK_CFG (DIV_ROUND_UP(NRF_QSPI_BASE_CLOCK_FREQ / 2, \
QSPI_IF_DEVICE_FREQUENCY) - 1)
#endif
#if BASE_CLOCK_DIV == NRF_CLOCK_HFCLK_DIV_1
/* For 8 MHz, use HFCLK192M / 1 / (2*12) */
#define INST_0_SCK_CFG_WAKE NRF_QSPI_FREQ_DIV12
#elif BASE_CLOCK_DIV == NRF_CLOCK_HFCLK_DIV_2
/* For 8 MHz, use HFCLK192M / 2 / (2*6) */
#define INST_0_SCK_CFG_WAKE NRF_QSPI_FREQ_DIV6
#else
#error "Unsupported base clock divider for wake-up frequency."
#endif
/* After the base clock divider is changed, some time is needed for the new
* setting to take effect. This value specifies the delay (in microseconds)
* to be applied to ensure that the clock is ready when the QSPI operation
* starts. It was measured with a logic analyzer (unfortunately, the nRF5340
* specification does not provide any numbers in this regard).
*/
/* FIXME: This has adverse impact on performance, ~3Mbps, so, for now, it is
* disabled till further investigation.
*/
#define BASE_CLOCK_SWITCH_DELAY_US 0
#else
/*
* On nRF52 Series SoCs, the base clock divider is not configurable,
* so BASE_CLOCK_DIV is not defined.
*/
#if (QSPI_IF_DEVICE_FREQUENCY >= NRF_QSPI_BASE_CLOCK_FREQ)
#define INST_0_SCK_CFG NRF_QSPI_FREQ_DIV1
#else
#define INST_0_SCK_CFG (DIV_ROUND_UP(NRF_QSPI_BASE_CLOCK_FREQ, \
QSPI_IF_DEVICE_FREQUENCY) - 1)
#endif
/* For 8 MHz, use PCLK32M / 4 */
#define INST_0_SCK_CFG_WAKE NRF_QSPI_FREQ_DIV4
#endif /* defined(CONFIG_SOC_SERIES_NRF53X) */
static int qspi_device_init(const struct device *dev);
static void qspi_device_uninit(const struct device *dev);
#define WORD_SIZE 4
LOG_MODULE_DECLARE(wifi_nrf_bus, CONFIG_WIFI_NRF70_BUSLIB_LOG_LEVEL);
/**
* @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;
};
/**
* @brief Structure for defining the QSPI NOR access
*/
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 */
};
static inline int qspi_get_mode(bool cpol, bool cpha)
{
register int ret = -EINVAL;
if ((!cpol) && (!cpha)) {
ret = 0;
} else if (cpol && cpha) {
ret = 1;
}
__ASSERT(ret != -EINVAL, "Invalid QSPI mode");
return ret;
}
static inline bool qspi_write_is_quad(nrf_qspi_writeoc_t lines)
{
switch (lines) {
case NRF_QSPI_WRITEOC_PP4IO:
case NRF_QSPI_WRITEOC_PP4O:
return true;
default:
return false;
}
}
static inline bool qspi_read_is_quad(nrf_qspi_readoc_t lines)
{
switch (lines) {
case NRF_QSPI_READOC_READ4IO:
case NRF_QSPI_READOC_READ4O:
return true;
default:
return false;
}
}
static inline int qspi_get_lines_write(uint8_t lines)
{
register int ret = -EINVAL;
switch (lines) {
case 3:
ret = NRF_QSPI_WRITEOC_PP4IO;
break;
case 2:
ret = NRF_QSPI_WRITEOC_PP4O;
break;
case 1:
ret = NRF_QSPI_WRITEOC_PP2O;
break;
case 0:
ret = NRF_QSPI_WRITEOC_PP;
break;
default:
break;
}
__ASSERT(ret != -EINVAL, "Invalid QSPI write line");
return ret;
}
static inline int qspi_get_lines_read(uint8_t lines)
{
register int ret = -EINVAL;
switch (lines) {
case 4:
ret = NRF_QSPI_READOC_READ4IO;
break;
case 3:
ret = NRF_QSPI_READOC_READ4O;
break;
case 2:
ret = NRF_QSPI_READOC_READ2IO;
break;
case 1:
ret = NRF_QSPI_READOC_READ2O;
break;
case 0:
ret = NRF_QSPI_READOC_FASTREAD;
break;
default:
break;
}
__ASSERT(ret != -EINVAL, "Invalid QSPI read line");
return ret;
}
nrfx_err_t _nrfx_qspi_read(void *p_rx_buffer, size_t rx_buffer_length, uint32_t src_address)
{
return nrfx_qspi_read(p_rx_buffer, rx_buffer_length, src_address);
}
nrfx_err_t _nrfx_qspi_write(void const *p_tx_buffer, size_t tx_buffer_length, uint32_t dst_address)
{
return nrfx_qspi_write(p_tx_buffer, tx_buffer_length, dst_address);
}
nrfx_err_t _nrfx_qspi_init(nrfx_qspi_config_t const *p_config, nrfx_qspi_handler_t handler,
void *p_context)
{
NRF_QSPI_Type *p_reg = NRF_QSPI;
nrfx_qspi_init(p_config, handler, p_context);
/* RDC4IO = 4'hA (register IFTIMING), which means 10 Dummy Cycles for READ4. */
p_reg->IFTIMING |= qspi_cfg->RDC4IO;
/* LOG_DBG("%04x : IFTIMING", p_reg->IFTIMING & qspi_cfg->RDC4IO); */
/* ACTIVATE task fails for slave bitfile so ignore it */
return NRFX_SUCCESS;
}
/**
* @brief Main configuration structure
*/
static struct qspi_nor_data qspi_nor_memory_data = {
#ifdef CONFIG_MULTITHREADING
.trans = Z_SEM_INITIALIZER(qspi_nor_memory_data.trans, 1, 1),
.sem = Z_SEM_INITIALIZER(qspi_nor_memory_data.sem, 1, 1),
.sync = Z_SEM_INITIALIZER(qspi_nor_memory_data.sync, 0, 1),
.count = Z_SEM_INITIALIZER(qspi_nor_memory_data.count, 0, K_SEM_MAX_LIMIT),
#endif /* CONFIG_MULTITHREADING */
};
NRF_DT_CHECK_NODE_HAS_PINCTRL_SLEEP(QSPI_IF_BUS_NODE);
IF_ENABLED(CONFIG_PINCTRL, (PINCTRL_DT_DEFINE(QSPI_IF_BUS_NODE)));
/**
* @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;
#if NRF53_ERRATA_159_ENABLE_WORKAROUND
case NRFX_ERROR_FORBIDDEN:
LOG_ERR("nRF5340 anomaly 159 conditions detected");
LOG_ERR("Set the CPU clock to 64 MHz before starting QSPI operation");
return -ECANCELED;
#endif
case NRFX_ERROR_BUSY:
case NRFX_ERROR_TIMEOUT:
default:
return -EBUSY;
}
}
static inline struct qspi_nor_data *get_dev_data(const struct device *dev)
{
return dev->data;
}
static inline void qspi_lock(const struct device *dev)
{
#ifdef CONFIG_MULTITHREADING
struct qspi_nor_data *dev_data = get_dev_data(dev);
k_sem_take(&dev_data->sem, K_FOREVER);
#else /* CONFIG_MULTITHREADING */
ARG_UNUSED(dev);
#endif /* CONFIG_MULTITHREADING */
/*
* Change the base clock divider only for the time the driver is locked
* to perform a QSPI operation, otherwise the power consumption would be
* increased also when the QSPI peripheral is idle.
*/
#if defined(CONFIG_SOC_SERIES_NRF53X)
nrf_clock_hfclk192m_div_set(NRF_CLOCK, BASE_CLOCK_DIV);
k_busy_wait(BASE_CLOCK_SWITCH_DELAY_US);
#endif
}
static inline void qspi_unlock(const struct device *dev)
{
#if defined(CONFIG_SOC_SERIES_NRF53X)
/* Restore the default base clock divider to reduce power consumption.
*/
nrf_clock_hfclk192m_div_set(NRF_CLOCK, NRF_CLOCK_HFCLK_DIV_4);
k_busy_wait(BASE_CLOCK_SWITCH_DELAY_US);
#endif
#ifdef CONFIG_MULTITHREADING
struct qspi_nor_data *dev_data = get_dev_data(dev);
k_sem_give(&dev_data->sem);
#else /* CONFIG_MULTITHREADING */
ARG_UNUSED(dev);
#endif /* CONFIG_MULTITHREADING */
}
static inline void qspi_trans_lock(const struct device *dev)
{
#ifdef CONFIG_MULTITHREADING
struct qspi_nor_data *dev_data = get_dev_data(dev);
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 = get_dev_data(dev);
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 = get_dev_data(dev);
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 */
}
static inline void _qspi_complete(struct qspi_nor_data *dev_data)
{
if (!qspi_cfg->easydma) {
return;
}
qspi_complete(dev_data);
}
static inline void _qspi_wait_for_completion(const struct device *dev, nrfx_err_t res)
{
if (!qspi_cfg->easydma) {
return;
}
qspi_wait_for_completion(dev, res);
}
/**
* @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 bool qspi_initialized;
static int qspi_device_init(const struct device *dev)
{
struct qspi_nor_data *dev_data = get_dev_data(dev);
nrfx_err_t res;
int ret = 0;
if (!IS_ENABLED(CONFIG_NRF70_QSPI_LOW_POWER)) {
return 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) {
res = nrfx_qspi_init(&QSPIconfig, qspi_handler, dev_data);
ret = qspi_get_zephyr_ret_code(res);
NRF_QSPI->IFTIMING |= qspi_cfg->RDC4IO;
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 = get_dev_data(dev);
/* 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);
}
static void qspi_device_uninit(const struct device *dev)
{
if (!IS_ENABLED(CONFIG_NRF70_QSPI_LOW_POWER)) {
return;
}
_qspi_device_uninit(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);
}
/* RDSR wrapper. Negative value is error. */
static int qspi_rdsr(const struct device *dev)
{
uint8_t sr = -1;
const struct qspi_buf sr_buf = {
.buf = &sr,
.len = sizeof(sr),
};
struct qspi_cmd cmd = {
.op_code = SPI_NOR_CMD_RDSR,
.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);
} while ((ret >= 0) && ((ret & SPI_NOR_WIP_BIT) != 0U));
return (ret < 0) ? ret : 0;
}
/**
* @brief Fills init struct
*
* @param config Pointer to the config struct provided by user
* @param initstruct Pointer to the configuration struct
* @retval None
*/
static inline void qspi_fill_init_struct(nrfx_qspi_config_t *initstruct)
{
/* Configure XIP offset */
initstruct->xip_offset = 0;
#ifdef CONFIG_PINCTRL
initstruct->skip_gpio_cfg = true,
initstruct->skip_psel_cfg = true,
#else
/* Configure pins */
initstruct->pins.sck_pin = QSPI_IF_BUS_SCK_PIN;
initstruct->pins.csn_pin = QSPI_IF_BUS_CSN_PIN;
initstruct->pins.io0_pin = QSPI_IF_BUS_IO0_PIN;
initstruct->pins.io1_pin = QSPI_IF_BUS_IO1_PIN;
#if QSPI_IF_BUS_HAS_4_IO_PINS
initstruct->pins.io2_pin = QSPI_IF_BUS_IO2_PIN;
initstruct->pins.io3_pin = QSPI_IF_BUS_IO3_PIN;
#else
initstruct->pins.io2_pin = NRF_QSPI_PIN_NOT_CONNECTED;
initstruct->pins.io3_pin = NRF_QSPI_PIN_NOT_CONNECTED;
#endif
#endif /* CONFIG_PINCTRL */
/* Configure Protocol interface */
initstruct->prot_if.addrmode = NRF_QSPI_ADDRMODE_24BIT;
initstruct->prot_if.dpmconfig = false;
/* Configure physical interface */
initstruct->phy_if.sck_freq = INST_0_SCK_CFG;
/* Using MHZ fails checkpatch constant check */
if (QSPI_IF_DEVICE_FREQUENCY >= 16000000) {
qspi_cfg->qspi_slave_latency = 1;
}
initstruct->phy_if.sck_delay = QSPI_IF_DEVICE_RX_DELAY;
initstruct->phy_if.spi_mode = qspi_get_mode(QSPI_IF_DEVICE_CPOL, QSPI_IF_DEVICE_CPHA);
if (QSPI_IF_DEVICE_QUAD_MODE) {
initstruct->prot_if.readoc = NRF_QSPI_READOC_READ4IO;
initstruct->prot_if.writeoc = NRF_QSPI_WRITEOC_PP4IO;
} else {
initstruct->prot_if.readoc = NRF_QSPI_READOC_FASTREAD;
initstruct->prot_if.writeoc = NRF_QSPI_WRITEOC_PP;
}
initstruct->phy_if.dpmen = false;
}
/* Configures QSPI memory for the transfer */
static int qspi_nrfx_configure(const struct device *dev)
{
if (!dev) {
return -ENXIO;
}
struct qspi_nor_data *dev_data = dev->data;
qspi_fill_init_struct(&QSPIconfig);
#if defined(CONFIG_SOC_SERIES_NRF53X)
/* When the QSPI peripheral is activated, during the nrfx_qspi driver
* initialization, it reads the status of the connected flash chip.
* Make sure this transaction is performed with a valid base clock
* divider.
*/
nrf_clock_hfclk192m_div_set(NRF_CLOCK, BASE_CLOCK_DIV);
k_busy_wait(BASE_CLOCK_SWITCH_DELAY_US);
#endif
nrfx_err_t res = _nrfx_qspi_init(&QSPIconfig, qspi_handler, dev_data);
#if defined(CONFIG_SOC_SERIES_NRF53X)
/* Restore the default /4 divider after the QSPI initialization. */
nrf_clock_hfclk192m_div_set(NRF_CLOCK, NRF_CLOCK_HFCLK_DIV_4);
k_busy_wait(BASE_CLOCK_SWITCH_DELAY_US);
#endif
int ret = qspi_get_zephyr_ret_code(res);
if (ret == 0) {
/* 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. Other
* options require more logic.
*/
ret = qspi_rdsr(dev);
if (ret < 0) {
LOG_ERR("RDSR failed: %d", ret);
return ret;
}
uint8_t sr = (uint8_t)ret;
bool qe_value = (qspi_write_is_quad(QSPIconfig.prot_if.writeoc)) ||
(qspi_read_is_quad(QSPIconfig.prot_if.readoc));
const uint8_t qe_mask = BIT(6); /* only S1B6 */
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) {
const struct qspi_buf sr_buf = {
.buf = &sr,
.len = sizeof(sr),
};
struct qspi_cmd cmd = {
.op_code = SPI_NOR_CMD_WRSR,
.tx_buf = &sr_buf,
};
sr ^= qe_mask;
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);
}
}
if (ret < 0) {
LOG_ERR("QE %s failed: %d", qe_value ? "set" : "clear", ret);
}
}
return ret;
}
static inline nrfx_err_t read_non_aligned(const struct device *dev, int addr, void *dest,
size_t size)
{
uint8_t __aligned(WORD_SIZE) buf[WORD_SIZE * 2];
uint8_t *dptr = dest;
int flash_prefix = (WORD_SIZE - (addr % WORD_SIZE)) % WORD_SIZE;
if (flash_prefix > size) {
flash_prefix = size;
}
int dest_prefix = (WORD_SIZE - (int)dptr % WORD_SIZE) % WORD_SIZE;
if (dest_prefix > size) {
dest_prefix = size;
}
int flash_suffix = (size - flash_prefix) % WORD_SIZE;
int flash_middle = size - flash_prefix - flash_suffix;
int 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, int addr, void *dest, size_t size)
{
if (!dest) {
return -EINVAL;
}
/* read size must be non-zero */
if (!size) {
return 0;
}
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, int 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;
}
static int qspi_nor_write(const struct device *dev, int 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;
}
nrfx_err_t res = NRFX_SUCCESS;
int rc = qspi_device_init(dev);
if (rc != 0) {
goto out;
}
qspi_trans_lock(dev);
qspi_lock(dev);
if (size < 4U) {
res = write_sub_word(dev, addr, src, size);
} else {
res = _nrfx_qspi_write(src, size, addr);
_qspi_wait_for_completion(dev, res);
}
qspi_unlock(dev);
qspi_trans_unlock(dev);
rc = qspi_get_zephyr_ret_code(res);
out:
qspi_device_uninit(dev);
return rc;
}
/**
* @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);
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)
{
#ifdef CONFIG_PINCTRL
int ret = pinctrl_apply_state(QSPI_IF_BUS_PINCTRL_DT_DEV_CONFIG_GET,
PINCTRL_STATE_DEFAULT);
if (ret < 0) {
return ret;
}
#endif
IRQ_CONNECT(QSPI_IF_BUS_IRQN,
QSPI_IF_BUS_IRQ_PRIO,
nrfx_isr,
nrfx_qspi_irq_handler,
0);
return qspi_nor_configure(dev);
}
#if defined(CONFIG_SOC_SERIES_NRF53X)
static int qspi_cmd_encryption(const struct device *dev, nrf_qspi_encryption_t *p_cfg)
{
const struct qspi_buf tx_buf = { .buf = (uint8_t *)&p_cfg->nonce[1],
.len = sizeof(p_cfg->nonce[1]) };
const struct qspi_cmd cmd = {
.op_code = 0x4f,
.tx_buf = &tx_buf,
};
int ret = qspi_device_init(dev);
if (ret == 0) {
ret = qspi_send_cmd(dev, &cmd, false);
}
qspi_device_uninit(dev);
if (ret < 0) {
LOG_DBG("cmd_encryption failed %d", ret);
}
return ret;
}
#endif
int qspi_RDSR2(const struct device *dev, uint8_t *rdsr2)
{
int ret = 0;
uint8_t sr = 0;
const struct qspi_buf sr_buf = {
.buf = &sr,
.len = sizeof(sr),
};
struct qspi_cmd cmd = {
.op_code = 0x2f,
.rx_buf = &sr_buf,
};
ret = qspi_device_init(dev);
ret = qspi_send_cmd(dev, &cmd, false);
qspi_device_uninit(dev);
LOG_DBG("RDSR2 = 0x%x", sr);
if (ret == 0) {
*rdsr2 = sr;
}
return ret;
}
/* Wait until RDSR2 confirms RPU_WAKE write is successful */
int qspi_validate_rpu_wake_writecmd(const struct device *dev)
{
int ret = 0;
uint8_t rdsr2 = 0;
for (int ii = 0; ii < 1; ii++) {
ret = qspi_RDSR2(dev, &rdsr2);
if (!ret && (rdsr2 & RPU_WAKEUP_NOW)) {
return 0;
}
}
return -1;
}
int qspi_RDSR1(const struct device *dev, uint8_t *rdsr1)
{
int ret = 0;
uint8_t sr = 0;
const struct qspi_buf sr_buf = {
.buf = &sr,
.len = sizeof(sr),
};
struct qspi_cmd cmd = {
.op_code = 0x1f,
.rx_buf = &sr_buf,
};
ret = qspi_device_init(dev);
ret = qspi_send_cmd(dev, &cmd, false);
qspi_device_uninit(dev);
LOG_DBG("RDSR1 = 0x%x", sr);
if (ret == 0) {
*rdsr1 = sr;
}
return ret;
}
/* Wait until RDSR1 confirms RPU_AWAKE/RPU_READY */
int qspi_wait_while_rpu_awake(const struct device *dev)
{
int ret;
uint8_t val = 0;
for (int ii = 0; ii < 10; ii++) {
ret = qspi_RDSR1(dev, &val);
LOG_DBG("RDSR1 = 0x%x", val);
if (!ret && (val & RPU_AWAKE_BIT)) {
break;
}
k_msleep(1);
}
if (ret || !(val & RPU_AWAKE_BIT)) {
LOG_ERR("RPU is not awake even after 10ms");
return -1;
}
/* Restore QSPI clock frequency from DTS */
QSPIconfig.phy_if.sck_freq = INST_0_SCK_CFG;
return val;
}
int qspi_WRSR2(const struct device *dev, uint8_t data)
{
const struct qspi_buf tx_buf = {
.buf = &data,
.len = sizeof(data),
};
const struct qspi_cmd cmd = {
.op_code = 0x3f,
.tx_buf = &tx_buf,
};
int ret = qspi_device_init(dev);
if (ret == 0) {
ret = qspi_send_cmd(dev, &cmd, false);
}
qspi_device_uninit(dev);
if (ret < 0) {
LOG_ERR("cmd_wakeup RPU failed %d", ret);
}
return ret;
}
int qspi_cmd_wakeup_rpu(const struct device *dev, uint8_t data)
{
int ret;
/* Waking RPU works reliably only with lowest frequency (8MHz) */
QSPIconfig.phy_if.sck_freq = INST_0_SCK_CFG_WAKE;
ret = qspi_WRSR2(dev, data);
return ret;
}
/**
* @brief Read a register via QSPI
*
* @param dev QSPI device
* @param reg_addr Register address (opcode)
* @param reg_value Pointer to store the read value
* @return int 0 on success, negative error code on failure
*/
int qspi_read_reg(const struct device *dev, uint8_t reg_addr, uint8_t *reg_value)
{
int ret = 0;
uint8_t sr = 0;
const struct qspi_buf sr_buf = {
.buf = &sr,
.len = sizeof(sr),
};
struct qspi_cmd cmd = {
.op_code = reg_addr,
.rx_buf = &sr_buf,
};
ret = qspi_device_init(dev);
if (ret != 0) {
return ret;
}
ret = qspi_send_cmd(dev, &cmd, false);
qspi_device_uninit(dev);
LOG_DBG("QSPI read reg 0x%02x = 0x%02x", reg_addr, sr);
if (ret == 0) {
*reg_value = sr;
}
return ret;
}
/**
* @brief Write a register via QSPI
*
* @param dev QSPI device
* @param reg_addr Register address (opcode)
* @param reg_value Value to write
* @return int 0 on success, negative error code on failure
*/
int qspi_write_reg(const struct device *dev, uint8_t reg_addr, uint8_t reg_value)
{
int ret = 0;
const struct qspi_buf tx_buf = {
.buf = &reg_value,
.len = sizeof(reg_value),
};
const struct qspi_cmd cmd = {
.op_code = reg_addr,
.tx_buf = &tx_buf,
};
ret = qspi_device_init(dev);
if (ret != 0) {
return ret;
}
ret = qspi_send_cmd(dev, &cmd, false);
qspi_device_uninit(dev);
LOG_DBG("QSPI write reg 0x%02x = 0x%02x", reg_addr, reg_value);
if (ret < 0) {
LOG_ERR("QSPI write reg 0x%02x failed: %d", reg_addr, ret);
}
return ret;
}
struct device qspi_perip = {
.data = &qspi_nor_memory_data,
};
int qspi_deinit(void)
{
if (nrfx_qspi_init_check()) {
_qspi_device_uninit(&qspi_perip);
}
return 0;
}
int qspi_init(struct qspi_config *config)
{
unsigned int rc;
qspi_cfg = config;
config->readoc = config->quad_spi ? NRF_QSPI_READOC_READ4IO : NRF_QSPI_READOC_FASTREAD;
config->writeoc = config->quad_spi ? NRF_QSPI_WRITEOC_PP4IO : NRF_QSPI_WRITEOC_PP;
rc = qspi_nor_init(&qspi_perip);
k_sem_init(&qspi_cfg->lock, 1, 1);
return rc;
}
void qspi_update_nonce(unsigned int addr, int len, int hlread)
{
#if NRF_QSPI_HAS_XIP_ENC || NRF_QSPI_HAS_DMA_ENC
NRF_QSPI_Type *p_reg = NRF_QSPI;
if (!qspi_cfg->encryption) {
return;
}
if (nonce_last_addr == 0 || hlread) {
p_reg->DMA_ENC.NONCE2 = ++nonce_cnt;
} else if ((nonce_last_addr + 4) != addr) {
p_reg->DMA_ENC.NONCE2 = ++nonce_cnt;
}
nonce_last_addr = addr + len - 4;
#endif /*NRF_QSPI_HAS_XIP_ENC || NRF_QSPI_HAS_DMA_ENC*/
}
void qspi_addr_check(unsigned int addr, const void *data, unsigned int len)
{
if ((addr % 4 != 0) || (((unsigned int)data) % 4 != 0) || (len % 4 != 0)) {
LOG_ERR("%s : Unaligned address %x %x %d %x %x", __func__, addr,
(unsigned int)data, (addr % 4 != 0), (((unsigned int)data) % 4 != 0),
(len % 4 != 0));
}
}
int qspi_write(unsigned int addr, const void *data, int len)
{
int status;
qspi_addr_check(addr, data, len);
addr |= qspi_cfg->addrmask;
k_sem_take(&qspi_cfg->lock, K_FOREVER);
qspi_update_nonce(addr, len, 0);
status = qspi_nor_write(&qspi_perip, addr, data, len);
k_sem_give(&qspi_cfg->lock);
return status;
}
int qspi_read(unsigned int addr, void *data, int len)
{
int status;
qspi_addr_check(addr, data, len);
addr |= qspi_cfg->addrmask;
k_sem_take(&qspi_cfg->lock, K_FOREVER);
qspi_update_nonce(addr, len, 0);
status = qspi_nor_read(&qspi_perip, addr, data, len);
k_sem_give(&qspi_cfg->lock);
return status;
}
int qspi_hl_readw(unsigned int addr, void *data)
{
int status;
uint32_t len = 4;
uint8_t rxb[4 + (NRF_WIFI_QSPI_SLAVE_MAX_LATENCY * 4)];
len += (4 * qspi_cfg->qspi_slave_latency);
if (len > sizeof(rxb)) {
LOG_ERR("%s: len exceeded, check NRF_WIFI_QSPI_SLAVE_MAX_LATENCY (len=%u, rxb=%zu)",
__func__, (unsigned int)len, sizeof(rxb));
return -ENOMEM;
}
memset(rxb, 0, len);
k_sem_take(&qspi_cfg->lock, K_FOREVER);
qspi_update_nonce(addr, 4, 1);
status = qspi_nor_read(&qspi_perip, addr, rxb, len);
k_sem_give(&qspi_cfg->lock);
*(uint32_t *)data = *(uint32_t *)(rxb + (len - 4));
return status;
}
int qspi_hl_read(unsigned int addr, void *data, int len)
{
int count = 0;
qspi_addr_check(addr, data, len);
while (count < (len / 4)) {
qspi_hl_readw(addr + (4 * count), ((char *)data + (4 * count)));
count++;
}
return 0;
}
int qspi_cmd_sleep_rpu(const struct device *dev)
{
uint8_t data = 0x0;
/* printf("TODO : %s:", __func__); */
const struct qspi_buf tx_buf = {
.buf = &data,
.len = sizeof(data),
};
const struct qspi_cmd cmd = {
.op_code = 0x3f, /* 0x3f, //WRSR2(0x3F) WakeUP RPU. */
.tx_buf = &tx_buf,
};
int ret = qspi_device_init(dev);
if (ret == 0) {
ret = qspi_send_cmd(dev, &cmd, false);
}
qspi_device_uninit(dev);
if (ret < 0) {
LOG_ERR("cmd_wakeup RPU failed: %d", ret);
}
return ret;
}
/* Encryption public API */
int qspi_enable_encryption(uint8_t *key)
{
#if defined(CONFIG_SOC_SERIES_NRF53X)
int err = 0;
if (qspi_cfg->encryption) {
return -EALREADY;
}
int ret = qspi_device_init(&qspi_perip);
if (ret != 0) {
LOG_ERR("qspi_device_init failed: %d", ret);
return -EIO;
}
memcpy(qspi_cfg->p_cfg.key, key, 16);
err = nrfx_qspi_dma_encrypt(&qspi_cfg->p_cfg);
if (err != NRFX_SUCCESS) {
LOG_ERR("nrfx_qspi_dma_encrypt failed: %d", err);
return -EIO;
}
err = qspi_cmd_encryption(&qspi_perip, &qspi_cfg->p_cfg);
if (err != 0) {
LOG_ERR("qspi_cmd_encryption failed: %d", err);
return -EIO;
}
qspi_cfg->encryption = true;
qspi_device_uninit(&qspi_perip);
return 0;
#else
return -ENOTSUP;
#endif
}