/* * Copyright (c) 2019-2021, Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nordic_qspi_nor #include #include #include #include #include #include "spi_nor.h" #include "jesd216.h" #include "flash_priv.h" #include #include struct qspi_nor_config { /* JEDEC id from devicetree */ uint8_t id[SPI_NOR_MAX_ID_LEN]; /* Size from devicetree, in bytes */ uint32_t size; }; /* Main config structure */ static nrfx_qspi_config_t QSPIconfig; /* 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 */ #define INST_0_BYTES (DT_INST_PROP(0, size) / 8) #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."); /* for accessing devicetree properties of the bus node */ #define QSPI_NODE DT_BUS(DT_DRV_INST(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_, \ DT_STRING_TOKEN(DT_DRV_INST(0), \ quad_enable_requirements)) BUILD_ASSERT(((INST_0_QER == JESD216_DW15_QER_NONE) || (INST_0_QER == JESD216_DW15_QER_S1B6)), "Driver only supports NONE or S1B6 for quad-enable-requirements"); #if NRF52_ERRATA_122_PRESENT #include static int anomaly_122_init(const struct device *dev); static void anomaly_122_uninit(const struct device *dev); #define ANOMALY_122_INIT(dev) anomaly_122_init(dev) #define ANOMALY_122_UNINIT(dev) anomaly_122_uninit(dev) #else #define ANOMALY_122_INIT(dev) 0 #define ANOMALY_122_UNINIT(dev) #endif #define WORD_SIZE 4 LOG_MODULE_REGISTER(qspi_nor, CONFIG_FLASH_LOG_LEVEL); static const struct flash_parameters qspi_flash_parameters = { .write_block_size = 4, .erase_value = 0xff, }; /** * @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; #if NRF52_ERRATA_122_PRESENT /* The semaphore to control driver init/uninit. */ struct k_sem count; #endif #else /* CONFIG_MULTITHREADING */ /* A flag that signals completed transfer when threads are * not enabled. */ volatile bool ready; #endif /* CONFIG_MULTITHREADING */ }; static int qspi_nor_write_protection_set(const struct device *dev, bool write_protect); 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; } static inline nrf_qspi_addrmode_t qspi_get_address_size(bool addr_size) { return addr_size ? NRF_QSPI_ADDRMODE_32BIT : NRF_QSPI_ADDRMODE_24BIT; } /** * @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 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), #if NRF52_ERRATA_122_PRESENT .count = Z_SEM_INITIALIZER(qspi_nor_memory_data.count, 0, K_SEM_MAX_LIMIT), #endif #endif /* CONFIG_MULTITHREADING */ }; /** * @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 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 */ } static inline void qspi_unlock(const struct device *dev) { #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 */ } /** * @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); } } #if NRF52_ERRATA_122_PRESENT static bool qspi_initialized; static int anomaly_122_init(const struct device *dev) { struct qspi_nor_data *dev_data = get_dev_data(dev); nrfx_err_t res; int ret = 0; if (!nrf52_errata_122()) { return 0; } qspi_lock(dev); /* In multithreading, driver can call anomaly_122_init more than once * before calling anomaly_122_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); qspi_initialized = (ret == 0); } qspi_unlock(dev); return ret; } static void anomaly_122_uninit(const struct device *dev) { bool last = true; if (!nrf52_errata_122()) { return; } 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); } } nrf_gpio_cfg_output(QSPI_PROP_AT(csn_pins, 0)); nrf_gpio_pin_set(QSPI_PROP_AT(csn_pins, 0)); nrfx_qspi_uninit(); qspi_initialized = false; } qspi_unlock(dev); } #endif /* NRF52_ERRATA_122_PRESENT */ /* 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; } /* 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 = ANOMALY_122_INIT(dev); if (rv != 0) { goto out; } qspi_trans_lock(dev); rv = qspi_nor_write_protection_set(dev, false); qspi_lock(dev); while ((rv == 0) && (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); rv = -EINVAL; } 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); } } qspi_unlock(dev); int rv2 = qspi_nor_write_protection_set(dev, true); qspi_trans_unlock(dev); if (!rv) { rv = rv2; } out: ANOMALY_122_UNINIT(dev); return rv; } /** * @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; /* Configure pins */ initstruct->pins.sck_pin = DT_PROP(QSPI_NODE, sck_pin); initstruct->pins.csn_pin = QSPI_PROP_AT(csn_pins, 0); initstruct->pins.io0_pin = QSPI_PROP_AT(io_pins, 0); initstruct->pins.io1_pin = QSPI_PROP_AT(io_pins, 1); #if QSPI_PROP_LEN(io_pins) > 2 initstruct->pins.io2_pin = QSPI_PROP_AT(io_pins, 2); initstruct->pins.io3_pin = QSPI_PROP_AT(io_pins, 3); #else initstruct->pins.io2_pin = NRF_QSPI_PIN_NOT_CONNECTED; initstruct->pins.io3_pin = NRF_QSPI_PIN_NOT_CONNECTED; #endif /* Configure Protocol interface */ #if DT_INST_NODE_HAS_PROP(0, readoc) initstruct->prot_if.readoc = (nrf_qspi_readoc_t)qspi_get_lines_read(DT_ENUM_IDX(DT_DRV_INST(0), readoc)); #else initstruct->prot_if.readoc = NRF_QSPI_READOC_FASTREAD; #endif #if DT_INST_NODE_HAS_PROP(0, writeoc) initstruct->prot_if.writeoc = (nrf_qspi_writeoc_t)qspi_get_lines_write(DT_ENUM_IDX(DT_DRV_INST(0), writeoc)); #else initstruct->prot_if.writeoc = NRF_QSPI_WRITEOC_PP; #endif initstruct->prot_if.addrmode = qspi_get_address_size(DT_INST_PROP(0, address_size_32)); initstruct->prot_if.dpmconfig = false; /* Configure physical interface */ initstruct->phy_if.sck_freq = (INST_0_SCK_FREQUENCY > NRF_QSPI_BASE_CLOCK_FREQ) ? NRF_QSPI_FREQ_DIV1 : (NRF_QSPI_BASE_CLOCK_FREQ / INST_0_SCK_FREQUENCY) - 1; initstruct->phy_if.sck_delay = DT_INST_PROP(0, sck_delay); initstruct->phy_if.spi_mode = qspi_get_mode(DT_INST_PROP(0, cpol), DT_INST_PROP(0, cpha)); 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); nrfx_err_t res = nrfx_qspi_init(&QSPIconfig, qspi_handler, dev_data); int ret = qspi_get_zephyr_ret_code(res); #if DT_INST_NODE_HAS_PROP(0, rx_delay) if (ret == 0 && !nrf53_errata_121()) { nrf_qspi_iftiming_set(NRF_QSPI, DT_INST_PROP(0, rx_delay)); } #endif if ((ret == 0) && (INST_0_QER != JESD216_DW15_QER_NONE)) { /* 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 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 = ANOMALY_122_INIT(dev); if (ret == 0) { ret = qspi_send_cmd(dev, &cmd, false); } ANOMALY_122_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 res = ANOMALY_122_INIT(dev); if (res != NRFX_SUCCESS) { LOG_DBG("ANOMALY_122_INIT: %x", res); goto out; } 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); ANOMALY_122_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 * @param flash_id The flash info structure which contains the * expected JEDEC ID * @return 0 on success, negative errno code otherwise */ static inline int qspi_nor_read_id(const struct device *dev, const struct qspi_nor_config *const flash_id) { uint8_t id[SPI_NOR_MAX_ID_LEN]; int ret = qspi_read_jedec_id(dev, id); if (ret != 0) { return -EIO; } if (memcmp(flash_id->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], flash_id->id[0], flash_id->id[1], flash_id->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 = ANOMALY_122_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: ANOMALY_122_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"); #define NVMC_WRITE_OK (CONFIG_NORDIC_QSPI_NOR_STACK_WRITE_BUFFER_SIZE > 0) /* 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) { #if NVMC_WRITE_OK uint8_t __aligned(4) buf[CONFIG_NORDIC_QSPI_NOR_STACK_WRITE_BUFFER_SIZE]; const uint8_t *sp = sptr; nrfx_err_t res = NRFX_SUCCESS; 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 /* NVMC_WRITE_OK */ nrfx_err_t res = NRFX_ERROR_INVALID_ADDR; #endif /* NVMC_WRITE_OK */ 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 = ANOMALY_122_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: ANOMALY_122_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) { const struct qspi_nor_config *params = dev->config; int ret = qspi_nrfx_configure(dev); if (ret != 0) { return ret; } ANOMALY_122_UNINIT(dev); /* now the spi bus is configured, we can verify the flash id */ if (qspi_nor_read_id(dev, params) != 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 PCLK192M clock, from which the SCK frequency is * derived, is not prescaled (the default setting after reset is * "divide by 4"). */ nrf_clock_hfclk192m_div_set(NRF_CLOCK, NRF_CLOCK_HFCLK_DIV_1); #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); 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 */ }; static const struct qspi_nor_config flash_id = { .id = DT_INST_PROP(0, jedec_id), .size = INST_0_BYTES, }; DEVICE_DT_INST_DEFINE(0, &qspi_nor_init, NULL, &qspi_nor_memory_data, &flash_id, POST_KERNEL, CONFIG_NORDIC_QSPI_NOR_INIT_PRIORITY, &qspi_nor_api);