[PATCH 14/14] mtd: rawnand: gpmi: Implement exec_op

Miquel Raynal miquel.raynal at bootlin.com
Wed Apr 17 03:33:55 PDT 2019


Hi Sascha,

Sascha Hauer <s.hauer at pengutronix.de> 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 at pengutronix.de>
> ---
>  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



More information about the linux-mtd mailing list