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 <swift.tian@ambiq.com>
This commit is contained in:
Swift Tian 2025-04-21 12:53:20 +08:00 committed by Benjamin Cabé
parent cc5c142535
commit 69c14e37ac
14 changed files with 816 additions and 38 deletions

View File

@ -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>;
};
};

View File

@ -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) \
}; \

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,497 @@
/*
* Copyright (c) 2025, Ambiq Micro Inc. <www.ambiq.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/mspi.h>
#include <zephyr/cache.h>
#include <zephyr/drivers/flash.h>
#define LOG_LEVEL CONFIG_MSPI_LOG_LEVEL
#include <zephyr/logging/log.h>
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(&param, 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, &param, &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;
}

View File

@ -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,
>

View File

@ -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()

View File

@ -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 <dt-guide>`
``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 ***
<inf> mspi_ambiq_timing_scan: TxNeg=0, RxNeg=0, RxCap=0, Turnaround=5
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 0, RxDQSDelay Scan = 0x0007FFFE, Window size = 18
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 1, RxDQSDelay Scan = 0x0007FFFF, Window size = 19
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 1, RxDQSDelay Scan = 0x0007FFFF, Window size = 19
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 2, RxDQSDelay Scan = 0x0007FFFE, Window size = 18
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 3, RxDQSDelay Scan = 0x0007FFFF, Window size = 19
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 4, RxDQSDelay Scan = 0x0007FFFE, Window size = 18
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 5, RxDQSDelay Scan = 0x0005FD54, Window size = 7
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 6, RxDQSDelay Scan = 0x00000000, Window size = 0
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 7, RxDQSDelay Scan = 0x00000000, Window size = 0
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 8, RxDQSDelay Scan = 0x00000000, Window size = 0
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 9, RxDQSDelay Scan = 0x00000000, Window size = 0
<inf> mspi_ambiq_timing_scan: TxDQSDelay: 10, RxDQSDelay Scan = 0x00000000, Window size = 0
<inf> mspi_ambiq_timing_scan: Selected setting: TxNeg=0, RxNeg=0, RxCap=0, Turnaround=5,TxDQSDelay=2, RxDQSDelay=9

View File

@ -0,0 +1,10 @@
# Copyright (c) 2025 Ambiq Micro Inc. <www.ambiq.com>
# 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

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2025, Ambiq Micro Inc. <www.ambiq.com>
*
* 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";
};

View File

@ -0,0 +1,2 @@
CONFIG_STDOUT_CONSOLE=y
CONFIG_MSPI=y

View File

@ -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:
- "<inf> mspi_ambiq_timing_scan: TxNeg=0, RxNeg=0, RxCap=0, Turnaround=[0-9]*"
- "<inf> mspi_ambiq_timing_scan: TxDQSDelay: [0-9]*, RxDQSDelay Scan = 0x[0-9]*,(.*)"
- "<inf> 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:
- "<inf> mspi_ambiq_timing_scan: TxNeg=0, RxNeg=0, RxCap=0, Turnaround=[0-9]*"
- "<inf> mspi_ambiq_timing_scan: TxDQSDelay: [0-9]*, RxDQSDelay Scan = 0x[0-9]*,(.*)"
- "<inf> 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

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) 2025 Ambiq Micro Inc. <www.ambiq.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/drivers/mspi.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <stdio.h>
#include <string.h>
#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;
}