Загрузка данных


static void lpi2c_imx_write(struct lpi2c_imx_struct *lpi2c_imx,
			    struct i2c_msg *msgs)
{
	lpi2c_imx->tx_buf = msgs->buf;
	lpi2c_imx_set_tx_watermark(lpi2c_imx);
	lpi2c_imx_write_txfifo(lpi2c_imx);
}

static void lpi2c_imx_read(struct lpi2c_imx_struct *lpi2c_imx,
			   struct i2c_msg *msgs)
{
	unsigned int temp;

	lpi2c_imx->rx_buf = msgs->buf;
	lpi2c_imx->block_data = msgs->flags & I2C_M_RECV_LEN;

	lpi2c_imx_set_rx_watermark(lpi2c_imx);
	temp = msgs->len > CHUNK_DATA ? CHUNK_DATA - 1 : msgs->len - 1;
	temp |= (RECV_DATA << 8);
	writel(temp, lpi2c_imx->base + LPI2C_MTDR);

	lpi2c_imx_intctrl(lpi2c_imx, MIER_RDIE | MIER_NDIE);
}

static bool is_use_dma(struct lpi2c_imx_struct *lpi2c_imx, struct i2c_msg *msg)
{
	if (!lpi2c_imx->can_use_dma)
		return false;

	/*
	 * When system is in suspend process. LPI2C should use PIO to transfer data to
	 * avoid issue caused by not ready DMA HW resource.
	 */
	if (pm_suspend_target_state != PM_SUSPEND_ON)
		return false;
	/*
	 * When the length of data is less than I2C_DMA_THRESHOLD,
	 * cpu mode is used directly to avoid low performance.
	 */
	if (msg->len < I2C_DMA_THRESHOLD)
		return false;

	return true;
}

static int lpi2c_imx_pio_xfer(struct lpi2c_imx_struct *lpi2c_imx,
				 struct i2c_msg *msg)
{
	int ret;

	reinit_completion(&lpi2c_imx->complete);

	if (msg->flags & I2C_M_RD)
		lpi2c_imx_read(lpi2c_imx, msg);
	else
		lpi2c_imx_write(lpi2c_imx, msg);

	ret = lpi2c_imx_pio_msg_complete(lpi2c_imx);
	if (ret)
		return ret;

	return 0;
}

static int lpi2c_imx_dma_timeout_calculate(struct lpi2c_imx_struct *lpi2c_imx)
{
	unsigned long time = 0;

	time = 8 * lpi2c_imx->dma->dma_len * 1000 / lpi2c_imx->bitrate;

	/* Add extra second for scheduler related activities */
	time += 1;

	/* Double calculated time */
	return msecs_to_jiffies(time * MSEC_PER_SEC);
}

static int lpi2c_imx_alloc_rx_cmd_buf(struct lpi2c_imx_struct *lpi2c_imx)
{
	struct lpi2c_imx_dma *dma = lpi2c_imx->dma;
	u16 rx_remain = dma->dma_len;
	int cmd_num;
	u16 temp;

	/*
	 * Calculate the number of rx command words via the DMA TX channel
	 * writing into command register based on the i2c msg len, and build
	 * the rx command words buffer.
	 */
	cmd_num = DIV_ROUND_UP(rx_remain, CHUNK_DATA);
	dma->rx_cmd_buf = kcalloc(cmd_num, sizeof(u16), GFP_KERNEL);
	dma->rx_cmd_buf_len = cmd_num * sizeof(u16);

	if (!dma->rx_cmd_buf) {
		dev_err(&lpi2c_imx->adapter.dev, "Alloc RX cmd buffer failed\n");
		return -ENOMEM;
	}

	for (int i = 0; i < cmd_num ; i++) {
		temp = rx_remain > CHUNK_DATA ? CHUNK_DATA - 1 : rx_remain - 1;
		temp |= (RECV_DATA << 8);
		rx_remain -= CHUNK_DATA;
		dma->rx_cmd_buf[i] = temp;
	}

	return 0;
}

static int lpi2c_imx_dma_msg_complete(struct lpi2c_imx_struct *lpi2c_imx)
{
	unsigned long time_left, time;

	time = lpi2c_imx_dma_timeout_calculate(lpi2c_imx);
	time_left = wait_for_completion_timeout(&lpi2c_imx->complete, time);
	if (time_left == 0) {
		dev_err(&lpi2c_imx->adapter.dev, "I/O Error in DMA Data Transfer\n");
		return -ETIMEDOUT;
	}

	return 0;
}

static void lpi2c_dma_unmap(struct lpi2c_imx_dma *dma)
{
	struct dma_chan *chan = dma->dma_direction == DMA_FROM_DEVICE
				? dma->chan_rx : dma->chan_tx;

	dma_unmap_single(chan->device->dev, dma->dma_addr,
			 dma->dma_len, dma->dma_direction);

	dma->dma_direction = DMA_NONE;
}

