2018-11-21 13:07:01

by Radu Pirea

[permalink] [raw]
Subject: [PATCH 0/3] Add PM and DMA support for AT91 USART as SPI

Hi,

This patch series improves the SPI driver for AT91 USART IP and
adds support for DMA transfers, updates the bindings of driver
and PM callback functions.

I decided to use DMA only for transfers long than 16 bytes. DMA
setup introduces a little overhead and is better to use PIO for
short transfers.

Radu Pirea (3):
spi: at91-usart: add power management support
dt-bindings: mfd: atmel-usart: add DMA bindings for SPI mode
spi: at91-usart: add DMA support

.../devicetree/bindings/mfd/atmel-usart.txt | 15 +-
drivers/spi/spi-at91-usart.c | 284 +++++++++++++++++-
2 files changed, 292 insertions(+), 7 deletions(-)

--
2.19.1



2018-11-21 11:48:12

by Radu Pirea

[permalink] [raw]
Subject: [PATCH 3/3] spi: at91-usart: add DMA support

This patch adds support for DMA. Transfers are done with dma only if
they are longer than 16 bytes in order to achieve a better performance.
DMA setup introduces a little overhead and for transfers shorter than 16
bytes there is no performance improvement.

Signed-off-by: Radu Pirea <[email protected]>
---
drivers/spi/spi-at91-usart.c | 223 ++++++++++++++++++++++++++++++++++-
1 file changed, 221 insertions(+), 2 deletions(-)

diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c
index 0b07c746453d..4d908afeaec9 100644
--- a/drivers/spi/spi-at91-usart.c
+++ b/drivers/spi/spi-at91-usart.c
@@ -8,9 +8,12 @@

#include <linux/clk.h>
#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-direction.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
@@ -58,6 +61,8 @@

#define US_INIT \
(US_MR_SPI_MASTER | US_MR_CHRL | US_MR_CLKO | US_MR_WRDBT)
+#define US_DMA_MIN_BYTES 16
+#define US_DMA_TIMEOUT (msecs_to_jiffies(1000))

