static int gpmi_nfc_exec_op()

in nand/raw/gpmi-nand/gpmi-nand.c [2250:2421]


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 *dma_completion, *bch_completion;
	unsigned long to;

	if (check_only)
		return 0;

	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) {
		pm_runtime_put_noidle(this->dev);
		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;
		ret = gpmi_nfc_apply_timings(this);
		if (ret)
			return ret;
	}

	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;
		}

		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);
	}

	desc->callback = dma_irq_callback;
	desc->callback_param = this;
	dma_completion = &this->dma_done;
	bch_completion = NULL;

	init_completion(dma_completion);

	if (this->bch && buf_read) {
		writel(BM_BCH_CTRL_COMPLETE_IRQ_EN,
		       this->resources.bch_regs + HW_BCH_CTRL_SET);
		bch_completion = &this->bch_done;
		init_completion(bch_completion);
	}

	dmaengine_submit(desc);
	dma_async_issue_pending(get_dma_chan(this));

	to = wait_for_completion_timeout(dma_completion, msecs_to_jiffies(1000));
	if (!to) {
		dev_err(this->dev, "DMA timeout, last DMA\n");
		gpmi_dump_info(this);
		ret = -ETIMEDOUT;
		goto unmap;
	}

	if (this->bch && buf_read) {
		to = wait_for_completion_timeout(bch_completion, msecs_to_jiffies(1000));
		if (!to) {
			dev_err(this->dev, "BCH 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;
}