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
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(¶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; |
|
}
|
|
|