Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

497 lines
14 KiB

/*
* 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;
}