mirror of
https://github.com/FunKey-Project/FunKey-OS.git
synced 2025-12-12 15:48:51 +01:00
patchwork for DMA in spi-sun6i driver
This commit is contained in:
parent
fe44a5e298
commit
63dc1cd7c2
@ -0,0 +1,173 @@
|
||||
diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
|
||||
index 8533f4e..88ad45e 100644
|
||||
--- a/drivers/spi/spi-sun6i.c
|
||||
+++ b/drivers/spi/spi-sun6i.c
|
||||
@@ -88,8 +88,12 @@
|
||||
#define SUN6I_TXDATA_REG 0x200
|
||||
#define SUN6I_RXDATA_REG 0x300
|
||||
|
||||
+#define SUN6I_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST)
|
||||
+
|
||||
+#define SUN6I_SPI_MAX_SPEED_HZ 100000000
|
||||
+#define SUN6I_SPI_MIN_SPEED_HZ 3000
|
||||
+
|
||||
struct sun6i_spi {
|
||||
- struct spi_master *master;
|
||||
void __iomem *base_addr;
|
||||
struct clk *hclk;
|
||||
struct clk *mclk;
|
||||
@@ -189,6 +193,9 @@ static void sun6i_spi_set_cs(struct spi_device *spi, bool enable)
|
||||
else
|
||||
reg &= ~SUN6I_TFR_CTL_CS_LEVEL;
|
||||
|
||||
+ /* We want to control the chip select manually */
|
||||
+ reg |= SUN6I_TFR_CTL_CS_MANUAL;
|
||||
+
|
||||
sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
|
||||
}
|
||||
|
||||
@@ -197,6 +204,39 @@ static size_t sun6i_spi_max_transfer_size(struct spi_device *spi)
|
||||
return SUN6I_MAX_XFER_SIZE - 1;
|
||||
}
|
||||
|
||||
+static int sun6i_spi_prepare_message(struct spi_master *master,
|
||||
+ struct spi_message *msg)
|
||||
+{
|
||||
+ struct spi_device *spi = msg->spi;
|
||||
+ struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||
+ u32 reg;
|
||||
+
|
||||
+ /*
|
||||
+ * Setup the transfer control register: Chip Select,
|
||||
+ * polarities, etc.
|
||||
+ */
|
||||
+ reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||
+
|
||||
+ if (spi->mode & SPI_CPOL)
|
||||
+ reg |= SUN6I_TFR_CTL_CPOL;
|
||||
+ else
|
||||
+ reg &= ~SUN6I_TFR_CTL_CPOL;
|
||||
+
|
||||
+ if (spi->mode & SPI_CPHA)
|
||||
+ reg |= SUN6I_TFR_CTL_CPHA;
|
||||
+ else
|
||||
+ reg &= ~SUN6I_TFR_CTL_CPHA;
|
||||
+
|
||||
+ if (spi->mode & SPI_LSB_FIRST)
|
||||
+ reg |= SUN6I_TFR_CTL_FBS;
|
||||
+ else
|
||||
+ reg &= ~SUN6I_TFR_CTL_FBS;
|
||||
+
|
||||
+ sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *tfr)
|
||||
@@ -235,27 +275,8 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
(trig_level << SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_BITS) |
|
||||
(trig_level << SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_BITS));
|
||||
|
||||
- /*
|
||||
- * Setup the transfer control register: Chip Select,
|
||||
- * polarities, etc.
|
||||
- */
|
||||
- reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||
-
|
||||
- if (spi->mode & SPI_CPOL)
|
||||
- reg |= SUN6I_TFR_CTL_CPOL;
|
||||
- else
|
||||
- reg &= ~SUN6I_TFR_CTL_CPOL;
|
||||
-
|
||||
- if (spi->mode & SPI_CPHA)
|
||||
- reg |= SUN6I_TFR_CTL_CPHA;
|
||||
- else
|
||||
- reg &= ~SUN6I_TFR_CTL_CPHA;
|
||||
-
|
||||
- if (spi->mode & SPI_LSB_FIRST)
|
||||
- reg |= SUN6I_TFR_CTL_FBS;
|
||||
- else
|
||||
- reg &= ~SUN6I_TFR_CTL_FBS;
|
||||
|
||||
+ reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||
/*
|
||||
* If it's a TX only transfer, we don't want to fill the RX
|
||||
* FIFO with bogus data
|
||||
@@ -265,11 +286,9 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
else
|
||||
reg |= SUN6I_TFR_CTL_DHB;
|
||||
|
||||
- /* We want to control the chip select manually */
|
||||
- reg |= SUN6I_TFR_CTL_CS_MANUAL;
|
||||
-
|
||||
sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
|
||||
|
||||
+
|
||||
/* Ensure that we have a parent clock fast enough */
|
||||
mclk_rate = clk_get_rate(sspi->mclk);
|
||||
if (mclk_rate < (2 * tfr->speed_hz)) {
|
||||
@@ -442,12 +461,24 @@ static int sun6i_spi_probe(struct platform_device *pdev)
|
||||
struct resource *res;
|
||||
int ret = 0, irq;
|
||||
|
||||
- master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi));
|
||||
+ master = spi_alloc_master(&pdev->dev, sizeof(*sspi));
|
||||
if (!master) {
|
||||
dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
+ master->max_speed_hz = SUN6I_SPI_MAX_SPEED_HZ;
|
||||
+ master->min_speed_hz = SUN6I_SPI_MIN_SPEED_HZ;
|
||||
+ master->num_chipselect = 4;
|
||||
+ master->mode_bits = SUN6I_SPI_MODE_BITS;
|
||||
+ master->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||
+ master->set_cs = sun6i_spi_set_cs;
|
||||
+ master->prepare_message = sun6i_spi_prepare_message;
|
||||
+ master->transfer_one = sun6i_spi_transfer_one;
|
||||
+ master->max_transfer_size = sun6i_spi_max_transfer_size;
|
||||
+ master->dev.of_node = pdev->dev.of_node;
|
||||
+ master->auto_runtime_pm = true;
|
||||
+
|
||||
platform_set_drvdata(pdev, master);
|
||||
sspi = spi_master_get_devdata(master);
|
||||
|
||||
@@ -466,26 +497,14 @@ static int sun6i_spi_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, sun6i_spi_handler,
|
||||
- 0, "sun6i-spi", sspi);
|
||||
+ 0, dev_name(&pdev->dev), sspi);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Cannot request IRQ\n");
|
||||
goto err_free_master;
|
||||
}
|
||||
|
||||
- sspi->master = master;
|
||||
sspi->fifo_depth = (unsigned long)of_device_get_match_data(&pdev->dev);
|
||||
|
||||
- master->max_speed_hz = 100 * 1000 * 1000;
|
||||
- master->min_speed_hz = 3 * 1000;
|
||||
- master->set_cs = sun6i_spi_set_cs;
|
||||
- master->transfer_one = sun6i_spi_transfer_one;
|
||||
- master->num_chipselect = 4;
|
||||
- master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST;
|
||||
- master->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||
- master->dev.of_node = pdev->dev.of_node;
|
||||
- master->auto_runtime_pm = true;
|
||||
- master->max_transfer_size = sun6i_spi_max_transfer_size;
|
||||
-
|
||||
sspi->hclk = devm_clk_get(&pdev->dev, "ahb");
|
||||
if (IS_ERR(sspi->hclk)) {
|
||||
dev_err(&pdev->dev, "Unable to acquire AHB clock\n");
|
||||
@@ -525,7 +544,7 @@ static int sun6i_spi_probe(struct platform_device *pdev)
|
||||
|
||||
ret = devm_spi_register_master(&pdev->dev, master);
|
||||
if (ret) {
|
||||
- dev_err(&pdev->dev, "cannot register SPI master\n");
|
||||
+ dev_err(&pdev->dev, "Couldn't register SPI master\n");
|
||||
goto err_pm_disable;
|
||||
}
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
|
||||
index 88ad45e..78acc1f 100644
|
||||
--- a/drivers/spi/spi-sun6i.c
|
||||
+++ b/drivers/spi/spi-sun6i.c
|
||||
@@ -193,6 +193,12 @@ static void sun6i_spi_set_cs(struct spi_device *spi, bool enable)
|
||||
else
|
||||
reg &= ~SUN6I_TFR_CTL_CS_LEVEL;
|
||||
|
||||
+ /* Handle chip select "reverse" polarity */
|
||||
+ if (spi->mode & SPI_CS_HIGH)
|
||||
+ reg &= ~SUN6I_TFR_CTL_SPOL;
|
||||
+ else
|
||||
+ reg |= SUN6I_TFR_CTL_SPOL;
|
||||
+
|
||||
/* We want to control the chip select manually */
|
||||
reg |= SUN6I_TFR_CTL_CS_MANUAL;
|
||||
|
||||
@ -0,0 +1,111 @@
|
||||
diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
|
||||
index 78acc1f..4db1f20 100644
|
||||
--- a/drivers/spi/spi-sun6i.c
|
||||
+++ b/drivers/spi/spi-sun6i.c
|
||||
@@ -207,7 +207,10 @@ static void sun6i_spi_set_cs(struct spi_device *spi, bool enable)
|
||||
|
||||
static size_t sun6i_spi_max_transfer_size(struct spi_device *spi)
|
||||
{
|
||||
- return SUN6I_MAX_XFER_SIZE - 1;
|
||||
+ struct spi_master *master = spi->master;
|
||||
+ struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||
+
|
||||
+ return sspi->fifo_depth;
|
||||
}
|
||||
|
||||
static int sun6i_spi_prepare_message(struct spi_master *master,
|
||||
@@ -250,13 +253,18 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||
unsigned int mclk_rate, div, timeout;
|
||||
unsigned int start, end, tx_time;
|
||||
- unsigned int trig_level;
|
||||
unsigned int tx_len = 0;
|
||||
int ret = 0;
|
||||
u32 reg;
|
||||
|
||||
- if (tfr->len > SUN6I_MAX_XFER_SIZE)
|
||||
- return -EINVAL;
|
||||
+ /* A zero length transfer never finishes if programmed
|
||||
+ in the hardware */
|
||||
+ if (!tfr->len)
|
||||
+ return 0;
|
||||
+
|
||||
+ /* Don't support transfer larger than the FIFO */
|
||||
+ if (tfr->len > sspi->fifo_depth)
|
||||
+ return -EMSGSIZE;
|
||||
|
||||
reinit_completion(&sspi->done);
|
||||
sspi->tx_buf = tfr->tx_buf;
|
||||
@@ -270,17 +278,6 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG,
|
||||
SUN6I_FIFO_CTL_RF_RST | SUN6I_FIFO_CTL_TF_RST);
|
||||
|
||||
- /*
|
||||
- * Setup FIFO interrupt trigger level
|
||||
- * Here we choose 3/4 of the full fifo depth, as it's the hardcoded
|
||||
- * value used in old generation of Allwinner SPI controller.
|
||||
- * (See spi-sun4i.c)
|
||||
- */
|
||||
- trig_level = sspi->fifo_depth / 4 * 3;
|
||||
- sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG,
|
||||
- (trig_level << SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_BITS) |
|
||||
- (trig_level << SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_BITS));
|
||||
-
|
||||
|
||||
reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||
/*
|
||||
@@ -342,12 +339,8 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
/* Fill the TX FIFO */
|
||||
sun6i_spi_fill_fifo(sspi, sspi->fifo_depth);
|
||||
|
||||
- /* Enable the interrupts */
|
||||
- sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, SUN6I_INT_CTL_TC);
|
||||
- sun6i_spi_enable_interrupt(sspi, SUN6I_INT_CTL_TC |
|
||||
- SUN6I_INT_CTL_RF_RDY);
|
||||
- if (tx_len > sspi->fifo_depth)
|
||||
- sun6i_spi_enable_interrupt(sspi, SUN6I_INT_CTL_TF_ERQ);
|
||||
+ /* Enable transfer complete interrupt */
|
||||
+ sun6i_spi_enable_interrupt(sspi, SUN6I_INT_CTL_TC);
|
||||
|
||||
/* Start the transfer */
|
||||
reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||
@@ -376,7 +369,9 @@ out:
|
||||
static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct sun6i_spi *sspi = dev_id;
|
||||
- u32 status = sun6i_spi_read(sspi, SUN6I_INT_STA_REG);
|
||||
+ u32 status;
|
||||
+
|
||||
+ status = sun6i_spi_read(sspi, SUN6I_INT_STA_REG);
|
||||
|
||||
/* Transfer complete */
|
||||
if (status & SUN6I_INT_CTL_TC) {
|
||||
@@ -386,28 +381,6 @@ static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
- /* Receive FIFO 3/4 full */
|
||||
- if (status & SUN6I_INT_CTL_RF_RDY) {
|
||||
- sun6i_spi_drain_fifo(sspi, SUN6I_FIFO_DEPTH);
|
||||
- /* Only clear the interrupt _after_ draining the FIFO */
|
||||
- sun6i_spi_write(sspi, SUN6I_INT_STA_REG, SUN6I_INT_CTL_RF_RDY);
|
||||
- return IRQ_HANDLED;
|
||||
- }
|
||||
-
|
||||
- /* Transmit FIFO 3/4 empty */
|
||||
- if (status & SUN6I_INT_CTL_TF_ERQ) {
|
||||
- sun6i_spi_fill_fifo(sspi, SUN6I_FIFO_DEPTH);
|
||||
-
|
||||
- if (!sspi->len)
|
||||
- /* nothing left to transmit */
|
||||
- sun6i_spi_disable_interrupt(sspi, SUN6I_INT_CTL_TF_ERQ);
|
||||
-
|
||||
- /* Only clear the interrupt _after_ re-seeding the FIFO */
|
||||
- sun6i_spi_write(sspi, SUN6I_INT_STA_REG, SUN6I_INT_CTL_TF_ERQ);
|
||||
-
|
||||
- return IRQ_HANDLED;
|
||||
- }
|
||||
-
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
|
||||
index 4db1f20..210cef9 100644
|
||||
--- a/drivers/spi/spi-sun6i.c
|
||||
+++ b/drivers/spi/spi-sun6i.c
|
||||
@@ -99,8 +99,6 @@ struct sun6i_spi {
|
||||
struct clk *mclk;
|
||||
struct reset_control *rstc;
|
||||
|
||||
- struct completion done;
|
||||
-
|
||||
const u8 *tx_buf;
|
||||
u8 *rx_buf;
|
||||
int len;
|
||||
@@ -246,6 +244,30 @@ static int sun6i_spi_prepare_message(struct spi_master *master,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int sun6i_spi_wait_for_transfer(struct spi_device *spi,
|
||||
+ struct spi_transfer *tfr)
|
||||
+{
|
||||
+ struct spi_master *master = spi->master;
|
||||
+ unsigned int start, end, tx_time;
|
||||
+ unsigned int timeout;
|
||||
+
|
||||
+ /* smart wait for completion */
|
||||
+ tx_time = max(tfr->len * 8 * 2 / (tfr->speed_hz / 1000), 100U);
|
||||
+ start = jiffies;
|
||||
+ timeout = wait_for_completion_timeout(&master->xfer_completion,
|
||||
+ msecs_to_jiffies(tx_time));
|
||||
+ end = jiffies;
|
||||
+ if (!timeout) {
|
||||
+ dev_warn(&master->dev,
|
||||
+ "%s: timeout transferring %u bytes@%iHz for %i(%i)ms",
|
||||
+ dev_name(&spi->dev), tfr->len, tfr->speed_hz,
|
||||
+ jiffies_to_msecs(end - start), tx_time);
|
||||
+ return -ETIMEDOUT;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *tfr)
|
||||
@@ -266,7 +288,6 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
if (tfr->len > sspi->fifo_depth)
|
||||
return -EMSGSIZE;
|
||||
|
||||
- reinit_completion(&sspi->done);
|
||||
sspi->tx_buf = tfr->tx_buf;
|
||||
sspi->rx_buf = tfr->rx_buf;
|
||||
sspi->len = tfr->len;
|
||||
@@ -346,21 +367,9 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||
sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg | SUN6I_TFR_CTL_XCH);
|
||||
|
||||
- tx_time = max(tfr->len * 8 * 2 / (tfr->speed_hz / 1000), 100U);
|
||||
- start = jiffies;
|
||||
- timeout = wait_for_completion_timeout(&sspi->done,
|
||||
- msecs_to_jiffies(tx_time));
|
||||
- end = jiffies;
|
||||
- if (!timeout) {
|
||||
- dev_warn(&master->dev,
|
||||
- "%s: timeout transferring %u bytes@%iHz for %i(%i)ms",
|
||||
- dev_name(&spi->dev), tfr->len, tfr->speed_hz,
|
||||
- jiffies_to_msecs(end - start), tx_time);
|
||||
- ret = -ETIMEDOUT;
|
||||
- goto out;
|
||||
- }
|
||||
+ /* Wait for completion */
|
||||
+ ret = sun6i_spi_wait_for_transfer(spi, tfr);
|
||||
|
||||
-out:
|
||||
sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, 0);
|
||||
|
||||
return ret;
|
||||
@@ -368,7 +377,8 @@ out:
|
||||
|
||||
static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
|
||||
{
|
||||
- struct sun6i_spi *sspi = dev_id;
|
||||
+ struct spi_master *master = dev_id;
|
||||
+ struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||
u32 status;
|
||||
|
||||
status = sun6i_spi_read(sspi, SUN6I_INT_STA_REG);
|
||||
@@ -377,7 +387,7 @@ static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
|
||||
if (status & SUN6I_INT_CTL_TC) {
|
||||
sun6i_spi_write(sspi, SUN6I_INT_STA_REG, SUN6I_INT_CTL_TC);
|
||||
sun6i_spi_drain_fifo(sspi, sspi->fifo_depth);
|
||||
- complete(&sspi->done);
|
||||
+ spi_finalize_current_transfer(master);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
@@ -476,7 +486,7 @@ static int sun6i_spi_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, sun6i_spi_handler,
|
||||
- 0, dev_name(&pdev->dev), sspi);
|
||||
+ 0, dev_name(&pdev->dev), master);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Cannot request IRQ\n");
|
||||
goto err_free_master;
|
||||
@@ -498,8 +508,6 @@ static int sun6i_spi_probe(struct platform_device *pdev)
|
||||
goto err_free_master;
|
||||
}
|
||||
|
||||
- init_completion(&sspi->done);
|
||||
-
|
||||
sspi->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
|
||||
if (IS_ERR(sspi->rstc)) {
|
||||
dev_err(&pdev->dev, "Couldn't get reset controller\n");
|
||||
@ -0,0 +1,82 @@
|
||||
diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
|
||||
index 210cef9..18f9344 100644
|
||||
--- a/drivers/spi/spi-sun6i.c
|
||||
+++ b/drivers/spi/spi-sun6i.c
|
||||
@@ -115,29 +115,29 @@ static inline void sun6i_spi_write(struct sun6i_spi *sspi, u32 reg, u32 value)
|
||||
writel(value, sspi->base_addr + reg);
|
||||
}
|
||||
|
||||
-static inline u32 sun6i_spi_get_tx_fifo_count(struct sun6i_spi *sspi)
|
||||
+static inline void sun6i_spi_set(struct sun6i_spi *sspi, u32 addr, u32 val)
|
||||
{
|
||||
- u32 reg = sun6i_spi_read(sspi, SUN6I_FIFO_STA_REG);
|
||||
-
|
||||
- reg >>= SUN6I_FIFO_STA_TF_CNT_BITS;
|
||||
+ u32 reg = sun6i_spi_read(sspi, addr);
|
||||
|
||||
- return reg & SUN6I_FIFO_STA_TF_CNT_MASK;
|
||||
+ reg |= val;
|
||||
+ sun6i_spi_write(sspi, addr, reg);
|
||||
}
|
||||
|
||||
-static inline void sun6i_spi_enable_interrupt(struct sun6i_spi *sspi, u32 mask)
|
||||
+static inline void sun6i_spi_unset(struct sun6i_spi *sspi, u32 addr, u32 val)
|
||||
{
|
||||
- u32 reg = sun6i_spi_read(sspi, SUN6I_INT_CTL_REG);
|
||||
+ u32 reg = sun6i_spi_read(sspi, addr);
|
||||
|
||||
- reg |= mask;
|
||||
- sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, reg);
|
||||
+ reg &= ~val;
|
||||
+ sun6i_spi_write(sspi, addr, reg);
|
||||
}
|
||||
|
||||
-static inline void sun6i_spi_disable_interrupt(struct sun6i_spi *sspi, u32 mask)
|
||||
+static inline u32 sun6i_spi_get_tx_fifo_count(struct sun6i_spi *sspi)
|
||||
{
|
||||
- u32 reg = sun6i_spi_read(sspi, SUN6I_INT_CTL_REG);
|
||||
+ u32 reg = sun6i_spi_read(sspi, SUN6I_FIFO_STA_REG);
|
||||
|
||||
- reg &= ~mask;
|
||||
- sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, reg);
|
||||
+ reg >>= SUN6I_FIFO_STA_TF_CNT_BITS;
|
||||
+
|
||||
+ return reg & SUN6I_FIFO_STA_TF_CNT_MASK;
|
||||
}
|
||||
|
||||
static inline void sun6i_spi_drain_fifo(struct sun6i_spi *sspi, int len)
|
||||
@@ -299,18 +299,14 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG,
|
||||
SUN6I_FIFO_CTL_RF_RST | SUN6I_FIFO_CTL_TF_RST);
|
||||
|
||||
-
|
||||
- reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||
/*
|
||||
* If it's a TX only transfer, we don't want to fill the RX
|
||||
* FIFO with bogus data
|
||||
*/
|
||||
if (sspi->rx_buf)
|
||||
- reg &= ~SUN6I_TFR_CTL_DHB;
|
||||
+ sun6i_spi_unset(sspi, SUN6I_TFR_CTL_REG, SUN6I_TFR_CTL_DHB);
|
||||
else
|
||||
- reg |= SUN6I_TFR_CTL_DHB;
|
||||
-
|
||||
- sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
|
||||
+ sun6i_spi_set(sspi, SUN6I_TFR_CTL_REG, SUN6I_TFR_CTL_DHB);
|
||||
|
||||
|
||||
/* Ensure that we have a parent clock fast enough */
|
||||
@@ -361,11 +357,10 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
sun6i_spi_fill_fifo(sspi, sspi->fifo_depth);
|
||||
|
||||
/* Enable transfer complete interrupt */
|
||||
- sun6i_spi_enable_interrupt(sspi, SUN6I_INT_CTL_TC);
|
||||
+ sun6i_spi_set(sspi, SUN6I_INT_CTL_REG, SUN6I_INT_CTL_TC);
|
||||
|
||||
/* Start the transfer */
|
||||
- reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG);
|
||||
- sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg | SUN6I_TFR_CTL_XCH);
|
||||
+ sun6i_spi_set(sspi, SUN6I_TFR_CTL_REG, SUN6I_TFR_CTL_XCH);
|
||||
|
||||
/* Wait for completion */
|
||||
ret = sun6i_spi_wait_for_transfer(spi, tfr);
|
||||
@ -0,0 +1,390 @@
|
||||
diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
|
||||
index 18f9344..5665c84 100644
|
||||
--- a/drivers/spi/spi-sun6i.c
|
||||
+++ b/drivers/spi/spi-sun6i.c
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
+#include <linux/dmaengine.h>
|
||||
+#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
@@ -55,11 +57,14 @@
|
||||
|
||||
#define SUN6I_FIFO_CTL_REG 0x18
|
||||
#define SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_MASK 0xff
|
||||
-#define SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_BITS 0
|
||||
+#define SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_POS 0
|
||||
+#define SUN6I_FIFO_CTL_RF_DRQ_EN BIT(8)
|
||||
#define SUN6I_FIFO_CTL_RF_RST BIT(15)
|
||||
#define SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_MASK 0xff
|
||||
-#define SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_BITS 16
|
||||
+#define SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_POS 16
|
||||
+#define SUN6I_FIFO_CTL_TF_DRQ_EN BIT(24)
|
||||
#define SUN6I_FIFO_CTL_TF_RST BIT(31)
|
||||
+#define SUN6I_FIFO_CTL_DMA_DEDICATE BIT(9)|BIT(25)
|
||||
|
||||
#define SUN6I_FIFO_STA_REG 0x1c
|
||||
#define SUN6I_FIFO_STA_RF_CNT_MASK 0x7f
|
||||
@@ -177,6 +182,15 @@ static inline void sun6i_spi_fill_fifo(struct sun6i_spi *sspi, int len)
|
||||
}
|
||||
}
|
||||
|
||||
+static bool sun6i_spi_can_dma(struct spi_master *master,
|
||||
+ struct spi_device *spi,
|
||||
+ struct spi_transfer *tfr)
|
||||
+{
|
||||
+ struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||
+
|
||||
+ return tfr->len > sspi->fifo_depth;
|
||||
+}
|
||||
+
|
||||
static void sun6i_spi_set_cs(struct spi_device *spi, bool enable)
|
||||
{
|
||||
struct sun6i_spi *sspi = spi_master_get_devdata(spi->master);
|
||||
@@ -208,6 +222,9 @@ static size_t sun6i_spi_max_transfer_size(struct spi_device *spi)
|
||||
struct spi_master *master = spi->master;
|
||||
struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||
|
||||
+ if (master->can_dma)
|
||||
+ return SUN6I_MAX_XFER_SIZE;
|
||||
+
|
||||
return sspi->fifo_depth;
|
||||
}
|
||||
|
||||
@@ -268,15 +285,174 @@ static int sun6i_spi_wait_for_transfer(struct spi_device *spi,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static void sun6i_spi_dma_callback(void *param)
|
||||
+{
|
||||
+ struct spi_master *master = param;
|
||||
+
|
||||
+ dev_dbg(&master->dev, "DMA transfer complete\n");
|
||||
+ spi_finalize_current_transfer(master);
|
||||
+}
|
||||
+
|
||||
+static int sun6i_spi_dmap_prep_tx(struct spi_master *master,
|
||||
+ struct spi_transfer *tfr,
|
||||
+ dma_cookie_t *cookie)
|
||||
+{
|
||||
+ struct dma_async_tx_descriptor *chan_desc = NULL;
|
||||
+
|
||||
+ chan_desc = dmaengine_prep_slave_sg(master->dma_tx,
|
||||
+ tfr->tx_sg.sgl, tfr->tx_sg.nents,
|
||||
+ DMA_TO_DEVICE,
|
||||
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
+ if (!chan_desc) {
|
||||
+ dev_err(&master->dev,
|
||||
+ "Couldn't prepare TX DMA slave\n");
|
||||
+ return -EIO;
|
||||
+ }
|
||||
+
|
||||
+ chan_desc->callback = sun6i_spi_dma_callback;
|
||||
+ chan_desc->callback_param = master;
|
||||
+
|
||||
+ *cookie = dmaengine_submit(chan_desc);
|
||||
+ dma_async_issue_pending(master->dma_tx);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sun6i_spi_dmap_prep_rx(struct spi_master *master,
|
||||
+ struct spi_transfer *tfr,
|
||||
+ dma_cookie_t *cookie)
|
||||
+{
|
||||
+ struct dma_async_tx_descriptor *chan_desc = NULL;
|
||||
+
|
||||
+ chan_desc = dmaengine_prep_slave_sg(master->dma_rx,
|
||||
+ tfr->rx_sg.sgl, tfr->rx_sg.nents,
|
||||
+ DMA_FROM_DEVICE,
|
||||
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
+ if (!chan_desc) {
|
||||
+ dev_err(&master->dev,
|
||||
+ "Couldn't prepare RX DMA slave\n");
|
||||
+ return -EIO;
|
||||
+ }
|
||||
+
|
||||
+ chan_desc->callback = sun6i_spi_dma_callback;
|
||||
+ chan_desc->callback_param = master;
|
||||
+
|
||||
+ *cookie = dmaengine_submit(chan_desc);
|
||||
+ dma_async_issue_pending(master->dma_rx);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int sun6i_spi_transfer_one_dma(struct spi_device *spi,
|
||||
+ struct spi_transfer *tfr)
|
||||
+{
|
||||
+ struct spi_master *master = spi->master;
|
||||
+ struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||
+ dma_cookie_t tx_cookie = 0,rx_cookie = 0;
|
||||
+ enum dma_status status;
|
||||
+ int ret;
|
||||
+ u32 reg, trig_level = 0;
|
||||
+
|
||||
+ dev_dbg(&master->dev, "Using DMA mode for transfer\n");
|
||||
+
|
||||
+ reg = sun6i_spi_read(sspi, SUN6I_FIFO_CTL_REG);
|
||||
+
|
||||
+ if (sspi->tx_buf) {
|
||||
+ ret = sun6i_spi_dmap_prep_tx(master, tfr, &tx_cookie);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ reg |= SUN6I_FIFO_CTL_TF_DRQ_EN;
|
||||
+
|
||||
+ trig_level = sspi->fifo_depth;
|
||||
+ reg &= ~SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_MASK;
|
||||
+ reg |= (trig_level << SUN6I_FIFO_CTL_TF_ERQ_TRIG_LEVEL_POS);
|
||||
+ }
|
||||
+
|
||||
+ if (sspi->rx_buf) {
|
||||
+ ret = sun6i_spi_dmap_prep_rx(master, tfr, &rx_cookie);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ reg |= SUN6I_FIFO_CTL_RF_DRQ_EN;
|
||||
+
|
||||
+ trig_level = 1;
|
||||
+ reg &= ~SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_MASK;
|
||||
+ reg |= (trig_level << SUN6I_FIFO_CTL_RF_RDY_TRIG_LEVEL_POS);
|
||||
+ }
|
||||
+
|
||||
+ /* Enable Dedicated DMA requests */
|
||||
+ sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG,
|
||||
+ reg | SUN6I_FIFO_CTL_DMA_DEDICATE);
|
||||
+
|
||||
+ /* Start transfer */
|
||||
+ sun6i_spi_set(sspi, SUN6I_TFR_CTL_REG, SUN6I_TFR_CTL_XCH);
|
||||
+
|
||||
+ ret = sun6i_spi_wait_for_transfer(spi, tfr);
|
||||
+ if (ret)
|
||||
+ goto out;
|
||||
+
|
||||
+ if (sspi->tx_buf && (status = dma_async_is_tx_complete(master->dma_tx,
|
||||
+ tx_cookie, NULL, NULL))) {
|
||||
+ dev_warn(&master->dev,
|
||||
+ "DMA returned completion status of: %s\n",
|
||||
+ status == DMA_ERROR ? "error" : "in progress");
|
||||
+ }
|
||||
+ if (sspi->rx_buf && (status = dma_async_is_tx_complete(master->dma_rx,
|
||||
+ rx_cookie, NULL, NULL))) {
|
||||
+ dev_warn(&master->dev,
|
||||
+ "DMA returned completion status of: %s\n",
|
||||
+ status == DMA_ERROR ? "error" : "in progress");
|
||||
+ }
|
||||
+
|
||||
+out:
|
||||
+ if (ret) {
|
||||
+ dev_dbg(&master->dev, "DMA channel teardown\n");
|
||||
+ if (sspi->tx_buf)
|
||||
+ dmaengine_terminate_sync(master->dma_tx);
|
||||
+ if (sspi->rx_buf)
|
||||
+ dmaengine_terminate_sync(master->dma_rx);
|
||||
+ }
|
||||
+
|
||||
+ sun6i_spi_drain_fifo(sspi, sspi->fifo_depth);
|
||||
+
|
||||
+ sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, 0);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int sun6i_spi_transfer_one_pio(struct spi_device *spi,
|
||||
+ struct spi_transfer *tfr)
|
||||
+{
|
||||
+ struct spi_master *master = spi->master;
|
||||
+ struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||
+ int ret;
|
||||
+
|
||||
+ /* Disable DMA requests */
|
||||
+ sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG, 0);
|
||||
+
|
||||
+ sun6i_spi_fill_fifo(sspi, sspi->fifo_depth);
|
||||
+
|
||||
+ /* Enable transfer complete IRQ */
|
||||
+ sun6i_spi_set(sspi, SUN6I_INT_CTL_REG, SUN6I_INT_CTL_TC);
|
||||
+
|
||||
+ /* Start transfer */
|
||||
+ sun6i_spi_set(sspi, SUN6I_TFR_CTL_REG, SUN6I_TFR_CTL_XCH);
|
||||
+
|
||||
+ ret = sun6i_spi_wait_for_transfer(spi, tfr);
|
||||
+
|
||||
+ sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, 0);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *tfr)
|
||||
{
|
||||
struct sun6i_spi *sspi = spi_master_get_devdata(master);
|
||||
- unsigned int mclk_rate, div, timeout;
|
||||
- unsigned int start, end, tx_time;
|
||||
+ unsigned int mclk_rate, div;
|
||||
unsigned int tx_len = 0;
|
||||
- int ret = 0;
|
||||
u32 reg;
|
||||
|
||||
/* A zero length transfer never finishes if programmed
|
||||
@@ -284,10 +460,15 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
if (!tfr->len)
|
||||
return 0;
|
||||
|
||||
- /* Don't support transfer larger than the FIFO */
|
||||
- if (tfr->len > sspi->fifo_depth)
|
||||
+ if (tfr->len > SUN6I_MAX_XFER_SIZE)
|
||||
return -EMSGSIZE;
|
||||
|
||||
+ if (!master->can_dma) {
|
||||
+ /* Don't support transfer larger than the FIFO */
|
||||
+ if (tfr->len > sspi->fifo_depth)
|
||||
+ return -EMSGSIZE;
|
||||
+ }
|
||||
+
|
||||
sspi->tx_buf = tfr->tx_buf;
|
||||
sspi->rx_buf = tfr->rx_buf;
|
||||
sspi->len = tfr->len;
|
||||
@@ -353,21 +534,10 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
|
||||
sun6i_spi_write(sspi, SUN6I_BURST_CTL_CNT_REG,
|
||||
SUN6I_BURST_CTL_CNT_STC(tx_len));
|
||||
|
||||
- /* Fill the TX FIFO */
|
||||
- sun6i_spi_fill_fifo(sspi, sspi->fifo_depth);
|
||||
-
|
||||
- /* Enable transfer complete interrupt */
|
||||
- sun6i_spi_set(sspi, SUN6I_INT_CTL_REG, SUN6I_INT_CTL_TC);
|
||||
-
|
||||
- /* Start the transfer */
|
||||
- sun6i_spi_set(sspi, SUN6I_TFR_CTL_REG, SUN6I_TFR_CTL_XCH);
|
||||
-
|
||||
- /* Wait for completion */
|
||||
- ret = sun6i_spi_wait_for_transfer(spi, tfr);
|
||||
-
|
||||
- sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, 0);
|
||||
+ if (sun6i_spi_can_dma(master, spi, tfr))
|
||||
+ return sun6i_spi_transfer_one_dma(spi, tfr);
|
||||
|
||||
- return ret;
|
||||
+ return sun6i_spi_transfer_one_pio(spi, tfr);
|
||||
}
|
||||
|
||||
static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
|
||||
@@ -389,6 +559,76 @@ static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
+static int sun6i_spi_dma_setup(struct platform_device *pdev,
|
||||
+ struct resource *res)
|
||||
+{
|
||||
+ struct spi_master *master = platform_get_drvdata(pdev);
|
||||
+ struct dma_slave_config dma_sconf;
|
||||
+ int ret;
|
||||
+
|
||||
+ master->dma_tx = dma_request_slave_channel_reason(&pdev->dev, "tx");
|
||||
+ if (IS_ERR(master->dma_tx)) {
|
||||
+ dev_err(&pdev->dev, "Unable to acquire DMA TX channel\n");
|
||||
+ ret = PTR_ERR(master->dma_tx);
|
||||
+ goto out;
|
||||
+ }
|
||||
+
|
||||
+ dma_sconf.direction = DMA_MEM_TO_DEV;
|
||||
+ dma_sconf.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||||
+ dma_sconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||||
+ dma_sconf.dst_addr = res->start + SUN6I_TXDATA_REG;
|
||||
+ dma_sconf.src_maxburst = 1;
|
||||
+ dma_sconf.dst_maxburst = 1;
|
||||
+
|
||||
+ ret = dmaengine_slave_config(master->dma_tx, &dma_sconf);
|
||||
+ if (ret) {
|
||||
+ dev_err(&pdev->dev, "Unable to configure DMA TX slave\n");
|
||||
+ goto err_rel_tx;
|
||||
+ }
|
||||
+
|
||||
+ master->dma_rx = dma_request_slave_channel_reason(&pdev->dev, "rx");
|
||||
+ if (IS_ERR(master->dma_rx)) {
|
||||
+ dev_err(&pdev->dev, "Unable to acquire DMA RX channel\n");
|
||||
+ ret = PTR_ERR(master->dma_rx);
|
||||
+ goto err_rel_tx;
|
||||
+ }
|
||||
+
|
||||
+ dma_sconf.direction = DMA_DEV_TO_MEM;
|
||||
+ dma_sconf.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||||
+ dma_sconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||||
+ dma_sconf.src_addr = res->start + SUN6I_RXDATA_REG;
|
||||
+ dma_sconf.src_maxburst = 1;
|
||||
+ dma_sconf.dst_maxburst = 1;
|
||||
+
|
||||
+ ret = dmaengine_slave_config(master->dma_rx, &dma_sconf);
|
||||
+ if (ret) {
|
||||
+ dev_err(&pdev->dev, "Unable to configure DMA RX slave\n");
|
||||
+ goto err_rel_rx;
|
||||
+ }
|
||||
+
|
||||
+ /* don't set can_dma unless both channels are valid*/
|
||||
+ master->can_dma = sun6i_spi_can_dma;
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+err_rel_rx:
|
||||
+ dma_release_channel(master->dma_rx);
|
||||
+err_rel_tx:
|
||||
+ dma_release_channel(master->dma_tx);
|
||||
+out:
|
||||
+ master->dma_tx = NULL;
|
||||
+ master->dma_rx = NULL;
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static void sun6i_spi_dma_release(struct spi_master *master)
|
||||
+{
|
||||
+ if (master->can_dma) {
|
||||
+ dma_release_channel(master->dma_rx);
|
||||
+ dma_release_channel(master->dma_tx);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static int sun6i_spi_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct spi_master *master = dev_get_drvdata(dev);
|
||||
@@ -510,6 +750,15 @@ static int sun6i_spi_probe(struct platform_device *pdev)
|
||||
goto err_free_master;
|
||||
}
|
||||
|
||||
+ ret = sun6i_spi_dma_setup(pdev, res);
|
||||
+ if (ret) {
|
||||
+ if (ret == -EPROBE_DEFER) {
|
||||
+ /* wait for the dma driver to load */
|
||||
+ goto err_free_master;
|
||||
+ }
|
||||
+ dev_warn(&pdev->dev, "DMA transfer not supported\n");
|
||||
+ }
|
||||
+
|
||||
/*
|
||||
* This wake-up/shutdown pattern is to be able to have the
|
||||
* device woken up, even if runtime_pm is disabled
|
||||
@@ -536,14 +785,19 @@ err_pm_disable:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
sun6i_spi_runtime_suspend(&pdev->dev);
|
||||
err_free_master:
|
||||
+ sun6i_spi_dma_release(master);
|
||||
spi_master_put(master);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sun6i_spi_remove(struct platform_device *pdev)
|
||||
{
|
||||
+ struct spi_master *master = platform_get_drvdata(pdev);
|
||||
+
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
+ sun6i_spi_dma_release(master);
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user