📄 samsung s3c24xx sdmmc driver.c
字号:
+ host->status = "nothing to complete";
+ clear_imask(host);
+ goto irq_out;
+ }
+
+ if (!host->mrq) {
+ host->status = "no active mrq";
+ clear_imask(host);
+ goto irq_out;
+ }
+
+ cmd = host->cmd_is_stop?host->mrq->stop:host->mrq->cmd;
+
+ if (!cmd) {
+ host->status = "no active cmd";
+ clear_imask(host);
+ goto irq_out;
+ }
+
+ if (!host->dodma) {
+ if ((host->pio_active == XFER_WRITE) &&
+ (mci_fsta & S3C2410_SDIFSTA_TFDET)) {
+
+ disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
+ tasklet_schedule(&host->pio_tasklet);
+ host->status = "pio tx";
+ }
+
+ if ((host->pio_active == XFER_READ) &&
+ (mci_fsta & S3C2410_SDIFSTA_RFDET)) {
+
+ disable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF |
+ S3C2410_SDIIMSK_RXFIFOLAST);
+
+ tasklet_schedule(&host->pio_tasklet);
+ host->status = "pio rx";
+ }
+ }
+
+ if (mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) {
+ cmd->error = -ETIMEDOUT;
+ host->status = "error: command timeout";
+ goto fail_transfer;
+ }
+
+ if (mci_csta & S3C2410_SDICMDSTAT_CMDSENT) {
+ if (host->complete_what == COMPLETION_CMDSENT) {
+ host->status = "ok: command sent";
+ goto close_transfer;
+ }
+
+ mci_cclear |= S3C2410_SDICMDSTAT_CMDSENT;
+ }
+
+ if (mci_csta & S3C2410_SDICMDSTAT_CRCFAIL) {
+ if (cmd->flags & MMC_RSP_CRC) {
+ if (host->mrq->cmd->flags & MMC_RSP_136) {
+ dbg(host, dbg_irq, "fixup for chip bug: "
+ "ignore CRC fail with long rsp\n");
+ } else {
+ cmd->error = -EILSEQ;
+ host->status = "error: bad command crc";
+ goto fail_transfer;
+ }
+ }
+
+ mci_cclear |= S3C2410_SDICMDSTAT_CRCFAIL;
+ }
+
+ if (mci_csta & S3C2410_SDICMDSTAT_RSPFIN) {
+ if (host->complete_what == COMPLETION_RSPFIN) {
+ host->status = "ok: command response received";
+ goto close_transfer;
+ }
+
+ if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
+ host->complete_what = COMPLETION_XFERFINISH;
+
+ mci_cclear |= S3C2410_SDICMDSTAT_RSPFIN;
+ }
+
+ /* errors handled after this point are only relevant
+ when a data transfer is in progress */
+
+ if (!cmd->data)
+ goto clear_status_bits;
+
+ /* Check for FIFO failure */
+ if (host->is2440) {
+ if (mci_fsta & S3C2440_SDIFSTA_FIFOFAIL) {
+ host->mrq->data->error = -EIO;
+ host->status = "error: 2440 fifo failure";
+ goto fail_transfer;
+ }
+ } else {
+ if (mci_dsta & S3C2410_SDIDSTA_FIFOFAIL) {
+ cmd->data->error = -EIO;
+ host->status = "error: fifo failure";
+ goto fail_transfer;
+ }
+ }
+
+ if (mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL) {
+ cmd->data->error = -EILSEQ;
+ host->status = "error: bad data crc (outgoing)";
+ goto fail_transfer;
+ }
+
+ if (mci_dsta & S3C2410_SDIDSTA_CRCFAIL) {
+ cmd->data->error = -EIO;
+ host->status = "error: bad data crc (incoming)";
+ goto fail_transfer;
+ }
+
+ if (mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT) {
+ cmd->data->error = -ETIMEDOUT;
+ host->status = "error: data timeout";
+ goto fail_transfer;
+ }
+
+ if (mci_dsta & S3C2410_SDIDSTA_XFERFINISH) {
+ if (host->complete_what == COMPLETION_XFERFINISH) {
+ host->status = "ok: data transfer completed";
+ goto close_transfer;
+ }
+
+ if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
+ host->complete_what = COMPLETION_RSPFIN;
+
+ mci_dclear |= S3C2410_SDIDSTA_XFERFINISH;
+ }
+
+clear_status_bits:
+ writel(mci_cclear, host->base + S3C2410_SDICMDSTAT);
+ writel(mci_dclear, host->base + S3C2410_SDIDSTA);
+
+ goto irq_out;
+
+fail_transfer:
+ host->pio_active = XFER_NONE;
+
+close_transfer:
+ host->complete_what = COMPLETION_FINALIZE;
+
+ clear_imask(host);
+ tasklet_schedule(&host->pio_tasklet);
+
+ goto irq_out;
+
+irq_out:
+ dbg(host, dbg_irq, "csta:0x%08x dsta:0x%08x "
+ "fsta:0x%08x dcnt:0x%08x status:%s.\n",
+ mci_csta, mci_dsta, mci_fsta,
+ mci_dcnt, host->status);
+
+ spin_unlock_irqrestore(&host->complete_lock, iflags);
+ return IRQ_HANDLED;
+
+}
+
+/*
+ * ISR for the CardDetect Pin
+*/
+
+static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
+{
+ struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
+
+ dbg(host, dbg_irq, "card detect\n");
+
+ mmc_detect_change(host->mmc, 500);
+
+ return IRQ_HANDLED;
+}
+
+void s3cmci_dma_done_callback(struct s3c2410_dma_chan *dma_ch, void *buf_id,
+ int size, enum s3c2410_dma_buffresult result)
+{
+ unsigned long iflags;
+ u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt;
+ struct s3cmci_host *host = (struct s3cmci_host *)buf_id;
+
+ mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
+ mci_dsta = readl(host->base + S3C2410_SDIDSTA);
+ mci_fsta = readl(host->base + S3C2410_SDIFSTA);
+ mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
+
+ if ((!host->mrq) || (!host->mrq) || (!host->mrq->data))
+ return;
+
+ if (!host->dmatogo)
+ return;
+
+ spin_lock_irqsave(&host->complete_lock, iflags);
+
+ if (result != S3C2410_RES_OK) {
+ dbg(host, dbg_fail, "DMA FAILED: csta=0x%08x dsta=0x%08x "
+ "fsta=0x%08x dcnt:0x%08x result:0x%08x toGo:%u\n",
+ mci_csta, mci_dsta, mci_fsta,
+ mci_dcnt, result, host->dmatogo);
+
+ goto fail_request;
+ }
+
+ host->dmatogo--;
+ if (host->dmatogo) {
+ dbg(host, dbg_dma, "DMA DONE Size:%i DSTA:[%08x] "
+ "DCNT:[%08x] toGo:%u\n",
+ size, mci_dsta, mci_dcnt, host->dmatogo);
+
+ goto out;
+ }
+
+ dbg(host, dbg_dma, "DMA FINISHED Size:%i DSTA:%08x DCNT:%08x\n",
+ size, mci_dsta, mci_dcnt);
+
+ host->complete_what = COMPLETION_FINALIZE;
+
+out:
+ tasklet_schedule(&host->pio_tasklet);
+ spin_unlock_irqrestore(&host->complete_lock, iflags);
+ return;
+
+
+fail_request:
+ host->mrq->data->error = -EIO;
+ host->complete_what = COMPLETION_FINALIZE;
+ writel(0, host->base + host->sdiimsk);
+ goto out;
+
+}
+
+static void finalize_request(struct s3cmci_host *host)
+{
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_command *cmd = host->cmd_is_stop?mrq->stop:mrq->cmd;
+ int debug_as_failure = 0;
+
+ if (host->complete_what != COMPLETION_FINALIZE)
+ return;
+
+ if (!mrq)
+ return;
+
+ if (cmd->data && (cmd->error == 0) &&
+ (cmd->data->error == 0)) {
+
+ if (host->dodma && (!host->dma_complete)) {
+ dbg(host, dbg_dma, "DMA Missing!\n");
+ return;
+ }
+ }
+
+ /* Read response */
+ cmd->resp[0] = readl(host->base + S3C2410_SDIRSP0);
+ cmd->resp[1] = readl(host->base + S3C2410_SDIRSP1);
+ cmd->resp[2] = readl(host->base + S3C2410_SDIRSP2);
+ cmd->resp[3] = readl(host->base + S3C2410_SDIRSP3);
+
+ /* reset clock speed, as it could still be set low for */
+ writel(host->prescaler, host->base + S3C2410_SDIPRE);
+
+ if (cmd->error)
+ debug_as_failure = 1;
+
+ if (cmd->data && cmd->data->error)
+ debug_as_failure = 1;
+
+#ifdef CONFIG_MMC_DEBUG
+ dbg_dumpcmd(host, cmd, debug_as_failure);
+#endif
+ /* Cleanup controller */
+ writel(0, host->base + S3C2410_SDICMDARG);
+ writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
+ writel(0, host->base + S3C2410_SDICMDCON);
+ writel(0, host->base + host->sdiimsk);
+
+ if (cmd->data && cmd->error)
+ cmd->data->error = cmd->error;
+
+ if (cmd->data && cmd->data->stop && (!host->cmd_is_stop)) {
+ host->cmd_is_stop = 1;
+ s3cmci_send_request(host->mmc);
+ return;
+ }
+
+ /* If we have no data transfer we are finished here */
+ if (!mrq->data)
+ goto request_done;
+
+ /* Calulate the amout of bytes transfer, but only if there was
+ * no error */
+ if (mrq->data->error == 0)
+ mrq->data->bytes_xfered =
+ (mrq->data->blocks * mrq->data->blksz);
+ else
+ mrq->data->bytes_xfered = 0;
+
+ /* If we had an error while transfering data we flush the
+ * DMA channel and the fifo to clear out any garbage */
+ if (mrq->data->error) {
+ if (host->dodma)
+ s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
+
+ if (host->is2440) {
+ /* Clear failure register and reset fifo */
+ writel(S3C2440_SDIFSTA_FIFORESET |
+ S3C2440_SDIFSTA_FIFOFAIL,
+ host->base + S3C2410_SDIFSTA);
+ } else {
+ u32 mci_con;
+
+ /* reset fifo */
+ mci_con = readl(host->base + S3C2410_SDICON);
+ mci_con |= S3C2410_SDICON_FIFORESET;
+
+ writel(mci_con, host->base + S3C2410_SDICON);
+ }
+ }
+
+request_done:
+ host->complete_what = COMPLETION_NONE;
+ host->mrq = NULL;
+ mmc_request_done(host->mmc, mrq);
+}
+
+
+void s3cmci_dma_setup(struct s3cmci_host *host, enum s3c2410_dmasrc source)
+{
+ static int setup_ok;
+ static enum s3c2410_dmasrc last_source = -1;
+
+ if (last_source == source)
+ return;
+
+ last_source = source;
+
+ s3c2410_dma_devconfig(host->dma, source, 3,
+ host->mem->start + host->sdidata);
+
+ if (!setup_ok) {
+ s3c2410_dma_config(host->dma, 4,
+ (S3C2410_DCON_HWTRIG | S3C2410_DCON_CH0_SDI));
+ s3c2410_dma_set_buffdone_fn(host->dma,
+ s3cmci_dma_done_callback);
+ s3c2410_dma_setflags(host->dma, S3C2410_DMAF_AUTOSTART);
+ setup_ok = 1;
+ }
+}
+
+static void s3cmci_send_command(struct s3cmci_host *host,
+ struct mmc_command *cmd)
+{
+ u32 ccon, imsk;
+
+ imsk = S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_CMDTIMEOUT |
+ S3C2410_SDIIMSK_RESPONSEND | S3C2410_SDIIMSK_CMDSENT |
+ S3C2410_SDIIMSK_RESPONSECRC;
+
+ enable_imask(host, imsk);
+
+ if (cmd->data)
+ host->complete_what = COMPLETION_XFERFINISH_RSPFIN;
+ else if (cmd->flags & MMC_RSP_PRESENT)
+ host->complete_what = COMPLETION_RSPFIN;
+ else
+ host->complete_what = COMPLETION_CMDSENT;
+
+ writel(cmd->arg, host->base + S3C2410_SDICMDARG);
+
+ ccon = cmd->opcode & S3C2410_SDICMDCON_INDEX;
+ ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART;
+
+ if (cmd->flags & MMC_RSP_PRESENT)
+ ccon |= S3C2410_SDICMDCON_WAITRSP;
+
+ if (cmd->flags & MMC_RSP_136)
+ ccon |= S3C2410_SDICMDCON_LONGRSP;
+
+ writel(ccon, host->base + S3C2410_SDICMDCON);
+}
+
+static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
+{
+ u32 dcon, imsk, stoptries = 3;
+
+ /* write DCON register */
+
+ if (!data) {
+ writel(0, host->base + S3C2410_SDIDCON);
+ return 0;
+ }
+
+ while (readl(host->base + S3C2410_SDIDSTA) &
+ (S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON)) {
+
+ dbg(host, dbg_err,
+ "mci_setup_data() transfer stillin progress.\n");
+
+ writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
+ s3cmci_reset(host);
+
+ if (0 == (stoptries--)) {
+#ifdef CONFIG_MMC_DEBUG
+ dbg_dumpregs(host, "DRF");
+#endif
+
+ return -EINVAL;
+ }
+ }
+
+ dcon = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK;
+
+ if (host->dodma)
+ dcon |= S3C2410_SDIDCON_DMAEN;
+
+ if (host->bus_width == MMC_BUS_WIDTH_4)
+ dcon |= S3C2410_SDIDCON_WIDEBUS;
+
+ if (!(data->flags & MMC_DATA_STREAM))
+ dcon |= S3C2410_SDIDCON_BLOCKMODE;
+
+ if (data->flags & MMC_DATA_WRITE) {
+ dcon |= S3C2410_SDIDCON_TXAFTERRESP;
+ dcon |= S3C2410_SDIDCON_XFER_TXSTART;
+ }
+
+ if (data->flags & MMC_DATA_READ) {
+ dcon |= S3C2410_SDIDCON_RXAFTERCMD;
+ dcon |= S3C2410_SDIDCON_XFER_RXSTART;
+ }
+
+ if (host->is2440) {
+ dcon |= S3C2440_SDIDCON_DS_WORD;
+ dcon |= S3C2440_SDIDCON_DATSTART;
+ }
+
+ writel(dcon, host->base + S3C2410_SDIDCON);
+
+ /* write BSIZE register */
+
+ writel(data->blksz, host->base + S3C2410_SDIBSIZE);
+
+ /* add to IMASK register */
+ imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
+ S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
+
+ enable_imask(host, imsk);
+
+ /* write TIMER register */
+
+ if (host->is2440) {
+ writel(0x007FFFFF, host->base + S3C2410_SDITIMER);
+ } else {
+ writel(0x0000FFFF, host->base + S3C2410_SDITIMER);
+
+ /* FIX: set slow clock to prevent timeouts on read */
+ if (data->flags & MMC_DATA_READ)
+ writel(0xFF, host->base + S3C2410_SDIPRE);
+ }
+
+ return 0;
+}
+
+static int s3cmci_prepare_pio(struct s3cmci_host *host, struct mmc_data *data)
+{
+ int rw = (data->flags & MMC_DATA_WRITE)?1:0;
+
+ if (rw != ((data->flags & MMC_DATA_READ)?0:1))
+ return -EINVAL;
+
+ host->pio_sgptr = 0;
+ host->pio_words = 0;
+ host->pio_count = 0;
+ host->pio_active = rw?XFER_WRITE:XFER_READ;
+
+ if (rw) {
+ do_pio_write(host);
+ enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
+ } else {
+ enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF
+ | S3C2410_SDIIMSK_RXFIFOLAST);
+ }
+
+ return 0;
+}
+
+static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
+{
+ int dma_len, i;
+
+ int rw = (data->flags & MMC_DATA_WRITE)?1:0;
+
+ if (rw != ((data->flags & MMC_DATA_READ)?0:1))
+ return -EINVAL;
+
+ s3cmci_dma_setup(host, rw?S3C2410_DMASRC_MEM:S3C2410_DMASRC_HW);
+ s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
+
+ dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ (rw)?DMA_TO_DEVICE:DMA_FROM_DEVICE);
+
+
+ if (dma_len == 0)
+ return -ENOMEM;
+
+ host->dma_complete = 0;
+ host->dmatogo = dma_len;
+
+ for (i = 0; i < dma_len; i++) {
+ int res;
+
+ dbg(host, dbg_dma, "enqueue %i:%u@%u\n", i,
+ sg_dma_address(&data->sg[i]),
+ sg_dma_len(&data->sg[i]));
+
+ res = s3c2410_dma_enqueue(host->dma, (void *) host,
+ sg_dma_address(&data->sg[i]),
+ sg_dma_len(&data->sg[i]));
+
+ if (res) {
+ s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
+ return -EBUSY;
+ }
+ }
+
+ s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);
+
+ return 0;
+}
+
+static void s3cmci_send_request(struct mmc_host *mmc)
+{
+ struct s3cmci_host *host = mmc_priv(mmc);
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_command *cmd = host->cmd_is_stop?mrq->stop:mrq->cmd;
+
+ host->ccnt++;
+#ifdef CONFIG_MMC_DEBUG
+ prepare_dbgmsg(host, cmd, host->cmd_is_stop);
+#endif
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -