From d4f7fc52dc20803bedd5edd517e423ced63c73d4 Mon Sep 17 00:00:00 2001 From: Masaharu Hayakawa Date: Mon, 11 Dec 2017 20:53:39 +0900 Subject: [PATCH 2/2] mmc: renesas_sdhi: Add SDHI-SEQUENCER support This is Workaround for SDHI-DMAC restriction of R-Car H3(WS1.x)/M3(WS1.0). Restriction: Mismatch of the transfer completion interrupt time and data transfer completion time. Overview: It does not take into account the bus response, the transfer completion interrupt IP outputs is in the early out. Therefore, when carrying out the data verification data read from the SD card, there is a possibility that the data of the last sector might be missing. (MMC Interface is also the same.) S/W Workaround: The last sector data is preserved by reading data for 2 sectors extra in the SDHI Driver of Linux kernel. SDHI Driver achieves a dummy read for 2 sectors by the SDHI-SEQ function. In case of CMD18(MMC_READ_MULTIPLE_BLOCK) were requested, 1024 bytes are read additionally by CMD18. In other cases commands, CMD17 and CMD53(SD_IO_RW_EXTENDED) is carried out additionally twice. Signed-off-by: Masaharu Hayakawa --- drivers/mmc/host/renesas_sdhi_core.c | 20 +- drivers/mmc/host/renesas_sdhi_internal_dmac.c | 554 +++++++++++++++++++++++++- 2 files changed, 570 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/renesas_sdhi_core.c b/drivers/mmc/host/renesas_sdhi_core.c index 7d3c35e..4ae36b3 100644 --- a/drivers/mmc/host/renesas_sdhi_core.c +++ b/drivers/mmc/host/renesas_sdhi_core.c @@ -53,11 +53,13 @@ static const struct soc_device_attribute sdhi_quirks_match[] = { { .soc_id = "r8a7795", .revision = "ES1.*", - .data = (void *)(DTRAEND1_SET_BIT17 | HS400_USE_4TAP), }, + .data = (void *)(DTRAEND1_SET_BIT17 | HS400_USE_4TAP | + USE_SEQUENCER), }, { .soc_id = "r8a7795", .revision = "ES2.0", .data = (void *)HS400_USE_4TAP, }, { .soc_id = "r8a7796", .revision = "ES1.0", - .data = (void *)(DTRAEND1_SET_BIT17 | HS400_USE_4TAP), }, + .data = (void *)(DTRAEND1_SET_BIT17 | HS400_USE_4TAP | + USE_SEQUENCER), }, { .soc_id = "r8a7796", .revision = "ES1.1", .data = (void *)HS400_USE_4TAP, }, {/*sentinel*/}, @@ -730,6 +732,9 @@ int renesas_sdhi_probe(struct platform_device *pdev, host->hs400_use_4tap = (host->sdhi_quirks & HS400_USE_4TAP) ? true : false; + host->seq_enabled = (host->sdhi_quirks & USE_SEQUENCER) ? + true : false; + if (of_data) { mmc_data->flags |= of_data->tmio_flags; mmc_data->ocr_mask = of_data->tmio_ocr_mask; @@ -742,7 +747,16 @@ int renesas_sdhi_probe(struct platform_device *pdev, host->bus_shift = of_data->bus_shift; priv->scc_offset = of_data->scc_offset; } - + if (host->seq_enabled) { + mmc_data->max_blk_count = 0xffffffff / 512; +#ifdef CONFIG_MMC_BLOCK_BOUNCE + /* (CMD23+CMD18)*1 + (dummy read command) */ + mmc_data->max_segs = 1; +#else + /* (CMD23+CMD18)*3 + (dummy read command) */ + mmc_data->max_segs = 3; +#endif + } host->dma = dma_priv; host->write16_hook = renesas_sdhi_write16_hook; host->clk_enable = renesas_sdhi_clk_enable; diff --git a/drivers/mmc/host/renesas_sdhi_internal_dmac.c b/drivers/mmc/host/renesas_sdhi_internal_dmac.c index 92dcc88..caa9e30 100644 --- a/drivers/mmc/host/renesas_sdhi_internal_dmac.c +++ b/drivers/mmc/host/renesas_sdhi_internal_dmac.c @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -23,6 +26,9 @@ #include "renesas_sdhi.h" #include "tmio_mmc.h" +#define DM_CM_SEQ_REGSET 0x800 +#define DM_CM_SEQ_MODE 0x808 +#define DM_CM_SEQ_CTRL 0x810 #define DM_CM_DTRAN_MODE 0x820 #define DM_CM_DTRAN_CTRL 0x828 #define DM_CM_RST 0x830 @@ -30,7 +36,16 @@ #define DM_CM_INFO1_MASK 0x848 #define DM_CM_INFO2 0x850 #define DM_CM_INFO2_MASK 0x858 +#define DM_CM_TUNING_STAT 0x860 +#define DM_CM_SEQ_STAT 0x868 #define DM_DTRAN_ADDR 0x880 +#define DM_SEQ_CMD 0x8a0 +#define DM_SEQ_ARG 0x8a8 +#define DM_SEQ_SIZE 0x8b0 +#define DM_SEQ_SECCNT 0x8b8 +#define DM_SEQ_RSP 0x8c0 +#define DM_SEQ_RSP_CHK 0x8c8 +#define DM_SEQ_ADDR 0x8d0 /* DM_CM_DTRAN_MODE */ #define DTRAN_MODE_CH_NUM_CH0 0 /* "downstream" = for write commands */ @@ -44,6 +59,7 @@ /* DM_CM_RST */ #define RST_DTRANRST1 BIT(9) #define RST_DTRANRST0 BIT(8) +#define RST_SEQRST BIT(0) #define RST_RESERVED_BITS GENMASK_ULL(32, 0) /* DM_CM_INFO1 and DM_CM_INFO1_MASK */ @@ -51,6 +67,8 @@ #define INFO1_DTRANEND1_BIT20 BIT(20) #define INFO1_DTRANEND1_BIT17 BIT(17) #define INFO1_DTRANEND0 BIT(16) +#define INFO1_SEQSUSPEND BIT(8) +#define INFO1_SEQEND BIT(0) /* DM_CM_INFO2 and DM_CM_INFO2_MASK */ #define INFO2_DTRANERR1 BIT(17) @@ -119,7 +137,8 @@ renesas_sdhi_internal_dmac_enable_dma(struct tmio_mmc_host *host, bool enable) INFO1_CLEAR); if (host->dma->enable) { - host->dma_irq_mask = ~(host->dma_tranend1 | INFO1_DTRANEND0); + host->dma_irq_mask = host->seq_enabled ? + 0xffffffff : ~(host->dma_tranend1 | INFO1_DTRANEND0); host->dma->enable(host, enable); renesas_sdhi_internal_dmac_dm_write(host, DM_CM_INFO1_MASK, host->dma_irq_mask); @@ -132,12 +151,20 @@ renesas_sdhi_internal_dmac_abort_dma(struct tmio_mmc_host *host) { renesas_sdhi_internal_dmac_enable_dma(host, false); + if (host->seq_enabled) + val |= RST_SEQRST; renesas_sdhi_internal_dmac_dm_write(host, DM_CM_RST, RST_RESERVED_BITS & ~val); renesas_sdhi_internal_dmac_dm_write(host, DM_CM_RST, RST_RESERVED_BITS | val); renesas_sdhi_internal_dmac_enable_dma(host, true); + + if (host->bounce_sg_mapped) { + dma_unmap_sg(&host->pdev->dev, &host->bounce_sg, 1, + DMA_FROM_DEVICE); + host->bounce_sg_mapped = false; + } } static void @@ -215,17 +242,446 @@ static void renesas_sdhi_internal_dmac_complete_tasklet_fn(unsigned long arg) mmc_get_dma_dir(host->data)); host->data->host_cookie = COOKIE_UNMAPPED; } + if (host->bounce_sg_mapped) { + dma_unmap_sg(&host->pdev->dev, &host->bounce_sg, 1, + DMA_FROM_DEVICE); + host->bounce_sg_mapped = false; + } tmio_mmc_do_data_irq(host); out: spin_unlock_irq(&host->lock); } +static void +renesas_sdhi_internal_dmac_seq_complete_tasklet_fn(unsigned long arg) +{ + renesas_sdhi_internal_dmac_complete_tasklet_fn(arg); +} + +/* DM_CM_SEQ_REGSET bits */ +#define DM_CM_SEQ_REGSET_TABLE_NUM BIT(8) + +/* DM_CM_SEQ_CTRL bits */ +#define DM_CM_SEQ_CTRL_SEQ_TABLE BIT(28) +#define DM_CM_SEQ_CTRL_T_NUM BIT(24) +#define DM_CM_SEQ_CTRL_SEQ_TYPE_SD BIT(16) +#define DM_CM_SEQ_CTRL_START_NUM(x) ((x) << 12) +#define DM_CM_SEQ_CTRL_END_NUM(x) ((x) << 8) +#define DM_CM_SEQ_CTRL_SEQ_START BIT(0) + +/* DM_SEQ_CMD bits */ +#define DM_SEQ_CMD_MULTI BIT(13) +#define DM_SEQ_CMD_DIO BIT(12) +#define DM_SEQ_CMD_CMDTYP BIT(11) +#define DM_SEQ_CMD_RSP_NONE (BIT(9) | BIT(8)) +#define DM_SEQ_CMD_RSP_R1 BIT(10) +#define DM_SEQ_CMD_RSP_R1B (BIT(10) | BIT(8)) +#define DM_SEQ_CMD_RSP_R2 (BIT(10) | BIT(9)) +#define DM_SEQ_CMD_RSP_R3 (BIT(10) | BIT(9) | BIT(8)) +#define DM_SEQ_CMD_NONAUTOSTP BIT(7) +#define DM_SEQ_CMD_APP BIT(6) + +#define MAX_CONTEXT_NUM 8 + +struct tmio_mmc_context { + u64 seq_cmd; + u64 seq_arg; + u64 seq_size; + u64 seq_seccnt; + u64 seq_rsp; + u64 seq_rsp_chk; + u64 seq_addr; +}; + +static void +renesas_sdhi_internal_dmac_set_seq_context(struct tmio_mmc_host *host, + int ctxt_num, + struct tmio_mmc_context *ctxt) +{ + u64 val; + + WARN_ON(ctxt_num >= MAX_CONTEXT_NUM); + + /* set sequencer table/context number */ + if (ctxt_num < 4) + val = ctxt_num; + else + val = DM_CM_SEQ_REGSET_TABLE_NUM | (ctxt_num - 4); + renesas_sdhi_internal_dmac_dm_write(host, DM_CM_SEQ_REGSET, val); + + /* set command parameter */ + renesas_sdhi_internal_dmac_dm_write(host, DM_SEQ_CMD, ctxt->seq_cmd); + renesas_sdhi_internal_dmac_dm_write(host, DM_SEQ_ARG, ctxt->seq_arg); + renesas_sdhi_internal_dmac_dm_write(host, DM_SEQ_SIZE, ctxt->seq_size); + renesas_sdhi_internal_dmac_dm_write(host, DM_SEQ_SECCNT, + ctxt->seq_seccnt); + renesas_sdhi_internal_dmac_dm_write(host, DM_SEQ_RSP, ctxt->seq_rsp); + renesas_sdhi_internal_dmac_dm_write(host, DM_SEQ_RSP_CHK, + ctxt->seq_rsp_chk); + renesas_sdhi_internal_dmac_dm_write(host, DM_SEQ_ADDR, ctxt->seq_addr); +} + +static int renesas_sdhi_internal_dmac_set_seq_table(struct tmio_mmc_host *host, + struct mmc_request *mrq, + struct scatterlist *sg, + bool ipmmu_on) +{ + struct mmc_card *card = host->mmc->card; + struct mmc_data *data = mrq->data; + struct scatterlist *sg_tmp; + struct tmio_mmc_context ctxt; + unsigned int blksz, blocks; + u32 cmd_opcode, cmd_flag, cmd_arg; + u32 sbc_opcode = 0, sbc_arg = 0; + int i, ctxt_cnt = 0; + + /* SD_COMBO media not tested */ + cmd_opcode = (mrq->cmd->opcode & 0x3f); + cmd_flag = DM_SEQ_CMD_CMDTYP; + if (data->flags & MMC_DATA_READ) + cmd_flag |= DM_SEQ_CMD_DIO; + if (mmc_op_multi(mrq->cmd->opcode) || + (cmd_opcode == SD_IO_RW_EXTENDED && mrq->cmd->arg & 0x08000000)) + cmd_flag |= DM_SEQ_CMD_MULTI; + if (mrq->sbc || cmd_opcode == SD_IO_RW_EXTENDED) + cmd_flag |= DM_SEQ_CMD_NONAUTOSTP; + + switch (mmc_resp_type(mrq->cmd)) { + case MMC_RSP_NONE: + cmd_flag |= DM_SEQ_CMD_RSP_NONE; + break; + case MMC_RSP_R1: + case MMC_RSP_R1 & ~MMC_RSP_CRC: + cmd_flag |= DM_SEQ_CMD_RSP_R1; + break; + case MMC_RSP_R1B: + cmd_flag |= DM_SEQ_CMD_RSP_R1B; + break; + case MMC_RSP_R2: + cmd_flag |= DM_SEQ_CMD_RSP_R2; + break; + case MMC_RSP_R3: + cmd_flag |= DM_SEQ_CMD_RSP_R3; + break; + default: + pr_debug("Unknown response type %d\n", mmc_resp_type(mrq->cmd)); + return -EINVAL; + } + + cmd_arg = mrq->cmd->arg; + if (cmd_opcode == SD_IO_RW_EXTENDED && cmd_arg & 0x08000000) { + /* SDIO CMD53 block mode */ + cmd_arg &= ~0x1ff; + } + + if (mrq->sbc) { + sbc_opcode = (mrq->sbc->opcode & 0x3f) | DM_SEQ_CMD_RSP_R1; + sbc_arg = mrq->sbc->arg & (MMC_CMD23_ARG_REL_WR | + MMC_CMD23_ARG_PACKED | MMC_CMD23_ARG_TAG_REQ); + } + + blksz = data->blksz; + if (ipmmu_on) { + blocks = data->blocks; + memset(&ctxt, 0, sizeof(ctxt)); + + if (sbc_opcode) { + /* set CMD23 */ + ctxt.seq_cmd = sbc_opcode; + ctxt.seq_arg = sbc_arg | blocks; + renesas_sdhi_internal_dmac_set_seq_context + (host, ctxt_cnt, &ctxt); + ctxt_cnt++; + } + + /* set CMD */ + ctxt.seq_cmd = cmd_opcode | cmd_flag; + ctxt.seq_arg = cmd_arg; + if (cmd_opcode == SD_IO_RW_EXTENDED && cmd_arg & 0x08000000) { + /* SDIO CMD53 block mode */ + ctxt.seq_arg |= blocks; + } + ctxt.seq_size = blksz; + ctxt.seq_seccnt = blocks; + ctxt.seq_addr = sg_dma_address(sg); + renesas_sdhi_internal_dmac_set_seq_context + (host, ctxt_cnt, &ctxt); + } else { + for_each_sg(sg, sg_tmp, host->sg_len, i) { + blocks = sg_tmp->length / blksz; + memset(&ctxt, 0, sizeof(ctxt)); + + if (sbc_opcode) { + /* set CMD23 */ + ctxt.seq_cmd = sbc_opcode; + ctxt.seq_arg = sbc_arg | blocks; + if (sbc_arg & MMC_CMD23_ARG_TAG_REQ && card && + card->ext_csd.data_tag_unit_size && + blksz * blocks < + card->ext_csd.data_tag_unit_size) + ctxt.seq_arg &= ~MMC_CMD23_ARG_TAG_REQ; + renesas_sdhi_internal_dmac_set_seq_context + (host, ctxt_cnt, &ctxt); + ctxt_cnt++; + } + + /* set CMD */ + ctxt.seq_cmd = cmd_opcode | cmd_flag; + ctxt.seq_arg = cmd_arg; + if (cmd_opcode == SD_IO_RW_EXTENDED && + cmd_arg & 0x08000000) { + /* SDIO CMD53 block mode */ + ctxt.seq_arg |= blocks; + } + ctxt.seq_size = blksz; + ctxt.seq_seccnt = blocks; + ctxt.seq_addr = sg_dma_address(sg_tmp); + renesas_sdhi_internal_dmac_set_seq_context + (host, ctxt_cnt, &ctxt); + + if (i < (host->sg_len - 1)) { + /* increment address */ + if (cmd_opcode == SD_IO_RW_EXTENDED) { + /* + * sg_len should be 1 in SDIO CMD53 + * byte mode + */ + WARN_ON(!(cmd_arg & 0x08000000)); + if (cmd_arg & 0x04000000) { + /* + * SDIO CMD53 address + * increment mode + */ + cmd_arg += + (blocks * blksz) << 9; + } + } else { + /* card uses block-addressing */ + if (card && !(card->state & 0x4)) + cmd_arg += blocks * blksz; + else + cmd_arg += blocks; + } + ctxt_cnt++; + } + } + } + + if (data->flags & MMC_DATA_READ) { + /* dummy read */ + if (cmd_opcode == MMC_READ_MULTIPLE_BLOCK && card && + blksz == 512 && data->blocks > 1) { + memset(&ctxt, 0, sizeof(ctxt)); + if (sbc_opcode) { + /* set CMD23 */ + ctxt.seq_cmd = sbc_opcode; + ctxt.seq_arg = sbc_arg | 2; + if (sbc_arg & MMC_CMD23_ARG_TAG_REQ && + card->ext_csd.data_tag_unit_size && + blksz * 2 < + card->ext_csd.data_tag_unit_size) + ctxt.seq_arg &= ~MMC_CMD23_ARG_TAG_REQ; + ctxt_cnt++; + renesas_sdhi_internal_dmac_set_seq_context + (host, ctxt_cnt, &ctxt); + } + + /* set CMD18 */ + ctxt.seq_cmd = cmd_opcode | cmd_flag; + ctxt.seq_arg = mrq->cmd->arg; + /* card uses block-addressing */ + if (!(card->state & 0x4)) + ctxt.seq_arg += (data->blocks - 2) * 512; + else + ctxt.seq_arg += data->blocks - 2; + ctxt.seq_size = 512; + ctxt.seq_seccnt = 2; + ctxt.seq_addr = sg_dma_address(&host->bounce_sg); + ctxt_cnt++; + renesas_sdhi_internal_dmac_set_seq_context + (host, ctxt_cnt, &ctxt); + } else { + if (cmd_opcode == SD_SWITCH) { + /* set SD CMD6 twice */ + ctxt.seq_addr = + sg_dma_address(&host->bounce_sg); + } else if ((card && (mmc_card_sdio(card) || + card->type == MMC_TYPE_SD_COMBO)) || + cmd_opcode == SD_IO_RW_EXTENDED) { + /* + * In case of SDIO/SD_COMBO, + * read Common I/O Area 0x0-0x1FF twice. + */ + memset(&ctxt, 0, sizeof(ctxt)); + ctxt.seq_cmd = SD_IO_RW_EXTENDED | + DM_SEQ_CMD_CMDTYP | + DM_SEQ_CMD_DIO | + DM_SEQ_CMD_NONAUTOSTP | + DM_SEQ_CMD_RSP_R1; + /* + * SD_IO_RW_EXTENDED argument format: + * [31] R/W flag -> 0 + * [30:28] Function number -> 0x0 selects + * Common I/O Area + * [27] Block mode -> 0 + * [26] Increment address -> 1 + * [25:9] Regiser address -> 0x0 + * [8:0] Byte/block count -> 0x0 -> 512Bytes + */ + ctxt.seq_arg = 0x04000000; + ctxt.seq_size = 512; + ctxt.seq_seccnt = 1; + ctxt.seq_addr = + sg_dma_address(&host->bounce_sg); + } else { + /* set CMD17 twice */ + memset(&ctxt, 0, sizeof(ctxt)); + ctxt.seq_cmd = MMC_READ_SINGLE_BLOCK | + DM_SEQ_CMD_CMDTYP | + DM_SEQ_CMD_DIO | + DM_SEQ_CMD_RSP_R1; + if ((cmd_opcode == MMC_READ_SINGLE_BLOCK || + cmd_opcode == MMC_READ_MULTIPLE_BLOCK) && + blksz == 512) + ctxt.seq_arg = mrq->cmd->arg; + else + ctxt.seq_arg = 0; + ctxt.seq_size = 512; + ctxt.seq_seccnt = 1; + ctxt.seq_addr = + sg_dma_address(&host->bounce_sg); + } + + for (i = 0; i < 2; i++) { + ctxt_cnt++; + renesas_sdhi_internal_dmac_set_seq_context + (host, ctxt_cnt, &ctxt); + } + } + } + + return ctxt_cnt; +} + +void renesas_sdhi_internal_dmac_start_sequencer(struct tmio_mmc_host *host) +{ + struct mmc_card *card = host->mmc->card; + struct scatterlist *sg = host->sg_ptr; + struct mmc_host *mmc = host->mmc; + struct mmc_request *mrq = host->mrq; + struct mmc_data *data = mrq->data; + int ret, ctxt_num; + u64 val; + bool ipmmu_on = false; + + /* This DMAC cannot handle if sg_len larger than max_segs */ + if (mmc->max_segs == 1 || mmc->max_segs == 3) + WARN_ON(host->sg_len > mmc->max_segs); + else + ipmmu_on = true; + + dev_dbg(&host->pdev->dev, "%s: %d, %x\n", __func__, host->sg_len, + data->flags); + + if (!card && host->mrq->cmd->opcode == MMC_SEND_TUNING_BLOCK) { + /* + * workaround: if card is NULL, + * we can not decide a dummy read command to be added + * to the CMD19. + */ + goto force_pio; + } + + ret = tmio_mmc_pre_dma_transfer(host, data, COOKIE_MAPPED); + if (ret == 0) + goto force_pio; + + if (ipmmu_on) { + /* + * workaround: if we use IPMMU, sometimes unhandled error + * happened + */ + switch (host->mrq->cmd->opcode) { + case MMC_SEND_TUNING_BLOCK_HS200: + case MMC_SEND_TUNING_BLOCK: + goto force_pio; + default: + break; + } + } + + if (data->flags & MMC_DATA_READ && !host->bounce_sg_mapped) { + if (dma_map_sg(&host->pdev->dev, &host->bounce_sg, 1, + DMA_FROM_DEVICE) <= 0) { + dev_err(&host->pdev->dev, "%s: bounce_sg map failed\n", + __func__); + goto force_pio; + } + host->bounce_sg_mapped = true; + } + + renesas_sdhi_internal_dmac_enable_dma(host, true); + /* set context */ + ctxt_num = renesas_sdhi_internal_dmac_set_seq_table(host, mrq, sg, + ipmmu_on); + if (ctxt_num < 0) + goto unmap_sg; + /* set dma mode */ + renesas_sdhi_internal_dmac_dm_write(host, DM_CM_DTRAN_MODE, + DTRAN_MODE_BUS_WID_TH); + /* enable SEQEND irq */ + renesas_sdhi_internal_dmac_dm_write(host, DM_CM_INFO1_MASK, + GENMASK_ULL(31, 0) & ~INFO1_SEQEND); + + if (ctxt_num < 4) { + /* issue table0 commands */ + val = DM_CM_SEQ_CTRL_SEQ_TYPE_SD | + DM_CM_SEQ_CTRL_START_NUM(0) | + DM_CM_SEQ_CTRL_END_NUM(ctxt_num) | + DM_CM_SEQ_CTRL_SEQ_START; + renesas_sdhi_internal_dmac_dm_write(host, DM_CM_SEQ_CTRL, val); + } else { + /* issue table0 commands */ + val = DM_CM_SEQ_CTRL_SEQ_TYPE_SD | + DM_CM_SEQ_CTRL_T_NUM | + DM_CM_SEQ_CTRL_START_NUM(0) | + DM_CM_SEQ_CTRL_END_NUM(3) | + DM_CM_SEQ_CTRL_SEQ_START; + renesas_sdhi_internal_dmac_dm_write(host, DM_CM_SEQ_CTRL, val); + /* issue table1 commands */ + val = DM_CM_SEQ_CTRL_SEQ_TABLE | + DM_CM_SEQ_CTRL_SEQ_TYPE_SD | + DM_CM_SEQ_CTRL_T_NUM | + DM_CM_SEQ_CTRL_START_NUM(0) | + DM_CM_SEQ_CTRL_END_NUM(ctxt_num - 4) | + DM_CM_SEQ_CTRL_SEQ_START; + renesas_sdhi_internal_dmac_dm_write(host, DM_CM_SEQ_CTRL, val); + } + + return; + +unmap_sg: + if (host->bounce_sg_mapped) { + dma_unmap_sg(&host->pdev->dev, &host->bounce_sg, 1, + DMA_FROM_DEVICE); + host->bounce_sg_mapped = false; + } +force_pio: + host->force_pio = true; + renesas_sdhi_internal_dmac_enable_dma(host, false); + + return; /* return for PIO */ +} + static bool renesas_sdhi_internal_dmac_dma_irq(struct tmio_mmc_host *host) { unsigned int ireg, status; status = renesas_sdhi_internal_dmac_dm_read(host, DM_CM_INFO1); + if (host->seq_enabled && status & INFO1_SEQEND) + return true; + ireg = status & ~host->dma_irq_mask; if (ireg & INFO1_DTRANEND0) { @@ -244,6 +700,76 @@ static bool renesas_sdhi_internal_dmac_dma_irq(struct tmio_mmc_host *host) return false; } +static void renesas_sdhi_internal_dmac_seq_irq(struct tmio_mmc_host *host, + int status) +{ + struct mmc_data *data; + struct mmc_command *cmd, *sbc; + u64 dm_cm_info2; + + spin_lock(&host->lock); + data = host->data; + cmd = host->mrq->cmd; + sbc = host->mrq->sbc; + + dm_cm_info2 = renesas_sdhi_internal_dmac_dm_read(host, DM_CM_INFO2); + renesas_sdhi_internal_dmac_dm_write(host, DM_CM_INFO1, 0x0); + renesas_sdhi_internal_dmac_dm_write(host, DM_CM_INFO2, 0x0); + + if (dm_cm_info2) { + pr_debug("sequencer error, CMD%d SD_INFO2=0x%x\n", + cmd->opcode, status >> 16); + if (status & TMIO_STAT_CMDTIMEOUT) { + cmd->error = -ETIMEDOUT; + if (sbc) + sbc->error = -ETIMEDOUT; + } else if ((status & TMIO_STAT_CRCFAIL && + cmd->flags & MMC_RSP_CRC) || + status & TMIO_STAT_STOPBIT_ERR || + status & TMIO_STAT_CMD_IDX_ERR) { + cmd->error = -EILSEQ; + if (sbc) + sbc->error = -EILSEQ; + } + + if (status & TMIO_STAT_DATATIMEOUT) + data->error = -ETIMEDOUT; + else if (status & TMIO_STAT_CRCFAIL || + status & TMIO_STAT_STOPBIT_ERR || + status & TMIO_STAT_TXUNDERRUN) + data->error = -EILSEQ; + } + + if (host->chan_tx && (data->flags & MMC_DATA_WRITE)) { + u32 status = sd_ctrl_read16_and_16_as_32(host, CTL_STATUS); + bool done = false; + + /* + * Has all data been written out yet? Testing on SuperH showed, + * that in most cases the first interrupt comes already with the + * BUSY status bit clear, but on some operations, like mount or + * in the beginning of a write / sync / umount, there is one + * DATAEND interrupt with the BUSY bit set, in this cases + * waiting for one more interrupt fixes the problem. + */ + if (host->pdata->flags & TMIO_MMC_HAS_IDLE_WAIT) { + if (status & TMIO_STAT_SCLKDIVEN) + done = true; + } else { + if (!(status & TMIO_STAT_CMD_BUSY)) + done = true; + } + if (!done) + goto out; + } + /* mask sequencer irq */ + renesas_sdhi_internal_dmac_dm_write(host, DM_CM_INFO1_MASK, 0xffffffff); + tasklet_schedule(&host->seq_complete); + +out: + spin_unlock(&host->lock); +} + static void renesas_sdhi_internal_dmac_request_dma(struct tmio_mmc_host *host, struct tmio_mmc_data *pdata) @@ -260,6 +786,19 @@ renesas_sdhi_internal_dmac_request_dma(struct tmio_mmc_host *host, tasklet_init(&host->dma_issue, renesas_sdhi_internal_dmac_issue_tasklet_fn, (unsigned long)host); + tasklet_init(&host->seq_complete, + renesas_sdhi_internal_dmac_seq_complete_tasklet_fn, + (unsigned long)host); + /* alloc bounce_buf for dummy read */ + host->bounce_buf = (u8 *)__get_free_page(GFP_KERNEL | GFP_DMA); + if (!host->bounce_buf) { + host->chan_rx = NULL; + host->chan_tx = NULL; + return; + } + /* setup bounce_sg for dummy read */ + sg_init_one(&host->bounce_sg, host->bounce_buf, 1024); + host->bounce_sg_mapped = false; } static void @@ -267,6 +806,17 @@ renesas_sdhi_internal_dmac_release_dma(struct tmio_mmc_host *host) { /* Each value is set to zero to assume "disabling" each DMA */ host->chan_rx = host->chan_tx = NULL; + + /* free bounce_buf for dummy read */ + if (host->bounce_buf) { + if (host->bounce_sg_mapped) { + dma_unmap_sg(&host->pdev->dev, &host->bounce_sg, 1, + DMA_FROM_DEVICE); + host->bounce_sg_mapped = false; + } + free_pages((unsigned long)host->bounce_buf, 0); + host->bounce_buf = NULL; + } } static const struct tmio_mmc_dma_ops renesas_sdhi_internal_dmac_dma_ops = { @@ -277,6 +827,8 @@ static const struct tmio_mmc_dma_ops renesas_sdhi_internal_dmac_dma_ops = { .abort = renesas_sdhi_internal_dmac_abort_dma, .dataend = renesas_sdhi_internal_dmac_dataend_dma, .dma_irq = renesas_sdhi_internal_dmac_dma_irq, + .seq_start = renesas_sdhi_internal_dmac_start_sequencer, + .seq_irq = renesas_sdhi_internal_dmac_seq_irq, }; /* -- 2.7.4