static void lpi2c_cleanup_rx_cmd_dma(struct lpi2c_imx_dma *dma)
{
	dmaengine_terminate_sync(dma->chan_tx);
	dma_unmap_single(dma->chan_tx->device->dev, dma->dma_tx_addr,
				dma->rx_cmd_buf_len, DMA_TO_DEVICE);
}

static void lpi2c_cleanup_dma(struct lpi2c_imx_dma *dma)
{
	if (dma->dma_direction == DMA_FROM_DEVICE)
		dmaengine_terminate_sync(dma->chan_rx);
	else if (dma->dma_direction == DMA_TO_DEVICE)
		dmaengine_terminate_sync(dma->chan_tx);

	lpi2c_dma_unmap(dma);
}

static void lpi2c_dma_callback(void *data)
{
	struct lpi2c_imx_struct *lpi2c_imx = (struct lpi2c_imx_struct *)data;

	complete(&lpi2c_imx->complete);
}

static int lpi2c_dma_rx_cmd_submit(struct lpi2c_imx_struct *lpi2c_imx)
{
	struct lpi2c_imx_dma *dma = lpi2c_imx->dma;
	struct dma_chan *txchan = dma->chan_tx;
	struct dma_async_tx_descriptor *rx_cmd_desc;
	dma_cookie_t cookie;

	dma->dma_tx_addr = dma_map_single(txchan->device->dev,
						 dma->rx_cmd_buf,
						 dma->rx_cmd_buf_len, DMA_TO_DEVICE);
	if (dma_mapping_error(txchan->device->dev, dma->dma_tx_addr)) {
		dev_err(&lpi2c_imx->adapter.dev, "dma map failed, use pio\n");
		return -EINVAL;
	}

	rx_cmd_desc = dmaengine_prep_slave_single(txchan, dma->dma_tx_addr,
				 dma->rx_cmd_buf_len, DMA_MEM_TO_DEV,
				 DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
	if (!rx_cmd_desc) {
		dma_unmap_single(txchan->device->dev, dma->dma_tx_addr,
				 dma->rx_cmd_buf_len, DMA_TO_DEVICE);
		dev_err(&lpi2c_imx->adapter.dev, "dma prep slave sg failed, use pio\n");
		return -EINVAL;
	}

	cookie = dmaengine_submit(rx_cmd_desc);
	if (dma_submit_error(cookie)) {
		dma_unmap_single(txchan->device->dev, dma->dma_tx_addr,
				 dma->rx_cmd_buf_len, DMA_TO_DEVICE);
		dmaengine_desc_free(rx_cmd_desc);
		dev_err(&lpi2c_imx->adapter.dev, "submitting dma failed, use pio\n");
		return -EINVAL;
	}

	dma_async_issue_pending(txchan);

	return 0;
}

static int lpi2c_dma_submit(struct lpi2c_imx_struct *lpi2c_imx)
{
	struct lpi2c_imx_dma *dma = lpi2c_imx->dma;
	bool read = dma->dma_msg_flag & I2C_M_RD;
	enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
	struct dma_chan *chan = read ? dma->chan_rx : dma->chan_tx;
	struct dma_async_tx_descriptor *desc;
	dma_cookie_t cookie;

	dma->dma_direction = dir;
	dma->dma_addr = dma_map_single(chan->device->dev,
					     dma->dma_buf,
					     dma->dma_len, dir);
	if (dma_mapping_error(chan->device->dev, dma->dma_addr)) {
		dev_err(&lpi2c_imx->adapter.dev, "dma map failed, use pio\n");
		return -EINVAL;
	}

	desc = dmaengine_prep_slave_single(chan, dma->dma_addr,
					 dma->dma_len, read ?
					 DMA_DEV_TO_MEM : DMA_MEM_TO_DEV,
					 DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
	if (!desc) {
		dev_err(&lpi2c_imx->adapter.dev, "dma prep slave sg failed, use pio\n");
		lpi2c_dma_unmap(dma);
		return -EINVAL;
	}

	reinit_completion(&lpi2c_imx->complete);
	desc->callback = lpi2c_dma_callback;
	desc->callback_param = (void *)lpi2c_imx;

	cookie = dmaengine_submit(desc);
	if (dma_submit_error(cookie)) {
		dev_err(&lpi2c_imx->adapter.dev, "submitting dma failed, use pio\n");
		lpi2c_dma_unmap(dma);
		dmaengine_desc_free(desc);
		return -EINVAL;
	}

	/* Can't switch to PIO mode when DMA have started transfer */
	dma->using_pio_mode = false;

	dma_async_issue_pending(chan);

	return 0;
}