// SPDX-License-Identifier: GPL-2.0-or-later /* * Efinix SDIO Host Controller Tuning Support * * Copyright (C) 2026 Efinix, Inc. * Author: Khor Swee Aun */ #include #include #include #include #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; }