Files
efinix-linux/drivers/mmc/host/efx_sdio_core.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

1098 lines
34 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Efinix SDIO Host Controller Core Operations
*
* Copyright (C) 2026 Efinix, Inc.
* Author: Khor Swee Aun <sakhor@efinixinc.com>
*/
#include <linux/delay.h>
#include <linux/scatterlist.h>
#include <linux/jiffies.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio.h>
#include "efx_sdio.h"
void efx_sdio_enable_sdio_irq_without_lock(struct mmc_host *host, int enable)
{
struct efx_sdio_host *sdio_host = mmc_priv(host);
u32 int_signal_en;
u32 int_status_en;
int_status_en = efx_sdio_readl(sdio_host, EFX_SDIO_INT_STATUS_EN);
int_signal_en = efx_sdio_readl(sdio_host, EFX_SDIO_INT_SIGNAL_EN);
if (enable) {
// Set EFX_SDIO_INT_CARD for EFX_SDIO_INT_STATUS_EN and EFX_SDIO_INT_SIGNAL_EN
int_status_en |= EFX_SDIO_INT_CARD;
int_signal_en |= EFX_SDIO_INT_CARD;
efx_sdio_writel(sdio_host, int_status_en, EFX_SDIO_INT_STATUS_EN);
efx_sdio_writel(sdio_host, int_signal_en, EFX_SDIO_INT_SIGNAL_EN);
} else {
// Clear EFX_SDIO_INT_CARD for EFX_SDIO_INT_STATUS_EN and EFX_SDIO_INT_SIGNAL_EN
int_status_en &= ~EFX_SDIO_INT_CARD;
int_signal_en &= ~EFX_SDIO_INT_CARD;
efx_sdio_writel(sdio_host, int_status_en, EFX_SDIO_INT_STATUS_EN);
efx_sdio_writel(sdio_host, int_signal_en, EFX_SDIO_INT_SIGNAL_EN);
}
}
bool efx_sdio_card_busy(struct efx_sdio_host *host)
{
// return true when card is busy (DAT0 line low)
// DAT0 EFX_SDIO_DAT_0_SIG_LVL 0 = busy, 1 = not busy
dev_dbg(&host->pdev->dev, "Card busy status: %d\n",
!(efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE) &
EFX_SDIO_DAT_0_SIG_LVL));
return !(efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE) &
EFX_SDIO_DAT_0_SIG_LVL);
}
void efx_sdio_finish_request(struct efx_sdio_host *host,
struct mmc_request *mrq)
{
/* Cleanup DMA if used */
if (host->data) {
efx_sdio_cleanup_dma(host, host->data);
}
host->mrq = NULL;
host->cmd = NULL;
host->data = NULL;
host->bytes_to_transfer = 0;
host->blocks_done = 0;
host->use_dma = false;
mmc_request_done(host->mmc, mrq);
}
static void efx_sdio_transfer_pio_read(struct efx_sdio_host *host)
{
struct mmc_data *data;
struct scatterlist *sg;
u32 *buf;
unsigned int words_in_fifo, words_transferred, i, remaining_bytes_transferred;
unsigned int sg_offset, remaining_in_sg, words_to_transfer;
u32 present_state;
data = host->data;
if (!data) {
return;
}
sg = data->sg;
if (!sg) {
dev_err(&host->pdev->dev, "No scatter-gather list for read\n");
return;
}
/* Pre-calculate words per block */
words_in_fifo = data->blksz / sizeof(u32);
sg_offset = host->sg_offset;
words_transferred = 0;
remaining_bytes_transferred = 0;
// sg_offset is current sg offset in bytes
// sg->length is total length of current sg entry
while (host->bytes_to_transfer > 0) {
while (sg && sg_offset >= sg->length) {
sg_offset -= sg->length;
sg = sg_next(sg);
}
if (!sg) {
dev_err(&host->pdev->dev,
"No more scatter-gather entries\n");
break;
}
buf = (u32 *)(sg_virt(sg) + sg_offset);
remaining_in_sg = sg->length - sg_offset;
if (host->bytes_to_transfer > 3) {
/* Calculate words to transfer */
// Words to transfer depends the min of remaining in sg and bytes to transfer
words_to_transfer = min(remaining_in_sg / sizeof(u32),
(unsigned int)(host->bytes_to_transfer / sizeof(u32))
);
// Having this checking to avoid remaining_in_sg = 0
// Need to sg_next in the upper while loop
if (words_to_transfer != 0) {
for (i = 0; i < words_to_transfer; i++) {
buf[i] = efx_sdio_readl(host, EFX_SDIO_BUFFER_DATA_PORT);
}
words_transferred += words_to_transfer;
sg_offset += words_to_transfer * 4;
host->bytes_to_transfer -= words_to_transfer * 4;
}
} else {
// Handle remaining bytes less than 4 bytes
// if bytes_to_copy = 4
// EFX_SDIO_BUFFER_DATA_PORT = 0xAABBCCDD
// buffer[0] = 0xDD
// buffer[1] = 0xCC
// buffer[2] = 0xBB
// buffer[3] = 0xAA
int bytes_to_copy = min(remaining_in_sg,
host->bytes_to_transfer
);
// Having this checking to avoid remaining_in_sg = 0
// Need to sg_next in the upper while loop
if (bytes_to_copy != 0) {
u32 received_data = efx_sdio_readl(host, EFX_SDIO_BUFFER_DATA_PORT);
for (i = 0; i < bytes_to_copy; i++) {
((u8 *)buf)[i] = (received_data >> ((4 - bytes_to_copy + i) * 8)) & 0xFF;
}
remaining_bytes_transferred += bytes_to_copy;
sg_offset += bytes_to_copy;
host->bytes_to_transfer -= bytes_to_copy;
}
}
efx_sdio_dbg_pio(host,
"PIO read: %d words, %d bytes remaining\n",
words_to_transfer, host->bytes_to_transfer);
}
host->sg_offset += words_transferred * 4 + remaining_bytes_transferred;
host->blocks_done = (data->blksz * data->blocks -
host->bytes_to_transfer) / data->blksz;
efx_sdio_dbg_pio(host,
"PIO read completed: %d words total, "
"with extra %d bytes, "
"%d blocks done, %d bytes remaining\n",
words_transferred, remaining_bytes_transferred,
host->blocks_done, host->bytes_to_transfer);
present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
efx_sdio_dbg_pio(host, "Present state after PIO read: 0x%08x\n",
present_state);
}
static void efx_sdio_transfer_pio_write(struct efx_sdio_host *host)
{
struct mmc_data *data;
struct scatterlist *sg;
u32 *buf;
unsigned int words_in_fifo, words_transferred, i, remaining_bytes_transferred;
unsigned int sg_offset, remaining_in_sg, words_to_transfer;
u32 present_state;
data = host->data;
if (!data) {
return;
}
sg = data->sg;
if (!sg) {
dev_err(&host->pdev->dev, "No scatter-gather list for write\n");
return;
}
/* Pre-calculate words per block */
words_in_fifo = data->blksz / sizeof(u32);
sg_offset = host->sg_offset;
words_transferred = 0;
remaining_bytes_transferred = 0;
while (host->bytes_to_transfer > 0) {
while (sg && sg_offset >= sg->length) {
sg_offset -= sg->length;
sg = sg_next(sg);
}
if (!sg) {
dev_err(&host->pdev->dev,
"No more scatter-gather entries\n");
break;
}
buf = (u32 *)(sg_virt(sg) + sg_offset);
remaining_in_sg = sg->length - sg_offset;
if (host->bytes_to_transfer > 3) {
/* Calculate words to transfer */
words_to_transfer = min(remaining_in_sg / sizeof(u32),
(unsigned int)(host->bytes_to_transfer / sizeof(u32)));
// Having this checking to avoid remaining_in_sg = 0
// Need to sg_next in the upper while loop
if (words_to_transfer != 0) {
for (i = 0; i < words_to_transfer; i++) {
efx_sdio_writel(host, buf[i], EFX_SDIO_BUFFER_DATA_PORT);
}
words_transferred += words_to_transfer;
sg_offset += words_to_transfer * 4;
host->bytes_to_transfer -= words_to_transfer * 4;
}
} else {
// Less than 4 bytes to copy
// Example 3 bytes to copy to WRITE_DATA_PORT
// buf[0] = 0xAA
// buf[1] = 0xBB
// buf[2] = 0xCC
// WRITE_DATA_PORT = 0x00CCBBAA
int bytes_to_copy = min(remaining_in_sg,
host->bytes_to_transfer
);
// Having this checking to avoid remaining_in_sg = 0
// Need to sg_next in the upper while loop
if (bytes_to_copy != 0) {
u32 write_data = 0;
for (i = 0; i < bytes_to_copy; i++) {
write_data |= ((u32)((u8 *)buf)[i]) << (i * 8);
}
efx_sdio_writel(host, write_data, EFX_SDIO_BUFFER_DATA_PORT);
remaining_bytes_transferred += bytes_to_copy;
sg_offset += bytes_to_copy;
host->bytes_to_transfer -= bytes_to_copy;
}
}
efx_sdio_dbg_pio(host,
"PIO write: %d words, %d bytes remaining\n",
words_to_transfer, host->bytes_to_transfer);
}
host->sg_offset += words_transferred * 4 + remaining_bytes_transferred;
host->blocks_done = (data->blksz * data->blocks -
host->bytes_to_transfer) / data->blksz;
efx_sdio_dbg_pio(host,
"PIO write completed: %d words total,"
"with extra %d bytes, "
"%d blocks done, %d bytes remaining\n",
words_transferred, remaining_bytes_transferred,
host->blocks_done, host->bytes_to_transfer);
present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
efx_sdio_dbg_pio(host, "Present state after PIO write: 0x%08x\n",
present_state);
}
void efx_sdio_transfer_pio(struct efx_sdio_host *host)
{
struct mmc_data *data;
u32 present_state;
data = host->data;
if (!data) {
return;
}
/* Check buffer ready and perform transfer */
present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
if (data->flags & MMC_DATA_READ) {
if (present_state & EFX_SDIO_BUFFER_READ_EN) {
efx_sdio_transfer_pio_read(host);
}
} else {
if (present_state & EFX_SDIO_BUFFER_WRITE_EN) {
efx_sdio_transfer_pio_write(host);
}
}
}
void efx_sdio_finish_data(struct efx_sdio_host *host)
{
struct mmc_data *data;
u32 present_state;
int retry_count;
data = host->data;
if (!data) {
return;
}
if (data->error == 0) {
data->bytes_xfered = data->blksz * data->blocks;
efx_sdio_dbg_cmd(host,
"Data transfer completed: %d bytes (DMA: %s)\n",
data->bytes_xfered,
host->use_dma ? "enabled" : "disabled");
} else {
data->bytes_xfered = 0;
dev_err(&host->pdev->dev,
"Data transfer failed with error %d\n",
data->error);
}
/* Clear data pointer */
host->data = NULL;
host->bytes_to_transfer = 0;
host->blocks_done = 0;
host->sg_offset = 0;
efx_sdio_dbg_cmd(host, "Finishing request\n");
if (host->mrq) {
efx_sdio_finish_request(host, host->mrq);
}
}
void efx_sdio_finish_command(struct efx_sdio_host *host)
{
struct mmc_command *cmd;
u32 resp[4];
u32 present_state;
u32 instatus;
cmd = host->cmd;
if (!cmd) {
return;
}
if (cmd->flags & MMC_RSP_PRESENT) {
if (cmd->flags & MMC_RSP_136) {
/* 120-bit response - read all 4 registers */
resp[0] = efx_sdio_readl(host, EFX_SDIO_RESPONSE0);
resp[1] = efx_sdio_readl(host, EFX_SDIO_RESPONSE1);
resp[2] = efx_sdio_readl(host, EFX_SDIO_RESPONSE2);
resp[3] = efx_sdio_readl(host, EFX_SDIO_RESPONSE3) & 0xFFFFFF;
/* Convert hardware 120-bit response to MMC core 136-bit format
* by shifting and concatenating registers with proper alignment */
cmd->resp[0] = resp[3] << 8 | resp[2] >> 24;
cmd->resp[1] = resp[2] << 8 | resp[1] >> 24;
cmd->resp[2] = resp[1] << 8 | resp[0] >> 24;
cmd->resp[3] = resp[0] << 8;
efx_sdio_dbg_cmd(host,
"CMD%d 136-bit response: %08x %08x %08x %08x\n",
cmd->opcode, cmd->resp[0], cmd->resp[1],
cmd->resp[2], cmd->resp[3]);
} else {
/* 48-bit response */
cmd->resp[0] = efx_sdio_readl(host, EFX_SDIO_RESPONSE0);
efx_sdio_dbg_cmd(host, "CMD%d response: 0x%08x\n",
cmd->opcode, cmd->resp[0]);
}
}
/* Clear the command pointer - we're done with command phase */
host->cmd = NULL;
/* If no data transfer, finish the request immediately */
if (!host->data) {
efx_sdio_dbg_cmd(host, "Command complete, finishing request\n");
if (host->mrq) {
efx_sdio_finish_request(host, host->mrq);
}
} else {
efx_sdio_dbg_cmd(host,
"Command complete, data transfer continues (DMA: %s)\n",
host->use_dma ? "enabled" : "disabled");
/* For PIO transfers, check if buffer is ready - single register read */
if (!host->use_dma) {
bool buffer_ready_read, buffer_ready_write;
present_state = efx_sdio_readl(host,
EFX_SDIO_PRESENT_STATE);
efx_sdio_dbg_cmd(host,
"Present state after command: 0x%08x\n",
present_state);
/* Cache register read result to avoid redundant access */
buffer_ready_read = !!(present_state & EFX_SDIO_BUFFER_READ_EN);
buffer_ready_write = !!(present_state & EFX_SDIO_BUFFER_WRITE_EN);
// Clear interrupts flag if write or read buffer ready is set
instatus = efx_sdio_readl(host, EFX_SDIO_INT_STATUS);
if (host->data->flags & MMC_DATA_READ) {
if (buffer_ready_read) {
efx_sdio_dbg_pio(host,
"Buffer immediately ready for read, starting PIO\n");
efx_sdio_transfer_pio(host);
// Clear EFX_SDIO_INT_BUFFER_READ_RDY interrupt flag
if (instatus & EFX_SDIO_INT_BUFFER_READ_RDY) {
efx_sdio_writel(host, EFX_SDIO_INT_BUFFER_READ_RDY,
EFX_SDIO_INT_STATUS);
}
}
} else {
if (buffer_ready_write) {
efx_sdio_dbg_pio(host,
"Buffer immediately ready for write, starting PIO\n");
efx_sdio_transfer_pio(host);
// Clear EFX_SDIO_INT_BUFFER_WRITE_RDY interrupt flag
if (instatus & EFX_SDIO_INT_BUFFER_WRITE_RDY) {
efx_sdio_writel(host, EFX_SDIO_INT_BUFFER_WRITE_RDY,
EFX_SDIO_INT_STATUS);
}
}
}
}
/* For DMA transfers, hardware handles data transfer automatically */
}
}
void efx_sdio_send_command(struct efx_sdio_host *host, struct mmc_command *cmd)
{
u32 command, present_state;
unsigned long timeout;
struct mmc_data *data;
u16 cmd_timeout;
data = cmd->data;
host->cmd = cmd;
efx_sdio_dbg_cmd(host, "Sending CMD%d, arg=0x%08x%s\n",
cmd->opcode, cmd->arg, (cmd->opcode == 12) ? " (STOP)" : "");
cmd_timeout = 500; /* Default timeout for all commands */
timeout = jiffies + msecs_to_jiffies(cmd_timeout);
while (time_before(jiffies, timeout)) {
present_state = efx_sdio_readl(host, EFX_SDIO_BASE_STATUS_REG0);
if (!(efx_sdio_readl(host, EFX_SDIO_BASE_STATUS_REG0) &
(EFX_SDIO_BASE_STATUS_CMD_BUSY |
EFX_SDIO_BASE_STATUS_DAT_BUSY))) {
break;
}
cpu_relax();
}
if (time_after_eq(jiffies, timeout)) {
dev_err(&host->pdev->dev,
"CMD/DAT line Busy timeout, base_status=0x%08x\n",
efx_sdio_readl(host, EFX_SDIO_BASE_STATUS_REG0));
cmd->error = -ETIMEDOUT;
if (host->mrq) {
efx_sdio_finish_request(host, host->mrq);
}
return;
}
/* Set command argument */
efx_sdio_writel(host, cmd->arg, EFX_SDIO_ARG1);
/* Build command register value */
command = (cmd->opcode << EFX_SDIO_CMD_INDEX_SHIFT) &
EFX_SDIO_CMD_INDEX_MASK;
if (cmd->flags & MMC_RSP_PRESENT) {
if (cmd->flags & MMC_RSP_136) {
command |= (EFX_SDIO_RESP_TYPE_136 << EFX_SDIO_RESP_TYPE_SHIFT);
} else if (cmd->flags & MMC_RSP_BUSY) {
command |= (EFX_SDIO_RESP_TYPE_48_BUSY << EFX_SDIO_RESP_TYPE_SHIFT);
} else {
command |= (EFX_SDIO_RESP_TYPE_48 << EFX_SDIO_RESP_TYPE_SHIFT);
}
if (cmd->flags & MMC_RSP_CRC) {
command |= EFX_SDIO_CMD_CRC_CHECK_EN;
}
if (cmd->flags & MMC_RSP_OPCODE) {
command |= EFX_SDIO_CMD_INDEX_CHECK_EN;
}
}
if (data) {
command |= EFX_SDIO_DATA_PRESENT;
/* Use DMA for transfers >= 64 bytes */
host->use_dma = (data->blksz * data->blocks >= 64);
/* Prepare DMA if enabled */
if (host->use_dma) {
efx_sdio_prepare_dma(host, data);
}
/* Set up data transfer */
efx_sdio_writel(host, (data->blocks << EFX_SDIO_BLOCK_COUNT_SHIFT) |
(data->blksz & EFX_SDIO_BLOCK_SIZE_MASK),
EFX_SDIO_BLOCK_SIZE);
if (data->blocks > 1) {
command |= EFX_SDIO_MULTI_BLOCK_SEL;
command |= EFX_SDIO_BLOCK_COUNT_EN;
}
if (data->flags & MMC_DATA_READ) {
command |= EFX_SDIO_DATA_XFER_DIR;
}
/* Enable DMA if prepared successfully */
if (host->use_dma) {
command |= EFX_SDIO_DMA_EN;
efx_sdio_dbg_cmd(host, "DMA enabled for data transfer\n");
} else {
/* Initialize PIO transfer state */
unsigned long total_bytes = (unsigned long)data->blksz * data->blocks;
if (total_bytes > INT_MAX) {
dev_err(&host->pdev->dev, "Transfer too large: %lu bytes\n", total_bytes);
cmd->error = -EINVAL;
if (host->mrq) {
efx_sdio_finish_request(host, host->mrq);
}
return;
}
host->bytes_to_transfer = (int)total_bytes;
host->blocks_done = 0;
host->sg_offset = 0;
efx_sdio_dbg_cmd(host, "Using PIO for data transfer\n");
}
/* Initialize data transfer state */
host->data = data;
efx_sdio_dbg_cmd(host, "Data transfer: %d blocks of %d bytes, %s, %s\n",
data->blocks, data->blksz,
(data->flags & MMC_DATA_READ) ? "read" : "write",
host->use_dma ? "DMA" : "PIO");
}
efx_sdio_dbg_cmd(host, "Command register: 0x%08x\n", command);
efx_sdio_writel(host, command, EFX_SDIO_TRANSFER_MODE);
}
void efx_sdio_set_clock(struct efx_sdio_host *host, unsigned int clock)
{
u32 div, reg;
unsigned long timeout;
if (clock == 0) {
/* Disable clock */
reg = efx_sdio_readl(host, EFX_SDIO_BASE_REG0);
reg &= ~EFX_SDIO_BASE_REG0_CLK_EN;
efx_sdio_writel(host, reg, EFX_SDIO_BASE_REG0);
host->current_clk = 0;
dev_info(&host->pdev->dev, "Clock disabled\n");
return;
}
/* Calculate clock divider (hardware constraint: must be 1 or even number)
* Formula: actual_freq = base_freq / divider
* This ensures we don't exceed the requested frequency.
*/
/* Enforce maximum frequency limit */
if (clock > EFX_SDIO_MAX_FREQ) {
clock = EFX_SDIO_MAX_FREQ;
}
/* Calculate clock divider */
if (clock >= host->base_clk) {
div = 1;
} else {
div = (host->base_clk + clock - 1) / clock; /* Round up */
/* Ensure even divider for hardware compliance */
if (div > 1 && (div & 1)) {
div += 1;
}
}
/* Disable clock first */
reg = efx_sdio_readl(host, EFX_SDIO_BASE_REG0);
reg &= ~EFX_SDIO_BASE_REG0_CLK_EN;
efx_sdio_writel(host, reg, EFX_SDIO_BASE_REG0);
/* Set divider */
reg = (reg & ~EFX_SDIO_BASE_REG0_CLK_DIV_MASK) |
(div & EFX_SDIO_BASE_REG0_CLK_DIV_MASK);
efx_sdio_writel(host, reg, EFX_SDIO_BASE_REG0);
/* Enable clock */
reg |= EFX_SDIO_BASE_REG0_CLK_EN;
efx_sdio_writel(host, reg, EFX_SDIO_BASE_REG0);
/*Set sample count*/
reg = efx_sdio_readl(host, EFX_SDIO_BASE_REG1);
reg &= ~EFX_SDIO_BASE_REG1_SAMPLE_CNT_MASK;
reg |= (div / 4) << EFX_SDIO_BASE_REG1_SAMPLE_CNT_SHIFT;
efx_sdio_writel(host, reg, EFX_SDIO_BASE_REG1);
/* Set read pause delay based on clock speed
* N = 4
* clk_div = 1, Read Pause Delay = 4 + 2 = 6
* clk_div = 2, Read Pause Delay = 4 + 6 = 10
* clk_div = 4 or 6,Read Pause Delay = 4 + 9 = 13
* clk_div >= 8, Read Pause Delay = 4 + 10 = 14
*/
reg = efx_sdio_readl(host, EFX_SDIO_HOST_ADJUSTMENT);
reg &= ~0xFF; // Clear bit0~bit7
if (div == 1) {
reg |= 6;
} else if (div == 2) {
reg |= 10;
} else if (div == 4 || div == 6) {
reg |= 13;
} else {
reg |= 14;
}
dev_info(&host->pdev->dev, "Setting Read Pause Delay to %u\n", reg & 0xFF);
efx_sdio_writel(host, reg, EFX_SDIO_HOST_ADJUSTMENT);
/* Clock stabilization delays based on eMMC specification:
* - Low freq (≤400kHz): 0.5-1ms for card identification mode
* - Medium freq (≤25MHz): 100-200us for normal operation
* - High freq (>25MHz): 50-100us for high-speed modes
* These delays ensure PLL lock and signal integrity.
*/
if (clock <= 400000) {
usleep_range(500, 1000);
} else if (clock <= 25000000) {
usleep_range(100, 200);
} else {
usleep_range(50, 100);
}
/* Verify controller is ready */
timeout = jiffies + msecs_to_jiffies(50);
while (time_before(jiffies, timeout)) {
if (!(efx_sdio_readl(host, EFX_SDIO_BASE_STATUS_REG0) &
(EFX_SDIO_BASE_STATUS_CMD_BUSY |
EFX_SDIO_BASE_STATUS_DAT_BUSY))) {
break;
}
cpu_relax();
}
host->current_clk = host->base_clk / div;
host->clk_div = div; /* Store for tuning algorithm */
dev_dbg(&host->pdev->dev, "Set clock to %u Hz (div=%u, actual=%u)\n",
clock, div, host->current_clk);
}
void efx_sdio_set_bus_width(struct efx_sdio_host *host, int width)
{
u32 reg;
reg = efx_sdio_readl(host, EFX_SDIO_HOST_CONTROL);
reg &= ~EFX_SDIO_DATA_WIDTH_MASK;
switch (width) {
case MMC_BUS_WIDTH_1:
reg |= (EFX_SDIO_DATA_WIDTH_1BIT << EFX_SDIO_DATA_WIDTH_SHIFT);
break;
case MMC_BUS_WIDTH_4:
reg |= (EFX_SDIO_DATA_WIDTH_4BIT << EFX_SDIO_DATA_WIDTH_SHIFT);
break;
default:
dev_warn(&host->pdev->dev, "Unsupported bus width: %d\n", width);
return;
}
efx_sdio_writel(host, reg, EFX_SDIO_HOST_CONTROL);
dev_dbg(&host->pdev->dev, "Set bus width to %d bits\n", width);
}
void efx_sdio_set_timing(struct efx_sdio_host *host, unsigned int timing)
{
u32 reg;
reg = efx_sdio_readl(host, EFX_SDIO_HOST_CONTROL);
switch (timing) {
case MMC_TIMING_LEGACY:
case MMC_TIMING_MMC_HS:
case MMC_TIMING_UHS_SDR12:
case MMC_TIMING_UHS_SDR25:
case MMC_TIMING_UHS_SDR50:
case MMC_TIMING_UHS_SDR104:
reg &= ~EFX_SDIO_DATA_SAMPLING_MODE;
/* Set SDR mode */
break;
case MMC_TIMING_UHS_DDR50:
reg |= EFX_SDIO_DATA_SAMPLING_MODE;
/* Set DDR mode */
break;
default:
dev_warn(&host->pdev->dev, "Unsupported timing: %d\n", timing);
return;
}
efx_sdio_writel(host, reg, EFX_SDIO_HOST_CONTROL);
dev_dbg(&host->pdev->dev, "Set timing mode: %s (%d)\n",
timing == MMC_TIMING_LEGACY ? "Legacy" :
timing == MMC_TIMING_MMC_HS ? "High Speed" :
timing == MMC_TIMING_UHS_DDR50 ? "DDR50" :
timing == MMC_TIMING_UHS_SDR12 ? "SDR12" :
timing == MMC_TIMING_UHS_SDR25 ? "SDR25" :
timing == MMC_TIMING_UHS_SDR50 ? "SDR50" :
timing == MMC_TIMING_UHS_SDR104 ? "SDR104" : "Unknown", timing);
/* Update previous timing for reference */
host->prev_timing = timing;
}
irqreturn_t efx_sdio_irq(int irq, void *dev_id)
{
struct efx_sdio_host *host;
u32 intstat, present_state;
irqreturn_t result;
host = dev_id;
result = IRQ_NONE;
spin_lock(&host->lock);
intstat = efx_sdio_readl(host, EFX_SDIO_INT_STATUS);
if (!intstat) {
goto out;
}
present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE);
efx_sdio_dbg_irq(host, "IRQ: status=0x%08x, present=0x%08x\n",
intstat, present_state);
// Clear all interrupt status bits first
efx_sdio_writel(host, intstat, EFX_SDIO_INT_STATUS);
result = IRQ_HANDLED;
/* Handle error interrupts first */
if (intstat & EFX_SDIO_INT_ERROR_MASK) {
/* Handle ADMA error specifically */
if (intstat & EFX_SDIO_INT_ADMA_ERROR) {
dev_err(&host->pdev->dev, "ADMA error detected\n");
if (host->data) {
host->data->error = -EIO;
}
}
/* Log error but don't spam for expected errors during detection */
if (host->cmd && (host->cmd->opcode == 52 || host->cmd->opcode == 8 ||
host->cmd->opcode == 5 || host->cmd->opcode == 55)) {
efx_sdio_dbg_irq(host,
"Expected timeout for CMD%d during card detection\n",
host->cmd->opcode);
} else if (host->tuning_in_progress) {
if (intstat & (EFX_SDIO_INT_CMD_CRC_ERR |
EFX_SDIO_INT_DATA_CRC_ERR)) {
host->tuning_crc_error = true;
}
} else {
// Log message with CMD index if possible
if (host) {
dev_err(&host->pdev->dev, "Error interrupt: 0x%08x CMD:%d\n",
(unsigned int)(intstat & EFX_SDIO_INT_ERROR_MASK),
host->cmd ? host->cmd->opcode : -1);
} else {
dev_err(&host->pdev->dev, "Error interrupt: 0x%08x CMD:host is NULL\n",
(unsigned int)(intstat & EFX_SDIO_INT_ERROR_MASK));
}
}
if (host->cmd) {
if (intstat & EFX_SDIO_INT_CMD_TIMEOUT_ERR) {
host->cmd->error = -ETIMEDOUT;
dev_dbg(&host->pdev->dev, "CMD%d timeout\n", host->cmd->opcode);
host->cmd = NULL;
if (host->mrq) {
efx_sdio_finish_request(host, host->mrq);
}
goto out;
} else if (intstat & (EFX_SDIO_INT_CMD_CRC_ERR |
EFX_SDIO_INT_CMD_END_BIT_ERR |
EFX_SDIO_INT_CMD_INDEX_ERR)) {
host->cmd->error = -EILSEQ;
if (host->cmd->opcode == 6) {
dev_err(&host->pdev->dev,
"CMD6 (mode switch) CRC/protocol error - arg=0x%08x\n",
host->cmd->arg);
} else {
dev_err(&host->pdev->dev,
"CMD%d CRC/protocol error\n",
host->cmd->opcode);
}
}
}
if (host->data) {
if (intstat & EFX_SDIO_INT_DATA_TIMEOUT_ERR) {
host->data->error = -ETIMEDOUT;
dev_err(&host->pdev->dev, "Data timeout error\n");
}
if (intstat & EFX_SDIO_INT_DATA_CRC_ERR) {
host->data->error = -EILSEQ;
dev_err(&host->pdev->dev, "Data CRC error\n");
}
if (intstat & EFX_SDIO_INT_DATA_END_BIT_ERR) {
host->data->error = -EILSEQ;
dev_err(&host->pdev->dev, "Data end bit error\n");
}
}
if (!(host->cmd && host->cmd->opcode == 12 &&
(intstat & EFX_SDIO_INT_CMD_TIMEOUT_ERR))) {
if (host->mrq) {
efx_sdio_finish_request(host, host->mrq);
goto out;
}
}
}
/* Handle DMA interrupt */
if (intstat & EFX_SDIO_INT_DMA_INTERRUPT) {
/* DMA boundary reached, but transfer continues automatically */
efx_sdio_dbg_irq(host, "DMA interrupt\n");
}
/* Handle command completion */
if (intstat & EFX_SDIO_INT_CMD_COMPLETE) {
efx_sdio_dbg_irq(host, "Command complete\n");
efx_sdio_finish_command(host);
}
/* Handle SDIO Card interrupt */
if (intstat & EFX_SDIO_INT_CARD) {
efx_sdio_dbg_irq(host, "SDIO card interrupt\n");
efx_sdio_enable_sdio_irq_without_lock(host->mmc, 0);
sdio_signal_irq(host->mmc);
}
/* Handle data buffer ready interrupts (for PIO only) */
if (likely(!host->use_dma) && host->data) {
if (intstat & (EFX_SDIO_INT_BUFFER_READ_RDY | EFX_SDIO_INT_BUFFER_WRITE_RDY)) {
if (host->bytes_to_transfer != 0) {
efx_sdio_dbg_irq(host, "Buffer ready interrupt for %s\n",
(intstat & EFX_SDIO_INT_BUFFER_READ_RDY) ? "read" : "write");
efx_sdio_transfer_pio(host);
if (host->bytes_to_transfer > 0) {
efx_sdio_dbg_pio(host,
"Waiting for more data: %d bytes remaining\n",
host->bytes_to_transfer);
}
// When bytes_to_transfer reaches 0, the transfer is complete
// Check again transfer complete interrupt status if current instatus transfer complete interrup is not set
if ((host->bytes_to_transfer == 0) && ((intstat & EFX_SDIO_INT_XFER_COMPLETE) == 0)) {
if (efx_sdio_readl(host, EFX_SDIO_INT_STATUS) & EFX_SDIO_INT_XFER_COMPLETE) {
// Update intstat to include transfer complete
// So that it can be handled in subsequent Handle transfer completion code
intstat |= EFX_SDIO_INT_XFER_COMPLETE;
// Clear the transfer complete interrupt status bit
efx_sdio_writel(host, EFX_SDIO_INT_XFER_COMPLETE, EFX_SDIO_INT_STATUS);
}
}
}
}
// Handle no more data bytes to transfer as handled during command completion
// Possible host->bytes_to_transfer == 0 here
if ((host->bytes_to_transfer == 0) && ((intstat & EFX_SDIO_INT_XFER_COMPLETE) == 0)) {
if (efx_sdio_readl(host, EFX_SDIO_INT_STATUS) & EFX_SDIO_INT_XFER_COMPLETE) {
// Update intstat to include transfer complete
// So that it can be handled in subsequent Handle transfer completion code
intstat |= EFX_SDIO_INT_XFER_COMPLETE;
// Clear the transfer complete interrupt status bit
efx_sdio_writel(host, EFX_SDIO_INT_XFER_COMPLETE, EFX_SDIO_INT_STATUS);
}
}
}
/* Handle transfer completion */
if (intstat & EFX_SDIO_INT_XFER_COMPLETE) {
efx_sdio_dbg_irq(host, "Transfer complete interrupt (DMA: %s)\n",
host->use_dma ? "enabled" : "disabled");
if (!host->use_dma && host->data && host->bytes_to_transfer != 0) {
dev_err(&host->pdev->dev,
"Error! PIO transfer complete interrupt but %d bytes remain\n",
host->bytes_to_transfer);
}
efx_sdio_finish_data(host);
}
/* Debug: Check for any unhandled interrupts */
if (intstat & ~(EFX_SDIO_INT_CMD_COMPLETE | EFX_SDIO_INT_BUFFER_READ_RDY |
EFX_SDIO_INT_BUFFER_WRITE_RDY | EFX_SDIO_INT_DMA_INTERRUPT |
EFX_SDIO_INT_XFER_COMPLETE | EFX_SDIO_INT_ERROR_MASK |
EFX_SDIO_INT_CARD)) {
efx_sdio_dbg_irq(host, "Unhandled interrupt bits: 0x%08x\n",
(unsigned int)(intstat &
~(EFX_SDIO_INT_CMD_COMPLETE |
EFX_SDIO_INT_BUFFER_READ_RDY |
EFX_SDIO_INT_BUFFER_WRITE_RDY |
EFX_SDIO_INT_DMA_INTERRUPT |
EFX_SDIO_INT_XFER_COMPLETE |
EFX_SDIO_INT_ERROR_MASK |
EFX_SDIO_INT_CARD)));
}
out:
spin_unlock(&host->lock);
return result;
}
void efx_sdio_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct efx_sdio_host *host;
unsigned long flags;
host = mmc_priv(mmc);
spin_lock_irqsave(&host->lock, flags);
if (!host->clk) {
dev_err(&host->pdev->dev, "No clock available\n");
mrq->cmd->error = -ENODEV;
mmc_request_done(mmc, mrq);
spin_unlock_irqrestore(&host->lock, flags);
return;
}
efx_sdio_dbg_cmd(host, "New request: CMD%d\n", mrq->cmd->opcode);
host->mrq = mrq;
efx_sdio_send_command(host, mrq->cmd);
spin_unlock_irqrestore(&host->lock, flags);
}
void efx_sdio_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct efx_sdio_host *host;
unsigned long flags;
bool need_host_control_update = false;
host = mmc_priv(mmc);
spin_lock_irqsave(&host->lock, flags);
if (ios->clock != host->current_clk) {
// info print
dev_info(&host->pdev->dev, "Changing clock from %u Hz to %u Hz\n",
host->current_clk, ios->clock);
efx_sdio_set_clock(host, ios->clock);
}
if (ios->bus_width != MMC_BUS_WIDTH_1) {
efx_sdio_set_bus_width(host, ios->bus_width);
need_host_control_update = true;
}
/* Log significant timing mode changes */
if (ios->timing != host->prev_timing) {
dev_dbg(&host->pdev->dev, "Timing mode: %u -> %u, Clock: %u Hz\n",
host->prev_timing, ios->timing, ios->clock);
}
efx_sdio_set_timing(host, ios->timing);
spin_unlock_irqrestore(&host->lock, flags);
// Custom tuning handling for SDR12, SDR25 or DDR50 && clock >= 12.5MHz
if (host->current_clk >= 12500000 &&
(host->prev_timing == MMC_TIMING_UHS_SDR12 ||
host->prev_timing == MMC_TIMING_UHS_SDR25 ||
host->prev_timing == MMC_TIMING_UHS_DDR50 )) {
dev_info(&host->pdev->dev,
"Timing mode changed to %d, performing custom tuning\n", host->prev_timing);
// Use UINT32_MAX to indicate custom tuning for SDR12, SDR25 and DDR50
efx_sdio_execute_tuning(host->mmc, UINT_MAX);
}
}
int efx_sdio_get_cd(struct mmc_host *mmc)
{
return 1; /* always present */
}
int efx_sdio_card_busy_wrapper(struct mmc_host *mmc)
{
struct efx_sdio_host *host;
host = mmc_priv(mmc);
return !(efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE) &
EFX_SDIO_DAT_0_SIG_LVL);
}
int efx_sdio_get_ro(struct mmc_host *mmc)
{
return 0; /* Never read-only */
}
int efx_sdio_start_signal_voltage_switch(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct efx_sdio_host *host;
unsigned long flags;
int ret = 0;
host = mmc_priv(mmc);
spin_lock_irqsave(&host->lock, flags);
switch(ios->signal_voltage) {
case MMC_SIGNAL_VOLTAGE_330:
if (host->io_voltage != EFX_SDIO_IO_VOLTAGE_3_3V)
{
dev_warn(&host->pdev->dev,
"Voltage switching to 3.3V not supported\n");
ret = -EOPNOTSUPP;
} else {
dev_dbg(&host->pdev->dev, "Switching to 3.3V signal voltage\n");
}
break;
case MMC_SIGNAL_VOLTAGE_180:
if (host->io_voltage != EFX_SDIO_IO_VOLTAGE_1_8V)
{
dev_warn(&host->pdev->dev,
"Voltage switching to 1.8V not supported\n");
ret = -EOPNOTSUPP;
break;
} else {
dev_dbg(&host->pdev->dev, "Switching to 1.8V signal voltage\n");
}
break;
default:
dev_warn(&host->pdev->dev,
"Unsupported signal voltage: %d\n", ios->signal_voltage);
ret = -EOPNOTSUPP;
break;
}
spin_unlock_irqrestore(&host->lock, flags);
return ret;
}
void efx_sdio_enable_sdio_irq(struct mmc_host *host, int enable)
{
struct efx_sdio_host *sdio_host = mmc_priv(host);
unsigned long flags;
spin_lock_irqsave(&sdio_host->lock, flags);
efx_sdio_enable_sdio_irq_without_lock(host, enable);
spin_unlock_irqrestore(&sdio_host->lock, flags);
}
void efx_sdio_ack_sdio_irq(struct mmc_host *host)
{
struct efx_sdio_host *sdio_host = mmc_priv(host);
unsigned long flags;
spin_lock_irqsave(&sdio_host->lock, flags);
efx_sdio_enable_sdio_irq_without_lock(host, 1);
spin_unlock_irqrestore(&sdio_host->lock, flags);
}