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>
1098 lines
34 KiB
C
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);
|
|
}
|
|
|
|
|