Files
efinix-linux/drivers/mmc/host/efx_sdio_tuning.c
Swee Aun Khor 334eb3ba15 Add a new SDIO controller driver for Efinix devices
The driver supports UHS-I bus speed modes: SDR25, DDR50, and SDR104.
By default, the controller operates in SDR25 mode. Higher-speed modes
can be enabled via the Device Tree by adding the following properties
to the SDIO node:

  - sd-uhs-ddr50
  - sd-uhs-sdr104

When these properties are present, the driver negotiates the highest
supported UHS mode with the card and host.

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

589 lines
19 KiB
C
Raw 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;
int optimal_row_count, center_row, i;
char optimal_row_str[32];
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);
/* 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);
kfree(optimal_rows);
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);
}
/* 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 {
//SA tuning_result_map[sample_count][pll_shift] = 1;
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;
}