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