976 lines
26 KiB
C
976 lines
26 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Efinix eMMC Host Controller Core Operations
|
|
*
|
|
* Copyright (C) 2025 Efinix, Inc.
|
|
* Author: Teoh Choon Zone <czteoh@efinixinc.com>
|
|
*/
|
|
|
|
|
|
#include <linux/compiler.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/mmc/mmc.h>
|
|
#include <linux/mmc/host.h>
|
|
|
|
#include "efx_emmc.h"
|
|
|
|
static void efx_emmc_complete_pending_request(struct efx_emmc_host *host)
|
|
{
|
|
struct mmc_request *mrq;
|
|
|
|
mrq = READ_ONCE(host->mrq_done);
|
|
if (!mrq)
|
|
return;
|
|
|
|
WRITE_ONCE(host->mrq_done, NULL);
|
|
mmc_request_done(host->mmc, mrq);
|
|
}
|
|
|
|
bool efx_emmc_card_busy(struct efx_emmc_host *host)
|
|
{
|
|
return !!(efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE) &
|
|
EFX_EMMC_DAT_LINE_ACTIVE);
|
|
}
|
|
|
|
static bool efx_emmc_need_stop_command(struct mmc_data *data,
|
|
struct mmc_request *mrq)
|
|
{
|
|
if (data->blocks <= 1)
|
|
return false;
|
|
|
|
if (!data->stop)
|
|
return false;
|
|
|
|
if (mrq->sbc)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void efx_emmc_finish_request(struct efx_emmc_host *host,
|
|
struct mmc_request *mrq)
|
|
{
|
|
|
|
if (host->data) {
|
|
efx_emmc_cleanup_dma(host, host->data);
|
|
}
|
|
|
|
host->mrq = NULL;
|
|
host->cmd = NULL;
|
|
host->data = NULL;
|
|
host->bytes_to_transfer = 0;
|
|
host->blocks_done = 0;
|
|
host->sg_offset = 0;
|
|
host->use_dma = false;
|
|
|
|
if (mrq)
|
|
WRITE_ONCE(host->mrq_done, mrq);
|
|
}
|
|
|
|
static void efx_emmc_transfer_pio_read(struct efx_emmc_host *host)
|
|
{
|
|
struct mmc_data *data;
|
|
struct scatterlist *sg;
|
|
u32 *buf;
|
|
int words_in_fifo, words_transferred, i;
|
|
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;
|
|
}
|
|
|
|
words_in_fifo = data->blksz / sizeof(u32);
|
|
sg_offset = host->sg_offset;
|
|
words_transferred = 0;
|
|
|
|
while (words_transferred < words_in_fifo &&
|
|
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;
|
|
words_to_transfer = min3(remaining_in_sg / sizeof(u32),
|
|
(unsigned int)(words_in_fifo -
|
|
words_transferred),
|
|
(unsigned int)(host->bytes_to_transfer /
|
|
sizeof(u32)));
|
|
|
|
if (words_to_transfer == 0) {
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < words_to_transfer; i++) {
|
|
buf[i] = efx_emmc_readl(host, EFX_EMMC_BUFFER_DATA_PORT);
|
|
}
|
|
|
|
words_transferred += words_to_transfer;
|
|
sg_offset += words_to_transfer * 4;
|
|
host->bytes_to_transfer -= words_to_transfer * 4;
|
|
|
|
efx_emmc_dbg_pio(host,
|
|
"PIO read: %d words, %d bytes remaining\n",
|
|
words_to_transfer, host->bytes_to_transfer);
|
|
}
|
|
|
|
host->sg_offset += words_transferred * 4;
|
|
host->blocks_done = (data->blksz * data->blocks -
|
|
host->bytes_to_transfer) / data->blksz;
|
|
|
|
efx_emmc_dbg_pio(host,
|
|
"PIO read completed: %d words total, "
|
|
"%d blocks done, %d bytes remaining\n",
|
|
words_transferred, host->blocks_done,
|
|
host->bytes_to_transfer);
|
|
|
|
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
|
|
efx_emmc_dbg_pio(host, "Present state after PIO read: 0x%08x\n",
|
|
present_state);
|
|
}
|
|
|
|
static void efx_emmc_transfer_pio_write(struct efx_emmc_host *host)
|
|
{
|
|
struct mmc_data *data;
|
|
struct scatterlist *sg;
|
|
u32 *buf;
|
|
int words_in_fifo, words_transferred, i;
|
|
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;
|
|
}
|
|
|
|
words_in_fifo = data->blksz / sizeof(u32);
|
|
sg_offset = host->sg_offset;
|
|
words_transferred = 0;
|
|
|
|
while (words_transferred < words_in_fifo &&
|
|
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;
|
|
words_to_transfer = min3(remaining_in_sg / sizeof(u32),
|
|
(unsigned int)(words_in_fifo -
|
|
words_transferred),
|
|
(unsigned int)(host->bytes_to_transfer /
|
|
sizeof(u32)));
|
|
|
|
if (words_to_transfer == 0) {
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < words_to_transfer; i++) {
|
|
efx_emmc_writel(host, buf[i], EFX_EMMC_BUFFER_DATA_PORT);
|
|
}
|
|
|
|
words_transferred += words_to_transfer;
|
|
sg_offset += words_to_transfer * 4;
|
|
host->bytes_to_transfer -= words_to_transfer * 4;
|
|
|
|
efx_emmc_dbg_pio(host,
|
|
"PIO write: %d words, %d bytes remaining\n",
|
|
words_to_transfer, host->bytes_to_transfer);
|
|
}
|
|
|
|
host->sg_offset += words_transferred * 4;
|
|
host->blocks_done = (data->blksz * data->blocks -
|
|
host->bytes_to_transfer) / data->blksz;
|
|
|
|
efx_emmc_dbg_pio(host,
|
|
"PIO write completed: %d words total, %d blocks done, "
|
|
"%d bytes remaining\\n",
|
|
words_transferred, host->blocks_done,
|
|
host->bytes_to_transfer);
|
|
|
|
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
|
|
efx_emmc_dbg_pio(host, "Present state after PIO write: 0x%08x\n",
|
|
present_state);
|
|
}
|
|
|
|
void efx_emmc_transfer_pio(struct efx_emmc_host *host)
|
|
{
|
|
struct mmc_data *data;
|
|
u32 present_state;
|
|
|
|
data = host->data;
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
|
|
|
|
if (data->flags & MMC_DATA_READ) {
|
|
if (present_state & EFX_EMMC_BUFFER_READ_EN) {
|
|
efx_emmc_transfer_pio_read(host);
|
|
}
|
|
} else {
|
|
if (present_state & EFX_EMMC_BUFFER_WRITE_EN) {
|
|
efx_emmc_transfer_pio_write(host);
|
|
}
|
|
}
|
|
}
|
|
|
|
void efx_emmc_finish_data(struct efx_emmc_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_emmc_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);
|
|
}
|
|
|
|
efx_emmc_cleanup_dma(host, data);
|
|
|
|
host->data = NULL;
|
|
host->bytes_to_transfer = 0;
|
|
host->blocks_done = 0;
|
|
host->sg_offset = 0;
|
|
|
|
if (efx_emmc_need_stop_command(data, host->mrq) && !data->error) {
|
|
retry_count = 0;
|
|
while (retry_count < 100) {
|
|
present_state = efx_emmc_readl(host,
|
|
EFX_EMMC_PRESENT_STATE);
|
|
if (!(present_state & (EFX_EMMC_DAT_LINE_ACTIVE |
|
|
EFX_EMMC_READ_XFER_ACTIVE |
|
|
EFX_EMMC_WRITE_XFER_ACTIVE))) {
|
|
break;
|
|
}
|
|
udelay(10);
|
|
retry_count++;
|
|
}
|
|
|
|
if (retry_count >= 100) {
|
|
dev_warn(&host->pdev->dev,
|
|
"Data lines still active before CMD12, proceeding anyway\n");
|
|
}
|
|
|
|
udelay(100);
|
|
|
|
efx_emmc_dbg_cmd(host,
|
|
"Sending CMD12 (STOP) for %d-block transfer\n",
|
|
data->blocks);
|
|
host->cmd = data->stop;
|
|
efx_emmc_send_command(host, data->stop);
|
|
} else {
|
|
efx_emmc_dbg_cmd(host, "Finishing request\n");
|
|
if (host->mrq) {
|
|
efx_emmc_finish_request(host, host->mrq);
|
|
}
|
|
}
|
|
}
|
|
|
|
void efx_emmc_finish_command(struct efx_emmc_host *host)
|
|
{
|
|
struct mmc_command *cmd;
|
|
u32 resp[4];
|
|
u32 present_state;
|
|
|
|
cmd = host->cmd;
|
|
if (!cmd) {
|
|
return;
|
|
}
|
|
|
|
if (cmd->flags & MMC_RSP_PRESENT) {
|
|
if (cmd->flags & MMC_RSP_136) {
|
|
resp[0] = efx_emmc_readl(host, EFX_EMMC_RESPONSE0);
|
|
resp[1] = efx_emmc_readl(host, EFX_EMMC_RESPONSE1);
|
|
resp[2] = efx_emmc_readl(host, EFX_EMMC_RESPONSE2);
|
|
resp[3] = efx_emmc_readl(host, EFX_EMMC_RESPONSE3) & 0xFFFFFF;
|
|
|
|
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_emmc_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 {
|
|
cmd->resp[0] = efx_emmc_readl(host, EFX_EMMC_RESPONSE0);
|
|
efx_emmc_dbg_cmd(host, "CMD%d response: 0x%08x\n",
|
|
cmd->opcode, cmd->resp[0]);
|
|
}
|
|
}
|
|
|
|
if (cmd->opcode == 6 && cmd->error == 0) {
|
|
u32 arg = cmd->arg;
|
|
u8 index = (arg >> 16) & 0xFF;
|
|
u8 value = (arg >> 8) & 0xFF;
|
|
|
|
if (index == 185) {
|
|
switch (value) {
|
|
case 0:
|
|
case 1:
|
|
dev_dbg(&host->pdev->dev,
|
|
"CMD6 mode switch to legacy/HS completed\n");
|
|
efx_emmc_set_timing_config(host, 0, 0);
|
|
break;
|
|
case 2:
|
|
dev_dbg(&host->pdev->dev,
|
|
"CMD6 mode switch to DDR52 completed\n");
|
|
efx_emmc_set_timing_config(host, 2, 2);
|
|
break;
|
|
case 3:
|
|
dev_dbg(&host->pdev->dev,
|
|
"CMD6 mode switch to HS200 completed\n");
|
|
break;
|
|
default:
|
|
efx_emmc_dbg_cmd(host,
|
|
"CMD6 mode switch to timing value %u\n",
|
|
value);
|
|
break;
|
|
}
|
|
|
|
efx_emmc_writel(host, 0xFFFFFFFF, EFX_EMMC_INT_STATUS);
|
|
|
|
}
|
|
}
|
|
|
|
if (cmd->opcode == 23 && host->mrq && host->mrq->cmd) {
|
|
efx_emmc_dbg_cmd(host,
|
|
"CMD23 complete, sending main command CMD%d\n",
|
|
host->mrq->cmd->opcode);
|
|
host->cmd = host->mrq->cmd;
|
|
efx_emmc_send_command(host, host->mrq->cmd);
|
|
return;
|
|
}
|
|
|
|
host->cmd = NULL;
|
|
|
|
if (!host->data) {
|
|
efx_emmc_dbg_cmd(host, "Command complete, finishing request\n");
|
|
if (host->mrq) {
|
|
efx_emmc_finish_request(host, host->mrq);
|
|
}
|
|
} else {
|
|
efx_emmc_dbg_cmd(host,
|
|
"Command complete, data transfer continues (DMA: %s)\n",
|
|
host->use_dma ? "enabled" : "disabled");
|
|
|
|
if (!host->use_dma) {
|
|
bool buffer_ready_read, buffer_ready_write;
|
|
|
|
present_state = efx_emmc_readl(host,
|
|
EFX_EMMC_PRESENT_STATE);
|
|
efx_emmc_dbg_cmd(host,
|
|
"Present state after command: 0x%08x\n",
|
|
present_state);
|
|
|
|
buffer_ready_read = !!(present_state & EFX_EMMC_BUFFER_READ_EN);
|
|
buffer_ready_write = !!(present_state & EFX_EMMC_BUFFER_WRITE_EN);
|
|
|
|
if (host->data->flags & MMC_DATA_READ) {
|
|
if (buffer_ready_read) {
|
|
efx_emmc_dbg_pio(host,
|
|
"Buffer immediately ready for read, starting PIO\n");
|
|
efx_emmc_transfer_pio(host);
|
|
}
|
|
} else {
|
|
if (buffer_ready_write) {
|
|
efx_emmc_dbg_pio(host,
|
|
"Buffer immediately ready for write, starting PIO\n");
|
|
efx_emmc_transfer_pio(host);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void efx_emmc_send_command(struct efx_emmc_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_emmc_dbg_cmd(host, "Sending CMD%d, arg=0x%08x%s\n",
|
|
cmd->opcode, cmd->arg, (cmd->opcode == 12) ? " (STOP)" : "");
|
|
|
|
if (cmd->opcode == 6) {
|
|
u32 arg = cmd->arg;
|
|
u8 index = (arg >> 16) & 0xFF;
|
|
u8 value = (arg >> 8) & 0xFF;
|
|
|
|
if (index == 185) {
|
|
dev_dbg(&host->pdev->dev,
|
|
"CMD6 mode switch detected (index=%u, value=%u)\n",
|
|
index, value);
|
|
efx_emmc_set_timing_config(host, 0, 0);
|
|
msleep(10);
|
|
|
|
efx_emmc_writel(host, 0xFFFFFFFF, EFX_EMMC_INT_STATUS);
|
|
}
|
|
}
|
|
|
|
switch (cmd->opcode) {
|
|
case 6:
|
|
cmd_timeout = 2000;
|
|
break;
|
|
case 12:
|
|
cmd_timeout = 1000;
|
|
break;
|
|
case 23:
|
|
cmd_timeout = 500;
|
|
break;
|
|
default:
|
|
cmd_timeout = 500;
|
|
break;
|
|
}
|
|
timeout = jiffies + msecs_to_jiffies(cmd_timeout);
|
|
while (time_before(jiffies, timeout)) {
|
|
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
|
|
if (!(present_state & EFX_EMMC_CMD_INHIBIT_CMD)) {
|
|
if (!data || !(present_state & EFX_EMMC_CMD_INHIBIT_DAT)) {
|
|
break;
|
|
}
|
|
}
|
|
cpu_relax();
|
|
}
|
|
|
|
if (time_after_eq(jiffies, timeout)) {
|
|
dev_err(&host->pdev->dev,
|
|
"Command line timeout, present_state=0x%08x\n",
|
|
efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE));
|
|
cmd->error = -ETIMEDOUT;
|
|
if (host->mrq) {
|
|
efx_emmc_finish_request(host, host->mrq);
|
|
}
|
|
return;
|
|
}
|
|
|
|
efx_emmc_writel(host, cmd->arg, EFX_EMMC_ARG1);
|
|
|
|
command = (cmd->opcode << EFX_EMMC_CMD_INDEX_SHIFT) &
|
|
EFX_EMMC_CMD_INDEX_MASK;
|
|
|
|
if (cmd->flags & MMC_RSP_PRESENT) {
|
|
if (cmd->flags & MMC_RSP_136) {
|
|
command |= (EFX_EMMC_RESP_TYPE_136 << EFX_EMMC_RESP_TYPE_SHIFT);
|
|
} else if (cmd->flags & MMC_RSP_BUSY) {
|
|
command |= (EFX_EMMC_RESP_TYPE_48_BUSY << EFX_EMMC_RESP_TYPE_SHIFT);
|
|
} else {
|
|
command |= (EFX_EMMC_RESP_TYPE_48 << EFX_EMMC_RESP_TYPE_SHIFT);
|
|
}
|
|
|
|
if (cmd->flags & MMC_RSP_CRC) {
|
|
command |= EFX_EMMC_CMD_CRC_CHECK_EN;
|
|
}
|
|
|
|
if (cmd->flags & MMC_RSP_OPCODE) {
|
|
command |= EFX_EMMC_CMD_INDEX_CHECK_EN;
|
|
}
|
|
}
|
|
|
|
if (data) {
|
|
command |= EFX_EMMC_DATA_PRESENT;
|
|
|
|
host->use_dma = (data->blksz * data->blocks >= 512);
|
|
|
|
if (host->use_dma) {
|
|
efx_emmc_prepare_dma(host, data);
|
|
}
|
|
|
|
efx_emmc_writel(host, (data->blocks << EFX_EMMC_BLOCK_COUNT_SHIFT) |
|
|
(data->blksz & EFX_EMMC_BLOCK_SIZE_MASK),
|
|
EFX_EMMC_BLOCK_SIZE);
|
|
|
|
if (data->blocks > 1) {
|
|
command |= EFX_EMMC_MULTI_BLOCK_SEL;
|
|
command |= EFX_EMMC_BLOCK_COUNT_EN;
|
|
if (host->mrq && host->mrq->sbc) {
|
|
efx_emmc_dbg_cmd(host,
|
|
"Multi-block transfer: CMD23 used, no CMD12 needed\n");
|
|
} else {
|
|
efx_emmc_dbg_cmd(host,
|
|
"Multi-block transfer: manual CMD12 will be used\n");
|
|
}
|
|
}
|
|
|
|
if (data->flags & MMC_DATA_READ) {
|
|
command |= EFX_EMMC_DATA_XFER_DIR;
|
|
}
|
|
|
|
if (host->use_dma) {
|
|
command |= EFX_EMMC_DMA_EN;
|
|
efx_emmc_dbg_cmd(host, "DMA enabled for data transfer\n");
|
|
} else {
|
|
host->bytes_to_transfer = data->blksz * data->blocks;
|
|
host->blocks_done = 0;
|
|
host->sg_offset = 0;
|
|
efx_emmc_dbg_cmd(host, "Using PIO for data transfer\n");
|
|
}
|
|
|
|
host->data = data;
|
|
|
|
efx_emmc_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_emmc_dbg_cmd(host, "Command register: 0x%08x\n", command);
|
|
|
|
efx_emmc_writel(host, command, EFX_EMMC_TRANSFER_MODE);
|
|
}
|
|
|
|
void efx_emmc_set_clock(struct efx_emmc_host *host, unsigned int clock)
|
|
{
|
|
u32 div, reg;
|
|
unsigned long timeout;
|
|
|
|
if (clock == 0) {
|
|
reg = efx_emmc_readl(host, EFX_EMMC_BASE_REG0);
|
|
reg &= ~EFX_EMMC_BASE_REG0_CLK_EN;
|
|
efx_emmc_writel(host, reg, EFX_EMMC_BASE_REG0);
|
|
host->current_clk = 0;
|
|
return;
|
|
}
|
|
|
|
if (clock > EFX_EMMC_MAX_FREQ) {
|
|
clock = EFX_EMMC_MAX_FREQ;
|
|
}
|
|
|
|
if (clock >= host->base_clk) {
|
|
div = 1;
|
|
} else {
|
|
div = (host->base_clk + clock - 1) / clock;
|
|
if (div > 1 && (div & 1)) {
|
|
div += 1;
|
|
}
|
|
}
|
|
|
|
reg = efx_emmc_readl(host, EFX_EMMC_BASE_REG0);
|
|
reg &= ~EFX_EMMC_BASE_REG0_CLK_EN;
|
|
efx_emmc_writel(host, reg, EFX_EMMC_BASE_REG0);
|
|
|
|
reg = (reg & ~EFX_EMMC_BASE_REG0_CLK_DIV_MASK) |
|
|
(div & EFX_EMMC_BASE_REG0_CLK_DIV_MASK);
|
|
efx_emmc_writel(host, reg, EFX_EMMC_BASE_REG0);
|
|
|
|
reg |= EFX_EMMC_BASE_REG0_CLK_EN;
|
|
efx_emmc_writel(host, reg, EFX_EMMC_BASE_REG0);
|
|
|
|
if (clock <= 400000) {
|
|
usleep_range(500, 1000);
|
|
} else if (clock <= 25000000) {
|
|
usleep_range(100, 200);
|
|
} else {
|
|
usleep_range(50, 100);
|
|
}
|
|
|
|
timeout = jiffies + msecs_to_jiffies(50);
|
|
while (time_before(jiffies, timeout)) {
|
|
if (!(efx_emmc_readl(host, EFX_EMMC_BASE_STATUS_REG0) &
|
|
(EFX_EMMC_BASE_STATUS_CMD_BUSY |
|
|
EFX_EMMC_BASE_STATUS_DAT_BUSY))) {
|
|
break;
|
|
}
|
|
cpu_relax();
|
|
}
|
|
|
|
host->current_clk = host->base_clk / div;
|
|
host->clk_div = div;
|
|
dev_dbg(&host->pdev->dev, "Set clock to %u Hz (div=%u, actual=%u)\n",
|
|
clock, div, host->current_clk);
|
|
}
|
|
|
|
void efx_emmc_set_bus_width(struct efx_emmc_host *host, int width)
|
|
{
|
|
u32 reg;
|
|
|
|
reg = efx_emmc_readl(host, EFX_EMMC_HOST_CONTROL);
|
|
reg &= ~EFX_EMMC_DATA_WIDTH_MASK;
|
|
|
|
switch (width) {
|
|
case MMC_BUS_WIDTH_1:
|
|
reg |= (EFX_EMMC_DATA_WIDTH_1BIT << EFX_EMMC_DATA_WIDTH_SHIFT);
|
|
break;
|
|
case MMC_BUS_WIDTH_4:
|
|
reg |= (EFX_EMMC_DATA_WIDTH_4BIT << EFX_EMMC_DATA_WIDTH_SHIFT);
|
|
break;
|
|
case MMC_BUS_WIDTH_8:
|
|
reg |= (EFX_EMMC_DATA_WIDTH_8BIT << EFX_EMMC_DATA_WIDTH_SHIFT);
|
|
break;
|
|
default:
|
|
dev_warn(&host->pdev->dev, "Unsupported bus width: %d\n", width);
|
|
return;
|
|
}
|
|
|
|
efx_emmc_writel(host, reg, EFX_EMMC_HOST_CONTROL);
|
|
dev_dbg(&host->pdev->dev, "Set bus width to %d bits\n", width);
|
|
}
|
|
|
|
void efx_emmc_set_timing(struct efx_emmc_host *host, unsigned int timing)
|
|
{
|
|
u32 reg;
|
|
bool needs_tuning = false;
|
|
|
|
reg = efx_emmc_readl(host, EFX_EMMC_HOST_CONTROL);
|
|
|
|
switch (timing) {
|
|
case MMC_TIMING_LEGACY:
|
|
case MMC_TIMING_MMC_HS:
|
|
reg &= ~EFX_EMMC_DATA_SAMPLING_MODE;
|
|
break;
|
|
case MMC_TIMING_MMC_DDR52:
|
|
reg |= EFX_EMMC_DATA_SAMPLING_MODE;
|
|
host->tuning_done = true;
|
|
set_bit(timing, &host->tuned_timing_modes);
|
|
break;
|
|
case MMC_TIMING_MMC_HS200:
|
|
reg &= ~EFX_EMMC_DATA_SAMPLING_MODE;
|
|
if (!test_bit(timing, &host->tuned_timing_modes)) {
|
|
needs_tuning = true;
|
|
host->tuning_done = false;
|
|
dev_dbg(&host->pdev->dev, "HS200 mode - tuning required\n");
|
|
} else {
|
|
efx_emmc_set_timing_config(host, host->hs200_sample_count,
|
|
host->hs200_pll_shift);
|
|
dev_dbg(&host->pdev->dev,
|
|
"HS200 mode - already tuned, restored timing (sample=%u, pll=%u)\n",
|
|
host->hs200_sample_count, host->hs200_pll_shift);
|
|
}
|
|
break;
|
|
case MMC_TIMING_MMC_HS400:
|
|
reg |= EFX_EMMC_DATA_SAMPLING_MODE;
|
|
|
|
if (!test_bit(timing, &host->tuned_timing_modes)) {
|
|
u32 initial_sample = host->hs400_default_sample;
|
|
u32 initial_pll = host->hs400_default_pll;
|
|
|
|
efx_emmc_set_timing_config(host, initial_sample, initial_pll);
|
|
needs_tuning = true;
|
|
host->tuning_done = false;
|
|
host->hs400_retune_pending = true;
|
|
|
|
dev_info(&host->pdev->dev,
|
|
"HS400 mode - using default sample=%u pll=%u, clock=%u Hz\n",
|
|
initial_sample, initial_pll, host->mmc->ios.clock);
|
|
dev_info(&host->pdev->dev,
|
|
"HS400 tuning scheduled (clock=%u Hz)\n",
|
|
host->mmc->ios.clock);
|
|
schedule_delayed_work(&host->hs400_retune_work,
|
|
msecs_to_jiffies(10));
|
|
} else {
|
|
efx_emmc_set_timing_config(host, host->hs400_sample_count,
|
|
host->hs400_pll_shift);
|
|
dev_info(&host->pdev->dev,
|
|
"HS400 mode - already tuned, restored timing (sample=%u, pll=%u)\n",
|
|
host->hs400_sample_count, host->hs400_pll_shift);
|
|
}
|
|
break;
|
|
default:
|
|
dev_warn(&host->pdev->dev, "Unsupported timing: %d\n", timing);
|
|
return;
|
|
}
|
|
|
|
efx_emmc_writel(host, reg, EFX_EMMC_HOST_CONTROL);
|
|
dev_dbg(&host->pdev->dev, "Set timing mode: %s (%d) - %s\n",
|
|
timing == MMC_TIMING_LEGACY ? "Legacy" :
|
|
timing == MMC_TIMING_MMC_HS ? "High Speed" :
|
|
timing == MMC_TIMING_MMC_DDR52 ? "DDR52" :
|
|
timing == MMC_TIMING_MMC_HS200 ? "HS200" :
|
|
timing == MMC_TIMING_MMC_HS400 ? "HS400" : "Unknown", timing,
|
|
needs_tuning ? "NEEDS TUNING" : "NO TUNING NEEDED");
|
|
|
|
host->prev_timing = timing;
|
|
}
|
|
|
|
irqreturn_t efx_emmc_irq(int irq, void *dev_id)
|
|
{
|
|
struct efx_emmc_host *host;
|
|
u32 intstat, present_state;
|
|
irqreturn_t result;
|
|
|
|
host = dev_id;
|
|
result = IRQ_NONE;
|
|
|
|
spin_lock(&host->lock);
|
|
|
|
intstat = efx_emmc_readl(host, EFX_EMMC_INT_STATUS);
|
|
if (!intstat) {
|
|
goto out;
|
|
}
|
|
|
|
present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE);
|
|
efx_emmc_dbg_irq(host, "IRQ: status=0x%08x, present=0x%08x\n",
|
|
intstat, present_state);
|
|
|
|
efx_emmc_writel(host, intstat, EFX_EMMC_INT_STATUS);
|
|
result = IRQ_HANDLED;
|
|
|
|
if (intstat & EFX_EMMC_INT_ERROR_MASK) {
|
|
if (intstat & EFX_EMMC_INT_ADMA_ERROR) {
|
|
dev_err(&host->pdev->dev, "ADMA error detected\n");
|
|
if (host->data) {
|
|
host->data->error = -EIO;
|
|
}
|
|
}
|
|
|
|
if (host->cmd && (host->cmd->opcode == 52 || host->cmd->opcode == 8 ||
|
|
host->cmd->opcode == 5 || host->cmd->opcode == 55)) {
|
|
efx_emmc_dbg_irq(host,
|
|
"Expected timeout for CMD%d during card detection\n",
|
|
host->cmd->opcode);
|
|
} else if (host->tuning_in_progress) {
|
|
efx_emmc_dbg_irq(host, "Tuning error (expected): 0x%08x\n",
|
|
(unsigned int)(intstat & EFX_EMMC_INT_ERROR_MASK));
|
|
} else {
|
|
dev_err(&host->pdev->dev, "Error interrupt: 0x%08x\n",
|
|
(unsigned int)(intstat & EFX_EMMC_INT_ERROR_MASK));
|
|
}
|
|
|
|
if (host->cmd) {
|
|
if (intstat & EFX_EMMC_INT_CMD_TIMEOUT_ERR) {
|
|
host->cmd->error = -ETIMEDOUT;
|
|
if (host->cmd->opcode == 12) {
|
|
dev_warn(&host->pdev->dev,
|
|
"CMD12 timeout - data may have completed normally\n");
|
|
host->cmd = NULL;
|
|
if (host->mrq) {
|
|
efx_emmc_finish_request(host, host->mrq);
|
|
}
|
|
goto out;
|
|
} else if (host->cmd->opcode == 6) {
|
|
dev_err(&host->pdev->dev,
|
|
"CMD6 (mode switch) timeout - arg=0x%08x\n",
|
|
host->cmd->arg);
|
|
} else if (!(host->cmd->opcode == 52 ||
|
|
host->cmd->opcode == 8 ||
|
|
host->cmd->opcode == 5 ||
|
|
host->cmd->opcode == 55)) {
|
|
dev_err(&host->pdev->dev, "CMD%d timeout\n",
|
|
host->cmd->opcode);
|
|
}
|
|
} else if (intstat & (EFX_EMMC_INT_CMD_CRC_ERR |
|
|
EFX_EMMC_INT_CMD_END_BIT_ERR |
|
|
EFX_EMMC_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_EMMC_INT_DATA_TIMEOUT_ERR) {
|
|
host->data->error = -ETIMEDOUT;
|
|
dev_err(&host->pdev->dev, "Data timeout error\n");
|
|
}
|
|
if (intstat & EFX_EMMC_INT_DATA_CRC_ERR) {
|
|
host->data->error = -EILSEQ;
|
|
dev_err(&host->pdev->dev, "Data CRC error\n");
|
|
}
|
|
if (intstat & EFX_EMMC_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_EMMC_INT_CMD_TIMEOUT_ERR))) {
|
|
if (host->mrq) {
|
|
efx_emmc_finish_request(host, host->mrq);
|
|
}
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (intstat & EFX_EMMC_INT_CMD_COMPLETE) {
|
|
efx_emmc_dbg_irq(host, "Command complete\n");
|
|
efx_emmc_finish_command(host);
|
|
}
|
|
|
|
if (likely(!host->use_dma) &&
|
|
(intstat & (EFX_EMMC_INT_BUFFER_READ_RDY |
|
|
EFX_EMMC_INT_BUFFER_WRITE_RDY))) {
|
|
efx_emmc_dbg_irq(host, "Buffer ready for %s\n",
|
|
(intstat & EFX_EMMC_INT_BUFFER_READ_RDY) ? "read" : "write");
|
|
efx_emmc_transfer_pio(host);
|
|
|
|
if (host->data && host->bytes_to_transfer > 0) {
|
|
efx_emmc_dbg_pio(host,
|
|
"Waiting for more data: %d bytes remaining\n",
|
|
host->bytes_to_transfer);
|
|
} else if (host->data && host->bytes_to_transfer == 0) {
|
|
efx_emmc_dbg_pio(host,
|
|
"All data transferred via PIO, completing transfer\n");
|
|
efx_emmc_finish_data(host);
|
|
}
|
|
}
|
|
|
|
if (intstat & EFX_EMMC_INT_XFER_COMPLETE) {
|
|
efx_emmc_dbg_irq(host, "Transfer complete interrupt (DMA: %s)\n",
|
|
host->use_dma ? "enabled" : "disabled");
|
|
efx_emmc_finish_data(host);
|
|
}
|
|
|
|
if (intstat & ~(EFX_EMMC_INT_CMD_COMPLETE | EFX_EMMC_INT_BUFFER_READ_RDY |
|
|
EFX_EMMC_INT_BUFFER_WRITE_RDY |
|
|
EFX_EMMC_INT_XFER_COMPLETE | EFX_EMMC_INT_ERROR_MASK)) {
|
|
efx_emmc_dbg_irq(host, "Unhandled interrupt bits: 0x%08x\n",
|
|
(unsigned int)(intstat &
|
|
~(EFX_EMMC_INT_CMD_COMPLETE |
|
|
EFX_EMMC_INT_BUFFER_READ_RDY |
|
|
EFX_EMMC_INT_BUFFER_WRITE_RDY |
|
|
EFX_EMMC_INT_XFER_COMPLETE |
|
|
EFX_EMMC_INT_ERROR_MASK)));
|
|
}
|
|
|
|
out:
|
|
spin_unlock(&host->lock);
|
|
efx_emmc_complete_pending_request(host);
|
|
return result;
|
|
}
|
|
|
|
void efx_emmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
{
|
|
struct efx_emmc_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;
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
mmc_request_done(mmc, mrq);
|
|
return;
|
|
}
|
|
|
|
efx_emmc_dbg_cmd(host, "New request: CMD%d\n", mrq->cmd->opcode);
|
|
|
|
host->mrq = mrq;
|
|
|
|
if (mrq->sbc) {
|
|
efx_emmc_dbg_cmd(host,
|
|
"Sending CMD23 (SET_BLOCK_COUNT) first, blocks=%u\n",
|
|
mrq->sbc->arg);
|
|
host->cmd = mrq->sbc;
|
|
efx_emmc_send_command(host, mrq->sbc);
|
|
} else {
|
|
efx_emmc_send_command(host, mrq->cmd);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
efx_emmc_complete_pending_request(host);
|
|
}
|
|
|
|
void efx_emmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
{
|
|
struct efx_emmc_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) {
|
|
efx_emmc_set_clock(host, ios->clock);
|
|
}
|
|
|
|
if (ios->bus_width != MMC_BUS_WIDTH_1) {
|
|
efx_emmc_set_bus_width(host, ios->bus_width);
|
|
need_host_control_update = true;
|
|
}
|
|
|
|
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_emmc_set_timing(host, ios->timing);
|
|
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
|
|
int efx_emmc_get_cd(struct mmc_host *mmc)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int efx_emmc_card_busy_wrapper(struct mmc_host *mmc)
|
|
{
|
|
struct efx_emmc_host *host;
|
|
unsigned long flags;
|
|
bool busy;
|
|
|
|
host = mmc_priv(mmc);
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
busy = efx_emmc_card_busy(host);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
|
|
return busy;
|
|
}
|
|
|
|
int efx_emmc_get_ro(struct mmc_host *mmc)
|
|
{
|
|
return 0;
|
|
}
|