Improve the SDIO tuning algorithm to increase stability and performance under low-temperature conditions. Signed-off-by: Swee Aun Khor <sakhor@efinixinc.com>
682 lines
24 KiB
C
682 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
/*
|
||
* Efinix SDIO Host Controller Tuning Support
|
||
*
|
||
* Copyright (C) 2026 Efinix, Inc.
|
||
* Author: Khor Swee Aun <sakhor@efinixinc.com>
|
||
*/
|
||
|
||
#include <linux/delay.h>
|
||
#include <linux/slab.h>
|
||
#include <linux/jiffies.h>
|
||
#include <linux/mmc/mmc.h>
|
||
|
||
#include "efx_sdio.h"
|
||
|
||
/* Standard eMMC/SDIO tuning block patterns from bare metal driver */
|
||
static const u32 tuning_block_pattern_8b_mode[] = {
|
||
0xff00ffff, 0x0000ffff, 0xccccffff, 0xcccc33cc,
|
||
0xcc3333cc, 0xffffcccc, 0xffffeeff, 0xffeeeeff,
|
||
0xffddffff, 0xddddffff, 0xbbffffff, 0xbbffffff,
|
||
0xffffffbb, 0xffffff77, 0x77ff7777, 0xffeeddbb,
|
||
0x00ffffff, 0x00ffffff, 0xccffff00, 0xcc33cccc,
|
||
0x3333cccc, 0xffcccccc, 0xffeeffff, 0xeeeeffff,
|
||
0xddffffff, 0xddffffff, 0xffffffdd, 0xffffffbb,
|
||
0xffffbbbb, 0xffff77ff, 0xff7777ff, 0xeeddbb77
|
||
};
|
||
|
||
static const u32 tuning_block_pattern_4b_mode[] = {
|
||
0x00ff0fff, 0xccc3ccff, 0xffcc3cc3, 0xeffefffe,
|
||
0xddffdfff, 0xfbfffbff, 0xff7fffbf, 0xefbdf777,
|
||
0xf0fff0ff, 0x3cccfc0f, 0xcfcc33cc, 0xeeffefff,
|
||
0xfdfffdff, 0xffbfffdf, 0xfff7ffbb, 0xde7b7ff7
|
||
};
|
||
|
||
void efx_sdio_set_timing_config(struct efx_sdio_host *host,
|
||
u32 sample_count, u32 pll_shift)
|
||
{
|
||
u32 config_value;
|
||
|
||
/* Build timing configuration: sample_count[31:16] | pll_shift[8:6] */
|
||
config_value = (sample_count << 16) | (pll_shift << 6);
|
||
|
||
/* Apply timing configuration with hardware trigger sequence:
|
||
* 1. Write config with trigger bit clear (bit 0 = 0)
|
||
* 2. Write config with trigger bit set (bit 0 = 1) to latch settings
|
||
*/
|
||
efx_sdio_writel(host, config_value | 0x0, EFX_SDIO_BASE_REG1);
|
||
efx_sdio_writel(host, config_value | 0x1, EFX_SDIO_BASE_REG1);
|
||
efx_sdio_writel(host, config_value | 0x0, EFX_SDIO_BASE_REG1);
|
||
|
||
/* Wait for PLL settling */
|
||
udelay(100); /* 100us is sufficient for PLL settling */
|
||
}
|
||
|
||
/* Execute custom tuning command for SDR12, SDR25 and DDR50 modes
|
||
* Reminder : This function is called with host lock held
|
||
*/
|
||
int efx_sdio_execute_custom_tuning_command(struct efx_sdio_host *host, int bus_width)
|
||
{
|
||
u32 block_size, command_config, word_count;
|
||
//const u32 *reference_pattern;
|
||
u32 received_data;
|
||
int i, mismatches = 0;
|
||
unsigned long timeout;
|
||
u32 tuning_present_state;
|
||
unsigned long flags;
|
||
u32 reg_addr = 0x0;
|
||
|
||
// Reset CRC error flag at start of tuning command
|
||
host->tuning_crc_error = false;
|
||
|
||
/* Determine block size and reference pattern based on bus width */
|
||
block_size = 16;
|
||
word_count = block_size/4;
|
||
//reference_pattern = NULL;
|
||
|
||
/* Configure arguments for CMD53
|
||
[31] R/W flag = 0
|
||
[30:28] Function number = 0
|
||
[27] Block mode = 0
|
||
[26] OP code (fixed/increment) = 1
|
||
[25:9] Register address = reg_addr
|
||
[8:0] Byte count / block count = 16 (byte)
|
||
*/
|
||
u32 arg = 0;
|
||
arg |= ( 1 << 26 | reg_addr << 9 | 16 );
|
||
efx_sdio_writel(host, arg, EFX_SDIO_ARG1);
|
||
|
||
/* Byte mode
|
||
For byte mode, block size is set to the byte count and block count is set to 1
|
||
Might not neeeded as this is a byte mode transfer
|
||
*/
|
||
efx_sdio_writel(host, (1 << 16) | block_size, EFX_SDIO_BLOCK_SIZE);
|
||
|
||
|
||
/* Configure command based on single or multi-block transfer */
|
||
u32 val = 0;
|
||
u32 cmd_index = 53; // READ_SINGLE_BLOCK command
|
||
u32 data_available = 1; // Data transfer expected
|
||
u32 cmd_index_check_en = 1; // Enable command index check
|
||
u32 cmd_crc_en = 1; // Enable command CRC check
|
||
u32 resp_type = 2; // R1 response type (48-bit)
|
||
u32 data_direction = 1; // Read from card
|
||
u32 auto_cmd_en = 0; // No auto command
|
||
u32 multi_block_en = 0; // Single block transfer
|
||
u32 block_counter_en = 0; // Block counter disabled
|
||
u32 dma_mode = 0; // DMA mode disabled
|
||
|
||
/* Build command register value from configuration bits */
|
||
val = (cmd_index << 24) | (data_available << 21) | (cmd_index_check_en << 20) |
|
||
(cmd_crc_en << 19) | (resp_type << 16) | (multi_block_en << 5) |
|
||
(data_direction << 4) | (auto_cmd_en << 2) | (block_counter_en << 1) | (dma_mode << 0);
|
||
|
||
efx_sdio_writel(host, val, EFX_SDIO_TRANSFER_MODE);
|
||
|
||
/* Wait for buffer ready with timeout */
|
||
timeout = jiffies + msecs_to_jiffies(5);
|
||
|
||
do {
|
||
tuning_present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
|
||
if (tuning_present_state & EFX_SDIO_BUFFER_READ_EN) {
|
||
break;
|
||
}
|
||
|
||
if (time_after(jiffies, timeout)) {
|
||
dev_dbg(&host->pdev->dev, "Custom tuning command timeout after 5ms\n");
|
||
return 0; /* Failure */
|
||
}
|
||
|
||
cpu_relax();
|
||
} while (1);
|
||
|
||
/* Read custom tuning data */
|
||
for (i = 0; i < word_count; i++) {
|
||
received_data = efx_sdio_readl(host, EFX_SDIO_BUFFER_DATA_PORT);
|
||
/* Print received data for debugging */
|
||
dev_dbg(&host->pdev->dev, "Received custom tuning data word %d: 0x%08x\n", i, received_data);
|
||
}
|
||
|
||
/* Delay 1ms */
|
||
udelay(1000);
|
||
if (host->tuning_crc_error) {
|
||
dev_dbg(&host->pdev->dev,
|
||
"Custom tuning command CRC error detected\n");
|
||
host->tuning_crc_error = false; /* Reset CRC error flag after handling */
|
||
return 0; /* Failure due to CRC error */
|
||
}
|
||
return 1; /* Success */
|
||
}
|
||
|
||
int efx_sdio_execute_tuning_command(struct efx_sdio_host *host, int bus_width)
|
||
{
|
||
u32 block_size, command_config, word_count;
|
||
const u32 *reference_pattern;
|
||
u32 received_data;
|
||
int i, mismatches = 0;
|
||
unsigned long timeout;
|
||
u32 tuning_present_state;
|
||
unsigned long flags;
|
||
|
||
/* Determine block size and reference pattern based on bus width */
|
||
if (bus_width == 8) {
|
||
block_size = EFX_SDIO_TUNING_BLOCK_SIZE_8BIT;
|
||
word_count = 32;
|
||
reference_pattern = tuning_block_pattern_8b_mode;
|
||
} else {
|
||
block_size = EFX_SDIO_TUNING_BLOCK_SIZE_4BIT;
|
||
word_count = 16;
|
||
reference_pattern = tuning_block_pattern_4b_mode;
|
||
}
|
||
|
||
/* Configure command parameters for CMD19 */
|
||
efx_sdio_writel(host, (1 << 16) | block_size, EFX_SDIO_BLOCK_SIZE);
|
||
efx_sdio_writel(host, 0x0, EFX_SDIO_ARG1);
|
||
|
||
/* Issue CMD19 tuning command with specific configuration */
|
||
/* CMD19 (index=19), data present, CRC check, 48-bit response */
|
||
//
|
||
command_config = 0x133A0010;
|
||
spin_lock_irqsave(&host->lock, flags);
|
||
host->tuning_crc_error = false; // Reset CRC error flag at start of tuning command
|
||
spin_unlock_irqrestore(&host->lock, flags);
|
||
efx_sdio_writel(host, command_config, EFX_SDIO_TRANSFER_MODE);
|
||
|
||
/* Wait for buffer ready with timeout */
|
||
timeout = jiffies + msecs_to_jiffies(5);
|
||
|
||
do {
|
||
tuning_present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
|
||
if (tuning_present_state & EFX_SDIO_BUFFER_READ_EN) {
|
||
break;
|
||
}
|
||
|
||
if (time_after(jiffies, timeout)) {
|
||
dev_dbg(&host->pdev->dev, "Tuning command timeout after 5ms\n");
|
||
return 0; /* Failure */
|
||
}
|
||
|
||
cpu_relax();
|
||
} while (1);
|
||
|
||
/* Read tuning data and compare against expected pattern */
|
||
for (i = 0; i < word_count; i++) {
|
||
|
||
received_data = efx_sdio_readl(host, EFX_SDIO_BUFFER_DATA_PORT);
|
||
/* Compare against standard pattern */
|
||
if (received_data != reference_pattern[i]) {
|
||
mismatches++;
|
||
dev_dbg(&host->pdev->dev,
|
||
"Tuning data mismatch at word %d: got 0x%08x, expected 0x%08x\n",
|
||
i, received_data, reference_pattern[i]);
|
||
}
|
||
}
|
||
|
||
udelay(1000);
|
||
if (host->tuning_crc_error) {
|
||
dev_dbg(&host->pdev->dev,
|
||
"Tuning command CRC error detected\n");
|
||
host->tuning_crc_error = false; /* Reset CRC error flag after handling */
|
||
return 0; /* Failure due to CRC error */
|
||
}
|
||
|
||
/* Allow up to 2 mismatches due to electrical noise during tuning */
|
||
// SDIO: change to 0 mismatches for stricter tuning
|
||
if (mismatches == 0) {
|
||
return 1; /* Success */
|
||
} else {
|
||
dev_dbg(&host->pdev->dev, "Too many mismatches: %d\n", mismatches);
|
||
return 0; /* Failure */
|
||
}
|
||
}
|
||
|
||
static int efx_sdio_find_longest_consecutive_ones(u8 *row, int length)
|
||
{
|
||
int max_len, current_len, i;
|
||
|
||
max_len = 0;
|
||
current_len = 0;
|
||
|
||
for (i = 0; i < length; i++) {
|
||
if (row[i] == 1) {
|
||
current_len++;
|
||
if (current_len > max_len) {
|
||
max_len = current_len;
|
||
}
|
||
} else {
|
||
current_len = 0;
|
||
}
|
||
}
|
||
|
||
return max_len;
|
||
}
|
||
|
||
static int efx_sdio_find_center_of_consecutive_ones(u8 *row, int length)
|
||
{
|
||
int max_len, current_len, max_start, current_start, center, i;
|
||
|
||
max_len = 0;
|
||
current_len = 0;
|
||
max_start = 0;
|
||
current_start = 0;
|
||
|
||
for (i = 0; i < length; i++) {
|
||
if (row[i] == 1) {
|
||
if (current_len == 0) {
|
||
current_start = i;
|
||
}
|
||
current_len++;
|
||
if (current_len > max_len) {
|
||
max_len = current_len;
|
||
max_start = current_start;
|
||
}
|
||
} else {
|
||
current_len = 0;
|
||
}
|
||
}
|
||
|
||
/* Return center of longest consecutive sequence */
|
||
center = max_start + (max_len / 2);
|
||
return center;
|
||
}
|
||
|
||
int efx_sdio_find_optimal_timing(struct efx_sdio_host *host,
|
||
u8 result_map[][EFX_SDIO_MAX_PLL_SHIFT],
|
||
u32 max_sample_count)
|
||
{
|
||
int max_consecutive_length, row_length, optimal_sample_count,
|
||
optimal_pll_shift;
|
||
int *optimal_rows = NULL;
|
||
int optimal_row_count, center_row, i;
|
||
char optimal_row_str[32];
|
||
|
||
// Max sample count should be less than 8 With system clock is 200Mhz and target SDIO clock is 25Mhz.
|
||
// Shall perform basic tuning if the max sample count more than 8.
|
||
if (max_sample_count > 8) {
|
||
optimal_rows = kmalloc(max_sample_count * sizeof(int), GFP_KERNEL);
|
||
if (!optimal_rows) {
|
||
return -ENOMEM;
|
||
}
|
||
|
||
/* Find rows with longest consecutive 1's */
|
||
dev_dbg(&host->pdev->dev, "Analyzing timing results\n");
|
||
max_consecutive_length = 0;
|
||
for (i = 0; i < max_sample_count; i++) {
|
||
row_length = efx_sdio_find_longest_consecutive_ones(result_map[i],
|
||
EFX_SDIO_MAX_PLL_SHIFT);
|
||
dev_dbg(&host->pdev->dev, "Sample[%u]: consecutive_length=%d\n",
|
||
i, row_length);
|
||
if (row_length > max_consecutive_length) {
|
||
dev_dbg(&host->pdev->dev,
|
||
"New best: Sample[%u] length=%d\n",
|
||
i, row_length);
|
||
max_consecutive_length = row_length;
|
||
}
|
||
}
|
||
dev_dbg(&host->pdev->dev, "Best consecutive length: %d\n",
|
||
max_consecutive_length);
|
||
|
||
|
||
if (max_consecutive_length < EFX_SDIO_MIN_TIMING_MARGIN) {
|
||
dev_warn(&host->pdev->dev,
|
||
"Insufficient timing margin: %d (minimum %d)\n",
|
||
max_consecutive_length, EFX_SDIO_MIN_TIMING_MARGIN);
|
||
}
|
||
|
||
/* Collect all rows with maximum consecutive length */
|
||
optimal_row_count = 0;
|
||
for (i = 0; i < max_sample_count; i++) {
|
||
if (efx_sdio_find_longest_consecutive_ones(result_map[i],
|
||
EFX_SDIO_MAX_PLL_SHIFT) ==
|
||
max_consecutive_length) {
|
||
optimal_rows[optimal_row_count++] = i;
|
||
}
|
||
}
|
||
|
||
if (optimal_row_count == 0) {
|
||
kfree(optimal_rows);
|
||
return -ENODEV; /* No valid configurations found */
|
||
}
|
||
|
||
/* Find center row */
|
||
center_row = optimal_row_count / 2;
|
||
optimal_sample_count = optimal_rows[center_row];
|
||
|
||
/* Find center column within optimal row */
|
||
dev_dbg(&host->pdev->dev,
|
||
"Selected sample_count=%d from %d optimal rows\n",
|
||
optimal_sample_count, optimal_row_count);
|
||
|
||
/* Debug: Show the row being analyzed for center calculation */
|
||
optimal_row_str[0] = '\0';
|
||
for (i = 0; i < EFX_SDIO_MAX_PLL_SHIFT; i++) {
|
||
sprintf(optimal_row_str + strlen(optimal_row_str), "%d",
|
||
result_map[optimal_sample_count][i]);
|
||
}
|
||
dev_dbg(&host->pdev->dev,
|
||
"Analyzing row[%d]: [%s] for center calculation\n",
|
||
optimal_sample_count, optimal_row_str);
|
||
|
||
optimal_pll_shift =
|
||
efx_sdio_find_center_of_consecutive_ones(result_map[optimal_sample_count],
|
||
EFX_SDIO_MAX_PLL_SHIFT);
|
||
|
||
dev_dbg(&host->pdev->dev,
|
||
"Center PLL calculation result: pll_shift=%d\n",
|
||
optimal_pll_shift);
|
||
|
||
// Check before free
|
||
if (optimal_rows) {
|
||
kfree(optimal_rows);
|
||
}
|
||
|
||
} else {
|
||
|
||
u8 *flat_map;
|
||
int flat_length;
|
||
|
||
// Append the same result_map to make it look like 1D for better center calculation
|
||
flat_length = max_sample_count * 2 * EFX_SDIO_MAX_PLL_SHIFT;
|
||
flat_map = kmalloc(flat_length, GFP_KERNEL);
|
||
if (!flat_map) {
|
||
return -ENOMEM;
|
||
}
|
||
int idx = 0;
|
||
for (i = 0; i < max_sample_count; i++) {
|
||
memcpy(&flat_map[idx], result_map[i], EFX_SDIO_MAX_PLL_SHIFT);
|
||
idx += EFX_SDIO_MAX_PLL_SHIFT;
|
||
}
|
||
for (i = 0; i < max_sample_count; i++) {
|
||
memcpy(&flat_map[idx], result_map[i], EFX_SDIO_MAX_PLL_SHIFT);
|
||
idx += EFX_SDIO_MAX_PLL_SHIFT;
|
||
}
|
||
|
||
//Debug: Print the flat map for analysis
|
||
dev_dbg(&host->pdev->dev, "Flat map:\n");
|
||
for (i = 0; i < flat_length; i++) {
|
||
dev_dbg(&host->pdev->dev, "flat_map[%d] = %d\n", i, flat_map[i]);
|
||
}
|
||
|
||
max_consecutive_length = efx_sdio_find_longest_consecutive_ones(flat_map, flat_length);
|
||
dev_dbg(&host->pdev->dev, "Best consecutive length in flat map: %d\n", max_consecutive_length);
|
||
if (max_consecutive_length < EFX_SDIO_MIN_TIMING_MARGIN) {
|
||
dev_warn(&host->pdev->dev,
|
||
"Insufficient timing margin in flat map: %d (minimum %d)\n",
|
||
max_consecutive_length, EFX_SDIO_MIN_TIMING_MARGIN);
|
||
}
|
||
|
||
// DDR and SDR modes using different selection strategy for better tuning results
|
||
// DDR mode: select center of longest consecutive 1's for better stability as DDR is more sensitive to timing
|
||
// SDR mode: select first occurrence of longest consecutive 1's for better performance as SDR is less sensitive to timing and can benefit from more aggressive settings
|
||
if (host->mmc->ios.timing == MMC_TIMING_UHS_DDR50) {
|
||
int center_idx = efx_sdio_find_center_of_consecutive_ones(flat_map, flat_length);
|
||
optimal_sample_count = (center_idx%(EFX_SDIO_MAX_PLL_SHIFT * max_sample_count)) / EFX_SDIO_MAX_PLL_SHIFT;
|
||
optimal_pll_shift = center_idx % EFX_SDIO_MAX_PLL_SHIFT;
|
||
dev_info(&host->pdev->dev,
|
||
"DDR mode: Selected center of longest sequence at index %d (sample_count=%d, pll_shift=%d)\n",
|
||
center_idx, optimal_sample_count, optimal_pll_shift);
|
||
} else {
|
||
// For SDR modes, find the first occurrence of the longest consecutive 1's
|
||
int i, j;
|
||
optimal_sample_count = 0;
|
||
optimal_pll_shift = 0;
|
||
bool found = false;
|
||
for (i = 0; i < flat_length; i++) {
|
||
if (flat_map[i] == 1) {
|
||
int current_length = 1;
|
||
for (j = i + 1; j < flat_length && flat_map[j] == 1; j++) {
|
||
current_length++;
|
||
}
|
||
if (current_length == max_consecutive_length) {
|
||
optimal_sample_count = (i%(EFX_SDIO_MAX_PLL_SHIFT * max_sample_count))/EFX_SDIO_MAX_PLL_SHIFT;
|
||
optimal_pll_shift = i % EFX_SDIO_MAX_PLL_SHIFT;
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
dev_info(&host->pdev->dev,
|
||
"SDR mode: Selected first occurrence of longest sequence at index %d (sample_count=%d, pll_shift=%d)\n",
|
||
optimal_sample_count * EFX_SDIO_MAX_PLL_SHIFT + optimal_pll_shift,
|
||
optimal_sample_count, optimal_pll_shift);
|
||
}
|
||
|
||
kfree(flat_map);
|
||
}
|
||
|
||
/* Store optimal configuration */
|
||
host->optimal_sample_count = optimal_sample_count;
|
||
host->optimal_pll_shift = optimal_pll_shift;
|
||
host->optimal_margin = max_consecutive_length;
|
||
|
||
dev_dbg(&host->pdev->dev,
|
||
"Optimal timing found: sample_count=%u, pll_shift=%u, margin=%d\n",
|
||
optimal_sample_count, optimal_pll_shift, max_consecutive_length);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Execute tuning procedure for given opcode
|
||
* Reminder: For custom tuning, this function is called with host lock released
|
||
*/
|
||
int efx_sdio_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
||
{
|
||
struct efx_sdio_host *host;
|
||
u32 max_sample_count, sample_count, pll_shift;
|
||
int bus_width, success, ret;
|
||
unsigned long flags, timeout;
|
||
u8 (*tuning_result_map)[EFX_SDIO_MAX_PLL_SHIFT];
|
||
u32 tuning_loop = 10;
|
||
|
||
host = mmc_priv(mmc);
|
||
|
||
/* Set tuning in progress flag to suppress error logging */
|
||
host->tuning_in_progress = true;
|
||
|
||
dev_dbg(&host->pdev->dev, "Starting tuning: timing=%u, clock=%u Hz\n",
|
||
mmc->ios.timing, mmc->ios.clock);
|
||
|
||
/* Skip tuning if already tuned for current mode */
|
||
if (host->prev_timing == mmc->ios.timing && host->tuning_done) {
|
||
dev_dbg(&host->pdev->dev,
|
||
"Tuning skipped: already tuned for timing=%u (sample=%u, pll=%u)\n",
|
||
mmc->ios.timing, host->optimal_sample_count,
|
||
host->optimal_pll_shift);
|
||
return 0;
|
||
}
|
||
|
||
/* Validate SDIO tuning opcode
|
||
* MMC_SEND_TUNING_BLOCK or UINT_MAX for custom tuning
|
||
*/
|
||
if (opcode != MMC_SEND_TUNING_BLOCK && opcode != UINT_MAX) {
|
||
dev_err(&host->pdev->dev, "Unsupported tuning opcode: %u\n", opcode);
|
||
return -EINVAL;
|
||
}
|
||
|
||
/* Valid bus widths is 4-bit */
|
||
if (mmc->ios.bus_width != MMC_BUS_WIDTH_4) {
|
||
dev_warn(&host->pdev->dev,
|
||
"UHS-I Tuning only supported for 4-bit bus width, current width=%d\n",
|
||
mmc->ios.bus_width);
|
||
return -EINVAL;
|
||
}
|
||
bus_width = 4;
|
||
|
||
/* max_sample_count should match clk_div - sample count can't exceed
|
||
* clock divider
|
||
*/
|
||
/* Use actual clock divider */
|
||
max_sample_count = host->clk_div ? host->clk_div : 1;
|
||
|
||
dev_dbg(&host->pdev->dev,
|
||
"Starting tuning algorithm (bus_width=%d, max_sample=%u)\n",
|
||
bus_width, max_sample_count);
|
||
|
||
/* Dynamically allocate tuning result map */
|
||
tuning_result_map = kmalloc(max_sample_count *
|
||
sizeof(u8[EFX_SDIO_MAX_PLL_SHIFT]),
|
||
GFP_KERNEL);
|
||
if (!tuning_result_map) {
|
||
dev_err(&host->pdev->dev, "Failed to allocate tuning result map\n");
|
||
return -ENOMEM;
|
||
}
|
||
|
||
// Init entire map to 1
|
||
memset(tuning_result_map, 1,
|
||
max_sample_count * sizeof(u8[EFX_SDIO_MAX_PLL_SHIFT]));
|
||
|
||
dev_dbg(&host->pdev->dev, "Using clk_div=%u for sample count\n",
|
||
max_sample_count);
|
||
|
||
/* Set tuning timeout to 10 seconds */
|
||
timeout = jiffies + msecs_to_jiffies(10000);
|
||
|
||
/* Tuning loop */
|
||
for (tuning_loop = 0; tuning_loop < 10; tuning_loop++) {
|
||
dev_dbg(&host->pdev->dev, "Tuning loop %d:\n", tuning_loop + 1);
|
||
/* Phase 2: Timing configuration search */
|
||
dev_dbg(&host->pdev->dev, "Testing %d samples × %d PLL positions\n",
|
||
max_sample_count, EFX_SDIO_MAX_PLL_SHIFT);
|
||
|
||
for (sample_count = 0; sample_count < max_sample_count; sample_count++) {
|
||
|
||
int consecutive_passes = 0;
|
||
for (pll_shift = 0; pll_shift < EFX_SDIO_MAX_PLL_SHIFT; pll_shift++) {
|
||
|
||
dev_dbg(&host->pdev->dev,
|
||
"Tuning loop %d: Testing sample_count=%u, pll_shift=%u\n",
|
||
tuning_loop + 1, sample_count, pll_shift);
|
||
|
||
/* Apply timing configuration */
|
||
efx_sdio_set_timing_config(host, sample_count, pll_shift);
|
||
|
||
/* Execute validation test */
|
||
if (opcode == MMC_SEND_TUNING_BLOCK) {
|
||
success = efx_sdio_execute_tuning_command(host, bus_width);
|
||
} else {
|
||
/* Custom tuning - implement specific test if needed */
|
||
success = efx_sdio_execute_custom_tuning_command(host, bus_width);
|
||
}
|
||
|
||
// for non-DDR modes, additional checking needed to ensure result is successful
|
||
// success only 1 if bit 8 and bit 9 of EFX_SDIO_HOST_ADJUSTMENT register are both 1
|
||
if (success && mmc->ios.timing != MMC_TIMING_UHS_DDR50) {
|
||
u32 host_adjustment = efx_sdio_readl(host, EFX_SDIO_HOST_ADJUSTMENT);
|
||
if ((host_adjustment & 0x300) != 0x300) {
|
||
success = 0; // Mark as failure if either bit 8 or bit 9 is not set
|
||
dev_dbg(&host->pdev->dev,
|
||
"Additional check failed: host_adjustment=0x%08x\n",
|
||
host_adjustment);
|
||
}
|
||
}
|
||
|
||
/* Update result map */
|
||
if (!success) {
|
||
dev_dbg(&host->pdev->dev,
|
||
"Tuning [%u][%u]: FAIL\n",
|
||
sample_count, pll_shift);
|
||
|
||
// Only update the unsuccessful result
|
||
tuning_result_map[sample_count][pll_shift] = 0;
|
||
consecutive_passes = 0;
|
||
} else {
|
||
consecutive_passes++;
|
||
}
|
||
|
||
dev_dbg(&host->pdev->dev,
|
||
"Tuning [%u][%u]: %s (consecutive: %d)\n",
|
||
sample_count, pll_shift, success ? "PASS" : "FAIL",
|
||
consecutive_passes);
|
||
|
||
/* Check timeout */
|
||
if (time_after(jiffies, timeout)) {
|
||
dev_warn(&host->pdev->dev, "Tuning timeout after 10 seconds\n");
|
||
goto find_optimal;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
find_optimal:
|
||
|
||
/* Print timing map results - only for new timing modes */
|
||
if (!test_bit(mmc->ios.timing, &host->tuned_timing_modes)) {
|
||
dev_info(&host->pdev->dev, "Tuning result map:\n");
|
||
for (sample_count = 0; sample_count < max_sample_count;
|
||
sample_count++) {
|
||
char row_str[32] = "";
|
||
for (pll_shift = 0; pll_shift < EFX_SDIO_MAX_PLL_SHIFT;
|
||
pll_shift++) {
|
||
sprintf(row_str + strlen(row_str), "%d",
|
||
tuning_result_map[sample_count][pll_shift]);
|
||
}
|
||
dev_info(&host->pdev->dev, "Sample[%u]: [%s]\n",
|
||
sample_count, row_str);
|
||
}
|
||
}
|
||
|
||
/* Phase 4: Find optimal timing configuration using dynamic map */
|
||
ret = efx_sdio_find_optimal_timing(host, tuning_result_map,
|
||
max_sample_count);
|
||
if (ret == 0) {
|
||
/* Use optimal timing found by tuning algorithm */
|
||
efx_sdio_set_timing_config(host, host->optimal_sample_count,
|
||
host->optimal_pll_shift);
|
||
|
||
/* Show detailed results only for new timing modes */
|
||
if (!test_bit(mmc->ios.timing, &host->tuned_timing_modes)) {
|
||
dev_info(&host->pdev->dev,
|
||
"Tuning completed: sample=%u, pll=%u, margin=%u\n",
|
||
host->optimal_sample_count, host->optimal_pll_shift,
|
||
host->optimal_margin);
|
||
} else {
|
||
dev_info(&host->pdev->dev,
|
||
"Tuning reconfirmed: sample=%u, pll=%u\n",
|
||
host->optimal_sample_count, host->optimal_pll_shift);
|
||
}
|
||
|
||
host->tuning_done = true;
|
||
/* Mark this timing mode as successfully tuned */
|
||
set_bit(mmc->ios.timing, &host->tuned_timing_modes);
|
||
} else {
|
||
/* Fallback to safe timing configuration */
|
||
dev_warn(&host->pdev->dev,
|
||
"Tuning failed: %d, using fallback configuration\n",
|
||
ret);
|
||
if (max_sample_count == 1) {
|
||
efx_sdio_set_timing_config(host, 0, 2); /* Safe for 200MHz */
|
||
host->optimal_sample_count = 0;
|
||
host->optimal_pll_shift = 2;
|
||
} else {
|
||
/* Conservative default timing */
|
||
efx_sdio_set_timing_config(host, 1, 1);
|
||
host->optimal_sample_count = 1;
|
||
host->optimal_pll_shift = 1;
|
||
}
|
||
host->tuning_done = true; /* Mark as done to prevent retry loops */
|
||
/* Mark this timing mode as successfully tuned (fallback) */
|
||
set_bit(mmc->ios.timing, &host->tuned_timing_modes);
|
||
ret = 0; /* Return success to allow operation to continue */
|
||
}
|
||
|
||
/* Update previous timing for reference */
|
||
spin_lock_irqsave(&host->lock, flags);
|
||
host->prev_timing = mmc->ios.timing;
|
||
spin_unlock_irqrestore(&host->lock, flags);
|
||
|
||
/* Free dynamically allocated tuning result map */
|
||
kfree(tuning_result_map);
|
||
|
||
/* Clear tuning in progress flag */
|
||
host->tuning_in_progress = false;
|
||
|
||
if (ret == 0) {
|
||
dev_dbg(&host->pdev->dev, "Tuning completed successfully\n");
|
||
} else {
|
||
dev_err(&host->pdev->dev, "Tuning failed: %d\n", ret);
|
||
}
|
||
|
||
return ret;
|
||
}
|