/* Register access macros */
#define at91_usart_spi_readl(port, reg) \
@@ -71,14 +76,19 @@
writeb_relaxed((value), (port)->regs + US_##reg)

struct at91_usart_spi {
+ struct platform_device *mpdev;
struct spi_transfer *current_transfer;
void __iomem *regs;
struct device *dev;
struct clk *clk;

+ struct completion xfer_completion;
+
/*used in interrupt to protect data reading*/
spinlock_t lock;

+ phys_addr_t phybase;
+
int irq;
unsigned int current_tx_remaining_bytes;
unsigned int current_rx_remaining_bytes;
@@ -87,8 +97,184 @@ struct at91_usart_spi {
u32 status;

bool xfer_failed;
+ bool use_dma;
};

+static void dma_callback(void *data)
+{
+ struct spi_controller *ctlr = data;
+ struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
+
+ at91_usart_spi_writel(aus, IER, US_IR_RXRDY);
+ aus->current_rx_remaining_bytes = 0;
+ complete(&aus->xfer_completion);
+}
+
+static bool at91_usart_spi_can_dma(struct spi_controller *ctrl,
+ struct spi_device *spi,
+ struct spi_transfer *xfer)
+{
+ struct at91_usart_spi *aus = spi_master_get_devdata(ctrl);
+
+ return aus->use_dma && xfer->len >= US_DMA_MIN_BYTES;
+}
+
+static int at91_usart_spi_configure_dma(struct spi_controller *ctlr,
+ struct at91_usart_spi *aus)
+{
+ struct dma_slave_config slave_config;
+ struct device *dev = &aus->mpdev->dev;
+ phys_addr_t phybase = aus->phybase;
+ dma_cap_mask_t mask;
+ int err = 0;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ ctlr->dma_tx = dma_request_slave_channel_reason(dev, "tx");
+ if (IS_ERR_OR_NULL(ctlr->dma_tx)) {
+ if (IS_ERR(ctlr->dma_tx)) {
+ err = PTR_ERR(ctlr->dma_tx);
+ goto at91_usart_spi_error_clear;
+ }
+
+ dev_dbg(dev,
+ "DMA TX channel not available, SPI unable to use DMA\n");
+ err = -EBUSY;
+ goto at91_usart_spi_error_clear;
+ }
+
+ ctlr->dma_rx = dma_request_slave_channel_reason(dev, "rx");
+ if (IS_ERR_OR_NULL(ctlr->dma_rx)) {
+ if (IS_ERR(ctlr->dma_rx)) {
+ err = PTR_ERR(ctlr->dma_rx);
+ goto at91_usart_spi_error;
+ }
+
+ dev_dbg(dev,
+ "DMA RX channel not available, SPI unable to use DMA\n");
+ err = -EBUSY;
+ goto at91_usart_spi_error;
+ }
+
+ slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ slave_config.dst_addr = (dma_addr_t)phybase + US_THR;
+ slave_config.src_addr = (dma_addr_t)phybase + US_RHR;
+ slave_config.src_maxburst = 1;
+ slave_config.dst_maxburst = 1;
+ slave_config.device_fc = false;
+
+ slave_config.direction = DMA_DEV_TO_MEM;
+ if (dmaengine_slave_config(ctlr->dma_rx, &slave_config)) {
+ dev_err(&ctlr->dev,
+ "failed to configure rx dma channel\n");
+ err = -EINVAL;
+ goto at91_usart_spi_error;
+ }
+
+ slave_config.direction = DMA_MEM_TO_DEV;
+ if (dmaengine_slave_config(ctlr->dma_tx, &slave_config)) {
+ dev_err(&ctlr->dev,
+ "failed to configure tx dma channel\n");
+ err = -EINVAL;
+ goto at91_usart_spi_error;
+ }
+
+ aus->use_dma = true;
+ return 0;
+
+at91_usart_spi_error:
+ if (!IS_ERR_OR_NULL(ctlr->dma_tx))
+ dma_release_channel(ctlr->dma_tx);
+ if (!IS_ERR_OR_NULL(ctlr->dma_rx))
+ dma_release_channel(ctlr->dma_rx);
+ ctlr->dma_tx = NULL;
+ ctlr->dma_rx = NULL;
+
+at91_usart_spi_error_clear:
+ return err;
+}
+
+static void at91_usart_spi_release_dma(struct spi_controller *ctlr)
+{
+ if (ctlr->dma_rx)
+ dma_release_channel(ctlr->dma_rx);
+ if (ctlr->dma_tx)
+ dma_release_channel(ctlr->dma_tx);
+}
+
+static void at91_usart_spi_stop_dma(struct spi_controller *ctlr)
+{
+ if (ctlr->dma_rx)
+ dmaengine_terminate_all(ctlr->dma_rx);
+ if (ctlr->dma_tx)
+ dmaengine_terminate_all(ctlr->dma_tx);
+}
+
+static int at91_usart_spi_dma_transfer(struct spi_controller *ctlr,
+ struct spi_transfer *xfer)
+{
+ struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
+ struct dma_chan *rxchan = ctlr->dma_rx;
+ struct dma_chan *txchan = ctlr->dma_tx;
+ struct dma_async_tx_descriptor *rxdesc;
+ struct dma_async_tx_descriptor *txdesc;
+ dma_cookie_t cookie;
+
+ rxdesc = dmaengine_prep_slave_sg(rxchan,
+ xfer->rx_sg.sgl,
+ xfer->rx_sg.nents,
+ DMA_FROM_DEVICE,
+ DMA_PREP_INTERRUPT |
+ DMA_CTRL_ACK);
+ if (!rxdesc)
+ goto at91_usart_spi_err_dma;
+
+ txdesc = dmaengine_prep_slave_sg(txchan,
+ xfer->tx_sg.sgl,
+ xfer->tx_sg.nents,
+ DMA_TO_DEVICE,
+ DMA_PREP_INTERRUPT |
+ DMA_CTRL_ACK);
+ if (!txdesc)
+ goto at91_usart_spi_err_dma;
+
+ /* Disable RX interrupt */
+ at91_usart_spi_writel(aus, IDR, US_IR_RXRDY);
+
+ rxdesc->callback = dma_callback;
+ rxdesc->callback_param = ctlr;
+
+ cookie = rxdesc->tx_submit(rxdesc);
+ if (dma_submit_error(cookie))
+ goto at91_usart_spi_err_dma;
+
+ cookie = txdesc->tx_submit(txdesc);
+ if (dma_submit_error(cookie))
+ goto at91_usart_spi_err_dma;
+
+ rxchan->device->device_issue_pending(rxchan);
+ txchan->device->device_issue_pending(txchan);
+
+ return 0;
+
+at91_usart_spi_err_dma:
+ /* Enable RX interrupt if submission of any of descriptors fails
+ * and fallback to PIO
+ */
+ at91_usart_spi_writel(aus, IER, US_IR_RXRDY);
+ at91_usart_spi_stop_dma(ctlr);
+
+ return -ENOMEM;
+}
+
+static unsigned long at91_usart_spi_dma_timeout(struct at91_usart_spi *aus)
+{
+ return wait_for_completion_timeout(&aus->xfer_completion,
+ US_DMA_TIMEOUT);
+}
+
static inline u32 at91_usart_spi_tx_ready(struct at91_usart_spi *aus)
{
return aus->status & US_IR_TXRDY;
@@ -221,6 +407,8 @@ static int at91_usart_spi_transfer_one(struct spi_controller *ctlr,
struct spi_transfer *xfer)
{
struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
+ unsigned long dma_timeout = 0;
+ int ret = 0;

at91_usart_spi_set_xfer_speed(aus, xfer);
aus->xfer_failed = false;
@@ -230,8 +418,25 @@ static int at91_usart_spi_transfer_one(struct spi_controller *ctlr,

while ((aus->current_tx_remaining_bytes ||
aus->current_rx_remaining_bytes) && !aus->xfer_failed) {
- at91_usart_spi_read_status(aus);
- at91_usart_spi_tx(aus);
+ reinit_completion(&aus->xfer_completion);
+ if (at91_usart_spi_can_dma(ctlr, spi, xfer) &&
+ !ret) {
+ ret = at91_usart_spi_dma_transfer(ctlr, xfer);
+ if (ret)
+ continue;
+
+ dma_timeout = at91_usart_spi_dma_timeout(aus);
+
+ if (WARN_ON(dma_timeout == 0)) {
+ dev_err(&spi->dev, "DMA transfer timeout\n");
+ return -EIO;
+ }
+ aus->current_tx_remaining_bytes = 0;
+ } else {
+ at91_usart_spi_read_status(aus);
+ at91_usart_spi_tx(aus);
+ }
+
cpu_relax();
}

@@ -350,6 +555,7 @@ static int at91_usart_spi_probe(struct platform_device *pdev)
controller->transfer_one = at91_usart_spi_transfer_one;
controller->prepare_message = at91_usart_spi_prepare_message;
controller->unprepare_message = at91_usart_spi_unprepare_message;
+ controller->can_dma = at91_usart_spi_can_dma;
controller->cleanup = at91_usart_spi_cleanup;
controller->max_speed_hz = DIV_ROUND_UP(clk_get_rate(clk),
US_MIN_CLK_DIV);
@@ -381,7 +587,17 @@ static int at91_usart_spi_probe(struct platform_device *pdev)
aus->spi_clk = clk_get_rate(clk);
at91_usart_spi_init(aus);

+ aus->phybase = regs->start;
+
+ aus->mpdev = to_platform_device(pdev->dev.parent);
+
+ ret = at91_usart_spi_configure_dma(controller, aus);
+ if (ret)
+ goto at91_usart_fail_dma;
+
spin_lock_init(&aus->lock);
+ init_completion(&aus->xfer_completion);
+
ret = devm_spi_register_master(&pdev->dev, controller);
if (ret)
goto at91_usart_fail_register_master;
@@ -394,6 +610,8 @@ static int at91_usart_spi_probe(struct platform_device *pdev)
return 0;

at91_usart_fail_register_master:
+ at91_usart_spi_release_dma(controller);
+at91_usart_fail_dma:
clk_disable_unprepare(clk);
at91_usart_spi_probe_fail:
spi_master_put(controller);
@@ -458,6 +676,7 @@ static int at91_usart_spi_remove(struct platform_device *pdev)
struct spi_controller *ctlr = platform_get_drvdata(pdev);
struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);

+ at91_usart_spi_release_dma(ctlr);
clk_disable_unprepare(aus->clk);

return 0;
--
2.19.1


2018-11-21 13:07:04

by Radu Pirea

[permalink] [raw]
Subject: [PATCH 1/3] spi: at91-usart: add power management support

This patch implements power management callback function for USART as
SPI driver.

Signed-off-by: Radu Pirea <[email protected]>
---
drivers/spi/spi-at91-usart.c | 61 ++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)

diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c
index a924657642fa..0b07c746453d 100644
--- a/drivers/spi/spi-at91-usart.c
+++ b/drivers/spi/spi-at91-usart.c
@@ -13,6 +13,7 @@
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>

#include <linux/spi/spi.h>

@@ -399,6 +400,59 @@ static int at91_usart_spi_probe(struct platform_device *pdev)
return ret;
}

+__maybe_unused static int at91_usart_spi_runtime_suspend(struct device *dev)
+{
+ struct spi_controller *ctlr = dev_get_drvdata(dev);
+ struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
+
+ clk_disable_unprepare(aus->clk);
+ pinctrl_pm_select_sleep_state(dev);
+
+ return 0;
+}
+
+__maybe_unused static int at91_usart_spi_runtime_resume(struct device *dev)
+{
+ struct spi_controller *ctrl = dev_get_drvdata(dev);
+ struct at91_usart_spi *aus = spi_master_get_devdata(ctrl);
+
+ pinctrl_pm_select_default_state(dev);
+
+ return clk_prepare_enable(aus->clk);
+}
+
+__maybe_unused static int at91_usart_spi_suspend(struct device *dev)
+{
+ struct spi_controller *ctrl = dev_get_drvdata(dev);
+ int ret;
+
+ ret = spi_controller_suspend(ctrl);
+ if (ret)
+ return ret;
+
+ if (!pm_runtime_suspended(dev))
+ at91_usart_spi_runtime_suspend(dev);
+
+ return 0;
+}
+
+__maybe_unused static int at91_usart_spi_resume(struct device *dev)
+{
+ struct spi_controller *ctrl = dev_get_drvdata(dev);
+ struct at91_usart_spi *aus = spi_master_get_devdata(ctrl);
+ int ret;
+
+ if (!pm_runtime_suspended(dev)) {
+ ret = at91_usart_spi_runtime_resume(dev);
+ if (ret)
+ return ret;
+ }
+
+ at91_usart_spi_init(aus);
+
+ return spi_controller_resume(ctrl);
+}
+
static int at91_usart_spi_remove(struct platform_device *pdev)
{
struct spi_controller *ctlr = platform_get_drvdata(pdev);
@@ -409,6 +463,12 @@ static int at91_usart_spi_remove(struct platform_device *pdev)
return 0;
}

+static const struct dev_pm_ops at91_usart_spi_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(at91_usart_spi_suspend, at91_usart_spi_resume)
+ SET_RUNTIME_PM_OPS(at91_usart_spi_runtime_suspend,
+ at91_usart_spi_runtime_resume, NULL)
+};
+
static const struct of_device_id at91_usart_spi_dt_ids[] = {
{ .compatible = "microchip,at91sam9g45-usart-spi"},
{ /* sentinel */}
@@ -419,6 +479,7 @@ MODULE_DEVICE_TABLE(of, at91_usart_spi_dt_ids);
static struct platform_driver at91_usart_spi_driver = {
.driver = {
.name = "at91_usart_spi",
+ .pm = &at91_usart_spi_pm_ops,
},
.probe = at91_usart_spi_probe,
.remove = at91_usart_spi_remove,
--
2.19.1


2018-11-21 13:07:10

by Radu Pirea

[permalink] [raw]
Subject: [PATCH 2/3] dt-bindings: mfd: atmel-usart: add DMA bindings for SPI mode

The bindings for DMA are now common for both drivers of the USART
IP.

The node given as an example for USART in SPI mode has been updated in
order to include DMA bindings.

Signed-off-by: Radu Pirea <[email protected]>
---
.../devicetree/bindings/mfd/atmel-usart.txt | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/Documentation/devicetree/bindings/mfd/atmel-usart.txt b/Documentation/devicetree/bindings/mfd/atmel-usart.txt
index 7f0cd72f47d2..8ad008175343 100644
--- a/Documentation/devicetree/bindings/mfd/atmel-usart.txt
+++ b/Documentation/devicetree/bindings/mfd/atmel-usart.txt
@@ -17,17 +17,19 @@ Required properties for USART in SPI mode:
- cs-gpios: chipselects (internal cs not supported)
- atmel,usart-mode : Must be <AT91_USART_MODE_SPI> (found in dt-bindings/mfd/at91-usart.h)

+Optional properties in serial and SPI mode:
+- dma bindings for dma transfer:
+ - dmas: DMA specifier, consisting of a phandle to DMA controller node,
+ memory peripheral interface and USART DMA channel ID, FIFO configuration.
+ Refer to dma.txt and atmel-dma.txt for details.
+ - dma-names: "rx" for RX channel, "tx" for TX channel.
+
Optional properties in serial mode:
- atmel,use-dma-rx: use of PDC or DMA for receiving data
- atmel,use-dma-tx: use of PDC or DMA for transmitting data
- {rts,cts,dtr,dsr,rng,dcd}-gpios: specify a GPIO for RTS/CTS/DTR/DSR/RI/DCD line respectively.
It will use specified PIO instead of the peripheral function pin for the USART feature.
If unsure, don't specify this property.
-- add dma bindings for dma transfer:
- - dmas: DMA specifier, consisting of a phandle to DMA controller node,
- memory peripheral interface and USART DMA channel ID, FIFO configuration.
- Refer to dma.txt and atmel-dma.txt for details.
- - dma-names: "rx" for RX channel, "tx" for TX channel.
- atmel,fifo-size: maximum number of data the RX and TX FIFOs can store for FIFO
capable USARTs.
- rs485-rts-delay, rs485-rx-during-tx, linux,rs485-enabled-at-boot-time: see rs485.txt
@@ -81,5 +83,8 @@ Example:
interrupts = <12 IRQ_TYPE_LEVEL_HIGH 5>;
clocks = <&usart0_clk>;
clock-names = "usart";
+ dmas = <&dma0 2 AT91_DMA_CFG_PER_ID(3)>,
+ <&dma0 2 (AT91_DMA_CFG_PER_ID(4) | AT91_DMA_CFG_FIFOCFG_ASAP)>;
+ dma-names = "tx", "rx";
cs-gpios = <&pioB 3 0>;
};
--
2.19.1


2018-11-21 18:34:24

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 2/3] dt-bindings: mfd: atmel-usart: add DMA bindings for SPI mode

On Wed, Nov 21, 2018 at 5:29 AM Radu Pirea <[email protected]> wrote:
>
> The bindings for DMA are now common for both drivers of the USART
> IP.
>
> The node given as an example for USART in SPI mode has been updated in
> order to include DMA bindings.
>
> Signed-off-by: Radu Pirea <[email protected]>
> ---
> .../devicetree/bindings/mfd/atmel-usart.txt | 15 ++++++++++-----
> 1 file changed, 10 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/mfd/atmel-usart.txt b/Documentation/devicetree/bindings/mfd/atmel-usart.txt
> index 7f0cd72f47d2..8ad008175343 100644
> --- a/Documentation/devicetree/bindings/mfd/atmel-usart.txt
> +++ b/Documentation/devicetree/bindings/mfd/atmel-usart.txt
> @@ -17,17 +17,19 @@ Required properties for USART in SPI mode:
> - cs-gpios: chipselects (internal cs not supported)
> - atmel,usart-mode : Must be <AT91_USART_MODE_SPI> (found in dt-bindings/mfd/at91-usart.h)
>
> +Optional properties in serial and SPI mode:
> +- dma bindings for dma transfer:
> + - dmas: DMA specifier, consisting of a phandle to DMA controller node,
> + memory peripheral interface and USART DMA channel ID, FIFO configuration.
> + Refer to dma.txt and atmel-dma.txt for details.
> + - dma-names: "rx" for RX channel, "tx" for TX channel.

> + dma-names = "tx", "rx";

The dma-names should have a defined order.

Rob

2018-11-21 18:35:38

by Alexandre Belloni

[permalink] [raw]
Subject: Re: [PATCH 2/3] dt-bindings: mfd: atmel-usart: add DMA bindings for SPI mode

Hi Rob,

On 21/11/2018 10:41:01-0600, Rob Herring wrote:
> > +Optional properties in serial and SPI mode:
> > +- dma bindings for dma transfer:
> > + - dmas: DMA specifier, consisting of a phandle to DMA controller node,
> > + memory peripheral interface and USART DMA channel ID, FIFO configuration.
> > + Refer to dma.txt and atmel-dma.txt for details.
> > + - dma-names: "rx" for RX channel, "tx" for TX channel.
>
> > + dma-names = "tx", "rx";
>
> The dma-names should have a defined order.
>

Why is that? Isn't the purpose of names to get rid of any particular
ordering?


--
Alexandre Belloni, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

2018-11-21 19:10:45

by Robin Murphy

[permalink] [raw]
Subject: Re: [PATCH 3/3] spi: at91-usart: add DMA support

On 21/11/2018 11:27, Radu Pirea wrote:
> This patch adds support for DMA. Transfers are done with dma only if
> they are longer than 16 bytes in order to achieve a better performance.
> DMA setup introduces a little overhead and for transfers shorter than 16
> bytes there is no performance improvement.
>
> Signed-off-by: Radu Pirea <[email protected]>
> ---
> drivers/spi/spi-at91-usart.c | 223 ++++++++++++++++++++++++++++++++++-
> 1 file changed, 221 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c
> index 0b07c746453d..4d908afeaec9 100644
> --- a/drivers/spi/spi-at91-usart.c
> +++ b/drivers/spi/spi-at91-usart.c
> @@ -8,9 +8,12 @@
>
> #include <linux/clk.h>
> #include <linux/delay.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dma-direction.h>

It looks rather odd to include this from a driver that isn't otherwise
touching anything from linux/dma-mapping.h.

> #include <linux/interrupt.h>
> #include <linux/kernel.h>
> #include <linux/module.h>
> +#include <linux/of_platform.h>
> #include <linux/of_gpio.h>
> #include <linux/platform_device.h>
> #include <linux/pm_runtime.h>
> @@ -58,6 +61,8 @@
>
> #define US_INIT \
> (US_MR_SPI_MASTER | US_MR_CHRL | US_MR_CLKO | US_MR_WRDBT)
> +#define US_DMA_MIN_BYTES 16
> +#define US_DMA_TIMEOUT (msecs_to_jiffies(1000))
>
> /* Register access macros */
> #define at91_usart_spi_readl(port, reg) \
> @@ -71,14 +76,19 @@
> writeb_relaxed((value), (port)->regs + US_##reg)
>
> struct at91_usart_spi {
> + struct platform_device *mpdev;
> struct spi_transfer *current_transfer;
> void __iomem *regs;
> struct device *dev;
> struct clk *clk;
>
> + struct completion xfer_completion;
> +
> /*used in interrupt to protect data reading*/
> spinlock_t lock;
>
> + phys_addr_t phybase;
> +
> int irq;
> unsigned int current_tx_remaining_bytes;
> unsigned int current_rx_remaining_bytes;
> @@ -87,8 +97,184 @@ struct at91_usart_spi {
> u32 status;
>
> bool xfer_failed;
> + bool use_dma;
> };
>
> +static void dma_callback(void *data)
> +{
> + struct spi_controller *ctlr = data;
> + struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
> +
> + at91_usart_spi_writel(aus, IER, US_IR_RXRDY);
> + aus->current_rx_remaining_bytes = 0;
> + complete(&aus->xfer_completion);
> +}
> +
> +static bool at91_usart_spi_can_dma(struct spi_controller *ctrl,
> + struct spi_device *spi,
> + struct spi_transfer *xfer)
> +{
> + struct at91_usart_spi *aus = spi_master_get_devdata(ctrl);
> +
> + return aus->use_dma && xfer->len >= US_DMA_MIN_BYTES;
> +}
> +
> +static int at91_usart_spi_configure_dma(struct spi_controller *ctlr,
> + struct at91_usart_spi *aus)
> +{
> + struct dma_slave_config slave_config;
> + struct device *dev = &aus->mpdev->dev;
> + phys_addr_t phybase = aus->phybase;
> + dma_cap_mask_t mask;
> + int err = 0;
> +
> + dma_cap_zero(mask);
> + dma_cap_set(DMA_SLAVE, mask);
> +
> + ctlr->dma_tx = dma_request_slave_channel_reason(dev, "tx");
> + if (IS_ERR_OR_NULL(ctlr->dma_tx)) {
> + if (IS_ERR(ctlr->dma_tx)) {
> + err = PTR_ERR(ctlr->dma_tx);
> + goto at91_usart_spi_error_clear;
> + }
> +
> + dev_dbg(dev,
> + "DMA TX channel not available, SPI unable to use DMA\n");
> + err = -EBUSY;
> + goto at91_usart_spi_error_clear;
> + }
> +
> + ctlr->dma_rx = dma_request_slave_channel_reason(dev, "rx");
> + if (IS_ERR_OR_NULL(ctlr->dma_rx)) {
> + if (IS_ERR(ctlr->dma_rx)) {
> + err = PTR_ERR(ctlr->dma_rx);
> + goto at91_usart_spi_error;
> + }
> +
> + dev_dbg(dev,
> + "DMA RX channel not available, SPI unable to use DMA\n");
> + err = -EBUSY;
> + goto at91_usart_spi_error;
> + }
> +
> + slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
> + slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
> + slave_config.dst_addr = (dma_addr_t)phybase + US_THR;
> + slave_config.src_addr = (dma_addr_t)phybase + US_RHR;
> + slave_config.src_maxburst = 1;
> + slave_config.dst_maxburst = 1;
> + slave_config.device_fc = false;
> +
> + slave_config.direction = DMA_DEV_TO_MEM;
> + if (dmaengine_slave_config(ctlr->dma_rx, &slave_config)) {
> + dev_err(&ctlr->dev,
> + "failed to configure rx dma channel\n");
> + err = -EINVAL;
> + goto at91_usart_spi_error;
> + }
> +
> + slave_config.direction = DMA_MEM_TO_DEV;
> + if (dmaengine_slave_config(ctlr->dma_tx, &slave_config)) {
> + dev_err(&ctlr->dev,
> + "failed to configure tx dma channel\n");
> + err = -EINVAL;
> + goto at91_usart_spi_error;
> + }
> +
> + aus->use_dma = true;
> + return 0;
> +
> +at91_usart_spi_error:
> + if (!IS_ERR_OR_NULL(ctlr->dma_tx))
> + dma_release_channel(ctlr->dma_tx);
> + if (!IS_ERR_OR_NULL(ctlr->dma_rx))
> + dma_release_channel(ctlr->dma_rx);
> + ctlr->dma_tx = NULL;
> + ctlr->dma_rx = NULL;
> +
> +at91_usart_spi_error_clear:
> + return err;
> +}
> +
> +static void at91_usart_spi_release_dma(struct spi_controller *ctlr)
> +{
> + if (ctlr->dma_rx)
> + dma_release_channel(ctlr->dma_rx);
> + if (ctlr->dma_tx)
> + dma_release_channel(ctlr->dma_tx);
> +}
> +
> +static void at91_usart_spi_stop_dma(struct spi_controller *ctlr)
> +{
> + if (ctlr->dma_rx)
> + dmaengine_terminate_all(ctlr->dma_rx);
> + if (ctlr->dma_tx)
> + dmaengine_terminate_all(ctlr->dma_tx);
> +}
> +
> +static int at91_usart_spi_dma_transfer(struct spi_controller *ctlr,
> + struct spi_transfer *xfer)
> +{
> + struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
> + struct dma_chan *rxchan = ctlr->dma_rx;
> + struct dma_chan *txchan = ctlr->dma_tx;
> + struct dma_async_tx_descriptor *rxdesc;
> + struct dma_async_tx_descriptor *txdesc;
> + dma_cookie_t cookie;
> +
> + rxdesc = dmaengine_prep_slave_sg(rxchan,
> + xfer->rx_sg.sgl,
> + xfer->rx_sg.nents,
> + DMA_FROM_DEVICE,

Ah, this argument should be a dma_transfer_direction, not a
dma_data_direction (confusing I know, but they belong to different
APIs). I assume you mean DMA_DEV_TO_MEM here...

> + DMA_PREP_INTERRUPT |
> + DMA_CTRL_ACK);
> + if (!rxdesc)
> + goto at91_usart_spi_err_dma;
> +
> + txdesc = dmaengine_prep_slave_sg(txchan,
> + xfer->tx_sg.sgl,
> + xfer->tx_sg.nents,
> + DMA_TO_DEVICE,

...and DMA_MEM_TO_DEV here.

Robin.

> + DMA_PREP_INTERRUPT |
> + DMA_CTRL_ACK);
> + if (!txdesc)
> + goto at91_usart_spi_err_dma;
> +
> + /* Disable RX interrupt */
> + at91_usart_spi_writel(aus, IDR, US_IR_RXRDY);
> +
> + rxdesc->callback = dma_callback;
> + rxdesc->callback_param = ctlr;
> +
> + cookie = rxdesc->tx_submit(rxdesc);
> + if (dma_submit_error(cookie))
> + goto at91_usart_spi_err_dma;
> +
> + cookie = txdesc->tx_submit(txdesc);
> + if (dma_submit_error(cookie))
> + goto at91_usart_spi_err_dma;
> +
> + rxchan->device->device_issue_pending(rxchan);
> + txchan->device->device_issue_pending(txchan);
> +
> + return 0;
> +
> +at91_usart_spi_err_dma:
> + /* Enable RX interrupt if submission of any of descriptors fails
> + * and fallback to PIO
> + */
> + at91_usart_spi_writel(aus, IER, US_IR_RXRDY);
> + at91_usart_spi_stop_dma(ctlr);
> +
> + return -ENOMEM;
> +}
> +
> +static unsigned long at91_usart_spi_dma_timeout(struct at91_usart_spi *aus)
> +{
> + return wait_for_completion_timeout(&aus->xfer_completion,
> + US_DMA_TIMEOUT);
> +}
> +
> static inline u32 at91_usart_spi_tx_ready(struct at91_usart_spi *aus)
> {
> return aus->status & US_IR_TXRDY;
> @@ -221,6 +407,8 @@ static int at91_usart_spi_transfer_one(struct spi_controller *ctlr,
> struct spi_transfer *xfer)
> {
> struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
> + unsigned long dma_timeout = 0;
> + int ret = 0;
>
> at91_usart_spi_set_xfer_speed(aus, xfer);
> aus->xfer_failed = false;
> @@ -230,8 +418,25 @@ static int at91_usart_spi_transfer_one(struct spi_controller *ctlr,
>
> while ((aus->current_tx_remaining_bytes ||
> aus->current_rx_remaining_bytes) && !aus->xfer_failed) {
> - at91_usart_spi_read_status(aus);
> - at91_usart_spi_tx(aus);
> + reinit_completion(&aus->xfer_completion);
> + if (at91_usart_spi_can_dma(ctlr, spi, xfer) &&
> + !ret) {
> + ret = at91_usart_spi_dma_transfer(ctlr, xfer);
> + if (ret)
> + continue;
> +
> + dma_timeout = at91_usart_spi_dma_timeout(aus);
> +
> + if (WARN_ON(dma_timeout == 0)) {
> + dev_err(&spi->dev, "DMA transfer timeout\n");
> + return -EIO;
> + }
> + aus->current_tx_remaining_bytes = 0;
> + } else {
> + at91_usart_spi_read_status(aus);
> + at91_usart_spi_tx(aus);
> + }
> +
> cpu_relax();
> }
>
> @@ -350,6 +555,7 @@ static int at91_usart_spi_probe(struct platform_device *pdev)
> controller->transfer_one = at91_usart_spi_transfer_one;
> controller->prepare_message = at91_usart_spi_prepare_message;
> controller->unprepare_message = at91_usart_spi_unprepare_message;
> + controller->can_dma = at91_usart_spi_can_dma;
> controller->cleanup = at91_usart_spi_cleanup;
> controller->max_speed_hz = DIV_ROUND_UP(clk_get_rate(clk),
> US_MIN_CLK_DIV);
> @@ -381,7 +587,17 @@ static int at91_usart_spi_probe(struct platform_device *pdev)
> aus->spi_clk = clk_get_rate(clk);
> at91_usart_spi_init(aus);
>
> + aus->phybase = regs->start;
> +
> + aus->mpdev = to_platform_device(pdev->dev.parent);
> +
> + ret = at91_usart_spi_configure_dma(controller, aus);
> + if (ret)
> + goto at91_usart_fail_dma;
> +
> spin_lock_init(&aus->lock);
> + init_completion(&aus->xfer_completion);
> +
> ret = devm_spi_register_master(&pdev->dev, controller);
> if (ret)
> goto at91_usart_fail_register_master;
> @@ -394,6 +610,8 @@ static int at91_usart_spi_probe(struct platform_device *pdev)
> return 0;
>
> at91_usart_fail_register_master:
> + at91_usart_spi_release_dma(controller);
> +at91_usart_fail_dma:
> clk_disable_unprepare(clk);
> at91_usart_spi_probe_fail:
> spi_master_put(controller);
> @@ -458,6 +676,7 @@ static int at91_usart_spi_remove(struct platform_device *pdev)
> struct spi_controller *ctlr = platform_get_drvdata(pdev);
> struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
>
> + at91_usart_spi_release_dma(ctlr);
> clk_disable_unprepare(aus->clk);
>
> return 0;
>

2018-11-22 07:43:17

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH 2/3] dt-bindings: mfd: atmel-usart: add DMA bindings for SPI mode

On Wed, Nov 21, 2018 at 10:48 AM Alexandre Belloni
<[email protected]> wrote:
>
> Hi Rob,
>
> On 21/11/2018 10:41:01-0600, Rob Herring wrote:
> > > +Optional properties in serial and SPI mode:
> > > +- dma bindings for dma transfer:
> > > + - dmas: DMA specifier, consisting of a phandle to DMA controller node,
> > > + memory peripheral interface and USART DMA channel ID, FIFO configuration.
> > > + Refer to dma.txt and atmel-dma.txt for details.
> > > + - dma-names: "rx" for RX channel, "tx" for TX channel.
> >
> > > + dma-names = "tx", "rx";
> >
> > The dma-names should have a defined order.
> >
>
> Why is that? Isn't the purpose of names to get rid of any particular
> ordering?

Because a fundamental rule of DT is the order is defined and
important. The purpose was to allow for cases that had a variable
number of entries, but even a variable number still have a defined
order.

Rob

2018-11-22 08:23:43

by Alexandre Belloni

[permalink] [raw]
Subject: Re: [PATCH 2/3] dt-bindings: mfd: atmel-usart: add DMA bindings for SPI mode

On 21/11/2018 14:52:29-0600, Rob Herring wrote:
> On Wed, Nov 21, 2018 at 10:48 AM Alexandre Belloni
> <[email protected]> wrote:
> >
> > Hi Rob,
> >
> > On 21/11/2018 10:41:01-0600, Rob Herring wrote:
> > > > +Optional properties in serial and SPI mode:
> > > > +- dma bindings for dma transfer:
> > > > + - dmas: DMA specifier, consisting of a phandle to DMA controller node,
> > > > + memory peripheral interface and USART DMA channel ID, FIFO configuration.
> > > > + Refer to dma.txt and atmel-dma.txt for details.
> > > > + - dma-names: "rx" for RX channel, "tx" for TX channel.
> > >
> > > > + dma-names = "tx", "rx";
> > >
> > > The dma-names should have a defined order.
> > >
> >
> > Why is that? Isn't the purpose of names to get rid of any particular
> > ordering?
>
> Because a fundamental rule of DT is the order is defined and
> important. The purpose was to allow for cases that had a variable
> number of entries, but even a variable number still have a defined
> order.
>

Thank you for the clarification!


--
Alexandre Belloni, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

2018-11-24 08:42:01

by Radu Pirea

[permalink] [raw]
Subject: Re: [PATCH 3/3] spi: at91-usart: add DMA support

On Wed, 2018-11-21 at 17:38 +0000, Robin Murphy wrote:
> On 21/11/2018 11:27, Radu Pirea wrote:
> > This patch adds support for DMA. Transfers are done with dma only
> > if
> > they are longer than 16 bytes in order to achieve a better
> > performance.
> > DMA setup introduces a little overhead and for transfers shorter
> > than 16
> > bytes there is no performance improvement.
> >
> > Signed-off-by: Radu Pirea <[email protected]>
> > ---
> > drivers/spi/spi-at91-usart.c | 223
> > ++++++++++++++++++++++++++++++++++-
> > 1 file changed, 221 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-
> > usart.c
> > index 0b07c746453d..4d908afeaec9 100644
> > --- a/drivers/spi/spi-at91-usart.c
> > +++ b/drivers/spi/spi-at91-usart.c
> > @@ -8,9 +8,12 @@
> >
> > #include <linux/clk.h>
> > #include <linux/delay.h>
> > +#include <linux/dmaengine.h>
> > +#include <linux/dma-direction.h>
>
> It looks rather odd to include this from a driver that isn't
> otherwise
> touching anything from linux/dma-mapping.h.
>
> > #include <linux/interrupt.h>
> > #include <linux/kernel.h>
> > #include <linux/module.h>
> > +#include <linux/of_platform.h>
> > #include <linux/of_gpio.h>
> > #include <linux/platform_device.h>
> > #include <linux/pm_runtime.h>
> > @@ -58,6 +61,8 @@
> >
> > #define US_INIT \
> > (US_MR_SPI_MASTER | US_MR_CHRL | US_MR_CLKO | US_MR_WRDBT)
> > +#define US_DMA_MIN_BYTES 16
> > +#define US_DMA_TIMEOUT (msecs_to_jiffies(1000))
> >
> > /* Register access macros */
> > #define at91_usart_spi_readl(port, reg) \
> > @@ -71,14 +76,19 @@
> > writeb_relaxed((value), (port)->regs + US_##reg)
> >
> > struct at91_usart_spi {
> > + struct platform_device *mpdev;
> > struct spi_transfer *current_transfer;
> > void __iomem *regs;
> > struct device *dev;
> > struct clk *clk;
> >
> > + struct completion xfer_completion;
> > +
> > /*used in interrupt to protect data reading*/
> > spinlock_t lock;
> >
> > + phys_addr_t phybase;
> > +
> > int irq;
> > unsigned int current_tx_remaining_bytes;
> > unsigned int current_rx_remaining_bytes;
> > @@ -87,8 +97,184 @@ struct at91_usart_spi {
> > u32 status;
> >
> > bool xfer_failed;
> > + bool use_dma;
> > };
> >
> > +static void dma_callback(void *data)
> > +{
> > + struct spi_controller *ctlr = data;
> > + struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
> > +
> > + at91_usart_spi_writel(aus, IER, US_IR_RXRDY);
> > + aus->current_rx_remaining_bytes = 0;
> > + complete(&aus->xfer_completion);
> > +}
> > +
> > +static bool at91_usart_spi_can_dma(struct spi_controller *ctrl,
> > + struct spi_device *spi,
> > + struct spi_transfer *xfer)
> > +{
> > + struct at91_usart_spi *aus = spi_master_get_devdata(ctrl);
> > +
> > + return aus->use_dma && xfer->len >= US_DMA_MIN_BYTES;
> > +}
> > +
> > +static int at91_usart_spi_configure_dma(struct spi_controller
> > *ctlr,
> > + struct at91_usart_spi *aus)
> > +{
> > + struct dma_slave_config slave_config;
> > + struct device *dev = &aus->mpdev->dev;
> > + phys_addr_t phybase = aus->phybase;
> > + dma_cap_mask_t mask;
> > + int err = 0;
> > +
> > + dma_cap_zero(mask);
> > + dma_cap_set(DMA_SLAVE, mask);
> > +
> > + ctlr->dma_tx = dma_request_slave_channel_reason(dev, "tx");
> > + if (IS_ERR_OR_NULL(ctlr->dma_tx)) {
> > + if (IS_ERR(ctlr->dma_tx)) {
> > + err = PTR_ERR(ctlr->dma_tx);
> > + goto at91_usart_spi_error_clear;
> > + }
> > +
> > + dev_dbg(dev,
> > + "DMA TX channel not available, SPI unable to
> > use DMA\n");
> > + err = -EBUSY;
> > + goto at91_usart_spi_error_clear;
> > + }
> > +
> > + ctlr->dma_rx = dma_request_slave_channel_reason(dev, "rx");
> > + if (IS_ERR_OR_NULL(ctlr->dma_rx)) {
> > + if (IS_ERR(ctlr->dma_rx)) {
> > + err = PTR_ERR(ctlr->dma_rx);
> > + goto at91_usart_spi_error;
> > + }
> > +
> > + dev_dbg(dev,
> > + "DMA RX channel not available, SPI unable to
> > use DMA\n");
> > + err = -EBUSY;
> > + goto at91_usart_spi_error;
> > + }
> > +
> > + slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
> > + slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
> > + slave_config.dst_addr = (dma_addr_t)phybase + US_THR;
> > + slave_config.src_addr = (dma_addr_t)phybase + US_RHR;
> > + slave_config.src_maxburst = 1;
> > + slave_config.dst_maxburst = 1;
> > + slave_config.device_fc = false;
> > +
> > + slave_config.direction = DMA_DEV_TO_MEM;
> > + if (dmaengine_slave_config(ctlr->dma_rx, &slave_config)) {
> > + dev_err(&ctlr->dev,
> > + "failed to configure rx dma channel\n");
> > + err = -EINVAL;
> > + goto at91_usart_spi_error;
> > + }
> > +
> > + slave_config.direction = DMA_MEM_TO_DEV;
> > + if (dmaengine_slave_config(ctlr->dma_tx, &slave_config)) {
> > + dev_err(&ctlr->dev,
> > + "failed to configure tx dma channel\n");
> > + err = -EINVAL;
> > + goto at91_usart_spi_error;
> > + }
> > +
> > + aus->use_dma = true;
> > + return 0;
> > +
> > +at91_usart_spi_error:
> > + if (!IS_ERR_OR_NULL(ctlr->dma_tx))
> > + dma_release_channel(ctlr->dma_tx);
> > + if (!IS_ERR_OR_NULL(ctlr->dma_rx))
> > + dma_release_channel(ctlr->dma_rx);
> > + ctlr->dma_tx = NULL;
> > + ctlr->dma_rx = NULL;
> > +
> > +at91_usart_spi_error_clear:
> > + return err;
> > +}
> > +
> > +static void at91_usart_spi_release_dma(struct spi_controller
> > *ctlr)
> > +{
> > + if (ctlr->dma_rx)
> > + dma_release_channel(ctlr->dma_rx);
> > + if (ctlr->dma_tx)
> > + dma_release_channel(ctlr->dma_tx);
> > +}
> > +
> > +static void at91_usart_spi_stop_dma(struct spi_controller *ctlr)
> > +{
> > + if (ctlr->dma_rx)
> > + dmaengine_terminate_all(ctlr->dma_rx);
> > + if (ctlr->dma_tx)
> > + dmaengine_terminate_all(ctlr->dma_tx);
> > +}
> > +
> > +static int at91_usart_spi_dma_transfer(struct spi_controller
> > *ctlr,
> > + struct spi_transfer *xfer)
> > +{
> > + struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
> > + struct dma_chan *rxchan = ctlr->dma_rx;
> > + struct dma_chan *txchan = ctlr->dma_tx;
> > + struct dma_async_tx_descriptor *rxdesc;
> > + struct dma_async_tx_descriptor *txdesc;
> > + dma_cookie_t cookie;
> > +
> > + rxdesc = dmaengine_prep_slave_sg(rxchan,
> > + xfer->rx_sg.sgl,
> > + xfer->rx_sg.nents,
> > + DMA_FROM_DEVICE,
>
> Ah, this argument should be a dma_transfer_direction, not a
> dma_data_direction (confusing I know, but they belong to different
> APIs). I assume you mean DMA_DEV_TO_MEM here...

Hi Robin,

I hoped I used the correct values here, but it seems not.

Thanks. I will fix.

>
> > + DMA_PREP_INTERRUPT |
> > + DMA_CTRL_ACK);
> > + if (!rxdesc)
> > + goto at91_usart_spi_err_dma;
> > +
> > + txdesc = dmaengine_prep_slave_sg(txchan,
> > + xfer->tx_sg.sgl,
> > + xfer->tx_sg.nents,
> > + DMA_TO_DEVICE,
>
> ...and DMA_MEM_TO_DEV here.
>
> Robin.
>
> > + DMA_PREP_INTERRUPT |
> > + DMA_CTRL_ACK);
> > + if (!txdesc)
> > + goto at91_usart_spi_err_dma;
> > +
> > + /* Disable RX interrupt */
> > + at91_usart_spi_writel(aus, IDR, US_IR_RXRDY);
> > +
> > + rxdesc->callback = dma_callback;
> > + rxdesc->callback_param = ctlr;
> > +
> > + cookie = rxdesc->tx_submit(rxdesc);
> > + if (dma_submit_error(cookie))
> > + goto at91_usart_spi_err_dma;
> > +
> > + cookie = txdesc->tx_submit(txdesc);
> > + if (dma_submit_error(cookie))
> > + goto at91_usart_spi_err_dma;
> > +
> > + rxchan->device->device_issue_pending(rxchan);
> > + txchan->device->device_issue_pending(txchan);
> > +
> > + return 0;
> > +
> > +at91_usart_spi_err_dma:
> > + /* Enable RX interrupt if submission of any of descriptors
> > fails
> > + * and fallback to PIO
> > + */
> > + at91_usart_spi_writel(aus, IER, US_IR_RXRDY);
> > + at91_usart_spi_stop_dma(ctlr);
> > +
> > + return -ENOMEM;
> > +}
> > +
> > +static unsigned long at91_usart_spi_dma_timeout(struct
> > at91_usart_spi *aus)
> > +{
> > + return wait_for_completion_timeout(&aus->xfer_completion,
> > + US_DMA_TIMEOUT);
> > +}
> > +
> > static inline u32 at91_usart_spi_tx_ready(struct at91_usart_spi
> > *aus)
> > {
> > return aus->status & US_IR_TXRDY;
> > @@ -221,6 +407,8 @@ static int at91_usart_spi_transfer_one(struct
> > spi_controller *ctlr,
> > struct spi_transfer *xfer)
> > {
> > struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
> > + unsigned long dma_timeout = 0;
> > + int ret = 0;
> >
> > at91_usart_spi_set_xfer_speed(aus, xfer);
> > aus->xfer_failed = false;
> > @@ -230,8 +418,25 @@ static int at91_usart_spi_transfer_one(struct
> > spi_controller *ctlr,
> >
> > while ((aus->current_tx_remaining_bytes ||
> > aus->current_rx_remaining_bytes) && !aus->xfer_failed)
> > {
> > - at91_usart_spi_read_status(aus);
> > - at91_usart_spi_tx(aus);
> > + reinit_completion(&aus->xfer_completion);
> > + if (at91_usart_spi_can_dma(ctlr, spi, xfer) &&
> > + !ret) {
> > + ret = at91_usart_spi_dma_transfer(ctlr, xfer);
> > + if (ret)
> > + continue;
> > +
> > + dma_timeout = at91_usart_spi_dma_timeout(aus);
> > +
> > + if (WARN_ON(dma_timeout == 0)) {
> > + dev_err(&spi->dev, "DMA transfer
> > timeout\n");
> > + return -EIO;
> > + }
> > + aus->current_tx_remaining_bytes = 0;
> > + } else {
> > + at91_usart_spi_read_status(aus);
> > + at91_usart_spi_tx(aus);
> > + }
> > +
> > cpu_relax();
> > }
> >
> > @@ -350,6 +555,7 @@ static int at91_usart_spi_probe(struct
> > platform_device *pdev)
> > controller->transfer_one = at91_usart_spi_transfer_one;
> > controller->prepare_message = at91_usart_spi_prepare_message;
> > controller->unprepare_message =
> > at91_usart_spi_unprepare_message;
> > + controller->can_dma = at91_usart_spi_can_dma;
> > controller->cleanup = at91_usart_spi_cleanup;
> > controller->max_speed_hz = DIV_ROUND_UP(clk_get_rate(clk),
> > US_MIN_CLK_DIV);
> > @@ -381,7 +587,17 @@ static int at91_usart_spi_probe(struct
> > platform_device *pdev)
> > aus->spi_clk = clk_get_rate(clk);
> > at91_usart_spi_init(aus);
> >
> > + aus->phybase = regs->start;
> > +
> > + aus->mpdev = to_platform_device(pdev->dev.parent);
> > +
> > + ret = at91_usart_spi_configure_dma(controller, aus);
> > + if (ret)
> > + goto at91_usart_fail_dma;
> > +
> > spin_lock_init(&aus->lock);
> > + init_completion(&aus->xfer_completion);
> > +
> > ret = devm_spi_register_master(&pdev->dev, controller);
> > if (ret)
> > goto at91_usart_fail_register_master;
> > @@ -394,6 +610,8 @@ static int at91_usart_spi_probe(struct
> > platform_device *pdev)
> > return 0;
> >
> > at91_usart_fail_register_master:
> > + at91_usart_spi_release_dma(controller);
> > +at91_usart_fail_dma:
> > clk_disable_unprepare(clk);
> > at91_usart_spi_probe_fail:
> > spi_master_put(controller);
> > @@ -458,6 +676,7 @@ static int at91_usart_spi_remove(struct
> > platform_device *pdev)
> > struct spi_controller *ctlr = platform_get_drvdata(pdev);
> > struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
> >
> > + at91_usart_spi_release_dma(ctlr);
> > clk_disable_unprepare(aus->clk);
> >
> > return 0;
> >


2018-11-24 08:42:46

by Radu Pirea

[permalink] [raw]
Subject: Re: [PATCH 2/3] dt-bindings: mfd: atmel-usart: add DMA bindings for SPI mode

On Wed, 2018-11-21 at 10:41 -0600, Rob Herring wrote:
> On Wed, Nov 21, 2018 at 5:29 AM Radu Pirea <[email protected]
> > wrote:
> > The bindings for DMA are now common for both drivers of the USART
> > IP.
> >
> > The node given as an example for USART in SPI mode has been updated
> > in
> > order to include DMA bindings.
> >
> > Signed-off-by: Radu Pirea <[email protected]>
> > ---
> > .../devicetree/bindings/mfd/atmel-usart.txt | 15 ++++++++++-
> > ----
> > 1 file changed, 10 insertions(+), 5 deletions(-)
> >
> > diff --git a/Documentation/devicetree/bindings/mfd/atmel-usart.txt
> > b/Documentation/devicetree/bindings/mfd/atmel-usart.txt
> > index 7f0cd72f47d2..8ad008175343 100644
> > --- a/Documentation/devicetree/bindings/mfd/atmel-usart.txt
> > +++ b/Documentation/devicetree/bindings/mfd/atmel-usart.txt
> > @@ -17,17 +17,19 @@ Required properties for USART in SPI mode:
> > - cs-gpios: chipselects (internal cs not supported)
> > - atmel,usart-mode : Must be <AT91_USART_MODE_SPI> (found in dt-
> > bindings/mfd/at91-usart.h)
> >
> > +Optional properties in serial and SPI mode:
> > +- dma bindings for dma transfer:
> > + - dmas: DMA specifier, consisting of a phandle to DMA
> > controller node,
> > + memory peripheral interface and USART DMA channel
> > ID, FIFO configuration.
> > + Refer to dma.txt and atmel-dma.txt for details.
> > + - dma-names: "rx" for RX channel, "tx" for TX channel.
> > + dma-names = "tx", "rx";
>
> The dma-names should have a defined order.

Thanks Rob.
I will specify in bindings a fixed order for dmas and dma-names.

>
> Rob


2018-11-28 16:09:00

by Mark Brown

[permalink] [raw]
Subject: Applied "spi: at91-usart: add power management support" to the spi tree

The patch

spi: at91-usart: add power management support

has been applied to the spi tree at

https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 96ed3ecde2c0911ffa11838050a058c2a40742f0 Mon Sep 17 00:00:00 2001
From: Radu Pirea <[email protected]>
Date: Wed, 21 Nov 2018 13:27:30 +0200
Subject: [PATCH] spi: at91-usart: add power management support

This patch implements power management callback function for USART as
SPI driver.

Signed-off-by: Radu Pirea <[email protected]>
Signed-off-by: Mark Brown <[email protected]>
---
drivers/spi/spi-at91-usart.c | 61 ++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)

diff --git a/drivers/spi/spi-at91-usart.c b/drivers/spi/spi-at91-usart.c
index a924657642fa..0b07c746453d 100644
--- a/drivers/spi/spi-at91-usart.c
+++ b/drivers/spi/spi-at91-usart.c
@@ -13,6 +13,7 @@
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>

#include <linux/spi/spi.h>

@@ -399,6 +400,59 @@ static int at91_usart_spi_probe(struct platform_device *pdev)
return ret;
}

+__maybe_unused static int at91_usart_spi_runtime_suspend(struct device *dev)
+{
+ struct spi_controller *ctlr = dev_get_drvdata(dev);
+ struct at91_usart_spi *aus = spi_master_get_devdata(ctlr);
+
+ clk_disable_unprepare(aus->clk);
+ pinctrl_pm_select_sleep_state(dev);
+
+ return 0;
+}
+
+__maybe_unused static int at91_usart_spi_runtime_resume(struct device *dev)
+{
+ struct spi_controller *ctrl = dev_get_drvdata(dev);
+ struct at91_usart_spi *aus = spi_master_get_devdata(ctrl);
+
+ pinctrl_pm_select_default_state(dev);
+
+ return clk_prepare_enable(aus->clk);
+}
+
+__maybe_unused static int at91_usart_spi_suspend(struct device *dev)
+{
+ struct spi_controller *ctrl = dev_get_drvdata(dev);
+ int ret;
+
+ ret = spi_controller_suspend(ctrl);
+ if (ret)
+ return ret;
+
+ if (!pm_runtime_suspended(dev))
+ at91_usart_spi_runtime_suspend(dev);
+
+ return 0;
+}
+
+__maybe_unused static int at91_usart_spi_resume(struct device *dev)
+{
+ struct spi_controller *ctrl = dev_get_drvdata(dev);
+ struct at91_usart_spi *aus = spi_master_get_devdata(ctrl);
+ int ret;
+
+ if (!pm_runtime_suspended(dev)) {
+ ret = at91_usart_spi_runtime_resume(dev);
+ if (ret)
+ return ret;
+ }
+
+ at91_usart_spi_init(aus);
+
+ return spi_controller_resume(ctrl);
+}
+
static int at91_usart_spi_remove(struct platform_device *pdev)
{
struct spi_controller *ctlr = platform_get_drvdata(pdev);
@@ -409,6 +463,12 @@ static int at91_usart_spi_remove(struct platform_device *pdev)
return 0;
}

+static const struct dev_pm_ops at91_usart_spi_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(at91_usart_spi_suspend, at91_usart_spi_resume)
+ SET_RUNTIME_PM_OPS(at91_usart_spi_runtime_suspend,
+ at91_usart_spi_runtime_resume, NULL)
+};
+
static const struct of_device_id at91_usart_spi_dt_ids[] = {
{ .compatible = "microchip,at91sam9g45-usart-spi"},
{ /* sentinel */}
@@ -419,6 +479,7 @@ MODULE_DEVICE_TABLE(of, at91_usart_spi_dt_ids);
static struct platform_driver at91_usart_spi_driver = {
.driver = {
.name = "at91_usart_spi",
+ .pm = &at91_usart_spi_pm_ops,
},
.probe = at91_usart_spi_probe,
.remove = at91_usart_spi_remove,
--
2.19.0.rc2