From 69c14e37ac9193457c8d22acff91b651a8199cbe Mon Sep 17 00:00:00 2001 From: Swift Tian Date: Mon, 21 Apr 2025 12:53:20 +0800 Subject: [PATCH] drivers: mspi: add ambiq mspi timing scan utility The utility may be used during development stage to get ambiq platform specific timing parameters for mspi devices. Signed-off-by: Swift Tian --- boards/ambiq/apollo510_evb/apollo510_evb.dts | 4 +- drivers/memc/memc_mspi_aps_z8.c | 2 + drivers/mspi/CMakeLists.txt | 1 + drivers/mspi/Kconfig.ambiq | 25 + drivers/mspi/mspi_ambiq.h | 88 ++-- drivers/mspi/mspi_ambiq_timing_scan.c | 497 ++++++++++++++++++ dts/bindings/mspi/ambiq,mspi-device.yaml | 3 +- .../mspi/mspi_timing_scan/CMakeLists.txt | 13 + .../drivers/mspi/mspi_timing_scan/README.rst | 46 ++ .../boards/apollo510_evb.conf | 10 + .../boards/apollo510_evb.overlay | 31 ++ .../drivers/mspi/mspi_timing_scan/prj.conf | 2 + .../drivers/mspi/mspi_timing_scan/sample.yaml | 47 ++ .../drivers/mspi/mspi_timing_scan/src/main.c | 85 +++ 14 files changed, 816 insertions(+), 38 deletions(-) create mode 100644 drivers/mspi/mspi_ambiq_timing_scan.c create mode 100644 samples/drivers/mspi/mspi_timing_scan/CMakeLists.txt create mode 100644 samples/drivers/mspi/mspi_timing_scan/README.rst create mode 100644 samples/drivers/mspi/mspi_timing_scan/boards/apollo510_evb.conf create mode 100644 samples/drivers/mspi/mspi_timing_scan/boards/apollo510_evb.overlay create mode 100644 samples/drivers/mspi/mspi_timing_scan/prj.conf create mode 100644 samples/drivers/mspi/mspi_timing_scan/sample.yaml create mode 100644 samples/drivers/mspi/mspi_timing_scan/src/main.c diff --git a/boards/ambiq/apollo510_evb/apollo510_evb.dts b/boards/ambiq/apollo510_evb/apollo510_evb.dts index e1ee43cc427..3ed5e7859cd 100644 --- a/boards/ambiq/apollo510_evb/apollo510_evb.dts +++ b/boards/ambiq/apollo510_evb/apollo510_evb.dts @@ -156,7 +156,7 @@ xip-config = <1 0 DT_SIZE_M(64) 0>; ce-break-config = <1024 4>; ambiq,timing-config-mask = <0x62>; - ambiq,timing-config = <8 7 1 0 0 3 10 0>; + ambiq,timing-config = <8 7 1 0 0 3 10>; zephyr,pm-device-runtime-auto; }; }; @@ -195,7 +195,7 @@ xip-config = <1 0 DT_SIZE_M(8) 0>; ce-break-config = <0 0>; ambiq,timing-config-mask = <0x62>; - ambiq,timing-config = <0 16 1 0 0 5 20 0>; + ambiq,timing-config = <0 16 1 0 0 5 20>; }; }; diff --git a/drivers/memc/memc_mspi_aps_z8.c b/drivers/memc/memc_mspi_aps_z8.c index fe1a1299b37..231cdcb4fef 100644 --- a/drivers/memc/memc_mspi_aps_z8.c +++ b/drivers/memc/memc_mspi_aps_z8.c @@ -48,6 +48,7 @@ struct memc_mspi_aps_z8_config { MSPI_SCRAMBLE_CFG_STRUCT_DECLARE(tar_scramble_cfg) MSPI_TIMING_CFG_STRUCT_DECLARE(tar_timing_cfg) MSPI_TIMING_PARAM_DECLARE(timing_cfg_mask) + MSPI_XIP_BASE_ADDR_DECLARE(xip_base_addr) bool sw_multi_periph; bool pm_dev_rt_auto; @@ -598,6 +599,7 @@ static int memc_mspi_aps_z8_init(const struct device *psram) tar_timing_cfg, MSPI_TIMING_CONFIG(n)) \ MSPI_OPTIONAL_CFG_STRUCT_INIT(CONFIG_MSPI_TIMING, \ timing_cfg_mask, MSPI_TIMING_CONFIG_MASK(n)) \ + MSPI_XIP_BASE_ADDR_INIT(xip_base_addr, DT_INST_BUS(n)) \ .sw_multi_periph = DT_PROP(DT_INST_BUS(n), software_multiperipheral), \ .pm_dev_rt_auto = DT_INST_PROP(n, zephyr_pm_device_runtime_auto) \ }; \ diff --git a/drivers/mspi/CMakeLists.txt b/drivers/mspi/CMakeLists.txt index 7f0a98ddb3b..cb2a985ec07 100644 --- a/drivers/mspi/CMakeLists.txt +++ b/drivers/mspi/CMakeLists.txt @@ -5,5 +5,6 @@ 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_AMBIQ_AP5 mspi_ambiq_ap5.c) +zephyr_library_sources_ifdef(CONFIG_MSPI_AMBIQ_TIMING_SCAN mspi_ambiq_timing_scan.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.ambiq b/drivers/mspi/Kconfig.ambiq index 2ca136d04ef..1deb9366bb6 100644 --- a/drivers/mspi/Kconfig.ambiq +++ b/drivers/mspi/Kconfig.ambiq @@ -46,4 +46,29 @@ config MSPI_AMBIQ_BUFF_ALIGNMENT help This option specifies the mspi buffer alignment +config MSPI_AMBIQ_TIMING_SCAN + bool "Turn on Ambiq timing scan utility" + depends on MSPI_TIMING + help + Enable timing scan will cost time and space during init + +if MSPI_AMBIQ_TIMING_SCAN + +config MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE + int "Timing scan buffer size in bytes" + default 131072 + help + This option specifies the memory buffer size used for timing scan, + carefully adjust this size between a few KBs to hundreds of KBs to achieve + balance between time and accurary + +config MSPI_AMBIQ_TIMING_SCAN_DATA_SIZE + int "Total data sizes in bytes used in timing scan" + default 131072 + help + This option specifies the total data size used in timing scan, + it must be divisible by MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE + +endif # MSPI_AMBIQ_TIMING_SCAN + endif # MSPI_AMBIQ_CONTROLLER diff --git a/drivers/mspi/mspi_ambiq.h b/drivers/mspi/mspi_ambiq.h index 8aabf0a1f6d..66e31f6e6de 100644 --- a/drivers/mspi/mspi_ambiq.h +++ b/drivers/mspi/mspi_ambiq.h @@ -27,6 +27,54 @@ #define MSPI_CQ_MAX_ENTRY MSPI0_CQCURIDX_CQCURIDX_Msk +enum mspi_ambiq_timing_param { + MSPI_AMBIQ_SET_WLC = BIT(0), + MSPI_AMBIQ_SET_RLC = BIT(1), + MSPI_AMBIQ_SET_TXNEG = BIT(2), + MSPI_AMBIQ_SET_RXNEG = BIT(3), + MSPI_AMBIQ_SET_RXCAP = BIT(4), + MSPI_AMBIQ_SET_TXDQSDLY = BIT(5), + MSPI_AMBIQ_SET_RXDQSDLY = BIT(6), +}; + +enum mspi_ambiq_timing_scan_type { + MSPI_AMBIQ_TIMING_SCAN_MEMC = 0, + MSPI_AMBIQ_TIMING_SCAN_FLASH, +}; + +struct mspi_ambiq_timing_scan_range { + int8_t rlc_start; + int8_t rlc_end; + int8_t txneg_start; + int8_t txneg_end; + int8_t rxneg_start; + int8_t rxneg_end; + int8_t rxcap_start; + int8_t rxcap_end; + int8_t txdqs_start; + int8_t txdqs_end; + int8_t rxdqs_start; + int8_t rxdqs_end; +}; + +struct mspi_ambiq_timing_cfg { + uint8_t ui8WriteLatency; + uint8_t ui8TurnAround; + bool bTxNeg; + bool bRxNeg; + bool bRxCap; + uint32_t ui32TxDQSDelay; + uint32_t ui32RxDQSDelay; +}; + +struct mspi_ambiq_timing_scan { + struct mspi_ambiq_timing_scan_range range; + enum mspi_ambiq_timing_scan_type scan_type; + uint32_t min_window; + uint32_t device_addr; + struct mspi_ambiq_timing_cfg result; +}; + #define TIMING_CFG_GET_RX_DUMMY(cfg) \ { \ mspi_timing_cfg *timing = (mspi_timing_cfg *)cfg; \ @@ -48,7 +96,6 @@ .bRxCap = DT_INST_PROP_BY_IDX(n, ambiq_timing_config, 4), \ .ui32TxDQSDelay = DT_INST_PROP_BY_IDX(n, ambiq_timing_config, 5), \ .ui32RxDQSDelay = DT_INST_PROP_BY_IDX(n, ambiq_timing_config, 6), \ - .ui32RXDQSDelayEXT = DT_INST_PROP_BY_IDX(n, ambiq_timing_config, 7), \ } #define MSPI_AMBIQ_TIMING_CONFIG_MASK(n) DT_INST_PROP(n, ambiq_timing_config_mask) @@ -57,38 +104,11 @@ (MSPI1_BASE - MSPI0_BASE)) -struct mspi_ambiq_timing_cfg { - uint8_t ui8WriteLatency; - uint8_t ui8TurnAround; - bool bTxNeg; - bool bRxNeg; - bool bRxCap; - uint32_t ui32TxDQSDelay; - uint32_t ui32RxDQSDelay; - uint32_t ui32RXDQSDelayEXT; -}; - -enum mspi_ambiq_timing_param { - MSPI_AMBIQ_SET_WLC = BIT(0), - MSPI_AMBIQ_SET_RLC = BIT(1), - MSPI_AMBIQ_SET_TXNEG = BIT(2), - MSPI_AMBIQ_SET_RXNEG = BIT(3), - MSPI_AMBIQ_SET_RXCAP = BIT(4), - MSPI_AMBIQ_SET_TXDQSDLY = BIT(5), - MSPI_AMBIQ_SET_RXDQSDLY = BIT(6), - MSPI_AMBIQ_SET_RXDQSDLYEXT = BIT(7), -}; - -#define TIMING_CFG_GET_RX_DUMMY(cfg) \ - { \ - mspi_timing_cfg *timing = (mspi_timing_cfg *)cfg; \ - timing->ui8TurnAround; \ - } - -#define TIMING_CFG_SET_RX_DUMMY(cfg, num) \ - { \ - mspi_timing_cfg *timing = (mspi_timing_cfg *)cfg; \ - timing->ui8TurnAround = num; \ - } +int mspi_ambiq_timing_scan(const struct device *dev, + const struct device *bus, + const struct mspi_dev_id *dev_id, + uint32_t param_mask, + struct mspi_ambiq_timing_cfg *timing, + struct mspi_ambiq_timing_scan *scan); #endif diff --git a/drivers/mspi/mspi_ambiq_timing_scan.c b/drivers/mspi/mspi_ambiq_timing_scan.c new file mode 100644 index 00000000000..2812e72f1e0 --- /dev/null +++ b/drivers/mspi/mspi_ambiq_timing_scan.c @@ -0,0 +1,497 @@ +/* + * Copyright (c) 2025, Ambiq Micro Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#define LOG_LEVEL CONFIG_MSPI_LOG_LEVEL +#include +LOG_MODULE_REGISTER(mspi_ambiq_timing_scan); + +#include "mspi_ambiq.h" + +BUILD_ASSERT(CONFIG_MSPI_AMBIQ_TIMING_SCAN_DATA_SIZE % + CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE == 0); +#if defined(CONFIG_SOC_SERIES_APOLLO4X) +BUILD_ASSERT(CONFIG_MSPI_AMBIQ_BUFF_ALIGNMENT == 16); +#elif defined(CONFIG_SOC_SERIES_APOLLO5X) +#if CONFIG_DCACHE +BUILD_ASSERT(CONFIG_MSPI_AMBIQ_BUFF_ALIGNMENT == CONFIG_DCACHE_LINE_SIZE); +#endif +#endif + +struct longest_ones { + int start; + int length; +}; + +uint8_t txdata_buff[CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE] +__attribute__((section(".ambiq_dma_buff"))); +uint8_t rxdata_buff[CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE] +__attribute__((section(".ambiq_dma_buff"))); + +static int flash_write_data(const struct device *dev, + uint32_t device_addr) +{ + int ret; + uint32_t num_bytes_left = CONFIG_MSPI_AMBIQ_TIMING_SCAN_DATA_SIZE; + uint32_t test_bytes = 0; + + ret = flash_erase(dev, device_addr, num_bytes_left); + if (ret) { + LOG_ERR("timing scan flash erase failed.\n"); + return ret; + } + + while (num_bytes_left) { + if (num_bytes_left > CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE) { + test_bytes = CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE; + num_bytes_left -= CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE; + } else { + test_bytes = num_bytes_left; + num_bytes_left = 0; + } + + LOG_DBG("Write at %08x, size %08x\n", device_addr, test_bytes); + ret = flash_write(dev, device_addr, txdata_buff, test_bytes); + if (ret) { + LOG_ERR("timing scan flash write failed.\n"); + return ret; + } + device_addr += test_bytes; + } + return 0; +} + +static int flash_read_scan(const struct device *dev, + uint32_t device_addr) +{ + int ret; + uint32_t num_bytes_left = CONFIG_MSPI_AMBIQ_TIMING_SCAN_DATA_SIZE; + uint32_t test_bytes = 0; + + while (num_bytes_left) { + if (num_bytes_left > CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE) { + test_bytes = CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE; + num_bytes_left -= CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE; + } else { + test_bytes = num_bytes_left; + num_bytes_left = 0; + } + + LOG_DBG("Read at %08x, size %08x\n", device_addr, test_bytes); + ret = flash_read(dev, device_addr, rxdata_buff, test_bytes); + if (ret) { + LOG_ERR("timing scan flash read failed.\n"); + return ret; + } + sys_cache_data_flush_and_invd_all(); + if (memcmp(txdata_buff, rxdata_buff, test_bytes)) { + return 1; + } + device_addr += test_bytes; + } + return 0; +} + +#define SECTOR_SIZE 1024 + +static void prepare_test_pattern(uint8_t *buff, uint32_t len) +{ + uint32_t *ui32_tx_ptr = (uint32_t *)buff; + uint8_t *ui8_tx_ptr = (uint8_t *)buff; + uint32_t pattern_index = 0; + uint32_t byte_left = len - len % SECTOR_SIZE; + + while (byte_left > 0) { + + switch (pattern_index) { + case 0: + /* 0x5555AAAA */ + for (uint32_t i = 0; i < SECTOR_SIZE / 4; i++) { + ui32_tx_ptr[i] = (0x5555AAAA); + } + break; + case 1: + /* 0xFFFF0000 */ + for (uint32_t i = 0; i < SECTOR_SIZE / 4; i++) { + ui32_tx_ptr[i] = (0xFFFF0000); + } + break; + case 2: + /* walking */ + for (uint32_t i = 0; i < SECTOR_SIZE; i++) { + ui8_tx_ptr[i] = 0x01 << (i % 8); + } + break; + case 3: + /* incremental from 1 */ + for (uint32_t i = 0; i < SECTOR_SIZE; i++) { + ui8_tx_ptr[i] = ((i + 1) & 0xFF); + } + break; + case 4: + /* decremental from 0xff */ + for (uint32_t i = 0; i < SECTOR_SIZE; i++) { + ui8_tx_ptr[i] = (0xff - i) & 0xFF; + } + break; + default: + /* incremental from 1 */ + for (uint32_t i = 0; i < SECTOR_SIZE; i++) { + ui8_tx_ptr[i] = ((i + 1) & 0xFF); + } + break; + + } + + byte_left -= SECTOR_SIZE; + ui32_tx_ptr += SECTOR_SIZE / 4; + ui8_tx_ptr += SECTOR_SIZE; + pattern_index++; + pattern_index = pattern_index % 5; + } +} + +static void find_longest_ones(const unsigned char *data, int bit_len, + struct longest_ones *result) +{ + /* Default result if no 1s are found */ + int current_len = 0; + int current_start = -1; + + result->start = -1; + result->length = 0; + + for (int i = 0; i < bit_len; i++) { + int byte_index = i / 8; + int bit_index = i % 8; + + if (data[byte_index] & (1 << bit_index)) { + if (current_len == 0) { + current_start = i; + } + current_len++; + } else { + if (current_len > result->length) { + result->start = current_start; + result->length = current_len; + } + current_len = 0; + } + } + if (current_len > result->length) { + result->start = current_start; + result->length = current_len; + } +} + +static int find_mid_point(const unsigned char *data, int bit_len) +{ + struct longest_ones result; + + find_longest_ones(data, bit_len, &result); + + if (result.start == -1) { + return 0; + } + + return result.start + (result.length - 1) / 2; +} + +static int timing_scan_write_read_memc(const struct device *dev, + uint32_t device_addr) +{ + uint32_t num_bytes_left = CONFIG_MSPI_AMBIQ_TIMING_SCAN_DATA_SIZE; + uint32_t test_bytes = 0; + + while (num_bytes_left) { + if (num_bytes_left > CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE) { + test_bytes = CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE; + num_bytes_left -= CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE; + } else { + test_bytes = num_bytes_left; + num_bytes_left = 0; + } + + LOG_DBG("Write read at %08x, size %08x\n", device_addr, test_bytes); + memcpy((void *)device_addr, txdata_buff, test_bytes); + sys_cache_data_flush_and_invd_all(); + memcpy(rxdata_buff, (void *)device_addr, test_bytes); + if (memcmp(txdata_buff, rxdata_buff, test_bytes)) { + return 1; + } + } + return 0; +} + +static int check_param(struct mspi_ambiq_timing_scan *scan, uint32_t param_mask) +{ + struct mspi_ambiq_timing_scan_range *range = &scan->range; + + if (scan->min_window > range->rxdqs_end - range->rxdqs_start) { + LOG_ERR("invalid min_window or txdqs, rxdqs scan range.\n"); + return 1; + } + + if (!(param_mask & MSPI_AMBIQ_SET_RLC) && + (range->rlc_start != 0) && (range->rlc_end != 0)) { + LOG_ERR("invalid RLC range.\n"); + return 1; + } + + if (!(param_mask & MSPI_AMBIQ_SET_TXNEG) && + (range->txneg_start != 0) && (range->txneg_end != 0)) { + LOG_ERR("invalid TXNEG range.\n"); + return 1; + } + + if (!(param_mask & MSPI_AMBIQ_SET_RXNEG) && + (range->rxneg_start != 0) && (range->rxneg_end != 0)) { + LOG_ERR("invalid RXNEG range.\n"); + return 1; + } + + if (!(param_mask & MSPI_AMBIQ_SET_RXCAP) && + (range->rxcap_start != 0) && (range->rxcap_end != 0)) { + LOG_ERR("invalid RXCAP range.\n"); + return 1; + } + + if (!(param_mask & MSPI_AMBIQ_SET_TXDQSDLY) && + (range->txdqs_start != 0) && (range->txdqs_end != 0)) { + LOG_ERR("invalid TXDQSDLY range.\n"); + return 1; + } + + if (!(param_mask & MSPI_AMBIQ_SET_RXDQSDLY) && + (range->rxdqs_start != 0) && (range->rxdqs_end != 0)) { + LOG_ERR("invalid RXDQSDLY range.\n"); + return 1; + } + + return 0; +} + +static inline int timing_scan(const struct device *dev, + const struct device *bus, + const struct mspi_dev_id *dev_id, + uint32_t param_mask, + struct mspi_ambiq_timing_scan *scan, + struct mspi_ambiq_timing_cfg *param, + uint32_t *max_window) +{ + int ret = 0; + uint32_t txdqsdelay = 0; + uint32_t rxdqsdelay = 0; + uint32_t tx_result = 0; + uint32_t address = scan->device_addr; + struct mspi_ambiq_timing_scan_range *range = &scan->range; + struct longest_ones longest; + uint32_t rx_res[32]; + + memset(rx_res, 0, sizeof(rx_res)); + + if (scan->scan_type == MSPI_AMBIQ_TIMING_SCAN_FLASH) { + ret = flash_write_data(dev, address); + if (ret) { + LOG_ERR("Flash write failed, code:%d\n", ret); + return ret; + } + } + + /* LOOP_TXDQSDELAY */ + for (param->ui32TxDQSDelay = (param_mask & MSPI_AMBIQ_SET_TXDQSDLY) ? + range->txdqs_start : 0; + param->ui32TxDQSDelay <= ((param_mask & MSPI_AMBIQ_SET_TXDQSDLY) ? + range->txdqs_end : 0); + param->ui32TxDQSDelay++) { + + /* LOOP_RXDQSDELAY */ + for (param->ui32RxDQSDelay = (param_mask & MSPI_AMBIQ_SET_RXDQSDLY) ? + range->rxdqs_start : 0; + param->ui32RxDQSDelay <= ((param_mask & MSPI_AMBIQ_SET_RXDQSDLY) ? + range->rxdqs_end : 0); + param->ui32RxDQSDelay++) { + if (scan->scan_type == MSPI_AMBIQ_TIMING_SCAN_MEMC) { + address = scan->device_addr; + address += (param->bTxNeg + param->bRxNeg + param->bRxCap + + param->ui8TurnAround) * + CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE + + (param->ui32TxDQSDelay + param->ui32RxDQSDelay) * 2; + + ret = mspi_dev_config(bus, dev_id, MSPI_DEVICE_CONFIG_NONE, NULL); + if (ret) { + LOG_ERR("failed to acquire controller, code:%d\n", ret); + return ret; + } + ret = mspi_timing_config(bus, dev_id, param_mask, param); + if (ret) { + LOG_ERR("failed to configure mspi timing!!\n"); + return ret; + } + /* run data check */ + ret = timing_scan_write_read_memc(dev, address); + } else if (scan->scan_type == MSPI_AMBIQ_TIMING_SCAN_FLASH) { + + ret = mspi_dev_config(bus, dev_id, MSPI_DEVICE_CONFIG_NONE, NULL); + if (ret) { + LOG_ERR("failed to acquire controller, code:%d\n", ret); + return ret; + } + ret = mspi_timing_config(bus, dev_id, param_mask, param); + if (ret) { + LOG_ERR("failed to configure mspi timing!!\n"); + return ret; + } + + /* run data check */ + ret = flash_read_scan(dev, address); + } + if (ret == 0) { + /* data check pass */ + rx_res[param->ui32TxDQSDelay] |= 0x01 << param->ui32RxDQSDelay; + } else if (ret != 1) { + return ret; + } + } + + if (range->rxdqs_start != range->rxdqs_end && + (param_mask & MSPI_AMBIQ_SET_RXDQSDLY)) { + find_longest_ones((const unsigned char *)&rx_res[param->ui32TxDQSDelay], + 32, &longest); + if (longest.start != -1 && longest.length >= scan->min_window) { + tx_result |= 0x01 << param->ui32TxDQSDelay; + } + LOG_INF(" TxDQSDelay: %d, RxDQSDelay Scan = 0x%08X, Window size = %d\n", + param->ui32TxDQSDelay, rx_res[param->ui32TxDQSDelay], + longest.length); + } else { + if (rx_res[param->ui32TxDQSDelay] != 0) { + tx_result |= 0x01 << param->ui32TxDQSDelay; + } + LOG_INF(" TxDQSDelay: %d, RxDQSDelay Scan = 0x%08X\n", + param->ui32TxDQSDelay, rx_res[param->ui32TxDQSDelay]); + } + } + + /* Find TXDQSDELAY Value */ + if (range->txdqs_start != range->txdqs_end && + (param_mask & MSPI_AMBIQ_SET_TXDQSDLY)) { + txdqsdelay = find_mid_point((const unsigned char *)&tx_result, 32); + } else { + txdqsdelay = param->ui32TxDQSDelay; + } + + /* Find RXDQSDELAY Value */ + if (range->rxdqs_start != range->rxdqs_end && + (param_mask & MSPI_AMBIQ_SET_RXDQSDLY)) { + rxdqsdelay = find_mid_point((const unsigned char *)&rx_res[txdqsdelay], 32); + } else { + rxdqsdelay = param->ui32RxDQSDelay; + } + + find_longest_ones((const unsigned char *)&tx_result, 32, &longest); + if (*max_window < longest.length || + (range->txdqs_start == range->txdqs_end && + range->rxdqs_start == range->rxdqs_end) || + ((param_mask & (MSPI_AMBIQ_SET_TXDQSDLY | MSPI_AMBIQ_SET_RXDQSDLY)) == 0)) { + *max_window = longest.length; + scan->result = *param; + scan->result.ui32TxDQSDelay = txdqsdelay; + scan->result.ui32RxDQSDelay = rxdqsdelay; + LOG_INF("Selected setting: TxNeg=%d, RxNeg=%d, RxCap=%d, Turnaround=%d," + "TxDQSDelay=%d, RxDQSDelay=%d\n", param->bTxNeg, param->bRxNeg, + param->bRxCap, param->ui8TurnAround, + txdqsdelay, rxdqsdelay); + } else { + LOG_INF("Candidate setting: TxNeg=%d, RxNeg=%d, RxCap=%d, Turnaround=%d," + "TxDQSDelay=%d, RxDQSDelay=%d\n", param->bTxNeg, param->bRxNeg, + param->bRxCap, param->ui8TurnAround, + txdqsdelay, rxdqsdelay); + } + + return 0; +} + +int mspi_ambiq_timing_scan(const struct device *dev, + const struct device *bus, + const struct mspi_dev_id *dev_id, + uint32_t param_mask, + struct mspi_ambiq_timing_cfg *timing, + struct mspi_ambiq_timing_scan *scan) +{ + int ret; + int txneg; + int rxneg; + int rxcap; + int max_window = 0; + struct mspi_ambiq_timing_cfg param; + struct mspi_ambiq_timing_scan_range *range = &scan->range; + + if (check_param(scan, param_mask)) { + return -EINVAL; + } + + /* Generate data into the buffer */ + prepare_test_pattern(txdata_buff, CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE); + if (CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE > 64*1024) { + sys_cache_data_flush_all(); + } else { + sys_cache_data_flush_range(txdata_buff, CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE); + } + + memset(¶m, 0, sizeof(struct mspi_ambiq_timing_cfg)); + + /* LOOP_TXNEG */ + for (txneg = (param_mask & MSPI_AMBIQ_SET_TXNEG) ? range->txneg_start : 0; + txneg <= ((param_mask & MSPI_AMBIQ_SET_TXNEG) ? range->txneg_end : 0); txneg++) { + param.bTxNeg = (bool)txneg; + + /* LOOP_RXNEG */ + for (rxneg = (param_mask & MSPI_AMBIQ_SET_RXNEG) ? range->rxneg_start : 0; + rxneg <= ((param_mask & MSPI_AMBIQ_SET_RXNEG) ? range->rxneg_end : 0); + rxneg++) { + param.bRxNeg = (bool)rxneg; + + /* LOOP_RXCAP */ + for (rxcap = (param_mask & MSPI_AMBIQ_SET_RXCAP) ? range->rxcap_start : 0; + rxcap <= ((param_mask & MSPI_AMBIQ_SET_RXCAP) ? range->rxcap_end : 0); + rxcap++) { + param.bRxCap = (bool)rxcap; + /* LOOP_TURNAROUND */ + for (param.ui8TurnAround = (param_mask & MSPI_AMBIQ_SET_RLC) ? + range->rlc_start + timing->ui8TurnAround : 0; + param.ui8TurnAround <= ((param_mask & MSPI_AMBIQ_SET_RLC) ? + range->rlc_end + timing->ui8TurnAround : 0); + param.ui8TurnAround++) { + param.ui8WriteLatency = timing->ui8WriteLatency; + LOG_INF("TxNeg=%d, RxNeg=%d, RxCap=%d, Turnaround=%d\n", + param.bTxNeg, param.bRxNeg, param.bRxCap, + param.ui8TurnAround); + ret = timing_scan(dev, bus, dev_id, param_mask, + scan, ¶m, &max_window); + if (ret) { + LOG_ERR("Timing scan failed, code:%d\n", ret); + return ret; + } + + if (((range->txdqs_start == range->txdqs_end && + range->rxdqs_start == range->rxdqs_end) || + ((param_mask & (MSPI_AMBIQ_SET_TXDQSDLY | + MSPI_AMBIQ_SET_RXDQSDLY)) == 0)) && + max_window != 0) { + return 0; + } + } + + } + } + } + + return ret; +} diff --git a/dts/bindings/mspi/ambiq,mspi-device.yaml b/dts/bindings/mspi/ambiq,mspi-device.yaml index 9021574e3ad..e339264b966 100644 --- a/dts/bindings/mspi/ambiq,mspi-device.yaml +++ b/dts/bindings/mspi/ambiq,mspi-device.yaml @@ -48,7 +48,7 @@ properties: ambiq,timing-config: type: array - default: [0, 0, 0, 0, 0, 0, 0, 0] + default: [0, 0, 0, 0, 0, 0, 0] description: | Array of tuples to configure the timing parameters default = @@ -60,5 +60,4 @@ properties: .bRxCap = false, .ui32TxDQSDelay = 0, .ui32RxDQSDelay = 0, - .ui32RXDQSDelayEXT = 0, > diff --git a/samples/drivers/mspi/mspi_timing_scan/CMakeLists.txt b/samples/drivers/mspi/mspi_timing_scan/CMakeLists.txt new file mode 100644 index 00000000000..44795456778 --- /dev/null +++ b/samples/drivers/mspi/mspi_timing_scan/CMakeLists.txt @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(mspi_timing_scan) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) + +if(CONFIG_MSPI) + target_include_directories(app PRIVATE ${ZEPHYR_BASE}/include/zephyr/drivers) + target_include_directories(app PRIVATE ${ZEPHYR_BASE}/drivers/mspi) +endif() diff --git a/samples/drivers/mspi/mspi_timing_scan/README.rst b/samples/drivers/mspi/mspi_timing_scan/README.rst new file mode 100644 index 00000000000..15eeab9c5c2 --- /dev/null +++ b/samples/drivers/mspi/mspi_timing_scan/README.rst @@ -0,0 +1,46 @@ +.. zephyr:code-sample:: mspi-timing-scan + :name: Ambiq MSPI timing scan + :relevant-api: flash_interface + + Find the appropriate timing for a given device on a given board. + +Overview +******** + +This sample demonstrates the usage of ambiq timing scan utility. + +Building and Running +******************** + +The application will build only for a target that has a :ref:`devicetree ` +``flash0`` or ``psram0`` alias depending on the interface used. +They refers to an entry with the following bindings as a compatible: + +* :dtcompatible:`ambiq,mspi-device` + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/mspi/mspi_timing_scan + :board: apollo5_eb + :goals: build flash + :compact: + +Sample Output +============= + +.. code-block:: console + + *** Booting Zephyr OS build zephyr-v3.4.0-27775-g750ed00d564b *** + mspi_ambiq_timing_scan: TxNeg=0, RxNeg=0, RxCap=0, Turnaround=5 + mspi_ambiq_timing_scan: TxDQSDelay: 0, RxDQSDelay Scan = 0x0007FFFE, Window size = 18 + mspi_ambiq_timing_scan: TxDQSDelay: 1, RxDQSDelay Scan = 0x0007FFFF, Window size = 19 + mspi_ambiq_timing_scan: TxDQSDelay: 1, RxDQSDelay Scan = 0x0007FFFF, Window size = 19 + mspi_ambiq_timing_scan: TxDQSDelay: 2, RxDQSDelay Scan = 0x0007FFFE, Window size = 18 + mspi_ambiq_timing_scan: TxDQSDelay: 3, RxDQSDelay Scan = 0x0007FFFF, Window size = 19 + mspi_ambiq_timing_scan: TxDQSDelay: 4, RxDQSDelay Scan = 0x0007FFFE, Window size = 18 + mspi_ambiq_timing_scan: TxDQSDelay: 5, RxDQSDelay Scan = 0x0005FD54, Window size = 7 + mspi_ambiq_timing_scan: TxDQSDelay: 6, RxDQSDelay Scan = 0x00000000, Window size = 0 + mspi_ambiq_timing_scan: TxDQSDelay: 7, RxDQSDelay Scan = 0x00000000, Window size = 0 + mspi_ambiq_timing_scan: TxDQSDelay: 8, RxDQSDelay Scan = 0x00000000, Window size = 0 + mspi_ambiq_timing_scan: TxDQSDelay: 9, RxDQSDelay Scan = 0x00000000, Window size = 0 + mspi_ambiq_timing_scan: TxDQSDelay: 10, RxDQSDelay Scan = 0x00000000, Window size = 0 + mspi_ambiq_timing_scan: Selected setting: TxNeg=0, RxNeg=0, RxCap=0, Turnaround=5,TxDQSDelay=2, RxDQSDelay=9 diff --git a/samples/drivers/mspi/mspi_timing_scan/boards/apollo510_evb.conf b/samples/drivers/mspi/mspi_timing_scan/boards/apollo510_evb.conf new file mode 100644 index 00000000000..c63e005cd7d --- /dev/null +++ b/samples/drivers/mspi/mspi_timing_scan/boards/apollo510_evb.conf @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Ambiq Micro Inc. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_MSPI_AMBIQ_TIMING_SCAN=y +CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=y +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_MSPI_LOG_LEVEL_INF=y +CONFIG_MSPI_INIT_PRIORITY=40 +CONFIG_MAIN_STACK_SIZE=8192 diff --git a/samples/drivers/mspi/mspi_timing_scan/boards/apollo510_evb.overlay b/samples/drivers/mspi/mspi_timing_scan/boards/apollo510_evb.overlay new file mode 100644 index 00000000000..e2bdfe92ea5 --- /dev/null +++ b/samples/drivers/mspi/mspi_timing_scan/boards/apollo510_evb.overlay @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025, Ambiq Micro Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + flash0 = &is25wx064; + psram0 = &aps51216ba; + }; +}; + +&mspi0 { + /delete-property/ zephyr,pm-device-runtime-auto; + status = "okay"; +}; + +&aps51216ba { + /delete-property/ zephyr,pm-device-runtime-auto; + status = "okay"; +}; + +&mspi1 { + /delete-property/ zephyr,pm-device-runtime-auto; + status = "okay"; +}; + +&is25wx064 { + status = "okay"; +}; diff --git a/samples/drivers/mspi/mspi_timing_scan/prj.conf b/samples/drivers/mspi/mspi_timing_scan/prj.conf new file mode 100644 index 00000000000..8ece30a3243 --- /dev/null +++ b/samples/drivers/mspi/mspi_timing_scan/prj.conf @@ -0,0 +1,2 @@ +CONFIG_STDOUT_CONSOLE=y +CONFIG_MSPI=y diff --git a/samples/drivers/mspi/mspi_timing_scan/sample.yaml b/samples/drivers/mspi/mspi_timing_scan/sample.yaml new file mode 100644 index 00000000000..bd967bd1c9d --- /dev/null +++ b/samples/drivers/mspi/mspi_timing_scan/sample.yaml @@ -0,0 +1,47 @@ +sample: + name: MSPI Flash Sample +tests: + sample.drivers.mspi.timing_scan.flash: + tags: + - mspi + filter: dt_compat_enabled("ambiq,mspi-device") + harness: console + harness_config: + type: multi_line + ordered: true + regex: + - " mspi_ambiq_timing_scan: TxNeg=0, RxNeg=0, RxCap=0, Turnaround=[0-9]*" + - " mspi_ambiq_timing_scan: TxDQSDelay: [0-9]*, RxDQSDelay Scan = 0x[0-9]*,(.*)" + - " mspi_ambiq_timing_scan: Selected setting: TxNeg=0, RxNeg=0, RxCap=0, (.*)" + platform_allow: + - apollo510_evb + integration_platforms: + - apollo510_evb + depends_on: mspi + extra_configs: + - CONFIG_FLASH=y + - CONFIG_FLASH_INIT_PRIORITY=50 + - CONFIG_FLASH_MSPI_XIP_READ=y + - CONFIG_MSPI_AMBIQ_TIMING_SCAN_BUFFER_SIZE=8192 + - CONFIG_MSPI_AMBIQ_TIMING_SCAN_DATA_SIZE=131072 + + sample.drivers.mspi.timing_scan.memc: + tags: + - mspi + filter: dt_compat_enabled("ambiq,mspi-device") + harness: console + harness_config: + type: multi_line + ordered: true + regex: + - " mspi_ambiq_timing_scan: TxNeg=0, RxNeg=0, RxCap=0, Turnaround=[0-9]*" + - " mspi_ambiq_timing_scan: TxDQSDelay: [0-9]*, RxDQSDelay Scan = 0x[0-9]*,(.*)" + - " mspi_ambiq_timing_scan: Selected setting: TxNeg=0, RxNeg=0, RxCap=0, (.*)" + platform_allow: + - apollo510_evb + integration_platforms: + - apollo510_evb + depends_on: mspi + extra_configs: + - CONFIG_MEMC=y + - CONFIG_MEMC_INIT_PRIORITY=50 diff --git a/samples/drivers/mspi/mspi_timing_scan/src/main.c b/samples/drivers/mspi/mspi_timing_scan/src/main.c new file mode 100644 index 00000000000..1ee8096685d --- /dev/null +++ b/samples/drivers/mspi/mspi_timing_scan/src/main.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 Ambiq Micro Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mspi_ambiq.h" + +#if CONFIG_FLASH_MSPI +#define TARGET_DEVICE DT_ALIAS(flash0) +#elif CONFIG_MEMC_MSPI +#define TARGET_DEVICE DT_ALIAS(psram0) +#else +#error "Unsupported device type!!" +#endif + +#define MSPI_AMBIQ_TIMING_CONFIG_DT(n) \ + { \ + .ui8WriteLatency = DT_PROP_BY_IDX(n, ambiq_timing_config, 0), \ + .ui8TurnAround = DT_PROP_BY_IDX(n, ambiq_timing_config, 1), \ + .bTxNeg = DT_PROP_BY_IDX(n, ambiq_timing_config, 2), \ + .bRxNeg = DT_PROP_BY_IDX(n, ambiq_timing_config, 3), \ + .bRxCap = DT_PROP_BY_IDX(n, ambiq_timing_config, 4), \ + .ui32TxDQSDelay = DT_PROP_BY_IDX(n, ambiq_timing_config, 5), \ + .ui32RxDQSDelay = DT_PROP_BY_IDX(n, ambiq_timing_config, 6), \ + } + +#define MSPI_AMBIQ_TIMING_CONFIG_MASK_DT(n) DT_PROP(n, ambiq_timing_config_mask) + +int main(void) +{ + const struct device *tar_bus = DEVICE_DT_GET(DT_BUS(TARGET_DEVICE)); + const struct device *tar_dev = DEVICE_DT_GET(TARGET_DEVICE); + struct mspi_dev_id dev_id = MSPI_DEVICE_ID_DT(TARGET_DEVICE); +#if CONFIG_MEMC_MSPI + struct mspi_xip_cfg tar_xip_cfg = MSPI_XIP_CONFIG_DT(TARGET_DEVICE); +#endif + struct mspi_ambiq_timing_cfg tar_timing_cfg = MSPI_AMBIQ_TIMING_CONFIG_DT(TARGET_DEVICE); + uint32_t timing_cfg_mask = MSPI_AMBIQ_TIMING_CONFIG_MASK_DT(TARGET_DEVICE); + struct mspi_ambiq_timing_scan scan; + + if (!device_is_ready(tar_dev)) { + printk("%s: device not ready.\n", tar_dev->name); + return 1; + } + +#if CONFIG_MEMC_MSPI + if (!tar_xip_cfg.enable) { + printk("Need to enable XIP for timing scan.\n"); + return 1; + } +#endif + + memset(&scan, 0, sizeof(struct mspi_ambiq_timing_scan)); + scan.range.txdqs_end = 10; + scan.range.rxdqs_end = 31; +#if CONFIG_FLASH_MSPI + scan.scan_type = MSPI_AMBIQ_TIMING_SCAN_FLASH; +#elif CONFIG_MEMC_MSPI + uint32_t base_addr = DT_REG_ADDR_BY_IDX(DT_BUS(TARGET_DEVICE), 1); + + scan.scan_type = MSPI_AMBIQ_TIMING_SCAN_MEMC; + scan.device_addr = base_addr + + tar_xip_cfg.address_offset + + tar_xip_cfg.size / 2; +#endif + scan.min_window = 6; + + if (mspi_ambiq_timing_scan(tar_dev, tar_bus, &dev_id, timing_cfg_mask, + (void *)&tar_timing_cfg, &scan)) { + printk("Failed to timing scan\n"); + return 1; + } + + printf("==========================\n"); + return 0; +}