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>
This commit is contained in:
@@ -1121,3 +1121,17 @@ config MMC_EFX_EMMC
|
||||
If you have an Efinix platform with an eMMC device, say Y here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_EFX_SDIO
|
||||
tristate "Efinix SDIO host controller support"
|
||||
depends on OF
|
||||
help
|
||||
This selects support for the Efinix SDIO Host Controller.
|
||||
The current controller support USH bus speed mode SDR25, DDR50 and SDR104.
|
||||
By default, driver only support SDR25. To enable DDR50 and SDR104, add
|
||||
sd-uhs-ddr50 or sd-uhs-sdr104 in DTS.
|
||||
It includes hardware reset support and is designed for embedded applications.
|
||||
|
||||
Say M here to build the driver as a module.
|
||||
|
||||
Say N to exclude it.
|
||||
|
||||
@@ -108,6 +108,8 @@ obj-$(CONFIG_MMC_HSQ) += mmc_hsq.o
|
||||
obj-$(CONFIG_MMC_SDHCI_EFX) += sdhci-efx.o
|
||||
obj-$(CONFIG_MMC_EFX_EMMC) += efx-emmc.o
|
||||
efx-emmc-y += efx_emmc_core.o efx_emmc_platform.o efx_emmc_dma.o efx_emmc_tuning.o
|
||||
obj-$(CONFIG_MMC_EFX_SDIO) += efx-sdio.o
|
||||
efx-sdio-y += efx_sdio_platform.o efx_sdio_core.o efx_sdio_dma.o efx_sdio_tuning.o
|
||||
|
||||
ifeq ($(CONFIG_CB710_DEBUG),y)
|
||||
CFLAGS-cb710-mmc += -DDEBUG
|
||||
|
||||
386
drivers/mmc/host/efx_sdio.h
Normal file
386
drivers/mmc/host/efx_sdio.h
Normal file
@@ -0,0 +1,386 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Efinix SDIO Host Controller Driver Header with DMA Support
|
||||
*
|
||||
* Copyright (C) 2026 Efinix, Inc.
|
||||
* Author: Khor Swee Aun <sakhor@efinixinc.com>
|
||||
*/
|
||||
|
||||
#ifndef __EFX_SDIO_H__
|
||||
#define __EFX_SDIO_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
/* Compatibility macros */
|
||||
#ifndef min3
|
||||
#define min3(x, y, z) min(min(x, y), z)
|
||||
#endif
|
||||
|
||||
/* ADMA descriptor definitions */
|
||||
#define EFX_ADMA_DESC_VALID BIT(0)
|
||||
#define EFX_ADMA_DESC_END BIT(1)
|
||||
#define EFX_ADMA_DESC_INT BIT(2)
|
||||
#define EFX_ADMA_DESC_NOP (0 << 4)
|
||||
#define EFX_ADMA_DESC_TRAN (2 << 4)
|
||||
#define EFX_ADMA_DESC_LINK (3 << 4)
|
||||
|
||||
#define EFX_ADMA_MAX_LEN 65536
|
||||
#define EFX_ADMA_DESC_ALIGN 8
|
||||
#define EFX_ADMA_TABLE_SZ (512 * 8) /* Support up to 512 descriptors */
|
||||
|
||||
/* DMA boundary sizes */
|
||||
#define EFX_DMA_BOUNDARY_4K 0
|
||||
#define EFX_DMA_BOUNDARY_8K 1
|
||||
#define EFX_DMA_BOUNDARY_16K 2
|
||||
#define EFX_DMA_BOUNDARY_32K 3
|
||||
#define EFX_DMA_BOUNDARY_64K 4
|
||||
#define EFX_DMA_BOUNDARY_128K 5
|
||||
#define EFX_DMA_BOUNDARY_256K 6
|
||||
#define EFX_DMA_BOUNDARY_512K 7
|
||||
|
||||
/* SDIO IP Register Offsets - Efinix SDIO Controller */
|
||||
#define EFX_SDIO_VERSION 0x000
|
||||
#define EFX_SDIO_BASE_REG0 0x004
|
||||
#define EFX_SDIO_BASE_STATUS_REG0 0x008
|
||||
#define EFX_SDIO_BASE_REG1 0x00C
|
||||
#define EFX_SDIO_ARG2 0x100
|
||||
#define EFX_SDIO_BLOCK_SIZE 0x104
|
||||
#define EFX_SDIO_ARG1 0x108
|
||||
#define EFX_SDIO_TRANSFER_MODE 0x10C
|
||||
#define EFX_SDIO_RESPONSE0 0x110
|
||||
#define EFX_SDIO_RESPONSE1 0x114
|
||||
#define EFX_SDIO_RESPONSE2 0x118
|
||||
#define EFX_SDIO_RESPONSE3 0x11C
|
||||
#define EFX_SDIO_BUFFER_DATA_PORT 0x120
|
||||
#define EFX_SDIO_PRESENT_STATE 0x124
|
||||
#define EFX_SDIO_HOST_CONTROL 0x128
|
||||
#define EFX_SDIO_INT_STATUS 0x130
|
||||
#define EFX_SDIO_INT_STATUS_EN 0x134
|
||||
#define EFX_SDIO_INT_SIGNAL_EN 0x138
|
||||
#define EFX_SDIO_HOST_CAPABILITIES 0x140
|
||||
#define EFX_SDIO_HOST_ADJUSTMENT 0x144
|
||||
#define EFX_SDIO_ADMA_SYS_ADDR_LOW 0x158
|
||||
#define EFX_SDIO_ADMA_SYS_ADDR_HIGH 0x15C
|
||||
|
||||
/* System Register Offsets */
|
||||
#define EFX_SYS_DATE_REG 0x000
|
||||
#define EFX_SYS_TEST_REG 0x004
|
||||
#define EFX_SYS_RESET_REG 0x008
|
||||
|
||||
/* Base Register 0 (0x004) */
|
||||
#define EFX_SDIO_BASE_REG0_CLK_EN BIT(16)
|
||||
#define EFX_SDIO_BASE_REG0_CLK_DIV_MASK 0xFFFF
|
||||
|
||||
/* Base Status Register 0 (0x008) */
|
||||
#define EFX_SDIO_BASE_STATUS_DAT_BUSY BIT(1)
|
||||
#define EFX_SDIO_BASE_STATUS_CMD_BUSY BIT(0)
|
||||
|
||||
/* Base Register 1 (0x00C) */
|
||||
#define EFX_SDIO_BASE_REG1_SAMPLE_CNT_SHIFT 16
|
||||
#define EFX_SDIO_BASE_REG1_SAMPLE_CNT_MASK (0xFFFF << 16)
|
||||
#define EFX_SDIO_BASE_REG1_PHASE_SHIFT 6
|
||||
#define EFX_SDIO_BASE_REG1_PHASE_MASK (0x7 << 6)
|
||||
#define EFX_SDIO_BASE_REG1_PHASE_PULSE BIT(0)
|
||||
|
||||
/* Block Size Register (0x104) */
|
||||
#define EFX_SDIO_BLOCK_COUNT_SHIFT 16
|
||||
#define EFX_SDIO_BLOCK_COUNT_MASK (0xFFFF << 16)
|
||||
#define EFX_SDIO_BLOCK_SIZE_MASK 0xFFF
|
||||
#define EFX_SDIO_DMA_BOUNDARY_SHIFT 12
|
||||
#define EFX_SDIO_DMA_BOUNDARY_MASK (0x7 << 12)
|
||||
|
||||
/* Transfer Mode Register (0x10C) */
|
||||
#define EFX_SDIO_CMD_INDEX_SHIFT 24
|
||||
#define EFX_SDIO_CMD_INDEX_MASK (0x3F << 24)
|
||||
#define EFX_SDIO_DATA_PRESENT BIT(21)
|
||||
#define EFX_SDIO_CMD_INDEX_CHECK_EN BIT(20)
|
||||
#define EFX_SDIO_CMD_CRC_CHECK_EN BIT(19)
|
||||
#define EFX_SDIO_RESP_TYPE_SHIFT 16
|
||||
#define EFX_SDIO_RESP_TYPE_MASK (0x3 << 16)
|
||||
#define EFX_SDIO_RESP_TYPE_NONE 0
|
||||
#define EFX_SDIO_RESP_TYPE_136 1
|
||||
#define EFX_SDIO_RESP_TYPE_48 2
|
||||
#define EFX_SDIO_RESP_TYPE_48_BUSY 3
|
||||
#define EFX_SDIO_MULTI_BLOCK_SEL BIT(5)
|
||||
#define EFX_SDIO_DATA_XFER_DIR BIT(4)
|
||||
#define EFX_SDIO_AUTO_CMD_EN_SHIFT 2
|
||||
#define EFX_SDIO_AUTO_CMD_EN_MASK (0x3 << 2)
|
||||
#define EFX_SDIO_BLOCK_COUNT_EN BIT(1)
|
||||
#define EFX_SDIO_DMA_EN BIT(0)
|
||||
|
||||
/* Present State Register (0x124) */
|
||||
#define EFX_SDIO_DAT_0_SIG_LVL BIT(20)
|
||||
#define EFX_SDIO_BUFFER_READ_EN BIT(11)
|
||||
#define EFX_SDIO_BUFFER_WRITE_EN BIT(10)
|
||||
#define EFX_SDIO_READ_XFER_ACTIVE BIT(9)
|
||||
#define EFX_SDIO_WRITE_XFER_ACTIVE BIT(8)
|
||||
#define EFX_SDIO_DAT_LINE_ACTIVE BIT(2)
|
||||
#define EFX_SDIO_CMD_INHIBIT_DAT BIT(1)
|
||||
#define EFX_SDIO_CMD_INHIBIT_CMD BIT(0)
|
||||
|
||||
/* Host Control Register (0x128) */
|
||||
#define EFX_SDIO_DATA_SAMPLING_MODE BIT(3)
|
||||
#define EFX_SDIO_DATA_WIDTH_SHIFT 1
|
||||
#define EFX_SDIO_DATA_WIDTH_MASK (0x3 << 1)
|
||||
#define EFX_SDIO_DATA_WIDTH_1BIT 0
|
||||
#define EFX_SDIO_DATA_WIDTH_4BIT 1
|
||||
#define EFX_SDIO_DATA_WIDTH_8BIT 2
|
||||
|
||||
/* Interrupt Status Register bits */
|
||||
#define EFX_SDIO_INT_ADMA_ERROR BIT(25)
|
||||
#define EFX_SDIO_INT_DATA_TIMEOUT_ERR BIT(22)
|
||||
#define EFX_SDIO_INT_DATA_CRC_ERR BIT(21)
|
||||
#define EFX_SDIO_INT_DATA_END_BIT_ERR BIT(20)
|
||||
#define EFX_SDIO_INT_CMD_INDEX_ERR BIT(19)
|
||||
#define EFX_SDIO_INT_CMD_END_BIT_ERR BIT(18)
|
||||
#define EFX_SDIO_INT_CMD_CRC_ERR BIT(17)
|
||||
#define EFX_SDIO_INT_CMD_TIMEOUT_ERR BIT(16)
|
||||
#define EFX_SDIO_INT_CARD BIT(8)
|
||||
#define EFX_SDIO_INT_BUFFER_READ_RDY BIT(5)
|
||||
#define EFX_SDIO_INT_BUFFER_WRITE_RDY BIT(4)
|
||||
#define EFX_SDIO_INT_DMA_INTERRUPT BIT(3)
|
||||
#define EFX_SDIO_INT_BLOCK_GAP_EVENT BIT(2)
|
||||
#define EFX_SDIO_INT_XFER_COMPLETE BIT(1)
|
||||
#define EFX_SDIO_INT_CMD_COMPLETE BIT(0)
|
||||
|
||||
#define EFX_SDIO_INT_ERROR_MASK (EFX_SDIO_INT_ADMA_ERROR | \
|
||||
EFX_SDIO_INT_DATA_TIMEOUT_ERR | \
|
||||
EFX_SDIO_INT_DATA_CRC_ERR | \
|
||||
EFX_SDIO_INT_DATA_END_BIT_ERR | \
|
||||
EFX_SDIO_INT_CMD_INDEX_ERR | \
|
||||
EFX_SDIO_INT_CMD_END_BIT_ERR | \
|
||||
EFX_SDIO_INT_CMD_CRC_ERR | \
|
||||
EFX_SDIO_INT_CMD_TIMEOUT_ERR)
|
||||
|
||||
/* SDIO Ccard interrupt not included in ALL_MASK as it is handled separately
|
||||
* by mmc core during SDIO IO driver initialization
|
||||
*/
|
||||
#define EFX_SDIO_INT_ALL_MASK (EFX_SDIO_INT_ERROR_MASK | \
|
||||
EFX_SDIO_INT_BUFFER_READ_RDY | \
|
||||
EFX_SDIO_INT_BUFFER_WRITE_RDY | \
|
||||
EFX_SDIO_INT_DMA_INTERRUPT | \
|
||||
EFX_SDIO_INT_BLOCK_GAP_EVENT | \
|
||||
EFX_SDIO_INT_XFER_COMPLETE | \
|
||||
EFX_SDIO_INT_CMD_COMPLETE)
|
||||
|
||||
/* System Reset Register bits */
|
||||
#define EFX_SYS_RESET_SDIO_DEV BIT(3)
|
||||
#define EFX_SYS_RESET_SDIO_IP BIT(2)
|
||||
|
||||
/* Host capabilities */
|
||||
#define EFX_SDIO_BASE_CLK_FREQ_MHZ 200
|
||||
#define EFX_SDIO_MAX_BLOCK_LENGTH 512
|
||||
#define EFX_SDIO_TIMEOUT_CLK_FREQ 200000000
|
||||
#define EFX_SDIO_IO_VOLTAGE_1_8V 0
|
||||
#define EFX_SDIO_IO_VOLTAGE_3_3V 1
|
||||
|
||||
/* Driver constants */
|
||||
#define EFX_SDIO_MIN_FREQ 400000 /* 400 KHz */
|
||||
#define EFX_SDIO_MAX_FREQ 200000000 /* 200 MHz */
|
||||
#define EFX_SDIO_PIO_TIMEOUT_MS 1000
|
||||
|
||||
/* Hardware specific constants from documentation */
|
||||
#define EFX_SDIO_CLOCK_STABILIZE_DELAY 1000 /* 1ms + 74 clock cycles */
|
||||
#define EFX_SDIO_RESET_PULSE_WIDTH 1 /* 1us minimum */
|
||||
#define EFX_SDIO_POST_RESET_DELAY 200 /* 200us minimum */
|
||||
#define EFX_SDIO_CMD_RETRY_COUNT 3 /* Command retry attempts */
|
||||
|
||||
/* Tuning algorithm constants */
|
||||
#define EFX_SDIO_MAX_PLL_SHIFT 8 /* 8 phase positions (45°) */
|
||||
#define EFX_SDIO_PLL_SETTLING_TIME 50 /* 50ms PLL settling time */
|
||||
#define EFX_SDIO_TUNING_TIMEOUT_MS 50 /* CMD19 timeout */
|
||||
#define EFX_SDIO_TUNING_POLL_INTERVAL 200 /* 200us polling interval */
|
||||
#define EFX_SDIO_TUNING_BLOCK_SIZE_4BIT 64 /* 4-bit bus tuning block */
|
||||
#define EFX_SDIO_TUNING_BLOCK_SIZE_8BIT 128 /* 8-bit bus tuning block */
|
||||
#define EFX_SDIO_MIN_TIMING_MARGIN 1 /* Min consecutive valid */
|
||||
|
||||
/**
|
||||
* struct efx_adma_desc - ADMA descriptor structure
|
||||
* @attr: Descriptor attributes (valid, end, interrupt, type)
|
||||
* @len: Data length for this descriptor
|
||||
* @addr: 32-bit DMA address
|
||||
*
|
||||
* Hardware ADMA descriptor structure, must be 8-byte aligned
|
||||
*/
|
||||
struct efx_adma_desc {
|
||||
u16 attr;
|
||||
u16 len;
|
||||
u32 addr;
|
||||
} __packed __aligned(8);
|
||||
|
||||
/**
|
||||
* struct efx_sdio_host - Efinix SDIO host controller instance
|
||||
* @mmc: MMC host structure
|
||||
* @ioaddr: Base address for SDIO registers
|
||||
* @sys_ioaddr: Base address for system registers
|
||||
* @clk: Controller clock
|
||||
* @irq: Interrupt number
|
||||
* @mrq: Current MMC request
|
||||
* @cmd: Current MMC command
|
||||
* @data: Current MMC data transfer
|
||||
* @base_clk: Base clock frequency
|
||||
* @current_clk: Current configured clock frequency
|
||||
* @bytes_to_transfer: Remaining bytes for PIO transfer
|
||||
* @blocks_done: Number of completed blocks
|
||||
* @sg_offset: Current offset in scatter-gather list
|
||||
* @adma_desc: ADMA descriptor table
|
||||
* @adma_desc_dma: DMA address of descriptor table
|
||||
* @adma_desc_sz: Size of descriptor table
|
||||
* @bounce_buffer: Bounce buffer for unaligned transfers
|
||||
* @bounce_dma: DMA address of bounce buffer
|
||||
* @bounce_buffer_size: Size of bounce buffer
|
||||
* @use_dma: Flag indicating DMA mode is active
|
||||
* @dma_64bit: Flag indicating 64-bit DMA support
|
||||
* @clk_div: Current clock divider value
|
||||
* @optimal_sample_count: Optimal sample count from tuning
|
||||
* @optimal_pll_shift: Optimal PLL shift from tuning
|
||||
* @optimal_margin: Timing margin from tuning
|
||||
* @tuning_done: Flag indicating tuning completion
|
||||
* @tuning_in_progress: Flag indicating active tuning
|
||||
* @prev_timing: Previous timing mode
|
||||
* @hs400_retune_pending: Flag indicating HS400 retune needed
|
||||
* @hs400_retune_work: Delayed work for HS400 retuning
|
||||
* @tuned_timing_modes: Bitmap of successfully tuned timing modes
|
||||
* @lock: Spinlock for protecting shared data
|
||||
* @pdev: Platform device
|
||||
*/
|
||||
struct efx_sdio_host {
|
||||
struct mmc_host *mmc;
|
||||
void __iomem *ioaddr;
|
||||
void __iomem *sys_ioaddr;
|
||||
struct clk *clk;
|
||||
int irq;
|
||||
u32 io_voltage;
|
||||
|
||||
struct mmc_request *mrq;
|
||||
struct mmc_command *cmd;
|
||||
struct mmc_data *data;
|
||||
|
||||
u32 base_clk;
|
||||
u32 current_clk;
|
||||
|
||||
/* Transfer state tracking */
|
||||
unsigned int bytes_to_transfer;
|
||||
unsigned int blocks_done;
|
||||
unsigned int sg_offset;
|
||||
|
||||
/* DMA related fields */
|
||||
struct efx_adma_desc *adma_desc;
|
||||
dma_addr_t adma_desc_dma;
|
||||
size_t adma_desc_sz;
|
||||
|
||||
void *bounce_buffer;
|
||||
dma_addr_t bounce_dma;
|
||||
unsigned int bounce_buffer_size;
|
||||
bool use_bounce;
|
||||
|
||||
bool use_dma;
|
||||
bool dma_64bit;
|
||||
|
||||
/* Tuning related fields */
|
||||
u32 clk_div;
|
||||
u32 optimal_sample_count;
|
||||
u32 optimal_pll_shift;
|
||||
u32 optimal_margin;
|
||||
bool tuning_done;
|
||||
bool tuning_in_progress;
|
||||
unsigned int prev_timing;
|
||||
bool hs400_retune_pending;
|
||||
bool tuning_crc_error;
|
||||
struct delayed_work hs400_retune_work;
|
||||
|
||||
/* Tuning state bitmap - tracks which timing modes have been tuned */
|
||||
unsigned long tuned_timing_modes;
|
||||
|
||||
spinlock_t lock;
|
||||
|
||||
struct platform_device *pdev;
|
||||
};
|
||||
|
||||
/* Debug macros */
|
||||
#define efx_sdio_dbg_irq(host, fmt, ...) \
|
||||
dev_dbg(&(host)->pdev->dev, fmt, ##__VA_ARGS__)
|
||||
#define efx_sdio_dbg_pio(host, fmt, ...) \
|
||||
dev_dbg(&(host)->pdev->dev, fmt, ##__VA_ARGS__)
|
||||
#define efx_sdio_dbg_cmd(host, fmt, ...) \
|
||||
dev_dbg(&(host)->pdev->dev, fmt, ##__VA_ARGS__)
|
||||
|
||||
/* Platform driver function prototypes */
|
||||
int efx_sdio_probe(struct platform_device *pdev);
|
||||
int efx_sdio_remove(struct platform_device *pdev);
|
||||
int efx_sdio_init_hw(struct efx_sdio_host *host);
|
||||
void efx_sdio_reset_hw(struct efx_sdio_host *host);
|
||||
void efx_sdio_hs400_retune_work(struct work_struct *work);
|
||||
|
||||
/* Core MMC host operation prototypes */
|
||||
void efx_sdio_request(struct mmc_host *mmc, struct mmc_request *mrq);
|
||||
void efx_sdio_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
|
||||
int efx_sdio_get_cd(struct mmc_host *mmc);
|
||||
int efx_sdio_card_busy_wrapper(struct mmc_host *mmc);
|
||||
int efx_sdio_get_ro(struct mmc_host *mmc);
|
||||
irqreturn_t efx_sdio_irq(int irq, void *dev_id);
|
||||
void efx_sdio_enable_sdio_irq(struct mmc_host *host, int enable);
|
||||
void efx_sdio_ack_sdio_irq(struct mmc_host *host);
|
||||
int efx_sdio_start_signal_voltage_switch(struct mmc_host *mmc, struct mmc_ios *ios);
|
||||
|
||||
/* Core helper function prototypes */
|
||||
void efx_sdio_send_command(struct efx_sdio_host *host, struct mmc_command *cmd);
|
||||
void efx_sdio_finish_request(struct efx_sdio_host *host,
|
||||
struct mmc_request *mrq);
|
||||
void efx_sdio_finish_command(struct efx_sdio_host *host);
|
||||
void efx_sdio_finish_data(struct efx_sdio_host *host);
|
||||
void efx_sdio_transfer_pio(struct efx_sdio_host *host);
|
||||
void efx_sdio_set_clock(struct efx_sdio_host *host, unsigned int clock);
|
||||
void efx_sdio_set_bus_width(struct efx_sdio_host *host, int width);
|
||||
void efx_sdio_set_timing(struct efx_sdio_host *host, unsigned int timing);
|
||||
bool efx_sdio_card_busy(struct efx_sdio_host *host);
|
||||
|
||||
/* DMA function prototypes */
|
||||
int efx_sdio_adma_table_pre(struct efx_sdio_host *host,
|
||||
struct mmc_data *data);
|
||||
void efx_sdio_adma_table_post(struct efx_sdio_host *host,
|
||||
struct mmc_data *data);
|
||||
void efx_sdio_prepare_dma(struct efx_sdio_host *host, struct mmc_data *data);
|
||||
void efx_sdio_cleanup_dma(struct efx_sdio_host *host, struct mmc_data *data);
|
||||
void efx_sdio_set_adma_addr(struct efx_sdio_host *host, dma_addr_t addr);
|
||||
|
||||
/* Tuning function prototypes */
|
||||
int efx_sdio_execute_tuning(struct mmc_host *mmc, u32 opcode);
|
||||
int efx_sdio_execute_tuning_command(struct efx_sdio_host *host,
|
||||
int bus_width);
|
||||
int efx_sdio_execute_custom_tuning_command(struct efx_sdio_host *host,
|
||||
int bus_width);
|
||||
void efx_sdio_set_timing_config(struct efx_sdio_host *host,
|
||||
u32 sample_count, u32 pll_shift);
|
||||
int efx_sdio_find_optimal_timing(struct efx_sdio_host *host,
|
||||
u8 result_map[][EFX_SDIO_MAX_PLL_SHIFT],
|
||||
u32 max_sample_count);
|
||||
|
||||
/* Register access helpers */
|
||||
static inline u32 efx_sdio_readl(struct efx_sdio_host *host, u32 reg)
|
||||
{
|
||||
return readl(host->ioaddr + reg);
|
||||
}
|
||||
|
||||
static inline void efx_sdio_writel(struct efx_sdio_host *host, u32 val, u32 reg)
|
||||
{
|
||||
writel(val, host->ioaddr + reg);
|
||||
}
|
||||
|
||||
static inline u32 efx_sdio_sys_readl(struct efx_sdio_host *host, u32 reg)
|
||||
{
|
||||
return readl(host->sys_ioaddr + reg);
|
||||
}
|
||||
|
||||
static inline void efx_sdio_sys_writel(struct efx_sdio_host *host, u32 val,
|
||||
u32 reg)
|
||||
{
|
||||
writel(val, host->sys_ioaddr + reg);
|
||||
}
|
||||
|
||||
#endif /* __EFX_SDIO_H__ */
|
||||
1097
drivers/mmc/host/efx_sdio_core.c
Normal file
1097
drivers/mmc/host/efx_sdio_core.c
Normal file
File diff suppressed because it is too large
Load Diff
227
drivers/mmc/host/efx_sdio_dma.c
Normal file
227
drivers/mmc/host/efx_sdio_dma.c
Normal file
@@ -0,0 +1,227 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Efinix SDIO Host Controller DMA Support
|
||||
*
|
||||
* Copyright (C) 2026 Efinix, Inc.
|
||||
* Author: Khor Swee Aun <sakhor@efinixinc.com>
|
||||
*/
|
||||
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "efx_sdio.h"
|
||||
|
||||
void efx_sdio_set_adma_addr(struct efx_sdio_host *host, dma_addr_t addr)
|
||||
{
|
||||
efx_sdio_writel(host, (u32)addr, EFX_SDIO_ADMA_SYS_ADDR_LOW);
|
||||
|
||||
if (host->dma_64bit) {
|
||||
efx_sdio_writel(host, (u32)((u64)addr >> 32),
|
||||
EFX_SDIO_ADMA_SYS_ADDR_HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
static void efx_sdio_adma_mark_end(struct efx_adma_desc *desc)
|
||||
{
|
||||
desc->attr |= EFX_ADMA_DESC_END;
|
||||
}
|
||||
|
||||
static void efx_sdio_adma_set_desc(struct efx_adma_desc *desc, u32 addr,
|
||||
u16 len, u16 attr)
|
||||
{
|
||||
desc->attr = attr;
|
||||
desc->len = len;
|
||||
desc->addr = addr;
|
||||
}
|
||||
|
||||
int efx_sdio_adma_table_pre(struct efx_sdio_host *host, struct mmc_data *data)
|
||||
{
|
||||
struct efx_adma_desc *desc;
|
||||
struct scatterlist *sg;
|
||||
dma_addr_t addr, align_addr;
|
||||
u32 len, offset, align_len;
|
||||
int i, desc_count = 0;
|
||||
|
||||
/* Init use_bounce flag */
|
||||
host->use_bounce = false;
|
||||
|
||||
/* Check if we need bounce buffer due to alignment requirements */
|
||||
for_each_sg(data->sg, sg, data->sg_len, i) {
|
||||
addr = sg_dma_address(sg);
|
||||
len = sg_dma_len(sg);
|
||||
|
||||
/* Check 4-byte alignment len requirement for DMA */
|
||||
if (len & 0x3) {
|
||||
host->use_bounce = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (host->use_bounce) {
|
||||
/* Use bounce buffer for unaligned transfers */
|
||||
if (!host->bounce_buffer) {
|
||||
dev_err(&host->pdev->dev, "Bounce buffer not available\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (data->blksz * data->blocks > host->bounce_buffer_size) {
|
||||
dev_err(&host->pdev->dev, "Transfer too large for bounce buffer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Copy data to bounce buffer for write operations */
|
||||
if (data->flags & MMC_DATA_WRITE) {
|
||||
struct scatterlist *sg;
|
||||
char *bounce_pos;
|
||||
int i;
|
||||
|
||||
bounce_pos = host->bounce_buffer;
|
||||
|
||||
for_each_sg(data->sg, sg, data->sg_len, i) {
|
||||
/* Copy data to bounce buffer */
|
||||
memcpy(bounce_pos, sg_virt(sg), sg->length);
|
||||
bounce_pos += sg->length;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup single descriptor for bounce buffer */
|
||||
desc = host->adma_desc;
|
||||
efx_sdio_adma_set_desc(desc, host->bounce_dma,
|
||||
data->blksz * data->blocks,
|
||||
EFX_ADMA_DESC_VALID | EFX_ADMA_DESC_TRAN);
|
||||
efx_sdio_adma_mark_end(desc);
|
||||
desc_count = 1;
|
||||
|
||||
} else {
|
||||
|
||||
/*Not using bounce buffer */
|
||||
/* Setup descriptors for scatter-gather list */
|
||||
desc = host->adma_desc;
|
||||
|
||||
for_each_sg(data->sg, sg, data->sg_len, i) {
|
||||
addr = sg_dma_address(sg);
|
||||
len = sg_dma_len(sg);
|
||||
offset = 0;
|
||||
|
||||
while (len > 0) {
|
||||
align_addr = addr + offset;
|
||||
align_len = min(len, (u32)EFX_ADMA_MAX_LEN);
|
||||
|
||||
if (desc_count >=
|
||||
(EFX_ADMA_TABLE_SZ / sizeof(struct efx_adma_desc))) {
|
||||
dev_err(&host->pdev->dev, "Too many ADMA descriptors\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
efx_sdio_adma_set_desc(&desc[desc_count], align_addr, align_len,
|
||||
EFX_ADMA_DESC_VALID | EFX_ADMA_DESC_TRAN);
|
||||
|
||||
offset += align_len;
|
||||
len -= align_len;
|
||||
desc_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (desc_count > 0) {
|
||||
efx_sdio_adma_mark_end(&desc[desc_count - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (desc_count == 0) {
|
||||
dev_err(&host->pdev->dev, "No ADMA descriptors created\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void efx_sdio_adma_table_post(struct efx_sdio_host *host, struct mmc_data *data)
|
||||
{
|
||||
|
||||
/* If bounce buffer is used, copy data from bounce buffer for read operations */
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
|
||||
if (host->use_bounce) {
|
||||
struct scatterlist *sg;
|
||||
char *bounce_pos;
|
||||
int i;
|
||||
|
||||
bounce_pos = host->bounce_buffer;
|
||||
|
||||
dma_sync_single_for_cpu(&host->pdev->dev, host->bounce_dma,
|
||||
data->blksz * data->blocks, DMA_FROM_DEVICE);
|
||||
|
||||
for_each_sg(data->sg, sg, data->sg_len, i) {
|
||||
/* Copy data from bounce buffer */
|
||||
memcpy(sg_virt(sg), bounce_pos, sg->length);
|
||||
bounce_pos += sg->length;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Sync scatter-gather list for CPU from device
|
||||
dma_sync_sg_for_cpu(&host->pdev->dev, data->sg, data->sg_len,
|
||||
DMA_FROM_DEVICE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void efx_sdio_prepare_dma(struct efx_sdio_host *host, struct mmc_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!host->use_dma || !data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Can not do DMA on a block size is not 4-byte aligned
|
||||
if ( (data->blksz ) & 3) {
|
||||
dev_warn(&host->pdev->dev,
|
||||
"Data block size %u not 4-byte aligned, falling back to PIO\n",
|
||||
data->blksz);
|
||||
host->use_dma = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Map scatter-gather list for DMA */
|
||||
ret = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len,
|
||||
(data->flags & MMC_DATA_READ) ?
|
||||
DMA_FROM_DEVICE : DMA_TO_DEVICE);
|
||||
if (ret == 0) {
|
||||
dev_err(&host->pdev->dev, "Failed to map DMA scatter-gather list\n");
|
||||
host->use_dma = false;
|
||||
return;
|
||||
}
|
||||
|
||||
data->sg_len = ret;
|
||||
/* Setup ADMA descriptor table */
|
||||
ret = efx_sdio_adma_table_pre(host, data);
|
||||
if (ret) {
|
||||
dev_err(&host->pdev->dev, "Failed to setup ADMA table: %d\n", ret);
|
||||
dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
|
||||
(data->flags & MMC_DATA_READ) ?
|
||||
DMA_FROM_DEVICE : DMA_TO_DEVICE);
|
||||
host->use_dma = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set ADMA system address */
|
||||
efx_sdio_set_adma_addr(host, host->adma_desc_dma);
|
||||
|
||||
}
|
||||
|
||||
void efx_sdio_cleanup_dma(struct efx_sdio_host *host, struct mmc_data *data)
|
||||
{
|
||||
if (!host->use_dma || !data) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Post-process ADMA table */
|
||||
efx_sdio_adma_table_post(host, data);
|
||||
|
||||
/* Unmap scatter-gather list */
|
||||
dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
|
||||
(data->flags & MMC_DATA_READ) ?
|
||||
DMA_FROM_DEVICE : DMA_TO_DEVICE);
|
||||
}
|
||||
426
drivers/mmc/host/efx_sdio_platform.c
Normal file
426
drivers/mmc/host/efx_sdio_platform.c
Normal file
@@ -0,0 +1,426 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Efinix SDIO Host Controller Platform Driver
|
||||
*
|
||||
* Copyright (C) 2026 Efinix, Inc.
|
||||
* Author: Khor Swee Aun <sakhor@efinixinc.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "efx_sdio.h"
|
||||
|
||||
static const struct mmc_host_ops efx_sdio_ops = {
|
||||
.request = efx_sdio_request,
|
||||
.set_ios = efx_sdio_set_ios,
|
||||
.get_cd = efx_sdio_get_cd,
|
||||
.get_ro = efx_sdio_get_ro,
|
||||
.card_busy = efx_sdio_card_busy_wrapper,
|
||||
.enable_sdio_irq = efx_sdio_enable_sdio_irq,
|
||||
.ack_sdio_irq = efx_sdio_ack_sdio_irq,
|
||||
.execute_tuning = efx_sdio_execute_tuning,
|
||||
.start_signal_voltage_switch = efx_sdio_start_signal_voltage_switch,
|
||||
};
|
||||
|
||||
/**
|
||||
* efx_sdio_reset_hw - Reset SDIO IP and device
|
||||
* @host: SDIO host controller instance
|
||||
*
|
||||
* Performs hardware reset sequence according to SDIO specification:
|
||||
* 1. Reset IP core (minimum 1us pulse)
|
||||
* 2. Reset SDIO device (minimum 1us pulse)
|
||||
* 3. Wait for device initialization (200us minimum)
|
||||
*/
|
||||
void efx_sdio_reset_hw(struct efx_sdio_host *host)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
/* Reset SDIO IP - minimum 1us pulse width per documentation */
|
||||
reg = efx_sdio_sys_readl(host, EFX_SYS_RESET_REG);
|
||||
reg |= EFX_SYS_RESET_SDIO_IP;
|
||||
efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG);
|
||||
udelay(EFX_SDIO_RESET_PULSE_WIDTH);
|
||||
|
||||
/* Release IP reset */
|
||||
reg &= ~EFX_SYS_RESET_SDIO_IP;
|
||||
efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG);
|
||||
udelay(EFX_SDIO_RESET_PULSE_WIDTH);
|
||||
|
||||
/* Reset SDIO device - minimum 1us pulse width (tRSTW) */
|
||||
reg |= EFX_SYS_RESET_SDIO_DEV;
|
||||
efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG);
|
||||
udelay(EFX_SDIO_RESET_PULSE_WIDTH);
|
||||
|
||||
/* Release device reset */
|
||||
reg &= ~EFX_SYS_RESET_SDIO_DEV;
|
||||
efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG);
|
||||
|
||||
/* Wait 200us (tRSCA) or 74 clock cycles per documentation */
|
||||
udelay(EFX_SDIO_POST_RESET_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* efx_sdio_init_hw - Initialize SDIO hardware
|
||||
* @host: SDIO host controller instance
|
||||
*
|
||||
* Initializes the SDIO controller hardware including:
|
||||
* - Hardware reset
|
||||
* - Capability reading and base clock setup
|
||||
* - Interrupt configuration
|
||||
* - Initial bus width and clock settings
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
int efx_sdio_init_hw(struct efx_sdio_host *host)
|
||||
{
|
||||
u32 caps, reg;
|
||||
|
||||
/* Reset hardware */
|
||||
efx_sdio_reset_hw(host);
|
||||
|
||||
/* Read capabilities */
|
||||
caps = efx_sdio_readl(host, EFX_SDIO_HOST_CAPABILITIES);
|
||||
host->base_clk = (caps & 0x3FF) * 1000000; /* Convert MHz to Hz */
|
||||
|
||||
if (host->base_clk == 0) {
|
||||
host->base_clk = EFX_SDIO_BASE_CLK_FREQ_MHZ * 1000000;
|
||||
}
|
||||
|
||||
host->io_voltage = (caps >> 12) & 0xF;
|
||||
|
||||
dev_info(&host->pdev->dev, "Base clock: %u Hz, IO Voltage: %sV, Capabilities: 0x%08x\n",
|
||||
host->base_clk, host->io_voltage == EFX_SDIO_IO_VOLTAGE_1_8V ? "1.8" : "3.0", caps);
|
||||
|
||||
/* Disable all interrupts initially */
|
||||
efx_sdio_writel(host, 0, EFX_SDIO_INT_SIGNAL_EN);
|
||||
efx_sdio_writel(host, 0, EFX_SDIO_INT_STATUS_EN);
|
||||
|
||||
/* Clear any pending interrupts */
|
||||
efx_sdio_writel(host, EFX_SDIO_INT_ALL_MASK, EFX_SDIO_INT_STATUS);
|
||||
|
||||
/* Set initial bus width to 1-bit */
|
||||
reg = efx_sdio_readl(host, EFX_SDIO_HOST_CONTROL);
|
||||
reg &= ~EFX_SDIO_DATA_WIDTH_MASK;
|
||||
reg |= (EFX_SDIO_DATA_WIDTH_1BIT << EFX_SDIO_DATA_WIDTH_SHIFT);
|
||||
|
||||
/*Bit 4 for IB or OOB interrupt
|
||||
* Mask bit 4 for IB interrupt
|
||||
*reg |= 0x10; Set bit 4 for OOB interrupt
|
||||
*/
|
||||
reg &= ~0x10;
|
||||
|
||||
efx_sdio_writel(host, reg, EFX_SDIO_HOST_CONTROL);
|
||||
|
||||
/* Set initial clock to identification frequency */
|
||||
efx_sdio_set_clock(host, EFX_SDIO_MIN_FREQ);
|
||||
|
||||
/* Wait for hardware to stabilize */
|
||||
msleep(10);
|
||||
|
||||
/* Enable interrupts */
|
||||
efx_sdio_writel(host, EFX_SDIO_INT_ALL_MASK, EFX_SDIO_INT_STATUS_EN);
|
||||
efx_sdio_writel(host, EFX_SDIO_INT_ALL_MASK, EFX_SDIO_INT_SIGNAL_EN);
|
||||
|
||||
dev_info(&host->pdev->dev, "Hardware initialized successfully\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int efx_sdio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mmc_host *mmc;
|
||||
struct efx_sdio_host *host;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
u32 version, present_state;
|
||||
|
||||
mmc = mmc_alloc_host(sizeof(struct efx_sdio_host), &pdev->dev);
|
||||
if (!mmc) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
host = mmc_priv(mmc);
|
||||
host->mmc = mmc;
|
||||
host->pdev = pdev;
|
||||
|
||||
spin_lock_init(&host->lock);
|
||||
|
||||
/* Initialize tuning-related fields */
|
||||
host->tuning_done = false;
|
||||
host->tuning_in_progress = false;
|
||||
host->optimal_sample_count = 0;
|
||||
host->optimal_pll_shift = 0;
|
||||
host->optimal_margin = 0;
|
||||
host->prev_timing = MMC_TIMING_LEGACY;
|
||||
host->hs400_retune_pending = false;
|
||||
host->tuned_timing_modes = 0; /* Clear all bits - no modes tuned yet */
|
||||
|
||||
/* Get memory resources */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
host->ioaddr = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(host->ioaddr)) {
|
||||
ret = PTR_ERR(host->ioaddr);
|
||||
goto err_free_host;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
host->sys_ioaddr = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(host->sys_ioaddr)) {
|
||||
ret = PTR_ERR(host->sys_ioaddr);
|
||||
goto err_free_host;
|
||||
}
|
||||
|
||||
/* Get clock */
|
||||
host->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(host->clk)) {
|
||||
ret = PTR_ERR(host->clk);
|
||||
dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
|
||||
goto err_free_host;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(host->clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to enable clock: %d\n", ret);
|
||||
goto err_free_host;
|
||||
}
|
||||
|
||||
/* Set up DMA mask */
|
||||
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
|
||||
if (ret) {
|
||||
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to set DMA mask\n");
|
||||
goto err_clk_disable;
|
||||
}
|
||||
host->dma_64bit = false;
|
||||
} else {
|
||||
host->dma_64bit = true;
|
||||
}
|
||||
|
||||
/* Allocate ADMA descriptor table */
|
||||
host->adma_desc_sz = EFX_ADMA_TABLE_SZ;
|
||||
host->adma_desc = dma_alloc_coherent(&pdev->dev, host->adma_desc_sz,
|
||||
&host->adma_desc_dma, GFP_KERNEL);
|
||||
if (!host->adma_desc) {
|
||||
dev_err(&pdev->dev, "Failed to allocate ADMA descriptor table\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
/* Allocate bounce buffer for unaligned transfers */
|
||||
host->bounce_buffer_size = 512 * 1024;
|
||||
host->bounce_buffer = dma_alloc_coherent(&pdev->dev,
|
||||
host->bounce_buffer_size,
|
||||
&host->bounce_dma, GFP_KERNEL);
|
||||
if (!host->bounce_buffer) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Failed to allocate bounce buffer, using software alignment\n");
|
||||
host->bounce_buffer_size = 0;
|
||||
}
|
||||
|
||||
/* Get IRQ */
|
||||
host->irq = platform_get_irq(pdev, 0);
|
||||
if (host->irq < 0) {
|
||||
ret = host->irq;
|
||||
goto err_free_dma;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, host->irq, efx_sdio_irq,
|
||||
IRQF_SHARED, mmc_hostname(mmc), host);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
|
||||
goto err_free_dma;
|
||||
}
|
||||
|
||||
/* Initialize hardware */
|
||||
ret = efx_sdio_init_hw(host);
|
||||
if (ret) {
|
||||
goto err_free_dma;
|
||||
}
|
||||
|
||||
/* Read version register to verify hardware is accessible */
|
||||
version = efx_sdio_readl(host, EFX_SDIO_VERSION);
|
||||
present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
|
||||
dev_info(&pdev->dev, "Version: 0x%08x, Present state: 0x%08x\n",
|
||||
version, present_state);
|
||||
|
||||
/* Set up MMC host */
|
||||
mmc->ops = &efx_sdio_ops;
|
||||
mmc->f_min = EFX_SDIO_MIN_FREQ;
|
||||
mmc->f_max = EFX_SDIO_MAX_FREQ;
|
||||
|
||||
/* SDIO-specific capabilities */
|
||||
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
|
||||
|
||||
/* Not support SD and eMMC
|
||||
SDIO IRQ NOTHREAD
|
||||
*/
|
||||
mmc->caps2 = MMC_CAP2_NO_SD | MMC_CAP2_NO_MMC | MMC_CAP2_SDIO_IRQ_NOTHREAD;
|
||||
|
||||
/* Clear all UHS capability bits first */
|
||||
mmc->caps &= ~MMC_CAP_UHS;
|
||||
|
||||
/* Read UHS mode flags directly from DTS */
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
bool uhs_set = false;
|
||||
|
||||
if (np) {
|
||||
if (of_property_read_bool(np, "sd-uhs-sdr25")) {
|
||||
mmc->caps |= MMC_CAP_UHS_SDR25;
|
||||
uhs_set = true;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(np, "sd-uhs-ddr50")) {
|
||||
mmc->caps |= MMC_CAP_UHS_DDR50;
|
||||
uhs_set = true;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(np, "sd-uhs-sdr104")) {
|
||||
mmc->caps |= MMC_CAP_UHS_SDR104;
|
||||
uhs_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Default if no UHS mode specified in DTS */
|
||||
if (!uhs_set) {
|
||||
mmc->caps |= MMC_CAP_UHS_SDR25;
|
||||
dev_info(&pdev->dev,
|
||||
"No UHS mode in DTS, defaulting to SDR25\n");
|
||||
} else {
|
||||
dev_info(&pdev->dev,
|
||||
"UHS modes: %s%s%s\n",
|
||||
(mmc->caps & MMC_CAP_UHS_SDR25) ? "SDR25 " : "",
|
||||
(mmc->caps & MMC_CAP_UHS_DDR50) ? "DDR50 " : "",
|
||||
(mmc->caps & MMC_CAP_UHS_SDR104) ? "SDR104 " : "");
|
||||
}
|
||||
|
||||
/* Voltage support: 1.7-1.95V and 2.7-3.6V */
|
||||
mmc->ocr_avail = MMC_VDD_165_195 | MMC_VDD_27_28 | MMC_VDD_28_29 |
|
||||
MMC_VDD_29_30 | MMC_VDD_30_31 | MMC_VDD_31_32 |
|
||||
MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_34_35 |
|
||||
MMC_VDD_35_36;
|
||||
|
||||
// Maximum segment size each scatter-gather descriptor can handle
|
||||
mmc->max_seg_size = 65536;
|
||||
// Maximum number of scatter-gather segments per request
|
||||
mmc->max_segs = 128;
|
||||
// Maximum request size in bytes for all scatter-gather descriptors
|
||||
mmc->max_req_size = mmc->max_seg_size * mmc->max_segs;
|
||||
// Maximum block size
|
||||
mmc->max_blk_size = EFX_SDIO_MAX_BLOCK_LENGTH;
|
||||
// Maximum number of blocks per request
|
||||
mmc->max_blk_count = 65535;
|
||||
|
||||
platform_set_drvdata(pdev, mmc);
|
||||
|
||||
ret = mmc_add_host(mmc);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to add SDIO host: %d\n", ret);
|
||||
goto err_free_dma;
|
||||
}
|
||||
|
||||
/* Force card detection after a delay */
|
||||
mmc_detect_change(mmc, msecs_to_jiffies(500));
|
||||
|
||||
dev_info(&pdev->dev, "Efinix SDIO Host Controller registered (DMA: %s)\n",
|
||||
host->adma_desc ? "enabled" : "disabled");
|
||||
dev_info(&pdev->dev, "SDIO caps: 0x%08x, OCR: 0x%08x\n",
|
||||
mmc->caps, mmc->ocr_avail);
|
||||
dev_info(&pdev->dev, "Clock range: %u - %u Hz\n", mmc->f_min, mmc->f_max);
|
||||
dev_info(&pdev->dev, "Max block size: %u, Max segments: %u\n",
|
||||
mmc->max_blk_size, mmc->max_segs);
|
||||
dev_info(&pdev->dev,
|
||||
"ADMA desc table: %zu bytes, Bounce buffer: %u bytes\n",
|
||||
host->adma_desc_sz, host->bounce_buffer_size);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_dma:
|
||||
if (host->bounce_buffer) {
|
||||
dma_free_coherent(&pdev->dev, host->bounce_buffer_size,
|
||||
host->bounce_buffer, host->bounce_dma);
|
||||
}
|
||||
if (host->adma_desc) {
|
||||
dma_free_coherent(&pdev->dev, host->adma_desc_sz,
|
||||
host->adma_desc, host->adma_desc_dma);
|
||||
}
|
||||
err_clk_disable:
|
||||
clk_disable_unprepare(host->clk);
|
||||
err_free_host:
|
||||
mmc_free_host(mmc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int efx_sdio_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mmc_host *mmc;
|
||||
struct efx_sdio_host *host;
|
||||
|
||||
mmc = platform_get_drvdata(pdev);
|
||||
host = mmc_priv(mmc);
|
||||
|
||||
mmc_remove_host(mmc);
|
||||
|
||||
/* Cancel any pending delayed work */
|
||||
//SA cancel_delayed_work_sync(&host->hs400_retune_work);
|
||||
|
||||
/* Disable interrupts */
|
||||
efx_sdio_writel(host, 0, EFX_SDIO_INT_SIGNAL_EN);
|
||||
efx_sdio_writel(host, 0, EFX_SDIO_INT_STATUS_EN);
|
||||
|
||||
/* Reset hardware */
|
||||
efx_sdio_reset_hw(host);
|
||||
|
||||
/* Free DMA resources */
|
||||
if (host->bounce_buffer) {
|
||||
dma_free_coherent(&pdev->dev, host->bounce_buffer_size,
|
||||
host->bounce_buffer, host->bounce_dma);
|
||||
}
|
||||
if (host->adma_desc) {
|
||||
dma_free_coherent(&pdev->dev, host->adma_desc_sz,
|
||||
host->adma_desc, host->adma_desc_dma);
|
||||
}
|
||||
|
||||
clk_disable_unprepare(host->clk);
|
||||
mmc_free_host(mmc);
|
||||
|
||||
dev_info(&pdev->dev, "Efinix SDIO Host Controller removed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id efx_sdio_of_match[] = {
|
||||
{ .compatible = "efinix,sdio-host-controller", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, efx_sdio_of_match);
|
||||
|
||||
static struct platform_driver efx_sdio_driver = {
|
||||
.probe = efx_sdio_probe,
|
||||
.remove = efx_sdio_remove,
|
||||
.driver = {
|
||||
.name = "efx-sdio",
|
||||
.of_match_table = efx_sdio_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(efx_sdio_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Efinix SDIO Host Controller Driver with DMA Support");
|
||||
MODULE_AUTHOR("Khor Swee Aun <sakhor@efinixinc.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_VERSION("1.0");
|
||||
588
drivers/mmc/host/efx_sdio_tuning.c
Normal file
588
drivers/mmc/host/efx_sdio_tuning.c
Normal file
@@ -0,0 +1,588 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user