diff --git a/CODEOWNERS b/CODEOWNERS index 18f4d8f7e493..c5109aff35c6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -283,6 +283,7 @@ /drivers/gpio/ @nrfconnect/ncs-co-drivers @nrfconnect/ncs-ll-ursus /drivers/hw_cc3xx/ @nrfconnect/ncs-co-drivers @nrfconnect/ncs-aegir /drivers/mpsl/ @nrfconnect/ncs-co-drivers @nrfconnect/ncs-dragoon +/drivers/mspi/ @nrfconnect/ncs-co-drivers @nrfconnect/ncs-ll-ursus /drivers/net/ @nrfconnect/ncs-co-drivers @doki-nordic /drivers/serial/ @nrfconnect/ncs-co-drivers @nordic-krch /drivers/sensor/bh1749/ @nrfconnect/ncs-co-drivers @nrfconnect/ncs-cia @@ -317,6 +318,7 @@ /include/drivers/flash/ @nrfconnect/ncs-co-drivers /include/drivers/gpio/ @nrfconnect/ncs-co-drivers @nrfconnect/ncs-ll-ursus /include/drivers/bme68x_iaq.h @nrfconnect/ncs-co-drivers @nrfconnect/ncs-cia +/include/drivers/mspi/nrfe_mspi.h @nrfconnect/ncs-co-drivers @nrfconnect/ncs-ll-ursus /include/drivers/sensor_sim.h @nrfconnect/ncs-co-drivers @nrfconnect/ncs-cia /include/drivers/sensor_stub.h @nrfconnect/ncs-co-drivers @nrfconnect/ncs-cia /include/emds/ @balaklaka @nrfconnect/ncs-paladin @@ -608,6 +610,7 @@ /scripts/print_docker_image.sh @nrfconnect/ncs-ci /scripts/print_toolchain_checksum.sh @nrfconnect/ncs-ci /scripts/sdp/ @nrfconnect/ncs-ll-ursus +/scripts/twister/alt/zephyr/tests/drivers/mspi/api/testcase.yaml @nrfconnect/ncs-ll-ursus /scripts/docker/*.rst @nrfconnect/ncs-doc-leads /scripts/hid_configurator/*.rst @nrfconnect/ncs-si-bluebagel-doc diff --git a/applications/sdp/mspi/CMakeLists.txt b/applications/sdp/mspi/CMakeLists.txt new file mode 100644 index 000000000000..338d1d7ad562 --- /dev/null +++ b/applications/sdp/mspi/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(sdp_mspi) + +sdp_assembly_generate("${CMAKE_SOURCE_DIR}/src/hrt/hrt.c") +sdp_assembly_check("${CMAKE_SOURCE_DIR}/src/hrt/hrt.c") +sdp_assembly_prepare_install("${CMAKE_SOURCE_DIR}/src/hrt/hrt.c") + +target_sources(app PRIVATE src/main.c) +target_sources(app PRIVATE src/hrt/hrt.s) + +add_dependencies(app asm_check) diff --git a/applications/sdp/mspi/boards/nrf54l15dk_nrf54l15_cpuflpr.conf b/applications/sdp/mspi/boards/nrf54l15dk_nrf54l15_cpuflpr.conf new file mode 100644 index 000000000000..6c1b7543e212 --- /dev/null +++ b/applications/sdp/mspi/boards/nrf54l15dk_nrf54l15_cpuflpr.conf @@ -0,0 +1,46 @@ +# Single-threaded +CONFIG_MULTITHREADING=n +CONFIG_KERNEL_MEM_POOL=n +CONFIG_LOG=n + +# Drivers and peripherals +CONFIG_I2C=n +CONFIG_WATCHDOG=n +CONFIG_GPIO=n +CONFIG_PINCTRL=n +CONFIG_SPI=n +CONFIG_SERIAL=n +CONFIG_FLASH=n + +# Power management +CONFIG_PM=n + +# Interrupts +CONFIG_DYNAMIC_INTERRUPTS=n +CONFIG_IRQ_OFFLOAD=n +CONFIG_GEN_SW_ISR_TABLE=n + +# Memory protection +CONFIG_THREAD_STACK_INFO=n +CONFIG_THREAD_CUSTOM_DATA=n +CONFIG_FPU=n + +# Boot +CONFIG_BOOT_BANNER=n +CONFIG_NCS_BOOT_BANNER=n + +# Console +CONFIG_CONSOLE=n +CONFIG_UART_CONSOLE=n +CONFIG_STDOUT_CONSOLE=n +CONFIG_PRINTK=n +CONFIG_EARLY_CONSOLE=n + +# Build +CONFIG_SIZE_OPTIMIZATIONS=y + +# No timer support in the kernel +CONFIG_SYS_CLOCK_EXISTS=n + +CONFIG_OUTPUT_DISASSEMBLY=y +CONFIG_COMMON_LIBC_MALLOC=n diff --git a/applications/sdp/mspi/boards/nrf54l15dk_nrf54l15_cpuflpr.overlay b/applications/sdp/mspi/boards/nrf54l15dk_nrf54l15_cpuflpr.overlay new file mode 100644 index 000000000000..c3c8081a6be0 --- /dev/null +++ b/applications/sdp/mspi/boards/nrf54l15dk_nrf54l15_cpuflpr.overlay @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + soc { + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + + sram_tx: memory@2003c000 { + reg = <0x2003c000 0x0800>; + }; + + sram_rx: memory@2003c800 { + reg = <0x2003c800 0x0800>; + }; + }; + }; + + ipc { + ipc0: ipc0 { + compatible = "zephyr,ipc-icmsg"; + tx-region = <&sram_tx>; + rx-region = <&sram_rx>; + mboxes = <&cpuflpr_vevif_rx 16>, <&cpuflpr_vevif_tx 20>; + mbox-names = "rx", "tx"; + status = "okay"; + }; + }; +}; + +&cpuflpr_rram { + reg = <0x17a000 DT_SIZE_K(12)>; +}; + +&cpuflpr_code_partition { + reg = <0x0 DT_SIZE_K(12)>; +}; + +&cpuflpr_sram { + reg = <0x2003d000 DT_SIZE_K(12)>; + ranges = <0x0 0x2003d000 0x3000>; +}; + +&cpuflpr_vevif_rx { + status = "okay"; + interrupts = <16 NRF_DEFAULT_IRQ_PRIORITY>; + nordic,tasks = <1>; + nordic,tasks-mask = <0x00010000>; +}; + +&cpuflpr_vevif_tx { + status = "okay"; +}; + +&gpio0 { + status = "disabled"; +}; + +&gpio1 { + status = "disabled"; +}; + +&gpio2 { + status = "disabled"; +}; + +&gpiote20 { + status = "disabled"; +}; + +&gpiote30 { + status = "disabled"; +}; + +&grtc { + status = "disabled"; +}; + +&uart20 { + status = "disabled"; +}; + +&uart30 { + status = "disabled"; +}; + +&pwm20 { + status = "disabled"; +}; diff --git a/applications/sdp/mspi/prj.conf b/applications/sdp/mspi/prj.conf new file mode 100644 index 000000000000..5db8be05d329 --- /dev/null +++ b/applications/sdp/mspi/prj.conf @@ -0,0 +1,3 @@ +CONFIG_MBOX=y +CONFIG_IPC_SERVICE=y +CONFIG_IPC_SERVICE_BACKEND_ICMSG=y diff --git a/applications/sdp/mspi/sample.yaml b/applications/sdp/mspi/sample.yaml new file mode 100644 index 000000000000..a3c84c2d6056 --- /dev/null +++ b/applications/sdp/mspi/sample.yaml @@ -0,0 +1,14 @@ +sample: + name: SDP mSPI application + description: SDP mSPI application +common: + integration_platforms: + - nrf54l15dk/nrf54l15/cpuflpr +tests: + applications.sdp.mspi: + build_only: true + sysbuild: true + platform_allow: nrf54l15dk/nrf54l15/cpuflpr + tags: ci_build sysbuild mspi + required_snippets: + - sdp-mspi diff --git a/applications/sdp/mspi/src/hrt/hrt.c b/applications/sdp/mspi/src/hrt/hrt.c new file mode 100644 index 000000000000..e341c904b60d --- /dev/null +++ b/applications/sdp/mspi/src/hrt/hrt.c @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +#include "hrt.h" +#include +#include + +#define CLK_FIRST_CYCLE_MULTIPLICATOR (3) + +void write_single_by_word(volatile uint32_t *data, uint8_t data_len, uint32_t counter_top, + uint8_t word_size, bool ce_enable_state, bool hold_ce) +{ + + NRFX_ASSERT(word_size <= MAX_WORD_SIZE); + /* Configuration step */ + uint16_t dir = nrf_vpr_csr_vio_dir_get(); + + nrf_vpr_csr_vio_dir_set(dir | PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_DQ0_PIN_NUMBER)) | + PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) | + PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + + uint16_t out = nrf_vpr_csr_vio_out_get(); + + nrf_vpr_csr_vio_out_set(out | PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_DQ0_PIN_NUMBER)) | + PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) | + PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + + nrf_vpr_csr_vio_mode_out_t out_mode = { + .mode = NRF_VPR_CSR_VIO_SHIFT_OUTB_TOGGLE, + .frame_width = 1, + }; + + nrf_vpr_csr_vio_mode_out_set(&out_mode); + nrf_vpr_csr_vio_mode_in_buffered_set(NRF_VPR_CSR_VIO_MODE_IN_CONTINUOUS); + + nrf_vpr_csr_vio_config_t config; + + nrf_vpr_csr_vio_config_get(&config); + config.input_sel = false; + nrf_vpr_csr_vio_config_set(&config); + + /* Fix position of data if word size < MAX_WORD_SIZE, + * so that leading zeros would not be printed instead of data bits. + */ + if (word_size < MAX_WORD_SIZE) { + for (uint8_t i = 0; i < data_len; i++) { + data[i] = data[i] << (MAX_WORD_SIZE - word_size); + } + } + + /* Counter settings */ + nrf_vpr_csr_vtim_count_mode_set(0, NRF_VPR_CSR_VTIM_COUNT_RELOAD); + nrf_vpr_csr_vtim_simple_counter_top_set(0, counter_top); + + /* Set number of shifts before OUTB needs to be updated. + * First shift needs to be increased by 1. + */ + nrf_vpr_csr_vio_shift_cnt_out_set(word_size); + nrf_vpr_csr_vio_shift_cnt_out_buffered_set(word_size - 1); + + /* Enable CS */ + out = nrf_vpr_csr_vio_out_get(); + out &= ~PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)); + out |= ce_enable_state ? PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) + : PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)); + nrf_vpr_csr_vio_out_set(out); + + /* Start counter */ + nrf_vpr_csr_vtim_simple_counter_set(0, CLK_FIRST_CYCLE_MULTIPLICATOR * counter_top); + + /* Send data */ + for (uint8_t i = 0; i < data_len; i++) { + nrf_vpr_csr_vio_out_buffered_reversed_byte_set(data[i]); + } + + /* Clear all bits, wait until the last word is sent */ + nrf_vpr_csr_vio_out_buffered_set(0); + + /* Final configuration */ + out_mode.mode = NRF_VPR_CSR_VIO_SHIFT_NONE; + nrf_vpr_csr_vio_mode_out_buffered_set(&out_mode); + nrf_vpr_csr_vio_mode_in_buffered_set(NRF_VPR_CSR_VIO_MODE_IN_CONTINUOUS); + + /* Deselect slave */ + if (!hold_ce) { + out = nrf_vpr_csr_vio_out_get(); + out &= ~(PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) | + PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + out |= ce_enable_state ? PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) + : PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)); + out |= PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER)); + nrf_vpr_csr_vio_out_set(out); + } + + /* Stop counter */ + nrf_vpr_csr_vtim_count_mode_set(0, NRF_VPR_CSR_VTIM_COUNT_STOP); +} + +void write_quad_by_word(volatile uint32_t *data, uint8_t data_len, uint32_t counter_top, + uint8_t word_size, bool ce_enable_state, bool hold_ce) +{ + NRFX_ASSERT(word_size % 4 == 0); + NRFX_ASSERT(word_size <= MAX_WORD_SIZE); + /* Configuration step */ + uint16_t dir = nrf_vpr_csr_vio_dir_get(); + + nrf_vpr_csr_vio_dir_set(dir | PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_DQ0_PIN_NUMBER)) | + PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_DQ1_PIN_NUMBER)) | + PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_DQ2_PIN_NUMBER)) | + PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_DQ3_PIN_NUMBER)) | + PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) | + PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + + uint16_t out = nrf_vpr_csr_vio_out_get(); + + nrf_vpr_csr_vio_out_set(out | PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_DQ0_PIN_NUMBER)) | + PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_DQ1_PIN_NUMBER)) | + PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_DQ2_PIN_NUMBER)) | + PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_DQ3_PIN_NUMBER)) | + PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) | + PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + + nrf_vpr_csr_vio_mode_out_t out_mode = { + .mode = NRF_VPR_CSR_VIO_SHIFT_OUTB_TOGGLE, + .frame_width = 4, + }; + + nrf_vpr_csr_vio_mode_out_set(&out_mode); + nrf_vpr_csr_vio_mode_in_buffered_set(NRF_VPR_CSR_VIO_MODE_IN_CONTINUOUS); + + nrf_vpr_csr_vio_config_t config; + + nrf_vpr_csr_vio_config_get(&config); + config.input_sel = false; + nrf_vpr_csr_vio_config_set(&config); + + /* Fix position of data if word size < MAX_WORD_SIZE, + * so that leading zeros would not be printed instead of data. + */ + if (word_size < MAX_WORD_SIZE) { + for (uint8_t i = 0; i < data_len; i++) { + data[i] = data[i] << (MAX_WORD_SIZE - word_size); + } + } + + /* Counter settings */ + nrf_vpr_csr_vtim_count_mode_set(0, NRF_VPR_CSR_VTIM_COUNT_RELOAD); + nrf_vpr_csr_vtim_simple_counter_top_set(0, counter_top); + + /* Set number of shifts before OUTB needs to be updated. + * First shift needs to be increased by 1. + */ + nrf_vpr_csr_vio_shift_cnt_out_set(word_size / 4); + nrf_vpr_csr_vio_shift_cnt_out_buffered_set(word_size / 4 - 1); + + /* Enable CS */ + out = nrf_vpr_csr_vio_out_get(); + out &= ~PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)); + out |= ce_enable_state ? PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) + : PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)); + nrf_vpr_csr_vio_out_set(out); + + /* Start counter */ + nrf_vpr_csr_vtim_simple_counter_set(0, 3 * counter_top); + + /* Send data */ + for (uint8_t i = 0; i < data_len; i++) { + nrf_vpr_csr_vio_out_buffered_reversed_byte_set(data[i]); + } + + /* Clear all bits, wait until the last word is sent */ + nrf_vpr_csr_vio_out_buffered_set(0); + + /* Final configuration */ + out_mode.mode = NRF_VPR_CSR_VIO_SHIFT_NONE; + nrf_vpr_csr_vio_mode_out_buffered_set(&out_mode); + nrf_vpr_csr_vio_mode_in_buffered_set(NRF_VPR_CSR_VIO_MODE_IN_CONTINUOUS); + + /* Deselect slave */ + if (!hold_ce) { + out = nrf_vpr_csr_vio_out_get(); + out &= ~(PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) | + PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + out |= ce_enable_state ? PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)) + : PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_CS0_PIN_NUMBER)); + out |= PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER)); + nrf_vpr_csr_vio_out_set(out); + } + + /* Stop counter */ + nrf_vpr_csr_vtim_count_mode_set(0, NRF_VPR_CSR_VTIM_COUNT_STOP); +} diff --git a/applications/sdp/mspi/src/hrt/hrt.h b/applications/sdp/mspi/src/hrt/hrt.h new file mode 100644 index 000000000000..7e1aa409ea07 --- /dev/null +++ b/applications/sdp/mspi/src/hrt/hrt.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _HRT_H__ +#define _HRT_H__ + +#include +#include +#include + +/* Max word size. */ +#define MAX_WORD_SIZE NRF_VPR_CSR_VIO_SHIFT_CNT_OUT_BUFFERED_MAX + +/* Macro for getting direction mask for specified pin and direction. */ +#define PIN_DIR_MASK(PIN_NUM, DIR) \ + (VPRCSR_NORDIC_DIR_PIN##PIN_NUM##_##DIR << VPRCSR_NORDIC_DIR_PIN##PIN_NUM##_Pos) + +/* Macro for getting output mask for specified pin. */ +#define PIN_DIR_OUT_MASK(PIN_NUM) PIN_DIR_MASK(PIN_NUM, OUTPUT) + +/* Macro for getting input mask for specified pin. */ +#define PIN_DIR_IN_MASK(PIN_NUM) PIN_DIR_MASK(PIN_NUM, INPUT) + +/* Macro for getting state mask for specified pin and state. */ +#define PIN_OUT_MASK(PIN_NUM, STATE) \ + (VPRCSR_NORDIC_OUT_PIN##PIN_NUM##_##STATE << VPRCSR_NORDIC_OUT_PIN##PIN_NUM##_Pos) + +/* Macro for getting high state mask for specified pin. */ +#define PIN_OUT_HIGH_MASK(PIN_NUM) PIN_OUT_MASK(PIN_NUM, HIGH) + +/* Macro for getting low state mask for specified pin. */ +#define PIN_OUT_LOW_MASK(PIN_NUM) PIN_OUT_MASK(PIN_NUM, LOW) + +/** @brief Write on single line. + * + * Function to be used to write data on single data line (SPI). + * + * @param[in] data Data to be sent. + * @param[in] data_len Length of data in words. + * @param[in] counter_top Top value of VTIM. This will determine clock frequency + * (SPI_CLOCK ~= CPU_CLOCK / (2 * TOP)). + * @param[in] word_size Size of a word in bits. + * @param[in] ce_enable_state Chip enable pin polarity in enabled state. + * @param[in] hold_ce If true CE pin will be left enabled after transfer. + */ +void write_single_by_word(volatile uint32_t *data, uint8_t data_len, uint32_t counter_top, + uint8_t word_size, bool ce_enable_state, bool hold_ce); + +/** @brief Write on four lines. + * + * Function to be used to write data on quad data line (SPI). + * + * @param[in] data Data to be sent. + * @param[in] data_len Length of data in words. + * @param[in] counter_top Top value of VTIM. This will determine clock frequency + * (SPI_CLOCK ~= CPU_CLOCK / (2 * TOP)). + * @param[in] word_size Size of a word in bits. + * @param[in] ce_enable_state Chip enable pin polarity in enabled state. + * @param[in] hold_ce If true CE pin will be left enabled after transfer. + */ +void write_quad_by_word(volatile uint32_t *data, uint8_t data_len, uint32_t counter_top, + uint8_t word_size, bool ce_enable_state, bool hold_ce); + +#endif /* _HRT_H__ */ diff --git a/applications/sdp/mspi/src/hrt/hrt.s b/applications/sdp/mspi/src/hrt/hrt.s new file mode 100644 index 000000000000..e4eece2a87bb --- /dev/null +++ b/applications/sdp/mspi/src/hrt/hrt.s @@ -0,0 +1,273 @@ + .file "hrt.c" + .option nopic + .attribute arch, "rv32e1p9_m2p0_c2p0_zicsr2p0" + .attribute unaligned_access, 0 + .attribute stack_align, 4 + .text + .section .text.write_single_by_word,"ax",@progbits + .align 1 + .globl write_single_by_word + .type write_single_by_word, @function +write_single_by_word: + addi sp,sp,-4 + sw s0,0(sp) + mv t1,a4 + #APP + csrr a4, 3009 + #NO_APP + ori a4,a4,35 + slli a4,a4,16 + srli a4,a4,16 + #APP + csrw 3009, a4 + csrr a4, 3008 + #NO_APP + ori a4,a4,32 + slli a4,a4,16 + srli a4,a4,16 + #APP + csrw 3008, a4 + #NO_APP + li a4,65536 + addi a4,a4,4 + #APP + csrw 3043, a4 + csrw 3045, 0 + csrr a4, 1996 + #NO_APP + andi a4,a4,17 + #APP + csrw 1996, a4 + #NO_APP + li a4,31 + bleu a3,a4,.L8 +.L5: + #APP + csrw 2000, 2 + csrr t0, 2003 + #NO_APP + li a4,-65536 + and t0,t0,a4 + slli a4,a2,16 + srli a4,a4,16 + or a4,a4,t0 + #APP + csrw 2003, a4 + csrw 3022, a3 + #NO_APP + addi a3,a3,-1 + andi a3,a3,0xff + #APP + csrw 3023, a3 + csrr a4, 3008 + #NO_APP + andi a4,a4,-33 + slli a4,a4,16 + slli a3,t1,5 + srli a4,a4,16 + or a4,a4,a3 + #APP + csrw 3008, a4 + #NO_APP + li a4,3 + mul a4,a2,a4 + slli a4,a4,16 + srli a4,a4,16 + #APP + csrw 2005, a4 + #NO_APP + li a4,0 +.L3: + andi a3,a4,0xff + bltu a3,a1,.L6 + #APP + csrw 3012, 0 + #NO_APP + li a4,65536 + #APP + csrw 3043, a4 + csrw 3045, 0 + #NO_APP + bne a5,zero,.L7 + #APP + csrr a4, 3008 + #NO_APP + andi a4,a4,-34 + slli a4,a4,16 + xori t1,t1,1 + slli t1,t1,5 + srli a4,a4,16 + or a4,a4,t1 + slli a4,a4,16 + srli a4,a4,16 + #APP + csrw 3008, a4 + #NO_APP +.L7: + #APP + csrw 2000, 0 + #NO_APP + lw s0,0(sp) + addi sp,sp,4 + jr ra +.L4: + slli t0,a4,2 + add t0,a0,t0 + lw s0,0(t0) + addi a4,a4,1 + sll s0,s0,t2 + sw s0,0(t0) +.L2: + andi t0,a4,0xff + bgtu a1,t0,.L4 + j .L5 +.L8: + li t2,32 + li a4,0 + sub t2,t2,a3 + j .L2 +.L6: + slli a3,a4,2 + add a3,a0,a3 + lw a3,0(a3) + #APP + csrw 3016, a3 + #NO_APP + addi a4,a4,1 + j .L3 + .size write_single_by_word, .-write_single_by_word + .section .text.write_quad_by_word,"ax",@progbits + .align 1 + .globl write_quad_by_word + .type write_quad_by_word, @function +write_quad_by_word: + addi sp,sp,-4 + sw s0,0(sp) + mv t1,a4 + #APP + csrr a4, 3009 + #NO_APP + ori a4,a4,63 + slli a4,a4,16 + srli a4,a4,16 + #APP + csrw 3009, a4 + csrr a4, 3008 + #NO_APP + ori a4,a4,32 + slli a4,a4,16 + srli a4,a4,16 + #APP + csrw 3008, a4 + #NO_APP + li a4,262144 + addi a4,a4,4 + #APP + csrw 3043, a4 + csrw 3045, 0 + csrr a4, 1996 + #NO_APP + andi a4,a4,17 + #APP + csrw 1996, a4 + #NO_APP + li a4,31 + bleu a3,a4,.L17 +.L14: + #APP + csrw 2000, 2 + csrr t0, 2003 + #NO_APP + li a4,-65536 + and t0,t0,a4 + slli a4,a2,16 + srli a4,a4,16 + or a4,a4,t0 + #APP + csrw 2003, a4 + #NO_APP + srli a3,a3,2 + #APP + csrw 3022, a3 + #NO_APP + addi a3,a3,-1 + andi a3,a3,0xff + #APP + csrw 3023, a3 + csrr a4, 3008 + #NO_APP + andi a4,a4,-33 + slli a4,a4,16 + slli a3,t1,5 + srli a4,a4,16 + or a4,a4,a3 + #APP + csrw 3008, a4 + #NO_APP + li a4,3 + mul a4,a2,a4 + slli a4,a4,16 + srli a4,a4,16 + #APP + csrw 2005, a4 + #NO_APP + li a4,0 +.L12: + andi a3,a4,0xff + bltu a3,a1,.L15 + #APP + csrw 3012, 0 + #NO_APP + li a4,262144 + #APP + csrw 3043, a4 + csrw 3045, 0 + #NO_APP + bne a5,zero,.L16 + #APP + csrr a4, 3008 + #NO_APP + andi a4,a4,-34 + slli a4,a4,16 + xori t1,t1,1 + slli t1,t1,5 + srli a4,a4,16 + or a4,a4,t1 + slli a4,a4,16 + srli a4,a4,16 + #APP + csrw 3008, a4 + #NO_APP +.L16: + #APP + csrw 2000, 0 + #NO_APP + lw s0,0(sp) + addi sp,sp,4 + jr ra +.L13: + slli t0,a4,2 + add t0,a0,t0 + lw s0,0(t0) + addi a4,a4,1 + sll s0,s0,t2 + sw s0,0(t0) +.L11: + andi t0,a4,0xff + bgtu a1,t0,.L13 + j .L14 +.L17: + li t2,32 + li a4,0 + sub t2,t2,a3 + j .L11 +.L15: + slli a3,a4,2 + add a3,a0,a3 + lw a3,0(a3) + #APP + csrw 3016, a3 + #NO_APP + addi a4,a4,1 + j .L12 + .size write_quad_by_word, .-write_quad_by_word diff --git a/applications/sdp/mspi/src/main.c b/applications/sdp/mspi/src/main.c new file mode 100644 index 000000000000..782d2f66d962 --- /dev/null +++ b/applications/sdp/mspi/src/main.c @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "hrt/hrt.h" + +#include +#include +#include + +#include +#include +#include + +#include + +#define MAX_DATA_LEN 256 + +#define XFER_COMMAND_IDX (0) +#define XFER_ADDRESS_IDX (1) +#define XFER_DATA_IDX (2) + +#define HRT_IRQ_PRIORITY 2 +#define HRT_VEVIF_IDX_WRITE_SINGLE 17 +#define HRT_VEVIF_IDX_WRITE_QUAD 18 + +/* how many words are needed for given amount of bytes.*/ +#define WORDS_FOR_BYTES(bytes) ((bytes - 1) / 4 + 1) + +#define VEVIF_IRQN(vevif) VEVIF_IRQN_1(vevif) +#define VEVIF_IRQN_1(vevif) VPRCLIC_##vevif##_IRQn + +struct mspi_config { + uint8_t *data; + uint8_t data_len; + uint8_t word_size; +}; + +struct mspi_dev_config { + enum mspi_io_mode io_mode; + enum mspi_ce_polarity ce_polarity; + uint32_t read_cmd; + uint32_t write_cmd; + uint8_t cmd_length; /* Command length in bits. */ + uint8_t addr_length; /* Address length in bits. */ +}; + +static struct mspi_dev_config mspi_dev_configs; + +uint32_t data_buffer[MAX_DATA_LEN + 2]; + +volatile uint8_t counter_top = 4; +volatile uint8_t word_size; +volatile uint32_t *data_to_send; +volatile uint8_t data_len; +volatile uint8_t ce_hold; + +static struct ipc_ept ep; +static atomic_t ipc_atomic_sem = ATOMIC_INIT(0); + +void process_packet(const void *data, size_t len); + +static void ep_bound(void *priv) +{ + atomic_set_bit(&ipc_atomic_sem, NRFE_MSPI_EP_BOUNDED); +} + +static void ep_recv(const void *data, size_t len, void *priv) +{ + (void)priv; + + process_packet(data, len); +} + +static struct ipc_ept_cfg ep_cfg = { + .cb = { + .bound = ep_bound, + .received = ep_recv, + }, +}; + +void process_packet(const void *data, size_t len) +{ + (void)len; + nrfe_mspi_flpr_response_t response; + uint8_t *buffer = (uint8_t *)data; + uint8_t opcode = *buffer++; + + response.opcode = opcode; + + switch (opcode) { + case NRFE_MSPI_CONFIG_PINS: { + nrfe_mspi_pinctrl_soc_pin_t *pins_cfg = (nrfe_mspi_pinctrl_soc_pin_t *)buffer; + + for (uint8_t i = 0; i < NRFE_MSPI_PINS_MAX; i++) { + uint32_t psel = NRF_GET_PIN(pins_cfg->pin[i]); + uint32_t fun = NRF_GET_FUN(pins_cfg->pin[i]); + NRF_GPIO_Type *reg = nrf_gpio_pin_port_decode(&psel); + /* TODO: Process pinctrl config data */ + } + break; + } + case NRFE_MSPI_CONFIG_CTRL: { + struct mspi_cfg *cfg = (struct mspi_cfg *)buffer; + /* TODO: Process controller config data */ + break; + } + case NRFE_MSPI_CONFIG_DEV: { + struct mspi_dev_cfg *cfg = (struct mspi_dev_cfg *)buffer; + /* TODO: Process device config data */ + break; + } + case NRFE_MSPI_CONFIG_XFER: { + struct mspi_xfer *cfg = (struct mspi_xfer *)buffer; + /* TODO: Process xfer config data */ + break; + } + case NRFE_MSPI_TX: + case NRFE_MSPI_TXRX: { + struct mspi_xfer_packet *packet = (struct mspi_xfer_packet *)buffer; + + if (packet->dir == MSPI_RX) { + /* TODO: Process received data */ + } else if (packet->dir == MSPI_TX) { + /* TODO: Send data */ + } + break; + } + default: + response.opcode = NRFE_MSPI_WRONG_OPCODE; + break; + } + + ipc_service_send(&ep, (const void *)&response.opcode, sizeof(response)); +} + +void configure_clock(enum mspi_cpp_mode cpp_mode) +{ + nrf_vpr_csr_vio_config_t vio_config = { + .input_sel = 0, + .stop_cnt = 0, + }; + + nrf_vpr_csr_vio_dir_set(PIN_DIR_OUT_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + + switch (cpp_mode) { + case MSPI_CPP_MODE_0: { + vio_config.clk_polarity = 0; + nrf_vpr_csr_vio_out_set(PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + break; + } + case MSPI_CPP_MODE_1: { + vio_config.clk_polarity = 1; + nrf_vpr_csr_vio_out_set(PIN_OUT_LOW_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + break; + } + case MSPI_CPP_MODE_2: { + vio_config.clk_polarity = 1; + nrf_vpr_csr_vio_out_set(PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + break; + } + case MSPI_CPP_MODE_3: { + vio_config.clk_polarity = 0; + nrf_vpr_csr_vio_out_set(PIN_OUT_HIGH_MASK(VIO(NRFE_MSPI_SCK_PIN_NUMBER))); + break; + } + } + + nrf_vpr_csr_vio_config_set(&vio_config); +} + +void prepare_and_send_data(uint8_t *data, uint8_t data_length) +{ + /* this is here only temporarly to set command and address parameters */ + data_buffer[XFER_COMMAND_IDX] = 0xe5b326c1; + data_buffer[XFER_ADDRESS_IDX] = 0xaabbccdd; + + memcpy(&(data_buffer[2]), data, data_length); + + /* Send command */ + ce_hold = true; + word_size = mspi_dev_configs.cmd_length; + data_len = 1; + data_to_send = data_buffer; + + if (mspi_dev_configs.io_mode == MSPI_IO_MODE_QUAD) { + nrf_vpr_clic_int_pending_set(NRF_VPRCLIC, VEVIF_IRQN(HRT_VEVIF_IDX_WRITE_QUAD)); + } else { + nrf_vpr_clic_int_pending_set(NRF_VPRCLIC, VEVIF_IRQN(HRT_VEVIF_IDX_WRITE_SINGLE)); + } + + /* Send address */ + word_size = mspi_dev_configs.addr_length; + data_to_send = data_buffer + XFER_ADDRESS_IDX; + + if (mspi_dev_configs.io_mode == MSPI_IO_MODE_SINGLE || + mspi_dev_configs.io_mode == MSPI_IO_MODE_QUAD_1_1_4) { + nrf_vpr_clic_int_pending_set(NRF_VPRCLIC, VEVIF_IRQN(HRT_VEVIF_IDX_WRITE_SINGLE)); + } else { + nrf_vpr_clic_int_pending_set(NRF_VPRCLIC, VEVIF_IRQN(HRT_VEVIF_IDX_WRITE_QUAD)); + } + + /* Send data */ + ce_hold = false; + word_size = 32; + /* TODO: this system needs to be fixed as for now, + * there are problems when (data_length%4) != 0 + */ + data_len = WORDS_FOR_BYTES(data_length); + data_to_send = data_buffer + XFER_DATA_IDX; + + if (mspi_dev_configs.io_mode == MSPI_IO_MODE_SINGLE) { + nrf_vpr_clic_int_pending_set(NRF_VPRCLIC, VEVIF_IRQN(HRT_VEVIF_IDX_WRITE_SINGLE)); + } else { + nrf_vpr_clic_int_pending_set(NRF_VPRCLIC, VEVIF_IRQN(HRT_VEVIF_IDX_WRITE_QUAD)); + } +} + +__attribute__((interrupt)) void hrt_handler_write_single(void) +{ + bool ce_enable_state = (mspi_dev_configs.ce_polarity == MSPI_CE_ACTIVE_LOW); + + write_single_by_word(data_to_send, data_len, counter_top, word_size, ce_enable_state, + ce_hold); +} + +__attribute__((interrupt)) void hrt_handler_write_quad(void) +{ + bool ce_enable_state = (mspi_dev_configs.ce_polarity == MSPI_CE_ACTIVE_LOW); + + write_quad_by_word(data_to_send, data_len, counter_top, word_size, ce_enable_state, + ce_hold); +} + +static int backend_init(void) +{ + int ret = 0; + const struct device *ipc0_instance; + volatile uint32_t delay = 0; + +#if !defined(CONFIG_SYS_CLOCK_EXISTS) + /* Wait a little bit for IPC service to be ready on APP side */ + while (delay < 1000) { + delay++; + } +#endif + + ipc0_instance = DEVICE_DT_GET(DT_NODELABEL(ipc0)); + + ret = ipc_service_open_instance(ipc0_instance); + if ((ret < 0) && (ret != -EALREADY)) { + return ret; + } + + ret = ipc_service_register_endpoint(ipc0_instance, &ep, &ep_cfg); + if (ret < 0) { + return ret; + } + + /* Wait for endpoint to be bound */ + while (!atomic_test_and_clear_bit(&ipc_atomic_sem, NRFE_MSPI_EP_BOUNDED)) { + } + + return 0; +} + +int main(void) +{ + int ret = 0; + + ret = backend_init(); + if (ret < 0) { + return 0; + } + + IRQ_DIRECT_CONNECT(HRT_VEVIF_IDX_WRITE_SINGLE, HRT_IRQ_PRIORITY, hrt_handler_write_single, + 0); + nrf_vpr_clic_int_enable_set(NRF_VPRCLIC, VEVIF_IRQN(HRT_VEVIF_IDX_WRITE_SINGLE), true); + + IRQ_DIRECT_CONNECT(HRT_VEVIF_IDX_WRITE_QUAD, HRT_IRQ_PRIORITY, hrt_handler_write_quad, 0); + nrf_vpr_clic_int_enable_set(NRF_VPRCLIC, VEVIF_IRQN(HRT_VEVIF_IDX_WRITE_QUAD), true); + + nrf_vpr_csr_rtperiph_enable_set(true); + + mspi_dev_configs.ce_polarity = MSPI_CE_ACTIVE_LOW; + mspi_dev_configs.io_mode = MSPI_IO_MODE_SINGLE; + mspi_dev_configs.cmd_length = 32; + mspi_dev_configs.addr_length = 32; + + /* this is temporary sample data */ + uint8_t data[30] = {0xa3, 0x21, 0x54, 0x3a, 0x55, 0xa5, 0x45, 0x35, 0x34, 0x23, + 0xa3, 0xad, 0x97, 0xb2, 0x56, 0x54, 0x38, 0x88, 0x0, 0x5, + 0x33, 0x6, 0x34, 0x6, 0x57, 0x7, 0xbb, 0xba, 0xa3, 0xf6}; + prepare_and_send_data(data, 30); + + mspi_dev_configs.io_mode = MSPI_IO_MODE_QUAD; + + prepare_and_send_data(data, 30); + + while (true) { + k_cpu_idle(); + } + + return 0; +} diff --git a/applications/sdp/mspi/sysbuild.conf b/applications/sdp/mspi/sysbuild.conf new file mode 100644 index 000000000000..6408669a8474 --- /dev/null +++ b/applications/sdp/mspi/sysbuild.conf @@ -0,0 +1 @@ +SB_CONFIG_PARTITION_MANAGER=n diff --git a/cmake/sysbuild/sdp.cmake b/cmake/sysbuild/sdp.cmake index f440b687796b..3dc1d8c0f734 100644 --- a/cmake/sysbuild/sdp.cmake +++ b/cmake/sysbuild/sdp.cmake @@ -18,6 +18,9 @@ if(SB_CONFIG_SDP) set(snippet_name "sdp-gpio-icbmsg") endif() endif() + if(SB_CONFIG_SDP_MSPI) + set(snippet_name "sdp-mspi") + endif() sdp_apply_snippets(${snippet_name}) set(snippet_name) diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 3a178f3e1a93..685c1253103a 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(hw_cc3xx) if (CONFIG_MPSL AND NOT CONFIG_MPSL_FEM_ONLY) add_subdirectory(mpsl) endif() +add_subdirectory_ifdef(CONFIG_MSPI mspi) add_subdirectory_ifdef(CONFIG_NETWORKING net) add_subdirectory_ifdef(CONFIG_SENSOR sensor) add_subdirectory(serial) diff --git a/drivers/Kconfig b/drivers/Kconfig index 554b6a917249..3004a83b241e 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -16,6 +16,7 @@ if MPSL && !MPSL_FEM_ONLY rsource "mpsl/Kconfig" endif +rsource "mspi/Kconfig" rsource "net/Kconfig" rsource "sensor/Kconfig" rsource "serial/Kconfig" @@ -23,6 +24,7 @@ rsource "serial/Kconfig" config NRFE bool default y if GPIO_NRFE + default y if MSPI_NRFE # Temporary kconfig to include DPPI channel allocation for NRFE endmenu diff --git a/drivers/mspi/CMakeLists.txt b/drivers/mspi/CMakeLists.txt new file mode 100644 index 000000000000..9e00cb992f4c --- /dev/null +++ b/drivers/mspi/CMakeLists.txt @@ -0,0 +1,8 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library_amend() +zephyr_library_sources_ifdef(CONFIG_MSPI_NRFE mspi_nrfe.c) diff --git a/drivers/mspi/Kconfig b/drivers/mspi/Kconfig new file mode 100644 index 000000000000..a2f4c8979ff9 --- /dev/null +++ b/drivers/mspi/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +if MSPI + +rsource "Kconfig.nrfe" + +endif # MSPI diff --git a/drivers/mspi/Kconfig.nrfe b/drivers/mspi/Kconfig.nrfe new file mode 100644 index 000000000000..765f104ed2cc --- /dev/null +++ b/drivers/mspi/Kconfig.nrfe @@ -0,0 +1,28 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +# +# MSPI_NRFE Driver +# +menuconfig MSPI_NRFE + bool "SDP MSPI driver" + default y + depends on DT_HAS_NORDIC_NRFE_MSPI_CONTROLLER_ENABLED + select MBOX + select IPC_SERVICE + select IPC_SERVICE_BACKEND_ICMSG + help + Enable SDP MSPI driver. + +if MSPI_NRFE + +config MSPI_NRFE_INIT_PRIORITY + int "SDP MSPI init priority" + depends on MSPI_NRFE + default MSPI_INIT_PRIORITY + help + SDP MSPI driver device initialization priority. + SDP MSPI initialization depends on IPC initialization + which is done at the same init level and has init priority equal to 46. + +endif # MSPI_NRFE diff --git a/drivers/mspi/mspi_nrfe.c b/drivers/mspi/mspi_nrfe.c new file mode 100644 index 000000000000..41477d707eea --- /dev/null +++ b/drivers/mspi/mspi_nrfe.c @@ -0,0 +1,833 @@ +/* + * Copyright (c) 2024, Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#define DT_DRV_COMPAT nordic_nrfe_mspi_controller + +#include +#include +#include +#include +#include +#if !defined(CONFIG_MULTITHREADING) +#include +#endif +#include +LOG_MODULE_REGISTER(mspi_nrfe, CONFIG_MSPI_LOG_LEVEL); + +#include +#include + +#define MSPI_NRFE_NODE DT_DRV_INST(0) +#define MAX_TX_MSG_SIZE (DT_REG_SIZE(DT_NODELABEL(sram_tx))) +#define MAX_RX_MSG_SIZE (DT_REG_SIZE(DT_NODELABEL(sram_rx))) +#define CONFIG_TIMEOUT_MS 100 +#define EP_SEND_TIMEOUT_MS 10 + +#define SDP_MPSI_PINCTRL_DEV_CONFIG_INIT(node_id) \ + { \ + .reg = PINCTRL_REG_NONE, \ + .states = Z_PINCTRL_STATES_NAME(node_id), \ + .state_cnt = ARRAY_SIZE(Z_PINCTRL_STATES_NAME(node_id)), \ + } + +#define SDP_MSPI_PINCTRL_DT_DEFINE(node_id) \ + LISTIFY(DT_NUM_PINCTRL_STATES(node_id), Z_PINCTRL_STATE_PINS_DEFINE, (;), node_id); \ + Z_PINCTRL_STATES_DEFINE(node_id) \ + Z_PINCTRL_DEV_CONFIG_STATIC Z_PINCTRL_DEV_CONFIG_CONST struct pinctrl_dev_config \ + Z_PINCTRL_DEV_CONFIG_NAME(node_id) = SDP_MPSI_PINCTRL_DEV_CONFIG_INIT(node_id) + +SDP_MSPI_PINCTRL_DT_DEFINE(MSPI_NRFE_NODE); + +static struct ipc_ept ep; +static size_t ipc_received; +static uint8_t *ipc_receive_buffer; + +#if defined(CONFIG_MULTITHREADING) +static K_SEM_DEFINE(ipc_sem, 0, 1); +static K_SEM_DEFINE(ipc_sem_cfg, 0, 1); +static K_SEM_DEFINE(ipc_sem_xfer, 0, 1); +#else +static atomic_t ipc_atomic_sem = ATOMIC_INIT(0); +#endif + +#define MSPI_CONFIG \ + { \ + .channel_num = 0, \ + .op_mode = DT_PROP_OR(MSPI_NRFE_NODE, op_mode, MSPI_OP_MODE_CONTROLLER), \ + .duplex = DT_PROP_OR(MSPI_NRFE_NODE, duplex, MSPI_FULL_DUPLEX), \ + .dqs_support = DT_PROP_OR(MSPI_NRFE_NODE, dqs_support, false), \ + .num_periph = DT_CHILD_NUM(MSPI_NRFE_NODE), \ + .max_freq = DT_PROP(MSPI_NRFE_NODE, clock_frequency), \ + .re_init = true, \ + .sw_multi_periph = false, \ + } + +struct mspi_nrfe_data { + struct mspi_xfer xfer; + struct mspi_dev_id dev_id; + struct mspi_dev_cfg dev_cfg; +}; + +static struct mspi_nrfe_data dev_data; + +struct mspi_nrfe_config { + struct mspi_cfg mspicfg; + const struct pinctrl_dev_config *pcfg; +}; + +static const struct mspi_nrfe_config dev_config = { + .mspicfg = MSPI_CONFIG, + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), +}; + +static void ipc_recv_clbk(const void *data, size_t len); + +static void ep_bound(void *priv) +{ + ipc_received = 0; +#if defined(CONFIG_MULTITHREADING) + k_sem_give(&ipc_sem); +#else + atomic_set_bit(&ipc_atomic_sem, NRFE_MSPI_EP_BOUNDED); +#endif + LOG_DBG("Eb bounded"); +} + +static void ep_recv(const void *data, size_t len, void *priv) +{ + (void)priv; + + ipc_recv_clbk(data, len); +} + +static struct ipc_ept_cfg ep_cfg = { + .cb = { + .bound = ep_bound, + .received = ep_recv, + }, +}; + +/** + * @brief IPC receive callback function. + * + * This function is called by the IPC stack when a message is received from the + * other core. The function checks the opcode of the received message and takes + * appropriate action. + * + * @param data Pointer to the received message. + * @param len Length of the received message. + */ +static void ipc_recv_clbk(const void *data, size_t len) +{ + nrfe_mspi_flpr_response_t *response = (nrfe_mspi_flpr_response_t *)data; + + switch (response->opcode) { + case NRFE_MSPI_CONFIG_PINS: { +#if defined(CONFIG_MULTITHREADING) + k_sem_give(&ipc_sem_cfg); +#else + atomic_set_bit(&ipc_atomic_sem, NRFE_MSPI_CONFIG_PINS); +#endif + break; + } + case NRFE_MSPI_CONFIG_CTRL: { +#if defined(CONFIG_MULTITHREADING) + k_sem_give(&ipc_sem_cfg); +#else + atomic_set_bit(&ipc_atomic_sem, NRFE_MSPI_CONFIG_CTRL); +#endif + break; + } + case NRFE_MSPI_CONFIG_DEV: { +#if defined(CONFIG_MULTITHREADING) + k_sem_give(&ipc_sem_cfg); +#else + atomic_set_bit(&ipc_atomic_sem, NRFE_MSPI_CONFIG_DEV); +#endif + break; + } + case NRFE_MSPI_CONFIG_XFER: { +#if defined(CONFIG_MULTITHREADING) + k_sem_give(&ipc_sem_cfg); +#else + atomic_set_bit(&ipc_atomic_sem, NRFE_MSPI_CONFIG_XFER); +#endif + break; + } + case NRFE_MSPI_TX: { +#if defined(CONFIG_MULTITHREADING) + k_sem_give(&ipc_sem_xfer); +#else + atomic_set_bit(&ipc_atomic_sem, NRFE_MSPI_TX); +#endif + break; + } + case NRFE_MSPI_TXRX: { + if (len) { + ipc_received = len - 1; + ipc_receive_buffer = (uint8_t *)&response->data; + } +#if defined(CONFIG_MULTITHREADING) + k_sem_give(&ipc_sem_xfer); +#else + atomic_set_bit(&ipc_atomic_sem, NRFE_MSPI_TXRX); +#endif + break; + } + default: { + LOG_ERR("Invalid response opcode: %d", response->opcode); + break; + } + } + + LOG_DBG("Received msg with opcode: %d", response->opcode); +} + +/** + * @brief Send data to the flpr with the given opcode. + * + * @param opcode The opcode of the message to send. + * @param data The data to send. + * @param len The length of the data to send. + * + * @return 0 on success, -ENOMEM if there is no space in the buffer, + * -ETIMEDOUT if the transfer timed out. + */ +static int mspi_ipc_data_send(enum nrfe_mspi_opcode opcode, const void *data, size_t len) +{ + int rc; + + LOG_DBG("Sending msg with opcode: %d", (uint8_t)opcode); +#if defined(CONFIG_SYS_CLOCK_EXISTS) + uint32_t start = k_uptime_get_32(); +#else + uint32_t repeat = EP_SEND_TIMEOUT_MS; +#endif +#if !defined(CONFIG_MULTITHREADING) + atomic_clear_bit(&ipc_atomic_sem, opcode); +#endif + + do { + rc = ipc_service_send(&ep, data, len); +#if defined(CONFIG_SYS_CLOCK_EXISTS) + if ((k_uptime_get_32() - start) > EP_SEND_TIMEOUT_MS) { +#else + repeat--; + if ((rc < 0) && (repeat == 0)) { +#endif + break; + }; + } while (rc == -ENOMEM); /* No space in the buffer. Retry. */ + + return rc; +} + +/** + * @brief Waits for a response from the peer with the given opcode. + * + * @param opcode The opcode of the response to wait for. + * @param timeout The timeout in milliseconds. + * + * @return 0 on success, -ETIMEDOUT if the operation timed out. + */ +static int nrfe_mspi_wait_for_response(enum nrfe_mspi_opcode opcode, uint32_t timeout) +{ +#if defined(CONFIG_MULTITHREADING) + int ret = 0; + + switch (opcode) { + case NRFE_MSPI_CONFIG_PINS: + case NRFE_MSPI_CONFIG_CTRL: + case NRFE_MSPI_CONFIG_DEV: + case NRFE_MSPI_CONFIG_XFER: { + ret = k_sem_take(&ipc_sem_cfg, K_MSEC(timeout)); + break; + } + case NRFE_MSPI_TX: + case NRFE_MSPI_TXRX: + ret = k_sem_take(&ipc_sem_xfer, K_MSEC(timeout)); + break; + default: + break; + } + + if (ret < 0) { + return -ETIMEDOUT; + } +#else +#if defined(CONFIG_SYS_CLOCK_EXISTS) + uint32_t start = k_uptime_get_32(); +#else + uint32_t repeat = timeout * 1000; /* Convert ms to us */ +#endif + while (!atomic_test_and_clear_bit(&ipc_atomic_sem, opcode)) { +#if defined(CONFIG_SYS_CLOCK_EXISTS) + if ((k_uptime_get_32() - start) > timeout) { + return -ETIMEDOUT; + }; +#else + repeat--; + if (!repeat) { + return -ETIMEDOUT; + }; +#endif + k_sleep(K_USEC(1)); + } +#endif /* CONFIG_MULTITHREADING */ + return 0; +} + +/** + * @brief Send a data struct to the FLPR core using the IPC service. + * + * The function sends a data structure to the FLPR core, + * inserting a byte at the beginning responsible for the opcode. + * + * @param opcode The NRFE MSPI opcode. + * @param data The data to send. + * @param len The length of the data to send. + * + * @return 0 on success, negative errno code on failure. + */ +static int send_with_opcode(enum nrfe_mspi_opcode opcode, const void *data, size_t len) +{ + uint8_t buffer[len + 1]; + + buffer[0] = (uint8_t)opcode; + memcpy(&buffer[1], data, len); + + return mspi_ipc_data_send(opcode, buffer, sizeof(buffer)); +} + +/** + * @brief Send a configuration struct to the FLPR core using the IPC service. + * + * @param opcode The configuration packet opcode to send. + * @param config The data to send. + * @param len The length of the data to send. + * + * @return 0 on success, negative errno code on failure. + */ +static int send_config(enum nrfe_mspi_opcode opcode, const void *config, size_t len) +{ + int rc; + + rc = send_with_opcode(opcode, config, len); + if (rc < 0) { + LOG_ERR("%d: Configuration send failed: %d", __LINE__, rc); + return rc; + } + + rc = nrfe_mspi_wait_for_response(opcode, CONFIG_TIMEOUT_MS); + if (rc < 0) { + LOG_ERR("%d: FLPR config: %d response timeout: %d!", __LINE__, opcode, rc); + } + + return rc; +} + +/** + * @brief Configures the MSPI controller based on the provided spec. + * + * This function configures the MSPI controller according to the provided + * spec. It checks if the spec is valid and sends the configuration to + * the FLPR. + * + * @param spec The MSPI spec to use for configuration. + * + * @return 0 on success, negative errno code on failure. + */ +static int api_config(const struct mspi_dt_spec *spec) +{ + int ret; + const struct mspi_cfg *config = &spec->config; + const struct mspi_nrfe_config *drv_cfg = spec->bus->config; + + if (config->op_mode != MSPI_OP_MODE_CONTROLLER) { + LOG_ERR("%u, only support MSPI controller mode.", __LINE__); + return -ENOTSUP; + } + + if (config->dqs_support) { + LOG_ERR("%u, only support non-DQS mode.", __LINE__); + return -ENOTSUP; + } + + if (config->max_freq > drv_cfg->mspicfg.max_freq) { + LOG_ERR("%u, max_freq too large.", __LINE__); + return -ENOTSUP; + } + + /* Create pinout configuration */ + uint8_t state_id; + nrfe_mspi_pinctrl_soc_pin_t pins_cfg; + + for (state_id = 0; state_id < drv_cfg->pcfg->state_cnt; state_id++) { + if (drv_cfg->pcfg->states[state_id].id == PINCTRL_STATE_DEFAULT) { + break; + } + } + + if (drv_cfg->pcfg->states[state_id].pin_cnt > NRFE_MSPI_PINS_MAX) { + LOG_ERR("%u, too much pins defined. Max: %d", __LINE__, NRFE_MSPI_PINS_MAX); + return -ENOTSUP; + } + + if (drv_cfg->pcfg->states[state_id].id != PINCTRL_STATE_DEFAULT) { + LOG_ERR("%u, pins default state not found.", __LINE__); + return -ENOTSUP; + } + + for (uint8_t i = 0; i < drv_cfg->pcfg->states[state_id].pin_cnt; i++) { + pins_cfg.pin[i] = drv_cfg->pcfg->states[state_id].pins[i]; + } + + /* Send pinout configuration to FLPR */ + ret = send_config(NRFE_MSPI_CONFIG_PINS, (const void *)pins_cfg.pin, + sizeof(pins_cfg)); + if (ret < 0) { + return ret; + } + + /* Send controller configuration to FLPR */ + return send_config(NRFE_MSPI_CONFIG_CTRL, (const void *)config, + sizeof(struct mspi_cfg)); +} + +static int check_io_mode(enum mspi_io_mode io_mode) +{ + switch (io_mode) { + case MSPI_IO_MODE_SINGLE: + case MSPI_IO_MODE_QUAD: + case MSPI_IO_MODE_QUAD_1_1_4: + case MSPI_IO_MODE_QUAD_1_4_4: + break; + default: + LOG_ERR("IO mode %d not supported", io_mode); + return -ENOTSUP; + } + + return 0; +} + +/** + * @brief Configure a device on the MSPI bus. + * + * @param dev MSPI controller device. + * @param dev_id Device ID to configure. + * @param param_mask Bitmask of parameters to configure. + * @param cfg Device configuration. + * + * @return 0 on success, negative errno code on failure. + */ +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_nrfe_config *drv_cfg = dev->config; + struct mspi_nrfe_data *drv_data = dev->data; + int rc; + + 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_FREQUENCY) { + if (cfg->freq > drv_cfg->mspicfg.max_freq) { + LOG_ERR("Invalid frequency: %u, MAX: %u", cfg->freq, + drv_cfg->mspicfg.max_freq); + return -EINVAL; + } + } + + if (param_mask & MSPI_DEVICE_CONFIG_IO_MODE) { + rc = check_io_mode(cfg->io_mode); + if (rc < 0) { + return rc; + } + } + + if (param_mask & MSPI_DEVICE_CONFIG_DATA_RATE) { + 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) { + if (cfg->dqs_enable) { + LOG_ERR("DQS signal is not supported."); + return -ENOTSUP; + } + } + + memcpy((void *)&drv_data->dev_cfg, (void *)cfg, sizeof(drv_data->dev_cfg)); + drv_data->dev_id = *dev_id; + + return send_config(NRFE_MSPI_CONFIG_DEV, (void *)cfg, sizeof(struct mspi_dev_cfg)); +} + +static int api_get_channel_status(const struct device *dev, uint8_t ch) +{ + return 0; +} + +/** + * @brief Send a transfer packet to the eMSPI controller. + * + * @param dev eMSPI controller device + * @param packet Transfer packet containing the data to be transferred + * @param timeout Timeout in milliseconds + * + * @retval 0 on success + * @retval -ENOTSUP if the packet is not supported + * @retval -ENOMEM if there is no space in the buffer + * @retval -ETIMEDOUT if the transfer timed out + */ +static int xfer_packet(struct mspi_xfer_packet *packet, uint32_t timeout) +{ + int rc; + uint32_t struct_size = sizeof(struct mspi_xfer_packet); + uint32_t len = struct_size + packet->num_bytes + 1; + uint8_t buffer[len]; + enum nrfe_mspi_opcode opcode = (packet->dir == MSPI_RX) ? NRFE_MSPI_TXRX : NRFE_MSPI_TX; + + buffer[0] = (uint8_t)opcode; + memcpy((void *)&buffer[1], (void *)packet, struct_size); + memcpy((void *)(&buffer[1] + struct_size), (void *)packet->data_buf, packet->num_bytes); + + rc = mspi_ipc_data_send(opcode, buffer, len); + if (rc < 0) { + LOG_ERR("%d: Packet transfer error: %d", __LINE__, rc); + } + + rc = nrfe_mspi_wait_for_response(opcode, timeout); + if (rc < 0) { + LOG_ERR("%d: FLPR Xfer response timeout: %d", __LINE__, rc); + return rc; + } + + /* Wait for the transfer to complete and receive data. */ + if (packet->dir == MSPI_RX) { + if ((ipc_receive_buffer != NULL) && (ipc_received > 0)) { + memcpy((void *)packet->data_buf, (void *)ipc_receive_buffer, ipc_received); + packet->num_bytes = ipc_received; + + /* Clear the receive buffer pointer and size */ + ipc_receive_buffer = NULL; + ipc_received = 0; + } + } + + return rc; +} + +/** + * @brief Initiates the transfer of the next packet in an MSPI transaction. + * + * This function prepares and starts the transmission of the next packet + * specified in the MSPI transfer configuration. It checks if the packet + * size is within the allowable limits before initiating the transfer. + * + * @param xfer Pointer to the mspi_xfer structure. + * @param packets_done Number of packets that have already been processed. + * + * @retval 0 If the packet transfer is successfully started. + * @retval -EINVAL If the packet size exceeds the maximum transmission size. + */ +static int start_next_packet(struct mspi_xfer *xfer, uint32_t packets_done) +{ + struct mspi_xfer_packet *packet = (struct mspi_xfer_packet *)&xfer->packets[packets_done]; + + if (packet->num_bytes >= MAX_TX_MSG_SIZE) { + LOG_ERR("Packet size to large: %u. Increase SRAM data region.", packet->num_bytes); + return -EINVAL; + } + + return xfer_packet(packet, xfer->timeout); +} + +/** + * @brief Send a multi-packet transfer request to the host. + * + * This function sends a multi-packet transfer request to the host and waits + * for the host to complete the transfer. This function does not support + * asynchronous transfers. + * + * @param dev Pointer to the device structure. + * @param dev_id Pointer to the device identification structure. + * @param req Pointer to the xfer structure. + * + * @retval 0 If successful. + * @retval -ENOTSUP If asynchronous transfers are requested. + * @retval -EIO If an I/O error occurs. + */ +static int api_transceive(const struct device *dev, const struct mspi_dev_id *dev_id, + const struct mspi_xfer *req) +{ + (void)dev_id; + struct mspi_nrfe_data *drv_data = dev->data; + uint32_t packets_done = 0; + int rc; + + /* TODO: add support for asynchronous transfers */ + if (req->async) { + return -ENOTSUP; + } + + if (req->num_packet == 0 || !req->packets || + req->timeout > CONFIG_MSPI_COMPLETION_TIMEOUT_TOLERANCE) { + return -EFAULT; + } + + drv_data->xfer = *req; + + rc = send_config(NRFE_MSPI_CONFIG_XFER, (void *)&drv_data->xfer, sizeof(struct mspi_xfer)); + if (rc < 0) { + LOG_ERR("Send xfer config error: %d", rc); + return rc; + } + + while (packets_done < drv_data->xfer.num_packet) { + rc = start_next_packet(&drv_data->xfer, packets_done); + if (rc < 0) { + LOG_ERR("Start next packet error: %d", rc); + return rc; + } + ++packets_done; + } + + return 0; +} + +#if CONFIG_PM_DEVICE +/** + * @brief Callback function to handle power management actions. + * + * This function is responsible for handling power management actions + * such as suspend and resume for the given device. It performs the + * necessary operations when the device is requested to transition + * between different power states. + * + * @param dev Pointer to the device structure. + * @param action The power management action to be performed. + * + * @retval 0 If successful. + * @retval -ENOTSUP If the action is not supported. + */ +static int dev_pm_action_cb(const struct device *dev, enum pm_device_action action) +{ + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + /* TODO: Handle PM suspend state */ + break; + case PM_DEVICE_ACTION_RESUME: + /* TODO: Handle PM resume state */ + break; + default: + return -ENOTSUP; + } + + return 0; +} +#endif + +/** + * @brief Check the configuration of pins for the MSPI controller. + * + * This function verifies that the specified pins in the pinctrl state match the + * expected MSPI configuration for the given device. It checks that the pins are + * in the correct GPIO port and have the correct pin numbers for each MSPI + * function (e.g., CS0, SCK, DQ0, etc.). If any pin is incorrectly configured, + * the function logs an error and returns an error code. + * + * @param config Pointer to the pinctrl device configuration structure. + * @param id Identifier of the pinctrl state to check. + * + * @retval 0 If the pin configuration is correct. + * @retval -ENOTSUP If the pin is not supported or in the wrong port. + */ +static int check_pins_config(const struct pinctrl_dev_config *config, uint8_t id) +{ + int ret; + const struct pinctrl_state *state; + const pinctrl_soc_pin_t *pins; + uint8_t pin_cnt; + + ret = pinctrl_lookup_state(config, id, &state); + if (ret < 0) { + return ret; + } + + pins = state->pins; + pin_cnt = state->pin_cnt; + + for (uint8_t i = 0U; i < pin_cnt; i++) { + uint32_t psel = NRF_GET_PIN(pins[i]); + uint32_t fun = NRF_GET_FUN(pins[i]); + NRF_GPIO_Type *reg = nrf_gpio_pin_port_decode(&psel); + + if (reg != (NRF_GPIO_Type *)(NRFX_CONCAT(NRF_P, NRFE_MSPI_PORT_NUMBER))) { + LOG_ERR("Wrong GPIO port number. Needs to be port %d!", + NRFE_MSPI_PORT_NUMBER); + return -ENOTSUP; + } else if (reg == NULL) { + LOG_ERR("Cannot obtain GPIO register for GPIO pin %d!", psel); + return -ENOTSUP; + } + + switch (fun) { + case NRF_FUN_SDP_MSPI_CS0: { + if (psel < NRFE_MSPI_CS0_PIN_NUMBER) { + LOG_ERR("Wrong CS0 GPIO pin number: %d! Needs to be >= %d.", psel, + NRFE_MSPI_CS0_PIN_NUMBER); + return -ENOTSUP; + } + break; + } + case NRF_FUN_SDP_MSPI_SCK: { + if (psel != NRFE_MSPI_SCK_PIN_NUMBER) { + LOG_ERR("Wrong SCK GPIO pin number: %d! Needs to be = %d.", psel, + NRFE_MSPI_SCK_PIN_NUMBER); + return -ENOTSUP; + } + break; + } + case NRF_FUN_SDP_MSPI_DQ0: + if (psel != NRFE_MSPI_DQ0_PIN_NUMBER) { + LOG_ERR("Wrong DQ0 GPIO pin number: %d! Needs to be = %d.", psel, + NRFE_MSPI_DQ0_PIN_NUMBER); + return -ENOTSUP; + } + break; + case NRF_FUN_SDP_MSPI_DQ1: + if (psel != NRFE_MSPI_DQ1_PIN_NUMBER) { + LOG_ERR("Wrong DQ1 GPIO pin number: %d! Needs to be = %d.", psel, + NRFE_MSPI_DQ1_PIN_NUMBER); + return -ENOTSUP; + } + break; + case NRF_FUN_SDP_MSPI_DQ2: + if (psel != NRFE_MSPI_DQ2_PIN_NUMBER) { + LOG_ERR("Wrong DQ2 GPIO pin number: %d! Needs to be = %d.", psel, + NRFE_MSPI_DQ2_PIN_NUMBER); + return -ENOTSUP; + } + break; + case NRF_FUN_SDP_MSPI_DQ3: + if (psel != NRFE_MSPI_DQ3_PIN_NUMBER) { + LOG_ERR("Wrong DQ3 GPIO pin number: %d! Needs to be = %d.", psel, + NRFE_MSPI_DQ3_PIN_NUMBER); + return -ENOTSUP; + } + break; + case NRF_FUN_SDP_MSPI_CS1: /* TODO: Support more CS */ + case NRF_FUN_SDP_MSPI_CS2: + case NRF_FUN_SDP_MSPI_CS3: + case NRF_FUN_SDP_MSPI_CS4: + case NRF_FUN_SDP_MSPI_DQ4: /* Only single and QSPI modes are supported */ + case NRF_FUN_SDP_MSPI_DQ5: + case NRF_FUN_SDP_MSPI_DQ6: + case NRF_FUN_SDP_MSPI_DQ7: + default: + LOG_ERR("Not supported function: %d for GPIO pin number: %d!", fun, psel); + return -ENOTSUP; + } + } + + return 0; +} + +/** + * @brief Initialize the MSPI NRFE driver. + * + * This function initializes the MSPI NRFE driver. It is responsible for + * setting up the hardware and registering the IPC endpoint for the + * driver. + * + * @param dev Pointer to the device structure for the MSPI NRFE driver. + * + * @retval 0 If successful. + * @retval -errno If an error occurs. + */ +static int nrfe_mspi_init(const struct device *dev) +{ + int ret; + const struct device *ipc_instance = DEVICE_DT_GET(DT_NODELABEL(ipc0)); + const struct mspi_nrfe_config *drv_cfg = dev->config; + const struct mspi_dt_spec spec = { + .bus = dev, + .config = drv_cfg->mspicfg, + }; + const struct pinctrl_state *state = &drv_cfg->pcfg->states[0]; + + while (state < &drv_cfg->pcfg->states[drv_cfg->pcfg->state_cnt]) { + ret = check_pins_config(drv_cfg->pcfg, state->id); + if (ret < 0) { + return ret; + } + state++; + } + + ret = pinctrl_apply_state(drv_cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (ret) { + return ret; + } + + ret = ipc_service_open_instance(ipc_instance); + if ((ret < 0) && (ret != -EALREADY)) { + LOG_ERR("ipc_service_open_instance() failure"); + return ret; + } + + ret = ipc_service_register_endpoint(ipc_instance, &ep, &ep_cfg); + if (ret < 0) { + LOG_ERR("ipc_service_register_endpoint() failure"); + return ret; + } + + /* Wait for ep to be bounded */ +#if defined(CONFIG_MULTITHREADING) + k_sem_take(&ipc_sem, K_FOREVER); +#else + while (!atomic_test_and_clear_bit(&ipc_atomic_sem, NRFE_MSPI_EP_BOUNDED)) { + } +#endif + + ret = api_config(&spec); + if (ret < 0) { + return ret; + } + +#if CONFIG_PM_DEVICE + ret = pm_device_driver_init(dev, dev_pm_action_cb); + if (ret < 0) { + return ret; + } +#endif + return ret; +} + +static const struct mspi_driver_api drv_api = { + .config = api_config, + .dev_config = api_dev_config, + .get_channel_status = api_get_channel_status, + .transceive = api_transceive, +}; + +PM_DEVICE_DT_INST_DEFINE(0, dev_pm_action_cb); + +DEVICE_DT_INST_DEFINE(0, nrfe_mspi_init, PM_DEVICE_DT_INST_GET(0), &dev_data, &dev_config, + POST_KERNEL, CONFIG_MSPI_NRFE_INIT_PRIORITY, &drv_api); diff --git a/dts/bindings/mspi/nordic,nrfe-mspi-controller.yaml b/dts/bindings/mspi/nordic,nrfe-mspi-controller.yaml new file mode 100644 index 000000000000..999d10286b78 --- /dev/null +++ b/dts/bindings/mspi/nordic,nrfe-mspi-controller.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +description: Nordic SDP MSPI controller + +compatible: "nordic,nrfe-mspi-controller" + +include: [mspi-controller.yaml, pinctrl-device.yaml] diff --git a/include/drivers/mspi/nrfe_mspi.h b/include/drivers/mspi/nrfe_mspi.h new file mode 100644 index 000000000000..7be10d0c639e --- /dev/null +++ b/include/drivers/mspi/nrfe_mspi.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef DRIVERS_MSPI_NRFE_MSPI_H +#define DRIVERS_MSPI_NRFE_MSPI_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef CONFIG_SOC_NRF54L15 +#define NRFE_MSPI_PORT_NUMBER 2 /* Physical port number */ +#define NRFE_MSPI_SCK_PIN_NUMBER 1 /* Physical pins number on port 2 */ +#define NRFE_MSPI_DQ0_PIN_NUMBER 2 +#define NRFE_MSPI_DQ1_PIN_NUMBER 4 +#define NRFE_MSPI_DQ2_PIN_NUMBER 3 +#define NRFE_MSPI_DQ3_PIN_NUMBER 0 +#define NRFE_MSPI_CS0_PIN_NUMBER 5 +#define NRFE_MSPI_PINS_MAX 6 + +#define NRFE_MSPI_SCK_PIN_NUMBER_VIO 0 /* FLPR VIO SCLK pin number */ +#define NRFE_MSPI_DQ0_PIN_NUMBER_VIO 1 +#define NRFE_MSPI_DQ1_PIN_NUMBER_VIO 2 +#define NRFE_MSPI_DQ2_PIN_NUMBER_VIO 3 +#define NRFE_MSPI_DQ3_PIN_NUMBER_VIO 4 +#define NRFE_MSPI_CS0_PIN_NUMBER_VIO 5 + +#define VIO(_pin_) _pin_##_VIO +#else +#error "Unsupported SoC for SDP MSPI" +#endif + +#define NRFE_MSPI_MAX_CE_PINS_COUNT 5 /* Ex. CE0 CE1 CE2 CE3 CE4 */ + +/** @brief eMSPI opcodes. */ +enum nrfe_mspi_opcode { + NRFE_MSPI_EP_BOUNDED = 0, + NRFE_MSPI_CONFIG_PINS, + NRFE_MSPI_CONFIG_CTRL, /* struct mspi_cfg */ + NRFE_MSPI_CONFIG_DEV, /* struct mspi_dev_cfg */ + NRFE_MSPI_CONFIG_XFER, /* struct mspi_xfer */ + NRFE_MSPI_TX, + NRFE_MSPI_TXRX, + NRFE_MSPI_WRONG_OPCODE, + NRFE_MSPI_ALL_OPCODES = NRFE_MSPI_WRONG_OPCODE, +}; + +typedef struct __packed { + pinctrl_soc_pin_t pin[NRFE_MSPI_PINS_MAX]; +} nrfe_mspi_pinctrl_soc_pin_t; + +typedef struct __packed { + uint8_t opcode; /* nrfe_mspi_opcode */ + uint8_t data; +} nrfe_mspi_flpr_response_t; + +#ifdef __cplusplus +} +#endif + +#endif /* DRIVERS_MSPI_NRFE_MSPI_H */ diff --git a/scripts/twister/alt/zephyr/tests/drivers/mspi/api/testcase.yaml b/scripts/twister/alt/zephyr/tests/drivers/mspi/api/testcase.yaml new file mode 100644 index 000000000000..1e0db0ee313b --- /dev/null +++ b/scripts/twister/alt/zephyr/tests/drivers/mspi/api/testcase.yaml @@ -0,0 +1,30 @@ +tests: + drivers.mspi.api: + tags: + - drivers + - mspi + - api + filter: dt_compat_enabled("zephyr,mspi-emul-controller") or + dt_compat_enabled("ambiq,mspi-controller") + harness: ztest + platform_allow: + - native_sim + - apollo3p_evb + integration_platforms: + - native_sim + + drivers.mspi.api.emspi: + tags: + - drivers + - mspi + - api + harness: ztest + platform_allow: + - nrf54l15dk/nrf54l15/cpuapp + integration_platforms: + - nrf54l15dk/nrf54l15/cpuapp + extra_args: + - SB_CONFIG_VPR_LAUNCHER=n + - SB_CONFIG_PARTITION_MANAGER=n + - SB_CONFIG_SDP=y + - SB_CONFIG_SDP_MSPI=y diff --git a/snippets/sdp/mspi/app.conf b/snippets/sdp/mspi/app.conf new file mode 100644 index 000000000000..0bf532300b85 --- /dev/null +++ b/snippets/sdp/mspi/app.conf @@ -0,0 +1,4 @@ +CONFIG_MSPI=y +CONFIG_MBOX=y +CONFIG_IPC_SERVICE=y +CONFIG_IPC_SERVICE_BACKEND_ICMSG=y diff --git a/snippets/sdp/mspi/flpr.conf b/snippets/sdp/mspi/flpr.conf new file mode 100644 index 000000000000..5db8be05d329 --- /dev/null +++ b/snippets/sdp/mspi/flpr.conf @@ -0,0 +1,3 @@ +CONFIG_MBOX=y +CONFIG_IPC_SERVICE=y +CONFIG_IPC_SERVICE_BACKEND_ICMSG=y diff --git a/snippets/sdp/mspi/sdp-mspi-app.overlay b/snippets/sdp/mspi/sdp-mspi-app.overlay new file mode 100644 index 000000000000..e210e5b108ea --- /dev/null +++ b/snippets/sdp/mspi/sdp-mspi-app.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +&cpuflpr_vpr { + status = "okay"; + + sdp_mspi: sdp_mspi { + compatible = "nordic,nrfe-mspi-controller"; + #address-cells = <1>; + #size-cells = <0>; + }; +}; diff --git a/snippets/sdp/mspi/snippet.yml b/snippets/sdp/mspi/snippet.yml new file mode 100644 index 000000000000..76d30ab6949c --- /dev/null +++ b/snippets/sdp/mspi/snippet.yml @@ -0,0 +1,19 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +name: sdp-mspi + +boards: + /.*/cpuapp/: + append: + EXTRA_DTC_OVERLAY_FILE: sdp-mspi-app.overlay + EXTRA_CONF_FILE: app.conf + /.*/cpuflpr/: + append: + EXTRA_CONF_FILE: flpr.conf + /.*/nrf54l15/cpuapp/: + append: + EXTRA_DTC_OVERLAY_FILE: soc/nrf54l15_cpuapp.overlay diff --git a/snippets/sdp/mspi/soc/nrf54l15_cpuapp.overlay b/snippets/sdp/mspi/soc/nrf54l15_cpuapp.overlay new file mode 100644 index 000000000000..07f040a1fd9a --- /dev/null +++ b/snippets/sdp/mspi/soc/nrf54l15_cpuapp.overlay @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + soc { + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + + cpuflpr_code_partition: image@17a000 { + reg = <0x17a000 DT_SIZE_K(12)>; + }; + + sram_rx: memory@2003c000 { + reg = <0x2003c000 0x0800>; + }; + + sram_tx: memory@2003c800 { + reg = <0x2003c800 0x0800>; + }; + }; + + + cpuflpr_sram_code_data: memory@2003d000 { + compatible = "mmio-sram"; + reg = <0x2003d000 DT_SIZE_K(12)>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x2003d000 0x3000>; + }; + }; + + ipc { + ipc0: ipc0 { + compatible = "zephyr,ipc-icmsg"; + tx-region = <&sram_tx>; + rx-region = <&sram_rx>; + mboxes = <&cpuapp_vevif_rx 20>, <&cpuapp_vevif_tx 16>; + mbox-names = "rx", "tx"; + status = "okay"; + }; + }; +}; + +&cpuapp_rram { + reg = <0x0 DT_SIZE_K(1512)>; +}; + +&cpuapp_sram { + reg = <0x20000000 DT_SIZE_K(244)>; + ranges = <0x0 0x20000000 0x3d000>; +}; + +&cpuflpr_vpr { + execution-memory = <&cpuflpr_sram_code_data>; + source-memory = <&cpuflpr_code_partition>; +}; + +&gpio2 { + status = "okay"; +}; + +&cpuapp_vevif_rx { + status = "okay"; +}; + +&cpuapp_vevif_tx { + status = "okay"; +}; + +&pinctrl { + /omit-if-no-ref/ sdp_mspi_default: sdp_mspi_default { + group1 { + psels = , + , + , + , + , + ; + nordic,drive-mode = ; + }; + }; + /omit-if-no-ref/ sdp_mspi_sleep: sdp_mspi_sleep { + group1 { + psels = , + , + , + , + , + ; + low-power-enable; + }; + }; +}; + +&sdp_mspi { + clock-frequency = ; + pinctrl-0 = <&sdp_mspi_default>; + pinctrl-1 = <&sdp_mspi_sleep>; + pinctrl-names = "default", "sleep"; + status = "okay"; +}; diff --git a/sysbuild/Kconfig.sdp b/sysbuild/Kconfig.sdp index 7529d937e07f..a84fbdc745f4 100644 --- a/sysbuild/Kconfig.sdp +++ b/sysbuild/Kconfig.sdp @@ -12,6 +12,9 @@ if SDP config SDP_GPIO bool "SDP GPIO application" +config SDP_MSPI + bool "SDP MSPI application" + if SDP_GPIO choice SDP_GPIO_BACKEND diff --git a/sysbuild/sdp.cmake b/sysbuild/sdp.cmake index 2167bfac5655..34d779ac1782 100644 --- a/sysbuild/sdp.cmake +++ b/sysbuild/sdp.cmake @@ -2,18 +2,29 @@ # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# If it is enabled, include the SDP GPIO application in the build -if(SB_CONFIG_SDP_GPIO) +if(SB_CONFIG_SDP) # Extract SoC name from related variables string(REPLACE "/" ";" split_board_qualifiers "${BOARD_QUALIFIERS}") list(GET split_board_qualifiers 1 target_soc) set(board_target_flpr "${BOARD}/${target_soc}/cpuflpr") set(target_soc) + # Select the SDP application + if(SB_CONFIG_SDP_GPIO) + set(sdp_app_dir "${ZEPHYR_NRF_MODULE_DIR}/applications/sdp/gpio") + elseif(SB_CONFIG_SDP_MSPI) + set(sdp_app_dir "${ZEPHYR_NRF_MODULE_DIR}/applications/sdp/mspi") + else() + message(FATAL_ERROR "Unknown SDP application type") + endif() + + # Include the SDP application in the build ExternalZephyrProject_Add( APPLICATION sdp - SOURCE_DIR ${ZEPHYR_NRF_MODULE_DIR}/applications/sdp/gpio + SOURCE_DIR ${sdp_app_dir} BOARD ${board_target_flpr} BOARD_REVISION ${BOARD_REVISION} ) + set(sdp_app_dir) + set(board_target_flpr) endif() diff --git a/west.yml b/west.yml index 7dcd1918391d..fb96c55f1f5c 100644 --- a/west.yml +++ b/west.yml @@ -69,7 +69,7 @@ manifest: # https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/modules.html - name: zephyr repo-path: sdk-zephyr - revision: b4762701d5b8d1a2e6924a21cbbe64e8fdebb1eb + revision: pull/2260/head import: # In addition to the zephyr repository itself, NCS also # imports the contents of zephyr/west.yml at the above