ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/marvell/linux/drivers/spi/spi-asr-dma.c b/marvell/linux/drivers/spi/spi-asr-dma.c
new file mode 100644
index 0000000..27c1780
--- /dev/null
+++ b/marvell/linux/drivers/spi/spi-asr-dma.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for asr spi controller dma mode
+ *
+ * Copyright (C) 2019 ASR Micro Limited
+ *
+ * Tim Wang <timwang@asrmicro.com>
+ */
+
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/scatterlist.h>
+#include <linux/sizes.h>
+#include <linux/spi/spi.h>
+#include <linux/delay.h>
+
+#include "spi-asr.h"
+
+#define MAX_SEG_SIZE   SZ_4K
+
+static int asr_spi_map_dma_buffer(struct spi_driver_data *drv_data,
+				enum dma_data_direction dir)
+{
+	int i, nents, len = drv_data->len;
+	struct scatterlist *sg;
+	struct device *dmadev;
+	struct sg_table *sgt;
+	void *buf, *pbuf;
+	int desc_len;
+	size_t bytes;
+	struct page *vm_page;
+	bool vmalloced_buf = false;
+
+	if (dir == DMA_TO_DEVICE) {
+		dmadev = drv_data->tx_chan->device->dev;
+		sgt = &drv_data->tx_sgt;
+		buf = drv_data->tx;
+		drv_data->tx_map_len = len;
+	} else {
+		dmadev = drv_data->rx_chan->device->dev;
+		sgt = &drv_data->rx_sgt;
+		buf = drv_data->rx;
+		drv_data->rx_map_len = len;
+	}
+
+	desc_len = MAX_SEG_SIZE;
+	if (buf && is_vmalloc_addr(buf)) {
+		vmalloced_buf = true;
+		desc_len = min_t(unsigned long, MAX_SEG_SIZE, PAGE_SIZE);
+		nents = DIV_ROUND_UP(len + offset_in_page(buf), desc_len);
+	} else {
+		nents = DIV_ROUND_UP(len, desc_len);
+	}
+
+	if (nents != sgt->nents) {
+		int ret;
+
+		sg_free_table(sgt);
+		ret = sg_alloc_table(sgt, nents, GFP_ATOMIC);
+		if (ret)
+			return ret;
+	}
+
+	pbuf = buf;
+	for_each_sg(sgt->sgl, sg, sgt->nents, i) {
+
+		if (vmalloced_buf) {
+			bytes = min_t(size_t, desc_len,
+						min_t(size_t, len,
+						PAGE_SIZE - offset_in_page(pbuf)));
+			vm_page = vmalloc_to_page(pbuf);
+			if (!vm_page) {
+				return -ENOMEM;
+			}
+			sg_set_page(sg, vm_page,
+					bytes, offset_in_page(pbuf));
+		} else if (buf) {
+			bytes = min_t(size_t, len, desc_len);
+			sg_set_buf(sg, pbuf, bytes);
+		} else {
+			bytes = min_t(size_t, len, desc_len);
+			sg_set_buf(sg, drv_data->dummy, bytes);
+		}
+
+		pbuf += bytes;
+		len -= bytes;
+	}
+
+	nents = dma_map_sg(dmadev, sgt->sgl, sgt->nents, dir);
+	if (!nents)
+		return -ENOMEM;
+
+	return nents;
+}
+
+static void asr_spi_unmap_dma_buffer(struct spi_driver_data *drv_data,
+					enum dma_data_direction dir)
+{
+	struct device *dmadev;
+	struct sg_table *sgt;
+
+	if (dir == DMA_TO_DEVICE) {
+		dmadev = drv_data->tx_chan->device->dev;
+		sgt = &drv_data->tx_sgt;
+	} else {
+		dmadev = drv_data->rx_chan->device->dev;
+		sgt = &drv_data->rx_sgt;
+	}
+
+	dma_unmap_sg(dmadev, sgt->sgl, sgt->nents, dir);
+}
+
+static void asr_spi_unmap_dma_buffers(struct spi_driver_data *drv_data)
+{
+	if (!drv_data->dma_mapped)
+		return;
+
+	asr_spi_unmap_dma_buffer(drv_data, DMA_FROM_DEVICE);
+	asr_spi_unmap_dma_buffer(drv_data, DMA_TO_DEVICE);
+
+	drv_data->dma_mapped = 0;
+}
+
+static void asr_spi_dma_transfer_complete(struct spi_driver_data *drv_data,
+					     bool error)
+{
+	struct spi_message *msg = drv_data->cur_msg;
+
+	/*
+	 * It is possible that one CPU is handling ROR interrupt and other
+	 * just gets DMA completion. Calling pump_transfers() twice for the
+	 * same transfer leads to problems thus we prevent concurrent calls
+	 * by using ->dma_running.
+	 */
+	if (atomic_dec_and_test(&drv_data->dma_running)) {
+		/*
+		 * If the other CPU is still handling the ROR interrupt we
+		 * might not know about the error yet. So we re-check the
+		 * ROR bit here before we clear the status register.
+		 */
+		if (!error) {
+			u32 status = asr_spi_read(drv_data, STATUS)
+				     & drv_data->mask_sr;
+			error = status & STATUS_ROR;
+		}
+
+		/* Clear status & disable interrupts */
+		asr_spi_write(drv_data, FIFO_CTRL,
+				asr_spi_read(drv_data, FIFO_CTRL)
+				& ~drv_data->dma_fifo_ctrl);
+		asr_spi_write(drv_data, TOP_CTRL,
+				asr_spi_read(drv_data, TOP_CTRL)
+				& ~drv_data->dma_top_ctrl);
+		asr_spi_write(drv_data, STATUS, drv_data->clear_sr);
+		asr_spi_write(drv_data, TO, 0);
+
+		if (drv_data->xfer_way == XFER_SPIMEM) {
+			asr_spi_unmap_dma_buffers(drv_data);
+
+			complete(&drv_data->dma_completion);
+			return;
+		}
+
+		if (!error) {
+			asr_spi_unmap_dma_buffers(drv_data);
+
+			drv_data->tx += drv_data->tx_map_len;
+			drv_data->rx += drv_data->rx_map_len;
+
+			msg->actual_length += drv_data->len;
+			msg->state = asr_spi_next_transfer(drv_data);
+		} else {
+			/* In case we got an error we disable the SSP now */
+			asr_spi_write(drv_data, TOP_CTRL,
+					 asr_spi_read(drv_data, TOP_CTRL)
+					 & ~TOP_SSE);
+
+			msg->state = ERROR_STATE;
+		}
+		asr_spi_pump_transfers(drv_data);
+	}
+}
+
+static void asr_spi_dma_callback(void *data)
+{
+	asr_spi_dma_transfer_complete(data, false);
+}
+
+static struct dma_async_tx_descriptor *
+asr_spi_dma_prepare_one(struct spi_driver_data *drv_data,
+			   enum dma_transfer_direction dir)
+{
+	struct chip_data *chip = drv_data->cur_chip;
+	enum dma_slave_buswidth width;
+	struct dma_slave_config cfg;
+	struct dma_chan *chan;
+	struct sg_table *sgt;
+	int nents, ret;
+
+	switch (drv_data->n_bytes) {
+	case 1:
+		width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+		break;
+	case 2:
+		width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+		break;
+	default:
+		width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		break;
+	}
+
+	memset(&cfg, 0, sizeof(cfg));
+	cfg.direction = dir;
+
+	if (dir == DMA_MEM_TO_DEV) {
+		cfg.dst_addr = drv_data->ssdr_physical;
+		cfg.dst_addr_width = width;
+		cfg.dst_maxburst = chip->dma_burst_size;
+
+		sgt = &drv_data->tx_sgt;
+		nents = drv_data->tx_nents;
+		chan = drv_data->tx_chan;
+	} else {
+		cfg.src_addr = drv_data->ssdr_physical;
+		cfg.src_addr_width = width;
+		cfg.src_maxburst = chip->dma_burst_size;
+
+		sgt = &drv_data->rx_sgt;
+		nents = drv_data->rx_nents;
+		chan = drv_data->rx_chan;
+	}
+
+	ret = dmaengine_slave_config(chan, &cfg);
+	if (ret) {
+		dev_warn(&drv_data->pdev->dev, "DMA slave config failed\n");
+		return NULL;
+	}
+
+	return dmaengine_prep_slave_sg(chan, sgt->sgl, nents, dir,
+			DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+}
+
+bool asr_spi_dma_is_possible(size_t len)
+{
+	return len <= MAX_DMA_LEN;
+}
+
+int asr_spi_map_dma_buffers(struct spi_driver_data *drv_data)
+{
+	const struct chip_data *chip = drv_data->cur_chip;
+	int ret;
+
+	if (!chip->enable_dma)
+		return 0;
+
+	/* Don't bother with DMA if we can't do even a single burst */
+	if (drv_data->len < chip->dma_burst_size)
+		return 0;
+
+	ret = asr_spi_map_dma_buffer(drv_data, DMA_TO_DEVICE);
+	if (ret <= 0) {
+		dev_warn(&drv_data->pdev->dev, "failed to DMA map TX\n");
+		return 0;
+	}
+
+	drv_data->tx_nents = ret;
+
+	ret = asr_spi_map_dma_buffer(drv_data, DMA_FROM_DEVICE);
+	if (ret <= 0) {
+		asr_spi_unmap_dma_buffer(drv_data, DMA_TO_DEVICE);
+		dev_warn(&drv_data->pdev->dev, "failed to DMA map RX\n");
+		return 0;
+	}
+
+	drv_data->rx_nents = ret;
+	return 1;
+}
+
+irqreturn_t asr_spi_dma_transfer(struct spi_driver_data *drv_data)
+{
+	u32 status;
+
+	status = asr_spi_read(drv_data, STATUS) & drv_data->mask_sr;
+
+	if ( status & STATUS_ROR ) {
+		dmaengine_terminate_all(drv_data->rx_chan);
+		dmaengine_terminate_all(drv_data->tx_chan);
+		asr_spi_dma_transfer_complete(drv_data, true);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+void asr_spi_slave_sw_timeout_callback(struct spi_driver_data *drv_data)
+{
+	dmaengine_terminate_all(drv_data->rx_chan);
+	dmaengine_terminate_all(drv_data->tx_chan);
+	asr_spi_dma_transfer_complete(drv_data, true);
+}
+
+int asr_spi_dma_prepare(struct spi_driver_data *drv_data, u32 dma_burst)
+{
+	struct dma_async_tx_descriptor *tx_desc, *rx_desc;
+
+	tx_desc = asr_spi_dma_prepare_one(drv_data, DMA_MEM_TO_DEV);
+	if (!tx_desc) {
+		dev_err(&drv_data->pdev->dev,
+			"failed to get DMA TX descriptor\n");
+		return -EBUSY;
+	}
+
+	rx_desc = asr_spi_dma_prepare_one(drv_data, DMA_DEV_TO_MEM);
+	if (!rx_desc) {
+		dev_err(&drv_data->pdev->dev,
+			"failed to get DMA RX descriptor\n");
+		return -EBUSY;
+	}
+
+	/* We are ready when RX completes */
+	rx_desc->callback = asr_spi_dma_callback;
+	rx_desc->callback_param = drv_data;
+
+	dmaengine_submit(rx_desc);
+	dmaengine_submit(tx_desc);
+	return 0;
+}
+
+void asr_spi_dma_start(struct spi_driver_data *drv_data)
+{
+
+	if (drv_data->xfer_way == XFER_SPIMEM)
+		reinit_completion(&drv_data->dma_completion);
+
+	dma_async_issue_pending(drv_data->rx_chan);
+	dma_async_issue_pending(drv_data->tx_chan);
+
+	atomic_set(&drv_data->dma_running, 1);
+}
+
+int asr_spi_dma_setup(struct spi_driver_data *drv_data)
+{
+	struct asr_spi_master *pdata = drv_data->master_info;
+	struct device *dev = &drv_data->pdev->dev;
+	dma_cap_mask_t mask;
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	drv_data->dummy = devm_kzalloc(dev, MAX_SEG_SIZE, GFP_KERNEL);
+	if (!drv_data->dummy)
+		return -ENOMEM;
+
+	drv_data->tx_chan = dma_request_slave_channel_compat(mask,
+				pdata->dma_filter, pdata->tx_param, dev, "tx");
+	if (!drv_data->tx_chan)
+		return -ENODEV;
+
+	drv_data->rx_chan = dma_request_slave_channel_compat(mask,
+				pdata->dma_filter, pdata->rx_param, dev, "rx");
+	if (!drv_data->rx_chan) {
+		dma_release_channel(drv_data->tx_chan);
+		drv_data->tx_chan = NULL;
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+void asr_spi_dma_release(struct spi_driver_data *drv_data)
+{
+	if (drv_data->rx_chan) {
+		dmaengine_terminate_all(drv_data->rx_chan);
+		dma_release_channel(drv_data->rx_chan);
+		sg_free_table(&drv_data->rx_sgt);
+		drv_data->rx_chan = NULL;
+	}
+	if (drv_data->tx_chan) {
+		dmaengine_terminate_all(drv_data->tx_chan);
+		dma_release_channel(drv_data->tx_chan);
+		sg_free_table(&drv_data->tx_sgt);
+		drv_data->tx_chan = NULL;
+	}
+}
+
+int asr_spi_set_dma_burst_and_threshold(struct chip_data *chip,
+					   struct spi_device *spi,
+					   u8 bits_per_word, u32 *burst_code,
+					   u32 *threshold)
+{
+	/*
+	 * If the DMA burst size is given in chip_info we use
+	 * that, otherwise we set it to half of FIFO size; SPI
+	 * FIFO has 16 entry, so FIFO size = 16*bits_per_word/8;
+	 * Also we use the default FIFO thresholds for now.
+	 */
+	if (chip && chip->dma_burst_size)
+		*burst_code = chip->dma_burst_size;
+	else if (bits_per_word <= 8) {
+		*burst_code = 8;
+	}
+	else if (bits_per_word <= 16)
+		*burst_code = 16;
+	else
+		*burst_code = 32;
+
+	*threshold = FIFO_RxTresh(RX_THRESH_DFLT)
+		   | FIFO_TxTresh(TX_THRESH_DFLT);
+
+	return 0;
+}