diff --git a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi index 8392389c0ad67..165fa12ba80d5 100644 --- a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi +++ b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi @@ -174,6 +174,15 @@ zephyr,memory-region = "DMA_RAM3x_RAD"; zephyr,memory-attr = <( DT_MEM_DMA )>; }; + + xip_region: memory@60000000 { + compatible = "nordic,owned-memory"; + reg = <0x60000000 0x20000000>; + status = "disabled"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x60000000 0x20000000>; + }; }; }; diff --git a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-pinctrl.dtsi b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-pinctrl.dtsi index 9b574a18ec52c..f62df87dfe269 100644 --- a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-pinctrl.dtsi +++ b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-pinctrl.dtsi @@ -73,12 +73,37 @@ /omit-if-no-ref/ exmif_default: exmif_default { group1 { psels = , + , + , , - ; + , + , + , + , + , + , + ; nordic,drive-mode = ; }; }; + /omit-if-no-ref/ exmif_sleep: exmif_sleep { + group1 { + low-power-enable; + psels = , + , + , + , + , + , + , + , + , + , + ; + }; + }; + /omit-if-no-ref/ can120_default: can120_default { group1 { psels = , diff --git a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp.dts b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp.dts index 95d557bf944de..ed53ade9d0ffe 100644 --- a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp.dts +++ b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp.dts @@ -256,15 +256,15 @@ ipc0: &cpuapp_cpurad_ipc { }; &exmif { - cs-gpios = <&gpio6 3 GPIO_ACTIVE_LOW>; pinctrl-0 = <&exmif_default>; - pinctrl-names = "default"; + pinctrl-1 = <&exmif_sleep>; + pinctrl-names = "default", "sleep"; status = "okay"; + mx25uw63: mx25uw6345g@0 { - compatible = "jedec,spi-nor"; + compatible = "jedec,mspi-nor"; status = "disabled"; reg = <0>; - spi-max-frequency = ; jedec-id = [c2 84 37]; sfdp-bfp = [ e5 20 8a ff ff ff ff 03 00 ff 00 ff 00 ff 00 ff @@ -277,6 +277,17 @@ ipc0: &cpuapp_cpurad_ipc { has-dpd; t-enter-dpd = <10000>; t-exit-dpd = <30000>; + reset-gpios = <&gpio6 12 GPIO_ACTIVE_LOW>; + t-reset-pulse = <10000>; + t-reset-recovery = <35000>; + + mspi-max-frequency = ; + mspi-io-mode = "MSPI_IO_MODE_OCTAL"; + mspi-data-rate = "MSPI_DATA_RATE_SINGLE"; + mspi-hardware-ce-num = <1>; + mspi-cpp-mode = "MSPI_CPP_MODE_0"; + mspi-endian = "MSPI_BIG_ENDIAN"; + mspi-ce-polarity = "MSPI_CE_ACTIVE_LOW"; }; }; diff --git a/boards/nordic/nrf9280pdk/nrf9280pdk_nrf9280-pinctrl.dtsi b/boards/nordic/nrf9280pdk/nrf9280pdk_nrf9280-pinctrl.dtsi index 4cae3ba580ffc..709fda74f89fb 100644 --- a/boards/nordic/nrf9280pdk/nrf9280pdk_nrf9280-pinctrl.dtsi +++ b/boards/nordic/nrf9280pdk/nrf9280pdk_nrf9280-pinctrl.dtsi @@ -54,12 +54,37 @@ /omit-if-no-ref/ exmif_default: exmif_default { group1 { psels = , + , + , , - ; + , + , + , + , + , + , + ; nordic,drive-mode = ; }; }; + /omit-if-no-ref/ exmif_sleep: exmif_sleep { + group1 { + low-power-enable; + psels = , + , + , + , + , + , + , + , + , + , + ; + }; + }; + /omit-if-no-ref/ pwm130_default: pwm130_default { group1 { psels = ; diff --git a/boards/nordic/nrf9280pdk/nrf9280pdk_nrf9280_cpuapp.dts b/boards/nordic/nrf9280pdk/nrf9280pdk_nrf9280_cpuapp.dts index 11760974a11aa..31502189d3f8e 100644 --- a/boards/nordic/nrf9280pdk/nrf9280pdk_nrf9280_cpuapp.dts +++ b/boards/nordic/nrf9280pdk/nrf9280pdk_nrf9280_cpuapp.dts @@ -242,15 +242,15 @@ ipc0: &cpuapp_cpurad_ipc { }; &exmif { - cs-gpios = <&gpio6 3 GPIO_ACTIVE_LOW>; pinctrl-0 = <&exmif_default>; - pinctrl-names = "default"; + pinctrl-1 = <&exmif_sleep>; + pinctrl-names = "default", "sleep"; status = "okay"; + mx25uw63: mx25uw6345g@0 { - compatible = "jedec,spi-nor"; + compatible = "jedec,mspi-nor"; status = "disabled"; reg = <0>; - spi-max-frequency = ; jedec-id = [c2 84 37]; sfdp-bfp = [ e5 20 8a ff ff ff ff 03 00 ff 00 ff 00 ff 00 ff @@ -263,6 +263,17 @@ ipc0: &cpuapp_cpurad_ipc { has-dpd; t-enter-dpd = <10000>; t-exit-dpd = <30000>; + reset-gpios = <&gpio6 12 GPIO_ACTIVE_LOW>; + t-reset-pulse = <10000>; + t-reset-recovery = <35000>; + + mspi-max-frequency = ; + mspi-io-mode = "MSPI_IO_MODE_OCTAL"; + mspi-data-rate = "MSPI_DATA_RATE_SINGLE"; + mspi-hardware-ce-num = <1>; + mspi-cpp-mode = "MSPI_CPP_MODE_0"; + mspi-endian = "MSPI_BIG_ENDIAN"; + mspi-ce-polarity = "MSPI_CE_ACTIVE_LOW"; }; }; diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 474205e397350..925a4cf8f4242 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -34,6 +34,7 @@ zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_FLEXSPI_MX25UM51345G flash_mcux_f zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_FLEXSPI_NOR flash_mcux_flexspi_nor.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MSPI_ATXP032 flash_mspi_atxp032.c) zephyr_library_sources_ifdef(CONFIG_FLASH_MSPI_EMUL_DEVICE flash_mspi_emul_device.c) +zephyr_library_sources_ifdef(CONFIG_FLASH_MSPI_NOR flash_mspi_nor.c) zephyr_library_sources_ifdef(CONFIG_FLASH_NPCX_FIU_NOR flash_npcx_fiu_nor.c) zephyr_library_sources_ifdef(CONFIG_FLASH_NPCX_FIU_QSPI flash_npcx_fiu_qspi.c) zephyr_library_sources_ifdef(CONFIG_FLASH_RPI_PICO flash_rpi_pico.c) diff --git a/drivers/flash/Kconfig.mspi b/drivers/flash/Kconfig.mspi index 158fcefb528f5..d699aa4930937 100644 --- a/drivers/flash/Kconfig.mspi +++ b/drivers/flash/Kconfig.mspi @@ -28,4 +28,27 @@ config FLASH_MSPI_ATXP032 select FLASH_JESD216 select MSPI_AMBIQ_AP3 if SOC_SERIES_APOLLO3X +menuconfig FLASH_MSPI_NOR + bool "Generic MSPI NOR Flash" + default y + depends on DT_HAS_JEDEC_MSPI_NOR_ENABLED + select FLASH_MSPI + select FLASH_HAS_EXPLICIT_ERASE + select FLASH_JESD216 + select GPIO if $(dt_compat_any_has_prop,$(DT_COMPAT_JEDEC_MSPI_NOR),reset-gpios) + +if FLASH_MSPI_NOR + +config FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE + int "Page size to use for FLASH_LAYOUT feature" + depends on FLASH_PAGE_LAYOUT + default 65536 + help + When CONFIG_FLASH_PAGE_LAYOUT is used, this driver will support that + API. By default the page size corresponds to the block size (65536). + Other options include the 32K-byte erase size (32768), the sector + size (4096), or any non-zero multiple of the sector size. + +endif # FLASH_MSPI_NOR + endmenu diff --git a/drivers/flash/flash_mspi_nor.c b/drivers/flash/flash_mspi_nor.c new file mode 100644 index 0000000000000..a5ced1e00703b --- /dev/null +++ b/drivers/flash/flash_mspi_nor.c @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT jedec_mspi_nor + +#include +#include +#include +#include +#include +#include + +#include "jesd216.h" +#include "spi_nor.h" + +LOG_MODULE_REGISTER(flash_mspi_nor, CONFIG_FLASH_LOG_LEVEL); + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) +#define WITH_RESET_GPIO 1 +#endif + +struct flash_mspi_nor_data { + struct k_sem acquired; + struct mspi_xfer_packet packet; + struct mspi_xfer xfer; +}; + +struct flash_mspi_nor_config { + const struct device *bus; + uint32_t flash_size; + struct mspi_dev_id mspi_id; + struct mspi_dev_cfg mspi_cfg; + enum mspi_dev_cfg_mask mspi_cfg_mask; +#if defined(CONFIG_MSPI_XIP) + struct mspi_xip_cfg xip_cfg; +#endif +#if defined(WITH_RESET_GPIO) + struct gpio_dt_spec reset; + uint32_t reset_pulse_us; + uint32_t reset_recovery_us; +#endif +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + struct flash_pages_layout layout; +#endif + uint8_t jedec_id[SPI_NOR_MAX_ID_LEN]; +}; + +static int acquire(const struct device *dev) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + int rc; + + k_sem_take(&dev_data->acquired, K_FOREVER); + + rc = pm_device_runtime_get(dev_config->bus); + if (rc < 0) { + LOG_ERR("pm_device_runtime_get() failed: %d", rc); + } else { + /* This acquires the MSPI controller and reconfigures it + * if needed for the flash device. + */ + rc = mspi_dev_config(dev_config->bus, &dev_config->mspi_id, + dev_config->mspi_cfg_mask, + &dev_config->mspi_cfg); + if (rc < 0) { + LOG_ERR("mspi_dev_config() failed: %d", rc); + } else { + return 0; + } + + (void)pm_device_runtime_put(dev_config->bus); + } + + k_sem_give(&dev_data->acquired); + return rc; +} + +static void release(const struct device *dev) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + + /* This releases the MSPI controller. */ + (void)mspi_get_channel_status(dev_config->bus, 0); + + (void)pm_device_runtime_put(dev_config->bus); + + k_sem_give(&dev_data->acquired); +} + +static inline uint32_t dev_flash_size(const struct device *dev) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + + return dev_config->flash_size; +} + +static inline uint16_t dev_page_size(const struct device *dev) +{ + return SPI_NOR_PAGE_SIZE; +} + +static int api_read(const struct device *dev, off_t addr, void *dest, + size_t size) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + const uint32_t flash_size = dev_flash_size(dev); + int rc; + + if (size == 0) { + return 0; + } + + if ((addr < 0) || ((addr + size) > flash_size)) { + return -EINVAL; + } + + rc = acquire(dev); + if (rc < 0) { + return rc; + } + + /* TODO: get rid of all these hard-coded values for MX25Ux chips */ + dev_data->xfer.cmd_length = 2; + dev_data->xfer.addr_length = 4; + dev_data->xfer.rx_dummy = 20; + dev_data->packet.dir = MSPI_RX; + dev_data->packet.cmd = SPI_NOR_OCMD_RD; + dev_data->packet.address = addr; + dev_data->packet.data_buf = dest; + dev_data->packet.num_bytes = size; + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + + release(dev); + + if (rc < 0) { + LOG_ERR("SPI_NOR_OCMD_RD xfer failed: %d", rc); + return rc; + } + + return 0; +} + +static int wait_until_ready(const struct device *dev, k_timeout_t poll_period) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + uint8_t status_reg; + int rc; + + while (true) { + dev_data->xfer.cmd_length = 2; + dev_data->xfer.addr_length = 4; + dev_data->xfer.rx_dummy = 4; + dev_data->packet.dir = MSPI_RX; + dev_data->packet.cmd = SPI_NOR_OCMD_RDSR; + dev_data->packet.address = 0; + dev_data->packet.data_buf = &status_reg; + dev_data->packet.num_bytes = sizeof(status_reg); + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + LOG_ERR("SPI_NOR_OCMD_RDSR xfer failed: %d", rc); + return rc; + } + if (!(status_reg & SPI_NOR_WIP_BIT)) { + break; + } + + k_sleep(poll_period); + } + + return 0; +} + +static int api_write(const struct device *dev, off_t addr, const void *src, + size_t size) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + const uint32_t flash_size = dev_flash_size(dev); + const uint16_t page_size = dev_page_size(dev); + int rc; + + if (size == 0) { + return 0; + } + + if ((addr < 0) || ((addr + size) > flash_size)) { + return -EINVAL; + } + + rc = acquire(dev); + if (rc < 0) { + return rc; + } + + while (size > 0) { + /* Split write into parts, each within one page only. */ + uint16_t page_offset = (uint16_t)(addr % page_size); + uint16_t page_left = page_size - page_offset; + uint16_t to_write = (uint16_t)MIN(size, page_left); + + dev_data->xfer.cmd_length = 2; + dev_data->xfer.tx_dummy = 0; + dev_data->packet.dir = MSPI_TX; + + dev_data->xfer.addr_length = 0; + dev_data->packet.cmd = SPI_NOR_OCMD_WREN; + dev_data->packet.num_bytes = 0; + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + LOG_ERR("SPI_NOR_OCMD_WREN xfer failed: %d", rc); + break; + } + + dev_data->xfer.addr_length = 4; + dev_data->packet.cmd = SPI_NOR_OCMD_PAGE_PRG; + dev_data->packet.address = addr; + dev_data->packet.data_buf = (uint8_t *)src; + dev_data->packet.num_bytes = to_write; + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + LOG_ERR("SPI_NOR_OCMD_PAGE_PRG xfer failed: %d", rc); + break; + } + + addr += to_write; + src = (const uint8_t *)src + to_write; + size -= to_write; + + rc = wait_until_ready(dev, K_MSEC(1)); + if (rc < 0) { + break; + } + } + + release(dev); + + return rc; +} + +static int api_erase(const struct device *dev, off_t addr, size_t size) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + const uint32_t flash_size = dev_flash_size(dev); + int rc = 0; + + if ((addr < 0) || ((addr + size) > flash_size)) { + return -EINVAL; + } + + if (!SPI_NOR_IS_SECTOR_ALIGNED(addr)) { + return -EINVAL; + } + + if ((size % SPI_NOR_SECTOR_SIZE) != 0) { + return -EINVAL; + } + + rc = acquire(dev); + if (rc < 0) { + return rc; + } + + while (size > 0) { + dev_data->xfer.cmd_length = 2; + dev_data->xfer.tx_dummy = 0; + dev_data->packet.dir = MSPI_TX; + dev_data->packet.num_bytes = 0; + + dev_data->xfer.addr_length = 0; + dev_data->packet.cmd = SPI_NOR_OCMD_WREN; + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + LOG_ERR("SPI_NOR_OCMD_WREN xfer failed: %d", rc); + break; + } + + if (size == flash_size) { + /* Chip erase. */ + dev_data->xfer.addr_length = 0; + dev_data->packet.cmd = SPI_NOR_OCMD_CE; + + size -= flash_size; + } else { + /* Sector erase. */ + dev_data->xfer.addr_length = 4; + dev_data->packet.cmd = SPI_NOR_OCMD_SE; + dev_data->packet.address = addr; + + addr += SPI_NOR_SECTOR_SIZE; + size -= SPI_NOR_SECTOR_SIZE; + } + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + LOG_ERR("Erase command 0x%02x xfer failed: %d", + dev_data->packet.cmd, rc); + break; + } + + rc = wait_until_ready(dev, K_MSEC(1)); + if (rc < 0) { + break; + } + } + + release(dev); + + return rc; +} + +static const +struct flash_parameters *api_get_parameters(const struct device *dev) +{ + ARG_UNUSED(dev); + + static const struct flash_parameters parameters = { + .write_block_size = 1, + .erase_value = 0xff, + }; + + return ¶meters; +} + +static int read_jedec_id(const struct device *dev, uint8_t *id) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + int rc; + + dev_data->xfer.cmd_length = 2; + dev_data->xfer.addr_length = 4; + dev_data->xfer.rx_dummy = 4; + dev_data->packet.dir = MSPI_RX; + dev_data->packet.cmd = JESD216_OCMD_READ_ID; + dev_data->packet.address = 0; + dev_data->packet.data_buf = id; + dev_data->packet.num_bytes = JESD216_READ_ID_LEN; + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + printk("mspi_transceive() failed: %d\n", rc); + return rc; + } + + return rc; +} + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) +static void api_page_layout(const struct device *dev, + const struct flash_pages_layout **layout, + size_t *layout_size) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + + *layout = &dev_config->layout; + *layout_size = 1; +} +#endif /* CONFIG_FLASH_PAGE_LAYOUT */ + +#if defined(CONFIG_FLASH_JESD216_API) +static int api_sfdp_read(const struct device *dev, off_t addr, void *dest, + size_t size) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + int rc; + + if (size == 0) { + return 0; + } + + rc = acquire(dev); + if (rc < 0) { + return rc; + } + + dev_data->xfer.cmd_length = 2; + dev_data->xfer.addr_length = 4; + dev_data->xfer.rx_dummy = 20; + dev_data->packet.dir = MSPI_RX; + dev_data->packet.cmd = JESD216_OCMD_READ_SFDP; + dev_data->packet.address = addr; + dev_data->packet.data_buf = dest; + dev_data->packet.num_bytes = size; + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + printk("JESD216_OCMD_READ_SFDP xfer failed: %d\n", rc); + return rc; + } + + release(dev); + + return rc; +} + +static int api_read_jedec_id(const struct device *dev, uint8_t *id) +{ + int rc = 0; + + rc = acquire(dev); + if (rc < 0) { + return rc; + } + + rc = read_jedec_id(dev, id); + + release(dev); + + return rc; +} +#endif /* CONFIG_FLASH_JESD216_API */ + +static int dev_pm_action_cb(const struct device *dev, + enum pm_device_action action) +{ + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + break; + case PM_DEVICE_ACTION_RESUME: + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static int flash_chip_init(const struct device *dev) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + struct mspi_dev_cfg init_dev_cfg = dev_config->mspi_cfg; + uint8_t id[JESD216_READ_ID_LEN] = {0}; + int rc; + + init_dev_cfg.freq = MHZ(1); + init_dev_cfg.io_mode = MSPI_IO_MODE_SINGLE; + + rc = mspi_dev_config(dev_config->bus, &dev_config->mspi_id, + MSPI_DEVICE_CONFIG_ALL, &init_dev_cfg); + if (rc < 0) { + LOG_ERR("Failed to set initial device config: %d", rc); + return rc; + } + + dev_data->xfer.xfer_mode = MSPI_PIO; + dev_data->xfer.packets = &dev_data->packet; + dev_data->xfer.num_packet = 1; + dev_data->xfer.timeout = 10; + + dev_data->xfer.cmd_length = 1; + dev_data->xfer.addr_length = 0; + dev_data->xfer.tx_dummy = 0; + dev_data->xfer.rx_dummy = 0; + + dev_data->packet.dir = MSPI_RX; + dev_data->packet.cmd = JESD216_CMD_READ_ID; + dev_data->packet.data_buf = id; + dev_data->packet.num_bytes = sizeof(id); + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + LOG_ERR("Failed to read JEDEC ID in single line mode: %d", rc); + return rc; + } + + /* + * If the read ID does not match the one from DTS, assume the flash + * is already in the Octa I/O mode, so switching it is not needed. + */ + if (memcmp(id, dev_config->jedec_id, sizeof(id)) == 0) { + static const uint8_t enable_sopi[] = { 0x01 }; + + dev_data->packet.dir = MSPI_TX; + dev_data->packet.cmd = SPI_NOR_CMD_WREN; + dev_data->packet.num_bytes = 0; + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + LOG_ERR("SPI_NOR_CMD_WREN xfer failed: %d", rc); + return rc; + } + + dev_data->xfer.addr_length = 4; + dev_data->packet.cmd = SPI_NOR_CMD_WR_CFGREG2; + dev_data->packet.address = 0; + dev_data->packet.data_buf = (uint8_t *)&enable_sopi; + dev_data->packet.num_bytes = sizeof(enable_sopi); + rc = mspi_transceive(dev_config->bus, &dev_config->mspi_id, + &dev_data->xfer); + if (rc < 0) { + printk("SPI_NOR_CMD_WR_CFGREG2 xfer failed: %d\n", rc); + return rc; + } + } + + rc = mspi_dev_config(dev_config->bus, &dev_config->mspi_id, + MSPI_DEVICE_CONFIG_ALL, &dev_config->mspi_cfg); + if (rc < 0) { + LOG_ERR("Failed to set device config: %d", rc); + return rc; + } + + rc = read_jedec_id(dev, id); + if (rc < 0) { + return rc; + } + + if (memcmp(id, dev_config->jedec_id, sizeof(id)) != 0) { + LOG_ERR("JEDEC ID mismatch, read: %02x %02x %02x, " + "expected: %02x %02x %02x", + id[0], id[1], id[2], + dev_config->jedec_id[0], + dev_config->jedec_id[1], + dev_config->jedec_id[2]); + return -ENODEV; + } + +#if defined(CONFIG_MSPI_XIP) + /* Enable XIP access for this chip if specified so in DT. */ + if (dev_config->xip_cfg.enable) { + rc = mspi_xip_config(dev_config->bus, &dev_config->mspi_id, + &dev_config->xip_cfg); + if (rc < 0) { + return rc; + } + } +#endif + + return 0; +} + +static int drv_init(const struct device *dev) +{ + const struct flash_mspi_nor_config *dev_config = dev->config; + struct flash_mspi_nor_data *dev_data = dev->data; + int rc; + + if (!device_is_ready(dev_config->bus)) { + LOG_ERR("Device %s is not ready", dev_config->bus->name); + return -ENODEV; + } + +#if defined(WITH_RESET_GPIO) + if (dev_config->reset.port) { + if (!gpio_is_ready_dt(&dev_config->reset)) { + LOG_ERR("Device %s is not ready", + dev_config->reset.port->name); + return -ENODEV; + } + + rc = gpio_pin_configure_dt(&dev_config->reset, + GPIO_OUTPUT_ACTIVE); + if (rc < 0) { + LOG_ERR("Failed to activate RESET: %d", rc); + return -EIO; + } + + if (dev_config->reset_pulse_us != 0) { + k_busy_wait(dev_config->reset_pulse_us); + } + + rc = gpio_pin_set_dt(&dev_config->reset, 0); + if (rc < 0) { + LOG_ERR("Failed to deactivate RESET: %d", rc); + return -EIO; + } + + if (dev_config->reset_recovery_us != 0) { + k_busy_wait(dev_config->reset_recovery_us); + } + } +#endif + + rc = pm_device_runtime_get(dev_config->bus); + if (rc < 0) { + LOG_ERR("pm_device_runtime_get() failed: %d", rc); + return rc; + } + + rc = flash_chip_init(dev); + + /* Release the MSPI controller - it was acquired by the call to + * mspi_dev_config() in flash_chip_init(). + */ + (void)mspi_get_channel_status(dev_config->bus, 0); + + (void)pm_device_runtime_put(dev_config->bus); + + if (rc < 0) { + return rc; + } + + k_sem_init(&dev_data->acquired, 1, K_SEM_MAX_LIMIT); + + return pm_device_driver_init(dev, dev_pm_action_cb); +} + +static DEVICE_API(flash, drv_api) = { + .read = api_read, + .write = api_write, + .erase = api_erase, + .get_parameters = api_get_parameters, +#if defined(CONFIG_FLASH_PAGE_LAYOUT) + .page_layout = api_page_layout, +#endif +#if defined(CONFIG_FLASH_JESD216_API) + .sfdp_read = api_sfdp_read, + .read_jedec_id = api_read_jedec_id, +#endif +}; + +#define FLASH_SIZE_INST(inst) (DT_INST_PROP(inst, size) / 8) + +#if defined(CONFIG_FLASH_PAGE_LAYOUT) +BUILD_ASSERT((CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE % 4096) == 0, + "MSPI_NOR_FLASH_LAYOUT_PAGE_SIZE must be multiple of 4096"); +#define FLASH_PAGE_LAYOUT_DEFINE(inst) \ + .layout = { \ + .pages_size = CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE, \ + .pages_count = FLASH_SIZE_INST(inst) \ + / CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE, \ + }, +#define FLASH_PAGE_LAYOUT_CHECK(inst) \ +BUILD_ASSERT((FLASH_SIZE_INST(inst) % CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE) == 0, \ + "MSPI_NOR_FLASH_LAYOUT_PAGE_SIZE incompatible with flash size, instance " #inst); +#else +#define FLASH_PAGE_LAYOUT_DEFINE(inst) +#define FLASH_PAGE_LAYOUT_CHECK(inst) +#endif + +/* MSPI bus must be initialized before this device. */ +#if (CONFIG_MSPI_INIT_PRIORITY < CONFIG_FLASH_INIT_PRIORITY) +#define INIT_PRIORITY CONFIG_FLASH_INIT_PRIORITY +#else +#define INIT_PRIORITY UTIL_INC(CONFIG_MSPI_INIT_PRIORITY) +#endif + +#define FLASH_MSPI_NOR_INST(inst) \ + BUILD_ASSERT(DT_INST_ENUM_IDX(inst, mspi_io_mode) == \ + MSPI_IO_MODE_OCTAL, \ + "Only Octal I/O mode is supported for now"); \ + PM_DEVICE_DT_INST_DEFINE(inst, dev_pm_action_cb); \ + static struct flash_mspi_nor_data dev##inst##_data; \ + static const struct flash_mspi_nor_config dev##inst##_config = { \ + .bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + .flash_size = FLASH_SIZE_INST(inst), \ + .mspi_id = MSPI_DEVICE_ID_DT_INST(inst), \ + .mspi_cfg = MSPI_DEVICE_CONFIG_DT_INST(inst), \ + .mspi_cfg_mask = DT_PROP(DT_INST_BUS(inst), \ + software_multiperipheral) \ + ? MSPI_DEVICE_CONFIG_ALL \ + : MSPI_DEVICE_CONFIG_NONE, \ + IF_ENABLED(CONFIG_MSPI_XIP, \ + (.xip_cfg = MSPI_XIP_CONFIG_DT_INST(inst),)) \ + IF_ENABLED(WITH_RESET_GPIO, \ + (.reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}), \ + .reset_pulse_us = DT_INST_PROP_OR(inst, t_reset_pulse, 0) \ + / 1000, \ + .reset_recovery_us = DT_INST_PROP_OR(inst, t_reset_recovery, 0) \ + / 1000,)) \ + FLASH_PAGE_LAYOUT_DEFINE(inst) \ + .jedec_id = DT_INST_PROP(inst, jedec_id), \ + }; \ + FLASH_PAGE_LAYOUT_CHECK(inst) \ + DEVICE_DT_INST_DEFINE(inst, \ + drv_init, PM_DEVICE_DT_INST_GET(inst), \ + &dev##inst##_data, &dev##inst##_config, \ + POST_KERNEL, INIT_PRIORITY, \ + &drv_api); + +DT_INST_FOREACH_STATUS_OKAY(FLASH_MSPI_NOR_INST) diff --git a/drivers/mspi/CMakeLists.txt b/drivers/mspi/CMakeLists.txt index f248cb0b53337..78f704393f428 100644 --- a/drivers/mspi/CMakeLists.txt +++ b/drivers/mspi/CMakeLists.txt @@ -4,4 +4,5 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/mspi.h) zephyr_library() zephyr_library_sources_ifdef(CONFIG_MSPI_AMBIQ_AP3 mspi_ambiq_ap3.c) +zephyr_library_sources_ifdef(CONFIG_MSPI_DW mspi_dw.c) zephyr_library_sources_ifdef(CONFIG_MSPI_EMUL mspi_emul.c) diff --git a/drivers/mspi/Kconfig b/drivers/mspi/Kconfig index 0adb2a3439338..269d8d16f04ac 100644 --- a/drivers/mspi/Kconfig +++ b/drivers/mspi/Kconfig @@ -60,6 +60,7 @@ module-str = mspi source "subsys/logging/Kconfig.template.log_config" source "drivers/mspi/Kconfig.ambiq" +source "drivers/mspi/Kconfig.dw" source "drivers/mspi/Kconfig.mspi_emul" endif # MSPI diff --git a/drivers/mspi/Kconfig.dw b/drivers/mspi/Kconfig.dw new file mode 100644 index 0000000000000..1ab82da0e8547 --- /dev/null +++ b/drivers/mspi/Kconfig.dw @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config MSPI_DW + bool "DesignWare SSI controller driver" + default y + depends on DT_HAS_SNPS_DESIGNWARE_SSI_ENABLED + select PINCTRL if $(dt_compat_any_has_prop,$(DT_COMPAT_SNPS_DESIGNWARE_SSI),pinctrl-0) + imply MSPI_XIP diff --git a/drivers/mspi/mspi_dw.c b/drivers/mspi/mspi_dw.c new file mode 100644 index 0000000000000..39cf463f4beec --- /dev/null +++ b/drivers/mspi/mspi_dw.c @@ -0,0 +1,1374 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT snps_designware_ssi + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mspi_dw.h" +#include "mspi_dw_vendor_specific.h" + +LOG_MODULE_REGISTER(mspi_dw, CONFIG_MSPI_LOG_LEVEL); + +#define DUMMY_BYTE 0xAA + +#if defined(CONFIG_MSPI_XIP) +struct xip_params { + uint32_t read_cmd; + uint32_t write_cmd; + uint16_t rx_dummy; + uint16_t tx_dummy; + uint8_t cmd_length; + uint8_t addr_length; + enum mspi_io_mode io_mode; +}; + +struct xip_ctrl { + uint32_t read; + uint32_t write; +}; +#endif + +struct mspi_dw_data { + const struct mspi_dev_id *dev_id; + uint32_t packets_done; + uint8_t *buf_pos; + const uint8_t *buf_end; + + uint32_t ctrlr0; + uint32_t spi_ctrlr0; + uint32_t baudr; + +#if defined(CONFIG_MSPI_XIP) + uint32_t xip_freq; + struct xip_params xip_params_stored; + struct xip_params xip_params_active; + uint16_t xip_enabled; + enum mspi_cpp_mode xip_cpp; +#endif + + uint16_t dummy_bytes; + uint8_t bytes_to_discard; + uint8_t bytes_per_frame_exp; + bool standard_spi; + bool suspended; + + struct k_sem finished; + /* For synchronization of API calls made from different contexts. */ + struct k_sem ctx_lock; + /* For locking of controller configuration. */ + struct k_sem cfg_lock; + struct mspi_xfer xfer; +}; + +struct mspi_dw_config { + DEVICE_MMIO_ROM; + void (*irq_config)(void); + uint32_t clock_frequency; +#if defined(CONFIG_PINCTRL) + const struct pinctrl_dev_config *pcfg; +#endif + const struct gpio_dt_spec *ce_gpios; + uint8_t ce_gpios_len; + uint8_t tx_fifo_depth_minus_1; + uint8_t tx_fifo_threshold; + uint8_t rx_fifo_threshold; + DECLARE_REG_ACCESS(); + bool sw_multi_periph; +}; + +/* Register access helpers. */ +#define DEFINE_MM_REG_RD_WR(reg, off) \ + DEFINE_MM_REG_RD(reg, off) \ + DEFINE_MM_REG_WR(reg, off) + +DEFINE_MM_REG_WR(ctrlr0, 0x00) +DEFINE_MM_REG_WR(ctrlr1, 0x04) +DEFINE_MM_REG_WR(ssienr, 0x08) +DEFINE_MM_REG_WR(ser, 0x10) +DEFINE_MM_REG_WR(baudr, 0x14) +DEFINE_MM_REG_RD_WR(txftlr, 0x18) +DEFINE_MM_REG_RD_WR(rxftlr, 0x1c) +DEFINE_MM_REG_RD(txflr, 0x20) +DEFINE_MM_REG_RD(rxflr, 0x24) +DEFINE_MM_REG_RD(sr, 0x28) +DEFINE_MM_REG_WR(imr, 0x2c) +DEFINE_MM_REG_RD(isr, 0x30) +DEFINE_MM_REG_RD_WR(dr, 0x60) +DEFINE_MM_REG_WR(spi_ctrlr0, 0xf4) + +#if defined(CONFIG_MSPI_XIP) +DEFINE_MM_REG_WR(xip_incr_inst, 0x100) +DEFINE_MM_REG_WR(xip_wrap_inst, 0x104) +DEFINE_MM_REG_WR(xip_ctrl, 0x108) +DEFINE_MM_REG_WR(xip_write_incr_inst, 0x140) +DEFINE_MM_REG_WR(xip_write_wrap_inst, 0x144) +DEFINE_MM_REG_WR(xip_write_ctrl, 0x148) +#endif + +static void tx_data(const struct device *dev, + const struct mspi_xfer_packet *packet) +{ + struct mspi_dw_data *dev_data = dev->data; + const struct mspi_dw_config *dev_config = dev->config; + const uint8_t *buf_pos = dev_data->buf_pos; + const uint8_t *buf_end = dev_data->buf_end; + /* When the function is called, it is known that at least one item + * can be written to the FIFO. The loop below writes to the FIFO + * the number of items that is known to fit and then updates that + * number basing on the actual FIFO level (because some data may get + * sent while the FIFO is written; especially for high frequencies + * this may often occur) and continues until the FIFO is filled up + * or the buffer end is reached. + */ + uint32_t room = 1; + uint8_t bytes_per_frame_exp = dev_data->bytes_per_frame_exp; + uint8_t tx_fifo_depth = dev_config->tx_fifo_depth_minus_1 + 1; + uint32_t data; + + do { + if (bytes_per_frame_exp == 2) { + data = sys_get_be32(buf_pos); + buf_pos += 4; + } else if (bytes_per_frame_exp == 1) { + data = sys_get_be16(buf_pos); + buf_pos += 2; + } else { + data = *buf_pos; + buf_pos += 1; + } + write_dr(dev, data); + + if (buf_pos >= buf_end) { + write_txftlr(dev, 0); + break; + } + + if (--room == 0) { + room = tx_fifo_depth + - FIELD_GET(TXFLR_TXTFL_MASK, read_txflr(dev)); + } + } while (room); + + dev_data->buf_pos = (uint8_t *)buf_pos; +} + +static bool make_rx_cycles(const struct device *dev) +{ + struct mspi_dw_data *dev_data = dev->data; + const struct mspi_dw_config *dev_config = dev->config; + uint16_t dummy_bytes = dev_data->dummy_bytes; + /* See tx_data(). */ + uint32_t room = 1; + uint8_t tx_fifo_depth = dev_config->tx_fifo_depth_minus_1 + 1; + + do { + write_dr(dev, DUMMY_BYTE); + + --dummy_bytes; + if (!dummy_bytes) { + dev_data->dummy_bytes = 0; + return true; + } + + if (--room == 0) { + room = tx_fifo_depth + - FIELD_GET(TXFLR_TXTFL_MASK, read_txflr(dev)); + } + } while (room); + + dev_data->dummy_bytes = dummy_bytes; + return false; +} + +static void read_rx_fifo(const struct device *dev, + const struct mspi_xfer_packet *packet) +{ + struct mspi_dw_data *dev_data = dev->data; + const struct mspi_dw_config *dev_config = dev->config; + uint8_t bytes_to_discard = dev_data->bytes_to_discard; + uint8_t *buf_pos = dev_data->buf_pos; + const uint8_t *buf_end = &packet->data_buf[packet->num_bytes]; + uint8_t bytes_per_frame_exp = dev_data->bytes_per_frame_exp; + /* See `room` in tx_data(). */ + uint32_t in_fifo = 1; + uint32_t remaining_frames; + + do { + uint32_t data = read_dr(dev); + + if (bytes_to_discard) { + --bytes_to_discard; + } else { + if (bytes_per_frame_exp == 2) { + sys_put_be32(data, buf_pos); + buf_pos += 4; + } else if (bytes_per_frame_exp == 1) { + sys_put_be16(data, buf_pos); + buf_pos += 2; + } else { + *buf_pos = (uint8_t)data; + buf_pos += 1; + } + + if (buf_pos >= buf_end) { + dev_data->bytes_to_discard = bytes_to_discard; + dev_data->buf_pos = buf_pos; + return; + } + } + + if (--in_fifo == 0) { + in_fifo = FIELD_GET(RXFLR_RXTFL_MASK, read_rxflr(dev)); + } + } while (in_fifo); + + remaining_frames = (bytes_to_discard + buf_end - buf_pos) + >> bytes_per_frame_exp; + if (remaining_frames - 1 < dev_config->rx_fifo_threshold) { + write_rxftlr(dev, remaining_frames - 1); + } + + dev_data->bytes_to_discard = bytes_to_discard; + dev_data->buf_pos = buf_pos; +} + +static void mspi_dw_isr(const struct device *dev) +{ + struct mspi_dw_data *dev_data = dev->data; + const struct mspi_xfer_packet *packet = + &dev_data->xfer.packets[dev_data->packets_done]; + uint32_t int_status = read_isr(dev); + + if (int_status & ISR_RXFIS_BIT) { + read_rx_fifo(dev, packet); + } + + if (dev_data->buf_pos >= dev_data->buf_end) { + write_imr(dev, 0); + /* It may happen that at this point the controller is still + * shifting out the last frame (the last interrupt occurs when + * the TX FIFO is empty). Wait if it signals that it is busy. + */ + while (read_sr(dev) & SR_BUSY_BIT) { + } + + k_sem_give(&dev_data->finished); + } else { + if (int_status & ISR_TXEIS_BIT) { + if (dev_data->dummy_bytes) { + if (make_rx_cycles(dev)) { + write_imr(dev, IMR_RXFIM_BIT); + } + } else { + tx_data(dev, packet); + } + } + } + + vendor_specific_irq_clear(dev); +} + +static int api_config(const struct mspi_dt_spec *spec) +{ + ARG_UNUSED(spec); + + return -ENOTSUP; +} + +static bool apply_io_mode(struct mspi_dw_data *dev_data, + enum mspi_io_mode io_mode) +{ + dev_data->ctrlr0 &= ~CTRLR0_SPI_FRF_MASK; + dev_data->spi_ctrlr0 &= ~SPI_CTRLR0_TRANS_TYPE_MASK; + + /* Frame format used for transferring data. */ + + if (io_mode == MSPI_IO_MODE_SINGLE) { + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_SPI_FRF_MASK, + CTRLR0_SPI_FRF_STANDARD); + dev_data->standard_spi = true; + return true; + } + + dev_data->standard_spi = false; + + switch (io_mode) { + case MSPI_IO_MODE_DUAL: + case MSPI_IO_MODE_DUAL_1_1_2: + case MSPI_IO_MODE_DUAL_1_2_2: + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_SPI_FRF_MASK, + CTRLR0_SPI_FRF_DUAL); + break; + case MSPI_IO_MODE_QUAD: + case MSPI_IO_MODE_QUAD_1_1_4: + case MSPI_IO_MODE_QUAD_1_4_4: + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_SPI_FRF_MASK, + CTRLR0_SPI_FRF_QUAD); + break; + case MSPI_IO_MODE_OCTAL: + case MSPI_IO_MODE_OCTAL_1_1_8: + case MSPI_IO_MODE_OCTAL_1_8_8: + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_SPI_FRF_MASK, + CTRLR0_SPI_FRF_OCTAL); + break; + default: + LOG_ERR("IO mode %d not supported", io_mode); + return false; + } + + /* Transfer format used for Address and Instruction: */ + + switch (io_mode) { + case MSPI_IO_MODE_DUAL_1_1_2: + case MSPI_IO_MODE_QUAD_1_1_4: + case MSPI_IO_MODE_OCTAL_1_1_8: + /* - both sent in Standard SPI mode */ + dev_data->spi_ctrlr0 |= FIELD_PREP(SPI_CTRLR0_TRANS_TYPE_MASK, + SPI_CTRLR0_TRANS_TYPE_TT0); + break; + case MSPI_IO_MODE_DUAL_1_2_2: + case MSPI_IO_MODE_QUAD_1_4_4: + case MSPI_IO_MODE_OCTAL_1_8_8: + /* - Instruction sent in Standard SPI mode, + * Address sent the same way as data + */ + dev_data->spi_ctrlr0 |= FIELD_PREP(SPI_CTRLR0_TRANS_TYPE_MASK, + SPI_CTRLR0_TRANS_TYPE_TT1); + break; + default: + /* - both sent the same way as data. */ + dev_data->spi_ctrlr0 |= FIELD_PREP(SPI_CTRLR0_TRANS_TYPE_MASK, + SPI_CTRLR0_TRANS_TYPE_TT2); + break; + } + + return true; +} + +static bool apply_cmd_length(struct mspi_dw_data *dev_data, uint32_t cmd_length) +{ + switch (cmd_length) { + case 0: + dev_data->spi_ctrlr0 |= FIELD_PREP(SPI_CTRLR0_INST_L_MASK, + SPI_CTRLR0_INST_L0); + break; + case 1: + dev_data->spi_ctrlr0 |= FIELD_PREP(SPI_CTRLR0_INST_L_MASK, + SPI_CTRLR0_INST_L8); + break; + case 2: + dev_data->spi_ctrlr0 |= FIELD_PREP(SPI_CTRLR0_INST_L_MASK, + SPI_CTRLR0_INST_L16); + break; + default: + LOG_ERR("Command length %d not supported", cmd_length); + return false; + } + + return true; +} + +static bool apply_addr_length(struct mspi_dw_data *dev_data, + uint32_t addr_length) +{ + dev_data->spi_ctrlr0 |= FIELD_PREP(SPI_CTRLR0_ADDR_L_MASK, + addr_length * 2); + + return true; +} + +#if defined(CONFIG_MSPI_XIP) +static bool apply_xip_io_mode(const struct mspi_dw_data *dev_data, + struct xip_ctrl *ctrl) +{ + enum mspi_io_mode io_mode = dev_data->xip_params_active.io_mode; + + /* Frame format used for transferring data. */ + + if (io_mode == MSPI_IO_MODE_SINGLE) { + LOG_ERR("XIP not available in single line mode"); + return false; + } + + switch (io_mode) { + case MSPI_IO_MODE_DUAL: + case MSPI_IO_MODE_DUAL_1_1_2: + case MSPI_IO_MODE_DUAL_1_2_2: + ctrl->read |= FIELD_PREP(XIP_CTRL_FRF_MASK, + XIP_CTRL_FRF_DUAL); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_FRF_MASK, + XIP_WRITE_CTRL_FRF_DUAL); + break; + case MSPI_IO_MODE_QUAD: + case MSPI_IO_MODE_QUAD_1_1_4: + case MSPI_IO_MODE_QUAD_1_4_4: + ctrl->read |= FIELD_PREP(XIP_CTRL_FRF_MASK, + XIP_CTRL_FRF_QUAD); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_FRF_MASK, + XIP_WRITE_CTRL_FRF_QUAD); + break; + case MSPI_IO_MODE_OCTAL: + case MSPI_IO_MODE_OCTAL_1_1_8: + case MSPI_IO_MODE_OCTAL_1_8_8: + ctrl->read |= FIELD_PREP(XIP_CTRL_FRF_MASK, + XIP_CTRL_FRF_OCTAL); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_FRF_MASK, + XIP_WRITE_CTRL_FRF_OCTAL); + break; + default: + LOG_ERR("IO mode %d not supported", io_mode); + return false; + } + + /* Transfer format used for Address and Instruction: */ + + switch (io_mode) { + case MSPI_IO_MODE_DUAL_1_1_2: + case MSPI_IO_MODE_QUAD_1_1_4: + case MSPI_IO_MODE_OCTAL_1_1_8: + /* - both sent in Standard SPI mode */ + ctrl->read |= FIELD_PREP(XIP_CTRL_TRANS_TYPE_MASK, + XIP_CTRL_TRANS_TYPE_TT0); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_TRANS_TYPE_MASK, + XIP_WRITE_CTRL_TRANS_TYPE_TT0); + break; + case MSPI_IO_MODE_DUAL_1_2_2: + case MSPI_IO_MODE_QUAD_1_4_4: + case MSPI_IO_MODE_OCTAL_1_8_8: + /* - Instruction sent in Standard SPI mode, + * Address sent the same way as data + */ + ctrl->read |= FIELD_PREP(XIP_CTRL_TRANS_TYPE_MASK, + XIP_CTRL_TRANS_TYPE_TT1); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_TRANS_TYPE_MASK, + XIP_WRITE_CTRL_TRANS_TYPE_TT1); + break; + default: + /* - both sent the same way as data. */ + ctrl->read |= FIELD_PREP(XIP_CTRL_TRANS_TYPE_MASK, + XIP_CTRL_TRANS_TYPE_TT2); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_TRANS_TYPE_MASK, + XIP_WRITE_CTRL_TRANS_TYPE_TT2); + break; + } + + return true; +} + +static bool apply_xip_cmd_length(const struct mspi_dw_data *dev_data, + struct xip_ctrl *ctrl) +{ + uint8_t cmd_length = dev_data->xip_params_active.cmd_length; + + switch (cmd_length) { + case 0: + ctrl->read |= FIELD_PREP(XIP_CTRL_INST_L_MASK, + XIP_CTRL_INST_L0); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_INST_L_MASK, + XIP_WRITE_CTRL_INST_L0); + break; + case 1: + ctrl->read |= XIP_CTRL_INST_EN_BIT + | FIELD_PREP(XIP_CTRL_INST_L_MASK, + XIP_CTRL_INST_L8); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_INST_L_MASK, + XIP_WRITE_CTRL_INST_L8); + break; + case 2: + ctrl->read |= XIP_CTRL_INST_EN_BIT + | FIELD_PREP(XIP_CTRL_INST_L_MASK, + XIP_CTRL_INST_L16); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_INST_L_MASK, + XIP_WRITE_CTRL_INST_L16); + break; + default: + LOG_ERR("Command length %d not supported", cmd_length); + return false; + } + + return true; +} + +static bool apply_xip_addr_length(const struct mspi_dw_data *dev_data, + struct xip_ctrl *ctrl) +{ + uint8_t addr_length = dev_data->xip_params_active.addr_length; + + ctrl->read |= FIELD_PREP(XIP_CTRL_ADDR_L_MASK, addr_length * 2); + ctrl->write |= FIELD_PREP(XIP_WRITE_CTRL_ADDR_L_MASK, addr_length * 2); + + return true; +} +#endif /* defined(CONFIG_MSPI_XIP) */ + +static int _api_dev_config(const struct device *dev, + const enum mspi_dev_cfg_mask param_mask, + const struct mspi_dev_cfg *cfg) +{ + const struct mspi_dw_config *dev_config = dev->config; + struct mspi_dw_data *dev_data = dev->data; + + if (param_mask & MSPI_DEVICE_CONFIG_ENDIAN) { + if (cfg->endian != MSPI_XFER_BIG_ENDIAN) { + LOG_ERR("Only big endian transfers are supported."); + return -ENOTSUP; + } + } + + if (param_mask & MSPI_DEVICE_CONFIG_CE_POL) { + if (cfg->ce_polarity != MSPI_CE_ACTIVE_LOW) { + LOG_ERR("Only active low CE is supported."); + return -ENOTSUP; + } + } + + if (param_mask & MSPI_DEVICE_CONFIG_MEM_BOUND) { + if (cfg->mem_boundary) { + LOG_ERR("Auto CE break is not supported."); + return -ENOTSUP; + } + } + + if (param_mask & MSPI_DEVICE_CONFIG_BREAK_TIME) { + if (cfg->time_to_break) { + LOG_ERR("Auto CE break is not supported."); + return -ENOTSUP; + } + } + + if (param_mask & MSPI_DEVICE_CONFIG_IO_MODE) { +#if defined(CONFIG_MSPI_XIP) + dev_data->xip_params_stored.io_mode = cfg->io_mode; +#endif + + if (!apply_io_mode(dev_data, cfg->io_mode)) { + return -EINVAL; + } + } + + if (param_mask & MSPI_DEVICE_CONFIG_CPP) { +#if defined(CONFIG_MSPI_XIP) + /* Make sure the new setting is compatible with the one used + * for XIP if it is enabled. + */ + if (!dev_data->xip_enabled) { + dev_data->xip_cpp = cfg->cpp; + } else if (dev_data->xip_cpp != cfg->cpp) { + LOG_ERR("Conflict with configuration used for XIP."); + return -EINVAL; + } +#endif + + dev_data->ctrlr0 &= ~(CTRLR0_SCPOL_BIT | CTRLR0_SCPH_BIT); + + switch (cfg->cpp) { + default: + case MSPI_CPP_MODE_0: + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_SCPOL_BIT, 0) | + FIELD_PREP(CTRLR0_SCPH_BIT, 0); + break; + case MSPI_CPP_MODE_1: + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_SCPOL_BIT, 0) | + FIELD_PREP(CTRLR0_SCPH_BIT, 1); + break; + case MSPI_CPP_MODE_2: + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_SCPOL_BIT, 1) | + FIELD_PREP(CTRLR0_SCPH_BIT, 0); + break; + case MSPI_CPP_MODE_3: + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_SCPOL_BIT, 1) | + FIELD_PREP(CTRLR0_SCPH_BIT, 1); + break; + } + } + + if (param_mask & MSPI_DEVICE_CONFIG_FREQUENCY) { + if (cfg->freq > dev_config->clock_frequency / 2 || + cfg->freq < dev_config->clock_frequency / 65534) { + LOG_ERR("Invalid frequency: %u, MIN: %u, MAX: %u", + cfg->freq, dev_config->clock_frequency / 65534, + dev_config->clock_frequency / 2); + return -EINVAL; + } + +#if defined(CONFIG_MSPI_XIP) + /* Make sure the new setting is compatible with the one used + * for XIP if it is enabled. + */ + if (!dev_data->xip_enabled) { + dev_data->xip_freq = cfg->freq; + } else if (dev_data->xip_freq != cfg->freq) { + LOG_ERR("Conflict with configuration used for XIP."); + return -EINVAL; + } +#endif + + dev_data->baudr = dev_config->clock_frequency / cfg->freq; + } + + if (param_mask & MSPI_DEVICE_CONFIG_DATA_RATE) { + /* TODO: add support for DDR */ + if (cfg->data_rate != MSPI_DATA_RATE_SINGLE) { + LOG_ERR("Only single data rate is supported."); + return -ENOTSUP; + } + } + + if (param_mask & MSPI_DEVICE_CONFIG_DQS) { + /* TODO: add support for DQS */ + if (cfg->dqs_enable) { + LOG_ERR("DQS line is not supported."); + return -ENOTSUP; + } + } + +#if defined(CONFIG_MSPI_XIP) + if (param_mask & MSPI_DEVICE_CONFIG_READ_CMD) { + dev_data->xip_params_stored.read_cmd = cfg->read_cmd; + } + if (param_mask & MSPI_DEVICE_CONFIG_WRITE_CMD) { + dev_data->xip_params_stored.write_cmd = cfg->write_cmd; + } + if (param_mask & MSPI_DEVICE_CONFIG_RX_DUMMY) { + dev_data->xip_params_stored.rx_dummy = cfg->rx_dummy; + } + if (param_mask & MSPI_DEVICE_CONFIG_TX_DUMMY) { + dev_data->xip_params_stored.tx_dummy = cfg->tx_dummy; + } + if (param_mask & MSPI_DEVICE_CONFIG_CMD_LEN) { + dev_data->xip_params_stored.cmd_length = cfg->cmd_length; + } + if (param_mask & MSPI_DEVICE_CONFIG_ADDR_LEN) { + dev_data->xip_params_stored.addr_length = cfg->addr_length; + } +#endif + + /* Always use Motorola SPI frame format. */ + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_FRF_MASK, CTRLR0_FRF_SPI); + /* Enable clock stretching. */ + dev_data->spi_ctrlr0 |= SPI_CTRLR0_CLK_STRETCH_EN_BIT; + + return 0; +} + +static int api_dev_config(const struct device *dev, + const struct mspi_dev_id *dev_id, + const enum mspi_dev_cfg_mask param_mask, + const struct mspi_dev_cfg *cfg) +{ + const struct mspi_dw_config *dev_config = dev->config; + struct mspi_dw_data *dev_data = dev->data; + int rc; + + if (dev_id != dev_data->dev_id) { + rc = k_sem_take(&dev_data->cfg_lock, + K_MSEC(CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE)); + if (rc < 0) { + LOG_ERR("Failed to switch controller to device"); + return -EBUSY; + } + + dev_data->dev_id = dev_id; + } + + if (param_mask == MSPI_DEVICE_CONFIG_NONE && + !dev_config->sw_multi_periph) { + return 0; + } + + (void)k_sem_take(&dev_data->ctx_lock, K_FOREVER); + + rc = _api_dev_config(dev, param_mask, cfg); + + k_sem_give(&dev_data->ctx_lock); + + if (rc < 0) { + dev_data->dev_id = NULL; + k_sem_give(&dev_data->cfg_lock); + } + + return rc; +} + +static int api_get_channel_status(const struct device *dev, uint8_t ch) +{ + ARG_UNUSED(ch); + + struct mspi_dw_data *dev_data = dev->data; + + (void)k_sem_take(&dev_data->ctx_lock, K_FOREVER); + + dev_data->dev_id = NULL; + k_sem_give(&dev_data->cfg_lock); + + k_sem_give(&dev_data->ctx_lock); + + return 0; +} + +static void tx_control_field(const struct device *dev, + uint32_t field, uint8_t len) +{ + uint8_t shift = 8 * len; + + do { + shift -= 8; + write_dr(dev, field >> shift); + } while (shift); +} + +static int start_next_packet(const struct device *dev, k_timeout_t timeout) +{ + const struct mspi_dw_config *dev_config = dev->config; + struct mspi_dw_data *dev_data = dev->data; + const struct mspi_xfer_packet *packet = + &dev_data->xfer.packets[dev_data->packets_done]; + bool xip_enabled = COND_CODE_1(CONFIG_MSPI_XIP, + (dev_data->xip_enabled != 0), + (false)); + unsigned int key; + uint8_t tx_fifo_threshold; + uint32_t packet_frames; + uint32_t imr; + int rc = 0; + + if (packet->num_bytes == 0 && + dev_data->xfer.cmd_length == 0 && + dev_data->xfer.addr_length == 0) { + return 0; + } + + dev_data->dummy_bytes = 0; + + dev_data->ctrlr0 &= ~CTRLR0_TMOD_MASK + & ~CTRLR0_DFS_MASK; + + dev_data->spi_ctrlr0 &= ~SPI_CTRLR0_WAIT_CYCLES_MASK; + + if (dev_data->standard_spi && + (dev_data->xfer.cmd_length != 0 || + dev_data->xfer.addr_length != 0)) { + dev_data->bytes_per_frame_exp = 0; + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_DFS_MASK, 7); + } else { + if ((packet->num_bytes % 4) == 0) { + dev_data->bytes_per_frame_exp = 2; + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_DFS_MASK, 31); + } else if ((packet->num_bytes % 2) == 0) { + dev_data->bytes_per_frame_exp = 1; + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_DFS_MASK, 15); + } else { + dev_data->bytes_per_frame_exp = 0; + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_DFS_MASK, 7); + } + } + + packet_frames = packet->num_bytes >> dev_data->bytes_per_frame_exp; + + if (packet_frames > UINT16_MAX + 1) { + LOG_ERR("Packet length (%u) exceeds supported maximum", + packet->num_bytes); + return -EINVAL; + } + + if (packet->dir == MSPI_TX || packet->num_bytes == 0) { + imr = IMR_TXEIM_BIT; + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_TMOD_MASK, + CTRLR0_TMOD_TX); + dev_data->spi_ctrlr0 |= FIELD_PREP(SPI_CTRLR0_WAIT_CYCLES_MASK, + dev_data->xfer.tx_dummy); + + write_rxftlr(dev, 0); + tx_fifo_threshold = dev_config->tx_fifo_threshold; + } else { + uint32_t tmod; + uint8_t rx_fifo_threshold; + + /* In Standard SPI Mode, the controller does not support + * sending the command and address fields separately, they + * need to be sent as data; hence, for RX packets with these + * fields, the TX/RX transfer mode needs to be used and + * consequently, dummy bytes need to be transmitted so that + * clock cycles for the RX part are provided (the controller + * does not do it automatically in the TX/RX mode). + */ + if (dev_data->standard_spi && + (dev_data->xfer.cmd_length != 0 || + dev_data->xfer.addr_length != 0)) { + uint32_t rx_total_bytes; + + dev_data->bytes_to_discard = dev_data->xfer.cmd_length + + dev_data->xfer.addr_length; + rx_total_bytes = dev_data->bytes_to_discard + + packet->num_bytes; + + dev_data->dummy_bytes = packet->num_bytes; + + imr = IMR_TXEIM_BIT | IMR_RXFIM_BIT; + tmod = CTRLR0_TMOD_TX_RX; + tx_fifo_threshold = dev_config->tx_fifo_threshold; + /* For standard SPI, only 1-byte frames are used. */ + rx_fifo_threshold = MIN(rx_total_bytes - 1, + dev_config->rx_fifo_threshold); + } else { + imr = IMR_RXFIM_BIT; + tmod = CTRLR0_TMOD_RX; + tx_fifo_threshold = 0; + rx_fifo_threshold = MIN(packet_frames - 1, + dev_config->rx_fifo_threshold); + } + + dev_data->ctrlr0 |= FIELD_PREP(CTRLR0_TMOD_MASK, tmod); + dev_data->spi_ctrlr0 |= FIELD_PREP(SPI_CTRLR0_WAIT_CYCLES_MASK, + dev_data->xfer.rx_dummy); + + write_rxftlr(dev, FIELD_PREP(RXFTLR_RFT_MASK, + rx_fifo_threshold)); + } + + if (dev_data->dev_id->ce.port) { + rc = gpio_pin_set_dt(&dev_data->dev_id->ce, 1); + if (rc < 0) { + LOG_ERR("Failed to activate CE line (%d)", rc); + return rc; + } + } + + if (xip_enabled) { + key = irq_lock(); + write_ssienr(dev, 0); + } + + /* These registers cannot be written when the controller is enabled, + * that's why it is temporarily disabled above; with locked interrupts, + * to prevent potential XIP transfers during that period. + */ + write_ctrlr0(dev, dev_data->ctrlr0); + write_ctrlr1(dev, packet_frames > 0 + ? FIELD_PREP(CTRLR1_NDF_MASK, packet_frames - 1) + : 0); + write_spi_ctrlr0(dev, dev_data->spi_ctrlr0); + write_baudr(dev, dev_data->baudr); + write_ser(dev, BIT(dev_data->dev_id->dev_idx)); + + if (xip_enabled) { + write_ssienr(dev, SSIENR_SSIC_EN_BIT); + irq_unlock(key); + } + + dev_data->buf_pos = packet->data_buf; + dev_data->buf_end = &packet->data_buf[packet->num_bytes]; + + if ((imr & IMR_TXEIM_BIT) && dev_data->buf_pos < dev_data->buf_end) { + uint32_t start_level = tx_fifo_threshold; + + if (dev_data->dummy_bytes) { + uint32_t tx_total = dev_data->bytes_to_discard + + dev_data->dummy_bytes; + + if (start_level > tx_total - 1) { + start_level = tx_total - 1; + } + } + + write_txftlr(dev, + FIELD_PREP(TXFTLR_TXFTHR_MASK, start_level) | + FIELD_PREP(TXFTLR_TFT_MASK, tx_fifo_threshold)); + } else { + write_txftlr(dev, 0); + } + + /* Ensure that there will be no interrupt from the controller yet. */ + write_imr(dev, 0); + /* Enable the controller. This must be done before DR is written. */ + write_ssienr(dev, SSIENR_SSIC_EN_BIT); + + if (dev_data->standard_spi) { + if (dev_data->xfer.cmd_length) { + tx_control_field(dev, packet->cmd, + dev_data->xfer.cmd_length); + } + + if (dev_data->xfer.addr_length) { + tx_control_field(dev, packet->address, + dev_data->xfer.addr_length); + } + } else { + if (dev_data->xfer.cmd_length) { + write_dr(dev, packet->cmd); + } + + if (dev_data->xfer.addr_length) { + write_dr(dev, packet->address); + } + } + + if (dev_data->dummy_bytes) { + if (make_rx_cycles(dev)) { + imr = IMR_RXFIM_BIT; + } + } else if (packet->dir == MSPI_TX && packet->num_bytes) { + tx_data(dev, packet); + } + + /* Enable interrupts now and wait until the packet is done. */ + write_imr(dev, imr); + + rc = k_sem_take(&dev_data->finished, timeout); + if (rc < 0) { + rc = -ETIMEDOUT; + } + + /* Disable the controller. This will immediately halt the transfer + * if it hasn't finished yet. + */ + if (xip_enabled) { + /* If XIP is enabled, the controller must be kept enabled, + * so disable it only momentarily if there's a need to halt + * a transfer that has timeout out. + */ + if (rc == -ETIMEDOUT) { + key = irq_lock(); + + write_ssienr(dev, 0); + write_ssienr(dev, SSIENR_SSIC_EN_BIT); + + irq_unlock(key); + } + } else { + write_ssienr(dev, 0); + } + + if (dev_data->dev_id->ce.port) { + int rc2; + + /* Do not use `rc` to not overwrite potential timeout error. */ + rc2 = gpio_pin_set_dt(&dev_data->dev_id->ce, 0); + if (rc2 < 0) { + LOG_ERR("Failed to deactivate CE line (%d)", rc2); + return rc2; + } + } + + return rc; +} + +static int _api_transceive(const struct device *dev, + const struct mspi_xfer *req) +{ + struct mspi_dw_data *dev_data = dev->data; + int rc; + + dev_data->spi_ctrlr0 &= ~SPI_CTRLR0_WAIT_CYCLES_MASK + & ~SPI_CTRLR0_INST_L_MASK + & ~SPI_CTRLR0_ADDR_L_MASK; + + if (!apply_cmd_length(dev_data, req->cmd_length) || + !apply_addr_length(dev_data, req->addr_length)) { + return -EINVAL; + } + + if (dev_data->standard_spi && + (req->rx_dummy != 0 || req->tx_dummy != 0)) { + LOG_ERR("Dummy cycles unsupported in single line mode"); + return -EINVAL; + } else if (req->rx_dummy > SPI_CTRLR0_WAIT_CYCLES_MAX || + req->tx_dummy > SPI_CTRLR0_WAIT_CYCLES_MAX) { + LOG_ERR("Unsupported RX (%u) or TX (%u) dummy cycles", + req->rx_dummy, req->tx_dummy); + return -EINVAL; + } + + dev_data->xfer = *req; + + for (dev_data->packets_done = 0; + dev_data->packets_done < dev_data->xfer.num_packet; + dev_data->packets_done++) { + rc = start_next_packet(dev, K_MSEC(dev_data->xfer.timeout)); + if (rc < 0) { + return rc; + } + } + + return 0; +} + +static int api_transceive(const struct device *dev, + const struct mspi_dev_id *dev_id, + const struct mspi_xfer *req) +{ + struct mspi_dw_data *dev_data = dev->data; + int rc, rc2; + + if (dev_id != dev_data->dev_id) { + LOG_ERR("Controller is not configured for this device"); + return -EINVAL; + } + + /* TODO: add support for asynchronous transfers */ + if (req->async) { + LOG_ERR("Asynchronous transfers are not supported"); + return -ENOTSUP; + } + + rc = pm_device_runtime_get(dev); + if (rc < 0) { + LOG_ERR("pm_device_runtime_get() failed: %d", rc); + return rc; + } + + (void)k_sem_take(&dev_data->ctx_lock, K_FOREVER); + + if (dev_data->suspended) { + rc = -EFAULT; + } else { + rc = _api_transceive(dev, req); + } + + k_sem_give(&dev_data->ctx_lock); + + rc2 = pm_device_runtime_put(dev); + if (rc2 < 0) { + LOG_ERR("pm_device_runtime_put() failed: %d", rc2); + rc = (rc < 0 ? rc : rc2); + } + + return rc; +} + +#if defined(CONFIG_MSPI_XIP) +static int _api_xip_config(const struct device *dev, + const struct mspi_dev_id *dev_id, + const struct mspi_xip_cfg *cfg) +{ + struct mspi_dw_data *dev_data = dev->data; + int rc; + + if (!cfg->enable) { + rc = vendor_specific_xip_disable(dev, dev_id, cfg); + if (rc < 0) { + return rc; + } + + dev_data->xip_enabled &= ~BIT(dev_id->dev_idx); + + if (!dev_data->xip_enabled) { + write_ssienr(dev, 0); + + /* Since XIP is disabled, it is okay for the controller + * to be suspended. + */ + rc = pm_device_runtime_put(dev); + if (rc < 0) { + LOG_ERR("pm_device_runtime_put() failed: %d", rc); + return rc; + } + } + + return 0; + } + + if (!dev_data->xip_enabled) { + struct xip_params *params = &dev_data->xip_params_active; + struct xip_ctrl ctrl = {0}; + + *params = dev_data->xip_params_stored; + + if (!apply_xip_io_mode(dev_data, &ctrl) || + !apply_xip_cmd_length(dev_data, &ctrl) || + !apply_xip_addr_length(dev_data, &ctrl)) { + return -EINVAL; + } + + if (params->rx_dummy > SPI_CTRLR0_WAIT_CYCLES_MAX || + params->tx_dummy > SPI_CTRLR0_WAIT_CYCLES_MAX) { + LOG_ERR("Unsupported RX (%u) or TX (%u) dummy cycles", + params->rx_dummy, params->tx_dummy); + return -EINVAL; + } + + /* Increase usage count additionally to prevent the controller + * from being suspended as long as XIP is active. + */ + rc = pm_device_runtime_get(dev); + if (rc < 0) { + LOG_ERR("pm_device_runtime_get() failed: %d", rc); + return rc; + } + + ctrl.read |= FIELD_PREP(XIP_CTRL_WAIT_CYCLES_MASK, + params->rx_dummy); + ctrl.write |= FIELD_PREP(XIP_WRITE_CTRL_WAIT_CYCLES_MASK, + params->tx_dummy); + + /* Make sure the baud rate and serial clock phase/polarity + * registers are configured properly. They may not be if + * non-XIP transfers have not been performed yet. + */ + write_ctrlr0(dev, dev_data->ctrlr0); + write_baudr(dev, dev_data->baudr); + + write_xip_incr_inst(dev, params->read_cmd); + write_xip_wrap_inst(dev, params->read_cmd); + write_xip_ctrl(dev, ctrl.read); + write_xip_write_incr_inst(dev, params->write_cmd); + write_xip_write_wrap_inst(dev, params->write_cmd); + write_xip_write_ctrl(dev, ctrl.write); + } else if (dev_data->xip_params_active.read_cmd != + dev_data->xip_params_stored.read_cmd || + dev_data->xip_params_active.write_cmd != + dev_data->xip_params_stored.write_cmd || + dev_data->xip_params_active.cmd_length != + dev_data->xip_params_stored.cmd_length || + dev_data->xip_params_active.addr_length != + dev_data->xip_params_stored.addr_length || + dev_data->xip_params_active.rx_dummy != + dev_data->xip_params_stored.rx_dummy || + dev_data->xip_params_active.tx_dummy != + dev_data->xip_params_stored.tx_dummy) { + LOG_ERR("Conflict with configuration already used for XIP."); + return -EINVAL; + } + + rc = vendor_specific_xip_enable(dev, dev_id, cfg); + if (rc < 0) { + return rc; + } + + write_ssienr(dev, SSIENR_SSIC_EN_BIT); + + dev_data->xip_enabled |= BIT(dev_id->dev_idx); + + return 0; +} + +static int api_xip_config(const struct device *dev, + const struct mspi_dev_id *dev_id, + const struct mspi_xip_cfg *cfg) +{ + struct mspi_dw_data *dev_data = dev->data; + int rc, rc2; + + if (cfg->enable && dev_id != dev_data->dev_id) { + LOG_ERR("Controller is not configured for this device"); + return -EINVAL; + } + + rc = pm_device_runtime_get(dev); + if (rc < 0) { + LOG_ERR("pm_device_runtime_get() failed: %d", rc); + return rc; + } + + (void)k_sem_take(&dev_data->ctx_lock, K_FOREVER); + + if (dev_data->suspended) { + rc = -EFAULT; + } else { + rc = _api_xip_config(dev, dev_id, cfg); + } + + k_sem_give(&dev_data->ctx_lock); + + rc2 = pm_device_runtime_put(dev); + if (rc2 < 0) { + LOG_ERR("pm_device_runtime_put() failed: %d", rc2); + rc = (rc < 0 ? rc : rc2); + } + + return rc; +} +#endif /* defined(CONFIG_MSPI_XIP) */ + +static int dev_pm_action_cb(const struct device *dev, + enum pm_device_action action) +{ + struct mspi_dw_data *dev_data = dev->data; + + if (action == PM_DEVICE_ACTION_RESUME) { +#if defined(CONFIG_PINCTRL) + const struct mspi_dw_config *dev_config = dev->config; + int rc = pinctrl_apply_state(dev_config->pcfg, + PINCTRL_STATE_DEFAULT); + + if (rc < 0) { + LOG_ERR("Cannot apply default pins state (%d)", rc); + return rc; + } +#endif + vendor_specific_resume(dev); + + dev_data->suspended = false; + + return 0; + } + + if (IS_ENABLED(CONFIG_PM_DEVICE) && + action == PM_DEVICE_ACTION_SUSPEND) { + bool xip_enabled = COND_CODE_1(CONFIG_MSPI_XIP, + (dev_data->xip_enabled != 0), + (false)); + +#if defined(CONFIG_PINCTRL) + const struct mspi_dw_config *dev_config = dev->config; + int rc = pinctrl_apply_state(dev_config->pcfg, + PINCTRL_STATE_SLEEP); + + if (rc < 0) { + LOG_ERR("Cannot apply sleep pins state (%d)", rc); + return rc; + } +#endif + if (xip_enabled || + k_sem_take(&dev_data->ctx_lock, K_NO_WAIT) != 0) { + LOG_ERR("Controller in use, cannot be suspended"); + return -EBUSY; + } + + dev_data->suspended = true; + + vendor_specific_suspend(dev); + + k_sem_give(&dev_data->ctx_lock); + + return 0; + } + + return -ENOTSUP; +} + +static int dev_init(const struct device *dev) +{ + struct mspi_dw_data *dev_data = dev->data; + const struct mspi_dw_config *dev_config = dev->config; + const struct gpio_dt_spec *ce_gpio; + int rc; + + DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); + + vendor_specific_init(dev); + + dev_config->irq_config(); + + k_sem_init(&dev_data->finished, 0, 1); + k_sem_init(&dev_data->cfg_lock, 1, 1); + k_sem_init(&dev_data->ctx_lock, 1, 1); + + for (ce_gpio = dev_config->ce_gpios; + ce_gpio < &dev_config->ce_gpios[dev_config->ce_gpios_len]; + ce_gpio++) { + if (!device_is_ready(ce_gpio->port)) { + LOG_ERR("CE GPIO port %s is not ready", + ce_gpio->port->name); + return -ENODEV; + } + + rc = gpio_pin_configure_dt(ce_gpio, GPIO_OUTPUT_INACTIVE); + if (rc < 0) { + return rc; + } + } + +#if defined(CONFIG_PINCTRL) + if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) { + rc = pinctrl_apply_state(dev_config->pcfg, PINCTRL_STATE_SLEEP); + if (rc < 0) { + LOG_ERR("Cannot apply sleep pins state (%d)", rc); + return rc; + } + } +#endif + + return pm_device_driver_init(dev, dev_pm_action_cb); +} + +static DEVICE_API(mspi, drv_api) = { + .config = api_config, + .dev_config = api_dev_config, + .get_channel_status = api_get_channel_status, + .transceive = api_transceive, +#if defined(CONFIG_MSPI_XIP) + .xip_config = api_xip_config, +#endif +}; + +#define MSPI_DW_INST_IRQ(idx, inst) \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, idx, irq), \ + DT_INST_IRQ_BY_IDX(inst, idx, priority), \ + mspi_dw_isr, DEVICE_DT_INST_GET(inst), 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(inst, idx, irq)) + +#define MSPI_DW_MMIO_ROM_INIT(node_id) \ + COND_CODE_1(DT_REG_HAS_NAME(node_id, core), \ + (Z_DEVICE_MMIO_NAMED_ROM_INITIALIZER(core, node_id)), \ + (DEVICE_MMIO_ROM_INIT(node_id))) + +#define MSPI_DW_CLOCK_FREQUENCY(inst) \ + COND_CODE_1(DT_NODE_HAS_PROP(DT_INST_PHANDLE(inst, clocks), \ + clock_frequency), \ + (DT_INST_PROP_BY_PHANDLE(inst, clocks, \ + clock_frequency)), \ + (DT_INST_PROP(inst, clock_frequency))) + +#define MSPI_DW_DT_INST_PROP(inst, prop) .prop = DT_INST_PROP(inst, prop) + +#define FOREACH_CE_GPIOS_ELEM(inst) \ + DT_INST_FOREACH_PROP_ELEM_SEP(inst, ce_gpios, \ + GPIO_DT_SPEC_GET_BY_IDX, (,)) +#define MSPI_DW_CE_GPIOS(inst) \ + .ce_gpios = (const struct gpio_dt_spec []) \ + { FOREACH_CE_GPIOS_ELEM(inst) }, \ + .ce_gpios_len = DT_INST_PROP_LEN(inst, ce_gpios) + +#define TX_FIFO_DEPTH(inst) DT_INST_PROP(inst, fifo_depth) +#define RX_FIFO_DEPTH(inst) DT_INST_PROP_OR(inst, rx_fifo_depth, \ + TX_FIFO_DEPTH(inst)) +#define MSPI_DW_FIFO_PROPS(inst) \ + .tx_fifo_depth_minus_1 = TX_FIFO_DEPTH(inst) - 1, \ + .tx_fifo_threshold = \ + DT_INST_PROP_OR(inst, tx_fifo_threshold, \ + 7 * TX_FIFO_DEPTH(inst) / 8 - 1), \ + .rx_fifo_threshold = \ + DT_INST_PROP_OR(inst, rx_fifo_threshold, \ + 1 * RX_FIFO_DEPTH(inst) / 8 - 1) + +#define MSPI_DW_INST(inst) \ + PM_DEVICE_DT_INST_DEFINE(inst, dev_pm_action_cb); \ + IF_ENABLED(CONFIG_PINCTRL, (PINCTRL_DT_INST_DEFINE(inst);)) \ + static void irq_config##inst(void) \ + { \ + LISTIFY(DT_INST_NUM_IRQS(inst), \ + MSPI_DW_INST_IRQ, (;), inst); \ + } \ + static struct mspi_dw_data dev##inst##_data; \ + static const struct mspi_dw_config dev##inst##_config = { \ + MSPI_DW_MMIO_ROM_INIT(DT_DRV_INST(inst)), \ + .irq_config = irq_config##inst, \ + .clock_frequency = MSPI_DW_CLOCK_FREQUENCY(inst), \ + IF_ENABLED(CONFIG_PINCTRL, \ + (.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),)) \ + IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, ce_gpios), \ + (MSPI_DW_CE_GPIOS(inst),)) \ + MSPI_DW_FIFO_PROPS(inst), \ + DEFINE_REG_ACCESS(inst) \ + .sw_multi_periph = \ + DT_INST_PROP(inst, software_multiperipheral), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, \ + dev_init, PM_DEVICE_DT_INST_GET(inst), \ + &dev##inst##_data, &dev##inst##_config, \ + POST_KERNEL, CONFIG_MSPI_INIT_PRIORITY, \ + &drv_api); + +DT_INST_FOREACH_STATUS_OKAY(MSPI_DW_INST) diff --git a/drivers/mspi/mspi_dw.h b/drivers/mspi/mspi_dw.h new file mode 100644 index 0000000000000..bd81d061e09ac --- /dev/null +++ b/drivers/mspi/mspi_dw.h @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This header is part of mspi_dw.c extracted only for clarity. + * It is not supposed to be included by any file other than mspi_dw.c. + */ + +/* CTRLR0 - Control Register 0 */ +#define CTRLR0_SPI_FRF_MASK GENMASK(23, 22) +#define CTRLR0_SPI_FRF_STANDARD 0UL +#define CTRLR0_SPI_FRF_DUAL 1UL +#define CTRLR0_SPI_FRF_QUAD 2UL +#define CTRLR0_SPI_FRF_OCTAL 3UL +#define CTRLR0_TMOD_MASK GENMASK(11, 10) +#define CTRLR0_TMOD_TX_RX 0UL +#define CTRLR0_TMOD_TX 1UL +#define CTRLR0_TMOD_RX 2UL +#define CTRLR0_TMOD_EEPROM 3UL +#define CTRLR0_SCPOL_BIT BIT(9) +#define CTRLR0_SCPH_BIT BIT(8) +#define CTRLR0_FRF_MASK GENMASK(7, 6) +#define CTRLR0_FRF_SPI 0UL +#define CTRLR0_FRF_SSP 1UL +#define CTRLR0_FRF_MICROWIRE 2UL +#define CTRLR0_DFS_MASK GENMASK(4, 0) + +/* CTRLR1- Control Register 1 */ +#define CTRLR1_NDF_MASK GENMASK(15, 0) + +/* SSIENR - SSI Enable Register */ +#define SSIENR_SSIC_EN_BIT BIT(0) + +/* TXFTLR - Transmit FIFO Threshold Level */ +#define TXFTLR_TXFTHR_MASK GENMASK(23, 16) +#define TXFTLR_TFT_MASK GENMASK(7, 0) + +/* RXFTLR - Receive FIFO Threshold Level */ +#define RXFTLR_RFT_MASK GENMASK(7, 0) + +/* TXFLR - Transmit FIFO Level Register */ +#define TXFLR_TXTFL_MASK GENMASK(7, 0) + +/* RXFLR - Receive FIFO Level Register */ +#define RXFLR_RXTFL_MASK GENMASK(7, 0) + +/* SR - Status Register */ +#define SR_BUSY_BIT BIT(0) + +/* IMR - Interrupt Mask Register */ +#define IMR_TXEIM_BIT BIT(0) +#define IMR_TXOIM_BIT BIT(1) +#define IMR_RXUIM_BIT BIT(2) +#define IMR_RXOIM_BIT BIT(3) +#define IMR_RXFIM_BIT BIT(4) +#define IMR_MSTIM_BIT BIT(5) + +/* ISR - Interrupt Status Register */ +#define ISR_TXEIS_BIT BIT(0) +#define ISR_TXOIS_BIT BIT(1) +#define ISR_RXUIS_BIT BIT(2) +#define ISR_RXOIS_BIT BIT(3) +#define ISR_RXFIS_BIT BIT(4) +#define ISR_MSTIS_BIT BIT(5) + +/* SPI_CTRLR0 - SPI Control Register */ +#define SPI_CTRLR0_CLK_STRETCH_EN_BIT BIT(30) +#define SPI_CTRLR0_XIP_PREFETCH_EN_BIT BIT(29) +#define SPI_CTRLR0_XIP_MBL_BIT BIT(26) +#define SPI_CTRLR0_SPI_RXDS_SIG_EN_BIT BIT(25) +#define SPI_CTRLR0_SPI_DM_EN_BIT BIT(24) +#define SPI_CTRLR0_RXDS_VL_EN_BIT BIT(23) +#define SPI_CTRLR0_SSIC_XIP_CONT_XFER_EN_BIT BIT(21) +#define SPI_CTRLR0_XIP_INST_EN_BIT BIT(20) +#define SPI_CTRLR0_XIP_DFS_HC_BIT BIT(19) +#define SPI_CTRLR0_SPI_RXDS_EN_BIT BIT(18) +#define SPI_CTRLR0_INST_DDR_EN_BIT BIT(17) +#define SPI_CTRLR0_SPI_DDR_EN_BIT BIT(16) +#define SPI_CTRLR0_WAIT_CYCLES_MASK GENMASK(15, 11) +#define SPI_CTRLR0_WAIT_CYCLES_MAX BIT_MASK(5) +#define SPI_CTRLR0_INST_L_MASK GENMASK(9, 8) +#define SPI_CTRLR0_INST_L0 0UL +#define SPI_CTRLR0_INST_L4 1UL +#define SPI_CTRLR0_INST_L8 2UL +#define SPI_CTRLR0_INST_L16 3UL +#define SPI_CTRLR0_XIP_MD_BIT_EN_BIT BIT(7) +#define SPI_CTRLR0_ADDR_L_MASK GENMASK(5, 2) +#define SPI_CTRLR0_TRANS_TYPE_MASK GENMASK(1, 0) +#define SPI_CTRLR0_TRANS_TYPE_TT0 0UL +#define SPI_CTRLR0_TRANS_TYPE_TT1 1UL +#define SPI_CTRLR0_TRANS_TYPE_TT2 2UL +#define SPI_CTRLR0_TRANS_TYPE_TT3 3UL + +/* XIP_CTRL - XIP Control Register */ +#define XIP_CTRL_XIP_PREFETCH_EN_BIT BIT(28) +#define XIP_CTRL_XIP_MBL_MASK GENMASK(27, 26) +#define XIP_CTRL_XIP_MBL_2 0UL +#define XIP_CTRL_XIP_MBL_4 1UL +#define XIP_CTRL_XIP_MBL_8 2UL +#define XIP_CTRL_XIP_MBL_16 3UL +#define XIP_CTRL_RXDS_SIG_EN_BIT BIT(25) +#define XIP_CTRL_XIP_HYBERBUS_EN_BIT BIT(24) +#define XIP_CTRL_CONT_XFER_EN_BIT BIT(23) +#define XIP_CTRL_INST_EN_BIT BIT(22) +#define XIP_CTRL_RXDS_EN_BIT BIT(21) +#define XIP_CTRL_INST_DDR_EN_BIT BIT(20) +#define XIP_CTRL_DDR_EN_BIT BIT(19) +#define XIP_CTRL_DFS_HC_BIT BIT(18) +#define XIP_CTRL_WAIT_CYCLES_MASK GENMASK(17, 13) +#define XIP_CTRL_WAIT_CYCLES_MAX BIT_MASK(5) +#define XIP_CTRL_MD_BITS_EN_BIT BIT(12) +#define XIP_CTRL_INST_L_MASK GENMASK(10, 9) +#define XIP_CTRL_INST_L0 0UL +#define XIP_CTRL_INST_L4 1UL +#define XIP_CTRL_INST_L8 2UL +#define XIP_CTRL_INST_L16 3UL +#define XIP_CTRL_ADDR_L_MASK GENMASK(7, 4) +#define XIP_CTRL_TRANS_TYPE_MASK GENMASK(3, 2) +#define XIP_CTRL_TRANS_TYPE_TT0 0UL +#define XIP_CTRL_TRANS_TYPE_TT1 1UL +#define XIP_CTRL_TRANS_TYPE_TT2 2UL +#define XIP_CTRL_FRF_MASK GENMASK(1, 0) +#define XIP_CTRL_FRF_DUAL 1UL +#define XIP_CTRL_FRF_QUAD 2UL +#define XIP_CTRL_FRF_OCTAL 3UL + +/* XIP_CTRL - XIP Control Register */ +#define XIP_CTRL_XIP_PREFETCH_EN_BIT BIT(28) +#define XIP_CTRL_XIP_MBL_MASK GENMASK(27, 26) +#define XIP_CTRL_XIP_MBL_2 0UL +#define XIP_CTRL_XIP_MBL_4 1UL +#define XIP_CTRL_XIP_MBL_8 2UL +#define XIP_CTRL_XIP_MBL_16 3UL +#define XIP_CTRL_XIP_HYBERBUS_EN_BIT BIT(24) +#define XIP_CTRL_CONT_XFER_EN_BIT BIT(23) +#define XIP_CTRL_INST_EN_BIT BIT(22) +#define XIP_CTRL_RXDS_EN_BIT BIT(21) +#define XIP_CTRL_INST_DDR_EN_BIT BIT(20) +#define XIP_CTRL_DDR_EN_BIT BIT(19) +#define XIP_CTRL_DFS_HC_BIT BIT(18) + +/* XIP_WRITE_CTRL - XIP Write Control Register */ +#define XIP_WRITE_CTRL_WAIT_CYCLES_MASK GENMASK(20, 16) +#define XIP_WRITE_CTRL_WAIT_CYCLES_MAX BIT_MASK(5) +#define XIP_WRITE_CTRL_RXDS_SIG_EN_BIT BIT(13) +#define XIP_WRITE_CTRL_HYBERBUS_EN_BIT BIT(12) +#define XIP_WRITE_CTRL_INST_DDR_EN_BIT BIT(11) +#define XIP_WRITE_CTRL_SPI_DDR_EN_BIT BIT(10) +#define XIP_WRITE_CTRL_INST_L_MASK GENMASK(9, 8) +#define XIP_WRITE_CTRL_INST_L0 0UL +#define XIP_WRITE_CTRL_INST_L4 1UL +#define XIP_WRITE_CTRL_INST_L8 2UL +#define XIP_WRITE_CTRL_INST_L16 3UL +#define XIP_WRITE_CTRL_ADDR_L_MASK GENMASK(7, 4) +#define XIP_WRITE_CTRL_TRANS_TYPE_MASK GENMASK(3, 2) +#define XIP_WRITE_CTRL_TRANS_TYPE_TT0 0UL +#define XIP_WRITE_CTRL_TRANS_TYPE_TT1 1UL +#define XIP_WRITE_CTRL_TRANS_TYPE_TT2 2UL +#define XIP_WRITE_CTRL_FRF_MASK GENMASK(1, 0) +#define XIP_WRITE_CTRL_FRF_DUAL 1UL +#define XIP_WRITE_CTRL_FRF_QUAD 2UL +#define XIP_WRITE_CTRL_FRF_OCTAL 3UL + +/* Register access helpers. */ +#define USES_AUX_REG(inst) + DT_INST_PROP(inst, aux_reg_enable) +#define AUX_REG_INSTANCES (0 DT_INST_FOREACH_STATUS_OKAY(USES_AUX_REG)) +#define BASE_ADDR(dev) (mm_reg_t)DEVICE_MMIO_GET(dev) + +#if AUX_REG_INSTANCES != 0 +static uint32_t aux_reg_read(const struct device *dev, uint32_t off) +{ + return sys_in32(BASE_ADDR(dev) + off/4); +} +static void aux_reg_write(uint32_t data, const struct device *dev, uint32_t off) +{ + sys_out32(data, BASE_ADDR(dev) + off/4); +} +#endif + +#if AUX_REG_INSTANCES != DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) +static uint32_t reg_read(const struct device *dev, uint32_t off) +{ + return sys_read32(BASE_ADDR(dev) + off); +} +static void reg_write(uint32_t data, const struct device *dev, uint32_t off) +{ + sys_write32(data, BASE_ADDR(dev) + off); +} +#endif + +#if AUX_REG_INSTANCES == 0 +/* If no instance uses aux-reg access. */ +#define DECLARE_REG_ACCESS() +#define DEFINE_REG_ACCESS(inst) +#define DEFINE_MM_REG_RD(reg, off) \ + static inline uint32_t read_##reg(const struct device *dev) \ + { return reg_read(dev, off); } +#define DEFINE_MM_REG_WR(reg, off) \ + static inline void write_##reg(const struct device *dev, uint32_t data) \ + { reg_write(data, dev, off); } + +#elif AUX_REG_INSTANCES == DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) +/* If all instances use aux-reg access. */ +#define DECLARE_REG_ACCESS() +#define DEFINE_REG_ACCESS(inst) +#define DEFINE_MM_REG_RD(reg, off) \ + static inline uint32_t read_##reg(const struct device *dev) \ + { return aux_reg_read(dev, off); } +#define DEFINE_MM_REG_WR(reg, off) \ + static inline void write_##reg(const struct device *dev, uint32_t data) \ + { aux_reg_write(data, dev, off); } + +#else +/* If register access varies by instance. */ +#define DECLARE_REG_ACCESS() \ + uint32_t (*read)(const struct device *dev, uint32_t off); \ + void (*write)(uint32_t data, const struct device *dev, uint32_t off) +#define DEFINE_REG_ACCESS(inst) \ + COND_CODE_1(DT_INST_PROP(inst, aux_reg_enable), \ + (.read = aux_reg_read, \ + .write = aux_reg_write,), \ + (.read = reg_read, \ + .write = reg_write,)) +#define DEFINE_MM_REG_RD(reg, off) \ + static inline uint32_t read_##reg(const struct device *dev) \ + { \ + const struct mspi_dw_config *dev_config = dev->config; \ + return dev_config->read(dev, off); \ + } +#define DEFINE_MM_REG_WR(reg, off) \ + static inline void write_##reg(const struct device *dev, uint32_t data) \ + { \ + const struct mspi_dw_config *dev_config = dev->config; \ + dev_config->write(data, dev, off); \ + } +#endif diff --git a/drivers/mspi/mspi_dw_vendor_specific.h b/drivers/mspi/mspi_dw_vendor_specific.h new file mode 100644 index 0000000000000..4913e536c92d9 --- /dev/null +++ b/drivers/mspi/mspi_dw_vendor_specific.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This header is part of mspi_dw.c extracted only for clarity. + * It is not supposed to be included by any file other than mspi_dw.c. + */ + +#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_exmif) + +#include + +static inline void vendor_specific_init(const struct device *dev) +{ + ARG_UNUSED(dev); + + NRF_EXMIF->EVENTS_CORE = 0; + NRF_EXMIF->INTENSET = BIT(EXMIF_INTENSET_CORE_Pos); +} + +static inline void vendor_specific_suspend(const struct device *dev) +{ + ARG_UNUSED(dev); + + NRF_EXMIF->TASKS_STOP = 1; +} + +static inline void vendor_specific_resume(const struct device *dev) +{ + ARG_UNUSED(dev); + + NRF_EXMIF->TASKS_START = 1; +} + +static inline void vendor_specific_irq_clear(const struct device *dev) +{ + ARG_UNUSED(dev); + + NRF_EXMIF->EVENTS_CORE = 0; +} + +#if defined(CONFIG_MSPI_XIP) +static inline int vendor_specific_xip_enable(const struct device *dev, + const struct mspi_dev_id *dev_id, + const struct mspi_xip_cfg *cfg) +{ + ARG_UNUSED(dev); + + if (dev_id->dev_idx == 0) { + NRF_EXMIF->EXTCONF1.OFFSET = cfg->address_offset; + NRF_EXMIF->EXTCONF1.SIZE = cfg->address_offset + + cfg->size - 1; + NRF_EXMIF->EXTCONF1.ENABLE = 1; + } else if (dev_id->dev_idx == 1) { + NRF_EXMIF->EXTCONF2.OFFSET = cfg->address_offset; + NRF_EXMIF->EXTCONF2.SIZE = cfg->address_offset + + cfg->size - 1; + NRF_EXMIF->EXTCONF2.ENABLE = 1; + } else { + return -EINVAL; + } + + return 0; +} + +static inline int vendor_specific_xip_disable(const struct device *dev, + const struct mspi_dev_id *dev_id, + const struct mspi_xip_cfg *cfg) +{ + ARG_UNUSED(dev); + ARG_UNUSED(cfg); + + if (dev_id->dev_idx == 0) { + NRF_EXMIF->EXTCONF1.ENABLE = 0; + } else if (dev_id->dev_idx == 1) { + NRF_EXMIF->EXTCONF2.ENABLE = 0; + } else { + return -EINVAL; + } + + return 0; +} +#endif /* defined(CONFIG_MSPI_XIP) */ + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_exmif) */ diff --git a/drivers/pinctrl/pinctrl_nrf.c b/drivers/pinctrl/pinctrl_nrf.c index 3ede211958ac8..77387e4880661 100644 --- a/drivers/pinctrl/pinctrl_nrf.c +++ b/drivers/pinctrl/pinctrl_nrf.c @@ -383,6 +383,24 @@ int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, input = NRF_GPIO_PIN_INPUT_CONNECT; break; #endif /* DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_can) */ +#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_exmif) + /* Pin routing is controlled by secure domain, via UICR */ + case NRF_FUN_EXMIF_CK: + case NRF_FUN_EXMIF_DQ0: + case NRF_FUN_EXMIF_DQ1: + case NRF_FUN_EXMIF_DQ2: + case NRF_FUN_EXMIF_DQ3: + case NRF_FUN_EXMIF_DQ4: + case NRF_FUN_EXMIF_DQ5: + case NRF_FUN_EXMIF_DQ6: + case NRF_FUN_EXMIF_DQ7: + case NRF_FUN_EXMIF_CS0: + case NRF_FUN_EXMIF_CS1: + case NRF_FUN_EXMIF_RWDS: + dir = NRF_GPIO_PIN_DIR_INPUT; + input = NRF_GPIO_PIN_INPUT_DISCONNECT; + break; +#endif /* DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_exmif) */ #if defined(NRF_PSEL_TWIS) case NRF_FUN_TWIS_SCL: NRF_PSEL_TWIS(reg, SCL) = psel; diff --git a/dts/bindings/spi/nordic,nrf-exmif.yaml b/dts/bindings/mspi/nordic,nrf-exmif.yaml similarity index 70% rename from dts/bindings/spi/nordic,nrf-exmif.yaml rename to dts/bindings/mspi/nordic,nrf-exmif.yaml index d2b8815046b5b..294254aa60efb 100644 --- a/dts/bindings/spi/nordic,nrf-exmif.yaml +++ b/dts/bindings/mspi/nordic,nrf-exmif.yaml @@ -5,8 +5,4 @@ description: Nordic External Memory Interface (EXMIF) compatible: "nordic,nrf-exmif" -include: snps,designware-spi.yaml - -properties: - reg: - required: true +include: snps,designware-ssi.yaml diff --git a/dts/bindings/mspi/snps,designware-ssi.yaml b/dts/bindings/mspi/snps,designware-ssi.yaml new file mode 100644 index 0000000000000..fb516cb783588 --- /dev/null +++ b/dts/bindings/mspi/snps,designware-ssi.yaml @@ -0,0 +1,45 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: Synopsys DesignWare Synchronous Serial Interface (SSI) node + +compatible: "snps,designware-ssi" + +include: [mspi-controller.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + aux-reg-enable: + type: boolean + description: | + Activates auxiliary register access that is needed on some platforms. + + fifo-depth: + required: true + type: int + description: | + Number of items that can be stored in the TX FIFO. Range: 8-256. + If the RX FIFO depth is not specified separately in the rx-fifo-depth + property, this value specifies depth of both TX and RX FIFOs. + + rx-fifo-depth: + type: int + description: | + Number of items that can be stored in the RX FIFO. Range: 8-256. + + tx-fifo-threshold: + type: int + description: | + Number of entries in the TX FIFO above which the TX transfer is started. + Maximum value is the TX FIFO depth - 1. + + rx-fifo-threshold: + type: int + description: | + Number of entries in the RX FIFO above which the controller gets an RX + interrupt. Maximum value is the RX FIFO depth - 1. diff --git a/dts/bindings/mtd/jedec,mspi-nor.yaml b/dts/bindings/mtd/jedec,mspi-nor.yaml new file mode 100644 index 0000000000000..5bad8f54b5dc6 --- /dev/null +++ b/dts/bindings/mtd/jedec,mspi-nor.yaml @@ -0,0 +1,24 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: Generic NOR flash on MSPI bus + +compatible: "jedec,mspi-nor" + +include: [mspi-device.yaml, "jedec,spi-nor-common.yaml"] + +properties: + reset-gpios: + type: phandle-array + description: | + RESET line. If specified, the flash chip will be reset at initialization. + + t-reset-pulse: + type: int + description: | + Minimum duration, in nanoseconds, of an active pulse on the RESET line. + + t-reset-recovery: + type: int + description: | + Minimum time, in nanoseconds, the flash chip needs to recover after reset. diff --git a/dts/common/nordic/nrf54h20.dtsi b/dts/common/nordic/nrf54h20.dtsi index 3dc8afb707189..45a3f76e45b5f 100644 --- a/dts/common/nordic/nrf54h20.dtsi +++ b/dts/common/nordic/nrf54h20.dtsi @@ -510,8 +510,8 @@ status = "disabled"; }; - exmif: spi@95000 { - compatible = "nordic,nrf-exmif"; + exmif: exmif@95000 { + compatible = "nordic,nrf-exmif", "snps,designware-ssi"; #address-cells = <1>; #size-cells = <0>; reg = <0x95000 0x500 0x95500 0xb00>; @@ -520,7 +520,6 @@ power-domains = <&gpd NRF_GPD_FAST_ACTIVE0>; clock-frequency = ; fifo-depth = <32>; - max-xfer-size = <16>; status = "disabled"; }; diff --git a/dts/common/nordic/nrf9280.dtsi b/dts/common/nordic/nrf9280.dtsi index ee201dbacfde0..55f7f8d20fcff 100644 --- a/dts/common/nordic/nrf9280.dtsi +++ b/dts/common/nordic/nrf9280.dtsi @@ -375,8 +375,8 @@ status = "disabled"; }; - exmif: spi@95000 { - compatible = "nordic,nrf-exmif"; + exmif: exmif@95000 { + compatible = "nordic,nrf-exmif", "snps,designware-ssi"; #address-cells = <1>; #size-cells = <0>; reg = <0x95000 0x500 0x95500 0xb00>; @@ -384,7 +384,6 @@ interrupts = <149 NRF_DEFAULT_IRQ_PRIORITY>; clock-frequency = ; fifo-depth = <32>; - max-xfer-size = <16>; status = "disabled"; }; diff --git a/include/zephyr/dt-bindings/pinctrl/nrf-pinctrl.h b/include/zephyr/dt-bindings/pinctrl/nrf-pinctrl.h index 51656a1507430..7d75f0d710946 100644 --- a/include/zephyr/dt-bindings/pinctrl/nrf-pinctrl.h +++ b/include/zephyr/dt-bindings/pinctrl/nrf-pinctrl.h @@ -166,6 +166,8 @@ #define NRF_FUN_TWIS_SCL 48U /** TWIS SDA */ #define NRF_FUN_TWIS_SDA 49U +/** EXMIF RWDS */ +#define NRF_FUN_EXMIF_RWDS 50U /** GRTC fast clock output */ #define NRF_FUN_GRTC_CLKOUT_FAST 55U /** GRTC slow clock output */ diff --git a/samples/application_development/code_relocation_nocopy/boards/nrf54h20dk_nrf54h20_cpuapp.conf b/samples/application_development/code_relocation_nocopy/boards/nrf54h20dk_nrf54h20_cpuapp.conf new file mode 100644 index 0000000000000..8303508138954 --- /dev/null +++ b/samples/application_development/code_relocation_nocopy/boards/nrf54h20dk_nrf54h20_cpuapp.conf @@ -0,0 +1 @@ +CONFIG_FLASH=y diff --git a/samples/application_development/code_relocation_nocopy/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/samples/application_development/code_relocation_nocopy/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 0000000000000..ab53f12725d77 --- /dev/null +++ b/samples/application_development/code_relocation_nocopy/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,17 @@ +&cpuapp_ram0x_region { + nordic,access = ; +}; + +&xip_region { + status = "okay"; + nordic,access = ; +}; + +&mx25uw63 { + read-command = <0xEC13>; + command-length = "INSTR_2_BYTE"; + address-length = "ADDR_4_BYTE"; + rx-dummy = <20>; + + xip-config = <1 0 0x20000000 0>; +}; diff --git a/samples/application_development/code_relocation_nocopy/linker_arm_nocopy.ld b/samples/application_development/code_relocation_nocopy/linker_arm_nocopy.ld index a1467f8e26454..0a11783cca2ae 100644 --- a/samples/application_development/code_relocation_nocopy/linker_arm_nocopy.ld +++ b/samples/application_development/code_relocation_nocopy/linker_arm_nocopy.ld @@ -47,6 +47,13 @@ #define EXTFLASH_ADDR DT_REG_ADDR(DT_INST(0, st_stm32_xspi_nor)) #define EXTFLASH_SIZE DT_REG_ADDR_BY_IDX(DT_INST(0, st_stm32_xspi_nor), 1) +#elif defined(CONFIG_FLASH_MSPI_NOR) && defined(CONFIG_SOC_NRF54H20_CPUAPP) + +#define EXTFLASH_NODE DT_INST(0, jedec_mspi_nor) +#define EXTFLASH_ADDR 0x60000000 +#define EXTFLASH_SIZE DT_PROP_OR(EXTFLASH_NODE, size_in_bytes, \ + DT_PROP(EXTFLASH_NODE, size) / 8) + #else /* diff --git a/samples/drivers/jesd216/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/samples/drivers/jesd216/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 0000000000000..b8f138ad2b2e2 --- /dev/null +++ b/samples/drivers/jesd216/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&mx25uw63 { + status = "okay"; +}; diff --git a/samples/drivers/jesd216/sample.yaml b/samples/drivers/jesd216/sample.yaml index 0ea0dae331aee..4be5e5defbbe4 100644 --- a/samples/drivers/jesd216/sample.yaml +++ b/samples/drivers/jesd216/sample.yaml @@ -21,7 +21,7 @@ tests: - hifive_unmatched/fu740/u74 - mimxrt1170_evk/mimxrt1176/cm7 - mimxrt1170_evk/mimxrt1176/cm4 - filter: dt_compat_enabled("jedec,spi-nor") + filter: dt_compat_enabled("jedec,spi-nor") or dt_compat_enabled("jedec,mspi-nor") depends_on: spi sample.drivers.jesd216.nrf52840dk_spi: extra_args: diff --git a/samples/drivers/jesd216/src/main.c b/samples/drivers/jesd216/src/main.c index 480649397b6b2..b8dad485997a3 100644 --- a/samples/drivers/jesd216/src/main.c +++ b/samples/drivers/jesd216/src/main.c @@ -14,6 +14,8 @@ #if DT_HAS_COMPAT_STATUS_OKAY(jedec_spi_nor) #define FLASH_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(jedec_spi_nor) +#elif DT_HAS_COMPAT_STATUS_OKAY(jedec_mspi_nor) +#define FLASH_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(jedec_mspi_nor) #elif DT_HAS_COMPAT_STATUS_OKAY(nordic_qspi_nor) #define FLASH_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(nordic_qspi_nor) #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_qspi_nor) @@ -201,7 +203,7 @@ static void summarize_dw15(const struct jesd216_param_header *php, printf("0-4-4 Mode methods: entry 0x%01x ; exit 0x%02x\n", dw15.entry_044, dw15.exit_044); } else { - printf("0-4-4 Mode: not supported"); + printf("0-4-4 Mode: not supported\n"); } printf("4-4-4 Mode sequences: enable 0x%02x ; disable 0x%01x\n", dw15.enable_444, dw15.disable_444); diff --git a/samples/drivers/spi_flash/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/samples/drivers/spi_flash/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 0000000000000..b8f138ad2b2e2 --- /dev/null +++ b/samples/drivers/spi_flash/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&mx25uw63 { + status = "okay"; +}; diff --git a/samples/drivers/spi_flash/sample.yaml b/samples/drivers/spi_flash/sample.yaml index a3b8cfed380a5..351e33997a0b8 100644 --- a/samples/drivers/spi_flash/sample.yaml +++ b/samples/drivers/spi_flash/sample.yaml @@ -7,7 +7,7 @@ tests: - flash filter: dt_compat_enabled("jedec,spi-nor") or dt_compat_enabled("st,stm32-qspi-nor") or dt_compat_enabled("st,stm32-ospi-nor") or dt_compat_enabled("st,stm32-xspi-nor") - or (dt_compat_enabled("nordic,qspi-nor") and CONFIG_NORDIC_QSPI_NOR) + or dt_compat_enabled("nordic,qspi-nor") or dt_compat_enabled("jedec,mspi-nor") platform_exclude: - hifive_unmatched/fu740/s7 - hifive_unmatched/fu740/u74 diff --git a/samples/drivers/spi_flash/src/main.c b/samples/drivers/spi_flash/src/main.c index b84359ae05bb1..52e8b6062b538 100644 --- a/samples/drivers/spi_flash/src/main.c +++ b/samples/drivers/spi_flash/src/main.c @@ -34,6 +34,8 @@ #if DT_HAS_COMPAT_STATUS_OKAY(jedec_spi_nor) #define SPI_FLASH_COMPAT jedec_spi_nor +#elif DT_HAS_COMPAT_STATUS_OKAY(jedec_mspi_nor) +#define SPI_FLASH_COMPAT jedec_mspi_nor #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_qspi_nor) #define SPI_FLASH_COMPAT st_stm32_qspi_nor #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_ospi_nor) diff --git a/tests/drivers/flash/common/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/tests/drivers/flash/common/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 0000000000000..b8f138ad2b2e2 --- /dev/null +++ b/tests/drivers/flash/common/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&mx25uw63 { + status = "okay"; +}; diff --git a/tests/drivers/flash/common/src/main.c b/tests/drivers/flash/common/src/main.c index e7552aaf177b2..54b868a923413 100644 --- a/tests/drivers/flash/common/src/main.c +++ b/tests/drivers/flash/common/src/main.c @@ -14,6 +14,8 @@ #define TEST_AREA_DEV_NODE DT_INST(0, nordic_qspi_nor) #elif defined(CONFIG_SPI_NOR) #define TEST_AREA_DEV_NODE DT_INST(0, jedec_spi_nor) +#elif defined(CONFIG_FLASH_MSPI_NOR) +#define TEST_AREA_DEV_NODE DT_INST(0, jedec_mspi_nor) #else #define TEST_AREA storage_partition #endif