Files
efinix-linux/drivers/mmc/host/efx_sdio_tuning.c
Swee Aun Khor 9074d7744f Enhance SDIO tuning algorithm
Improve the SDIO tuning algorithm to increase stability and
performance under low-temperature conditions.

Signed-off-by: Swee Aun Khor <sakhor@efinixinc.com>
2026-08-09 22:44:47 -07:00

682 lines
24 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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;
}