Hi Sascha, Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> wrote on Tue, 9 Apr 2019 13:34:21 +0200: > The gpmi driver performance suffers from nand operations being split > in multiple small dma transfers. This has been forced by the nand layer > in the former days, but now with exec_op we can use the controller as > intended. > > With this patch gpmi_nfc_exec_op becomes the main entry point to nand > operations. Here all instructions are collected and chained as separate > DMA transfers. In the end whole chain is fired and waited to be > finished. gpmi_nfc_exec_op only does the hardware operations, bad block > marker swapping and buffer scrambling is done by the callers. It's worth > noting that the nand_*_op functions always take the buffer lengths for > the data that the nand chip actually transfers. When doing BCH we have > to calculate the net data size from the raw data size in some places. > > This patch has been tested with 2048/64 and 2048/128 byte nand on > i.MX6q. mtd_oobtest, mtd_subpagetest and mtd_speedtest run without > errors. nandbiterrs, nandpagetest and nandsubpagetest userspace tests > from mtdutils run without errors and UBIFS can successfully be mounted. > > Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> > --- > drivers/dma/mxs-dma.c | 3 + [...] > +static int gpmi_nfc_exec_op(struct nand_chip *chip, > + const struct nand_operation *op, > + bool check_only) > +{ > + const struct nand_op_instr *instr; > + struct gpmi_nand_data *this = nand_get_controller_data(chip); > + struct dma_async_tx_descriptor *desc = NULL; > + int i, ret, buf_len = 0, nbufs = 0; > + u8 cmd = 0; > + void *buf_read = NULL; > + const void *buf_write = NULL; > + bool direct = false; > + struct completion *completion; > + unsigned long to; > + > + this->ntransfers = 0; > + for (i = 0; i < GPMI_MAX_TRANSFERS; i++) > + this->transfers[i].direction = DMA_NONE; > + > + ret = pm_runtime_get_sync(this->dev); > + if (ret < 0) > + return ret; > + > + /* > + * This driver currently supports only one NAND chip. Plus, dies share > + * the same configuration. So once timings have been applied on the > + * controller side, they will not change anymore. When the time will > + * come, the check on must_apply_timings will have to be dropped. > + */ > + if (this->hw.must_apply_timings) { > + this->hw.must_apply_timings = false; > + gpmi_nfc_apply_timings(this); > + } > + > + dev_dbg(this->dev, "%s: %d instructions\n", __func__, op->ninstrs); > + > + for (i = 0; i < op->ninstrs; i++) { > + instr = &op->instrs[i]; > + > + nand_op_trace(" ", instr); > + > + switch (instr->type) { > + case NAND_OP_WAITRDY_INSTR: > + desc = gpmi_chain_wait_ready(this); > + break; > + case NAND_OP_CMD_INSTR: > + cmd = instr->ctx.cmd.opcode; > + > + /* > + * When this command has an address cycle chain it > + * together with the address cycle > + */ > + if (i + 1 != op->ninstrs && > + op->instrs[i + 1].type == NAND_OP_ADDR_INSTR) > + continue; > + > + desc = gpmi_chain_command(this, cmd, NULL, 0); > + > + break; > + case NAND_OP_ADDR_INSTR: > + desc = gpmi_chain_command(this, cmd, instr->ctx.addr.addrs, > + instr->ctx.addr.naddrs); > + break; > + case NAND_OP_DATA_OUT_INSTR: > + buf_write = instr->ctx.data.buf.out; > + buf_len = instr->ctx.data.len; > + nbufs++; > + > + desc = gpmi_chain_data_write(this, buf_write, buf_len); > + > + break; > + case NAND_OP_DATA_IN_INSTR: > + if (!instr->ctx.data.len) > + break; > + buf_read = instr->ctx.data.buf.in; > + buf_len = instr->ctx.data.len; > + nbufs++; > + > + desc = gpmi_chain_data_read(this, buf_read, buf_len, > + &direct); > + break; > + } So there is no limitation for the controller in terms of address/data cycles that can be asserted in one go? > + > + if (!desc) { > + ret = -ENXIO; > + goto unmap; > + } > + } > + > + dev_dbg(this->dev, "%s setup done\n", __func__); > + > + if (nbufs > 1) { > + dev_err(this->dev, "Multiple data instructions not supported\n"); > + ret = -EINVAL; > + goto unmap; > + } > + > + if (this->bch) { > + writel(this->bch_flashlayout0, > + this->resources.bch_regs + HW_BCH_FLASH0LAYOUT0); > + writel(this->bch_flashlayout1, > + this->resources.bch_regs + HW_BCH_FLASH0LAYOUT1); > + } > + > + if (this->bch && buf_read) { > + writel(BM_BCH_CTRL_COMPLETE_IRQ_EN, > + this->resources.bch_regs + HW_BCH_CTRL_SET); > + completion = &this->bch_done; > + } else { > + desc->callback = dma_irq_callback; > + desc->callback_param = this; > + completion = &this->dma_done; > + } > + > + init_completion(completion); > + > + dmaengine_submit(desc); > + dma_async_issue_pending(get_dma_chan(this)); > + > + to = wait_for_completion_timeout(completion, msecs_to_jiffies(1000)); > + if (!to) { > + dev_err(this->dev, "DMA timeout, last DMA\n"); > + gpmi_dump_info(this); > + ret = -ETIMEDOUT; > + goto unmap; > + } > + > + writel(BM_BCH_CTRL_COMPLETE_IRQ_EN, > + this->resources.bch_regs + HW_BCH_CTRL_CLR); > + gpmi_clear_bch(this); > + > + ret = 0; > + > +unmap: > + for (i = 0; i < this->ntransfers; i++) { > + struct gpmi_transfer *transfer = &this->transfers[i]; > + > + if (transfer->direction != DMA_NONE) > + dma_unmap_sg(this->dev, &transfer->sgl, 1, > + transfer->direction); > + } > + > + if (!ret && buf_read && !direct) > + memcpy(buf_read, this->data_buffer_dma, > + gpmi_raw_len_to_len(this, buf_len)); > + > + this->bch = false; > + > + pm_runtime_mark_last_busy(this->dev); > + pm_runtime_put_autosuspend(this->dev); > + > + return ret; > +} > + Thanks, Miquèl ______________________________________________________ Linux MTD discussion mailing list http://lists.infradead.org/mailman/listinfo/linux-mtd/