| /* |
| * 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 = ®_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 |
| } |