|
@@ -21,6 +21,8 @@
|
|
|
#include <linux/clk.h>
|
|
|
#include <linux/completion.h>
|
|
|
#include <linux/delay.h>
|
|
|
+#include <linux/dmaengine.h>
|
|
|
+#include <linux/dma-mapping.h>
|
|
|
#include <linux/err.h>
|
|
|
#include <linux/gpio.h>
|
|
|
#include <linux/interrupt.h>
|
|
@@ -37,6 +39,7 @@
|
|
|
#include <linux/of_device.h>
|
|
|
#include <linux/of_gpio.h>
|
|
|
|
|
|
+#include <linux/platform_data/dma-imx.h>
|
|
|
#include <linux/platform_data/spi-imx.h>
|
|
|
|
|
|
#define DRIVER_NAME "spi_imx"
|
|
@@ -51,6 +54,9 @@
|
|
|
#define MXC_INT_RR (1 << 0) /* Receive data ready interrupt */
|
|
|
#define MXC_INT_TE (1 << 1) /* Transmit FIFO empty interrupt */
|
|
|
|
|
|
+/* The maximum bytes that a sdma BD can transfer.*/
|
|
|
+#define MAX_SDMA_BD_BYTES (1 << 15)
|
|
|
+#define IMX_DMA_TIMEOUT (msecs_to_jiffies(3000))
|
|
|
struct spi_imx_config {
|
|
|
unsigned int speed_hz;
|
|
|
unsigned int bpw;
|
|
@@ -95,6 +101,16 @@ struct spi_imx_data {
|
|
|
const void *tx_buf;
|
|
|
unsigned int txfifo; /* number of words pushed in tx FIFO */
|
|
|
|
|
|
+ /* DMA */
|
|
|
+ unsigned int dma_is_inited;
|
|
|
+ unsigned int dma_finished;
|
|
|
+ bool usedma;
|
|
|
+ u32 rx_wml;
|
|
|
+ u32 tx_wml;
|
|
|
+ u32 rxt_wml;
|
|
|
+ struct completion dma_rx_completion;
|
|
|
+ struct completion dma_tx_completion;
|
|
|
+
|
|
|
const struct spi_imx_devtype_data *devtype_data;
|
|
|
int chipselect[0];
|
|
|
};
|
|
@@ -181,9 +197,21 @@ static unsigned int spi_imx_clkdiv_2(unsigned int fin,
|
|
|
return 7;
|
|
|
}
|
|
|
|
|
|
+static bool spi_imx_can_dma(struct spi_master *master, struct spi_device *spi,
|
|
|
+ struct spi_transfer *transfer)
|
|
|
+{
|
|
|
+ struct spi_imx_data *spi_imx = spi_master_get_devdata(master);
|
|
|
+
|
|
|
+ if (spi_imx->dma_is_inited && (transfer->len > spi_imx->rx_wml)
|
|
|
+ && (transfer->len > spi_imx->tx_wml))
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
#define MX51_ECSPI_CTRL 0x08
|
|
|
#define MX51_ECSPI_CTRL_ENABLE (1 << 0)
|
|
|
#define MX51_ECSPI_CTRL_XCH (1 << 2)
|
|
|
+#define MX51_ECSPI_CTRL_SMC (1 << 3)
|
|
|
#define MX51_ECSPI_CTRL_MODE_MASK (0xf << 4)
|
|
|
#define MX51_ECSPI_CTRL_POSTDIV_OFFSET 8
|
|
|
#define MX51_ECSPI_CTRL_PREDIV_OFFSET 12
|
|
@@ -201,6 +229,18 @@ static unsigned int spi_imx_clkdiv_2(unsigned int fin,
|
|
|
#define MX51_ECSPI_INT_TEEN (1 << 0)
|
|
|
#define MX51_ECSPI_INT_RREN (1 << 3)
|
|
|
|
|
|
+#define MX51_ECSPI_DMA 0x14
|
|
|
+#define MX51_ECSPI_DMA_TX_WML_OFFSET 0
|
|
|
+#define MX51_ECSPI_DMA_TX_WML_MASK 0x3F
|
|
|
+#define MX51_ECSPI_DMA_RX_WML_OFFSET 16
|
|
|
+#define MX51_ECSPI_DMA_RX_WML_MASK (0x3F << 16)
|
|
|
+#define MX51_ECSPI_DMA_RXT_WML_OFFSET 24
|
|
|
+#define MX51_ECSPI_DMA_RXT_WML_MASK (0x3F << 24)
|
|
|
+
|
|
|
+#define MX51_ECSPI_DMA_TEDEN_OFFSET 7
|
|
|
+#define MX51_ECSPI_DMA_RXDEN_OFFSET 23
|
|
|
+#define MX51_ECSPI_DMA_RXTDEN_OFFSET 31
|
|
|
+
|
|
|
#define MX51_ECSPI_STAT 0x18
|
|
|
#define MX51_ECSPI_STAT_RR (1 << 3)
|
|
|
|
|
@@ -257,17 +297,22 @@ static void __maybe_unused mx51_ecspi_intctrl(struct spi_imx_data *spi_imx, int
|
|
|
|
|
|
static void __maybe_unused mx51_ecspi_trigger(struct spi_imx_data *spi_imx)
|
|
|
{
|
|
|
- u32 reg;
|
|
|
-
|
|
|
- reg = readl(spi_imx->base + MX51_ECSPI_CTRL);
|
|
|
- reg |= MX51_ECSPI_CTRL_XCH;
|
|
|
+ u32 reg = readl(spi_imx->base + MX51_ECSPI_CTRL);
|
|
|
+
|
|
|
+ if (!spi_imx->usedma)
|
|
|
+ reg |= MX51_ECSPI_CTRL_XCH;
|
|
|
+ else if (!spi_imx->dma_finished)
|
|
|
+ reg |= MX51_ECSPI_CTRL_SMC;
|
|
|
+ else
|
|
|
+ reg &= ~MX51_ECSPI_CTRL_SMC;
|
|
|
writel(reg, spi_imx->base + MX51_ECSPI_CTRL);
|
|
|
}
|
|
|
|
|
|
static int __maybe_unused mx51_ecspi_config(struct spi_imx_data *spi_imx,
|
|
|
struct spi_imx_config *config)
|
|
|
{
|
|
|
- u32 ctrl = MX51_ECSPI_CTRL_ENABLE, cfg = 0;
|
|
|
+ u32 ctrl = MX51_ECSPI_CTRL_ENABLE, cfg = 0, dma = 0;
|
|
|
+ u32 tx_wml_cfg, rx_wml_cfg, rxt_wml_cfg;
|
|
|
u32 clk = config->speed_hz, delay;
|
|
|
|
|
|
/*
|
|
@@ -319,6 +364,30 @@ static int __maybe_unused mx51_ecspi_config(struct spi_imx_data *spi_imx,
|
|
|
else /* SCLK is _very_ slow */
|
|
|
usleep_range(delay, delay + 10);
|
|
|
|
|
|
+ /*
|
|
|
+ * Configure the DMA register: setup the watermark
|
|
|
+ * and enable DMA request.
|
|
|
+ */
|
|
|
+ if (spi_imx->dma_is_inited) {
|
|
|
+ dma = readl(spi_imx->base + MX51_ECSPI_DMA);
|
|
|
+
|
|
|
+ spi_imx->tx_wml = spi_imx_get_fifosize(spi_imx) / 2;
|
|
|
+ spi_imx->rx_wml = spi_imx_get_fifosize(spi_imx) / 2;
|
|
|
+ spi_imx->rxt_wml = spi_imx_get_fifosize(spi_imx) / 2;
|
|
|
+ rx_wml_cfg = spi_imx->rx_wml << MX51_ECSPI_DMA_RX_WML_OFFSET;
|
|
|
+ tx_wml_cfg = spi_imx->tx_wml << MX51_ECSPI_DMA_TX_WML_OFFSET;
|
|
|
+ rxt_wml_cfg = spi_imx->rxt_wml << MX51_ECSPI_DMA_RXT_WML_OFFSET;
|
|
|
+ dma = (dma & ~MX51_ECSPI_DMA_TX_WML_MASK
|
|
|
+ & ~MX51_ECSPI_DMA_RX_WML_MASK
|
|
|
+ & ~MX51_ECSPI_DMA_RXT_WML_MASK)
|
|
|
+ | rx_wml_cfg | tx_wml_cfg | rxt_wml_cfg
|
|
|
+ |(1 << MX51_ECSPI_DMA_TEDEN_OFFSET)
|
|
|
+ |(1 << MX51_ECSPI_DMA_RXDEN_OFFSET)
|
|
|
+ |(1 << MX51_ECSPI_DMA_RXTDEN_OFFSET);
|
|
|
+
|
|
|
+ writel(dma, spi_imx->base + MX51_ECSPI_DMA);
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -730,7 +799,186 @@ static int spi_imx_setupxfer(struct spi_device *spi,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static int spi_imx_transfer(struct spi_device *spi,
|
|
|
+static void spi_imx_sdma_exit(struct spi_imx_data *spi_imx)
|
|
|
+{
|
|
|
+ struct spi_master *master = spi_imx->bitbang.master;
|
|
|
+
|
|
|
+ if (master->dma_rx) {
|
|
|
+ dma_release_channel(master->dma_rx);
|
|
|
+ master->dma_rx = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (master->dma_tx) {
|
|
|
+ dma_release_channel(master->dma_tx);
|
|
|
+ master->dma_tx = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ spi_imx->dma_is_inited = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int spi_imx_sdma_init(struct device *dev, struct spi_imx_data *spi_imx,
|
|
|
+ struct spi_master *master,
|
|
|
+ const struct resource *res)
|
|
|
+{
|
|
|
+ struct dma_slave_config slave_config = {};
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Prepare for TX DMA: */
|
|
|
+ master->dma_tx = dma_request_slave_channel(dev, "tx");
|
|
|
+ if (!master->dma_tx) {
|
|
|
+ dev_err(dev, "cannot get the TX DMA channel!\n");
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ slave_config.direction = DMA_MEM_TO_DEV;
|
|
|
+ slave_config.dst_addr = res->start + MXC_CSPITXDATA;
|
|
|
+ slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
|
|
+ slave_config.dst_maxburst = spi_imx_get_fifosize(spi_imx) / 2;
|
|
|
+ ret = dmaengine_slave_config(master->dma_tx, &slave_config);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(dev, "error in TX dma configuration.\n");
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Prepare for RX : */
|
|
|
+ master->dma_rx = dma_request_slave_channel(dev, "rx");
|
|
|
+ if (!master->dma_rx) {
|
|
|
+ dev_dbg(dev, "cannot get the DMA channel.\n");
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ slave_config.direction = DMA_DEV_TO_MEM;
|
|
|
+ slave_config.src_addr = res->start + MXC_CSPIRXDATA;
|
|
|
+ slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
|
|
+ slave_config.src_maxburst = spi_imx_get_fifosize(spi_imx) / 2;
|
|
|
+ ret = dmaengine_slave_config(master->dma_rx, &slave_config);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(dev, "error in RX dma configuration.\n");
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ init_completion(&spi_imx->dma_rx_completion);
|
|
|
+ init_completion(&spi_imx->dma_tx_completion);
|
|
|
+ master->can_dma = spi_imx_can_dma;
|
|
|
+ master->max_dma_len = MAX_SDMA_BD_BYTES;
|
|
|
+ spi_imx->bitbang.master->flags = SPI_MASTER_MUST_RX |
|
|
|
+ SPI_MASTER_MUST_TX;
|
|
|
+ spi_imx->dma_is_inited = 1;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+err:
|
|
|
+ spi_imx_sdma_exit(spi_imx);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void spi_imx_dma_rx_callback(void *cookie)
|
|
|
+{
|
|
|
+ struct spi_imx_data *spi_imx = (struct spi_imx_data *)cookie;
|
|
|
+
|
|
|
+ complete(&spi_imx->dma_rx_completion);
|
|
|
+}
|
|
|
+
|
|
|
+static void spi_imx_dma_tx_callback(void *cookie)
|
|
|
+{
|
|
|
+ struct spi_imx_data *spi_imx = (struct spi_imx_data *)cookie;
|
|
|
+
|
|
|
+ complete(&spi_imx->dma_tx_completion);
|
|
|
+}
|
|
|
+
|
|
|
+static int spi_imx_dma_transfer(struct spi_imx_data *spi_imx,
|
|
|
+ struct spi_transfer *transfer)
|
|
|
+{
|
|
|
+ struct dma_async_tx_descriptor *desc_tx = NULL, *desc_rx = NULL;
|
|
|
+ int ret;
|
|
|
+ u32 dma;
|
|
|
+ int left;
|
|
|
+ struct spi_master *master = spi_imx->bitbang.master;
|
|
|
+ struct sg_table *tx = &transfer->tx_sg, *rx = &transfer->rx_sg;
|
|
|
+
|
|
|
+ if (tx) {
|
|
|
+ desc_tx = dmaengine_prep_slave_sg(master->dma_tx,
|
|
|
+ tx->sgl, tx->nents, DMA_TO_DEVICE,
|
|
|
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
|
|
+ if (!desc_tx)
|
|
|
+ goto no_dma;
|
|
|
+
|
|
|
+ desc_tx->callback = spi_imx_dma_tx_callback;
|
|
|
+ desc_tx->callback_param = (void *)spi_imx;
|
|
|
+ dmaengine_submit(desc_tx);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rx) {
|
|
|
+ desc_rx = dmaengine_prep_slave_sg(master->dma_rx,
|
|
|
+ rx->sgl, rx->nents, DMA_FROM_DEVICE,
|
|
|
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
|
|
+ if (!desc_rx)
|
|
|
+ goto no_dma;
|
|
|
+
|
|
|
+ desc_rx->callback = spi_imx_dma_rx_callback;
|
|
|
+ desc_rx->callback_param = (void *)spi_imx;
|
|
|
+ dmaengine_submit(desc_rx);
|
|
|
+ }
|
|
|
+
|
|
|
+ reinit_completion(&spi_imx->dma_rx_completion);
|
|
|
+ reinit_completion(&spi_imx->dma_tx_completion);
|
|
|
+
|
|
|
+ /* Trigger the cspi module. */
|
|
|
+ spi_imx->dma_finished = 0;
|
|
|
+
|
|
|
+ dma = readl(spi_imx->base + MX51_ECSPI_DMA);
|
|
|
+ dma = dma & (~MX51_ECSPI_DMA_RXT_WML_MASK);
|
|
|
+ /* Change RX_DMA_LENGTH trigger dma fetch tail data */
|
|
|
+ left = transfer->len % spi_imx->rxt_wml;
|
|
|
+ if (left)
|
|
|
+ writel(dma | (left << MX51_ECSPI_DMA_RXT_WML_OFFSET),
|
|
|
+ spi_imx->base + MX51_ECSPI_DMA);
|
|
|
+ spi_imx->devtype_data->trigger(spi_imx);
|
|
|
+
|
|
|
+ dma_async_issue_pending(master->dma_tx);
|
|
|
+ dma_async_issue_pending(master->dma_rx);
|
|
|
+ /* Wait SDMA to finish the data transfer.*/
|
|
|
+ ret = wait_for_completion_timeout(&spi_imx->dma_tx_completion,
|
|
|
+ IMX_DMA_TIMEOUT);
|
|
|
+ if (!ret) {
|
|
|
+ pr_warn("%s %s: I/O Error in DMA TX\n",
|
|
|
+ dev_driver_string(&master->dev),
|
|
|
+ dev_name(&master->dev));
|
|
|
+ dmaengine_terminate_all(master->dma_tx);
|
|
|
+ } else {
|
|
|
+ ret = wait_for_completion_timeout(&spi_imx->dma_rx_completion,
|
|
|
+ IMX_DMA_TIMEOUT);
|
|
|
+ if (!ret) {
|
|
|
+ pr_warn("%s %s: I/O Error in DMA RX\n",
|
|
|
+ dev_driver_string(&master->dev),
|
|
|
+ dev_name(&master->dev));
|
|
|
+ spi_imx->devtype_data->reset(spi_imx);
|
|
|
+ dmaengine_terminate_all(master->dma_rx);
|
|
|
+ }
|
|
|
+ writel(dma |
|
|
|
+ spi_imx->rxt_wml << MX51_ECSPI_DMA_RXT_WML_OFFSET,
|
|
|
+ spi_imx->base + MX51_ECSPI_DMA);
|
|
|
+ }
|
|
|
+
|
|
|
+ spi_imx->dma_finished = 1;
|
|
|
+ spi_imx->devtype_data->trigger(spi_imx);
|
|
|
+
|
|
|
+ if (!ret)
|
|
|
+ ret = -ETIMEDOUT;
|
|
|
+ else if (ret > 0)
|
|
|
+ ret = transfer->len;
|
|
|
+
|
|
|
+ return ret;
|
|
|
+
|
|
|
+no_dma:
|
|
|
+ pr_warn_once("%s %s: DMA not available, falling back to PIO\n",
|
|
|
+ dev_driver_string(&master->dev),
|
|
|
+ dev_name(&master->dev));
|
|
|
+ return -EAGAIN;
|
|
|
+}
|
|
|
+
|
|
|
+static int spi_imx_pio_transfer(struct spi_device *spi,
|
|
|
struct spi_transfer *transfer)
|
|
|
{
|
|
|
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
|
@@ -751,6 +999,24 @@ static int spi_imx_transfer(struct spi_device *spi,
|
|
|
return transfer->len;
|
|
|
}
|
|
|
|
|
|
+static int spi_imx_transfer(struct spi_device *spi,
|
|
|
+ struct spi_transfer *transfer)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
|
|
+
|
|
|
+ if (spi_imx->bitbang.master->can_dma &&
|
|
|
+ spi_imx_can_dma(spi_imx->bitbang.master, spi, transfer)) {
|
|
|
+ spi_imx->usedma = true;
|
|
|
+ ret = spi_imx_dma_transfer(spi_imx, transfer);
|
|
|
+ if (ret != -EAGAIN)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ spi_imx->usedma = false;
|
|
|
+
|
|
|
+ return spi_imx_pio_transfer(spi, transfer);
|
|
|
+}
|
|
|
+
|
|
|
static int spi_imx_setup(struct spi_device *spi)
|
|
|
{
|
|
|
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
|
|
@@ -911,6 +1177,13 @@ static int spi_imx_probe(struct platform_device *pdev)
|
|
|
goto out_put_per;
|
|
|
|
|
|
spi_imx->spi_clk = clk_get_rate(spi_imx->clk_per);
|
|
|
+ /*
|
|
|
+ * Only validated on i.mx6 now, can remove the constrain if validated on
|
|
|
+ * other chips.
|
|
|
+ */
|
|
|
+ if (spi_imx->devtype_data == &imx51_ecspi_devtype_data
|
|
|
+ && spi_imx_sdma_init(&pdev->dev, spi_imx, master, res))
|
|
|
+ dev_err(&pdev->dev, "dma setup error,use pio instead\n");
|
|
|
|
|
|
spi_imx->devtype_data->reset(spi_imx);
|
|
|
|
|
@@ -949,6 +1222,7 @@ static int spi_imx_remove(struct platform_device *pdev)
|
|
|
writel(0, spi_imx->base + MXC_CSPICTRL);
|
|
|
clk_unprepare(spi_imx->clk_ipg);
|
|
|
clk_unprepare(spi_imx->clk_per);
|
|
|
+ spi_imx_sdma_exit(spi_imx);
|
|
|
spi_master_put(master);
|
|
|
|
|
|
return 0;
|